diff --git a/components.d.ts b/components.d.ts index 124043e..1cff276 100644 --- a/components.d.ts +++ b/components.d.ts @@ -21,10 +21,12 @@ declare module 'vue' { ElAlert: typeof import('element-plus/es')['ElAlert'] ElAside: typeof import('element-plus/es')['ElAside'] ElAvatar: typeof import('element-plus/es')['ElAvatar'] + ElBadge: typeof import('element-plus/es')['ElBadge'] ElBreadcrumb: typeof import('element-plus/es')['ElBreadcrumb'] ElBreadcrumbItem: typeof import('element-plus/es')['ElBreadcrumbItem'] ElButton: typeof import('element-plus/es')['ElButton'] ElCard: typeof import('element-plus/es')['ElCard'] + ElCascader: typeof import('element-plus/es')['ElCascader'] ElCheckbox: typeof import('element-plus/es')['ElCheckbox'] ElCol: typeof import('element-plus/es')['ElCol'] ElCollapse: typeof import('element-plus/es')['ElCollapse'] @@ -50,6 +52,7 @@ declare module 'vue' { ElImageViewer: typeof import('element-plus/es')['ElImageViewer'] ElInput: typeof import('element-plus/es')['ElInput'] ElInputNumber: typeof import('element-plus/es')['ElInputNumber'] + ElLink: typeof import('element-plus/es')['ElLink'] ElMain: typeof import('element-plus/es')['ElMain'] ElMenu: typeof import('element-plus/es')['ElMenu'] ElMenuItem: typeof import('element-plus/es')['ElMenuItem'] diff --git a/src/api/request/publicRequest.ts b/src/api/request/publicRequest.ts index 729ceaa..166235d 100644 --- a/src/api/request/publicRequest.ts +++ b/src/api/request/publicRequest.ts @@ -70,6 +70,9 @@ function reportError(error: PublicApiError, notifyError: boolean) { } async function readErrorMessage(response: Response) { + if (response.status === 413) { + return "单个文件不能超过20MB,请压缩后重新上传"; + } try { const payload = await response.clone().json(); return payload?.msg || payload?.message || `请求失败(${response.status})`; diff --git a/src/components/Selectors/RoleSelector/index.vue b/src/components/Selectors/RoleSelector/index.vue index e0408a0..3e354bb 100644 --- a/src/components/Selectors/RoleSelector/index.vue +++ b/src/components/Selectors/RoleSelector/index.vue @@ -4,56 +4,62 @@ * @Date: 2023-12-15 15:40:39 !--> @@ -67,32 +73,41 @@ import { Plus, Delete } from "@element-plus/icons-vue"; const emit = defineEmits({ successful: null }); // 自定义事件 -const visible = ref(false); //是否显示 +const visible = ref(false); // 是否显示 -// 定义组件props +// 定义组件 props const props = withDefaults(defineProps(), { permission: false, - multiple: false + multiple: false, + formSize: "1300px" }); -// 如果表格需要初始化请求参数,直接定义传给 ProTable(之后每次请求都会自动带上该参数,此参数更改之后也会一直带上,改变此参数会自动刷新表格数据) +// 如果表格需要初始化请求参数,直接定义传给 ProTable const initParam = reactive({}); -// 获取 userTable 元素,调用其获取刷新数据方法(还能获取到当前查询参数,方便导出携带参数) +// 获取 userTable 元素 const userTable = ref(); -// 获取 chooseTable 元素,调用其获取刷新数据方法(还能获取到当前查询参数,方便导出携带参数) +// 获取 chooseTable 元素 const chooseTable = ref(); -// 表格配置项 + +// 待选角色表格配置项(包含搜索项) const columns: ColumnProps[] = [ { type: "selection", fixed: "left", width: 50 }, { prop: "operation", label: "操作", width: 80, fixed: "left" }, - { prop: "name", label: "角色名", search: { el: "input", span: 2 } } + { prop: "name", label: "角色名", search: { el: "input" } } +]; + +// 已选角色表格配置项(不包含搜索,更轻量) +const chooseColumns: ColumnProps[] = [ + { type: "selection", fixed: "left", width: 50 }, + { prop: "operation", label: "操作", width: 80, fixed: "left" }, + { prop: "name", label: "角色名" } ]; /** 显示选择器 */ function showSelector(data: SysRole.SysRoleInfo[] = []) { visible.value = true; - chooseDataTmp.value = data; - chooseData.value = data; + chooseDataTmp.value = data ? [...data] : []; + chooseData.value = data ? [...data] : []; } /** 关闭选择器 */ @@ -110,74 +125,140 @@ function handleOk() { /** 部门切换 */ function changeOrgTreeFilter(val: number | string) { - userTable.value!.pageable.pageNum = 1; - if (val != "") { - // 如果传入的val不为空 + if (userTable.value?.pageable) { + userTable.value.pageable.pageNum = 1; + } + if (val !== "") { initParam.orgId = val; } else { initParam.orgId = null; } } -const chooseData = ref([]); //选择的数据 -const chooseDataTmp = ref([]); //临时选择的数据 +const chooseData = ref([]); // 选择的数据 +const chooseDataTmp = ref([]); // 临时选择的数据 /** 添加记录 */ function addRecords(records: any[]) { - //如果不是多选,判断是否已经添加了 + // 如果不是多选,判断是否已经添加了 if (!props.multiple) { if (chooseData.value.length > 0 || records.length > 1) { - ElMessage.warning("只可选择一条"); + ElMessage.warning("只可选择一个角色"); return; } chooseData.value = records; chooseDataTmp.value = chooseData.value; } else { - //如果是多选,先判断已添加列表是否有重复的id,有则过滤掉,没有则直接添加 - records = records.filter(item => !chooseData.value.find(it => it.id == item.id)); + // 如果是多选,先判断已添加列表是否有重复的id,有则过滤掉,没有则直接添加 + records = records.filter(item => !chooseData.value.find(it => it.id === item.id)); if (props.maxCount && props.maxCount < records.length + chooseData.value.length) { - ElMessage.warning("最多选择" + props.maxCount + "条"); + ElMessage.warning(`最多选择 ${props.maxCount} 个角色`); return; } - chooseData.value = chooseData.value.concat(records); //添加到已选中列表 + chooseData.value = chooseData.value.concat(records); // 添加到已选中列表 chooseDataTmp.value = chooseData.value; } - chooseTable.value?.refresh(); //刷新表格 + chooseTable.value?.refresh(); // 刷新表格 } /** 删除记录 */ function delRecords(records: any[]) { - chooseData.value = chooseData.value.filter(item => !records.includes(item)); //过滤掉已选中的 + chooseData.value = chooseData.value.filter(item => !records.includes(item)); // 过滤掉已选中的 chooseDataTmp.value = chooseData.value; - chooseTable.value?.refresh(); //刷新表格 + chooseTable.value?.refresh(); // 刷新表格 } -/** 搜索记录 */ -function searchRecords() { - if (chooseTable.value?.searchParam?.name) { - //搜索name符合的记录 - chooseDataTmp.value = chooseDataTmp.value.filter(item => item.name.includes(chooseTable.value?.searchParam.name)); //过滤掉已选中的 - chooseTable.value?.refresh(); //刷新表格 - } -} - -/** 重置记录 */ -function resetRecords() { - chooseDataTmp.value = chooseData.value; - chooseTable.value?.refresh(); //刷新表格 -} // 暴露方法 defineExpose({ showSelector }); diff --git a/src/components/Selectors/RoleSelector/interface.ts b/src/components/Selectors/RoleSelector/interface.ts index f7d135c..38b29af 100644 --- a/src/components/Selectors/RoleSelector/interface.ts +++ b/src/components/Selectors/RoleSelector/interface.ts @@ -27,6 +27,8 @@ export interface RoleSelectProps { multiple?: boolean; /** 最大角色数 */ maxCount?: number; + /** 弹窗宽度尺寸 */ + formSize?: string; } /** 角色选择器表格初始化参数 */ diff --git a/src/views/biz/onboarding/employee/index.vue b/src/views/biz/onboarding/employee/index.vue index 007b39c..aff4588 100644 --- a/src/views/biz/onboarding/employee/index.vue +++ b/src/views/biz/onboarding/employee/index.vue @@ -243,36 +243,54 @@ :key="record.id" :timestamp="formatTime(record.createTime)" placement="top" - type="primary" + :type="changeTimelineType(record.changeType)" >
+
- {{ changeTypeText(record.changeType) }} - {{ record.createUser || "系统" }} +
+ + {{ changeTypeText(record.changeType) }} + + + 生效于 {{ formatDate(record.effectiveDate) }} + +
+
+ + {{ record.createUser || "系统" }} +
-
- 生效日期:{{ formatDate(record.effectiveDate) }} -
-
- 备注:{{ record.remark }} -
-
-
变更明细
- +
+
- - - - + {{ diff.label }} +
+ {{ diff.beforeText }} + + {{ diff.afterText }} +
+
+
+ 前后字段值无实质差异 +
- 该记录未保存修改前后值 + 该记录未保存修改前后快照
+ + +
+ + {{ record.remark }} +
+ +
= 10) { + ElMessage.warning("文件总数不超过 10"); + return false; + } const extension = "." + (file.name.split(".").pop() || "").toLowerCase(); const allowed = [ ".pdf", ".doc", ".docx", ".xls", ".xlsx", @@ -1506,6 +1574,9 @@ async function submitRegularization() { if (!regularizationForm.regularizationDate) { return ElMessage.warning("请选择转正日期"); } + if (regularizationFiles.value.length > 10) { + return ElMessage.warning("文件总数不超过 10"); + } if (regularizationFiles.value.some(file => file.status === "uploading")) { return ElMessage.warning("材料仍在上传,请稍候"); } @@ -1513,6 +1584,9 @@ async function submitRegularization() { const fileIds = regularizationFiles.value .map(file => Number((file.response as any)?.fileId)) .filter(fileId => Number.isFinite(fileId) && fileId > 0); + if (fileIds.length > 10) { + return ElMessage.warning("文件总数不超过 10"); + } if ( regularizationFiles.value.some(file => file.status === "fail") || fileIds.length !== regularizationFiles.value.length @@ -1753,59 +1827,175 @@ onBeforeUnmount(clearMaterialPreviews); } .change-record-card { - padding: 12px 14px; + padding: 14px 16px; border: 1px solid #e2e8f0; - border-radius: 8px; - background: #f8fafc; + border-radius: 10px; + background: #ffffff; + box-shadow: 0 1px 3px rgba(15, 23, 42, 0.04); + transition: all 0.2s ease; +} + +.change-record-card:hover { + border-color: #cbd5e1; + box-shadow: 0 4px 12px rgba(15, 23, 42, 0.06); } .change-record-header { display: flex; align-items: center; justify-content: space-between; - gap: 12px; - color: #1e293b; + flex-wrap: wrap; + gap: 10px; + padding-bottom: 10px; + border-bottom: 1px dashed #f1f5f9; } -.change-record-header span, -.change-record-line { +.change-record-title-wrap { + display: flex; + align-items: center; + flex-wrap: wrap; + gap: 8px; +} + +.change-type-tag { + font-weight: 600; + border-radius: 6px; +} + +.change-effective-badge { + display: inline-flex; + align-items: center; + font-size: 12px; color: #64748b; + background: #f8fafc; + padding: 2px 8px; + border-radius: 4px; +} + +.change-operator { + display: inline-flex; + align-items: center; + gap: 4px; font-size: 13px; + color: #475569; + background: #f1f5f9; + padding: 3px 10px; + border-radius: 20px; } -.change-record-line { - margin-top: 6px; +.operator-icon { + font-size: 13px; + color: #64748b; } -.change-diff { +.operator-name { + font-weight: 500; +} + +/* 变更 Diff 标签流 */ +.change-diff-flow { + display: flex; + flex-direction: column; + gap: 8px; margin-top: 12px; } -.change-diff-title { - margin-bottom: 8px; - color: #334155; +.change-diff-item { + display: flex; + align-items: center; + flex-wrap: wrap; + gap: 12px; font-size: 13px; - font-weight: 600; + padding: 8px 12px; + background: #f8fafc; + border-radius: 6px; + border: 1px solid #f1f5f9; } -.change-diff-table { - width: 100%; +.diff-field-label { + min-width: 90px; + font-weight: 600; + color: #475569; + flex-shrink: 0; +} + +.diff-values { + display: flex; + align-items: center; + flex-wrap: wrap; + gap: 8px; +} + +.diff-val-before { + color: #64748b; + background: #ffffff; + padding: 2px 8px; + border-radius: 4px; + border: 1px solid #e2e8f0; + font-size: 12px; +} + +.diff-arrow { + color: #94a3b8; + font-size: 13px; +} + +.diff-val-after { + color: #2563eb; + background: #eff6ff; + padding: 2px 10px; + border-radius: 4px; + border: 1px solid #bfdbfe; + font-weight: 600; + font-size: 12px; +} + +.change-diff-none { + color: #94a3b8; + font-size: 13px; + padding: 6px 0; } .change-record-empty { margin-top: 10px; padding: 8px 10px; - border-radius: 4px; - background: #f1f5f9; + border-radius: 6px; + background: #f8fafc; color: #94a3b8; font-size: 13px; } +/* 真实备注气泡框 */ +.change-remark-box { + margin-top: 10px; + padding: 8px 12px; + border-radius: 6px; + background: #f8fafc; + border-left: 3px solid #cbd5e1; + display: flex; + align-items: flex-start; + gap: 6px; + font-size: 13px; + color: #475569; +} + +.remark-icon { + margin-top: 2px; + color: #94a3b8; + flex-shrink: 0; +} + +.remark-text { + line-height: 1.5; +} + .change-attachments { display: flex; flex-wrap: wrap; gap: 8px; - margin-top: 8px; + margin-top: 10px; + padding-top: 8px; + border-top: 1px dashed #f1f5f9; } .section-card { diff --git a/src/views/biz/onboarding/entry-config/index.vue b/src/views/biz/onboarding/entry-config/index.vue index 3b88c1f..28d2b44 100644 --- a/src/views/biz/onboarding/entry-config/index.vue +++ b/src/views/biz/onboarding/entry-config/index.vue @@ -92,7 +92,7 @@ - + MB diff --git a/src/views/biz/onboarding/task/index.vue b/src/views/biz/onboarding/task/index.vue index 88592e1..a78b984 100644 --- a/src/views/biz/onboarding/task/index.vue +++ b/src/views/biz/onboarding/task/index.vue @@ -888,21 +888,10 @@ plain :disabled="item.reviewStatus === 'passed' || !canManage(detail.task)" :title="disabledReason(detail.task)" - @click="reviewMaterial(item.id, 'passed')" + @click="reviewMaterial(item.id)" > 合格 - - 需补充 -
@@ -2869,21 +2858,9 @@ async function download(fileId: number, fallbackFileName?: string) { onBeforeUnmount(clearMaterialPreviews); -async function reviewMaterial(taskMaterialId: number, reviewStatus: string) { - let reviewRemark = ""; - if (reviewStatus === "need_supplement") { - const result = await getMessageBoxPromptResult( - ElMessageBox.prompt("请输入该材料退回补充说明:", "材料不合格退回", { - inputPattern: /\S+/, - inputErrorMessage: "请填写说明" - }) - ); - if (!result) return; - reviewRemark = result.value; - } - - await onboardingTaskApi.reviewMaterial({ taskMaterialId, reviewStatus, reviewRemark }); - ElMessage.success(reviewStatus === "passed" ? "该材料已标记为合格" : "已标记需补充"); +async function reviewMaterial(taskMaterialId: number) { + await onboardingTaskApi.reviewMaterial({ taskMaterialId, reviewStatus: "passed", reviewRemark: "" }); + ElMessage.success("该材料已标记为合格"); if (detail.value?.task) { await showDetail(detail.value.task, true); diff --git a/src/views/onboarding/fill/index.vue b/src/views/onboarding/fill/index.vue index fc4f60f..249d35a 100644 --- a/src/views/onboarding/fill/index.vue +++ b/src/views/onboarding/fill/index.vue @@ -1396,12 +1396,16 @@ function getRemainingFileCount(mat: any) { ); } +const PUBLIC_MATERIAL_MAX_FILE_SIZE_MB = 20; +const PUBLIC_MATERIAL_DEFAULT_EXTENSIONS = ["png", "jpg", "jpeg", "pdf"]; + function getAllowedExtensions(mat: any) { - return String(mat.allowedExtensions || "") + const configured = String(mat.allowedExtensions || "") .toLowerCase() .split(",") .map((item: string) => item.trim().replace(/^\./, "")) .filter(Boolean); + return configured.length ? configured : [...PUBLIC_MATERIAL_DEFAULT_EXTENSIONS]; } function getUploadAccept(mat: any) { @@ -1415,9 +1419,11 @@ function getMaterialLimitText(mat: any) { const formatText = allowedExtensions.length ? allowedExtensions.join("、").toUpperCase() : "不限"; - const sizeText = mat.maxFileSizeMb - ? ` · 单个不超过 ${mat.maxFileSizeMb} MB` - : ""; + const configuredSize = Number(mat.maxFileSizeMb); + const maxFileSizeMb = configuredSize > 0 + ? Math.min(configuredSize, PUBLIC_MATERIAL_MAX_FILE_SIZE_MB) + : PUBLIC_MATERIAL_MAX_FILE_SIZE_MB; + const sizeText = ` · 单个不超过 ${maxFileSizeMb} MB`; return `最多 ${getMaxFileCount(mat)} 份 · 格式:${formatText}${sizeText}`; } @@ -1702,8 +1708,12 @@ function beforeUpload(file: File, mat: any) { ElMessage.error(`仅支持格式:${mat.allowedExtensions}`); return false; } - if (mat.maxFileSizeMb && file.size > Number(mat.maxFileSizeMb) * 1024 * 1024) { - ElMessage.error(`单个文件不能超过 ${mat.maxFileSizeMb} MB`); + const configuredSize = Number(mat.maxFileSizeMb); + const maxFileSizeMb = configuredSize > 0 + ? Math.min(configuredSize, PUBLIC_MATERIAL_MAX_FILE_SIZE_MB) + : PUBLIC_MATERIAL_MAX_FILE_SIZE_MB; + if (file.size > maxFileSizeMb * 1024 * 1024) { + ElMessage.error(`单个文件不能超过 ${maxFileSizeMb} MB,请压缩后重新上传`); return false; } setMaterialUploading(mat.materialTypeId, true);