|
|
@@ -280,6 +280,7 @@
|
|
|
}
|
|
|
if (!state.data) return;
|
|
|
body.innerHTML = state.view === 'cards' ? renderCards(state.data) : renderTables(state.data);
|
|
|
+ if (state.view === 'table') bindRowHover();
|
|
|
}
|
|
|
|
|
|
function cellValue(shop, metric, date) {
|
|
|
@@ -332,14 +333,16 @@
|
|
|
${metrics.map((m) => `<th class="ops-metric-col">${META[m].label}</th>`).join('')}
|
|
|
</tr>`;
|
|
|
|
|
|
- const body = data.groups.map((group) => {
|
|
|
+ const body = data.groups.map((group, groupIdx) => {
|
|
|
const schoolRow = `<tr class="ops-school-row"><td colspan="${colCount}">${esc(group.school)}<em>${group.shops.length} 家</em></td></tr>`;
|
|
|
|
|
|
- const shopRows = group.shops.map((shop) => {
|
|
|
- // 只展示该店有数据的日期(平台明细可能缺某天)
|
|
|
+ const shopRows = group.shops.map((shop, shopIdx) => {
|
|
|
const shopDates = dates.filter((d) => metrics.some((m) => cellValue(shop, m, d) !== null));
|
|
|
const useDates = shopDates.length ? shopDates : dates.slice(0, 1);
|
|
|
- return useDates.map((date, i) => `<tr>
|
|
|
+ // 每个店铺一个色块与悬停组,日期行共用同一个 block key
|
|
|
+ const blockKey = `b${groupIdx}-${shopIdx}`;
|
|
|
+ const alt = shopIdx % 2 === 1 ? ' ops-block-alt' : '';
|
|
|
+ return useDates.map((date, i) => `<tr data-block="${blockKey}" class="ops-shop-row${i === 0 ? ' ops-block-start' : ''}${alt}">
|
|
|
${i === 0 ? `<td class="ops-shop-cell" rowspan="${useDates.length}" title="${esc(shop.shopName)}">${esc(shop.shopName)}${
|
|
|
shop.status && shop.status !== '已上线' ? `<span class="badge badge-muted ops-status">${esc(shop.status)}</span>` : ''
|
|
|
}</td>` : ''}
|
|
|
@@ -372,6 +375,23 @@
|
|
|
</section>`;
|
|
|
}
|
|
|
|
|
|
+ /** 悬停时整块高亮(同一店铺的多天一起亮),避免分不清行属于哪家店 */
|
|
|
+ function bindRowHover() {
|
|
|
+ document.querySelectorAll('.ops-table-wide tbody').forEach((tbody) => {
|
|
|
+ if (tbody.dataset.hoverBound === '1') return;
|
|
|
+ tbody.dataset.hoverBound = '1';
|
|
|
+ const clear = () => tbody.querySelectorAll('tr.ops-hover').forEach((tr) => tr.classList.remove('ops-hover'));
|
|
|
+ tbody.addEventListener('mouseover', (e) => {
|
|
|
+ const tr = e.target.closest('tr[data-block]');
|
|
|
+ if (!tr) return;
|
|
|
+ if (tr.classList.contains('ops-hover')) return;
|
|
|
+ clear();
|
|
|
+ tbody.querySelectorAll(`tr[data-block="${tr.dataset.block}"]`).forEach((row) => row.classList.add('ops-hover'));
|
|
|
+ });
|
|
|
+ tbody.addEventListener('mouseleave', clear);
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
function sparkline(shop, metric, dates) {
|
|
|
const values = dates.map((d) => {
|
|
|
const v = (shop.series[metric] || {})[d];
|