Files
郭庆泰 cf5790229a 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)
2026-08-15 12:41:10 +08:00

262 lines
22 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!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>