diff --git a/src/api/approval/directory/index.ts b/src/api/approval/directory/index.ts new file mode 100644 index 0000000..b7a2e8a --- /dev/null +++ b/src/api/approval/directory/index.ts @@ -0,0 +1,34 @@ +import request from '@/utils/request'; +import type { ApiResult } from '@/api'; +import type { ApprovalOrganization, ApprovalUser } from '../instance/model'; +import { THINK_API_URL } from '@/config/setting'; + +/** + * 启用组织(审批管理下拉) + */ +export async function listApprovalOrganizations() { + const res = await request.get>( + THINK_API_URL + '/approval/directory/organizations' + ); + if (res.data.code === 0 && res.data.data) { + return res.data.data; + } + return Promise.reject(new Error(res.data.message)); +} + +/** + * 启用用户远程搜索(审批管理下拉) + */ +export async function searchApprovalUsers(params?: { + keywords?: string; + organizationId?: number; +}) { + const res = await request.get>( + THINK_API_URL + '/approval/directory/users', + { params } + ); + if (res.data.code === 0 && res.data.data) { + return res.data.data; + } + return Promise.reject(new Error(res.data.message)); +} diff --git a/src/api/approval/instance/index.ts b/src/api/approval/instance/index.ts new file mode 100644 index 0000000..5c58c4a --- /dev/null +++ b/src/api/approval/instance/index.ts @@ -0,0 +1,65 @@ +import request from '@/utils/request'; +import type { ApiResult, PageResult } from '@/api'; +import type { + ApprovalExportFile, + ApprovalInstance, + ApprovalItem, + ApprovalQueryParam, + ApprovalTodoParam +} from './model'; +import { THINK_API_URL } from '@/config/setting'; + +/** + * 单据查询分页 + */ +export async function pageInstances(params: ApprovalQueryParam) { + const res = await request.get>>( + THINK_API_URL + '/approval/instance/page', + { params } + ); + if (res.data.code === 0) { + return res.data.data; + } + return Promise.reject(new Error(res.data.message)); +} + +/** + * 我的待办分页 + */ +export async function pageApprovalTodo(params: ApprovalTodoParam) { + const res = await request.get>>( + THINK_API_URL + '/approval/todo/page', + { params } + ); + if (res.data.code === 0) { + return res.data.data; + } + return Promise.reject(new Error(res.data.message)); +} + +/** + * 单据详情(只读) + */ +export async function getInstance(id: number) { + const res = await request.get>( + THINK_API_URL + '/approval/instance/' + id + ); + if (res.data.code === 0) { + return res.data.data; + } + return Promise.reject(new Error(res.data.message)); +} + +/** + * 导出单据(返回完整结果,含文件下载地址) + */ +export async function exportInstances(data: ApprovalQueryParam) { + const res = await request.post>( + THINK_API_URL + '/approval/export', + data + ); + if (res.data.code === 0) { + return res.data; + } + return Promise.reject(new Error(res.data.message)); +} diff --git a/src/api/approval/instance/model/index.ts b/src/api/approval/instance/model/index.ts new file mode 100644 index 0000000..e9610ae --- /dev/null +++ b/src/api/approval/instance/model/index.ts @@ -0,0 +1,234 @@ +import type { PageParam } from '@/api'; + +/** + * 单据查询条件 + */ +export interface ApprovalQueryParam extends PageParam { + // 单据类型 completion/coordination + docType?: string; + // 单号 + bizNo?: string; + // 关键词:单号、名称或标题、实施单位 + keywords?: string; + // 报送部门id + reportOrgId?: number; + // 受理部门id + acceptOrgId?: number; + // 报送人id + reporterId?: number; + // 单据状态 + status?: string; + // 提交时间起 + submitTimeStart?: string; + // 提交时间止 + submitTimeEnd?: string; + // 归档时间起 + archiveTimeStart?: string; + // 归档时间止 + archiveTimeEnd?: string; +} + +/** + * 单据列表行 + */ +export interface ApprovalItem { + id?: number; + bizNo?: string; + docType?: string; + docTypeName?: string; + status?: string; + statusName?: string; + // 名称或标题 + title?: string; + submitTime?: string; + currentNodeName?: string; + reporterId?: number; + reporterName?: string; + reportOrgName?: string; + acceptOrgName?: string; + archiveTime?: string; +} + +/** + * 附件 + */ +export interface ApprovalAttachment { + id?: number; + fileId?: number; + name?: string; + url?: string; + length?: number; + category?: string; + categoryName?: string; + uploaderId?: number; + uploaderName?: string; + createTime?: string; +} + +/** + * 处理记录 + */ +export interface ApprovalRecord { + id?: number; + taskId?: number; + nodeKey?: string; + nodeName?: string; + operatorId?: number; + operatorName?: string; + action?: string; + actionName?: string; + opinion?: string; + round?: number; + createTime?: string; +} + +/** + * 当前轮存档确认 + */ +export interface ApprovalConfirm { + userId?: number; + userName?: string; + status?: string; + statusName?: string; + handleTime?: string; +} + +/** + * 实例上的待办任务(PC 按任务改派) + */ +export interface ApprovalPendingTask { + id?: number; + nodeKey?: string; + nodeName?: string; + nodeType?: string; + assigneeId?: number; + assigneeName?: string; +} + +/** + * 我的待处理任务 + */ +export interface ApprovalMyTask { + id?: number; + nodeKey?: string; + nodeType?: string; + status?: string; +} + +/** + * 单据详情 + */ +export interface ApprovalInstance { + id?: number; + bizNo?: string; + docType?: string; + docTypeName?: string; + flowCode?: string; + status?: string; + statusName?: string; + round?: number; + reporterId?: number; + reporterName?: string; + reportOrgId?: number; + reportOrgName?: string; + acceptOrgId?: number; + acceptOrgName?: string; + currentNodeKey?: string; + currentNodeType?: string; + currentNodeName?: string; + submitTime?: string; + approveTime?: string; + archiveTime?: string; + archivistId?: number; + archivistName?: string; + // 业务完成确认单 + name?: string; + implementOrg?: string; + fee?: number; + serviceContent?: string; + // 内部协调单 + title?: string; + content?: string; + remark?: string; + dispatchTargetId?: number; + dispatchTargetName?: string; + attachments?: ApprovalAttachment[]; + records?: ApprovalRecord[]; + confirms?: ApprovalConfirm[]; + myTask?: ApprovalMyTask | null; + pendingTasks?: ApprovalPendingTask[]; + permissions?: Record; +} + +/** + * 审批目录组织 + */ +export interface ApprovalOrganization { + organizationId?: number; + parentId?: number; + organizationName?: string; + organizationFullName?: string; + organizationCode?: string; + leaderId?: number; +} + +/** + * 审批目录用户 + */ +export interface ApprovalUser { + userId?: number; + username?: string; + nickname?: string; + realName?: string; + organizationId?: number; + organizationName?: string; +} + +/** + * 导出文件 + */ +export interface ApprovalExportFile { + id?: number; + name?: string; + path?: string; + length?: number; + contentType?: string; + url?: string; +} + +/** + * 待办/已办查询条件 + */ +export interface ApprovalTodoParam extends PageParam { + // 单据状态 + status?: string; + // 单据类型 + docType?: string; + // 查询范围:mine(默认本人待办)/all(全租户待办,需 assign 权限) + scope?: 'mine' | 'all'; + // 关键词:单号、名称或标题、实施单位 + keywords?: string; +} + +/** + * 待办改派参数 + */ +export interface ApprovalAssignParam { + // 改派对象用户id + targetUserId: number; + // 改派原因 + reason: string; +} + +/** + * 改派结果 + */ +export interface ApprovalAssignResult { + instanceId?: number; + taskId?: number; + originalAssigneeId?: number; + originalAssigneeName?: string; + assigneeId?: number; + assigneeName?: string; + reason?: string; +} diff --git a/src/api/approval/task/index.ts b/src/api/approval/task/index.ts new file mode 100644 index 0000000..7633baf --- /dev/null +++ b/src/api/approval/task/index.ts @@ -0,0 +1,106 @@ +import request from '@/utils/request'; +import type { ApiResult } from '@/api'; +import { THINK_API_URL } from '@/config/setting'; +import type { + ApprovalAssignParam, + ApprovalAssignResult, + ApprovalUser +} from '../instance/model'; + +/** + * 通过任务 + */ +export async function approveApprovalTask(id: number, opinion?: string) { + const res = await request.post>( + THINK_API_URL + '/approval/task/' + id + '/approve', + { opinion } + ); + if (res.data.code === 0) { + return res.data.message; + } + return Promise.reject(new Error(res.data.message)); +} + +/** + * 驳回任务(意见必填) + */ +export async function rejectApprovalTask(id: number, opinion: string) { + const res = await request.post>( + THINK_API_URL + '/approval/task/' + id + '/reject', + { opinion } + ); + if (res.data.code === 0) { + return res.data.message; + } + return Promise.reject(new Error(res.data.message)); +} + +/** + * 存档确认 + */ +export async function confirmApprovalTask(id: number) { + const res = await request.post>( + THINK_API_URL + '/approval/task/' + id + '/confirm' + ); + if (res.data.code === 0) { + return res.data.message; + } + return Promise.reject(new Error(res.data.message)); +} + +/** + * 归档 + */ +export async function archiveApprovalTask(id: number) { + const res = await request.post>( + THINK_API_URL + '/approval/task/' + id + '/archive' + ); + if (res.data.code === 0) { + return res.data.message; + } + return Promise.reject(new Error(res.data.message)); +} + +/** + * 派单候选池(仅派单节点、本人待办) + */ +export async function listDispatchCandidates(id: number) { + const res = await request.get>( + THINK_API_URL + '/approval/task/' + id + '/candidates' + ); + if (res.data.code === 0) { + return res.data.data ?? []; + } + return Promise.reject(new Error(res.data.message)); +} + +/** + * 派单(选定 1 人即完成派单节点) + */ +export async function dispatchApprovalTask(id: number, targetUserId: number) { + const res = await request.post>( + THINK_API_URL + '/approval/task/' + id + '/dispatch', + { targetUserId } + ); + if (res.data.code === 0) { + return res.data.message; + } + return Promise.reject(new Error(res.data.message)); +} + +/** + * 改派待办 + */ +export async function assignApprovalTask( + id: number, + data: ApprovalAssignParam +) { + const res = await request.post>( + THINK_API_URL + '/approval/task/' + id + '/assign', + data + ); + if (res.data.code === 0) { + return res.data.data; + } + return Promise.reject(new Error(res.data.message)); +} diff --git a/src/api/system/role/index.ts b/src/api/system/role/index.ts index 352e352..1dcb9c1 100644 --- a/src/api/system/role/index.ts +++ b/src/api/system/role/index.ts @@ -1,8 +1,8 @@ import request from '@/utils/request'; import type { ApiResult, PageResult } from '@/api'; -import type { Role, RoleParam } from './model'; +import type { Role, RoleMember, RoleMemberParam, RoleParam } from './model'; import type { Menu } from '../menu/model'; -import { SERVER_API_URL } from '@/config/setting'; +import { SERVER_API_URL, THINK_API_URL } from '@/config/setting'; /** * 分页查询角色 @@ -117,3 +117,45 @@ export async function updateRoleMenus(roleId?: number, data?: number[]) { } return Promise.reject(new Error(res.data.message)); } + +/** + * 分页查询角色成员 + */ +export async function pageRoleMembers(params: RoleMemberParam) { + const res = await request.get>>( + THINK_API_URL + '/system/role-member/page', + { params } + ); + if (res.data.code === 0) { + return res.data.data; + } + return Promise.reject(new Error(res.data.message)); +} + +/** + * 添加角色成员 + */ +export async function addRoleMember(data: RoleMemberParam) { + const res = await request.post>( + THINK_API_URL + '/system/role-member', + data + ); + if (res.data.code === 0) { + return res.data.message; + } + return Promise.reject(new Error(res.data.message)); +} + +/** + * 移除角色成员 + */ +export async function removeRoleMember(roleId?: number, userId?: number) { + const res = await request.delete>( + THINK_API_URL + '/system/role-member', + { params: { roleId, userId } } + ); + if (res.data.code === 0) { + return res.data.message; + } + return Promise.reject(new Error(res.data.message)); +} diff --git a/src/api/system/role/model/index.ts b/src/api/system/role/model/index.ts index d4c9a50..4a02264 100644 --- a/src/api/system/role/model/index.ts +++ b/src/api/system/role/model/index.ts @@ -25,3 +25,31 @@ export interface RoleParam extends PageParam { roleCode?: string; comments?: string; } + +/** + * 角色成员 + */ +export interface RoleMember { + // 用户id + userId?: number; + // 账号 + username?: string; + // 真实姓名 + realName?: string; + // 昵称 + nickname?: string; + // 所属组织id + organizationId?: number; + // 所属组织名称 + organizationName?: string; + // 状态, 0启用, 1停用 + status?: number; +} + +/** + * 角色成员查询/操作参数 + */ +export interface RoleMemberParam extends PageParam { + roleId?: number; + userId?: number; +} diff --git a/src/api/system/user/index.ts b/src/api/system/user/index.ts index 2ac2612..f0b4411 100644 --- a/src/api/system/user/index.ts +++ b/src/api/system/user/index.ts @@ -1,7 +1,7 @@ import request from '@/utils/request'; import type { ApiResult, PageResult } from '@/api'; import type { User, UserParam } from './model'; -import { SERVER_API_URL } from '@/config/setting'; +import { SERVER_API_URL, THINK_API_URL } from '@/config/setting'; /** * 分页查询用户 @@ -97,7 +97,7 @@ export async function addUser(data: User) { */ export async function updateUser(data: User) { const res = await request.put>( - SERVER_API_URL + '/system/user', + THINK_API_URL + '/system/user', data ); if (res.data.code === 0) { diff --git a/src/views/approval/instance/components/detail.vue b/src/views/approval/instance/components/detail.vue new file mode 100644 index 0000000..4fa3feb --- /dev/null +++ b/src/views/approval/instance/components/detail.vue @@ -0,0 +1,280 @@ + + + + + + diff --git a/src/views/approval/instance/components/search.vue b/src/views/approval/instance/components/search.vue new file mode 100644 index 0000000..c0053a5 --- /dev/null +++ b/src/views/approval/instance/components/search.vue @@ -0,0 +1,254 @@ + + + + diff --git a/src/views/approval/instance/index.vue b/src/views/approval/instance/index.vue new file mode 100644 index 0000000..5a3dc31 --- /dev/null +++ b/src/views/approval/instance/index.vue @@ -0,0 +1,231 @@ + + + + + + diff --git a/src/views/approval/todo/components/detail-drawer.vue b/src/views/approval/todo/components/detail-drawer.vue new file mode 100644 index 0000000..18f740f --- /dev/null +++ b/src/views/approval/todo/components/detail-drawer.vue @@ -0,0 +1,695 @@ + + + + + + + + diff --git a/src/views/approval/todo/format.ts b/src/views/approval/todo/format.ts new file mode 100644 index 0000000..f1e8936 --- /dev/null +++ b/src/views/approval/todo/format.ts @@ -0,0 +1,46 @@ +import { toDateString } from 'ele-admin-pro'; + +/** + * 审批时间统一展示:兼容 ISO 字符串与时间戳 + */ +export function formatApprovalTime(value?: string | number | null): string { + if (value === undefined || value === null || value === '') { + return ''; + } + if (typeof value === 'number') { + return toDateString(value, 'yyyy-MM-dd HH:mm:ss'); + } + return String(value).replace('T', ' ').slice(0, 19); +} + +/** + * 附件大小展示 + */ +export function formatFileSize(length?: number): string { + if (!length || length <= 0) { + return '0 KB'; + } + if (length < 1024 * 1024) { + return (length / 1024).toFixed(1) + ' KB'; + } + return (length / 1024 / 1024).toFixed(1) + ' MB'; +} + +/** + * 单据状态标签颜色 + */ +export function approvalStatusColor(status?: string): string { + if (status === 'PENDING' || status === 'DRAFT') { + return 'blue'; + } + if (status === 'REJECTED') { + return 'red'; + } + if (status === 'WITHDRAWN') { + return 'orange'; + } + if (status === 'APPROVED') { + return 'green'; + } + return 'default'; +} diff --git a/src/views/approval/todo/index.vue b/src/views/approval/todo/index.vue new file mode 100644 index 0000000..afa3347 --- /dev/null +++ b/src/views/approval/todo/index.vue @@ -0,0 +1,231 @@ + + + + + + + + diff --git a/src/views/system/role/components/role-member.vue b/src/views/system/role/components/role-member.vue new file mode 100644 index 0000000..8228a11 --- /dev/null +++ b/src/views/system/role/components/role-member.vue @@ -0,0 +1,230 @@ + + + + diff --git a/src/views/system/role/index.vue b/src/views/system/role/index.vue index ab6ebca..4462465 100644 --- a/src/views/system/role/index.vue +++ b/src/views/system/role/index.vue @@ -39,6 +39,8 @@ 修改 + 成员 + 分配权限 + + @@ -77,6 +81,7 @@ import RoleSearch from './components/role-search.vue'; import RoleEdit from './components/role-edit.vue'; import RoleAuth from './components/role-auth.vue'; + import RoleMember from './components/role-member.vue'; import { pageRoles, removeRole, removeRoles } from '@/api/system/role'; import type { Role, RoleParam } from '@/api/system/role/model'; @@ -120,7 +125,7 @@ { title: '操作', key: 'action', - width: 200, + width: 260, align: 'center' } ]); @@ -137,6 +142,9 @@ // 是否显示权限分配弹窗 const showAuth = ref(false); + // 是否显示成员维护弹窗 + const showMember = ref(false); + // 表格数据源 const datasource: DatasourceFunction = ({ page, limit, where, orders }) => { return pageRoles({ ...where, ...orders, page, limit }); @@ -160,6 +168,12 @@ showAuth.value = true; }; + /* 打开成员维护弹窗 */ + const openMember = (row?: Role) => { + current.value = row ?? null; + showMember.value = true; + }; + /* 删除单个 */ const remove = (row: Role) => { const hide = messageLoading('请求中..', 0); diff --git a/src/views/system/user/components/user-edit.vue b/src/views/system/user/components/user-edit.vue index 13f807d..59649d2 100644 --- a/src/views/system/user/components/user-edit.vue +++ b/src/views/system/user/components/user-edit.vue @@ -255,14 +255,8 @@ // trigger: 'blur' // } // ], - roles: [ - { - required: true, - message: '请选择角色', - type: 'array', - trigger: 'blur' - } - ], + // 角色可以为空:清空并保存表示删除该用户的全部角色关联 + roles: [], email: [ { pattern: emailReg,