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 12a3b718..859718e1 100644 --- a/apps/web-antd/src/locales/langs/en-US/demos.json +++ b/apps/web-antd/src/locales/langs/en-US/demos.json @@ -6,6 +6,7 @@ "about": "About", "document": "Document", "antdv": "Ant Design Vue Version", + "antdv-next": "Antdv Next Version", "naive-ui": "Naive UI Version", "element-plus": "Element Plus Version", "tdesign": "TDesign Vue Version" 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 b5007ea7..29910cdf 100644 --- a/apps/web-antd/src/locales/langs/zh-CN/demos.json +++ b/apps/web-antd/src/locales/langs/zh-CN/demos.json @@ -6,6 +6,7 @@ "about": "关于", "document": "文档", "antdv": "Ant Design Vue 版本", + "antdv-next": "Antdv Next 版本", "naive-ui": "Naive UI 版本", "element-plus": "Element Plus 版本", "tdesign": "TDesign Vue 版本" diff --git a/apps/web-antd/src/router/routes/modules/vben.ts b/apps/web-antd/src/router/routes/modules/vben.ts index 631facc0..283d83e5 100644 --- a/apps/web-antd/src/router/routes/modules/vben.ts +++ b/apps/web-antd/src/router/routes/modules/vben.ts @@ -1,6 +1,7 @@ import type { RouteRecordRaw } from 'vue-router'; import { + VBEN_ANTDV_NEXT_PREVIEW_URL, VBEN_DOC_URL, VBEN_ELE_PREVIEW_URL, VBEN_GITHUB_URL, @@ -8,7 +9,7 @@ import { VBEN_NAIVE_PREVIEW_URL, VBEN_TD_PREVIEW_URL, } from '@vben/constants'; -import { SvgTDesignIcon } from '@vben/icons'; +import { SvgAntdvNextLogoIcon, SvgTDesignIcon } from '@vben/icons'; import { IFrameView } from '#/layouts'; import { $t } from '#/locales'; @@ -44,6 +45,17 @@ const routes: RouteRecordRaw[] = [ title: 'Github', }, }, + { + name: 'VbenAntdVNext', + path: '/vben-admin/antdv-next', + component: IFrameView, + meta: { + badgeType: 'dot', + icon: SvgAntdvNextLogoIcon, + link: VBEN_ANTDV_NEXT_PREVIEW_URL, + title: $t('demos.vben.antdv-next'), + }, + }, { name: 'VbenNaive', path: '/vben-admin/naive', diff --git a/apps/web-antdv-next/.env b/apps/web-antdv-next/.env new file mode 100644 index 00000000..40b046d2 --- /dev/null +++ b/apps/web-antdv-next/.env @@ -0,0 +1,8 @@ +# 应用标题 +VITE_APP_TITLE=Vben Admin Antdv Next + +# 应用命名空间,用于缓存、store等功能的前缀,确保隔离 +VITE_APP_NAMESPACE=vben-web-antdv-next + +# 对store进行加密的密钥,在将store持久化到localStorage时会使用该密钥进行加密 +VITE_APP_STORE_SECURE_KEY=please-replace-me-with-your-own-key diff --git a/apps/web-antdv-next/.env.analyze b/apps/web-antdv-next/.env.analyze new file mode 100644 index 00000000..ffafa8dd --- /dev/null +++ b/apps/web-antdv-next/.env.analyze @@ -0,0 +1,7 @@ +# public path +VITE_BASE=/ + +# Basic interface address SPA +VITE_GLOB_API_URL=/api + +VITE_VISUALIZER=true diff --git a/apps/web-antdv-next/.env.development b/apps/web-antdv-next/.env.development new file mode 100644 index 00000000..f2b44428 --- /dev/null +++ b/apps/web-antdv-next/.env.development @@ -0,0 +1,16 @@ +# 端口号 +VITE_PORT=5999 + +VITE_BASE=/ + +# 接口地址 +VITE_GLOB_API_URL=/api + +# 是否开启 Nitro Mock服务,true 为开启,false 为关闭 +VITE_NITRO_MOCK=true + +# 是否打开 devtools,true 为打开,false 为关闭 +VITE_DEVTOOLS=false + +# 是否注入全局loading +VITE_INJECT_APP_LOADING=true diff --git a/apps/web-antdv-next/.env.production b/apps/web-antdv-next/.env.production new file mode 100644 index 00000000..5375847a --- /dev/null +++ b/apps/web-antdv-next/.env.production @@ -0,0 +1,19 @@ +VITE_BASE=/ + +# 接口地址 +VITE_GLOB_API_URL=https://mock-napi.vben.pro/api + +# 是否开启压缩,可以设置为 none, brotli, gzip +VITE_COMPRESS=none + +# 是否开启 PWA +VITE_PWA=false + +# vue-router 的模式 +VITE_ROUTER_HISTORY=hash + +# 是否注入全局loading +VITE_INJECT_APP_LOADING=true + +# 打包后是否生成dist.zip +VITE_ARCHIVER=true diff --git a/apps/web-antdv-next/index.html b/apps/web-antdv-next/index.html new file mode 100644 index 00000000..480eb84d --- /dev/null +++ b/apps/web-antdv-next/index.html @@ -0,0 +1,35 @@ + + + + + + + + + + + + <%= VITE_APP_TITLE %> + + + + +
+ + + diff --git a/apps/web-antdv-next/package.json b/apps/web-antdv-next/package.json new file mode 100644 index 00000000..acbb5e95 --- /dev/null +++ b/apps/web-antdv-next/package.json @@ -0,0 +1,50 @@ +{ + "name": "@vben/web-antdv-next", + "version": "5.5.9", + "homepage": "https://vben.pro", + "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", + "repository": { + "type": "git", + "url": "git+https://github.com/vbenjs/vue-vben-admin.git", + "directory": "apps/web-antdv-next" + }, + "license": "MIT", + "author": { + "name": "vben", + "email": "ann.vben@gmail.com", + "url": "https://github.com/anncwb" + }, + "type": "module", + "scripts": { + "build": "pnpm vite build --mode production", + "build:analyze": "pnpm vite build --mode analyze", + "dev": "pnpm vite --mode development", + "preview": "vite preview", + "typecheck": "vue-tsc --noEmit --skipLibCheck" + }, + "imports": { + "#/*": "./src/*" + }, + "dependencies": { + "@vben/access": "workspace:*", + "@vben/common-ui": "workspace:*", + "@vben/constants": "workspace:*", + "@vben/hooks": "workspace:*", + "@vben/icons": "workspace:*", + "@vben/layouts": "workspace:*", + "@vben/locales": "workspace:*", + "@vben/plugins": "workspace:*", + "@vben/preferences": "workspace:*", + "@vben/request": "workspace:*", + "@vben/stores": "workspace:*", + "@vben/styles": "workspace:*", + "@vben/types": "workspace:*", + "@vben/utils": "workspace:*", + "@vueuse/core": "catalog:", + "antdv-next": "catalog:", + "dayjs": "catalog:", + "pinia": "catalog:", + "vue": "catalog:", + "vue-router": "catalog:" + } +} diff --git a/apps/web-antdv-next/postcss.config.mjs b/apps/web-antdv-next/postcss.config.mjs new file mode 100644 index 00000000..3d807045 --- /dev/null +++ b/apps/web-antdv-next/postcss.config.mjs @@ -0,0 +1 @@ +export { default } from '@vben/tailwind-config/postcss'; diff --git a/apps/web-antdv-next/public/favicon.ico b/apps/web-antdv-next/public/favicon.ico new file mode 100644 index 00000000..fcf9818e Binary files /dev/null and b/apps/web-antdv-next/public/favicon.ico differ diff --git a/apps/web-antdv-next/src/adapter/component/index.ts b/apps/web-antdv-next/src/adapter/component/index.ts new file mode 100644 index 00000000..4ce4f57a --- /dev/null +++ b/apps/web-antdv-next/src/adapter/component/index.ts @@ -0,0 +1,603 @@ +/** + * 通用组件共同的使用的基础组件,原先放在 adapter/form 内部,限制了使用范围,这里提取出来,方便其他地方使用 + * 可用于 vben-form、vben-modal、vben-drawer 等组件使用, + */ + +/* eslint-disable vue/one-component-per-file */ + +import type { UploadChangeParam, UploadFile, UploadProps } from 'antdv-next'; + +import type { Component, Ref } from 'vue'; + +import type { BaseFormComponentType } from '@vben/common-ui'; +import type { Recordable } from '@vben/types'; + +import { + computed, + defineAsyncComponent, + defineComponent, + h, + ref, + render, + unref, + watch, +} from 'vue'; + +import { + ApiComponent, + globalShareState, + IconPicker, + VCropper, +} from '@vben/common-ui'; +import { IconifyIcon } from '@vben/icons'; +import { $t } from '@vben/locales'; +import { isEmpty } from '@vben/utils'; + +import { message, Modal, notification } from 'antdv-next'; + +const AutoComplete = defineAsyncComponent( + () => import('antdv-next/dist/auto-complete/index'), +); +const Button = defineAsyncComponent( + () => import('antdv-next/dist/button/index'), +); +const Checkbox = defineAsyncComponent( + () => import('antdv-next/dist/checkbox/index'), +); +const CheckboxGroup = defineAsyncComponent( + () => import('antdv-next/dist/checkbox/Group'), +); +const DatePicker = defineAsyncComponent( + () => import('antdv-next/dist/date-picker/index'), +); +const Divider = defineAsyncComponent( + () => import('antdv-next/dist/divider/index'), +); +const Input = defineAsyncComponent(() => import('antdv-next/dist/input/index')); +const InputNumber = defineAsyncComponent( + () => import('antdv-next/dist/input-number/index'), +); +const InputPassword = defineAsyncComponent(() => + import('antdv-next/dist/input/index').then((res) => res.InputPassword), +); +const Mentions = defineAsyncComponent( + () => import('antdv-next/dist/mentions/index'), +); +const Radio = defineAsyncComponent(() => import('antdv-next/dist/radio/index')); +const RadioGroup = defineAsyncComponent(() => + import('antdv-next/dist/radio/index').then((res) => res.RadioGroup), +); +const RangePicker = defineAsyncComponent(() => + import('antdv-next/dist/date-picker/index').then( + (res) => res.DateRangePicker, + ), +); +const Rate = defineAsyncComponent(() => import('antdv-next/dist/rate/index')); +const Select = defineAsyncComponent( + () => import('antdv-next/dist/select/index'), +); +const Space = defineAsyncComponent(() => import('antdv-next/dist/space/index')); +const Switch = defineAsyncComponent( + () => import('antdv-next/dist/switch/index'), +); +const Textarea = defineAsyncComponent( + () => import('antdv-next/dist/input/TextArea'), +); +const TimePicker = defineAsyncComponent( + () => import('antdv-next/dist/time-picker/index'), +); +const TreeSelect = defineAsyncComponent( + () => import('antdv-next/dist/tree-select/index'), +); +const Cascader = defineAsyncComponent( + () => import('antdv-next/dist/cascader/index'), +); +const Upload = defineAsyncComponent( + () => import('antdv-next/dist/upload/index'), +); +const Image = defineAsyncComponent(() => import('antdv-next/dist/image/index')); +const PreviewGroup = defineAsyncComponent(() => + import('antdv-next/dist/image/index').then((res) => res.ImagePreviewGroup), +); + +const withDefaultPlaceholder = ( + component: T, + type: 'input' | 'select', + componentProps: Recordable = {}, +) => { + return defineComponent({ + name: component.name, + inheritAttrs: false, + setup: (props: any, { attrs, expose, slots }) => { + const placeholder = + props?.placeholder || + attrs?.placeholder || + $t(`ui.placeholder.${type}`); + // 透传组件暴露的方法 + const innerRef = ref(); + expose( + new Proxy( + {}, + { + get: (_target, key) => innerRef.value?.[key], + has: (_target, key) => key in (innerRef.value || {}), + }, + ), + ); + return () => + h( + component, + { ...componentProps, placeholder, ...props, ...attrs, ref: innerRef }, + slots, + ); + }, + }); +}; + +const withPreviewUpload = () => { + // 检查是否为图片文件的辅助函数 + const isImageFile = (file: UploadFile): boolean => { + const imageExtensions = new Set([ + 'bmp', + 'gif', + 'jpeg', + 'jpg', + 'png', + 'svg', + 'webp', + ]); + if (file.url) { + try { + const pathname = new URL(file.url, 'http://localhost').pathname; + const ext = pathname.split('.').pop()?.toLowerCase(); + return ext ? imageExtensions.has(ext) : false; + } catch { + const ext = file.url?.split('.').pop()?.toLowerCase(); + return ext ? imageExtensions.has(ext) : false; + } + } + if (!file.type) { + const ext = file.name?.split('.').pop()?.toLowerCase(); + return ext ? imageExtensions.has(ext) : false; + } + return file.type.startsWith('image/'); + }; + // 创建默认的上传按钮插槽 + const createDefaultSlotsWithUpload = ( + listType: string, + placeholder: string, + ) => { + switch (listType) { + case 'picture-card': { + return { + default: () => placeholder, + }; + } + default: { + return { + default: () => + h( + Button, + { + icon: h(IconifyIcon, { + icon: 'ant-design:upload-outlined', + class: 'mb-1 size-4', + }), + }, + () => placeholder, + ), + }; + } + } + }; + // 构建预览图片组 + const previewImage = async ( + file: UploadFile, + visible: Ref, + fileList: Ref, + ) => { + // 如果当前文件不是图片,直接打开 + if (!isImageFile(file)) { + if (file.url) { + window.open(file.url, '_blank'); + } else if (file.preview) { + window.open(file.preview, '_blank'); + } else { + message.error($t('ui.formRules.previewWarning')); + } + return; + } + + // 对于图片文件,继续使用预览组 + const [ImageComponent, PreviewGroupComponent] = await Promise.all([ + Image, + PreviewGroup, + ]); + + const getBase64 = (file: File) => { + return new Promise((resolve, reject) => { + const reader = new FileReader(); + reader.readAsDataURL(file); + reader.addEventListener('load', () => resolve(reader.result)); + reader.addEventListener('error', (error) => reject(error)); + }); + }; + // 从fileList中过滤出所有图片文件 + const imageFiles = (unref(fileList) || []).filter((element) => + isImageFile(element), + ); + + // 为所有没有预览地址的图片生成预览 + for (const imgFile of imageFiles) { + if (!imgFile.url && !imgFile.preview && imgFile.originFileObj) { + imgFile.preview = (await getBase64(imgFile.originFileObj)) as string; + } + } + const container: HTMLElement | null = document.createElement('div'); + document.body.append(container); + + // 用于追踪组件是否已卸载 + let isUnmounted = false; + + const PreviewWrapper = { + setup() { + return () => { + if (isUnmounted) return null; + return h( + PreviewGroupComponent, + { + class: 'hidden', + preview: { + open: visible.value, + // 设置初始显示的图片索引 + current: imageFiles.findIndex((f) => f.uid === file.uid), + onOpenChange: (value: boolean) => { + visible.value = value; + if (!value) { + // 延迟清理,确保动画完成 + setTimeout(() => { + if (!isUnmounted && container) { + isUnmounted = true; + render(null, container); + container.remove(); + } + }, 300); + } + }, + }, + }, + () => + // 渲染所有图片文件 + imageFiles.map((imgFile) => + h(ImageComponent, { + key: imgFile.uid, + src: imgFile.url || imgFile.preview, + }), + ), + ); + }; + }, + }; + + render(h(PreviewWrapper), container); + }; + + // 图片裁剪操作 + const cropImage = (file: File, aspectRatio: string | undefined) => { + return new Promise((resolve, reject) => { + const container: HTMLElement | null = document.createElement('div'); + document.body.append(container); + + // 用于追踪组件是否已卸载 + let isUnmounted = false; + let objectUrl: null | string = null; + + const open = ref(true); + const cropperRef = ref | null>(null); + + const closeModal = () => { + open.value = false; + // 延迟清理,确保动画完成 + setTimeout(() => { + if (!isUnmounted && container) { + if (objectUrl) { + URL.revokeObjectURL(objectUrl); + } + isUnmounted = true; + render(null, container); + container.remove(); + } + }, 300); + }; + + const CropperWrapper = { + setup() { + return () => { + if (isUnmounted) return null; + if (!objectUrl) { + objectUrl = URL.createObjectURL(file); + } + return h( + Modal, + { + open: open.value, + title: h('div', {}, [ + $t('ui.crop.title'), + h( + 'span', + { + class: `${aspectRatio ? '' : 'hidden'} ml-2 text-sm text-gray-400 font-normal`, + }, + $t('ui.crop.titleTip', [aspectRatio]), + ), + ]), + centered: true, + width: 548, + keyboard: false, + maskClosable: false, + closable: false, + cancelText: $t('common.cancel'), + okText: $t('ui.crop.confirm'), + destroyOnHidden: true, + onOk: async () => { + const cropper = cropperRef.value; + if (!cropper) { + reject(new Error('Cropper not found')); + closeModal(); + return; + } + try { + const dataUrl = await cropper.getCropImage(); + resolve(dataUrl); + } catch { + reject(new Error($t('ui.crop.errorTip'))); + } finally { + closeModal(); + } + }, + onCancel() { + resolve(''); + closeModal(); + }, + }, + () => + h(VCropper, { + ref: (ref: any) => (cropperRef.value = ref), + img: objectUrl as string, + aspectRatio, + }), + ); + }; + }, + }; + + render(h(CropperWrapper), container); + }); + }; + + return defineComponent({ + name: 'AUpload', + emits: ['update:modelValue'], + setup: ( + props: any, + { attrs, slots, emit }: { attrs: any; emit: any; slots: any }, + ) => { + const previewVisible = ref(false); + + const placeholder = attrs?.placeholder || $t(`ui.placeholder.upload`); + + const listType = attrs?.listType || attrs?.['list-type'] || 'text'; + + const fileList = ref( + attrs?.fileList || attrs?.['file-list'] || [], + ); + + const maxSize = computed(() => attrs?.maxSize ?? attrs?.['max-size']); + const aspectRatio = computed( + () => attrs?.aspectRatio ?? attrs?.['aspect-ratio'], + ); + + const handleBeforeUpload = async ( + file: UploadFile, + originFileList: Array, + ) => { + if (maxSize.value && (file.size || 0) / 1024 / 1024 > maxSize.value) { + message.error($t('ui.formRules.sizeLimit', [maxSize.value])); + file.status = 'removed'; + return false; + } + // 多选或者非图片不唤起裁剪框 + if ( + attrs.crop && + !attrs.multiple && + originFileList[0] && + isImageFile(file) + ) { + file.status = 'removed'; + // antd Upload组件问题 file参数获取的是UploadFile类型对象无法取到File类型 所以通过originFileList[0]获取 + const blob = await cropImage(originFileList[0], aspectRatio.value); + return new Promise((resolve, reject) => { + if (!blob) { + return reject(new Error($t('ui.crop.errorTip'))); + } + resolve(blob); + }); + } + + return attrs.beforeUpload?.(file) ?? true; + }; + + const handleChange = (event: UploadChangeParam) => { + try { + // 行内写法 handleChange: (event) => {} + attrs.handleChange?.(event); + // template写法 @handle-change="(event) => {}" + attrs.onHandleChange?.(event); + } catch (error) { + // Avoid breaking internal v-model sync on user handler errors + console.error(error); + } + fileList.value = event.fileList.filter( + (file) => file.status !== 'removed', + ); + emit( + 'update:modelValue', + event.fileList?.length ? fileList.value : undefined, + ); + }; + + const handlePreview = async (file: UploadFile) => { + previewVisible.value = true; + await previewImage(file, previewVisible, fileList); + }; + + const renderUploadButton = (): any => { + const isDisabled = attrs.disabled; + + // 如果禁用,不渲染上传按钮 + if (isDisabled) { + return null; + } + + // 否则渲染默认上传按钮 + return isEmpty(slots) + ? createDefaultSlotsWithUpload(listType, placeholder) + : slots; + }; + + // 可以监听到表单API设置的值 + watch( + () => attrs.modelValue, + (res) => { + fileList.value = res; + }, + ); + + return () => + h( + Upload, + { + ...props, + ...attrs, + fileList: fileList.value, + beforeUpload: handleBeforeUpload, + onChange: handleChange, + onPreview: handlePreview, + }, + renderUploadButton(), + ); + }, + }); +}; + +// 这里需要自行根据业务组件库进行适配,需要用到的组件都需要在这里类型说明 +export type ComponentType = + | 'ApiCascader' + | 'ApiSelect' + | 'ApiTreeSelect' + | 'AutoComplete' + | 'Cascader' + | 'Checkbox' + | 'CheckboxGroup' + | 'DatePicker' + | 'DefaultButton' + | 'Divider' + | 'IconPicker' + | 'Input' + | 'InputNumber' + | 'InputPassword' + | 'Mentions' + | 'PrimaryButton' + | 'Radio' + | 'RadioGroup' + | 'RangePicker' + | 'Rate' + | 'Select' + | 'Space' + | 'Switch' + | 'Textarea' + | 'TimePicker' + | 'TreeSelect' + | 'Upload' + | BaseFormComponentType; + +async function initComponentAdapter() { + const components: Partial> = { + // 如果你的组件体积比较大,可以使用异步加载 + // Button: () => + // import('xxx').then((res) => res.Button), + + ApiCascader: withDefaultPlaceholder(ApiComponent, 'select', { + component: Cascader, + fieldNames: { label: 'label', value: 'value', children: 'children' }, + loadingSlot: 'suffixIcon', + modelPropName: 'value', + visibleEvent: 'onVisibleChange', + }), + ApiSelect: withDefaultPlaceholder(ApiComponent, 'select', { + component: Select, + loadingSlot: 'suffixIcon', + modelPropName: 'value', + visibleEvent: 'onVisibleChange', + }), + ApiTreeSelect: withDefaultPlaceholder(ApiComponent, 'select', { + component: TreeSelect, + fieldNames: { label: 'label', value: 'value', children: 'children' }, + loadingSlot: 'suffixIcon', + modelPropName: 'value', + optionsPropName: 'treeData', + visibleEvent: 'onVisibleChange', + }), + AutoComplete, + Cascader, + Checkbox, + CheckboxGroup, + DatePicker, + // 自定义默认按钮 + DefaultButton: (props, { attrs, slots }) => { + return h(Button, { ...props, attrs, type: 'default' }, slots); + }, + Divider, + IconPicker: withDefaultPlaceholder(IconPicker, 'select', { + iconSlot: 'addonAfter', + inputComponent: Input, + modelValueProp: 'value', + }), + Input: withDefaultPlaceholder(Input, 'input'), + InputNumber: withDefaultPlaceholder(InputNumber, 'input'), + InputPassword: withDefaultPlaceholder(InputPassword, 'input'), + Mentions: withDefaultPlaceholder(Mentions, 'input'), + // 自定义主要按钮 + PrimaryButton: (props, { attrs, slots }) => { + return h(Button, { ...props, attrs, type: 'primary' }, slots); + }, + Radio, + RadioGroup, + RangePicker, + Rate, + Select: withDefaultPlaceholder(Select, 'select'), + Space, + Switch, + Textarea: withDefaultPlaceholder(Textarea, 'input'), + TimePicker, + TreeSelect: withDefaultPlaceholder(TreeSelect, 'select'), + Upload: withPreviewUpload(), + }; + + // 将组件注册到全局共享状态中 + globalShareState.setComponents(components); + + // 定义全局共享状态中的消息提示 + globalShareState.defineMessage({ + // 复制成功消息提示 + copyPreferencesSuccess: (title, content) => { + notification.success({ + description: content, + title, + placement: 'bottomRight', + }); + }, + }); +} + +export { initComponentAdapter }; diff --git a/apps/web-antdv-next/src/adapter/form.ts b/apps/web-antdv-next/src/adapter/form.ts new file mode 100644 index 00000000..983a7f51 --- /dev/null +++ b/apps/web-antdv-next/src/adapter/form.ts @@ -0,0 +1,49 @@ +import type { + VbenFormSchema as FormSchema, + VbenFormProps, +} from '@vben/common-ui'; + +import type { ComponentType } from './component'; + +import { setupVbenForm, useVbenForm as useForm, z } from '@vben/common-ui'; +import { $t } from '@vben/locales'; + +async function initSetupVbenForm() { + setupVbenForm({ + config: { + // ant design vue组件库默认都是 v-model:value + baseModelPropName: 'value', + + // 一些组件是 v-model:checked 或者 v-model:fileList + modelPropNameMap: { + Checkbox: 'checked', + Radio: 'checked', + Switch: 'checked', + Upload: 'fileList', + }, + }, + defineRules: { + // 输入项目必填国际化适配 + required: (value, _params, ctx) => { + if (value === undefined || value === null || value.length === 0) { + return $t('ui.formRules.required', [ctx.label]); + } + return true; + }, + // 选择项目必填国际化适配 + selectRequired: (value, _params, ctx) => { + if (value === undefined || value === null) { + return $t('ui.formRules.selectRequired', [ctx.label]); + } + return true; + }, + }, + }); +} + +const useVbenForm = useForm; + +export { initSetupVbenForm, useVbenForm, z }; + +export type VbenFormSchema = FormSchema; +export type { VbenFormProps }; diff --git a/apps/web-antdv-next/src/adapter/vxe-table.ts b/apps/web-antdv-next/src/adapter/vxe-table.ts new file mode 100644 index 00000000..3b5d83b3 --- /dev/null +++ b/apps/web-antdv-next/src/adapter/vxe-table.ts @@ -0,0 +1,70 @@ +import type { VxeTableGridOptions } from '@vben/plugins/vxe-table'; + +import { h } from 'vue'; + +import { setupVbenVxeTable, useVbenVxeGrid } from '@vben/plugins/vxe-table'; + +import { Button, Image } from 'antdv-next'; + +import { useVbenForm } from './form'; + +setupVbenVxeTable({ + configVxeTable: (vxeUI) => { + vxeUI.setConfig({ + grid: { + align: 'center', + border: false, + columnConfig: { + resizable: true, + }, + minHeight: 180, + formConfig: { + // 全局禁用vxe-table的表单配置,使用formOptions + enabled: false, + }, + proxyConfig: { + autoLoad: true, + response: { + result: 'items', + total: 'total', + list: 'items', + }, + showActiveMsg: true, + showResponseMsg: false, + }, + round: true, + showOverflow: true, + size: 'small', + } as VxeTableGridOptions, + }); + + // 表格配置项可以用 cellRender: { name: 'CellImage' }, + vxeUI.renderer.add('CellImage', { + renderTableDefault(renderOpts, params) { + const { props } = renderOpts; + const { column, row } = params; + return h(Image, { src: row[column.field], ...props }); + }, + }); + + // 表格配置项可以用 cellRender: { name: 'CellLink' }, + vxeUI.renderer.add('CellLink', { + renderTableDefault(renderOpts) { + const { props } = renderOpts; + return h( + Button, + { size: 'small', type: 'link' }, + { default: () => props?.text }, + ); + }, + }); + + // 这里可以自行扩展 vxe-table 的全局配置,比如自定义格式化 + // vxeUI.formats.add + }, + useVbenForm, +}); + +export { useVbenVxeGrid }; + +export type * from '@vben/plugins/vxe-table'; diff --git a/apps/web-antdv-next/src/api/core/auth.ts b/apps/web-antdv-next/src/api/core/auth.ts new file mode 100644 index 00000000..71d9f994 --- /dev/null +++ b/apps/web-antdv-next/src/api/core/auth.ts @@ -0,0 +1,51 @@ +import { baseRequestClient, requestClient } from '#/api/request'; + +export namespace AuthApi { + /** 登录接口参数 */ + export interface LoginParams { + password?: string; + username?: string; + } + + /** 登录接口返回值 */ + export interface LoginResult { + accessToken: string; + } + + export interface RefreshTokenResult { + data: string; + status: number; + } +} + +/** + * 登录 + */ +export async function loginApi(data: AuthApi.LoginParams) { + return requestClient.post('/auth/login', data); +} + +/** + * 刷新accessToken + */ +export async function refreshTokenApi() { + return baseRequestClient.post('/auth/refresh', { + withCredentials: true, + }); +} + +/** + * 退出登录 + */ +export async function logoutApi() { + return baseRequestClient.post('/auth/logout', { + withCredentials: true, + }); +} + +/** + * 获取用户权限码 + */ +export async function getAccessCodesApi() { + return requestClient.get('/auth/codes'); +} diff --git a/apps/web-antdv-next/src/api/core/index.ts b/apps/web-antdv-next/src/api/core/index.ts new file mode 100644 index 00000000..28a5aef4 --- /dev/null +++ b/apps/web-antdv-next/src/api/core/index.ts @@ -0,0 +1,3 @@ +export * from './auth'; +export * from './menu'; +export * from './user'; diff --git a/apps/web-antdv-next/src/api/core/menu.ts b/apps/web-antdv-next/src/api/core/menu.ts new file mode 100644 index 00000000..9ef60b11 --- /dev/null +++ b/apps/web-antdv-next/src/api/core/menu.ts @@ -0,0 +1,10 @@ +import type { RouteRecordStringComponent } from '@vben/types'; + +import { requestClient } from '#/api/request'; + +/** + * 获取用户所有菜单 + */ +export async function getAllMenusApi() { + return requestClient.get('/menu/all'); +} diff --git a/apps/web-antdv-next/src/api/core/user.ts b/apps/web-antdv-next/src/api/core/user.ts new file mode 100644 index 00000000..7e28ea84 --- /dev/null +++ b/apps/web-antdv-next/src/api/core/user.ts @@ -0,0 +1,10 @@ +import type { UserInfo } from '@vben/types'; + +import { requestClient } from '#/api/request'; + +/** + * 获取用户信息 + */ +export async function getUserInfoApi() { + return requestClient.get('/user/info'); +} diff --git a/apps/web-antdv-next/src/api/index.ts b/apps/web-antdv-next/src/api/index.ts new file mode 100644 index 00000000..4b0e0413 --- /dev/null +++ b/apps/web-antdv-next/src/api/index.ts @@ -0,0 +1 @@ +export * from './core'; diff --git a/apps/web-antdv-next/src/api/request.ts b/apps/web-antdv-next/src/api/request.ts new file mode 100644 index 00000000..d97a2163 --- /dev/null +++ b/apps/web-antdv-next/src/api/request.ts @@ -0,0 +1,113 @@ +/** + * 该文件可自行根据业务逻辑进行调整 + */ +import type { RequestClientOptions } from '@vben/request'; + +import { useAppConfig } from '@vben/hooks'; +import { preferences } from '@vben/preferences'; +import { + authenticateResponseInterceptor, + defaultResponseInterceptor, + errorMessageResponseInterceptor, + RequestClient, +} from '@vben/request'; +import { useAccessStore } from '@vben/stores'; + +import { message } from 'antdv-next'; + +import { useAuthStore } from '#/store'; + +import { refreshTokenApi } from './core'; + +const { apiURL } = useAppConfig(import.meta.env, import.meta.env.PROD); + +function createRequestClient(baseURL: string, options?: RequestClientOptions) { + const client = new RequestClient({ + ...options, + baseURL, + }); + + /** + * 重新认证逻辑 + */ + async function doReAuthenticate() { + console.warn('Access token or refresh token is invalid or expired. '); + const accessStore = useAccessStore(); + const authStore = useAuthStore(); + accessStore.setAccessToken(null); + if ( + preferences.app.loginExpiredMode === 'modal' && + accessStore.isAccessChecked + ) { + accessStore.setLoginExpired(true); + } else { + await authStore.logout(); + } + } + + /** + * 刷新token逻辑 + */ + async function doRefreshToken() { + const accessStore = useAccessStore(); + const resp = await refreshTokenApi(); + const newToken = resp.data; + accessStore.setAccessToken(newToken); + return newToken; + } + + function formatToken(token: null | string) { + return token ? `Bearer ${token}` : null; + } + + // 请求头处理 + client.addRequestInterceptor({ + fulfilled: async (config) => { + const accessStore = useAccessStore(); + + config.headers.Authorization = formatToken(accessStore.accessToken); + config.headers['Accept-Language'] = preferences.app.locale; + return config; + }, + }); + + // 处理返回的响应数据格式 + client.addResponseInterceptor( + defaultResponseInterceptor({ + codeField: 'code', + dataField: 'data', + successCode: 0, + }), + ); + + // token过期的处理 + client.addResponseInterceptor( + authenticateResponseInterceptor({ + client, + doReAuthenticate, + doRefreshToken, + enableRefreshToken: preferences.app.enableRefreshToken, + formatToken, + }), + ); + + // 通用的错误处理,如果没有进入上面的错误处理逻辑,就会进入这里 + client.addResponseInterceptor( + errorMessageResponseInterceptor((msg: string, error) => { + // 这里可以根据业务进行定制,你可以拿到 error 内的信息进行定制化处理,根据不同的 code 做不同的提示,而不是直接使用 message.error 提示 msg + // 当前mock接口返回的错误字段是 error 或者 message + const responseData = error?.response?.data ?? {}; + const errorMessage = responseData?.error ?? responseData?.message ?? ''; + // 如果没有错误信息,则会根据状态码进行提示 + message.error(errorMessage || msg); + }), + ); + + return client; +} + +export const requestClient = createRequestClient(apiURL, { + responseReturn: 'data', +}); + +export const baseRequestClient = new RequestClient({ baseURL: apiURL }); diff --git a/apps/web-antdv-next/src/app.vue b/apps/web-antdv-next/src/app.vue new file mode 100644 index 00000000..59ca8618 --- /dev/null +++ b/apps/web-antdv-next/src/app.vue @@ -0,0 +1,39 @@ + + + diff --git a/apps/web-antdv-next/src/bootstrap.ts b/apps/web-antdv-next/src/bootstrap.ts new file mode 100644 index 00000000..8c161703 --- /dev/null +++ b/apps/web-antdv-next/src/bootstrap.ts @@ -0,0 +1,76 @@ +import { createApp, watchEffect } from 'vue'; + +import { registerAccessDirective } from '@vben/access'; +import { registerLoadingDirective } from '@vben/common-ui/es/loading'; +import { preferences } from '@vben/preferences'; +import { initStores } from '@vben/stores'; +import '@vben/styles'; +import '@vben/styles/antdv-next'; + +import { useTitle } from '@vueuse/core'; + +import { $t, setupI18n } from '#/locales'; + +import { initComponentAdapter } from './adapter/component'; +import { initSetupVbenForm } from './adapter/form'; +import App from './app.vue'; +import { router } from './router'; + +async function bootstrap(namespace: string) { + // 初始化组件适配器 + await initComponentAdapter(); + + // 初始化表单组件 + await initSetupVbenForm(); + + // // 设置弹窗的默认配置 + // setDefaultModalProps({ + // fullscreenButton: false, + // }); + // // 设置抽屉的默认配置 + // setDefaultDrawerProps({ + // zIndex: 1020, + // }); + + const app = createApp(App); + + // 注册v-loading指令 + registerLoadingDirective(app, { + loading: 'loading', // 在这里可以自定义指令名称,也可以明确提供false表示不注册这个指令 + spinning: 'spinning', + }); + + // 国际化 i18n 配置 + await setupI18n(app); + + // 配置 pinia-tore + await initStores(app, { namespace }); + + // 安装权限指令 + registerAccessDirective(app); + + // 初始化 tippy + const { initTippy } = await import('@vben/common-ui/es/tippy'); + initTippy(app); + + // 配置路由及路由守卫 + app.use(router); + + // 配置Motion插件 + const { MotionPlugin } = await import('@vben/plugins/motion'); + app.use(MotionPlugin); + + // 动态更新标题 + watchEffect(() => { + if (preferences.app.dynamicTitle) { + const routeTitle = router.currentRoute.value.meta?.title; + const pageTitle = + (routeTitle ? `${$t(routeTitle)} - ` : '') + preferences.app.name; + useTitle(pageTitle); + } + }); + + app.mount('#app'); +} + +export { bootstrap }; diff --git a/apps/web-antdv-next/src/layouts/auth.vue b/apps/web-antdv-next/src/layouts/auth.vue new file mode 100644 index 00000000..8ba66e85 --- /dev/null +++ b/apps/web-antdv-next/src/layouts/auth.vue @@ -0,0 +1,25 @@ + + + diff --git a/apps/web-antdv-next/src/layouts/basic.vue b/apps/web-antdv-next/src/layouts/basic.vue new file mode 100644 index 00000000..2226c68a --- /dev/null +++ b/apps/web-antdv-next/src/layouts/basic.vue @@ -0,0 +1,206 @@ + + + diff --git a/apps/web-antdv-next/src/layouts/index.ts b/apps/web-antdv-next/src/layouts/index.ts new file mode 100644 index 00000000..a4320780 --- /dev/null +++ b/apps/web-antdv-next/src/layouts/index.ts @@ -0,0 +1,6 @@ +const BasicLayout = () => import('./basic.vue'); +const AuthPageLayout = () => import('./auth.vue'); + +const IFrameView = () => import('@vben/layouts').then((m) => m.IFrameView); + +export { AuthPageLayout, BasicLayout, IFrameView }; diff --git a/apps/web-antdv-next/src/locales/README.md b/apps/web-antdv-next/src/locales/README.md new file mode 100644 index 00000000..7b451032 --- /dev/null +++ b/apps/web-antdv-next/src/locales/README.md @@ -0,0 +1,3 @@ +# locale + +每个app使用的国际化可能不同,这里用于扩展国际化的功能,例如扩展 dayjs、antd组件库的多语言切换,以及app本身的国际化文件。 diff --git a/apps/web-antdv-next/src/locales/index.ts b/apps/web-antdv-next/src/locales/index.ts new file mode 100644 index 00000000..2586d859 --- /dev/null +++ b/apps/web-antdv-next/src/locales/index.ts @@ -0,0 +1,102 @@ +import type { Locale } from 'antdv-next/dist/locale/index'; + +import type { App } from 'vue'; + +import type { LocaleSetupOptions, SupportedLanguagesType } from '@vben/locales'; + +import { ref } from 'vue'; + +import { + $t, + setupI18n as coreSetup, + loadLocalesMapFromDir, +} from '@vben/locales'; +import { preferences } from '@vben/preferences'; + +import antdEnLocale from 'antdv-next/dist/locale/en_US'; +import antdDefaultLocale from 'antdv-next/dist/locale/zh_CN'; +import dayjs from 'dayjs'; + +const antdLocale = ref(antdDefaultLocale); + +const modules = import.meta.glob('./langs/**/*.json'); + +const localesMap = loadLocalesMapFromDir( + /\.\/langs\/([^/]+)\/(.*)\.json$/, + modules, +); +/** + * 加载应用特有的语言包 + * 这里也可以改造为从服务端获取翻译数据 + * @param lang + */ +async function loadMessages(lang: SupportedLanguagesType) { + const [appLocaleMessages] = await Promise.all([ + localesMap[lang]?.(), + loadThirdPartyMessage(lang), + ]); + return appLocaleMessages?.default; +} + +/** + * 加载第三方组件库的语言包 + * @param lang + */ +async function loadThirdPartyMessage(lang: SupportedLanguagesType) { + await Promise.all([loadAntdLocale(lang), loadDayjsLocale(lang)]); +} + +/** + * 加载dayjs的语言包 + * @param lang + */ +async function loadDayjsLocale(lang: SupportedLanguagesType) { + let locale; + switch (lang) { + case 'en-US': { + locale = await import('dayjs/locale/en'); + break; + } + case 'zh-CN': { + locale = await import('dayjs/locale/zh-cn'); + break; + } + // 默认使用英语 + default: { + locale = await import('dayjs/locale/en'); + } + } + if (locale) { + dayjs.locale(locale); + } else { + console.error(`Failed to load dayjs locale for ${lang}`); + } +} + +/** + * 加载antd的语言包 + * @param lang + */ +async function loadAntdLocale(lang: SupportedLanguagesType) { + switch (lang) { + case 'en-US': { + antdLocale.value = antdEnLocale; + break; + } + case 'zh-CN': { + antdLocale.value = antdDefaultLocale; + break; + } + } +} + +async function setupI18n(app: App, options: LocaleSetupOptions = {}) { + await coreSetup(app, { + defaultLocale: preferences.app.locale, + loadMessages, + missingWarn: !import.meta.env.PROD, + ...options, + }); +} + +export { $t, antdLocale, setupI18n }; diff --git a/apps/web-antdv-next/src/locales/langs/en-US/demos.json b/apps/web-antdv-next/src/locales/langs/en-US/demos.json new file mode 100644 index 00000000..551f22b4 --- /dev/null +++ b/apps/web-antdv-next/src/locales/langs/en-US/demos.json @@ -0,0 +1,14 @@ +{ + "title": "Demos", + "antd": "Antdv Next", + "vben": { + "title": "Project", + "about": "About", + "document": "Document", + "antdv": "Ant Design Vue Version", + "antdv-next": "Antdv Next Version", + "naive-ui": "Naive UI Version", + "element-plus": "Element Plus Version", + "tdesign": "TDesign Vue Version" + } +} diff --git a/apps/web-antdv-next/src/locales/langs/en-US/page.json b/apps/web-antdv-next/src/locales/langs/en-US/page.json new file mode 100644 index 00000000..39f1641c --- /dev/null +++ b/apps/web-antdv-next/src/locales/langs/en-US/page.json @@ -0,0 +1,15 @@ +{ + "auth": { + "login": "Login", + "register": "Register", + "codeLogin": "Code Login", + "qrcodeLogin": "Qr Code Login", + "forgetPassword": "Forget Password", + "profile": "Profile" + }, + "dashboard": { + "title": "Dashboard", + "analytics": "Analytics", + "workspace": "Workspace" + } +} diff --git a/apps/web-antdv-next/src/locales/langs/zh-CN/demos.json b/apps/web-antdv-next/src/locales/langs/zh-CN/demos.json new file mode 100644 index 00000000..ef4e43fd --- /dev/null +++ b/apps/web-antdv-next/src/locales/langs/zh-CN/demos.json @@ -0,0 +1,14 @@ +{ + "title": "演示", + "antd": "Antdv Next", + "vben": { + "title": "项目", + "about": "关于", + "document": "文档", + "antdv": "Ant Design Vue 版本", + "antdv-next": "Antdv Next 版本", + "naive-ui": "Naive UI 版本", + "element-plus": "Element Plus 版本", + "tdesign": "TDesign Vue 版本" + } +} diff --git a/apps/web-antdv-next/src/locales/langs/zh-CN/page.json b/apps/web-antdv-next/src/locales/langs/zh-CN/page.json new file mode 100644 index 00000000..2192d1d5 --- /dev/null +++ b/apps/web-antdv-next/src/locales/langs/zh-CN/page.json @@ -0,0 +1,15 @@ +{ + "auth": { + "login": "登录", + "register": "注册", + "codeLogin": "验证码登录", + "qrcodeLogin": "二维码登录", + "forgetPassword": "忘记密码", + "profile": "个人中心" + }, + "dashboard": { + "title": "概览", + "analytics": "分析页", + "workspace": "工作台" + } +} diff --git a/apps/web-antdv-next/src/main.ts b/apps/web-antdv-next/src/main.ts new file mode 100644 index 00000000..5d728a02 --- /dev/null +++ b/apps/web-antdv-next/src/main.ts @@ -0,0 +1,31 @@ +import { initPreferences } from '@vben/preferences'; +import { unmountGlobalLoading } from '@vben/utils'; + +import { overridesPreferences } from './preferences'; + +/** + * 应用初始化完成之后再进行页面加载渲染 + */ +async function initApplication() { + // name用于指定项目唯一标识 + // 用于区分不同项目的偏好设置以及存储数据的key前缀以及其他一些需要隔离的数据 + const env = import.meta.env.PROD ? 'prod' : 'dev'; + const appVersion = import.meta.env.VITE_APP_VERSION; + const namespace = `${import.meta.env.VITE_APP_NAMESPACE}-${appVersion}-${env}`; + + // app偏好设置初始化 + await initPreferences({ + namespace, + overrides: overridesPreferences, + }); + + // 启动应用并挂载 + // vue应用主要逻辑及视图 + const { bootstrap } = await import('./bootstrap'); + await bootstrap(namespace); + + // 移除并销毁loading + unmountGlobalLoading(); +} + +initApplication(); diff --git a/apps/web-antdv-next/src/preferences.ts b/apps/web-antdv-next/src/preferences.ts new file mode 100644 index 00000000..b2e9ace4 --- /dev/null +++ b/apps/web-antdv-next/src/preferences.ts @@ -0,0 +1,13 @@ +import { defineOverridesPreferences } from '@vben/preferences'; + +/** + * @description 项目配置文件 + * 只需要覆盖项目中的一部分配置,不需要的配置不用覆盖,会自动使用默认配置 + * !!! 更改配置后请清空缓存,否则可能不生效 + */ +export const overridesPreferences = defineOverridesPreferences({ + // overrides + app: { + name: import.meta.env.VITE_APP_TITLE, + }, +}); diff --git a/apps/web-antdv-next/src/router/access.ts b/apps/web-antdv-next/src/router/access.ts new file mode 100644 index 00000000..e9e98511 --- /dev/null +++ b/apps/web-antdv-next/src/router/access.ts @@ -0,0 +1,42 @@ +import type { + ComponentRecordType, + GenerateMenuAndRoutesOptions, +} from '@vben/types'; + +import { generateAccessible } from '@vben/access'; +import { preferences } from '@vben/preferences'; + +import { message } from 'antdv-next'; + +import { getAllMenusApi } from '#/api'; +import { BasicLayout, IFrameView } from '#/layouts'; +import { $t } from '#/locales'; + +const forbiddenComponent = () => import('#/views/_core/fallback/forbidden.vue'); + +async function generateAccess(options: GenerateMenuAndRoutesOptions) { + const pageMap: ComponentRecordType = import.meta.glob('../views/**/*.vue'); + + const layoutMap: ComponentRecordType = { + BasicLayout, + IFrameView, + }; + + return await generateAccessible(preferences.app.accessMode, { + ...options, + fetchMenuListAsync: async () => { + message.loading({ + content: `${$t('common.loadingMenu')}...`, + duration: 1.5, + }); + return await getAllMenusApi(); + }, + // 可以指定没有权限跳转403页面 + forbiddenComponent, + // 如果 route.meta.menuVisibleWithForbidden = true + layoutMap, + pageMap, + }); +} + +export { generateAccess }; diff --git a/apps/web-antdv-next/src/router/guard.ts b/apps/web-antdv-next/src/router/guard.ts new file mode 100644 index 00000000..a1ad6d88 --- /dev/null +++ b/apps/web-antdv-next/src/router/guard.ts @@ -0,0 +1,133 @@ +import type { Router } from 'vue-router'; + +import { LOGIN_PATH } from '@vben/constants'; +import { preferences } from '@vben/preferences'; +import { useAccessStore, useUserStore } from '@vben/stores'; +import { startProgress, stopProgress } from '@vben/utils'; + +import { accessRoutes, coreRouteNames } from '#/router/routes'; +import { useAuthStore } from '#/store'; + +import { generateAccess } from './access'; + +/** + * 通用守卫配置 + * @param router + */ +function setupCommonGuard(router: Router) { + // 记录已经加载的页面 + const loadedPaths = new Set(); + + router.beforeEach((to) => { + to.meta.loaded = loadedPaths.has(to.path); + + // 页面加载进度条 + if (!to.meta.loaded && preferences.transition.progress) { + startProgress(); + } + return true; + }); + + router.afterEach((to) => { + // 记录页面是否加载,如果已经加载,后续的页面切换动画等效果不在重复执行 + + loadedPaths.add(to.path); + + // 关闭页面加载进度条 + if (preferences.transition.progress) { + stopProgress(); + } + }); +} + +/** + * 权限访问守卫配置 + * @param router + */ +function setupAccessGuard(router: Router) { + router.beforeEach(async (to, from) => { + const accessStore = useAccessStore(); + const userStore = useUserStore(); + const authStore = useAuthStore(); + + // 基本路由,这些路由不需要进入权限拦截 + if (coreRouteNames.includes(to.name as string)) { + if (to.path === LOGIN_PATH && accessStore.accessToken) { + return decodeURIComponent( + (to.query?.redirect as string) || + userStore.userInfo?.homePath || + preferences.app.defaultHomePath, + ); + } + return true; + } + + // accessToken 检查 + if (!accessStore.accessToken) { + // 明确声明忽略权限访问权限,则可以访问 + if (to.meta.ignoreAccess) { + return true; + } + + // 没有访问权限,跳转登录页面 + if (to.fullPath !== LOGIN_PATH) { + return { + path: LOGIN_PATH, + // 如不需要,直接删除 query + query: + to.fullPath === preferences.app.defaultHomePath + ? {} + : { redirect: encodeURIComponent(to.fullPath) }, + // 携带当前跳转的页面,登录后重新跳转该页面 + replace: true, + }; + } + return to; + } + + // 是否已经生成过动态路由 + if (accessStore.isAccessChecked) { + return true; + } + + // 生成路由表 + // 当前登录用户拥有的角色标识列表 + const userInfo = userStore.userInfo || (await authStore.fetchUserInfo()); + const userRoles = userInfo.roles ?? []; + + // 生成菜单和路由 + const { accessibleMenus, accessibleRoutes } = await generateAccess({ + roles: userRoles, + router, + // 则会在菜单中显示,但是访问会被重定向到403 + routes: accessRoutes, + }); + + // 保存菜单信息和路由信息 + accessStore.setAccessMenus(accessibleMenus); + accessStore.setAccessRoutes(accessibleRoutes); + accessStore.setIsAccessChecked(true); + const redirectPath = (from.query.redirect ?? + (to.path === preferences.app.defaultHomePath + ? userInfo.homePath || preferences.app.defaultHomePath + : to.fullPath)) as string; + + return { + ...router.resolve(decodeURIComponent(redirectPath)), + replace: true, + }; + }); +} + +/** + * 项目守卫配置 + * @param router + */ +function createRouterGuard(router: Router) { + /** 通用 */ + setupCommonGuard(router); + /** 权限访问 */ + setupAccessGuard(router); +} + +export { createRouterGuard }; diff --git a/apps/web-antdv-next/src/router/index.ts b/apps/web-antdv-next/src/router/index.ts new file mode 100644 index 00000000..48402303 --- /dev/null +++ b/apps/web-antdv-next/src/router/index.ts @@ -0,0 +1,37 @@ +import { + createRouter, + createWebHashHistory, + createWebHistory, +} from 'vue-router'; + +import { resetStaticRoutes } from '@vben/utils'; + +import { createRouterGuard } from './guard'; +import { routes } from './routes'; + +/** + * @zh_CN 创建vue-router实例 + */ +const router = createRouter({ + history: + import.meta.env.VITE_ROUTER_HISTORY === 'hash' + ? createWebHashHistory(import.meta.env.VITE_BASE) + : createWebHistory(import.meta.env.VITE_BASE), + // 应该添加到路由的初始路由列表。 + routes, + scrollBehavior: (to, _from, savedPosition) => { + if (savedPosition) { + return savedPosition; + } + return to.hash ? { behavior: 'smooth', el: to.hash } : { left: 0, top: 0 }; + }, + // 是否应该禁止尾部斜杠。 + // strict: true, +}); + +const resetRoutes = () => resetStaticRoutes(router, routes); + +// 创建路由守卫 +createRouterGuard(router); + +export { resetRoutes, router }; diff --git a/apps/web-antdv-next/src/router/routes/core.ts b/apps/web-antdv-next/src/router/routes/core.ts new file mode 100644 index 00000000..949b0b65 --- /dev/null +++ b/apps/web-antdv-next/src/router/routes/core.ts @@ -0,0 +1,97 @@ +import type { RouteRecordRaw } from 'vue-router'; + +import { LOGIN_PATH } from '@vben/constants'; +import { preferences } from '@vben/preferences'; + +import { $t } from '#/locales'; + +const BasicLayout = () => import('#/layouts/basic.vue'); +const AuthPageLayout = () => import('#/layouts/auth.vue'); +/** 全局404页面 */ +const fallbackNotFoundRoute: RouteRecordRaw = { + component: () => import('#/views/_core/fallback/not-found.vue'), + meta: { + hideInBreadcrumb: true, + hideInMenu: true, + hideInTab: true, + title: '404', + }, + name: 'FallbackNotFound', + path: '/:path(.*)*', +}; + +/** 基本路由,这些路由是必须存在的 */ +const coreRoutes: RouteRecordRaw[] = [ + /** + * 根路由 + * 使用基础布局,作为所有页面的父级容器,子级就不必配置BasicLayout。 + * 此路由必须存在,且不应修改 + */ + { + component: BasicLayout, + meta: { + hideInBreadcrumb: true, + title: 'Root', + }, + name: 'Root', + path: '/', + redirect: preferences.app.defaultHomePath, + children: [], + }, + { + component: AuthPageLayout, + meta: { + hideInTab: true, + title: 'Authentication', + }, + name: 'Authentication', + path: '/auth', + redirect: LOGIN_PATH, + children: [ + { + name: 'Login', + path: 'login', + component: () => import('#/views/_core/authentication/login.vue'), + meta: { + title: $t('page.auth.login'), + }, + }, + { + name: 'CodeLogin', + path: 'code-login', + component: () => import('#/views/_core/authentication/code-login.vue'), + meta: { + title: $t('page.auth.codeLogin'), + }, + }, + { + name: 'QrCodeLogin', + path: 'qrcode-login', + component: () => + import('#/views/_core/authentication/qrcode-login.vue'), + meta: { + title: $t('page.auth.qrcodeLogin'), + }, + }, + { + name: 'ForgetPassword', + path: 'forget-password', + component: () => + import('#/views/_core/authentication/forget-password.vue'), + meta: { + title: $t('page.auth.forgetPassword'), + }, + }, + { + name: 'Register', + path: 'register', + component: () => import('#/views/_core/authentication/register.vue'), + meta: { + title: $t('page.auth.register'), + }, + }, + ], + }, +]; + +export { coreRoutes, fallbackNotFoundRoute }; diff --git a/apps/web-antdv-next/src/router/routes/index.ts b/apps/web-antdv-next/src/router/routes/index.ts new file mode 100644 index 00000000..e6fb1440 --- /dev/null +++ b/apps/web-antdv-next/src/router/routes/index.ts @@ -0,0 +1,37 @@ +import type { RouteRecordRaw } from 'vue-router'; + +import { mergeRouteModules, traverseTreeValues } from '@vben/utils'; + +import { coreRoutes, fallbackNotFoundRoute } from './core'; + +const dynamicRouteFiles = import.meta.glob('./modules/**/*.ts', { + eager: true, +}); + +// 有需要可以自行打开注释,并创建文件夹 +// const externalRouteFiles = import.meta.glob('./external/**/*.ts', { eager: true }); +// const staticRouteFiles = import.meta.glob('./static/**/*.ts', { eager: true }); + +/** 动态路由 */ +const dynamicRoutes: RouteRecordRaw[] = mergeRouteModules(dynamicRouteFiles); + +/** 外部路由列表,访问这些页面可以不需要Layout,可能用于内嵌在别的系统(不会显示在菜单中) */ +// const externalRoutes: RouteRecordRaw[] = mergeRouteModules(externalRouteFiles); +// const staticRoutes: RouteRecordRaw[] = mergeRouteModules(staticRouteFiles); +const staticRoutes: RouteRecordRaw[] = []; +const externalRoutes: RouteRecordRaw[] = []; + +/** 路由列表,由基本路由、外部路由和404兜底路由组成 + * 无需走权限验证(会一直显示在菜单中) */ +const routes: RouteRecordRaw[] = [ + ...coreRoutes, + ...externalRoutes, + fallbackNotFoundRoute, +]; + +/** 基本路由列表,这些路由不需要进入权限拦截 */ +const coreRouteNames = traverseTreeValues(coreRoutes, (route) => route.name); + +/** 有权限校验的路由列表,包含动态路由和静态路由 */ +const accessRoutes = [...dynamicRoutes, ...staticRoutes]; +export { accessRoutes, coreRouteNames, routes }; diff --git a/apps/web-antdv-next/src/router/routes/modules/dashboard.ts b/apps/web-antdv-next/src/router/routes/modules/dashboard.ts new file mode 100644 index 00000000..5254dc65 --- /dev/null +++ b/apps/web-antdv-next/src/router/routes/modules/dashboard.ts @@ -0,0 +1,38 @@ +import type { RouteRecordRaw } from 'vue-router'; + +import { $t } from '#/locales'; + +const routes: RouteRecordRaw[] = [ + { + meta: { + icon: 'lucide:layout-dashboard', + order: -1, + title: $t('page.dashboard.title'), + }, + name: 'Dashboard', + path: '/dashboard', + children: [ + { + name: 'Analytics', + path: '/analytics', + component: () => import('#/views/dashboard/analytics/index.vue'), + meta: { + affixTab: true, + icon: 'lucide:area-chart', + title: $t('page.dashboard.analytics'), + }, + }, + { + name: 'Workspace', + path: '/workspace', + component: () => import('#/views/dashboard/workspace/index.vue'), + meta: { + icon: 'carbon:workspace', + title: $t('page.dashboard.workspace'), + }, + }, + ], + }, +]; + +export default routes; diff --git a/apps/web-antdv-next/src/router/routes/modules/demos.ts b/apps/web-antdv-next/src/router/routes/modules/demos.ts new file mode 100644 index 00000000..4605d91c --- /dev/null +++ b/apps/web-antdv-next/src/router/routes/modules/demos.ts @@ -0,0 +1,28 @@ +import type { RouteRecordRaw } from 'vue-router'; + +import { $t } from '#/locales'; + +const routes: RouteRecordRaw[] = [ + { + meta: { + icon: 'ic:baseline-view-in-ar', + keepAlive: true, + order: 1000, + title: $t('demos.title'), + }, + name: 'Demos', + path: '/demos', + children: [ + { + meta: { + title: $t('demos.antd'), + }, + name: 'AntDesignDemos', + path: '/demos/ant-design-next', + component: () => import('#/views/demos/antd/index.vue'), + }, + ], + }, +]; + +export default routes; diff --git a/apps/web-antdv-next/src/router/routes/modules/vben.ts b/apps/web-antdv-next/src/router/routes/modules/vben.ts new file mode 100644 index 00000000..96f741ef --- /dev/null +++ b/apps/web-antdv-next/src/router/routes/modules/vben.ts @@ -0,0 +1,116 @@ +import type { RouteRecordRaw } from 'vue-router'; + +import { + VBEN_ANT_PREVIEW_URL, + VBEN_DOC_URL, + VBEN_ELE_PREVIEW_URL, + VBEN_GITHUB_URL, + VBEN_LOGO_URL, + VBEN_NAIVE_PREVIEW_URL, + VBEN_TD_PREVIEW_URL, +} from '@vben/constants'; +import { SvgAntdvLogoIcon, SvgTDesignIcon } from '@vben/icons'; + +import { IFrameView } from '#/layouts'; +import { $t } from '#/locales'; + +const routes: RouteRecordRaw[] = [ + { + meta: { + badgeType: 'dot', + icon: VBEN_LOGO_URL, + order: 9998, + title: $t('demos.vben.title'), + }, + name: 'VbenProject', + path: '/vben-admin', + children: [ + { + name: 'VbenDocument', + path: '/vben-admin/document', + component: IFrameView, + meta: { + icon: 'lucide:book-open-text', + link: VBEN_DOC_URL, + title: $t('demos.vben.document'), + }, + }, + { + name: 'VbenGithub', + path: '/vben-admin/github', + component: IFrameView, + meta: { + icon: 'mdi:github', + link: VBEN_GITHUB_URL, + title: 'Github', + }, + }, + { + name: 'VbenAntd', + path: '/vben-admin/antd', + component: IFrameView, + meta: { + badgeType: 'dot', + icon: SvgAntdvLogoIcon, + link: VBEN_ANT_PREVIEW_URL, + title: $t('demos.vben.antdv'), + }, + }, + { + name: 'VbenNaive', + path: '/vben-admin/naive', + component: IFrameView, + meta: { + badgeType: 'dot', + icon: 'logos:naiveui', + link: VBEN_NAIVE_PREVIEW_URL, + title: $t('demos.vben.naive-ui'), + }, + }, + { + name: 'VbenTDesign', + path: '/vben-admin/tdesign', + component: IFrameView, + meta: { + badgeType: 'dot', + icon: SvgTDesignIcon, + link: VBEN_TD_PREVIEW_URL, + title: $t('demos.vben.tdesign'), + }, + }, + { + name: 'VbenElementPlus', + path: '/vben-admin/ele', + component: IFrameView, + meta: { + badgeType: 'dot', + icon: 'logos:element', + link: VBEN_ELE_PREVIEW_URL, + title: $t('demos.vben.element-plus'), + }, + }, + ], + }, + { + name: 'VbenAbout', + path: '/vben-admin/about', + component: () => import('#/views/_core/about/index.vue'), + meta: { + icon: 'lucide:copyright', + title: $t('demos.vben.about'), + 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-antdv-next/src/store/auth.ts b/apps/web-antdv-next/src/store/auth.ts new file mode 100644 index 00000000..6f7a3750 --- /dev/null +++ b/apps/web-antdv-next/src/store/auth.ts @@ -0,0 +1,118 @@ +import type { Recordable, UserInfo } from '@vben/types'; + +import { ref } from 'vue'; +import { useRouter } from 'vue-router'; + +import { LOGIN_PATH } from '@vben/constants'; +import { preferences } from '@vben/preferences'; +import { resetAllStores, useAccessStore, useUserStore } from '@vben/stores'; + +import { notification } from 'antdv-next'; +import { defineStore } from 'pinia'; + +import { getAccessCodesApi, getUserInfoApi, loginApi, logoutApi } from '#/api'; +import { $t } from '#/locales'; + +export const useAuthStore = defineStore('auth', () => { + const accessStore = useAccessStore(); + const userStore = useUserStore(); + const router = useRouter(); + + const loginLoading = ref(false); + + /** + * 异步处理登录操作 + * Asynchronously handle the login process + * @param params 登录表单数据 + */ + async function authLogin( + params: Recordable, + onSuccess?: () => Promise | void, + ) { + // 异步处理用户登录操作并获取 accessToken + let userInfo: null | UserInfo = null; + try { + loginLoading.value = true; + const { accessToken } = await loginApi(params); + + // 如果成功获取到 accessToken + if (accessToken) { + accessStore.setAccessToken(accessToken); + + // 获取用户信息并存储到 accessStore 中 + const [fetchUserInfoResult, accessCodes] = await Promise.all([ + fetchUserInfo(), + getAccessCodesApi(), + ]); + + userInfo = fetchUserInfoResult; + + userStore.setUserInfo(userInfo); + accessStore.setAccessCodes(accessCodes); + + if (accessStore.loginExpired) { + accessStore.setLoginExpired(false); + } else { + onSuccess + ? await onSuccess?.() + : await router.push( + userInfo.homePath || preferences.app.defaultHomePath, + ); + } + + if (userInfo?.realName) { + notification.success({ + description: `${$t('authentication.loginSuccessDesc')}:${userInfo?.realName}`, + duration: 3, + title: $t('authentication.loginSuccess'), + }); + } + } + } finally { + loginLoading.value = false; + } + + return { + userInfo, + }; + } + + async function logout(redirect: boolean = true) { + try { + await logoutApi(); + } catch { + // 不做任何处理 + } + resetAllStores(); + accessStore.setLoginExpired(false); + + // 回登录页带上当前路由地址 + await router.replace({ + path: LOGIN_PATH, + query: redirect + ? { + redirect: encodeURIComponent(router.currentRoute.value.fullPath), + } + : {}, + }); + } + + async function fetchUserInfo() { + let userInfo: null | UserInfo = null; + userInfo = await getUserInfoApi(); + userStore.setUserInfo(userInfo); + return userInfo; + } + + function $reset() { + loginLoading.value = false; + } + + return { + $reset, + authLogin, + fetchUserInfo, + loginLoading, + logout, + }; +}); diff --git a/apps/web-antdv-next/src/store/index.ts b/apps/web-antdv-next/src/store/index.ts new file mode 100644 index 00000000..269586ee --- /dev/null +++ b/apps/web-antdv-next/src/store/index.ts @@ -0,0 +1 @@ +export * from './auth'; diff --git a/apps/web-antdv-next/src/views/_core/README.md b/apps/web-antdv-next/src/views/_core/README.md new file mode 100644 index 00000000..8248afe6 --- /dev/null +++ b/apps/web-antdv-next/src/views/_core/README.md @@ -0,0 +1,3 @@ +# \_core + +此目录包含应用程序正常运行所需的基本视图。这些视图是应用程序布局中使用的视图。 diff --git a/apps/web-antdv-next/src/views/_core/about/index.vue b/apps/web-antdv-next/src/views/_core/about/index.vue new file mode 100644 index 00000000..0ee52433 --- /dev/null +++ b/apps/web-antdv-next/src/views/_core/about/index.vue @@ -0,0 +1,9 @@ + + + diff --git a/apps/web-antdv-next/src/views/_core/authentication/code-login.vue b/apps/web-antdv-next/src/views/_core/authentication/code-login.vue new file mode 100644 index 00000000..acfd1fd7 --- /dev/null +++ b/apps/web-antdv-next/src/views/_core/authentication/code-login.vue @@ -0,0 +1,69 @@ + + + diff --git a/apps/web-antdv-next/src/views/_core/authentication/forget-password.vue b/apps/web-antdv-next/src/views/_core/authentication/forget-password.vue new file mode 100644 index 00000000..fef0d427 --- /dev/null +++ b/apps/web-antdv-next/src/views/_core/authentication/forget-password.vue @@ -0,0 +1,43 @@ + + + diff --git a/apps/web-antdv-next/src/views/_core/authentication/login.vue b/apps/web-antdv-next/src/views/_core/authentication/login.vue new file mode 100644 index 00000000..099e4c8c --- /dev/null +++ b/apps/web-antdv-next/src/views/_core/authentication/login.vue @@ -0,0 +1,98 @@ + + + diff --git a/apps/web-antdv-next/src/views/_core/authentication/qrcode-login.vue b/apps/web-antdv-next/src/views/_core/authentication/qrcode-login.vue new file mode 100644 index 00000000..23f5f2da --- /dev/null +++ b/apps/web-antdv-next/src/views/_core/authentication/qrcode-login.vue @@ -0,0 +1,10 @@ + + + diff --git a/apps/web-antdv-next/src/views/_core/authentication/register.vue b/apps/web-antdv-next/src/views/_core/authentication/register.vue new file mode 100644 index 00000000..b1a5de72 --- /dev/null +++ b/apps/web-antdv-next/src/views/_core/authentication/register.vue @@ -0,0 +1,96 @@ + + + diff --git a/apps/web-antdv-next/src/views/_core/fallback/coming-soon.vue b/apps/web-antdv-next/src/views/_core/fallback/coming-soon.vue new file mode 100644 index 00000000..f394930f --- /dev/null +++ b/apps/web-antdv-next/src/views/_core/fallback/coming-soon.vue @@ -0,0 +1,7 @@ + + + diff --git a/apps/web-antdv-next/src/views/_core/fallback/forbidden.vue b/apps/web-antdv-next/src/views/_core/fallback/forbidden.vue new file mode 100644 index 00000000..8ea65fed --- /dev/null +++ b/apps/web-antdv-next/src/views/_core/fallback/forbidden.vue @@ -0,0 +1,9 @@ + + + diff --git a/apps/web-antdv-next/src/views/_core/fallback/internal-error.vue b/apps/web-antdv-next/src/views/_core/fallback/internal-error.vue new file mode 100644 index 00000000..819a47d5 --- /dev/null +++ b/apps/web-antdv-next/src/views/_core/fallback/internal-error.vue @@ -0,0 +1,9 @@ + + + diff --git a/apps/web-antdv-next/src/views/_core/fallback/not-found.vue b/apps/web-antdv-next/src/views/_core/fallback/not-found.vue new file mode 100644 index 00000000..4d178e9c --- /dev/null +++ b/apps/web-antdv-next/src/views/_core/fallback/not-found.vue @@ -0,0 +1,9 @@ + + + diff --git a/apps/web-antdv-next/src/views/_core/fallback/offline.vue b/apps/web-antdv-next/src/views/_core/fallback/offline.vue new file mode 100644 index 00000000..5de4a88d --- /dev/null +++ b/apps/web-antdv-next/src/views/_core/fallback/offline.vue @@ -0,0 +1,9 @@ + + + diff --git a/apps/web-antdv-next/src/views/_core/profile/base-setting.vue b/apps/web-antdv-next/src/views/_core/profile/base-setting.vue new file mode 100644 index 00000000..aa8a4c26 --- /dev/null +++ b/apps/web-antdv-next/src/views/_core/profile/base-setting.vue @@ -0,0 +1,65 @@ + + diff --git a/apps/web-antdv-next/src/views/_core/profile/index.vue b/apps/web-antdv-next/src/views/_core/profile/index.vue new file mode 100644 index 00000000..8740894e --- /dev/null +++ b/apps/web-antdv-next/src/views/_core/profile/index.vue @@ -0,0 +1,49 @@ + + diff --git a/apps/web-antdv-next/src/views/_core/profile/notification-setting.vue b/apps/web-antdv-next/src/views/_core/profile/notification-setting.vue new file mode 100644 index 00000000..324a4b39 --- /dev/null +++ b/apps/web-antdv-next/src/views/_core/profile/notification-setting.vue @@ -0,0 +1,31 @@ + + diff --git a/apps/web-antdv-next/src/views/_core/profile/password-setting.vue b/apps/web-antdv-next/src/views/_core/profile/password-setting.vue new file mode 100644 index 00000000..adb065a2 --- /dev/null +++ b/apps/web-antdv-next/src/views/_core/profile/password-setting.vue @@ -0,0 +1,63 @@ + + diff --git a/apps/web-antdv-next/src/views/_core/profile/security-setting.vue b/apps/web-antdv-next/src/views/_core/profile/security-setting.vue new file mode 100644 index 00000000..be30db58 --- /dev/null +++ b/apps/web-antdv-next/src/views/_core/profile/security-setting.vue @@ -0,0 +1,43 @@ + + diff --git a/apps/web-antdv-next/src/views/dashboard/analytics/analytics-trends.vue b/apps/web-antdv-next/src/views/dashboard/analytics/analytics-trends.vue new file mode 100644 index 00000000..f1f0b232 --- /dev/null +++ b/apps/web-antdv-next/src/views/dashboard/analytics/analytics-trends.vue @@ -0,0 +1,98 @@ + + + diff --git a/apps/web-antdv-next/src/views/dashboard/analytics/analytics-visits-data.vue b/apps/web-antdv-next/src/views/dashboard/analytics/analytics-visits-data.vue new file mode 100644 index 00000000..190fb41f --- /dev/null +++ b/apps/web-antdv-next/src/views/dashboard/analytics/analytics-visits-data.vue @@ -0,0 +1,82 @@ + + + diff --git a/apps/web-antdv-next/src/views/dashboard/analytics/analytics-visits-sales.vue b/apps/web-antdv-next/src/views/dashboard/analytics/analytics-visits-sales.vue new file mode 100644 index 00000000..6ff52086 --- /dev/null +++ b/apps/web-antdv-next/src/views/dashboard/analytics/analytics-visits-sales.vue @@ -0,0 +1,46 @@ + + + diff --git a/apps/web-antdv-next/src/views/dashboard/analytics/analytics-visits-source.vue b/apps/web-antdv-next/src/views/dashboard/analytics/analytics-visits-source.vue new file mode 100644 index 00000000..0915c7af --- /dev/null +++ b/apps/web-antdv-next/src/views/dashboard/analytics/analytics-visits-source.vue @@ -0,0 +1,65 @@ + + + diff --git a/apps/web-antdv-next/src/views/dashboard/analytics/analytics-visits.vue b/apps/web-antdv-next/src/views/dashboard/analytics/analytics-visits.vue new file mode 100644 index 00000000..7e0f1013 --- /dev/null +++ b/apps/web-antdv-next/src/views/dashboard/analytics/analytics-visits.vue @@ -0,0 +1,55 @@ + + + diff --git a/apps/web-antdv-next/src/views/dashboard/analytics/index.vue b/apps/web-antdv-next/src/views/dashboard/analytics/index.vue new file mode 100644 index 00000000..5e3d6d28 --- /dev/null +++ b/apps/web-antdv-next/src/views/dashboard/analytics/index.vue @@ -0,0 +1,90 @@ + + + diff --git a/apps/web-antdv-next/src/views/dashboard/workspace/index.vue b/apps/web-antdv-next/src/views/dashboard/workspace/index.vue new file mode 100644 index 00000000..b95d6138 --- /dev/null +++ b/apps/web-antdv-next/src/views/dashboard/workspace/index.vue @@ -0,0 +1,266 @@ + + + diff --git a/apps/web-antdv-next/src/views/demos/antd/index.vue b/apps/web-antdv-next/src/views/demos/antd/index.vue new file mode 100644 index 00000000..6fb1998d --- /dev/null +++ b/apps/web-antdv-next/src/views/demos/antd/index.vue @@ -0,0 +1,66 @@ + + + diff --git a/apps/web-antdv-next/tailwind.config.mjs b/apps/web-antdv-next/tailwind.config.mjs new file mode 100644 index 00000000..f17f556f --- /dev/null +++ b/apps/web-antdv-next/tailwind.config.mjs @@ -0,0 +1 @@ +export { default } from '@vben/tailwind-config'; diff --git a/apps/web-antdv-next/tsconfig.json b/apps/web-antdv-next/tsconfig.json new file mode 100644 index 00000000..02c287fe --- /dev/null +++ b/apps/web-antdv-next/tsconfig.json @@ -0,0 +1,12 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "extends": "@vben/tsconfig/web-app.json", + "compilerOptions": { + "baseUrl": ".", + "paths": { + "#/*": ["./src/*"] + } + }, + "references": [{ "path": "./tsconfig.node.json" }], + "include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"] +} diff --git a/apps/web-antdv-next/tsconfig.node.json b/apps/web-antdv-next/tsconfig.node.json new file mode 100644 index 00000000..c2f0d86c --- /dev/null +++ b/apps/web-antdv-next/tsconfig.node.json @@ -0,0 +1,10 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "extends": "@vben/tsconfig/node.json", + "compilerOptions": { + "composite": true, + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", + "noEmit": false + }, + "include": ["vite.config.mts"] +} diff --git a/apps/web-antdv-next/vite.config.mts b/apps/web-antdv-next/vite.config.mts new file mode 100644 index 00000000..b6360f1d --- /dev/null +++ b/apps/web-antdv-next/vite.config.mts @@ -0,0 +1,20 @@ +import { defineConfig } from '@vben/vite-config'; + +export default defineConfig(async () => { + return { + application: {}, + vite: { + server: { + proxy: { + '/api': { + changeOrigin: true, + rewrite: (path) => path.replace(/^\/api/, ''), + // mock代理目标地址 + target: 'http://localhost:5320/api', + ws: true, + }, + }, + }, + }, + }; +}); diff --git a/apps/web-ele/src/locales/langs/en-US/demos.json b/apps/web-ele/src/locales/langs/en-US/demos.json index cc404511..e379d22a 100644 --- a/apps/web-ele/src/locales/langs/en-US/demos.json +++ b/apps/web-ele/src/locales/langs/en-US/demos.json @@ -7,6 +7,7 @@ "about": "About", "document": "Document", "antdv": "Ant Design Vue Version", + "antdv-next": "Antdv Next Version", "naive-ui": "Naive UI Version", "element-plus": "Element Plus Version", "tdesign": "TDesign Vue Version" diff --git a/apps/web-ele/src/locales/langs/zh-CN/demos.json b/apps/web-ele/src/locales/langs/zh-CN/demos.json index f8379c36..054c447c 100644 --- a/apps/web-ele/src/locales/langs/zh-CN/demos.json +++ b/apps/web-ele/src/locales/langs/zh-CN/demos.json @@ -7,6 +7,7 @@ "about": "关于", "document": "文档", "antdv": "Ant Design Vue 版本", + "antdv-next": "Antdv Next 版本", "naive-ui": "Naive UI 版本", "element-plus": "Element Plus 版本", "tdesign": "TDesign Vue 版本" diff --git a/apps/web-ele/src/router/routes/modules/vben.ts b/apps/web-ele/src/router/routes/modules/vben.ts index 5c522f30..1e8176ea 100644 --- a/apps/web-ele/src/router/routes/modules/vben.ts +++ b/apps/web-ele/src/router/routes/modules/vben.ts @@ -2,13 +2,18 @@ import type { RouteRecordRaw } from 'vue-router'; import { VBEN_ANT_PREVIEW_URL, + VBEN_ANTDV_NEXT_PREVIEW_URL, VBEN_DOC_URL, VBEN_GITHUB_URL, VBEN_LOGO_URL, VBEN_NAIVE_PREVIEW_URL, VBEN_TD_PREVIEW_URL, } from '@vben/constants'; -import { SvgAntdvLogoIcon, SvgTDesignIcon } from '@vben/icons'; +import { + SvgAntdvLogoIcon, + SvgAntdvNextLogoIcon, + SvgTDesignIcon, +} from '@vben/icons'; import { IFrameView } from '#/layouts'; import { $t } from '#/locales'; @@ -66,6 +71,17 @@ const routes: RouteRecordRaw[] = [ title: $t('demos.vben.antdv'), }, }, + { + name: 'VbenAntdVNext', + path: '/vben-admin/antdv-next', + component: IFrameView, + meta: { + badgeType: 'dot', + icon: SvgAntdvNextLogoIcon, + link: VBEN_ANTDV_NEXT_PREVIEW_URL, + title: $t('demos.vben.antdv-next'), + }, + }, { name: 'VbenTDesign', path: '/vben-admin/tdesign', diff --git a/apps/web-naive/src/locales/langs/en-US/demos.json b/apps/web-naive/src/locales/langs/en-US/demos.json index 3128b0be..5be6cdd5 100644 --- a/apps/web-naive/src/locales/langs/en-US/demos.json +++ b/apps/web-naive/src/locales/langs/en-US/demos.json @@ -8,6 +8,7 @@ "about": "About", "document": "Document", "antdv": "Ant Design Vue Version", + "antdv-next": "Antdv Next Version", "naive-ui": "Naive UI Version", "element-plus": "Element Plus Version", "tdesign": "TDesign Vue Version" diff --git a/apps/web-naive/src/locales/langs/zh-CN/demos.json b/apps/web-naive/src/locales/langs/zh-CN/demos.json index 3c3957a9..d818cb5c 100644 --- a/apps/web-naive/src/locales/langs/zh-CN/demos.json +++ b/apps/web-naive/src/locales/langs/zh-CN/demos.json @@ -8,6 +8,7 @@ "about": "关于", "document": "文档", "antdv": "Ant Design Vue 版本", + "antdv-next": "Antdv Next 版本", "naive-ui": "Naive UI 版本", "element-plus": "Element Plus 版本", "tdesign": "TDesign Vue 版本" diff --git a/apps/web-naive/src/router/routes/modules/vben.ts b/apps/web-naive/src/router/routes/modules/vben.ts index 32c21ca4..888ff4cb 100644 --- a/apps/web-naive/src/router/routes/modules/vben.ts +++ b/apps/web-naive/src/router/routes/modules/vben.ts @@ -2,13 +2,18 @@ import type { RouteRecordRaw } from 'vue-router'; import { VBEN_ANT_PREVIEW_URL, + VBEN_ANTDV_NEXT_PREVIEW_URL, VBEN_DOC_URL, VBEN_ELE_PREVIEW_URL, VBEN_GITHUB_URL, VBEN_LOGO_URL, VBEN_TD_PREVIEW_URL, } from '@vben/constants'; -import { SvgAntdvLogoIcon, SvgTDesignIcon } from '@vben/icons'; +import { + SvgAntdvLogoIcon, + SvgAntdvNextLogoIcon, + SvgTDesignIcon, +} from '@vben/icons'; import { IFrameView } from '#/layouts'; import { $t } from '#/locales'; @@ -55,6 +60,17 @@ const routes: RouteRecordRaw[] = [ title: $t('demos.vben.antdv'), }, }, + { + name: 'VbenAntdVNext', + path: '/vben-admin/antdv-next', + component: IFrameView, + meta: { + badgeType: 'dot', + icon: SvgAntdvNextLogoIcon, + link: VBEN_ANTDV_NEXT_PREVIEW_URL, + title: $t('demos.vben.antdv-next'), + }, + }, { name: 'VbenTDesign', path: '/vben-admin/tdesign', diff --git a/apps/web-tdesign/src/locales/langs/en-US/demos.json b/apps/web-tdesign/src/locales/langs/en-US/demos.json index e7bcea12..0cb50b7f 100644 --- a/apps/web-tdesign/src/locales/langs/en-US/demos.json +++ b/apps/web-tdesign/src/locales/langs/en-US/demos.json @@ -6,6 +6,7 @@ "about": "About", "document": "Document", "antdv": "Ant Design Vue Version", + "antdv-next": "Antdv Next Version", "naive-ui": "Naive UI Version", "element-plus": "Element Plus Version" } diff --git a/apps/web-tdesign/src/locales/langs/zh-CN/demos.json b/apps/web-tdesign/src/locales/langs/zh-CN/demos.json index 843a1f30..25e2a0f9 100644 --- a/apps/web-tdesign/src/locales/langs/zh-CN/demos.json +++ b/apps/web-tdesign/src/locales/langs/zh-CN/demos.json @@ -6,6 +6,7 @@ "about": "关于", "document": "文档", "antdv": "Ant Design Vue 版本", + "antdv-next": "Antdv Next 版本", "naive-ui": "Naive UI 版本", "element-plus": "Element Plus 版本" } diff --git a/apps/web-tdesign/src/router/routes/modules/vben.ts b/apps/web-tdesign/src/router/routes/modules/vben.ts index a5f0aa4d..db555083 100644 --- a/apps/web-tdesign/src/router/routes/modules/vben.ts +++ b/apps/web-tdesign/src/router/routes/modules/vben.ts @@ -2,13 +2,14 @@ import type { RouteRecordRaw } from 'vue-router'; import { VBEN_ANT_PREVIEW_URL, + VBEN_ANTDV_NEXT_PREVIEW_URL, VBEN_DOC_URL, VBEN_ELE_PREVIEW_URL, VBEN_GITHUB_URL, VBEN_LOGO_URL, VBEN_NAIVE_PREVIEW_URL, } from '@vben/constants'; -import { SvgAntdvLogoIcon } from '@vben/icons'; +import { SvgAntdvLogoIcon, SvgAntdvNextLogoIcon } from '@vben/icons'; import { IFrameView } from '#/layouts'; import { $t } from '#/locales'; @@ -66,6 +67,17 @@ const routes: RouteRecordRaw[] = [ title: $t('demos.vben.antdv'), }, }, + { + name: 'VbenAntdVNext', + path: '/vben-admin/antdv-next', + component: IFrameView, + meta: { + badgeType: 'dot', + icon: SvgAntdvNextLogoIcon, + link: VBEN_ANTDV_NEXT_PREVIEW_URL, + title: $t('demos.vben.antdv-next'), + }, + }, { name: 'VbenElementPlus', path: '/vben-admin/ele', diff --git a/docs/src/en/guide/introduction/quick-start.md b/docs/src/en/guide/introduction/quick-start.md index 757679c3..12793c60 100644 --- a/docs/src/en/guide/introduction/quick-start.md +++ b/docs/src/en/guide/introduction/quick-start.md @@ -85,6 +85,7 @@ You will see an output similar to the following, allowing you to select the proj │ ◆ Select the app you need to run [dev]: │ ● @vben/web-antd +│ ○ @vben/web-antdv-next │ ○ @vben/web-ele │ ○ @vben/web-naive │ ○ @vben/docs diff --git a/docs/src/guide/introduction/quick-start.md b/docs/src/guide/introduction/quick-start.md index 6b451ddf..f940672d 100644 --- a/docs/src/guide/introduction/quick-start.md +++ b/docs/src/guide/introduction/quick-start.md @@ -88,7 +88,8 @@ pnpm dev ```bash │ ◆ Select the app you need to run [dev]: -│ ○ @vben/web-antd +│ ● @vben/web-antd +│ ○ @vben/web-antdv-next │ ○ @vben/web-ele │ ○ @vben/web-naive │ ○ @vben/docs diff --git a/package.json b/package.json index aa438664..cf616d16 100644 --- a/package.json +++ b/package.json @@ -44,6 +44,7 @@ "commit": "czg", "dev": "turbo-run dev", "dev:antd": "pnpm -F @vben/web-antd run dev", + "dev:antdv-next": "pnpm -F @vben/web-antdv-next run dev", "dev:docs": "pnpm -F @vben/docs run dev", "dev:ele": "pnpm -F @vben/web-ele run dev", "dev:naive": "pnpm -F @vben/web-naive run dev", diff --git a/packages/@core/base/shared/src/constants/vben.ts b/packages/@core/base/shared/src/constants/vben.ts index 5ba78f46..37b8cd09 100644 --- a/packages/@core/base/shared/src/constants/vben.ts +++ b/packages/@core/base/shared/src/constants/vben.ts @@ -19,6 +19,8 @@ export const VBEN_LOGO_URL = */ export const VBEN_PREVIEW_URL = 'https://www.vben.pro'; +export const VBEN_ANTDV_NEXT_PREVIEW_URL = 'https://antdv-next.vben.pro'; + export const VBEN_ELE_PREVIEW_URL = 'https://ele.vben.pro'; export const VBEN_NAIVE_PREVIEW_URL = 'https://naive.vben.pro'; diff --git a/packages/icons/src/svg/icons/antdv-next-logo.svg b/packages/icons/src/svg/icons/antdv-next-logo.svg new file mode 100644 index 00000000..b335be1d --- /dev/null +++ b/packages/icons/src/svg/icons/antdv-next-logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/icons/src/svg/index.ts b/packages/icons/src/svg/index.ts index af83e317..5044a37f 100644 --- a/packages/icons/src/svg/index.ts +++ b/packages/icons/src/svg/index.ts @@ -17,9 +17,11 @@ const SvgQQChatIcon = createIconifyIcon('svg:qqchat'); const SvgWeChatIcon = createIconifyIcon('svg:wechat'); const SvgDingDingIcon = createIconifyIcon('svg:dingding'); const SvgTDesignIcon = createIconifyIcon('svg:tdesign-logo'); +const SvgAntdvNextLogoIcon = createIconifyIcon('svg:antdv-next-logo'); export { SvgAntdvLogoIcon, + SvgAntdvNextLogoIcon, SvgAvatar1Icon, SvgAvatar2Icon, SvgAvatar3Icon, diff --git a/packages/styles/package.json b/packages/styles/package.json index 0716a5e5..b7297495 100644 --- a/packages/styles/package.json +++ b/packages/styles/package.json @@ -18,6 +18,9 @@ "./antd": { "default": "./src/antd/index.css" }, + "./antdv-next": { + "default": "./src/antdv-next/index.css" + }, "./ele": { "default": "./src/ele/index.css" }, diff --git a/packages/styles/src/antdv-next/index.css b/packages/styles/src/antdv-next/index.css new file mode 100644 index 00000000..420dfe96 --- /dev/null +++ b/packages/styles/src/antdv-next/index.css @@ -0,0 +1,77 @@ +/* antdv-next 组件库的一些样式重置 */ + +.ant-app { + width: 100%; + height: 100%; + overscroll-behavior: none; + color: inherit; +} + +.ant-btn { + .anticon { + display: inline-flex; + } + + /* * 修复按钮添加图标时的位置问题 */ + > .ant-btn-icon { + svg { + display: inline-block; + } + + svg + span { + margin-inline-start: 6px; + } + } +} + +.ant-tag { + > svg { + display: inline-block; + } + + > svg + span { + margin-inline-start: 4px; + } +} + +.ant-message-notice-content, +.ant-notification-notice { + @apply dark:border-border/60 dark:border; +} + +.form-valid-error { + /** select 选择器的样式 */ + + .ant-select:not(.valid-success) { + border-color: hsl(var(--destructive)) !important; + } + + .ant-select-focused { + box-shadow: 0 0 0 2px rgb(255 38 5 / 6%) !important; + } + + /** 数字输入框样式 */ + .ant-input-number-focused { + box-shadow: 0 0 0 2px rgb(255 38 5 / 6%); + } + + /** 密码输入框样式 */ + .ant-input-affix-wrapper:hover { + border-color: hsl(var(--destructive)); + box-shadow: 0 0 0 2px rgb(255 38 5 / 6%); + } + + .ant-input:not(.valid-success) { + border-color: hsl(var(--destructive)) !important; + } +} + +/** 区间选择器下面来回切换时的样式 */ +.ant-app .form-valid-error .ant-picker-active-bar { + background-color: hsl(var(--destructive)); +} + +/** 时间选择器的样式 */ +.ant-app .form-valid-error .ant-picker-focused { + box-shadow: 0 0 0 2px rgb(255 38 5 / 6%); +} diff --git a/playground/src/locales/langs/en-US/demos.json b/playground/src/locales/langs/en-US/demos.json index 8697ea3c..6b2fcfb9 100644 --- a/playground/src/locales/langs/en-US/demos.json +++ b/playground/src/locales/langs/en-US/demos.json @@ -64,6 +64,7 @@ "about": "About", "document": "Document", "antdv": "Ant Design Vue Version", + "antdv-next": "Antdv Next Version", "naive-ui": "Naive UI Version", "element-plus": "Element Plus Version", "tdesign": "TDesign Vue Version" diff --git a/playground/src/locales/langs/zh-CN/demos.json b/playground/src/locales/langs/zh-CN/demos.json index b7823b72..9191a8ee 100644 --- a/playground/src/locales/langs/zh-CN/demos.json +++ b/playground/src/locales/langs/zh-CN/demos.json @@ -65,6 +65,7 @@ "about": "关于", "document": "文档", "antdv": "Ant Design Vue 版本", + "antdv-next": "Antdv Next 版本", "naive-ui": "Naive UI 版本", "element-plus": "Element Plus 版本", "tdesign": "TDesign Vue 版本" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 26cee672..9525dc2a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -147,6 +147,9 @@ catalogs: ant-design-vue: specifier: ^4.2.6 version: 4.2.6 + antdv-next: + specifier: ^1.0.2 + version: 1.0.2 archiver: specifier: ^7.0.1 version: 7.0.1 @@ -712,6 +715,69 @@ importers: specifier: 'catalog:' version: 4.6.4(vue@3.5.27(typescript@5.9.3)) + apps/web-antdv-next: + dependencies: + '@vben/access': + specifier: workspace:* + version: link:../../packages/effects/access + '@vben/common-ui': + specifier: workspace:* + version: link:../../packages/effects/common-ui + '@vben/constants': + specifier: workspace:* + version: link:../../packages/constants + '@vben/hooks': + specifier: workspace:* + version: link:../../packages/effects/hooks + '@vben/icons': + specifier: workspace:* + version: link:../../packages/icons + '@vben/layouts': + specifier: workspace:* + version: link:../../packages/effects/layouts + '@vben/locales': + specifier: workspace:* + version: link:../../packages/locales + '@vben/plugins': + specifier: workspace:* + version: link:../../packages/effects/plugins + '@vben/preferences': + specifier: workspace:* + version: link:../../packages/preferences + '@vben/request': + specifier: workspace:* + version: link:../../packages/effects/request + '@vben/stores': + specifier: workspace:* + version: link:../../packages/stores + '@vben/styles': + specifier: workspace:* + version: link:../../packages/styles + '@vben/types': + specifier: workspace:* + version: link:../../packages/types + '@vben/utils': + specifier: workspace:* + version: link:../../packages/utils + '@vueuse/core': + specifier: 'catalog:' + version: 14.1.0(vue@3.5.27(typescript@5.9.3)) + antdv-next: + specifier: 'catalog:' + version: 1.0.2(date-fns@4.1.0)(vue@3.5.27(typescript@5.9.3)) + dayjs: + specifier: 'catalog:' + version: 1.11.19 + pinia: + specifier: ^3.0.4 + version: 3.0.4(typescript@5.9.3)(vue@3.5.27(typescript@5.9.3)) + vue: + specifier: ^3.5.27 + version: 3.5.27(typescript@5.9.3) + vue-router: + specifier: 'catalog:' + version: 4.6.4(vue@3.5.27(typescript@5.9.3)) + apps/web-ele: dependencies: '@vben/access': @@ -958,7 +1024,7 @@ importers: dependencies: '@commitlint/cli': specifier: 'catalog:' - version: 19.8.1(@types/node@24.10.9)(typescript@5.9.3) + version: 19.8.1(@types/node@25.0.10)(typescript@5.9.3) '@commitlint/config-conventional': specifier: 'catalog:' version: 19.8.1 @@ -2082,6 +2148,17 @@ packages: '@ant-design/colors@6.0.0': resolution: {integrity: sha512-qAZRvPzfdWHtfameEGP2Qvuf838NhergR35o+EuVyB5XvSA98xod5r4utvi4TJ3ywmevm290g9nsCG5MryrdWQ==} + '@ant-design/colors@7.2.1': + resolution: {integrity: sha512-lCHDcEzieu4GA3n8ELeZ5VQ8pKQAWcGGLRTQ50aQM2iqPpq2evTxER84jfdPvsPAtEcZ7m44NI45edFMo8oOYQ==} + + '@ant-design/fast-color@2.0.6': + resolution: {integrity: sha512-y2217gk4NqL35giHl72o6Zzqji9O7vHh9YmhUVkPtAOpoTCH4uWxo/pr4VE8t0+ChEPs0qo4eJRC5Q1eXWo3vA==} + engines: {node: '>=8.x'} + + '@ant-design/fast-color@3.0.1': + resolution: {integrity: sha512-esKJegpW4nckh0o6kV3Tkb7NPIZYbPnnFxmQDUmL08ukXZAvV85TZBr70eGuke/CIArLaP6aw8lt9KILjnWuOw==} + engines: {node: '>=8.x'} + '@ant-design/icons-svg@4.4.2': resolution: {integrity: sha512-vHbT+zJEVzllwP+CM+ul7reTEfBR0vgxFe7+lREAsAA7YGsYpboiq2sQNeQeRvh09GfQgs/GyFEvZpJ9cLXpXA==} @@ -2090,6 +2167,16 @@ packages: peerDependencies: vue: ^3.5.27 + '@antdv-next/cssinjs@1.0.1': + resolution: {integrity: sha512-9C7f2dZ7seDLuRU7dy23dmyxsWGZyHl9OriDQvamYHjqpKf6WdJhdUkQpTZ7q63t6h5JXrDgX4VYZH3JD6hX3g==} + peerDependencies: + vue: ^3.5.27 + + '@antdv-next/icons@1.0.0': + resolution: {integrity: sha512-O3gxRGEYOYsNbyPuhEqZ+HIDTakl6v3ukBDQwZ3ZvbzOPBorgdhMhHQ1WFjlLBCo72bdx5/wKyjz6oFRhk0G+g==} + peerDependencies: + vue: ^3.5.27 + '@antfu/install-pkg@1.1.0': resolution: {integrity: sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ==} @@ -3436,6 +3523,9 @@ packages: '@emotion/hash@0.9.2': resolution: {integrity: sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==} + '@emotion/unitless@0.7.5': + resolution: {integrity: sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==} + '@emotion/unitless@0.8.1': resolution: {integrity: sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ==} @@ -4789,6 +4879,220 @@ packages: cpu: [x64] os: [win32] + '@v-c/async-validator@1.0.0': + resolution: {integrity: sha512-nwi4hW/V3L5M4qY8cwScEuUonGfm1KRmN6aPwGpG9zhy7UDTEXKA3Tv4pdfjY9ryXKQc5TYo78TLSX9EjAPLUA==} + + '@v-c/cascader@1.0.0': + resolution: {integrity: sha512-GoDcocPUnIgJOVknk96nxzx7Jkf9kFKJ+sjcZPT45pBynZvaMOFJ9ZLGMB2ZMOYtRr0SfXeqZ09lLy9c8PtwZw==} + peerDependencies: + vue: ^3.5.27 + + '@v-c/checkbox@1.0.0': + resolution: {integrity: sha512-2abQmtpdYpQjb9+Xe9w1iPjdgSKDyQ1TpVq2QfTMdcj656S9npfDT2AH6HOgQV76oezV86dxgi1QG+lmXnDm0Q==} + peerDependencies: + vue: ^3.5.27 + + '@v-c/collapse@1.0.0': + resolution: {integrity: sha512-y4NAl3j4mka193ZMDLHdISA8to61qoROG6/kTQ0myM2ZuEsonnEK1QWlqoEw3gveMsa6a4RdyoXLxdGdcJyp0Q==} + peerDependencies: + vue: ^3.5.27 + + '@v-c/color-picker@1.0.4': + resolution: {integrity: sha512-VwNowJREYp25C6M39EHyTQ1nrxN2Usg96xGPupV2XiZs6wyKpXpXSjMbDXzD5/NKW1PKu/BTZOnrdLsrmY0mxg==} + peerDependencies: + vue: ^3.5.27 + + '@v-c/dialog@1.0.1': + resolution: {integrity: sha512-8+HqvlDXGcgie4Z+sQB3HnMkkFN0N1zfIxdQwtpXTl+gWu9ue8tz+zI7pNXMq7XdM3DU6A+x+qeNRstJ2poCbw==} + peerDependencies: + vue: ^3.5.27 + + '@v-c/drawer@1.0.0': + resolution: {integrity: sha512-1FhalqOqDmdvEfXGmlEZtwbeZBMKeK3+LIeDfXfqUiLy38QMMDTxMARmnHCxUGhuj20zFxrZUl0sxNEaTjTkEA==} + peerDependencies: + vue: ^3.5.27 + + '@v-c/dropdown@1.0.2': + resolution: {integrity: sha512-D6TACf3jUiRWx4xW5h2+wVT9SMYxUasFlAHESYJr4ZMjLTLLM1Q8iBjkjhGF+vA0eYR5zqRTwlaacN0DNDZBPw==} + peerDependencies: + vue: ^3.5.27 + + '@v-c/image@1.0.2': + resolution: {integrity: sha512-uTAhX+SRWjQHIUPJ0DAYUSzOEVnVDAmZazJNP5OqCqLuncrS7hUN37CQ1XgNKaF2Dt8b1aPFZlpXYb/zXfQdXw==} + peerDependencies: + vue: ^3.5.27 + + '@v-c/input-number@1.0.2': + resolution: {integrity: sha512-9wbAmdC0Nt18XlLLgfVbmulY50K7tc5RZsNeZuWnmqluTK3JTgZRSbPdRkzl308x8vtGyCOufnepsMrFei1NoQ==} + peerDependencies: + vue: ^3.5.27 + + '@v-c/input@1.0.2': + resolution: {integrity: sha512-NzBor6XbUYP42zRrcaBUgWtQI1aIaN3oylmMdvSZ5UMcWsAXRNC8XKsedkFF/LLOEJBJ2NaTbomHRwkIjmtQAA==} + peerDependencies: + vue: ^3.5.27 + + '@v-c/mentions@1.0.0': + resolution: {integrity: sha512-trkG1lvfiaIY7UnHn0gx6B01o3rFLEMin3KGp1q4oU6zOCRWde4ejZ+EHSvmXzOz2N+FlRMTE4EMJFi4w0oOlQ==} + peerDependencies: + vue: ^3.5.27 + + '@v-c/menu@1.0.10': + resolution: {integrity: sha512-iKXuPpZteVjPlg3nQ+4YbAlYa3/I21N/5RswKLai61IRO/8IcfRLc+YBEoG3n5xV+I5b2Yby2iJjsA8lU1En/w==} + peerDependencies: + vue: ^3.5.27 + + '@v-c/mini-decimal@1.0.1': + resolution: {integrity: sha512-76wZLdlkI017iDlaZMNOWZyDCv29YVabUJn5urQgIKtW4dnI5AkNXWtmLyhl/mu/OS7ZGisRi5ai/558QhLQxQ==} + + '@v-c/mutate-observer@1.0.1': + resolution: {integrity: sha512-84+9KGORX8LY9u+K0DEGyRwRCJaky0sjRkXxBC7X/jahHJl8NQGQ0Gxve5IVwaxRTfZ9eftlRmHs90JD6Utfqg==} + peerDependencies: + vue: ^3.5.27 + + '@v-c/notification@1.0.0': + resolution: {integrity: sha512-aU5g+ZiYxp0KVdKuho067wJRF38Mv7MrQS95dwSJLsbDmVFBpjO3Lo3ptakfPkwn+7uwRytHKIf39t9QVGk+sg==} + peerDependencies: + vue: ^3.5.27 + + '@v-c/overflow@1.0.3': + resolution: {integrity: sha512-GVWxd2gk9T0t9kO7EH1fMy2DgYULle/D+GBXiEeB5j/V1b9Gj39pvFLA1EHuiiyJW56Lr/P/uJ/ZM38WFh755w==} + peerDependencies: + vue: ^3.5.27 + + '@v-c/pagination@1.0.0': + resolution: {integrity: sha512-uYIMkvHKMtY+nwHTu5rXxiq6KPf0zGpZbtQTn1nDPng0tOyA1vLQ+R6OfE+1LOwuQqvFTEDnAq4vb90By+eBfw==} + peerDependencies: + vue: ^3.5.27 + + '@v-c/picker@1.0.2': + resolution: {integrity: sha512-Lf9qPZ/hODaIBEoXpkpGmHK5oXubquia8bYN8zVWigr312jbl/bI6txRF4p+zci0UTtJQsOX1zp9fqX8rg0vSA==} + peerDependencies: + date-fns: '>= 2.x' + dayjs: '>= 1.x' + luxon: '>= 3.x' + moment: '>= 2.x' + vue: ^3.5.27 + peerDependenciesMeta: + date-fns: + optional: true + dayjs: + optional: true + luxon: + optional: true + moment: + optional: true + + '@v-c/portal@1.0.7': + resolution: {integrity: sha512-iIcU+9C8GNas7BWMXlWkwz9RoB8E7B4f0D+qPNTPLUbdwDvKRNypATYImkIWK/BSGDHOkxu1kIBshxgolaODfA==} + peerDependencies: + vue: ^3.5.27 + + '@v-c/progress@1.0.0': + resolution: {integrity: sha512-kWDTU1uXnPDMmoezwyAECxuSH+WKn92OjSdk/GgDbQgZ0qNy9woOiRe5fOsrcy61agHdJxzf0MvsUy1b6bZVlA==} + peerDependencies: + vue: ^3.5.27 + + '@v-c/qrcode@1.0.0': + resolution: {integrity: sha512-OSMrYDhP/NQiUcO6J0X2X8BskHPRqX/E/F9npH3oayZgjCo5Aom+63Ja3J0u6SOmKP1JgLSgjrm5karc0671jw==} + peerDependencies: + vue: ^3.5.27 + + '@v-c/rate@1.0.0': + resolution: {integrity: sha512-H2cj/dS3guxq9s79HlTzu8uUzH/dQM8Ko5zlPosWrBI33YvySqoxcShY8cZS/tcq8I4xDE/SjeAmBe7rHd9VEA==} + peerDependencies: + vue: ^3.5.27 + + '@v-c/resize-observer@1.0.8': + resolution: {integrity: sha512-VH8WBsNfZA5KQ+CXVaQ1PK5B6FIHnuTdqOLrjRWiZTrIYDZi/MyREi9b21YDj55fbFWMRx4yapnO9tiZX1RNxA==} + peerDependencies: + vue: ^3.5.27 + + '@v-c/segmented@1.0.0': + resolution: {integrity: sha512-HWo8Ck6Lg0epTEvw5d2yhE+mU/SOxTN6/ngMXLz7iwGI2TwskKu8l+atOSNcJ7XqS/QgsqIHpL26GATPOS8qvQ==} + peerDependencies: + vue: ^3.5.27 + + '@v-c/select@1.0.9': + resolution: {integrity: sha512-RpZMtjKi3BIWj2+SKJKFKhydBE0Q0vZYyJduJZ6GioBi7JF26JPQuwZOsthaHDVr6oaOoc9ejlSAQqP92tFIKA==} + peerDependencies: + vue: ^3.5.27 + + '@v-c/slick@1.0.0': + resolution: {integrity: sha512-Pzb4bahxbXvXTCmgOJ9OX6Ek1joUHx0EFUxmjDSAfrvaAkHWv2pSqGLDF0CJvm+/uG+rcdT2YQUNcxMtKInQ3A==} + peerDependencies: + vue: ^3.5.27 + + '@v-c/slider@1.0.10': + resolution: {integrity: sha512-KMIVytBm8K8RQ+aPPraS28GmBptGHESF/gDRbGjOLD7xyivuQDJeEqVaUFY3EcCWsERjh4VP/L96gUbMTF0uag==} + peerDependencies: + vue: ^3.5.27 + + '@v-c/steps@1.0.0': + resolution: {integrity: sha512-DPL0OOb8pDLlTPZB93b8+Saxiz6V5zEpGXKaCnsbXUuOhimkc7089AuEKfpMw+8x1SrVe+gapWf5RRHWXUm2pg==} + peerDependencies: + vue: ^3.5.27 + + '@v-c/switch@1.0.0': + resolution: {integrity: sha512-VIem244KJkYfqDgofpgHjK00sGL9rJ/9OtmK4Gbs4hnPsrTtzHDBRltYxR4IT7HQleathZfj6NhcZ1bjdWKYUw==} + peerDependencies: + vue: ^3.5.27 + + '@v-c/table@1.0.0': + resolution: {integrity: sha512-1/TMEppX3BpLYYSXzAcwrD5Os6O1VX5OGZsvGyqIp9A8Iy8QeI4Vb54XizGyooHNMs378RIWzX4yDe9JxYMm/A==} + peerDependencies: + vue: ^3.5.27 + + '@v-c/tabs@1.0.1': + resolution: {integrity: sha512-6G6cWKdxb8l3IuR802mZGV+l8GAvCEJCoVQcyG3BwiBYOeOii6eyET5D+yMp7mC7dsFRxc2y7q0krxyo97CosQ==} + peerDependencies: + vue: ^3.5.27 + + '@v-c/textarea@1.0.3': + resolution: {integrity: sha512-oCpqdOyiNPFgLRUw913IjTdIMVtryy9maJEaSz+ledn/cVO4OJ44dEP8eCnhxlHTfduLKesKIlDoRIBhLu4qqQ==} + peerDependencies: + vue: ^3.5.27 + + '@v-c/tooltip@1.0.2': + resolution: {integrity: sha512-EwKbftSPCBrp+D40qMXV0/IzOIshQ6Wm5a+yYBZKJxF03uR0sUkQP+/cj7V55Gr9rDJ5G1EaxQjCuQAezwhzig==} + peerDependencies: + vue: ^3.5.27 + + '@v-c/tour@1.0.3': + resolution: {integrity: sha512-y4DVJPP7jvL+MWUMAKQWxLAMXSWJEfZXaKASPn3DKbSQ8drBhsjMXwcep3glAfrCjCKfj/QD3OrUMxqydi4qFw==} + peerDependencies: + vue: ^3.5.27 + + '@v-c/tree-select@1.0.0': + resolution: {integrity: sha512-EOUt3zBFMm2wCCDgY5pYU2mjff7NQiGss0uM/J2dOw8JP3LgPjhCuT2/0ZgXt6HkqK+Fl5AdhzxYujGbgTw6Ow==} + peerDependencies: + vue: ^3.5.27 + + '@v-c/tree@1.0.2': + resolution: {integrity: sha512-Z/x1DuazauoPtuKWaL6iaNdTq6t/c+6j8dbOxQN7kAJOW4RJ8BKKx8DA0fq49R7OCUH/YQLOWTrWI5gyQ0169A==} + peerDependencies: + vue: ^3.5.27 + + '@v-c/trigger@1.0.10': + resolution: {integrity: sha512-wPX/RBzJ7JzSShqBZlX8IWFPpa9c5jHvxzGdGTukBRukuPDBuXLLKHgt9IIpkqukuJuN3HLU39m+uuUK1rZ4Lg==} + peerDependencies: + vue: ^3.5.27 + + '@v-c/upload@1.0.0': + resolution: {integrity: sha512-W92PNCD61aM/B5w8oUzHQSDHur1T8484726Ls0IoNMO5nPiF/15eEE3RuuI/t7xXQVP/fA06hNSwzXwGWdDg1w==} + peerDependencies: + vue: ^3.5.27 + + '@v-c/util@1.0.13': + resolution: {integrity: sha512-/CUsbqrgqMz2TGqdzUnq1jwNFwX6lqy+HsYzmGH8auTUly9buIQiDmoxLxnjsI2XO6IryCqhzOP2Ii1213zqtA==} + peerDependencies: + vue: ^3.5.27 + + '@v-c/virtual-list@1.0.5': + resolution: {integrity: sha512-hv+ZIXkT7Bprv4pRloa/EYfWsJjOL2hI6wRSWX/XOxMcra+eq9uFvTY5GSOzYURPyzA/ExGq4607+fl1vEhwZQ==} + peerDependencies: + vue: ^3.5.27 + '@vee-validate/zod@4.15.1': resolution: {integrity: sha512-329Z4TDBE5Vx0FdbA8S4eR9iGCFFUNGbxjpQ20ff5b5wGueScjocUIx9JHPa79LTG06RnlUR4XogQsjN4tecKA==} peerDependencies: @@ -4984,6 +5288,11 @@ packages: peerDependencies: vue: ^3.5.27 + '@vueuse/core@14.2.0': + resolution: {integrity: sha512-tpjzVl7KCQNVd/qcaCE9XbejL38V6KJAEq/tVXj7mDPtl6JtzmUdnXelSS+ULRkkrDgzYVK7EerQJvd2jR794Q==} + peerDependencies: + vue: ^3.5.27 + '@vueuse/integrations@12.8.2': resolution: {integrity: sha512-fbGYivgK5uBTRt7p5F3zy6VrETlV9RtZjBqd1/HxGdjdckBgBM4ugP8LHpjolqTj14TXTxSK1ZfgPbHYyGuH7g==} peerDependencies: @@ -5079,6 +5388,9 @@ packages: '@vueuse/metadata@14.1.0': resolution: {integrity: sha512-7hK4g015rWn2PhKcZ99NyT+ZD9sbwm7SGvp7k+k+rKGWnLjS/oQozoIZzWfCewSUeBmnJkIb+CNr7Zc/EyRnnA==} + '@vueuse/metadata@14.2.0': + resolution: {integrity: sha512-i3axTGjU8b13FtyR4Keeama+43iD+BwX9C2TmzBVKqjSHArF03hjkp2SBZ1m72Jk2UtrX0aYCugBq2R1fhkuAQ==} + '@vueuse/motion@3.0.3': resolution: {integrity: sha512-4B+ITsxCI9cojikvrpaJcLXyq0spj3sdlzXjzesWdMRd99hhtFI6OJ/1JsqwtF73YooLe0hUn/xDR6qCtmn5GQ==} peerDependencies: @@ -5100,6 +5412,11 @@ packages: peerDependencies: vue: ^3.5.27 + '@vueuse/shared@14.2.0': + resolution: {integrity: sha512-Z0bmluZTlAXgUcJ4uAFaML16JcD8V0QG00Db3quR642I99JXIDRa2MI2LGxiLVhcBjVnL1jOzIvT5TT2lqJlkA==} + peerDependencies: + vue: ^3.5.27 + '@vxe-ui/core@4.3.1': resolution: {integrity: sha512-sr2WdFDWM3IKID02HbSaDxxRDvj1LZ5ZkOnH2POvGkkCfCWItkx3avkizfRUk8RtjNU+wXozaPbYTNha5kjSdg==} peerDependencies: @@ -5215,6 +5532,9 @@ packages: peerDependencies: vue: ^3.5.27 + antdv-next@1.0.2: + resolution: {integrity: sha512-JkPXDLk7MfAtye2pS3z/heLiUeow1K3JJCAnmaM+vzZhCds+wXUCNNHJ03qUIQvKCNbFdko4OJ9p0dY/pDJncg==} + any-promise@1.3.0: resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} @@ -5716,6 +6036,9 @@ packages: compute-scroll-into-view@1.0.20: resolution: {integrity: sha512-UCB0ioiyj8CRjtrvaceBLqqhZCVP+1B8+NWQhmdsm0VXOJtobBCf1dBQmebCCo34qZmUwZfIH2MZLqNHazrfjg==} + compute-scroll-into-view@3.1.1: + resolution: {integrity: sha512-VRhuHOLoKYOy4UbilLbUzbYg93XLjv2PncJC50EuTWPA3gaja1UjBsUP/D/9/juV3vQFr6XBEzn9KCAHdUvOHw==} + concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} @@ -6355,6 +6678,9 @@ packages: resolution: {integrity: sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==} engines: {node: '>= 0.4'} + es-toolkit@1.43.0: + resolution: {integrity: sha512-SKCT8AsWvYzBBuUqMk4NPwFlSdqLpJwmy6AP322ERn8W2YLIB6JBXnwMI2Qsh2gfphT3q7EKAxKb23cvFHFwKA==} + es-toolkit@1.44.0: resolution: {integrity: sha512-6penXeZalaV88MM3cGkFZZfOoLGWshWWfdy0tWw/RlVVyhvMaWSBTOvXNeiW3e5FwdS5ePW0LGEu17zT139ktg==} @@ -9362,6 +9688,9 @@ packages: scroll-into-view-if-needed@2.2.31: resolution: {integrity: sha512-dGCXy99wZQivjmjIqihaBQNjryrz5rueJY7eHfTdyWEiR4ttYpsajb14rn9s5d4DY4EcY6+4+U/maARBXJedkA==} + scroll-into-view-if-needed@3.1.0: + resolution: {integrity: sha512-49oNpRjWRvnU8NyGVmUaYG4jtTkNonFZI86MmGRDqBphEK2EXT9gdEUoQPZhuBM8yWHxCWbobltqYO5M4XrUvQ==} + scslre@0.3.0: resolution: {integrity: sha512-3A6sD0WYP7+QrjbfNA2FN3FsOaGGFoekCVgTyypy53gPxhbkCIjtO6YWgdrfM+n/8sI8JeXZOIxsHjMTNxQ4nQ==} engines: {node: ^14.0.0 || >=16.0.0} @@ -10978,6 +11307,16 @@ snapshots: dependencies: '@ctrl/tinycolor': 4.2.0 + '@ant-design/colors@7.2.1': + dependencies: + '@ant-design/fast-color': 2.0.6 + + '@ant-design/fast-color@2.0.6': + dependencies: + '@babel/runtime': 7.28.6 + + '@ant-design/fast-color@3.0.1': {} + '@ant-design/icons-svg@4.4.2': {} '@ant-design/icons-vue@7.0.1(vue@3.5.27(typescript@5.9.3))': @@ -10986,6 +11325,24 @@ snapshots: '@ant-design/icons-svg': 4.4.2 vue: 3.5.27(typescript@5.9.3) + '@antdv-next/cssinjs@1.0.1(vue@3.5.27(typescript@5.9.3))': + dependencies: + '@emotion/hash': 0.8.0 + '@emotion/unitless': 0.7.5 + '@v-c/util': 1.0.13(vue@3.5.27(typescript@5.9.3)) + csstype: 3.2.3 + defu: 6.1.4 + stylis: 4.3.6 + vue: 3.5.27(typescript@5.9.3) + + '@antdv-next/icons@1.0.0(vue@3.5.27(typescript@5.9.3))': + dependencies: + '@ant-design/colors': 7.2.1 + '@ant-design/icons-svg': 4.4.2 + '@v-c/util': 1.0.13(vue@3.5.27(typescript@5.9.3)) + es-toolkit: 1.43.0 + vue: 3.5.27(typescript@5.9.3) + '@antfu/install-pkg@1.1.0': dependencies: package-manager-detector: 1.6.0 @@ -11926,11 +12283,11 @@ snapshots: '@cloudflare/kv-asset-handler@0.4.2': {} - '@commitlint/cli@19.8.1(@types/node@24.10.9)(typescript@5.9.3)': + '@commitlint/cli@19.8.1(@types/node@25.0.10)(typescript@5.9.3)': dependencies: '@commitlint/format': 19.8.1 '@commitlint/lint': 19.8.1 - '@commitlint/load': 19.8.1(@types/node@24.10.9)(typescript@5.9.3) + '@commitlint/load': 19.8.1(@types/node@25.0.10)(typescript@5.9.3) '@commitlint/read': 19.8.1 '@commitlint/types': 19.8.1 tinyexec: 1.0.2 @@ -11977,7 +12334,7 @@ snapshots: '@commitlint/rules': 19.8.1 '@commitlint/types': 19.8.1 - '@commitlint/load@19.8.1(@types/node@24.10.9)(typescript@5.9.3)': + '@commitlint/load@19.8.1(@types/node@25.0.10)(typescript@5.9.3)': dependencies: '@commitlint/config-validator': 19.8.1 '@commitlint/execute-rule': 19.8.1 @@ -11985,7 +12342,7 @@ snapshots: '@commitlint/types': 19.8.1 chalk: 5.6.2 cosmiconfig: 9.0.0(typescript@5.9.3) - cosmiconfig-typescript-loader: 6.2.0(@types/node@24.10.9)(cosmiconfig@9.0.0(typescript@5.9.3))(typescript@5.9.3) + cosmiconfig-typescript-loader: 6.2.0(@types/node@25.0.10)(cosmiconfig@9.0.0(typescript@5.9.3))(typescript@5.9.3) lodash.isplainobject: 4.0.6 lodash.merge: 4.6.2 lodash.uniq: 4.5.0 @@ -12627,6 +12984,8 @@ snapshots: '@emotion/hash@0.9.2': {} + '@emotion/unitless@0.7.5': {} + '@emotion/unitless@0.8.1': {} '@epic-web/invariant@1.0.0': {} @@ -13968,6 +14327,249 @@ snapshots: '@unrs/resolver-binding-win32-x64-msvc@1.11.1': optional: true + '@v-c/async-validator@1.0.0': {} + + '@v-c/cascader@1.0.0(vue@3.5.27(typescript@5.9.3))': + dependencies: + '@v-c/select': 1.0.9(vue@3.5.27(typescript@5.9.3)) + '@v-c/tree': 1.0.2(vue@3.5.27(typescript@5.9.3)) + '@v-c/util': 1.0.13(vue@3.5.27(typescript@5.9.3)) + vue: 3.5.27(typescript@5.9.3) + + '@v-c/checkbox@1.0.0(vue@3.5.27(typescript@5.9.3))': + dependencies: + '@v-c/util': 1.0.13(vue@3.5.27(typescript@5.9.3)) + vue: 3.5.27(typescript@5.9.3) + + '@v-c/collapse@1.0.0(vue@3.5.27(typescript@5.9.3))': + dependencies: + '@v-c/util': 1.0.13(vue@3.5.27(typescript@5.9.3)) + vue: 3.5.27(typescript@5.9.3) + + '@v-c/color-picker@1.0.4(vue@3.5.27(typescript@5.9.3))': + dependencies: + '@ant-design/fast-color': 3.0.1 + '@v-c/util': 1.0.13(vue@3.5.27(typescript@5.9.3)) + vue: 3.5.27(typescript@5.9.3) + + '@v-c/dialog@1.0.1(vue@3.5.27(typescript@5.9.3))': + dependencies: + '@v-c/portal': 1.0.7(vue@3.5.27(typescript@5.9.3)) + '@v-c/util': 1.0.13(vue@3.5.27(typescript@5.9.3)) + vue: 3.5.27(typescript@5.9.3) + + '@v-c/drawer@1.0.0(vue@3.5.27(typescript@5.9.3))': + dependencies: + '@v-c/portal': 1.0.7(vue@3.5.27(typescript@5.9.3)) + '@v-c/util': 1.0.13(vue@3.5.27(typescript@5.9.3)) + vue: 3.5.27(typescript@5.9.3) + + '@v-c/dropdown@1.0.2(vue@3.5.27(typescript@5.9.3))': + dependencies: + '@v-c/trigger': 1.0.10(vue@3.5.27(typescript@5.9.3)) + '@v-c/util': 1.0.13(vue@3.5.27(typescript@5.9.3)) + vue: 3.5.27(typescript@5.9.3) + + '@v-c/image@1.0.2(vue@3.5.27(typescript@5.9.3))': + dependencies: + '@v-c/portal': 1.0.7(vue@3.5.27(typescript@5.9.3)) + '@v-c/util': 1.0.13(vue@3.5.27(typescript@5.9.3)) + vue: 3.5.27(typescript@5.9.3) + + '@v-c/input-number@1.0.2(vue@3.5.27(typescript@5.9.3))': + dependencies: + '@v-c/input': 1.0.2(vue@3.5.27(typescript@5.9.3)) + '@v-c/mini-decimal': 1.0.1 + '@v-c/util': 1.0.13(vue@3.5.27(typescript@5.9.3)) + vue: 3.5.27(typescript@5.9.3) + + '@v-c/input@1.0.2(vue@3.5.27(typescript@5.9.3))': + dependencies: + '@v-c/util': 1.0.13(vue@3.5.27(typescript@5.9.3)) + vue: 3.5.27(typescript@5.9.3) + + '@v-c/mentions@1.0.0(vue@3.5.27(typescript@5.9.3))': + dependencies: + '@v-c/input': 1.0.2(vue@3.5.27(typescript@5.9.3)) + '@v-c/menu': 1.0.10(vue@3.5.27(typescript@5.9.3)) + '@v-c/textarea': 1.0.3(vue@3.5.27(typescript@5.9.3)) + '@v-c/trigger': 1.0.10(vue@3.5.27(typescript@5.9.3)) + '@v-c/util': 1.0.13(vue@3.5.27(typescript@5.9.3)) + vue: 3.5.27(typescript@5.9.3) + + '@v-c/menu@1.0.10(vue@3.5.27(typescript@5.9.3))': + dependencies: + '@v-c/overflow': 1.0.3(vue@3.5.27(typescript@5.9.3)) + '@v-c/trigger': 1.0.10(vue@3.5.27(typescript@5.9.3)) + '@v-c/util': 1.0.13(vue@3.5.27(typescript@5.9.3)) + vue: 3.5.27(typescript@5.9.3) + + '@v-c/mini-decimal@1.0.1': {} + + '@v-c/mutate-observer@1.0.1(vue@3.5.27(typescript@5.9.3))': + dependencies: + '@v-c/resize-observer': 1.0.8(vue@3.5.27(typescript@5.9.3)) + '@v-c/util': 1.0.13(vue@3.5.27(typescript@5.9.3)) + vue: 3.5.27(typescript@5.9.3) + + '@v-c/notification@1.0.0(vue@3.5.27(typescript@5.9.3))': + dependencies: + '@v-c/util': 1.0.13(vue@3.5.27(typescript@5.9.3)) + vue: 3.5.27(typescript@5.9.3) + + '@v-c/overflow@1.0.3(vue@3.5.27(typescript@5.9.3))': + dependencies: + '@v-c/resize-observer': 1.0.8(vue@3.5.27(typescript@5.9.3)) + '@v-c/util': 1.0.13(vue@3.5.27(typescript@5.9.3)) + vue: 3.5.27(typescript@5.9.3) + + '@v-c/pagination@1.0.0(vue@3.5.27(typescript@5.9.3))': + dependencies: + '@v-c/util': 1.0.13(vue@3.5.27(typescript@5.9.3)) + vue: 3.5.27(typescript@5.9.3) + + '@v-c/picker@1.0.2(date-fns@4.1.0)(dayjs@1.11.19)(vue@3.5.27(typescript@5.9.3))': + dependencies: + '@v-c/overflow': 1.0.3(vue@3.5.27(typescript@5.9.3)) + '@v-c/resize-observer': 1.0.8(vue@3.5.27(typescript@5.9.3)) + '@v-c/trigger': 1.0.10(vue@3.5.27(typescript@5.9.3)) + '@v-c/util': 1.0.13(vue@3.5.27(typescript@5.9.3)) + vue: 3.5.27(typescript@5.9.3) + optionalDependencies: + date-fns: 4.1.0 + dayjs: 1.11.19 + + '@v-c/portal@1.0.7(vue@3.5.27(typescript@5.9.3))': + dependencies: + '@v-c/util': 1.0.13(vue@3.5.27(typescript@5.9.3)) + vue: 3.5.27(typescript@5.9.3) + + '@v-c/progress@1.0.0(vue@3.5.27(typescript@5.9.3))': + dependencies: + '@v-c/util': 1.0.13(vue@3.5.27(typescript@5.9.3)) + vue: 3.5.27(typescript@5.9.3) + + '@v-c/qrcode@1.0.0(vue@3.5.27(typescript@5.9.3))': + dependencies: + '@v-c/util': 1.0.13(vue@3.5.27(typescript@5.9.3)) + vue: 3.5.27(typescript@5.9.3) + + '@v-c/rate@1.0.0(vue@3.5.27(typescript@5.9.3))': + dependencies: + '@v-c/util': 1.0.13(vue@3.5.27(typescript@5.9.3)) + vue: 3.5.27(typescript@5.9.3) + + '@v-c/resize-observer@1.0.8(vue@3.5.27(typescript@5.9.3))': + dependencies: + '@v-c/util': 1.0.13(vue@3.5.27(typescript@5.9.3)) + resize-observer-polyfill: 1.5.1 + vue: 3.5.27(typescript@5.9.3) + + '@v-c/segmented@1.0.0(vue@3.5.27(typescript@5.9.3))': + dependencies: + '@v-c/util': 1.0.13(vue@3.5.27(typescript@5.9.3)) + vue: 3.5.27(typescript@5.9.3) + + '@v-c/select@1.0.9(vue@3.5.27(typescript@5.9.3))': + dependencies: + '@v-c/overflow': 1.0.3(vue@3.5.27(typescript@5.9.3)) + '@v-c/trigger': 1.0.10(vue@3.5.27(typescript@5.9.3)) + '@v-c/util': 1.0.13(vue@3.5.27(typescript@5.9.3)) + '@v-c/virtual-list': 1.0.5(vue@3.5.27(typescript@5.9.3)) + vue: 3.5.27(typescript@5.9.3) + + '@v-c/slick@1.0.0(vue@3.5.27(typescript@5.9.3))': + dependencies: + '@v-c/util': 1.0.13(vue@3.5.27(typescript@5.9.3)) + es-toolkit: 1.44.0 + vue: 3.5.27(typescript@5.9.3) + + '@v-c/slider@1.0.10(vue@3.5.27(typescript@5.9.3))': + dependencies: + '@v-c/util': 1.0.13(vue@3.5.27(typescript@5.9.3)) + vue: 3.5.27(typescript@5.9.3) + + '@v-c/steps@1.0.0(vue@3.5.27(typescript@5.9.3))': + dependencies: + '@v-c/util': 1.0.13(vue@3.5.27(typescript@5.9.3)) + vue: 3.5.27(typescript@5.9.3) + + '@v-c/switch@1.0.0(vue@3.5.27(typescript@5.9.3))': + dependencies: + '@v-c/util': 1.0.13(vue@3.5.27(typescript@5.9.3)) + vue: 3.5.27(typescript@5.9.3) + + '@v-c/table@1.0.0(vue@3.5.27(typescript@5.9.3))': + dependencies: + '@v-c/resize-observer': 1.0.8(vue@3.5.27(typescript@5.9.3)) + '@v-c/util': 1.0.13(vue@3.5.27(typescript@5.9.3)) + '@v-c/virtual-list': 1.0.5(vue@3.5.27(typescript@5.9.3)) + vue: 3.5.27(typescript@5.9.3) + + '@v-c/tabs@1.0.1(vue@3.5.27(typescript@5.9.3))': + dependencies: + '@v-c/dropdown': 1.0.2(vue@3.5.27(typescript@5.9.3)) + '@v-c/menu': 1.0.10(vue@3.5.27(typescript@5.9.3)) + '@v-c/overflow': 1.0.3(vue@3.5.27(typescript@5.9.3)) + '@v-c/resize-observer': 1.0.8(vue@3.5.27(typescript@5.9.3)) + '@v-c/util': 1.0.13(vue@3.5.27(typescript@5.9.3)) + vue: 3.5.27(typescript@5.9.3) + + '@v-c/textarea@1.0.3(vue@3.5.27(typescript@5.9.3))': + dependencies: + '@v-c/input': 1.0.2(vue@3.5.27(typescript@5.9.3)) + '@v-c/resize-observer': 1.0.8(vue@3.5.27(typescript@5.9.3)) + '@v-c/util': 1.0.13(vue@3.5.27(typescript@5.9.3)) + vue: 3.5.27(typescript@5.9.3) + + '@v-c/tooltip@1.0.2(vue@3.5.27(typescript@5.9.3))': + dependencies: + '@v-c/trigger': 1.0.10(vue@3.5.27(typescript@5.9.3)) + '@v-c/util': 1.0.13(vue@3.5.27(typescript@5.9.3)) + vue: 3.5.27(typescript@5.9.3) + + '@v-c/tour@1.0.3(vue@3.5.27(typescript@5.9.3))': + dependencies: + '@v-c/portal': 1.0.7(vue@3.5.27(typescript@5.9.3)) + '@v-c/trigger': 1.0.10(vue@3.5.27(typescript@5.9.3)) + '@v-c/util': 1.0.13(vue@3.5.27(typescript@5.9.3)) + vue: 3.5.27(typescript@5.9.3) + + '@v-c/tree-select@1.0.0(vue@3.5.27(typescript@5.9.3))': + dependencies: + '@v-c/select': 1.0.9(vue@3.5.27(typescript@5.9.3)) + '@v-c/tree': 1.0.2(vue@3.5.27(typescript@5.9.3)) + '@v-c/util': 1.0.13(vue@3.5.27(typescript@5.9.3)) + vue: 3.5.27(typescript@5.9.3) + + '@v-c/tree@1.0.2(vue@3.5.27(typescript@5.9.3))': + dependencies: + '@v-c/util': 1.0.13(vue@3.5.27(typescript@5.9.3)) + '@v-c/virtual-list': 1.0.5(vue@3.5.27(typescript@5.9.3)) + vue: 3.5.27(typescript@5.9.3) + + '@v-c/trigger@1.0.10(vue@3.5.27(typescript@5.9.3))': + dependencies: + '@v-c/portal': 1.0.7(vue@3.5.27(typescript@5.9.3)) + '@v-c/resize-observer': 1.0.8(vue@3.5.27(typescript@5.9.3)) + '@v-c/util': 1.0.13(vue@3.5.27(typescript@5.9.3)) + vue: 3.5.27(typescript@5.9.3) + + '@v-c/upload@1.0.0(vue@3.5.27(typescript@5.9.3))': + dependencies: + '@v-c/util': 1.0.13(vue@3.5.27(typescript@5.9.3)) + vue: 3.5.27(typescript@5.9.3) + + '@v-c/util@1.0.13(vue@3.5.27(typescript@5.9.3))': + dependencies: + vue: 3.5.27(typescript@5.9.3) + + '@v-c/virtual-list@1.0.5(vue@3.5.27(typescript@5.9.3))': + dependencies: + '@v-c/resize-observer': 1.0.8(vue@3.5.27(typescript@5.9.3)) + '@v-c/util': 1.0.13(vue@3.5.27(typescript@5.9.3)) + vue: 3.5.27(typescript@5.9.3) + '@vee-validate/zod@4.15.1(vue@3.5.27(typescript@5.9.3))(zod@3.25.76)': dependencies: type-fest: 4.41.0 @@ -14318,6 +14920,13 @@ snapshots: '@vueuse/shared': 14.1.0(vue@3.5.27(typescript@5.9.3)) vue: 3.5.27(typescript@5.9.3) + '@vueuse/core@14.2.0(vue@3.5.27(typescript@5.9.3))': + dependencies: + '@types/web-bluetooth': 0.0.21 + '@vueuse/metadata': 14.2.0 + '@vueuse/shared': 14.2.0(vue@3.5.27(typescript@5.9.3)) + vue: 3.5.27(typescript@5.9.3) + '@vueuse/integrations@12.8.2(async-validator@4.2.5)(axios@1.13.4)(change-case@5.4.4)(focus-trap@7.8.0)(nprogress@0.2.0)(qrcode@1.5.4)(sortablejs@1.15.6)(typescript@5.9.3)': dependencies: '@vueuse/core': 12.8.2(typescript@5.9.3) @@ -14356,6 +14965,8 @@ snapshots: '@vueuse/metadata@14.1.0': {} + '@vueuse/metadata@14.2.0': {} + '@vueuse/motion@3.0.3(magicast@0.5.1)(vue@3.5.27(typescript@5.9.3))': dependencies: '@vueuse/core': 13.9.0(vue@3.5.27(typescript@5.9.3)) @@ -14391,6 +15002,10 @@ snapshots: dependencies: vue: 3.5.27(typescript@5.9.3) + '@vueuse/shared@14.2.0(vue@3.5.27(typescript@5.9.3))': + dependencies: + vue: 3.5.27(typescript@5.9.3) + '@vxe-ui/core@4.3.1(vue@3.5.27(typescript@5.9.3))': dependencies: dom-zindex: 1.0.6 @@ -14527,6 +15142,62 @@ snapshots: vue-types: 3.0.2(vue@3.5.27(typescript@5.9.3)) warning: 4.0.3 + antdv-next@1.0.2(date-fns@4.1.0)(vue@3.5.27(typescript@5.9.3)): + dependencies: + '@ant-design/colors': 7.2.1 + '@ant-design/fast-color': 3.0.1 + '@antdv-next/cssinjs': 1.0.1(vue@3.5.27(typescript@5.9.3)) + '@antdv-next/icons': 1.0.0(vue@3.5.27(typescript@5.9.3)) + '@v-c/async-validator': 1.0.0 + '@v-c/cascader': 1.0.0(vue@3.5.27(typescript@5.9.3)) + '@v-c/checkbox': 1.0.0(vue@3.5.27(typescript@5.9.3)) + '@v-c/collapse': 1.0.0(vue@3.5.27(typescript@5.9.3)) + '@v-c/color-picker': 1.0.4(vue@3.5.27(typescript@5.9.3)) + '@v-c/dialog': 1.0.1(vue@3.5.27(typescript@5.9.3)) + '@v-c/drawer': 1.0.0(vue@3.5.27(typescript@5.9.3)) + '@v-c/dropdown': 1.0.2(vue@3.5.27(typescript@5.9.3)) + '@v-c/image': 1.0.2(vue@3.5.27(typescript@5.9.3)) + '@v-c/input': 1.0.2(vue@3.5.27(typescript@5.9.3)) + '@v-c/input-number': 1.0.2(vue@3.5.27(typescript@5.9.3)) + '@v-c/mentions': 1.0.0(vue@3.5.27(typescript@5.9.3)) + '@v-c/menu': 1.0.10(vue@3.5.27(typescript@5.9.3)) + '@v-c/mutate-observer': 1.0.1(vue@3.5.27(typescript@5.9.3)) + '@v-c/notification': 1.0.0(vue@3.5.27(typescript@5.9.3)) + '@v-c/pagination': 1.0.0(vue@3.5.27(typescript@5.9.3)) + '@v-c/picker': 1.0.2(date-fns@4.1.0)(dayjs@1.11.19)(vue@3.5.27(typescript@5.9.3)) + '@v-c/progress': 1.0.0(vue@3.5.27(typescript@5.9.3)) + '@v-c/qrcode': 1.0.0(vue@3.5.27(typescript@5.9.3)) + '@v-c/rate': 1.0.0(vue@3.5.27(typescript@5.9.3)) + '@v-c/resize-observer': 1.0.8(vue@3.5.27(typescript@5.9.3)) + '@v-c/segmented': 1.0.0(vue@3.5.27(typescript@5.9.3)) + '@v-c/select': 1.0.9(vue@3.5.27(typescript@5.9.3)) + '@v-c/slick': 1.0.0(vue@3.5.27(typescript@5.9.3)) + '@v-c/slider': 1.0.10(vue@3.5.27(typescript@5.9.3)) + '@v-c/steps': 1.0.0(vue@3.5.27(typescript@5.9.3)) + '@v-c/switch': 1.0.0(vue@3.5.27(typescript@5.9.3)) + '@v-c/table': 1.0.0(vue@3.5.27(typescript@5.9.3)) + '@v-c/tabs': 1.0.1(vue@3.5.27(typescript@5.9.3)) + '@v-c/textarea': 1.0.3(vue@3.5.27(typescript@5.9.3)) + '@v-c/tooltip': 1.0.2(vue@3.5.27(typescript@5.9.3)) + '@v-c/tour': 1.0.3(vue@3.5.27(typescript@5.9.3)) + '@v-c/tree': 1.0.2(vue@3.5.27(typescript@5.9.3)) + '@v-c/tree-select': 1.0.0(vue@3.5.27(typescript@5.9.3)) + '@v-c/trigger': 1.0.10(vue@3.5.27(typescript@5.9.3)) + '@v-c/upload': 1.0.0(vue@3.5.27(typescript@5.9.3)) + '@v-c/util': 1.0.13(vue@3.5.27(typescript@5.9.3)) + '@v-c/virtual-list': 1.0.5(vue@3.5.27(typescript@5.9.3)) + '@vueuse/core': 14.2.0(vue@3.5.27(typescript@5.9.3)) + dayjs: 1.11.19 + defu: 6.1.4 + es-toolkit: 1.43.0 + scroll-into-view-if-needed: 3.1.0 + throttle-debounce: 5.0.2 + transitivePeerDependencies: + - date-fns + - luxon + - moment + - vue + any-promise@1.3.0: {} anymatch@3.1.3: @@ -15064,6 +15735,8 @@ snapshots: compute-scroll-into-view@1.0.20: {} + compute-scroll-into-view@3.1.1: {} + concat-map@0.0.1: {} confbox@0.1.8: {} @@ -15125,9 +15798,9 @@ snapshots: core-util-is@1.0.3: {} - cosmiconfig-typescript-loader@6.2.0(@types/node@24.10.9)(cosmiconfig@9.0.0(typescript@5.9.3))(typescript@5.9.3): + cosmiconfig-typescript-loader@6.2.0(@types/node@25.0.10)(cosmiconfig@9.0.0(typescript@5.9.3))(typescript@5.9.3): dependencies: - '@types/node': 24.10.9 + '@types/node': 25.0.10 cosmiconfig: 9.0.0(typescript@5.9.3) jiti: 2.6.1 typescript: 5.9.3 @@ -15780,6 +16453,8 @@ snapshots: is-date-object: 1.1.0 is-symbol: 1.1.1 + es-toolkit@1.43.0: {} + es-toolkit@1.44.0: {} esbuild@0.25.12: @@ -18950,6 +19625,10 @@ snapshots: dependencies: compute-scroll-into-view: 1.0.20 + scroll-into-view-if-needed@3.1.0: + dependencies: + compute-scroll-into-view: 3.1.1 + scslre@0.3.0: dependencies: '@eslint-community/regexpp': 4.12.2 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 0460629b..b2d70f29 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -72,6 +72,7 @@ catalog: '@vueuse/integrations': ^14.1.0 '@vueuse/motion': ^3.0.3 ant-design-vue: ^4.2.6 + antdv-next: ^1.0.2 archiver: ^7.0.1 autoprefixer: ^10.4.23 axios: ^1.13.4 diff --git a/vben-admin.code-workspace b/vben-admin.code-workspace index 33df5a75..a18a9f48 100644 --- a/vben-admin.code-workspace +++ b/vben-admin.code-workspace @@ -8,6 +8,10 @@ "name": "@vben/web-antd", "path": "apps/web-antd", }, + { + "name": "@vben/web-antdv-next", + "path": "apps/web-antdv-next", + }, { "name": "@vben/web-ele", "path": "apps/web-ele",