feat: 结算设备复秤对账(03收银页对账条+08补齐明细智能推荐) + 营养管理端报表中心两页面补齐(场所经营分析/场所目标考核)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
fengpu
2026-08-20 17:02:25 +08:00
co-authored by Claude Opus 4.7
parent ee6041257f
commit dd0331bb98
6 changed files with 1549 additions and 26 deletions
+600 -8
View File
@@ -2,6 +2,7 @@
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>营养管理 - 场所经营分析 | 健康CQ原型</title>
<style>
*{box-sizing:border-box;margin:0;padding:0;}
@@ -40,10 +41,128 @@ body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei
.sidebar-subgroup.open .sidebar-subgroup__body{display:block;}
/* 主区域 */
.main{margin-left:240px;margin-top:56px;padding:24px;min-height:calc(100vh - 56px);}
/* 页面占位 */
.page-placeholder{background:#fff;border-radius:var(--radius-md);box-shadow:var(--shadow-sm);padding:48px 24px;text-align:center;}
.page-placeholder__title{font-size:var(--font-xl);font-weight:600;color:var(--text-primary);margin-bottom:8px;}
.page-placeholder__hint{font-size:var(--font-sm);color:var(--text-placeholder);}
/* 页面信息栏(食堂切换区) */
.page-header-bar{display:flex;align-items:center;justify-content:space-between;background:#fff;border-radius:var(--radius-md);padding:16px 24px;margin-bottom:16px;box-shadow:var(--shadow-sm);}
.page-header-bar__left{display:flex;align-items:baseline;gap:12px;}
.page-header-bar__title{font-size:var(--font-lg);font-weight:700;color:var(--text-primary);}
.page-header-bar__meta{font-size:var(--font-sm);color:var(--text-placeholder);}
.page-header-bar__right{display:flex;align-items:center;gap:12px;}
.page-header-bar__right label{font-size:var(--font-sm);color:var(--text-secondary);white-space:nowrap;}
.canteen-select{height:36px;border:2px solid var(--primary);border-radius:var(--radius-sm);padding:0 32px 0 14px;font-size:var(--font-sm);color:var(--primary);font-weight:500;outline:none;background:#fff url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath d='M0 0l5 6 5-6z' fill='%231890FF'/%3E%3C/svg%3E") no-repeat right 12px center;appearance:none;cursor:pointer;min-width:150px;}
/* 筛选栏 */
.filter-bar{display:flex;align-items:center;gap:14px;background:#fff;padding:16px 20px;border-radius:var(--radius-md);margin-bottom:16px;box-shadow:var(--shadow-sm);flex-wrap:wrap;}
.filter-item{display:flex;align-items:center;gap:8px;}
.filter-item label{font-size:var(--font-sm);color:var(--text-secondary);white-space:nowrap;}
.filter-input,.filter-select{height:34px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 12px;font-size:var(--font-sm);color:var(--text-primary);outline:none;background:#fff;min-width:140px;}
.filter-select{padding-right:28px;background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath d='M0 0l5 6 5-6z' fill='%23999'/%3E%3C/svg%3E");background-repeat:no-repeat;background-position:right 10px center;appearance:none;cursor:pointer;}
.filter-input:focus,.filter-select:focus{border-color:var(--primary);}
.filter-search{position:relative;}
.filter-search input{height:34px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 12px 0 30px;font-size:var(--font-sm);outline:none;background:#fff url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='14' height='14' fill='none'%3E%3Ccircle cx='6' cy='6' r='4.5' stroke='%23999' stroke-width='1.2'/%3E%3Cpath d='M9.5 9.5L12.5 12.5' stroke='%23999' stroke-width='1.2' stroke-linecap='round'/%3E%3C/svg%3E") no-repeat 10px center;width:220px;}
.filter-search input:focus{border-color:var(--primary);}
/* 范围筛选 */
.range-box{display:flex;align-items:center;gap:4px;height:34px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 8px;background:#fff;}
.range-box:focus-within{border-color:var(--primary);}
.range-input{border:none;outline:none;width:64px;height:100%;font-size:var(--font-sm);color:var(--text-primary);background:transparent;}
.range-input::-webkit-inner-spin-button,.range-input::-webkit-outer-spin-button{-webkit-appearance:none;margin:0;}
.range-input{-moz-appearance:textfield;}
.range-sep{color:var(--text-placeholder);font-size:var(--font-sm);}
.range-unit{color:var(--text-placeholder);font-size:var(--font-xs);white-space:nowrap;}
.btn{height:34px;padding:0 16px;border-radius:var(--radius-sm);font-size:var(--font-sm);cursor:pointer;border:none;transition:all .2s;display:inline-flex;align-items:center;gap:6px;}
.btn-primary{background:var(--primary);color:#fff;}.btn-primary:hover{background:#40a9ff;}
.btn-default{background:#fff;color:var(--text-primary);border:1px solid var(--border);}.btn-default:hover{color:var(--primary);border-color:var(--primary);}
/* 操作栏 */
.action-bar{display:flex;align-items:center;gap:8px;margin-bottom:16px;}
/* 表格卡片 */
.table-card{background:#fff;border-radius:var(--radius-md);box-shadow:var(--shadow-sm);overflow:hidden;}
.table-card__header{display:flex;align-items:center;justify-content:space-between;padding:16px 24px;border-bottom:1px solid var(--border);}
.table-card__title{font-size:var(--font-base);font-weight:600;}
.table-card__content{overflow-x:auto;overflow-y:hidden;-webkit-overflow-scrolling:touch;}
.table-card__content table{min-width:100%;white-space:nowrap;border-collapse:collapse;width:100%;}
/* 双表头 */
thead th{font-size:var(--font-sm);color:var(--text-secondary);font-weight:500;border-bottom:1px solid var(--border);white-space:nowrap;padding:11px 14px;text-align:center;}
thead th.group-th{background:#EDF4FF;color:#1d39c4;font-weight:600;border-bottom:1px solid var(--border);border-right:1px solid #E0EAFA;padding:10px 14px;}
thead th.group-th:last-of-type{border-right:none;}
thead tr.sub-row th{background:#FAFAFA;border-right:1px solid #F0F0F0;padding:10px 14px;}
tbody td{padding:13px 14px;font-size:var(--font-sm);color:var(--text-primary);border-bottom:1px solid var(--border);white-space:nowrap;text-align:center;}
tbody tr:last-child td{border-bottom:none;}
tbody tr:hover td{background:#F8FBFF;}
td.td-left{text-align:left;}
/* 数值强调 */
.num{font-variant-numeric:tabular-nums;font-weight:500;}
.num--money{color:#CF1322;}
.num--rate{color:#389E0D;}
/* 备注列:固定列宽 + 省略号 + 悬浮查看 */
.note-cell{max-width:150px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;text-align:left;cursor:help;}
/* 状态标签 */
.tag{display:inline-flex;align-items:center;padding:2px 8px;border-radius:10px;font-size:var(--font-xs);}
.tag-green{background:#F6FFED;color:var(--success);}
.tag-gray{background:#F5F5F5;color:#999;}
.tag-orange{background:#FFF7E6;color:#D46B08;}
.link{color:var(--primary);cursor:pointer;font-size:var(--font-sm);text-decoration:none;margin:0 6px;}.link:hover{color:#40a9ff;}
/* 操作列固定右侧 */
thead th.th-actions,tbody td.td-actions{position:sticky;right:0;background:#fff;box-shadow:-4px 0 8px rgba(0,0,0,0.04);z-index:3;white-space:nowrap;}
thead th.th-actions{background:#FAFAFA;z-index:4;}
tbody tr:hover td.td-actions{background:#F8FBFF;}
/* 分页 */
.pagination{display:flex;align-items:center;justify-content:flex-end;padding:16px 24px;gap:8px;border-top:1px solid var(--border);}
.pagination span{font-size:var(--font-sm);color:var(--text-secondary);}
.page-btn{width:32px;height:32px;border:1px solid var(--border);border-radius:var(--radius-sm);background:#fff;cursor:pointer;font-size:var(--font-sm);display:inline-flex;align-items:center;justify-content:center;transition:all .2s;}
.page-btn:hover,.page-btn.active{background:var(--primary);color:#fff;border-color:var(--primary);}
/* 弹窗通用 */
.modal-overlay{position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.45);z-index:1000;display:flex;align-items:center;justify-content:center;}
.modal{background:#fff;border-radius:8px;max-height:88vh;overflow-y:auto;box-shadow:0 8px 24px rgba(0,0,0,0.16);}
.modal-header{display:flex;align-items:center;justify-content:space-between;padding:16px 24px;border-bottom:1px solid #e8e8e8;}
.modal-title{font-size:16px;font-weight:600;color:#262626;}
.modal-close{font-size:22px;cursor:pointer;color:#8c8c8c;line-height:1;}.modal-close:hover{color:#262626;}
.modal-body{padding:24px;}
.modal-footer{display:flex;justify-content:flex-end;gap:12px;padding:16px 24px;border-top:1px solid #e8e8e8;}
.detail-info{display:grid;grid-template-columns:1fr 1fr 1fr;gap:12px 24px;margin-bottom:20px;padding:16px;background:#FAFAFA;border-radius:var(--radius-md);}
.detail-info__item{display:flex;align-items:center;gap:6px;font-size:var(--font-sm);}
.detail-info__label{color:var(--text-secondary);}
.detail-info__value{color:var(--text-primary);font-weight:500;}
.form-item{display:flex;flex-direction:column;gap:6px;margin-bottom:16px;}
.form-item label{font-size:var(--font-sm);color:var(--text-secondary);}
.form-item input,.form-item textarea{border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 12px;font-size:var(--font-sm);outline:none;background:#fff;width:100%;}
.form-item input{height:36px;}
.form-item textarea{height:72px;padding:10px 12px;resize:none;font-family:inherit;line-height:1.6;}
.form-item input:focus,.form-item textarea:focus{border-color:var(--primary);}
.modal-table{width:100%;border-collapse:collapse;font-size:var(--font-sm);}
.modal-table thead th{background:#FAFAFA;padding:10px 12px;text-align:left;color:var(--text-secondary);font-weight:500;border-bottom:1px solid var(--border);}
.modal-table tbody td{padding:10px 12px;border-bottom:1px solid var(--border);color:var(--text-primary);}
/* ===== 详情报表(正式报表样式) ===== */
.report-modal{width:900px;}
.report-toolbar{display:flex;align-items:center;gap:10px;}
.report-toolbar .btn{height:32px;}
.report-body{background:#fff;}
.report-title{text-align:center;font-size:22px;font-weight:700;color:#1d1d1f;letter-spacing:3px;margin-bottom:6px;}
.report-subtitle{text-align:center;font-size:var(--font-sm);color:var(--text-placeholder);letter-spacing:1px;margin-bottom:6px;}
.report-divider{height:2px;background:linear-gradient(90deg,transparent,#1890FF,transparent);margin-bottom:20px;}
.report-meta{display:grid;grid-template-columns:1fr 1fr 1fr 1fr;gap:12px;padding:14px 16px;background:#F7F9FC;border:1px solid var(--border);border-radius:var(--radius-sm);margin-bottom:20px;font-size:var(--font-sm);}
.report-meta__item{display:flex;align-items:center;gap:6px;}
.report-meta__item label{color:var(--text-secondary);white-space:nowrap;}
.report-meta__item span{color:var(--text-primary);font-weight:500;}
.report-section{margin-bottom:18px;}
.report-section__title{font-size:var(--font-base);font-weight:600;color:var(--text-primary);padding:8px 12px;background:#EDF4FF;border-left:3px solid var(--primary);border-radius:2px;margin-bottom:10px;}
.report-table{width:100%;border-collapse:collapse;font-size:var(--font-sm);border:1px solid var(--border);}
.report-table th{background:#FAFAFA;padding:9px 12px;text-align:center;color:var(--text-secondary);font-weight:500;border:1px solid var(--border);white-space:nowrap;}
.report-table td{padding:9px 12px;text-align:center;color:var(--text-primary);border:1px solid var(--border);}
.report-table td.report-table__label{background:#FAFAFA;color:var(--text-secondary);text-align:left;width:160px;white-space:nowrap;}
.report-table td.report-note-cell{text-align:left;white-space:normal;word-break:break-word;line-height:1.7;vertical-align:top;min-height:42px;}
.report-sign{margin-top:26px;display:flex;justify-content:space-between;gap:24px;font-size:var(--font-sm);color:var(--text-secondary);padding-top:16px;border-top:1px dashed #ccc;}
.report-sign__item{display:flex;flex-direction:column;gap:6px;}
.report-sign__line{width:140px;border-bottom:1px solid #999;height:24px;}
.report-note{margin-top:16px;padding:12px 16px;background:#FFFBE6;border:1px solid #FFE58F;border-radius:var(--radius-sm);font-size:var(--font-sm);color:#874D00;line-height:1.6;}
.report-note b{color:#D46B08;}
/* Toast */
.toast{position:fixed;top:80px;left:50%;transform:translateX(-50%) translateY(-20px);background:rgba(0,0,0,0.75);color:#fff;padding:10px 24px;border-radius:var(--radius-md);font-size:var(--font-sm);opacity:0;pointer-events:none;transition:all .3s;z-index:2000;}
.toast.show{opacity:1;transform:translateX(-50%) translateY(0);}
/* 打印:仅输出详情报表 */
@media print{
.top-bar,.sidebar,.main,.modal-overlay:not(#detailOverlay){display:none !important;}
#detailOverlay{position:static;background:#fff;display:block !important;}
#detailOverlay .modal{max-height:none;overflow:visible;box-shadow:none;width:100%;border-radius:0;}
#detailOverlay .report-toolbar,#detailOverlay .modal-close{display:none !important;}
}
</style>
</head>
<body>
@@ -142,17 +261,490 @@ body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei
<!-- 主内容 -->
<main class="main">
<div class="page-placeholder">
<div class="page-placeholder__title">场所经营分析</div>
<div class="page-placeholder__hint">页面功能待补充</div>
<!-- ① 食堂切换区 -->
<div class="page-header-bar">
<div class="page-header-bar__left">
<span class="page-header-bar__title">场所经营分析</span>
<span class="page-header-bar__meta">按日统计各场所营收数据 · 数据实时更新</span>
</div>
<div class="page-header-bar__right">
<label>当前场所</label>
<select class="canteen-select" id="globalPlace">
<option value="全部">全部场所</option>
<option value="一场所(东区)">一场所(东区)</option>
<option value="二场所(西区)">二场所(西区)</option>
<option value="三场所(北区)">三场所(北区)</option>
</select>
</div>
</div>
<!-- ② 搜索区 -->
<div class="filter-bar">
<div class="filter-item"><label>统计日期</label><input class="filter-input" type="date" value="2026-08-20"></div>
<div class="filter-item">
<label>取餐总额</label>
<div class="range-box">
<input class="range-input" type="number" placeholder="最小">
<span class="range-sep"></span>
<input class="range-input" type="number" placeholder="最大">
<span class="range-unit"></span>
</div>
</div>
<div class="filter-item">
<label>取餐笔数</label>
<div class="range-box">
<input class="range-input" type="number" placeholder="最小">
<span class="range-sep"></span>
<input class="range-input" type="number" placeholder="最大">
<span class="range-unit"></span>
</div>
</div>
<div class="filter-item">
<label>实收金额</label>
<div class="range-box">
<input class="range-input" type="number" placeholder="最小">
<span class="range-sep"></span>
<input class="range-input" type="number" placeholder="最大">
<span class="range-unit"></span>
</div>
</div>
<div class="filter-item">
<label>毛利额</label>
<div class="range-box">
<input class="range-input" type="number" placeholder="最小">
<span class="range-sep"></span>
<input class="range-input" type="number" placeholder="最大">
<span class="range-unit"></span>
</div>
</div>
<button class="btn btn-primary" onclick="showToast('查询成功')">🔍 查询</button>
<button class="btn btn-default" onclick="resetFilter(this)">⟳ 重置</button>
</div>
<!-- ③ 操作栏 -->
<div class="action-bar">
<button class="btn btn-primary" onclick="showToast('导出任务已创建,请至「查看导出任务」下载')">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none"><path d="M2 10v2h10v-2M7 2v7M4.5 6.5L7 9l2.5-2.5" stroke="#fff" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"/></svg>导出列表数据
</button>
<button class="btn btn-default" onclick="openExportModal()">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none"><rect x="2" y="2" width="10" height="10" rx="1.5" stroke="currentColor" stroke-width="1.4"/><path d="M4 5h6M4 7.5h4" stroke="currentColor" stroke-width="1.2" stroke-linecap="round"/></svg>查看导出任务
</button>
</div>
<!-- ④ 列表区(双表头) -->
<div class="table-card">
<div class="table-card__header"><div class="table-card__title">场所营收明细</div></div>
<div class="table-card__content">
<table id="dataTable">
<thead>
<tr>
<th class="group-th" colspan="3">基础门店信息</th>
<th class="group-th" colspan="5">客流底盘指标</th>
<th class="group-th" colspan="4">营收单价指标</th>
<th class="group-th" colspan="4">资金结算风控</th>
<th class="group-th" colspan="4">盈利成本核心</th>
<th class="group-th" colspan="2">运营管理字段</th>
<th class="th-actions" rowspan="2">操作</th>
</tr>
<tr class="sub-row">
<th>场所名称</th><th>统计日期</th><th>营业时段</th>
<th>取餐笔数</th><th>就餐人数</th><th>取餐总份数</th><th>单人平均取餐份数</th><th>翻台率</th>
<th>取餐总额</th><th>笔单价</th><th>人均消费</th><th>优惠减免金额</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 data-place="一场所(东区)">
<td class="td-left">一场所(东区)</td><td>2026-08-20</td><td>午餐</td>
<td class="num">1,286</td><td class="num">892</td><td class="num">3,340</td><td class="num">3.74</td><td class="num">2.6</td>
<td class="num num--money">¥30,580.00</td><td class="num">¥23.78</td><td class="num">¥34.28</td><td class="num">¥1,380.00</td>
<td class="num num--money">¥29,200.00</td><td class="num">¥0.00</td><td class="num num--rate">100%</td><td class="td-left td-reason"><div class="note-cell"></div></td>
<td class="num">¥19,500.00</td><td class="num">63.8%</td><td class="num num--rate">¥9,700.00</td><td class="num num--rate">31.7%</td>
<td class="td-auditor">王丽华</td><td class="td-abnormal td-left"><div class="note-cell"></div></td>
<td class="td-actions"><a class="link" onclick="openAudit(this)">报表审计</a><a class="link" onclick="openDetail(this)">详情</a></td>
</tr>
<tr data-place="一场所(东区)">
<td class="td-left">一场所(东区)</td><td>2026-08-20</td><td>晚餐</td>
<td class="num">762</td><td class="num">521</td><td class="num">1,980</td><td class="num">3.80</td><td class="num">1.5</td>
<td class="num num--money">¥18,120.00</td><td class="num">¥23.78</td><td class="num">¥34.78</td><td class="num">¥820.00</td>
<td class="num num--money">¥17,300.00</td><td class="num">¥0.00</td><td class="num num--rate">100%</td><td class="td-left td-reason"><div class="note-cell"></div></td>
<td class="num">¥11,600.00</td><td class="num">64.0%</td><td class="num num--rate">¥5,700.00</td><td class="num num--rate">31.5%</td>
<td class="td-auditor"><span class="tag tag-gray">未审核</span></td><td class="td-abnormal td-left"><div class="note-cell"></div></td>
<td class="td-actions"><a class="link" onclick="openAudit(this)">报表审计</a><a class="link" onclick="openDetail(this)">详情</a></td>
</tr>
<tr data-place="二场所(西区)">
<td class="td-left">二场所(西区)</td><td>2026-08-20</td><td>早餐,午餐</td>
<td class="num">1,543</td><td class="num">1,042</td><td class="num">4,050</td><td class="num">3.89</td><td class="num">2.9</td>
<td class="num num--money">¥36,780.00</td><td class="num">¥23.84</td><td class="num">¥35.30</td><td class="num">¥1,680.00</td>
<td class="num num--money">¥35,100.00</td><td class="num">¥120.00</td><td class="num num--rate">99.7%</td><td class="td-left td-reason"><div class="note-cell">2笔餐券补录延迟,已通知收银员次日补录</div></td>
<td class="num">¥23,600.00</td><td class="num">64.2%</td><td class="num num--rate">¥11,500.00</td><td class="num num--rate">31.3%</td>
<td class="td-auditor">李建军</td><td class="td-abnormal td-left"><div class="note-cell">晚餐时段客流超预期,已临时加派两个打餐窗口,食材供应充足</div></td>
<td class="td-actions"><a class="link" onclick="openAudit(this)">报表审计</a><a class="link" onclick="openDetail(this)">详情</a></td>
</tr>
<tr data-place="二场所(西区)">
<td class="td-left">二场所(西区)</td><td>2026-08-20</td><td>晚餐</td>
<td class="num">918</td><td class="num">610</td><td class="num">2,380</td><td class="num">3.90</td><td class="num">1.8</td>
<td class="num num--money">¥22,050.00</td><td class="num">¥24.02</td><td class="num">¥36.15</td><td class="num">¥960.00</td>
<td class="num num--money">¥21,090.00</td><td class="num">¥0.00</td><td class="num num--rate">100%</td><td class="td-left td-reason"><div class="note-cell"></div></td>
<td class="num">¥14,300.00</td><td class="num">64.9%</td><td class="num num--rate">¥6,790.00</td><td class="num num--rate">30.8%</td>
<td class="td-auditor"><span class="tag tag-gray">未审核</span></td><td class="td-abnormal td-left"><div class="note-cell"></div></td>
<td class="td-actions"><a class="link" onclick="openAudit(this)">报表审计</a><a class="link" onclick="openDetail(this)">详情</a></td>
</tr>
<tr data-place="三场所(北区)">
<td class="td-left">三场所(北区)</td><td>2026-08-20</td><td>午餐</td>
<td class="num">1,102</td><td class="num">758</td><td class="num">2,860</td><td class="num">3.77</td><td class="num">2.3</td>
<td class="num num--money">¥26,340.00</td><td class="num">¥23.90</td><td class="num">¥34.75</td><td class="num">¥1,150.00</td>
<td class="num num--money">¥25,190.00</td><td class="num">¥200.00</td><td class="num num--rate">99.2%</td><td class="td-left td-reason"><div class="note-cell">现金盘点差异200元,待财务核实后调整入账</div></td>
<td class="num">¥16,900.00</td><td class="num">64.2%</td><td class="num num--rate">¥8,290.00</td><td class="num num--rate">31.5%</td>
<td class="td-auditor">陈晓东</td><td class="td-abnormal td-left"><div class="note-cell">现金盘点差异200元,已提交财务核实,暂不影响当日结算</div></td>
<td class="td-actions"><a class="link" onclick="openAudit(this)">报表审计</a><a class="link" onclick="openDetail(this)">详情</a></td>
</tr>
<tr data-place="三场所(北区)">
<td class="td-left">三场所(北区)</td><td>2026-08-20</td><td>午餐,晚餐</td>
<td class="num">635</td><td class="num">428</td><td class="num">1,620</td><td class="num">3.79</td><td class="num">1.3</td>
<td class="num num--money">¥14,980.00</td><td class="num">¥23.59</td><td class="num">¥35.00</td><td class="num">¥680.00</td>
<td class="num num--money">¥14,300.00</td><td class="num">¥0.00</td><td class="num num--rate">100%</td><td class="td-left td-reason"><div class="note-cell"></div></td>
<td class="num">¥9,650.00</td><td class="num">64.4%</td><td class="num num--rate">¥4,650.00</td><td class="num num--rate">31.0%</td>
<td class="td-auditor"><span class="tag tag-gray">未审核</span></td><td class="td-abnormal td-left"><div class="note-cell"></div></td>
<td class="td-actions"><a class="link" onclick="openAudit(this)">报表审计</a><a class="link" onclick="openDetail(this)">详情</a></td>
</tr>
</tbody>
</table>
</div>
<div class="pagination">
<span>显示 6 条,共 6 条</span>
<button class="page-btn"></button>
<button class="page-btn active">1</button>
<button class="page-btn"></button>
</div>
</div>
</main>
<!-- 报表审计弹窗 -->
<div class="modal-overlay" id="auditOverlay" style="display:none;">
<div class="modal" style="width:560px;">
<div class="modal-header">
<span class="modal-title">报表审计</span>
<span class="modal-close" onclick="closeAudit()">&times;</span>
</div>
<div class="modal-body">
<div class="detail-info">
<div class="detail-info__item"><span class="detail-info__label">场所名称:</span><span class="detail-info__value" id="auditPlace"></span></div>
<div class="detail-info__item"><span class="detail-info__label">统计日期:</span><span class="detail-info__value" id="auditDate"></span></div>
<div class="detail-info__item"><span class="detail-info__label">营业时段:</span><span class="detail-info__value" id="auditMeal"></span></div>
</div>
<div class="form-item">
<label>审核人</label>
<input type="text" id="auditName" placeholder="请输入审核人姓名">
</div>
<div class="form-item">
<label>差额成因备注</label>
<textarea id="auditReason" placeholder="请输入未结算差额的成因说明(可选)"></textarea>
</div>
<div class="form-item" style="margin-bottom:0;">
<label>经营异常备注</label>
<textarea id="auditNote" placeholder="请输入经营异常情况说明(可选)"></textarea>
</div>
</div>
<div class="modal-footer">
<button class="btn btn-default" onclick="closeAudit()">取消</button>
<button class="btn btn-primary" onclick="confirmAudit()">确认审计</button>
</div>
</div>
</div>
<!-- 详情报表弹窗 -->
<div class="modal-overlay" id="detailOverlay" style="display:none;">
<div class="modal report-modal">
<div class="modal-header">
<span class="modal-title">经营分析报表</span>
<div style="display:flex;align-items:center;gap:12px;">
<div class="report-toolbar">
<button class="btn btn-primary" onclick="showToast('正在导出 PDF...')">📄 导出 PDF</button>
<button class="btn btn-default" onclick="window.print()">🖨️ 打印</button>
</div>
<span class="modal-close" onclick="closeDetail()">&times;</span>
</div>
</div>
<div class="modal-body report-body">
<div class="report-title">场所经营分析报表</div>
<div class="report-subtitle">DAILY PLACE BUSINESS ANALYSIS REPORT</div>
<div class="report-divider"></div>
<div class="report-meta">
<div class="report-meta__item"><label>报表编号:</label><span>RPT-20260820-001</span></div>
<div class="report-meta__item"><label>统计周期:</label><span id="rptDate">2026-08-20</span></div>
<div class="report-meta__item"><label>营业时段:</label><span id="rptMeal">午餐</span></div>
<div class="report-meta__item"><label>生成时间:</label><span>2026-08-20 18:30</span></div>
</div>
<div class="report-section">
<div class="report-section__title">基础门店信息</div>
<table class="report-table">
<tr>
<td class="report-table__label">场所名称</td><td id="rptPlace">一场所(东区)</td>
<td class="report-table__label">统计日期</td><td id="rptDate2">2026-08-20</td>
<td class="report-table__label">营业时段</td><td id="rptMeal2">午餐</td>
</tr>
</table>
</div>
<div class="report-section">
<div class="report-section__title">客流底盘指标</div>
<table class="report-table">
<tr>
<th>取餐笔数</th><th>就餐人数</th><th>取餐总份数</th><th>单人平均取餐份数</th><th>翻台率</th>
</tr>
<tr>
<td id="rptC1"></td><td id="rptC2"></td><td id="rptC3"></td><td id="rptC4"></td><td id="rptC5"></td>
</tr>
</table>
</div>
<div class="report-section">
<div class="report-section__title">营收单价指标</div>
<table class="report-table">
<tr>
<th>取餐总额</th><th>笔单价</th><th>人均消费</th><th>优惠减免金额</th>
</tr>
<tr>
<td id="rptC6"></td><td id="rptC7"></td><td id="rptC8"></td><td id="rptC9"></td>
</tr>
</table>
</div>
<div class="report-section">
<div class="report-section__title">资金结算风控</div>
<table class="report-table">
<tr>
<th>实收金额</th><th>未结算差额</th><th>结算完成率</th>
</tr>
<tr>
<td id="rptC10"></td><td id="rptC11"></td><td id="rptC12"></td>
</tr>
<tr>
<td class="report-table__label">差额成因备注</td>
<td id="rptC13" colspan="2" class="report-note-cell"></td>
</tr>
</table>
</div>
<div class="report-section">
<div class="report-section__title">盈利成本核心</div>
<table class="report-table">
<tr>
<th>当日食材耗用总成本</th><th>食材成本率</th><th>毛利额</th><th>毛利率</th>
</tr>
<tr>
<td id="rptC14"></td><td id="rptC15"></td><td id="rptC16"></td><td id="rptC17"></td>
</tr>
</table>
</div>
<div class="report-section">
<div class="report-section__title">运营管理字段</div>
<table class="report-table">
<tr>
<td class="report-table__label">审核人</td>
<td id="rptAuditor" colspan="3"></td>
</tr>
<tr>
<td class="report-table__label">经营异常备注</td>
<td id="rptAbnormal" colspan="3" class="report-note-cell"></td>
</tr>
</table>
</div>
<div class="report-note" id="rptNote" style="display:none;"><b>经营异常:</b><span id="rptNoteText"></span></div>
<div class="report-sign">
<div class="report-sign__item"><span>审核人签字:</span><span class="report-sign__line"></span></div>
<div class="report-sign__item"><span>经营负责人签字:</span><span class="report-sign__line"></span></div>
<div class="report-sign__item"><span>打印日期:2026-08-20</span></div>
</div>
</div>
</div>
</div>
<!-- 导出任务弹窗 -->
<div class="modal-overlay" id="exportOverlay" style="display:none;">
<div class="modal" style="width:640px;">
<div class="modal-header">
<span class="modal-title">导出任务列表</span>
<span class="modal-close" onclick="closeExportModal()">&times;</span>
</div>
<div class="modal-body">
<table class="modal-table">
<thead><tr><th>任务ID</th><th>导出内容</th><th>创建时间</th><th>状态</th><th>操作</th></tr></thead>
<tbody>
<tr><td>EXP-20260820-001</td><td>场所经营分析列表</td><td>2026-08-20 09:12</td><td><span class="tag tag-green">已完成</span></td><td><span class="link" onclick="showToast('开始下载文件')">下载</span></td></tr>
<tr><td>EXP-20260820-002</td><td>场所经营分析列表</td><td>2026-08-20 10:35</td><td><span class="tag tag-orange">处理中</span></td><td><span class="link" style="color:#999;cursor:default;">下载</span></td></tr>
<tr><td>EXP-20260819-003</td><td>场所经营分析列表</td><td>2026-08-19 16:48</td><td><span class="tag tag-green">已完成</span></td><td><span class="link" onclick="showToast('开始下载文件')">下载</span></td></tr>
</tbody>
</table>
</div>
<div class="modal-footer">
<button class="btn btn-default" onclick="closeExportModal()">关闭</button>
</div>
</div>
</div>
<!-- Toast -->
<div class="toast" id="toast"></div>
<script>
/* 侧边栏子组折叠 */
/* ========== 侧边栏子组折叠 ========== */
function toggleSubgroup(header) {
header.parentElement.classList.toggle('open');
}
/* 自动展开当前激活项所在子组 */
(function(){
var active = document.querySelector('.sidebar-item.active');
if (active) { var sub = active.closest('.sidebar-subgroup'); if (sub) sub.classList.add('open'); }
})();
/* ========== Toast ========== */
function showToast(msg){
var el = document.getElementById('toast');
el.textContent = msg;
el.classList.add('show');
clearTimeout(el._t);
el._t = setTimeout(function(){ el.classList.remove('show'); }, 2000);
}
/* ========== 备注列 title 初始化(悬浮查看全部) ========== */
function initNoteTitles(){
document.querySelectorAll('#dataTable .note-cell').forEach(function(cell){
var t = cell.textContent.trim();
cell.title = (t === '—') ? '' : t;
});
}
/* ========== 食堂切换区 ========== */
document.getElementById('globalPlace').addEventListener('change', function(){
var val = this.value;
var rows = document.querySelectorAll('#dataTable tbody tr');
var visible = 0;
rows.forEach(function(tr){
var show = (val === '全部') || (tr.getAttribute('data-place') === val);
tr.style.display = show ? '' : 'none';
if (show) visible++;
});
showToast(val === '全部' ? '已显示全部场所数据' : ('已切换至「' + val + '」,共 ' + visible + ' 条记录'));
});
/* ========== 筛选重置 ========== */
function resetFilter(btn){
var bar = btn.closest('.filter-bar');
bar.querySelectorAll('input').forEach(function(i){ i.value = ''; });
bar.querySelectorAll('select').forEach(function(s){ s.selectedIndex = 0; });
showToast('筛选条件已重置');
}
/* ========== 备注单元格写入(同步省略号 title ========== */
function setNoteCell(td, val){
var text = val && val.trim() ? val.trim() : '—';
var cell = td.querySelector('.note-cell');
if (cell) {
cell.textContent = text;
cell.title = (text === '—') ? '' : text;
} else {
td.textContent = text;
td.title = (text === '—') ? '' : text;
}
}
function getNoteText(td){
var cell = td.querySelector('.note-cell');
return cell ? cell.textContent.trim() : td.textContent.trim();
}
/* ========== 报表审计 ========== */
var auditTargetRow = null;
function openAudit(link){
auditTargetRow = link.closest('tr');
var tds = auditTargetRow.querySelectorAll('td');
document.getElementById('auditPlace').textContent = tds[0].textContent;
document.getElementById('auditDate').textContent = tds[1].textContent;
document.getElementById('auditMeal').textContent = tds[2].textContent;
// 预填差额成因备注(第15列)
var reason = getNoteText(tds[15]);
document.getElementById('auditReason').value = (reason === '—') ? '' : reason;
// 预填审核人(第20列)
var auditor = tds[20].textContent;
document.getElementById('auditName').value = (auditor.indexOf('未审核') > -1) ? '' : auditor;
// 预填经营异常备注(第21列)
var abnormal = getNoteText(tds[21]);
document.getElementById('auditNote').value = (abnormal === '—') ? '' : abnormal;
document.getElementById('auditOverlay').style.display = 'flex';
}
function closeAudit(){ document.getElementById('auditOverlay').style.display = 'none'; }
function confirmAudit(){
var name = document.getElementById('auditName').value.trim();
if (!name) { showToast('请填写审核人姓名'); return; }
var reason = document.getElementById('auditReason').value;
var note = document.getElementById('auditNote').value;
if (auditTargetRow) {
var tds = auditTargetRow.querySelectorAll('td');
setNoteCell(tds[15], reason); // 差额成因备注
tds[20].textContent = name; // 审核人
setNoteCell(tds[21], note); // 经营异常备注
}
closeAudit();
showToast('审核完成,已回填审核人、差额成因备注与经营异常备注');
}
document.getElementById('auditOverlay').addEventListener('click', function(e){ if (e.target === this) closeAudit(); });
/* ========== 详情报表 ========== */
function openDetail(link){
var tr = link.closest('tr');
var tds = tr.querySelectorAll('td');
var place = tds[0].textContent, date = tds[1].textContent, meal = tds[2].textContent;
var v = [];
for (var i = 3; i <= 21; i++) v.push(tds[i].textContent.trim());
// v[0..4]=客流(5) v[5..8]=营收(4) v[9..12]=结算(4) v[13..16]=成本(4) v[17]=审核人 v[18]=经营异常备注
document.getElementById('rptPlace').textContent = place;
document.getElementById('rptDate').textContent = date;
document.getElementById('rptDate2').textContent = date;
document.getElementById('rptMeal').textContent = meal;
document.getElementById('rptMeal2').textContent = meal;
for (var i = 0; i < 17; i++) {
document.getElementById('rptC' + (i + 1)).textContent = v[i];
}
var auditor = v[17], abnormal = v[18];
document.getElementById('rptAuditor').textContent = (auditor.indexOf('未审核') > -1) ? '—' : auditor;
document.getElementById('rptAbnormal').textContent = (abnormal === '—') ? '—' : abnormal;
var note = document.getElementById('rptNote');
if (abnormal && abnormal !== '—') {
note.style.display = '';
document.getElementById('rptNoteText').textContent = abnormal;
} else {
note.style.display = 'none';
}
document.getElementById('detailOverlay').style.display = 'flex';
}
function closeDetail(){ document.getElementById('detailOverlay').style.display = 'none'; }
document.getElementById('detailOverlay').addEventListener('click', function(e){ if (e.target === this) closeDetail(); });
/* ========== 导出任务弹窗 ========== */
function openExportModal(){ document.getElementById('exportOverlay').style.display = 'flex'; }
function closeExportModal(){ document.getElementById('exportOverlay').style.display = 'none'; }
document.getElementById('exportOverlay').addEventListener('click', function(e){ if (e.target === this) closeExportModal(); });
/* ========== 分页交互 ========== */
document.querySelectorAll('.pagination').forEach(function(pag){
pag.querySelectorAll('.page-btn').forEach(function(btn){
btn.addEventListener('click', function(){
if (this.textContent === '' || this.textContent === '' || this.textContent === '...') return;
pag.querySelectorAll('.page-btn').forEach(function(b){ b.classList.remove('active'); });
this.classList.add('active');
showToast('已切换至第 ' + this.textContent + ' 页');
});
});
});
/* ========== 初始化 ========== */
initNoteTitles();
</script>
</body>
</html>
+484 -8
View File
@@ -2,6 +2,7 @@
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>营养管理 - 场所目标考核 | 健康CQ原型</title>
<style>
*{box-sizing:border-box;margin:0;padding:0;}
@@ -40,10 +41,96 @@ body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei
.sidebar-subgroup.open .sidebar-subgroup__body{display:block;}
/* 主区域 */
.main{margin-left:240px;margin-top:56px;padding:24px;min-height:calc(100vh - 56px);}
/* 页面占位 */
.page-placeholder{background:#fff;border-radius:var(--radius-md);box-shadow:var(--shadow-sm);padding:48px 24px;text-align:center;}
.page-placeholder__title{font-size:var(--font-xl);font-weight:600;color:var(--text-primary);margin-bottom:8px;}
.page-placeholder__hint{font-size:var(--font-sm);color:var(--text-placeholder);}
/* 统计总览卡片 */
.overview-card{background:#fff;border-radius:var(--radius-md);box-shadow:var(--shadow-sm);margin-bottom:16px;overflow:hidden;}
.overview-card__title{font-size:var(--font-base);font-weight:600;padding:14px 20px;border-bottom:1px solid #f0f0f0;}
.overview-card__meta{font-size:var(--font-xs);color:var(--text-placeholder);font-weight:400;margin-left:10px;}
.overview-card__body{display:grid;grid-template-columns:repeat(4,1fr);padding:28px 20px 32px;}
.kpi{text-align:center;}
.kpi__label{font-size:var(--font-sm);color:var(--text-secondary);margin-bottom:14px;}
.kpi__value{font-size:22px;font-weight:700;color:var(--text-primary);font-variant-numeric:tabular-nums;}
.kpi__value--rate{color:#D46B08;}
/* 筛选栏 */
.filter-bar{display:flex;align-items:center;gap:14px;background:#fff;padding:16px 20px;border-radius:var(--radius-md);margin-bottom:16px;box-shadow:var(--shadow-sm);flex-wrap:wrap;}
.filter-item{display:flex;align-items:center;gap:8px;}
.filter-item label{font-size:var(--font-sm);color:var(--text-secondary);white-space:nowrap;}
.filter-input,.filter-select{height:34px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 12px;font-size:var(--font-sm);color:var(--text-primary);outline:none;background:#fff;min-width:140px;}
.filter-select{padding-right:28px;background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath d='M0 0l5 6 5-6z' fill='%23999'/%3E%3C/svg%3E");background-repeat:no-repeat;background-position:right 10px center;appearance:none;cursor:pointer;}
.filter-input:focus,.filter-select:focus{border-color:var(--primary);}
.filter-search{position:relative;}
.filter-search input{height:34px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 12px 0 30px;font-size:var(--font-sm);outline:none;background:#fff url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='14' height='14' fill='none'%3E%3Ccircle cx='6' cy='6' r='4.5' stroke='%23999' stroke-width='1.2'/%3E%3Cpath d='M9.5 9.5L12.5 12.5' stroke='%23999' stroke-width='1.2' stroke-linecap='round'/%3E%3C/svg%3E") no-repeat 10px center;width:240px;}
.filter-search input:focus{border-color:var(--primary);}
.filter-input--range{width:88px;min-width:0;}
.range-sep{color:var(--text-placeholder);font-size:var(--font-sm);}
.btn{height:34px;padding:0 16px;border-radius:var(--radius-sm);font-size:var(--font-sm);cursor:pointer;border:none;transition:all .2s;display:inline-flex;align-items:center;gap:6px;}
.btn-primary{background:var(--primary);color:#fff;}.btn-primary:hover{background:#40a9ff;}
.btn-default{background:#fff;color:var(--text-primary);border:1px solid var(--border);}.btn-default:hover{color:var(--primary);border-color:var(--primary);}
/* 操作栏 */
.action-bar{display:flex;align-items:center;gap:8px;margin-bottom:16px;}
/* 表格卡片 */
.table-card{background:#fff;border-radius:var(--radius-md);box-shadow:var(--shadow-sm);overflow:hidden;}
.table-card__header{display:flex;align-items:center;justify-content:space-between;padding:16px 24px;border-bottom:1px solid var(--border);}
.table-card__title{font-size:var(--font-base);font-weight:600;}
.table-card__content{overflow-x:auto;overflow-y:hidden;-webkit-overflow-scrolling:touch;}
.table-card__content table{min-width:100%;white-space:nowrap;border-collapse:collapse;width:100%;}
thead th{font-size:var(--font-sm);color:var(--text-secondary);font-weight:500;border-bottom:1px solid var(--border);white-space:nowrap;padding:11px 14px;text-align:center;}
tbody td{padding:13px 14px;font-size:var(--font-sm);color:var(--text-primary);border-bottom:1px solid var(--border);white-space:nowrap;text-align:center;}
tbody tr:last-child td{border-bottom:none;}
tbody tr:hover td{background:#F8FBFF;}
td.td-left{text-align:left;}
/* 数值强调 */
.num{font-variant-numeric:tabular-nums;font-weight:500;}
.num--money{color:#CF1322;}
.num--rate{color:#389E0D;}
.num--rate-warn{color:#D46B08;}
.num--up{color:#CF1322;font-weight:600;}
.num--down{color:#389E0D;font-weight:600;}
.muted{color:#C0C4CC;}
/* 经营小结列省略号 + 点击查看 */
.td-summary{max-width:180px;}
.summary-text{display:inline-block;max-width:160px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;vertical-align:middle;cursor:pointer;}
.summary-text:hover{color:var(--primary);}
.summary-full{padding:16px;background:#FAFAFA;border-radius:var(--radius-md);font-size:var(--font-sm);line-height:1.8;color:var(--text-primary);white-space:normal;word-break:break-all;}
/* 操作列固定右侧 */
thead th.th-actions,tbody td.td-actions{position:sticky;right:0;background:#fff;box-shadow:-4px 0 8px rgba(0,0,0,0.04);z-index:3;white-space:nowrap;}
thead th.th-actions{background:#FAFAFA;z-index:4;}
tbody tr:hover td.td-actions{background:#F8FBFF;}
.link{color:var(--primary);cursor:pointer;font-size:var(--font-sm);text-decoration:none;margin:0 6px;}.link:hover{color:#40a9ff;}
/* 分页 */
.pagination{display:flex;align-items:center;justify-content:flex-end;padding:16px 24px;gap:8px;border-top:1px solid var(--border);}
.pagination span{font-size:var(--font-sm);color:var(--text-secondary);}
.page-btn{width:32px;height:32px;border:1px solid var(--border);border-radius:var(--radius-sm);background:#fff;cursor:pointer;font-size:var(--font-sm);display:inline-flex;align-items:center;justify-content:center;transition:all .2s;}
.page-btn:hover,.page-btn.active{background:var(--primary);color:#fff;border-color:var(--primary);}
/* 弹窗通用 */
.modal-overlay{position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.45);z-index:1000;display:flex;align-items:center;justify-content:center;}
.modal{background:#fff;border-radius:8px;max-height:88vh;overflow-y:auto;box-shadow:0 8px 24px rgba(0,0,0,0.16);}
.modal-header{display:flex;align-items:center;justify-content:space-between;padding:16px 24px;border-bottom:1px solid #e8e8e8;}
.modal-title{font-size:16px;font-weight:600;color:#262626;}
.modal-close{font-size:22px;cursor:pointer;color:#8c8c8c;line-height:1;}.modal-close:hover{color:#262626;}
.modal-body{padding:24px;}
.modal-footer{display:flex;justify-content:flex-end;gap:12px;padding:16px 24px;border-top:1px solid #e8e8e8;}
.detail-info{display:grid;grid-template-columns:1fr 1fr 1fr;gap:12px 24px;margin-bottom:20px;padding:16px;background:#FAFAFA;border-radius:var(--radius-md);}
.detail-info__item{display:flex;align-items:center;gap:6px;font-size:var(--font-sm);}
.detail-info__label{color:var(--text-secondary);}
.detail-info__value{color:var(--text-primary);font-weight:500;}
.form-item{display:flex;flex-direction:column;gap:6px;margin-bottom:16px;}
.form-item label{font-size:var(--font-sm);color:var(--text-secondary);}
.form-item input,.form-item textarea{border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 12px;font-size:var(--font-sm);outline:none;background:#fff;width:100%;}
.form-item input{height:36px;}
.form-item textarea{height:88px;padding:10px 12px;resize:none;font-family:inherit;line-height:1.6;}
.form-item input:focus,.form-item textarea:focus{border-color:var(--primary);}
.form-item .input-prefix{position:relative;}
.form-item .input-prefix input{padding-left:26px;}
.form-item .input-prefix::before{content:'¥';position:absolute;left:12px;top:50%;transform:translateY(-50%);color:var(--text-placeholder);font-size:var(--font-sm);}
.modal-table{width:100%;border-collapse:collapse;font-size:var(--font-sm);}
.modal-table thead th{background:#FAFAFA;padding:10px 12px;text-align:left;color:var(--text-secondary);font-weight:500;border-bottom:1px solid var(--border);}
.modal-table tbody td{padding:10px 12px;border-bottom:1px solid var(--border);color:var(--text-primary);text-align:left;}
.tag{display:inline-flex;align-items:center;padding:2px 8px;border-radius:10px;font-size:var(--font-xs);}
.tag-green{background:#F6FFED;color:var(--success);}
.tag-orange{background:#FFF7E6;color:#D46B08;}
/* Toast */
.toast{position:fixed;top:80px;left:50%;transform:translateX(-50%) translateY(-20px);background:rgba(0,0,0,0.75);color:#fff;padding:10px 24px;border-radius:var(--radius-md);font-size:var(--font-sm);opacity:0;pointer-events:none;transition:all .3s;z-index:2000;}
.toast.show{opacity:1;transform:translateX(-50%) translateY(0);}
</style>
</head>
<body>
@@ -142,17 +229,406 @@ body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei
<!-- 主内容 -->
<main class="main">
<div class="page-placeholder">
<div class="page-placeholder__title">场所目标考核</div>
<div class="page-placeholder__hint">页面功能待补充</div>
<!-- ① 本周目标考核总览 -->
<div class="overview-card">
<div class="overview-card__title">本周目标考核总览<span class="overview-card__meta">按周统计各场所目标完成率 · 数据每周更新</span></div>
<div class="overview-card__body">
<div class="kpi">
<div class="kpi__label">本周就餐人数(人)</div>
<div class="kpi__value">15,924</div>
</div>
<div class="kpi">
<div class="kpi__label">本周应付营收(元)</div>
<div class="kpi__value">¥547,530.00</div>
</div>
<div class="kpi">
<div class="kpi__label">本周目标营收(元)</div>
<div class="kpi__value">¥400,000.00</div>
</div>
<div class="kpi">
<div class="kpi__label">平均营收完成率</div>
<div class="kpi__value kpi__value--rate">98.2%</div>
</div>
</div>
</div>
<!-- ② 搜索区 -->
<div class="filter-bar">
<div class="filter-item">
<label>统计周期</label>
<select class="filter-select" id="filterWeek">
<option value="w34" selected>2026年第34周(08-17~08-23</option>
<option value="w33">2026年第33周(08-10~08-16</option>
<option value="w32">2026年第32周(08-03~08-09</option>
</select>
</div>
<div class="filter-item">
<label>场所名称</label>
<select class="filter-select" id="filterPlace">
<option value="全部" selected>全部</option>
<option value="一场所(东区)">一场所(东区)</option>
<option value="二场所(西区)">二场所(西区)</option>
<option value="三场所(北区)">三场所(北区)</option>
</select>
</div>
<div class="filter-item">
<label>应付营收</label>
<input class="filter-input filter-input--range" id="filterRevMin" type="text" placeholder="最低">
<span class="range-sep">~</span>
<input class="filter-input filter-input--range" id="filterRevMax" type="text" placeholder="最高">
</div>
<div class="filter-item">
<label>目标营收</label>
<input class="filter-input filter-input--range" id="filterGoalMin" type="text" placeholder="最低">
<span class="range-sep">~</span>
<input class="filter-input filter-input--range" id="filterGoalMax" type="text" placeholder="最高">
</div>
<div class="filter-item">
<label>营收完成率</label>
<input class="filter-input filter-input--range" id="filterRateMin" type="text" placeholder="最低%">
<span class="range-sep">~</span>
<input class="filter-input filter-input--range" id="filterRateMax" type="text" placeholder="最高%">
</div>
<button class="btn btn-primary" onclick="applyFilter()">🔍 查询</button>
<button class="btn btn-default" onclick="resetFilter(this)">⟳ 重置</button>
</div>
<!-- ③ 操作栏 -->
<div class="action-bar">
<button class="btn btn-primary" onclick="showToast('导出任务已创建,请至「查看导出任务」下载')">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none"><path d="M2 10v2h10v-2M7 2v7M4.5 6.5L7 9l2.5-2.5" stroke="#fff" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"/></svg>导出列表数据
</button>
<button class="btn btn-default" onclick="openExportModal()">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none"><rect x="2" y="2" width="10" height="10" rx="1.5" stroke="currentColor" stroke-width="1.4"/><path d="M4 5h6M4 7.5h4" stroke="currentColor" stroke-width="1.2" stroke-linecap="round"/></svg>查看导出任务
</button>
</div>
<!-- ④ 列表区 -->
<div class="table-card">
<div class="table-card__header"><div class="table-card__title">场所目标考核明细</div></div>
<div class="table-card__content">
<table id="dataTable">
<thead>
<tr>
<th>统计周期</th>
<th>场所名称</th>
<th>就餐人数</th>
<th>环比人数变动</th>
<th>应付营收</th>
<th>目标营收</th>
<th>营收完成率</th>
<th>结算率</th>
<th>食材成本率</th>
<th>毛利率</th>
<th>翻台率</th>
<th>经营小结</th>
<th class="th-actions">操作</th>
</tr>
</thead>
<tbody>
<tr data-place="一场所(东区)" data-rate="up">
<td>第34周(08-17~08-23</td>
<td class="td-left">一场所(东区)</td>
<td class="num">5,214</td>
<td class="num num--up">↑ +6.2%</td>
<td class="td-revenue num num--money">¥182,450.00</td>
<td class="td-goal num">¥180,000.00</td>
<td class="td-rate num num--rate">101.4%</td>
<td class="num">99.2%</td>
<td class="num">63.5%</td>
<td class="num num--rate">30.8%</td>
<td class="num">2.6</td>
<td class="td-summary td-left"><span class="summary-text" title="午晚高峰翻台良好,营收超额达成" onclick="viewSummary(this)">午晚高峰翻台良好,营收超额达成</span></td>
<td class="td-actions"><a class="link" onclick="openAudit(this)">考核审计</a></td>
</tr>
<tr data-place="二场所(西区)" data-rate="down">
<td>第34周(08-17~08-23</td>
<td class="td-left">二场所(西区)</td>
<td class="num">6,128</td>
<td class="num num--down">↓ -3.1%</td>
<td class="td-revenue num num--money">¥208,760.00</td>
<td class="td-goal num">¥220,000.00</td>
<td class="td-rate num num--rate-warn">94.9%</td>
<td class="num">98.7%</td>
<td class="num">64.9%</td>
<td class="num num--rate">30.4%</td>
<td class="num">2.4</td>
<td class="td-summary td-left"><span class="summary-text" title="受高温影响客流略降,营收缺口集中在晚餐" onclick="viewSummary(this)">受高温影响客流略降,营收缺口集中在晚餐</span></td>
<td class="td-actions"><a class="link" onclick="openAudit(this)">考核审计</a></td>
</tr>
<tr data-place="三场所(北区)" data-rate="pending">
<td>第34周(08-17~08-23</td>
<td class="td-left">三场所(北区)</td>
<td class="num">4,582</td>
<td class="num num--up">↑ +1.8%</td>
<td class="td-revenue num num--money">¥156,320.00</td>
<td class="td-goal num muted"></td>
<td class="td-rate num muted"></td>
<td class="num">99.5%</td>
<td class="num">62.8%</td>
<td class="num num--rate">31.5%</td>
<td class="num">2.1</td>
<td class="td-summary td-left"><span class="summary-text muted" onclick="viewSummary(this)"></span></td>
<td class="td-actions"><a class="link" onclick="openAudit(this)">考核审计</a></td>
</tr>
</tbody>
</table>
</div>
<div class="pagination">
<span id="pageInfo">显示 3 条,共 3 条</span>
<button class="page-btn"></button>
<button class="page-btn active">1</button>
<button class="page-btn"></button>
</div>
</div>
</main>
<!-- 考核审计弹窗 -->
<div class="modal-overlay" id="auditOverlay" style="display:none;">
<div class="modal" style="width:560px;">
<div class="modal-header">
<span class="modal-title">考核审计</span>
<span class="modal-close" onclick="closeAudit()">&times;</span>
</div>
<div class="modal-body">
<div class="detail-info">
<div class="detail-info__item"><span class="detail-info__label">场所名称:</span><span class="detail-info__value" id="auditPlace"></span></div>
<div class="detail-info__item"><span class="detail-info__label">统计周期:</span><span class="detail-info__value" id="auditWeek"></span></div>
<div class="detail-info__item"><span class="detail-info__label">本周应付营收:</span><span class="detail-info__value" id="auditRevenue"></span></div>
</div>
<div class="form-item">
<label>目标营收(元)<span style="color:var(--danger);">*</span></label>
<div class="input-prefix">
<input type="text" id="auditGoal" placeholder="请输入本周目标营收,用于核算完成率">
</div>
</div>
<div class="form-item" style="margin-bottom:0;">
<label>经营小结</label>
<textarea id="auditSummary" placeholder="请输入本周经营小结(可选)"></textarea>
</div>
</div>
<div class="modal-footer">
<button class="btn btn-default" onclick="closeAudit()">取消</button>
<button class="btn btn-primary" onclick="confirmAudit()">确认审计</button>
</div>
</div>
</div>
<!-- 经营小结查看弹窗 -->
<div class="modal-overlay" id="summaryOverlay" style="display:none;">
<div class="modal" style="width:480px;">
<div class="modal-header">
<span class="modal-title">经营小结</span>
<span class="modal-close" onclick="closeSummary()">&times;</span>
</div>
<div class="modal-body">
<div class="detail-info">
<div class="detail-info__item"><span class="detail-info__label">场所名称:</span><span class="detail-info__value" id="summaryPlace"></span></div>
<div class="detail-info__item"><span class="detail-info__label">统计周期:</span><span class="detail-info__value" id="summaryWeek"></span></div>
<div class="detail-info__item"><span class="detail-info__label">营收完成率:</span><span class="detail-info__value" id="summaryRate"></span></div>
</div>
<div class="summary-full" id="summaryFull"></div>
</div>
<div class="modal-footer">
<button class="btn btn-default" onclick="closeSummary()">关闭</button>
</div>
</div>
</div>
<!-- 导出任务弹窗 -->
<div class="modal-overlay" id="exportOverlay" style="display:none;">
<div class="modal" style="width:640px;">
<div class="modal-header">
<span class="modal-title">导出任务列表</span>
<span class="modal-close" onclick="closeExportModal()">&times;</span>
</div>
<div class="modal-body">
<table class="modal-table">
<thead><tr><th>任务ID</th><th>导出内容</th><th>创建时间</th><th>状态</th><th>操作</th></tr></thead>
<tbody>
<tr><td>EXP-20260820-001</td><td>场所目标考核列表</td><td>2026-08-20 09:12</td><td><span class="tag tag-green">已完成</span></td><td><span class="link" onclick="showToast('开始下载文件')">下载</span></td></tr>
<tr><td>EXP-20260820-002</td><td>场所目标考核列表</td><td>2026-08-20 10:35</td><td><span class="tag tag-orange">处理中</span></td><td><span class="link" style="color:#999;cursor:default;">下载</span></td></tr>
<tr><td>EXP-20260819-003</td><td>场所目标考核列表</td><td>2026-08-19 16:48</td><td><span class="tag tag-green">已完成</span></td><td><span class="link" onclick="showToast('开始下载文件')">下载</span></td></tr>
</tbody>
</table>
</div>
<div class="modal-footer">
<button class="btn btn-default" onclick="closeExportModal()">关闭</button>
</div>
</div>
</div>
<!-- Toast -->
<div class="toast" id="toast"></div>
<script>
/* 侧边栏子组折叠 */
/* ========== 侧边栏子组折叠 ========== */
function toggleSubgroup(header) {
header.parentElement.classList.toggle('open');
}
/* 自动展开当前激活项所在子组 */
(function(){
var active = document.querySelector('.sidebar-item.active');
if (active) { var sub = active.closest('.sidebar-subgroup'); if (sub) sub.classList.add('open'); }
})();
/* ========== Toast ========== */
function showToast(msg){
var el = document.getElementById('toast');
el.textContent = msg;
el.classList.add('show');
clearTimeout(el._t);
el._t = setTimeout(function(){ el.classList.remove('show'); }, 2000);
}
/* ========== 金额格式化(千分位 + 两位小数) ========== */
function formatMoney(n){
var s = n.toFixed(2);
var parts = s.split('.');
parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ',');
return '¥' + parts.join('.');
}
/* ========== 统计周期切换 ========== */
document.getElementById('filterWeek').addEventListener('change', function(){
var val = this.value;
if (val === 'w34') {
showToast('已查看本周(第34周)数据');
} else {
showToast('已切换统计周期,该周暂无考核数据');
}
});
/* ========== 数值解析(去掉 ¥ % 千分位逗号) ========== */
function parseNum(str){
if (!str || str === '—') return null;
var n = parseFloat(str.replace(/[¥,%]/g, '').replace(/,/g, ''));
return isNaN(n) ? null : n;
}
/* 范围判断:未设范围不过滤;设了范围但该行无值则不匹配 */
function inRange(val, minStr, maxStr){
var hasMin = minStr !== '' && minStr !== null;
var hasMax = maxStr !== '' && maxStr !== null;
if (!hasMin && !hasMax) return true;
if (val === null) return false;
var min = hasMin ? parseFloat(minStr) : -Infinity;
var max = hasMax ? parseFloat(maxStr) : Infinity;
return val >= min && val <= max;
}
/* ========== 查询(场所 + 三项范围 + 关键字) ========== */
function applyFilter(){
var place = document.getElementById('filterPlace').value;
var kw = document.getElementById('filterKeyword').value.trim().toLowerCase();
var revMin = document.getElementById('filterRevMin').value.trim();
var revMax = document.getElementById('filterRevMax').value.trim();
var goalMin = document.getElementById('filterGoalMin').value.trim();
var goalMax = document.getElementById('filterGoalMax').value.trim();
var rateMin = document.getElementById('filterRateMin').value.trim();
var rateMax = document.getElementById('filterRateMax').value.trim();
var rows = document.querySelectorAll('#dataTable tbody tr');
var count = 0;
rows.forEach(function(tr){
var p = tr.getAttribute('data-place');
var revenue = parseNum(tr.querySelector('.td-revenue').textContent);
var goal = parseNum(tr.querySelector('.td-goal').textContent);
var rate = parseNum(tr.querySelector('.td-rate').textContent);
var text = tr.textContent.toLowerCase();
var okPlace = place === '全部' || p === place;
var okRev = inRange(revenue, revMin, revMax);
var okGoal = inRange(goal, goalMin, goalMax);
var okRate = inRange(rate, rateMin, rateMax);
var okKw = !kw || text.indexOf(kw) > -1;
var show = okPlace && okRev && okGoal && okRate && okKw;
tr.style.display = show ? '' : 'none';
if (show) count++;
});
document.getElementById('pageInfo').textContent = '显示 ' + count + ' 条,共 ' + count + ' 条';
showToast('查询完成,共 ' + count + ' 条记录');
}
/* ========== 筛选重置 ========== */
function resetFilter(btn){
var bar = btn.closest('.filter-bar');
bar.querySelectorAll('select').forEach(function(s){ s.selectedIndex = 0; });
bar.querySelectorAll('input').forEach(function(i){ i.value = ''; });
applyFilter();
showToast('筛选条件已重置');
}
/* ========== 考核审计 ========== */
var auditTargetRow = null;
function openAudit(link){
auditTargetRow = link.closest('tr');
var tds = auditTargetRow.querySelectorAll('td');
// 列顺序:0统计周期 1门店 2就餐人数 3环比 4应付营收 5目标营收 6完成率 7结算率 8成本率 9毛利率 10翻台率 11小结 12操作
document.getElementById('auditPlace').textContent = tds[1].textContent;
document.getElementById('auditWeek').textContent = tds[0].textContent;
document.getElementById('auditRevenue').textContent = tds[4].textContent;
var goalText = tds[5].textContent;
var summaryText = tds[11].textContent;
document.getElementById('auditGoal').value = (goalText === '—') ? '' : goalText.replace(/[¥,]/g, '');
document.getElementById('auditSummary').value = (summaryText === '—') ? '' : summaryText;
document.getElementById('auditOverlay').style.display = 'flex';
}
function closeAudit(){ document.getElementById('auditOverlay').style.display = 'none'; }
function confirmAudit(){
var goalInput = document.getElementById('auditGoal').value.trim();
if (!goalInput) { showToast('请填写目标营收'); return; }
var goal = parseFloat(goalInput.replace(/[¥,]/g, ''));
if (isNaN(goal) || goal <= 0) { showToast('目标营收格式不正确'); return; }
var summary = document.getElementById('auditSummary').value.trim();
if (auditTargetRow) {
// 回填目标营收与经营小结
auditTargetRow.querySelector('.td-goal').textContent = formatMoney(goal);
auditTargetRow.querySelector('.td-goal').classList.remove('muted');
var summarySpan = auditTargetRow.querySelector('.summary-text');
summarySpan.textContent = summary || '—';
summarySpan.classList.toggle('muted', !summary);
summarySpan.title = summary || '';
// 重算营收完成率 = 应付营收 ÷ 目标营收
var revenue = parseFloat(auditTargetRow.querySelector('.td-revenue').textContent.replace(/[¥,]/g, ''));
var rate = revenue / goal * 100;
var rateCell = auditTargetRow.querySelector('.td-rate');
rateCell.textContent = rate.toFixed(1) + '%';
rateCell.className = 'td-rate num ' + (rate >= 100 ? 'num--rate' : 'num--rate-warn');
auditTargetRow.setAttribute('data-rate', rate >= 100 ? 'up' : 'down');
}
closeAudit();
showToast('考核审计完成,已回填目标营收与经营小结并重算完成率');
}
document.getElementById('auditOverlay').addEventListener('click', function(e){ if (e.target === this) closeAudit(); });
/* ========== 经营小结查看 ========== */
function viewSummary(span){
var text = span.textContent.trim();
if (!text || text === '—') return;
var tr = span.closest('tr');
var tds = tr.querySelectorAll('td');
document.getElementById('summaryPlace').textContent = tds[1].textContent;
document.getElementById('summaryWeek').textContent = tds[0].textContent;
document.getElementById('summaryRate').textContent = tds[6].textContent;
document.getElementById('summaryFull').textContent = text;
document.getElementById('summaryOverlay').style.display = 'flex';
}
function closeSummary(){ document.getElementById('summaryOverlay').style.display = 'none'; }
document.getElementById('summaryOverlay').addEventListener('click', function(e){ if (e.target === this) closeSummary(); });
/* ========== 导出任务弹窗 ========== */
function openExportModal(){ document.getElementById('exportOverlay').style.display = 'flex'; }
function closeExportModal(){ document.getElementById('exportOverlay').style.display = 'none'; }
document.getElementById('exportOverlay').addEventListener('click', function(e){ if (e.target === this) closeExportModal(); });
/* ========== 分页交互 ========== */
document.querySelectorAll('.pagination').forEach(function(pag){
pag.querySelectorAll('.page-btn').forEach(function(btn){
btn.addEventListener('click', function(){
if (this.textContent === '' || this.textContent === '' || this.textContent === '...') return;
pag.querySelectorAll('.page-btn').forEach(function(b){ b.classList.remove('active'); });
this.classList.add('active');
showToast('已切换至第 ' + this.textContent + ' 页');
});
});
});
</script>
</body>
</html>
+14
View File
@@ -1,5 +1,19 @@
# 营养用餐管理端 CHANGELOG
## [2026-08-20] feat: 报表中心两页面补齐 — 场所经营分析 + 场所目标考核(`din-place-biz-analysis.html` / `din-place-goal.html`)
- 原「场所经营分析」「场所目标考核」为占位页,本次补齐为完整报表页
- `din-place-biz-analysis.html` 场所经营分析:
- 食堂切换区(当前场所下拉:全部 / 一场所东区 / 二场所西区 / 三场所北区)
- 搜索筛选区(统计日期 + 取餐总额 / 取餐笔数 / 实收金额范围筛选)
- 双表头表格(分组表头 + 子表头)+ 分页
- 详情报表弹窗:正式报表样式(标题 / 元信息 / 分段表格 / 签字栏),支持打印(`@media print` 仅输出报表)
- `din-place-goal.html` 场所目标考核:
- 本周目标考核总览(4 个 KPI:就餐人数 / 应付营收 / 目标营收 / 平均完成率)
- 搜索筛选区(统计周期 + 场所名称)
- 明细表格(经营小结列省略号 + 点击查看全文、操作列固定右侧)+ 分页
- 目标编辑弹窗(金额输入带 ¥ 前缀)
## [2026-08-19] feat: 餐线管理重构 — 收费模式周设置矩阵 + 设备概况/详情弹窗(`din-canteen-line.html`)
- 餐线列表新增结算方式(自动/手动)·状态(胶囊开关,点击启用/停用切换)两字段;收费模式因每天餐次可能不同,不在列表展示