feat: 积分商城兑换记录、次卡管理、满赠/第二份半价、营销日历

- 积分商城: 新增兑换记录tab、到账通知、核销抽屉、商品类型标签
- 次卡管理: 新页面,支持完全免费/金额上限两种模式,4种适用范围
- 满减活动: 扩展支持满赠(买X赠Y)和第二份半价,抽屉联动切换
- 营销日历: 甘特图总览所有活动,冲突提示,类型图例
- 会员管理: 动态等级、开关式权益配置(上次遗留同步提交)
This commit is contained in:
2026-02-12 15:50:58 +08:00
parent 7463c595a2
commit e16c30791e
6 changed files with 1849 additions and 103 deletions

View File

@@ -97,6 +97,20 @@
font-size:15px; font-weight:600; color:var(--g-text);
padding-left:10px; border-left:3px solid var(--primary); margin-bottom:16px;
}
/* 活动类型标签 */
.fr-type-tag { font-size:11px; font-weight:500; padding:1px 6px; border-radius:4px; }
.fr-type-tag.blue { background:color-mix(in srgb, var(--primary) 12%, #fff); color:var(--primary); }
.fr-type-tag.green { background:color-mix(in srgb, var(--g-success) 12%, #fff); color:var(--g-success); }
.fr-type-tag.orange { background:color-mix(in srgb, var(--g-warning) 12%, #fff); color:var(--g-warning); }
/* 满赠商品选择 */
.fr-gift-item {
display:inline-flex; align-items:center; gap:6px; padding:6px 10px;
background:#f8f9fb; border-radius:6px; font-size:12px; color:var(--g-text-secondary); border:1px solid #e5e7eb;
}
.fr-gift-item button { background:none; border:none; color:var(--g-text-muted); cursor:pointer; padding:0; }
.fr-gift-item button:hover { color:var(--g-danger); }
</style>
<div class="page-fr">
@@ -110,6 +124,12 @@
<option>老三家外卖(通州店)</option>
<option>老三家外卖(丰台店)</option>
</select>
<select style="width:130px;">
<option value="">全部类型</option>
<option>满减</option>
<option>满赠</option>
<option>第二份半价</option>
</select>
<select style="width:130px;">
<option value="">全部状态</option>
<option>进行中</option>
@@ -119,7 +139,7 @@
<input type="text" placeholder="搜索活动名称" style="width:200px;" />
<div style="flex:1;"></div>
<button class="g-btn g-btn-primary" onclick="openFrDrawer('create')">
<i data-lucide="plus" style="width:14px;height:14px;"></i>创建满减活动
<i data-lucide="plus" style="width:14px;height:14px;"></i>创建活动
</button>
</div>
@@ -150,6 +170,7 @@
<div class="fr-card">
<div class="fr-card-hd">
<span class="fr-card-name">午市满减优惠</span>
<span class="fr-type-tag blue">满减</span>
<span class="g-tag g-tag-green">进行中</span>
</div>
<div class="fr-tiers">
@@ -180,6 +201,7 @@
<div class="fr-card">
<div class="fr-card-hd">
<span class="fr-card-name">晚市大额满减</span>
<span class="fr-type-tag blue">满减</span>
<span class="g-tag g-tag-green">进行中</span>
</div>
<div class="fr-tiers">
@@ -210,6 +232,7 @@
<div class="fr-card">
<div class="fr-card-hd">
<span class="fr-card-name">春季新客满减</span>
<span class="fr-type-tag blue">满减</span>
<span class="g-tag g-tag-blue">未开始</span>
</div>
<div class="fr-tiers">
@@ -240,6 +263,7 @@
<div class="fr-card fr-ended">
<div class="fr-card-hd">
<span class="fr-card-name">元旦满减狂欢</span>
<span class="fr-type-tag blue">满减</span>
<span class="g-tag g-tag-gray">已结束</span>
</div>
<div class="fr-tiers">
@@ -265,6 +289,62 @@
</div>
</div>
<!-- 活动5满赠 进行中 -->
<div class="fr-card">
<div class="fr-card-hd">
<span class="fr-card-name">饮品买二赠一</span>
<span class="fr-type-tag green">满赠</span>
<span class="g-tag g-tag-green">进行中</span>
</div>
<div class="fr-tiers">
<span class="fr-tier-pill">买2杯赠1杯</span>
</div>
<div class="fr-meta">
<span class="fr-meta-item"><i data-lucide="calendar" style="width:14px;height:14px;"></i>2026-02-01 ~ 2026-03-31</span>
<span class="fr-meta-item"><i data-lucide="truck" style="width:14px;height:14px;"></i>外卖 / 自提 / 堂食</span>
<span class="fr-meta-item"><i data-lucide="store" style="width:14px;height:14px;"></i>全部门店</span>
<span class="fr-meta-item"><i data-lucide="gift" style="width:14px;height:14px;"></i>赠品:指定饮品(价低者)</span>
</div>
<div class="fr-data">
<span class="fr-data-item">参与订单 <span>98单</span></span>
<span class="fr-data-item">赠出商品 <span>98件</span></span>
<span class="fr-data-item">带动销售 <span>¥4,120</span></span>
</div>
<div class="fr-card-ft">
<a class="g-action" onclick="openFrDrawer('edit')">编辑</a>
<a class="g-action">停用</a>
<a class="g-action g-action-danger">删除</a>
</div>
</div>
<!-- 活动6第二份半价 进行中 -->
<div class="fr-card">
<div class="fr-card-hd">
<span class="fr-card-name">甜品第二份半价</span>
<span class="fr-type-tag orange">第二份半价</span>
<span class="g-tag g-tag-green">进行中</span>
</div>
<div class="fr-tiers">
<span class="fr-tier-pill">第2份5折</span>
</div>
<div class="fr-meta">
<span class="fr-meta-item"><i data-lucide="calendar" style="width:14px;height:14px;"></i>2026-02-10 ~ 2026-02-28</span>
<span class="fr-meta-item"><i data-lucide="truck" style="width:14px;height:14px;"></i>外卖 / 堂食</span>
<span class="fr-meta-item"><i data-lucide="store" style="width:14px;height:14px;"></i>全部门店</span>
<span class="fr-meta-item"><i data-lucide="tag" style="width:14px;height:14px;"></i>适用:甜品分类</span>
</div>
<div class="fr-data">
<span class="fr-data-item">参与订单 <span>45单</span></span>
<span class="fr-data-item">优惠总额 <span>¥380</span></span>
<span class="fr-data-item">连带率提升 <span>32%</span></span>
</div>
<div class="fr-card-ft">
<a class="g-action" onclick="openFrDrawer('edit')">编辑</a>
<a class="g-action">停用</a>
<a class="g-action g-action-danger">删除</a>
</div>
</div>
</div>
<!-- 分页 -->
@@ -286,43 +366,115 @@
<button class="g-drawer-close" onclick="closeFrDrawer()"><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>
<div style="display:flex;gap:10px;">
<span class="g-pill checked" onclick="selectFrType(this,'reduce')">满减</span>
<span class="g-pill" onclick="selectFrType(this,'gift')">满赠</span>
<span class="g-pill" onclick="selectFrType(this,'half')">第二份半价</span>
</div>
</div>
<!-- 活动名称 -->
<div class="g-form-group">
<label class="g-form-label required">活动名称</label>
<input class="g-input" placeholder="如:午市满减优惠" />
</div>
<!-- 满减规则 -->
<div class="fr-section-hd" style="margin-top:8px;">满减规则</div>
<div id="frTierList">
<div class="fr-tier-row">
<span></span>
<input type="number" value="30" placeholder="金额" />
<span>元减</span>
<input type="number" value="5" placeholder="金额" />
<span></span>
<button class="fr-tier-del" onclick="removeFrTier(this)" title="删除"><i data-lucide="trash-2" style="width:14px;height:14px;"></i></button>
<!-- ===== 满减规则 ===== -->
<div id="frRuleReduce">
<div class="fr-section-hd" style="margin-top:8px;">满减规则</div>
<div id="frTierList">
<div class="fr-tier-row">
<span></span>
<input type="number" value="30" placeholder="金额" />
<span>元减</span>
<input type="number" value="5" placeholder="金额" />
<span></span>
<button class="fr-tier-del" onclick="removeFrTier(this)" title="删除"><i data-lucide="trash-2" style="width:14px;height:14px;"></i></button>
</div>
<div class="fr-tier-row">
<span></span>
<input type="number" value="50" placeholder="金额" />
<span>元减</span>
<input type="number" value="10" placeholder="金额" />
<span></span>
<button class="fr-tier-del" onclick="removeFrTier(this)" title="删除"><i data-lucide="trash-2" style="width:14px;height:14px;"></i></button>
</div>
<div class="fr-tier-row">
<span></span>
<input type="number" value="80" placeholder="金额" />
<span>元减</span>
<input type="number" value="20" placeholder="金额" />
<span></span>
<button class="fr-tier-del" onclick="removeFrTier(this)" title="删除"><i data-lucide="trash-2" style="width:14px;height:14px;"></i></button>
</div>
</div>
<div class="fr-tier-row">
<span></span>
<input type="number" value="50" placeholder="金额" />
<span>元减</span>
<input type="number" value="10" placeholder="金额" />
<span></span>
<button class="fr-tier-del" onclick="removeFrTier(this)" title="删除"><i data-lucide="trash-2" style="width:14px;height:14px;"></i></button>
<button class="fr-add-tier" onclick="addFrTier()">
<i data-lucide="plus" style="width:14px;height:14px;"></i>添加阶梯
</button>
</div>
<!-- ===== 满赠规则 ===== -->
<div id="frRuleGift" style="display:none;">
<div class="fr-section-hd" style="margin-top:8px;">满赠规则</div>
<div class="g-form-group">
<label class="g-form-label required">赠送条件</label>
<div style="display:flex;align-items:center;gap:8px;font-size:13px;color:var(--g-text);">
<span>购买满</span>
<input class="g-input" type="number" value="2" min="1" style="width:70px;text-align:center;" placeholder="如2">
<span>件赠</span>
<input class="g-input" type="number" value="1" min="1" style="width:70px;text-align:center;" placeholder="如1">
<span></span>
</div>
</div>
<div class="fr-tier-row">
<span></span>
<input type="number" value="80" placeholder="金额" />
<span>元减</span>
<input type="number" value="20" placeholder="金额" />
<span></span>
<button class="fr-tier-del" onclick="removeFrTier(this)" title="删除"><i data-lucide="trash-2" style="width:14px;height:14px;"></i></button>
<div class="g-form-group">
<label class="g-form-label required">赠品范围</label>
<div style="display:flex;gap:10px;margin-bottom:8px;">
<span class="g-pill checked" onclick="this.parentElement.querySelectorAll('.g-pill').forEach(function(p){p.classList.remove('checked')});this.classList.add('checked')">同商品(价低者)</span>
<span class="g-pill" onclick="this.parentElement.querySelectorAll('.g-pill').forEach(function(p){p.classList.remove('checked')});this.classList.add('checked')">指定赠品</span>
</div>
<div class="g-hint">选"同商品"时,赠品为购买商品中价格最低的一件</div>
</div>
<div class="g-form-group">
<label class="g-form-label">适用商品</label>
<button class="g-btn g-btn-sm" onclick="openProductPicker({title:'选择适用商品',subtitle:'满赠活动',onConfirm:function(){}})"><i data-lucide="plus" style="width:13px;height:13px;"></i>选择商品/分类</button>
<div style="display:flex;flex-wrap:wrap;gap:6px;margin-top:8px;">
<span class="fr-gift-item">热饮分类 <button onclick="this.parentElement.remove()"><i data-lucide="x" style="width:12px;height:12px;"></i></button></span>
<span class="fr-gift-item">冷饮分类 <button onclick="this.parentElement.remove()"><i data-lucide="x" style="width:12px;height:12px;"></i></button></span>
</div>
<div class="g-hint">留空表示全部商品参与</div>
</div>
</div>
<!-- ===== 第二份半价规则 ===== -->
<div id="frRuleHalf" style="display:none;">
<div class="fr-section-hd" style="margin-top:8px;">第二份优惠</div>
<div class="g-form-group">
<label class="g-form-label required">第二份折扣</label>
<div style="display:flex;gap:10px;">
<span class="g-pill checked" onclick="this.parentElement.querySelectorAll('.g-pill').forEach(function(p){p.classList.remove('checked')});this.classList.add('checked')">5折</span>
<span class="g-pill" onclick="this.parentElement.querySelectorAll('.g-pill').forEach(function(p){p.classList.remove('checked')});this.classList.add('checked')">6折</span>
<span class="g-pill" onclick="this.parentElement.querySelectorAll('.g-pill').forEach(function(p){p.classList.remove('checked')});this.classList.add('checked')">7折</span>
<span class="g-pill" onclick="this.parentElement.querySelectorAll('.g-pill').forEach(function(p){p.classList.remove('checked')});this.classList.add('checked')">免费</span>
</div>
</div>
<div class="g-form-group">
<label class="g-form-label required">适用商品</label>
<div style="display:flex;gap:10px;margin-bottom:8px;">
<span class="g-pill checked" onclick="this.parentElement.querySelectorAll('.g-pill').forEach(function(p){p.classList.remove('checked')});this.classList.add('checked')">指定分类</span>
<span class="g-pill" onclick="this.parentElement.querySelectorAll('.g-pill').forEach(function(p){p.classList.remove('checked')});this.classList.add('checked')">指定商品</span>
</div>
<select class="g-select" style="width:100%;">
<option value="">请选择分类</option>
<option>甜品</option>
<option>饮品</option>
<option>小食</option>
</select>
<div class="g-hint">第二份优惠仅适用于所选范围内的商品</div>
</div>
</div>
<button class="fr-add-tier" onclick="addFrTier()">
<i data-lucide="plus" style="width:14px;height:14px;"></i>添加阶梯
</button>
<!-- 活动时间 -->
<div class="g-form-group" style="margin-top:20px;">
@@ -383,13 +535,22 @@
function openFrDrawer(mode) {
document.getElementById('frDrawerMask').classList.add('open');
document.getElementById('frDrawer').classList.add('open');
document.getElementById('frDrawerTitle').textContent = mode === 'edit' ? '编辑满减活动' : '创建满减活动';
document.getElementById('frDrawerTitle').textContent = mode === 'edit' ? '编辑活动' : '创建活动';
}
function closeFrDrawer() {
document.getElementById('frDrawerMask').classList.remove('open');
document.getElementById('frDrawer').classList.remove('open');
}
/* 活动类型切换 */
function selectFrType(el, type) {
el.parentElement.querySelectorAll('.g-pill').forEach(function(p) { p.classList.remove('checked'); });
el.classList.add('checked');
document.getElementById('frRuleReduce').style.display = type === 'reduce' ? '' : 'none';
document.getElementById('frRuleGift').style.display = type === 'gift' ? '' : 'none';
document.getElementById('frRuleHalf').style.display = type === 'half' ? '' : 'none';
}
/* 添加阶梯行 */
function addFrTier() {
var list = document.getElementById('frTierList');