From a48c5f63437f6a34707c64330e8afef7350ab9d2 Mon Sep 17 00:00:00 2001 From: lijing Date: Tue, 22 Sep 2026 09:46:33 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E6=97=A5=E6=9C=9F?= =?UTF-8?q?=E7=AD=9B=E9=80=89=E4=B8=8E=E8=A1=A8=E5=8D=95=E5=8F=82=E6=95=B0?= =?UTF-8?q?=E5=8F=8C=E5=86=99=E3=80=81=E4=B8=8B=E7=BA=BF=E8=8C=83=E5=9B=B4?= =?UTF-8?q?=E9=A2=84=E4=BC=B0=EF=BC=8C=E5=B9=B6=E6=94=B9=E9=80=A0=E5=88=97?= =?UTF-8?q?=E8=A1=A8=E4=B8=89=E5=88=97=E6=A0=B7=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - v29 删除前端对 notice/ScopePreview(推送范围「已选学员数」预估)的请求: api 方法、ScopePreviewVo 类型、dev:mock 桩、ClassScopeInput/PushScopePanel 两个组件的预估 UI 一并移除。 - v30 修复发文部门/联系组名称重复且带逗号(如「校园部,校园部」并逐次翻倍): postForm 原先把参数同时拼进 URL query 与 urlencoded body,Servlet 会合并两处取值、 Spring @RequestParam String 再以「,」拼接。改为只发 body,仅在 dev:mock 下附带 query (vite-plugin-mock 不解析 urlencoded body),不影响线上。 - 修复时间筛选失效:DatePicker 的 Date 实例被 alova 直接 encodeURIComponent 成浏览器本地化 日期串(Fri Aug 21 2026 ... GMT+0800),后端按 yyyy-MM-dd 解析不了。列表侧提交前归一化为 短日期,alova-core 另加一层 Date → 本地 'yyyy-MM-dd HH:mm:ss' 的兜底归一化。 - 列表「类型 / 状态 / 阅读情况」三列改醒目样式(口径不变,仅样式): 类型改胶囊(重要=实心红、普通=浅灰弱化);状态改彩色圆点胶囊,已发布绿/定时中蓝/ 待审批橙/已驳回红/草稿灰,已撤回、置顶改实心小徽标;阅读情况改渐变进度条 + 已读/应读·百分比, 读满转绿。同步微调类型(80→90)、阅读情况(110→180)列宽。 - 同步更新《执行计划任务方案.md》v29 / v30 记录。 --- src/api/module/notice.ts | 22 ++---- src/assets/mock/noticeapi.ts | 25 ------ src/components/ClassScopeInput.vue | 33 +------- src/components/PushScopePanel.vue | 50 +----------- src/types/notice.ts | 12 +-- src/utils/alova/alova-core.ts | 29 +++++++ src/views/notice/NoticeList.vue | 122 +++++++++++++++++++++++------ 执行计划任务方案.md | 103 ++++++++++++++++++++++++ 8 files changed, 244 insertions(+), 152 deletions(-) diff --git a/src/api/module/notice.ts b/src/api/module/notice.ts index 3c3e30c..4da6707 100644 --- a/src/api/module/notice.ts +++ b/src/api/module/notice.ts @@ -23,8 +23,6 @@ import type { ClassTreeNode, ApprovalQuery, ApprovalTabCountVo, - PushScopeItem, - ScopePreviewVo, SubmitRequest, } from "@/types/notice"; @@ -46,7 +44,7 @@ import type { * 变更要点(P1-7 / 校园通知改造): * 7. 全部方法加返回值泛型标注,替换 Record * 8. 列表分页 content 字段对齐后端 BasePageResult(非旧 list) - * 9. 新增:ScopeTree/ScopePreview 推送范围、SaveDraft/DraftList 草稿闭环、 + * 9. 新增:ScopeTree 推送范围、SaveDraft/DraftList 草稿闭环、 * Submit 提交审批、ReadReceipt 阅读回执、Approve 带 opinion */ @@ -88,9 +86,12 @@ function postForm(url: string, data?: Record): Method v !== undefined && v !== null) : []; const form = pairs.map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(String(v))}`).join("&"); - // 同时把参数拼到 URL query:后端 @RequestParam 表单/查询参数都能接收, - // 且本地 vite-plugin-mock 不解析 urlencoded body,靠 query 才能拿到参数(不影响线上)。 - const fullUrl = form ? `${url}${url.includes("?") ? "&" : "?"}${form}` : url; + // 真后端:参数只放进 x-www-form-urlencoded body,URL 不带 query。 + // 修复 v30:若 query 与 body 同时携带同名参数,Servlet 会合并两处取值, + // Spring @RequestParam String 再以「,」拼接,导致部门/联系组等名称变成「校园部,校园部」, + // 且每次改名/启停把已含逗号的值原样回发再翻倍。只发 body 后每个参数只被取到一次。 + // 本地 dev:mock:vite-plugin-mock 不解析 urlencoded body,仅在此模式下把参数附带进 query(不影响线上)。 + const fullUrl = conf.isMock && form ? `${url}${url.includes("?") ? "&" : "?"}${form}` : url; return new Method>( "POST", alovaInstance, @@ -194,15 +195,6 @@ class NoticeApi { return postForm(`${NOTICE}/ReceiveUsers`, { serid, users }); } - // ========== 推送范围(改造方案 3.1 · v3 换源) ========== - /** 范围树数据源已改 base/getStuTree(见 baseApi.getStuTree);此处仅按已选范围预估学员数 */ - scopePreview(scopes: PushScopeItem[], receiveList?: string) { - return get(`${NOTICE}/ScopePreview`, { - scopes: JSON.stringify(scopes || []), - receiveList: receiveList || "", - }); - } - // ========== 发布/编辑/删除/草稿 ========== /** 新建通知(JSON body,fileIds 为附件ID逗号串,smsFlag 为短信标记;带 flow 即送审) */ create(data: NoticeRequest) { diff --git a/src/assets/mock/noticeapi.ts b/src/assets/mock/noticeapi.ts index cbccd01..2b02a80 100644 --- a/src/assets/mock/noticeapi.ts +++ b/src/assets/mock/noticeapi.ts @@ -1184,31 +1184,6 @@ export default [ }, ]), }, - { - // 范围预估:全校=ALL_STUDENTS;年级/班级按 studentCount 汇总;教师按名单数 - url: "/api/notice/ScopePreview", - method: "get", - response: ({ query }: any) => { - let studentCount = 0; - try { - const scopes = JSON.parse(query.scopes || "[]"); - for (const s of scopes) { - if (s.scopeType === 1) studentCount += ALL_STUDENTS; - else if (s.scopeType === 3) { - const g: any = gradeClasses.find((x) => x.gradeId === s.scopeId); - if (g) studentCount += g.classes.reduce((sum: number, c: any) => sum + c.studentCount, 0); - } else if (s.scopeType === 4) { - studentCount += (classMap[String(s.scopeId)] || { studentCount: 0 }).studentCount; - } - } - } catch { - /* ignore */ - } - const receive = String(query.receiveList || "").trim(); - const teacherCount = !receive || /^all$/i.test(receive) ? 0 : receive.split(/[,,\s]+/).filter(Boolean).length; - return ok({ studentCount, teacherCount, totalCount: studentCount + teacherCount }); - }, - }, { // 存为草稿:不圈人,仅内容 + 范围快照 + 附件 url: "/api/notice/SaveDraft", diff --git a/src/components/ClassScopeInput.vue b/src/components/ClassScopeInput.vue index 8d9aba7..c1b3e4f 100644 --- a/src/components/ClassScopeInput.vue +++ b/src/components/ClassScopeInput.vue @@ -10,7 +10,6 @@ -
已选学员 {{ preview.studentCount }} 人
@@ -25,8 +24,8 @@ * 树数据源 base/getStuTree?toUser=false(后端无 GetClassTree),自底向上归出「年级→班级」两层。 */ import UserCheckModal from "@/components/UserCheckModal.vue"; -import { baseApi, noticeApi } from "@/api/module/notice"; -import type { PushScopeItem, SchTreeNode, ScopePreviewVo } from "@/types/notice"; +import { baseApi } from "@/api/module/notice"; +import type { PushScopeItem, SchTreeNode } from "@/types/notice"; /** 全部班级范围项(后端 scopeType=1 不解析 scopeId) */ const ALL_SCOPE: PushScopeItem = { scopeType: 1, scopeId: 0, scopeName: "全部班级" }; @@ -56,8 +55,6 @@ export default { treeFailed: false, /** 树未就绪时的回显缓冲(对齐 PushScopePanel pendingScopes 口径) */ pendingScopes: null as PushScopeItem[] | null, - preview: null as ScopePreviewVo | null, - previewTimer: undefined as number | undefined, sources: [{ name: "班级", loader: () => this.loadTree() }], }; }, @@ -93,9 +90,8 @@ export default { created() { if (this.modelValue.length) { this.syncFromValue(this.modelValue); - this.runPreview(); } else { - this.applyAll(); // 新建默认全部班级(emit 后由 watch 触发预估) + this.applyAll(); // 新建默认全部班级 } }, mounted() { @@ -268,24 +264,12 @@ export default { this.classStr = [...extras, ...classes].map((x) => x.scopeName).join("、"); } }, - async runPreview() { - if (!this.modelValue.length) { - this.preview = null; - return; - } - try { - this.preview = await noticeApi.scopePreview(this.modelValue as PushScopeItem[]).send(); - } catch { - this.preview = null; - } - }, }, watch: { modelValue: { handler(val: PushScopeItem[]) { - // 自身 emit 的变化只跳过状态回灌,预估仍按最新值刷新 + // 自身 emit 的变化只跳过状态回灌 if (!this.suppressWatch) this.syncFromValue(val || []); - this.runPreview(); }, deep: true, }, @@ -311,14 +295,5 @@ export default { flex: 1; } } - .scope-preview { - margin-top: 8px; - font-size: 12px; - color: #808695; - b { - color: #2d8cf0; - font-size: 14px; - } - } } diff --git a/src/components/PushScopePanel.vue b/src/components/PushScopePanel.vue index ce5c221..bb7ee16 100644 --- a/src/components/PushScopePanel.vue +++ b/src/components/PushScopePanel.vue @@ -35,11 +35,6 @@
未选择推送范围
- - -
- 已选学员 {{ preview ? preview.studentCount : "…" }} -
@@ -49,13 +44,12 @@ * v-model = PushScopeItem[](最粗粒度勾选:父节点勾选则忽略其子孙)。 * getStuTree 组节点不区分学校/学段/年级/班级,toScopeNode 自底向上归类(对齐 PushScopeTypeEnum): * 无组子节点=班级(4),其父=年级(3),再上=学部(2),顶层根强制=全校(1)。 - * 选中学员数:notice/ScopePreview.studentCount。 * 编辑回显:父组件加载详情后调用 setScopes(pushScopes) 勾选对应节点; * 旧链路班级通知(receiveType=class)由父组件映射为 scopeType=4 的班级项。 */ -import { ref, computed, onMounted, watch } from "vue"; -import { baseApi, noticeApi } from "@/api/module/notice"; -import type { PushScopeItem, SchTreeNode, ScopeTreeNode, ScopePreviewVo } from "@/types/notice"; +import { ref, computed, onMounted } from "vue"; +import { baseApi } from "@/api/module/notice"; +import type { PushScopeItem, SchTreeNode, ScopeTreeNode } from "@/types/notice"; const props = defineProps<{ modelValue: PushScopeItem[]; @@ -232,31 +226,6 @@ function applyScopes(scopes?: PushScopeItem[] | null) { defineExpose({ setScopes }); -/* ============ 选中学员数预估(ScopePreview,防抖 350ms) ============ */ -const preview = ref(null); -let previewTimer: number | undefined; - -async function runPreview() { - if (!props.modelValue.length) { - preview.value = null; - return; - } - try { - preview.value = await noticeApi.scopePreview(props.modelValue).send(); - } catch { - preview.value = null; - } -} - -watch( - () => props.modelValue, - () => { - window.clearTimeout(previewTimer); - previewTimer = window.setTimeout(runPreview, 350); - }, - { deep: true } -); - /** getStuTree 组节点 → 面板范围节点:自底向上归类 scopeType(对齐 api-test toScopeNode,见头注释) */ function toScopeNode(node: SchTreeNode, forcedType?: number): ScopeTreeNode | null { if (!node || node.type !== 1) return null; // toUser=false 仅组节点;出现人员叶子防御性跳过 @@ -368,18 +337,5 @@ onMounted(async () => { font-size: 12px; margin-top: 8px; } - .scope-preview { - margin-top: 8px; - background: #f8fafc; - border-radius: 4px; - padding: 8px 12px; - font-size: 13px; - display: flex; - gap: 16px; - b { - font-size: 15px; - color: #2d8cf0; - } - } } diff --git a/src/types/notice.ts b/src/types/notice.ts index 04f5b7e..93eeb1f 100644 --- a/src/types/notice.ts +++ b/src/types/notice.ts @@ -12,7 +12,7 @@ * - NoticeStatisticsVo → 后端 model/notice/NoticeStatisticsVo.java * - NoticeRequest → 后端 model/notice/NoticeRequest.java * - NoticeQuery → 后端 model/notice/NoticeQuery.java + BasePageQuery.java - * - PushScopeItem / ScopeTreeNode / ScopePreviewVo → 改造方案 3.1 推送范围 + * - PushScopeItem / ScopeTreeNode → 改造方案 3.1 推送范围 * - ReadReceiptVo → 后端 model/notice/ReadReceiptVo.java(阅读回执分组) * - ReadTabCountVo → 后端 model/notice/ReadTabCountVo.java(v17「已收到」三 tab 统计) * - SubmitRequest / ApproverSuggestVo → 审核改造(步骤表 + 审核记录表 + 记忆表) @@ -152,16 +152,6 @@ export interface ScopeTreeNode { children?: ScopeTreeNode[]; } -/** 推送范围预估 VO(notice/ScopePreview) */ -export interface ScopePreviewVo { - /** 范围展开学员数(按账号去重) */ - studentCount: number; - /** 教师定向名单数(receiveList 解析) */ - teacherCount: number; - /** 合计预估接收人数 */ - totalCount: number; -} - // ============================ 阅读回执 ============================ /** 已发送通知阅读回执(notice/ReadReceipt,按 年级→班级→学生 分组 + 教师组) */ diff --git a/src/utils/alova/alova-core.ts b/src/utils/alova/alova-core.ts index 9a01f9c..7586189 100644 --- a/src/utils/alova/alova-core.ts +++ b/src/utils/alova/alova-core.ts @@ -33,6 +33,33 @@ const defHooks: Required = { onHttpError: () => {}, }; +/* ============= query 参数归一化 ============= */ +const pad2 = (n: number) => String(n).padStart(2, "0"); + +/** 本地时间 → 'yyyy-MM-dd HH:mm:ss' */ +function localDateTime(d: Date): string { + return `${d.getFullYear()}-${pad2(d.getMonth() + 1)}-${pad2(d.getDate())} ${pad2(d.getHours())}:${pad2( + d.getMinutes() + )}:${pad2(d.getSeconds())}`; +} + +/** + * 兜底:把 params 里的 Date 实例转成本地 'yyyy-MM-dd HH:mm:ss'。 + * + * alova 拼 query 时对参数只做 `encodeURIComponent(params[key])`,Date 会被隐式转成 + * 浏览器本地化串「Fri Aug 21 2026 00:00:00 GMT+0800 (中国标准时间)」,后端无法解析。 + * 业务侧应显式传字符串(如校园公告列表的 startTime/endTime 用 yyyy-MM-dd 短日期), + * 这里只做防止 Date 直接泄漏到 URL 的最后一道保险,不改变已是字符串的参数。 + */ +function normalizeParams(method: Method) { + const params: any = method.config?.params; + if (!params || typeof params !== "object" || Array.isArray(params)) return; + Object.keys(params).forEach((k) => { + const v = params[k]; + if (v instanceof Date && !isNaN(v.getTime())) params[k] = localDateTime(v); + }); +} + /* ============= 错误处理(原 axios 逻辑平移) ============= */ function errorHandle(res: BschResponse, status: number): T { // http 层错误 @@ -95,6 +122,8 @@ export function createBschAlova( onLoadingEnd(); } }); + // 0. query 参数兜底归一化(Date → 本地时间串),避免本地化日期串进 URL + normalizeParams(method); // 1. 用户注入票据/角色等头 injectHeaders(method); diff --git a/src/views/notice/NoticeList.vue b/src/views/notice/NoticeList.vue index 37f971a..6afbdf6 100644 --- a/src/views/notice/NoticeList.vue +++ b/src/views/notice/NoticeList.vue @@ -28,6 +28,22 @@ function fmtDate(d: Date) { return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`; } +/** + * 日期筛选值 → 接口短日期(yyyy-MM-dd)。 + * + * DatePicker 的 v-model 绑定的是 **Date 实例**(视图内部值),而 alova/fetch 拼 query + * 时对参数只做 encodeURIComponent(String(v)),Date 会退化成浏览器本地化串: + * startTime=Fri%20Aug%2021%202026%2000:00:00%20GMT+0800%20(中国标准时间) + * 后端按 yyyy-MM-dd 解析不了(NoticeQuery.startTime 契约即短日期),时间筛选因此失效。 + * 故发请求前统一归一化:Date 走本地日期,字符串(resetFilter 写入的默认值)取其前 10 位。 + */ +function shortDate(v: unknown): string { + if (!v) return ""; + if (v instanceof Date) return isNaN(v.getTime()) ? "" : fmtDate(v); + const m = String(v).match(/^(\d{4})-(\d{1,2})-(\d{1,2})/); + return m ? `${m[1]}-${m[2].padStart(2, "0")}-${m[3].padStart(2, "0")}` : ""; +} + /* ============ 分页(规范 §3:customPageCount 页长记忆) ============ */ const pageNum = ref(1); const pageSize = ref(parseInt(localStorage.getItem("customPageCount") || "10")); @@ -47,8 +63,10 @@ async function run() { }; if (statusType.value !== "") params.status = Number(statusType.value); if (noticeType.value !== "") params.type = Number(noticeType.value); - if (startDt.value) params.startTime = startDt.value; - if (endDt.value) params.endTime = endDt.value; + const startTime = shortDate(startDt.value); + const endTime = shortDate(endDt.value); + if (startTime) params.startTime = startTime; + if (endTime) params.endTime = endTime; if (!isManage.value) { params.scope = "user"; } @@ -226,31 +244,93 @@ function downloadFiles(row: NoticeVo & { UpFile?: string }) { }); } -/* 状态列:草稿/已发布(定时中)/待审批(步骤进度)/已驳回 + 已撤回/置顶徽标 */ -const STATUS_TAG: Record = { pending: "warning", approved: "success", rejected: "error", none: "default" }; +/* ============ 单元格视觉(纯样式,口径不变;视觉语言对齐家庭活动「我发布的」列表) ============ + * render 单元格由 Table 内部渲染,不吃本文件的 scoped 样式,故统一用内联样式。 + * 色板:灰 #c5c8ce / 蓝 #2d8cf0 / 橙 #ff9900 / 绿 #19be6b / 红 #ed4014(与参考列表同一套)。 + */ +const C_GREY = "#c5c8ce"; +const C_BLUE = "#2d8cf0"; +const C_ORANGE = "#ff9900"; +const C_GREEN = "#19be6b"; +const C_RED = "#ed4014"; +/** 状态胶囊:彩色圆点 + 文字(参考列表 statusPill 同款) */ +function statusPill(h: any, hex: string, text: string) { + return h("span", { style: "display:inline-flex;align-items:center;gap:6px;font-size:13px;color:#515a6e;white-space:nowrap;" }, [ + h("i", { style: `width:8px;height:8px;border-radius:50%;display:inline-block;flex:none;background:${hex};` }), + text, + ]); +} + +/** 实心小徽标:置顶 / 已撤回(参考列表「顶」「已撤回」同款) */ +function miniBadge(h: any, hex: string, text: string) { + return h( + "span", + { style: `display:inline-block;padding:0 5px;margin-left:4px;font-size:11px;line-height:16px;color:#fff;background:${hex};border-radius:3px;vertical-align:1px;white-space:nowrap;` }, + text + ); +} + +/** 类型胶囊:重要=实心红醒目,普通=浅灰弱化(同列对比出重要程度) */ +function typeCell(h: any, row: any) { + const important = row.type == 2; + return h( + "span", + { + style: important + ? `display:inline-block;padding:0 9px;font-size:12px;line-height:18px;font-weight:600;color:#fff;background:${C_RED};border-radius:9px;` + : "display:inline-block;padding:0 9px;font-size:12px;line-height:18px;color:#808695;background:#f5f7fa;border:1px solid #e8eaec;border-radius:9px;", + }, + row.typeDesc || (important ? "重要" : "普通") + ); +} + +/** 阅读情况:渐变进度条 + 已读/应读 · 百分比(参考列表回执进度同款;读满转绿加粗) */ +function readCell(h: any, row: any) { + // 未发布(草稿/待审批/已驳回)无阅读数据,不占进度条 + if (row.status !== 1) return h("span", { style: `color:${C_GREY};font-size:13px;` }, "—"); + const allow = Number(row.allowCount) || 0; + const read = Number(row.readCount) || 0; + if (!allow) return h("span", { style: "color:#808695;font-size:13px;" }, "暂无应读人"); + const pct = Math.min(100, Math.round((read / allow) * 100)); + const done = read >= allow; + return h("div", { style: "display:flex;align-items:center;gap:8px;" }, [ + h("div", { style: "flex:1;min-width:54px;height:6px;background:#f0f2f5;border-radius:4px;overflow:hidden;" }, [ + h("div", { + style: `width:${pct}%;height:100%;border-radius:4px;background:${done ? `linear-gradient(90deg,${C_GREEN},#3fc98a)` : `linear-gradient(90deg,${C_BLUE},#5cadff)`};`, + }), + ]), + h( + "span", + { style: `font-size:12px;white-space:nowrap;color:${done ? C_GREEN : "#515a6e"};font-weight:${done ? 600 : 400};` }, + `${read}/${allow} · ${pct}%` + ), + ]); +} + +/* 状态列:草稿/已发布(定时中)/待审批(步骤进度)/已驳回 + 已撤回/置顶徽标 */ function renderStatus(h: any, row: any) { const parts: any[] = []; if (row.status === 1) { // 定时未到点:定时中(已排期) if (row.timingSendTime && row.timingPublished === 0 && row.isPulled !== 1) { - parts.push(h("Tag", { color: "purple" }, () => "定时中")); + parts.push(statusPill(h, C_BLUE, "定时中")); } else { - parts.push(h("Tag", { color: "success" }, () => "已发布")); + parts.push(statusPill(h, C_GREEN, "已发布")); } } else if (row.status === 2) { - parts.push(h("Tag", { color: "warning" }, () => "待审批")); + parts.push(statusPill(h, C_ORANGE, "待审批")); // 多步进度文本(如「第1/2步 校级审核·会签 0/2」),与徽标重复时不再显示 if (row.approvalStatusDesc && row.approvalStatusDesc !== "待审批") { parts.push(h("div", { style: "font-size:12px;color:#808695;line-height:1.4;margin-top:2px" }, String(row.approvalStatusDesc))); } } else if (row.status === 3) { - parts.push(h("Tag", { color: "error" }, () => "已驳回")); + parts.push(statusPill(h, C_RED, "已驳回")); } else { - parts.push(h("Tag", {}, () => row.statusDesc || "草稿")); + parts.push(statusPill(h, C_GREY, row.statusDesc || "草稿")); } - if (row.isPulled === 1) parts.push(h("Tag", { style: "margin-left:4px" }, () => "已撤回")); - if (row.topmost == 1) parts.push(h("Tag", { color: "orange", style: "margin-left:4px" }, () => "置顶")); + if (row.isPulled === 1) parts.push(miniBadge(h, C_ORANGE, "已撤回")); + if (row.topmost == 1) parts.push(miniBadge(h, C_RED, "置顶")); return h("div", {}, parts); } @@ -268,26 +348,17 @@ const columns = computed(() => [ return h("span", { attrs: { title: t } }, t.length > 8 ? `${t.slice(0, 8)}…` : t); }, }, - { title: "类型", width: 80, align: "center", - render: (h: any, p: any) => h("Tag", { color: p.row.type == 2 ? "error" : "default" }, () => p.row.typeDesc || (p.row.type == 2 ? "重要" : "普通")) }, + { title: "类型", width: 80, align: "center", render: (h: any, p: any) => typeCell(h, p.row) }, { title: "来源部门", key: "deptName", width: 110, align: "left" }, { title: "发布人", key: "publisherName", width: 90, align: "left", tooltip: true }, { title: "状态", width: 190, align: "left", render: (h: any, p: any) => renderStatus(h, p.row) }, { title: "发布时间", - width: 150, + width: 160, align: "left", render: (h: any, p: any) => h("span", {}, String(p.row.timingSendTime || p.row.createTime || "").replace("T", " ").slice(0, 16)), }, - { - title: "阅读情况", - width: 110, - align: "left", - render: (h: any, p: any) => - p.row.status === 1 - ? h("span", {}, `${p.row.readCount || 0} / ${p.row.allowCount || 0} 已读`) - : h("span", { style: "color:#999;" }, "—"), - }, + { title: "阅读情况", width: 180, align: "left", render: (h: any, p: any) => readCell(h, p.row) }, { title: "附件", width: 70, @@ -341,9 +412,10 @@ const tableHeight = computed(() => Utils.useTableHeight({ isSearch: true, isOper
- + + 至 - +