feat: 新增订单管理、评价管理、营销中心、会员中心模块

- 订单管理(4页): 订单大厅(看板)、全部订单、退款售后、订单设置
- 评价管理(1页): 评价列表+统计+回复抽屉
- 营销中心(5页): 优惠券、满减活动、限时折扣(含周期循环)、秒杀活动、新客有礼
- 会员中心(5页): 会员管理、储值卡、积分商城、客户画像、消息触达
- 侧边栏菜单重构: 营销中心拆分为营销中心+会员中心两个一级菜单
This commit is contained in:
2026-02-12 12:08:28 +08:00
parent 50147887d8
commit 7463c595a2
16 changed files with 6959 additions and 14 deletions

436
pages/mbr-messaging.html Normal file
View File

@@ -0,0 +1,436 @@
<!-- 消息触达页 -->
<style>
.msg-toolbar { display:flex; align-items:center; gap:10px; margin-bottom:16px; background:#fff; border-radius:10px; box-shadow:var(--g-shadow-sm); padding:12px 16px; flex-wrap:wrap; }
.msg-spacer { flex:1; }
.msg-stats { display:grid; grid-template-columns:repeat(4,1fr); gap:14px; margin-bottom:16px; }
.msg-stat-card { background:#fff; border-radius:10px; box-shadow:var(--g-shadow-sm); padding:16px 20px; transition:box-shadow var(--g-transition), transform var(--g-transition); }
.msg-stat-card:hover { box-shadow:var(--g-shadow-md); transform:translateY(-1px); }
.msg-stat-label { font-size:12px; color:var(--g-text-muted); margin-bottom:6px; }
.msg-stat-value { font-size:22px; font-weight:700; color:var(--g-text); }
.msg-stat-value .msg-stat-unit { font-size:13px; font-weight:400; color:var(--g-text-secondary); margin-left:2px; }
.msg-channel-dot { display:inline-block; width:6px; height:6px; border-radius:50%; margin-right:4px; vertical-align:middle; }
.msg-open-rate { font-size:11px; color:var(--g-text-muted); margin-top:2px; }
/* Template cards */
.msg-tpl-grid { display:grid; grid-template-columns:1fr 1fr; gap:14px; }
.msg-tpl-card { background:#fff; border-radius:10px; box-shadow:var(--g-shadow-sm); padding:18px 20px; transition:box-shadow var(--g-transition), transform var(--g-transition); display:flex; flex-direction:column; }
.msg-tpl-card:hover { box-shadow:var(--g-shadow-md); transform:translateY(-1px); }
.msg-tpl-hd { display:flex; align-items:center; gap:8px; margin-bottom:10px; }
.msg-tpl-name { font-size:14px; font-weight:600; color:var(--g-text); flex:1; }
.msg-tpl-preview { font-size:12px; color:var(--g-text-secondary); line-height:1.6; display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical; overflow:hidden; margin-bottom:12px; flex:1; }
.msg-tpl-ft { display:flex; align-items:center; justify-content:space-between; margin-top:auto; }
.msg-tpl-usage { font-size:12px; color:var(--g-text-muted); }
.msg-tpl-actions { display:flex; align-items:center; gap:10px; }
.msg-tpl-actions a { font-size:12px; color:var(--primary); cursor:pointer; text-decoration:none; transition:var(--g-transition); }
.msg-tpl-actions a:hover { text-decoration:underline; }
.msg-tpl-actions a.msg-tpl-danger { color:var(--g-danger); }
/* Drawer: tag pills for target audience */
.msg-tag-pills { display:flex; flex-wrap:wrap; gap:8px; margin-top:10px; }
.msg-reach { font-size:12px; color:var(--primary); margin-top:10px; display:flex; align-items:center; gap:4px; }
.msg-tpl-link { font-size:13px; color:var(--primary); cursor:pointer; text-decoration:none; display:inline-flex; align-items:center; gap:4px; margin-top:8px; transition:var(--g-transition); }
.msg-tpl-link:hover { text-decoration:underline; }
</style>
<div>
<!-- Tabs -->
<div class="g-seg" style="margin-bottom:16px;">
<div class="g-seg-item active" onclick="switchMsgTab(this,'list')">消息列表</div>
<div class="g-seg-item" onclick="switchMsgTab(this,'tpl')">消息模板</div>
</div>
<!-- TAB 1: 消息列表 -->
<div id="msgTabList">
<div class="msg-toolbar">
<select class="g-select" style="min-width:100px">
<option>全部状态</option>
<option>已发送</option>
<option>待发送</option>
<option>草稿</option>
</select>
<select class="g-select" style="min-width:100px">
<option>全部渠道</option>
<option>站内信</option>
<option>短信</option>
<option>微信</option>
</select>
<input class="g-input" placeholder="搜索消息标题" style="width:180px" />
<span class="msg-spacer"></span>
<button class="g-btn g-btn-primary" onclick="openMsgDrawer()"><i data-lucide="plus" style="width:14px;height:14px;vertical-align:-2px;margin-right:2px;"></i> 创建消息</button>
</div>
<!-- Stats -->
<div class="msg-stats">
<div class="msg-stat-card">
<div class="msg-stat-label">本月发送</div>
<div class="msg-stat-value">12<span class="msg-stat-unit"></span></div>
</div>
<div class="msg-stat-card">
<div class="msg-stat-label">触达人数</div>
<div class="msg-stat-value">3,680<span class="msg-stat-unit"></span></div>
</div>
<div class="msg-stat-card">
<div class="msg-stat-label">打开率</div>
<div class="msg-stat-value">45.2<span class="msg-stat-unit">%</span></div>
</div>
<div class="msg-stat-card">
<div class="msg-stat-label">转化率</div>
<div class="msg-stat-value">12.8<span class="msg-stat-unit">%</span></div>
</div>
</div>
<!-- Table -->
<div class="g-card">
<table class="g-table">
<thead>
<tr>
<th>消息标题</th>
<th>推送渠道</th>
<th>目标人群</th>
<th>触达人数</th>
<th>发送时间</th>
<th>状态</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<div style="font-weight:500;color:var(--g-text)">新品上线通知</div>
<div class="msg-open-rate">打开率 52.3% | 转化率 15.6%</div>
</td>
<td><span class="g-tag g-tag-blue">站内信</span></td>
<td>全部会员</td>
<td>1,280</td>
<td>2026-02-10 10:00</td>
<td><span class="g-tag g-tag-green">已发送</span></td>
<td><a class="g-action">详情</a></td>
</tr>
<tr>
<td>
<div style="font-weight:500;color:var(--g-text)">春节优惠活动</div>
<div class="msg-open-rate">打开率 48.1% | 转化率 18.2%</div>
</td>
<td><span class="g-tag g-tag-green">短信</span></td>
<td>全部会员</td>
<td>2,400</td>
<td>2026-02-08 09:30</td>
<td><span class="g-tag g-tag-green">已发送</span></td>
<td><a class="g-action">详情</a></td>
</tr>
<tr>
<td>
<div style="font-weight:500;color:var(--g-text)">沉睡客户召回</div>
<div class="msg-open-rate">打开率 35.7% | 转化率 8.4%</div>
</td>
<td><span class="g-tag g-tag-orange">微信模板</span></td>
<td>沉睡客户(456人)</td>
<td>456</td>
<td>2026-02-05 14:00</td>
<td><span class="g-tag g-tag-green">已发送</span></td>
<td><a class="g-action">详情</a></td>
</tr>
<tr>
<td>
<div style="font-weight:500;color:var(--g-text)">会员日提醒</div>
</td>
<td><span class="g-tag g-tag-blue">站内信</span></td>
<td>高频客户(328人)</td>
<td>328</td>
<td>2026-02-15 08:00</td>
<td><span class="g-tag g-tag-blue">待发送</span></td>
<td><a class="g-action">详情</a><a class="g-action-danger">删除</a></td>
</tr>
<tr>
<td>
<div style="font-weight:500;color:var(--g-text)">储值优惠推送</div>
</td>
<td><span class="g-tag g-tag-green">短信</span></td>
<td>全部会员</td>
<td></td>
<td></td>
<td><span class="g-tag g-tag-gray">草稿</span></td>
<td><a class="g-action">详情</a><a class="g-action">编辑</a><a class="g-action-danger">删除</a></td>
</tr>
<tr>
<td>
<div style="font-weight:500;color:var(--g-text)">新客欢迎礼</div>
</td>
<td><span class="g-tag g-tag-orange">微信模板</span></td>
<td>新客(86人)</td>
<td></td>
<td></td>
<td><span class="g-tag g-tag-gray">草稿</span></td>
<td><a class="g-action">详情</a><a class="g-action">编辑</a><a class="g-action-danger">删除</a></td>
</tr>
</tbody>
</table>
<div class="g-pagination" style="margin-top:16px;">
<span style="font-size:13px;color:var(--g-text-secondary);">共 6 条</span>
<button class="g-page-btn" disabled>&lt;</button>
<button class="g-page-btn active">1</button>
<button class="g-page-btn">&gt;</button>
</div>
</div>
</div>
<!-- TAB 2: 消息模板 -->
<div id="msgTabTpl" style="display:none">
<div class="msg-toolbar">
<select class="g-select" style="min-width:100px">
<option>全部分类</option>
<option>营销</option>
<option>通知</option>
<option>召回</option>
</select>
<input class="g-input" placeholder="搜索模板名称" style="width:180px" />
<span class="msg-spacer"></span>
<button class="g-btn g-btn-primary"><i data-lucide="plus" style="width:14px;height:14px;vertical-align:-2px;margin-right:2px;"></i> 新建模板</button>
</div>
<div class="msg-tpl-grid">
<!-- Template 1 -->
<div class="msg-tpl-card">
<div class="msg-tpl-hd">
<span class="msg-tpl-name">新品上线通知</span>
<span class="g-tag g-tag-blue">通知</span>
</div>
<div class="msg-tpl-preview">亲爱的{name},我们上新啦!{product}现已上线,口感升级,诚意满满,快来尝鲜吧!</div>
<div class="msg-tpl-ft">
<span class="msg-tpl-usage"><i data-lucide="bar-chart-2" style="width:12px;height:12px;vertical-align:-1px;margin-right:2px;"></i>已使用 28次</span>
<div class="msg-tpl-actions">
<button class="g-btn g-btn-sm g-btn-primary" onclick="useMsgTemplate('新品上线通知','亲爱的{name},我们上新啦!{product}现已上线,口感升级,诚意满满,快来尝鲜吧!')">使用</button>
<a>编辑</a>
<a class="msg-tpl-danger">删除</a>
</div>
</div>
</div>
<!-- Template 2 -->
<div class="msg-tpl-card">
<div class="msg-tpl-hd">
<span class="msg-tpl-name">节日问候</span>
<span class="g-tag g-tag-orange">营销</span>
</div>
<div class="msg-tpl-preview">亲爱的{name}{holiday}快乐!为您准备了专属优惠,下单立享折扣,温暖过节从一顿好饭开始!</div>
<div class="msg-tpl-ft">
<span class="msg-tpl-usage"><i data-lucide="bar-chart-2" style="width:12px;height:12px;vertical-align:-1px;margin-right:2px;"></i>已使用 15次</span>
<div class="msg-tpl-actions">
<button class="g-btn g-btn-sm g-btn-primary" onclick="useMsgTemplate('节日问候','亲爱的{name}{holiday}快乐!为您准备了专属优惠,下单立享折扣,温暖过节从一顿好饭开始!')">使用</button>
<a>编辑</a>
<a class="msg-tpl-danger">删除</a>
</div>
</div>
</div>
<!-- Template 3 -->
<div class="msg-tpl-card">
<div class="msg-tpl-hd">
<span class="msg-tpl-name">沉睡客户召回</span>
<span class="g-tag g-tag-red">召回</span>
</div>
<div class="msg-tpl-preview">{name},好久不见!我们很想念您,特送上一份专属回归礼券,期待与您再次相遇!</div>
<div class="msg-tpl-ft">
<span class="msg-tpl-usage"><i data-lucide="bar-chart-2" style="width:12px;height:12px;vertical-align:-1px;margin-right:2px;"></i>已使用 42次</span>
<div class="msg-tpl-actions">
<button class="g-btn g-btn-sm g-btn-primary" onclick="useMsgTemplate('沉睡客户召回','{name},好久不见!我们很想念您,特送上一份专属回归礼券,期待与您再次相遇!')">使用</button>
<a>编辑</a>
<a class="msg-tpl-danger">删除</a>
</div>
</div>
</div>
<!-- Template 4 -->
<div class="msg-tpl-card">
<div class="msg-tpl-hd">
<span class="msg-tpl-name">储值优惠提醒</span>
<span class="g-tag g-tag-orange">营销</span>
</div>
<div class="msg-tpl-preview">限时充值优惠!充{amount}送{gift},多充多送,优惠截止本周日,别错过!</div>
<div class="msg-tpl-ft">
<span class="msg-tpl-usage"><i data-lucide="bar-chart-2" style="width:12px;height:12px;vertical-align:-1px;margin-right:2px;"></i>已使用 19次</span>
<div class="msg-tpl-actions">
<button class="g-btn g-btn-sm g-btn-primary" onclick="useMsgTemplate('储值优惠提醒','限时充值优惠!充{amount}送{gift},多充多送,优惠截止本周日,别错过!')">使用</button>
<a>编辑</a>
<a class="msg-tpl-danger">删除</a>
</div>
</div>
</div>
<!-- Template 5 -->
<div class="msg-tpl-card">
<div class="msg-tpl-hd">
<span class="msg-tpl-name">订单完成感谢</span>
<span class="g-tag g-tag-blue">通知</span>
</div>
<div class="msg-tpl-preview">感谢您的订单!期待下次光临,如有任何问题请随时联系我们,祝您用餐愉快!</div>
<div class="msg-tpl-ft">
<span class="msg-tpl-usage"><i data-lucide="bar-chart-2" style="width:12px;height:12px;vertical-align:-1px;margin-right:2px;"></i>已使用 56次</span>
<div class="msg-tpl-actions">
<button class="g-btn g-btn-sm g-btn-primary" onclick="useMsgTemplate('订单完成感谢','感谢您的订单!期待下次光临,如有任何问题请随时联系我们,祝您用餐愉快!')">使用</button>
<a>编辑</a>
<a class="msg-tpl-danger">删除</a>
</div>
</div>
</div>
<!-- Template 6 -->
<div class="msg-tpl-card">
<div class="msg-tpl-hd">
<span class="msg-tpl-name">会员升级通知</span>
<span class="g-tag g-tag-blue">通知</span>
</div>
<div class="msg-tpl-preview">恭喜{name}升级为{level}会员!解锁更多专属权益,感谢您一路以来的支持与信赖!</div>
<div class="msg-tpl-ft">
<span class="msg-tpl-usage"><i data-lucide="bar-chart-2" style="width:12px;height:12px;vertical-align:-1px;margin-right:2px;"></i>已使用 33次</span>
<div class="msg-tpl-actions">
<button class="g-btn g-btn-sm g-btn-primary" onclick="useMsgTemplate('会员升级通知','恭喜{name}升级为{level}会员!解锁更多专属权益,感谢您一路以来的支持与信赖!')">使用</button>
<a>编辑</a>
<a class="msg-tpl-danger">删除</a>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Drawer mask -->
<div class="g-drawer-mask" id="msgDrawerMask" onclick="closeMsgDrawer()"></div>
<!-- Create message drawer -->
<div class="g-drawer" id="msgDrawer" style="width:560px;">
<div class="g-drawer-hd">
<span class="g-drawer-title">创建消息</span>
<button class="g-drawer-close" onclick="closeMsgDrawer()"><i data-lucide="x" style="width:18px;height:18px;"></i></button>
</div>
<div class="g-drawer-bd">
<!-- 消息标题 -->
<div class="g-form-group">
<label class="g-form-label required">消息标题</label>
<input class="g-input" id="msgTitle" placeholder="如:新品上线通知" />
</div>
<!-- 推送渠道 -->
<div class="g-form-group">
<label class="g-form-label required">推送渠道</label>
<div style="display:flex;gap:8px;">
<span class="g-pill checked" onclick="selectMsgChannel(this)">站内信</span>
<span class="g-pill" onclick="selectMsgChannel(this)">短信</span>
<span class="g-pill" onclick="selectMsgChannel(this)">微信模板消息</span>
</div>
</div>
<!-- 目标人群 -->
<div class="g-form-group">
<label class="g-form-label required">目标人群</label>
<select class="g-select" id="msgTargetSelect" onchange="selectMsgTarget(this)">
<option value="all">全部会员</option>
<option value="tag">按标签筛选</option>
</select>
<div id="msgTagSection" style="display:none;">
<div class="msg-tag-pills">
<span class="g-pill" onclick="toggleMsgTagPill(this)">高频客户</span>
<span class="g-pill" onclick="toggleMsgTagPill(this)">新客</span>
<span class="g-pill" onclick="toggleMsgTagPill(this)">沉睡客户</span>
<span class="g-pill" onclick="toggleMsgTagPill(this)">流失客户</span>
<span class="g-pill" onclick="toggleMsgTagPill(this)">午餐常客</span>
<span class="g-pill" onclick="toggleMsgTagPill(this)">大额消费</span>
</div>
<div class="msg-reach" id="msgReach">
<i data-lucide="users" style="width:14px;height:14px;"></i>
<span>预计触达 456人</span>
</div>
</div>
</div>
<!-- 消息内容 -->
<div class="g-form-group">
<label class="g-form-label required">消息内容</label>
<textarea class="g-textarea" id="msgContent" rows="5" placeholder="支持变量:{name}姓名 {level}等级"></textarea>
<div class="g-hint">可使用变量实现个性化推送</div>
</div>
<!-- 发送时间 -->
<div class="g-form-group">
<label class="g-form-label required">发送时间</label>
<div style="display:flex;gap:8px;">
<span class="g-pill checked" onclick="selectMsgSendTime(this,'now')">立即发送</span>
<span class="g-pill" onclick="selectMsgSendTime(this,'scheduled')">定时发送</span>
</div>
<div id="msgScheduleTime" style="display:none;margin-top:10px;">
<input class="g-input" type="datetime-local" style="width:240px;" />
</div>
</div>
<!-- 选择模板 -->
<div class="g-form-group">
<a class="msg-tpl-link" onclick="switchMsgTab(document.querySelectorAll('.g-seg-item')[1],'tpl');closeMsgDrawer();">
<i data-lucide="file-text" style="width:14px;height:14px;"></i>
从模板选择
</a>
</div>
</div>
<div class="g-drawer-ft">
<button class="g-btn" onclick="closeMsgDrawer()">存为草稿</button>
<button class="g-btn g-btn-primary" onclick="closeMsgDrawer()">发送</button>
</div>
</div>
<script>
/* Tab switching */
function switchMsgTab(el, tab) {
document.querySelectorAll('.g-seg-item').forEach(function(s){ s.classList.remove('active'); });
el.classList.add('active');
document.getElementById('msgTabList').style.display = tab === 'list' ? '' : 'none';
document.getElementById('msgTabTpl').style.display = tab === 'tpl' ? '' : 'none';
}
/* Drawer open/close */
function openMsgDrawer() {
document.getElementById('msgDrawerMask').classList.add('open');
document.getElementById('msgDrawer').classList.add('open');
}
function closeMsgDrawer() {
document.getElementById('msgDrawerMask').classList.remove('open');
document.getElementById('msgDrawer').classList.remove('open');
}
/* Channel pill: single select */
function selectMsgChannel(el) {
var pills = el.parentElement.querySelectorAll('.g-pill');
pills.forEach(function(p){ p.classList.remove('checked'); });
el.classList.add('checked');
}
/* Target audience */
function selectMsgTarget(el) {
var section = document.getElementById('msgTagSection');
section.style.display = el.value === 'tag' ? '' : 'none';
}
/* Tag pill toggle (multi-select) */
function toggleMsgTagPill(el) {
el.classList.toggle('checked');
var count = el.parentElement.querySelectorAll('.g-pill.checked').length;
var reachMap = [0, 456, 712, 980, 1240, 1580, 1860];
var reach = reachMap[count] || 0;
document.getElementById('msgReach').querySelector('span').textContent = '预计触达 ' + reach + '人';
}
/* Send time pill: single select */
function selectMsgSendTime(el, mode) {
var pills = el.parentElement.querySelectorAll('.g-pill');
pills.forEach(function(p){ p.classList.remove('checked'); });
el.classList.add('checked');
document.getElementById('msgScheduleTime').style.display = mode === 'scheduled' ? '' : 'none';
}
/* Use template: pre-fill drawer */
function useMsgTemplate(title, content) {
openMsgDrawer();
document.getElementById('msgTitle').value = title;
document.getElementById('msgContent').value = content;
}
if (typeof lucide !== 'undefined') lucide.createIcons();
</script>