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

644 lines
29 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>
<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-header {
margin-bottom: 20px;
}
.page-title {
font-size: 22px;
font-weight: 600;
color: #1a1a1a;
margin-bottom: 4px;
}
.page-subtitle {
font-size: 13px;
color: #8c8c8c;
}
.card {
background: #fff;
border-radius: 8px;
padding: 20px 24px;
margin-bottom: 16px;
border: 1px solid #f0f0f0;
}
/* ============= 筛选区 ============= */
.filter-row {
display: grid;
grid-template-columns: 1fr 2fr 1fr 1fr;
gap: 12px;
align-items: center;
margin-bottom: 16px;
}
.filter-row.row-2 {
grid-template-columns: 1fr 1fr 1fr auto;
}
.filter-field {
display: flex;
align-items: center;
gap: 8px;
}
.filter-label {
font-size: 14px;
color: #595959;
white-space: nowrap;
flex-shrink: 0;
}
.filter-input,
.filter-select {
flex: 1;
height: 32px;
padding: 0 10px;
border: 1px solid #d9d9d9;
border-radius: 4px;
font-size: 13px;
color: #333;
background: #fff;
outline: none;
}
.filter-input:focus,
.filter-select:focus {
border-color: #1890ff;
box-shadow: 0 0 0 3px rgba(24,144,255,0.1);
}
.filter-select {
cursor: pointer;
appearance: none;
background-image: url("data:image/svg+xml;charset=UTF-8,%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");
background-repeat: no-repeat;
background-position: right 10px center;
background-size: 14px;
padding-right: 24px;
}
.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; }
/* ============= 红色提示 ============= */
.hint-list {
background: #fff;
border: 1px solid #f0f0f0;
border-radius: 8px;
padding: 14px 20px;
margin-bottom: 16px;
font-size: 12px;
color: #ff4d4f;
line-height: 1.8;
}
.hint-list p { margin-bottom: 4px; }
/* ============= 表格按钮区 ============= */
.table-actions {
display: flex;
gap: 12px;
margin-bottom: 12px;
}
.bulk-btn {
height: 36px;
padding: 0 24px;
background: #1890ff;
color: #fff;
border: none;
border-radius: 4px;
font-size: 14px;
cursor: pointer;
}
.bulk-btn:hover { background: #096dd9; }
.bulk-btn.secondary {
background: #fff;
color: #1890ff;
border: 1px solid #1890ff;
}
.bulk-btn.secondary:hover { background: #e6f7ff; }
/* ============= 表格 ============= */
.table-wrap {
background: #fff;
border: 1px solid #f0f0f0;
border-radius: 8px;
overflow-x: auto;
}
.data-table {
width: 100%;
border-collapse: collapse;
font-size: 13px;
table-layout: auto;
min-width: 1700px;
}
.data-table thead th {
background: #fafafa;
padding: 12px 10px;
text-align: left;
color: #1a1a1a;
font-weight: 600;
border-bottom: 1px solid #f0f0f0;
white-space: nowrap;
}
.data-table tbody td {
padding: 12px 10px;
border-bottom: 1px solid #f5f5f5;
color: #595959;
vertical-align: middle;
white-space: nowrap;
}
.data-table tbody tr:hover { background: #fafcff; }
.col-check { width: 36px; }
.data-table input[type="checkbox"] {
cursor: pointer;
width: 14px;
height: 14px;
accent-color: #1890ff;
}
.col-name { color: #1a1a1a; font-weight: 500; }
.col-direction {
max-width: 240px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
display: inline-block;
vertical-align: middle;
}
.status-tag {
display: inline-block;
padding: 2px 10px;
border-radius: 10px;
font-size: 12px;
}
.status-pending { background: #fff7e6; color: #fa8c16; }
.status-done { background: #f6ffed; color: #52c41a; }
.status-reject { background: #fff1f0; color: #ff4d4f; }
.op-link {
color: #1890ff;
text-decoration: none;
margin-right: 8px;
font-size: 13px;
}
.op-link:last-child { margin-right: 0; }
.op-link:hover { text-decoration: underline; }
/* ============= 备注 ============= */
.col-remark {
max-width: 140px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.col-num {
color: #1a1a1a;
font-family: ui-monospace, "Courier New", monospace;
}
/* 项目形式提示 */
.form-note {
font-size: 12px;
color: #ff4d4f;
margin-bottom: 8px;
}
/* Modal (原型 u2764/u2785) */
.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: 480px; 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-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); }
.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); }
.radio-row { display: flex; align-items: center; gap: 8px; cursor: pointer; padding: 4px 0; }
.radio-row input { cursor: pointer; accent-color: #1890ff; }
.modal-hint { font-size: 12px; color: #ff0000; line-height: 1.6; margin-top: 8px; }
.modal-footer { padding: 16px 24px; border-top: 1px solid #f0f0f0; text-align: right; display: flex; gap: 8px; justify-content: flex-end; }
.modal-footer .btn-default, .modal-footer .btn-primary { height: 32px; padding: 0 20px; font-size: 13px; border-radius: 4px; cursor: pointer; }
.modal-footer .btn-default { background: #fff; color: #333; border: 1px solid #d9d9d9; }
.modal-footer .btn-default:hover { border-color: #1890ff; color: #1890ff; }
.modal-footer .btn-primary { background: #1890ff; color: #fff; border: 1px solid #1890ff; }
.modal-footer .btn-primary:hover { background: #096dd9; }
@media (max-width: 768px) {
.filter-row { grid-template-columns: 1fr; }
.filter-row.row-2 { grid-template-columns: 1fr; }
.content { padding: 16px; }
.data-table { font-size: 12px; }
.data-table { display: block; overflow-x: auto; white-space: nowrap; }
}
</style>
</head>
<body>
<main class="content">
<div class="page-header">
<h1 class="page-title">项目策划方案管理</h1>
<p class="page-subtitle">审核 / 修改 / 结算 项目策划方案</p>
</div>
<!-- 筛选区 -->
<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>
<select class="filter-select">
<option value="">请选择</option>
<option value="plan1">专项计划一:《规范化诊疗及医疗质量提升专项计划(2026-2030年)》</option>
<option value="plan2">专项计划二:《科研创新专项行动计划(2026-2030年)》</option>
</select>
</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>
<div class="filter-field">
<span class="filter-label">备注:</span>
<input type="text" class="filter-input" placeholder="输入备注">
</div>
</div>
<div class="filter-row row-2">
<div class="filter-field">
<span class="filter-label">项目类别:</span>
<select class="filter-select">
<option value="">请选择</option>
</select>
</div>
<div class="filter-field">
<span class="filter-label">是否结算:</span>
<select class="filter-select">
<option value="">请选择</option>
<option value="settled">已结算</option>
<option value="unsettled">未结算</option>
</select>
</div>
<div class="filter-field">
<span class="filter-label">项目编号:</span>
<input type="text" class="filter-input" placeholder="输入项目编号">
</div>
<button class="filter-btn">查找</button>
</div>
</div>
<!-- 红色提示 -->
<div class="hint-list">
<p>*批量审核:策划案名称,投稿ID,审核结果,审核意见,只能审核不通过</p>
</div>
<!-- 表格按钮 -->
<div class="table-actions">
<button class="bulk-btn" onclick="openBatchAudit()">批量审核</button>
<button class="bulk-btn secondary">批量结算</button>
</div>
<!-- 项目形式提示 -->
<p class="form-note">*项目形式是否保留?</p>
<!-- 表格 -->
<div class="table-wrap">
<table class="data-table">
<thead>
<tr>
<th class="col-check"><input type="checkbox" id="selectAll"></th>
<th>策划方案名称</th>
<th>项目方向</th>
<th>项目类别</th>
<th>项目形式</th>
<th>设计文件</th>
<th>状态</th>
<th>是否结算</th>
<th>项目编号</th>
<th>备注</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr>
<td><input type="checkbox" class="row-check"></td>
<td><a class="col-name" href="javascript:void(0)">小牛血清创新应用研讨会</a></td>
<td><span class="col-direction" title="专项计划一:《规范化诊疗及医疗质量提升专项计划(2026-2030年)》">专项计划一:《规范化诊疗及医疗质.....</span></td>
<td>会议项目</td>
<td>线上</td>
<td><a class="op-link" href="javascript:void(0)">查看</a> <a class="op-link" href="javascript:void(0)">下载</a></td>
<td><span class="status-tag status-pending">待审核</span></td>
<td>已结算</td>
<td class="col-num">ZH-2026-658</td>
<td class="col-remark" title="XXXXXX">XXXXXX</td>
<td>
<a class="op-link" onclick="openAudit(this)">审核</a>
<a class="op-link" onclick="goEdit(this)">修改</a>
<a class="op-link" onclick="openSettle(this)">结算</a>
</td>
</tr>
<tr>
<td><input type="checkbox" class="row-check"></td>
<td><a class="col-name" href="javascript:void(0)">小牛血清创新应用研讨会</a></td>
<td><span class="col-direction" title="专项计划一:《规范化诊疗及医疗质量提升专项计划(2026-2030年)》">专项计划一:《规范化诊疗及医疗质.....</span></td>
<td>会议项目</td>
<td>线下</td>
<td><a class="op-link" href="javascript:void(0)">查看</a> <a class="op-link" href="javascript:void(0)">下载</a></td>
<td><span class="status-tag status-done">审核通过</span></td>
<td>未结算</td>
<td class="col-num">ZH-2026-658</td>
<td class="col-remark" title="XXXXXX">XXXXXX</td>
<td>
<a class="op-link" onclick="openAudit(this)">审核</a>
<a class="op-link" onclick="goEdit(this)">修改</a>
<a class="op-link" onclick="openSettle(this)">结算</a>
</td>
</tr>
<tr>
<td><input type="checkbox" class="row-check"></td>
<td><a class="col-name" href="javascript:void(0)">小牛血清创新应用研讨会</a></td>
<td><span class="col-direction" title="专项计划一:《规范化诊疗及医疗质量提升专项计划(2026-2030年)》">专项计划一:《规范化诊疗及医疗质.....</span></td>
<td>会议项目</td>
<td>线上+线下</td>
<td><a class="op-link" href="javascript:void(0)">查看</a> <a class="op-link" href="javascript:void(0)">下载</a></td>
<td><span class="status-tag status-reject">已退回</span></td>
<td>未结算</td>
<td class="col-num">ZH-2026-658</td>
<td class="col-remark" title="XXXXXX">XXXXXX</td>
<td>
<a class="op-link" onclick="openAudit(this)">审核</a>
<a class="op-link" onclick="goEdit(this)">修改</a>
<a class="op-link" onclick="openSettle(this)">结算</a>
</td>
</tr>
<tr>
<td><input type="checkbox" class="row-check"></td>
<td><a class="col-name" href="javascript:void(0)">整合医学学会项目设计</a></td>
<td><span class="col-direction" title="专项计划一:《规范化诊疗及医疗质量提升专项计划(2026-2030年)》">专项计划一:《规范化诊疗及医疗质.....</span></td>
<td>科研项目</td>
<td>线上</td>
<td><a class="op-link" href="javascript:void(0)">查看</a> <a class="op-link" href="javascript:void(0)">下载</a></td>
<td><span class="status-tag status-pending">待审核</span></td>
<td>未结算</td>
<td class="col-num">ZH-2026-659</td>
<td class="col-remark" title="XXXXXX">XXXXXX</td>
<td>
<a class="op-link" onclick="openAudit(this)">审核</a>
<a class="op-link" onclick="goEdit(this)">修改</a>
<a class="op-link" onclick="openSettle(this)">结算</a>
</td>
</tr>
<tr>
<td><input type="checkbox" class="row-check"></td>
<td><a class="col-name" href="javascript:void(0)">智慧医院建设项目方案</a></td>
<td><span class="col-direction" title="专项计划一:《规范化诊疗及医疗质量提升专项计划(2026-2030年)》">专项计划一:《规范化诊疗及医疗质.....</span></td>
<td>学术项目</td>
<td>线下</td>
<td><a class="op-link" href="javascript:void(0)">查看</a> <a class="op-link" href="javascript:void(0)">下载</a></td>
<td><span class="status-tag status-done">审核通过</span></td>
<td>未结算</td>
<td class="col-num">ZH-2026-650</td>
<td class="col-remark" title="XXXXXX">XXXXXX</td>
<td>
<a class="op-link" onclick="openAudit(this)">审核</a>
<a class="op-link" onclick="goEdit(this)">修改</a>
<a class="op-link" onclick="openSettle(this)">结算</a>
</td>
</tr>
<tr>
<td><input type="checkbox" class="row-check"></td>
<td><a class="col-name" href="javascript:void(0)">基层医疗改革试点方案</a></td>
<td><span class="col-direction" title="专项计划一:《规范化诊疗及医疗质量提升专项计划(2026-2030年)》">专项计划一:《规范化诊疗及医疗质.....</span></td>
<td>指南</td>
<td>线下</td>
<td><a class="op-link" href="javascript:void(0)">查看</a> <a class="op-link" href="javascript:void(0)">下载</a></td>
<td><span class="status-tag status-pending">待审核</span></td>
<td>未结算</td>
<td class="col-num">ZH-2026-645</td>
<td class="col-remark" title="XXXXXX">XXXXXX</td>
<td>
<a class="op-link" onclick="openAudit(this)">审核</a>
<a class="op-link" onclick="goEdit(this)">修改</a>
<a class="op-link" onclick="openSettle(this)">结算</a>
</td>
</tr>
<tr>
<td><input type="checkbox" class="row-check"></td>
<td><a class="col-name" href="javascript:void(0)">公共卫生应急体系建设方案</a></td>
<td><span class="col-direction" title="专项计划一:《规范化诊疗及医疗质量提升专项计划(2026-2030年)》">专项计划一:《规范化诊疗及医疗质.....</span></td>
<td>对外交流</td>
<td>线上</td>
<td><a class="op-link" href="javascript:void(0)">查看</a> <a class="op-link" href="javascript:void(0)">下载</a></td>
<td><span class="status-tag status-done">审核通过</span></td>
<td>未结算</td>
<td class="col-num">ZH-2026-640</td>
<td class="col-remark" title="XXXXXX">XXXXXX</td>
<td>
<a class="op-link" onclick="openAudit(this)">审核</a>
<a class="op-link" onclick="goEdit(this)">修改</a>
<a class="op-link" onclick="openSettle(this)">结算</a>
</td>
</tr>
<tr>
<td><input type="checkbox" class="row-check"></td>
<td><a class="col-name" href="javascript:void(0)">数字化医疗转型方案</a></td>
<td><span class="col-direction" title="专项计划一:《规范化诊疗及医疗质量提升专项计划(2026-2030年)》">专项计划一:《规范化诊疗及医疗质.....</span></td>
<td>共识</td>
<td>线下</td>
<td><a class="op-link" href="javascript:void(0)">查看</a> <a class="op-link" href="javascript:void(0)">下载</a></td>
<td><span class="status-tag status-reject">已退回</span></td>
<td>未结算</td>
<td class="col-num">ZH-2026-635</td>
<td class="col-remark" title="XXXXXX">XXXXXX</td>
<td>
<a class="op-link" onclick="openAudit(this)">审核</a>
<a class="op-link" onclick="goEdit(this)">修改</a>
<a class="op-link" onclick="openSettle(this)">结算</a>
</td>
</tr>
</tbody>
</table>
</div>
</main>
<!-- Modal: 审核 (原型 u2764) -->
<div class="modal-mask" id="auditModal">
<div class="modal">
<div class="modal-header">
<span class="modal-title">项目设计投稿审核</span>
<button class="modal-close" onclick="closeModal('auditModal')">×</button>
</div>
<div class="modal-body">
<div class="modal-row">
<div class="modal-row-label">策划方案名称</div>
<div class="modal-row-value" id="auditName" style="font-weight:500"></div>
</div>
<div class="modal-row">
<label class="radio-row"><input type="radio" name="auditResult" value="pass" checked onchange="toggleAudit()">通过</label>
<label class="radio-row"><input type="radio" name="auditResult" value="reject" onchange="toggleAudit()">退回</label>
</div>
<div class="modal-row" id="auditCodeRow">
<div class="modal-row-label">项目编号</div>
<input type="text" class="modal-input" id="auditCode" placeholder="通过时必填">
</div>
<div class="modal-row">
<div class="modal-row-label">审核意见 (非必填)</div>
<textarea class="modal-textarea" id="auditOpinion" placeholder="请输入审核意见..."></textarea>
</div>
<p class="modal-hint">*通过时,项目编号为必填;退回时,不显示项目编号;审核意见为非必填</p>
</div>
<div class="modal-footer">
<button class="btn-default" onclick="closeModal('auditModal')">取消</button>
<button class="btn-primary" onclick="confirmAudit()">确认</button>
</div>
</div>
</div>
<!-- Modal: 结算 (原型 u2785) -->
<div class="modal-mask" id="settleModal">
<div class="modal">
<div class="modal-header">
<span class="modal-title">结算确认</span>
<button class="modal-close" onclick="closeModal('settleModal')">×</button>
</div>
<div class="modal-body">
<p style="font-size:16px;color:#1a1a1a;text-align:center;margin:20px 0">您确定要结算吗?</p>
</div>
<div class="modal-footer">
<button class="btn-default" onclick="closeModal('settleModal')">取消</button>
<button class="btn-primary" onclick="confirmSettle()">确定</button>
</div>
</div>
</div>
<script>
document.getElementById('selectAll').addEventListener('change', function() {
document.querySelectorAll('.row-check').forEach(cb => cb.checked = this.checked);
});
var auditTargetTr = null;
function openAudit(btn) {
auditTargetTr = btn.closest('tr');
document.getElementById('auditName').textContent = auditTargetTr.querySelector('.col-name').textContent;
document.querySelector('input[name="auditResult"][value="pass"]').checked = true;
document.getElementById('auditCode').value = auditTargetTr.querySelector('.col-num').textContent;
document.getElementById('auditOpinion').value = '';
toggleAudit();
document.getElementById('auditModal').classList.add('show');
}
function toggleAudit() {
var isPass = document.querySelector('input[name="auditResult"]:checked').value === 'pass';
document.getElementById('auditCodeRow').style.display = isPass ? 'block' : 'none';
}
function confirmAudit() {
var result = document.querySelector('input[name="auditResult"]:checked').value;
var code = document.getElementById('auditCode').value.trim();
if (result === 'pass' && !code) { alert('通过时项目编号为必填'); return; }
// 更新行内状态徽章
var tag = auditTargetTr.querySelector('.status-tag');
tag.className = 'status-tag ' + (result === 'pass' ? 'status-done' : 'status-reject');
tag.textContent = result === 'pass' ? '审核通过' : '已退回';
alert('审核' + (result === 'pass' ? '通过' : '退回') + '成功');
closeModal('auditModal');
}
var settleTargetTr = null;
function openSettle(btn) {
settleTargetTr = btn.closest('tr');
document.getElementById('settleModal').classList.add('show');
}
function confirmSettle() {
var cell = settleTargetTr.children[7]; // 是否结算列
cell.textContent = '已结算';
alert('结算成功');
closeModal('settleModal');
}
function goEdit(btn) {
var tr = btn.closest('tr');
var code = tr.querySelector('.col-num').textContent;
navigateTo('edit-project-plan.html?code=' + encodeURIComponent(code));
}
function navigateTo(href) {
try {
if (window.parent && window.parent.document.getElementById('contentFrame')) {
window.parent.document.getElementById('contentFrame').src = href;
} else {
window.location.href = href;
}
} catch (e) { window.location.href = href; }
}
function openBatchAudit() {
var checked = document.querySelectorAll('.row-check:checked').length;
if (!checked) { alert('请先勾选项目'); return; }
auditTargetTr = null;
document.getElementById('auditName').textContent = '批量审核 (' + checked + ' 个项目)';
document.querySelector('input[name="auditResult"][value="pass"]').checked = true;
document.getElementById('auditCode').value = '';
document.getElementById('auditOpinion').value = '';
toggleAudit();
document.getElementById('auditModal').classList.add('show');
}
function closeModal(id) { document.getElementById(id).classList.remove('show'); }
document.querySelectorAll('.modal-mask').forEach(m => m.addEventListener('click', function(e) { if (e.target === this) closeModal(this.id); }));
</script>
</body>
</html>