新需求更新 同步更新
This commit is contained in:
+637
-13
@@ -108,6 +108,7 @@
|
||||
'query-asin': 'panel-query-asin',
|
||||
'product-categories': 'panel-product-categories',
|
||||
'image-video-tasks': 'panel-image-video-tasks',
|
||||
'shop-data-crawl-tasks': 'panel-shop-data-crawl-tasks',
|
||||
'history': 'panel-history',
|
||||
'version': 'panel-version',
|
||||
'digital-human-version': 'panel-digital-human-version'
|
||||
@@ -126,6 +127,7 @@
|
||||
else if (tabName === 'query-asin') loadQueryAsin(1);
|
||||
else if (tabName === 'product-categories') loadProductCategories();
|
||||
else if (tabName === 'image-video-tasks') loadImageVideoTasks(1);
|
||||
else if (tabName === 'shop-data-crawl-tasks') loadShopDataCrawlTasks(1);
|
||||
else if (tabName === 'history') loadHistory(1);
|
||||
else if (tabName === 'version') loadSoftwareVersions();
|
||||
else if (tabName === 'digital-human-version') loadDigitalHumanVersions();
|
||||
@@ -438,7 +440,8 @@
|
||||
.then(function (res) {
|
||||
if (!res.success) return;
|
||||
var availableColumns = (res.items || []).filter(function (item) {
|
||||
if (item.column_key === 'admin_image_video_task_data') return false;
|
||||
if (item.column_key === 'admin_image_video_task_data' ||
|
||||
item.column_key === 'admin_shop_data_crawl_task_data') return false;
|
||||
return true;
|
||||
});
|
||||
if (currentUserRole === 'admin') {
|
||||
@@ -880,6 +883,8 @@
|
||||
function updateImageVideoPermissionAccess() {
|
||||
var button = document.getElementById('btnOpenImageVideoPermissions');
|
||||
if (button) button.style.display = currentUserRole === 'super_admin' ? 'inline-flex' : 'none';
|
||||
var shopButton = document.getElementById('btnOpenShopDataTaskPermissions');
|
||||
if (shopButton) shopButton.style.display = currentUserRole === 'super_admin' ? 'inline-flex' : 'none';
|
||||
}
|
||||
function imageVideoPermissionUsersForView() {
|
||||
if (imageVideoPermissionView === 'granted') {
|
||||
@@ -1386,6 +1391,523 @@
|
||||
}
|
||||
};
|
||||
|
||||
// ========== 店铺数据任务管理 ==========
|
||||
var shopDataTaskPage = 1, shopDataTaskPageSize = 20;
|
||||
var shopDataTaskGroups = [];
|
||||
var shopDataTasks = [];
|
||||
var selectedShopDataResultIds = new Set();
|
||||
var shopDataDownloadInProgress = false;
|
||||
var shopDataPermissionUsers = [];
|
||||
var shopDataPermissionInitialUserIds = new Set();
|
||||
var selectedShopDataPermissionUserIds = new Set();
|
||||
var shopDataPermissionView = 'granted';
|
||||
|
||||
function buildShopDataTaskQuery(page) {
|
||||
var params = new URLSearchParams();
|
||||
params.set('page', String(page || 1));
|
||||
params.set('page_size', String(shopDataTaskPageSize));
|
||||
var values = {
|
||||
shop_name: document.getElementById('shopDataTaskFilterShop').value.trim(),
|
||||
group_name: document.getElementById('shopDataTaskFilterGroup').value.trim(),
|
||||
created_from: document.getElementById('shopDataTaskFilterFrom').value,
|
||||
created_to: document.getElementById('shopDataTaskFilterTo').value
|
||||
};
|
||||
Object.keys(values).forEach(function (key) {
|
||||
if (values[key]) params.set(key, values[key]);
|
||||
});
|
||||
return params.toString();
|
||||
}
|
||||
|
||||
function shopDataResultId(item) {
|
||||
if (!item) return 0;
|
||||
var value = item.result_id != null ? item.result_id : item.resultId;
|
||||
var id = Number(value);
|
||||
return isFinite(id) && id > 0 ? id : 0;
|
||||
}
|
||||
|
||||
function shopDataBoolean(value) {
|
||||
if (typeof value === 'string') {
|
||||
return ['1', 'true', 'yes', 'y'].indexOf(value.toLowerCase()) >= 0;
|
||||
}
|
||||
return !!value;
|
||||
}
|
||||
|
||||
function shopDataDateValue(value) {
|
||||
if (!value) return 0;
|
||||
var timestamp = Date.parse(String(value).replace(' ', 'T'));
|
||||
return isNaN(timestamp) ? 0 : timestamp;
|
||||
}
|
||||
|
||||
function shopDataResultSort(a, b) {
|
||||
var dateDiff = shopDataDateValue(b.created_at || b.finished_at) - shopDataDateValue(a.created_at || a.finished_at);
|
||||
if (dateDiff) return dateDiff;
|
||||
return shopDataResultId(b) - shopDataResultId(a);
|
||||
}
|
||||
|
||||
function shopDataGroupKey(item) {
|
||||
var name = String((item && (item.shop_name || item.shop || item.source_filename)) || '').trim();
|
||||
// Keep the same trimmed, case-insensitive key as the Flask grouping query.
|
||||
return 'name:' + name.toLowerCase();
|
||||
}
|
||||
|
||||
function shopDataNormalizeResult(group, raw) {
|
||||
var result = {};
|
||||
Object.keys(group || {}).forEach(function (key) {
|
||||
if (key !== 'results' && key !== 'group_results') result[key] = group[key];
|
||||
});
|
||||
Object.keys(raw || {}).forEach(function (key) {
|
||||
if (key !== 'results' && key !== 'group_results') result[key] = raw[key];
|
||||
});
|
||||
result.shop_name = result.shop_name || result.shop || result.source_filename || '';
|
||||
result.shop_id = result.shop_id || result.shopId || result.source_file_url || '';
|
||||
result.group_name = result.group_name || result.group || '';
|
||||
result.status = result.status || result.task_status || result.file_status || '';
|
||||
result.error = result.error || result.result_error || result.error_message || result.task_error || result.file_error || '';
|
||||
result.output_filename = result.output_filename || result.result_filename || result.filename || '';
|
||||
result.country_codes = result.country_codes || result.countryCodes || [];
|
||||
if (result.file_size == null) result.file_size = result.result_file_size;
|
||||
if (result.row_count == null) result.row_count = result.rows;
|
||||
if (result.finished_at == null) result.finished_at = result.completed_at;
|
||||
if (result.result_id == null && raw && raw.resultId != null) result.result_id = raw.resultId;
|
||||
if (result.result_id == null && raw && raw.id != null && !Array.isArray(raw.results) && !Array.isArray(raw.group_results)) result.result_id = raw.id;
|
||||
if (result.file_ready == null) {
|
||||
result.file_ready = !!String(result.result_file_url || result.resultFileUrl || '').trim();
|
||||
} else {
|
||||
result.file_ready = shopDataBoolean(result.file_ready);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// The admin API now returns one group per shop. Keep a flat result list
|
||||
// for selection/actions while rendering the grouped view.
|
||||
function normalizeShopDataTaskGroups(items) {
|
||||
var groups = [];
|
||||
var byKey = Object.create(null);
|
||||
(Array.isArray(items) ? items : []).forEach(function (rawGroup) {
|
||||
if (!rawGroup || typeof rawGroup !== 'object') return;
|
||||
var rawResults = Array.isArray(rawGroup.results)
|
||||
? rawGroup.results
|
||||
: (Array.isArray(rawGroup.group_results) ? rawGroup.group_results : [rawGroup]);
|
||||
var groupBase = {};
|
||||
Object.keys(rawGroup).forEach(function (key) {
|
||||
if (key !== 'results' && key !== 'group_results') groupBase[key] = rawGroup[key];
|
||||
});
|
||||
if (!groupBase.shop_name && rawResults.length) {
|
||||
groupBase.shop_name = rawResults[0].shop_name || rawResults[0].shop || rawResults[0].source_filename || '';
|
||||
}
|
||||
if (!groupBase.shop_id && rawResults.length) {
|
||||
groupBase.shop_id = rawResults[0].shop_id || rawResults[0].shopId || rawResults[0].source_file_url || '';
|
||||
}
|
||||
var key = shopDataGroupKey(groupBase);
|
||||
var group = byKey[key];
|
||||
if (!group) {
|
||||
group = {
|
||||
key: key,
|
||||
shop_name: groupBase.shop_name || '',
|
||||
shop_id: groupBase.shop_id || '',
|
||||
group_name: groupBase.group_name || groupBase.group || '',
|
||||
latest_created_at: groupBase.latest_created_at || '',
|
||||
results: []
|
||||
};
|
||||
byKey[key] = group;
|
||||
groups.push(group);
|
||||
}
|
||||
rawResults.forEach(function (rawResult) {
|
||||
if (!rawResult || typeof rawResult !== 'object') return;
|
||||
var result = shopDataNormalizeResult(groupBase, rawResult);
|
||||
var resultId = shopDataResultId(result);
|
||||
if (!resultId || !result.file_ready) return;
|
||||
if (resultId && group.results.some(function (existing) { return shopDataResultId(existing) === resultId; })) return;
|
||||
group.results.push(result);
|
||||
if (!group.shop_name) group.shop_name = result.shop_name || '';
|
||||
if (!group.shop_id) group.shop_id = result.shop_id || '';
|
||||
if (!group.group_name) group.group_name = result.group_name || '';
|
||||
});
|
||||
});
|
||||
groups.forEach(function (group) {
|
||||
group.results.sort(shopDataResultSort);
|
||||
group.results = group.results.slice(0, 3);
|
||||
if (!group.latest_created_at && group.results.length) {
|
||||
group.latest_created_at = group.results[0].created_at || group.results[0].finished_at || '';
|
||||
}
|
||||
});
|
||||
return groups.filter(function (group) { return group.results.length > 0; });
|
||||
}
|
||||
|
||||
function shopDataDeleteIcon() {
|
||||
return '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M3 6h18"></path><path d="M8 6V4h8v2"></path><path d="M19 6l-1 15H6L5 6"></path><path d="M10 11v6m4-6v6"></path></svg>';
|
||||
}
|
||||
|
||||
function renderShopDataStatus(item, status) {
|
||||
var normalized = String(status || '-').toUpperCase();
|
||||
var errorTitle = item && item.error ? ' title="' + escapeHtml(item.error) + '"' : '';
|
||||
return '<span class="image-video-status ' + escapeHtml(normalized) + '"' + errorTitle + '>' + escapeHtml(normalized) + '</span>';
|
||||
}
|
||||
|
||||
function renderShopDataTaskResult(item) {
|
||||
var resultId = shopDataResultId(item);
|
||||
var selected = resultId > 0 && selectedShopDataResultIds.has(resultId);
|
||||
var status = String(item.status || item.file_status || '').toUpperCase();
|
||||
var terminal = ['SUCCESS', 'FAILED', 'CANCELLED'].indexOf(status) >= 0;
|
||||
var countryCodes = item.country_codes != null ? item.country_codes : item.countryCodes;
|
||||
var countries = Array.isArray(countryCodes) ? countryCodes.join('、') : (String(countryCodes || '') || '-');
|
||||
var filename = item.output_filename || '-';
|
||||
var checkbox = '<input type="checkbox" data-shop-data-select="' + (resultId || '') + '"' +
|
||||
(selected ? ' checked' : '') + (item.file_ready && resultId > 0 ? '' : ' disabled') + '>';
|
||||
return '<div class="shop-data-result' + (selected ? ' selected' : '') + '" data-shop-data-card="' + (resultId || '') + '">' +
|
||||
'<div class="shop-data-result-head">' +
|
||||
'<label class="shop-data-task-title">' + checkbox +
|
||||
'<span title="任务 #' + escapeHtml(item.task_id || item.task_no || '-') + '">任务 #' + escapeHtml(item.task_id || item.task_no || '-') + '</span>' +
|
||||
'</label>' +
|
||||
renderShopDataStatus(item, status || item.file_status) +
|
||||
'</div>' +
|
||||
'<div class="image-video-card-info">' +
|
||||
'<div class="image-video-info-row"><label>国家</label><span>' + escapeHtml(countries) + '</span></div>' +
|
||||
'<div class="image-video-info-row"><label>文件</label><span title="' + escapeHtml(filename) + '">' + escapeHtml(filename) + '</span></div>' +
|
||||
'</div>' +
|
||||
'<div class="image-video-card-actions">' +
|
||||
'<button class="image-video-card-action" type="button" data-shop-data-download="' + (resultId || '') + '"' + (item.file_ready && resultId > 0 ? '' : ' disabled') + '>' + imageVideoDownloadIcon() + '下载文件</button>' +
|
||||
'<button class="image-video-card-action shop-data-delete-action" type="button" data-shop-data-delete="' + (resultId || '') + '"' + (terminal && resultId > 0 ? '' : ' disabled') + '>' + shopDataDeleteIcon() + '删除</button>' +
|
||||
'</div>' +
|
||||
'</div>';
|
||||
}
|
||||
|
||||
function renderShopDataTaskCard(group) {
|
||||
var results = Array.isArray(group.results) ? group.results : [];
|
||||
var latest = group.latest_created_at || (results[0] && (results[0].created_at || results[0].finished_at)) || '-';
|
||||
return '<article class="image-video-card shop-data-task-card" data-shop-data-group="' + escapeHtml(group.key || '') + '">' +
|
||||
'<div class="image-video-card-body">' +
|
||||
'<div class="image-video-card-head shop-data-group-head">' +
|
||||
'<div class="shop-data-task-title"><span title="' + escapeHtml(group.shop_name || '-') + '">' + escapeHtml(group.shop_name || '-') + '</span></div>' +
|
||||
'<span class="shop-data-group-meta">' + results.length + '/3 份结果</span>' +
|
||||
'</div>' +
|
||||
'<div class="image-video-card-info">' +
|
||||
'<div class="image-video-info-row"><label>分组</label><span>' + escapeHtml(group.group_name || '-') + '</span></div>' +
|
||||
'<div class="image-video-info-row"><label>最新</label><span>' + escapeHtml(latest) + '</span></div>' +
|
||||
'</div>' +
|
||||
'<div class="shop-data-result-list">' +
|
||||
(results.length ? results.map(renderShopDataTaskResult).join('') : '<div class="image-video-empty">暂无结果</div>') +
|
||||
'</div>' +
|
||||
'</div>' +
|
||||
'</article>';
|
||||
}
|
||||
|
||||
function syncShopDataSelectionUi() {
|
||||
document.querySelectorAll('[data-shop-data-card]').forEach(function (card) {
|
||||
var resultId = Number(card.dataset.shopDataCard);
|
||||
var selected = selectedShopDataResultIds.has(resultId);
|
||||
card.classList.toggle('selected', selected);
|
||||
var checkbox = card.querySelector('[data-shop-data-select]');
|
||||
if (checkbox) checkbox.checked = selected && !checkbox.disabled;
|
||||
});
|
||||
var selectable = shopDataTasks.filter(function (item) { return !!item.file_ready && shopDataResultId(item) > 0; });
|
||||
var selectedCount = selectable.filter(function (item) {
|
||||
return selectedShopDataResultIds.has(shopDataResultId(item));
|
||||
}).length;
|
||||
var selectAll = document.getElementById('shopDataTaskSelectAll');
|
||||
selectAll.checked = selectable.length > 0 && selectedCount === selectable.length;
|
||||
selectAll.indeterminate = selectedCount > 0 && selectedCount < selectable.length;
|
||||
selectAll.disabled = shopDataDownloadInProgress || selectable.length === 0;
|
||||
var batch = document.getElementById('btnBatchDownloadShopDataTasks');
|
||||
batch.disabled = shopDataDownloadInProgress || selectedCount === 0;
|
||||
batch.innerHTML = imageVideoDownloadIcon() + (shopDataDownloadInProgress
|
||||
? '处理中'
|
||||
: '批量下载' + (selectedCount ? ' (' + selectedCount + ')' : ''));
|
||||
}
|
||||
|
||||
function renderShopDataTasks() {
|
||||
var grid = document.getElementById('shopDataTaskGrid');
|
||||
grid.innerHTML = shopDataTaskGroups.length
|
||||
? shopDataTaskGroups.map(renderShopDataTaskCard).join('')
|
||||
: '<div class="image-video-empty">暂无符合条件的店铺数据任务</div>';
|
||||
syncShopDataSelectionUi();
|
||||
}
|
||||
|
||||
function loadShopDataCrawlTasks(page) {
|
||||
shopDataTaskPage = page || 1;
|
||||
selectedShopDataResultIds.clear();
|
||||
var grid = document.getElementById('shopDataTaskGrid');
|
||||
grid.innerHTML = '<div class="image-video-empty">加载中...</div>';
|
||||
document.getElementById('shopDataTaskDownloadProgress').textContent = '';
|
||||
fetch('/api/admin/shop-data-crawl-tasks?' + buildShopDataTaskQuery(shopDataTaskPage))
|
||||
.then(function (response) { return response.json(); })
|
||||
.then(function (res) {
|
||||
if (!res.success) throw new Error(res.error || '加载失败');
|
||||
var payload = res.data && typeof res.data === 'object' && !Array.isArray(res.data) ? res.data : res;
|
||||
shopDataTaskGroups = normalizeShopDataTaskGroups(payload.items || []);
|
||||
shopDataTasks = shopDataTaskGroups.reduce(function (all, group) {
|
||||
return all.concat(group.results || []);
|
||||
}, []);
|
||||
var total = payload.total != null ? Number(payload.total) : shopDataTaskGroups.length;
|
||||
var responsePage = payload.page || page;
|
||||
var responsePageSize = payload.page_size || shopDataTaskPageSize;
|
||||
document.getElementById('shopDataTaskTotal').textContent = '共 ' + (total || 0) + ' 家店铺 · 每家店铺保留最新 3 份任务结果';
|
||||
renderShopDataTasks();
|
||||
renderPagination('shopDataTaskPagination', total, responsePage, responsePageSize, loadShopDataCrawlTasks);
|
||||
})
|
||||
.catch(function (error) {
|
||||
shopDataTaskGroups = [];
|
||||
shopDataTasks = [];
|
||||
grid.innerHTML = '<div class="image-video-empty">加载失败:' + escapeHtml(error.message || '') + '</div>';
|
||||
document.getElementById('shopDataTaskTotal').textContent = '';
|
||||
syncShopDataSelectionUi();
|
||||
});
|
||||
}
|
||||
|
||||
function downloadShopDataTask(item) {
|
||||
var resultId = shopDataResultId(item);
|
||||
if (!item || !item.file_ready || !resultId) return;
|
||||
var filename = item.output_filename || ('shop-data-task-' + resultId + '.xlsx');
|
||||
triggerImageVideoLink('/api/admin/shop-data-crawl-tasks/' + resultId + '/download', filename, false);
|
||||
}
|
||||
|
||||
function deleteShopDataTask(item) {
|
||||
var resultId = shopDataResultId(item);
|
||||
if (!item || !resultId || ['SUCCESS', 'FAILED', 'CANCELLED'].indexOf(String(item.status || item.file_status || '').toUpperCase()) < 0) return;
|
||||
if (!window.confirm('确认删除店铺“' + (item.shop_name || '-') + '”的任务 #' + item.task_id + ' 及结果文件?')) return;
|
||||
var progress = document.getElementById('shopDataTaskDownloadProgress');
|
||||
progress.textContent = '正在删除任务 #' + item.task_id + '...';
|
||||
fetch('/api/admin/shop-data-crawl-tasks/' + resultId, { method: 'DELETE' })
|
||||
.then(function (response) { return response.json().then(function (data) { return { ok: response.ok, data: data }; }); })
|
||||
.then(function (result) {
|
||||
if (!result.ok || !result.data.success) throw new Error(result.data.error || '删除失败');
|
||||
progress.textContent = result.data.msg || '删除成功';
|
||||
loadShopDataCrawlTasks(shopDataTaskPage);
|
||||
})
|
||||
.catch(function (error) {
|
||||
progress.textContent = error.message || '删除失败';
|
||||
});
|
||||
}
|
||||
|
||||
function downloadShopDataTasksZip() {
|
||||
var resultIds = Array.from(selectedShopDataResultIds);
|
||||
if (shopDataDownloadInProgress || !resultIds.length) return;
|
||||
shopDataDownloadInProgress = true;
|
||||
syncShopDataSelectionUi();
|
||||
var progress = document.getElementById('shopDataTaskDownloadProgress');
|
||||
progress.textContent = '正在打包 ' + resultIds.length + ' 个文件...';
|
||||
fetch('/api/admin/shop-data-crawl-tasks/download-zip', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ result_ids: resultIds }),
|
||||
__skipLoading: true
|
||||
}).then(function (response) {
|
||||
if (!response.ok) {
|
||||
return response.json().catch(function () { return {}; }).then(function (data) {
|
||||
throw new Error(data.error || '压缩包生成失败');
|
||||
});
|
||||
}
|
||||
var filename = imageVideoZipFilename(response);
|
||||
var errorCount = Number(response.headers.get('X-Archive-Error-Count') || 0);
|
||||
return response.blob().then(function (blob) {
|
||||
return { blob: blob, filename: filename, errorCount: errorCount };
|
||||
});
|
||||
}).then(function (result) {
|
||||
var objectUrl = URL.createObjectURL(result.blob);
|
||||
triggerImageVideoLink(objectUrl, result.filename, false);
|
||||
setTimeout(function () { URL.revokeObjectURL(objectUrl); }, 1000);
|
||||
progress.textContent = result.errorCount
|
||||
? '压缩包已下载,' + result.errorCount + ' 个文件失败,详见包内清单'
|
||||
: '压缩包下载已开始';
|
||||
}).catch(function (error) {
|
||||
progress.textContent = error.message || '批量下载失败';
|
||||
}).finally(function () {
|
||||
shopDataDownloadInProgress = false;
|
||||
syncShopDataSelectionUi();
|
||||
});
|
||||
}
|
||||
|
||||
function shopDataPermissionUsersForView() {
|
||||
return shopDataPermissionView === 'granted'
|
||||
? shopDataPermissionUsers.filter(function (user) {
|
||||
return shopDataPermissionInitialUserIds.has(Number(user.id));
|
||||
})
|
||||
: shopDataPermissionUsers;
|
||||
}
|
||||
|
||||
function filteredShopDataPermissionUsers() {
|
||||
var users = shopDataPermissionUsersForView();
|
||||
var keyword = (document.getElementById('shopDataTaskPermissionSearch').value || '').trim().toLowerCase();
|
||||
return keyword ? users.filter(function (user) {
|
||||
return String(user.username || '').toLowerCase().indexOf(keyword) >= 0;
|
||||
}) : users;
|
||||
}
|
||||
|
||||
function renderShopDataPermissionUsers() {
|
||||
var visibleUsers = filteredShopDataPermissionUsers();
|
||||
document.getElementById('shopDataTaskPermissionGrantedCount').textContent = '(' + shopDataPermissionInitialUserIds.size + ')';
|
||||
document.getElementById('shopDataTaskPermissionAllCount').textContent = '(' + shopDataPermissionUsers.length + ')';
|
||||
document.querySelectorAll('[data-shop-data-permission-view]').forEach(function (tab) {
|
||||
var active = tab.dataset.shopDataPermissionView === shopDataPermissionView;
|
||||
tab.classList.toggle('active', active);
|
||||
tab.setAttribute('aria-selected', active ? 'true' : 'false');
|
||||
});
|
||||
var pendingCount = shopDataPermissionUsers.filter(function (user) {
|
||||
var userId = Number(user.id);
|
||||
return shopDataPermissionInitialUserIds.has(userId) !== selectedShopDataPermissionUserIds.has(userId);
|
||||
}).length;
|
||||
document.getElementById('shopDataTaskPermissionSummary').textContent =
|
||||
(shopDataPermissionView === 'granted' ? '当前显示已分配用户,共 ' + visibleUsers.length + ' 人' : '当前显示全部用户,已分配 ' + shopDataPermissionInitialUserIds.size + ' 人') +
|
||||
(pendingCount ? ' · 待保存变更 ' + pendingCount + ' 项' : '');
|
||||
document.getElementById('shopDataTaskPermissionList').innerHTML = visibleUsers.length
|
||||
? visibleUsers.map(function (user) {
|
||||
var userId = Number(user.id);
|
||||
var saved = shopDataPermissionInitialUserIds.has(userId);
|
||||
var selected = selectedShopDataPermissionUserIds.has(userId);
|
||||
var changed = saved !== selected;
|
||||
return '<div class="image-video-permission-row">' +
|
||||
'<input type="checkbox" data-shop-data-permission-user="' + userId + '"' + (selected ? ' checked' : '') + '>' +
|
||||
'<span class="image-video-permission-user"><span class="image-video-permission-name">' + escapeHtml(user.username || '-') + '</span><span class="image-video-permission-role">' + escapeHtml(roleLabel(user.role)) + '</span></span>' +
|
||||
'<button class="image-video-permission-state' + (changed ? ' pending' : (saved ? ' granted' : '')) + '" type="button" data-shop-data-permission-toggle="' + userId + '">' +
|
||||
(changed ? (selected ? '待保存分配' : '待保存取消') : (saved ? '取消分配' : '分配')) +
|
||||
'</button></div>';
|
||||
}).join('')
|
||||
: '<div class="image-video-permission-empty">暂无匹配用户</div>';
|
||||
var selectedVisibleCount = visibleUsers.filter(function (user) {
|
||||
return selectedShopDataPermissionUserIds.has(Number(user.id));
|
||||
}).length;
|
||||
var selectAll = document.getElementById('shopDataTaskPermissionSelectAll');
|
||||
selectAll.checked = visibleUsers.length > 0 && selectedVisibleCount === visibleUsers.length;
|
||||
selectAll.indeterminate = selectedVisibleCount > 0 && selectedVisibleCount < visibleUsers.length;
|
||||
selectAll.disabled = visibleUsers.length === 0;
|
||||
}
|
||||
|
||||
function openShopDataTaskPermissions() {
|
||||
if (currentUserRole !== 'super_admin') return;
|
||||
var modal = document.getElementById('shopDataTaskPermissionModal');
|
||||
var saveButton = document.getElementById('btnSaveShopDataTaskPermissions');
|
||||
var permissionLoaded = false;
|
||||
modal.classList.add('show');
|
||||
shopDataPermissionView = 'granted';
|
||||
document.getElementById('shopDataTaskPermissionSearch').value = '';
|
||||
document.getElementById('shopDataTaskPermissionMessage').textContent = '';
|
||||
document.getElementById('shopDataTaskPermissionList').innerHTML = '<div class="image-video-permission-empty">加载中...</div>';
|
||||
saveButton.disabled = true;
|
||||
fetch('/api/admin/shop-data-crawl-task-permissions')
|
||||
.then(function (response) { return response.json(); })
|
||||
.then(function (res) {
|
||||
if (!res.success) throw new Error(res.error || '权限加载失败');
|
||||
shopDataPermissionUsers = res.items || [];
|
||||
shopDataPermissionInitialUserIds = new Set(shopDataPermissionUsers.filter(function (user) { return !!user.granted; }).map(function (user) { return Number(user.id); }));
|
||||
selectedShopDataPermissionUserIds = new Set(shopDataPermissionInitialUserIds);
|
||||
permissionLoaded = true;
|
||||
renderShopDataPermissionUsers();
|
||||
}).catch(function (error) {
|
||||
shopDataPermissionUsers = [];
|
||||
shopDataPermissionInitialUserIds = new Set();
|
||||
selectedShopDataPermissionUserIds = new Set();
|
||||
document.getElementById('shopDataTaskPermissionList').innerHTML = '<div class="image-video-permission-empty">' + escapeHtml(error.message || '权限加载失败') + '</div>';
|
||||
document.getElementById('shopDataTaskPermissionMessage').textContent = '权限加载失败,请关闭后重试';
|
||||
}).finally(function () {
|
||||
saveButton.disabled = !permissionLoaded;
|
||||
});
|
||||
}
|
||||
|
||||
function closeShopDataTaskPermissions() {
|
||||
document.getElementById('shopDataTaskPermissionModal').classList.remove('show');
|
||||
}
|
||||
|
||||
function saveShopDataTaskPermissions() {
|
||||
var message = document.getElementById('shopDataTaskPermissionMessage');
|
||||
message.textContent = '保存中...';
|
||||
message.className = 'msg';
|
||||
document.getElementById('btnSaveShopDataTaskPermissions').disabled = true;
|
||||
fetch('/api/admin/shop-data-crawl-task-permissions', {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ user_ids: Array.from(selectedShopDataPermissionUserIds).sort(function (a, b) { return a - b; }) })
|
||||
}).then(function (response) { return response.json(); })
|
||||
.then(function (res) {
|
||||
if (!res.success) throw new Error(res.error || '权限保存失败');
|
||||
shopDataPermissionInitialUserIds = new Set(selectedShopDataPermissionUserIds);
|
||||
renderShopDataPermissionUsers();
|
||||
message.textContent = res.msg || '保存成功';
|
||||
message.className = 'msg ok';
|
||||
}).catch(function (error) {
|
||||
message.textContent = error.message || '权限保存失败';
|
||||
message.className = 'msg err';
|
||||
}).finally(function () {
|
||||
document.getElementById('btnSaveShopDataTaskPermissions').disabled = false;
|
||||
});
|
||||
}
|
||||
|
||||
document.getElementById('btnFilterShopDataTasks').onclick = function () { loadShopDataCrawlTasks(1); };
|
||||
document.getElementById('btnResetShopDataTasks').onclick = function () {
|
||||
['shopDataTaskFilterShop', 'shopDataTaskFilterGroup', 'shopDataTaskFilterFrom', 'shopDataTaskFilterTo']
|
||||
.forEach(function (id) { document.getElementById(id).value = ''; });
|
||||
loadShopDataCrawlTasks(1);
|
||||
};
|
||||
document.getElementById('shopDataTaskSelectAll').onchange = function (event) {
|
||||
shopDataTasks.forEach(function (item) {
|
||||
var resultId = shopDataResultId(item);
|
||||
if (!item.file_ready || !resultId) return;
|
||||
if (event.target.checked) selectedShopDataResultIds.add(resultId);
|
||||
else selectedShopDataResultIds.delete(resultId);
|
||||
});
|
||||
syncShopDataSelectionUi();
|
||||
};
|
||||
document.getElementById('btnBatchDownloadShopDataTasks').onclick = downloadShopDataTasksZip;
|
||||
document.getElementById('shopDataTaskGrid').onchange = function (event) {
|
||||
var checkbox = event.target.closest('[data-shop-data-select]');
|
||||
if (!checkbox) return;
|
||||
var resultId = Number(checkbox.dataset.shopDataSelect);
|
||||
if (!resultId) return;
|
||||
if (checkbox.checked) selectedShopDataResultIds.add(resultId);
|
||||
else selectedShopDataResultIds.delete(resultId);
|
||||
syncShopDataSelectionUi();
|
||||
};
|
||||
document.getElementById('shopDataTaskGrid').onclick = function (event) {
|
||||
var downloadButton = event.target.closest('[data-shop-data-download]');
|
||||
if (downloadButton) {
|
||||
var downloadItem = shopDataTasks.find(function (task) { return shopDataResultId(task) === Number(downloadButton.dataset.shopDataDownload); });
|
||||
downloadShopDataTask(downloadItem);
|
||||
return;
|
||||
}
|
||||
var deleteButton = event.target.closest('[data-shop-data-delete]');
|
||||
if (deleteButton) {
|
||||
var deleteItem = shopDataTasks.find(function (task) { return shopDataResultId(task) === Number(deleteButton.dataset.shopDataDelete); });
|
||||
deleteShopDataTask(deleteItem);
|
||||
}
|
||||
};
|
||||
document.getElementById('btnOpenShopDataTaskPermissions').onclick = openShopDataTaskPermissions;
|
||||
document.getElementById('btnCloseShopDataTaskPermissions').onclick = closeShopDataTaskPermissions;
|
||||
document.getElementById('btnCancelShopDataTaskPermissions').onclick = closeShopDataTaskPermissions;
|
||||
document.getElementById('btnSaveShopDataTaskPermissions').onclick = saveShopDataTaskPermissions;
|
||||
document.querySelectorAll('[data-shop-data-permission-view]').forEach(function (tab) {
|
||||
tab.onclick = function () {
|
||||
shopDataPermissionView = tab.dataset.shopDataPermissionView || 'granted';
|
||||
document.getElementById('shopDataTaskPermissionSearch').value = '';
|
||||
renderShopDataPermissionUsers();
|
||||
};
|
||||
});
|
||||
document.getElementById('shopDataTaskPermissionSearch').oninput = renderShopDataPermissionUsers;
|
||||
document.getElementById('shopDataTaskPermissionSelectAll').onchange = function (event) {
|
||||
filteredShopDataPermissionUsers().forEach(function (user) {
|
||||
if (event.target.checked) selectedShopDataPermissionUserIds.add(Number(user.id));
|
||||
else selectedShopDataPermissionUserIds.delete(Number(user.id));
|
||||
});
|
||||
renderShopDataPermissionUsers();
|
||||
};
|
||||
document.getElementById('shopDataTaskPermissionList').onclick = function (event) {
|
||||
var button = event.target.closest('[data-shop-data-permission-toggle]');
|
||||
if (!button) return;
|
||||
var userId = Number(button.dataset.shopDataPermissionToggle);
|
||||
if (selectedShopDataPermissionUserIds.has(userId)) selectedShopDataPermissionUserIds.delete(userId);
|
||||
else selectedShopDataPermissionUserIds.add(userId);
|
||||
renderShopDataPermissionUsers();
|
||||
};
|
||||
document.getElementById('shopDataTaskPermissionList').onchange = function (event) {
|
||||
var checkbox = event.target.closest('[data-shop-data-permission-user]');
|
||||
if (!checkbox) return;
|
||||
var userId = Number(checkbox.dataset.shopDataPermissionUser);
|
||||
if (checkbox.checked) selectedShopDataPermissionUserIds.add(userId);
|
||||
else selectedShopDataPermissionUserIds.delete(userId);
|
||||
renderShopDataPermissionUsers();
|
||||
};
|
||||
document.getElementById('shopDataTaskPermissionModal').onclick = function (event) {
|
||||
if (event.target === event.currentTarget) closeShopDataTaskPermissions();
|
||||
};
|
||||
|
||||
var historyPage = 1, historyPageSize = 15;
|
||||
function toSqlDatetime(val) {
|
||||
if (!val) return '';
|
||||
@@ -1636,15 +2158,33 @@
|
||||
|
||||
// ========== 数据去重总数据 ==========
|
||||
var dedupeTotalDataPage = 1, dedupeTotalDataPageSize = 15;
|
||||
function getDedupeTotalDataDateRange() {
|
||||
return {
|
||||
startDate: document.getElementById('exportDedupeTotalDataStartDate').value || '',
|
||||
endDate: document.getElementById('exportDedupeTotalDataEndDate').value || ''
|
||||
};
|
||||
}
|
||||
function validateDedupeTotalDataDateRange() {
|
||||
var dateRange = getDedupeTotalDataDateRange();
|
||||
if (dateRange.startDate && dateRange.endDate && dateRange.startDate > dateRange.endDate) {
|
||||
alert('开始日期不能晚于结束日期');
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
function buildDedupeTotalDataQuery(page) {
|
||||
var q = 'page=' + (page || 1) + '&page_size=' + dedupeTotalDataPageSize;
|
||||
var keyword = (document.getElementById('searchDedupeTotalData').value || '').trim();
|
||||
var username = (document.getElementById('searchDedupeTotalDataUsername').value || '').trim();
|
||||
var dateRange = getDedupeTotalDataDateRange();
|
||||
if (keyword) q += '&keyword=' + encodeURIComponent(keyword);
|
||||
if (username) q += '&username=' + encodeURIComponent(username);
|
||||
if (dateRange.startDate) q += '&start_date=' + encodeURIComponent(dateRange.startDate);
|
||||
if (dateRange.endDate) q += '&end_date=' + encodeURIComponent(dateRange.endDate);
|
||||
return q;
|
||||
}
|
||||
function loadDedupeTotalData(page) {
|
||||
if (!validateDedupeTotalDataDateRange()) return;
|
||||
dedupeTotalDataPage = page || 1;
|
||||
fetch('/api/admin/dedupe-total-data?' + buildDedupeTotalDataQuery(dedupeTotalDataPage))
|
||||
.then(function (r) { return r.json(); })
|
||||
@@ -1698,16 +2238,12 @@
|
||||
document.getElementById('btnSearchDedupeTotalData').onclick = function () { loadDedupeTotalData(1); };
|
||||
document.getElementById('btnExportDedupeTotalData').onclick = function () {
|
||||
var username = (document.getElementById('searchDedupeTotalDataUsername').value || '').trim();
|
||||
var startDate = document.getElementById('exportDedupeTotalDataStartDate').value || '';
|
||||
var endDate = document.getElementById('exportDedupeTotalDataEndDate').value || '';
|
||||
if (startDate && endDate && startDate > endDate) {
|
||||
alert('开始日期不能晚于结束日期');
|
||||
return;
|
||||
}
|
||||
var dateRange = getDedupeTotalDataDateRange();
|
||||
if (!validateDedupeTotalDataDateRange()) return;
|
||||
var params = [];
|
||||
if (username) params.push('username=' + encodeURIComponent(username));
|
||||
if (startDate) params.push('start_date=' + encodeURIComponent(startDate));
|
||||
if (endDate) params.push('end_date=' + encodeURIComponent(endDate));
|
||||
if (dateRange.startDate) params.push('start_date=' + encodeURIComponent(dateRange.startDate));
|
||||
if (dateRange.endDate) params.push('end_date=' + encodeURIComponent(dateRange.endDate));
|
||||
fetch('/api/admin/dedupe-total-data/export' + (params.length ? ('?' + params.join('&')) : ''))
|
||||
.then(function (response) {
|
||||
var contentType = response.headers.get('content-type') || '';
|
||||
@@ -2112,6 +2648,20 @@
|
||||
function buildShopKeyQuery(page) {
|
||||
return 'page=' + (page || 1) + '&page_size=' + shopKeyPageSize;
|
||||
}
|
||||
function renderShopKeyWhitelistStatus(item) {
|
||||
var status = String(item.ip_whitelist_status || 'UNKNOWN').toUpperCase();
|
||||
var statusMeta = {
|
||||
ALLOWED: { label: '正常', className: 'is-allowed' },
|
||||
BLOCKED: { label: '未加白名单', className: 'is-blocked' },
|
||||
UNKNOWN: { label: '未检测', className: 'is-unknown' }
|
||||
};
|
||||
var meta = statusMeta[status] || statusMeta.UNKNOWN;
|
||||
var details = [];
|
||||
if (item.ip_whitelist_checked_at) details.push('检测时间:' + item.ip_whitelist_checked_at);
|
||||
if (item.ip_whitelist_message) details.push(item.ip_whitelist_message);
|
||||
return '<span class="shop-key-whitelist-status ' + meta.className + '" title="' +
|
||||
escapeHtml(details.join('\n')) + '">' + escapeHtml(meta.label) + '</span>';
|
||||
}
|
||||
function loadShopKeys(page) {
|
||||
shopKeyPage = page || 1;
|
||||
fetch('/api/admin/shop-keys?' + buildShopKeyQuery(shopKeyPage))
|
||||
@@ -2124,11 +2674,11 @@
|
||||
}
|
||||
var items = res.items || [];
|
||||
if (items.length === 0) {
|
||||
tbody.innerHTML = '<tr><td colspan="7" class="empty-tip">暂无店铺密钥</td></tr>';
|
||||
tbody.innerHTML = '<tr><td colspan="8" class="empty-tip">暂无店铺密钥</td></tr>';
|
||||
} else {
|
||||
tbody.innerHTML = items.map(function (item, index) {
|
||||
var rowNo = (shopKeyPage - 1) * shopKeyPageSize + index + 1;
|
||||
return '<tr><td>' + rowNo + '</td><td>' + (item.remark_name || '') + '</td><td>' + (item.ziniao_account_name || '') + '</td><td>' + (item.ziniao_token || '') + '</td><td>' + (item.created_at || '') + '</td><td>' + (item.updated_at || '') + '</td><td>' +
|
||||
return '<tr><td>' + rowNo + '</td><td>' + escapeHtml(item.remark_name || '') + '</td><td>' + escapeHtml(item.ziniao_account_name || '') + '</td><td>' + escapeHtml(item.ziniao_token || '') + '</td><td>' + renderShopKeyWhitelistStatus(item) + '</td><td>' + escapeHtml(item.created_at || '') + '</td><td>' + escapeHtml(item.updated_at || '') + '</td><td>' +
|
||||
'<button class="btn btn-sm" data-shop-key-edit="' + item.id + '" data-shop-key="' + (JSON.stringify(item).replace(/"/g, '"')) + '">编辑</button> ' +
|
||||
'<button class="btn btn-sm btn-danger" data-shop-key-delete="' + item.id + '" data-ziniao-account-name="' + (item.ziniao_account_name || '').replace(/"/g, '"') + '">删除</button>' +
|
||||
'</td></tr>';
|
||||
@@ -2138,7 +2688,7 @@
|
||||
bindShopKeyActions();
|
||||
})
|
||||
.catch(function () {
|
||||
document.getElementById('shopKeyListBody').innerHTML = '<tr><td colspan="7" class="empty-tip">请求失败</td></tr>';
|
||||
document.getElementById('shopKeyListBody').innerHTML = '<tr><td colspan="8" class="empty-tip">请求失败</td></tr>';
|
||||
});
|
||||
}
|
||||
function bindShopKeyActions() {
|
||||
@@ -2264,6 +2814,21 @@
|
||||
return query;
|
||||
}
|
||||
|
||||
function shopPasswordIcon(revealed) {
|
||||
if (revealed) {
|
||||
return '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="m2 2 20 20"></path><path d="M6.71 6.71C4.7 8.1 3.34 10.08 2 12c2.12 3.04 5.5 6 10 6 1.67 0 3.17-.41 4.47-1.05"></path><path d="M10.73 5.08A9.36 9.36 0 0 1 12 5c4.5 0 7.88 2.96 10 7a15.82 15.82 0 0 1-2.12 2.91"></path><path d="M14.12 14.12A3 3 0 0 1 9.88 9.88"></path></svg>';
|
||||
}
|
||||
return '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M2.06 12.35a1 1 0 0 1 0-.7C3.54 8.04 7.06 5.5 12 5.5s8.46 2.54 9.94 6.15a1 1 0 0 1 0 .7C20.46 15.96 16.94 18.5 12 18.5S3.54 15.96 2.06 12.35"></path><circle cx="12" cy="12" r="3"></circle></svg>';
|
||||
}
|
||||
|
||||
function renderShopPasswordCell(item) {
|
||||
var maskedPassword = item.password || '******';
|
||||
return '<span class="shop-password-cell">' +
|
||||
'<span class="shop-password-value" data-shop-password-value>' + escapeHtml(maskedPassword) + '</span>' +
|
||||
'<button type="button" class="shop-password-toggle" data-shop-password-toggle="' + escapeHtml(item.id) + '" data-shop-name="' + escapeHtml(item.shop_name || '') + '" data-masked-password="' + escapeHtml(maskedPassword) + '" aria-label="显示密码" aria-pressed="false" title="显示密码">' +
|
||||
shopPasswordIcon(false) + '</button></span>';
|
||||
}
|
||||
|
||||
function loadShopManage(page) {
|
||||
shopManagePage = page || 1;
|
||||
fetch('/api/admin/shop-manages?' + buildShopManageQuery(shopManagePage))
|
||||
@@ -2280,7 +2845,7 @@
|
||||
} else {
|
||||
tbody.innerHTML = items.map(function (item, index) {
|
||||
var rowNo = (shopManagePage - 1) * shopManagePageSize + index + 1;
|
||||
return '<tr><td>' + rowNo + '</td><td>' + (item.group_name || '') + '</td><td>' + (item.shop_name || '') + '</td><td>' + (item.mall_name || '') + '</td><td>' + (item.account || '') + '</td><td>' + (item.password || '') + '</td><td>' + (item.created_at || '') + '</td><td>' + (item.updated_at || '') + '</td><td>' +
|
||||
return '<tr><td>' + rowNo + '</td><td>' + (item.group_name || '') + '</td><td>' + (item.shop_name || '') + '</td><td>' + (item.mall_name || '') + '</td><td>' + (item.account || '') + '</td><td>' + renderShopPasswordCell(item) + '</td><td>' + (item.created_at || '') + '</td><td>' + (item.updated_at || '') + '</td><td>' +
|
||||
'<button class="btn btn-sm" data-shop-manage-edit="' + item.id + '" data-shop-manage="' + (JSON.stringify(item).replace(/"/g, '"')) + '">编辑</button> ' +
|
||||
'<button class="btn btn-sm btn-danger" data-shop-manage-delete="' + item.id + '" data-shop-manage-name="' + (item.shop_name || '').replace(/"/g, '"') + '">删除</button>' +
|
||||
'</td></tr>';
|
||||
@@ -2295,6 +2860,42 @@
|
||||
}
|
||||
|
||||
function bindShopManageActions() {
|
||||
document.querySelectorAll('[data-shop-password-toggle]').forEach(function (btn) {
|
||||
btn.onclick = function () {
|
||||
var valueEl = btn.parentElement.querySelector('[data-shop-password-value]');
|
||||
var revealed = btn.dataset.revealed === 'true';
|
||||
if (revealed) {
|
||||
valueEl.textContent = btn.dataset.maskedPassword || '******';
|
||||
btn.dataset.revealed = 'false';
|
||||
btn.setAttribute('aria-label', '显示密码');
|
||||
btn.setAttribute('aria-pressed', 'false');
|
||||
btn.title = '显示密码';
|
||||
btn.innerHTML = shopPasswordIcon(false);
|
||||
return;
|
||||
}
|
||||
|
||||
btn.disabled = true;
|
||||
valueEl.textContent = '读取中...';
|
||||
fetch('/api/admin/shop-manage/' + encodeURIComponent(btn.dataset.shopPasswordToggle) + '/credential?shop_name=' + encodeURIComponent(btn.dataset.shopName || ''))
|
||||
.then(function (r) { return r.json(); })
|
||||
.then(function (res) {
|
||||
if (!res.success) throw new Error(res.error || '读取密码失败');
|
||||
valueEl.textContent = res.password || '';
|
||||
btn.dataset.revealed = 'true';
|
||||
btn.setAttribute('aria-label', '隐藏密码');
|
||||
btn.setAttribute('aria-pressed', 'true');
|
||||
btn.title = '隐藏密码';
|
||||
btn.innerHTML = shopPasswordIcon(true);
|
||||
})
|
||||
.catch(function (error) {
|
||||
valueEl.textContent = btn.dataset.maskedPassword || '******';
|
||||
alert(error.message || '读取密码失败');
|
||||
})
|
||||
.finally(function () {
|
||||
btn.disabled = false;
|
||||
});
|
||||
};
|
||||
});
|
||||
document.querySelectorAll('[data-shop-manage-edit]').forEach(function (btn) {
|
||||
btn.onclick = function () {
|
||||
var item = {};
|
||||
@@ -4472,6 +5073,7 @@
|
||||
html += '<span class="page-item' + active + '" onclick="loadDigitalHumanVersions(' + i + ')">' + i + '</span>';
|
||||
}
|
||||
pagination.innerHTML = html;
|
||||
appendPaginationQuickJump(pagination, pages, current, loadDigitalHumanVersions);
|
||||
} else {
|
||||
pagination.innerHTML = '';
|
||||
}
|
||||
@@ -4918,6 +5520,27 @@
|
||||
if (document.getElementById('editColumnMenuType')) document.getElementById('editColumnMenuType').onchange = populateColumnParentSelects;
|
||||
|
||||
// ========== 分页 ==========
|
||||
function appendPaginationQuickJump(el, totalPages, page, onPage) {
|
||||
el.insertAdjacentHTML('beforeend',
|
||||
'<label class="pagination-jump">跳至<input type="number" min="1" max="' + totalPages + '" step="1" inputmode="numeric" aria-label="跳转页码" data-page-jump-input>页</label>' +
|
||||
'<button type="button" data-page-jump>跳转</button>');
|
||||
var jumpInput = el.querySelector('[data-page-jump-input]');
|
||||
var jumpToPage = function () {
|
||||
var targetPage = parseInt(jumpInput.value, 10);
|
||||
if (isNaN(targetPage)) {
|
||||
jumpInput.focus();
|
||||
return;
|
||||
}
|
||||
targetPage = Math.min(Math.max(targetPage, 1), totalPages);
|
||||
jumpInput.value = targetPage;
|
||||
if (targetPage !== page) onPage(targetPage);
|
||||
};
|
||||
el.querySelector('[data-page-jump]').onclick = jumpToPage;
|
||||
jumpInput.onkeydown = function (event) {
|
||||
if (event.key === 'Enter') jumpToPage();
|
||||
};
|
||||
}
|
||||
|
||||
function renderPagination(elId, total, page, pageSize, onPage) {
|
||||
var el = document.getElementById(elId);
|
||||
if (!el) return;
|
||||
@@ -4929,6 +5552,7 @@
|
||||
el.querySelectorAll('[data-p]').forEach(function (b) {
|
||||
if (!b.disabled) b.onclick = function () { onPage(parseInt(b.dataset.p, 10)); };
|
||||
});
|
||||
appendPaginationQuickJump(el, totalPages, page, onPage);
|
||||
}
|
||||
|
||||
// 初始化
|
||||
|
||||
Reference in New Issue
Block a user