diff --git a/app/composables/LocalizedCollection.ts b/app/composables/LocalizedCollection.ts new file mode 100644 index 0000000..25a2546 --- /dev/null +++ b/app/composables/LocalizedCollection.ts @@ -0,0 +1,66 @@ +// /composables/useLocalizedCollection.ts +import type { Collections } from "@nuxt/content"; + +export type UseLocalizedOptions = { + /** 默认 locale -> suffix 映射 */ + localeMap?: Record; + /** 回退 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( + `${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; + }); +} diff --git a/app/composables/NavLinks.ts b/app/composables/NavLinks.ts index 10d361a..44c989f 100644 --- a/app/composables/NavLinks.ts +++ b/app/composables/NavLinks.ts @@ -13,6 +13,7 @@ export const useNavLinks = () => { 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"), diff --git a/app/pages/index.vue b/app/pages/index.vue index ddb0b97..15aee9e 100644 --- a/app/pages/index.vue +++ b/app/pages/index.vue @@ -71,47 +71,7 @@ + + diff --git a/content.config.ts b/content.config.ts index f222fb7..073d870 100644 --- a/content.config.ts +++ b/content.config.ts @@ -40,6 +40,54 @@ const defineIndexSchema = () => }), }); +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( + z.object({ + title: z.string().min(1), + description: z.string().optional(), + price: z.string().optional(), // 保持为 string(例如 "RM499 起"),如需更严可用 regex + discount: z.string().optional(), + tagline: z.string().optional(), + highlight: z.boolean().optional().default(false), + features: z.array(z.string()).optional().default([]), + button: z + .object({ + label: z.string().min(1), + href: z.string().url().optional(), // 如果你可能会传链接 + // 预留扩展字段(例如:variant、target 等) + variant: z + .enum([ + "link", + "solid", + "outline", + "soft", + "subtle", + "ghost", + ]) + .default("subtle"), + target: z.enum(["_self", "_blank"]).optional(), + }) + .partial({ href: true, variant: true, target: true }) // 全部可选除 label(如果你希望 label 也可选,可把 min(1) 去掉或改 optional) + .optional(), + }) + ) + .min(1), + // 预留扩展字段(例如:category、tags、hidden 等) + category: z.string().optional(), + tags: z.array(z.string()).optional(), + }) + ), + }); + export default defineContentConfig({ collections: { index_en: defineCollection({ @@ -52,5 +100,15 @@ export default defineContentConfig({ 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(), + }), }, }); diff --git a/content/en-US/webDev.yml b/content/en-US/webDev.yml new file mode 100644 index 0000000..267a608 --- /dev/null +++ b/content/en-US/webDev.yml @@ -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 (1–3 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 (4–6 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 8–12 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" diff --git a/content/zh-CN/webDev.yml b/content/zh-CN/webDev.yml new file mode 100644 index 0000000..8b62943 --- /dev/null +++ b/content/zh-CN/webDev.yml @@ -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: "预约企业方案"