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
+262
View File
@@ -0,0 +1,262 @@
<!DOCTYPE html>
<!-- 角色:赞助方 父级:sponsor.html 原型:原型/项目列表_1.html
补全(2026-08-08):
- 筛选区:增加 项目时间(u9539,含 起/止 日期输入)
- 表格区:增加 项目开始时间(u9499) + 项目结束时间(u9501) 两列
- Modal:项目评价(u9385,执行单位评分 - 4 维度)+ 项目分配(u9400,监察员选择) -->
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>项目列表 - 项目管理系统</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: -apple-system, "PingFang SC", "Microsoft YaHei", sans-serif; color: #333; background: #fafafa; min-height: 100vh; }
.content { padding: 24px 32px; }
.page-title { font-size: 22px; font-weight: 600; color: #1a1a1a; margin-bottom: 20px; }
.card { background: #fff; border-radius: 8px; padding: 20px 24px; margin-bottom: 16px; border: 1px solid #f0f0f0; }
.filter-row { display: flex; flex-wrap: wrap; gap: 12px; align-items: center; }
.filter-field { display: flex; align-items: center; gap: 8px; }
.filter-label { font-size: 14px; color: #595959; white-space: nowrap; }
.filter-input,.filter-select,.filter-date { width: 180px; height: 32px; padding: 0 12px; border: 1px solid #d9d9d9; border-radius: 4px; font-size: 13px; color: #333; background: #fff; outline: none; }
.filter-date { width: 140px; }
.filter-range { display: flex; align-items: center; gap: 6px; }
.filter-range-sep { color: #8c8c8c; font-size: 13px; }
.filter-select { cursor: pointer; appearance: none; background: #fff url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23999' stroke-width='2'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E") no-repeat right 10px center / 14px; padding-right: 28px; }
.filter-input:focus,.filter-select:focus,.filter-date:focus { border-color: #1890ff; box-shadow: 0 0 0 3px rgba(24,144,255,0.1); }
.filter-btn { height: 32px; padding: 0 20px; background: #1890ff; color: #fff; border: none; border-radius: 4px; font-size: 13px; cursor: pointer; }
.filter-btn:hover { background: #096dd9; }
.action-bar { display: flex; gap: 12px; margin-bottom: 16px; flex-wrap: wrap; align-items: center; }
.primary-btn { height: 36px; padding: 0 24px; background: #1890ff; color: #fff; border: none; border-radius: 4px; font-size: 14px; cursor: pointer; }
.primary-btn:hover { background: #096dd9; }
.default-btn { height: 36px; padding: 0 24px; background: #fff; color: #333; border: 1px solid #d9d9d9; border-radius: 4px; font-size: 14px; cursor: pointer; }
.default-btn:hover { border-color: #1890ff; color: #1890ff; }
.hint-text { font-size: 12px; color: #ff4d4f; line-height: 1.6; }
.data-card { padding: 0; overflow-x: auto; }
.data-table { width: 100%; border-collapse: collapse; font-size: 13px; }
.data-table th { background: #fafafa; padding: 10px 12px; text-align: left; color: #1a1a1a; font-weight: 600; border-bottom: 1px solid #f0f0f0; white-space: nowrap; }
.data-table td { padding: 10px 12px; border-bottom: 1px solid #f5f5f5; color: #595959; vertical-align: middle; white-space: nowrap; }
.data-table tr:hover { background: #fafcff; }
.data-table input[type="checkbox"] { cursor: pointer; width: 14px; height: 14px; accent-color: #1890ff; }
.col-code { color: #1890ff; font-family: ui-monospace, "Courier New", monospace; }
.col-name { color: #1a1a1a; font-weight: 500; }
.col-money { text-align: right; font-family: ui-monospace, "Courier New", monospace; }
.col-score { color: #1890ff; font-weight: 600; }
.col-date { font-family: ui-monospace, "Courier New", monospace; color: #595959; font-size: 12px; }
.op-link { color: #1890ff; text-decoration: none; font-size: 13px; margin-right: 8px; cursor: pointer; }
.op-link:hover { text-decoration: underline; }
.op-link:last-child { margin-right: 0; }
/* Modal */
.modal-mask { display: none; position: fixed; inset: 0; background: rgba(0,0,0,.45); z-index: 1000; align-items: center; justify-content: center; }
.modal-mask.show { display: flex; }
.modal { background: #fff; border-radius: 8px; width: 520px; max-width: 90vw; max-height: 90vh; overflow-y: auto; box-shadow: 0 10px 40px rgba(0,0,0,.2); }
.modal-header { padding: 20px 24px; border-bottom: 1px solid #f0f0f0; display: flex; align-items: center; justify-content: space-between; }
.modal-title { font-size: 16px; font-weight: 600; color: #1a1a1a; }
.modal-close { cursor: pointer; color: #8c8c8c; font-size: 20px; background: none; border: none; padding: 4px; line-height: 1; }
.modal-close:hover { color: #1a1a1a; }
.modal-body { padding: 24px; }
.modal-row { margin-bottom: 16px; }
.modal-row-label { font-size: 13px; color: #595959; margin-bottom: 6px; }
.modal-row-value { font-size: 14px; color: #1a1a1a; font-weight: 500; }
.modal-textarea { width: 100%; min-height: 70px; padding: 8px 12px; border: 1px solid #d9d9d9; border-radius: 4px; font-size: 13px; color: #333; resize: vertical; outline: none; box-sizing: border-box; font-family: inherit; }
.modal-textarea:focus { border-color: #1890ff; box-shadow: 0 0 0 3px rgba(24,144,255,.1); }
.modal-input { width: 100%; height: 32px; padding: 0 12px; border: 1px solid #d9d9d9; border-radius: 4px; font-size: 13px; color: #333; outline: none; box-sizing: border-box; }
.modal-input:focus { border-color: #1890ff; box-shadow: 0 0 0 3px rgba(24,144,255,.1); }
.rating-row { display: flex; align-items: center; justify-content: space-between; padding: 10px 0; border-bottom: 1px dashed #f0f0f0; gap: 12px; }
.rating-row:last-child { border-bottom: none; }
.rating-info { flex: 1; min-width: 0; }
.rating-name { font-size: 14px; color: #1a1a1a; font-weight: 500; margin-bottom: 2px; }
.rating-desc { font-size: 12px; color: #8c8c8c; }
.rating-stars { color: #faad14; font-size: 18px; letter-spacing: 2px; white-space: nowrap; cursor: pointer; user-select: none; }
.rating-stars .empty { color: #e8e8e8; }
.rating-input { width: 50px; height: 28px; padding: 0 8px; border: 1px solid #d9d9d9; border-radius: 4px; font-size: 13px; text-align: center; outline: none; }
.rating-input:focus { border-color: #1890ff; }
.modal-footer { padding: 16px 24px; border-top: 1px solid #f0f0f0; text-align: right; display: flex; gap: 8px; justify-content: flex-end; }
.modal-footer .default-btn,
.modal-footer .primary-btn { height: 32px; padding: 0 20px; font-size: 13px; }
.assign-hint { font-size: 12px; color: #ff4d4f; margin-top: 8px; }
@media (max-width:768px) { .content { padding: 16px; } .data-table { font-size: 12px; } .modal { width: 96vw; } }
</style>
</head>
<body>
<main class="content">
<h1 class="page-title">项目列表</h1>
<div class="card">
<div class="filter-row">
<div class="filter-field"><span class="filter-label">项目编号:</span><input type="text" class="filter-input" placeholder="输入项目编号"></div>
<div class="filter-field"><span class="filter-label">项目名称:</span><input type="text" class="filter-input" placeholder="输入项目名称"></div>
<div class="filter-field"><span class="filter-label">项目时间:</span>
<div class="filter-range">
<input type="date" class="filter-date" value="2026-01-01">
<span class="filter-range-sep"></span>
<input type="date" class="filter-date" value="2026-12-31">
</div>
</div>
<div class="filter-field"><span class="filter-label">是否结题:</span>
<select class="filter-select"><option value="">请选择</option><option value="done">已结题</option><option value="undone">未结题</option></select>
</div>
<button class="filter-btn">查找</button>
</div>
</div>
<div class="action-bar">
<button class="primary-btn" onclick="batchAssign()">项目分配</button>
<button class="default-btn" onclick="batchEval()">批量评价</button>
<span class="hint-text" style="margin-left:16px">*支持方管理员在分配项目的时候,只能分配监察员</span>
</div>
<div class="card data-card">
<table class="data-table">
<thead>
<tr>
<th style="width:40px"><input type="checkbox" id="selectAll"></th>
<th>项目编号</th>
<th>项目名称</th>
<th style="text-align:center">总场次</th>
<th style="text-align:center">已执行</th>
<th style="text-align:center">未执行</th>
<th style="text-align:right">总金额</th>
<th style="text-align:right">可用金额</th>
<th style="text-align:right">劳务费</th>
<th style="text-align:right">会务费</th>
<th>项目开始时间</th>
<th>项目结束时间</th>
<th style="text-align:center">是否结题</th>
<th>执行单位评价</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr><td><input type="checkbox" class="row-check"></td><td><a class="col-code" href="javascript:void(0)">ZH-2026-658</a></td><td><a class="col-name" href="javascript:void(0)">小牛血清创新应用研讨会</a></td><td style="text-align:center">26</td><td style="text-align:center">16</td><td style="text-align:center">10</td><td class="col-money">3,112,000</td><td class="col-money">1,112,000</td><td class="col-money">1,000,000</td><td class="col-money">1,000,000</td><td class="col-date">2026-05-11</td><td class="col-date">2026-06-30</td><td style="text-align:center">未结题</td><td><a class="op-link" href="javascript:void(0)">3.5</a></td><td><a class="op-link" onclick="openEval('ZH-2026-658','小牛血清创新应用研讨会')">项目评价</a><a class="op-link" onclick="openAssign('ZH-2026-658','小牛血清创新应用研讨会')">分配</a><a class="op-link" href="sponsor-project-detail.html">查看</a></td></tr>
<tr><td><input type="checkbox" class="row-check"></td><td><a class="col-code" href="javascript:void(0)">ZH-2026-650</a></td><td><a class="col-name" href="javascript:void(0)">整合医学学会项目评审会</a></td><td style="text-align:center">18</td><td style="text-align:center">12</td><td style="text-align:center">6</td><td class="col-money">2,500,000</td><td class="col-money">980,000</td><td class="col-money">900,000</td><td class="col-money">620,000</td><td class="col-date">2026-04-01</td><td class="col-date">2026-05-20</td><td style="text-align:center">已结题</td><td><a class="op-link" href="javascript:void(0)">4.5</a></td><td><a class="op-link" onclick="openEval('ZH-2026-650','整合医学学会项目评审会')">项目评价</a><a class="op-link" onclick="openAssign('ZH-2026-650','整合医学学会项目评审会')">分配</a><a class="op-link" href="sponsor-project-detail.html">查看</a></td></tr>
<tr><td><input type="checkbox" class="row-check"></td><td><a class="col-code" href="javascript:void(0)">ZH-2026-645</a></td><td><a class="col-name" href="javascript:void(0)">智慧医院建设项目方案论证</a></td><td style="text-align:center">12</td><td style="text-align:center">7</td><td style="text-align:center">5</td><td class="col-money">1,800,000</td><td class="col-money">760,000</td><td class="col-money">700,000</td><td class="col-money">340,000</td><td class="col-date">2026-03-15</td><td class="col-date">2026-04-30</td><td style="text-align:center">未结题</td><td><a class="op-link" href="javascript:void(0)">5.0</a></td><td><a class="op-link" onclick="openEval('ZH-2026-645','智慧医院建设项目方案论证')">项目评价</a><a class="op-link" onclick="openAssign('ZH-2026-645','智慧医院建设项目方案论证')">分配</a><a class="op-link" href="sponsor-project-detail.html">查看</a></td></tr>
<tr><td><input type="checkbox" class="row-check"></td><td><a class="col-code" href="javascript:void(0)">ZH-2026-640</a></td><td><a class="col-name" href="javascript:void(0)">基层医疗改革试点中期评估</a></td><td style="text-align:center">20</td><td style="text-align:center">14</td><td style="text-align:center">6</td><td class="col-money">2,800,000</td><td class="col-money">1,050,000</td><td class="col-money">950,000</td><td class="col-money">800,000</td><td class="col-date">2026-02-01</td><td class="col-date">2026-03-31</td><td style="text-align:center">已结题</td><td><a class="op-link" href="javascript:void(0)">4.5</a></td><td><a class="op-link" onclick="openEval('ZH-2026-640','基层医疗改革试点中期评估')">项目评价</a><a class="op-link" onclick="openAssign('ZH-2026-640','基层医疗改革试点中期评估')">分配</a><a class="op-link" href="sponsor-project-detail.html">查看</a></td></tr>
<tr><td><input type="checkbox" class="row-check"></td><td><a class="col-code" href="javascript:void(0)">ZH-2026-635</a></td><td><a class="col-name" href="javascript:void(0)">数字化医疗转型方案评审</a></td><td style="text-align:center">22</td><td style="text-align:center">15</td><td style="text-align:center">7</td><td class="col-money">3,000,000</td><td class="col-money">1,200,000</td><td class="col-money">1,100,000</td><td class="col-money">700,000</td><td class="col-date">2026-01-10</td><td class="col-date">2026-02-28</td><td style="text-align:center">已结题</td><td><a class="op-link" href="javascript:void(0)">5.0</a></td><td><a class="op-link" onclick="openEval('ZH-2026-635','数字化医疗转型方案评审')">项目评价</a><a class="op-link" onclick="openAssign('ZH-2026-635','数字化医疗转型方案评审')">分配</a><a class="op-link" href="sponsor-project-detail.html">查看</a></td></tr>
</tbody>
</table>
</div>
<p class="hint-text" style="margin-top:12px">*点击后可查看具体评价维度,评价标准与供应商系统保持一致</p>
</main>
<!-- Modal: 项目评价 -->
<div class="modal-mask" id="evalModal">
<div class="modal">
<div class="modal-header">
<span class="modal-title">执行单位评分</span>
<button class="modal-close" onclick="closeModal('evalModal')">×</button>
</div>
<div class="modal-body">
<div class="modal-row">
<div class="modal-row-label">项目编号 / 项目名称</div>
<div class="modal-row-value" id="evalProj"></div>
</div>
<div class="modal-row">
<div class="modal-row-label">评价维度 (1~5 分)</div>
<div class="rating-row">
<div class="rating-info"><div class="rating-name">履约质量</div><div class="rating-desc">服务 / 活动效果达标度</div></div>
<div class="rating-stars" onclick="rate(this,5)">★★★★★</div>
</div>
<div class="rating-row">
<div class="rating-info"><div class="rating-name">时效响应</div><div class="rating-desc">执行 &amp; 售后响应速度</div></div>
<div class="rating-stars" onclick="rate(this,5)">★★★★★</div>
</div>
<div class="rating-row">
<div class="rating-info"><div class="rating-name">配合度</div><div class="rating-desc">沟通配合 &amp; 问题处理</div></div>
<div class="rating-stars" onclick="rate(this,4)">★★★★<span class="empty"></span></div>
</div>
<div class="rating-row">
<div class="rating-info"><div class="rating-name">合规安全</div><div class="rating-desc">流程合规 &amp; 无安全事故</div></div>
<div class="rating-stars" onclick="rate(this,3)">★★★<span class="empty">★★</span></div>
</div>
</div>
<div class="modal-row">
<div class="modal-row-label">评价备注</div>
<textarea class="modal-textarea" placeholder="请输入评价备注..."></textarea>
</div>
</div>
<div class="modal-footer">
<button class="default-btn" onclick="closeModal('evalModal')">取消</button>
<button class="primary-btn" onclick="saveEval()">确定</button>
</div>
</div>
</div>
<!-- Modal: 项目分配 -->
<div class="modal-mask" id="assignModal">
<div class="modal">
<div class="modal-header">
<span class="modal-title">项目分配</span>
<button class="modal-close" onclick="closeModal('assignModal')">×</button>
</div>
<div class="modal-body">
<div class="modal-row">
<div class="modal-row-label">项目编号 / 项目名称</div>
<div class="modal-row-value" id="assignProj"></div>
</div>
<div class="modal-row">
<div class="modal-row-label">分配给监察员</div>
<input type="text" class="modal-input" id="assignTo" placeholder="搜索监察员姓名 / 手机号">
<div class="assign-hint">*支持方管理员在分配项目的时候,只能分配监察员</div>
</div>
<div class="modal-row">
<div class="modal-row-label">候选监察员</div>
<div style="display:flex;flex-wrap:wrap;gap:8px">
<span class="status-tag" style="background:#e6f4ff;color:#1890ff;padding:6px 12px;border-radius:4px;cursor:pointer" onclick="pickAssign('张三')">张三</span>
<span class="status-tag" style="background:#e6f4ff;color:#1890ff;padding:6px 12px;border-radius:4px;cursor:pointer" onclick="pickAssign('李四')">李四</span>
<span class="status-tag" style="background:#e6f4ff;color:#1890ff;padding:6px 12px;border-radius:4px;cursor:pointer" onclick="pickAssign('王五')">王五</span>
</div>
</div>
<div class="modal-row">
<div class="modal-row-label">分配说明</div>
<textarea class="modal-textarea" placeholder="请输入分配说明..."></textarea>
</div>
<div class="modal-row">
<div class="modal-row-label">监察要点</div>
<textarea class="modal-textarea" placeholder="请输入监察要点..."></textarea>
</div>
</div>
<div class="modal-footer">
<button class="default-btn" onclick="closeModal('assignModal')">取消</button>
<button class="primary-btn" onclick="saveAssign()">确定</button>
</div>
</div>
</div>
<script>
document.getElementById('selectAll').addEventListener('change', function () { document.querySelectorAll('.row-check').forEach(c => c.checked = this.checked); });
function openEval(code, name) {
document.getElementById('evalProj').textContent = code + ' / ' + name;
document.getElementById('evalModal').classList.add('show');
}
function closeModal(id) { document.getElementById(id).classList.remove('show'); }
function saveEval() { closeModal('evalModal'); alert('评价已保存'); }
function openAssign(code, name) {
document.getElementById('assignProj').textContent = code + ' / ' + name;
document.getElementById('assignTo').value = '';
document.getElementById('assignModal').classList.add('show');
}
function pickAssign(name) { document.getElementById('assignTo').value = name; }
function saveAssign() { closeModal('assignModal'); alert('分配已保存'); }
function batchAssign() {
const checked = document.querySelectorAll('.row-check:checked').length;
if (!checked) { alert('请先勾选项目'); return; }
document.getElementById('assignProj').textContent = '批量分配 (' + checked + ' 个项目)';
document.getElementById('assignTo').value = '';
document.getElementById('assignModal').classList.add('show');
}
function batchEval() { alert('批量评价:原型仅定义了 1v1 评价流,批量逻辑待业务确认'); }
function rate(el, max) {
const n = parseInt(prompt('请输入 1~5 的评分', max)) || 0;
if (n < 1 || n > 5) return;
el.innerHTML = '★'.repeat(n) + '<span class="empty">' + '★'.repeat(5 - n) + '</span>';
}
document.querySelectorAll('.modal-mask').forEach(m => m.addEventListener('click', function (e) { if (e.target === this) this.classList.remove('show'); }));
</script>
</body>
</html>