2159 lines
106 KiB
JavaScript
2159 lines
106 KiB
JavaScript
|
|
// 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, '"')})" 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, '"')})" 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;
|