diff --git a/README.md b/README.md index 7a71e761..86995df8 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ ## 🐶 新手必读 -- nodejs > 20.10.0 && pnpm > 10.10.0 (强制使用pnpm) +- nodejs > 20.10.0 && pnpm > 10.14.0 (强制使用pnpm) - 演示地址【Vue3 + element-plus】: - 演示地址【Vue3 + vben5(ant-design-vue)】: - 演示地址【Vue2 + element-ui】: @@ -35,7 +35,7 @@ ## 外包项目请联系【非项目需求请勿扫码,非客服,不解答项目问题】 -![alt 定制开发](.image/wx-xingyu.png) +![alt 软件定制开发 数舵科技](.image/wx-xingyu.png) ## 技术栈 diff --git a/apps/web-antd/src/adapter/component/index.ts b/apps/web-antd/src/adapter/component/index.ts index f1ab1016..68e7e88b 100644 --- a/apps/web-antd/src/adapter/component/index.ts +++ b/apps/web-antd/src/adapter/component/index.ts @@ -22,6 +22,9 @@ const AutoComplete = defineAsyncComponent( () => import('ant-design-vue/es/auto-complete'), ); const Button = defineAsyncComponent(() => import('ant-design-vue/es/button')); +const Cascader = defineAsyncComponent( + () => import('ant-design-vue/es/cascader'), +); const Checkbox = defineAsyncComponent( () => import('ant-design-vue/es/checkbox'), ); @@ -59,6 +62,9 @@ const Textarea = defineAsyncComponent(() => const TimePicker = defineAsyncComponent( () => import('ant-design-vue/es/time-picker'), ); +const TimeRangePicker = defineAsyncComponent(() => + import('ant-design-vue/es/time-picker').then((res) => res.TimeRangePicker), +); const TreeSelect = defineAsyncComponent( () => import('ant-design-vue/es/tree-select'), ); @@ -100,6 +106,7 @@ const withDefaultPlaceholder = ( // 这里需要自行根据业务组件库进行适配,需要用到的组件都需要在这里类型说明 export type ComponentType = + | 'ApiCascader' | 'ApiSelect' | 'ApiTreeSelect' | 'AutoComplete' @@ -126,6 +133,7 @@ export type ComponentType = | 'Switch' | 'Textarea' | 'TimePicker' + | 'TimeRangePicker' | 'TreeSelect' | 'Upload' | BaseFormComponentType; @@ -135,6 +143,21 @@ async function initComponentAdapter() { // 如果你的组件体积比较大,可以使用异步加载 // Button: () => // import('xxx').then((res) => res.Button), + ApiCascader: withDefaultPlaceholder( + { + ...ApiComponent, + name: 'ApiCascader', + }, + 'select', + { + component: Cascader, + fieldNames: { label: 'label', value: 'value', children: 'children' }, + loadingSlot: 'suffixIcon', + modelPropName: 'value', + optionsPropName: 'treeData', + visibleEvent: 'onVisibleChange', + }, + ), ApiSelect: withDefaultPlaceholder( { ...ApiComponent, @@ -195,6 +218,7 @@ async function initComponentAdapter() { Textarea: withDefaultPlaceholder(Textarea, 'input'), RichTextarea, TimePicker, + TimeRangePicker, TreeSelect: withDefaultPlaceholder(TreeSelect, 'select'), Upload, FileUpload, diff --git a/apps/web-antd/src/adapter/style.css b/apps/web-antd/src/adapter/style.css deleted file mode 100644 index 01368a92..00000000 --- a/apps/web-antd/src/adapter/style.css +++ /dev/null @@ -1,79 +0,0 @@ -/* 来自 @vben/plugins/vxe-table style.css,覆盖 vxe-table 原有的样式定义,使用 vben 的样式主题 */ -:root { - --vxe-ui-font-color: hsl(var(--foreground)); - --vxe-ui-font-primary-color: hsl(var(--primary)); - - /* --vxe-ui-font-lighten-color: #babdc0; - --vxe-ui-font-darken-color: #86898e; */ - --vxe-ui-font-disabled-color: hsl(var(--foreground) / 50%); - - /* base */ - --vxe-ui-base-popup-border-color: hsl(var(--border)); - --vxe-ui-input-disabled-color: hsl(var(--border) / 60%); - - /* --vxe-ui-base-popup-box-shadow: 0px 12px 30px 8px rgb(0 0 0 / 50%); */ - - /* layout */ - --vxe-ui-layout-background-color: hsl(var(--background)); - --vxe-ui-table-resizable-line-color: hsl(var(--heavy)); - - /* --vxe-ui-table-fixed-left-scrolling-box-shadow: 8px 0px 10px -5px hsl(var(--accent)); - --vxe-ui-table-fixed-right-scrolling-box-shadow: -8px 0px 10px -5px hsl(var(--accent)); */ - - /* input */ - --vxe-ui-input-border-color: hsl(var(--border)); - - /* --vxe-ui-input-placeholder-color: #8d9095; */ - - /* --vxe-ui-input-disabled-background-color: #262727; */ - - /* loading */ - --vxe-ui-loading-background-color: hsl(var(--overlay-content)); - - /* table */ - --vxe-ui-table-header-background-color: hsl(var(--accent)); - --vxe-ui-table-border-color: hsl(var(--border)); - --vxe-ui-table-row-hover-background-color: hsl(var(--accent-hover)); - --vxe-ui-table-row-striped-background-color: hsl(var(--accent) / 60%); - --vxe-ui-table-row-hover-striped-background-color: hsl(var(--accent)); - --vxe-ui-table-row-radio-checked-background-color: hsl(var(--accent)); - --vxe-ui-table-row-hover-radio-checked-background-color: hsl( - var(--accent-hover) - ); - --vxe-ui-table-row-checkbox-checked-background-color: hsl(var(--accent)); - --vxe-ui-table-row-hover-checkbox-checked-background-color: hsl( - var(--accent-hover) - ); - --vxe-ui-table-row-current-background-color: hsl(var(--accent)); - --vxe-ui-table-row-hover-current-background-color: hsl(var(--accent-hover)); - --vxe-ui-font-primary-tinge-color: hsl(var(--primary)); - --vxe-ui-font-primary-lighten-color: hsl(var(--primary) / 60%); - --vxe-ui-font-primary-darken-color: hsl(var(--primary)); - - /* height: auto !important; */ - - /* --vxe-ui-table-fixed-scrolling-box-shadow-color: rgb(0 0 0 / 80%); */ -} - -.vxe-tools--operate { - margin-right: 0.25rem; - margin-left: 0.75rem; -} - -.vxe-table-custom--checkbox-option:hover { - background: none !important; -} - -.vxe-toolbar { - padding: 0; -} - -.vxe-buttons--wrapper:not(:empty), -.vxe-tools--operate:not(:empty), -.vxe-tools--wrapper:not(:empty) { - padding: 0.6em 0; -} - -.vxe-tools--operate:not(:has(button)) { - margin-left: 0; -} diff --git a/apps/web-antd/src/adapter/vxe-table.ts b/apps/web-antd/src/adapter/vxe-table.ts index e28a6ae1..96c85d12 100644 --- a/apps/web-antd/src/adapter/vxe-table.ts +++ b/apps/web-antd/src/adapter/vxe-table.ts @@ -6,7 +6,8 @@ import { h } from 'vue'; import { IconifyIcon } from '@vben/icons'; import { $te } from '@vben/locales'; import { - AsyncComponents, + AsyncVxeColumn, + AsyncVxeTable, createRequiredValidation, setupVbenVxeTable, useVbenVxeGrid, @@ -34,8 +35,6 @@ import { $t } from '#/locales'; import { useVbenForm } from './form'; -import '#/adapter/style.css'; - setupVbenVxeTable({ configVxeTable: (vxeUI) => { vxeUI.setConfig({ @@ -357,16 +356,8 @@ setupVbenVxeTable({ export { createRequiredValidation, useVbenVxeGrid }; -const [VxeTable, VxeColumn, VxeToolbar] = AsyncComponents; -export { VxeColumn, VxeTable, VxeToolbar }; +export const [VxeTable, VxeColumn] = [AsyncVxeTable, AsyncVxeColumn]; -// add by 芋艿:from https://github.com/vbenjs/vue-vben-admin/blob/main/playground/src/adapter/vxe-table.ts#L264-L270 -export type OnActionClickParams> = { - code: string; - row: T; -}; -export type OnActionClickFn> = ( - params: OnActionClickParams, -) => void; export * from '#/components/table-action'; + export type * from '@vben/plugins/vxe-table'; diff --git a/apps/web-antd/src/api/infra/codegen/index.ts b/apps/web-antd/src/api/infra/codegen/index.ts index c72f3ff1..c8dc9044 100644 --- a/apps/web-antd/src/api/infra/codegen/index.ts +++ b/apps/web-antd/src/api/infra/codegen/index.ts @@ -71,7 +71,7 @@ export namespace InfraCodegenApi { } /** 创建代码生成请求 */ - export interface CodegenCreateListReq { + export interface CodegenCreateListReqVO { dataSourceConfigId?: number; tableNames: string[]; } @@ -136,7 +136,7 @@ export function getSchemaTableList(params: any) { } /** 基于数据库的表结构,创建代码生成器的表定义 */ -export function createCodegenList(data: InfraCodegenApi.CodegenCreateListReq) { +export function createCodegenList(data: InfraCodegenApi.CodegenCreateListReqVO) { return requestClient.post('/infra/codegen/create-list', data); } diff --git a/apps/web-antd/src/api/mall/product/spu.ts b/apps/web-antd/src/api/mall/product/spu.ts index 3d9a1513..76ca7b55 100644 --- a/apps/web-antd/src/api/mall/product/spu.ts +++ b/apps/web-antd/src/api/mall/product/spu.ts @@ -113,6 +113,8 @@ export namespace MallSpuApi { createTime?: Date; /** 商品状态 */ status?: number; + /** 浏览量 */ + browseCount?: number; } /** 商品状态更新 */ diff --git a/apps/web-antd/src/api/mall/trade/delivery/pickUpStore/index.ts b/apps/web-antd/src/api/mall/trade/delivery/pickUpStore/index.ts index fe1d9d39..b97f2c3d 100644 --- a/apps/web-antd/src/api/mall/trade/delivery/pickUpStore/index.ts +++ b/apps/web-antd/src/api/mall/trade/delivery/pickUpStore/index.ts @@ -31,6 +31,8 @@ export namespace MallDeliveryPickUpStoreApi { status: number; /** 绑定用户编号组数 */ verifyUserIds: number[]; + /** 营业时间 用于fieldMappingTime */ + rangeTime: any[]; } /** 绑定自提店员请求 */ diff --git a/apps/web-antd/src/api/pay/app/index.ts b/apps/web-antd/src/api/pay/app/index.ts index d44e5ae2..53f67674 100644 --- a/apps/web-antd/src/api/pay/app/index.ts +++ b/apps/web-antd/src/api/pay/app/index.ts @@ -16,6 +16,7 @@ export namespace PayAppApi { merchantId: number; merchantName: string; createTime?: Date; + channelCodes: string[]; } /** 更新状态请求 */ diff --git a/apps/web-antd/src/api/system/mail/template/index.ts b/apps/web-antd/src/api/system/mail/template/index.ts index 5f7e7525..ef08e248 100644 --- a/apps/web-antd/src/api/system/mail/template/index.ts +++ b/apps/web-antd/src/api/system/mail/template/index.ts @@ -19,7 +19,7 @@ export namespace SystemMailTemplateApi { } /** 邮件发送信息 */ - export interface MailSendReq { + export interface MailSendReqVO { toMails: string[]; ccMails?: string[]; bccMails?: string[]; @@ -66,6 +66,6 @@ export function deleteMailTemplateList(ids: number[]) { } /** 发送邮件 */ -export function sendMail(data: SystemMailTemplateApi.MailSendReq) { +export function sendMail(data: SystemMailTemplateApi.MailSendReqVO) { return requestClient.post('/system/mail-template/send-mail', data); } diff --git a/apps/web-antd/src/api/system/notify/template/index.ts b/apps/web-antd/src/api/system/notify/template/index.ts index 5eab6fa4..dd19f4b8 100644 --- a/apps/web-antd/src/api/system/notify/template/index.ts +++ b/apps/web-antd/src/api/system/notify/template/index.ts @@ -17,7 +17,7 @@ export namespace SystemNotifyTemplateApi { } /** 发送站内信请求 */ - export interface NotifySendReq { + export interface NotifySendReqVO { userId: number; userType: number; templateCode: string; @@ -74,6 +74,6 @@ export function exportNotifyTemplate(params: any) { } /** 发送站内信 */ -export function sendNotify(data: SystemNotifyTemplateApi.NotifySendReq) { +export function sendNotify(data: SystemNotifyTemplateApi.NotifySendReqVO) { return requestClient.post('/system/notify-template/send-notify', data); } diff --git a/apps/web-antd/src/api/system/oauth2/client/index.ts b/apps/web-antd/src/api/system/oauth2/client/index.ts index 7c1db774..5817b858 100644 --- a/apps/web-antd/src/api/system/oauth2/client/index.ts +++ b/apps/web-antd/src/api/system/oauth2/client/index.ts @@ -55,3 +55,10 @@ export function updateOAuth2Client(data: SystemOAuth2ClientApi.OAuth2Client) { export function deleteOAuth2Client(id: number) { return requestClient.delete(`/system/oauth2-client/delete?id=${id}`); } + +/** 批量删除 OAuth2.0 客户端 */ +export function deleteOAuth2ClientList(ids: number[]) { + return requestClient.delete( + `/system/oauth2-client/delete-list?ids=${ids.join(',')}`, + ); +} diff --git a/apps/web-antd/src/api/system/sms/template/index.ts b/apps/web-antd/src/api/system/sms/template/index.ts index 8f8e4c8c..eccfb911 100644 --- a/apps/web-antd/src/api/system/sms/template/index.ts +++ b/apps/web-antd/src/api/system/sms/template/index.ts @@ -20,7 +20,7 @@ export namespace SystemSmsTemplateApi { } /** 发送短信请求 */ - export interface SmsSendReq { + export interface SmsSendReqVO { mobile: string; templateCode: string; templateParams: Record; @@ -72,6 +72,6 @@ export function exportSmsTemplate(params: any) { } /** 发送短信 */ -export function sendSms(data: SystemSmsTemplateApi.SmsSendReq) { +export function sendSms(data: SystemSmsTemplateApi.SmsSendReqVO) { return requestClient.post('/system/sms-template/send-sms', data); } diff --git a/apps/web-antd/src/api/system/social/client/index.ts b/apps/web-antd/src/api/system/social/client/index.ts index d4da4dd4..181cdf86 100644 --- a/apps/web-antd/src/api/system/social/client/index.ts +++ b/apps/web-antd/src/api/system/social/client/index.ts @@ -46,3 +46,10 @@ export function updateSocialClient(data: SystemSocialClientApi.SocialClient) { export function deleteSocialClient(id: number) { return requestClient.delete(`/system/social-client/delete?id=${id}`); } + +/** 批量删除社交客户端 */ +export function deleteSocialClientList(ids: number[]) { + return requestClient.delete( + `/system/social-client/delete-list?ids=${ids.join(',')}`, + ); +} diff --git a/apps/web-antd/src/components/description/description.vue b/apps/web-antd/src/components/description/description.vue index 25dab3ce..23682e7a 100644 --- a/apps/web-antd/src/components/description/description.vue +++ b/apps/web-antd/src/components/description/description.vue @@ -29,8 +29,8 @@ const Description = defineComponent({ }, setup(props: DescriptionsOptions) { - // TODO @puhui999:每个 field 的 slot 的考虑 - // TODO @puhui999:from 5.0:extra: () => getSlot(slots, 'extra') + // TODO @xingyu:每个 field 的 slot 的考虑 + // TODO @xingyu:from 5.0:extra: () => getSlot(slots, 'extra') /** 过滤掉不需要展示的 */ const shouldShowItem = (item: DescriptionItemSchema) => { if (item.hidden === undefined) return true; @@ -75,6 +75,6 @@ const Description = defineComponent({ }, }); -// TODO @puhui999:from 5.0:emits: ['register'] 事件 +// TODO @xingyu:from 5.0:emits: ['register'] 事件 export default Description; diff --git a/apps/web-antd/src/components/description/typing.ts b/apps/web-antd/src/components/description/typing.ts index c1628d24..a0e99777 100644 --- a/apps/web-antd/src/components/description/typing.ts +++ b/apps/web-antd/src/components/description/typing.ts @@ -2,9 +2,9 @@ import type { DescriptionsProps } from 'ant-design-vue'; import type { CSSProperties, VNode } from 'vue'; -// TODO @puhui999:【content】这个纠结下;1)vben2.0 是 render;https://doc.vvbin.cn/components/desc.html#usage 2) -// TODO @puhui999:vben2.0 还有 sapn【done】、labelMinWidth、contentMinWidth -// TODO @puhui999:【hidden】这个纠结下;1)vben2.0 是 show; +// TODO @xingyu:【content】这个纠结下;1)vben2.0 是 render;https://doc.vvbin.cn/components/desc.html#usage 2) +// TODO @xingyu:vben2.0 还有 sapn【done】、labelMinWidth、contentMinWidth +// TODO @xingyu:【hidden】这个纠结下;1)vben2.0 是 show; export interface DescriptionItemSchema { label: string | VNode; // 内容的描述 field?: string; // 对应 data 中的字段名 @@ -15,11 +15,11 @@ export interface DescriptionItemSchema { hidden?: ((data: any) => boolean) | boolean; // 是否显示 } -// TODO @puhui999:vben2.0 还有 title【done】、bordered【done】d、useCollapse、collapseOptions -// TODO @puhui999:from 5.0:bordered 默认为 true -// TODO @puhui999:from 5.0:column 默认为 lg: 3, md: 3, sm: 2, xl: 3, xs: 1, xxl: 4 -// TODO @puhui999:from 5.0:size 默认为 small;有 'default', 'middle', 'small', undefined -// TODO @puhui999:from 5.0:useCollapse 默认为 true +// TODO @xingyu:vben2.0 还有 title【done】、bordered【done】d、useCollapse、collapseOptions +// TODO @xingyu:from 5.0:bordered 默认为 true +// TODO @xingyu:from 5.0:column 默认为 lg: 3, md: 3, sm: 2, xl: 3, xs: 1, xxl: 4 +// TODO @xingyu:from 5.0:size 默认为 small;有 'default', 'middle', 'small', undefined +// TODO @xingyu:from 5.0:useCollapse 默认为 true export interface DescriptionsOptions { data?: Record; // 数据 schema?: DescriptionItemSchema[]; // 描述项配置 diff --git a/apps/web-antd/src/components/description/use-description.ts b/apps/web-antd/src/components/description/use-description.ts index c131be26..7f99238b 100644 --- a/apps/web-antd/src/components/description/use-description.ts +++ b/apps/web-antd/src/components/description/use-description.ts @@ -16,7 +16,7 @@ class DescriptionApi { return this.state as DescriptionsOptions; } - // TODO @puhui999:【setState】纠结下:1)vben2.0 是 data https://doc.vvbin.cn/components/desc.html#usage; + // TODO @xingyu:【setState】纠结下:1)vben2.0 是 data https://doc.vvbin.cn/components/desc.html#usage; setState(newState: Partial) { this.state = { ...this.state, ...newState }; } @@ -27,7 +27,7 @@ export type ExtendedDescriptionApi = DescriptionApi; export function useDescription(options: DescriptionsOptions) { const IS_REACTIVE = isReactive(options); const api = new DescriptionApi(options); - // 扩展API + // 扩展 API const extendedApi: ExtendedDescriptionApi = api as never; const Desc = defineComponent({ name: 'UseDescription', diff --git a/apps/web-antd/src/components/dict-tag/dict-tag.vue b/apps/web-antd/src/components/dict-tag/dict-tag.vue index c51b523c..635cb8ea 100644 --- a/apps/web-antd/src/components/dict-tag/dict-tag.vue +++ b/apps/web-antd/src/components/dict-tag/dict-tag.vue @@ -1,12 +1,11 @@ - - diff --git a/apps/web-antd/src/hooks/index.ts b/apps/web-antd/src/hooks/index.ts deleted file mode 100644 index 75f01b53..00000000 --- a/apps/web-antd/src/hooks/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './use-table-toolbar'; diff --git a/apps/web-antd/src/hooks/use-table-toolbar.ts b/apps/web-antd/src/hooks/use-table-toolbar.ts deleted file mode 100644 index c35b3a77..00000000 --- a/apps/web-antd/src/hooks/use-table-toolbar.ts +++ /dev/null @@ -1,47 +0,0 @@ -import type { VxeTableInstance, VxeToolbarInstance } from '#/adapter/vxe-table'; -import type { TableToolbar } from '#/components/table-toolbar'; - -import { ref, watch } from 'vue'; - -/** - * vxe 原生工具栏挂载封装 - * 解决每个组件使用 vxe-table 组件时都需要写一遍的问题 - */ -export function useTableToolbar() { - const hiddenSearchBar = ref(false); // 隐藏搜索栏 - const tableToolbarRef = ref>(); - const tableRef = ref(); - const isBound = ref(false); - - /** 挂载 toolbar 工具栏 */ - async function bindTableToolbar() { - const table = tableRef.value; - const tableToolbar = tableToolbarRef.value; - if (table && tableToolbar) { - // 延迟 1 秒,确保 toolbar 组件已经挂载 - setTimeout(async () => { - const toolbar = tableToolbar.getToolbarRef(); - if (!toolbar) { - console.error('[toolbar 挂载失败] Table toolbar not found'); - } - await table.connect(toolbar as VxeToolbarInstance); - isBound.value = true; - }, 1000); // 延迟挂载确保 toolbar 正确挂载 - } - } - - watch( - () => tableRef.value, - async (val) => { - if (!val || isBound.value) return; - await bindTableToolbar(); - }, - { immediate: true }, - ); - - return { - hiddenSearchBar, - tableToolbarRef, - tableRef, - }; -} diff --git a/apps/web-antd/src/router/guard.ts b/apps/web-antd/src/router/guard.ts index f969c0ff..8b88c834 100644 --- a/apps/web-antd/src/router/guard.ts +++ b/apps/web-antd/src/router/guard.ts @@ -3,14 +3,14 @@ import type { Router } from 'vue-router'; import { LOGIN_PATH } from '@vben/constants'; import { $t } from '@vben/locales'; import { preferences } from '@vben/preferences'; -import { useAccessStore, useUserStore } from '@vben/stores'; +import { useAccessStore, useDictStore, useUserStore } from '@vben/stores'; import { startProgress, stopProgress } from '@vben/utils'; import { message } from 'ant-design-vue'; import { getSimpleDictDataList } from '#/api/system/dict/data'; import { accessRoutes, coreRouteNames } from '#/router/routes'; -import { useAuthStore, useDictStore } from '#/store'; +import { useAuthStore } from '#/store'; import { generateAccess } from './access'; diff --git a/apps/web-antd/src/router/routes/modules/mall.ts b/apps/web-antd/src/router/routes/modules/mall.ts index 41e50a1e..d5d39646 100644 --- a/apps/web-antd/src/router/routes/modules/mall.ts +++ b/apps/web-antd/src/router/routes/modules/mall.ts @@ -1,76 +1,76 @@ -// import type { RouteRecordRaw } from 'vue-router'; +import type { RouteRecordRaw } from 'vue-router'; -// const routes: RouteRecordRaw[] = [ -// { -// path: '/mall/product', -// name: 'ProductCenter', -// meta: { -// title: '商品中心', -// icon: 'lucide:shopping-bag', -// keepAlive: true, -// hideInMenu: true, -// }, -// children: [ -// { -// path: 'spu/add', -// name: 'ProductSpuAdd', -// meta: { -// title: '商品添加', -// activeMenu: '/mall/product/spu', -// }, -// component: () => import('#/views/mall/product/spu/form/index.vue'), -// }, -// { -// path: String.raw`spu/edit/:id(\d+)`, -// name: 'ProductSpuEdit', -// meta: { -// title: '商品编辑', -// activeMenu: '/mall/product/spu', -// }, -// component: () => import('#/views/mall/product/spu/form/index.vue'), -// }, -// { -// path: String.raw`spu/detail/:id(\d+)`, -// name: 'ProductSpuDetail', -// meta: { -// title: '商品详情', -// activeMenu: '/crm/business', -// }, -// component: () => import('#/views/mall/product/spu/form/index.vue'), -// }, -// ], -// }, -// { -// path: '/mall/trade', -// name: 'TradeCenter', -// meta: { -// title: '交易中心', -// icon: 'lucide:shopping-cart', -// keepAlive: true, -// hideInMenu: true, -// }, -// children: [ -// { -// path: String.raw`order/detail/:id(\d+)`, -// name: 'TradeOrderDetail', -// meta: { -// title: '订单详情', -// activeMenu: '/mall/trade/order', -// }, -// component: () => import('#/views/mall/trade/order/detail/index.vue'), -// }, -// { -// path: String.raw`after-sale/detail/:id(\d+)`, -// name: 'TradeAfterSaleDetail', -// meta: { -// title: '退款详情', -// activeMenu: '/mall/trade/after-sale', -// }, -// component: () => -// import('#/views/mall/trade/afterSale/detail/index.vue'), -// }, -// ], -// }, -// ]; +const routes: RouteRecordRaw[] = [ + { + path: '/mall/product', + name: 'ProductCenter', + meta: { + title: '商品中心', + icon: 'lucide:shopping-bag', + keepAlive: true, + hideInMenu: true, + }, + children: [ + { + path: 'spu/add', + name: 'ProductSpuAdd', + meta: { + title: '商品添加', + activeMenu: '/mall/product/spu', + }, + component: () => import('#/views/mall/product/spu/modules/form.vue'), + }, + { + path: String.raw`spu/edit/:id(\d+)`, + name: 'ProductSpuEdit', + meta: { + title: '商品编辑', + activeMenu: '/mall/product/spu', + }, + component: () => import('#/views/mall/product/spu/modules/form.vue'), + }, + { + path: String.raw`spu/detail/:id(\d+)`, + name: 'ProductSpuDetail', + meta: { + title: '商品详情', + activeMenu: '/crm/business', + }, + component: () => import('#/views/mall/product/spu/modules/detail.vue'), + }, + ], + }, + { + path: '/mall/trade', + name: 'TradeCenter', + meta: { + title: '交易中心', + icon: 'lucide:shopping-cart', + keepAlive: true, + hideInMenu: true, + }, + children: [ + { + path: String.raw`order/detail/:id(\d+)`, + name: 'TradeOrderDetail', + meta: { + title: '订单详情', + activeMenu: '/mall/trade/order', + }, + component: () => import('#/views/mall/trade/order/modules/detail.vue'), + }, + { + path: String.raw`after-sale/detail/:id(\d+)`, + name: 'TradeAfterSaleDetail', + meta: { + title: '退款详情', + activeMenu: '/mall/trade/after-sale', + }, + component: () => + import('#/views/mall/trade/afterSale/modules/detail.vue'), + }, + ], + }, +]; -// export default routes; +export default routes; diff --git a/apps/web-antd/src/store/index.ts b/apps/web-antd/src/store/index.ts index b6a7763b..269586ee 100644 --- a/apps/web-antd/src/store/index.ts +++ b/apps/web-antd/src/store/index.ts @@ -1,2 +1 @@ export * from './auth'; -export * from './dict'; diff --git a/apps/web-antd/src/utils/dict.ts b/apps/web-antd/src/utils/dict.ts deleted file mode 100644 index 2329729b..00000000 --- a/apps/web-antd/src/utils/dict.ts +++ /dev/null @@ -1,192 +0,0 @@ -// TODO @芋艿:后续再优化 -// TODO @芋艿:可以共享么? - -import type { DictItem } from '#/store'; - -import { isObject } from '@vben/utils'; - -import { useDictStore } from '#/store'; - -// TODO @dhb52:top-level 调用 导致:"getActivePinia()" was called but there was no active Pinia -// 先临时移入到方法中 -// const dictStore = useDictStore(); - -/** AntD 组件的颜色类型 */ -type ColorType = 'error' | 'info' | 'success' | 'warning'; - -/** 字典值类型 */ -type DictValueType = 'boolean' | 'number' | 'string'; - -/** 基础字典数据类型 */ -export interface DictDataType { - dictType?: string; - label: string; - value: boolean | number | string; - colorType?: string; - cssClass?: string; -} - -/** 数字类型字典数据 */ -export interface NumberDictDataType extends DictDataType { - value: number; -} - -/** 字符串类型字典数据 */ -export interface StringDictDataType extends DictDataType { - value: string; -} - -/** 布尔类型字典数据 */ -export interface BooleanDictDataType extends DictDataType { - value: boolean; -} - -/** 字典缓存管理器 */ -class DictCacheManager { - private cache = new Map(); - private maxCacheSize = 100; // 最大缓存数量 - - /** 清空缓存 */ - clear(): void { - this.cache.clear(); - } - - /** 删除指定字典类型的缓存 */ - delete(dictType: string): void { - const keysToDelete = []; - for (const key of this.cache.keys()) { - if (key.startsWith(`${dictType}:`)) { - keysToDelete.push(key); - } - } - keysToDelete.forEach((key) => this.cache.delete(key)); - } - - /** 获取缓存数据 */ - get(dictType: string, valueType: DictValueType): DictDataType[] | undefined { - return this.cache.get(this.getCacheKey(dictType, valueType)); - } - - /** 设置缓存数据 */ - set(dictType: string, valueType: DictValueType, data: DictDataType[]): void { - const key = this.getCacheKey(dictType, valueType); - - // 如果缓存数量超过限制,删除最早的缓存 - if (this.cache.size >= this.maxCacheSize) { - const firstKey = this.cache.keys().next().value; - if (firstKey) { - this.cache.delete(firstKey); - } - } - - this.cache.set(key, data); - } - - /** 获取缓存键 */ - private getCacheKey(dictType: string, valueType: DictValueType): string { - return `${dictType}:${valueType}`; - } -} - -/** 字典缓存实例 */ -const dictCache = new DictCacheManager(); - -/** 值转换器映射 */ -const valueConverters: Record< - DictValueType, - (value: any) => boolean | number | string -> = { - boolean: (value: any) => `${value}` === 'true', - number: (value: any) => Number.parseInt(`${value}`, 10), - string: (value: any) => `${value}`, -}; - -/** - * 获取字典标签 - * @param dictType 字典类型 - * @param value 字典值 - * @returns 字典标签 - */ -function getDictLabel(dictType: string, value: any): string { - const dictStore = useDictStore(); - const dictObj = dictStore.getDictData(dictType, value); - return isObject(dictObj) ? dictObj.label : ''; -} - -/** - * 获取字典对象 - * @param dictType 字典类型 - * @param value 字典值 - * @returns 字典对象 - */ -function getDictObj(dictType: string, value: any): DictItem | null { - const dictStore = useDictStore(); - const dictObj = dictStore.getDictData(dictType, value); - return isObject(dictObj) ? dictObj : null; -} - -/** - * 获取字典数组 - 优化版本,支持缓存和泛型 - * @param dictType 字典类型 - * @param valueType 字典值类型,默认 string 类型 - * @returns 字典数组 - */ -function getDictOptions( - dictType: string, - valueType: T = 'string' as T, -): T extends 'number' - ? NumberDictDataType[] - : T extends 'boolean' - ? BooleanDictDataType[] - : StringDictDataType[] { - // 检查缓存 - const cachedData = dictCache.get(dictType, valueType); - if (cachedData) { - return cachedData as any; - } - - const dictStore = useDictStore(); - const dictOpts = dictStore.getDictOptions(dictType); - - if (dictOpts.length === 0) { - return [] as any; - } - - const converter = valueConverters[valueType]; - const dictOptions: DictDataType[] = dictOpts.map((d: DictItem) => ({ - value: converter(d.value), - label: d.label, - colorType: d.colorType, - cssClass: d.cssClass, - })); - - // 缓存结果 - dictCache.set(dictType, valueType, dictOptions); - - return dictOptions as any; -} - -/** - * 清空字典缓存 - */ -export const clearDictCache = (): void => { - dictCache.clear(); -}; - -/** - * 删除指定字典类型的缓存 - * @param dictType 字典类型 - */ -export const deleteDictCache = (dictType: string): void => { - dictCache.delete(dictType); -}; - -export { - type ColorType, - type DictValueType, - getDictLabel, - getDictObj, - getDictOptions, -}; - -export { DICT_TYPE } from '@vben/constants'; diff --git a/apps/web-antd/src/utils/index.ts b/apps/web-antd/src/utils/index.ts index 25ad9869..b1d2a146 100644 --- a/apps/web-antd/src/utils/index.ts +++ b/apps/web-antd/src/utils/index.ts @@ -1,5 +1,3 @@ -export * from './dict'; export * from './formCreate'; export * from './rangePickerProps'; export * from './routerHelper'; -export { CommonStatusEnum } from '@vben/constants'; diff --git a/apps/web-antd/src/utils/rangePickerProps.ts b/apps/web-antd/src/utils/rangePickerProps.ts index 245e3d81..69b753ba 100644 --- a/apps/web-antd/src/utils/rangePickerProps.ts +++ b/apps/web-antd/src/utils/rangePickerProps.ts @@ -48,6 +48,7 @@ export function getRangePickerDefaultProps() { format: 'HH:mm:ss', }, transformDateFunc: (dates: any) => { + // TODO @xingyu:貌似这个没用??? if (dates && dates.length === 2) { // 格式化为后台支持的时间格式 return [dates.createTime[0], dates.createTime[1]].join(','); diff --git a/apps/web-antd/src/views/_core/profile/modules/base-info.vue b/apps/web-antd/src/views/_core/profile/modules/base-info.vue index 894b785a..abfb2668 100644 --- a/apps/web-antd/src/views/_core/profile/modules/base-info.vue +++ b/apps/web-antd/src/views/_core/profile/modules/base-info.vue @@ -5,13 +5,14 @@ import type { SystemUserProfileApi } from '#/api/system/user/profile'; import { watch } from 'vue'; +import { DICT_TYPE } from '@vben/constants'; +import { getDictOptions } from '@vben/hooks'; import { $t } from '@vben/locales'; import { message } from 'ant-design-vue'; import { useVbenForm, z } from '#/adapter/form'; import { updateUserProfile } from '#/api/system/user/profile'; -import { DICT_TYPE, getDictOptions } from '#/utils'; const props = defineProps<{ profile?: SystemUserProfileApi.UserProfileResp; diff --git a/apps/web-antd/src/views/_core/profile/modules/user-social.vue b/apps/web-antd/src/views/_core/profile/modules/user-social.vue index 4a977bb6..8a34b177 100644 --- a/apps/web-antd/src/views/_core/profile/modules/user-social.vue +++ b/apps/web-antd/src/views/_core/profile/modules/user-social.vue @@ -6,7 +6,8 @@ import { computed, onMounted, ref } from 'vue'; import { useRoute } from 'vue-router'; import { confirm } from '@vben/common-ui'; -import { SystemUserSocialTypeEnum } from '@vben/constants'; +import { DICT_TYPE, SystemUserSocialTypeEnum } from '@vben/constants'; +import { getDictLabel } from '@vben/hooks'; import { getUrlValue } from '@vben/utils'; import { Button, Card, Image, message } from 'ant-design-vue'; @@ -19,7 +20,6 @@ import { socialUnbind, } from '#/api/system/social/user'; import { $t } from '#/locales'; -import { DICT_TYPE, getDictLabel } from '#/utils'; const emit = defineEmits<{ (e: 'update:activeName', v: string): void; diff --git a/apps/web-antd/src/views/ai/chat/manager/data.ts b/apps/web-antd/src/views/ai/chat/manager/data.ts index 8aeb4829..e9379d91 100644 --- a/apps/web-antd/src/views/ai/chat/manager/data.ts +++ b/apps/web-antd/src/views/ai/chat/manager/data.ts @@ -1,8 +1,10 @@ import type { VbenFormSchema } from '#/adapter/form'; import type { VxeTableGridOptions } from '#/adapter/vxe-table'; +import { DICT_TYPE } from '@vben/constants'; + import { getSimpleUserList } from '#/api/system/user'; -import { DICT_TYPE, getRangePickerDefaultProps } from '#/utils'; +import { getRangePickerDefaultProps } from '#/utils'; /** 列表的搜索表单 */ export function useGridFormSchemaConversation(): VbenFormSchema[] { diff --git a/apps/web-antd/src/views/ai/image/index/components/midjourney/index.vue b/apps/web-antd/src/views/ai/image/index/components/midjourney/index.vue index c4dc490c..bd753d6c 100644 --- a/apps/web-antd/src/views/ai/image/index/components/midjourney/index.vue +++ b/apps/web-antd/src/views/ai/image/index/components/midjourney/index.vue @@ -225,7 +225,7 @@ defineExpose({ settingValues }); +