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:
郭庆泰
2026-08-15 12:41:10 +08:00
commit cf5790229a
694 changed files with 102090 additions and 0 deletions
+156
View File
@@ -0,0 +1,156 @@
<template>
<!--
原型: 工作台.html
SIDEBAR: ['项目管理', '项目策划方案管理', '会议管理', '专家审核']
筛选区 (top 165-205, left): 项目策划方案管理 / 项目总数量 / 已结算的会议 / 已结题的项目
列头 (top 285-335, left): 工作台 / 会议管理
-->
<div class="sponsor-home">
<!-- KPI: 5 个数字面板, 1 行展示 -->
<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/meetings?isSettled=1">
<div class="stat-label">已结算的会议</div>
<div class="stat-value">{{ stats.settledMeetings }}</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=已执行">
<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=未执行">
<div class="stat-label">未执行会议</div>
<div class="stat-value">{{ stats.pendingMeetings }}</div>
<div class="stat-desc">查看未执行会议</div>
</router-link>
</div>
<!-- 消息通知 -->
<section class="section">
<h2 class="section-title">消息通知<router-link class="more" to="/sponsor/messages">更多 </router-link></h2>
<ul class="notice-list">
<li class="notice-item" v-for="n in notices" :key="n.title" :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,
settledMeetings: 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 || 0
const ms = await bizList('meeting', { pageNum: 1, pageSize: 1 })
stats.value.executedMeetings = ms.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>
.sponsor-home { padding: 16px; }
.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: #fff;
border-radius: 4px;
padding: 24px;
border: 1px solid #f0f0f0;
cursor: pointer;
text-decoration: none;
color: inherit;
display: block;
}
.stat-label { font-size: 14px; color: #8c8c8c; margin-bottom: 16px; }
.stat-value { font-size: 32px; font-weight: 700; color: var(--brand-primary); line-height: 1; margin-bottom: 8px; }
.stat-desc { font-size: 12px; color: #bfbfbf; }
.hint-text { font-size: 12px; color: #ff4d4f; margin: 12px 0; text-align: center; }
.section { background: #fff; border-radius: 4px; 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);
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; }
@media (max-width: 768px) {
.stats-grid-3 { grid-template-columns: repeat(2, 1fr); }
.stats-grid-2 { grid-template-columns: 1fr; }
}
</style>