mirror of
https://gitee.com/dapppp/ruoyi-plus-vben5.git
synced 2026-04-15 10:33:14 +08:00
Merge branch 'dev' of https://gitee.com/dapppp/ruoyi-plus-vben5 into warmflow
This commit is contained in:
@@ -1,4 +1,10 @@
|
|||||||
export default {
|
export default {
|
||||||
|
'*.md': ['prettier --cache --ignore-unknown --write'],
|
||||||
|
'*.vue': [
|
||||||
|
'prettier --write',
|
||||||
|
'eslint --cache --fix',
|
||||||
|
'stylelint --fix --allow-empty-input',
|
||||||
|
],
|
||||||
'*.{js,jsx,ts,tsx}': [
|
'*.{js,jsx,ts,tsx}': [
|
||||||
'prettier --cache --ignore-unknown --write',
|
'prettier --cache --ignore-unknown --write',
|
||||||
'eslint --cache --fix',
|
'eslint --cache --fix',
|
||||||
@@ -7,14 +13,8 @@ export default {
|
|||||||
'prettier --cache --ignore-unknown --write',
|
'prettier --cache --ignore-unknown --write',
|
||||||
'stylelint --fix --allow-empty-input',
|
'stylelint --fix --allow-empty-input',
|
||||||
],
|
],
|
||||||
'*.md': ['prettier --cache --ignore-unknown --write'],
|
'package.json': ['prettier --cache --write'],
|
||||||
'*.vue': [
|
|
||||||
'prettier --write',
|
|
||||||
'eslint --cache --fix',
|
|
||||||
'stylelint --fix --allow-empty-input',
|
|
||||||
],
|
|
||||||
'{!(package)*.json,*.code-snippets,.!(browserslist)*rc}': [
|
'{!(package)*.json,*.code-snippets,.!(browserslist)*rc}': [
|
||||||
'prettier --cache --write--parser json',
|
'prettier --cache --write--parser json',
|
||||||
],
|
],
|
||||||
'package.json': ['prettier --cache --write'],
|
|
||||||
};
|
};
|
||||||
|
|||||||
8
.vscode/launch.json
vendored
8
.vscode/launch.json
vendored
@@ -9,7 +9,7 @@
|
|||||||
"url": "http://localhost:5555",
|
"url": "http://localhost:5555",
|
||||||
"env": { "NODE_ENV": "development" },
|
"env": { "NODE_ENV": "development" },
|
||||||
"sourceMaps": true,
|
"sourceMaps": true,
|
||||||
"webRoot": "${workspaceFolder}"
|
"webRoot": "${workspaceFolder}/playground"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "chrome",
|
"type": "chrome",
|
||||||
@@ -18,7 +18,7 @@
|
|||||||
"url": "http://localhost:5666",
|
"url": "http://localhost:5666",
|
||||||
"env": { "NODE_ENV": "development" },
|
"env": { "NODE_ENV": "development" },
|
||||||
"sourceMaps": true,
|
"sourceMaps": true,
|
||||||
"webRoot": "${workspaceFolder}"
|
"webRoot": "${workspaceFolder}/apps/web-antd"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "chrome",
|
"type": "chrome",
|
||||||
@@ -27,7 +27,7 @@
|
|||||||
"url": "http://localhost:5777",
|
"url": "http://localhost:5777",
|
||||||
"env": { "NODE_ENV": "development" },
|
"env": { "NODE_ENV": "development" },
|
||||||
"sourceMaps": true,
|
"sourceMaps": true,
|
||||||
"webRoot": "${workspaceFolder}"
|
"webRoot": "${workspaceFolder}/apps/web-ele"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "chrome",
|
"type": "chrome",
|
||||||
@@ -36,7 +36,7 @@
|
|||||||
"url": "http://localhost:5888",
|
"url": "http://localhost:5888",
|
||||||
"env": { "NODE_ENV": "development" },
|
"env": { "NODE_ENV": "development" },
|
||||||
"sourceMaps": true,
|
"sourceMaps": true,
|
||||||
"webRoot": "${workspaceFolder}"
|
"webRoot": "${workspaceFolder}/apps/web-naive"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
14
CHANGELOG.md
14
CHANGELOG.md
@@ -1,8 +1,22 @@
|
|||||||
# 1.1.4
|
# 1.1.4
|
||||||
|
|
||||||
|
**REFACTOR**
|
||||||
|
|
||||||
|
- 菜单选择组件重构为Table形式
|
||||||
|
|
||||||
**Features**
|
**Features**
|
||||||
|
|
||||||
- 通用的vxe-table排序事件(排序逻辑改为在排序事件中处理而非在api处理)
|
- 通用的vxe-table排序事件(排序逻辑改为在排序事件中处理而非在api处理)
|
||||||
|
- getDict/getDictOptions 提取公共逻辑 减少冗余代码
|
||||||
|
|
||||||
|
**BUG FIXES**
|
||||||
|
|
||||||
|
- 字典项为空时getDict方法无限调用接口((无奈兼容 不给字典item本来就是错误用法))
|
||||||
|
- 表格排序翻页会丢失排序参数
|
||||||
|
|
||||||
|
**OTHERS**
|
||||||
|
|
||||||
|
- 用户管理 新增只获取一次(mounted)默认密码而非每次打开modal都获取
|
||||||
|
|
||||||
# 1.1.3
|
# 1.1.3
|
||||||
|
|
||||||
|
|||||||
@@ -43,6 +43,31 @@ export default eventHandler(async (event) => {
|
|||||||
|
|
||||||
await sleep(600);
|
await sleep(600);
|
||||||
|
|
||||||
const { page, pageSize } = getQuery(event);
|
const { page, pageSize, sortBy, sortOrder } = getQuery(event);
|
||||||
return usePageResponseSuccess(page as string, pageSize as string, mockData);
|
const listData = structuredClone(mockData);
|
||||||
|
if (sortBy && Reflect.has(listData[0], sortBy as string)) {
|
||||||
|
listData.sort((a, b) => {
|
||||||
|
if (sortOrder === 'asc') {
|
||||||
|
if (sortBy === 'price') {
|
||||||
|
return (
|
||||||
|
Number.parseFloat(a[sortBy as string]) -
|
||||||
|
Number.parseFloat(b[sortBy as string])
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return a[sortBy as string] > b[sortBy as string] ? 1 : -1;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (sortBy === 'price') {
|
||||||
|
return (
|
||||||
|
Number.parseFloat(b[sortBy as string]) -
|
||||||
|
Number.parseFloat(a[sortBy as string])
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return a[sortBy as string] < b[sortBy as string] ? 1 : -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return usePageResponseSuccess(page as string, pageSize as string, listData);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ export interface UserInfo {
|
|||||||
realName: string;
|
realName: string;
|
||||||
roles: string[];
|
roles: string[];
|
||||||
username: string;
|
username: string;
|
||||||
|
homePath?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const MOCK_USERS: UserInfo[] = [
|
export const MOCK_USERS: UserInfo[] = [
|
||||||
@@ -20,6 +21,7 @@ export const MOCK_USERS: UserInfo[] = [
|
|||||||
realName: 'Admin',
|
realName: 'Admin',
|
||||||
roles: ['admin'],
|
roles: ['admin'],
|
||||||
username: 'admin',
|
username: 'admin',
|
||||||
|
homePath: '/workspace',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 2,
|
id: 2,
|
||||||
@@ -27,6 +29,7 @@ export const MOCK_USERS: UserInfo[] = [
|
|||||||
realName: 'Jack',
|
realName: 'Jack',
|
||||||
roles: ['user'],
|
roles: ['user'],
|
||||||
username: 'jack',
|
username: 'jack',
|
||||||
|
homePath: '/analytics',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@@ -4,13 +4,12 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import type { BaseFormComponentType } from '@vben/common-ui';
|
import type { BaseFormComponentType } from '@vben/common-ui';
|
||||||
|
|
||||||
import type { Component, SetupContext } from 'vue';
|
import type { Component, SetupContext } from 'vue';
|
||||||
import { h } from 'vue';
|
|
||||||
|
|
||||||
|
import { Tinymce as RichTextarea } from '#/components/tinymce';
|
||||||
|
import { FileUpload, ImageUpload } from '#/components/upload';
|
||||||
import { ApiComponent, globalShareState, IconPicker } from '@vben/common-ui';
|
import { ApiComponent, globalShareState, IconPicker } from '@vben/common-ui';
|
||||||
import { $t } from '@vben/locales';
|
import { $t } from '@vben/locales';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
AutoComplete,
|
AutoComplete,
|
||||||
Button,
|
Button,
|
||||||
@@ -35,9 +34,7 @@ import {
|
|||||||
TreeSelect,
|
TreeSelect,
|
||||||
Upload,
|
Upload,
|
||||||
} from 'ant-design-vue';
|
} from 'ant-design-vue';
|
||||||
|
import { h } from 'vue';
|
||||||
import { Tinymce as RichTextarea } from '#/components/tinymce';
|
|
||||||
import { FileUpload, ImageUpload } from '#/components/upload';
|
|
||||||
|
|
||||||
const withDefaultPlaceholder = <T extends Component>(
|
const withDefaultPlaceholder = <T extends Component>(
|
||||||
component: T,
|
component: T,
|
||||||
|
|||||||
@@ -1,12 +1,9 @@
|
|||||||
import { h, type Ref } from 'vue';
|
import type { VxeGridDefines, VxeGridPropTypes } from '@vben/plugins/vxe-table';
|
||||||
|
import type { Ref } from 'vue';
|
||||||
import {
|
|
||||||
setupVbenVxeTable,
|
|
||||||
useVbenVxeGrid,
|
|
||||||
type VxeGridDefines,
|
|
||||||
} from '@vben/plugins/vxe-table';
|
|
||||||
|
|
||||||
|
import { setupVbenVxeTable, useVbenVxeGrid } from '@vben/plugins/vxe-table';
|
||||||
import { Button, Image } from 'ant-design-vue';
|
import { Button, Image } from 'ant-design-vue';
|
||||||
|
import { h } from 'vue';
|
||||||
|
|
||||||
import { useVbenForm } from './form';
|
import { useVbenForm } from './form';
|
||||||
|
|
||||||
@@ -133,6 +130,7 @@ export function vxeCheckboxChecked(
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 通用的vxe-table排序事件 支持单/多字段排序
|
* 通用的vxe-table排序事件 支持单/多字段排序
|
||||||
|
* @deprecated 翻页后排序会丢失,使用addSortParams代替
|
||||||
* @param tableApi api
|
* @param tableApi api
|
||||||
* @param sortParams 排序参数
|
* @param sortParams 排序参数
|
||||||
*/
|
*/
|
||||||
@@ -151,3 +149,23 @@ export function vxeSortEvent(
|
|||||||
const isAsc = sortList.map((item) => item.order).join(',');
|
const isAsc = sortList.map((item) => item.order).join(',');
|
||||||
tableApi.query({ orderByColumn, isAsc });
|
tableApi.query({ orderByColumn, isAsc });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通用的 排序参数添加到请求参数中
|
||||||
|
* @param params 请求参数
|
||||||
|
* @param sortList vxe-table的排序参数
|
||||||
|
*/
|
||||||
|
export function addSortParams(
|
||||||
|
params: Record<string, any>,
|
||||||
|
sortList: VxeGridPropTypes.ProxyAjaxQuerySortCheckedParams[],
|
||||||
|
) {
|
||||||
|
// 这里是排序取消 length为0 就不添加参数了
|
||||||
|
if (sortList.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 支持单/多字段排序
|
||||||
|
const orderByColumn = sortList.map((item) => item.field).join(',');
|
||||||
|
const isAsc = sortList.map((item) => item.order).join(',');
|
||||||
|
params.orderByColumn = orderByColumn;
|
||||||
|
params.isAsc = isAsc;
|
||||||
|
}
|
||||||
|
|||||||
@@ -36,5 +36,5 @@ export function forceLogout(tokenId: string) {
|
|||||||
* @returns void
|
* @returns void
|
||||||
*/
|
*/
|
||||||
export function forceLogout2(tokenId: string) {
|
export function forceLogout2(tokenId: string) {
|
||||||
return requestClient.postWithMsg<void>(`${Api.root}/${tokenId}`);
|
return requestClient.deleteWithMsg<void>(`${Api.root}/myself/${tokenId}`);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import type { Menu, MenuOption, MenuResp } from './model';
|
|
||||||
|
|
||||||
import type { ID, IDS } from '#/api/common';
|
import type { ID, IDS } from '#/api/common';
|
||||||
|
|
||||||
|
import type { Menu, MenuOption, MenuResp } from './model';
|
||||||
|
|
||||||
import { requestClient } from '#/api/request';
|
import { requestClient } from '#/api/request';
|
||||||
|
|
||||||
enum Api {
|
enum Api {
|
||||||
|
|||||||
2
apps/web-antd/src/api/system/menu/model.d.ts
vendored
2
apps/web-antd/src/api/system/menu/model.d.ts
vendored
@@ -33,6 +33,8 @@ export interface MenuOption {
|
|||||||
weight: number;
|
weight: number;
|
||||||
children: MenuOption[];
|
children: MenuOption[];
|
||||||
key: string; // 实际上不存在 ide报错
|
key: string; // 实际上不存在 ide报错
|
||||||
|
menuType: string;
|
||||||
|
icon: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import type { OssFile } from './model';
|
|
||||||
|
|
||||||
import type { ID, IDS, PageQuery, PageResult } from '#/api/common';
|
import type { ID, IDS, PageQuery, PageResult } from '#/api/common';
|
||||||
|
|
||||||
|
import type { OssFile } from './model';
|
||||||
|
|
||||||
import { ContentTypeEnum } from '#/api/helper';
|
import { ContentTypeEnum } from '#/api/helper';
|
||||||
import { requestClient } from '#/api/request';
|
import { requestClient } from '#/api/request';
|
||||||
|
|
||||||
|
|||||||
@@ -1 +1,2 @@
|
|||||||
|
export { default as MenuSelectTable } from './src/menu-select-table.vue';
|
||||||
export { default as TreeSelectPanel } from './src/tree-select-panel.vue';
|
export { default as TreeSelectPanel } from './src/tree-select-panel.vue';
|
||||||
|
|||||||
85
apps/web-antd/src/components/tree/src/data.tsx
Normal file
85
apps/web-antd/src/components/tree/src/data.tsx
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
import type { VxeGridProps } from '#/adapter/vxe-table';
|
||||||
|
import type { ID } from '#/api/common';
|
||||||
|
import type { MenuOption } from '#/api/system/menu/model';
|
||||||
|
|
||||||
|
import { h, markRaw } from 'vue';
|
||||||
|
|
||||||
|
import { FolderIcon, MenuIcon, OkButtonIcon, VbenIcon } from '@vben/icons';
|
||||||
|
|
||||||
|
export interface Permission {
|
||||||
|
checked: boolean;
|
||||||
|
id: ID;
|
||||||
|
label: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MenuPermissionOption extends MenuOption {
|
||||||
|
permissions: Permission[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const menuTypes = {
|
||||||
|
C: { icon: markRaw(MenuIcon), value: '菜单' },
|
||||||
|
F: { icon: markRaw(OkButtonIcon), value: '按钮' },
|
||||||
|
M: { icon: markRaw(FolderIcon), value: '目录' },
|
||||||
|
};
|
||||||
|
|
||||||
|
export const nodeOptions = [
|
||||||
|
{ label: '节点关联', value: true },
|
||||||
|
{ label: '节点独立', value: false },
|
||||||
|
];
|
||||||
|
|
||||||
|
export const columns: VxeGridProps['columns'] = [
|
||||||
|
{
|
||||||
|
type: 'checkbox',
|
||||||
|
title: '菜单名称',
|
||||||
|
field: 'label',
|
||||||
|
treeNode: true,
|
||||||
|
headerAlign: 'left',
|
||||||
|
align: 'left',
|
||||||
|
width: 230,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '图标',
|
||||||
|
field: 'icon',
|
||||||
|
width: 80,
|
||||||
|
slots: {
|
||||||
|
default: ({ row }) => {
|
||||||
|
if (row?.icon === '#') {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<span class={'flex justify-center'}>
|
||||||
|
<VbenIcon icon={row.icon} />
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '类型',
|
||||||
|
field: 'menuType',
|
||||||
|
width: 80,
|
||||||
|
slots: {
|
||||||
|
default: ({ row }) => {
|
||||||
|
const current = menuTypes[row.menuType as 'C' | 'F' | 'M'];
|
||||||
|
if (!current) {
|
||||||
|
return '未知';
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<span class="flex items-center justify-center gap-1">
|
||||||
|
{h(current.icon, { class: 'size-[18px]' })}
|
||||||
|
<span>{current.value}</span>
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '权限标识',
|
||||||
|
field: 'permissions',
|
||||||
|
headerAlign: 'left',
|
||||||
|
align: 'left',
|
||||||
|
slots: {
|
||||||
|
default: 'permissions',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
133
apps/web-antd/src/components/tree/src/helper.tsx
Normal file
133
apps/web-antd/src/components/tree/src/helper.tsx
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
import type { MenuPermissionOption } from './data';
|
||||||
|
|
||||||
|
import type { useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||||
|
import type { MenuOption } from '#/api/system/menu/model';
|
||||||
|
|
||||||
|
import { eachTree, treeToList } from '@vben/utils';
|
||||||
|
|
||||||
|
import { difference, isEmpty, isUndefined } from 'lodash-es';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 权限列设置是否全选
|
||||||
|
* @param record 行记录
|
||||||
|
* @param checked 是否选中
|
||||||
|
*/
|
||||||
|
export function setPermissionsChecked(
|
||||||
|
record: MenuPermissionOption,
|
||||||
|
checked: boolean,
|
||||||
|
) {
|
||||||
|
if (record?.permissions?.length > 0) {
|
||||||
|
// 全部设置为选中
|
||||||
|
record.permissions.forEach((permission) => {
|
||||||
|
permission.checked = checked;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置当前行 & 所有子节点选中状态
|
||||||
|
* @param record 行
|
||||||
|
* @param checked 是否选中
|
||||||
|
*/
|
||||||
|
export function rowAndChildrenChecked(
|
||||||
|
record: MenuPermissionOption,
|
||||||
|
checked: boolean,
|
||||||
|
) {
|
||||||
|
// 当前行选中
|
||||||
|
setPermissionsChecked(record, checked);
|
||||||
|
// 所有子节点选中
|
||||||
|
record?.children?.forEach?.((permission) => {
|
||||||
|
rowAndChildrenChecked(permission as MenuPermissionOption, checked);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* void方法 会直接修改原始数据
|
||||||
|
* 将树结构转为 tree+permissions结构
|
||||||
|
* @param menus 后台返回的menu
|
||||||
|
*/
|
||||||
|
export function menusWithPermissions(menus: MenuOption[]) {
|
||||||
|
eachTree(menus, (item: MenuPermissionOption) => {
|
||||||
|
if (item.children && item.children.length > 0) {
|
||||||
|
/**
|
||||||
|
* 所有为按钮的节点提取出来
|
||||||
|
* 需要注意 这里需要过滤目录下直接是按钮的情况item.menuType !== 'M'
|
||||||
|
* 将按钮往children添加而非加到permissions
|
||||||
|
*/
|
||||||
|
const permissions = item.children.filter(
|
||||||
|
(child: MenuOption) => child.menuType === 'F' && item.menuType !== 'M',
|
||||||
|
);
|
||||||
|
// 取差集
|
||||||
|
const diffCollection = difference(item.children, permissions);
|
||||||
|
// 更新后的children 即去除按钮
|
||||||
|
item.children = diffCollection;
|
||||||
|
|
||||||
|
// permissions作为字段添加到item
|
||||||
|
const permissionsArr = permissions.map((permission) => {
|
||||||
|
return {
|
||||||
|
id: permission.id,
|
||||||
|
label: permission.label,
|
||||||
|
checked: false,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
item.permissions = permissionsArr;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置表格选中
|
||||||
|
* @param checkedKeys 选中的keys
|
||||||
|
* @param menus 菜单 转换后的菜单
|
||||||
|
* @param tableApi api
|
||||||
|
* @param association 是否节点关联
|
||||||
|
*/
|
||||||
|
export function setTableChecked(
|
||||||
|
checkedKeys: (number | string)[],
|
||||||
|
menus: MenuPermissionOption[],
|
||||||
|
tableApi: ReturnType<typeof useVbenVxeGrid>['1'],
|
||||||
|
association: boolean,
|
||||||
|
) {
|
||||||
|
// tree转list
|
||||||
|
const menuList: MenuPermissionOption[] = treeToList(menus);
|
||||||
|
// 拿到勾选的行数据
|
||||||
|
let checkedRows = menuList.filter((item) => checkedKeys.includes(item.id));
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 节点独立切换到节点关联 只需要最末尾的数据 即children为空
|
||||||
|
*/
|
||||||
|
if (!association) {
|
||||||
|
checkedRows = checkedRows.filter(
|
||||||
|
(item) => isUndefined(item.children) || isEmpty(item.children),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置行选中 & permissions选中
|
||||||
|
checkedRows.forEach((item) => {
|
||||||
|
tableApi.grid.setCheckboxRow(item, true);
|
||||||
|
if (item?.permissions?.length > 0) {
|
||||||
|
item.permissions.forEach((permission) => {
|
||||||
|
if (checkedKeys.includes(permission.id)) {
|
||||||
|
permission.checked = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 节点独立切换到节点关联
|
||||||
|
* 勾选后还需要过滤权限没有任何勾选的情况 这时候取消行的勾选
|
||||||
|
*/
|
||||||
|
if (!association) {
|
||||||
|
const emptyRows = checkedRows.filter((item) => {
|
||||||
|
if (isUndefined(item.permissions) || isEmpty(item.permissions)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return item.permissions.every(
|
||||||
|
(permission) => permission.checked === false,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
// 设置为不选中
|
||||||
|
tableApi.grid.setCheckboxRow(emptyRows, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
57
apps/web-antd/src/components/tree/src/hook.tsx
Normal file
57
apps/web-antd/src/components/tree/src/hook.tsx
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
||||||
|
import type { TourProps } from 'ant-design-vue';
|
||||||
|
|
||||||
|
import { defineComponent, ref } from 'vue';
|
||||||
|
|
||||||
|
import { useLocalStorage } from '@vueuse/core';
|
||||||
|
import { Tour } from 'ant-design-vue';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 全屏引导
|
||||||
|
* @returns value
|
||||||
|
*/
|
||||||
|
export function useFullScreenGuide() {
|
||||||
|
const open = ref(false);
|
||||||
|
/**
|
||||||
|
* 是否已读 只显示一次
|
||||||
|
*/
|
||||||
|
const read = useLocalStorage('menu_select_fullscreen_read', false);
|
||||||
|
|
||||||
|
function openGuide() {
|
||||||
|
if (!read.value) {
|
||||||
|
open.value = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeGuide() {
|
||||||
|
open.value = false;
|
||||||
|
read.value = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const steps: TourProps['steps'] = [
|
||||||
|
{
|
||||||
|
title: '提示',
|
||||||
|
description: '点击这里可以全屏',
|
||||||
|
target: () =>
|
||||||
|
document.querySelector(
|
||||||
|
'div#menu-select-table .vxe-tools--operate > button[title="全屏"]',
|
||||||
|
)!,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const FullScreenGuide = defineComponent({
|
||||||
|
name: 'FullScreenGuide',
|
||||||
|
inheritAttrs: false,
|
||||||
|
setup() {
|
||||||
|
return () => (
|
||||||
|
<Tour onClose={closeGuide} open={open.value} steps={steps} />
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
FullScreenGuide,
|
||||||
|
openGuide,
|
||||||
|
closeGuide,
|
||||||
|
};
|
||||||
|
}
|
||||||
412
apps/web-antd/src/components/tree/src/menu-select-table.vue
Normal file
412
apps/web-antd/src/components/tree/src/menu-select-table.vue
Normal file
@@ -0,0 +1,412 @@
|
|||||||
|
<!--
|
||||||
|
不兼容也不会兼容一些错误用法
|
||||||
|
比如: 菜单下放目录 菜单下放菜单
|
||||||
|
比如: 按钮下放目录 按钮下放菜单 按钮下放按钮
|
||||||
|
-->
|
||||||
|
<script setup lang="tsx">
|
||||||
|
import type { RadioChangeEvent } from 'ant-design-vue';
|
||||||
|
|
||||||
|
import type { MenuPermissionOption } from './data';
|
||||||
|
|
||||||
|
import type { VxeGridProps } from '#/adapter/vxe-table';
|
||||||
|
import type { MenuOption } from '#/api/system/menu/model';
|
||||||
|
|
||||||
|
import { nextTick, onMounted, ref, shallowRef, watch } from 'vue';
|
||||||
|
|
||||||
|
import { cloneDeep, findGroupParentIds } from '@vben/utils';
|
||||||
|
|
||||||
|
import { Alert, Checkbox, RadioGroup, Space } from 'ant-design-vue';
|
||||||
|
import { uniq } from 'lodash-es';
|
||||||
|
|
||||||
|
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||||
|
|
||||||
|
import { columns, nodeOptions } from './data';
|
||||||
|
import {
|
||||||
|
menusWithPermissions,
|
||||||
|
rowAndChildrenChecked,
|
||||||
|
setPermissionsChecked,
|
||||||
|
setTableChecked,
|
||||||
|
} from './helper';
|
||||||
|
import { useFullScreenGuide } from './hook';
|
||||||
|
|
||||||
|
defineOptions({
|
||||||
|
name: 'MenuSelectTable',
|
||||||
|
inheritAttrs: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
const props = withDefaults(
|
||||||
|
defineProps<{
|
||||||
|
checkedKeys: (number | string)[];
|
||||||
|
defaultExpandAll?: boolean;
|
||||||
|
menus: MenuOption[];
|
||||||
|
}>(),
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* 是否默认展开全部
|
||||||
|
*/
|
||||||
|
defaultExpandAll: true,
|
||||||
|
/**
|
||||||
|
* 注意这里不是双向绑定 需要调用getCheckedKeys实例方法来获取真正选中的节点
|
||||||
|
*/
|
||||||
|
checkedKeys: () => [],
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否节点关联
|
||||||
|
*/
|
||||||
|
const association = defineModel('association', {
|
||||||
|
type: Boolean,
|
||||||
|
default: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const gridOptions: VxeGridProps = {
|
||||||
|
checkboxConfig: {
|
||||||
|
// checkbox显示的字段
|
||||||
|
labelField: 'label',
|
||||||
|
// 是否严格模式 即节点不关联
|
||||||
|
checkStrictly: !association.value,
|
||||||
|
},
|
||||||
|
size: 'small',
|
||||||
|
columns,
|
||||||
|
height: 'auto',
|
||||||
|
keepSource: true,
|
||||||
|
pagerConfig: {
|
||||||
|
enabled: false,
|
||||||
|
},
|
||||||
|
proxyConfig: {
|
||||||
|
enabled: false,
|
||||||
|
},
|
||||||
|
toolbarConfig: {
|
||||||
|
// 自定义列
|
||||||
|
custom: false,
|
||||||
|
// 最大化
|
||||||
|
zoom: true,
|
||||||
|
// 刷新
|
||||||
|
refresh: false,
|
||||||
|
},
|
||||||
|
rowConfig: {
|
||||||
|
isHover: false,
|
||||||
|
isCurrent: false,
|
||||||
|
keyField: 'id',
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 开启虚拟滚动
|
||||||
|
* 数据量小可以选择关闭
|
||||||
|
* 如果遇到样式问题(空白、错位 滚动等)可以选择关闭虚拟滚动
|
||||||
|
*/
|
||||||
|
scrollY: {
|
||||||
|
enabled: true,
|
||||||
|
gt: 0,
|
||||||
|
},
|
||||||
|
treeConfig: {
|
||||||
|
parentField: 'parentId',
|
||||||
|
rowField: 'id',
|
||||||
|
transform: false,
|
||||||
|
},
|
||||||
|
// 溢出换行显示
|
||||||
|
showOverflow: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用于界面显示选中的数量
|
||||||
|
*/
|
||||||
|
const checkedNum = ref(0);
|
||||||
|
/**
|
||||||
|
* 更新选中的数量
|
||||||
|
*/
|
||||||
|
function updateCheckedNumber() {
|
||||||
|
checkedNum.value = getCheckedKeys().length;
|
||||||
|
}
|
||||||
|
|
||||||
|
const [BasicTable, tableApi] = useVbenVxeGrid({
|
||||||
|
gridOptions,
|
||||||
|
gridEvents: {
|
||||||
|
// 勾选事件
|
||||||
|
checkboxChange: (params) => {
|
||||||
|
// 选中还是取消选中
|
||||||
|
const checked = params.checked;
|
||||||
|
// 行
|
||||||
|
const record = params.row;
|
||||||
|
if (association.value) {
|
||||||
|
// 节点关联
|
||||||
|
// 设置所有子节点选中状态
|
||||||
|
rowAndChildrenChecked(record, checked);
|
||||||
|
} else {
|
||||||
|
// 节点独立
|
||||||
|
// 点行会勾选/取消全部权限 点权限不会勾选行
|
||||||
|
setPermissionsChecked(record, checked);
|
||||||
|
}
|
||||||
|
updateCheckedNumber();
|
||||||
|
},
|
||||||
|
// 全选事件
|
||||||
|
checkboxAll: (params) => {
|
||||||
|
const records = params.$grid.getData();
|
||||||
|
records.forEach((item) => {
|
||||||
|
rowAndChildrenChecked(item, params.checked);
|
||||||
|
});
|
||||||
|
updateCheckedNumber();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置表格选中
|
||||||
|
* @param menus menu
|
||||||
|
* @param keys 选中的key
|
||||||
|
* @param triggerOnchange 节点独立情况 不需要触发onChange(false)
|
||||||
|
*/
|
||||||
|
function setCheckedByKeys(
|
||||||
|
menus: MenuPermissionOption[],
|
||||||
|
keys: (number | string)[],
|
||||||
|
triggerOnchange: boolean,
|
||||||
|
) {
|
||||||
|
menus.forEach((item) => {
|
||||||
|
// 设置行选中
|
||||||
|
if (keys.includes(item.id)) {
|
||||||
|
tableApi.grid.setCheckboxRow(item, true);
|
||||||
|
}
|
||||||
|
// 设置权限columns选中
|
||||||
|
if (item.permissions && item.permissions.length > 0) {
|
||||||
|
// 遍历 设置勾选
|
||||||
|
item.permissions.forEach((permission) => {
|
||||||
|
if (keys.includes(permission.id)) {
|
||||||
|
permission.checked = true;
|
||||||
|
// 手动触发onChange来选中 节点独立情况不需要处理
|
||||||
|
triggerOnchange && handlePermissionChange(item);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// 设置children选中
|
||||||
|
if (item.children && item.children.length > 0) {
|
||||||
|
setCheckedByKeys(item.children as any, keys, triggerOnchange);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const { FullScreenGuide, openGuide } = useFullScreenGuide();
|
||||||
|
onMounted(() => {
|
||||||
|
/**
|
||||||
|
* 加载表格数据 转为指定结构
|
||||||
|
*/
|
||||||
|
watch(
|
||||||
|
() => props.menus,
|
||||||
|
async (menus) => {
|
||||||
|
const clonedMenus = cloneDeep(menus);
|
||||||
|
menusWithPermissions(clonedMenus);
|
||||||
|
// console.log(clonedMenus);
|
||||||
|
await tableApi.grid.loadData(clonedMenus);
|
||||||
|
// 展开全部 默认true
|
||||||
|
if (props.defaultExpandAll) {
|
||||||
|
await nextTick();
|
||||||
|
setExpandOrCollapse(true);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 节点关联变动 更新表格勾选效果
|
||||||
|
*/
|
||||||
|
watch(association, (value) => {
|
||||||
|
tableApi.setGridOptions({
|
||||||
|
checkboxConfig: {
|
||||||
|
checkStrictly: !value,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* checkedKeys依赖menus
|
||||||
|
* 要注意加载顺序
|
||||||
|
* !!!要在外部确保menus先加载!!!
|
||||||
|
*/
|
||||||
|
watch(
|
||||||
|
() => props.checkedKeys,
|
||||||
|
(value) => {
|
||||||
|
const allCheckedKeys = uniq([...value]);
|
||||||
|
// 获取表格data 如果checkedKeys在menus的watch之前触发 这里会拿到空 导致勾选异常
|
||||||
|
const records = tableApi.grid.getData();
|
||||||
|
setCheckedByKeys(records, allCheckedKeys, association.value);
|
||||||
|
updateCheckedNumber();
|
||||||
|
|
||||||
|
// 全屏引导
|
||||||
|
setTimeout(openGuide, 1000);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 缓存上次(切换节点关系前)选中的keys
|
||||||
|
const lastCheckedKeys = shallowRef<(number | string)[]>([]);
|
||||||
|
/**
|
||||||
|
* 节点关联变动 事件
|
||||||
|
*/
|
||||||
|
async function handleAssociationChange(e: RadioChangeEvent) {
|
||||||
|
lastCheckedKeys.value = getCheckedKeys();
|
||||||
|
// 清空全部permissions选中
|
||||||
|
const records = tableApi.grid.getData();
|
||||||
|
records.forEach((item) => {
|
||||||
|
rowAndChildrenChecked(item, false);
|
||||||
|
});
|
||||||
|
// 需要清空全部勾选
|
||||||
|
await tableApi.grid.clearCheckboxRow();
|
||||||
|
// 滚动到顶部
|
||||||
|
await tableApi.grid.scrollTo(0, 0);
|
||||||
|
|
||||||
|
// 节点切换 不同的选中
|
||||||
|
setTableChecked(lastCheckedKeys.value, records, tableApi, !e.target.value);
|
||||||
|
|
||||||
|
updateCheckedNumber();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 全部展开/折叠
|
||||||
|
* @param expand 是否展开
|
||||||
|
*/
|
||||||
|
function setExpandOrCollapse(expand: boolean) {
|
||||||
|
tableApi.grid?.setAllTreeExpand(expand);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 权限列表 checkbox勾选的事件
|
||||||
|
* @param row 行
|
||||||
|
*/
|
||||||
|
function handlePermissionChange(row: any) {
|
||||||
|
// 节点关联
|
||||||
|
if (association.value) {
|
||||||
|
const checkedPermissions = row.permissions.filter(
|
||||||
|
(item: any) => item.checked === true,
|
||||||
|
);
|
||||||
|
// 有一条选中 则整个行选中
|
||||||
|
if (checkedPermissions.length > 0) {
|
||||||
|
tableApi.grid.setCheckboxRow(row, true);
|
||||||
|
}
|
||||||
|
// 无任何选中 则整个行不选中
|
||||||
|
if (checkedPermissions.length === 0) {
|
||||||
|
tableApi.grid.setCheckboxRow(row, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 节点独立 不处理
|
||||||
|
updateCheckedNumber();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取勾选的key
|
||||||
|
* @param records 行记录列表
|
||||||
|
* @param addCurrent 是否添加当前行的id
|
||||||
|
*/
|
||||||
|
function getKeys(records: MenuPermissionOption[], addCurrent: boolean) {
|
||||||
|
const allKeys: (number | string)[] = [];
|
||||||
|
records.forEach((item) => {
|
||||||
|
// 处理children
|
||||||
|
if (item.children && item.children.length > 0) {
|
||||||
|
const keys = getKeys(item.children as MenuPermissionOption[], addCurrent);
|
||||||
|
allKeys.push(...keys);
|
||||||
|
} else {
|
||||||
|
// 当前行的id
|
||||||
|
addCurrent && allKeys.push(item.id);
|
||||||
|
// 当前行权限id 获取已经选中的
|
||||||
|
if (item.permissions && item.permissions.length > 0) {
|
||||||
|
const ids = item.permissions
|
||||||
|
.filter((m) => m.checked === true)
|
||||||
|
.map((m) => m.id);
|
||||||
|
allKeys.push(...ids);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return uniq(allKeys);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取选中的key
|
||||||
|
*/
|
||||||
|
function getCheckedKeys() {
|
||||||
|
// 节点关联
|
||||||
|
if (association.value) {
|
||||||
|
const records = tableApi?.grid?.getCheckboxRecords?.() ?? [];
|
||||||
|
// 子节点
|
||||||
|
const nodeKeys = getKeys(records, true);
|
||||||
|
// 所有父节点
|
||||||
|
const parentIds = findGroupParentIds(props.menus, nodeKeys as number[]);
|
||||||
|
// 拼接 去重
|
||||||
|
const realKeys = uniq([...parentIds, ...nodeKeys]);
|
||||||
|
return realKeys;
|
||||||
|
}
|
||||||
|
// 节点独立
|
||||||
|
|
||||||
|
// 勾选的行
|
||||||
|
const records = tableApi?.grid?.getCheckboxRecords?.() ?? [];
|
||||||
|
// 全部数据 用于获取permissions
|
||||||
|
const allRecords = tableApi?.grid?.getData?.() ?? [];
|
||||||
|
// 表格已经选中的行ids
|
||||||
|
const checkedIds = records.map((item) => item.id);
|
||||||
|
// 所有已经勾选权限的ids
|
||||||
|
const permissionIds = getKeys(allRecords, false);
|
||||||
|
// 合并 去重
|
||||||
|
const allIds = uniq([...checkedIds, ...permissionIds]);
|
||||||
|
return allIds;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 暴露给外部使用 获取已选中的key
|
||||||
|
*/
|
||||||
|
defineExpose({
|
||||||
|
getCheckedKeys,
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="flex h-full flex-col" id="menu-select-table">
|
||||||
|
<BasicTable>
|
||||||
|
<template #toolbar-actions>
|
||||||
|
<RadioGroup
|
||||||
|
v-model:value="association"
|
||||||
|
:options="nodeOptions"
|
||||||
|
button-style="solid"
|
||||||
|
option-type="button"
|
||||||
|
@change="handleAssociationChange"
|
||||||
|
/>
|
||||||
|
<Alert class="mx-2" type="info">
|
||||||
|
<template #message>
|
||||||
|
<div>
|
||||||
|
已选中
|
||||||
|
<span class="text-primary mx-1 font-semibold">
|
||||||
|
{{ checkedNum }}
|
||||||
|
</span>
|
||||||
|
个节点
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</Alert>
|
||||||
|
</template>
|
||||||
|
<template #toolbar-tools>
|
||||||
|
<Space>
|
||||||
|
<a-button @click="setExpandOrCollapse(false)">
|
||||||
|
{{ $t('pages.common.collapse') }}
|
||||||
|
</a-button>
|
||||||
|
<a-button @click="setExpandOrCollapse(true)">
|
||||||
|
{{ $t('pages.common.expand') }}
|
||||||
|
</a-button>
|
||||||
|
</Space>
|
||||||
|
</template>
|
||||||
|
<template #permissions="{ row }">
|
||||||
|
<div class="flex flex-wrap gap-x-3 gap-y-1">
|
||||||
|
<Checkbox
|
||||||
|
v-for="permission in row.permissions"
|
||||||
|
:key="permission.id"
|
||||||
|
v-model:checked="permission.checked"
|
||||||
|
@change="() => handlePermissionChange(row)"
|
||||||
|
>
|
||||||
|
{{ permission.label }}
|
||||||
|
</Checkbox>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</BasicTable>
|
||||||
|
<!-- 全屏引导 -->
|
||||||
|
<FullScreenGuide />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
:deep(.ant-alert) {
|
||||||
|
padding: 4px 8px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -3,7 +3,9 @@ import type { CheckboxChangeEvent } from 'ant-design-vue/es/checkbox/interface';
|
|||||||
import type { DataNode } from 'ant-design-vue/es/tree';
|
import type { DataNode } from 'ant-design-vue/es/tree';
|
||||||
import type { CheckInfo } from 'ant-design-vue/es/vc-tree/props';
|
import type { CheckInfo } from 'ant-design-vue/es/vc-tree/props';
|
||||||
|
|
||||||
import { computed, nextTick, onMounted, type PropType, ref, watch } from 'vue';
|
import type { PropType } from 'vue';
|
||||||
|
|
||||||
|
import { computed, nextTick, onMounted, ref, watch } from 'vue';
|
||||||
|
|
||||||
import { findGroupParentIds, treeToList } from '@vben/utils';
|
import { findGroupParentIds, treeToList } from '@vben/utils';
|
||||||
|
|
||||||
@@ -108,8 +110,8 @@ const stop = watch([checkedKeys, () => props.treeData], () => {
|
|||||||
* @param info info.halfCheckedKeys为父节点的ID
|
* @param info info.halfCheckedKeys为父节点的ID
|
||||||
*/
|
*/
|
||||||
type CheckedState<T = number | string> =
|
type CheckedState<T = number | string> =
|
||||||
| { checked: T[]; halfChecked: T[] }
|
| T[]
|
||||||
| T[];
|
| { checked: T[]; halfChecked: T[] };
|
||||||
function handleChecked(checkedStateKeys: CheckedState, info: CheckInfo) {
|
function handleChecked(checkedStateKeys: CheckedState, info: CheckInfo) {
|
||||||
// 数组的话为节点关联
|
// 数组的话为节点关联
|
||||||
if (Array.isArray(checkedStateKeys)) {
|
if (Array.isArray(checkedStateKeys)) {
|
||||||
|
|||||||
@@ -2,15 +2,13 @@
|
|||||||
import type { UploadFile, UploadProps } from 'ant-design-vue';
|
import type { UploadFile, UploadProps } from 'ant-design-vue';
|
||||||
import type { UploadRequestOption } from 'ant-design-vue/lib/vc-upload/interface';
|
import type { UploadRequestOption } from 'ant-design-vue/lib/vc-upload/interface';
|
||||||
|
|
||||||
import { ref, toRefs, watch } from 'vue';
|
|
||||||
|
|
||||||
import { $t } from '@vben/locales';
|
|
||||||
|
|
||||||
import { PlusOutlined } from '@ant-design/icons-vue';
|
|
||||||
import { message, Modal, Upload } from 'ant-design-vue';
|
|
||||||
import { isArray, isFunction, isObject, isString } from 'lodash-es';
|
|
||||||
|
|
||||||
import { uploadApi } from '#/api';
|
import { uploadApi } from '#/api';
|
||||||
|
import { ossInfo } from '#/api/system/oss';
|
||||||
|
import { PlusOutlined } from '@ant-design/icons-vue';
|
||||||
|
import { $t } from '@vben/locales';
|
||||||
|
import { message, Modal, Upload } from 'ant-design-vue';
|
||||||
|
import { isArray, isFunction, isObject, isString, uniqueId } from 'lodash-es';
|
||||||
|
import { ref, toRefs, watch } from 'vue';
|
||||||
|
|
||||||
import { checkImageFileType, defaultImageAccept } from './helper';
|
import { checkImageFileType, defaultImageAccept } from './helper';
|
||||||
import { UploadResultStatus } from './typing';
|
import { UploadResultStatus } from './typing';
|
||||||
@@ -37,7 +35,7 @@ const props = withDefaults(
|
|||||||
multiple?: boolean;
|
multiple?: boolean;
|
||||||
// support xxx.xxx.xx
|
// support xxx.xxx.xx
|
||||||
// 返回的字段 默认url
|
// 返回的字段 默认url
|
||||||
resultField?: 'fileName' | 'ossId' | 'url' | string;
|
resultField?: 'fileName' | 'ossId' | 'url';
|
||||||
value?: string | string[];
|
value?: string | string[];
|
||||||
}>(),
|
}>(),
|
||||||
{
|
{
|
||||||
@@ -50,7 +48,7 @@ const props = withDefaults(
|
|||||||
accept: () => defaultImageAccept,
|
accept: () => defaultImageAccept,
|
||||||
multiple: false,
|
multiple: false,
|
||||||
api: uploadApi,
|
api: uploadApi,
|
||||||
resultField: '',
|
resultField: 'url',
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
const emit = defineEmits(['change', 'update:value', 'delete']);
|
const emit = defineEmits(['change', 'update:value', 'delete']);
|
||||||
@@ -74,7 +72,7 @@ const isFirstRender = ref<boolean>(true);
|
|||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => props.value,
|
() => props.value,
|
||||||
(v) => {
|
async (v) => {
|
||||||
if (isInnerOperate.value) {
|
if (isInnerOperate.value) {
|
||||||
isInnerOperate.value = false;
|
isInnerOperate.value = false;
|
||||||
return;
|
return;
|
||||||
@@ -90,19 +88,40 @@ watch(
|
|||||||
}
|
}
|
||||||
// 直接赋值 可能为string | string[]
|
// 直接赋值 可能为string | string[]
|
||||||
value = v;
|
value = v;
|
||||||
fileList.value = _fileList.map((item, i) => {
|
const withUrlList: UploadProps['fileList'] = [];
|
||||||
|
for (const item of _fileList) {
|
||||||
|
// ossId情况
|
||||||
|
if (props.resultField === 'ossId') {
|
||||||
|
const resp = await ossInfo([item]);
|
||||||
if (item && isString(item)) {
|
if (item && isString(item)) {
|
||||||
return {
|
withUrlList.push({
|
||||||
uid: `${-i}`,
|
uid: item, // ossId作为uid 方便getValue获取
|
||||||
|
name: item.slice(Math.max(0, item.lastIndexOf('/') + 1)),
|
||||||
|
status: 'done',
|
||||||
|
url: resp?.[0]?.url,
|
||||||
|
});
|
||||||
|
} else if (item && isObject(item)) {
|
||||||
|
withUrlList.push({
|
||||||
|
...(item as any),
|
||||||
|
uid: item,
|
||||||
|
url: resp?.[0]?.url,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 非ossId情况
|
||||||
|
if (item && isString(item)) {
|
||||||
|
withUrlList.push({
|
||||||
|
uid: uniqueId(),
|
||||||
name: item.slice(Math.max(0, item.lastIndexOf('/') + 1)),
|
name: item.slice(Math.max(0, item.lastIndexOf('/') + 1)),
|
||||||
status: 'done',
|
status: 'done',
|
||||||
url: item,
|
url: item,
|
||||||
};
|
});
|
||||||
} else if (item && isObject(item)) {
|
} else if (item && isObject(item)) {
|
||||||
return item;
|
withUrlList.push(item);
|
||||||
}
|
}
|
||||||
return null;
|
}
|
||||||
}) as UploadProps['fileList'];
|
}
|
||||||
|
fileList.value = withUrlList;
|
||||||
}
|
}
|
||||||
if (!isFirstRender.value) {
|
if (!isFirstRender.value) {
|
||||||
emit('change', value);
|
emit('change', value);
|
||||||
@@ -200,12 +219,17 @@ async function customRequest(info: UploadRequestOption<any>) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getValue() {
|
function getValue() {
|
||||||
|
console.log(fileList.value);
|
||||||
const list = (fileList.value || [])
|
const list = (fileList.value || [])
|
||||||
.filter((item) => item?.status === UploadResultStatus.DONE)
|
.filter((item) => item?.status === UploadResultStatus.DONE)
|
||||||
.map((item: any) => {
|
.map((item: any) => {
|
||||||
if (item?.response && props?.resultField) {
|
if (item?.response && props?.resultField) {
|
||||||
return item?.response?.[props.resultField];
|
return item?.response?.[props.resultField];
|
||||||
}
|
}
|
||||||
|
// ossId兼容 uid为ossId直接返回
|
||||||
|
if (props.resultField === 'ossId' && item.uid) {
|
||||||
|
return item.uid;
|
||||||
|
}
|
||||||
// 适用于已经有图片 回显的情况 会默认在init处理为{url: 'xx'}
|
// 适用于已经有图片 回显的情况 会默认在init处理为{url: 'xx'}
|
||||||
if (item?.url) {
|
if (item?.url) {
|
||||||
return item.url;
|
return item.url;
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
import type { LocaleSetupOptions, SupportedLanguagesType } from '@vben/locales';
|
import type { LocaleSetupOptions, SupportedLanguagesType } from '@vben/locales';
|
||||||
import type { Locale } from 'ant-design-vue/es/locale';
|
import type { Locale } from 'ant-design-vue/es/locale';
|
||||||
|
|
||||||
import type { App } from 'vue';
|
import type { App } from 'vue';
|
||||||
import { ref } from 'vue';
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
$t,
|
$t,
|
||||||
@@ -10,10 +8,10 @@ import {
|
|||||||
loadLocalesMapFromDir,
|
loadLocalesMapFromDir,
|
||||||
} from '@vben/locales';
|
} from '@vben/locales';
|
||||||
import { preferences } from '@vben/preferences';
|
import { preferences } from '@vben/preferences';
|
||||||
|
|
||||||
import antdEnLocale from 'ant-design-vue/es/locale/en_US';
|
import antdEnLocale from 'ant-design-vue/es/locale/en_US';
|
||||||
import antdDefaultLocale from 'ant-design-vue/es/locale/zh_CN';
|
import antdDefaultLocale from 'ant-design-vue/es/locale/zh_CN';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
|
import { ref } from 'vue';
|
||||||
|
|
||||||
const antdLocale = ref<Locale>(antdDefaultLocale);
|
const antdLocale = ref<Locale>(antdDefaultLocale);
|
||||||
|
|
||||||
|
|||||||
@@ -54,7 +54,9 @@ function setupAccessGuard(router: Router) {
|
|||||||
if (coreRouteNames.includes(to.name as string)) {
|
if (coreRouteNames.includes(to.name as string)) {
|
||||||
if (to.path === LOGIN_PATH && accessStore.accessToken) {
|
if (to.path === LOGIN_PATH && accessStore.accessToken) {
|
||||||
return decodeURIComponent(
|
return decodeURIComponent(
|
||||||
(to.query?.redirect as string) || DEFAULT_HOME_PATH,
|
(to.query?.redirect as string) ||
|
||||||
|
userStore.userInfo?.homePath ||
|
||||||
|
DEFAULT_HOME_PATH,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
@@ -72,7 +74,10 @@ function setupAccessGuard(router: Router) {
|
|||||||
return {
|
return {
|
||||||
path: LOGIN_PATH,
|
path: LOGIN_PATH,
|
||||||
// 如不需要,直接删除 query
|
// 如不需要,直接删除 query
|
||||||
query: { redirect: encodeURIComponent(to.fullPath) },
|
query:
|
||||||
|
to.fullPath === DEFAULT_HOME_PATH
|
||||||
|
? {}
|
||||||
|
: { redirect: encodeURIComponent(to.fullPath) },
|
||||||
// 携带当前跳转的页面,登录后重新跳转该页面
|
// 携带当前跳转的页面,登录后重新跳转该页面
|
||||||
replace: true,
|
replace: true,
|
||||||
};
|
};
|
||||||
@@ -102,7 +107,10 @@ function setupAccessGuard(router: Router) {
|
|||||||
accessStore.setAccessMenus(accessibleMenus);
|
accessStore.setAccessMenus(accessibleMenus);
|
||||||
accessStore.setAccessRoutes(accessibleRoutes);
|
accessStore.setAccessRoutes(accessibleRoutes);
|
||||||
accessStore.setIsAccessChecked(true);
|
accessStore.setIsAccessChecked(true);
|
||||||
const redirectPath = (from.query.redirect ?? to.fullPath) as string;
|
const redirectPath = (from.query.redirect ??
|
||||||
|
(to.path === DEFAULT_HOME_PATH
|
||||||
|
? userInfo.homePath || DEFAULT_HOME_PATH
|
||||||
|
: to.fullPath)) as string;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...router.resolve(decodeURIComponent(redirectPath)),
|
...router.resolve(decodeURIComponent(redirectPath)),
|
||||||
|
|||||||
@@ -1,15 +1,22 @@
|
|||||||
import type { DictData } from '#/api/system/dict/dict-data-model';
|
|
||||||
|
|
||||||
import { dictDataInfo } from '#/api/system/dict/dict-data';
|
import { dictDataInfo } from '#/api/system/dict/dict-data';
|
||||||
import { type Option, useDictStore } from '#/store/dict';
|
import { useDictStore } from '#/store/dict';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 抽取公共逻辑的基础方法
|
||||||
|
* @param dictName 字典名称
|
||||||
|
* @param dataGetter 获取字典数据的函数
|
||||||
|
* @returns 数据
|
||||||
|
*/
|
||||||
|
function fetchAndCacheDictData<T>(
|
||||||
|
dictName: string,
|
||||||
|
dataGetter: () => T[],
|
||||||
|
): T[] {
|
||||||
|
const { dictRequestCache, setDictInfo } = useDictStore();
|
||||||
|
// 有调用方决定如何获取数据
|
||||||
|
const dataList = dataGetter();
|
||||||
|
|
||||||
// todo 重复代码的封装
|
|
||||||
export function getDict(dictName: string): DictData[] {
|
|
||||||
const { dictRequestCache, getDict, setDictInfo } = useDictStore();
|
|
||||||
// 这里拿到
|
|
||||||
const dictList = getDict(dictName);
|
|
||||||
// 检查请求状态缓存
|
// 检查请求状态缓存
|
||||||
if (dictList.length === 0 && !dictRequestCache.has(dictName)) {
|
if (dataList.length === 0 && !dictRequestCache.has(dictName)) {
|
||||||
dictRequestCache.set(
|
dictRequestCache.set(
|
||||||
dictName,
|
dictName,
|
||||||
dictDataInfo(dictName)
|
dictDataInfo(dictName)
|
||||||
@@ -20,31 +27,36 @@ export function getDict(dictName: string): DictData[] {
|
|||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
// 移除请求状态缓存
|
// 移除请求状态缓存
|
||||||
|
/**
|
||||||
|
* 这里主要判断字典item为空的情况(无奈兼容 不给字典item本来就是错误用法)
|
||||||
|
* 会导致if一直进入逻辑导致接口无限刷新
|
||||||
|
* 在这里dictList为空时 不删除缓存
|
||||||
|
*/
|
||||||
|
if (dataList.length > 0) {
|
||||||
dictRequestCache.delete(dictName);
|
dictRequestCache.delete(dictName);
|
||||||
|
}
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return dictList;
|
return dataList;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getDictOptions(dictName: string): Option[] {
|
/**
|
||||||
const { dictRequestCache, getDictOptions, setDictInfo } = useDictStore();
|
* 这里是提供给渲染标签使用的方法
|
||||||
const dictOptionList = getDictOptions(dictName);
|
* @param dictName 字典名称
|
||||||
// 检查请求状态缓存
|
* @returns 字典信息
|
||||||
if (dictOptionList.length === 0 && !dictRequestCache.has(dictName)) {
|
*/
|
||||||
dictRequestCache.set(
|
export function getDict(dictName: string) {
|
||||||
dictName,
|
const { getDict } = useDictStore();
|
||||||
dictDataInfo(dictName)
|
return fetchAndCacheDictData(dictName, () => getDict(dictName));
|
||||||
.then((resp) => {
|
|
||||||
// 缓存到store 这样就不用重复获取了
|
|
||||||
// 内部处理了push的逻辑 这里不用push
|
|
||||||
setDictInfo(dictName, resp);
|
|
||||||
})
|
|
||||||
.finally(() => {
|
|
||||||
// 移除请求状态缓存
|
|
||||||
dictRequestCache.delete(dictName);
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
return dictOptionList;
|
|
||||||
|
/**
|
||||||
|
* 一般是Select, Radio, Checkbox等组件使用
|
||||||
|
* @param dictName 字典名称
|
||||||
|
* @returns Options数组
|
||||||
|
*/
|
||||||
|
export function getDictOptions(dictName: string) {
|
||||||
|
const { getDictOptions } = useDictStore();
|
||||||
|
return fetchAndCacheDictData(dictName, () => getDictOptions(dictName));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import { useAuthStore } from '#/store';
|
|||||||
defineOptions({ name: 'CodeLogin' });
|
defineOptions({ name: 'CodeLogin' });
|
||||||
|
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
|
const CODE_LENGTH = 6;
|
||||||
|
|
||||||
const tenantInfo = ref<TenantResp>({
|
const tenantInfo = ref<TenantResp>({
|
||||||
tenantEnabled: false,
|
tenantEnabled: false,
|
||||||
@@ -98,7 +99,9 @@ const formSchema = computed((): VbenFormSchema[] => {
|
|||||||
},
|
},
|
||||||
fieldName: 'code',
|
fieldName: 'code',
|
||||||
label: $t('authentication.code'),
|
label: $t('authentication.code'),
|
||||||
rules: z.string().min(1, { message: $t('authentication.codeTip') }),
|
rules: z.string().length(CODE_LENGTH, {
|
||||||
|
message: $t('authentication.codeTip', [CODE_LENGTH]),
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -10,7 +10,9 @@ import { columns } from '#/views/monitor/online/data';
|
|||||||
const gridOptions: VxeGridProps = {
|
const gridOptions: VxeGridProps = {
|
||||||
columns,
|
columns,
|
||||||
keepSource: true,
|
keepSource: true,
|
||||||
pagerConfig: {},
|
pagerConfig: {
|
||||||
|
enabled: false,
|
||||||
|
},
|
||||||
proxyConfig: {
|
proxyConfig: {
|
||||||
ajax: {
|
ajax: {
|
||||||
query: async () => {
|
query: async () => {
|
||||||
|
|||||||
@@ -1,18 +1,34 @@
|
|||||||
import { defineComponent } from 'vue';
|
import { defineComponent } from 'vue';
|
||||||
|
|
||||||
import { Fallback } from '@vben/common-ui';
|
import { Page } from '@vben/common-ui';
|
||||||
|
import { openWindow } from '@vben/utils';
|
||||||
|
|
||||||
|
import { Result } from 'ant-design-vue';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'CommonSkeleton',
|
name: 'CommonSkeleton',
|
||||||
setup() {
|
setup() {
|
||||||
return () => (
|
return () => (
|
||||||
<div class="flex h-[600px] w-full items-center justify-center">
|
<Page autoContentHeight={true}>
|
||||||
<Fallback
|
<Result
|
||||||
description="等待后端重构工作流后开发"
|
status="success"
|
||||||
status="coming-soon"
|
sub-title="等待后端发布"
|
||||||
title="等待开发"
|
title="已经开发完毕(warmflow分支)"
|
||||||
/>
|
>
|
||||||
|
{{
|
||||||
|
extra: (
|
||||||
|
<div>
|
||||||
|
<a-button
|
||||||
|
onClick={() => openWindow('http://106.55.255.76')}
|
||||||
|
type="primary"
|
||||||
|
>
|
||||||
|
前往工作流版本演示站
|
||||||
|
</a-button>
|
||||||
</div>
|
</div>
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
</Result>
|
||||||
|
</Page>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,11 +1,8 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { onMounted, ref } from 'vue';
|
import type { EchartsUIType } from '@vben/plugins/echarts';
|
||||||
|
|
||||||
import {
|
import { EchartsUI, useEcharts } from '@vben/plugins/echarts';
|
||||||
EchartsUI,
|
import { onMounted, ref } from 'vue';
|
||||||
type EchartsUIType,
|
|
||||||
useEcharts,
|
|
||||||
} from '@vben/plugins/echarts';
|
|
||||||
|
|
||||||
const chartRef = ref<EchartsUIType>();
|
const chartRef = ref<EchartsUIType>();
|
||||||
const { renderEcharts } = useEcharts(chartRef);
|
const { renderEcharts } = useEcharts(chartRef);
|
||||||
|
|||||||
@@ -1,11 +1,8 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { onMounted, ref } from 'vue';
|
import type { EchartsUIType } from '@vben/plugins/echarts';
|
||||||
|
|
||||||
import {
|
import { EchartsUI, useEcharts } from '@vben/plugins/echarts';
|
||||||
EchartsUI,
|
import { onMounted, ref } from 'vue';
|
||||||
type EchartsUIType,
|
|
||||||
useEcharts,
|
|
||||||
} from '@vben/plugins/echarts';
|
|
||||||
|
|
||||||
const chartRef = ref<EchartsUIType>();
|
const chartRef = ref<EchartsUIType>();
|
||||||
const { renderEcharts } = useEcharts(chartRef);
|
const { renderEcharts } = useEcharts(chartRef);
|
||||||
|
|||||||
@@ -1,11 +1,8 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { onMounted, ref } from 'vue';
|
import type { EchartsUIType } from '@vben/plugins/echarts';
|
||||||
|
|
||||||
import {
|
import { EchartsUI, useEcharts } from '@vben/plugins/echarts';
|
||||||
EchartsUI,
|
import { onMounted, ref } from 'vue';
|
||||||
type EchartsUIType,
|
|
||||||
useEcharts,
|
|
||||||
} from '@vben/plugins/echarts';
|
|
||||||
|
|
||||||
const chartRef = ref<EchartsUIType>();
|
const chartRef = ref<EchartsUIType>();
|
||||||
const { renderEcharts } = useEcharts(chartRef);
|
const { renderEcharts } = useEcharts(chartRef);
|
||||||
|
|||||||
@@ -1,11 +1,8 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { onMounted, ref } from 'vue';
|
import type { EchartsUIType } from '@vben/plugins/echarts';
|
||||||
|
|
||||||
import {
|
import { EchartsUI, useEcharts } from '@vben/plugins/echarts';
|
||||||
EchartsUI,
|
import { onMounted, ref } from 'vue';
|
||||||
type EchartsUIType,
|
|
||||||
useEcharts,
|
|
||||||
} from '@vben/plugins/echarts';
|
|
||||||
|
|
||||||
const chartRef = ref<EchartsUIType>();
|
const chartRef = ref<EchartsUIType>();
|
||||||
const { renderEcharts } = useEcharts(chartRef);
|
const { renderEcharts } = useEcharts(chartRef);
|
||||||
|
|||||||
@@ -1,11 +1,8 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { onMounted, ref } from 'vue';
|
import type { EchartsUIType } from '@vben/plugins/echarts';
|
||||||
|
|
||||||
import {
|
import { EchartsUI, useEcharts } from '@vben/plugins/echarts';
|
||||||
EchartsUI,
|
import { onMounted, ref } from 'vue';
|
||||||
type EchartsUIType,
|
|
||||||
useEcharts,
|
|
||||||
} from '@vben/plugins/echarts';
|
|
||||||
|
|
||||||
const chartRef = ref<EchartsUIType>();
|
const chartRef = ref<EchartsUIType>();
|
||||||
const { renderEcharts } = useEcharts(chartRef);
|
const { renderEcharts } = useEcharts(chartRef);
|
||||||
|
|||||||
@@ -26,18 +26,22 @@ const gridOptions: VxeGridProps = {
|
|||||||
columns,
|
columns,
|
||||||
height: 'auto',
|
height: 'auto',
|
||||||
keepSource: true,
|
keepSource: true,
|
||||||
pagerConfig: {},
|
pagerConfig: {
|
||||||
|
enabled: false,
|
||||||
|
},
|
||||||
proxyConfig: {
|
proxyConfig: {
|
||||||
ajax: {
|
ajax: {
|
||||||
query: async ({ page }, formValues) => {
|
query: async (_, formValues) => {
|
||||||
return await onlineList({
|
return await onlineList({
|
||||||
pageNum: page.currentPage,
|
|
||||||
pageSize: page.pageSize,
|
|
||||||
...formValues,
|
...formValues,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
scrollY: {
|
||||||
|
enabled: true,
|
||||||
|
gt: 0,
|
||||||
|
},
|
||||||
rowConfig: {
|
rowConfig: {
|
||||||
isHover: true,
|
isHover: true,
|
||||||
keyField: 'tokenId',
|
keyField: 'tokenId',
|
||||||
@@ -51,11 +55,24 @@ async function handleForceOffline(row: Recordable<any>) {
|
|||||||
await forceLogout(row.tokenId);
|
await forceLogout(row.tokenId);
|
||||||
await tableApi.query();
|
await tableApi.query();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function onlineCount() {
|
||||||
|
return tableApi?.grid?.getData?.()?.length ?? 0;
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Page :auto-content-height="true">
|
<Page :auto-content-height="true">
|
||||||
<BasicTable table-title="在线用户列表">
|
<BasicTable>
|
||||||
|
<template #toolbar-actions>
|
||||||
|
<div class="mr-1 pl-1 text-[1rem]">
|
||||||
|
<div>
|
||||||
|
在线用户列表 (共
|
||||||
|
<span class="text-primary font-bold">{{ onlineCount() }}</span>
|
||||||
|
人在线)
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
<template #action="{ row }">
|
<template #action="{ row }">
|
||||||
<Popconfirm
|
<Popconfirm
|
||||||
:get-popup-container="getVxePopupContainer"
|
:get-popup-container="getVxePopupContainer"
|
||||||
|
|||||||
@@ -9,10 +9,10 @@ import { $t } from '@vben/locales';
|
|||||||
import { Modal, Space } from 'ant-design-vue';
|
import { Modal, Space } from 'ant-design-vue';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
addSortParams,
|
||||||
useVbenVxeGrid,
|
useVbenVxeGrid,
|
||||||
vxeCheckboxChecked,
|
vxeCheckboxChecked,
|
||||||
type VxeGridProps,
|
type VxeGridProps,
|
||||||
vxeSortEvent,
|
|
||||||
} from '#/adapter/vxe-table';
|
} from '#/adapter/vxe-table';
|
||||||
import {
|
import {
|
||||||
operLogClean,
|
operLogClean,
|
||||||
@@ -60,12 +60,14 @@ const gridOptions: VxeGridProps<OperationLog> = {
|
|||||||
pagerConfig: {},
|
pagerConfig: {},
|
||||||
proxyConfig: {
|
proxyConfig: {
|
||||||
ajax: {
|
ajax: {
|
||||||
query: async ({ page }, formValues = {}) => {
|
query: async ({ page, sorts }, formValues = {}) => {
|
||||||
const params: any = {
|
const params: any = {
|
||||||
pageNum: page.currentPage,
|
pageNum: page.currentPage,
|
||||||
pageSize: page.pageSize,
|
pageSize: page.pageSize,
|
||||||
...formValues,
|
...formValues,
|
||||||
};
|
};
|
||||||
|
// 添加排序参数
|
||||||
|
addSortParams(params, sorts);
|
||||||
return await operLogList(params);
|
return await operLogList(params);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -87,7 +89,8 @@ const [BasicTable, tableApi] = useVbenVxeGrid({
|
|||||||
formOptions,
|
formOptions,
|
||||||
gridOptions,
|
gridOptions,
|
||||||
gridEvents: {
|
gridEvents: {
|
||||||
sortChange: (sortParams) => vxeSortEvent(tableApi, sortParams),
|
// 排序 重新请求接口
|
||||||
|
sortChange: () => tableApi.query(),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -48,7 +48,12 @@ async function setupMenuSelect() {
|
|||||||
item.menuName = $t(item.menuName);
|
item.menuName = $t(item.menuName);
|
||||||
});
|
});
|
||||||
// const folderArray = menuArray.filter((item) => item.menuType === 'M');
|
// const folderArray = menuArray.filter((item) => item.menuType === 'M');
|
||||||
const menuTree = listToTree(menuArray, { id: 'menuId', pid: 'parentId' });
|
/**
|
||||||
|
* 这里需要过滤掉按钮类型
|
||||||
|
* 不允许在按钮下添加数据
|
||||||
|
*/
|
||||||
|
const filteredList = menuArray.filter((item) => item.menuType !== 'F');
|
||||||
|
const menuTree = listToTree(filteredList, { id: 'menuId', pid: 'parentId' });
|
||||||
const fullMenuTree = [
|
const fullMenuTree = [
|
||||||
{
|
{
|
||||||
menuId: 0,
|
menuId: 0,
|
||||||
|
|||||||
@@ -128,6 +128,12 @@ export const drawerSchema: FormSchemaGetter = () => [
|
|||||||
fieldName: 'domain',
|
fieldName: 'domain',
|
||||||
label: '自定义域名',
|
label: '自定义域名',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
component: 'Input',
|
||||||
|
fieldName: 'tip',
|
||||||
|
label: '占位作为提示使用',
|
||||||
|
hideLabel: true,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
component: 'Divider',
|
component: 'Divider',
|
||||||
componentProps: {
|
componentProps: {
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import { useVbenDrawer } from '@vben/common-ui';
|
|||||||
import { $t } from '@vben/locales';
|
import { $t } from '@vben/locales';
|
||||||
import { cloneDeep } from '@vben/utils';
|
import { cloneDeep } from '@vben/utils';
|
||||||
|
|
||||||
|
import { Alert } from 'ant-design-vue';
|
||||||
|
|
||||||
import { useVbenForm } from '#/adapter/form';
|
import { useVbenForm } from '#/adapter/form';
|
||||||
import {
|
import {
|
||||||
ossConfigAdd,
|
ossConfigAdd,
|
||||||
@@ -79,7 +81,17 @@ async function handleCancel() {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<BasicDrawer :close-on-click-modal="false" :title="title" class="w-[650px]">
|
<BasicDrawer :close-on-click-modal="false" :title="title" class="w-[650px]">
|
||||||
<BasicForm />
|
<BasicForm>
|
||||||
|
<template #tip>
|
||||||
|
<div class="ml-7 w-full">
|
||||||
|
<Alert
|
||||||
|
message="私有桶使用自定义域名无法预览, 但可以正常上传/下载"
|
||||||
|
show-icon
|
||||||
|
type="warning"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</BasicForm>
|
||||||
</BasicDrawer>
|
</BasicDrawer>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
@@ -19,10 +19,10 @@ import {
|
|||||||
} from 'ant-design-vue';
|
} from 'ant-design-vue';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
addSortParams,
|
||||||
useVbenVxeGrid,
|
useVbenVxeGrid,
|
||||||
vxeCheckboxChecked,
|
vxeCheckboxChecked,
|
||||||
type VxeGridProps,
|
type VxeGridProps,
|
||||||
vxeSortEvent,
|
|
||||||
} from '#/adapter/vxe-table';
|
} from '#/adapter/vxe-table';
|
||||||
import { configInfoByKey } from '#/api/system/config';
|
import { configInfoByKey } from '#/api/system/config';
|
||||||
import { ossDownload, ossList, ossRemove } from '#/api/system/oss';
|
import { ossDownload, ossList, ossRemove } from '#/api/system/oss';
|
||||||
@@ -66,12 +66,14 @@ const gridOptions: VxeGridProps = {
|
|||||||
pagerConfig: {},
|
pagerConfig: {},
|
||||||
proxyConfig: {
|
proxyConfig: {
|
||||||
ajax: {
|
ajax: {
|
||||||
query: async ({ page }, formValues = {}) => {
|
query: async ({ page, sorts }, formValues = {}) => {
|
||||||
const params: any = {
|
const params: any = {
|
||||||
pageNum: page.currentPage,
|
pageNum: page.currentPage,
|
||||||
pageSize: page.pageSize,
|
pageSize: page.pageSize,
|
||||||
...formValues,
|
...formValues,
|
||||||
};
|
};
|
||||||
|
// 添加排序参数
|
||||||
|
addSortParams(params, sorts);
|
||||||
return await ossList(params);
|
return await ossList(params);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -94,7 +96,8 @@ const [BasicTable, tableApi] = useVbenVxeGrid({
|
|||||||
formOptions,
|
formOptions,
|
||||||
gridOptions,
|
gridOptions,
|
||||||
gridEvents: {
|
gridEvents: {
|
||||||
sortChange: (sortParams) => vxeSortEvent(tableApi, sortParams),
|
// 排序 重新请求接口
|
||||||
|
sortChange: () => tableApi.query(),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -134,8 +137,8 @@ function handleToSettings() {
|
|||||||
|
|
||||||
const preview = ref(false);
|
const preview = ref(false);
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
const resp = await configInfoByKey('sys.oss.previewListResource');
|
const previewStr = await configInfoByKey('sys.oss.previewListResource');
|
||||||
preview.value = Boolean(resp);
|
preview.value = previewStr === 'true';
|
||||||
});
|
});
|
||||||
|
|
||||||
function isImageFile(ext: string) {
|
function isImageFile(ext: string) {
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import type { FormSchemaGetter } from '#/adapter/form';
|
||||||
import type { VxeGridProps } from '#/adapter/vxe-table';
|
import type { VxeGridProps } from '#/adapter/vxe-table';
|
||||||
|
|
||||||
import { DictEnum } from '@vben/constants';
|
import { DictEnum } from '@vben/constants';
|
||||||
@@ -5,7 +6,6 @@ import { getPopupContainer } from '@vben/utils';
|
|||||||
|
|
||||||
import { Tag } from 'ant-design-vue';
|
import { Tag } from 'ant-design-vue';
|
||||||
|
|
||||||
import { type FormSchemaGetter } from '#/adapter/form';
|
|
||||||
import { getDictOptions } from '#/utils/dict';
|
import { getDictOptions } from '#/utils/dict';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -17,6 +17,7 @@ export const authScopeOptions = [
|
|||||||
{ color: 'orange', label: '本部门数据权限', value: '3' },
|
{ color: 'orange', label: '本部门数据权限', value: '3' },
|
||||||
{ color: 'cyan', label: '本部门及以下数据权限', value: '4' },
|
{ color: 'cyan', label: '本部门及以下数据权限', value: '4' },
|
||||||
{ color: 'error', label: '仅本人数据权限', value: '5' },
|
{ color: 'error', label: '仅本人数据权限', value: '5' },
|
||||||
|
{ color: 'default', label: '部门及以下或本人数据权限', value: '6' },
|
||||||
];
|
];
|
||||||
|
|
||||||
export const querySchema: FormSchemaGetter = () => [
|
export const querySchema: FormSchemaGetter = () => [
|
||||||
|
|||||||
@@ -1,16 +1,14 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import type { VbenFormProps } from '@vben/common-ui';
|
||||||
import type { Recordable } from '@vben/types';
|
import type { Recordable } from '@vben/types';
|
||||||
|
|
||||||
|
import type { VxeGridProps } from '#/adapter/vxe-table';
|
||||||
|
|
||||||
import { computed } from 'vue';
|
import { computed } from 'vue';
|
||||||
import { useRouter } from 'vue-router';
|
import { useRouter } from 'vue-router';
|
||||||
|
|
||||||
import { useAccess } from '@vben/access';
|
import { useAccess } from '@vben/access';
|
||||||
import {
|
import { Page, useVbenDrawer, useVbenModal } from '@vben/common-ui';
|
||||||
Page,
|
|
||||||
useVbenDrawer,
|
|
||||||
useVbenModal,
|
|
||||||
type VbenFormProps,
|
|
||||||
} from '@vben/common-ui';
|
|
||||||
import { getVxePopupContainer } from '@vben/utils';
|
import { getVxePopupContainer } from '@vben/utils';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -22,11 +20,7 @@ import {
|
|||||||
Space,
|
Space,
|
||||||
} from 'ant-design-vue';
|
} from 'ant-design-vue';
|
||||||
|
|
||||||
import {
|
import { useVbenVxeGrid, vxeCheckboxChecked } from '#/adapter/vxe-table';
|
||||||
useVbenVxeGrid,
|
|
||||||
vxeCheckboxChecked,
|
|
||||||
type VxeGridProps,
|
|
||||||
} from '#/adapter/vxe-table';
|
|
||||||
import {
|
import {
|
||||||
roleChangeStatus,
|
roleChangeStatus,
|
||||||
roleExport,
|
roleExport,
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, ref } from 'vue';
|
import type { MenuOption } from '#/api/system/menu/model';
|
||||||
|
|
||||||
|
import { computed, nextTick, ref } from 'vue';
|
||||||
|
|
||||||
import { useVbenDrawer } from '@vben/common-ui';
|
import { useVbenDrawer } from '@vben/common-ui';
|
||||||
import { $t } from '@vben/locales';
|
import { $t } from '@vben/locales';
|
||||||
@@ -8,7 +10,7 @@ import { cloneDeep, eachTree } from '@vben/utils';
|
|||||||
import { useVbenForm } from '#/adapter/form';
|
import { useVbenForm } from '#/adapter/form';
|
||||||
import { menuTreeSelect, roleMenuTreeSelect } from '#/api/system/menu';
|
import { menuTreeSelect, roleMenuTreeSelect } from '#/api/system/menu';
|
||||||
import { roleAdd, roleInfo, roleUpdate } from '#/api/system/role';
|
import { roleAdd, roleInfo, roleUpdate } from '#/api/system/role';
|
||||||
import { TreeSelectPanel } from '#/components/tree';
|
import { MenuSelectTable } from '#/components/tree';
|
||||||
|
|
||||||
import { drawerSchema } from './data';
|
import { drawerSchema } from './data';
|
||||||
|
|
||||||
@@ -32,11 +34,10 @@ const [BasicForm, formApi] = useVbenForm({
|
|||||||
wrapperClass: 'grid-cols-2 gap-x-4',
|
wrapperClass: 'grid-cols-2 gap-x-4',
|
||||||
});
|
});
|
||||||
|
|
||||||
const menuTree = ref<any[]>([]);
|
const menuTree = ref<MenuOption[]>([]);
|
||||||
async function setupMenuTree(id?: number | string) {
|
async function setupMenuTree(id?: number | string) {
|
||||||
if (id) {
|
if (id) {
|
||||||
const resp = await roleMenuTreeSelect(id);
|
const resp = await roleMenuTreeSelect(id);
|
||||||
formApi.setFieldValue('menuIds', resp.checkedKeys);
|
|
||||||
const menus = resp.menus;
|
const menus = resp.menus;
|
||||||
// i18n处理
|
// i18n处理
|
||||||
eachTree(menus, (node) => {
|
eachTree(menus, (node) => {
|
||||||
@@ -44,15 +45,20 @@ async function setupMenuTree(id?: number | string) {
|
|||||||
});
|
});
|
||||||
// 设置菜单信息
|
// 设置菜单信息
|
||||||
menuTree.value = resp.menus;
|
menuTree.value = resp.menus;
|
||||||
|
// keys依赖于menu 需要先加载menu
|
||||||
|
await nextTick();
|
||||||
|
await formApi.setFieldValue('menuIds', resp.checkedKeys);
|
||||||
} else {
|
} else {
|
||||||
const resp = await menuTreeSelect();
|
const resp = await menuTreeSelect();
|
||||||
formApi.setFieldValue('menuIds', []);
|
|
||||||
// i18n处理
|
// i18n处理
|
||||||
eachTree(resp, (node) => {
|
eachTree(resp, (node) => {
|
||||||
node.label = $t(node.label);
|
node.label = $t(node.label);
|
||||||
});
|
});
|
||||||
// 设置菜单信息
|
// 设置菜单信息
|
||||||
menuTree.value = resp;
|
menuTree.value = resp;
|
||||||
|
// keys依赖于menu 需要先加载menu
|
||||||
|
await nextTick();
|
||||||
|
await formApi.setFieldValue('menuIds', []);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -78,11 +84,7 @@ const [BasicDrawer, drawerApi] = useVbenDrawer({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
const menuSelectRef = ref<InstanceType<typeof MenuSelectTable>>();
|
||||||
* 这里拿到的是一个数组ref
|
|
||||||
*/
|
|
||||||
const menuSelectRef = ref();
|
|
||||||
|
|
||||||
async function handleConfirm() {
|
async function handleConfirm() {
|
||||||
try {
|
try {
|
||||||
drawerApi.drawerLoading(true);
|
drawerApi.drawerLoading(true);
|
||||||
@@ -91,7 +93,7 @@ async function handleConfirm() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// 这个用于提交
|
// 这个用于提交
|
||||||
const menuIds = menuSelectRef.value?.[0]?.getCheckedKeys() ?? [];
|
const menuIds = menuSelectRef.value?.getCheckedKeys?.() ?? [];
|
||||||
// formApi.getValues拿到的是一个readonly对象,不能直接修改,需要cloneDeep
|
// formApi.getValues拿到的是一个readonly对象,不能直接修改,需要cloneDeep
|
||||||
const data = cloneDeep(await formApi.getValues());
|
const data = cloneDeep(await formApi.getValues());
|
||||||
data.menuIds = menuIds;
|
data.menuIds = menuIds;
|
||||||
@@ -120,17 +122,19 @@ function handleMenuCheckStrictlyChange(value: boolean) {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<BasicDrawer :close-on-click-modal="false" :title="title" class="w-[600px]">
|
<BasicDrawer :close-on-click-modal="false" :title="title" class="w-[800px]">
|
||||||
<BasicForm>
|
<BasicForm>
|
||||||
<template #menuIds="slotProps">
|
<template #menuIds="slotProps">
|
||||||
<!-- check-strictly为readonly 不能通过v-model绑定 -->
|
<div class="h-[600px] w-full">
|
||||||
<TreeSelectPanel
|
<!-- association为readonly 不能通过v-model绑定 -->
|
||||||
|
<MenuSelectTable
|
||||||
ref="menuSelectRef"
|
ref="menuSelectRef"
|
||||||
v-bind="slotProps"
|
:checked-keys="slotProps.value"
|
||||||
:check-strictly="formApi.form.values.menuCheckStrictly"
|
:association="formApi.form.values.menuCheckStrictly"
|
||||||
:tree-data="menuTree"
|
:menus="menuTree"
|
||||||
@check-strictly-change="handleMenuCheckStrictlyChange"
|
@update:association="handleMenuCheckStrictlyChange"
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</BasicForm>
|
</BasicForm>
|
||||||
</BasicDrawer>
|
</BasicDrawer>
|
||||||
|
|||||||
@@ -1,23 +1,24 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, ref } from 'vue';
|
import type { MenuOption } from '#/api/system/menu/model';
|
||||||
|
|
||||||
|
import { computed, nextTick, ref } from 'vue';
|
||||||
|
|
||||||
import { useVbenDrawer } from '@vben/common-ui';
|
import { useVbenDrawer } from '@vben/common-ui';
|
||||||
import { $t } from '@vben/locales';
|
import { $t } from '@vben/locales';
|
||||||
import { cloneDeep, eachTree, listToTree } from '@vben/utils';
|
import { cloneDeep, eachTree } from '@vben/utils';
|
||||||
|
|
||||||
import { omit } from 'lodash-es';
|
import { omit } from 'lodash-es';
|
||||||
|
|
||||||
import { useVbenForm } from '#/adapter/form';
|
import { useVbenForm } from '#/adapter/form';
|
||||||
import { menuList, tenantPackageMenuTreeSelect } from '#/api/system/menu';
|
import { menuTreeSelect, tenantPackageMenuTreeSelect } from '#/api/system/menu';
|
||||||
import {
|
import {
|
||||||
packageAdd,
|
packageAdd,
|
||||||
packageInfo,
|
packageInfo,
|
||||||
packageUpdate,
|
packageUpdate,
|
||||||
} from '#/api/system/tenant-package';
|
} from '#/api/system/tenant-package';
|
||||||
import { TreeSelectPanel } from '#/components/tree';
|
import { MenuSelectTable } from '#/components/tree';
|
||||||
|
|
||||||
import { drawerSchema } from './data';
|
import { drawerSchema } from './data';
|
||||||
import TreeItem from './tree-item';
|
|
||||||
|
|
||||||
const emit = defineEmits<{ reload: [] }>();
|
const emit = defineEmits<{ reload: [] }>();
|
||||||
|
|
||||||
@@ -36,23 +37,32 @@ const [BasicForm, formApi] = useVbenForm({
|
|||||||
wrapperClass: 'grid-cols-2',
|
wrapperClass: 'grid-cols-2',
|
||||||
});
|
});
|
||||||
|
|
||||||
async function setupMenuTreeSelect(id?: number | string) {
|
const menuTree = ref<MenuOption[]>([]);
|
||||||
|
async function setupMenuTree(id?: number | string) {
|
||||||
if (id) {
|
if (id) {
|
||||||
const resp = await tenantPackageMenuTreeSelect(id);
|
const resp = await tenantPackageMenuTreeSelect(id);
|
||||||
await formApi.setFieldValue('menuIds', resp.checkedKeys);
|
const menus = resp.menus;
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const menuTree = ref<any[]>([]);
|
|
||||||
async function setupMenuTree() {
|
|
||||||
const resp = await menuList();
|
|
||||||
const treeData = listToTree(resp, { id: 'menuId' });
|
|
||||||
// i18n处理
|
// i18n处理
|
||||||
eachTree(treeData, (node) => {
|
eachTree(menus, (node) => {
|
||||||
node.menuName = $t(node.menuName);
|
node.label = $t(node.label);
|
||||||
});
|
});
|
||||||
// 设置菜单信息
|
// 设置菜单信息
|
||||||
menuTree.value = treeData;
|
menuTree.value = resp.menus;
|
||||||
|
// keys依赖于menu 需要先加载menu
|
||||||
|
await nextTick();
|
||||||
|
await formApi.setFieldValue('menuIds', resp.checkedKeys);
|
||||||
|
} else {
|
||||||
|
const resp = await menuTreeSelect();
|
||||||
|
// i18n处理
|
||||||
|
eachTree(resp, (node) => {
|
||||||
|
node.label = $t(node.label);
|
||||||
|
});
|
||||||
|
// 设置菜单信息
|
||||||
|
menuTree.value = resp;
|
||||||
|
// keys依赖于menu 需要先加载menu
|
||||||
|
await nextTick();
|
||||||
|
await formApi.setFieldValue('menuIds', []);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const [BasicDrawer, drawerApi] = useVbenDrawer({
|
const [BasicDrawer, drawerApi] = useVbenDrawer({
|
||||||
@@ -72,20 +82,14 @@ const [BasicDrawer, drawerApi] = useVbenDrawer({
|
|||||||
// 通过setupMenuTreeSelect设置
|
// 通过setupMenuTreeSelect设置
|
||||||
await formApi.setValues(omit(record, ['menuIds']));
|
await formApi.setValues(omit(record, ['menuIds']));
|
||||||
}
|
}
|
||||||
/**
|
// init菜单 注意顺序要放在赋值record之后 内部watch会依赖record
|
||||||
* 加载菜单树和已勾选菜单
|
await setupMenuTree(id);
|
||||||
*/
|
|
||||||
await Promise.all([setupMenuTree(), setupMenuTreeSelect(id)]);
|
|
||||||
|
|
||||||
drawerApi.drawerLoading(false);
|
drawerApi.drawerLoading(false);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
const menuSelectRef = ref<InstanceType<typeof MenuSelectTable>>();
|
||||||
* 这里拿到的是一个数组ref
|
|
||||||
*/
|
|
||||||
const menuSelectRef = ref();
|
|
||||||
|
|
||||||
async function handleConfirm() {
|
async function handleConfirm() {
|
||||||
try {
|
try {
|
||||||
drawerApi.drawerLoading(true);
|
drawerApi.drawerLoading(true);
|
||||||
@@ -94,7 +98,7 @@ async function handleConfirm() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// 这个用于提交
|
// 这个用于提交
|
||||||
const menuIds = menuSelectRef.value?.[0]?.getCheckedKeys() ?? [];
|
const menuIds = menuSelectRef.value?.getCheckedKeys?.() ?? [];
|
||||||
// formApi.getValues拿到的是一个readonly对象,不能直接修改,需要cloneDeep
|
// formApi.getValues拿到的是一个readonly对象,不能直接修改,需要cloneDeep
|
||||||
const data = cloneDeep(await formApi.getValues());
|
const data = cloneDeep(await formApi.getValues());
|
||||||
data.menuIds = menuIds;
|
data.menuIds = menuIds;
|
||||||
@@ -123,22 +127,19 @@ function handleMenuCheckStrictlyChange(value: boolean) {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<BasicDrawer :close-on-click-modal="false" :title="title" class="w-[600px]">
|
<BasicDrawer :close-on-click-modal="false" :title="title" class="w-[800px]">
|
||||||
<BasicForm>
|
<BasicForm>
|
||||||
<template #menuIds="slotProps">
|
<template #menuIds="slotProps">
|
||||||
<TreeSelectPanel
|
<div class="h-[600px] w-full">
|
||||||
|
<!-- association为readonly 不能通过v-model绑定 -->
|
||||||
|
<MenuSelectTable
|
||||||
ref="menuSelectRef"
|
ref="menuSelectRef"
|
||||||
v-bind="slotProps"
|
:checked-keys="slotProps.value"
|
||||||
:check-strictly="formApi.form.values.menuCheckStrictly"
|
:association="formApi.form.values.menuCheckStrictly"
|
||||||
:expand-all-on-init="false"
|
:menus="menuTree"
|
||||||
:field-names="{ title: 'menuName', key: 'menuId' }"
|
@update:association="handleMenuCheckStrictlyChange"
|
||||||
:tree-data="menuTree"
|
/>
|
||||||
@check-strictly-change="handleMenuCheckStrictlyChange"
|
</div>
|
||||||
>
|
|
||||||
<template #title="data">
|
|
||||||
<TreeItem :data="data" />
|
|
||||||
</template>
|
|
||||||
</TreeSelectPanel>
|
|
||||||
</template>
|
</template>
|
||||||
</BasicForm>
|
</BasicForm>
|
||||||
</BasicDrawer>
|
</BasicDrawer>
|
||||||
|
|||||||
@@ -1,14 +1,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { Role } from '#/api/system/user/model';
|
import type { Role } from '#/api/system/user/model';
|
||||||
|
|
||||||
import { computed, h, ref } from 'vue';
|
|
||||||
|
|
||||||
import { useVbenDrawer } from '@vben/common-ui';
|
|
||||||
import { $t } from '@vben/locales';
|
|
||||||
import { addFullName, cloneDeep, getPopupContainer } from '@vben/utils';
|
|
||||||
|
|
||||||
import { Tag } from 'ant-design-vue';
|
|
||||||
|
|
||||||
import { useVbenForm } from '#/adapter/form';
|
import { useVbenForm } from '#/adapter/form';
|
||||||
import { configInfoByKey } from '#/api/system/config';
|
import { configInfoByKey } from '#/api/system/config';
|
||||||
import { postOptionSelect } from '#/api/system/post';
|
import { postOptionSelect } from '#/api/system/post';
|
||||||
@@ -19,6 +11,11 @@ import {
|
|||||||
userUpdate,
|
userUpdate,
|
||||||
} from '#/api/system/user';
|
} from '#/api/system/user';
|
||||||
import { authScopeOptions } from '#/views/system/role/data';
|
import { authScopeOptions } from '#/views/system/role/data';
|
||||||
|
import { useVbenDrawer } from '@vben/common-ui';
|
||||||
|
import { $t } from '@vben/locales';
|
||||||
|
import { addFullName, cloneDeep, getPopupContainer } from '@vben/utils';
|
||||||
|
import { Tag } from 'ant-design-vue';
|
||||||
|
import { computed, h, onMounted, ref } from 'vue';
|
||||||
|
|
||||||
import { drawerSchema } from './data';
|
import { drawerSchema } from './data';
|
||||||
|
|
||||||
@@ -117,13 +114,20 @@ async function setupDeptSelect() {
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const defaultPassword = ref('');
|
||||||
|
onMounted(async () => {
|
||||||
|
const password = await configInfoByKey('sys.user.initPassword');
|
||||||
|
if (password) {
|
||||||
|
defaultPassword.value = password;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 新增时候 从参数设置获取默认密码
|
* 新增时候 从参数设置获取默认密码
|
||||||
*/
|
*/
|
||||||
async function loadDefaultPassword(update: boolean) {
|
async function loadDefaultPassword(update: boolean) {
|
||||||
if (!update) {
|
if (!update && defaultPassword.value) {
|
||||||
const defaultPassword = await configInfoByKey('sys.user.initPassword');
|
formApi.setFieldValue('password', defaultPassword.value);
|
||||||
defaultPassword && formApi.setFieldValue('password', defaultPassword);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
27
apps/web-antd/src/views/演示使用自行删除/menu/index.vue
Normal file
27
apps/web-antd/src/views/演示使用自行删除/menu/index.vue
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { MenuOption } from '#/api/system/menu/model';
|
||||||
|
|
||||||
|
import { roleMenuTreeSelect } from '#/api/system/menu';
|
||||||
|
import { MenuSelectTable } from '#/components/tree';
|
||||||
|
import { Page } from '@vben/common-ui';
|
||||||
|
import { onMounted, ref, shallowRef } from 'vue';
|
||||||
|
|
||||||
|
const checkedKeys = ref<number[]>([]);
|
||||||
|
const menus = shallowRef<MenuOption[]>([]);
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
const resp = await roleMenuTreeSelect(3);
|
||||||
|
menus.value = resp.menus;
|
||||||
|
checkedKeys.value = resp.checkedKeys;
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Page :auto-content-height="true">
|
||||||
|
<MenuSelectTable
|
||||||
|
:menus="menus"
|
||||||
|
v-model:checked-keys="checkedKeys"
|
||||||
|
:association="true"
|
||||||
|
/>
|
||||||
|
</Page>
|
||||||
|
</template>
|
||||||
@@ -1,11 +1,8 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from 'vue';
|
|
||||||
|
|
||||||
import { JsonPreview, Page } from '@vben/common-ui';
|
|
||||||
|
|
||||||
import { Alert, RadioGroup } from 'ant-design-vue';
|
|
||||||
|
|
||||||
import { FileUpload, ImageUpload } from '#/components/upload';
|
import { FileUpload, ImageUpload } from '#/components/upload';
|
||||||
|
import { JsonPreview, Page } from '@vben/common-ui';
|
||||||
|
import { Alert, RadioGroup } from 'ant-design-vue';
|
||||||
|
import { ref } from 'vue';
|
||||||
|
|
||||||
const resultField = ref<'ossId' | 'url'>('ossId');
|
const resultField = ref<'ossId' | 'url'>('ossId');
|
||||||
|
|
||||||
@@ -17,7 +14,7 @@ const fieldOptions = [
|
|||||||
];
|
];
|
||||||
const fileAccept = ['xlsx', 'word', 'pdf'];
|
const fileAccept = ['xlsx', 'word', 'pdf'];
|
||||||
|
|
||||||
const signleImage = ref<string>('');
|
const signleImage = ref<string>('1745443704356782081');
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -27,7 +24,11 @@ const signleImage = ref<string>('');
|
|||||||
:show-icon="true"
|
:show-icon="true"
|
||||||
message="新特性: 设置max-number为1时, 会被绑定为string而非string[]类型 省去手动转换"
|
message="新特性: 设置max-number为1时, 会被绑定为string而非string[]类型 省去手动转换"
|
||||||
/>
|
/>
|
||||||
<ImageUpload v-model:value="signleImage" :max-number="1" />
|
<ImageUpload
|
||||||
|
v-model:value="signleImage"
|
||||||
|
:max-number="1"
|
||||||
|
result-field="ossId"
|
||||||
|
/>
|
||||||
<JsonPreview :data="signleImage" />
|
<JsonPreview :data="signleImage" />
|
||||||
</div>
|
</div>
|
||||||
<div class="bg-background flex flex-col gap-[12px] rounded-lg p-6">
|
<div class="bg-background flex flex-col gap-[12px] rounded-lg p-6">
|
||||||
|
|||||||
234
apps/web-antd/src/views/演示使用自行删除/vxe/edit-table.vue
Normal file
234
apps/web-antd/src/views/演示使用自行删除/vxe/edit-table.vue
Normal file
@@ -0,0 +1,234 @@
|
|||||||
|
<script setup lang="tsx">
|
||||||
|
import type { VxeGridProps } from '#/adapter/vxe-table';
|
||||||
|
|
||||||
|
import { nextTick, onMounted } from 'vue';
|
||||||
|
|
||||||
|
import { JsonPreview } from '@vben/common-ui';
|
||||||
|
import { getPopupContainer } from '@vben/utils';
|
||||||
|
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Input,
|
||||||
|
InputNumber,
|
||||||
|
message,
|
||||||
|
Modal,
|
||||||
|
Select,
|
||||||
|
Space,
|
||||||
|
} from 'ant-design-vue';
|
||||||
|
|
||||||
|
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||||
|
|
||||||
|
const gridOptions: VxeGridProps = {
|
||||||
|
editConfig: {
|
||||||
|
// 触发编辑的方式
|
||||||
|
trigger: 'click',
|
||||||
|
// 触发编辑的模式
|
||||||
|
mode: 'row',
|
||||||
|
showStatus: true,
|
||||||
|
},
|
||||||
|
border: true,
|
||||||
|
rowConfig: {
|
||||||
|
drag: true,
|
||||||
|
},
|
||||||
|
checkboxConfig: {},
|
||||||
|
editRules: {
|
||||||
|
name: [{ required: true, message: '请输入姓名', trigger: 'blur' }],
|
||||||
|
age: [
|
||||||
|
{ required: true, message: '请输入年龄', trigger: 'blur' },
|
||||||
|
{ min: 0, max: 200, message: '年龄必须为1-200' },
|
||||||
|
],
|
||||||
|
job: [{ required: true, message: '请选择工作', trigger: 'blur' }],
|
||||||
|
},
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
type: 'checkbox',
|
||||||
|
width: 60,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
dragSort: true,
|
||||||
|
title: '排序',
|
||||||
|
width: 60,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'name',
|
||||||
|
title: '姓名',
|
||||||
|
align: 'left',
|
||||||
|
editRender: {},
|
||||||
|
slots: {
|
||||||
|
default: ({ row }) => {
|
||||||
|
if (!row.name) {
|
||||||
|
return <span class="text-red-500">未填写</span>;
|
||||||
|
}
|
||||||
|
return <span>{row.name}</span>;
|
||||||
|
},
|
||||||
|
edit: ({ row }) => {
|
||||||
|
return <Input placeholder={'请输入'} v-model:value={row.name} />;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'age',
|
||||||
|
title: '年龄',
|
||||||
|
align: 'left',
|
||||||
|
editRender: {},
|
||||||
|
slots: {
|
||||||
|
default: ({ row }) => {
|
||||||
|
if (!row.age) {
|
||||||
|
return <span class="text-red-500">未填写</span>;
|
||||||
|
}
|
||||||
|
return <span>{row.age}</span>;
|
||||||
|
},
|
||||||
|
edit: ({ row }) => {
|
||||||
|
return (
|
||||||
|
<InputNumber
|
||||||
|
class="w-full"
|
||||||
|
placeholder={'请输入'}
|
||||||
|
v-model:value={row.age}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: '工作',
|
||||||
|
title: 'job',
|
||||||
|
align: 'left',
|
||||||
|
editRender: {},
|
||||||
|
slots: {
|
||||||
|
default: ({ row }) => {
|
||||||
|
if (!row.job) {
|
||||||
|
return <span class="text-red-500">未选择</span>;
|
||||||
|
}
|
||||||
|
return <span>{row.job}</span>;
|
||||||
|
},
|
||||||
|
edit: ({ row }) => {
|
||||||
|
const options = ['前端佬', '后端佬', '组长'].map((item) => ({
|
||||||
|
label: item,
|
||||||
|
value: item,
|
||||||
|
}));
|
||||||
|
return (
|
||||||
|
<Select
|
||||||
|
class="w-full"
|
||||||
|
getPopupContainer={getPopupContainer}
|
||||||
|
options={options}
|
||||||
|
placeholder={'请选择'}
|
||||||
|
v-model:value={row.job}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'action',
|
||||||
|
title: '操作',
|
||||||
|
width: 100,
|
||||||
|
slots: {
|
||||||
|
default: ({ $table, row }) => {
|
||||||
|
function handleDelete() {
|
||||||
|
$table.remove(row);
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<Button danger={true} onClick={handleDelete} size={'small'}>
|
||||||
|
删除
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
height: 'auto',
|
||||||
|
keepSource: true,
|
||||||
|
pagerConfig: {
|
||||||
|
enabled: false,
|
||||||
|
},
|
||||||
|
proxyConfig: {
|
||||||
|
enabled: false,
|
||||||
|
},
|
||||||
|
toolbarConfig: {
|
||||||
|
// 自定义列
|
||||||
|
custom: false,
|
||||||
|
// 最大化
|
||||||
|
zoom: false,
|
||||||
|
// 刷新
|
||||||
|
refresh: false,
|
||||||
|
},
|
||||||
|
showOverflow: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
const [BasicTable, tableApi] = useVbenVxeGrid({
|
||||||
|
gridOptions,
|
||||||
|
});
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
const data = [
|
||||||
|
{
|
||||||
|
name: '张三',
|
||||||
|
age: 18,
|
||||||
|
job: '前端佬',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '李四',
|
||||||
|
age: 19,
|
||||||
|
job: '后端佬',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '王五',
|
||||||
|
age: 20,
|
||||||
|
job: '组长',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
await nextTick();
|
||||||
|
await tableApi.grid.loadData(data);
|
||||||
|
});
|
||||||
|
async function handleAdd() {
|
||||||
|
const record = { name: '', age: undefined, job: undefined };
|
||||||
|
const { row: newRow } = await tableApi.grid.insert(record);
|
||||||
|
await tableApi.grid.setEditCell(newRow, 'name');
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleRemove() {
|
||||||
|
await tableApi.grid.removeCheckboxRow();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleValidate() {
|
||||||
|
const result = await tableApi.grid.validate(true);
|
||||||
|
if (result) {
|
||||||
|
message.error('校验失败');
|
||||||
|
} else {
|
||||||
|
message.success('校验成功');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getData() {
|
||||||
|
const data = tableApi.grid.getTableData();
|
||||||
|
const { fullData } = data;
|
||||||
|
console.log(fullData);
|
||||||
|
Modal.info({
|
||||||
|
title: '提示',
|
||||||
|
content: (
|
||||||
|
<div class="max-h-[350px] overflow-y-auto">
|
||||||
|
<JsonPreview data={fullData} />
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<BasicTable>
|
||||||
|
<template #toolbar-tools>
|
||||||
|
<Space>
|
||||||
|
<a-button @click="getData">获取表格数据</a-button>
|
||||||
|
<a-button @click="handleValidate">校验</a-button>
|
||||||
|
<a-button danger @click="handleRemove"> 删除勾选 </a-button>
|
||||||
|
<a-button
|
||||||
|
type="primary"
|
||||||
|
v-access:code="['system:config:add']"
|
||||||
|
@click="handleAdd"
|
||||||
|
>
|
||||||
|
{{ $t('pages.common.add') }}
|
||||||
|
</a-button>
|
||||||
|
</Space>
|
||||||
|
</template>
|
||||||
|
</BasicTable>
|
||||||
|
</template>
|
||||||
17
apps/web-antd/src/views/演示使用自行删除/vxe/index.vue
Normal file
17
apps/web-antd/src/views/演示使用自行删除/vxe/index.vue
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { Page } from '@vben/common-ui';
|
||||||
|
|
||||||
|
import { Card } from 'ant-design-vue';
|
||||||
|
|
||||||
|
import EditTable from './edit-table.vue';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Page>
|
||||||
|
<div class="flex flex-col gap-4">
|
||||||
|
<Card title="可编辑表格" size="small">
|
||||||
|
<EditTable class="h-[500px]" />
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
</Page>
|
||||||
|
</template>
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@vben/web-ele",
|
"name": "@vben/web-ele",
|
||||||
"version": "5.5.0",
|
"version": "5.5.2",
|
||||||
"homepage": "https://vben.pro",
|
"homepage": "https://vben.pro",
|
||||||
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
|
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
|
||||||
"repository": {
|
"repository": {
|
||||||
|
|||||||
@@ -5,13 +5,10 @@
|
|||||||
|
|
||||||
import type { BaseFormComponentType } from '@vben/common-ui';
|
import type { BaseFormComponentType } from '@vben/common-ui';
|
||||||
import type { Recordable } from '@vben/types';
|
import type { Recordable } from '@vben/types';
|
||||||
|
|
||||||
import type { Component, SetupContext } from 'vue';
|
import type { Component, SetupContext } from 'vue';
|
||||||
import { h } from 'vue';
|
|
||||||
|
|
||||||
import { ApiComponent, globalShareState, IconPicker } from '@vben/common-ui';
|
import { ApiComponent, globalShareState, IconPicker } from '@vben/common-ui';
|
||||||
import { $t } from '@vben/locales';
|
import { $t } from '@vben/locales';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
ElButton,
|
ElButton,
|
||||||
ElCheckbox,
|
ElCheckbox,
|
||||||
@@ -32,6 +29,7 @@ import {
|
|||||||
ElTreeSelect,
|
ElTreeSelect,
|
||||||
ElUpload,
|
ElUpload,
|
||||||
} from 'element-plus';
|
} from 'element-plus';
|
||||||
|
import { h } from 'vue';
|
||||||
|
|
||||||
const withDefaultPlaceholder = <T extends Component>(
|
const withDefaultPlaceholder = <T extends Component>(
|
||||||
component: T,
|
component: T,
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
import type { LocaleSetupOptions, SupportedLanguagesType } from '@vben/locales';
|
import type { LocaleSetupOptions, SupportedLanguagesType } from '@vben/locales';
|
||||||
import type { Language } from 'element-plus/es/locale';
|
import type { Language } from 'element-plus/es/locale';
|
||||||
|
|
||||||
import type { App } from 'vue';
|
import type { App } from 'vue';
|
||||||
import { ref } from 'vue';
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
$t,
|
$t,
|
||||||
@@ -10,10 +8,10 @@ import {
|
|||||||
loadLocalesMapFromDir,
|
loadLocalesMapFromDir,
|
||||||
} from '@vben/locales';
|
} from '@vben/locales';
|
||||||
import { preferences } from '@vben/preferences';
|
import { preferences } from '@vben/preferences';
|
||||||
|
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import enLocale from 'element-plus/es/locale/lang/en';
|
import enLocale from 'element-plus/es/locale/lang/en';
|
||||||
import defaultLocale from 'element-plus/es/locale/lang/zh-cn';
|
import defaultLocale from 'element-plus/es/locale/lang/zh-cn';
|
||||||
|
import { ref } from 'vue';
|
||||||
|
|
||||||
const elementLocale = ref<Language>(defaultLocale);
|
const elementLocale = ref<Language>(defaultLocale);
|
||||||
|
|
||||||
|
|||||||
@@ -54,7 +54,9 @@ function setupAccessGuard(router: Router) {
|
|||||||
if (coreRouteNames.includes(to.name as string)) {
|
if (coreRouteNames.includes(to.name as string)) {
|
||||||
if (to.path === LOGIN_PATH && accessStore.accessToken) {
|
if (to.path === LOGIN_PATH && accessStore.accessToken) {
|
||||||
return decodeURIComponent(
|
return decodeURIComponent(
|
||||||
(to.query?.redirect as string) || DEFAULT_HOME_PATH,
|
(to.query?.redirect as string) ||
|
||||||
|
userStore.userInfo?.homePath ||
|
||||||
|
DEFAULT_HOME_PATH,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
@@ -72,7 +74,10 @@ function setupAccessGuard(router: Router) {
|
|||||||
return {
|
return {
|
||||||
path: LOGIN_PATH,
|
path: LOGIN_PATH,
|
||||||
// 如不需要,直接删除 query
|
// 如不需要,直接删除 query
|
||||||
query: { redirect: encodeURIComponent(to.fullPath) },
|
query:
|
||||||
|
to.fullPath === DEFAULT_HOME_PATH
|
||||||
|
? {}
|
||||||
|
: { redirect: encodeURIComponent(to.fullPath) },
|
||||||
// 携带当前跳转的页面,登录后重新跳转该页面
|
// 携带当前跳转的页面,登录后重新跳转该页面
|
||||||
replace: true,
|
replace: true,
|
||||||
};
|
};
|
||||||
@@ -102,7 +107,10 @@ function setupAccessGuard(router: Router) {
|
|||||||
accessStore.setAccessMenus(accessibleMenus);
|
accessStore.setAccessMenus(accessibleMenus);
|
||||||
accessStore.setAccessRoutes(accessibleRoutes);
|
accessStore.setAccessRoutes(accessibleRoutes);
|
||||||
accessStore.setIsAccessChecked(true);
|
accessStore.setIsAccessChecked(true);
|
||||||
const redirectPath = (from.query.redirect ?? to.fullPath) as string;
|
const redirectPath = (from.query.redirect ??
|
||||||
|
(to.path === DEFAULT_HOME_PATH
|
||||||
|
? userInfo.homePath || DEFAULT_HOME_PATH
|
||||||
|
: to.fullPath)) as string;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...router.resolve(decodeURIComponent(redirectPath)),
|
...router.resolve(decodeURIComponent(redirectPath)),
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import { $t } from '@vben/locales';
|
|||||||
defineOptions({ name: 'CodeLogin' });
|
defineOptions({ name: 'CodeLogin' });
|
||||||
|
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
|
const CODE_LENGTH = 6;
|
||||||
|
|
||||||
const formSchema = computed((): VbenFormSchema[] => {
|
const formSchema = computed((): VbenFormSchema[] => {
|
||||||
return [
|
return [
|
||||||
@@ -30,6 +31,7 @@ const formSchema = computed((): VbenFormSchema[] => {
|
|||||||
{
|
{
|
||||||
component: 'VbenPinInput',
|
component: 'VbenPinInput',
|
||||||
componentProps: {
|
componentProps: {
|
||||||
|
codeLength: CODE_LENGTH,
|
||||||
createText: (countdown: number) => {
|
createText: (countdown: number) => {
|
||||||
const text =
|
const text =
|
||||||
countdown > 0
|
countdown > 0
|
||||||
@@ -41,7 +43,9 @@ const formSchema = computed((): VbenFormSchema[] => {
|
|||||||
},
|
},
|
||||||
fieldName: 'code',
|
fieldName: 'code',
|
||||||
label: $t('authentication.code'),
|
label: $t('authentication.code'),
|
||||||
rules: z.string().min(1, { message: $t('authentication.codeTip') }),
|
rules: z.string().length(CODE_LENGTH, {
|
||||||
|
message: $t('authentication.codeTip', [CODE_LENGTH]),
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,11 +1,8 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { onMounted, ref } from 'vue';
|
import type { EchartsUIType } from '@vben/plugins/echarts';
|
||||||
|
|
||||||
import {
|
import { EchartsUI, useEcharts } from '@vben/plugins/echarts';
|
||||||
EchartsUI,
|
import { onMounted, ref } from 'vue';
|
||||||
type EchartsUIType,
|
|
||||||
useEcharts,
|
|
||||||
} from '@vben/plugins/echarts';
|
|
||||||
|
|
||||||
const chartRef = ref<EchartsUIType>();
|
const chartRef = ref<EchartsUIType>();
|
||||||
const { renderEcharts } = useEcharts(chartRef);
|
const { renderEcharts } = useEcharts(chartRef);
|
||||||
|
|||||||
@@ -1,11 +1,8 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { onMounted, ref } from 'vue';
|
import type { EchartsUIType } from '@vben/plugins/echarts';
|
||||||
|
|
||||||
import {
|
import { EchartsUI, useEcharts } from '@vben/plugins/echarts';
|
||||||
EchartsUI,
|
import { onMounted, ref } from 'vue';
|
||||||
type EchartsUIType,
|
|
||||||
useEcharts,
|
|
||||||
} from '@vben/plugins/echarts';
|
|
||||||
|
|
||||||
const chartRef = ref<EchartsUIType>();
|
const chartRef = ref<EchartsUIType>();
|
||||||
const { renderEcharts } = useEcharts(chartRef);
|
const { renderEcharts } = useEcharts(chartRef);
|
||||||
|
|||||||
@@ -1,11 +1,8 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { onMounted, ref } from 'vue';
|
import type { EchartsUIType } from '@vben/plugins/echarts';
|
||||||
|
|
||||||
import {
|
import { EchartsUI, useEcharts } from '@vben/plugins/echarts';
|
||||||
EchartsUI,
|
import { onMounted, ref } from 'vue';
|
||||||
type EchartsUIType,
|
|
||||||
useEcharts,
|
|
||||||
} from '@vben/plugins/echarts';
|
|
||||||
|
|
||||||
const chartRef = ref<EchartsUIType>();
|
const chartRef = ref<EchartsUIType>();
|
||||||
const { renderEcharts } = useEcharts(chartRef);
|
const { renderEcharts } = useEcharts(chartRef);
|
||||||
|
|||||||
@@ -1,11 +1,8 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { onMounted, ref } from 'vue';
|
import type { EchartsUIType } from '@vben/plugins/echarts';
|
||||||
|
|
||||||
import {
|
import { EchartsUI, useEcharts } from '@vben/plugins/echarts';
|
||||||
EchartsUI,
|
import { onMounted, ref } from 'vue';
|
||||||
type EchartsUIType,
|
|
||||||
useEcharts,
|
|
||||||
} from '@vben/plugins/echarts';
|
|
||||||
|
|
||||||
const chartRef = ref<EchartsUIType>();
|
const chartRef = ref<EchartsUIType>();
|
||||||
const { renderEcharts } = useEcharts(chartRef);
|
const { renderEcharts } = useEcharts(chartRef);
|
||||||
|
|||||||
@@ -1,11 +1,8 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { onMounted, ref } from 'vue';
|
import type { EchartsUIType } from '@vben/plugins/echarts';
|
||||||
|
|
||||||
import {
|
import { EchartsUI, useEcharts } from '@vben/plugins/echarts';
|
||||||
EchartsUI,
|
import { onMounted, ref } from 'vue';
|
||||||
type EchartsUIType,
|
|
||||||
useEcharts,
|
|
||||||
} from '@vben/plugins/echarts';
|
|
||||||
|
|
||||||
const chartRef = ref<EchartsUIType>();
|
const chartRef = ref<EchartsUIType>();
|
||||||
const { renderEcharts } = useEcharts(chartRef);
|
const { renderEcharts } = useEcharts(chartRef);
|
||||||
|
|||||||
@@ -139,6 +139,7 @@ const [Form, formApi] = useVbenForm({
|
|||||||
fieldName: 'select',
|
fieldName: 'select',
|
||||||
label: 'Select',
|
label: 'Select',
|
||||||
componentProps: {
|
componentProps: {
|
||||||
|
filterable: true,
|
||||||
options: [
|
options: [
|
||||||
{ value: 'A', label: '选项A' },
|
{ value: 'A', label: '选项A' },
|
||||||
{ value: 'B', label: '选项B' },
|
{ value: 'B', label: '选项B' },
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@vben/web-naive",
|
"name": "@vben/web-naive",
|
||||||
"version": "5.5.0",
|
"version": "5.5.2",
|
||||||
"homepage": "https://vben.pro",
|
"homepage": "https://vben.pro",
|
||||||
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
|
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
|
||||||
"repository": {
|
"repository": {
|
||||||
|
|||||||
@@ -4,13 +4,11 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import type { BaseFormComponentType } from '@vben/common-ui';
|
import type { BaseFormComponentType } from '@vben/common-ui';
|
||||||
|
|
||||||
import type { Component, SetupContext } from 'vue';
|
import type { Component, SetupContext } from 'vue';
|
||||||
import { h } from 'vue';
|
|
||||||
|
|
||||||
|
import { message } from '#/adapter/naive';
|
||||||
import { ApiComponent, globalShareState, IconPicker } from '@vben/common-ui';
|
import { ApiComponent, globalShareState, IconPicker } from '@vben/common-ui';
|
||||||
import { $t } from '@vben/locales';
|
import { $t } from '@vben/locales';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
NButton,
|
NButton,
|
||||||
NCheckbox,
|
NCheckbox,
|
||||||
@@ -29,8 +27,7 @@ import {
|
|||||||
NTreeSelect,
|
NTreeSelect,
|
||||||
NUpload,
|
NUpload,
|
||||||
} from 'naive-ui';
|
} from 'naive-ui';
|
||||||
|
import { h } from 'vue';
|
||||||
import { message } from '#/adapter/naive';
|
|
||||||
|
|
||||||
const withDefaultPlaceholder = <T extends Component>(
|
const withDefaultPlaceholder = <T extends Component>(
|
||||||
component: T,
|
component: T,
|
||||||
|
|||||||
@@ -10,8 +10,6 @@ import { $t } from '@vben/locales';
|
|||||||
|
|
||||||
setupVbenForm<ComponentType>({
|
setupVbenForm<ComponentType>({
|
||||||
config: {
|
config: {
|
||||||
// naive-ui组件不接受onChang事件,所以需要禁用
|
|
||||||
disabledOnChangeListener: true,
|
|
||||||
// naive-ui组件的空值为null,不能是undefined,否则重置表单时不生效
|
// naive-ui组件的空值为null,不能是undefined,否则重置表单时不生效
|
||||||
emptyStateValue: null,
|
emptyStateValue: null,
|
||||||
baseModelPropName: 'value',
|
baseModelPropName: 'value',
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import type { LocaleSetupOptions, SupportedLanguagesType } from '@vben/locales';
|
import type { LocaleSetupOptions, SupportedLanguagesType } from '@vben/locales';
|
||||||
|
|
||||||
import type { App } from 'vue';
|
import type { App } from 'vue';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
|||||||
@@ -54,7 +54,9 @@ function setupAccessGuard(router: Router) {
|
|||||||
if (coreRouteNames.includes(to.name as string)) {
|
if (coreRouteNames.includes(to.name as string)) {
|
||||||
if (to.path === LOGIN_PATH && accessStore.accessToken) {
|
if (to.path === LOGIN_PATH && accessStore.accessToken) {
|
||||||
return decodeURIComponent(
|
return decodeURIComponent(
|
||||||
(to.query?.redirect as string) || DEFAULT_HOME_PATH,
|
(to.query?.redirect as string) ||
|
||||||
|
userStore.userInfo?.homePath ||
|
||||||
|
DEFAULT_HOME_PATH,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
@@ -72,7 +74,10 @@ function setupAccessGuard(router: Router) {
|
|||||||
return {
|
return {
|
||||||
path: LOGIN_PATH,
|
path: LOGIN_PATH,
|
||||||
// 如不需要,直接删除 query
|
// 如不需要,直接删除 query
|
||||||
query: { redirect: encodeURIComponent(to.fullPath) },
|
query:
|
||||||
|
to.fullPath === DEFAULT_HOME_PATH
|
||||||
|
? {}
|
||||||
|
: { redirect: encodeURIComponent(to.fullPath) },
|
||||||
// 携带当前跳转的页面,登录后重新跳转该页面
|
// 携带当前跳转的页面,登录后重新跳转该页面
|
||||||
replace: true,
|
replace: true,
|
||||||
};
|
};
|
||||||
@@ -101,7 +106,10 @@ function setupAccessGuard(router: Router) {
|
|||||||
accessStore.setAccessMenus(accessibleMenus);
|
accessStore.setAccessMenus(accessibleMenus);
|
||||||
accessStore.setAccessRoutes(accessibleRoutes);
|
accessStore.setAccessRoutes(accessibleRoutes);
|
||||||
accessStore.setIsAccessChecked(true);
|
accessStore.setIsAccessChecked(true);
|
||||||
const redirectPath = (from.query.redirect ?? to.fullPath) as string;
|
const redirectPath = (from.query.redirect ??
|
||||||
|
(to.path === DEFAULT_HOME_PATH
|
||||||
|
? userInfo.homePath || DEFAULT_HOME_PATH
|
||||||
|
: to.fullPath)) as string;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...router.resolve(decodeURIComponent(redirectPath)),
|
...router.resolve(decodeURIComponent(redirectPath)),
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import { $t } from '@vben/locales';
|
|||||||
defineOptions({ name: 'CodeLogin' });
|
defineOptions({ name: 'CodeLogin' });
|
||||||
|
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
|
const CODE_LENGTH = 6;
|
||||||
|
|
||||||
const formSchema = computed((): VbenFormSchema[] => {
|
const formSchema = computed((): VbenFormSchema[] => {
|
||||||
return [
|
return [
|
||||||
@@ -30,6 +31,7 @@ const formSchema = computed((): VbenFormSchema[] => {
|
|||||||
{
|
{
|
||||||
component: 'VbenPinInput',
|
component: 'VbenPinInput',
|
||||||
componentProps: {
|
componentProps: {
|
||||||
|
codeLength: CODE_LENGTH,
|
||||||
createText: (countdown: number) => {
|
createText: (countdown: number) => {
|
||||||
const text =
|
const text =
|
||||||
countdown > 0
|
countdown > 0
|
||||||
@@ -41,7 +43,9 @@ const formSchema = computed((): VbenFormSchema[] => {
|
|||||||
},
|
},
|
||||||
fieldName: 'code',
|
fieldName: 'code',
|
||||||
label: $t('authentication.code'),
|
label: $t('authentication.code'),
|
||||||
rules: z.string().min(1, { message: $t('authentication.codeTip') }),
|
rules: z.string().length(CODE_LENGTH, {
|
||||||
|
message: $t('authentication.codeTip', [CODE_LENGTH]),
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,11 +1,8 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { onMounted, ref } from 'vue';
|
import type { EchartsUIType } from '@vben/plugins/echarts';
|
||||||
|
|
||||||
import {
|
import { EchartsUI, useEcharts } from '@vben/plugins/echarts';
|
||||||
EchartsUI,
|
import { onMounted, ref } from 'vue';
|
||||||
type EchartsUIType,
|
|
||||||
useEcharts,
|
|
||||||
} from '@vben/plugins/echarts';
|
|
||||||
|
|
||||||
const chartRef = ref<EchartsUIType>();
|
const chartRef = ref<EchartsUIType>();
|
||||||
const { renderEcharts } = useEcharts(chartRef);
|
const { renderEcharts } = useEcharts(chartRef);
|
||||||
|
|||||||
@@ -1,11 +1,8 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { onMounted, ref } from 'vue';
|
import type { EchartsUIType } from '@vben/plugins/echarts';
|
||||||
|
|
||||||
import {
|
import { EchartsUI, useEcharts } from '@vben/plugins/echarts';
|
||||||
EchartsUI,
|
import { onMounted, ref } from 'vue';
|
||||||
type EchartsUIType,
|
|
||||||
useEcharts,
|
|
||||||
} from '@vben/plugins/echarts';
|
|
||||||
|
|
||||||
const chartRef = ref<EchartsUIType>();
|
const chartRef = ref<EchartsUIType>();
|
||||||
const { renderEcharts } = useEcharts(chartRef);
|
const { renderEcharts } = useEcharts(chartRef);
|
||||||
|
|||||||
@@ -1,11 +1,8 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { onMounted, ref } from 'vue';
|
import type { EchartsUIType } from '@vben/plugins/echarts';
|
||||||
|
|
||||||
import {
|
import { EchartsUI, useEcharts } from '@vben/plugins/echarts';
|
||||||
EchartsUI,
|
import { onMounted, ref } from 'vue';
|
||||||
type EchartsUIType,
|
|
||||||
useEcharts,
|
|
||||||
} from '@vben/plugins/echarts';
|
|
||||||
|
|
||||||
const chartRef = ref<EchartsUIType>();
|
const chartRef = ref<EchartsUIType>();
|
||||||
const { renderEcharts } = useEcharts(chartRef);
|
const { renderEcharts } = useEcharts(chartRef);
|
||||||
|
|||||||
@@ -1,11 +1,8 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { onMounted, ref } from 'vue';
|
import type { EchartsUIType } from '@vben/plugins/echarts';
|
||||||
|
|
||||||
import {
|
import { EchartsUI, useEcharts } from '@vben/plugins/echarts';
|
||||||
EchartsUI,
|
import { onMounted, ref } from 'vue';
|
||||||
type EchartsUIType,
|
|
||||||
useEcharts,
|
|
||||||
} from '@vben/plugins/echarts';
|
|
||||||
|
|
||||||
const chartRef = ref<EchartsUIType>();
|
const chartRef = ref<EchartsUIType>();
|
||||||
const { renderEcharts } = useEcharts(chartRef);
|
const { renderEcharts } = useEcharts(chartRef);
|
||||||
|
|||||||
@@ -1,11 +1,8 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { onMounted, ref } from 'vue';
|
import type { EchartsUIType } from '@vben/plugins/echarts';
|
||||||
|
|
||||||
import {
|
import { EchartsUI, useEcharts } from '@vben/plugins/echarts';
|
||||||
EchartsUI,
|
import { onMounted, ref } from 'vue';
|
||||||
type EchartsUIType,
|
|
||||||
useEcharts,
|
|
||||||
} from '@vben/plugins/echarts';
|
|
||||||
|
|
||||||
const chartRef = ref<EchartsUIType>();
|
const chartRef = ref<EchartsUIType>();
|
||||||
const { renderEcharts } = useEcharts(chartRef);
|
const { renderEcharts } = useEcharts(chartRef);
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { Page } from '@vben/common-ui';
|
import type { NotificationType } from 'naive-ui';
|
||||||
|
|
||||||
import { type NotificationType } from 'naive-ui';
|
import { Page } from '@vben/common-ui';
|
||||||
import { NButton, NCard, NSpace, useMessage, useNotification } from 'naive-ui';
|
import { NButton, NCard, NSpace, useMessage, useNotification } from 'naive-ui';
|
||||||
|
|
||||||
const notification = useNotification();
|
const notification = useNotification();
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
import { type DefaultTheme, defineConfig } from 'vitepress';
|
import type { DefaultTheme } from 'vitepress';
|
||||||
|
|
||||||
|
import { defineConfig } from 'vitepress';
|
||||||
|
|
||||||
import { version } from '../../../package.json';
|
import { version } from '../../../package.json';
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
import { type DefaultTheme, defineConfig } from 'vitepress';
|
import type { DefaultTheme } from 'vitepress';
|
||||||
|
|
||||||
|
import { defineConfig } from 'vitepress';
|
||||||
|
|
||||||
import { version } from '../../../package.json';
|
import { version } from '../../../package.json';
|
||||||
|
|
||||||
@@ -186,6 +188,10 @@ function sidebarComponents(): DefaultTheme.SidebarItem[] {
|
|||||||
link: 'common-ui/vben-count-to-animator',
|
link: 'common-ui/vben-count-to-animator',
|
||||||
text: 'CountToAnimator 数字动画',
|
text: 'CountToAnimator 数字动画',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
link: 'common-ui/vben-ellipsis-text',
|
||||||
|
text: 'EllipsisText 省略文本',
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import {
|
|||||||
|
|
||||||
// import { useAntdDesignTokens } from '@vben/hooks';
|
// import { useAntdDesignTokens } from '@vben/hooks';
|
||||||
// import { initPreferences } from '@vben/preferences';
|
// import { initPreferences } from '@vben/preferences';
|
||||||
|
|
||||||
import { ConfigProvider, theme } from 'ant-design-vue';
|
import { ConfigProvider, theme } from 'ant-design-vue';
|
||||||
import mediumZoom from 'medium-zoom';
|
import mediumZoom from 'medium-zoom';
|
||||||
import { useRoute } from 'vitepress';
|
import { useRoute } from 'vitepress';
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@vben/docs",
|
"name": "@vben/docs",
|
||||||
"version": "5.5.0",
|
"version": "5.5.2",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "vitepress build",
|
"build": "vitepress build",
|
||||||
|
|||||||
@@ -4,13 +4,10 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import type { BaseFormComponentType } from '@vben/common-ui';
|
import type { BaseFormComponentType } from '@vben/common-ui';
|
||||||
|
|
||||||
import type { Component, SetupContext } from 'vue';
|
import type { Component, SetupContext } from 'vue';
|
||||||
import { h } from 'vue';
|
|
||||||
|
|
||||||
import { globalShareState } from '@vben/common-ui';
|
import { globalShareState } from '@vben/common-ui';
|
||||||
import { $t } from '@vben/locales';
|
import { $t } from '@vben/locales';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
AutoComplete,
|
AutoComplete,
|
||||||
Button,
|
Button,
|
||||||
@@ -35,6 +32,7 @@ import {
|
|||||||
TreeSelect,
|
TreeSelect,
|
||||||
Upload,
|
Upload,
|
||||||
} from 'ant-design-vue';
|
} from 'ant-design-vue';
|
||||||
|
import { h } from 'vue';
|
||||||
|
|
||||||
const withDefaultPlaceholder = <T extends Component>(
|
const withDefaultPlaceholder = <T extends Component>(
|
||||||
component: T,
|
component: T,
|
||||||
|
|||||||
@@ -14,8 +14,6 @@ initComponentAdapter();
|
|||||||
setupVbenForm<ComponentType>({
|
setupVbenForm<ComponentType>({
|
||||||
config: {
|
config: {
|
||||||
baseModelPropName: 'value',
|
baseModelPropName: 'value',
|
||||||
// naive-ui组件不接受onChang事件,所以需要禁用
|
|
||||||
disabledOnChangeListener: true,
|
|
||||||
// naive-ui组件的空值为null,不能是undefined,否则重置表单时不生效
|
// naive-ui组件的空值为null,不能是undefined,否则重置表单时不生效
|
||||||
emptyStateValue: null,
|
emptyStateValue: null,
|
||||||
modelPropNameMap: {
|
modelPropNameMap: {
|
||||||
|
|||||||
@@ -123,6 +123,8 @@ function fetchApi(): Promise<Record<string, any>> {
|
|||||||
|
|
||||||
:::
|
:::
|
||||||
|
|
||||||
|
## API
|
||||||
|
|
||||||
### Props
|
### Props
|
||||||
|
|
||||||
| 属性名 | 描述 | 类型 | 默认值 |
|
| 属性名 | 描述 | 类型 | 默认值 |
|
||||||
|
|||||||
@@ -54,6 +54,7 @@ Drawer 内的内容一般业务中,会比较复杂,所以我们可以将 dra
|
|||||||
|
|
||||||
- `VbenDrawer` 组件对与参数的处理优先级是 `slot` > `props` > `state`(通过api更新的状态以及useVbenDrawer参数)。如果你已经传入了 `slot` 或者 `props`,那么 `setState` 将不会生效,这种情况下你可以通过 `slot` 或者 `props` 来更新状态。
|
- `VbenDrawer` 组件对与参数的处理优先级是 `slot` > `props` > `state`(通过api更新的状态以及useVbenDrawer参数)。如果你已经传入了 `slot` 或者 `props`,那么 `setState` 将不会生效,这种情况下你可以通过 `slot` 或者 `props` 来更新状态。
|
||||||
- 如果你使用到了 `connectedComponent` 参数,那么会存在 2 个`useVbenDrawer`, 此时,如果同时设置了相同的参数,那么以内部为准(也就是没有设置 connectedComponent 的代码)。比如 同时设置了 `onConfirm`,那么以内部的 `onConfirm` 为准。`onOpenChange`事件除外,内外都会触发。
|
- 如果你使用到了 `connectedComponent` 参数,那么会存在 2 个`useVbenDrawer`, 此时,如果同时设置了相同的参数,那么以内部为准(也就是没有设置 connectedComponent 的代码)。比如 同时设置了 `onConfirm`,那么以内部的 `onConfirm` 为准。`onOpenChange`事件除外,内外都会触发。
|
||||||
|
- 使用了`connectedComponent`参数时,可以配置`destroyOnClose`属性来决定当关闭弹窗时,是否要销毁`connectedComponent`组件(重新创建`connectedComponent`组件,这将会把其内部所有的变量、状态、数据等恢复到初始状态。)。
|
||||||
|
|
||||||
:::
|
:::
|
||||||
|
|
||||||
@@ -75,12 +76,15 @@ const [Drawer, drawerApi] = useVbenDrawer({
|
|||||||
| 属性名 | 描述 | 类型 | 默认值 |
|
| 属性名 | 描述 | 类型 | 默认值 |
|
||||||
| --- | --- | --- | --- |
|
| --- | --- | --- | --- |
|
||||||
| appendToMain | 是否挂载到内容区域(默认挂载到body) | `boolean` | `false` |
|
| appendToMain | 是否挂载到内容区域(默认挂载到body) | `boolean` | `false` |
|
||||||
|
| connectedComponent | 连接另一个Modal组件 | `Component` | - |
|
||||||
|
| destroyOnClose | 关闭时销毁`connectedComponent` | `boolean` | `false` |
|
||||||
| title | 标题 | `string\|slot` | - |
|
| title | 标题 | `string\|slot` | - |
|
||||||
| titleTooltip | 标题提示信息 | `string\|slot` | - |
|
| titleTooltip | 标题提示信息 | `string\|slot` | - |
|
||||||
| description | 描述信息 | `string\|slot` | - |
|
| description | 描述信息 | `string\|slot` | - |
|
||||||
| isOpen | 弹窗打开状态 | `boolean` | `false` |
|
| isOpen | 弹窗打开状态 | `boolean` | `false` |
|
||||||
| loading | 弹窗加载状态 | `boolean` | `false` |
|
| loading | 弹窗加载状态 | `boolean` | `false` |
|
||||||
| closable | 显示关闭按钮 | `boolean` | `true` |
|
| closable | 显示关闭按钮 | `boolean` | `true` |
|
||||||
|
| closeIconPlacement | 关闭按钮位置 | `'left'\|'right'` | `right` |
|
||||||
| modal | 显示遮罩 | `boolean` | `true` |
|
| modal | 显示遮罩 | `boolean` | `true` |
|
||||||
| header | 显示header | `boolean` | `true` |
|
| header | 显示header | `boolean` | `true` |
|
||||||
| footer | 显示footer | `boolean\|slot` | `true` |
|
| footer | 显示footer | `boolean\|slot` | `true` |
|
||||||
@@ -108,12 +112,14 @@ const [Drawer, drawerApi] = useVbenDrawer({
|
|||||||
|
|
||||||
以下事件,只有在 `useVbenDrawer({onCancel:()=>{}})` 中传入才会生效。
|
以下事件,只有在 `useVbenDrawer({onCancel:()=>{}})` 中传入才会生效。
|
||||||
|
|
||||||
| 事件名 | 描述 | 类型 |
|
| 事件名 | 描述 | 类型 | 版本限制 |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- | --- |
|
||||||
| onBeforeClose | 关闭前触发,返回 `false`则禁止关闭 | `()=>boolean` |
|
| onBeforeClose | 关闭前触发,返回 `false`则禁止关闭 | `()=>boolean` | --- |
|
||||||
| onCancel | 点击取消按钮触发 | `()=>void` |
|
| onCancel | 点击取消按钮触发 | `()=>void` | --- |
|
||||||
| onConfirm | 点击确认按钮触发 | `()=>void` |
|
| onClosed | 关闭动画播放完毕时触发 | `()=>void` | >5.5.2 |
|
||||||
| onOpenChange | 关闭或者打开弹窗时触发 | `(isOpen:boolean)=>void` |
|
| onConfirm | 点击确认按钮触发 | `()=>void` | --- |
|
||||||
|
| onOpenChange | 关闭或者打开弹窗时触发 | `(isOpen:boolean)=>void` | --- |
|
||||||
|
| onOpened | 打开动画播放完毕时触发 | `()=>void` | >5.5.2 |
|
||||||
|
|
||||||
### Slots
|
### Slots
|
||||||
|
|
||||||
@@ -124,6 +130,8 @@ const [Drawer, drawerApi] = useVbenDrawer({
|
|||||||
| default | 默认插槽 - 弹窗内容 |
|
| default | 默认插槽 - 弹窗内容 |
|
||||||
| prepend-footer | 取消按钮左侧 |
|
| prepend-footer | 取消按钮左侧 |
|
||||||
| append-footer | 取消按钮右侧 |
|
| append-footer | 取消按钮右侧 |
|
||||||
|
| close-icon | 关闭按钮图标 |
|
||||||
|
| extra | 额外内容(标题右侧) |
|
||||||
|
|
||||||
### modalApi
|
### modalApi
|
||||||
|
|
||||||
|
|||||||
56
docs/src/components/common-ui/vben-ellipsis-text.md
Normal file
56
docs/src/components/common-ui/vben-ellipsis-text.md
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
---
|
||||||
|
outline: deep
|
||||||
|
---
|
||||||
|
|
||||||
|
# Vben EllipsisText 省略文本
|
||||||
|
|
||||||
|
框架提供的文本展示组件,可配置超长省略、tooltip提示、展开收起等功能。
|
||||||
|
|
||||||
|
> 如果文档内没有参数说明,可以尝试在在线示例内寻找
|
||||||
|
|
||||||
|
## 基础用法
|
||||||
|
|
||||||
|
通过默认插槽设置文本内容,`maxWidth`属性设置最大宽度。
|
||||||
|
|
||||||
|
<DemoPreview dir="demos/vben-ellipsis-text/line" />
|
||||||
|
|
||||||
|
## 可折叠的文本块
|
||||||
|
|
||||||
|
通过`line`设置折叠后的行数,`expand`属性设置是否支持展开收起。
|
||||||
|
|
||||||
|
<DemoPreview dir="demos/vben-ellipsis-text/expand" />
|
||||||
|
|
||||||
|
## 自定义提示浮层
|
||||||
|
|
||||||
|
通过名为`tooltip`的插槽定制提示信息。
|
||||||
|
|
||||||
|
<DemoPreview dir="demos/vben-ellipsis-text/tooltip" />
|
||||||
|
|
||||||
|
## API
|
||||||
|
|
||||||
|
### Props
|
||||||
|
|
||||||
|
| 属性名 | 描述 | 类型 | 默认值 |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| expand | 支持点击展开或收起 | `boolean` | `false` |
|
||||||
|
| line | 文本最大行数 | `number` | `1` |
|
||||||
|
| maxWidth | 文本区域最大宽度 | `number \| string` | `'100%'` |
|
||||||
|
| placement | 提示浮层的位置 | `'bottom'\|'left'\|'right'\|'top'` | `'top'` |
|
||||||
|
| tooltip | 启用文本提示 | `boolean` | `true` |
|
||||||
|
| tooltipBackgroundColor | 提示文本的背景颜色 | `string` | - |
|
||||||
|
| tooltipColor | 提示文本的颜色 | `string` | - |
|
||||||
|
| tooltipFontSize | 提示文本的大小 | `string` | - |
|
||||||
|
| tooltipMaxWidth | 提示浮层的最大宽度。如不设置则保持与文本宽度一致 | `number` | - |
|
||||||
|
| tooltipOverlayStyle | 提示框内容区域样式 | `CSSProperties` | `{ textAlign: 'justify' }` |
|
||||||
|
|
||||||
|
### Events
|
||||||
|
|
||||||
|
| 事件名 | 描述 | 类型 |
|
||||||
|
| ------------ | ------------ | -------------------------- |
|
||||||
|
| expandChange | 展开状态改变 | `(isExpand:boolean)=>void` |
|
||||||
|
|
||||||
|
### Slots
|
||||||
|
|
||||||
|
| 插槽名 | 描述 |
|
||||||
|
| ------- | -------------------------------- |
|
||||||
|
| tooltip | 启用文本提示时,用来定制提示内容 |
|
||||||
@@ -287,6 +287,8 @@ useVbenForm 返回的第二个参数,是一个对象,包含了一些表单
|
|||||||
| setValues | 设置表单值, 默认会过滤不在schema中定义的field, 可通过filterFields形参关闭过滤 | `(fields: Record<string, any>, filterFields?: boolean, shouldValidate?: boolean) => Promise<void>` |
|
| setValues | 设置表单值, 默认会过滤不在schema中定义的field, 可通过filterFields形参关闭过滤 | `(fields: Record<string, any>, filterFields?: boolean, shouldValidate?: boolean) => Promise<void>` |
|
||||||
| getValues | 获取表单值 | `(fields:Record<string, any>,shouldValidate: boolean = false)=>Promise<void>` |
|
| getValues | 获取表单值 | `(fields:Record<string, any>,shouldValidate: boolean = false)=>Promise<void>` |
|
||||||
| validate | 表单校验 | `()=>Promise<void>` |
|
| validate | 表单校验 | `()=>Promise<void>` |
|
||||||
|
| validateField | 校验指定字段 | `(fieldName: string)=>Promise<ValidationResult<unknown>>` |
|
||||||
|
| isFieldValid | 检查某个字段是否已通过校验 | `(fieldName: string)=>Promise<boolean>` |
|
||||||
| resetValidate | 重置表单校验 | `()=>Promise<void>` |
|
| resetValidate | 重置表单校验 | `()=>Promise<void>` |
|
||||||
| updateSchema | 更新formSchema | `(schema:FormSchema[])=>void` |
|
| updateSchema | 更新formSchema | `(schema:FormSchema[])=>void` |
|
||||||
| setFieldValue | 设置字段值 | `(field: string, value: any, shouldValidate?: boolean)=>Promise<void>` |
|
| setFieldValue | 设置字段值 | `(field: string, value: any, shouldValidate?: boolean)=>Promise<void>` |
|
||||||
@@ -311,14 +313,14 @@ useVbenForm 返回的第二个参数,是一个对象,包含了一些表单
|
|||||||
| resetButtonOptions | 重置按钮组件参数 | `ActionButtonOptions` | - |
|
| resetButtonOptions | 重置按钮组件参数 | `ActionButtonOptions` | - |
|
||||||
| submitButtonOptions | 提交按钮组件参数 | `ActionButtonOptions` | - |
|
| submitButtonOptions | 提交按钮组件参数 | `ActionButtonOptions` | - |
|
||||||
| showDefaultActions | 是否显示默认操作按钮 | `boolean` | `true` |
|
| showDefaultActions | 是否显示默认操作按钮 | `boolean` | `true` |
|
||||||
| collapsed | 是否折叠,在`是否展开,在showCollapseButton=true`时生效 | `boolean` | `false` |
|
| collapsed | 是否折叠,在`showCollapseButton`为`true`时生效 | `boolean` | `false` |
|
||||||
| collapseTriggerResize | 折叠时,触发`resize`事件 | `boolean` | `false` |
|
| collapseTriggerResize | 折叠时,触发`resize`事件 | `boolean` | `false` |
|
||||||
| collapsedRows | 折叠时保持的行数 | `number` | `1` |
|
| collapsedRows | 折叠时保持的行数 | `number` | `1` |
|
||||||
| fieldMappingTime | 用于将表单内时间区域的应设成 2 个字段 | `[string, [string, string], string?][]` | - |
|
| fieldMappingTime | 用于将表单内时间区域组件的数组值映射成 2 个字段 | `[string, [string, string], string?][]` | - |
|
||||||
| commonConfig | 表单项的通用配置,每个配置都会传递到每个表单项,表单项可覆盖 | `FormCommonConfig` | - |
|
| commonConfig | 表单项的通用配置,每个配置都会传递到每个表单项,表单项可覆盖 | `FormCommonConfig` | - |
|
||||||
| schema | 表单项的每一项配置 | `FormSchema` | - |
|
| schema | 表单项的每一项配置 | `FormSchema[]` | - |
|
||||||
| submitOnEnter | 按下回车健时提交表单 | `boolean` | false |
|
| submitOnEnter | 按下回车健时提交表单 | `boolean` | false |
|
||||||
| submitOnChange | 字段值改变时提交表单 | `boolean` | false |
|
| submitOnChange | 字段值改变时提交表单(内部防抖,这个属性一般用于表格的搜索表单) | `boolean` | false |
|
||||||
|
|
||||||
### TS 类型说明
|
### TS 类型说明
|
||||||
|
|
||||||
@@ -355,10 +357,21 @@ export interface FormCommonConfig {
|
|||||||
* 所有表单项的props
|
* 所有表单项的props
|
||||||
*/
|
*/
|
||||||
componentProps?: ComponentProps;
|
componentProps?: ComponentProps;
|
||||||
|
/**
|
||||||
|
* 是否紧凑模式(移除表单底部为显示校验错误信息所预留的空间)。
|
||||||
|
* 在有设置校验规则的场景下,建议不要将其设置为true
|
||||||
|
* 默认为false。但用作表格的搜索表单时,默认为true
|
||||||
|
* @default false
|
||||||
|
*/
|
||||||
|
compact?: boolean;
|
||||||
/**
|
/**
|
||||||
* 所有表单项的控件样式
|
* 所有表单项的控件样式
|
||||||
*/
|
*/
|
||||||
controlClass?: string;
|
controlClass?: string;
|
||||||
|
/**
|
||||||
|
* 在表单项的Label后显示一个冒号
|
||||||
|
*/
|
||||||
|
colon?: boolean;
|
||||||
/**
|
/**
|
||||||
* 所有表单项的禁用状态
|
* 所有表单项的禁用状态
|
||||||
* @default false
|
* @default false
|
||||||
@@ -418,7 +431,7 @@ export interface FormSchema<
|
|||||||
dependencies?: FormItemDependencies;
|
dependencies?: FormItemDependencies;
|
||||||
/** 描述 */
|
/** 描述 */
|
||||||
description?: string;
|
description?: string;
|
||||||
/** 字段名 */
|
/** 字段名,也作为自定义插槽的名称 */
|
||||||
fieldName: string;
|
fieldName: string;
|
||||||
/** 帮助信息 */
|
/** 帮助信息 */
|
||||||
help?: string;
|
help?: string;
|
||||||
@@ -441,7 +454,7 @@ export interface FormSchema<
|
|||||||
|
|
||||||
```ts
|
```ts
|
||||||
dependencies: {
|
dependencies: {
|
||||||
// 只有当 name 字段的值变化时,才会触发联动
|
// 触发字段。只有这些字段值变动时,联动才会触发
|
||||||
triggerFields: ['name'],
|
triggerFields: ['name'],
|
||||||
// 动态判断当前字段是否需要显示,不显示则直接销毁
|
// 动态判断当前字段是否需要显示,不显示则直接销毁
|
||||||
if(values,formApi){},
|
if(values,formApi){},
|
||||||
@@ -462,11 +475,11 @@ dependencies: {
|
|||||||
|
|
||||||
### 表单校验
|
### 表单校验
|
||||||
|
|
||||||
表单联动需要通过 schema 内的 `rules` 属性进行配置。
|
表单校验需要通过 schema 内的 `rules` 属性进行配置。
|
||||||
|
|
||||||
rules的值可以是一个字符串,也可以是一个zod的schema。
|
rules的值可以是字符串(预定义的校验规则名称),也可以是一个zod的schema。
|
||||||
|
|
||||||
#### 字符串
|
#### 预定义的校验规则
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
// 表示字段必填,默认会根据适配器的required进行国际化
|
// 表示字段必填,默认会根据适配器的required进行国际化
|
||||||
@@ -492,11 +505,16 @@ import { z } from '#/adapter/form';
|
|||||||
rules: z.string().min(1, { message: '请输入字符串' });
|
rules: z.string().min(1, { message: '请输入字符串' });
|
||||||
}
|
}
|
||||||
|
|
||||||
// 可选,并且携带默认值
|
// 可选(可以是undefined),并且携带默认值。注意zod的optional不包括空字符串''
|
||||||
{
|
{
|
||||||
rules: z.string().default('默认值').optional(),
|
rules: z.string().default('默认值').optional(),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 可以是空字符串、undefined或者一个邮箱地址
|
||||||
|
{
|
||||||
|
rules: z.union(z.string().email().optional(), z.literal(""))
|
||||||
|
}
|
||||||
|
|
||||||
// 复杂校验
|
// 复杂校验
|
||||||
{
|
{
|
||||||
z.string().min(1, { message: "请输入" })
|
z.string().min(1, { message: "请输入" })
|
||||||
|
|||||||
@@ -60,6 +60,7 @@ Modal 内的内容一般业务中,会比较复杂,所以我们可以将 moda
|
|||||||
|
|
||||||
- `VbenModal` 组件对与参数的处理优先级是 `slot` > `props` > `state`(通过api更新的状态以及useVbenModal参数)。如果你已经传入了 `slot` 或者 `props`,那么 `setState` 将不会生效,这种情况下你可以通过 `slot` 或者 `props` 来更新状态。
|
- `VbenModal` 组件对与参数的处理优先级是 `slot` > `props` > `state`(通过api更新的状态以及useVbenModal参数)。如果你已经传入了 `slot` 或者 `props`,那么 `setState` 将不会生效,这种情况下你可以通过 `slot` 或者 `props` 来更新状态。
|
||||||
- 如果你使用到了 `connectedComponent` 参数,那么会存在 2 个`useVbenModal`, 此时,如果同时设置了相同的参数,那么以内部为准(也就是没有设置 connectedComponent 的代码)。比如 同时设置了 `onConfirm`,那么以内部的 `onConfirm` 为准。`onOpenChange`事件除外,内外都会触发。
|
- 如果你使用到了 `connectedComponent` 参数,那么会存在 2 个`useVbenModal`, 此时,如果同时设置了相同的参数,那么以内部为准(也就是没有设置 connectedComponent 的代码)。比如 同时设置了 `onConfirm`,那么以内部的 `onConfirm` 为准。`onOpenChange`事件除外,内外都会触发。
|
||||||
|
- 使用了`connectedComponent`参数时,可以配置`destroyOnClose`属性来决定当关闭弹窗时,是否要销毁`connectedComponent`组件(重新创建`connectedComponent`组件,这将会把其内部所有的变量、状态、数据等恢复到初始状态。)。
|
||||||
|
|
||||||
:::
|
:::
|
||||||
|
|
||||||
@@ -81,6 +82,8 @@ const [Modal, modalApi] = useVbenModal({
|
|||||||
| 属性名 | 描述 | 类型 | 默认值 |
|
| 属性名 | 描述 | 类型 | 默认值 |
|
||||||
| --- | --- | --- | --- |
|
| --- | --- | --- | --- |
|
||||||
| appendToMain | 是否挂载到内容区域(默认挂载到body) | `boolean` | `false` |
|
| appendToMain | 是否挂载到内容区域(默认挂载到body) | `boolean` | `false` |
|
||||||
|
| connectedComponent | 连接另一个Modal组件 | `Component` | - |
|
||||||
|
| destroyOnClose | 关闭时销毁`connectedComponent` | `boolean` | `false` |
|
||||||
| title | 标题 | `string\|slot` | - |
|
| title | 标题 | `string\|slot` | - |
|
||||||
| titleTooltip | 标题提示信息 | `string\|slot` | - |
|
| titleTooltip | 标题提示信息 | `string\|slot` | - |
|
||||||
| description | 描述信息 | `string\|slot` | - |
|
| description | 描述信息 | `string\|slot` | - |
|
||||||
|
|||||||
@@ -165,6 +165,8 @@ vxeUI.renderer.add('CellLink', {
|
|||||||
|
|
||||||
**表单搜索** 部分采用了`Vben Form 表单`,参考 [Vben Form 表单文档](/components/common-ui/vben-form)。
|
**表单搜索** 部分采用了`Vben Form 表单`,参考 [Vben Form 表单文档](/components/common-ui/vben-form)。
|
||||||
|
|
||||||
|
当启用了表单搜索时,可以在toolbarConfig中配置`search`为`true`来让表格在工具栏区域显示一个搜索表单控制按钮。
|
||||||
|
|
||||||
<DemoPreview dir="demos/vben-vxe-table/form" />
|
<DemoPreview dir="demos/vben-vxe-table/form" />
|
||||||
|
|
||||||
## 单元格编辑
|
## 单元格编辑
|
||||||
@@ -215,14 +217,15 @@ const [Grid, gridApi] = useVbenVxeGrid({
|
|||||||
|
|
||||||
useVbenVxeGrid 返回的第二个参数,是一个对象,包含了一些表单的方法。
|
useVbenVxeGrid 返回的第二个参数,是一个对象,包含了一些表单的方法。
|
||||||
|
|
||||||
| 方法名 | 描述 | 类型 |
|
| 方法名 | 描述 | 类型 | 说明 |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- | --- |
|
||||||
| setLoading | 设置loading状态 | `(loading)=>void` |
|
| setLoading | 设置loading状态 | `(loading)=>void` | - |
|
||||||
| setGridOptions | 设置vxe-table grid组件参数 | `(options: Partial<VxeGridProps['gridOptions'])=>void` |
|
| setGridOptions | 设置vxe-table grid组件参数 | `(options: Partial<VxeGridProps['gridOptions'])=>void` | - |
|
||||||
| reload | 重载表格,会进行初始化 | `(params:any)=>void` |
|
| reload | 重载表格,会进行初始化 | `(params:any)=>void` | - |
|
||||||
| query | 重载表格,会保留当前分页 | `(params:any)=>void` |
|
| query | 重载表格,会保留当前分页 | `(params:any)=>void` | - |
|
||||||
| grid | vxe-table grid实例 | `VxeGridInstance` |
|
| grid | vxe-table grid实例 | `VxeGridInstance` | - |
|
||||||
| formApi | vbenForm api实例 | `FormApi` |
|
| formApi | vbenForm api实例 | `FormApi` | - |
|
||||||
|
| toggleSearchForm | 设置搜索表单显示状态 | `(show?: boolean)=>boolean` | 当省略参数时,则将表单在显示和隐藏两种状态之间切换 |
|
||||||
|
|
||||||
## Props
|
## Props
|
||||||
|
|
||||||
@@ -236,3 +239,4 @@ useVbenVxeGrid 返回的第二个参数,是一个对象,包含了一些表
|
|||||||
| gridOptions | grid组件的参数 | `VxeTableGridProps` |
|
| gridOptions | grid组件的参数 | `VxeTableGridProps` |
|
||||||
| gridEvents | grid组件的触发的⌚️ | `VxeGridListeners` |
|
| gridEvents | grid组件的触发的⌚️ | `VxeGridListeners` |
|
||||||
| formOptions | 表单参数 | `VbenFormProps` |
|
| formOptions | 表单参数 | `VbenFormProps` |
|
||||||
|
| showSearchForm | 是否显示搜索表单 | `boolean` |
|
||||||
|
|||||||
10
docs/src/demos/vben-ellipsis-text/expand/index.vue
Normal file
10
docs/src/demos/vben-ellipsis-text/expand/index.vue
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import { EllipsisText } from '@vben/common-ui';
|
||||||
|
|
||||||
|
const text = `
|
||||||
|
Vben Admin 是一个基于 Vue3.0、Vite、 TypeScript 的后台解决方案,目标是为开发中大型项目提供开箱即用的解决方案。包括二次封装组件、utils、hooks、动态菜单、权限校验、多主题配置、按钮级别权限控制等功能。项目会使用前端较新的技术栈,可以作为项目的启动模版,以帮助你快速搭建企业级中后台产品原型。也可以作为一个示例,用于学习 vue3、vite、ts 等主流技术。该项目会持续跟进最新技术,并将其应用在项目中。Vben Admin 是一个基于 Vue3.0、Vite、 TypeScript 的后台解决方案,目标是为开发中大型项目提供开箱即用的解决方案。包括二次封装组件、utils、hooks、动态菜单、权限校验、多主题配置、按钮级别权限控制等功能。项目会使用前端较新的技术栈,可以作为项目的启动模版,以帮助你快速搭建企业级中后台产品原型。也可以作为一个示例,用于学习 vue3、vite、ts 等主流技术。该项目会持续跟进最新技术,并将其应用在项目中。Vben Admin 是一个基于 Vue3.0、Vite、 TypeScript 的后台解决方案,目标是为开发中大型项目提供开箱即用的解决方案。包括二次封装组件、utils、hooks、动态菜单、权限校验、多主题配置、按钮级别权限控制等功能。项目会使用前端较新的技术栈,可以作为项目的启动模版,以帮助你快速搭建企业级中后台产品原型。也可以作为一个示例,用于学习 vue3、vite、ts 等主流技术。该项目会持续跟进最新技术,并将其应用在项目中。Vben Admin 是一个基于 Vue3.0、Vite、 TypeScript 的后台解决方案,目标是为开发中大型项目提供开箱即用的解决方案。包括二次封装组件、utils、hooks、动态菜单、权限校验、多主题配置、按钮级别权限控制等功能。项目会使用前端较新的技术栈,可以作为项目的启动模版,以帮助你快速搭建企业级中后台产品原型。也可以作为一个示例,用于学习 vue3、vite、ts 等主流技术。该项目会持续跟进最新技术,并将其应用在项目中。
|
||||||
|
`;
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<EllipsisText :line="3" expand>{{ text }}</EllipsisText>
|
||||||
|
</template>
|
||||||
10
docs/src/demos/vben-ellipsis-text/line/index.vue
Normal file
10
docs/src/demos/vben-ellipsis-text/line/index.vue
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import { EllipsisText } from '@vben/common-ui';
|
||||||
|
|
||||||
|
const text = `
|
||||||
|
Vben Admin 是一个基于 Vue3.0、Vite、 TypeScript 的后台解决方案,目标是为开发中大型项目提供开箱即用的解决方案。包括二次封装组件、utils、hooks、动态菜单、权限校验、多主题配置、按钮级别权限控制等功能。项目会使用前端较新的技术栈,可以作为项目的启动模版,以帮助你快速搭建企业级中后台产品原型。也可以作为一个示例,用于学习 vue3、vite、ts 等主流技术。该项目会持续跟进最新技术,并将其应用在项目中。Vben Admin 是一个基于 Vue3.0、Vite、 TypeScript 的后台解决方案,目标是为开发中大型项目提供开箱即用的解决方案。包括二次封装组件、utils、hooks、动态菜单、权限校验、多主题配置、按钮级别权限控制等功能。项目会使用前端较新的技术栈,可以作为项目的启动模版,以帮助你快速搭建企业级中后台产品原型。也可以作为一个示例,用于学习 vue3、vite、ts 等主流技术。该项目会持续跟进最新技术,并将其应用在项目中。Vben Admin 是一个基于 Vue3.0、Vite、 TypeScript 的后台解决方案,目标是为开发中大型项目提供开箱即用的解决方案。包括二次封装组件、utils、hooks、动态菜单、权限校验、多主题配置、按钮级别权限控制等功能。项目会使用前端较新的技术栈,可以作为项目的启动模版,以帮助你快速搭建企业级中后台产品原型。也可以作为一个示例,用于学习 vue3、vite、ts 等主流技术。该项目会持续跟进最新技术,并将其应用在项目中。Vben Admin 是一个基于 Vue3.0、Vite、 TypeScript 的后台解决方案,目标是为开发中大型项目提供开箱即用的解决方案。包括二次封装组件、utils、hooks、动态菜单、权限校验、多主题配置、按钮级别权限控制等功能。项目会使用前端较新的技术栈,可以作为项目的启动模版,以帮助你快速搭建企业级中后台产品原型。也可以作为一个示例,用于学习 vue3、vite、ts 等主流技术。该项目会持续跟进最新技术,并将其应用在项目中。
|
||||||
|
`;
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<EllipsisText :max-width="500">{{ text }}</EllipsisText>
|
||||||
|
</template>
|
||||||
14
docs/src/demos/vben-ellipsis-text/tooltip/index.vue
Normal file
14
docs/src/demos/vben-ellipsis-text/tooltip/index.vue
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import { EllipsisText } from '@vben/common-ui';
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<EllipsisText :max-width="240">
|
||||||
|
住在我心里孤独的 孤独的海怪 痛苦之王 开始厌倦 深海的光 停滞的海浪
|
||||||
|
<template #tooltip>
|
||||||
|
<div style="text-align: center">
|
||||||
|
《秦皇岛》<br />住在我心里孤独的<br />孤独的海怪 痛苦之王<br />开始厌倦
|
||||||
|
深海的光 停滞的海浪
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</EllipsisText>
|
||||||
|
</template>
|
||||||
@@ -110,6 +110,11 @@ const gridOptions: VxeGridProps<RowType> = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
toolbarConfig: {
|
||||||
|
// 是否显示搜索表单控制按钮
|
||||||
|
// @ts-ignore 正式环境时有完整的类型声明
|
||||||
|
search: true,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const [Grid] = useVbenVxeGrid({ formOptions, gridOptions });
|
const [Grid] = useVbenVxeGrid({ formOptions, gridOptions });
|
||||||
|
|||||||
@@ -217,6 +217,7 @@ const defaultPreferences: Preferences = {
|
|||||||
globalSearch: true,
|
globalSearch: true,
|
||||||
},
|
},
|
||||||
sidebar: {
|
sidebar: {
|
||||||
|
autoActivateChild: false,
|
||||||
collapsed: false,
|
collapsed: false,
|
||||||
collapsedShowTitle: false,
|
collapsedShowTitle: false,
|
||||||
enable: true,
|
enable: true,
|
||||||
|
|||||||
@@ -240,6 +240,7 @@ const defaultPreferences: Preferences = {
|
|||||||
globalSearch: true,
|
globalSearch: true,
|
||||||
},
|
},
|
||||||
sidebar: {
|
sidebar: {
|
||||||
|
autoActivateChild: false,
|
||||||
collapsed: false,
|
collapsed: false,
|
||||||
collapsedShowTitle: false,
|
collapsedShowTitle: false,
|
||||||
enable: true,
|
enable: true,
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
## 新增组件库应用
|
## 新增组件库应用
|
||||||
|
|
||||||
如果你想用其他别的组件库,你只需要按一下步骤进行操作:
|
如果你想用其他别的组件库,你只需要按以下步骤进行操作:
|
||||||
|
|
||||||
1. 在`apps`内创建一个新的文件夹,例如`apps/web-xxx`。
|
1. 在`apps`内创建一个新的文件夹,例如`apps/web-xxx`。
|
||||||
2. 更改`apps/web-xxx/package.json`的`name`字段为`web-xxx`。
|
2. 更改`apps/web-xxx/package.json`的`name`字段为`web-xxx`。
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ pnpm install
|
|||||||
::: tip 注意
|
::: tip 注意
|
||||||
|
|
||||||
- 项目只支持使用 `pnpm` 进行依赖安装,默认会使用 `corepack` 来安装指定版本的 `pnpm`。:
|
- 项目只支持使用 `pnpm` 进行依赖安装,默认会使用 `corepack` 来安装指定版本的 `pnpm`。:
|
||||||
- 如果你的网络环境无法访问npm源,你可以设置系统的环境变量`COREPACK_REGISTRY=https://registry.npmmirror.com`,然后再执行`pnpm install`。
|
- 如果你的网络环境无法访问npm源,你可以设置系统的环境变量`COREPACK_NPM_REGISTRY=https://registry.npmmirror.com`,然后再执行`pnpm install`。
|
||||||
- 如果你不想使用`corepack`,你需要禁用`corepack`,然后使用你自己的`pnpm`进行安装。
|
- 如果你不想使用`corepack`,你需要禁用`corepack`,然后使用你自己的`pnpm`进行安装。
|
||||||
|
|
||||||
:::
|
:::
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@vben/commitlint-config",
|
"name": "@vben/commitlint-config",
|
||||||
"version": "5.5.0",
|
"version": "5.5.2",
|
||||||
"private": true,
|
"private": true,
|
||||||
"homepage": "https://github.com/vbenjs/vue-vben-admin",
|
"homepage": "https://github.com/vbenjs/vue-vben-admin",
|
||||||
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
|
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ export async function importPluginConfig(): Promise<Linter.Config[]> {
|
|||||||
import: pluginImport,
|
import: pluginImport,
|
||||||
},
|
},
|
||||||
rules: {
|
rules: {
|
||||||
|
'import/consistent-type-specifier-style': ['error', 'prefer-top-level'],
|
||||||
'import/first': 'error',
|
'import/first': 'error',
|
||||||
'import/newline-after-import': 'error',
|
'import/newline-after-import': 'error',
|
||||||
'import/no-duplicates': 'error',
|
'import/no-duplicates': 'error',
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import type { Linter } from 'eslint';
|
import type { Linter } from 'eslint';
|
||||||
|
|
||||||
// @ts-expect-error - no types
|
|
||||||
import js from '@eslint/js';
|
import js from '@eslint/js';
|
||||||
import pluginUnusedImports from 'eslint-plugin-unused-imports';
|
import pluginUnusedImports from 'eslint-plugin-unused-imports';
|
||||||
import globals from 'globals';
|
import globals from 'globals';
|
||||||
|
|||||||
@@ -1,8 +1,13 @@
|
|||||||
import type { Linter } from 'eslint';
|
import type { Linter } from 'eslint';
|
||||||
|
|
||||||
import perfectionistPlugin from 'eslint-plugin-perfectionist';
|
import { interopDefault } from '../util';
|
||||||
|
|
||||||
export async function perfectionist(): Promise<Linter.Config[]> {
|
export async function perfectionist(): Promise<Linter.Config[]> {
|
||||||
|
const perfectionistPlugin = await interopDefault(
|
||||||
|
// @ts-expect-error - no types
|
||||||
|
import('eslint-plugin-perfectionist'),
|
||||||
|
);
|
||||||
|
|
||||||
return [
|
return [
|
||||||
perfectionistPlugin.configs['recommended-natural'],
|
perfectionistPlugin.configs['recommended-natural'],
|
||||||
{
|
{
|
||||||
@@ -19,21 +24,28 @@ export async function perfectionist(): Promise<Linter.Config[]> {
|
|||||||
{
|
{
|
||||||
customGroups: {
|
customGroups: {
|
||||||
type: {
|
type: {
|
||||||
vben: 'vben',
|
'vben-core-type': ['^@vben-core/.+'],
|
||||||
vue: 'vue',
|
'vben-type': ['^@vben/.+'],
|
||||||
|
'vue-type': ['^vue$', '^vue-.+', '^@vue/.+'],
|
||||||
},
|
},
|
||||||
value: {
|
value: {
|
||||||
vben: ['@vben*', '@vben/**/**', '@vben-core/**/**'],
|
vben: ['^@vben/.+'],
|
||||||
vue: ['vue', 'vue-*', '@vue*'],
|
'vben-core': ['^@vben-core/.+'],
|
||||||
|
vue: ['^vue$', '^vue-.+', '^@vue/.+'],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
environment: 'node',
|
||||||
groups: [
|
groups: [
|
||||||
['external-type', 'builtin-type', 'type'],
|
['external-type', 'builtin-type', 'type'],
|
||||||
|
'vue-type',
|
||||||
|
'vben-type',
|
||||||
|
'vben-core-type',
|
||||||
['parent-type', 'sibling-type', 'index-type'],
|
['parent-type', 'sibling-type', 'index-type'],
|
||||||
['internal-type'],
|
['internal-type'],
|
||||||
'builtin',
|
'builtin',
|
||||||
'vue',
|
'vue',
|
||||||
'vben',
|
'vben',
|
||||||
|
'vben-core',
|
||||||
'external',
|
'external',
|
||||||
'internal',
|
'internal',
|
||||||
['parent', 'sibling', 'index'],
|
['parent', 'sibling', 'index'],
|
||||||
@@ -43,12 +55,13 @@ export async function perfectionist(): Promise<Linter.Config[]> {
|
|||||||
'object',
|
'object',
|
||||||
'unknown',
|
'unknown',
|
||||||
],
|
],
|
||||||
internalPattern: ['#*', '#*/**'],
|
internalPattern: ['^#/.+'],
|
||||||
newlinesBetween: 'always',
|
newlinesBetween: 'always',
|
||||||
order: 'asc',
|
order: 'asc',
|
||||||
type: 'natural',
|
type: 'natural',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
'perfectionist/sort-modules': 'off',
|
||||||
'perfectionist/sort-named-exports': [
|
'perfectionist/sort-named-exports': [
|
||||||
'error',
|
'error',
|
||||||
{
|
{
|
||||||
@@ -67,42 +80,6 @@ export async function perfectionist(): Promise<Linter.Config[]> {
|
|||||||
groups: ['unknown', 'items', 'list', 'children'],
|
groups: ['unknown', 'items', 'list', 'children'],
|
||||||
ignorePattern: ['children'],
|
ignorePattern: ['children'],
|
||||||
order: 'asc',
|
order: 'asc',
|
||||||
partitionByComment: 'Part:**',
|
|
||||||
type: 'natural',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
'perfectionist/sort-vue-attributes': [
|
|
||||||
'error',
|
|
||||||
{
|
|
||||||
// Based on: https://vuejs.org/style-guide/rules-recommended.html#element-attribute-order
|
|
||||||
customGroups: {
|
|
||||||
/* eslint-disable perfectionist/sort-objects */
|
|
||||||
DEFINITION: '*(is|:is|v-is)',
|
|
||||||
LIST_RENDERING: 'v-for',
|
|
||||||
CONDITIONALS: 'v-*(else-if|if|else|show|cloak)',
|
|
||||||
RENDER_MODIFIERS: 'v-*(pre|once)',
|
|
||||||
GLOBAL: '*(:id|id)',
|
|
||||||
UNIQUE: '*(ref|key|:ref|:key)',
|
|
||||||
SLOT: '*(v-slot|slot)',
|
|
||||||
TWO_WAY_BINDING: '*(v-model|v-model:*)',
|
|
||||||
// OTHER_DIRECTIVES e.g. 'v-custom-directive'
|
|
||||||
EVENTS: '*(v-on|@*)',
|
|
||||||
CONTENT: 'v-*(html|text)',
|
|
||||||
/* eslint-enable perfectionist/sort-objects */
|
|
||||||
},
|
|
||||||
groups: [
|
|
||||||
'DEFINITION',
|
|
||||||
'LIST_RENDERING',
|
|
||||||
'CONDITIONALS',
|
|
||||||
'RENDER_MODIFIERS',
|
|
||||||
'GLOBAL',
|
|
||||||
'UNIQUE',
|
|
||||||
'SLOT',
|
|
||||||
'TWO_WAY_BINDING',
|
|
||||||
'unknown',
|
|
||||||
'EVENTS',
|
|
||||||
'CONTENT',
|
|
||||||
],
|
|
||||||
type: 'natural',
|
type: 'natural',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import { interopDefault } from '../util';
|
|||||||
|
|
||||||
export async function vue(): Promise<Linter.Config[]> {
|
export async function vue(): Promise<Linter.Config[]> {
|
||||||
const [pluginVue, parserVue, parserTs] = await Promise.all([
|
const [pluginVue, parserVue, parserTs] = await Promise.all([
|
||||||
// @ts-expect-error missing types
|
|
||||||
interopDefault(import('eslint-plugin-vue')),
|
interopDefault(import('eslint-plugin-vue')),
|
||||||
interopDefault(import('vue-eslint-parser')),
|
interopDefault(import('vue-eslint-parser')),
|
||||||
// @ts-expect-error missing types
|
// @ts-expect-error missing types
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@vben/stylelint-config",
|
"name": "@vben/stylelint-config",
|
||||||
"version": "5.5.0",
|
"version": "5.5.2",
|
||||||
"private": true,
|
"private": true,
|
||||||
"homepage": "https://github.com/vbenjs/vue-vben-admin",
|
"homepage": "https://github.com/vbenjs/vue-vben-admin",
|
||||||
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
|
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@vben/node-utils",
|
"name": "@vben/node-utils",
|
||||||
"version": "5.5.0",
|
"version": "5.5.2",
|
||||||
"private": true,
|
"private": true,
|
||||||
"homepage": "https://github.com/vbenjs/vue-vben-admin",
|
"homepage": "https://github.com/vbenjs/vue-vben-admin",
|
||||||
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
|
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
import ora, { type Ora } from 'ora';
|
import type { Ora } from 'ora';
|
||||||
|
|
||||||
|
import ora from 'ora';
|
||||||
|
|
||||||
interface SpinnerOptions {
|
interface SpinnerOptions {
|
||||||
failedText?: string;
|
failedText?: string;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@vben/tailwind-config",
|
"name": "@vben/tailwind-config",
|
||||||
"version": "5.5.0",
|
"version": "5.5.2",
|
||||||
"private": true,
|
"private": true,
|
||||||
"homepage": "https://github.com/vbenjs/vue-vben-admin",
|
"homepage": "https://github.com/vbenjs/vue-vben-admin",
|
||||||
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
|
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@vben/tsconfig",
|
"name": "@vben/tsconfig",
|
||||||
"version": "5.5.0",
|
"version": "5.5.2",
|
||||||
"private": true,
|
"private": true,
|
||||||
"homepage": "https://github.com/vbenjs/vue-vben-admin",
|
"homepage": "https://github.com/vbenjs/vue-vben-admin",
|
||||||
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
|
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@vben/vite-config",
|
"name": "@vben/vite-config",
|
||||||
"version": "5.5.0",
|
"version": "5.5.2",
|
||||||
"private": true,
|
"private": true,
|
||||||
"homepage": "https://github.com/vbenjs/vue-vben-admin",
|
"homepage": "https://github.com/vbenjs/vue-vben-admin",
|
||||||
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
|
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import type { UserConfig } from 'vite';
|
import type { CSSOptions, UserConfig } from 'vite';
|
||||||
|
|
||||||
import type { DefineApplicationOptions } from '../typing';
|
import type { DefineApplicationOptions } from '../typing';
|
||||||
|
|
||||||
@@ -100,7 +100,7 @@ function defineApplicationConfig(userConfigPromise?: DefineApplicationOptions) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function createCssOptions(injectGlobalScss = true) {
|
function createCssOptions(injectGlobalScss = true): CSSOptions {
|
||||||
const root = findMonorepoRoot();
|
const root = findMonorepoRoot();
|
||||||
return {
|
return {
|
||||||
preprocessorOptions: injectGlobalScss
|
preprocessorOptions: injectGlobalScss
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import type { PluginOption } from 'vite';
|
||||||
|
|
||||||
import fs from 'node:fs';
|
import fs from 'node:fs';
|
||||||
import fsp from 'node:fs/promises';
|
import fsp from 'node:fs/promises';
|
||||||
import { join } from 'node:path';
|
import { join } from 'node:path';
|
||||||
@@ -5,8 +7,6 @@ import { fileURLToPath } from 'node:url';
|
|||||||
|
|
||||||
import { readPackageJSON } from '@vben/node-utils';
|
import { readPackageJSON } from '@vben/node-utils';
|
||||||
|
|
||||||
import { type PluginOption } from 'vite';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 用于生成将loading样式注入到项目中
|
* 用于生成将loading样式注入到项目中
|
||||||
* 为多app提供loading样式,无需在每个 app -> index.html单独引入
|
* 为多app提供loading样式,无需在每个 app -> index.html单独引入
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user