From d8e7e2a2a824ca2fc8cf35f1635383c1bb8e4f47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B5=B5=E5=BF=A0=E6=9E=97?= <170083662@qq.com> Date: Fri, 6 Nov 2026 12:04:32 +0800 Subject: [PATCH] =?UTF-8?q?feat(volunteer):=20=E6=96=B0=E5=A2=9E=E5=BF=97?= =?UTF-8?q?=E6=84=BF=E6=9C=8D=E5=8A=A1=E8=AF=81=E6=98=8E=E6=9D=90=E6=96=99?= =?UTF-8?q?=E8=AF=86=E5=88=AB=E5=8F=8A=E5=B9=B4=E5=BA=A6=E8=AF=81=E6=98=8E?= =?UTF-8?q?=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增志愿证明材料识别功能,支持自动识别材料并回填关键信息 - 在志愿时长编辑页面添加材料识别按钮及识别结果展示 - 新增志愿服务证明模块,支持查询、生成及下载年度志愿服务证明PDF - 志愿时长列表及审核详情中新增“服务地点”字段显示 - 志愿时长导入功能支持“服务地点”字段,完善导入表头与数据映射 - 志愿时长详情、审核流程等界面优化,增加识别材料公章状态及流程节点提示 - 志愿时长待审核项支持用户撤回操作,撤回后记录被删除需重新录入 - 社团年度报告相关接口新增,实现社团年度报告生成及分页查询 - 调整相关样式与界面布局,提升用户体验与信息展示完整性 --- docs/ai-qmgc-student-analysis-contract.md | 68 +++ docs/phase2-P0-delivery.md | 4 +- docs/phase2-implementation-plan.md | 4 + src/api/ai/qmgcStudentAnalysis/index.ts | 24 + src/api/ai/qmgcStudentAnalysis/model/index.ts | 21 + src/api/growth/club/index.ts | 56 +++ src/api/growth/volunteer/index.ts | 140 ++++++ src/utils/ai-stream.ts | 14 + src/views/club/report/index.vue | 350 ++++++++++++++ src/views/club/volunteerCertificate/index.vue | 176 +++++-- .../club/volunteerHour/components/edit.vue | 251 +++++++++- .../club/volunteerHour/components/import.vue | 3 + src/views/club/volunteerHour/index.vue | 102 +++- src/views/club/volunteerReview/index.vue | 269 ++++++++++- .../qmgcStudent/components/aiAnalysis.vue | 444 ++++++++++++++++++ src/views/gxmu/qmgcStudent/index.vue | 74 ++- .../components/reviewFlowConfigEdit.vue | 4 +- .../reviewList/components/moduleDataModal.vue | 20 +- .../components/moduleDetailConfig.ts | 37 ++ .../reviewList/components/reviewListEdit.vue | 18 +- .../gxmu/reviewList/components/search.vue | 4 +- 21 files changed, 1980 insertions(+), 103 deletions(-) create mode 100644 docs/ai-qmgc-student-analysis-contract.md create mode 100644 src/api/ai/qmgcStudentAnalysis/index.ts create mode 100644 src/api/ai/qmgcStudentAnalysis/model/index.ts create mode 100644 src/views/club/report/index.vue create mode 100644 src/views/gxmu/qmgcStudent/components/aiAnalysis.vue diff --git a/docs/ai-qmgc-student-analysis-contract.md b/docs/ai-qmgc-student-analysis-contract.md new file mode 100644 index 0000000..2e77d3b --- /dev/null +++ b/docs/ai-qmgc-student-analysis-contract.md @@ -0,0 +1,68 @@ +# AI 学情分析接口契约(前端已接入,后端待实现) + +青马学员档案里的「AI 学情分析」入口复用现有 AI 流式链路(`utils/ai-stream.ts`), +后端需要新增一个与 `activityOutlineGen` / `manuscriptGen` 同构的端点。 + +## 1. 端点 + +| 用途 | 方法 | 路径 | +| --- | --- | --- | +| 流式分析(页面实际调用) | POST | `/api/ai/qmgcStudentAnalysis/stream` | +| 一次性返回(调试/他端) | POST | `/api/ai/qmgcStudentAnalysis` | + +响应格式与现有 AI 链路一致: + +- `/stream` 返回 `text/event-stream`,每帧一个 `data:` 行,帧内容为 JSON; +- 正文帧:`{ "answer": "" }`,也兼容 `{ "notice": "<片段>" }`; +- 心跳/占位帧:`{ "loading": "true" }`,前端忽略; +- 结束帧:`{ "answer": "__END__" }`; +- 失败时可用 `{ "answer": "<片段>", "reset": "true" }` 覆盖已推内容, + 末帧 `status: "failed"`,前端会显示统一失败文案。 + +## 2. 入参 + +```json +{ + "studentId": 123, + "focus": "thought | difficulty | advice | undefined", + "context": { + "studentName": "张三", + "studentNo": "20230001", + "collegeName": "第一临床医学院", + "className": "临床医学 2023 级 1 班", + "batch": "2026 年第 1 期", + "stage": "study", + "totalScore": 86, + "materialCount": 5, + "scoredMaterialCount": 4, + "taskDoneCount": 6, + "taskTotal": 8, + "materialTexts": ["【学习心得】……"], + "taskTexts": ["完成线上团课(已完成)"], + "scoreTexts": ["理论/课堂表现:90 分"] + } +} +``` + +`context` 是前端已加载数据的摘要,后端实现了按 `studentId` 自行取数后可以忽略它; +保留它是为了后端聚合尚未覆盖某类数据时,AI 仍能拿到培养材料正文。 + +`focus` 为空表示综合维度;调度到 Dify 时可映射成不同的提示词变量。 + +## 3. 数据口径 + +第一期可用的输入只有青马域已有数据: + +- `gxmu_qmgc_material`:培养材料标题与正文、评分与评语; +- 学员任务分配记录:任务标题、完成状态; +- `gxmu_qmgc_score`:考核维度、评分项、得分与评语。 + +**系统目前没有考勤、签到、课堂互动类数据。** 需求原文提到的「课堂互动数据」 +在采集入口落地前不参与分析,前端弹窗里已明确向教师提示这一点。后续若新增采集, +在 `context` 中补充对应摘要字段即可,无需改动流式协议。 + +## 4. 历史记录 + +如果要把分析结果写入 `ai_chat_list`,`chat_type` 需要新增一个取值 +(如 `student_analysis`),并同步前端 `utils/ai-chat-type.ts` 的类型与中文名; +本次前端改动未接入历史记录,属于可选增强。 diff --git a/docs/phase2-P0-delivery.md b/docs/phase2-P0-delivery.md index 5e0c2f8..2d3e514 100644 --- a/docs/phase2-P0-delivery.md +++ b/docs/phase2-P0-delivery.md @@ -137,7 +137,9 @@ src/views/system/{messageTemplate,messageLog}/ | 项 | 说明 | | --- | --- | -| 志愿证明 PDF | 当前只做数据汇总与提示,模板渲染与电子章在 P1 接入(需学校提供模板与印章图) | +| 志愿证明 PDF | **已实现**:按年度汇总已通过审核的志愿时长,生成含志愿者姓名、学院、专业、班级、学号、活动名称、活动时间、活动地点、时长、数据来源、总时长的 PDF,自动加水印,不设电子章(正文留公章位)。接口 `/api/gxmu/volunteer/certificate/*` | +| 社团年度数据报告 | **已实现**:按年度汇总活动、成员、场地数据,生成含核心指标、类别分布、月度趋势、成员构成图表与文字小结的 PDF。入口「社团与志愿 → 年度数据报告」(`/club/report`),接口 `/api/gxmu/club/report/*`。文字小结可按需编辑后再生成 | +| 志愿记录服务地点 | 新增 `gxmu_volunteer_record.service_place`,录入表单与导入模板均支持;证明材料「活动地点」列取此值 | | OCR 公章识别 | 字段已预留(`ocr_status` / `ocr_org` / `ocr_raw`),未接 OCR 服务;活动审批暂由人工核验材料 | | 审核流并行 | **已实现**,见下方「补充交付」 | | 学分自动获取 | 第二课堂/桂志愿暂无 API,先走 Excel 导入,`source` + `external_id` 已为后续对接预留 | diff --git a/docs/phase2-implementation-plan.md b/docs/phase2-implementation-plan.md index 8354943..590b202 100644 --- a/docs/phase2-implementation-plan.md +++ b/docs/phase2-implementation-plan.md @@ -84,6 +84,10 @@ AI 学员学习情况分析、AI 定制培养建议、OCR 公章识别、AI 数字人、移动端接口整理。 +> AI 学员学习情况分析的前端入口已落在青马学员档案(列表操作列「AI 分析」+ 档案抽屉按钮), +> 复用 `utils/ai-stream.ts` 流式链路,接口契约见 `docs/ai-qmgc-student-analysis-contract.md`。 +> 归入 AI 能力域、入口留在业务域,不作为独立 AI 菜单页。 + ## 四、数据模型 统一规范:`id BIGINT AUTO_INCREMENT`、`tenant_id`、`user_id`、`deleted`、`create_time`、`update_time`。 diff --git a/src/api/ai/qmgcStudentAnalysis/index.ts b/src/api/ai/qmgcStudentAnalysis/index.ts new file mode 100644 index 0000000..dced02c --- /dev/null +++ b/src/api/ai/qmgcStudentAnalysis/index.ts @@ -0,0 +1,24 @@ +import request from '@/utils/request'; +import type { ApiResult } from '@/api'; +import { MODULES_API_URL } from '@/config/setting'; +import type { + QmgcStudentAnalysisParams, + QmgcStudentAnalysisResponse +} from './model'; + +/** + * 青马学员学情分析(非流式)。 + * + * 页面实际走 `/stream`(见 `@/utils/ai-stream`),这里保留普通 POST 便于 + * 其他端或调试直接取完整结果。 + */ +export async function qmgcStudentAnalysis(data: QmgcStudentAnalysisParams) { + const res = await request.post>( + MODULES_API_URL + '/ai/qmgcStudentAnalysis', + data + ); + if (res.data.code === 0) { + return res.data.data; + } + return Promise.reject(new Error(res.data.message || '分析失败')); +} diff --git a/src/api/ai/qmgcStudentAnalysis/model/index.ts b/src/api/ai/qmgcStudentAnalysis/model/index.ts new file mode 100644 index 0000000..e9aa201 --- /dev/null +++ b/src/api/ai/qmgcStudentAnalysis/model/index.ts @@ -0,0 +1,21 @@ +/** 青马学员学情分析入参 */ +export interface QmgcStudentAnalysisParams { + /** 青马学员档案 id */ + studentId: number; + /** 分析侧重点,为空时按「思想动态 + 学习难点」通用维度分析 */ + focus?: string; +} + +/** 青马学员学情分析结果 */ +export interface QmgcStudentAnalysisResponse { + answer?: string; + /** + * 上游工作流状态。失败时为 `'failed'`,前端据此区分失败文案与正文 + * (见 `@/utils/ai-result`)。 + */ + status?: string; + conversationId?: string; + messageId?: string; + raw?: Record; + [key: string]: any; +} diff --git a/src/api/growth/club/index.ts b/src/api/growth/club/index.ts index 4214c57..49ca105 100644 --- a/src/api/growth/club/index.ts +++ b/src/api/growth/club/index.ts @@ -81,6 +81,62 @@ export interface ClubActivityParam extends PageParam { startTimeEnd?: string; } +/** 社团年度数据报告 */ +export interface ClubReport { + id?: number; + clubId?: number; + clubName?: string; + year?: number; + summary?: string; + narrative?: string; + fileId?: number; + fileUrl?: string; + downloadUrl?: string; + status?: number; + createTime?: string; + updateTime?: string; +} + +/** 社团年度报告数据预览 */ +export async function getClubReportPreview(params: { clubId: number; year?: number }) { + const res = await request.get>>( + MODULES_API_URL + '/gxmu/club/report/preview', + { params } + ); + if (res.data.code === 0) { + return res.data.data; + } + return Promise.reject(new Error(res.data.message)); +} + +/** 生成社团年度数据报告 PDF */ +export async function generateClubReport(data: { + clubId?: number; + year: number; + narrative?: string; +}) { + const res = await request.post>( + MODULES_API_URL + '/gxmu/club/report/generate', + data + ); + if (res.data.code === 0 && res.data.data) { + return res.data.data; + } + return Promise.reject(new Error(res.data.message)); +} + +/** 分页查询社团年度报告 */ +export async function pageClubReports(params?: Record) { + const res = await request.get>>( + MODULES_API_URL + '/gxmu/club/report/page', + { params } + ); + if (res.data.code === 0 && res.data.data) { + return res.data.data; + } + return Promise.reject(new Error(res.data.message)); +} + /** 场地 */ export interface Venue { id?: number; diff --git a/src/api/growth/volunteer/index.ts b/src/api/growth/volunteer/index.ts index 270adb8..7a306be 100644 --- a/src/api/growth/volunteer/index.ts +++ b/src/api/growth/volunteer/index.ts @@ -15,11 +15,19 @@ export interface VolunteerRecord { externalId?: string; projectName?: string; orgName?: string; + servicePlace?: string; hours?: number; serviceDate?: string; serviceEndDate?: string; content?: string; proofFiles?: string; + ocrStatus?: number; + ocrOrg?: string; + ocrProjectName?: string; + ocrPlace?: string; + ocrHours?: number; + ocrHasSeal?: number; + ocrRaw?: string; status?: number; reviewRemark?: string; reviewerId?: number; @@ -53,6 +61,17 @@ export async function pageVolunteerRecords(params: VolunteerRecordParam) { return Promise.reject(new Error(res.data.message)); } +/** 按 id 查询志愿时长详情 */ +export async function getVolunteerRecord(id: number) { + const res = await request.get>( + MODULES_API_URL + '/gxmu/volunteer/' + id + ); + if (res.data.code === 0 && res.data.data) { + return res.data.data; + } + return Promise.reject(new Error(res.data.message)); +} + /** 个人录入志愿时长 */ export async function submitVolunteerRecord(data: VolunteerRecord) { const res = await request.post>( @@ -134,3 +153,124 @@ export async function getMyVolunteerProjectStats(year?: number) { } return Promise.reject(new Error(res.data.message)); } + +/** 已生成的年度志愿服务证明 */ +export interface VolunteerCertificate { + id?: number; + userId?: number; + studentNo?: string; + studentName?: string; + year?: number; + totalHours?: number; + serialNo?: string; + fileId?: number; + fileUrl?: string; + downloadUrl?: string; + status?: number; + createTime?: string; + updateTime?: string; +} + +/** 年度志愿服务证明数据核对 */ +export async function getVolunteerCertificateSummary(params: { + year?: number; + studentNo?: string; +}) { + const res = await request.get>>( + MODULES_API_URL + '/gxmu/volunteer/certificate/summary', + { params } + ); + if (res.data.code === 0) { + return res.data.data; + } + return Promise.reject(new Error(res.data.message)); +} + +/** 生成年度志愿服务证明 PDF */ +export async function generateVolunteerCertificate(data: { + year: number; + studentNo?: string; +}) { + const res = await request.post>( + MODULES_API_URL + '/gxmu/volunteer/certificate/generate', + data + ); + if (res.data.code === 0 && res.data.data) { + return res.data.data; + } + return Promise.reject(new Error(res.data.message)); +} + +/** 分页查询已生成的志愿证明 */ +export async function pageVolunteerCertificates(params: Record) { + const res = await request.get>>( + MODULES_API_URL + '/gxmu/volunteer/certificate/page', + { params } + ); + if (res.data.code === 0 && res.data.data) { + return res.data.data; + } + return Promise.reject(new Error(res.data.message)); +} + +/** 我的年度志愿服务证明列表 */ +export async function getMyVolunteerCertificates() { + const res = await request.get>( + MODULES_API_URL + '/gxmu/volunteer/certificate/my' + ); + if (res.data.code === 0 && res.data.data) { + return res.data.data; + } + return Promise.reject(new Error(res.data.message)); +} + +/** 审核进度节点 */ +export interface VolunteerReviewNode { + id?: number; + nodeName?: string; + reviewerId?: number; + reviewerName?: string; + status?: number; + content?: string; + parallelGroup?: number; + passMode?: string; + createTime?: string; + updateTime?: string; +} + +/** 志愿记录审核进度 */ +export async function getVolunteerProgress(id: number) { + const res = await request.get>>( + MODULES_API_URL + '/gxmu/volunteer/' + id + '/progress' + ); + if (res.data.code === 0 && res.data.data) { + return res.data.data; + } + return Promise.reject(new Error(res.data.message)); +} + +/** 撤回本人待审核的志愿时长 */ +export async function withdrawVolunteerRecord(id: number) { + const res = await request.put>( + MODULES_API_URL + '/gxmu/volunteer/' + id + '/withdraw' + ); + if (res.data.code === 0) { + return res.data.message; + } + return Promise.reject(new Error(res.data.message)); +} + +/** 识别证明材料并回填关键信息 */ +export async function recognizeVolunteerProof(data: { + fileUrl: string; + fileName?: string; +}) { + const res = await request.post>>( + MODULES_API_URL + '/gxmu/volunteer/proof/recognize', + data + ); + if (res.data.code === 0 && res.data.data) { + return res.data.data; + } + return Promise.reject(new Error(res.data.message)); +} diff --git a/src/utils/ai-stream.ts b/src/utils/ai-stream.ts index 7edc60f..5b5c0e3 100644 --- a/src/utils/ai-stream.ts +++ b/src/utils/ai-stream.ts @@ -134,6 +134,20 @@ export function streamActivityOutlineGen( }); } +/** 青马学员学情分析(HTTP 流式) */ +export function streamQmgcStudentAnalysis( + data: Record, + onMessage: (raw: string) => void, + signal?: AbortSignal +): Promise { + return streamAiRequest({ + path: '/ai/qmgcStudentAnalysis/stream', + data, + onMessage, + signal + }); +} + /** 创意助手(HTTP 流式) */ export function streamCreativeAssistant( data: Record, diff --git a/src/views/club/report/index.vue b/src/views/club/report/index.vue new file mode 100644 index 0000000..bb35fe2 --- /dev/null +++ b/src/views/club/report/index.vue @@ -0,0 +1,350 @@ + + + + + diff --git a/src/views/club/volunteerCertificate/index.vue b/src/views/club/volunteerCertificate/index.vue index 645a604..ef5fbbf 100644 --- a/src/views/club/volunteerCertificate/index.vue +++ b/src/views/club/volunteerCertificate/index.vue @@ -6,7 +6,7 @@ type="info" show-icon style="margin-bottom: 16px" - message="年度志愿服务证明按「已通过审核」的志愿时长自动汇总生成。证书编号由后端按年度顺序生成,模板与电子章在 P1 阶段接入。" + message="年度志愿服务证明按「已通过审核」的志愿时长自动汇总生成,下载为 PDF。证书编号由系统按年度生成,加盖公章后有效。" /> @@ -20,16 +20,55 @@ v-model:value="studentNo" allow-clear style="width: 180px" - placeholder="学号(管理员)" + placeholder="学号(管理员代查)" + @pressEnter="loadData" /> - 查询 - - 生成证明 + 查询 + + {{ summary.generated ? '重新生成' : '生成证明' }} + + + 下载 PDF - + + {{ + summary.studentName || '-' + }} + {{ + summary.studentNo || '-' + }} + {{ + summary.collegeName || '-' + }} + {{ + summary.major || '-' + }} + {{ + summary.className || '-' + }} + {{ + summary.serialNo || '未生成' + }} + + +
年度累计时长
@@ -40,7 +79,7 @@
志愿项目数
-
{{ projects.length }}
+
{{ summary.projectCount ?? 0 }}
按项目名称去重
@@ -48,18 +87,21 @@
记录条数
{{ summary.approvedCount ?? 0 }}
-
已通过的志愿记录
+
该年度已通过的志愿记录
- - + + @@ -76,9 +118,8 @@ import { message } from 'ant-design-vue'; import type { ColumnItem } from 'ele-admin-pro/es/ele-pro-table/types'; import { - getMyVolunteerSummary, - getMyVolunteerProjectStats, - getMyVolunteerTotal + getVolunteerCertificateSummary, + generateVolunteerCertificate } from '@/api/growth/volunteer'; const loading = ref(false); @@ -86,7 +127,6 @@ const year = ref(dayjs().year()); const studentNo = ref(''); const summary = ref>({}); - const projects = ref[]>([]); const yearOptions = computed(() => { const current = dayjs().year(); @@ -97,44 +137,112 @@ }); const projectColumns: ColumnItem[] = [ - { title: '志愿项目', dataIndex: 'projectName', key: 'projectName' }, - { title: '组织单位', dataIndex: 'orgName', key: 'orgName', width: 260 }, - { title: '时长', dataIndex: 'hours', key: 'hours', width: 100, align: 'center' }, - { title: '记录数', dataIndex: 'count', key: 'count', width: 100, align: 'center' } + { + title: '志愿项目', + dataIndex: 'projectName', + key: 'projectName', + ellipsis: true + }, + { + title: '活动时间', + dataIndex: 'serviceDate', + key: 'serviceDate', + width: 180 + }, + { + title: '服务地点', + dataIndex: 'servicePlace', + key: 'servicePlace', + width: 200, + ellipsis: true + }, + { + title: '组织单位', + dataIndex: 'orgName', + key: 'orgName', + width: 200, + ellipsis: true + }, + { + title: '时长', + dataIndex: 'hours', + key: 'hours', + width: 90, + align: 'center' + }, + { + title: '数据来源', + dataIndex: 'source', + key: 'source', + width: 110, + align: 'center', + customRender: ({ text }) => sourceLabel(text) + } ]; + const sourceLabel = (value?: string) => { + const map: Record = { + self: '个人录入', + gz: '桂志愿', + import: '导入' + }; + return map[value ?? ''] ?? value ?? '-'; + }; + const loadData = async () => { loading.value = true; try { - const [summaryData, projectData, total] = await Promise.all([ - getMyVolunteerSummary(), - getMyVolunteerProjectStats(year.value), - getMyVolunteerTotal(year.value) - ]); - summary.value = { ...summaryData, totalHours: total ?? 0 }; - projects.value = projectData || []; + summary.value = await getVolunteerCertificateSummary({ + year: year.value, + studentNo: studentNo.value || undefined + }); } catch (e: any) { + summary.value = {}; message.error(e?.message || '加载志愿数据失败'); } finally { loading.value = false; } }; - const generate = () => { + const generate = async () => { generating.value = true; - // 证明文件由后端模板渲染,P0 先给出数据核对结果,文件下载在模板接入后开放 - setTimeout(() => { + try { + const certificate = await generateVolunteerCertificate({ + year: year.value, + studentNo: studentNo.value || undefined + }); + message.success('证明已生成,可下载 PDF'); + summary.value = { + ...summary.value, + generated: true, + serialNo: certificate.serialNo, + downloadUrl: certificate.downloadUrl || certificate.fileUrl, + generatedAt: certificate.createTime + }; + } catch (e: any) { + message.error(e?.message || '生成证明失败'); + } finally { generating.value = false; - message.info( - `已汇总 ${year.value} 年 ${summary.value.totalHours ?? 0} 小时志愿时长,证明材料模板接入后可在此下载 PDF` - ); - }, 300); + } + }; + + const download = () => { + const url = summary.value.downloadUrl; + if (!url) { + message.warning('暂无可下载的证明文件,请先生成'); + return; + } + window.open(url, '_blank'); }; onMounted(loadData); diff --git a/src/views/club/volunteerHour/components/import.vue b/src/views/club/volunteerHour/components/import.vue index 47c3e08..7b0350c 100644 --- a/src/views/club/volunteerHour/components/import.vue +++ b/src/views/club/volunteerHour/components/import.vue @@ -35,6 +35,7 @@ { header: '姓名', key: 'studentName', required: true }, { header: '志愿项目', key: 'projectName', required: true }, { header: '组织单位', key: 'orgName' }, + { header: '服务地点', key: 'servicePlace' }, { header: '服务时长', key: 'hours', required: true }, { header: '服务日期', key: 'serviceDate', required: true }, { header: '外部编号', key: 'externalId' } @@ -45,6 +46,7 @@ 姓名: '张三', 志愿项目: '社区义诊志愿服务', 组织单位: '广西医科大学青年志愿者协会', + 服务地点: '南宁市青秀区社区服务中心', 服务时长: '4', 服务日期: '2025-10-12', 外部编号: 'gzy-2025-0001' @@ -64,6 +66,7 @@ studentName: String(row['姓名'] || '').trim(), projectName: String(row['志愿项目'] || '').trim(), orgName: String(row['组织单位'] || '').trim() || undefined, + servicePlace: String(row['服务地点'] || '').trim() || undefined, hours: Number(row['服务时长'] || 0), serviceDate: normalizeDate(row['服务日期']), externalId: String(row['外部编号'] || '').trim() || undefined, diff --git a/src/views/club/volunteerHour/index.vue b/src/views/club/volunteerHour/index.vue index 409414d..cfb5152 100644 --- a/src/views/club/volunteerHour/index.vue +++ b/src/views/club/volunteerHour/index.vue @@ -16,7 +16,9 @@
待审核 - {{ summary.pendingCount ?? 0 }} + {{ + summary.pendingCount ?? 0 + }} 条
@@ -25,7 +27,10 @@ 录入时长 - + 导入桂志愿数据 刷新 @@ -37,7 +42,7 @@ row-key="id" :columns="columns" :datasource="datasource" - :scroll="{ x: 1200 }" + :scroll="{ x: 1360 }" tool-class="ele-toolbar-form" >