From 6236c7f7235c47d0de934906946201d3a7c5a133 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B5=B5=E5=BF=A0=E6=9E=97?= <170083662@qq.com> Date: Sat, 7 Nov 2026 10:28:08 +0800 Subject: [PATCH] =?UTF-8?q?feat(growth):=20=E5=AE=9E=E7=8E=B0=E5=AD=A6?= =?UTF-8?q?=E7=94=9F=E7=94=BB=E5=83=8F=E5=8A=9F=E8=83=BD=E5=B9=B6=E4=B8=8B?= =?UTF-8?q?=E7=BA=BF=E6=8E=A8=E8=8D=90=E5=86=85=E5=AE=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 替换学生画像页面占位符,新增简历式头部展示 - 集成雷达图展示能力维度评分 - 增加学分类别达成和成长轨迹时间线展示 - 管理端支持学生检索与切换查看画像 - 接口调用区分纯学生角色权限限制 - 删除推荐内容相关前端页面及菜单配置 - 更新文档说明学生画像功能与推荐内容下线情况 - 细节优化标题本地化判断逻辑及状态类型声明 --- docs/phase2-P0-delivery.md | 37 +- docs/phase2-implementation-plan.md | 5 +- src/api/growth/portrait/index.ts | 82 +++ src/layout/index.vue | 7 +- src/layout/menu-icons.ts | 5 +- src/utils/document-title-util.ts | 5 +- src/views/club/volunteerReview/index.vue | 4 +- src/views/growth/portrait/index.vue | 771 ++++++++++++++++++++++- src/views/growth/recommend/index.vue | 52 -- 9 files changed, 871 insertions(+), 97 deletions(-) create mode 100644 src/api/growth/portrait/index.ts delete mode 100644 src/views/growth/recommend/index.vue diff --git a/docs/phase2-P0-delivery.md b/docs/phase2-P0-delivery.md index 2d3e514..36b39e3 100644 --- a/docs/phase2-P0-delivery.md +++ b/docs/phase2-P0-delivery.md @@ -15,9 +15,11 @@ | 7 | 青马培养档案 | 已完成 | `/api/gxmu/qmgc-student` | `/gxmu/qmgcStudent`、`/gxmu/qmgcTask`、`/gxmu/qmgcScore` | | 8 | 主题团课库 | 已完成 | `/api/gxmu/thematic-course` | `/gxmu/thematicCourse` | -P1/P2 的 10 个功能已建菜单与占位页(写明实施依赖),不会出现菜单 404: +P1/P2 的功能已建菜单与占位页(写明实施依赖),不会出现菜单 404: -社会实践学分、学分预警、学生画像、推荐内容、社团年审、社团评优、AI 辅助教学设计、AI 主题团日设计、专家评审、专家库。 +社会实践学分、学分预警、学生画像、社团年审、社团评优、AI 辅助教学设计、AI 主题团日设计、专家评审、专家库。 + +> 推荐内容已按业务反馈下线:前端页面删除,`sys_menu` 中的 14506 与其角色授权由 08 脚本清理。 ## 二、数据库部署 @@ -44,7 +46,7 @@ mysql ... < sql/20261105_phase2_09_dict.sql | 05 | 主题团课库(1 张表) | 无 | | 06 | 站内消息 + 消息模板(2 张表) | 10 条消息模板 | | 07 | 审核流并行节点改造(ALTER 4 处) | 回填存量 parallel_group | -| 08 | 菜单 + 按钮权限 + 角色授权(62 条菜单/按钮) | 超管全量、学生部分 | +| 08 | 菜单 + 按钮权限 + 角色授权(61 条菜单/按钮) | 超管全量、学生部分 | | 09 | 字典(学分类别、社团类别) | 11 条字典项 | **注意**:脚本 07 会修改 `gxmu_review_flow` / `gxmu_review_list` / `gxmu_review_flow_config` 三张生产表,只是新增列并回填,不影响存量串行审核逻辑。 @@ -90,7 +92,7 @@ gxmu: src/api/growth/{credit,volunteer,home,club,qmgc,thematic}/ src/api/system/notice/ src/components/MultiFileUpload/ 统一多附件上传(JSON 数组与后端契约) -src/views/growth/{myHome,credit,practiceCredit,creditWarning,portrait,recommend}/ +src/views/growth/{myHome,credit,practiceCredit,creditWarning,portrait}/ src/views/club/{volunteerHour,volunteerReview,volunteerCertificate,club,activity,activityReview,venue,venueReview,annualReview,award}/ src/views/gxmu/{qmgcStudent,qmgcTask,qmgcScore,thematicCourse,teachingDesign,themeDayDesign,expertReview,expertPool}/ src/views/system/{messageTemplate,messageLog}/ @@ -253,6 +255,31 @@ src/views/system/{messageTemplate,messageLog}/ 初版曾把 `gxmu_practice_credit` 加进 `DeclareGuardServiceImpl` 的模块列表,导致审核回写时按「模块名=表名」去更新不存在的表。已移除——社会实践用独立表 `gxmu_practice_credit_apply`,状态回写由自己的 service 负责。 +### 9.5 学生画像 + +**后端**(`com.gxwebsoft.gxmu.growth`) + +| 文件 | 说明 | +| --- | --- | +| `StudentPortraitController` | `/api/gxmu/portrait/my`、`/api/gxmu/portrait?userId=`、`/api/gxmu/portrait/student/search` | +| `StudentPortraitService` + `Impl` | 六维评分、标签、一句话画像、成长轨迹聚合 | +| `StudentProfile` + Mapper | 每次打开画像写一份快照到 `gxmu_student_profile` | + +**六个维度**:思想引领(政治面貌/团内职务/青马/档案完善度)、学业表现(学分完成率)、 +志愿服务(40 小时折算满分)、社会实践(2 学分折算满分)、组织参与(在社数量与骨干经历)、 +文体拓展(文体/学术科技/技能三类学分达成率取最高)。评分都在服务端算好,前端只负责展示。 + +**权限收口**:菜单仍按原规划只授予管理端(学生角色的 14500–14504 子集不含画像), +接口层保留 `/my` 供后续学生端接入;纯学生角色调用时只能取本人数据,检索接口直接拒绝。 +画像列表不叠加组织范围:学生名册本身对所有管理端角色可见,画像与之一致,且不含审核数据。 + +**前端**:`/growth/portrait`,替换原占位页。简历式头部 + 雷达图 + 学分类别达成 + 成长轨迹时间线, +管理端顶部可检索学生切换查看。 + +**推荐内容下线**:前端目录 `src/views/growth/recommend` 删除;`sys_menu` 14506 与角色授权由 +`sql/20261105_phase2_08_menu_permission.sql` 与新增的 `sql/20261107_portrait_menu.sql` 清理。 + + ## 十、部署排查:部署后看不到新功能 ### 症状 @@ -307,7 +334,7 @@ done ### 执行后的验证 ```sql -SELECT COUNT(*) FROM sys_menu WHERE menu_id BETWEEN 14500 AND 14663; -- 应为 62 +SELECT COUNT(*) FROM sys_menu WHERE menu_id BETWEEN 14500 AND 14663; -- 应为 61(推荐内容 14506 已下线) SELECT COUNT(*) FROM oa_notice_template; -- 应为 10 SELECT COUNT(*) FROM gxmu_graduate_requirement; -- 应为 6 ``` diff --git a/docs/phase2-implementation-plan.md b/docs/phase2-implementation-plan.md index 590b202..0c0c1f3 100644 --- a/docs/phase2-implementation-plan.md +++ b/docs/phase2-implementation-plan.md @@ -32,8 +32,7 @@ ├ 学分明细 /growth/credit 14502 ├ 社会实践学分 /growth/practiceCredit 14503 ├ 学分预警 /growth/creditWarning 14504 -├ 学生画像 /growth/portrait 14505 -└ 推荐内容 /growth/recommend 14506 +└ 学生画像 /growth/portrait 14505 社团与志愿 /club menu_id 14520 ├ 志愿服务时长 /club/volunteerHour 14521 @@ -132,7 +131,7 @@ com.gxwebsoft.notice 消息中心 新增目录: ``` -src/views/growth/{myHome,credit,practiceCredit,creditWarning,portrait,recommend} +src/views/growth/{myHome,credit,practiceCredit,creditWarning,portrait} src/views/club/{volunteerHour,volunteerReview,volunteerCertificate,club,activity,activityReview,venue,venueReview,annualReview,award} src/views/gxmu/{qmgcStudent,qmgcTask,qmgcScore,thematicCourse,teachingDesign,themeDayDesign,expertReview,expertPool} src/views/system/{messageTemplate,messageLog} diff --git a/src/api/growth/portrait/index.ts b/src/api/growth/portrait/index.ts new file mode 100644 index 0000000..fc3da3e --- /dev/null +++ b/src/api/growth/portrait/index.ts @@ -0,0 +1,82 @@ +import request from '@/utils/request'; +import type { ApiResult } from '@/api'; +import { MODULES_API_URL } from '@/config/setting'; +import type { GrowthHome } from '@/api/growth/home'; + +/** 画像维度 */ +export interface PortraitDimension { + key: string; + name: string; + score: number; + desc?: string; +} + +/** 成长轨迹节点 */ +export interface PortraitTimelineItem { + type: string; + typeName: string; + title: string; + time: string; + extra?: string; +} + +/** 学生画像 */ +export interface StudentPortrait { + basic?: GrowthHome['basic']; + dimensions?: PortraitDimension[]; + tags?: string[]; + summary?: string; + credit?: GrowthHome['credit']; + volunteer?: Record; + activity?: Record; + practice?: Record; + qmgc?: Record | null; + timeline?: PortraitTimelineItem[]; + updateTime?: string; +} + +/** 画像可选学生 */ +export interface PortraitStudentOption { + userId: number; + name?: string; + studentNo?: string; + collegeName?: string; + className?: string; + major?: string; + grade?: string; +} + +/** 我的画像 */ +export async function getMyPortrait() { + const res = await request.get>( + MODULES_API_URL + '/gxmu/portrait/my' + ); + if (res.data.code === 0 && res.data.data) { + return res.data.data; + } + return Promise.reject(new Error(res.data.message)); +} + +/** 指定学生的画像 */ +export async function getStudentPortrait(userId: number) { + const res = await request.get>( + MODULES_API_URL + '/gxmu/portrait', + { params: { userId } } + ); + if (res.data.code === 0 && res.data.data) { + return res.data.data; + } + return Promise.reject(new Error(res.data.message)); +} + +/** 检索可查看画像的学生 */ +export async function searchPortraitStudents(keyword?: string) { + const res = await request.get>( + MODULES_API_URL + '/gxmu/portrait/student/search', + { params: { keyword } } + ); + if (res.data.code === 0 && res.data.data) { + return res.data.data; + } + return Promise.reject(new Error(res.data.message)); +} diff --git a/src/layout/index.vue b/src/layout/index.vue index be94f9d..86b6428 100644 --- a/src/layout/index.vue +++ b/src/layout/index.vue @@ -112,7 +112,7 @@ import type { TabCtxMenuOption } from 'ele-admin-pro/es/ele-pro-layout/types'; const { push } = useRouter(); - const { t, locale } = useI18n(); + const { t, te, locale } = useI18n(); const userStore = useUserStore(); const themeStore = useThemeStore(); @@ -255,9 +255,8 @@ return; } const k = 'route.' + key + '._name'; - const title = t(k); - if (title !== k) { - return title; + if (te(k)) { + return t(k); } }; diff --git a/src/layout/menu-icons.ts b/src/layout/menu-icons.ts index b97d090..d38dab5 100644 --- a/src/layout/menu-icons.ts +++ b/src/layout/menu-icons.ts @@ -147,5 +147,8 @@ export { BorderOutlined, PieChartOutlined, AreaChartOutlined, - KeyOutlined + KeyOutlined, + RadarChartOutlined, + RiseOutlined, + SolutionOutlined } from '@ant-design/icons-vue'; diff --git a/src/utils/document-title-util.ts b/src/utils/document-title-util.ts index fc8202e..bf33327 100644 --- a/src/utils/document-title-util.ts +++ b/src/utils/document-title-util.ts @@ -30,7 +30,7 @@ export function setDocumentTitle(title: string) { */ export function useSetDocumentTitle() { const { currentRoute } = useRouter(); - const { t, locale } = useI18n(); + const { t, te, locale } = useI18n(); const themeStore = useThemeStore(); const { tabs } = storeToRefs(themeStore); @@ -50,8 +50,7 @@ export function useSetDocumentTitle() { return; } const k = `route.${pathKey}._name`; - const v = t(k); - setDocumentTitle(v === k || !v ? title : v); + setDocumentTitle(te(k) ? t(k) : title); }; watch( diff --git a/src/views/club/volunteerReview/index.vue b/src/views/club/volunteerReview/index.vue index fbced21..a9eaf9d 100644 --- a/src/views/club/volunteerReview/index.vue +++ b/src/views/club/volunteerReview/index.vue @@ -211,7 +211,7 @@ const tableRef = ref | null>(null); const userStore = useUserStore(); const loginUserId = computed(() => Number(userStore.info?.userId || 0)); - const status = ref(0); + const status = ref(0); const searchText = ref(''); const showReview = ref(false); const current = ref(null); @@ -407,8 +407,6 @@ if (value === 2) return 'red'; return 'orange'; }; - - onMounted(() => undefined); diff --git a/src/views/growth/recommend/index.vue b/src/views/growth/recommend/index.vue deleted file mode 100644 index 0fb6afe..0000000 --- a/src/views/growth/recommend/index.vue +++ /dev/null @@ -1,52 +0,0 @@ - - - - -