feat(user): 支持修改当前用户资料接口并更新个人中心调用
- 新增 updateSelfUser 方法,用于修改当前登录用户的个人资料 - 个人中心调用接口由原 updateUser 切换为 updateSelfUser,避免学生账号权限不足导致的保存失败 - 更新了头像保存、个人信息保存等相关调用代码,添加异常捕获和提示 - 修正环境变量 .env.development 中 API URL 配置为生产地址 - 优化审核记录排序逻辑,新增获取最新审核记录和编辑入口显示判断函数 - 调整审核状态标签使用统一排序规则,防止显示和编辑状态不一致 - 新增社团活动、社团编辑、志愿时长录入、学分记录编辑、社会实践学分申报、青马学员编辑等多个新增组件及表单功能 - 各新增功能支持表单验证、操作反馈和数据加载优化
This commit is contained in:
@@ -0,0 +1,173 @@
|
||||
<template>
|
||||
<div class="page">
|
||||
<div class="ele-body">
|
||||
<a-card :bordered="false" :body-style="{ padding: '16px' }">
|
||||
<a-alert
|
||||
type="info"
|
||||
show-icon
|
||||
style="margin-bottom: 16px"
|
||||
message="年度志愿服务证明按「已通过审核」的志愿时长自动汇总生成。证书编号由后端按年度顺序生成,模板与电子章在 P1 阶段接入。"
|
||||
/>
|
||||
|
||||
<a-space style="margin-bottom: 16px" wrap>
|
||||
<a-select
|
||||
v-model:value="year"
|
||||
style="width: 140px"
|
||||
:options="yearOptions"
|
||||
@change="loadData"
|
||||
/>
|
||||
<a-input
|
||||
v-model:value="studentNo"
|
||||
allow-clear
|
||||
style="width: 180px"
|
||||
placeholder="学号(管理员)"
|
||||
/>
|
||||
<a-button type="primary" :loading="loading" @click="loadData">查询</a-button>
|
||||
<a-button :disabled="!summary.totalHours" :loading="generating" @click="generate">
|
||||
生成证明
|
||||
</a-button>
|
||||
</a-space>
|
||||
|
||||
<a-spin :spinning="loading">
|
||||
<a-row :gutter="[16, 16]">
|
||||
<a-col :xs="24" :sm="8">
|
||||
<div class="metric-card">
|
||||
<div class="metric-label">年度累计时长</div>
|
||||
<div class="metric-value">{{ summary.totalHours ?? 0 }}</div>
|
||||
<div class="metric-foot">仅统计已审核通过的记录</div>
|
||||
</div>
|
||||
</a-col>
|
||||
<a-col :xs="24" :sm="8">
|
||||
<div class="metric-card accent">
|
||||
<div class="metric-label">志愿项目数</div>
|
||||
<div class="metric-value">{{ projects.length }}</div>
|
||||
<div class="metric-foot">按项目名称去重</div>
|
||||
</div>
|
||||
</a-col>
|
||||
<a-col :xs="24" :sm="8">
|
||||
<div class="metric-card warm">
|
||||
<div class="metric-label">记录条数</div>
|
||||
<div class="metric-value">{{ summary.approvedCount ?? 0 }}</div>
|
||||
<div class="metric-foot">已通过的志愿记录</div>
|
||||
</div>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-card :bordered="false" class="project-card" title="项目明细">
|
||||
<a-empty v-if="!projects.length" description="该年度暂无可生成证明的志愿记录" />
|
||||
<a-table
|
||||
v-else
|
||||
row-key="projectName"
|
||||
size="small"
|
||||
:data-source="projects"
|
||||
:columns="projectColumns"
|
||||
:pagination="false"
|
||||
/>
|
||||
</a-card>
|
||||
</a-spin>
|
||||
</a-card>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, onMounted, ref } from 'vue';
|
||||
import dayjs from 'dayjs';
|
||||
import { message } from 'ant-design-vue';
|
||||
import type { ColumnItem } from 'ele-admin-pro/es/ele-pro-table/types';
|
||||
import {
|
||||
getMyVolunteerSummary,
|
||||
getMyVolunteerProjectStats,
|
||||
getMyVolunteerTotal
|
||||
} from '@/api/growth/volunteer';
|
||||
|
||||
const loading = ref(false);
|
||||
const generating = ref(false);
|
||||
const year = ref(dayjs().year());
|
||||
const studentNo = ref('');
|
||||
const summary = ref<Record<string, any>>({});
|
||||
const projects = ref<Record<string, any>[]>([]);
|
||||
|
||||
const yearOptions = computed(() => {
|
||||
const current = dayjs().year();
|
||||
return Array.from({ length: 6 }, (_, index) => {
|
||||
const value = current - index;
|
||||
return { label: `${value} 年`, value };
|
||||
});
|
||||
});
|
||||
|
||||
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' }
|
||||
];
|
||||
|
||||
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 || [];
|
||||
} catch (e: any) {
|
||||
message.error(e?.message || '加载志愿数据失败');
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const generate = () => {
|
||||
generating.value = true;
|
||||
// 证明文件由后端模板渲染,P0 先给出数据核对结果,文件下载在模板接入后开放
|
||||
setTimeout(() => {
|
||||
generating.value = false;
|
||||
message.info(
|
||||
`已汇总 ${year.value} 年 ${summary.value.totalHours ?? 0} 小时志愿时长,证明材料模板接入后可在此下载 PDF`
|
||||
);
|
||||
}, 300);
|
||||
};
|
||||
|
||||
onMounted(loadData);
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.metric-card {
|
||||
padding: 18px 20px;
|
||||
border-radius: 10px;
|
||||
background: #fff;
|
||||
border-left: 4px solid #1677ff;
|
||||
}
|
||||
|
||||
.metric-card.accent {
|
||||
border-left-color: #13c2c2;
|
||||
}
|
||||
|
||||
.metric-card.warm {
|
||||
border-left-color: #faad14;
|
||||
}
|
||||
|
||||
.metric-label {
|
||||
font-size: 13px;
|
||||
color: #8c8c8c;
|
||||
}
|
||||
|
||||
.metric-value {
|
||||
margin: 6px 0 4px;
|
||||
font-size: 26px;
|
||||
font-weight: 600;
|
||||
color: #262626;
|
||||
}
|
||||
|
||||
.metric-foot {
|
||||
font-size: 12px;
|
||||
color: #bfbfbf;
|
||||
}
|
||||
|
||||
.project-card {
|
||||
margin-top: 16px;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user