diff --git a/.eslintrc-auto-import.json b/.eslintrc-auto-import.json index 98226d9..cb7058a 100644 --- a/.eslintrc-auto-import.json +++ b/.eslintrc-auto-import.json @@ -315,6 +315,9 @@ "watchThrottled": true, "watchTriggerable": true, "watchWithFilter": true, - "whenever": true + "whenever": true, + "Slot": true, + "Slots": true, + "createRef": true } } diff --git a/README.md b/README.md index ebc088b..1c89bf9 100644 --- a/README.md +++ b/README.md @@ -2,16 +2,23 @@ - 本仓库为前端技术栈 [Vue3](https://v3.cn.vuejs.org) + [TS](https://www.typescriptlang.org/) + [Element Plus](https://element-plus.org/zh-CN) + [Vite](https://cn.vitejs.dev) 版本。 - 成员项目: 基于 vben5(ant-design-vue) 的前端项目 [ruoyi-plus-vben5](https://gitee.com/dapppp/ruoyi-plus-vben5) -- 配套后端代码仓库地址 -- [RuoYi-Vue-Plus 5.X(注意版本号)](https://gitee.com/dromara/RuoYi-Vue-Plus) -- [RuoYi-Cloud-Plus 2.X(注意版本号)](https://gitee.com/dromara/RuoYi-Cloud-Plus) +- 成员项目: 基于soybean 的前端项目 [ruoyi-plus-soybean](https://gitee.com/xlsea/ruoyi-plus-soybean) + +## 配套后端代码仓库地址 + +| 介绍 | 项目名 | 项目地址 | +|------------|:-----------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| 🔥 分布式集群框架 | RuoYi-Vue-Plus | - [Gitee](https://gitee.com/dromara/RuoYi-Vue-Plus)
- [GitHub](https://github.com/dromara/RuoYi-Vue-Plus)
- [GitCode](https://gitcode.com/dromara/RuoYi-Vue-Plus) | +| 🔥 微服务框架 | RuoYi-Cloud-Plus | - [Gitee](https://gitee.com/dromara/RuoYi-Cloud-Plus)
- [GitHub](https://github.com/dromara/RuoYi-Cloud-Plus)
- [GitCode](https://gitcode.com/dromara/RuoYi-Cloud-Plus) | + +## 分支说明 + +- ts分支(稳定发布主分支 生产可用) +- dev分支(开发分支 开发过程中使用) ## 前端运行 ```bash -# 克隆项目 -git clone https://gitee.com/JavaLionLi/plus-ui.git - # 安装依赖 npm install --registry=https://registry.npmmirror.com diff --git a/package.json b/package.json index 2bffcd8..e2fac7e 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package", - "name": "lx-vue-plus", - "version": "5.3.1-2.3.0", + "name": "ruoyi-vue-plus", + "version": "5.4.0-2.4.0", "description": "龙翔管理系统", "author": "LionLi", "license": "MIT", @@ -23,31 +23,31 @@ "@element-plus/icons-vue": "2.3.1", "@highlightjs/vue-plugin": "2.1.0", "@vueup/vue-quill": "1.2.0", - "@vueuse/core": "12.7.0", + "@vueuse/core": "13.1.0", "animate.css": "4.1.1", "await-to-js": "3.0.0", - "axios": "1.7.8", + "axios": "1.8.4", "crypto-js": "4.2.0", - "echarts": "5.5.0", - "element-plus": "2.8.8", + "echarts": "5.6.0", + "element-plus": "2.9.8", "file-saver": "2.0.5", "highlight.js": "11.9.0", "image-conversion": "2.1.1", "js-cookie": "3.0.5", "jsencrypt": "3.3.2", "nprogress": "0.2.0", - "pinia": "2.2.6", + "pinia": "3.0.2", "screenfull": "6.0.2", "vue": "3.5.13", "vue-cropper": "1.1.1", - "vue-i18n": "10.0.5", + "vue-i18n": "11.1.3", "vue-json-pretty": "2.4.0", - "vue-router": "4.4.5", - "vue-types": "5.1.3", - "vxe-table": "4.5.22" + "vue-router": "4.5.0", + "vue-types": "6.0.0", + "vxe-table": "4.13.7" }, "devDependencies": { - "@iconify/json": "2.2.276", + "@iconify/json": "^2.2.276", "@types/crypto-js": "4.2.2", "@types/file-saver": "2.0.7", "@types/js-cookie": "3.0.6", @@ -56,8 +56,8 @@ "@unocss/preset-attributify": "66.0.0", "@unocss/preset-icons": "66.0.0", "@unocss/preset-uno": "66.0.0", - "@vitejs/plugin-vue": "5.2.1", - "@vue/compiler-sfc": "3.4.23", + "@vitejs/plugin-vue": "5.2.3", + "@vue/compiler-sfc": "3.5.13", "@vue/eslint-config-prettier": "10.2.0", "@vue/eslint-config-typescript": "14.4.0", "autoprefixer": "10.4.20", @@ -66,19 +66,22 @@ "eslint-plugin-vue": "9.32.0", "globals": "16.0.0", "prettier": "3.5.2", - "sass": "1.84.0", - "typescript": "~5.7.3", + "sass": "1.87.0", + "typescript": "~5.8.3", "unocss": "66.0.0", - "unplugin-auto-import": "0.17.5", - "unplugin-icons": "0.18.5", - "unplugin-vue-components": "28.0.0", + "unplugin-auto-import": "19.1.2", + "unplugin-icons": "22.1.0", + "unplugin-vue-components": "28.5.0", "unplugin-vue-setup-extend-plus": "1.0.1", - "vite": "5.4.11", + "vite": "6.3.2", "vite-plugin-compression": "0.5.1", - "vite-plugin-svg-icons-ng": "^1.2.2", - "vite-plugin-vue-devtools": "7.7.1", - "vitest": "3.0.5", - "vue-tsc": "^2.2.2" + "vite-plugin-svg-icons-ng": "^1.4.0", + "vite-plugin-vue-devtools": "7.7.5", + "vitest": "3.1.2", + "vue-tsc": "^2.2.8" + }, + "overrides": { + "quill": "2.0.2" }, "engines": { "node": ">=18.18.0", diff --git a/src/api/operate/employee/index.ts b/src/api/operate/employee/index.ts new file mode 100644 index 0000000..bab2382 --- /dev/null +++ b/src/api/operate/employee/index.ts @@ -0,0 +1,240 @@ +import { DeptTreeVO, DeptVO } from './../dept/types'; +import { RoleVO } from '@/api/system/role/types'; +import request from '@/utils/request'; +import { AxiosPromise } from 'axios'; +import { UserForm, UserQuery, UserVO, UserInfoVO } from './types'; +import { parseStrEmpty } from '@/utils/ruoyi'; + +/** + * 查询用户列表 + * @param query + */ +export const listUser = (query: UserQuery): AxiosPromise => { + return request({ + url: '/operate/user/list', + method: 'get', + params: query + }); +}; + +/** + * 通过用户ids查询用户 + * @param userIds + */ +export const optionSelect = (userIds: (number | string)[]): AxiosPromise => { + return request({ + url: '/operate/user/optionselect?userIds=' + userIds, + method: 'get' + }); +}; + +/** + * 获取用户详情 + * @param userId + */ +export const getUser = (userId?: string | number): AxiosPromise => { + return request({ + url: '/operate/user/' + parseStrEmpty(userId), + method: 'get' + }); +}; + +/** + * 新增用户 + */ +export const addUser = (data: UserForm) => { + return request({ + url: '/operate/user', + method: 'post', + data: data + }); +}; + +/** + * 修改用户 + */ +export const updateUser = (data: UserForm) => { + return request({ + url: '/operate/user', + method: 'put', + data: data + }); +}; + +/** + * 删除用户 + * @param userId 用户ID + */ +export const delUser = (userId: Array | string | number) => { + return request({ + url: '/operate/user/' + userId, + method: 'delete' + }); +}; + +/** + * 用户密码重置 + * @param userId 用户ID + * @param password 密码 + */ +export const resetUserPwd = (userId: string | number, password: string) => { + const data = { + userId, + password + }; + return request({ + url: '/operate/user/resetPwd', + method: 'put', + headers: { + isEncrypt: true, + repeatSubmit: false + }, + data: data + }); +}; + +/** + * 用户状态修改 + * @param userId 用户ID + * @param status 用户状态 + */ +export const changeUserStatus = (userId: number | string, status: string) => { + const data = { + userId, + status + }; + return request({ + url: '/operate/user/changeStatus', + method: 'put', + data: data + }); +}; + +/** + * 查询用户个人信息 + */ +export const getUserProfile = (): AxiosPromise => { + return request({ + url: '/operate/user/profile', + method: 'get' + }); +}; + +/** + * 修改用户个人信息 + * @param data 用户信息 + */ +export const updateUserProfile = (data: UserForm) => { + return request({ + url: '/operate/user/profile', + method: 'put', + data: data + }); +}; + +/** + * 用户密码重置 + * @param oldPassword 旧密码 + * @param newPassword 新密码 + */ +export const updateUserPwd = (oldPassword: string, newPassword: string) => { + const data = { + oldPassword, + newPassword + }; + return request({ + url: '/operate/user/profile/updatePwd', + method: 'put', + headers: { + isEncrypt: true, + repeatSubmit: false + }, + data: data + }); +}; + +/** + * 用户头像上传 + * @param data 头像文件 + */ +export const uploadAvatar = (data: FormData) => { + return request({ + url: '/operate/user/profile/avatar', + method: 'post', + data: data + }); +}; + +/** + * 查询授权角色 + * @param userId 用户ID + */ +export const getAuthRole = (userId: string | number): AxiosPromise<{ user: UserVO; roles: RoleVO[] }> => { + return request({ + url: '/operate/user/authRole/' + userId, + method: 'get' + }); +}; + +/** + * 保存授权角色 + * @param data 用户ID + */ +export const updateAuthRole = (data: { userId: string; roleIds: string }) => { + return request({ + url: '/operate/user/authRole', + method: 'put', + params: data + }); +}; + +/** + * 查询当前部门的所有用户信息 + * @param deptId + */ +export const listUserByDeptId = (deptId: string | number): AxiosPromise => { + return request({ + url: '/operate/user/list/dept/' + deptId, + method: 'get' + }); +}; + +/** + * 查询部门下拉树结构 + */ +export const deptTreeSelect = (): AxiosPromise => { + return request({ + url: '/operate/user/deptTree', + method: 'get' + }); +}; + +/** + * 设置部门主管 + */ +export const setDeptAdmin = (userId: Array | string | number) => { + return request({ + url: '/operate/user/setDeptAdmin/' + userId, + method: 'get' + }); +}; + +export default { + listUser, + getUser, + optionSelect, + addUser, + updateUser, + delUser, + resetUserPwd, + changeUserStatus, + getUserProfile, + updateUserProfile, + updateUserPwd, + uploadAvatar, + getAuthRole, + updateAuthRole, + deptTreeSelect, + listUserByDeptId, + setDeptAdmin +}; diff --git a/src/api/operate/employee/types.ts b/src/api/operate/employee/types.ts new file mode 100644 index 0000000..60bf6c4 --- /dev/null +++ b/src/api/operate/employee/types.ts @@ -0,0 +1,90 @@ +import { RoleVO } from '@/api/system/role/types'; +import { PostVO } from '@/api/system/post/types'; + +/** + * 用户信息 + */ +export interface UserInfo { + user: UserVO; + roles: string[]; + permissions: string[]; +} + +/** + * 用户查询对象类型 + */ +export interface UserQuery extends PageQuery { + userName?: string; + nickName?: string; + phonenumber?: string; + status?: string; + deptId?: string | number; + roleId?: string | number; + userIds?: string; + employeeName?: string; +} + +/** + * 用户返回对象 + */ +export interface UserVO extends BaseEntity { + userId: string | number; + tenantId: string; + deptId: number; + userName: string; + nickName: string; + userType: string; + email: string; + phonenumber: string; + sex: string; + avatar: string; + status: string; + delFlag: string; + loginIp: string; + loginDate: string; + remark: string; + deptName: string; + roles: RoleVO[]; + roleIds: any; + postIds: any; + roleId: any; + admin: boolean; + employeeName: string; +} + +/** + * 用户表单类型 + */ +export interface UserForm { + id?: string; + userId?: string; + deptId?: number; + userName: string; + employeeName?: string; + nickName?: string; + password: string; + phonenumber?: string; + email?: string; + sex?: string; + status: string; + remark?: string; + postIds: string[]; + roleIds: string[]; + +} + +export interface UserInfoVO { + user: UserVO; + roles: RoleVO[]; + roleIds: string[]; + posts: PostVO[]; + postIds: string[]; + roleGroup: string; + postGroup: string; +} + +export interface ResetPwdForm { + oldPassword: string; + newPassword: string; + confirmPassword: string; +} diff --git a/src/api/system/dept/index.ts b/src/api/system/dept/index.ts index f16cb2c..6177506 100644 --- a/src/api/system/dept/index.ts +++ b/src/api/system/dept/index.ts @@ -1,6 +1,6 @@ import request from '@/utils/request'; import { AxiosPromise } from 'axios'; -import {DeptForm, DeptQuery, DeptTreeVO, DeptVO} from './types'; +import { DeptForm, DeptQuery, DeptTreeVO, DeptVO } from './types'; // 查询部门列表 export const listDept = (query?: DeptQuery) => { diff --git a/src/api/system/menu/index.ts b/src/api/system/menu/index.ts index 7a0cf74..81461fd 100644 --- a/src/api/system/menu/index.ts +++ b/src/api/system/menu/index.ts @@ -68,3 +68,11 @@ export const delMenu = (menuId: string | number) => { method: 'delete' }); }; + +// 级联删除菜单 +export const cascadeDelMenu = (menuIds: Array) => { + return request({ + url: '/system/menu/cascade/' + menuIds, + method: 'delete' + }); +}; diff --git a/src/api/system/user/index.ts b/src/api/system/user/index.ts index 987d185..4a46183 100644 --- a/src/api/system/user/index.ts +++ b/src/api/system/user/index.ts @@ -1,4 +1,4 @@ -import {DeptTreeVO, DeptVO} from './../dept/types'; +import { DeptTreeVO, DeptVO } from './../dept/types'; import { RoleVO } from '@/api/system/role/types'; import request from '@/utils/request'; import { AxiosPromise } from 'axios'; diff --git a/src/api/system/user/types.ts b/src/api/system/user/types.ts index ff7a811..60bf6c4 100644 --- a/src/api/system/user/types.ts +++ b/src/api/system/user/types.ts @@ -1,6 +1,5 @@ import { RoleVO } from '@/api/system/role/types'; import { PostVO } from '@/api/system/post/types'; -import { string } from 'vue-types'; /** * 用户信息 @@ -16,6 +15,7 @@ export interface UserInfo { */ export interface UserQuery extends PageQuery { userName?: string; + nickName?: string; phonenumber?: string; status?: string; deptId?: string | number; @@ -61,6 +61,7 @@ export interface UserForm { deptId?: number; userName: string; employeeName?: string; + nickName?: string; password: string; phonenumber?: string; email?: string; diff --git a/src/api/workflow/instance/index.ts b/src/api/workflow/instance/index.ts index 42d748d..fde16a2 100644 --- a/src/api/workflow/instance/index.ts +++ b/src/api/workflow/instance/index.ts @@ -31,9 +31,9 @@ export const pageByFinish = (query: FlowInstanceQuery): AxiosPromise { +export const flowHisTaskList = (businessId: string | number) => { return request({ - url: `/workflow/instance/flowImage/${businessId}` + '?t' + Math.random(), + url: `/workflow/instance/flowHisTaskList/${businessId}` + '?t' + Math.random(), method: 'get' }); }; diff --git a/src/assets/styles/sidebar.scss b/src/assets/styles/sidebar.scss index f19abe4..7410b6f 100644 --- a/src/assets/styles/sidebar.scss +++ b/src/assets/styles/sidebar.scss @@ -26,7 +26,7 @@ z-index: 1001; overflow: hidden; -webkit-box-shadow: 2px 0 6px rgba(0, 21, 41, 0.35); - box-shadow: 2px 0 6px rgba(0, 21, 41, 0.35); + box-shadow: 0 0 8px 0 rgba(0, 0, 0, 0.1); // reset element-ui css .horizontal-collapse-transition { diff --git a/src/components/Breadcrumb/index.vue b/src/components/Breadcrumb/index.vue index fd91abc..a9cdef4 100644 --- a/src/components/Breadcrumb/index.vue +++ b/src/components/Breadcrumb/index.vue @@ -44,7 +44,7 @@ const findPathNum = (str, char = '/') => { return str.split(char).length - 1; }; const getMatched = (pathList, routeList, matched) => { - let data = routeList.find((item) => item.path == pathList[0] || (item.name += '').toLowerCase() == pathList[0]); + const data = routeList.find((item) => item.path == pathList[0] || (item.name += '').toLowerCase() == pathList[0]); if (data) { matched.push(data); if (data.children && pathList.length) { diff --git a/src/components/Editor/index.vue b/src/components/Editor/index.vue index ba70fbb..67394aa 100644 --- a/src/components/Editor/index.vue +++ b/src/components/Editor/index.vue @@ -95,7 +95,7 @@ const options = ref({ }); const styles = computed(() => { - let style: any = {}; + const style: any = {}; if (props.minHeight) { style.minHeight = `${props.minHeight}px`; } @@ -121,9 +121,9 @@ const handleUploadSuccess = (res: any) => { // 如果上传成功 if (res.code === 200) { // 获取富文本实例 - let quill = toRaw(quillEditorRef.value).getQuill(); + const quill = toRaw(quillEditorRef.value).getQuill(); // 获取光标位置 - let length = quill.selection.savedRange.index; + const length = quill.selection.savedRange.index; // 插入图片,res为服务器返回的图片链接地址 quill.insertEmbed(length, 'image', res.data.url); // 调整光标到最后 diff --git a/src/components/FileUpload/index.vue b/src/components/FileUpload/index.vue index 7256585..1767b1b 100644 --- a/src/components/FileUpload/index.vue +++ b/src/components/FileUpload/index.vue @@ -176,7 +176,7 @@ const handleUploadSuccess = (res: any, file: UploadFile) => { // 删除文件 const handleDelete = (index: number) => { - let ossId = fileList.value[index].ossId; + const ossId = fileList.value[index].ossId; delOss(ossId); fileList.value.splice(index, 1); emit('update:modelValue', listToString(fileList.value)); diff --git a/src/components/ImagePreview/index.vue b/src/components/ImagePreview/index.vue index 98e6479..afe02c7 100644 --- a/src/components/ImagePreview/index.vue +++ b/src/components/ImagePreview/index.vue @@ -27,7 +27,7 @@ const realSrc = computed(() => { if (!props.src) { return; } - let real_src = props.src.split(',')[0]; + const real_src = props.src.split(',')[0]; return real_src; }); @@ -35,8 +35,8 @@ const realSrcList = computed(() => { if (!props.src) { return []; } - let real_src_list = props.src.split(','); - let srcList: string[] = []; + const real_src_list = props.src.split(','); + const srcList: string[] = []; real_src_list.forEach((item: string) => { if (item.trim() === '') { return; diff --git a/src/components/ImageUpload/index.vue b/src/components/ImageUpload/index.vue index 2e53dcb..f81e4bc 100644 --- a/src/components/ImageUpload/index.vue +++ b/src/components/ImageUpload/index.vue @@ -189,7 +189,7 @@ const handleUploadSuccess = (res: any, file: UploadFile) => { const handleDelete = (file: UploadFile): boolean => { const findex = fileList.value.map((f) => f.name).indexOf(file.name); if (findex > -1 && uploadList.value.length === number.value) { - let ossId = fileList.value[findex].ossId; + const ossId = fileList.value[findex].ossId; delOss(ossId); fileList.value.splice(findex, 1); emit('update:modelValue', listToString(fileList.value)); @@ -225,7 +225,7 @@ const handlePictureCardPreview = (file: any) => { const listToString = (list: any[], separator?: string) => { let strs = ''; separator = separator || ','; - for (let i in list) { + for (const i in list) { if (undefined !== list[i].ossId && list[i].url.indexOf('blob:') !== 0) { strs += list[i].ossId + separator; } diff --git a/src/components/Process/approvalButton.vue b/src/components/Process/approvalButton.vue new file mode 100644 index 0000000..cb4fe8d --- /dev/null +++ b/src/components/Process/approvalButton.vue @@ -0,0 +1,56 @@ + + diff --git a/src/components/Process/approvalRecord.vue b/src/components/Process/approvalRecord.vue index 84fbfb1..2413f08 100644 --- a/src/components/Process/approvalRecord.vue +++ b/src/components/Process/approvalRecord.vue @@ -3,21 +3,7 @@ -
- - - -
+
@@ -73,10 +59,10 @@
diff --git a/src/components/Process/flowChart.vue b/src/components/Process/flowChart.vue new file mode 100644 index 0000000..3c4dd68 --- /dev/null +++ b/src/components/Process/flowChart.vue @@ -0,0 +1,40 @@ +