From 773c4953b20e494a66d1db4aa2584fc354959136 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E7=90=A6?= Date: Tue, 4 Aug 2026 08:45:09 +0800 Subject: [PATCH] =?UTF-8?q?1.=20=E8=BF=8E=E5=AE=BE=E6=B5=B7=E6=8A=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- hunliji-api/hotelwelcome/welcome.go | 44 +- vite-tailwindcss/package-lock.json | 60 ++ vite-tailwindcss/package.json | 6 + .../src/composables/hotelWelcomeDefaults.js | 697 +++++++++++------- vite-tailwindcss/src/style.css | 18 +- .../views/admin/HotelWelcomePosterAdmin.vue | 61 +- 6 files changed, 574 insertions(+), 312 deletions(-) diff --git a/hunliji-api/hotelwelcome/welcome.go b/hunliji-api/hotelwelcome/welcome.go index a01bbda..c4b9b03 100644 --- a/hunliji-api/hotelwelcome/welcome.go +++ b/hunliji-api/hotelwelcome/welcome.go @@ -19,33 +19,47 @@ var ( ) var allowedTemplates = map[string]bool{ - "banner-bottom": true, - "banner-top": true, - "banner-center": true, - "banner-names": true, + "banner-bottom": true, + "banner-top": true, + "banner-center": true, + "banner-names": true, + "banner-lower-third": true, + "banner-split-names": true, + "banner-frame-bottom": true, + "banner-minimal": true, + "art-welcome-brush": true, + "art-love-life": true, + "art-gaobai": true, + "art-married": true, + "art-hand-love": true, + "art-like-you": true, + "art-give-home": true, + "art-xi-welcome": true, + "art-wujia": true, + "art-tianci": true, + "art-tianzuo": true, + "art-welcome-v": true, + "art-longest": true, + "art-just-love": true, + "custom": true, + // 旧 key 兼容 "banner-zh": true, - "banner-lower-third": true, "banner-upper-third": true, "banner-mid-band": true, "banner-left-stack": true, "banner-right-stack": true, - "banner-split-names": true, "banner-vertical-left": true, "banner-vertical-right": true, "banner-hero-name": true, "banner-date-focus": true, "banner-welcome-en": true, - "banner-minimal": true, "banner-xi-corner": true, "banner-top-bottom": true, - "banner-frame-bottom": true, - "custom": true, - // 旧 key 兼容 - "welcome-bottom": true, - "welcome-top": true, - "welcome-left": true, - "welcome-right": true, - "welcome-center": true, + "welcome-bottom": true, + "welcome-top": true, + "welcome-left": true, + "welcome-right": true, + "welcome-center": true, } // Register 酒店迎宾海报 CRUD(均需管理员) diff --git a/vite-tailwindcss/package-lock.json b/vite-tailwindcss/package-lock.json index 41cde13..b114bb0 100644 --- a/vite-tailwindcss/package-lock.json +++ b/vite-tailwindcss/package-lock.json @@ -11,8 +11,14 @@ "@ant-design-vue/pro-layout": "^3.2.5", "@ant-design/icons-vue": "^7.0.1", "@fingerprintjs/fingerprintjs": "^5.2.0", + "@fontsource/cinzel": "^5.3.0", "@fontsource/great-vibes": "^5.3.0", + "@fontsource/liu-jian-mao-cao": "^5.3.0", + "@fontsource/long-cang": "^5.3.0", "@fontsource/ma-shan-zheng": "^5.3.0", + "@fontsource/pinyon-script": "^5.3.0", + "@fontsource/playfair-display": "^5.3.0", + "@fontsource/zhi-mang-xing": "^5.3.0", "@fortawesome/fontawesome-free": "^6.7.2", "@tailwindcss/vite": "^4.1.4", "@vueuse/head": "^2.0.0", @@ -588,6 +594,15 @@ "integrity": "sha512-j+2nInkwCQNTJcNhOjvkGM/nLRTuGJTC6xai4quqvUpjob2ssrGwBZjS7k55nOmKvge7qvJT2nS3i/IRvQSTQA==", "license": "MIT" }, + "node_modules/@fontsource/cinzel": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/@fontsource/cinzel/-/cinzel-5.3.0.tgz", + "integrity": "sha512-KEOYTsrRppW0uRIPWBDPrQgunCS6f8u4nzZxYRBfo9SCknKojLYjK7B0ZoVmJhyVLefz271ZQRF9EOpHZlfhRw==", + "license": "OFL-1.1", + "funding": { + "url": "https://github.com/sponsors/ayuhito" + } + }, "node_modules/@fontsource/great-vibes": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/@fontsource/great-vibes/-/great-vibes-5.3.0.tgz", @@ -597,6 +612,24 @@ "url": "https://github.com/sponsors/ayuhito" } }, + "node_modules/@fontsource/liu-jian-mao-cao": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/@fontsource/liu-jian-mao-cao/-/liu-jian-mao-cao-5.3.0.tgz", + "integrity": "sha512-OaFm4okm77XoGA9jp1J89tDjdWlzb68yJCUL7LV75QxUWF/qOBRVqmhP7XFFsgcn/PL+nc2dPvRGT/8yit5LZg==", + "license": "OFL-1.1", + "funding": { + "url": "https://github.com/sponsors/ayuhito" + } + }, + "node_modules/@fontsource/long-cang": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/@fontsource/long-cang/-/long-cang-5.3.0.tgz", + "integrity": "sha512-lCcBJb+LWZpYCVtGvQRiZPy5/EaGPuYLycpJXUIOz28EYGGqhTVWBRqY6ucaW8l1F8gvfjutY1PoAPUrFiP0wg==", + "license": "OFL-1.1", + "funding": { + "url": "https://github.com/sponsors/ayuhito" + } + }, "node_modules/@fontsource/ma-shan-zheng": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/@fontsource/ma-shan-zheng/-/ma-shan-zheng-5.3.0.tgz", @@ -606,6 +639,33 @@ "url": "https://github.com/sponsors/ayuhito" } }, + "node_modules/@fontsource/pinyon-script": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/@fontsource/pinyon-script/-/pinyon-script-5.3.0.tgz", + "integrity": "sha512-ilhy4l5apzyDkfuKb7PZBUT5Sf6nfxucdXT5TQ0wQDyWtDPlouLy1lQq3lMEPk1lPsHHVrd11EkPnEG8FqDgUA==", + "license": "OFL-1.1", + "funding": { + "url": "https://github.com/sponsors/ayuhito" + } + }, + "node_modules/@fontsource/playfair-display": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/@fontsource/playfair-display/-/playfair-display-5.3.0.tgz", + "integrity": "sha512-uJSABalvdWc10fEPppQjQocamT21LEyRfzzWfRfqbkPz+sRv+kKpYDKyluBUskvJREkJTos9epj1WjQROGu99Q==", + "license": "OFL-1.1", + "funding": { + "url": "https://github.com/sponsors/ayuhito" + } + }, + "node_modules/@fontsource/zhi-mang-xing": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/@fontsource/zhi-mang-xing/-/zhi-mang-xing-5.3.0.tgz", + "integrity": "sha512-V21yCCm5o2PHdXF2P9KKh7uiqL8QFFuII4DRQkpG24tfyDzJPchXKsxkcvKM43xEPvQKdbhHCBbXEFjcm/TUCQ==", + "license": "OFL-1.1", + "funding": { + "url": "https://github.com/sponsors/ayuhito" + } + }, "node_modules/@fortawesome/fontawesome-free": { "version": "6.7.2", "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-6.7.2.tgz", diff --git a/vite-tailwindcss/package.json b/vite-tailwindcss/package.json index 2cb3432..8bcb065 100644 --- a/vite-tailwindcss/package.json +++ b/vite-tailwindcss/package.json @@ -12,8 +12,14 @@ "@ant-design-vue/pro-layout": "^3.2.5", "@ant-design/icons-vue": "^7.0.1", "@fingerprintjs/fingerprintjs": "^5.2.0", + "@fontsource/cinzel": "^5.3.0", "@fontsource/great-vibes": "^5.3.0", + "@fontsource/liu-jian-mao-cao": "^5.3.0", + "@fontsource/long-cang": "^5.3.0", "@fontsource/ma-shan-zheng": "^5.3.0", + "@fontsource/pinyon-script": "^5.3.0", + "@fontsource/playfair-display": "^5.3.0", + "@fontsource/zhi-mang-xing": "^5.3.0", "@fortawesome/fontawesome-free": "^6.7.2", "@tailwindcss/vite": "^4.1.4", "@vueuse/head": "^2.0.0", diff --git a/vite-tailwindcss/src/composables/hotelWelcomeDefaults.js b/vite-tailwindcss/src/composables/hotelWelcomeDefaults.js index 708337d..bb66092 100644 --- a/vite-tailwindcss/src/composables/hotelWelcomeDefaults.js +++ b/vite-tailwindcss/src/composables/hotelWelcomeDefaults.js @@ -1,15 +1,40 @@ -/** 酒店迎宾易拉宝:婚礼欢迎海报模板与文字图层(非按贵宾制) */ +/** 酒店迎宾易拉宝:普通文字 + 艺术字模板 */ + +/** 字体预设:key 存库,css 用于渲染 */ +export const TEXT_FONTS = [ + { value: 'serif', label: '衬线正文', css: 'var(--font-serif)', sample: '新郎新娘' }, + { value: 'sans', label: '无衬线', css: 'var(--font-sans)', sample: 'WEDDING' }, + { value: 'display', label: '西文衬线', css: 'var(--font-display)', sample: 'Wedding' }, + { value: 'cinzel', label: '典雅大写', css: 'var(--font-cinzel)', sample: 'WEDDING' }, + { value: 'script', label: '英文手写', css: 'var(--font-calligraphy)', sample: 'Wedding' }, + { value: 'mashan', label: '马善政体', css: 'var(--font-mashan)', sample: '囍' }, + { value: 'brush', label: '芝麻行书', css: 'var(--font-brush)', sample: '我们结婚啦' }, + { value: 'longcang', label: '龙藏体', css: 'var(--font-longcang)', sample: '吾家有喜' }, + { value: 'liujian', label: '刘建毛草', css: 'var(--font-liujian)', sample: '以爱之名' }, + { value: 'kai', label: '楷体', css: 'var(--font-kai)', sample: '欢迎光临' }, +] + +const FONT_MAP = Object.fromEntries(TEXT_FONTS.map((f) => [f.value, f])) + +export function resolveFontCss(key) { + return FONT_MAP[key]?.css || FONT_MAP.serif.css +} + +export function fontLabel(key) { + return FONT_MAP[key]?.label || key || '衬线正文' +} /** - * zones: 模态框预览用的文字占位区域(百分比) - * texts 由 defaultTextsForTemplate 提供,可与 zones 大致对应 + * zones: 模态预览占位 + * artistic: 是否艺术字模板(分组展示) */ export const HOTEL_WELCOME_TEMPLATES = [ + // —— 普通文字 —— { value: 'banner-bottom', label: '底部信息', - group: '经典', - desc: '全幅婚纱照 + 底部 WEDDING / 姓名 / 日期', + group: '普通文字', + desc: 'WEDDING / 姓名 / 日期叠底部', zones: [ { x: 18, y: 66, w: 64, h: 7, label: 'WEDDING' }, { x: 22, y: 76, w: 56, h: 6, label: '姓名' }, @@ -20,8 +45,8 @@ export const HOTEL_WELCOME_TEMPLATES = [ { value: 'banner-top', label: '顶部欢迎', - group: '经典', - desc: '顶部 Welcome 与姓名,适合下半画面主体', + group: '普通文字', + desc: '顶部 Welcome 与姓名', zones: [ { x: 16, y: 8, w: 68, h: 5, label: 'Welcome' }, { x: 20, y: 16, w: 60, h: 7, label: '姓名' }, @@ -31,8 +56,8 @@ export const HOTEL_WELCOME_TEMPLATES = [ { value: 'banner-center', label: '居中大字', - group: '经典', - desc: '画面中部叠 WEDDING 与新人名', + group: '普通文字', + desc: '中部 WEDDING + 姓名', zones: [ { x: 14, y: 40, w: 72, h: 9, label: 'WEDDING' }, { x: 22, y: 52, w: 56, h: 6, label: '姓名' }, @@ -42,217 +67,278 @@ export const HOTEL_WELCOME_TEMPLATES = [ { value: 'banner-names', label: '姓名主视觉', - group: '经典', - desc: '新人姓名作主标题,辅以日期场地', + group: '普通文字', + desc: '姓名作主标题', zones: [ { x: 28, y: 56, w: 44, h: 4, label: 'OUR WEDDING' }, { x: 30, y: 64, w: 40, h: 7, label: '新郎' }, { x: 46, y: 74, w: 8, h: 4, label: '&' }, { x: 30, y: 81, w: 40, h: 7, label: '新娘' }, - { x: 22, y: 92, w: 56, h: 4, label: '日期·场地' }, - ], - }, - { - value: 'banner-zh', - label: '中式欢迎', - group: '中式', - desc: '囍 + 中文欢迎语 + 姓名日期', - zones: [ - { x: 42, y: 8, w: 16, h: 8, label: '囍' }, - { x: 16, y: 70, w: 68, h: 6, label: '欢迎光临' }, - { x: 22, y: 80, w: 56, h: 6, label: '姓名' }, - { x: 24, y: 90, w: 52, h: 4, label: '日期' }, + { x: 22, y: 92, w: 56, h: 4, label: '日期' }, ], }, { value: 'banner-lower-third', label: '下三分之一', - group: '分区', - desc: '文字集中在画面底部三分之一', + group: '普通文字', + desc: '文字集中在底部', zones: [ { x: 20, y: 70, w: 60, h: 5, label: 'Welcome' }, { x: 18, y: 78, w: 64, h: 8, label: '姓名' }, { x: 30, y: 90, w: 40, h: 4, label: '日期' }, ], }, - { - value: 'banner-upper-third', - label: '上三分之一', - group: '分区', - desc: '文字集中在顶部三分之一', - zones: [ - { x: 22, y: 8, w: 56, h: 8, label: 'WEDDING' }, - { x: 20, y: 20, w: 60, h: 6, label: '姓名' }, - { x: 30, y: 30, w: 40, h: 4, label: '日期' }, - ], - }, - { - value: 'banner-mid-band', - label: '中部横带', - group: '分区', - desc: '中部一条信息带:标题 + 姓名 + 日期', - zones: [ - { x: 10, y: 44, w: 80, h: 5, label: 'WEDDING' }, - { x: 18, y: 52, w: 64, h: 6, label: '姓名' }, - { x: 28, y: 61, w: 44, h: 4, label: '日期' }, - ], - }, - { - value: 'banner-left-stack', - label: '左侧堆叠', - group: '对齐', - desc: '文字左对齐堆叠在左侧', - zones: [ - { x: 8, y: 68, w: 42, h: 5, label: 'WEDDING' }, - { x: 8, y: 76, w: 48, h: 6, label: '姓名' }, - { x: 8, y: 86, w: 36, h: 4, label: '日期' }, - { x: 8, y: 92, w: 40, h: 4, label: '场地' }, - ], - }, - { - value: 'banner-right-stack', - label: '右侧堆叠', - group: '对齐', - desc: '文字右对齐堆叠在右侧', - zones: [ - { x: 50, y: 68, w: 42, h: 5, label: 'WEDDING' }, - { x: 44, y: 76, w: 48, h: 6, label: '姓名' }, - { x: 56, y: 86, w: 36, h: 4, label: '日期' }, - { x: 52, y: 92, w: 40, h: 4, label: '场地' }, - ], - }, { value: 'banner-split-names', label: '左右姓名', - group: '对齐', - desc: '新郎 / 新娘分列两侧,中间 &', + group: '普通文字', + desc: '新郎新娘分列', zones: [ { x: 22, y: 72, w: 22, h: 7, label: '新郎' }, { x: 46, y: 74, w: 8, h: 5, label: '&' }, { x: 56, y: 72, w: 22, h: 7, label: '新娘' }, { x: 28, y: 86, w: 44, h: 4, label: '日期' }, - { x: 24, y: 92, w: 52, h: 4, label: 'Welcome' }, - ], - }, - { - value: 'banner-vertical-left', - label: '左侧竖排', - group: '竖排', - desc: '左侧竖排中文欢迎与姓名', - zones: [ - { x: 8, y: 28, w: 8, h: 36, label: '欢迎', vertical: true }, - { x: 20, y: 30, w: 10, h: 40, label: '姓名', vertical: true }, - { x: 34, y: 38, w: 8, h: 24, label: '日期', vertical: true }, - ], - }, - { - value: 'banner-vertical-right', - label: '右侧竖排', - group: '竖排', - desc: '右侧竖排中文欢迎与姓名', - zones: [ - { x: 84, y: 28, w: 8, h: 36, label: '欢迎', vertical: true }, - { x: 70, y: 30, w: 10, h: 40, label: '姓名', vertical: true }, - { x: 58, y: 38, w: 8, h: 24, label: '日期', vertical: true }, - ], - }, - { - value: 'banner-hero-name', - label: '超大姓名', - group: '强调', - desc: '姓名超大居中偏下,副标题精简', - zones: [ - { x: 12, y: 62, w: 76, h: 12, label: '新郎 & 新娘' }, - { x: 28, y: 78, w: 44, h: 4, label: 'WEDDING' }, - { x: 32, y: 86, w: 36, h: 4, label: '日期' }, - ], - }, - { - value: 'banner-date-focus', - label: '日期主视觉', - group: '强调', - desc: '大号日期居中,姓名与标题辅之', - zones: [ - { x: 24, y: 58, w: 52, h: 5, label: 'OUR WEDDING' }, - { x: 16, y: 68, w: 68, h: 10, label: '2026.10.01' }, - { x: 22, y: 82, w: 56, h: 6, label: '姓名' }, - { x: 28, y: 92, w: 44, h: 4, label: '场地' }, - ], - }, - { - value: 'banner-welcome-en', - label: '英文欢迎', - group: '强调', - desc: '英文 Welcome 为主,姓名日期居下', - zones: [ - { x: 10, y: 64, w: 80, h: 6, label: 'Welcome to' }, - { x: 14, y: 73, w: 72, h: 8, label: 'our Wedding' }, - { x: 22, y: 85, w: 56, h: 5, label: '姓名' }, - { x: 32, y: 93, w: 36, h: 3, label: '日期' }, - ], - }, - { - value: 'banner-minimal', - label: '极简底部', - group: '极简', - desc: '仅底部两行:姓名 + 日期', - zones: [ - { x: 24, y: 82, w: 52, h: 6, label: '姓名' }, - { x: 32, y: 92, w: 36, h: 4, label: '日期' }, - ], - }, - { - value: 'banner-xi-corner', - label: '角标囍', - group: '中式', - desc: '角落囍字 + 底部婚礼信息', - zones: [ - { x: 8, y: 6, w: 14, h: 8, label: '囍' }, - { x: 18, y: 72, w: 64, h: 6, label: 'WEDDING' }, - { x: 22, y: 82, w: 56, h: 6, label: '姓名' }, - { x: 28, y: 92, w: 44, h: 4, label: '日期' }, - ], - }, - { - value: 'banner-top-bottom', - label: '上下呼应', - group: '分区', - desc: '顶部标题 + 底部姓名日期', - zones: [ - { x: 18, y: 8, w: 64, h: 6, label: 'WEDDING' }, - { x: 24, y: 16, w: 52, h: 4, label: 'Welcome' }, - { x: 20, y: 80, w: 60, h: 7, label: '姓名' }, - { x: 30, y: 91, w: 40, h: 4, label: '日期' }, ], }, { value: 'banner-frame-bottom', label: '底栏卡片', - group: '分区', - desc: '底部像信息卡:标题 / 姓名 / 日期场地', + group: '普通文字', + desc: '底部信息卡式排版', zones: [ - { x: 12, y: 68, w: 76, h: 5, label: 'OUR WEDDING DAY' }, + { x: 12, y: 68, w: 76, h: 5, label: 'OUR WEDDING' }, { x: 16, y: 76, w: 68, h: 7, label: '姓名' }, { x: 20, y: 86, w: 60, h: 4, label: '日期' }, { x: 24, y: 92, w: 52, h: 4, label: '场地' }, ], }, + { + value: 'banner-minimal', + label: '极简底部', + group: '普通文字', + desc: '仅姓名 + 日期', + zones: [ + { x: 24, y: 82, w: 52, h: 6, label: '姓名' }, + { x: 32, y: 92, w: 36, h: 4, label: '日期' }, + ], + }, + + // —— 艺术字(参考易拉宝常见排版)—— + { + value: 'art-welcome-brush', + label: '笔刷欢迎', + group: '艺术字', + artistic: true, + desc: '顶部书法「欢迎来我们的婚礼」+ 底部西文姓名', + zones: [ + { x: 12, y: 10, w: 76, h: 14, label: '欢迎来…', art: true }, + { x: 22, y: 78, w: 56, h: 5, label: '姓名' }, + { x: 20, y: 86, w: 60, h: 4, label: 'WELCOME' }, + { x: 32, y: 93, w: 36, h: 3, label: '日期' }, + ], + }, + { + value: 'art-love-life', + label: '以爱之名', + group: '艺术字', + artistic: true, + desc: '书法主句 + 底部 Welcome / 姓名', + zones: [ + { x: 14, y: 12, w: 72, h: 16, label: '以爱之名', art: true }, + { x: 24, y: 78, w: 52, h: 5, label: '姓名' }, + { x: 18, y: 87, w: 64, h: 4, label: 'WELCOME' }, + { x: 30, y: 93, w: 40, h: 3, label: '日期' }, + ], + }, + { + value: 'art-gaobai', + label: '告白', + group: '艺术字', + artistic: true, + desc: '英文手写 Wedding Day + 大字「告白」', + zones: [ + { x: 18, y: 38, w: 50, h: 6, label: 'Wedding Day', art: true }, + { x: 22, y: 48, w: 36, h: 16, label: '告白', art: true }, + { x: 24, y: 86, w: 52, h: 5, label: '姓名' }, + { x: 30, y: 93, w: 40, h: 3, label: '日期' }, + ], + }, + { + value: 'art-married', + label: '我们结婚啦', + group: '艺术字', + artistic: true, + desc: 'WEDDING 底字 + 笔刷「我们结婚啦」叠字', + zones: [ + { x: 10, y: 14, w: 80, h: 10, label: 'WEDDING' }, + { x: 14, y: 18, w: 72, h: 14, label: '我们结婚啦', art: true }, + { x: 18, y: 36, w: 64, h: 4, label: '【欢迎参加】' }, + { x: 20, y: 44, w: 60, h: 5, label: '姓名' }, + { x: 22, y: 88, w: 56, h: 4, label: '日期·酒店' }, + ], + }, + { + value: 'art-hand-love', + label: '携手热爱', + group: '艺术字', + artistic: true, + desc: '行草主句「携手热爱,奔赴山海」', + zones: [ + { x: 10, y: 14, w: 80, h: 14, label: '携手热爱…', art: true }, + { x: 20, y: 32, w: 60, h: 4, label: '【欢迎参加】' }, + { x: 22, y: 40, w: 56, h: 5, label: '姓名' }, + { x: 24, y: 88, w: 52, h: 4, label: '日期·酒店' }, + ], + }, + { + value: 'art-like-you', + label: '就是喜欢你', + group: '艺术字', + artistic: true, + desc: '金色笔刷主句 + 底部信息', + zones: [ + { x: 12, y: 16, w: 76, h: 14, label: '就是喜欢你', art: true }, + { x: 20, y: 34, w: 60, h: 4, label: '【欢迎参加】' }, + { x: 22, y: 42, w: 56, h: 5, label: '姓名' }, + { x: 24, y: 88, w: 52, h: 4, label: '日期·酒店' }, + ], + }, + { + value: 'art-give-home', + label: '给她一个家', + group: '艺术字', + artistic: true, + desc: '描边 WELCOME + 现代感中文主句', + zones: [ + { x: 12, y: 12, w: 76, h: 6, label: 'WELCOME' }, + { x: 16, y: 22, w: 68, h: 12, label: '给她一个家', art: true }, + { x: 20, y: 38, w: 60, h: 4, label: '【欢迎参加】' }, + { x: 22, y: 46, w: 56, h: 5, label: '姓名' }, + { x: 24, y: 88, w: 52, h: 4, label: '日期·酒店' }, + ], + }, + { + value: 'art-xi-welcome', + label: '囍·中式', + group: '艺术字', + artistic: true, + desc: '大囍 + 书法欢迎语', + zones: [ + { x: 38, y: 8, w: 24, h: 12, label: '囍', art: true }, + { x: 14, y: 68, w: 72, h: 10, label: '欢迎光临', art: true }, + { x: 22, y: 82, w: 56, h: 6, label: '姓名' }, + { x: 26, y: 92, w: 48, h: 4, label: '日期' }, + ], + }, + { + value: 'art-wujia', + label: '吾家有喜', + group: '艺术字', + artistic: true, + desc: '竖排书法主句 + 欢迎与日期', + zones: [ + { x: 18, y: 22, w: 14, h: 48, label: '吾家有喜', art: true, vertical: true }, + { x: 40, y: 30, w: 10, h: 36, label: '欢迎', vertical: true }, + { x: 58, y: 72, w: 32, h: 5, label: '姓名' }, + { x: 58, y: 82, w: 32, h: 4, label: '日期' }, + ], + }, + { + value: 'art-tianci', + label: '天赐良缘', + group: '艺术字', + artistic: true, + desc: '竖排书法 + 新人信息', + zones: [ + { x: 16, y: 20, w: 14, h: 50, label: '天赐良缘', art: true, vertical: true }, + { x: 38, y: 28, w: 10, h: 40, label: '婚礼', vertical: true }, + { x: 55, y: 70, w: 36, h: 5, label: '姓名' }, + { x: 55, y: 80, w: 36, h: 4, label: '日期' }, + { x: 55, y: 88, w: 36, h: 4, label: '酒店' }, + ], + }, + { + value: 'art-tianzuo', + label: '天作之合', + group: '艺术字', + artistic: true, + desc: '四字方阵艺术字 + 欢迎语', + zones: [ + { x: 18, y: 18, w: 28, h: 12, label: '天', art: true }, + { x: 50, y: 18, w: 28, h: 12, label: '作', art: true }, + { x: 18, y: 34, w: 28, h: 12, label: '之', art: true }, + { x: 50, y: 34, w: 28, h: 12, label: '合', art: true }, + { x: 22, y: 72, w: 56, h: 5, label: '欢迎参加' }, + { x: 24, y: 82, w: 52, h: 5, label: '姓名' }, + { x: 28, y: 92, w: 44, h: 4, label: '日期' }, + ], + }, + { + value: 'art-welcome-v', + label: '竖排欢迎', + group: '艺术字', + artistic: true, + desc: '左侧竖排书法欢迎全文', + zones: [ + { x: 10, y: 18, w: 12, h: 58, label: '欢迎参加…', art: true, vertical: true }, + { x: 30, y: 22, w: 8, h: 28, label: 'Welcome', vertical: true }, + { x: 48, y: 78, w: 42, h: 5, label: '姓名' }, + { x: 48, y: 88, w: 42, h: 4, label: '日期' }, + ], + }, + { + value: 'art-longest', + label: '最长的告白', + group: '艺术字', + artistic: true, + desc: '笔刷主句 + 英文手写叠字', + zones: [ + { x: 12, y: 12, w: 76, h: 12, label: '最长的告白', art: true }, + { x: 16, y: 24, w: 68, h: 6, label: 'Accompanying', art: true }, + { x: 22, y: 78, w: 56, h: 5, label: '姓名' }, + { x: 26, y: 88, w: 48, h: 4, label: '日期' }, + ], + }, + { + value: 'art-just-love', + label: '就爱这样的你', + group: '艺术字', + artistic: true, + desc: '现代笔刷主句 + 括号欢迎语', + zones: [ + { x: 12, y: 14, w: 76, h: 14, label: '就爱这样的你', art: true }, + { x: 18, y: 32, w: 64, h: 4, label: '【欢迎参加】' }, + { x: 20, y: 40, w: 60, h: 5, label: '姓名' }, + { x: 22, y: 88, w: 56, h: 4, label: '日期·酒店' }, + ], + }, { value: 'custom', label: '自定义空白', group: '自定义', - desc: '无预设文字,自行添加并拖拽', + desc: '无预设,自行添加普通字 / 艺术字并拖拽', zones: [], }, ] -/** 旧模板 key → 新 key(兼容已保存数据) */ const LEGACY_TEMPLATE_MAP = { 'welcome-bottom': 'banner-bottom', 'welcome-top': 'banner-top', 'welcome-center': 'banner-center', - 'welcome-left': 'banner-vertical-left', - 'welcome-right': 'banner-vertical-right', + 'welcome-left': 'art-welcome-v', + 'welcome-right': 'banner-split-names', + 'banner-zh': 'art-xi-welcome', + 'banner-upper-third': 'banner-top', + 'banner-mid-band': 'banner-center', + 'banner-left-stack': 'banner-lower-third', + 'banner-right-stack': 'banner-lower-third', + 'banner-vertical-left': 'art-welcome-v', + 'banner-vertical-right': 'art-wujia', + 'banner-hero-name': 'banner-names', + 'banner-date-focus': 'banner-frame-bottom', + 'banner-welcome-en': 'banner-bottom', + 'banner-xi-corner': 'art-xi-welcome', + 'banner-top-bottom': 'banner-top', } export function resolveTemplate(value) { @@ -286,6 +372,7 @@ function uid() { } export function createTextLayer(partial = {}) { + const fontKey = FONT_MAP[partial.fontFamily] ? partial.fontFamily : 'serif' return { id: partial.id || uid(), content: partial.content ?? 'WEDDING', @@ -293,12 +380,14 @@ export function createTextLayer(partial = {}) { y: clampPct(partial.y ?? 70), fontSize: Number(partial.fontSize) > 0 ? Number(partial.fontSize) : 28, color: partial.color || '#ffffff', - fontFamily: partial.fontFamily || 'serif', + fontFamily: fontKey, fontWeight: partial.fontWeight || '600', align: partial.align || 'center', letterSpacing: Number.isFinite(Number(partial.letterSpacing)) ? Number(partial.letterSpacing) : 2, shadow: partial.shadow !== false, vertical: !!partial.vertical, + stroke: !!partial.stroke, + strokeColor: partial.strokeColor || 'rgba(255,255,255,0.35)', } } @@ -308,143 +397,179 @@ export function clampPct(n) { return Math.min(100, Math.max(0, v)) } -/** 各模板预设:婚礼迎宾易拉宝文案位(百分比坐标,可再拖拽) */ +function L(partial) { + return createTextLayer(partial) +} + +/** 各模板预设文案(含艺术字体) */ export function defaultTextsForTemplate(template) { switch (resolveTemplate(template)) { case 'banner-top': return [ - createTextLayer({ content: 'Welcome to our Wedding', x: 50, y: 10, fontSize: 18, letterSpacing: 3, fontWeight: '400' }), - createTextLayer({ content: '新郎 & 新娘', x: 50, y: 18, fontSize: 34, fontWeight: '700', letterSpacing: 4 }), - createTextLayer({ content: '2026.10.01', x: 50, y: 26, fontSize: 16, letterSpacing: 4, fontWeight: '400' }), + L({ content: 'Welcome to our Wedding', x: 50, y: 10, fontSize: 18, letterSpacing: 3, fontWeight: '400', fontFamily: 'display' }), + L({ content: '新郎 & 新娘', x: 50, y: 18, fontSize: 34, fontWeight: '700', letterSpacing: 4, fontFamily: 'serif' }), + L({ content: '2026.10.01', x: 50, y: 26, fontSize: 16, letterSpacing: 4, fontWeight: '400', fontFamily: 'sans' }), ] case 'banner-center': return [ - createTextLayer({ content: 'WEDDING', x: 50, y: 42, fontSize: 42, letterSpacing: 12, fontWeight: '700' }), - createTextLayer({ content: '新郎 & 新娘', x: 50, y: 54, fontSize: 28, letterSpacing: 6 }), - createTextLayer({ content: 'Welcome to our Wedding', x: 50, y: 62, fontSize: 14, letterSpacing: 2, fontWeight: '400' }), + L({ content: 'WEDDING', x: 50, y: 42, fontSize: 42, letterSpacing: 12, fontWeight: '700', fontFamily: 'cinzel' }), + L({ content: '新郎 & 新娘', x: 50, y: 54, fontSize: 28, letterSpacing: 6, fontFamily: 'serif' }), + L({ content: 'Welcome to our Wedding', x: 50, y: 62, fontSize: 14, letterSpacing: 2, fontWeight: '400', fontFamily: 'display' }), ] case 'banner-names': return [ - createTextLayer({ content: 'OUR WEDDING', x: 50, y: 58, fontSize: 14, letterSpacing: 8, fontWeight: '400' }), - createTextLayer({ content: '新郎', x: 50, y: 68, fontSize: 36, fontWeight: '700', letterSpacing: 10 }), - createTextLayer({ content: '&', x: 50, y: 76, fontSize: 22, fontWeight: '400' }), - createTextLayer({ content: '新娘', x: 50, y: 84, fontSize: 36, fontWeight: '700', letterSpacing: 10 }), - createTextLayer({ content: '2026.10.01 · 婚礼酒店', x: 50, y: 93, fontSize: 14, letterSpacing: 3, fontWeight: '400' }), - ] - case 'banner-zh': - return [ - createTextLayer({ content: '囍', x: 50, y: 12, fontSize: 36, color: '#c45c5c', letterSpacing: 0, shadow: false }), - createTextLayer({ content: '欢迎光临我们的婚礼', x: 50, y: 72, fontSize: 22, letterSpacing: 6 }), - createTextLayer({ content: '新郎 & 新娘', x: 50, y: 82, fontSize: 32, fontWeight: '700', letterSpacing: 4 }), - createTextLayer({ content: '谨定于 2026年10月1日', x: 50, y: 91, fontSize: 14, letterSpacing: 2, fontWeight: '400' }), + L({ content: 'OUR WEDDING', x: 50, y: 58, fontSize: 14, letterSpacing: 8, fontWeight: '400', fontFamily: 'cinzel' }), + L({ content: '新郎', x: 50, y: 68, fontSize: 36, fontWeight: '700', letterSpacing: 10, fontFamily: 'serif' }), + L({ content: '&', x: 50, y: 76, fontSize: 22, fontWeight: '400', fontFamily: 'script' }), + L({ content: '新娘', x: 50, y: 84, fontSize: 36, fontWeight: '700', letterSpacing: 10, fontFamily: 'serif' }), + L({ content: '2026.10.01 · 婚礼酒店', x: 50, y: 93, fontSize: 14, letterSpacing: 3, fontWeight: '400', fontFamily: 'sans' }), ] case 'banner-lower-third': return [ - createTextLayer({ content: 'Welcome to our Wedding', x: 50, y: 72, fontSize: 16, letterSpacing: 2, fontWeight: '400' }), - createTextLayer({ content: '新郎 & 新娘', x: 50, y: 81, fontSize: 32, fontWeight: '700', letterSpacing: 5 }), - createTextLayer({ content: '2026.10.01', x: 50, y: 91, fontSize: 15, letterSpacing: 4, fontWeight: '400' }), - ] - case 'banner-upper-third': - return [ - createTextLayer({ content: 'WEDDING', x: 50, y: 10, fontSize: 38, letterSpacing: 12, fontWeight: '700' }), - createTextLayer({ content: '新郎 & 新娘', x: 50, y: 22, fontSize: 26, letterSpacing: 5 }), - createTextLayer({ content: '2026.10.01', x: 50, y: 32, fontSize: 15, letterSpacing: 4, fontWeight: '400' }), - ] - case 'banner-mid-band': - return [ - createTextLayer({ content: 'WEDDING', x: 50, y: 46, fontSize: 28, letterSpacing: 14, fontWeight: '700' }), - createTextLayer({ content: '新郎 & 新娘', x: 50, y: 55, fontSize: 26, letterSpacing: 6 }), - createTextLayer({ content: '2026.10.01', x: 50, y: 63, fontSize: 14, letterSpacing: 4, fontWeight: '400' }), - ] - case 'banner-left-stack': - return [ - createTextLayer({ content: 'WEDDING', x: 12, y: 70, fontSize: 18, letterSpacing: 6, align: 'left', fontWeight: '700' }), - createTextLayer({ content: '新郎 & 新娘', x: 12, y: 78, fontSize: 26, letterSpacing: 3, align: 'left', fontWeight: '700' }), - createTextLayer({ content: '2026.10.01', x: 12, y: 87, fontSize: 14, letterSpacing: 2, align: 'left', fontWeight: '400' }), - createTextLayer({ content: '婚礼酒店', x: 12, y: 93, fontSize: 13, letterSpacing: 2, align: 'left', fontWeight: '400' }), - ] - case 'banner-right-stack': - return [ - createTextLayer({ content: 'WEDDING', x: 88, y: 70, fontSize: 18, letterSpacing: 6, align: 'right', fontWeight: '700' }), - createTextLayer({ content: '新郎 & 新娘', x: 88, y: 78, fontSize: 26, letterSpacing: 3, align: 'right', fontWeight: '700' }), - createTextLayer({ content: '2026.10.01', x: 88, y: 87, fontSize: 14, letterSpacing: 2, align: 'right', fontWeight: '400' }), - createTextLayer({ content: '婚礼酒店', x: 88, y: 93, fontSize: 13, letterSpacing: 2, align: 'right', fontWeight: '400' }), + L({ content: 'Welcome to our Wedding', x: 50, y: 72, fontSize: 16, letterSpacing: 2, fontWeight: '400', fontFamily: 'display' }), + L({ content: '新郎 & 新娘', x: 50, y: 81, fontSize: 32, fontWeight: '700', letterSpacing: 5, fontFamily: 'serif' }), + L({ content: '2026.10.01', x: 50, y: 91, fontSize: 15, letterSpacing: 4, fontWeight: '400', fontFamily: 'sans' }), ] case 'banner-split-names': return [ - createTextLayer({ content: '新郎', x: 28, y: 74, fontSize: 30, fontWeight: '700', letterSpacing: 8 }), - createTextLayer({ content: '&', x: 50, y: 75, fontSize: 20, fontWeight: '400' }), - createTextLayer({ content: '新娘', x: 72, y: 74, fontSize: 30, fontWeight: '700', letterSpacing: 8 }), - createTextLayer({ content: '2026.10.01', x: 50, y: 87, fontSize: 14, letterSpacing: 4, fontWeight: '400' }), - createTextLayer({ content: 'Welcome to our Wedding', x: 50, y: 93, fontSize: 12, letterSpacing: 2, fontWeight: '400' }), - ] - case 'banner-vertical-left': - return [ - createTextLayer({ content: '欢迎光临', x: 10, y: 42, fontSize: 18, vertical: true, letterSpacing: 10 }), - createTextLayer({ content: '新郎新娘', x: 22, y: 45, fontSize: 28, vertical: true, fontWeight: '700', letterSpacing: 8 }), - createTextLayer({ content: '2026.10.01', x: 34, y: 48, fontSize: 14, vertical: true, letterSpacing: 4, fontWeight: '400' }), - ] - case 'banner-vertical-right': - return [ - createTextLayer({ content: '欢迎光临', x: 90, y: 42, fontSize: 18, vertical: true, letterSpacing: 10 }), - createTextLayer({ content: '新郎新娘', x: 78, y: 45, fontSize: 28, vertical: true, fontWeight: '700', letterSpacing: 8 }), - createTextLayer({ content: '2026.10.01', x: 66, y: 48, fontSize: 14, vertical: true, letterSpacing: 4, fontWeight: '400' }), - ] - case 'banner-hero-name': - return [ - createTextLayer({ content: '新郎 & 新娘', x: 50, y: 68, fontSize: 40, fontWeight: '700', letterSpacing: 6 }), - createTextLayer({ content: 'WEDDING', x: 50, y: 80, fontSize: 16, letterSpacing: 10, fontWeight: '400' }), - createTextLayer({ content: '2026.10.01', x: 50, y: 88, fontSize: 15, letterSpacing: 4, fontWeight: '400' }), - ] - case 'banner-date-focus': - return [ - createTextLayer({ content: 'OUR WEDDING', x: 50, y: 60, fontSize: 14, letterSpacing: 8, fontWeight: '400' }), - createTextLayer({ content: '2026.10.01', x: 50, y: 72, fontSize: 40, fontWeight: '700', letterSpacing: 6 }), - createTextLayer({ content: '新郎 & 新娘', x: 50, y: 84, fontSize: 24, letterSpacing: 4 }), - createTextLayer({ content: '婚礼酒店', x: 50, y: 93, fontSize: 13, letterSpacing: 3, fontWeight: '400' }), - ] - case 'banner-welcome-en': - return [ - createTextLayer({ content: 'Welcome to', x: 50, y: 66, fontSize: 20, letterSpacing: 4, fontWeight: '400' }), - createTextLayer({ content: 'our Wedding', x: 50, y: 76, fontSize: 34, letterSpacing: 3, fontWeight: '700' }), - createTextLayer({ content: '新郎 & 新娘', x: 50, y: 87, fontSize: 22, letterSpacing: 4 }), - createTextLayer({ content: '2026.10.01', x: 50, y: 94, fontSize: 13, letterSpacing: 3, fontWeight: '400' }), - ] - case 'banner-minimal': - return [ - createTextLayer({ content: '新郎 & 新娘', x: 50, y: 84, fontSize: 26, letterSpacing: 5, fontWeight: '700' }), - createTextLayer({ content: '2026.10.01', x: 50, y: 93, fontSize: 14, letterSpacing: 4, fontWeight: '400' }), - ] - case 'banner-xi-corner': - return [ - createTextLayer({ content: '囍', x: 12, y: 10, fontSize: 32, color: '#c45c5c', letterSpacing: 0, shadow: false, align: 'left' }), - createTextLayer({ content: 'WEDDING', x: 50, y: 74, fontSize: 28, letterSpacing: 12, fontWeight: '700' }), - createTextLayer({ content: '新郎 & 新娘', x: 50, y: 84, fontSize: 26, letterSpacing: 5 }), - createTextLayer({ content: '2026.10.01', x: 50, y: 93, fontSize: 14, letterSpacing: 4, fontWeight: '400' }), - ] - case 'banner-top-bottom': - return [ - createTextLayer({ content: 'WEDDING', x: 50, y: 10, fontSize: 30, letterSpacing: 12, fontWeight: '700' }), - createTextLayer({ content: 'Welcome to our Wedding', x: 50, y: 18, fontSize: 13, letterSpacing: 2, fontWeight: '400' }), - createTextLayer({ content: '新郎 & 新娘', x: 50, y: 82, fontSize: 30, letterSpacing: 5, fontWeight: '700' }), - createTextLayer({ content: '2026.10.01', x: 50, y: 92, fontSize: 14, letterSpacing: 4, fontWeight: '400' }), + L({ content: '新郎', x: 28, y: 74, fontSize: 30, fontWeight: '700', letterSpacing: 8, fontFamily: 'serif' }), + L({ content: '&', x: 50, y: 75, fontSize: 20, fontWeight: '400', fontFamily: 'script' }), + L({ content: '新娘', x: 72, y: 74, fontSize: 30, fontWeight: '700', letterSpacing: 8, fontFamily: 'serif' }), + L({ content: '2026.10.01', x: 50, y: 87, fontSize: 14, letterSpacing: 4, fontWeight: '400', fontFamily: 'sans' }), + L({ content: 'Welcome to our Wedding', x: 50, y: 93, fontSize: 12, letterSpacing: 2, fontWeight: '400', fontFamily: 'display' }), ] case 'banner-frame-bottom': return [ - createTextLayer({ content: 'OUR WEDDING DAY', x: 50, y: 70, fontSize: 13, letterSpacing: 6, fontWeight: '400' }), - createTextLayer({ content: '新郎 & 新娘', x: 50, y: 79, fontSize: 30, letterSpacing: 5, fontWeight: '700' }), - createTextLayer({ content: '2026.10.01', x: 50, y: 88, fontSize: 14, letterSpacing: 4, fontWeight: '400' }), - createTextLayer({ content: '婚礼酒店', x: 50, y: 94, fontSize: 13, letterSpacing: 3, fontWeight: '400' }), + L({ content: 'OUR WEDDING DAY', x: 50, y: 70, fontSize: 13, letterSpacing: 6, fontWeight: '400', fontFamily: 'cinzel' }), + L({ content: '新郎 & 新娘', x: 50, y: 79, fontSize: 30, letterSpacing: 5, fontWeight: '700', fontFamily: 'serif' }), + L({ content: '2026.10.01', x: 50, y: 88, fontSize: 14, letterSpacing: 4, fontWeight: '400', fontFamily: 'sans' }), + L({ content: '婚礼酒店', x: 50, y: 94, fontSize: 13, letterSpacing: 3, fontWeight: '400', fontFamily: 'kai' }), ] + case 'banner-minimal': + return [ + L({ content: '新郎 & 新娘', x: 50, y: 84, fontSize: 26, letterSpacing: 5, fontWeight: '700', fontFamily: 'serif' }), + L({ content: '2026.10.01', x: 50, y: 93, fontSize: 14, letterSpacing: 4, fontWeight: '400', fontFamily: 'sans' }), + ] + + case 'art-welcome-brush': + return [ + L({ content: '欢迎来我们的婚礼', x: 50, y: 16, fontSize: 48, letterSpacing: 4, fontWeight: '400', fontFamily: 'brush', shadow: true }), + L({ content: 'Mr. 新郎 & Miss. 新娘', x: 50, y: 80, fontSize: 18, letterSpacing: 2, fontFamily: 'display', fontWeight: '400' }), + L({ content: 'WELCOME TO OUR WEDDING', x: 50, y: 88, fontSize: 12, letterSpacing: 4, fontFamily: 'cinzel', fontWeight: '400' }), + L({ content: '2026.10.01', x: 50, y: 94, fontSize: 13, letterSpacing: 3, fontFamily: 'sans', fontWeight: '400' }), + ] + case 'art-love-life': + return [ + L({ content: '以爱之名 度一生', x: 50, y: 18, fontSize: 46, letterSpacing: 6, fontWeight: '400', fontFamily: 'liujian', shadow: true }), + L({ content: '新郎 & 新娘', x: 50, y: 80, fontSize: 20, letterSpacing: 4, fontFamily: 'serif' }), + L({ content: 'WELCOME TO OUR WEDDING', x: 50, y: 88, fontSize: 11, letterSpacing: 4, fontFamily: 'cinzel', fontWeight: '400' }), + L({ content: '2026.10.01 · 婚礼酒店', x: 50, y: 94, fontSize: 12, letterSpacing: 2, fontFamily: 'sans', fontWeight: '400' }), + ] + case 'art-gaobai': + return [ + L({ content: 'Wedding Day', x: 38, y: 42, fontSize: 28, letterSpacing: 2, fontWeight: '400', fontFamily: 'script', color: '#e8c87a' }), + L({ content: '告白', x: 40, y: 56, fontSize: 64, letterSpacing: 8, fontWeight: '400', fontFamily: 'brush', shadow: true }), + L({ content: '新郎 & 新娘', x: 50, y: 86, fontSize: 18, letterSpacing: 4, fontFamily: 'serif' }), + L({ content: '2026.10.01', x: 50, y: 93, fontSize: 13, letterSpacing: 3, fontFamily: 'sans', fontWeight: '400' }), + ] + case 'art-married': + return [ + L({ content: 'WEDDING', x: 50, y: 18, fontSize: 52, letterSpacing: 10, fontWeight: '700', fontFamily: 'cinzel', color: 'rgba(255,255,255,0.35)', shadow: false, stroke: true }), + L({ content: '我们结婚啦', x: 50, y: 24, fontSize: 44, letterSpacing: 4, fontWeight: '400', fontFamily: 'brush', color: '#ff6b3d', shadow: true }), + L({ content: '【欢迎参加我们的婚礼】', x: 50, y: 38, fontSize: 14, letterSpacing: 2, fontFamily: 'kai', fontWeight: '400' }), + L({ content: '新郎:XXX & 新娘:XXX', x: 50, y: 46, fontSize: 16, letterSpacing: 2, fontFamily: 'serif', fontWeight: '400' }), + L({ content: '10月01日 12:00', x: 50, y: 88, fontSize: 14, letterSpacing: 2, fontFamily: 'sans', fontWeight: '400' }), + L({ content: '婚礼酒店', x: 50, y: 94, fontSize: 13, letterSpacing: 2, fontFamily: 'kai', fontWeight: '400' }), + ] + case 'art-hand-love': + return [ + L({ content: '携手热爱,奔赴山海', x: 50, y: 18, fontSize: 40, letterSpacing: 4, fontWeight: '400', fontFamily: 'liujian', shadow: true }), + L({ content: '【欢迎参加我们的婚礼】', x: 50, y: 34, fontSize: 14, letterSpacing: 2, fontFamily: 'kai', fontWeight: '400' }), + L({ content: '新郎:XXX & 新娘:XXX', x: 50, y: 42, fontSize: 16, letterSpacing: 2, fontFamily: 'serif', fontWeight: '400' }), + L({ content: '10月01日 12:00', x: 50, y: 88, fontSize: 14, letterSpacing: 2, fontFamily: 'sans', fontWeight: '400' }), + L({ content: '婚礼酒店', x: 50, y: 94, fontSize: 13, letterSpacing: 2, fontFamily: 'kai', fontWeight: '400' }), + ] + case 'art-like-you': + return [ + L({ content: '就是喜欢你', x: 50, y: 20, fontSize: 48, letterSpacing: 6, fontWeight: '400', fontFamily: 'longcang', color: '#f0d48a', shadow: true }), + L({ content: '【欢迎参加我们的婚礼】', x: 50, y: 36, fontSize: 14, letterSpacing: 2, fontFamily: 'kai', fontWeight: '400' }), + L({ content: '新郎:XXX & 新娘:XXX', x: 50, y: 44, fontSize: 16, letterSpacing: 2, fontFamily: 'serif', fontWeight: '400' }), + L({ content: '10月01日 12:00', x: 50, y: 88, fontSize: 14, letterSpacing: 2, fontFamily: 'sans', fontWeight: '400' }), + L({ content: '婚礼酒店', x: 50, y: 94, fontSize: 13, letterSpacing: 2, fontFamily: 'kai', fontWeight: '400' }), + ] + case 'art-give-home': + return [ + L({ content: 'WELCOME TO OUR WEDDING', x: 50, y: 14, fontSize: 16, letterSpacing: 5, fontWeight: '700', fontFamily: 'cinzel', stroke: true, shadow: false }), + L({ content: '给她一个家', x: 50, y: 26, fontSize: 42, letterSpacing: 8, fontWeight: '400', fontFamily: 'mashan', shadow: true }), + L({ content: '【欢迎参加我们的婚礼】', x: 50, y: 40, fontSize: 14, letterSpacing: 2, fontFamily: 'kai', fontWeight: '400' }), + L({ content: '新郎:XXX & 新娘:XXX', x: 50, y: 48, fontSize: 16, letterSpacing: 2, fontFamily: 'serif', fontWeight: '400' }), + L({ content: '10月01日 12:00', x: 50, y: 88, fontSize: 14, letterSpacing: 2, fontFamily: 'sans', fontWeight: '400' }), + L({ content: '婚礼酒店', x: 50, y: 94, fontSize: 13, letterSpacing: 2, fontFamily: 'kai', fontWeight: '400' }), + ] + case 'art-xi-welcome': + return [ + L({ content: '囍', x: 50, y: 12, fontSize: 56, color: '#e8c87a', letterSpacing: 0, shadow: false, fontFamily: 'mashan', fontWeight: '400' }), + L({ content: '欢迎光临我们的婚礼', x: 50, y: 72, fontSize: 30, letterSpacing: 4, fontFamily: 'brush', fontWeight: '400' }), + L({ content: '新郎 & 新娘', x: 50, y: 84, fontSize: 24, letterSpacing: 4, fontFamily: 'serif' }), + L({ content: '谨定于 2026年10月1日', x: 50, y: 93, fontSize: 13, letterSpacing: 2, fontFamily: 'kai', fontWeight: '400' }), + ] + case 'art-wujia': + return [ + L({ content: '吾家有喜', x: 22, y: 42, fontSize: 42, letterSpacing: 14, fontFamily: 'longcang', color: '#e8c87a', vertical: true, fontWeight: '400' }), + L({ content: '欢迎参加', x: 38, y: 40, fontSize: 18, letterSpacing: 10, fontFamily: 'kai', vertical: true, fontWeight: '400' }), + L({ content: '新郎 & 新娘', x: 72, y: 74, fontSize: 18, letterSpacing: 2, fontFamily: 'serif', align: 'left' }), + L({ content: '2026.10.01', x: 72, y: 84, fontSize: 14, letterSpacing: 2, fontFamily: 'sans', fontWeight: '400', align: 'left' }), + ] + case 'art-tianci': + return [ + L({ content: '天赐良缘', x: 20, y: 42, fontSize: 40, letterSpacing: 14, fontFamily: 'longcang', color: '#e8c87a', vertical: true, fontWeight: '400' }), + L({ content: '吾家有喜', x: 36, y: 40, fontSize: 18, letterSpacing: 10, fontFamily: 'kai', vertical: true, fontWeight: '400' }), + L({ content: '新郎 & 新娘', x: 72, y: 72, fontSize: 18, letterSpacing: 2, fontFamily: 'serif', align: 'left' }), + L({ content: '2026年10月1日', x: 72, y: 82, fontSize: 13, letterSpacing: 1, fontFamily: 'sans', fontWeight: '400', align: 'left' }), + L({ content: '婚礼酒店', x: 72, y: 90, fontSize: 13, letterSpacing: 2, fontFamily: 'kai', fontWeight: '400', align: 'left' }), + ] + case 'art-tianzuo': + return [ + L({ content: '天', x: 32, y: 22, fontSize: 48, fontFamily: 'longcang', color: '#e8c87a', fontWeight: '400', letterSpacing: 0 }), + L({ content: '作', x: 68, y: 22, fontSize: 48, fontFamily: 'longcang', color: '#e8c87a', fontWeight: '400', letterSpacing: 0 }), + L({ content: '之', x: 32, y: 40, fontSize: 48, fontFamily: 'longcang', color: '#e8c87a', fontWeight: '400', letterSpacing: 0 }), + L({ content: '合', x: 68, y: 40, fontSize: 48, fontFamily: 'longcang', color: '#e8c87a', fontWeight: '400', letterSpacing: 0 }), + L({ content: '欢迎参加我们的婚礼', x: 50, y: 72, fontSize: 16, letterSpacing: 4, fontFamily: 'kai', fontWeight: '400' }), + L({ content: '新郎 & 新娘', x: 50, y: 82, fontSize: 20, letterSpacing: 4, fontFamily: 'serif' }), + L({ content: '2026.10.01', x: 50, y: 92, fontSize: 13, letterSpacing: 3, fontFamily: 'sans', fontWeight: '400' }), + ] + case 'art-welcome-v': + return [ + L({ content: '欢迎参加我们的婚礼', x: 14, y: 45, fontSize: 28, letterSpacing: 12, fontFamily: 'brush', vertical: true, fontWeight: '400' }), + L({ content: 'Welcome', x: 28, y: 36, fontSize: 16, letterSpacing: 6, fontFamily: 'script', vertical: true, fontWeight: '400', color: '#e8c87a' }), + L({ content: '新郎 & 新娘', x: 70, y: 80, fontSize: 18, letterSpacing: 2, fontFamily: 'serif', align: 'left' }), + L({ content: '2026.10.01', x: 70, y: 90, fontSize: 13, letterSpacing: 2, fontFamily: 'sans', fontWeight: '400', align: 'left' }), + ] + case 'art-longest': + return [ + L({ content: '最长的告白', x: 50, y: 16, fontSize: 44, letterSpacing: 6, fontFamily: 'brush', fontWeight: '400', shadow: true }), + L({ content: 'Accompanying You', x: 50, y: 28, fontSize: 22, letterSpacing: 2, fontFamily: 'script', color: '#e07a7a', fontWeight: '400' }), + L({ content: '新郎 & 新娘', x: 50, y: 80, fontSize: 20, letterSpacing: 4, fontFamily: 'serif' }), + L({ content: '2026.10.01 · 婚礼酒店', x: 50, y: 90, fontSize: 13, letterSpacing: 2, fontFamily: 'sans', fontWeight: '400' }), + ] + case 'art-just-love': + return [ + L({ content: '就爱这样的你', x: 50, y: 18, fontSize: 42, letterSpacing: 4, fontFamily: 'liujian', color: '#7eb6e8', fontWeight: '400', shadow: true }), + L({ content: '【欢迎参加我们的婚礼】', x: 50, y: 34, fontSize: 14, letterSpacing: 2, fontFamily: 'kai', fontWeight: '400' }), + L({ content: '新郎:XXX & 新娘:XXX', x: 50, y: 42, fontSize: 16, letterSpacing: 2, fontFamily: 'serif', fontWeight: '400' }), + L({ content: '10月01日 12:00', x: 50, y: 88, fontSize: 14, letterSpacing: 2, fontFamily: 'sans', fontWeight: '400' }), + L({ content: '婚礼酒店', x: 50, y: 94, fontSize: 13, letterSpacing: 2, fontFamily: 'kai', fontWeight: '400' }), + ] + case 'custom': return [] case 'banner-bottom': default: return [ - createTextLayer({ content: 'WEDDING', x: 50, y: 68, fontSize: 36, letterSpacing: 14, fontWeight: '700' }), - createTextLayer({ content: '新郎 & 新娘', x: 50, y: 78, fontSize: 26, letterSpacing: 6 }), - createTextLayer({ content: 'Welcome to our Wedding', x: 50, y: 86, fontSize: 13, letterSpacing: 2, fontWeight: '400' }), - createTextLayer({ content: '2026.10.01', x: 50, y: 93, fontSize: 14, letterSpacing: 4, fontWeight: '400' }), + L({ content: 'WEDDING', x: 50, y: 68, fontSize: 36, letterSpacing: 14, fontWeight: '700', fontFamily: 'cinzel' }), + L({ content: '新郎 & 新娘', x: 50, y: 78, fontSize: 26, letterSpacing: 6, fontFamily: 'serif' }), + L({ content: 'Welcome to our Wedding', x: 50, y: 86, fontSize: 13, letterSpacing: 2, fontWeight: '400', fontFamily: 'display' }), + L({ content: '2026.10.01', x: 50, y: 93, fontSize: 14, letterSpacing: 4, fontWeight: '400', fontFamily: 'sans' }), ] } } diff --git a/vite-tailwindcss/src/style.css b/vite-tailwindcss/src/style.css index a98a242..bd8202a 100644 --- a/vite-tailwindcss/src/style.css +++ b/vite-tailwindcss/src/style.css @@ -1,14 +1,26 @@ @import "tailwindcss"; @import "@fontsource/great-vibes/400.css"; @import "@fontsource/ma-shan-zheng/400.css"; +@import "@fontsource/zhi-mang-xing/400.css"; +@import "@fontsource/long-cang/400.css"; +@import "@fontsource/liu-jian-mao-cao/400.css"; +@import "@fontsource/playfair-display/400.css"; +@import "@fontsource/playfair-display/700.css"; +@import "@fontsource/pinyon-script/400.css"; +@import "@fontsource/cinzel/400.css"; +@import "@fontsource/cinzel/700.css"; :root { --font-sans: "Avenir Next", "Helvetica Neue", "PingFang SC", "Microsoft YaHei", sans-serif; --font-serif: "Noto Serif SC", "Source Han Serif SC", "Songti SC", "STSong", "SimSun", serif; - --font-display: "Didot", "Bodoni 72", "Cormorant Garamond", "Times New Roman", serif; + --font-display: "Playfair Display", "Didot", "Bodoni 72", "Cormorant Garamond", "Times New Roman", serif; --font-kai: "Kaiti SC", "STKaiti", "KaiTi", "FangSong", serif; - --font-brush: "HanziPen SC", "STXingkai", "华文行楷", "FZKai-Z03S", "Kaiti SC", "STKaiti", "KaiTi", "FangSong", serif; - --font-calligraphy: "Great Vibes", "Ma Shan Zheng", cursive, serif; + --font-brush: "Zhi Mang Xing", "Liu Jian Mao Cao", "HanziPen SC", "STXingkai", "华文行楷", "Kaiti SC", serif; + --font-calligraphy: "Great Vibes", "Pinyon Script", "Ma Shan Zheng", cursive, serif; + --font-mashan: "Ma Shan Zheng", "Zhi Mang Xing", cursive, serif; + --font-longcang: "Long Cang", "Ma Shan Zheng", cursive, serif; + --font-liujian: "Liu Jian Mao Cao", "Zhi Mang Xing", cursive, serif; + --font-cinzel: "Cinzel", "Playfair Display", serif; --font-number: "Cochin", "Georgia", "Times New Roman", serif; } diff --git a/vite-tailwindcss/src/views/admin/HotelWelcomePosterAdmin.vue b/vite-tailwindcss/src/views/admin/HotelWelcomePosterAdmin.vue index 8d19627..9fe0bf7 100644 --- a/vite-tailwindcss/src/views/admin/HotelWelcomePosterAdmin.vue +++ b/vite-tailwindcss/src/views/admin/HotelWelcomePosterAdmin.vue @@ -170,9 +170,15 @@ 删除 + + +
+ {{ fontSample(t.fontFamily) }} +
+
- + @@ -194,9 +200,10 @@ />
-
+
竖排 阴影 + 描边
位置 X {{ Math.round(t.x) }}% · Y {{ Math.round(t.y) }}%
@@ -278,12 +285,15 @@ v-for="(z, i) in tpl.zones" :key="i" class="hw-tpl-zone" - :class="{ vertical: z.vertical }" + :class="{ vertical: z.vertical, art: z.art }" :style="zoneStyle(z)" >{{ z.label }} 自定义空白 -
{{ tpl.label }}
+
+ {{ tpl.label }} + 艺术字 +
{{ tpl.desc }}
@@ -312,10 +322,12 @@ import { import ImageField from '@/components/ImageField.vue' import MediaPickerModal from '@/components/MediaPickerModal.vue' import { + TEXT_FONTS, clampPct, createTextLayer, defaultTextsForTemplate, normalizeHotelWelcomeConfig, + resolveFontCss, resolveTemplate, serializeHotelWelcomeConfig, templateGroups, @@ -363,6 +375,11 @@ const columns = [ const currentTemplateMeta = computed(() => templateMeta(detail.value?.template)) const createTemplateMeta = computed(() => templateMeta(createForm.template)) const currentTemplateDesc = computed(() => currentTemplateMeta.value?.desc || '') +const fontSelectOptions = TEXT_FONTS.map((f) => ({ value: f.value, label: f.label })) + +function fontSample(key) { + return TEXT_FONTS.find((f) => f.value === key)?.sample || 'Aa' +} function zoneStyle(z) { return { @@ -559,20 +576,23 @@ function textStyle(t) { : align === 'right' ? 'translate(-100%, -50%)' : 'translate(-50%, -50%)' - return { + const style = { left: `${clampPct(t.x)}%`, top: `${clampPct(t.y)}%`, transform, fontSize: `${t.fontSize || 24}px`, color: t.color || '#fff', - fontFamily: t.fontFamily === 'serif' - ? '"Ma Shan Zheng", "Songti SC", serif' - : t.fontFamily || 'serif', + fontFamily: resolveFontCss(t.fontFamily), fontWeight: t.fontWeight || '600', letterSpacing: `${t.letterSpacing || 0}px`, textAlign: align, writingMode: t.vertical ? 'vertical-rl' : 'horizontal-tb', } + if (t.stroke) { + style.webkitTextStroke = `1px ${t.strokeColor || 'rgba(255,255,255,0.35)'}` + style.paintOrder = 'stroke fill' + } + return style } function onTextPointerDown(e, t) { @@ -970,10 +990,35 @@ defineExpose({ loadList, refresh: loadList }) white-space: nowrap; letter-spacing: 0.12em; } +.hw-tpl-zone.art { + background: rgba(232, 200, 122, 0.9); + color: #5a3d1a; + font-family: var(--font-brush); + font-size: 10px; +} .hw-tpl-card-label { font-size: 12px; color: #2c2c2c; font-weight: 500; + display: flex; + align-items: center; + gap: 6px; + flex-wrap: wrap; +} +.hw-tpl-art-tag { + font-size: 10px; + font-weight: 500; + color: #9a4d3a; + background: rgba(196, 92, 92, 0.12); + border: 1px solid rgba(196, 92, 92, 0.28); + border-radius: 999px; + padding: 0 6px; + line-height: 16px; +} +.hw-font-sample { + font-size: 18px; + line-height: 1.2; + color: #7a6550; } .hw-tpl-card-desc { margin-top: 2px;