From 1f5d9635579c1b8d684736e0db63985c9241fb63 Mon Sep 17 00:00:00 2001 From: xlsea Date: Sat, 20 Apr 2024 22:40:47 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9Ecron=E7=BB=84?= =?UTF-8?q?=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 1 + packages/cron-input/package.json | 15 ++ packages/cron-input/src/components/cron.vue | 206 +++++++++++++++ .../src/components/internal/cron-base.vue | 247 ++++++++++++++++++ .../src/components/internal/input-number.vue | 59 +++++ packages/cron-input/src/i18n/i18n.d.ts | 53 ++++ packages/cron-input/src/i18n/index.ts | 10 + packages/cron-input/src/i18n/langs/en-us.ts | 47 ++++ packages/cron-input/src/i18n/langs/zh-cn.ts | 47 ++++ packages/cron-input/src/index.ts | 3 + packages/cron-input/src/shared/constants.ts | 77 ++++++ packages/cron-input/src/shared/utils.ts | 80 ++++++ packages/cron-input/tsconfig.json | 20 ++ pnpm-lock.yaml | 21 ++ src/router/elegant/routes.ts | 5 +- src/typings/components.d.ts | 1 + 16 files changed, 888 insertions(+), 4 deletions(-) create mode 100644 packages/cron-input/package.json create mode 100644 packages/cron-input/src/components/cron.vue create mode 100644 packages/cron-input/src/components/internal/cron-base.vue create mode 100644 packages/cron-input/src/components/internal/input-number.vue create mode 100644 packages/cron-input/src/i18n/i18n.d.ts create mode 100644 packages/cron-input/src/i18n/index.ts create mode 100644 packages/cron-input/src/i18n/langs/en-us.ts create mode 100644 packages/cron-input/src/i18n/langs/zh-cn.ts create mode 100644 packages/cron-input/src/index.ts create mode 100644 packages/cron-input/src/shared/constants.ts create mode 100644 packages/cron-input/src/shared/utils.ts create mode 100644 packages/cron-input/tsconfig.json diff --git a/package.json b/package.json index d7e6ecc..70b3746 100644 --- a/package.json +++ b/package.json @@ -44,6 +44,7 @@ "@iconify/vue": "4.1.1", "@sa/axios": "workspace:*", "@sa/color-palette": "workspace:*", + "@sa/cron-input": "workspace:*", "@sa/hooks": "workspace:*", "@sa/materials": "workspace:*", "@sa/utils": "workspace:*", diff --git a/packages/cron-input/package.json b/packages/cron-input/package.json new file mode 100644 index 0000000..d10eb05 --- /dev/null +++ b/packages/cron-input/package.json @@ -0,0 +1,15 @@ +{ + "name": "@sa/cron-input", + "version": "1.0.3", + "exports": { + ".": "./src/index.ts" + }, + "typesVersions": { + "*": { + "*": ["./src/*"] + } + }, + "dependencies": { + "cron-parser": "^4.9.0" + } +} diff --git a/packages/cron-input/src/components/cron.vue b/packages/cron-input/src/components/cron.vue new file mode 100644 index 0000000..1d06411 --- /dev/null +++ b/packages/cron-input/src/components/cron.vue @@ -0,0 +1,206 @@ + + + + + + + diff --git a/packages/cron-input/src/components/internal/cron-base.vue b/packages/cron-input/src/components/internal/cron-base.vue new file mode 100644 index 0000000..8cc9de6 --- /dev/null +++ b/packages/cron-input/src/components/internal/cron-base.vue @@ -0,0 +1,247 @@ + + + + + diff --git a/packages/cron-input/src/components/internal/input-number.vue b/packages/cron-input/src/components/internal/input-number.vue new file mode 100644 index 0000000..a0e3de8 --- /dev/null +++ b/packages/cron-input/src/components/internal/input-number.vue @@ -0,0 +1,59 @@ + + + + + diff --git a/packages/cron-input/src/i18n/i18n.d.ts b/packages/cron-input/src/i18n/i18n.d.ts new file mode 100644 index 0000000..9d20b2b --- /dev/null +++ b/packages/cron-input/src/i18n/i18n.d.ts @@ -0,0 +1,53 @@ +declare namespace I18n { + type LocaleType = 'en-US' | 'zh-CN'; + + type FieldType = 'second' | 'minute' | 'hour' | 'date' | 'month' | 'week' | 'year'; + + interface LocaleFields { + second: string; + minute: string; + hour: string; + date: string; + month: string; + week: string; + year: string; + } + + interface LocaleFieldAlias { + second: string; + minute: string; + hour: string; + date: string; + month: string; + week: string; + year: string; + } + + interface LocaleTypes { + empty: string; + every: string; + unspecific: string; + range: string[]; + step: string[]; + well: string[]; + weekday: string[]; + lastWeekday: string; + lastDayOfDate: string; + lastDayOfWeek: string; + specify: string; + } + + interface LocaleWeek { + [key: string]: string; + } + + interface Translations { + field: LocaleFields; + fieldAlias: LocaleFieldAlias; + type: LocaleTypes; + week: LocaleWeek; + expression: string; + preview: string[]; + previewError: string; + } +} diff --git a/packages/cron-input/src/i18n/index.ts b/packages/cron-input/src/i18n/index.ts new file mode 100644 index 0000000..419a00e --- /dev/null +++ b/packages/cron-input/src/i18n/index.ts @@ -0,0 +1,10 @@ +import { LOCALE_CN, LOCALE_EN } from '../shared/constants'; +import zhCN from './langs/zh-cn'; +import enUS from './langs/en-us'; + +const locales: Record = { + [LOCALE_CN]: zhCN, + [LOCALE_EN]: enUS +}; + +export default locales; diff --git a/packages/cron-input/src/i18n/langs/en-us.ts b/packages/cron-input/src/i18n/langs/en-us.ts new file mode 100644 index 0000000..9d418b7 --- /dev/null +++ b/packages/cron-input/src/i18n/langs/en-us.ts @@ -0,0 +1,47 @@ +const englishTranslations: I18n.Translations = { + field: { + second: 'Second', + minute: 'Minute', + hour: 'Hour', + date: 'Date', + month: 'Month', + week: 'Week', + year: 'Year' + }, + fieldAlias: { + second: 'second', + minute: 'minute', + hour: 'hour', + date: 'date', + month: 'month', + week: 'week', + year: 'year' + }, + type: { + empty: 'Empty', + every: 'Every ', + unspecific: 'Unspecific', + range: ['From ', ' to ', ''], + step: ['Start with ', ', execute every', ''], + well: ['The ', ''], + weekday: ['Nearest weekday to the ', ' of current month'], + lastWeekday: 'Last weekday of current month', + lastDayOfDate: 'Last day of current month', + lastDayOfWeek: 'Last ', + specify: 'Specify' + }, + week: { + Sunday: 'Sunday', + Monday: 'Monday', + Tuesday: 'Tuesday', + Wednesday: 'Wednesday', + Thursday: 'Thursday', + Friday: 'Friday', + Saturday: 'Saturday' + }, + expression: 'The complete expression', + preview: ['Last ', ' runtimes'], + previewError: 'This expression is temporarily unparsed!' +}; + +export default englishTranslations; diff --git a/packages/cron-input/src/i18n/langs/zh-cn.ts b/packages/cron-input/src/i18n/langs/zh-cn.ts new file mode 100644 index 0000000..39d2cd8 --- /dev/null +++ b/packages/cron-input/src/i18n/langs/zh-cn.ts @@ -0,0 +1,47 @@ +const chineseTranslations: I18n.Translations = { + field: { + second: '秒', + minute: '分', + hour: '时', + date: '日', + month: '月', + week: '周', + year: '年' + }, + fieldAlias: { + second: '秒钟', + minute: '分钟', + hour: '小时', + date: '天', + month: '个月', + week: '星期', + year: '年' + }, + type: { + empty: '不指定', + every: '每', + unspecific: '不指定', + range: ['从', '到', ''], + step: ['从', '开始,每', '执行一次'], + well: ['当月第', '个'], + weekday: ['离当月', '号最近的那个工作日'], + lastWeekday: '当月最后一个工作日', + lastDayOfDate: '当月最后一天', + lastDayOfWeek: '当月最后一个', + specify: '指定' + }, + week: { + Sunday: '星期日', + Monday: '星期一', + Tuesday: '星期二', + Wednesday: '星期三', + Thursday: '星期四', + Friday: '星期五', + Saturday: '星期六' + }, + expression: '完整表达式', + preview: ['最近', '次运行时间'], + previewError: '此表达式暂时无法解析!' +}; + +export default chineseTranslations; diff --git a/packages/cron-input/src/index.ts b/packages/cron-input/src/index.ts new file mode 100644 index 0000000..a7fc9ca --- /dev/null +++ b/packages/cron-input/src/index.ts @@ -0,0 +1,3 @@ +import Cron from './components/cron.vue'; + +export default Cron; diff --git a/packages/cron-input/src/shared/constants.ts b/packages/cron-input/src/shared/constants.ts new file mode 100644 index 0000000..352fdc4 --- /dev/null +++ b/packages/cron-input/src/shared/constants.ts @@ -0,0 +1,77 @@ +export const MIN_SECOND = 0; +export const MAX_SECOND = 59; + +export const MIN_MINUTE = 0; +export const MAX_MINUTE = 59; + +export const MIN_HOUR = 0; +export const MAX_HOUR = 23; + +export const MIN_DATE = 1; +export const MAX_DATE = 31; + +export const MIN_MONTH = 1; +export const MAX_MONTH = 12; + +export const MIN_WEEK = 0; +export const MAX_WEEK = 6; + +export const MIN_YEAR = new Date().getFullYear(); +export const MAX_YEAR = 2099; + +export const SUNDAY = 'sunday'; +export const MONDAY = 'monday'; +export const TUESDAY = 'tuesday'; +export const WEDNESDAY = 'wednesday'; +export const THURSDAY = 'thursday'; +export const FRIDAY = 'friday'; +export const SATURDAY = 'saturday'; + +export const WEEKS = [ + { value: SUNDAY, abbr: 'SUN', index: '0' }, + { value: MONDAY, abbr: 'MON', index: '1' }, + { value: TUESDAY, abbr: 'TUE', index: '2' }, + { value: WEDNESDAY, abbr: 'WED', index: '3' }, + { value: THURSDAY, abbr: 'THU', index: '4' }, + { value: FRIDAY, abbr: 'FRI', index: '5' }, + { value: SATURDAY, abbr: 'SAT', index: '6' } +]; + +export const WEEK_INDEX_REGEXP = new RegExp(WEEKS.map(({ index }) => `(? abbr).join('|'), 'g'); + +export const TYPE = { + EVERY: '*', + RANGE: '-', + STEP: '/', + SPECIFY: ',', + UNSPECIFIC: '?', + EMPTY: '', + LAST_DAY: 'L', + LAST_WEEKDAY: 'LW', + WELL: '#', + WEEKDAY: 'W' +}; + +export const SECOND = 'second'; +export const MINUTE = 'minute'; +export const HOUR = 'hour'; +export const DATE = 'date'; +export const MONTH = 'month'; +export const WEEK = 'week'; +export const YEAR = 'year'; + +export const FIELDS: { value: I18n.FieldType; min: number; max: number }[] = [ + { value: SECOND, min: MIN_SECOND, max: MAX_SECOND }, + { value: MINUTE, min: MIN_MINUTE, max: MAX_MINUTE }, + { value: HOUR, min: MIN_HOUR, max: MAX_HOUR }, + { value: DATE, min: MIN_DATE, max: MAX_DATE }, + { value: MONTH, min: MIN_MONTH, max: MAX_MONTH }, + { value: WEEK, min: MIN_WEEK, max: MAX_WEEK }, + { value: YEAR, min: MIN_YEAR, max: MAX_YEAR } +]; + +export const LOCALE_EN: I18n.LocaleType = 'en-US'; +export const LOCALE_CN: I18n.LocaleType = 'zh-CN'; +export const DEFAULT_CRON_EXPRESSION = '* * * * * ?'; +export const DEFAULT_LOCALE: I18n.LocaleType = LOCALE_CN; diff --git a/packages/cron-input/src/shared/utils.ts b/packages/cron-input/src/shared/utils.ts new file mode 100644 index 0000000..9a5a64b --- /dev/null +++ b/packages/cron-input/src/shared/utils.ts @@ -0,0 +1,80 @@ +/* eslint-disable @typescript-eslint/no-shadow */ +import I18n from '../i18n'; +import { WEEK, WEEKS, WEEK_INDEX_REGEXP, WEEK_LETTER_REGEXP } from './constants'; + +/** + * 生成指定数组. + * + * @param min - 区间最小值. + * @param max - 区间最大值. + * @param labels - 显示名称. + */ +export function generateSpecifies( + min: number, + max: number, + labels: string[] | null +): { value: number; label: string }[] { + const specifies = []; + + let index = 0; + + for (let specify = min; specify <= max; specify += 1) { + specifies.push({ value: specify, label: labels ? labels[index] : specify.toString() }); + index += 1; + } + + return specifies; +} + +/** + * 日期时间字段补零. + * + * @param value - 待补零处理的数字. + */ +export function zerofill(value: number): string { + const prefix = value < 10 ? '0' : ''; + + return `${prefix}${value}`; +} + +/** + * 转换星期数字为缩写. + * + * @param value - . + */ +export function weekNumberToLetter(value: string): string { + return value.replace(WEEK_INDEX_REGEXP, value => WEEKS.find(({ index }) => [index].includes(value))?.abbr || value); +} + +/** + * 转换星期缩写为数字. + * + * @param value - . + */ +export function weekLetterToNumber(value: string): string { + return value.replace(WEEK_LETTER_REGEXP, value => WEEKS.find(({ abbr }) => abbr === value)?.index || value); +} + +/** + * 星期字段的 组件 formatter 函数. + * + * @param value - . + * @param locale - 国际化字段. + */ +export function formatterWeek(value: string, locale: I18n.LocaleType): string { + const day = WEEKS.find(({ index }) => index === value)?.value; + + return I18n[locale].week[day!]; +} + +/** + * 星期字段的 组件 parser 函数. + * + * @param value - . + * @param locale - 国际化字段. + */ +export function parserWeek(value: string, locale: I18n.LocaleType): number { + const [key] = Object.entries(I18n[locale][WEEK]).find(([, val]) => val === value) as [string, string]; + + return WEEKS.findIndex(({ value }) => value === key); +} diff --git a/packages/cron-input/tsconfig.json b/packages/cron-input/tsconfig.json new file mode 100644 index 0000000..0438528 --- /dev/null +++ b/packages/cron-input/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "target": "ESNext", + "jsx": "preserve", + "lib": ["DOM", "ESNext"], + "baseUrl": ".", + "module": "ESNext", + "moduleResolution": "node", + "resolveJsonModule": true, + "types": ["node", "naive-ui/volar"], + "strict": true, + "strictNullChecks": true, + "noUnusedLocals": true, + "allowSyntheticDefaultImports": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist"] +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 96910be..74c056d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -20,6 +20,9 @@ importers: '@sa/color-palette': specifier: workspace:* version: link:packages/color-palette + '@sa/cron-input': + specifier: workspace:* + version: link:packages/cron-input '@sa/hooks': specifier: workspace:* version: link:packages/hooks @@ -188,6 +191,12 @@ importers: specifier: 2.9.3 version: 2.9.3 + packages/cron-input: + dependencies: + cron-parser: + specifier: ^4.9.0 + version: 4.9.0 + packages/hooks: dependencies: '@sa/axios': @@ -3239,6 +3248,13 @@ packages: vary: 1.1.2 dev: true + /cron-parser@4.9.0: + resolution: {integrity: sha512-p0SaNjrHOnQeR8/VnfGbmg9te2kfyYSQ7Sc/j/6DtPL3JQvKxmjO9TSjNFpujqV3vEYYBvNNvXSxzyksBWAx1Q==} + engines: {node: '>=12.0.0'} + dependencies: + luxon: 3.4.4 + dev: false + /cross-spawn@7.0.3: resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} engines: {node: '>= 8'} @@ -5644,6 +5660,11 @@ packages: engines: {node: '>=12'} dev: true + /luxon@3.4.4: + resolution: {integrity: sha512-zobTr7akeGHnv7eBOXcRgMeCP6+uyYsczwmeRCauvpvaAltgNyTbLH/+VaEAPUeWBT+1GuNmz4wC/6jtQzbbVA==} + engines: {node: '>=12'} + dev: false + /magic-string@0.30.6: resolution: {integrity: sha512-n62qCLbPjNjyo+owKtveQxZFZTBm+Ms6YoGD23Wew6Vw337PElFNifQpknPruVRQV57kVShPnLGo9vWxVhpPvA==} engines: {node: '>=12'} diff --git a/src/router/elegant/routes.ts b/src/router/elegant/routes.ts index bd41999..ee380cf 100644 --- a/src/router/elegant/routes.ts +++ b/src/router/elegant/routes.ts @@ -45,10 +45,7 @@ export const generatedRoutes: GeneratedRoute[] = [ component: 'layout.base$view.about', meta: { title: 'about', - i18nKey: 'route.about', - icon: 'fluent:book-information-24-regular', - order: 10, - hideInMenu: true + i18nKey: 'route.about' } }, { diff --git a/src/typings/components.d.ts b/src/typings/components.d.ts index 8562bdb..48fdafc 100644 --- a/src/typings/components.d.ts +++ b/src/typings/components.d.ts @@ -41,6 +41,7 @@ declare module 'vue' { NButton: typeof import('naive-ui')['NButton'] NCard: typeof import('naive-ui')['NCard'] NCheckbox: typeof import('naive-ui')['NCheckbox'] + NCheckboxGroup: typeof import('naive-ui')['NCheckboxGroup'] NColorPicker: typeof import('naive-ui')['NColorPicker'] NDataTable: typeof import('naive-ui')['NDataTable'] NDatePicker: typeof import('naive-ui')['NDatePicker']