4182 lines
214 KiB
HTML
4182 lines
214 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="zh-CN">
|
||
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>管理后台 - 南日AI</title>
|
||
<style>
|
||
* {
|
||
margin: 0;
|
||
padding: 0;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
body {
|
||
font-family: "Microsoft YaHei", "PingFang SC", sans-serif;
|
||
background: #f5f5f5;
|
||
padding: 24px;
|
||
}
|
||
|
||
.page-head {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
gap: 16px;
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.page-head h1 {
|
||
margin-bottom: 0;
|
||
}
|
||
|
||
.admin-user-box {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 10px;
|
||
padding: 8px 12px;
|
||
background: #fff;
|
||
border: 1px solid #e6eaf2;
|
||
border-radius: 999px;
|
||
box-shadow: 0 4px 14px rgba(102, 126, 234, 0.08);
|
||
}
|
||
|
||
.admin-user-name {
|
||
font-size: 14px;
|
||
color: #333;
|
||
max-width: 220px;
|
||
white-space: nowrap;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
}
|
||
|
||
.admin-user-role {
|
||
font-size: 12px;
|
||
color: #667eea;
|
||
background: #eef1ff;
|
||
padding: 4px 8px;
|
||
border-radius: 999px;
|
||
}
|
||
|
||
.admin-user-logout {
|
||
padding: 6px 12px;
|
||
font-size: 13px;
|
||
}
|
||
|
||
.nav {
|
||
margin-bottom: 24px;
|
||
}
|
||
|
||
.nav a {
|
||
color: #667eea;
|
||
text-decoration: none;
|
||
}
|
||
|
||
.nav a:hover {
|
||
text-decoration: underline;
|
||
}
|
||
|
||
h1 {
|
||
font-size: 20px;
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
/* Tabs */
|
||
.tabs {
|
||
display: flex;
|
||
gap: 4px;
|
||
margin-bottom: 20px;
|
||
border-bottom: 1px solid #e0e0e0;
|
||
}
|
||
|
||
.tab {
|
||
padding: 12px 24px;
|
||
cursor: pointer;
|
||
color: #666;
|
||
font-size: 15px;
|
||
border-bottom: 2px solid transparent;
|
||
margin-bottom: -1px;
|
||
}
|
||
|
||
.tab:hover {
|
||
color: #667eea;
|
||
}
|
||
|
||
.tab.active {
|
||
color: #667eea;
|
||
font-weight: 600;
|
||
border-bottom-color: #667eea;
|
||
}
|
||
|
||
.tab-panel {
|
||
display: none;
|
||
}
|
||
|
||
.tab-panel.active {
|
||
display: block;
|
||
}
|
||
|
||
/* Form & Table */
|
||
.form-box,
|
||
.panel-box {
|
||
background: #fff;
|
||
border-radius: 8px;
|
||
padding: 24px;
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.form-group {
|
||
margin-bottom: 16px;
|
||
}
|
||
|
||
.form-group label {
|
||
display: block;
|
||
font-size: 14px;
|
||
margin-bottom: 6px;
|
||
}
|
||
|
||
.form-group input,
|
||
.form-group select {
|
||
width: 100%;
|
||
padding: 10px;
|
||
border: 1px solid #ddd;
|
||
border-radius: 6px;
|
||
}
|
||
|
||
.form-group input[type="checkbox"] {
|
||
width: auto;
|
||
margin-right: 8px;
|
||
}
|
||
|
||
.form-row {
|
||
display: flex;
|
||
gap: 16px;
|
||
flex-wrap: wrap;
|
||
align-items: flex-end;
|
||
margin-bottom: 16px;
|
||
}
|
||
|
||
.form-row .form-group {
|
||
margin-bottom: 0;
|
||
flex: 1;
|
||
min-width: 120px;
|
||
}
|
||
|
||
.btn {
|
||
padding: 10px 20px;
|
||
background: #667eea;
|
||
color: #fff;
|
||
border: none;
|
||
border-radius: 6px;
|
||
cursor: pointer;
|
||
font-size: 14px;
|
||
}
|
||
|
||
.btn:hover {
|
||
opacity: 0.9;
|
||
}
|
||
|
||
.btn-sm {
|
||
padding: 6px 12px;
|
||
font-size: 13px;
|
||
}
|
||
|
||
.btn-danger {
|
||
background: #e74c3c;
|
||
}
|
||
|
||
.btn-secondary {
|
||
background: #95a5a6;
|
||
}
|
||
|
||
.msg {
|
||
margin-top: 12px;
|
||
font-size: 14px;
|
||
}
|
||
|
||
.msg.ok {
|
||
color: #27ae60;
|
||
}
|
||
|
||
.msg.err {
|
||
color: #e74c3c;
|
||
}
|
||
|
||
.progress-wrap {
|
||
margin-top: 12px;
|
||
}
|
||
|
||
.progress-bar {
|
||
width: 100%;
|
||
height: 10px;
|
||
background: #edf0f5;
|
||
border-radius: 999px;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.progress-fill {
|
||
width: 0;
|
||
height: 100%;
|
||
background: #667eea;
|
||
transition: width 0.2s ease;
|
||
}
|
||
|
||
.progress-text {
|
||
margin-top: 8px;
|
||
font-size: 13px;
|
||
color: #666;
|
||
}
|
||
|
||
table {
|
||
width: 100%;
|
||
border-collapse: collapse;
|
||
}
|
||
|
||
th,
|
||
td {
|
||
padding: 12px;
|
||
text-align: left;
|
||
border-bottom: 1px solid #eee;
|
||
font-size: 14px;
|
||
}
|
||
|
||
th {
|
||
background: #f8f9fa;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.pagination {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
margin-top: 16px;
|
||
flex-wrap: wrap;
|
||
}
|
||
|
||
.pagination span {
|
||
font-size: 14px;
|
||
color: #666;
|
||
}
|
||
|
||
.pagination button {
|
||
padding: 6px 12px;
|
||
border: 1px solid #ddd;
|
||
background: #fff;
|
||
cursor: pointer;
|
||
border-radius: 4px;
|
||
}
|
||
|
||
.pagination button:hover:not(:disabled) {
|
||
background: #f0f0f0;
|
||
}
|
||
|
||
.pagination button:disabled {
|
||
opacity: 0.5;
|
||
cursor: not-allowed;
|
||
}
|
||
|
||
.thumb {
|
||
width: 60px;
|
||
height: 60px;
|
||
object-fit: cover;
|
||
border-radius: 4px;
|
||
margin-right: 8px;
|
||
vertical-align: middle;
|
||
}
|
||
|
||
.thumb-wrap {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 4px;
|
||
}
|
||
|
||
.empty-tip {
|
||
color: #999;
|
||
font-size: 14px;
|
||
padding: 24px;
|
||
text-align: center;
|
||
}
|
||
|
||
.modal-mask {
|
||
display: none;
|
||
position: fixed;
|
||
left: 0;
|
||
top: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
background: rgba(0, 0, 0, 0.4);
|
||
z-index: 1000;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
.modal-mask.show {
|
||
display: flex;
|
||
}
|
||
|
||
#shopManageGroupModal {
|
||
z-index: 1010;
|
||
}
|
||
|
||
.modal {
|
||
background: #fff;
|
||
border-radius: 8px;
|
||
padding: 24px;
|
||
min-width: 320px;
|
||
width: min(920px, calc(100vw - 48px));
|
||
max-width: 90%;
|
||
max-height: calc(100vh - 48px);
|
||
overflow-y: auto;
|
||
overflow-x: hidden;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.modal h3 {
|
||
margin-bottom: 16px;
|
||
font-size: 16px;
|
||
}
|
||
|
||
.column-permission-cards {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 8px;
|
||
min-height: 32px;
|
||
margin-bottom: 8px;
|
||
width: 100%;
|
||
max-width: 100%;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.column-permission-card {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 6px;
|
||
padding: 4px 10px;
|
||
background: #e8ebf7;
|
||
color: #667eea;
|
||
border-radius: 6px;
|
||
font-size: 13px;
|
||
max-width: 100%;
|
||
min-width: 0;
|
||
flex: 0 1 auto;
|
||
white-space: normal;
|
||
overflow-wrap: anywhere;
|
||
word-break: break-word;
|
||
}
|
||
|
||
.column-permission-card .col-card-remove {
|
||
flex: 0 0 auto;
|
||
margin-left: 2px;
|
||
padding: 0 4px;
|
||
line-height: 1;
|
||
cursor: pointer;
|
||
color: #999;
|
||
border: none;
|
||
background: none;
|
||
font-size: 16px;
|
||
}
|
||
|
||
.column-permission-card .col-card-remove:hover {
|
||
color: #e74c3c;
|
||
}
|
||
|
||
.column-permission-select {
|
||
width: 100%;
|
||
max-width: 100%;
|
||
min-height: 80px;
|
||
max-height: 140px;
|
||
overflow-y: auto;
|
||
padding: 8px;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.column-permission-select option {
|
||
padding: 4px 0;
|
||
}
|
||
</style>
|
||
</head>
|
||
|
||
<body>
|
||
<!-- <div class="nav"><a href="/home">返回首页</a></div> -->
|
||
<h1>管理后台</h1>
|
||
|
||
<div class="admin-user-box" style="position:absolute;top:24px;right:24px;z-index:10;">
|
||
<span class="admin-user-name" id="adminCurrentUsername">加载中...</span>
|
||
<span class="admin-user-role" id="adminCurrentUserRole" style="display:none;"></span>
|
||
<button class="btn btn-secondary admin-user-logout" id="btnAdminLogout" type="button">退出登录</button>
|
||
</div>
|
||
|
||
<div class="tabs" id="adminTabs">
|
||
<div class="tab active" data-tab="users">用户管理</div>
|
||
<div class="tab" data-tab="columns">栏目权限配置</div>
|
||
<div class="tab" data-tab="dedupe-total-data">数据去重总数据</div>
|
||
<div class="tab" data-tab="shop-keys">店铺密钥管理</div>
|
||
<div class="tab" data-tab="shop-manage">店铺管理</div>
|
||
<div class="tab" data-tab="skip-price-asin">跳过跟价 ASIN</div>
|
||
<div class="tab" data-tab="query-asin">查询 ASIN</div>
|
||
<div class="tab" data-tab="history">查看生成记录</div>
|
||
<div class="tab" data-tab="version">版本管理</div>
|
||
</div>
|
||
|
||
<!-- 用户管理 -->
|
||
<div id="panel-users" class="tab-panel">
|
||
<div class="form-box">
|
||
<h3 style="margin-bottom:16px;font-size:15px;">创建用户</h3>
|
||
<p style="margin-bottom:16px;font-size:13px;color:#666;">
|
||
当前没有开放注册入口,仅管理员可在此创建用户。层级关系:超级管理员 -> 管理员 -> 普通账号。</p>
|
||
<div class="form-group">
|
||
<label>用户名</label>
|
||
<input type="text" id="username" placeholder="用户名(至少 2 个字符)">
|
||
</div>
|
||
<div class="form-group">
|
||
<label>密码</label>
|
||
<input type="password" id="password" placeholder="密码(至少 6 个字符)">
|
||
</div>
|
||
<div class="form-group" id="formGroupRole">
|
||
<label>角色</label>
|
||
<select id="createRole">
|
||
<option value="normal">普通账号</option>
|
||
<option value="admin" id="optAdmin">管理员</option>
|
||
</select>
|
||
</div>
|
||
<div class="form-group" id="formGroupCreatedBy" style="display:none;">
|
||
<label>所属管理员</label>
|
||
<select id="createCreatedBy">
|
||
<option value="">请选择管理员</option>
|
||
</select>
|
||
</div>
|
||
<div class="form-group" id="formGroupCreateColumns">
|
||
<label>菜单权限(可多选,Ctrl/Shift 点选)</label>
|
||
<div id="createColumnCards" class="column-permission-cards"></div>
|
||
<select id="createColumnPermissionWrap" class="column-permission-select" multiple></select>
|
||
</div>
|
||
<button class="btn" id="btnCreate">创建用户</button>
|
||
<p class="msg" id="msgCreate"></p>
|
||
</div>
|
||
|
||
<div class="panel-box">
|
||
<h3 style="margin-bottom:16px;font-size:15px;">用户列表</h3>
|
||
<div class="form-row" style="margin-bottom:16px;">
|
||
<div class="form-group" style="min-width:180px;">
|
||
<label>用户名(模糊搜索)</label>
|
||
<input type="text" id="searchUsername" placeholder="输入用户名关键字">
|
||
</div>
|
||
<div class="form-group" id="filterCreatedByGroup" style="min-width:160px;display:none;">
|
||
<label>所属管理员</label>
|
||
<select id="filterCreatedBy">
|
||
<option value="">全部</option>
|
||
</select>
|
||
</div>
|
||
<button class="btn" id="btnSearchUsers">查询</button>
|
||
</div>
|
||
<table>
|
||
<thead>
|
||
<tr>
|
||
<th>ID</th>
|
||
<th>用户名</th>
|
||
<th>角色</th>
|
||
<th>所属管理员</th>
|
||
<th>创建时间</th>
|
||
<th>操作</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="userListBody"></tbody>
|
||
</table>
|
||
<div class="pagination" id="userPagination"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 栏目权限配置 -->
|
||
<div id="panel-columns" class="tab-panel">
|
||
<div class="form-box">
|
||
<h3 style="margin-bottom:16px;font-size:15px;">新增菜单</h3>
|
||
<div class="form-row">
|
||
<div class="form-group" style="min-width:200px;">
|
||
<label>菜单名称</label>
|
||
<input type="text" id="columnName" placeholder="例如:店铺管理">
|
||
</div>
|
||
<div class=" form-group" style="min-width:200px;">
|
||
<label>栏目标识</label>
|
||
<input type="text" id="columnKey" placeholder="例如:shop_manage">
|
||
</div>
|
||
<div class="form-group" style="min-width:220px;">
|
||
<label>菜单路由</label>
|
||
<input type="text" id="columnRoutePath" placeholder="例如:dedupe-total-data">
|
||
</div>
|
||
<div class="form-group" style="min-width:140px;">
|
||
<label>排序</label>
|
||
<input type="number" id="columnSortOrder" placeholder="例如:10">
|
||
</div>
|
||
<div class="form-group" style="min-width:160px;">
|
||
<label>菜单类型</label>
|
||
<select id="columnMenuType">
|
||
<option value="admin">后台(admin)</option>
|
||
<option value="app">软件(app)</option>
|
||
</select>
|
||
</div>
|
||
<button class="btn" id="btnAddColumn">新增菜单</button>
|
||
</div>
|
||
<p class="msg" id="msgColumn"></p>
|
||
</div>
|
||
<div class="panel-box">
|
||
<h3 style="margin-bottom:16px;font-size:15px;">栏目列表</h3>
|
||
<table>
|
||
<thead>
|
||
<tr>
|
||
<th>ID</th>
|
||
<th>菜单名称</th>
|
||
<th>栏目标识</th>
|
||
<th>菜单类型</th>
|
||
<th>排序</th>
|
||
<th>菜单路由</th>
|
||
<th>创建时间</th>
|
||
<th>操作</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="columnListBody"></tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 版本管理 -->
|
||
<div id="panel-version" class="tab-panel">
|
||
<div class="form-box">
|
||
<h3 style="margin-bottom:16px;font-size:15px;">发布新版本</h3>
|
||
<div class="form-group">
|
||
<label>版本号</label>
|
||
<input type="text" id="versionNumber" placeholder="例如:1.0.0">
|
||
</div>
|
||
<div class="form-group">
|
||
<label>ZIP 压缩包</label>
|
||
<input type="file" id="versionZip" accept=".zip">
|
||
</div>
|
||
<button class="btn" id="btnUploadVersion">上传并发布</button>
|
||
<p class="msg" id="msgVersion"></p>
|
||
</div>
|
||
<div class="panel-box">
|
||
<h3 style="margin-bottom:16px;font-size:15px;">版本列表</h3>
|
||
<table>
|
||
<thead>
|
||
<tr>
|
||
<th>版本号</th>
|
||
<th>下载链接</th>
|
||
<th>发布时间</th>
|
||
<th>操作</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="versionListBody"></tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 数据去重总数据 -->
|
||
<div id="panel-dedupe-total-data" class="tab-panel">
|
||
<div class="form-box">
|
||
<div class="form-row" style="align-items:flex-start;gap:16px;">
|
||
<div style="flex:1;min-width:320px;">
|
||
<h3 style="margin-bottom:16px;font-size:15px;">新增总数据</h3>
|
||
<div class="form-row">
|
||
<div class="form-group" style="min-width:320px;">
|
||
<label>上传 Excel 文件(读取 ASIN 列)</label>
|
||
<input type="file" id="dedupeTotalDataFile" accept=".xlsx,.xls">
|
||
</div>
|
||
<button class="btn" id="btnAddDedupeTotalData">上传并导入</button>
|
||
</div>
|
||
<p class="msg" id="msgDedupeTotalData"></p>
|
||
<div class="progress-wrap" id="dedupeTotalDataProgressWrap" style="display:none;">
|
||
<div class="progress-bar">
|
||
<div class="progress-fill" id="dedupeTotalDataProgressFill"></div>
|
||
</div>
|
||
<div class="progress-text" id="dedupeTotalDataProgressText"></div>
|
||
</div>
|
||
</div>
|
||
<div style="flex:1;min-width:320px;">
|
||
<h3 style="margin-bottom:16px;font-size:15px;">删除匹配总数据</h3>
|
||
<div class="form-row">
|
||
<div class="form-group" style="min-width:320px;">
|
||
<label>上传 Excel 文件(读取 ASIN 列)</label>
|
||
<input type="file" id="dedupeTotalDataDeleteFile" accept=".xlsx,.xls">
|
||
</div>
|
||
<button class="btn btn-danger" id="btnDeleteImportDedupeTotalData">上传并删除</button>
|
||
</div>
|
||
<p class="msg" id="msgDeleteDedupeTotalData"></p>
|
||
<div class="progress-wrap" id="dedupeTotalDataDeleteProgressWrap" style="display:none;">
|
||
<div class="progress-bar">
|
||
<div class="progress-fill" id="dedupeTotalDataDeleteProgressFill"></div>
|
||
</div>
|
||
<div class="progress-text" id="dedupeTotalDataDeleteProgressText"></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="panel-box">
|
||
<h3 style="margin-bottom:16px;font-size:15px;">总数据列表</h3>
|
||
<div class="form-row" style="margin-bottom:16px;">
|
||
<div class="form-group" style="min-width:220px;">
|
||
<label>数据值(模糊搜索)</label>
|
||
<input type="text" id="searchDedupeTotalData" placeholder="输入关键字">
|
||
</div>
|
||
<button class=" btn" id="btnSearchDedupeTotalData">查询</button>
|
||
</div>
|
||
<table>
|
||
<thead>
|
||
<tr>
|
||
<th>ID</th>
|
||
<th>总数据值</th>
|
||
<th>创建时间</th>
|
||
<th>操作</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="dedupeTotalDataListBody"></tbody>
|
||
</table>
|
||
<div class="pagination" id="dedupeTotalDataPagination"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 店铺密钥管理 -->
|
||
<div id="panel-shop-keys" class="tab-panel">
|
||
<div class="form-box">
|
||
<h3 style="margin-bottom:16px;font-size:15px;">新增店铺密钥</h3>
|
||
<div class="form-row">
|
||
<div class="form-group" style="min-width:220px;">
|
||
<label>紫鸟账号名称</label>
|
||
<input type="text" id="shopKeyZiniaoAccountName" placeholder="请输入紫鸟账号名称">
|
||
</div>
|
||
<div class=" form-group" style="min-width:260px;">
|
||
<label>紫鸟令牌</label>
|
||
<input type="text" id="shopKeyZiniaoToken" placeholder="请输入紫鸟令牌">
|
||
</div>
|
||
<button class=" btn" id="btnCreateShopKey">新增店铺密钥</button>
|
||
</div>
|
||
<p class="msg" id="msgShopKey"></p>
|
||
</div>
|
||
<div class="panel-box">
|
||
<h3 style="margin-bottom:16px;font-size:15px;">店铺密钥列表</h3>
|
||
<table>
|
||
<thead>
|
||
<tr>
|
||
<th>序号</th>
|
||
<th>紫鸟账号名称</th>
|
||
<th>紫鸟令牌</th>
|
||
<th>创建时间</th>
|
||
<th>修改时间</th>
|
||
<th>操作</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="shopKeyListBody"></tbody>
|
||
</table>
|
||
<div class="pagination" id="shopKeyPagination"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 店铺管理 -->
|
||
<div id="panel-shop-manage" class="tab-panel">
|
||
<div class="form-box">
|
||
<h3 style="margin-bottom:16px;font-size:15px;">新增店铺</h3>
|
||
<div class="form-row">
|
||
<div class="form-group" style="min-width:220px;">
|
||
<label>分组</label>
|
||
<div style="display:flex;gap:8px;align-items:center;">
|
||
<select id="shopManageGroupSelect" style="min-width:150px;">
|
||
<option value="">请选择分组</option>
|
||
</select>
|
||
<button class="btn btn-secondary" id="btnManageShopGroups" type="button">管理分组</button>
|
||
</div>
|
||
</div>
|
||
<div class="form-group" style="min-width:160px;">
|
||
<label>店铺名</label>
|
||
<input type="text" id="shopManageShopName" placeholder="请输入店铺名称">
|
||
</div>
|
||
<div class=" form-group" style="min-width:180px;">
|
||
<label>店铺商城名</label>
|
||
<input type="text" id="shopManageMallName" placeholder="请输入店铺商城名">
|
||
</div>
|
||
<div class="form-group" style="min-width:180px;">
|
||
<label>账号</label>
|
||
<input type="text" id="shopManageAccount" placeholder="请输入账号">
|
||
</div>
|
||
<div class=" form-group" style="min-width:180px;">
|
||
<label>密码</label>
|
||
<input type="text" id="shopManagePassword" placeholder="请输入密码">
|
||
</div>
|
||
<button class=" btn" id="btnCreateShopManage">新增店铺</button>
|
||
</div>
|
||
<p class="msg" id="msgShopManage"></p>
|
||
</div>
|
||
<div class="panel-box">
|
||
<h3 style="margin-bottom:16px;font-size:15px;">店铺列表</h3>
|
||
<div class="form-row" style="margin-bottom:12px;">
|
||
<div class="form-group" style="min-width:180px;">
|
||
<label>分组</label>
|
||
<select id="shopManageFilterGroupId">
|
||
<option value="">全部分组</option>
|
||
</select>
|
||
</div>
|
||
<div class="form-group" style="min-width:220px;">
|
||
<label>店铺名</label>
|
||
<input type="text" id="shopManageFilterShopName" placeholder="请输入店铺名">
|
||
</div>
|
||
<button class="btn" id="btnSearchShopManage">查询</button>
|
||
</div>
|
||
<table>
|
||
<thead>
|
||
<tr>
|
||
<th>序号</th>
|
||
<th>分组</th>
|
||
<th>店铺名</th>
|
||
<th>店铺商城名</th>
|
||
<th>账号</th>
|
||
<th>密码</th>
|
||
<th>创建时间</th>
|
||
<th>修改时间</th>
|
||
<th>操作</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="shopManageListBody"></tbody>
|
||
</table>
|
||
<div class="pagination" id="shopManagePagination"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 查看生成记录 -->
|
||
<div id="panel-skip-price-asin" class="tab-panel">
|
||
<div class="form-box">
|
||
<h3 style="margin-bottom:16px;font-size:15px;">新增 ASIN</h3>
|
||
<div class="form-row">
|
||
<div class="form-group" style="min-width:220px;">
|
||
<label>分组</label>
|
||
<div style="display:flex;gap:8px;align-items:center;">
|
||
<select id="skipPriceAsinGroupSelect" style="min-width:150px;">
|
||
<option value="">请选择分组</option>
|
||
</select>
|
||
<button class="btn btn-secondary" id="btnManageSkipPriceAsinGroups" type="button">管理分组</button>
|
||
</div>
|
||
</div>
|
||
<div class="form-group" style="min-width:180px;">
|
||
<label>店铺名</label>
|
||
<input type="text" id="skipPriceAsinShopName" placeholder="请输入店铺名称">
|
||
</div>
|
||
<div class=" form-group" style="min-width:220px;">
|
||
<label>国家</label>
|
||
<div style="margin-bottom:8px;">
|
||
<label> </label>
|
||
<button class="btn btn-secondary" id="btnChooseSkipPriceAsinShop" type="button">选择店铺</button>
|
||
</div>
|
||
<select id="skipPriceAsinCountries" multiple size="5">
|
||
<option value="DE">德国</option>
|
||
<option value="UK">英国</option>
|
||
<option value="FR">法国</option>
|
||
<option value="IT">意大利</option>
|
||
<option value="ES">西班牙</option>
|
||
</select>
|
||
</div>
|
||
<div class="form-group" style="min-width:320px;flex:1;">
|
||
<label>ASIN / 最低价</label>
|
||
<div id="skipPriceAsinInputs" style="display:flex;flex-direction:column;gap:8px;">
|
||
<div style="color:#999;font-size:13px;">请选择国家后输入对应 ASIN 和最低价</div>
|
||
</div>
|
||
</div>
|
||
<button class="btn" id="btnCreateSkipPriceAsin">新增 ASIN</button>
|
||
</div>
|
||
<p class="msg" id="msgSkipPriceAsin"></p>
|
||
</div>
|
||
<div class="panel-box">
|
||
<h3 style="margin-bottom:16px;font-size:15px;">店铺列表</h3>
|
||
<div class="form-row" style="margin-bottom:12px;">
|
||
<div class="form-group" style="min-width:180px;">
|
||
<label>分组</label>
|
||
<select id="skipPriceAsinFilterGroupId">
|
||
<option value="">全部分组</option>
|
||
</select>
|
||
</div>
|
||
<div class="form-group" style="min-width:220px;">
|
||
<label>店铺名</label>
|
||
<input type="text" id="skipPriceAsinFilterShopName" placeholder="请输入店铺名">
|
||
</div>
|
||
<div class="form-group" style="min-width:220px;">
|
||
<label>ASIN</label>
|
||
<input type="text" id="skipPriceAsinFilterAsin" placeholder="请输入 ASIN">
|
||
</div>
|
||
<button class="btn" id="btnSearchSkipPriceAsin">查询</button>
|
||
</div>
|
||
<table>
|
||
<thead>
|
||
<tr>
|
||
<th>序号</th>
|
||
<th>分组</th>
|
||
<th>店铺名</th>
|
||
<th>德国</th>
|
||
<th>英国</th>
|
||
<th>法国</th>
|
||
<th>意大利</th>
|
||
<th>西班牙</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="skipPriceAsinListBody"></tbody>
|
||
</table>
|
||
<div class="pagination" id="skipPriceAsinPagination"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="panel-query-asin" class="tab-panel">
|
||
<div class="form-box">
|
||
<h3 style="margin-bottom:16px;font-size:15px;">新增查询 ASIN</h3>
|
||
<div class="form-row">
|
||
<div class="form-group" style="min-width:220px;">
|
||
<label>分组</label>
|
||
<div style="display:flex;gap:8px;align-items:center;">
|
||
<select id="queryAsinGroupSelect" style="min-width:150px;">
|
||
<option value="">请选择分组</option>
|
||
</select>
|
||
<button class="btn btn-secondary" id="btnManageQueryAsinGroups" type="button">管理分组</button>
|
||
</div>
|
||
</div>
|
||
<div class="form-group" style="min-width:340px;">
|
||
<label>店铺名</label>
|
||
<div style="display:flex;gap:8px;align-items:center;">
|
||
<input type="text" id="queryAsinShopName" placeholder="请输入店铺名称" style="flex:1;min-width:180px;">
|
||
<button class="btn btn-secondary" id="btnChooseQueryAsinShop" type="button">选择店铺</button>
|
||
</div>
|
||
</div>
|
||
<div class="form-group" style="min-width:220px;">
|
||
<label>国家</label>
|
||
<select id="queryAsinCountries" multiple size="5" style="min-height:98px;">
|
||
<option value="DE">德国</option>
|
||
<option value="UK">英国</option>
|
||
<option value="FR">法国</option>
|
||
<option value="IT">意大利</option>
|
||
<option value="ES">西班牙</option>
|
||
</select>
|
||
</div>
|
||
<div class="form-group" style="min-width:320px;flex:1;">
|
||
<label>ASIN</label>
|
||
<div id="queryAsinInputs" style="display:flex;flex-direction:column;gap:8px;">
|
||
<div style="color:#999;font-size:13px;">请选择国家后输入对应 ASIN</div>
|
||
</div>
|
||
</div>
|
||
<button class="btn" id="btnCreateQueryAsin" style="align-self:flex-end;margin-bottom:40px;">新增 ASIN</button>
|
||
</div>
|
||
<p class="msg" id="msgQueryAsin"></p>
|
||
<div class="form-row" style="align-items:flex-start;gap:16px;margin-top:18px;border-top:1px solid #f0f0f0;padding-top:16px;">
|
||
<div style="flex:1;min-width:320px;">
|
||
<h3 style="margin-bottom:12px;font-size:14px;">导入添加</h3>
|
||
<div class="form-row">
|
||
<div class="form-group" style="min-width:320px;">
|
||
<label>上传 Excel(支持两种格式:1. 分组/店铺名/德国-西班牙;2. 店铺账号/ASIN/中文国家名。文件无分组列时取页面当前分组)</label>
|
||
<input type="file" id="queryAsinImportFile" accept=".xlsx,.xls">
|
||
</div>
|
||
<button class="btn" id="btnImportQueryAsin" type="button">上传并添加</button>
|
||
</div>
|
||
<p class="msg" id="msgQueryAsinImport"></p>
|
||
<div class="progress-wrap" id="queryAsinImportProgressWrap" style="display:none;">
|
||
<div class="progress-bar">
|
||
<div class="progress-fill" id="queryAsinImportProgressFill"></div>
|
||
</div>
|
||
<div class="progress-text" id="queryAsinImportProgressText"></div>
|
||
</div>
|
||
</div>
|
||
<div style="flex:1;min-width:320px;">
|
||
<h3 style="margin-bottom:12px;font-size:14px;">导入删除</h3>
|
||
<div class="form-row">
|
||
<div class="form-group" style="min-width:320px;">
|
||
<label>上传 Excel(支持按 分组/店铺名/国家列 或 店铺账号/ASIN/中文国家名 删除;文件无分组列时取页面当前分组)</label>
|
||
<input type="file" id="queryAsinDeleteImportFile" accept=".xlsx,.xls">
|
||
</div>
|
||
<button class="btn btn-danger" id="btnDeleteImportQueryAsin" type="button">上传并删除</button>
|
||
</div>
|
||
<p class="msg" id="msgQueryAsinDeleteImport"></p>
|
||
<div class="progress-wrap" id="queryAsinDeleteImportProgressWrap" style="display:none;">
|
||
<div class="progress-bar">
|
||
<div class="progress-fill" id="queryAsinDeleteImportProgressFill"></div>
|
||
</div>
|
||
<div class="progress-text" id="queryAsinDeleteImportProgressText"></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="panel-box">
|
||
<h3 style="margin-bottom:16px;font-size:15px;">店铺列表</h3>
|
||
<div class="form-row" style="margin-bottom:12px;">
|
||
<div class="form-group" style="min-width:180px;">
|
||
<label>分组</label>
|
||
<select id="queryAsinFilterGroupId">
|
||
<option value="">全部分组</option>
|
||
</select>
|
||
</div>
|
||
<div class="form-group" style="min-width:220px;">
|
||
<label>店铺名</label>
|
||
<input type="text" id="queryAsinFilterShopName" placeholder="请输入店铺名">
|
||
</div>
|
||
<div class="form-group" style="min-width:220px;">
|
||
<label>ASIN</label>
|
||
<input type="text" id="queryAsinFilterAsin" placeholder="请输入 ASIN">
|
||
</div>
|
||
<button class="btn" id="btnSearchQueryAsin">查询</button>
|
||
</div>
|
||
<table>
|
||
<thead>
|
||
<tr>
|
||
<th>序号</th>
|
||
<th>分组</th>
|
||
<th>店铺名</th>
|
||
<th>德国</th>
|
||
<th>英国</th>
|
||
<th>法国</th>
|
||
<th>意大利</th>
|
||
<th>西班牙</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="queryAsinListBody"></tbody>
|
||
</table>
|
||
<div class="pagination" id="queryAsinPagination"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="panel-history" class="tab-panel">
|
||
<div class="form-box">
|
||
<h3 style="margin-bottom:16px;font-size:15px;">筛选条件</h3>
|
||
<div class="form-row">
|
||
<div class="form-group" style="min-width:140px;">
|
||
<label>指定用户</label>
|
||
<select id="filterUser">
|
||
<option value="">全部用户</option>
|
||
</select>
|
||
</div>
|
||
<div class="form-group" style="min-width:140px;">
|
||
<label>开始时间</label>
|
||
<input type="datetime-local" id="filterTimeStart">
|
||
</div>
|
||
<div class="form-group" style="min-width:140px;">
|
||
<label>结束时间</label>
|
||
<input type="datetime-local" id="filterTimeEnd">
|
||
</div>
|
||
<button class="btn" id="btnFilterHistory">查询</button>
|
||
</div>
|
||
</div>
|
||
<div class="panel-box">
|
||
<h3 style="margin-bottom:16px;font-size:15px;">生成记录</h3>
|
||
<table>
|
||
<thead>
|
||
<tr>
|
||
<th>ID</th>
|
||
<th>用户</th>
|
||
<th>类型</th>
|
||
<th>创建时间</th>
|
||
<th>结果预览</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="historyListBody"></tbody>
|
||
</table>
|
||
<div class="pagination" id="historyPagination"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 编辑用户弹窗 -->
|
||
<div class="modal-mask" id="editUserModal">
|
||
<div class="modal">
|
||
<h3>编辑用户</h3>
|
||
<input type="hidden" id="editUserId">
|
||
<div class="form-group">
|
||
<label>用户名</label>
|
||
<input type="text" id="editUsername" readonly style="background:#f5f5f5;">
|
||
</div>
|
||
<div class="form-group">
|
||
<label>新密码(不修改可留空)</label>
|
||
<input type="password" id="editPassword" placeholder="留空则不修改密码">
|
||
</div>
|
||
<div class="form-group" id="editFormGroupRole" style="display:none;">
|
||
<label>角色</label>
|
||
<select id="editRole">
|
||
<option value="normal">普通账号</option>
|
||
<option value="admin">管理员</option>
|
||
</select>
|
||
</div>
|
||
<div class="form-group" id="editFormGroupCreator" style="display:none;">
|
||
<label>所属管理员</label>
|
||
<input type="text" id="editCreatorName" readonly style="background:#f5f5f5;">
|
||
</div>
|
||
<div class="form-group">
|
||
<label>菜单权限(可多选,Ctrl/Shift 点选)</label>
|
||
<div id="editColumnCards" class="column-permission-cards"></div>
|
||
<select id="editColumnPermissionWrap" class="column-permission-select" multiple></select>
|
||
</div>
|
||
<p class="msg" id="msgEdit"></p>
|
||
<div style="margin-top:16px;display:flex;gap:8px;">
|
||
<button class="btn" id="btnSaveUser">保存</button>
|
||
<button class="btn btn-secondary" id="btnCloseEdit">取消</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 编辑总数据弹窗 -->
|
||
<div class="modal-mask" id="editDedupeTotalDataModal">
|
||
<div class="modal">
|
||
<h3>编辑总数据</h3>
|
||
<input type="hidden" id="editDedupeTotalDataId">
|
||
<div class="form-group">
|
||
<label>总数据值</label>
|
||
<input type="text" id="editDedupeTotalDataValue" placeholder="请输入总数据值">
|
||
</div>
|
||
<p class=" msg" id="msgEditDedupeTotalData"></p>
|
||
<div style="margin-top:16px;display:flex;gap:8px;">
|
||
<button class="btn" id="btnSaveDedupeTotalData">保存</button>
|
||
<button class="btn btn-secondary" id="btnCloseEditDedupeTotalData">取消</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 编辑栏目弹窗 -->
|
||
<div class="modal-mask" id="editColumnModal">
|
||
<div class="modal">
|
||
<h3>编辑栏目</h3>
|
||
<input type="hidden" id="editColumnId">
|
||
<div class="form-group">
|
||
<label>菜单名称</label>
|
||
<input type="text" id="editColumnName" placeholder="菜单名称">
|
||
</div>
|
||
<div class=" form-group">
|
||
<label>栏目标识</label>
|
||
<input type="text" id="editColumnKey" placeholder="栏目标识">
|
||
</div>
|
||
<div class="form-group">
|
||
<label>菜单路由</label>
|
||
<input type="text" id="editColumnRoutePath" placeholder="菜单路由">
|
||
</div>
|
||
<div class="form-group">
|
||
<label>排序</label>
|
||
<input type="number" id="editColumnSortOrder" placeholder="排序">
|
||
</div>
|
||
<div class="form-group">
|
||
<label>菜单类型</label>
|
||
<select id="editColumnMenuType">
|
||
<option value="admin">后台(admin)</option>
|
||
<option value="app">软件(app)</option>
|
||
</select>
|
||
</div>
|
||
<p class="msg" id="msgEditColumn"></p>
|
||
<div style="margin-top:16px;display:flex;gap:8px;">
|
||
<button class="btn" id="btnSaveColumn">保存</button>
|
||
<button class="btn btn-secondary" id="btnCloseEditColumn">取消</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 分组管理弹窗 -->
|
||
<div class="modal-mask" id="shopManageGroupModal">
|
||
<div class="modal" style="max-width:760px;">
|
||
<h3>分组管理</h3>
|
||
<div class="form-row">
|
||
<input type="hidden" id="shopManageGroupEditId">
|
||
<input type="hidden" id="shopManageGroupLeaderUserId">
|
||
<div class="form-group" style="min-width:220px;">
|
||
<label>组长</label>
|
||
<input type="text" id="shopManageGroupLeaderName" placeholder="当前登录用户" readonly
|
||
style="background:#f5f5f5;">
|
||
</div>
|
||
<div class="form-group" style="min-width:260px;">
|
||
<label>分组名称</label>
|
||
<input type="text" id="shopManageGroupInput" placeholder="请输入分组名称">
|
||
</div>
|
||
<div class="form-group" style="min-width:280px;flex:1;">
|
||
<label>组员</label>
|
||
<select id="shopManageGroupMemberSelect" multiple size="6" style="min-height:140px;">
|
||
</select>
|
||
<div id="shopManageGroupMemberHelp" style="color:#888;font-size:12px;margin-top:6px;">可添加当前组长创建的普通员工账号,按住 Ctrl 或 Command 可多选。</div>
|
||
</div>
|
||
</div>
|
||
<div style="display:flex;gap:8px;justify-content:flex-end;margin-bottom:12px;">
|
||
<button class="btn" id="btnSaveShopManageGroup" type="button">保存分组</button>
|
||
<button class="btn btn-secondary" id="btnCancelShopManageGroupEdit" type="button">取消编辑</button>
|
||
</div>
|
||
<p class="msg" id="msgShopManageGroup"></p>
|
||
<table>
|
||
<thead>
|
||
<tr>
|
||
<th>序号</th>
|
||
<th>分组名称</th>
|
||
<th>组长</th>
|
||
<th>组员数量</th>
|
||
<th>组员</th>
|
||
<th>创建时间</th>
|
||
<th>修改时间</th>
|
||
<th>操作</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="shopManageGroupListBody"></tbody>
|
||
</table>
|
||
<div style="margin-top:16px;display:flex;gap:8px;justify-content:flex-end;">
|
||
<button class="btn btn-secondary" id="btnCloseShopManageGroupModal" type="button">关闭</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 编辑店铺管理弹窗 -->
|
||
<div class="modal-mask" id="chooseSkipPriceAsinShopModal">
|
||
<div class="modal" style="max-width:900px;width:90%;max-height:80vh;display:flex;flex-direction:column;">
|
||
<h3>选择店铺</h3>
|
||
<div class="form-row">
|
||
<div class="form-group" style="min-width:180px;">
|
||
<label>分组</label>
|
||
<select id="chooseSkipPriceAsinShopGroupId">
|
||
<option value="">全部分组</option>
|
||
</select>
|
||
</div>
|
||
<div class="form-group" style="min-width:220px;">
|
||
<label>店铺名</label>
|
||
<input type="text" id="chooseSkipPriceAsinShopKeyword" placeholder="请输入店铺名">
|
||
</div>
|
||
<button class="btn" id="btnSearchChooseSkipPriceAsinShop" type="button">查询</button>
|
||
</div>
|
||
<div style="flex:1;min-height:0;overflow:auto;">
|
||
<table>
|
||
<thead>
|
||
<tr>
|
||
<th>序号</th>
|
||
<th>分组</th>
|
||
<th>店铺名</th>
|
||
<th>店铺商城名</th>
|
||
<th>账号</th>
|
||
<th>操作</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="chooseSkipPriceAsinShopListBody"></tbody>
|
||
</table>
|
||
</div>
|
||
<div class="pagination" id="chooseSkipPriceAsinShopPagination"></div>
|
||
<div style="margin-top:16px;display:flex;gap:8px;justify-content:flex-end;">
|
||
<button class="btn btn-secondary" id="btnCloseChooseSkipPriceAsinShopModal" type="button">关闭</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="modal-mask" id="editSkipPriceAsinModal">
|
||
<div class="modal">
|
||
<h3>编辑跳过跟价 ASIN</h3>
|
||
<input type="hidden" id="editSkipPriceAsinId">
|
||
<input type="hidden" id="editSkipPriceAsinCountry">
|
||
<div class="form-group">
|
||
<label>店铺名</label>
|
||
<input type="text" id="editSkipPriceAsinShopName" readonly style="background:#f5f5f5;">
|
||
</div>
|
||
<div class="form-group">
|
||
<label>国家</label>
|
||
<input type="text" id="editSkipPriceAsinCountryLabel" readonly style="background:#f5f5f5;">
|
||
</div>
|
||
<div class="form-group">
|
||
<label>ASIN</label>
|
||
<input type="text" id="editSkipPriceAsinValue" placeholder="请输入 ASIN">
|
||
</div>
|
||
<div class="form-group">
|
||
<label>最低价</label>
|
||
<input type="number" id="editSkipPriceMinimumPrice" min="0" step="0.01" placeholder="请输入最低价,可留空">
|
||
</div>
|
||
<p class="msg" id="msgEditSkipPriceAsin"></p>
|
||
<div style="margin-top:16px;display:flex;gap:8px;">
|
||
<button class="btn" id="btnSaveSkipPriceAsin">保存</button>
|
||
<button class="btn btn-secondary" id="btnCloseEditSkipPriceAsin">取消</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="modal-mask" id="chooseQueryAsinShopModal">
|
||
<div class="modal" style="max-width:900px;width:90%;max-height:80vh;display:flex;flex-direction:column;">
|
||
<h3>选择店铺</h3>
|
||
<div class="form-row">
|
||
<div class="form-group" style="min-width:180px;">
|
||
<label>分组</label>
|
||
<select id="chooseQueryAsinShopGroupId">
|
||
<option value="">全部分组</option>
|
||
</select>
|
||
</div>
|
||
<div class="form-group" style="min-width:220px;">
|
||
<label>店铺名</label>
|
||
<input type="text" id="chooseQueryAsinShopKeyword" placeholder="请输入店铺名">
|
||
</div>
|
||
<button class="btn" id="btnSearchChooseQueryAsinShop" type="button">查询</button>
|
||
</div>
|
||
<div style="flex:1;min-height:0;overflow:auto;">
|
||
<table>
|
||
<thead>
|
||
<tr>
|
||
<th>序号</th>
|
||
<th>分组</th>
|
||
<th>店铺名</th>
|
||
<th>店铺商城名</th>
|
||
<th>账号</th>
|
||
<th>操作</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="chooseQueryAsinShopListBody"></tbody>
|
||
</table>
|
||
</div>
|
||
<div class="pagination" id="chooseQueryAsinShopPagination"></div>
|
||
<div style="margin-top:16px;display:flex;gap:8px;justify-content:flex-end;">
|
||
<button class="btn btn-secondary" id="btnCloseChooseQueryAsinShopModal" type="button">关闭</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="modal-mask" id="editQueryAsinModal">
|
||
<div class="modal">
|
||
<h3>编辑查询 ASIN</h3>
|
||
<input type="hidden" id="editQueryAsinId">
|
||
<input type="hidden" id="editQueryAsinCountry">
|
||
<div class="form-group">
|
||
<label>店铺名</label>
|
||
<input type="text" id="editQueryAsinShopName" readonly style="background:#f5f5f5;">
|
||
</div>
|
||
<div class="form-group">
|
||
<label>国家</label>
|
||
<input type="text" id="editQueryAsinCountryLabel" readonly style="background:#f5f5f5;">
|
||
</div>
|
||
<div class="form-group">
|
||
<label>ASIN</label>
|
||
<input type="text" id="editQueryAsinValue" placeholder="请输入 ASIN">
|
||
</div>
|
||
<p class="msg" id="msgEditQueryAsin"></p>
|
||
<div style="margin-top:16px;display:flex;gap:8px;">
|
||
<button class="btn" id="btnSaveQueryAsin">保存</button>
|
||
<button class="btn btn-secondary" id="btnCloseEditQueryAsin">取消</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="modal-mask" id="editShopManageModal">
|
||
<div class="modal">
|
||
<h3>编辑店铺</h3>
|
||
<input type="hidden" id="editShopManageId">
|
||
<div class="form-group">
|
||
<label>分组</label>
|
||
<div style="display:flex;gap:8px;align-items:center;">
|
||
<select id="editShopManageGroupSelect" style="min-width:200px;">
|
||
<option value="">请选择分组</option>
|
||
</select>
|
||
<button class="btn btn-secondary" id="btnManageShopGroupsFromEdit" type="button">管理分组</button>
|
||
</div>
|
||
</div>
|
||
<div class="form-group">
|
||
<label>店铺名</label>
|
||
<input type="text" id="editShopManageShopName" placeholder="店铺名">
|
||
</div>
|
||
<div class=" form-group">
|
||
<label>店铺商城名</label>
|
||
<input type="text" id="editShopManageMallName" placeholder="店铺商城名">
|
||
</div>
|
||
<div class=" form-group">
|
||
<label>账号</label>
|
||
<input type="text" id="editShopManageAccount" placeholder="账号">
|
||
</div>
|
||
<div class="form-group">
|
||
<label>密码</label>
|
||
<input type="text" id="editShopManagePassword" placeholder="密码">
|
||
</div>
|
||
<p class="msg" id="msgEditShopManage"></p>
|
||
<div style="margin-top:16px;display:flex;gap:8px;">
|
||
<button class="btn" id="btnSaveShopManage">保存</button>
|
||
<button class="btn btn-secondary" id="btnCloseEditShopManage">取消</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 编辑店铺密钥弹窗 -->
|
||
<div class="modal-mask" id="editShopKeyModal">
|
||
<div class="modal">
|
||
<h3>编辑店铺密钥</h3>
|
||
<input type="hidden" id="editShopKeyId">
|
||
<div class="form-group">
|
||
<label>紫鸟账号名称</label>
|
||
<input type="text" id="editShopKeyZiniaoAccountName" placeholder="紫鸟账号名称">
|
||
</div>
|
||
<div class="form-group">
|
||
<label>紫鸟令牌</label>
|
||
<input type="text" id="editShopKeyZiniaoToken" placeholder="紫鸟令牌">
|
||
</div>
|
||
<p class="msg" id="msgEditShopKey"></p>
|
||
<div style="margin-top:16px;display:flex;gap:8px;">
|
||
<button class="btn" id="btnSaveShopKey">保存</button>
|
||
<button class="btn btn-secondary" id="btnCloseEditShopKey">取消</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
(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',
|
||
'query-asin': 'panel-query-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 === 'query-asin') loadQueryAsin(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 = '<div class="tab active" style="cursor:default;">暂无可用菜单</div>';
|
||
hideAllAdminPanels();
|
||
return;
|
||
}
|
||
if (adminTabsEl) {
|
||
adminTabsEl.innerHTML = knownItems.map(function (item) {
|
||
return '<div class="tab" data-tab="' + item.route_path + '">' + (item.name || item.route_path) + '</div>';
|
||
}).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 = '<div class="tab active" style="cursor:default;">菜单加载失败</div>';
|
||
hideAllAdminPanels();
|
||
return;
|
||
}
|
||
renderAdminTabs(res.items || []);
|
||
})
|
||
.catch(function () {
|
||
if (adminTabsEl) adminTabsEl.innerHTML = '<div class="tab active" style="cursor:default;">菜单加载失败</div>';
|
||
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 = '<tr><td colspan="7" class="empty-tip">加载失败: ' + (res.error || '') + '</td></tr>';
|
||
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 = '<tr><td colspan="6" class="empty-tip">暂无用户</td></tr>';
|
||
} else {
|
||
tbody.innerHTML = items.map(function (u) {
|
||
return '<tr><td>' + u.id + '</td><td>' + (u.username || '') + '</td><td>' +
|
||
roleLabel(u.role || 'normal') + '</td><td>' + (u.creator_username || '-') + '</td><td>' + (u.created_at || '') + '</td><td>' +
|
||
'<button class="btn btn-sm" data-edit="' + u.id + '" data-user="' + (JSON.stringify(u).replace(/"/g, '"')) + '">编辑</button> ' +
|
||
'<button class="btn btn-sm btn-danger" data-delete="' + u.id + '" data-name="' + (u.username || '').replace(/"/g, '"') + '">删除</button>' +
|
||
'</td></tr>';
|
||
}).join('');
|
||
}
|
||
renderPagination('userPagination', res.total, res.page, res.page_size, loadUsers);
|
||
bindUserActions();
|
||
updateCreateFormByRole();
|
||
updateUserFilterByRole();
|
||
})
|
||
.catch(function () {
|
||
document.getElementById('userListBody').innerHTML = '<tr><td colspan="7" class="empty-tip">请求失败</td></tr>';
|
||
});
|
||
}
|
||
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 = '<option value="">全部</option>';
|
||
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')
|
||
.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')
|
||
.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')
|
||
.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 = '<option value="">请选择管理员</option>';
|
||
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 = '<tr><td colspan="5" class="empty-tip">加载失败: ' + (res.error || '') + '</td></tr>';
|
||
return;
|
||
}
|
||
var items = res.items || [];
|
||
if (items.length === 0) {
|
||
tbody.innerHTML = '<tr><td colspan="5" class="empty-tip">暂无记录</td></tr>';
|
||
} 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 '<img src="' + (url || '').replace(/"/g, '"') + '" class="thumb" alt="">';
|
||
}).join('');
|
||
return '<tr><td>' + h.id + '</td><td>' + (h.username || '-') + '</td><td>' +
|
||
(h.panel_type || '-') + '</td><td>' + (h.created_at || '') + '</td><td>' +
|
||
'<div class="thumb-wrap">' + (thumbs || '-') + '</div></td></tr>';
|
||
}).join('');
|
||
}
|
||
renderPagination('historyPagination', res.total, res.page, res.page_size, loadHistory);
|
||
})
|
||
.catch(function () {
|
||
document.getElementById('historyListBody').innerHTML = '<tr><td colspan="5" class="empty-tip">请求失败</td></tr>';
|
||
});
|
||
}
|
||
var shopManageAllUsers = [];
|
||
function getEligibleShopManageGroupUsers(leaderUserId) {
|
||
var canViewAllMembers = currentUserRole === 'super_admin';
|
||
return shopManageAllUsers.filter(function (u) {
|
||
if (String(u.id || '') === String(leaderUserId || '')) return false;
|
||
if (canViewAllMembers) return true;
|
||
return String(u.role || '') === 'normal' &&
|
||
String(u.created_by_id || '') === String(leaderUserId || '');
|
||
});
|
||
}
|
||
function refreshShopManageGroupMemberSelect(leaderUserId, selectedUserIds) {
|
||
var sel = document.getElementById('shopManageGroupMemberSelect');
|
||
var helpEl = document.getElementById('shopManageGroupMemberHelp');
|
||
if (!sel) return;
|
||
var selectedMap = {};
|
||
(selectedUserIds || []).forEach(function (id) {
|
||
selectedMap[String(id)] = true;
|
||
});
|
||
var eligibleUsers = getEligibleShopManageGroupUsers(leaderUserId);
|
||
var options = eligibleUsers.map(function (u) {
|
||
return '<option value="' + u.id + '"' + (selectedMap[String(u.id)] ? ' selected' : '') + '>' + (u.username || '') + '</option>';
|
||
});
|
||
if (options.length) {
|
||
sel.innerHTML = options.join('');
|
||
if (helpEl) {
|
||
helpEl.textContent = '可添加当前组长创建的普通员工账号,按住 Ctrl 或 Command 可多选。';
|
||
}
|
||
return;
|
||
}
|
||
sel.innerHTML = '<option value="" disabled>当前组长暂无可添加组员</option>';
|
||
if (helpEl) {
|
||
helpEl.textContent = currentUserRole === 'normal'
|
||
? '普通账号没有下属普通员工时,这里会为空;当前账号只能作为组长使用。'
|
||
: '当前组长名下暂无可添加的普通员工账号。';
|
||
}
|
||
}
|
||
function setShopManageGroupLeader(leaderUserId, leaderUsername, selectedUserIds) {
|
||
var leaderIdEl = document.getElementById('shopManageGroupLeaderUserId');
|
||
var leaderNameEl = document.getElementById('shopManageGroupLeaderName');
|
||
if (leaderIdEl) leaderIdEl.value = leaderUserId ? String(leaderUserId) : '';
|
||
if (leaderNameEl) leaderNameEl.value = leaderUsername || '';
|
||
refreshShopManageGroupMemberSelect(leaderUserId, selectedUserIds || []);
|
||
}
|
||
function getSelectedShopManageGroupMemberIds() {
|
||
var sel = document.getElementById('shopManageGroupMemberSelect');
|
||
if (!sel) return [];
|
||
return Array.prototype.slice.call(sel.options || [])
|
||
.filter(function (opt) { return !!opt.selected; })
|
||
.map(function (opt) { return Number(opt.value); })
|
||
.filter(function (id) { return !!id; });
|
||
}
|
||
function loadUserOptions() {
|
||
return fetch('/api/admin/users?page=1&page_size=999')
|
||
.then(function (r) { return r.json(); })
|
||
.then(function (res) {
|
||
if (!res.success) {
|
||
throw new Error(res.error || '加载用户失败');
|
||
}
|
||
var items = res.items || [];
|
||
shopManageAllUsers = items.map(function (u) {
|
||
return {
|
||
id: u.id,
|
||
username: u.username || '',
|
||
role: u.role || 'normal',
|
||
created_by_id: u.created_by_id || null
|
||
};
|
||
});
|
||
setShopManageGroupLeader(
|
||
document.getElementById('shopManageGroupLeaderUserId') ? document.getElementById('shopManageGroupLeaderUserId').value : '',
|
||
document.getElementById('shopManageGroupLeaderName') ? document.getElementById('shopManageGroupLeaderName').value : '',
|
||
getSelectedShopManageGroupMemberIds()
|
||
);
|
||
var sel = document.getElementById('filterUser');
|
||
var cur = sel.value;
|
||
sel.innerHTML = '<option value="">全部用户</option>';
|
||
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 || '';
|
||
})
|
||
.catch(function () {
|
||
shopManageAllUsers = [];
|
||
var sel = document.getElementById('filterUser');
|
||
if (sel) {
|
||
sel.innerHTML = '<option value="">全部用户</option>';
|
||
}
|
||
refreshShopManageGroupMemberSelect(
|
||
document.getElementById('shopManageGroupLeaderUserId') ? document.getElementById('shopManageGroupLeaderUserId').value : '',
|
||
[]
|
||
);
|
||
});
|
||
}
|
||
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 = '<tr><td colspan="4" class="empty-tip">加载失败: ' + (res.error || '') + '</td></tr>';
|
||
return;
|
||
}
|
||
var items = res.items || [];
|
||
if (items.length === 0) {
|
||
tbody.innerHTML = '<tr><td colspan="4" class="empty-tip">暂无总数据</td></tr>';
|
||
} else {
|
||
tbody.innerHTML = items.map(function (item) {
|
||
return '<tr><td>' + item.id + '</td><td>' + (item.data_value || '') + '</td><td>' + (item.created_at || '') + '</td><td>' +
|
||
'<button class="btn btn-sm" data-dedupe-total-edit="' + item.id + '" data-value="' + (item.data_value || '').replace(/"/g, '"') + '">编辑</button> ' +
|
||
'<button class="btn btn-sm btn-danger" data-dedupe-total-delete="' + item.id + '" data-value="' + (item.data_value || '').replace(/"/g, '"') + '">删除</button>' +
|
||
'</td></tr>';
|
||
}).join('');
|
||
}
|
||
renderPagination('dedupeTotalDataPagination', res.total, res.page, res.page_size, loadDedupeTotalData);
|
||
bindDedupeTotalDataActions();
|
||
})
|
||
.catch(function () {
|
||
document.getElementById('dedupeTotalDataListBody').innerHTML = '<tr><td colspan="4" class="empty-tip">请求失败</td></tr>';
|
||
});
|
||
}
|
||
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 = '<tr><td colspan="8" class="empty-tip">加载失败: ' + (res.error || '') + '</td></tr>';
|
||
return;
|
||
}
|
||
var items = res.items || [];
|
||
if (items.length === 0) {
|
||
tbody.innerHTML = '<tr><td colspan="6" 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.ziniao_account_name || '') + '</td><td>' + (item.ziniao_token || '') + '</td><td>' + (item.created_at || '') + '</td><td>' + (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>';
|
||
}).join('');
|
||
}
|
||
renderPagination('shopKeyPagination', res.total, res.page, res.page_size, loadShopKeys);
|
||
bindShopKeyActions();
|
||
})
|
||
.catch(function () {
|
||
document.getElementById('shopKeyListBody').innerHTML = '<tr><td colspan="7" class="empty-tip">请求失败</td></tr>';
|
||
});
|
||
}
|
||
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 = [];
|
||
var currentShopManageGroupGrantRoutes = [];
|
||
|
||
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 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 = '<tr><td colspan="9" class="empty-tip">加载失败: ' + (res.error || '') + '</td></tr>';
|
||
return;
|
||
}
|
||
var items = res.items || [];
|
||
if (items.length === 0) {
|
||
tbody.innerHTML = '<tr><td colspan="9" class="empty-tip">暂无店铺</td></tr>';
|
||
} 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>' +
|
||
'<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>';
|
||
}).join('');
|
||
}
|
||
renderPagination('shopManagePagination', res.total, res.page, res.page_size, loadShopManage);
|
||
bindShopManageActions();
|
||
})
|
||
.catch(function () {
|
||
document.getElementById('shopManageListBody').innerHTML = '<tr><td colspan="9" class="empty-tip">请求失败</td></tr>';
|
||
});
|
||
}
|
||
|
||
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 || '删除失败'); }
|
||
});
|
||
};
|
||
});
|
||
}
|
||
|
||
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 queryCreateSel = document.getElementById('queryAsinGroupSelect');
|
||
var queryFilterSel = document.getElementById('queryAsinFilterGroupId');
|
||
var chooseQueryShopSel = document.getElementById('chooseQueryAsinShopGroupId');
|
||
var selectedFilterId = filterSel ? filterSel.value : '';
|
||
var selectedSkipCreateId = skipCreateSel ? skipCreateSel.value : '';
|
||
var selectedSkipFilterId = skipFilterSel ? skipFilterSel.value : '';
|
||
var selectedChooseSkipShopId = chooseSkipShopSel ? chooseSkipShopSel.value : '';
|
||
var selectedQueryCreateId = queryCreateSel ? queryCreateSel.value : '';
|
||
var selectedQueryFilterId = queryFilterSel ? queryFilterSel.value : '';
|
||
var selectedChooseQueryShopId = chooseQueryShopSel ? chooseQueryShopSel.value : '';
|
||
var createOpts = ['<option value="">请选择分组</option>'];
|
||
var filterOpts = ['<option value="">全部分组</option>'];
|
||
shopManageGroups.forEach(function (g) {
|
||
var option = '<option value="' + g.id + '">' + (g.group_name || '') + '</option>';
|
||
createOpts.push(option);
|
||
filterOpts.push(option);
|
||
});
|
||
createSel.innerHTML = createOpts.join('');
|
||
editSel.innerHTML = createOpts.join('');
|
||
if (skipCreateSel) skipCreateSel.innerHTML = createOpts.join('');
|
||
if (queryCreateSel) queryCreateSel.innerHTML = createOpts.join('');
|
||
if (filterSel) filterSel.innerHTML = filterOpts.join('');
|
||
if (skipFilterSel) skipFilterSel.innerHTML = filterOpts.join('');
|
||
if (chooseSkipShopSel) chooseSkipShopSel.innerHTML = filterOpts.join('');
|
||
if (queryFilterSel) queryFilterSel.innerHTML = filterOpts.join('');
|
||
if (chooseQueryShopSel) chooseQueryShopSel.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;
|
||
if (queryCreateSel && selectedQueryCreateId) queryCreateSel.value = selectedQueryCreateId;
|
||
if (queryFilterSel && selectedQueryFilterId) queryFilterSel.value = selectedQueryFilterId;
|
||
if (chooseQueryShopSel && selectedChooseQueryShopId) chooseQueryShopSel.value = selectedChooseQueryShopId;
|
||
}
|
||
|
||
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('shopManageGroupLeaderUserId').value = currentUserId || '';
|
||
document.getElementById('shopManageGroupLeaderName').value = currentUserUsername || '';
|
||
document.getElementById('shopManageGroupInput').value = '';
|
||
document.getElementById('msgShopManageGroup').textContent = '';
|
||
document.getElementById('msgShopManageGroup').className = 'msg';
|
||
refreshShopManageGroupMemberSelect(currentUserId, []);
|
||
}
|
||
|
||
function setShopManageGroupGrantRoutes(routes) {
|
||
currentShopManageGroupGrantRoutes = Array.isArray(routes) ? routes.slice() : [];
|
||
}
|
||
|
||
function updateShopManageGroupButtonsAccess() {
|
||
var canManageGroups = !!currentUserId;
|
||
['btnManageShopGroups', 'btnManageShopGroupsFromEdit', 'btnManageSkipPriceAsinGroups', 'btnManageQueryAsinGroups'].forEach(function (id) {
|
||
var btn = document.getElementById(id);
|
||
if (btn) btn.style.display = canManageGroups ? '' : 'none';
|
||
});
|
||
}
|
||
|
||
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 = '<tr><td colspan="8" class="empty-tip">暂无分组</td></tr>';
|
||
return;
|
||
}
|
||
tbody.innerHTML = shopManageGroups.map(function (item, index) {
|
||
var memberNames = Array.isArray(item.member_usernames) ? item.member_usernames.join('、') : '';
|
||
var canEdit = currentUserRole === 'super_admin' || String(item.leader_user_id || '') === String(currentUserId || '');
|
||
var actionHtml = canEdit
|
||
? ('<button class="btn btn-sm" data-shop-group-edit="' + item.id + '">编辑</button> ' +
|
||
'<button class="btn btn-sm btn-danger" data-shop-group-delete="' + item.id + '" data-shop-group-name="' + (item.group_name || '').replace(/"/g, '"') + '">删除</button>')
|
||
: '<span style="color:#999;">-</span>';
|
||
return '<tr><td>' + (index + 1) + '</td><td>' + (item.group_name || '') + '</td><td>' + (item.leader_username || '') + '</td><td>' + (item.member_count || 0) + '</td><td>' + (memberNames || '-') + '</td><td>' + (item.created_at || '') + '</td><td>' + (item.updated_at || '') + '</td><td>' +
|
||
actionHtml + '</td></tr>';
|
||
}).join('');
|
||
|
||
document.querySelectorAll('[data-shop-group-edit]').forEach(function (btn) {
|
||
btn.onclick = function () {
|
||
var item = null;
|
||
shopManageGroups.some(function (group) {
|
||
if (String(group.id || '') === String(btn.dataset.shopGroupEdit || '')) {
|
||
item = group;
|
||
return true;
|
||
}
|
||
return false;
|
||
});
|
||
if (!item) return;
|
||
document.getElementById('shopManageGroupEditId').value = item.id || '';
|
||
document.getElementById('shopManageGroupInput').value = item.group_name || '';
|
||
setShopManageGroupLeader(item.leader_user_id || '', item.leader_username || '', item.member_user_ids || []);
|
||
document.getElementById('msgShopManageGroup').textContent = '';
|
||
document.getElementById('msgShopManageGroup').className = 'msg';
|
||
};
|
||
});
|
||
|
||
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('btnManageShopGroups').onclick = function () {
|
||
setShopManageGroupGrantRoutes(['shop-manage']);
|
||
openShopManageGroupModal();
|
||
};
|
||
document.getElementById('btnManageShopGroupsFromEdit').onclick = function () {
|
||
setShopManageGroupGrantRoutes(['shop-manage']);
|
||
openShopManageGroupModal();
|
||
};
|
||
document.getElementById('btnManageSkipPriceAsinGroups').onclick = function () {
|
||
setShopManageGroupGrantRoutes(['skip-price-asin']);
|
||
openShopManageGroupModal();
|
||
};
|
||
document.getElementById('btnManageQueryAsinGroups').onclick = function () {
|
||
setShopManageGroupGrantRoutes(['query-asin']);
|
||
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 = resetShopManageGroupForm;
|
||
document.getElementById('btnSaveShopManageGroup').onclick = function () {
|
||
var editId = (document.getElementById('shopManageGroupEditId').value || '').trim();
|
||
var groupName = (document.getElementById('shopManageGroupInput').value || '').trim();
|
||
var memberUserIds = getSelectedShopManageGroupMemberIds();
|
||
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,
|
||
member_user_ids: memberUserIds,
|
||
grant_menu_routes: currentShopManageGroupGrantRoutes
|
||
})
|
||
})
|
||
.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', minimumPriceField: 'minimum_price_de', label: '德国' },
|
||
{ code: 'UK', field: 'asin_uk', minimumPriceField: 'minimum_price_uk', label: '英国' },
|
||
{ code: 'FR', field: 'asin_fr', minimumPriceField: 'minimum_price_fr', label: '法国' },
|
||
{ code: 'IT', field: 'asin_it', minimumPriceField: 'minimum_price_it', label: '意大利' },
|
||
{ code: 'ES', field: 'asin_es', minimumPriceField: 'minimum_price_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 = '<tr><td colspan="7" class="empty-tip">加载失败: ' + (res.error || '') + '</td></tr>';
|
||
return;
|
||
}
|
||
var items = res.items || [];
|
||
if (!items.length) {
|
||
tbody.innerHTML = '<tr><td colspan="6" class="empty-tip">暂无店铺</td></tr>';
|
||
} else {
|
||
tbody.innerHTML = items.map(function (item, index) {
|
||
var rowNo = (chooseSkipPriceAsinShopPage - 1) * chooseSkipPriceAsinShopPageSize + 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>' +
|
||
'<button class="btn btn-sm" type="button" data-choose-skip-price-asin-shop="' + item.id + '" data-shop-name="' + (item.shop_name || '').replace(/"/g, '"') + '" data-group-id="' + (item.group_id || '') + '">选择</button>' +
|
||
'</td></tr>';
|
||
}).join('');
|
||
}
|
||
renderPagination('chooseSkipPriceAsinShopPagination', res.total, res.page, res.page_size, loadChooseSkipPriceAsinShops);
|
||
bindChooseSkipPriceAsinShopActions();
|
||
})
|
||
.catch(function () {
|
||
document.getElementById('chooseSkipPriceAsinShopListBody').innerHTML = '<tr><td colspan="7" class="empty-tip">请求失败</td></tr>';
|
||
});
|
||
}
|
||
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 formatSkipPriceMinimumPrice(value) {
|
||
if (value === null || value === undefined || value === '') return '';
|
||
var num = Number(value);
|
||
if (!isFinite(num)) return String(value);
|
||
return num.toFixed(2);
|
||
}
|
||
function getSkipPriceCountryLabel(countryCode) {
|
||
var country = skipPriceCountryColumns.find(function (item) { return item.code === countryCode; });
|
||
return country ? country.label : countryCode;
|
||
}
|
||
function renderSkipPriceAsinInputs() {
|
||
var container = document.getElementById('skipPriceAsinInputs');
|
||
var selectedCountries = getSelectedSkipPriceCountries();
|
||
var existingValues = {};
|
||
var existingMinimumPrices = {};
|
||
container.querySelectorAll('[data-skip-price-country-input]').forEach(function (input) {
|
||
existingValues[input.getAttribute('data-skip-price-country-input')] = input.value;
|
||
});
|
||
container.querySelectorAll('[data-skip-price-country-minimum-price-input]').forEach(function (input) {
|
||
existingMinimumPrices[input.getAttribute('data-skip-price-country-minimum-price-input')] = input.value;
|
||
});
|
||
if (!selectedCountries.length) {
|
||
container.innerHTML = '<div style="color:#999;font-size:13px;">请选择国家后输入 ASIN 和最低价</div>';
|
||
return;
|
||
}
|
||
container.innerHTML = selectedCountries.map(function (countryCode) {
|
||
var label = getSkipPriceCountryLabel(countryCode);
|
||
var value = existingValues[countryCode] || '';
|
||
var minimumPrice = existingMinimumPrices[countryCode] || '';
|
||
return '<div style="display:flex;align-items:center;gap:8px;flex-wrap:wrap;">' +
|
||
'<span style="min-width:56px;color:#555;">' + label + '</span>' +
|
||
'<input type="text" data-skip-price-country-input="' + countryCode + '" value="' + value.replace(/"/g, '"') + '" placeholder="请输入' + label + ' ASIN" style="flex:1;min-width:180px;">' +
|
||
'<input type="number" data-skip-price-country-minimum-price-input="' + countryCode + '" value="' + minimumPrice.replace(/"/g, '"') + '" min="0" step="0.01" placeholder="最低价" style="width:140px;">' +
|
||
'</div>';
|
||
}).join('');
|
||
}
|
||
function collectSkipPriceAsinMappings(countries) {
|
||
var asinMappings = {};
|
||
var minimumPriceMappings = {};
|
||
for (var i = 0; i < countries.length; i++) {
|
||
var countryCode = countries[i];
|
||
var input = document.querySelector('[data-skip-price-country-input="' + countryCode + '"]');
|
||
var minimumPriceInput = document.querySelector('[data-skip-price-country-minimum-price-input="' + countryCode + '"]');
|
||
var asin = input ? (input.value || '').trim().toUpperCase() : '';
|
||
if (!asin) {
|
||
var label = getSkipPriceCountryLabel(countryCode);
|
||
throw new Error(label + ' ASIN 不能为空');
|
||
}
|
||
var minimumPrice = minimumPriceInput ? (minimumPriceInput.value || '').trim() : '';
|
||
if (minimumPrice) {
|
||
var minimumPriceNumber = Number(minimumPrice);
|
||
if (!isFinite(minimumPriceNumber) || minimumPriceNumber < 0) {
|
||
throw new Error(getSkipPriceCountryLabel(countryCode) + ' 最低价格式不正确');
|
||
}
|
||
minimumPriceMappings[countryCode] = minimumPrice;
|
||
}
|
||
asinMappings[countryCode] = asin;
|
||
}
|
||
return {
|
||
asinMappings: asinMappings,
|
||
minimumPriceMappings: minimumPriceMappings
|
||
};
|
||
}
|
||
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 asinValue = item[country.field] || '';
|
||
var minimumPriceValue = formatSkipPriceMinimumPrice(item[country.minimumPriceField]);
|
||
var hasValue = !!asinValue || !!minimumPriceValue;
|
||
var infoHtml = hasValue
|
||
? ('<div style="display:flex;flex-direction:column;gap:4px;min-width:0;">' +
|
||
'<span>' + (asinValue || '<span style="color:#999;">-</span>') + '</span>' +
|
||
'<span style="color:#666;font-size:12px;">最低价:' + (minimumPriceValue || '-') + '</span>' +
|
||
'</div>')
|
||
: '<span style="color:#999;">-</span>';
|
||
var deleteHtml = hasValue
|
||
? ('<button class="btn btn-sm btn-danger" data-skip-price-asin-delete="' + item.id + '" data-country="' + country.code + '" data-shop-name="' + (item.shop_name || '').replace(/"/g, '"') + '">删除</button>')
|
||
: '';
|
||
return '<div style="display:flex;align-items:center;gap:8px;flex-wrap:wrap;">' +
|
||
infoHtml +
|
||
'<button class="btn btn-sm" data-skip-price-asin-edit="' + item.id + '" data-country="' + country.code + '" data-asin="' + asinValue.replace(/"/g, '"') + '" data-minimum-price="' + minimumPriceValue.replace(/"/g, '"') + '" data-shop-name="' + (item.shop_name || '').replace(/"/g, '"') + '">编辑</button>' +
|
||
deleteHtml +
|
||
'</div>';
|
||
}
|
||
function openEditSkipPriceAsinModal(itemId, countryCode, shopName, asinValue, minimumPriceValue) {
|
||
document.getElementById('editSkipPriceAsinId').value = itemId || '';
|
||
document.getElementById('editSkipPriceAsinCountry').value = countryCode || '';
|
||
document.getElementById('editSkipPriceAsinShopName').value = shopName || '';
|
||
document.getElementById('editSkipPriceAsinCountryLabel').value = getSkipPriceCountryLabel(countryCode || '');
|
||
document.getElementById('editSkipPriceAsinValue').value = asinValue || '';
|
||
document.getElementById('editSkipPriceMinimumPrice').value = minimumPriceValue || '';
|
||
document.getElementById('msgEditSkipPriceAsin').textContent = '';
|
||
document.getElementById('msgEditSkipPriceAsin').className = 'msg';
|
||
document.getElementById('editSkipPriceAsinModal').classList.add('show');
|
||
}
|
||
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 currentMinimumPrice = (btn.dataset.minimumPrice || '').replace(/"/g, '"');
|
||
openEditSkipPriceAsinModal(
|
||
btn.dataset.skipPriceAsinEdit,
|
||
countryCode,
|
||
shopName,
|
||
currentAsin,
|
||
currentMinimumPrice
|
||
);
|
||
};
|
||
});
|
||
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 = '<tr><td colspan="8" class="empty-tip">加载失败: ' + (res.error || '') + '</td></tr>';
|
||
return;
|
||
}
|
||
var items = res.items || [];
|
||
if (items.length === 0) {
|
||
tbody.innerHTML = '<tr><td colspan="8" class="empty-tip">暂无数据</td></tr>';
|
||
} else {
|
||
tbody.innerHTML = items.map(function (item, index) {
|
||
var rowNo = (skipPriceAsinPage - 1) * skipPriceAsinPageSize + index + 1;
|
||
return '<tr><td>' + rowNo + '</td><td>' + (item.group_name || '') + '</td><td>' + (item.shop_name || '') + '</td>' +
|
||
skipPriceCountryColumns.map(function (country) {
|
||
return '<td>' + renderSkipPriceAsinCell(item, country) + '</td>';
|
||
}).join('') +
|
||
'</tr>';
|
||
}).join('');
|
||
}
|
||
renderPagination('skipPriceAsinPagination', res.total, res.page, res.page_size, loadSkipPriceAsin);
|
||
bindSkipPriceAsinActions();
|
||
})
|
||
.catch(function () {
|
||
document.getElementById('skipPriceAsinListBody').innerHTML = '<tr><td colspan="8" class="empty-tip">请求失败</td></tr>';
|
||
});
|
||
}
|
||
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('btnCloseEditSkipPriceAsin').onclick = function () {
|
||
document.getElementById('editSkipPriceAsinModal').classList.remove('show');
|
||
};
|
||
document.getElementById('btnSaveSkipPriceAsin').onclick = function () {
|
||
var itemId = (document.getElementById('editSkipPriceAsinId').value || '').trim();
|
||
var countryCode = (document.getElementById('editSkipPriceAsinCountry').value || '').trim();
|
||
var asin = (document.getElementById('editSkipPriceAsinValue').value || '').trim().toUpperCase();
|
||
var minimumPrice = (document.getElementById('editSkipPriceMinimumPrice').value || '').trim();
|
||
var msgEl = document.getElementById('msgEditSkipPriceAsin');
|
||
msgEl.textContent = '';
|
||
msgEl.className = 'msg';
|
||
if (!itemId || !countryCode) {
|
||
msgEl.textContent = '缺少编辑记录';
|
||
msgEl.className = 'msg err';
|
||
return;
|
||
}
|
||
if (!asin) {
|
||
msgEl.textContent = 'ASIN 不能为空';
|
||
msgEl.className = 'msg err';
|
||
return;
|
||
}
|
||
if (minimumPrice) {
|
||
var minimumPriceNumber = Number(minimumPrice);
|
||
if (!isFinite(minimumPriceNumber) || minimumPriceNumber < 0) {
|
||
msgEl.textContent = '最低价格式不正确';
|
||
msgEl.className = 'msg err';
|
||
return;
|
||
}
|
||
}
|
||
fetch('/api/admin/skip-price-asin/' + itemId + '/country/' + countryCode, {
|
||
method: 'PUT',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({
|
||
asin: asin,
|
||
minimum_price: minimumPrice || null
|
||
})
|
||
})
|
||
.then(function (r) { return r.json(); })
|
||
.then(function (res) {
|
||
if (!res.success) {
|
||
msgEl.textContent = res.error || '保存失败';
|
||
msgEl.className = 'msg err';
|
||
return;
|
||
}
|
||
document.getElementById('editSkipPriceAsinModal').classList.remove('show');
|
||
loadSkipPriceAsin(skipPriceAsinPage);
|
||
})
|
||
.catch(function () {
|
||
msgEl.textContent = '请求失败';
|
||
msgEl.className = 'msg err';
|
||
});
|
||
};
|
||
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 = {};
|
||
var minimumPriceMappings = {};
|
||
try {
|
||
var mappings = collectSkipPriceAsinMappings(countries);
|
||
asinMappings = mappings.asinMappings || {};
|
||
minimumPriceMappings = mappings.minimumPriceMappings || {};
|
||
} 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,
|
||
minimum_price_mappings: minimumPriceMappings
|
||
})
|
||
})
|
||
.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();
|
||
|
||
// ========== 查询 ASIN ==========
|
||
var queryAsinPage = 1, queryAsinPageSize = 15;
|
||
var chooseQueryAsinShopPage = 1, chooseQueryAsinShopPageSize = 10;
|
||
var queryAsinCountryColumns = [
|
||
{ 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 getQueryAsinCountryLabel(countryCode) {
|
||
var country = queryAsinCountryColumns.find(function (item) { return item.code === countryCode; });
|
||
return country ? country.label : countryCode;
|
||
}
|
||
function getSelectedQueryAsinCountries() {
|
||
return Array.from(document.getElementById('queryAsinCountries').selectedOptions).map(function (option) {
|
||
return option.value;
|
||
});
|
||
}
|
||
function renderQueryAsinInputs() {
|
||
var container = document.getElementById('queryAsinInputs');
|
||
var selectedCountries = getSelectedQueryAsinCountries();
|
||
var existingValues = {};
|
||
container.querySelectorAll('[data-query-asin-country-input]').forEach(function (input) {
|
||
existingValues[input.getAttribute('data-query-asin-country-input')] = input.value;
|
||
});
|
||
if (!selectedCountries.length) {
|
||
container.innerHTML = '<div style="color:#999;font-size:13px;">请选择国家后输入 ASIN</div>';
|
||
return;
|
||
}
|
||
container.innerHTML = selectedCountries.map(function (countryCode) {
|
||
var label = getQueryAsinCountryLabel(countryCode);
|
||
var value = existingValues[countryCode] || '';
|
||
return '<div style="display:flex;align-items:center;gap:8px;flex-wrap:wrap;">' +
|
||
'<span style="min-width:56px;color:#555;">' + label + '</span>' +
|
||
'<input type="text" data-query-asin-country-input="' + countryCode + '" value="' + value.replace(/"/g, '"') + '" placeholder="请输入' + label + ' ASIN" style="flex:1;min-width:180px;">' +
|
||
'</div>';
|
||
}).join('');
|
||
}
|
||
function collectQueryAsinMappings(countries) {
|
||
var asinMappings = {};
|
||
for (var i = 0; i < countries.length; i++) {
|
||
var countryCode = countries[i];
|
||
var input = document.querySelector('[data-query-asin-country-input="' + countryCode + '"]');
|
||
var asin = input ? (input.value || '').trim().toUpperCase() : '';
|
||
if (!asin) {
|
||
throw new Error(getQueryAsinCountryLabel(countryCode) + ' ASIN 不能为空');
|
||
}
|
||
asinMappings[countryCode] = asin;
|
||
}
|
||
return asinMappings;
|
||
}
|
||
function buildChooseQueryAsinShopQuery(page) {
|
||
var query = 'page=' + (page || 1) + '&page_size=' + chooseQueryAsinShopPageSize;
|
||
var groupId = (document.getElementById('chooseQueryAsinShopGroupId').value || '').trim();
|
||
var shopName = (document.getElementById('chooseQueryAsinShopKeyword').value || '').trim();
|
||
if (groupId) query += '&group_id=' + encodeURIComponent(groupId);
|
||
if (shopName) query += '&shop_name=' + encodeURIComponent(shopName);
|
||
return query;
|
||
}
|
||
function loadChooseQueryAsinShops(page) {
|
||
chooseQueryAsinShopPage = page || 1;
|
||
fetch('/api/admin/shop-manages?' + buildChooseQueryAsinShopQuery(chooseQueryAsinShopPage))
|
||
.then(function (r) { return r.json(); })
|
||
.then(function (res) {
|
||
var tbody = document.getElementById('chooseQueryAsinShopListBody');
|
||
if (!res.success) {
|
||
tbody.innerHTML = '<tr><td colspan="6" class="empty-tip">加载失败: ' + (res.error || '') + '</td></tr>';
|
||
return;
|
||
}
|
||
var items = res.items || [];
|
||
if (items.length === 0) {
|
||
tbody.innerHTML = '<tr><td colspan="6" class="empty-tip">暂无店铺</td></tr>';
|
||
} else {
|
||
tbody.innerHTML = items.map(function (item, index) {
|
||
var rowNo = (chooseQueryAsinShopPage - 1) * chooseQueryAsinShopPageSize + index + 1;
|
||
return '<tr><td>' + rowNo + '</td><td>' + (item.group_name || '') + '</td><td>' + (item.shop_name || '') + '</td><td>' + (item.mall_name || '') + '</td><td>' + (item.username || '') + '</td><td>' +
|
||
'<button class="btn btn-sm" type="button" data-choose-query-asin-shop="' + item.id + '" data-shop-name="' + (item.shop_name || '').replace(/"/g, '"') + '" data-group-id="' + (item.group_id || '') + '">选择</button>' +
|
||
'</td></tr>';
|
||
}).join('');
|
||
}
|
||
renderPagination('chooseQueryAsinShopPagination', res.total, res.page, res.page_size, loadChooseQueryAsinShops);
|
||
bindChooseQueryAsinShopActions();
|
||
})
|
||
.catch(function () {
|
||
document.getElementById('chooseQueryAsinShopListBody').innerHTML = '<tr><td colspan="6" class="empty-tip">请求失败</td></tr>';
|
||
});
|
||
}
|
||
function bindChooseQueryAsinShopActions() {
|
||
document.querySelectorAll('[data-choose-query-asin-shop]').forEach(function (btn) {
|
||
btn.onclick = function () {
|
||
document.getElementById('queryAsinShopName').value = (btn.dataset.shopName || '').replace(/"/g, '"');
|
||
if (btn.dataset.groupId) {
|
||
document.getElementById('queryAsinGroupSelect').value = btn.dataset.groupId;
|
||
}
|
||
document.getElementById('chooseQueryAsinShopModal').classList.remove('show');
|
||
};
|
||
});
|
||
}
|
||
function openChooseQueryAsinShopModal() {
|
||
var currentGroupId = (document.getElementById('queryAsinGroupSelect').value || '').trim();
|
||
if (currentGroupId) {
|
||
document.getElementById('chooseQueryAsinShopGroupId').value = currentGroupId;
|
||
}
|
||
document.getElementById('chooseQueryAsinShopKeyword').value = (document.getElementById('queryAsinShopName').value || '').trim();
|
||
document.getElementById('chooseQueryAsinShopModal').classList.add('show');
|
||
loadChooseQueryAsinShops(1);
|
||
}
|
||
function buildQueryAsinQuery(page) {
|
||
var query = 'page=' + (page || 1) + '&page_size=' + queryAsinPageSize;
|
||
var groupId = (document.getElementById('queryAsinFilterGroupId').value || '').trim();
|
||
var shopName = (document.getElementById('queryAsinFilterShopName').value || '').trim();
|
||
var asin = (document.getElementById('queryAsinFilterAsin').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 renderQueryAsinCell(item, country) {
|
||
var asinValue = item[country.field] || '';
|
||
var infoHtml = asinValue ? '<span>' + asinValue + '</span>' : '<span style="color:#999;">-</span>';
|
||
var deleteHtml = asinValue
|
||
? ('<button class="btn btn-sm btn-danger" data-query-asin-delete="' + item.id + '" data-country="' + country.code + '" data-shop-name="' + (item.shop_name || '').replace(/"/g, '"') + '">删除</button>')
|
||
: '';
|
||
return '<div style="display:flex;align-items:center;gap:8px;flex-wrap:wrap;">' +
|
||
infoHtml +
|
||
'<button class="btn btn-sm" data-query-asin-edit="' + item.id + '" data-country="' + country.code + '" data-asin="' + asinValue.replace(/"/g, '"') + '" data-shop-name="' + (item.shop_name || '').replace(/"/g, '"') + '">编辑</button>' +
|
||
deleteHtml +
|
||
'</div>';
|
||
}
|
||
function openEditQueryAsinModal(itemId, countryCode, shopName, asinValue) {
|
||
document.getElementById('editQueryAsinId').value = itemId || '';
|
||
document.getElementById('editQueryAsinCountry').value = countryCode || '';
|
||
document.getElementById('editQueryAsinShopName').value = shopName || '';
|
||
document.getElementById('editQueryAsinCountryLabel').value = getQueryAsinCountryLabel(countryCode || '');
|
||
document.getElementById('editQueryAsinValue').value = asinValue || '';
|
||
document.getElementById('msgEditQueryAsin').textContent = '';
|
||
document.getElementById('msgEditQueryAsin').className = 'msg';
|
||
document.getElementById('editQueryAsinModal').classList.add('show');
|
||
}
|
||
function bindQueryAsinActions() {
|
||
document.querySelectorAll('[data-query-asin-edit]').forEach(function (btn) {
|
||
btn.onclick = function () {
|
||
openEditQueryAsinModal(
|
||
btn.dataset.queryAsinEdit,
|
||
btn.dataset.country || '',
|
||
(btn.dataset.shopName || '').replace(/"/g, '"'),
|
||
(btn.dataset.asin || '').replace(/"/g, '"')
|
||
);
|
||
};
|
||
});
|
||
document.querySelectorAll('[data-query-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/query-asin/' + btn.dataset.queryAsinDelete + '/country/' + countryCode, {
|
||
method: 'DELETE'
|
||
})
|
||
.then(function (r) { return r.json(); })
|
||
.then(function (res) {
|
||
if (res.success) loadQueryAsin(queryAsinPage);
|
||
else alert(res.error || '删除失败');
|
||
});
|
||
};
|
||
});
|
||
}
|
||
function loadQueryAsin(page) {
|
||
queryAsinPage = page || 1;
|
||
fetch('/api/admin/query-asins?' + buildQueryAsinQuery(queryAsinPage))
|
||
.then(function (r) { return r.json(); })
|
||
.then(function (res) {
|
||
var tbody = document.getElementById('queryAsinListBody');
|
||
if (!res.success) {
|
||
tbody.innerHTML = '<tr><td colspan="8" class="empty-tip">加载失败: ' + (res.error || '') + '</td></tr>';
|
||
return;
|
||
}
|
||
var items = res.items || [];
|
||
if (items.length === 0) {
|
||
tbody.innerHTML = '<tr><td colspan="8" class="empty-tip">暂无数据</td></tr>';
|
||
} else {
|
||
tbody.innerHTML = items.map(function (item, index) {
|
||
var rowNo = (queryAsinPage - 1) * queryAsinPageSize + index + 1;
|
||
return '<tr><td>' + rowNo + '</td><td>' + (item.group_name || '') + '</td><td>' + (item.shop_name || '') + '</td>' +
|
||
queryAsinCountryColumns.map(function (country) {
|
||
return '<td>' + renderQueryAsinCell(item, country) + '</td>';
|
||
}).join('') +
|
||
'</tr>';
|
||
}).join('');
|
||
}
|
||
renderPagination('queryAsinPagination', res.total, res.page, res.page_size, loadQueryAsin);
|
||
bindQueryAsinActions();
|
||
})
|
||
.catch(function () {
|
||
document.getElementById('queryAsinListBody').innerHTML = '<tr><td colspan="8" class="empty-tip">请求失败</td></tr>';
|
||
});
|
||
}
|
||
var queryAsinImportPollTimer = null;
|
||
var queryAsinDeleteImportPollTimer = null;
|
||
function stopQueryAsinImportProgress() {
|
||
if (queryAsinImportPollTimer) {
|
||
clearInterval(queryAsinImportPollTimer);
|
||
queryAsinImportPollTimer = null;
|
||
}
|
||
}
|
||
function stopQueryAsinDeleteImportProgress() {
|
||
if (queryAsinDeleteImportPollTimer) {
|
||
clearInterval(queryAsinDeleteImportPollTimer);
|
||
queryAsinDeleteImportPollTimer = null;
|
||
}
|
||
}
|
||
function setQueryAsinImportProgress(percent, text) {
|
||
var wrap = document.getElementById('queryAsinImportProgressWrap');
|
||
var fill = document.getElementById('queryAsinImportProgressFill');
|
||
var textEl = document.getElementById('queryAsinImportProgressText');
|
||
wrap.style.display = 'block';
|
||
fill.style.width = Math.max(0, Math.min(100, percent || 0)) + '%';
|
||
textEl.textContent = text || '';
|
||
}
|
||
function setQueryAsinDeleteImportProgress(percent, text) {
|
||
var wrap = document.getElementById('queryAsinDeleteImportProgressWrap');
|
||
var fill = document.getElementById('queryAsinDeleteImportProgressFill');
|
||
var textEl = document.getElementById('queryAsinDeleteImportProgressText');
|
||
wrap.style.display = 'block';
|
||
fill.style.width = Math.max(0, Math.min(100, percent || 0)) + '%';
|
||
textEl.textContent = text || '';
|
||
}
|
||
function pollQueryAsinImport(importId) {
|
||
stopQueryAsinImportProgress();
|
||
function tick() {
|
||
fetch('/api/admin/query-asins/import/' + encodeURIComponent(importId))
|
||
.then(function (r) { return r.json(); })
|
||
.then(function (res) {
|
||
if (!res.success) {
|
||
stopQueryAsinImportProgress();
|
||
document.getElementById('msgQueryAsinImport').textContent = res.error || '查询导入进度失败';
|
||
document.getElementById('msgQueryAsinImport').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;
|
||
setQueryAsinImportProgress(percent, '处理中:已处理 ' + processedRows + ' / ' + totalRows + ',添加 ' + (progress.inserted_count || 0) + ',跳过 ' + (progress.skipped_count || 0));
|
||
if (progress.status === 'success') {
|
||
stopQueryAsinImportProgress();
|
||
document.getElementById('msgQueryAsinImport').textContent = '导入成功:总行数 ' + totalRows + ',ASIN 数量 ' + (progress.asin_count || 0) + ',添加 ' + (progress.inserted_count || 0) + ',跳过 ' + (progress.skipped_count || 0);
|
||
document.getElementById('msgQueryAsinImport').className = 'msg ok';
|
||
loadQueryAsin(1);
|
||
} else if (progress.status === 'failed') {
|
||
stopQueryAsinImportProgress();
|
||
document.getElementById('msgQueryAsinImport').textContent = progress.error_message || '导入失败';
|
||
document.getElementById('msgQueryAsinImport').className = 'msg err';
|
||
}
|
||
})
|
||
.catch(function () {
|
||
stopQueryAsinImportProgress();
|
||
document.getElementById('msgQueryAsinImport').textContent = '查询导入进度失败';
|
||
document.getElementById('msgQueryAsinImport').className = 'msg err';
|
||
});
|
||
}
|
||
tick();
|
||
queryAsinImportPollTimer = setInterval(tick, 1000);
|
||
}
|
||
function pollQueryAsinDeleteImport(importId) {
|
||
stopQueryAsinDeleteImportProgress();
|
||
function tick() {
|
||
fetch('/api/admin/query-asins/delete-import/' + encodeURIComponent(importId))
|
||
.then(function (r) { return r.json(); })
|
||
.then(function (res) {
|
||
if (!res.success) {
|
||
stopQueryAsinDeleteImportProgress();
|
||
document.getElementById('msgQueryAsinDeleteImport').textContent = res.error || '查询删除进度失败';
|
||
document.getElementById('msgQueryAsinDeleteImport').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;
|
||
setQueryAsinDeleteImportProgress(percent, '处理中:已处理 ' + processedRows + ' / ' + totalRows + ',删除 ' + (progress.deleted_count || 0) + ',跳过 ' + (progress.skipped_count || 0));
|
||
if (progress.status === 'success') {
|
||
stopQueryAsinDeleteImportProgress();
|
||
document.getElementById('msgQueryAsinDeleteImport').textContent = '删除成功:总行数 ' + totalRows + ',ASIN 数量 ' + (progress.asin_count || 0) + ',删除 ' + (progress.deleted_count || 0) + ',跳过 ' + (progress.skipped_count || 0);
|
||
document.getElementById('msgQueryAsinDeleteImport').className = 'msg ok';
|
||
loadQueryAsin(1);
|
||
} else if (progress.status === 'failed') {
|
||
stopQueryAsinDeleteImportProgress();
|
||
document.getElementById('msgQueryAsinDeleteImport').textContent = progress.error_message || '删除失败';
|
||
document.getElementById('msgQueryAsinDeleteImport').className = 'msg err';
|
||
}
|
||
})
|
||
.catch(function () {
|
||
stopQueryAsinDeleteImportProgress();
|
||
document.getElementById('msgQueryAsinDeleteImport').textContent = '查询删除进度失败';
|
||
document.getElementById('msgQueryAsinDeleteImport').className = 'msg err';
|
||
});
|
||
}
|
||
tick();
|
||
queryAsinDeleteImportPollTimer = setInterval(tick, 1000);
|
||
}
|
||
function uploadQueryAsinImport(deleteMode) {
|
||
var fileInput = document.getElementById(deleteMode ? 'queryAsinDeleteImportFile' : 'queryAsinImportFile');
|
||
var msgEl = document.getElementById(deleteMode ? 'msgQueryAsinDeleteImport' : 'msgQueryAsinImport');
|
||
msgEl.textContent = '';
|
||
msgEl.className = 'msg';
|
||
if (deleteMode) {
|
||
stopQueryAsinDeleteImportProgress();
|
||
document.getElementById('queryAsinDeleteImportProgressWrap').style.display = 'none';
|
||
} else {
|
||
stopQueryAsinImportProgress();
|
||
document.getElementById('queryAsinImportProgressWrap').style.display = 'none';
|
||
}
|
||
if (!fileInput.files || fileInput.files.length === 0) {
|
||
msgEl.textContent = '请选择 Excel 文件';
|
||
msgEl.className = 'msg err';
|
||
return;
|
||
}
|
||
var file = fileInput.files[0];
|
||
var lowerName = (file.name || '').toLowerCase();
|
||
if (!(lowerName.endsWith('.xlsx') || lowerName.endsWith('.xls'))) {
|
||
msgEl.textContent = '仅支持 .xlsx 或 .xls 文件';
|
||
msgEl.className = 'msg err';
|
||
return;
|
||
}
|
||
if (deleteMode && !confirm('确定按 Excel 中的分组、店铺名和国家 ASIN 批量删除吗?')) {
|
||
return;
|
||
}
|
||
var formData = new FormData();
|
||
formData.append('file', file);
|
||
var groupId = (document.getElementById('queryAsinGroupSelect').value || '').trim();
|
||
if (groupId) formData.append('group_id', groupId);
|
||
var xhr = new XMLHttpRequest();
|
||
xhr.open('POST', deleteMode ? '/api/admin/query-asins/delete-import' : '/api/admin/query-asins/import', true);
|
||
xhr.upload.onprogress = function (event) {
|
||
if (event.lengthComputable) {
|
||
var percent = Math.round(event.loaded * 100 / event.total);
|
||
if (deleteMode) setQueryAsinDeleteImportProgress(percent, '上传中:' + percent + '%');
|
||
else setQueryAsinImportProgress(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 || (deleteMode ? '删除失败' : '导入失败');
|
||
msgEl.className = 'msg err';
|
||
return;
|
||
}
|
||
if (deleteMode) {
|
||
setQueryAsinDeleteImportProgress(100, '上传完成,后端处理中...');
|
||
pollQueryAsinDeleteImport(res.import_id);
|
||
} else {
|
||
setQueryAsinImportProgress(100, '上传完成,后端处理中...');
|
||
pollQueryAsinImport(res.import_id);
|
||
}
|
||
fileInput.value = '';
|
||
};
|
||
xhr.onerror = function () {
|
||
msgEl.textContent = '请求失败';
|
||
msgEl.className = 'msg err';
|
||
};
|
||
xhr.send(formData);
|
||
}
|
||
document.getElementById('btnChooseQueryAsinShop').onclick = openChooseQueryAsinShopModal;
|
||
document.getElementById('btnSearchChooseQueryAsinShop').onclick = function () {
|
||
loadChooseQueryAsinShops(1);
|
||
};
|
||
document.getElementById('chooseQueryAsinShopGroupId').onchange = function () {
|
||
loadChooseQueryAsinShops(1);
|
||
};
|
||
document.getElementById('chooseQueryAsinShopKeyword').addEventListener('keydown', function (e) {
|
||
if (e.key === 'Enter') {
|
||
e.preventDefault();
|
||
loadChooseQueryAsinShops(1);
|
||
}
|
||
});
|
||
document.getElementById('btnCloseChooseQueryAsinShopModal').onclick = function () {
|
||
document.getElementById('chooseQueryAsinShopModal').classList.remove('show');
|
||
};
|
||
document.getElementById('btnCloseEditQueryAsin').onclick = function () {
|
||
document.getElementById('editQueryAsinModal').classList.remove('show');
|
||
};
|
||
document.getElementById('btnSaveQueryAsin').onclick = function () {
|
||
var itemId = (document.getElementById('editQueryAsinId').value || '').trim();
|
||
var countryCode = (document.getElementById('editQueryAsinCountry').value || '').trim();
|
||
var asin = (document.getElementById('editQueryAsinValue').value || '').trim().toUpperCase();
|
||
var msgEl = document.getElementById('msgEditQueryAsin');
|
||
msgEl.textContent = '';
|
||
msgEl.className = 'msg';
|
||
if (!itemId || !countryCode) {
|
||
msgEl.textContent = '缺少编辑记录';
|
||
msgEl.className = 'msg err';
|
||
return;
|
||
}
|
||
if (!asin) {
|
||
msgEl.textContent = 'ASIN 不能为空';
|
||
msgEl.className = 'msg err';
|
||
return;
|
||
}
|
||
fetch('/api/admin/query-asin/' + itemId + '/country/' + countryCode, {
|
||
method: 'PUT',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({ asin: asin })
|
||
})
|
||
.then(function (r) { return r.json(); })
|
||
.then(function (res) {
|
||
if (!res.success) {
|
||
msgEl.textContent = res.error || '保存失败';
|
||
msgEl.className = 'msg err';
|
||
return;
|
||
}
|
||
document.getElementById('editQueryAsinModal').classList.remove('show');
|
||
loadQueryAsin(queryAsinPage);
|
||
})
|
||
.catch(function () {
|
||
msgEl.textContent = '请求失败';
|
||
msgEl.className = 'msg err';
|
||
});
|
||
};
|
||
document.getElementById('queryAsinCountries').addEventListener('change', renderQueryAsinInputs);
|
||
document.getElementById('btnSearchQueryAsin').onclick = function () {
|
||
loadQueryAsin(1);
|
||
};
|
||
document.getElementById('queryAsinFilterShopName').addEventListener('keydown', function (e) {
|
||
if (e.key === 'Enter') {
|
||
e.preventDefault();
|
||
loadQueryAsin(1);
|
||
}
|
||
});
|
||
document.getElementById('queryAsinFilterAsin').addEventListener('keydown', function (e) {
|
||
if (e.key === 'Enter') {
|
||
e.preventDefault();
|
||
loadQueryAsin(1);
|
||
}
|
||
});
|
||
document.getElementById('btnCreateQueryAsin').onclick = function () {
|
||
var groupId = (document.getElementById('queryAsinGroupSelect').value || '').trim();
|
||
var shopName = (document.getElementById('queryAsinShopName').value || '').trim();
|
||
var countries = getSelectedQueryAsinCountries();
|
||
var msgEl = document.getElementById('msgQueryAsin');
|
||
msgEl.textContent = '';
|
||
msgEl.className = 'msg';
|
||
if (!groupId || !shopName || !countries.length) {
|
||
msgEl.textContent = '请完整填写分组、店铺名、国家和 ASIN';
|
||
msgEl.className = 'msg err';
|
||
return;
|
||
}
|
||
var asinMappings = {};
|
||
try {
|
||
asinMappings = collectQueryAsinMappings(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/query-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('queryAsinGroupSelect').value = '';
|
||
document.getElementById('queryAsinShopName').value = '';
|
||
Array.from(document.getElementById('queryAsinCountries').options).forEach(function (option) {
|
||
option.selected = false;
|
||
});
|
||
renderQueryAsinInputs();
|
||
msgEl.textContent = res.msg || '保存成功';
|
||
msgEl.className = 'msg ok';
|
||
loadQueryAsin(1);
|
||
})
|
||
.catch(function () {
|
||
msgEl.textContent = '请求失败';
|
||
msgEl.className = 'msg err';
|
||
});
|
||
};
|
||
document.getElementById('btnImportQueryAsin').onclick = function () {
|
||
uploadQueryAsinImport(false);
|
||
};
|
||
document.getElementById('btnDeleteImportQueryAsin').onclick = function () {
|
||
uploadQueryAsinImport(true);
|
||
};
|
||
renderQueryAsinInputs();
|
||
|
||
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 = '<tr><td colspan="4" class="empty-tip">加载失败: ' + (res.error || '') + '</td></tr>';
|
||
return;
|
||
}
|
||
var items = res.items || [];
|
||
if (items.length === 0) {
|
||
tbody.innerHTML = '<tr><td colspan="4" class="empty-tip">暂无版本记录</td></tr>';
|
||
} else {
|
||
tbody.innerHTML = items.map(function (v) {
|
||
var url = (v.file_url || '').replace(/"/g, '"');
|
||
return '<tr><td>' + (v.version || '') + '</td><td><a href="' + url + '" target="_blank" rel="noopener">' + url + '</a></td><td>' + (v.created_at || '') + '</td><td><a href="' + url + '" download class="btn btn-sm">下载</a></td></tr>';
|
||
}).join('');
|
||
}
|
||
})
|
||
.catch(function () {
|
||
document.getElementById('versionListBody').innerHTML = '<tr><td colspan="4" class="empty-tip">请求失败</td></tr>';
|
||
});
|
||
}
|
||
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 = '<tr><td colspan="5" class="empty-tip">加载失败: ' + (res.error || '') + '</td></tr>';
|
||
return;
|
||
}
|
||
allColumnsList = res.items || [];
|
||
if (allColumnsList.length === 0) {
|
||
tbody.innerHTML = '<tr><td colspan="5" class="empty-tip">暂无菜单,请在上方新增</td></tr>';
|
||
} else {
|
||
tbody.innerHTML = allColumnsList.map(function (c) {
|
||
return '<tr><td>' + c.id + '</td><td>' + (c.name || '') + '</td><td>' + (c.column_key || '') + '</td><td>' + (c.created_at || '') + '</td><td>' +
|
||
'<button class="btn btn-sm" data-column-edit="' + c.id + '" data-name="' + (c.name || '').replace(/"/g, '"') + '" data-key="' + (c.column_key || '').replace(/"/g, '"') + '">编辑</button> ' +
|
||
'<button class="btn btn-sm btn-danger" data-column-delete="' + c.id + '" data-name="' + (c.name || '').replace(/"/g, '"') + '">删除</button></td></tr>';
|
||
}).join('');
|
||
}
|
||
bindColumnActions();
|
||
})
|
||
.catch(function () {
|
||
document.getElementById('columnListBody').innerHTML = '<tr><td colspan="5" class="empty-tip">请求失败</td></tr>';
|
||
});
|
||
}
|
||
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 = '<tr><td colspan="7" class="empty-tip">加载失败: ' + (res.error || '') + '</td></tr>';
|
||
return;
|
||
}
|
||
allColumnsList = res.items || [];
|
||
if (allColumnsList.length === 0) {
|
||
tbody.innerHTML = '<tr><td colspan="8" class="empty-tip">暂无菜单,请先在上方新增</td></tr>';
|
||
} else {
|
||
tbody.innerHTML = allColumnsList.map(function (c, index) {
|
||
var moveButtons =
|
||
'<button class="btn btn-sm btn-secondary" data-column-move-up="' + c.id + '"' + (index === 0 ? ' disabled' : '') + '>上移</button> ' +
|
||
'<button class="btn btn-sm btn-secondary" data-column-move-down="' + c.id + '"' + (index === allColumnsList.length - 1 ? ' disabled' : '') + '>下移</button> ';
|
||
return '<tr><td>' + c.id + '</td><td>' + (c.name || '') + '</td><td>' + (c.column_key || '') + '</td><td>' + ((c.menu_type || '') === 'admin' ? '后台(admin)' : '软件(app)') + '</td><td>' + (c.sort_order != null ? c.sort_order : 0) + '</td><td>' + (c.route_path || '') + '</td><td>' + (c.created_at || '') + '</td><td>' +
|
||
moveButtons +
|
||
'<button class="btn btn-sm" data-column-edit="' + c.id + '" data-name="' + (c.name || '').replace(/"/g, '"') + '" data-key="' + (c.column_key || '').replace(/"/g, '"') + '" data-route="' + (c.route_path || '').replace(/"/g, '"') + '" data-menu-type="' + (c.menu_type || 'app').replace(/"/g, '"') + '" data-sort-order="' + (c.sort_order != null ? String(c.sort_order) : '0').replace(/"/g, '"') + '">编辑</button> ' +
|
||
'<button class="btn btn-sm btn-danger" data-column-delete="' + c.id + '" data-name="' + (c.name || '').replace(/"/g, '"') + '">删除</button></td></tr>';
|
||
}).join('');
|
||
}
|
||
bindColumnActions();
|
||
})
|
||
.catch(function () {
|
||
document.getElementById('columnListBody').innerHTML = '<tr><td colspan="8" class="empty-tip">请求失败</td></tr>';
|
||
});
|
||
}
|
||
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 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;
|
||
fetch('/api/admin/column/reorder', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({
|
||
column_id: currentItem.id,
|
||
target_id: targetItem.id
|
||
})
|
||
}).then(function (r) { return r.json(); })
|
||
.then(function (res) {
|
||
if (!res || !res.success) {
|
||
alert((res && res.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 = '<span>共' + total + ' 条</span>' +
|
||
'<button ' + (page <= 1 ? 'disabled' : '') + ' data-p="' + (page - 1) + '">上一页</button>' +
|
||
'<span>第' + page + ' / ' + totalPages + ' 页</span>' +
|
||
'<button ' + (page >= totalPages ? 'disabled' : '') + ' data-p="' + (page + 1) + '">下一页</button>';
|
||
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 || {};
|
||
currentUserId = item.id || currentUserId;
|
||
currentUserRole = item.role || currentUserRole;
|
||
currentUserUsername = item.username || currentUserUsername;
|
||
nameEl.textContent = item.username || '管理员';
|
||
if (item.role) {
|
||
roleEl.textContent = item.role === 'super_admin'
|
||
? '超级管理员'
|
||
: (item.role === 'admin' ? '管理员' : '普通账号');
|
||
roleEl.style.display = 'inline-block';
|
||
} else {
|
||
roleEl.style.display = 'none';
|
||
}
|
||
updateShopManageGroupButtonsAccess();
|
||
})
|
||
.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';
|
||
updateShopManageGroupButtonsAccess();
|
||
})
|
||
.catch(function () {
|
||
document.getElementById('adminCurrentUsername').textContent = '当前用户';
|
||
document.getElementById('adminCurrentUserRole').style.display = 'none';
|
||
updateShopManageGroupButtonsAccess();
|
||
});
|
||
});
|
||
}
|
||
|
||
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.replace(res.redirect || '/login?logout=1');
|
||
})
|
||
.catch(function () {
|
||
window.location.replace('/logout');
|
||
});
|
||
};
|
||
|
||
loadAdminCurrentUser();
|
||
loadUserOptions();
|
||
loadColumnsForPermission();
|
||
loadShopManageGroups();
|
||
loadAdminMenus();
|
||
}) ();
|
||
</script>
|
||
</body>
|
||
|
||
</html>
|