From d8f4e0a1aae9e302de170300002e1ac9238fcd86 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Sat, 29 Mar 2025 15:10:08 +0800 Subject: [PATCH] =?UTF-8?q?feat=EF=BC=9A=E5=AE=8C=E5=96=84=20dept=20?= =?UTF-8?q?=E9=83=A8=E9=97=A8=2060%=EF=BC=88=E6=96=B0=E5=A2=9E=E3=80=81?= =?UTF-8?q?=E4=BF=AE=E6=94=B9=E3=80=81=E5=88=A0=E9=99=A4=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/web-antd/src/api/system/user/index.ts | 75 +++ apps/web-antd/src/store/dict.ts | 1 + apps/web-antd/src/utils/constants.ts | 466 ++++++++++++++++++ apps/web-antd/src/utils/dict.ts | 1 + apps/web-antd/src/utils/tree.ts | 56 +++ apps/web-antd/src/views/system/dept/data.ts | 125 +++-- apps/web-antd/src/views/system/dept/index.vue | 29 +- .../src/views/system/dept/modules/form.vue | 53 +- .../src/locales/langs/en-US/system.json | 10 - .../src/locales/langs/zh-CN/system.json | 11 - 10 files changed, 736 insertions(+), 91 deletions(-) create mode 100644 apps/web-antd/src/api/system/user/index.ts create mode 100644 apps/web-antd/src/utils/constants.ts create mode 100644 apps/web-antd/src/utils/tree.ts 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 000000000..be4b19b9e --- /dev/null +++ b/apps/web-antd/src/api/system/user/index.ts @@ -0,0 +1,75 @@ +import { requestClient } from '#/api/request'; + +export namespace SystemUserApi { + /** 用户信息 */ + export interface SystemUser { + id?: number; + username: string; + nickname: string; + deptId: number; + postIds: string[]; + email: string; + mobile: string; + sex: number; + avatar: string; + loginIp: string; + status: number; + remark: string; + createTime?: Date; + } +} + +/** 查询用户管理列表 */ +export function getUserPage(params: any) { + return requestClient.get('/system/user/page', { params }); +} + +/** 查询所有用户列表 */ +export function getAllUser() { + return requestClient.get('/system/user/all'); +} + +/** 查询用户详情 */ +export function getUser(id: number) { + return requestClient.get(`/system/user/get?id=${id}`); +} + +/** 新增用户 */ +export function createUser(data: SystemUserApi.SystemUser) { + return requestClient.post('/system/user/create', data); +} + +/** 修改用户 */ +export function updateUser(data: SystemUserApi.SystemUser) { + return requestClient.put('/system/user/update', data); +} + +/** 删除用户 */ +export function deleteUser(id: number) { + return requestClient.delete(`/system/user/delete?id=${id}`); +} + +/** 导出用户 */ +export function exportUser(params: any) { + return requestClient.download('/system/user/export', params); +} + +/** 下载用户导入模板 */ +export function importUserTemplate() { + return requestClient.download('/system/user/get-import-template'); +} + +/** 用户密码重置 */ +export function resetUserPwd(id: number, password: string) { + return requestClient.put('/system/user/update-password', { id, password }); +} + +/** 用户状态修改 */ +export function updateUserStatus(id: number, status: number) { + return requestClient.put('/system/user/update-status', { id, status }); +} + +/** 获取用户精简信息列表 */ +export function getSimpleUserList(): Promise { + return requestClient.get('/system/user/simple-list'); +} diff --git a/apps/web-antd/src/store/dict.ts b/apps/web-antd/src/store/dict.ts index 4cc4c8865..37f636d2e 100644 --- a/apps/web-antd/src/store/dict.ts +++ b/apps/web-antd/src/store/dict.ts @@ -13,6 +13,7 @@ interface DictState { dictCache: Dict; } +// TODO @芋艿:可以共享么? export const useDictStore = defineStore('dict', { actions: { getDictData(dictType: string, value: any) { diff --git a/apps/web-antd/src/utils/constants.ts b/apps/web-antd/src/utils/constants.ts new file mode 100644 index 000000000..39803bb7d --- /dev/null +++ b/apps/web-antd/src/utils/constants.ts @@ -0,0 +1,466 @@ +// todo @芋艿:要不要共享 +/** + * Created by 芋道源码 + * + * 枚举类 + */ + +// ========== COMMON 模块 ========== +// 全局通用状态枚举 +export const CommonStatusEnum = { + ENABLE: 0, // 开启 + DISABLE: 1 // 禁用 +} + +// 全局用户类型枚举 +export const UserTypeEnum = { + MEMBER: 1, // 会员 + ADMIN: 2 // 管理员 +} + +// ========== SYSTEM 模块 ========== +/** + * 菜单的类型枚举 + */ +export const SystemMenuTypeEnum = { + DIR: 1, // 目录 + MENU: 2, // 菜单 + BUTTON: 3 // 按钮 +} + +/** + * 角色的类型枚举 + */ +export const SystemRoleTypeEnum = { + SYSTEM: 1, // 内置角色 + CUSTOM: 2 // 自定义角色 +} + +/** + * 数据权限的范围枚举 + */ +export const SystemDataScopeEnum = { + ALL: 1, // 全部数据权限 + DEPT_CUSTOM: 2, // 指定部门数据权限 + DEPT_ONLY: 3, // 部门数据权限 + DEPT_AND_CHILD: 4, // 部门及以下数据权限 + DEPT_SELF: 5 // 仅本人数据权限 +} + +/** + * 用户的社交平台的类型枚举 + */ +export const SystemUserSocialTypeEnum = { + DINGTALK: { + title: '钉钉', + type: 20, + source: 'dingtalk', + img: 'https://s1.ax1x.com/2022/05/22/OzMDRs.png' + }, + WECHAT_ENTERPRISE: { + title: '企业微信', + type: 30, + source: 'wechat_enterprise', + img: 'https://s1.ax1x.com/2022/05/22/OzMrzn.png' + } +} + +// ========== INFRA 模块 ========== +/** + * 代码生成模板类型 + */ +export const InfraCodegenTemplateTypeEnum = { + CRUD: 1, // 基础 CRUD + TREE: 2, // 树形 CRUD + SUB: 3 // 主子表 CRUD +} + +/** + * 任务状态的枚举 + */ +export const InfraJobStatusEnum = { + INIT: 0, // 初始化中 + NORMAL: 1, // 运行中 + STOP: 2 // 暂停运行 +} + +/** + * API 异常数据的处理状态 + */ +export const InfraApiErrorLogProcessStatusEnum = { + INIT: 0, // 未处理 + DONE: 1, // 已处理 + IGNORE: 2 // 已忽略 +} + +// ========== PAY 模块 ========== +/** + * 支付渠道枚举 + */ +export const PayChannelEnum = { + WX_PUB: { + code: 'wx_pub', + name: '微信 JSAPI 支付' + }, + WX_LITE: { + code: 'wx_lite', + name: '微信小程序支付' + }, + WX_APP: { + code: 'wx_app', + name: '微信 APP 支付' + }, + WX_NATIVE: { + code: 'wx_native', + name: '微信 Native 支付' + }, + WX_WAP: { + code: 'wx_wap', + name: '微信 WAP 网站支付' + }, + WX_BAR: { + code: 'wx_bar', + name: '微信条码支付' + }, + ALIPAY_PC: { + code: 'alipay_pc', + name: '支付宝 PC 网站支付' + }, + ALIPAY_WAP: { + code: 'alipay_wap', + name: '支付宝 WAP 网站支付' + }, + ALIPAY_APP: { + code: 'alipay_app', + name: '支付宝 APP 支付' + }, + ALIPAY_QR: { + code: 'alipay_qr', + name: '支付宝扫码支付' + }, + ALIPAY_BAR: { + code: 'alipay_bar', + name: '支付宝条码支付' + }, + WALLET: { + code: 'wallet', + name: '钱包支付' + }, + MOCK: { + code: 'mock', + name: '模拟支付' + } +} + +/** + * 支付的展示模式每局 + */ +export const PayDisplayModeEnum = { + URL: { + mode: 'url' + }, + IFRAME: { + mode: 'iframe' + }, + FORM: { + mode: 'form' + }, + QR_CODE: { + mode: 'qr_code' + }, + APP: { + mode: 'app' + } +} + +/** + * 支付类型枚举 + */ +export const PayType = { + WECHAT: 'WECHAT', + ALIPAY: 'ALIPAY', + MOCK: 'MOCK' +} + +/** + * 支付订单状态枚举 + */ +export const PayOrderStatusEnum = { + WAITING: { + status: 0, + name: '未支付' + }, + SUCCESS: { + status: 10, + name: '已支付' + }, + CLOSED: { + status: 20, + name: '未支付' + } +} + +// ========== MALL - 商品模块 ========== +/** + * 商品 SPU 状态 + */ +export const ProductSpuStatusEnum = { + RECYCLE: { + status: -1, + name: '回收站' + }, + DISABLE: { + status: 0, + name: '下架' + }, + ENABLE: { + status: 1, + name: '上架' + } +} + +// ========== MALL - 营销模块 ========== +/** + * 优惠劵模板的有限期类型的枚举 + */ +export const CouponTemplateValidityTypeEnum = { + DATE: { + type: 1, + name: '固定日期可用' + }, + TERM: { + type: 2, + name: '领取之后可用' + } +} + +/** + * 优惠劵模板的领取方式的枚举 + */ +export const CouponTemplateTakeTypeEnum = { + USER: { + type: 1, + name: '直接领取' + }, + ADMIN: { + type: 2, + name: '指定发放' + }, + REGISTER: { + type: 3, + name: '新人券' + } +} + +/** + * 营销的商品范围枚举 + */ +export const PromotionProductScopeEnum = { + ALL: { + scope: 1, + name: '通用劵' + }, + SPU: { + scope: 2, + name: '商品劵' + }, + CATEGORY: { + scope: 3, + name: '品类劵' + } +} + +/** + * 营销的条件类型枚举 + */ +export const PromotionConditionTypeEnum = { + PRICE: { + type: 10, + name: '满 N 元' + }, + COUNT: { + type: 20, + name: '满 N 件' + } +} + +/** + * 优惠类型枚举 + */ +export const PromotionDiscountTypeEnum = { + PRICE: { + type: 1, + name: '满减' + }, + PERCENT: { + type: 2, + name: '折扣' + } +} + +// ========== MALL - 交易模块 ========== +/** + * 分销关系绑定模式枚举 + */ +export const BrokerageBindModeEnum = { + ANYTIME: { + mode: 1, + name: '首次绑定' + }, + REGISTER: { + mode: 2, + name: '注册绑定' + }, + OVERRIDE: { + mode: 3, + name: '覆盖绑定' + } +} +/** + * 分佣模式枚举 + */ +export const BrokerageEnabledConditionEnum = { + ALL: { + condition: 1, + name: '人人分销' + }, + ADMIN: { + condition: 2, + name: '指定分销' + } +} +/** + * 佣金记录业务类型枚举 + */ +export const BrokerageRecordBizTypeEnum = { + ORDER: { + type: 1, + name: '获得推广佣金' + }, + WITHDRAW: { + type: 2, + name: '提现申请' + } +} +/** + * 佣金提现状态枚举 + */ +export const BrokerageWithdrawStatusEnum = { + AUDITING: { + status: 0, + name: '审核中' + }, + AUDIT_SUCCESS: { + status: 10, + name: '审核通过' + }, + AUDIT_FAIL: { + status: 20, + name: '审核不通过' + }, + WITHDRAW_SUCCESS: { + status: 11, + name: '提现成功' + }, + WITHDRAW_FAIL: { + status: 21, + name: '提现失败' + } +} +/** + * 佣金提现类型枚举 + */ +export const BrokerageWithdrawTypeEnum = { + WALLET: { + type: 1, + name: '钱包' + }, + BANK: { + type: 2, + name: '银行卡' + }, + WECHAT: { + type: 3, + name: '微信' + }, + ALIPAY: { + type: 4, + name: '支付宝' + } +} + +/** + * 配送方式枚举 + */ +export const DeliveryTypeEnum = { + EXPRESS: { + type: 1, + name: '快递发货' + }, + PICK_UP: { + type: 2, + name: '到店自提' + } +} +/** + * 交易订单 - 状态 + */ +export const TradeOrderStatusEnum = { + UNPAID: { + status: 0, + name: '待支付' + }, + UNDELIVERED: { + status: 10, + name: '待发货' + }, + DELIVERED: { + status: 20, + name: '已发货' + }, + COMPLETED: { + status: 30, + name: '已完成' + }, + CANCELED: { + status: 40, + name: '已取消' + } +} + +// ========== ERP - 企业资源计划 ========== + +export const ErpBizType = { + PURCHASE_ORDER: 10, + PURCHASE_IN: 11, + PURCHASE_RETURN: 12, + SALE_ORDER: 20, + SALE_OUT: 21, + SALE_RETURN: 22 +} + +// ========== BPM 模块 ========== + +export const BpmModelType = { + BPMN: 10, // BPMN 设计器 + SIMPLE: 20 // 简易设计器 +} + +export const BpmModelFormType = { + NORMAL: 10, // 流程表单 + CUSTOM: 20 // 业务表单 +} + +export const BpmProcessInstanceStatus = { + NOT_START: -1, // 未开始 + RUNNING: 1, // 审批中 + APPROVE: 2, // 审批通过 + REJECT: 3, // 审批不通过 + CANCEL: 4 // 已取消 +} + +export const BpmAutoApproveType = { + NONE: 0, // 不自动通过 + APPROVE_ALL: 1, // 仅审批一次,后续重复的审批节点均自动通过 + APPROVE_SEQUENT: 2, // 仅针对连续审批的节点自动通过 +} diff --git a/apps/web-antd/src/utils/dict.ts b/apps/web-antd/src/utils/dict.ts index 179eda8b2..426560e48 100644 --- a/apps/web-antd/src/utils/dict.ts +++ b/apps/web-antd/src/utils/dict.ts @@ -1,5 +1,6 @@ import type { DefaultOptionType } from 'ant-design-vue/es/select'; // TODO @芋艿:后续再优化 +// TODO @芋艿:可以共享么? import { isObject } from '@vben/utils'; diff --git a/apps/web-antd/src/utils/tree.ts b/apps/web-antd/src/utils/tree.ts new file mode 100644 index 000000000..a7355e980 --- /dev/null +++ b/apps/web-antd/src/utils/tree.ts @@ -0,0 +1,56 @@ +// TODO @芋艿:1)代码优化;2)是不是抽到公共的? +/** + * 构造树型结构数据 + * @param {*} data 数据源 + * @param {*} id id字段 默认 'id' + * @param {*} parentId 父节点字段 默认 'parentId' + * @param {*} children 孩子节点字段 默认 'children' + */ +export const handleTree = (data: any[], id?: string, parentId?: string, children?: string) => { + if (!Array.isArray(data)) { + console.warn('data must be an array') + return [] + } + const config = { + id: id || 'id', + parentId: parentId || 'parentId', + childrenList: children || 'children' + } + + const childrenListMap = {} + const nodeIds = {} + const tree: any[] = [] + + for (const d of data) { + const parentId = d[config.parentId] + if (childrenListMap[parentId] == null) { + childrenListMap[parentId] = [] + } + nodeIds[d[config.id]] = d + childrenListMap[parentId].push(d) + } + + for (const d of data) { + const parentId = d[config.parentId] + if (nodeIds[parentId] == null) { + tree.push(d) + } + } + + for (const t of tree) { + adaptToChildrenList(t) + } + + function adaptToChildrenList(o) { + if (childrenListMap[o[config.id]] !== null) { + o[config.childrenList] = childrenListMap[o[config.id]] + } + if (o[config.childrenList]) { + for (const c of o[config.childrenList]) { + adaptToChildrenList(c) + } + } + } + + return tree +} \ No newline at end of file diff --git a/apps/web-antd/src/views/system/dept/data.ts b/apps/web-antd/src/views/system/dept/data.ts index cce00cf90..457973942 100644 --- a/apps/web-antd/src/views/system/dept/data.ts +++ b/apps/web-antd/src/views/system/dept/data.ts @@ -3,67 +3,125 @@ import type { VbenFormSchema } from '#/adapter/form'; import type { OnActionClickFn } from '#/adapter/vxe-table'; import type { SystemDeptApi } from '#/api/system/dept'; +import { $t } from '#/locales'; import { z } from '#/adapter/form'; import { getDeptList } from '#/api/system/dept'; -import { $t } from '#/locales'; -import { DICT_TYPE } from '#/utils/dict'; +import { getSimpleUserList } from '#/api/system/user'; +import { DICT_TYPE, getDictOptions } from '#/utils/dict'; +import { CommonStatusEnum } from '#/utils/constants'; +import { handleTree } from '#/utils/tree'; /** 获取编辑表单的字段配置 */ -// TODO @芋艿:表单的整理 export function useSchema(): VbenFormSchema[] { return [ { component: 'ApiTreeSelect', componentProps: { allowClear: true, - api: getDeptList, + api: async () => { + const data = await getDeptList(); + data.unshift({ + id: 0, + name: '顶级部门', + }); + return handleTree(data); + }, class: 'w-full', labelField: 'name', valueField: 'id', - childrenField: 'children' + childrenField: 'children', + placeholder: '请选择上级部门', + treeDefaultExpandAll: true, }, fieldName: 'parentId', label: '上级部门', + // TODO @芋艿:number 的必填,写起来有点麻烦,后续得研究下; + rules: z + .number() + .nullable() + .refine((val) => val != null && val >= 0, '上级部门不能为空') + .default(null), }, { component: 'Input', + componentProps: { + placeholder: '请输入部门名称', + }, fieldName: 'name', - label: $t('system.dept.deptName'), + label: '部门名称', rules: z .string() - .min(2, $t('ui.formRules.minLength', [$t('system.dept.deptName'), 2])) - .max( - 20, - $t('ui.formRules.maxLength', [$t('system.dept.deptName'), 20]), - ), + .min(2, $t('ui.formRules.minLength', ['部门名称', 2])) + .max(20, $t('ui.formRules.maxLength', ['部门名称', 20])), + }, + { + component: 'InputNumber', + componentProps: { + min: 0, + class: 'w-full', + controlsPosition: 'right', + placeholder: '请输入显示排序', + }, + fieldName: 'sort', + label: '显示排序', + rules: z + .number() + .nullable() + .refine((val) => val != null && val >= 0, '显示排序不能为空') + .default(null), + }, + { + component: 'ApiSelect', + componentProps: { + api: getSimpleUserList, + class: 'w-full', + labelField: 'nickname', + valueField: 'id', + placeholder: '请选择负责人', + allowClear: true, + }, + fieldName: 'leaderUserId', + label: '负责人', + rules: z.number().optional(), + }, + { + component: 'Input', + componentProps: { + maxLength: 11, + placeholder: '请输入联系电话', + }, + fieldName: 'phone', + label: '联系电话', + rules: z + .string() + // TODO @芋艿:未来怎么拓展一个手机的 + .regex(/^1[3|4|5|6|7|8|9][0-9]\d{8}$/, '请输入正确的手机号码') + .optional(), + }, + { + component: 'Input', + componentProps: { + maxLength: 50, + placeholder: '请输入邮箱', + }, + fieldName: 'email', + label: '邮箱', + rules: z + .string() + .email('请输入正确的邮箱地址') + .max(50, $t('ui.formRules.maxLength', ['邮箱', 50])) + .optional(), }, { component: 'RadioGroup', componentProps: { buttonStyle: 'solid', - options: [ - { label: $t('common.enabled'), value: 1 }, - { label: $t('common.disabled'), value: 0 }, - ], + options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'), optionType: 'button', }, - defaultValue: 1, fieldName: 'status', - label: $t('system.dept.status'), - }, - { - component: 'Textarea', - componentProps: { - maxLength: 50, - rows: 3, - showCount: true, - }, - fieldName: 'remark', - label: $t('system.dept.remark'), - rules: z - .string() - .max(50, $t('ui.formRules.maxLength', [$t('system.dept.remark'), 50])) - .optional(), + label: '状态', + rules: z.number().default(CommonStatusEnum.ENABLE), }, ]; } @@ -93,7 +151,10 @@ export function useColumns( minWidth: 100, }, { - cellRender: { name: 'CellDict', props: { type: DICT_TYPE.COMMON_STATUS } }, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.COMMON_STATUS }, + }, field: 'status', title: '状态', minWidth: 100, diff --git a/apps/web-antd/src/views/system/dept/index.vue b/apps/web-antd/src/views/system/dept/index.vue index e7f2e3b99..92e1eda5f 100644 --- a/apps/web-antd/src/views/system/dept/index.vue +++ b/apps/web-antd/src/views/system/dept/index.vue @@ -1,5 +1,8 @@ diff --git a/playground/src/locales/langs/en-US/system.json b/playground/src/locales/langs/en-US/system.json index 003dfbbed..8c0195855 100644 --- a/playground/src/locales/langs/en-US/system.json +++ b/playground/src/locales/langs/en-US/system.json @@ -1,15 +1,5 @@ { "title": "System Management", - "dept": { - "name": "Department", - "title": "Department Management", - "deptName": "Department Name", - "status": "Status", - "createTime": "Create Time", - "remark": "Remark", - "operation": "Operation", - "parentDept": "Parent Department" - }, "menu": { "title": "Menu Management", "parent": "Parent Menu", diff --git a/playground/src/locales/langs/zh-CN/system.json b/playground/src/locales/langs/zh-CN/system.json index b0f5e7fad..9943bd492 100644 --- a/playground/src/locales/langs/zh-CN/system.json +++ b/playground/src/locales/langs/zh-CN/system.json @@ -1,15 +1,4 @@ { - "dept": { - "list": "部门列表", - "createTime": "创建时间", - "deptName": "部门名称", - "name": "部门", - "operation": "操作", - "parentDept": "上级部门", - "remark": "备注", - "status": "状态", - "title": "部门管理" - }, "menu": { "list": "菜单列表", "activeIcon": "激活图标",