From f42296180b48b0e807aec8b7f365b3cbb039ed53 Mon Sep 17 00:00:00 2001 From: zhihao <18915542763@163.com> Date: Tue, 14 Apr 2026 10:03:00 +0800 Subject: [PATCH] =?UTF-8?q?czh-20260414-=E5=AE=9E=E7=8E=B0=E5=88=86?= =?UTF-8?q?=E5=8F=91=E5=8A=9F=E8=83=BD=E7=9A=84=E5=89=8D=E7=AB=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../distributeComponent/SzDistribute.api.ts | 91 +++ .../distributeComponent/SzDistribute.vue | 696 ++++++++++++++++++ .../distributeComponent/SzSelectDepart.vue | 659 +++++++++++++++++ .../distributeComponent/SzSelectUser.vue | 411 +++++++++++ .../SzSelectedDepartsAndUsers.vue | 341 +++++++++ .../semri/distributeComponent/USAGE.md | 290 ++++++++ src/views/semri/test/SzDistributeTest.vue | 351 +++++++++ 7 files changed, 2839 insertions(+) create mode 100644 src/components/semri/distributeComponent/SzDistribute.api.ts create mode 100644 src/components/semri/distributeComponent/SzDistribute.vue create mode 100644 src/components/semri/distributeComponent/SzSelectDepart.vue create mode 100644 src/components/semri/distributeComponent/SzSelectUser.vue create mode 100644 src/components/semri/distributeComponent/SzSelectedDepartsAndUsers.vue create mode 100644 src/components/semri/distributeComponent/USAGE.md create mode 100644 src/views/semri/test/SzDistributeTest.vue diff --git a/src/components/semri/distributeComponent/SzDistribute.api.ts b/src/components/semri/distributeComponent/SzDistribute.api.ts new file mode 100644 index 0000000..97a8bc1 --- /dev/null +++ b/src/components/semri/distributeComponent/SzDistribute.api.ts @@ -0,0 +1,91 @@ +import { defHttp } from '/@/utils/http/axios'; + +enum Api { + list = '/officialdocDistribute/djOfficialdocDistribute/list', + save = '/officialdocDistribute/djOfficialdocDistribute/add', + edit = '/officialdocDistribute/djOfficialdocDistribute/edit', + deleteOne = '/officialdocDistribute/djOfficialdocDistribute/delete', +} + +export const list = (params?) => defHttp.get({ url: Api.list, params }); +export const save = (params) => defHttp.post({ url: Api.save, params }); +export const edit = (params) => defHttp.post({ url: Api.edit, params }); +export const deleteOne = (params) => defHttp.delete({ url: Api.deleteOne, params }, { joinParamsToUrl: true }); +export const saveOrUpdate = (params, isUpdate) => { + let url = isUpdate ? Api.edit : Api.save; + return defHttp.post({ url, params }); +}; + +export interface DepartTreeNode { + id: string; + key: string; + value: string; + title: string; + isLeaf?: boolean; + departType?: string | number; + distributable?: number; + disableCheckbox?: boolean; + assignType?: number; + children?: DepartTreeNode[]; + description?: string; + parentId?: string; + orgCode?: string; +} + +export interface UserItem { + id: string; + username: string; + realname: string; + orgCodeTxt?: string; + userSecurityLevel?: number; + userSecurityLevel_dictText?: string; + sex_dictText?: string; + sortno?: number; + departId?: string; + departName?: string; + assignType?: number; +} + +export interface DistributeRecord { + id: string; + name: string; + userId: string; + username: string; + selected: string; +} + +export type AssignType = 1 | 2 | 3; + +export function deleteIfExist>(array: T[], value: T, key: string): void { + const idx = array.findIndex((item) => item[key] === value[key]); + if (idx >= 0) { + array.splice(idx, 1); + } +} + +export function recurTree, R>(tree: T[], callback: (node: T) => R[]): R[] { + let res: R[] = []; + tree.forEach((item) => { + res = res.concat(callback(item)); + if (item.children) { + res = res.concat(recurTree(item.children, callback)); + } + }); + return res; +} + +export function filterDepartTree>(departTree: T[], searchValue: string): T[] { + let filtered: T[] = []; + departTree.forEach((item) => { + if (item.title && item.title.includes(searchValue)) { + filtered.push(Object.assign({}, item, { children: null, isLeaf: true })); + } + if (item.children) { + const result = filterDepartTree(item.children, searchValue); + if (result.length > 0) { + filtered = filtered.concat(result); + } + } + }); + return filtered; +} diff --git a/src/components/semri/distributeComponent/SzDistribute.vue b/src/components/semri/distributeComponent/SzDistribute.vue new file mode 100644 index 0000000..8dc9cf2 --- /dev/null +++ b/src/components/semri/distributeComponent/SzDistribute.vue @@ -0,0 +1,696 @@ + + + + + diff --git a/src/components/semri/distributeComponent/SzSelectDepart.vue b/src/components/semri/distributeComponent/SzSelectDepart.vue new file mode 100644 index 0000000..e1da721 --- /dev/null +++ b/src/components/semri/distributeComponent/SzSelectDepart.vue @@ -0,0 +1,659 @@ + + + + + + + diff --git a/src/components/semri/distributeComponent/SzSelectUser.vue b/src/components/semri/distributeComponent/SzSelectUser.vue new file mode 100644 index 0000000..7535ff4 --- /dev/null +++ b/src/components/semri/distributeComponent/SzSelectUser.vue @@ -0,0 +1,411 @@ + + + + + diff --git a/src/components/semri/distributeComponent/SzSelectedDepartsAndUsers.vue b/src/components/semri/distributeComponent/SzSelectedDepartsAndUsers.vue new file mode 100644 index 0000000..4b3f4a4 --- /dev/null +++ b/src/components/semri/distributeComponent/SzSelectedDepartsAndUsers.vue @@ -0,0 +1,341 @@ + + + + + diff --git a/src/components/semri/distributeComponent/USAGE.md b/src/components/semri/distributeComponent/USAGE.md new file mode 100644 index 0000000..32f0848 --- /dev/null +++ b/src/components/semri/distributeComponent/USAGE.md @@ -0,0 +1,290 @@ +# SzDistribute 分发方案组件 使用说明 + +## 组件概述 + +SzDistribute 是一个公文分发方案组件,支持按部门(主办/协办/阅文)和人员(办文/阅文)进行分发配置,并提供方案保存/加载功能。 + +基于 JeecgBoot 3.8.0 + Vue 3 + TypeScript 开发。 + +--- + +## 文件结构 + +``` +distributeComponent/ +├── SzDistribute.vue # 主入口组件(分发方案弹窗) +├── SzSelectDepart.vue # 部门选择组件(树形多选 + 办/协/阅标记) +├── SzSelectUser.vue # 人员选择组件(部门树 + 人员表格) +├── SzSelectedDepartsAndUsers.vue # 已选部门和人员展示组件 +├── SzDistribute.api.ts # 方案增删改查 API +├── types.ts # TypeScript 类型定义 +├── utils.ts # 工具函数 +└── USAGE.md # 本文件 +``` + +--- + +## 快速开始 + +### 基础用法 + +```vue + + + +``` + +--- + +## SzDistribute 主组件 API + +### Props(输入) + +| 属性 | 类型 | 必填 | 默认值 | 说明 | +|------|------|------|--------|------| +| `secLevel` | `Number` | 是 | - | 文件密级。1=非密, 2=内部, 3=秘密, 4=机密 | +| `role` | `Number \| String` | 是 | `1` | 当前用户角色。1=领导, 2=部门负责人 | +| `departId` | `String` | 是 | `''` | 当前用户部门ID。支持 orgCode(如 `"A01"`)或 UUID 格式,组件内部自动转换 | +| `docId` | `String` | 否 | `''` | 公文ID。传入后自动排除该公文已分发的部门 | +| `isSpecial` | `Number` | 否 | `0` | 是否特殊分发。0=否, 1=是 | +| `title` | `String` | 否 | `'分发方案'` | 弹窗标题(会自动追加密级文字) | +| `width` | `Number \| String` | 否 | `1500` | 弹窗宽度(px) | +| `showButton` | `Boolean` | 否 | `true` | 是否显示"分发对象"触发按钮。设为 `false` 时需通过 ref 调用 `showModal()` | + +### v-model(双向绑定) + +| 属性 | 类型 | 说明 | +|------|------|------| +| `checkedMainDepartIds` | `String` | 主办部门ID列表,逗号分隔,如 `"id1,id2,id3"` | +| `checkedAssistDepartIds` | `String` | 协办部门ID列表,逗号分隔 | +| `checkedReadDepartIds` | `String` | 阅文部门ID列表,逗号分隔 | +| `checkedUsernames` | `String` | 办文人员用户名列表,逗号分隔,如 `"zhangsan,lisi"` | +| `checkedReadUsernames` | `String` | 阅文人员用户名列表,逗号分隔 | +| `reasons` | `String` | 分发意见/备注文本 | +| `modelValue` | `String` | 完整分发数据字符串(内部格式) | + +### Events(事件) + +| 事件 | 参数 | 说明 | +|------|------|------| +| `change` | `distributeStr: string` | 分发方案确认时触发 | +| `update:checkedDepartIds` | `ids: string` | 所有已选部门ID(逗号分隔) | +| `update:checkedDeparts` | `DepartTreeNode[]` | 所有已选部门节点对象数组 | + +### Methods(通过 ref 调用) + +```typescript +const distributeRef = ref(); + +// 手动打开分发弹窗 +distributeRef.value.showModal(); +``` + +--- + +## 角色权限说明 + +### 领导(role=1) + +- 部门选择 Tab:显示全部部门树,可自由选择主办/协办/阅文 +- 办文人员 Tab:显示全部部门树,选择部门后加载该部门人员 +- 阅文人员 Tab:同办文人员 +- 默认激活 Tab:选择部门 + +### 部门负责人(role=2) + +- 部门选择 Tab:根据当前部门的类型(职能/业务/科室)显示可见部门 +- 办文人员 Tab:只显示本部门及下级部门的人员 +- 阅文人员 Tab:同办文人员 +- 默认激活 Tab:选择阅文人员(因部门负责人通常负责阅文分发) + +--- + +## 子组件说明 + +### SzSelectDepart — 部门选择 + +树形结构展示部门,每个选中的部门可标记为「办」「协」「阅」三种类型。 + +**Props:** + +| 属性 | 类型 | 说明 | +|------|------|------| +| `selectedMainDeparts` | `DepartTreeNode[]` | 当前主办部门列表 | +| `selectedAssistDeparts` | `DepartTreeNode[]` | 当前协办部门列表 | +| `selectedReadDeparts` | `DepartTreeNode[]` | 当前阅文部门列表 | +| `excludeDeparts` | `string[]` | 需排除的部门ID列表 | +| `departId` | `string` | 当前用户部门ID | +| `role` | `string \| number` | 用户角色 | + +**Events:** `update:selectedMainDeparts`, `update:selectedAssistDeparts`, `update:selectedReadDeparts` + +**快捷操作:** +- 全选部门 / 全选职能部门 / 全选业务部门 / 全选本部门科室 +- 每种全选可指定办/协/阅类型 +- 右键节点文字可快速切换选中状态 + +### SzSelectUser — 人员选择 + +左侧部门树 + 右侧人员表格,支持搜索、分页、全选。 + +**Props:** + +| 属性 | 类型 | 说明 | +|------|------|------| +| `secLevel` | `any` | 文件密级,用于过滤人员(只显示密级 > 文件密级的人员) | +| `checkedUser` | `UserItem[]` | 已选中的人员列表 | +| `selectedDepId` | `string` | 当前选中的部门ID | +| `departId` | `string` | 当前用户部门ID(支持 orgCode 或 UUID) | +| `role` | `string \| number` | 用户角色 | +| `isSpecial` | `number` | 是否特殊分发 | +| `multi` | `boolean` | 是否多选(默认 `true`) | + +**v-model:** `selectedDepId` — 当前选中部门 + +**Events:** `update:checkedUser`, `update:selectedDepId` + +**密级过滤规则:** 只显示 `人员密级 > 文件密级` 的人员。例如文件密级为 3(秘密),则只显示密级为 4(机密)的人员。 + +### SzSelectedDepartsAndUsers — 已选展示 + +以标签形式展示已选择的部门和人员,支持删除和办/协/阅类型切换。 + +**Props:** + +| 属性 | 类型 | 说明 | +|------|------|------| +| `disabled` | `boolean` | 是否禁用操作(默认 `false`) | +| `selectedMainDeparts` | `DepartTreeNode[]` | 主办部门 | +| `selectedAssistDeparts` | `DepartTreeNode[]` | 协办部门 | +| `selectedReadDeparts` | `DepartTreeNode[]` | 阅文部门 | +| `selectedUsers` | `UserItem[]` | 办文人员 | +| `selectedReadUsers` | `UserItem[]` | 阅文人员 | + +**Events:** `update:selectedMainDeparts`, `update:selectedAssistDeparts`, `update:selectedReadDeparts`, `update:selectedUsers`, `update:selectedReadUsers` + +--- + +## 类型定义 + +### DepartTreeNode + +```typescript +interface DepartTreeNode { + id: string; // 部门 UUID + key: string; // 树节点 key(等于 id) + value: string; // 树节点 value(等于 id) + title: string; // 部门名称 + isLeaf?: boolean; // 是否叶子节点 + departType?: string | number; // 部门类型:0=根, 1=职能, 2=业务, 3=科室 + distributable?: number; // 是否可分发:1=是 + disableCheckbox?: boolean; // 是否禁用选择 + assignType?: number; // 分配类型:1=办, 2=协, 3=阅 + children?: DepartTreeNode[]; // 子部门 + description?: string; // 部门描述 + parentId?: string; // 父部门ID + orgCode?: string; // 部门编码(如 "A01", "A03A02") +} +``` + +### UserItem + +```typescript +interface UserItem { + id: string; + username: string; + realname: string; + orgCodeTxt?: string; // 所属部门名称 + userSecurityLevel?: number; // 人员密级数值 + userSecurityLevel_dictText?: string; // 人员密级文字 + sex_dictText?: string; // 性别 + sortno?: number; // 排序号 + departId?: string; + departName?: string; + assignType?: number; // 分配类型:1=办, 3=阅 +} +``` + +--- + +## departId 说明 + +`departId` 属性支持两种格式,组件内部会自动识别并转换: + +| 格式 | 示例 | 说明 | +|------|------|------| +| orgCode | `"A01"`, `"A03A02"` | 部门编码,通常来自 `userInfo.orgCode` | +| UUID | `"f4d5979edc1d4785ba2322f0b8200a21"` | 部门唯一标识 | + +推荐从 `useUserStore().getUserInfo.orgCode` 获取: + +```typescript +import { useUserStore } from '/@/store/modules/user'; + +const userStore = useUserStore(); +const departId = userStore.getUserInfo?.orgCode || ''; +``` + +--- + +## 方案管理 + +组件内置方案保存/加载功能,数据存储在后端 `djOfficialdocDistribute` 表中。 + +- **保存方案**:输入方案名 → 点击"保存为方案" +- **加载方案**:在弹窗左侧已存方案列表中点击方案名 +- **快速应用**:在弹窗外部的方案按钮直接点击即可应用并提交 +- **删除方案**:点击方案旁的关闭按钮 + +加载方案时会自动校验人员状态(密级变更、部门变动、离职冻结),异常人员会弹出提示并自动移除。 + +--- + +## 依赖的后端接口 + +| 接口 | 用途 | +|------|------| +| `GET /sys/sysDepart/queryTreeList` | 获取部门树 | +| `GET /sys/user/queryUserComponentData` | 查询部门下的人员 | +| `GET /officialdocDistribute/djOfficialdocDistribute/list` | 方案列表 | +| `POST /officialdocDistribute/djOfficialdocDistribute/add` | 新增方案 | +| `POST /officialdocDistribute/djOfficialdocDistribute/edit` | 编辑方案 | +| `DELETE /officialdocDistribute/djOfficialdocDistribute/delete` | 删除方案 | +| `GET /distributionProcessinfo/officialdocDistributionProcessinfo/queryDeptIds` | 查询公文已分发部门ID | + +--- + +## 测试页面 + +访问 `/semri/test/SzDistributeTest` 可查看组件演示和所有输入输出字段。 diff --git a/src/views/semri/test/SzDistributeTest.vue b/src/views/semri/test/SzDistributeTest.vue new file mode 100644 index 0000000..024182f --- /dev/null +++ b/src/views/semri/test/SzDistributeTest.vue @@ -0,0 +1,351 @@ + + + + +