refactor: 合并 biz_support_unit/biz_execution_unit/biz_service_org 为 biz_org, 删除 2 张孤儿表, 改 biz_person.work_unit → org_id FK
主要改动: - SQL: biz_support_unit → biz_org, 加 org_type, 加 business_nature; 删 biz_execution_unit, biz_service_org - SQL: biz_project.support_unit_id/name + service_org_id/name → org_id/name/type - SQL: biz_meeting.support_unit_name → org_name - SQL: biz_person.work_unit → org_id (FK) - 后端: 新 BizOrg entity/mapper/service/controller - 后端: BizProject/BizMeeting 字段重命名 - 后端: 删 12 个 BizSupportUnit/BizExecutionUnit/BizServiceOrg Java 文件 - 后端: BizPersonImportVO.workUnit → orgName (导入时查 biz_org 取 org_id) - 后端: BizAuthController.registerExecutor 完整实现 (原 registerSupplier stub) - 前端: 新 admin/Orgs.vue + manager/Orgs.vue (原 SupportUnits) - 前端: RegisterExecutor.vue (原 RegisterSupplier, 单页 2 步) - 前端: sponsor/executor/manager 多个文件 workUnit → orgId/orgName 重命名 - 前端: 统一 supplier → executor, 业务命名 sponsor(赞助方) / executor(执行方=供应商) - 前端: 全工程 execution → executor (company type / role / person unit_type)
This commit is contained in:
@@ -0,0 +1,131 @@
|
||||
<template>
|
||||
<div class="manager-workbench">
|
||||
|
||||
<!-- 我的项目策划方案 标题 (原 top=142) -->
|
||||
<h2 class="page-title">我的项目策划方案</h2>
|
||||
|
||||
<!-- 5 个 KPI 卡 (原 top=165 已结算/已结题 + top=458 已执行/未执行) -->
|
||||
<div class="stats-grid">
|
||||
<router-link class="stat-card" to="/manager/projects">
|
||||
<div class="stat-label">项目总数量</div>
|
||||
<div class="stat-value">{{ stats.totalProjects }}</div>
|
||||
</router-link>
|
||||
<router-link class="stat-card" to="/manager/meetings">
|
||||
<div class="stat-label">已结算数量</div>
|
||||
<div class="stat-value">{{ stats.settledProjects }}</div>
|
||||
</router-link>
|
||||
<router-link class="stat-card" to="/manager/projects">
|
||||
<div class="stat-label">已结题数量</div>
|
||||
<div class="stat-value">{{ stats.completedProjects }}</div>
|
||||
</router-link>
|
||||
<router-link class="stat-card" to="/manager/meetings">
|
||||
<div class="stat-label">已执行会议</div>
|
||||
<div class="stat-value">{{ stats.executedMeetings }}</div>
|
||||
</router-link>
|
||||
<router-link class="stat-card" to="/manager/meetings">
|
||||
<div class="stat-label">未执行会议</div>
|
||||
<div class="stat-value">{{ stats.pendingMeetings }}</div>
|
||||
</router-link>
|
||||
</div>
|
||||
|
||||
<p class="hint-text">*点击数字,跳转到相应的列表页</p>
|
||||
|
||||
<!-- 消息通知 (新增) -->
|
||||
<section class="section">
|
||||
<h2 class="section-title">消息通知<a class="more" href="javascript:void(0)">更多 →</a></h2>
|
||||
<ul class="notice-list">
|
||||
<li class="notice-item" v-for="n in notices" :key="n.id" :class="{ read: n.read }">
|
||||
<div class="dot"></div>
|
||||
<div class="body">
|
||||
<div class="title">{{ n.title }}</div>
|
||||
<div class="text">{{ n.text }}</div>
|
||||
</div>
|
||||
<span class="time">{{ n.time }}</span>
|
||||
</li>
|
||||
<li v-if="notices.length === 0" class="empty">暂无消息</li>
|
||||
</ul>
|
||||
</section>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { bizList, listMyMessages } from '@/api/public'
|
||||
|
||||
const stats = ref({
|
||||
totalProjects: 0, settledProjects: 0, completedProjects: 0,
|
||||
executedMeetings: 0, pendingMeetings: 0
|
||||
})
|
||||
const notices = ref([])
|
||||
|
||||
async function loadStats() {
|
||||
try {
|
||||
// 项目总数量
|
||||
const ps = await bizList('project', { pageNum: 1, pageSize: 1 })
|
||||
stats.value.totalProjects = ps.total || ps.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
|
||||
// 已执行会议
|
||||
const em = await bizList('meeting', { pageNum: 1, pageSize: 1, status: '已执行' })
|
||||
stats.value.executedMeetings = em.total || em.data?.total || 0
|
||||
stats.value.pendingMeetings = meetingTotal - stats.value.executedMeetings
|
||||
// 已结算项目
|
||||
const sp = await bizList('project', { pageNum: 1, pageSize: 1, isSettled: '1' })
|
||||
stats.value.settledProjects = sp.total || sp.data?.total || 0
|
||||
} catch (e) {
|
||||
console.warn('loadStats failed', e)
|
||||
}
|
||||
}
|
||||
|
||||
async function loadNotices() {
|
||||
try {
|
||||
const r = await listMyMessages({ limit: 50 }); const list = r.rows
|
||||
notices.value = list.map(n => ({
|
||||
id: n.id,
|
||||
title: n.title,
|
||||
text: n.text,
|
||||
time: n.time,
|
||||
read: n.read
|
||||
}))
|
||||
} catch (e) {
|
||||
console.warn('loadNotices failed', e)
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => { loadStats(); loadNotices() })
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.manager-workbench { padding: 16px; }
|
||||
.page-title { font-size: 20px; font-weight: 600; color: #1a1a1a; margin-bottom: 16px; padding-left: 10px; border-left: 3px solid var(--brand-primary); }
|
||||
.stats-grid { display: grid; grid-template-columns: repeat(5, 1fr); gap: 16px; margin-bottom: 12px; }
|
||||
.stat-card {
|
||||
background: #fff; border-radius: 4px; padding: 24px;
|
||||
border: 1px solid #ebeef5; cursor: pointer; text-decoration: none; color: inherit;
|
||||
display: flex; flex-direction: column; align-items: center; justify-content: center;
|
||||
transition: opacity .15s, border-color .15s;
|
||||
min-height: 120px;
|
||||
}
|
||||
.stat-card:hover { border-color: var(--brand-primary); opacity: .9; }
|
||||
.stat-label { font-size: 14px; color: #606266; margin-bottom: 16px; }
|
||||
.stat-value { font-size: 32px; font-weight: 600; color: var(--brand-primary); line-height: 1.2; }
|
||||
.hint-text { font-size: 12px; color: #ff4d4f; margin: 12px 0 20px; text-align: center; }
|
||||
.section { background: #fff; border-radius: 4px; padding: 20px 24px; border: 1px solid #ebeef5; }
|
||||
.section-title { font-size: 15px; font-weight: 600; color: #1a1a1a; margin-bottom: 16px; padding-left: 10px; border-left: 3px solid var(--brand-primary); display: flex; justify-content: space-between; }
|
||||
.more { font-size: 12px; color: var(--brand-primary); text-decoration: none; }
|
||||
.notice-list { list-style: none; border-top: 1px solid #f0f0f0; }
|
||||
.notice-item { padding: 12px 0; border-bottom: 1px solid #f0f0f0; display: flex; align-items: center; gap: 12px; }
|
||||
.notice-item:last-child { border-bottom: none; }
|
||||
.notice-item .dot { width: 6px; height: 6px; border-radius: 50%; background: var(--brand-primary); flex-shrink: 0; }
|
||||
.notice-item.read .dot { background: transparent; border: 1px solid #d9d9d9; }
|
||||
.notice-item .body { flex: 1; min-width: 0; }
|
||||
.notice-item .title { font-size: 13px; color: #1a1a1a; }
|
||||
.notice-item.read .title { color: #8c8c8c; }
|
||||
.notice-item .text { font-size: 12px; color: #595959; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; margin-top: 2px; }
|
||||
.notice-item .time { font-size: 12px; color: #bfbfbf; flex-shrink: 0; }
|
||||
.empty { padding: 24px; text-align: center; color: #909399; }
|
||||
</style>
|
||||
Reference in New Issue
Block a user