feat(tokens): 全球出海设计Token系统与多语言国际化统一字典
这个提交包含在:
@@ -1,5 +1,8 @@
|
|||||||
|
import { omniLocales } from './omni.ts';
|
||||||
|
|
||||||
const i18n = {
|
const i18n = {
|
||||||
'en-US': {
|
'en-US': {
|
||||||
|
...omniLocales['en-US'].messages,
|
||||||
'menu.welcome': 'Welcome',
|
'menu.welcome': 'Welcome',
|
||||||
'menu.dashboard': 'Dashboard',
|
'menu.dashboard': 'Dashboard',
|
||||||
'menu.list': 'List',
|
'menu.list': 'List',
|
||||||
@@ -59,6 +62,7 @@ const i18n = {
|
|||||||
'navbar.search.placeholder': 'Please search',
|
'navbar.search.placeholder': 'Please search',
|
||||||
},
|
},
|
||||||
'zh-CN': {
|
'zh-CN': {
|
||||||
|
...omniLocales['zh-CN'].messages,
|
||||||
'menu.dashboard': '仪表盘',
|
'menu.dashboard': '仪表盘',
|
||||||
'menu.list': '列表页',
|
'menu.list': '列表页',
|
||||||
'menu.result': '结果页',
|
'menu.result': '结果页',
|
||||||
@@ -116,6 +120,9 @@ const i18n = {
|
|||||||
'message.lang.tips': '语言切换至 ',
|
'message.lang.tips': '语言切换至 ',
|
||||||
'navbar.search.placeholder': '输入内容查询',
|
'navbar.search.placeholder': '输入内容查询',
|
||||||
},
|
},
|
||||||
|
'ar-SA': {
|
||||||
|
...omniLocales['ar-SA'].messages,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default i18n;
|
export default i18n;
|
||||||
|
|||||||
156
admin/src/locale/omni.ts
普通文件
156
admin/src/locale/omni.ts
普通文件
@@ -0,0 +1,156 @@
|
|||||||
|
/**
|
||||||
|
* OmniCard AI 跨端与管理后台统一多语言国际化字典
|
||||||
|
* 严格遵循 AGENTS.md 约束规范:
|
||||||
|
* 1. 禁止使用任何 emoji
|
||||||
|
* 2. 纯文本国际化
|
||||||
|
* 3. 完整支持阿拉伯语 RTL 镜像布局
|
||||||
|
*/
|
||||||
|
|
||||||
|
export type OmniLocale = 'zh-CN' | 'en-US' | 'ar-SA';
|
||||||
|
export type Direction = 'ltr' | 'rtl';
|
||||||
|
|
||||||
|
export interface LocaleMeta {
|
||||||
|
readonly code: OmniLocale;
|
||||||
|
readonly name: string;
|
||||||
|
readonly dir: Direction;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type OmniMessageKey =
|
||||||
|
| 'common.confirm'
|
||||||
|
| 'common.cancel'
|
||||||
|
| 'common.save'
|
||||||
|
| 'common.delete'
|
||||||
|
| 'common.edit'
|
||||||
|
| 'common.search'
|
||||||
|
| 'common.loading'
|
||||||
|
| 'common.success'
|
||||||
|
| 'common.error'
|
||||||
|
| 'common.empty'
|
||||||
|
| 'card.save_contact'
|
||||||
|
| 'card.exchange'
|
||||||
|
| 'card.ai_chat'
|
||||||
|
| 'card.share'
|
||||||
|
| 'card.scan_nfc'
|
||||||
|
| 'card.view_profile'
|
||||||
|
| 'radar.online_visitor'
|
||||||
|
| 'radar.active_now'
|
||||||
|
| 'radar.visitor_trace'
|
||||||
|
| 'radar.lead_score'
|
||||||
|
| 'crm.contacts'
|
||||||
|
| 'crm.leads'
|
||||||
|
| 'crm.follow_up';
|
||||||
|
|
||||||
|
export interface OmniLocaleConfig {
|
||||||
|
readonly code: OmniLocale;
|
||||||
|
readonly name: string;
|
||||||
|
readonly dir: Direction;
|
||||||
|
readonly messages: Record<OmniMessageKey, string>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const omniLocales: Record<OmniLocale, OmniLocaleConfig> = {
|
||||||
|
'zh-CN': {
|
||||||
|
code: 'zh-CN',
|
||||||
|
name: '简体中文',
|
||||||
|
dir: 'ltr',
|
||||||
|
messages: {
|
||||||
|
'common.confirm': '确认',
|
||||||
|
'common.cancel': '取消',
|
||||||
|
'common.save': '保存',
|
||||||
|
'common.delete': '删除',
|
||||||
|
'common.edit': '编辑',
|
||||||
|
'common.search': '搜索',
|
||||||
|
'common.loading': '加载中...',
|
||||||
|
'common.success': '操作成功',
|
||||||
|
'common.error': '操作失败',
|
||||||
|
'common.empty': '暂无数据',
|
||||||
|
'card.save_contact': '保存到通讯录',
|
||||||
|
'card.exchange': '交换名片',
|
||||||
|
'card.ai_chat': 'AI 咨询',
|
||||||
|
'card.share': '分享名片',
|
||||||
|
'card.scan_nfc': 'NFC 触碰感应',
|
||||||
|
'card.view_profile': '查看微官网',
|
||||||
|
'radar.online_visitor': '在线访客',
|
||||||
|
'radar.active_now': '当前活跃',
|
||||||
|
'radar.visitor_trace': '访问轨迹',
|
||||||
|
'radar.lead_score': '意向度评分',
|
||||||
|
'crm.contacts': '联系人列表',
|
||||||
|
'crm.leads': '商机线索',
|
||||||
|
'crm.follow_up': '跟进记录',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'en-US': {
|
||||||
|
code: 'en-US',
|
||||||
|
name: 'English',
|
||||||
|
dir: 'ltr',
|
||||||
|
messages: {
|
||||||
|
'common.confirm': 'Confirm',
|
||||||
|
'common.cancel': 'Cancel',
|
||||||
|
'common.save': 'Save',
|
||||||
|
'common.delete': 'Delete',
|
||||||
|
'common.edit': 'Edit',
|
||||||
|
'common.search': 'Search',
|
||||||
|
'common.loading': 'Loading...',
|
||||||
|
'common.success': 'Success',
|
||||||
|
'common.error': 'Error occurred',
|
||||||
|
'common.empty': 'No data available',
|
||||||
|
'card.save_contact': 'Save Contact',
|
||||||
|
'card.exchange': 'Exchange Card',
|
||||||
|
'card.ai_chat': 'AI Assistant',
|
||||||
|
'card.share': 'Share Card',
|
||||||
|
'card.scan_nfc': 'NFC Tap',
|
||||||
|
'card.view_profile': 'View Profile',
|
||||||
|
'radar.online_visitor': 'Online Visitors',
|
||||||
|
'radar.active_now': 'Active Now',
|
||||||
|
'radar.visitor_trace': 'Visitor Trail',
|
||||||
|
'radar.lead_score': 'Lead Score',
|
||||||
|
'crm.contacts': 'Contacts',
|
||||||
|
'crm.leads': 'Leads',
|
||||||
|
'crm.follow_up': 'Follow-up',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'ar-SA': {
|
||||||
|
code: 'ar-SA',
|
||||||
|
name: 'العربية',
|
||||||
|
dir: 'rtl',
|
||||||
|
messages: {
|
||||||
|
'common.confirm': 'تأكيد',
|
||||||
|
'common.cancel': 'إلغاء',
|
||||||
|
'common.save': 'حفظ',
|
||||||
|
'common.delete': 'حذف',
|
||||||
|
'common.edit': 'تعديل',
|
||||||
|
'common.search': 'بحث',
|
||||||
|
'common.loading': 'جاري التحميل...',
|
||||||
|
'common.success': 'تمت العملية بنجاح',
|
||||||
|
'common.error': 'حدث خطأ',
|
||||||
|
'common.empty': 'لا توجد بيانات',
|
||||||
|
'card.save_contact': 'حفظ جهة الاتصال',
|
||||||
|
'card.exchange': 'تبادل البطاقات',
|
||||||
|
'card.ai_chat': 'مساعد الذكاء الاصطناعي',
|
||||||
|
'card.share': 'مشاركة البطاقة',
|
||||||
|
'card.scan_nfc': 'مسح NFC',
|
||||||
|
'card.view_profile': 'عرض الملف الشخصي',
|
||||||
|
'radar.online_visitor': 'الزوار الحاليون',
|
||||||
|
'radar.active_now': 'نشط الآن',
|
||||||
|
'radar.visitor_trace': 'مسار الزائر',
|
||||||
|
'radar.lead_score': 'درجة العميل المحتمل',
|
||||||
|
'crm.contacts': 'جهات الاتصال',
|
||||||
|
'crm.leads': 'العملاء المحتملون',
|
||||||
|
'crm.follow_up': 'المتابعة',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断指定语言是否需要 RTL 镜像排版
|
||||||
|
*/
|
||||||
|
export function isRtl(locale: OmniLocale): boolean {
|
||||||
|
return omniLocales[locale]?.dir === 'rtl';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取翻译文本,若不存在则回退至英文或 key 本身
|
||||||
|
*/
|
||||||
|
export function getOmniText(locale: OmniLocale, key: OmniMessageKey): string {
|
||||||
|
const dict = omniLocales[locale] || omniLocales['en-US'];
|
||||||
|
return dict.messages[key] || omniLocales['en-US'].messages[key] || key;
|
||||||
|
}
|
||||||
140
admin/src/style/tokens.ts
普通文件
140
admin/src/style/tokens.ts
普通文件
@@ -0,0 +1,140 @@
|
|||||||
|
/**
|
||||||
|
* OmniCard AI 设计 Token 系统 (Admin & Web)
|
||||||
|
* 严格遵循 AGENTS.md 约束规范
|
||||||
|
* 瑞士国际主义 + 现代 SaaS + 编辑感
|
||||||
|
*/
|
||||||
|
|
||||||
|
export interface ColorTokens {
|
||||||
|
readonly primary: string;
|
||||||
|
readonly primarySoft: string;
|
||||||
|
readonly ai: string;
|
||||||
|
readonly bg: string;
|
||||||
|
readonly surface: string;
|
||||||
|
readonly border: string;
|
||||||
|
readonly text: string;
|
||||||
|
readonly text2: string;
|
||||||
|
readonly success: string;
|
||||||
|
readonly warning: string;
|
||||||
|
readonly error: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const lightColors: ColorTokens = {
|
||||||
|
primary: '#0B5CFF',
|
||||||
|
primarySoft: '#EAF1FF',
|
||||||
|
ai: '#7C5CFF',
|
||||||
|
bg: '#FFFFFF',
|
||||||
|
surface: '#F7F8FA',
|
||||||
|
border: '#E3E7ED',
|
||||||
|
text: '#101828',
|
||||||
|
text2: '#667085',
|
||||||
|
success: '#12B76A',
|
||||||
|
warning: '#F79009',
|
||||||
|
error: '#F04438',
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export const darkColors: ColorTokens = {
|
||||||
|
primary: '#4D8DFF',
|
||||||
|
primarySoft: '#102A4D',
|
||||||
|
ai: '#9B8CFF',
|
||||||
|
bg: '#0B0D10',
|
||||||
|
surface: '#12151A',
|
||||||
|
border: '#262B33',
|
||||||
|
text: '#F2F4F7',
|
||||||
|
text2: '#98A2B3',
|
||||||
|
success: '#32D583',
|
||||||
|
warning: '#FDB022',
|
||||||
|
error: '#F97066',
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export const spacing = {
|
||||||
|
4: '4px',
|
||||||
|
8: '8px',
|
||||||
|
12: '12px',
|
||||||
|
16: '16px',
|
||||||
|
24: '24px',
|
||||||
|
32: '32px',
|
||||||
|
48: '48px',
|
||||||
|
64: '64px',
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export const radius = {
|
||||||
|
button: '12px',
|
||||||
|
card: '16px',
|
||||||
|
modal: '20px',
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export const typography = {
|
||||||
|
fontFamily: 'Inter, Noto Sans SC, Noto Sans Arabic, Noto Sans JP, Noto Sans KR, sans-serif',
|
||||||
|
fontSize: {
|
||||||
|
12: '12px',
|
||||||
|
14: '14px',
|
||||||
|
16: '16px',
|
||||||
|
20: '20px',
|
||||||
|
24: '24px',
|
||||||
|
32: '32px',
|
||||||
|
40: '40px',
|
||||||
|
},
|
||||||
|
lineHeight: {
|
||||||
|
body: 1.6,
|
||||||
|
title: 1.25,
|
||||||
|
arabic: 1.7,
|
||||||
|
},
|
||||||
|
shadow: '0 1px 2px rgba(16,24,40,0.04)',
|
||||||
|
maxWidth: {
|
||||||
|
content: '1200px',
|
||||||
|
article: '720px',
|
||||||
|
},
|
||||||
|
motion: {
|
||||||
|
micro: '150ms',
|
||||||
|
drawer: '250ms',
|
||||||
|
page: '400ms',
|
||||||
|
easing: 'cubic-bezier(0.2, 0.8, 0.2, 1)',
|
||||||
|
},
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取指定主题下的 CSS 变量键值对
|
||||||
|
*/
|
||||||
|
export function getCssVariables(theme: 'light' | 'dark' = 'light'): Record<string, string> {
|
||||||
|
const colors = theme === 'dark' ? darkColors : lightColors;
|
||||||
|
|
||||||
|
return {
|
||||||
|
'--omni-color-primary': colors.primary,
|
||||||
|
'--omni-color-primary-soft': colors.primarySoft,
|
||||||
|
'--omni-color-ai': colors.ai,
|
||||||
|
'--omni-color-bg': colors.bg,
|
||||||
|
'--omni-color-surface': colors.surface,
|
||||||
|
'--omni-color-border': colors.border,
|
||||||
|
'--omni-color-text': colors.text,
|
||||||
|
'--omni-color-text-2': colors.text2,
|
||||||
|
'--omni-color-success': colors.success,
|
||||||
|
'--omni-color-warning': colors.warning,
|
||||||
|
'--omni-color-error': colors.error,
|
||||||
|
|
||||||
|
'--omni-space-4': spacing[4],
|
||||||
|
'--omni-space-8': spacing[8],
|
||||||
|
'--omni-space-12': spacing[12],
|
||||||
|
'--omni-space-16': spacing[16],
|
||||||
|
'--omni-space-24': spacing[24],
|
||||||
|
'--omni-space-32': spacing[32],
|
||||||
|
'--omni-space-48': spacing[48],
|
||||||
|
'--omni-space-64': spacing[64],
|
||||||
|
|
||||||
|
'--omni-radius-button': radius.button,
|
||||||
|
'--omni-radius-card': radius.card,
|
||||||
|
'--omni-radius-modal': radius.modal,
|
||||||
|
|
||||||
|
'--omni-font-family': typography.fontFamily,
|
||||||
|
'--omni-shadow': typography.shadow,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将 CSS 变量对象序列化为 CSS 样式声明字符串
|
||||||
|
*/
|
||||||
|
export function getCssVariableDeclarations(theme: 'light' | 'dark' = 'light'): string {
|
||||||
|
const vars = getCssVariables(theme);
|
||||||
|
return Object.entries(vars)
|
||||||
|
.map(([k, v]) => `${k}: ${v};`)
|
||||||
|
.join(' ');
|
||||||
|
}
|
||||||
@@ -0,0 +1,135 @@
|
|||||||
|
import test from 'node:test';
|
||||||
|
import assert from 'node:assert/strict';
|
||||||
|
import fs from 'node:fs';
|
||||||
|
import path from 'node:path';
|
||||||
|
import { lightColors, darkColors, spacing, radius, typography, getCssVariables } from '../src/style/tokens.ts';
|
||||||
|
import { omniLocales, isRtl, getOmniText } from '../src/locale/omni.ts';
|
||||||
|
import defaultI18n from '../src/locale/index.ts';
|
||||||
|
|
||||||
|
test('1. admin/src/style/tokens.ts - 颜色 Token 校验 (Light & Dark)', () => {
|
||||||
|
// Light 模式颜色
|
||||||
|
assert.equal(lightColors.primary, '#0B5CFF');
|
||||||
|
assert.equal(lightColors.primarySoft, '#EAF1FF');
|
||||||
|
assert.equal(lightColors.ai, '#7C5CFF');
|
||||||
|
assert.equal(lightColors.bg, '#FFFFFF');
|
||||||
|
assert.equal(lightColors.surface, '#F7F8FA');
|
||||||
|
assert.equal(lightColors.border, '#E3E7ED');
|
||||||
|
assert.equal(lightColors.text, '#101828');
|
||||||
|
assert.equal(lightColors.text2, '#667085');
|
||||||
|
assert.equal(lightColors.success, '#12B76A');
|
||||||
|
assert.equal(lightColors.warning, '#F79009');
|
||||||
|
assert.equal(lightColors.error, '#F04438');
|
||||||
|
|
||||||
|
// Dark 模式颜色
|
||||||
|
assert.equal(darkColors.primary, '#4D8DFF');
|
||||||
|
assert.equal(darkColors.primarySoft, '#102A4D');
|
||||||
|
assert.equal(darkColors.ai, '#9B8CFF');
|
||||||
|
assert.equal(darkColors.bg, '#0B0D10');
|
||||||
|
assert.equal(darkColors.surface, '#12151A');
|
||||||
|
assert.equal(darkColors.border, '#262B33');
|
||||||
|
assert.equal(darkColors.text, '#F2F4F7');
|
||||||
|
assert.equal(darkColors.text2, '#98A2B3');
|
||||||
|
assert.equal(darkColors.success, '#32D583');
|
||||||
|
assert.equal(darkColors.warning, '#FDB022');
|
||||||
|
assert.equal(darkColors.error, '#F97066');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('2. admin/src/style/tokens.ts - 间距、圆角与排版校验', () => {
|
||||||
|
// 间距 4, 8, 12, 16, 24, 32, 48, 64
|
||||||
|
assert.equal(spacing[4], '4px');
|
||||||
|
assert.equal(spacing[8], '8px');
|
||||||
|
assert.equal(spacing[12], '12px');
|
||||||
|
assert.equal(spacing[16], '16px');
|
||||||
|
assert.equal(spacing[24], '24px');
|
||||||
|
assert.equal(spacing[32], '32px');
|
||||||
|
assert.equal(spacing[48], '48px');
|
||||||
|
assert.equal(spacing[64], '64px');
|
||||||
|
|
||||||
|
// 圆角 button 12px, card 16px, modal 20px
|
||||||
|
assert.equal(radius.button, '12px');
|
||||||
|
assert.equal(radius.card, '16px');
|
||||||
|
assert.equal(radius.modal, '20px');
|
||||||
|
|
||||||
|
// 阴影与排版
|
||||||
|
assert.equal(typography.shadow, '0 1px 2px rgba(16,24,40,0.04)');
|
||||||
|
assert.equal(typography.lineHeight.body, 1.6);
|
||||||
|
assert.equal(typography.lineHeight.title, 1.25);
|
||||||
|
assert.equal(typography.lineHeight.arabic, 1.7);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('3. admin/src/style/tokens.ts - CSS 变量生成辅助函数', () => {
|
||||||
|
const lightVars = getCssVariables('light');
|
||||||
|
assert.ok(lightVars['--omni-color-primary'] === '#0B5CFF');
|
||||||
|
assert.ok(lightVars['--omni-color-bg'] === '#FFFFFF');
|
||||||
|
assert.ok(lightVars['--omni-radius-card'] === '16px');
|
||||||
|
|
||||||
|
const darkVars = getCssVariables('dark');
|
||||||
|
assert.ok(darkVars['--omni-color-primary'] === '#4D8DFF');
|
||||||
|
assert.ok(darkVars['--omni-color-bg'] === '#0B0D10');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('4. admin/src/locale/omni.ts - 多语言字典与 RTL 支持', () => {
|
||||||
|
const locales = ['zh-CN', 'en-US', 'ar-SA'] as const;
|
||||||
|
for (const loc of locales) {
|
||||||
|
assert.ok(omniLocales[loc], `应当包含语言包: ${loc}`);
|
||||||
|
assert.ok(omniLocales[loc].messages['card.save_contact']);
|
||||||
|
assert.ok(omniLocales[loc].messages['card.exchange']);
|
||||||
|
assert.ok(omniLocales[loc].messages['card.ai_chat']);
|
||||||
|
assert.ok(omniLocales[loc].messages['radar.online_visitor']);
|
||||||
|
assert.ok(omniLocales[loc].messages['common.confirm']);
|
||||||
|
assert.ok(omniLocales[loc].messages['common.cancel']);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 阿拉伯语 RTL 校验
|
||||||
|
assert.equal(omniLocales['ar-SA'].dir, 'rtl');
|
||||||
|
assert.equal(isRtl('ar-SA'), true);
|
||||||
|
assert.equal(omniLocales['zh-CN'].dir, 'ltr');
|
||||||
|
assert.equal(isRtl('zh-CN'), false);
|
||||||
|
assert.equal(isRtl('en-US'), false);
|
||||||
|
|
||||||
|
// 获取文案辅助函数
|
||||||
|
assert.equal(getOmniText('zh-CN', 'card.save_contact'), '保存到通讯录');
|
||||||
|
assert.equal(getOmniText('en-US', 'card.save_contact'), 'Save Contact');
|
||||||
|
assert.equal(getOmniText('ar-SA', 'card.save_contact'), 'حفظ جهة الاتصال');
|
||||||
|
|
||||||
|
// 禁止包含任何 emoji 校验
|
||||||
|
const emojiRegex = /[\u{1F300}-\u{1F9FF}\u{2600}-\u{26FF}\u{2700}-\u{27BF}]/u;
|
||||||
|
for (const loc of locales) {
|
||||||
|
for (const [key, val] of Object.entries(omniLocales[loc].messages)) {
|
||||||
|
assert.ok(!emojiRegex.test(val), `语言 ${loc} 的词条 ${key} 严禁含有 emoji: ${val}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('5. mpuniapp/common/tokens.uts - uni-app x 跨端 Token 校验', () => {
|
||||||
|
const utsPath = path.resolve('m:/2026项目/CRMmp/mpuniapp/common/tokens.uts');
|
||||||
|
assert.ok(fs.existsSync(utsPath), 'tokens.uts 文件必须存在');
|
||||||
|
|
||||||
|
const content = fs.readFileSync(utsPath, 'utf-8');
|
||||||
|
|
||||||
|
// 必须导出 utsLightColors, utsDarkColors, utsSpacing, utsRadius, utsTypography
|
||||||
|
assert.ok(content.includes('export const utsLightColors: UtsColorTokens ='));
|
||||||
|
assert.ok(content.includes('export const utsDarkColors: UtsColorTokens ='));
|
||||||
|
assert.ok(content.includes('export const utsSpacing: UtsSpacing ='));
|
||||||
|
assert.ok(content.includes('export const utsRadius: UtsRadius ='));
|
||||||
|
assert.ok(content.includes('export const utsTypography: UtsTypography ='));
|
||||||
|
|
||||||
|
// 校验关键值
|
||||||
|
assert.ok(content.includes("'#0B5CFF'"));
|
||||||
|
assert.ok(content.includes("'#4D8DFF'"));
|
||||||
|
assert.ok(content.includes("'#7C5CFF'"));
|
||||||
|
assert.ok(content.includes("'#9B8CFF'"));
|
||||||
|
assert.ok(content.includes("button: '12px'"));
|
||||||
|
assert.ok(content.includes("card: '16px'"));
|
||||||
|
assert.ok(content.includes("modal: '20px'"));
|
||||||
|
assert.ok(content.includes("space16: '16px'"));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('6. admin/src/locale/index.ts - 成功合并 omni 词条且支持 ar-SA', () => {
|
||||||
|
assert.ok(defaultI18n['zh-CN']['card.save_contact'] === '保存到通讯录');
|
||||||
|
assert.ok(defaultI18n['en-US']['card.save_contact'] === 'Save Contact');
|
||||||
|
assert.ok(defaultI18n['ar-SA']['card.save_contact'] === 'حفظ جهة الاتصال');
|
||||||
|
assert.ok(defaultI18n['ar-SA']['radar.online_visitor'] === 'الزوار الحاليون');
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
90
mpuniapp/common/tokens.uts
普通文件
90
mpuniapp/common/tokens.uts
普通文件
@@ -0,0 +1,90 @@
|
|||||||
|
/**
|
||||||
|
* OmniCard AI - uni-app x (uts) 全局设计 Token 常量定义
|
||||||
|
* 严格遵循 AGENTS.md 约束规范与 uts 强类型规范
|
||||||
|
*/
|
||||||
|
|
||||||
|
export type UtsColorTokens = {
|
||||||
|
primary: string
|
||||||
|
primarySoft: string
|
||||||
|
ai: string
|
||||||
|
bg: string
|
||||||
|
surface: string
|
||||||
|
border: string
|
||||||
|
text: string
|
||||||
|
text2: string
|
||||||
|
success: string
|
||||||
|
warning: string
|
||||||
|
error: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export const utsLightColors: UtsColorTokens = {
|
||||||
|
primary: '#0B5CFF',
|
||||||
|
primarySoft: '#EAF1FF',
|
||||||
|
ai: '#7C5CFF',
|
||||||
|
bg: '#FFFFFF',
|
||||||
|
surface: '#F7F8FA',
|
||||||
|
border: '#E3E7ED',
|
||||||
|
text: '#101828',
|
||||||
|
text2: '#667085',
|
||||||
|
success: '#12B76A',
|
||||||
|
warning: '#F79009',
|
||||||
|
error: '#F04438'
|
||||||
|
}
|
||||||
|
|
||||||
|
export const utsDarkColors: UtsColorTokens = {
|
||||||
|
primary: '#4D8DFF',
|
||||||
|
primarySoft: '#102A4D',
|
||||||
|
ai: '#9B8CFF',
|
||||||
|
bg: '#0B0D10',
|
||||||
|
surface: '#12151A',
|
||||||
|
border: '#262B33',
|
||||||
|
text: '#F2F4F7',
|
||||||
|
text2: '#98A2B3',
|
||||||
|
success: '#32D583',
|
||||||
|
warning: '#FDB022',
|
||||||
|
error: '#F97066'
|
||||||
|
}
|
||||||
|
|
||||||
|
export type UtsSpacing = {
|
||||||
|
space4: string
|
||||||
|
space8: string
|
||||||
|
space12: string
|
||||||
|
space16: string
|
||||||
|
space24: string
|
||||||
|
space32: string
|
||||||
|
space48: string
|
||||||
|
space64: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export const utsSpacing: UtsSpacing = {
|
||||||
|
space4: '4px',
|
||||||
|
space8: '8px',
|
||||||
|
space12: '12px',
|
||||||
|
space16: '16px',
|
||||||
|
space24: '24px',
|
||||||
|
space32: '32px',
|
||||||
|
space48: '48px',
|
||||||
|
space64: '64px'
|
||||||
|
}
|
||||||
|
|
||||||
|
export type UtsRadius = {
|
||||||
|
button: string
|
||||||
|
card: string
|
||||||
|
modal: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export const utsRadius: UtsRadius = {
|
||||||
|
button: '12px',
|
||||||
|
card: '16px',
|
||||||
|
modal: '20px'
|
||||||
|
}
|
||||||
|
|
||||||
|
export type UtsTypography = {
|
||||||
|
fontFamily: string
|
||||||
|
shadow: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export const utsTypography: UtsTypography = {
|
||||||
|
fontFamily: 'Inter, Noto Sans SC, Noto Sans Arabic, Noto Sans JP, Noto Sans KR, sans-serif',
|
||||||
|
shadow: '0 1px 2px rgba(16,24,40,0.04)'
|
||||||
|
}
|
||||||
在新工单中引用
屏蔽一个用户