From d5cffd2a763e3bcfee63acaa590017c875c72965 Mon Sep 17 00:00:00 2001 From: zhouwentao Date: Sun, 1 Feb 2026 13:48:16 +0800 Subject: [PATCH] updates --- docs/user_volunteer_controller_api_doc.md | 515 ++++++++++++++++++++++ src/pages/simulate.vue | 238 ++++++++-- src/service/api/volunteer.ts | 53 ++- src/service/request/index.ts | 8 + src/utils/dict.ts | 16 +- src/utils/message.ts | 2 +- 6 files changed, 778 insertions(+), 54 deletions(-) create mode 100644 docs/user_volunteer_controller_api_doc.md diff --git a/docs/user_volunteer_controller_api_doc.md b/docs/user_volunteer_controller_api_doc.md new file mode 100644 index 0000000..b026258 --- /dev/null +++ b/docs/user_volunteer_controller_api_doc.md @@ -0,0 +1,515 @@ +# 用户志愿控制器 API 接口文档 + +## 概述 + +用户志愿控制器 (UserVolunteerController) 提供了志愿管理的相关接口,包括志愿保存、详情查询、名称修改、列表查询、删除和切换功能。 + +**基础路径**: `/api/user/volunteer` + +**认证方式**: 需要登录认证,通过 JWT Token 进行身份验证 + +--- + +## 1. 保存志愿明细 + +保存用户选择的志愿专业列表到当前激活的志愿表中。 + +**请求** + +- **方法**: `POST` +- **路径**: `/api/user/volunteer/save` +- **Content-Type**: `application/json` + +**请求参数** + +| 参数名 | 类型 | 必填 | 说明 | +|--------|--------|------|----------------------------------------| +| keys | string[] | 是 | 志愿专业Key列表,格式:`学校代码_专业代码_招生代码` | + +**请求示例** + +```json +[ + "10001_1001_001", + "10002_2001_002", + "10003_3001_003" +] +``` + +**响应** + +| 字段名 | 类型 | 说明 | +|----------|--------|----------| +| code | int | 状态码 | +| message | string | 响应消息 | +| data | string | 响应数据 | + +**成功响应示例** + +```json +{ + "code": 200, + "message": "success", + "data": "保存成功" +} +``` + +**错误响应示例** + +```json +{ + "code": 500, + "message": "未找到计算表名", + "data": null +} +``` + +**错误码说明** + +| 错误码 | 说明 | +|--------|----------------------------| +| 500 | 获取用户成绩信息失败 | +| 500 | 未找到计算表名 | +| 500 | 查找志愿表失败 | +| 500 | 请先创建志愿表 | +| 500 | 查找专业信息失败 | +| 500 | 删除旧数据失败 | +| 500 | 保存失败 | + +**业务逻辑** + +1. 对传入的keys进行去重处理 +2. 获取当前登录用户的激活成绩信息 +3. 查找当前激活的志愿表 +4. 根据keys查找对应的专业信息 +5. 构建志愿记录列表,保持提交顺序 +6. 先删除旧的志愿记录,再批量插入新记录 + +--- + +## 2. 获取当前志愿单详情 + +获取当前激活志愿单的详细信息,包括志愿记录按批次分组展示。 + +**请求** + +- **方法**: `GET` +- **路径**: `/api/user/volunteer/detail` + +**请求参数** + +无 + +**响应** + +| 字段名 | 类型 | 说明 | +|----------|--------|--------------------| +| code | int | 状态码 | +| message | string | 响应消息 | +| data | object | 志愿详情数据 | + +**data 数据结构** + +| 字段名 | 类型 | 说明 | +|------------|--------|--------------------------| +| volunteer | object | 志愿单基本信息 | +| items | object | 按批次分组的志愿明细 | + +**items 数据结构** + +| 批次 | 类型 | 说明 | +|--------|------|------| +| 提前批 | array | 提前批志愿记录列表 | +| 本科批 | array | 本科批志愿记录列表 | +| 专科批 | array | 专科批志愿记录列表 | + +**志愿记录项数据结构** + +| 字段名 | 类型 | 说明 | +|------------------------|---------|--------------------------------| +| volunteerID | string | 志愿单ID | +| schoolCode | string | 学校代码 | +| majorCode | string | 专业代码 | +| enrollmentCode | string | 招生代码 | +| indexs | int | 志愿序号 | +| batch | string | 批次 | +| enrollProbability | float64 | 录取概率 | +| studentConvertedScore | float64 | 学生折合分 | +| calculationMajorID | string | 计算专业ID | +| schoolName | string | 学校名称 | +| majorName | string | 专业名称 | +| planNum | int | 计划人数 | +| tuition | string | 学费 | +| province | string | 省份 | +| schoolNature | string | 院校性质 | +| institutionType | string | 院校类型 | +| majorDetail | string | 专业详情 | + +**成功响应示例** + +```json +{ + "code": 200, + "message": "success", + "data": { + "volunteer": { + "id": "123456789", + "volunteerName": "我的志愿表", + "scoreId": "987654321", + "createType": 1, + "state": 1 + }, + "items": { + "提前批": [ + { + "volunteerID": "123456789", + "schoolCode": "10001", + "majorCode": "1001", + "enrollmentCode": "001", + "indexs": 1, + "batch": "本科提前批", + "schoolName": "某某大学", + "majorName": "美术学", + "planNum": 30, + "tuition": "8000元/年", + "enrollProbability": 85.5 + } + ], + "本科批": [], + "专科批": [] + } + } +} +``` + +**错误码说明** + +| 错误码 | 说明 | +|--------|------------------------| +| 500 | 获取用户成绩信息失败 | +| 500 | 查找志愿表失败 | +| 500 | 查找志愿明细失败 | + +**批次分类规则** + +- **提前批**: `提前批`、`本科提前批` +- **专科批**: `高职高专`、`专科批` +- **本科批**: 其他所有批次 + +--- + +## 3. 编辑志愿单名称 + +修改志愿单的名称。 + +**请求** + +- **方法**: `PUT` +- **路径**: `/api/user/volunteer/updateName` + +**请求参数** + +| 参数名 | 类型 | 必填 | 位置 | 说明 | +|--------|--------|------|--------|------------| +| id | string | 是 | query | 志愿单ID | +| name | string | 是 | query | 志愿单名称 | + +**请求示例** + +``` +PUT /api/user/volunteer/updateName?id=123456789&name=我的新志愿表 +``` + +**响应** + +| 字段名 | 类型 | 说明 | +|----------|--------|----------| +| code | int | 状态码 | +| message | string | 响应消息 | +| data | string | 响应数据 | + +**成功响应示例** + +```json +{ + "code": 200, + "message": "success", + "data": "更新成功" +} +``` + +**错误响应示例** + +```json +{ + "code": 400, + "message": "参数错误", + "data": null +} +``` + +**错误码说明** + +| 错误码 | 说明 | +|--------|----------------| +| 400 | 参数错误 | +| 500 | 更新失败 | + +--- + +## 4. 获取当前用户志愿单列表 + +分页查询当前用户的志愿单列表。 + +**请求** + +- **方法**: `GET` +- **路径**: `/api/user/volunteer/list` + +**请求参数** + +| 参数名 | 类型 | 必填 | 位置 | 默认值 | 说明 | +|--------|------|------|-------|--------|--------| +| page | int | 否 | query | 1 | 页码 | +| size | int | 否 | query | 10 | 每页数量 | + +**请求示例** + +``` +GET /api/user/volunteer/list?page=1&size=10 +``` + +**响应** + +| 字段名 | 类型 | 说明 | +|----------|--------|----------| +| code | int | 状态码 | +| message | string | 响应消息 | +| data | object | 响应数据 | + +**data 数据结构** + +| 字段名 | 类型 | 说明 | +|--------|--------|--------------| +| items | array | 志愿单列表 | +| total | int64 | 总数量 | + +**志愿单数据结构** + +| 字段名 | 类型 | 说明 | +|----------------|---------|------------------------------| +| id | string | 志愿单ID | +| volunteerName | string | 志愿单名称 | +| scoreId | string | 关联成绩ID | +| createType | int | 生成类型 (1.手动, 2.智能) | +| state | string | 状态 (0.未使用, 1.使用中, 2.历史) | +| createBy | string | 创建人 | +| createTime | string | 创建时间 | +| updateBy | string | 更新人 | +| updateTime | string | 更新时间 | + +**成功响应示例** + +```json +{ + "code": 200, + "message": "success", + "data": { + "items": [ + { + "id": "123456789", + "volunteerName": "我的志愿表", + "scoreId": "987654321", + "createType": 1, + "state": "1", + "createBy": "user001", + "createTime": "2026-01-31T10:00:00Z" + } + ], + "total": 1 + } +} +``` + +**错误码说明** + +| 错误码 | 说明 | +|--------|----------------| +| 500 | 查询失败 | + +--- + +## 5. 删除志愿单 + +删除指定的志愿单。 + +**请求** + +- **方法**: `DELETE` +- **路径**: `/api/user/volunteer/delete` + +**请求参数** + +| 参数名 | 类型 | 必填 | 位置 | 说明 | +|--------|--------|------|-------|----------| +| id | string | 是 | query | 志愿单ID | + +**请求示例** + +``` +DELETE /api/user/volunteer/delete?id=123456789 +``` + +**响应** + +| 字段名 | 类型 | 说明 | +|----------|--------|----------| +| code | int | 状态码 | +| message | string | 响应消息 | +| data | string | 响应数据 | + +**成功响应示例** + +```json +{ + "code": 200, + "message": "success", + "data": "删除成功" +} +``` + +**错误响应示例** + +```json +{ + "code": 400, + "message": "参数错误", + "data": null +} +``` + +**错误码说明** + +| 错误码 | 说明 | +|--------|----------------| +| 400 | 参数错误 | +| 500 | 删除失败 | + +--- + +## 6. 切换当前志愿单 + +切换当前激活的志愿单。 + +**请求** + +- **方法**: `POST` +- **路径**: `/api/user/volunteer/switch` + +**请求参数** + +| 参数名 | 类型 | 必填 | 位置 | 说明 | +|--------|--------|------|-------|----------| +| id | string | 是 | query | 志愿单ID | + +**请求示例** + +``` +POST /api/user/volunteer/switch?id=123456789 +``` + +**响应** + +| 字段名 | 类型 | 说明 | +|----------|--------|----------| +| code | int | 状态码 | +| message | string | 响应消息 | +| data | string | 响应数据 | + +**成功响应示例** + +```json +{ + "code": 200, + "message": "success", + "data": "切换成功" +} +``` + +**特殊响应示例(已是当前志愿单)** + +```json +{ + "code": 200, + "message": "success", + "data": "已是当前志愿单,忽略切换" +} +``` + +**错误码说明** + +| 错误码 | 说明 | +|--------|----------------------------| +| 400 | 参数错误 | +| 500 | 获取用户成绩信息失败 | +| 500 | 切换失败 | + +**业务逻辑** + +1. 检查当前是否已是该志愿单,如果是则忽略切换 +2. 执行志愿单切换操作 +3. Redis 缓存同步(由 Service 层处理) + +--- + +## 通用响应格式说明 + +### 成功响应 + +```json +{ + "code": 200, + "message": "success", + "data": {} +} +``` + +### 错误响应 + +```json +{ + "code": 错误码, + "message": "错误信息", + "data": null +} +``` + +### 常见错误码 + +| 错误码 | 说明 | +|--------|--------------------| +| 200 | 成功 | +| 400 | 请求参数错误 | +| 500 | 服务器内部错误 | + +--- + +## 认证说明 + +所有接口都需要在请求头中携带 JWT Token: + +``` +Authorization: Bearer {token} +``` + +Token 从登录接口获取,有效期24小时。 + +--- + +## 注意事项 + +1. **保存志愿明细**: 每次保存会先删除当前志愿表中的所有记录,再插入新记录 +2. **批次分类**: 志愿详情按 提前批、本科批、专科批 三大类分组展示 +3. **数据去重**: 保存志愿时会自动去重,避免重复添加相同专业 +4. **顺序保持**: 志愿序号按照提交的 keys 顺序依次递增 +5. **权限控制**: 所有操作都基于当前登录用户的身份,只能操作自己的志愿数据 \ No newline at end of file diff --git a/src/pages/simulate.vue b/src/pages/simulate.vue index e7cde86..4a83b16 100644 --- a/src/pages/simulate.vue +++ b/src/pages/simulate.vue @@ -3,7 +3,7 @@ import type { FilterState } from '~/components/FilterBar.vue' import { onMounted, ref, watch, computed } from 'vue' import { onBeforeRouteLeave } from 'vue-router' import { getUserMajorList, type MajorItem } from '~/service/api/major' -import { saveVolunteer, getVolunteerDetail, type VolunteerItem, type VolunteerInfo } from '~/service/api/volunteer' +import { saveVolunteer, getVolunteerDetail, updateVolunteerName, getVolunteerList, deleteVolunteer, switchVolunteer, type VolunteerItem, type VolunteerInfo, type VolunteerPlanItem } from '~/service/api/volunteer' // --- 类型定义 --- type TabKey = 'all' | 'hard' | 'risky' | 'safe' | 'stable' | '本科' | '专科' | '985/211/双一流' | '公办本科' | '民办本科' @@ -32,34 +32,12 @@ interface MajorDetail { // --- 状态数据 --- const showSwitchModal = ref(false) // 控制切换方案弹窗显示 -const activePlanId = ref('2025121426') // 当前选中的方案ID +const showEditNameModal = ref(false) // 控制编辑名称弹窗显示 +const editingPlanName = ref('') // 正在编辑的志愿单名称 +const isUpdatingName = ref(false) // 更新名称Loading状态 +const activePlanId = ref('') // 当前选中的方案ID // 模拟方案列表数据 (对应截图) -const volunteerPlans = ref([ - { - id: '2025121426', - name: '志愿2025121426', - tag: '手动', // 截图中的橙色标签 - province: '北京', - artType: '美术与设计类', - cultureScore: 450, - cultureSubjects: '物化生', - artScore: 250, - updateTime: '2025-12-14 10:33:46', - status: '有效', - }, - { - id: '2025121427', - name: '志愿2025121427', - tag: '智能', - province: '湖北', - artType: '音乐类', - cultureScore: 480, - cultureSubjects: '历史', - artScore: 240, - updateTime: '2025-12-13 14:20:00', - status: '有效', - }, -]) +const volunteerPlans = ref([]) const activePanel = ref('market') // 当前激活的面板 const myVolunteers = ref([]) @@ -560,30 +538,133 @@ function handleCreatePlan() { } function handleEditPlan() { - console.warn('点击修改方案信息') - // 逻辑:修改当前方案的分数/选科等... + if (!currentVolunteerInfo.value?.volunteerName) { + // @ts-ignore + window.$message?.warning?.('当前志愿单信息为空') + return + } + editingPlanName.value = currentVolunteerInfo.value.volunteerName + showEditNameModal.value = true } -function handleSwitchPlan() { +/** + * 保存志愿单名称 + */ +async function saveVolunteerName() { + if (!currentVolunteerInfo.value?.id || !editingPlanName.value.trim()) { + // @ts-ignore + window.$message?.warning?.('志愿单名称不能为空') + return + } + + isUpdatingName.value = true + try { + await updateVolunteerName(currentVolunteerInfo.value.id, editingPlanName.value.trim()) + // @ts-ignore + window.$message?.success?.('名称修改成功') + // 更新本地数据 + if (currentVolunteerInfo.value) { + currentVolunteerInfo.value.volunteerName = editingPlanName.value.trim() + } + showEditNameModal.value = false + } catch (error) { + console.error('修改志愿单名称失败:', error) + // @ts-ignore + window.$message?.error?.('修改失败,请重试') + } finally { + isUpdatingName.value = false + } +} + +/** + * 取消编辑名称 + */ +function cancelEditName() { + showEditNameModal.value = false + editingPlanName.value = '' +} + +/** + * 打开切换方案弹框并加载方案列表 + */ +async function handleSwitchPlan() { showSwitchModal.value = true + try { + const res = await getVolunteerList(1, 100) + if (res && res.items) { + // 将 API 返回的数据转换为界面需要的格式 + volunteerPlans.value = res.items.map(item => ({ + id: item.id, + name: item.volunteerName, + tag: item.createType === '1' ? '手动' : '智能', + province: '河南', // API 返回数据中没有这些字段,暂时使用默认值 + artType: '美术与设计类', + culturalScore: item.culturalScore, + professionalScore: item.professionalScore, + cultureSubjects: '-', + updateTime: item.updateTime, + status: item.state === '1' ? '使用中' : (item.state === '0' ? '未使用' : '历史'), + })) + // 更新当前激活的方案ID + if (currentVolunteerInfo.value?.id) { + activePlanId.value = currentVolunteerInfo.value.id + } + } + } catch (error) { + console.error('获取志愿单列表失败:', error) + // @ts-ignore + window.$message?.error?.('获取志愿单列表失败') + } } function handleExportPlan() { console.warn('点击导出当前方案') } -// 切换到指定方案 -function switchActivePlan(planId: string) { - activePlanId.value = planId - showSwitchModal.value = false - console.warn('切换到了方案:', planId) - // 逻辑:重新加载 myVolunteers 数据... - // isLoading.value = true ... +/** + * 切换到指定方案 + */ +async function switchActivePlan(planId: string) { + try { + await switchVolunteer(planId) + activePlanId.value = planId + showSwitchModal.value = false + // @ts-ignore + window.$message?.success?.('切换志愿单成功') + // 重新加载志愿详情 + await fetchVolunteerDetail() + } catch (error) { + console.error('切换志愿单失败:', error) + // @ts-ignore + window.$message?.error?.('切换志愿单失败') + } } -function deletePlan(planId: string) { - console.warn('删除方案:', planId) - // 逻辑:删除API调用... +/** + * 删除志愿单 + */ +async function deletePlan(planId: string) { + const confirmDelete = window.confirm('确定要删除这个志愿单吗?删除后无法恢复。') + if (!confirmDelete) return + + try { + await deleteVolunteer(planId) + // @ts-ignore + window.$message?.success?.('删除成功') + // 从列表中移除 + const index = volunteerPlans.value.findIndex(p => p.id === planId) + if (index > -1) { + volunteerPlans.value.splice(index, 1) + } + // 如果删除的是当前激活的志愿单,重新加载详情 + if (activePlanId.value === planId) { + await fetchVolunteerDetail() + } + } catch (error) { + console.error('删除志愿单失败:', error) + // @ts-ignore + window.$message?.error?.('删除失败') + } } @@ -1478,8 +1559,7 @@ function deletePlan(planId: string) { class="appearance-none border border-slate-300 dark:border-slate-700 rounded bg-white dark:bg-slate-800 py-1.5 pl-3 pr-8 text-sm text-slate-700 dark:text-slate-300 focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500" > - - +
@@ -1540,12 +1620,12 @@ function deletePlan(planId: string) { {{ plan.artType }} - {{ plan.cultureScore }} - {{ plan.cultureSubjects }} + {{ plan.culturalScore }} + {{ - plan.artScore }} + plan.professionalScore }} {{ @@ -1584,6 +1664,72 @@ function deletePlan(planId: string) {
+ + +
+
+ +
+

+ 修改志愿单名称 +

+ +
+ + +
+
+ + +

+ 最多输入 50 个字符 +

+
+
+ + +
+ + +
+
+
diff --git a/src/service/api/volunteer.ts b/src/service/api/volunteer.ts index 77dbda0..ee49c00 100644 --- a/src/service/api/volunteer.ts +++ b/src/service/api/volunteer.ts @@ -28,12 +28,29 @@ export interface VolunteerDetailResponse { items: Record } +export interface VolunteerPlanItem { + id: string + volunteerName: string + scoreId: string + createType: string + state: string + createBy: string + createTime: string + updateBy: string + updateTime: string +} + +export interface VolunteerListResponse { + items: VolunteerPlanItem[] + total: number +} + /** * 保存志愿明细 * @param data schoolCode_majorCode_enrollmentCode 字符串数组 */ export function saveVolunteer(data: string[]) { - return request.post('/user/volunteer/save', data) + return request.post('/user/volunteer/save', {keys:data}) } /** @@ -42,3 +59,37 @@ export function saveVolunteer(data: string[]) { export function getVolunteerDetail() { return request.get('/user/volunteer/detail') } + +/** + * 编辑志愿单名称 + * @param id 志愿单ID + * @param name 志愿单名称 + */ +export function updateVolunteerName(id: string, name: string) { + return request.put('/user/volunteer/updateName', null, { params: { id, name } }) +} + +/** + * 获取当前用户志愿单列表 + * @param page 页码 + * @param size 每页数量 + */ +export function getVolunteerList(page: number = 1, size: number = 10) { + return request.get('/user/volunteer/list', { params: { page, size } }) +} + +/** + * 删除志愿单 + * @param id 志愿单ID + */ +export function deleteVolunteer(id: string) { + return request.delete('/user/volunteer/delete', { params: { id } }) +} + +/** + * 切换当前志愿单 + * @param id 志愿单ID + */ +export function switchVolunteer(id: string) { + return request.post('/user/volunteer/switch', null, { params: { id } }) +} diff --git a/src/service/request/index.ts b/src/service/request/index.ts index 30e59cb..5ecd889 100644 --- a/src/service/request/index.ts +++ b/src/service/request/index.ts @@ -141,6 +141,14 @@ class Request { post(url: string, data?: any, config?: RequestConfig): Promise { return this.instance.post(url, data, config) } + + put(url: string, data?: any, config?: RequestConfig): Promise { + return this.instance.put(url, data, config) + } + + delete(url: string, config?: RequestConfig): Promise { + return this.instance.delete(url, config) + } } export default new Request({ diff --git a/src/utils/dict.ts b/src/utils/dict.ts index 2a4b875..7c4dc70 100644 --- a/src/utils/dict.ts +++ b/src/utils/dict.ts @@ -22,9 +22,13 @@ export interface DictType { const staticDicts: DictType = { // 专业类别 professionalCategory: [ - { label: '理工类', value: 'science', color: '#108ee9' }, - { label: '文史类', value: 'liberal_arts', color: '#2db7f5' }, - { label: '艺术类', value: 'art', color: '#87d068' }, + { label: '美术与设计类', value: 'science', color: '#108ee9' }, + { label: '播音与主持类', value: 'liberal_arts', color: '#2db7f5' }, + { label: '表演类', value: 'art', color: '#87d068' }, + { label: '音乐类', value: 'sports', color: '#ff5500' }, + { label: '舞蹈类', value: 'sports', color: '#ff5500' }, + { label: '书法类', value: 'sports', color: '#ff5500' }, + { label: '戏曲类', value: 'sports', color: '#ff5500' }, { label: '体育类', value: 'sports', color: '#ff5500' }, ], @@ -73,9 +77,9 @@ const staticDicts: DictType = { // 科目列表 subjectList: [ - { label: '语文', value: 'chinese', color: '#108ee9' }, - { label: '数学', value: 'mathematics', color: '#2db7f5' }, - { label: '英语', value: 'english', color: '#87d068' }, + // { label: '语文', value: 'chinese', color: '#108ee9' }, + // { label: '数学', value: 'mathematics', color: '#2db7f5' }, + // { label: '英语', value: 'english', color: '#87d068' }, { label: '物理', value: 'physics', color: '#ff5500' }, { label: '化学', value: 'chemistry', color: '#f5222d' }, { label: '生物', value: 'biology', color: '#fa8c16' }, diff --git a/src/utils/message.ts b/src/utils/message.ts index f92dae4..38beba5 100644 --- a/src/utils/message.ts +++ b/src/utils/message.ts @@ -36,7 +36,7 @@ const Message = (options: MessageOptions) => { if (!messageContainer) { messageContainer = document.createElement('div') // Common classes - let classes = `w-message-container ${containerClass} fixed z-50 flex pointer-events-none transition-all duration-300` + let classes = `w-message-container ${containerClass} fixed z-[9999] flex pointer-events-none transition-all duration-300` // Position specific classes classes += ` ${positionClasses[position]}`