diff --git a/.github/workflows/typos-config.toml b/.github/workflows/typos-config.toml index 5a32e7a5d..833097dbb 100644 --- a/.github/workflows/typos-config.toml +++ b/.github/workflows/typos-config.toml @@ -10,9 +10,10 @@ loosing = "loosing" [files] extend-exclude = [ - "CHANGELOG*.md", - "changelog.json", + "CHANGELOG*.md", + "changelog.json", "echarts.js", + "packages/components/locale/*", "packages/uniapp-components/npm/**/*", "packages/uniapp-pro-components/chat/npm/**/*", "packages/tdesign-uniapp/app/common/uni.css", diff --git a/.gitignore b/.gitignore index 27ad2e4d7..03cce17b5 100644 --- a/.gitignore +++ b/.gitignore @@ -17,7 +17,7 @@ packages/tdesign-miniprogram/test/e2e packages/tdesign-miniprogram/test/unit packages/tdesign-miniprogram/test/unit-virtualHost -# about uniapp +# about uniapp packages/tdesign-uniapp/example/src/_tdesign* packages/tdesign-uniapp/example/src/pages-more/ packages/tdesign-uniapp/npm_dist/ diff --git a/package.json b/package.json index 80989332f..64302aa7d 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ "url": "https://github.com/Tencent/tdesign-miniprogram/issues" }, "scripts": { + "init": "git submodule init && git submodule update", "start": "npm run dev", "dev": "cross-env NODE_ENV=development gulp dev --gulpfile script/gulpfile.js --cwd ./", "uniapp": "pnpm -C packages/tdesign-uniapp", @@ -22,6 +23,7 @@ "update:icons": "node script/update-icons.mjs", "api:css": "node packages/common/scripts/generate-css-vars.mjs Miniprogram", "api:css:uni": "node packages/common/scripts/generate-css-vars.mjs Uniapp", + "update:locale": "gulp copyLocale --gulpfile script/gulpfile.locale.js --cwd ./", "lintfix": "eslint '{packages/components,packages/pro-components,packages/tdesign-miniprogram/example}/**/*.{js,ts}' --fix", "lint": "eslint '{packages/components,packages/pro-components,packages/tdesign-miniprogram/example}/**/*.{js,ts}'", "format": "prettier {packages/components,packages/pro-components,packages/tdesign-miniprogram/example,script}/**/*.{js,ts,wxss,less,wxml,html,json,md,wxs} --write", @@ -125,4 +127,4 @@ "eslint --fix" ] } -} \ No newline at end of file +} diff --git a/packages/common b/packages/common index 29c152354..77df6d88f 160000 --- a/packages/common +++ b/packages/common @@ -1 +1 @@ -Subproject commit 29c152354dc7dabdcc774e9afb95ec5589b2faea +Subproject commit 77df6d88f4f4c13da066f002a2c3d8536df24fb1 diff --git a/packages/components/action-sheet/README.md b/packages/components/action-sheet/README.md index 13e6d7e92..1a46ded94 100644 --- a/packages/components/action-sheet/README.md +++ b/packages/components/action-sheet/README.md @@ -13,7 +13,7 @@ isComponent: true 该组件于 0.9.0 版本上线,请留意版本。 - + ## 引入 全局引入,在 miniprogram 根目录下的`app.json`中配置,局部引入,在需要引入的页面或组件的`index.json`中配置。 diff --git a/packages/components/action-sheet/action-sheet.ts b/packages/components/action-sheet/action-sheet.ts index 2cbe55fe0..b6df35a92 100644 --- a/packages/components/action-sheet/action-sheet.ts +++ b/packages/components/action-sheet/action-sheet.ts @@ -4,15 +4,16 @@ import config from '../common/config'; import { ActionSheetTheme, show } from './show'; import props from './props'; import useCustomNavbar from '../mixins/using-custom-navbar'; +import usingConfig from '../mixins/using-config'; const { prefix } = config; -const name = `${prefix}-action-sheet`; +const componentName = 'action-sheet'; @wxComponent() export default class ActionSheet extends SuperComponent { static show = show; - behaviors = [useCustomNavbar]; + behaviors = [useCustomNavbar, usingConfig({ componentName })]; externalClasses = [`${prefix}-class`, `${prefix}-class-content`, `${prefix}-class-cancel`]; @@ -22,7 +23,7 @@ export default class ActionSheet extends SuperComponent { data = { prefix, - classPrefix: name, + classPrefix: `${prefix}-${componentName}`, gridThemeItems: [], currentSwiperIndex: 0, defaultPopUpProps: {}, @@ -40,6 +41,9 @@ export default class ActionSheet extends SuperComponent { items() { this.splitGridThemeActions(); }, + globalConfig() { + this.updateInitialData(); + }, }; lifetimes = { @@ -55,6 +59,10 @@ export default class ActionSheet extends SuperComponent { }, memoInitialData() { + this.updateInitialData(); + }, + + updateInitialData() { this.initialData = { ...this.properties, ...this.data, diff --git a/packages/components/action-sheet/action-sheet.wxml b/packages/components/action-sheet/action-sheet.wxml index 95d4a4e96..248b9933b 100644 --- a/packages/components/action-sheet/action-sheet.wxml +++ b/packages/components/action-sheet/action-sheet.wxml @@ -61,7 +61,7 @@ bind:tap="onCancel" aria-role="button" > - {{ cancelText || '取消' }} + {{ cancelText || globalConfig.cancel }} diff --git a/packages/components/avatar/README.md b/packages/components/avatar/README.md index b4e793326..77374eba0 100644 --- a/packages/components/avatar/README.md +++ b/packages/components/avatar/README.md @@ -5,7 +5,7 @@ spline: data isComponent: true --- - + ## 引入 全局引入,在 miniprogram 根目录下的`app.json`中配置,局部引入,在需要引入的页面或组件的`index.json`中配置。 diff --git a/packages/components/back-top/README.md b/packages/components/back-top/README.md index b68189dd7..52d391fc7 100644 --- a/packages/components/back-top/README.md +++ b/packages/components/back-top/README.md @@ -5,7 +5,7 @@ spline: navigation isComponent: true --- - + ## 引入 全局引入,在 miniprogram 根目录下的`app.json`中配置,局部引入,在需要引入的页面或组件的`index.json`中配置。 diff --git a/packages/components/badge/README.md b/packages/components/badge/README.md index 86df76419..5b008408b 100644 --- a/packages/components/badge/README.md +++ b/packages/components/badge/README.md @@ -5,7 +5,7 @@ spline: data isComponent: true --- - + ## 引入 全局引入,在 miniprogram 根目录下的`app.json`中配置,局部引入,在需要引入的页面或组件的`index.json`中配置。 diff --git a/packages/components/button/README.md b/packages/components/button/README.md index 53998c0f6..3d204c70a 100644 --- a/packages/components/button/README.md +++ b/packages/components/button/README.md @@ -5,7 +5,7 @@ spline: base isComponent: true --- - + ## 引入 全局引入,在 miniprogram 根目录下的`app.json`中配置,局部引入,在需要引入的页面或组件的`index.json`中配置。 diff --git a/packages/components/calendar/README.md b/packages/components/calendar/README.md index 1644760f7..377a5d9ad 100644 --- a/packages/components/calendar/README.md +++ b/packages/components/calendar/README.md @@ -12,7 +12,7 @@ isComponent: true 该组件于 0.22.0 版本上线,请留意版本。 - + ## 引入 全局引入,在 miniprogram 根目录下的`app.json`中配置,局部引入,在需要引入的页面或组件的`index.json`中配置。 diff --git a/packages/components/calendar/calendar.ts b/packages/components/calendar/calendar.ts index 9f3ac4efd..4d9d16522 100644 --- a/packages/components/calendar/calendar.ts +++ b/packages/components/calendar/calendar.ts @@ -4,24 +4,23 @@ import props from './props'; import TCalendar from '../common/shared/calendar/index'; import { TdCalendarProps } from './type'; import useCustomNavbar from '../mixins/using-custom-navbar'; +import usingConfig from '../mixins/using-config'; import { getPrevMonth, getPrevYear, getNextMonth, getNextYear } from './utils'; const { prefix } = config; -const name = `${prefix}-calendar`; - -const defaultLocaleText = { - title: '请选择日期', - weekdays: ['日', '一', '二', '三', '四', '五', '六'], - monthTitle: '{year} 年 {month}', - months: ['1 月', '2 月', '3 月', '4 月', '5 月', '6 月', '7 月', '8 月', '9 月', '10 月', '11 月', '12 月'], - confirm: '确认', -}; +const componentName = 'calendar'; export interface CalendarProps extends TdCalendarProps {} @wxComponent() export default class Calendar extends SuperComponent { - behaviors = [useCustomNavbar]; + behaviors = [ + useCustomNavbar, + usingConfig({ + componentName, + localeTextPropName: 'localeText', + }), + ]; externalClasses = [`${prefix}-class`]; @@ -33,7 +32,7 @@ export default class Calendar extends SuperComponent { data = { prefix, - classPrefix: name, + classPrefix: `${prefix}-${componentName}`, months: [], scrollIntoView: '', innerConfirmBtn: {}, @@ -64,14 +63,8 @@ export default class Calendar extends SuperComponent { }, ready() { - const realLocalText = { ...defaultLocaleText, ...this.properties.localeText }; this.initialValue(); - this.setData({ - days: this.base.getDays(realLocalText.weekdays), - realLocalText, - }); - this.calcMonths(); this.updateCurrentMonth(); @@ -82,6 +75,18 @@ export default class Calendar extends SuperComponent { }; observers = { + localeText() { + this.updateLocale?.(); + }, + + globalConfig() { + const { globalConfig } = this.data; + this.setData({ + days: this.base.getDays(globalConfig.weekdays), + realLocalText: globalConfig, + }); + }, + type(v) { this.base.type = v; }, diff --git a/packages/components/cascader/README.en-US.md b/packages/components/cascader/README.en-US.md index 3d1e21b9d..cb48f5df1 100644 --- a/packages/components/cascader/README.en-US.md +++ b/packages/components/cascader/README.en-US.md @@ -12,7 +12,7 @@ check-strictly | Boolean | false | \- | N close-btn | Boolean | true | \- | N keys | Object | - | Typescript: `CascaderKeysType` `type CascaderKeysType = TreeKeysType`。[see more ts definition](https://github.com/Tencent/tdesign-miniprogram/blob/develop/packages/components/common/common.ts)。[see more ts definition](https://github.com/Tencent/tdesign-miniprogram/blob/develop/packages/components/cascader/type.ts) | N options | Array | [] | Typescript: `Array` | N -placeholder | String | 选择选项 | \- | N +placeholder | String | - | \- | N sub-titles | Array | [] | Typescript: `Array` | N theme | String | step | options: step/tab | N title | String | - | \- | N diff --git a/packages/components/cascader/README.md b/packages/components/cascader/README.md index d8311094a..be8de1f1f 100644 --- a/packages/components/cascader/README.md +++ b/packages/components/cascader/README.md @@ -12,7 +12,7 @@ isComponent: true 该组件于 0.23.0 版本上线,请留意版本。 - + ## 引入 全局引入,在 miniprogram 根目录下的`app.json`中配置,局部引入,在需要引入的页面或组件的`index.json`中配置。 @@ -69,7 +69,7 @@ check-strictly | Boolean | false | 父子节点选中状态不再关联,可各 close-btn | Boolean | true | 关闭按钮 | N keys | Object | - | 用来定义 value / label / children / disabled 在 `options` 中对应的字段别名。TS 类型:`CascaderKeysType` `type CascaderKeysType = TreeKeysType`。[通用类型定义](https://github.com/Tencent/tdesign-miniprogram/blob/develop/packages/components/common/common.ts)。[详细类型定义](https://github.com/Tencent/tdesign-miniprogram/blob/develop/packages/components/cascader/type.ts) | N options | Array | [] | 可选项数据源。TS 类型:`Array` | N -placeholder | String | 选择选项 | 未选中时的提示文案 | N +placeholder | String | - | 未选中时的提示文案。组件内置默认值为:'选择选项' | N sub-titles | Array | [] | 每级展示的次标题。TS 类型:`Array` | N theme | String | step | 展示风格。可选项:step/tab | N title | String | - | 标题 | N diff --git a/packages/components/cascader/cascader.ts b/packages/components/cascader/cascader.ts index ac13625f3..b5e5a58d5 100644 --- a/packages/components/cascader/cascader.ts +++ b/packages/components/cascader/cascader.ts @@ -3,9 +3,10 @@ import config from '../common/config'; import props from './props'; import { TdCascaderProps } from './type'; import { getRect } from '../common/utils'; +import usingConfig from '../mixins/using-config'; const { prefix } = config; -const name = `${prefix}-cascader`; +const componentName = 'cascader'; export interface CascaderProps extends TdCascaderProps {} @@ -36,6 +37,8 @@ const defaultState = { @wxComponent() export default class Cascader extends SuperComponent { + behaviors = [usingConfig({ componentName })]; + externalClasses = [`${prefix}-class`]; options: WechatMiniprogram.Component.ComponentOptions = { @@ -58,7 +61,7 @@ export default class Cascader extends SuperComponent { data = { prefix, - name, + classPrefix: `${prefix}-${componentName}`, stepIndex: 0, selectedIndexes: [], selectedValue: [], @@ -135,13 +138,14 @@ export default class Cascader extends SuperComponent { }, async initOptionsHeight(steps: number) { + const { classPrefix } = this.data; const { theme, subTitles } = this.properties; - const { height } = await getRect(this, `.${name}__content`); + const { height } = await getRect(this, `.${classPrefix}__content`); this.state.contentHeight = height; if (theme === 'step') { - await Promise.all([getRect(this, `.${name}__steps`), getRect(this, `.${name}__step`)]).then( + await Promise.all([getRect(this, `.${classPrefix}__steps`), getRect(this, `.${classPrefix}__step`)]).then( ([stepsRect, stepRect]) => { this.state.stepsInitHeight = stepsRect.height - (steps - 1) * stepRect.height; this.state.stepHeight = stepRect.height; @@ -150,7 +154,7 @@ export default class Cascader extends SuperComponent { } if (subTitles.length > 0) { - const { height } = await getRect(this, `.${name}__options-title`); + const { height } = await getRect(this, `.${classPrefix}__options-title`); this.state.subTitlesHeight = height; } @@ -229,7 +233,7 @@ export default class Cascader extends SuperComponent { }); }, genItems() { - const { options, selectedIndexes, keys, placeholder } = this.data; + const { options, selectedIndexes, keys, placeholder, globalConfig } = this.data; const selectedValue = []; const steps = []; const items = [parseOptions(options, keys)]; @@ -251,7 +255,7 @@ export default class Cascader extends SuperComponent { } if (steps.length < items.length) { - steps.push(placeholder); + steps.push(placeholder || globalConfig.placeholder); } return { diff --git a/packages/components/cascader/cascader.wxml b/packages/components/cascader/cascader.wxml index 56ad3cc81..bee766aba 100644 --- a/packages/components/cascader/cascader.wxml +++ b/packages/components/cascader/cascader.wxml @@ -1,29 +1,37 @@ - - + + - {{title}} + {{title || globalConfig.title}} - + - + - - + + - + {{ item }} - + @@ -33,18 +41,18 @@ - {{subTitles[stepIndex]}} ; }; /** - * 未选中时的提示文案 - * @default 选择选项 + * 未选中时的提示文案。组件内置默认值为:'选择选项' + * @default '' */ placeholder?: { type: StringConstructor; diff --git a/packages/components/cell/README.md b/packages/components/cell/README.md index ef5dc8433..8e6213daf 100644 --- a/packages/components/cell/README.md +++ b/packages/components/cell/README.md @@ -5,7 +5,7 @@ spline: data isComponent: true --- - + ## 引入 全局引入,在 miniprogram 根目录下的`app.json`中配置,局部引入,在需要引入的页面或组件的`index.json`中配置。 diff --git a/packages/components/checkbox/README.md b/packages/components/checkbox/README.md index b1b3dbe4d..2630a940b 100644 --- a/packages/components/checkbox/README.md +++ b/packages/components/checkbox/README.md @@ -5,7 +5,7 @@ spline: form isComponent: true --- - + ## 引入 全局引入,在 miniprogram 根目录下的`app.json`中配置,局部引入,在需要引入的页面或组件的`index.json`中配置。 diff --git a/packages/components/collapse/README.md b/packages/components/collapse/README.md index 34f932500..0859c204f 100644 --- a/packages/components/collapse/README.md +++ b/packages/components/collapse/README.md @@ -13,7 +13,7 @@ isComponent: true 该组件于 0.7.3 版本上线,请留意版本。 - + ## 引入 全局引入,在 miniprogram 根目录下的`app.json`中配置,局部引入,在需要引入的页面或组件的`index.json`中配置。 diff --git a/packages/components/common/utils.ts b/packages/components/common/utils.ts index f102f5233..684e88538 100644 --- a/packages/components/common/utils.ts +++ b/packages/components/common/utils.ts @@ -262,6 +262,19 @@ export const setIcon = (iconName, icon, defaultIcon) => { export const toCamel = (str) => str.replace(/-(\w)/g, (match, m1) => m1.toUpperCase()); +/** + * 将 camelCase 转换为 kebab-case + * @param str 需要转换的字符串 + * @returns kebab-case 格式的字符串 + */ +export function toKebabCase(str: string): string { + return str + .replace(/([a-z])([A-Z])/g, '$1-$2') + .replace(/([A-Z])([A-Z][a-z])/g, '$1-$2') + .replace(/([0-9])([a-zA-Z])/g, '$1-$2') + .toLowerCase(); +} + export const getCurrentPage = function () { const pages = getCurrentPages(); return pages[pages.length - 1] as T & WechatMiniprogram.Page.TrivialInstance; diff --git a/packages/components/common/utils.wxs b/packages/components/common/utils.wxs index 774cd1108..a33945151 100644 --- a/packages/components/common/utils.wxs +++ b/packages/components/common/utils.wxs @@ -110,7 +110,7 @@ function _style(styles) { return item != null && item !== ''; }) .map(function (item) { - return isArray(item) ? _style(item) : endsWith(item, ';'); + return isString(item) ? endsWith(item, ';') : _style(item); }) .join(' '); } diff --git a/packages/components/config-provider/README.en-US.md b/packages/components/config-provider/README.en-US.md new file mode 100644 index 000000000..9b32d7992 --- /dev/null +++ b/packages/components/config-provider/README.en-US.md @@ -0,0 +1,176 @@ +:: BASE_DOC :: + +## API + +### ConfigProvider Props + +name | type | default | description | required +-- | -- | -- | -- | -- +style | Object | - | CSS(Cascading Style Sheets) | N +custom-style | Object | - | CSS(Cascading Style Sheets),used to set style on virtual component | N +global-config | Object | - | global config。Typescript: `GlobalConfigProvider` | N +theme-vars | Object | - | theme Variables | N + +### GlobalConfigProvider + +name | type | default | description | required +-- | -- | -- | -- | -- +action-sheet | Object | - | ActionSheet global configs。Typescript: `ActionSheetConfig` | N +calendar | Object | - | Calendar global configs。Typescript: `CalendarConfig` | N +cascader | Object | - | Cascader global configs。Typescript: `CascaderConfig` | N +class-prefix | String | t | \- | N +date-time-picker | Object | - | DateTimePicker global configs。Typescript: `DateTimePickerConfig` | N +dropdown-menu | Object | - | DropdownMenu global configs。Typescript: `DropdownMenuConfig` | N +guide | Object | - | Guide global configs。Typescript: `GuideConfig` | N +picker | Object | - | Picker global configs。Typescript: `PickerConfig` | N +pull-down-refresh | Object | - | PullDownRefresh global configs。Typescript: `PullDownRefreshConfig` | N +qrcode | Object | - | QRCode global configs。Typescript: `QRCodeConfig` | N +rate | Object | - | Rate global configs。Typescript: `RateConfig` | N +tab-bar | Object | - | TabBar global configs。Typescript: `TabBarConfig` | N +upload | Object | - | Upload global configs。Typescript: `UploadConfig` | N + +### ActionSheetConfig + +name | type | default | description | required +-- | -- | -- | -- | -- +cancel | String | - | cancel text | N + +### AttachmentsConfig + +name | type | default | description | required +-- | -- | -- | -- | -- +status | Object | - | Typescript: `{ pending: string; fail: string; }` | N + +### CalendarConfig + +name | type | default | description | required +-- | -- | -- | -- | -- +confirm | String | - | confirm text | N +month-title | String | - | \- | N +months | Array | - | Typescript: `string[]` | N +title | String | - | \- | N +weekdays | Array | - | Typescript: `string[]` | N + +### CascaderConfig + +name | type | default | description | required +-- | -- | -- | -- | -- +placeholder | String | - | \- | N +title | String | - | \- | N + +### ChatActionbarConfig + +name | type | default | description | required +-- | -- | -- | -- | -- +action-bar | Object | - | Typescript: `{ replay: string; copy: string; good: string; bad: string; share: string; quote: string; }` | N + +### ChatSenderConfig + +name | type | default | description | required +-- | -- | -- | -- | -- +placeholder | String | - | \- | N +send-text | String | - | \- | N +stop-text | String | - | \- | N + +### ChatThinkingConfig + +name | type | default | description | required +-- | -- | -- | -- | -- +status | Object | - | Typescript: `{ pending: string; complete: string; stop: string; }` | N + +### DateTimePickerConfig + +name | type | default | description | required +-- | -- | -- | -- | -- +cancel | String | - | \- | N +confirm | String | - | \- | N +date-label | String | - | \- | N +format | String | 'YYYY-MM-DD HH:mm:ss' | \- | N +hour-label | String | - | \- | N +minute-label | String | - | \- | N +month-label | String | - | \- | N +second-label | String | - | \- | N +title | String | - | \- | N +year-label | String | - | \- | N + +### DropdownMenuConfig + +name | type | default | description | required +-- | -- | -- | -- | -- +confirm | String | - | confirm text | N +reset | String | - | reset text | N + +### GuideConfig + +name | type | default | description | required +-- | -- | -- | -- | -- +back | String | - | \- | N +finish | String | - | \- | N +next | String | - | \- | N +skip | String | - | \- | N + +### ImageConfig + +name | type | default | description | required +-- | -- | -- | -- | -- +error-text | String | - | loading text, default value is "Error" | N +loading-text | String | - | loading text, default value is "loading" | N +replace-image-src | Function | - | replace all `src` attribute of images。Typescript: `(params: ImageProps) => string`,[Image API Documents](./image?tab=api)。[see more ts definition](https://github.com/Tencent/tdesign-miniprogram/blob/develop/packages/components/config-provider/type.ts) | N + +### InputConfig + +name | type | default | description | required +-- | -- | -- | -- | -- +placeholder | String | - | \- | N + +### PickerConfig + +name | type | default | description | required +-- | -- | -- | -- | -- +cancel | String | - | cancel text | N +confirm | String | - | confirm text | N + +### PullDownRefreshConfig + +name | type | default | description | required +-- | -- | -- | -- | -- +loading-texts | Array | - | Typescript: `string[]` | N + +### QRCodeConfig + +name | type | default | description | required +-- | -- | -- | -- | -- +expired-text | String | - | Language configuration, "QR code expired" description text | N +refresh-text | String | - | Language configuration, "QR code refresh" description text | N +scanned-text | String | - | Language configuration, "QR code scanned" description text | N + +### RateConfig + +name | type | default | description | required +-- | -- | -- | -- | -- +no-value-text | String | - | \- | N +value-text | String | - | \- | N + +### TabBarConfig + +name | type | default | description | required +-- | -- | -- | -- | -- +have-more-news-aria-label | String | - | \- | N +have-news-aria-label | String | - | \- | N +more-news-aria-label | String | - | \- | N +news-aria-label | String | - | \- | N + +### UploadConfig + +name | type | default | description | required +-- | -- | -- | -- | -- +progress | Object | - | Typescript: `UploadConfigProgress` | N + +### UploadConfigProgress + +name | type | default | description | required +-- | -- | -- | -- | -- +fail-text | String | - | \- | N +success-text | String | - | \- | N +uploading-text | String | - | \- | N +waiting-text | String | - | \- | N diff --git a/packages/components/config-provider/README.md b/packages/components/config-provider/README.md new file mode 100644 index 000000000..7278f09e8 --- /dev/null +++ b/packages/components/config-provider/README.md @@ -0,0 +1,206 @@ +--- +title: ConfigProvider 全局特性配置 +description: 全局特性配置包含各个组件的文本语言配置及其他通用配置,可以减少重复的通用配置。 +spline: data +isComponent: true +--- + + + +## 引入 + +全局引入,在 miniprogram 根目录下的`app.json`中配置,局部引入,在需要引入的页面或组件的`index.json`中配置。 + +```json +"usingComponents": { + "t-config-provider": "tdesign-miniprogram/config-provider/config-provider" +} +``` + +## 代码演示 + + 在开发者工具中预览效果 + +
+

Tips: 请确保开发者工具为打开状态。导入开发者工具后,依次执行:npm i > 构建npm包 > 勾选 "将JS编译成ES5"

+
+ +### 01 配置示例 + +{{ base }} + + +## API + +### ConfigProvider Props + +名称 | 类型 | 默认值 | 描述 | 必传 +-- | -- | -- | -- | -- +style | Object | - | 样式 | N +custom-style | Object | - | 样式,一般用于开启虚拟化组件节点场景 | N +global-config | Object | - | 全局配置。TS 类型:`GlobalConfigProvider` | N +theme-vars | Object | - | 全局配置 | N + +### GlobalConfigProvider + +名称 | 类型 | 默认值 | 描述 | 必传 +-- | -- | -- | -- | -- +action-sheet | Object | - | 动作面板全局配置。TS 类型:`ActionSheetConfig` | N +calendar | Object | - | 日历组件全局配置。TS 类型:`CalendarConfig` | N +cascader | Object | - | 级联选择器全局配置。TS 类型:`CascaderConfig` | N +class-prefix | String | t | CSS 类名前缀 | N +date-time-picker | Object | - | 时间选择器全局配置。TS 类型:`DateTimePickerConfig` | N +dropdown-menu | Object | - | 下拉菜单全局配置。TS 类型:`DropdownMenuConfig` | N +guide | Object | - | 引导全局配置。TS 类型:`GuideConfig` | N +picker | Object | - | 选择器全局配置。TS 类型:`PickerConfig` | N +pull-down-refresh | Object | - | 下拉刷新全局配置。TS 类型:`PullDownRefreshConfig` | N +qrcode | Object | - | 二维码全局配置。TS 类型:`QRCodeConfig` | N +rate | Object | - | 评分全局配置。TS 类型:`RateConfig` | N +tab-bar | Object | - | 标签栏全局配置。TS 类型:`TabBarConfig` | N +upload | Object | - | 上传组件全局配置。TS 类型:`UploadConfig` | N + +### ActionSheetConfig + +名称 | 类型 | 默认值 | 描述 | 必传 +-- | -- | -- | -- | -- +cancel | String | - | 语言配置,“取消” 按钮描述文本 | N + +### AttachmentsConfig + +名称 | 类型 | 默认值 | 描述 | 必传 +-- | -- | -- | -- | -- +status | Object | - | 语言配置,附件上传状态描述文本。TS 类型:`{ pending: string; fail: string; }` | N + +### CalendarConfig + +名称 | 类型 | 默认值 | 描述 | 必传 +-- | -- | -- | -- | -- +confirm | String | - | 语言配置,“确定” 按钮描述文本 | N +month-title | String | - | 语言配置,日期月面板标题描述文本。示例:“{year} / {month}” | N +months | Array | - | 月文本描述,默认值:['1 月', '2 月', '3 月', '4 月', '5 月', '6 月', '7 月', '8 月', '9 月', '10 月', '11 月', '12 月']。TS 类型:`string[]` | N +title | String | - | 语言配置,组件标题“请选择日期”描述文本 | N +weekdays | Array | - | 星期文本描述,默认值:['日', '一', '二', '三', '四', '五', '六']。TS 类型:`string[]` | N + +### CascaderConfig + +名称 | 类型 | 默认值 | 描述 | 必传 +-- | -- | -- | -- | -- +placeholder | String | - | 语言配置,未选中时的提示文案“选择选项”描述文本 | N +title | String | - | 语言配置,组件标题“选择地址”描述文本 | N + +### ChatActionbarConfig + +名称 | 类型 | 默认值 | 描述 | 必传 +-- | -- | -- | -- | -- +action-bar | Object | - | 语言配置,对话操作栏描述文本。TS 类型:`{ replay: string; copy: string; good: string; bad: string; share: string; quote: string; }` | N + +### ChatSenderConfig + +名称 | 类型 | 默认值 | 描述 | 必传 +-- | -- | -- | -- | -- +placeholder | String | - | 语言配置,占位符描述文本 | N +send-text | String | - | 语言配置,发送按钮描述文本 | N +stop-text | String | - | 语言配置,停止发送按钮描述文本 | N + +### ChatThinkingConfig + +名称 | 类型 | 默认值 | 描述 | 必传 +-- | -- | -- | -- | -- +status | Object | - | 语言配置,思考状态描述文本。TS 类型:`{ pending: string; complete: string; stop: string; }` | N + +### DateTimePickerConfig + +名称 | 类型 | 默认值 | 描述 | 必传 +-- | -- | -- | -- | -- +cancel | String | - | 语言配置,“取消”按钮描述文本 | N +confirm | String | - | 语言配置,“确定”按钮描述文本 | N +date-label | String | - | 语言配置,“日” 描述文本 | N +format | String | 'YYYY-MM-DD HH:mm:ss' | 日期格式化规则 | N +hour-label | String | - | 语言配置,“时” 描述文本 | N +minute-label | String | - | 语言配置,“分” 描述文本 | N +month-label | String | - | 语言配置,“月” 描述文本 | N +second-label | String | - | 语言配置,“秒” 描述文本 | N +title | String | - | 语言配置,组件标题“选择时间”描述文本 | N +year-label | String | - | 语言配置,“年” 描述文本 | N + +### DropdownMenuConfig + +名称 | 类型 | 默认值 | 描述 | 必传 +-- | -- | -- | -- | -- +confirm | String | - | 语言配置,“确定” 按钮描述文本 | N +reset | String | - | 语言配置,“重置” 按钮描述文本 | N + +### GuideConfig + +名称 | 类型 | 默认值 | 描述 | 必传 +-- | -- | -- | -- | -- +back | String | - | 语言配置, “返回” 描述文本 | N +finish | String | - | 语言配置, “完成” 描述文本 | N +next | String | - | 语言配置, “下一步” 描述文本 | N +skip | String | - | 语言配置, “跳过” 描述文本 | N + +### ImageConfig + +名称 | 类型 | 默认值 | 描述 | 必传 +-- | -- | -- | -- | -- +error-text | String | - | 图片加载失败显示的文本,中文默认为“图片无法显示” | N +loading-text | String | - | 图片加载中显示的文本,中文默认为“图片加载中” | N +replace-image-src | Function | - | 统一替换图片 `src` 地址,参数为组件的全部属性,返回值为新的图片地址。TS 类型:`(params: ImageProps) => string`,[Image API Documents](./image?tab=api)。[详细类型定义](https://github.com/Tencent/tdesign-miniprogram/blob/develop/packages/components/config-provider/type.ts) | N + +### InputConfig + +名称 | 类型 | 默认值 | 描述 | 必传 +-- | -- | -- | -- | -- +placeholder | String | - | 语言配置,“请输入”占位符描述文本 | N + +### PickerConfig + +名称 | 类型 | 默认值 | 描述 | 必传 +-- | -- | -- | -- | -- +cancel | String | - | 语言配置,“取消” 按钮描述文本 | N +confirm | String | - | 语言配置,“确认” 按钮描述文本 | N + +### PullDownRefreshConfig + +名称 | 类型 | 默认值 | 描述 | 必传 +-- | -- | -- | -- | -- +loading-texts | Array | - | 提示文本描述,默认值:['下拉刷新', '松手刷新', '正在刷新', '刷新完成']。TS 类型:`string[]` | N + +### QRCodeConfig + +名称 | 类型 | 默认值 | 描述 | 必传 +-- | -- | -- | -- | -- +expired-text | String | - | 语言配置,“二维码过期”描述文本 | N +refresh-text | String | - | 语言配置,“点击刷新”描述文本 | N +scanned-text | String | - | 语言配置,“已扫描”描述文本 | N + +### RateConfig + +名称 | 类型 | 默认值 | 描述 | 必传 +-- | -- | -- | -- | -- +no-value-text | String | - | 语言配置,“未评分”描述文本 | N +value-text | String | - | 语言配置,评分值描述文本。示例:“{value} 分” | N + +### TabBarConfig + +名称 | 类型 | 默认值 | 描述 | 必传 +-- | -- | -- | -- | -- +have-more-news-aria-label | String | - | 语言配置,“有n+条新的消息”描述文本。示例:“有 {value}+ 条消息” | N +have-news-aria-label | String | - | 语言配置,“有n条新的消息”描述文本。示例:“有 {value} 条消息” | N +more-news-aria-label | String | - | 语言配置,“有很多消息”描述文本 | N +news-aria-label | String | - | 语言配置,“有新的消息”描述文本 | N + +### UploadConfig + +名称 | 类型 | 默认值 | 描述 | 必传 +-- | -- | -- | -- | -- +progress | Object | - | 语言配置,上传进度相关。示例:{ uploadText: '上传中', waitingText: '待上传', 'failText': '上传失败', successText: '上传成功' }。TS 类型:`UploadConfigProgress` | N + +### UploadConfigProgress + +名称 | 类型 | 默认值 | 描述 | 必传 +-- | -- | -- | -- | -- +fail-text | String | - | 语言配置,“上传失败”文本描述 | N +success-text | String | - | 语言配置,“上传成功”文本描述 | N +uploading-text | String | - | 语言配置,“上传中”文本描述 | N +waiting-text | String | - | 语言配置,“待上传”文本描述 | N diff --git a/packages/components/config-provider/__test__/__snapshots__/demo.test.js.snap b/packages/components/config-provider/__test__/__snapshots__/demo.test.js.snap new file mode 100644 index 000000000..46f5aa599 --- /dev/null +++ b/packages/components/config-provider/__test__/__snapshots__/demo.test.js.snap @@ -0,0 +1,501 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`ConfigProvider ConfigProvider chat-en demo works fine 1`] = ` + + + + + + + +`; + +exports[`ConfigProvider ConfigProvider other-en demo works fine 1`] = ` + + + + + Rating + + + + + + + + + +`; + +exports[`ConfigProvider ConfigProvider upload-en demo works fine 1`] = ` + + + + + + + +`; diff --git a/packages/components/config-provider/__test__/demo.test.js b/packages/components/config-provider/__test__/demo.test.js new file mode 100644 index 000000000..457b7c5cb --- /dev/null +++ b/packages/components/config-provider/__test__/demo.test.js @@ -0,0 +1,19 @@ +/** + * 该文件为由脚本 `npm run test:demo` 自动生成,如需修改,执行脚本命令即可。请勿手写直接修改,否则会被覆盖 + */ + +import path from 'path'; +import simulate from 'miniprogram-simulate'; + +const mapper = ['chat-en', 'other-en', 'upload-en']; + +describe('ConfigProvider', () => { + mapper.forEach((demoName) => { + it(`ConfigProvider ${demoName} demo works fine`, () => { + const id = load(path.resolve(__dirname, `../../config-provider/_example/${demoName}/index`), demoName); + const container = simulate.render(id); + container.attach(document.createElement('parent-wrapper')); + expect(container.toJSON()).toMatchSnapshot(); + }); + }); +}); diff --git a/packages/components/config-provider/__test__/index.test.js b/packages/components/config-provider/__test__/index.test.js new file mode 100644 index 000000000..844d03b66 --- /dev/null +++ b/packages/components/config-provider/__test__/index.test.js @@ -0,0 +1,726 @@ +import path from 'path'; +import simulate from 'miniprogram-simulate'; +import { configStore } from '../config-store'; +import ReactiveState from '../reactive-state'; +import themeVarsToCSS from '../utils'; +import { getComponentLocale, useConfig } from '../use-config'; + +describe('config-provider', () => { + const configProvider = load(path.resolve(__dirname, '../config-provider')); + + it(`: style && customStyle`, async () => { + const id = simulate.load({ + template: ``, + data: { + style: 'color: red', + customStyle: 'font-size: 9px', + }, + + usingComponents: { + 't-config-provider': configProvider, + }, + }); + const comp = simulate.render(id); + comp.attach(document.createElement('parent-wrapper')); + const $configProvider = comp.querySelector('.config >>> .t-config-provider'); + if (VIRTUAL_HOST) { + expect( + $configProvider.dom.getAttribute('style').includes(`${comp.data.style}; ${comp.data.customStyle}`), + ).toBeTruthy(); + } else { + expect($configProvider.dom.getAttribute('style').includes(`${comp.data.customStyle}`)).toBeTruthy(); + } + }); + + it(`:base`, async () => { + const id = simulate.load({ + template: ``, + usingComponents: { + 't-config-provider': configProvider, + }, + }); + const comp = simulate.render(id); + comp.attach(document.createElement('parent-wrapper')); + + const $config = comp.querySelector('.config >>> .t-config-provider'); + expect($config).toBeTruthy(); + }); + + it(`:globalConfig`, async () => { + const id = simulate.load({ + template: ``, + data: { + globalConfig: { + calendar: { + title: '选择日期', + confirm: '确定', + weekdays: ['日', '一', '二', '三', '四', '五', '六'], + }, + picker: { + cancel: '取消', + confirm: '确认', + }, + }, + }, + usingComponents: { + 't-config-provider': configProvider, + }, + }); + const comp = simulate.render(id); + comp.attach(document.createElement('parent-wrapper')); + + const $config = comp.querySelector('.config >>> .t-config-provider'); + expect($config).toBeTruthy(); + + // 更新 globalConfig + await simulate.sleep(10); + comp.setData({ + globalConfig: { + calendar: { + title: 'Select Date', + confirm: 'Confirm', + weekdays: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'], + }, + }, + }); + + await simulate.sleep(10); + }); + + it(`:themeVars`, async () => { + const id = simulate.load({ + template: ``, + data: { + themeVars: { + buttonPrimaryBorderColor: '#ff6b00', + 'button-primary-color': '#ff6b00', + 'button-primary-bg-color': '#ff6a0094', + }, + }, + usingComponents: { + 't-config-provider': configProvider, + }, + }); + const comp = simulate.render(id); + comp.attach(document.createElement('parent-wrapper')); + + const $config = comp.querySelector('.config >>> .t-config-provider'); + expect($config).toBeTruthy(); + + // 更新 themeVars + await simulate.sleep(10); + comp.setData({ + themeVars: { + buttonPrimaryBorderColor: '#0052d9', + 'button-primary-color': '#0052d9', + }, + }); + + await simulate.sleep(10); + }); + + it(`:mixedConfig`, async () => { + const id = simulate.load({ + template: ``, + data: { + globalConfig: { + actionSheet: { + cancel: '取消', + }, + }, + themeVars: { + primaryColor: '#0052d9', + }, + }, + usingComponents: { + 't-config-provider': configProvider, + }, + }); + const comp = simulate.render(id); + comp.attach(document.createElement('parent-wrapper')); + + const $config = comp.querySelector('.config >>> .t-config-provider'); + expect($config).toBeTruthy(); + + // 同时更新 globalConfig 和 themeVars + await simulate.sleep(10); + comp.setData({ + globalConfig: { + actionSheet: { + cancel: 'Cancel', + }, + }, + themeVars: { + primaryColor: '#ff6b00', + }, + }); + + await simulate.sleep(10); + }); + + it(`:componentLifecycle`, async () => { + const id = simulate.load({ + template: ``, + data: { + globalConfig: { + picker: { + cancel: '取消', + }, + }, + }, + usingComponents: { + 't-config-provider': configProvider, + }, + }); + const comp = simulate.render(id); + comp.attach(document.createElement('parent-wrapper')); + + // 检查组件是否正确初始化 + const $config = comp.querySelector('.config >>> .t-config-provider'); + expect($config).toBeTruthy(); + + // 销毁组件 + comp.detach(); + + await simulate.sleep(10); + }); + + it(`:emptyConfig`, async () => { + const id = simulate.load({ + template: ``, + data: { + globalConfig: {}, + themeVars: {}, + }, + usingComponents: { + 't-config-provider': configProvider, + }, + }); + const comp = simulate.render(id); + comp.attach(document.createElement('parent-wrapper')); + + const $config = comp.querySelector('.config >>> .t-config-provider'); + expect($config).toBeTruthy(); + }); +}); + +describe('config-store', () => { + beforeEach(() => { + // 每个测试前重置 store + configStore.currentLocale.value = {}; + configStore.themeVars.value = {}; + }); + + describe('switchLocale', () => { + it('should not switch when componentId is undefined', () => { + const initialValue = configStore.currentLocale.value; + configStore.switchLocale({ test: 'value' }); + expect(configStore.currentLocale.value).toEqual(initialValue); + }); + + it('should set locale on first call', () => { + const componentId = 'test-1'; + const locale = { picker: { cancel: '取消' } }; + configStore.switchLocale(locale, componentId); + expect(configStore.currentLocale.value).toEqual(locale); + }); + + it('should update locale on subsequent calls with different content', () => { + const componentId = 'test-2'; + const locale1 = { picker: { cancel: '取消' } }; + const locale2 = { picker: { cancel: 'Cancel' } }; + + configStore.switchLocale(locale1, componentId); + configStore.switchLocale(locale2, componentId); + + expect(configStore.currentLocale.value).toEqual(locale2); + }); + + it('should not update locale when content is same', () => { + const componentId = 'test-3'; + const locale = { picker: { cancel: '取消' } }; + + configStore.switchLocale(locale, componentId); + const valueAfterFirst = configStore.currentLocale.value; + configStore.switchLocale(locale, componentId); + + expect(configStore.currentLocale.value).toBe(valueAfterFirst); + }); + + it('should handle empty locale', () => { + const componentId = 'test-4'; + const locale1 = { picker: { cancel: '取消' } }; + const locale2 = {}; + + configStore.switchLocale(locale1, componentId); + configStore.switchLocale(locale2, componentId); + + expect(configStore.currentLocale.value).toEqual(locale2); + }); + }); + + describe('updateThemeVars', () => { + it('should merge and override theme vars', () => { + configStore.updateThemeVars({ color: '#ff0000', fontSize: 14 }); + configStore.updateThemeVars({ color: '#0000ff', bgColor: '#00ff00' }); + + expect(configStore.themeVars.value).toEqual({ + color: '#0000ff', + fontSize: 14, + bgColor: '#00ff00', + }); + }); + + it('should handle empty vars', () => { + configStore.updateThemeVars({}); + expect(configStore.themeVars.value).toEqual({}); + }); + }); + + describe('resetPageState', () => { + it('should reset page state', () => { + const componentId = 'test-reset'; + const locale = { picker: { cancel: '取消' } }; + configStore.switchLocale(locale, componentId); + + configStore.resetPageState(componentId); + // After reset, should be able to set locale again as if it's the first time + const newLocale = { picker: { cancel: 'Cancel' } }; + configStore.switchLocale(newLocale, componentId); + expect(configStore.currentLocale.value).toEqual(newLocale); + }); + + it('should handle undefined componentId', () => { + expect(() => { + configStore.resetPageState(undefined); + }).not.toThrow(); + }); + + it('should execute cleanup callback', () => { + const componentId = 'test-cleanup'; + const mockCleanup = jest.fn(); + configStore.registerCleanup(componentId, mockCleanup); + configStore.resetPageState(componentId); + + expect(mockCleanup).toHaveBeenCalled(); + }); + + it('should handle cleanup errors gracefully', () => { + const componentId = 'test-error'; + const errorCleanup = jest.fn(() => { + throw new Error('Cleanup error'); + }); + const consoleSpy = jest.spyOn(console, 'error').mockImplementation(); + + configStore.registerCleanup(componentId, errorCleanup); + expect(() => { + configStore.resetPageState(componentId); + }).not.toThrow(); + + expect(consoleSpy).toHaveBeenCalled(); + consoleSpy.mockRestore(); + }); + }); + + describe('registerCleanup', () => { + it('should register and execute cleanup callback', () => { + const componentId = 'test-register'; + const cleanup = jest.fn(); + configStore.registerCleanup(componentId, cleanup); + configStore.resetPageState(componentId); + expect(cleanup).toHaveBeenCalled(); + }); + }); + + describe('locale comparison behavior', () => { + it('should detect nested object and array changes', () => { + const componentId = 'test-nested'; + const locale1 = { + picker: { + cancel: '取消', + confirm: '确定', + }, + calendar: { + title: '选择日期', + weekdays: ['日', '一', '二', '三', '四', '五', '六'], + }, + }; + const locale2 = { + picker: { + cancel: '取消', + confirm: '确定', + }, + calendar: { + title: 'Select Date', + weekdays: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'], + }, + }; + + configStore.switchLocale(locale1, componentId); + configStore.switchLocale(locale2, componentId); + + expect(configStore.currentLocale.value).toEqual(locale2); + }); + + it('should handle locale with same structure but different values', () => { + const componentId = 'test-diff-value'; + const locale1 = { + picker: { + cancel: '取消', + confirm: '确认', + }, + }; + const locale2 = { + picker: { + cancel: 'Cancel', + confirm: 'Confirm', + }, + }; + + configStore.switchLocale(locale1, componentId); + configStore.switchLocale(locale2, componentId); + + expect(configStore.currentLocale.value).toEqual(locale2); + }); + }); +}); + +describe('reactive-state', () => { + it('should initialize with value', () => { + const state = new ReactiveState({ test: 'value' }); + expect(state.value).toEqual({ test: 'value' }); + }); + + it('should notify subscribers on value change', () => { + const state = new ReactiveState(0); + const mockFn = jest.fn(); + state.subscribe(mockFn); + + state.value = 1; + expect(mockFn).toHaveBeenCalledTimes(2); // initial + change + expect(mockFn).toHaveBeenLastCalledWith(1); + }); + + it('should not notify on same value or reference', () => { + const state = new ReactiveState(0); + const mockFn = jest.fn(); + state.subscribe(mockFn); + + state.value = 0; + expect(mockFn).toHaveBeenCalledTimes(1); // only initial + + const obj = { a: 1 }; + state.value = obj; + state.value = obj; + expect(mockFn).toHaveBeenCalledTimes(2); // initial + obj change + }); + + it('should unsubscribe correctly', () => { + const state = new ReactiveState(0); + const mockFn = jest.fn(); + const unsubscribe = state.subscribe(mockFn); + + unsubscribe(); + state.value = 1; + + expect(mockFn).toHaveBeenCalledTimes(1); // only initial + }); + + it('should handle multiple subscribers', () => { + const state = new ReactiveState(0); + const mockFn1 = jest.fn(); + const mockFn2 = jest.fn(); + + state.subscribe(mockFn1); + state.subscribe(mockFn2); + + state.value = 1; + + expect(mockFn1).toHaveBeenLastCalledWith(1); + expect(mockFn2).toHaveBeenLastCalledWith(1); + }); + + it('should handle different value types', () => { + const objState = new ReactiveState({ a: 1 }); + const mockFn1 = jest.fn(); + objState.subscribe(mockFn1); + + objState.value = { a: 2 }; + expect(mockFn1).toHaveBeenLastCalledWith({ a: 2 }); + + const arrState = new ReactiveState([1, 2, 3]); + const mockFn2 = jest.fn(); + arrState.subscribe(mockFn2); + + arrState.value = [1, 2, 4]; + expect(mockFn2).toHaveBeenLastCalledWith([1, 2, 4]); + + const boolState = new ReactiveState(false); + const mockFn3 = jest.fn(); + boolState.subscribe(mockFn3); + + boolState.value = true; + expect(mockFn3).toHaveBeenLastCalledWith(true); + }); + + it('should handle falsy values correctly', () => { + const state = new ReactiveState(0); + const mockFn = jest.fn(); + state.subscribe(mockFn); + + state.value = 0; + expect(mockFn).toHaveBeenCalledTimes(1); // only initial + + state.value = null; + expect(mockFn).toHaveBeenLastCalledWith(null); + + state.value = undefined; + expect(mockFn).toHaveBeenLastCalledWith(undefined); + }); + + it('should unsubscribe only specific listener', () => { + const state = new ReactiveState(0); + const mockFn1 = jest.fn(); + const mockFn2 = jest.fn(); + const mockFn3 = jest.fn(); + + const unsubscribe1 = state.subscribe(mockFn1); + state.subscribe(mockFn2); + const unsubscribe3 = state.subscribe(mockFn3); + + unsubscribe1(); + + state.value = 1; + + expect(mockFn1).toHaveBeenCalledTimes(1); // only initial + expect(mockFn2).toHaveBeenCalledTimes(2); // initial + change + expect(mockFn3).toHaveBeenCalledTimes(2); // initial + change + + unsubscribe3(); + state.value = 2; + + expect(mockFn1).toHaveBeenCalledTimes(1); // only initial + expect(mockFn2).toHaveBeenCalledTimes(3); // initial + 2 changes + expect(mockFn3).toHaveBeenCalledTimes(2); // initial + 1 change + }); +}); + +describe('themeVarsToCSS', () => { + it('should convert camelCase to kebab-case', () => { + const result = themeVarsToCSS({ buttonPrimaryColor: '#ff0000' }); + expect(result).toEqual({ '--td-button-primary-color': '#ff0000' }); + }); + + it('should handle keys with hyphens', () => { + const result = themeVarsToCSS({ 'button-primary-color': '#ff0000' }); + expect(result).toEqual({ '--td-button-primary-color': '#ff0000' }); + }); + + it('should preserve keys starting with --', () => { + const result = themeVarsToCSS({ '--custom-var': '#ff0000', '----custom-var': '#00ff00' }); + expect(result).toEqual({ '--custom-var': '#ff0000', '----custom-var': '#00ff00' }); + }); + + it('should handle mixed key formats', () => { + const result = themeVarsToCSS({ + buttonPrimaryColor: '#ff0000', + 'button-bg-color': '#00ff00', + '--custom-var': '#0000ff', + 'button-primary-bg-color-hover': '#ff0000', + }); + expect(result).toEqual({ + '--td-button-primary-color': '#ff0000', + '--td-button-bg-color': '#00ff00', + '--custom-var': '#0000ff', + '--td-button-primary-bg-color-hover': '#ff0000', + }); + }); + + it('should use custom prefix', () => { + const result = themeVarsToCSS({ buttonColor: '#ff0000' }, '--custom-'); + expect(result).toEqual({ '--custom-button-color': '#ff0000' }); + }); + + it('should handle empty object', () => { + const result = themeVarsToCSS({}); + expect(result).toEqual({}); + }); + + it('should convert all values to strings', () => { + const result = themeVarsToCSS({ + color: '#ff0000', + fontSize: 14, + lineHeight: 1.5, + opacity: 0.5, + zIndex: 100, + }); + expect(result).toEqual({ + '--td-color': '#ff0000', + '--td-font-size': '14', + '--td-line-height': '1.5', + '--td-opacity': '0.5', + '--td-z-index': '100', + }); + }); + + it('should preserve zero values', () => { + const result = themeVarsToCSS({ opacity: 0 }); + expect(result).toEqual({ '--td-opacity': '0' }); + }); +}); + +describe('useConfig', () => { + beforeEach(() => { + // 每个测试前重置 store + configStore.currentLocale.value = {}; + configStore.themeVars.value = {}; + }); + + describe('getComponentLocale', () => { + it('should return default locale when no config provided', () => { + const component = {}; + const defaultLocale = { cancel: '取消', confirm: '确定' }; + const result = getComponentLocale(component, 'picker', defaultLocale); + expect(result).toEqual(defaultLocale); + }); + + it('should merge global and component locale config', () => { + configStore.currentLocale.value = { + picker: { cancel: 'GlobalCancel', confirm: 'GlobalConfirm' }, + }; + const component = { + properties: { + locale: { cancel: 'ComponentCancel' }, + }, + }; + const defaultLocale = { cancel: '取消', confirm: '确定' }; + const result = getComponentLocale(component, 'picker', defaultLocale, 'locale'); + expect(result).toEqual({ + cancel: 'ComponentCancel', + confirm: 'GlobalConfirm', + }); + }); + + it('should handle component without properties', () => { + const component = {}; + const defaultLocale = { cancel: '取消' }; + const result = getComponentLocale(component, 'picker', defaultLocale, 'locale'); + expect(result).toEqual(defaultLocale); + }); + + it('should handle empty and null global locale', () => { + const component = {}; + const defaultLocale = { cancel: '取消', confirm: '确定' }; + + configStore.currentLocale.value = {}; + let result = getComponentLocale(component, 'picker', defaultLocale); + expect(result).toEqual(defaultLocale); + + configStore.currentLocale.value = null; + result = getComponentLocale(component, 'picker', defaultLocale); + expect(result).toEqual(defaultLocale); + }); + + it('should handle component locale not in global config', () => { + configStore.currentLocale.value = { + calendar: { title: '选择日期' }, + }; + const component = {}; + const defaultLocale = { cancel: '取消' }; + const result = getComponentLocale(component, 'picker', defaultLocale); + expect(result).toEqual(defaultLocale); + }); + + it('should merge all locale sources correctly', () => { + configStore.currentLocale.value = { + picker: { + cancel: 'GlobalCancel', + confirm: 'GlobalConfirm', + title: 'GlobalTitle', + }, + }; + const component = { + properties: { + locale: { + cancel: 'ComponentCancel', + confirm: 'ComponentConfirm', + }, + }, + }; + const defaultLocale = { + cancel: '默认取消', + confirm: '默认确认', + title: '默认标题', + placeholder: '默认占位符', + }; + const result = getComponentLocale(component, 'picker', defaultLocale, 'locale'); + expect(result).toEqual({ + cancel: 'ComponentCancel', + confirm: 'ComponentConfirm', + title: 'GlobalTitle', + placeholder: '默认占位符', + }); + }); + }); + + describe('useConfig hook', () => { + it('should get locale correctly', () => { + const hook = useConfig('picker'); + configStore.currentLocale.value = { + picker: { cancel: 'Cancel' }, + }; + const component = {}; + const defaultLocale = { cancel: '取消', confirm: '确定' }; + const result = hook.getLocale(defaultLocale, component); + expect(result.cancel).toBe('Cancel'); + expect(result.confirm).toBe('确定'); + }); + + it('should subscribe to locale changes', () => { + const hook = useConfig('picker'); + const component = {}; + const mockCallback = jest.fn(); + + hook.subscribeLocale(component, mockCallback); + + configStore.currentLocale.value = { + picker: { cancel: 'NewCancel' }, + }; + + expect(mockCallback).toHaveBeenCalled(); + }); + + it('should return unsubscribe function', () => { + const hook = useConfig('picker'); + const component = {}; + const mockCallback = jest.fn(); + + const unsubscribe = hook.subscribeLocale(component, mockCallback); + unsubscribe(); + + configStore.currentLocale.value = { + picker: { cancel: 'NewCancel' }, + }; + + // After unsubscribe, callback should still be called because it's initial call + expect(mockCallback).toHaveBeenCalled(); + }); + }); +}); diff --git a/packages/components/config-provider/_example/chat-en/index.js b/packages/components/config-provider/_example/chat-en/index.js new file mode 100644 index 000000000..481701146 --- /dev/null +++ b/packages/components/config-provider/_example/chat-en/index.js @@ -0,0 +1,115 @@ +import enUS from 'tdesign-miniprogram/locale/en_US'; + +Component({ + data: { + thinking: true, + fullText: + '嗯,用户问牛顿第一定律是不是适用于所有参考系。首先,我得先回忆一下牛顿第一定律的内容。牛顿第一定律,也就是惯性定律,说物体在没有外力作用时会保持静止或匀速直线运动。也就是说, 保持原来的运动状态。', + currentText: '', + isTyping: true, + content: { + text: '', + }, + typeSpeed: 50, + status: 'pending', + startTime: 0, + + // 全部配置 + globalConfig: enUS, + themeVars: { + buttonPrimaryBorderColor: '#ff6b00', + 'button-primary-color': '#ff6b00', + 'button-primary-bg-color': '#ff6a0094', + }, + }, + + lifetimes: { + attached() { + this.setData({ + startTime: Date.now(), + }); + this.startTyping(); + }, + + detached() { + if (this.typingTimer) { + clearTimeout(this.typingTimer); + } + }, + }, + + methods: { + startTyping() { + const { fullText, typeSpeed } = this.data; + let currentIndex = 0; + const typeNextChar = () => { + if (currentIndex <= fullText.length) { + const currentText = fullText.substring(0, currentIndex); + // 检查是否已经完成打字 + if (currentIndex === fullText.length) { + this.setData({ + currentText, + content: { + text: currentText, + }, + isTyping: false, + status: 'complete', + }); + return; // 直接返回,不再继续执行 + } + // 正常打字过程 + this.setData({ + currentText, + content: { + text: currentText, + }, + isTyping: currentIndex < fullText.length, + }); + if (currentIndex < fullText.length) { + this.typingTimer = setTimeout(typeNextChar, typeSpeed); + } + currentIndex += 1; + } + }; + typeNextChar(); + }, + replayTyping() { + if (this.typingTimer) { + clearTimeout(this.typingTimer); + } + this.setData({ + currentText: '', + content: { + text: '', + }, + isTyping: true, + startTime: Date.now(), + }); + this.startTyping(); + }, + onStop() { + console.log('停止思考'); + this.setData({ + thinking: false, + }); + wx.showToast({ + title: '已停止思考', + icon: 'success', + }); + }, + toggleThinking() { + this.setData({ + thinking: !this.data.thinking, + }); + }, + resetThinking() { + this.setData({ + thinking: true, + }); + wx.showToast({ + title: '已重置', + icon: 'success', + }); + }, + }, +}); diff --git a/packages/components/config-provider/_example/chat-en/index.json b/packages/components/config-provider/_example/chat-en/index.json new file mode 100644 index 000000000..e6dc1263a --- /dev/null +++ b/packages/components/config-provider/_example/chat-en/index.json @@ -0,0 +1,8 @@ +{ + "component": true, + "styleIsolation": "apply-shared", + "usingComponents": { + "t-config-provider": "tdesign-miniprogram/config-provider/config-provider", + "t-chat-thinking": "tdesign-miniprogram/chat-thinking/chat-thinking" + } +} diff --git a/packages/components/config-provider/_example/chat-en/index.wxml b/packages/components/config-provider/_example/chat-en/index.wxml new file mode 100644 index 000000000..405eb84cc --- /dev/null +++ b/packages/components/config-provider/_example/chat-en/index.wxml @@ -0,0 +1,11 @@ + + + + + diff --git a/packages/components/config-provider/_example/chat-en/index.wxss b/packages/components/config-provider/_example/chat-en/index.wxss new file mode 100644 index 000000000..700cf8b15 --- /dev/null +++ b/packages/components/config-provider/_example/chat-en/index.wxss @@ -0,0 +1,4 @@ +.chat-example-block { + background-color: var(--td-bg-color-container); + padding: 32rpx; +} diff --git a/packages/components/config-provider/_example/config-provider.json b/packages/components/config-provider/_example/config-provider.json new file mode 100644 index 000000000..176cbbce6 --- /dev/null +++ b/packages/components/config-provider/_example/config-provider.json @@ -0,0 +1,8 @@ +{ + "navigationBarTitleText": "ConfigProvider", + "usingComponents": { + "upload-en": "./upload-en", + "chat-en": "./chat-en", + "other-en": "./other-en" + } +} diff --git a/packages/components/config-provider/_example/config-provider.less b/packages/components/config-provider/_example/config-provider.less new file mode 100644 index 000000000..f7a7e91fd --- /dev/null +++ b/packages/components/config-provider/_example/config-provider.less @@ -0,0 +1,21 @@ +.upload-demo, +.chat-demo { + background-color: var(--td-bg-color-container); + padding: 32rpx; +} + +.rate-demo-cell { + background-color: var(--bg-color-demo); + color: var(--td-text-color-primary); + height: 96rpx; + display: flex; + align-items: center; + justify-content: space-between; + padding: 0 32rpx; + margin-top: 32rpx; + margin-bottom: 32rpx; + + &__title { + width: 200rpx; + } +} diff --git a/packages/components/config-provider/_example/config-provider.ts b/packages/components/config-provider/_example/config-provider.ts new file mode 100644 index 000000000..560d44d43 --- /dev/null +++ b/packages/components/config-provider/_example/config-provider.ts @@ -0,0 +1 @@ +Page({}); diff --git a/packages/components/config-provider/_example/config-provider.wxml b/packages/components/config-provider/_example/config-provider.wxml new file mode 100644 index 000000000..3aa0ca4df --- /dev/null +++ b/packages/components/config-provider/_example/config-provider.wxml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + diff --git a/packages/components/config-provider/_example/config-provider.wxs b/packages/components/config-provider/_example/config-provider.wxs new file mode 100644 index 000000000..ad5d5073e --- /dev/null +++ b/packages/components/config-provider/_example/config-provider.wxs @@ -0,0 +1,44 @@ +function getDateLabel(monthItem, dateItem) { + var weekdayText = ['日', '一', '二', '三', '四', '五', '六']; + var weekday = (monthItem.weekdayOfFirstDay + dateItem.day - 1) % 7; + var label = monthItem.month + 1 + '月' + dateItem.day + '日, 星期' + weekdayText[weekday]; + if (dateItem.type === 'start') { + label = '开始日期:' + label; + } + if (dateItem.type === 'end') { + label = '结束日期:' + label; + } + if (isDateSelected(dateItem)) { + label = '已选中, ' + label; + } + if (dateItem.prefix) { + label += ', ' + dateItem.prefix; + } + if (dateItem.suffix) { + label += ', ' + dateItem.suffix; + } + return label; +} + +function isDateSelected(dateItem) { + return ['start', 'end', 'selected', 'centre'].indexOf(dateItem.type) >= 0; +} + +function getMonthTitle(year, month, pattern = '') { + // prettier-ignore + var REGEXP = getRegExp('\{year\}|\{month\}', 'g'); + + return pattern.replace(REGEXP, function (match) { + var replacements = { + '{year}': year, + '{month}': month < 10 ? '0' + month : month, + }; + return replacements[match] || match; + }); +} + +module.exports = { + getDateLabel: getDateLabel, + isDateSelected: isDateSelected, + getMonthTitle: getMonthTitle, +}; diff --git a/packages/components/config-provider/_example/other-en/index.js b/packages/components/config-provider/_example/other-en/index.js new file mode 100644 index 000000000..cd1f4156e --- /dev/null +++ b/packages/components/config-provider/_example/other-en/index.js @@ -0,0 +1,74 @@ +Component({ + data: { + // rate + rateValue: 3, + + // calendar + visible: false, + value: null, + + // date-time-picker + mode: '', + dateVisible: false, + date: new Date('2021-12-23').getTime(), // 支持时间戳传入 + dateText: '', + popupProps: { + usingCustomNavbar: true, + }, + }, + methods: { + // rate + onRateChange(e) { + const { value } = e.detail; + this.setData({ + rateValue: value, + }); + }, + + // calendar + handleCalendar() { + this.setData({ visible: true }); + }, + + handleCalendarConfirm(e) { + const { value } = e.detail; + console.log(value); + this.setData({ + value, + }); + }, + + onCalendarClose({ detail }) { + console.log(detail.trigger); + }, + + // date-time-picker + showPicker(e) { + const { mode } = e.currentTarget.dataset; + this.setData({ + mode, + [`${mode}Visible`]: true, + }); + }, + + handlePickerClose(e) { + console.log('handleClose:', e); + }, + + onPickerConfirm(e) { + const { value } = e.detail; + const { mode } = this.data; + + console.log('confirm', value); + + this.setData({ + [mode]: value, + [`${mode}Text`]: value, + }); + }, + + onPickerColumnChange(e) { + console.log('pick', e.detail.value); + }, + }, +}); diff --git a/packages/components/config-provider/_example/other-en/index.json b/packages/components/config-provider/_example/other-en/index.json new file mode 100644 index 000000000..2f53897ef --- /dev/null +++ b/packages/components/config-provider/_example/other-en/index.json @@ -0,0 +1,11 @@ +{ + "component": true, + "styleIsolation": "apply-shared", + "usingComponents": { + "t-config-provider": "tdesign-miniprogram/config-provider/config-provider", + "t-calendar": "tdesign-miniprogram/calendar/calendar", + "t-cell": "tdesign-miniprogram/cell/cell", + "t-date-time-picker": "tdesign-miniprogram/date-time-picker/date-time-picker", + "t-rate": "tdesign-miniprogram/rate/rate" + } +} diff --git a/packages/components/config-provider/_example/other-en/index.wxml b/packages/components/config-provider/_example/other-en/index.wxml new file mode 100644 index 000000000..179524e23 --- /dev/null +++ b/packages/components/config-provider/_example/other-en/index.wxml @@ -0,0 +1,47 @@ + + + + + + Rating + + + + + + + + + + + + + diff --git a/packages/components/config-provider/_example/other-en/index.wxss b/packages/components/config-provider/_example/other-en/index.wxss new file mode 100644 index 000000000..e69de29bb diff --git a/packages/components/config-provider/_example/upload-en/index.js b/packages/components/config-provider/_example/upload-en/index.js new file mode 100644 index 000000000..03bfac513 --- /dev/null +++ b/packages/components/config-provider/_example/upload-en/index.js @@ -0,0 +1,55 @@ +import enUS from 'tdesign-miniprogram/locale/en_US'; + +Component({ + data: { + globalConfig: enUS, + originFiles: [ + { + url: 'https://tdesign.gtimg.com/mobile/demos/example4.png', + name: 'uploaded1.png', + type: 'image', + status: 'loading', + }, + { + url: 'https://tdesign.gtimg.com/mobile/demos/example5.png', + name: 'uploaded2.png', + type: 'image', + percent: 68, + status: 'loading', + }, + { + url: 'https://tdesign.gtimg.com/mobile/demos/example5.png', + name: 'uploaded4.png', + type: 'image', + status: 'failed', + }, + ], + gridConfig: { + column: 4, + width: 160, + height: 160, + }, + config: { + count: 1, + }, + }, + methods: { + handleSuccess(e) { + const { files } = e.detail; + this.setData({ + originFiles: files, + }); + }, + handleRemove(e) { + const { index } = e.detail; + const { originFiles } = this.data; + originFiles.splice(index, 1); + this.setData({ + originFiles, + }); + }, + handleClick(e) { + console.log(e.detail.file); + }, + }, +}); diff --git a/packages/components/config-provider/_example/upload-en/index.json b/packages/components/config-provider/_example/upload-en/index.json new file mode 100644 index 000000000..327c35180 --- /dev/null +++ b/packages/components/config-provider/_example/upload-en/index.json @@ -0,0 +1,8 @@ +{ + "component": true, + "styleIsolation": "apply-shared", + "usingComponents": { + "t-config-provider": "tdesign-miniprogram/config-provider/config-provider", + "t-upload": "tdesign-miniprogram/upload/upload" + } +} diff --git a/packages/components/config-provider/_example/upload-en/index.wxml b/packages/components/config-provider/_example/upload-en/index.wxml new file mode 100644 index 000000000..c66fbab24 --- /dev/null +++ b/packages/components/config-provider/_example/upload-en/index.wxml @@ -0,0 +1,13 @@ + + + + + diff --git a/packages/components/config-provider/_example/upload-en/index.wxss b/packages/components/config-provider/_example/upload-en/index.wxss new file mode 100644 index 000000000..e69de29bb diff --git a/packages/components/config-provider/config-provider.json b/packages/components/config-provider/config-provider.json new file mode 100644 index 000000000..79f273097 --- /dev/null +++ b/packages/components/config-provider/config-provider.json @@ -0,0 +1,4 @@ +{ + "component": true, + "styleIsolation": "apply-shared" +} diff --git a/packages/components/config-provider/config-provider.less b/packages/components/config-provider/config-provider.less new file mode 100644 index 000000000..e69de29bb diff --git a/packages/components/config-provider/config-provider.ts b/packages/components/config-provider/config-provider.ts new file mode 100644 index 000000000..f923e7d92 --- /dev/null +++ b/packages/components/config-provider/config-provider.ts @@ -0,0 +1,101 @@ +import { SuperComponent, wxComponent } from '../common/src/index'; +import config from '../common/config'; +import props from './props'; +import { TdConfigProviderProps } from './type'; +import { configStore } from './config-store'; +import themeVarsToCSS from './utils'; + +const { prefix } = config; +const componentName = 'config-provider'; + +export interface ConfigProviderProps extends TdConfigProviderProps {} + +@wxComponent() +export default class ConfigProvider extends SuperComponent { + options = { + multipleSlots: true, + }; + + externalClasses = [`${prefix}-class`]; + + properties = props; + + data = { + prefix, + classPrefix: `${prefix}-${componentName}`, + cssVars: {}, + }; + + // 存储取消订阅的函数 + _unsubscribeLocale?: () => void; + + // 组件唯一标识符 + _componentId?: string; + + observers = { + 'themeVars, globalConfig'() { + this.updateConfig(); + }, + }; + + lifetimes = { + attached() { + // 生成组件唯一标识符 + this._componentId = `${Date.now()}-${Math.random().toString(36).slice(2)}`; + + this.initStore(); + this.updateConfig(); + }, + + detached() { + // 组件销毁时取消订阅 + if (this._unsubscribeLocale) { + this._unsubscribeLocale(); + } + // 重置页面状态 + if (this._componentId) { + configStore.resetPageState(this._componentId); + } + }, + }; + + methods = { + /** + * 初始化 Store 并订阅状态变化 + */ + initStore() { + // 订阅语言包变化 + this._unsubscribeLocale = configStore.currentLocale.subscribe(() => {}); + }, + + /** + * 更新配置 + */ + updateConfig() { + const { themeVars, globalConfig } = this.properties; + + // 切换语言包 + if (globalConfig) { + configStore.switchLocale(globalConfig, this._componentId); + } + + // 更新主题变量 + if (themeVars) { + configStore.updateThemeVars(themeVars); + } + + // 应用主题变量 + this.applyTheme(); + }, + + applyTheme() { + const { themeVars } = this.properties; + + const cssVars = themeVarsToCSS(themeVars || {}); + + this.setData({ + cssVars, + }); + }, + }; +} diff --git a/packages/components/config-provider/config-provider.wxml b/packages/components/config-provider/config-provider.wxml new file mode 100644 index 000000000..fd1539b90 --- /dev/null +++ b/packages/components/config-provider/config-provider.wxml @@ -0,0 +1,5 @@ + + + + + diff --git a/packages/components/config-provider/config-store.ts b/packages/components/config-provider/config-store.ts new file mode 100644 index 000000000..cc1b31d08 --- /dev/null +++ b/packages/components/config-provider/config-store.ts @@ -0,0 +1,139 @@ +/** + * 全局配置 Store + * 提供响应式的语言包状态管理 + */ + +import ReactiveState from './reactive-state'; + +export type Locale = Record; + +/** + * 全局配置 Store 单例 + */ +class ConfigStore { + // 当前语言包 + currentLocale = new ReactiveState({}); + + // 主题变量 + themeVars = new ReactiveState>({}); + + // 使用 Map 存储页面级别状态标记 + private _pageInitFlags = new Map(); + + // 存储组件销毁时的清理函数 + private _cleanupCallbacks = new Map void>(); + + /** + * 深度比较两个对象是否相等 + */ + private _deepEqual(a: any, b: any): boolean { + // 快速引用比较 + if (a === b) return true; + + // 类型检查 + if (typeof a !== typeof b) return false; + + // null/undefined 检查 + if (a == null || b == null) return a === b; + + // 基本类型比较 + if (typeof a !== 'object') return false; + + // 快速属性数量检查 + const keysA = Object.keys(a); + const keysB = Object.keys(b); + if (keysA.length !== keysB.length) return false; + + // 对象比较:先尝试 JSON 快速比较(仅在属性数量相同时) + try { + const jsonA = JSON.stringify(a); + const jsonB = JSON.stringify(b); + if (jsonA === jsonB) return true; + } catch (e) { + // 序列化失败(如循环引用),回退到深度遍历 + } + + // 深度遍历比较 + return keysA.every((key) => this._deepEqual(a[key], b[key])); + } + + /** + * 切换语言包 + * @param locale 语言包 + * @param componentId 组件唯一标识符 + */ + switchLocale(locale: Locale, componentId?: string): void { + if (!componentId) return; + + const pageInitFlag = this._getOrInitPageFlag(componentId); + + // 首次设置时标记为已初始化 + if (!pageInitFlag.locale) { + pageInitFlag.locale = true; + this.currentLocale.value = locale; + } else { + // 已初始化后,允许动态切换语言(检查内容是否真的变化) + // 先进行快速非空检查,避免空对象比较 + const isEmptyLocale = !locale || Object.keys(locale).length === 0; + const isCurrentEmpty = Object.keys(this.currentLocale.value).length === 0; + + // 如果一个是空的另一个不是,或者深度比较不相等,则更新 + if (isEmptyLocale !== isCurrentEmpty || !this._deepEqual(locale, this.currentLocale.value)) { + this.currentLocale.value = locale; + } + } + } + + /** + * 更新主题变量 + * @param vars 主题变量对象 + */ + updateThemeVars(vars: Record): void { + this.themeVars.value = { ...this.themeVars.value, ...vars }; + } + + /** + * 获取或初始化页面级别状态标记 + * @param componentId 组件唯一标识符 + */ + private _getOrInitPageFlag(componentId: string): { theme: boolean; locale: boolean } { + if (!this._pageInitFlags.has(componentId)) { + this._pageInitFlags.set(componentId, { theme: false, locale: false }); + } + + return this._pageInitFlags.get(componentId)!; + } + + /** + * 注册组件销毁时的清理回调 + * @param componentId 组件唯一标识符 + * @param cleanup 清理函数 + */ + registerCleanup(componentId: string, cleanup: () => void): void { + this._cleanupCallbacks.set(componentId, cleanup); + } + + /** + * 重置页面状态 + * @param componentId 组件唯一标识符 + */ + resetPageState(componentId?: string): void { + if (componentId) { + this._pageInitFlags.delete(componentId); + + // 执行清理回调 + const cleanup = this._cleanupCallbacks.get(componentId); + if (cleanup) { + try { + cleanup(); + } catch (e) { + console.error(`[ConfigStore] Error during cleanup for ${componentId}:`, e); + } + this._cleanupCallbacks.delete(componentId); + } + } + } +} + +// 导出单例 +export const configStore = new ConfigStore(); diff --git a/packages/components/config-provider/props.ts b/packages/components/config-provider/props.ts new file mode 100644 index 000000000..979d2fcc4 --- /dev/null +++ b/packages/components/config-provider/props.ts @@ -0,0 +1,19 @@ +/* eslint-disable */ + +/** + * 该文件为脚本自动生成文件,请勿随意修改。如需修改请联系 PMC + * */ + +import { TdConfigProviderProps } from './type'; +const props: TdConfigProviderProps = { + /** 全局配置 */ + globalConfig: { + type: Object, + }, + /** 全局配置 */ + themeVars: { + type: Object, + }, +}; + +export default props; diff --git a/packages/components/config-provider/reactive-state.ts b/packages/components/config-provider/reactive-state.ts new file mode 100644 index 000000000..798f148c6 --- /dev/null +++ b/packages/components/config-provider/reactive-state.ts @@ -0,0 +1,50 @@ +/** + * 响应式状态基类 + * 使用简易的发布-订阅模式实现响应式更新 + */ +export default class ReactiveState { + private _value: T; + + private _listeners: Set<(value: T) => void> = new Set(); + + constructor(initialValue: T) { + this._value = initialValue; + } + + get value(): T { + return this._value; + } + + set value(newValue: T) { + if (this._value !== newValue) { + this._value = newValue; + this._notify(); + } + } + + /** + * 订阅状态变化 + * @param listener 状态变化回调 + * @returns 取消订阅的函数 + */ + subscribe(listener: (value: T) => void): () => void { + this._listeners.add(listener); + + // 立即执行一次,确保初始值被消费 + listener(this._value); + + return () => { + this._listeners.delete(listener); + }; + } + + private _notify(): void { + this._listeners.forEach((listener) => { + try { + listener(this._value); + } catch (error) { + console.error('State listener error:', error); + } + }); + } +} diff --git a/packages/components/config-provider/type.ts b/packages/components/config-provider/type.ts new file mode 100644 index 000000000..ca93b9968 --- /dev/null +++ b/packages/components/config-provider/type.ts @@ -0,0 +1,384 @@ +/* eslint-disable */ + +/** + * 该文件为脚本自动生成文件,请勿随意修改。如需修改请联系 PMC + * */ + +import { ImageProps } from '../image/index'; + +export interface TdConfigProviderProps { + /** + * 全局配置 + */ + globalConfig?: { + type: ObjectConstructor; + value?: GlobalConfigProvider; + }; + /** + * 全局配置 + */ + themeVars?: { + type: ObjectConstructor; + value?: object; + }; +} + +export interface GlobalConfigProvider { + /** + * 动作面板全局配置 + */ + actionSheet?: ActionSheetConfig; + /** + * 日历组件全局配置 + */ + calendar?: CalendarConfig; + /** + * 级联选择器全局配置 + */ + cascader?: CascaderConfig; + /** + * CSS 类名前缀 + * @default t + */ + classPrefix?: string; + /** + * 时间选择器全局配置 + */ + dateTimePicker?: DateTimePickerConfig; + /** + * 下拉菜单全局配置 + */ + dropdownMenu?: DropdownMenuConfig; + /** + * 引导全局配置 + */ + guide?: GuideConfig; + /** + * 选择器全局配置 + */ + picker?: PickerConfig; + /** + * 下拉刷新全局配置 + */ + pullDownRefresh?: PullDownRefreshConfig; + /** + * 二维码全局配置 + */ + qrcode?: QRCodeConfig; + /** + * 评分全局配置 + */ + rate?: RateConfig; + /** + * 标签栏全局配置 + */ + tabBar?: TabBarConfig; + /** + * 上传组件全局配置 + */ + upload?: UploadConfig; +} + +export interface ActionSheetConfig { + /** + * 语言配置,“取消” 按钮描述文本 + * @default '' + */ + cancel?: string; +} + +export interface AttachmentsConfig { + /** + * 语言配置,附件上传状态描述文本 + */ + status?: { pending: string; fail: string }; +} + +export interface CalendarConfig { + /** + * 语言配置,“确定” 按钮描述文本 + * @default '' + */ + confirm?: string; + /** + * 语言配置,日期月面板标题描述文本。示例:“{year} / {month}” + * @default '' + */ + monthTitle?: string; + /** + * 月文本描述,默认值:['1 月', '2 月', '3 月', '4 月', '5 月', '6 月', '7 月', '8 月', '9 月', '10 月', '11 月', '12 月'] + */ + months?: string[]; + /** + * 语言配置,组件标题“请选择日期”描述文本 + * @default '' + */ + title?: string; + /** + * 星期文本描述,默认值:['日', '一', '二', '三', '四', '五', '六'] + */ + weekdays?: string[]; +} + +export interface CascaderConfig { + /** + * 语言配置,未选中时的提示文案“选择选项”描述文本 + * @default '' + */ + placeholder?: string; + /** + * 语言配置,组件标题“选择地址”描述文本 + * @default '' + */ + title?: string; +} + +export interface ChatActionbarConfig { + /** + * 语言配置,对话操作栏描述文本 + */ + actionBar?: { replay: string; copy: string; good: string; bad: string; share: string; quote: string }; +} + +export interface ChatSenderConfig { + /** + * 语言配置,占位符描述文本 + * @default '' + */ + placeholder?: string; + /** + * 语言配置,发送按钮描述文本 + * @default '' + */ + sendText?: string; + /** + * 语言配置,停止发送按钮描述文本 + * @default '' + */ + stopText?: string; +} + +export interface ChatThinkingConfig { + /** + * 语言配置,思考状态描述文本 + */ + status?: { pending: string; complete: string; stop: string }; +} + +export interface DateTimePickerConfig { + /** + * 语言配置,“取消”按钮描述文本 + * @default '' + */ + cancel?: string; + /** + * 语言配置,“确定”按钮描述文本 + * @default '' + */ + confirm?: string; + /** + * 语言配置,“日” 描述文本 + * @default '' + */ + dateLabel?: string; + /** + * 日期格式化规则 + * @default 'YYYY-MM-DD HH:mm:ss' + */ + format?: string; + /** + * 语言配置,“时” 描述文本 + * @default '' + */ + hourLabel?: string; + /** + * 语言配置,“分” 描述文本 + * @default '' + */ + minuteLabel?: string; + /** + * 语言配置,“月” 描述文本 + * @default '' + */ + monthLabel?: string; + /** + * 语言配置,“秒” 描述文本 + * @default '' + */ + secondLabel?: string; + /** + * 语言配置,组件标题“选择时间”描述文本 + * @default '' + */ + title?: string; + /** + * 语言配置,“年” 描述文本 + * @default '' + */ + yearLabel?: string; +} + +export interface DropdownMenuConfig { + /** + * 语言配置,“确定” 按钮描述文本 + * @default '' + */ + confirm?: string; + /** + * 语言配置,“重置” 按钮描述文本 + * @default '' + */ + reset?: string; +} + +export interface GuideConfig { + /** + * 语言配置, “返回” 描述文本 + * @default '' + */ + back?: string; + /** + * 语言配置, “完成” 描述文本 + * @default '' + */ + finish?: string; + /** + * 语言配置, “下一步” 描述文本 + * @default '' + */ + next?: string; + /** + * 语言配置, “跳过” 描述文本 + * @default '' + */ + skip?: string; +} + +export interface ImageConfig { + /** + * 图片加载失败显示的文本,中文默认为“图片无法显示” + * @default '' + */ + errorText?: string; + /** + * 图片加载中显示的文本,中文默认为“图片加载中” + * @default '' + */ + loadingText?: string; + /** + * 统一替换图片 `src` 地址,参数为组件的全部属性,返回值为新的图片地址 + */ + replaceImageSrc?: (params: ImageProps) => string; +} + +export interface InputConfig { + /** + * 语言配置,“请输入”占位符描述文本 + * @default '' + */ + placeholder?: string; +} + +export interface PickerConfig { + /** + * 语言配置,“取消” 按钮描述文本 + * @default '' + */ + cancel?: string; + /** + * 语言配置,“确认” 按钮描述文本 + * @default '' + */ + confirm?: string; +} + +export interface PullDownRefreshConfig { + /** + * 提示文本描述,默认值:['下拉刷新', '松手刷新', '正在刷新', '刷新完成'] + */ + loadingTexts?: string[]; +} + +export interface QRCodeConfig { + /** + * 语言配置,“二维码过期”描述文本 + * @default '' + */ + expiredText?: string; + /** + * 语言配置,“点击刷新”描述文本 + * @default '' + */ + refreshText?: string; + /** + * 语言配置,“已扫描”描述文本 + * @default '' + */ + scannedText?: string; +} + +export interface RateConfig { + /** + * 语言配置,“未评分”描述文本 + * @default '' + */ + noValueText?: string; + /** + * 语言配置,评分值描述文本。示例:“{value} 分” + * @default '' + */ + valueText?: string; +} + +export interface TabBarConfig { + /** + * 语言配置,“有n+条新的消息”描述文本。示例:“有 {value}+ 条消息” + * @default '' + */ + haveMoreNewsAriaLabel?: string; + /** + * 语言配置,“有n条新的消息”描述文本。示例:“有 {value} 条消息” + * @default '' + */ + haveNewsAriaLabel?: string; + /** + * 语言配置,“有很多消息”描述文本 + * @default '' + */ + moreNewsAriaLabel?: string; + /** + * 语言配置,“有新的消息”描述文本 + * @default '' + */ + newsAriaLabel?: string; +} + +export interface UploadConfig { + /** + * 语言配置,上传进度相关。示例:{ uploadText: '上传中', waitingText: '待上传', 'failText': '上传失败', successText: '上传成功' } + */ + progress?: UploadConfigProgress; +} + +export interface UploadConfigProgress { + /** + * 语言配置,“上传失败”文本描述 + * @default '' + */ + failText?: string; + /** + * 语言配置,“上传成功”文本描述 + * @default '' + */ + successText?: string; + /** + * 语言配置,“上传中”文本描述 + * @default '' + */ + uploadingText?: string; + /** + * 语言配置,“待上传”文本描述 + * @default '' + */ + waitingText?: string; +} diff --git a/packages/components/config-provider/use-config.ts b/packages/components/config-provider/use-config.ts new file mode 100644 index 000000000..c012fbf92 --- /dev/null +++ b/packages/components/config-provider/use-config.ts @@ -0,0 +1,71 @@ +/** + * 获取配置的工具函数 + * 用于子组件从 config-provider 获取全局配置 + */ + +import { configStore } from './config-store'; + +/** + * 获取组件的 locale 配置 + * @param component 组件实例 + * @param componentName 以组件名作为 locale 中的 key(如 'picker'、'calendar' 等) + * @param defaultLocale 默认语言包 + * @param localePropName 组件自定义语言文本的属性名 + * @returns 合并后的语言包 + */ +export function getComponentLocale>( + component: any, + componentName: string, + defaultLocale: T, + localePropName?: string, +): T { + let componentLocale = {}; + if (localePropName) { + componentLocale = component.properties?.[localePropName] || {}; + } + + // 从全局 store 获取 config-provider 的 locale,然后得到当前组件的语言包 + const globalLocaleConfig = configStore.currentLocale.value; + const globalLocale = (globalLocaleConfig && globalLocaleConfig[componentName]) || {}; + + // 合并顺序:组件传入 > config-provider > 默认值 + return { + ...defaultLocale, + ...globalLocale, + ...componentLocale, + }; +} + +/** + * 获取全局配置的 hook + * 返回一个包含 locale 订阅和取消订阅的对象 + * @param componentName 以组件名作为 locale 中的 key + */ +export function useConfig(componentName: string) { + return { + /** + * 获取当前的 locale + * @param defaultLocale 默认语言包 + * @param component 组件实例 + * @returns 合并后的语言包 + */ + getLocale>(defaultLocale: T, component: any): T { + return getComponentLocale(component, componentName, defaultLocale); + }, + + /** + * 订阅 locale 变化 + * @param component 组件实例 + * @param callback locale 变化时的回调 + * @returns 取消订阅的函数 + */ + subscribeLocale(component: any, callback: (locale: Record) => void): () => void { + const unsubscribe = configStore.currentLocale.subscribe((locale) => { + // 调用回调 + callback(locale); + }); + + return unsubscribe; + }, + }; +} diff --git a/packages/components/config-provider/utils.ts b/packages/components/config-provider/utils.ts new file mode 100644 index 000000000..9547cfa7c --- /dev/null +++ b/packages/components/config-provider/utils.ts @@ -0,0 +1,33 @@ +import { toKebabCase } from '../common/utils'; + +/** + * 将主题变量对象转换为 CSS 变量对象 + * @param themeVars 主题变量对象 + * @param prefix CSS 变量前缀 + * @returns CSS 变量对象 + */ +export default function themeVarsToCSS( + themeVars: Record, + prefix: string = '--td-', +): Record { + const cssVarMap: Record = {}; + + Object.keys(themeVars).forEach((key) => { + let cssVarName: string; + + // key 以 -- 开头,认为是标准的 css vars,直接使用 + if (key.startsWith('--')) { + cssVarName = key; + } + // 如果 key 包含短横线,认为是 kebab-case 格式,直接使用 + else if (key.includes('-')) { + cssVarName = `${prefix}${key}`; + } else { + cssVarName = `${prefix}${toKebabCase(key)}`; + } + + cssVarMap[cssVarName] = String(themeVars[key]); + }); + + return cssVarMap; +} diff --git a/packages/components/count-down/README.md b/packages/components/count-down/README.md index 8c3cc2f82..dc29f4758 100644 --- a/packages/components/count-down/README.md +++ b/packages/components/count-down/README.md @@ -9,7 +9,7 @@ isComponent: true > CountDown 组件用于实时展示倒计时数值。 如果需要与站点演示一致的数字字体效果,推荐您到 数字字体章节,将 TCloudNumber 字体下载并将包含的 TCloudNumberVF.ttf 做为 TCloudNumber 字体资源引入到具体项目中使用。 - + ## 引入 全局引入,在 miniprogram 根目录下的`app.json`中配置,局部引入,在需要引入的页面或组件的`index.json`中配置。 diff --git a/packages/components/date-time-picker/README.en-US.md b/packages/components/date-time-picker/README.en-US.md index a90f30fd0..687d27658 100644 --- a/packages/components/date-time-picker/README.en-US.md +++ b/packages/components/date-time-picker/README.en-US.md @@ -11,21 +11,21 @@ custom-style | Object | - | CSS(Cascading Style Sheets),used to set style on v auto-close | Boolean | false | \- | N cancel-btn | String | 取消 | \- | N confirm-btn | String | - | \- | N -custom-locale | String | zh | \- | N +custom-locale | String | zh | `deprecated` | N end | String / Number | - | \- | N -filter | Function | - | Typescript:`(type: TimeModeValues, columns: DateTimePickerColumn) => DateTimePickerColumn` `type DateTimePickerColumn = DateTimePickerColumnItem[]` `interface DateTimePickerColumnItem { label: string,value: string}`。[see more ts definition](https://github.com/Tencent/tdesign-miniprogram/blob/develop/packages/components/date-time-picker/type.ts) | N +filter | Function | - | Typescript: `(type: TimeModeValues, columns: DateTimePickerColumn) => DateTimePickerColumn` `type DateTimePickerColumn = DateTimePickerColumnItem[]` `interface DateTimePickerColumnItem { label: string,value: string}`。[see more ts definition](https://github.com/Tencent/tdesign-miniprogram/blob/develop/packages/components/date-time-picker/type.ts) | N format | String | 'YYYY-MM-DD HH:mm:ss' | \- | N -formatter | Function | - | Typescript:`(option: DateTimePickerColumnItem, columnIndex: number) => DateTimePickerColumnItem` | N +formatter | Function | - | Typescript: `(option: DateTimePickerColumnItem, columnIndex: number) => DateTimePickerColumnItem` | N header | Boolean | true | \- | N -mode | String / Array | 'date' | Typescript:`DateTimePickerMode` `type DateTimePickerMode = TimeModeValues \| Array ` `type TimeModeValues = 'year' \| 'month' \| 'date' \| 'hour' \| 'minute' \| 'second'`。[see more ts definition](https://github.com/Tencent/tdesign-miniprogram/blob/develop/packages/components/date-time-picker/type.ts) | N -popup-props | Object | {} | popup properties。Typescript:`PopupProps`,[Popup API Documents](./popup?tab=api)。[see more ts definition](https://github.com/Tencent/tdesign-miniprogram/blob/develop/packages/components/date-time-picker/type.ts) | N +mode | String / Array | 'date' | Typescript: `DateTimePickerMode` `type DateTimePickerMode = TimeModeValues \| Array ` `type TimeModeValues = 'year' \| 'month' \| 'date' \| 'hour' \| 'minute' \| 'second'`。[see more ts definition](https://github.com/Tencent/tdesign-miniprogram/blob/develop/packages/components/date-time-picker/type.ts) | N +popup-props | Object | {} | popup properties。Typescript: `PopupProps`,[Popup API Documents](./popup?tab=api)。[see more ts definition](https://github.com/Tencent/tdesign-miniprogram/blob/develop/packages/components/date-time-picker/type.ts) | N show-week | Boolean | false | `1.9.0` | N start | String / Number | - | \- | N -steps | Object | {} | Typescript:`{ [key in TimeModeValues]?: number }` | N +steps | Object | {} | Typescript: `{ [key in TimeModeValues]?: number }` | N title | String | - | title of picker | N use-popup | Boolean | true | \- | N -value | String / Number | - | Typescript:`DateValue` `type DateValue = string \| number`。[see more ts definition](https://github.com/Tencent/tdesign-miniprogram/blob/develop/packages/components/date-time-picker/type.ts) | N -default-value | String / Number | undefined | uncontrolled property。Typescript:`DateValue` `type DateValue = string \| number`。[see more ts definition](https://github.com/Tencent/tdesign-miniprogram/blob/develop/packages/components/date-time-picker/type.ts) | N +value | String / Number | - | Typescript: `DateValue` `type DateValue = string \| number`。[see more ts definition](https://github.com/Tencent/tdesign-miniprogram/blob/develop/packages/components/date-time-picker/type.ts) | N +default-value | String / Number | undefined | uncontrolled property。Typescript: `DateValue` `type DateValue = string \| number`。[see more ts definition](https://github.com/Tencent/tdesign-miniprogram/blob/develop/packages/components/date-time-picker/type.ts) | N visible | Boolean | false | \- | N ### DateTimePicker Events diff --git a/packages/components/date-time-picker/README.md b/packages/components/date-time-picker/README.md index c64abf34c..efdf99e72 100644 --- a/packages/components/date-time-picker/README.md +++ b/packages/components/date-time-picker/README.md @@ -5,7 +5,7 @@ spline: form isComponent: true --- - + ## 引入 全局引入,在 miniprogram 根目录下的`app.json`中配置,局部引入,在需要引入的页面或组件的`index.json`中配置。 @@ -65,7 +65,7 @@ custom-style | Object | - | 样式,一般用于开启虚拟化组件节点场 auto-close | Boolean | false | 自动关闭;在确认、取消、点击遮罩层自动关闭,不需要手动设置 visible | N cancel-btn | String | 取消 | 取消按钮文字 | N confirm-btn | String | - | 确定按钮文字 | N -custom-locale | String | zh | 组件国际化语言,目前支持: 简体中文(zh)、(tc)、英文(en)、日语(ja)、韩语(ko)、俄语(ru)等六种语言 | N +custom-locale | String | zh | 已废弃。组件国际化语言,目前支持: 简体中文(zh)、(tc)、英文(en)、日语(ja)、韩语(ko)、俄语(ru)等六种。请使用 `ConfigProvider` 实现多语言 | N end | String / Number | - | 选择器的最大可选时间,默认为当前时间+10年 | N filter | Function | - | 列选项过滤函数,支持自定义列内容。(type 值可为: year, month, date, hour, minute, second)。TS 类型:`(type: TimeModeValues, columns: DateTimePickerColumn) => DateTimePickerColumn` `type DateTimePickerColumn = DateTimePickerColumnItem[]` `interface DateTimePickerColumnItem { label: string,value: string}`。[详细类型定义](https://github.com/Tencent/tdesign-miniprogram/blob/develop/packages/components/date-time-picker/type.ts) | N format | String | 'YYYY-MM-DD HH:mm:ss' | 用于格式化 pick、change、confirm 事件返回的值,[详细文档](https://day.js.org/docs/en/display/format) | N diff --git a/packages/components/date-time-picker/__test__/__snapshots__/index.test.js.snap b/packages/components/date-time-picker/__test__/__snapshots__/index.test.js.snap index e62f7df48..0e47361b6 100644 --- a/packages/components/date-time-picker/__test__/__snapshots__/index.test.js.snap +++ b/packages/components/date-time-picker/__test__/__snapshots__/index.test.js.snap @@ -49,7 +49,7 @@ exports[`date-time-picker :base 1`] = ` class="t-picker__confirm t-class-confirm" bind:tap="onConfirm" > - 确定 + 确认 - 1月 + 1 月 - 2月 + 2 月 - 3月 + 3 月 - 4月 + 4 月 - 5月 + 5 月 - 6月 + 6 月 - 7月 + 7 月 - 8月 + 8 月 - 9月 + 9 月 - 10月 + 10 月 - 11月 + 11 月 - 12月 + 12 月 diff --git a/packages/components/date-time-picker/date-time-picker.ts b/packages/components/date-time-picker/date-time-picker.ts index f92f97809..e86e9c5fe 100644 --- a/packages/components/date-time-picker/date-time-picker.ts +++ b/packages/components/date-time-picker/date-time-picker.ts @@ -1,10 +1,9 @@ import type { Dayjs } from 'dayjs'; - import config from '../common/config'; import { SuperComponent, wxComponent } from '../common/src/index'; +import usingConfig from '../mixins/using-config'; import props from './props'; -import dayjsLocaleMap from './locale/dayjs'; const dayjs = require('dayjs'); const localeData = require('dayjs/plugin/localeData'); @@ -15,11 +14,8 @@ const localeData = require('dayjs/plugin/localeData'); dayjs.extend(localeData); dayjs.locale('zh-cn'); -// const defaultLocale = dayjsLocaleMap.default.key; -const defaultLocale = dayjsLocaleMap[dayjs.locale()]?.key || dayjsLocaleMap.default?.key; - const { prefix } = config; -const name = `${prefix}-date-time-picker`; +const componentName = 'date-time-picker'; enum ModeItem { YEAR = 'year', @@ -41,6 +37,8 @@ interface ColumnItemValue { @wxComponent() export default class DateTimePicker extends SuperComponent { + behaviors = [usingConfig({ componentName })]; + properties = props; externalClasses = [`${prefix}-class`, `${prefix}-class-confirm`, `${prefix}-class-cancel`, `${prefix}-class-title`]; @@ -50,18 +48,10 @@ export default class DateTimePicker extends SuperComponent { }; observers = { - 'start, end, value': function () { + 'start, end, value, globalConfig'() { this.updateColumns(); }, - customLocale(v) { - if (!v || !dayjsLocaleMap[v].key) return; - this.setData({ - locale: dayjsLocaleMap[v].i18n, - dayjsLocale: dayjsLocaleMap[v].key, - }); - }, - mode(m) { const fullModes = this.getFullModeArray(m); this.setData({ @@ -75,12 +65,10 @@ export default class DateTimePicker extends SuperComponent { data = { prefix, - classPrefix: name, + classPrefix: `${prefix}-${componentName}`, columns: [], columnsValue: [], fullModes: [], - locale: dayjsLocaleMap[defaultLocale].i18n, // 国际化语言包 - dayjsLocale: dayjsLocaleMap[defaultLocale].key, // dayjs 自适应的 key }; controlledProps = [ @@ -102,19 +90,20 @@ export default class DateTimePicker extends SuperComponent { }, getDaysOfWeekInMonth(date: Dayjs, type: string): Array<{ value: string; label: string }> { - const { locale, steps, dayjsLocale } = this.data; + const { globalConfig, steps } = this.data; const startOfMonth = date.startOf('month'); const minEdge = this.getOptionEdge('min', type); const maxEdge = this.getOptionEdge('max', type); const step = steps?.[type] ?? 1; const daysOfWeek = []; + const dayjsLocale = globalConfig?.dayjsLocale || 'zh-cn'; for (let i = minEdge; i <= maxEdge; i += step) { - const currentDate = startOfMonth.date(i).locale(dayjsLocale); - const dayName = currentDate.format('ddd'); + const week = startOfMonth.date(i).locale(dayjsLocale).format('ddd'); + daysOfWeek.push({ value: `${i}`, - label: `${i}${locale.date || ''} ${dayName}`, + label: `${i}${globalConfig?.dateLabel} ${week}`, }); } @@ -209,13 +198,12 @@ export default class DateTimePicker extends SuperComponent { }, getOptionByType(type: string) { - const { locale, steps, showWeek } = this.data; + const { globalConfig, steps, showWeek } = this.data; const options: ColumnItemValue[] = []; const minEdge = this.getOptionEdge('min', type); const maxEdge = this.getOptionEdge('max', type); const step = steps?.[type] ?? 1; - const dayjsMonthsShort = dayjs().locale(this.data.dayjsLocale).localeData().monthsShort(); if (type === 'date' && showWeek) { return this.getDaysOfWeekInMonth(this.date, type); @@ -224,7 +212,7 @@ export default class DateTimePicker extends SuperComponent { for (let i = minEdge; i <= maxEdge; i += step) { options.push({ value: `${i}`, - label: type === 'month' ? dayjsMonthsShort[i] : `${i + locale[type]}`, + label: type === 'month' ? globalConfig.months[i] : `${i}${globalConfig[`${type}Label`]}`, }); } @@ -232,14 +220,14 @@ export default class DateTimePicker extends SuperComponent { }, getYearOptions(dateParams): ColumnItemValue[] { - const { locale } = this.data; + const { globalConfig } = this.data; const { minDateYear, maxDateYear } = dateParams; const years: ColumnItemValue[] = []; for (let i = minDateYear; i <= maxDateYear; i += 1) { years.push({ value: `${i}`, - label: `${i + locale.year}`, + label: `${i}${globalConfig.yearLabel}`, }); } return years; @@ -265,16 +253,16 @@ export default class DateTimePicker extends SuperComponent { }, getMonthOptions(): ColumnItemValue[] { + const { globalConfig } = this.data; const months: ColumnItemValue[] = []; const minMonth = this.getOptionEdge('min', 'month'); const maxMonth = this.getOptionEdge('max', 'month'); - const dayjsMonthsShort = dayjs.monthsShort(); for (let i = minMonth; i <= maxMonth; i += 1) { months.push({ value: `${i}`, - label: dayjsMonthsShort[i], + label: globalConfig.months[i], }); } @@ -282,7 +270,7 @@ export default class DateTimePicker extends SuperComponent { }, getDayOptions(): ColumnItemValue[] { - const { locale } = this.data; + const { globalConfig } = this.data; const days: ColumnItemValue[] = []; const minDay = this.getOptionEdge('min', 'date'); const maxDay = this.getOptionEdge('max', 'date'); @@ -290,7 +278,7 @@ export default class DateTimePicker extends SuperComponent { for (let i = minDay; i <= maxDay; i += 1) { days.push({ value: `${i}`, - label: `${i + locale.day}`, + label: `${i}${globalConfig.dateLabel}`, }); } @@ -298,7 +286,7 @@ export default class DateTimePicker extends SuperComponent { }, getHourOptions() { - const { locale } = this.data; + const { globalConfig } = this.data; const hours: ColumnItemValue[] = []; const minHour = this.getOptionEdge('min', 'hour'); const maxHour = this.getOptionEdge('max', 'hour'); @@ -306,7 +294,7 @@ export default class DateTimePicker extends SuperComponent { for (let i = minHour; i <= maxHour; i += 1) { hours.push({ value: `${i}`, - label: `${i + locale.hour}`, + label: `${i}${globalConfig.hourLabel}`, }); } @@ -314,7 +302,7 @@ export default class DateTimePicker extends SuperComponent { }, getMinuteOptions() { - const { locale } = this.data; + const { globalConfig } = this.data; const minutes: ColumnItemValue[] = []; const minMinute = this.getOptionEdge('min', 'minute'); const maxMinute = this.getOptionEdge('max', 'minute'); @@ -322,7 +310,7 @@ export default class DateTimePicker extends SuperComponent { for (let i = minMinute; i <= maxMinute; i += 1) { minutes.push({ value: `${i}`, - label: `${i + locale.minute}`, + label: `${i}${globalConfig.minuteLabel}`, }); } diff --git a/packages/components/date-time-picker/date-time-picker.wxml b/packages/components/date-time-picker/date-time-picker.wxml index b03f63ada..482a4fe66 100644 --- a/packages/components/date-time-picker/date-time-picker.wxml +++ b/packages/components/date-time-picker/date-time-picker.wxml @@ -5,10 +5,10 @@ visible="{{visible}}" value="{{columnsValue}}" header="{{header}}" - title="{{title}}" + title="{{title || globalConfig.title}}" auto-close="{{autoClose}}" - confirm-btn="{{confirmBtn || locale.confirm}}" - cancel-btn="{{cancelBtn || locale.cancel}}" + confirm-btn="{{confirmBtn || globalConfig.confirm}}" + cancel-btn="{{cancelBtn || globalConfig.cancel}}" use-popup="{{usePopup}}" popup-props="{{ popupProps }}" bind:pick="onColumnChange" diff --git a/packages/components/date-time-picker/locale/dayjs.ts b/packages/components/date-time-picker/locale/dayjs.ts deleted file mode 100644 index 7a59f15ee..000000000 --- a/packages/components/date-time-picker/locale/dayjs.ts +++ /dev/null @@ -1,81 +0,0 @@ -// dayjs 语言包 -import * as enLocale from 'dayjs/locale/en'; -import * as zhLocale from 'dayjs/locale/zh-cn'; -import * as tcLocale from 'dayjs/locale/zh-tw'; // 繁体 -import * as koLocale from 'dayjs/locale/ko'; // 韩语 -import * as jaLocale from 'dayjs/locale/ja'; // 日语 -import * as ruLocale from 'dayjs/locale/ru'; // 俄语 - -// 本地语言包 -import en from './en'; -import zh from './zh'; -import tc from './tc'; -import ko from './ko'; -import ja from './ja'; -import ru from './ru'; - -export default { - default: { - key: 'zh-cn', - label: '简体中文', - locale: zhLocale, - i18n: zh, - }, - en: { - key: 'en', - label: 'English', - locale: enLocale, - i18n: en, - }, - 'zh-cn': { - key: 'zh-cn', - label: '简体中文', - locale: zhLocale, - i18n: zh, - }, - // 容错处理 - zh: { - key: 'zh-cn', - label: '简体中文', - locale: zhLocale, - i18n: zh, - }, - 'zh-tw': { - key: 'zh-tw', - label: '繁体中文', - locale: tcLocale, - i18n: tc, - }, - // 容错处理 - tc: { - key: 'zh-tw', - label: '繁体中文', - locale: tcLocale, - i18n: tc, - }, - ko: { - key: 'ko', - label: '한국어', - locale: koLocale, - i18n: ko, - }, - // 容错处理 - kr: { - key: 'ko', - label: '한국어', - locale: koLocale, - i18n: ko, - }, - ja: { - key: 'ja', - label: '日本語', - locale: jaLocale, - i18n: ja, - }, - ru: { - key: 'ru', - label: 'русский', - locale: ruLocale, - i18n: ru, - }, -}; diff --git a/packages/components/date-time-picker/locale/en.ts b/packages/components/date-time-picker/locale/en.ts deleted file mode 100644 index 59f5a03d1..000000000 --- a/packages/components/date-time-picker/locale/en.ts +++ /dev/null @@ -1,12 +0,0 @@ -export default { - year: '', - month: '', - date: '', - hour: '', - minute: '', - second: '', - am: 'AM', - pm: 'PM', - confirm: 'confirm', - cancel: 'cancel', -}; diff --git a/packages/components/date-time-picker/locale/ja.ts b/packages/components/date-time-picker/locale/ja.ts deleted file mode 100644 index 07569740e..000000000 --- a/packages/components/date-time-picker/locale/ja.ts +++ /dev/null @@ -1,12 +0,0 @@ -export default { - year: '年', - month: '月', - date: '日', - hour: '時', - minute: '分', - second: '秒', - am: '午前', - pm: '午後', - confirm: '確認', - cancel: 'キャンセル', -}; diff --git a/packages/components/date-time-picker/locale/ko.ts b/packages/components/date-time-picker/locale/ko.ts deleted file mode 100644 index 76a395310..000000000 --- a/packages/components/date-time-picker/locale/ko.ts +++ /dev/null @@ -1,12 +0,0 @@ -export default { - year: '년', - month: '월', - date: '일', - hour: '시', - minute: '분', - second: '초', - am: '오전', - pm: '오후', - confirm: '확인', - cancel: '취소', -}; diff --git a/packages/components/date-time-picker/locale/ru.ts b/packages/components/date-time-picker/locale/ru.ts deleted file mode 100644 index 23c707c8e..000000000 --- a/packages/components/date-time-picker/locale/ru.ts +++ /dev/null @@ -1,12 +0,0 @@ -export default { - year: '', - month: '', - date: '', - hour: '', - minute: '', - second: '', - am: 'до полудня', - pm: 'после полудня', - confirm: 'подтвердить', - cancel: 'отменить', -}; diff --git a/packages/components/date-time-picker/locale/tc.ts b/packages/components/date-time-picker/locale/tc.ts deleted file mode 100644 index 58e7cbfe5..000000000 --- a/packages/components/date-time-picker/locale/tc.ts +++ /dev/null @@ -1,12 +0,0 @@ -export default { - year: '年', - month: '月', - date: '日', - hour: '時', - minute: '分', - second: '秒', - am: '上午', - pm: '下午', - confirm: '確定', - cancel: '取消', -}; diff --git a/packages/components/date-time-picker/locale/zh.ts b/packages/components/date-time-picker/locale/zh.ts deleted file mode 100644 index 66566742b..000000000 --- a/packages/components/date-time-picker/locale/zh.ts +++ /dev/null @@ -1,12 +0,0 @@ -export default { - year: '年', - month: '月', - date: '日', - hour: '时', - minute: '分', - second: '秒', - am: '上午', - pm: '下午', - confirm: '确定', - cancel: '取消', -}; diff --git a/packages/components/date-time-picker/props.ts b/packages/components/date-time-picker/props.ts index 43e07e56b..ffae8c3cf 100644 --- a/packages/components/date-time-picker/props.ts +++ b/packages/components/date-time-picker/props.ts @@ -21,11 +21,6 @@ const props: TdDateTimePickerProps = { type: String, value: '', }, - /** 组件国际化语言,目前支持: 简体中文(zh)、(tc)、英文(en)、日语(ja)、韩语(ko)、俄语(ru)等六种语言 */ - customLocale: { - type: String, - value: 'zh', - }, /** 选择器的最大可选时间,默认为当前时间+10年 */ end: { type: null, diff --git a/packages/components/date-time-picker/type.ts b/packages/components/date-time-picker/type.ts index 6a155dbf4..91e82eab2 100644 --- a/packages/components/date-time-picker/type.ts +++ b/packages/components/date-time-picker/type.ts @@ -31,14 +31,6 @@ export interface TdDateTimePickerProps { type: StringConstructor; value?: string; }; - /** - * 组件国际化语言,目前支持: 简体中文(zh)、(tc)、英文(en)、日语(ja)、韩语(ko)、俄语(ru)等六种语言 - * @default zh - */ - customLocale?: { - type: StringConstructor; - value?: string; - }; /** * 选择器的最大可选时间,默认为当前时间+10年 */ diff --git a/packages/components/dialog/README.md b/packages/components/dialog/README.md index 5f2054ff0..2a2de0f0b 100644 --- a/packages/components/dialog/README.md +++ b/packages/components/dialog/README.md @@ -5,7 +5,7 @@ spline: message isComponent: true --- - + ## 引入 全局引入,在 miniprogram 根目录下的`app.json`中配置,局部引入,在需要引入的页面或组件的`index.json`中配置。 diff --git a/packages/components/divider/README.md b/packages/components/divider/README.md index 4a0a9d83f..25a45ba1c 100644 --- a/packages/components/divider/README.md +++ b/packages/components/divider/README.md @@ -5,7 +5,7 @@ spline: message isComponent: true --- - + ## 引入 全局引入,在 miniprogram 根目录下的`app.json`中配置,局部引入,在需要引入的页面或组件的`index.json`中配置。 diff --git a/packages/components/drawer/README.md b/packages/components/drawer/README.md index 40b6992fd..f9b93c72c 100644 --- a/packages/components/drawer/README.md +++ b/packages/components/drawer/README.md @@ -13,7 +13,7 @@ isComponent: true 该组件于 0.7.2 版本上线,请留意版本。 - + ## 引入 全局引入,在 miniprogram 根目录下的`app.json`中配置,局部引入,在需要引入的页面或组件的`index.json`中配置。 diff --git a/packages/components/dropdown-item/dropdown-item.ts b/packages/components/dropdown-item/dropdown-item.ts index 4560b1c48..8a716bc7d 100644 --- a/packages/components/dropdown-item/dropdown-item.ts +++ b/packages/components/dropdown-item/dropdown-item.ts @@ -4,13 +4,17 @@ import props from './props'; import menuProps from '../dropdown-menu/props'; import type { TdDropdownItemProps } from './type'; import { getRect } from '../common/utils'; +import usingConfig from '../mixins/using-config'; const { prefix } = config; -const name = `${prefix}-dropdown-item`; +const parentComponentName = 'dropdown-menu'; +const componentName = 'dropdown-item'; export interface DropdownItemProps extends TdDropdownItemProps {} @wxComponent() export default class DropdownMenuItem extends SuperComponent { + behaviors = [usingConfig({ componentName: parentComponentName })]; + options = { multipleSlots: true, }; @@ -28,7 +32,7 @@ export default class DropdownMenuItem extends SuperComponent { data = { prefix, - classPrefix: name, + classPrefix: `${prefix}-${componentName}`, show: false, top: 0, maskHeight: 0, diff --git a/packages/components/dropdown-item/dropdown-item.wxml b/packages/components/dropdown-item/dropdown-item.wxml index c75e3ad15..e1d4c1710 100644 --- a/packages/components/dropdown-item/dropdown-item.wxml +++ b/packages/components/dropdown-item/dropdown-item.wxml @@ -91,7 +91,7 @@ block class="{{classPrefix}}__footer-btn {{classPrefix}}__reset-btn" theme="light" - content="重置" + content="{{globalConfig.reset}}" disabled="{{value.length == 0}}" bindtap="handleReset" /> @@ -99,7 +99,7 @@ block class="{{classPrefix}}__footer-btn {{classPrefix}}__confirm-btn" theme="primary" - content="确定" + content="{{globalConfig.confirm}}" disabled="{{value.length == 0}}" bindtap="handleConfirm" /> diff --git a/packages/components/dropdown-menu/README.md b/packages/components/dropdown-menu/README.md index cb4202bd2..feb547322 100644 --- a/packages/components/dropdown-menu/README.md +++ b/packages/components/dropdown-menu/README.md @@ -13,7 +13,7 @@ isComponent: true 该组件于 0.8.0 版本上线,请留意版本。 - + ## 引入 ### 引入组件 diff --git a/packages/components/dropdown-menu/dropdown-menu.ts b/packages/components/dropdown-menu/dropdown-menu.ts index 4dd646b0b..4284beb49 100644 --- a/packages/components/dropdown-menu/dropdown-menu.ts +++ b/packages/components/dropdown-menu/dropdown-menu.ts @@ -5,7 +5,7 @@ import type { TdDropdownMenuProps } from './type'; import { calcIcon } from '../common/utils'; const { prefix } = config; -const name = `${prefix}-dropdown-menu`; +const componentName = 'dropdown-menu'; export interface DropdownMenuProps extends TdDropdownMenuProps {} @@ -19,7 +19,7 @@ export default class DropdownMenu extends SuperComponent { data = { prefix, - classPrefix: name, + classPrefix: `${prefix}-${componentName}`, menus: null, activeIdx: -1, bottom: 0, diff --git a/packages/components/empty/README.md b/packages/components/empty/README.md index d6884425c..6687c2a24 100644 --- a/packages/components/empty/README.md +++ b/packages/components/empty/README.md @@ -5,7 +5,7 @@ spline: data isComponent: true --- - + ## 引入 全局引入,在 miniprogram 根目录下的`app.json`中配置,局部引入,在需要引入的页面或组件的`index.json`中配置。 diff --git a/packages/components/fab/README.md b/packages/components/fab/README.md index 6f6780dff..fe5779032 100644 --- a/packages/components/fab/README.md +++ b/packages/components/fab/README.md @@ -13,7 +13,7 @@ isComponent: true 该组件于 0.7.2 版本上线,请留意版本。 - + ## 引入 全局引入,在 miniprogram 根目录下的`app.json`中配置,局部引入,在需要引入的页面或组件的`index.json`中配置。 diff --git a/packages/components/footer/README.md b/packages/components/footer/README.md index dfb7137f6..fd03aca30 100644 --- a/packages/components/footer/README.md +++ b/packages/components/footer/README.md @@ -5,7 +5,7 @@ spline: data isComponent: true --- - + ## 引入 全局引入,在 miniprogram 根目录下的`app.json`中配置,局部引入,在需要引入的页面或组件的`index.json`中配置。 diff --git a/packages/components/grid/README.md b/packages/components/grid/README.md index 3ad6fd7e1..4880efdc2 100644 --- a/packages/components/grid/README.md +++ b/packages/components/grid/README.md @@ -5,7 +5,7 @@ spline: data isComponent: true --- - + ## 引入 全局引入,在 miniprogram 根目录下的`app.json`中配置,局部引入,在需要引入的页面或组件的`index.json`中配置。 diff --git a/packages/components/guide/guide.ts b/packages/components/guide/guide.ts index a9c26c1b4..483d9241b 100644 --- a/packages/components/guide/guide.ts +++ b/packages/components/guide/guide.ts @@ -4,15 +4,18 @@ import config from '../common/config'; import { isFunction, isNumeric } from '../common/validator'; import { TdGuideProps, GuideStep } from './type'; import { debounce, getRect, rpx2px, styles, unitConvert, nextTick, systemInfo } from '../common/utils'; +import usingConfig from '../mixins/using-config'; export interface GuideProps extends TdGuideProps {} export { GuideStep }; const { prefix } = config; -const name = `${prefix}-guide`; +const componentName = 'guide'; @wxComponent() export default class Guide extends SuperComponent { + behaviors = [usingConfig({ componentName })]; + externalClasses = [ `${prefix}-class`, `${prefix}-class-reference`, @@ -36,7 +39,7 @@ export default class Guide extends SuperComponent { data = { prefix, - classPrefix: name, + classPrefix: `${prefix}-${componentName}`, visible: false, _current: -1, _steps: [], @@ -124,48 +127,50 @@ export default class Guide extends SuperComponent { } }, async placementOffset({ placement, offset }: GuideStep, place: CSSStyleDeclaration) { + const { classPrefix } = this.data; await nextTick(); - const rect = await getRect(this, `.${name}__container`); + const rect = await getRect(this, `.${classPrefix}__container`); const style = this._getPlacement[placement]?.(rect, place, offset); return styles({ position: 'absolute', ...style }); }, buttonProps(step, mode) { + const { classPrefix, globalConfig } = this.data; let skipButton = step.skipButtonProps ?? this.data.skipButtonProps; const size = mode === 'popover' ? 'extra-small' : 'medium'; skipButton = { theme: 'light', - content: '跳过', + content: globalConfig.skip, size, ...skipButton, - tClass: `${prefix}-class-skip ${name}__button ${skipButton?.class || ''}`, + tClass: `${prefix}-class-skip ${classPrefix}__button ${skipButton?.class || ''}`, type: 'skip', }; let nextButton = step.nextButtonProps ?? this.data.nextButtonProps; nextButton = { theme: 'primary', - content: '下一步', + content: globalConfig.next, size, ...nextButton, - tClass: `${prefix}-class-next ${name}__button ${nextButton?.class || ''}`, + tClass: `${prefix}-class-next ${classPrefix}__button ${nextButton?.class || ''}`, type: 'next', }; nextButton = { ...nextButton, content: this.buttonContent(nextButton) }; let backButton = step.backButtonProps ?? this.data.backButtonProps; backButton = { theme: 'light', - content: '返回', + content: globalConfig.back, size, ...backButton, - tClass: `${prefix}-class-back ${name}__button ${backButton?.class || ''}`, + tClass: `${prefix}-class-back ${classPrefix}__button ${backButton?.class || ''}`, type: 'back', }; let finishButton = step.finishButtonProps ?? this.data.finishButtonProps; finishButton = { theme: 'primary', - content: '完成', + content: globalConfig.finish, size, ...finishButton, - tClass: `${prefix}-class-finish ${name}__button ${finishButton?.class || ''}`, + tClass: `${prefix}-class-finish ${classPrefix}__button ${finishButton?.class || ''}`, type: 'finish', }; finishButton = { ...finishButton, content: this.buttonContent(finishButton) }; diff --git a/packages/components/icon/README.md b/packages/components/icon/README.md index fb041f519..78c72444d 100644 --- a/packages/components/icon/README.md +++ b/packages/components/icon/README.md @@ -6,7 +6,7 @@ isComponent: true --- - + ## 引入 全局引入,在 miniprogram 根目录下的`app.json`中配置,局部引入,在需要引入的页面或组件的`index.json`中配置。 diff --git a/packages/components/image-viewer/README.md b/packages/components/image-viewer/README.md index 7077fe63a..395b79eda 100644 --- a/packages/components/image-viewer/README.md +++ b/packages/components/image-viewer/README.md @@ -13,7 +13,7 @@ isComponent: true 该组件于 0.10.0 版本上线,请留意版本。 - + ## 引入 全局引入,在 miniprogram 根目录下的`app.json`中配置,局部引入,在需要引入的页面或组件的`index.json`中配置。 diff --git a/packages/components/image/README.md b/packages/components/image/README.md index 3ca51ed88..10089fb8e 100644 --- a/packages/components/image/README.md +++ b/packages/components/image/README.md @@ -6,7 +6,7 @@ isComponent: true --- - + ## 引入 全局引入,在 miniprogram 根目录下的`app.json`中配置,局部引入,在需要引入的页面或组件的`index.json`中配置。 diff --git a/packages/components/indexes/README.md b/packages/components/indexes/README.md index a5e95699f..860d41da0 100644 --- a/packages/components/indexes/README.md +++ b/packages/components/indexes/README.md @@ -14,7 +14,7 @@ isComponent: true - + ## 引入 全局引入,在 miniprogram 根目录下的`app.json`中配置,局部引入,在需要引入的页面或组件的`index.json`中配置。 diff --git a/packages/components/input/README.md b/packages/components/input/README.md index bd9550b4d..be93c4036 100644 --- a/packages/components/input/README.md +++ b/packages/components/input/README.md @@ -6,7 +6,7 @@ isComponent: true --- - + ## 引入 全局引入,在 miniprogram 根目录下的`app.json`中配置,局部引入,在需要引入的页面或组件的`index.json`中配置。 diff --git a/packages/components/link/README.md b/packages/components/link/README.md index 0c25ff96a..99502fbb8 100644 --- a/packages/components/link/README.md +++ b/packages/components/link/README.md @@ -12,7 +12,7 @@ isComponent: true 该组件于 0.32.0 版本上线,请留意版本。 - + ## 引入 全局引入,在 miniprogram 根目录下的`app.json`中配置,局部引入,在需要引入的页面或组件的`index.json`中配置。 diff --git a/packages/components/loading/README.md b/packages/components/loading/README.md index 1c6306ffd..d369449f2 100644 --- a/packages/components/loading/README.md +++ b/packages/components/loading/README.md @@ -5,7 +5,7 @@ spline: message isComponent: true --- - + ## 引入 全局引入,在 miniprogram 根目录下的`app.json`中配置,局部引入,在需要引入的页面或组件的`index.json`中配置。 diff --git a/packages/components/locale/ar_KW.ts b/packages/components/locale/ar_KW.ts new file mode 100644 index 000000000..3fea713ec --- /dev/null +++ b/packages/components/locale/ar_KW.ts @@ -0,0 +1,157 @@ +/* eslint-disable no-template-curly-in-string */ +// 文件有效,为国际化做准备 +import 'dayjs/locale/ar'; + +export default { + actionSheet: { + cancel: 'الإلغاء', + }, + calendar: { + confirm: 'أكد', + title: 'انتقِ التاريخ', + weekdays: ['يوم الأحد', 'الاثنين', 'الثلاثاء', 'الأربعاء', 'الخميس', 'الجمعة', 'السبت'], + monthTitle: '{شهر واحد} {سنة واحدة}', + months: [ + 'يناير', + 'فبراير', + 'مارس', + 'أبريل', + 'مايو', + 'يونيو', + 'يوليو', + 'أغسطس', + 'سبتمبر', + 'أكتوبر', + 'نوفمبر', + 'ديسمبر', + ], + }, + cascader: { + title: 'العنوان', + placeholder: 'اختر الخيارات', + }, + dropdownMenu: { + reset: 'إعادة الضبط', + confirm: 'أكد', + }, + dateTimePicker: { + dayjsLocale: 'ar', + title: 'انتقِ التاريخ', + cancel: 'الإلغاء', + confirm: 'أكد', + format: 'DD-MM-YYYY', + months: [ + 'يناير', + 'فبراير', + 'مارس', + 'أبريل', + 'مايو', + 'يونيو', + 'يوليو', + 'أغسطس', + 'سبتمبر', + 'أكتوبر', + 'نوفمبر', + 'ديسمبر', + ], + yearLabel: 'سنة', + monthLabel: 'الشهر', + dateLabel: 'التاريخ', + hourLabel: 'الساعة', + minuteLabel: 'دقيقة', + secondLabel: 'ثانيا', + }, + form: { + errorMessage: { + date: 'الرجاء إدخال ${name} الصحيح', + url: 'الرجاء إدخال ${name} الصحيح', + whitespace: 'لا يمكن أن يكون ${name} فارغًا', + required: 'مطلوب ${name}', + max: 'يمكن أن يحتوي ${name} على ما يصل إلى ${validate} حرفًا', + min: 'لا يمكن أن يكون ${name} أقل من ${validate} حرفًا', + len: 'يجب أن يتكون ${name} من أحرف ${validate} بالضبط', + enum: '${name} يجب أن يكون واحدًا من ${validate} ', + idcard: 'الرجاء إدخال ${name} الصحيح', + telnumber: 'الرجاء إدخال ${name} الصحيح', + pattern: 'الرجاء إدخال ${name} الصحيح', + validator: '${name} غير صالح', + boolean: '${name} ليس منطقيًا', + number: 'يجب أن يكون ${name} رقمًا', + }, + }, + picker: { + cancel: 'الإلغاء', + confirm: 'أكد', + }, + pullDownRefresh: { + loadingTexts: ['اسحب للتحديث', 'مرتخية للتجديد', 'منعش', 'اكتمل التحديث'], + }, + rate: { + texts: ['سيء', 'مخيب', 'عادي', 'جيد', 'ممتاز'], + valueText: '{القيمة} نتيجة', + noValueText: 'لا توجد نقاط', + }, + tabBar: { + newsAriaLabel: 'هناك أخبار جديدة', + moreNewsAriaLabel: 'هناك الكثير من الأخبار السيئة', + haveMoreNewsAriaLabel: 'هناك { قيمة }+ أخبار', + haveNewsAriaLabel: 'هناك { قيمة } أخبار', + }, + table: { + empty: 'البيانات الفارغة', + }, + list: { + loading: 'التحميل...', + loadingMoreText: 'انقر لتحميل المزيد', + pulling: 'اسحب للتحديث...', + loosing: 'مرتخية للتجديد...', + success: 'تم التحديث بنجاح', + }, + upload: { + progress: { + uploadingText: 'جارٍ التحميل...', + waitingText: 'الانتظار', + failText: 'فشل', + successText: 'النجاح', + }, + }, + guide: { + next: 'التالي', + skip: 'تخطي', + finish: 'أنهي', + back: 'العودة', + }, + qrcode: { + expiredText: 'منتهي الصلاحية', + refreshText: 'ينعش', + scannedText: 'تم مسحها ضوئيًا', + }, + attachments: { + status: { + pending: 'جارٍ التحميل...', + fail: 'فشل التحميل', + }, + }, + chatActionbar: { + actionBar: { + replay: 'تحديث', + copy: 'نسخ', + good: 'إعجاب', + bad: 'عدم إعجاب', + share: 'مشاركة', + quote: 'اقتباس', + }, + }, + chatSender: { + placeholder: 'الرجاء إدخال الرسالة...', + sendText: 'إرسال', + stopText: 'إيقاف', + }, + chatThinking: { + status: { + pending: 'جاري التفكير...', + complete: 'تم الانتهاء من التفكير', + stop: 'تم إيقاف التفكير', + }, + }, +}; diff --git a/packages/components/locale/en_US.ts b/packages/components/locale/en_US.ts new file mode 100644 index 000000000..807ad12a3 --- /dev/null +++ b/packages/components/locale/en_US.ts @@ -0,0 +1,146 @@ +/* eslint-disable no-template-curly-in-string */ +// 文件有效,为国际化做准备 +import 'dayjs/locale/en'; + +export default { + actionSheet: { + cancel: 'Cancel', + }, + calendar: { + confirm: 'Confirm', + title: 'Select Date', + weekdays: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'], + monthTitle: '{month} {year}', + months: [ + 'January', + 'February', + 'March', + 'April', + 'May', + 'June', + 'July', + 'August', + 'September', + 'October', + 'November', + 'December', + ], + }, + cascader: { + title: 'Title', + placeholder: 'Select options', + }, + dropdownMenu: { + reset: 'Reset', + confirm: 'Confirm', + }, + dateTimePicker: { + dayjsLocale: 'en', + title: 'Select Date', + cancel: 'Cancel', + confirm: 'Confirm', + format: 'YYYY-MM-DD', + months: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'], + yearLabel: '', + monthLabel: '', + dateLabel: '', + hourLabel: '', + minuteLabel: '', + secondLabel: '', + }, + form: { + errorMessage: { + date: '${name} is invalid', + url: '${name} is invalid', + required: '${name} is required', + whitespace: '${name} cannot be empty', + max: '${name} cannot be longer than ${validate} characters', + min: '${name} must be at least ${validate} characters', + len: '${name} must be exactly ${validate} characters', + enum: '${name} must be one of ${validate}', + idcard: '${name} is invalid', + telnumber: '${name} is invalid', + pattern: '${name} is invalid', + validator: '${name} is invalid', + boolean: '${name} is not a boolean', + number: '${name} must be a number', + }, + colonText: ':', + }, + picker: { + cancel: 'Cancel', + confirm: 'Confirm', + }, + pullDownRefresh: { + loadingTexts: ['Pull to refresh', 'Loose to refresh', 'Refreshing ', 'Refresh completed'], + }, + rate: { + texts: ['Poor', 'Fair', 'Average', 'Good', 'Excellent'], + valueText: '{value} score', + noValueText: 'No score', + }, + tabBar: { + newsAriaLabel: 'There is new news', + moreNewsAriaLabel: 'There is a lot of news', + haveMoreNewsAriaLabel: 'There are {value}+ news', + haveNewsAriaLabel: 'There are {value} news', + }, + table: { + empty: 'Empty Data', + }, + list: { + loading: 'Loading...', + loadingMoreText: 'Click to load more', + pulling: 'Pull to refresh...', + loosing: 'Loose to refresh...', + success: 'Refresh successful', + }, + upload: { + progress: { + uploadingText: 'Uploading...', + waitingText: 'Waiting', + failText: 'Failed', + successText: 'Success', + reloadText: 'Reload', + }, + }, + guide: { + next: 'Next', + skip: 'Skip', + finish: 'Finish', + back: 'Back', + }, + qrcode: { + expiredText: 'expired', + refreshText: 'refresh', + scannedText: 'scanned', + }, + attachments: { + status: { + pending: 'uploading...', + fail: 'upload failed', + }, + }, + chatActionbar: { + actionBar: { + replay: 'refresh', + copy: 'copy', + good: 'good', + bad: 'bad', + share: 'share', + quote: 'quote', + }, + }, + chatSender: { + placeholder: 'please enter message...', + sendText: 'send', + stopText: 'stop', + }, + chatThinking: { + status: { + pending: 'thinking...', + complete: 'thinking process completed', + stop: 'thinking has stopped', + }, + }, +}; diff --git a/packages/components/locale/it_IT.ts b/packages/components/locale/it_IT.ts new file mode 100644 index 000000000..f3a03d218 --- /dev/null +++ b/packages/components/locale/it_IT.ts @@ -0,0 +1,145 @@ +/* eslint-disable no-template-curly-in-string */ +// 文件有效,为国际化做准备 +import 'dayjs/locale/it'; + +export default { + actionSheet: { + cancel: 'Annulla', + }, + calendar: { + confirm: 'Conferma', + title: 'Seleziona Data', + weekdays: ['Domenica', 'Lunedì', 'Martedì', 'Mercoledì', 'Giovedì', 'Venerdì', 'Sabato'], + monthTitle: '{mese} {anno}', + months: [ + 'Gennaio', + 'Febbraio', + 'Marzo', + 'Aprile', + 'Maggio', + 'Giugno', + 'Luglio', + 'Agosto', + 'Settembre', + 'Ottobre', + 'Novembre', + 'Dicembre', + ], + }, + cascader: { + title: 'Titolo', + placeholder: 'Seleziona opzioni', + }, + dropdownMenu: { + reset: 'Reimposta', + confirm: 'Conferma', + }, + dateTimePicker: { + dayjsLocale: 'it', + title: 'Seleziona Data', + cancel: 'Annulla', + confirm: 'Conferma', + format: 'DD-MM-YYYY', + months: ['Gen', 'Feb', 'Mar', 'Apr', 'Mag', 'Giu', 'Lug', 'Ago', 'Set', 'Ott', 'Nov', 'Dic'], + yearLabel: 'Anno', + monthLabel: 'Mese', + dateLabel: 'Data', + hourLabel: 'Ora', + minuteLabel: 'Minuto', + secondLabel: 'Secondo', + }, + form: { + errorMessage: { + date: 'Inserisci la ${name} corretta', + url: 'Inserisci la ${name} corretta', + whitespace: 'Il ${name} non può essere vuoto', + required: '${name} obbligatorio', + max: 'La lunghezza dei caratteri di ${name} non può superare i ${validate} caratteri', + min: 'La lunghezza dei caratteri di ${name} non può essere inferiore a ${validate} caratteri', + len: 'La lunghezza dei caratteri di ${name} deve essere ${validate}', + enum: '${name} può essere solo ${validate}, ecc.', + idcard: 'Inserisci la ${name} corretta', + telnumber: 'Inserisci la ${name} corretta', + pattern: 'Inserisci la ${name} corretta', + validator: '${name} non conforme ai requisiti', + boolean: 'Il tipo di dati di ${name} deve essere booleano', + number: '${name} deve essere un numero', + }, + colonText: ':', + }, + picker: { + cancel: 'Annulla', + confirm: 'Conferma', + }, + pullDownRefresh: { + loadingTexts: ['Tirare per aggiornare', 'Libera da aggiornare', 'Rinfrescante', 'Aggiornamento completato'], + }, + rate: { + texts: ['Pessimo', 'Scarso', 'Normale', 'Buono', 'Eccellente'], + valueText: '{valore} punteggio', + noValueText: 'Nessun punteggio', + }, + tabBar: { + newsAriaLabel: 'Ci sono nuove notizie', + moreNewsAriaLabel: 'Ci sono molte notizie', + haveMoreNewsAriaLabel: 'Ci sono {valore}+ notizie', + haveNewsAriaLabel: 'Ci sono {valore} notizie', + }, + table: { + empty: 'Dati Vuoti', + }, + list: { + loading: 'Caricamento...', + loadingMoreText: 'Fai clic per caricare di più', + pulling: 'Estrai per aggiornare...', + loosing: 'Libera da aggiornare...', + success: 'Aggiorna riuscito', + }, + upload: { + progress: { + uploadingText: 'Invio...', + waitingText: 'Attesa', + failText: 'Fallito', + successText: 'Successo', + }, + }, + guide: { + next: 'Successivo', + skip: 'Salta', + finish: 'Finisci', + back: 'Indietro', + }, + qrcode: { + expiredText: 'scaduto', + refreshText: 'aggiornare', + scannedText: 'scansionato', + }, + attachments: { + status: { + pending: 'Caricamento...', + fail: 'Caricamento non riuscito', + }, + }, + chatActionbar: { + actionBar: { + replay: 'Aggiorna', + copy: 'Copia', + good: 'Mi piace', + bad: 'Non mi piace', + share: 'Condividi', + quote: 'Cita', + }, + }, + chatSender: { + placeholder: 'Inserisci il messaggio...', + sendText: 'Invia', + stopText: 'Ferma', + }, + chatThinking: { + status: { + pending: 'Pensando...', + complete: 'Pensiero completato', + stop: 'Pensiero interrotto', + }, + }, +}; diff --git a/packages/components/locale/ja_JP.ts b/packages/components/locale/ja_JP.ts new file mode 100644 index 000000000..393b6b502 --- /dev/null +++ b/packages/components/locale/ja_JP.ts @@ -0,0 +1,132 @@ +/* eslint-disable no-template-curly-in-string */ +// 文件有效,为国际化做准备 +import 'dayjs/locale/ja'; + +export default { + actionSheet: { + cancel: 'キャンセル', + }, + calendar: { + confirm: '確認', + title: '日付の選択', + weekdays: ['日', '月', '火', '水', '木', '金', '土'], + monthTitle: '{month} {year}', + months: ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月'], + }, + cascader: { + title: 'タイトル', + placeholder: 'オプションを選択', + }, + dropdownMenu: { + reset: 'リセット', + confirm: '確認', + }, + dateTimePicker: { + dayjsLocale: 'ja', + title: '日付を選択', + cancel: 'キャンセル', + confirm: '確認', + format: 'YYYY-MM-DD', + months: ['1 月', '2 月', '3 月', '4 月', '5 月', '6 月', '7 月', '8 月', '9 月', '10 月', '11 月', '12 月'], + yearLabel: '年', + monthLabel: '月', + dateLabel: '日', + hourLabel: '時', + minuteLabel: '分', + secondLabel: '秒', + }, + form: { + errorMessage: { + date: '正しく入力してください${name}', + url: '正しく入力してください${name}', + required: '${name}必須項目', + whitespace: '${name}を空にすることはできません', + max: '${name}文字数制限 ${validate} 文字,一中二文', + min: '${name}を下回る文字数は使用できません ${validate} 文字,一中二文', + len: '${name}文字の長さは、必ず ${validate}', + enum: '${name}でしかありえません${validate}等', + idcard: '正しく入力してください${name}', + telnumber: '正しく入力してください${name}', + pattern: '正しく入力してください${name}', + validator: '${name}要件を満たしていない', + boolean: '${name}データ型は Boolean 型であること', + number: '${name}デジタルであること', + }, + colonText: ':', + }, + picker: { + cancel: 'キャンセル', + confirm: '確認', + }, + pullDownRefresh: { + loadingTexts: ['更新に引っ張ってください', '緩めて更新中', '更新中…', '更新が完了しました'], + }, + rate: { + texts: ['悪い', '不満', '普通', '良い', '最高'], + valueText: '{value} 点', + noValueText: 'スコアなし', + }, + tabBar: { + newsAriaLabel: '新しいニュースがあります', + moreNewsAriaLabel: 'たくさんのニュースがあります', + haveMoreNewsAriaLabel: '{value}+ 件のニュースがあります', + haveNewsAriaLabel: '{value} 件のニュースがあります', + }, + table: { + empty: 'データがありません', + }, + list: { + loading: '読み込み中…', + loadingMoreText: 'もっと見るにはクリックしてください', + pulling: '更新に引っ張ってください…', + loosing: '緩めて更新中…', + success: '更新が成功しました', + }, + upload: { + progress: { + uploadingText: 'アップロード中…', + waitingText: '待機中', + failText: '失敗しました', + successText: '成功しました', + }, + }, + guide: { + next: '次へ', + skip: 'スキップ', + finish: '完了', + back: '戻る', + }, + qrcode: { + expiredText: '期限切れ', + refreshText: 'リフレッシュ', + scannedText: 'スキャンされた', + }, + attachments: { + status: { + pending: 'アップロード中...', + fail: 'アップロード失敗', + }, + }, + chatActionbar: { + actionBar: { + replay: '更新', + copy: 'コピー', + good: 'いいね', + bad: '低評価', + share: '共有', + quote: '引用', + }, + }, + chatSender: { + placeholder: 'メッセージを入力してください...', + sendText: '送信', + stopText: '停止', + }, + chatThinking: { + status: { + pending: '思考中...', + complete: '思考完了', + stop: '思考を停止', + }, + }, +}; diff --git a/packages/components/locale/ko_KR.ts b/packages/components/locale/ko_KR.ts new file mode 100644 index 000000000..5b32411d6 --- /dev/null +++ b/packages/components/locale/ko_KR.ts @@ -0,0 +1,132 @@ +/* eslint-disable no-template-curly-in-string */ +// 文件有效,为国际化做准备 +import 'dayjs/locale/ko'; + +export default { + actionSheet: { + cancel: '취소', + }, + calendar: { + confirm: '확인', + title: '날짜 선택', + weekdays: ['일', '월', '화', '수', '목', '금', '토'], + monthTitle: '{month} {year}', + months: ['1월', '2월', '3월', '4월', '5월', '6월', '7월', '8월', '9월', '10월', '11월', '12월'], + }, + cascader: { + title: '제목', + placeholder: '옵션 선택', + }, + dropdownMenu: { + reset: '초기화', + confirm: '확인', + }, + dateTimePicker: { + dayjsLocale: 'ko', + title: '날짜 선택', + cancel: '취소', + confirm: '확인', + format: 'YYYY-MM-DD', + months: ['1 월', '2 월', '3 월', '4 월', '5 월', '6 월', '7 월', '8 월', '9 월', '10 월', '11 월', '12 월'], + yearLabel: '년', + monthLabel: '월', + dateLabel: '일', + hourLabel: '시', + minuteLabel: '분', + secondLabel: '초', + }, + form: { + errorMessage: { + date: '정확한 내용을 입력해주세요${name}', + url: '정확한 내용을 입력해주세요${name}', + required: '${name}필수의', + whitespace: '${name}은 비어 있을 수 없습니다', + max: '${name}문자 길이는 초과할 수 없습니다 ${validate} 캐릭터,한자는 두 글자와 같다', + min: '${name}문자 길이는 다음보다 작을 수 없습니다 ${validate} 캐릭터,한자는 두 글자와 같다', + len: '${name}문자 길이는 다음과 같아야 합니다. ${validate}', + enum: '${name}만 될 수 있습니다${validate}그리고 더', + idcard: '정확한 내용을 입력해주세요${name}', + telnumber: '정확한 내용을 입력해주세요${name}', + pattern: '정확한 내용을 입력해주세요${name}', + validator: '${name}비준수', + boolean: '${name}데이터 유형은 부울이어야 합니다', + number: '${name}숫자여야 합니다', + }, + colonText: ':', + }, + picker: { + cancel: '취소', + confirm: '확인', + }, + pullDownRefresh: { + loadingTexts: ['새로고침을 당겨주세요', '느슨하게 하여 새로 고침', '새로고침 중...', '새로고침 완료'], + }, + rate: { + texts: ['나쁨', '실망', '보통', '만족', '최고'], + valueText: '{value}점', + noValueText: '점수 없음', + }, + tabBar: { + newsAriaLabel: '새 뉴스가 있습니다', + moreNewsAriaLabel: '많은 뉴스가 있습니다', + haveMoreNewsAriaLabel: '{value}+건의 뉴스가 있습니다', + haveNewsAriaLabel: '{value}건의 뉴스가 있습니다', + }, + table: { + empty: '빈 데이터', + }, + list: { + loading: '로딩 중...', + loadingMoreText: '더 많은 것을 보시려면 클릭하세요', + pulling: '새로고침을 당겨주세요...', + loosing: '느슨하게 하여 새로 고침...', + success: '새로고침 성공', + }, + upload: { + progress: { + uploadingText: '업로드 중...', + waitingText: '대기 중', + failText: '실패했습니다', + successText: '성공했습니다', + }, + }, + guide: { + next: '다음', + skip: '건너뛰기', + finish: '완료', + back: '뒤로', + }, + qrcode: { + expiredText: '만료됨', + refreshText: '새로 고치다', + scannedText: '스캔됨', + }, + attachments: { + status: { + pending: '업로드 중...', + fail: '업로드 실패', + }, + }, + chatActionbar: { + actionBar: { + replay: '새로고침', + copy: '복사', + good: '좋아요', + bad: '싫어요', + share: '공유', + quote: '인용', + }, + }, + chatSender: { + placeholder: '메시지를 입력하세요...', + sendText: '보내기', + stopText: '중지', + }, + chatThinking: { + status: { + pending: '생각 중...', + complete: '생각 완료', + stop: '생각 중지', + }, + }, +}; diff --git a/packages/components/locale/ru_RU.ts b/packages/components/locale/ru_RU.ts new file mode 100644 index 000000000..da4bad60c --- /dev/null +++ b/packages/components/locale/ru_RU.ts @@ -0,0 +1,157 @@ +/* eslint-disable no-template-curly-in-string */ +import 'dayjs/locale/ru'; + +export default { + actionSheet: { + cancel: 'Отмена', + }, + calendar: { + confirm: 'Подтвердить', + title: 'Выберите дату', + weekdays: ['Вс', 'Пн', 'Вт', 'Ср', 'Чт', 'Пт', 'Сб'], + monthTitle: '{month} {year}', + months: [ + 'Январь', + 'Февраль', + 'Март', + 'Апрель', + 'Май', + 'Июнь', + 'Июль', + 'Август', + 'Сентябрь', + 'Октябрь', + 'Ноябрь', + 'Декабрь', + ], + }, + cascader: { + title: 'Название', + placeholder: 'Выберите опцию', + }, + dropdownMenu: { + reset: 'Сброс', + confirm: 'Подтвердить', + }, + dateTimePicker: { + dayjsLocale: 'ru', + title: 'Выберите время', + cancel: 'Отмена', + confirm: 'Подтвердить', + format: 'DD.MM.YYYY', + months: [ + 'Январь', + 'Февраль', + 'Март', + 'Апрель', + 'Май', + 'Июнь', + 'Июль', + 'Август', + 'Сентябрь', + 'Октябрь', + 'Ноябрь', + 'Декабрь', + ], + yearLabel: 'Год', + monthLabel: 'Месяц', + dateLabel: 'День', + hourLabel: 'Час', + minuteLabel: 'Минута', + secondLabel: 'Секунда', + }, + form: { + errorMessage: { + date: 'Введите правильный ${name}', + url: 'Введите правильный ${name}', + whitespace: '${name} не может быть пустым', + required: '${name} обязательно для заполнения', + max: 'Длина символов ${name} не должна превышать ${validate} символов', + min: 'Длина символов ${name} не должна быть меньше ${validate} символов', + len: 'Длина символов ${name} должна быть ${validate}', + enum: '${name} может быть только ${validate} и т.д.', + idcard: 'Введите правильный ${name}', + telnumber: 'Введите правильный ${name}', + pattern: 'Введите правильный ${name}', + validator: '${name} не соответствует требованиям', + boolean: 'Тип данных ${name} должен быть булевым', + number: '${name} должно быть числом', + }, + colonText: ':', + }, + picker: { + cancel: 'Отмена', + confirm: 'Подтвердить', + }, + pullDownRefresh: { + loadingTexts: ['Потяните вниз для обновления', 'Отпустите для обновления', 'Обновление...', 'Обновление завершено'], + }, + rate: { + texts: ['Плохо', 'Обидно', 'Нормально', 'Хорошо', 'Отлично'], + valueText: '{value} баллов', + noValueText: 'Без оценки', + }, + tabBar: { + newsAriaLabel: 'Есть новые сообщения', + moreNewsAriaLabel: 'Есть много новых сообщений', + haveMoreNewsAriaLabel: 'Есть {value}+ сообщений', + haveNewsAriaLabel: 'Есть {value} сообщений', + }, + table: { + empty: 'Нет данных', + }, + list: { + loading: 'Загрузка...', + loadingMoreText: 'Нажмите, чтобы загрузить больше', + pulling: 'Потяните, чтобы обновить...', + loosing: 'Отпустите, чтобы обновить...', + success: 'Успешно обновлено', + }, + upload: { + progress: { + uploadingText: 'Загрузка...', + waitingText: 'Ожидание загрузки', + failText: 'Ошибка загрузки', + successText: 'Загрузка завершена', + }, + }, + guide: { + next: 'Далее', + skip: 'Пропустить', + finish: 'Готово', + back: 'Назад', + }, + qrcode: { + expiredText: 'истекший', + refreshText: 'обновить', + scannedText: 'сканированный', + }, + attachments: { + status: { + pending: 'Загрузка...', + fail: 'Ошибка загрузки', + }, + }, + chatActionbar: { + actionBar: { + replay: 'Обновить', + copy: 'Копировать', + good: 'Нравится', + bad: 'Не нравится', + share: 'Поделиться', + quote: 'Цитировать', + }, + }, + chatSender: { + placeholder: 'Введите сообщение...', + sendText: 'Отправить', + stopText: 'Остановить', + }, + chatThinking: { + status: { + pending: 'Думаю...', + complete: 'Завершил размышления', + stop: 'Размышления остановлены', + }, + }, +}; diff --git a/packages/components/locale/zh_CN.ts b/packages/components/locale/zh_CN.ts new file mode 100644 index 000000000..dc7e2001c --- /dev/null +++ b/packages/components/locale/zh_CN.ts @@ -0,0 +1,133 @@ +/* eslint-disable no-template-curly-in-string */ +// 文件有效,为国际化做准备 +import 'dayjs/locale/zh-cn'; + +export default { + actionSheet: { + cancel: '取消', + }, + calendar: { + title: '请选择日期', + confirm: '确认', + weekdays: ['日', '一', '二', '三', '四', '五', '六'], + monthTitle: '{year} 年 {month}', + months: ['1 月', '2 月', '3 月', '4 月', '5 月', '6 月', '7 月', '8 月', '9 月', '10 月', '11 月', '12 月'], + }, + cascader: { + title: '标题', + placeholder: '选择选项', + }, + dropdownMenu: { + reset: '重置', + confirm: '确定', + }, + dateTimePicker: { + dayjsLocale: 'zh-cn', + title: '选择时间', + cancel: '取消', + confirm: '确定', + format: 'YYYY-MM-DD', + months: ['1 月', '2 月', '3 月', '4 月', '5 月', '6 月', '7 月', '8 月', '9 月', '10 月', '11 月', '12 月'], + yearLabel: '年', + monthLabel: '月', + dateLabel: '日', + hourLabel: '时', + minuteLabel: '分', + secondLabel: '秒', + }, + form: { + errorMessage: { + date: '请输入正确的${name}', + url: '请输入正确的${name}', + required: '${name}必填', + whitespace: '${name}不能为空', + max: '${name}字符长度不能超过 ${validate} 个字符,一个中文等于两个字符', + min: '${name}字符长度不能少于 ${validate} 个字符,一个中文等于两个字符', + len: '${name}字符长度必须是 ${validate}', + enum: '${name}只能是${validate}等', + idcard: '请输入正确的${name}', + telnumber: '请输入正确的${name}', + pattern: '请输入正确的${name}', + validator: '${name}不符合要求', + boolean: '${name}数据类型必须是布尔类型', + number: '${name}必须是数字', + }, + colonText: ':', + }, + picker: { + cancel: '取消', + confirm: '确认', + }, + pullDownRefresh: { + loadingTexts: ['下拉刷新', '松手刷新', '正在刷新', '刷新完成'], + }, + rate: { + texts: ['极差', '失望', '一般', '满意', '惊喜'], + valueText: '{value} 分', + noValueText: '未评分', + }, + tabBar: { + newsAriaLabel: '有新的消息', + moreNewsAriaLabel: '有很多消息', + haveMoreNewsAriaLabel: '有 {value}+ 条消息', + haveNewsAriaLabel: '有 {value} 条消息', + }, + table: { + empty: '暂无数据', + }, + list: { + loading: '加载中...', + loadingMoreText: '点击加载更多', + pulling: '下拉即可刷新...', + loosing: '释放即可刷新...', + success: '刷新成功', + }, + upload: { + progress: { + uploadingText: '上传中...', + waitingText: '待上传', + failText: '上传失败', + successText: '上传成功', + reloadText: '重新上传', + }, + }, + guide: { + next: '下一步', + skip: '跳过', + finish: '完成', + back: '返回', + }, + qrcode: { + expiredText: '二维码过期', + refreshText: '点击刷新', + scannedText: '已扫描', + }, + attachments: { + status: { + pending: '上传中...', + fail: '上传失败', + }, + }, + chatActionbar: { + actionBar: { + replay: '刷新', + copy: '复制', + good: '点赞', + bad: '点踩', + share: '分享', + quote: '引用', + }, + }, + chatSender: { + placeholder: '请输入消息...', + sendText: '发送', + stopText: '停止', + }, + chatThinking: { + status: { + pending: '正在思考中...', + complete: '已完成思考', + stop: '已停止思考', + }, + }, +}; diff --git a/packages/components/locale/zh_TW.ts b/packages/components/locale/zh_TW.ts new file mode 100644 index 000000000..62c0989d5 --- /dev/null +++ b/packages/components/locale/zh_TW.ts @@ -0,0 +1,132 @@ +/* eslint-disable no-template-curly-in-string */ +// 文件有效,为国际化做准备 +import 'dayjs/locale/zh-tw'; + +export default { + actionSheet: { + cancel: '取消', + }, + calendar: { + title: '請選擇日期', + confirm: '確認', + weekdays: ['日', '一', '二', '三', '四', '五', '六'], + monthTitle: '{year} 年 {month}', + months: ['1 月', '2 月', '3 月', '4 月', '5 月', '6 月', '7 月', '8 月', '9 月', '10 月', '11 月', '12 月'], + }, + cascader: { + title: '標題', + placeholder: '選擇選項', + }, + dropdownMenu: { + reset: '重置', + confirm: '確定', + }, + dateTimePicker: { + dayjsLocale: 'zh-tw', + title: '選擇時間', + cancel: '取消', + confirm: '確定', + format: 'YYYY-MM-DD', + months: ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月'], + yearLabel: '年', + monthLabel: '月', + dateLabel: '日', + hourLabel: '時', + minuteLabel: '分', + secondLabel: '秒', + }, + form: { + errorMessage: { + date: '請輸入正確的${name}', + url: '請輸入正確的${name}', + whitespace: '${name}不能為空', + required: '${name}必填', + max: '${name}字符長度不能超過 ${validate} 個字符,一個中文等於兩個字符', + min: '${name}字符長度不能少於 ${validate} 個字符,一個中文等於兩個字符', + len: '${name}字符長度必須是 ${validate}', + enum: '${name}只能是${validate}等', + idcard: '請輸入正確的${name}', + telnumber: '請輸入正確的${name}', + pattern: '請輸入正確的${name}', + validator: '${name}不符合要求', + boolean: '${name}數據類型必須是布林類型', + number: '${name}必須是數字', + }, + colonText: ':', + }, + picker: { + cancel: '取消', + confirm: '確認', + }, + pullDownRefresh: { + loadingTexts: ['下拉刷新', '鬆手刷新', '正在刷新', '刷新完成'], + }, + rate: { + texts: ['極差', '失望', '一般', '滿意', '驚喜'], + valueText: '{value} 分', + noValueText: '未評分', + }, + tabBar: { + newsAriaLabel: '有新消息', + moreNewsAriaLabel: '有很多消息', + haveMoreNewsAriaLabel: '有 {value}+ 條消息', + haveNewsAriaLabel: '有 {value} 條消息', + }, + table: { + empty: '暫無數據', + }, + list: { + loading: '加載中...', + loadingMoreText: '點擊加載更多', + pulling: '下拉即可刷新...', + loosing: '釋放即可刷新...', + success: '刷新成功', + }, + upload: { + progress: { + uploadingText: '上傳中...', + waitingText: '待上傳', + failText: '上傳失敗', + successText: '上傳成功', + }, + }, + guide: { + next: '下一步', + skip: '跳過', + finish: '完成', + back: '返回', + }, + qrcode: { + expiredText: '二維碼過期', + refreshText: '點擊刷新', + scannedText: '已掃描', + }, + attachments: { + status: { + pending: '上傳中...', + fail: '上傳失敗', + }, + }, + chatActionbar: { + actionBar: { + replay: '刷新', + copy: '複製', + good: '點讚', + bad: '點踩', + share: '分享', + quote: '引用', + }, + }, + chatSender: { + placeholder: '請輸入消息...', + sendText: '發送', + stopText: '停止', + }, + chatThinking: { + status: { + pending: '正在思考中...', + complete: '已完成思考', + stop: '已停止思考', + }, + }, +}; diff --git a/packages/components/message/README.md b/packages/components/message/README.md index b4fecef83..7fa89a4ce 100644 --- a/packages/components/message/README.md +++ b/packages/components/message/README.md @@ -5,7 +5,7 @@ spline: message isComponent: true --- - + ## 引入 全局引入,在 miniprogram 根目录下的`app.json`中配置,局部引入,在需要引入的页面或组件的`index.json`中配置。 diff --git a/packages/components/mixins/using-config.ts b/packages/components/mixins/using-config.ts new file mode 100644 index 000000000..076fa6f92 --- /dev/null +++ b/packages/components/mixins/using-config.ts @@ -0,0 +1,61 @@ +/** + * 提供子组件从 config-provider 获取全局配置的能力 + */ + +import { getComponentLocale, useConfig } from '../config-provider/use-config'; +import { toCamel } from '../common/utils'; +import defaultLocale from '../locale/zh_CN'; + +export interface UsingConfigBehaviorOptions { + /** 组件在 locale 中的 key(如 'picker'、'calendar' 等) */ + componentName: string; + /** 组件自定义语言文本的属性名(如 'localeText') */ + localeTextPropName?: string; +} + +export default function usingConfig(options: UsingConfigBehaviorOptions) { + const { componentName, localeTextPropName } = options; + const _componentName = toCamel(componentName); + + return Behavior({ + data: { + globalConfig: {}, + }, + + lifetimes: { + attached() { + this.updateLocale?.(); + + // 订阅全局 locale 变化 + const configHook = useConfig(_componentName); + this._unsubscribeLocale = configHook.subscribeLocale(this, () => { + this.updateLocale?.(); + }); + }, + + detached() { + // 清理订阅 + const unsubscribe = this._unsubscribeLocale; + if (unsubscribe) { + unsubscribe(); + this._unsubscribeLocale = null; + } + }, + }, + + methods: { + /** + * 更新语言包 + * 子组件可以覆盖此方法以自定义更新逻辑 + */ + updateLocale() { + const componentDefaultLocale = defaultLocale[_componentName] || {}; + const globalConfig = getComponentLocale(this, _componentName, componentDefaultLocale, localeTextPropName); + + this.setData({ + globalConfig, + }); + }, + }, + }); +} diff --git a/packages/components/navbar/README.md b/packages/components/navbar/README.md index 9713d5481..2e64337bb 100644 --- a/packages/components/navbar/README.md +++ b/packages/components/navbar/README.md @@ -5,7 +5,7 @@ spline: navigation isComponent: true --- - + ## 引入 全局引入,在 miniprogram 根目录下的`app.json`中配置,局部引入,在需要引入的页面或组件的`index.json`中配置。 diff --git a/packages/components/notice-bar/README.md b/packages/components/notice-bar/README.md index b72aea1f8..9ed99dc0e 100644 --- a/packages/components/notice-bar/README.md +++ b/packages/components/notice-bar/README.md @@ -13,7 +13,7 @@ isComponent: true 该组件于 0.9.0 版本上线,请留意版本。 - + ## 引入 全局引入,在 miniprogram 根目录下的`app.json`中配置,局部引入,在需要引入的页面或组件的`index.json`中配置。 diff --git a/packages/components/overlay/README.md b/packages/components/overlay/README.md index 9117a292a..c459bbae2 100644 --- a/packages/components/overlay/README.md +++ b/packages/components/overlay/README.md @@ -13,7 +13,7 @@ isComponent: true 该组件于 0.10.0 版本上线,请留意版本。 - + ## 引入 全局引入,在 miniprogram 根目录下的`app.json`中配置,局部引入,在需要引入的页面或组件的`index.json`中配置。 diff --git a/packages/components/picker/README.md b/packages/components/picker/README.md index d2ff999ca..6ad138312 100644 --- a/packages/components/picker/README.md +++ b/packages/components/picker/README.md @@ -5,7 +5,7 @@ spline: form isComponent: true --- - + ## 引入 全局引入,在 miniprogram 根目录下的`app.json`中配置,局部引入,在需要引入的页面或组件的`index.json`中配置。 diff --git a/packages/components/picker/picker.ts b/packages/components/picker/picker.ts index f4ddb9dd3..6a030adaf 100644 --- a/packages/components/picker/picker.ts +++ b/packages/components/picker/picker.ts @@ -2,15 +2,16 @@ import { SuperComponent, wxComponent, RelationsOptions } from '../common/src/ind import config from '../common/config'; import props from './props'; import useCustomNavbar from '../mixins/using-custom-navbar'; +import usingConfig from '../mixins/using-config'; const { prefix } = config; -const name = `${prefix}-picker`; +const componentName = 'picker'; const DEFAULT_KEYS = { value: 'value', label: 'label', icon: 'icon' }; @wxComponent() export default class Picker extends SuperComponent { - behaviors = [useCustomNavbar]; + behaviors = [useCustomNavbar, usingConfig({ componentName })]; properties = props; @@ -46,7 +47,7 @@ export default class Picker extends SuperComponent { data = { prefix, - classPrefix: name, + classPrefix: `${prefix}-${componentName}`, defaultPopUpProps: {}, defaultPopUpzIndex: 11500, indicatorTop: 72, // 默认indicator位置,会动态计算 diff --git a/packages/components/picker/template.wxml b/packages/components/picker/template.wxml index b294acd19..43c678222 100644 --- a/packages/components/picker/template.wxml +++ b/packages/components/picker/template.wxml @@ -3,11 +3,11 @@ {{cancelBtn}}{{globalConfig.cancel}} {{title}} {{confirmBtn}}{{globalConfig.confirm}}
diff --git a/packages/components/popover/README.md b/packages/components/popover/README.md index c41e20c4e..eac6585e0 100644 --- a/packages/components/popover/README.md +++ b/packages/components/popover/README.md @@ -6,6 +6,7 @@ isComponent: true --- + ## 引入 全局引入,在 miniprogram 根目录下的`app.json`中配置,局部引入,在需要引入的页面或组件的`index.json`中配置。 diff --git a/packages/components/popup/README.md b/packages/components/popup/README.md index a74ac634f..ca6617e16 100644 --- a/packages/components/popup/README.md +++ b/packages/components/popup/README.md @@ -6,7 +6,7 @@ isComponent: true --- - + ## 引入 全局引入,在 miniprogram 根目录下的`app.json`中配置,局部引入,在需要引入的页面或组件的`index.json`中配置。 diff --git a/packages/components/progress/README.md b/packages/components/progress/README.md index f91dedd0e..b0708cdd5 100644 --- a/packages/components/progress/README.md +++ b/packages/components/progress/README.md @@ -13,7 +13,7 @@ isComponent: true 该组件于 0.7.3 版本上线,请留意版本。 - + ## 引入 ### 引入组件 diff --git a/packages/components/pull-down-refresh/README.md b/packages/components/pull-down-refresh/README.md index 818e3390d..deeba51fc 100644 --- a/packages/components/pull-down-refresh/README.md +++ b/packages/components/pull-down-refresh/README.md @@ -6,7 +6,7 @@ isComponent: true --- - + ## 引入 全局引入,在 miniprogram 根目录下的`app.json`中配置,局部引入,在需要引入的页面或组件的`index.json`中配置。 diff --git a/packages/components/pull-down-refresh/pull-down-refresh.ts b/packages/components/pull-down-refresh/pull-down-refresh.ts index 746efe3de..b186bc7e3 100644 --- a/packages/components/pull-down-refresh/pull-down-refresh.ts +++ b/packages/components/pull-down-refresh/pull-down-refresh.ts @@ -4,10 +4,10 @@ import config from '../common/config'; import props from './props'; import { getRect, systemInfo, unitConvert } from '../common/utils'; import { canUseProxyScrollView } from '../common/version'; +import usingConfig from '../mixins/using-config'; const { prefix } = config; -const name = `${prefix}-pull-down-refresh`; -const defaultLoadingTexts = ['下拉刷新', '松手刷新', '正在刷新', '刷新完成']; +const componentName = 'pull-down-refresh'; @wxComponent() export default class PullDownRefresh extends SuperComponent { @@ -28,7 +28,9 @@ export default class PullDownRefresh extends SuperComponent { externalClasses = [`${prefix}-class`, `${prefix}-class-loading`, `${prefix}-class-text`, `${prefix}-class-indicator`]; - behaviors = canUseProxyScrollView() ? ['wx://proxy-scroll-view'] : []; + behaviors = canUseProxyScrollView() + ? ['wx://proxy-scroll-view', usingConfig({ componentName })] + : [usingConfig({ componentName })]; options = { multipleSlots: true, @@ -45,7 +47,7 @@ export default class PullDownRefresh extends SuperComponent { data = { prefix, - classPrefix: name, + classPrefix: `${prefix}-${componentName}`, distanceTop: 0, barHeight: 0, tipsHeight: 0, @@ -60,13 +62,11 @@ export default class PullDownRefresh extends SuperComponent { lifetimes = { attached() { const { screenWidth } = systemInfo; - const { loadingTexts, maxBarHeight, loadingBarHeight } = this.properties; - const isCustomLoadingTexts = Array.isArray(loadingTexts) && loadingTexts.length >= 4; + const { maxBarHeight, loadingBarHeight } = this.properties; this.setData({ _maxBarHeight: unitConvert(maxBarHeight), _loadingBarHeight: unitConvert(loadingBarHeight), - loadingTexts: isCustomLoadingTexts ? loadingTexts : defaultLoadingTexts, }); this.pixelRatio = 750 / screenWidth; @@ -113,23 +113,28 @@ export default class PullDownRefresh extends SuperComponent { loadingBarHeight(v) { this.setData({ _loadingBarHeight: unitConvert(v) }); }, + + globalConfig() { + this.updateLoadingTexts(); + }, }; methods = { updateDistanceTop() { + const { classPrefix } = this.data; const update = (top: number) => { this.setData({ distanceTop: top, }); }; - getRect(this, `.${name}`).then((rect) => { + getRect(this, `.${classPrefix}`).then((rect) => { if (rect.top) { update(rect.top); return; } - getObserver(this, `.${name}`).then((res) => { + getObserver(this, `.${classPrefix}`).then((res) => { if (res.intersectionRatio > 0) { update(res.boundingClientRect.top); } @@ -137,6 +142,16 @@ export default class PullDownRefresh extends SuperComponent { }); }, + updateLoadingTexts() { + const { loadingTexts } = this.properties; + const { globalConfig } = this.data; + const isCustomLoadingTexts = Array.isArray(loadingTexts) && loadingTexts.length >= 4; + + this.setData({ + realLoadingTexts: isCustomLoadingTexts ? loadingTexts : globalConfig.loadingTexts, + }); + }, + resetTimer() { if (this.refreshStatusTimer) { clearTimeout(this.refreshStatusTimer); diff --git a/packages/components/pull-down-refresh/pull-down-refresh.wxml b/packages/components/pull-down-refresh/pull-down-refresh.wxml index d2bf1fce0..efdbf2527 100644 --- a/packages/components/pull-down-refresh/pull-down-refresh.wxml +++ b/packages/components/pull-down-refresh/pull-down-refresh.wxml @@ -48,13 +48,13 @@ progress="{{loadingProps.progress || 0}}" reverse="{{loadingProps.reverse || false}}" size="{{loadingProps.size || '50rpx'}}" - text="{{loadingProps.text || loadingTexts[refreshStatus]}}" + text="{{loadingProps.text || realLoadingTexts[refreshStatus]}}" theme="{{loadingProps.theme || 'circular'}}" t-class="{{prefix}}-class-loading" t-class-indicator="{{prefix}}-class-indicator" /> {{loadingTexts[refreshStatus]}}{{realLoadingTexts[refreshStatus]}}
diff --git a/packages/components/qrcode/README.md b/packages/components/qrcode/README.md index 48abff631..84d645191 100644 --- a/packages/components/qrcode/README.md +++ b/packages/components/qrcode/README.md @@ -5,7 +5,7 @@ spline: message isComponent: true --- - + ## 引入 全局引入,在 miniprogram 根目录下的`app.json`中配置,局部引入,在需要引入的页面或组件的`index.json`中配置。 diff --git a/packages/components/qrcode/qrcode.ts b/packages/components/qrcode/qrcode.ts index 1bbd3be88..be9f2a9f3 100644 --- a/packages/components/qrcode/qrcode.ts +++ b/packages/components/qrcode/qrcode.ts @@ -1,17 +1,19 @@ import props from './props'; import config from '../common/config'; import { SuperComponent, wxComponent } from '../common/src/index'; +import usingConfig from '../mixins/using-config'; const { prefix } = config; -const name = `${prefix}-qrcode`; +const componentName = 'qrcode'; @wxComponent() export default class QRCode extends SuperComponent { + behaviors = [usingConfig({ componentName })]; + externalClasses = [`${prefix}-class`, `${prefix}-class-canvas`]; options = { multipleSlots: true, - virtualHost: true, }; properties = { @@ -20,20 +22,12 @@ export default class QRCode extends SuperComponent { type: Boolean, value: false, }, - style: { - type: String, - value: '', - }, - customStyle: { - type: String, - value: '', - }, }; data = { prefix, showMask: false, - classPrefix: name, + classPrefix: `${prefix}-${componentName}`, canvasReady: false, }; diff --git a/packages/components/qrcode/qrcode.wxml b/packages/components/qrcode/qrcode.wxml index b90b3a528..3b2990670 100644 --- a/packages/components/qrcode/qrcode.wxml +++ b/packages/components/qrcode/qrcode.wxml @@ -21,7 +21,12 @@ - + diff --git a/packages/components/radio/README.md b/packages/components/radio/README.md index 5397e4173..2e9c6bc7d 100644 --- a/packages/components/radio/README.md +++ b/packages/components/radio/README.md @@ -6,7 +6,7 @@ isComponent: true --- - + ## 引入 全局引入,在 miniprogram 根目录下的`app.json`中配置,局部引入,在需要引入的页面或组件的`index.json`中配置。 diff --git a/packages/components/rate/README.md b/packages/components/rate/README.md index 64b14649b..6e224e164 100644 --- a/packages/components/rate/README.md +++ b/packages/components/rate/README.md @@ -5,7 +5,7 @@ spline: form isComponent: true --- - + ## 引入 全局引入,在 miniprogram 根目录下的`app.json`中配置,局部引入,在需要引入的页面或组件的`index.json`中配置。 diff --git a/packages/components/rate/__test__/index.test.js b/packages/components/rate/__test__/index.test.js index 2d5aa88e7..f05034a49 100644 --- a/packages/components/rate/__test__/index.test.js +++ b/packages/components/rate/__test__/index.test.js @@ -102,22 +102,23 @@ describe('Rate', () => { const box = comp.querySelector('.box'); const text = box.querySelector('.t-rate__text'); + const globalConfig = ['极差', '失望', '一般', '满意', '惊喜']; expect(text.dom.textContent).toBe('未评分'); comp.setData({ value: 1 }); - expect(text.dom.textContent).toBe(box.data.defaultTexts[0]); + expect(text.dom.textContent).toBe(globalConfig[0]); comp.setData({ value: 2 }); - expect(text.dom.textContent).toBe(box.data.defaultTexts[1]); + expect(text.dom.textContent).toBe(globalConfig[1]); comp.setData({ value: 3 }); - expect(text.dom.textContent).toBe(box.data.defaultTexts[2]); + expect(text.dom.textContent).toBe(globalConfig[2]); comp.setData({ value: 4 }); - expect(text.dom.textContent).toBe(box.data.defaultTexts[3]); + expect(text.dom.textContent).toBe(globalConfig[3]); comp.setData({ value: 5 }); - expect(text.dom.textContent).toBe(box.data.defaultTexts[4]); + expect(text.dom.textContent).toBe(globalConfig[4]); }); it(':show-text custom texts', async () => { diff --git a/packages/components/rate/rate.ts b/packages/components/rate/rate.ts index 8e847044d..0e4d93db7 100644 --- a/packages/components/rate/rate.ts +++ b/packages/components/rate/rate.ts @@ -2,12 +2,15 @@ import { SuperComponent, wxComponent } from '../common/src/index'; import config from '../common/config'; import props from './props'; import { unitConvert, getRect } from '../common/utils'; +import usingConfig from '../mixins/using-config'; const { prefix } = config; -const name = `${prefix}-rate`; +const componentName = 'rate'; @wxComponent() export default class Rate extends SuperComponent { + behaviors = [usingConfig({ componentName })]; + externalClasses = [`${prefix}-class`, `${prefix}-class-icon`, `${prefix}-class-text`]; properties = props; @@ -21,8 +24,7 @@ export default class Rate extends SuperComponent { data = { prefix, - classPrefix: name, - defaultTexts: ['极差', '失望', '一般', '满意', '惊喜'], + classPrefix: `${prefix}-${componentName}`, tipsVisible: false, tipsLeft: 0, actionType: '', @@ -32,10 +34,11 @@ export default class Rate extends SuperComponent { methods = { onTouch(e: WechatMiniprogram.TouchEvent, eventType: 'tap' | 'move') { + const { classPrefix } = this.data; const { count, allowHalf, gap, value: currentValue, size } = this.properties; const [touch] = e.changedTouches; const margin = unitConvert(gap); - getRect(this, `.${name}__wrapper`).then((rect) => { + getRect(this, `.${classPrefix}__wrapper`).then((rect) => { const { width, left } = rect; const starWidth = (width - (count - 1) * margin) / count; const offsetX = touch.pageX - left; diff --git a/packages/components/rate/rate.wxml b/packages/components/rate/rate.wxml index a31cd1c8c..1d38ac762 100644 --- a/packages/components/rate/rate.wxml +++ b/packages/components/rate/rate.wxml @@ -14,7 +14,7 @@ aria-valuemax="{{count}}" aria-valuemin="{{0}}" aria-valuenow="{{value}}" - aria-valuetext="{{utils.getText(texts,value,defaultTexts)}}" + aria-valuetext="{{utils.getText(texts,value,globalConfig)}}" > {{utils.getText(texts,value,defaultTexts)}}{{utils.getText(texts,value,globalConfig)}} {{value+'星'}} {{utils.getText(texts,value,defaultTexts)}}{{value+'星'}} {{utils.getText(texts,value,globalConfig)}} - + ## 引入 全局引入,在 miniprogram 根目录下的`app.json`中配置,局部引入,在需要引入的页面或组件的`index.json`中配置。 diff --git a/packages/components/search/README.md b/packages/components/search/README.md index fc4da7204..62725bc44 100644 --- a/packages/components/search/README.md +++ b/packages/components/search/README.md @@ -6,7 +6,7 @@ isComponent: true --- - + ## 引入 全局引入,在 miniprogram 根目录下的`app.json`中配置,局部引入,在需要引入的页面或组件的`index.json`中配置。 diff --git a/packages/components/side-bar/README.md b/packages/components/side-bar/README.md index 7e102be70..3e7383ff7 100644 --- a/packages/components/side-bar/README.md +++ b/packages/components/side-bar/README.md @@ -12,7 +12,7 @@ isComponent: true 该组件于 0.25.0 版本上线,请留意版本。 - + ## 引入 全局引入,在 miniprogram 根目录下的`app.json`中配置,局部引入,在需要引入的页面或组件的`index.json`中配置。 diff --git a/packages/components/skeleton/README.md b/packages/components/skeleton/README.md index 3a5836909..a7f4789a6 100644 --- a/packages/components/skeleton/README.md +++ b/packages/components/skeleton/README.md @@ -5,7 +5,7 @@ spline: data isComponent: true --- - + ## 引入 全局引入,在 miniprogram 根目录下的`app.json`中配置,局部引入,在需要引入的页面或组件的`index.json`中配置。 diff --git a/packages/components/slider/README.md b/packages/components/slider/README.md index b15a031e1..ed0592cdb 100644 --- a/packages/components/slider/README.md +++ b/packages/components/slider/README.md @@ -5,7 +5,7 @@ spline: data isComponent: true --- - + ## 引入 全局引入,在 miniprogram 根目录下的`app.json`中配置,局部引入,在需要引入的页面或组件的`index.json`中配置。 diff --git a/packages/components/stepper/README.md b/packages/components/stepper/README.md index 1fd963cac..8ce347634 100644 --- a/packages/components/stepper/README.md +++ b/packages/components/stepper/README.md @@ -5,7 +5,7 @@ spline: form isComponent: true --- - + ## 引入 全局引入,在 miniprogram 根目录下的`app.json`中配置,局部引入,在需要引入的页面或组件的`index.json`中配置。 diff --git a/packages/components/steps/README.md b/packages/components/steps/README.md index 51361dc60..f652d979a 100644 --- a/packages/components/steps/README.md +++ b/packages/components/steps/README.md @@ -5,7 +5,7 @@ spline: navigation isComponent: true --- - + ## 引入 全局引入,在 miniprogram 根目录下的`app.json`中配置,局部引入,在需要引入的页面或组件的`index.json`中配置。 diff --git a/packages/components/sticky/README.md b/packages/components/sticky/README.md index 249e9056e..0679ef411 100644 --- a/packages/components/sticky/README.md +++ b/packages/components/sticky/README.md @@ -5,7 +5,7 @@ spline: data isComponent: true --- - + ## 引入 全局引入,在 miniprogram 根目录下的`app.json`中配置,局部引入,在需要引入的页面或组件的`index.json`中配置。 diff --git a/packages/components/swipe-cell/README.md b/packages/components/swipe-cell/README.md index 1c365906f..b9361276b 100644 --- a/packages/components/swipe-cell/README.md +++ b/packages/components/swipe-cell/README.md @@ -6,7 +6,7 @@ isComponent: true --- - + ## 引入 全局引入,在 miniprogram 根目录下的`app.json`中配置,局部引入,在需要引入的页面或组件的`index.json`中配置。 diff --git a/packages/components/swiper/README.md b/packages/components/swiper/README.md index 2c265b9e9..0671ed9b3 100644 --- a/packages/components/swiper/README.md +++ b/packages/components/swiper/README.md @@ -5,7 +5,7 @@ spline: message isComponent: true --- - + ## 引入 全局引入,在 miniprogram 根目录下的`app.json`中配置,局部引入,在需要引入的页面或组件的`index.json`中配置。 diff --git a/packages/components/switch/README.md b/packages/components/switch/README.md index e14933c00..c55f57fc1 100644 --- a/packages/components/switch/README.md +++ b/packages/components/switch/README.md @@ -5,7 +5,7 @@ spline: form isComponent: true --- - + ## 引入 全局引入,在 miniprogram 根目录下的`app.json`中配置,局部引入,在需要引入的页面或组件的`index.json`中配置。 diff --git a/packages/components/tab-bar/README.md b/packages/components/tab-bar/README.md index a55040536..bc3c0ca8d 100644 --- a/packages/components/tab-bar/README.md +++ b/packages/components/tab-bar/README.md @@ -5,7 +5,7 @@ spline: navigation isComponent: true --- - + ## 引入 全局引入,在 miniprogram 根目录下的`app.json`中配置,局部引入,在需要引入的页面或组件的`index.json`中配置。 diff --git a/packages/components/tabs/README.md b/packages/components/tabs/README.md index 373cf8d51..daf1ac8c2 100644 --- a/packages/components/tabs/README.md +++ b/packages/components/tabs/README.md @@ -5,7 +5,7 @@ spline: navigation isComponent: true --- - + ## 引入 全局引入,在 miniprogram 根目录下的`app.json`中配置,局部引入,在需要引入的页面或组件的`index.json`中配置。 diff --git a/packages/components/tag/README.md b/packages/components/tag/README.md index 5c4b20831..e90396703 100644 --- a/packages/components/tag/README.md +++ b/packages/components/tag/README.md @@ -5,7 +5,7 @@ spline: data isComponent: true --- - + ## 引入 全局引入,在 miniprogram 根目录下的`app.json`中配置,局部引入,在需要引入的页面或组件的`index.json`中配置。 diff --git a/packages/components/textarea/README.md b/packages/components/textarea/README.md index 470922fc8..38e522c51 100644 --- a/packages/components/textarea/README.md +++ b/packages/components/textarea/README.md @@ -6,7 +6,7 @@ isComponent: true --- - + ## 引入 全局引入,在 miniprogram 根目录下的`app.json`中配置,局部引入,在需要引入的页面或组件的`index.json`中配置。 diff --git a/packages/components/toast/README.md b/packages/components/toast/README.md index c43ad66a1..7da7620ea 100644 --- a/packages/components/toast/README.md +++ b/packages/components/toast/README.md @@ -5,7 +5,7 @@ spline: message isComponent: true --- - + ## 引入 全局引入,在 miniprogram 根目录下的`app.json`中配置,局部引入,在需要引入的页面或组件的`index.json`中配置。 diff --git a/packages/components/transition/README.md b/packages/components/transition/README.md index e2bff7ced..9c640af5a 100644 --- a/packages/components/transition/README.md +++ b/packages/components/transition/README.md @@ -5,7 +5,7 @@ spline: message isComponent: true --- - + ## 引入 ### 引入组件 diff --git a/packages/components/tree-select/README.md b/packages/components/tree-select/README.md index c705f26dd..cf6b92544 100644 --- a/packages/components/tree-select/README.md +++ b/packages/components/tree-select/README.md @@ -12,7 +12,7 @@ isComponent: true 该组件于 0.32.0 版本上线,请留意版本。 - + ## 引入 全局引入,在 miniprogram 根目录下的`app.json`中配置,局部引入,在需要引入的页面或组件的`index.json`中配置。 diff --git a/packages/components/upload/README.md b/packages/components/upload/README.md index 7e65dd0f4..d4a1e5ed9 100644 --- a/packages/components/upload/README.md +++ b/packages/components/upload/README.md @@ -5,7 +5,7 @@ spline: form isComponent: true --- - + ## 引入 全局引入,在 miniprogram 根目录下的`app.json`中配置,局部引入,在需要引入的页面或组件的`index.json`中配置。 diff --git a/packages/components/upload/upload.ts b/packages/components/upload/upload.ts index 368a1af2e..7f62befff 100644 --- a/packages/components/upload/upload.ts +++ b/packages/components/upload/upload.ts @@ -4,11 +4,15 @@ import { UploadFile, SizeLimitObj } from './type'; import config from '../common/config'; import { isOverSize, isWxWork, isPC } from '../common/utils'; import { isObject } from '../common/validator'; +import usingConfig from '../mixins/using-config'; const { prefix } = config; -const name = `${prefix}-upload`; +const componentName = 'upload'; + @wxComponent() export default class Upload extends SuperComponent { + behaviors = [usingConfig({ componentName })]; + externalClasses = [`${prefix}-class`]; options = { @@ -16,7 +20,7 @@ export default class Upload extends SuperComponent { }; data = { - classPrefix: name, + classPrefix: `${prefix}-${componentName}`, prefix, current: false, proofs: [], diff --git a/packages/components/upload/upload.wxml b/packages/components/upload/upload.wxml index 883351bb7..c219a79c9 100644 --- a/packages/components/upload/upload.wxml +++ b/packages/components/upload/upload.wxml @@ -64,11 +64,13 @@ > - {{file.percent ? file.percent + '%' : '上传中...'}} + {{file.percent ? file.percent + '%' : globalConfig.progress.uploadingText}} - {{file.status == 'reload' ? '重新上传' : '上传失败'}} + {{file.status == 'reload' ? globalConfig.progress.reloadText : globalConfig.progress.failText}} @@ -175,7 +177,7 @@ {{file.percent ? file.percent + '%' : '上传中...'}}{{file.percent ? file.percent + '%' : globalConfig.progress.uploadingText}}
- {{file.status == 'reload' ? '重新上传' : '上传失败'}} + {{file.status == 'reload' ? globalConfig.progress.reloadText : globalConfig.progress.failText}}
diff --git a/packages/pro-components/chat/attachments/README.md b/packages/pro-components/chat/attachments/README.md index 71da52b5b..55d0e154d 100644 --- a/packages/pro-components/chat/attachments/README.md +++ b/packages/pro-components/chat/attachments/README.md @@ -6,6 +6,7 @@ isComponent: true --- + ## 引入 全局引入,在 miniprogram 根目录下的`app.json`中配置,局部引入,在需要引入的页面或组件的`index.json`中配置。 diff --git a/packages/pro-components/chat/attachments/attachments.ts b/packages/pro-components/chat/attachments/attachments.ts index 2c78d7785..dc1545fe9 100644 --- a/packages/pro-components/chat/attachments/attachments.ts +++ b/packages/pro-components/chat/attachments/attachments.ts @@ -1,16 +1,19 @@ -import { SuperComponent, wxComponent, ComponentsOptionsType } from '../../../components/common/src/index'; +import { SuperComponent, wxComponent } from '../../../components/common/src/index'; import config from '../../../components/common/config'; import props from './props'; import { TdAttachmentsProps } from './type'; +import usingConfig from '../../../components/mixins/using-config'; const { prefix } = config; -const name = `${prefix}-attachments`; +const componentName = 'attachments'; export interface AttachmentsProps extends TdAttachmentsProps {} @wxComponent() export default class Attachments extends SuperComponent { - options: ComponentsOptionsType = { + behaviors = [usingConfig({ componentName })]; + + options = { multipleSlots: true, }; @@ -25,7 +28,7 @@ export default class Attachments extends SuperComponent { }; data = { - classPrefix: name, + classPrefix: `${prefix}-${componentName}`, files: [], }; diff --git a/packages/pro-components/chat/attachments/attachments.wxml b/packages/pro-components/chat/attachments/attachments.wxml index 0769446fe..b0f8abe11 100644 --- a/packages/pro-components/chat/attachments/attachments.wxml +++ b/packages/pro-components/chat/attachments/attachments.wxml @@ -56,9 +56,11 @@ {{item.name}} 上传中...{{item.progress || 0+"%"}}{{globalConfig.status['pending']}} {{item.progress || 0+"%"}}
+ {{globalConfig.status['fail']}} - 上传失败 {{item.errorMessage}} {{item.desc}} diff --git a/packages/pro-components/chat/chat-actionbar/README.md b/packages/pro-components/chat/chat-actionbar/README.md index 4c2f58267..f0543e26a 100644 --- a/packages/pro-components/chat/chat-actionbar/README.md +++ b/packages/pro-components/chat/chat-actionbar/README.md @@ -6,6 +6,7 @@ isComponent: true --- + ## 引入 全局引入,在 miniprogram 根目录下的`app.json`中配置,局部引入,在需要引入的页面或组件的`index.json`中配置。 diff --git a/packages/pro-components/chat/chat-actionbar/chat-actionbar.ts b/packages/pro-components/chat/chat-actionbar/chat-actionbar.ts index f2fe1ea8b..9bbce02c1 100644 --- a/packages/pro-components/chat/chat-actionbar/chat-actionbar.ts +++ b/packages/pro-components/chat/chat-actionbar/chat-actionbar.ts @@ -1,22 +1,24 @@ import { SuperComponent, wxComponent, ComponentsOptionsType } from '../../../components/common/src/index'; import config from '../../../components/common/config'; import props from './props'; +import usingConfig from '../../../components/mixins/using-config'; const { prefix } = config; -const name = `${prefix}-chat-actionbar`; +const componentName = 'chat-actionbar'; @wxComponent() export default class ChatActionbar extends SuperComponent { + behaviors = [usingConfig({ componentName })]; + options: ComponentsOptionsType = { multipleSlots: true, - styleIsolation: 'shared', }; properties = props; data = { actions: [], - classPrefix: name, + classPrefix: `${prefix}-${componentName}`, pComment: '', iconMap: { good: 'thumb-up', @@ -140,14 +142,7 @@ export default class ChatActionbar extends SuperComponent { }, setActions() { - const text = { - replay: '刷新', - copy: '复制', - good: '点赞', - bad: '点踩', - share: '分享', - quote: '引用', - }; + const { globalConfig } = this.data; const baseActions = []; let dataActions = []; @@ -161,13 +156,13 @@ export default class ChatActionbar extends SuperComponent { baseActions.push({ name: item, isActive: this.data.pComment === item, - text: text[item] || item, + text: globalConfig.actionBar[item] || item, }); } else { baseActions.push({ name: item, isActive: false, - text: text[item] || item, + text: globalConfig.actionBar[item] || item, }); } }); diff --git a/packages/pro-components/chat/chat-content/README.md b/packages/pro-components/chat/chat-content/README.md index 5cec4e373..7579dc2a9 100644 --- a/packages/pro-components/chat/chat-content/README.md +++ b/packages/pro-components/chat/chat-content/README.md @@ -5,6 +5,7 @@ spline: base isComponent: true --- + ## 引入 全局引入,在 miniprogram 根目录下的`app.json`中配置,局部引入,在需要引入的页面或组件的`index.json`中配置。 diff --git a/packages/pro-components/chat/chat-list/README.md b/packages/pro-components/chat/chat-list/README.md index a1d0b4c36..80ef21d2a 100644 --- a/packages/pro-components/chat/chat-list/README.md +++ b/packages/pro-components/chat/chat-list/README.md @@ -6,6 +6,7 @@ isComponent: true --- + ## 引入 全局引入,在 miniprogram 根目录下的`app.json`中配置,局部引入,在需要引入的页面或组件的`index.json`中配置。 diff --git a/packages/pro-components/chat/chat-loading/README.md b/packages/pro-components/chat/chat-loading/README.md index 1c8548d57..8ed8a91c7 100644 --- a/packages/pro-components/chat/chat-loading/README.md +++ b/packages/pro-components/chat/chat-loading/README.md @@ -5,6 +5,7 @@ spline: base isComponent: true --- + ## 引入 全局引入,在 miniprogram 根目录下的`app.json`中配置,局部引入,在需要引入的页面或组件的`index.json`中配置。 diff --git a/packages/pro-components/chat/chat-markdown/README.md b/packages/pro-components/chat/chat-markdown/README.md index c2619ff11..f03ffbf59 100644 --- a/packages/pro-components/chat/chat-markdown/README.md +++ b/packages/pro-components/chat/chat-markdown/README.md @@ -5,6 +5,7 @@ spline: base isComponent: true --- + ## 引入 全局引入,在 miniprogram 根目录下的`app.json`中配置,局部引入,在需要引入的页面或组件的`index.json`中配置。 diff --git a/packages/pro-components/chat/chat-message/README.md b/packages/pro-components/chat/chat-message/README.md index efd35fa74..3e1ce4966 100644 --- a/packages/pro-components/chat/chat-message/README.md +++ b/packages/pro-components/chat/chat-message/README.md @@ -5,6 +5,7 @@ spline: base isComponent: true --- + ## 引入 全局引入,在 miniprogram 根目录下的`app.json`中配置,局部引入,在需要引入的页面或组件的`index.json`中配置。 diff --git a/packages/pro-components/chat/chat-sender/README.en-US.md b/packages/pro-components/chat/chat-sender/README.en-US.md index 9257205cb..3f43670a6 100644 --- a/packages/pro-components/chat/chat-sender/README.en-US.md +++ b/packages/pro-components/chat/chat-sender/README.en-US.md @@ -14,7 +14,7 @@ auto-rise-with-keyboard | Boolean | false | \- | N disabled | Boolean | false | \- | N file-list | Array | [] | Typescript: `FileItem[]` | N loading | Boolean | false | \- | N -placeholder | String | 请输入消息... | \- | N +placeholder | String | - | \- | N render-presets | Array | [{name: 'upload', presets: ['uploadCamera', 'uploadImage', 'uploadAttachment'], status: ''},{ name: 'send', type: 'icon'}] | Typescript: `ChatActionButtons` `type ChatActionButtons = Array` `type ChatActionButton = UploadButton \| SendButton` `interface UploadButton { name: 'upload'; presets: string[]; status?: string; }` `interface SendButton { name: 'send'; type: 'icon' \| 'text';}`。[see more ts definition](https://github.com/Tencent/tdesign-miniprogram/blob/develop/packages/pro-components/chat/chat-sender/type.ts) | N textarea-props | Boolean / Object | { autosize: { maxHeight: 264, minHeight: 48 } } | \- | N value | String | - | input value | N diff --git a/packages/pro-components/chat/chat-sender/README.md b/packages/pro-components/chat/chat-sender/README.md index 46893c51e..222e3ef41 100644 --- a/packages/pro-components/chat/chat-sender/README.md +++ b/packages/pro-components/chat/chat-sender/README.md @@ -6,6 +6,7 @@ isComponent: true --- + ## 引入 全局引入,在 miniprogram 根目录下的`app.json`中配置,局部引入,在需要引入的页面或组件的`index.json`中配置。 @@ -61,7 +62,7 @@ auto-rise-with-keyboard | Boolean | false | 键盘弹起时自动顶起来输入 disabled | Boolean | false | 是否禁用输入框 | N file-list | Array | [] | 附件文件列表。TS 类型:`FileItem[]` | N loading | Boolean | false | 发送按钮是否处于加载状态 | N -placeholder | String | 请输入消息... | 输入框默认文案 | N +placeholder | String | - | 输入框默认文案。组件内置默认值为:'请输入消息...' | N render-presets | Array | [{name: 'upload', presets: ['uploadCamera', 'uploadImage', 'uploadAttachment'], status: ''},{ name: 'send', type: 'icon'}] | 预设发送区渲染配置,用于灵活配置发送区的上传入口和发送按钮,支持自定义类型、顺序、样式。TS 类型:`ChatActionButtons` `type ChatActionButtons = Array` `type ChatActionButton = UploadButton \| SendButton` `interface UploadButton { name: 'upload'; presets: string[]; status?: string; }` `interface SendButton { name: 'send'; type: 'icon' \| 'text';}`。[详细类型定义](https://github.com/Tencent/tdesign-miniprogram/blob/develop/packages/pro-components/chat/chat-sender/type.ts) | N textarea-props | Boolean / Object | { autosize: { maxHeight: 264, minHeight: 48 } } | 透传给 Textarea 组件的属性,autosize数值单位为 rpx | N value | String | - | 输入框的值 | N diff --git a/packages/pro-components/chat/chat-sender/chat-sender.ts b/packages/pro-components/chat/chat-sender/chat-sender.ts index 391be0457..46aea837d 100644 --- a/packages/pro-components/chat/chat-sender/chat-sender.ts +++ b/packages/pro-components/chat/chat-sender/chat-sender.ts @@ -1,12 +1,15 @@ import { SuperComponent, wxComponent } from '../../../components/common/src/index'; import config from '../../../components/common/config'; import props from './props'; +import usingConfig from '../../../components/mixins/using-config'; const { prefix } = config; -const name = `${prefix}-chat-sender`; +const componentName = 'chat-sender'; @wxComponent() export default class ChatSender extends SuperComponent { + behaviors = [usingConfig({ componentName })]; + options = { multipleSlots: true, }; @@ -14,7 +17,7 @@ export default class ChatSender extends SuperComponent { properties = props; data = { - classPrefix: name, + classPrefix: `${prefix}-${componentName}`, scrollViewTop: 0, focusFlag: false, isSending: false, diff --git a/packages/pro-components/chat/chat-sender/chat-sender.wxml b/packages/pro-components/chat/chat-sender/chat-sender.wxml index 61c7a18ff..260ffec82 100644 --- a/packages/pro-components/chat/chat-sender/chat-sender.wxml +++ b/packages/pro-components/chat/chat-sender/chat-sender.wxml @@ -50,7 +50,9 @@ bindconfirm="handleSendClick" > - {{placeholder}} + {{placeholder || globalConfig.placeholder}} @@ -78,7 +80,7 @@ class="send-btn-{{item.type}} {{value || loading ? 'active' : 'disabled'}}" bindtap="handleSendClick" > - {{loading ? '停止' : '发送'}} + {{loading ? globalConfig.sendText : globalConfig.stopText}} diff --git a/packages/pro-components/chat/chat-sender/props.ts b/packages/pro-components/chat/chat-sender/props.ts index f7da99914..a07506260 100644 --- a/packages/pro-components/chat/chat-sender/props.ts +++ b/packages/pro-components/chat/chat-sender/props.ts @@ -35,10 +35,10 @@ const props: TdChatSenderProps = { type: Boolean, value: false, }, - /** 输入框默认文案 */ + /** 输入框默认文案。组件内置默认值为:'请输入消息...' */ placeholder: { type: String, - value: '请输入消息...', + value: '', }, /** 预设发送区渲染配置,用于灵活配置发送区的上传入口和发送按钮,支持自定义类型、顺序、样式 */ renderPresets: { diff --git a/packages/pro-components/chat/chat-sender/type.ts b/packages/pro-components/chat/chat-sender/type.ts index 2f5dc0386..1381c1911 100644 --- a/packages/pro-components/chat/chat-sender/type.ts +++ b/packages/pro-components/chat/chat-sender/type.ts @@ -55,8 +55,8 @@ export interface TdChatSenderProps { value?: boolean; }; /** - * 输入框默认文案 - * @default 请输入消息... + * 输入框默认文案。组件内置默认值为:'请输入消息...' + * @default '' */ placeholder?: { type: StringConstructor; diff --git a/packages/pro-components/chat/chat-thinking/README.md b/packages/pro-components/chat/chat-thinking/README.md index fb130713c..836dd71a0 100644 --- a/packages/pro-components/chat/chat-thinking/README.md +++ b/packages/pro-components/chat/chat-thinking/README.md @@ -6,6 +6,7 @@ isComponent: true --- + ## 引入 全局引入,在 miniprogram 根目录下的`app.json`中配置,局部引入,在需要引入的页面或组件的`index.json`中配置。 diff --git a/packages/pro-components/chat/chat-thinking/chat-thinking.ts b/packages/pro-components/chat/chat-thinking/chat-thinking.ts index e0e77185b..f794cb814 100644 --- a/packages/pro-components/chat/chat-thinking/chat-thinking.ts +++ b/packages/pro-components/chat/chat-thinking/chat-thinking.ts @@ -1,12 +1,15 @@ import { SuperComponent, wxComponent } from '../../../components/common/src/index'; import config from '../../../components/common/config'; import props from './props'; +import usingConfig from '../../../components/mixins/using-config'; const { prefix } = config; -const name = `${prefix}-chat-thinking`; +const componentName = 'chat-thinking'; @wxComponent() export default class ChatThinking extends SuperComponent { + behaviors = [usingConfig({ componentName })]; + options = { multipleSlots: true, }; @@ -16,7 +19,7 @@ export default class ChatThinking extends SuperComponent { data = { localCollapsed: false, contentStyle: '', - classPrefix: name, + classPrefix: `${prefix}-${componentName}`, }; observers = { diff --git a/packages/pro-components/chat/chat-thinking/chat-thinking.wxml b/packages/pro-components/chat/chat-thinking/chat-thinking.wxml index 4017dc232..a901d45a3 100644 --- a/packages/pro-components/chat/chat-thinking/chat-thinking.wxml +++ b/packages/pro-components/chat/chat-thinking/chat-thinking.wxml @@ -12,7 +12,7 @@ - {{content.title || "正在思考中..."}} + {{content.title || globalConfig.status[status]}} import('@/config-provider/README.md'), + componentEn: () => import('@/config-provider/README.en-US.md'), + }, { title: '深色模式', titleEn: 'Dark Mode', diff --git a/packages/tdesign-miniprogram/test/jest.config.js b/packages/tdesign-miniprogram/test/jest.config.js index 83f84062b..fb98a8a5d 100644 --- a/packages/tdesign-miniprogram/test/jest.config.js +++ b/packages/tdesign-miniprogram/test/jest.config.js @@ -10,7 +10,7 @@ module.exports = { '^@behaviors/(.*)': '/packages/tdesign-miniprogram/example/behaviors/$1', }, testMatch: ['/packages/(components|pro-components)/**/__test__/**/*.test.{js,ts}'], - collectCoverageFrom: ['/packages/(components|pro-components)/**/*.{js,ts}', '!**/__test__/**', '!**/_example/**'], + collectCoverageFrom: ['/packages/(components|pro-components)/**/*.{js,ts}', '!**/__test__/**', '!**/_example/**', '!**/type.ts', '!**/props.ts'], collectCoverage: true, coverageProvider: 'v8', coverageDirectory: '/packages/tdesign-miniprogram/test/unit/coverage', diff --git a/packages/tdesign-miniprogram/test/scripts/coverage-badge.js b/packages/tdesign-miniprogram/test/scripts/coverage-badge.js index b984a5621..362f4043f 100644 --- a/packages/tdesign-miniprogram/test/scripts/coverage-badge.js +++ b/packages/tdesign-miniprogram/test/scripts/coverage-badge.js @@ -3,79 +3,134 @@ const fs = require('fs'); const os = require('os'); const specify = process.argv[2]; - const data = require('../unit/coverage/coverage-summary.json'); +// 父子组件映射表 +const RELATED_MAP = { + avatar: 'avatar-group', + button: 'button-group', + cell: 'cell-group', + checkbox: 'checkbox-group', + 'dropdown-menu': 'dropdown-item', + grid: 'grid-item', + picker: 'picker-item', + radio: 'radio-group', + steps: 'step-item', + 'tab-bar': 'tab-bar-item', + tabs: 'tab-panel', + tag: 'check-tag', +}; + +/** + * 规范化文件路径(兼容 Windows) + */ +function normalizePath(fPath) { + return os.platform() === 'win32' ? fPath.slice(2).replace(/\\/g, '/') : fPath; +} + +/** + * 从路径中提取组件 key + * @param {string} fPath - 文件路径 + * @returns {string|null} 组件 key + */ +function extractComponentKey(fPath) { + const componentMatch = /packages\/components\/([\w-]+)\//.exec(fPath); + if (componentMatch) return componentMatch[1]; + + const proComponentMatch = /packages\/pro-components\/([\w-]+)\/([\w-]+)\//.exec(fPath); + if (proComponentMatch) return `${proComponentMatch[1]}/${proComponentMatch[2]}`; + + return null; +} + +/** + * 计算覆盖率百分比 + * @param {object} item - 覆盖率数据项 + * @returns {string} 百分比字符串 + */ +function calculateCoverage(item) { + if (item.total === 0) return '100'; + return ((item.covered / item.total) * 100).toFixed(0); +} + +/** + * 获取 README.md 文件路径 + * @param {string} component - 组件 key + * @returns {string} README.md 文件路径 + */ +function getReadmePath(component) { + if (component.includes('/')) { + const [group, subComponent] = component.split('/'); + return path.resolve(__dirname, `../../../pro-components/${group}/${subComponent}/README.md`); + } + return path.resolve(__dirname, `../../../components/${component}/README.md`); +} + +/** + * 生成覆盖率徽章 SVG + * @param {string} type - 类型(lines/functions/statements/branches) + * @param {number} percentage - 覆盖率百分比 + * @returns {string} SVG HTML 字符串 + */ +function generateBadge(type, percentage) { + const color = parseInt(percentage, 10) >= 80 ? 'blue' : 'red'; + return ``; +} + const ans = new Map(); +// 聚合覆盖率数据 Object.keys(data).forEach((fPath) => { - const _fPath = os.platform() === 'win32' ? fPath.slice(2).replace(/\\/g, '/') : fPath; - if (_fPath.startsWith('/')) { - const [, component] = /packages\/components\/([\w-]+)\//.exec(_fPath) ?? []; - - if (component) { - if (!fPath.includes('/_example/')) { - const set = data[fPath]; - const target = ans.get(component) ?? { - lines: { total: 0, covered: 0, skipped: 0 }, - functions: { total: 0, covered: 0, skipped: 0 }, - statements: { total: 0, covered: 0, skipped: 0 }, - branches: { total: 0, covered: 0, skipped: 0 }, - }; - - Object.entries(set).forEach(([type, dataset]) => { - Object.entries(dataset).forEach(([category, val]) => { - target[type][category] = val + target[type][category]; - }); - }); - ans.set(component, target); - } - } - } + const _fPath = normalizePath(fPath); + if (!_fPath.startsWith('/')) return; + + const componentKey = extractComponentKey(_fPath); + if (!componentKey || fPath.includes('/_example/')) return; + + const set = data[fPath]; + const existing = ans.get(componentKey); + const target = existing || { + lines: { total: 0, covered: 0, skipped: 0 }, + functions: { total: 0, covered: 0, skipped: 0 }, + statements: { total: 0, covered: 0, skipped: 0 }, + branches: { total: 0, covered: 0, skipped: 0 }, + }; + + Object.entries(set).forEach(([type, dataset]) => { + Object.entries(dataset).forEach(([category, val]) => { + target[type][category] += val; + }); + }); + ans.set(componentKey, target); }); +// 生成并写入徽章到 README.md ans.forEach((items, component) => { let svgs = ''; Object.entries(items).forEach(([type, item]) => { - let val = item.total === 0 ? 100 : ((item.covered / item.total) * 100).toFixed(0); - const relatedMap = { - avatar: 'avatar-group', - button: 'button-group', - cell: 'cell-group', - checkbox: 'checkbox-group', - 'dropdown-menu': 'dropdown-item', - grid: 'grid-item', - picker: 'picker-item', - radio: 'radio-group', - steps: 'step-item', - // swiper: ['swiper-item', 'swiper-nav'], - 'tab-bar': 'tab-bar-item', - tabs: 'tab-panel', - tag: 'check-tag', - }; - - if (component in relatedMap) { - const related = ans.get(relatedMap[component]); + let val = calculateCoverage(item); + + // 处理相关组件的合并覆盖率 + if (component in RELATED_MAP) { + const related = ans.get(RELATED_MAP[component]); if (related) { const denominator = item.total + related[type].total; - val = denominator === 0 ? 100 : (((item.covered + related[type].covered) / denominator) * 100).toFixed(0); + val = denominator === 0 ? '100' : (((item.covered + related[type].covered) / denominator) * 100).toFixed(0); } } - const message = Number.isNaN(val) ? '0' : val; - const color = parseInt(val, 10) >= 80 ? 'blue' : 'red'; - svgs += ``; + const message = Number.isNaN(val) ? '0' : val; + svgs += generateBadge(type, message); }); - if ((specify && component === specify) || !specify) { - const fPath = path.resolve(__dirname, `../packages/components/${component}/README.md`); - - if (!fs.existsSync(fPath)) return; + if (!specify || component === specify) { + const readmePath = getReadmePath(component); - let readme = fs.readFileSync(fPath, { encoding: 'utf-8' }); + if (!fs.existsSync(readmePath)) return; + let readme = fs.readFileSync(readmePath, { encoding: 'utf-8' }); readme = readme.replace(/\n/g, ''); readme = readme.replace('## 引入', `${svgs}\n## 引入`); - fs.writeFileSync(fPath, readme); + fs.writeFileSync(readmePath, readme); } }); diff --git a/script/gulpfile.js b/script/gulpfile.js index cc9f37dbd..6763e2d7c 100644 --- a/script/gulpfile.js +++ b/script/gulpfile.js @@ -2,6 +2,7 @@ const gulp = require('gulp'); const dist = require('./gulpfile.dist'); const example = require('./gulpfile.example'); const wechatide = require('./gulpfile.wechatide'); + /** `gulp build` * 构建 * */ diff --git a/script/gulpfile.locale.js b/script/gulpfile.locale.js new file mode 100644 index 000000000..fbdd13286 --- /dev/null +++ b/script/gulpfile.locale.js @@ -0,0 +1,9 @@ +const gulp = require('gulp'); + +/** `gulp copyLocale` + * 复制 packages/common/js/global-config/mobile/locale 的 ts 文件到 packages/components/locale + * */ +const copyLocale = () => + gulp.src('packages/common/js/global-config/mobile/locale/*.ts').pipe(gulp.dest('packages/components/locale')); + +module.exports = { copyLocale };