diff --git a/apps/web-antd/src/api/system/post/index.ts b/apps/web-antd/src/api/system/post/index.ts new file mode 100644 index 00000000..13952892 --- /dev/null +++ b/apps/web-antd/src/api/system/post/index.ts @@ -0,0 +1,46 @@ +import type { Post } from './model'; + +import type { ID, IDS, PageQuery } from '#/api/common'; + +import { commonExport } from '#/api/helper'; +import { requestClient } from '#/api/request'; + +enum Api { + postExport = '/system/post/export', + postList = '/system/post/list', + postSelect = '/system/post/optionselect', + root = '/system/post', +} + +/** + * 获取岗位列表 + * @param params 参数 + * @returns Post[] + */ +export function postList(params?: PageQuery) { + return requestClient.get(Api.postList, { params }); +} + +export function postExport(data: any) { + return commonExport(Api.postExport, data); +} + +export function postInfo(postId: ID) { + return requestClient.get(`${Api.root}/${postId}`); +} + +export function postAdd(data: any) { + return requestClient.postWithMsg(Api.root, data); +} + +export function postUpdate(data: any) { + return requestClient.putWithMsg(Api.root, data); +} + +export function postRemove(postIds: IDS) { + return requestClient.deleteWithMsg(`${Api.root}/${postIds}`); +} + +export function postOptionSelect(deptId: ID) { + return requestClient.get(Api.postSelect, { params: { deptId } }); +} diff --git a/apps/web-antd/src/api/system/post/model.d.ts b/apps/web-antd/src/api/system/post/model.d.ts new file mode 100644 index 00000000..b75f3c01 --- /dev/null +++ b/apps/web-antd/src/api/system/post/model.d.ts @@ -0,0 +1,12 @@ +/** + * @description: Post interface + */ +export interface Post { + postId: number; + postCode: string; + postName: string; + postSort: number; + status: string; + remark: string; + createTime: string; +} diff --git a/apps/web-antd/src/api/system/profile/index.ts b/apps/web-antd/src/api/system/profile/index.ts index 3b67d583..4eb3f015 100644 --- a/apps/web-antd/src/api/system/profile/index.ts +++ b/apps/web-antd/src/api/system/profile/index.ts @@ -1,7 +1,8 @@ import type { FileCallBack, UpdatePasswordParam, UserProfile } from './model'; +import { buildUUID } from '@vben/utils'; + import { requestClient } from '#/api/request'; -import { buildUUID } from '#/utils/uuid'; enum Api { root = '/system/user/profile', diff --git a/apps/web-antd/src/api/system/user/index.ts b/apps/web-antd/src/api/system/user/index.ts new file mode 100644 index 00000000..4d81bf5d --- /dev/null +++ b/apps/web-antd/src/api/system/user/index.ts @@ -0,0 +1,164 @@ +import type { + DeptTree, + ResetPwdParam, + User, + UserImportParam, + UserInfoResponse, +} from './model'; + +import type { ID, IDS, PageQuery, PageResult } from '#/api/common'; + +import { commonExport, ContentTypeEnum } from '#/api/helper'; +import { requestClient } from '#/api/request'; + +enum Api { + deptTree = '/system/user/deptTree', + listDeptUsers = '/system/user/list/dept', + root = '/system/user', + userAuthRole = '/system/user/authRole', + userExport = '/system/user/export', + userImport = '/system/user/importData', + userImportTemplate = '/system/user/importTemplate', + userList = '/system/user/list', + userResetPassword = '/system/user/resetPwd', + userStatusChange = '/system/user/changeStatus', +} + +/** + * 获取用户列表 + * @param params + * @returns User + */ +export function userList(params?: PageQuery) { + return requestClient.get>(Api.userList, { params }); +} + +/** + * 导出excel + * @param data data + * @returns blob + */ +export function userExport(data: any) { + return commonExport(Api.userExport, data); +} + +/** + * 从excel导入用户 + * @param data + * @returns void + */ +export function userImportData(data: UserImportParam) { + return requestClient.post(Api.userImport, data, { + // 返回的msg包含
用modal显示 + errorMessageMode: 'modal', + headers: { + 'Content-Type': ContentTypeEnum.FORM_DATA, + }, + successMessageMode: 'modal', + }); +} + +/** + * 下载用户导入模板 + * @returns blob + */ +export function downloadImportTemplate() { + return requestClient.post( + Api.userImportTemplate, + {}, + { + isTransformResponse: false, + responseType: 'blob', + }, + ); +} + +/** + * 可以不传ID 返回部门和角色options 需要获得原始数据 + * @param userId 用户ID + * @returns 用户信息 + */ +export function findUserInfo(userId?: ID) { + const url = userId ? `${Api.root}/${userId}` : `${Api.root}`; + return requestClient.get(url); +} + +/** + * 新增用户 + * @param data data + * @returns void + */ +export function userAdd(data: any) { + return requestClient.postWithMsg(Api.root, data); +} + +/** + * 更新用户 + * @param data data + * @returns void + */ +export function userUpdate(data: any) { + return requestClient.putWithMsg(Api.root, data); +} + +/** + * 更新用户状态 + * @param data data + * @returns void + */ +export function userStatusChange(data: any) { + return requestClient.putWithMsg(Api.userStatusChange, data); +} + +/** + * 删除用户 + * @param userIds 用户ID数组 + * @returns void + */ +export function userRemove(userIds: IDS) { + return requestClient.deleteWithMsg(`${Api.root}/${userIds}`); +} + +/** + * 重置用户密码 需要加密 + * @param data + * @returns void + */ +export function userResetPassword(data: ResetPwdParam) { + return requestClient.putWithMsg(Api.userResetPassword, data, { + encrypt: true, + }); +} + +/** + * 这个方法未调用过 + * @param userId + * @returns void + */ +export function getUserAuthRole(userId: ID) { + return requestClient.get(`${Api.userAuthRole}/${userId}`); +} + +/** + * 这个方法未调用过 + * @param userId + * @returns void + */ +export function userAuthRoleUpdate(userId: ID, roleIds: number[]) { + return requestClient.putWithMsg(Api.userAuthRole, { roleIds, userId }); +} + +/** + * 获取部门树 + * @returns 部门树数组 + */ +export function getDeptTree() { + return requestClient.get(Api.deptTree); +} + +/** + * 获取部门下的所有用户信息 + */ +export function listUserByDeptId(deptId: ID) { + return requestClient.get(`${Api.listDeptUsers}/${deptId}`); +} diff --git a/apps/web-antd/src/api/system/user/model.d.ts b/apps/web-antd/src/api/system/user/model.d.ts new file mode 100644 index 00000000..c9ec4ad4 --- /dev/null +++ b/apps/web-antd/src/api/system/user/model.d.ts @@ -0,0 +1,116 @@ +/** + * @description: 用户导入 + * @param updateSupport 是否覆盖数据 + * @param file excel文件 + */ +export interface UserImportParam { + updateSupport: boolean; + file: Blob | File; +} + +/** + * @description: 重置密码 + */ +export interface ResetPwdParam { + userId: string; + password: string; +} + +export interface Dept { + deptId: number; + parentId: number; + parentName?: string; + ancestors: string; + deptName: string; + orderNum: number; + leader: string; + phone?: string; + email?: string; + status: string; + createTime?: string; +} + +export interface Role { + roleId: string; + roleName: string; + roleKey: string; + roleSort: number; + dataScope: string; + menuCheckStrictly?: boolean; + deptCheckStrictly?: boolean; + status: string; + remark: string; + createTime?: string; + flag: boolean; + superAdmin: boolean; +} + +export interface User { + userId: string; + tenantId: string; + deptId: number; + userName: string; + nickName: string; + userType: string; + email: string; + phonenumber: string; + sex: string; + avatar?: string; + status: string; + loginIp: string; + loginDate: string; + remark: string; + createTime: string; + dept: Dept; + roles: Role[]; + roleIds?: string[]; + postIds?: number[]; + roleId: string; +} + +export interface Post { + postId: number; + postCode: string; + postName: string; + postSort: number; + status: string; + remark: string; + createTime: string; +} + +/** + * @description 用户信息 + * @param user 用户个人信息 + * @param roleIds 角色IDS 不传id为空 + * @param roles 所有的角色 + * @param postIds 岗位IDS 不传id为空 + * @param posts 所有的岗位 + */ +export interface UserInfoResponse { + user?: User; + roleIds?: string[]; + roles: Role[]; + postIds?: number[]; + posts: Post[]; +} + +/** + * @description: 部门树 + */ +export interface DeptTree { + id: number; + /** + * antd组件必须要这个属性 实际是没有这个属性的 + */ + key: string; + parentId: number; + label: string; + weight: number; + children?: DeptTree[]; +} + +export interface DeptTreeData { + id: number; + label: string; + children?: DeptTreeData[]; +} diff --git a/apps/web-antd/src/components/tinymce/src/editor.vue b/apps/web-antd/src/components/tinymce/src/editor.vue index e4a8e2dd..52ebd920 100644 --- a/apps/web-antd/src/components/tinymce/src/editor.vue +++ b/apps/web-antd/src/components/tinymce/src/editor.vue @@ -17,12 +17,12 @@ import { } from 'vue'; import { preferences, usePreferences } from '@vben/preferences'; +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 { buildShortUUID } from '#/utils/uuid'; import { bindHandlers } from './helper'; import ImgUpload from './img-upload.vue'; diff --git a/apps/web-antd/src/views/system/client/secret-input.vue b/apps/web-antd/src/views/system/client/secret-input.vue index d0e4cd16..b5dfdfdd 100644 --- a/apps/web-antd/src/views/system/client/secret-input.vue +++ b/apps/web-antd/src/views/system/client/secret-input.vue @@ -1,10 +1,9 @@ diff --git a/apps/web-antd/src/views/system/post/post-drawer.vue b/apps/web-antd/src/views/system/post/post-drawer.vue new file mode 100644 index 00000000..9224d1f5 --- /dev/null +++ b/apps/web-antd/src/views/system/post/post-drawer.vue @@ -0,0 +1,115 @@ + + + diff --git a/packages/utils/src/helpers/index.ts b/packages/utils/src/helpers/index.ts index da2cd8d7..1e7f7f4a 100644 --- a/packages/utils/src/helpers/index.ts +++ b/packages/utils/src/helpers/index.ts @@ -5,4 +5,6 @@ export * from './generate-routes-frontend'; export * from './get-popup-container'; export * from './merge-route-modules'; export * from './reset-routes'; +export * from './tree'; export * from './unmount-global-loading'; +export * from './uuid'; diff --git a/packages/utils/src/helpers/tree.ts b/packages/utils/src/helpers/tree.ts new file mode 100644 index 00000000..9b0f4b7e --- /dev/null +++ b/packages/utils/src/helpers/tree.ts @@ -0,0 +1,400 @@ +/* eslint-disable @typescript-eslint/no-non-null-assertion */ +interface TreeHelperConfig { + children: string; + id: string; + pid: string; +} + +type Fn = (node: any, parentNode?: any) => any; + +// 默认配置 +const DEFAULT_CONFIG: TreeHelperConfig = { + id: 'id', + pid: 'parentId', + children: 'children', +}; + +// 获取配置。 Object.assign 从一个或多个源对象复制到目标对象 +const getConfig = (config: Partial) => + Object.assign({}, DEFAULT_CONFIG, config); + +// tree from list +// 列表中的树 +export function listToTree( + list: any[], + config: Partial = {}, +): T[] { + const conf = getConfig(config) as TreeHelperConfig; + const nodeMap = new Map(); + const result: T[] = []; + const { id, pid, children } = conf; + + for (const node of list) { + node[children] = node[children] || []; + nodeMap.set(node[id], node); + } + for (const node of list) { + const parent = nodeMap.get(node[pid]); + (parent ? parent[children] : result).push(node); + } + return result; +} + +export function treeToList( + tree: any, + config: Partial = {}, +): T { + config = getConfig(config); + const { children } = config; + const result: any = [...tree]; + for (let i = 0; i < result.length; i++) { + if (!result[i][children!]) continue; + result.splice(i + 1, 0, ...result[i][children!]); + } + return result; +} + +export function findNode( + tree: any, + func: Fn, + config: Partial = {}, +): null | T { + config = getConfig(config); + const { children } = config; + const list = [...tree]; + for (const node of list) { + if (func(node)) return node; + node[children!] && list.push(...node[children!]); + } + return null; +} + +export function findNodeAll( + tree: any, + func: Fn, + config: Partial = {}, +): T[] { + config = getConfig(config); + const { children } = config; + const list = [...tree]; + const result: T[] = []; + for (const node of list) { + func(node) && result.push(node); + node[children!] && list.push(...node[children!]); + } + return result; +} + +export function findPath( + tree: any, + func: Fn, + config: Partial = {}, +): null | T | T[] { + config = getConfig(config); + const path: T[] = []; + const list = [...tree]; + const visitedSet = new Set(); + const { children } = config; + while (list.length > 0) { + const node = list[0]; + if (visitedSet.has(node)) { + path.pop(); + list.shift(); + } else { + visitedSet.add(node); + node[children!] && list.unshift(...node[children!]); + path.push(node); + if (func(node)) { + return path; + } + } + } + return null; +} + +export function findPathAll( + tree: any, + func: Fn, + config: Partial = {}, +) { + config = getConfig(config); + const path: any[] = []; + const list = [...tree]; + const result: any[] = []; + const { children } = config; + const visitedSet = new Set(); + while (list.length > 0) { + const node = list[0]; + if (visitedSet.has(node)) { + path.pop(); + list.shift(); + } else { + visitedSet.add(node); + node[children!] && list.unshift(...node[children!]); + path.push(node); + func(node) && result.push([...path]); + } + } + return result; +} + +export function filter( + tree: T[], + func: (n: T) => boolean, + // Partial 将 T 中的所有属性设为可选 + config: Partial = {}, +): T[] { + // 获取配置 + config = getConfig(config); + const children = config.children as string; + + function listFilter(list: T[]) { + return list + .map((node: any) => ({ ...node })) + .filter((node) => { + // 递归调用 对含有children项 进行再次调用自身函数 listFilter + node[children] = node[children] && listFilter(node[children]); + // 执行传入的回调 func 进行过滤 + return func(node) || (node[children] && node[children].length > 0); + }); + } + + return listFilter(tree); +} + +export function forEach( + tree: T[], + func: (n: T) => any, + config: Partial = {}, +): void { + config = getConfig(config); + const list: any[] = [...tree]; + const { children } = config; + for (let i = 0; i < list.length; i++) { + // func 返回true就终止遍历,避免大量节点场景下无意义循环,引起浏览器卡顿 + if (func(list[i])) { + return; + } + children && + list[i][children] && + list.splice(i + 1, 0, ...list[i][children]); + } +} + +/** + * @description: Extract tree specified structure + * @description: 提取树指定结构 + */ +export function treeMap( + treeData: T[], + opt: { children?: string; conversion: Fn }, +): T[] { + return treeData.map((item) => treeMapEach(item, opt)); +} + +/** + * @description: Extract tree specified structure + * @description: 提取树指定结构 + */ +export function treeMapEach( + data: any, + { conversion, children = 'children' }: { children?: string; conversion: Fn }, +) { + const haveChildren = + Array.isArray(data[children]) && data[children].length > 0; + const conversionData = conversion(data) || {}; + return haveChildren + ? { + ...conversionData, + [children]: data[children].map((i: number) => + treeMapEach(i, { + children, + conversion, + }), + ), + } + : { + ...conversionData, + }; +} + +/** + * 递归遍历树结构 + * @param treeDatas 树 + * @param callBack 回调 + * @param parentNode 父节点 + */ +export function eachTree(treeDatas: any[], callBack: Fn, parentNode = {}) { + treeDatas.forEach((element) => { + const newNode = callBack(element, parentNode) || element; + if (element.children) { + eachTree(element.children, callBack, newNode); + } + }); +} + +// 如果节点的children为空, 则删除children属性 +export function removeEmptyChildren(data: any[], childrenField = 'children') { + data.forEach((item) => { + if (!item[childrenField]) { + return; + } + if (item[childrenField].length > 0) { + removeEmptyChildren(item[childrenField]); + } else { + Reflect.deleteProperty(item, childrenField); + } + }); +} + +// eslint-disable-next-line jsdoc/require-returns-check +/** + * + * 添加全名 如 祖先节点-父节点-子节点 + * @param treeData 已经是tree数据 + * @param labelName 标签的字段名称 + * @param splitStr 分隔符 + * @returns void 无返回值 会修改原始数据 + */ +export function addFullName( + treeData: any[], + labelName = 'label', + splitStr = '-', +) { + function addFullNameProperty(node: any, parentNames: any[] = []) { + const fullNameParts = [...parentNames, node[labelName]]; + node.fullName = fullNameParts.join(splitStr); + if (node.children && node.children.length > 0) { + node.children.forEach((childNode: any) => { + addFullNameProperty(childNode, fullNameParts); + }); + } + } + + treeData.forEach((item: any) => { + addFullNameProperty(item); + }); +} + +/** + * https://blog.csdn.net/Web_J/article/details/129281329 + * 给出节点nodeId 找到所有父节点ID + * @param treeList 树形结构list + * @param nodeId 要寻找的节点ID + * @param config config + * @returns 父节点ID数组 + */ +export function findParentsIds( + treeList: any[], + nodeId: number, + config: Partial = {}, +) { + const conf = getConfig(config) as TreeHelperConfig; + const { id, children } = conf; + + // 用于存储所有父节点ID的数组 + const parentIds: number[] = []; + + function traverse(node: any, nodeId: number) { + if (node[id] === nodeId) { + return true; + } + if (node[children]) { + // 如果当前节点有子节点,则继续遍历子节点 + for (const childNode of node[children]) { + if (traverse(childNode, nodeId)) { + // 如果在子节点中找到了子节点的父节点,则将当前节点的ID添加到父节点ID数组中,并返回true表示已经找到了子节点 + parentIds.push(node[id]); + return true; + } + } + } + return false; + } + + for (const node of treeList) { + if (traverse(node, nodeId)) { + // 如果在当前节点的子树中找到了子节点的父节点,则直接退出循环 + break; + } + } + + return parentIds.sort(); +} + +/** + * 给出节点数组 找到所有父节点ID + * @param treeList 树形结构list + * @param nodeIds 要寻找的节点ID list + * @param config config + * @returns 父节点ID数组 + */ +export function findGroupParentIds( + treeList: any[], + nodeIds: number[], + config: Partial = {}, +) { + // 用于存储所有父节点ID的Set 主要为了去重 + const parentIds = new Set(); + + nodeIds.forEach((nodeId) => { + findParentsIds(treeList, nodeId, config).forEach((parentId) => { + parentIds.add(parentId); + }); + }); + + return [...parentIds].sort(); +} + +/** + * 找到所有ID 返回数组 + * @param treeList list + * @param config + * @returns ID数组 + */ +export function findAllIds( + treeList: any[], + config: Partial = DEFAULT_CONFIG, +) { + const conf = getConfig(config) as TreeHelperConfig; + const { id, children } = conf; + const ids: number[] = []; + + treeList.forEach((item) => { + if (item[children]) { + const tempIds = findAllIds(item[children], config); + ids.push(...tempIds); + } + ids.push(item[id]); + }); + + return [...ids].sort(); +} + +/** + * @description 这里抄的filterByLevel函数 + * @description 主要用于获取指定层级的节点数组 + */ +export function findIdsByLevel( + level = 1, + list?: any[], + config: Partial = DEFAULT_CONFIG, + currentLevel = 1, +) { + if (!level) { + return []; + } + const res: (number | string)[] = []; + const data = list || []; + for (const item of data) { + const { id: keyField, children: childrenField } = config; + const key = keyField ? item[keyField] : ''; + const children = childrenField ? item[childrenField] : []; + res.push(key); + if (children && children.length > 0 && currentLevel < level) { + currentLevel += 1; + res.push(...findIdsByLevel(level, children, config, currentLevel)); + } + } + return res as number[] | string[]; +} diff --git a/packages/utils/src/helpers/uuid.ts b/packages/utils/src/helpers/uuid.ts new file mode 100644 index 00000000..81c49a09 --- /dev/null +++ b/packages/utils/src/helpers/uuid.ts @@ -0,0 +1,42 @@ +const hexList: string[] = []; +for (let i = 0; i <= 15; i++) { + hexList[i] = i.toString(16); +} + +export function buildUUID(): string { + let uuid = ''; + for (let i = 1; i <= 36; i++) { + switch (i) { + case 9: + case 14: + case 19: + case 24: { + uuid += '-'; + + break; + } + case 15: { + uuid += 4; + + break; + } + case 20: { + uuid += hexList[(Math.random() * 4) | 8]; + + break; + } + default: { + uuid += hexList[Math.trunc(Math.random() * 16)]; + } + } + } + return uuid.replaceAll('-', ''); +} + +let unique = 0; +export function buildShortUUID(prefix = ''): string { + const time = Date.now(); + const random = Math.floor(Math.random() * 1_000_000_000); + unique++; + return `${prefix}_${random}${unique}${String(time)}`; +}