info
This commit is contained in:
1006
src/views/ydoyun/report/lijun/reportpage6/API接口文档.md
Normal file
1006
src/views/ydoyun/report/lijun/reportpage6/API接口文档.md
Normal file
File diff suppressed because it is too large
Load Diff
298
src/views/ydoyun/report/lijun/reportpage6/KB22.html
Normal file
298
src/views/ydoyun/report/lijun/reportpage6/KB22.html
Normal file
@@ -0,0 +1,298 @@
|
||||
<!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>
|
||||
:root {
|
||||
--card-width: 340px;
|
||||
--primary: #0f172a;
|
||||
--secondary: #64748b;
|
||||
--accent-blue: #3b82f6; /* 销量色 */
|
||||
--bg-page: #f1f5f9;
|
||||
--danger: #ef4444;
|
||||
--success: #10b981;
|
||||
--border: #e2e8f0;
|
||||
}
|
||||
|
||||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
body {
|
||||
background: var(--bg-page);
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
min-height: 100vh;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
/* --- 基础卡片样式保持不变 --- */
|
||||
.card {
|
||||
background: #fff;
|
||||
width: var(--card-width);
|
||||
border-radius: 16px;
|
||||
box-shadow: 0 10px 30px -10px rgba(0,0,0,0.1);
|
||||
border: 1px solid #fff;
|
||||
overflow: hidden;
|
||||
display: flex; flex-direction: column;
|
||||
}
|
||||
.header-bar {
|
||||
background: #f8fafc; padding: 10px 18px; border-bottom: 1px solid var(--border);
|
||||
display: flex; justify-content: space-between; align-items: center;
|
||||
font-size: 13px; color: var(--secondary); font-weight: 500;
|
||||
}
|
||||
.color-badge { color: var(--primary); font-weight: 700; display: flex; align-items: center; gap: 6px; }
|
||||
.color-dot { width: 10px; height: 10px; background: #d4b483; border-radius: 50%; box-shadow: 0 0 0 1px rgba(0,0,0,0.1); }
|
||||
.card-body { padding: 18px; flex: 1; display: flex; flex-direction: column; }
|
||||
|
||||
/* 媒体区 */
|
||||
.media-row { display: flex; gap: 16px; margin-bottom: 20px; }
|
||||
.thumb-box { width: 90px; height: 120px; border-radius: 8px; overflow: hidden; flex-shrink: 0; position: relative; border: 1px solid var(--border); }
|
||||
.thumb-img { width: 100%; height: 100%; object-fit: cover; }
|
||||
.info-col { flex: 1; display: flex; flex-direction: column; min-width: 0; }
|
||||
.prod-title { font-size: 16px; font-weight: 700; color: var(--primary); margin-bottom: 8px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
||||
.tags-container { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 12px; }
|
||||
.tag-pill { font-size: 10px; padding: 2px 8px; border-radius: 4px; font-weight: 600; }
|
||||
.tag-blue { background: #eff6ff; color: var(--accent-blue); border: 1px solid #dbeafe; }
|
||||
.tag-purple { background: #f5f3ff; color: #7c3aed; border: 1px solid #ede9fe; }
|
||||
.price-section { margin-top: auto; }
|
||||
.current-price { font-size: 22px; font-weight: 800; color: var(--primary); letter-spacing: -0.5px; }
|
||||
.cost-price { font-size: 12px; color: #94a3b8; text-decoration: line-through; margin-left: 6px;}
|
||||
.margin-text { font-size: 12px; color: #d97706; font-weight: 600; float: right; margin-top: 8px;}
|
||||
|
||||
/* 重点数据区 */
|
||||
.stats-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; margin-bottom: 20px; }
|
||||
.stat-card { padding: 14px; border-radius: 12px; display: flex; flex-direction: column; justify-content: center; }
|
||||
.stat-card.sales { background: #f0f9ff; border: 1px solid #bae6fd; }
|
||||
.stat-card.stock { background: #faf5ff; border: 1px solid #e9d5ff; }
|
||||
.stat-label { font-size: 11px; color: var(--secondary); margin-bottom: 2px; }
|
||||
.stat-value { font-size: 28px; font-weight: 900; color: var(--primary); line-height: 1.1; font-family: 'Roboto', sans-serif; }
|
||||
.stat-sub { font-size: 12px; font-weight: 500; opacity: 0.6; margin-left: 2px; }
|
||||
.sales-footer { margin-top: 8px; display: flex; flex-direction: column; align-items: flex-start; }
|
||||
.mini-progress { width: 100%; height: 4px; background: rgba(0,0,0,0.06); border-radius: 2px; margin-bottom: 4px; overflow: hidden; }
|
||||
.mini-fill { height: 100%; background: var(--accent-blue); width: 62%; }
|
||||
.rate-text { font-size: 10px; color: var(--accent-blue); font-weight: 600; }
|
||||
|
||||
/* --- 4. 底部库存与销量双显 (核心修改区域) --- */
|
||||
.stock-footer {
|
||||
border-top: 1px dashed var(--border);
|
||||
padding-top: 16px;
|
||||
}
|
||||
.section-header {
|
||||
font-size: 11px; color: #94a3b8; margin-bottom: 10px;
|
||||
display: flex; justify-content: space-between; align-items: center;
|
||||
}
|
||||
|
||||
/* 尺码网格 */
|
||||
.size-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr); /* 4列 */
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
/* 单个尺码盒子:设计成上下结构 */
|
||||
.size-cell {
|
||||
background: #f8fafc;
|
||||
border: 1px solid #e2e8f0;
|
||||
border-radius: 8px;
|
||||
padding: 6px 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* 尺码标签 (S/M/L) - 居中置顶 */
|
||||
.sz-tag {
|
||||
font-size: 10px;
|
||||
color: #94a3b8;
|
||||
text-align: center;
|
||||
font-weight: 600;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
/* 数据行容器 */
|
||||
.sz-data-row {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
/* 库存数字 (主) */
|
||||
.sz-stock {
|
||||
font-size: 14px;
|
||||
font-weight: 800;
|
||||
color: var(--primary);
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
/* 销量数字 (辅) */
|
||||
.sz-sales {
|
||||
font-size: 10px;
|
||||
color: var(--accent-blue);
|
||||
background: rgba(59, 130, 246, 0.1); /* 浅蓝色背景 */
|
||||
padding: 1px 6px;
|
||||
border-radius: 4px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* --- 缺货状态样式 (M码) --- */
|
||||
.size-cell.alert {
|
||||
background: #fef2f2;
|
||||
border-color: #fecaca;
|
||||
}
|
||||
.size-cell.alert .sz-tag { color: var(--danger); }
|
||||
.size-cell.alert .sz-stock { color: var(--danger); }
|
||||
/* 缺货时的销量背景改红,保持统一感 */
|
||||
.size-cell.alert .sz-sales {
|
||||
color: #b91c1c;
|
||||
background: rgba(239, 68, 68, 0.15);
|
||||
}
|
||||
|
||||
/* 缺货角标 */
|
||||
.alert-badge {
|
||||
position: absolute; top: -4px; right: -4px;
|
||||
background: var(--danger); color: white;
|
||||
font-size: 8px; padding: 2px 4px; border-radius: 4px;
|
||||
transform: scale(0.9); font-weight: bold;
|
||||
}
|
||||
|
||||
/* 底部说明 */
|
||||
.legend {
|
||||
display: flex; gap: 12px; margin-top: 8px; justify-content: flex-end;
|
||||
}
|
||||
.legend-item { display: flex; align-items: center; gap: 4px; font-size: 9px; color: #94a3b8; }
|
||||
.dot { width: 6px; height: 6px; border-radius: 50%; }
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="card">
|
||||
<div class="header-bar">
|
||||
<div>款号: <span style="font-family: monospace; font-weight:600;">54000245</span></div>
|
||||
<div class="color-badge">
|
||||
<span class="color-dot"></span>
|
||||
卡其色
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-body">
|
||||
<div class="media-row">
|
||||
<div class="thumb-box">
|
||||
<img src="https://images.unsplash.com/photo-1624378439575-d8705ad7ae80?q=80&w=200&auto=format&fit=crop" class="thumb-img">
|
||||
</div>
|
||||
<div class="info-col">
|
||||
<div class="prod-title">LK2025 春季纯棉修身休闲裤</div>
|
||||
<div class="tags-container">
|
||||
<span class="tag-pill tag-blue">25春 一波段</span>
|
||||
<span class="tag-pill tag-purple">8.5折</span>
|
||||
</div>
|
||||
<div class="price-section">
|
||||
<span class="current-price">¥369</span>
|
||||
<span class="cost-price">¥158</span>
|
||||
<span class="margin-text">毛利 57%</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="stats-grid">
|
||||
<div class="stat-card sales">
|
||||
<div class="stat-label">总销量</div>
|
||||
<div class="stat-value">450<span class="stat-sub">件</span></div>
|
||||
<div class="sales-footer">
|
||||
<div class="mini-progress"><div class="mini-fill"></div></div>
|
||||
<div class="rate-text">售罄率 62%</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stat-card stock">
|
||||
<div class="stat-label">当前库存</div>
|
||||
<div class="stat-value">280<span class="stat-sub">件</span></div>
|
||||
<div style="font-size:10px; color:#a855f7; margin-top:auto; font-weight:600;">周转: 38天 (健康)</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="stock-footer">
|
||||
<div class="section-header">
|
||||
<span>SKU明细 (8码)</span>
|
||||
<span style="color:#ef4444; font-weight:600; font-size:10px;">⚠️ M码断货</span>
|
||||
</div>
|
||||
|
||||
<div class="size-grid">
|
||||
<div class="size-cell">
|
||||
<span class="sz-tag">XS</span>
|
||||
<div class="sz-data-row">
|
||||
<span class="sz-stock">12</span>
|
||||
<span class="sz-sales">销 5</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="size-cell">
|
||||
<span class="sz-tag">S</span>
|
||||
<div class="sz-data-row">
|
||||
<span class="sz-stock">45</span>
|
||||
<span class="sz-sales">销 20</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="size-cell alert">
|
||||
<div class="alert-badge">补</div>
|
||||
<span class="sz-tag">M</span>
|
||||
<div class="sz-data-row">
|
||||
<span class="sz-stock">0</span>
|
||||
<span class="sz-sales">销 128</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="size-cell">
|
||||
<span class="sz-tag">L</span>
|
||||
<div class="sz-data-row">
|
||||
<span class="sz-stock">52</span>
|
||||
<span class="sz-sales">销 88</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="size-cell">
|
||||
<span class="sz-tag">XL</span>
|
||||
<div class="sz-data-row">
|
||||
<span class="sz-stock">38</span>
|
||||
<span class="sz-sales">销 42</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="size-cell">
|
||||
<span class="sz-tag">2XL</span>
|
||||
<div class="sz-data-row">
|
||||
<span class="sz-stock">24</span>
|
||||
<span class="sz-sales">销 15</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="size-cell">
|
||||
<span class="sz-tag">3XL</span>
|
||||
<div class="sz-data-row">
|
||||
<span class="sz-stock">10</span>
|
||||
<span class="sz-sales">销 6</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="size-cell">
|
||||
<span class="sz-tag">4XL</span>
|
||||
<div class="sz-data-row">
|
||||
<span class="sz-stock">5</span>
|
||||
<span class="sz-sales">销 2</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="legend">
|
||||
<div class="legend-item"><div class="dot" style="background:#0f172a"></div>库存</div>
|
||||
<div class="legend-item"><div class="dot" style="background:#3b82f6"></div>销量</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
174
src/views/ydoyun/report/lijun/reportpage6/KBStyle.HTML
Normal file
174
src/views/ydoyun/report/lijun/reportpage6/KBStyle.HTML
Normal file
@@ -0,0 +1,174 @@
|
||||
<!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>
|
||||
:root {
|
||||
--primary: #1F2937;
|
||||
--blue: #2563EB;
|
||||
--red: #EF4444;
|
||||
--green: #10B981;
|
||||
--orange: #F59E0B;
|
||||
--bg: #F0F2F5;
|
||||
}
|
||||
body { font-family: -apple-system, sans-serif; background: var(--bg); margin: 0; font-size: 12px; color: #333; }
|
||||
|
||||
/* 紧凑型页眉 */
|
||||
.header { background: var(--primary); color: #fff; padding: 12px 15px; display: flex; justify-content: space-between; align-items: center; }
|
||||
.header-main { display: flex; align-items: baseline; gap: 8px; }
|
||||
.header-val { font-size: 20px; font-weight: bold; }
|
||||
|
||||
.container { padding: 8px; }
|
||||
|
||||
/* 紧凑按钮组 */
|
||||
.action-row { display: flex; gap: 6px; margin-bottom: 8px; }
|
||||
.btn-sm { flex: 1; padding: 8px; border-radius: 6px; color: white; text-align: center; font-weight: bold; font-size: 12px; text-decoration: none; }
|
||||
.bg-gradient-purple { background: linear-gradient(90deg, #6366F1, #8B5CF6); }
|
||||
.bg-gradient-orange { background: linear-gradient(90deg, #F59E0B, #EA580C); }
|
||||
|
||||
.card { background: #fff; border-radius: 8px; padding: 10px; margin-bottom: 8px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); }
|
||||
.card-title { font-size: 13px; font-weight: bold; margin-bottom: 8px; display: flex; align-items: center; justify-content: space-between; }
|
||||
|
||||
/* 极窄标签栏 */
|
||||
.filter-bar { display: flex; gap: 4px; overflow-x: auto; margin-bottom: 8px; padding-bottom: 2px; }
|
||||
.filter-bar::-webkit-scrollbar { display: none; }
|
||||
.pill { white-space: nowrap; padding: 2px 8px; background: #F3F4F6; border-radius: 4px; font-size: 11px; color: #666; border: 0.5px solid #E5E7EB; }
|
||||
.pill.active { background: var(--blue); color: #fff; border-color: var(--blue); }
|
||||
|
||||
/* 高密度表格 */
|
||||
.dense-table { width: 100%; border-collapse: collapse; }
|
||||
.dense-table th { text-align: left; font-size: 10px; color: #999; padding: 4px 2px; border-bottom: 1px solid #EEE; font-weight: normal; }
|
||||
.dense-table td { padding: 6px 2px; border-bottom: 0.5px solid #F9FAFB; font-size: 12px; vertical-align: middle; }
|
||||
|
||||
/* 状态微标签 */
|
||||
.s-tag { font-size: 9px; padding: 0px 3px; border-radius: 2px; margin-left: 2px; display: inline-block; vertical-align: middle; }
|
||||
.red { background: #FEE2E2; color: var(--red); }
|
||||
.blue { background: #DBEAFE; color: var(--blue); }
|
||||
.orange { background: #FEF3C7; color: var(--orange); }
|
||||
.green { background: #D1FAE5; color: var(--green); }
|
||||
|
||||
.num { font-family: "Monaco", monospace; font-weight: 500; }
|
||||
.dim { color: #999; font-size: 10px; }
|
||||
|
||||
.expand-link { text-align: center; color: var(--blue); padding-top: 8px; font-size: 11px; cursor: pointer; }
|
||||
.hidden { display: none !important; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="header">
|
||||
<div class="header-main">
|
||||
<span style="font-size: 11px; opacity: 0.8;">今日男装</span>
|
||||
<span class="header-val">¥128,402</span>
|
||||
</div>
|
||||
<div style="text-align: right">
|
||||
<div style="font-size: 11px;">毛利 <span style="color:var(--green)">42.5%</span></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
<div class="action-row">
|
||||
<a class="btn-sm bg-gradient-purple">上新中心 ➔</a>
|
||||
<a class="btn-sm bg-gradient-orange">补货预警 (12)</a>
|
||||
</div>
|
||||
|
||||
<div class="card" id="cat-card">
|
||||
<div class="card-title">品类表现 (Top 10)</div>
|
||||
<div class="filter-bar">
|
||||
<div class="pill active" data-f="all">全部</div>
|
||||
<div class="pill" data-f="low-m">低毛利</div>
|
||||
<div class="pill" data-f="stockout">缺货</div>
|
||||
<div class="pill" data-f="overstock">积压</div>
|
||||
</div>
|
||||
<table class="dense-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th width="35%">品类/标签</th>
|
||||
<th width="20%">毛利</th>
|
||||
<th width="20%">库销比</th>
|
||||
<th width="25%" style="text-align:right">销售额</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="cat-body">
|
||||
<tr data-t="stockout"><td>夹克<span class="s-tag blue">缺</span></td><td class="num">48%</td><td class="num red">1.2</td><td class="num" align="right">42,100</td></tr>
|
||||
<tr data-t="low-m"><td>衬衫<span class="s-tag red">低</span></td><td class="num red">22%</td><td class="num">4.2</td><td class="num" align="right">31,200</td></tr>
|
||||
<tr data-t="overstock"><td>T恤<span class="s-tag orange">压</span></td><td class="num">35%</td><td class="num orange">8.5</td><td class="num" align="right">12,500</td></tr>
|
||||
<tr data-t="low-m"><td>长裤<span class="s-tag red">低</span></td><td class="num red">28%</td><td class="num">3.5</td><td class="num" align="right">10,200</td></tr>
|
||||
<tr data-t="stockout"><td>针织衫<span class="s-tag blue">缺</span></td><td class="num">41%</td><td class="num red">1.8</td><td class="num" align="right">9,800</td></tr>
|
||||
<tr class="extra hidden" data-t="stockout"><td>卫衣<span class="s-tag blue">缺</span></td><td class="num">38%</td><td class="num red">1.5</td><td class="num" align="right">7,402</td></tr>
|
||||
<tr class="extra hidden" data-t="overstock"><td>牛仔<span class="s-tag orange">压</span></td><td class="num">32%</td><td class="num orange">9.1</td><td class="num" align="right">6,200</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="expand-link" onclick="toggleRows('cat-card')">展开更多数据 ▾</div>
|
||||
</div>
|
||||
|
||||
<div class="card" id="store-card">
|
||||
<div class="card-title">门店业绩排行</div>
|
||||
<div class="filter-bar">
|
||||
<div class="pill active" data-f="all">全部</div>
|
||||
<div class="pill" data-f="high">高效</div>
|
||||
<div class="pill" data-f="discount">强折</div>
|
||||
<div class="pill" data-f="stockout">缺货</div>
|
||||
</div>
|
||||
<table class="dense-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th width="40%">门店名称</th>
|
||||
<th width="20%">毛利</th>
|
||||
<th width="20%">正价%</th>
|
||||
<th width="20%" style="text-align:right">销售</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="store-body">
|
||||
<tr data-t="high"><td>淮海旗舰店<span class="s-tag green">优</span></td><td class="num">52%</td><td class="num">82%</td><td class="num" align="right">1.8w</td></tr>
|
||||
<tr data-t="discount"><td>万象城店<span class="s-tag orange">折</span></td><td class="num red">18%</td><td class="num red">30%</td><td class="num" align="right">1.5w</td></tr>
|
||||
<tr data-t="stockout"><td>北京SKP<span class="s-tag blue">缺</span></td><td class="num">41%</td><td class="num">95%</td><td class="num" align="right">1.4w</td></tr>
|
||||
<tr data-t="high"><td>南京德基<span class="s-tag green">优</span></td><td class="num">49%</td><td class="num">78%</td><td class="num" align="right">1.2w</td></tr>
|
||||
<tr data-t="discount"><td>广州太古汇<span class="s-tag orange">折</span></td><td class="num red">25%</td><td class="num red">45%</td><td class="num" align="right">1.1w</td></tr>
|
||||
<tr class="extra hidden" data-t="stockout"><td>万象天地<span class="s-tag blue">缺</span></td><td class="num">39%</td><td class="num">88%</td><td class="num" align="right">9.8k</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="expand-link" onclick="toggleRows('store-card')">展开更多数据 ▾</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// 简易过滤与展开逻辑
|
||||
document.querySelectorAll('.pill').forEach(pill => {
|
||||
pill.addEventListener('click', function() {
|
||||
const card = this.closest('.card');
|
||||
const filter = this.getAttribute('data-f');
|
||||
|
||||
card.querySelectorAll('.pill').forEach(p => p.classList.remove('active'));
|
||||
this.classList.add('active');
|
||||
|
||||
const rows = card.querySelectorAll('tbody tr');
|
||||
rows.forEach(row => {
|
||||
const tags = row.getAttribute('data-t');
|
||||
if(filter === 'all' || (tags && tags.includes(filter))) {
|
||||
row.classList.remove('hidden');
|
||||
} else {
|
||||
row.classList.add('hidden');
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function toggleRows(cardId) {
|
||||
const card = document.getElementById(cardId);
|
||||
const extraRows = card.querySelectorAll('.extra');
|
||||
const link = card.querySelector('.expand-link');
|
||||
const isHidden = extraRows[0].classList.contains('hidden');
|
||||
|
||||
extraRows.forEach(row => {
|
||||
if(isHidden) row.classList.remove('hidden');
|
||||
else row.classList.add('hidden');
|
||||
});
|
||||
link.innerText = isHidden ? '收起数据 ▴' : '展开更多数据 ▾';
|
||||
}
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,685 @@
|
||||
<template>
|
||||
<div class="category-diagnostic-card" :class="[getCardBorderClass(categoryData), { clickable: useMiddleClassMetrics }]" @click="handleCardClick">
|
||||
<!-- 卡片头部 -->
|
||||
<div class="card-header">
|
||||
<div class="header-left">
|
||||
<h2 class="category-name">{{ categoryData.name }}</h2>
|
||||
<div class="category-tags">
|
||||
<span
|
||||
v-for="(pair, pi) in parseLabelTrendPairs(categoryData.label, categoryData.trend)"
|
||||
:key="pi"
|
||||
class="label-tag"
|
||||
:class="'label-trend-' + (pair.trend || 'flat')"
|
||||
>
|
||||
{{ pair.label }}
|
||||
</span>
|
||||
<el-tag
|
||||
v-for="(tag, tagIndex) in getTagsFromStatus(categoryData.status)"
|
||||
:key="tagIndex"
|
||||
:type="getTagType(tag)"
|
||||
size="small"
|
||||
effect="plain"
|
||||
class="category-tag"
|
||||
>
|
||||
{{ tag }}
|
||||
</el-tag>
|
||||
</div>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<span class="rate-label">周转天</span>
|
||||
<span class="rate-value turnover-days">{{ categoryData.turnoverDays ?? '-' }}<small>天</small></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 指标表格头部 -->
|
||||
<div class="metric-header">
|
||||
<span>检测指标</span>
|
||||
<span>实际结果</span>
|
||||
<span>基准参考</span>
|
||||
</div>
|
||||
|
||||
<!-- 指标数据行:统一 8 项(销量、销售额、库存量、库存成本、库存吊销额、毛利率、消化率、动销率) -->
|
||||
<div class="metric-body">
|
||||
<div class="metric-row" :class="getRowTrendClass(categoryData.salesCount, categoryData.salesCountRef)">
|
||||
<span class="metric-label">销量</span>
|
||||
<span class="metric-value">
|
||||
{{ formatNumber(categoryData.salesCount ?? 0) }}
|
||||
<span
|
||||
v-if="getTrendArrow(categoryData.salesCount, categoryData.salesCountRef)"
|
||||
class="trend-arrow"
|
||||
:class="getTrendClass(categoryData.salesCount, categoryData.salesCountRef)"
|
||||
>
|
||||
{{ getTrendArrow(categoryData.salesCount, categoryData.salesCountRef) }}
|
||||
</span>
|
||||
</span>
|
||||
<span class="metric-ref">{{ formatNumber(categoryData.salesCountRef ?? 0) }}</span>
|
||||
</div>
|
||||
<div class="metric-row" :class="getRowTrendClass(categoryData.salesAmount ?? 0, categoryData.salesAmountRef ?? 0)">
|
||||
<span class="metric-label">销售额</span>
|
||||
<span class="metric-value">
|
||||
¥{{ formatNumber(categoryData.salesAmount ?? 0) }}
|
||||
<span
|
||||
v-if="getTrendArrow(categoryData.salesAmount ?? 0, categoryData.salesAmountRef ?? 0)"
|
||||
class="trend-arrow"
|
||||
:class="getTrendClass(categoryData.salesAmount ?? 0, categoryData.salesAmountRef ?? 0)"
|
||||
>
|
||||
{{ getTrendArrow(categoryData.salesAmount ?? 0, categoryData.salesAmountRef ?? 0) }}
|
||||
</span>
|
||||
</span>
|
||||
<span class="metric-ref">¥{{ formatNumber(categoryData.salesAmountRef ?? 0) }}</span>
|
||||
</div>
|
||||
<div class="metric-row" :class="getRowTrendClass(categoryData.inventoryCount ?? 0, categoryData.inventoryCountRef ?? 0)">
|
||||
<span class="metric-label">库存量</span>
|
||||
<span class="metric-value">
|
||||
{{ formatNumber(categoryData.inventoryCount ?? 0) }}
|
||||
<span
|
||||
v-if="getTrendArrow(categoryData.inventoryCount ?? 0, categoryData.inventoryCountRef ?? 0)"
|
||||
class="trend-arrow"
|
||||
:class="getTrendClass(categoryData.inventoryCount ?? 0, categoryData.inventoryCountRef ?? 0)"
|
||||
>
|
||||
{{ getTrendArrow(categoryData.inventoryCount ?? 0, categoryData.inventoryCountRef ?? 0) }}
|
||||
</span>
|
||||
</span>
|
||||
<span class="metric-ref">{{ formatNumber(categoryData.inventoryCountRef ?? 0) }}</span>
|
||||
</div>
|
||||
<div class="metric-row" :class="getRowTrendClass(categoryData.inventoryCost ?? 0, categoryData.inventoryCostRef ?? 0)">
|
||||
<span class="metric-label">库存成本</span>
|
||||
<span class="metric-value">
|
||||
¥{{ formatNumber(categoryData.inventoryCost ?? 0) }}
|
||||
<span
|
||||
v-if="getTrendArrow(categoryData.inventoryCost ?? 0, categoryData.inventoryCostRef ?? 0)"
|
||||
class="trend-arrow"
|
||||
:class="getTrendClass(categoryData.inventoryCost ?? 0, categoryData.inventoryCostRef ?? 0)"
|
||||
>
|
||||
{{ getTrendArrow(categoryData.inventoryCost ?? 0, categoryData.inventoryCostRef ?? 0) }}
|
||||
</span>
|
||||
</span>
|
||||
<span class="metric-ref">¥{{ formatNumber(categoryData.inventoryCostRef ?? 0) }}</span>
|
||||
</div>
|
||||
<div class="metric-row" :class="getRowTrendClass(categoryData.inventoryTagAmount ?? 0, categoryData.inventoryTagAmountRef ?? 0)">
|
||||
<span class="metric-label">库存吊销额</span>
|
||||
<span class="metric-value">
|
||||
¥{{ formatNumber(categoryData.inventoryTagAmount ?? 0) }}
|
||||
<span
|
||||
v-if="getTrendArrow(categoryData.inventoryTagAmount ?? 0, categoryData.inventoryTagAmountRef ?? 0)"
|
||||
class="trend-arrow"
|
||||
:class="getTrendClass(categoryData.inventoryTagAmount ?? 0, categoryData.inventoryTagAmountRef ?? 0)"
|
||||
>
|
||||
{{ getTrendArrow(categoryData.inventoryTagAmount ?? 0, categoryData.inventoryTagAmountRef ?? 0) }}
|
||||
</span>
|
||||
</span>
|
||||
<span class="metric-ref">¥{{ formatNumber(categoryData.inventoryTagAmountRef ?? 0) }}</span>
|
||||
</div>
|
||||
<div class="metric-row" :class="getRowTrendClass(categoryData.grossMarginRate ?? 0, categoryData.grossMarginRateRef ?? 0)">
|
||||
<span class="metric-label">毛利率</span>
|
||||
<span class="metric-value">
|
||||
{{ categoryData.grossMarginRate ?? '-' }}{{ categoryData.grossMarginRate != null ? '%' : '' }}
|
||||
<span
|
||||
v-if="getTrendArrow(categoryData.grossMarginRate ?? 0, categoryData.grossMarginRateRef ?? 0)"
|
||||
class="trend-arrow"
|
||||
:class="getTrendClass(categoryData.grossMarginRate ?? 0, categoryData.grossMarginRateRef ?? 0)"
|
||||
>
|
||||
{{ getTrendArrow(categoryData.grossMarginRate ?? 0, categoryData.grossMarginRateRef ?? 0) }}
|
||||
</span>
|
||||
</span>
|
||||
<span class="metric-ref">{{ categoryData.grossMarginRateRef ?? '-' }}{{ categoryData.grossMarginRateRef != null ? '%' : '' }}</span>
|
||||
</div>
|
||||
<div class="metric-row" :class="getRowTrendClass(categoryData.digestionRate ?? 0, categoryData.digestionRateRef ?? 0)">
|
||||
<span class="metric-label">消化率</span>
|
||||
<span class="metric-value">
|
||||
{{ categoryData.digestionRate ?? '-' }}{{ categoryData.digestionRate != null ? '%' : '' }}
|
||||
<span
|
||||
v-if="getTrendArrow(categoryData.digestionRate ?? 0, categoryData.digestionRateRef ?? 0)"
|
||||
class="trend-arrow"
|
||||
:class="getTrendClass(categoryData.digestionRate ?? 0, categoryData.digestionRateRef ?? 0)"
|
||||
>
|
||||
{{ getTrendArrow(categoryData.digestionRate ?? 0, categoryData.digestionRateRef ?? 0) }}
|
||||
</span>
|
||||
</span>
|
||||
<span class="metric-ref">{{ categoryData.digestionRateRef ?? '-' }}{{ categoryData.digestionRateRef != null ? '%' : '' }}</span>
|
||||
</div>
|
||||
<div class="metric-row" :class="getRowTrendClass(categoryData.turnoverRate ?? 0, categoryData.turnoverRateRef ?? 0)">
|
||||
<span class="metric-label">动销率</span>
|
||||
<span class="metric-value">
|
||||
{{ categoryData.turnoverRate ?? '-' }}{{ categoryData.turnoverRate != null ? '%' : '' }}
|
||||
<span
|
||||
v-if="getTrendArrow(categoryData.turnoverRate ?? 0, categoryData.turnoverRateRef ?? 0)"
|
||||
class="trend-arrow"
|
||||
:class="getTrendClass(categoryData.turnoverRate ?? 0, categoryData.turnoverRateRef ?? 0)"
|
||||
>
|
||||
{{ getTrendArrow(categoryData.turnoverRate ?? 0, categoryData.turnoverRateRef ?? 0) }}
|
||||
</span>
|
||||
</span>
|
||||
<span class="metric-ref">{{ categoryData.turnoverRateRef ?? '-' }}{{ categoryData.turnoverRateRef != null ? '%' : '' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 卡片底部 -->
|
||||
<div class="card-footer" :class="{ 'middle-class-footer': useMiddleClassMetrics }">
|
||||
<span class="footer-label">{{ useMiddleClassMetrics ? '中类销售分析' : 'Diagnostic Report' }}</span>
|
||||
<span class="footer-link" @click.stop="handleViewDetail">点击查看详情 →</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { CategoryDiagnosticData } from './categoryCardListComponents.vue'
|
||||
|
||||
// 重新导出类型,方便外部使用
|
||||
export type { CategoryDiagnosticData }
|
||||
|
||||
defineOptions({ name: 'CategoryDiagnosticCard' })
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
categoryData: CategoryDiagnosticData
|
||||
/** 中类销售排名模式:显示 8 项指标(销量/销售额/库存量等) */
|
||||
useMiddleClassMetrics?: boolean
|
||||
}>(),
|
||||
{ useMiddleClassMetrics: false }
|
||||
)
|
||||
|
||||
const emit = defineEmits<{
|
||||
viewDetail: [category: CategoryDiagnosticData]
|
||||
}>()
|
||||
|
||||
// 格式化数字(支持 number | undefined,按 0 处理)
|
||||
const formatNumber = (num: number | undefined, unit: 'W' | 'K' | '' = '') => {
|
||||
const n = num ?? 0
|
||||
if (unit === 'W') {
|
||||
return (n / 10000).toFixed(1) + 'W'
|
||||
}
|
||||
if (unit === 'K') {
|
||||
return (n / 1000).toFixed(1) + 'K'
|
||||
}
|
||||
return n.toLocaleString('zh-CN')
|
||||
}
|
||||
|
||||
// 获取趋势箭头(支持 number | undefined,按 0 处理)
|
||||
const getTrendArrow = (actual: number | undefined, reference: number | undefined): string => {
|
||||
const a = actual ?? 0
|
||||
const r = reference ?? 0
|
||||
if (a > r) return '↑'
|
||||
if (a < r) return '↓'
|
||||
return ''
|
||||
}
|
||||
|
||||
// 获取趋势样式类
|
||||
const getTrendClass = (actual: number | undefined, reference: number | undefined): string => {
|
||||
const a = actual ?? 0
|
||||
const r = reference ?? 0
|
||||
if (a > r) return 'trend-up'
|
||||
if (a < r) return 'trend-down'
|
||||
return ''
|
||||
}
|
||||
|
||||
// 获取行级趋势样式类(上升金色,下跌绿色)
|
||||
const getRowTrendClass = (actual: number | undefined, reference: number | undefined): string => {
|
||||
const a = actual ?? 0
|
||||
const r = reference ?? 0
|
||||
if (a > r) return 'row-trend-up'
|
||||
if (a < r) return 'row-trend-down'
|
||||
return ''
|
||||
}
|
||||
|
||||
/** 从 status 解析标签(0全部、1低毛利、2缺货、3积压,多选用逗号拼接) */
|
||||
/** 将逗号分隔的 label 与 trend 拆成一一对应的数组,支持多值拼接 */
|
||||
const parseLabelTrendPairs = (labelStr?: string, trendStr?: string): { label: string; trend: string }[] => {
|
||||
const labels = (labelStr ?? '')
|
||||
.split(',')
|
||||
.map((s) => s.trim())
|
||||
.filter(Boolean)
|
||||
if (labels.length === 0) return []
|
||||
const trends = (trendStr ?? '')
|
||||
.split(',')
|
||||
.map((s) => s.trim())
|
||||
return labels.map((label, i) => ({ label, trend: trends[i] ?? 'flat' }))
|
||||
}
|
||||
|
||||
const getTagsFromStatus = (status?: string): string[] => {
|
||||
const codes = (status ?? '').split(',').map((s) => s.trim())
|
||||
const labels: string[] = []
|
||||
if (codes.includes('1')) labels.push('低毛利')
|
||||
if (codes.includes('2')) labels.push('缺货')
|
||||
if (codes.includes('3')) labels.push('积压')
|
||||
return labels
|
||||
}
|
||||
|
||||
// 获取卡片边框样式类(按 status 推断)
|
||||
const getCardBorderClass = (category: CategoryDiagnosticData): string => {
|
||||
const codes = (category.status ?? '').split(',').map((s) => s.trim())
|
||||
if (codes.includes('2') || codes.includes('3')) return 'border-red'
|
||||
if (codes.includes('1')) return 'border-yellow'
|
||||
return 'border-blue'
|
||||
}
|
||||
|
||||
// 获取标签类型
|
||||
const getTagType = (tag: string): 'success' | 'warning' | 'danger' | 'info' => {
|
||||
if (tag === '低毛利') return 'warning'
|
||||
if (tag === '缺货') return 'danger'
|
||||
if (tag === '积压') return 'warning'
|
||||
return 'info'
|
||||
}
|
||||
|
||||
|
||||
// 查看详情
|
||||
const handleViewDetail = () => {
|
||||
emit('viewDetail', props.categoryData)
|
||||
}
|
||||
|
||||
// 点击卡片(中类模式下直接进入详情)
|
||||
const handleCardClick = () => {
|
||||
if (props.useMiddleClassMetrics) {
|
||||
emit('viewDetail', props.categoryData)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.category-diagnostic-card {
|
||||
background: var(--el-bg-color);
|
||||
border-radius: 20px;
|
||||
border: 3px solid var(--el-border-color-lighter);
|
||||
border-top-width: 6px;
|
||||
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
|
||||
padding: 24px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 420px;
|
||||
max-width: 100%;
|
||||
|
||||
// 边框颜色变体
|
||||
&.border-red {
|
||||
border-top-color: var(--el-color-danger);
|
||||
}
|
||||
|
||||
&.border-blue {
|
||||
border-top-color: var(--el-color-primary);
|
||||
}
|
||||
|
||||
&.border-green {
|
||||
border-top-color: var(--el-color-success);
|
||||
}
|
||||
|
||||
&.border-yellow {
|
||||
border-top-color: var(--el-color-warning);
|
||||
}
|
||||
|
||||
// 卡片头部
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
margin-bottom: 20px;
|
||||
padding: 0 8px;
|
||||
|
||||
.header-left {
|
||||
flex: 1;
|
||||
|
||||
.category-name {
|
||||
font-size: 24px;
|
||||
font-weight: 900;
|
||||
color: var(--el-text-color-primary);
|
||||
letter-spacing: -0.5px;
|
||||
margin: 0 0 12px 0;
|
||||
}
|
||||
|
||||
.category-tags {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
|
||||
.label-tag {
|
||||
font-size: 11px;
|
||||
padding: 2px 8px;
|
||||
border-radius: 6px;
|
||||
font-weight: 600;
|
||||
|
||||
&.label-trend-up {
|
||||
background: #fef2f2;
|
||||
color: #ef4444; /* 上升 → 红色 */
|
||||
}
|
||||
|
||||
&.label-trend-down {
|
||||
background: #f0fdf4;
|
||||
color: #22c55e; /* 下降 → 绿色 */
|
||||
}
|
||||
|
||||
&.label-trend-flat {
|
||||
background: #fefce8;
|
||||
color: var(--el-color-warning);
|
||||
}
|
||||
}
|
||||
|
||||
.category-tag {
|
||||
font-weight: 900;
|
||||
font-size: 11px;
|
||||
padding: 2px 8px;
|
||||
border-radius: 6px;
|
||||
margin-right: 4px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
// 危险标签(滞后、高)
|
||||
:deep(.el-tag--danger) {
|
||||
background: #fee2e2 !important;
|
||||
color: #ef4444 !important;
|
||||
border: 1px solid #fecaca !important;
|
||||
}
|
||||
|
||||
// 警告标签(慢、低、预警)
|
||||
:deep(.el-tag--warning) {
|
||||
background: #fef3c7 !important;
|
||||
color: #d97706 !important;
|
||||
border: 1px solid #fde68a !important;
|
||||
}
|
||||
|
||||
// 成功标签(优秀、领先)
|
||||
:deep(.el-tag--success) {
|
||||
background: #dcfce7 !important;
|
||||
color: #22c55e !important;
|
||||
border: 1px solid #bbf7d0 !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.header-right {
|
||||
text-align: right;
|
||||
flex-shrink: 0;
|
||||
|
||||
.rate-label {
|
||||
display: block;
|
||||
font-size: 10px;
|
||||
color: var(--el-text-color-placeholder);
|
||||
font-weight: 900;
|
||||
text-transform: uppercase;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.rate-value {
|
||||
font-size: 24px;
|
||||
font-weight: 900;
|
||||
font-style: italic;
|
||||
line-height: 1;
|
||||
|
||||
&.rate-excellent {
|
||||
color: var(--el-color-success);
|
||||
}
|
||||
|
||||
&.rate-good {
|
||||
color: var(--el-color-primary);
|
||||
}
|
||||
|
||||
&.rate-normal {
|
||||
color: var(--el-color-primary);
|
||||
}
|
||||
|
||||
&.turnover-days {
|
||||
color: var(--el-color-primary);
|
||||
font-size: 28px;
|
||||
|
||||
small {
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
font-style: normal;
|
||||
margin-left: 2px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 卡片可点击样式(中类模式)
|
||||
&.clickable {
|
||||
cursor: pointer;
|
||||
transition: all 0.25s ease;
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-4px);
|
||||
box-shadow: 0 16px 40px rgba(0, 0, 0, 0.15);
|
||||
border-color: var(--el-color-primary-light-5);
|
||||
}
|
||||
}
|
||||
|
||||
// 指标表格头部
|
||||
.metric-header {
|
||||
display: grid;
|
||||
grid-template-columns: 2fr 1.5fr 1.5fr;
|
||||
gap: 4px;
|
||||
align-items: center;
|
||||
padding: 0 14px;
|
||||
height: 34px;
|
||||
font-size: 12px;
|
||||
color: var(--el-text-color-regular);
|
||||
font-weight: 900;
|
||||
border-bottom: 2px solid var(--el-border-color-lighter);
|
||||
background: var(--el-fill-color-lighter);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
// 指标数据行
|
||||
.metric-body {
|
||||
flex: 1;
|
||||
padding: 8px 0;
|
||||
}
|
||||
|
||||
.metric-row {
|
||||
display: grid;
|
||||
grid-template-columns: 2fr 1.5fr 1.5fr;
|
||||
gap: 4px;
|
||||
align-items: center;
|
||||
padding: 0 14px;
|
||||
height: 40px;
|
||||
border-bottom: 1px solid var(--el-border-color-lighter);
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
&.highlight-row {
|
||||
background: var(--el-fill-color-lighter);
|
||||
border-radius: 12px;
|
||||
margin: 8px 0;
|
||||
padding: 8px 14px;
|
||||
}
|
||||
|
||||
// 上升行 → 红色
|
||||
&.row-trend-up {
|
||||
.metric-label,
|
||||
.metric-value,
|
||||
.metric-ref,
|
||||
.trend-arrow {
|
||||
color: #ef4444 !important;
|
||||
}
|
||||
}
|
||||
|
||||
// 下降行 → 绿色
|
||||
&.row-trend-down {
|
||||
.metric-label,
|
||||
.metric-value,
|
||||
.metric-ref,
|
||||
.trend-arrow {
|
||||
color: #22c55e !important;
|
||||
}
|
||||
}
|
||||
|
||||
.metric-label {
|
||||
font-size: 13px;
|
||||
color: var(--el-text-color-regular);
|
||||
font-weight: 800;
|
||||
white-space: nowrap;
|
||||
|
||||
// 当日总额标签
|
||||
&.text-blue-800 {
|
||||
color: #1e40af;
|
||||
}
|
||||
|
||||
// 特价相关标签
|
||||
&.text-orange-600 {
|
||||
color: #ea580c;
|
||||
}
|
||||
|
||||
// 当日总成本标签
|
||||
&.text-indigo-500 {
|
||||
color: #6366f1;
|
||||
}
|
||||
|
||||
// 日目标达成标签
|
||||
&.text-blue-700 {
|
||||
color: #1d4ed8;
|
||||
}
|
||||
|
||||
// 月累计金额标签
|
||||
&.text-slate-800 {
|
||||
color: #1e293b;
|
||||
}
|
||||
}
|
||||
|
||||
.metric-value {
|
||||
font-family: 'Inter', sans-serif;
|
||||
font-size: 15px;
|
||||
font-weight: 900;
|
||||
color: var(--el-text-color-primary);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
|
||||
// 当日总额值
|
||||
&.text-blue-700 {
|
||||
color: #1d4ed8;
|
||||
}
|
||||
|
||||
// 正价相关值
|
||||
&.text-emerald-600 {
|
||||
color: #059669;
|
||||
}
|
||||
|
||||
// 特价相关值
|
||||
&.text-orange-600 {
|
||||
color: #ea580c;
|
||||
}
|
||||
|
||||
// 当日总成本值
|
||||
&.text-indigo-600 {
|
||||
color: #4f46e5;
|
||||
}
|
||||
|
||||
// 当日标准额
|
||||
&.text-slate-400 {
|
||||
color: #94a3b8;
|
||||
}
|
||||
|
||||
// 日目标达成值
|
||||
&.text-blue-800 {
|
||||
color: #1e40af;
|
||||
}
|
||||
|
||||
// 月累计金额值
|
||||
&.text-blue-700 {
|
||||
color: #1d4ed8;
|
||||
}
|
||||
|
||||
.trend-arrow {
|
||||
font-size: 18px;
|
||||
font-weight: 900;
|
||||
margin-left: 2px;
|
||||
|
||||
&.trend-up {
|
||||
color: #ef4444; /* 上升 → 红色 */
|
||||
}
|
||||
|
||||
&.trend-down {
|
||||
color: #22c55e; /* 下降 → 绿色 */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.metric-ref {
|
||||
font-family: 'Inter', sans-serif;
|
||||
font-size: 12px;
|
||||
color: var(--el-text-color-placeholder);
|
||||
font-weight: 600;
|
||||
|
||||
small {
|
||||
font-size: 9px;
|
||||
}
|
||||
|
||||
// 日目标达成参考值
|
||||
&.text-blue-400 {
|
||||
color: #60a5fa;
|
||||
}
|
||||
|
||||
// 月累计金额参考值
|
||||
&.text-slate-400 {
|
||||
color: #94a3b8;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 卡片底部
|
||||
.card-footer {
|
||||
margin-top: 16px;
|
||||
padding-top: 16px;
|
||||
border-top: 2px solid var(--el-border-color-lighter);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 8px;
|
||||
|
||||
.footer-label {
|
||||
font-size: 10px;
|
||||
color: var(--el-text-color-placeholder);
|
||||
font-weight: 900;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 2px;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.footer-link {
|
||||
font-size: 10px;
|
||||
color: var(--el-color-primary);
|
||||
font-weight: 900;
|
||||
text-transform: uppercase;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
color: var(--el-color-primary-light-3);
|
||||
}
|
||||
}
|
||||
|
||||
// 中类模式底部样式(字体更大更明显)
|
||||
&.middle-class-footer {
|
||||
margin-top: 20px;
|
||||
padding-top: 18px;
|
||||
background: linear-gradient(135deg, var(--el-fill-color-lighter) 0%, var(--el-fill-color) 100%);
|
||||
border-radius: 12px;
|
||||
padding: 14px 16px;
|
||||
border-top: none;
|
||||
|
||||
.footer-label {
|
||||
font-size: 13px;
|
||||
color: var(--el-text-color-secondary);
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
.footer-link {
|
||||
font-size: 15px;
|
||||
padding: 6px 14px;
|
||||
background: var(--el-color-primary);
|
||||
color: #fff;
|
||||
border-radius: 8px;
|
||||
text-transform: none;
|
||||
|
||||
&:hover {
|
||||
text-decoration: none;
|
||||
background: var(--el-color-primary-dark-2);
|
||||
transform: scale(1.02);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,476 @@
|
||||
<template>
|
||||
<div class="middle-class-ranking-page">
|
||||
<!-- 查询条件区域:与报表页一致,支持携带条件进入 -->
|
||||
<el-card class="query-card" shadow="never">
|
||||
<div class="query-header">
|
||||
<h1 class="page-title">中类销售排名</h1>
|
||||
</div>
|
||||
<div class="query-form">
|
||||
<div class="query-item">
|
||||
<span class="query-label">时间区间</span>
|
||||
<el-date-picker
|
||||
v-model="dateRange"
|
||||
type="daterange"
|
||||
range-separator="至"
|
||||
start-placeholder="开始日期"
|
||||
end-placeholder="结束日期"
|
||||
value-format="YYYY-MM-DD"
|
||||
style="width: 240px"
|
||||
@change="handleDateRangeChange"
|
||||
/>
|
||||
</div>
|
||||
<div class="query-item">
|
||||
<span class="query-label">门店</span>
|
||||
<el-select
|
||||
v-model="queryParams.ckdm"
|
||||
placeholder="请选择门店"
|
||||
filterable
|
||||
clearable
|
||||
style="width: 150px"
|
||||
>
|
||||
<el-option
|
||||
v-for="opt in storeOptions"
|
||||
:key="opt.value"
|
||||
:label="opt.label"
|
||||
:value="opt.value"
|
||||
/>
|
||||
</el-select>
|
||||
</div>
|
||||
<div class="query-item">
|
||||
<span class="query-label">品牌</span>
|
||||
<el-select
|
||||
v-model="queryParams.pp"
|
||||
placeholder="请选择品牌"
|
||||
filterable
|
||||
clearable
|
||||
style="width: 150px"
|
||||
>
|
||||
<el-option
|
||||
v-for="opt in brandOptions"
|
||||
:key="opt.value"
|
||||
:label="opt.label"
|
||||
:value="opt.value"
|
||||
/>
|
||||
</el-select>
|
||||
</div>
|
||||
<div class="query-item">
|
||||
<span class="query-label">季节归属</span>
|
||||
<el-select
|
||||
v-model="queryParams.season"
|
||||
placeholder="请选择季节"
|
||||
filterable
|
||||
clearable
|
||||
style="width: 150px"
|
||||
>
|
||||
<el-option
|
||||
v-for="opt in seasonOptions"
|
||||
:key="opt.value"
|
||||
:label="opt.label"
|
||||
:value="opt.value"
|
||||
/>
|
||||
</el-select>
|
||||
</div>
|
||||
<div class="query-item">
|
||||
<span class="query-label">大类</span>
|
||||
<el-select
|
||||
v-model="queryParams.category"
|
||||
placeholder="请选择大类"
|
||||
filterable
|
||||
clearable
|
||||
style="width: 150px"
|
||||
>
|
||||
<el-option
|
||||
v-for="opt in categoryOptions"
|
||||
:key="opt.value"
|
||||
:label="opt.label"
|
||||
:value="opt.value"
|
||||
/>
|
||||
</el-select>
|
||||
</div>
|
||||
<div class="query-item">
|
||||
<el-button type="primary" @click="handleQuery" :loading="loading">查询</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<!-- 中类卡片列表:左上中类名称,右上周转天+数值,8项指标 -->
|
||||
<div class="middle-class-cards-wrap" v-loading="loading">
|
||||
<div class="middle-class-cards-grid">
|
||||
<div
|
||||
v-for="(item, index) in middleClassList"
|
||||
:key="item.categoryId || index"
|
||||
class="middle-class-card-item"
|
||||
>
|
||||
<div class="card-header">
|
||||
<div class="header-left">
|
||||
<h2 class="middle-class-name">{{ item.name }}</h2>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<span class="turnover-label">周转天</span>
|
||||
<span class="turnover-value">{{ item.turnoverDays ?? '-' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-percent-row">
|
||||
<span class="percent-num">{{ item.percent }}%</span>
|
||||
<span class="percent-tag">占比</span>
|
||||
<el-progress
|
||||
:percentage="item.percent"
|
||||
:color="item.color"
|
||||
:stroke-width="8"
|
||||
:show-text="false"
|
||||
class="progress-bar"
|
||||
/>
|
||||
</div>
|
||||
<div class="metric-list">
|
||||
<div class="metric-row">
|
||||
<span class="metric-label">销量</span>
|
||||
<span class="metric-value">{{ item.salesCount ?? '-' }}</span>
|
||||
</div>
|
||||
<div class="metric-row">
|
||||
<span class="metric-label">销售额</span>
|
||||
<span class="metric-value metric-money">¥ {{ formatNumber(item.salesAmount ?? 0) }}</span>
|
||||
</div>
|
||||
<div class="metric-row">
|
||||
<span class="metric-label">库存量</span>
|
||||
<span class="metric-value">{{ item.inventoryCount ?? '-' }}</span>
|
||||
</div>
|
||||
<div class="metric-row">
|
||||
<span class="metric-label">库存成本</span>
|
||||
<span class="metric-value metric-money">¥ {{ formatNumber(item.inventoryCost ?? 0) }}</span>
|
||||
</div>
|
||||
<div class="metric-row">
|
||||
<span class="metric-label">库存吊牌额</span>
|
||||
<span class="metric-value metric-money">¥ {{ formatNumber(item.inventoryTagAmount ?? 0) }}</span>
|
||||
</div>
|
||||
<div class="metric-row">
|
||||
<span class="metric-label">毛利率</span>
|
||||
<span class="metric-value metric-rate">{{ item.grossMarginRate != null ? item.grossMarginRate + '%' : '-' }}</span>
|
||||
</div>
|
||||
<div class="metric-row">
|
||||
<span class="metric-label">消化率</span>
|
||||
<span class="metric-value metric-rate">{{ item.digestionRate != null ? item.digestionRate + '%' : '-' }}</span>
|
||||
</div>
|
||||
<div class="metric-row">
|
||||
<span class="metric-label">动销率</span>
|
||||
<span class="metric-value metric-rate">{{ item.turnoverRate != null ? item.turnoverRate + '%' : '-' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, computed, onMounted, watch } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import dayjs from 'dayjs'
|
||||
import { ReportApi } from '@/api/ydoyun/report/reportpage'
|
||||
import { ElMessage } from 'element-plus'
|
||||
|
||||
const REPORT_ID = 6
|
||||
|
||||
/** 中类销售排名项 */
|
||||
interface MiddleClassItem {
|
||||
name: string
|
||||
percent: number
|
||||
color: string
|
||||
categoryId?: string
|
||||
turnoverDays?: number
|
||||
salesCount?: number
|
||||
salesAmount?: number
|
||||
inventoryCount?: number
|
||||
inventoryCost?: number
|
||||
inventoryTagAmount?: number
|
||||
grossMarginRate?: number
|
||||
digestionRate?: number
|
||||
turnoverRate?: number
|
||||
}
|
||||
|
||||
defineOptions({ name: 'MiddleClassRanking' })
|
||||
|
||||
const route = useRoute()
|
||||
const loading = ref(false)
|
||||
const dateRange = ref<[string, string] | null>([
|
||||
dayjs().subtract(6, 'day').format('YYYY-MM-DD'),
|
||||
dayjs().format('YYYY-MM-DD')
|
||||
])
|
||||
|
||||
const queryParams = reactive({
|
||||
rq: dayjs().subtract(6, 'day').format('YYYY-MM-DD'),
|
||||
rq2: dayjs().format('YYYY-MM-DD'),
|
||||
ckdm: '',
|
||||
pp: '',
|
||||
season: '',
|
||||
category: ''
|
||||
})
|
||||
|
||||
// 门店选项(executeTable tableName=kehu)
|
||||
const storeOptions = ref<{ label: string; value: string }[]>([])
|
||||
const brandOptions = ref<{ label: string; value: string }[]>([])
|
||||
const seasonOptions = ref<{ label: string; value: string }[]>([])
|
||||
const categoryOptions = ref<{ label: string; value: string }[]>([])
|
||||
|
||||
const middleClassList = ref<MiddleClassItem[]>([
|
||||
{ name: '女装 - 连衣裙', percent: 35, color: '#3b82f6', categoryId: 'C001', salesAmount: 2948750, salesCount: 12500, turnoverDays: 28, inventoryCount: 12500, inventoryCost: 1850000, inventoryTagAmount: 3200000, grossMarginRate: 42, digestionRate: 68, turnoverRate: 55 },
|
||||
{ name: '女装 - 休闲裤', percent: 22, color: '#60a5fa', categoryId: 'C002', salesAmount: 1853500, salesCount: 8900, turnoverDays: 35, inventoryCount: 9800, inventoryCost: 1420000, inventoryTagAmount: 2450000, grossMarginRate: 38, digestionRate: 62, turnoverRate: 48 },
|
||||
{ name: '女装 - 风衣/外套', percent: 18, color: '#93c5fd', categoryId: 'C003', salesAmount: 1516500, salesCount: 3200, turnoverDays: 52, inventoryCount: 8500, inventoryCost: 1180000, inventoryTagAmount: 2000000, grossMarginRate: 40, digestionRate: 58, turnoverRate: 52 },
|
||||
{ name: '女装 - 衬衫', percent: 12, color: '#bfdbfe', categoryId: 'C004', salesAmount: 1011000, salesCount: 6800, turnoverDays: 42, inventoryCount: 7200, inventoryCost: 920000, inventoryTagAmount: 1580000, grossMarginRate: 36, digestionRate: 52, turnoverRate: 45 },
|
||||
{ name: '女装 - 毛衫', percent: 8, color: '#dbeafe', categoryId: 'C005', salesAmount: 674000, salesCount: 4200, turnoverDays: 58, inventoryCount: 5500, inventoryCost: 680000, inventoryTagAmount: 1150000, grossMarginRate: 39, digestionRate: 48, turnoverRate: 42 },
|
||||
{ name: '女装 - T恤', percent: 5, color: '#eff6ff', categoryId: 'C006', salesAmount: 421000, salesCount: 2600, turnoverDays: 65, inventoryCount: 4200, inventoryCost: 480000, inventoryTagAmount: 820000, grossMarginRate: 37, digestionRate: 45, turnoverRate: 38 }
|
||||
])
|
||||
|
||||
function resolveTableList(res: any): any[] {
|
||||
if (res == null) return []
|
||||
if (Array.isArray(res)) return res
|
||||
const data = (res as any).data
|
||||
if (Array.isArray(data)) return data
|
||||
const list = (res as any).list ?? (res as any).result ?? (res as any).rows
|
||||
return Array.isArray(list) ? list : []
|
||||
}
|
||||
|
||||
async function fetchBrandOptions() {
|
||||
try {
|
||||
const res = await ReportApi.executeTable({ reportId: REPORT_ID, tableName: 'pinpai' })
|
||||
const list = resolveTableList(res)
|
||||
brandOptions.value = (list || []).map((item: any) => ({ label: item?.PPMC ?? '', value: item?.PPDM != null ? String(item.PPDM) : '' })).filter((o: any) => o.label && o.value)
|
||||
} catch (_) {
|
||||
brandOptions.value = []
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchSeasonOptions() {
|
||||
try {
|
||||
const res = await ReportApi.executeTable({ reportId: REPORT_ID, tableName: 'jijie' })
|
||||
const list = resolveTableList(res)
|
||||
seasonOptions.value = (list || []).map((item: any) => ({ label: item?.JJMC ?? '', value: item?.JJDM != null ? String(item.JJDM) : '' })).filter((o: any) => o.label && o.value)
|
||||
} catch (_) {
|
||||
seasonOptions.value = []
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchCategoryOptions() {
|
||||
try {
|
||||
const res = await ReportApi.executeTable({ reportId: REPORT_ID, tableName: 'dalei' })
|
||||
const list = resolveTableList(res)
|
||||
categoryOptions.value = (list || []).map((item: any) => ({ label: item?.DLMC ?? '', value: item?.DLDM != null ? String(item.DLDM) : '' })).filter((o: any) => o.label && o.value)
|
||||
} catch (_) {
|
||||
categoryOptions.value = []
|
||||
}
|
||||
}
|
||||
|
||||
/** kehu 返回:{ khmc, khdm };兼容后端返回大类结构 { DLMC, DLDM } */
|
||||
function mapKehuToOptions(list: any[]): { label: string; value: string }[] {
|
||||
return (list || []).map((item: any) => {
|
||||
const label = item?.khmc ?? item?.CKMC ?? item?.KHMC ?? item?.DLMC ?? item?.label ?? item?.name ?? ''
|
||||
const value = item?.khdm != null ? String(item.khdm) : item?.CKDM != null ? String(item.CKDM) : item?.KHDM != null ? String(item.KHDM) : item?.DLDM != null ? String(item.DLDM) : item?.value != null ? String(item.value) : item?.code != null ? String(item.code) : ''
|
||||
return { label, value }
|
||||
}).filter((o: any) => o.label && o.value)
|
||||
}
|
||||
|
||||
async function fetchStoreOptions() {
|
||||
try {
|
||||
const res = await ReportApi.executeTable({ reportId: REPORT_ID, tableName: 'kehu' })
|
||||
const list = resolveTableList(res)
|
||||
storeOptions.value = mapKehuToOptions(list)
|
||||
} catch (_) {
|
||||
storeOptions.value = []
|
||||
}
|
||||
}
|
||||
|
||||
function applyQueryFromRoute() {
|
||||
const q = route.query
|
||||
if (q.rq && typeof q.rq === 'string') queryParams.rq = q.rq
|
||||
if (q.rq2 && typeof q.rq2 === 'string') queryParams.rq2 = q.rq2
|
||||
if (q.ckdm !== undefined) queryParams.ckdm = String(q.ckdm)
|
||||
if (q.pp !== undefined) queryParams.pp = String(q.pp)
|
||||
if (q.season !== undefined) queryParams.season = String(q.season)
|
||||
if (q.category !== undefined) queryParams.category = String(q.category)
|
||||
dateRange.value = [queryParams.rq, queryParams.rq2]
|
||||
}
|
||||
|
||||
function handleDateRangeChange(val: [string, string] | null) {
|
||||
if (val && val.length === 2) {
|
||||
queryParams.rq = val[0]
|
||||
queryParams.rq2 = val[1]
|
||||
}
|
||||
}
|
||||
|
||||
function handleQuery() {
|
||||
loading.value = true
|
||||
setTimeout(() => {
|
||||
loading.value = false
|
||||
ElMessage.success('查询成功')
|
||||
}, 300)
|
||||
}
|
||||
|
||||
function formatNumber(n: number): string {
|
||||
if (n >= 10000) return (n / 10000).toFixed(1) + 'w'
|
||||
if (n >= 1000) return (n / 1000).toFixed(1) + 'k'
|
||||
return String(Math.round(n))
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
applyQueryFromRoute()
|
||||
await Promise.all([fetchBrandOptions(), fetchSeasonOptions(), fetchCategoryOptions(), fetchStoreOptions()])
|
||||
})
|
||||
|
||||
watch(() => route.query, () => applyQueryFromRoute(), { deep: true })
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.middle-class-ranking-page {
|
||||
padding: 16px;
|
||||
background: var(--el-bg-color-page);
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.query-card {
|
||||
margin-bottom: 20px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.query-header {
|
||||
margin-bottom: 16px;
|
||||
|
||||
.page-title {
|
||||
font-size: 20px;
|
||||
font-weight: 700;
|
||||
color: var(--el-text-color-primary);
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.query-form {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 20px 24px;
|
||||
|
||||
.query-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
|
||||
.query-label {
|
||||
font-size: 14px;
|
||||
color: var(--el-text-color-regular);
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.middle-class-cards-wrap {
|
||||
min-height: 200px;
|
||||
}
|
||||
|
||||
.middle-class-cards-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(340px, 1fr));
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.middle-class-card-item {
|
||||
background: var(--el-fill-color-lighter);
|
||||
border: 1px solid var(--el-border-color-lighter);
|
||||
border-radius: 12px;
|
||||
padding: 16px;
|
||||
transition: box-shadow 0.2s ease;
|
||||
}
|
||||
|
||||
.middle-class-card-item:hover {
|
||||
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
gap: 12px;
|
||||
margin-bottom: 12px;
|
||||
padding-bottom: 12px;
|
||||
border-bottom: 1px solid var(--el-border-color-lighter);
|
||||
}
|
||||
|
||||
.header-left {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.middle-class-name {
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
color: var(--el-text-color-primary);
|
||||
margin: 0;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.header-right {
|
||||
flex-shrink: 0;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.turnover-label {
|
||||
display: block;
|
||||
font-size: 11px;
|
||||
color: var(--el-text-color-placeholder);
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.turnover-value {
|
||||
font-size: 18px;
|
||||
font-weight: 800;
|
||||
color: var(--el-color-primary);
|
||||
}
|
||||
|
||||
.card-percent-row {
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
|
||||
.card-percent-row .percent-num {
|
||||
font-weight: 800;
|
||||
font-size: 14px;
|
||||
color: var(--el-color-primary);
|
||||
margin-right: 6px;
|
||||
}
|
||||
|
||||
.card-percent-row .percent-tag {
|
||||
font-size: 11px;
|
||||
color: var(--el-text-color-placeholder);
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.metric-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.metric-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 8px 10px;
|
||||
font-size: 13px;
|
||||
border-radius: 6px;
|
||||
background: var(--el-fill-color);
|
||||
}
|
||||
|
||||
.metric-label {
|
||||
color: var(--el-text-color-secondary);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.metric-value {
|
||||
font-weight: 700;
|
||||
color: var(--el-text-color-primary);
|
||||
}
|
||||
|
||||
.metric-money {
|
||||
color: var(--el-color-primary);
|
||||
}
|
||||
|
||||
.metric-rate {
|
||||
color: var(--el-color-success);
|
||||
}
|
||||
</style>
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,525 @@
|
||||
<template>
|
||||
<div class="supplier-ranking-page">
|
||||
<!-- 查询条件区域:与报表页一致,支持携带条件进入 -->
|
||||
<el-card class="query-card" shadow="never">
|
||||
<div class="query-header">
|
||||
<h1 class="page-title">供货商销售排行</h1>
|
||||
</div>
|
||||
<div class="query-form">
|
||||
<div class="query-item">
|
||||
<span class="query-label">时间区间</span>
|
||||
<el-date-picker
|
||||
v-model="dateRange"
|
||||
type="daterange"
|
||||
range-separator="至"
|
||||
start-placeholder="开始日期"
|
||||
end-placeholder="结束日期"
|
||||
value-format="YYYY-MM-DD"
|
||||
style="width: 240px"
|
||||
@change="handleDateRangeChange"
|
||||
/>
|
||||
</div>
|
||||
<div class="query-item">
|
||||
<span class="query-label">门店</span>
|
||||
<el-select
|
||||
v-model="queryParams.ckdm"
|
||||
placeholder="请选择门店"
|
||||
filterable
|
||||
clearable
|
||||
style="width: 150px"
|
||||
>
|
||||
<el-option
|
||||
v-for="opt in storeOptions"
|
||||
:key="opt.value"
|
||||
:label="opt.label"
|
||||
:value="opt.value"
|
||||
/>
|
||||
</el-select>
|
||||
</div>
|
||||
<div class="query-item">
|
||||
<span class="query-label">品牌</span>
|
||||
<el-select
|
||||
v-model="queryParams.pp"
|
||||
placeholder="请选择品牌"
|
||||
filterable
|
||||
clearable
|
||||
style="width: 150px"
|
||||
>
|
||||
<el-option
|
||||
v-for="opt in brandOptions"
|
||||
:key="opt.value"
|
||||
:label="opt.label"
|
||||
:value="opt.value"
|
||||
/>
|
||||
</el-select>
|
||||
</div>
|
||||
<div class="query-item">
|
||||
<span class="query-label">季节归属</span>
|
||||
<el-select
|
||||
v-model="queryParams.season"
|
||||
placeholder="请选择季节"
|
||||
filterable
|
||||
clearable
|
||||
style="width: 150px"
|
||||
>
|
||||
<el-option
|
||||
v-for="opt in seasonOptions"
|
||||
:key="opt.value"
|
||||
:label="opt.label"
|
||||
:value="opt.value"
|
||||
/>
|
||||
</el-select>
|
||||
</div>
|
||||
<div class="query-item">
|
||||
<span class="query-label">大类</span>
|
||||
<el-select
|
||||
v-model="queryParams.category"
|
||||
placeholder="请选择大类"
|
||||
filterable
|
||||
clearable
|
||||
style="width: 150px"
|
||||
>
|
||||
<el-option
|
||||
v-for="opt in categoryOptions"
|
||||
:key="opt.value"
|
||||
:label="opt.label"
|
||||
:value="opt.value"
|
||||
/>
|
||||
</el-select>
|
||||
</div>
|
||||
<div class="query-item">
|
||||
<el-button type="primary" @click="handleQuery" :loading="loading">查询</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<!-- 供货商卡片列表 -->
|
||||
<div class="supplier-cards-wrap" v-loading="loading">
|
||||
<div class="supplier-cards-grid">
|
||||
<div
|
||||
v-for="(item, index) in supplierList"
|
||||
:key="item.supplierId || index"
|
||||
class="supplier-card-item"
|
||||
>
|
||||
<div class="supplier-card-header">
|
||||
<span class="supplier-rank" :class="getRankClass(index)">{{ index + 1 }}</span>
|
||||
<span class="supplier-name">{{ item.name }}</span>
|
||||
<span class="supplier-amount">¥ {{ formatNumber(item.salesAmount) }}</span>
|
||||
</div>
|
||||
<div class="supplier-percent-row">
|
||||
<span class="percent-num">{{ item.percent }}%</span>
|
||||
<span class="percent-tag">占比</span>
|
||||
<el-progress
|
||||
:percentage="item.percent"
|
||||
:color="item.color"
|
||||
:stroke-width="8"
|
||||
:show-text="false"
|
||||
class="supplier-progress-bar"
|
||||
/>
|
||||
</div>
|
||||
<div class="supplier-detail">
|
||||
<div class="detail-section">
|
||||
<div class="detail-section-title">销售与库存</div>
|
||||
<div class="detail-grid">
|
||||
<div class="detail-item">
|
||||
<span class="detail-label">SKC</span>
|
||||
<span class="detail-value">{{ item.skc ?? '-' }}</span>
|
||||
</div>
|
||||
<div class="detail-item">
|
||||
<span class="detail-label">销量</span>
|
||||
<span class="detail-value">{{ item.salesCount ?? '-' }}</span>
|
||||
</div>
|
||||
<div class="detail-item">
|
||||
<span class="detail-label">销售额</span>
|
||||
<span class="detail-value detail-value-money">¥ {{ formatNumber(item.salesAmount) }}</span>
|
||||
</div>
|
||||
<div class="detail-item">
|
||||
<span class="detail-label">库存量</span>
|
||||
<span class="detail-value">{{ item.inventoryCount ?? '-' }}</span>
|
||||
</div>
|
||||
<div class="detail-item">
|
||||
<span class="detail-label">库存成本</span>
|
||||
<span class="detail-value detail-value-money">¥ {{ formatNumber(item.inventoryCost ?? 0) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="detail-section">
|
||||
<div class="detail-section-title">效率指标</div>
|
||||
<div class="detail-grid">
|
||||
<div class="detail-item">
|
||||
<span class="detail-label">毛利率</span>
|
||||
<span class="detail-value detail-value-rate">{{ item.grossMarginRate != null ? item.grossMarginRate + '%' : '-' }}</span>
|
||||
</div>
|
||||
<div class="detail-item">
|
||||
<span class="detail-label">消化率</span>
|
||||
<span class="detail-value detail-value-rate">{{ item.digestionRate != null ? item.digestionRate + '%' : '-' }}</span>
|
||||
</div>
|
||||
<div class="detail-item">
|
||||
<span class="detail-label">动销率</span>
|
||||
<span class="detail-value detail-value-rate">{{ item.turnOverRate != null ? item.turnOverRate + '%' : '-' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, computed, onMounted, watch } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import dayjs from 'dayjs'
|
||||
import { ReportApi } from '@/api/ydoyun/report/reportpage'
|
||||
import { ElMessage } from 'element-plus'
|
||||
|
||||
const REPORT_ID = 6
|
||||
|
||||
/** 供货商销售排行项 */
|
||||
interface SupplierContributionData {
|
||||
name: string
|
||||
percent: number
|
||||
color: string
|
||||
supplierId?: string
|
||||
salesAmount: number
|
||||
skc?: number
|
||||
salesCount?: number
|
||||
inventoryCount?: number
|
||||
inventoryCost?: number
|
||||
grossMarginRate?: number
|
||||
digestionRate?: number
|
||||
turnOverRate?: number
|
||||
}
|
||||
|
||||
defineOptions({ name: 'SupplierRanking' })
|
||||
|
||||
const route = useRoute()
|
||||
const loading = ref(false)
|
||||
const dateRange = ref<[string, string] | null>([
|
||||
dayjs().subtract(6, 'day').format('YYYY-MM-DD'),
|
||||
dayjs().format('YYYY-MM-DD')
|
||||
])
|
||||
|
||||
const queryParams = reactive({
|
||||
rq: dayjs().subtract(6, 'day').format('YYYY-MM-DD'),
|
||||
rq2: dayjs().format('YYYY-MM-DD'),
|
||||
ckdm: '',
|
||||
pp: '',
|
||||
season: '',
|
||||
category: ''
|
||||
})
|
||||
|
||||
// 门店选项(executeTable tableName=kehu)
|
||||
const storeOptions = ref<{ label: string; value: string }[]>([])
|
||||
const brandOptions = ref<{ label: string; value: string }[]>([])
|
||||
const seasonOptions = ref<{ label: string; value: string }[]>([])
|
||||
const categoryOptions = ref<{ label: string; value: string }[]>([])
|
||||
|
||||
const supplierList = ref<SupplierContributionData[]>([
|
||||
{ name: '供货商A', percent: 28, color: '#3b82f6', supplierId: 'S001', salesAmount: 2450000, skc: 320, salesCount: 10200, inventoryCount: 18500, inventoryCost: 920000, grossMarginRate: 42, digestionRate: 68, turnOverRate: 55 },
|
||||
{ name: '供货商B', percent: 22, color: '#60a5fa', supplierId: 'S002', salesAmount: 1920000, skc: 280, salesCount: 8900, inventoryCount: 15200, inventoryCost: 760000, grossMarginRate: 38, digestionRate: 62, turnOverRate: 48 },
|
||||
{ name: '供货商C', percent: 18, color: '#93c5fd', supplierId: 'S003', salesAmount: 1575000, skc: 210, salesCount: 6500, inventoryCount: 12800, inventoryCost: 640000, grossMarginRate: 40, digestionRate: 58, turnOverRate: 52 },
|
||||
{ name: '供货商D', percent: 14, color: '#bfdbfe', supplierId: 'S004', salesAmount: 1220000, skc: 165, salesCount: 4800, inventoryCount: 9800, inventoryCost: 490000, grossMarginRate: 36, digestionRate: 52, turnOverRate: 45 },
|
||||
{ name: '供货商E', percent: 10, color: '#dbeafe', supplierId: 'S005', salesAmount: 875000, skc: 120, salesCount: 3500, inventoryCount: 7200, inventoryCost: 360000, grossMarginRate: 39, digestionRate: 48, turnOverRate: 42 },
|
||||
{ name: '供货商F', percent: 8, color: '#eff6ff', supplierId: 'S006', salesAmount: 700000, skc: 95, salesCount: 2800, inventoryCount: 5500, inventoryCost: 275000, grossMarginRate: 37, digestionRate: 45, turnOverRate: 38 }
|
||||
])
|
||||
|
||||
function resolveTableList(res: any): any[] {
|
||||
if (res == null) return []
|
||||
if (Array.isArray(res)) return res
|
||||
const data = (res as any).data
|
||||
if (Array.isArray(data)) return data
|
||||
const list = (res as any).list ?? (res as any).result ?? (res as any).rows
|
||||
return Array.isArray(list) ? list : []
|
||||
}
|
||||
|
||||
async function fetchBrandOptions() {
|
||||
try {
|
||||
const res = await ReportApi.executeTable({ reportId: REPORT_ID, tableName: 'pinpai' })
|
||||
const list = resolveTableList(res)
|
||||
brandOptions.value = (list || []).map((item: any) => ({ label: item?.PPMC ?? '', value: item?.PPDM != null ? String(item.PPDM) : '' })).filter((o: any) => o.label && o.value)
|
||||
} catch (_) {
|
||||
brandOptions.value = []
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchSeasonOptions() {
|
||||
try {
|
||||
const res = await ReportApi.executeTable({ reportId: REPORT_ID, tableName: 'jijie' })
|
||||
const list = resolveTableList(res)
|
||||
seasonOptions.value = (list || []).map((item: any) => ({ label: item?.JJMC ?? '', value: item?.JJDM != null ? String(item.JJDM) : '' })).filter((o: any) => o.label && o.value)
|
||||
} catch (_) {
|
||||
seasonOptions.value = []
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchCategoryOptions() {
|
||||
try {
|
||||
const res = await ReportApi.executeTable({ reportId: REPORT_ID, tableName: 'dalei' })
|
||||
const list = resolveTableList(res)
|
||||
categoryOptions.value = (list || []).map((item: any) => ({ label: item?.DLMC ?? '', value: item?.DLDM != null ? String(item.DLDM) : '' })).filter((o: any) => o.label && o.value)
|
||||
} catch (_) {
|
||||
categoryOptions.value = []
|
||||
}
|
||||
}
|
||||
|
||||
/** kehu 返回:{ khmc, khdm };兼容后端返回大类结构 { DLMC, DLDM } */
|
||||
function mapKehuToOptions(list: any[]): { label: string; value: string }[] {
|
||||
return (list || []).map((item: any) => {
|
||||
const label = item?.khmc ?? item?.CKMC ?? item?.KHMC ?? item?.DLMC ?? item?.label ?? item?.name ?? ''
|
||||
const value = item?.khdm != null ? String(item.khdm) : item?.CKDM != null ? String(item.CKDM) : item?.KHDM != null ? String(item.KHDM) : item?.DLDM != null ? String(item.DLDM) : item?.value != null ? String(item.value) : item?.code != null ? String(item.code) : ''
|
||||
return { label, value }
|
||||
}).filter((o: any) => o.label && o.value)
|
||||
}
|
||||
|
||||
async function fetchStoreOptions() {
|
||||
try {
|
||||
const res = await ReportApi.executeTable({ reportId: REPORT_ID, tableName: 'kehu' })
|
||||
const list = resolveTableList(res)
|
||||
storeOptions.value = mapKehuToOptions(list)
|
||||
} catch (_) {
|
||||
storeOptions.value = []
|
||||
}
|
||||
}
|
||||
|
||||
function applyQueryFromRoute() {
|
||||
const q = route.query
|
||||
if (q.rq && typeof q.rq === 'string') queryParams.rq = q.rq
|
||||
if (q.rq2 && typeof q.rq2 === 'string') queryParams.rq2 = q.rq2
|
||||
if (q.ckdm !== undefined) queryParams.ckdm = String(q.ckdm)
|
||||
if (q.pp !== undefined) queryParams.pp = String(q.pp)
|
||||
if (q.season !== undefined) queryParams.season = String(q.season)
|
||||
if (q.category !== undefined) queryParams.category = String(q.category)
|
||||
dateRange.value = [queryParams.rq, queryParams.rq2]
|
||||
}
|
||||
|
||||
function handleDateRangeChange(val: [string, string] | null) {
|
||||
if (val && val.length === 2) {
|
||||
queryParams.rq = val[0]
|
||||
queryParams.rq2 = val[1]
|
||||
}
|
||||
}
|
||||
|
||||
function handleQuery() {
|
||||
loading.value = true
|
||||
// 可在此调用供货商排行接口,传入 queryParams
|
||||
setTimeout(() => {
|
||||
loading.value = false
|
||||
ElMessage.success('查询成功')
|
||||
}, 300)
|
||||
}
|
||||
|
||||
function formatNumber(n: number): string {
|
||||
if (n >= 10000) return (n / 10000).toFixed(1) + 'w'
|
||||
if (n >= 1000) return (n / 1000).toFixed(1) + 'k'
|
||||
return String(n)
|
||||
}
|
||||
|
||||
function getRankClass(index: number): string {
|
||||
if (index === 0) return 'rank-1'
|
||||
if (index === 1) return 'rank-2'
|
||||
if (index === 2) return 'rank-3'
|
||||
return ''
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
applyQueryFromRoute()
|
||||
await Promise.all([fetchBrandOptions(), fetchSeasonOptions(), fetchCategoryOptions(), fetchStoreOptions()])
|
||||
})
|
||||
|
||||
watch(() => route.query, () => applyQueryFromRoute(), { deep: true })
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.supplier-ranking-page {
|
||||
padding: 16px;
|
||||
background: var(--el-bg-color-page);
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.query-card {
|
||||
margin-bottom: 20px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.query-header {
|
||||
margin-bottom: 16px;
|
||||
|
||||
.page-title {
|
||||
font-size: 20px;
|
||||
font-weight: 700;
|
||||
color: var(--el-text-color-primary);
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.query-form {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 20px 24px;
|
||||
|
||||
.query-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
|
||||
.query-label {
|
||||
font-size: 14px;
|
||||
color: var(--el-text-color-regular);
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.supplier-cards-wrap {
|
||||
min-height: 200px;
|
||||
}
|
||||
|
||||
.supplier-cards-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.supplier-card-item {
|
||||
background: var(--el-fill-color-lighter);
|
||||
border: 1px solid var(--el-border-color-lighter);
|
||||
border-radius: 12px;
|
||||
padding: 16px;
|
||||
transition: box-shadow 0.2s ease;
|
||||
}
|
||||
|
||||
.supplier-card-item:hover {
|
||||
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.supplier-card-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
margin-bottom: 12px;
|
||||
padding-bottom: 12px;
|
||||
border-bottom: 1px solid var(--el-border-color-lighter);
|
||||
}
|
||||
|
||||
.supplier-rank {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 8px;
|
||||
font-weight: 800;
|
||||
font-size: 14px;
|
||||
color: var(--el-text-color-primary);
|
||||
background: var(--el-fill-color);
|
||||
border: 1px solid var(--el-border-color-lighter);
|
||||
}
|
||||
|
||||
.supplier-rank.rank-1 {
|
||||
background: #f59e0b;
|
||||
color: #fff;
|
||||
border-color: #d97706;
|
||||
}
|
||||
|
||||
.supplier-rank.rank-2 {
|
||||
background: #64748b;
|
||||
color: #fff;
|
||||
border-color: #475569;
|
||||
}
|
||||
|
||||
.supplier-rank.rank-3 {
|
||||
background: #b45309;
|
||||
color: #fff;
|
||||
border-color: #92400e;
|
||||
}
|
||||
|
||||
.supplier-name {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
font-weight: 700;
|
||||
font-size: 15px;
|
||||
color: var(--el-text-color-primary);
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.supplier-amount {
|
||||
font-weight: 800;
|
||||
font-size: 14px;
|
||||
color: var(--el-color-primary);
|
||||
}
|
||||
|
||||
.supplier-percent-row {
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
|
||||
.supplier-percent-row .percent-num {
|
||||
font-weight: 800;
|
||||
font-size: 14px;
|
||||
color: var(--el-color-primary);
|
||||
margin-right: 6px;
|
||||
}
|
||||
|
||||
.supplier-percent-row .percent-tag {
|
||||
font-size: 11px;
|
||||
color: var(--el-text-color-placeholder);
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.supplier-progress-bar {
|
||||
margin-top: 6px;
|
||||
}
|
||||
|
||||
.supplier-detail {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.detail-section-title {
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
color: var(--el-text-color-placeholder);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.detail-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 8px 12px;
|
||||
}
|
||||
|
||||
.detail-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
font-size: 12px;
|
||||
padding: 6px 8px;
|
||||
background: var(--el-fill-color);
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.detail-label {
|
||||
color: var(--el-text-color-secondary);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.detail-value {
|
||||
font-weight: 700;
|
||||
color: var(--el-text-color-primary);
|
||||
}
|
||||
|
||||
.detail-value-money {
|
||||
color: var(--el-color-primary);
|
||||
}
|
||||
|
||||
.detail-value-rate {
|
||||
color: var(--el-color-success);
|
||||
}
|
||||
</style>
|
||||
File diff suppressed because it is too large
Load Diff
1737
src/views/ydoyun/report/lijun/reportpage6/detail.vue
Normal file
1737
src/views/ydoyun/report/lijun/reportpage6/detail.vue
Normal file
File diff suppressed because it is too large
Load Diff
4093
src/views/ydoyun/report/lijun/reportpage6/index.vue
Normal file
4093
src/views/ydoyun/report/lijun/reportpage6/index.vue
Normal file
File diff suppressed because it is too large
Load Diff
183
src/views/ydoyun/report/lijun/reportpage6/品类21.html
Normal file
183
src/views/ydoyun/report/lijun/reportpage6/品类21.html
Normal file
@@ -0,0 +1,183 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>品类全维度诊断看板 - 全字段版</title>
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<style>
|
||||
body { background-color: #f4f9ff; font-family: 'PingFang SC', sans-serif; color: #1e293b; }
|
||||
|
||||
/* 强化框体:显著边框与深度阴影 */
|
||||
.category-card {
|
||||
background: #ffffff; border-radius: 20px;
|
||||
border: 3px solid #e2eefe;
|
||||
box-shadow: 0 10px 30px rgba(59, 130, 246, 0.1);
|
||||
transition: all 0.3s ease; display: flex; flex-direction: column;
|
||||
}
|
||||
.category-card:hover { border-color: #3b82f6; box-shadow: 0 15px 40px rgba(59, 130, 246, 0.2); }
|
||||
|
||||
/* 严格三列对齐比例 */
|
||||
.grid-layout {
|
||||
display: grid;
|
||||
grid-template-columns: 2fr 1.5fr 1.5fr;
|
||||
gap: 4px;
|
||||
align-items: center;
|
||||
padding: 0 14px;
|
||||
}
|
||||
|
||||
.metric-header {
|
||||
height: 34px;
|
||||
font-size: 12px;
|
||||
color: #64748b;
|
||||
font-weight: 900;
|
||||
border-bottom: 2px solid #f0f6ff;
|
||||
background: #fcfdfe;
|
||||
}
|
||||
|
||||
.metric-row {
|
||||
height: 40px;
|
||||
border-bottom: 1px solid #f8fbff;
|
||||
}
|
||||
.metric-row:last-child { border-bottom: none; }
|
||||
|
||||
/* 字体加粗加大 */
|
||||
.label { font-size: 13px; color: #475569; font-weight: 800; white-space: nowrap; }
|
||||
.val-actual { font-family: 'Inter', sans-serif; font-size: 15px; font-weight: 900; color: #0f172a; display: flex; align-items: center; }
|
||||
.val-ref { font-family: 'Inter', sans-serif; font-size: 12px; color: #94a3b8; font-weight: 600; }
|
||||
|
||||
/* 体检报告风趋势箭头 */
|
||||
.arrow-up { color: #ef4444; margin-left: 2px; font-size: 18px; font-weight: 900; }
|
||||
.arrow-down { color: #3b82f6; margin-left: 2px; font-size: 18px; font-weight: 900; }
|
||||
|
||||
.badge { padding: 2px 8px; border-radius: 6px; font-size: 11px; font-weight: 900; margin-right: 4px; margin-bottom: 4px; }
|
||||
.bg-danger { background: #fee2e2; color: #ef4444; border: 1px solid #fecaca; }
|
||||
.bg-warn { background: #fef3c7; color: #d97706; border: 1px solid #fde68a; }
|
||||
.bg-success { background: #dcfce7; color: #22c55e; border: 1px solid #bbf7d0; }
|
||||
</style>
|
||||
</head>
|
||||
<body class="p-4 md:p-8">
|
||||
<div class="max-w-[1850px] mx-auto">
|
||||
<header class="mb-8 border-b-4 border-blue-200 pb-4 flex justify-between items-end">
|
||||
<div>
|
||||
<h1 class="text-3xl font-black text-blue-900 tracking-tighter uppercase">Category Diagnostic <span class="text-blue-600 italic">PRO Full</span></h1>
|
||||
<p class="text-slate-500 text-xs font-black mt-1 uppercase tracking-widest">全字段不缩减对齐版 | 2026-01-12</p>
|
||||
</div>
|
||||
<div class="bg-white px-4 py-2 rounded-xl border-2 border-blue-100 shadow-sm">
|
||||
<span class="text-blue-600 font-black text-sm uppercase">15项原始字段已锁定</span>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6">
|
||||
<script>
|
||||
// 完整品类数据定义
|
||||
const allCategories = [
|
||||
{ name: "女装", tags: ["进度偏慢", "低毛利"], rate: "43.1%", color: "blue", s_act: 137, s_ref: 155, a_act: "8,425", a_ref: "11,200", target: "9,699" },
|
||||
{ name: "男装", tags: ["稳步运行"], rate: "52.4%", color: "blue", s_act: 101, s_ref: 145, a_act: "8,049", a_ref: "13,557", target: "10,109" },
|
||||
{ name: "百货", tags: ["销量领先"], rate: "65.8%", color: "blue", s_act: 640, s_ref: 1222, a_act: "3,952", a_ref: "7,516", target: "5,082" },
|
||||
{ name: "鞋区", tags: ["严重滞后", "特价过高"], rate: "19.8%", color: "red", s_act: 127, s_ref: 187, a_act: "3,569", a_ref: "6,510", target: "5,109" },
|
||||
{ name: "内衣睡衣", tags: ["库存预警"], rate: "54.4%", color: "yellow", s_act: 279, s_ref: 353, a_act: "3,338", a_ref: "4,323", target: "5,191" },
|
||||
{ name: "床上用品", tags: ["达成优秀"], rate: "90.0%", color: "green", s_act: 51, s_ref: 78, a_act: "2,904", a_ref: "5,776", target: "2,732" },
|
||||
{ name: "童装", tags: ["同期持平"], rate: "41.7%", color: "blue", s_act: 51, s_ref: 163, a_act: "2,414", a_ref: "10,380", target: "7,951" },
|
||||
{ name: "玩具", tags: ["基数较小"], rate: "43.7%", color: "blue", s_act: 16, s_ref: 42, a_act: "282", a_ref: "842", target: "546" }
|
||||
];
|
||||
|
||||
allCategories.forEach(item => {
|
||||
const tagHtml = item.tags.map(t => {
|
||||
let cls = t.includes('慢') || t.includes('低') || t.includes('预警') ? 'bg-warn' :
|
||||
t.includes('滞后') || t.includes('高') ? 'bg-danger' : 'bg-success';
|
||||
return `<span class="badge ${cls}">${t}</span>`;
|
||||
}).join('');
|
||||
|
||||
const getArrow = (actStr, refStr) => {
|
||||
const act = parseFloat(actStr.replace(/,/g, ''));
|
||||
const ref = parseFloat(refStr.replace(/,/g, ''));
|
||||
if (act > ref) return '<span class="arrow-up">↑</span>';
|
||||
if (act < ref) return '<span class="arrow-down">↓</span>';
|
||||
return '';
|
||||
};
|
||||
|
||||
document.write(`
|
||||
<div class="category-card p-6 border-t-[6px] ${item.color === 'red' ? 'border-t-red-500' : 'border-t-blue-500'}">
|
||||
<div class="flex justify-between items-start mb-4 px-2">
|
||||
<div>
|
||||
<h2 class="text-2xl font-black text-slate-900 tracking-tighter">${item.name}</h2>
|
||||
<div class="mt-2 flex flex-wrap">${tagHtml}</div>
|
||||
</div>
|
||||
<div class="text-right">
|
||||
<span class="text-[10px] text-slate-400 block font-black uppercase mb-1">月度达成率</span>
|
||||
<span class="text-2xl font-black ${item.rate.startsWith('9') ? 'text-green-600' : 'text-blue-600'} italic leading-none">${item.rate}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid-layout metric-header uppercase tracking-wider">
|
||||
<span>检测指标</span>
|
||||
<span>实际结果</span>
|
||||
<span>基准参考</span>
|
||||
</div>
|
||||
|
||||
<div class="flex-grow py-1">
|
||||
<div class="grid-layout metric-row">
|
||||
<span class="label">当日销量</span>
|
||||
<span class="val-actual">${item.s_act} ${getArrow(item.s_act.toString(), item.s_ref.toString())}</span>
|
||||
<span class="val-ref">${item.s_ref} <small class="text-[9px]">/昨</small></span>
|
||||
</div>
|
||||
<div class="grid-layout metric-row">
|
||||
<span class="label text-blue-800">当日总额</span>
|
||||
<span class="val-actual text-blue-700">¥${item.a_act} ${getArrow(item.a_act, item.a_ref)}</span>
|
||||
<span class="val-ref">¥${item.a_ref}</span>
|
||||
</div>
|
||||
<div class="grid-layout metric-row">
|
||||
<span class="label">正价销量</span>
|
||||
<span class="val-actual text-emerald-600 text-sm">45</span>
|
||||
<span class="val-ref text-xs">占比 63%</span>
|
||||
</div>
|
||||
<div class="grid-layout metric-row">
|
||||
<span class="label">正价同比</span>
|
||||
<span class="val-actual text-emerald-600 text-sm">123%</span>
|
||||
<span class="val-ref text-xs">去年同期额</span>
|
||||
</div>
|
||||
<div class="grid-layout metric-row">
|
||||
<span class="label text-orange-600">特价销量</span>
|
||||
<span class="val-actual text-orange-600 text-sm">92 ${getArrow("92", "50")}</span>
|
||||
<span class="val-ref text-xs">占比 37%</span>
|
||||
</div>
|
||||
<div class="grid-layout metric-row">
|
||||
<span class="label text-orange-600">特价金额</span>
|
||||
<span class="val-actual text-orange-600 text-sm">¥3,104</span>
|
||||
<span class="val-ref text-xs">目标基准值</span>
|
||||
</div>
|
||||
<div class="grid-layout metric-row">
|
||||
<span class="label text-indigo-500">当日总成本</span>
|
||||
<span class="val-actual text-indigo-600 text-sm">¥5,072</span>
|
||||
<span class="val-ref text-xs">毛利率 39%</span>
|
||||
</div>
|
||||
<div class="grid-layout metric-row">
|
||||
<span class="label">当日标准额</span>
|
||||
<span class="val-actual text-slate-400 text-sm">¥9,053</span>
|
||||
<span class="val-ref text-xs">折扣 0.93</span>
|
||||
</div>
|
||||
<div class="grid-layout metric-row bg-blue-50/50 my-1 rounded-xl">
|
||||
<span class="label font-black text-blue-700 italic">日目标达成</span>
|
||||
<span class="val-actual text-blue-800 font-black text-sm">¥${item.a_act} ${getArrow(item.a_act, item.target)}</span>
|
||||
<span class="val-ref font-black text-blue-400 text-xs">¥${item.target}</span>
|
||||
</div>
|
||||
<div class="grid-layout metric-row">
|
||||
<span class="label font-black text-slate-800">月累计金额</span>
|
||||
<span class="val-actual text-blue-700 font-black text-sm">¥15.3W ${getArrow("15.3", "12.0")}</span>
|
||||
<span class="val-ref font-black text-slate-400 text-xs">¥35.5W</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-4 pt-4 border-t-2 border-blue-50 flex items-center justify-between px-2">
|
||||
<span class="text-[10px] text-slate-400 font-black uppercase italic tracking-widest">Diagnostic Report</span>
|
||||
<span class="text-[10px] text-blue-600 font-black hover:underline cursor-pointer uppercase">详情 →</span>
|
||||
</div>
|
||||
</div>
|
||||
`);
|
||||
});
|
||||
</script>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user