(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();
}) ();