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,177 @@
|
||||
<template>
|
||||
<div class="page-card doctor-home">
|
||||
<div class="breadcrumb">首页</div>
|
||||
|
||||
<!-- 欢迎栏 -->
|
||||
<div class="welcome-bar">
|
||||
<div class="welcome-text">
|
||||
<h2>下午好,{{ store.user?.userName || '专家' }}专家</h2>
|
||||
<p>欢迎使用项目管理系统</p>
|
||||
</div>
|
||||
<div class="welcome-time">
|
||||
<div class="now">{{ nowTime }}</div>
|
||||
<div class="date">{{ nowDate }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 待参加的会议 + 待签署的协议 -->
|
||||
<div class="cols-row">
|
||||
<div class="section">
|
||||
<h2 class="section-title">
|
||||
待参加的会议
|
||||
<a class="more" @click.prevent="$router.push('/doctor/meetings')">更多 →</a>
|
||||
</h2>
|
||||
<ul class="simple-list">
|
||||
<li class="simple-item" v-for="m in upcomingMeetings" :key="m.meetingId" @click="$router.push('/doctor/meetings')">
|
||||
<div class="item-main">
|
||||
<span class="item-title">{{ m.meetingName || m.title }}</span>
|
||||
</div>
|
||||
<span class="item-status">{{ formatTime(m.startTime) }}</span>
|
||||
</li>
|
||||
<li v-if="!upcomingMeetings.length" class="empty">暂无待参加会议</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="section">
|
||||
<h2 class="section-title">
|
||||
待签署的协议
|
||||
<a class="more" @click.prevent="$router.push('/doctor/submissions')">更多 →</a>
|
||||
</h2>
|
||||
<ul class="simple-list">
|
||||
<li class="simple-item" v-for="s in pendingAgreements" :key="s.subId">
|
||||
<div class="item-main">
|
||||
<span class="item-title">{{ s.title }}</span>
|
||||
</div>
|
||||
<span class="item-status" :class="{ done: s.status === 'done' }">
|
||||
{{ s.status === 'done' ? '已完成' : '待签署' }}
|
||||
</span>
|
||||
</li>
|
||||
<li v-if="!pendingAgreements.length" class="empty">暂无待签协议</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 通知消息 -->
|
||||
<section class="section">
|
||||
<h2 class="section-title">
|
||||
通知消息
|
||||
<a class="more" @click.prevent="$router.push('/doctor/messages')">更多 →</a>
|
||||
</h2>
|
||||
<ul class="notice-list">
|
||||
<li class="notice-item" :class="{ read: n.read }" v-for="n in notices" :key="n.id" @click="n.read = true">
|
||||
<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" class="empty">暂无通知</li>
|
||||
</ul>
|
||||
</section>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted, onBeforeUnmount } from 'vue'
|
||||
import { useUserStore } from '@/store/user'
|
||||
import { bizList, listMyMessages } from '@/api/public'
|
||||
|
||||
const store = useUserStore()
|
||||
|
||||
const upcomingMeetings = ref([])
|
||||
const pendingAgreements = ref([])
|
||||
const notices = ref([])
|
||||
|
||||
const nowTime = ref('')
|
||||
const nowDate = ref('')
|
||||
let timer = null
|
||||
|
||||
function formatTime(t) {
|
||||
if (!t) return ''
|
||||
const d = new Date(t)
|
||||
const today = new Date()
|
||||
const diff = Math.floor((d - today) / 86400000)
|
||||
if (diff === 0) return d.toTimeString().slice(0, 5)
|
||||
if (diff === 1) return '明天'
|
||||
return `${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`
|
||||
}
|
||||
|
||||
function updateClock() {
|
||||
const now = new Date()
|
||||
nowTime.value = now.toTimeString().slice(0, 5)
|
||||
nowDate.value = now.toLocaleDateString('zh-CN', { month: 'long', day: 'numeric', weekday: 'long' })
|
||||
}
|
||||
|
||||
async function load() {
|
||||
// 待参加会议
|
||||
try {
|
||||
const { data } = await bizList('meeting', { pageNum: 1, pageSize: 5 })
|
||||
upcomingMeetings.value = (data?.rows || []).slice(0, 5)
|
||||
} catch (e) { upcomingMeetings.value = [] }
|
||||
|
||||
// 待签署协议
|
||||
try {
|
||||
const { data } = await bizList('submission', { pageNum: 1, pageSize: 5 })
|
||||
pendingAgreements.value = (data?.rows || []).slice(0, 5).map(s => ({
|
||||
subId: s.subId,
|
||||
title: s.title,
|
||||
status: s.status === '审核通过' ? 'done' : 'pending'
|
||||
}))
|
||||
} catch (e) { pendingAgreements.value = [] }
|
||||
|
||||
// 通知
|
||||
try {
|
||||
notices.value = (await listMyMessages({ limit: 5 })).rows.map((a, i) => ({
|
||||
id: a.id,
|
||||
title: a.title,
|
||||
text: a.text,
|
||||
time: a.time,
|
||||
read: a.read || i >= 2
|
||||
}))
|
||||
} catch (e) { notices.value = [] }
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
updateClock()
|
||||
timer = setInterval(updateClock, 60000)
|
||||
load()
|
||||
})
|
||||
onBeforeUnmount(() => { if (timer) clearInterval(timer) })
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.doctor-home { padding: 16px 20px; }
|
||||
.breadcrumb { font-size: 13px; color: #8c8c8c; margin-bottom: 12px; }
|
||||
.welcome-bar { background: var(--brand-primary); border-radius: 4px; padding: 20px 24px; color: #fff; display: flex; align-items: center; justify-content: space-between; margin-bottom: 16px; }
|
||||
.welcome-text h2 { font-size: 20px; font-weight: 600; margin-bottom: 6px; }
|
||||
.welcome-text p { font-size: 13px; opacity: 0.85; }
|
||||
.welcome-time .now { font-size: 14px; font-weight: 500; }
|
||||
.welcome-time .date { font-size: 12px; opacity: 0.75; margin-top: 4px; }
|
||||
.section { background: #fff; padding: 16px 20px; margin-bottom: 16px; border: 1px solid #f0f0f0; border-radius: 4px; }
|
||||
.section-title { font-size: 15px; font-weight: 600; color: #1a1a1a; margin: 0 0 12px; display: flex; align-items: center; justify-content: space-between; }
|
||||
.more { font-size: 12px; color: var(--brand-primary); text-decoration: none; font-weight: normal; cursor: pointer; }
|
||||
.more:hover { text-decoration: underline; }
|
||||
.cols-row { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
|
||||
.cols-row .section { margin-bottom: 16px; }
|
||||
.simple-list { list-style: none; border-top: 1px solid #f0f0f0; }
|
||||
.simple-item { padding: 10px 0; border-bottom: 1px solid #f0f0f0; display: flex; align-items: center; justify-content: space-between; gap: 12px; cursor: pointer; }
|
||||
.simple-item:last-child { border-bottom: none; }
|
||||
.simple-item:hover .item-title { color: var(--brand-primary); }
|
||||
.item-main { display: flex; align-items: center; gap: 8px; min-width: 0; flex: 1; }
|
||||
.item-title { font-size: 13px; color: #1a1a1a; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex: 1; }
|
||||
.item-status { flex-shrink: 0; font-size: 12px; color: #8c8c8c; }
|
||||
.item-status.done { color: #52c41a; }
|
||||
.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; cursor: pointer; }
|
||||
.notice-item:last-child { border-bottom: none; }
|
||||
.notice-item:hover .title { color: var(--brand-primary); }
|
||||
.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; }
|
||||
.notice-item .time { font-size: 12px; color: #bfbfbf; flex-shrink: 0; }
|
||||
.empty { padding: 16px 0; text-align: center; color: #bfbfbf; font-size: 13px; }
|
||||
@media (max-width: 768px) { .cols-row { grid-template-columns: 1fr; } }
|
||||
</style>
|
||||
Reference in New Issue
Block a user