diff --git a/apps/web-antd/src/locales/langs/en-US/demos.json b/apps/web-antd/src/locales/langs/en-US/demos.json index 07156434..12a3b718 100644 --- a/apps/web-antd/src/locales/langs/en-US/demos.json +++ b/apps/web-antd/src/locales/langs/en-US/demos.json @@ -7,6 +7,7 @@ "document": "Document", "antdv": "Ant Design Vue Version", "naive-ui": "Naive UI Version", - "element-plus": "Element Plus Version" + "element-plus": "Element Plus Version", + "tdesign": "TDesign Vue Version" } } diff --git a/apps/web-antd/src/locales/langs/en-US/page.json b/apps/web-antd/src/locales/langs/en-US/page.json index 8fe0f0ca..5127b6ac 100644 --- a/apps/web-antd/src/locales/langs/en-US/page.json +++ b/apps/web-antd/src/locales/langs/en-US/page.json @@ -5,7 +5,8 @@ "codeLogin": "Code Login", "qrcodeLogin": "Qr Code Login", "forgetPassword": "Forget Password", - "oauthLogin": "Oauth Login" + "oauthLogin": "Oauth Login", + "profile": "Profile" }, "dashboard": { "title": "Dashboard", diff --git a/apps/web-antd/src/locales/langs/zh-CN/demos.json b/apps/web-antd/src/locales/langs/zh-CN/demos.json index 93ee722f..b5007ea7 100644 --- a/apps/web-antd/src/locales/langs/zh-CN/demos.json +++ b/apps/web-antd/src/locales/langs/zh-CN/demos.json @@ -7,6 +7,7 @@ "document": "文档", "antdv": "Ant Design Vue 版本", "naive-ui": "Naive UI 版本", - "element-plus": "Element Plus 版本" + "element-plus": "Element Plus 版本", + "tdesign": "TDesign Vue 版本" } } diff --git a/apps/web-antd/src/locales/langs/zh-CN/page.json b/apps/web-antd/src/locales/langs/zh-CN/page.json index 77dceffa..2192d1d5 100644 --- a/apps/web-antd/src/locales/langs/zh-CN/page.json +++ b/apps/web-antd/src/locales/langs/zh-CN/page.json @@ -5,7 +5,7 @@ "codeLogin": "验证码登录", "qrcodeLogin": "二维码登录", "forgetPassword": "忘记密码", - "oauthLogin": "第三方登录" + "profile": "个人中心" }, "dashboard": { "title": "概览", diff --git a/apps/web-antd/src/router/routes/modules/vben.ts b/apps/web-antd/src/router/routes/modules/vben.ts index a54bb8f8..3f22cb54 100644 --- a/apps/web-antd/src/router/routes/modules/vben.ts +++ b/apps/web-antd/src/router/routes/modules/vben.ts @@ -85,6 +85,16 @@ const routes: RouteRecordRaw[] = [ order: 9999, }, }, + { + name: 'Profile', + path: '/profile', + component: () => import('#/views/_core/profile/index.vue'), + meta: { + icon: 'lucide:user', + hideInMenu: true, + title: $t('page.auth.profile'), + }, + }, ]; export default routes; diff --git a/apps/web-antd/src/views/_core/profile/base-setting.vue b/apps/web-antd/src/views/_core/profile/base-setting.vue new file mode 100644 index 00000000..aa8a4c26 --- /dev/null +++ b/apps/web-antd/src/views/_core/profile/base-setting.vue @@ -0,0 +1,65 @@ + + + + diff --git a/apps/web-antd/src/views/_core/profile/notification-setting.vue b/apps/web-antd/src/views/_core/profile/notification-setting.vue new file mode 100644 index 00000000..324a4b39 --- /dev/null +++ b/apps/web-antd/src/views/_core/profile/notification-setting.vue @@ -0,0 +1,31 @@ + + + + diff --git a/apps/web-antd/src/views/_core/profile/password-setting.vue b/apps/web-antd/src/views/_core/profile/password-setting.vue new file mode 100644 index 00000000..b246bc37 --- /dev/null +++ b/apps/web-antd/src/views/_core/profile/password-setting.vue @@ -0,0 +1,66 @@ + + + + diff --git a/apps/web-antd/src/views/_core/profile/security-setting.vue b/apps/web-antd/src/views/_core/profile/security-setting.vue new file mode 100644 index 00000000..be30db58 --- /dev/null +++ b/apps/web-antd/src/views/_core/profile/security-setting.vue @@ -0,0 +1,43 @@ + + + + diff --git a/cspell.json b/cspell.json index 4e6b8723..39c441d3 100644 --- a/cspell.json +++ b/cspell.json @@ -45,6 +45,7 @@ "Qqchat", "qrcode", "ruoyi", + "reka", "shadcn", "sonner", "sortablejs", diff --git a/docs/src/components/common-ui/vben-form.md b/docs/src/components/common-ui/vben-form.md index 48772bfa..30cbec44 100644 --- a/docs/src/components/common-ui/vben-form.md +++ b/docs/src/components/common-ui/vben-form.md @@ -335,6 +335,7 @@ useVbenForm 返回的第二个参数,是一个对象,包含了一些表单 | handleReset | 表单重置回调 | `(values: Record,) => Promise \| void` | - | | handleSubmit | 表单提交回调 | `(values: Record,) => Promise \| void` | - | | handleValuesChange | 表单值变化回调 | `(values: Record, fieldsChanged: string[]) => void` | - | +| handleCollapsedChange | 表单收起展开状态变化回调 | `(collapsed: boolean) => void` | - | | actionButtonsReverse | 调换操作按钮位置 | `boolean` | `false` | | resetButtonOptions | 重置按钮组件参数 | `ActionButtonOptions` | - | | submitButtonOptions | 提交按钮组件参数 | `ActionButtonOptions` | - | diff --git a/docs/src/en/guide/essentials/development.md b/docs/src/en/guide/essentials/development.md index 9ff832b7..437810f7 100644 --- a/docs/src/en/guide/essentials/development.md +++ b/docs/src/en/guide/essentials/development.md @@ -60,6 +60,8 @@ The execution command is: `pnpm run [script]` or `npm run [script]`. "build:ele": "pnpm run build --filter=@vben/web-ele", // Build the web-naive application separately "build:naive": "pnpm run build --filter=@vben/naive", + // Build the web-tdesign application separately + "build:tdesign": "pnpm run build --filter=@vben/web-tdesign", // Build the playground application separately "build:play": "pnpm run build --filter=@vben/playground", // Changeset version management diff --git a/docs/src/en/guide/introduction/thin.md b/docs/src/en/guide/introduction/thin.md index a310ef39..7ca0468d 100644 --- a/docs/src/en/guide/introduction/thin.md +++ b/docs/src/en/guide/introduction/thin.md @@ -56,6 +56,7 @@ After slimming down, you may need to adjust commands according to your project. "build:docs": "pnpm run build --filter=@vben/docs", "build:ele": "pnpm run build --filter=@vben/web-ele", "build:naive": "pnpm run build --filter=@vben/web-naive", + "build:tdesign": "pnpm run build --filter=@vben/web-tdesign", "build:play": "pnpm run build --filter=@vben/playground", "dev:antd": "pnpm -F @vben/web-antd run dev", "dev:docs": "pnpm -F @vben/docs run dev", diff --git a/docs/src/guide/essentials/development.md b/docs/src/guide/essentials/development.md index cb55b6b5..7f4e5107 100644 --- a/docs/src/guide/essentials/development.md +++ b/docs/src/guide/essentials/development.md @@ -60,6 +60,8 @@ npm 脚本是项目常见的配置,用于执行一些常见的任务,比如 "build:ele": "pnpm run build --filter=@vben/web-ele", // 单独构建 web-naive 应用 "build:naive": "pnpm run build --filter=@vben/naive", + // 单独构建 web-tdesign 应用 + "build:tdesign": "pnpm run build --filter=@vben/web-tdesign", // 单独构建 playground 应用 "build:play": "pnpm run build --filter=@vben/playground", // changeset 版本管理 diff --git a/docs/src/guide/introduction/thin.md b/docs/src/guide/introduction/thin.md index 80884005..157d51ff 100644 --- a/docs/src/guide/introduction/thin.md +++ b/docs/src/guide/introduction/thin.md @@ -60,6 +60,7 @@ pnpm install "build:docs": "pnpm run build --filter=@vben/docs", "build:ele": "pnpm run build --filter=@vben/web-ele", "build:naive": "pnpm run build --filter=@vben/web-naive", + "build:tdesign": "pnpm run build --filter=@vben/web-tdesign", "build:play": "pnpm run build --filter=@vben/playground", "dev:antd": "pnpm -F @vben/web-antd run dev", "dev:docs": "pnpm -F @vben/docs run dev", diff --git a/internal/tailwind-config/src/index.ts b/internal/tailwind-config/src/index.ts index dafaaf91..17fae7dc 100644 --- a/internal/tailwind-config/src/index.ts +++ b/internal/tailwind-config/src/index.ts @@ -176,18 +176,18 @@ export default { keyframes: { 'accordion-down': { from: { height: '0' }, - to: { height: 'var(--radix-accordion-content-height)' }, + to: { height: 'var(--reka-accordion-content-height)' }, }, 'accordion-up': { - from: { height: 'var(--radix-accordion-content-height)' }, + from: { height: 'var(--reka-accordion-content-height)' }, to: { height: '0' }, }, 'collapsible-down': { from: { height: '0' }, - to: { height: 'var(--radix-collapsible-content-height)' }, + to: { height: 'var(--reka-collapsible-content-height)' }, }, 'collapsible-up': { - from: { height: 'var(--radix-collapsible-content-height)' }, + from: { height: 'var(--reka-collapsible-content-height)' }, to: { height: '0' }, }, float: { diff --git a/package.json b/package.json index 6ef23c21..ace03ad2 100644 --- a/package.json +++ b/package.json @@ -88,7 +88,7 @@ "unbuild": "catalog:", "vite": "catalog:", "vitest": "catalog:", - "vue": "catalog:", + "vue": "^3.5.24", "vue-tsc": "catalog:" }, "engines": { @@ -108,7 +108,8 @@ "clsx": "catalog:", "esbuild": "0.25.3", "pinia": "catalog:", - "vue": "catalog:" + "vue": "catalog:", + "jiti": "^2.6.1" }, "neverBuiltDependencies": [ "canvas", diff --git a/packages/@core/base/shared/package.json b/packages/@core/base/shared/package.json index c57a0acf..2e453400 100644 --- a/packages/@core/base/shared/package.json +++ b/packages/@core/base/shared/package.json @@ -85,10 +85,8 @@ "clsx": "catalog:", "dayjs": "catalog:", "defu": "catalog:", + "es-toolkit": "catalog:", "lodash.clonedeep": "catalog:", - "lodash.get": "catalog:", - "lodash.isequal": "catalog:", - "lodash.set": "catalog:", "nprogress": "catalog:", "tailwind-merge": "catalog:", "theme-colors": "catalog:" diff --git a/packages/@core/base/shared/src/constants/vben.ts b/packages/@core/base/shared/src/constants/vben.ts index 0f6cbbe1..5ba78f46 100644 --- a/packages/@core/base/shared/src/constants/vben.ts +++ b/packages/@core/base/shared/src/constants/vben.ts @@ -24,3 +24,5 @@ export const VBEN_ELE_PREVIEW_URL = 'https://ele.vben.pro'; export const VBEN_NAIVE_PREVIEW_URL = 'https://naive.vben.pro'; export const VBEN_ANT_PREVIEW_URL = 'https://ant.vben.pro'; + +export const VBEN_TD_PREVIEW_URL = 'https://tdesign.vben.pro'; diff --git a/packages/@core/base/shared/src/utils/date.ts b/packages/@core/base/shared/src/utils/date.ts index b56023c8..926050d1 100644 --- a/packages/@core/base/shared/src/utils/date.ts +++ b/packages/@core/base/shared/src/utils/date.ts @@ -7,7 +7,19 @@ dayjs.extend(timezone); type FormatDate = Date | dayjs.Dayjs | number | string; -export function formatDate(time: FormatDate, format = 'YYYY-MM-DD') { +type Format = + | 'HH' + | 'HH:mm' + | 'HH:mm:ss' + | 'YYYY' + | 'YYYY-MM' + | 'YYYY-MM-DD' + | 'YYYY-MM-DD HH' + | 'YYYY-MM-DD HH:mm' + | 'YYYY-MM-DD HH:mm:ss' + | (string & {}); + +export function formatDate(time?: FormatDate, format: Format = 'YYYY-MM-DD') { try { const date = dayjs.isDayjs(time) ? time : dayjs(time); if (!date.isValid()) { @@ -16,11 +28,11 @@ export function formatDate(time: FormatDate, format = 'YYYY-MM-DD') { return date.tz().format(format); } catch (error) { console.error(`Error formatting date: ${error}`); - return String(time); + return String(time ?? ''); } } -export function formatDateTime(time: FormatDate) { +export function formatDateTime(time?: FormatDate) { return formatDate(time, 'YYYY-MM-DD HH:mm:ss'); } diff --git a/packages/@core/base/shared/src/utils/index.ts b/packages/@core/base/shared/src/utils/index.ts index c067c731..fe8cd289 100644 --- a/packages/@core/base/shared/src/utils/index.ts +++ b/packages/@core/base/shared/src/utils/index.ts @@ -15,7 +15,6 @@ export * from './unique'; export * from './update-css-variables'; export * from './util'; export * from './window'; +export { get, isEqual, set } from 'es-toolkit/compat'; +// export { cloneDeep } from 'es-toolkit/object'; export { default as cloneDeep } from 'lodash.clonedeep'; -export { default as get } from 'lodash.get'; -export { default as isEqual } from 'lodash.isequal'; -export { default as set } from 'lodash.set'; diff --git a/packages/@core/ui-kit/form-ui/src/components/form-actions.vue b/packages/@core/ui-kit/form-ui/src/components/form-actions.vue index 42101c12..36b1caa0 100644 --- a/packages/@core/ui-kit/form-ui/src/components/form-actions.vue +++ b/packages/@core/ui-kit/form-ui/src/components/form-actions.vue @@ -47,7 +47,7 @@ async function handleSubmit(e: Event) { return; } - const values = toRaw(await props.formApi.getValues()); + const values = toRaw(await props.formApi.getValues()) ?? {}; await props.handleSubmit?.(values); } @@ -56,7 +56,7 @@ async function handleReset(e: Event) { e?.stopPropagation(); const props = unref(rootProps); - const values = toRaw(await props.formApi?.getValues()); + const values = toRaw(await props.formApi?.getValues()) ?? {}; if (isFunction(props.handleReset)) { await props.handleReset?.(values); diff --git a/packages/@core/ui-kit/form-ui/src/form-api.ts b/packages/@core/ui-kit/form-ui/src/form-api.ts index 401748c3..f5f353dc 100644 --- a/packages/@core/ui-kit/form-ui/src/form-api.ts +++ b/packages/@core/ui-kit/form-ui/src/form-api.ts @@ -36,6 +36,7 @@ function getDefaultState(): VbenFormProps { handleReset: undefined, handleSubmit: undefined, handleValuesChange: undefined, + handleCollapsedChange: undefined, layout: 'horizontal', resetButtonOptions: {}, schema: [], diff --git a/packages/@core/ui-kit/form-ui/src/types.ts b/packages/@core/ui-kit/form-ui/src/types.ts index 824b7a44..6d704145 100644 --- a/packages/@core/ui-kit/form-ui/src/types.ts +++ b/packages/@core/ui-kit/form-ui/src/types.ts @@ -379,6 +379,10 @@ export interface VbenFormProps< * 表单字段映射 */ fieldMappingTime?: FieldMappingTime; + /** + * 表单收起展开状态变化回调 + */ + handleCollapsedChange?: (collapsed: boolean) => void; /** * 表单重置回调 */ diff --git a/packages/@core/ui-kit/form-ui/src/use-form-context.ts b/packages/@core/ui-kit/form-ui/src/use-form-context.ts index 4ef182ed..e95c87b9 100644 --- a/packages/@core/ui-kit/form-ui/src/use-form-context.ts +++ b/packages/@core/ui-kit/form-ui/src/use-form-context.ts @@ -13,7 +13,7 @@ import { useForm } from 'vee-validate'; import { object, ZodIntersection, ZodNumber, ZodObject, ZodString } from 'zod'; import { getDefaultsForSchema } from 'zod-defaults'; -type ExtendFormProps = VbenFormProps & { formApi: ExtendedFormApi }; +type ExtendFormProps = VbenFormProps & { formApi?: ExtendedFormApi }; export const [injectFormProps, provideFormProps] = createContext<[ComputedRef | ExtendFormProps, FormActions]>( diff --git a/packages/@core/ui-kit/form-ui/src/vben-form.vue b/packages/@core/ui-kit/form-ui/src/vben-form.vue index 260e75cd..f3ba37f0 100644 --- a/packages/@core/ui-kit/form-ui/src/vben-form.vue +++ b/packages/@core/ui-kit/form-ui/src/vben-form.vue @@ -40,7 +40,9 @@ const { delegatedSlots, form } = useFormInitial(props); provideFormProps([props, form]); const handleUpdateCollapsed = (value: boolean) => { - currentCollapsed.value = !!value; + currentCollapsed.value = value; + // 触发收起展开状态变化回调 + props.handleCollapsedChange?.(value); }; watchEffect(() => { diff --git a/packages/@core/ui-kit/form-ui/src/vben-use-form.vue b/packages/@core/ui-kit/form-ui/src/vben-use-form.vue index 3e7b00b6..e9386725 100644 --- a/packages/@core/ui-kit/form-ui/src/vben-use-form.vue +++ b/packages/@core/ui-kit/form-ui/src/vben-use-form.vue @@ -25,7 +25,7 @@ import { } from './use-form-context'; // 通过 extends 会导致热更新卡死,所以重复写了一遍 interface Props extends VbenFormProps { - formApi: ExtendedFormApi; + formApi?: ExtendedFormApi; } const props = defineProps(); @@ -44,11 +44,13 @@ provideComponentRefMap(componentRefMap); props.formApi?.mount?.(form, componentRefMap); const handleUpdateCollapsed = (value: boolean) => { - props.formApi?.setState({ collapsed: !!value }); + props.formApi?.setState({ collapsed: value }); + // 触发收起展开状态变化回调 + forward.value.handleCollapsedChange?.(value); }; function handleKeyDownEnter(event: KeyboardEvent) { - if (!state.value.submitOnEnter || !forward.value.formApi?.isMounted) { + if (!state?.value.submitOnEnter || !forward.value.formApi?.isMounted) { return; } // 如果是 textarea 不阻止默认行为,否则会导致无法换行。 @@ -58,11 +60,11 @@ function handleKeyDownEnter(event: KeyboardEvent) { } event.preventDefault(); - forward.value.formApi.validateAndSubmitForm(); + forward.value.formApi?.validateAndSubmitForm(); } const handleValuesChangeDebounced = useDebounceFn(async () => { - state.value.submitOnChange && forward.value.formApi?.validateAndSubmitForm(); + state?.value.submitOnChange && forward.value.formApi?.validateAndSubmitForm(); }, 300); const valuesCache: Recordable = {}; @@ -74,7 +76,7 @@ onMounted(async () => { () => form.values, async (newVal) => { if (forward.value.handleValuesChange) { - const fields = state.value.schema?.map((item) => { + const fields = state?.value.schema?.map((item) => { return item.fieldName; }); @@ -91,8 +93,9 @@ onMounted(async () => { if (changedFields.length > 0) { // 调用handleValuesChange回调,传入所有表单值的深拷贝和变更的字段列表 + const values = await forward.value.formApi?.getValues(); forward.value.handleValuesChange( - cloneDeep(await forward.value.formApi.getValues()), + cloneDeep(values ?? {}) as Record, changedFields, ); } @@ -109,7 +112,7 @@ onMounted(async () => { { diff --git a/packages/@core/ui-kit/shadcn-ui/src/components/button/button.ts b/packages/@core/ui-kit/shadcn-ui/src/components/button/button.ts index dc84a18a..e12729a4 100644 --- a/packages/@core/ui-kit/shadcn-ui/src/components/button/button.ts +++ b/packages/@core/ui-kit/shadcn-ui/src/components/button/button.ts @@ -13,7 +13,7 @@ export interface VbenButtonProps { /** * Change the default rendered element for the one passed as a child, merging their props and behavior. * - * Read our [Composition](https://www.radix-vue.com/guides/composition.html) guide for more details. + * Read our [Composition](https://www.reka-ui.com/docs/guides/composition) guide for more details. */ asChild?: boolean; class?: any; diff --git a/packages/effects/common-ui/src/ui/index.ts b/packages/effects/common-ui/src/ui/index.ts index fb99fdec..8b1db558 100644 --- a/packages/effects/common-ui/src/ui/index.ts +++ b/packages/effects/common-ui/src/ui/index.ts @@ -2,3 +2,4 @@ export * from './about'; export * from './authentication'; export * from './dashboard'; export * from './fallback'; +export * from './profile'; diff --git a/packages/effects/common-ui/src/ui/profile/base-setting.vue b/packages/effects/common-ui/src/ui/profile/base-setting.vue new file mode 100644 index 00000000..1ef1c1a0 --- /dev/null +++ b/packages/effects/common-ui/src/ui/profile/base-setting.vue @@ -0,0 +1,56 @@ + + + + + + 更新基本信息 + + + diff --git a/packages/effects/common-ui/src/ui/profile/index.ts b/packages/effects/common-ui/src/ui/profile/index.ts new file mode 100644 index 00000000..209a2e81 --- /dev/null +++ b/packages/effects/common-ui/src/ui/profile/index.ts @@ -0,0 +1,6 @@ +export { default as ProfileBaseSetting } from './base-setting.vue'; +export { default as ProfileNotificationSetting } from './notification-setting.vue'; +export { default as ProfilePasswordSetting } from './password-setting.vue'; +export { default as Profile } from './profile.vue'; +export { default as ProfileSecuritySetting } from './security-setting.vue'; +export type * from './types'; diff --git a/packages/effects/common-ui/src/ui/profile/notification-setting.vue b/packages/effects/common-ui/src/ui/profile/notification-setting.vue new file mode 100644 index 00000000..9a3e4882 --- /dev/null +++ b/packages/effects/common-ui/src/ui/profile/notification-setting.vue @@ -0,0 +1,53 @@ + + + + + + + + + {{ item.label }} + + {{ item.description }} + + + + + + + + + + + diff --git a/packages/effects/common-ui/src/ui/profile/password-setting.vue b/packages/effects/common-ui/src/ui/profile/password-setting.vue new file mode 100644 index 00000000..1d90e086 --- /dev/null +++ b/packages/effects/common-ui/src/ui/profile/password-setting.vue @@ -0,0 +1,56 @@ + + + + + + 更新密码 + + + diff --git a/packages/effects/common-ui/src/ui/profile/profile.vue b/packages/effects/common-ui/src/ui/profile/profile.vue new file mode 100644 index 00000000..5ab39e61 --- /dev/null +++ b/packages/effects/common-ui/src/ui/profile/profile.vue @@ -0,0 +1,62 @@ + + + + + + + + + {{ userInfo?.realName ?? '' }} + + + {{ userInfo?.username ?? '' }} + + + + + + + {{ tab.label }} + + + + + + + + + + diff --git a/packages/effects/common-ui/src/ui/profile/security-setting.vue b/packages/effects/common-ui/src/ui/profile/security-setting.vue new file mode 100644 index 00000000..9a3e4882 --- /dev/null +++ b/packages/effects/common-ui/src/ui/profile/security-setting.vue @@ -0,0 +1,53 @@ + + + + + + + + + {{ item.label }} + + {{ item.description }} + + + + + + + + + + + diff --git a/packages/effects/common-ui/src/ui/profile/types.ts b/packages/effects/common-ui/src/ui/profile/types.ts new file mode 100644 index 00000000..e600bde8 --- /dev/null +++ b/packages/effects/common-ui/src/ui/profile/types.ts @@ -0,0 +1,21 @@ +import type { BasicUserInfo } from '@vben/types'; + +export interface Props { + title?: string; + userInfo: BasicUserInfo | null; + tabs: { + label: string; + value: string; + }[]; +} + +export interface FormSchemaItem { + description: string; + fieldName: string; + label: string; + value: boolean; +} + +export interface SettingProps { + formSchema: FormSchemaItem[]; +} diff --git a/packages/effects/layouts/src/widgets/notification/types.ts b/packages/effects/layouts/src/widgets/notification/types.ts index 3b50e124..f43013de 100644 --- a/packages/effects/layouts/src/widgets/notification/types.ts +++ b/packages/effects/layouts/src/widgets/notification/types.ts @@ -1,4 +1,5 @@ interface NotificationItem { + id: number | string; avatar: string; date: string; isRead?: boolean; diff --git a/packages/effects/request/src/request-client/modules/downloader.test.ts b/packages/effects/request/src/request-client/modules/downloader.test.ts index d44dcbb7..27e8a30c 100644 --- a/packages/effects/request/src/request-client/modules/downloader.test.ts +++ b/packages/effects/request/src/request-client/modules/downloader.test.ts @@ -30,6 +30,7 @@ describe('fileDownloader', () => { expect(result).toBeInstanceOf(Blob); expect(result).toEqual(mockBlob); expect(mockAxiosInstance.get).toHaveBeenCalledWith(url, { + method: 'GET', responseType: 'blob', responseReturn: 'body', }); @@ -51,6 +52,7 @@ describe('fileDownloader', () => { expect(result).toEqual(mockBlob); expect(mockAxiosInstance.get).toHaveBeenCalledWith(url, { ...customConfig, + method: 'GET', responseType: 'blob', responseReturn: 'body', }); @@ -84,3 +86,72 @@ describe('fileDownloader', () => { ); }); }); + +describe('fileDownloader use other method', () => { + let fileDownloader: FileDownloader; + + it('should call request using get', async () => { + const url = 'https://example.com/file'; + const mockBlob = new Blob(['file content'], { type: 'text/plain' }); + const mockResponse: Blob = mockBlob; + + const mockAxiosInstance = { + request: vi.fn(), + } as any; + + fileDownloader = new FileDownloader(mockAxiosInstance); + + mockAxiosInstance.request.mockResolvedValueOnce(mockResponse); + + const result = await fileDownloader.download(url); + + expect(result).toBeInstanceOf(Blob); + expect(result).toEqual(mockBlob); + expect(mockAxiosInstance.request).toHaveBeenCalledWith(url, { + method: 'GET', + responseType: 'blob', + responseReturn: 'body', + }); + }); + + it('should call post', async () => { + const url = 'https://example.com/file'; + + const mockAxiosInstance = { + post: vi.fn(), + } as any; + + fileDownloader = new FileDownloader(mockAxiosInstance); + + const customConfig: AxiosRequestConfig = { + method: 'POST', + data: { name: 'aa' }, + }; + + await fileDownloader.download(url, customConfig); + + expect(mockAxiosInstance.post).toHaveBeenCalledWith( + url, + { name: 'aa' }, + { + method: 'POST', + responseType: 'blob', + responseReturn: 'body', + }, + ); + }); + + it('should handle errors gracefully', async () => { + const url = 'https://example.com/file'; + const mockAxiosInstance = { + post: vi.fn(), + } as any; + + fileDownloader = new FileDownloader(mockAxiosInstance); + await expect(() => + fileDownloader.download(url, { method: 'postt' }), + ).rejects.toThrow( + 'RequestClient does not support method "POSTT". Please ensure the method is properly implemented in your RequestClient instance.', + ); + }); +}); diff --git a/packages/effects/request/src/request-client/modules/downloader.ts b/packages/effects/request/src/request-client/modules/downloader.ts index 77e72c6c..6c300058 100644 --- a/packages/effects/request/src/request-client/modules/downloader.ts +++ b/packages/effects/request/src/request-client/modules/downloader.ts @@ -28,13 +28,32 @@ class FileDownloader { ): Promise { const finalConfig: DownloadRequestConfig = { responseReturn: 'body', + method: 'GET', ...config, responseType: 'blob', }; - const response = await this.client.get(url, finalConfig); + // Prefer a generic request if available; otherwise, dispatch to method-specific calls. + const method = (finalConfig.method || 'GET').toUpperCase(); + const clientAny = this.client as any; - return response; + if (typeof clientAny.request === 'function') { + return await clientAny.request(url, finalConfig); + } + const lower = method.toLowerCase(); + + if (typeof clientAny[lower] === 'function') { + if (['POST', 'PUT'].includes(method)) { + const { data, ...rest } = finalConfig as Record; + return await clientAny[lower](url, data, rest); + } + + return await clientAny[lower](url, finalConfig); + } + + throw new Error( + `RequestClient does not support method "${method}". Please ensure the method is properly implemented in your RequestClient instance.`, + ); } } diff --git a/packages/icons/src/svg/icons/tdesign-logo.svg b/packages/icons/src/svg/icons/tdesign-logo.svg new file mode 100644 index 00000000..bdbba5cd --- /dev/null +++ b/packages/icons/src/svg/icons/tdesign-logo.svg @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/playground/src/api/request.ts b/playground/src/api/request.ts index e741552d..a8855361 100644 --- a/playground/src/api/request.ts +++ b/playground/src/api/request.ts @@ -29,11 +29,15 @@ function createRequestClient(baseURL: string, options?: RequestClientOptions) { baseURL, transformResponse: (data: any, header: AxiosResponseHeaders) => { // storeAsString指示将BigInt存储为字符串,设为false则会存储为内置的BigInt类型 - return header.getContentType()?.toString().includes('application/json') - ? cloneDeep( - JSONBigInt({ storeAsString: true, strict: true }).parse(data), - ) - : data; + if ( + header.getContentType()?.toString().includes('application/json') && + typeof data === 'string' + ) { + return cloneDeep( + JSONBigInt({ storeAsString: true, strict: true }).parse(data), + ); + } + return data; }, }); diff --git a/playground/src/layouts/basic.vue b/playground/src/layouts/basic.vue index 05787651..25318499 100644 --- a/playground/src/layouts/basic.vue +++ b/playground/src/layouts/basic.vue @@ -2,6 +2,7 @@ import type { NotificationItem } from '@vben/layouts'; import { computed, onBeforeMount, ref, watch } from 'vue'; +import { useRouter } from 'vue-router'; import { AuthenticationLoginExpiredModal } from '@vben/common-ui'; import { VBEN_DOC_URL, VBEN_GITHUB_URL } from '@vben/constants'; @@ -36,6 +37,7 @@ setMenuList([ const notifications = ref([ { + id: 1, avatar: 'https://avatar.vercel.sh/vercel.svg?text=VB', date: '3小时前', isRead: true, @@ -43,6 +45,7 @@ const notifications = ref([ title: '收到了 14 份新周报', }, { + id: 2, avatar: 'https://avatar.vercel.sh/1', date: '刚刚', isRead: false, @@ -50,6 +53,7 @@ const notifications = ref([ title: '朱偏右 回复了你', }, { + id: 3, avatar: 'https://avatar.vercel.sh/1', date: '2024-01-01', isRead: false, @@ -57,14 +61,34 @@ const notifications = ref([ title: '曲丽丽 评论了你', }, { + id: 4, avatar: 'https://avatar.vercel.sh/satori', date: '1天前', isRead: false, message: '描述信息描述信息描述信息', title: '代办提醒', }, + { + id: 5, + avatar: 'https://avatar.vercel.sh/satori', + date: '1天前', + isRead: false, + message: '描述信息描述信息描述信息', + title: '跳转Workspace示例', + link: '/workspace', + }, + { + id: 6, + avatar: 'https://avatar.vercel.sh/satori', + date: '1天前', + isRead: false, + message: '描述信息描述信息描述信息', + title: '跳转外部链接示例', + link: 'https://doc.vben.pro', + }, ]); +const router = useRouter(); const userStore = useUserStore(); const authStore = useAuthStore(); const accessStore = useAccessStore(); @@ -74,6 +98,13 @@ const showDot = computed(() => ); const menus = computed(() => [ + { + handler: () => { + router.push({ name: 'Profile' }); + }, + icon: 'lucide:user', + text: $t('page.auth.profile'), + }, { handler: () => { openWindow(VBEN_DOC_URL, { @@ -115,6 +146,17 @@ function handleNoticeClear() { notifications.value = []; } +function markRead(id: number | string) { + const item = notifications.value.find((item) => item.id === id); + if (item) { + item.isRead = true; + } +} + +function remove(id: number | string) { + notifications.value = notifications.value.filter((item) => item.id !== id); +} + function handleMakeAll() { notifications.value.forEach((item) => (item.isRead = true)); } @@ -170,6 +212,8 @@ onBeforeMount(() => { :dot="showDot" :notifications="notifications" @clear="handleNoticeClear" + @read="(item) => item.id && markRead(item.id)" + @remove="(item) => item.id && remove(item.id)" @make-all="handleMakeAll" /> diff --git a/playground/src/locales/langs/en-US/demos.json b/playground/src/locales/langs/en-US/demos.json index 44b12780..8697ea3c 100644 --- a/playground/src/locales/langs/en-US/demos.json +++ b/playground/src/locales/langs/en-US/demos.json @@ -65,6 +65,7 @@ "document": "Document", "antdv": "Ant Design Vue Version", "naive-ui": "Naive UI Version", - "element-plus": "Element Plus Version" + "element-plus": "Element Plus Version", + "tdesign": "TDesign Vue Version" } } diff --git a/playground/src/locales/langs/en-US/page.json b/playground/src/locales/langs/en-US/page.json index 50f19dc4..32e323c2 100644 --- a/playground/src/locales/langs/en-US/page.json +++ b/playground/src/locales/langs/en-US/page.json @@ -6,7 +6,8 @@ "qrcodeLogin": "Qr Code Login", "forgetPassword": "Forget Password", "sendingCode": "SMS Code is sending...", - "codeSentTo": "Code has been sent to {0}" + "codeSentTo": "Code has been sent to {0}", + "profile": "Profile" }, "dashboard": { "title": "Dashboard", diff --git a/playground/src/locales/langs/zh-CN/demos.json b/playground/src/locales/langs/zh-CN/demos.json index 5cd87ce5..b7823b72 100644 --- a/playground/src/locales/langs/zh-CN/demos.json +++ b/playground/src/locales/langs/zh-CN/demos.json @@ -66,6 +66,7 @@ "document": "文档", "antdv": "Ant Design Vue 版本", "naive-ui": "Naive UI 版本", - "element-plus": "Element Plus 版本" + "element-plus": "Element Plus 版本", + "tdesign": "TDesign Vue 版本" } } diff --git a/playground/src/locales/langs/zh-CN/page.json b/playground/src/locales/langs/zh-CN/page.json index 666db5b7..437068c9 100644 --- a/playground/src/locales/langs/zh-CN/page.json +++ b/playground/src/locales/langs/zh-CN/page.json @@ -6,7 +6,8 @@ "qrcodeLogin": "二维码登录", "forgetPassword": "忘记密码", "sendingCode": "正在发送验证码", - "codeSentTo": "验证码已发送至{0}" + "codeSentTo": "验证码已发送至{0}", + "profile": "个人中心" }, "dashboard": { "title": "概览", diff --git a/playground/src/router/routes/modules/vben.ts b/playground/src/router/routes/modules/vben.ts index ec44d58a..6b621e9e 100644 --- a/playground/src/router/routes/modules/vben.ts +++ b/playground/src/router/routes/modules/vben.ts @@ -7,8 +7,9 @@ import { VBEN_GITHUB_URL, VBEN_LOGO_URL, VBEN_NAIVE_PREVIEW_URL, + VBEN_TD_PREVIEW_URL, } from '@vben/constants'; -import { SvgAntdvLogoIcon } from '@vben/icons'; +import { SvgAntdvLogoIcon, SvgTDesignIcon } from '@vben/icons'; import { IFrameView } from '#/layouts'; import { $t } from '#/locales'; @@ -77,6 +78,17 @@ const routes: RouteRecordRaw[] = [ title: $t('demos.vben.element-plus'), }, }, + { + name: 'VbenTDesign', + path: '/vben-admin/tdesign', + component: IFrameView, + meta: { + badgeType: 'dot', + icon: SvgTDesignIcon, + link: VBEN_TD_PREVIEW_URL, + title: $t('demos.vben.tdesign'), + }, + }, ], }, { @@ -89,6 +101,16 @@ const routes: RouteRecordRaw[] = [ name: 'VbenAbout', path: '/vben-admin/about', }, + { + name: 'Profile', + path: '/profile', + component: () => import('#/views/_core/profile/index.vue'), + meta: { + icon: 'lucide:user', + hideInMenu: true, + title: $t('page.auth.profile'), + }, + }, ]; export default routes; diff --git a/playground/src/views/_core/profile/base-setting.vue b/playground/src/views/_core/profile/base-setting.vue new file mode 100644 index 00000000..aa8a4c26 --- /dev/null +++ b/playground/src/views/_core/profile/base-setting.vue @@ -0,0 +1,65 @@ + + + + diff --git a/playground/src/views/_core/profile/index.vue b/playground/src/views/_core/profile/index.vue new file mode 100644 index 00000000..8740894e --- /dev/null +++ b/playground/src/views/_core/profile/index.vue @@ -0,0 +1,49 @@ + + + + + + + + + + + diff --git a/playground/src/views/_core/profile/notification-setting.vue b/playground/src/views/_core/profile/notification-setting.vue new file mode 100644 index 00000000..324a4b39 --- /dev/null +++ b/playground/src/views/_core/profile/notification-setting.vue @@ -0,0 +1,31 @@ + + + + diff --git a/playground/src/views/_core/profile/password-setting.vue b/playground/src/views/_core/profile/password-setting.vue new file mode 100644 index 00000000..b246bc37 --- /dev/null +++ b/playground/src/views/_core/profile/password-setting.vue @@ -0,0 +1,66 @@ + + + + diff --git a/playground/src/views/_core/profile/security-setting.vue b/playground/src/views/_core/profile/security-setting.vue new file mode 100644 index 00000000..be30db58 --- /dev/null +++ b/playground/src/views/_core/profile/security-setting.vue @@ -0,0 +1,43 @@ + + + + diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 0796669e..61899979 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -14,17 +14,17 @@ packages: - playground catalog: - '@ast-grep/napi': ^0.37.0 + '@ast-grep/napi': ^0.39.9 '@changesets/changelog-github': ^0.5.1 - '@changesets/cli': ^2.29.5 + '@changesets/cli': ^2.29.7 '@changesets/git': ^3.0.4 - '@clack/prompts': ^0.10.1 + '@clack/prompts': ^0.11.0 '@commitlint/cli': ^19.8.1 '@commitlint/config-conventional': ^19.8.1 '@ctrl/tinycolor': ^4.1.0 '@eslint/js': ^9.30.1 '@faker-js/faker': ^9.9.0 - '@iconify/json': ^2.2.354 + '@iconify/json': ^2.2.406 '@iconify/tailwind': ^1.2.0 '@iconify/vue': ^5.0.0 '@intlify/core-base': ^11.1.7 @@ -32,13 +32,13 @@ catalog: '@jspm/generator': ^2.6.2 '@manypkg/get-packages': ^3.0.0 '@nolebase/vitepress-plugin-git-changelog': ^2.18.0 - '@playwright/test': ^1.53.2 - '@pnpm/workspace.read-manifest': ^1000.2.0 + '@playwright/test': ^1.56.1 + '@pnpm/workspace.read-manifest': ^1000.2.6 '@stylistic/stylelint-plugin': ^3.1.3 '@tailwindcss/nesting': 0.0.0-insiders.565cd3e '@tailwindcss/typography': ^0.5.16 - '@tanstack/vue-query': ^5.81.5 - '@tanstack/vue-store': ^0.7.1 + '@tanstack/vue-query': ^5.91.0 + '@tanstack/vue-store': ^0.8.0 '@types/archiver': ^6.0.3 '@types/eslint': ^9.6.1 '@types/html-minifier-terser': ^7.0.2 @@ -54,21 +54,21 @@ catalog: '@types/qrcode': ^1.5.5 '@types/qs': ^6.14.0 '@types/sortablejs': ^1.15.8 - '@typescript-eslint/eslint-plugin': ^8.35.1 - '@typescript-eslint/parser': ^8.35.1 + '@typescript-eslint/eslint-plugin': ^8.46.4 + '@typescript-eslint/parser': ^8.46.4 '@vee-validate/zod': ^4.15.1 '@vite-pwa/vitepress': ^1.0.0 '@vitejs/plugin-vue': ^6.0.1 - '@vitejs/plugin-vue-jsx': ^5.0.1 + '@vitejs/plugin-vue-jsx': ^5.1.1 '@vue/reactivity': ^3.5.17 - '@vue/shared': ^3.5.17 + '@vue/shared': ^3.5.24 '@vue/test-utils': ^2.4.6 '@vueuse/core': ^13.4.0 '@vueuse/integrations': ^14.0.0 '@vueuse/motion': ^3.0.3 ant-design-vue: ^4.2.6 archiver: ^7.0.1 - autoprefixer: ^10.4.21 + autoprefixer: ^10.4.22 axios: ^1.10.0 axios-mock-adapter: ^2.1.0 cac: ^6.7.14 @@ -77,7 +77,7 @@ catalog: circular-dependency-scanner: ^2.3.0 class-variance-authority: ^0.7.1 clsx: ^2.1.1 - commitlint-plugin-function-rules: ^4.0.2 + commitlint-plugin-function-rules: ^4.1.1 consola: ^3.4.2 cross-env: ^7.0.3 cspell: ^8.19.4 @@ -88,10 +88,10 @@ catalog: defu: ^6.1.4 depcheck: ^1.4.7 dotenv: ^16.6.1 - echarts: ^5.6.0 + echarts: ^6.0.0 element-plus: ^2.10.2 eslint: ^9.30.1 - eslint-config-turbo: ^2.5.4 + eslint-config-turbo: ^2.6.1 eslint-plugin-command: ^3.3.1 eslint-plugin-eslint-comments: ^3.2.0 eslint-plugin-import-x: ^4.16.1 @@ -122,7 +122,7 @@ catalog: lodash.get: ^4.4.2 lodash.isequal: ^4.5.0 lodash.set: ^4.3.2 - lucide-vue-next: ^0.507.0 + lucide-vue-next: ^0.553.0 medium-zoom: ^1.1.0 naive-ui: ^2.42.0 nitropack: ^2.11.13 @@ -131,7 +131,7 @@ catalog: pinia: ^3.0.3 pinia-plugin-persistedstate: ^4.4.1 pkg-types: ^2.2.0 - playwright: ^1.53.2 + playwright: ^1.56.1 postcss: ^8.5.6 postcss-antd-fixes: ^0.2.0 postcss-html: ^1.8.0 @@ -139,21 +139,21 @@ catalog: postcss-preset-env: ^10.2.4 postcss-scss: ^4.0.9 prettier: ^3.6.2 - prettier-plugin-tailwindcss: ^0.6.13 + prettier-plugin-tailwindcss: ^0.7.1 publint: ^0.3.12 qrcode: ^1.5.4 qs: ^6.14.0 reka-ui: ^2.6.0 resolve.exports: ^2.0.3 - rimraf: ^6.0.1 + rimraf: ^6.1.0 rollup: ^4.44.1 rollup-plugin-visualizer: ^5.14.0 - sass: ^1.89.2 + sass: ^1.94.0 secure-ls: ^2.0.0 sortablejs: ^1.15.6 stylelint: ^16.21.0 stylelint-config-recess-order: ^6.1.0 - stylelint-config-recommended: ^16.0.0 + stylelint-config-recommended: ^17.0.0 stylelint-config-recommended-scss: ^14.1.0 stylelint-config-recommended-vue: ^1.6.1 stylelint-config-standard: ^38.0.0 @@ -161,16 +161,16 @@ catalog: stylelint-prettier: ^5.0.3 stylelint-scss: ^6.12.1 tailwind-merge: ^2.6.0 - tailwindcss: ^3.4.17 + tailwindcss: ^3.4.18 tailwindcss-animate: ^1.0.7 theme-colors: ^0.1.0 tippy.js: ^6.3.7 - turbo: ^2.5.4 - typescript: ^5.8.3 + turbo: ^2.6.1 + typescript: ^5.9.3 unbuild: ^3.6.1 - unplugin-element-plus: ^0.10.0 + unplugin-element-plus: ^0.11.1 vee-validate: ^4.15.1 - vite: ^7.1.2 + vite: ^7.2.2 vite-plugin-compression: ^0.5.1 vite-plugin-dts: ^4.5.4 vite-plugin-html: ^3.2.2 @@ -180,15 +180,16 @@ catalog: vitepress: ^1.6.3 vitepress-plugin-group-icons: ^1.6.1 vitest: ^3.2.4 - vue: ^3.5.17 + vue: ^3.5.24 vue-eslint-parser: ^10.2.0 vue-i18n: ^11.1.7 vue-json-viewer: ^3.0.4 vue-router: ^4.5.1 vue-tippy: ^6.7.1 vue-tsc: 2.2.10 - vxe-pc-ui: ^4.9.29 - vxe-table: ^4.16.11 + vxe-pc-ui: ^4.10.22 + vxe-table: ^4.17.14 watermark-js-plus: ^1.6.2 zod: ^3.25.67 - zod-defaults: ^0.1.3 + zod-defaults: 0.1.3 + es-toolkit: ^1.41.0