mirror of
https://gitee.com/dapppp/ruoyi-plus-vben5.git
synced 2026-04-02 23:33:23 +08:00
Compare commits
47 Commits
1.3.0-back
...
1.3.1-back
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4dc7543bb6 | ||
|
|
d8e7945f9f | ||
|
|
2fd1fdcb32 | ||
|
|
44ba945a12 | ||
|
|
2680101872 | ||
|
|
1c2e27613c | ||
|
|
3e7a2336b0 | ||
|
|
022d5182d7 | ||
|
|
329a176a5c | ||
|
|
41962ef380 | ||
|
|
9003df713c | ||
|
|
ebb4738be7 | ||
|
|
ad7c33a7d6 | ||
|
|
a114335a56 | ||
|
|
9379093a4f | ||
|
|
c9014d0338 | ||
|
|
b8ec8edb38 | ||
|
|
ed26dca64e | ||
|
|
08c6496e24 | ||
|
|
a8c5df38e9 | ||
|
|
5b9f647cfd | ||
|
|
ae6bf6ee53 | ||
|
|
77894d5df4 | ||
|
|
ba8f36a2c0 | ||
|
|
133abe9ded | ||
|
|
ef390ae636 | ||
|
|
6d2f4e8486 | ||
|
|
c4962aaf85 | ||
|
|
f7128b099e | ||
|
|
5510b6dea4 | ||
|
|
98f658d46f | ||
|
|
e307db2f3d | ||
|
|
e6dab8300d | ||
|
|
eb9f278e7f | ||
|
|
34e5812de9 | ||
|
|
07587c0faf | ||
|
|
88316d7498 | ||
|
|
53e02d46c2 | ||
|
|
5e1de6fc79 | ||
|
|
7463df053a | ||
|
|
1286b52135 | ||
|
|
92fe406ae9 | ||
|
|
5b72d9b79d | ||
|
|
b97fe47afd | ||
|
|
4f2354b53a | ||
|
|
8f9006c96d | ||
|
|
71e8d12b70 |
14
.vscode/settings.json
vendored
14
.vscode/settings.json
vendored
@@ -224,10 +224,20 @@
|
|||||||
"commentTranslate.multiLineMerge": true,
|
"commentTranslate.multiLineMerge": true,
|
||||||
"vue.server.hybridMode": true,
|
"vue.server.hybridMode": true,
|
||||||
"vitest.disableWorkspaceWarning": true,
|
"vitest.disableWorkspaceWarning": true,
|
||||||
"cSpell.words": ["tinymce", "vditor"],
|
|
||||||
"typescript.tsdk": "node_modules/typescript/lib",
|
"typescript.tsdk": "node_modules/typescript/lib",
|
||||||
"editor.linkedEditing": true, // 自动同步更改html标签,
|
"editor.linkedEditing": true, // 自动同步更改html标签,
|
||||||
"vscodeCustomCodeColor.highlightValue": "v-access", // v-access显示的颜色
|
"vscodeCustomCodeColor.highlightValue": "v-access", // v-access显示的颜色
|
||||||
"vscodeCustomCodeColor.highlightValueColor": "#CCFFFF",
|
"vscodeCustomCodeColor.highlightValueColor": "#CCFFFF",
|
||||||
"oxc.enable": false
|
"oxc.enable": false,
|
||||||
|
"cSpell.words": [
|
||||||
|
"archiver",
|
||||||
|
"axios",
|
||||||
|
"dotenv",
|
||||||
|
"isequal",
|
||||||
|
"jspm",
|
||||||
|
"napi",
|
||||||
|
"nolebase",
|
||||||
|
"rollup",
|
||||||
|
"vitest"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
20
CHANGELOG.md
20
CHANGELOG.md
@@ -1,3 +1,23 @@
|
|||||||
|
# 1.3.1
|
||||||
|
|
||||||
|
**REFACTOR**
|
||||||
|
|
||||||
|
- 所有Modal/Drawer表单关闭前会进行表单数据对比来弹出提示框
|
||||||
|
- 字典项颜色选择从`原生input type=color`改为`vue3-colorpicker`组件
|
||||||
|
- 全局Header: ClientID 更改大小写 [spring的问题导致](https://gitee.com/dapppp/ruoyi-plus-vben5/issues/IC0BDS)
|
||||||
|
|
||||||
|
**BUG FIX**
|
||||||
|
|
||||||
|
- getVxePopupContainer逻辑调整 解决表格固定高度展开不全的问题
|
||||||
|
|
||||||
|
**FEATURES**
|
||||||
|
|
||||||
|
- 字典渲染支持loading(length为0情况)
|
||||||
|
|
||||||
|
**OTHERS**
|
||||||
|
|
||||||
|
- useForm的组件改为异步导入(官方更新) bootstrap.js体积从2M降到600K 首屏加载速度提升
|
||||||
|
|
||||||
# 1.3.0
|
# 1.3.0
|
||||||
|
|
||||||
注意: 如果你使用老版本的`文件上传`/`图片上传` 可暂时使用
|
注意: 如果你使用老版本的`文件上传`/`图片上传` 可暂时使用
|
||||||
|
|||||||
@@ -54,7 +54,8 @@
|
|||||||
"tinymce": "^7.3.0",
|
"tinymce": "^7.3.0",
|
||||||
"unplugin-vue-components": "^0.27.3",
|
"unplugin-vue-components": "^0.27.3",
|
||||||
"vue": "catalog:",
|
"vue": "catalog:",
|
||||||
"vue-router": "catalog:"
|
"vue-router": "catalog:",
|
||||||
|
"vue3-colorpicker": "^2.3.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/crypto-js": "^4.2.2",
|
"@types/crypto-js": "^4.2.2",
|
||||||
|
|||||||
@@ -8,40 +8,80 @@ import type { Component } from 'vue';
|
|||||||
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 { computed, defineComponent, getCurrentInstance, h, ref } from 'vue';
|
import {
|
||||||
|
computed,
|
||||||
|
defineAsyncComponent,
|
||||||
|
defineComponent,
|
||||||
|
getCurrentInstance,
|
||||||
|
h,
|
||||||
|
ref,
|
||||||
|
} 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 { notification } from 'ant-design-vue';
|
||||||
AutoComplete,
|
|
||||||
Button,
|
|
||||||
Checkbox,
|
|
||||||
CheckboxGroup,
|
|
||||||
DatePicker,
|
|
||||||
Divider,
|
|
||||||
Input,
|
|
||||||
InputNumber,
|
|
||||||
InputPassword,
|
|
||||||
Mentions,
|
|
||||||
notification,
|
|
||||||
Radio,
|
|
||||||
RadioGroup,
|
|
||||||
RangePicker,
|
|
||||||
Rate,
|
|
||||||
Select,
|
|
||||||
Space,
|
|
||||||
Switch,
|
|
||||||
Textarea,
|
|
||||||
TimePicker,
|
|
||||||
TreeSelect,
|
|
||||||
Upload,
|
|
||||||
} from 'ant-design-vue';
|
|
||||||
|
|
||||||
import { Tinymce as RichTextarea } from '#/components/tinymce';
|
|
||||||
import { FileUpload, ImageUpload } from '#/components/upload';
|
|
||||||
import { FileUploadOld, ImageUploadOld } from '#/components/upload-old';
|
import { FileUploadOld, ImageUploadOld } from '#/components/upload-old';
|
||||||
|
|
||||||
|
const RichTextarea = defineAsyncComponent(() =>
|
||||||
|
import('#/components/tinymce/index').then((res) => res.Tinymce),
|
||||||
|
);
|
||||||
|
|
||||||
|
const FileUpload = defineAsyncComponent(() =>
|
||||||
|
import('#/components/upload').then((res) => res.FileUpload),
|
||||||
|
);
|
||||||
|
|
||||||
|
const ImageUpload = defineAsyncComponent(() =>
|
||||||
|
import('#/components/upload').then((res) => res.ImageUpload),
|
||||||
|
);
|
||||||
|
|
||||||
|
const AutoComplete = defineAsyncComponent(
|
||||||
|
() => import('ant-design-vue/es/auto-complete'),
|
||||||
|
);
|
||||||
|
const Button = defineAsyncComponent(() => import('ant-design-vue/es/button'));
|
||||||
|
const Checkbox = defineAsyncComponent(
|
||||||
|
() => import('ant-design-vue/es/checkbox'),
|
||||||
|
);
|
||||||
|
const CheckboxGroup = defineAsyncComponent(() =>
|
||||||
|
import('ant-design-vue/es/checkbox').then((res) => res.CheckboxGroup),
|
||||||
|
);
|
||||||
|
const DatePicker = defineAsyncComponent(
|
||||||
|
() => import('ant-design-vue/es/date-picker'),
|
||||||
|
);
|
||||||
|
const Divider = defineAsyncComponent(() => import('ant-design-vue/es/divider'));
|
||||||
|
const Input = defineAsyncComponent(() => import('ant-design-vue/es/input'));
|
||||||
|
const InputNumber = defineAsyncComponent(
|
||||||
|
() => import('ant-design-vue/es/input-number'),
|
||||||
|
);
|
||||||
|
const InputPassword = defineAsyncComponent(() =>
|
||||||
|
import('ant-design-vue/es/input').then((res) => res.InputPassword),
|
||||||
|
);
|
||||||
|
const Mentions = defineAsyncComponent(
|
||||||
|
() => import('ant-design-vue/es/mentions'),
|
||||||
|
);
|
||||||
|
const Radio = defineAsyncComponent(() => import('ant-design-vue/es/radio'));
|
||||||
|
const RadioGroup = defineAsyncComponent(() =>
|
||||||
|
import('ant-design-vue/es/radio').then((res) => res.RadioGroup),
|
||||||
|
);
|
||||||
|
const RangePicker = defineAsyncComponent(() =>
|
||||||
|
import('ant-design-vue/es/date-picker').then((res) => res.RangePicker),
|
||||||
|
);
|
||||||
|
const Rate = defineAsyncComponent(() => import('ant-design-vue/es/rate'));
|
||||||
|
const Select = defineAsyncComponent(() => import('ant-design-vue/es/select'));
|
||||||
|
const Space = defineAsyncComponent(() => import('ant-design-vue/es/space'));
|
||||||
|
const Switch = defineAsyncComponent(() => import('ant-design-vue/es/switch'));
|
||||||
|
const Textarea = defineAsyncComponent(() =>
|
||||||
|
import('ant-design-vue/es/input').then((res) => res.Textarea),
|
||||||
|
);
|
||||||
|
const TimePicker = defineAsyncComponent(
|
||||||
|
() => import('ant-design-vue/es/time-picker'),
|
||||||
|
);
|
||||||
|
const TreeSelect = defineAsyncComponent(
|
||||||
|
() => import('ant-design-vue/es/tree-select'),
|
||||||
|
);
|
||||||
|
const Upload = defineAsyncComponent(() => import('ant-design-vue/es/upload'));
|
||||||
|
|
||||||
const withDefaultPlaceholder = <T extends Component>(
|
const withDefaultPlaceholder = <T extends Component>(
|
||||||
component: T,
|
component: T,
|
||||||
type: 'input' | 'select',
|
type: 'input' | 'select',
|
||||||
|
|||||||
@@ -93,9 +93,12 @@ function createRequestClient(baseURL: string) {
|
|||||||
const language = preferences.app.locale.replace('-', '_');
|
const language = preferences.app.locale.replace('-', '_');
|
||||||
config.headers['Accept-Language'] = language;
|
config.headers['Accept-Language'] = language;
|
||||||
config.headers['Content-Language'] = language;
|
config.headers['Content-Language'] = language;
|
||||||
// 添加全局clientId
|
/**
|
||||||
config.headers.clientId = clientId;
|
* 添加全局clientId
|
||||||
|
* 关于header的clientId被错误绑定到实体类
|
||||||
|
* https://gitee.com/dapppp/ruoyi-plus-vben5/issues/IC0BDS
|
||||||
|
*/
|
||||||
|
config.headers.ClientID = clientId;
|
||||||
/**
|
/**
|
||||||
* 格式化get/delete参数
|
* 格式化get/delete参数
|
||||||
* 如果包含自定义的paramsSerializer则不走此逻辑
|
* 如果包含自定义的paramsSerializer则不走此逻辑
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
import { createApp, watchEffect } from 'vue';
|
import { createApp, watchEffect } from 'vue';
|
||||||
|
|
||||||
import { registerAccessDirective } from '@vben/access';
|
import { registerAccessDirective } from '@vben/access';
|
||||||
import { initTippy, registerLoadingDirective } from '@vben/common-ui';
|
import { registerLoadingDirective } from '@vben/common-ui/es/loading';
|
||||||
import { MotionPlugin } from '@vben/plugins/motion';
|
|
||||||
import { preferences } from '@vben/preferences';
|
import { preferences } from '@vben/preferences';
|
||||||
import { initStores } from '@vben/stores';
|
import { initStores } from '@vben/stores';
|
||||||
import '@vben/styles';
|
import '@vben/styles';
|
||||||
@@ -50,12 +49,14 @@ async function bootstrap(namespace: string) {
|
|||||||
registerAccessDirective(app);
|
registerAccessDirective(app);
|
||||||
|
|
||||||
// 初始化 tippy
|
// 初始化 tippy
|
||||||
|
const { initTippy } = await import('@vben/common-ui/es/tippy');
|
||||||
initTippy(app);
|
initTippy(app);
|
||||||
|
|
||||||
// 配置路由及路由守卫
|
// 配置路由及路由守卫
|
||||||
app.use(router);
|
app.use(router);
|
||||||
|
|
||||||
// 配置Motion插件
|
// 配置Motion插件
|
||||||
|
const { MotionPlugin } = await import('@vben/plugins/motion');
|
||||||
app.use(MotionPlugin);
|
app.use(MotionPlugin);
|
||||||
|
|
||||||
// 动态更新标题
|
// 动态更新标题
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import type { DictData } from '#/api/system/dict/dict-data-model';
|
|||||||
|
|
||||||
import { computed } from 'vue';
|
import { computed } from 'vue';
|
||||||
|
|
||||||
import { Tag } from 'ant-design-vue';
|
import { Spin, Tag } from 'ant-design-vue';
|
||||||
|
|
||||||
import { tagTypes } from './data';
|
import { tagTypes } from './data';
|
||||||
|
|
||||||
@@ -41,12 +41,22 @@ const label = computed<number | string>(() => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const tagComponent = computed(() => (color.value ? Tag : 'div'));
|
const tagComponent = computed(() => (color.value ? Tag : 'div'));
|
||||||
|
|
||||||
|
const loading = computed(() => {
|
||||||
|
return props.dicts?.length === 0;
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<component :is="tagComponent" :class="cssClass" :color="color">
|
<component
|
||||||
|
v-if="!loading"
|
||||||
|
:is="tagComponent"
|
||||||
|
:class="cssClass"
|
||||||
|
:color="color"
|
||||||
|
>
|
||||||
{{ label }}
|
{{ label }}
|
||||||
</component>
|
</component>
|
||||||
|
<Spin v-else :spinning="true" size="small" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -204,7 +204,12 @@ export function useUpload(
|
|||||||
if (props.maxCount === 1) {
|
if (props.maxCount === 1) {
|
||||||
bindValue.value = ossId;
|
bindValue.value = ossId;
|
||||||
} else {
|
} else {
|
||||||
(bindValue.value as string[]).push(ossId);
|
// 给默认值
|
||||||
|
if (!Array.isArray(bindValue.value)) {
|
||||||
|
bindValue.value = [];
|
||||||
|
}
|
||||||
|
// 直接使用.value无法触发useForm的更新(原生是正常的) 需要修改地址
|
||||||
|
bindValue.value = [...bindValue.value, ossId];
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -344,12 +349,16 @@ export function useUpload(
|
|||||||
!props.keepMissingId &&
|
!props.keepMissingId &&
|
||||||
props.maxCount !== 1
|
props.maxCount !== 1
|
||||||
) {
|
) {
|
||||||
bindValue.value = (bindValue.value as string[]).filter((ossId) =>
|
// 给默认值
|
||||||
|
if (!Array.isArray(bindValue.value)) {
|
||||||
|
bindValue.value = [];
|
||||||
|
}
|
||||||
|
bindValue.value = bindValue.value.filter((ossId) =>
|
||||||
resp.map((res) => res.ossId).includes(ossId),
|
resp.map((res) => res.ossId).includes(ossId),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{ immediate: true, deep: props.deepWatch },
|
{ immediate: true },
|
||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -87,13 +87,6 @@ export interface BaseUploadProps {
|
|||||||
* @default false
|
* @default false
|
||||||
*/
|
*/
|
||||||
enableDragUpload?: boolean;
|
enableDragUpload?: boolean;
|
||||||
/**
|
|
||||||
* 是否开启深度监听
|
|
||||||
* 默认外部的数组地址重新改变才会触发watch 不会监听内部元素的变化
|
|
||||||
* 开启后 无论内部还是外部改变都会触发查询信息接口(包括上传后, 删除等操作都会触发)
|
|
||||||
* @default false
|
|
||||||
*/
|
|
||||||
deepWatch?: boolean;
|
|
||||||
/**
|
/**
|
||||||
* 当ossId查询不到文件信息时 比如被删除了
|
* 当ossId查询不到文件信息时 比如被删除了
|
||||||
* 是否保留列表对应的ossId 默认不保留
|
* 是否保留列表对应的ossId 默认不保留
|
||||||
|
|||||||
@@ -21,6 +21,7 @@
|
|||||||
"preview": "Preview",
|
"preview": "Preview",
|
||||||
"tip": "Tip",
|
"tip": "Tip",
|
||||||
"enable": "On",
|
"enable": "On",
|
||||||
"disable": "Off"
|
"disable": "Off",
|
||||||
|
"beforeCloseTip": "You have unsaved changes. Are you sure you want to exit?"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,6 +21,7 @@
|
|||||||
"preview": "预览",
|
"preview": "预览",
|
||||||
"tip": "提示",
|
"tip": "提示",
|
||||||
"enable": "启用",
|
"enable": "启用",
|
||||||
"disable": "禁用"
|
"disable": "禁用",
|
||||||
|
"beforeCloseTip": "您有未保存的更改,确认要退出吗?"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,10 +2,10 @@ import type { RouteRecordRaw } from 'vue-router';
|
|||||||
|
|
||||||
import { DEFAULT_HOME_PATH, LOGIN_PATH } from '@vben/constants';
|
import { DEFAULT_HOME_PATH, LOGIN_PATH } from '@vben/constants';
|
||||||
|
|
||||||
import { AuthPageLayout, BasicLayout } from '#/layouts';
|
|
||||||
import { $t } from '#/locales';
|
import { $t } from '#/locales';
|
||||||
import Login from '#/views/_core/authentication/login.vue';
|
|
||||||
|
|
||||||
|
const BasicLayout = () => import('#/layouts/basic.vue');
|
||||||
|
const AuthPageLayout = () => import('#/layouts/auth.vue');
|
||||||
/** 全局404页面 */
|
/** 全局404页面 */
|
||||||
const fallbackNotFoundRoute: RouteRecordRaw = {
|
const fallbackNotFoundRoute: RouteRecordRaw = {
|
||||||
component: () => import('#/views/_core/fallback/not-found.vue'),
|
component: () => import('#/views/_core/fallback/not-found.vue'),
|
||||||
@@ -58,7 +58,7 @@ const coreRoutes: RouteRecordRaw[] = [
|
|||||||
{
|
{
|
||||||
name: 'Login',
|
name: 'Login',
|
||||||
path: 'login',
|
path: 'login',
|
||||||
component: Login,
|
component: () => import('#/views/_core/authentication/login.vue'),
|
||||||
meta: {
|
meta: {
|
||||||
title: $t('page.auth.login'),
|
title: $t('page.auth.login'),
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -59,6 +59,7 @@ export const useDictStore = defineStore('app-dict', () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function resetCache() {
|
function resetCache() {
|
||||||
|
dictRequestCache.clear();
|
||||||
dictOptionsMap.clear();
|
dictOptionsMap.clear();
|
||||||
/**
|
/**
|
||||||
* 不需要清空dictRequestCache 每次请求成功/失败都清空key
|
* 不需要清空dictRequestCache 每次请求成功/失败都清空key
|
||||||
|
|||||||
@@ -29,43 +29,52 @@ interface BeforeCloseDiffProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @deprecated 注意为实验性功能 可能有api变动/被移除
|
* 用于Drawer/Modal使用 判断表单是否有变动来决定是否弹窗提示
|
||||||
* @param props props
|
* @param props props
|
||||||
* @returns hook
|
* @returns hook
|
||||||
*
|
|
||||||
* 待解决问题: 网速慢情况直接关闭 会导致数据不一致问题
|
|
||||||
* 但是使用api.lock会导致在报错情况无法关闭(因为目前代码没有finally)
|
|
||||||
*/
|
*/
|
||||||
export function useBeforeCloseDiff(props: BeforeCloseDiffProps) {
|
export function useBeforeCloseDiff(props: BeforeCloseDiffProps) {
|
||||||
const { initializedGetter, currentGetter, compare } = props;
|
const { initializedGetter, currentGetter, compare } = props;
|
||||||
|
/**
|
||||||
|
* 记录初始值 json
|
||||||
|
*/
|
||||||
const initialized = ref<string>('');
|
const initialized = ref<string>('');
|
||||||
|
/**
|
||||||
|
* 是否已经初始化了 通过这个值判断是否需要进行对比 为false直接关闭 不弹窗
|
||||||
|
*/
|
||||||
const isInitialized = ref(false);
|
const isInitialized = ref(false);
|
||||||
const isSubmitted = ref(false);
|
|
||||||
|
|
||||||
async function updateInitialized(data?: string) {
|
/**
|
||||||
|
* 标记是否已经完成初始化 后续需要进行对比
|
||||||
|
* @param data 自定义初始化数据 可选
|
||||||
|
*/
|
||||||
|
async function markInitialized(data?: string) {
|
||||||
initialized.value = data || (await initializedGetter());
|
initialized.value = data || (await initializedGetter());
|
||||||
isInitialized.value = true;
|
isInitialized.value = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
function setSubmitted() {
|
/**
|
||||||
isSubmitted.value = true;
|
* 重置初始化状态 需要在closed前调用 或者打开窗口时
|
||||||
|
*/
|
||||||
|
function resetInitialized() {
|
||||||
|
initialized.value = '';
|
||||||
|
isInitialized.value = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 提供给useVbenForm/useVbenDrawer使用
|
||||||
|
* @returns 是否允许关闭
|
||||||
|
*/
|
||||||
async function onBeforeClose(): Promise<boolean> {
|
async function onBeforeClose(): Promise<boolean> {
|
||||||
// 如果还未初始化,直接允许关闭
|
// 如果还未初始化,直接允许关闭
|
||||||
if (!isInitialized.value) {
|
if (!isInitialized.value) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
// 如果已经提交过,直接允许关闭
|
|
||||||
if (isSubmitted.value) {
|
|
||||||
// 重置状态
|
|
||||||
isSubmitted.value = false;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// 获取当前表单数据
|
||||||
const current = await currentGetter();
|
const current = await currentGetter();
|
||||||
|
// 自定义比较的情况
|
||||||
if (isFunction(compare) && compare(initialized.value, current)) {
|
if (isFunction(compare) && compare(initialized.value, current)) {
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
@@ -79,7 +88,7 @@ export function useBeforeCloseDiff(props: BeforeCloseDiffProps) {
|
|||||||
return new Promise<boolean>((resolve) => {
|
return new Promise<boolean>((resolve) => {
|
||||||
Modal.confirm({
|
Modal.confirm({
|
||||||
title: $t('pages.common.tip'),
|
title: $t('pages.common.tip'),
|
||||||
content: $t('您有未保存的更改,确认要退出吗?'),
|
content: $t('pages.common.beforeCloseTip'),
|
||||||
centered: true,
|
centered: true,
|
||||||
okButtonProps: { danger: true },
|
okButtonProps: { danger: true },
|
||||||
cancelText: $t('common.cancel'),
|
cancelText: $t('common.cancel'),
|
||||||
@@ -99,8 +108,8 @@ export function useBeforeCloseDiff(props: BeforeCloseDiffProps) {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
onBeforeClose,
|
onBeforeClose,
|
||||||
updateInitialized,
|
markInitialized,
|
||||||
setSubmitted,
|
resetInitialized,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { cloneDeep } from '@vben/utils';
|
|||||||
|
|
||||||
import { useVbenForm } from '#/adapter/form';
|
import { useVbenForm } from '#/adapter/form';
|
||||||
import { clientAdd, clientInfo, clientUpdate } from '#/api/system/client';
|
import { clientAdd, clientInfo, clientUpdate } from '#/api/system/client';
|
||||||
|
import { defaultFormValueGetter, useBeforeCloseDiff } from '#/utils/popup';
|
||||||
|
|
||||||
import { drawerSchema } from './data';
|
import { drawerSchema } from './data';
|
||||||
import SecretInput from './secret-input.vue';
|
import SecretInput from './secret-input.vue';
|
||||||
@@ -55,6 +56,13 @@ function setupForm(update: boolean) {
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { onBeforeClose, markInitialized, resetInitialized } = useBeforeCloseDiff(
|
||||||
|
{
|
||||||
|
initializedGetter: defaultFormValueGetter(formApi),
|
||||||
|
currentGetter: defaultFormValueGetter(formApi),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
// 提取生成状态字段Schema的函数
|
// 提取生成状态字段Schema的函数
|
||||||
const getStatusSchema = (disabled: boolean) => [
|
const getStatusSchema = (disabled: boolean) => [
|
||||||
{
|
{
|
||||||
@@ -64,13 +72,15 @@ const getStatusSchema = (disabled: boolean) => [
|
|||||||
];
|
];
|
||||||
|
|
||||||
const [BasicDrawer, drawerApi] = useVbenDrawer({
|
const [BasicDrawer, drawerApi] = useVbenDrawer({
|
||||||
onCancel: handleCancel,
|
onBeforeClose,
|
||||||
|
onClosed: handleClosed,
|
||||||
onConfirm: handleConfirm,
|
onConfirm: handleConfirm,
|
||||||
async onOpenChange(isOpen) {
|
async onOpenChange(isOpen) {
|
||||||
if (!isOpen) {
|
if (!isOpen) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
drawerApi.drawerLoading(true);
|
drawerApi.drawerLoading(true);
|
||||||
|
|
||||||
const { id } = drawerApi.getData() as { id?: number | string };
|
const { id } = drawerApi.getData() as { id?: number | string };
|
||||||
isUpdate.value = !!id;
|
isUpdate.value = !!id;
|
||||||
// 初始化
|
// 初始化
|
||||||
@@ -84,36 +94,39 @@ const [BasicDrawer, drawerApi] = useVbenDrawer({
|
|||||||
// 新增模式: 确保状态字段可用
|
// 新增模式: 确保状态字段可用
|
||||||
formApi.updateSchema(getStatusSchema(false));
|
formApi.updateSchema(getStatusSchema(false));
|
||||||
}
|
}
|
||||||
|
await markInitialized();
|
||||||
|
|
||||||
drawerApi.drawerLoading(false);
|
drawerApi.drawerLoading(false);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
async function handleConfirm() {
|
async function handleConfirm() {
|
||||||
try {
|
try {
|
||||||
drawerApi.drawerLoading(true);
|
drawerApi.lock(true);
|
||||||
const { valid } = await formApi.validate();
|
const { valid } = await formApi.validate();
|
||||||
if (!valid) {
|
if (!valid) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const data = cloneDeep(await formApi.getValues());
|
const data = cloneDeep(await formApi.getValues());
|
||||||
await (isUpdate.value ? clientUpdate(data) : clientAdd(data));
|
await (isUpdate.value ? clientUpdate(data) : clientAdd(data));
|
||||||
|
resetInitialized();
|
||||||
emit('reload');
|
emit('reload');
|
||||||
await handleCancel();
|
drawerApi.close();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
} finally {
|
} finally {
|
||||||
drawerApi.drawerLoading(false);
|
drawerApi.lock(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleCancel() {
|
async function handleClosed() {
|
||||||
drawerApi.close();
|
|
||||||
await formApi.resetForm();
|
await formApi.resetForm();
|
||||||
|
resetInitialized();
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<BasicDrawer :close-on-click-modal="false" :title="title" class="w-[600px]">
|
<BasicDrawer :title="title" class="w-[600px]">
|
||||||
<BasicForm>
|
<BasicForm>
|
||||||
<template #clientSecret="slotProps">
|
<template #clientSecret="slotProps">
|
||||||
<SecretInput v-bind="slotProps" :disabled="isUpdate" />
|
<SecretInput v-bind="slotProps" :disabled="isUpdate" />
|
||||||
|
|||||||
@@ -26,10 +26,12 @@ const [BasicForm, formApi] = useVbenForm({
|
|||||||
showDefaultActions: false,
|
showDefaultActions: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
const { onBeforeClose, updateInitialized, setSubmitted } = useBeforeCloseDiff({
|
const { onBeforeClose, markInitialized, resetInitialized } = useBeforeCloseDiff(
|
||||||
initializedGetter: defaultFormValueGetter(formApi),
|
{
|
||||||
currentGetter: defaultFormValueGetter(formApi),
|
initializedGetter: defaultFormValueGetter(formApi),
|
||||||
});
|
currentGetter: defaultFormValueGetter(formApi),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
const [BasicModal, modalApi] = useVbenModal({
|
const [BasicModal, modalApi] = useVbenModal({
|
||||||
fullscreenButton: false,
|
fullscreenButton: false,
|
||||||
@@ -40,22 +42,18 @@ const [BasicModal, modalApi] = useVbenModal({
|
|||||||
if (!isOpen) {
|
if (!isOpen) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
try {
|
modalApi.modalLoading(true);
|
||||||
modalApi.lock(true);
|
|
||||||
|
|
||||||
const { id } = modalApi.getData() as { id?: number | string };
|
const { id } = modalApi.getData() as { id?: number | string };
|
||||||
isUpdate.value = !!id;
|
isUpdate.value = !!id;
|
||||||
|
|
||||||
if (isUpdate.value && id) {
|
if (isUpdate.value && id) {
|
||||||
const record = await configInfo(id);
|
const record = await configInfo(id);
|
||||||
await formApi.setValues(record);
|
await formApi.setValues(record);
|
||||||
}
|
|
||||||
await updateInitialized();
|
|
||||||
} catch (error) {
|
|
||||||
console.error(error);
|
|
||||||
} finally {
|
|
||||||
modalApi.lock(false);
|
|
||||||
}
|
}
|
||||||
|
await markInitialized();
|
||||||
|
|
||||||
|
modalApi.modalLoading(false);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -68,7 +66,7 @@ async function handleConfirm() {
|
|||||||
}
|
}
|
||||||
const data = cloneDeep(await formApi.getValues());
|
const data = cloneDeep(await formApi.getValues());
|
||||||
await (isUpdate.value ? configUpdate(data) : configAdd(data));
|
await (isUpdate.value ? configUpdate(data) : configAdd(data));
|
||||||
setSubmitted();
|
resetInitialized();
|
||||||
emit('reload');
|
emit('reload');
|
||||||
modalApi.close();
|
modalApi.close();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -80,6 +78,7 @@ async function handleConfirm() {
|
|||||||
|
|
||||||
async function handleClosed() {
|
async function handleClosed() {
|
||||||
await formApi.resetForm();
|
await formApi.resetForm();
|
||||||
|
resetInitialized();
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import {
|
|||||||
deptUpdate,
|
deptUpdate,
|
||||||
} from '#/api/system/dept';
|
} from '#/api/system/dept';
|
||||||
import { listUserByDeptId } from '#/api/system/user';
|
import { listUserByDeptId } from '#/api/system/user';
|
||||||
|
import { defaultFormValueGetter, useBeforeCloseDiff } from '#/utils/popup';
|
||||||
|
|
||||||
import { drawerSchema } from './data';
|
import { drawerSchema } from './data';
|
||||||
|
|
||||||
@@ -107,8 +108,16 @@ async function setLeaderOptions() {
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { onBeforeClose, markInitialized, resetInitialized } = useBeforeCloseDiff(
|
||||||
|
{
|
||||||
|
initializedGetter: defaultFormValueGetter(formApi),
|
||||||
|
currentGetter: defaultFormValueGetter(formApi),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
const [BasicDrawer, drawerApi] = useVbenDrawer({
|
const [BasicDrawer, drawerApi] = useVbenDrawer({
|
||||||
onCancel: handleCancel,
|
onBeforeClose,
|
||||||
|
onClosed: handleClosed,
|
||||||
onConfirm: handleConfirm,
|
onConfirm: handleConfirm,
|
||||||
async onOpenChange(isOpen) {
|
async onOpenChange(isOpen) {
|
||||||
if (!isOpen) {
|
if (!isOpen) {
|
||||||
@@ -130,6 +139,7 @@ const [BasicDrawer, drawerApi] = useVbenDrawer({
|
|||||||
await (update && id ? initDeptUsers(id) : setLeaderOptions());
|
await (update && id ? initDeptUsers(id) : setLeaderOptions());
|
||||||
/** 部门选择 下拉框 */
|
/** 部门选择 下拉框 */
|
||||||
await initDeptSelect(id);
|
await initDeptSelect(id);
|
||||||
|
await markInitialized();
|
||||||
|
|
||||||
drawerApi.drawerLoading(false);
|
drawerApi.drawerLoading(false);
|
||||||
},
|
},
|
||||||
@@ -137,30 +147,31 @@ const [BasicDrawer, drawerApi] = useVbenDrawer({
|
|||||||
|
|
||||||
async function handleConfirm() {
|
async function handleConfirm() {
|
||||||
try {
|
try {
|
||||||
drawerApi.drawerLoading(true);
|
drawerApi.lock(true);
|
||||||
const { valid } = await formApi.validate();
|
const { valid } = await formApi.validate();
|
||||||
if (!valid) {
|
if (!valid) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const data = cloneDeep(await formApi.getValues());
|
const data = cloneDeep(await formApi.getValues());
|
||||||
await (isUpdate.value ? deptUpdate(data) : deptAdd(data));
|
await (isUpdate.value ? deptUpdate(data) : deptAdd(data));
|
||||||
|
resetInitialized();
|
||||||
emit('reload');
|
emit('reload');
|
||||||
await handleCancel();
|
drawerApi.close();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
} finally {
|
} finally {
|
||||||
drawerApi.drawerLoading(false);
|
drawerApi.lock(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleCancel() {
|
async function handleClosed() {
|
||||||
drawerApi.close();
|
|
||||||
await formApi.resetForm();
|
await formApi.resetForm();
|
||||||
|
resetInitialized();
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<BasicDrawer :close-on-click-modal="false" :title="title" class="w-[600px]">
|
<BasicDrawer :title="title" class="w-[600px]">
|
||||||
<BasicForm />
|
<BasicForm />
|
||||||
</BasicDrawer>
|
</BasicDrawer>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import {
|
|||||||
dictDetailInfo,
|
dictDetailInfo,
|
||||||
} from '#/api/system/dict/dict-data';
|
} from '#/api/system/dict/dict-data';
|
||||||
import { tagTypes } from '#/components/dict';
|
import { tagTypes } from '#/components/dict';
|
||||||
|
import { defaultFormValueGetter, useBeforeCloseDiff } from '#/utils/popup';
|
||||||
|
|
||||||
import { drawerSchema } from './data';
|
import { drawerSchema } from './data';
|
||||||
import TagStylePicker from './tag-style-picker.vue';
|
import TagStylePicker from './tag-style-picker.vue';
|
||||||
@@ -57,8 +58,16 @@ function setupSelectType(listClass: string) {
|
|||||||
selectType.value = isDefault ? 'default' : 'custom';
|
selectType.value = isDefault ? 'default' : 'custom';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { onBeforeClose, markInitialized, resetInitialized } = useBeforeCloseDiff(
|
||||||
|
{
|
||||||
|
initializedGetter: defaultFormValueGetter(formApi),
|
||||||
|
currentGetter: defaultFormValueGetter(formApi),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
const [BasicDrawer, drawerApi] = useVbenDrawer({
|
const [BasicDrawer, drawerApi] = useVbenDrawer({
|
||||||
onCancel: handleCancel,
|
onBeforeClose,
|
||||||
|
onClosed: handleClosed,
|
||||||
onConfirm: handleConfirm,
|
onConfirm: handleConfirm,
|
||||||
async onOpenChange(isOpen) {
|
async onOpenChange(isOpen) {
|
||||||
if (!isOpen) {
|
if (!isOpen) {
|
||||||
@@ -75,6 +84,7 @@ const [BasicDrawer, drawerApi] = useVbenDrawer({
|
|||||||
setupSelectType(record.listClass);
|
setupSelectType(record.listClass);
|
||||||
await formApi.setValues(record);
|
await formApi.setValues(record);
|
||||||
}
|
}
|
||||||
|
await markInitialized();
|
||||||
|
|
||||||
drawerApi.drawerLoading(false);
|
drawerApi.drawerLoading(false);
|
||||||
},
|
},
|
||||||
@@ -82,7 +92,7 @@ const [BasicDrawer, drawerApi] = useVbenDrawer({
|
|||||||
|
|
||||||
async function handleConfirm() {
|
async function handleConfirm() {
|
||||||
try {
|
try {
|
||||||
drawerApi.drawerLoading(true);
|
drawerApi.lock(true);
|
||||||
const { valid } = await formApi.validate();
|
const { valid } = await formApi.validate();
|
||||||
if (!valid) {
|
if (!valid) {
|
||||||
return;
|
return;
|
||||||
@@ -93,19 +103,20 @@ async function handleConfirm() {
|
|||||||
data.listClass = '';
|
data.listClass = '';
|
||||||
}
|
}
|
||||||
await (isUpdate.value ? dictDataUpdate(data) : dictDataAdd(data));
|
await (isUpdate.value ? dictDataUpdate(data) : dictDataAdd(data));
|
||||||
|
resetInitialized();
|
||||||
emit('reload');
|
emit('reload');
|
||||||
await handleCancel();
|
drawerApi.close();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
} finally {
|
} finally {
|
||||||
drawerApi.drawerLoading(false);
|
drawerApi.lock(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleCancel() {
|
async function handleClosed() {
|
||||||
drawerApi.close();
|
|
||||||
await formApi.resetForm();
|
await formApi.resetForm();
|
||||||
selectType.value = 'default';
|
selectType.value = 'default';
|
||||||
|
resetInitialized();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -117,7 +128,7 @@ async function handleDeSelect() {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<BasicDrawer :close-on-click-modal="false" :title="title" class="w-[600px]">
|
<BasicDrawer :title="title" class="w-[600px]">
|
||||||
<BasicForm>
|
<BasicForm>
|
||||||
<template #listClass="slotProps">
|
<template #listClass="slotProps">
|
||||||
<TagStylePicker
|
<TagStylePicker
|
||||||
|
|||||||
@@ -1,14 +1,17 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { RadioChangeEvent } from 'ant-design-vue';
|
import type { RadioChangeEvent } from 'ant-design-vue';
|
||||||
|
|
||||||
import type { PropType } from 'vue';
|
|
||||||
|
|
||||||
import { computed } from 'vue';
|
import { computed } from 'vue';
|
||||||
|
|
||||||
import { Input, RadioGroup, Select } from 'ant-design-vue';
|
import { usePreferences } from '@vben/preferences';
|
||||||
|
|
||||||
|
import { RadioGroup, Select } from 'ant-design-vue';
|
||||||
|
import { ColorPicker } from 'vue3-colorpicker';
|
||||||
|
|
||||||
import { tagSelectOptions } from '#/components/dict';
|
import { tagSelectOptions } from '#/components/dict';
|
||||||
|
|
||||||
|
import 'vue3-colorpicker/style.css';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 需要禁止透传
|
* 需要禁止透传
|
||||||
* 不禁止会有奇怪的bug 会绑定到selectType上
|
* 不禁止会有奇怪的bug 会绑定到selectType上
|
||||||
@@ -32,23 +35,26 @@ const computedOptions = computed(
|
|||||||
|
|
||||||
type SelectType = (typeof options)[number]['value'];
|
type SelectType = (typeof options)[number]['value'];
|
||||||
|
|
||||||
const selectType = defineModel('selectType', {
|
const selectType = defineModel<SelectType>('selectType', {
|
||||||
default: 'default',
|
default: 'default',
|
||||||
type: String as PropType<SelectType>,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* color必须为hex颜色或者undefined
|
* color必须为hex颜色或者undefined
|
||||||
*/
|
*/
|
||||||
const color = defineModel('value', {
|
const color = defineModel<string | undefined>('value', {
|
||||||
default: undefined,
|
default: undefined,
|
||||||
type: String as PropType<string | undefined>,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
function handleSelectTypeChange(e: RadioChangeEvent) {
|
function handleSelectTypeChange(e: RadioChangeEvent) {
|
||||||
// 必须给默认hex颜色 不能为空字符串
|
// 必须给默认hex颜色 不能为空字符串
|
||||||
color.value = e.target.value === 'custom' ? '#000000' : undefined;
|
color.value = e.target.value === 'custom' ? '#1677ff' : undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { isDark } = usePreferences();
|
||||||
|
const theme = computed(() => {
|
||||||
|
return isDark.value ? 'black' : 'white';
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -69,15 +75,12 @@ function handleSelectTypeChange(e: RadioChangeEvent) {
|
|||||||
placeholder="请选择标签样式"
|
placeholder="请选择标签样式"
|
||||||
@deselect="$emit('deselect')"
|
@deselect="$emit('deselect')"
|
||||||
/>
|
/>
|
||||||
<Input
|
<ColorPicker
|
||||||
v-if="selectType === 'custom'"
|
v-if="selectType === 'custom'"
|
||||||
v-model:value="color"
|
disable-alpha
|
||||||
class="flex-1"
|
format="hex"
|
||||||
disabled
|
v-model:pure-color="color"
|
||||||
>
|
:theme="theme"
|
||||||
<template #addonAfter>
|
/>
|
||||||
<input v-model="color" class="rounded-lg" type="color" />
|
|
||||||
</template>
|
|
||||||
</Input>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -71,7 +71,6 @@ export const modalSchema: FormSchemaGetter = () => [
|
|||||||
{
|
{
|
||||||
component: 'Textarea',
|
component: 'Textarea',
|
||||||
fieldName: 'remark',
|
fieldName: 'remark',
|
||||||
formItemClass: 'items-start',
|
|
||||||
label: '备注',
|
label: '备注',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import {
|
|||||||
dictTypeInfo,
|
dictTypeInfo,
|
||||||
dictTypeUpdate,
|
dictTypeUpdate,
|
||||||
} from '#/api/system/dict/dict-type';
|
} from '#/api/system/dict/dict-type';
|
||||||
|
import { defaultFormValueGetter, useBeforeCloseDiff } from '#/utils/popup';
|
||||||
|
|
||||||
import { modalSchema } from './data';
|
import { modalSchema } from './data';
|
||||||
|
|
||||||
@@ -22,6 +23,7 @@ const title = computed(() => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const [BasicForm, formApi] = useVbenForm({
|
const [BasicForm, formApi] = useVbenForm({
|
||||||
|
layout: 'vertical',
|
||||||
commonConfig: {
|
commonConfig: {
|
||||||
labelWidth: 100,
|
labelWidth: 100,
|
||||||
},
|
},
|
||||||
@@ -29,51 +31,63 @@ const [BasicForm, formApi] = useVbenForm({
|
|||||||
showDefaultActions: false,
|
showDefaultActions: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const { onBeforeClose, markInitialized, resetInitialized } = useBeforeCloseDiff(
|
||||||
|
{
|
||||||
|
initializedGetter: defaultFormValueGetter(formApi),
|
||||||
|
currentGetter: defaultFormValueGetter(formApi),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
const [BasicModal, modalApi] = useVbenModal({
|
const [BasicModal, modalApi] = useVbenModal({
|
||||||
fullscreenButton: false,
|
fullscreenButton: false,
|
||||||
onCancel: handleCancel,
|
onBeforeClose,
|
||||||
|
onClosed: handleClosed,
|
||||||
onConfirm: handleConfirm,
|
onConfirm: handleConfirm,
|
||||||
onOpenChange: async (isOpen) => {
|
onOpenChange: async (isOpen) => {
|
||||||
if (!isOpen) {
|
if (!isOpen) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
modalApi.modalLoading(true);
|
modalApi.modalLoading(true);
|
||||||
|
|
||||||
const { id } = modalApi.getData() as { id?: number | string };
|
const { id } = modalApi.getData() as { id?: number | string };
|
||||||
isUpdate.value = !!id;
|
isUpdate.value = !!id;
|
||||||
if (isUpdate.value && id) {
|
if (isUpdate.value && id) {
|
||||||
const record = await dictTypeInfo(id);
|
const record = await dictTypeInfo(id);
|
||||||
await formApi.setValues(record);
|
await formApi.setValues(record);
|
||||||
}
|
}
|
||||||
|
await markInitialized();
|
||||||
|
|
||||||
modalApi.modalLoading(false);
|
modalApi.modalLoading(false);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
async function handleConfirm() {
|
async function handleConfirm() {
|
||||||
try {
|
try {
|
||||||
modalApi.modalLoading(true);
|
modalApi.lock(true);
|
||||||
const { valid } = await formApi.validate();
|
const { valid } = await formApi.validate();
|
||||||
if (!valid) {
|
if (!valid) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const data = cloneDeep(await formApi.getValues());
|
const data = cloneDeep(await formApi.getValues());
|
||||||
await (isUpdate.value ? dictTypeUpdate(data) : dictTypeAdd(data));
|
await (isUpdate.value ? dictTypeUpdate(data) : dictTypeAdd(data));
|
||||||
|
resetInitialized();
|
||||||
emit('reload');
|
emit('reload');
|
||||||
await handleCancel();
|
modalApi.close();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
} finally {
|
} finally {
|
||||||
modalApi.modalLoading(false);
|
modalApi.lock(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleCancel() {
|
async function handleClosed() {
|
||||||
modalApi.close();
|
|
||||||
await formApi.resetForm();
|
await formApi.resetForm();
|
||||||
|
resetInitialized();
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<BasicModal :close-on-click-modal="false" :title="title">
|
<BasicModal :title="title">
|
||||||
<BasicForm />
|
<BasicForm />
|
||||||
</BasicModal>
|
</BasicModal>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import {
|
|||||||
|
|
||||||
import { useVbenForm } from '#/adapter/form';
|
import { useVbenForm } from '#/adapter/form';
|
||||||
import { menuAdd, menuInfo, menuList, menuUpdate } from '#/api/system/menu';
|
import { menuAdd, menuInfo, menuList, menuUpdate } from '#/api/system/menu';
|
||||||
|
import { defaultFormValueGetter, useBeforeCloseDiff } from '#/utils/popup';
|
||||||
|
|
||||||
import { drawerSchema } from './data';
|
import { drawerSchema } from './data';
|
||||||
|
|
||||||
@@ -88,14 +89,23 @@ async function setupMenuSelect() {
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { onBeforeClose, markInitialized, resetInitialized } = useBeforeCloseDiff(
|
||||||
|
{
|
||||||
|
initializedGetter: defaultFormValueGetter(formApi),
|
||||||
|
currentGetter: defaultFormValueGetter(formApi),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
const [BasicDrawer, drawerApi] = useVbenDrawer({
|
const [BasicDrawer, drawerApi] = useVbenDrawer({
|
||||||
onCancel: handleCancel,
|
onBeforeClose,
|
||||||
|
onClosed: handleClosed,
|
||||||
onConfirm: handleConfirm,
|
onConfirm: handleConfirm,
|
||||||
async onOpenChange(isOpen) {
|
async onOpenChange(isOpen) {
|
||||||
if (!isOpen) {
|
if (!isOpen) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
drawerApi.drawerLoading(true);
|
drawerApi.drawerLoading(true);
|
||||||
|
|
||||||
const { id, update } = drawerApi.getData() as ModalProps;
|
const { id, update } = drawerApi.getData() as ModalProps;
|
||||||
isUpdate.value = update;
|
isUpdate.value = update;
|
||||||
|
|
||||||
@@ -108,36 +118,39 @@ const [BasicDrawer, drawerApi] = useVbenDrawer({
|
|||||||
await formApi.setValues(record);
|
await formApi.setValues(record);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
await markInitialized();
|
||||||
|
|
||||||
drawerApi.drawerLoading(false);
|
drawerApi.drawerLoading(false);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
async function handleConfirm() {
|
async function handleConfirm() {
|
||||||
try {
|
try {
|
||||||
drawerApi.drawerLoading(true);
|
drawerApi.lock(true);
|
||||||
const { valid } = await formApi.validate();
|
const { valid } = await formApi.validate();
|
||||||
if (!valid) {
|
if (!valid) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const data = cloneDeep(await formApi.getValues());
|
const data = cloneDeep(await formApi.getValues());
|
||||||
await (isUpdate.value ? menuUpdate(data) : menuAdd(data));
|
await (isUpdate.value ? menuUpdate(data) : menuAdd(data));
|
||||||
|
resetInitialized();
|
||||||
emit('reload');
|
emit('reload');
|
||||||
await handleCancel();
|
drawerApi.close();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
} finally {
|
} finally {
|
||||||
drawerApi.drawerLoading(false);
|
drawerApi.lock(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleCancel() {
|
async function handleClosed() {
|
||||||
drawerApi.close();
|
|
||||||
await formApi.resetForm();
|
await formApi.resetForm();
|
||||||
|
resetInitialized();
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<BasicDrawer :close-on-click-modal="false" :title="title" class="w-[600px]">
|
<BasicDrawer :title="title" class="w-[600px]">
|
||||||
<BasicForm />
|
<BasicForm />
|
||||||
</BasicDrawer>
|
</BasicDrawer>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import { pick } from 'lodash-es';
|
|||||||
import { noticeAdd, noticeInfo, noticeUpdate } from '#/api/system/notice';
|
import { noticeAdd, noticeInfo, noticeUpdate } from '#/api/system/notice';
|
||||||
import { Tinymce } from '#/components/tinymce';
|
import { Tinymce } from '#/components/tinymce';
|
||||||
import { getDictOptions } from '#/utils/dict';
|
import { getDictOptions } from '#/utils/dict';
|
||||||
|
import { useBeforeCloseDiff } from '#/utils/popup';
|
||||||
|
|
||||||
const emit = defineEmits<{ reload: [] }>();
|
const emit = defineEmits<{ reload: [] }>();
|
||||||
|
|
||||||
@@ -74,17 +75,29 @@ const { validate, validateInfos, resetFields } = Form.useForm(
|
|||||||
formRules,
|
formRules,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
function customFormValueGetter() {
|
||||||
|
return JSON.stringify(formData.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { onBeforeClose, markInitialized, resetInitialized } = useBeforeCloseDiff(
|
||||||
|
{
|
||||||
|
initializedGetter: customFormValueGetter,
|
||||||
|
currentGetter: customFormValueGetter,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
const [BasicModal, modalApi] = useVbenModal({
|
const [BasicModal, modalApi] = useVbenModal({
|
||||||
class: 'w-[800px]',
|
class: 'w-[800px]',
|
||||||
fullscreenButton: true,
|
fullscreenButton: true,
|
||||||
closeOnClickModal: false,
|
onBeforeClose,
|
||||||
onClosed: handleCancel,
|
onClosed: handleClosed,
|
||||||
onConfirm: handleConfirm,
|
onConfirm: handleConfirm,
|
||||||
onOpenChange: async (isOpen) => {
|
onOpenChange: async (isOpen) => {
|
||||||
if (!isOpen) {
|
if (!isOpen) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
modalApi.modalLoading(true);
|
modalApi.modalLoading(true);
|
||||||
|
|
||||||
const { id } = modalApi.getData() as { id?: number | string };
|
const { id } = modalApi.getData() as { id?: number | string };
|
||||||
isUpdate.value = !!id;
|
isUpdate.value = !!id;
|
||||||
if (isUpdate.value && id) {
|
if (isUpdate.value && id) {
|
||||||
@@ -93,30 +106,33 @@ const [BasicModal, modalApi] = useVbenModal({
|
|||||||
const filterRecord = pick(record, Object.keys(defaultValues));
|
const filterRecord = pick(record, Object.keys(defaultValues));
|
||||||
formData.value = filterRecord;
|
formData.value = filterRecord;
|
||||||
}
|
}
|
||||||
|
await markInitialized();
|
||||||
|
|
||||||
modalApi.modalLoading(false);
|
modalApi.modalLoading(false);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
async function handleConfirm() {
|
async function handleConfirm() {
|
||||||
try {
|
try {
|
||||||
modalApi.modalLoading(true);
|
modalApi.lock(true);
|
||||||
await validate();
|
await validate();
|
||||||
// 可能会做数据处理 使用cloneDeep深拷贝
|
// 可能会做数据处理 使用cloneDeep深拷贝
|
||||||
const data = cloneDeep(formData.value);
|
const data = cloneDeep(formData.value);
|
||||||
await (isUpdate.value ? noticeUpdate(data) : noticeAdd(data));
|
await (isUpdate.value ? noticeUpdate(data) : noticeAdd(data));
|
||||||
|
resetInitialized();
|
||||||
emit('reload');
|
emit('reload');
|
||||||
await handleCancel();
|
modalApi.close();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
} finally {
|
} finally {
|
||||||
modalApi.modalLoading(false);
|
modalApi.lock(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleCancel() {
|
async function handleClosed() {
|
||||||
modalApi.close();
|
|
||||||
formData.value = defaultValues;
|
formData.value = defaultValues;
|
||||||
resetFields();
|
resetFields();
|
||||||
|
resetInitialized();
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import {
|
|||||||
ossConfigInfo,
|
ossConfigInfo,
|
||||||
ossConfigUpdate,
|
ossConfigUpdate,
|
||||||
} from '#/api/system/oss-config';
|
} from '#/api/system/oss-config';
|
||||||
|
import { defaultFormValueGetter, useBeforeCloseDiff } from '#/utils/popup';
|
||||||
|
|
||||||
import { drawerSchema } from './data';
|
import { drawerSchema } from './data';
|
||||||
|
|
||||||
@@ -33,27 +34,38 @@ const [BasicForm, formApi] = useVbenForm({
|
|||||||
wrapperClass: 'grid-cols-3',
|
wrapperClass: 'grid-cols-3',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const { onBeforeClose, markInitialized, resetInitialized } = useBeforeCloseDiff(
|
||||||
|
{
|
||||||
|
initializedGetter: defaultFormValueGetter(formApi),
|
||||||
|
currentGetter: defaultFormValueGetter(formApi),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
const [BasicDrawer, drawerApi] = useVbenDrawer({
|
const [BasicDrawer, drawerApi] = useVbenDrawer({
|
||||||
onCancel: handleCancel,
|
onBeforeClose,
|
||||||
|
onClosed: handleClosed,
|
||||||
onConfirm: handleConfirm,
|
onConfirm: handleConfirm,
|
||||||
async onOpenChange(isOpen) {
|
async onOpenChange(isOpen) {
|
||||||
if (!isOpen) {
|
if (!isOpen) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
drawerApi.drawerLoading(true);
|
drawerApi.drawerLoading(true);
|
||||||
|
|
||||||
const { id } = drawerApi.getData() as { id?: number | string };
|
const { id } = drawerApi.getData() as { id?: number | string };
|
||||||
isUpdate.value = !!id;
|
isUpdate.value = !!id;
|
||||||
if (isUpdate.value && id) {
|
if (isUpdate.value && id) {
|
||||||
const record = await ossConfigInfo(id);
|
const record = await ossConfigInfo(id);
|
||||||
await formApi.setValues(record);
|
await formApi.setValues(record);
|
||||||
}
|
}
|
||||||
|
await markInitialized();
|
||||||
|
|
||||||
drawerApi.drawerLoading(false);
|
drawerApi.drawerLoading(false);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
async function handleConfirm() {
|
async function handleConfirm() {
|
||||||
try {
|
try {
|
||||||
drawerApi.drawerLoading(true);
|
drawerApi.lock(true);
|
||||||
/**
|
/**
|
||||||
* 这里解构出来的values只能获取到自定义校验参数的值
|
* 这里解构出来的values只能获取到自定义校验参数的值
|
||||||
* 需要自行调用formApi.getValues()获取表单值
|
* 需要自行调用formApi.getValues()获取表单值
|
||||||
@@ -64,23 +76,24 @@ async function handleConfirm() {
|
|||||||
}
|
}
|
||||||
const data = cloneDeep(await formApi.getValues());
|
const data = cloneDeep(await formApi.getValues());
|
||||||
await (isUpdate.value ? ossConfigUpdate(data) : ossConfigAdd(data));
|
await (isUpdate.value ? ossConfigUpdate(data) : ossConfigAdd(data));
|
||||||
|
resetInitialized();
|
||||||
emit('reload');
|
emit('reload');
|
||||||
await handleCancel();
|
drawerApi.close();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
} finally {
|
} finally {
|
||||||
drawerApi.drawerLoading(false);
|
drawerApi.lock(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleCancel() {
|
async function handleClosed() {
|
||||||
drawerApi.close();
|
|
||||||
await formApi.resetForm();
|
await formApi.resetForm();
|
||||||
|
resetInitialized();
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<BasicDrawer :close-on-click-modal="false" :title="title" class="w-[650px]">
|
<BasicDrawer :title="title" class="w-[650px]">
|
||||||
<BasicForm>
|
<BasicForm>
|
||||||
<template #tip>
|
<template #tip>
|
||||||
<div class="ml-7 w-full">
|
<div class="ml-7 w-full">
|
||||||
|
|||||||
@@ -83,6 +83,9 @@ const gridOptions: VxeGridProps = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
headerCellConfig: {
|
||||||
|
height: 44,
|
||||||
|
},
|
||||||
cellConfig: {
|
cellConfig: {
|
||||||
height: 65,
|
height: 65,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -96,7 +96,7 @@ async function handleCancel() {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<BasicDrawer :close-on-click-modal="false" :title="title" class="w-[600px]">
|
<BasicDrawer :title="title" class="w-[600px]">
|
||||||
<BasicForm />
|
<BasicForm />
|
||||||
</BasicDrawer>
|
</BasicDrawer>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import { cloneDeep } from '@vben/utils';
|
|||||||
import { useVbenForm } from '#/adapter/form';
|
import { useVbenForm } from '#/adapter/form';
|
||||||
import { roleDataScope, roleDeptTree, roleInfo } from '#/api/system/role';
|
import { roleDataScope, roleDeptTree, roleInfo } from '#/api/system/role';
|
||||||
import { TreeSelectPanel } from '#/components/tree';
|
import { TreeSelectPanel } from '#/components/tree';
|
||||||
|
import { defaultFormValueGetter, useBeforeCloseDiff } from '#/utils/popup';
|
||||||
|
|
||||||
import { authModalSchemas } from './data';
|
import { authModalSchemas } from './data';
|
||||||
|
|
||||||
@@ -33,9 +34,25 @@ async function setupDeptTree(id: number | string) {
|
|||||||
deptTree.value = resp.depts;
|
deptTree.value = resp.depts;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function customFormValueGetter() {
|
||||||
|
const v = await defaultFormValueGetter(formApi)();
|
||||||
|
// 获取勾选信息
|
||||||
|
const menuIds = deptSelectRef.value?.[0]?.getCheckedKeys() ?? [];
|
||||||
|
const mixStr = v + menuIds.join(',');
|
||||||
|
return mixStr;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { onBeforeClose, markInitialized, resetInitialized } = useBeforeCloseDiff(
|
||||||
|
{
|
||||||
|
initializedGetter: customFormValueGetter,
|
||||||
|
currentGetter: customFormValueGetter,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
const [BasicModal, modalApi] = useVbenModal({
|
const [BasicModal, modalApi] = useVbenModal({
|
||||||
fullscreenButton: false,
|
fullscreenButton: false,
|
||||||
onCancel: handleCancel,
|
onBeforeClose,
|
||||||
|
onCancel: handleClosed,
|
||||||
onConfirm: handleConfirm,
|
onConfirm: handleConfirm,
|
||||||
onOpenChange: async (isOpen) => {
|
onOpenChange: async (isOpen) => {
|
||||||
if (!isOpen) {
|
if (!isOpen) {
|
||||||
@@ -48,6 +65,7 @@ const [BasicModal, modalApi] = useVbenModal({
|
|||||||
setupDeptTree(id);
|
setupDeptTree(id);
|
||||||
const record = await roleInfo(id);
|
const record = await roleInfo(id);
|
||||||
await formApi.setValues(record);
|
await formApi.setValues(record);
|
||||||
|
markInitialized();
|
||||||
|
|
||||||
modalApi.modalLoading(false);
|
modalApi.modalLoading(false);
|
||||||
},
|
},
|
||||||
@@ -60,7 +78,7 @@ const deptSelectRef = ref();
|
|||||||
|
|
||||||
async function handleConfirm() {
|
async function handleConfirm() {
|
||||||
try {
|
try {
|
||||||
modalApi.modalLoading(true);
|
modalApi.lock(true);
|
||||||
const { valid } = await formApi.validate();
|
const { valid } = await formApi.validate();
|
||||||
if (!valid) {
|
if (!valid) {
|
||||||
return;
|
return;
|
||||||
@@ -75,18 +93,19 @@ async function handleConfirm() {
|
|||||||
data.deptIds = [];
|
data.deptIds = [];
|
||||||
}
|
}
|
||||||
await roleDataScope(data);
|
await roleDataScope(data);
|
||||||
|
resetInitialized();
|
||||||
emit('reload');
|
emit('reload');
|
||||||
await handleCancel();
|
modalApi.close();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
} finally {
|
} finally {
|
||||||
modalApi.modalLoading(false);
|
modalApi.lock(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleCancel() {
|
async function handleClosed() {
|
||||||
modalApi.close();
|
|
||||||
await formApi.resetForm();
|
await formApi.resetForm();
|
||||||
|
resetInitialized();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -99,11 +118,7 @@ function handleCheckStrictlyChange(value: boolean) {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<BasicModal
|
<BasicModal class="min-h-[600px] w-[550px]" title="分配权限">
|
||||||
:close-on-click-modal="false"
|
|
||||||
class="min-h-[600px] w-[550px]"
|
|
||||||
title="分配权限"
|
|
||||||
>
|
|
||||||
<BasicForm>
|
<BasicForm>
|
||||||
<template #deptIds="slotProps">
|
<template #deptIds="slotProps">
|
||||||
<TreeSelectPanel
|
<TreeSelectPanel
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
<!--
|
||||||
|
TODO: 这个页面要优化逻辑
|
||||||
|
-->
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { MenuOption } from '#/api/system/menu/model';
|
import type { MenuOption } from '#/api/system/menu/model';
|
||||||
|
|
||||||
@@ -11,6 +14,7 @@ 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 { MenuSelectTable } from '#/components/tree';
|
import { MenuSelectTable } from '#/components/tree';
|
||||||
|
import { defaultFormValueGetter, useBeforeCloseDiff } from '#/utils/popup';
|
||||||
|
|
||||||
import { drawerSchema } from './data';
|
import { drawerSchema } from './data';
|
||||||
|
|
||||||
@@ -62,14 +66,31 @@ async function setupMenuTree(id?: number | string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function customFormValueGetter() {
|
||||||
|
const v = await defaultFormValueGetter(formApi)();
|
||||||
|
// 获取勾选信息
|
||||||
|
const menuIds = menuSelectRef.value?.getCheckedKeys?.() ?? [];
|
||||||
|
const mixStr = v + menuIds.join(',');
|
||||||
|
return mixStr;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { onBeforeClose, markInitialized, resetInitialized } = useBeforeCloseDiff(
|
||||||
|
{
|
||||||
|
initializedGetter: customFormValueGetter,
|
||||||
|
currentGetter: customFormValueGetter,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
const [BasicDrawer, drawerApi] = useVbenDrawer({
|
const [BasicDrawer, drawerApi] = useVbenDrawer({
|
||||||
onCancel: handleCancel,
|
onBeforeClose,
|
||||||
|
onClosed: handleClosed,
|
||||||
onConfirm: handleConfirm,
|
onConfirm: handleConfirm,
|
||||||
async onOpenChange(isOpen) {
|
async onOpenChange(isOpen) {
|
||||||
if (!isOpen) {
|
if (!isOpen) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
drawerApi.drawerLoading(true);
|
drawerApi.drawerLoading(true);
|
||||||
|
|
||||||
const { id } = drawerApi.getData() as { id?: number | string };
|
const { id } = drawerApi.getData() as { id?: number | string };
|
||||||
isUpdate.value = !!id;
|
isUpdate.value = !!id;
|
||||||
|
|
||||||
@@ -79,6 +100,7 @@ const [BasicDrawer, drawerApi] = useVbenDrawer({
|
|||||||
}
|
}
|
||||||
// init菜单 注意顺序要放在赋值record之后 内部watch会依赖record
|
// init菜单 注意顺序要放在赋值record之后 内部watch会依赖record
|
||||||
await setupMenuTree(id);
|
await setupMenuTree(id);
|
||||||
|
await markInitialized();
|
||||||
|
|
||||||
drawerApi.drawerLoading(false);
|
drawerApi.drawerLoading(false);
|
||||||
},
|
},
|
||||||
@@ -87,7 +109,8 @@ const [BasicDrawer, drawerApi] = useVbenDrawer({
|
|||||||
const menuSelectRef = ref<InstanceType<typeof MenuSelectTable>>();
|
const menuSelectRef = ref<InstanceType<typeof MenuSelectTable>>();
|
||||||
async function handleConfirm() {
|
async function handleConfirm() {
|
||||||
try {
|
try {
|
||||||
drawerApi.drawerLoading(true);
|
drawerApi.lock(true);
|
||||||
|
|
||||||
const { valid } = await formApi.validate();
|
const { valid } = await formApi.validate();
|
||||||
if (!valid) {
|
if (!valid) {
|
||||||
return;
|
return;
|
||||||
@@ -99,17 +122,18 @@ async function handleConfirm() {
|
|||||||
data.menuIds = menuIds;
|
data.menuIds = menuIds;
|
||||||
await (isUpdate.value ? roleUpdate(data) : roleAdd(data));
|
await (isUpdate.value ? roleUpdate(data) : roleAdd(data));
|
||||||
emit('reload');
|
emit('reload');
|
||||||
await handleCancel();
|
resetInitialized();
|
||||||
|
drawerApi.close();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
} finally {
|
} finally {
|
||||||
drawerApi.drawerLoading(false);
|
drawerApi.lock(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleCancel() {
|
async function handleClosed() {
|
||||||
drawerApi.close();
|
|
||||||
await formApi.resetForm();
|
await formApi.resetForm();
|
||||||
|
resetInitialized();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -122,7 +146,7 @@ function handleMenuCheckStrictlyChange(value: boolean) {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<BasicDrawer :close-on-click-modal="false" :title="title" class="w-[800px]">
|
<BasicDrawer :title="title" class="w-[800px]">
|
||||||
<BasicForm>
|
<BasicForm>
|
||||||
<template #menuIds="slotProps">
|
<template #menuIds="slotProps">
|
||||||
<div class="h-[600px] w-full">
|
<div class="h-[600px] w-full">
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import { useVbenForm } from '#/adapter/form';
|
|||||||
import { tenantAdd, tenantInfo, tenantUpdate } from '#/api/system/tenant';
|
import { tenantAdd, tenantInfo, tenantUpdate } from '#/api/system/tenant';
|
||||||
import { packageSelectList } from '#/api/system/tenant-package';
|
import { packageSelectList } from '#/api/system/tenant-package';
|
||||||
import { useTenantStore } from '#/store/tenant';
|
import { useTenantStore } from '#/store/tenant';
|
||||||
|
import { defaultFormValueGetter, useBeforeCloseDiff } from '#/utils/popup';
|
||||||
|
|
||||||
import { drawerSchema } from './data';
|
import { drawerSchema } from './data';
|
||||||
|
|
||||||
@@ -51,22 +52,33 @@ async function setupPackageSelect() {
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { onBeforeClose, markInitialized, resetInitialized } = useBeforeCloseDiff(
|
||||||
|
{
|
||||||
|
initializedGetter: defaultFormValueGetter(formApi),
|
||||||
|
currentGetter: defaultFormValueGetter(formApi),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
const [BasicDrawer, drawerApi] = useVbenDrawer({
|
const [BasicDrawer, drawerApi] = useVbenDrawer({
|
||||||
onCancel: handleCancel,
|
onBeforeClose,
|
||||||
|
onClosed: handleClosed,
|
||||||
onConfirm: handleConfirm,
|
onConfirm: handleConfirm,
|
||||||
async onOpenChange(isOpen) {
|
async onOpenChange(isOpen) {
|
||||||
if (!isOpen) {
|
if (!isOpen) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
drawerApi.drawerLoading(true);
|
drawerApi.drawerLoading(true);
|
||||||
|
|
||||||
const { id } = drawerApi.getData() as { id?: number | string };
|
const { id } = drawerApi.getData() as { id?: number | string };
|
||||||
isUpdate.value = !!id;
|
isUpdate.value = !!id;
|
||||||
// 初始化
|
// 初始化
|
||||||
await setupPackageSelect();
|
await setupPackageSelect();
|
||||||
|
|
||||||
if (isUpdate.value && id) {
|
if (isUpdate.value && id) {
|
||||||
const record = await tenantInfo(id);
|
const record = await tenantInfo(id);
|
||||||
await formApi.setValues(record);
|
await formApi.setValues(record);
|
||||||
}
|
}
|
||||||
|
|
||||||
formApi.updateSchema([
|
formApi.updateSchema([
|
||||||
{
|
{
|
||||||
fieldName: 'packageId',
|
fieldName: 'packageId',
|
||||||
@@ -75,6 +87,8 @@ const [BasicDrawer, drawerApi] = useVbenDrawer({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
await markInitialized();
|
||||||
|
|
||||||
drawerApi.drawerLoading(false);
|
drawerApi.drawerLoading(false);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -82,32 +96,33 @@ const [BasicDrawer, drawerApi] = useVbenDrawer({
|
|||||||
const tenantStore = useTenantStore();
|
const tenantStore = useTenantStore();
|
||||||
async function handleConfirm() {
|
async function handleConfirm() {
|
||||||
try {
|
try {
|
||||||
drawerApi.drawerLoading(true);
|
drawerApi.lock(true);
|
||||||
const { valid } = await formApi.validate();
|
const { valid } = await formApi.validate();
|
||||||
if (!valid) {
|
if (!valid) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const data = cloneDeep(await formApi.getValues());
|
const data = cloneDeep(await formApi.getValues());
|
||||||
await (isUpdate.value ? tenantUpdate(data) : tenantAdd(data));
|
await (isUpdate.value ? tenantUpdate(data) : tenantAdd(data));
|
||||||
|
resetInitialized();
|
||||||
emit('reload');
|
emit('reload');
|
||||||
await handleCancel();
|
drawerApi.close();
|
||||||
// 重新加载租户信息
|
// 重新加载租户信息
|
||||||
tenantStore.initTenant();
|
tenantStore.initTenant();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
} finally {
|
} finally {
|
||||||
drawerApi.drawerLoading(false);
|
drawerApi.lock(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleCancel() {
|
async function handleClosed() {
|
||||||
drawerApi.close();
|
|
||||||
await formApi.resetForm();
|
await formApi.resetForm();
|
||||||
|
resetInitialized();
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<BasicDrawer :close-on-click-modal="false" :title="title" class="w-[600px]">
|
<BasicDrawer :title="title" class="w-[600px]">
|
||||||
<BasicForm />
|
<BasicForm />
|
||||||
</BasicDrawer>
|
</BasicDrawer>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -65,12 +65,6 @@ export const drawerSchema: FormSchemaGetter = () => [
|
|||||||
{
|
{
|
||||||
component: 'Textarea',
|
component: 'Textarea',
|
||||||
fieldName: 'remark',
|
fieldName: 'remark',
|
||||||
formItemClass: 'items-start',
|
|
||||||
label: '备注',
|
label: '备注',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
// 租户管理 不可分配 只有superadmin有权限操作 分配了也没用
|
|
||||||
export const excludeIds = [
|
|
||||||
6, 121, 122, 1606, 1607, 1608, 1609, 1610, 1611, 1612, 1613, 1614, 1615,
|
|
||||||
];
|
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import {
|
|||||||
packageUpdate,
|
packageUpdate,
|
||||||
} from '#/api/system/tenant-package';
|
} from '#/api/system/tenant-package';
|
||||||
import { MenuSelectTable } from '#/components/tree';
|
import { MenuSelectTable } from '#/components/tree';
|
||||||
|
import { defaultFormValueGetter, useBeforeCloseDiff } from '#/utils/popup';
|
||||||
|
|
||||||
import { drawerSchema } from './data';
|
import { drawerSchema } from './data';
|
||||||
|
|
||||||
@@ -65,8 +66,24 @@ async function setupMenuTree(id?: number | string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function customFormValueGetter() {
|
||||||
|
const v = await defaultFormValueGetter(formApi)();
|
||||||
|
// 获取勾选信息
|
||||||
|
const menuIds = menuSelectRef.value?.getCheckedKeys?.() ?? [];
|
||||||
|
const mixStr = v + menuIds.join(',');
|
||||||
|
return mixStr;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { onBeforeClose, markInitialized, resetInitialized } = useBeforeCloseDiff(
|
||||||
|
{
|
||||||
|
initializedGetter: customFormValueGetter,
|
||||||
|
currentGetter: customFormValueGetter,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
const [BasicDrawer, drawerApi] = useVbenDrawer({
|
const [BasicDrawer, drawerApi] = useVbenDrawer({
|
||||||
onCancel: handleCancel,
|
onBeforeClose,
|
||||||
|
onClosed: handleClosed,
|
||||||
onConfirm: handleConfirm,
|
onConfirm: handleConfirm,
|
||||||
async onOpenChange(isOpen) {
|
async onOpenChange(isOpen) {
|
||||||
if (!isOpen) {
|
if (!isOpen) {
|
||||||
@@ -84,6 +101,7 @@ const [BasicDrawer, drawerApi] = useVbenDrawer({
|
|||||||
}
|
}
|
||||||
// init菜单 注意顺序要放在赋值record之后 内部watch会依赖record
|
// init菜单 注意顺序要放在赋值record之后 内部watch会依赖record
|
||||||
await setupMenuTree(id);
|
await setupMenuTree(id);
|
||||||
|
await markInitialized();
|
||||||
|
|
||||||
drawerApi.drawerLoading(false);
|
drawerApi.drawerLoading(false);
|
||||||
},
|
},
|
||||||
@@ -103,8 +121,9 @@ async function handleConfirm() {
|
|||||||
const data = cloneDeep(await formApi.getValues());
|
const data = cloneDeep(await formApi.getValues());
|
||||||
data.menuIds = menuIds;
|
data.menuIds = menuIds;
|
||||||
await (isUpdate.value ? packageUpdate(data) : packageAdd(data));
|
await (isUpdate.value ? packageUpdate(data) : packageAdd(data));
|
||||||
|
resetInitialized();
|
||||||
emit('reload');
|
emit('reload');
|
||||||
await handleCancel();
|
drawerApi.close();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
} finally {
|
} finally {
|
||||||
@@ -112,9 +131,9 @@ async function handleConfirm() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleCancel() {
|
async function handleClosed() {
|
||||||
drawerApi.close();
|
|
||||||
await formApi.resetForm();
|
await formApi.resetForm();
|
||||||
|
resetInitialized();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -127,7 +146,7 @@ function handleMenuCheckStrictlyChange(value: boolean) {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<BasicDrawer :close-on-click-modal="false" :title="title" class="w-[800px]">
|
<BasicDrawer :title="title" class="w-[800px]">
|
||||||
<BasicForm>
|
<BasicForm>
|
||||||
<template #menuIds="slotProps">
|
<template #menuIds="slotProps">
|
||||||
<div class="h-[600px] w-full">
|
<div class="h-[600px] w-full">
|
||||||
|
|||||||
@@ -1,44 +0,0 @@
|
|||||||
import type { PropType } from 'vue';
|
|
||||||
|
|
||||||
import type { Menu } from '#/api/system/menu/model';
|
|
||||||
|
|
||||||
import { computed, defineComponent } from 'vue';
|
|
||||||
|
|
||||||
import { Tag } from 'ant-design-vue';
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
name: 'TreeItem',
|
|
||||||
props: {
|
|
||||||
data: {
|
|
||||||
required: true,
|
|
||||||
type: Object as PropType<Menu>,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
setup(props, { expose }) {
|
|
||||||
expose();
|
|
||||||
|
|
||||||
interface TagProp {
|
|
||||||
color: string;
|
|
||||||
text: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const menuTagProp = computed<TagProp>(() => {
|
|
||||||
// 正则判断是否为链接
|
|
||||||
if (/^https?:\/\/[^\s/$.?#].\S*$/i.test(props.data.path)) {
|
|
||||||
return { color: 'pink', text: '外链' };
|
|
||||||
}
|
|
||||||
const type = props.data.menuType;
|
|
||||||
if (type === 'M') return { color: 'green', text: '目录' };
|
|
||||||
if (type === 'C') return { color: 'blue', text: '菜单' };
|
|
||||||
if (type === 'F') return { color: '', text: '按钮' };
|
|
||||||
return { color: 'error', text: '未知' };
|
|
||||||
});
|
|
||||||
|
|
||||||
return () => (
|
|
||||||
<div class="flex gap-[6px]">
|
|
||||||
<span>{props.data.menuName}</span>
|
|
||||||
<Tag color={menuTagProp.value.color}>{menuTagProp.value.text}</Tag>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
@@ -113,6 +113,9 @@ const gridOptions: VxeGridProps = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
headerCellConfig: {
|
||||||
|
height: 44,
|
||||||
|
},
|
||||||
cellConfig: {
|
cellConfig: {
|
||||||
height: 48,
|
height: 48,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import {
|
|||||||
userAdd,
|
userAdd,
|
||||||
userUpdate,
|
userUpdate,
|
||||||
} from '#/api/system/user';
|
} from '#/api/system/user';
|
||||||
|
import { defaultFormValueGetter, useBeforeCloseDiff } from '#/utils/popup';
|
||||||
import { authScopeOptions } from '#/views/system/role/data';
|
import { authScopeOptions } from '#/views/system/role/data';
|
||||||
|
|
||||||
import { drawerSchema } from './data';
|
import { drawerSchema } from './data';
|
||||||
@@ -134,8 +135,16 @@ async function loadDefaultPassword(update: boolean) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { onBeforeClose, markInitialized, resetInitialized } = useBeforeCloseDiff(
|
||||||
|
{
|
||||||
|
initializedGetter: defaultFormValueGetter(formApi),
|
||||||
|
currentGetter: defaultFormValueGetter(formApi),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
const [BasicDrawer, drawerApi] = useVbenDrawer({
|
const [BasicDrawer, drawerApi] = useVbenDrawer({
|
||||||
onCancel: handleCancel,
|
onBeforeClose,
|
||||||
|
onClosed: handleClosed,
|
||||||
onConfirm: handleConfirm,
|
onConfirm: handleConfirm,
|
||||||
async onOpenChange(isOpen) {
|
async onOpenChange(isOpen) {
|
||||||
if (!isOpen) {
|
if (!isOpen) {
|
||||||
@@ -149,6 +158,7 @@ const [BasicDrawer, drawerApi] = useVbenDrawer({
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
drawerApi.drawerLoading(true);
|
drawerApi.drawerLoading(true);
|
||||||
|
|
||||||
const { id } = drawerApi.getData() as { id?: number | string };
|
const { id } = drawerApi.getData() as { id?: number | string };
|
||||||
isUpdate.value = !!id;
|
isUpdate.value = !!id;
|
||||||
/** update时 禁用用户名修改 不显示密码框 */
|
/** update时 禁用用户名修改 不显示密码框 */
|
||||||
@@ -186,10 +196,11 @@ const [BasicDrawer, drawerApi] = useVbenDrawer({
|
|||||||
fieldName: 'postIds',
|
fieldName: 'postIds',
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
// 部门选择 && 初始密码
|
|
||||||
await Promise.all([setupDeptSelect(), loadDefaultPassword(isUpdate.value)]);
|
// 部门选择、初始密码及用户相关操作并行处理
|
||||||
|
const promises = [setupDeptSelect(), loadDefaultPassword(isUpdate.value)];
|
||||||
if (user) {
|
if (user) {
|
||||||
await Promise.all([
|
promises.push(
|
||||||
// 添加基础信息
|
// 添加基础信息
|
||||||
formApi.setValues(user),
|
formApi.setValues(user),
|
||||||
// 添加角色和岗位
|
// 添加角色和岗位
|
||||||
@@ -197,38 +208,43 @@ const [BasicDrawer, drawerApi] = useVbenDrawer({
|
|||||||
formApi.setFieldValue('roleIds', roleIds),
|
formApi.setFieldValue('roleIds', roleIds),
|
||||||
// 更新时不会触发onSelect 需要手动调用
|
// 更新时不会触发onSelect 需要手动调用
|
||||||
setupPostOptions(user.deptId),
|
setupPostOptions(user.deptId),
|
||||||
]);
|
);
|
||||||
}
|
}
|
||||||
|
// 并行处理 重构后会带来10-50ms的优化
|
||||||
|
await Promise.all(promises);
|
||||||
|
await markInitialized();
|
||||||
|
|
||||||
drawerApi.drawerLoading(false);
|
drawerApi.drawerLoading(false);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
async function handleConfirm() {
|
async function handleConfirm() {
|
||||||
try {
|
try {
|
||||||
drawerApi.drawerLoading(true);
|
drawerApi.lock(true);
|
||||||
const { valid } = await formApi.validate();
|
const { valid } = await formApi.validate();
|
||||||
if (!valid) {
|
if (!valid) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const data = cloneDeep(await formApi.getValues());
|
const data = cloneDeep(await formApi.getValues());
|
||||||
await (isUpdate.value ? userUpdate(data) : userAdd(data));
|
await (isUpdate.value ? userUpdate(data) : userAdd(data));
|
||||||
|
resetInitialized();
|
||||||
emit('reload');
|
emit('reload');
|
||||||
await handleCancel();
|
drawerApi.close();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
} finally {
|
} finally {
|
||||||
drawerApi.drawerLoading(false);
|
drawerApi.lock(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleCancel() {
|
async function handleClosed() {
|
||||||
drawerApi.close();
|
formApi.resetForm();
|
||||||
await formApi.resetForm();
|
resetInitialized();
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<BasicDrawer :close-on-click-modal="false" :title="title" class="w-[600px]">
|
<BasicDrawer :title="title" class="w-[600px]">
|
||||||
<BasicForm />
|
<BasicForm />
|
||||||
</BasicDrawer>
|
</BasicDrawer>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import {
|
|||||||
categoryList,
|
categoryList,
|
||||||
categoryUpdate,
|
categoryUpdate,
|
||||||
} from '#/api/workflow/category';
|
} from '#/api/workflow/category';
|
||||||
|
import { defaultFormValueGetter, useBeforeCloseDiff } from '#/utils/popup';
|
||||||
|
|
||||||
import { modalSchema } from './data';
|
import { modalSchema } from './data';
|
||||||
|
|
||||||
@@ -65,9 +66,17 @@ async function setupCategorySelect() {
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { onBeforeClose, markInitialized, resetInitialized } = useBeforeCloseDiff(
|
||||||
|
{
|
||||||
|
initializedGetter: defaultFormValueGetter(formApi),
|
||||||
|
currentGetter: defaultFormValueGetter(formApi),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
const [BasicModal, modalApi] = useVbenModal({
|
const [BasicModal, modalApi] = useVbenModal({
|
||||||
fullscreenButton: false,
|
fullscreenButton: false,
|
||||||
onCancel: handleCancel,
|
onBeforeClose,
|
||||||
|
onClosed: handleClosed,
|
||||||
onConfirm: handleConfirm,
|
onConfirm: handleConfirm,
|
||||||
onOpenChange: async (isOpen) => {
|
onOpenChange: async (isOpen) => {
|
||||||
if (!isOpen) {
|
if (!isOpen) {
|
||||||
@@ -89,6 +98,7 @@ const [BasicModal, modalApi] = useVbenModal({
|
|||||||
await formApi.setValues({ parentId });
|
await formApi.setValues({ parentId });
|
||||||
}
|
}
|
||||||
await setupCategorySelect();
|
await setupCategorySelect();
|
||||||
|
await markInitialized();
|
||||||
|
|
||||||
modalApi.modalLoading(false);
|
modalApi.modalLoading(false);
|
||||||
},
|
},
|
||||||
@@ -96,7 +106,7 @@ const [BasicModal, modalApi] = useVbenModal({
|
|||||||
|
|
||||||
async function handleConfirm() {
|
async function handleConfirm() {
|
||||||
try {
|
try {
|
||||||
modalApi.modalLoading(true);
|
modalApi.lock(true);
|
||||||
const { valid } = await formApi.validate();
|
const { valid } = await formApi.validate();
|
||||||
if (!valid) {
|
if (!valid) {
|
||||||
return;
|
return;
|
||||||
@@ -104,27 +114,24 @@ async function handleConfirm() {
|
|||||||
// getValues获取为一个readonly的对象 需要修改必须先深拷贝一次
|
// getValues获取为一个readonly的对象 需要修改必须先深拷贝一次
|
||||||
const data = cloneDeep(await formApi.getValues());
|
const data = cloneDeep(await formApi.getValues());
|
||||||
await (isUpdate.value ? categoryUpdate(data) : categoryAdd(data));
|
await (isUpdate.value ? categoryUpdate(data) : categoryAdd(data));
|
||||||
|
resetInitialized();
|
||||||
emit('reload');
|
emit('reload');
|
||||||
await handleCancel();
|
modalApi.close();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
} finally {
|
} finally {
|
||||||
modalApi.modalLoading(false);
|
modalApi.lock(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleCancel() {
|
async function handleClosed() {
|
||||||
modalApi.close();
|
|
||||||
await formApi.resetForm();
|
await formApi.resetForm();
|
||||||
|
resetInitialized();
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<BasicModal
|
<BasicModal :title="title" class="min-h-[500px]">
|
||||||
:close-on-click-modal="false"
|
|
||||||
:title="title"
|
|
||||||
class="min-h-[500px]"
|
|
||||||
>
|
|
||||||
<BasicForm />
|
<BasicForm />
|
||||||
</BasicModal>
|
</BasicModal>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -93,9 +93,14 @@ const gridOptions: VxeGridProps = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
headerCellConfig: {
|
||||||
|
height: 44,
|
||||||
|
},
|
||||||
|
cellConfig: {
|
||||||
|
height: 100,
|
||||||
|
},
|
||||||
rowConfig: {
|
rowConfig: {
|
||||||
keyField: 'id',
|
keyField: 'id',
|
||||||
height: 100,
|
|
||||||
},
|
},
|
||||||
id: 'workflow-definition-index',
|
id: 'workflow-definition-index',
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import {
|
|||||||
workflowDefinitionInfo,
|
workflowDefinitionInfo,
|
||||||
workflowDefinitionUpdate,
|
workflowDefinitionUpdate,
|
||||||
} from '#/api/workflow/definition';
|
} from '#/api/workflow/definition';
|
||||||
|
import { defaultFormValueGetter, useBeforeCloseDiff } from '#/utils/popup';
|
||||||
|
|
||||||
import { modalSchema } from './data';
|
import { modalSchema } from './data';
|
||||||
|
|
||||||
@@ -65,8 +66,16 @@ async function setupCategorySelect() {
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { onBeforeClose, markInitialized, resetInitialized } = useBeforeCloseDiff(
|
||||||
|
{
|
||||||
|
initializedGetter: defaultFormValueGetter(formApi),
|
||||||
|
currentGetter: defaultFormValueGetter(formApi),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
const [BasicDrawer, modalApi] = useVbenModal({
|
const [BasicDrawer, modalApi] = useVbenModal({
|
||||||
onCancel: handleCancel,
|
onBeforeClose,
|
||||||
|
onCancel: handleClosed,
|
||||||
onConfirm: handleConfirm,
|
onConfirm: handleConfirm,
|
||||||
async onOpenChange(isOpen) {
|
async onOpenChange(isOpen) {
|
||||||
if (!isOpen) {
|
if (!isOpen) {
|
||||||
@@ -83,6 +92,7 @@ const [BasicDrawer, modalApi] = useVbenModal({
|
|||||||
const record = await workflowDefinitionInfo(id);
|
const record = await workflowDefinitionInfo(id);
|
||||||
await formApi.setValues(record);
|
await formApi.setValues(record);
|
||||||
}
|
}
|
||||||
|
await markInitialized();
|
||||||
|
|
||||||
modalApi.modalLoading(false);
|
modalApi.modalLoading(false);
|
||||||
},
|
},
|
||||||
@@ -90,7 +100,7 @@ const [BasicDrawer, modalApi] = useVbenModal({
|
|||||||
|
|
||||||
async function handleConfirm() {
|
async function handleConfirm() {
|
||||||
try {
|
try {
|
||||||
modalApi.modalLoading(true);
|
modalApi.lock(true);
|
||||||
const { valid } = await formApi.validate();
|
const { valid } = await formApi.validate();
|
||||||
if (!valid) {
|
if (!valid) {
|
||||||
return;
|
return;
|
||||||
@@ -103,27 +113,23 @@ async function handleConfirm() {
|
|||||||
await workflowDefinitionAdd(data);
|
await workflowDefinitionAdd(data);
|
||||||
emit('reload', 'add');
|
emit('reload', 'add');
|
||||||
}
|
}
|
||||||
await handleCancel();
|
resetInitialized();
|
||||||
|
modalApi.close();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
} finally {
|
} finally {
|
||||||
modalApi.modalLoading(false);
|
modalApi.lock(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleCancel() {
|
async function handleClosed() {
|
||||||
modalApi.close();
|
|
||||||
await formApi.resetForm();
|
await formApi.resetForm();
|
||||||
|
resetInitialized();
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<BasicDrawer
|
<BasicDrawer :fullscreen-button="false" :title="title" class="w-[550px]">
|
||||||
:close-on-click-modal="false"
|
|
||||||
:fullscreen-button="false"
|
|
||||||
:title="title"
|
|
||||||
class="w-[550px]"
|
|
||||||
>
|
|
||||||
<div class="min-h-[400px]">
|
<div class="min-h-[400px]">
|
||||||
<BasicForm />
|
<BasicForm />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ const formOptions: VbenFormProps = {
|
|||||||
wrapperClass: 'grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4',
|
wrapperClass: 'grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4',
|
||||||
handleReset: async () => {
|
handleReset: async () => {
|
||||||
selectedCode.value = [];
|
selectedCode.value = [];
|
||||||
// eslint-disable-next-line no-use-before-define
|
|
||||||
const { formApi, reload } = tableApi;
|
const { formApi, reload } = tableApi;
|
||||||
await formApi.resetForm();
|
await formApi.resetForm();
|
||||||
const formValues = formApi.form.values;
|
const formValues = formApi.form.values;
|
||||||
@@ -68,7 +68,7 @@ async function handleTypeChange(e: RadioChangeEvent) {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line no-use-before-define
|
|
||||||
await tableApi.reload();
|
await tableApi.reload();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -103,9 +103,14 @@ const gridOptions: VxeGridProps = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
headerCellConfig: {
|
||||||
|
height: 44,
|
||||||
|
},
|
||||||
|
cellConfig: {
|
||||||
|
height: 66,
|
||||||
|
},
|
||||||
rowConfig: {
|
rowConfig: {
|
||||||
keyField: 'id',
|
keyField: 'id',
|
||||||
height: 66,
|
|
||||||
},
|
},
|
||||||
id: 'workflow-definition-index',
|
id: 'workflow-definition-index',
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import type { CustomGetter } from '#/components/upload/src/props';
|
|||||||
|
|
||||||
import { h, ref } from 'vue';
|
import { h, ref } from 'vue';
|
||||||
|
|
||||||
import { CodeMirror, Page } from '@vben/common-ui';
|
import { CodeMirror, Page, useVbenModal } from '@vben/common-ui';
|
||||||
|
|
||||||
import { useClipboard } from '@vueuse/core';
|
import { useClipboard } from '@vueuse/core';
|
||||||
import { Alert, Card, Modal, RadioGroup, Switch } from 'ant-design-vue';
|
import { Alert, Card, Modal, RadioGroup, Switch } from 'ant-design-vue';
|
||||||
@@ -14,6 +14,7 @@ import { FileUpload, ImageUpload } from '#/components/upload';
|
|||||||
|
|
||||||
import { useFileType, useImageType } from './hook';
|
import { useFileType, useImageType } from './hook';
|
||||||
import sql from './insert.sql?raw';
|
import sql from './insert.sql?raw';
|
||||||
|
import uploadModal from './upload-modal.vue';
|
||||||
|
|
||||||
const singleImageId = ref('1905537674682916865');
|
const singleImageId = ref('1905537674682916865');
|
||||||
const singleFileId = ref('1905191167882518529');
|
const singleFileId = ref('1905191167882518529');
|
||||||
@@ -53,6 +54,10 @@ const customThumbnailUrl: CustomGetter<undefined> = () => {
|
|||||||
const { copy } = useClipboard({ legacy: true });
|
const { copy } = useClipboard({ legacy: true });
|
||||||
|
|
||||||
const animationEnable = ref(false);
|
const animationEnable = ref(false);
|
||||||
|
|
||||||
|
const [UploadModal, uploadModalApi] = useVbenModal({
|
||||||
|
connectedComponent: uploadModal,
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -63,6 +68,10 @@ const animationEnable = ref(false);
|
|||||||
<CodeMirror class="mt-2" v-model="sql" language="sql" readonly />
|
<CodeMirror class="mt-2" v-model="sql" language="sql" readonly />
|
||||||
</Card>
|
</Card>
|
||||||
<div class="grid grid-cols-2 gap-4">
|
<div class="grid grid-cols-2 gap-4">
|
||||||
|
<Card title="表单上传">
|
||||||
|
<a-button @click="uploadModalApi.open()">打开</a-button>
|
||||||
|
<UploadModal />
|
||||||
|
</Card>
|
||||||
<Card title="单上传, 会绑定为string" size="small">
|
<Card title="单上传, 会绑定为string" size="small">
|
||||||
<ImageUpload v-model:value="singleImageId" />
|
<ImageUpload v-model:value="singleImageId" />
|
||||||
当前绑定值: {{ singleImageId }}
|
当前绑定值: {{ singleImageId }}
|
||||||
|
|||||||
70
apps/web-antd/src/views/演示使用自行删除/upload/upload-modal.vue
Normal file
70
apps/web-antd/src/views/演示使用自行删除/upload/upload-modal.vue
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { h } from 'vue';
|
||||||
|
|
||||||
|
import { JsonPreview, useVbenModal } from '@vben/common-ui';
|
||||||
|
|
||||||
|
import { Modal, Space } from 'ant-design-vue';
|
||||||
|
|
||||||
|
import { useVbenForm } from '#/adapter/form';
|
||||||
|
|
||||||
|
const [BasicForm, formApi] = useVbenForm({
|
||||||
|
layout: 'vertical',
|
||||||
|
schema: [
|
||||||
|
{
|
||||||
|
label: '图片上传多图',
|
||||||
|
component: 'ImageUpload',
|
||||||
|
fieldName: 'ossIds',
|
||||||
|
componentProps: {
|
||||||
|
maxCount: 3,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '图片上传单图',
|
||||||
|
component: 'ImageUpload',
|
||||||
|
fieldName: 'ossId',
|
||||||
|
componentProps: {
|
||||||
|
maxCount: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
showDefaultActions: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
async function getValues() {
|
||||||
|
try {
|
||||||
|
const v = await formApi.getValues();
|
||||||
|
console.log(v);
|
||||||
|
|
||||||
|
Modal.info({
|
||||||
|
content: () => h(JsonPreview, { data: v }),
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleAssign() {
|
||||||
|
const ids = ['1908761290673315841', '1907738568539332610'];
|
||||||
|
await formApi.setValues({
|
||||||
|
ossIds: ids,
|
||||||
|
ossId: ids[0],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const [BasicModal] = useVbenModal({
|
||||||
|
title: '上传',
|
||||||
|
footer: false,
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<BasicModal>
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<Space>
|
||||||
|
<a-button @click="handleAssign">赋值</a-button>
|
||||||
|
<a-button @click="getValues">获取值</a-button>
|
||||||
|
</Space>
|
||||||
|
<BasicForm />
|
||||||
|
</div>
|
||||||
|
</BasicModal>
|
||||||
|
</template>
|
||||||
@@ -43,6 +43,9 @@ export type BeforeCloseScope = {
|
|||||||
isConfirm: boolean;
|
isConfirm: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* alert 属性
|
||||||
|
*/
|
||||||
export type AlertProps = {
|
export type AlertProps = {
|
||||||
/** 关闭前的回调,如果返回false,则终止关闭 */
|
/** 关闭前的回调,如果返回false,则终止关闭 */
|
||||||
beforeClose?: (
|
beforeClose?: (
|
||||||
@@ -50,6 +53,8 @@ export type AlertProps = {
|
|||||||
) => boolean | Promise<boolean | undefined> | undefined;
|
) => boolean | Promise<boolean | undefined> | undefined;
|
||||||
/** 边框 */
|
/** 边框 */
|
||||||
bordered?: boolean;
|
bordered?: boolean;
|
||||||
|
/** 按钮对齐方式 */
|
||||||
|
buttonAlign?: 'center' | 'end' | 'start';
|
||||||
/** 取消按钮的标题 */
|
/** 取消按钮的标题 */
|
||||||
cancelText?: string;
|
cancelText?: string;
|
||||||
/** 是否居中显示 */
|
/** 是否居中显示 */
|
||||||
@@ -62,6 +67,8 @@ export type AlertProps = {
|
|||||||
content: Component | string;
|
content: Component | string;
|
||||||
/** 弹窗内容的额外样式 */
|
/** 弹窗内容的额外样式 */
|
||||||
contentClass?: string;
|
contentClass?: string;
|
||||||
|
/** 执行beforeClose回调期间,在内容区域显示一个loading遮罩*/
|
||||||
|
contentMasking?: boolean;
|
||||||
/** 弹窗的图标(在标题的前面) */
|
/** 弹窗的图标(在标题的前面) */
|
||||||
icon?: Component | IconType;
|
icon?: Component | IconType;
|
||||||
/** 是否显示取消按钮 */
|
/** 是否显示取消按钮 */
|
||||||
@@ -70,6 +77,25 @@ export type AlertProps = {
|
|||||||
title?: string;
|
title?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** prompt 属性 */
|
||||||
|
export type PromptProps<T = any> = {
|
||||||
|
/** 关闭前的回调,如果返回false,则终止关闭 */
|
||||||
|
beforeClose?: (scope: {
|
||||||
|
isConfirm: boolean;
|
||||||
|
value: T | undefined;
|
||||||
|
}) => boolean | Promise<boolean | undefined> | undefined;
|
||||||
|
/** 用于接受用户输入的组件 */
|
||||||
|
component?: Component;
|
||||||
|
/** 输入组件的属性 */
|
||||||
|
componentProps?: Recordable<any>;
|
||||||
|
/** 输入组件的插槽 */
|
||||||
|
componentSlots?: Recordable<Component>;
|
||||||
|
/** 默认值 */
|
||||||
|
defaultValue?: T;
|
||||||
|
/** 输入组件的值属性名 */
|
||||||
|
modelPropName?: string;
|
||||||
|
} & Omit<AlertProps, 'beforeClose'>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 函数签名
|
* 函数签名
|
||||||
* alert和confirm的函数签名相同。
|
* alert和confirm的函数签名相同。
|
||||||
|
|||||||
@@ -167,6 +167,23 @@ vxeUI.renderer.add('CellLink', {
|
|||||||
|
|
||||||
当启用了表单搜索时,可以在toolbarConfig中配置`search`为`true`来让表格在工具栏区域显示一个搜索表单控制按钮。表格的所有以`form-`开头的命名插槽都会被传递给搜索表单。
|
当启用了表单搜索时,可以在toolbarConfig中配置`search`为`true`来让表格在工具栏区域显示一个搜索表单控制按钮。表格的所有以`form-`开头的命名插槽都会被传递给搜索表单。
|
||||||
|
|
||||||
|
### 定制分隔条
|
||||||
|
|
||||||
|
当你启用表单搜索时,在表单和表格之间会显示一个分隔条。这个分隔条使用了默认的组件背景色,并且横向贯穿整个Vben Vxe Table在视觉上融入了页面的默认背景中。如果你在Vben Vxe Table的外层包裹了一个不同背景色的容器(如将其放在一个Card内),默认的表单和表格之间的分隔条可能就显得格格不入了,下面的代码演示了如何定制这个分隔条。
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const [Grid] = useVbenVxeGrid({
|
||||||
|
formOptions: {},
|
||||||
|
gridOptions: {},
|
||||||
|
// 完全移除分隔条
|
||||||
|
separator: false,
|
||||||
|
// 你也可以使用下面的代码来移除分隔条
|
||||||
|
// separator: { show: false },
|
||||||
|
// 或者使用下面的代码来改变分隔条的颜色
|
||||||
|
// separator: { backgroundColor: 'rgba(100,100,0,0.5)' },
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
<DemoPreview dir="demos/vben-vxe-table/form" />
|
<DemoPreview dir="demos/vben-vxe-table/form" />
|
||||||
|
|
||||||
## 单元格编辑
|
## 单元格编辑
|
||||||
@@ -231,15 +248,16 @@ useVbenVxeGrid 返回的第二个参数,是一个对象,包含了一些表
|
|||||||
|
|
||||||
所有属性都可以传入 `useVbenVxeGrid` 的第一个参数中。
|
所有属性都可以传入 `useVbenVxeGrid` 的第一个参数中。
|
||||||
|
|
||||||
| 属性名 | 描述 | 类型 |
|
| 属性名 | 描述 | 类型 | 版本要求 |
|
||||||
| -------------- | -------------------- | ------------------- |
|
| --- | --- | --- | --- |
|
||||||
| tableTitle | 表格标题 | `string` |
|
| tableTitle | 表格标题 | `string` | - |
|
||||||
| tableTitleHelp | 表格标题帮助信息 | `string` |
|
| tableTitleHelp | 表格标题帮助信息 | `string` | - |
|
||||||
| gridClass | grid组件的class | `string` |
|
| gridClass | grid组件的class | `string` | - |
|
||||||
| gridOptions | grid组件的参数 | `VxeTableGridProps` |
|
| gridOptions | grid组件的参数 | `VxeTableGridProps` | - |
|
||||||
| gridEvents | grid组件的触发的事件 | `VxeGridListeners` |
|
| gridEvents | grid组件的触发的事件 | `VxeGridListeners` | - |
|
||||||
| formOptions | 表单参数 | `VbenFormProps` |
|
| formOptions | 表单参数 | `VbenFormProps` | - |
|
||||||
| showSearchForm | 是否显示搜索表单 | `boolean` |
|
| showSearchForm | 是否显示搜索表单 | `boolean` | - |
|
||||||
|
| separator | 搜索表单与表格主体之间的分隔条 | `boolean\|SeparatorOptions` | >5.5.4 |
|
||||||
|
|
||||||
## Slots
|
## Slots
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { h } from 'vue';
|
|||||||
|
|
||||||
import { alert, VbenButton } from '@vben/common-ui';
|
import { alert, VbenButton } from '@vben/common-ui';
|
||||||
|
|
||||||
import { Empty } from 'ant-design-vue';
|
import { Result } from 'ant-design-vue';
|
||||||
|
|
||||||
function showAlert() {
|
function showAlert() {
|
||||||
alert('This is an alert message');
|
alert('This is an alert message');
|
||||||
@@ -18,7 +18,12 @@ function showIconAlert() {
|
|||||||
|
|
||||||
function showCustomAlert() {
|
function showCustomAlert() {
|
||||||
alert({
|
alert({
|
||||||
content: h(Empty, { description: '什么都没有' }),
|
buttonAlign: 'center',
|
||||||
|
content: h(Result, {
|
||||||
|
status: 'success',
|
||||||
|
subTitle: '已成功创建订单。订单ID:2017182818828182881',
|
||||||
|
title: '操作成功',
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import { h } from 'vue';
|
||||||
|
|
||||||
import { alert, prompt, VbenButton } from '@vben/common-ui';
|
import { alert, prompt, VbenButton } from '@vben/common-ui';
|
||||||
|
|
||||||
import { VbenSelect } from '@vben-core/shadcn-ui';
|
import { Input, RadioGroup } from 'ant-design-vue';
|
||||||
|
import { BadgeJapaneseYen } from 'lucide-vue-next';
|
||||||
|
|
||||||
function showPrompt() {
|
function showPrompt() {
|
||||||
prompt({
|
prompt({
|
||||||
@@ -17,25 +20,62 @@ function showPrompt() {
|
|||||||
|
|
||||||
function showSelectPrompt() {
|
function showSelectPrompt() {
|
||||||
prompt({
|
prompt({
|
||||||
component: VbenSelect,
|
component: Input,
|
||||||
componentProps: {
|
componentProps: {
|
||||||
|
placeholder: '请输入',
|
||||||
|
prefix: '充值金额',
|
||||||
|
type: 'number',
|
||||||
|
},
|
||||||
|
componentSlots: {
|
||||||
|
addonAfter: () => h(BadgeJapaneseYen),
|
||||||
|
},
|
||||||
|
content: '此弹窗演示了如何使用componentSlots传递自定义插槽',
|
||||||
|
icon: 'question',
|
||||||
|
modelPropName: 'value',
|
||||||
|
}).then((val) => {
|
||||||
|
if (val) alert(`你输入的是${val}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function sleep(ms: number) {
|
||||||
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||||
|
}
|
||||||
|
|
||||||
|
function showAsyncPrompt() {
|
||||||
|
prompt({
|
||||||
|
async beforeClose(scope) {
|
||||||
|
console.log(scope);
|
||||||
|
if (scope.isConfirm) {
|
||||||
|
if (scope.value) {
|
||||||
|
// 模拟异步操作,如果不成功,可以返回false
|
||||||
|
await sleep(2000);
|
||||||
|
} else {
|
||||||
|
alert('请选择一个选项');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
component: RadioGroup,
|
||||||
|
componentProps: {
|
||||||
|
class: 'flex flex-col',
|
||||||
options: [
|
options: [
|
||||||
{ label: 'Option 1', value: 'option1' },
|
{ label: 'Option 1', value: 'option1' },
|
||||||
{ label: 'Option 2', value: 'option2' },
|
{ label: 'Option 2', value: 'option2' },
|
||||||
{ label: 'Option 3', value: 'option3' },
|
{ label: 'Option 3', value: 'option3' },
|
||||||
],
|
],
|
||||||
placeholder: '请选择',
|
|
||||||
},
|
},
|
||||||
content: 'This is an alert message with icon',
|
content: '选择一个选项后再点击[确认]',
|
||||||
icon: 'question',
|
icon: 'question',
|
||||||
|
modelPropName: 'value',
|
||||||
}).then((val) => {
|
}).then((val) => {
|
||||||
alert(`你选择的是${val}`);
|
alert(`${val} 已设置。`);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<div class="flex gap-4">
|
<div class="flex gap-4">
|
||||||
<VbenButton @click="showPrompt">Prompt</VbenButton>
|
<VbenButton @click="showPrompt">Prompt</VbenButton>
|
||||||
<VbenButton @click="showSelectPrompt">Confirm With Select</VbenButton>
|
<VbenButton @click="showSelectPrompt">Prompt With Select</VbenButton>
|
||||||
|
<VbenButton @click="showAsyncPrompt">Prompt With Async</VbenButton>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import type { Component } from 'vue';
|
import type { Component, VNode } from 'vue';
|
||||||
|
|
||||||
import type { Recordable } from '@vben-core/typings';
|
import type { Recordable } from '@vben-core/typings';
|
||||||
|
|
||||||
import type { AlertProps, BeforeCloseScope } from './alert';
|
import type { AlertProps, BeforeCloseScope, PromptProps } from './alert';
|
||||||
|
|
||||||
import { h, ref, render } from 'vue';
|
import { h, nextTick, ref, render } from 'vue';
|
||||||
|
|
||||||
import { useSimpleLocale } from '@vben-core/composables';
|
import { useSimpleLocale } from '@vben-core/composables';
|
||||||
import { Input } from '@vben-core/shadcn-ui';
|
import { Input } from '@vben-core/shadcn-ui';
|
||||||
@@ -130,40 +130,58 @@ export function vbenConfirm(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function vbenPrompt<T = any>(
|
export async function vbenPrompt<T = any>(
|
||||||
options: Omit<AlertProps, 'beforeClose'> & {
|
options: PromptProps<T>,
|
||||||
beforeClose?: (scope: {
|
|
||||||
isConfirm: boolean;
|
|
||||||
value: T | undefined;
|
|
||||||
}) => boolean | Promise<boolean | undefined> | undefined;
|
|
||||||
component?: Component;
|
|
||||||
componentProps?: Recordable<any>;
|
|
||||||
defaultValue?: T;
|
|
||||||
modelPropName?: string;
|
|
||||||
},
|
|
||||||
): Promise<T | undefined> {
|
): Promise<T | undefined> {
|
||||||
const {
|
const {
|
||||||
component: _component,
|
component: _component,
|
||||||
componentProps: _componentProps,
|
componentProps: _componentProps,
|
||||||
|
componentSlots,
|
||||||
content,
|
content,
|
||||||
defaultValue,
|
defaultValue,
|
||||||
modelPropName: _modelPropName,
|
modelPropName: _modelPropName,
|
||||||
...delegated
|
...delegated
|
||||||
} = options;
|
} = options;
|
||||||
const contents: Component[] = [];
|
|
||||||
const modelValue = ref<T | undefined>(defaultValue);
|
const modelValue = ref<T | undefined>(defaultValue);
|
||||||
|
const inputComponentRef = ref<null | VNode>(null);
|
||||||
|
const staticContents: Component[] = [];
|
||||||
|
|
||||||
if (isString(content)) {
|
if (isString(content)) {
|
||||||
contents.push(h('span', content));
|
staticContents.push(h('span', content));
|
||||||
} else {
|
} else if (content) {
|
||||||
contents.push(content);
|
staticContents.push(content as Component);
|
||||||
}
|
}
|
||||||
const componentProps = _componentProps || {};
|
|
||||||
const modelPropName = _modelPropName || 'modelValue';
|
const modelPropName = _modelPropName || 'modelValue';
|
||||||
componentProps[modelPropName] = modelValue.value;
|
const componentProps = { ..._componentProps };
|
||||||
componentProps[`onUpdate:${modelPropName}`] = (val: any) => {
|
|
||||||
modelValue.value = val;
|
// 每次渲染时都会重新计算的内容函数
|
||||||
|
const contentRenderer = () => {
|
||||||
|
const currentProps = { ...componentProps };
|
||||||
|
|
||||||
|
// 设置当前值
|
||||||
|
currentProps[modelPropName] = modelValue.value;
|
||||||
|
|
||||||
|
// 设置更新处理函数
|
||||||
|
currentProps[`onUpdate:${modelPropName}`] = (val: T) => {
|
||||||
|
modelValue.value = val;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 创建输入组件
|
||||||
|
inputComponentRef.value = h(
|
||||||
|
_component || Input,
|
||||||
|
currentProps,
|
||||||
|
componentSlots,
|
||||||
|
);
|
||||||
|
|
||||||
|
// 返回包含静态内容和输入组件的数组
|
||||||
|
return h(
|
||||||
|
'div',
|
||||||
|
{ class: 'flex flex-col gap-2' },
|
||||||
|
{ default: () => [...staticContents, inputComponentRef.value] },
|
||||||
|
);
|
||||||
};
|
};
|
||||||
const componentRef = h(_component || Input, componentProps);
|
|
||||||
contents.push(componentRef);
|
|
||||||
const props: AlertProps & Recordable<any> = {
|
const props: AlertProps & Recordable<any> = {
|
||||||
...delegated,
|
...delegated,
|
||||||
async beforeClose(scope: BeforeCloseScope) {
|
async beforeClose(scope: BeforeCloseScope) {
|
||||||
@@ -174,23 +192,46 @@ export async function vbenPrompt<T = any>(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
content: h(
|
// 使用函数形式,每次渲染都会重新计算内容
|
||||||
'div',
|
content: contentRenderer,
|
||||||
{ class: 'flex flex-col gap-2' },
|
contentMasking: true,
|
||||||
{ default: () => contents },
|
async onOpened() {
|
||||||
),
|
await nextTick();
|
||||||
onOpened() {
|
const componentRef: null | VNode = inputComponentRef.value;
|
||||||
// 组件挂载完成后,自动聚焦到输入组件
|
if (componentRef) {
|
||||||
if (
|
if (
|
||||||
componentRef.component?.exposed &&
|
componentRef.component?.exposed &&
|
||||||
isFunction(componentRef.component.exposed.focus)
|
isFunction(componentRef.component.exposed.focus)
|
||||||
) {
|
) {
|
||||||
componentRef.component.exposed.focus();
|
componentRef.component.exposed.focus();
|
||||||
} else if (componentRef.el && isFunction(componentRef.el.focus)) {
|
} else {
|
||||||
componentRef.el.focus();
|
if (componentRef.el) {
|
||||||
|
if (
|
||||||
|
isFunction(componentRef.el.focus) &&
|
||||||
|
['BUTTON', 'INPUT', 'SELECT', 'TEXTAREA'].includes(
|
||||||
|
componentRef.el.tagName,
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
componentRef.el.focus();
|
||||||
|
} else if (isFunction(componentRef.el.querySelector)) {
|
||||||
|
const focusableElement = componentRef.el.querySelector(
|
||||||
|
'input, select, textarea, button',
|
||||||
|
);
|
||||||
|
if (focusableElement && isFunction(focusableElement.focus)) {
|
||||||
|
focusableElement.focus();
|
||||||
|
}
|
||||||
|
} else if (
|
||||||
|
componentRef.el.nextElementSibling &&
|
||||||
|
isFunction(componentRef.el.nextElementSibling.focus)
|
||||||
|
) {
|
||||||
|
componentRef.el.nextElementSibling.focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
await vbenConfirm(props);
|
await vbenConfirm(props);
|
||||||
return modelValue.value;
|
return modelValue.value;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
import type { Component } from 'vue';
|
import type { Component, VNode, VNodeArrayChildren } from 'vue';
|
||||||
|
|
||||||
|
import type { Recordable } from '@vben-core/typings';
|
||||||
|
|
||||||
export type IconType = 'error' | 'info' | 'question' | 'success' | 'warning';
|
export type IconType = 'error' | 'info' | 'question' | 'success' | 'warning';
|
||||||
|
|
||||||
@@ -13,6 +15,11 @@ export type AlertProps = {
|
|||||||
) => boolean | Promise<boolean | undefined> | undefined;
|
) => boolean | Promise<boolean | undefined> | undefined;
|
||||||
/** 边框 */
|
/** 边框 */
|
||||||
bordered?: boolean;
|
bordered?: boolean;
|
||||||
|
/**
|
||||||
|
* 按钮对齐方式
|
||||||
|
* @default 'end'
|
||||||
|
*/
|
||||||
|
buttonAlign?: 'center' | 'end' | 'start';
|
||||||
/** 取消按钮的标题 */
|
/** 取消按钮的标题 */
|
||||||
cancelText?: string;
|
cancelText?: string;
|
||||||
/** 是否居中显示 */
|
/** 是否居中显示 */
|
||||||
@@ -25,6 +32,8 @@ export type AlertProps = {
|
|||||||
content: Component | string;
|
content: Component | string;
|
||||||
/** 弹窗内容的额外样式 */
|
/** 弹窗内容的额外样式 */
|
||||||
contentClass?: string;
|
contentClass?: string;
|
||||||
|
/** 执行beforeClose回调期间,在内容区域显示一个loading遮罩*/
|
||||||
|
contentMasking?: boolean;
|
||||||
/** 弹窗的图标(在标题的前面) */
|
/** 弹窗的图标(在标题的前面) */
|
||||||
icon?: Component | IconType;
|
icon?: Component | IconType;
|
||||||
/** 是否显示取消按钮 */
|
/** 是否显示取消按钮 */
|
||||||
@@ -32,3 +41,26 @@ export type AlertProps = {
|
|||||||
/** 弹窗标题 */
|
/** 弹窗标题 */
|
||||||
title?: string;
|
title?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** Prompt属性 */
|
||||||
|
export type PromptProps<T = any> = {
|
||||||
|
/** 关闭前的回调,如果返回false,则终止关闭 */
|
||||||
|
beforeClose?: (scope: {
|
||||||
|
isConfirm: boolean;
|
||||||
|
value: T | undefined;
|
||||||
|
}) => boolean | Promise<boolean | undefined> | undefined;
|
||||||
|
/** 用于接受用户输入的组件 */
|
||||||
|
component?: Component;
|
||||||
|
/** 输入组件的属性 */
|
||||||
|
componentProps?: Recordable<any>;
|
||||||
|
/** 输入组件的插槽 */
|
||||||
|
componentSlots?:
|
||||||
|
| (() => any)
|
||||||
|
| Recordable<unknown>
|
||||||
|
| VNode
|
||||||
|
| VNodeArrayChildren;
|
||||||
|
/** 默认值 */
|
||||||
|
defaultValue?: T;
|
||||||
|
/** 输入组件的值属性名 */
|
||||||
|
modelPropName?: string;
|
||||||
|
} & Omit<AlertProps, 'beforeClose'>;
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ import { cn } from '@vben-core/shared/utils';
|
|||||||
|
|
||||||
const props = withDefaults(defineProps<AlertProps>(), {
|
const props = withDefaults(defineProps<AlertProps>(), {
|
||||||
bordered: true,
|
bordered: true,
|
||||||
|
buttonAlign: 'end',
|
||||||
centered: true,
|
centered: true,
|
||||||
containerClass: 'w-[520px]',
|
containerClass: 'w-[520px]',
|
||||||
});
|
});
|
||||||
@@ -154,9 +155,9 @@ async function handleOpenChange(val: boolean) {
|
|||||||
<div class="m-4 mb-6 min-h-[30px]">
|
<div class="m-4 mb-6 min-h-[30px]">
|
||||||
<VbenRenderContent :content="content" render-br />
|
<VbenRenderContent :content="content" render-br />
|
||||||
</div>
|
</div>
|
||||||
<VbenLoading v-if="loading" :spinning="loading" />
|
<VbenLoading v-if="loading && contentMasking" :spinning="loading" />
|
||||||
</AlertDialogDescription>
|
</AlertDialogDescription>
|
||||||
<div class="flex justify-end gap-x-2">
|
<div class="flex justify-end gap-x-2" :class="`justify-${buttonAlign}`">
|
||||||
<AlertDialogCancel v-if="showCancel" :disabled="loading">
|
<AlertDialogCancel v-if="showCancel" :disabled="loading">
|
||||||
<component
|
<component
|
||||||
:is="components.DefaultButton || VbenButton"
|
:is="components.DefaultButton || VbenButton"
|
||||||
|
|||||||
@@ -88,10 +88,10 @@ export class DrawerApi {
|
|||||||
/**
|
/**
|
||||||
* 关闭弹窗
|
* 关闭弹窗
|
||||||
*/
|
*/
|
||||||
close() {
|
async close() {
|
||||||
// 通过 onBeforeClose 钩子函数来判断是否允许关闭弹窗
|
// 通过 onBeforeClose 钩子函数来判断是否允许关闭弹窗
|
||||||
// 如果 onBeforeClose 返回 false,则不关闭弹窗
|
// 如果 onBeforeClose 返回 false,则不关闭弹窗
|
||||||
const allowClose = this.api.onBeforeClose?.() ?? true;
|
const allowClose = (await this.api.onBeforeClose?.()) ?? true;
|
||||||
if (allowClose) {
|
if (allowClose) {
|
||||||
this.store.setState((prev) => ({
|
this.store.setState((prev) => ({
|
||||||
...prev,
|
...prev,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import type { Component, Ref } from 'vue';
|
import type { Component, Ref } from 'vue';
|
||||||
|
|
||||||
import type { ClassType } from '@vben-core/typings';
|
import type { ClassType, MaybePromise } from '@vben-core/typings';
|
||||||
|
|
||||||
import type { DrawerApi } from './drawer-api';
|
import type { DrawerApi } from './drawer-api';
|
||||||
|
|
||||||
@@ -151,7 +151,7 @@ export interface DrawerApiOptions extends DrawerState {
|
|||||||
* 关闭前的回调,返回 false 可以阻止关闭
|
* 关闭前的回调,返回 false 可以阻止关闭
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
onBeforeClose?: () => void;
|
onBeforeClose?: () => MaybePromise<boolean | undefined>;
|
||||||
/**
|
/**
|
||||||
* 点击取消按钮的回调
|
* 点击取消按钮的回调
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -6,11 +6,11 @@ import type { ValueType, VbenButtonGroupProps } from './button';
|
|||||||
import { computed, ref, watch } from 'vue';
|
import { computed, ref, watch } from 'vue';
|
||||||
|
|
||||||
import { Circle, CircleCheckBig, LoaderCircle } from '@vben-core/icons';
|
import { Circle, CircleCheckBig, LoaderCircle } from '@vben-core/icons';
|
||||||
import { VbenRenderContent } from '@vben-core/shadcn-ui';
|
|
||||||
import { cn, isFunction } from '@vben-core/shared/utils';
|
import { cn, isFunction } from '@vben-core/shared/utils';
|
||||||
|
|
||||||
import { objectOmit } from '@vueuse/core';
|
import { objectOmit } from '@vueuse/core';
|
||||||
|
|
||||||
|
import { VbenRenderContent } from '../render-content';
|
||||||
import VbenButtonGroup from './button-group.vue';
|
import VbenButtonGroup from './button-group.vue';
|
||||||
import Button from './button.vue';
|
import Button from './button.vue';
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,14 @@
|
|||||||
".": {
|
".": {
|
||||||
"types": "./src/index.ts",
|
"types": "./src/index.ts",
|
||||||
"default": "./src/index.ts"
|
"default": "./src/index.ts"
|
||||||
|
},
|
||||||
|
"./es/tippy": {
|
||||||
|
"types": "./src/components/tippy/index.ts",
|
||||||
|
"default": "./src/components/tippy/index.ts"
|
||||||
|
},
|
||||||
|
"./es/loading": {
|
||||||
|
"types": "./src/components/loading/index.ts",
|
||||||
|
"default": "./src/components/loading/index.ts"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
@@ -5,9 +5,11 @@ import type {
|
|||||||
RouteLocationNormalizedLoadedGeneric,
|
RouteLocationNormalizedLoadedGeneric,
|
||||||
} from 'vue-router';
|
} from 'vue-router';
|
||||||
|
|
||||||
|
import { computed } from 'vue';
|
||||||
|
import { RouterView } from 'vue-router';
|
||||||
|
|
||||||
import { preferences, usePreferences } from '@vben/preferences';
|
import { preferences, usePreferences } from '@vben/preferences';
|
||||||
import { storeToRefs, useTabbarStore } from '@vben/stores';
|
import { storeToRefs, useTabbarStore } from '@vben/stores';
|
||||||
import { RouterView } from 'vue-router';
|
|
||||||
|
|
||||||
import { IFrameRouterView } from '../../iframe';
|
import { IFrameRouterView } from '../../iframe';
|
||||||
|
|
||||||
@@ -19,6 +21,15 @@ const { keepAlive } = usePreferences();
|
|||||||
const { getCachedTabs, getExcludeCachedTabs, renderRouteView } =
|
const { getCachedTabs, getExcludeCachedTabs, renderRouteView } =
|
||||||
storeToRefs(tabbarStore);
|
storeToRefs(tabbarStore);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否使用动画
|
||||||
|
*/
|
||||||
|
const getEnabledTransition = computed(() => {
|
||||||
|
const { transition } = preferences;
|
||||||
|
const transitionName = transition.name;
|
||||||
|
return transitionName && transition.enable;
|
||||||
|
});
|
||||||
|
|
||||||
// 页面切换动画
|
// 页面切换动画
|
||||||
function getTransitionName(_route: RouteLocationNormalizedLoaded) {
|
function getTransitionName(_route: RouteLocationNormalizedLoaded) {
|
||||||
// 如果偏好设置未设置,则不使用动画
|
// 如果偏好设置未设置,则不使用动画
|
||||||
@@ -89,7 +100,12 @@ function transformComponent(
|
|||||||
<div class="relative h-full">
|
<div class="relative h-full">
|
||||||
<IFrameRouterView />
|
<IFrameRouterView />
|
||||||
<RouterView v-slot="{ Component, route }">
|
<RouterView v-slot="{ Component, route }">
|
||||||
<Transition :name="getTransitionName(route)" appear mode="out-in">
|
<Transition
|
||||||
|
v-if="getEnabledTransition"
|
||||||
|
:name="getTransitionName(route)"
|
||||||
|
appear
|
||||||
|
mode="out-in"
|
||||||
|
>
|
||||||
<KeepAlive
|
<KeepAlive
|
||||||
v-if="keepAlive"
|
v-if="keepAlive"
|
||||||
:exclude="getExcludeCachedTabs"
|
:exclude="getExcludeCachedTabs"
|
||||||
@@ -108,6 +124,25 @@ function transformComponent(
|
|||||||
:key="route.fullPath"
|
:key="route.fullPath"
|
||||||
/>
|
/>
|
||||||
</Transition>
|
</Transition>
|
||||||
|
<template v-else>
|
||||||
|
<KeepAlive
|
||||||
|
v-if="keepAlive"
|
||||||
|
:exclude="getExcludeCachedTabs"
|
||||||
|
:include="getCachedTabs"
|
||||||
|
>
|
||||||
|
<component
|
||||||
|
:is="transformComponent(Component, route)"
|
||||||
|
v-if="renderRouteView"
|
||||||
|
v-show="!route.meta.iframeSrc"
|
||||||
|
:key="route.fullPath"
|
||||||
|
/>
|
||||||
|
</KeepAlive>
|
||||||
|
<component
|
||||||
|
:is="Component"
|
||||||
|
v-else-if="renderRouteView"
|
||||||
|
:key="route.fullPath"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
</RouterView>
|
</RouterView>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -46,9 +46,13 @@
|
|||||||
--vxe-ui-table-row-current-background-color: hsl(var(--accent));
|
--vxe-ui-table-row-current-background-color: hsl(var(--accent));
|
||||||
--vxe-ui-table-row-hover-current-background-color: hsl(var(--accent-hover));
|
--vxe-ui-table-row-hover-current-background-color: hsl(var(--accent-hover));
|
||||||
|
|
||||||
height: auto !important;
|
|
||||||
|
|
||||||
/* --vxe-ui-table-fixed-scrolling-box-shadow-color: rgb(0 0 0 / 80%); */
|
/* --vxe-ui-table-fixed-scrolling-box-shadow-color: rgb(0 0 0 / 80%); */
|
||||||
|
|
||||||
|
/** 右上角toolbar按钮色/翻页主题色保持一致 */
|
||||||
|
--vxe-ui-font-primary-lighten-color: hsl(var(--primary-500));
|
||||||
|
--vxe-ui-font-primary-darken-color: hsl(var(--primary-600));
|
||||||
|
|
||||||
|
height: auto !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
html[data-vxe-ui-theme='dark'] .vxe-grid {
|
html[data-vxe-ui-theme='dark'] .vxe-grid {
|
||||||
|
|||||||
@@ -1,6 +1,3 @@
|
|||||||
import type { ClassType, DeepPartial } from '@vben/types';
|
|
||||||
import type { VbenFormProps } from '@vben-core/form-ui';
|
|
||||||
import type { Ref } from 'vue';
|
|
||||||
import type {
|
import type {
|
||||||
VxeGridListeners,
|
VxeGridListeners,
|
||||||
VxeGridPropTypes,
|
VxeGridPropTypes,
|
||||||
@@ -8,6 +5,12 @@ import type {
|
|||||||
VxeUIExport,
|
VxeUIExport,
|
||||||
} from 'vxe-table';
|
} from 'vxe-table';
|
||||||
|
|
||||||
|
import type { Ref } from 'vue';
|
||||||
|
|
||||||
|
import type { ClassType, DeepPartial } from '@vben/types';
|
||||||
|
|
||||||
|
import type { VbenFormProps } from '@vben-core/form-ui';
|
||||||
|
|
||||||
import type { VxeGridApi } from './api';
|
import type { VxeGridApi } from './api';
|
||||||
|
|
||||||
import { useVbenForm } from '@vben-core/form-ui';
|
import { useVbenForm } from '@vben-core/form-ui';
|
||||||
@@ -28,6 +31,10 @@ export interface VxeTableGridOptions<T = any> extends VxeTableGridProps<T> {
|
|||||||
toolbarConfig?: ToolbarConfigOptions;
|
toolbarConfig?: ToolbarConfigOptions;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface SeparatorOptions {
|
||||||
|
show?: boolean;
|
||||||
|
backgroundColor?: string;
|
||||||
|
}
|
||||||
export interface VxeGridProps {
|
export interface VxeGridProps {
|
||||||
/**
|
/**
|
||||||
* 标题
|
* 标题
|
||||||
@@ -61,13 +68,17 @@ export interface VxeGridProps {
|
|||||||
* 显示搜索表单
|
* 显示搜索表单
|
||||||
*/
|
*/
|
||||||
showSearchForm?: boolean;
|
showSearchForm?: boolean;
|
||||||
|
/**
|
||||||
|
* 搜索表单与表格主体之间的分隔条
|
||||||
|
*/
|
||||||
|
separator?: boolean | SeparatorOptions;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ExtendedVxeGridApi = {
|
export type ExtendedVxeGridApi = VxeGridApi & {
|
||||||
useStore: <T = NoInfer<VxeGridProps>>(
|
useStore: <T = NoInfer<VxeGridProps>>(
|
||||||
selector?: (state: NoInfer<VxeGridProps>) => T,
|
selector?: (state: NoInfer<VxeGridProps>) => T,
|
||||||
) => Readonly<Ref<T>>;
|
) => Readonly<Ref<T>>;
|
||||||
} & VxeGridApi;
|
};
|
||||||
|
|
||||||
export interface SetupVxeTable {
|
export interface SetupVxeTable {
|
||||||
configVxeTable: (ui: VxeUIExport) => void;
|
configVxeTable: (ui: VxeUIExport) => void;
|
||||||
|
|||||||
@@ -29,7 +29,13 @@ import { usePriorityValues } from '@vben/hooks';
|
|||||||
import { EmptyIcon } from '@vben/icons';
|
import { EmptyIcon } from '@vben/icons';
|
||||||
import { $t } from '@vben/locales';
|
import { $t } from '@vben/locales';
|
||||||
import { usePreferences } from '@vben/preferences';
|
import { usePreferences } from '@vben/preferences';
|
||||||
import { cloneDeep, cn, isEqual, mergeWithArrayOverride } from '@vben/utils';
|
import {
|
||||||
|
cloneDeep,
|
||||||
|
cn,
|
||||||
|
isBoolean,
|
||||||
|
isEqual,
|
||||||
|
mergeWithArrayOverride,
|
||||||
|
} from '@vben/utils';
|
||||||
|
|
||||||
import { VbenHelpTooltip, VbenLoading } from '@vben-core/shadcn-ui';
|
import { VbenHelpTooltip, VbenLoading } from '@vben-core/shadcn-ui';
|
||||||
|
|
||||||
@@ -67,10 +73,30 @@ const {
|
|||||||
tableTitle,
|
tableTitle,
|
||||||
tableTitleHelp,
|
tableTitleHelp,
|
||||||
showSearchForm,
|
showSearchForm,
|
||||||
|
separator,
|
||||||
} = usePriorityValues(props, state);
|
} = usePriorityValues(props, state);
|
||||||
|
|
||||||
const { isMobile } = usePreferences();
|
const { isMobile } = usePreferences();
|
||||||
|
const isSeparator = computed(() => {
|
||||||
|
if (
|
||||||
|
!formOptions.value ||
|
||||||
|
showSearchForm.value === false ||
|
||||||
|
separator.value === false
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (separator.value === true || separator.value === undefined) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return separator.value.show !== false;
|
||||||
|
});
|
||||||
|
const separatorBg = computed(() => {
|
||||||
|
return !separator.value ||
|
||||||
|
isBoolean(separator.value) ||
|
||||||
|
!separator.value.backgroundColor
|
||||||
|
? undefined
|
||||||
|
: separator.value.backgroundColor;
|
||||||
|
});
|
||||||
const slots: SetupContext['slots'] = useSlots();
|
const slots: SetupContext['slots'] = useSlots();
|
||||||
|
|
||||||
const [Form, formApi] = useTableForm({
|
const [Form, formApi] = useTableForm({
|
||||||
@@ -380,7 +406,18 @@ onUnmounted(() => {
|
|||||||
<div
|
<div
|
||||||
v-if="formOptions"
|
v-if="formOptions"
|
||||||
v-show="showSearchForm !== false"
|
v-show="showSearchForm !== false"
|
||||||
:class="cn('relative rounded py-3', isCompactForm ? 'pb-8' : 'pb-4')"
|
:class="
|
||||||
|
cn(
|
||||||
|
'relative rounded py-3',
|
||||||
|
isCompactForm
|
||||||
|
? isSeparator
|
||||||
|
? 'pb-8'
|
||||||
|
: 'pb-4'
|
||||||
|
: isSeparator
|
||||||
|
? 'pb-4'
|
||||||
|
: 'pb-0',
|
||||||
|
)
|
||||||
|
"
|
||||||
>
|
>
|
||||||
<slot name="form">
|
<slot name="form">
|
||||||
<Form>
|
<Form>
|
||||||
@@ -409,6 +446,10 @@ onUnmounted(() => {
|
|||||||
</Form>
|
</Form>
|
||||||
</slot>
|
</slot>
|
||||||
<div
|
<div
|
||||||
|
v-if="isSeparator"
|
||||||
|
:style="{
|
||||||
|
...(separatorBg ? { backgroundColor: separatorBg } : undefined),
|
||||||
|
}"
|
||||||
class="bg-background-deep z-100 absolute -left-2 bottom-1 h-2 w-[calc(100%+1rem)] overflow-hidden md:bottom-2 md:h-3"
|
class="bg-background-deep z-100 absolute -left-2 bottom-1 h-2 w-[calc(100%+1rem)] overflow-hidden md:bottom-2 md:h-3"
|
||||||
></div>
|
></div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -12,20 +12,55 @@ export function getPopupContainer(node?: HTMLElement): HTMLElement {
|
|||||||
/**
|
/**
|
||||||
* VxeTable专用弹窗层
|
* VxeTable专用弹窗层
|
||||||
* 解决问题: https://gitee.com/dapppp/ruoyi-plus-vben5/issues/IB1DM3
|
* 解决问题: https://gitee.com/dapppp/ruoyi-plus-vben5/issues/IB1DM3
|
||||||
* @param _node 触发的元素
|
* @param node 触发的元素
|
||||||
|
* @param tableId 表格ID,用于区分不同表格(可选)
|
||||||
* @returns 挂载节点
|
* @returns 挂载节点
|
||||||
*/
|
*/
|
||||||
export function getVxePopupContainer(_node?: HTMLElement): HTMLElement {
|
export function getVxePopupContainer(
|
||||||
|
node?: HTMLElement,
|
||||||
|
tableId?: string,
|
||||||
|
): HTMLElement {
|
||||||
|
if (!node) return document.body;
|
||||||
|
|
||||||
|
// 检查是否在固定列内
|
||||||
|
const isInFixedColumn =
|
||||||
|
node.closest('.vxe-table--fixed-wrapper') ||
|
||||||
|
node.closest('.vxe-table--fixed-left-wrapper') ||
|
||||||
|
node.closest('.vxe-table--fixed-right-wrapper');
|
||||||
|
|
||||||
|
// 如果在固定列内,则挂载到固定列容器
|
||||||
|
if (isInFixedColumn) {
|
||||||
|
// 优先查找表格容器及父级容器
|
||||||
|
const tableContainer =
|
||||||
|
// 查找通用固定列容器
|
||||||
|
node.closest('.vxe-table--fixed-wrapper') ||
|
||||||
|
// 查找固定列容器(左侧固定列)
|
||||||
|
node.closest('.vxe-table--fixed-left-wrapper') ||
|
||||||
|
// 查找固定列容器(右侧固定列)
|
||||||
|
node.closest('.vxe-table--fixed-right-wrapper');
|
||||||
|
|
||||||
|
// 如果指定了tableId,可以查找特定ID的表格
|
||||||
|
if (tableId && tableContainer) {
|
||||||
|
const specificTable = tableContainer.closest(
|
||||||
|
`[data-table-id="${tableId}"]`,
|
||||||
|
);
|
||||||
|
if (specificTable) {
|
||||||
|
return specificTable as HTMLElement;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return tableContainer as HTMLElement;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 需要区分是否为固定列情况 如果为固定列返回parent会导致展开宽度不正常
|
* 设置行高度需要特殊处理
|
||||||
* 如果是固定列的情况直接返回body 但是这样不会跟随滚动(个人认为这属于极限场景)
|
|
||||||
* 如果有更好的办法解决 请告知
|
|
||||||
*/
|
*/
|
||||||
// if (_node?.closest('td.fixed--width')) {
|
const fixedHeightElement = node.closest('td.col--cs-height');
|
||||||
// return document.body;
|
if (fixedHeightElement) {
|
||||||
// }
|
// 默认为hidden 显示异常
|
||||||
/**
|
(fixedHeightElement as HTMLTableCellElement).style.overflow = 'visible';
|
||||||
* 兼容以前代码 先返回body 这样会造成无法跟随滚动
|
}
|
||||||
*/
|
|
||||||
return _node?.parentElement ?? document.body;
|
// 兜底方案:使用元素的父节点或文档体
|
||||||
|
return (node.parentNode as HTMLElement) || document.body;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,35 +8,64 @@ import type { Component } from 'vue';
|
|||||||
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 { defineComponent, getCurrentInstance, h, ref } from 'vue';
|
import {
|
||||||
|
defineAsyncComponent,
|
||||||
|
defineComponent,
|
||||||
|
getCurrentInstance,
|
||||||
|
h,
|
||||||
|
ref,
|
||||||
|
} 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 { notification } from 'ant-design-vue';
|
||||||
AutoComplete,
|
|
||||||
Button,
|
const AutoComplete = defineAsyncComponent(
|
||||||
Checkbox,
|
() => import('ant-design-vue/es/auto-complete'),
|
||||||
CheckboxGroup,
|
);
|
||||||
DatePicker,
|
const Button = defineAsyncComponent(() => import('ant-design-vue/es/button'));
|
||||||
Divider,
|
const Checkbox = defineAsyncComponent(
|
||||||
Input,
|
() => import('ant-design-vue/es/checkbox'),
|
||||||
InputNumber,
|
);
|
||||||
InputPassword,
|
const CheckboxGroup = defineAsyncComponent(() =>
|
||||||
Mentions,
|
import('ant-design-vue/es/checkbox').then((res) => res.CheckboxGroup),
|
||||||
notification,
|
);
|
||||||
Radio,
|
const DatePicker = defineAsyncComponent(
|
||||||
RadioGroup,
|
() => import('ant-design-vue/es/date-picker'),
|
||||||
RangePicker,
|
);
|
||||||
Rate,
|
const Divider = defineAsyncComponent(() => import('ant-design-vue/es/divider'));
|
||||||
Select,
|
const Input = defineAsyncComponent(() => import('ant-design-vue/es/input'));
|
||||||
Space,
|
const InputNumber = defineAsyncComponent(
|
||||||
Switch,
|
() => import('ant-design-vue/es/input-number'),
|
||||||
Textarea,
|
);
|
||||||
TimePicker,
|
const InputPassword = defineAsyncComponent(() =>
|
||||||
TreeSelect,
|
import('ant-design-vue/es/input').then((res) => res.InputPassword),
|
||||||
Upload,
|
);
|
||||||
} from 'ant-design-vue';
|
const Mentions = defineAsyncComponent(
|
||||||
|
() => import('ant-design-vue/es/mentions'),
|
||||||
|
);
|
||||||
|
const Radio = defineAsyncComponent(() => import('ant-design-vue/es/radio'));
|
||||||
|
const RadioGroup = defineAsyncComponent(() =>
|
||||||
|
import('ant-design-vue/es/radio').then((res) => res.RadioGroup),
|
||||||
|
);
|
||||||
|
const RangePicker = defineAsyncComponent(() =>
|
||||||
|
import('ant-design-vue/es/date-picker').then((res) => res.RangePicker),
|
||||||
|
);
|
||||||
|
const Rate = defineAsyncComponent(() => import('ant-design-vue/es/rate'));
|
||||||
|
const Select = defineAsyncComponent(() => import('ant-design-vue/es/select'));
|
||||||
|
const Space = defineAsyncComponent(() => import('ant-design-vue/es/space'));
|
||||||
|
const Switch = defineAsyncComponent(() => import('ant-design-vue/es/switch'));
|
||||||
|
const Textarea = defineAsyncComponent(() =>
|
||||||
|
import('ant-design-vue/es/input').then((res) => res.Textarea),
|
||||||
|
);
|
||||||
|
const TimePicker = defineAsyncComponent(
|
||||||
|
() => import('ant-design-vue/es/time-picker'),
|
||||||
|
);
|
||||||
|
const TreeSelect = defineAsyncComponent(
|
||||||
|
() => import('ant-design-vue/es/tree-select'),
|
||||||
|
);
|
||||||
|
const Upload = defineAsyncComponent(() => import('ant-design-vue/es/upload'));
|
||||||
|
|
||||||
const withDefaultPlaceholder = <T extends Component>(
|
const withDefaultPlaceholder = <T extends Component>(
|
||||||
component: T,
|
component: T,
|
||||||
|
|||||||
@@ -1,14 +1,12 @@
|
|||||||
import { createApp, watchEffect } from 'vue';
|
import { createApp, watchEffect } from 'vue';
|
||||||
|
|
||||||
import { registerAccessDirective } from '@vben/access';
|
import { registerAccessDirective } from '@vben/access';
|
||||||
import { initTippy, registerLoadingDirective } from '@vben/common-ui';
|
import { registerLoadingDirective } from '@vben/common-ui';
|
||||||
import { MotionPlugin } from '@vben/plugins/motion';
|
|
||||||
import { preferences } from '@vben/preferences';
|
import { preferences } from '@vben/preferences';
|
||||||
import { initStores } from '@vben/stores';
|
import { initStores } from '@vben/stores';
|
||||||
import '@vben/styles';
|
import '@vben/styles';
|
||||||
import '@vben/styles/antd';
|
import '@vben/styles/antd';
|
||||||
|
|
||||||
import { VueQueryPlugin } from '@tanstack/vue-query';
|
|
||||||
import { useTitle } from '@vueuse/core';
|
import { useTitle } from '@vueuse/core';
|
||||||
|
|
||||||
import { $t, setupI18n } from '#/locales';
|
import { $t, setupI18n } from '#/locales';
|
||||||
@@ -21,13 +19,13 @@ async function bootstrap(namespace: string) {
|
|||||||
// 初始化组件适配器
|
// 初始化组件适配器
|
||||||
await initComponentAdapter();
|
await initComponentAdapter();
|
||||||
|
|
||||||
// // 设置弹窗的默认配置
|
// 设置弹窗的默认配置
|
||||||
// setDefaultModalProps({
|
// setDefaultModalProps({
|
||||||
// fullscreenButton: false,
|
// fullscreenButton: false,
|
||||||
// });
|
// });
|
||||||
// // 设置抽屉的默认配置
|
// 设置抽屉的默认配置
|
||||||
// setDefaultDrawerProps({
|
// setDefaultDrawerProps({
|
||||||
// // zIndex: 1020,
|
// zIndex: 1020,
|
||||||
// });
|
// });
|
||||||
|
|
||||||
const app = createApp(App);
|
const app = createApp(App);
|
||||||
@@ -48,15 +46,18 @@ async function bootstrap(namespace: string) {
|
|||||||
registerAccessDirective(app);
|
registerAccessDirective(app);
|
||||||
|
|
||||||
// 初始化 tippy
|
// 初始化 tippy
|
||||||
|
const { initTippy } = await import('@vben/common-ui/es/tippy');
|
||||||
initTippy(app);
|
initTippy(app);
|
||||||
|
|
||||||
// 配置路由及路由守卫
|
// 配置路由及路由守卫
|
||||||
app.use(router);
|
app.use(router);
|
||||||
|
|
||||||
// 配置@tanstack/vue-query
|
// 配置@tanstack/vue-query
|
||||||
|
const { VueQueryPlugin } = await import('@tanstack/vue-query');
|
||||||
app.use(VueQueryPlugin);
|
app.use(VueQueryPlugin);
|
||||||
|
|
||||||
// 配置Motion插件
|
// 配置Motion插件
|
||||||
|
const { MotionPlugin } = await import('@vben/plugins/motion');
|
||||||
app.use(MotionPlugin);
|
app.use(MotionPlugin);
|
||||||
|
|
||||||
// 动态更新标题
|
// 动态更新标题
|
||||||
|
|||||||
@@ -2,10 +2,10 @@ import type { RouteRecordRaw } from 'vue-router';
|
|||||||
|
|
||||||
import { DEFAULT_HOME_PATH, LOGIN_PATH } from '@vben/constants';
|
import { DEFAULT_HOME_PATH, LOGIN_PATH } from '@vben/constants';
|
||||||
|
|
||||||
import { AuthPageLayout, BasicLayout } from '#/layouts';
|
|
||||||
import { $t } from '#/locales';
|
import { $t } from '#/locales';
|
||||||
import Login from '#/views/_core/authentication/login.vue';
|
|
||||||
|
|
||||||
|
const BasicLayout = () => import('#/layouts/basic.vue');
|
||||||
|
const AuthPageLayout = () => import('#/layouts/auth.vue');
|
||||||
/** 全局404页面 */
|
/** 全局404页面 */
|
||||||
const fallbackNotFoundRoute: RouteRecordRaw = {
|
const fallbackNotFoundRoute: RouteRecordRaw = {
|
||||||
component: () => import('#/views/_core/fallback/not-found.vue'),
|
component: () => import('#/views/_core/fallback/not-found.vue'),
|
||||||
@@ -50,7 +50,7 @@ const coreRoutes: RouteRecordRaw[] = [
|
|||||||
{
|
{
|
||||||
name: 'Login',
|
name: 'Login',
|
||||||
path: 'login',
|
path: 'login',
|
||||||
component: Login,
|
component: () => import('#/views/_core/authentication/login.vue'),
|
||||||
meta: {
|
meta: {
|
||||||
title: $t('page.auth.login'),
|
title: $t('page.auth.login'),
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -21,22 +21,22 @@ catalog:
|
|||||||
'@commitlint/cli': ^19.8.0
|
'@commitlint/cli': ^19.8.0
|
||||||
'@commitlint/config-conventional': ^19.8.0
|
'@commitlint/config-conventional': ^19.8.0
|
||||||
'@ctrl/tinycolor': ^4.1.0
|
'@ctrl/tinycolor': ^4.1.0
|
||||||
'@eslint/js': ^9.23.0
|
'@eslint/js': ^9.24.0
|
||||||
'@faker-js/faker': ^9.6.0
|
'@faker-js/faker': ^9.6.0
|
||||||
'@iconify/json': ^2.2.323
|
'@iconify/json': ^2.2.324
|
||||||
'@iconify/tailwind': ^1.2.0
|
'@iconify/tailwind': ^1.2.0
|
||||||
'@iconify/vue': ^4.3.0
|
'@iconify/vue': ^4.3.0
|
||||||
'@intlify/core-base': ^11.1.2
|
'@intlify/core-base': ^11.1.3
|
||||||
'@intlify/unplugin-vue-i18n': ^6.0.5
|
'@intlify/unplugin-vue-i18n': ^6.0.5
|
||||||
'@jspm/generator': ^2.5.1
|
'@jspm/generator': ^2.5.1
|
||||||
'@manypkg/get-packages': ^2.2.2
|
'@manypkg/get-packages': ^2.2.2
|
||||||
'@nolebase/vitepress-plugin-git-changelog': ^2.15.1
|
'@nolebase/vitepress-plugin-git-changelog': ^2.16.0
|
||||||
'@playwright/test': ^1.51.1
|
'@playwright/test': ^1.51.1
|
||||||
'@pnpm/workspace.read-manifest': ^1000.1.2
|
'@pnpm/workspace.read-manifest': ^1000.1.3
|
||||||
'@stylistic/stylelint-plugin': ^3.1.2
|
'@stylistic/stylelint-plugin': ^3.1.2
|
||||||
'@tailwindcss/nesting': 0.0.0-insiders.565cd3e
|
'@tailwindcss/nesting': 0.0.0-insiders.565cd3e
|
||||||
'@tailwindcss/typography': ^0.5.16
|
'@tailwindcss/typography': ^0.5.16
|
||||||
'@tanstack/vue-query': ^5.71.1
|
'@tanstack/vue-query': ^5.72.0
|
||||||
'@tanstack/vue-store': ^0.7.0
|
'@tanstack/vue-store': ^0.7.0
|
||||||
'@types/archiver': ^6.0.3
|
'@types/archiver': ^6.0.3
|
||||||
'@types/eslint': ^9.6.1
|
'@types/eslint': ^9.6.1
|
||||||
@@ -46,14 +46,14 @@ catalog:
|
|||||||
'@types/lodash.get': ^4.4.9
|
'@types/lodash.get': ^4.4.9
|
||||||
'@types/lodash.isequal': ^4.5.8
|
'@types/lodash.isequal': ^4.5.8
|
||||||
'@types/lodash.set': ^4.3.9
|
'@types/lodash.set': ^4.3.9
|
||||||
'@types/node': ^22.13.17
|
'@types/node': ^22.14.0
|
||||||
'@types/nprogress': ^0.2.3
|
'@types/nprogress': ^0.2.3
|
||||||
'@types/postcss-import': ^14.0.3
|
'@types/postcss-import': ^14.0.3
|
||||||
'@types/qrcode': ^1.5.5
|
'@types/qrcode': ^1.5.5
|
||||||
'@types/qs': ^6.9.18
|
'@types/qs': ^6.9.18
|
||||||
'@types/sortablejs': ^1.15.8
|
'@types/sortablejs': ^1.15.8
|
||||||
'@typescript-eslint/eslint-plugin': ^8.29.0
|
'@typescript-eslint/eslint-plugin': ^8.29.1
|
||||||
'@typescript-eslint/parser': ^8.29.0
|
'@typescript-eslint/parser': ^8.29.1
|
||||||
'@vee-validate/zod': ^4.15.0
|
'@vee-validate/zod': ^4.15.0
|
||||||
'@vite-pwa/vitepress': ^0.5.4
|
'@vite-pwa/vitepress': ^0.5.4
|
||||||
'@vitejs/plugin-vue': ^5.2.3
|
'@vitejs/plugin-vue': ^5.2.3
|
||||||
@@ -88,17 +88,17 @@ catalog:
|
|||||||
dotenv: ^16.4.7
|
dotenv: ^16.4.7
|
||||||
echarts: ^5.6.0
|
echarts: ^5.6.0
|
||||||
element-plus: ^2.9.7
|
element-plus: ^2.9.7
|
||||||
eslint: ^9.23.0
|
eslint: ^9.24.0
|
||||||
eslint-config-turbo: ^2.4.4
|
eslint-config-turbo: ^2.5.0
|
||||||
eslint-plugin-command: ^0.2.7
|
eslint-plugin-command: ^0.2.7
|
||||||
eslint-plugin-eslint-comments: ^3.2.0
|
eslint-plugin-eslint-comments: ^3.2.0
|
||||||
eslint-plugin-import-x: ^4.10.0
|
eslint-plugin-import-x: ^4.10.2
|
||||||
eslint-plugin-jsdoc: ^50.6.9
|
eslint-plugin-jsdoc: ^50.6.9
|
||||||
eslint-plugin-jsonc: ^2.20.0
|
eslint-plugin-jsonc: ^2.20.0
|
||||||
eslint-plugin-n: ^17.17.0
|
eslint-plugin-n: ^17.17.0
|
||||||
eslint-plugin-no-only-tests: ^3.3.0
|
eslint-plugin-no-only-tests: ^3.3.0
|
||||||
eslint-plugin-perfectionist: ^4.11.0
|
eslint-plugin-perfectionist: ^4.11.0
|
||||||
eslint-plugin-prettier: ^5.2.5
|
eslint-plugin-prettier: ^5.2.6
|
||||||
eslint-plugin-regexp: ^2.7.0
|
eslint-plugin-regexp: ^2.7.0
|
||||||
eslint-plugin-unicorn: ^56.0.1
|
eslint-plugin-unicorn: ^56.0.1
|
||||||
eslint-plugin-unused-imports: ^4.1.4
|
eslint-plugin-unused-imports: ^4.1.4
|
||||||
@@ -146,9 +146,9 @@ catalog:
|
|||||||
rimraf: ^6.0.1
|
rimraf: ^6.0.1
|
||||||
rollup: ^4.39.0
|
rollup: ^4.39.0
|
||||||
rollup-plugin-visualizer: ^5.14.0
|
rollup-plugin-visualizer: ^5.14.0
|
||||||
sass: ^1.86.1
|
sass: ^1.86.3
|
||||||
sortablejs: ^1.15.6
|
sortablejs: ^1.15.6
|
||||||
stylelint: ^16.17.0
|
stylelint: ^16.18.0
|
||||||
stylelint-config-recess-order: ^5.1.1
|
stylelint-config-recess-order: ^5.1.1
|
||||||
stylelint-config-recommended: ^14.0.1
|
stylelint-config-recommended: ^14.0.1
|
||||||
stylelint-config-recommended-scss: ^14.1.0
|
stylelint-config-recommended-scss: ^14.1.0
|
||||||
@@ -162,12 +162,12 @@ catalog:
|
|||||||
tailwindcss-animate: ^1.0.7
|
tailwindcss-animate: ^1.0.7
|
||||||
theme-colors: ^0.1.0
|
theme-colors: ^0.1.0
|
||||||
tippy.js: ^6.2.5
|
tippy.js: ^6.2.5
|
||||||
turbo: ^2.4.4
|
turbo: ^2.5.0
|
||||||
typescript: ^5.8.2
|
typescript: ^5.8.3
|
||||||
unbuild: ^3.5.0
|
unbuild: ^3.5.0
|
||||||
unplugin-element-plus: ^0.9.1
|
unplugin-element-plus: ^0.9.1
|
||||||
vee-validate: ^4.15.0
|
vee-validate: ^4.15.0
|
||||||
vite: ^6.2.4
|
vite: ^6.2.5
|
||||||
vite-plugin-compression: ^0.5.1
|
vite-plugin-compression: ^0.5.1
|
||||||
vite-plugin-dts: ^4.5.3
|
vite-plugin-dts: ^4.5.3
|
||||||
vite-plugin-html: ^3.2.2
|
vite-plugin-html: ^3.2.2
|
||||||
@@ -179,12 +179,12 @@ catalog:
|
|||||||
vitest: ^2.1.9
|
vitest: ^2.1.9
|
||||||
vue: ^3.5.13
|
vue: ^3.5.13
|
||||||
vue-eslint-parser: ^9.4.3
|
vue-eslint-parser: ^9.4.3
|
||||||
vue-i18n: ^11.1.2
|
vue-i18n: ^11.1.3
|
||||||
vue-json-viewer: ^3.0.4
|
vue-json-viewer: ^3.0.4
|
||||||
vue-router: ^4.5.0
|
vue-router: ^4.5.0
|
||||||
vue-tippy: ^6.7.0
|
vue-tippy: ^6.7.0
|
||||||
vue-tsc: 2.1.10
|
vue-tsc: 2.1.10
|
||||||
vxe-pc-ui: ^4.5.11
|
vxe-pc-ui: ^4.5.14
|
||||||
vxe-table: ^4.12.5
|
vxe-table: ^4.12.5
|
||||||
watermark-js-plus: ^1.5.8
|
watermark-js-plus: ^1.5.8
|
||||||
zod: ^3.24.2
|
zod: ^3.24.2
|
||||||
|
|||||||
Reference in New Issue
Block a user