Merge branch 'fork/xingyu4j/antdv-next'

pull/348/MERGE
Jin Mao 2026-05-19 14:14:28 +08:00
commit 76cfcda2e9
168 changed files with 4489 additions and 4284 deletions

View File

@ -1,5 +0,0 @@
---
'@vben/layouts': patch
---
fix: update primary color when toggling dark/light mode with custom theme

View File

@ -1,5 +0,0 @@
---
'@vben/common-ui': patch
---
fix: skip fixed footer height in auto-content-height calculation

View File

@ -1,5 +0,0 @@
---
'@vben/icons': patch
---
fix: guard svg icon loading during docs SSR

View File

@ -1,5 +0,0 @@
---
'@vben-core/shadcn-ui': patch
---
fix: preserve tree default value when treeData starts empty

12
.npmrc
View File

@ -1,13 +1 @@
registry=https://registry.npmmirror.com
public-hoist-pattern[]=lefthook
public-hoist-pattern[]=eslint
public-hoist-pattern[]=oxfmt
public-hoist-pattern[]=oxlint
public-hoist-pattern[]=stylelint
public-hoist-pattern[]=*postcss*
public-hoist-pattern[]=@commitlint/*
public-hoist-pattern[]=czg
strict-peer-dependencies=false
auto-install-peers=true
dedupe-peer-dependents=true

View File

@ -38,7 +38,7 @@
// lint && format
"oxc.enable": true,
"oxc.typeAware": true,
"oxc.typeAware": false,
"oxc.configPath": "oxlint.config.ts",
"oxc.fmt.configPath": "oxfmt.config.ts",
"eslint.useFlatConfig": true,

View File

@ -0,0 +1,85 @@
import { faker } from '@faker-js/faker';
import { eventHandler, getQuery } from 'h3';
import { verifyAccessToken } from '~/utils/jwt-utils';
import { unAuthorizedResponse, usePageResponseSuccess } from '~/utils/response';
const formatterCN = new Intl.DateTimeFormat('zh-CN', {
timeZone: 'Asia/Shanghai',
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
});
function generateMockDataList(count: number) {
const dataList = [];
for (let i = 0; i < count; i++) {
const dataItem: Record<string, any> = {
id: faker.string.uuid(),
name: faker.commerce.product(),
status: faker.helpers.arrayElement([0, 1]),
createTime: formatterCN.format(
faker.date.between({ from: '2022-01-01', to: '2025-01-01' }),
),
deptId: faker.string.uuid(),
remark: faker.lorem.sentence(),
};
dataList.push(dataItem);
}
return dataList;
}
const mockData = generateMockDataList(100);
export default eventHandler(async (event) => {
const userinfo = verifyAccessToken(event);
if (!userinfo) {
return unAuthorizedResponse(event);
}
const {
page = 1,
pageSize = 20,
name,
id,
remark,
startTime,
endTime,
deptId,
status,
} = getQuery(event);
let listData = structuredClone(mockData);
if (name) {
listData = listData.filter((item) =>
item.name.toLowerCase().includes(String(name).toLowerCase()),
);
}
if (id) {
listData = listData.filter((item) =>
item.id.toLowerCase().includes(String(id).toLowerCase()),
);
}
if (remark) {
listData = listData.filter((item) =>
item.remark?.toLowerCase()?.includes(String(remark).toLowerCase()),
);
}
if (startTime) {
listData = listData.filter((item) => item.createTime >= startTime);
}
if (endTime) {
listData = listData.filter((item) => item.createTime <= endTime);
}
if (['0', '1'].includes(status as string)) {
listData = listData.filter((item) => item.status === Number(status));
}
if (deptId) {
listData = listData.filter((item) => item.deptId === deptId);
}
return usePageResponseSuccess(page as string, pageSize as string, listData);
});

View File

@ -131,8 +131,8 @@ const PreviewGroup = defineAsyncComponent(() =>
import('ant-design-vue/es/image').then((res) => res.ImagePreviewGroup),
);
const withDefaultPlaceholder = <T extends Component>(
component: T,
const withDefaultPlaceholder = (
component: Component,
type: 'input' | 'select',
componentProps: Recordable<any> = {},
) => {
@ -702,7 +702,9 @@ async function initComponentAdapter() {
modelValueProp: 'value',
}),
Input: withDefaultPlaceholder(Input, 'input'),
InputNumber: withDefaultPlaceholder(InputNumber, 'input'),
InputNumber: withDefaultPlaceholder(InputNumber, 'input', {
style: { width: '100%' },
}),
InputPassword: withDefaultPlaceholder(InputPassword, 'input'),
Mentions: withDefaultPlaceholder(Mentions, 'input'),
// 自定义主要按钮

View File

@ -136,8 +136,8 @@ const PreviewGroup = defineAsyncComponent(() =>
import('antdv-next/dist/image/index').then((res) => res.ImagePreviewGroup),
);
const withDefaultPlaceholder = <T extends Component>(
component: T,
const withDefaultPlaceholder = (
component: Component,
type: 'input' | 'select',
componentProps: Recordable<any> = {},
) => {
@ -633,7 +633,9 @@ async function initComponentAdapter() {
modelValueProp: 'value',
}),
Input: withDefaultPlaceholder(Input, 'input'),
InputNumber: withDefaultPlaceholder(InputNumber, 'input'),
InputNumber: withDefaultPlaceholder(InputNumber, 'input', {
style: { width: '100%' },
}),
InputPassword: withDefaultPlaceholder(InputPassword, 'input'),
Mentions: withDefaultPlaceholder(Mentions, 'input'),
// 自定义主要按钮

View File

@ -141,8 +141,8 @@ const ElUpload = defineAsyncComponent(() =>
]).then(([res]) => res.ElUpload),
);
const withDefaultPlaceholder = <T extends Component>(
component: T,
const withDefaultPlaceholder = (
component: Component,
type: 'input' | 'select',
componentProps: Recordable<any> = {},
) => {
@ -284,7 +284,9 @@ async function initComponentAdapter() {
inputComponent: ElInput,
}),
Input: withDefaultPlaceholder(ElInput, 'input'),
InputNumber: withDefaultPlaceholder(ElInputNumber, 'input'),
InputNumber: withDefaultPlaceholder(ElInputNumber, 'input', {
style: { width: '100%' },
}),
RadioGroup: (props, { attrs, slots }) => {
let defaultSlot;
if (Reflect.has(slots, 'default')) {

View File

@ -84,8 +84,8 @@ const NUpload = defineAsyncComponent(() =>
import('naive-ui/es/upload').then((res) => res.NUpload),
);
const withDefaultPlaceholder = <T extends Component>(
component: T,
const withDefaultPlaceholder = (
component: Component,
type: 'input' | 'select',
componentProps: Recordable<any> = {},
) => {
@ -225,7 +225,9 @@ async function initComponentAdapter() {
inputComponent: NInput,
}),
Input: withDefaultPlaceholder(NInput, 'input'),
InputNumber: withDefaultPlaceholder(NInputNumber, 'input'),
InputNumber: withDefaultPlaceholder(NInputNumber, 'input', {
style: { width: '100%' },
}),
RadioGroup: (props, { attrs, slots }) => {
let defaultSlot;
if (Reflect.has(slots, 'default')) {

View File

@ -89,8 +89,8 @@ const TreeSelect = defineAsyncComponent(
);
const Upload = defineAsyncComponent(() => import('tdesign-vue-next/es/upload'));
const withDefaultPlaceholder = <T extends Component>(
component: T,
const withDefaultPlaceholder = (
component: Component,
type: 'input' | 'select',
componentProps: Recordable<any> = {},
) => {
@ -239,7 +239,9 @@ async function initComponentAdapter() {
modelValueProp: 'value',
}),
Input: withDefaultPlaceholder(Input, 'input'),
InputNumber: withDefaultPlaceholder(InputNumber, 'input'),
InputNumber: withDefaultPlaceholder(InputNumber, 'input', {
style: { width: '100%' },
}),
// InputPassword: withDefaultPlaceholder(InputPassword, 'input'),
// Mentions: withDefaultPlaceholder(Mentions, 'input'),
// 自定义主要按钮

View File

@ -0,0 +1,2 @@
import { register } from 'node:module';
register('./dayjs-resolve-hook.mjs', import.meta.url);

View File

@ -0,0 +1,10 @@
const DAYJS_SUBPATH_RE = /^dayjs\/(plugin|locale)\/([^./]+)$/;
/** @type {import('node:module').ResolveHook} */
export async function resolve(specifier, context, nextResolve) {
const match = specifier.match(DAYJS_SUBPATH_RE);
if (match) {
return nextResolve(`${specifier}.js`, context);
}
return nextResolve(specifier, context);
}

View File

@ -1,12 +1,12 @@
import { withPwa } from '@vite-pwa/vitepress';
import { defineConfigWithTheme } from 'vitepress';
import { defineConfig } from 'vitepress';
import { en } from './en.mts';
import { shared } from './shared.mts';
import { zh } from './zh.mts';
export default withPwa(
defineConfigWithTheme({
defineConfig({
...shared,
locales: {
en: {

View File

@ -5,6 +5,7 @@ import { resolve } from 'node:path';
import {
viteArchiverPlugin,
viteDayjsPlugin,
viteVxeTableImportsPlugin,
} from '@vben/vite-config';
@ -61,7 +62,7 @@ export const shared = defineConfig({
},
preprocessorOptions: {
scss: {
api: 'modern',
// api: 'modern',
},
},
},
@ -69,6 +70,7 @@ export const shared = defineConfig({
stringify: true,
},
plugins: [
viteDayjsPlugin(),
tailwindcss(),
GitChangelog({
mapAuthors: [
@ -85,9 +87,23 @@ export const shared = defineConfig({
name: 'Li Kui',
username: 'likui628',
},
{
mapByNameAliases: ['Jin Mao', 'jinmao'],
name: 'Jin Mao',
username: 'jinmao88',
},
{
name: 'Netfan',
username: 'mynetfan',
},
{
mapByNameAliases: ['xingyu4j', 'xingyu'],
name: 'xingyu4j',
username: 'xingyu4j',
},
],
repoURL: () => 'https://github.com/vbenjs/vue-vben-admin',
}),
}) as any,
GitChangelogMarkdownSection(),
viteArchiverPlugin({ outputDir: '.vitepress' }),
groupIconVitePlugin(),
@ -103,6 +119,7 @@ export const shared = defineConfig({
ssr: {
external: ['@vue/repl'],
noExternal: ['@v-c/picker'],
},
},
});
@ -113,7 +130,7 @@ function head(): HeadConfig[] {
[
'meta',
{
content: 'vben, vitejs, vite, shacdn-ui, vue',
content: 'vben, vitejs, vite, shadcn-ui, vue',
name: 'keywords',
},
],

View File

@ -337,8 +337,8 @@ export const search: DefaultTheme.AlgoliaSearchOptions['locales'] = {
footer: {
closeText: '关闭',
navigateText: '切换',
searchByText: '搜索提供者',
selectText: '选择',
poweredByText: '搜索提供者',
},
noResultsScreen: {
noResultsText: '无法找到相关结果',
@ -347,10 +347,10 @@ export const search: DefaultTheme.AlgoliaSearchOptions['locales'] = {
suggestedQueryText: '你可以尝试查询',
},
searchBox: {
cancelButtonAriaLabel: '取消',
cancelButtonText: '取消',
resetButtonAriaLabel: '清除查询条件',
resetButtonTitle: '清除查询条件',
closeButtonAriaLabel: '取消',
closeButtonText: '取消',
clearButtonAriaLabel: '清除查询条件',
clearButtonTitle: '清除查询条件',
},
startScreen: {
favoriteSearchesTitle: '收藏',

View File

@ -10,7 +10,7 @@ import {
// import { useAntdDesignTokens } from '@vben/hooks';
// import { initPreferences } from '@vben/preferences';
import { ConfigProvider, theme } from 'ant-design-vue';
import { ConfigProvider, theme } from 'antdv-next';
import mediumZoom from 'medium-zoom';
import { useRoute } from 'vitepress';
import DefaultTheme from 'vitepress/theme';

View File

@ -2,8 +2,9 @@
"name": "@vben/docs",
"version": "5.7.0",
"private": true,
"type": "module",
"scripts": {
"build": "vitepress build",
"build": "cross-env NODE_OPTIONS=\"--import ./.vitepress/build/dayjs-loader.mjs\" vitepress build",
"dev": "vitepress dev",
"docs:preview": "vitepress preview"
},
@ -19,7 +20,7 @@
"@vben/locales": "workspace:*",
"@vben/plugins": "workspace:*",
"@vben/styles": "workspace:*",
"ant-design-vue": "catalog:",
"antdv-next": "catalog:",
"lucide-vue-next": "catalog:",
"medium-zoom": "catalog:",
"reka-ui": "catalog:",

View File

@ -26,16 +26,15 @@ import {
notification,
Radio,
RadioGroup,
RangePicker,
Rate,
Select,
Space,
Switch,
Textarea,
TextArea,
TimePicker,
TreeSelect,
Upload,
} from 'ant-design-vue';
} from 'antdv-next';
const withDefaultPlaceholder = <T extends Component>(
component: T,
@ -98,12 +97,12 @@ async function initComponentAdapter() {
},
Radio,
RadioGroup,
RangePicker,
RangePicker: withDefaultPlaceholder(DatePicker.RangePicker, 'select'),
Rate,
Select: withDefaultPlaceholder(Select, 'select'),
Space,
Switch,
Textarea: withDefaultPlaceholder(Textarea, 'input'),
Textarea: withDefaultPlaceholder(TextArea, 'input'),
TimePicker,
TreeSelect: withDefaultPlaceholder(TreeSelect, 'select'),
Upload,
@ -118,7 +117,7 @@ async function initComponentAdapter() {
copyPreferencesSuccess: (title, content) => {
notification.success({
description: content,
message: title,
title,
placement: 'bottomRight',
});
},

View File

@ -2,7 +2,7 @@ import { h } from 'vue';
import { setupVbenVxeTable, useVbenVxeGrid } from '@vben/plugins/vxe-table';
import { Button, Image } from 'ant-design-vue';
import { Button, Image } from 'antdv-next';
import { useVbenForm } from './form';

View File

@ -22,7 +22,7 @@ outline: deep
<script lang="ts" setup>
import { ApiComponent } from '@vben/common-ui';
import { Cascader } from 'ant-design-vue';
import { Cascader } from 'antdv-next';
const treeData: Record<string, any> = [
{

View File

@ -117,7 +117,7 @@ import {
TimePicker,
TreeSelect,
Upload,
} from 'ant-design-vue';
} from 'antdv-next';
const withDefaultPlaceholder = <T extends Component>(
component: T,

View File

@ -25,7 +25,7 @@ import { h } from 'vue';
import { setupVbenVxeTable, useVbenVxeGrid } from '@vben/plugins/vxe-table';
import { Button, Image } from 'ant-design-vue';
import { Button, Image } from 'antdv-next';
import { useVbenForm } from './form';

View File

@ -3,7 +3,7 @@ import { h } from 'vue';
import { alert, VbenButton } from '@vben/common-ui';
import { Result } from 'ant-design-vue';
import { Result } from 'antdv-next';
function showAlert() {
alert('This is an alert message');

View File

@ -3,7 +3,7 @@ import { h, ref } from 'vue';
import { alert, confirm, VbenButton } from '@vben/common-ui';
import { Checkbox, message } from 'ant-design-vue';
import { Checkbox, message } from 'antdv-next';
function showConfirm() {
confirm('This is an alert message')

View File

@ -3,7 +3,7 @@ import { h } from 'vue';
import { alert, prompt, useAlertContext, VbenButton } from '@vben/common-ui';
import { Input, RadioGroup, Select } from 'ant-design-vue';
import { Input, RadioGroup, Select } from 'antdv-next';
import { BadgeJapaneseYen } from 'lucide-vue-next';
function showPrompt() {

View File

@ -1,7 +1,7 @@
<script lang="ts" setup>
import { ApiComponent } from '@vben/common-ui';
import { Cascader } from 'ant-design-vue';
import { Cascader } from 'antdv-next';
const treeData: Record<string, any> = [
{

View File

@ -1,5 +1,5 @@
<script lang="ts" setup>
import { Button, message, Space } from 'ant-design-vue';
import { Button, message, Space } from 'antdv-next';
import { useVbenForm } from '#/adapter/form';

View File

@ -1,5 +1,5 @@
<script lang="ts" setup>
import { message } from 'ant-design-vue';
import { message } from 'antdv-next';
import { useVbenForm } from '#/adapter/form';

View File

@ -1,7 +1,7 @@
<script lang="ts" setup>
import { h } from 'vue';
import { Input, message } from 'ant-design-vue';
import { Input, message } from 'antdv-next';
import { useVbenForm } from '#/adapter/form';

View File

@ -1,5 +1,5 @@
<script lang="ts" setup>
import { message } from 'ant-design-vue';
import { message } from 'antdv-next';
import { useVbenForm } from '#/adapter/form';

View File

@ -1,5 +1,5 @@
<script lang="ts" setup>
import { message } from 'ant-design-vue';
import { message } from 'antdv-next';
import { useVbenForm } from '#/adapter/form';

View File

@ -1,5 +1,5 @@
<script lang="ts" setup>
import { message } from 'ant-design-vue';
import { message } from 'antdv-next';
import { useVbenForm, z } from '#/adapter/form';

View File

@ -1,7 +1,7 @@
<script lang="ts" setup>
import { computed, nextTick, onMounted, ref, watch } from 'vue';
import { Button, Card, message, Space, Tag } from 'ant-design-vue';
import { Button, Card, message, Space, Tag } from 'antdv-next';
import { useVbenForm } from '#/adapter/form';

View File

@ -1,7 +1,7 @@
<script lang="ts" setup>
import type { VxeGridListeners, VxeGridProps } from '#/adapter/vxe-table';
import { Button, message } from 'ant-design-vue';
import { Button, message } from 'antdv-next';
import { useVbenVxeGrid } from '#/adapter/vxe-table';

View File

@ -1,7 +1,7 @@
<script lang="ts" setup>
import type { VxeGridProps } from '#/adapter/vxe-table';
import { Button, Image, Switch, Tag } from 'ant-design-vue';
import { Button, Image, Switch, Tag } from 'antdv-next';
import { useVbenVxeGrid } from '#/adapter/vxe-table';

View File

@ -1,7 +1,7 @@
<script lang="ts" setup>
import type { VxeGridProps } from '#/adapter/vxe-table';
import { Button, message } from 'ant-design-vue';
import { Button, message } from 'antdv-next';
import { useVbenVxeGrid } from '#/adapter/vxe-table';

View File

@ -1,7 +1,7 @@
<script lang="ts" setup>
import type { VxeGridProps } from '#/adapter/vxe-table';
import { Button } from 'ant-design-vue';
import { Button } from 'antdv-next';
import { useVbenVxeGrid } from '#/adapter/vxe-table';

View File

@ -2,7 +2,7 @@
import type { VbenFormProps } from '#/adapter/form';
import type { VxeGridProps } from '#/adapter/vxe-table';
import { message } from 'ant-design-vue';
import { message } from 'antdv-next';
import { useVbenVxeGrid } from '#/adapter/vxe-table';

View File

@ -3,7 +3,7 @@ import type { DemoTableApi } from '../mock-api';
import type { VxeGridProps } from '#/adapter/vxe-table';
import { Button } from 'ant-design-vue';
import { Button } from 'antdv-next';
import { useVbenVxeGrid } from '#/adapter/vxe-table';

View File

@ -1,7 +1,7 @@
<script lang="ts" setup>
import type { VxeGridProps } from '#/adapter/vxe-table';
import { Button } from 'ant-design-vue';
import { Button } from 'antdv-next';
import { useVbenVxeGrid } from '#/adapter/vxe-table';

View File

@ -20,7 +20,7 @@ The current wrapper flow is:
<script lang="ts" setup>
import { ApiComponent } from '@vben/common-ui';
import { Cascader } from 'ant-design-vue';
import { Cascader } from 'antdv-next';
function fetchApi() {
return Promise.resolve([

View File

@ -103,7 +103,7 @@ import {
TimePicker,
TreeSelect,
Upload,
} from 'ant-design-vue';
} from 'antdv-next';
const withDefaultPlaceholder = <T extends Component>(
component: T,

View File

@ -1,6 +1,6 @@
# External Modules
In addition to the external modules that are included by default in the project, sometimes we need to import other external modules. Let's take [ant-design-vue](https://antdv.com/components/overview) as an example:
In addition to the external modules that are included by default in the project, sometimes we need to import other external modules. Let's take [antdv-next](https://antdv.com/components/overview) as an example:
## Installing Dependencies
@ -13,7 +13,7 @@ In addition to the external modules that are included by default in the project,
```bash
# cd /path/to/your/package
pnpm add ant-design-vue
pnpm add antdv-next
```
## Usage
@ -22,9 +22,9 @@ pnpm add ant-design-vue
```ts
import { createApp } from 'vue';
import Antd from 'ant-design-vue';
import Antd from 'antdv-next';
import App from './App';
import 'ant-design-vue/dist/reset.css';
import 'antdv-next/dist/reset.css';
const app = createApp(App);
@ -43,7 +43,7 @@ app.use(Antd).mount('#app');
```vue
<script setup lang="ts">
import { Button } from 'ant-design-vue';
import { Button } from 'antdv-next';
</script>
<template>

View File

@ -174,7 +174,7 @@ import {
} from '@vben/request';
import { useAccessStore } from '@vben/stores';
import { message } from 'ant-design-vue';
import { message } from 'antdv-next';
import { useAuthStore } from '#/store';

View File

@ -110,7 +110,7 @@ registry = https://registry.npmmirror.com/
If you encounter errors similar to the following, please check that the full project path (including all parent paths) does not contain Chinese, Japanese, or Korean characters. Otherwise, you will encounter a 404 error for the path, leading to the following issue:
```ts
[vite] Failed to resolve module import "ant-design-vue/dist/antd.css-vben-adminode_modulesant-design-vuedistantd.css". (imported by /@/setup/ant-design-vue/index.ts)
[vite] Failed to resolve module import "antdv-next/dist/antd.css-vben-adminode_modulesantdv-nextdistantd.css". (imported by /@/setup/antdv-next/index.ts)
```
## Console Route Warning Issue

View File

@ -1,6 +1,6 @@
# 外部模块
除了项目默认引入的外部模块,有时我们还需要引入其他外部模块。我们以 [ant-design-vue](https://antdv.com/components/overview) 为例:
除了项目默认引入的外部模块,有时我们还需要引入其他外部模块。我们以 [antdv-next](https://antdv.com/components/overview) 为例:
## 安装依赖
@ -13,7 +13,7 @@
```bash
# cd /path/to/your/package
pnpm add ant-design-vue
pnpm add antdv-next
```
## 使用
@ -22,9 +22,9 @@ pnpm add ant-design-vue
```ts
import { createApp } from 'vue';
import Antd from 'ant-design-vue';
import Antd from 'antdv-next';
import App from './App';
import 'ant-design-vue/dist/reset.css';
import 'antdv-next/dist/reset.css';
const app = createApp(App);
@ -43,7 +43,7 @@ app.use(Antd).mount('#app');
```vue
<script setup lang="ts">
import { Button } from 'ant-design-vue';
import { Button } from 'antdv-next';
</script>
<template>

View File

@ -204,7 +204,7 @@ import {
} from '@vben/request';
import { useAccessStore } from '@vben/stores';
import { message } from 'ant-design-vue';
import { message } from 'antdv-next';
import { useAuthStore } from '#/store';

View File

@ -60,7 +60,7 @@ pnpm add version-polling
```ts
import { h } from 'vue';
import { Button, notification } from 'ant-design-vue';
import { Button, notification } from 'antdv-next';
import { createVersionPolling } from 'version-polling';
createVersionPolling({

View File

@ -110,7 +110,7 @@ registry = https://registry.npmmirror.com/
如果出现类似以下错误,请检查项目全路径(包含所有父级路径)不能出现中文、日文、韩文。否则将会出现路径访问 404 导致以下问题
```ts
[vite] Failed to resolve module import "ant-design-vue/dist/antd.css-vben-adminode_modulesant-design-vuedistantd.css". (imported by /@/setup/ant-design-vue/index.ts)
[vite] Failed to resolve module import "antdv-next/dist/antd.css-vben-adminode_modulesantdv-nextdistantd.css". (imported by /@/setup/antdv-next/index.ts)
```
## 控制台路由警告问题

View File

@ -8,6 +8,7 @@ const comments: OxlintConfig = {
},
],
rules: {
'eslint/no-underscore-dangle': 'off',
'eslint-comments/no-aggregating-enable': 'error',
'eslint-comments/no-duplicate-disable': 'error',
'eslint-comments/no-unlimited-disable': 'error',

View File

@ -0,0 +1,70 @@
import type { Plugin } from 'vite';
function viteDayjsPlugin(): Plugin {
return {
name: 'vite-dayjs-plugin',
enforce: 'pre',
async resolveId(source, importer, options) {
// 1) 已经使用了 dayjs/esm 的不处理
if (source.startsWith('dayjs/esm')) return null;
// 2) 根入口dayjs -> dayjs/esm
if (source === 'dayjs') {
return await this.resolve('dayjs/esm', importer, {
skipSelf: true,
...options,
});
}
// 3) 插件入口的多种写法
// - dayjs/plugin/xxx.js -> dayjs/esm/plugin/xxx/index.js
// - dayjs/plugin/xxx -> dayjs/esm/plugin/xxx
const pluginWithJs = source.match(/^dayjs\/plugin\/([^/]+)\.js$/);
if (pluginWithJs) {
const target = `dayjs/esm/plugin/${pluginWithJs[1]}/index.js`;
return await this.resolve(target, importer, {
skipSelf: true,
...options,
});
}
const pluginBare = source.match(/^dayjs\/plugin\/([^/]+)$/);
if (pluginBare) {
const target = `dayjs/esm/plugin/${pluginBare[1]}`;
return await this.resolve(target, importer, {
skipSelf: true,
...options,
});
}
// 4) 处理多语言包
// - dayjs/locale/xxx.js -> dayjs/esm/locale/xxx.js
const localeWithJs = source.match(/^dayjs\/locale\/([^/]+)\.js$/);
if (localeWithJs) {
const target = `dayjs/esm/locale/${localeWithJs[1]}.js`;
return await this.resolve(target, importer, {
skipSelf: true,
...options,
});
}
const localeBare = source.match(/^dayjs\/locale\/([^/]+)$/);
if (localeBare) {
const target = `dayjs/esm/locale/${localeBare[1]}`;
return await this.resolve(target, importer, {
skipSelf: true,
...options,
});
}
return null;
},
config() {
return {
optimizeDeps: {
exclude: ['dayjs'],
},
};
},
};
}
export { viteDayjsPlugin };

View File

@ -18,6 +18,7 @@ import { VitePWA } from 'vite-plugin-pwa';
import viteVueDevTools from 'vite-plugin-vue-devtools';
import { viteArchiverPlugin } from './archiver';
import { viteDayjsPlugin } from './dayjs';
import { viteExtraAppConfigPlugin } from './extra-app-config';
import { viteHtmlPlugin } from './html';
import { viteImportMapPlugin } from './importmap';
@ -105,6 +106,7 @@ async function loadApplicationPlugins(
compressTypes,
extraAppConfig,
html,
dayjs,
i18n,
importmap,
importmapOptions,
@ -219,6 +221,10 @@ async function loadApplicationPlugins(
return [await viteArchiverPlugin(archiverPluginOptions)];
},
},
{
condition: dayjs,
plugins: () => [viteDayjsPlugin()],
},
]);
}
@ -247,6 +253,7 @@ export {
loadLibraryPlugins,
viteArchiverPlugin,
viteCompressPlugin,
viteDayjsPlugin,
viteDtsPlugin,
viteHtmlPlugin,
viteVisualizerPlugin,

View File

@ -214,6 +214,11 @@ interface ApplicationPluginOptions extends CommonPluginOptions {
* @description
*/
compressTypes?: ('brotli' | 'gzip')[];
/**
* dayjs
* @default true
*/
dayjs?: boolean;
/**
*
* @default false

View File

@ -102,8 +102,8 @@
"vue-tsc": "catalog:"
},
"engines": {
"node": "^20.19.0 || ^22.18.0 || ^24.0.0",
"node": "^22.18.0 || ^24.0.0",
"pnpm": ">=10.0.0"
},
"packageManager": "pnpm@10.33.0"
"packageManager": "pnpm@10.33.4"
}

View File

@ -87,7 +87,7 @@ class IndexedDBDriver implements IStorageDriver {
});
}
async setItem<T>(key: string, value: T): Promise<void> {
async setItem(key: string, value: unknown): Promise<void> {
const db = await this.getDB();
return new Promise((resolve, reject) => {
const tx = db.transaction(this.storeName, 'readwrite');

View File

@ -62,7 +62,7 @@ class LocalStorageDriver implements IStorageDriver {
this.storage.removeItem(key);
}
async setItem<T>(key: string, value: T): Promise<void> {
async setItem(key: string, value: unknown): Promise<void> {
this.storage.setItem(key, JSON.stringify(value));
}
}

View File

@ -24,7 +24,7 @@ class MemoryStorageDriver implements IStorageDriver {
this.store.delete(key);
}
async setItem<T>(key: string, value: T): Promise<void> {
async setItem(key: string, value: unknown): Promise<void> {
this.store.set(key, value);
}
}

View File

@ -106,10 +106,10 @@ class StorageManager {
* @param value
* @param ttl
*/
async setItem<T>(key: string, value: T, ttl?: number): Promise<void> {
async setItem(key: string, value: unknown, ttl?: number): Promise<void> {
const fullKey = this.getFullKey(key);
const expiry = ttl ? Date.now() + ttl : undefined;
const item: StorageItem<T> = { expiry, value };
const item: StorageItem<unknown> = { expiry, value };
await this.driver.setItem(fullKey, item);
}

View File

@ -17,7 +17,7 @@ interface IStorageDriver {
removeItem(key: string): Promise<void>;
/** 设置存储项 */
setItem<T>(key: string, value: T): Promise<void>;
setItem(key: string, value: unknown): Promise<void>;
}
/**

View File

@ -34,7 +34,9 @@ describe('stateHandler', () => {
}, 10);
// 等待过程中,期望 Promise 被 reject
await expect(handler.waitForCondition()).rejects.toThrow();
await expect(handler.waitForCondition()).rejects.toThrow(
'Condition was set to false',
);
expect(handler.isConditionTrue()).toBe(false);
});

View File

@ -138,8 +138,10 @@ describe('getNestedValue', () => {
expect(result).toBe(2);
});
it('should return the entire object if path is empty', () => {
expect(() => getNestedValue(data, '')()).toThrow();
it('should throw if path is empty', () => {
expect(() => getNestedValue(data, '')).toThrow(
'Path must be a non-empty string',
);
});
it('should handle paths with array indexes', () => {

View File

@ -1,6 +1,6 @@
export class StateHandler {
private condition: boolean = false;
private rejectCondition: (() => void) | null = null;
private rejectCondition: ((reason?: Error) => void) | null = null;
private resolveCondition: (() => void) | null = null;
isConditionTrue(): boolean {
@ -16,7 +16,7 @@ export class StateHandler {
setConditionFalse() {
this.condition = false;
if (this.rejectCondition) {
this.rejectCondition();
this.rejectCondition(new Error('Condition was set to false'));
this.clearPromises();
}
}

View File

@ -180,11 +180,7 @@ class PreferenceManager {
*
* @param updates -
*/
updateCustomPreferences = <
TCustomPreferences extends object = CustomPreferencesRecord,
>(
updates: DeepPartial<TCustomPreferences>,
) => {
updateCustomPreferences = (updates: DeepPartial<object>) => {
if (!this.customPreferencesExtension) {
return;
}

View File

@ -66,6 +66,7 @@ function findPlaceholderPos(doc: ProseMirrorNode, blobUrl: string): number {
found = offset;
return false;
}
return true;
});
return found;
}

View File

@ -1,3 +1,4 @@
/* eslint-disable unicorn/no-nested-ternary */
import type { VxeGridProps as VxeTableGridProps } from 'vxe-table';
import type {
@ -182,13 +183,11 @@ export function useViewedRow<T = any>(
options: ViewedRowOptions<T> & { keyField: string },
) {
// ========== 解析持久化配置 ==========
let persistOpts: null | ViewedRowPersistOptions = null;
if (options.persist) {
persistOpts =
typeof options.persist === 'string'
? { key: options.persist, type: 'localStorage' }
: options.persist;
}
const persistOpts: null | ViewedRowPersistOptions = options.persist
? typeof options.persist === 'string'
? { key: options.persist, type: 'localStorage' }
: options.persist
: null;
const adapter = createStorageAdapter(options.persist);
const maxSize = persistOpts?.maxSize ?? 100;
@ -521,12 +520,12 @@ export function applyViewedRowOptions(
};
// 拦截 CellOperation columns
let actionCodes: string[] = [];
if (!isBoolean(viewedRowConfig) && viewedRowConfig.actionCodes) {
actionCodes = Array.isArray(viewedRowConfig.actionCodes)
? viewedRowConfig.actionCodes
: [viewedRowConfig.actionCodes];
}
const actionCodes =
!isBoolean(viewedRowConfig) && viewedRowConfig.actionCodes
? Array.isArray(viewedRowConfig.actionCodes)
? viewedRowConfig.actionCodes
: [viewedRowConfig.actionCodes]
: [];
if (actionCodes.length > 0 && Array.isArray(mergedOptions.columns)) {
mergedOptions.columns = wrapColumnsForViewedRow(

View File

@ -50,24 +50,18 @@ describe('requestClient', () => {
it('should handle network errors', async () => {
mock.onGet('/test/error').networkError();
try {
await requestClient.get('/test/error');
expect(true).toBe(false);
} catch (error: any) {
expect(error.isAxiosError).toBe(true);
expect(error.message).toBe('Network Error');
}
await expect(requestClient.get('/test/error')).rejects.toMatchObject({
isAxiosError: true,
message: 'Network Error',
});
});
it('should handle timeout', async () => {
mock.onGet('/test/timeout').timeout();
try {
await requestClient.get('/test/timeout');
expect(true).toBe(false);
} catch (error: any) {
expect(error.isAxiosError).toBe(true);
expect(error.code).toBe('ECONNABORTED');
}
await expect(requestClient.get('/test/timeout')).rejects.toMatchObject({
isAxiosError: true,
code: 'ECONNABORTED',
});
});
it('should successfully upload a file', async () => {
@ -92,7 +86,7 @@ describe('requestClient', () => {
mock.onGet('/test/download').reply(200, mockFileContent);
const res = await requestClient.download('/test/download');
const res = await requestClient.download<any>('/test/download');
expect(res.data).toBeInstanceOf(Blob);
});

View File

@ -48,7 +48,7 @@
"@vben/types": "workspace:*",
"@vben/utils": "workspace:*",
"@vueuse/core": "catalog:",
"ant-design-vue": "catalog:",
"antdv-next": "catalog:",
"dayjs": "catalog:",
"json-bigint": "catalog:",
"pinia": "catalog:",

View File

@ -18,6 +18,7 @@ import type {
MentionsProps,
RadioGroupProps,
RadioProps,
RangePickerProps,
RateProps,
SelectProps,
SpaceProps,
@ -28,8 +29,7 @@ import type {
UploadChangeParam,
UploadFile,
UploadProps,
} from 'ant-design-vue';
import type { RangePickerProps } from 'ant-design-vue/es/date-picker';
} from 'antdv-next';
import type { Component, Ref } from 'vue';
@ -72,7 +72,7 @@ import { isEmpty } from '@vben/utils';
import { VbenCollapsibleParams } from '@vben-core/shadcn-ui';
import { message, Modal, notification } from 'ant-design-vue';
import { message, Modal, notification } from 'antdv-next';
import { upload_file } from '#/api/examples/upload';
type AdapterUploadProps = UploadProps & {
@ -86,60 +86,72 @@ type AdapterUploadProps = UploadProps & {
};
const AutoComplete = defineAsyncComponent(
() => import('ant-design-vue/es/auto-complete'),
() => import('antdv-next/dist/auto-complete/index'),
);
const Button = defineAsyncComponent(
() => import('antdv-next/dist/button/index'),
);
const Button = defineAsyncComponent(() => import('ant-design-vue/es/button'));
const Checkbox = defineAsyncComponent(
() => import('ant-design-vue/es/checkbox'),
() => import('antdv-next/dist/checkbox/index'),
);
const CheckboxGroup = defineAsyncComponent(() =>
import('ant-design-vue/es/checkbox').then((res) => res.CheckboxGroup),
import('antdv-next/dist/checkbox/index').then((res) => res.CheckboxGroup),
);
const DatePicker = defineAsyncComponent(
() => import('ant-design-vue/es/date-picker'),
() => import('antdv-next/dist/date-picker/index'),
);
const Divider = defineAsyncComponent(() => import('ant-design-vue/es/divider'));
const Input = defineAsyncComponent(() => import('ant-design-vue/es/input'));
const Divider = defineAsyncComponent(
() => import('antdv-next/dist/divider/index'),
);
const Input = defineAsyncComponent(() => import('antdv-next/dist/input/index'));
const InputNumber = defineAsyncComponent(
() => import('ant-design-vue/es/input-number'),
() => import('antdv-next/dist/input-number/index'),
);
const InputPassword = defineAsyncComponent(() =>
import('ant-design-vue/es/input').then((res) => res.InputPassword),
import('antdv-next/dist/input/index').then((res) => res.InputPassword),
);
const Mentions = defineAsyncComponent(
() => import('ant-design-vue/es/mentions'),
() => import('antdv-next/dist/mentions/index'),
);
const Radio = defineAsyncComponent(() => import('ant-design-vue/es/radio'));
const Radio = defineAsyncComponent(() => import('antdv-next/dist/radio/index'));
const RadioGroup = defineAsyncComponent(() =>
import('ant-design-vue/es/radio').then((res) => res.RadioGroup),
import('antdv-next/dist/radio/index').then((res) => res.RadioGroup),
);
const RangePicker = defineAsyncComponent(() =>
import('ant-design-vue/es/date-picker').then((res) => res.RangePicker),
import('antdv-next/dist/date-picker/index').then(
(res) => res.DateRangePicker,
),
);
const Rate = defineAsyncComponent(() => import('ant-design-vue/es/rate'));
const Select = defineAsyncComponent(() => import('ant-design-vue/es/select'));
const Space = defineAsyncComponent(() => import('ant-design-vue/es/space'));
const Switch = defineAsyncComponent(() => import('ant-design-vue/es/switch'));
const Textarea = defineAsyncComponent(() =>
import('ant-design-vue/es/input').then((res) => res.Textarea),
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('ant-design-vue/es/time-picker'),
() => import('antdv-next/dist/time-picker/index'),
);
const TreeSelect = defineAsyncComponent(
() => import('ant-design-vue/es/tree-select'),
() => import('antdv-next/dist/tree-select/index'),
);
const Cascader = defineAsyncComponent(
() => import('ant-design-vue/es/cascader'),
() => import('antdv-next/dist/cascader/index'),
);
const Upload = defineAsyncComponent(() => import('ant-design-vue/es/upload'));
const Image = defineAsyncComponent(() => import('ant-design-vue/es/image'));
const Upload = defineAsyncComponent(
() => import('antdv-next/dist/upload/index'),
);
const Image = defineAsyncComponent(() => import('antdv-next/dist/image/index'));
const PreviewGroup = defineAsyncComponent(() =>
import('ant-design-vue/es/image').then((res) => res.ImagePreviewGroup),
import('antdv-next/dist/image/index').then((res) => res.ImagePreviewGroup),
);
const withDefaultPlaceholder = <T extends Component>(
component: T,
const withDefaultPlaceholder = (
component: Component,
type: 'input' | 'select',
componentProps: Recordable<any> = {},
) => {
@ -242,7 +254,7 @@ function getBase64(file: File): Promise<string> {
*/
async function previewImage(
file: UploadFile,
visible: Ref<boolean>,
open: Ref<boolean>,
fileList: Ref<UploadProps['fileList']>,
) {
// 非图片文件直接打开链接
@ -250,6 +262,8 @@ async function previewImage(
const url = file.url || file.preview;
if (url) {
window.open(url, '_blank');
} else if (file.preview) {
window.open(file.preview, '_blank');
} else {
message.error($t('ui.formRules.previewWarning'));
}
@ -285,10 +299,10 @@ async function previewImage(
{
class: 'hidden',
preview: {
visible: visible.value,
open: open.value,
current: currentIndex,
onVisibleChange: (value: boolean) => {
visible.value = value;
onOpenChange: (value: boolean) => {
open.value = value;
if (!value) {
setTimeout(() => {
if (!isUnmounted && container) {
@ -330,7 +344,7 @@ function cropImage(file: File, aspectRatio: string | undefined) {
const open = ref<boolean>(true);
const cropperRef = ref<InstanceType<typeof VCropper> | null>(null);
const closeModal = () => {
function closeModal() {
open.value = false;
setTimeout(() => {
if (!isUnmounted && container) {
@ -342,7 +356,7 @@ function cropImage(file: File, aspectRatio: string | undefined) {
container.remove();
}
}, 300);
};
}
const CropperWrapper = {
setup() {
@ -372,7 +386,7 @@ function cropImage(file: File, aspectRatio: string | undefined) {
closable: false,
cancelText: $t('common.cancel'),
okText: $t('ui.crop.confirm'),
destroyOnClose: true,
destroyOnHidden: true,
onOk: async () => {
const cropper = cropperRef.value;
if (!cropper) {
@ -416,7 +430,7 @@ function cropImage(file: File, aspectRatio: string | undefined) {
/**
*
*/
const withPreviewUpload = () => {
function withPreviewUpload() {
return defineComponent({
name: Upload.name,
emits: ['update:modelValue'],
@ -436,10 +450,10 @@ const withPreviewUpload = () => {
() => attrs?.aspectRatio ?? attrs?.['aspect-ratio'],
);
const handleBeforeUpload = async (
async function handleBeforeUpload(
file: UploadFile,
originFileList: Array<File>,
) => {
) {
// 文件大小限制
if (maxSize.value && (file.size || 0) / 1024 / 1024 > maxSize.value) {
message.error($t('ui.formRules.sizeLimit', [maxSize.value]));
@ -463,9 +477,9 @@ const withPreviewUpload = () => {
}
return attrs.beforeUpload?.(file) ?? true;
};
}
const handleChange = (event: UploadChangeParam) => {
function handleChange(event: UploadChangeParam) {
try {
attrs.handleChange?.(event);
attrs.onHandleChange?.(event);
@ -479,19 +493,19 @@ const withPreviewUpload = () => {
'update:modelValue',
event.fileList?.length ? fileList.value : undefined,
);
};
}
const handlePreview = async (file: UploadFile) => {
function handlePreview(file: UploadFile) {
previewVisible.value = true;
await previewImage(file, previewVisible, fileList);
};
return previewImage(file, previewVisible, fileList);
}
const renderUploadButton = () => {
function renderUploadButton() {
if (attrs.disabled) return null;
return isEmpty(slots)
? createDefaultUploadSlots(listType, placeholder)
: slots;
};
}
// 拖拽排序
const draggable = computed(
@ -600,7 +614,7 @@ const withPreviewUpload = () => {
);
},
});
};
}
// 这里需要自行根据业务组件库进行适配,需要用到的组件都需要在这里类型说明
export type ComponentType =
@ -681,13 +695,13 @@ async function initComponentAdapter() {
fieldNames: { label: 'label', value: 'value', children: 'children' },
loadingSlot: 'suffixIcon',
modelPropName: 'value',
visibleEvent: 'onVisibleChange',
visibleEvent: 'onOpenChange',
}),
ApiSelect: withDefaultPlaceholder(ApiComponent, 'select', {
component: Select,
loadingSlot: 'suffixIcon',
modelPropName: 'value',
visibleEvent: 'onVisibleChange',
visibleEvent: 'onOpenChange',
}),
ApiTreeSelect: withDefaultPlaceholder(ApiComponent, 'select', {
component: TreeSelect,
@ -695,7 +709,7 @@ async function initComponentAdapter() {
loadingSlot: 'suffixIcon',
modelPropName: 'value',
optionsPropName: 'treeData',
visibleEvent: 'onVisibleChange',
visibleEvent: 'onOpenChange',
}),
AutoComplete,
Cascader,
@ -713,7 +727,9 @@ async function initComponentAdapter() {
modelValueProp: 'value',
}),
Input: withDefaultPlaceholder(Input, 'input'),
InputNumber: withDefaultPlaceholder(InputNumber, 'input'),
InputNumber: withDefaultPlaceholder(InputNumber, 'input', {
style: { width: '100%' },
}),
InputPassword: withDefaultPlaceholder(InputPassword, 'input'),
Mentions: withDefaultPlaceholder(Mentions, 'input'),
// 自定义主要按钮
@ -764,7 +780,7 @@ async function initComponentAdapter() {
copyPreferencesSuccess: (title, content) => {
notification.success({
description: content,
message: title,
title,
placement: 'bottomRight',
});
},

View File

@ -14,7 +14,7 @@ import {
import { get, isFunction, isString } from '@vben/utils';
import { objectOmit } from '@vueuse/core';
import { Button, Image, Popconfirm, Switch, Tag } from 'ant-design-vue';
import { Button, Image, Popconfirm, Switch, Tag } from 'antdv-next';
import { $t } from '#/locales';

View File

@ -14,7 +14,7 @@ import {
import { useAccessStore } from '@vben/stores';
import { cloneDeep } from '@vben/utils';
import { message } from 'ant-design-vue';
import { message } from 'antdv-next';
import JSONBigInt from 'json-bigint';
import { useAuthStore } from '#/store';

View File

@ -1,3 +1,4 @@
export * from './dept';
export * from './menu';
export * from './role';
export * from './user';

View File

@ -0,0 +1,55 @@
import type { Recordable } from '@vben/types';
import { requestClient } from '#/api/request';
export namespace SystemUserApi {
export interface SystemUser {
[key: string]: any;
id: string;
name: string;
permissions: string[];
remark?: string;
status: 0 | 1;
}
}
/**
*
*/
async function getUserList(params: Recordable<any>) {
return requestClient.get<Array<SystemUserApi.SystemUser>>(
'/system/user/list',
{ params },
);
}
/**
*
* @param data
*/
async function createUser(data: Omit<SystemUserApi.SystemUser, 'id'>) {
return requestClient.post('/system/user', data);
}
/**
*
*
* @param id ID
* @param data
*/
async function updateUser(
id: string,
data: Omit<SystemUserApi.SystemUser, 'id'>,
) {
return requestClient.put(`/system/user/${id}`, data);
}
/**
*
* @param id ID
*/
async function deleteUser(id: string) {
return requestClient.delete(`/system/user/${id}`);
}
export { createUser, deleteUser, getUserList, updateUser };

View File

@ -1,10 +1,10 @@
<script lang="ts" setup>
import { computed } from 'vue';
import { computed, watch } from 'vue';
import { useAntdDesignTokens } from '@vben/hooks';
import { preferences, usePreferences } from '@vben/preferences';
import { App, ConfigProvider, theme } from 'ant-design-vue';
import { App, ConfigProvider, theme } from 'antdv-next';
import { antdLocale } from '#/locales';
@ -28,6 +28,14 @@ const tokenTheme = computed(() => {
token: tokens,
};
});
watch(
tokenTheme,
(themeConfig) => {
ConfigProvider.config({ theme: themeConfig });
},
{ immediate: true },
);
</script>
<template>

View File

@ -6,7 +6,7 @@ import { providePluginsOptions } from '@vben/plugins';
import { preferences } from '@vben/preferences';
import { initStores } from '@vben/stores';
import '@vben/styles';
import '@vben/styles/antd';
import '@vben/styles/antdv-next';
import { useTitle } from '@vueuse/core';

View File

@ -1,4 +1,4 @@
import type { Locale } from 'ant-design-vue/es/locale';
import type { Locale } from 'antdv-next/dist/locale/index';
import type { App } from 'vue';
@ -13,8 +13,8 @@ import {
} from '@vben/locales';
import { preferences } from '@vben/preferences';
import antdEnLocale from 'ant-design-vue/es/locale/en_US';
import antdDefaultLocale from 'ant-design-vue/es/locale/zh_CN';
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<Locale>(antdDefaultLocale);

View File

@ -1,5 +1,18 @@
{
"title": "System Management",
"user": {
"title": "User Management",
"list": "User List",
"name": "User",
"userName": "User Name",
"id": "User ID",
"dept": "Department",
"status": "Status",
"remark": "Remark",
"createTime": "Creation Time",
"operation": "Operation",
"placeholder": "Search Department..."
},
"dept": {
"name": "Department",
"title": "Department Management",

View File

@ -1,5 +1,18 @@
{
"title": "系统管理",
"user": {
"title": "用户管理",
"list": "用户列表",
"name": "用户名",
"userName": "用户名称",
"id": "用户ID",
"dept": "部门",
"status": "状态",
"remark": "备注",
"createTime": "创建时间",
"operation": "操作",
"placeholder": "搜索部门..."
},
"dept": {
"list": "部门列表",
"createTime": "创建时间",

View File

@ -29,4 +29,4 @@ async function initApplication() {
unmountGlobalLoading();
}
initApplication();
void initApplication();

View File

@ -6,7 +6,7 @@ import type {
import { generateAccessible } from '@vben/access';
import { preferences } from '@vben/preferences';
import { message } from 'ant-design-vue';
import { message } from 'antdv-next';
import { getAllMenusApi } from '#/api';
import { BasicLayout, IFrameView } from '#/layouts';

View File

@ -12,6 +12,15 @@ const routes: RouteRecordRaw[] = [
name: 'System',
path: '/system',
children: [
{
path: '/system/user',
name: 'SystemUser',
meta: {
icon: 'mdi:user',
title: $t('system.user.title'),
},
component: () => import('#/views/system/user/list.vue'),
},
{
path: '/system/role',
name: 'SystemRole',

View File

@ -7,7 +7,7 @@ import { LOGIN_PATH } from '@vben/constants';
import { preferences } from '@vben/preferences';
import { resetAllStores, useAccessStore, useUserStore } from '@vben/stores';
import { notification } from 'ant-design-vue';
import { notification } from 'antdv-next';
import { defineStore } from 'pinia';
import { getAccessCodesApi, getUserInfoApi, loginApi, logoutApi } from '#/api';
@ -65,7 +65,7 @@ export const useAuthStore = defineStore('auth', () => {
notification.success({
description: `${$t('authentication.loginSuccessDesc')}:${userInfo?.realName}`,
duration: 3,
message: $t('authentication.loginSuccess'),
title: $t('authentication.loginSuccess'),
});
}
}

View File

@ -7,7 +7,7 @@ import { computed, ref, useTemplateRef } from 'vue';
import { AuthenticationCodeLogin, z } from '@vben/common-ui';
import { $t } from '@vben/locales';
import { message } from 'ant-design-vue';
import { message } from 'antdv-next';
defineOptions({ name: 'CodeLogin' });

View File

@ -5,7 +5,7 @@ import { computed } from 'vue';
import { ProfilePasswordSetting, z } from '@vben/common-ui';
import { message } from 'ant-design-vue';
import { message } from 'antdv-next';
const formSchema = computed((): VbenFormSchema[] => {
return [

View File

@ -7,7 +7,7 @@ import { AccessControl, useAccess } from '@vben/access';
import { Page } from '@vben/common-ui';
import { resetAllStores, useUserStore } from '@vben/stores';
import { Button, Card } from 'ant-design-vue';
import { Button, Card } from 'antdv-next';
import { useAuthStore } from '#/store';

View File

@ -7,7 +7,7 @@ import { useAccess } from '@vben/access';
import { Page } from '@vben/common-ui';
import { resetAllStores, useUserStore } from '@vben/stores';
import { Button, Card } from 'ant-design-vue';
import { Button, Card } from 'antdv-next';
import { useAuthStore } from '#/store';

View File

@ -7,7 +7,7 @@ import { useAccessStore } from '@vben/stores';
import { MenuBadge } from '@vben-core/menu-ui';
import { Button, Card, Radio, RadioGroup } from 'ant-design-vue';
import { Button, Card, Radio, RadioGroup } from 'antdv-next';
import { useVbenForm } from '#/adapter/form';

View File

@ -3,7 +3,7 @@ import { useRouter } from 'vue-router';
import { Fallback } from '@vben/common-ui';
import { Button } from 'ant-design-vue';
import { Button } from 'antdv-next';
const router = useRouter();
</script>

View File

@ -3,7 +3,7 @@ import { useRouter } from 'vue-router';
import { Fallback } from '@vben/common-ui';
import { Button } from 'ant-design-vue';
import { Button } from 'antdv-next';
const router = useRouter();

View File

@ -4,7 +4,7 @@ import { ref } from 'vue';
import { Page } from '@vben/common-ui';
import { useClipboard } from '@vueuse/core';
import { Button, Card, Input } from 'ant-design-vue';
import { Button, Card, Input } from 'antdv-next';
const source = ref('Hello');
const { copy, text } = useClipboard({ legacy: true, source });

View File

@ -9,7 +9,7 @@ import {
downloadFileFromUrl,
} from '@vben/utils';
import { Button, Card } from 'ant-design-vue';
import { Button, Card } from 'antdv-next';
import { downloadFile1, downloadFile2 } from '#/api/examples/download';

View File

@ -4,7 +4,7 @@ import { ref } from 'vue';
import { Page } from '@vben/common-ui';
import { useFullscreen } from '@vueuse/core';
import { Button, Card } from 'ant-design-vue';
import { Button, Card } from 'antdv-next';
const domRef = ref<HTMLElement>();

View File

@ -18,7 +18,7 @@ import {
SvgWeChatIcon,
} from '@vben/icons';
import { Card, Input } from 'ant-design-vue';
import { Card, Input } from 'antdv-next';
const iconValue1 = ref('ant-design:trademark-outlined');
const iconValue2 = ref('svg:avatar-1');

View File

@ -3,7 +3,7 @@ import { ref } from 'vue';
import { Page } from '@vben/common-ui';
import { Alert, Button, Card } from 'ant-design-vue';
import { Alert, Button, Card } from 'antdv-next';
import { getBigIntData } from '#/api/examples/json-bigint';
@ -21,7 +21,7 @@ function fetchData() {
>
<Card>
<Alert>
<template #message>
<template #title>
有些后端接口返回的ID是长整数但javascript原生的JSON解析是不支持超过2^53-1的长整数的
这种情况可以建议后端返回数据前将长整数转换为字符串类型如果后端不接受我们的建议😡
<br />

View File

@ -4,7 +4,7 @@ import type { LoginExpiredModeType } from '@vben/types';
import { Page } from '@vben/common-ui';
import { preferences, updatePreferences } from '@vben/preferences';
import { Button, Card } from 'ant-design-vue';
import { Button, Card } from 'antdv-next';
import { getMockStatusApi } from '#/api';

View File

@ -9,7 +9,7 @@ import {
updateCustomPreferences,
} from '@vben/preferences';
import { Alert, Button, Card, Space, Tag } from 'ant-design-vue';
import { Alert, Button, Card, Space, Tag } from 'antdv-next';
import { $t } from '#/locales';
@ -105,7 +105,7 @@ function applyPreset(type: 'compact' | 'focus' | 'review') {
},
};
updateCustomPreferences<PlaygroundPreferencesExtension>(presetMap[type]);
updateCustomPreferences(presetMap[type]);
}
function getPriorityColor(priority: DemoTaskItem['priority']) {
@ -136,7 +136,7 @@ function getPriorityColor(priority: DemoTaskItem['priority']) {
:title="$t('demos.preferencesExtensionDemo.currentConfig')"
>
<Alert :type="toneConfig.alertType" show-icon>
<template #message>
<template #title>
{{
$t('demos.preferencesExtensionDemo.currentTitle', {
title: playgroundPreferences.reportTitle,

View File

@ -3,7 +3,7 @@ import { computed, ref, watchEffect } from 'vue';
import { Page } from '@vben/common-ui';
import { Card, Radio, RadioGroup } from 'ant-design-vue';
import { Card, Radio, RadioGroup } from 'antdv-next';
import { getParamsData } from '#/api/examples/params';

View File

@ -5,7 +5,7 @@ import { useRouter } from 'vue-router';
import { Page } from '@vben/common-ui';
import { useTabs } from '@vben/hooks';
import { Button, Card, Input } from 'ant-design-vue';
import { Button, Card, Input } from 'antdv-next';
const router = useRouter();
const newTabTitle = ref('');

View File

@ -2,7 +2,7 @@
import { Page } from '@vben/common-ui';
import { refAutoReset } from '@vueuse/core';
import { Button, Card, Empty } from 'ant-design-vue';
import { Button, Card, Empty } from 'antdv-next';
import ConcurrencyCaching from './concurrency-caching.vue';
import InfiniteQueries from './infinite-queries.vue';
@ -27,7 +27,7 @@ const showCaching = refAutoReset(true, 1000);
<Card
title="并发和缓存"
v-spinning="!showCaching"
:body-style="{ minHeight: '330px' }"
:styles="{ body: { minHeight: '330px' } }"
>
<template #extra>
<Button @click="showCaching = false">重新加载</Button>

Some files were not shown because too many files have changed in this diff Show More