!11 follow后端发布

* docs: readme
* fix: missing formPath
* chore: 去除锁定的esbuild版本
* perf: 去除debug组件
* perf: 参数键值 自动高度
* refactor: 代码生成配置页面重构 去除步骤条
* perf: 移除文件
* docs: 文件夹说明
* chore: 移除一些配置项
* chore: 注释优化
* refactor: 移除ele和naive目录
* Merge branch 'main' of https://github.com/vbenjs/vue-vben-admin into dev
* perf: request support to set how to return response (#5436)
* Merge branch 'main' of https://github.com/vbenjs/vue-vben-admin into dev
* refactor: 登录超时的i18n
* fix: requestClient缺失i18n内容
* refactor: 优化oss下载进度提示
* feat: 下载进度loading
* fix: antd button icon style (#5421)
* feat: oss下载进度(已下载的KB 无法作为进度显示 total返回为null)
* fix: 下载文件时(responseType === 'blob')需要判断下载失败(返回json而非二进制)的情况
* Merge branch 'main' of https://github.com/vbenjs/vue-vben-admin into dev
* refactor: 新增后跳转到未发布流程
* fix: same name route
* chore: 调整为部署json类型
* fix: mouse events ignored on modal loading (#5409)
* docs: update docs (#5408)
* refactor: 移除已经弃用的方法
* refactor: follow官方handleRangeTimeValue更新
* chore: 删除文件夹(前端路由需要的)
* chore: 修改本地路由写法(新版)/新增本地菜单图标
* fix: form update state error before form mounted (#5406)
* fix: demos route fixed (#5405)
* chore: 不使用基础布局(仅在顶级生效)
* feat: modal state locked on submitting (#5401)
* chore: 修改zIndex
* Merge branch 'main' of https://github.com/vbenjs/vue-vben-admin into dev
* refactor: fix popup component zIndex (#5397)
* style: element plus loading style fixed (#5393)
* perf: improve fieldMappingTime to support format function (#5392)
* fix: hide root route in breadcrumb
* feat: support set default props for drawer and modal (#5390)
* fix: root router config fixed (#5389)
* fix: 修改Vxe默认zIndex为995 解决右上角全屏后modal/drawer(zIndex: 1000)被遮挡
* feat: add `noBasicLayout` in route meta (#5386)
* chore: wechat image
* Merge branch 'main' of https://github.com/vbenjs/vue-vben-admin into dev
* chore: 改为全局参数配置 去除局部参数
* fix: spinner may stop playing animation after dismiss (#5365)
* style: popover bgColor is too close to common (#5364)
* docs: version update
* docs: changelog
* chore: 文件上传 描述
* ci: retry deploy while faild
* feat: 文件上传 进度条+提示文字
* feat: 文件上传 进度条
* feat: 上传文件格式说明
* Merge branch 'main' of https://github.com/vbenjs/vue-vben-admin into warmflow
* fix: useEcharts return invalid instance (#5360)
* feat: popup component support overlay blur effect (#5359)
* feat: improve `tippy` demo (#5357)
* feat: integrate new component `Tippy` with demo (#5355)
* chore: 优化表格图片显示
* perf: add nested modal demo (#5353)
* chore: 默认显示右边的滚动条 防止出现滚动条被挤压
* perf: modal and drawer api support chain calls (#5351)
* feat: allow close tab when mouse middle button click (#5347)
* Merge branch 'main' of https://github.com/vbenjs/vue-vben-admin into warmflow
* refactor: 重构显示total的逻辑
* chore: 调整高度自适应代码
* chore: vxe升级4.10.0版本(锁定)
* fix: 添加失效的option
* fix: 需要为数组
* fix: locale switching logic correction (#5344)
* Merge branch 'main' of https://github.com/vbenjs/vue-vben-admin into warmflow
* chore: 导入类型优化 解决eslint报红
* refactor: type/注释优化 去除大量any
* fix: vxeGrid init without search form (#5342)
* chore: 锁屏默认false  关闭该功能
* chore: 调整接口
* chore: update deps
* fix: primaryColor calculation (#5337)
* fix: form valid-error style in naive (#5336)
* fix: form `fieldMappingTime` improve and `modelPropName` support (#5335)
* fix: code lint
* fix: form `fieldMappingTime` is not working (#5333)
* chore: 选人组件样式
* fix: download from url triggered twice sometimes (#5319)
* chore: 优化代码
* chore: 动态类名(无效)改为style
* refactor: 字典相关功能重构 采用一个Map储存字典(之前为两个Map)
* feat: 字典支持number类型存储
* chore: 调整样式
* chore: 修改选中border为1px
* chore: 字段
* chore: 改为新窗口打开(适用于pdf/图片)而非直接下载
* chore: 更新样式
* chore: 更新字段
* chore: 改为computed
* chore: 跳转到未发布流程tab
* chore: 优化样式
* docs: readme
* fix: name重复导致的404
* Merge branch 'dev' of https://gitee.com/dapppp/ruoyi-plus-vben5 into warmflow
* chore: 使用legacy来保证copy的兼容性
* chore: 去除log 添加说明
* chore: 优化代码
* feat: 节点关联/节点独立的切换逻辑
* chore: remove logic
* chore: vxe可编辑表格demo
* chore: 不允许在按钮下添加数据
* docs: changelog
* fix: wrong code
* chore: 移除测试菜单
* chore: 优化代码
* refactor: 租户套餐菜单替换为新版
* refactor: 使用新版菜单勾选
* chore: 点行会勾选/取消全部权限  点权限不会勾选行
* chore: 全屏引导+样式优化
* chore: 调整间距
* Merge branch 'main' of https://github.com/vbenjs/vue-vben-admin into dev
* feat: useEcharts exports echarts instance#5294 (#5299)
* chore: update quick-start.md (#5303)
* chore: updateCheckedNumber
* refactor: 优化代码
* chore: 优化代码
* chore: 优化样式
* chore: keys依赖于menu 需要先加载menu
* chore: 菜单加载完毕再显示
* feat: 新的菜单选择组件(beta)
* chore: $t
* chore: 测试菜单页面
* chore: 优化代码
* feat: 对ossId回显的支持
* chore: 只获取一次默认密码而非每次打开modal都获取
* fix: vben select placeholder color (#5286)
* Merge branch 'main' of https://github.com/vbenjs/vue-vben-admin into dev
* perf: format code with better style (#5283)
* chore: 工作流演示站
* fix: sidebar preferences fixed (#5276)
* Merge branch 'main' of https://github.com/vbenjs/vue-vben-admin into dev
* fix: breadcrumb setting not valid for `header-sidebar-nav` layout (#5275)
* fix: header logo may not be collapsed in `header-sidebar-nav` layout (#5274)
* feat: new layout `sidebar nav with full header` (#5270)
* feat: drawer close icon placement (#5269)
* docs: update dialog and drawer docs
* feat: drawer support destroy on close
* feat: drawer support `onOpened` & `onClosed`
* feat: modal support destroy on close
* fix: wrong boolean
* Merge branch 'main' of https://github.com/vbenjs/vue-vben-admin into dev
* chore: 调整tab位置
* chore: 删除历史流程 改为tab切换
* fix: header-mixed layout side-menu active (#5265)
* feat: header mixed layout (#5263)
* chore: release 5.5.2
* Merge branch 'main' of https://github.com/vbenjs/vue-vben-admin into dev
* chore: downgrade vue-tsc version
* feat: header menu align support (#5256)
* chore: update deps
* Merge branch 'main' of https://github.com/vbenjs/vue-vben-admin into dev
* chore: add apiSelect remote search demo (#5246)
* chore: 审批改为description而非disabled的表单
* chore: 改为ts
* chore: 错误的conetnt
* refactor: 终止/转办/委托支持填写意见
* chore: 第一次拿到的是readonly的数据 如果需要修改 需要cloneDeep
* Merge branch 'main' of https://github.com/vbenjs/vue-vben-admin into dev
* fix: grid form submit button locale switch (#5205)
* chore: 调整驳回
* Merge branch 'main' of https://github.com/vbenjs/vue-vben-admin into dev
* fix: build error (#5199)
* fix: esbuild自动升级导致运行/打包报错
* fix: esbuild自动升级导致运行/打包报错
* chore: 流程定义 激活改为switch
* chore: 流程申请支持上传文件
* chore: title 审批通过
* fix: vxeGrid top padding (#5193)
* fix: 表格排序翻页会丢失排序参数
* chore: 去除log打印
* chore: 流程监控 待办任务
* chore: 我发起的
* chore: 去除已经移除的菜单页面
* chore: 我的已办
* chore: 页面优化
* chore: 重置tooltip
* feat: 我的抄送搜索/优化重复触发的接口
* feat: 流程定义 历史
* chore: 修改分类
* Merge branch 'main' of https://github.com/vbenjs/vue-vben-admin into dev
* fix: grid tools in toolbar config not working as expected (#5190)
* feat: add `resizable` and `ColPage` component (#5188)
* chore: 条件
* chore: break-all
* feat: 流程分类 搜索
* chore: 弹窗关闭后仍然显示表单浮层
* chore: 选人组件的样式
* chore: 搜索的样式
* chore: 漏掉的导入
* chore: 最大显示的头像数量 超过显示为省略号头像
* fix: 选人的一些问题
* Merge branch 'warmflow' of https://gitee.com/dapppp/ruoyi-plus-vben5 i…
* chore: 没有更多数据了
* fix: sidebar header height (#5183)
* chore: 搜索表单布局+申请人
* fix: remove the overlap caused by border-b (#5160)
* docs: fix typos (#5169)
* fix: resolve eslint errors as well as TS type errors (#5172)
* chore: enter提交表单
* chore: 修改文案
* chore: 默认全部展开
* Merge branch 'main' of https://github.com/vbenjs/vue-vben-admin into dev
* feat: page content class override (#5179)
* fix: sidebar style on focus (#5178)
* fix: 抄送选人 最右侧已选中删除item无效
* feat: 复制
* chore: 昵称过长的显示
* chore: 默认选中第一个
* chore: 修改relative位置
* chore: 搜索
* feat: 我的待办 - 搜索条件
* chore: 流程监控 - 待办任务页面的id不唯一 改为前端处理
* feat: 修改办理人
* chore: 流程干预 - 加签/减签
* chore: avatar大小
* chore: 抄送需要手动添加createByName
* Merge branch 'main' of https://github.com/vbenjs/vue-vben-admin into dev
* chore: 审批通过 抄送
* feat: 流程实例-流程预览
* chore: spell
* chore: clientid
* chore: 分类条件
* chore: 修改办理人
* chore: 更改postMessage参数
* chore: 内嵌iframe高度根据表单高度调整
* chore: 流程详情
* feat: 抄送选择
* chore: 调整分类树
* fix: user homePath no effect sometimes (#5166)
* feat: form compact mode support (#5165)
* fix: form auto submit no effect when showDefaultActions is false (#5163)
* chore: 修改width
* feat: 待办任务
* feat: 我的抄送
* chore: 流程定义 样式
* chore: 退回后重新申请
* chore: 请假申请布局
* chore: 请假申请-并行会签网关
* chore: 分类去除根目录
* chore: 详情modal(未完成)
* chore: 请假申请根据不同状态显示按钮
* chore: 流程删除/撤销
* chore: 审批完成后刷新当前页
* feat: 选人组件(未完成) 加签减签
* docs: fix docs-link and add `EllipsisText` docs (#5158)
* chore: 新窗口打开文件
* chore: 审批通过
* chore: 使用useEventListener替换原生
* chore: 字段错误
* chore: iframe通信 加载完毕后才显示表单 解决卡顿问题
* chore: 审批终止/驳回
* chore: 附件图标
* chore: process_running显示按钮
* chore: label错误
* chore: 保存的事件
* chore: 需要加上clientId
* Merge branch 'main' of https://github.com/vbenjs/vue-vben-admin into dev
* feat: form `colon` support (#5156)
* chore: 完善请假申请
* feat: improve code login demo (#5154)
* chore: 客户端管理 行高自适应
* chore: 内嵌表单的路径
* chore: 修改avatar背景色
* chore: 注释
* chore: activePath
* chore: leave表单
* chore: 修改请假demo路径
* chore: categoryId
* chore: 我的已办
* chore: 我发起的
* chore: loading
* chore: 历史版本
* chore: 完善task api
* chore: 隐藏'菜单加载中'
* chore: missing import
* feat: add demo for modify menu badge data
* chore: 流程实例
* chore: 审批附件
* chore: 我的待办 提取公共组件
* chore: 流程部署
* chore: 新增/编辑/导出xml
* chore: 流程定义(除历史版本)
* feat: `autoActivateChild` support more layout mode (#5148)
* feat: auto activate subMenu on select root menu (#5147)
* fix: `disabledOnChangeListener` not work in form (#5146)
* fix: login expired modal z-index (#5145)
* feat: user-dropdown support `hover` trigger (#5143)
* fix: pinInput value synchronous update (#5142)
* fix: vxeGrid default sort data no effect in first query (#5141)
* fix: vscode debug profile (#5140)
* fix: form component events bind (#5137)
* chore: 在线用户样式 开启虚拟滚动
* chore: 去掉个人中心 在线设备的分页
* chore: 去掉在线用户的分页
* chore: changelog
* refactor: 获取字典的方法 提取公共函数 减少冗余代码
* fix: element plus validate failed style (#5130)
* chore: 使用私有桶的提示
* feat: tabbar support mouse wheel vertical (#5129)
* fix: form support `disabledOnInputListener` (#5127)
* fix: form submission not appropriate (#5126)
* Merge branch 'main' of https://gitee.com/dapppp/ruoyi-plus-vben5 into dev
* chore: release 5.5.1
* feat: table search form visible control (#5121)
* chore: 需要隐藏菜单
* chore: 我的待办 & 请假
* chore: 流程定义(未完成)
* chore: 流程定义(开发中)
* Merge branch 'main' of https://gitee.com/dapppp/ruoyi-plus-vben5 into dev
* Merge branch 'main' of https://gitee.com/dapppp/ruoyi-plus-vben5 into dev
* Merge branch 'main' of https://gitee.com/dapppp/ruoyi-plus-vben5 into dev
* chore: version
* chore: 锁定vxe-table版本 4.9.8版本存在样式问题
* chore: 暂时锁定cspell版本
* refactor: 由于不能输入 需要使用watch监听
* chore: https://gitee.com/dapppp/ruoyi-plus-vben5/issues/IB7ANL
* chore: 移除冗余代码
* chore: 组件卸载时移除emitter
* fix: the route path did not synchronize with the page (#4947)
* style: typo (#4948)
* chore: 替换为commonDownloadExcel
* fix: 左边部门树错误emit导致会调用两次列表api
* chore: label样式
* chore: 改为Textarea
* chore: 滚动条宽度
* chore: 审批样式
* chore: 部门及以下或本人数据权限
* Merge branch 'main' of https://gitee.com/dapppp/ruoyi-plus-vben5 into dev
* chore: 个人中心强退设备接口路径
This commit is contained in:
玲娜贝er
2025-01-20 03:43:19 +00:00
parent 8ab0bd2212
commit 29f9f3de31
729 changed files with 12839 additions and 9132 deletions

View File

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

View File

@@ -3,9 +3,10 @@
* 可用于 vben-form、vben-modal、vben-drawer 等组件使用,
*/
import type { Component, SetupContext } from 'vue';
import type { BaseFormComponentType } from '@vben/common-ui';
import type { Component, SetupContext } from 'vue';
import { h } from 'vue';
import { ApiComponent, globalShareState, IconPicker } from '@vben/common-ui';

View File

@@ -1,10 +1,8 @@
import { h, type Ref } from 'vue';
import type { VxeGridPropTypes } from '@vben/plugins/vxe-table';
import {
setupVbenVxeTable,
useVbenVxeGrid,
type VxeGridDefines,
} from '@vben/plugins/vxe-table';
import { h } from 'vue';
import { setupVbenVxeTable, useVbenVxeGrid } from '@vben/plugins/vxe-table';
import { Button, Image } from 'ant-design-vue';
@@ -43,7 +41,7 @@ setupVbenVxeTable({
// 鼠标移入行显示 hover 样式
isHover: true,
// 点击行高亮
isCurrent: true,
isCurrent: false,
},
columnConfig: {
// 可拖拽列宽
@@ -105,21 +103,6 @@ export { useVbenVxeGrid };
export type * from '@vben/plugins/vxe-table';
/**
* 通用的表格复选框是否选中事件
* @deprecated 使用vxeCheckboxChecked代替
* @param checked 是否选中
* @returns function
*/
export function tableCheckboxEvent(checked: Ref<boolean>) {
const event: (params: VxeGridDefines.CheckboxChangeEventParams) => void = (
params,
) => {
checked.value = params.$table.getCheckboxRecords().length > 0;
};
return event;
}
/**
* 判断vxe-table的复选框是否选中
* @param tableApi api
@@ -132,22 +115,21 @@ export function vxeCheckboxChecked(
}
/**
* 通用的vxe-table排序事件 支持单/多字段排序
* @param tableApi api
* @param sortParams 排序参数
* 通用的 排序参数添加到请求参数中
* @param params 请求参数
* @param sortList vxe-table的排序参数
*/
export function vxeSortEvent(
tableApi: ReturnType<typeof useVbenVxeGrid>[1],
sortParams: VxeGridDefines.SortChangeEventParams,
export function addSortParams(
params: Record<string, any>,
sortList: VxeGridPropTypes.ProxyAjaxQuerySortCheckedParams[],
) {
const { sortList } = sortParams;
// 这里是排序取消 length为0 就不传参数了
// 这里是排序取消 length为0 就不添加参数了
if (sortList.length === 0) {
tableApi.query();
return;
}
// 支持单/多字段排序
const orderByColumn = sortList.map((item) => item.field).join(',');
const isAsc = sortList.map((item) => item.order).join(',');
tableApi.query({ orderByColumn, isAsc });
params.orderByColumn = orderByColumn;
params.isAsc = isAsc;
}

View File

@@ -38,4 +38,5 @@ export interface PageQuery {
orderByColumn?: string;
pageNum?: number;
pageSize?: number;
[key: string]: any;
}

View File

@@ -19,7 +19,7 @@ export interface MenuMeta {
* @param name 菜单名
* @param path 菜单路径
* @param hidden 是否隐藏
* @param component 组件名称 Laout
* @param component 组件名称 Layout
* @param alwaysShow 总是显示
* @param query 路由参数(json形式)
* @param meta 路由信息

View File

@@ -1,12 +1,27 @@
import type { AxiosRequestConfig } from '@vben/request';
import { requestClient } from '#/api/request';
/**
* Axios上传进度事件
*/
export type AxiosProgressEvent = AxiosRequestConfig['onUploadProgress'];
/**
* 通过单文件上传接口
* @param file 上传的文件
* @param onUploadProgress 上传进度事件 非必传
* @returns 上传结果
*/
export function uploadApi(file: Blob | File) {
return requestClient.upload('/resource/oss/upload', { file });
export function uploadApi(
file: Blob | File,
onUploadProgress?: AxiosProgressEvent,
) {
return requestClient.upload(
'/resource/oss/upload',
{ file },
{ onUploadProgress, timeout: 60_000 },
);
}
/**
* 默认上传结果

View File

@@ -17,6 +17,11 @@ export function onlineDeviceList() {
return requestClient.get<PageResult<OnlineUser>>(Api.root);
}
/**
* 这里的分页参数无效 返回的是全部的分页
* @param params 请求参数
* @returns 结果
*/
export function onlineList(params?: PageQuery) {
return requestClient.get<PageResult<OnlineUser>>(Api.onlineList, { params });
}
@@ -36,5 +41,5 @@ export function forceLogout(tokenId: string) {
* @returns void
*/
export function forceLogout2(tokenId: string) {
return requestClient.postWithMsg<void>(`${Api.root}/${tokenId}`);
return requestClient.deleteWithMsg<void>(`${Api.root}/myself/${tokenId}`);
}

View File

@@ -12,20 +12,36 @@ enum Api {
root = '/monitor/operlog',
}
/**
* 操作日志分页
* @param params 查询参数
* @returns 分页结果
*/
export function operLogList(params?: PageQuery) {
return requestClient.get<PageResult<OperationLog>>(Api.operLogList, {
params,
});
}
/**
* 删除操作日志
* @param operIds id/ids
*/
export function operLogDelete(operIds: IDS) {
return requestClient.deleteWithMsg<void>(`${Api.root}/${operIds}`);
}
/**
* 清空全部分页日志
*/
export function operLogClean() {
return requestClient.deleteWithMsg<void>(Api.operLogClean);
}
export function operLogExport(data: any) {
/**
* 导出操作日志
* @param data 查询参数
*/
export function operLogExport(data: Partial<OperationLog>) {
return commonExport(Api.operLogExport, data);
}

View File

@@ -1,6 +1,7 @@
/**
* 该文件可自行根据业务逻辑进行调整
*/
import type { HttpResponse } from '@vben/request';
import { useAppConfig } from '@vben/hooks';
@@ -134,14 +135,14 @@ function createRequestClient(baseURL: string) {
});
// 通用的错误处理, 如果没有进入上面的错误处理逻辑,就会进入这里
// 主要处理http状态码不为200的情况 必须放在在下面的响应拦截器之前
// 主要处理http状态码不为200(如网络异常/离线)的情况 必须放在在下面的响应拦截器之前
client.addResponseInterceptor(
errorMessageResponseInterceptor((msg: string) => message.error(msg)),
);
client.addResponseInterceptor<HttpResponse>({
fulfilled: (response) => {
const encryptKey = (response.headers || {})['encrypt-key'];
fulfilled: async (response) => {
const encryptKey = (response.headers ?? {})['encrypt-key'];
if (encryptKey) {
/** RSA私钥解密 拿到解密秘钥的base64 */
const base64Str = encryptUtil.decrypt(encryptKey);
@@ -164,39 +165,57 @@ function createRequestClient(baseURL: string) {
// 不进行任何处理,直接返回
// 用于页面代码可能需要直接获取codedatamessage这些信息时开启
if (!isTransformResponse) {
return response.data;
/**
* 需要判断下载二进制的情况 正常是返回二进制 报错会返回json
* 当type为blob且content-type为application/json时 则判断已经下载出错
*/
if (
response.config.responseType === 'blob' &&
response.headers['content-type']?.includes?.('application/json')
) {
// 这时候的data为blob类型
const blob = response.data as unknown as Blob;
// 拿到字符串转json对象
response.data = JSON.parse(await blob.text());
// 然后按正常逻辑执行下面的代码(判断业务状态码)
} else {
// 其他情况 直接返回
return response.data;
}
}
const axiosResponseData = response.data;
if (!axiosResponseData) {
throw new Error($t('fallback.http.apiRequestFailed'));
throw new Error($t('http.apiRequestFailed'));
}
// ruoyi-plus没有采用严格的{code, msg, data}模式
// 后端并没有采用严格的{code, msg, data}模式
const { code, data, msg, ...other } = axiosResponseData;
// 这里逻辑可以根据项目进行修改
// 业务状态码为200则请求成功
const hasSuccess = Reflect.has(axiosResponseData, 'code') && code === 200;
if (hasSuccess) {
let successMsg = msg;
if (isNull(successMsg) || isEmpty(successMsg)) {
successMsg = $t(`fallback.http.operationSuccess`);
successMsg = $t(`http.operationSuccess`);
}
if (response.config.successMessageMode === 'modal') {
Modal.success({
content: successMsg,
title: $t('fallback.http.successTip'),
title: $t('http.successTip'),
});
} else if (response.config.successMessageMode === 'message') {
message.success(successMsg);
}
// 分页情况下为code msg rows total 并没有data字段
// 如果有data 直接返回data 没有data将剩余参数(...other)封装为data返回
// 需要考虑data为null的情况(比如查询为空)
// 需要考虑data为null的情况(比如查询为空) 所以这里直接判断undefined
if (data !== undefined) {
return data;
}
// 没有data 将其他参数包装为data
return other;
}
// 在此处根据自己项目的实际情况对不同的code执行不同的操作
@@ -209,7 +228,7 @@ function createRequestClient(baseURL: string) {
return;
}
isLogoutProcessing = true;
const _msg = '登录超时, 请重新登录';
const _msg = $t('http.loginTimeout');
const userStore = useAuthStore();
userStore.logout().finally(() => {
message.error(_msg);
@@ -230,13 +249,13 @@ function createRequestClient(baseURL: string) {
if (response.config.errorMessageMode === 'modal') {
Modal.error({
content: timeoutMsg,
title: $t('fallback.http.errorTip'),
title: $t('http.errorTip'),
});
} else if (response.config.errorMessageMode === 'message') {
message.error(timeoutMsg);
}
throw new Error(timeoutMsg || $t('fallback.http.apiRequestFailed'));
throw new Error(timeoutMsg || $t('http.apiRequestFailed'));
},
});

View File

@@ -12,30 +12,60 @@ enum Api {
root = '/system/client',
}
/**
* 查询客户端分页列表
* @param params 请求参数
* @returns 列表
*/
export function clientList(params?: PageQuery) {
return requestClient.get<PageResult<Client>>(Api.clientList, { params });
}
export function clientExport(data: any) {
/**
* 导出客户端excel
* @param data 请求参数
*/
export function clientExport(data: Partial<Client>) {
return commonExport(Api.clientExport, data);
}
/**
* 客户端详情
* @param id id
* @returns 详情
*/
export function clientInfo(id: ID) {
return requestClient.get<Client>(`${Api.root}/${id}`);
}
export function clientAdd(data: any) {
/**
* 客户端新增
* @param data 参数
*/
export function clientAdd(data: Partial<Client>) {
return requestClient.postWithMsg<void>(Api.root, data);
}
export function clientUpdate(data: any) {
/**
* 客户端修改
* @param data 参数
*/
export function clientUpdate(data: Partial<Client>) {
return requestClient.putWithMsg<void>(Api.root, data);
}
/**
* 客户端状态修改
* @param data 状态
*/
export function clientChangeStatus(data: any) {
return requestClient.putWithMsg<void>(Api.root, data);
return requestClient.putWithMsg<void>(Api.clientChangeStatus, data);
}
/**
* 客户端删除
* @param ids id集合
*/
export function clientRemove(ids: IDS) {
return requestClient.deleteWithMsg(`${Api.root}/${ids}`);
return requestClient.deleteWithMsg<void>(`${Api.root}/${ids}`);
}

View File

@@ -1,4 +1,4 @@
import type { Config } from './model';
import type { SysConfig } from './model';
import type { ID, IDS, PageQuery, PageResult } from '#/api/common';
@@ -13,15 +13,24 @@ enum Api {
root = '/system/config',
}
/**
* 系统参数分页列表
* @param params 请求参数
* @returns 列表
*/
export function configList(params?: PageQuery) {
return requestClient.get<PageResult<Config>>(Api.configList, { params });
return requestClient.get<PageResult<SysConfig>>(Api.configList, { params });
}
export function configInfo(configId: ID) {
return requestClient.get<Config>(`${Api.root}/${configId}`);
return requestClient.get<SysConfig>(`${Api.root}/${configId}`);
}
export function configExport(data: any) {
/**
* 导出
* @param data 参数
*/
export function configExport(data: Partial<SysConfig>) {
return commonExport(Api.configExport, data);
}
@@ -33,14 +42,26 @@ export function configRefreshCache() {
return requestClient.deleteWithMsg<void>(Api.configRefreshCache);
}
export function configUpdate(data: any) {
/**
* 更新系统配置
* @param data 参数
*/
export function configUpdate(data: Partial<SysConfig>) {
return requestClient.putWithMsg<void>(Api.root, data);
}
export function configAdd(data: any) {
/**
* 新增系统配置
* @param data 参数
*/
export function configAdd(data: Partial<SysConfig>) {
return requestClient.postWithMsg<void>(Api.root, data);
}
/**
* 删除配置
* @param configIds ids
*/
export function configRemove(configIds: IDS) {
return requestClient.deleteWithMsg<void>(`${Api.root}/${configIds}`);
}

View File

@@ -1,4 +1,4 @@
export interface Config {
export interface SysConfig {
configId: number;
configName: string;
configKey: string;

View File

@@ -10,7 +10,11 @@ enum Api {
root = '/system/dept',
}
export function deptList(params?: any) {
/**
* 部门列表
* @returns list
*/
export function deptList(params?: { deptName?: string; status?: string }) {
return requestClient.get<Dept[]>(Api.deptList, { params });
}
@@ -23,15 +27,28 @@ export function deptNodeList(deptId: ID) {
return requestClient.get<Dept[]>(`${Api.deptNodeInfo}/${deptId}`);
}
/**
* 部门详情
* @param deptId 部门id
* @returns 部门信息
*/
export function deptInfo(deptId: ID) {
return requestClient.get<Dept>(`${Api.root}/${deptId}`);
}
export function deptAdd(data: any) {
/**
* 部门新增
* @param data 参数
*/
export function deptAdd(data: Partial<Dept>) {
return requestClient.postWithMsg<void>(Api.root, data);
}
export function deptUpdate(data: any) {
/**
* 部门更新
* @param data 参数
*/
export function deptUpdate(data: Partial<Dept>) {
return requestClient.putWithMsg<void>(Api.root, data);
}

View File

@@ -34,7 +34,7 @@ export function dictDataList(params?: PageQuery) {
* @param data 表单参数
* @returns blob
*/
export function dictDataExport(data: any) {
export function dictDataExport(data: Partial<DictData>) {
return commonExport(Api.dictDataExport, data);
}
@@ -52,7 +52,7 @@ export function dictDataRemove(dictIds: IDS) {
* @param data 表单参数
* @returns void
*/
export function dictDataAdd(data: any) {
export function dictDataAdd(data: Partial<DictData>) {
return requestClient.postWithMsg<void>(Api.root, data);
}
@@ -61,7 +61,7 @@ export function dictDataAdd(data: any) {
* @param data 表单参数
* @returns void
*/
export function dictDataUpdate(data: any) {
export function dictDataUpdate(data: Partial<DictData>) {
return requestClient.putWithMsg<void>(Api.root, data);
}

View File

@@ -27,7 +27,7 @@ export function dictTypeList(params?: PageQuery) {
* @param data 表单参数
* @returns blob
*/
export function dictTypeExport(data: any) {
export function dictTypeExport(data: Partial<DictType>) {
return commonExport(Api.dictTypeExport, data);
}
@@ -53,7 +53,7 @@ export function refreshDictTypeCache() {
* @param data 表单参数
* @returns void
*/
export function dictTypeAdd(data: any) {
export function dictTypeAdd(data: Partial<DictType>) {
return requestClient.postWithMsg<void>(Api.root, data);
}
@@ -62,7 +62,7 @@ export function dictTypeAdd(data: any) {
* @param data 表单参数
* @returns void
*/
export function dictTypeUpdate(data: any) {
export function dictTypeUpdate(data: Partial<DictType>) {
return requestClient.putWithMsg<void>(Api.root, data);
}
@@ -76,6 +76,7 @@ export function dictTypeInfo(dictId: ID) {
}
/**
* 这个在ele用到 v5用不上
* 下拉框 返回值和list一样
* @returns options
*/

View File

@@ -1,4 +1,4 @@
import type { Menu, MenuOption, MenuResp } from './model';
import type { Menu, MenuOption, MenuQuery, MenuResp } from './model';
import type { ID, IDS } from '#/api/common';
@@ -12,22 +12,44 @@ enum Api {
tenantPackageMenuTreeselect = '/system/menu/tenantPackageMenuTreeselect',
}
export function menuList(params?: any) {
/**
* 菜单列表
* @param params 参数
* @returns 列表
*/
export function menuList(params?: MenuQuery) {
return requestClient.get<Menu[]>(Api.menuList, { params });
}
/**
* 菜单详情
* @param menuId 菜单id
* @returns 菜单详情
*/
export function menuInfo(menuId: ID) {
return requestClient.get<Menu>(`${Api.root}/${menuId}`);
}
export function menuAdd(data: any) {
/**
* 菜单新增
* @param data 参数
*/
export function menuAdd(data: Partial<Menu>) {
return requestClient.postWithMsg<void>(Api.root, data);
}
export function menuUpdate(data: any) {
/**
* 菜单更新
* @param data 参数
*/
export function menuUpdate(data: Partial<Menu>) {
return requestClient.putWithMsg<void>(Api.root, data);
}
/**
* 菜单删除
* @param menuIds ids
*/
export function menuRemove(menuIds: IDS) {
return requestClient.deleteWithMsg<void>(`${Api.root}/${menuIds}`);
}

View File

@@ -33,6 +33,8 @@ export interface MenuOption {
weight: number;
children: MenuOption[];
key: string; // 实际上不存在 ide报错
menuType: string;
icon: string;
}
/**
@@ -44,3 +46,12 @@ export interface MenuResp {
checkedKeys: number[];
menus: MenuOption[];
}
/**
* 菜单表单查询
*/
export interface MenuQuery {
menuName?: string;
visible?: string;
status?: string;
}

View File

@@ -9,22 +9,44 @@ enum Api {
root = '/system/notice',
}
/**
* 通知公告分页
* @param params 分页参数
* @returns 分页结果
*/
export function noticeList(params?: PageQuery) {
return requestClient.get<Notice[]>(Api.noticeList, { params });
}
/**
* 通知公告详情
* @param noticeId id
* @returns 详情
*/
export function noticeInfo(noticeId: ID) {
return requestClient.get<Notice>(`${Api.root}/${noticeId}`);
}
export function noticeAdd(data: any) {
/**
* 通知公告新增
* @param data 参数
*/
export function noticeAdd(data: Partial<Notice>) {
return requestClient.postWithMsg<void>(Api.root, data);
}
/**
* 通知公告更新
* @param data 参数
*/
export function noticeUpdate(data: any) {
return requestClient.putWithMsg<void>(Api.root, data);
}
/**
* 通知公告删除
* @param noticeIds ids
*/
export function noticeRemove(noticeIds: IDS) {
return requestClient.deleteWithMsg<void>(`${Api.root}/${noticeIds}`);
}

View File

@@ -21,12 +21,12 @@ export function ossConfigInfo(ossConfigId: ID) {
}
// 添加新的OSS配置
export function ossConfigAdd(data: any) {
export function ossConfigAdd(data: Partial<OssConfig>) {
return requestClient.postWithMsg<void>(Api.root, data);
}
// 更新现有的OSS配置
export function ossConfigUpdate(data: any) {
export function ossConfigUpdate(data: Partial<OssConfig>) {
return requestClient.putWithMsg<void>(Api.root, data);
}

View File

@@ -1,3 +1,5 @@
import type { AxiosRequestConfig } from '@vben/request';
import type { OssFile } from './model';
import type { ID, IDS, PageQuery, PageResult } from '#/api/common';
@@ -13,20 +15,30 @@ enum Api {
root = '/resource/oss',
}
/**
* 文件list
* @param params 参数
* @returns 分页
*/
export function ossList(params?: PageQuery) {
return requestClient.get<PageResult<OssFile>>(Api.ossList, { params });
}
/**
* 查询文件信息 返回为数组
* @param ossIds id数组
* @returns 信息数组
*/
export function ossInfo(ossIds: IDS) {
return requestClient.get<OssFile>(`${Api.ossInfo}/${ossIds}`);
return requestClient.get<OssFile[]>(`${Api.ossInfo}/${ossIds}`);
}
/**
* @deprecated
* @deprecated 使用apps/web-antd/src/api/core/upload.ts uploadApi方法
* @param file 文件
* @returns void
*/
export function ossUpload(file: any) {
export function ossUpload(file: Blob | File) {
const formData = new FormData();
formData.append('file', file);
return requestClient.postWithMsg(Api.ossUpload, formData, {
@@ -38,16 +50,26 @@ export function ossUpload(file: any) {
/**
* 下载文件 返回为二进制
* @param ossId ossId
* @param onDownloadProgress 下载进度(可选)
* @returns blob
*/
export function ossDownload(ossId: ID) {
export function ossDownload(
ossId: ID,
onDownloadProgress?: AxiosRequestConfig['onDownloadProgress'],
) {
return requestClient.get<Blob>(`${Api.ossDownload}/${ossId}`, {
responseType: 'blob',
timeout: 30 * 1000,
isTransformResponse: false,
onDownloadProgress,
});
}
/**
* 删除文件
* @param ossIds id数组
* @returns void
*/
export function ossRemove(ossIds: IDS) {
return requestClient.deleteWithMsg<void>(`${Api.root}/${ossIds}`);
}

View File

@@ -21,26 +21,56 @@ export function postList(params?: PageQuery) {
return requestClient.get<Post[]>(Api.postList, { params });
}
export function postExport(data: any) {
/**
* 导出岗位信息
* @param data 请求参数
* @returns blob
*/
export function postExport(data: Partial<Post>) {
return commonExport(Api.postExport, data);
}
/**
* 查询岗位信息
* @param postId id
* @returns 岗位信息
*/
export function postInfo(postId: ID) {
return requestClient.get<Post>(`${Api.root}/${postId}`);
}
export function postAdd(data: any) {
/**
* 岗位新增
* @param data 参数
* @returns void
*/
export function postAdd(data: Partial<Post>) {
return requestClient.postWithMsg<void>(Api.root, data);
}
export function postUpdate(data: any) {
/**
* 岗位更新
* @param data 参数
* @returns void
*/
export function postUpdate(data: Partial<Post>) {
return requestClient.putWithMsg<void>(Api.root, data);
}
/**
* 岗位删除
* @param postIds ids
* @returns void
*/
export function postRemove(postIds: IDS) {
return requestClient.deleteWithMsg<void>(`${Api.root}/${postIds}`);
}
/**
* 根据部门id获取岗位下拉列表
* @param deptId 部门id
* @returns 岗位
*/
export function postOptionSelect(deptId: ID) {
return requestClient.get<Post[]>(Api.postSelect, { params: { deptId } });
}

View File

@@ -1,3 +1,4 @@
import type { User } from '../user/model';
import type { DeptResp, Role } from './model';
import type { ID, IDS, PageQuery, PageResult } from '#/api/common';
@@ -20,30 +21,65 @@ enum Api {
root = '/system/role',
}
/**
* 查询角色分页列表
* @param params 搜索条件
* @returns 分页列表
*/
export function roleList(params?: PageQuery) {
return requestClient.get<PageResult<Role>>(Api.roleList, { params });
}
export function roleExport(data: any) {
/**
* 导出角色信息
* @param data 查询参数
* @returns blob
*/
export function roleExport(data: Partial<Role>) {
return commonExport(Api.roleExport, data);
}
/**
* 查询角色信息
* @param roleId 角色id
* @returns 角色信息
*/
export function roleInfo(roleId: ID) {
return requestClient.get<Role>(`${Api.root}/${roleId}`);
}
export function roleAdd(data: any) {
/**
* 角色新增
* @param data 参数
* @returns void
*/
export function roleAdd(data: Partial<Role>) {
return requestClient.postWithMsg<void>(Api.root, data);
}
export function roleUpdate(data: any) {
/**
* 角色更新
* @param data 参数
* @returns void
*/
export function roleUpdate(data: Partial<Role>) {
return requestClient.putWithMsg<void>(Api.root, data);
}
export function roleChangeStatus(data: any) {
/**
* 修改角色状态
* @param data 参数
* @returns void
*/
export function roleChangeStatus(data: Partial<Role>) {
return requestClient.putWithMsg<void>(Api.roleChangeStatus, data);
}
/**
* 角色删除
* @param roleIds ids
* @returns void
*/
export function roleRemove(roleIds: IDS) {
return requestClient.deleteWithMsg<void>(`${Api.root}/${roleIds}`);
}
@@ -57,12 +93,20 @@ export function roleDataScope(data: any) {
return requestClient.putWithMsg<void>(Api.roleDataScope, data);
}
/**
* @deprecated 全局并没有用到这个方法
*/
export function roleOptionSelect(params?: any) {
return requestClient.get(Api.roleOptionSelect, { params });
}
export function roleAllocatedList(params: any) {
return requestClient.get(Api.roleAllocatedList, { params });
/**
* 已分配角色的用户分页
* @param params 请求参数
* @returns 分页
*/
export function roleAllocatedList(params?: PageQuery) {
return requestClient.get<PageResult<User>>(Api.roleAllocatedList, { params });
}
/**
@@ -71,28 +115,26 @@ export function roleAllocatedList(params: any) {
* @returns void
*/
export function roleUnallocatedList(params: any) {
return requestClient.get(Api.roleUnallocatedList, { params });
return requestClient.get<PageResult<User>>(Api.roleUnallocatedList, {
params,
});
}
/**
* 取消授权
* @param data {userId: 2, roleId: "2"}
* 取消用户角色授权
* @returns void
*/
export function roleAuthCancel(data: any) {
export function roleAuthCancel(data: { roleId: ID; userId: ID }) {
return requestClient.putWithMsg<void>(Api.roleAuthCancel, data);
}
/**
* 批量取消授权
* @param roleId
* @param userIds
* @param roleId 角色ID
* @param userIds 用户ID集合
* @returns void
*/
export function roleAuthCancelAll(
roleId: number | string,
userIds: number[] | string[],
) {
export function roleAuthCancelAll(roleId: ID, userIds: IDS) {
return requestClient.putWithMsg<void>(
`${Api.roleAuthCancelAll}?roleId=${roleId}&userIds=${userIds.join(',')}`,
);
@@ -100,21 +142,18 @@ export function roleAuthCancelAll(
/**
* 批量授权用户
* @param roleId
* @param userIds
* @param roleId 角色ID
* @param userIds 用户ID集合
* @returns void
*/
export function roleSelectAll(
roleId: number | string,
userIds: number[] | string[],
) {
export function roleSelectAll(roleId: ID, userIds: IDS) {
return requestClient.putWithMsg<void>(
`${Api.roleAuthSelectAll}?roleId=${roleId}&userIds=${userIds.join(',')}`,
);
}
/**
* 部门树
* 根据角色id获取部门树
* @param roleId 角色id
* @returns DeptResp
*/

View File

@@ -1,5 +1,7 @@
import type { SocialInfo } from './model';
import type { ID } from '#/api/common';
import { requestClient } from '#/api/request';
enum Api {
@@ -15,6 +17,9 @@ export function socialList() {
return requestClient.get<SocialInfo[]>(Api.socialList);
}
export function socialInfo(id: number | string) {
/**
* @deprecated 并没有用到这个方法
*/
export function socialInfo(id: ID) {
return requestClient.get(`${Api.root}/${id}`);
}

View File

@@ -13,37 +13,75 @@ enum Api {
root = '/system/tenant/package',
}
/**
* 租户套餐分页列表
* @param params 请求参数
* @returns 分页列表
*/
export function packageList(params?: PageQuery) {
return requestClient.get<PageResult<TenantPackage>>(Api.packageList, {
params,
});
}
// 下拉框
/**
* 租户套餐下拉框
* @returns 下拉框
*/
export function packageSelectList() {
return requestClient.get<TenantPackage[]>(Api.packageSelectList);
}
export function packageExport(data: any) {
/**
* 租户套餐导出
* @param data 参数
* @returns blob
*/
export function packageExport(data: Partial<TenantPackage>) {
return commonExport(Api.packageExport, data);
}
/**
* 租户套餐信息
* @param id id
* @returns 信息
*/
export function packageInfo(id: ID) {
return requestClient.get<TenantPackage>(`${Api.root}/${id}`);
}
export function packageAdd(data: any) {
/**
* 租户套餐新增
* @param data data
* @returns void
*/
export function packageAdd(data: Partial<TenantPackage>) {
return requestClient.postWithMsg<void>(Api.root, data);
}
export function packageUpdate(data: any) {
/**
* 租户套餐更新
* @param data data
* @returns void
*/
export function packageUpdate(data: Partial<TenantPackage>) {
return requestClient.putWithMsg<void>(Api.root, data);
}
export function packageChangeStatus(data: any) {
return requestClient.putWithMsg(Api.packageChangeStatus, data);
/**
* 租户套餐状态变更
* @param data data
* @returns void
*/
export function packageChangeStatus(data: Partial<TenantPackage>) {
return requestClient.putWithMsg<void>(Api.packageChangeStatus, data);
}
/**
* 租户套餐移除
* @param ids ids
* @returns void
*/
export function packageRemove(ids: IDS) {
return requestClient.deleteWithMsg(`${Api.root}/${ids}`);
return requestClient.deleteWithMsg<void>(`${Api.root}/${ids}`);
}

View File

@@ -16,14 +16,29 @@ enum Api {
tenantSyncPackage = '/system/tenant/syncTenantPackage',
}
/**
* 查询租户分页列表
* @param params 参数
* @returns 分页
*/
export function tenantList(params?: PageQuery) {
return requestClient.get<Tenant[]>(Api.tenantList, { params });
}
export function tenantExport(data: any) {
/**
* 租户导出
* @param data data
* @returns void
*/
export function tenantExport(data: Partial<Tenant>) {
return commonExport(Api.tenantExport, data);
}
/**
* 查询租户信息
* @param id id
* @returns 租户信息
*/
export function tenantInfo(id: ID) {
return requestClient.get<Tenant>(`${Api.root}/${id}`);
}
@@ -33,18 +48,33 @@ export function tenantInfo(id: ID) {
* @param data data
* @returns void
*/
export function tenantAdd(data: any) {
export function tenantAdd(data: Partial<Tenant>) {
return requestClient.postWithMsg<void>(Api.root, data, { encrypt: true });
}
export function tenantUpdate(data: any) {
/**
* 租户更新
* @param data data
* @returns void
*/
export function tenantUpdate(data: Partial<Tenant>) {
return requestClient.putWithMsg<void>(Api.root, data);
}
export function tenantStatusChange(data: any) {
/**
* 租户状态更新
* @param data data
* @returns void
*/
export function tenantStatusChange(data: Partial<Tenant>) {
return requestClient.putWithMsg(Api.tenantStatus, data);
}
/**
* 租户删除
* @param ids ids
* @returns void
*/
export function tenantRemove(ids: IDS) {
return requestClient.deleteWithMsg(`${Api.root}/${ids}`);
}
@@ -70,17 +100,12 @@ export function tenantDynamicClear() {
* 租户套餐同步
* @param tenantId 租户id
* @param packageId 套餐id
* @param showMsg 是否显示成功信息
* @returns void
*/
export function tenantSyncPackage(
tenantId: string,
packageId: string,
showMsg = true,
) {
export function tenantSyncPackage(tenantId: string, packageId: string) {
return requestClient.get<void>(Api.tenantSyncPackage, {
params: { packageId, tenantId },
successMessageMode: showMsg ? 'message' : 'none',
successMessageMode: 'message',
});
}

View File

@@ -9,7 +9,7 @@ export interface Tenant {
id: number;
intro: string;
licenseNumber?: any;
packageId?: string;
packageId: string;
remark?: string;
status: string;
tenantId: string;

View File

@@ -38,7 +38,7 @@ export function userList(params?: PageQuery) {
* @param data data
* @returns blob
*/
export function userExport(data: any) {
export function userExport(data: Partial<User>) {
return commonExport(Api.userExport, data);
}
@@ -91,7 +91,7 @@ export function findUserInfo(userId?: ID) {
* @param data data
* @returns void
*/
export function userAdd(data: any) {
export function userAdd(data: Partial<User>) {
return requestClient.postWithMsg<void>(Api.root, data);
}
@@ -100,7 +100,7 @@ export function userAdd(data: any) {
* @param data data
* @returns void
*/
export function userUpdate(data: any) {
export function userUpdate(data: Partial<User>) {
return requestClient.putWithMsg<void>(Api.root, data);
}
@@ -109,7 +109,7 @@ export function userUpdate(data: any) {
* @param data data
* @returns void
*/
export function userStatusChange(data: any) {
export function userStatusChange(data: Partial<User>) {
return requestClient.putWithMsg<void>(Api.userStatusChange, data);
}

View File

@@ -1,6 +1,6 @@
import type { GenInfo } from './model';
import type { ID, IDS } from '#/api/common';
import type { ID, IDS, PageQuery } from '#/api/common';
import { ContentTypeEnum } from '#/api/helper';
import { requestClient } from '#/api/request';
@@ -19,7 +19,7 @@ enum Api {
syncDb = '/tool/gen/synchDb',
}
// 查询代码生成列表
export function generatedList(params: any) {
export function generatedList(params?: PageQuery) {
return requestClient.get(Api.generatedList, { params });
}
@@ -29,7 +29,7 @@ export function genInfo(tableId: ID) {
}
// 查询数据库列表
export function readyToGenList(params: any) {
export function readyToGenList(params?: PageQuery) {
return requestClient.get(Api.readyToGenList, { params });
}
@@ -63,6 +63,7 @@ export function editSave(data: any) {
export function genRemove(tableIds: IDS) {
return requestClient.deleteWithMsg(`${Api.root}/${tableIds}`);
}
// 预览代码
export function previewCode(tableId: ID) {
return requestClient.get<{ [key: string]: string }>(

View File

@@ -1,9 +1,22 @@
import type { CategoryForm, CategoryQuery, CategoryVO } from './model';
import type {
CategoryForm,
CategoryQuery,
CategoryTree,
CategoryVO,
} from './model';
import type { ID, IDS } from '#/api/common';
import { requestClient } from '#/api/request';
/**
* 获取流程分类树列表
* @returns tree
*/
export function categoryTree() {
return requestClient.get<CategoryTree[]>('/workflow/category/categoryTree');
}
/**
* 查询流程分类列表
* @param params

View File

@@ -30,6 +30,7 @@ export interface CategoryVO {
* 子对象
*/
children: CategoryVO[];
key: string;
}
export interface CategoryForm extends BaseEntity {
@@ -85,3 +86,12 @@ export interface CategoryQuery {
*/
params?: any;
}
export interface CategoryTree {
id: number;
parentId: number;
label: string;
weight: number;
children: CategoryTree[];
key: string;
}

View File

@@ -0,0 +1,155 @@
import type { ProcessDefinition } from './model';
import type { ID, IDS, PageQuery, PageResult } from '#/api/common';
import { requestClient } from '#/api/request';
/**
* 全部的流程定义
* @param params 查询参数
* @returns 分页
*/
export function workflowDefinitionList(params?: PageQuery) {
return requestClient.get<PageResult<ProcessDefinition>>(
'/workflow/definition/list',
{ params },
);
}
/**
* 未发布的流程定义
* @param params 查询参数
* @returns 分页
*/
export function unPublishList(params?: PageQuery) {
return requestClient.get<PageResult<ProcessDefinition>>(
'/workflow/definition/unPublishList',
{ params },
);
}
/**
* 获取历史流程定义列表
* @param flowCode
* @returns ProcessDefinition[]
*/
export function getHisListByKey(flowCode: string) {
return requestClient.get<ProcessDefinition[]>(
`/workflow/definition/getHisListByKey/${flowCode}`,
);
}
/**
* 获取流程定义详细信息
* @param id id
* @returns ProcessDefinition
*/
export function workflowDefinitionInfo(id: ID) {
return requestClient.get<ProcessDefinition>(`/workflow/definition/${id}`);
}
/**
* 新增流程定义
* @param data
*/
export function workflowDefinitionAdd(data: any) {
return requestClient.postWithMsg<void>('/workflow/definition', data);
}
/**
* 更新流程定义
* @param data
*/
export function workflowDefinitionUpdate(data: any) {
return requestClient.putWithMsg<void>('/workflow/definition', data);
}
/**
* 发布流程定义
* @param id id
* @returns boolean
*/
export function workflowDefinitionPublish(id: ID) {
return requestClient.putWithMsg<boolean>(
`/workflow/definition/publish/${id}`,
);
}
/**
* 取消发布流程定义
* @param id id
* @returns boolean
*/
export function workflowDefinitionUnPublish(id: ID) {
return requestClient.putWithMsg<boolean>(
`/workflow/definition/unPublish/${id}`,
);
}
/**
* 删除流程定义
* @param ids idList
*/
export function workflowDefinitionDelete(ids: IDS) {
return requestClient.deleteWithMsg<void>(`/workflow/definition/${ids}`);
}
/**
* 复制流程定义
* @param id id
*/
export function workflowDefinitionCopy(id: ID) {
return requestClient.postWithMsg<void>(`/workflow/definition/copy/${id}`);
}
/**
* 导入流程定义
* @returns boolean
*/
export function workflowDefinitionImport(data: {
category: ID;
file: Blob | File;
}) {
return requestClient.postWithMsg<boolean>(
'/workflow/definition/importDef',
data,
{ headers: { 'Content-Type': 'multipart/form-data' } },
);
}
/**
* 导出流程定义
* @param id id
* @returns blob
*/
export function workflowDefinitionExport(id: ID) {
return requestClient.postWithMsg<Blob>(
`/workflow/definition/exportDef/${id}`,
{},
{
responseType: 'blob',
isTransformResponse: false,
},
);
}
/**
* 获取流程定义xml字符串
* @param id id
* @returns xml
*/
export function workflowDefinitionXml(id: ID) {
return requestClient.get<string>(`/workflow/definition/xmlString/${id}`);
}
/**
* 激活/挂起流程定义
* @param id 流程定义id
* @param active 激活/挂起
* @returns boolean
*/
export function workflowDefinitionActive(id: ID, active: boolean) {
return requestClient.putWithMsg<boolean>(
`/workflow/definition/active/${id}?active=${active}`,
);
}

View File

@@ -0,0 +1,19 @@
export interface ProcessDefinition {
id: string;
createTime: string;
updateTime: string;
tenantId: string;
delFlag: string;
flowCode: string;
flowName: string;
category: string;
categoryName: string;
version: string;
isPublish: number;
formCustom: string;
formPath: string;
activityStatus: number;
listenerType?: any;
listenerPath?: any;
ext?: any;
}

View File

@@ -0,0 +1,120 @@
import type { TaskInfo } from '../task/model';
import type { FlowInfoResponse } from './model';
import type { ID, IDS, PageQuery, PageResult } from '#/api/common';
import { requestClient } from '#/api/request';
/**
* @param businessId 业务ID
* @returns TaskInfo
*/
export function getTaskByBusinessId(businessId: string) {
return requestClient.get<TaskInfo>(
`/workflow/instance/getInfo/${businessId}`,
);
}
/**
* 分页查询正在运行的流程实例
* @param params
* @returns
*/
export function pageByRunning(params?: PageQuery) {
return requestClient.get('/workflow/instance/pageByRunning', { params });
}
/**
* pageByFinish
* @param params
* @returns
*/
export function pageByFinish(params?: PageQuery) {
return requestClient.get('/workflow/instance/pageByFinish', { params });
}
/**
* 按照业务id删除流程实例
* @param businessIds 业务id
*/
export function deleteByBusinessIds(businessIds: IDS) {
return requestClient.deleteWithMsg<void>(
`/workflow/instance/deleteByBusinessIds${businessIds}`,
);
}
/**
* 按照实例id删除流程实例
* @param instanceIds 实例id
*/
export function deleteByInstanceIds(instanceIds: IDS) {
return requestClient.deleteWithMsg<void>(
`/workflow/instance/deleteByInstanceIds/${instanceIds}`,
);
}
/**
* 撤销流程
* @param data
*/
export function cancelProcessApply(data: { businessId: ID; message?: string }) {
return requestClient.putWithMsg<void>(
'/workflow/instance/cancelProcessApply',
data,
);
}
/**
* 激活/挂起流程实例
* @param instanceId
* @param active
*/
export function workflowInstanceActive(instanceId: ID, active: boolean) {
return requestClient.putWithMsg<void>(
`/workflow/instance/active/${instanceId}?active=${active}`,
);
}
/**
* 获取当前登录人发起的流程实例
* @param params
* @returns PageResult<Flow>
*/
export function pageByCurrent(params?: PageQuery) {
return requestClient.get<PageResult<TaskInfo>>(
'/workflow/instance/pageByCurrent',
{ params },
);
}
/**
* 获取流程图,流程记录
* @param businessId 业务标识
* @returns 流程图,流程记录
*/
export function flowInfo(businessId: string) {
return requestClient.get<FlowInfoResponse>(
`/workflow/instance/flowImage/${businessId}`,
);
}
/**
* 获取流程变量
* @param instanceId
* @returns Map<string,any>
*/
export function instanceVariable(instanceId: string) {
return requestClient.get<Record<string, any>>(
`/workflow/instance/variable/${instanceId}`,
);
}
/**
* 作废流程
*/
export function workflowInstanceInvalid(data: {
comment?: string;
id: string;
}) {
return requestClient.postWithMsg<void>('/workflow/instance/invalid', data);
}

View File

@@ -0,0 +1,41 @@
export interface Flow {
id: string;
createTime: string;
updateTime: string;
tenantId: string;
delFlag: string;
definitionId: string;
flowName?: any;
instanceId: string;
taskId: string;
cooperateType: number;
cooperateTypeName: string;
businessId?: any;
nodeCode: string;
nodeName: string;
nodeType: number;
targetNodeCode: string;
targetNodeName: string;
approver: string;
approveName: string;
collaborator?: any;
permissionList?: any;
skipType: string;
flowStatus: string;
flowTaskStatus?: any;
flowStatusName?: any;
message: string;
ext: null | string;
createBy?: any;
formCustom: string;
formPath: string;
flowCode?: any;
version?: any;
runDuration: string;
nickName?: any;
}
export interface FlowInfoResponse {
image: string;
list: Flow[];
}

View File

@@ -0,0 +1,158 @@
import type {
CompleteTaskReqData,
StartWorkFlowReqData,
TaskInfo,
TaskOperationData,
TaskOperationType,
} from './model';
import type { ID, IDS, PageQuery, PageResult } from '#/api/common';
import { requestClient } from '#/api/request';
/**
* 启动任务
* @param data
*/
export function startWorkFlow(data: StartWorkFlowReqData) {
return requestClient.post<{
processInstanceId: string;
taskId: string;
}>('/workflow/task/startWorkFlow', data);
}
/**
* 办理任务
* @param data
*/
export function completeTask(data: CompleteTaskReqData) {
return requestClient.postWithMsg<void>('/workflow/task/completeTask', data);
}
/**
* 查询当前用户的待办任务
* @param params
*/
export function pageByTaskWait(params?: PageQuery) {
return requestClient.get<PageResult<TaskInfo>>(
'/workflow/task/pageByTaskWait',
{ params },
);
}
/**
* 查询当前用户的已办任务
* @param params
*/
export function pageByTaskFinish(params?: PageQuery) {
return requestClient.get<PageResult<TaskInfo>>(
'/workflow/task/pageByTaskFinish',
{ params },
);
}
/**
* 查询所有待办任务
* @param params
*/
export function pageByAllTaskWait(params?: PageQuery) {
return requestClient.get<PageResult<TaskInfo>>(
'/workflow/task/pageByAllTaskWait',
{ params },
);
}
/**
* 查询已办任务
* @param params
*/
export function pageByAllTaskFinish(params?: PageQuery) {
return requestClient.get<PageResult<TaskInfo>>(
'/workflow/task/pageByAllTaskFinish',
{ params },
);
}
/**
* 查询当前用户的抄送
* @param params
*/
export function pageByTaskCopy(params?: PageQuery) {
return requestClient.get<PageResult<TaskInfo>>(
'/workflow/task/pageByTaskCopy',
{ params },
);
}
/**
* 根据taskId查询代表任务
* @param taskId 任务id
* @returns info
*/
export function getTaskByTaskId(taskId: string) {
return requestClient.get<TaskInfo>(`/workflow/task/getTask/${taskId}`);
}
/**
* 终止任务
*/
export function terminationTask(data: { comment?: string; taskId: string }) {
return requestClient.postWithMsg<void>(
'/workflow/task/terminationTask',
data,
);
}
/**
* 任务操作
* @param taskOperationData 参数
* @param taskOperation 操作类型,委派 delegateTask、转办 transferTask、加签 addSignature、减签 reductionSignature
*/
export function taskOperation(
taskOperationData: TaskOperationData,
taskOperation: TaskOperationType,
) {
return requestClient.postWithMsg<void>(
`/workflow/task/taskOperation/${taskOperation}`,
taskOperationData,
);
}
/**
* 修改任务办理人
* @param taskIdList 任务id
* @param userId 办理人id
*/
export function updateAssignee(taskIdList: IDS, userId: ID) {
return requestClient.putWithMsg<void>(
`/workflow/task/updateAssignee/${userId}`,
taskIdList,
);
}
/**
* 驳回审批
* @param data 参数
*/
export function backProcess(data: any) {
return requestClient.postWithMsg<void>('/workflow/task/backProcess', data);
}
/**
* 获取可驳回节点
* @param definitionId 流程定义ID
* @param nodeCode 当前节点编码
*/
export function getBackTaskNode(definitionId: string, nodeCode: string) {
return requestClient.get<{ nodeCode: string; nodeName: string }[]>(
`/workflow/task/getBackTaskNode/${definitionId}/${nodeCode}`,
);
}
/**
* 获取当前任务的所有办理人
* @param taskId 任务id
*/
export function currentTaskAllUser(taskId: ID) {
return requestClient.get<any>(`/workflow/task/currentTaskAllUser/${taskId}`);
}

View File

@@ -0,0 +1,74 @@
export interface TaskInfo {
id: string;
categoryName: string;
createTime: string;
updateTime: string;
tenantId: string;
delFlag?: any;
definitionId: string;
instanceId: string;
flowName: string;
businessId: string;
nodeCode: string;
nodeName: string;
nodeType: number;
permissionList?: any;
userList?: any;
formCustom: string;
formPath?: any;
flowCode: string;
version: string;
flowStatus: string;
flowStatusName: string;
assigneeIds: string;
assigneeNames: string;
processedBy: string;
type: string;
nodeRatio?: string;
createBy: string;
createByName: string;
targetNodeName?: string;
}
export interface CompleteTaskReqData {
messageType: string[];
flowCopyList: { userId: string; userName: string }[];
taskId: ID;
taskVariables: Record<string, any>;
variables: any;
// 附件ID 1,2,3,4形式
fileId?: string;
}
export interface StartWorkFlowReqData {
/**
* 业务ID
*/
businessId: ID;
/**
* flowCode
*/
flowCode: string;
/**
* 流程变量
*/
variables: Record<string, any>;
}
export interface TaskOperationData {
message?: string;
taskId: ID;
// 单个操作人
userId?: ID;
// 多个操作人
userIds?: IDS;
}
/**
* 操作类型,委派 delegateTask、转办 transferTask、加签 addSignature、减签 reductionSignature
*/
export type TaskOperationType =
| 'addSignature'
| 'delegateTask'
| 'reductionSignature'
| 'transferTask';

View File

@@ -1,6 +1,7 @@
import { createApp, watchEffect } from 'vue';
import { registerAccessDirective } from '@vben/access';
import { initTippy } from '@vben/common-ui';
import { preferences } from '@vben/preferences';
import { initStores } from '@vben/stores';
import '@vben/styles';
@@ -19,6 +20,15 @@ async function bootstrap(namespace: string) {
// 初始化组件适配器
await initComponentAdapter();
// // 设置弹窗的默认配置
// setDefaultModalProps({
// fullscreenButton: false,
// });
// // 设置抽屉的默认配置
// setDefaultDrawerProps({
// zIndex: 1020,
// });
const app = createApp(App);
// 全局组件
@@ -33,6 +43,9 @@ async function bootstrap(namespace: string) {
// 安装权限指令
registerAccessDirective(app);
// 初始化 tippy
initTippy(app);
// 配置路由及路由守卫
app.use(router);

View File

@@ -1,4 +1,3 @@
export { default as CropperImage } from './src/cropper.vue';
export { default as CropperAvatar } from './src/cropper-avatar.vue';
export { default as CropperImage } from './src/cropper.vue';
export type { Cropper } from './src/typing';

View File

@@ -1,18 +1,14 @@
<script lang="ts" setup>
import {
computed,
type CSSProperties,
type PropType,
ref,
unref,
watch,
watchEffect,
} from 'vue';
import type { ButtonProps } from 'ant-design-vue';
import type { CSSProperties, PropType } from 'vue';
import { computed, ref, unref, watch, watchEffect } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { $t as t } from '@vben/locales';
import { type ButtonProps, message } from 'ant-design-vue';
import { message } from 'ant-design-vue';
import cropperModal from './cropper-modal.vue';

View File

@@ -1,7 +1,9 @@
<script lang="ts" setup>
import type { PropType } from 'vue';
import type { CropendResult, Cropper } from './typing';
import { type PropType, ref } from 'vue';
import { ref } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { $t as t } from '@vben/locales';

View File

@@ -1,5 +1,6 @@
<script lang="ts" setup>
import type { CSSProperties, PropType } from 'vue';
import { computed, onMounted, onUnmounted, ref, unref, useAttrs } from 'vue';
import { useDebounceFn } from '@vueuse/core';

View File

@@ -2,19 +2,11 @@
import type { CardSize } from 'ant-design-vue/es/card/Card';
import type { DescriptionsProps } from 'ant-design-vue/es/descriptions';
import type { CSSProperties, PropType, Slots } from 'vue';
import type { DescInstance, DescItem, DescriptionProps } from './typing';
import {
computed,
type CSSProperties,
defineComponent,
type PropType,
ref,
type Slots,
toRefs,
unref,
useAttrs,
} from 'vue';
import { computed, defineComponent, ref, toRefs, unref, useAttrs } from 'vue';
import { Card, Descriptions } from 'ant-design-vue';
import { get, isFunction } from 'lodash-es';

View File

@@ -1,9 +1,10 @@
import type { Recordable } from '@vben/types';
import type { DescriptionsProps } from 'ant-design-vue/es/descriptions';
import type { JSX } from 'vue/jsx-runtime';
import type { CSSProperties, VNode } from 'vue';
import type { Recordable } from '@vben/types';
export interface DescItem {
labelMinWidth?: number;
contentMinWidth?: number;

View File

@@ -1,4 +1,4 @@
import { type VNode } from 'vue';
import type { VNode } from 'vue';
import { Tag } from 'ant-design-vue';

View File

@@ -1,3 +1,4 @@
<!-- eslint-disable eqeqeq -->
<script setup lang="ts">
import type { DictData } from '#/api/system/dict/dict-data-model';
@@ -17,7 +18,6 @@ const props = withDefaults(defineProps<Props>(), {
});
const color = computed<string>(() => {
// eslint-disable-next-line eqeqeq
const current = props.dicts.find((item) => item.dictValue == props.value);
const listClass = current?.listClass ?? '';
// 是否为默认的颜色
@@ -31,13 +31,11 @@ const color = computed<string>(() => {
});
const cssClass = computed<string>(() => {
// eslint-disable-next-line eqeqeq
const current = props.dicts.find((item) => item.dictValue == props.value);
return current?.cssClass ?? '';
});
const label = computed<number | string>(() => {
// eslint-disable-next-line eqeqeq
const current = props.dicts.find((item) => item.dictValue == props.value);
return current?.dictLabel ?? 'unknown';
});

View File

@@ -1 +1,2 @@
export { default as OptionsTag } from './src/options-tag.vue';
export { default as TableSwitch } from './src/table-switch.vue';

View File

@@ -0,0 +1,21 @@
<script setup lang="tsx">
import { computed } from 'vue';
import { Tag } from 'ant-design-vue';
defineOptions({ name: 'OptionsTag' });
const props = defineProps<{
options: { color?: string; label: string; value: number | string }[];
value: number | string;
}>();
const found = computed(() =>
props.options.find((item) => item.value === props.value),
);
</script>
<template>
<Tag v-if="found" :color="found.color">{{ found.label }}</Tag>
<span v-else>未知</span>
</template>

View File

@@ -2,6 +2,10 @@
import type { IPropTypes } from '@tinymce/tinymce-vue/lib/cjs/main/ts/components/EditorPropTypes';
import type { Editor as EditorType } from 'tinymce/tinymce';
import type { PropType } from 'vue';
import type { UploadResult } from '#/api/core/upload';
import {
computed,
nextTick,
@@ -9,7 +13,6 @@ import {
onBeforeUnmount,
onDeactivated,
onMounted,
type PropType,
ref,
unref,
useAttrs,
@@ -22,7 +25,7 @@ import { buildShortUUID } from '@vben/utils';
import Editor from '@tinymce/tinymce-vue';
import { isNumber } from 'lodash-es';
import { uploadApi, type UploadResult } from '#/api/core/upload';
import { uploadApi } from '#/api/core/upload';
import { bindHandlers } from './helper';
import ImgUpload from './img-upload.vue';

View File

@@ -4,9 +4,9 @@ const validEvents = new Set([
'onBeforeAddUndo',
'onBeforeExecCommand',
'onBeforeGetContent',
'onBeforePaste',
'onBeforeRenderUI',
'onBeforeSetContent',
'onBeforePaste',
'onBlur',
'onChange',
'onClearUndos',
@@ -42,8 +42,8 @@ const validEvents = new Set([
'onMouseOver',
'onMouseUp',
'onNodeChange',
'onObjectResizeStart',
'onObjectResized',
'onObjectResizeStart',
'onObjectSelected',
'onPaste',
'onPostProcess',

View File

@@ -1 +1,2 @@
export { default as MenuSelectTable } from './src/menu-select-table.vue';
export { default as TreeSelectPanel } from './src/tree-select-panel.vue';

View File

@@ -0,0 +1,85 @@
import type { VxeGridProps } from '#/adapter/vxe-table';
import type { ID } from '#/api/common';
import type { MenuOption } from '#/api/system/menu/model';
import { h, markRaw } from 'vue';
import { FolderIcon, MenuIcon, OkButtonIcon, VbenIcon } from '@vben/icons';
export interface Permission {
checked: boolean;
id: ID;
label: string;
}
export interface MenuPermissionOption extends MenuOption {
permissions: Permission[];
}
const menuTypes = {
C: { icon: markRaw(MenuIcon), value: '菜单' },
F: { icon: markRaw(OkButtonIcon), value: '按钮' },
M: { icon: markRaw(FolderIcon), value: '目录' },
};
export const nodeOptions = [
{ label: '节点关联', value: true },
{ label: '节点独立', value: false },
];
export const columns: VxeGridProps['columns'] = [
{
type: 'checkbox',
title: '菜单名称',
field: 'label',
treeNode: true,
headerAlign: 'left',
align: 'left',
width: 230,
},
{
title: '图标',
field: 'icon',
width: 80,
slots: {
default: ({ row }) => {
if (row?.icon === '#') {
return '';
}
return (
<span class={'flex justify-center'}>
<VbenIcon icon={row.icon} />
</span>
);
},
},
},
{
title: '类型',
field: 'menuType',
width: 80,
slots: {
default: ({ row }) => {
const current = menuTypes[row.menuType as 'C' | 'F' | 'M'];
if (!current) {
return '未知';
}
return (
<span class="flex items-center justify-center gap-1">
{h(current.icon, { class: 'size-[18px]' })}
<span>{current.value}</span>
</span>
);
},
},
},
{
title: '权限标识',
field: 'permissions',
headerAlign: 'left',
align: 'left',
slots: {
default: 'permissions',
},
},
];

View File

@@ -0,0 +1,133 @@
import type { MenuPermissionOption } from './data';
import type { useVbenVxeGrid } from '#/adapter/vxe-table';
import type { MenuOption } from '#/api/system/menu/model';
import { eachTree, treeToList } from '@vben/utils';
import { difference, isEmpty, isUndefined } from 'lodash-es';
/**
* 权限列设置是否全选
* @param record 行记录
* @param checked 是否选中
*/
export function setPermissionsChecked(
record: MenuPermissionOption,
checked: boolean,
) {
if (record?.permissions?.length > 0) {
// 全部设置为选中
record.permissions.forEach((permission) => {
permission.checked = checked;
});
}
}
/**
* 设置当前行 & 所有子节点选中状态
* @param record 行
* @param checked 是否选中
*/
export function rowAndChildrenChecked(
record: MenuPermissionOption,
checked: boolean,
) {
// 当前行选中
setPermissionsChecked(record, checked);
// 所有子节点选中
record?.children?.forEach?.((permission) => {
rowAndChildrenChecked(permission as MenuPermissionOption, checked);
});
}
/**
* void方法 会直接修改原始数据
* 将树结构转为 tree+permissions结构
* @param menus 后台返回的menu
*/
export function menusWithPermissions(menus: MenuOption[]) {
eachTree(menus, (item: MenuPermissionOption) => {
if (item.children && item.children.length > 0) {
/**
* 所有为按钮的节点提取出来
* 需要注意 这里需要过滤目录下直接是按钮的情况item.menuType !== 'M'
* 将按钮往children添加而非加到permissions
*/
const permissions = item.children.filter(
(child: MenuOption) => child.menuType === 'F' && item.menuType !== 'M',
);
// 取差集
const diffCollection = difference(item.children, permissions);
// 更新后的children 即去除按钮
item.children = diffCollection;
// permissions作为字段添加到item
const permissionsArr = permissions.map((permission) => {
return {
id: permission.id,
label: permission.label,
checked: false,
};
});
item.permissions = permissionsArr;
}
});
}
/**
* 设置表格选中
* @param checkedKeys 选中的keys
* @param menus 菜单 转换后的菜单
* @param tableApi api
* @param association 是否节点关联
*/
export function setTableChecked(
checkedKeys: (number | string)[],
menus: MenuPermissionOption[],
tableApi: ReturnType<typeof useVbenVxeGrid>['1'],
association: boolean,
) {
// tree转list
const menuList: MenuPermissionOption[] = treeToList(menus);
// 拿到勾选的行数据
let checkedRows = menuList.filter((item) => checkedKeys.includes(item.id));
/**
* 节点独立切换到节点关联 只需要最末尾的数据 即children为空
*/
if (!association) {
checkedRows = checkedRows.filter(
(item) => isUndefined(item.children) || isEmpty(item.children),
);
}
// 设置行选中 & permissions选中
checkedRows.forEach((item) => {
tableApi.grid.setCheckboxRow(item, true);
if (item?.permissions?.length > 0) {
item.permissions.forEach((permission) => {
if (checkedKeys.includes(permission.id)) {
permission.checked = true;
}
});
}
});
/**
* 节点独立切换到节点关联
* 勾选后还需要过滤权限没有任何勾选的情况 这时候取消行的勾选
*/
if (!association) {
const emptyRows = checkedRows.filter((item) => {
if (isUndefined(item.permissions) || isEmpty(item.permissions)) {
return false;
}
return item.permissions.every(
(permission) => permission.checked === false,
);
});
// 设置为不选中
tableApi.grid.setCheckboxRow(emptyRows, false);
}
}

View File

@@ -0,0 +1,62 @@
/* eslint-disable @typescript-eslint/no-non-null-assertion */
import type { TourProps } from 'ant-design-vue';
import { defineComponent, ref } from 'vue';
import { useLocalStorage } from '@vueuse/core';
import { Tour } from 'ant-design-vue';
/**
* 全屏引导
* @returns value
*/
export function useFullScreenGuide() {
const open = ref(false);
/**
* 是否已读 只显示一次
*/
const read = useLocalStorage('menu_select_fullscreen_read', false);
function openGuide() {
if (!read.value) {
open.value = true;
}
}
function closeGuide() {
open.value = false;
read.value = true;
}
const steps: TourProps['steps'] = [
{
title: '提示',
description: '点击这里可以全屏',
target: () =>
document.querySelector(
'div#menu-select-table .vxe-tools--operate > button[title="全屏"]',
)!,
},
];
const FullScreenGuide = defineComponent({
name: 'FullScreenGuide',
inheritAttrs: false,
setup() {
return () => (
<Tour
onClose={closeGuide}
open={open.value}
steps={steps}
zIndex={9999}
/>
);
},
});
return {
FullScreenGuide,
openGuide,
closeGuide,
};
}

View File

@@ -0,0 +1,412 @@
<!--
不兼容也不会兼容一些错误用法
比如: 菜单下放目录 菜单下放菜单
比如: 按钮下放目录 按钮下放菜单 按钮下放按钮
-->
<script setup lang="tsx">
import type { RadioChangeEvent } from 'ant-design-vue';
import type { MenuPermissionOption } from './data';
import type { VxeGridProps } from '#/adapter/vxe-table';
import type { MenuOption } from '#/api/system/menu/model';
import { nextTick, onMounted, ref, shallowRef, watch } from 'vue';
import { cloneDeep, findGroupParentIds } from '@vben/utils';
import { Alert, Checkbox, RadioGroup, Space } from 'ant-design-vue';
import { uniq } from 'lodash-es';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { columns, nodeOptions } from './data';
import {
menusWithPermissions,
rowAndChildrenChecked,
setPermissionsChecked,
setTableChecked,
} from './helper';
import { useFullScreenGuide } from './hook';
defineOptions({
name: 'MenuSelectTable',
inheritAttrs: false,
});
const props = withDefaults(
defineProps<{
checkedKeys: (number | string)[];
defaultExpandAll?: boolean;
menus: MenuOption[];
}>(),
{
/**
* 是否默认展开全部
*/
defaultExpandAll: true,
/**
* 注意这里不是双向绑定 需要调用getCheckedKeys实例方法来获取真正选中的节点
*/
checkedKeys: () => [],
},
);
/**
* 是否节点关联
*/
const association = defineModel('association', {
type: Boolean,
default: true,
});
const gridOptions: VxeGridProps = {
checkboxConfig: {
// checkbox显示的字段
labelField: 'label',
// 是否严格模式 即节点不关联
checkStrictly: !association.value,
},
size: 'small',
columns,
height: 'auto',
keepSource: true,
pagerConfig: {
enabled: false,
},
proxyConfig: {
enabled: false,
},
toolbarConfig: {
// 自定义列
custom: false,
// 最大化
zoom: true,
// 刷新
refresh: false,
},
rowConfig: {
isHover: false,
isCurrent: false,
keyField: 'id',
},
/**
* 开启虚拟滚动
* 数据量小可以选择关闭
* 如果遇到样式问题(空白、错位 滚动等)可以选择关闭虚拟滚动
*/
scrollY: {
enabled: true,
gt: 0,
},
treeConfig: {
parentField: 'parentId',
rowField: 'id',
transform: false,
},
// 溢出换行显示
showOverflow: false,
};
/**
* 用于界面显示选中的数量
*/
const checkedNum = ref(0);
/**
* 更新选中的数量
*/
function updateCheckedNumber() {
checkedNum.value = getCheckedKeys().length;
}
const [BasicTable, tableApi] = useVbenVxeGrid({
gridOptions,
gridEvents: {
// 勾选事件
checkboxChange: (params) => {
// 选中还是取消选中
const checked = params.checked;
// 行
const record = params.row;
if (association.value) {
// 节点关联
// 设置所有子节点选中状态
rowAndChildrenChecked(record, checked);
} else {
// 节点独立
// 点行会勾选/取消全部权限 点权限不会勾选行
setPermissionsChecked(record, checked);
}
updateCheckedNumber();
},
// 全选事件
checkboxAll: (params) => {
const records = params.$grid.getData();
records.forEach((item) => {
rowAndChildrenChecked(item, params.checked);
});
updateCheckedNumber();
},
},
});
/**
* 设置表格选中
* @param menus menu
* @param keys 选中的key
* @param triggerOnchange 节点独立情况 不需要触发onChange(false)
*/
function setCheckedByKeys(
menus: MenuPermissionOption[],
keys: (number | string)[],
triggerOnchange: boolean,
) {
menus.forEach((item) => {
// 设置行选中
if (keys.includes(item.id)) {
tableApi.grid.setCheckboxRow(item, true);
}
// 设置权限columns选中
if (item.permissions && item.permissions.length > 0) {
// 遍历 设置勾选
item.permissions.forEach((permission) => {
if (keys.includes(permission.id)) {
permission.checked = true;
// 手动触发onChange来选中 节点独立情况不需要处理
triggerOnchange && handlePermissionChange(item);
}
});
}
// 设置children选中
if (item.children && item.children.length > 0) {
setCheckedByKeys(item.children as any, keys, triggerOnchange);
}
});
}
const { FullScreenGuide, openGuide } = useFullScreenGuide();
onMounted(() => {
/**
* 加载表格数据 转为指定结构
*/
watch(
() => props.menus,
async (menus) => {
const clonedMenus = cloneDeep(menus);
menusWithPermissions(clonedMenus);
// console.log(clonedMenus);
await tableApi.grid.loadData(clonedMenus);
// 展开全部 默认true
if (props.defaultExpandAll) {
await nextTick();
setExpandOrCollapse(true);
}
},
);
/**
* 节点关联变动 更新表格勾选效果
*/
watch(association, (value) => {
tableApi.setGridOptions({
checkboxConfig: {
checkStrictly: !value,
},
});
});
/**
* checkedKeys依赖menus
* 要注意加载顺序
* !!!要在外部确保menus先加载!!!
*/
watch(
() => props.checkedKeys,
(value) => {
const allCheckedKeys = uniq([...value]);
// 获取表格data 如果checkedKeys在menus的watch之前触发 这里会拿到空 导致勾选异常
const records = tableApi.grid.getData();
setCheckedByKeys(records, allCheckedKeys, association.value);
updateCheckedNumber();
// 全屏引导
setTimeout(openGuide, 1000);
},
);
});
// 缓存上次(切换节点关系前)选中的keys
const lastCheckedKeys = shallowRef<(number | string)[]>([]);
/**
* 节点关联变动 事件
*/
async function handleAssociationChange(e: RadioChangeEvent) {
lastCheckedKeys.value = getCheckedKeys();
// 清空全部permissions选中
const records = tableApi.grid.getData();
records.forEach((item) => {
rowAndChildrenChecked(item, false);
});
// 需要清空全部勾选
await tableApi.grid.clearCheckboxRow();
// 滚动到顶部
await tableApi.grid.scrollTo(0, 0);
// 节点切换 不同的选中
setTableChecked(lastCheckedKeys.value, records, tableApi, !e.target.value);
updateCheckedNumber();
}
/**
* 全部展开/折叠
* @param expand 是否展开
*/
function setExpandOrCollapse(expand: boolean) {
tableApi.grid?.setAllTreeExpand(expand);
}
/**
* 权限列表 checkbox勾选的事件
* @param row 行
*/
function handlePermissionChange(row: any) {
// 节点关联
if (association.value) {
const checkedPermissions = row.permissions.filter(
(item: any) => item.checked === true,
);
// 有一条选中 则整个行选中
if (checkedPermissions.length > 0) {
tableApi.grid.setCheckboxRow(row, true);
}
// 无任何选中 则整个行不选中
if (checkedPermissions.length === 0) {
tableApi.grid.setCheckboxRow(row, false);
}
}
// 节点独立 不处理
updateCheckedNumber();
}
/**
* 获取勾选的key
* @param records 行记录列表
* @param addCurrent 是否添加当前行的id
*/
function getKeys(records: MenuPermissionOption[], addCurrent: boolean) {
const allKeys: (number | string)[] = [];
records.forEach((item) => {
// 处理children
if (item.children && item.children.length > 0) {
const keys = getKeys(item.children as MenuPermissionOption[], addCurrent);
allKeys.push(...keys);
} else {
// 当前行的id
addCurrent && allKeys.push(item.id);
// 当前行权限id 获取已经选中的
if (item.permissions && item.permissions.length > 0) {
const ids = item.permissions
.filter((m) => m.checked === true)
.map((m) => m.id);
allKeys.push(...ids);
}
}
});
return uniq(allKeys);
}
/**
* 获取选中的key
*/
function getCheckedKeys() {
// 节点关联
if (association.value) {
const records = tableApi?.grid?.getCheckboxRecords?.() ?? [];
// 子节点
const nodeKeys = getKeys(records, true);
// 所有父节点
const parentIds = findGroupParentIds(props.menus, nodeKeys as number[]);
// 拼接 去重
const realKeys = uniq([...parentIds, ...nodeKeys]);
return realKeys;
}
// 节点独立
// 勾选的行
const records = tableApi?.grid?.getCheckboxRecords?.() ?? [];
// 全部数据 用于获取permissions
const allRecords = tableApi?.grid?.getData?.() ?? [];
// 表格已经选中的行ids
const checkedIds = records.map((item) => item.id);
// 所有已经勾选权限的ids
const permissionIds = getKeys(allRecords, false);
// 合并 去重
const allIds = uniq([...checkedIds, ...permissionIds]);
return allIds;
}
/**
* 暴露给外部使用 获取已选中的key
*/
defineExpose({
getCheckedKeys,
});
</script>
<template>
<div class="flex h-full flex-col" id="menu-select-table">
<BasicTable>
<template #toolbar-actions>
<RadioGroup
v-model:value="association"
:options="nodeOptions"
button-style="solid"
option-type="button"
@change="handleAssociationChange"
/>
<Alert class="mx-2" type="info">
<template #message>
<div>
已选中
<span class="text-primary mx-1 font-semibold">
{{ checkedNum }}
</span>
个节点
</div>
</template>
</Alert>
</template>
<template #toolbar-tools>
<Space>
<a-button @click="setExpandOrCollapse(false)">
{{ $t('pages.common.collapse') }}
</a-button>
<a-button @click="setExpandOrCollapse(true)">
{{ $t('pages.common.expand') }}
</a-button>
</Space>
</template>
<template #permissions="{ row }">
<div class="flex flex-wrap gap-x-3 gap-y-1">
<Checkbox
v-for="permission in row.permissions"
:key="permission.id"
v-model:checked="permission.checked"
@change="() => handlePermissionChange(row)"
>
{{ permission.label }}
</Checkbox>
</div>
</template>
</BasicTable>
<!-- 全屏引导 -->
<FullScreenGuide />
</div>
</template>
<style scoped>
:deep(.ant-alert) {
padding: 4px 8px;
}
</style>

View File

@@ -3,7 +3,9 @@ import type { CheckboxChangeEvent } from 'ant-design-vue/es/checkbox/interface';
import type { DataNode } from 'ant-design-vue/es/tree';
import type { CheckInfo } from 'ant-design-vue/es/vc-tree/props';
import { computed, nextTick, onMounted, type PropType, ref, watch } from 'vue';
import type { PropType, SetupContext } from 'vue';
import { computed, nextTick, onMounted, ref, useSlots, watch } from 'vue';
import { findGroupParentIds, treeToList } from '@vben/utils';
@@ -108,8 +110,8 @@ const stop = watch([checkedKeys, () => props.treeData], () => {
* @param info info.halfCheckedKeys为父节点的ID
*/
type CheckedState<T = number | string> =
| { checked: T[]; halfChecked: T[] }
| T[];
| T[]
| { checked: T[]; halfChecked: T[] };
function handleChecked(checkedStateKeys: CheckedState, info: CheckInfo) {
// 数组的话为节点关联
if (Array.isArray(checkedStateKeys)) {
@@ -157,6 +159,8 @@ onMounted(async () => {
expandedKeys.value = allKeys.value;
}
});
const slots = useSlots() as SetupContext['slots'];
</script>
<template>
@@ -205,7 +209,7 @@ onMounted(async () => {
@check="handleChecked"
>
<template
v-for="slotName in Object.keys($slots)"
v-for="slotName in Object.keys(slots)"
:key="slotName"
#[slotName]="data"
>

View File

@@ -2,6 +2,10 @@
import type { UploadFile, UploadProps } from 'ant-design-vue';
import type { UploadRequestOption } from 'ant-design-vue/lib/vc-upload/interface';
import type { AxiosResponse } from '@vben/request';
import type { AxiosProgressEvent } from '#/api';
import { ref, toRefs, watch } from 'vue';
import { $t } from '@vben/locales';
@@ -26,7 +30,10 @@ const props = withDefaults(
* 需自行改造 ./helper/checkFileType方法
*/
accept?: string[];
api?: (...args: any[]) => Promise<any>;
api?: (
file: Blob | File,
onUploadProgress?: AxiosProgressEvent,
) => Promise<AxiosResponse<any>>;
disabled?: boolean;
helpText?: string;
// 最大数量的文件Infinity不限制
@@ -38,6 +45,10 @@ const props = withDefaults(
// support xxx.xxx.xx
// 返回的字段 默认url
resultField?: 'fileName' | 'ossId' | 'url' | string;
/**
* 是否显示下面的描述
*/
showDescription?: boolean;
value?: string[];
}>(),
{
@@ -50,6 +61,7 @@ const props = withDefaults(
multiple: false,
api: uploadApi,
resultField: '',
showDescription: true,
},
);
const emit = defineEmits(['change', 'update:value', 'delete']);
@@ -144,12 +156,19 @@ async function customRequest(info: UploadRequestOption<any>) {
return;
}
try {
const res = await api?.(info.file);
// 进度条事件
const progressEvent: AxiosProgressEvent = (e) => {
const percent = Math.trunc((e.loaded / e.total!) * 100);
info.onProgress!({ percent });
};
const res = await api?.(info.file as File, progressEvent);
/**
* 由getValue处理 传对象过去
* 直接传string(id)会被转为Number
* 内部的逻辑由requestClient.upload处理 这里不用判断业务状态码 不符合会自动reject
*/
info.onSuccess!(res);
message.success($t('component.upload.uploadSuccess'));
// 获取
const value = getValue();
isInnerOperate.value = true;
@@ -191,6 +210,7 @@ function getValue() {
:max-count="maxNumber"
:multiple="multiple"
list-type="text"
:progress="{ showInfo: true }"
@remove="handleRemove"
>
<div v-if="fileList && fileList.length < maxNumber">
@@ -199,6 +219,13 @@ function getValue() {
{{ $t('component.upload.upload') }}
</a-button>
</div>
<div v-if="showDescription" class="mt-2 flex flex-wrap items-center">
请上传不超过
<div class="text-primary mx-1 font-bold">{{ maxSize }}MB</div>
<div class="text-primary mx-1 font-bold">{{ accept.join('/') }}</div>
格式文件
</div>
</Upload>
</div>
</template>

View File

@@ -2,15 +2,20 @@
import type { UploadFile, UploadProps } from 'ant-design-vue';
import type { UploadRequestOption } from 'ant-design-vue/lib/vc-upload/interface';
import type { AxiosResponse } from '@vben/request';
import type { AxiosProgressEvent } from '#/api';
import { ref, toRefs, watch } from 'vue';
import { $t } from '@vben/locales';
import { PlusOutlined } from '@ant-design/icons-vue';
import { message, Modal, Upload } from 'ant-design-vue';
import { isArray, isFunction, isObject, isString } from 'lodash-es';
import { isArray, isFunction, isObject, isString, uniqueId } from 'lodash-es';
import { uploadApi } from '#/api';
import { ossInfo } from '#/api/system/oss';
import { checkImageFileType, defaultImageAccept } from './helper';
import { UploadResultStatus } from './typing';
@@ -24,7 +29,10 @@ const props = withDefaults(
* 包括拓展名(不带点) 文件头(image/png等 不包括泛写法即image/*)
*/
accept?: string[];
api?: (...args: any[]) => Promise<any>;
api?: (
file: Blob | File,
onUploadProgress?: AxiosProgressEvent,
) => Promise<AxiosResponse<any>>;
disabled?: boolean;
helpText?: string;
// eslint-disable-next-line no-use-before-define
@@ -37,7 +45,11 @@ const props = withDefaults(
multiple?: boolean;
// support xxx.xxx.xx
// 返回的字段 默认url
resultField?: 'fileName' | 'ossId' | 'url' | string;
resultField?: 'fileName' | 'ossId' | 'url';
/**
* 是否显示下面的描述
*/
showDescription?: boolean;
value?: string | string[];
}>(),
{
@@ -50,7 +62,8 @@ const props = withDefaults(
accept: () => defaultImageAccept,
multiple: false,
api: uploadApi,
resultField: '',
resultField: 'url',
showDescription: true,
},
);
const emit = defineEmits(['change', 'update:value', 'delete']);
@@ -74,7 +87,7 @@ const isFirstRender = ref<boolean>(true);
watch(
() => props.value,
(v) => {
async (v) => {
if (isInnerOperate.value) {
isInnerOperate.value = false;
return;
@@ -90,19 +103,40 @@ watch(
}
// 直接赋值 可能为string | string[]
value = v;
fileList.value = _fileList.map((item, i) => {
if (item && isString(item)) {
return {
uid: `${-i}`,
name: item.slice(Math.max(0, item.lastIndexOf('/') + 1)),
status: 'done',
url: item,
};
} else if (item && isObject(item)) {
return item;
const withUrlList: UploadProps['fileList'] = [];
for (const item of _fileList) {
// ossId情况
if (props.resultField === 'ossId') {
const resp = await ossInfo([item]);
if (item && isString(item)) {
withUrlList.push({
uid: item, // ossId作为uid 方便getValue获取
name: item.slice(Math.max(0, item.lastIndexOf('/') + 1)),
status: 'done',
url: resp?.[0]?.url,
});
} else if (item && isObject(item)) {
withUrlList.push({
...(item as any),
uid: item,
url: resp?.[0]?.url,
});
}
} else {
// 非ossId情况
if (item && isString(item)) {
withUrlList.push({
uid: uniqueId(),
name: item.slice(Math.max(0, item.lastIndexOf('/') + 1)),
status: 'done',
url: item,
});
} else if (item && isObject(item)) {
withUrlList.push(item);
}
}
return null;
}) as UploadProps['fileList'];
}
fileList.value = withUrlList;
}
if (!isFirstRender.value) {
emit('change', value);
@@ -182,12 +216,19 @@ async function customRequest(info: UploadRequestOption<any>) {
return;
}
try {
const res = await api?.(info.file);
// 进度条事件
const progressEvent: AxiosProgressEvent = (e) => {
const percent = Math.trunc((e.loaded / e.total!) * 100);
info.onProgress!({ percent });
};
const res = await api?.(info.file as File, progressEvent);
/**
* 由getValue处理 传对象过去
* 直接传string(id)会被转为Number
* 内部的逻辑由requestClient.upload处理 这里不用判断业务状态码 不符合会自动reject
*/
info.onSuccess!(res);
message.success($t('component.upload.uploadSuccess'));
// 获取
const value = getValue();
isInnerOperate.value = true;
@@ -200,12 +241,17 @@ async function customRequest(info: UploadRequestOption<any>) {
}
function getValue() {
console.log(fileList.value);
const list = (fileList.value || [])
.filter((item) => item?.status === UploadResultStatus.DONE)
.map((item: any) => {
if (item?.response && props?.resultField) {
return item?.response?.[props.resultField];
}
// ossId兼容 uid为ossId直接返回
if (props.resultField === 'ossId' && item.uid) {
return item.uid;
}
// 适用于已经有图片 回显的情况 会默认在init处理为{url: 'xx'}
if (item?.url) {
return item.url;
@@ -237,6 +283,7 @@ function getValue() {
:list-type="listType"
:max-count="maxNumber"
:multiple="multiple"
:progress="{ showInfo: true }"
@preview="handlePreview"
@remove="handleRemove"
>
@@ -245,6 +292,16 @@ function getValue() {
<div style="margin-top: 8px">{{ $t('component.upload.upload') }}</div>
</div>
</Upload>
<div
v-if="showDescription"
class="mt-2 flex flex-wrap items-center text-[14px]"
>
请上传不超过
<div class="text-primary mx-1 font-bold">{{ maxSize }}MB</div>
<div class="text-primary mx-1 font-bold">{{ accept.join('/') }}</div>
格式文件
</div>
<Modal
:footer="null"
:open="previewOpen"

View File

@@ -15,7 +15,7 @@ export interface FileItem {
percent: number;
file: File;
status?: UploadResultStatus;
response?: { fileName: string; ossId: string; url: string } | Recordable<any>;
response?: Recordable<any> | { fileName: string; ossId: string; url: string };
uuid: string;
}

View File

@@ -1,6 +1,7 @@
import { computed, unref } from 'vue';
import type { Ref } from 'vue';
import { computed, unref } from 'vue';
import { $t } from '@vben/locales';
export function useUploadType({

View File

@@ -1,7 +1,9 @@
import type { LocaleSetupOptions, SupportedLanguagesType } from '@vben/locales';
import type { Locale } from 'ant-design-vue/es/locale';
import type { App } from 'vue';
import type { LocaleSetupOptions, SupportedLanguagesType } from '@vben/locales';
import { ref } from 'vue';
import {
@@ -30,7 +32,8 @@ const localesMap = loadLocalesMapFromDir(
*/
async function loadMessages(lang: SupportedLanguagesType) {
const [appLocaleMessages] = await Promise.all([
localesMap[lang](),
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
localesMap[lang]!(),
loadThirdPartyMessage(lang),
]);
return appLocaleMessages.default;

View File

@@ -0,0 +1,7 @@
{
"apiRequestFailed": "Operation failed",
"operationSuccess": "Operation Success",
"successTip": "Success Tip",
"errorTip": "Error Tip",
"loginTimeout": "Login timeout, please log in again"
}

View File

@@ -0,0 +1,7 @@
{
"apiRequestFailed": "请求出错,请稍候重试",
"operationSuccess": "操作成功",
"successTip": "成功提示",
"errorTip": "错误提示",
"loginTimeout": "登录超时, 请重新登录"
}

View File

@@ -4,13 +4,15 @@ import type {
RouteRecordStringComponent,
} from '@vben/types';
import type { Menu } from '#/api';
import { generateAccessible } from '@vben/access';
import { preferences } from '@vben/preferences';
import { message } from 'ant-design-vue';
import { cloneDeep } from 'lodash-es';
import { getAllMenusApi, type Menu } from '#/api';
import { getAllMenusApi } from '#/api';
import { BasicLayout, IFrameView } from '#/layouts';
import { $t } from '#/locales';

View File

@@ -54,7 +54,9 @@ function setupAccessGuard(router: Router) {
if (coreRouteNames.includes(to.name as string)) {
if (to.path === LOGIN_PATH && accessStore.accessToken) {
return decodeURIComponent(
(to.query?.redirect as string) || DEFAULT_HOME_PATH,
(to.query?.redirect as string) ||
userStore.userInfo?.homePath ||
DEFAULT_HOME_PATH,
);
}
return true;
@@ -72,7 +74,10 @@ function setupAccessGuard(router: Router) {
return {
path: LOGIN_PATH,
// 如不需要,直接删除 query
query: { redirect: encodeURIComponent(to.fullPath) },
query:
to.fullPath === DEFAULT_HOME_PATH
? {}
: { redirect: encodeURIComponent(to.fullPath) },
// 携带当前跳转的页面,登录后重新跳转该页面
replace: true,
};
@@ -102,7 +107,10 @@ function setupAccessGuard(router: Router) {
accessStore.setAccessMenus(accessibleMenus);
accessStore.setAccessRoutes(accessibleRoutes);
accessStore.setIsAccessChecked(true);
const redirectPath = (from.query.redirect ?? to.fullPath) as string;
const redirectPath = (from.query.redirect ??
(to.path === DEFAULT_HOME_PATH
? userInfo.homePath || DEFAULT_HOME_PATH
: to.fullPath)) as string;
return {
...router.resolve(decodeURIComponent(redirectPath)),

View File

@@ -2,7 +2,7 @@ import type { RouteRecordRaw } from 'vue-router';
import { DEFAULT_HOME_PATH, LOGIN_PATH } from '@vben/constants';
import { AuthPageLayout } from '#/layouts';
import { AuthPageLayout, BasicLayout } from '#/layouts';
import { $t } from '#/locales';
import Login from '#/views/_core/authentication/login.vue';
@@ -21,13 +21,21 @@ const fallbackNotFoundRoute: RouteRecordRaw = {
/** 基本路由,这些路由是必须存在的 */
const coreRoutes: RouteRecordRaw[] = [
/**
* 根路由
* 使用基础布局作为所有页面的父级容器子级就不必配置BasicLayout。
* 此路由必须存在,且不应修改
*/
{
component: BasicLayout,
meta: {
hideInBreadcrumb: true,
title: 'Root',
},
name: 'Root',
path: '/',
redirect: DEFAULT_HOME_PATH,
children: [],
},
{
component: () => import('#/views/_core/social-callback/index.vue'),

View File

@@ -5,105 +5,83 @@ import { $t } from '@vben/locales';
/**
* 该文件放非后台返回的路由 比如个人中心 等需要跳转显示的页面
*/
/**
* 个人中心
*/
const profileRoute: RouteRecordStringComponent[] = [
const localRoutes: RouteRecordStringComponent[] = [
{
component: 'BasicLayout',
component: '/_core/profile/index',
meta: {
hideChildrenInMenu: true,
hideInMenu: true,
icon: 'mingcute:profile-line',
title: $t('ui.widgets.profile'),
hideInMenu: true,
},
name: 'Profile',
path: '/',
redirect: '/profile',
children: [
{
component: '/_core/profile/index',
meta: {
icon: 'mingcute:profile-line',
keepAlive: true,
title: $t('ui.widgets.profile'),
},
name: 'ProfileIndex',
path: '/profile',
},
],
path: '/profile',
},
{
component: 'BasicLayout',
component: '/system/oss-config/index',
meta: {
hideChildrenInMenu: true,
hideInMenu: true,
activePath: '/system/oss',
icon: 'ant-design:setting-outlined',
title: 'oss配置',
hideInMenu: true,
},
name: 'OssConfig',
path: '/',
redirect: '/system/oss-config',
children: [
{
component: '/system/oss-config/index',
meta: {
activePath: '/system/oss',
icon: 'ant-design:setting-outlined',
keepAlive: true,
title: 'oss配置',
},
name: 'OssConfigIndex',
path: '/system/oss-config',
},
],
path: '/system/oss-config',
},
{
component: 'BasicLayout',
component: '/tool/gen/edit-gen',
meta: {
hideChildrenInMenu: true,
activePath: '/tool/gen',
icon: 'tabler:code',
title: '生成配置',
hideInMenu: true,
title: '修改生成配置',
},
name: 'GenConfig',
path: '/',
redirect: '/code-gen/edit',
children: [
{
component: '/tool/gen/edit-gen',
meta: {
activePath: '/tool/gen',
icon: 'tabler:code',
keepAlive: true,
title: '生成配置',
},
name: 'GenConfigIndex',
path: '/code-gen/edit/:tableId',
},
],
path: '/code-gen/edit/:tableId',
},
{
component: 'BasicLayout',
component: '/system/role-assign/index',
meta: {
hideChildrenInMenu: true,
hideInMenu: true,
activePath: '/system/role',
icon: 'eos-icons:role-binding-outlined',
title: '分配角色',
hideInMenu: true,
},
name: 'RoleAssign',
path: '/',
redirect: '/system/role-assign',
children: [
{
component: '/system/role-assign/index',
meta: {
activePath: '/system/role',
icon: 'eos-icons:role-binding-outlined',
keepAlive: true,
title: '分配角色',
},
name: 'RoleAssignIndex',
path: '/system/role-assign/:roleId',
},
],
path: '/system/role-assign/:roleId',
},
{
component: '/workflow/components/flow-designer',
meta: {
activePath: '/workflow/processDefinition',
icon: 'fluent-mdl2:flow',
title: '流程设计',
hideInMenu: true,
},
name: 'WorkflowDesigner',
path: '/workflow/designer',
},
{
component: 'workflow/leave/leave-form',
meta: {
icon: 'flat-color-icons:leave',
title: '请假申请',
activePath: '/demo/leave',
hideInMenu: true,
},
name: 'WorkflowLeaveIndex',
path: '/workflow/leaveEdit/index',
},
// 这里是iframe使用的 去掉外层的BasicLayout
{
component: 'workflow/leave/leave-form',
meta: {
title: '请假申请',
hideInMenu: true,
// 不使用基础布局(仅在顶级生效)
noBasicLayout: true,
},
name: 'WorkflowLeaveInner',
path: '/workflow/leaveEdit/index/iframe',
},
];
@@ -116,6 +94,8 @@ export const localMenuList: RouteRecordStringComponent[] = [
meta: {
order: -1,
title: 'page.dashboard.title',
// 不使用基础布局(仅在顶级生效)
noBasicLayout: true,
},
name: 'Dashboard',
path: '/',
@@ -152,25 +132,14 @@ export const localMenuList: RouteRecordStringComponent[] = [
],
},
{
component: 'BasicLayout',
component: '/_core/about/index',
meta: {
hideChildrenInMenu: true,
icon: 'lucide:copyright',
order: 9999,
title: $t('demos.vben.about'),
},
name: 'About',
path: '/about',
children: [
{
component: '/_core/about/index',
meta: {
title: $t('demos.vben.about'),
},
name: 'VbenAbout',
path: '/vben-admin/about',
},
],
path: '/vben-admin/about',
},
...profileRoute,
...localRoutes,
];

View File

@@ -1,18 +1,16 @@
import type { RouteRecordRaw } from 'vue-router';
import { BasicLayout } from '#/layouts';
import { $t } from '#/locales';
const routes: RouteRecordRaw[] = [
{
component: BasicLayout,
meta: {
icon: 'lucide:layout-dashboard',
order: -1,
title: $t('page.dashboard.title'),
},
name: 'Dashboard',
path: '/',
path: '/dashboard',
children: [
{
name: 'Analytics',

View File

@@ -1,30 +0,0 @@
import type { RouteRecordRaw } from 'vue-router';
import { BasicLayout } from '#/layouts';
import { $t } from '#/locales';
const routes: RouteRecordRaw[] = [
{
component: BasicLayout,
meta: {
icon: 'ic:baseline-view-in-ar',
keepAlive: true,
order: 1000,
title: $t('demos.title'),
},
name: 'Demos',
path: '/demos',
children: [
{
meta: {
title: $t('demos.antd'),
},
name: 'AntDesignDemos',
path: '/demos/ant-design',
component: () => import('#/views/demos/antd/index.vue'),
},
],
},
];
export default routes;

View File

@@ -8,16 +8,15 @@ import {
VBEN_NAIVE_PREVIEW_URL,
} from '@vben/constants';
import { BasicLayout, IFrameView } from '#/layouts';
import { IFrameView } from '#/layouts';
import { $t } from '#/locales';
const routes: RouteRecordRaw[] = [
{
component: BasicLayout,
meta: {
badgeType: 'dot',
icon: VBEN_LOGO_URL,
order: 9999,
order: 9998,
title: $t('demos.vben.title'),
},
name: 'VbenProject',
@@ -76,6 +75,16 @@ const routes: RouteRecordRaw[] = [
},
],
},
{
name: 'VbenAbout',
path: '/vben-admin/about',
component: () => import('#/views/_core/about/index.vue'),
meta: {
icon: 'lucide:copyright',
title: $t('demos.vben.about'),
order: 9999,
},
},
];
export default routes;

View File

@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-non-null-assertion */
import type { DictData } from '#/api/system/dict/dict-data-model';
import { reactive } from 'vue';
@@ -6,29 +7,36 @@ import { defineStore } from 'pinia';
/**
* antd使用 select和radio通用
* 本质上是对DictData的拓展
*/
export interface Option {
export interface DictOption extends DictData {
disabled?: boolean;
label: string;
value: string;
value: number | string;
}
export function dictToOptions(data: DictData[]): Option[] {
/**
* 将字典数据转为Options
* @param data 字典数据
* @param formatNumber 是否需要将value格式化为number类型
* @returns options
*/
export function dictToOptions(
data: DictData[],
formatNumber = false,
): DictOption[] {
return data.map((item) => ({
...item,
label: item.dictLabel,
value: item.dictValue,
value: formatNumber ? Number(item.dictValue) : item.dictValue,
}));
}
export const useDictStore = defineStore('app-dict', () => {
/**
* 一般是dictTag使用
* select radio checkbox等使用 只能为固定格式{label, value}
*/
const dictMap = reactive(new Map<string, DictData[]>());
/**
* select radio radioButton使用 只能为固定格式(Option)
*/
const dictOptionsMap = reactive(new Map<string, Option[]>());
const dictOptionsMap = reactive(new Map<string, DictOption[]>());
/**
* 添加一个字典请求状态的缓存
*
@@ -40,30 +48,17 @@ export const useDictStore = defineStore('app-dict', () => {
new Map<string, Promise<DictData[] | void>>(),
);
function getDict(dictName: string): DictData[] {
if (!dictName) return [];
// 没有key 添加一个空数组
if (!dictMap.has(dictName)) {
dictMap.set(dictName, reactive([]));
}
// 这里拿到的就不可能为空了
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
return dictMap.get(dictName)!;
}
function getDictOptions(dictName: string): Option[] {
function getDictOptions(dictName: string): DictOption[] {
if (!dictName) return [];
// 没有key 添加一个空数组
if (!dictOptionsMap.has(dictName)) {
dictOptionsMap.set(dictName, []);
}
// 这里拿到的就不可能为空了
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
return dictOptionsMap.get(dictName)!;
}
function resetCache() {
dictMap.clear();
dictOptionsMap.clear();
}
@@ -76,19 +71,20 @@ export const useDictStore = defineStore('app-dict', () => {
* 否则 直接set
*
*/
function setDictInfo(dictName: string, dictValue: DictData[]) {
if (dictMap.has(dictName) && dictMap.get(dictName)?.length === 0) {
dictMap.get(dictName)?.push(...dictValue);
} else {
dictMap.set(dictName, dictValue);
}
function setDictInfo(
dictName: string,
dictValue: DictData[],
formatNumber = false,
) {
if (
dictOptionsMap.has(dictName) &&
dictOptionsMap.get(dictName)?.length === 0
) {
dictOptionsMap.get(dictName)?.push(...dictToOptions(dictValue));
dictOptionsMap
.get(dictName)
?.push(...dictToOptions(dictValue, formatNumber));
} else {
dictOptionsMap.set(dictName, dictToOptions(dictValue));
dictOptionsMap.set(dictName, dictToOptions(dictValue, formatNumber));
}
}
@@ -100,10 +96,8 @@ export const useDictStore = defineStore('app-dict', () => {
return {
$reset,
dictMap,
dictOptionsMap,
dictRequestCache,
getDict,
getDictOptions,
resetCache,
setDictInfo,

View File

@@ -1,11 +1,10 @@
import type { TenantOption } from '#/api/core/auth';
import { ref } from 'vue';
import { defineStore } from 'pinia';
import {
tenantList as tenantListApi,
type TenantOption,
} from '#/api/core/auth';
import { tenantList as tenantListApi } from '#/api/core/auth';
/**
* 用于超级管理员切换租户

View File

@@ -1,22 +1,31 @@
import type { DictData } from '#/api/system/dict/dict-data-model';
import { dictDataInfo } from '#/api/system/dict/dict-data';
import { type Option, useDictStore } from '#/store/dict';
import { useDictStore } from '#/store/dict';
/**
* 抽取公共逻辑的基础方法
* @param dictName 字典名称
* @param dataGetter 获取字典数据的函数
* @param formatNumber 是否格式化字典value为number类型
* @returns 数据
*/
function fetchAndCacheDictData<T>(
dictName: string,
dataGetter: () => T[],
formatNumber = false,
): T[] {
const { dictRequestCache, setDictInfo } = useDictStore();
// 有调用方决定如何获取数据
const dataList = dataGetter();
// todo 重复代码的封装
export function getDict(dictName: string): DictData[] {
const { dictRequestCache, getDict, setDictInfo } = useDictStore();
// 这里拿到
const dictList = getDict(dictName);
// 检查请求状态缓存
if (dictList.length === 0 && !dictRequestCache.has(dictName)) {
if (dataList.length === 0 && !dictRequestCache.has(dictName)) {
dictRequestCache.set(
dictName,
dictDataInfo(dictName)
.then((resp) => {
// 缓存到store 这样就不用重复获取了
// 内部处理了push的逻辑 这里不用push
setDictInfo(dictName, resp);
setDictInfo(dictName, resp, formatNumber);
})
.finally(() => {
// 移除请求状态缓存
@@ -25,40 +34,37 @@ export function getDict(dictName: string): DictData[] {
* 会导致if一直进入逻辑导致接口无限刷新
* 在这里dictList为空时 不删除缓存
*/
if (dictList.length > 0) {
if (dataList.length > 0) {
dictRequestCache.delete(dictName);
}
}),
);
}
return dictList;
return dataList;
}
export function getDictOptions(dictName: string): Option[] {
const { dictRequestCache, getDictOptions, setDictInfo } = useDictStore();
const dictOptionList = getDictOptions(dictName);
// 检查请求状态缓存
if (dictOptionList.length === 0 && !dictRequestCache.has(dictName)) {
dictRequestCache.set(
dictName,
dictDataInfo(dictName)
.then((resp) => {
// 缓存到store 这样就不用重复获取了
// 内部处理了push的逻辑 这里不用push
setDictInfo(dictName, resp);
})
.finally(() => {
// 移除请求状态缓存
/**
* 这里主要判断字典item为空的情况(无奈兼容 不给字典item本来就是错误用法)
* 会导致if一直进入逻辑导致接口五线刷新
* 在这里dictList为空时 不删除缓存
*/
if (dictOptionList.length > 0) {
dictRequestCache.delete(dictName);
}
}),
);
}
return dictOptionList;
/**
* 这里是提供给渲染标签使用的方法
* @deprecated 使用getDictOptions代替 于下个版本删除
* @param dictName 字典名称
* @returns 字典信息
*/
export function getDict(dictName: string) {
const { getDictOptions } = useDictStore();
return fetchAndCacheDictData(dictName, () => getDictOptions(dictName));
}
/**
* 一般是Select, Radio, Checkbox等组件使用
* @param dictName 字典名称
* @param formatNumber 是否格式化字典value为number类型
* @returns Options数组
*/
export function getDictOptions(dictName: string, formatNumber = false) {
const { getDictOptions } = useDictStore();
return fetchAndCacheDictData(
dictName,
() => getDictOptions(dictName),
formatNumber,
);
}

View File

@@ -4,6 +4,7 @@ import { $t } from '@vben/locales';
import { cloneDeep, formatDate } from '@vben/utils';
import { message } from 'ant-design-vue';
import { isFunction } from 'lodash-es';
import { dataURLtoBlob, urlToBase64 } from './base64Conver';
@@ -54,37 +55,42 @@ function handleRangeTimeValue(
fieldMappingTime.forEach(
([field, [startTimeKey, endTimeKey], format = 'YYYY-MM-DD']) => {
if (
values[field] === null &&
values[startTimeKey] &&
values[endTimeKey]
) {
if (startTimeKey && endTimeKey && values[field] === null) {
Reflect.deleteProperty(values, startTimeKey);
Reflect.deleteProperty(values, endTimeKey);
return;
// delete values[startTimeKey];
// delete values[endTimeKey];
}
if (!values[field]) {
Reflect.deleteProperty(values, field);
// delete values[field];
return;
}
const [startTime, endTime] = values[field];
const [startTimeFormat, endTimeFormat] = Array.isArray(format)
? format
: [format, format];
values[startTimeKey] = startTime
? formatDate(startTime, startTimeFormat)
: undefined;
values[endTimeKey] = endTime
? formatDate(endTime, endTimeFormat)
: undefined;
if (format === null) {
values[startTimeKey] = startTime;
values[endTimeKey] = endTime;
} else if (isFunction(format)) {
values[startTimeKey] = format(startTime, startTimeKey);
values[endTimeKey] = format(endTime, endTimeKey);
} else {
const [startTimeFormat, endTimeFormat] = Array.isArray(format)
? format
: [format, format];
values[startTimeKey] = startTime
? formatDate(startTime, startTimeFormat)
: undefined;
values[endTimeKey] = endTime
? formatDate(endTime, endTimeFormat)
: undefined;
}
// delete values[field];
Reflect.deleteProperty(values, field);
},
);
return values;
}

View File

@@ -0,0 +1,31 @@
/**
* 计算文件大小并以适当单位表示
*
* 此函数接收一个表示文件大小的数字(以字节为单位),并返回一个格式化后的字符串,
* 该字符串表示文件的大小以最适合的单位B, KB, MB, GB, TB表示
*
* @param size 文件大小,以字节为单位
* @param isInteger 是否返回整数大小默认为false如果设置为true
* 则返回的大小将不包含小数部分如果为false则根据单位的不同
* 返回最多3位小数的大小
* @returns 格式化后的文件大小字符串,如"4.5KB"或"3MB"
*/
export function calculateFileSize(size: number, isInteger = false) {
// 定义文件大小的单位数组
const units = ['B', 'KB', 'MB', 'GB', 'TB'];
// 定义换算基数1KB = 1024B1MB = 1024KB以此类推
const base = 1024;
// 初始化单位索引初始值为0即默认单位为B
let unitIndex = 0;
// 当文件大小大于等于基数且单位索引未超出单位数组范围时,循环进行单位转换
while (size >= base && unitIndex < units.length - 1) {
size /= base;
unitIndex++;
}
// 根据是否需要整数大小,确定输出的精度
const precision = isInteger ? 0 : Math.min(unitIndex, 3);
// 返回格式化后的文件大小字符串
return `${size.toFixed(precision)}${units[unitIndex]}`;
}

View File

@@ -1,8 +1,9 @@
import type { ModalFuncProps } from 'ant-design-vue';
import type { Rule } from 'ant-design-vue/es/form';
import { reactive } from 'vue';
import { Alert, Form, Input, Modal, type ModalFuncProps } from 'ant-design-vue';
import { Alert, Form, Input, Modal } from 'ant-design-vue';
import { isFunction } from 'lodash-es';
export interface ConfirmModalProps extends Omit<ModalFuncProps, 'visible'> {

View File

@@ -1,6 +1,8 @@
import type { Component as ComponentType } from 'vue';
import type { DictData } from '#/api/system/dict/dict-data-model';
import { type Component as ComponentType, h } from 'vue';
import { h } from 'vue';
import { JsonPreview } from '@vben/common-ui';
import {
@@ -29,7 +31,7 @@ import { Tag } from 'ant-design-vue';
import { DictTag } from '#/components/dict';
import { getDict } from './dict';
import { getDictOptions } from './dict';
/**
* 渲染标签
@@ -50,7 +52,10 @@ function renderTag(text: string, color?: string) {
*/
export function renderTags(tags: string[], wrap = false, gap = 1) {
return (
<div class={['flex', `gap-${gap}`, wrap ? 'flex-col' : 'flex-row']}>
<div
class={['flex', wrap ? 'flex-col' : 'flex-row']}
style={{ gap: `${gap}px` }}
>
{tags.map((tag, index) => {
return <div key={index}>{renderTag(tag)}</div>;
})}
@@ -133,7 +138,10 @@ export function renderDictTags(
return <div>{value}</div>;
}
return (
<div class={['flex', `gap-${gap}`, wrap ? 'flex-col' : 'flex-row']}>
<div
class={['flex', wrap ? 'flex-col' : 'flex-row']}
style={{ gap: `${gap}px` }}
>
{value.map((item, index) => {
return <div key={index}>{renderDictTag(item, dicts)}</div>;
})}
@@ -148,7 +156,7 @@ export function renderDictTags(
* @returns tag
*/
export function renderDict(value: string, dictName: string) {
const dictInfo = getDict(dictName);
const dictInfo = getDictOptions(dictName);
return renderDictTag(value, dictInfo);
}
export function renderIconSpan(
@@ -205,12 +213,8 @@ export function renderOsIcon(os: string, center = false) {
if (os.toLocaleLowerCase().includes('windows')) {
current = osOptions[0];
}
if (current) {
return renderIconSpan(current.icon, os, center, '5px');
}
// 返回默认
const defaultIcon = DefaultOsIcon;
return renderIconSpan(defaultIcon, os, center, '5px');
const icon = current ? current.icon : DefaultOsIcon;
return renderIconSpan(icon, os, center, '5px');
}
export function renderBrowserIcon(browser: string, center = false) {
@@ -220,10 +224,6 @@ export function renderBrowserIcon(browser: string, center = false) {
const current = browserOptions.find((item) =>
browser.toLocaleLowerCase().includes(item.value),
);
if (current) {
return renderIconSpan(current.icon, browser, center, '5px');
}
// 返回默认
const defaultIcon = DefaultBrowserIcon;
return renderIconSpan(defaultIcon, browser, center, '5px');
const icon = current ? current.icon : DefaultBrowserIcon;
return renderIconSpan(icon, browser, center, '5px');
}

View File

@@ -1,6 +1,8 @@
<script lang="ts" setup>
import type { LoginCodeParams, VbenFormSchema } from '@vben/common-ui';
import type { TenantResp } from '#/api';
import { computed, onMounted, ref, useTemplateRef } from 'vue';
import { AuthenticationCodeLogin, z } from '@vben/common-ui';
@@ -8,13 +10,14 @@ import { $t } from '@vben/locales';
import { Alert, message } from 'ant-design-vue';
import { tenantList, type TenantResp } from '#/api';
import { tenantList } from '#/api';
import { sendSmsCode } from '#/api/core/captcha';
import { useAuthStore } from '#/store';
defineOptions({ name: 'CodeLogin' });
const loading = ref(false);
const CODE_LENGTH = 6;
const tenantInfo = ref<TenantResp>({
tenantEnabled: false,
@@ -98,7 +101,9 @@ const formSchema = computed((): VbenFormSchema[] => {
},
fieldName: 'code',
label: $t('authentication.code'),
rules: z.string().min(1, { message: $t('authentication.codeTip') }),
rules: z.string().length(CODE_LENGTH, {
message: $t('authentication.codeTip', [CODE_LENGTH]),
}),
},
];
});

View File

@@ -1,6 +1,9 @@
<script lang="ts" setup>
import type { LoginAndRegisterParams, VbenFormSchema } from '@vben/common-ui';
import type { TenantResp } from '#/api';
import type { CaptchaResponse } from '#/api/core/captcha';
import { computed, onMounted, ref, useTemplateRef } from 'vue';
import { AuthenticationLogin, z } from '@vben/common-ui';
@@ -8,8 +11,8 @@ import { $t } from '@vben/locales';
import { omit } from 'lodash-es';
import { tenantList, type TenantResp } from '#/api';
import { captchaImage, type CaptchaResponse } from '#/api/core/captcha';
import { tenantList } from '#/api';
import { captchaImage } from '#/api/core/captcha';
import { useAuthStore } from '#/store';
import OAuthLogin from './oauth-login.vue';

View File

@@ -1,7 +1,11 @@
<script setup lang="tsx">
import type { VxeGridProps } from '@vben/plugins/vxe-table';
import type { BindItem } from '../../oauth-common';
import { computed, ref, unref } from 'vue';
import { useVbenVxeGrid, type VxeGridProps } from '@vben/plugins/vxe-table';
import { useVbenVxeGrid } from '@vben/plugins/vxe-table';
import {
Alert,
@@ -16,7 +20,7 @@ import {
import { authUnbinding } from '#/api';
import { socialList } from '#/api/system/social';
import { accountBindList, type BindItem } from '../../oauth-common';
import { accountBindList } from '../../oauth-common';
/**
* 没有传递action事件则不支持绑定 弹出默认提示
@@ -103,7 +107,6 @@ const gridOptions: VxeGridProps = {
},
},
rowConfig: {
isHover: true,
isCurrent: false,
keyField: 'id',
},

View File

@@ -1,16 +1,20 @@
<script setup lang="ts">
import type { Recordable } from '@vben/types';
import type { VxeGridProps } from '#/adapter/vxe-table';
import { Popconfirm } from 'ant-design-vue';
import { useVbenVxeGrid, type VxeGridProps } from '#/adapter/vxe-table';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { forceLogout2, onlineDeviceList } from '#/api/monitor/online';
import { columns } from '#/views/monitor/online/data';
const gridOptions: VxeGridProps = {
columns,
keepSource: true,
pagerConfig: {},
pagerConfig: {
enabled: false,
},
proxyConfig: {
ajax: {
query: async () => {
@@ -19,7 +23,6 @@ const gridOptions: VxeGridProps = {
},
},
rowConfig: {
isHover: true,
keyField: 'tokenId',
},
};

View File

@@ -1,4 +1,6 @@
<script setup lang="ts">
import type { AuthApi } from '#/api';
import { onMounted } from 'vue';
import { useRoute, useRouter } from 'vue-router';
@@ -7,7 +9,7 @@ import { useAccessStore } from '@vben/stores';
import { message } from 'ant-design-vue';
import { type AuthApi, authCallback } from '#/api';
import { authCallback } from '#/api';
import { useAuthStore } from '#/store';
import { accountBindList } from '../oauth-common';

View File

@@ -1,18 +0,0 @@
import { defineComponent } from 'vue';
import { Fallback } from '@vben/common-ui';
export default defineComponent({
name: 'CommonSkeleton',
setup() {
return () => (
<div class="flex h-[600px] w-full items-center justify-center">
<Fallback
description="等待后端重构工作流后开发"
status="coming-soon"
title="等待开发"
/>
</div>
);
},
});

View File

@@ -1,11 +1,8 @@
<script lang="ts" setup>
import { onMounted, ref } from 'vue';
import type { EchartsUIType } from '@vben/plugins/echarts';
import {
EchartsUI,
type EchartsUIType,
useEcharts,
} from '@vben/plugins/echarts';
import { EchartsUI, useEcharts } from '@vben/plugins/echarts';
import { onMounted, ref } from 'vue';
const chartRef = ref<EchartsUIType>();
const { renderEcharts } = useEcharts(chartRef);

View File

@@ -1,11 +1,8 @@
<script lang="ts" setup>
import { onMounted, ref } from 'vue';
import type { EchartsUIType } from '@vben/plugins/echarts';
import {
EchartsUI,
type EchartsUIType,
useEcharts,
} from '@vben/plugins/echarts';
import { EchartsUI, useEcharts } from '@vben/plugins/echarts';
import { onMounted, ref } from 'vue';
const chartRef = ref<EchartsUIType>();
const { renderEcharts } = useEcharts(chartRef);

View File

@@ -1,11 +1,8 @@
<script lang="ts" setup>
import { onMounted, ref } from 'vue';
import type { EchartsUIType } from '@vben/plugins/echarts';
import {
EchartsUI,
type EchartsUIType,
useEcharts,
} from '@vben/plugins/echarts';
import { EchartsUI, useEcharts } from '@vben/plugins/echarts';
import { onMounted, ref } from 'vue';
const chartRef = ref<EchartsUIType>();
const { renderEcharts } = useEcharts(chartRef);

View File

@@ -1,11 +1,8 @@
<script lang="ts" setup>
import { onMounted, ref } from 'vue';
import type { EchartsUIType } from '@vben/plugins/echarts';
import {
EchartsUI,
type EchartsUIType,
useEcharts,
} from '@vben/plugins/echarts';
import { EchartsUI, useEcharts } from '@vben/plugins/echarts';
import { onMounted, ref } from 'vue';
const chartRef = ref<EchartsUIType>();
const { renderEcharts } = useEcharts(chartRef);

View File

@@ -1,11 +1,8 @@
<script lang="ts" setup>
import { onMounted, ref } from 'vue';
import type { EchartsUIType } from '@vben/plugins/echarts';
import {
EchartsUI,
type EchartsUIType,
useEcharts,
} from '@vben/plugins/echarts';
import { EchartsUI, useEcharts } from '@vben/plugins/echarts';
import { onMounted, ref } from 'vue';
const chartRef = ref<EchartsUIType>();
const { renderEcharts } = useEcharts(chartRef);

View File

@@ -1,19 +1,18 @@
<script setup lang="ts">
import type { VbenFormProps } from '@vben/common-ui';
import type { Recordable } from '@vben/types';
import type { VxeGridProps } from '#/adapter/vxe-table';
import { ref } from 'vue';
import { Page, useVbenModal, type VbenFormProps } from '@vben/common-ui';
import { Page, useVbenModal } from '@vben/common-ui';
import { getPopupContainer } from '@vben/utils';
import { Modal, Popconfirm, Space } from 'ant-design-vue';
import {
tableCheckboxEvent,
useVbenVxeGrid,
type VxeGridProps,
} from '#/adapter/vxe-table';
import { downloadExcel } from '#/utils/file/download';
import { useVbenVxeGrid, vxeCheckboxChecked } from '#/adapter/vxe-table';
import { commonDownloadExcel } from '#/utils/file/download';
import { demoExport, demoList, demoRemove } from './api';
import { columns, querySchema } from './data';
@@ -61,10 +60,6 @@ const checked = ref(false);
const [BasicTable, tableApi] = useVbenVxeGrid({
formOptions,
gridOptions,
gridEvents: {
checkboxChange: tableCheckboxEvent(checked),
checkboxAll: tableCheckboxEvent(checked),
},
});
const [DemoModal, modalApi] = useVbenModal({
@@ -113,7 +108,7 @@ function handleMultiDelete() {
<a-button
v-access:code="['system:demo:export']"
@click="
downloadExcel(
commonDownloadExcel(
demoExport,
'测试单数据',
tableApi.formApi.form.values,
@@ -123,7 +118,7 @@ function handleMultiDelete() {
{{ $t('pages.common.export') }}
</a-button>
<a-button
:disabled="!checked"
:disabled="!vxeCheckboxChecked(tableApi)"
danger
type="primary"
v-access:code="['system:demo:remove']"

View File

@@ -1,14 +1,17 @@
<script setup lang="ts">
import type { VbenFormProps } from '@vben/common-ui';
import type { Recordable } from '@vben/types';
import type { VxeGridProps } from '#/adapter/vxe-table';
import { nextTick } from 'vue';
import { Page, useVbenModal, type VbenFormProps } from '@vben/common-ui';
import { Page, useVbenModal } from '@vben/common-ui';
import { getPopupContainer, listToTree } from '@vben/utils';
import { Popconfirm, Space } from 'ant-design-vue';
import { useVbenVxeGrid, type VxeGridProps } from '#/adapter/vxe-table';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { treeList, treeRemove } from './api';
import { columns, querySchema } from './data';
@@ -51,7 +54,6 @@ const gridOptions: VxeGridProps = {
},
},
rowConfig: {
isHover: true,
keyField: 'id',
},

View File

@@ -1,66 +0,0 @@
<script lang="ts" setup>
import { Page } from '@vben/common-ui';
import { Button, Card, message, notification, Space } from 'ant-design-vue';
type NotificationType = 'error' | 'info' | 'success' | 'warning';
function info() {
message.info('How many roads must a man walk down');
}
function error() {
message.error({
content: 'Once upon a time you dressed so fine',
duration: 2500,
});
}
function warning() {
message.warning('How many roads must a man walk down');
}
function success() {
message.success('Cause you walked hand in hand With another man in my place');
}
function notify(type: NotificationType) {
notification[type]({
duration: 2500,
message: '说点啥呢',
type,
});
}
</script>
<template>
<Page
description="支持多语言,主题功能集成切换等"
title="Ant Design Vue组件使用演示"
>
<Card class="mb-5" title="按钮">
<Space>
<Button>Default</Button>
<Button type="primary"> Primary </Button>
<Button> Info </Button>
<Button danger> Error </Button>
</Space>
</Card>
<Card class="mb-5" title="Message">
<Space>
<Button @click="info"> 信息 </Button>
<Button danger @click="error"> 错误 </Button>
<Button @click="warning"> 警告 </Button>
<Button @click="success"> 成功 </Button>
</Space>
</Card>
<Card class="mb-5" title="Notification">
<Space>
<Button @click="notify('info')"> 信息 </Button>
<Button danger @click="notify('error')"> 错误 </Button>
<Button @click="notify('warning')"> 警告 </Button>
<Button @click="notify('success')"> 成功 </Button>
</Space>
</Card>
</Page>
</template>

View File

@@ -1,20 +1,18 @@
<script lang="ts">
import type { EChartsOption } from 'echarts';
import type { PropType } from 'vue';
import type { EchartsUIType } from '@vben/plugins/echarts';
import { defineComponent, onActivated, onMounted, ref, watch } from 'vue';
import {
EchartsUI,
type EchartsUIType,
useEcharts,
} from '@vben/plugins/echarts';
import { EchartsUI, useEcharts } from '@vben/plugins/echarts';
export default defineComponent({
components: { EchartsUI },
props: {
data: {
default: () => [],
type: Array,
type: Array as PropType<{ name: string; value: string }[]>,
},
},
setup(props, { expose }) {
@@ -41,6 +39,7 @@ export default defineComponent({
*/
onActivated(() => resize(false));
type EChartsOption = Parameters<typeof renderEcharts>['0'];
function setEchartsOption(data: any[]) {
const option: EChartsOption = {
series: [

View File

@@ -1,13 +1,9 @@
<script lang="ts">
import type { EChartsOption } from 'echarts';
import type { EchartsUIType } from '@vben/plugins/echarts';
import { defineComponent, onActivated, onMounted, ref, watch } from 'vue';
import {
EchartsUI,
type EchartsUIType,
useEcharts,
} from '@vben/plugins/echarts';
import { EchartsUI, useEcharts } from '@vben/plugins/echarts';
export default defineComponent({
components: { EchartsUI },
@@ -38,6 +34,13 @@ export default defineComponent({
// 从其他页面切换回来会有一个奇怪的动画效果 需要调用resize
onActivated(resize);
/**
* 获取最近的十的幂次
* 该函数用于寻找大于给定数字num的最近的10的幂次
* 主要解决的问题是确定一个数附近较大的十的幂次,这在某些算法中很有用
*
* @param num {number} 输入的数字,用于寻找最近的十的幂次
*/
function getNearestPowerOfTen(num: number) {
let power = 10;
while (power <= num) {
@@ -46,6 +49,7 @@ export default defineComponent({
return power;
}
type EChartsOption = Parameters<typeof renderEcharts>['0'];
function setEchartsOption(value: string) {
// x10
const formattedValue = Math.floor(Number.parseFloat(value));

View File

@@ -1,13 +1,12 @@
<script setup lang="ts">
import type { PropType } from 'vue';
import type { RedisInfo } from '#/api/monitor/cache';
import type { DescItem } from '#/components/description';
import { onMounted, type PropType, watch } from 'vue';
import { onMounted, watch } from 'vue';
import {
type DescItem,
Description,
useDescription,
} from '#/components/description';
import { Description, useDescription } from '#/components/description';
interface IRedisInfo extends RedisInfo {
dbSize: string;

View File

@@ -1,4 +1,6 @@
<script setup lang="ts">
import type { RedisInfo } from '#/api/monitor/cache';
import { onMounted, reactive, ref } from 'vue';
import { Page } from '@vben/common-ui';
@@ -6,13 +8,16 @@ import { CommandLineIcon, MemoryIcon, RedisIcon } from '@vben/icons';
import { Button, Card, Col, Row } from 'ant-design-vue';
import { redisCacheInfo, type RedisInfo } from '#/api/monitor/cache';
import { redisCacheInfo } from '#/api/monitor/cache';
import { CommandChart, MemoryChart, RedisDescription } from './components';
const baseSpan = { lg: 12, md: 24, sm: 24, xl: 12, xs: 24 };
const chartData = reactive<{ command: any[]; memory: string }>({
const chartData = reactive<{
command: { name: string; value: string }[];
memory: string;
}>({
command: [],
memory: '0',
});
@@ -39,6 +44,7 @@ async function loadInfo() {
chartData.memory = usedMemory;
// 命令统计
chartData.command = ret.commandStats;
console.log(chartData.command);
// redis信息
redisInfo.value = { ...ret.info, dbSize: String(ret.dbSize) };
} catch (error) {

View File

@@ -1,11 +1,11 @@
import type { VNode } from 'vue';
import type { FormSchemaGetter } from '#/adapter/form';
import type { VxeGridProps } from '#/adapter/vxe-table';
import type { DescItem } from '#/components/description';
import type { VNode } from 'vue';
import { DictEnum } from '@vben/constants';
import { type FormSchemaGetter } from '#/adapter/form';
import { getDictOptions } from '#/utils/dict';
import { renderBrowserIcon, renderDict, renderOsIcon } from '#/utils/render';
@@ -68,7 +68,9 @@ export const columns: VxeGridProps['columns'] = [
field: 'os',
slots: {
default: ({ row }) => {
// Windows 10 or Windows Server 2016 太长了 分割一下 详情依旧能看到详细的
/**
* Windows 10 or Windows Server 2016 太长了 分割一下 详情依旧能看到详细的
*/
let value = row.os;
if (value) {
const split = value.split(' or ');

View File

@@ -1,19 +1,17 @@
<script setup lang="ts">
import type { Recordable } from '@vben/types';
import type { VbenFormProps } from '@vben/common-ui';
import type { VxeGridProps } from '#/adapter/vxe-table';
import type { LoginLog } from '#/api/monitor/logininfo/model';
import { ref } from 'vue';
import { Page, useVbenModal, type VbenFormProps } from '@vben/common-ui';
import { Page, useVbenModal } from '@vben/common-ui';
import { getVxePopupContainer } from '@vben/utils';
import { Modal, Popconfirm, Space } from 'ant-design-vue';
import {
useVbenVxeGrid,
vxeCheckboxChecked,
type VxeGridDefines,
type VxeGridProps,
} from '#/adapter/vxe-table';
import { useVbenVxeGrid, vxeCheckboxChecked } from '#/adapter/vxe-table';
import {
loginInfoClean,
loginInfoExport,
@@ -71,7 +69,6 @@ const gridOptions: VxeGridProps = {
},
},
rowConfig: {
isHover: true,
keyField: 'infoId',
},
id: 'monitor-logininfo-index',
@@ -82,9 +79,9 @@ const [BasicTable, tableApi] = useVbenVxeGrid({
formOptions,
gridOptions,
gridEvents: {
checkboxChange: (e: VxeGridDefines.CheckboxChangeEventParams) => {
checkboxChange: (e) => {
const records = e.$grid.getCheckboxRecords();
canUnlock.value = records.length === 1 && records[0]?.status === '1';
canUnlock.value = records.length === 1 && records[0]!.status === '1';
},
},
});
@@ -93,7 +90,7 @@ const [LoginInfoModal, modalApi] = useVbenModal({
connectedComponent: loginInfoModal,
});
function handlePreview(record: Recordable<any>) {
function handlePreview(record: LoginLog) {
modalApi.setData(record);
modalApi.open();
}
@@ -107,14 +104,14 @@ function handleClear() {
});
}
async function handleDelete(row: Recordable<any>) {
await loginInfoRemove(row.infoId);
async function handleDelete(row: LoginLog) {
await loginInfoRemove([row.infoId]);
await tableApi.query();
}
function handleMultiDelete() {
const rows = tableApi.grid.getCheckboxRecords();
const ids = rows.map((row: any) => row.infoId);
const ids = rows.map((row: LoginLog) => row.infoId);
Modal.confirm({
title: '提示',
okType: 'danger',

View File

@@ -1,10 +1,10 @@
import type { VxeGridProps } from '#/adapter/vxe-table';
import type { VNode } from 'vue';
import type { FormSchemaGetter } from '#/adapter/form';
import type { VxeGridProps } from '#/adapter/vxe-table';
import dayjs from 'dayjs';
import { type FormSchemaGetter } from '#/adapter/form';
import { renderBrowserIcon, renderOsIcon } from '#/utils/render';
export const querySchema: FormSchemaGetter = () => [

View File

@@ -1,12 +1,17 @@
<script setup lang="ts">
import type { Recordable } from '@vben/types';
import type { VbenFormProps } from '@vben/common-ui';
import { Page, type VbenFormProps } from '@vben/common-ui';
import type { VxeGridProps } from '#/adapter/vxe-table';
import type { OnlineUser } from '#/api/monitor/online/model';
import { ref } from 'vue';
import { Page } from '@vben/common-ui';
import { getVxePopupContainer } from '@vben/utils';
import { Popconfirm } from 'ant-design-vue';
import { useVbenVxeGrid, type VxeGridProps } from '#/adapter/vxe-table';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { forceLogout, onlineList } from '#/api/monitor/online';
import { columns, querySchema } from './data';
@@ -22,24 +27,30 @@ const formOptions: VbenFormProps = {
wrapperClass: 'grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4',
};
const onlineCount = ref(0);
const gridOptions: VxeGridProps = {
columns,
height: 'auto',
keepSource: true,
pagerConfig: {},
pagerConfig: {
enabled: false,
},
proxyConfig: {
ajax: {
query: async ({ page }, formValues) => {
return await onlineList({
pageNum: page.currentPage,
pageSize: page.pageSize,
query: async (_, formValues = {}) => {
const resp = await onlineList({
...formValues,
});
onlineCount.value = resp.total;
return resp;
},
},
},
scrollY: {
enabled: true,
gt: 0,
},
rowConfig: {
isHover: true,
keyField: 'tokenId',
},
id: 'monitor-online-index',
@@ -47,7 +58,7 @@ const gridOptions: VxeGridProps = {
const [BasicTable, tableApi] = useVbenVxeGrid({ formOptions, gridOptions });
async function handleForceOffline(row: Recordable<any>) {
async function handleForceOffline(row: OnlineUser) {
await forceLogout(row.tokenId);
await tableApi.query();
}
@@ -55,7 +66,16 @@ async function handleForceOffline(row: Recordable<any>) {
<template>
<Page :auto-content-height="true">
<BasicTable table-title="在线用户列表">
<BasicTable>
<template #toolbar-actions>
<div class="mr-1 pl-1 text-[1rem]">
<div>
在线用户列表 (
<span class="text-primary font-bold">{{ onlineCount }}</span>
人在线)
</div>
</div>
</template>
<template #action="{ row }">
<Popconfirm
:get-popup-container="getVxePopupContainer"

View File

@@ -1,3 +1,4 @@
import type { FormSchemaGetter } from '#/adapter/form';
import type { VxeGridProps } from '#/adapter/vxe-table';
import type { DescItem } from '#/components/description';
@@ -5,7 +6,6 @@ import { DictEnum } from '@vben/constants';
import { Tag } from 'ant-design-vue';
import { type FormSchemaGetter } from '#/adapter/form';
import { getDictOptions } from '#/utils/dict';
import {
renderDict,

View File

@@ -1,18 +1,19 @@
<script setup lang="ts">
import type { Recordable } from '@vben/types';
import type { VbenFormProps } from '@vben/common-ui';
import type { VxeGridProps } from '#/adapter/vxe-table';
import type { PageQuery } from '#/api/common';
import type { OperationLog } from '#/api/monitor/operlog/model';
import { Page, useVbenDrawer, type VbenFormProps } from '@vben/common-ui';
import { Page, useVbenDrawer } from '@vben/common-ui';
import { $t } from '@vben/locales';
import { Modal, Space } from 'ant-design-vue';
import {
addSortParams,
useVbenVxeGrid,
vxeCheckboxChecked,
type VxeGridProps,
vxeSortEvent,
} from '#/adapter/vxe-table';
import {
operLogClean,
@@ -60,18 +61,19 @@ const gridOptions: VxeGridProps<OperationLog> = {
pagerConfig: {},
proxyConfig: {
ajax: {
query: async ({ page }, formValues = {}) => {
const params: any = {
query: async ({ page, sorts }, formValues = {}) => {
const params: PageQuery = {
pageNum: page.currentPage,
pageSize: page.pageSize,
...formValues,
};
// 添加排序参数
addSortParams(params, sorts);
return await operLogList(params);
},
},
},
rowConfig: {
isHover: true,
keyField: 'operId',
},
sortConfig: {
@@ -87,7 +89,8 @@ const [BasicTable, tableApi] = useVbenVxeGrid({
formOptions,
gridOptions,
gridEvents: {
sortChange: (sortParams) => vxeSortEvent(tableApi, sortParams),
// 排序 重新请求接口
sortChange: () => tableApi.query(),
},
});
@@ -99,7 +102,7 @@ const [OperationPreviewDrawer, drawerApi] = useVbenDrawer({
* 预览
* @param record 操作日志记录
*/
function handlePreview(record: Recordable<any>) {
function handlePreview(record: OperationLog) {
drawerApi.setData({ record });
drawerApi.open();
}
@@ -120,7 +123,7 @@ function handleClear() {
*/
async function handleDelete() {
const rows = tableApi.grid.getCheckboxRecords();
const ids = rows.map((row: any) => row.operId);
const ids = rows.map((row: OperationLog) => row.operId);
Modal.confirm({
title: '提示',
okType: 'danger',

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