66 Commits
1.1.2 ... 1.1.3

Author SHA1 Message Date
dap
b29b41fc73 chore: version 2024-12-11 11:35:16 +08:00
dap
a7d9697397 chore: 移除调试代码 2024-12-11 11:22:13 +08:00
dap
7ec3cfb3fd refactor: 判断vxe-table的复选框是否选中 2024-12-11 11:09:16 +08:00
dap
3014b62086 chore: 标记为非代理对象 消除warning 2024-12-10 19:39:14 +08:00
dap
e0b4bf0956 fix: 上下文导致渲染不正常 2024-12-09 15:38:58 +08:00
dap
686e09a1f8 fix: h导致的页面报错(原因未知) 2024-12-09 15:24:12 +08:00
dap
4778ead7c2 chore: gitee地址 2024-12-09 09:57:27 +08:00
dap
131310efef Merge branch 'main' of https://gitee.com/dapppp/ruoyi-plus-vben5 2024-12-09 08:09:21 +08:00
dap
a13f808aa2 chore: 间距 2024-12-09 08:09:04 +08:00
dap
41b415362c chore: markdown直接v-model改变值 保持与编辑器的同步 2024-12-08 19:18:09 +08:00
dap
1178f7a0bf chore: 多余的文件 2024-12-08 19:05:03 +08:00
dap
359d837dee Merge branch 'main' of https://github.com/vbenjs/vue-vben-admin 2024-12-08 11:04:04 +08:00
Netfan
373766691f chore: remove useless fixedHeader prop for Page (#5069) 2024-12-07 23:26:47 +08:00
Netfan
bac0275624 chore: page prop type check (#5067) 2024-12-07 12:15:51 +08:00
Netfan
0fc0f13064 fix: layout overflow style (#5066) 2024-12-07 12:05:03 +08:00
Netfan
b75a8e6a2b fix: form setValues not support dayjs and Date value (#5064)
* fix: setFormValues not support  `dayjs object` value

* fix: setFormValues not support `Date` value

* chore: remove console log
2024-12-07 11:09:55 +08:00
Netfan
68ab73bdb5 fix: range picker props fixed for element-plus (#5042) 2024-12-07 11:09:33 +08:00
Netfan
4c1fc4a11e fix: validate message not display, fix #5034 (#5038) 2024-12-07 11:02:59 +08:00
Netfan
03f166f8a4 fix: form prop handleValuesChange no effect (#5060) 2024-12-07 11:02:14 +08:00
Netfan
d42daf9ce0 fix: modal radius not follow preferences (#5063) 2024-12-07 11:00:53 +08:00
Netfan
d1862fba27 fix: replace input component in IconPicker (#5047)
* fix: replace input component in `IconPicker`

* chore: fixed IconPicker demo
2024-12-06 13:46:52 +08:00
Netfan
f0db3d6b79 chore: codeowners update (#5048) 2024-12-06 13:46:32 +08:00
dap
0efbf57152 chore: 搜索忽略tinymce 2024-12-06 10:45:46 +08:00
dap
1d842b1b87 fix: break-normal更改位置 2024-12-06 09:32:27 +08:00
dap
48ca7aca8c fix: 微服务版本 区间查询和中文搜索条件一起使用 无法正确查询 2024-12-06 09:27:11 +08:00
dap
827ef2e403 fix: 默认word-break: break-word;会导致json预览样式异常 2024-12-05 16:08:56 +08:00
dap
4df62b563e fix: 在description组件中json预览样式异常 2024-12-05 16:03:35 +08:00
Netfan
21d37a1be0 fix: dialog and drawer footer gap in small screen (#5025) 2024-12-05 11:24:09 +08:00
huangxiaomin
fe236ea929 feat: add submitOnChange props to vben form (#5032) 2024-12-05 11:23:21 +08:00
huangxiaomin
05b4b61c6e fix: select Long option style problem (#5030) 2024-12-05 11:22:35 +08:00
dap
e2b91f88ab fix: 官方Commit17c7ce8造成的页面漂移 2024-12-05 08:29:46 +08:00
dap
eeba7b50f9 Merge branch 'main' of https://github.com/vbenjs/vue-vben-admin 2024-12-05 08:04:03 +08:00
vben
7ab00250bf chore: release 5.5.0 2024-12-04 22:57:27 +08:00
Vben
9896a67c21 feat: add api-select component (#5024) 2024-12-04 22:56:29 +08:00
Netfan
db38ef522f fix: Page header class in fixed mode (#5023) 2024-12-04 22:56:06 +08:00
Netfan
845f2a2abd fix: header left padding fixed (#5007) 2024-12-04 21:43:54 +08:00
Netfan
9b73792dc9 fix: extra menu title follow locale change (#5006) 2024-12-04 21:43:29 +08:00
Netfan
fccbe44cf7 feat: v-loading support for element plus (#5008) 2024-12-04 21:42:48 +08:00
Netfan
e23486dbc6 feat: form component IconPicker (#5005) 2024-12-04 21:42:21 +08:00
Netfan
935df713f3 fix: app config support .env.local (#5012) 2024-12-04 21:41:22 +08:00
Netfan
17c7ce8f21 feat: improve page component (#5013)
* feat: `page` component support fixed header

* docs: `page`  component documentation

* docs: Improve `props` types of `page`

* docs: improve `fixedHeader` description of `page`

* fix: `page` header border color with fixedHeader

* feat: add `headerClass` for `Page`
2024-12-04 21:40:41 +08:00
vben
24b9aa44d2 chore: Revert "fix: form 表单不支持field.xxx.xx格式的defaultValue配置 (#4965)"
This reverts commliit 12f216c0e7.
2024-12-02 00:47:06 +08:00
Vben
014e6d38a0 chore: update deps (#4993) 2024-12-01 21:53:52 +08:00
leizhiyou
12f216c0e7 fix: form 表单不支持field.xxx.xx格式的defaultValue配置 (#4965)
* fix: form 表单不支持field.xxx.xx格式的defaultValue配置

* chore: 修复代码规范问题
2024-12-01 21:48:54 +08:00
Netfan
ae3f7cb909 fix: mixed menu layout in full content mode (#4990) 2024-12-01 21:37:36 +08:00
Netfan
32117b73aa docs: add form slots docs (#4992) 2024-12-01 21:37:19 +08:00
dap
e1414b2b10 chore: i18n 2024-12-01 15:36:44 +08:00
dap
839e2e4321 Merge branch 'main' of https://github.com/vbenjs/vue-vben-admin 2024-11-30 20:03:53 +08:00
huangfe1
e8992a1d16 chore: update modal.vue (#4987)
loading时候 子组件禁用点击事件

Co-authored-by: Vben <ann.vben@gmail.com>
2024-11-30 11:18:22 +08:00
Svend
3c4af23edf fix: 修复 Form Api 根据字段名移除表单项,字段取反了的问题 (#4971) 2024-11-30 10:58:17 +08:00
LinaBell
e3a93970f4 fix: when VxeTable toolbarConfig.refresh is enabled, it will carry incorrect parameters (#4980) 2024-11-30 10:57:23 +08:00
richex-cn
7b9866158b chore: update deprecated document link in .github/ISSUE_TEMPLATE (#4986) 2024-11-30 10:56:42 +08:00
Netfan
3fb286b552 fix: element hover style in dark theme (#4983) 2024-11-30 10:55:29 +08:00
dap
7461693aa7 chore: 需要排除Button组件 全局已经默认导入了 2024-11-29 16:42:34 +08:00
dap
3afb8395a7 chore: 改为hideLabel 2024-11-29 16:03:16 +08:00
dap
0950022f09 chore: w-full改为在commonConfig配置 2024-11-29 15:55:16 +08:00
dap
227cf1e72b chore: 角色管理 优化Drawer布局 2024-11-29 15:45:42 +08:00
dap
a3e98aedf9 chore: 去除用于调试的menuList打印 2024-11-29 15:22:24 +08:00
dap
ac3ec4746f fix: 节点树在节点独立情况下的控制台warning 2024-11-29 15:21:03 +08:00
dap
36939f36ee fix: 节点树在编辑 & 空数组(不勾选)情况 勾选节点会造成watch延迟触发 导致会带上父节点id造成id重 2024-11-29 15:01:26 +08:00
dap
07325d4c5e chore: vxe-table的头部颜色和antd保持一致 2024-11-29 11:52:14 +08:00
Netfan
253abc5ef1 chore: tailwind css icon example (#4969) 2024-11-28 15:10:14 +08:00
Jeff
5f55799572 fix: button-control page mistake (#4963)
* fix: button-control page mistake

按钮控制展示逻辑错误

* fix: button-control.vue button text
2024-11-28 10:01:26 +08:00
vince
54a9ff088f feat: upgrade vite version to 6.0.0 (#4961)
* chore: upgrade vite version to 6.0.0

* chore: update lock
2024-11-27 15:52:25 +08:00
Netfan
73502677ff feat: add placement for Drawer (#4958) 2024-11-27 11:29:25 +08:00
Netfan
dedba18553 feat: add confirmDisabled for Dialog (#4959) 2024-11-27 11:28:49 +08:00
136 changed files with 1112 additions and 1387 deletions

18
.github/CODEOWNERS vendored
View File

@@ -1,14 +1,14 @@
# default onwer
* anncwb@126.com vince292007@gmail.com
* anncwb@126.com vince292007@gmail.com netfan@foxmail.com
# vben core onwer
/.github/ anncwb@126.com vince292007@gmail.com
/.vscode/ anncwb@126.com vince292007@gmail.com
/packages/ anncwb@126.com vince292007@gmail.com
/packages/@core/ anncwb@126.com vince292007@gmail.com
/internal/ anncwb@126.com vince292007@gmail.com
/scripts/ anncwb@126.com vince292007@gmail.com
/.github/ anncwb@126.com vince292007@gmail.com netfan@foxmail.com
/.vscode/ anncwb@126.com vince292007@gmail.com netfan@foxmail.com
/packages/ anncwb@126.com vince292007@gmail.com netfan@foxmail.com
/packages/@core/ anncwb@126.com vince292007@gmail.com netfan@foxmail.com
/internal/ anncwb@126.com vince292007@gmail.com netfan@foxmail.com
/scripts/ anncwb@126.com vince292007@gmail.com netfan@foxmail.com
# vben team onwer
apps/ anncwb@126.com vince292007@gmail.com @vbenjs/team-v5
docs/ anncwb@126.com vince292007@gmail.com @vbenjs/team-v5
apps/ anncwb@126.com vince292007@gmail.com netfan@foxmail.com @vbenjs/team-v5
docs/ anncwb@126.com vince292007@gmail.com netfan@foxmail.com @vbenjs/team-v5

View File

@@ -62,7 +62,7 @@ body:
description: Before submitting the issue, please make sure you do the following
# description: By submitting this issue, you agree to follow our [Code of Conduct](https://example.com).
options:
- label: Read the [docs](https://anncwb.github.io/vue-vben-admin-doc/)
- label: Read the [docs](https://doc.vben.pro/)
required: true
- label: Ensure the code is up to date. (Some issues have been fixed in the latest version)
required: true

View File

@@ -62,7 +62,7 @@ body:
label: Validations
description: Before submitting the issue, please make sure you do the following
options:
- label: Read the [docs](https://anncwb.github.io/vue-vben-admin-doc/)
- label: Read the [docs](https://doc.vben.pro/)
required: true
- label: Ensure the code is up to date. (Some issues have been fixed in the latest version)
required: true

View File

@@ -99,7 +99,8 @@
"**/.stylelintcache": true,
"**/.DS_Store": true,
"**/vite.config.mts.*": true,
"**/tea.yaml": true
"**/tea.yaml": true,
"**/public/tinymce/**": true
},
"files.watcherExclude": {
"**/.git/objects/**": true,

View File

@@ -1,3 +1,24 @@
# 1.1.3
**REFACTOR**
- 重构: 判断vxe-table的复选框是否选中
**Bug Fixes**
- 节点树在编辑 & 空数组(不勾选)情况 勾选节点会造成watch延迟触发 导致会带上父节点id造成id重复
- 节点树在节点独立情况下的控制台warning: Invalid prop: type check failed for prop "value". Expected Array, got Object
**Others**
- 角色管理 优化Drawer布局
- unplugin-vue-components插件(默认未开启) 需要排除Button组件 全局已经默认导入了
**BUG FIXES**
- 操作日志详情 在description组件中json预览样式异常
- 微服务版本 区间查询和中文搜索条件一起使用 无法正确查询
# 1.1.2
**Features**

View File

@@ -1,9 +0,0 @@
{
"editor.tabSize": 2,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit",
"source.fixAll.stylelint": "explicit"
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "@vben/web-antd",
"version": "1.1.2",
"version": "1.1.3",
"homepage": "https://vben.pro",
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
"repository": {

View File

@@ -8,7 +8,7 @@ import type { BaseFormComponentType } from '@vben/common-ui';
import type { Component, SetupContext } from 'vue';
import { h } from 'vue';
import { globalShareState } from '@vben/common-ui';
import { ApiSelect, globalShareState, IconPicker } from '@vben/common-ui';
import { $t } from '@vben/locales';
import {
@@ -51,6 +51,7 @@ const withDefaultPlaceholder = <T extends Component>(
// 这里需要自行根据业务组件库进行适配,需要用到的组件都需要在这里类型说明
export type ComponentType =
| 'ApiSelect'
| 'AutoComplete'
| 'Checkbox'
| 'CheckboxGroup'
@@ -58,6 +59,7 @@ export type ComponentType =
| 'DefaultButton'
| 'Divider'
| 'FileUpload'
| 'IconPicker'
| 'ImageUpload'
| 'Input'
| 'InputNumber'
@@ -83,7 +85,20 @@ async function initComponentAdapter() {
// 如果你的组件体积比较大,可以使用异步加载
// Button: () =>
// import('xxx').then((res) => res.Button),
ApiSelect: (props, { attrs, slots }) => {
return h(
ApiSelect,
{
...props,
...attrs,
component: Select,
loadingSlot: 'suffixIcon',
visibleEvent: 'onDropdownVisibleChange',
modelField: 'value',
},
slots,
);
},
AutoComplete,
Checkbox,
CheckboxGroup,
@@ -93,6 +108,13 @@ async function initComponentAdapter() {
return h(Button, { ...props, attrs, type: 'default' }, slots);
},
Divider,
IconPicker: (props, { attrs, slots }) => {
return h(
IconPicker,
{ iconSlot: 'addonAfter', inputComponent: Input, ...props, ...attrs },
slots,
);
},
Input: withDefaultPlaceholder(Input, 'input'),
InputNumber: withDefaultPlaceholder(InputNumber, 'input'),
InputPassword: withDefaultPlaceholder(InputPassword, 'input'),

View File

@@ -107,6 +107,7 @@ export type * from '@vben/plugins/vxe-table';
/**
* 通用的表格复选框是否选中事件
* @deprecated 使用vxeCheckboxChecked代替
* @param checked 是否选中
* @returns function
*/
@@ -118,3 +119,14 @@ export function tableCheckboxEvent(checked: Ref<boolean>) {
};
return event;
}
/**
* 判断vxe-table的复选框是否选中
* @param tableApi api
* @returns boolean
*/
export function vxeCheckboxChecked(
tableApi: ReturnType<typeof useVbenVxeGrid>[1],
) {
return tableApi?.grid?.getCheckboxRecords?.()?.length > 0;
}

View File

@@ -10,6 +10,7 @@ import {
authenticateResponseInterceptor,
errorMessageResponseInterceptor,
RequestClient,
stringify,
} from '@vben/request';
import { useAccessStore } from '@vben/stores';
@@ -94,6 +95,23 @@ function createRequestClient(baseURL: string) {
// 添加全局clientId
config.headers.clientId = clientId;
/**
* 格式化get/delete参数
* 如果包含自定义的paramsSerializer则不走此逻辑
*/
if (
['DELETE', 'GET'].includes(config.method?.toUpperCase() || '') &&
config.params &&
!config.paramsSerializer
) {
/**
* 1. 格式化参数 微服务在传递区间时间选择(后端的params Map类型参数)需要格式化key 否则接收不到
* 2. 数组参数需要格式化 后端才能正常接收 会变成arr=1&arr=2&arr=3的格式来接收
*/
config.paramsSerializer = (params) =>
stringify(params, { arrayFormat: 'repeat' });
}
const { encrypt } = config;
// 全局开启请求加密功能 && 该请求开启 && 是post/put请求
if (

View File

@@ -8,6 +8,7 @@ import { computed, nextTick, onMounted, type PropType, ref, watch } from 'vue';
import { findGroupParentIds, treeToList } from '@vben/utils';
import { Checkbox, Tree } from 'ant-design-vue';
import { uniq } from 'lodash-es';
/** 需要禁止透传 */
defineOptions({ inheritAttrs: false });
@@ -73,6 +74,8 @@ const checkedRealKeys = ref<(number | string)[]>([]);
/**
* 取第一次的menuTree id 设置到checkedMenuKeys
* 主要为了解决没有任何修改 直接点击保存的情况
*
* length为0情况(即新增时候没有勾选节点) 勾选这里会延迟触发 节点会拼接上父节点 导致ID重复
*/
const stop = watch([checkedKeys, () => props.treeData], () => {
if (
@@ -86,7 +89,10 @@ const stop = watch([checkedKeys, () => props.treeData], () => {
checkedKeys.value as any,
{ id: props.fieldNames.key },
);
checkedRealKeys.value = [...parentIds, ...checkedKeys.value];
/**
* uniq 解决上面的id重复问题
*/
checkedRealKeys.value = uniq([...parentIds, ...checkedKeys.value]);
stop();
}
if (!props.checkStrictly && checkedKeys.value.length > 0) {
@@ -98,19 +104,21 @@ const stop = watch([checkedKeys, () => props.treeData], () => {
/**
*
* @param checkedKeys 已经选中的子节点的ID
* @param checkedStateKeys 已经选中的子节点的ID
* @param info info.halfCheckedKeys为父节点的ID
*/
type CheckedState<T = number | string> =
| { checked: T[]; halfChecked: T[] }
| T[];
function handleChecked(checkedKeys: CheckedState, info: CheckInfo) {
function handleChecked(checkedStateKeys: CheckedState, info: CheckInfo) {
// 数组的话为节点关联
if (Array.isArray(checkedKeys)) {
if (Array.isArray(checkedStateKeys)) {
const halfCheckedKeys: number[] = (info.halfCheckedKeys || []) as number[];
checkedRealKeys.value = [...halfCheckedKeys, ...checkedKeys];
checkedRealKeys.value = [...halfCheckedKeys, ...checkedStateKeys];
} else {
checkedRealKeys.value = [...checkedKeys.checked];
checkedRealKeys.value = [...checkedStateKeys.checked];
// fix: Invalid prop: type check failed for prop "value". Expected Array, got Object
checkedKeys.value = [...checkedStateKeys.checked];
}
}
@@ -137,9 +145,10 @@ function handleCheckStrictlyChange(e: CheckboxChangeEvent) {
/**
* 暴露方法来获取用于提交的全部节点
* uniq去重(保险方案)
*/
defineExpose({
getCheckedKeys: () => checkedRealKeys.value,
getCheckedKeys: () => uniq(checkedRealKeys.value),
});
onMounted(async () => {

View File

@@ -1,5 +1,5 @@
<script lang="ts" setup>
import { computed, onMounted, watch } from 'vue';
import { computed, h, onMounted, watch } from 'vue';
import { useRouter } from 'vue-router';
import { AuthenticationLoginExpiredModal } from '@vben/common-ui';
@@ -8,6 +8,7 @@ import { useWatermark } from '@vben/hooks';
import {
BookOpenText,
CircleHelp,
GiteeIcon,
GitHubOutlined,
UserOutlined,
} from '@vben/icons';
@@ -55,6 +56,15 @@ const menus = computed(() => {
icon: UserOutlined,
text: $t('ui.widgets.profile'),
},
{
handler: () => {
openWindow('https://gitee.com/dapppp/ruoyi-plus-vben5', {
target: '_blank',
});
},
icon: () => h(GiteeIcon, { class: 'text-red-800' }),
text: 'Gitee项目地址',
},
{
handler: () => {
openWindow(VBEN_GITHUB_URL, {
@@ -62,7 +72,7 @@ const menus = computed(() => {
});
},
icon: GitHubOutlined,
text: 'GitHub',
text: 'Vben官方地址',
},
{
handler: () => {

View File

@@ -207,7 +207,7 @@ async function generateAccess(options: GenerateMenuAndRoutesOptions) {
const vbenMenuList = backMenuToVbenMenu(backMenuList);
// 特别注意 这里要深拷贝
const menuList = [...cloneDeep(localMenuList), ...vbenMenuList];
console.log('menuList', menuList);
// console.log('menuList', menuList);
return menuList;
},
// 可以指定没有权限跳转403页面

View File

@@ -68,7 +68,7 @@ export function renderJsonPreview(json: any) {
return <span>{json}</span>;
}
if (typeof json === 'object') {
return <JsonPreview data={json} />;
return <JsonPreview class="break-normal" data={json} />;
}
try {
const obj = JSON.parse(json);
@@ -76,7 +76,7 @@ export function renderJsonPreview(json: any) {
if (typeof obj !== 'object') {
return <span>{obj}</span>;
}
return <JsonPreview data={obj} />;
return <JsonPreview class="break-normal" data={obj} />;
} catch {
return <span>{json}</span>;
}

View File

@@ -9,8 +9,8 @@ import { getVxePopupContainer } from '@vben/utils';
import { Modal, Popconfirm, Space } from 'ant-design-vue';
import {
tableCheckboxEvent,
useVbenVxeGrid,
vxeCheckboxChecked,
type VxeGridDefines,
type VxeGridProps,
} from '#/adapter/vxe-table';
@@ -77,18 +77,15 @@ const gridOptions: VxeGridProps = {
id: 'monitor-logininfo-index',
};
const checked = ref(false);
const canUnlock = ref(false);
const [BasicTable, tableApi] = useVbenVxeGrid({
formOptions,
gridOptions,
gridEvents: {
checkboxChange: (e: VxeGridDefines.CheckboxChangeEventParams) => {
const records = e.$table.getCheckboxRecords();
checked.value = records.length > 0;
const records = e.$grid.getCheckboxRecords();
canUnlock.value = records.length === 1 && records[0]?.status === '1';
},
checkboxAll: tableCheckboxEvent(checked),
},
});
@@ -125,7 +122,6 @@ function handleMultiDelete() {
onOk: async () => {
await loginInfoRemove(ids);
await tableApi.query();
checked.value = false;
},
});
}
@@ -138,7 +134,6 @@ async function handleUnlock() {
const { userName } = records[0];
await userUnlock(userName);
await tableApi.query();
checked.value = false;
canUnlock.value = false;
tableApi.grid.clearCheckboxRow();
}
@@ -173,7 +168,7 @@ function handleDownloadExcel() {
{{ $t('pages.common.export') }}
</a-button>
<a-button
:disabled="!checked"
:disabled="!vxeCheckboxChecked(tableApi)"
danger
type="primary"
v-access:code="['monitor:logininfor:remove']"

View File

@@ -157,12 +157,15 @@ export const descSchema: DescItem[] = [
field: 'method',
label: '方法',
},
/**
* 默认word-break: break-word;会导致json预览样式异常
*/
{
field: 'operParam',
label: '请求参数',
render(value) {
return (
<div class="max-h-[300px] overflow-y-auto">
<div class="max-h-[300px] w-full overflow-y-auto">
{renderJsonPreview(value)}
</div>
);
@@ -173,7 +176,7 @@ export const descSchema: DescItem[] = [
label: '响应参数',
render(value) {
return (
<div class="max-h-[300px] overflow-y-auto">
<div class="max-h-[300px] w-full overflow-y-auto">
{renderJsonPreview(value)}
</div>
);

View File

@@ -3,8 +3,6 @@ import type { Recordable } from '@vben/types';
import type { OperationLog } from '#/api/monitor/operlog/model';
import { ref } from 'vue';
import { Page, useVbenDrawer, type VbenFormProps } from '@vben/common-ui';
import { $t } from '@vben/locales';
@@ -12,8 +10,8 @@ import { Modal, Space } from 'ant-design-vue';
import { isEmpty } from 'lodash-es';
import {
tableCheckboxEvent,
useVbenVxeGrid,
vxeCheckboxChecked,
type VxeGridProps,
} from '#/adapter/vxe-table';
import {
@@ -88,7 +86,6 @@ const gridOptions: VxeGridProps<OperationLog> = {
id: 'monitor-operlog-index',
};
const checked = ref(false);
const [BasicTable, tableApi] = useVbenVxeGrid({
formOptions,
gridOptions,
@@ -96,8 +93,6 @@ const [BasicTable, tableApi] = useVbenVxeGrid({
sortChange: () => {
tableApi.query();
},
checkboxChange: tableCheckboxEvent(checked),
checkboxAll: tableCheckboxEvent(checked),
},
});
@@ -138,7 +133,6 @@ async function handleDelete() {
onOk: async () => {
await operLogDelete(ids);
await tableApi.query();
checked.value = false;
},
});
}
@@ -168,7 +162,7 @@ function handleDownloadExcel() {
{{ $t('pages.common.export') }}
</a-button>
<a-button
:disabled="!checked"
:disabled="!vxeCheckboxChecked(tableApi)"
danger
type="primary"
v-access:code="['monitor:operlog:remove']"

View File

@@ -21,11 +21,14 @@ const title = computed(() => {
const [BasicForm, formApi] = useVbenForm({
commonConfig: {
formItemClass: 'col-span-2',
componentProps: {
class: 'w-full',
},
},
layout: 'vertical',
schema: drawerSchema(),
showDefaultActions: false,
wrapperClass: 'grid-cols-2',
wrapperClass: 'grid-cols-2 gap-x-4',
});
function setupForm(update: boolean) {

View File

@@ -134,7 +134,6 @@ export const drawerSchema: FormSchemaGetter = () => [
{
component: 'Select',
componentProps: {
class: 'w-full',
getPopupContainer,
mode: 'multiple',
optionFilterProp: 'label',
@@ -148,7 +147,6 @@ export const drawerSchema: FormSchemaGetter = () => [
component: 'Select',
componentProps: {
allowClear: false,
class: 'w-full',
getPopupContainer,
options: getDictOptions(DictEnum.SYS_DEVICE_TYPE),
},

View File

@@ -1,8 +1,6 @@
<script setup lang="ts">
import type { Recordable } from '@vben/types';
import { ref } from 'vue';
import { useAccess } from '@vben/access';
import { Page, useVbenDrawer, type VbenFormProps } from '@vben/common-ui';
import { getVxePopupContainer } from '@vben/utils';
@@ -10,8 +8,8 @@ import { getVxePopupContainer } from '@vben/utils';
import { Modal, Popconfirm, Space } from 'ant-design-vue';
import {
tableCheckboxEvent,
useVbenVxeGrid,
vxeCheckboxChecked,
type VxeGridProps,
} from '#/adapter/vxe-table';
import {
@@ -70,14 +68,9 @@ const gridOptions: VxeGridProps = {
id: 'system-client-index',
};
const checked = ref(false);
const [BasicTable, tableApi] = useVbenVxeGrid({
formOptions,
gridOptions,
gridEvents: {
checkboxChange: tableCheckboxEvent(checked),
checkboxAll: tableCheckboxEvent(checked),
},
});
const [ClientDrawer, drawerApi] = useVbenDrawer({
@@ -109,7 +102,6 @@ function handleMultiDelete() {
onOk: async () => {
await clientRemove(ids);
await tableApi.query();
checked.value = false;
},
});
}
@@ -133,7 +125,7 @@ const { hasAccessByCodes } = useAccess();
{{ $t('pages.common.export') }}
</a-button>
<a-button
:disabled="!checked"
:disabled="!vxeCheckboxChecked(tableApi)"
danger
type="primary"
v-access:code="['system:client:remove']"

View File

@@ -1,16 +1,14 @@
<script setup lang="ts">
import type { Recordable } from '@vben/types';
import { ref } from 'vue';
import { Page, useVbenModal, type VbenFormProps } from '@vben/common-ui';
import { getVxePopupContainer } from '@vben/utils';
import { Modal, Popconfirm, Space } from 'ant-design-vue';
import {
tableCheckboxEvent,
useVbenVxeGrid,
vxeCheckboxChecked,
type VxeGridProps,
} from '#/adapter/vxe-table';
import {
@@ -72,14 +70,9 @@ const gridOptions: VxeGridProps = {
id: 'system-config-index',
};
const checked = ref(false);
const [BasicTable, tableApi] = useVbenVxeGrid({
formOptions,
gridOptions,
gridEvents: {
checkboxChange: tableCheckboxEvent(checked),
checkboxAll: tableCheckboxEvent(checked),
},
});
const [ConfigModal, modalApi] = useVbenModal({
connectedComponent: configModal,
@@ -110,7 +103,6 @@ function handleMultiDelete() {
onOk: async () => {
await configRemove(ids);
await tableApi.query();
checked.value = false;
},
});
}
@@ -140,7 +132,7 @@ async function handleRefreshCache() {
{{ $t('pages.common.export') }}
</a-button>
<a-button
:disabled="!checked"
:disabled="!vxeCheckboxChecked(tableApi)"
danger
type="primary"
v-access:code="['system:config:remove']"

View File

@@ -9,8 +9,8 @@ import { getVxePopupContainer } from '@vben/utils';
import { Modal, Popconfirm, Space } from 'ant-design-vue';
import {
tableCheckboxEvent,
useVbenVxeGrid,
vxeCheckboxChecked,
type VxeGridProps,
} from '#/adapter/vxe-table';
import {
@@ -73,14 +73,9 @@ const gridOptions: VxeGridProps = {
id: 'system-dict-data-index',
};
const checked = ref(false);
const [BasicTable, tableApi] = useVbenVxeGrid({
formOptions,
gridOptions,
gridEvents: {
checkboxChange: tableCheckboxEvent(checked),
checkboxAll: tableCheckboxEvent(checked),
},
});
const [DictDataDrawer, drawerApi] = useVbenDrawer({
@@ -115,7 +110,6 @@ function handleMultiDelete() {
onOk: async () => {
await dictDataRemove(ids);
await tableApi.query();
checked.value = false;
},
});
}
@@ -142,7 +136,7 @@ emitter.on('rowClick', async (value) => {
{{ $t('pages.common.export') }}
</a-button>
<a-button
:disabled="!checked"
:disabled="!vxeCheckboxChecked(tableApi)"
danger
type="primary"
v-access:code="['system:dict:remove']"

View File

@@ -9,8 +9,8 @@ import { getVxePopupContainer } from '@vben/utils';
import { Modal, Popconfirm, Space } from 'ant-design-vue';
import {
tableCheckboxEvent,
useVbenVxeGrid,
vxeCheckboxChecked,
type VxeGridProps,
} from '#/adapter/vxe-table';
import {
@@ -67,7 +67,6 @@ const gridOptions: VxeGridProps = {
id: 'system-dict-type-index',
};
const checked = ref(false);
const lastDictType = ref('');
const [BasicTable, tableApi] = useVbenVxeGrid({
@@ -82,8 +81,6 @@ const [BasicTable, tableApi] = useVbenVxeGrid({
emitter.emit('rowClick', row.dictType);
lastDictType.value = row.dictType;
},
checkboxChange: tableCheckboxEvent(checked),
checkboxAll: tableCheckboxEvent(checked),
},
});
const [DictTypeModal, modalApi] = useVbenModal({
@@ -115,7 +112,6 @@ function handleMultiDelete() {
onOk: async () => {
await dictTypeRemove(ids);
await tableApi.query();
checked.value = false;
},
});
}
@@ -152,7 +148,7 @@ function handleDownloadExcel() {
{{ $t('pages.common.export') }}
</a-button>
<a-button
:disabled="!checked"
:disabled="!vxeCheckboxChecked(tableApi)"
danger
type="primary"
v-access:code="['system:dict:remove']"

View File

@@ -1,16 +1,14 @@
<script setup lang="ts">
import type { Recordable } from '@vben/types';
import { ref } from 'vue';
import { Page, useVbenModal, type VbenFormProps } from '@vben/common-ui';
import { getVxePopupContainer } from '@vben/utils';
import { Modal, Popconfirm, Space } from 'ant-design-vue';
import {
tableCheckboxEvent,
useVbenVxeGrid,
vxeCheckboxChecked,
type VxeGridProps,
} from '#/adapter/vxe-table';
import { noticeList, noticeRemove } from '#/api/system/notice';
@@ -60,14 +58,9 @@ const gridOptions: VxeGridProps = {
id: 'system-notice-index',
};
const checked = ref(false);
const [BasicTable, tableApi] = useVbenVxeGrid({
formOptions,
gridOptions,
gridEvents: {
checkboxChange: tableCheckboxEvent(checked),
checkboxAll: tableCheckboxEvent(checked),
},
});
const [NoticeModal, modalApi] = useVbenModal({
@@ -99,7 +92,6 @@ function handleMultiDelete() {
onOk: async () => {
await noticeRemove(ids);
await tableApi.query();
checked.value = false;
},
});
}
@@ -111,7 +103,7 @@ function handleMultiDelete() {
<template #toolbar-tools>
<Space>
<a-button
:disabled="!checked"
:disabled="!vxeCheckboxChecked(tableApi)"
danger
type="primary"
v-access:code="['system:notice:remove']"

View File

@@ -99,7 +99,7 @@ export const drawerSchema: FormSchemaGetter = () => [
orientation: 'center',
},
fieldName: 'divider1',
labelClass: 'w-0',
hideLabel: true,
renderComponentContent: () => ({
default: () => '基本信息',
}),
@@ -134,7 +134,7 @@ export const drawerSchema: FormSchemaGetter = () => [
orientation: 'center',
},
fieldName: 'divider2',
labelClass: 'w-0',
hideLabel: true,
renderComponentContent: () => ({
default: () => '认证信息',
}),
@@ -157,7 +157,7 @@ export const drawerSchema: FormSchemaGetter = () => [
orientation: 'center',
},
fieldName: 'divider3',
labelClass: 'w-0',
hideLabel: true,
renderComponentContent: () => ({
default: () => '其他信息',
}),

View File

@@ -1,8 +1,6 @@
<script setup lang="ts">
import type { Recordable } from '@vben/types';
import { ref } from 'vue';
import { useAccess } from '@vben/access';
import { Page, useVbenDrawer, type VbenFormProps } from '@vben/common-ui';
import { getVxePopupContainer } from '@vben/utils';
@@ -10,8 +8,8 @@ import { getVxePopupContainer } from '@vben/utils';
import { Modal, Popconfirm, Space } from 'ant-design-vue';
import {
tableCheckboxEvent,
useVbenVxeGrid,
vxeCheckboxChecked,
type VxeGridProps,
} from '#/adapter/vxe-table';
import {
@@ -66,14 +64,9 @@ const gridOptions: VxeGridProps = {
id: 'system-oss-config-index',
};
const checked = ref(false);
const [BasicTable, tableApi] = useVbenVxeGrid({
formOptions,
gridOptions,
gridEvents: {
checkboxChange: tableCheckboxEvent(checked),
checkboxAll: tableCheckboxEvent(checked),
},
});
const [OssConfigDrawer, drawerApi] = useVbenDrawer({
@@ -105,7 +98,6 @@ function handleMultiDelete() {
onOk: async () => {
await ossConfigRemove(ids);
await tableApi.query();
checked.value = false;
},
});
}
@@ -119,7 +111,7 @@ const { hasAccessByCodes } = useAccess();
<template #toolbar-tools>
<Space>
<a-button
:disabled="!checked"
:disabled="!vxeCheckboxChecked(tableApi)"
danger
type="primary"
v-access:code="['system:ossConfig:remove']"

View File

@@ -20,8 +20,8 @@ import {
import { isEmpty } from 'lodash-es';
import {
tableCheckboxEvent,
useVbenVxeGrid,
vxeCheckboxChecked,
type VxeGridProps,
} from '#/adapter/vxe-table';
import { configInfoByKey } from '#/api/system/config';
@@ -91,7 +91,6 @@ const gridOptions: VxeGridProps = {
id: 'system-oss-index',
};
const checked = ref(false);
const [BasicTable, tableApi] = useVbenVxeGrid({
formOptions,
gridOptions,
@@ -99,8 +98,6 @@ const [BasicTable, tableApi] = useVbenVxeGrid({
sortChange: () => {
tableApi.query();
},
checkboxChange: tableCheckboxEvent(checked),
checkboxAll: tableCheckboxEvent(checked),
},
});
@@ -172,7 +169,7 @@ const [FileUploadModal, fileUploadApi] = useVbenModal({
配置管理
</a-button>
<a-button
:disabled="!checked"
:disabled="!vxeCheckboxChecked(tableApi)"
danger
type="primary"
v-access:code="['system:oss:remove']"

View File

@@ -82,7 +82,6 @@ export const drawerSchema: FormSchemaGetter = () => [
{
component: 'TreeSelect',
componentProps: {
class: 'w-full',
getPopupContainer,
},
fieldName: 'deptId',

View File

@@ -9,8 +9,8 @@ import { getVxePopupContainer } from '@vben/utils';
import { Modal, Popconfirm, Space } from 'ant-design-vue';
import {
tableCheckboxEvent,
useVbenVxeGrid,
vxeCheckboxChecked,
type VxeGridProps,
} from '#/adapter/vxe-table';
import { postExport, postList, postRemove } from '#/api/system/post';
@@ -79,14 +79,9 @@ const gridOptions: VxeGridProps = {
id: 'system-post-index',
};
const checked = ref(false);
const [BasicTable, tableApi] = useVbenVxeGrid({
formOptions,
gridOptions,
gridEvents: {
checkboxChange: tableCheckboxEvent(checked),
checkboxAll: tableCheckboxEvent(checked),
},
});
const [PostDrawer, drawerApi] = useVbenDrawer({
@@ -118,7 +113,6 @@ function handleMultiDelete() {
onOk: async () => {
await postRemove(ids);
await tableApi.query();
checked.value = false;
},
});
}
@@ -146,7 +140,7 @@ function handleDownloadExcel() {
{{ $t('pages.common.export') }}
</a-button>
<a-button
:disabled="!checked"
:disabled="!vxeCheckboxChecked(tableApi)"
danger
type="primary"
v-access:code="['system:post:remove']"

View File

@@ -1,7 +1,6 @@
<script setup lang="ts">
import type { Recordable } from '@vben/types';
import { ref } from 'vue';
import { useRoute } from 'vue-router';
import { Page, useVbenDrawer, type VbenFormProps } from '@vben/common-ui';
@@ -10,8 +9,8 @@ import { getVxePopupContainer } from '@vben/utils';
import { Modal, Popconfirm, Space } from 'ant-design-vue';
import {
tableCheckboxEvent,
useVbenVxeGrid,
vxeCheckboxChecked,
type VxeGridProps,
} from '#/adapter/vxe-table';
import {
@@ -69,14 +68,9 @@ const gridOptions: VxeGridProps = {
id: 'system-role-assign-index',
};
const checked = ref(false);
const [BasicTable, tableApi] = useVbenVxeGrid({
formOptions,
gridOptions,
gridEvents: {
checkboxChange: tableCheckboxEvent(checked),
checkboxAll: tableCheckboxEvent(checked),
},
});
const [RoleAssignDrawer, drawerApi] = useVbenDrawer({
@@ -109,7 +103,6 @@ function handleMultipleAuthCancel() {
onOk: async () => {
await roleAuthCancelAll(roleId, ids);
await tableApi.query();
checked.value = false;
tableApi.grid.clearCheckboxRow();
},
});
@@ -122,7 +115,7 @@ function handleMultipleAuthCancel() {
<template #toolbar-tools>
<Space>
<a-button
:disabled="!checked"
:disabled="!vxeCheckboxChecked(tableApi)"
danger
type="primary"
v-access:code="['system:role:remove']"

View File

@@ -1,14 +1,9 @@
<script setup lang="ts">
import { ref } from 'vue';
import { useRoute } from 'vue-router';
import { useVbenDrawer, type VbenFormProps } from '@vben/common-ui';
import {
tableCheckboxEvent,
useVbenVxeGrid,
type VxeGridProps,
} from '#/adapter/vxe-table';
import { useVbenVxeGrid, type VxeGridProps } from '#/adapter/vxe-table';
import { roleSelectAll, roleUnallocatedList } from '#/api/system/role';
import { columns, querySchema } from './data';
@@ -62,14 +57,9 @@ const gridOptions: VxeGridProps = {
},
};
const checked = ref(false);
const [BasicTable, tableApi] = useVbenVxeGrid({
formOptions,
gridOptions,
gridEvents: {
checkboxChange: tableCheckboxEvent(checked),
checkboxAll: tableCheckboxEvent(checked),
},
});
async function handleSubmit() {

View File

@@ -153,12 +153,13 @@ export const drawerSchema: FormSchemaGetter = () => [
defaultValue: [],
fieldName: 'menuIds',
label: '菜单权限',
formItemClass: 'col-span-2',
},
{
component: 'Textarea',
defaultValue: '',
fieldName: 'remark',
formItemClass: 'items-baseline',
formItemClass: 'items-baseline col-span-2',
label: '备注',
},
];

View File

@@ -1,7 +1,7 @@
<script setup lang="ts">
import type { Recordable } from '@vben/types';
import { computed, ref } from 'vue';
import { computed } from 'vue';
import { useRouter } from 'vue-router';
import { useAccess } from '@vben/access';
@@ -23,8 +23,8 @@ import {
} from 'ant-design-vue';
import {
tableCheckboxEvent,
useVbenVxeGrid,
vxeCheckboxChecked,
type VxeGridProps,
} from '#/adapter/vxe-table';
import {
@@ -91,14 +91,9 @@ const gridOptions: VxeGridProps = {
id: 'system-role-index',
};
const checked = ref(false);
const [BasicTable, tableApi] = useVbenVxeGrid({
formOptions,
gridOptions,
gridEvents: {
checkboxChange: tableCheckboxEvent(checked),
checkboxAll: tableCheckboxEvent(checked),
},
});
const [RoleDrawer, drawerApi] = useVbenDrawer({
connectedComponent: roleDrawer,
@@ -129,7 +124,6 @@ function handleMultiDelete() {
onOk: async () => {
await roleRemove(ids);
await tableApi.query();
checked.value = false;
},
});
}
@@ -171,7 +165,7 @@ function handleAssignRole(record: Recordable<any>) {
{{ $t('pages.common.export') }}
</a-button>
<a-button
:disabled="!checked"
:disabled="!vxeCheckboxChecked(tableApi)"
danger
type="primary"
v-access:code="['system:role:remove']"

View File

@@ -24,12 +24,12 @@ const [BasicForm, formApi] = useVbenForm({
componentProps: {
class: 'w-full',
},
formItemClass: 'col-span-2',
formItemClass: 'col-span-1',
},
layout: 'vertical',
schema: drawerSchema(),
showDefaultActions: false,
wrapperClass: 'grid-cols-2',
wrapperClass: 'grid-cols-2 gap-x-4',
});
const menuTree = ref<any[]>([]);

View File

@@ -101,7 +101,7 @@ export const drawerSchema: FormSchemaGetter = () => [
orientation: 'center',
},
fieldName: 'divider1',
labelClass: 'w-0',
hideLabel: true,
renderComponentContent: () => ({
default: () => '基本信息',
}),
@@ -132,7 +132,7 @@ export const drawerSchema: FormSchemaGetter = () => [
orientation: 'center',
},
fieldName: 'divider2',
labelClass: 'w-0',
hideLabel: true,
renderComponentContent: () => ({
default: () => '管理员信息',
}),
@@ -167,7 +167,7 @@ export const drawerSchema: FormSchemaGetter = () => [
orientation: 'center',
},
fieldName: 'divider3',
labelClass: 'w-0',
hideLabel: true,
renderComponentContent: () => ({
default: () => '租户设置',
}),
@@ -175,7 +175,6 @@ export const drawerSchema: FormSchemaGetter = () => [
{
component: 'Select',
componentProps: {
class: 'w-full',
getPopupContainer,
},
fieldName: 'packageId',
@@ -237,7 +236,7 @@ export const drawerSchema: FormSchemaGetter = () => [
orientation: 'center',
},
fieldName: 'divider4',
labelClass: 'w-0',
hideLabel: true,
renderComponentContent: () => ({
default: () => '企业信息',
}),

View File

@@ -1,7 +1,7 @@
<script setup lang="ts">
import type { Recordable } from '@vben/types';
import { computed, ref } from 'vue';
import { computed } from 'vue';
import { useAccess } from '@vben/access';
import { Page, useVbenDrawer, type VbenFormProps } from '@vben/common-ui';
@@ -11,8 +11,8 @@ import { getVxePopupContainer } from '@vben/utils';
import { Modal, Popconfirm, Space } from 'ant-design-vue';
import {
tableCheckboxEvent,
useVbenVxeGrid,
vxeCheckboxChecked,
type VxeGridProps,
} from '#/adapter/vxe-table';
import {
@@ -73,14 +73,9 @@ const gridOptions: VxeGridProps = {
id: 'system-tenant-index',
};
const checked = ref(false);
const [BasicTable, tableApi] = useVbenVxeGrid({
formOptions,
gridOptions,
gridEvents: {
checkboxChange: tableCheckboxEvent(checked),
checkboxAll: tableCheckboxEvent(checked),
},
});
const [TenantDrawer, drawerApi] = useVbenDrawer({
@@ -121,7 +116,6 @@ function handleMultiDelete() {
onOk: async () => {
await tenantRemove(ids);
await tableApi.query();
checked.value = false;
// 重新加载租户信息
tenantStore.initTenant();
},
@@ -173,7 +167,7 @@ function handleSyncTenantDict() {
{{ $t('pages.common.export') }}
</a-button>
<a-button
:disabled="!checked"
:disabled="!vxeCheckboxChecked(tableApi)"
danger
type="primary"
v-access:code="['system:tenant:remove']"

View File

@@ -23,6 +23,9 @@ const [BasicForm, formApi] = useVbenForm({
commonConfig: {
formItemClass: 'col-span-2',
labelWidth: 100,
componentProps: {
class: 'w-full',
},
},
schema: drawerSchema(),
showDefaultActions: false,

View File

@@ -1,7 +1,7 @@
<script setup lang="ts">
import type { Recordable } from '@vben/types';
import { computed, ref } from 'vue';
import { computed } from 'vue';
import { useAccess } from '@vben/access';
import { Page, useVbenDrawer, type VbenFormProps } from '@vben/common-ui';
@@ -11,8 +11,8 @@ import { getVxePopupContainer } from '@vben/utils';
import { Modal, Popconfirm, Space } from 'ant-design-vue';
import {
tableCheckboxEvent,
useVbenVxeGrid,
vxeCheckboxChecked,
type VxeGridProps,
} from '#/adapter/vxe-table';
import {
@@ -69,14 +69,9 @@ const gridOptions: VxeGridProps = {
id: 'system-tenant-package-index',
};
const checked = ref(false);
const [BasicTable, tableApi] = useVbenVxeGrid({
formOptions,
gridOptions,
gridEvents: {
checkboxChange: tableCheckboxEvent(checked),
checkboxAll: tableCheckboxEvent(checked),
},
});
const [TenantPackageDrawer, drawerApi] = useVbenDrawer({
@@ -108,7 +103,6 @@ function handleMultiDelete() {
onOk: async () => {
await packageRemove(ids);
await tableApi.query();
checked.value = false;
},
});
}
@@ -144,7 +138,7 @@ const isSuperAdmin = computed(() => {
{{ $t('pages.common.export') }}
</a-button>
<a-button
:disabled="!checked"
:disabled="!vxeCheckboxChecked(tableApi)"
danger
type="primary"
v-access:code="['system:tenantPackage:remove']"

View File

@@ -71,7 +71,7 @@ onMounted(loadTree);
<div class="bg-background z-100 sticky left-0 top-0 p-[8px]">
<InputSearch
v-model:value="searchValue"
placeholder="Search"
:placeholder="$t('pages.common.search')"
size="small"
>
<template #enterButton>

View File

@@ -24,11 +24,8 @@ import {
Space,
} from 'ant-design-vue';
import {
tableCheckboxEvent,
useVbenVxeGrid,
type VxeGridProps,
} from '#/adapter/vxe-table';
import { useVbenVxeGrid, type VxeGridProps } from '#/adapter/vxe-table';
import { vxeCheckboxChecked } from '#/adapter/vxe-table';
import {
userExport,
userList,
@@ -126,14 +123,9 @@ const gridOptions: VxeGridProps = {
},
id: 'system-user-index',
};
const checked = ref(false);
const [BasicTable, tableApi] = useVbenVxeGrid({
formOptions,
gridOptions,
gridEvents: {
checkboxChange: tableCheckboxEvent(checked),
checkboxAll: tableCheckboxEvent(checked),
},
});
const [UserDrawer, userDrawerApi] = useVbenDrawer({
@@ -165,7 +157,6 @@ function handleMultiDelete() {
onOk: async () => {
await userRemove(ids);
await tableApi.query();
checked.value = false;
},
});
}
@@ -201,7 +192,7 @@ const { hasAccessByCodes } = useAccess();
<div class="flex h-full gap-[8px]">
<DeptTree
v-model:select-dept-id="selectDeptId"
class="w-[260px]"
:width="260"
@reload="() => tableApi.reload()"
@select="() => tableApi.reload()"
/>
@@ -221,7 +212,7 @@ const { hasAccessByCodes } = useAccess();
{{ $t('pages.common.import') }}
</a-button>
<a-button
:disabled="!checked"
:disabled="!vxeCheckboxChecked(tableApi)"
danger
type="primary"
v-access:code="['system:user:remove']"

View File

@@ -2,7 +2,7 @@
import type { Recordable } from '@vben/types';
import type { Key } from 'ant-design-vue/es/vc-tree/interface';
import { type Component, ref } from 'vue';
import { type Component, markRaw, ref } from 'vue';
import {
CodeMirror,
@@ -98,12 +98,12 @@ function convertToTree(paths: string[]): TreeNode[] {
}
const iconMap = [
{ key: 'java', value: JavaIcon },
{ key: 'xml', value: XmlIcon },
{ key: 'sql', value: SqlIcon },
{ key: 'ts', value: TsIcon },
{ key: 'vue', value: VueIcon },
{ key: 'folder', value: FolderIcon },
{ key: 'java', value: markRaw(JavaIcon) },
{ key: 'xml', value: markRaw(XmlIcon) },
{ key: 'sql', value: markRaw(SqlIcon) },
{ key: 'ts', value: markRaw(TsIcon) },
{ key: 'vue', value: markRaw(VueIcon) },
{ key: 'folder', value: markRaw(FolderIcon) },
];
function findIcon(path: string) {
const defaultFileIcon = DefaultFileIcon;

View File

@@ -1,7 +1,7 @@
<script setup lang="ts">
import type { Recordable } from '@vben/types';
import { onMounted, ref } from 'vue';
import { onMounted } from 'vue';
import { useRouter } from 'vue-router';
import { Page, useVbenModal, type VbenFormProps } from '@vben/common-ui';
@@ -11,8 +11,8 @@ import { message, Modal, Popconfirm, Space } from 'ant-design-vue';
import dayjs from 'dayjs';
import {
tableCheckboxEvent,
useVbenVxeGrid,
vxeCheckboxChecked,
type VxeGridProps,
} from '#/adapter/vxe-table';
import {
@@ -78,14 +78,9 @@ const gridOptions: VxeGridProps = {
id: 'tool-gen-index',
};
const checked = ref(false);
const [BasicTable, tableApi] = useVbenVxeGrid({
formOptions,
gridOptions,
gridEvents: {
checkboxChange: tableCheckboxEvent(checked),
checkboxAll: tableCheckboxEvent(checked),
},
});
onMounted(async () => {
@@ -177,7 +172,6 @@ function handleMultiDelete() {
onOk: async () => {
await genRemove(ids);
await tableApi.query();
checked.value = false;
},
});
}
@@ -203,7 +197,7 @@ function handleImport() {
</a>
<Space>
<a-button
:disabled="!checked"
:disabled="!vxeCheckboxChecked(tableApi)"
danger
type="primary"
v-access:code="['tool:gen:remove']"
@@ -212,7 +206,7 @@ function handleImport() {
{{ $t('pages.common.delete') }}
</a-button>
<a-button
:disabled="!checked"
:disabled="!vxeCheckboxChecked(tableApi)"
v-access:code="['tool:gen:code']"
@click="handleBatchGen"
>

View File

@@ -20,6 +20,8 @@ export default defineConfig(async () => {
// dts: './types/components.d.ts', // 输出类型文件
// resolvers: [
// AntDesignVueResolver({
// // 需要排除Button组件 全局已经默认导入了
// exclude: ['Button'],
// importStyle: false, // css in js
// }),
// ],

View File

@@ -1,6 +1,6 @@
{
"name": "@vben/web-ele",
"version": "5.4.8",
"version": "5.5.0",
"homepage": "https://vben.pro",
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
"repository": {

View File

@@ -4,11 +4,12 @@
*/
import type { BaseFormComponentType } from '@vben/common-ui';
import type { Recordable } from '@vben/types';
import type { Component, SetupContext } from 'vue';
import { h } from 'vue';
import { globalShareState } from '@vben/common-ui';
import { ApiSelect, globalShareState, IconPicker } from '@vben/common-ui';
import { $t } from '@vben/locales';
import {
@@ -22,6 +23,7 @@ import {
ElNotification,
ElRadioGroup,
ElSelect,
ElSelectV2,
ElSpace,
ElSwitch,
ElTimePicker,
@@ -41,10 +43,12 @@ const withDefaultPlaceholder = <T extends Component>(
// 这里需要自行根据业务组件库进行适配,需要用到的组件都需要在这里类型说明
export type ComponentType =
| 'ApiSelect'
| 'Checkbox'
| 'CheckboxGroup'
| 'DatePicker'
| 'Divider'
| 'IconPicker'
| 'Input'
| 'InputNumber'
| 'RadioGroup'
@@ -61,7 +65,19 @@ async function initComponentAdapter() {
// 如果你的组件体积比较大,可以使用异步加载
// Button: () =>
// import('xxx').then((res) => res.Button),
ApiSelect: (props, { attrs, slots }) => {
return h(
ApiSelect,
{
...props,
...attrs,
component: ElSelectV2,
loadingSlot: 'loading',
visibleEvent: 'onDropdownVisibleChange',
},
slots,
);
},
Checkbox: ElCheckbox,
CheckboxGroup: ElCheckboxGroup,
// 自定义默认按钮
@@ -73,14 +89,67 @@ async function initComponentAdapter() {
return h(ElButton, { ...props, attrs, type: 'primary' }, slots);
},
Divider: ElDivider,
IconPicker: (props, { attrs, slots }) => {
return h(
IconPicker,
{
iconSlot: 'append',
modelValueProp: 'model-value',
inputComponent: ElInput,
...props,
...attrs,
},
slots,
);
},
Input: withDefaultPlaceholder(ElInput, 'input'),
InputNumber: withDefaultPlaceholder(ElInputNumber, 'input'),
RadioGroup: ElRadioGroup,
Select: withDefaultPlaceholder(ElSelect, 'select'),
Space: ElSpace,
Switch: ElSwitch,
TimePicker: ElTimePicker,
DatePicker: ElDatePicker,
TimePicker: (props, { attrs, slots }) => {
const { name, id, isRange } = props;
const extraProps: Recordable<any> = {};
if (isRange) {
if (name && !Array.isArray(name)) {
extraProps.name = [name, `${name}_end`];
}
if (id && !Array.isArray(id)) {
extraProps.id = [id, `${id}_end`];
}
}
return h(
ElTimePicker,
{
...props,
...attrs,
...extraProps,
},
slots,
);
},
DatePicker: (props, { attrs, slots }) => {
const { name, id, type } = props;
const extraProps: Recordable<any> = {};
if (type && type.includes('range')) {
if (name && !Array.isArray(name)) {
extraProps.name = [name, `${name}_end`];
}
if (id && !Array.isArray(id)) {
extraProps.id = [id, `${id}_end`];
}
}
return h(
ElDatePicker,
{
...props,
...attrs,
...extraProps,
},
slots,
);
},
TreeSelect: withDefaultPlaceholder(ElTreeSelect, 'select'),
Upload: ElUpload,
};

View File

@@ -7,6 +7,7 @@ import '@vben/styles';
import '@vben/styles/ele';
import { useTitle } from '@vueuse/core';
import { ElLoading } from 'element-plus';
import { $t, setupI18n } from '#/locales';
@@ -19,6 +20,9 @@ async function bootstrap(namespace: string) {
await initComponentAdapter();
const app = createApp(App);
// 注册Element Plus提供的v-loading指令
app.directive('loading', ElLoading.directive);
// 国际化 i18n 配置
await setupI18n(app);

View File

@@ -1,4 +1,6 @@
<script lang="ts" setup>
import { ref } from 'vue';
import { Page } from '@vben/common-ui';
import {
@@ -6,6 +8,7 @@ import {
ElCard,
ElMessage,
ElNotification,
ElSegmented,
ElSpace,
ElTable,
} from 'element-plus';
@@ -47,6 +50,10 @@ const tableData = [
{ prop1: '5', prop2: 'E' },
{ prop1: '6', prop2: 'F' },
];
const segmentedValue = ref('Mon');
const segmentedOptions = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'];
</script>
<template>
@@ -54,41 +61,57 @@ const tableData = [
description="支持多语言,主题功能集成切换等"
title="Element Plus组件使用演示"
>
<ElCard class="mb-5">
<template #header> 按钮 </template>
<ElSpace>
<ElButton text>Text</ElButton>
<ElButton>Default</ElButton>
<ElButton type="primary"> Primary </ElButton>
<ElButton type="info"> Info </ElButton>
<ElButton type="success"> Success </ElButton>
<ElButton type="warning"> Warning </ElButton>
<ElButton type="danger"> Error </ElButton>
</ElSpace>
</ElCard>
<ElCard class="mb-5">
<template #header> Message </template>
<ElSpace>
<ElButton type="info" @click="info"> 信息 </ElButton>
<ElButton type="danger" @click="error"> 错误 </ElButton>
<ElButton type="warning" @click="warning"> 警告 </ElButton>
<ElButton type="success" @click="success"> 成功 </ElButton>
</ElSpace>
</ElCard>
<ElCard class="mb-5">
<template #header> Notification </template>
<ElSpace>
<ElButton type="info" @click="notify('info')"> 信息 </ElButton>
<ElButton type="danger" @click="notify('error')"> 错误 </ElButton>
<ElButton type="warning" @click="notify('warning')"> 警告 </ElButton>
<ElButton type="success" @click="notify('success')"> 成功 </ElButton>
</ElSpace>
</ElCard>
<ElCard class="mb-5">
<ElTable :data="tableData" stripe>
<ElTable.TableColumn label="测试列1" prop="prop1" />
<ElTable.TableColumn label="测试列2" prop="prop2" />
</ElTable>
</ElCard>
<div class="flex flex-wrap gap-5">
<ElCard class="mb-5 w-auto">
<template #header> 按钮 </template>
<ElSpace>
<ElButton text>Text</ElButton>
<ElButton>Default</ElButton>
<ElButton type="primary"> Primary </ElButton>
<ElButton type="info"> Info </ElButton>
<ElButton type="success"> Success </ElButton>
<ElButton type="warning"> Warning </ElButton>
<ElButton type="danger"> Error </ElButton>
</ElSpace>
</ElCard>
<ElCard class="mb-5 w-80">
<template #header> Message </template>
<ElSpace>
<ElButton type="info" @click="info"> 信息 </ElButton>
<ElButton type="danger" @click="error"> 错误 </ElButton>
<ElButton type="warning" @click="warning"> 警告 </ElButton>
<ElButton type="success" @click="success"> 成功 </ElButton>
</ElSpace>
</ElCard>
<ElCard class="mb-5 w-80">
<template #header> Notification </template>
<ElSpace>
<ElButton type="info" @click="notify('info')"> 信息 </ElButton>
<ElButton type="danger" @click="notify('error')"> 错误 </ElButton>
<ElButton type="warning" @click="notify('warning')"> 警告 </ElButton>
<ElButton type="success" @click="notify('success')"> 成功 </ElButton>
</ElSpace>
</ElCard>
<ElCard class="mb-5 w-auto">
<template #header> Segmented </template>
<ElSegmented
v-model="segmentedValue"
:options="segmentedOptions"
size="large"
/>
</ElCard>
<ElCard class="mb-5 w-80">
<template #header> V-Loading </template>
<div class="flex size-72 items-center justify-center" v-loading="true">
一些演示的内容
</div>
</ElCard>
<ElCard class="mb-5 w-80">
<ElTable :data="tableData" stripe>
<ElTable.TableColumn label="测试列1" prop="prop1" />
<ElTable.TableColumn label="测试列2" prop="prop2" />
</ElTable>
</ElCard>
</div>
</Page>
</template>

View File

@@ -1,6 +1,6 @@
{
"name": "@vben/web-naive",
"version": "5.4.8",
"version": "5.5.0",
"homepage": "https://vben.pro",
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
"repository": {

View File

@@ -8,7 +8,7 @@ import type { BaseFormComponentType } from '@vben/common-ui';
import type { Component, SetupContext } from 'vue';
import { h } from 'vue';
import { globalShareState } from '@vben/common-ui';
import { ApiSelect, globalShareState, IconPicker } from '@vben/common-ui';
import { $t } from '@vben/locales';
import {
@@ -42,10 +42,12 @@ const withDefaultPlaceholder = <T extends Component>(
// 这里需要自行根据业务组件库进行适配,需要用到的组件都需要在这里类型说明
export type ComponentType =
| 'ApiSelect'
| 'Checkbox'
| 'CheckboxGroup'
| 'DatePicker'
| 'Divider'
| 'IconPicker'
| 'Input'
| 'InputNumber'
| 'RadioGroup'
@@ -63,6 +65,18 @@ async function initComponentAdapter() {
// Button: () =>
// import('xxx').then((res) => res.Button),
ApiSelect: (props, { attrs, slots }) => {
return h(
ApiSelect,
{
...props,
...attrs,
component: NSelect,
modelField: 'value',
},
slots,
);
},
Checkbox: NCheckbox,
CheckboxGroup: NCheckboxGroup,
DatePicker: NDatePicker,
@@ -75,6 +89,13 @@ async function initComponentAdapter() {
return h(NButton, { ...props, attrs, type: 'primary' }, slots);
},
Divider: NDivider,
IconPicker: (props, { attrs, slots }) => {
return h(
IconPicker,
{ iconSlot: 'suffix', inputComponent: NInput, ...props, ...attrs },
slots,
);
},
Input: withDefaultPlaceholder(NInput, 'input'),
InputNumber: withDefaultPlaceholder(NInputNumber, 'input'),
RadioGroup: NRadioGroup,

View File

@@ -148,6 +148,16 @@ function sidebarComponents(): DefaultTheme.SidebarItem[] {
},
],
},
{
collapsed: false,
text: '布局组件',
items: [
{
link: 'layout-ui/page',
text: 'Page 页面',
},
],
},
{
collapsed: false,
text: '通用组件',

View File

@@ -1,6 +1,6 @@
{
"name": "@vben/docs",
"version": "5.4.8",
"version": "5.5.0",
"private": true,
"scripts": {
"build": "vitepress build",

View File

@@ -88,6 +88,7 @@ const [Drawer, drawerApi] = useVbenDrawer({
| closeOnPressEscape | esc 关闭弹窗 | `boolean` | `true` |
| confirmText | 确认按钮文本 | `string\|slot` | `确认` |
| cancelText | 取消按钮文本 | `string\|slot` | `取消` |
| placement | 抽屉弹出位置 | `'left'\|'right'\|'top'\|'bottom'` | `right` |
| showCancelButton | 显示取消按钮 | `boolean` | `true` |
| showConfirmButton | 显示确认按钮文本 | `boolean` | `true` |
| class | modal的class宽度通过这个配置 | `string` | - |

View File

@@ -87,7 +87,7 @@ import type { BaseFormComponentType } from '@vben/common-ui';
import type { Component, SetupContext } from 'vue';
import { h } from 'vue';
import { globalShareState } from '@vben/common-ui';
import { globalShareState, IconPicker } from '@vben/common-ui';
import { $t } from '@vben/locales';
import {
@@ -149,6 +149,7 @@ export type ComponentType =
| 'TimePicker'
| 'TreeSelect'
| 'Upload'
| 'IconPicker';
| BaseFormComponentType;
async function initComponentAdapter() {
@@ -166,6 +167,7 @@ async function initComponentAdapter() {
return h(Button, { ...props, attrs, type: 'default' }, slots);
},
Divider,
IconPicker,
Input: withDefaultPlaceholder(Input, 'input'),
InputNumber: withDefaultPlaceholder(InputNumber, 'input'),
InputPassword: withDefaultPlaceholder(InputPassword, 'input'),
@@ -314,6 +316,7 @@ useVbenForm 返回的第二个参数,是一个对象,包含了一些表单
| commonConfig | 表单项的通用配置,每个配置都会传递到每个表单项,表单项可覆盖 | `FormCommonConfig` | - |
| schema | 表单项的每一项配置 | `FormSchema` | - |
| submitOnEnter | 按下回车健时提交表单 | `boolean` | false |
| submitOnChange | 字段值改变时提交表单 | `boolean` | false |
### TS 类型说明
@@ -419,7 +422,7 @@ export interface FormSchema<
help?: string;
/** 表单项 */
label?: string;
// 自定义组件内部渲染
/** 自定义组件内部渲染 */
renderComponentContent?: RenderComponentContentType;
/** 字段规则 */
rules?: FormSchemaRuleType;
@@ -500,3 +503,20 @@ import { z } from '#/adapter/form';
});
}
```
## Slots
可以使用以下插槽在表单中插入自定义的内容
| 插槽名 | 描述 |
| ------------- | ------------------ |
| reset-before | 重置按钮之前的位置 |
| submit-before | 提交按钮之前的位置 |
| expand-before | 展开按钮之前的位置 |
| expand-after | 展开按钮之后的位置 |
::: tip 字段插槽
除了以上内置插槽之外,`schema`属性中每个字段的`fieldName`都可以作为插槽名称,这些字段插槽的优先级高于`component`定义的组件。也就是说,当提供了与`fieldName`同名的插槽时,这些插槽的内容将会作为这些字段的组件,此时`component`的值将会被忽略。
:::

View File

@@ -93,13 +93,14 @@ const [Modal, modalApi] = useVbenModal({
| modal | 显示遮罩 | `boolean` | `true` |
| header | 显示header | `boolean` | `true` |
| footer | 显示footer | `boolean\|slot` | `true` |
| confirmDisabled | 禁用确认按钮 | `boolean` | `false` |
| confirmLoading | 确认按钮loading状态 | `boolean` | `false` |
| closeOnClickModal | 点击遮罩关闭弹窗 | `boolean` | `true` |
| closeOnPressEscape | esc 关闭弹窗 | `boolean` | `true` |
| confirmText | 确认按钮文本 | `string\|slot` | `确认` |
| cancelText | 取消按钮文本 | `string\|slot` | `取消` |
| showCancelButton | 显示取消按钮 | `boolean` | `true` |
| showConfirmButton | 显示确认按钮文本 | `boolean` | `true` |
| showConfirmButton | 显示确认按钮 | `boolean` | `true` |
| class | modal的class宽度通过这个配置 | `string` | - |
| contentClass | modal内容区域的class | `string` | - |
| footerClass | modal底部区域的class | `string` | - |

View File

@@ -6,6 +6,10 @@
:::
## 布局组件
布局组件一般在页面内容区域用作顶层容器组件,提供一些统一的布局样式和基本功能。
## 通用组件
通用组件是一些常用的组件,比如弹窗、抽屉、表单等。大部分基于 `Tailwind CSS` 实现,可适用于不同 UI 组件库的应用。

View File

@@ -0,0 +1,44 @@
---
outline: deep
---
# Page 常规页面组件
提供一个常规页面布局的组件,包括头部、内容区域、底部三个部分。
::: info 写在前面
本组件是一个基本布局组件。如果有更多的通用页面布局需求(比如双列布局等),可以根据实际需求自行封装。
:::
## 基础用法
`Page`作为你的业务页面的根组件即可。
### Props
| 属性名 | 描述 | 类型 | 默认值 | 说明 |
| --- | --- | --- | --- | --- |
| title | 页面标题 | `string\|slot` | - | - |
| description | 页面描述(标题下的内容) | `string\|slot` | - | - |
| contentClass | 内容区域的class | `string` | - | - |
| headerClass | 头部区域的class | `string` | - | - |
| footerClass | 底部区域的class | `string` | - | - |
| autoContentHeight | 自动调整内容区域的高度 | `boolean` | `false` | - |
::: tip 注意
如果`title``description``extra`三者均未提供有效内容(通过`props`或者`slots`均可),则页面头部区域不会渲染。
:::
### Slots
| 插槽名称 | 描述 |
| ----------- | ------------ |
| default | 页面内容 |
| title | 页面标题 |
| description | 页面描述 |
| extra | 页面头部右侧 |
| footer | 页面底部 |

View File

@@ -76,6 +76,8 @@ const formOptions: VbenFormProps = {
submitButtonOptions: {
content: '查询',
},
// 是否在字段值改变时提交表单
submitOnChange: false,
// 按下回车时是否提交表单
submitOnEnter: false,
};

View File

@@ -1,6 +1,6 @@
{
"name": "@vben/commitlint-config",
"version": "5.4.8",
"version": "5.5.0",
"private": true,
"homepage": "https://github.com/vbenjs/vue-vben-admin",
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",

View File

@@ -1,6 +1,6 @@
{
"name": "@vben/stylelint-config",
"version": "5.4.8",
"version": "5.5.0",
"private": true,
"homepage": "https://github.com/vbenjs/vue-vben-admin",
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",

View File

@@ -1,6 +1,6 @@
{
"name": "@vben/node-utils",
"version": "5.4.8",
"version": "5.5.0",
"private": true,
"homepage": "https://github.com/vbenjs/vue-vben-admin",
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",

View File

@@ -1,6 +1,6 @@
{
"name": "@vben/tailwind-config",
"version": "5.4.8",
"version": "5.5.0",
"private": true,
"homepage": "https://github.com/vbenjs/vue-vben-admin",
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",

View File

@@ -1,6 +1,6 @@
{
"name": "@vben/tsconfig",
"version": "5.4.8",
"version": "5.5.0",
"private": true,
"homepage": "https://github.com/vbenjs/vue-vben-admin",
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",

View File

@@ -1,6 +1,6 @@
{
"name": "@vben/vite-config",
"version": "5.4.8",
"version": "5.5.0",
"private": true,
"homepage": "https://github.com/vbenjs/vue-vben-admin",
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",

View File

@@ -1,5 +1,6 @@
import type { ApplicationPluginOptions } from '../typing';
import { existsSync } from 'node:fs';
import { join } from 'node:path';
import { fs } from '@vben/node-utils';
@@ -21,12 +22,11 @@ function getConfFiles() {
const script = process.env.npm_lifecycle_script as string;
const reg = /--mode ([\d_a-z]+)/;
const result = reg.exec(script);
let mode = 'production';
if (result) {
const mode = result[1];
return ['.env', `.env.${mode}`];
mode = result[1] as string;
}
return ['.env', '.env.production'];
return ['.env', '.env.local', `.env.${mode}`, `.env.${mode}.local`];
}
/**
@@ -42,11 +42,14 @@ async function loadEnv<T = Record<string, string>>(
for (const confFile of confFiles) {
try {
const envPath = await fs.readFile(join(process.cwd(), confFile), {
encoding: 'utf8',
});
const env = dotenv.parse(envPath);
envConfig = { ...envConfig, ...env };
const confFilePath = join(process.cwd(), confFile);
if (existsSync(confFilePath)) {
const envPath = await fs.readFile(confFilePath, {
encoding: 'utf8',
});
const env = dotenv.parse(envPath);
envConfig = { ...envConfig, ...env };
}
} catch (error) {
console.error(`Error while parsing ${confFile}`, error);
}

View File

@@ -1,6 +1,6 @@
{
"name": "vben-admin-monorepo",
"version": "5.4.8",
"version": "5.5.0",
"private": true,
"keywords": [
"monorepo",
@@ -99,7 +99,7 @@
"node": ">=20.10.0",
"pnpm": ">=9.12.0"
},
"packageManager": "pnpm@9.14.2",
"packageManager": "pnpm@9.14.4",
"pnpm": {
"peerDependencyRules": {
"allowedVersions": {

View File

@@ -1,6 +1,6 @@
{
"name": "@vben-core/design",
"version": "5.4.8",
"version": "5.5.0",
"homepage": "https://github.com/vbenjs/vue-vben-admin",
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
"repository": {
@@ -28,7 +28,7 @@
".": {
"types": "./src/index.ts",
"development": "./src/index.ts",
"default": "./dist/style.css"
"default": "./dist/design.css"
}
},
"publishConfig": {

View File

@@ -58,6 +58,8 @@
/* Used for accents such as hover effects on <DropdownMenuItem>, <SelectItem>...etc */
--accent: 216 5% 19%;
--accent-dark: 240 0% 22%;
--accent-darker: 240 0% 26%;
--accent-lighter: 216 5% 12%;
--accent-hover: 216 5% 24%;
--accent-foreground: 0 0% 98%;

View File

@@ -58,6 +58,8 @@
/* Used for accents such as hover effects on <DropdownMenuItem>, <SelectItem>...etc */
--accent: 240 5% 96%;
--accent-dark: 216 14% 93%;
--accent-darker: 216 11% 91%;
--accent-lighter: 240 0% 98%;
--accent-hover: 200deg 10% 90%;
--accent-foreground: 240 6% 10%;

View File

@@ -1,6 +1,6 @@
{
"name": "@vben-core/icons",
"version": "5.4.8",
"version": "5.5.0",
"homepage": "https://github.com/vbenjs/vue-vben-admin",
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "@vben-core/shared",
"version": "5.4.8",
"version": "5.5.0",
"homepage": "https://github.com/vbenjs/vue-vben-admin",
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
"repository": {
@@ -86,12 +86,16 @@
"dayjs": "catalog:",
"defu": "catalog:",
"lodash.clonedeep": "catalog:",
"lodash.get": "catalog:",
"lodash.isequal": "catalog:",
"nprogress": "catalog:",
"tailwind-merge": "catalog:",
"theme-colors": "catalog:"
},
"devDependencies": {
"@types/lodash.clonedeep": "catalog:",
"@types/lodash.get": "catalog:",
"@types/lodash.isequal": "catalog:",
"@types/nprogress": "catalog:"
}
}

View File

@@ -16,3 +16,11 @@ export function formatDate(time: number | string, format = 'YYYY-MM-DD') {
export function formatDateTime(time: number | string) {
return formatDate(time, 'YYYY-MM-DD HH:mm:ss');
}
export function isDate(value: any): value is Date {
return value instanceof Date;
}
export function isDayjsObject(value: any): value is dayjs.Dayjs {
return dayjs.isDayjs(value);
}

View File

@@ -15,3 +15,5 @@ export * from './update-css-variables';
export * from './util';
export * from './window';
export { default as cloneDeep } from 'lodash.clonedeep';
export { default as get } from 'lodash.get';
export { default as isEqual } from 'lodash.isequal';

View File

@@ -1,6 +1,6 @@
{
"name": "@vben-core/typings",
"version": "5.4.8",
"version": "5.5.0",
"homepage": "https://github.com/vbenjs/vue-vben-admin",
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "@vben-core/composables",
"version": "5.4.8",
"version": "5.5.0",
"homepage": "https://github.com/vbenjs/vue-vben-admin",
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "@vben-core/preferences",
"version": "5.4.8",
"version": "5.5.0",
"homepage": "https://github.com/vbenjs/vue-vben-admin",
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "@vben-core/form-ui",
"version": "5.4.8",
"version": "5.5.0",
"homepage": "https://github.com/vbenjs/vue-vben-admin",
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
"repository": {

View File

@@ -14,6 +14,8 @@ import { Store } from '@vben-core/shared/store';
import {
bindMethods,
createMerge,
isDate,
isDayjsObject,
isFunction,
isObject,
mergeWithArrayOverride,
@@ -36,6 +38,7 @@ function getDefaultState(): VbenFormProps {
showCollapseButton: false,
showDefaultActions: true,
submitButtonOptions: {},
submitOnChange: false,
submitOnEnter: false,
wrapperClass: 'grid-cols-1',
};
@@ -185,7 +188,7 @@ export class FormApi {
const fieldSet = new Set(fields);
const schema = this.state?.schema ?? [];
const filterSchema = schema.filter((item) => fieldSet.has(item.fieldName));
const filterSchema = schema.filter((item) => !fieldSet.has(item.fieldName));
this.setState({
schema: filterSchema,
@@ -251,10 +254,19 @@ export class FormApi {
return;
}
/**
* 合并算法有待改进目前的算法不支持object类型的值。
* antd的日期时间相关组件的值类型为dayjs对象
* element-plus的日期时间相关组件的值类型可能为Date对象
* 以上两种类型需要排除深度合并
*/
const fieldMergeFn = createMerge((obj, key, value) => {
if (key in obj) {
obj[key] =
!Array.isArray(obj[key]) && isObject(obj[key])
!Array.isArray(obj[key]) &&
isObject(obj[key]) &&
!isDayjsObject(obj[key]) &&
!isDate(obj[key])
? fieldMergeFn(obj[key], value)
: value;
}

View File

@@ -342,6 +342,12 @@ export interface VbenFormProps<
*/
submitButtonOptions?: ActionButtonOptions;
/**
* 是否在字段值改变时提交表单
* @default false
*/
submitOnChange?: boolean;
/**
* 是否在回车时提交表单
* @default false

View File

@@ -6,7 +6,9 @@ import type { ExtendedFormApi, VbenFormProps } from './types';
import { useForwardPriorityValues } from '@vben-core/composables';
// import { isFunction } from '@vben-core/shared/utils';
import { useTemplateRef } from 'vue';
import { toRaw, useTemplateRef, watch } from 'vue';
import { useDebounceFn } from '@vueuse/core';
import FormActions from './components/form-actions.vue';
import {
@@ -56,6 +58,17 @@ function handleKeyDownEnter(event: KeyboardEvent) {
formActionsRef.value?.handleSubmit?.();
}
watch(
() => form.values,
useDebounceFn(() => {
(props.handleValuesChange ?? state.value.handleValuesChange)?.(
toRaw(form.values),
);
state.value.submitOnChange && props.formApi?.submitForm();
}, 300),
{ deep: true },
);
</script>
<template>

View File

@@ -1,6 +1,6 @@
{
"name": "@vben-core/layout-ui",
"version": "5.4.8",
"version": "5.5.0",
"homepage": "https://github.com/vbenjs/vue-vben-admin",
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
"repository": {

View File

@@ -63,7 +63,7 @@ const logoStyle = computed((): CSSProperties => {
<header
:class="theme"
:style="style"
class="border-border bg-header top-0 flex w-full flex-[0_0_auto] items-center border-b transition-[margin-top] duration-200"
class="border-border bg-header top-0 flex w-full flex-[0_0_auto] items-center border-b pl-2 transition-[margin-top] duration-200"
>
<div v-if="slots.logo" :style="logoStyle">
<slot name="logo"></slot>

View File

@@ -191,7 +191,10 @@ watchEffect(() => {
function calcMenuWidthStyle(isHiddenDom: boolean): CSSProperties {
const { extraWidth, fixedExtra, isSidebarMixed, show, width } = props;
let widthValue = `${width + (isSidebarMixed && fixedExtra && extraVisible.value ? extraWidth : 0)}px`;
let widthValue =
width === 0
? '0px'
: `${width + (isSidebarMixed && fixedExtra && extraVisible.value ? extraWidth : 0)}px`;
const { collapseWidth } = props;

View File

@@ -192,7 +192,7 @@ const headerFixed = computed(() => {
});
const showSidebar = computed(() => {
return isSideMode.value && sidebarEnable.value;
return isSideMode.value && sidebarEnable.value && !props.sidebarHidden;
});
/**
@@ -533,7 +533,7 @@ function handleHeaderToggle() {
<template #toggle-button>
<VbenIconButton
v-if="showHeaderToggleButton"
class="my-0 ml-2 mr-1 rounded-md"
class="my-0 mr-1 rounded-md"
@click="handleHeaderToggle"
>
<Menu class="size-4" />

View File

@@ -1,6 +1,6 @@
{
"name": "@vben-core/menu-ui",
"version": "5.4.8",
"version": "5.5.0",
"homepage": "https://github.com/vbenjs/vue-vben-admin",
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
"repository": {

View File

@@ -41,6 +41,7 @@ export class DrawerApi {
loading: false,
modal: true,
openAutoFocus: false,
placement: 'right',
showCancelButton: true,
showConfirmButton: true,
title: '',

View File

@@ -4,6 +4,8 @@ import type { DrawerApi } from './drawer-api';
import type { Component, Ref } from 'vue';
export type DrawerPlacement = 'bottom' | 'left' | 'right' | 'top';
export interface DrawerProps {
/**
* 取消按钮文字
@@ -72,6 +74,12 @@ export interface DrawerProps {
* 是否自动聚焦
*/
openAutoFocus?: boolean;
/**
* 抽屉位置
* @default right
*/
placement?: DrawerPlacement;
/**
* 是否显示取消按钮
* @default true

View File

@@ -62,6 +62,7 @@ const {
loading: showLoading,
modal,
openAutoFocus,
placement,
showCancelButton,
showConfirmButton,
title,
@@ -119,11 +120,13 @@ function handleFocusOutside(e: Event) {
<SheetContent
:class="
cn('flex w-[520px] flex-col', drawerClass, {
'!w-full': isMobile,
'!w-full': isMobile || placement === 'bottom' || placement === 'top',
'max-h-[100vh]': placement === 'bottom' || placement === 'top',
})
"
:modal="modal"
:open="state?.isOpen"
:side="placement"
@close-auto-focus="handleFocusOutside"
@escape-key-down="escapeKeyDown"
@focus-outside="handleFocusOutside"

View File

@@ -41,6 +41,7 @@ export class ModalApi {
class: '',
closeOnClickModal: true,
closeOnPressEscape: true,
confirmDisabled: false,
confirmLoading: false,
contentClass: '',
draggable: false,

View File

@@ -35,6 +35,10 @@ export interface ModalProps {
* @default true
*/
closeOnPressEscape?: boolean;
/**
* 禁用确认按钮
*/
confirmDisabled?: boolean;
/**
* 确定按钮 loading
* @default false

View File

@@ -59,6 +59,7 @@ const {
closable,
closeOnClickModal,
closeOnPressEscape,
confirmDisabled,
confirmLoading,
confirmText,
contentClass,
@@ -171,7 +172,7 @@ function handleFocusOutside(e: Event) {
ref="contentRef"
:class="
cn(
'left-0 right-0 top-[10vh] mx-auto flex max-h-[80%] w-[520px] flex-col p-0 sm:rounded-2xl',
'left-0 right-0 top-[10vh] mx-auto flex max-h-[80%] w-[520px] flex-col p-0 sm:rounded-[var(--radius)]',
modalClass,
{
'border-border border': bordered,
@@ -235,7 +236,7 @@ function handleFocusOutside(e: Event) {
ref="wrapperRef"
:class="
cn('relative min-h-40 flex-1 overflow-y-auto p-3', contentClass, {
'overflow-hidden': showLoading,
'pointer-events-none overflow-hidden': showLoading,
})
"
>
@@ -285,6 +286,7 @@ function handleFocusOutside(e: Event) {
<component
:is="components.PrimaryButton || VbenButton"
v-if="showConfirmButton"
:disabled="confirmDisabled"
:loading="confirmLoading"
@click="() => modalApi?.onConfirm()"
>

View File

@@ -1,6 +1,6 @@
{
"name": "@vben-core/shadcn-ui",
"version": "5.4.8",
"version": "5.5.0",
"#main": "./dist/index.mjs",
"#module": "./dist/index.mjs",
"homepage": "https://github.com/vbenjs/vue-vben-admin",

View File

@@ -7,10 +7,7 @@ const props = defineProps<{ class?: any }>();
<template>
<div
:class="
cn(
'flex flex-col-reverse sm:flex-row sm:justify-end sm:gap-x-2',
props.class,
)
cn('flex flex-row flex-col-reverse justify-end gap-x-2', props.class)
"
>
<slot></slot>

View File

@@ -7,10 +7,7 @@ const props = defineProps<{ class?: any }>();
<template>
<div
:class="
cn(
'flex flex-col-reverse sm:flex-row sm:justify-end sm:gap-x-2',
props.class,
)
cn('flex flex-row flex-col-reverse justify-end gap-x-2', props.class)
"
>
<slot></slot>

View File

@@ -1,6 +1,6 @@
{
"name": "@vben-core/tabs-ui",
"version": "5.4.8",
"version": "5.5.0",
"homepage": "https://github.com/vbenjs/vue-vben-admin",
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "@vben/constants",
"version": "5.4.8",
"version": "5.5.0",
"homepage": "https://github.com/vbenjs/vue-vben-admin",
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "@vben/access",
"version": "5.4.8",
"version": "5.5.0",
"homepage": "https://github.com/vbenjs/vue-vben-admin",
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "@vben/common-ui",
"version": "5.4.8",
"version": "5.5.0",
"homepage": "https://github.com/vbenjs/vue-vben-admin",
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
"repository": {
@@ -29,6 +29,7 @@
"@codemirror/theme-one-dark": "^6.1.2",
"@vben-core/form-ui": "workspace:*",
"@vben-core/popup-ui": "workspace:*",
"@vben-core/preferences": "workspace:*",
"@vben-core/shadcn-ui": "workspace:*",
"@vben-core/shared": "workspace:*",
"@vben/constants": "workspace:*",

View File

@@ -0,0 +1,182 @@
<script lang="ts" setup>
import type { AnyPromiseFunction } from '@vben/types';
import { computed, ref, unref, useAttrs, type VNode, watch } from 'vue';
import { LoaderCircle } from '@vben/icons';
import { get, isEqual, isFunction } from '@vben-core/shared/utils';
import { objectOmit } from '@vueuse/core';
type OptionsItem = {
[name: string]: any;
disabled?: boolean;
label?: string;
value?: string;
};
interface Props {
// 组件
component: VNode;
numberToString?: boolean;
api?: (arg?: any) => Promise<OptionsItem[] | Record<string, any>>;
params?: Record<string, any>;
resultField?: string;
labelField?: string;
valueField?: string;
immediate?: boolean;
alwaysLoad?: boolean;
beforeFetch?: AnyPromiseFunction<any, any>;
afterFetch?: AnyPromiseFunction<any, any>;
options?: OptionsItem[];
// 尾部插槽
loadingSlot?: string;
// 可见时触发的事件名
visibleEvent?: string;
modelField?: string;
}
defineOptions({ name: 'ApiSelect', inheritAttrs: false });
const props = withDefaults(defineProps<Props>(), {
labelField: 'label',
valueField: 'value',
resultField: '',
visibleEvent: '',
numberToString: false,
params: () => ({}),
immediate: true,
alwaysLoad: false,
loadingSlot: '',
beforeFetch: undefined,
afterFetch: undefined,
modelField: 'modelValue',
api: undefined,
options: () => [],
});
const emit = defineEmits<{
optionsChange: [OptionsItem[]];
}>();
const modelValue = defineModel({ default: '' });
const attrs = useAttrs();
const refOptions = ref<OptionsItem[]>([]);
const loading = ref(false);
// 首次是否加载过了
const isFirstLoaded = ref(false);
const getOptions = computed(() => {
const { labelField, valueField, numberToString } = props;
const data: OptionsItem[] = [];
const refOptionsData = unref(refOptions);
for (const next of refOptionsData) {
if (next) {
const value = get(next, valueField);
data.push({
...objectOmit(next, [labelField, valueField]),
label: get(next, labelField),
value: numberToString ? `${value}` : value,
});
}
}
return data.length > 0 ? data : props.options;
});
const bindProps = computed(() => {
return {
[props.modelField]: unref(modelValue),
[`onUpdate:${props.modelField}`]: (val: string) => {
modelValue.value = val;
},
...objectOmit(attrs, ['onUpdate:value']),
...(props.visibleEvent
? {
[props.visibleEvent]: handleFetchForVisible,
}
: {}),
};
});
async function fetchApi() {
let { api, beforeFetch, afterFetch, params, resultField } = props;
if (!api || !isFunction(api) || loading.value) {
return;
}
refOptions.value = [];
try {
loading.value = true;
if (beforeFetch && isFunction(beforeFetch)) {
params = (await beforeFetch(params)) || params;
}
let res = await api(params);
if (afterFetch && isFunction(afterFetch)) {
res = (await afterFetch(res)) || res;
}
isFirstLoaded.value = true;
if (Array.isArray(res)) {
refOptions.value = res;
emitChange();
return;
}
if (resultField) {
refOptions.value = get(res, resultField) || [];
}
emitChange();
} catch (error) {
console.warn(error);
// reset status
isFirstLoaded.value = false;
} finally {
loading.value = false;
}
}
async function handleFetchForVisible(visible: boolean) {
if (visible) {
if (props.alwaysLoad) {
await fetchApi();
} else if (!props.immediate && !unref(isFirstLoaded)) {
await fetchApi();
}
}
}
watch(
() => props.params,
(value, oldValue) => {
if (isEqual(value, oldValue)) {
return;
}
fetchApi();
},
{ deep: true, immediate: props.immediate },
);
function emitChange() {
emit('optionsChange', unref(getOptions));
}
</script>
<template>
<div v-bind="{ ...$attrs }">
<component
:is="component"
v-bind="bindProps"
:options="getOptions"
:placeholder="$attrs.placeholder"
>
<template v-for="item in Object.keys($slots)" #[item]="data">
<slot :name="item" v-bind="data || {}"></slot>
</template>
<template v-if="loadingSlot && loading" #[loadingSlot]>
<LoaderCircle class="animate-spin" />
</template>
</component>
</div>
</template>

View File

@@ -0,0 +1 @@
export { default as ApiSelect } from './api-select.vue';

View File

@@ -1,46 +0,0 @@
import { mount } from '@vue/test-utils';
import { describe, expect, it } from 'vitest';
import { EllipsisText } from '..';
describe('ellipsis-text.vue', () => {
it('renders the correct content and truncates text', async () => {
const wrapper = mount(EllipsisText, {
props: {
line: 1,
title: 'Test Title',
},
slots: {
default: 'This is a very long text that should be truncated.',
},
});
expect(wrapper.text()).toContain('This is a very long text');
// 检查 ellipsis 是否应用了正确的 class
const ellipsis = wrapper.find('.truncate');
expect(ellipsis.exists()).toBe(true);
});
it('expands text on click if expand is true', async () => {
const wrapper = mount(EllipsisText, {
props: {
expand: true,
line: 1,
},
slots: {
default: 'This is a very long text that should be truncated.',
},
});
const ellipsis = wrapper.find('.truncate');
// 点击 ellipsis应该触发 expandChange参数为 false
await ellipsis.trigger('click');
expect(wrapper.emitted('expandChange')).toBeTruthy();
expect(wrapper.emitted('expandChange')?.[0]).toEqual([true]);
// 再次点击,应该触发 expandChange参数为 false
await ellipsis.trigger('click');
expect(wrapper.emitted('expandChange')?.length).toBe(2);
expect(wrapper.emitted('expandChange')?.[1]).toEqual([false]);
});
});

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