dm-design/web/merchant/js/member-tabs.js

2159 lines
106 KiB
JavaScript
Raw Normal View History

2025-07-31 01:20:15 +00:00
// Tab系统管理 - 会员管理模块
// Tab管理器类
class MemberTabManager {
constructor() {
this.tabs = [];
this.activeTabId = '';
this.tabCounter = 0;
}
// 创建新tab
createTab(id, title, content, canClose = true) {
// 检查tab是否已存在
const existingTab = this.tabs.find(tab => tab.id === id);
if (existingTab) {
this.showTab(id);
return existingTab;
}
const tab = {
id: id,
title: title,
content: content,
canClose: canClose
};
this.tabs.push(tab);
this.renderTabs();
this.showTab(id);
return tab;
}
// 关闭tab
closeTab(tabId) {
const tabIndex = this.tabs.findIndex(tab => tab.id === tabId);
if (tabIndex === -1) return;
const tab = this.tabs[tabIndex];
if (!tab.canClose) return;
this.tabs.splice(tabIndex, 1);
// 如果关闭的是当前激活的tab切换到其他tab
if (this.activeTabId === tabId) {
if (this.tabs.length > 0) {
this.showTab(this.tabs[this.tabs.length - 1].id);
} else {
this.showDefaultContent();
}
}
this.renderTabs();
}
// 显示指定tab
showTab(tabId) {
const tab = this.tabs.find(t => t.id === tabId);
if (!tab) return;
this.activeTabId = tabId;
// 更新tab激活状态
document.querySelectorAll('.tab-item').forEach(tabEl => {
if (tabEl.dataset.tabId === tabId) {
tabEl.classList.add('border-green-500', 'text-green-600');
tabEl.classList.remove('border-transparent', 'text-gray-500');
} else {
tabEl.classList.remove('border-green-500', 'text-green-600');
tabEl.classList.add('border-transparent', 'text-gray-500');
}
});
// 更新内容区域
const contentArea = document.getElementById('tab-content-area');
if (contentArea) {
contentArea.innerHTML = tab.content;
}
// 显示tab容器
this.showTabContainer();
}
// 渲染tab导航
renderTabs() {
const tabNav = document.getElementById('tab-nav');
if (!tabNav) return;
tabNav.innerHTML = '';
this.tabs.forEach(tab => {
const tabElement = document.createElement('div');
tabElement.className = `tab-item flex items-center px-4 py-3 border-b-2 cursor-pointer transition-colors ${
this.activeTabId === tab.id
? 'border-green-500 text-green-600'
: 'border-transparent text-gray-500 hover:text-gray-700'
}`;
tabElement.dataset.tabId = tab.id;
tabElement.innerHTML = `
<span class="text-sm font-medium">${tab.title}</span>
${tab.canClose ? `
<button class="ml-2 p-1 hover:bg-gray-100 rounded" onclick="event.stopPropagation(); memberTabManager.closeTab('${tab.id}')">
<svg class="h-3 w-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
` : ''}
`;
tabElement.addEventListener('click', () => this.showTab(tab.id));
tabNav.appendChild(tabElement);
});
}
// 显示tab容器
showTabContainer() {
const tabContainer = document.getElementById('tab-container');
if (tabContainer) {
tabContainer.classList.remove('hidden');
tabContainer.classList.add('fade-in');
}
}
// 显示默认内容
showDefaultContent() {
const tabContainer = document.getElementById('tab-container');
if (tabContainer) {
const contentArea = document.getElementById('tab-content-area');
if (contentArea) {
contentArea.innerHTML = `
<div class="flex items-center justify-center h-96 text-gray-500">
<div class="text-center">
<svg class="h-16 w-16 mx-auto mb-4 text-gray-300" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2" />
<circle cx="9" cy="7" r="4" />
<path d="m22 21-3-3m0 0a8 8 0 1 1-11.31-11.31 8 8 0 0 1 11.31 11.31z" />
</svg>
<p class="text-lg">会员管理</p>
<p class="text-sm mt-2">请从左侧菜单选择功能</p>
</div>
</div>
`;
}
}
this.activeTabId = '';
this.tabs = [];
this.renderTabs();
}
// 清空所有tabs
clearAllTabs() {
this.tabs = [];
this.activeTabId = '';
this.showDefaultContent();
}
}
// 创建全局tab管理器实例
const memberTabManager = new MemberTabManager();
// 打开等级设置Tab
function openLevelSettingsTab() {
const tabId = 'level-settings';
const content = `
<div class="p-6">
<div class="flex items-center justify-between mb-6">
<h2 class="text-lg font-medium text-gray-900">等级设置</h2>
</div>
<!-- 摊位名称筛选器 -->
<div class="mb-6 flex items-center gap-4">
<div class="flex-1 max-w-md">
<label class="block text-sm font-medium text-gray-700 mb-2">摊位名称</label>
<div class="relative">
<select id="stall-filter" multiple class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-green-500 focus:border-green-500 bg-white" size="1" onclick="toggleStallDropdown(event)">
<option value="">请选择摊位名称...</option>
<option value="时尚服装店">时尚服装店</option>
<option value="数码电子城">数码电子城</option>
<option value="美食餐厅">美食餐厅</option>
<option value="家居生活馆">家居生活馆</option>
<option value="运动健身店">运动健身店</option>
</select>
<div id="selected-stalls" class="mt-2 flex flex-wrap gap-2"></div>
</div>
</div>
<div class="mt-7 flex gap-3">
<button id="query-btn" class="px-4 py-2 bg-green-600 text-white rounded-md hover:bg-green-700 transition-colors" onclick="filterStallTable()">
查询
</button>
<button id="batch-edit-btn" class="px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 transition-colors" onclick="openBatchLevelEditTab()">
批量会员等级编辑
</button>
</div>
</div>
<div class="overflow-x-auto">
<table class="w-full">
<thead class="bg-gray-50">
<tr>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
摊位名称
</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
是否启用会员
</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
会员等级
</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
操作
</th>
</tr>
</thead>
<tbody class="bg-white divide-y divide-gray-200">
<tr>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">时尚服装店</td>
<td class="px-6 py-4 whitespace-nowrap">
<label class="relative inline-flex items-center cursor-pointer">
<input type="checkbox" class="sr-only peer" checked onchange="toggleSwitch(this)">
<div class="w-11 h-6 bg-gray-200 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-green-300 rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-green-600"></div>
</label>
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">lv1, lv2, lv3, lv4</td>
<td class="px-6 py-4 whitespace-nowrap">
<button class="text-green-600 hover:text-green-900 text-sm font-medium" onclick="openLevelDetailTab('时尚服装店')">
设置等级明细
</button>
</td>
</tr>
<tr>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">数码电子城</td>
<td class="px-6 py-4 whitespace-nowrap">
<label class="relative inline-flex items-center cursor-pointer">
<input type="checkbox" class="sr-only peer" onchange="toggleSwitch(this)">
<div class="w-11 h-6 bg-gray-200 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-green-300 rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-green-600"></div>
</label>
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">lv1, lv2, lv3</td>
<td class="px-6 py-4 whitespace-nowrap">
<button class="text-green-600 hover:text-green-900 text-sm font-medium" onclick="openLevelDetailTab('数码电子城')">
设置等级明细
</button>
</td>
</tr>
<tr>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">美食餐厅</td>
<td class="px-6 py-4 whitespace-nowrap">
<label class="relative inline-flex items-center cursor-pointer">
<input type="checkbox" class="sr-only peer" checked onchange="toggleSwitch(this)">
<div class="w-11 h-6 bg-gray-200 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-green-300 rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-green-600"></div>
</label>
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">lv1, lv2, lv3, lv4</td>
<td class="px-6 py-4 whitespace-nowrap">
<button class="text-green-600 hover:text-green-900 text-sm font-medium" onclick="openLevelDetailTab('美食餐厅')">
设置等级明细
</button>
</td>
</tr>
<tr>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">家居生活馆</td>
<td class="px-6 py-4 whitespace-nowrap">
<label class="relative inline-flex items-center cursor-pointer">
<input type="checkbox" class="sr-only peer" onchange="toggleSwitch(this)">
<div class="w-11 h-6 bg-gray-200 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-green-300 rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-green-600"></div>
</label>
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">lv1, lv2</td>
<td class="px-6 py-4 whitespace-nowrap">
<button class="text-green-600 hover:text-green-900 text-sm font-medium" onclick="openLevelDetailTab('家居生活馆')">
设置等级明细
</button>
</td>
</tr>
<tr>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">运动健身店</td>
<td class="px-6 py-4 whitespace-nowrap">
<label class="relative inline-flex items-center cursor-pointer">
<input type="checkbox" class="sr-only peer" checked onchange="toggleSwitch(this)">
<div class="w-11 h-6 bg-gray-200 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-green-300 rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-green-600"></div>
</label>
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">lv1, lv2, lv3, lv4</td>
<td class="px-6 py-4 whitespace-nowrap">
<button class="text-green-600 hover:text-green-900 text-sm font-medium" onclick="openLevelDetailTab('运动健身店')">
设置等级明细
</button>
</td>
</tr>
</tbody>
</table>
</div>
</div>
`;
memberTabManager.createTab(tabId, '等级设置', content, false);
}
// 打开等级明细tab
function openLevelDetailTab(shopName) {
const tabId = `level-detail-${shopName}`;
// 生成模拟数据
const mockLevels = [
{ name: 'LV1', growth: 100, members: 1250, benefits: '9.5折优惠' },
{ name: 'LV2', growth: 500, members: 850, benefits: '9折优惠, 积分双倍' },
{ name: 'LV3', growth: 1200, members: 450, benefits: '8.5折优惠, 积分双倍, 生日优惠券' },
{ name: 'LV4', growth: 2500, members: 180, benefits: '8折优惠, 积分三倍, 生日优惠券, 专属客服' }
];
const content = `
<div class="p-6">
<div class="flex items-center justify-between mb-6">
<h2 class="text-lg font-medium text-gray-900">${shopName} - 等级设置明细</h2>
<div class="flex gap-3">
<button class="px-4 py-2 bg-green-600 text-white rounded-md hover:bg-green-700 transition-colors" onclick="openLevelEditTab('${shopName}')">
等级编辑
</button>
<button class="px-4 py-2 bg-green-600 text-white rounded-md hover:bg-green-700 transition-colors" onclick="submitLevelDetail('${shopName}')">
提交
</button>
</div>
</div>
<div class="overflow-x-auto">
<table class="w-full border border-gray-200 rounded-lg">
<thead class="bg-gray-50">
<tr>
<th class="px-4 py-3 text-left text-sm font-medium text-gray-900 border-b">等级名称</th>
<th class="px-4 py-3 text-left text-sm font-medium text-gray-900 border-b">所需成长值</th>
<th class="px-4 py-3 text-left text-sm font-medium text-gray-900 border-b">会员人数</th>
<th class="px-4 py-3 text-left text-sm font-medium text-gray-900 border-b">会员权益</th>
</tr>
</thead>
<tbody class="bg-white divide-y divide-gray-200">
${mockLevels.map(level => `
<tr>
<td class="px-4 py-3 text-sm text-gray-900">${level.name}</td>
<td class="px-4 py-3 text-sm text-gray-900">${level.growth}</td>
<td class="px-4 py-3 text-sm text-gray-900">${level.members}</td>
<td class="px-4 py-3 text-sm text-gray-900">${level.benefits}</td>
</tr>
`).join('')}
</tbody>
</table>
</div>
</div>
`;
memberTabManager.createTab(tabId, `${shopName} - 等级明细`, content, true);
}
// 打开等级编辑Tab
function openLevelEditTab(shopName) {
const tabId = `level-edit-${shopName}`;
// 生成模拟等级编辑数据
const mockEditLevels = [
{
level: 'LV1',
name: '铜牌会员',
growthMin: 0,
growthMax: 100,
memberDiscount: true,
discountRate: 5,
pointExchange: false,
birthdayCoupon: false,
birthdayDoublePoints: false
},
{
level: 'LV2',
name: '银牌会员',
growthMin: 101,
growthMax: 500,
memberDiscount: true,
discountRate: 10,
pointExchange: true,
birthdayCoupon: true,
birthdayDoublePoints: true
},
{
level: 'LV3',
name: '金牌会员',
growthMin: 501,
growthMax: 1200,
memberDiscount: true,
discountRate: 15,
pointExchange: true,
birthdayCoupon: true,
birthdayDoublePoints: true
},
{
level: 'LV4',
name: '钻石会员',
growthMin: 1201,
growthMax: 9999,
memberDiscount: true,
discountRate: 20,
pointExchange: true,
birthdayCoupon: true,
birthdayDoublePoints: true
}
];
const content = `
<div class="p-6">
<div class="flex items-center justify-between mb-6">
<h2 class="text-lg font-medium text-gray-900">${shopName} - 等级编辑</h2>
<div class="flex gap-3">
<button class="px-4 py-2 bg-gray-500 text-white rounded-md hover:bg-gray-600 transition-colors" onclick="memberTabManager.closeTab('${tabId}')">
关闭
</button>
<button class="px-4 py-2 bg-green-600 text-white rounded-md hover:bg-green-700 transition-colors" onclick="submitLevelEdit('${shopName}')">
提交
</button>
</div>
</div>
<div class="overflow-x-auto">
<table class="w-full border border-gray-200 rounded-lg">
<thead class="bg-gray-50">
<tr>
<th class="px-3 py-3 text-left text-xs font-medium text-gray-900 border-b">等级</th>
<th class="px-3 py-3 text-left text-xs font-medium text-gray-900 border-b">等级名称</th>
<th class="px-3 py-3 text-left text-xs font-medium text-gray-900 border-b">成长值范围</th>
<th class="px-3 py-3 text-left text-xs font-medium text-gray-900 border-b">开启会员折扣</th>
<th class="px-3 py-3 text-left text-xs font-medium text-gray-900 border-b">折扣率(%)</th>
<th class="px-3 py-3 text-left text-xs font-medium text-gray-900 border-b">开启积分兑换</th>
<th class="px-3 py-3 text-left text-xs font-medium text-gray-900 border-b">开启生日优惠券</th>
<th class="px-3 py-3 text-left text-xs font-medium text-gray-900 border-b">生日优惠券操作</th>
<th class="px-3 py-3 text-left text-xs font-medium text-gray-900 border-b">开启生日双倍积分</th>
</tr>
</thead>
<tbody class="bg-white divide-y divide-gray-200">
${mockEditLevels.map(level => `
<tr>
<td class="px-3 py-3 text-sm text-gray-900">${level.level}</td>
<td class="px-3 py-3">
<input type="text" value="${level.name}" class="w-full px-2 py-1 border border-gray-300 rounded text-sm">
</td>
<td class="px-3 py-3 text-sm text-gray-900">
<div class="flex items-center gap-1">
<input type="number" value="${level.growthMin}" class="w-16 px-2 py-1 border border-gray-300 rounded text-sm">
<span>-</span>
<input type="number" value="${level.growthMax}" class="w-16 px-2 py-1 border border-gray-300 rounded text-sm">
</div>
</td>
<td class="px-3 py-3">
<label class="relative inline-flex items-center cursor-pointer">
<input type="checkbox" class="sr-only peer" ${level.memberDiscount ? 'checked' : ''}>
<div class="w-9 h-5 bg-gray-200 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-green-300 rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-4 after:w-4 after:transition-all peer-checked:bg-green-600"></div>
</label>
</td>
<td class="px-3 py-3">
<input type="number" value="${level.discountRate}" class="w-16 px-2 py-1 border border-gray-300 rounded text-sm">
</td>
<td class="px-3 py-3">
<label class="relative inline-flex items-center cursor-pointer">
<input type="checkbox" class="sr-only peer" ${level.pointExchange ? 'checked' : ''}>
<div class="w-9 h-5 bg-gray-200 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-green-300 rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-4 after:w-4 after:transition-all peer-checked:bg-green-600"></div>
</label>
</td>
<td class="px-3 py-3">
<label class="relative inline-flex items-center cursor-pointer">
<input type="checkbox" class="sr-only peer" ${level.birthdayCoupon ? 'checked' : ''}>
<div class="w-9 h-5 bg-gray-200 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-green-300 rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-4 after:w-4 after:transition-all peer-checked:bg-green-600"></div>
</label>
</td>
<td class="px-3 py-3">
<button class="px-3 py-1 bg-blue-600 text-white text-xs rounded hover:bg-blue-700 transition-colors" onclick="openCouponModal('${level.level}')">
添加优惠券
</button>
</td>
<td class="px-3 py-3">
<label class="relative inline-flex items-center cursor-pointer">
<input type="checkbox" class="sr-only peer" ${level.birthdayDoublePoints ? 'checked' : ''}>
<div class="w-9 h-5 bg-gray-200 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-green-300 rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-4 after:w-4 after:transition-all peer-checked:bg-green-600"></div>
</label>
</td>
</tr>
`).join('')}
</tbody>
</table>
</div>
</div>
`;
memberTabManager.createTab(tabId, `${shopName} - 等级编辑`, content, true);
}
// 打开优惠券弹窗
function openCouponModal(level) {
const modal = document.getElementById('coupon-modal');
if (modal) {
modal.classList.remove('hidden');
resetCouponForm();
console.log(`为等级 ${level} 添加优惠券`);
}
}
// 测试优惠券弹窗功能
function testCouponModal() {
openCouponModal('TEST');
}
// 优惠券相关函数
function closeCouponModal(event) {
if (event && event.target !== event.currentTarget) {
return;
}
const modal = document.getElementById('coupon-modal');
if (modal) {
modal.classList.add('hidden');
}
}
function resetCouponForm() {
document.getElementById('coupon-name').value = '生日优惠券';
document.getElementById('coupon-threshold').value = '10';
document.getElementById('coupon-discount').value = '1';
document.getElementById('coupon-validity').value = '1';
}
function adjustThreshold(delta) {
const input = document.getElementById('coupon-threshold');
if (input) {
const currentValue = parseInt(input.value) || 0;
const newValue = Math.max(0, currentValue + delta);
input.value = newValue;
}
}
function adjustDiscount(delta) {
const input = document.getElementById('coupon-discount');
if (input) {
const currentValue = parseInt(input.value) || 0;
const newValue = Math.max(0, currentValue + delta);
input.value = newValue;
}
}
function submitCoupon() {
const couponData = {
name: document.getElementById('coupon-name').value,
threshold: document.getElementById('coupon-threshold').value,
discount: document.getElementById('coupon-discount').value,
validity: document.getElementById('coupon-validity').value
};
if (!couponData.name.trim()) {
showNotification('请输入优惠券名称', 'error');
return;
}
if (parseInt(couponData.discount) >= parseInt(couponData.threshold)) {
showNotification('减免金额不能大于或等于门槛金额', 'error');
return;
}
showNotification(`成功添加优惠券:${couponData.name}`, 'success');
closeCouponModal();
}
function submitLevelDetail(shopName) {
showNotification(`${shopName} 等级明细已提交`, 'success');
}
function submitLevelEdit(shopName) {
showNotification(`${shopName} 等级编辑已提交`, 'success');
}
// 摊位筛选相关功能
let selectedStalls = [];
let stallDropdownOpen = false;
function toggleStallDropdown(event) {
event.preventDefault();
const select = document.getElementById('stall-filter');
const selectedDiv = document.getElementById('selected-stalls');
if (!stallDropdownOpen) {
select.size = 6;
stallDropdownOpen = true;
// 添加点击事件监听器
document.addEventListener('click', closeStallDropdown);
}
}
function closeStallDropdown(event) {
const select = document.getElementById('stall-filter');
const selectedDiv = document.getElementById('selected-stalls');
if (!select || !select.contains(event.target)) {
select.size = 1;
stallDropdownOpen = false;
document.removeEventListener('click', closeStallDropdown);
}
}
// 处理多选选项
document.addEventListener('change', function(event) {
if (event.target.id === 'stall-filter') {
const select = event.target;
const selectedOptions = Array.from(select.selectedOptions);
const selectedDiv = document.getElementById('selected-stalls');
selectedStalls = selectedOptions
.filter(option => option.value && option.value !== '')
.map(option => option.value);
updateSelectedStallsDisplay();
}
});
function updateSelectedStallsDisplay() {
const selectedDiv = document.getElementById('selected-stalls');
if (!selectedDiv) return;
selectedDiv.innerHTML = '';
selectedStalls.forEach(stall => {
const tag = document.createElement('span');
tag.className = 'inline-flex items-center px-2 py-1 rounded-md text-xs font-medium bg-green-100 text-green-800';
tag.innerHTML = `
${stall}
<button type="button" class="ml-1 inline-flex items-center justify-center w-4 h-4 rounded-full hover:bg-green-200" onclick="removeSelectedStall('${stall}')">
<svg class="w-2 h-2" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd"></path>
</svg>
</button>
`;
selectedDiv.appendChild(tag);
});
}
function removeSelectedStall(stallName) {
selectedStalls = selectedStalls.filter(stall => stall !== stallName);
// 更新select控件的选中状态
const select = document.getElementById('stall-filter');
if (select) {
Array.from(select.options).forEach(option => {
if (option.value === stallName) {
option.selected = false;
}
});
}
updateSelectedStallsDisplay();
}
function filterStallTable() {
const tableRows = document.querySelectorAll('#tab-content-area tbody tr');
if (selectedStalls.length === 0) {
// 如果没有选择任何摊位,显示所有行
tableRows.forEach(row => {
row.style.display = '';
});
showNotification('已显示所有摊位', 'info');
} else {
// 根据选择的摊位筛选表格行
let visibleCount = 0;
tableRows.forEach(row => {
const stallNameCell = row.querySelector('td:first-child');
if (stallNameCell) {
const stallName = stallNameCell.textContent.trim();
if (selectedStalls.includes(stallName)) {
row.style.display = '';
visibleCount++;
} else {
row.style.display = 'none';
}
}
});
showNotification(`已筛选显示 ${visibleCount} 个摊位`, 'success');
}
}
// 打开批量会员等级编辑Tab
function openBatchLevelEditTab() {
const tabId = 'batch-level-edit';
// 生成摊位数据
const stallOptions = [
'新世纪电子城',
'金辉服装店',
'华联超市',
'美味餐厅',
'家具城',
'运动专营店',
'书香文具店',
'花园茶社'
];
// 生成等级编辑数据(与等级编辑页面内容一致)
const mockEditLevels = [
{
level: 'LV1',
name: '铜牌会员',
growthMin: 0,
growthMax: 100,
memberDiscount: true,
discountRate: 5,
pointExchange: false,
birthdayCoupon: false,
birthdayDoublePoints: false
},
{
level: 'LV2',
name: '银牌会员',
growthMin: 101,
growthMax: 500,
memberDiscount: true,
discountRate: 10,
pointExchange: true,
birthdayCoupon: true,
birthdayDoublePoints: true
},
{
level: 'LV3',
name: '金牌会员',
growthMin: 501,
growthMax: 1200,
memberDiscount: true,
discountRate: 15,
pointExchange: true,
birthdayCoupon: true,
birthdayDoublePoints: true
},
{
level: 'LV4',
name: '钻石会员',
growthMin: 1201,
growthMax: 9999,
memberDiscount: true,
discountRate: 20,
pointExchange: true,
birthdayCoupon: true,
birthdayDoublePoints: true
}
];
const content = `
<div class="p-6">
<div class="flex items-center justify-between mb-6">
<h2 class="text-lg font-medium text-gray-900">批量会员等级编辑</h2>
<div class="flex gap-3">
<button class="px-4 py-2 bg-gray-500 text-white rounded-md hover:bg-gray-600 transition-colors" onclick="memberTabManager.closeTab('${tabId}')">
关闭
</button>
<button class="px-4 py-2 bg-green-600 text-white rounded-md hover:bg-green-700 transition-colors" onclick="submitBatchLevelEdit()">
提交
</button>
</div>
</div>
<!-- 生效摊位选择区域 -->
<div class="mb-6 flex items-center gap-4">
<div class="flex-1 max-w-md">
<label class="block text-sm font-medium text-gray-700 mb-2">生效的摊位</label>
<div class="relative">
<button id="batch-stall-dropdown-btn" class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-green-500 focus:border-green-500 bg-white text-left flex items-center justify-between" onclick="toggleBatchStallDropdown()">
<span id="batch-dropdown-text">全部选择</span>
<svg class="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
</svg>
</button>
<div id="batch-stall-dropdown-list" class="absolute z-10 w-full mt-1 bg-white border border-gray-300 rounded-md shadow-lg hidden">
${stallOptions.map(stall => `
<label class="flex items-center px-3 py-2 hover:bg-gray-50 cursor-pointer">
<input type="checkbox" value="${stall}" class="mr-2" checked onchange="updateBatchSelectedStalls()">
${stall}
</label>
`).join('')}
</div>
<div id="batch-selected-stalls" class="mt-2 flex flex-wrap gap-2"></div>
</div>
</div>
<div class="mt-7">
<button class="px-4 py-2 bg-green-600 text-white rounded-md hover:bg-green-700 transition-colors" onclick="submitBatchLevelEdit()">
提交
</button>
</div>
</div>
<!-- 等级编辑表格与等级编辑页面内容一致 -->
<div class="overflow-x-auto">
<table class="w-full border border-gray-200 rounded-lg">
<thead class="bg-gray-50">
<tr>
<th class="px-3 py-3 text-left text-xs font-medium text-gray-900 border-b">等级</th>
<th class="px-3 py-3 text-left text-xs font-medium text-gray-900 border-b">等级名称</th>
<th class="px-3 py-3 text-left text-xs font-medium text-gray-900 border-b">成长值范围</th>
<th class="px-3 py-3 text-left text-xs font-medium text-gray-900 border-b">开启会员折扣</th>
<th class="px-3 py-3 text-left text-xs font-medium text-gray-900 border-b">折扣率(%)</th>
<th class="px-3 py-3 text-left text-xs font-medium text-gray-900 border-b">开启积分兑换</th>
<th class="px-3 py-3 text-left text-xs font-medium text-gray-900 border-b">开启生日优惠券</th>
<th class="px-3 py-3 text-left text-xs font-medium text-gray-900 border-b">生日优惠券操作</th>
<th class="px-3 py-3 text-left text-xs font-medium text-gray-900 border-b">开启生日双倍积分</th>
</tr>
</thead>
<tbody class="bg-white divide-y divide-gray-200">
${mockEditLevels.map(level => `
<tr>
<td class="px-3 py-3 text-sm text-gray-900">${level.level}</td>
<td class="px-3 py-3">
<input type="text" value="${level.name}" class="w-full px-2 py-1 border border-gray-300 rounded text-sm">
</td>
<td class="px-3 py-3 text-sm text-gray-900">
<div class="flex items-center gap-1">
<input type="number" value="${level.growthMin}" class="w-16 px-2 py-1 border border-gray-300 rounded text-sm">
<span>-</span>
<input type="number" value="${level.growthMax}" class="w-16 px-2 py-1 border border-gray-300 rounded text-sm">
</div>
</td>
<td class="px-3 py-3">
<label class="relative inline-flex items-center cursor-pointer">
<input type="checkbox" class="sr-only peer" ${level.memberDiscount ? 'checked' : ''}>
<div class="w-9 h-5 bg-gray-200 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-green-300 rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-4 after:w-4 after:transition-all peer-checked:bg-green-600"></div>
</label>
</td>
<td class="px-3 py-3">
<input type="number" value="${level.discountRate}" class="w-16 px-2 py-1 border border-gray-300 rounded text-sm">
</td>
<td class="px-3 py-3">
<label class="relative inline-flex items-center cursor-pointer">
<input type="checkbox" class="sr-only peer" ${level.pointExchange ? 'checked' : ''}>
<div class="w-9 h-5 bg-gray-200 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-green-300 rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-4 after:w-4 after:transition-all peer-checked:bg-green-600"></div>
</label>
</td>
<td class="px-3 py-3">
<label class="relative inline-flex items-center cursor-pointer">
<input type="checkbox" class="sr-only peer" ${level.birthdayCoupon ? 'checked' : ''}>
<div class="w-9 h-5 bg-gray-200 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-green-300 rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-4 after:w-4 after:transition-all peer-checked:bg-green-600"></div>
</label>
</td>
<td class="px-3 py-3">
<button class="px-3 py-1 bg-blue-600 text-white text-xs rounded hover:bg-blue-700 transition-colors" onclick="openCouponModal('${level.level}')">
添加优惠券
</button>
</td>
<td class="px-3 py-3">
<label class="relative inline-flex items-center cursor-pointer">
<input type="checkbox" class="sr-only peer" ${level.birthdayDoublePoints ? 'checked' : ''}>
<div class="w-9 h-5 bg-gray-200 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-green-300 rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-4 after:w-4 after:transition-all peer-checked:bg-green-600"></div>
</label>
</td>
</tr>
`).join('')}
</tbody>
</table>
</div>
</div>
`;
memberTabManager.createTab(tabId, '批量会员等级编辑', content, true);
// 初始化批量选择的摊位显示(默认全选)
setTimeout(() => {
batchSelectedStalls = [...stallOptions];
updateBatchSelectedStallsDisplay();
}, 100);
}
// 批量编辑相关的全局变量
let batchSelectedStalls = [];
let batchDropdownOpen = false;
// 批量摊位选择相关功能
function toggleBatchStallDropdown() {
const dropdown = document.getElementById('batch-stall-dropdown-list');
const button = document.getElementById('batch-stall-dropdown-btn');
if (dropdown && button) {
batchDropdownOpen = !batchDropdownOpen;
if (batchDropdownOpen) {
dropdown.classList.remove('hidden');
setTimeout(() => {
document.addEventListener('click', closeBatchDropdownOnClickOutside);
}, 0);
} else {
dropdown.classList.add('hidden');
document.removeEventListener('click', closeBatchDropdownOnClickOutside);
}
}
}
function closeBatchDropdownOnClickOutside(event) {
const dropdown = document.getElementById('batch-stall-dropdown-list');
const button = document.getElementById('batch-stall-dropdown-btn');
if (dropdown && button && !dropdown.contains(event.target) && !button.contains(event.target)) {
dropdown.classList.add('hidden');
batchDropdownOpen = false;
document.removeEventListener('click', closeBatchDropdownOnClickOutside);
}
}
function updateBatchSelectedStalls() {
const checkboxes = document.querySelectorAll('#batch-stall-dropdown-list input[type="checkbox"]');
batchSelectedStalls = [];
checkboxes.forEach(checkbox => {
if (checkbox.checked) {
batchSelectedStalls.push(checkbox.value);
}
});
updateBatchDropdownText();
updateBatchSelectedStallsDisplay();
}
function updateBatchDropdownText() {
const dropdownText = document.getElementById('batch-dropdown-text');
if (dropdownText) {
if (batchSelectedStalls.length === 0) {
dropdownText.textContent = '请选择摊位...';
} else if (batchSelectedStalls.length === 8) {
dropdownText.textContent = '全部选择';
} else if (batchSelectedStalls.length === 1) {
dropdownText.textContent = batchSelectedStalls[0];
} else {
dropdownText.textContent = `已选择 ${batchSelectedStalls.length} 个摊位`;
}
}
}
function updateBatchSelectedStallsDisplay() {
const selectedDiv = document.getElementById('batch-selected-stalls');
if (!selectedDiv) return;
selectedDiv.innerHTML = '';
batchSelectedStalls.forEach(stall => {
const tag = document.createElement('span');
tag.className = 'inline-flex items-center px-2 py-1 rounded-md text-xs font-medium bg-blue-100 text-blue-800';
tag.innerHTML = `
${stall}
<button type="button" class="ml-1 inline-flex items-center justify-center w-4 h-4 rounded-full hover:bg-blue-200" onclick="removeBatchSelectedStall('${stall}')">
<svg class="w-2 h-2" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd"></path>
</svg>
</button>
`;
selectedDiv.appendChild(tag);
});
}
function removeBatchSelectedStall(stallName) {
batchSelectedStalls = batchSelectedStalls.filter(stall => stall !== stallName);
// 更新checkbox状态
const checkboxes = document.querySelectorAll('#batch-stall-dropdown-list input[type="checkbox"]');
checkboxes.forEach(checkbox => {
if (checkbox.value === stallName) {
checkbox.checked = false;
}
});
updateBatchDropdownText();
updateBatchSelectedStallsDisplay();
}
function submitBatchLevelEdit() {
if (batchSelectedStalls.length === 0) {
showNotification('请至少选择一个摊位', 'error');
return;
}
const selectedStallsText = batchSelectedStalls.length === 8 ? '全部摊位' : `${batchSelectedStalls.length}个摊位`;
showNotification(`批量等级编辑已提交,将应用到${selectedStallsText}`, 'success');
}
// 打开会员列表Tab
function openMemberListTab() {
const tabId = 'member-list';
// 生成模拟会员数据
const mockMembers = [
{
id: 'M001',
avatar: 'https://ui-avatars.com/api/?name=张三&background=random',
nickname: '张三',
phone: '138****1234',
stall: '时尚服装店',
level: 'LV2',
birthday: '1990-05-15',
registerTime: '2024-01-15 14:30:25'
},
{
id: 'M002',
avatar: 'https://ui-avatars.com/api/?name=李四&background=random',
nickname: '李四',
phone: '139****5678',
stall: '数码电子城',
level: 'LV1',
birthday: '1985-08-20',
registerTime: '2024-02-20 09:15:30'
},
{
id: 'M003',
avatar: 'https://ui-avatars.com/api/?name=王五&background=random',
nickname: '王五',
phone: '136****9012',
stall: '美食餐厅',
level: 'LV3',
birthday: '1992-12-08',
registerTime: '2024-03-10 16:45:12'
},
{
id: 'M004',
avatar: 'https://ui-avatars.com/api/?name=赵六&background=random',
nickname: '赵六',
phone: '137****3456',
stall: '家居生活馆',
level: 'LV1',
birthday: '1988-03-25',
registerTime: '2024-04-05 11:20:45'
},
{
id: 'M005',
avatar: 'https://ui-avatars.com/api/?name=孙七&background=random',
nickname: '孙七',
phone: '135****7890',
stall: '运动健身店',
level: 'LV4',
birthday: '1995-07-12',
registerTime: '2024-05-22 13:55:18'
},
{
id: 'M006',
avatar: 'https://ui-avatars.com/api/?name=周八&background=random',
nickname: '周八',
phone: '134****2468',
stall: '时尚服装店',
level: 'LV2',
birthday: '1991-11-30',
registerTime: '2024-06-18 08:35:22'
}
];
const content = `
<div class="p-6">
<div class="flex items-center justify-between mb-6">
<h2 class="text-lg font-medium text-gray-900">会员列表</h2>
</div>
<!-- 筛选头部 -->
<div class="mb-6 bg-gray-50 p-4 rounded-lg">
<div class="grid grid-cols-1 md:grid-cols-4 gap-4 mb-4">
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">店铺</label>
<select id="member-stall-filter" class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-green-500 focus:border-green-500">
<option value="">全部店铺</option>
<option value="时尚服装店">时尚服装店</option>
<option value="数码电子城">数码电子城</option>
<option value="美食餐厅">美食餐厅</option>
<option value="家居生活馆">家居生活馆</option>
<option value="运动健身店">运动健身店</option>
</select>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">用户昵称</label>
<input type="text" id="member-nickname-filter" placeholder="请输入用户昵称" class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-green-500 focus:border-green-500">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">会员等级</label>
<select id="member-level-filter" class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-green-500 focus:border-green-500">
<option value="">全部等级</option>
<option value="LV1">LV1</option>
<option value="LV2">LV2</option>
<option value="LV3">LV3</option>
<option value="LV4">LV4</option>
</select>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">注册时间</label>
<input type="date" id="member-register-filter" class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-green-500 focus:border-green-500">
</div>
</div>
<div class="flex justify-end">
<button onclick="filterMemberList()" class="px-4 py-2 bg-green-600 text-white rounded-md hover:bg-green-700 transition-colors">
筛选查询
</button>
<button onclick="resetMemberFilters()" class="ml-2 px-4 py-2 bg-gray-500 text-white rounded-md hover:bg-gray-600 transition-colors">
重置
</button>
</div>
</div>
<!-- 信息统计区域 -->
<div class="mb-6 grid grid-cols-1 md:grid-cols-2 gap-4">
<div class="bg-blue-50 p-4 rounded-lg border border-blue-200">
<div class="flex items-center">
<div class="flex-shrink-0">
<svg class="h-8 w-8 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2" />
<circle cx="9" cy="7" r="4" />
<path d="m22 21-3-3m0 0a8 8 0 1 1-11.31-11.31 8 8 0 0 1 11.31 11.31z" />
</svg>
</div>
<div class="ml-4">
<p class="text-sm font-medium text-blue-600">会员总数</p>
<p class="text-2xl font-bold text-blue-900" id="total-members">1,247</p>
</div>
</div>
</div>
<div class="bg-green-50 p-4 rounded-lg border border-green-200">
<div class="flex items-center">
<div class="flex-shrink-0">
<svg class="h-8 w-8 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M18 9v3m0 0v3m0-3h3m-3 0h-3m-2-5a4 4 0 11-8 0 4 4 0 018 0zM3 20a6 6 0 0112 0v1H3v-1z" />
</svg>
</div>
<div class="ml-4">
<p class="text-sm font-medium text-green-600">今日新增</p>
<p class="text-2xl font-bold text-green-900" id="today-new-members">23</p>
</div>
</div>
</div>
</div>
<!-- 会员数据表格 -->
<div class="bg-white rounded-lg shadow-sm border border-gray-200 overflow-hidden">
<div class="overflow-x-auto">
<table class="w-full" id="member-table">
<thead class="bg-gray-50">
<tr>
<th class="px-4 py-3 text-left">
<input type="checkbox" id="select-all-members" onchange="toggleAllMembers(this)" class="rounded border-gray-300 text-green-600 shadow-sm focus:border-green-500 focus:ring-green-500">
</th>
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider cursor-pointer hover:bg-gray-100" onclick="sortMemberTable('stall')">
所属摊位
<svg class="inline-block ml-1 h-3 w-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 9l4-4 4 4m0 6l-4 4-4-4" />
</svg>
</th>
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider cursor-pointer hover:bg-gray-100" onclick="sortMemberTable('id')">
用户ID
<svg class="inline-block ml-1 h-3 w-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 9l4-4 4 4m0 6l-4 4-4-4" />
</svg>
</th>
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
用户头像
</th>
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider cursor-pointer hover:bg-gray-100" onclick="sortMemberTable('nickname')">
用户昵称
<svg class="inline-block ml-1 h-3 w-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 9l4-4 4 4m0 6l-4 4-4-4" />
</svg>
</th>
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
手机号
</th>
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider cursor-pointer hover:bg-gray-100" onclick="sortMemberTable('level')">
会员等级
<svg class="inline-block ml-1 h-3 w-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 9l4-4 4 4m0 6l-4 4-4-4" />
</svg>
</th>
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
生日
</th>
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider cursor-pointer hover:bg-gray-100" onclick="sortMemberTable('registerTime')">
注册时间
<svg class="inline-block ml-1 h-3 w-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 9l4-4 4 4m0 6l-4 4-4-4" />
</svg>
</th>
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
操作
</th>
</tr>
</thead>
<tbody class="bg-white divide-y divide-gray-200" id="member-table-body">
${mockMembers.map(member => `
<tr class="hover:bg-gray-50 member-row" data-stall="${member.stall}" data-nickname="${member.nickname}" data-level="${member.level}" data-register-date="${member.registerTime.split(' ')[0]}">
<td class="px-4 py-3">
<input type="checkbox" value="${member.id}" class="member-checkbox rounded border-gray-300 text-green-600 shadow-sm focus:border-green-500 focus:ring-green-500">
</td>
<td class="px-4 py-3 text-sm text-gray-900">${member.stall}</td>
<td class="px-4 py-3 text-sm font-mono text-gray-600">${member.id}</td>
<td class="px-4 py-3">
<img src="${member.avatar}" alt="${member.nickname}" class="h-8 w-8 rounded-full">
</td>
<td class="px-4 py-3 text-sm font-medium text-gray-900">${member.nickname}</td>
<td class="px-4 py-3 text-sm text-gray-600">${member.phone}</td>
<td class="px-4 py-3">
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ${getLevelBadgeColor(member.level)}">
${member.level}
</span>
</td>
<td class="px-4 py-3 text-sm text-gray-600">${member.birthday}</td>
<td class="px-4 py-3 text-sm text-gray-600">${member.registerTime}</td>
<td class="px-4 py-3">
<button onclick="viewMemberDetail('${member.id}')" class="text-green-600 hover:text-green-900 text-sm font-medium">
详情
</button>
</td>
</tr>
`).join('')}
</tbody>
</table>
</div>
<!-- 分页区域 -->
<div class="bg-white px-4 py-3 border-t border-gray-200 sm:px-6">
<div class="flex items-center justify-between">
<div class="flex items-center text-sm text-gray-700">
<span>显示</span>
<select id="page-size" onchange="changePageSize(this.value)" class="mx-2 px-3 py-1 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-green-500">
<option value="10">10</option>
<option value="20" selected>20</option>
<option value="50">50</option>
<option value="100">100</option>
</select>
<span>条记录 <span id="total-records">1,247</span> </span>
</div>
<div class="flex items-center space-x-2">
<button onclick="goToPage(1)" class="px-3 py-1 text-sm bg-gray-100 text-gray-600 rounded-md hover:bg-gray-200 transition-colors" id="first-page">
首页
</button>
<button onclick="goToPage(currentPage - 1)" class="px-3 py-1 text-sm bg-gray-100 text-gray-600 rounded-md hover:bg-gray-200 transition-colors" id="prev-page">
上一页
</button>
<span class="text-sm text-gray-700">
<input type="number" id="current-page-input" value="1" min="1" max="63" class="w-12 px-2 py-1 text-center border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-green-500" onchange="goToPage(this.value)"> <span id="total-pages">63</span>
</span>
<button onclick="goToPage(currentPage + 1)" class="px-3 py-1 text-sm bg-gray-100 text-gray-600 rounded-md hover:bg-gray-200 transition-colors" id="next-page">
下一页
</button>
<button onclick="goToPage(totalPages)" class="px-3 py-1 text-sm bg-gray-100 text-gray-600 rounded-md hover:bg-gray-200 transition-colors" id="last-page">
尾页
</button>
</div>
</div>
</div>
</div>
</div>
`;
memberTabManager.createTab(tabId, '会员列表', content, false);
// 初始化分页变量
setTimeout(() => {
initializeMemberListPagination();
}, 100);
}
// 获取等级徽章颜色
function getLevelBadgeColor(level) {
const colors = {
'LV1': 'bg-gray-100 text-gray-800',
'LV2': 'bg-blue-100 text-blue-800',
'LV3': 'bg-green-100 text-green-800',
'LV4': 'bg-purple-100 text-purple-800'
};
return colors[level] || 'bg-gray-100 text-gray-800';
}
// 分页相关变量
let currentPage = 1;
let pageSize = 20;
let totalPages = 63;
let totalRecords = 1247;
// 初始化分页
function initializeMemberListPagination() {
updatePaginationDisplay();
}
// 更新分页显示
function updatePaginationDisplay() {
document.getElementById('current-page-input').value = currentPage;
document.getElementById('total-pages').textContent = totalPages;
document.getElementById('total-records').textContent = totalRecords.toLocaleString();
// 更新按钮状态
const firstBtn = document.getElementById('first-page');
const prevBtn = document.getElementById('prev-page');
const nextBtn = document.getElementById('next-page');
const lastBtn = document.getElementById('last-page');
if (firstBtn) firstBtn.disabled = currentPage === 1;
if (prevBtn) prevBtn.disabled = currentPage === 1;
if (nextBtn) nextBtn.disabled = currentPage === totalPages;
if (lastBtn) lastBtn.disabled = currentPage === totalPages;
}
// 跳转到指定页面
function goToPage(page) {
const pageNum = parseInt(page);
if (pageNum >= 1 && pageNum <= totalPages) {
currentPage = pageNum;
updatePaginationDisplay();
showNotification(`已跳转到第 ${currentPage}`, 'info');
}
}
// 改变每页显示数量
function changePageSize(size) {
pageSize = parseInt(size);
totalPages = Math.ceil(totalRecords / pageSize);
currentPage = 1;
updatePaginationDisplay();
showNotification(`每页显示 ${pageSize} 条记录`, 'info');
}
// 筛选会员列表
function filterMemberList() {
const stallFilter = document.getElementById('member-stall-filter').value;
const nicknameFilter = document.getElementById('member-nickname-filter').value.toLowerCase();
const levelFilter = document.getElementById('member-level-filter').value;
const registerFilter = document.getElementById('member-register-filter').value;
const rows = document.querySelectorAll('.member-row');
let visibleCount = 0;
rows.forEach(row => {
const stall = row.dataset.stall;
const nickname = row.dataset.nickname.toLowerCase();
const level = row.dataset.level;
const registerDate = row.dataset.registerDate;
let shouldShow = true;
if (stallFilter && stall !== stallFilter) shouldShow = false;
if (nicknameFilter && !nickname.includes(nicknameFilter)) shouldShow = false;
if (levelFilter && level !== levelFilter) shouldShow = false;
if (registerFilter && registerDate !== registerFilter) shouldShow = false;
if (shouldShow) {
row.style.display = '';
visibleCount++;
} else {
row.style.display = 'none';
}
});
showNotification(`筛选完成,显示 ${visibleCount} 条记录`, 'success');
}
// 重置筛选条件
function resetMemberFilters() {
document.getElementById('member-stall-filter').value = '';
document.getElementById('member-nickname-filter').value = '';
document.getElementById('member-level-filter').value = '';
document.getElementById('member-register-filter').value = '';
// 显示所有行
const rows = document.querySelectorAll('.member-row');
rows.forEach(row => {
row.style.display = '';
});
showNotification('筛选条件已重置', 'info');
}
// 全选/取消全选
function toggleAllMembers(checkbox) {
const memberCheckboxes = document.querySelectorAll('.member-checkbox');
memberCheckboxes.forEach(cb => {
cb.checked = checkbox.checked;
});
const selectedCount = checkbox.checked ? memberCheckboxes.length : 0;
showNotification(`${checkbox.checked ? '选中' : '取消选中'} ${selectedCount} 个会员`, 'info');
}
// 表格排序
let sortDirection = {};
function sortMemberTable(column) {
const direction = sortDirection[column] === 'asc' ? 'desc' : 'asc';
sortDirection[column] = direction;
showNotification(`${column}${direction === 'asc' ? '升序' : '降序'}排序`, 'info');
}
// 查看会员详情
function viewMemberDetail(memberId) {
// 获取会员模拟数据
const memberData = getMemberDataById(memberId);
if (!memberData) {
showNotification('会员信息不存在', 'error');
return;
}
openMemberDetailTab(memberData);
}
// 获取会员数据(模拟数据)
function getMemberDataById(memberId) {
const mockMemberDetails = {
'M001': {
id: 'M001',
name: '张三',
phone: '138****1234',
registerDate: '2024-01-15',
birthday: '1990-05-15',
growth: 1250,
points: 890,
coupons: 5
},
'M002': {
id: 'M002',
name: '李四',
phone: '139****5678',
registerDate: '2024-02-20',
birthday: '1985-08-20',
growth: 680,
points: 450,
coupons: 3
},
'M003': {
id: 'M003',
name: '王五',
phone: '136****9012',
registerDate: '2024-03-10',
birthday: '1992-12-08',
growth: 2100,
points: 1200,
coupons: 8
},
'M004': {
id: 'M004',
name: '赵六',
phone: '137****3456',
registerDate: '2024-04-05',
birthday: '1988-03-25',
growth: 350,
points: 280,
coupons: 2
},
'M005': {
id: 'M005',
name: '孙七',
phone: '135****7890',
registerDate: '2024-05-22',
birthday: '1995-07-12',
growth: 3200,
points: 1650,
coupons: 12
},
'M006': {
id: 'M006',
name: '周八',
phone: '134****2468',
registerDate: '2024-06-18',
birthday: '1991-11-30',
growth: 920,
points: 560,
coupons: 4
}
};
return mockMemberDetails[memberId];
}
// 打开会员详情Tab
function openMemberDetailTab(memberData) {
const tabId = `member-detail-${memberData.id}`;
const tabTitle = `会员详情-${memberData.name}`;
// 生成优惠券数据
const couponData = generateMemberCoupons(memberData.coupons);
// 生成成长值明细数据
const growthDetailData = generateGrowthDetails(memberData.growth);
// 生成积分明细数据
const pointDetailData = generatePointDetails(memberData.points);
const content = `
<div class="p-6 max-w-7xl mx-auto">
<div class="flex items-center justify-between mb-6">
<h2 class="text-xl font-semibold text-gray-900">${tabTitle}</h2>
<button onclick="memberTabManager.closeTab('${tabId}')" class="px-4 py-2 bg-gray-500 text-white rounded-md hover:bg-gray-600 transition-colors">
关闭
</button>
</div>
<!-- 会员基本信息 -->
<div class="bg-white rounded-lg shadow-sm border border-gray-200 p-6 mb-6">
<h3 class="text-lg font-medium text-gray-900 mb-4">会员信息</h3>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
<div class="flex flex-col">
<span class="text-sm font-medium text-gray-500 mb-1">会员名称</span>
<span class="text-base text-gray-900">${memberData.name}</span>
</div>
<div class="flex flex-col">
<span class="text-sm font-medium text-gray-500 mb-1">会员手机号</span>
<span class="text-base text-gray-900">${memberData.phone}</span>
</div>
<div class="flex flex-col">
<span class="text-sm font-medium text-gray-500 mb-1">会员注册日期</span>
<span class="text-base text-gray-900">${memberData.registerDate}</span>
</div>
<div class="flex flex-col">
<span class="text-sm font-medium text-gray-500 mb-1">会员生日</span>
<span class="text-base text-gray-900">${memberData.birthday}</span>
</div>
</div>
</div>
<!-- 会员资产 -->
<div class="bg-white rounded-lg shadow-sm border border-gray-200 p-6 mb-6">
<h3 class="text-lg font-medium text-gray-900 mb-4">会员资产</h3>
<div class="grid grid-cols-1 md:grid-cols-3 gap-6">
<div class="bg-blue-50 p-4 rounded-lg">
<div class="flex items-center">
<div class="flex-shrink-0">
<svg class="h-8 w-8 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 7h8m0 0v8m0-8l-8 8-4-4-6 6" />
</svg>
</div>
<div class="ml-4">
<p class="text-sm font-medium text-blue-600">会员成长值</p>
<p class="text-2xl font-bold text-blue-900">${memberData.growth}</p>
</div>
</div>
</div>
<div class="bg-green-50 p-4 rounded-lg">
<div class="flex items-center">
<div class="flex-shrink-0">
<svg class="h-8 w-8 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8c-1.657 0-3 .895-3 2s1.343 2 3 2 3 .895 3 2-1.343 2-3 2m0-8c1.11 0 2.08.402 2.599 1M12 8V7m0 1v8m0 0v1m0-1c-1.11 0-2.08-.402-2.599-1" />
</svg>
</div>
<div class="ml-4">
<p class="text-sm font-medium text-green-600">会员积分</p>
<p class="text-2xl font-bold text-green-900">${memberData.points}</p>
</div>
</div>
</div>
<div class="bg-purple-50 p-4 rounded-lg">
<div class="flex items-center">
<div class="flex-shrink-0">
<svg class="h-8 w-8 text-purple-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 7h.01M7 3h5c.512 0 1.024.195 1.414.586l7 7a2 2 0 010 2.828l-7 7a2 2 0 01-2.828 0l-7-7A1.994 1.994 0 013 12V7a4 4 0 014-4z" />
</svg>
</div>
<div class="ml-4">
<p class="text-sm font-medium text-purple-600">会员优惠券</p>
<p class="text-2xl font-bold text-purple-900 cursor-pointer hover:text-purple-700" onclick="showMemberCouponsModal('${memberData.id}')">${memberData.coupons}</p>
</div>
</div>
</div>
</div>
</div>
<!-- Tab导航 -->
<div class="bg-white rounded-lg shadow-sm border border-gray-200">
<div class="border-b border-gray-200">
<nav class="-mb-px flex">
<button onclick="switchDetailTab('growth', '${memberData.id}')" id="growth-tab-${memberData.id}" class="detail-tab-btn active py-4 px-6 text-sm font-medium border-b-2 border-green-500 text-green-600">
会员成长值明细
</button>
<button onclick="switchDetailTab('points', '${memberData.id}')" id="points-tab-${memberData.id}" class="detail-tab-btn py-4 px-6 text-sm font-medium border-b-2 border-transparent text-gray-500 hover:text-gray-700">
会员积分明细
</button>
</nav>
</div>
<!-- Tab内容区域 -->
<div class="p-6">
<!-- 成长值明细表格 -->
<div id="growth-content-${memberData.id}" class="detail-tab-content">
<div class="overflow-x-auto">
<table class="w-full">
<thead class="bg-gray-50">
<tr>
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">变化时间</th>
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">成长值增加</th>
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">关联订单</th>
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">详情</th>
</tr>
</thead>
<tbody class="bg-white divide-y divide-gray-200" id="growth-table-body-${memberData.id}">
${growthDetailData.map(item => `
<tr>
<td class="px-4 py-3 text-sm text-gray-900">${item.time}</td>
<td class="px-4 py-3 text-sm text-gray-900">+${item.growth}</td>
<td class="px-4 py-3 text-sm text-gray-600">${item.orderId || '无'}</td>
<td class="px-4 py-3">
<button onclick="showGrowthDetail('${item.id}', '${item.type}', ${JSON.stringify(item).replace(/"/g, '&quot;')})" class="text-blue-600 hover:text-blue-900 text-sm font-medium">
详情
</button>
</td>
</tr>
`).join('')}
</tbody>
</table>
</div>
<!-- 分页控件 -->
<div class="mt-4 flex items-center justify-between">
<div class="text-sm text-gray-700">
<span class="font-medium">${growthDetailData.length}</span>
</div>
<div class="flex items-center space-x-2">
<button onclick="growthPrevPage('${memberData.id}')" class="px-3 py-1 text-sm bg-gray-100 text-gray-600 rounded-md hover:bg-gray-200 transition-colors">
上一页
</button>
<span class="text-sm text-gray-700"> 1 1 </span>
<button onclick="growthNextPage('${memberData.id}')" class="px-3 py-1 text-sm bg-gray-100 text-gray-600 rounded-md hover:bg-gray-200 transition-colors">
下一页
</button>
</div>
</div>
</div>
<!-- 积分明细表格 -->
<div id="points-content-${memberData.id}" class="detail-tab-content hidden">
<div class="overflow-x-auto">
<table class="w-full">
<thead class="bg-gray-50">
<tr>
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">变化时间</th>
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">积分变化</th>
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">关联订单</th>
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">详情</th>
</tr>
</thead>
<tbody class="bg-white divide-y divide-gray-200" id="points-table-body-${memberData.id}">
${pointDetailData.map(item => `
<tr>
<td class="px-4 py-3 text-sm text-gray-900">${item.time}</td>
<td class="px-4 py-3 text-sm ${item.change > 0 ? 'text-green-600' : 'text-red-600'}">${item.change > 0 ? '+' : ''}${item.change}</td>
<td class="px-4 py-3 text-sm text-gray-600">${item.orderId || '无'}</td>
<td class="px-4 py-3">
<button onclick="showPointDetail('${item.id}', '${item.type}', ${JSON.stringify(item).replace(/"/g, '&quot;')})" class="text-blue-600 hover:text-blue-900 text-sm font-medium">
详情
</button>
</td>
</tr>
`).join('')}
</tbody>
</table>
</div>
<!-- 分页控件 -->
<div class="mt-4 flex items-center justify-between">
<div class="text-sm text-gray-700">
<span class="font-medium">${pointDetailData.length}</span>
</div>
<div class="flex items-center space-x-2">
<button onclick="pointsPrevPage('${memberData.id}')" class="px-3 py-1 text-sm bg-gray-100 text-gray-600 rounded-md hover:bg-gray-200 transition-colors">
上一页
</button>
<span class="text-sm text-gray-700"> 1 1 </span>
<button onclick="pointsNextPage('${memberData.id}')" class="px-3 py-1 text-sm bg-gray-100 text-gray-600 rounded-md hover:bg-gray-200 transition-colors">
下一页
</button>
</div>
</div>
</div>
</div>
</div>
</div>
`;
memberTabManager.createTab(tabId, tabTitle, content, true);
}
// 生成会员优惠券数据
function generateMemberCoupons(count) {
const couponTypes = ['满减券', '折扣券', '生日券', '新人券', '节日券'];
const coupons = [];
for (let i = 0; i < count; i++) {
const type = couponTypes[Math.floor(Math.random() * couponTypes.length)];
const issueDate = new Date(2024, Math.floor(Math.random() * 12), Math.floor(Math.random() * 28) + 1);
const validityDays = Math.floor(Math.random() * 90) + 30;
const expiryDate = new Date(issueDate.getTime() + validityDays * 24 * 60 * 60 * 1000);
coupons.push({
id: `C${String(i + 1).padStart(3, '0')}`,
name: `${type}${i + 1}`,
issueDate: issueDate.toISOString().split('T')[0],
expiryDate: expiryDate.toISOString().split('T')[0],
content: getRandomCouponContent(type)
});
}
return coupons;
}
// 获取随机优惠券内容
function getRandomCouponContent(type) {
const contents = {
'满减券': ['满100减20', '满200减50', '满500减100'],
'折扣券': ['9折优惠', '8.5折优惠', '8折优惠'],
'生日券': ['生日专享8折', '生日满100减30'],
'新人券': ['新人专享9折', '新人满50减10'],
'节日券': ['节日满200减30', '节日8.8折优惠']
};
const typeContents = contents[type] || ['优惠券'];
return typeContents[Math.floor(Math.random() * typeContents.length)];
}
// 生成成长值明细数据
function generateGrowthDetails(totalGrowth) {
const details = [];
const reasons = [
{ type: 'register', reason: '注册奖励', growth: 50, hasOrder: false },
{ type: 'birthday', reason: '生日奖励', growth: 100, hasOrder: false },
{ type: 'order', reason: '订单消费', growth: null, hasOrder: true },
{ type: 'activity', reason: '活动奖励', growth: 20, hasOrder: false }
];
let currentGrowth = 0;
let id = 1;
while (currentGrowth < totalGrowth && details.length < 20) {
const reasonData = reasons[Math.floor(Math.random() * reasons.length)];
let growth = reasonData.growth;
if (reasonData.type === 'order') {
growth = Math.floor(Math.random() * 100) + 10;
}
if (currentGrowth + growth > totalGrowth) {
growth = totalGrowth - currentGrowth;
}
const days = Math.floor(Math.random() * 180);
const time = new Date(Date.now() - days * 24 * 60 * 60 * 1000);
details.push({
id: `G${String(id).padStart(3, '0')}`,
time: time.toISOString().split('T')[0] + ' ' + time.toTimeString().split(' ')[0].substring(0, 5),
growth: growth,
orderId: reasonData.hasOrder ? `ORD${String(Math.floor(Math.random() * 9999) + 1).padStart(4, '0')}` : null,
type: reasonData.type,
reason: reasonData.reason
});
currentGrowth += growth;
id++;
}
return details.sort((a, b) => new Date(b.time) - new Date(a.time));
}
// 生成积分明细数据
function generatePointDetails(totalPoints) {
const details = [];
const reasons = [
{ type: 'register', reason: '注册奖励', points: 100, hasOrder: false },
{ type: 'birthday', reason: '生日奖励', points: 200, hasOrder: false },
{ type: 'order_earn', reason: '订单获得积分', points: null, hasOrder: true },
{ type: 'order_use', reason: '积分抵扣', points: null, hasOrder: true, isNegative: true },
{ type: 'activity', reason: '活动奖励', points: 50, hasOrder: false }
];
let currentPoints = 0;
let id = 1;
while (details.length < 25) {
const reasonData = reasons[Math.floor(Math.random() * reasons.length)];
let points = reasonData.points;
if (reasonData.type === 'order_earn') {
points = Math.floor(Math.random() * 200) + 20;
} else if (reasonData.type === 'order_use') {
points = -(Math.floor(Math.random() * 150) + 10);
}
const days = Math.floor(Math.random() * 180);
const time = new Date(Date.now() - days * 24 * 60 * 60 * 1000);
details.push({
id: `P${String(id).padStart(3, '0')}`,
time: time.toISOString().split('T')[0] + ' ' + time.toTimeString().split(' ')[0].substring(0, 5),
change: points,
orderId: reasonData.hasOrder ? `ORD${String(Math.floor(Math.random() * 9999) + 1).padStart(4, '0')}` : null,
type: reasonData.type,
reason: reasonData.reason
});
currentPoints += points;
id++;
}
return details.sort((a, b) => new Date(b.time) - new Date(a.time));
}
// 切换详情Tab
function switchDetailTab(tabType, memberId) {
// 更新按钮状态
const allTabs = document.querySelectorAll(`#growth-tab-${memberId}, #points-tab-${memberId}`);
allTabs.forEach(tab => {
tab.classList.remove('active', 'border-green-500', 'text-green-600');
tab.classList.add('border-transparent', 'text-gray-500');
});
const activeTab = document.getElementById(`${tabType}-tab-${memberId}`);
if (activeTab) {
activeTab.classList.add('active', 'border-green-500', 'text-green-600');
activeTab.classList.remove('border-transparent', 'text-gray-500');
}
// 切换内容
const allContents = document.querySelectorAll(`#growth-content-${memberId}, #points-content-${memberId}`);
allContents.forEach(content => {
content.classList.add('hidden');
});
const activeContent = document.getElementById(`${tabType}-content-${memberId}`);
if (activeContent) {
activeContent.classList.remove('hidden');
}
}
// 显示优惠券弹窗
function showMemberCouponsModal(memberId) {
const memberData = getMemberDataById(memberId);
if (!memberData) return;
const coupons = generateMemberCoupons(memberData.coupons);
const modalHtml = `
<div id="member-coupons-modal" class="fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full z-50" onclick="closeMemberCouponsModal(event)">
<div class="relative top-20 mx-auto p-5 border w-11/12 max-w-4xl shadow-lg rounded-md bg-white">
<div class="flex items-center justify-between mb-4">
<h3 class="text-lg font-medium text-gray-900">${memberData.name} - 优惠券列表</h3>
<button onclick="closeMemberCouponsModal()" class="text-gray-400 hover:text-gray-600">
<svg class="h-6 w-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
<div class="overflow-x-auto">
<table class="w-full">
<thead class="bg-gray-50">
<tr>
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">优惠券名称</th>
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">发放日期</th>
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">有效期</th>
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">优惠券内容</th>
</tr>
</thead>
<tbody class="bg-white divide-y divide-gray-200">
${coupons.map(coupon => `
<tr>
<td class="px-4 py-3 text-sm font-medium text-gray-900">${coupon.name}</td>
<td class="px-4 py-3 text-sm text-gray-600">${coupon.issueDate}</td>
<td class="px-4 py-3 text-sm text-gray-600">${coupon.expiryDate}</td>
<td class="px-4 py-3 text-sm text-gray-900">${coupon.content}</td>
</tr>
`).join('')}
</tbody>
</table>
</div>
<div class="mt-4 flex justify-end">
<button onclick="closeMemberCouponsModal()" class="px-4 py-2 bg-gray-500 text-white rounded-md hover:bg-gray-600 transition-colors">
关闭
</button>
</div>
</div>
</div>
`;
// 添加到页面
const existingModal = document.getElementById('member-coupons-modal');
if (existingModal) {
existingModal.remove();
}
document.body.insertAdjacentHTML('beforeend', modalHtml);
}
// 关闭优惠券弹窗
function closeMemberCouponsModal(event) {
if (event && event.target !== event.currentTarget) {
return;
}
const modal = document.getElementById('member-coupons-modal');
if (modal) {
modal.remove();
}
}
// 显示成长值详情弹窗
function showGrowthDetail(detailId, type, detailData) {
const data = typeof detailData === 'string' ? JSON.parse(detailData) : detailData;
let modalContent = '';
if (data.orderId) {
// 有关联订单的情况
modalContent = `
<h3 class="text-lg font-medium text-gray-900 mb-4">订单详情</h3>
<div class="space-y-3">
<div class="flex justify-between">
<span class="text-sm font-medium text-gray-500">订单编号:</span>
<span class="text-sm text-gray-900">${data.orderId}</span>
</div>
<div class="flex justify-between">
<span class="text-sm font-medium text-gray-500">订单总金额:</span>
<span class="text-sm text-gray-900">¥${(Math.random() * 1000 + 100).toFixed(2)}</span>
</div>
<div class="flex justify-between">
<span class="text-sm font-medium text-gray-500">订单时间:</span>
<span class="text-sm text-gray-900">${data.time}</span>
</div>
<div class="flex justify-between">
<span class="text-sm font-medium text-gray-500">订单归属摊位:</span>
<span class="text-sm text-gray-900">${['时尚服装店', '数码电子城', '美食餐厅', '家居生活馆'][Math.floor(Math.random() * 4)]}</span>
</div>
<div class="flex justify-between">
<span class="text-sm font-medium text-gray-500">成长值变化:</span>
<span class="text-sm text-green-600">+${data.growth}</span>
</div>
</div>
`;
} else {
// 无关联订单的情况
modalContent = `
<h3 class="text-lg font-medium text-gray-900 mb-4">成长值变化详情</h3>
<div class="space-y-3">
<div class="flex justify-between">
<span class="text-sm font-medium text-gray-500">变化原因:</span>
<span class="text-sm text-gray-900">${data.reason}</span>
</div>
<div class="flex justify-between">
<span class="text-sm font-medium text-gray-500">变化时间:</span>
<span class="text-sm text-gray-900">${data.time}</span>
</div>
<div class="flex justify-between">
<span class="text-sm font-medium text-gray-500">成长值增加:</span>
<span class="text-sm text-green-600">+${data.growth}</span>
</div>
<div class="pt-2 text-sm text-gray-600">
${getGrowthReasonDescription(data.type)}
</div>
</div>
`;
}
showDetailModal('成长值详情', modalContent);
}
// 显示积分详情弹窗
function showPointDetail(detailId, type, detailData) {
const data = typeof detailData === 'string' ? JSON.parse(detailData) : detailData;
let modalContent = '';
if (data.orderId) {
// 有关联订单的情况
modalContent = `
<h3 class="text-lg font-medium text-gray-900 mb-4">订单详情</h3>
<div class="space-y-3">
<div class="flex justify-between">
<span class="text-sm font-medium text-gray-500">订单编号:</span>
<span class="text-sm text-gray-900">${data.orderId}</span>
</div>
<div class="flex justify-between">
<span class="text-sm font-medium text-gray-500">订单总金额:</span>
<span class="text-sm text-gray-900">¥${(Math.random() * 1000 + 100).toFixed(2)}</span>
</div>
<div class="flex justify-between">
<span class="text-sm font-medium text-gray-500">订单时间:</span>
<span class="text-sm text-gray-900">${data.time}</span>
</div>
<div class="flex justify-between">
<span class="text-sm font-medium text-gray-500">订单归属摊位:</span>
<span class="text-sm text-gray-900">${['时尚服装店', '数码电子城', '美食餐厅', '家居生活馆'][Math.floor(Math.random() * 4)]}</span>
</div>
<div class="flex justify-between">
<span class="text-sm font-medium text-gray-500">积分变化:</span>
<span class="text-sm ${data.change > 0 ? 'text-green-600' : 'text-red-600'}">${data.change > 0 ? '+' : ''}${data.change}</span>
</div>
<div class="pt-2 text-sm text-gray-600">
${data.change > 0 ? '订单消费获得积分奖励' : '使用积分抵扣订单金额'}
</div>
</div>
`;
} else {
// 无关联订单的情况
modalContent = `
<h3 class="text-lg font-medium text-gray-900 mb-4">积分变化详情</h3>
<div class="space-y-3">
<div class="flex justify-between">
<span class="text-sm font-medium text-gray-500">变化原因:</span>
<span class="text-sm text-gray-900">${data.reason}</span>
</div>
<div class="flex justify-between">
<span class="text-sm font-medium text-gray-500">变化时间:</span>
<span class="text-sm text-gray-900">${data.time}</span>
</div>
<div class="flex justify-between">
<span class="text-sm font-medium text-gray-500">积分变化:</span>
<span class="text-sm ${data.change > 0 ? 'text-green-600' : 'text-red-600'}">${data.change > 0 ? '+' : ''}${data.change}</span>
</div>
<div class="pt-2 text-sm text-gray-600">
${getPointReasonDescription(data.type)}
</div>
</div>
`;
}
showDetailModal('积分详情', modalContent);
}
// 显示通用详情弹窗
function showDetailModal(title, content) {
const modalHtml = `
<div id="detail-modal" class="fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full z-50" onclick="closeDetailModal(event)">
<div class="relative top-20 mx-auto p-5 border w-11/12 max-w-md shadow-lg rounded-md bg-white">
<div class="flex items-center justify-between mb-4">
<h3 class="text-lg font-medium text-gray-900">${title}</h3>
<button onclick="closeDetailModal()" class="text-gray-400 hover:text-gray-600">
<svg class="h-6 w-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
<div class="mb-4">
${content}
</div>
<div class="flex justify-end">
<button onclick="closeDetailModal()" class="px-4 py-2 bg-gray-500 text-white rounded-md hover:bg-gray-600 transition-colors">
关闭
</button>
</div>
</div>
</div>
`;
// 移除现有弹窗
const existingModal = document.getElementById('detail-modal');
if (existingModal) {
existingModal.remove();
}
document.body.insertAdjacentHTML('beforeend', modalHtml);
}
// 关闭详情弹窗
function closeDetailModal(event) {
if (event && event.target !== event.currentTarget) {
return;
}
const modal = document.getElementById('detail-modal');
if (modal) {
modal.remove();
}
}
// 获取成长值原因描述
function getGrowthReasonDescription(type) {
const descriptions = {
'register': '新用户注册时系统自动发放的奖励成长值',
'birthday': '会员生日当天系统自动发放的生日礼品成长值',
'activity': '参与平台活动获得的奖励成长值',
'order': '订单消费根据消费金额按比例获得的成长值'
};
return descriptions[type] || '其他原因获得的成长值';
}
// 获取积分原因描述
function getPointReasonDescription(type) {
const descriptions = {
'register': '新用户注册时系统自动发放的奖励积分',
'birthday': '会员生日当天系统自动发放的生日礼品积分',
'activity': '参与平台活动获得的奖励积分',
'order_earn': '订单消费根据消费金额按比例获得的积分奖励',
'order_use': '在订单中使用积分抵扣部分金额'
};
return descriptions[type] || '其他原因的积分变化';
}
// 分页功能(成长值)
function growthPrevPage(memberId) {
showNotification('已到第一页', 'info');
}
function growthNextPage(memberId) {
showNotification('已到最后一页', 'info');
}
// 分页功能(积分)
function pointsPrevPage(memberId) {
showNotification('已到第一页', 'info');
}
function pointsNextPage(memberId) {
showNotification('已到最后一页', 'info');
}
// 导出函数
window.memberTabManager = memberTabManager;
window.openLevelSettingsTab = openLevelSettingsTab;
window.openLevelDetailTab = openLevelDetailTab;
window.openLevelEditTab = openLevelEditTab;
window.openBatchLevelEditTab = openBatchLevelEditTab;
window.openCouponModal = openCouponModal;
window.testCouponModal = testCouponModal;
window.closeCouponModal = closeCouponModal;
window.resetCouponForm = resetCouponForm;
window.adjustThreshold = adjustThreshold;
window.adjustDiscount = adjustDiscount;
window.addCouponThreshold = addCouponThreshold;
window.submitCoupon = submitCoupon;
window.submitLevelDetail = submitLevelDetail;
window.submitLevelEdit = submitLevelEdit;
window.submitBatchLevelEdit = submitBatchLevelEdit;
window.toggleStallDropdown = toggleStallDropdown;
window.toggleBatchStallDropdown = toggleBatchStallDropdown;
window.updateBatchSelectedStalls = updateBatchSelectedStalls;
window.removeBatchSelectedStall = removeBatchSelectedStall;
window.removeSelectedStall = removeSelectedStall;
window.filterStallTable = filterStallTable;
window.openMemberListTab = openMemberListTab;
window.getLevelBadgeColor = getLevelBadgeColor;
window.initializeMemberListPagination = initializeMemberListPagination;
window.updatePaginationDisplay = updatePaginationDisplay;
window.goToPage = goToPage;
window.changePageSize = changePageSize;
window.filterMemberList = filterMemberList;
window.resetMemberFilters = resetMemberFilters;
window.toggleAllMembers = toggleAllMembers;
window.sortMemberTable = sortMemberTable;
window.viewMemberDetail = viewMemberDetail;
window.getMemberDataById = getMemberDataById;
window.openMemberDetailTab = openMemberDetailTab;
window.generateMemberCoupons = generateMemberCoupons;
window.generateGrowthDetails = generateGrowthDetails;
window.generatePointDetails = generatePointDetails;
window.switchDetailTab = switchDetailTab;
window.showMemberCouponsModal = showMemberCouponsModal;
window.closeMemberCouponsModal = closeMemberCouponsModal;
window.showGrowthDetail = showGrowthDetail;
window.showPointDetail = showPointDetail;
window.showDetailModal = showDetailModal;
window.closeDetailModal = closeDetailModal;
window.getGrowthReasonDescription = getGrowthReasonDescription;
window.getPointReasonDescription = getPointReasonDescription;
window.growthPrevPage = growthPrevPage;
window.growthNextPage = growthNextPage;
window.pointsPrevPage = pointsPrevPage;
window.pointsNextPage = pointsNextPage;