Files
guoju0808/ry-vue3/src/views/sponsor/Home.vue
T
guoju0808 18f7e93d5b feat: 其他角色首页移动端适配 (doctor/sponsor/executor)
doctor/Home.vue: 完善移动端适配
- 原只有 .cols-row → 1fr 一行规则
- 加 welcome-bar 紧凑 padding (20/24 → 14/16) + flex-wrap
- 加 welcome-text h2 字号 20 → 16, p 13 → 12
- 加 section padding 16/20 → 12/14, section-title 15 → 14
- 加 breadcrumb 字号 13 → 12, .doctor-home padding 16/20 → 12

sponsor/Home.vue: 补 stats-grid-5 媒体规则
- 原只有 stats-grid-3/2 媒体规则, 漏 stats-grid-5
- 加 stats-grid-5 → 2 列 + ≤480px → 1 列
- 加整页 padding 16 → 12, breadcrumb 13 → 12
- 加 stat-card padding 24 → 16/12 + min-height 120 → 90
- 加 stat-label/Value/Desc 字号缩小
- 加 .section padding 20/24 → 14/16

executor/Overview.vue: 补 stats-grid 5 列媒体规则
- 原 5 个 KPI 卡 5 列布局在 mobile 完全挤爆, 但媒体查询里没改 grid-template-columns
- 加 .stats-grid → 2 列 + ≤480px → 1 列
- 加 stat-card padding/min-height 调整
- 加 stat-label/Value 字号缩小
- 加 .section padding 20/24 → 14/16

admin/Workbench.vue: 已三档断点 (1200/768/480), 无需调整
manager/Workbench.vue: 已加 5 → 2 列布局, 无需调整
2026-08-26 12:24:55 +00:00

164 lines
6.6 KiB
Vue

<template>
<!--
原型: 工作台.html
SIDEBAR: ['项目管理', '项目策划方案管理', '会议管理', '专家审核']
筛选区 (top 165-205, left): 项目策划方案管理 / 项目总数量 / 已结算的会议 / 已结题的项目
列头 (top 285-335, left): 工作台 / 会议管理
-->
<div class="page-card sponsor-home">
<div class="breadcrumb">首页</div>
<!-- KPI: 5 个数字面板, 1 行展示 (跳转 + 自动应用筛选, 字段名跟 Projects.vue / Meetings.vue q 一致) -->
<div class="stats-grid stats-grid-5">
<router-link class="stat-card" to="/sponsor/projects">
<div class="stat-label">项目总数量</div>
<div class="stat-value">{{ stats.totalProjects }}</div>
<div class="stat-desc">查看全部项目</div>
</router-link>
<router-link class="stat-card" to="/sponsor/projects?isSettled=Y">
<div class="stat-label">已结算的项目</div>
<div class="stat-value">{{ stats.settledProjects }}</div>
<div class="stat-desc">查看已结算项目</div>
</router-link>
<router-link class="stat-card" to="/sponsor/projects?isFinished=1">
<div class="stat-label">已结题的项目</div>
<div class="stat-value">{{ stats.completedProjects }}</div>
<div class="stat-desc">查看已结题项目</div>
</router-link>
<router-link class="stat-card" to="/sponsor/meetings?currentStage=RUNNING">
<div class="stat-label">已执行会议</div>
<div class="stat-value">{{ stats.executedMeetings }}</div>
<div class="stat-desc">查看已执行会议</div>
</router-link>
<router-link class="stat-card" to="/sponsor/meetings?currentStage=NOT_STARTED">
<div class="stat-label">未执行会议</div>
<div class="stat-value">{{ stats.pendingMeetings }}</div>
<div class="stat-desc">查看未执行会议</div>
</router-link>
</div>
<!-- 消息通知 (NoticeList 组件内部已含 SSE 实时刷新 + 详情 dialog + 全部已读) -->
<section class="section">
<h2 class="section-title">消息通知<router-link class="more" to="/sponsor/messages">更多 </router-link></h2>
<NoticeList :limit="50" />
</section>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import { bizList } from '@/api/public'
import NoticeList from '@/components/NoticeList.vue'
// 字段名跟 Projects.vue / Meetings.vue q 一致, 之前 3/5 卡 stats 不更新或用错字段名 (status 后端不认, settledMeetings 误指项目级 isSettled)
const stats = ref({
totalProjects: 0,
settledProjects: 0, // 项目级 isSettled=Y
completedProjects: 0, // 项目级 isFinished=1
executedMeetings: 0, // 会议 currentStage=RUNNING (字段名跟 Meetings.vue q 一致, 之前误用 status= 后端不认, 数字一直是错的)
pendingMeetings: 0 // 总会议 - 已执行
})
async function loadStats() {
try {
// 项目总数量 (跟 manager/executor 一样调普通 list, 后续若需要按 sponsor 隔离再换 sponsorList 接口)
const ps = await bizList('project', { pageNum: 1, pageSize: 1 })
stats.value.totalProjects = ps.total || ps.data?.total || 0
// 已结算项目
const sp = await bizList('project', { pageNum: 1, pageSize: 1, isSettled: 'Y' })
stats.value.settledProjects = sp.total || sp.data?.total || 0
// 已结题项目
const cp = await bizList('project', { pageNum: 1, pageSize: 1, isFinished: '1' })
stats.value.completedProjects = cp.total || cp.data?.total || 0
// 会议总数量
const ms = await bizList('meeting', { pageNum: 1, pageSize: 1 })
const meetingTotal = ms.total || ms.data?.total || 0
// 已执行会议 = currentStage='RUNNING' (阶段已过开始时间, 执行方未提交)
const em = await bizList('meeting', { pageNum: 1, pageSize: 1, currentStage: 'RUNNING' })
stats.value.executedMeetings = em.total || em.data?.total || 0
stats.value.pendingMeetings = Math.max(0, meetingTotal - stats.value.executedMeetings)
} catch (e) {
console.warn('loadStats failed', e)
}
}
onMounted(() => {
loadStats()
})
</script>
<style scoped>
/* 整页面板 (与 People.vue 风格一致) */
.sponsor-home { padding: 16px; }
.breadcrumb { font-size: 13px; color: #8c8c8c; margin-bottom: 12px; }
.stats-grid { display: grid; gap: 16px; margin-bottom: 16px; }
.stats-grid-5 { grid-template-columns: repeat(5, 1fr); }
.stats-grid-3 { grid-template-columns: repeat(3, 1fr); }
.stats-grid-2 { grid-template-columns: repeat(2, 1fr); }
.stat-card {
background: var(--brand-primary);
border: 1px solid var(--brand-primary);
border-radius: 8px;
padding: 24px;
cursor: pointer;
text-decoration: none;
color: #fff;
display: block;
transition: opacity .15s, transform .15s, box-shadow .2s;
box-shadow: 0 2px 8px rgba(30, 58, 138, 0.15);
}
.stat-card:hover { opacity: .92; transform: translateY(-2px); box-shadow: 0 4px 12px rgba(30, 58, 138, 0.25); }
.stat-label { font-size: 14px; color: rgba(255, 255, 255, 0.85); margin-bottom: 16px; }
.stat-value {
font-size: 32px;
font-weight: 700;
line-height: 1;
margin-bottom: 8px;
color: #fff;
}
.stat-desc { font-size: 12px; color: rgba(255, 255, 255, 0.7); }
.section {
background: #fff;
border-radius: 8px;
padding: 20px 24px;
border: 1px solid #f0f0f0;
}
.section-title {
font-size: 15px; font-weight: 600; color: #1a1a1a;
margin-bottom: 16px; padding-left: 10px;
border-left: 3px solid var(--brand-primary);
line-height: 1;
display: flex; justify-content: space-between;
}
.more { font-size: 12px; color: var(--brand-primary); text-decoration: none; cursor: pointer; }
.more:hover { text-decoration: underline; }
@media (max-width: 768px) {
/* 5/3/2 列面板: 全部 ≤768px 改为 2 列布局 */
.stats-grid-5 { grid-template-columns: repeat(2, 1fr) !important; }
.stats-grid-3 { grid-template-columns: repeat(2, 1fr); }
.stats-grid-2 { grid-template-columns: 1fr; }
/* 整页 padding 收窄 */
.sponsor-home { padding: 12px !important; }
.breadcrumb { font-size: 12px !important; margin-bottom: 8px !important; }
/* 卡片 padding 收窄 + 字号缩小 */
.stat-card { padding: 16px 12px !important; min-height: 90px; }
.stat-label { font-size: 12px !important; margin-bottom: 8px !important; }
.stat-value { font-size: 24px !important; margin-bottom: 4px !important; }
.stat-desc { font-size: 11px !important; }
/* 消息通知 */
.section { padding: 14px 16px !important; }
.section-title { font-size: 14px !important; margin-bottom: 12px !important; }
}
/* ≤480px 极窄屏: 5 卡 → 1 列 */
@media (max-width: 480px) {
.stats-grid-5 { grid-template-columns: 1fr !important; }
}
</style>