448 lines
19 KiB
HTML
448 lines
19 KiB
HTML
<!-- 客户画像 — mbr-customers.html -->
|
|
<style>
|
|
/* ---- page-private: cu- prefix ---- */
|
|
.cu-toolbar {
|
|
background: #fff; border-radius: var(--g-radius); padding: 14px 20px;
|
|
display: flex; flex-wrap: wrap; gap: 10px; align-items: center;
|
|
box-shadow: var(--g-shadow-sm); border: 1px solid var(--g-border);
|
|
}
|
|
.cu-toolbar .g-select,
|
|
.cu-toolbar .g-input { height: 32px; font-size: 13px; }
|
|
.cu-search-wrap {
|
|
position: relative; width: 220px;
|
|
}
|
|
.cu-search-wrap .g-input { padding-left: 30px; width: 100%; }
|
|
.cu-search-wrap i {
|
|
position: absolute; left: 8px; top: 50%; transform: translateY(-50%);
|
|
color: var(--g-text-muted); pointer-events: none;
|
|
}
|
|
.cu-toolbar-right { margin-left: auto; display: flex; gap: 8px; }
|
|
|
|
/* stats */
|
|
.cu-stats { display: grid; grid-template-columns: repeat(4, 1fr); gap: 14px; margin-top: 14px; }
|
|
.cu-stat-card {
|
|
background: #fff; border-radius: var(--g-radius); border: 1px solid var(--g-border);
|
|
padding: 18px 20px; box-shadow: var(--g-shadow-sm);
|
|
transition: box-shadow var(--g-transition);
|
|
}
|
|
.cu-stat-card:hover { box-shadow: var(--g-shadow-md); }
|
|
.cu-stat-title { font-size: 13px; color: var(--g-text-secondary); margin-bottom: 8px; font-weight: 500; display: flex; align-items: center; gap: 6px; }
|
|
.cu-stat-title i { width: 16px; height: 16px; }
|
|
.cu-stat-value { font-size: 28px; font-weight: 700; color: var(--g-text); line-height: 1.2; }
|
|
.cu-stat-value.green { color: var(--g-success); }
|
|
.cu-stat-value.orange { color: var(--g-warning); }
|
|
.cu-stat-value.red { color: var(--g-danger); }
|
|
.cu-stat-hint { font-size: 11px; color: var(--g-text-muted); margin-top: 6px; }
|
|
|
|
/* tag distribution */
|
|
.cu-tag-dist {
|
|
background: #fff; border-radius: var(--g-radius); border: 1px solid var(--g-border);
|
|
padding: 16px 20px; margin-top: 14px; box-shadow: var(--g-shadow-sm);
|
|
display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
|
|
}
|
|
.cu-tag-dist-label {
|
|
font-size: 13px; font-weight: 600; color: var(--g-text); margin-right: 4px; white-space: nowrap;
|
|
}
|
|
.cu-tag-pill {
|
|
display: inline-flex; align-items: center; gap: 5px; padding: 5px 14px;
|
|
border-radius: 16px; font-size: 12px; cursor: pointer; user-select: none;
|
|
transition: all var(--g-transition); border: 1px solid #e8e8e8; background: #fff;
|
|
}
|
|
.cu-tag-pill:hover { border-color: var(--primary); }
|
|
.cu-tag-pill.active { background: color-mix(in srgb, var(--primary) 10%, transparent); border-color: var(--primary); color: var(--primary); font-weight: 500; }
|
|
.cu-tag-pill .cu-tag-count {
|
|
background: #f0f0f0; color: var(--g-text-secondary); font-size: 11px;
|
|
padding: 1px 6px; border-radius: 8px; font-weight: 600;
|
|
}
|
|
.cu-tag-pill.active .cu-tag-count { background: color-mix(in srgb, var(--primary) 18%, transparent); color: var(--primary); }
|
|
|
|
/* table card */
|
|
.cu-table-card {
|
|
background: #fff; border-radius: var(--g-radius); border: 1px solid var(--g-border);
|
|
margin-top: 14px; box-shadow: var(--g-shadow-sm); overflow: hidden;
|
|
}
|
|
.cu-table-card .g-table td { white-space: nowrap; }
|
|
.cu-tags-cell { display: flex; gap: 4px; flex-wrap: wrap; }
|
|
.cu-mini-tag {
|
|
display: inline-block; padding: 1px 7px; border-radius: 3px; font-size: 11px; font-weight: 500;
|
|
}
|
|
.cu-mini-tag.green { background: #f6ffed; color: #52c41a; }
|
|
.cu-mini-tag.blue { background: #e6f7ff; color: #1890ff; }
|
|
.cu-mini-tag.orange { background: #fff7e6; color: #fa8c16; }
|
|
.cu-mini-tag.red { background: #fff1f0; color: #f5222d; }
|
|
.cu-mini-tag.purple { background: #f9f0ff; color: #722ed1; }
|
|
.cu-mini-tag.gold { background: #fffbe6; color: #d48806; }
|
|
.cu-row-dimmed td { opacity: 0.55; }
|
|
.cu-row-dimmed:hover td { opacity: 0.75; }
|
|
|
|
/* drawer profile */
|
|
.cu-profile-hd {
|
|
display: flex; align-items: center; gap: 16px; margin-bottom: 20px;
|
|
padding-bottom: 16px; border-bottom: 1px solid #f5f5f5;
|
|
}
|
|
.cu-avatar {
|
|
width: 56px; height: 56px; border-radius: 50%; background: color-mix(in srgb, var(--primary) 12%, #fff);
|
|
display: flex; align-items: center; justify-content: center; flex-shrink: 0;
|
|
color: var(--primary);
|
|
}
|
|
.cu-profile-name { font-size: 18px; font-weight: 700; color: var(--g-text); }
|
|
.cu-profile-phone { font-size: 13px; color: var(--g-text-muted); margin-top: 2px; }
|
|
.cu-profile-meta { display: flex; gap: 8px; margin-top: 6px; align-items: center; flex-wrap: wrap; }
|
|
|
|
.cu-section-hd {
|
|
font-size: 15px; font-weight: 600; color: var(--g-text);
|
|
padding-left: 10px; border-left: 3px solid var(--primary); margin-bottom: 14px;
|
|
}
|
|
.cu-overview-grid {
|
|
display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px; margin-bottom: 20px;
|
|
}
|
|
.cu-ov-item {
|
|
background: var(--g-bg-subtle); border-radius: var(--g-radius-sm); padding: 12px 14px;
|
|
}
|
|
.cu-ov-label { font-size: 12px; color: var(--g-text-muted); margin-bottom: 4px; }
|
|
.cu-ov-val { font-size: 16px; font-weight: 700; color: var(--g-text); }
|
|
.cu-ov-val.primary { color: var(--primary); }
|
|
|
|
.cu-pref-section { margin-bottom: 20px; }
|
|
.cu-pref-row { margin-bottom: 10px; }
|
|
.cu-pref-label { font-size: 12px; color: var(--g-text-muted); margin-bottom: 6px; }
|
|
.cu-pref-pills { display: flex; gap: 6px; flex-wrap: wrap; }
|
|
.cu-pref-pill {
|
|
display: inline-block; padding: 4px 12px; border-radius: 14px;
|
|
font-size: 12px; background: #f5f5f5; color: var(--g-text-secondary);
|
|
}
|
|
|
|
.cu-mini-table { width: 100%; border-collapse: collapse; font-size: 12px; margin-bottom: 4px; }
|
|
.cu-mini-table th {
|
|
text-align: left; padding: 8px 10px; font-size: 11px; font-weight: 500;
|
|
color: var(--g-text-muted); background: var(--g-bg-subtle); border-bottom: 1px solid var(--g-border);
|
|
}
|
|
.cu-mini-table td {
|
|
padding: 8px 10px; border-bottom: 1px solid #f5f5f5; color: var(--g-text);
|
|
}
|
|
</style>
|
|
|
|
<div>
|
|
<!-- Toolbar -->
|
|
<div class="cu-toolbar">
|
|
<select class="g-select" style="width:180px;">
|
|
<option>全部门店</option>
|
|
<option>老三家外卖(朝阳店)</option>
|
|
<option>老三家外卖(海淀店)</option>
|
|
<option>老三家外卖(望京店)</option>
|
|
</select>
|
|
<select class="g-select" style="width:150px;" id="cuTagFilter" onchange="cuFilterTable()">
|
|
<option value="">全部标签</option>
|
|
<option value="高频客户">高频客户</option>
|
|
<option value="新客">新客</option>
|
|
<option value="沉睡客户">沉睡客户</option>
|
|
<option value="流失客户">流失客户</option>
|
|
<option value="午餐常客">午餐常客</option>
|
|
<option value="大额消费">大额消费</option>
|
|
</select>
|
|
<div class="cu-search-wrap">
|
|
<i data-lucide="search" style="width:14px;height:14px;"></i>
|
|
<input class="g-input" placeholder="搜索姓名/手机号" id="cuSearchInput" oninput="cuFilterTable()">
|
|
</div>
|
|
<div class="cu-toolbar-right">
|
|
<button class="g-btn"><i data-lucide="download" style="width:14px;height:14px;"></i> 导出</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Stats -->
|
|
<div class="cu-stats">
|
|
<div class="cu-stat-card">
|
|
<div class="cu-stat-title"><i data-lucide="users" style="color:var(--primary)"></i> 总客户</div>
|
|
<div class="cu-stat-value">2,156</div>
|
|
<div class="cu-stat-hint">累计注册客户数</div>
|
|
</div>
|
|
<div class="cu-stat-card">
|
|
<div class="cu-stat-title"><i data-lucide="flame" style="color:var(--g-success)"></i> 高频客户</div>
|
|
<div class="cu-stat-value green">328</div>
|
|
<div class="cu-stat-hint">月消费 ≥ 4次</div>
|
|
</div>
|
|
<div class="cu-stat-card">
|
|
<div class="cu-stat-title"><i data-lucide="moon" style="color:var(--g-warning)"></i> 沉睡客户</div>
|
|
<div class="cu-stat-value orange">456</div>
|
|
<div class="cu-stat-hint">30天未消费</div>
|
|
</div>
|
|
<div class="cu-stat-card">
|
|
<div class="cu-stat-title"><i data-lucide="user-x" style="color:var(--g-danger)"></i> 流失客户</div>
|
|
<div class="cu-stat-value red">189</div>
|
|
<div class="cu-stat-hint">60天未消费</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Tag Distribution -->
|
|
<div class="cu-tag-dist">
|
|
<span class="cu-tag-dist-label">标签分布</span>
|
|
<span class="cu-tag-pill" onclick="cuToggleTagPill(this, '高频客户')">高频客户 <span class="cu-tag-count">328</span></span>
|
|
<span class="cu-tag-pill" onclick="cuToggleTagPill(this, '新客')">新客 <span class="cu-tag-count">86</span></span>
|
|
<span class="cu-tag-pill" onclick="cuToggleTagPill(this, '午餐常客')">午餐常客 <span class="cu-tag-count">215</span></span>
|
|
<span class="cu-tag-pill" onclick="cuToggleTagPill(this, '大额消费')">大额消费 <span class="cu-tag-count">142</span></span>
|
|
<span class="cu-tag-pill" onclick="cuToggleTagPill(this, '沉睡客户')">沉睡客户 <span class="cu-tag-count">456</span></span>
|
|
<span class="cu-tag-pill" onclick="cuToggleTagPill(this, '流失客户')">流失客户 <span class="cu-tag-count">189</span></span>
|
|
</div>
|
|
|
|
<!-- Customer Table -->
|
|
<div class="cu-table-card">
|
|
<table class="g-table" id="cuTable">
|
|
<thead>
|
|
<tr>
|
|
<th>客户</th>
|
|
<th>手机号</th>
|
|
<th>标签</th>
|
|
<th>累计消费</th>
|
|
<th>消费次数</th>
|
|
<th>平均客单价</th>
|
|
<th>最近消费</th>
|
|
<th>注册时间</th>
|
|
<th>操作</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="cuTableBody"></tbody>
|
|
</table>
|
|
|
|
<!-- Pagination -->
|
|
<div class="g-pagination" style="padding: 12px 16px;">
|
|
<span style="margin-right:auto;font-size:12px;color:var(--g-text-muted);">共 2,156 条</span>
|
|
<button class="g-page-btn" disabled><</button>
|
|
<button class="g-page-btn active">1</button>
|
|
<button class="g-page-btn">2</button>
|
|
<button class="g-page-btn">3</button>
|
|
<button class="g-page-btn">...</button>
|
|
<button class="g-page-btn">108</button>
|
|
<button class="g-page-btn">></button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Customer Profile Drawer -->
|
|
<div class="g-drawer-mask" id="cuDrawerMask" onclick="closeCuDrawer()"></div>
|
|
<div class="g-drawer" id="cuDrawer" style="width:600px;">
|
|
<div class="g-drawer-hd">
|
|
<span class="g-drawer-title" id="cuDrawerTitle">客户画像 - 张**</span>
|
|
<button class="g-drawer-close" onclick="closeCuDrawer()"><i data-lucide="x" style="width:18px;height:18px;"></i></button>
|
|
</div>
|
|
<div class="g-drawer-bd">
|
|
<!-- 基本信息 -->
|
|
<div class="cu-profile-hd">
|
|
<div class="cu-avatar"><i data-lucide="user" style="width:28px;height:28px;"></i></div>
|
|
<div>
|
|
<div class="cu-profile-name" id="cuProfileName">张**</div>
|
|
<div class="cu-profile-phone" id="cuProfilePhone">138****6789</div>
|
|
<div class="cu-profile-meta" id="cuProfileMeta">
|
|
<span class="g-tag g-tag-blue">黄金会员</span>
|
|
<span style="font-size:12px;color:var(--g-text-muted);">注册于 2024-03-15</span>
|
|
</div>
|
|
<div class="cu-profile-meta" id="cuProfileTags" style="margin-top:4px;">
|
|
<span class="cu-mini-tag green">高频客户</span>
|
|
<span class="cu-mini-tag purple">午餐常客</span>
|
|
<span class="cu-mini-tag gold">大额消费</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 消费概览 -->
|
|
<div class="cu-section-hd">消费概览</div>
|
|
<div class="cu-overview-grid">
|
|
<div class="cu-ov-item">
|
|
<div class="cu-ov-label">累计消费</div>
|
|
<div class="cu-ov-val primary">¥3,680</div>
|
|
</div>
|
|
<div class="cu-ov-item">
|
|
<div class="cu-ov-label">消费次数</div>
|
|
<div class="cu-ov-val">42次</div>
|
|
</div>
|
|
<div class="cu-ov-item">
|
|
<div class="cu-ov-label">平均客单价</div>
|
|
<div class="cu-ov-val">¥87.6</div>
|
|
</div>
|
|
<div class="cu-ov-item">
|
|
<div class="cu-ov-label">最近消费</div>
|
|
<div class="cu-ov-val">2025-02-10</div>
|
|
</div>
|
|
<div class="cu-ov-item">
|
|
<div class="cu-ov-label">常用渠道</div>
|
|
<div class="cu-ov-val" style="font-size:13px;">外卖(68%) 自提(32%)</div>
|
|
</div>
|
|
<div class="cu-ov-item">
|
|
<div class="cu-ov-label">储值余额</div>
|
|
<div class="cu-ov-val primary">¥260</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 消费偏好 -->
|
|
<div class="cu-section-hd">消费偏好</div>
|
|
<div class="cu-pref-section">
|
|
<div class="cu-pref-row">
|
|
<div class="cu-pref-label">常点商品</div>
|
|
<div class="cu-pref-pills">
|
|
<span class="cu-pref-pill">宫保鸡丁 (12次)</span>
|
|
<span class="cu-pref-pill">鱼香肉丝 (8次)</span>
|
|
<span class="cu-pref-pill">米饭 (42次)</span>
|
|
</div>
|
|
</div>
|
|
<div class="cu-pref-row">
|
|
<div class="cu-pref-label">口味偏好</div>
|
|
<div class="cu-pref-pills">
|
|
<span class="cu-pref-pill">偏辣</span>
|
|
<span class="cu-pref-pill">少盐</span>
|
|
</div>
|
|
</div>
|
|
<div class="cu-pref-row">
|
|
<div class="cu-pref-label">下单时段</div>
|
|
<div class="cu-pref-pills">
|
|
<span class="cu-pref-pill">午餐 (78%)</span>
|
|
<span class="cu-pref-pill">晚餐 (22%)</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 最近订单 -->
|
|
<div class="cu-section-hd">最近订单</div>
|
|
<table class="cu-mini-table">
|
|
<thead>
|
|
<tr>
|
|
<th>日期</th>
|
|
<th>商品摘要</th>
|
|
<th>金额</th>
|
|
<th>渠道</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr>
|
|
<td>2025-02-10</td>
|
|
<td>宫保鸡丁+米饭+可乐</td>
|
|
<td>¥52.00</td>
|
|
<td><span class="g-tag g-tag-blue">外卖</span></td>
|
|
</tr>
|
|
<tr>
|
|
<td>2025-02-07</td>
|
|
<td>鱼香肉丝盖饭+蛋花汤</td>
|
|
<td>¥38.00</td>
|
|
<td><span class="g-tag g-tag-green">自提</span></td>
|
|
</tr>
|
|
<tr>
|
|
<td>2025-02-03</td>
|
|
<td>招牌红烧肉饭+凉菜拼盘</td>
|
|
<td>¥65.00</td>
|
|
<td><span class="g-tag g-tag-blue">外卖</span></td>
|
|
</tr>
|
|
<tr>
|
|
<td>2025-01-28</td>
|
|
<td>番茄牛腩面+珍珠奶茶</td>
|
|
<td>¥42.00</td>
|
|
<td><span class="g-tag g-tag-blue">外卖</span></td>
|
|
</tr>
|
|
<tr>
|
|
<td>2025-01-22</td>
|
|
<td>宫保鸡丁+麻婆豆腐+米饭x2</td>
|
|
<td>¥88.00</td>
|
|
<td><span class="g-tag g-tag-green">自提</span></td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
<div class="g-drawer-ft">
|
|
<button class="g-btn" onclick="closeCuDrawer()">关闭</button>
|
|
<button class="g-btn g-btn-primary"><i data-lucide="send" style="width:14px;height:14px;"></i> 发送消息</button>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
// Customer data
|
|
var cuCustomers = [
|
|
{ name:'张**', phone:'138****6789', tags:['高频客户','午餐常客','大额消费'], total:3680, count:42, avg:87.6, last:'2025-02-10', reg:'2024-03-15', churned:false },
|
|
{ name:'李**', phone:'139****2345', tags:['高频客户','大额消费'], total:5120, count:58, avg:88.3, last:'2025-02-11', reg:'2023-11-20', churned:false },
|
|
{ name:'王**', phone:'136****8901', tags:['新客'], total:156, count:2, avg:78.0, last:'2025-02-09', reg:'2025-02-01', churned:false },
|
|
{ name:'赵**', phone:'158****4567', tags:['午餐常客'], total:1890, count:28, avg:67.5, last:'2025-02-08', reg:'2024-06-10', churned:false },
|
|
{ name:'陈**', phone:'137****3210', tags:['沉睡客户'], total:960, count:12, avg:80.0, last:'2025-01-05', reg:'2024-04-22', churned:false },
|
|
{ name:'刘**', phone:'155****7654', tags:['流失客户'], total:420, count:5, avg:84.0, last:'2024-11-18', reg:'2024-08-03', churned:true },
|
|
{ name:'周**', phone:'186****9012', tags:['高频客户','午餐常客'], total:2760, count:35, avg:78.9, last:'2025-02-11', reg:'2024-01-08', churned:false },
|
|
{ name:'吴**', phone:'133****5678', tags:['流失客户','大额消费'], total:1540, count:11, avg:140.0, last:'2024-12-02', reg:'2024-05-15', churned:true },
|
|
];
|
|
|
|
var cuTagColorMap = {
|
|
'高频客户': 'green',
|
|
'新客': 'blue',
|
|
'沉睡客户': 'orange',
|
|
'流失客户': 'red',
|
|
'午餐常客': 'purple',
|
|
'大额消费': 'gold'
|
|
};
|
|
|
|
var cuActiveTagFilter = '';
|
|
|
|
function cuRenderTable() {
|
|
var kw = (document.getElementById('cuSearchInput').value || '').trim().toLowerCase();
|
|
var tagSel = document.getElementById('cuTagFilter').value;
|
|
var filterTag = cuActiveTagFilter || tagSel;
|
|
var tbody = document.getElementById('cuTableBody');
|
|
var html = '';
|
|
cuCustomers.forEach(function(c) {
|
|
if (kw && c.name.toLowerCase().indexOf(kw) === -1 && c.phone.indexOf(kw) === -1) return;
|
|
if (filterTag && c.tags.indexOf(filterTag) === -1) return;
|
|
var tagsHtml = c.tags.map(function(t) {
|
|
return '<span class="cu-mini-tag ' + (cuTagColorMap[t] || 'gray') + '">' + t + '</span>';
|
|
}).join('');
|
|
var rowClass = c.churned ? ' class="cu-row-dimmed"' : '';
|
|
html += '<tr' + rowClass + '>'
|
|
+ '<td style="font-weight:500;">' + c.name + '</td>'
|
|
+ '<td>' + c.phone + '</td>'
|
|
+ '<td><div class="cu-tags-cell">' + tagsHtml + '</div></td>'
|
|
+ '<td>¥' + c.total.toLocaleString() + '</td>'
|
|
+ '<td>' + c.count + '次</td>'
|
|
+ '<td>¥' + c.avg.toFixed(1) + '</td>'
|
|
+ '<td>' + c.last + '</td>'
|
|
+ '<td>' + c.reg + '</td>'
|
|
+ '<td><a class="g-action" onclick="openCuDrawer(\'' + c.name + '\')">画像</a></td>'
|
|
+ '</tr>';
|
|
});
|
|
if (!html) {
|
|
html = '<tr><td colspan="9" style="text-align:center;padding:40px;color:var(--g-text-muted);">暂无匹配客户</td></tr>';
|
|
}
|
|
tbody.innerHTML = html;
|
|
}
|
|
|
|
function cuFilterTable() {
|
|
cuActiveTagFilter = '';
|
|
document.querySelectorAll('.cu-tag-pill').forEach(function(p) { p.classList.remove('active'); });
|
|
cuRenderTable();
|
|
}
|
|
|
|
function cuToggleTagPill(el, tag) {
|
|
var wasActive = el.classList.contains('active');
|
|
document.querySelectorAll('.cu-tag-pill').forEach(function(p) { p.classList.remove('active'); });
|
|
if (wasActive) {
|
|
cuActiveTagFilter = '';
|
|
} else {
|
|
el.classList.add('active');
|
|
cuActiveTagFilter = tag;
|
|
}
|
|
document.getElementById('cuTagFilter').value = '';
|
|
cuRenderTable();
|
|
}
|
|
|
|
function openCuDrawer(name) {
|
|
var c = cuCustomers.find(function(x) { return x.name === name; });
|
|
if (c) {
|
|
document.getElementById('cuDrawerTitle').textContent = '客户画像 - ' + c.name;
|
|
document.getElementById('cuProfileName').textContent = c.name;
|
|
document.getElementById('cuProfilePhone').textContent = c.phone;
|
|
}
|
|
document.getElementById('cuDrawerMask').classList.add('open');
|
|
document.getElementById('cuDrawer').classList.add('open');
|
|
if (typeof lucide !== 'undefined') lucide.createIcons();
|
|
}
|
|
|
|
function closeCuDrawer() {
|
|
document.getElementById('cuDrawerMask').classList.remove('open');
|
|
document.getElementById('cuDrawer').classList.remove('open');
|
|
}
|
|
|
|
// Init
|
|
cuRenderTable();
|
|
if (typeof lucide !== 'undefined') lucide.createIcons();
|
|
</script>
|