Compare commits

...

11 Commits

Author SHA1 Message Date
f9e02372b2 feat(about): add team member profile page
This commit introduces a new section to showcase team members, starting with the founder's profile.

- Adds a dynamic page route `/about/` to display individual member profiles.
- Creates a new `about` content collection to source profile information from Markdown files.
- Adds the first profile for 'Xiaomai', including a detailed resume and background image.
- Integrates a 'Teams' dropdown into the main navigation header.
- Implements a `copyToClipboard` utility with a toast notification for sharing profile links.
2025-11-08 13:40:23 +08:00
fb67355a15 docs: overhaul README with detailed project guide
Replace the generic Nuxt starter README with comprehensive documentation specific to the Tootaio Studio website
project.

The new README provides a detailed guide for developers, covering:
- Project features and technology stack (Nuxt 4, Content, UI, i18n).
- Setup, installation, and environment configuration.
- Directory structure and code organization.
- Content management workflow using YAML and zod schemas.
- i18n implementation for UI text.
- UI components, theming, and dark mode.
- SEO and security best practices (CSP, routeRules).
- Contribution guidelines and deployment suggestions.
2025-11-07 14:11:08 +08:00
xiaomai
cc0cb01d28 fix(security): apply security hardening recommendations from audit
This commit implements several security enhancements based on the findings of a new security audit report, which has also been added to the documentation.

- **Security Headers:** Adds a strict Content-Security-Policy (CSP) and other security headers (X-Content-Type-Options, Referrer-Policy) via Nuxt route rules.
- **Production Hardening:** Disables Nuxt DevTools in production environments to reduce the attack surface.
- **Mixed Content:** All image assets are now loaded over HTTPS to resolve mixed content issues.
- **Tabnabbing:** Secures `window.open` calls by adding `noopener,noreferrer`.
- **Configuration:** Updates `.gitignore` to ignore all `.env.*` files.
- **Docs:** Adds the full security audit report for reference.
- **Build:** Corrects a case-sensitive import path to ensure cross-platform build compatibility.
2025-11-07 11:15:02 +08:00
xiaomai
ccfd268682 feat(webDev): add inquiry modal for pricing plans
This commit introduces a 'Contact Sales' modal on the web development page, allowing users to inquire about specific service plans.

- The pricing plan buttons now trigger this modal, pre-filled with the selected plan's details.
- Users can add custom remarks to their inquiry.
- On submission, a pre-formatted message is generated and opened in WhatsApp using a new `useWhatsAppMsgSender` composable.
- Adds `NUXT_PUBLIC_WHATSAPP_NUMBER` to the runtime configuration.
- Refactors content validation by introducing Zod schemas for `PricingPlan` and `Button` props to improve type safety.
- Adds new i18n keys for the modal interface and message templates.
2025-11-07 11:04:14 +08:00
xiaomai
40b3ee147f docs(engineering): add project audit report and improvement plan
This commit introduces a comprehensive engineering audit report for the Tootaio Studio project. The report is structured into documents covering architecture, code quality, performance, security, CI/CD, and
observability. It also includes a phased improvement roadmap and a set of `.patch` files to apply immediate fixes for content schemas, package scripts, and CI configuration.
2025-11-06 10:15:00 +08:00
xiaomai
8cc04b7f59 feat(pages): add web development services page
This commit introduces a new page at `/webDev` to display web development services and pricing plans.

To support this, a new reusable composable `useLocalizedCollection` has been created to simplify fetching localized content from Nuxt
Content. The index page has been refactored to use this new composable.

- Adds `webDev.vue` page and corresponding `webDev.yml` content files for EN and ZH.
- Defines a Zod schema in `content.config.ts` for the new content type.
- Updates the navigation link to point to the new page.
2025-11-06 09:02:50 +08:00
xiaomai
31a4103f9b feat(ui): implement dynamic dropdown navigation and refactor project cards
Replaced the static navigation with a dynamic, internationalized dropdown menu powered by a new `useNavLinks` composable. The
navigation items are now sourced from i18n files. The featured project cards on the homepage have been refactored to use the
`<UPageCard>` component, and the content schema is updated with `spotlight` and `highlight` options for enhanced display.
2025-11-06 07:44:41 +08:00
xiaomai
78bc2c34a0 refactor(content): migrate index page to Nuxt Content
This commit refactors the index page to source its content from @nuxt/content, replacing the previous implementation that used i18n
JSON files and hardcoded data within the component.

Key changes:
- Introduced `content.config.ts` to define collections and Zod schemas for type-safe content.
- Moved page content into localized YAML files (`content/en-US/index.yml` and `content/zh-CN/index.yml`).
- Updated `app/pages/index.vue` to fetch data dynamically using `useAsyncData` and `queryCollection`.
- Removed redundant content from i18n JSON files and the Vue component script.

This change decouples content from presentation, improves maintainability, and centralizes content management.
2025-11-05 17:55:20 +08:00
xiaomai
87731a6379 feat(content): add initial homepage content and navigation
This commit introduces the foundational content and navigation for the website's homepage.

- Adds a new navigation menu to the default layout, linking to Services, Projects, and Insights.
- Creates YAML content files for the homepage in both English (en) and Chinese (zh-CN).
- Populates the content with key sections including capabilities, featured projects, and tech stack.
2025-11-05 15:07:27 +08:00
xiaomai
113ebabb94 feat(ui): enhance homepage with dynamic background images and improved locale selection 2025-11-05 08:13:18 +08:00
xiaomai
5c8baf14c3 feat(ui): rebuild homepage with new sections and i18n
This commit introduces a complete overhaul of the homepage, rebuilding it from the ground up with Nuxt UI and full
internationalization support. The new design better showcases the studio's capabilities and projects.

- Re-architected the index page with multiple new sections: Capabilities, Featured Projects, Tech Stack, and Why Choose Us.
- Implemented full i18n for English (en-US) and Chinese (zh-CN) across all new content.
- Centralized the page structure into a `default.vue` layout with a global header (including color mode and locale selectors) and
footer.
- Replaced placeholder logos with a dynamic `UMarquee` of technology icons using Iconify.
2025-11-04 17:53:14 +08:00
55 changed files with 4029 additions and 1130 deletions

1
.env.example Normal file
View File

@@ -0,0 +1 @@
NUXT_PUBLIC_WHATSAPP_NUMBER=+60123456789

1
.npmrc
View File

@@ -1,2 +1 @@
shamefully-hoist=true shamefully-hoist=true
package-lock=false

View File

@@ -1,7 +1,7 @@
{ {
"i18n-ally.localesPaths": ["i18n/locales"], "i18n-ally.localesPaths": ["i18n/locales"],
"i18n-ally.enabledFrameworks": [ "i18n-ally.enabledFrameworks": ["vue"],
"vue" "i18n-ally.sourceLanguage": "en-US",
], "i18n-ally.displayLanguage": "en-US",
"i18n-ally.keystyle": "nested" "i18n-ally.keystyle": "nested"
} }

84
AGENTS.md Normal file
View File

@@ -0,0 +1,84 @@
当然可以,下面是完整的中文翻译版,保持了原文的结构与语气,适合直接放在项目的 `AGENTS.zh-CN.md` 中使用👇
---
# 仓库指南Repository Guidelines
本仓库托管了 **Tootaio Studio** 网站项目,基于 **Nuxt 4**`@nuxt/content``@nuxt/ui` 和 i18n 构建。
项目使用 **pnpm** 进行全部工作流程。
---
## 项目结构与模块组织
* `app/` — Nuxt 应用源码目录,包含:`pages/``layouts/``assets/``composables/``utils/` 等(例如:`app/pages/index.vue`)。
* `content/` — 基于 YAML 的多语言内容文件(如:`content/en-US``content/zh-CN`)。内容结构定义在 `content.config.ts` 中。
* `i18n/` — UI 翻译文件JSON 格式),位于 `i18n/locales/<locale>/`
* `public/` — 静态资源目录,内容将原样提供给客户端。
* 根级配置文件包括:`nuxt.config.ts``eslint.config.mjs``pnpm-workspace.yaml`。项目文档存放于 `docs/`
---
## 构建、测试与开发命令
* **安装依赖**`pnpm i`(会自动执行 `nuxt prepare`
* **开发服务器**`pnpm dev` — 启动后访问 [http://localhost:3000](http://localhost:3000),支持 HMR。
* **生产构建**`pnpm build` — 生成 `.output/` 目录。
* **预览构建**`pnpm preview` — 以生产模式运行构建产物。
* **静态生成**`pnpm generate` — 执行 SSG静态站点生成
* **代码检查**`pnpm exec eslint .`手动执行时使用CI 流程中会自动运行)。
---
## 代码风格与命名规范
* 使用 **TypeScript** 与 Vue 3 `<script setup lang="ts">`;配置文件采用 **ESM 模块** 格式。
* 缩进统一为 **2 空格**;保持与现有代码库的引号与风格一致。
* Vue 单文件组件SFC位于 `pages/` 下,文件名简洁(例如:`webDev.vue`
自定义组合式函数composables使用 **PascalCase**(如:`LocalizedCollection.ts`)。
* ESLint 通过 `@nuxt/eslint` 配置,依赖编辑器集成与 CI/构建阶段反馈。
* i18n 多语言 key 使用 **点号嵌套命名法**,例如:`$t('index.featuredProjects.viewDemo')`
---
## 测试指南
* 当前尚未配置自动化测试。
推荐在未来使用 **Vitest** 进行单元测试,**Playwright** 进行端到端e2e测试。
* 在此之前,请进行本地验证:
* 运行 `pnpm dev`
* 切换多语言;
* 检查 `content/*.yml` 是否符合 `content.config.ts` 中的 schema
* 确认 `pnpm build``pnpm preview` 能成功执行。
---
## 提交与 Pull Request 规范
* 遵循 **Conventional Commits** 规范,并带有 scope可参考提交历史
* 示例:`feat(content): …``refactor(ui): …``feat(pages): …`
* 分支命名格式:
* `feat/<简短描述>``fix/<简短描述>`
* Pull Request 要包含以下信息:
* **改动目的**
* **关联的 Issue如有**
* **UI 改动截图**
* **测试步骤**
* 若涉及内容文件,请确保 **中英文版本同步更新**
---
## 内容与多语言i18n建议
*`content/en-US/``content/zh-CN/` 下维护平行的内容文件。
若新增页面类型,请同时扩展 `content.config.ts` 的 schema。
* 使用 `useLocalizedCollection('index')` 或类似函数来获取内容以获得类型支持与语言回退fallback
* 保持多语言翻译文件在各个 locale 目录下同步更新:`i18n/locales/<locale>/`
---
是否希望我帮你把这份翻译排版成一个更正式、带 Front Matter 的文档版本(例如带 `title`, `lastUpdated`, `description` 的 YAML 头部),以便直接放进 Nuxt Content 或 docs 目录?

214
README.md
View File

@@ -1,75 +1,191 @@
# Nuxt Minimal Starter # Tootaio Studio 网站Nuxt 4
Look at the [Nuxt documentation](https://nuxt.com/docs/getting-started/introduction) to learn more. Tootaio Studio 的官方网站代码仓库,基于 Nuxt 4、@nuxt/content@nuxt/ui 与 i18n 构建,采用 TypeScript 与 ESM 配置。项目默认使用 pnpm 进行依赖与脚本管理。
## Setup - 在线地址:`https://tootaio.com`
- 技术要点:内容与 UI 分离YAML 内容 + JSON 翻译)、类型安全的内容加载、内置 SEO 与 CSP 头部策略
Make sure to install dependencies: ---
## 特性概览
- Nuxt 4 与 Vue 3 `<script setup lang="ts">`,全量 TypeScript 化
- @nuxt/content 管理结构化 YAML 内容zod 校验 schema类型安全
- @nuxt/ui 负责 UI 组件与样式,支持暗色模式与主题定制
- @nuxtjs/i18n 多语言(英文、简中),消息分包按需加载
- @nuxtjs/seo 统一 SEO 元信息配置OG/Twitter/keywords
- Nitro routeRules 内置严格 CSP、缓存与安全响应头
- 运行时配置集成 WhatsApp 咨询(公开变量)
---
## 快速开始
### 先决条件
- Node.js 18.20+ 或 20+
- pnpm 8+
### 安装依赖
```bash ```bash
# npm pnpm i
npm install
# pnpm
pnpm install
# yarn
yarn install
# bun
bun install
``` ```
## Development Server 安装后会自动执行 `nuxt prepare`(见 `package.json:postinstall`)。
Start the development server on `http://localhost:3000`: ### 配置环境变量
```bash ```bash
# npm cp .env.example .env
npm run dev ```
# pnpm 必须设置:
- `NUXT_PUBLIC_WHATSAPP_NUMBER`:用于页面内“立刻咨询”按钮,走 WhatsApp H5 链接。
注:`nuxt.config.ts` 提供默认占位值,生产环境应以 `.env` 覆盖。
### 本地开发
```bash
pnpm dev pnpm dev
# yarn
yarn dev
# bun
bun run dev
``` ```
## Production 访问 `http://localhost:3000`支持热更新HMR与暗色模式切换。
Build the application for production: ---
```bash ## 常用脚本
# npm
npm run build
# pnpm - 开发:`pnpm dev`
pnpm build - 构建:`pnpm build`(产物在 `.output/`
- 预览:`pnpm preview`(以生产模式本地运行)
- 静态生成:`pnpm generate`SSG 场景)
- 代码检查:`pnpm exec eslint .`
# yarn ---
yarn build
# bun ## 目录结构
bun run build
- `app/`Nuxt 应用源码
- `pages/`:页面组件(如 `app/pages/index.vue`, `app/pages/webDev.vue`
- `layouts/`:布局(默认头部含语言与主题切换)
- `assets/css/main.css`:主题与样式(集成 `@nuxt/ui``tailwindcss`
- `composables/`:组合式函数(如 `LocalizedCollection.ts`, `NavLinks.ts`
- `schemas/`zod 定义(如 `pricingPlanSchema.ts`, `buttonSchema.ts`
- `content/`YAML 内容(按语言区分 `en-US/`, `zh-CN/`
- `i18n/locales/<locale>/`UI 文案 JSON 包(按页面拆分,如 `common.json`, `index.json`
- `public/`:静态资源(如 `favicon.ico`, `og-image-1.png`, `_robots.txt`
- 根配置:`nuxt.config.ts`, `content.config.ts`, `eslint.config.mjs`
- 文档:`docs/`(工程审计、路线等)
---
## 内容与多语言
### 内容模型(@nuxt/content
项目在 `content.config.ts` 中用 zod 定义了页面内容的 schema并建立集合与源文件的对应关系
- `index_en``content/en-US/index.yml`
- `index_zh``content/zh-CN/index.yml`
- `webDev_en``content/en-US/webDev.yml`
- `webDev_zh``content/zh-CN/webDev.yml`
在页面中通过类型安全的加载器获取内容:
```ts
// 示例:在页面/组件中获取本地化内容
const { data: page } = await useLocalizedCollection('index')
``` ```
Locally preview production build: 上述加载器会根据当前语言自动选择集合,并在缺失时回退到英文。
```bash ### UI 文案(@nuxtjs/i18n
# npm
npm run preview
# pnpm - 语言:`en`en-US`zh-CN`
pnpm preview - 加载策略:`strategy: "no_prefix"`
- 文案文件:`i18n/locales/<locale>/*.json`
# yarn 示例 key`index.featuredProjects.viewDemo`, `common.button.submit`
yarn preview
# bun 新增页面或模块时,请同步维护中英文版本的 YAML 与 JSON保持 key 一致。
bun run preview
```
Check out the [deployment documentation](https://nuxt.com/docs/getting-started/deployment) for more information. ---
## UI 与主题
- 组件库:`@nuxt/ui``UPage*`, `UHeader`, `UButton`, `UTabs`, `UModal` 等)
- 主题:`app/assets/css/main.css` 中可定制字体与主色(项目默认霓虹红 `--color-primary`
- 暗色模式:内置 `UColorModeButton`,图标与样式随模式自适应
- 图标:`@iconify-json/mdi``skill-icons` 系列(通过 `UIcon` 使用)
---
## SEO 与安全
- SEO`@nuxtjs/seo` 统一设置标题、描述、OG、Twitter 等元信息(见 `nuxt.config.ts``DEFAULT_SEO`
- 站点地址:`site.url = https://tootaio.com`
- 安全头:在 `routeRules` 配置了 CSP、`X-Content-Type-Options`, `Referrer-Policy` 等;静态资源(`/_nuxt/**`)设置了长期缓存
- 图片白名单:`img-src` 允许 `self``https://img.tootaio.com``data:`
---
## 运行时配置与外部集成
- 环境变量:`NUXT_PUBLIC_WHATSAPP_NUMBER`(公开变量,用于发起 WhatsApp 咨询)
- 组合式函数:`useWhatsAppMsgSender()` 会根据该号码构造 WhatsApp H5 链接并 `window.open`
- 示例入口:`app/components/webDev/ContactSalesModal.vue` 在提交后拼接多语言消息并发起咨询
---
## 代码规范与校验
- 风格TypeScript、2 空格缩进、ESM 配置、组合式函数使用 PascalCase
- ESLint`@nuxt/eslint` 预设,配置见 `eslint.config.mjs`
- 手动检查:`pnpm exec eslint .`
---
## 测试(当前状态)
- 暂未配置自动化测试
- 建议:单测使用 VitestE2E 使用 Playwright
- 在此之前请进行本地验证:
- `pnpm dev` 运行并切换语言
- 校验 `content/*.yml` 是否符合 `content.config.ts` 中的 schema
- `pnpm build``pnpm preview` 能否成功
---
## 提交与协作
- 提交规范Conventional Commits含 scope`feat(content): ...`, `refactor(ui): ...`
- 分支命名:`feat/<desc>``fix/<desc>`
- PR 说明:改动目的 / 关联 Issue / UI 截图 / 测试步骤
- 内容改动请确保中英文同步更新YAML 与 i18n JSON
---
## 部署建议
- 服务器渲染(默认):`pnpm build` 生成 `.output/` 后按 Nuxt/Nitro 的 Node 运行方式部署
- 静态站点:`pnpm generate` 进行 SSG请根据实际路由策略评估可行性
- 常见平台Vercel / Netlify / 任意 Node 主机(注意保留 `routeRules` 与响应头设置)
---
## 参考文件
- 配置:`nuxt.config.ts:1``content.config.ts:1`
- 页面:`app/pages/index.vue:1``app/pages/webDev.vue:1`
- 主题:`app/assets/css/main.css:1`
- 多语言:`i18n/locales/en-US/common.json:1``i18n/locales/zh-CN/common.json:1`
- 环境:`.env.example:1`(复制为 `.env` 并填写)
---
## 许可
如无单独说明,本仓库内容归 Tootaio Studio 所有。

View File

@@ -0,0 +1,206 @@
<template>
<UModal
v-model:open="open"
:title="
$t('webDev.know_more_title', {
plan: pkg?.planTitle || '—',
})
"
:description="
$t('webDev.know_more_description', {
plan: pkg?.planTitle || '—',
})
"
aria-label="Contact Sales Modal"
>
<template #body>
<div v-if="!pkg" class="py-6">
<!-- 占位 / loading -->
<div class="text-center text-sm text-muted">
{{ $t("webDev.loading_plan", "Loading plan...") }}
</div>
</div>
<UForm
v-else
ref="form"
:schema="schema"
:state="state"
class="space-y-4"
@submit="onSubmit"
>
<p class="text-lg font-medium">
{{
$t("webDev.contact_intro", {
service: pkg.serviceTitle,
plan: pkg.planTitle,
})
}}
</p>
<p class="text-lg font-medium">
{{ $t("webDev.which_provides", "Which provides:") }}
</p>
<ul class="space-y-1">
<li v-for="(feature, idx) in pkg.features" :key="idx">
<UIcon
size="16"
name="mdi:check-circle-outline"
class="text-primary-500 mr-2"
/>{{ feature }}
</li>
</ul>
<p class="text-lg font-medium">
{{
$t("webDev.extra_remarks_title", "Besides that, I'd like to add:")
}}
</p>
<UFormField name="remarks">
<UTextarea
v-model="state.remarks"
:placeholder="
$t(
'webDev.remarks_placeholder',
'Enter your remarks or requirements, one per line'
)
"
class="w-full"
:rows="4"
/>
</UFormField>
</UForm>
</template>
<template #footer>
<div class="w-full flex gap-4">
<UButton
:label="$t('common.button.cancel')"
variant="subtle"
color="neutral"
class="flex-1"
@click="closeModal"
:disabled="isSubmitting"
/>
<UButton
:label="
isSubmitting
? $t('common.button.saving', 'Saving...')
: $t('common.button.submit')
"
variant="solid"
color="success"
class="flex-1"
@click="handleSubmit"
:loading="isSubmitting"
/>
</div>
</template>
</UModal>
</template>
<script lang="ts" setup>
import * as z from "zod";
import type { FormSubmitEvent } from "@nuxt/ui";
/* ----- types ----- */
type ContactSalesModalProps = {
serviceTitle: string;
planTitle: string;
startingPrice?: string;
features: string[];
};
/* ----- reactive state ----- */
const pkg = ref<ContactSalesModalProps | null>(null);
const open = ref<boolean>(false);
const isSubmitting = ref(false);
/* form ref保持和你原来用法一致 */
const form = useTemplateRef("form");
/* ----- zod schema ----- */
/* 注意zod 的提示文本这里使用英文/固定字符串,若要用 i18n 需要在 validate 时把错误替换为 t(...) */
const schema = z.object({
remarks: z.string().optional(),
});
type Schema = z.infer<typeof schema>;
/* ----- 表单状态初始化为空openModal 时会填充) ----- */
const state = reactive<Partial<Schema>>({
remarks: "",
});
/* ----- 外部调用:打开 modal 并填充数据 ----- */
const openModal = (pricingPlan: ContactSalesModalProps) => {
pkg.value = { ...pricingPlan };
state.remarks = "";
open.value = true;
};
/* ----- 关闭并重置 ----- */
const closeModal = () => {
open.value = false;
// 延迟清理以防动画或确认逻辑中读取到空
setTimeout(() => {
pkg.value = null;
state.remarks = "";
// 如果需要也可以重置表单验证状态: form.value?.reset()
}, 200);
};
/* ----- 表单提交处理 ----- */
async function onSubmit(event: FormSubmitEvent<Schema>) {
// event.data 已通过 schema 验证
try {
isSubmitting.value = true;
const payload = {
planTitle: pkg.value?.planTitle,
features: pkg.value?.features,
remarks: (event.data.remarks || "")
.split("\n")
.map((s) => s.trim())
.filter(Boolean),
// 可以加上更多 metadata比如 timestamp / user id / source page
};
// TODO: 在此发送到后端 API例如
// await $fetch('/api/contact-sales', { method: 'POST', body: payload });
// 临时方案,发送到 WhatsApp 去
const wa_msg = $t("webDev.whatsapp_message", {
service: pkg.value?.serviceTitle,
plan: pkg.value?.planTitle,
price: pkg.value?.startingPrice,
featureList: pkg.value?.features.map((f) => `${f}`).join("\n"),
remarkList: (event.data.remarks || "")
.split("\n")
.map((s) => s.trim())
.filter(Boolean)
.map((r) => `- ${r}`)
.join("\n"),
});
useWhatsAppMsgSender().sendMessage(wa_msg);
// 成功提示(如果你有全局 toast/notification e.g. useToast().success(...)
// 关闭并清理
closeModal();
} catch (err) {
// 处理错误(显示错误 toast / 控制台)
console.error("submit failed", err);
// 这里可以显示友好的错误信息,例如: useToast().error(t('webDev.submit_failed'))
} finally {
isSubmitting.value = false;
}
}
/* footer 按钮触发的提交(触发表单验证) */
async function handleSubmit() {
await form.value?.submit();
}
/* 暴露给父组件的 API */
defineExpose({ openModal, closeModal });
</script>

View File

@@ -0,0 +1,66 @@
// /composables/useLocalizedCollection.ts
import type { Collections } from "@nuxt/content";
export type UseLocalizedOptions = {
/** 默认 locale -> suffix 映射 */
localeMap?: Record<string, string>;
/** 回退 locale 的 suffix例如 'en' */
fallbackSuffix?: string;
/** 当找不到内容时是否抛错,默认 true */
throwOnMissing?: boolean;
/** useAsyncData 的 key 前缀(默认等于 baseName */
keyPrefix?: string;
};
function asCollectionKey(key: string) {
return key as keyof Collections;
}
/**
* 带类型安全的多语言内容加载器
* @example
* const { data: page } = await useLocalizedCollection('index')
*/
export function useLocalizedCollection<
B extends string, // 基础名称
>(baseName: B, opts: UseLocalizedOptions = {}) {
const { locale } = useI18n();
const localeMap = opts.localeMap ?? { en: "en", "zh-CN": "zh" };
const fallbackSuffix = opts.fallbackSuffix ?? "en";
const keyPrefix = opts.keyPrefix ?? baseName;
const throwOnMissing = opts.throwOnMissing ?? true;
// 🔥 自动推断对应集合类型
type LocalizedKey = keyof {
[K in keyof Collections as K extends `${B}_${string}` ? K : never]: any;
};
type Schema = Collections[LocalizedKey];
return useAsyncData<Schema | null>(
`${keyPrefix}-${locale.value}`,
async () => {
const suffix =
localeMap[locale.value] ?? locale.value.split("-")[0] ?? "en";
const key = asCollectionKey(`${baseName}_${suffix}`);
let content = (await queryCollection(key).first()) as Schema | null;
if (!content && suffix !== fallbackSuffix) {
const fallbackKey = asCollectionKey(`${baseName}_${fallbackSuffix}`);
content = (await queryCollection(fallbackKey).first()) as Schema | null;
}
return content;
},
{ watch: [locale] }
).then((res) => {
if (throwOnMissing && res && !res.data?.value) {
throw createError({
statusCode: 404,
statusMessage: `Page not found: ${baseName} for locale ${locale.value}`,
fatal: true,
});
}
return res;
});
}

View File

@@ -0,0 +1,94 @@
// composables/useNavLinks.ts
import type { NavigationMenuItem } from "@nuxt/ui";
export const useNavLinks = () => {
const { t } = useI18n();
const navLinks = computed<NavigationMenuItem[]>(() => [
{
label: t("common.header.services.label"),
icon: "mdi:briefcase-outline",
children: [
{
label: t("common.header.services.children.webDev.label"),
description: t("common.header.services.children.webDev.description"),
icon: "mdi:web",
to: "/webDev"
},
{
label: t("common.header.services.children.softwareDev.label"),
description: t(
"common.header.services.children.softwareDev.description"
),
icon: "mdi:tools",
},
{
label: t("common.header.services.children.eventVisual.label"),
description: t(
"common.header.services.children.eventVisual.description"
),
icon: "mdi:monitor-dashboard",
},
{
label: t("common.header.services.children.lab.label"),
description: t("common.header.services.children.lab.description"),
icon: "mdi:test-tube-off",
},
],
},
{
label: t("common.header.projects.label"),
icon: "mdi:lightbulb-group-outline",
children: [
{
label: t("common.header.projects.children.commercialWebsite.label"),
description: t(
"common.header.projects.children.commercialWebsite.description"
),
icon: "mdi:web",
},
{
label: t("common.header.projects.children.gameDev.label"),
description: t("common.header.projects.children.gameDev.description"),
icon: "mdi:gamepad-variant-outline",
},
{
label: t("common.header.projects.children.tools.label"),
description: t("common.header.projects.children.tools.description"),
icon: "mdi:tools",
},
{
label: t("common.header.projects.children.special.label"),
description: t("common.header.projects.children.special.description"),
icon: "mdi:star",
},
],
},
{
label: t("common.header.insights.label"),
icon: "mdi:brain",
children: [
{
label: t("common.header.insights.children.xiaomaiBlog.label"),
description: t(
"common.header.insights.children.xiaomaiBlog.description"
),
icon: "mdi:pencil-outline",
to: "https://xiaomai.tootaio.com/",
type: "link",
target: "_blank",
},
],
},
{label: t("common.header.teams.label"),
icon: "mdi:account-group-outline",
children: [{
label: t("common.header.teams.children.xiaomai.label"),
description: t("common.header.teams.children.xiaomai.description"),
to: '/about/xiaomai'
}]
}
]);
return navLinks;
};

View File

@@ -0,0 +1,21 @@
import { createSharedComposable } from "@vueuse/core";
const _useWhatsAppMsgSender = () => {
const config = useRuntimeConfig();
const phone = config.public.whatsappNumber;
// --- WhatsApp 自动消息逻辑 ---
const sendMessage = (message: string) => {
const text = encodeURIComponent(message);
const url = `https://api.whatsapp.com/send?phone=${phone}&text=${text}`;
window.open(url, "_blank", "noopener,noreferrer");
};
return {
sendMessage,
};
};
export const useWhatsAppMsgSender = createSharedComposable(
_useWhatsAppMsgSender
);

View File

@@ -1,13 +1,58 @@
<template> <template>
<div> <UPage>
<UHeader
:ui="{
left: 'flex items-center gap-1.5',
center: 'hidden lg:flex lg:flex-4',
right: 'flex items-center justify-end gap-1.5',
}"
>
<template #title> Tootaio Studio </template>
<template #default>
<UNavigationMenu
:items="navLinks"
variant="link"
class="w-full justify-center"
/>
</template>
<template #body>
<UNavigationMenu :items="navLinks" orientation="vertical" />
</template>
<template #right>
<UColorModeButton />
<UButton
icon="twemoji:flag-china"
:variant="$i18n.locale == 'zh-CN' ? 'outline' : 'ghost'"
class="cursor-pointer"
color="neutral"
:disabled="$i18n.locale == 'zh-CN'"
@click="setLocale('zh-CN')"
/>
<UButton
icon="twemoji:flag-united-states"
:variant="$i18n.locale == 'en' ? 'outline' : 'ghost'"
class="cursor-pointer"
color="neutral"
:disabled="$i18n.locale == 'en'"
@click="setLocale('en')"
/>
</template>
</UHeader>
<UMain>
<slot /> <slot />
</div> </UMain>
<UFooter>
<template #left>
&copy; {{ new Date().getFullYear() }} Tootaio Studio. All rights
reserved.
</template>
</UFooter>
</UPage>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
const { setLocale } = useI18n();
const navLinks = useNavLinks();
</script> </script>
<style> <style></style>
</style>

View File

@@ -0,0 +1,49 @@
<script lang="ts" setup>
const route = useRoute();
const articleLink = computed(() => `${window?.location}`);
const { data: page } = await useAsyncData(route.path, () =>
queryCollection("about").path(`/about/${route.params.slug}`).first()
);
</script>
<template>
<UPage>
<div
class="fixed top-0 left-0 -z-999 w-screen h-screen bg-[url('/images/xiaomai.png')] bg-cover lg:bg-contain bg-no-repeat opacity-40 animate-slide-in"
></div>
<UPageBody class="max-w-3xl mx-auto">
<ContentRenderer v-if="page?.body" :value="page" />
<div class="flex items-center justify-end gap-2 text-sm text-muted">
<UButton
size="sm"
variant="link"
color="neutral"
label="Copy link"
@click="
copyToClipboard(articleLink, 'Article link copied to clipboard')
"
/>
</div>
</UPageBody>
</UPage>
</template>
<style scoped>
@keyframes slide-in {
from {
transform: translateX(-100%);
opacity: 0;
}
to {
transform: translateX(0);
opacity: 0.4;
}
}
.animate-slide-in {
animation: slide-in 1.2s ease-out forwards;
}
</style>

View File

@@ -1,44 +1,141 @@
<template> <template>
<UPage> <div>
<UHeader> <!-- 全幅 Hero - Page 布局之外 -->
<template #title> Tootaio Studio </template> <UPageHero
<template #right> :title="page?.title"
<ULocaleSelect :description="page?.description"
:model-value="locale" :ui="{
:locales="[en, zh_cn]" root: 'relative before:absolute before:inset-0 before:bg-[image:var(--bg-image)] before:bg-cover before:bg-center before:-z-10 before:opacity-40',
@update:model-value="(v) => setLocale(v as 'en' | 'zh-CN')" }"
:style="{
'--bg-image': `url('${currentBgImage}')`,
}"
/> />
</template>
</UHeader>
<UPageSection <UPageSection
title="Trusted by" :title="page?.capabilities.title"
:description="$t('index.trustedBy', { count: 10000 })" :features="page?.capabilities.features"
/>
<UPageSection :title="page?.featuredProjects.title">
<UCarousel
v-slot="{ item }"
:items="page?.featuredProjects.projects"
:ui="{ item: 'basis-full sm:basis-1/2 lg:basis-1/3' }"
> >
<UPageCard
class="my-2"
:title="item.title"
:description="item.description"
:highlight="item.highlight"
:spotlight="item.spotlight"
>
<img :src="item.image" :alt="item.title" />
<UButton
v-if="item.demoLink"
:href="item.demoLink"
target="_blank"
rel="noopener"
size="sm"
>
{{ $t("index.featuredProjects.viewDemo") }}
</UButton>
</UPageCard>
</UCarousel>
</UPageSection>
<UPageSection :title="page?.techStack.title">
<UMarquee> <UMarquee>
<img <UIcon
v-for="logo in trustedBy" v-for="icon in techIcons"
:key="logo.src" :key="icon"
:src="logo.src" :name="icon"
:alt="logo.alt" class="size-16"
class="h-12 mx-8 grayscale opacity-60" />
</UMarquee>
<UMarquee reverse>
<UIcon
v-for="icon in toolsIcons"
:key="icon"
:name="icon"
class="size-16"
/> />
</UMarquee> </UMarquee>
</UPageSection> </UPageSection>
</UPage>
<UPageSection
:title="page?.whyChooseUs.title"
:features="page?.whyChooseUs.features"
/>
</div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { en, zh_cn } from "@nuxt/ui/locale"; const { data: page } = await useLocalizedCollection("index", {
const { locale, setLocale } = useI18n(); throwOnMissing: false,
});
const trustedBy = ref([ useSeoMeta({
{ src: "/index/trusted-by/logoipsum-284.svg", alt: "Logo Ipsum" }, title: page.value?.seo.title,
{ src: "/index/trusted-by/logoipsum-338.svg", alt: "Logo Ipsum" }, });
{ src: "/index/trusted-by/logoipsum-353.svg", alt: "Logo Ipsum" },
{ src: "/index/trusted-by/logoipsum-378.svg", alt: "Logo Ipsum" }, const backgroundImages = [
{ src: "/index/trusted-by/logoipsum-392.svg", alt: "Logo Ipsum" }, "https://img.tootaio.com/i/2025/11/05/avc5ld.png",
{ src: "/index/trusted-by/logoipsum-403.svg", alt: "Logo Ipsum" }, "https://img.tootaio.com/i/2025/11/05/avcaff.png",
{ src: "/index/trusted-by/logoipsum-409.svg", alt: "Logo Ipsum" }, "https://img.tootaio.com/i/2025/11/05/avcjbw.png",
"https://img.tootaio.com/i/2025/11/05/avcp16.png",
"https://img.tootaio.com/i/2025/11/05/avcv1q.png",
"https://img.tootaio.com/i/2025/11/05/avd47a.png",
"https://img.tootaio.com/i/2025/11/05/avdx6a.png",
"https://img.tootaio.com/i/2025/11/05/avegxy.png",
"https://img.tootaio.com/i/2025/11/05/avemgn.png",
"https://img.tootaio.com/i/2025/11/05/avf3wl.png",
];
const currentBgImage = ref<string | undefined>("");
// 随机选择背景
const randomBg = () => {
const randomIndex = Math.floor(Math.random() * backgroundImages.length);
currentBgImage.value = backgroundImages[randomIndex];
};
// 轮播背景
onMounted(() => {
randomBg();
});
const techIcons = computed(() => [
"skill-icons:html",
"skill-icons:css",
"skill-icons:javascript",
"skill-icons:typescript",
"skill-icons:docker",
"skill-icons:vuejs-light",
"skill-icons:nuxtjs-light",
"skill-icons:tailwindcss-light",
"skill-icons:nodejs-light",
"skill-icons:cs",
"skill-icons:python-light",
]);
const toolsIcons = ref([
"skill-icons:photoshop",
"skill-icons:illustrator",
"skill-icons:git",
"skill-icons:vscode-light",
"skill-icons:visualstudio-light",
"skill-icons:github-light",
"skill-icons:godot-light",
"skill-icons:unity-light",
"skill-icons:blender-light",
"skill-icons:androidstudio-light",
"skill-icons:windows-light",
"skill-icons:linux-light",
"skill-icons:apple-light",
"skill-icons:idea-light",
"skill-icons:pycharm-light",
"skill-icons:rider-light",
]); ]);
</script> </script>

47
app/pages/webDev.vue Normal file
View File

@@ -0,0 +1,47 @@
<template>
<UContainer>
<UPageHero :title="page?.title" :description="page?.description" />
<p class="text-muted py-4 text-center">{{ page?.remarks }}</p>
<UTabs :items="serviceTabs">
<template v-for="service in serviceTabs" :key="service.id" #[service.id]>
<UPricingPlans :plans="service.plans" />
</template>
</UTabs>
<WebDevContactSalesModal ref="webDevContactSalesModal" />
</UContainer>
</template>
<script lang="ts" setup>
import WebDevContactSalesModal from "~/components/webDev/ContactSalesModal.vue";
const { data: page } = await useLocalizedCollection("webDev");
const webDevContactSalesModal = ref<InstanceType<
typeof WebDevContactSalesModal
> | null>(null);
const serviceTabs = computed(() =>
page.value?.services.map((srv) => ({
...srv,
plans: srv.plans.map((pln) => ({
...pln,
button: {
label: "立刻咨询", // TODO: i18n 适配
onClick: () => {
webDevContactSalesModal.value?.openModal({
serviceTitle: srv.label,
planTitle: pln.title,
startingPrice: pln.price,
features: pln.features ?? [],
});
},
},
})),
slot: srv.id,
}))
);
</script>
<style></style>

View File

@@ -0,0 +1,37 @@
// buttonSchema.ts
import { z } from "zod";
const ButtonSize = z.enum(["2xs", "xs", "sm", "md", "lg", "xl"]);
const ButtonColor = z.enum([
"error",
"primary",
"secondary",
"success",
"info",
"warning",
"neutral",
]);
const ButtonVariant = z.enum(["solid", "outline", "soft", "ghost", "link"]);
export const ButtonPropsSchema = z.object({
size: ButtonSize.optional().default("sm"),
type: z.enum(["button", "submit", "reset"]).optional().default("button"),
label: z.string().optional().default(""),
color: ButtonColor.optional().default("primary"),
variant: ButtonVariant.optional().default("solid"),
icon: z.string().optional().default(""),
leading: z.boolean().optional().default(false),
trailing: z.boolean().optional().default(false),
disabled: z.boolean().optional().default(false),
loading: z.boolean().optional().default(false),
loadingIcon: z.string().optional().default("i-heroicons-arrow-path-20-solid"),
block: z.boolean().optional().default(false),
to: z.string().optional().default(""),
target: z.string().optional().default(""),
padded: z.boolean().optional().default(true),
square: z.boolean().optional().default(false),
truncate: z.boolean().optional().default(false),
attrs: z.record(z.any()).optional().default({}),
});
export type ButtonProps = z.infer<typeof ButtonPropsSchema>;

View File

@@ -0,0 +1,57 @@
import { z } from "zod";
import { ButtonPropsSchema } from "./buttonSchema";
/**
* Zod schema for UPricingPlan component (basic props only)
* Reference: https://ui.nuxt.com/docs/components/pricing-plan#props
*/
export const PricingPlanPropsSchema = z.object({
/** The title of the pricing plan. */
title: z.string(),
/** The description text shown under the title. */
description: z.string().optional(),
/** The current price of the plan. */
price: z.string().optional(),
/**
* Discounted price.
* When set, the main price will appear with a strikethrough.
*/
discount: z.string().optional(),
/** The unit period (e.g. /month) displayed next to price. */
billingCycle: z.string().optional(),
/** Additional billing text above the billing cycle. */
billingPeriod: z.string().optional(),
/**
* List of plan features.
* Can be an array of strings or array of objects (feature items).
*/
features: z.array(z.string()).optional().default([]),
/** The button displayed at the bottom (ButtonProps). */
button: ButtonPropsSchema.optional(),
/**
* Visual variant of the pricing plan.
* @default "outline"
*/
variant: z.enum(["soft", "solid", "outline", "subtle"]).optional().default("outline"),
/**
* Layout orientation of the component.
* @default "vertical"
*/
orientation: z.enum(["vertical", "horizontal"]).optional().default("vertical"),
/** Optional tagline text displayed above price. */
tagline: z.string().optional(),
/** Terms or disclaimer text displayed below features. */
terms: z.string().optional(),
/**
* Highlights the pricing plan visually (adds a ring around it).
* @default false
*/
highlight: z.boolean().optional().default(false),
/**
* Enlarges the plan card slightly for emphasis.
* @default false
*/
scale: z.boolean().optional().default(false),
});
export type PricingPlanProps = z.infer<typeof PricingPlanPropsSchema>;

13
app/utils/clipboard.ts Normal file
View File

@@ -0,0 +1,13 @@
export function copyToClipboard(
toCopy: string,
message: string = "Copied to clipboard"
) {
const toast = useToast();
navigator.clipboard.writeText(toCopy).then(() => {
toast.add({
title: message,
color: "success",
icon: "i-lucide-check-circle",
});
});
}

89
content.config.ts Normal file
View File

@@ -0,0 +1,89 @@
import { defineContentConfig, defineCollection, z } from "@nuxt/content";
import { PricingPlanPropsSchema } from "./app/schemas/pricingPlanSchema";
const defineIndexSchema = () =>
z.object({
capabilities: z.object({
title: z.string(),
features: z.array(
z.object({
title: z.string(),
description: z.string(),
icon: z.string(),
})
),
}),
featuredProjects: z.object({
title: z.string(),
projects: z.array(
z.object({
title: z.string(),
description: z.string(),
image: z.string(),
demoLink: z.string(),
highlight: z.boolean(),
spotlight: z.boolean(),
})
),
}),
techStack: z.object({
title: z.string(),
}),
whyChooseUs: z.object({
title: z.string(),
features: z.array(
z.object({
title: z.string(),
description: z.string(),
icon: z.string(),
})
),
}),
});
const defineWebDevSchema = () =>
z.object({
remarks: z.string(),
services: z.array(
z.object({
id: z.string().min(1),
label: z.string().min(1),
icon: z.string().optional(), // 比如 "lucide:mouse-pointer-click"
// 你原结构里通过 createService 包装,但最终是一个对象
plans: z.array(PricingPlanPropsSchema).min(1),
// 预留扩展字段例如category、tags、hidden 等)
category: z.string().optional(),
tags: z.array(z.string()).optional(),
})
),
});
export default defineContentConfig({
collections: {
index_en: defineCollection({
type: "page",
source: "en-US/index.yml",
schema: defineIndexSchema(),
}),
index_zh: defineCollection({
type: "page",
source: "zh-CN/index.yml",
schema: defineIndexSchema(),
}),
webDev_en: defineCollection({
type: "page",
source: "en-US/webDev.yml",
schema: defineWebDevSchema(),
}),
webDev_zh: defineCollection({
type: "page",
source: "zh-CN/webDev.yml",
schema: defineWebDevSchema(),
}),
about: defineCollection({
type: "page",
source: "about/*.md",
schema: z.object({}),
}),
},
});

138
content/about/xiaomai.md Normal file
View File

@@ -0,0 +1,138 @@
---
name: 小麦Xiaomai
title: 游戏开发者 | 前后端开发者 | 工作室创办人
location: 马来西亚,柔佛
email: xsbugh@gmail.com
website: https://github.com/kingsmai
education:
university: 武汉工商学院
major: 计算机科学与技术
gpa: 3.93/4.0
period: 2019年9月 - 2024年8月
rank: 专业排名 8/303
scholarship: 四年全额奖学金获得者
awards:
- 2021年全国高校商业精英挑战赛创新创业竞赛全国总决赛一等奖
- 2021年"互联网+"大学生创新创业大赛湖北省复赛金奖
- 马来西亚外交部志愿者贡献表彰2022年
skills:
- Unity 2D/3D 游戏开发
- Godot 4 游戏开发
- JavaWeb 后端开发 (SpringBoot)
- Web 前端开发 (Vue, Node.js, Webpack)
- Python 数据分析与自动化
- SQLite, MySQL 数据库管理
- Blender LowPoly 建模
- 区块链开发与NFT技术
- 自动化测试 (Selenium)
- AI与机器学习基础
---
# 麦祖奕 - 工作室创办人介绍
## 个人简介
我是一名来自马来西亚的华裔游戏开发者,本科以优异成绩毕业于武汉工商学院计算机科学与技术专业。作为一名充满热情的技术创作者,我拥有丰富的全栈开发经验。
我的技术生涯始于对游戏开发的热爱逐渐扩展到区块链技术、Web 全栈开发和自动化系统等多个领域。我相信技术应该服务于创意,致力于通过代码实现具有社会价值的创新项目。
**人生使命**:开发具有教育价值且在全球广受欢迎的角色扮演游戏,通过游戏传递知识、启发思考。
## 教育背景与学术成就
在武汉工商学院就读期间,我以 **3.93 的 GPA** 成绩位列专业前 3%8/303连续四年获得全额奖学金。系统学习了计算机科学的核心课程包括数据结构、算法设计、软件工程、数据库原理等为后续的技术实践奠定了坚实的理论基础。
## 专业技术能力
### 游戏开发专长
- **Unity引擎开发者**:精通 2D 游戏开发熟悉物理系统、动画系统、UI系统
- **Godot引擎实践者**:掌握 Godot 4 及 GDScript具备快速原型开发能力
- **游戏架构设计**:熟练运用单例模式、工厂模式、观察者模式、对象池等设计模式
- **AI系统实现**掌握状态机、上下文转向寻路、行为树等AI技术
- **数据持久化**:精通 SQLite 嵌入式数据库、PlayerPrefs 等存储方案
- **安全加密**:实践 XOR 数据加密技术保护游戏数据
### 全栈开发能力
- **后端开发**SpringBoot, ASP .NET Core, PHP, Node.js, 数据库设计与优化
- **前端开发**Vue.js, Nuxt, Webpack, 响应式设计, 静态站点生成
- **移动端开发**Android 原生开发,跨平台适配
- **自动化脚本**Python + Selenium 自动化操作
### 其他技术技能
- **区块链开发**NFT 自动化交易开发,智能合约理解
- **3D建模**Blender LowPoly 建模,游戏资源制作
- **数据分析**Python 数据处理与可视化
- **项目管理**Git 版本控制,团队协作开发
## 代表性项目
### 🎮 《匠人英雄》- 2D生存类游戏毕业设计
**技术栈**: Unity, SQLite, Context Steering AI, Perlin Noise, 设计模式
作为大学毕业设计我独立设计并开发了这款2D俯视角生存游戏。项目实现了
- 基于 CSV 和 XOR 加密的游戏数据配置系统
- 嵌入式 SQLite 数据库存档系统
- 上下文转向 AI 寻路与 Perlin 噪声地图生成
- 完整的设计模式应用架构
### 🔥 《光追》- 地牢冒险游戏GameJam作品
**技术栈**: Godot 4, 有限状态机, GDScript
在 48 小时极限开发挑战中,我担任程序与策划,带领团队完成:
- 基于状态机的 AI 行为系统
- 光影核心玩法机制设计
- B站宣传片获得 700+ 观看量
- 吸引 200+ 玩家参与测试
### 🌐 马来西亚赴华资料整合网站
**技术栈**: Node.js, Webpack, GitHub Pages, JSON API
疫情期间,我联合多所高校留学生发起并开发了这个信息整合平台:
- 3 天内完成从需求分析到部署上线的全流程
- 自研基于 JSON 的静态页面 API 模拟框架
- 帮助近 5000 名马来西亚留学生顺利返校
- 获得马来西亚外交部官方认可
### 💰 慈善竞标大屏系统
**技术栈**: Unity, 双屏显示, 实时动画
为慈善机构开发的商业化项目:
- 实时竞价数据显示与动画效果
- 主控台与大屏幕的双屏协同
- 在马来西亚多个组织中成功应用
## 工作与创业经历
### 马来西亚民主行动党 - 软件开发实习生
**2022年2月-2022年9月**
在实习期间,我承担了多项技术任务:
- 使用 Python 进行选举数据分析和可视化
- 基于 SpringBoot + Vue 开发中大型数据管理系统
- 实现 NFT 交易平台的自动化操作脚本
- 协助多语言新闻稿的发布与传播
### 技术创业经历
基于在游戏开发和 Web 全栈领域的技术积累,我具备从零到一的产品开发能力,能够带领团队完成技术选型、架构设计、开发实施和运营维护的全过程。
## 技术理念与愿景
### 开发哲学
我相信优秀的技术产品应该具备三个特质:**用户体验优先**、**技术架构稳健**、**社会价值正向**。在每一个项目中,我都努力平衡技术创新与实际需求,确保代码质量与开发效率的最佳结合。
### 工作室愿景
作为工作室创办人,我致力于:
1. 打造具有教育意义的精品游戏
2. 探索游戏与新兴技术的结合点
3. 培养年轻开发者的技术能力
4. 通过技术解决方案创造社会价值
## 荣誉与认可
除了在学术和技术竞赛中获得的奖项外,我最珍视的认可是那些通过技术帮助他人的时刻。特别是在疫情期间开发的留学生返校网站,不仅获得了马来西亚外交部的正式表彰,更重要的是切实帮助了数千名学子重返校园。
我相信,技术的真正价值在于它能够为人们的生活带来积极的改变。作为一名技术创作者,这始终是我前进的动力和追求的目标。
---
*欢迎通过以上联系方式与我交流技术创意、项目合作或投资机会。我始终对新的技术挑战和具有社会价值的项目保持开放态度。*

52
content/en-US/index.yml Normal file
View File

@@ -0,0 +1,52 @@
seo:
title: "Homepage"
title: "Tootaio Studio"
description: "Technology Meets Imagination."
capabilities:
title: "Our Capabilities"
features:
- title: "Custom Website Development"
description: "Tailored websites and backend systems built with modern frameworks like Nuxt and Next, delivering performance, scalability, and visual impact."
- title: "Software & Tool Engineering"
description: "Custom-built applications, internal dashboards, and automation tools that streamline workflows and enhance productivity."
- title: "Game Design & Development"
description: "From Game Jam prototypes to commercial releases — we design and develop immersive, creative, and technically robust gaming experiences."
- title: "Interactive Media & Event Systems"
description: "Creating dynamic, large-screen visuals and real-time interactive systems for events, exhibitions, and banquets."
- title: "Tech Exploration & Evaluation"
description: "Experimenting with emerging technologies and evaluating software and hardware to stay ahead of the innovation curve."
- title: "Creative Consulting & Digital Strategy"
description: "Providing expert guidance on product architecture, digital transformation, and long-term technology strategy for brands and startups."
featuredProjects:
title: "Featured Projects"
projects:
- title: "YPHS Alumni Official Website"
description: "Website Design and Development worth RM28,000. Donate to YPHS Alumni by founder of Tootaio."
image: "http://img.tootaio.com/i/2025/11/05/d9kurl.png"
demoLink: "https://yphsalumni.org"
- title: "Malaysia students return to China guide"
description: "An interactive guide which help more than 5000 students back to China Mainland to continue their study during pandemic COVID-19 in year 2022. This project is supported by The Ministry of Foreign Affairs (MFA) in Malaysia"
image: "http://img.tootaio.com/i/2025/11/05/d9kcma.png"
demoLink: "https://tootaio.github.io"
spotlight: true
- title: "Light Chasing"
description: "This is an entry from the 2023 G-bits University GameJam Challenge, based on the Godot engine."
image: "https://img.tootaio.com/i/2025/09/26/j2swgq.png"
techStack:
title: "Tech Stacks"
whyChooseUs:
title: "Why Choose Us"
description: "We dont just build code — we craft digital experiences."
features:
- title: "Fully Custom-Built"
description: "Every website, system, and game is built from scratch to match your unique brand identity and performance needs."
- title: "Tech-Driven, Not Just Design"
description: "Our engineers lead every project, optimizing architecture, performance, and security at every level."
- title: "Cross-Domain Expertise"
description: "From web systems to games and interactive tools — we merge creativity and engineering to deliver seamless experiences."
- title: "End-to-End Service"
description: "From concept, prototyping, and frontend to deployment and long-term support — we handle everything in-house."
- title: "Proven Project Value"
description: "Weve delivered projects for educational institutions, brands, and game developers — with real impact and measurable value."
- title: "Future-Oriented"
description: "Beyond client work, were building our own products and experiments, pushing the boundaries of whats possible."

95
content/en-US/webDev.yml Normal file
View File

@@ -0,0 +1,95 @@
seo:
title: "Web / Website Custom Development"
description: "Create a tailor-made online portal for your brand to boost visibility and conversion rates."
title: "Web / Website Custom Development"
description: "Create a tailor-made online portal for your brand to boost visibility and conversion rates."
remarks: "All plans include basic domain and server deployment for the first year. If server load becomes too high, we will assist with migration to a more stable third-party environment."
services:
- id: landing-page
label: "Landing Page"
icon: "mdi:cursor-default-click-outline"
plans:
- title: "Basic"
description: "Ideal for individuals or small businesses to launch a one-page showcase website quickly."
price: "From RM899"
tagline: "Includes domain & hosting"
features:
- "Single-page structure (13 sections)"
- "Responsive design (mobile / tablet / desktop)"
- "Basic text and image layout"
- "Contact form or WhatsApp button"
- "Google Analytics integration"
- "1 free revision"
- "Delivery within 7 days"
button:
label: "Contact Now"
- title: "Standard"
description: "Designed for brands and startups to create high-conversion pages."
price: "From RM1,599"
tagline: "Includes domain & hosting"
highlight: true
features:
- "Multi-section layout (46 sections)"
- "Custom brand styling and color scheme"
- "Lightweight animations and motion effects"
- "SEO optimization + performance tuning"
- "Tracking integration (GA / Pixel)"
- "2 free revisions"
- "Delivery within 14 days"
button:
label: "Request Quote"
- title: "Premium Custom"
description: "Comprehensive visual and marketing upgrade for established brands."
price: "From RM2,999"
features:
- "Exclusive visual design and interaction experience"
- "Complete brand style system"
- "A/B testing and conversion optimization"
- "Marketing tool integration (email, analytics, CRM)"
- "Multi-language / dynamic content support"
button:
label: "Book a Custom Plan"
- id: official-web
label: "Official Website"
icon: "lucide:globe"
plans:
- title: "Basic Website"
description: "Build a professional online presence for small and medium-sized businesses."
price: "From RM3,999"
tagline: "Includes domain & hosting"
features:
- "Up to 5 pages (Home, About, Services, Contact, etc.)"
- "Responsive design (desktop / tablet / mobile)"
- "Basic SEO setup"
- "Contact form + map + social media links"
button:
label: "Contact Now"
- title: "Standard Website"
description: "Ideal for brand upgrades and content-rich businesses."
price: "From RM6,999"
# discount: "From RM4,999"
tagline: "Includes domain & hosting"
highlight: true
features:
- "Around 812 pages (Case Studies, Blog, Team, etc.)"
- "Custom brand styling + UI/UX optimization"
- "Lightweight CMS for content management"
- "Advanced SEO optimization and performance acceleration"
button:
label: "Book Standard Plan"
- title: "Enterprise Custom"
description: "Fully tailored visual, functional, and interactive experience."
price: "From RM15,000"
features:
- "Fully custom UI / animation design"
- "Multi-language support / client login module"
- "API / third-party system integrations"
- "Enhanced security and automated backup mechanism"
button:
label: "Book Enterprise Plan"

64
content/zh-CN/index.yml Normal file
View File

@@ -0,0 +1,64 @@
seo:
title: "首页"
title: "Tootaio Studio"
description: "专为想要独特体验的品牌打造量身定制的数字产品。"
capabilities:
title: "我们的核心能力"
features:
- title: "网站定制开发"
description: "基于 Nuxt / Next 等现代框架,为企业打造高性能、可扩展且视觉出众的专属网站与后台系统。"
icon: mdi:web
- title: "软件与工具工程"
description: "为企业定制自动化工具、数据面板与业务流程系统,提高工作效率与可靠性。"
icon: mdi:cog-outline
- title: "游戏设计与开发"
description: "从 Game Jam 原型到商业发行,打造富有创意与技术深度的互动体验。"
icon: mdi:gamepad-variant-outline
- title: "互动媒体与宴会系统"
description: "为展会、活动与宴会定制实时交互内容与大型屏幕视觉展示。"
icon: mdi:monitor-dashboard
- title: "技术探索与评测"
description: "研究与评估前沿软硬件技术,保持创新优势与研发热情。"
icon: mdi:flask-outline
- title: "创意咨询与数字策略"
description: "为品牌与团队提供产品架构、数字化转型与长期技术规划咨询。"
icon: mdi:lightbulb-outline
featuredProjects:
title: "特色项目"
projects:
- title: "永中校友会官方网站"
description: "永平中学校友会官方网站的设计与开发项目,整体价值 RM28,000由本工作室创办人无偿捐赠予校友会永久使用。"
image: "http://img.tootaio.com/i/2025/11/05/d9kurl.png"
demoLink: "https://yphsalumni.org"
- title: "留华生来华资料汇总"
description: "2022 年疫情期间,为马来西亚留学生开发的返校攻略网站。帮助 5000+ 名留学生顺利返校。并获得马来西亚外交部推荐。"
image: "http://img.tootaio.com/i/2025/11/05/d9kcma.png"
demoLink: "https://tootaio.github.io"
spotlight: true
- title: "光追"
description: "基于 Godot 引擎的 2023 年吉比特高校挑战赛参赛作品。"
image: "https://img.tootaio.com/i/2025/09/26/j2swgq.png"
techStack:
title: "Tech Stacks"
whyChooseUs:
title: "为什么选择我们"
description: "我们不仅编写代码——我们打造数字体验。"
features:
- title: "完全定制开发"
description: "我们从不使用模板。每一个网站、系统、游戏都从零设计与开发,确保风格、性能与体验完全符合品牌个性。"
icon: mdi:brush-variant
- title: "技术驱动,而非仅仅是设计"
description: "作为开发导向的团队,我们理解底层逻辑。从架构、性能、安全到交互动画,所有细节都由工程师主导优化。"
icon: mdi:cog-sync-outline
- title: "跨领域专长"
description: "我们横跨网站、游戏、工具与交互内容开发,让每个项目都能获得更广阔的技术整合思路。"
icon: mdi:gamepad-variant-outline
- title: "端到端一站式服务"
description: "从概念、原型、前端到部署与长期维护,我们全程负责,让客户专注业务,而非技术问题。"
icon: mdi:rocket-launch-outline
- title: "经验证的项目价值"
description: "我们曾为教育机构、品牌活动、独立游戏等开发高价值系统,实力可见。"
icon: mdi:chart-timeline-variant
- title: "面向未来"
description: "我们在研发自己的产品与工具,不止接案,也在打造未来生态。这代表我们具备持续创新与自我进化的能力。"
icon: mdi:lightbulb-on-outline

95
content/zh-CN/webDev.yml Normal file
View File

@@ -0,0 +1,95 @@
seo:
title: "网页 / 网站定制开发"
description: "为您的品牌量身定做一套线上门户,增加产品曝光率与转化率。"
title: "网页 / 网站定制开发"
description: "为您的品牌量身定做一套线上门户,增加产品曝光率与转化率。"
remarks: "所有方案均含基础域名与服务器部署(首年)。若服务器负载过高,将协助迁移至更稳定的第三方环境。"
services:
- id: landing-page
label: "Landing Page"
icon: "mdi:cursor-default-click-outline"
plans:
- title: "基础版"
description: "适合个人、小型商家,快速上线单页展示网站。"
price: "RM899 起"
tagline: "含域名与服务器"
features:
- "单页面结构1-3 屏)"
- "响应式设计(手机 / 平板 / 桌面)"
- "基本图文排版"
- "联系表单或 WhatsApp 按钮"
- "Google Analytics 整合"
- "1 次免费修改"
- "7 日内交付"
button:
label: "立即咨询"
- title: "标准版"
description: "为品牌与创业项目打造高转化页面。"
price: "RM1,599 起"
tagline: "含域名与服务器"
highlight: true
features:
- "多区块结构4-6 屏)"
- "品牌定制风格与配色"
- "轻量动画与动效展示"
- "SEO 优化 + 加载性能优化"
- "整合追踪代码GA / Pixel"
- "2 次免费修改"
- "14 日内交付"
button:
label: "预约报价"
- title: "高级定制"
description: "为成熟品牌提供全面视觉与营销升级方案。"
price: "RM2,999 起"
features:
- "专属视觉设计与交互体验"
- "完整品牌风格系统"
- "A/B 测试与转化优化"
- "营销工具整合邮件、统计、CRM"
- "多语言 / 动态内容支持"
button:
label: "预约定制方案"
- id: official-web
label: "Official Website"
icon: "lucide:globe"
plans:
- title: "基础官网"
description: "为中小型企业建立专业在线形象。"
price: "RM3,999 起"
tagline: "含域名与服务器"
features:
- "最多 5 个页面(首页、关于、服务、联系等)"
- "响应式设计(桌面 / 平板 / 手机)"
- "基础 SEO 设置"
- "联系表单 + 地图 + 社交媒体链接"
button:
label: "立即咨询"
- title: "标准官网"
description: "适合品牌升级与内容扩展型企业。"
price: "RM6,999 起"
# discount: "RM4,999 起"
tagline: "含域名与服务器"
highlight: true
features:
- "约 8-12 个页面(案例、博客、团队等)"
- "品牌定制风格 + UI/UX 优化"
- "轻量 CMS 后台管理系统"
- "进阶 SEO 优化与性能加速"
button:
label: "预约标准方案"
- title: "企业定制"
description: "专属视觉、功能与交互体验整合。"
price: "RM15,000 起"
features:
- "完全定制 UI / 动效设计"
- "多语言支持 / 客户登录模块"
- "API / 第三方系统整合"
- "增强安全与自动备份机制"
button:
label: "预约企业方案"

28
docs/20251106/README.md Normal file
View File

@@ -0,0 +1,28 @@
# Tootaio Studio 工程与架构审计2025-11-06
本报告对当前基于 Nuxt 4.2.0 的网站项目进行一次性全方位工程化审计涵盖架构、代码质量、性能、CI/CD、安全与可观测性并提供可直接落地的改进方案与补丁。
## 摘要
- 架构总体清晰,采用 Nuxt Content + i18n + @nuxt/ui模块职责明确Low
- 发现内容 Schema 与实际 YAML 不一致Critical导致构建期校验风险与类型漂移。
- 缺少统一工程工具链lint/typecheck/CIHigh
- 未配置 Route Rules/SWR 与静态资源缓存Medium
- 运行时配置、监控与安全头未体系化Medium
## 关键输出
- 补丁:
- 修正 `content.config.ts` Schema 与内容对齐0001
- 增加 `package.json` 脚本lint/typecheck/check0002
- 提供 GitHub Actions 工作流lint → typecheck → build0003
- 可选:为首页与静态资源增加 `routeRules`0004
- 提供 8 份专题文档架构、质量、性能、CI/CD、安全、可观测性与路线图。
## 执行指引
1) 审阅 `docs/20251106/` 文档;
2) 依次应用补丁(位于 `docs/20251106/patches/`
- `git apply docs/20251106/patches/0001-content-config-align.patch`
- `git apply docs/20251106/patches/0002-package-scripts.patch`
- `git apply docs/20251106/patches/0003-github-actions-ci.patch`
- (可选)`git apply docs/20251106/patches/0004-nuxt-route-rules.patch`
3) 本地验证:`pnpm i && pnpm check && pnpm preview`

View File

@@ -0,0 +1,52 @@
# 架构结构与改进方案
本文审计 Nuxt 4.2.0 架构与模块使用,指出结构异味并提供可落地改进。
## 现状评估
- 模块与目录Low
- `app/`pages/layouts/composables/utils/assets职责清晰。
- `content/``content.config.ts` 使用 zod 定义 Schema利于类型驱动内容。
- `i18n/` 采用 `locales/<locale>` 结构;`no_prefix` 策略与当前站点定位一致。
- Nuxt 4 特性使用Medium
- 自动导入:已使用(如 `useLocalizedCollection`)。建议将文件命名统一为 `useXxx.ts` 便于识别。
- Runtime Config未使用。建议引入 `runtimeConfig` 管理外部域名、监控 DSN 等。
- Nitro Route Rules/缓存:未配置。建议为首页与静态资源设置 `prerender`+`swr` 与长缓存。
- 设计风险Critical
- `content.config.ts` 的 Index Schema 与 YAML 不一致:缺少 `title/description/seo` 字段;`icon/highlight/spotlight` 约束与内容不符。
## 改进策略
1) 内容 Schema 对齐Critical
- 为 Index 增加 `title/description/seo`;将 `icon` 设为可选,将 `highlight/spotlight` 设为可选并默认 `false`
- 参见补丁:`0001-content-config-align.patch`
2) 运行时配置Medium
-`nuxt.config.ts` 中:
```ts
export default defineNuxtConfig({
runtimeConfig: {
sentryDsn: '',
public: { assetBase: 'https://img.tootaio.com' },
},
})
```
- 在代码中读取 `useRuntimeConfig()`,避免硬编码外链与密钥。
3) 路由规则与缓存Medium
- 为首页预渲染并设置 SWR为构建产物设置 immutable 缓存:
```ts
export default defineNuxtConfig({
routeRules: {
'/': { prerender: true, swr: 3600 },
'/_nuxt/**': {
headers: { 'cache-control': 'public, max-age=31536000, immutable' },
},
},
})
```
- 参见补丁:`0004-nuxt-route-rules.patch`。
4) 组合式命名与分层Low
- 将 `app/composables/LocalizedCollection.ts` 重命名为 `useLocalizedCollection.ts`。
- 保持 composable 只做“取数+组装”,复杂 UI/状态拆到组件/Pinia若引入
## 与 Nuxt 4.2.0 兼容性
- 现有 `compatibilityDate: '2025-07-15'` 符合要求。
- 上述配置属于稳定 API`routeRules/runtimeConfig/auto-import`,安全可用。

40
docs/20251106/ci-cd.md Normal file
View File

@@ -0,0 +1,40 @@
# CI/CD 设计与部署方案
## 目标
提供从代码质量到产物构建的自动化流程lint → typecheck → build。
## GitHub Actions推荐
- 工作流文件参见补丁 `0003-github-actions-ci.patch`,关键步骤:
- 使用 `actions/setup-node@v4` + `corepack enable`
- 安装依赖:`pnpm i --frozen-lockfile`
- 执行 `pnpm lint && pnpm typecheck && pnpm build`
## 部署对比
- Vercel推荐
- 优点:零配置、内置 CDN、适配 Nuxt/NitroPreview 环境完善。
- 缺点:免费额度受限;对长时间 SSR 任务需商用套餐。
- Docker自管
- 优点:环境一致性高;更灵活接入内网服务。
- 缺点:需自建 CI/CD 与监控;维护成本更高。
- 自建 Node + Nginx
- 优点:成本可控;传统可见性强。
- 缺点:手工配置多;需要额外缓存/CDN 配合。
## Dockerfile示例
```dockerfile
FROM node:20-alpine AS build
WORKDIR /app
COPY . .
RUN corepack enable && pnpm i --frozen-lockfile && pnpm build
FROM node:20-alpine
WORKDIR /app
ENV NODE_ENV=production
COPY --from=build /app/.output ./.output
EXPOSE 3000
CMD ["node","./.output/server/index.mjs"]
```
## 环境变量与密钥
- 在 CI 中设置 `NITRO_PRESET``SENTRY_DSN`Nuxt 通过 `runtimeConfig` 注入,避免硬编码。

View File

@@ -0,0 +1,41 @@
# 代码规范与质量分析
## 主要发现
- 类型定义High
- `content.config.ts` 缺少 Index 页的 `title/description/seo`,导致 `page?.title` 等缺少类型提示;特定字段(`icon/highlight/spotlight`)与 YAML 不一致。
- 组合式命名Medium
- `LocalizedCollection.ts` 建议改为 `useLocalizedCollection.ts`,与 Nuxt 习惯保持一致,便于检索与自动导入心智统一。
## 统一约定
- 文件与目录
- composables`useXxx.ts`(示例:`useLocalizedCollection.ts`)。
- 页面:`pages/webDev.vue`(帕斯卡命名不用于路由页)。
- 工具:`utils/some-helper.ts`,避免与 composables 混用。
- 代码风格
- TypeScript、ESM、Vue SFC `<script setup lang="ts">`
- 缩进 2 空格;组件/组合式函数采用明确的返回类型。
- i18n 使用嵌套点式键:`$t('index.featuredProjects.viewDemo')`
- 提交规范
- 参考历史:`feat(content): ...``refactor(ui): ...``feat(pages): ...`
- 分支:`feat/<desc>``fix/<desc>`
## 重构建议(示例)
1) 内容类型对齐Critical
- 见补丁 `0001-content-config-align.patch`
2) 工程脚本完善High
- 新增脚本:`lint/typecheck/check`,见补丁 `0002-package-scripts.patch`
3) 组合式命名Medium
-`LocalizedCollection.ts` 重命名为 `useLocalizedCollection.ts`(建议)。
## 示例:更严格的返回类型
```ts
export function useLocalizedCollection<B extends string>(baseName: B) {
const { locale } = useI18n()
type LocalizedKey = keyof { [K in keyof Collections as K extends `${B}_${string}` ? K : never]: any }
type Schema = Collections[LocalizedKey]
return useAsyncData<Schema | null>(`${baseName}-${locale.value}`, async () => {
// ...
})
}
```

View File

@@ -0,0 +1,40 @@
# 监控、日志与可观测性
## 目标
构建可追踪错误、性能瓶颈与用户关键路径的最小监控闭环。
## Sentry推荐
1) 配置 `runtimeConfig.sentryDsn`
2) 插件(示例,待引入依赖后启用):
```ts
// plugins/sentry.client.ts
export default defineNuxtPlugin(() => {
const { public: pub, sentryDsn } = useRuntimeConfig()
if (!sentryDsn) return
// @ts-ignore: add SDK in deps later
const Sentry = (window as any).Sentry
if (Sentry) {
Sentry.init({ dsn: sentryDsn, tracesSampleRate: 0.1 })
}
})
```
## 日志与错误边界
- 服务器:使用 Nitro 默认日志(生产应接入集中式日志,如 CloudWatch/ELK
- 客户端:全局错误捕获(`app:error` 事件)上报到 Sentry/自建网关。
## Web Vitals可选
收集 LCP/CLS/INP 等指标(示例):
```ts
// plugins/web-vitals.client.ts
import { onCLS, onINP, onLCP } from 'web-vitals'
export default defineNuxtPlugin(() => {
const send = (metric: any) => navigator.sendBeacon('/vitals', JSON.stringify(metric))
onLCP(send); onCLS(send); onINP(send)
})
```
## Dashboard 路线
- 第一步:仅错误计数与关键事务。
- 第二步接口慢查询、资源错误分布、版本追踪commit SHA

View File

@@ -0,0 +1,89 @@
diff --git a/content.config.ts b/content.config.ts
--- a/content.config.ts
+++ b/content.config.ts
@@
const defineIndexSchema = () =>
- z.object({
- capabilities: z.object({
- title: z.string(),
- features: z.array(
- z.object({
- title: z.string(),
- description: z.string(),
- icon: z.string(),
- })
- ),
- }),
- featuredProjects: z.object({
- title: z.string(),
- projects: z.array(
- z.object({
- title: z.string(),
- description: z.string(),
- image: z.string(),
- demoLink: z.string(),
- highlight: z.boolean(),
- spotlight: z.boolean(),
- })
- ),
- }),
- techStack: z.object({
- title: z.string(),
- }),
- whyChooseUs: z.object({
- title: z.string(),
- features: z.array(
- z.object({
- title: z.string(),
- description: z.string(),
- icon: z.string(),
- })
- ),
- }),
- });
+ z.object({
+ title: z.string(),
+ description: z.string(),
+ seo: z
+ .object({
+ title: z.string().optional(),
+ description: z.string().optional(),
+ })
+ .optional(),
+ capabilities: z.object({
+ title: z.string(),
+ features: z.array(
+ z.object({
+ title: z.string(),
+ description: z.string(),
+ icon: z.string().optional(),
+ })
+ ),
+ }),
+ featuredProjects: z.object({
+ title: z.string(),
+ projects: z.array(
+ z.object({
+ title: z.string(),
+ description: z.string(),
+ image: z.string(),
+ demoLink: z.string().url().optional(),
+ highlight: z.boolean().optional().default(false),
+ spotlight: z.boolean().optional().default(false),
+ })
+ ),
+ }),
+ techStack: z.object({
+ title: z.string(),
+ }),
+ whyChooseUs: z.object({
+ title: z.string(),
+ features: z.array(
+ z.object({
+ title: z.string(),
+ description: z.string(),
+ icon: z.string().optional(),
+ })
+ ),
+ }),
+ });

View File

@@ -0,0 +1,39 @@
diff --git a/package.json b/package.json
--- a/package.json
+++ b/package.json
@@
"scripts": {
- "build": "nuxt build",
- "dev": "nuxt dev",
- "generate": "nuxt generate",
- "preview": "nuxt preview",
- "postinstall": "nuxt prepare"
+ "build": "nuxt build",
+ "dev": "nuxt dev",
+ "generate": "nuxt generate",
+ "preview": "nuxt preview",
+ "postinstall": "nuxt prepare",
+ "typecheck": "nuxt typecheck",
+ "lint": "eslint --ext .ts,.js,.mjs,.vue --ignore-path .gitignore .",
+ "lint:fix": "pnpm lint -- --fix",
+ "check": "pnpm lint && pnpm typecheck && pnpm build"
},
@@
"dependencies": {
"@iconify-json/mdi": "^1.2.3",
"@nuxt/content": "3.8.0",
"@nuxt/eslint": "1.10.0",
"@nuxt/ui": "4.1.0",
"@nuxtjs/i18n": "10.2.0",
"@nuxtjs/seo": "3.2.2",
"better-sqlite3": "^12.4.1",
"eslint": "^9.39.0",
"nuxt": "^4.2.0",
"typescript": "^5.9.3",
"vue": "^3.5.22",
"vue-router": "^4.6.3"
- }
+ },
+ "engines": {
+ "node": ">=18.20.0"
+ }

View File

@@ -0,0 +1,34 @@
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
--- /dev/null
+++ b/.github/workflows/ci.yml
@@
name: CI
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- name: Enable Corepack
run: corepack enable
- name: Install dependencies
run: pnpm i --frozen-lockfile
- name: Lint
run: pnpm lint
- name: Typecheck
run: pnpm typecheck
- name: Build
run: pnpm build

View File

@@ -0,0 +1,50 @@
diff --git a/nuxt.config.ts b/nuxt.config.ts
--- a/nuxt.config.ts
+++ b/nuxt.config.ts
@@
export default defineNuxtConfig({
compatibilityDate: "2025-07-15",
devtools: { enabled: true },
modules: [
"@nuxt/content",
"@nuxt/ui",
"@nuxt/eslint",
"@nuxtjs/i18n",
"@nuxtjs/seo",
],
css: ["@/assets/css/main.css"],
app: {
head: {
titleTemplate: "%s - Tootaio Studio",
meta: [
{ name: "viewport", content: "width=device-width, initial-scale=1" },
{ charset: "utf-8" },
],
},
},
@@
seo: {
meta: {
title: DEFAULT_SEO.title,
description: DEFAULT_SEO.description,
keywords: DEFAULT_SEO.keywords,
ogTitle: DEFAULT_SEO.title,
ogDescription: DEFAULT_SEO.description,
ogImage: DEFAULT_SEO.image,
ogUrl: DEFAULT_SEO.url,
twitterCard: "summary_large_image",
twitterTitle: DEFAULT_SEO.title,
twitterDescription: DEFAULT_SEO.description,
twitterImage: DEFAULT_SEO.image,
},
},
site: {
url: "https://tootaio.com",
},
+ routeRules: {
+ "/": { prerender: true, swr: 3600 },
+ "/_nuxt/**": {
+ headers: { "cache-control": "public, max-age=31536000, immutable" },
+ },
+ },
});

View File

@@ -0,0 +1,33 @@
# 性能诊断与优化建议
## 现状与影响
- SSR/CSRMedium默认 SSR未针对首页静态化或增量缓存配置变化不频繁的内容可降本提速。
- 资源缓存Medium未设置构建产物`/_nuxt/**`)的长期缓存与 immutable重复访问浪费带宽。
- 图片与外链LowOG 图与背景图使用外部域名,建议统一通过 CDN 域名与缓存规则管理。
## Nuxt 4 定向优化
1) Route Rules 与 SWRMedium
- 预渲染首页并启用 SWR降低首屏 TTFB。
- 长缓存构建产物:
```ts
export default defineNuxtConfig({
routeRules: {
'/': { prerender: true, swr: 3600 },
'/_nuxt/**': { headers: { 'cache-control': 'public, max-age=31536000, immutable' } },
},
})
```
- 参见补丁:`0004-nuxt-route-rules.patch`。
2) 代码分割与懒加载Low
- 仅在首屏需要时挂载大型组件(如轮播);通过 `v-if` 配合 `onMounted` 或 `client-only` 降低 SSR 负载。
3) 构建与依赖Low
- 保持 `pnpm-lock.yaml` 锁定CI 使用 `pnpm i --frozen-lockfile` 保证可重复构建。
4) 图片与静态资源Medium
- 将站点图片统一迁到自有 CDN如 Cloudflare/Vercel Assets配置缓存与压缩。
- 如未来引入 `@nuxt/image`,优先使用自动格式与尺寸。
## 部署建议
- Node/Nitro通用适合 SSR + SWR 与渐进更新。
- Edge部份路由静态资源 + 首页可下放至边缘,注意依赖兼容性。
- Static/Generate若大部分页面静态可考虑 `pnpm generate` 并配合 CDN。

34
docs/20251106/roadmap.md Normal file
View File

@@ -0,0 +1,34 @@
# 分阶段工程化改进路线图
## 阶段一基础规范化12 天)
- 目标:消除 Critical/High 风险,建立最小工程基线。
- 动作:
- 应用补丁 0001/0002
- 启用 `pnpm check` 在本地与 CI
- 修复内容与 Schema 校验。
- 验证:`pnpm check` 全绿;手测多语言与内容渲染。
## 阶段二模块解耦23 天)
- 目标:清晰分层,降低耦合与心智成本。
- 动作:
- 统一 composables 命名为 `useXxx.ts`
- 将硬编码外链迁到 `runtimeConfig.public.assetBase`
- 视需要引入 Pinia 拆分跨页状态。
- 验证:路由与数据流稳定;影响面与回归点有测试用例(增量引入 Vitest
## 阶段三自动化部署12 天)
- 目标PR 即可验证、主干可随时发布。
- 动作:
- 应用补丁 0003GitHub Actions
- 配置 Vercel/自建 Docker 部署;
- 产出构建产物与环境变量清单。
- 验证PR 触发 CI产物可预览。
## 阶段四性能与监控24 天)
- 目标:稳定响应、端到端可观测。
- 动作:
- 应用补丁 0004Route Rules/SWR
- 引入 SentryDSN + 插件);
- 图片与静态资源走 CDN 与长缓存。
- 验证:关键页面首屏数据、错误率与 Web Vitals。

33
docs/20251106/security.md Normal file
View File

@@ -0,0 +1,33 @@
# 安全审计与防护建议
## 发现与等级
- 外链与配置Medium
- 外部图片/链接硬编码在源码中;建议迁移到 `runtimeConfig.public.assetBase` 并集中管理。
- 内容与校验High
- Content Schema 与数据不一致会在构建期暴露为错误或导致运行时空值处理不当。
- 依赖与脚本Low
- 缺少 `typecheck`/`lint` 脚本,易让问题晚发现。
## 防护清单
1) 运行时配置Medium
```ts
export default defineNuxtConfig({
runtimeConfig: {
// 仅服务器可见
sentryDsn: '',
// 客户端可见
public: {
assetBase: 'https://img.tootaio.com',
},
},
})
```
2) 安全头(建议由网关/Nginx 配置Low
- `Content-Security-Policy`(允许必要的域名);
- `X-Content-Type-Options: nosniff`、`Referrer-Policy: strict-origin-when-cross-origin`
- 静态资源 `Cache-Control: immutable`。
3) 敏感信息检查Medium
- 使用 `.gitignore` 与 CI 检查(如 `trufflehog`/`gitleaks`,后续可引入)。
4) 依赖与锁定Low
- 全量使用 `pnpm-lock.yaml`CI 中启用 `--frozen-lockfile` 保证可复现构建。

View File

@@ -0,0 +1,188 @@
---
title: 安全审计报告Tootaio Studio 网站)
description: 开源发布前的安全评估与加固建议与清单
lastUpdated: 2025-11-07
---
# 概览Summary
- 架构为 Nuxt 4 + @nuxt/content 的前端站点,无自建服务端 API/上传功能,攻击面小。若开源,整体安全风险低。
- 主要关注点:外部资源使用 HTTP、窗口打开的 `opener` 风险、生产环境 DevTools 启用、缺少统一安全响应头、依赖冗余与未来内容来源治理。
## 审计范围
- 配置与依赖:`nuxt.config.ts``package.json``.env*``.gitignore`
- 前端页面与可执行逻辑:`app/` 下的页面、布局、可组合函数composables
- 内容与多语言:`content/**.yml``i18n/**.json`
- 文档与脚本:`docs/` 内建议信息。
# 结论与优先级
- 风险等级:低-中LowMedium。不存在明显的密钥泄露或远程代码执行面。
- 高价值、低成本修复项(建议开源前完成):
1) 将所有外部资源统一为 HTTPS。
2) 生产环境禁用 DevTools。
3) `window.open` 显式使用 `noopener,noreferrer`
4) 为外链/图片增加基础协议白名单校验http/https
5) 通过网关或 Nitro `routeRules` 添加基础安全响应头与静态资源缓存策略。
6) 精简未使用依赖(如未使用 `better-sqlite3`)。
# 关键发现与证据Evidence
- 公开运行时配置(非敏感):
- `nuxt.config.ts:22` 暴露 `runtimeConfig.public.whatsappNumber`(按设计公开,无敏感性)。
- DevTools 在生产可能启用:
- `nuxt.config.ts:14` `devtools: { enabled: true }`,建议生产禁用。
- 外链/图片使用了 HTTP混合内容与篡改风险
- `app/pages/index.vue:83-92` 背景图 `http://img.tootaio.com/...`
- `content/en-US/index.yml:25,29``content/zh-CN/index.yml:31,35` 项目图片使用 `http://`
- 外链打开策略:
- `app/pages/index.vue:37-38` 使用 `target="_blank"` 已含 `rel="noopener"`,建议补充 `noreferrer`
- `app/composables/WhatsAppMsgSender.ts:11``window.open` 未显式 `noopener,noreferrer`
- 统一安全头缺失:
- 未在 `nuxt.config.ts` 配置 `routeRules` 的安全头(可在网关/Nginx 层或 Nitro 层补充)。
- 依赖冗余:
- `package.json:19` 引入了 `better-sqlite3`,当前项目未使用,建议移除以降低供应链与构建复杂度。
- 非安全但会影响构建的细节:
- `content.config.ts:2` 大小写引用与实际文件名不一致Linux 下可能报错):应从 `./app/schemas/PricingPlanSchema` 调整为 `./app/schemas/pricingPlanSchema`
- 环境文件治理:
- `.env` 已被 `.gitignore` 忽略(`.gitignore:22`),且内容仅有公开号码(`.env:1`)。建议继续保持从未提交到历史。
# 修复与加固建议Actionable
## 开源前必须High Priority
- 统一使用 HTTPS 资源
-`http://img.tootaio.com/...` 统一替换为 `https://img.tootaio.com/...`
- 建议将资源基址抽离到 `runtimeConfig.public.assetBase` 并集中管理,减少散落硬编码。
- 生产禁用 DevTools
-`devtools` 改为:`devtools: { enabled: process.env.NODE_ENV !== 'production' }`
- `window.open` 加固
- 修改为:`window.open(url, '_blank', 'noopener,noreferrer')` 或在新窗口上设置 `opener = null`
- 外链与图片的协议白名单
- 在渲染外链/图片前校验 URL 协议,只允许 `http:``https:`,避免 `javascript:``data:` 等危险 scheme。
- 安全响应头(建议由网关/Nginx 配置,或用 Nitro routeRules
- 最小集:
- `Content-Security-Policy`(仅放行必要域名,样例见下)。
- `X-Content-Type-Options: nosniff`
- `Referrer-Policy: strict-origin-when-cross-origin`
- `Permissions-Policy`(按需收紧)
- 静态资源缓存:`/_nuxt/**` 设置 `Cache-Control: public, max-age=31536000, immutable`
## 建议完成Medium Priority
- 依赖精简
- 若未用到 `better-sqlite3`,从 `package.json` 移除并更新锁文件。
- `.gitignore` 更严格忽略 env 变体
- 将当前注释掉的 `.env.*` 忽略规则启用,并保留示例白名单:
- 忽略:`.env``.env.*`
- 白名单:`!.env.sample`
- 文件名大小写一致性
- `content.config.ts` 引用改为实际文件名大小写以避免跨平台问题。
## 可选增强Nice-to-have
- 在 CI 中启用:
- `pnpm install --frozen-lockfile`
- 依赖与漏洞审计Dependabot / `pnpm audit --prod`
- Secrets 扫描(`gitleaks`/`trufflehog`
- HSTS 与全站 HTTPS
- 前置网关开启 HSTS并确保所有外链与资源均可通过 HTTPS 访问。
# 附录:建议配置片段
## 1) Nitro routeRules示例
```ts
// nuxt.config.ts
export default defineNuxtConfig({
// ...
routeRules: {
'/**': {
headers: {
'Content-Security-Policy': [
"default-src 'self'",
"script-src 'self'",
"style-src 'self' 'unsafe-inline'",
"img-src 'self' https://img.tootaio.com data:",
"connect-src 'self'",
"frame-ancestors 'self'",
'upgrade-insecure-requests',
].join('; '),
'X-Content-Type-Options': 'nosniff',
'Referrer-Policy': 'strict-origin-when-cross-origin',
},
},
'/_nuxt/**': {
headers: { 'cache-control': 'public, max-age=31536000, immutable' },
},
},
})
```
请按实际依赖域名精简 CSP特别是 `img-src``connect-src`
## 2) `window.open` 安全用法(示例)
```ts
const win = window.open(url, '_blank', 'noopener,noreferrer')
// 或者:
const win = window.open(url, '_blank')
if (win) win.opener = null
```
## 3) 外链协议白名单(示意)
```ts
function isSafeHttpUrl(href: string) {
try {
const u = new URL(href)
return u.protocol === 'http:' || u.protocol === 'https:'
} catch {
return false
}
}
// 使用时:
// <UButton v-if="item.demoLink && isSafeHttpUrl(item.demoLink)" :href="item.demoLink" ... />
```
## 4) DevTools 生产禁用
```ts
export default defineNuxtConfig({
devtools: { enabled: process.env.NODE_ENV !== 'production' },
})
```
## 5) `.gitignore` 建议
```gitignore
# Local env files
.env
.env.*
!.env.sample
```
# 附注Non-security 但建议修复)
- `content.config.ts:2` 的大小写引用问题:应改为 `./app/schemas/pricingPlanSchema`(与实际文件名一致),避免在大小写敏感的文件系统上构建失败。
# 下一步
- 如需我可以基于本报告直接提交最小化补丁HTTPS 资源替换、`devtools` 切换、`window.open` 加固、`routeRules` 安全头、`.gitignore` 调整、文件名大小写修复),并附上验证与回退说明。

147
docs/CODEX-PROMPT.md Normal file
View File

@@ -0,0 +1,147 @@
你现在是 **Tootaio Studio 项目的高级工程化顾问兼代码审计师**
你的职责是对这个基于 **Nuxt 4.2.0** 的项目进行一次**全方位工程级分析与优化规划**
以生成一份可以直接落地的**架构与工程化报告**。
---
## 一、项目背景
本仓库托管的是 **Tootaio Studio 网站**,基于以下技术栈:
* **Nuxt 4.2.0**
* **@nuxt/content**(内容驱动)
* **@nuxt/ui**
* **i18n**
* 包管理器:**pnpm**
* 参考规范见 `AGENTS.md`(已提供)。
项目结构(符合最新 Nuxt 4.2.0
```
app/
├─ assets/
├─ composables/
├─ layouts/
├─ pages/
├─ app.vue
content/
i18n/
public/
docs/
```
---
## 二、分析目标
请你以拥有 10 年以上经验的 **Nuxt / 前端架构师** 的身份,
对整个项目进行一次性全面分析与输出,目标包括但不限于:
1. **架构审计**
* 分析模块划分、Nuxt 4 特性使用Nitro routes、auto-import、runtime config 等)。
* 指出结构异味与潜在设计风险,并提供优化策略。
* 判断 app/ 层结构是否遵循最新 Nuxt 4.2.0 官方建议。
2. **代码质量与维护性**
* 检查类型定义、composables 命名规范、逻辑分层是否清晰。
* 判断是否存在可重构点(如 utils / composables / layouts 中重复逻辑)。
* 给出统一命名与约定规范建议(含具体命名示例与代码片段)。
3. **性能与构建流程**
* 检查 SSR / CSR 渲染策略是否合理;有无未按需加载的模块。
* 给出针对 Nuxt 4 的性能优化建议build options, code-splitting, lazy loading, runtime config 优化等)。
* 分析缓存策略、图片优化、CDN 使用、Nitro 部署模式Node / Edge / Static
4. **开发体验与工程体系DX**
* 审查 eslint、prettier、tsconfig、vitest、commitlint、husky 等工具配置。
* 给出一份推荐的现代化工程工具链pnpm workspace + lint + test + CI/CD配置清单。
* 输出改进示例配置文件、命令、package.json scripts
5. **CI/CD 与部署**
* 检查是否存在 GitHub Actions / CI 流程;如无,请生成完整示例:
包含 lint → type-check → test → build → deploy 的 workflow 文件。
* 同时给出 Vercel / Docker / 自建部署方案的对比说明。
6. **安全与可观测性**
* 检查敏感信息暴露、dotenv 安全、runtimeConfig 泄露风险。
* 建议增加 Sentry/LogRocket/Datadog 等监控与日志方案,并提供示例代码。
7. **内容与多语言结构**
* 确认 `content/``i18n/` 结构是否合理、是否符合 `AGENTS.md` 规范。
* 检查 YAML schema 与 JSON locale 对齐情况,建议自动化同步方案。
8. **改进路线图Roadmap**
* 生成一个可执行的工程化升级路线图:
分为四个阶段(基础规范化 → 模块解耦 → 自动化部署 → 性能与监控)。
* 每个阶段列出要点、执行命令和预期产出。
---
## 三、输出要求(一次性全部完成)
请将你的分析结果写入 `docs/20251106/` 目录中,包含以下文件(全部在本次回复中完整输出):
| 文件路径 | 内容说明 |
| -------------------------------- | ---------------------------------------- |
| `docs/20251106/README.md` | 分析概要与执行总结 |
| `docs/20251106/architecture.md` | 架构结构与改进方案 |
| `docs/20251106/code-quality.md` | 代码规范与质量分析 |
| `docs/20251106/performance.md` | 性能诊断与优化建议 |
| `docs/20251106/ci-cd.md` | CI/CD 设计与部署方案 |
| `docs/20251106/security.md` | 安全审计与防护建议 |
| `docs/20251106/observability.md` | 监控、日志与运行时可观测性 |
| `docs/20251106/roadmap.md` | 分阶段工程化改进计划 |
| `docs/20251106/patches/` | 含至少 3 个可应用的 `.patch` 文件unified diff 格式) |
每个 patch 文件需附带:
* 修改目标与理由;
* 应用命令(`git apply patches/xxx.patch`
* 改动前后对比(`--- a/... +++ b/...`
* 若涉及配置文件,请保持类型安全与 Nuxt 4.2.0 兼容。
---
## 四、格式要求
* 所有输出以 Markdown 格式展示;
* 按文件分节,使用代码块展示 `.patch` 内容;
* 每份文档前加上清晰标题与简短摘要;
* 禁止模糊性推迟或要求我继续上传文件;
* 如果无法直接读取仓库,请列出你需要的关键文件清单(按优先级),但仍需生成基于假设的分析报告与目录结构模板。
---
## 五、风格与深度要求
* 用语风格:专业、实操、能落地;
* 所有建议应附示例代码、命令、配置;
* 明确指出每个问题的严重程度Critical / High / Medium / Low
* 推荐方案必须与 Nuxt 4.2.0 当前版本特性兼容;
* 输出中请表现出架构师的思考过程(但不需解释 Prompt
---
## 六、触发执行
准备好后,请开始分析项目并输出完整报告。
触发关键词:
`ANALYZE_AND_OUTPUT_NOW`
---
## 🔚 Prompt 结束
---
是否希望我现在帮你生成这个 Prompt 的 **可直接存放在仓库根目录的 `codex_prompt.md` 版本**(带标题、描述和 front-matter
那样你只需放进仓库、Codex 扫描就能自动理解执行。

24
docs/Company Overview.md Normal file
View File

@@ -0,0 +1,24 @@
我的工作室侧重于:定制开发,科技公司
涉及业务包括但不限于:
1. 网站 / 网页开发(高级定制,包括客制化后台系统等)
2. 程序开发
3. 游戏开发(曾经有一两款单机小游戏,目前暂停,未来会重新启动)
4. 工具开发
5. 软硬件测评(未来加入)
6. 额外服务:宴会大屏幕内容定制
7. 小网页开发(普通定制,用于简单展示)
目前线上可访问(大学做的,基于 Nuxt3游戏介绍为主。现在想把涉猎面广一些
目前我们已落地的产品:
- GameJam 游戏一个(地牢探险生存类)
- 业余开发的手机游戏一个(速降类)
- 永平中学校友会官方网站与管理系统(价值 RM32,000
- 宴会大屏竞标系统价值RM 500 / 次)
正在开发的产品:
- 室内设计公司高定官网(价值 RM 8,000

View File

@@ -0,0 +1,63 @@
## 🧩 Our Capabilities建议结构
### 1⃣ Custom Web Development
> 从企业官网到复杂后台系统,提供端到端的定制化开发。
> **关键词**Nuxt / Next / Tailwind / CMS / API Integration
可加一句扩展:
> 我们专注于速度、可维护性与美学并重的前端体验。
---
### 2⃣ Software & Tool Engineering
> 为企业或个人定制内部工具与业务流程系统。
> **关键词**Automation / Dashboard / Data Visualization / Desktop Apps
> 你可以展示如「宴会大屏幕竞标系统」或「管理系统」作为例子。
---
### 3⃣ Game Design & Development
> 将创意化为可交互的世界。独立游戏、原型、游戏化体验一站式实现。
> **关键词**Godot / Unity / WebGL / Procedural Design
> 可以提及 Game Jam 作品、速降手游、未来的重启计划。
---
### 4⃣ Interactive Media & Event Systems
> 为活动、展会、宴会提供沉浸式视觉与互动内容。
> **关键词**Large Screen Content / Real-time Animation / Custom Interaction
> 这块是你独有的“宴会大屏幕定制服务”,建议配视频或动效展示。
---
### 5⃣ Tech Exploration & Evaluation (Lab)
> 研究前沿软硬件,测试并分享最新技术成果。
> **关键词**Experimental / Hardware Review / Prototype / Innovation
> 可作为未来内容板块,让网站保持“持续成长感”。
---
### 6⃣ Creative Consulting & Digital Strategy *(可选)*
> 为品牌或团队提供技术方向规划与产品实现建议。
> **关键词**Digital Transformation / UX Consulting / Architecture Design
> 如果你常为客户做「网站+系统整体方案」,就适合加上这项。
---
## ✨ 展示形式建议
* 六宫格布局每项配一个动效图标iconify /lucide
* hover 时显示一句“我们的价值主张”
* 用对比色或霓虹描边营造科技氛围

122
docs/design-doc-v1-en.md Normal file
View File

@@ -0,0 +1,122 @@
# 🧭 1. Brand Positioning
### 🔹 Core Brand Statement
> “A creative technology studio offering custom development services, specializing in fullstack solutions for websites, software, games, and interactive experiences.”
**A creative technology studio offering *custom development services*, specializing in fullstack solutions for websites, software, games, and interactive experiences.**
### 🔹 Brand Keywords
Techforward, Professional, Creative, Reliable, Crossdisciplinary.
### 🔹 Suggested Slogans
* “Crafted Code, Custom Experience.”
* “Technology Meets Imagination.”
* (Chinese original translated) “Tailored digital products for brands that want unique experiences.”
---
# 🧩 2. Site Map (Information Architecture)
Because your business covers a wide range of services, we should present them in a structured way while keeping clear pathways to featured projects.
**Recommended structure:**
```
Home
├── About (brand story + tech stack + team / contact)
├── Services
│ ├── Website Development (highly customized + CMS)
│ ├── Software & Tools (automation / utilities / desktop apps)
│ ├── Game Development (indie titles + future roadmap)
│ ├── Event Displays (custom banquet/event screen systems)
│ └── Lab & Testing (experiments / future features)
├── Projects
│ ├── Corporate Sites
│ ├── Games
│ ├── Tools & Systems
│ └── Special Projects (e.g., event systems)
├── Insights / Blog
│ ├── Technical Articles
│ ├── Dev Logs
│ ├── Game Dev Journal
│ └── Test Reports (future)
└── Contact
```
✅ Use a **tagging system** (e.g., website / game / tool / event) to let visitors quickly filter projects.
---
# 🧱 3. Visual & Interaction Style Recommendations
### 🔹 Overall Aesthetic
| Element | Recommendation |
| ---------- | ------------------------------------------------------------------------------------------- |
| Tone | Monochrome base (black & white) + accent color (neon blue / electric purple / amber orange) |
| Typography | Inter / JetBrains Mono / Noto Sans |
| Motion | Scrollreveal, microinteractions, concise GSAP transitions |
| Layout | Generous white space + modular components (each text block paired with a visual panel) |
### 🔹 Homepage Layout Suggestions
1. **Hero Section** — Oneline mission statement + dynamic logo / particle animation demo.
2. **Our Capabilities** — Display 35 core services with subtle motion.
3. **Featured Works** — Select 36 highlighted projects.
4. **Tech Stack / Tools** — Visual badges for frameworks, languages, engines.
5. **Why Choose Us** — Differentiators (customization, speed, delivery standards).
6. **Call to Action** — Contact / Collaboration / Proposal button.
---
# ⚙️ 4. Technology Stack Recommendations (Nuxt 3 rebuild)
| Module | Recommended Implementation |
| ------------ | --------------------------------------------------- |
| UI Framework | Tailwind CSS + shadcn/ui |
| Animation | GSAP / Framer Motion (Vue compatible) |
| Content Mgmt | `@nuxt/content` (manage projects/blog via Markdown) |
| Image Optim | Nuxt Image Module |
| Deployment | Vercel / Cloudflare Pages |
| SEO | Nuxt SEO module + automatic sitemap generation |
| Analytics | Umami (lightweight, selfhosted) |
> 💡 To showcase technical capability, consider exposing an API surface, visualizing project metadata, or integrating a CMS backend (Directus or a custom Nuxt Admin module).
---
# 🎨 5. Content Strategy
| Section | Content Direction |
| --------------- | -------------------------------------------------------------------------- |
| Home | Who we are + representative works + what we can deliver |
| Services | Detailed explanation for each service + links to real examples |
| Projects | Storydriven case studies (Client Problem → Solution → Outcome) |
| Blog / Insights | More than technical posts: behindthescenes, lessons learned, dev stories |
| About | Emphasize team philosophy and future direction (mention game revival plan) |
---
# 📈 6. Phased Rebuild Roadmap (Execution Plan)
| Phase | Duration | Tasks |
| ----------------- | --------- | ---------------------------------------------- |
| 1⃣ Brand Refresh | 35 days | Copywriting, visual direction, finalize slogan |
| 2⃣ Architecture | 57 days | Sitemap, wireframes, content structure |
| 3⃣ Development | 23 weeks | Nuxt 3 + Tailwind skeleton, build pages |
| 4⃣ Content Import | 1 week | Integrate Markdown / CMS, upload projects |
| 5⃣ Motion & Polish | 35 days | Animations, SEO tuning, performance testing |
| 6⃣ Deploy & Launch | 1 day | Oneclick deploy to Vercel / Cloudflare Pages |
---
# 🔮 7. Future Expansion Opportunities
* Add a **Lab** section to showcase experimental tools and prototypes.
* Build a **custom CMS** (a lightweight Nuxt Admin is feasible).
* Add **multilingual support (i18n)** to reach international clients.
* For game projects, enable **WebGL / Godot Web Export** for inbrowser demos.
* Modularize project data as JSON / Markdown for easier maintenance.

View File

@@ -120,13 +120,3 @@
* 集成 **多语言支持i18n**,面向海外客户。 * 集成 **多语言支持i18n**,面向海外客户。
* 游戏项目可接入 **WebGL / Godot Web Export**,直接可试玩。 * 游戏项目可接入 **WebGL / Godot Web Export**,直接可试玩。
* 将案例模块化为 JSON / Markdown 文件,可持续维护。 * 将案例模块化为 JSON / Markdown 文件,可持续维护。
---
要不我直接帮你整理成一个完整的:
> 🧾《工作室网站重构计划书v1》——包括结构图、推荐技术栈、视觉方向与页面草稿说明。
格式我可以生成成 Word / PDF你可以直接交给团队或自己用来开发。
是否要我现在帮你生成?(如果要,请告诉我希望输出哪种格式:**Word** 还是 **PDF**

View File

@@ -0,0 +1,71 @@
{
"common": {
"header": {
"insights": {
"label": "Insights",
"children": {
"xiaomaiBlog": {
"label": "Founder's Blog",
"description": "The blog sites of our studio founder - Xiaomai."
}
}
},
"services": {
"label": "Services",
"children": {
"softwareDev": {
"label": "Software Development",
"description": "Software and Tools / Automation Development"
},
"eventVisual": {
"label": "Event Big Screen Visualization",
"description": "Display the content whichever you want on the big screen of your events!"
},
"lab": {
"label": "Labs and Tests",
"description": "Unavailable yet..."
},
"webDev": {
"label": "Web Development",
"description": "Customise Web design + CMS"
}
}
},
"projects": {
"label": "Projects",
"children": {
"commercialWebsite": {
"label": "Commercial Website",
"description": "Fully customize website showcases."
},
"tools": {
"label": "Tools and Softwares",
"description": "Some QoL tools we developed"
},
"special": {
"label": "Special Projects",
"description": "Special projects to meet customer needs"
},
"gameDev": {
"label": "Indie Games",
"description": "Have a look at our indie games!"
}
}
},
"teams": {
"label": "Teams",
"children": {
"xiaomai": {
"label": "Xiaomai",
"description": "Founder of Tootaio Studio - Full Stack Developer"
}
}
}
},
"button": {
"cancel": "Cancel",
"submit": "Submit",
"saving": "Saving..."
}
}
}

View File

@@ -1,5 +1,18 @@
{ {
"index": { "index": {
"trustedBy": "Trusted by over {count} users worldwide" "trustedBy": "Trusted by over {count} users worldwide",
"featuredProjects": {
"viewDemo": "Visit Site"
}
},
"webDev": {
"know_more_description": "I need to know more about the pricing and service about {plan} plan.",
"know_more_title": "Know more {plan} plan",
"contact_intro": "Hi — I'd like to learn more about {service} {plan} plan.",
"which_provides": "Which provides:",
"extra_remarks_title": "Besides that, I'd like to add:",
"remarks_placeholder": "Enter your remarks or requirements, one per line",
"loading_plan": "Loading plan...",
"whatsapp_message": "Hello! Im interested in the **{plan}** plan under your **{service}** service.\n\nHere are the plan details:\n💰 Price: {price}\n✨ Key Features:\n{featureList}\nAdditionally, I would like to request:\n{remarkList}\nPlease provide more detailed information. Thank you!"
} }
} }

View File

@@ -0,0 +1,71 @@
{
"common": {
"header": {
"services": {
"label": "服务",
"children": {
"softwareDev": {
"label": "软件开发",
"description": "软件和工具/自动化开发"
},
"eventVisual": {
"label": "活动大屏可视化",
"description": "在您的活动大屏幕上显示您想要的内容!"
},
"lab": {
"label": "实验室和测试",
"description": "还无法使用..."
},
"webDev": {
"label": "网站开发",
"description": "定制网页设计 + CMS"
}
}
},
"projects": {
"label": "案例",
"children": {
"commercialWebsite": {
"label": "商业网站",
"description": "完全定制网站展示。"
},
"tools": {
"label": "工具和软件",
"description": "我们开发的一些生活质量工具"
},
"special": {
"label": "特别项目",
"description": "特殊项目满足客户需求"
},
"gameDev": {
"label": "独立游戏",
"description": "看看我们的独立游戏!"
}
}
},
"insights": {
"label": "博客",
"children": {
"xiaomaiBlog": {
"label": "创始人博客",
"description": "我们工作室创始人小麦的博客网站。"
}
}
},
"teams": {
"label": "团队",
"children": {
"xiaomai": {
"label": "小麦",
"description": "Tootaio Studio 创始人 - 全栈开发者"
}
}
}
},
"button": {
"cancel": "取消",
"submit": "提交",
"saving": "保存..."
}
}
}

View File

@@ -1,5 +1,18 @@
{ {
"index": { "index": {
"trustedBy": "全球有超过 {count} 用户信赖" "trustedBy": "全球有超过 {count} 用户信赖",
"featuredProjects": {
"viewDemo": "访问页面"
}
},
"webDev": {
"know_more_description": "我需要了解有关 {plan} 的定价和服务的更多信息。",
"know_more_title": "了解更多{plan}",
"contact_intro": "你好——我想了解更多关于 {service} {plan}",
"which_provides": "其中包含:",
"extra_remarks_title": "除此之外,我还想补充:",
"remarks_placeholder": "输入您的备注或要求,每行一个",
"loading_plan": "配套加载中。。。",
"whatsapp_message": "您好!我对您的【{service}】服务中的【{plan}】方案感兴趣。\n\n方案详情\n💰 价格:{price}\n✨ 主要功能:\n{featureList}\n另外我还需要\n{remarkList}\n请提供更多详细信息谢谢"
} }
} }

View File

@@ -11,15 +11,49 @@ const DEFAULT_SEO = {
// https://nuxt.com/docs/api/configuration/nuxt-config // https://nuxt.com/docs/api/configuration/nuxt-config
export default defineNuxtConfig({ export default defineNuxtConfig({
compatibilityDate: "2025-07-15", compatibilityDate: "2025-07-15",
devtools: { enabled: true }, devtools: { enabled: process.env.NODE_ENV !== "production" },
modules: [ modules: [
"@nuxt/content",
"@nuxt/ui", "@nuxt/ui",
"@nuxt/content",
"@nuxt/eslint", "@nuxt/eslint",
"@nuxtjs/i18n", "@nuxtjs/i18n",
"@nuxtjs/seo", "@nuxtjs/seo",
], ],
runtimeConfig: {
public: {
whatsappNumber: "+601234567890",
},
},
routeRules: {
"/**": {
headers: {
"Content-Security-Policy": [
"default-src 'self'",
"script-src 'self'",
"style-src 'self' 'unsafe-inline'",
"img-src 'self' https://img.tootaio.com data:",
"connect-src 'self'",
"frame-ancestors 'self'",
"upgrade-insecure-requests",
].join("; "),
"X-Content-Type-Options": "nosniff",
"Referrer-Policy": "strict-origin-when-cross-origin",
},
},
"/_nuxt/**": {
headers: { "cache-control": "public, max-age=31536000, immutable" },
},
},
css: ["@/assets/css/main.css"], css: ["@/assets/css/main.css"],
app: {
head: {
titleTemplate: "%s - Tootaio Studio",
meta: [
{ name: "viewport", content: "width=device-width, initial-scale=1" },
{ charset: "utf-8" },
],
},
},
i18n: { i18n: {
defaultLocale: "en", defaultLocale: "en",
locales: [ locales: [
@@ -27,15 +61,16 @@ export default defineNuxtConfig({
code: "en", code: "en",
iso: "en-US", iso: "en-US",
name: "English", name: "English",
files: ["en-US/index.json"], files: ["en-US/common.json", "en-US/index.json"],
}, },
{ {
code: "zh-CN", code: "zh-CN",
iso: "zh-CN", iso: "zh-CN",
name: "简体中文", name: "简体中文",
files: ["zh-CN/index.json"], files: ["zh-CN/common.json", "zh-CN/index.json"],
}, },
], ],
strategy: "no_prefix",
}, },
seo: { seo: {
meta: { meta: {
@@ -52,4 +87,7 @@ export default defineNuxtConfig({
twitterImage: DEFAULT_SEO.image, twitterImage: DEFAULT_SEO.image,
}, },
}, },
site: {
url: "https://tootaio.com",
},
}); });

View File

@@ -10,6 +10,7 @@
"postinstall": "nuxt prepare" "postinstall": "nuxt prepare"
}, },
"dependencies": { "dependencies": {
"@iconify-json/mdi": "^1.2.3",
"@nuxt/content": "3.8.0", "@nuxt/content": "3.8.0",
"@nuxt/eslint": "1.10.0", "@nuxt/eslint": "1.10.0",
"@nuxt/ui": "4.1.0", "@nuxt/ui": "4.1.0",

2197
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

BIN
public/images/xiaomai.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 MiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 17 KiB

View File

@@ -1,3 +0,0 @@
<svg width="67" height="41" viewBox="0 0 67 41" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M45.0353 4.66312C45.8331 3.77669 46.7195 3.04539 47.6281 2.46921C49.2236 1.47198 50.9079 0.940125 52.6364 0.940125V15.411C51.3732 11.0232 48.6475 7.25591 45.0353 4.66312ZM66.5533 40.9401H15.2957C6.87461 40.9401 0.0712891 34.1146 0.0712891 25.7157C0.0712891 17.6714 6.3206 11.0675 14.232 10.5135V0.940125C16.0048 0.940125 17.7555 1.44982 19.3954 2.46921C20.304 3.02323 21.1904 3.75453 21.9882 4.59663C25.2458 2.31409 29.1904 0.984446 33.4674 0.984446C33.4674 10.2254 30.1433 20.9734 19.3289 20.9955H33.3566C32.9577 19.2005 31.3178 17.8709 29.3677 17.8487H37.5228C35.5727 17.8487 33.9328 19.2005 33.5339 21.0177H46.6087C49.2236 21.0177 51.8164 21.5274 54.2541 22.5468C56.6696 23.544 58.8857 25.0288 60.725 26.8681C62.5865 28.7296 64.0491 30.9235 65.0464 33.339C66.0436 35.7324 66.5533 38.3252 66.5533 40.9401ZM22.8525 10.7795C23.1849 11.6437 24.0713 12.6188 25.3123 13.3279C26.5533 14.0371 27.8386 14.3252 28.7472 14.1922C28.4148 13.3279 27.5284 12.3529 26.2874 11.6437C25.0464 10.9346 23.761 10.6465 22.8525 10.7795ZM41.5117 13.3279C40.2707 14.0371 38.9854 14.3252 38.0768 14.1922C38.4092 13.3279 39.2957 12.3529 40.5367 11.6437C41.7777 10.9346 43.063 10.6465 43.9716 10.7795C43.6613 11.6437 42.7527 12.6188 41.5117 13.3279Z" fill="#283841"></path>
</svg>

Before

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -1,3 +0,0 @@
<svg width="35" height="40" viewBox="0 0 35 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M29.4554 2.43478V0H35V22.4348C35 32.1358 27.165 40 17.5 40C8.24271 40 0.664262 32.7853 0.0413736 23.6522H0V0H5.54455V2.43478L14.901 2.43478V0H20.4455V2.43478L29.4554 2.43478ZM29.4554 22.4348V19.0202C28.8318 19.6656 28.1633 20.2785 27.4539 20.8558C25.1121 22.7615 22.3612 24.2503 19.369 25.2589C16.3764 26.2677 13.1833 26.7826 9.96797 26.7826H6.35343C8.08848 31.2608 12.425 34.4348 17.5 34.4348C24.1028 34.4348 29.4554 29.0622 29.4554 22.4348ZM15.4269 18.2435C14.3706 19.3674 13.18 20.3419 11.8852 21.1425C13.8545 20.9882 15.7827 20.5971 17.6038 19.9833C20.013 19.1712 22.1698 17.9913 23.9621 16.5329C25.7535 15.075 27.136 13.3757 28.0645 11.5515C28.6507 10.3998 29.0518 9.20727 29.2674 8H20.2671C20.0641 9.47968 19.6891 10.9319 19.1475 12.3231C18.2893 14.5274 17.0275 16.5405 15.4269 18.2435ZM5.54455 17.8146V8H14.6483C14.4948 8.78546 14.2724 9.55482 13.9832 10.2975C13.3786 11.8506 12.4962 13.2517 11.3938 14.4246C10.2918 15.5971 8.99228 16.518 7.57404 17.143C6.91535 17.4333 6.23601 17.6576 5.54455 17.8146Z" fill="#1D3AA7"></path>
</svg>

Before

Width:  |  Height:  |  Size: 1.2 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 25 KiB

View File

@@ -1,4 +0,0 @@
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M0 20C0 12.5231 0 8.78461 1.60769 6C2.66091 4.17577 4.17577 2.66091 6 1.60769C8.78461 0 12.5231 0 20 0C27.4769 0 31.2154 0 34 1.60769C35.8242 2.66091 37.3391 4.17577 38.3923 6C40 8.78461 40 12.5231 40 20C40 27.4769 40 31.2154 38.3923 34C37.3391 35.8242 35.8242 37.3391 34 38.3923C31.2154 40 27.4769 40 20 40C12.5231 40 8.78461 40 6 38.3923C4.17577 37.3391 2.66091 35.8242 1.60769 34C0 31.2154 0 27.4769 0 20Z" fill="#00DC33"></path>
<path fill-rule="evenodd" clip-rule="evenodd" d="M28.0441 7.60927C28.8868 6.80331 30.2152 6.79965 31.0622 7.58229L31.1425 7.66005L31.4164 7.94729C34.1911 10.9318 35.2251 14.4098 34.9599 17.8065C34.6908 21.2511 33.1012 24.4994 30.8836 27.0664C28.6673 29.6316 25.7084 31.6519 22.51 32.5287C19.2714 33.4164 15.7294 33.1334 12.6547 30.9629C10.0469 29.1218 9.05406 26.1465 8.98661 23.2561C7.52323 22.5384 5.98346 21.6463 4.36789 20.5615L3.941 20.2716L3.85006 20.206C2.93285 19.5053 2.72313 18.2084 3.39161 17.2564C4.06029 16.3043 5.36233 16.046 6.34665 16.6512L6.44134 16.7126L6.83024 16.9771C7.79805 17.6269 8.72153 18.1903 9.59966 18.6767C10.1661 16.6889 11.1047 14.7802 12.3413 13.207C14.1938 10.8501 16.9713 8.96525 20.374 9.24647C23.439 9.49995 25.7036 11.081 26.8725 13.3122C28.0044 15.4728 28.0211 18.0719 27.0319 20.307C26.0234 22.5857 23.976 24.484 21.0309 25.2662C18.9114 25.8291 16.4284 25.7905 13.6267 25.0367V25.0377C12.5115 24.7375 11.3427 24.323 10.1212 23.7846C9.8472 23.6638 9.60873 23.8483 10.1212 24.1686C11.5636 25.1924 13.5956 26.0505 14.1836 26.3385C14.4615 26.788 14.8061 27.1568 15.2011 27.4356C17.0188 28.7188 19.1451 28.9539 21.3396 28.3523C23.5743 27.7397 25.8141 26.2625 27.5514 24.2516C29.2873 22.2423 30.4065 19.8348 30.5909 17.4727C30.765 15.2439 30.1218 12.9543 28.1842 10.8736L27.9927 10.6731L27.9162 10.5906C27.1538 9.72748 27.2018 8.41516 28.0441 7.60927ZM20.0092 13.5651C18.6033 13.4489 17.1196 14.189 15.8013 15.8662C14.7973 17.1436 14.0376 18.8033 13.6503 20.5112C16.4093 21.4544 18.4655 21.4608 19.8942 21.0814C21.5481 20.6422 22.5399 19.6477 23.0172 18.5693C23.5137 17.4472 23.4628 16.2245 22.9813 15.3055C22.5369 14.4571 21.6422 13.7002 20.0092 13.5651Z" fill="#ffffff"></path>
</svg>

Before

Width:  |  Height:  |  Size: 2.2 KiB

View File

@@ -1,4 +0,0 @@
<svg width="55" height="41" viewBox="0 0 55 41" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M35.5 0.5C45.9934 0.5 54.5 9.00659 54.5 19.5V40.5C50.0817 40.5 46.5 36.9183 46.5 32.5V19.5C46.5 13.4249 41.5751 8.5 35.5 8.5H30.2988C27.6467 8.50004 25.1029 9.55339 23.2275 11.4287L9.67188 24.9854C8.92179 25.7354 8.50006 26.7527 8.5 27.8135V28.5C8.5 30.7091 10.2909 32.5 12.5 32.5H30.5C30.5 36.7801 27.1389 40.2748 22.9121 40.4893L22.5 40.5H12.5C5.87259 40.5 0.5 35.1274 0.5 28.5V27.8135C0.500062 24.631 1.76427 21.5785 4.01465 19.3281L17.5713 5.77246C20.9469 2.39685 25.525 0.500044 30.2988 0.5H35.5Z" fill="#FF500B"></path>
<path d="M37.5 12.5C40.2614 12.5 42.5 14.7386 42.5 17.5V40.5C38.0817 40.5 34.5 36.9183 34.5 32.5V20.5H31.1562L24.6207 27.0355C23.683 27.9732 22.4113 28.5 21.0852 28.5H12.9775C12.5588 28.5 12.3491 27.9937 12.6452 27.6976L26.3789 13.9648C27.3165 13.0272 28.588 12.5 29.9141 12.5H37.5Z" fill="#FF500B"></path>
</svg>

Before

Width:  |  Height:  |  Size: 944 B

View File

@@ -1,4 +0,0 @@
<svg width="100%" height="100%" viewBox="0 0 56 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M43 0C50.1797 6.44277e-07 56 5.8203 56 13C56 20.1797 50.1797 26 43 26H34.4844L48.4844 40H31.5156L15.7578 24.2422C14.672 23.1564 14 21.6569 14 20C14 16.6863 16.6863 14 20 14H43C43.5523 14 44 13.5523 44 13C44 12.4477 43.5523 12 43 12H20C15.5817 12 12 15.5817 12 20C12 22.3901 13.0482 24.5347 14.71 26H14.6875L28.6875 40H20C8.95431 40 0 31.0457 0 20C0 8.95431 8.9543 0 20 0H43Z" fill="#3902FF"></path>
<path d="M56 28V40H51.3125L39.3125 28H56Z" fill="#3902FF"></path>
</svg>

Before

Width:  |  Height:  |  Size: 580 B