(function () { // Tab 切换 var adminTabsEl = document.getElementById('adminTabs'); if (adminTabsEl) adminTabsEl.innerHTML = ''; var activeAdminTabName = ''; var ADMIN_PANEL_MAP = { 'users': 'panel-users', 'columns': 'panel-columns', 'dedupe-total-data': 'panel-dedupe-total-data', 'shop-keys': 'panel-shop-keys', 'shop-manage': 'panel-shop-manage', 'skip-price-asin': 'panel-skip-price-asin', 'history': 'panel-history', 'version': 'panel-version' }; function runTabLoader(tabName) { if (tabName === 'users') { loadUsers(1); loadColumnsForPermission(); } else if (tabName === 'columns') loadColumns(); else if (tabName === 'dedupe-total-data') loadDedupeTotalData(1); else if (tabName === 'shop-keys') loadShopKeys(1); else if (tabName === 'shop-manage') loadShopManage(1); else if (tabName === 'skip-price-asin') loadSkipPriceAsin(1); else if (tabName === 'history') loadHistory(1); else if (tabName === 'version') loadVersions(); } function hideAllAdminPanels() { document.querySelectorAll('.tab-panel').forEach(function (panel) { panel.classList.remove('active'); panel.style.display = 'none'; }); } function getActiveAdminTabName() { return activeAdminTabName; } function activateAdminTab(tabName) { var panelId = ADMIN_PANEL_MAP[tabName]; var panel = panelId ? document.getElementById(panelId) : null; if (!panel) return; activeAdminTabName = tabName; document.querySelectorAll('#adminTabs .tab').forEach(function (tab) { tab.classList.toggle('active', tab.dataset.tab === tabName); }); hideAllAdminPanels(); panel.style.display = ''; panel.classList.add('active'); runTabLoader(tabName); } function renderAdminTabs(items) { var knownItems = (items || []).filter(function (item) { return !!ADMIN_PANEL_MAP[item.route_path]; }); if (!knownItems.length) { activeAdminTabName = ''; if (adminTabsEl) adminTabsEl.innerHTML = '
暂无可用菜单
'; hideAllAdminPanels(); return; } if (adminTabsEl) { adminTabsEl.innerHTML = knownItems.map(function (item) { return '
' + (item.name || item.route_path) + '
'; }).join(''); } document.querySelectorAll('#adminTabs .tab').forEach(function (tab) { tab.onclick = function () { activateAdminTab(tab.dataset.tab); }; }); var fallbackTab = activeAdminTabName && knownItems.some(function (item) { return item.route_path === activeAdminTabName; }) ? activeAdminTabName : knownItems[0].route_path; activateAdminTab(fallbackTab); } function loadAdminMenus(preferredTab) { if (preferredTab) activeAdminTabName = preferredTab; fetch('/api/admin/current-user/menus') .then(function (r) { return r.json(); }) .then(function (res) { if (!res.success) { if (adminTabsEl) adminTabsEl.innerHTML = '
菜单加载失败
'; hideAllAdminPanels(); return; } renderAdminTabs(res.items || []); }) .catch(function () { if (adminTabsEl) adminTabsEl.innerHTML = '
菜单加载失败
'; hideAllAdminPanels(); }); } hideAllAdminPanels(); // ========== 用户管理 ========== var userPage = 1, userPageSize = 15; var currentUserId = null; var currentUserRole = 'admin'; var currentUserUsername = ''; var adminsList = []; function roleLabel(role) { if (role === 'super_admin') return '超级管理员'; if (role === 'admin') return '管理员'; return '普通账号'; } function buildUserListQuery(page) { var q = 'page=' + (page || 1) + '&page_size=' + userPageSize; var kw = (document.getElementById('searchUsername').value || '').trim(); if (kw) q += '&username=' + encodeURIComponent(kw); var cby = document.getElementById('filterCreatedBy').value; if (cby) q += '&created_by_id=' + encodeURIComponent(cby); return q; } function loadUsers(page) { userPage = page || 1; fetch('/api/admin/users?' + buildUserListQuery(userPage)) .then(function (r) { return r.json(); }) .then(function (res) { var tbody = document.getElementById('userListBody'); if (!res.success) { tbody.innerHTML = '加载失败: ' + (res.error || '') + ''; return; } currentUserId = res.current_user_id || null; currentUserRole = res.current_user_role || 'admin'; currentUserUsername = (res.current_user_username || '').trim(); adminsList = res.admins || []; var items = res.items || []; if (items.length === 0) { tbody.innerHTML = '暂无用户'; } else { tbody.innerHTML = items.map(function (u) { return '' + u.id + '' + (u.username || '') + '' + roleLabel(u.role || 'normal') + '' + (u.creator_username || '-') + '' + (u.created_at || '') + '' + ' ' + '' + ''; }).join(''); } renderPagination('userPagination', res.total, res.page, res.page_size, loadUsers); bindUserActions(); updateCreateFormByRole(); updateUserFilterByRole(); }) .catch(function () { document.getElementById('userListBody').innerHTML = '请求失败'; }); } function updateUserFilterByRole() { var grp = document.getElementById('filterCreatedByGroup'); var sel = document.getElementById('filterCreatedBy'); if (currentUserRole === 'super_admin') { grp.style.display = 'block'; var cur = sel.value; sel.innerHTML = ''; adminsList.forEach(function (a) { var opt = document.createElement('option'); opt.value = a.id; opt.textContent = a.username; sel.appendChild(opt); }); sel.value = cur || ''; } else { grp.style.display = 'none'; } } function updateDedupeTotalDataAccess() { var tab = document.querySelector('.tab[data-tab="dedupe-total-data"]'); var panel = document.getElementById('panel-dedupe-total-data'); var canUse = currentUserRole === 'super_admin' || (currentUserRole === 'admin' && currentUserUsername === ''); if (tab) tab.style.display = canUse ? '' : 'none'; if (panel) panel.style.display = canUse ? '' : 'none'; if (!canUse && tab && tab.classList.contains('active')) { tab.classList.remove('active'); if (panel) panel.classList.remove('active'); var usersTab = document.querySelector('.tab[data-tab="users"]'); var usersPanel = document.getElementById('panel-users'); if (usersTab) usersTab.classList.add('active'); if (usersPanel) usersPanel.classList.add('active'); } } function updateShopManageAccess() { var tab = document.querySelector('.tab[data-tab="shop-manage"]'); var panel = document.getElementById('panel-shop-manage'); if (!tab || !panel) return; var canUse = currentUserRole === 'super_admin' || !!currentUserAdminPermissionKeys['admin_shop_manage'] || !!currentUserAdminPermissionRoutes['shop-manage']; tab.style.display = canUse ? '' : 'none'; panel.style.display = canUse ? '' : 'none'; if (!canUse && tab.classList.contains('active')) { tab.classList.remove('active'); panel.classList.remove('active'); var usersTab = document.querySelector('.tab[data-tab="users"]'); var usersPanel = document.getElementById('panel-users'); if (usersTab) usersTab.classList.add('active'); if (usersPanel) usersPanel.classList.add('active'); } } function loadCurrentUserAdminPermissions() { currentUserAdminPermissionKeys = {}; currentUserAdminPermissionRoutes = {}; if (!currentUserId || currentUserRole === 'super_admin') { updateShopManageAccess(); return; } fetch('/api/admin/user/' + currentUserId + '/column-permissions?menu_type=admin') .then(function (r) { return r.json(); }) .then(function (res) { if (!res.success) { updateShopManageAccess(); return; } (res.items || []).forEach(function (item) { var columnKey = (item.column_key || '').trim(); var routePath = (item.route_path || '').trim(); if (columnKey) currentUserAdminPermissionKeys[columnKey] = true; if (routePath) currentUserAdminPermissionRoutes[routePath] = true; }); updateShopManageAccess(); }) .catch(function () { updateShopManageAccess(); }); } function applyTabAccess(tabName, canUse) { var tab = document.querySelector('.tab[data-tab="' + tabName + '"]'); var panel = document.getElementById('panel-' + tabName); if (tab) tab.style.display = canUse ? '' : 'none'; if (panel) panel.style.display = canUse ? '' : 'none'; if (!canUse && tab && tab.classList.contains('active')) { tab.classList.remove('active'); if (panel) panel.classList.remove('active'); var usersTab = document.querySelector('.tab[data-tab="users"]'); var usersPanel = document.getElementById('panel-users'); if (usersTab) usersTab.classList.add('active'); if (usersPanel) usersPanel.classList.add('active'); } } function hasAdminTabAccess(tabName) { var config = ADMIN_TAB_ACCESS_CONFIG[tabName]; if (!config) return true; if (currentUserRole === 'super_admin') return true; if (config.superAdminOnly) return false; return !!currentUserAdminPermissionKeys[config.columnKey] || !!currentUserAdminPermissionRoutes[config.routePath]; } function refreshAdminTabAccess() { Object.keys(ADMIN_TAB_ACCESS_CONFIG).forEach(function (tabName) { applyTabAccess(tabName, hasAdminTabAccess(tabName)); }); } function loadCurrentUserAdminPermissions() { currentUserAdminPermissionKeys = {}; currentUserAdminPermissionRoutes = {}; if (!currentUserId || currentUserRole === 'super_admin') { refreshAdminTabAccess(); return; } fetch('/api/admin/user/' + currentUserId + '/column-permissions?menu_type=admin') .then(function (r) { return r.json(); }) .then(function (res) { if (!res.success) { refreshAdminTabAccess(); return; } (res.items || []).forEach(function (item) { var columnKey = (item.column_key || '').trim(); var routePath = (item.route_path || '').trim(); if (columnKey) currentUserAdminPermissionKeys[columnKey] = true; if (routePath) currentUserAdminPermissionRoutes[routePath] = true; }); refreshAdminTabAccess(); }) .catch(function () { refreshAdminTabAccess(); }); } var allColumnsList = []; function loadColumnsForPermission() { fetch('/api/admin/columns?menu_type=admin') .then(function (r) { return r.json(); }) .then(function (res) { if (!res.success) return; allColumnsList = res.items || []; renderColumnPermissionWrap('createColumnPermissionWrap'); renderColumnPermissionWrap('editColumnPermissionWrap'); var selCreate = document.getElementById('createColumnPermissionWrap'); var selEdit = document.getElementById('editColumnPermissionWrap'); if (selCreate && !selCreate._colCardsBound) { selCreate._colCardsBound = true; selCreate.onchange = function () { renderColumnCards('createColumnPermissionWrap'); }; } if (selEdit && !selEdit._colCardsBound) { selEdit._colCardsBound = true; selEdit.onchange = function () { renderColumnCards('editColumnPermissionWrap'); }; } }) .catch(function () { }); } var columnCardsContainerMap = { createColumnPermissionWrap: 'createColumnCards', editColumnPermissionWrap: 'editColumnCards' }; function renderColumnCards(selectId) { var sel = document.getElementById(selectId); var cardsId = columnCardsContainerMap[selectId]; var cardsEl = cardsId ? document.getElementById(cardsId) : null; if (!sel || sel.tagName !== 'SELECT' || !cardsEl) return; cardsEl.innerHTML = ''; for (var i = 0; i < sel.options.length; i++) { var opt = sel.options[i]; if (opt.disabled || !opt.selected) continue; var card = document.createElement('span'); card.className = 'column-permission-card'; card.textContent = opt.textContent; var btn = document.createElement('button'); btn.type = 'button'; btn.className = 'col-card-remove'; btn.setAttribute('aria-label', '移除'); btn.textContent = '×'; (function (option, seldId) { btn.onclick = function () { option.selected = false; renderColumnCards(seldId); }; })(opt, selectId); card.appendChild(btn); cardsEl.appendChild(card); } } function renderColumnPermissionWrap(wrapId) { var sel = document.getElementById(wrapId); if (!sel || sel.tagName !== 'SELECT') return; sel.innerHTML = ''; allColumnsList.forEach(function (c) { var opt = document.createElement('option'); opt.value = c.id; opt.textContent = c.name + ' (' + c.column_key + ')'; sel.appendChild(opt); }); if (allColumnsList.length === 0) { var opt = document.createElement('option'); opt.disabled = true; opt.textContent = '暂无菜单,请先在“栏目权限配置”中新增菜单'; sel.appendChild(opt); } renderColumnCards(wrapId); } function getSelectedColumnIds(wrapId) { var sel = document.getElementById(wrapId); if (!sel || sel.tagName !== 'SELECT') return []; var ids = []; for (var i = 0; i < sel.options.length; i++) { if (sel.options[i].selected) { var v = parseInt(sel.options[i].value, 10); if (!isNaN(v)) ids.push(v); } } return ids; } function setColumnPermissionCheckboxes(wrapId, columnIds) { var sel = document.getElementById(wrapId); if (!sel || sel.tagName !== 'SELECT') return; var set = {}; (columnIds || []).forEach(function (id) { set[id] = true; }); for (var i = 0; i < sel.options.length; i++) { var opt = sel.options[i]; if (opt.disabled) continue; opt.selected = set[parseInt(opt.value, 10)] || false; } renderColumnCards(wrapId); } function updateCreateFormByRole() { var roleSel = document.getElementById('createRole'); var optAdmin = document.getElementById('optAdmin'); var formCreatedBy = document.getElementById('formGroupCreatedBy'); var selCreatedBy = document.getElementById('createCreatedBy'); if (currentUserRole === 'super_admin') { if (optAdmin) optAdmin.style.display = ''; formCreatedBy.style.display = (roleSel.value === 'normal') ? 'block' : 'none'; selCreatedBy.innerHTML = ''; adminsList.forEach(function (a) { var opt = document.createElement('option'); opt.value = a.id; opt.textContent = a.username; selCreatedBy.appendChild(opt); }); } else { if (optAdmin) optAdmin.style.display = 'none'; roleSel.value = 'normal'; formCreatedBy.style.display = 'none'; } } function bindUserActions() { document.querySelectorAll('[data-edit]').forEach(function (btn) { btn.onclick = function () { var raw = (btn.getAttribute('data-user') || '{}').replace(/"/g, '"'); var u; try { u = JSON.parse(raw); } catch (e) { u = {}; } document.getElementById('editUserId').value = u.id || ''; document.getElementById('editUsername').value = u.username || ''; document.getElementById('editPassword').value = ''; var editRole = document.getElementById('editRole'); var editFormGroupRole = document.getElementById('editFormGroupRole'); var editFormGroupCreator = document.getElementById('editFormGroupCreator'); var editCreatorName = document.getElementById('editCreatorName'); editFormGroupRole.style.display = (currentUserRole === 'super_admin' && u.role !== 'super_admin') ? 'block' : 'none'; editFormGroupCreator.style.display = (u.role === 'normal' && u.creator_username) ? 'block' : 'none'; editCreatorName.value = u.creator_username || ''; if (u.role !== 'super_admin') { editRole.value = u.role || 'normal'; } document.getElementById('msgEdit').textContent = ''; if (allColumnsList.length) { renderColumnPermissionWrap('editColumnPermissionWrap'); fetch('/api/admin/user/' + (u.id) + '/columns') .then(function (r) { return r.json(); }) .then(function (res) { if (res.success && res.column_ids) setColumnPermissionCheckboxes('editColumnPermissionWrap', res.column_ids); }); } document.getElementById('editUserModal').classList.add('show'); }; }); document.querySelectorAll('[data-delete]').forEach(function (btn) { btn.onclick = function () { if (!confirm('确定删除用户 "' + (btn.dataset.name || '') + '" 吗?')) return; fetch('/api/admin/user/' + btn.dataset.delete, { method: 'DELETE' }) .then(function (r) { return r.json(); }) .then(function (res) { if (res.success) { loadUsers(userPage); } else { alert(res.error || '删除失败'); } }); }; }); } document.getElementById('btnSearchUsers').onclick = function () { loadUsers(1); }; document.getElementById('createRole').onchange = function () { updateCreateFormByRole(); }; document.getElementById('btnCreate').onclick = function () { var username = (document.getElementById('username').value || '').trim(); var password = document.getElementById('password').value || ''; var role = document.getElementById('createRole').value || 'normal'; var createdById = document.getElementById('createCreatedBy').value ? parseInt(document.getElementById('createCreatedBy').value, 10) : null; var msgEl = document.getElementById('msgCreate'); msgEl.textContent = ''; msgEl.className = 'msg'; if (!username || username.length < 2) { msgEl.textContent = '用户名至少 2 个字符'; msgEl.classList.add('err'); return; } if (!password || password.length < 6) { msgEl.textContent = '密码至少 6 个字符'; msgEl.classList.add('err'); return; } var body = { username: username, password: password, role: role }; if (role === 'normal' && currentUserRole === 'super_admin' && createdById) body.created_by_id = createdById; body.column_ids = getSelectedColumnIds('createColumnPermissionWrap'); fetch('/api/admin/user', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) }) .then(function (r) { return r.json(); }) .then(function (res) { if (res.success) { msgEl.textContent = res.msg || '创建成功'; msgEl.classList.add('ok'); document.getElementById('username').value = ''; document.getElementById('password').value = ''; loadUsers(1); } else { msgEl.textContent = res.error || '创建失败'; msgEl.classList.add('err'); } }) .catch(function () { msgEl.textContent = '请求失败'; msgEl.classList.add('err'); }); }; document.getElementById('btnSaveUser').onclick = function () { var uid = document.getElementById('editUserId').value; var password = document.getElementById('editPassword').value; var editRoleEl = document.getElementById('editRole'); var msgEl = document.getElementById('msgEdit'); msgEl.textContent = ''; msgEl.className = 'msg'; var body = {}; if (password) body.password = password; if (currentUserRole === 'super_admin' && editRoleEl && editRoleEl.offsetParent !== null) body.role = editRoleEl.value || 'normal'; body.column_ids = getSelectedColumnIds('editColumnPermissionWrap'); fetch('/api/admin/user/' + uid, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) }) .then(function (r) { return r.json(); }) .then(function (res) { if (res.success) { msgEl.textContent = res.msg || '保存成功'; msgEl.classList.add('ok'); document.getElementById('editUserModal').classList.remove('show'); loadUsers(userPage); if (String(uid || '') === String(currentUserId || '')) { loadAdminMenus(getActiveAdminTabName()); } } else { msgEl.textContent = res.error || '保存失败'; msgEl.classList.add('err'); } }); }; document.getElementById('btnCloseEdit').onclick = function () { document.getElementById('editUserModal').classList.remove('show'); }; // ========== 生成记录 ========== var historyPage = 1, historyPageSize = 15; function toSqlDatetime(val) { if (!val) return ''; return val.replace('T', ' '); } function buildHistoryQuery(page) { var q = 'page=' + (page || 1) + '&page_size=' + historyPageSize; var uid = document.getElementById('filterUser').value; var start = toSqlDatetime(document.getElementById('filterTimeStart').value); var end = toSqlDatetime(document.getElementById('filterTimeEnd').value); if (uid) q += '&user_id=' + uid; if (start) q += '&time_start=' + encodeURIComponent(start); if (end) q += '&time_end=' + encodeURIComponent(end); return q; } function loadHistory(page) { historyPage = page || 1; fetch('/api/admin/history?' + buildHistoryQuery(historyPage)) .then(function (r) { return r.json(); }) .then(function (res) { var tbody = document.getElementById('historyListBody'); if (!res.success) { tbody.innerHTML = '加载失败: ' + (res.error || '') + ''; return; } var items = res.items || []; if (items.length === 0) { tbody.innerHTML = '暂无记录'; } else { tbody.innerHTML = items.map(function (h) { var urls = (h.result_urls || []); var thumbUrls = (h.long_image_url ? [h.long_image_url] : []).concat(urls); var thumbs = thumbUrls.slice(0, 3).map(function (url) { return ''; }).join(''); return '' + h.id + '' + (h.username || '-') + '' + (h.panel_type || '-') + '' + (h.created_at || '') + '' + '
' + (thumbs || '-') + '
'; }).join(''); } renderPagination('historyPagination', res.total, res.page, res.page_size, loadHistory); }) .catch(function () { document.getElementById('historyListBody').innerHTML = '请求失败'; }); } function loadUserOptions() { fetch('/api/admin/users?page=1&page_size=999') .then(function (r) { return r.json(); }) .then(function (res) { var sel = document.getElementById('filterUser'); var cur = sel.value; sel.innerHTML = ''; (res.items || []).forEach(function (u) { var opt = document.createElement('option'); opt.value = u.id; opt.textContent = u.username + ' (' + roleLabel(u.role || 'normal') + ')'; sel.appendChild(opt); }); sel.value = cur || ''; }); } var shopManageGroupUsers = []; function refreshShopManageGroupUserSelect(selectedUserId) { var sel = document.getElementById('shopManageGroupUserSelect'); if (!sel) return; var opts = ['']; shopManageGroupUsers.forEach(function (u) { opts.push(''); }); sel.innerHTML = opts.join(''); if (selectedUserId != null && selectedUserId !== '') { sel.value = String(selectedUserId); } syncShopManageGroupNameWithUser(); } function syncShopManageGroupNameWithUser() { var sel = document.getElementById('shopManageGroupUserSelect'); var input = document.getElementById('shopManageGroupInput'); if (!sel || !input) return; var selectedUser = null; shopManageGroupUsers.some(function (u) { if (String(u.id) === String(sel.value || '')) { selectedUser = u; return true; } return false; }); input.value = selectedUser ? (selectedUser.username || '') : ''; } function loadUserOptions() { return fetch('/api/admin/users?page=1&page_size=999') .then(function (r) { return r.json(); }) .then(function (res) { var items = res.items || []; shopManageGroupUsers = items.map(function (u) { return { id: u.id, username: u.username || '', role: u.role || 'normal' }; }); refreshShopManageGroupUserSelect(document.getElementById('shopManageGroupUserSelect') ? document.getElementById('shopManageGroupUserSelect').value : ''); var sel = document.getElementById('filterUser'); var cur = sel.value; sel.innerHTML = ''; items.forEach(function (u) { var opt = document.createElement('option'); opt.value = u.id; opt.textContent = u.username + ' (' + roleLabel(u.role || 'normal') + ')'; sel.appendChild(opt); }); sel.value = cur || ''; }); } document.getElementById('btnFilterHistory').onclick = function () { loadHistory(1); }; // ========== 数据去重总数据 ========== var dedupeTotalDataPage = 1, dedupeTotalDataPageSize = 15; function buildDedupeTotalDataQuery(page) { var q = 'page=' + (page || 1) + '&page_size=' + dedupeTotalDataPageSize; var keyword = (document.getElementById('searchDedupeTotalData').value || '').trim(); if (keyword) q += '&keyword=' + encodeURIComponent(keyword); return q; } function loadDedupeTotalData(page) { dedupeTotalDataPage = page || 1; fetch('/api/admin/dedupe-total-data?' + buildDedupeTotalDataQuery(dedupeTotalDataPage)) .then(function (r) { return r.json(); }) .then(function (res) { var tbody = document.getElementById('dedupeTotalDataListBody'); if (!res.success) { tbody.innerHTML = '加载失败: ' + (res.error || '') + ''; return; } var items = res.items || []; if (items.length === 0) { tbody.innerHTML = '暂无总数据'; } else { tbody.innerHTML = items.map(function (item) { return '' + item.id + '' + (item.data_value || '') + '' + (item.created_at || '') + '' + ' ' + '' + ''; }).join(''); } renderPagination('dedupeTotalDataPagination', res.total, res.page, res.page_size, loadDedupeTotalData); bindDedupeTotalDataActions(); }) .catch(function () { document.getElementById('dedupeTotalDataListBody').innerHTML = '请求失败'; }); } function bindDedupeTotalDataActions() { document.querySelectorAll('[data-dedupe-total-edit]').forEach(function (btn) { btn.onclick = function () { document.getElementById('editDedupeTotalDataId').value = btn.dataset.dedupeTotalEdit || ''; document.getElementById('editDedupeTotalDataValue').value = (btn.dataset.value || '').replace(/"/g, '"'); document.getElementById('msgEditDedupeTotalData').textContent = ''; document.getElementById('msgEditDedupeTotalData').className = 'msg'; document.getElementById('editDedupeTotalDataModal').classList.add('show'); }; }); document.querySelectorAll('[data-dedupe-total-delete]').forEach(function (btn) { btn.onclick = function () { var value = (btn.dataset.value || '').replace(/"/g, '"'); if (!confirm('确定删除总数据“' + value + '”吗?')) return; fetch('/api/admin/dedupe-total-data/' + btn.dataset.dedupeTotalDelete, { method: 'DELETE' }) .then(function (r) { return r.json(); }) .then(function (res) { if (res.success) { loadDedupeTotalData(dedupeTotalDataPage); } else { alert(res.error || '删除失败'); } }); }; }); } document.getElementById('btnSearchDedupeTotalData').onclick = function () { loadDedupeTotalData(1); }; var dedupeImportPollTimer = null; var dedupeDeleteImportPollTimer = null; function stopDedupeImportProgress() { if (dedupeImportPollTimer) { clearInterval(dedupeImportPollTimer); dedupeImportPollTimer = null; } } function stopDedupeDeleteImportProgress() { if (dedupeDeleteImportPollTimer) { clearInterval(dedupeDeleteImportPollTimer); dedupeDeleteImportPollTimer = null; } } function setDedupeImportProgress(percent, text) { var wrap = document.getElementById('dedupeTotalDataProgressWrap'); var fill = document.getElementById('dedupeTotalDataProgressFill'); var textEl = document.getElementById('dedupeTotalDataProgressText'); wrap.style.display = 'block'; fill.style.width = Math.max(0, Math.min(100, percent || 0)) + '%'; textEl.textContent = text || ''; } function setDedupeDeleteImportProgress(percent, text) { var wrap = document.getElementById('dedupeTotalDataDeleteProgressWrap'); var fill = document.getElementById('dedupeTotalDataDeleteProgressFill'); var textEl = document.getElementById('dedupeTotalDataDeleteProgressText'); wrap.style.display = 'block'; fill.style.width = Math.max(0, Math.min(100, percent || 0)) + '%'; textEl.textContent = text || ''; } function pollDedupeImport(importId) { stopDedupeImportProgress(); function tick() { fetch('/api/admin/dedupe-total-data/import/' + encodeURIComponent(importId)) .then(function (r) { return r.json(); }) .then(function (res) { if (!res.success) { stopDedupeImportProgress(); document.getElementById('msgDedupeTotalData').textContent = res.error || '查询导入进度失败'; document.getElementById('msgDedupeTotalData').className = 'msg err'; return; } var progress = res.progress || {}; var totalRows = progress.total_rows || 0; var processedRows = progress.processed_rows || 0; var percent = totalRows > 0 ? Math.round(processedRows * 100 / totalRows) : 0; setDedupeImportProgress(percent, '处理中:已处理 ' + processedRows + ' / ' + totalRows + ',新增 ' + (progress.inserted_count || 0) + ',跳过 ' + (progress.skipped_count || 0)); if (progress.status === 'success') { stopDedupeImportProgress(); document.getElementById('msgDedupeTotalData').textContent = '导入成功:总行数 ' + (progress.total_rows || 0) + ',ASIN 数量 ' + (progress.asin_count || 0) + ',新增 ' + (progress.inserted_count || 0) + ',跳过 ' + (progress.skipped_count || 0); document.getElementById('msgDedupeTotalData').className = 'msg ok'; loadDedupeTotalData(1); } else if (progress.status === 'failed') { stopDedupeImportProgress(); document.getElementById('msgDedupeTotalData').textContent = progress.error_message || '导入失败'; document.getElementById('msgDedupeTotalData').className = 'msg err'; } }) .catch(function () { stopDedupeImportProgress(); document.getElementById('msgDedupeTotalData').textContent = '查询导入进度失败'; document.getElementById('msgDedupeTotalData').className = 'msg err'; }); } tick(); dedupeImportPollTimer = setInterval(tick, 1000); } function pollDedupeDeleteImport(importId) { stopDedupeDeleteImportProgress(); function tick() { fetch('/api/admin/dedupe-total-data/delete-import/' + encodeURIComponent(importId)) .then(function (r) { return r.json(); }) .then(function (res) { if (!res.success) { stopDedupeDeleteImportProgress(); document.getElementById('msgDeleteDedupeTotalData').textContent = res.error || '查询删除进度失败'; document.getElementById('msgDeleteDedupeTotalData').className = 'msg err'; return; } var progress = res.progress || {}; var totalRows = progress.total_rows || 0; var processedRows = progress.processed_rows || 0; var percent = totalRows > 0 ? Math.round(processedRows * 100 / totalRows) : 0; setDedupeDeleteImportProgress(percent, '处理中:已处理 ' + processedRows + ' / ' + totalRows + ',删除 ' + (progress.deleted_count || 0) + ',跳过 ' + (progress.skipped_count || 0)); if (progress.status === 'success') { stopDedupeDeleteImportProgress(); document.getElementById('msgDeleteDedupeTotalData').textContent = '删除成功:总行数 ' + (progress.total_rows || 0) + ',ASIN 数量 ' + (progress.asin_count || 0) + ',删除 ' + (progress.deleted_count || 0) + ',跳过 ' + (progress.skipped_count || 0); document.getElementById('msgDeleteDedupeTotalData').className = 'msg ok'; loadDedupeTotalData(1); } else if (progress.status === 'failed') { stopDedupeDeleteImportProgress(); document.getElementById('msgDeleteDedupeTotalData').textContent = progress.error_message || '删除失败'; document.getElementById('msgDeleteDedupeTotalData').className = 'msg err'; } }) .catch(function () { stopDedupeDeleteImportProgress(); document.getElementById('msgDeleteDedupeTotalData').textContent = '查询删除进度失败'; document.getElementById('msgDeleteDedupeTotalData').className = 'msg err'; }); } tick(); dedupeDeleteImportPollTimer = setInterval(tick, 1000); } document.getElementById('btnAddDedupeTotalData').onclick = function () { var fileInput = document.getElementById('dedupeTotalDataFile'); var msgEl = document.getElementById('msgDedupeTotalData'); msgEl.textContent = ''; msgEl.className = 'msg'; stopDedupeImportProgress(); document.getElementById('dedupeTotalDataProgressWrap').style.display = 'none'; if (!fileInput.files || fileInput.files.length === 0) { msgEl.textContent = '请选择 Excel 文件'; msgEl.classList.add('err'); return; } var file = fileInput.files[0]; var lowerName = (file.name || '').toLowerCase(); if (!(lowerName.endsWith('.xlsx') || lowerName.endsWith('.xls'))) { msgEl.textContent = '仅支持 .xlsx 或 .xls 文件'; msgEl.classList.add('err'); return; } var formData = new FormData(); formData.append('file', file); var xhr = new XMLHttpRequest(); xhr.open('POST', '/api/admin/dedupe-total-data/import', true); xhr.upload.onprogress = function (event) { if (event.lengthComputable) { var percent = Math.round(event.loaded * 100 / event.total); setDedupeImportProgress(percent, '上传中:' + percent + '%'); } }; xhr.onreadystatechange = function () { if (xhr.readyState !== 4) return; if (xhr.status < 200 || xhr.status >= 300) { msgEl.textContent = '请求失败'; msgEl.className = 'msg err'; return; } var res; try { res = JSON.parse(xhr.responseText || '{}'); } catch (e) { res = { success: false, error: '返回格式错误' }; } if (!res.success) { msgEl.textContent = res.error || '导入失败'; msgEl.className = 'msg err'; return; } setDedupeImportProgress(100, '上传完成,后端处理中...'); pollDedupeImport(res.import_id); fileInput.value = ''; }; xhr.onerror = function () { msgEl.textContent = '请求失败'; msgEl.className = 'msg err'; }; xhr.send(formData); }; document.getElementById('btnDeleteImportDedupeTotalData').onclick = function () { var fileInput = document.getElementById('dedupeTotalDataDeleteFile'); var msgEl = document.getElementById('msgDeleteDedupeTotalData'); msgEl.textContent = ''; msgEl.className = 'msg'; stopDedupeDeleteImportProgress(); document.getElementById('dedupeTotalDataDeleteProgressWrap').style.display = 'none'; if (!fileInput.files || fileInput.files.length === 0) { msgEl.textContent = '请选择 Excel 文件'; msgEl.classList.add('err'); return; } var file = fileInput.files[0]; var lowerName = (file.name || '').toLowerCase(); if (!(lowerName.endsWith('.xlsx') || lowerName.endsWith('.xls'))) { msgEl.textContent = '仅支持 .xlsx 或 .xls 文件'; msgEl.classList.add('err'); return; } if (!confirm('确定按 Excel 中的 ASIN 批量删除匹配的总数据吗?')) { return; } var formData = new FormData(); formData.append('file', file); var xhr = new XMLHttpRequest(); xhr.open('POST', '/api/admin/dedupe-total-data/delete-import', true); xhr.upload.onprogress = function (event) { if (event.lengthComputable) { var percent = Math.round(event.loaded * 100 / event.total); setDedupeDeleteImportProgress(percent, '上传中:' + percent + '%'); } }; xhr.onreadystatechange = function () { if (xhr.readyState !== 4) return; if (xhr.status < 200 || xhr.status >= 300) { msgEl.textContent = '请求失败'; msgEl.className = 'msg err'; return; } var res; try { res = JSON.parse(xhr.responseText || '{}'); } catch (e) { res = { success: false, error: '返回格式错误' }; } if (!res.success) { msgEl.textContent = res.error || '删除失败'; msgEl.className = 'msg err'; return; } setDedupeDeleteImportProgress(100, '上传完成,后端处理中...'); pollDedupeDeleteImport(res.import_id); fileInput.value = ''; }; xhr.onerror = function () { msgEl.textContent = '请求失败'; msgEl.className = 'msg err'; }; xhr.send(formData); }; document.getElementById('btnSaveDedupeTotalData').onclick = function () { var itemId = document.getElementById('editDedupeTotalDataId').value; var value = (document.getElementById('editDedupeTotalDataValue').value || '').trim(); var msgEl = document.getElementById('msgEditDedupeTotalData'); msgEl.textContent = ''; msgEl.className = 'msg'; if (!value) { msgEl.textContent = '请填写总数据值'; msgEl.classList.add('err'); return; } fetch('/api/admin/dedupe-total-data/' + itemId, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ data_value: value }) }) .then(function (r) { return r.json(); }) .then(function (res) { if (res.success) { document.getElementById('editDedupeTotalDataModal').classList.remove('show'); loadDedupeTotalData(dedupeTotalDataPage); } else { msgEl.textContent = res.error || '保存失败'; msgEl.classList.add('err'); } }); }; document.getElementById('btnCloseEditDedupeTotalData').onclick = function () { document.getElementById('editDedupeTotalDataModal').classList.remove('show'); }; // ========== 店铺密钥管理 ========== var shopKeyPage = 1, shopKeyPageSize = 15; function buildShopKeyQuery(page) { return 'page=' + (page || 1) + '&page_size=' + shopKeyPageSize; } function loadShopKeys(page) { shopKeyPage = page || 1; fetch('/api/admin/shop-keys?' + buildShopKeyQuery(shopKeyPage)) .then(function (r) { return r.json(); }) .then(function (res) { var tbody = document.getElementById('shopKeyListBody'); if (!res.success) { tbody.innerHTML = '加载失败: ' + (res.error || '') + ''; return; } var items = res.items || []; if (items.length === 0) { tbody.innerHTML = '暂无店铺密钥'; } else { tbody.innerHTML = items.map(function (item, index) { var rowNo = (shopKeyPage - 1) * shopKeyPageSize + index + 1; return '' + rowNo + '' + (item.ziniao_account_name || '') + '' + (item.ziniao_token || '') + '' + (item.created_at || '') + '' + (item.updated_at || '') + '' + ' ' + '' + ''; }).join(''); } renderPagination('shopKeyPagination', res.total, res.page, res.page_size, loadShopKeys); bindShopKeyActions(); }) .catch(function () { document.getElementById('shopKeyListBody').innerHTML = '请求失败'; }); } function bindShopKeyActions() { document.querySelectorAll('[data-shop-key-edit]').forEach(function (btn) { btn.onclick = function () { var item = {}; try { item = JSON.parse((btn.dataset.shopKey || '').replace(/"/g, '"')); } catch (e) { item = {}; } document.getElementById('editShopKeyId').value = item.id || ''; document.getElementById('editShopKeyZiniaoAccountName').value = item.ziniao_account_name || ''; document.getElementById('editShopKeyZiniaoToken').value = item.ziniao_token || ''; document.getElementById('msgEditShopKey').textContent = ''; document.getElementById('msgEditShopKey').className = 'msg'; document.getElementById('editShopKeyModal').classList.add('show'); }; }); document.querySelectorAll('[data-shop-key-delete]').forEach(function (btn) { btn.onclick = function () { var name = (btn.dataset.ziniaoAccountName || '').replace(/"/g, '"'); if (!confirm('确定删除店铺密钥“' + name + '”吗?')) return; fetch('/api/admin/shop-key/' + btn.dataset.shopKeyDelete, { method: 'DELETE' }) .then(function (r) { return r.json(); }) .then(function (res) { if (res.success) { loadShopKeys(shopKeyPage); } else { alert(res.error || '删除失败'); } }); }; }); } document.getElementById('btnCreateShopKey').onclick = function () { var ziniaoAccountName = (document.getElementById('shopKeyZiniaoAccountName').value || '').trim(); var ziniaoToken = (document.getElementById('shopKeyZiniaoToken').value || '').trim(); var msgEl = document.getElementById('msgShopKey'); msgEl.textContent = ''; msgEl.className = 'msg'; if (!ziniaoAccountName || !ziniaoToken) { msgEl.textContent = '请完整填写紫鸟账号名称、紫鸟令牌'; msgEl.classList.add('err'); return; } fetch('/api/admin/shop-key', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ ziniao_account_name: ziniaoAccountName, ziniao_token: ziniaoToken }) }) .then(function (r) { return r.json(); }) .then(function (res) { if (res.success) { document.getElementById('shopKeyZiniaoAccountName').value = ''; document.getElementById('shopKeyZiniaoToken').value = ''; msgEl.textContent = res.msg || '创建成功'; msgEl.className = 'msg ok'; loadShopKeys(1); } else { msgEl.textContent = res.error || '创建失败'; msgEl.className = 'msg err'; } }) .catch(function () { msgEl.textContent = '请求失败'; msgEl.className = 'msg err'; }); }; document.getElementById('btnSaveShopKey').onclick = function () { var itemId = document.getElementById('editShopKeyId').value; var ziniaoAccountName = (document.getElementById('editShopKeyZiniaoAccountName').value || '').trim(); var ziniaoToken = (document.getElementById('editShopKeyZiniaoToken').value || '').trim(); var msgEl = document.getElementById('msgEditShopKey'); msgEl.textContent = ''; msgEl.className = 'msg'; if (!ziniaoAccountName || !ziniaoToken) { msgEl.textContent = '请完整填写紫鸟账号名称、紫鸟令牌'; msgEl.classList.add('err'); return; } fetch('/api/admin/shop-key/' + itemId, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ ziniao_account_name: ziniaoAccountName, ziniao_token: ziniaoToken }) }) .then(function (r) { return r.json(); }) .then(function (res) { if (res.success) { document.getElementById('editShopKeyModal').classList.remove('show'); loadShopKeys(shopKeyPage); } else { msgEl.textContent = res.error || '保存失败'; msgEl.classList.add('err'); } }) .catch(function () { msgEl.textContent = '请求失败'; msgEl.classList.add('err'); }); }; document.getElementById('btnCloseEditShopKey').onclick = function () { document.getElementById('editShopKeyModal').classList.remove('show'); }; // ========== 店铺管理 ========== var shopManagePage = 1, shopManagePageSize = 15; var shopManageGroups = []; function buildShopManageQuery(page) { var query = 'page=' + (page || 1) + '&page_size=' + shopManagePageSize; var groupId = (document.getElementById('shopManageFilterGroupId').value || '').trim(); var shopName = (document.getElementById('shopManageFilterShopName').value || '').trim(); if (groupId) query += '&group_id=' + encodeURIComponent(groupId); if (shopName) query += '&shop_name=' + encodeURIComponent(shopName); return query; } function refreshShopGroupSelects(selectedCreateId, selectedEditId) { var createSel = document.getElementById('shopManageGroupSelect'); var editSel = document.getElementById('editShopManageGroupSelect'); var filterSel = document.getElementById('shopManageFilterGroupId'); var skipCreateSel = document.getElementById('skipPriceAsinGroupSelect'); var skipFilterSel = document.getElementById('skipPriceAsinFilterGroupId'); var chooseSkipShopSel = document.getElementById('chooseSkipPriceAsinShopGroupId'); var selectedFilterId = filterSel ? filterSel.value : ''; var selectedSkipCreateId = skipCreateSel ? skipCreateSel.value : ''; var selectedSkipFilterId = skipFilterSel ? skipFilterSel.value : ''; var selectedChooseSkipShopId = chooseSkipShopSel ? chooseSkipShopSel.value : ''; var createOpts = ['']; var filterOpts = ['']; shopManageGroups.forEach(function (g) { var option = ''; createOpts.push(option); filterOpts.push(option); }); createSel.innerHTML = createOpts.join(''); editSel.innerHTML = createOpts.join(''); if (skipCreateSel) skipCreateSel.innerHTML = createOpts.join(''); if (filterSel) filterSel.innerHTML = filterOpts.join(''); if (skipFilterSel) skipFilterSel.innerHTML = filterOpts.join(''); if (chooseSkipShopSel) chooseSkipShopSel.innerHTML = filterOpts.join(''); if (selectedCreateId != null) createSel.value = String(selectedCreateId); if (selectedEditId != null) editSel.value = String(selectedEditId); if (filterSel && selectedFilterId) filterSel.value = selectedFilterId; if (skipCreateSel && selectedSkipCreateId) skipCreateSel.value = selectedSkipCreateId; if (skipFilterSel && selectedSkipFilterId) skipFilterSel.value = selectedSkipFilterId; if (chooseSkipShopSel && selectedChooseSkipShopId) chooseSkipShopSel.value = selectedChooseSkipShopId; } function loadShopManageGroups(selectedCreateId, selectedEditId) { return fetch('/api/admin/shop-manage-groups') .then(function (r) { return r.json(); }) .then(function (res) { if (!res.success) throw new Error(res.error || '加载分组失败'); shopManageGroups = res.items || []; refreshShopGroupSelects(selectedCreateId, selectedEditId); return shopManageGroups; }) .catch(function () { shopManageGroups = []; refreshShopGroupSelects(); return []; }); } function openShopManageGroupModal() { document.getElementById('shopManageGroupEditId').value = ''; document.getElementById('shopManageGroupInput').value = ''; document.getElementById('msgShopManageGroup').textContent = ''; document.getElementById('msgShopManageGroup').className = 'msg'; loadShopManageGroups().then(function () { renderShopManageGroupRows(); document.getElementById('shopManageGroupModal').classList.add('show'); }); } function renderShopManageGroupRows() { var tbody = document.getElementById('shopManageGroupListBody'); if (!shopManageGroups.length) { tbody.innerHTML = '暂无分组'; return; } tbody.innerHTML = shopManageGroups.map(function (item, index) { return '' + (index + 1) + '' + (item.group_name || '') + '' + (item.created_at || '') + '' + (item.updated_at || '') + '' + ' ' + '' + ''; }).join(''); document.querySelectorAll('[data-shop-group-edit]').forEach(function (btn) { btn.onclick = function () { document.getElementById('shopManageGroupEditId').value = btn.dataset.shopGroupEdit; document.getElementById('shopManageGroupInput').value = (btn.dataset.shopGroupName || '').replace(/"/g, '"'); }; }); document.querySelectorAll('[data-shop-group-delete]').forEach(function (btn) { btn.onclick = function () { var gid = btn.dataset.shopGroupDelete; var gname = (btn.dataset.shopGroupName || '').replace(/"/g, '"'); if (!confirm('确定删除分组“' + gname + '”吗?')) return; fetch('/api/admin/shop-manage-group/' + gid, { method: 'DELETE' }) .then(function (r) { return r.json(); }) .then(function (res) { if (!res.success) { alert(res.error || '删除失败'); return; } loadShopManageGroups().then(function () { renderShopManageGroupRows(); loadShopManage(shopManagePage); loadSkipPriceAsin(skipPriceAsinPage); }); }); }; }); } function loadShopManage(page) { shopManagePage = page || 1; fetch('/api/admin/shop-manages?' + buildShopManageQuery(shopManagePage)) .then(function (r) { return r.json(); }) .then(function (res) { var tbody = document.getElementById('shopManageListBody'); if (!res.success) { tbody.innerHTML = '加载失败: ' + (res.error || '') + ''; return; } var items = res.items || []; if (items.length === 0) { tbody.innerHTML = '暂无店铺'; } else { tbody.innerHTML = items.map(function (item, index) { var rowNo = (shopManagePage - 1) * shopManagePageSize + index + 1; return '' + rowNo + '' + (item.group_name || '') + '' + (item.shop_name || '') + '' + (item.mall_name || '') + '' + (item.account || '') + '' + (item.password || '') + '' + (item.created_at || '') + '' + (item.updated_at || '') + '' + ' ' + '' + ''; }).join(''); } renderPagination('shopManagePagination', res.total, res.page, res.page_size, loadShopManage); bindShopManageActions(); }) .catch(function () { document.getElementById('shopManageListBody').innerHTML = '请求失败'; }); } function bindShopManageActions() { document.querySelectorAll('[data-shop-manage-edit]').forEach(function (btn) { btn.onclick = function () { var item = {}; try { item = JSON.parse((btn.dataset.shopManage || '').replace(/"/g, '"')); } catch (e) { item = {}; } document.getElementById('editShopManageId').value = item.id || ''; document.getElementById('editShopManageShopName').value = item.shop_name || ''; document.getElementById('editShopManageMallName').value = item.mall_name || ''; document.getElementById('editShopManageAccount').value = item.account || ''; document.getElementById('editShopManagePassword').value = item.password || ''; document.getElementById('msgEditShopManage').textContent = ''; document.getElementById('msgEditShopManage').className = 'msg'; loadShopManageGroups(null, item.group_id).then(function () { document.getElementById('editShopManageModal').classList.add('show'); }); }; }); document.querySelectorAll('[data-shop-manage-delete]').forEach(function (btn) { btn.onclick = function () { var name = (btn.dataset.shopManageName || '').replace(/"/g, '"'); if (!confirm('确定删除店铺“' + name + '”吗?')) return; fetch('/api/admin/shop-manage/' + btn.dataset.shopManageDelete, { method: 'DELETE' }) .then(function (r) { return r.json(); }) .then(function (res) { if (res.success) { loadShopManage(shopManagePage); } else { alert(res.error || '删除失败'); } }); }; }); } document.getElementById('btnManageShopGroups').onclick = openShopManageGroupModal; document.getElementById('btnManageShopGroupsFromEdit').onclick = openShopManageGroupModal; document.getElementById('btnSearchShopManage').onclick = function () { loadShopManage(1); }; document.getElementById('shopManageFilterShopName').addEventListener('keydown', function (e) { if (e.key === 'Enter') { e.preventDefault(); loadShopManage(1); } }); document.getElementById('btnCloseShopManageGroupModal').onclick = function () { document.getElementById('shopManageGroupModal').classList.remove('show'); }; document.getElementById('btnCancelShopManageGroupEdit').onclick = function () { document.getElementById('shopManageGroupEditId').value = ''; document.getElementById('shopManageGroupInput').value = ''; }; document.getElementById('btnSaveShopManageGroup').onclick = function () { var editId = (document.getElementById('shopManageGroupEditId').value || '').trim(); var groupName = (document.getElementById('shopManageGroupInput').value || '').trim(); var msgEl = document.getElementById('msgShopManageGroup'); msgEl.textContent = ''; msgEl.className = 'msg'; if (!groupName) { msgEl.textContent = '请输入分组名称'; msgEl.className = 'msg err'; return; } var method = editId ? 'PUT' : 'POST'; var url = editId ? ('/api/admin/shop-manage-group/' + editId) : '/api/admin/shop-manage-group'; fetch(url, { method: method, headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ group_name: groupName }) }) .then(function (r) { return r.json(); }) .then(function (res) { if (!res.success) { msgEl.textContent = res.error || '保存失败'; msgEl.className = 'msg err'; return; } document.getElementById('shopManageGroupEditId').value = ''; document.getElementById('shopManageGroupInput').value = ''; msgEl.textContent = res.msg || '保存成功'; msgEl.className = 'msg ok'; loadShopManageGroups().then(function () { renderShopManageGroupRows(); loadShopManage(shopManagePage); loadSkipPriceAsin(skipPriceAsinPage); }); }) .catch(function () { msgEl.textContent = '请求失败'; msgEl.className = 'msg err'; }); }; function refreshShopGroupSelects(selectedCreateId, selectedEditId) { var createSel = document.getElementById('shopManageGroupSelect'); var editSel = document.getElementById('editShopManageGroupSelect'); var filterSel = document.getElementById('shopManageFilterGroupId'); var skipCreateSel = document.getElementById('skipPriceAsinGroupSelect'); var skipFilterSel = document.getElementById('skipPriceAsinFilterGroupId'); var chooseSkipShopSel = document.getElementById('chooseSkipPriceAsinShopGroupId'); var selectedFilterId = filterSel ? filterSel.value : ''; var selectedSkipCreateId = skipCreateSel ? skipCreateSel.value : ''; var selectedSkipFilterId = skipFilterSel ? skipFilterSel.value : ''; var selectedChooseSkipShopId = chooseSkipShopSel ? chooseSkipShopSel.value : ''; var createOpts = ['']; var filterOpts = ['']; shopManageGroups.forEach(function (g) { var option = ''; createOpts.push(option); filterOpts.push(option); }); createSel.innerHTML = createOpts.join(''); editSel.innerHTML = createOpts.join(''); if (skipCreateSel) skipCreateSel.innerHTML = createOpts.join(''); if (filterSel) filterSel.innerHTML = filterOpts.join(''); if (skipFilterSel) skipFilterSel.innerHTML = filterOpts.join(''); if (chooseSkipShopSel) chooseSkipShopSel.innerHTML = filterOpts.join(''); if (selectedCreateId != null) createSel.value = String(selectedCreateId); if (selectedEditId != null) editSel.value = String(selectedEditId); if (filterSel && selectedFilterId) filterSel.value = selectedFilterId; if (skipCreateSel && selectedSkipCreateId) skipCreateSel.value = selectedSkipCreateId; if (skipFilterSel && selectedSkipFilterId) skipFilterSel.value = selectedSkipFilterId; if (chooseSkipShopSel && selectedChooseSkipShopId) chooseSkipShopSel.value = selectedChooseSkipShopId; } function loadShopManageGroups(selectedCreateId, selectedEditId) { return fetch('/api/admin/shop-manage-groups') .then(function (r) { return r.json(); }) .then(function (res) { if (!res.success) throw new Error(res.error || '加载分组失败'); shopManageGroups = res.items || []; refreshShopGroupSelects(selectedCreateId, selectedEditId); return shopManageGroups; }) .catch(function () { shopManageGroups = []; refreshShopGroupSelects(); return []; }); } function resetShopManageGroupForm() { document.getElementById('shopManageGroupEditId').value = ''; document.getElementById('shopManageGroupInput').value = ''; document.getElementById('msgShopManageGroup').textContent = ''; document.getElementById('msgShopManageGroup').className = 'msg'; refreshShopManageGroupUserSelect(''); } function openShopManageGroupModal() { resetShopManageGroupForm(); loadUserOptions() .then(function () { return loadShopManageGroups(); }) .then(function () { renderShopManageGroupRows(); document.getElementById('shopManageGroupModal').classList.add('show'); }); } function renderShopManageGroupRows() { var tbody = document.getElementById('shopManageGroupListBody'); if (!shopManageGroups.length) { tbody.innerHTML = '暂无分组'; return; } tbody.innerHTML = shopManageGroups.map(function (item, index) { return '' + (index + 1) + '' + (item.group_name || '') + '' + (item.username || '') + '' + (item.created_at || '') + '' + (item.updated_at || '') + '' + ' ' + '' + ''; }).join(''); document.querySelectorAll('[data-shop-group-edit]').forEach(function (btn) { btn.onclick = function () { document.getElementById('shopManageGroupEditId').value = btn.dataset.shopGroupEdit; refreshShopManageGroupUserSelect(btn.dataset.shopGroupUserId || ''); }; }); document.querySelectorAll('[data-shop-group-delete]').forEach(function (btn) { btn.onclick = function () { var gid = btn.dataset.shopGroupDelete; var gname = (btn.dataset.shopGroupName || '').replace(/"/g, '"'); if (!confirm('确定删除分组“' + gname + '”吗?')) return; fetch('/api/admin/shop-manage-group/' + gid, { method: 'DELETE' }) .then(function (r) { return r.json(); }) .then(function (res) { if (!res.success) { alert(res.error || '删除失败'); return; } loadShopManageGroups().then(function () { renderShopManageGroupRows(); loadShopManage(shopManagePage); loadSkipPriceAsin(skipPriceAsinPage); }); }); }; }); } document.getElementById('shopManageGroupUserSelect').onchange = syncShopManageGroupNameWithUser; document.getElementById('btnManageShopGroups').onclick = openShopManageGroupModal; document.getElementById('btnManageShopGroupsFromEdit').onclick = openShopManageGroupModal; document.getElementById('btnManageSkipPriceAsinGroups').onclick = openShopManageGroupModal; document.getElementById('btnCloseShopManageGroupModal').onclick = function () { document.getElementById('shopManageGroupModal').classList.remove('show'); }; document.getElementById('btnCancelShopManageGroupEdit').onclick = resetShopManageGroupForm; document.getElementById('btnSaveShopManageGroup').onclick = function () { var editId = (document.getElementById('shopManageGroupEditId').value || '').trim(); var groupName = (document.getElementById('shopManageGroupInput').value || '').trim(); var userId = (document.getElementById('shopManageGroupUserSelect').value || '').trim(); var msgEl = document.getElementById('msgShopManageGroup'); msgEl.textContent = ''; msgEl.className = 'msg'; if (!userId || !groupName) { msgEl.textContent = '请选择关联用户'; msgEl.className = 'msg err'; return; } var method = editId ? 'PUT' : 'POST'; var url = editId ? ('/api/admin/shop-manage-group/' + editId) : '/api/admin/shop-manage-group'; fetch(url, { method: method, headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ group_name: groupName, user_id: Number(userId) }) }) .then(function (r) { return r.json(); }) .then(function (res) { if (!res.success) { msgEl.textContent = res.error || '保存失败'; msgEl.className = 'msg err'; return; } resetShopManageGroupForm(); msgEl.textContent = res.msg || '保存成功'; msgEl.className = 'msg ok'; loadShopManageGroups().then(function () { renderShopManageGroupRows(); loadShopManage(shopManagePage); loadSkipPriceAsin(skipPriceAsinPage); }); }) .catch(function () { msgEl.textContent = '请求失败'; msgEl.className = 'msg err'; }); }; document.getElementById('btnCreateShopManage').onclick = function () { var groupId = (document.getElementById('shopManageGroupSelect').value || '').trim(); var shopName = (document.getElementById('shopManageShopName').value || '').trim(); var mallName = (document.getElementById('shopManageMallName').value || '').trim(); var account = (document.getElementById('shopManageAccount').value || '').trim(); var password = (document.getElementById('shopManagePassword').value || '').trim(); var msgEl = document.getElementById('msgShopManage'); msgEl.textContent = ''; msgEl.className = 'msg'; if (!groupId || !shopName || !mallName || !account || !password) { msgEl.textContent = '请完整填写分组、店铺名、店铺商城名、账号、密码'; msgEl.classList.add('err'); return; } fetch('/api/admin/shop-manage', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ group_id: Number(groupId), shop_name: shopName, mall_name: mallName, account: account, password: password }) }) .then(function (r) { return r.json(); }) .then(function (res) { if (res.success) { document.getElementById('shopManageGroupSelect').value = ''; document.getElementById('shopManageShopName').value = ''; document.getElementById('shopManageMallName').value = ''; document.getElementById('shopManageAccount').value = ''; document.getElementById('shopManagePassword').value = ''; msgEl.textContent = res.msg || '创建成功'; msgEl.className = 'msg ok'; loadShopManage(1); } else { msgEl.textContent = res.error || '创建失败'; msgEl.className = 'msg err'; } }) .catch(function () { msgEl.textContent = '请求失败'; msgEl.className = 'msg err'; }); }; document.getElementById('btnSaveShopManage').onclick = function () { var itemId = document.getElementById('editShopManageId').value; var groupId = (document.getElementById('editShopManageGroupSelect').value || '').trim(); var shopName = (document.getElementById('editShopManageShopName').value || '').trim(); var mallName = (document.getElementById('editShopManageMallName').value || '').trim(); var account = (document.getElementById('editShopManageAccount').value || '').trim(); var password = (document.getElementById('editShopManagePassword').value || '').trim(); var msgEl = document.getElementById('msgEditShopManage'); msgEl.textContent = ''; msgEl.className = 'msg'; if (!groupId || !shopName || !mallName || !account || !password) { msgEl.textContent = '请完整填写分组、店铺名、店铺商城名、账号、密码'; msgEl.classList.add('err'); return; } fetch('/api/admin/shop-manage/' + itemId, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ group_id: Number(groupId), shop_name: shopName, mall_name: mallName, account: account, password: password }) }) .then(function (r) { return r.json(); }) .then(function (res) { if (res.success) { document.getElementById('editShopManageModal').classList.remove('show'); loadShopManage(shopManagePage); } else { msgEl.textContent = res.error || '保存失败'; msgEl.classList.add('err'); } }) .catch(function () { msgEl.textContent = '请求失败'; msgEl.classList.add('err'); }); }; document.getElementById('btnCloseEditShopManage').onclick = function () { document.getElementById('editShopManageModal').classList.remove('show'); }; // ========== 版本管理 ========== // ========== 跳过跟价 ASIN ========== var skipPriceAsinPage = 1, skipPriceAsinPageSize = 15; var chooseSkipPriceAsinShopPage = 1, chooseSkipPriceAsinShopPageSize = 10; var skipPriceCountryColumns = [ { code: 'DE', field: 'asin_de', label: '德国' }, { code: 'UK', field: 'asin_uk', label: '英国' }, { code: 'FR', field: 'asin_fr', label: '法国' }, { code: 'IT', field: 'asin_it', label: '意大利' }, { code: 'ES', field: 'asin_es', label: '西班牙' } ]; function buildChooseSkipPriceAsinShopQuery(page) { var query = 'page=' + (page || 1) + '&page_size=' + chooseSkipPriceAsinShopPageSize; var groupId = (document.getElementById('chooseSkipPriceAsinShopGroupId').value || '').trim(); var shopName = (document.getElementById('chooseSkipPriceAsinShopKeyword').value || '').trim(); if (groupId) query += '&group_id=' + encodeURIComponent(groupId); if (shopName) query += '&shop_name=' + encodeURIComponent(shopName); return query; } function loadChooseSkipPriceAsinShops(page) { chooseSkipPriceAsinShopPage = page || 1; fetch('/api/admin/shop-manages?' + buildChooseSkipPriceAsinShopQuery(chooseSkipPriceAsinShopPage)) .then(function (r) { return r.json(); }) .then(function (res) { var tbody = document.getElementById('chooseSkipPriceAsinShopListBody'); if (!res.success) { tbody.innerHTML = '加载失败: ' + (res.error || '') + ''; return; } var items = res.items || []; if (!items.length) { tbody.innerHTML = '暂无店铺'; } else { tbody.innerHTML = items.map(function (item, index) { var rowNo = (chooseSkipPriceAsinShopPage - 1) * chooseSkipPriceAsinShopPageSize + index + 1; return '' + rowNo + '' + (item.group_name || '') + '' + (item.shop_name || '') + '' + (item.mall_name || '') + '' + (item.account || '') + '' + '' + ''; }).join(''); } renderPagination('chooseSkipPriceAsinShopPagination', res.total, res.page, res.page_size, loadChooseSkipPriceAsinShops); bindChooseSkipPriceAsinShopActions(); }) .catch(function () { document.getElementById('chooseSkipPriceAsinShopListBody').innerHTML = '请求失败'; }); } function bindChooseSkipPriceAsinShopActions() { document.querySelectorAll('[data-choose-skip-price-asin-shop]').forEach(function (btn) { btn.onclick = function () { document.getElementById('skipPriceAsinShopName').value = (btn.dataset.shopName || '').replace(/"/g, '"'); if (!document.getElementById('skipPriceAsinGroupSelect').value && btn.dataset.groupId) { document.getElementById('skipPriceAsinGroupSelect').value = btn.dataset.groupId; } document.getElementById('chooseSkipPriceAsinShopModal').classList.remove('show'); }; }); } function openChooseSkipPriceAsinShopModal() { var currentGroupId = (document.getElementById('skipPriceAsinGroupSelect').value || '').trim(); if (currentGroupId) { document.getElementById('chooseSkipPriceAsinShopGroupId').value = currentGroupId; } document.getElementById('chooseSkipPriceAsinShopKeyword').value = (document.getElementById('skipPriceAsinShopName').value || '').trim(); document.getElementById('chooseSkipPriceAsinShopModal').classList.add('show'); loadChooseSkipPriceAsinShops(1); } function setupSkipPriceAsinShopPicker() { var input = document.getElementById('skipPriceAsinShopName'); var button = document.getElementById('btnChooseSkipPriceAsinShop'); if (!input || !button) return; var inputParent = input.parentNode; if (inputParent && inputParent.style && inputParent.style.display === 'flex') return; var legacyWrap = button.parentNode; var wrapper = document.createElement('div'); wrapper.style.display = 'flex'; wrapper.style.gap = '8px'; wrapper.style.alignItems = 'center'; input.parentNode.insertBefore(wrapper, input); wrapper.appendChild(input); wrapper.appendChild(button); button.style.whiteSpace = 'nowrap'; button.style.marginTop = '0'; if (legacyWrap && legacyWrap !== wrapper) { legacyWrap.style.display = 'none'; } } function getSelectedSkipPriceCountries() { return Array.from(document.getElementById('skipPriceAsinCountries').selectedOptions).map(function (option) { return option.value; }); } function renderSkipPriceAsinInputs() { var container = document.getElementById('skipPriceAsinInputs'); var selectedCountries = getSelectedSkipPriceCountries(); var existingValues = {}; container.querySelectorAll('[data-skip-price-country-input]').forEach(function (input) { existingValues[input.getAttribute('data-skip-price-country-input')] = input.value; }); if (!selectedCountries.length) { container.innerHTML = '
请选择国家后输入 ASIN
'; return; } container.innerHTML = selectedCountries.map(function (countryCode) { var country = skipPriceCountryColumns.find(function (item) { return item.code === countryCode; }); var label = country ? country.label : countryCode; var value = existingValues[countryCode] || ''; return '
' + '' + label + '' + '' + '
'; }).join(''); } function collectSkipPriceAsinMappings(countries) { var mappings = {}; for (var i = 0; i < countries.length; i++) { var countryCode = countries[i]; var input = document.querySelector('[data-skip-price-country-input="' + countryCode + '"]'); var asin = input ? (input.value || '').trim().toUpperCase() : ''; if (!asin) { var country = skipPriceCountryColumns.find(function (item) { return item.code === countryCode; }); var label = country ? country.label : countryCode; throw new Error(label + ' ASIN 不能为空'); } mappings[countryCode] = asin; } return mappings; } function buildSkipPriceAsinQuery(page) { var query = 'page=' + (page || 1) + '&page_size=' + skipPriceAsinPageSize; var groupId = (document.getElementById('skipPriceAsinFilterGroupId').value || '').trim(); var shopName = (document.getElementById('skipPriceAsinFilterShopName').value || '').trim(); var asin = (document.getElementById('skipPriceAsinFilterAsin').value || '').trim(); if (groupId) query += '&group_id=' + encodeURIComponent(groupId); if (shopName) query += '&shop_name=' + encodeURIComponent(shopName); if (asin) query += '&asin=' + encodeURIComponent(asin); return query; } function renderSkipPriceAsinCell(item, country) { var value = item[country.field] || ''; if (!value) return '-'; return '
' + '' + value + '' + '' + '' + '
'; } function bindSkipPriceAsinActions() { document.querySelectorAll('[data-skip-price-asin-edit]').forEach(function (btn) { btn.onclick = function () { var shopName = (btn.dataset.shopName || '').replace(/"/g, '"'); var countryCode = btn.dataset.country || ''; var currentAsin = (btn.dataset.asin || '').replace(/"/g, '"'); var nextAsin = prompt('请输入店铺“' + shopName + '”在 ' + countryCode + ' 的 ASIN', currentAsin); if (nextAsin === null) return; nextAsin = (nextAsin || '').trim(); if (!nextAsin) { alert('ASIN 不能为空'); return; } fetch('/api/admin/skip-price-asin/' + btn.dataset.skipPriceAsinEdit + '/country/' + countryCode, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ asin: nextAsin }) }) .then(function (r) { return r.json(); }) .then(function (res) { if (res.success) loadSkipPriceAsin(skipPriceAsinPage); else alert(res.error || '保存失败'); }); }; }); document.querySelectorAll('[data-skip-price-asin-delete]').forEach(function (btn) { btn.onclick = function () { var shopName = (btn.dataset.shopName || '').replace(/"/g, '"'); var countryCode = btn.dataset.country || ''; if (!confirm('确定删除店铺“' + shopName + '”在 ' + countryCode + ' 的 ASIN 吗?')) return; fetch('/api/admin/skip-price-asin/' + btn.dataset.skipPriceAsinDelete + '/country/' + countryCode, { method: 'DELETE' }) .then(function (r) { return r.json(); }) .then(function (res) { if (res.success) loadSkipPriceAsin(skipPriceAsinPage); else alert(res.error || '删除失败'); }); }; }); } function loadSkipPriceAsin(page) { skipPriceAsinPage = page || 1; fetch('/api/admin/skip-price-asins?' + buildSkipPriceAsinQuery(skipPriceAsinPage)) .then(function (r) { return r.json(); }) .then(function (res) { var tbody = document.getElementById('skipPriceAsinListBody'); if (!res.success) { tbody.innerHTML = '加载失败: ' + (res.error || '') + ''; return; } var items = res.items || []; if (items.length === 0) { tbody.innerHTML = '暂无数据'; } else { tbody.innerHTML = items.map(function (item, index) { var rowNo = (skipPriceAsinPage - 1) * skipPriceAsinPageSize + index + 1; return '' + rowNo + '' + (item.group_name || '') + '' + (item.shop_name || '') + '' + skipPriceCountryColumns.map(function (country) { return '' + renderSkipPriceAsinCell(item, country) + ''; }).join('') + ''; }).join(''); } renderPagination('skipPriceAsinPagination', res.total, res.page, res.page_size, loadSkipPriceAsin); bindSkipPriceAsinActions(); }) .catch(function () { document.getElementById('skipPriceAsinListBody').innerHTML = '请求失败'; }); } document.getElementById('btnManageSkipPriceAsinGroups').onclick = openShopManageGroupModal; document.getElementById('btnChooseSkipPriceAsinShop').onclick = openChooseSkipPriceAsinShopModal; document.getElementById('btnSearchChooseSkipPriceAsinShop').onclick = function () { loadChooseSkipPriceAsinShops(1); }; document.getElementById('chooseSkipPriceAsinShopGroupId').onchange = function () { loadChooseSkipPriceAsinShops(1); }; document.getElementById('chooseSkipPriceAsinShopKeyword').addEventListener('keydown', function (e) { if (e.key === 'Enter') { e.preventDefault(); loadChooseSkipPriceAsinShops(1); } }); document.getElementById('btnCloseChooseSkipPriceAsinShopModal').onclick = function () { document.getElementById('chooseSkipPriceAsinShopModal').classList.remove('show'); }; document.getElementById('skipPriceAsinCountries').addEventListener('change', renderSkipPriceAsinInputs); document.getElementById('btnSearchSkipPriceAsin').onclick = function () { loadSkipPriceAsin(1); }; document.getElementById('skipPriceAsinFilterShopName').addEventListener('keydown', function (e) { if (e.key === 'Enter') { e.preventDefault(); loadSkipPriceAsin(1); } }); document.getElementById('skipPriceAsinFilterAsin').addEventListener('keydown', function (e) { if (e.key === 'Enter') { e.preventDefault(); loadSkipPriceAsin(1); } }); document.getElementById('btnCreateSkipPriceAsin').onclick = function () { var groupId = (document.getElementById('skipPriceAsinGroupSelect').value || '').trim(); var shopName = (document.getElementById('skipPriceAsinShopName').value || '').trim(); var countries = getSelectedSkipPriceCountries(); var msgEl = document.getElementById('msgSkipPriceAsin'); msgEl.textContent = ''; msgEl.className = 'msg'; if (!groupId || !shopName || !countries.length) { msgEl.textContent = '请完整填写分组、店铺名、国家和 ASIN'; msgEl.className = 'msg err'; return; } var asinMappings = {}; try { asinMappings = collectSkipPriceAsinMappings(countries); } catch (err) { msgEl.textContent = err.message || '请输入 ASIN'; msgEl.className = 'msg err'; return; } var fallbackAsin = ''; Object.keys(asinMappings).some(function (countryCode) { fallbackAsin = asinMappings[countryCode] || ''; return !!fallbackAsin; }); fetch('/api/admin/skip-price-asin', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ group_id: Number(groupId), shop_name: shopName, countries: countries, asin: fallbackAsin, asin_mappings: asinMappings }) }) .then(function (r) { return r.json(); }) .then(function (res) { if (!res.success) { msgEl.textContent = res.error || '保存失败'; msgEl.className = 'msg err'; return; } document.getElementById('skipPriceAsinGroupSelect').value = ''; document.getElementById('skipPriceAsinShopName').value = ''; Array.from(document.getElementById('skipPriceAsinCountries').options).forEach(function (option) { option.selected = false; }); renderSkipPriceAsinInputs(); msgEl.textContent = res.msg || '保存成功'; msgEl.className = 'msg ok'; loadSkipPriceAsin(1); }) .catch(function () { msgEl.textContent = '请求失败'; msgEl.className = 'msg err'; }); }; setupSkipPriceAsinShopPicker(); renderSkipPriceAsinInputs(); function loadVersions() { fetch('/api/admin/versions') .then(function (r) { return r.json(); }) .then(function (res) { var tbody = document.getElementById('versionListBody'); if (!res.success) { tbody.innerHTML = '加载失败: ' + (res.error || '') + ''; return; } var items = res.items || []; if (items.length === 0) { tbody.innerHTML = '暂无版本记录'; } else { tbody.innerHTML = items.map(function (v) { var url = (v.file_url || '').replace(/"/g, '"'); return '' + (v.version || '') + '' + url + '' + (v.created_at || '') + '下载'; }).join(''); } }) .catch(function () { document.getElementById('versionListBody').innerHTML = '请求失败'; }); } document.getElementById('btnUploadVersion').onclick = function () { var version = (document.getElementById('versionNumber').value || '').trim(); var fileInput = document.getElementById('versionZip'); var msgEl = document.getElementById('msgVersion'); msgEl.textContent = ''; msgEl.className = 'msg'; if (!version) { msgEl.textContent = '请填写版本号'; msgEl.classList.add('err'); return; } if (!fileInput.files || fileInput.files.length === 0) { msgEl.textContent = '请选择 zip 压缩包'; msgEl.classList.add('err'); return; } var file = fileInput.files[0]; if (!(file.name || '').toLowerCase().endsWith('.zip')) { msgEl.textContent = '仅支持 .zip 格式'; msgEl.classList.add('err'); return; } var formData = new FormData(); formData.append('version', version); formData.append('file', file); msgEl.textContent = '上传中...'; msgEl.classList.remove('err', 'ok'); fetch('/api/admin/version', { method: 'POST', body: formData }) .then(function (r) { return r.json(); }) .then(function (res) { if (res.success) { msgEl.textContent = '发布成功。版本:' + res.version + ',链接:' + (res.file_url || ''); msgEl.classList.add('ok'); document.getElementById('versionNumber').value = ''; fileInput.value = ''; loadVersions(); } else { msgEl.textContent = res.error || '上传失败'; msgEl.classList.add('err'); } }) .catch(function () { msgEl.textContent = '请求失败'; msgEl.classList.add('err'); }); }; // ========== 栏目权限配置 ========== function loadColumns() { fetch('/api/admin/columns') .then(function (r) { return r.json(); }) .then(function (res) { var tbody = document.getElementById('columnListBody'); if (!res.success) { tbody.innerHTML = '加载失败: ' + (res.error || '') + ''; return; } allColumnsList = res.items || []; if (allColumnsList.length === 0) { tbody.innerHTML = '暂无菜单,请在上方新增'; } else { tbody.innerHTML = allColumnsList.map(function (c) { return '' + c.id + '' + (c.name || '') + '' + (c.column_key || '') + '' + (c.created_at || '') + '' + ' ' + ''; }).join(''); } bindColumnActions(); }) .catch(function () { document.getElementById('columnListBody').innerHTML = '请求失败'; }); } function bindColumnActions() { document.querySelectorAll('[data-column-edit]').forEach(function (btn) { btn.onclick = function () { document.getElementById('editColumnId').value = btn.dataset.columnEdit || ''; document.getElementById('editColumnName').value = (btn.dataset.name || '').replace(/"/g, '"'); document.getElementById('editColumnKey').value = (btn.dataset.key || '').replace(/"/g, '"'); document.getElementById('msgEditColumn').textContent = ''; document.getElementById('editColumnModal').classList.add('show'); }; }); document.querySelectorAll('[data-column-delete]').forEach(function (btn) { btn.onclick = function () { if (!confirm('确定删除菜单“' + (btn.dataset.name || '').replace(/"/g, '"') + '”吗?')) return; fetch('/api/admin/column/' + btn.dataset.columnDelete, { method: 'DELETE' }) .then(function (r) { return r.json(); }) .then(function (res) { if (res.success) { loadColumns(); loadColumnsForPermission(); loadAdminMenus(getActiveAdminTabName()); } else { alert(res.error || '删除失败'); } }); }; }); } document.getElementById('btnAddColumn').onclick = function () { var name = (document.getElementById('columnName').value || '').trim(); var key = (document.getElementById('columnKey').value || '').trim(); var msgEl = document.getElementById('msgColumn'); msgEl.textContent = ''; msgEl.className = 'msg'; if (!name) { msgEl.textContent = '请填写菜单名称'; msgEl.classList.add('err'); return; } if (!key) { msgEl.textContent = '请填写栏目标识'; msgEl.classList.add('err'); return; } fetch('/api/admin/column', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ name: name, column_key: key }) }) .then(function (r) { return r.json(); }) .then(function (res) { if (res.success) { msgEl.textContent = res.msg || '新增成功'; msgEl.classList.add('ok'); document.getElementById('columnName').value = ''; document.getElementById('columnKey').value = ''; loadColumns(); loadColumnsForPermission(); loadAdminMenus(getActiveAdminTabName()); } else { msgEl.textContent = res.error || '新增失败'; msgEl.classList.add('err'); } }) .catch(function () { msgEl.textContent = '请求失败'; msgEl.classList.add('err'); }); }; document.getElementById('btnSaveColumn').onclick = function () { var cid = document.getElementById('editColumnId').value; var name = (document.getElementById('editColumnName').value || '').trim(); var key = (document.getElementById('editColumnKey').value || '').trim(); var msgEl = document.getElementById('msgEditColumn'); msgEl.textContent = ''; msgEl.className = 'msg'; if (!name) { msgEl.textContent = '请填写菜单名称'; msgEl.classList.add('err'); return; } if (!key) { msgEl.textContent = '请填写栏目标识'; msgEl.classList.add('err'); return; } fetch('/api/admin/column/' + cid, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ name: name, column_key: key }) }) .then(function (r) { return r.json(); }) .then(function (res) { if (res.success) { document.getElementById('editColumnModal').classList.remove('show'); loadColumns(); loadColumnsForPermission(); loadAdminMenus(getActiveAdminTabName()); } else { msgEl.textContent = res.error || '保存失败'; msgEl.classList.add('err'); } }); }; document.getElementById('btnCloseEditColumn').onclick = function () { document.getElementById('editColumnModal').classList.remove('show'); }; function loadColumns() { fetch('/api/admin/columns') .then(function (r) { return r.json(); }) .then(function (res) { var tbody = document.getElementById('columnListBody'); if (!res.success) { tbody.innerHTML = '加载失败: ' + (res.error || '') + ''; return; } allColumnsList = res.items || []; if (allColumnsList.length === 0) { tbody.innerHTML = '暂无菜单,请先在上方新增'; } else { tbody.innerHTML = allColumnsList.map(function (c, index) { var moveButtons = ' ' + ' '; return '' + c.id + '' + (c.name || '') + '' + (c.column_key || '') + '' + ((c.menu_type || '') === 'admin' ? '后台(admin)' : '软件(app)') + '' + (c.sort_order != null ? c.sort_order : 0) + '' + (c.route_path || '') + '' + (c.created_at || '') + '' + moveButtons + ' ' + ''; }).join(''); } bindColumnActions(); }) .catch(function () { document.getElementById('columnListBody').innerHTML = '请求失败'; }); } function bindColumnActions() { document.querySelectorAll('[data-column-move-up]').forEach(function (btn) { btn.onclick = function () { if (btn.disabled) return; moveColumnItem(btn.dataset.columnMoveUp, -1, btn); }; }); document.querySelectorAll('[data-column-move-down]').forEach(function (btn) { btn.onclick = function () { if (btn.disabled) return; moveColumnItem(btn.dataset.columnMoveDown, 1, btn); }; }); document.querySelectorAll('[data-column-edit]').forEach(function (btn) { btn.onclick = function () { document.getElementById('editColumnId').value = btn.dataset.columnEdit || ''; document.getElementById('editColumnName').value = (btn.dataset.name || '').replace(/"/g, '"'); document.getElementById('editColumnKey').value = (btn.dataset.key || '').replace(/"/g, '"'); document.getElementById('editColumnRoutePath').value = (btn.dataset.route || '').replace(/"/g, '"'); document.getElementById('editColumnMenuType').value = ((btn.dataset.menuType || 'app').replace(/"/g, '"') || 'app'); document.getElementById('editColumnSortOrder').value = ((btn.dataset.sortOrder || '0').replace(/"/g, '"') || '0'); document.getElementById('msgEditColumn').textContent = ''; document.getElementById('editColumnModal').classList.add('show'); }; }); document.querySelectorAll('[data-column-delete]').forEach(function (btn) { btn.onclick = function () { if (!confirm('确定删除菜单“' + (btn.dataset.name || '').replace(/"/g, '"') + '”吗?')) return; fetch('/api/admin/column/' + btn.dataset.columnDelete, { method: 'DELETE' }) .then(function (r) { return r.json(); }) .then(function (res) { if (res.success) { loadColumns(); loadColumnsForPermission(); loadAdminMenus(getActiveAdminTabName()); } else { alert(res.error || '删除失败'); } }); }; }); } function updateColumnSortOrder(column, sortOrder) { return fetch('/api/admin/column/' + column.id, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ name: column.name || '', column_key: column.column_key || '', route_path: column.route_path || '', menu_type: column.menu_type || 'app', sort_order: sortOrder }) }).then(function (r) { return r.json(); }); } function moveColumnItem(columnId, delta, triggerBtn) { var currentIndex = -1; for (var i = 0; i < allColumnsList.length; i++) { if (String(allColumnsList[i].id) === String(columnId)) { currentIndex = i; break; } } if (currentIndex < 0) return; var targetIndex = currentIndex + delta; if (targetIndex < 0 || targetIndex >= allColumnsList.length) return; var currentItem = allColumnsList[currentIndex]; var targetItem = allColumnsList[targetIndex]; if (!currentItem || !targetItem) return; if (triggerBtn) triggerBtn.disabled = true; var currentSort = currentItem.sort_order != null ? currentItem.sort_order : (currentIndex + 1); var targetSort = targetItem.sort_order != null ? targetItem.sort_order : (targetIndex + 1); Promise.all([ updateColumnSortOrder(currentItem, targetSort), updateColumnSortOrder(targetItem, currentSort) ]).then(function (results) { var failed = (results || []).find(function (res) { return !res || !res.success; }); if (failed) { alert((failed && failed.error) || '排序保存失败'); return; } loadColumns(); loadColumnsForPermission(); loadAdminMenus(getActiveAdminTabName()); }).catch(function () { alert('排序保存失败'); }).finally(function () { if (triggerBtn) triggerBtn.disabled = false; }); } document.getElementById('btnAddColumn').onclick = function () { var name = (document.getElementById('columnName').value || '').trim(); var key = (document.getElementById('columnKey').value || '').trim(); var routePath = (document.getElementById('columnRoutePath').value || '').trim(); var sortOrderValue = (document.getElementById('columnSortOrder').value || '').trim(); var menuType = (document.getElementById('columnMenuType').value || 'admin').trim() || 'admin'; var sortOrder = sortOrderValue === '' ? null : parseInt(sortOrderValue, 10); var msgEl = document.getElementById('msgColumn'); msgEl.textContent = ''; msgEl.className = 'msg'; if (!name) { msgEl.textContent = '请填写菜单名称'; msgEl.classList.add('err'); return; } if (!key) { msgEl.textContent = '请填写栏目标识'; msgEl.classList.add('err'); return; } if (!routePath) { msgEl.textContent = '请填写菜单路由'; msgEl.classList.add('err'); return; } if (sortOrderValue !== '' && isNaN(sortOrder)) { msgEl.textContent = '排序必须是数字'; msgEl.classList.add('err'); return; } fetch('/api/admin/column', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ name: name, column_key: key, route_path: routePath, menu_type: menuType, sort_order: sortOrder }) }) .then(function (r) { return r.json(); }) .then(function (res) { if (res.success) { msgEl.textContent = res.msg || '新增成功'; msgEl.classList.add('ok'); document.getElementById('columnName').value = ''; document.getElementById('columnKey').value = ''; document.getElementById('columnRoutePath').value = ''; document.getElementById('columnSortOrder').value = ''; document.getElementById('columnMenuType').value = 'admin'; loadColumns(); loadColumnsForPermission(); loadAdminMenus(getActiveAdminTabName()); } else { msgEl.textContent = res.error || '新增失败'; msgEl.classList.add('err'); } }) .catch(function () { msgEl.textContent = '请求失败'; msgEl.classList.add('err'); }); }; document.getElementById('btnSaveColumn').onclick = function () { var cid = document.getElementById('editColumnId').value; var name = (document.getElementById('editColumnName').value || '').trim(); var key = (document.getElementById('editColumnKey').value || '').trim(); var routePath = (document.getElementById('editColumnRoutePath').value || '').trim(); var sortOrderValue = (document.getElementById('editColumnSortOrder').value || '').trim(); var menuType = (document.getElementById('editColumnMenuType').value || 'admin').trim() || 'admin'; var sortOrder = sortOrderValue === '' ? null : parseInt(sortOrderValue, 10); var msgEl = document.getElementById('msgEditColumn'); msgEl.textContent = ''; msgEl.className = 'msg'; if (!name) { msgEl.textContent = '请填写菜单名称'; msgEl.classList.add('err'); return; } if (!key) { msgEl.textContent = '请填写栏目标识'; msgEl.classList.add('err'); return; } if (!routePath) { msgEl.textContent = '请填写菜单路由'; msgEl.classList.add('err'); return; } if (sortOrderValue !== '' && isNaN(sortOrder)) { msgEl.textContent = '排序必须是数字'; msgEl.classList.add('err'); return; } fetch('/api/admin/column/' + cid, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ name: name, column_key: key, route_path: routePath, menu_type: menuType, sort_order: sortOrder }) }) .then(function (r) { return r.json(); }) .then(function (res) { if (res.success) { document.getElementById('editColumnModal').classList.remove('show'); loadColumns(); loadColumnsForPermission(); loadAdminMenus(getActiveAdminTabName()); } else { msgEl.textContent = res.error || '保存失败'; msgEl.classList.add('err'); } }); }; // ========== 分页 ========== function renderPagination(elId, total, page, pageSize, onPage) { var el = document.getElementById(elId); if (!el) return; var totalPages = Math.max(1, Math.ceil(total / pageSize)); el.innerHTML = '共' + total + ' 条' + '' + '第' + page + ' / ' + totalPages + ' 页' + ''; el.querySelectorAll('[data-p]').forEach(function (b) { if (!b.disabled) b.onclick = function () { onPage(parseInt(b.dataset.p, 10)); }; }); } // 初始化 function loadAdminCurrentUser() { fetch('/api/admin/current-user') .then(function (r) { if (r.status === 404) { return fetch('/api/auth/check') .then(function (fallbackResp) { return fallbackResp.json(); }) .then(function (fallbackRes) { if (fallbackRes && fallbackRes.logged_in) { return { success: true, item: { username: '当前用户', role: '' } }; } return { success: false }; }); } return r.json(); }) .then(function (res) { var nameEl = document.getElementById('adminCurrentUsername'); var roleEl = document.getElementById('adminCurrentUserRole'); if (!res.success) { nameEl.textContent = '未登录'; roleEl.style.display = 'none'; return; } var item = res.item || {}; nameEl.textContent = item.username || '管理员'; if (item.role) { roleEl.textContent = item.role === 'super_admin' ? '超级管理员' : '管理员'; roleEl.style.display = 'inline-block'; } else { roleEl.style.display = 'none'; } }) .catch(function () { fetch('/api/auth/check') .then(function (r) { return r.json(); }) .then(function (res) { document.getElementById('adminCurrentUsername').textContent = res && res.logged_in ? '当前用户' : '未登录'; document.getElementById('adminCurrentUserRole').style.display = 'none'; }) .catch(function () { document.getElementById('adminCurrentUsername').textContent = '当前用户'; document.getElementById('adminCurrentUserRole').style.display = 'none'; }); }); } document.getElementById('btnAdminLogout').onclick = function () { fetch('/api/admin/logout', { method: 'POST', headers: { 'X-Requested-With': 'XMLHttpRequest' } }) .then(function (r) { if (r.status === 404) { window.location.href = '/logout'; return null; } return r.json(); }) .then(function (res) { if (!res) return; if (!res.success) { alert(res.error || '退出失败'); return; } window.location.href = res.redirect || '/login'; }) .catch(function () { window.location.href = '/logout'; }); }; loadAdminCurrentUser(); loadUserOptions(); loadColumnsForPermission(); loadShopManageGroups(); loadAdminMenus(); }) ();