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

2159 lines
106 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 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;