Files
crawler-plugin/backend/web_source/admin.html
T
huangzd1997 8241cd704e
Build Backend JAR / build (push) Has been cancelled
feat(admin): A1 管理后台收敛 Java 单后台完整实现并修复 guard 误拦内部令牌
- Java 补齐后台全部迁移差集:shopduplicatecheck 店铺数据重复检查模块(V108 扫描表+查询/扫描服务)、
  PinyinAbbrUtil 拼音缩写、ImageHistory 接口调整为内部可用、AdminUser 支持内部令牌操作并放宽列表上限
- Flask 后台 admin_api.py 路由收敛转发 Java、admin.html/admin.js 适配新后台形态
- AdminApiGuardFilter 对可信 X-Internal-Token 放行(controller 自校验兜底),修复客户端仅凭
  内部令牌调用 /api/admin/shop-manages/credential 被误拦 401
- 测试:AdminApiGuardFilterTest 补可信/假令牌用例;AdminUserServiceTest 补菜单权限 mock;
  shopduplicatecheck 新增查询/聚合/CSV 单测
2026-09-04 11:30:09 +08:00

6631 lines
252 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="icon" href="data:,">
<title>管理后台 - 数富AI</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
:root {
--c-primary: #6366f1;
--c-primary-strong: #5457e8;
--c-primary-soft: #eef0fe;
--c-bg: #f2f4f8;
--c-card: #ffffff;
--c-border: #e5e8f0;
--c-text: #1c2333;
--c-text-2: #5d6675;
--c-text-3: #98a1b3;
--c-danger: #e5484d;
--c-danger-soft: #fdecec;
--c-success: #18a058;
--c-warning: #d97706;
--c-sidebar: #181d2b;
--c-sidebar-2: #212839;
--c-sidebar-text: #a8b1c7;
--radius: 12px;
--radius-sm: 8px;
--shadow-card: 0 1px 2px rgba(16, 24, 40, 0.04), 0 6px 16px -8px rgba(16, 24, 40, 0.08);
--shadow-pop: 0 12px 32px rgba(16, 24, 40, 0.14);
}
body {
font-family: "Microsoft YaHei", "PingFang SC", "Helvetica Neue", sans-serif;
background: var(--c-bg);
color: var(--c-text);
}
.admin-layout {
display: grid;
grid-template-columns: 236px minmax(0, 1fr);
min-height: 100vh;
}
/* ===== 侧边栏 ===== */
.admin-sidebar {
display: flex;
flex-direction: column;
background: linear-gradient(180deg, var(--c-sidebar-2) 0%, var(--c-sidebar) 100%);
color: var(--c-sidebar-text);
border-right: 1px solid rgba(255, 255, 255, 0.05);
position: sticky;
top: 0;
height: 100vh;
overflow: hidden;
z-index: 30;
}
.admin-brand {
display: flex;
align-items: center;
gap: 10px;
padding: 18px 16px 16px;
border-bottom: 1px solid rgba(255, 255, 255, 0.06);
}
.admin-brand-mark {
flex: 0 0 auto;
width: 32px;
height: 32px;
border-radius: 9px;
overflow: hidden;
background: #fff;
box-shadow: 0 4px 10px rgba(99, 102, 241, 0.35);
}
.admin-brand-logo {
display: block;
width: 100%;
height: 100%;
object-fit: cover;
}
.admin-brand-text {
min-width: 0;
line-height: 1.2;
}
.admin-brand-name {
display: block;
color: #fff;
font-size: 15px;
font-weight: 600;
letter-spacing: 0.5px;
white-space: nowrap;
}
.admin-brand-sub {
display: block;
font-size: 11px;
color: #7b8398;
margin-top: 2px;
white-space: nowrap;
}
.admin-menu {
flex: 1;
overflow-y: auto;
overflow-x: hidden;
padding: 6px 10px 16px;
}
.admin-menu::-webkit-scrollbar {
width: 6px;
}
.admin-menu::-webkit-scrollbar-thumb {
background: rgba(255, 255, 255, 0.12);
border-radius: 99px;
}
.menu-group {
margin-top: 10px;
}
.menu-group:first-child {
margin-top: 10px;
}
.menu-group-title {
display: flex;
align-items: center;
width: 100%;
padding: 9px 11px;
border: 0;
border-radius: 8px;
background: transparent;
color: #8a93ab;
font-family: inherit;
font-size: 12px;
font-weight: 600;
letter-spacing: 1.2px;
cursor: pointer;
text-align: left;
user-select: none;
transition: color 0.15s ease, background 0.15s ease;
}
.menu-group-title:hover {
color: #c3cadd;
background: rgba(255, 255, 255, 0.04);
}
.menu-group-title:focus-visible {
outline: 2px solid #8b8ff5;
outline-offset: -2px;
}
.menu-group-chevron {
flex: 0 0 auto;
display: inline-flex;
width: 13px;
height: 13px;
margin-left: auto;
color: currentColor;
transition: transform 0.18s ease;
}
.menu-group-chevron svg {
width: 13px;
height: 13px;
}
.menu-group.is-collapsed .menu-group-chevron {
transform: rotate(-90deg);
}
.menu-group.is-collapsed .menu-group-body {
display: none;
}
.menu-group-body {
padding: 3px 0 6px;
}
.menu-empty {
padding: 24px 12px;
text-align: center;
color: #69718a;
font-size: 13px;
}
/* 菜单项(保留 .tab 类名兼容权限显隐逻辑) */
.tab {
display: flex;
align-items: center;
gap: 10px;
padding: 9px 12px;
margin-bottom: 2px;
border-radius: 8px;
color: var(--c-sidebar-text);
font-size: 13.5px;
cursor: pointer;
position: relative;
user-select: none;
transition: background 0.15s ease, color 0.15s ease;
}
.tab:hover {
background: rgba(255, 255, 255, 0.06);
color: #fff;
}
.tab.active {
background: rgba(102, 126, 234, 0.20);
color: #fff;
font-weight: 600;
}
.tab.active::before {
content: "";
position: absolute;
left: 0;
top: 22%;
bottom: 22%;
width: 3px;
border-radius: 99px;
background: #8b8ff5;
}
.adm-icon {
flex: 0 0 auto;
display: inline-flex;
width: 18px;
height: 18px;
color: #8991a6;
}
.tab:hover .adm-icon,
.tab.active .adm-icon {
color: #8fa0f3;
}
.adm-icon svg {
width: 18px;
height: 18px;
}
.adm-label {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.admin-sidebar-foot {
padding: 12px 16px;
font-size: 11px;
color: #59627a;
border-top: 1px solid rgba(255, 255, 255, 0.06);
white-space: nowrap;
}
/* ===== 主内容区 ===== */
.admin-main {
min-width: 0;
display: flex;
flex-direction: column;
}
.admin-topbar {
position: sticky;
top: 0;
z-index: 20;
height: 56px;
background: rgba(255, 255, 255, 0.92);
backdrop-filter: blur(8px);
border-bottom: 1px solid var(--c-border);
display: flex;
align-items: center;
justify-content: space-between;
gap: 16px;
padding: 0 24px;
}
.admin-page-title {
font-size: 16px;
font-weight: 600;
color: var(--c-text);
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.admin-user-box {
display: flex;
align-items: center;
gap: 10px;
flex: 0 0 auto;
}
.admin-user-name {
font-size: 13px;
font-weight: 500;
color: var(--c-text);
max-width: 220px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.admin-user-role {
font-size: 12px;
color: var(--c-primary-strong);
background: var(--c-primary-soft);
padding: 3px 9px;
border-radius: 999px;
white-space: nowrap;
font-weight: 500;
}
.admin-user-logout {
padding: 6px 14px;
font-size: 12.5px;
font-weight: 500;
background: #fff;
color: var(--c-text-2);
border: 1px solid var(--c-border);
border-radius: var(--radius-sm);
cursor: pointer;
white-space: nowrap;
transition: color 0.15s ease, border-color 0.15s ease, background 0.15s ease;
}
.admin-user-logout:hover {
color: var(--c-primary);
border-color: #b9bcf3;
background: var(--c-primary-soft);
opacity: 1;
}
.admin-content {
flex: 1;
padding: 20px 24px 40px;
}
.tab-panel {
display: none;
}
.tab-panel.active {
display: block;
}
/* Form & Table */
.form-box,
.panel-box {
background: var(--c-card);
border: 1px solid var(--c-border);
border-radius: var(--radius);
padding: 24px;
margin-bottom: 20px;
box-shadow: var(--shadow-card);
}
.form-box > h3:first-child,
.panel-box > h3:first-child {
margin-bottom: 16px;
font-size: 15px;
font-weight: 600;
color: var(--c-text);
}
.form-box > p:first-child {
margin: -6px 0 16px;
}
.form-group {
margin-bottom: 16px;
}
.form-group label {
display: block;
font-size: 13px;
font-weight: 500;
color: var(--c-text-2);
margin-bottom: 6px;
}
.form-group input,
.form-group select,
.form-group textarea,
.form-group input[type="search"] {
width: 100%;
padding: 9px 12px;
border: 1px solid var(--c-border);
border-radius: var(--radius-sm);
background: #fbfcfe;
color: var(--c-text);
font: inherit;
font-size: 13.5px;
transition: border-color 0.15s ease, box-shadow 0.15s ease, background 0.15s ease;
}
.form-group input:focus,
.form-group select:focus,
.form-group textarea:focus {
outline: none;
border-color: var(--c-primary);
background: #fff;
box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.12);
}
.form-group input::placeholder,
.form-group textarea::placeholder {
color: var(--c-text-3);
}
.form-group input[type="file"] {
padding: 7px 10px;
background: #fff;
}
.form-group input[type="checkbox"],
.form-group input[type="radio"] {
width: 16px;
height: 16px;
margin-right: 8px;
accent-color: var(--c-primary);
vertical-align: -3px;
}
.form-group input[readonly] {
background: #f4f6fa;
color: var(--c-text-2);
}
.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;
}
.dedupe-filter-row {
display: grid;
grid-template-columns: repeat(4, minmax(160px, 1fr)) auto;
gap: 12px;
align-items: end;
margin-bottom: 16px;
}
.dedupe-filter-row .form-group {
min-width: 0;
margin-bottom: 0;
}
.dedupe-filter-actions {
display: flex;
gap: 8px;
align-items: center;
white-space: nowrap;
}
.dedupe-filter-date-row {
display: flex;
gap: 12px;
align-items: flex-end;
flex-wrap: wrap;
margin-bottom: 16px;
}
.dedupe-filter-date-row .form-group {
flex: 0 0 200px;
min-width: 0;
margin-bottom: 0;
}
.dedupe-filter-date-row .dedupe-filter-actions {
margin-left: auto;
}
.btn {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 6px;
padding: 9px 18px;
background: linear-gradient(135deg, #6366f1, #5457e8);
color: #fff;
border: none;
border-radius: var(--radius-sm);
cursor: pointer;
font: inherit;
font-size: 13.5px;
font-weight: 500;
line-height: 1;
white-space: nowrap;
box-shadow: 0 1px 2px rgba(84, 87, 232, 0.25);
transition: box-shadow 0.15s ease, transform 0.15s ease, background 0.15s ease, opacity 0.15s ease;
}
.btn:hover {
box-shadow: 0 4px 10px rgba(84, 87, 232, 0.32);
transform: translateY(-1px);
}
.btn:active {
transform: translateY(0);
}
.btn:focus-visible {
outline: 2px solid var(--c-primary);
outline-offset: 2px;
}
.btn-sm {
padding: 6px 12px;
font-size: 12.5px;
box-shadow: none;
}
.btn-sm:hover {
transform: none;
box-shadow: 0 2px 6px rgba(84, 87, 232, 0.25);
}
.btn-danger {
background: var(--c-danger);
box-shadow: 0 1px 2px rgba(229, 72, 77, 0.25);
}
.btn-danger:hover {
box-shadow: 0 4px 10px rgba(229, 72, 77, 0.32);
}
.btn-secondary {
background: #fff;
color: var(--c-text-2);
border: 1px solid var(--c-border);
box-shadow: none;
}
.btn-secondary:hover {
color: var(--c-primary);
border-color: #b9bcf3;
background: var(--c-primary-soft);
box-shadow: none;
}
.msg {
margin-top: 12px;
font-size: 13px;
line-height: 1.6;
padding: 8px 12px;
border-radius: var(--radius-sm);
border: 1px solid transparent;
white-space: pre-wrap;
word-break: break-word;
}
.msg.ok {
color: var(--c-success);
background: #f0faf4;
border-color: #c7e9d4;
}
.msg.err {
color: var(--c-danger);
background: #fff5f5;
border-color: #f5d2d2;
}
.progress-wrap {
margin-top: 12px;
}
.progress-bar {
width: 100%;
height: 8px;
background: #eef1f6;
border-radius: 999px;
overflow: hidden;
}
.progress-fill {
width: 0;
height: 100%;
background: linear-gradient(90deg, #6366f1, #8fa0f3);
border-radius: 999px;
transition: width 0.3s ease;
}
.progress-text {
margin-top: 8px;
font-size: 12.5px;
color: var(--c-text-2);
}
.request-loading-bar {
position: fixed;
left: 0;
top: 0;
width: 0;
height: 3px;
background: #6366f1;
opacity: 0;
z-index: 3000;
transition: width 0.2s ease, opacity 0.2s ease;
}
.request-loading-bar.show {
width: 74%;
opacity: 1;
}
.request-loading-bar.finishing {
width: 100%;
opacity: 0;
}
.request-loading {
position: fixed;
right: 24px;
top: 72px;
display: none;
align-items: center;
gap: 10px;
padding: 10px 14px;
color: var(--c-text);
background: rgba(255, 255, 255, 0.96);
border: 1px solid #e6eaf2;
border-radius: 10px;
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
font-size: 13px;
z-index: 3000;
pointer-events: none;
}
.request-loading.show {
display: flex;
}
.dedupe-export-wait-mask {
position: fixed;
inset: 0;
z-index: 3100;
display: none;
align-items: center;
justify-content: center;
padding: 20px;
background: rgba(17, 24, 39, 0.38);
}
.dedupe-export-wait-mask.show {
display: flex;
}
.dedupe-export-wait {
display: flex;
align-items: center;
gap: 14px;
width: min(440px, calc(100vw - 40px));
padding: 20px;
border-radius: 8px;
background: #fff;
box-shadow: 0 16px 40px rgba(0, 0, 0, 0.2);
}
.dedupe-export-wait .request-spinner {
flex: 0 0 auto;
width: 24px;
height: 24px;
border-width: 3px;
}
.dedupe-export-wait-title {
margin-bottom: 4px;
color: #1f2937;
font-size: 15px;
font-weight: 600;
}
.dedupe-export-wait-detail {
color: #687386;
font-size: 13px;
line-height: 1.5;
}
.request-spinner {
width: 16px;
height: 16px;
border: 2px solid #dfe3ff;
border-top-color: #6366f1;
border-radius: 50%;
animation: request-spin 0.8s linear infinite;
}
@keyframes request-spin {
to {
transform: rotate(360deg);
}
}
table {
width: 100%;
border-collapse: collapse;
font-size: 13.5px;
}
.panel-box > table,
.modal table {
width: 100%;
}
th,
td {
padding: 11px 12px;
text-align: left;
border-bottom: 1px solid #eef1f6;
font-size: 13.5px;
vertical-align: middle;
}
th {
background: #f7f8fb;
color: var(--c-text-2);
font-weight: 600;
font-size: 12.5px;
letter-spacing: 0.4px;
white-space: nowrap;
}
tbody tr {
transition: background 0.12s ease;
}
tbody tr:hover,
tbody tr:hover td {
background: #f6f8fd;
}
tbody tr:last-child td {
border-bottom: none;
}
.panel-box > table,
.modal > table {
display: block;
max-width: 100%;
overflow-x: auto;
}
.pagination {
display: flex;
align-items: center;
gap: 8px;
margin-top: 16px;
flex-wrap: wrap;
}
.pagination span {
font-size: 13px;
color: var(--c-text-2);
}
.pagination button {
padding: 6px 13px;
border: 1px solid var(--c-border);
background: #fff;
cursor: pointer;
border-radius: var(--radius-sm);
color: var(--c-text);
font: inherit;
font-size: 12.5px;
transition: color 0.15s ease, border-color 0.15s ease, background 0.15s ease;
}
.pagination button:hover:not(:disabled) {
color: var(--c-primary);
border-color: #b9bcf3;
background: var(--c-primary-soft);
}
.pagination button:disabled {
opacity: 0.45;
cursor: not-allowed;
background: #f7f8fb;
}
.pagination-jump {
display: inline-flex;
align-items: center;
gap: 6px;
font-size: 14px;
color: var(--c-text-2);
white-space: nowrap;
}
.pagination-jump input {
width: 64px;
height: 32px;
padding: 4px 8px;
border: 1px solid var(--c-border);
border-radius: var(--radius-sm);
font-size: 13px;
text-align: center;
background: #fbfcfe;
transition: border-color 0.15s ease, box-shadow 0.15s ease;
}
.pagination-jump input:focus {
outline: none;
border-color: var(--c-primary);
background: #fff;
box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.12);
}
/* ===== 店铺数据记录 ===== */
.shop-data-record-table-scroll {
margin-top: 4px;
max-height: none;
overflow-x: auto;
}
.shop-data-record-table-scroll table {
width: 100% !important;
table-layout: fixed;
}
.shop-data-record-table-scroll th,
.shop-data-record-table-scroll td {
vertical-align: middle;
text-align: left;
padding: 10px 12px;
border-bottom: 1px solid var(--c-border);
}
.shop-data-record-table-scroll td {
color: var(--c-text);
}
.shop-data-record-table-scroll td.muted {
color: var(--c-text-2);
}
.shop-data-record-table-scroll .shop-data-status {
display: inline-block;
min-width: 62px;
padding: 3px 10px;
border-radius: 999px;
font-size: 12px;
text-align: center;
white-space: nowrap;
}
.shop-data-record-table-scroll .shop-data-status.success {
color: var(--c-success);
background: #e6f6ec;
}
.shop-data-record-table-scroll .shop-data-status.failed {
color: var(--c-danger);
background: var(--c-danger-soft);
}
.shop-data-record-table-scroll .shop-data-status.running {
color: var(--c-warning);
background: #fdf0dc;
}
.shop-data-record-action {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 5px 10px;
border: 1px solid var(--c-border);
border-radius: var(--radius-sm);
background: #fff;
color: var(--c-text);
font: inherit;
font-size: 12.5px;
cursor: pointer;
transition: color 0.15s ease, border-color 0.15s ease, background 0.15s ease;
}
.shop-data-record-action:hover:not(:disabled) {
color: var(--c-primary);
border-color: #b9bcf3;
background: var(--c-primary-soft);
}
.shop-data-record-action.danger:hover:not(:disabled) {
color: var(--c-danger);
border-color: #f3b4b6;
background: var(--c-danger-soft);
}
.shop-data-record-action:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.shop-data-record-action svg {
width: 14px;
height: 14px;
fill: none;
stroke: currentColor;
stroke-width: 2;
stroke-linecap: round;
stroke-linejoin: round;
}
/* ===== 店铺数据卡片网格 ===== */
.shop-data-card-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(min(300px, 100%), 1fr));
gap: 14px;
padding: 4px 2px;
}
.shop-data-card {
min-width: 0;
display: flex;
flex-direction: column;
background: #fff;
border: 1px solid var(--c-border);
border-radius: 10px;
box-shadow: var(--shadow-card);
overflow: hidden;
}
.shop-data-card.selected {
border-color: var(--c-primary);
box-shadow: 0 0 0 1px var(--c-primary), var(--shadow-card);
}
.shop-data-card-head {
display: flex;
align-items: center;
gap: 12px;
padding: 12px 16px;
background: var(--c-primary-soft);
border-bottom: 1px solid var(--c-border);
}
.shop-data-card-shop {
font-weight: 700;
color: var(--c-text);
font-size: 14px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.shop-data-card-select-row {
display: flex;
align-items: center;
gap: 10px;
padding: 9px 16px;
border-bottom: 1px solid var(--c-border);
background: #fff;
}
.shop-data-card-select-row input[type="checkbox"] {
flex: 0 0 auto;
width: 15px;
height: 15px;
accent-color: var(--c-primary);
cursor: pointer;
margin: 0;
}
.shop-data-card-select-row .shop-data-card-task-no {
flex: 0 1 auto;
font-size: 13px;
font-weight: 600;
color: var(--c-text);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.shop-data-card-select-row .shop-data-status {
margin-left: auto;
}
.shop-data-card-row {
display: flex;
align-items: baseline;
gap: 10px;
padding: 7px 16px;
font-size: 13px;
}
.shop-data-card-label {
flex: 0 0 auto;
min-width: 38px;
color: var(--c-text-2);
}
.shop-data-card-value {
min-width: 0;
color: var(--c-text);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.shop-data-card-value.shop-data-card-file {
color: var(--c-primary-strong);
word-break: break-all;
white-space: normal;
}
.shop-data-card-actions {
display: flex;
gap: 8px;
padding: 10px 16px 14px;
border-top: 1px solid var(--c-border);
margin-top: auto;
}
.shop-data-card-actions .shop-data-record-action {
flex: 1;
justify-content: center;
}
.image-video-select-all {
display: inline-flex;
align-items: center;
gap: 6px;
color: var(--c-text);
font-size: 13px;
cursor: pointer;
user-select: none;
margin-left: 10px;
}
.image-video-select-all input {
width: 15px;
height: 15px;
accent-color: var(--c-primary);
}
/* ===== 重复 ASIN 卡片(卡内「店铺 | 上架时间」列表)===== */
.duplicate-asin-card {
min-width: 0;
display: flex;
flex-direction: column;
background: #fff;
border: 1px solid var(--c-border);
border-radius: 10px;
box-shadow: var(--shadow-card);
overflow: hidden;
}
.duplicate-asin-card-head {
display: flex;
align-items: center;
gap: 12px;
padding: 12px 16px;
background: var(--c-primary-soft);
border-bottom: 1px solid var(--c-border);
}
.duplicate-asin-card-head .dup-asin {
font-family: Consolas, Menlo, monospace;
font-size: 15px;
font-weight: 800;
color: var(--c-primary-strong);
letter-spacing: 0.02em;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.duplicate-asin-card-head .dup-count {
flex: 0 0 auto;
padding: 2px 10px;
border-radius: 999px;
background: #fff;
color: var(--c-primary-strong);
font-size: 12px;
font-weight: 700;
white-space: nowrap;
}
.duplicate-asin-card-head .dup-brand {
margin-left: auto;
color: var(--c-text-2);
font-size: 12.5px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.duplicate-asin-table {
width: 100%;
border-collapse: collapse;
font-size: 13px;
}
.duplicate-asin-table th,
.duplicate-asin-table td {
padding: 9px 16px;
text-align: left;
border-bottom: 1px solid var(--c-border);
}
.duplicate-asin-table th {
background: #fbfcfe;
color: var(--c-text-2);
font-size: 12.5px;
font-weight: 700;
}
.duplicate-asin-table tr:last-child td {
border-bottom: 0;
}
.duplicate-asin-table td.dup-shop {
font-weight: 700;
}
.duplicate-asin-table td.dup-country {
color: var(--c-text-2);
}
.duplicate-asin-table .dup-date {
color: var(--c-text-2);
font-variant-numeric: tabular-nums;
}
/* 卡内两列表格:内边距略收紧,无外边框(卡片自带圆角边框) */
.dup-card-table {
border: 0;
}
.dup-card-table th,
.dup-card-table td {
padding: 8px 16px;
}
.dup-card-table th {
background: #fbfcfe;
}
.dup-card-table tr:last-child td {
border-bottom: 0;
}
.shop-data-empty-hint {
padding: 28px 16px;
text-align: center;
color: var(--c-text-3);
font-size: 13px;
background: #fff;
border: 1px dashed var(--c-border);
border-radius: 10px;
}
.shop-data-refresh-btn {
background: #fff;
border: 1px solid var(--c-border);
color: var(--c-text);
}
.shop-data-refresh-btn:hover {
color: var(--c-primary);
border-color: #b9bcf3;
background: var(--c-primary-soft);
}
.dup-asin-cell {
font-family: Consolas, Menlo, monospace;
font-size: 12.5px;
font-weight: 700;
color: var(--c-primary-strong);
}
/* ===== 店铺数据重复检查(指标卡 + 分布 + 矩阵表格 + 抽屉)===== */
.dup-check-detail-block {
margin-top: 16px;
}
.dup-check-detail-head {
display: flex;
align-items: baseline;
gap: 10px;
margin-bottom: 10px;
}
.dup-check-detail-title {
font-size: 14px;
font-weight: 800;
color: var(--c-text);
}
.dup-check-detail-sub {
color: var(--c-text-3);
font-size: 12.5px;
}
.dup-check-detail-cards {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
gap: 14px;
max-height: 460px;
overflow-y: auto;
padding: 2px;
}
.dup-detail-count {
display: inline-block;
margin-left: 8px;
padding: 1px 8px;
border-radius: 999px;
background: var(--c-danger-soft);
color: var(--c-danger);
font-size: 11.5px;
font-weight: 700;
line-height: 1.6;
}
.dup-detail-view-btn {
flex: 0 0 auto;
margin-left: 6px;
}
.dup-check-overview {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr)) minmax(320px, 1.5fr);
gap: 14px;
margin-bottom: 14px;
}
.dup-check-metrics {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 12px;
}
.dup-check-metric {
min-width: 0;
display: flex;
flex-direction: column;
gap: 6px;
padding: 14px 16px;
background: #fff;
border: 1px solid var(--c-border);
border-radius: 10px;
box-shadow: var(--shadow-card);
}
.dup-check-metric-label {
color: var(--c-text-2);
font-size: 12.5px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.dup-check-metric-value {
color: var(--c-text);
font-size: 26px;
font-weight: 800;
font-variant-numeric: tabular-nums;
line-height: 1.1;
}
.dup-check-metric.accent {
background: linear-gradient(120deg, var(--c-primary-soft), #fff 72%);
border-color: #d9dcfb;
}
.dup-check-metric.accent .dup-check-metric-value {
color: var(--c-primary-strong);
}
.dup-check-metric.warn {
background: linear-gradient(120deg, var(--c-danger-soft), #fff 72%);
border-color: #f4cdce;
}
.dup-check-metric.warn .dup-check-metric-value {
color: var(--c-danger);
}
.dup-check-distribution {
min-width: 0;
display: flex;
flex-direction: column;
padding: 14px 16px;
background: #fff;
border: 1px solid var(--c-border);
border-radius: 10px;
box-shadow: var(--shadow-card);
}
.dup-check-distribution-head {
display: flex;
align-items: baseline;
justify-content: space-between;
gap: 10px;
margin-bottom: 10px;
}
.dup-check-distribution-title {
font-size: 13.5px;
font-weight: 700;
color: var(--c-text);
}
.dup-check-distribution-sub {
color: var(--c-text-3);
font-size: 12px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.dup-check-distribution-bars {
flex: 1;
min-height: 120px;
max-height: 250px;
overflow-y: auto;
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
align-content: start;
gap: 8px;
padding-right: 2px;
}
/* 店铺上架分布:双列网格(店名 + 记录数 / 进度条 / N 个 ASIN */
.dup-dist-item {
min-width: 0;
display: flex;
flex-direction: column;
gap: 6px;
padding: 10px 12px;
background: #fbfcfe;
border: 1px solid var(--c-border);
border-radius: 8px;
}
.dup-dist-item-head {
display: flex;
align-items: baseline;
justify-content: space-between;
gap: 8px;
}
.dup-dist-name {
min-width: 0;
font-size: 12.5px;
font-weight: 700;
color: var(--c-text);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.dup-dist-record {
flex: 0 0 auto;
font-size: 15px;
font-weight: 800;
color: var(--c-primary-strong);
font-variant-numeric: tabular-nums;
}
.dup-dist-track {
height: 7px;
background: #eef0f6;
border-radius: 999px;
overflow: hidden;
}
.dup-dist-fill {
display: block;
height: 100%;
min-width: 2px;
background: linear-gradient(90deg, var(--c-primary), #8b8ff3);
border-radius: 999px;
transition: width 0.3s ease;
}
.dup-dist-asin {
font-size: 12px;
color: var(--c-text-3);
font-variant-numeric: tabular-nums;
}
.dup-check-table-wrap {
background: #fff;
border: 1px solid var(--c-border);
border-radius: 10px;
box-shadow: var(--shadow-card);
padding: 14px 16px;
}
.dup-check-toolbar {
min-height: 38px;
margin-bottom: 10px;
}
.dup-check-view-tabs {
display: inline-flex;
gap: 4px;
padding: 3px;
background: #eef0f6;
border-radius: 9px;
}
.dup-check-view-tab {
padding: 6px 16px;
border: 0;
border-radius: 7px;
background: transparent;
color: var(--c-text-2);
font: inherit;
font-size: 13px;
cursor: pointer;
transition: color 0.15s ease, background 0.15s ease, box-shadow 0.15s ease;
}
.dup-check-view-tab:hover {
color: var(--c-text);
}
.dup-check-view-tab.active {
background: #fff;
color: var(--c-primary);
font-weight: 700;
box-shadow: var(--shadow-card);
}
.dup-check-table-scroll {
max-height: none;
}
.dup-check-table {
width: 100%;
border-collapse: collapse;
font-size: 13px;
}
.dup-check-table th,
.dup-check-table td {
padding: 9px 12px;
text-align: left;
border-bottom: 1px solid var(--c-border);
white-space: nowrap;
}
.dup-check-table thead th {
background: #fbfcfe;
color: var(--c-text-2);
font-size: 12.5px;
font-weight: 700;
position: sticky;
top: 0;
z-index: 2;
border-bottom: 1px solid var(--c-border);
}
.dup-check-table tbody tr:hover td {
background: #f8f9fd;
}
.dup-check-table tbody tr:last-child td {
border-bottom: 0;
}
.dup-check-table td.dup-cell {
min-width: 96px;
text-align: center;
padding: 5px 12px;
}
.dup-cell-num {
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 56px;
padding: 3px 10px;
border: 1px solid transparent;
border-radius: 6px;
font-weight: 700;
font-variant-numeric: tabular-nums;
cursor: pointer;
transition: border-color 0.15s ease, background 0.15s ease;
}
.dup-cell-num.zero {
color: var(--c-text-3);
background: #f4f6fa;
cursor: default;
font-weight: 400;
}
.dup-cell-num.has {
color: var(--c-primary-strong);
background: var(--c-primary-soft);
}
.dup-cell-num.has:hover {
border-color: #b9bcf3;
background: #e3e6fd;
}
.dup-cell-num.danger {
color: var(--c-danger);
background: var(--c-danger-soft);
}
.dup-cell-num.danger:hover {
border-color: #f3b4b6;
background: #fbdcdd;
}
.dup-check-table .dup-asin-col {
font-family: Consolas, Menlo, monospace;
font-weight: 700;
color: var(--c-primary-strong);
cursor: pointer;
}
.dup-check-table .dup-asin-col:hover {
text-decoration: underline;
}
.dup-check-site {
flex: 0 0 auto;
padding: 2px 8px;
border-radius: 999px;
background: #fff;
color: var(--c-text-2);
font-size: 11.5px;
font-weight: 700;
line-height: 1.6;
border: 1px solid var(--c-border);
}
/* 国家彩色徽章(撞款详情卡片 / 抽屉):DE橙 / UK靛蓝 / FR蓝 / IT绿 / ES红 / 其他兜底 */
.dup-site-badge {
display: inline-flex;
align-items: center;
padding: 2px 9px;
border-radius: 999px;
font-size: 11.5px;
font-weight: 700;
line-height: 1.6;
white-space: nowrap;
vertical-align: middle;
}
.dup-site-DE { background: #fff1e3; color: #c2570a; border: 1px solid #f3cba4; }
.dup-site-UK { background: #eeedfb; color: #5451d6; border: 1px solid #c9c7f2; }
.dup-site-FR { background: #e5f0fd; color: #1d5fc2; border: 1px solid #b5d3f5; }
.dup-site-IT { background: #e3f6ec; color: #18774c; border: 1px solid #aadcc2; }
.dup-site-ES { background: #fdeaea; color: #c03a2e; border: 1px solid #f2bcba; }
.dup-site-NA,
.dup-site- { background: #f4f6fa; color: var(--c-text-3); border: 1px solid var(--c-border); }
/* 抽屉底部徽章组:彩色滴状标签 */
.dup-drawer-inline-badges {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 6px;
margin-top: 6px;
}
.dup-drawer-badge {
display: inline-flex;
align-items: center;
gap: 4px;
padding: 3px 11px;
border-radius: 8px;
font-size: 12px;
font-weight: 700;
line-height: 1.5;
white-space: nowrap;
background: #eef2f7;
color: var(--c-text-2);
}
.dup-drawer-badge-danger { background: #fdecec; color: #d8483a; border: 1px solid #f3bcb8; }
.dup-drawer-badge-blue { background: var(--c-primary-soft); color: var(--c-primary-strong); border: 1px solid #d3d6fa; }
.dup-drawer-badge-green { background: #e3f6ec; color: #18774c; border: 1px solid #aadcc2; }
.dup-drawer-badge-cyan { background: #e0f5f9; color: #0e7d94; border: 1px solid #a8e0ec; }
/* 抽屉头部 ASIN 与品牌行 */
.dup-drawer-asin {
font-family: Consolas, Menlo, monospace;
font-size: 17px;
font-weight: 800;
color: var(--c-primary-strong);
letter-spacing: 0.02em;
}
.dup-drawer-brand-line {
padding: 12px 0;
font-size: 13px;
color: var(--c-text);
border-bottom: 1px solid var(--c-border);
overflow-wrap: anywhere;
}
.dup-check-drawer-table td.dup-date {
color: var(--c-text-2);
font-variant-numeric: tabular-nums;
}
.drawer-subtitle.dup-drawer-subtitle {
color: var(--c-text-2);
font-size: 12.5px;
}
.dup-check-drawer-meta {
display: grid;
grid-template-columns: 88px minmax(0, 1fr);
gap: 8px 12px;
padding: 14px 0;
border-bottom: 1px solid var(--c-border);
font-size: 13px;
}
.dup-check-drawer-meta dt {
color: var(--c-text-2);
}
.dup-check-drawer-meta dd {
margin: 0;
color: var(--c-text);
overflow-wrap: anywhere;
}
.dup-check-drawer-table {
width: 100%;
border-collapse: collapse;
font-size: 12.5px;
margin-top: 8px;
}
.dup-check-drawer-table th,
.dup-check-drawer-table td {
padding: 8px 10px;
text-align: left;
border-bottom: 1px solid var(--c-border);
}
.dup-check-drawer-table thead th {
background: #fbfcfe;
color: var(--c-text-2);
font-weight: 700;
white-space: nowrap;
}
.dup-check-drawer-table tbody tr:hover td {
background: #f8f9fd;
}
.dup-check-drawer-table tbody tr:last-child td {
border-bottom: 0;
}
@media (max-width: 1400px) {
.dup-check-overview {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.dup-check-distribution {
grid-column: 1 / -1;
}
}
@media (max-width: 760px) {
.dup-check-overview {
grid-template-columns: minmax(0, 1fr);
}
.dup-check-metrics {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.dup-check-distribution-bars {
grid-template-columns: minmax(0, 1fr);
}
}
.thumb {
width: 56px;
height: 56px;
object-fit: cover;
border-radius: 6px;
margin-right: 8px;
vertical-align: middle;
border: 1px solid var(--c-border);
}
.thumb-wrap {
display: flex;
flex-wrap: wrap;
gap: 4px;
}
.empty-tip {
color: var(--c-text-3);
font-size: 13.5px;
padding: 44px 24px;
text-align: center;
line-height: 1.6;
}
.tree-name-cell {
display: flex;
align-items: center;
gap: 8px;
min-height: 24px;
}
.tree-indent {
flex: 0 0 auto;
width: var(--indent, 0px);
}
.tree-node-mark {
width: 20px;
height: 20px;
border: 0;
border-radius: 5px;
background: #eef0fe;
color: #6366f1;
display: inline-flex;
align-items: center;
justify-content: center;
font-size: 13px;
font-weight: 700;
cursor: pointer;
padding: 0;
line-height: 1;
transition: background 0.12s ease, color 0.12s ease;
}
.tree-node-mark:not(.is-leaf):hover {
background: #6366f1;
color: #fff;
}
.tree-node-mark.is-leaf {
background: transparent;
cursor: default;
}
.category-path {
color: var(--c-text-3);
font-size: 12px;
margin-top: 4px;
}
.category-tag {
display: inline-flex;
align-items: center;
padding: 3px 9px;
border-radius: 999px;
background: var(--c-primary-soft);
color: var(--c-primary-strong);
font-size: 12px;
font-weight: 500;
}
.modal-mask {
display: none;
position: fixed;
left: 0;
top: 0;
right: 0;
bottom: 0;
background: rgba(15, 20, 34, 0.46);
backdrop-filter: blur(2px);
z-index: 1000;
align-items: center;
justify-content: center;
}
.modal-mask.show {
display: flex;
animation: modal-fade-in 0.16s ease;
}
@keyframes modal-fade-in {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
.shop-key-whitelist-status {
display: inline-flex;
align-items: center;
min-height: 24px;
padding: 3px 9px;
border: 1px solid transparent;
border-radius: 999px;
font-size: 12px;
font-weight: 600;
white-space: nowrap;
}
.shop-key-whitelist-status.is-allowed {
color: var(--c-success);
background: #eaf8f0;
border-color: #b9e3ca;
}
.shop-key-whitelist-status.is-blocked {
color: var(--c-danger);
background: #fff0ee;
border-color: #f2c0ba;
}
.shop-key-whitelist-status.is-unknown {
color: #667085;
background: #f2f4f7;
border-color: #d0d5dd;
}
.shop-key-table-scroll {
width: 100%;
overflow-x: auto;
}
.shop-key-table-scroll table {
min-width: 900px;
}
.shop-password-cell {
display: inline-flex;
align-items: center;
gap: 6px;
min-width: 86px;
}
.shop-password-value {
min-width: 48px;
max-width: 220px;
overflow-wrap: anywhere;
font-family: Consolas, "Courier New", monospace;
}
.shop-password-toggle {
display: inline-flex;
align-items: center;
justify-content: center;
flex: 0 0 28px;
width: 28px;
height: 28px;
padding: 0;
border: 0;
border-radius: 4px;
color: #667085;
background: transparent;
cursor: pointer;
}
.shop-password-toggle:hover:not(:disabled) {
color: #5158d9;
background: #eef0fe;
}
.shop-password-toggle:focus-visible {
outline: 2px solid #6366f1;
outline-offset: 2px;
}
.shop-password-toggle:disabled {
cursor: wait;
opacity: 0.5;
}
.shop-password-toggle svg {
width: 17px;
height: 17px;
fill: none;
stroke: currentColor;
stroke-width: 2;
stroke-linecap: round;
stroke-linejoin: round;
pointer-events: none;
}
.dedupe-group-access {
display: flex;
align-items: center;
justify-content: space-between;
gap: 16px;
margin-bottom: 18px;
padding: 0 0 14px;
border-bottom: 1px solid #edf0f5;
}
.dedupe-group-access-main {
min-width: 0;
display: flex;
align-items: center;
gap: 12px;
flex-wrap: wrap;
}
.dedupe-group-access-title {
flex: 0 0 auto;
font-size: 14px;
font-weight: 600;
color: var(--c-text);
}
.dedupe-group-summary {
min-width: 0;
display: flex;
align-items: center;
gap: 6px;
flex-wrap: wrap;
color: var(--c-text-2);
font-size: 13px;
}
.dedupe-group-summary-chip {
display: inline-flex;
align-items: center;
max-width: 220px;
padding: 4px 10px;
border: 1px solid var(--c-border);
border-radius: 6px;
background: #f8f9fc;
color: var(--c-text-2);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.modal {
background: #fff;
border-radius: 14px;
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;
box-shadow: 0 24px 60px rgba(16, 24, 40, 0.22);
}
.modal h3 {
margin-bottom: 16px;
font-size: 16px;
font-weight: 600;
color: var(--c-text);
}
.shop-group-editor {
padding: 18px 0 4px;
display: grid;
grid-template-columns: minmax(190px, 1fr) minmax(340px, 1.6fr);
gap: 24px;
align-items: start;
flex: 0 0 auto;
}
.shop-group-editor .form-group {
margin-bottom: 0;
}
.shop-group-members-panel {
position: relative;
border: 1px solid var(--c-border);
border-radius: 10px;
background: var(--c-card-raised, #f9fbfd);
overflow: hidden;
display: flex;
flex-direction: column;
}
.shop-group-members-head {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
padding: 10px 14px 8px;
}
.shop-group-members-title {
font-size: 13px;
font-weight: 600;
color: var(--c-text);
}
.shop-group-members-count {
font-size: 12px;
color: var(--c-text-3);
}
.shop-group-member-search {
display: flex;
align-items: center;
gap: 8px;
margin: 0 14px 10px;
padding: 7px 10px;
border: 1px solid var(--c-border);
border-radius: 8px;
background: var(--c-card, #fff);
transition: border-color 0.16s ease, box-shadow 0.16s ease;
}
.shop-group-member-search:focus-within {
border-color: var(--c-primary);
box-shadow: 0 0 0 3px color-mix(in srgb, var(--c-primary) 18%, transparent);
}
.shop-group-member-search svg {
flex: 0 0 15px;
width: 15px;
height: 15px;
color: var(--c-text-3);
}
.shop-group-member-search input {
flex: 1;
min-width: 0;
border: 0;
outline: 0;
padding: 0;
background: transparent;
font-size: 13px;
color: var(--c-text);
}
.shop-group-member-search input::placeholder {
color: var(--c-text-3);
}
.shop-group-contact {
flex: 1 1 220px;
min-height: 200px;
max-height: 300px;
overflow-y: auto;
overflow-x: hidden;
padding: 0 14px 10px;
scrollbar-width: thin;
}
.shop-group-contact-empty {
padding: 34px 10px;
text-align: center;
color: var(--c-text-3);
font-size: 13px;
line-height: 1.6;
}
.shop-group-contact-group {
display: flex;
align-items: center;
gap: 8px;
padding: 9px 2px 4px;
font-size: 12px;
color: var(--c-text-3);
font-weight: 600;
position: sticky;
top: 0;
z-index: 1;
background: var(--c-card-raised, #f9fbfd);
}
.shop-group-contact-group::after {
content: "";
flex: 1;
height: 1px;
background: var(--c-border);
}
.shop-group-contact-items {
display: flex;
flex-direction: column;
}
.shop-group-contact-item {
display: flex;
align-items: center;
gap: 9px;
padding: 6px 8px;
border-radius: 7px;
cursor: pointer;
user-select: none;
transition: background 0.14s ease;
}
.shop-group-contact-item:hover {
background: var(--c-primary-soft);
}
.shop-group-item-avatar {
flex: 0 0 26px;
width: 26px;
height: 26px;
border-radius: 50%;
display: inline-flex;
align-items: center;
justify-content: center;
font-size: 12px;
font-weight: 600;
color: #fff;
background: #9aa7c0;
}
.shop-group-item-name {
flex: 1;
min-width: 0;
font-size: 13px;
color: var(--c-text);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.shop-group-item-check {
flex: 0 0 16px;
width: 16px;
height: 16px;
border: 1.5px solid var(--c-border-strong);
border-radius: 4px;
display: inline-flex;
align-items: center;
justify-content: center;
color: transparent;
transition: background 0.14s ease, border-color 0.14s ease;
}
.shop-group-item-check svg {
width: 11px;
height: 11px;
stroke-width: 3;
}
.shop-group-contact-item.is-selected .shop-group-item-avatar {
background: var(--c-primary);
}
.shop-group-contact-item.is-selected .shop-group-item-check {
background: var(--c-primary);
border-color: var(--c-primary);
color: #fff;
}
.shop-group-contact-index {
position: absolute;
right: 4px;
top: 50%;
transform: translateY(-50%);
display: flex;
flex-direction: column;
gap: 1px;
z-index: 2;
padding: 4px 2px;
}
.shop-group-contact-index button {
border: 0;
background: transparent;
padding: 0;
min-width: 16px;
height: 16px;
line-height: 16px;
font-size: 10px;
font-weight: 600;
text-align: center;
color: var(--c-text-3);
cursor: pointer;
border-radius: 3px;
}
.shop-group-contact-index button:hover,
.shop-group-contact-index button.is-active {
color: var(--c-primary-strong);
}
.shop-group-contact-index button:disabled {
color: var(--c-text-3);
opacity: 0.35;
cursor: default;
}
.shop-group-member-select {
display: none;
}
.shop-group-help {
color: var(--c-text-2);
font-size: 12px;
line-height: 1.5;
margin-top: 10px;
}
.shop-group-action-bar {
padding: 0 24px 12px;
display: flex;
gap: 8px;
justify-content: flex-end;
align-items: center;
flex: 0 0 auto;
}
#msgShopManageGroup {
margin: 0;
padding: 0 24px 10px;
min-height: 18px;
flex: 0 0 auto;
}
.shop-group-table-wrap {
margin: 0 24px;
border: 1px solid #dbe5ee;
border-radius: 10px;
overflow: auto;
min-height: 220px;
flex: 1 1 auto;
background: #ffffff;
}
.shop-group-table {
min-width: 1040px;
table-layout: fixed;
}
.shop-group-table th {
background: #edf4fa;
color: #4e6479;
position: sticky;
top: 0;
z-index: 1;
}
.shop-group-table th,
.shop-group-table td {
vertical-align: middle;
}
.shop-group-table .col-index {
width: 58px;
}
.shop-group-table .col-name {
width: 150px;
}
.shop-group-table .col-leader {
width: 110px;
}
.shop-group-table .col-count {
width: 90px;
}
.shop-group-table .col-members {
width: auto;
}
.shop-group-table .col-time {
width: 140px;
white-space: nowrap;
}
.shop-group-table .col-action {
width: 124px;
}
.shop-group-time-cell,
.shop-group-action-cell {
white-space: nowrap;
}
.shop-group-name-cell,
.shop-group-leader-cell {
word-break: break-word;
}
.shop-group-members {
display: flex;
flex-wrap: wrap;
gap: 6px;
max-height: 96px;
overflow-y: auto;
padding-right: 4px;
}
.shop-group-member-chip {
display: inline-flex;
align-items: center;
max-width: 150px;
padding: 3px 8px;
border-radius: 999px;
background: #eef0fe;
color: #4f5ad8;
font-size: 12px;
line-height: 1.4;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
@media (max-width: 900px) {
.dedupe-filter-row {
grid-template-columns: repeat(2, minmax(180px, 1fr));
}
.dedupe-filter-actions {
grid-column: 1 / -1;
}
.dedupe-group-access {
align-items: stretch;
flex-direction: column;
}
.dedupe-group-access .btn {
align-self: flex-start;
}
.shop-group-editor {
grid-template-columns: 1fr;
}
.shop-group-member-search {
margin: 0 10px 10px;
}
.shop-group-contact {
padding: 0 28px 10px 10px;
}
.shop-group-table-wrap {
margin: 0 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-tree {
max-height: 280px;
overflow: auto;
border: 1px solid var(--c-border);
border-radius: var(--radius-sm);
padding: 8px 10px;
background: #fff;
}
.column-tree-node {
display: block;
padding: 4px 0;
}
.column-tree-group + .column-tree-group {
margin-top: 8px;
}
.column-tree-group-toggle {
display: flex;
align-items: center;
width: 100%;
min-height: 36px;
padding: 6px 10px;
border: 0;
border-radius: 4px;
background: #f3f5f8;
color: var(--c-text);
cursor: pointer;
text-align: left;
}
.column-tree-group-toggle::before {
content: '\203A';
flex: 0 0 20px;
font-size: 20px;
line-height: 1;
color: #667085;
transition: transform 0.15s ease;
}
.column-tree-group-toggle[aria-expanded="true"]::before {
transform: rotate(90deg);
}
.column-tree-group-toggle:hover {
background: #e9edf3;
}
.column-tree-group-toggle:focus-visible {
outline: 2px solid #4f46e5;
outline-offset: -2px;
}
.column-tree-group-name {
font-size: 13px;
font-weight: 600;
}
.column-tree-group-count {
margin-left: auto;
font-size: 12px;
color: #667085;
}
.column-tree-group-body {
padding-top: 4px;
}
.column-tree-row {
display: flex;
align-items: center;
min-height: 28px;
}
.column-tree-node label {
display: flex;
align-items: center;
gap: 7px;
margin: 0;
min-height: 28px;
font-size: 13px;
flex: 1;
min-width: 0;
}
.column-tree-toggle,
.column-tree-toggle-spacer {
flex: 0 0 24px;
width: 24px;
height: 28px;
}
.column-tree-toggle {
display: inline-flex;
align-items: center;
justify-content: center;
padding: 0;
border: 0;
background: transparent;
color: var(--c-text-2);
cursor: pointer;
}
.column-tree-toggle::before {
content: '\203A';
display: block;
font-size: 20px;
line-height: 1;
transition: transform 0.15s ease;
}
.column-tree-toggle[aria-expanded="true"]::before {
transform: rotate(90deg);
}
.column-tree-toggle:hover {
color: #4f46e5;
}
.column-tree-toggle:focus-visible {
outline: 2px solid #4f46e5;
outline-offset: -2px;
}
.column-tree-node[data-depth="1"] > .column-tree-row { padding-left: 22px; }
.column-tree-node[data-depth="2"] > .column-tree-row { padding-left: 44px; }
.column-tree-node input[type="checkbox"] { width: auto; }
.column-tree-node .inherited-label { color: var(--c-text-3); }
.multi-select-native {
position: absolute;
opacity: 0;
pointer-events: none;
width: 1px !important;
height: 1px !important;
min-height: 0 !important;
padding: 0 !important;
border: 0 !important;
}
.multi-select-dropdown {
position: relative;
width: 100%;
}
.multi-select-trigger {
width: 100%;
min-height: 0;
padding: 9px 34px 9px 12px;
border: 1px solid var(--c-border);
border-radius: var(--radius-sm);
background: #fbfcfe;
color: var(--c-text);
font-size: 13.5px;
text-align: left;
cursor: pointer;
position: relative;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
transition: border-color 0.15s ease, box-shadow 0.15s ease, background 0.15s ease;
}
.multi-select-trigger:hover {
border-color: #c4c8ec;
background: #fff;
}
.multi-select-trigger::after {
content: "";
position: absolute;
right: 12px;
top: 50%;
width: 7px;
height: 7px;
border-right: 1.5px solid var(--c-text-3);
border-bottom: 1.5px solid var(--c-text-3);
transform: translateY(-65%) rotate(45deg);
}
.multi-select-dropdown.open .multi-select-trigger {
border-color: var(--c-primary);
background: #fff;
box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.12);
}
.multi-select-panel {
display: none;
position: absolute;
left: 0;
right: 0;
top: calc(100% + 6px);
z-index: 30;
background: #fff;
border: 1px solid var(--c-border);
border-radius: 10px;
box-shadow: var(--shadow-pop);
padding: 6px;
max-height: 220px;
overflow-y: auto;
}
.multi-select-dropdown.open .multi-select-panel {
display: block;
}
.multi-select-option {
display: flex;
align-items: center;
gap: 8px;
min-height: 34px;
padding: 7px 8px;
border-radius: 6px;
cursor: pointer;
font-size: 14px;
color: var(--c-text);
}
.multi-select-option:hover {
background: var(--c-primary-soft);
}
.multi-select-option input {
width: auto;
margin: 0;
}
.column-permission-card {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 4px 10px;
background: #e9ecfb;
color: #6366f1;
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: var(--c-text-3);
border: none;
background: none;
font-size: 16px;
}
.column-permission-card .col-card-remove:hover {
color: var(--c-danger);
}
.column-permission-select {
width: 100%;
max-width: 100%;
min-height: 120px;
height: 220px;
max-height: 260px;
overflow-y: auto;
padding: 8px;
box-sizing: border-box;
}
.column-permission-select option {
padding: 4px 0;
}
.image-video-panel-box {
padding: 0;
}
.image-video-results {
padding: 20px 20px 0;
}
.image-video-panel-box>.pagination {
padding: 0 20px 20px;
}
.image-video-toolbar {
display: flex;
align-items: center;
justify-content: space-between;
gap: 16px;
margin-bottom: 20px;
}
.image-video-toolbar-main {
display: flex;
align-items: center;
gap: 18px;
flex-wrap: wrap;
}
.image-video-batch-btn {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 7px;
min-height: 36px;
padding: 8px 14px;
background: #6366f1;
}
.image-video-batch-btn:disabled {
cursor: not-allowed;
opacity: .45;
}
.image-video-batch-btn svg,
.image-video-card-action svg {
width: 15px;
height: 15px;
fill: none;
stroke: currentColor;
stroke-width: 2;
stroke-linecap: round;
stroke-linejoin: round;
}
.image-video-select-all {
display: inline-flex;
align-items: center;
gap: 8px;
color: var(--c-text);
font-size: 14px;
cursor: pointer;
}
.image-video-select-all input {
width: 16px;
height: 16px;
accent-color: #6366f1;
}
.image-video-download-progress {
min-height: 18px;
color: var(--c-text-2);
font-size: 13px;
}
.image-video-summary {
color: var(--c-text-2);
font-size: 13px;
text-align: right;
}
.image-video-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 20px;
}
.image-video-card {
min-width: 0;
overflow: hidden;
background: #fff;
border: 1px solid var(--c-border);
border-radius: 10px;
box-shadow: var(--shadow-card);
transition: border-color .2s ease, box-shadow .2s ease, transform .2s ease;
}
.image-video-card:hover {
border-color: #b9bcf3;
box-shadow: 0 10px 22px -8px rgba(99, 102, 241, .18);
transform: translateY(-1px);
}
.image-video-card.selected {
border-color: var(--c-primary);
box-shadow: 0 0 0 2px rgba(99, 102, 241, .14), 0 10px 22px -8px rgba(99, 102, 241, .2);
}
.image-video-media {
position: relative;
width: 100%;
aspect-ratio: 16/9;
display: flex;
align-items: center;
justify-content: center;
background: #10131a;
overflow: hidden;
}
.image-video-media video {
display: block;
width: 100%;
height: 100%;
object-fit: contain;
background: #000;
}
.image-video-card-check {
position: absolute;
top: 11px;
left: 11px;
z-index: 2;
width: 17px;
height: 17px;
accent-color: #6366f1;
cursor: pointer;
filter: drop-shadow(0 1px 2px rgba(0, 0, 0, .55));
}
.image-video-unavailable {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 8px;
padding: 20px;
color: #bcc2cc;
text-align: center;
}
.image-video-unavailable svg {
width: 34px;
height: 34px;
fill: none;
stroke: currentColor;
stroke-width: 1.5;
}
.image-video-unavailable strong {
color: #e2e5ec;
font-size: 14px;
font-weight: 500;
}
.image-video-card-body {
padding: 15px 16px 16px;
}
.image-video-card-head {
display: flex;
align-items: center;
justify-content: space-between;
gap: 10px;
margin-bottom: 12px;
}
.image-video-card-title {
min-width: 0;
font-size: 15px;
font-weight: 600;
color: var(--c-text);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.image-video-status {
flex: 0 0 auto;
display: inline-block;
padding: 3px 9px;
border-radius: 999px;
background: #f2f4f8;
color: #5d6675;
font-size: 11px;
font-weight: 600;
}
.image-video-status.SUCCESS {
color: var(--c-success);
background: #f0faf4;
}
.image-video-status.FAILED {
color: var(--c-danger);
background: #fff5f5;
}
.image-video-status.RUNNING,
.image-video-status.POLLING {
color: var(--c-warning);
background: #fdf6e9;
}
.image-video-card-info {
display: grid;
gap: 7px;
}
.image-video-info-row {
display: grid;
grid-template-columns: 68px minmax(0, 1fr);
gap: 8px;
color: var(--c-text-2);
font-size: 13px;
line-height: 1.45;
}
.image-video-info-row span,
.image-video-info-row a {
min-width: 0;
color: var(--c-text);
overflow-wrap: anywhere;
}
.image-video-info-row a {
color: #5457e8;
text-decoration: none;
}
.image-video-info-row a:hover {
text-decoration: underline;
}
.image-video-copy-link {
min-width: 0;
padding: 0;
border: 0;
background: none;
color: #5457e8;
cursor: pointer;
font: inherit;
text-align: left;
}
.image-video-copy-link:hover {
text-decoration: underline;
}
.image-video-copy-link.copied {
color: var(--c-success);
font-weight: 600;
}
.image-video-card-actions {
display: flex;
align-items: center;
gap: 9px;
margin-top: 15px;
}
.image-video-card-action {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 6px;
min-height: 32px;
padding: 6px 11px;
border: 1px solid #6366f1;
border-radius: 4px;
background: #fff;
color: #5457e8;
cursor: pointer;
font-size: 13px;
}
.image-video-card-action:hover {
background: #6366f1;
color: #fff;
}
.image-video-card-action:disabled {
cursor: not-allowed;
opacity: .45;
}
.image-video-empty {
grid-column: 1/-1;
padding: 64px 20px;
border: 1px dashed #c7cbf0;
border-radius: 10px;
color: var(--c-text-2);
text-align: center;
background: var(--c-primary-soft);
}
.image-video-permission-btn {
display: none;
min-height: 36px;
padding: 8px 14px;
}
.image-video-permission-modal {
width: min(620px, calc(100vw - 32px));
}
.image-video-permission-head {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
margin-bottom: 16px;
}
.image-video-permission-tools {
display: flex;
align-items: center;
gap: 12px;
margin-bottom: 12px;
}
.image-video-permission-tabs {
display: flex;
gap: 4px;
margin-bottom: 12px;
padding: 3px;
border-radius: 6px;
background: #f1f3f8;
}
.image-video-permission-tab {
flex: 1;
min-height: 34px;
padding: 7px 10px;
border: 0;
border-radius: 4px;
background: transparent;
color: var(--c-text-2);
cursor: pointer;
font: inherit;
font-size: 13px;
transition: background 0.15s ease, color 0.15s ease;
}
.image-video-permission-tab.active {
background: #fff;
color: #4f46e5;
box-shadow: 0 1px 4px rgba(99, 102, 241, 0.14);
font-weight: 600;
}
.image-video-permission-search {
flex: 1;
min-width: 0;
padding: 9px 10px;
border: 1px solid var(--c-border);
border-radius: var(--radius-sm);
font: inherit;
}
.image-video-permission-list {
max-height: 52vh;
overflow: auto;
border: 1px solid #e5e7f2;
border-radius: 6px;
}
.image-video-permission-row {
display: grid;
grid-template-columns: 24px minmax(0, 1fr) auto;
align-items: center;
gap: 10px;
min-height: 48px;
padding: 9px 12px;
border-bottom: 1px solid #eef1f6;
}
.image-video-permission-row:last-child {
border-bottom: 0;
}
.image-video-permission-row input {
width: 16px;
height: 16px;
accent-color: #6366f1;
}
.image-video-permission-user {
min-width: 0;
display: flex;
flex-direction: column;
gap: 2px;
}
.image-video-permission-name {
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
color: var(--c-text);
}
.image-video-permission-role {
color: var(--c-text-3);
font-size: 12px;
}
.image-video-permission-state {
min-width: 68px;
padding: 4px 10px;
border: 1px solid var(--c-border);
border-radius: 999px;
background: #fff;
color: #6d7688;
cursor: pointer;
font-size: 12px;
white-space: nowrap;
transition: border-color 0.15s ease, background 0.15s ease, color 0.15s ease;
}
.image-video-permission-state.granted {
border-color: #b9bcf3;
background: #f0f1fe;
color: #4f46e5;
}
.image-video-permission-state.pending {
border-color: #f0d48c;
background: #fdf6e9;
color: var(--c-warning);
}
.image-video-permission-summary {
margin: 0 0 10px;
color: var(--c-text-2);
font-size: 13px;
}
.image-video-permission-empty {
padding: 36px 16px;
color: var(--c-text-2);
text-align: center;
}
.image-video-permission-actions {
display: flex;
justify-content: flex-end;
gap: 8px;
margin-top: 16px;
}
@media (max-width: 960px) {
.admin-layout {
grid-template-columns: minmax(0, 1fr);
}
.admin-sidebar {
position: static;
height: auto;
border-right: none;
border-bottom: 1px solid rgba(255, 255, 255, 0.06);
}
.admin-brand {
padding: 12px 16px 8px;
}
.admin-menu {
display: flex;
align-items: center;
gap: 6px;
overflow-x: auto;
overscroll-behavior-x: contain;
padding: 0 12px 10px;
}
.menu-group {
display: flex;
align-items: center;
gap: 4px;
margin: 0;
}
.menu-group.is-collapsed .menu-group-body {
display: flex;
}
.menu-group-body {
display: flex;
align-items: center;
gap: 4px;
padding: 0;
}
.menu-group + .menu-group::before {
content: "";
width: 1px;
height: 14px;
background: rgba(255, 255, 255, 0.14);
margin: 0 6px;
flex: 0 0 auto;
}
.menu-group-title {
display: none;
}
.menu-group-chevron {
display: none;
}
.menu-group.is-collapsed .menu-group-body {
display: flex;
}
.tab {
flex: 0 0 auto;
padding: 7px 10px;
margin-bottom: 0;
white-space: nowrap;
}
.tab.active::before {
top: auto;
bottom: -10px;
left: 20%;
right: 20%;
width: auto;
height: 3px;
}
.admin-topbar {
padding: 0 14px;
height: 52px;
}
.admin-user-name {
max-width: 120px;
}
.admin-content {
padding: 16px 14px 32px;
}
.admin-sidebar-foot {
display: none;
}
}
@media (max-width: 760px) {
.form-row {
flex-direction: column;
align-items: stretch;
}
.form-row .form-group {
flex: none;
min-width: 0;
}
.dedupe-filter-date-row .form-group {
flex: 1 1 100%;
min-width: 0;
}
.dedupe-filter-date-row .dedupe-filter-actions {
margin-left: 0;
}
.admin-content {
padding: 16px 14px 28px;
}
.form-box,
.panel-box {
padding: 18px;
}
.dedupe-filter-row {
grid-template-columns: minmax(0, 1fr);
}
.dedupe-filter-actions {
grid-column: auto;
flex-wrap: wrap;
}
.admin-user-role {
display: none;
}
.image-video-results {
padding: 16px;
}
.image-video-toolbar {
align-items: flex-start;
flex-direction: column;
}
.image-video-summary {
text-align: left;
}
.image-video-grid {
grid-template-columns: minmax(0, 1fr);
gap: 16px;
}
}
/* ===== 全局滚动条 ===== */
::-webkit-scrollbar {
width: 8px;
height: 8px;
}
::-webkit-scrollbar-track {
background: transparent;
}
::-webkit-scrollbar-thumb {
background: #ccd3e2;
border-radius: 99px;
border: 2px solid transparent;
background-clip: content-box;
}
::-webkit-scrollbar-thumb:hover {
background: #aab3c8;
background-clip: content-box;
}
:focus-visible {
outline: 2px solid var(--c-primary);
outline-offset: 1px;
}
/* ===== 数富AI Admin Console Refresh =====
设计基线:深色专业控制台 / 高密度数据工作台 / 语义化状态色
与原有 class、id、inline loader 兼容,只重塑视觉层级与交互反馈。 */
:root {
color-scheme: dark;
--c-primary: #818cf8;
--c-primary-strong: #a5b4fc;
--c-primary-soft: rgba(129, 140, 248, 0.15);
--c-bg: #0b1220;
--c-card: #141e31;
--c-card-raised: #1a2740;
--c-border: rgba(148, 163, 184, 0.18);
--c-border-strong: rgba(148, 163, 184, 0.32);
--c-text: #f1f5f9;
--c-text-2: #b8c4d8;
--c-text-3: #8795ad;
--c-danger: #fb7185;
--c-danger-soft: rgba(251, 113, 133, 0.13);
--c-success: #4ade80;
--c-warning: #fbbf24;
--c-sidebar: #080f1d;
--c-sidebar-2: #101a2e;
--c-sidebar-text: #aab7cd;
--radius: 16px;
--radius-sm: 10px;
--shadow-card: 0 1px 2px rgba(2, 6, 23, 0.36), 0 16px 34px -24px rgba(2, 6, 23, 0.9);
--shadow-pop: 0 24px 80px rgba(2, 6, 23, 0.56);
--motion-fast: 160ms;
--motion-base: 220ms;
}
html {
min-width: 320px;
background: var(--c-bg);
}
body {
min-width: 320px;
font-family: Inter, "SF Pro Display", "Microsoft YaHei", "PingFang SC", "Helvetica Neue", sans-serif;
font-size: 14px;
line-height: 1.5;
background:
radial-gradient(circle at 16% -12%, rgba(99, 102, 241, 0.15), transparent 32rem),
radial-gradient(circle at 96% 8%, rgba(34, 197, 94, 0.05), transparent 24rem),
var(--c-bg);
color: var(--c-text);
}
.skip-link {
position: fixed;
top: 12px;
left: 12px;
z-index: 4000;
padding: 10px 14px;
border-radius: 9px;
background: var(--c-primary);
color: #0b1220;
font-weight: 700;
transform: translateY(-160%);
transition: transform var(--motion-fast) ease;
}
.skip-link:focus {
transform: translateY(0);
}
.admin-layout {
grid-template-columns: 252px minmax(0, 1fr);
background: transparent;
}
.admin-sidebar {
background:
linear-gradient(180deg, rgba(16, 26, 46, 0.98), rgba(8, 15, 29, 0.99)),
radial-gradient(circle at 10% 0%, rgba(129, 140, 248, 0.12), transparent 20rem);
border-right: 1px solid rgba(148, 163, 184, 0.14);
box-shadow: 20px 0 48px rgba(2, 6, 23, 0.18);
}
.admin-brand {
min-height: 76px;
padding: 20px 18px 18px;
border-bottom-color: rgba(148, 163, 184, 0.14);
}
.admin-brand-mark {
width: 36px;
height: 36px;
border-radius: 11px;
background: #fff;
box-shadow: 0 8px 22px rgba(79, 70, 229, 0.36);
}
.admin-brand-name {
color: #f8fafc;
font-size: 15px;
letter-spacing: 0.35px;
}
.admin-brand-sub {
color: #8492aa;
letter-spacing: 0.1px;
}
.admin-menu {
padding: 12px 12px 18px;
scrollbar-color: rgba(148, 163, 184, 0.22) transparent;
}
.menu-group {
margin-top: 18px;
}
.menu-group:first-child {
margin-top: 4px;
}
.menu-group-title {
min-height: 40px;
padding: 9px 11px;
color: #8190a8;
letter-spacing: 1.1px;
}
.menu-group-title:hover {
color: #d8e0ee;
background: rgba(148, 163, 184, 0.08);
}
.tab {
min-height: 42px;
margin-bottom: 4px;
padding: 10px 12px;
border: 1px solid transparent;
background: transparent;
color: var(--c-sidebar-text);
font: inherit;
font-size: 13.5px;
text-align: left;
width: 100%;
transition: background var(--motion-fast) ease, border-color var(--motion-fast) ease, color var(--motion-fast) ease, transform var(--motion-fast) ease;
}
.tab:hover {
background: rgba(148, 163, 184, 0.08);
border-color: rgba(148, 163, 184, 0.12);
color: #f8fafc;
transform: translateX(2px);
}
.tab.active {
background: linear-gradient(90deg, rgba(129, 140, 248, 0.22), rgba(129, 140, 248, 0.08));
border-color: rgba(129, 140, 248, 0.24);
color: #f8fafc;
box-shadow: inset 3px 0 0 #818cf8, 0 8px 18px -16px rgba(129, 140, 248, 0.9);
}
.tab.active::before {
display: none;
}
.adm-icon {
width: 19px;
height: 19px;
color: #8290a8;
}
.tab:hover .adm-icon,
.tab.active .adm-icon {
color: #a5b4fc;
}
.admin-sidebar-foot {
padding: 14px 18px 18px;
color: #72819a;
border-top: 1px solid rgba(148, 163, 184, 0.12);
letter-spacing: 0.2px;
}
.admin-main {
min-width: 0;
background: transparent;
}
.admin-topbar {
height: 76px;
padding: 0 32px;
background: rgba(11, 18, 32, 0.76);
border-bottom-color: rgba(148, 163, 184, 0.14);
box-shadow: 0 10px 30px -28px rgba(2, 6, 23, 0.9);
backdrop-filter: blur(18px);
}
.admin-topbar-left {
min-width: 0;
}
.admin-topbar-kicker {
display: block;
margin-bottom: 2px;
color: #7888a3;
font-size: 10px;
font-weight: 700;
letter-spacing: 1.6px;
line-height: 1.2;
}
.admin-page-heading {
display: flex;
align-items: baseline;
gap: 9px;
min-width: 0;
}
.admin-page-section,
.admin-page-separator {
color: #8d9bb2;
font-size: 12px;
white-space: nowrap;
}
.admin-page-separator {
color: #52617b;
}
.admin-page-title {
color: var(--c-text);
font-size: 18px;
font-weight: 650;
letter-spacing: -0.2px;
}
.admin-user-box {
gap: 12px;
}
.admin-user-role {
color: #c7d2fe;
background: rgba(129, 140, 248, 0.15);
border: 1px solid rgba(129, 140, 248, 0.22);
}
.admin-user-logout {
min-height: 40px;
padding: 8px 14px;
background: rgba(20, 30, 49, 0.86);
color: #b8c4d8;
border-color: rgba(148, 163, 184, 0.22);
}
.admin-user-logout:hover {
color: #f1f5f9;
border-color: rgba(129, 140, 248, 0.5);
background: rgba(129, 140, 248, 0.14);
}
.admin-content {
width: min(100%, 1680px);
padding: 30px 32px 56px;
}
.tab-panel.active {
animation: admin-panel-enter 240ms ease both;
}
@keyframes admin-panel-enter {
from { opacity: 0; transform: translateY(6px); }
to { opacity: 1; transform: translateY(0); }
}
.form-box,
.panel-box {
background: linear-gradient(145deg, rgba(20, 30, 49, 0.98), rgba(16, 26, 46, 0.96));
border-color: var(--c-border);
border-radius: 18px;
padding: 24px;
box-shadow: var(--shadow-card);
}
.form-box:hover,
.panel-box:hover {
border-color: rgba(148, 163, 184, 0.25);
}
.form-box > h3:first-child,
.panel-box > h3:first-child,
.modal h3 {
color: var(--c-text);
letter-spacing: -0.15px;
}
.form-group label {
color: var(--c-text-2);
letter-spacing: 0.05px;
}
.form-group input,
.form-group select,
.form-group textarea,
.form-group input[type="search"] {
min-height: 42px;
background: rgba(11, 18, 32, 0.7);
border-color: rgba(148, 163, 184, 0.22);
color: var(--c-text);
color-scheme: dark;
}
.form-group textarea {
min-height: 96px;
}
.form-group input:focus,
.form-group select:focus,
.form-group textarea:focus {
background: rgba(11, 18, 32, 0.9);
border-color: var(--c-primary);
box-shadow: 0 0 0 3px rgba(129, 140, 248, 0.16);
}
.form-group input::placeholder,
.form-group textarea::placeholder {
color: #708099;
}
.form-group input[type="file"] {
min-height: 42px;
padding: 7px 10px;
background: rgba(11, 18, 32, 0.7);
}
.form-group input[type="file"]::file-selector-button {
margin-right: 10px;
padding: 6px 10px;
border: 1px solid rgba(129, 140, 248, 0.32);
border-radius: 7px;
background: rgba(129, 140, 248, 0.13);
color: #dbe4ff;
font: inherit;
cursor: pointer;
}
.form-group input[readonly] {
background: rgba(148, 163, 184, 0.08) !important;
color: var(--c-text-2);
}
.btn {
min-height: 42px;
padding: 10px 17px;
background: linear-gradient(135deg, #818cf8, #6366f1);
color: #0b1220;
font-weight: 700;
box-shadow: 0 7px 18px -12px rgba(129, 140, 248, 0.9);
transition: box-shadow var(--motion-fast) ease, transform var(--motion-fast) ease, background var(--motion-fast) ease, opacity var(--motion-fast) ease;
}
.btn:hover {
background: linear-gradient(135deg, #a5b4fc, #818cf8);
color: #0b1220;
box-shadow: 0 11px 24px -12px rgba(129, 140, 248, 0.95);
}
.btn:active {
transform: translateY(1px) scale(0.99);
}
.btn-secondary {
background: rgba(20, 30, 49, 0.72);
color: var(--c-text-2);
border: 1px solid rgba(148, 163, 184, 0.24);
}
.btn-secondary:hover {
color: var(--c-text);
border-color: rgba(129, 140, 248, 0.48);
background: rgba(129, 140, 248, 0.13);
}
.btn-danger {
background: linear-gradient(135deg, #fb7185, #e11d48);
color: #fff1f2;
}
.btn-danger:hover {
background: linear-gradient(135deg, #fda4af, #fb7185);
color: #4c0519;
}
.btn-sm {
min-height: 36px;
padding: 7px 12px;
}
.msg {
color: var(--c-text-2);
background: rgba(148, 163, 184, 0.06);
border-color: rgba(148, 163, 184, 0.15);
}
.msg.ok {
color: #86efac;
background: rgba(74, 222, 128, 0.11);
border-color: rgba(74, 222, 128, 0.25);
}
.msg.err {
color: #fda4af;
background: rgba(251, 113, 133, 0.11);
border-color: rgba(251, 113, 133, 0.25);
}
.progress-bar {
background: rgba(148, 163, 184, 0.12);
}
.progress-fill {
background: linear-gradient(90deg, #818cf8, #4ade80);
}
.request-loading {
color: var(--c-text);
background: rgba(20, 30, 49, 0.96);
border-color: rgba(148, 163, 184, 0.24);
box-shadow: var(--shadow-pop);
}
.request-spinner {
border-color: rgba(129, 140, 248, 0.22);
border-top-color: var(--c-primary);
}
.dedupe-export-wait-mask {
background: rgba(2, 6, 23, 0.68);
backdrop-filter: blur(7px);
}
.dedupe-export-wait {
background: #18243a;
border: 1px solid var(--c-border-strong);
box-shadow: var(--shadow-pop);
}
.dedupe-export-wait-title {
color: var(--c-text);
}
.dedupe-export-wait-detail {
color: var(--c-text-2);
}
th,
td {
border-bottom-color: rgba(148, 163, 184, 0.14);
}
th {
background: rgba(11, 18, 32, 0.62);
color: var(--c-text-2);
}
tbody tr:hover {
background: rgba(129, 140, 248, 0.07);
}
.empty-tip {
color: var(--c-text-3);
}
.tree-node-mark {
background: rgba(129, 140, 248, 0.14);
color: var(--c-primary-strong);
}
.tree-node-mark:not(.is-leaf):hover {
background: var(--c-primary);
color: #0b1220;
}
.tree-node-mark.is-leaf {
background: transparent;
}
.category-path,
.progress-text {
color: var(--c-text-3);
}
.category-tag {
background: rgba(129, 140, 248, 0.14);
color: var(--c-primary-strong);
border: 1px solid rgba(129, 140, 248, 0.22);
}
.modal-mask {
background: rgba(2, 6, 23, 0.7);
backdrop-filter: blur(8px);
}
.modal {
background: #18243a;
border: 1px solid var(--c-border-strong);
box-shadow: var(--shadow-pop);
}
.shop-group-table-wrap,
.dedupe-group-access {
border-color: rgba(148, 163, 184, 0.16);
}
.shop-group-table-wrap {
background: rgba(11, 18, 32, 0.38);
}
.dedupe-group-summary-chip {
background: rgba(148, 163, 184, 0.08);
color: var(--c-text-2);
border-color: rgba(148, 163, 184, 0.18);
}
.shop-key-whitelist-status.is-allowed {
color: #86efac;
background: rgba(74, 222, 128, 0.11);
border-color: rgba(74, 222, 128, 0.25);
}
.shop-key-whitelist-status.is-blocked {
color: #fda4af;
background: rgba(251, 113, 133, 0.11);
border-color: rgba(251, 113, 133, 0.25);
}
.shop-key-whitelist-status.is-unknown {
color: var(--c-text-2);
background: rgba(148, 163, 184, 0.08);
border-color: rgba(148, 163, 184, 0.2);
}
.shop-password-toggle {
width: 40px;
height: 40px;
flex-basis: 40px;
color: var(--c-text-2);
}
.shop-password-toggle:hover:not(:disabled) {
color: var(--c-primary-strong);
background: rgba(129, 140, 248, 0.13);
}
::-webkit-scrollbar-thumb {
background: rgba(148, 163, 184, 0.28);
background-clip: content-box;
}
::-webkit-scrollbar-thumb:hover {
background: rgba(148, 163, 184, 0.46);
background-clip: content-box;
}
:focus-visible {
outline: 2px solid #a5b4fc;
outline-offset: 3px;
}
@media (max-width: 960px) {
.admin-topbar {
padding: 0 20px;
}
.admin-content {
padding: 24px 20px 44px;
}
}
@media (max-width: 760px) {
.admin-brand {
min-height: 68px;
padding: 14px 16px 12px;
}
.admin-topbar {
height: 68px;
padding: 0 14px;
}
.admin-topbar-kicker,
.admin-page-section,
.admin-page-separator {
display: none;
}
.admin-page-title {
font-size: 16px;
}
.admin-content {
padding: 18px 14px 32px;
}
.form-box,
.panel-box {
padding: 18px;
border-radius: 14px;
}
.admin-user-box {
gap: 8px;
}
.admin-user-name {
max-width: 104px;
}
.admin-user-logout {
min-height: 38px;
padding: 7px 10px;
}
}
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
scroll-behavior: auto !important;
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}
/* ===== 组件层深色适配与触控尺寸 ===== */
html { scroll-padding-top: 92px; }
.menu-group-title,
.tab,
.btn,
.btn-sm,
.admin-user-logout,
.shop-password-toggle,
.image-video-card-action,
.image-video-permission-tab,
.image-video-permission-state {
min-height: 44px;
}
.form-group input,
.form-group select,
.form-group textarea,
.form-group input[type="search"],
.multi-select-trigger,
.image-video-permission-search {
min-height: 44px;
}
.column-permission-tree,
.multi-select-panel,
.column-permission-select {
background: rgba(11, 18, 32, 0.72);
border-color: rgba(148, 163, 184, 0.22);
color: var(--c-text);
color-scheme: dark;
}
.column-tree-group-toggle {
min-height: 44px;
background: rgba(148, 163, 184, 0.08);
color: var(--c-text);
}
.column-tree-group-toggle:hover,
.column-tree-toggle:hover {
background: rgba(129, 140, 248, 0.13);
color: var(--c-primary-strong);
}
.column-tree-group-toggle::before,
.column-tree-group-count {
color: var(--c-text-3);
}
.column-tree-node label {
min-height: 36px;
color: var(--c-text-2);
}
.multi-select-trigger {
background: rgba(11, 18, 32, 0.7);
border-color: rgba(148, 163, 184, 0.22);
color: var(--c-text);
}
.multi-select-trigger:hover,
.multi-select-dropdown.open .multi-select-trigger {
background: rgba(11, 18, 32, 0.92);
border-color: var(--c-primary);
}
.multi-select-option {
min-height: 44px;
color: var(--c-text-2);
}
.multi-select-option:hover {
background: rgba(129, 140, 248, 0.13);
color: var(--c-text);
}
.column-permission-card {
background: rgba(129, 140, 248, 0.14);
color: var(--c-primary-strong);
border: 1px solid rgba(129, 140, 248, 0.22);
}
.image-video-card {
background: linear-gradient(145deg, rgba(20, 30, 49, 0.98), rgba(16, 26, 46, 0.96));
border-color: rgba(148, 163, 184, 0.18);
}
.image-video-card:hover {
border-color: rgba(129, 140, 248, 0.48);
box-shadow: 0 14px 28px -18px rgba(2, 6, 23, 0.95);
}
.image-video-status {
background: rgba(148, 163, 184, 0.1);
color: var(--c-text-2);
}
.image-video-status.SUCCESS {
color: #86efac;
background: rgba(74, 222, 128, 0.11);
}
.image-video-status.FAILED {
color: #fda4af;
background: rgba(251, 113, 133, 0.11);
}
.image-video-status.RUNNING,
.image-video-status.POLLING {
color: #fde68a;
background: rgba(251, 191, 36, 0.12);
}
.image-video-info-row a,
.image-video-copy-link {
color: var(--c-primary-strong);
}
.image-video-card-action {
border-color: rgba(129, 140, 248, 0.42);
background: rgba(20, 30, 49, 0.72);
color: var(--c-primary-strong);
}
.image-video-card-action:hover {
background: var(--c-primary);
color: #0b1220;
}
.image-video-empty {
border-color: rgba(129, 140, 248, 0.32);
background: rgba(129, 140, 248, 0.08);
color: var(--c-text-2);
}
.shop-data-result-list,
.shop-data-result {
border-color: rgba(148, 163, 184, 0.14);
}
.shop-data-result.selected {
background: rgba(129, 140, 248, 0.09);
}
.image-video-permission-tabs {
background: rgba(11, 18, 32, 0.72);
border: 1px solid rgba(148, 163, 184, 0.16);
}
.image-video-permission-tab {
color: var(--c-text-2);
}
.image-video-permission-tab:hover,
.image-video-permission-tab:focus-visible {
color: var(--c-text);
background: rgba(129, 140, 248, 0.1);
}
.image-video-permission-tab.active {
background: rgba(129, 140, 248, 0.2);
color: var(--c-primary-strong);
box-shadow: none;
}
.image-video-permission-search {
padding: 9px 12px;
background: rgba(11, 18, 32, 0.7);
border: 1px solid rgba(148, 163, 184, 0.22);
border-radius: var(--radius-sm);
color: var(--c-text);
font: inherit;
color-scheme: dark;
}
.image-video-permission-search:focus {
outline: none;
border-color: var(--c-primary);
box-shadow: 0 0 0 3px rgba(129, 140, 248, 0.16);
}
.image-video-permission-list {
border-color: rgba(148, 163, 184, 0.2);
background: rgba(11, 18, 32, 0.42);
}
.image-video-permission-row {
border-bottom-color: rgba(148, 163, 184, 0.14);
}
.image-video-permission-state {
display: inline-flex;
align-items: center;
justify-content: center;
background: rgba(148, 163, 184, 0.08);
color: var(--c-text-2);
}
.image-video-permission-state.granted {
border-color: rgba(129, 140, 248, 0.32);
background: rgba(129, 140, 248, 0.14);
color: var(--c-primary-strong);
}
.image-video-permission-state.pending {
border-color: rgba(251, 191, 36, 0.32);
background: rgba(251, 191, 36, 0.12);
color: #fde68a;
}
.image-video-permission-empty {
color: var(--c-text-2);
}
select option {
background: #141e31;
color: var(--c-text);
}
@media (max-width: 760px) {
.btn-sm,
.image-video-permission-tab {
min-height: 44px;
}
}
/* 重新声明断点规则:覆盖全局桌面网格,避免平板端侧栏被拉成整页高 */
@media (max-width: 960px) {
.admin-layout {
grid-template-columns: minmax(0, 1fr);
}
.admin-sidebar {
position: static;
height: auto;
border-right: none;
border-bottom: 1px solid rgba(148, 163, 184, 0.14);
}
.admin-brand {
padding: 14px 16px 10px;
}
.admin-menu {
display: flex;
align-items: center;
gap: 6px;
overflow-x: auto;
overflow-y: hidden;
overscroll-behavior-x: contain;
padding: 0 12px 12px;
}
.menu-group {
display: flex;
align-items: center;
gap: 4px;
flex: 0 0 auto;
margin: 0;
}
.menu-group-body {
display: flex;
align-items: center;
gap: 4px;
padding: 0;
}
.menu-group-title,
.menu-group-chevron {
display: none;
}
.menu-group + .menu-group::before {
content: "";
width: 1px;
height: 18px;
margin: 0 6px;
background: rgba(148, 163, 184, 0.2);
}
.tab {
flex: 0 0 auto;
width: auto;
margin-bottom: 0;
white-space: nowrap;
transform: none;
}
.tab:hover {
transform: translateY(-1px);
}
.tab.active {
box-shadow: inset 0 -3px 0 #818cf8, 0 8px 18px -16px rgba(129, 140, 248, 0.9);
}
.admin-sidebar-foot {
display: none;
}
.admin-topbar {
padding: 0 20px;
}
.admin-content {
padding: 24px 20px 44px;
}
}
@media (max-width: 760px) {
.admin-topbar {
height: 68px;
padding: 0 14px;
}
.admin-topbar-kicker,
.admin-page-section,
.admin-page-separator {
display: none;
}
.admin-page-title {
font-size: 16px;
}
.admin-content {
padding: 18px 14px 32px;
}
.form-box,
.panel-box {
padding: 18px;
border-radius: 14px;
}
.admin-user-box {
gap: 8px;
}
.admin-user-name {
max-width: 104px;
}
.admin-user-logout {
padding: 7px 10px;
}
}
/* ===== 莫兰迪亮色主题与数据表布局优化 ===== */
:root {
color-scheme: light;
--c-primary: #607a6d;
--c-primary-strong: #456052;
--c-primary-soft: #e7eeea;
--c-bg: #f2f4f1;
--c-card: #ffffff;
--c-card-raised: #f8faf8;
--c-border: #dbe3dd;
--c-border-strong: #bdcbc1;
--c-text: #29362f;
--c-text-2: #5d6c63;
--c-text-3: #7f8d84;
--c-danger: #a9545d;
--c-danger-soft: #f6e8e8;
--c-success: #4e8068;
--c-warning: #a87538;
--c-sidebar: #e7ece8;
--c-sidebar-2: #f1f4f1;
--c-sidebar-text: #596a60;
--radius: 14px;
--radius-sm: 9px;
--shadow-card: 0 1px 2px rgba(60, 77, 67, 0.05), 0 14px 30px -24px rgba(60, 77, 67, 0.28);
--shadow-pop: 0 22px 60px rgba(49, 64, 55, 0.2);
}
html { background: var(--c-bg); }
body {
color: var(--c-text);
background:
radial-gradient(circle at 14% -14%, rgba(177, 198, 185, 0.32), transparent 34rem),
radial-gradient(circle at 100% 0%, rgba(213, 190, 178, 0.17), transparent 26rem),
var(--c-bg);
}
.admin-sidebar {
background: linear-gradient(180deg, var(--c-sidebar-2), var(--c-sidebar));
color: var(--c-sidebar-text);
border-right: 1px solid #d5ded7;
box-shadow: 14px 0 34px -28px rgba(52, 70, 59, 0.34);
}
.admin-brand { border-bottom-color: #d5ded7; }
.admin-brand-mark {
background: #fff;
box-shadow: 0 8px 20px rgba(96, 122, 109, 0.25);
}
.admin-brand-name { color: var(--c-text); }
.admin-brand-sub { color: #7b8981; }
.admin-menu { scrollbar-color: #c1cec4 transparent; }
.menu-group-title { color: #748279; }
.menu-group-title:hover { color: var(--c-primary-strong); background: rgba(96, 122, 109, 0.09); }
.tab { color: var(--c-sidebar-text); }
.tab:hover { color: var(--c-primary-strong); background: rgba(96, 122, 109, 0.09); border-color: rgba(96, 122, 109, 0.16); }
.tab.active { color: var(--c-primary-strong); background: linear-gradient(90deg, #dce8df, #edf3ee); border-color: #b8cbbd; box-shadow: inset 3px 0 0 #6f8b7b, 0 8px 18px -16px rgba(96, 122, 109, 0.5); }
.tab.active .adm-icon, .tab:hover .adm-icon { color: #607f6d; }
.admin-sidebar-foot { color: #7b8981; border-top-color: #d5ded7; }
.admin-topbar {
background: rgba(255, 255, 255, 0.9);
border-bottom-color: #dbe3dd;
box-shadow: 0 10px 28px -28px rgba(52, 70, 59, 0.42);
}
.admin-topbar-kicker { color: #74877b; }
.admin-page-section { color: #77877e; }
.admin-page-separator { color: #b1bdb5; }
.admin-page-title { color: var(--c-text); }
.admin-user-name { color: var(--c-text); }
.admin-user-role { color: var(--c-primary-strong); background: #e5eee7; border-color: #c4d5c8; }
.admin-user-logout { color: var(--c-text-2); background: #ffffff; border-color: #cfdad2; }
.admin-user-logout:hover { color: var(--c-primary-strong); border-color: #9eb5a5; background: #edf4ee; }
.admin-content { width: 100%; max-width: none; }
.tab-panel { width: 100%; min-width: 0; }
.form-box, .panel-box { width: 100%; min-width: 0; background: linear-gradient(145deg, #ffffff, #f9fbf9); border-color: var(--c-border); box-shadow: var(--shadow-card); }
.form-box:hover, .panel-box:hover { border-color: #c3d2c7; }
.form-box > h3:first-child, .panel-box > h3:first-child, .modal h3 { color: var(--c-text); }
.form-group label { color: var(--c-text-2); }
.form-group input, .form-group select, .form-group textarea, .form-group input[type="search"], .multi-select-trigger, .image-video-permission-search { background: #f8faf8; border-color: #cfdad2; color: var(--c-text); color-scheme: light; }
.form-group input:hover, .form-group select:hover, .form-group textarea:hover, .multi-select-trigger:hover { border-color: #aebfb3; }
.form-group input:focus, .form-group select:focus, .form-group textarea:focus, .image-video-permission-search:focus { background: #ffffff; border-color: #6f8b7b; box-shadow: 0 0 0 3px rgba(111, 139, 123, 0.16); }
.form-group input::placeholder, .form-group textarea::placeholder, .image-video-permission-search::placeholder { color: #87958c; }
.form-group input[type="file"] { background: #ffffff; }
.form-group input[type="file"]::file-selector-button { border-color: #b5c9ba; background: #edf3ee; color: var(--c-primary-strong); }
.form-group input[readonly] { background: #eef2ef !important; color: var(--c-text-2); }
.btn { background: linear-gradient(135deg, #718b7c, #607a6d); color: #ffffff; box-shadow: 0 8px 18px -14px rgba(96, 122, 109, 0.72); }
.btn:hover { background: linear-gradient(135deg, #7f998a, #6b8577); color: #ffffff; box-shadow: 0 11px 22px -14px rgba(96, 122, 109, 0.78); }
.btn-secondary { background: #ffffff; color: var(--c-text-2); border-color: #cbd8cf; }
.btn-secondary:hover { color: var(--c-primary-strong); border-color: #99b09f; background: #edf4ee; }
.btn-danger { background: linear-gradient(135deg, #bb6c72, #a9545d); color: #ffffff; }
.btn-danger:hover { background: linear-gradient(135deg, #c77b80, #b45e67); color: #ffffff; }
.msg { color: var(--c-text-2); background: #f2f6f3; border-color: #d8e2da; }
.msg.ok { color: #3d7158; background: #e8f2eb; border-color: #bcd5c3; }
.msg.err { color: #91474f; background: #f8ebeb; border-color: #e4c2c5; }
.progress-bar { background: #e4ebe5; }
.progress-fill { background: linear-gradient(90deg, #7f998a, #b49a84); }
.request-loading { color: var(--c-text); background: rgba(255, 255, 255, 0.97); border-color: #cbd8cf; box-shadow: var(--shadow-pop); }
.request-spinner { border-color: #d9e5dc; border-top-color: #6f8b7b; }
.dedupe-export-wait-mask, .modal-mask { background: rgba(52, 67, 58, 0.38); backdrop-filter: blur(6px); }
.dedupe-export-wait, .modal { background: #ffffff; border-color: #cbd8cf; box-shadow: var(--shadow-pop); }
.dedupe-export-wait-title { color: var(--c-text); }
.dedupe-export-wait-detail { color: var(--c-text-2); }
.table-scroll { width: 100%; min-width: 0; overflow-x: auto; overflow-y: hidden; border: 1px solid #e0e7e1; border-radius: 10px; background: #ffffff; }
.panel-box > .table-scroll { width: 100%; }
.table-scroll > table { width: 100% !important; min-width: 0; table-layout: fixed; }
.panel-box > table { width: 100% !important; table-layout: fixed; }
th { background: #edf2ee; color: #4e6055; border-bottom-color: #d5e0d7; }
td { color: var(--c-text); border-bottom-color: #e2e9e3; }
tbody tr:hover { background: #f0f6f1; }
.empty-tip { color: var(--c-text-3); }
.pagination { color: var(--c-text-2); }
.pagination button { background: #ffffff; border-color: #cbd8cf; color: var(--c-text-2); }
.pagination button:hover:not(:disabled) { background: #edf4ee; border-color: #99b09f; color: var(--c-primary-strong); }
.pagination button:disabled { background: #eef2ef; color: #9aa79e; }
#panel-users .user-table-scroll th:nth-child(1) { width: 8%; }
#panel-users .user-table-scroll th:nth-child(2) { width: 20%; }
#panel-users .user-table-scroll th:nth-child(3) { width: 17%; }
#panel-users .user-table-scroll th:nth-child(4) { width: 20%; }
#panel-users .user-table-scroll th:nth-child(5) { width: 22%; }
#panel-users .user-table-scroll th:nth-child(6) { width: 13%; }
#panel-dedupe-total-data .dedupe-table-scroll th:nth-child(1) { width: 13%; }
#panel-dedupe-total-data .dedupe-table-scroll th:nth-child(2) { width: 18%; }
#panel-dedupe-total-data .dedupe-table-scroll th:nth-child(3) { width: 12%; }
#panel-dedupe-total-data .dedupe-table-scroll th:nth-child(4) { width: 16%; }
#panel-dedupe-total-data .dedupe-table-scroll th:nth-child(5) { width: 16%; }
#panel-dedupe-total-data .dedupe-table-scroll th:nth-child(6) { width: 16%; }
#panel-dedupe-total-data .dedupe-table-scroll th:nth-child(7) { width: 9%; }
.shop-manage-table-scroll > table { min-width: 1360px; table-layout: fixed; }
#panel-shop-manage .shop-manage-table-scroll th:nth-child(1) { width: 58px; }
#panel-shop-manage .shop-manage-table-scroll th:nth-child(2) { width: 116px; }
#panel-shop-manage .shop-manage-table-scroll th:nth-child(3) { width: 126px; }
#panel-shop-manage .shop-manage-table-scroll th:nth-child(4) { width: 210px; }
#panel-shop-manage .shop-manage-table-scroll th:nth-child(5) { width: 140px; }
#panel-shop-manage .shop-manage-table-scroll th:nth-child(6) { width: 180px; }
#panel-shop-manage .shop-manage-table-scroll th:nth-child(7) { width: 152px; }
#panel-shop-manage .shop-manage-table-scroll th:nth-child(8), #panel-shop-manage .shop-manage-table-scroll th:nth-child(9) { width: 132px; }
#panel-shop-manage .shop-manage-table-scroll th:nth-child(10) { width: 200px; }
#panel-shop-manage .shop-manage-table-scroll td { overflow: hidden; }
.table-ellipsis { display: block; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.shop-password-cell { max-width: 100%; }
.shop-password-value { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.shop-col-password { min-width: 0; }
.shop-col-actions { white-space: nowrap; }
.category-table-scroll > table { min-width: 860px; }
.category-table-scroll td { vertical-align: middle; }
.category-table-scroll .category-path-value { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.category-table-scroll .category-path { display: none; }
.category-tag { background: #e7eee9; border-color: #c5d6c9; color: var(--c-primary-strong); }
.tree-node-mark { background: #e5eee7; color: var(--c-primary-strong); }
.tree-node-mark:not(.is-leaf):hover { background: #6f8b7b; color: #ffffff; }
.column-permission-tree, .multi-select-panel, .column-permission-select { background: #ffffff; border-color: #cfdad2; color: var(--c-text); color-scheme: light; }
.column-tree-group-toggle { background: #edf3ee; color: var(--c-text); }
.column-tree-group-toggle:hover, .column-tree-toggle:hover { background: #e2ede4; color: var(--c-primary-strong); }
.column-tree-node label { color: var(--c-text-2); }
.multi-select-option { color: var(--c-text-2); }
.multi-select-option:hover { background: #edf4ee; color: var(--c-text); }
.column-permission-card { background: #e5eee7; color: var(--c-primary-strong); border-color: #c4d6c9; }
.image-video-card { background: #ffffff; border-color: var(--c-border); }
.image-video-card:hover { border-color: #a9bfaf; box-shadow: 0 14px 28px -18px rgba(60, 77, 67, 0.32); }
.image-video-status { background: #edf2ee; color: var(--c-text-2); }
.image-video-status.SUCCESS { color: #3e7258; background: #e8f2eb; }
.image-video-status.FAILED { color: #934a52; background: #f8ebeb; }
.image-video-status.RUNNING, .image-video-status.POLLING { color: #8c642b; background: #f7efdf; }
.image-video-info-row a, .image-video-copy-link { color: var(--c-primary-strong); }
.image-video-card-action { border-color: #a9bfaf; background: #ffffff; color: var(--c-primary-strong); }
.image-video-card-action:hover { background: #6f8b7b; color: #ffffff; }
.image-video-empty { border-color: #b8cdbd; background: #edf4ee; color: var(--c-text-2); }
.shop-data-result-list, .shop-data-result { border-color: #e0e8e1; }
.shop-data-result.selected { background: #eef5ef; }
.image-video-permission-tabs { background: #edf2ee; border-color: #d5e0d7; }
.image-video-permission-tab { color: var(--c-text-2); }
.image-video-permission-tab:hover, .image-video-permission-tab:focus-visible { color: var(--c-text); background: #e3eee5; }
.image-video-permission-tab.active { background: #ffffff; color: var(--c-primary-strong); }
.image-video-permission-list { border-color: #d5e0d7; background: #ffffff; }
.image-video-permission-row { border-bottom-color: #e2e9e3; }
.image-video-permission-state { background: #f0f4f1; color: var(--c-text-2); }
.image-video-permission-state.granted { border-color: #b8cdbd; background: #e7f0e9; color: var(--c-primary-strong); }
.image-video-permission-state.pending { border-color: #e1c98d; background: #f8f0df; color: #8b652f; }
select option { background: #ffffff; color: var(--c-text); }
::-webkit-scrollbar-thumb { background: #bdcbc0; background-clip: content-box; }
::-webkit-scrollbar-thumb:hover { background: #9daf9f; background-clip: content-box; }
:focus-visible { outline-color: #607a6d; }
@media (max-width: 960px) {
.admin-content { padding: 24px 20px 44px; }
}
@media (max-width: 760px) {
.admin-content { padding: 18px 14px 32px; }
.table-scroll { border-radius: 9px; }
.shop-manage-table-scroll > table, .category-table-scroll > table { min-width: 820px; }
}
/* ===== 蓝白色调 Admin UI / 全页面表格布局升级 ===== */
:root {
color-scheme: light;
--c-primary: #4f78a5;
--c-primary-strong: #2f5d8b;
--c-primary-soft: #e7f0f8;
--c-bg: #f4f7fb;
--c-card: #ffffff;
--c-card-raised: #f9fbfd;
--c-border: #d8e3ee;
--c-border-strong: #b7c9db;
--c-text: #24384d;
--c-text-2: #5b6f83;
--c-text-3: #8293a5;
--c-danger: #b35f6a;
--c-danger-soft: #faecee;
--c-success: #4e806d;
--c-warning: #a8793e;
--c-sidebar: #edf3f9;
--c-sidebar-2: #f8fbfe;
--c-sidebar-text: #536a81;
--radius: 14px;
--radius-sm: 9px;
--shadow-card: 0 1px 2px rgba(39, 67, 94, 0.04), 0 14px 30px -24px rgba(39, 67, 94, 0.28);
--shadow-pop: 0 22px 60px rgba(39, 67, 94, 0.2);
}
html { background: var(--c-bg); }
body {
color: var(--c-text);
background:
radial-gradient(circle at 12% -16%, rgba(178, 205, 229, 0.3), transparent 34rem),
radial-gradient(circle at 100% 0%, rgba(214, 226, 239, 0.28), transparent 28rem),
var(--c-bg);
}
.admin-sidebar {
background: linear-gradient(180deg, var(--c-sidebar-2), var(--c-sidebar));
color: var(--c-sidebar-text);
border-right-color: #d4e0eb;
box-shadow: 14px 0 34px -28px rgba(39, 67, 94, 0.34);
}
.admin-brand { border-bottom-color: #d4e0eb; }
.admin-brand-mark {
background: #fff;
box-shadow: 0 8px 20px rgba(79, 120, 165, 0.24);
}
.admin-brand-name { color: var(--c-text); }
.admin-brand-sub { color: #77899b; }
.admin-menu { scrollbar-color: #b9cbe0 transparent; }
.menu-group-title { color: #72859a; }
.menu-group-title:hover { color: var(--c-primary-strong); background: rgba(79, 120, 165, 0.09); }
.tab { color: var(--c-sidebar-text); }
.tab:hover { color: var(--c-primary-strong); background: rgba(79, 120, 165, 0.09); border-color: rgba(79, 120, 165, 0.16); }
.tab.active { color: var(--c-primary-strong); background: linear-gradient(90deg, #dce9f5, #edf4fa); border-color: #b6cbe0; box-shadow: inset 3px 0 0 #5f85ad, 0 8px 18px -16px rgba(79, 120, 165, 0.55); }
.tab.active .adm-icon, .tab:hover .adm-icon { color: #5f85ad; }
.admin-sidebar-foot { color: #7b8d9e; border-top-color: #d4e0eb; }
.admin-topbar { background: rgba(255, 255, 255, 0.92); border-bottom-color: #d8e3ee; box-shadow: 0 10px 28px -28px rgba(39, 67, 94, 0.42); }
.admin-topbar-kicker { color: #70859a; }
.admin-page-section { color: #718397; }
.admin-page-separator { color: #b2c1cf; }
.admin-page-title, .admin-user-name { color: var(--c-text); }
.admin-user-role { color: var(--c-primary-strong); background: #e6f0f8; border-color: #bfd1e2; }
.admin-user-logout { color: var(--c-text-2); background: #ffffff; border-color: #cbd9e6; }
.admin-user-logout:hover { color: var(--c-primary-strong); border-color: #94b0cb; background: #edf5fb; }
.admin-content { width: 100%; max-width: none; }
.tab-panel { width: 100%; min-width: 0; }
.form-box, .panel-box { width: 100%; min-width: 0; background: linear-gradient(145deg, #ffffff, #f9fbfd); border-color: var(--c-border); box-shadow: var(--shadow-card); }
.form-box:hover, .panel-box:hover { border-color: #bdd0e1; }
.form-box > h3:first-child, .panel-box > h3:first-child, .modal h3 { color: var(--c-text); }
.form-group label { color: var(--c-text-2); }
.form-group input, .form-group select, .form-group textarea, .form-group input[type="search"], .multi-select-trigger, .image-video-permission-search { background: #f8fbfd; border-color: #cbd9e6; color: var(--c-text); color-scheme: light; }
.form-group input:hover, .form-group select:hover, .form-group textarea:hover, .multi-select-trigger:hover { border-color: #9fb7cd; }
.form-group input:focus, .form-group select:focus, .form-group textarea:focus, .image-video-permission-search:focus { background: #ffffff; border-color: #5f85ad; box-shadow: 0 0 0 3px rgba(95, 133, 173, 0.16); }
.form-group input::placeholder, .form-group textarea::placeholder, .image-video-permission-search::placeholder { color: #8293a5; }
.form-group input[type="file"] { background: #ffffff; }
.form-group input[type="file"]::file-selector-button { border-color: #b4c9dd; background: #edf5fb; color: var(--c-primary-strong); }
.form-group input[readonly] { background: #eef3f7 !important; color: var(--c-text-2); }
.btn { background: linear-gradient(135deg, #5f85ad, #4f78a5); color: #ffffff; box-shadow: 0 8px 18px -14px rgba(79, 120, 165, 0.72); }
.btn:hover { background: linear-gradient(135deg, #7094ba, #5d83ac); color: #ffffff; box-shadow: 0 11px 22px -14px rgba(79, 120, 165, 0.8); }
.btn-secondary { background: #ffffff; color: var(--c-text-2); border-color: #c7d7e5; }
.btn-secondary:hover { color: var(--c-primary-strong); border-color: #95b1cb; background: #edf5fb; }
.btn-danger { background: linear-gradient(135deg, #c06d77, #b35f6a); color: #ffffff; }
.btn-danger:hover { background: linear-gradient(135deg, #cb7c84, #b96570); color: #ffffff; }
.msg { color: var(--c-text-2); background: #f1f6fa; border-color: #d6e2ec; }
.msg.ok { color: #3d7158; background: #e8f2eb; border-color: #bdd7c5; }
.msg.err { color: #91474f; background: #f8ebeb; border-color: #e4c2c5; }
.progress-bar { background: #e4ebf2; }
.progress-fill { background: linear-gradient(90deg, #6f95bb, #a3b7ca); }
.request-loading { color: var(--c-text); background: rgba(255, 255, 255, 0.97); border-color: #c7d7e5; box-shadow: var(--shadow-pop); }
.request-spinner { border-color: #d5e3ef; border-top-color: #5f85ad; }
.dedupe-export-wait-mask, .modal-mask { background: rgba(45, 66, 86, 0.36); backdrop-filter: blur(6px); }
.dedupe-export-wait, .modal { background: #ffffff; border-color: #c7d7e5; box-shadow: var(--shadow-pop); }
.dedupe-export-wait-title { color: var(--c-text); }
.dedupe-export-wait-detail { color: var(--c-text-2); }
.table-scroll, .shop-key-table-scroll { width: 100%; min-width: 0; overflow-x: auto; overflow-y: hidden; border: 1px solid #dbe5ee; border-radius: 10px; background: #ffffff; }
.panel-box > table, .panel-box > .table-scroll > table, .shop-key-table-scroll > table { width: 100% !important; table-layout: fixed; }
.table-scroll > table { min-width: 0; }
th { background: #edf4fa; color: #4e6479; border-bottom-color: #d5e1eb; }
td { color: var(--c-text); border-bottom-color: #e0e8ef; }
tbody tr:hover { background: #f1f7fb; }
.empty-tip { color: var(--c-text-3); }
.pagination { color: var(--c-text-2); }
.pagination button { background: #ffffff; border-color: #c7d7e5; color: var(--c-text-2); }
.pagination button:hover:not(:disabled) { background: #edf5fb; border-color: #95b1cb; color: var(--c-primary-strong); }
.pagination button:disabled { background: #eef3f7; color: #9baaba; }
.shop-key-table-scroll table { min-width: 980px; }
/* 分组管理面板 */
#panel-group-manage .table-scroll > table { min-width: 980px; }
#panel-group-manage .table-scroll th:nth-child(1) { width: 58px; }
#panel-group-manage .table-scroll th:nth-child(2) { width: 16%; }
#panel-group-manage .table-scroll th:nth-child(3) { width: 12%; }
#panel-group-manage .table-scroll th:nth-child(4) { width: 10%; }
#panel-group-manage .table-scroll th:nth-child(6) { width: 16%; }
#panel-group-manage .table-scroll th:nth-child(7) { width: 16%; }
#panel-group-manage .table-scroll th:nth-child(8) { width: 124px; }
#panel-group-manage .table-scroll th, #panel-group-manage .table-scroll td { vertical-align: middle; }
#panel-group-manage .table-scroll td { white-space: normal; word-break: break-word; }
.shop-group-time-cell, .shop-group-action-cell { white-space: nowrap; }
.shop-group-members { display: flex; flex-wrap: wrap; gap: 6px; max-height: 96px; overflow-y: auto; padding-right: 4px; }
.shop-group-member-chip { display: inline-flex; align-items: center; max-width: 150px; padding: 3px 9px; border-radius: 999px; background: var(--c-primary-soft); color: var(--c-primary-strong); font-size: 12px; line-height: 1.4; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
#panel-invalid-asin-data .invalid-asin-table-scroll > table { min-width: 760px; }
#panel-columns .columns-table-scroll > table { min-width: 820px; }
#panel-version .version-table-scroll > table { min-width: 760px; }
#panel-digital-human-version .digital-human-version-table-scroll > table { min-width: 1120px; }
#panel-history .history-table-scroll > table { min-width: 820px; }
.query-asin-table-scroll > table, .skip-price-asin-table-scroll > table { min-width: 820px; table-layout: fixed; }
#panel-query-asin .query-asin-table-scroll th:nth-child(1), #panel-skip-price-asin .skip-price-asin-table-scroll th:nth-child(1) { width: 6%; }
#panel-query-asin .query-asin-table-scroll th:nth-child(2), #panel-skip-price-asin .skip-price-asin-table-scroll th:nth-child(2) { width: 13%; }
#panel-query-asin .query-asin-table-scroll th:nth-child(3), #panel-skip-price-asin .skip-price-asin-table-scroll th:nth-child(3) { width: 15%; }
#panel-query-asin .query-asin-table-scroll th:nth-child(4), #panel-skip-price-asin .skip-price-asin-table-scroll th:nth-child(4) { width: 24%; }
#panel-query-asin .query-asin-table-scroll th:nth-child(5), #panel-skip-price-asin .skip-price-asin-table-scroll th:nth-child(5) { width: 12%; }
#panel-skip-price-asin .skip-price-asin-table-scroll th:nth-child(6) { width: 10%; }
#panel-query-asin .query-asin-table-scroll th:last-child, #panel-skip-price-asin .skip-price-asin-table-scroll th:last-child { width: 9%; }
#panel-query-asin .query-asin-table-scroll td, #panel-skip-price-asin .skip-price-asin-table-scroll td { vertical-align: middle; }
.table-ellipsis { display: block; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.asin-cell-content { min-width: 0; overflow: hidden; }
.asin-cell-meta { display: block; margin-top: 3px; color: #73869a; font-size: 12px; white-space: nowrap; }
.asin-empty-value { color: #8b9aaa; }
.asin-copy-text { display: block; max-width: 100%; margin: -3px 0 -3px -7px; padding: 3px 7px; border: 1px solid transparent; border-radius: var(--radius-sm); background: none; color: inherit; font: inherit; font-size: 13.5px; font-variant-numeric: tabular-nums; line-height: 1.5; text-align: left; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; cursor: pointer; }
.asin-copy-text:hover { border-color: #c7d7e5; background: #eef4fa; color: var(--c-primary-strong); }
.asin-copy-text:focus-visible { outline: 2px solid #4f78a5; outline-offset: 1px; }
.asin-copy-text.is-copied { border-color: #b9e3ca; background: #eaf8f0; color: var(--c-success); }
.asin-col-actions { position: sticky; right: 0; z-index: 2; text-align: right; background: #ffffff; box-shadow: -9px 0 12px -10px rgba(31, 48, 66, 0.45); }
th.asin-col-actions { z-index: 3; background: #f7f8fb; }
tbody tr:hover td.asin-col-actions { background: #f6f8fd; }
.visually-hidden { position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; border: 0; overflow: hidden; white-space: nowrap; clip: rect(0 0 0 0); clip-path: inset(50%); }
/* 菜单列表拖动排序 */
.columns-drag-tip { margin: -8px 0 14px; font-size: 13px; color: var(--c-text-2); }
.column-drag-cell { width: 48px; }
.column-drag-handle { display: inline-flex; align-items: center; justify-content: center; width: 28px; height: 28px; border: 1px solid transparent; border-radius: var(--radius-sm); background: none; color: #93a6b8; font-size: 15px; line-height: 1; cursor: grab; user-select: none; }
.column-drag-handle:hover { border-color: #cbd9e6; background: #eef4fa; color: var(--c-primary-strong); }
.column-drag-handle:focus-visible { outline: 2px solid #4f78a5; outline-offset: 1px; }
.column-drag-handle:active { cursor: grabbing; }
tr.is-column-dragging td { opacity: 0.45; }
tr.is-column-drop-before td { box-shadow: inset 0 2px 0 var(--c-primary); }
tr.is-column-drop-after td { box-shadow: inset 0 -2px 0 var(--c-primary); }
.column-child-mark { margin-right: 6px; color: #9fb1c2; }
/* 右侧抽屉:站点数量增长时仍能在一处维护完整配置 */
.drawer-mask { display: none; position: fixed; inset: 0; z-index: 1000; background: rgba(45, 66, 86, 0.36); backdrop-filter: blur(4px); }
.drawer-mask.show { display: block; animation: modal-fade-in 0.16s ease; }
.drawer { position: absolute; top: 0; right: 0; bottom: 0; display: flex; flex-direction: column; width: min(460px, 100%); background: #ffffff; border-left: 1px solid #c7d7e5; box-shadow: var(--shadow-pop); animation: drawer-slide-in 0.18s ease; }
@keyframes drawer-slide-in { from { transform: translateX(26px); opacity: 0.4; } to { transform: none; opacity: 1; } }
.drawer-header { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; padding: 20px 22px 14px; border-bottom: 1px solid #e6edf4; }
.drawer-header h3 { margin: 0; font-size: 15px; font-weight: 600; color: var(--c-text); }
.drawer-subtitle { margin: 6px 0 0; font-size: 13px; color: var(--c-text-2); }
.drawer-hint { margin: 0 0 14px; font-size: 12.5px; color: var(--c-text-3); }
.drawer-close { flex: 0 0 auto; width: 30px; height: 30px; border: 1px solid transparent; border-radius: var(--radius-sm); background: none; color: var(--c-text-2); font-size: 20px; line-height: 1; cursor: pointer; }
.drawer-close:hover { border-color: #cbd9e6; background: #eef4fa; color: var(--c-text); }
.drawer-body { flex: 1; min-height: 0; overflow-y: auto; padding: 18px 22px; }
.drawer-body .form-group:last-of-type { margin-bottom: 0; }
.drawer-footer { display: flex; gap: 8px; padding: 14px 22px 18px; border-top: 1px solid #e6edf4; }
.drawer-price-grid { display: grid; grid-template-columns: minmax(0, 1fr) 128px; gap: 8px; }
.drawer-price-grid input { min-width: 0; }
@media (max-width: 560px) {
.drawer { width: 100%; }
.drawer-price-grid { grid-template-columns: minmax(0, 1fr); }
}
/* 店铺列表筛选行:显式固定轨道(与左侧去重筛选 .dedupe-filter-row 同模式)。
禁止 auto-fit 与 auto 混用——那会导致整条 grid-template-columns 声明被判无效,
display:grid 退化为单列隐式轨道,字段/按钮全部竖排占满整行(即"筛选表单太大"的根因) */
#panel-query-asin .panel-box > .form-row { display: grid; grid-template-columns: minmax(140px, 1fr) minmax(160px, 1.1fr) minmax(110px, 0.7fr) minmax(160px, 1.1fr) auto auto; gap: 12px; align-items: end; }
#panel-skip-price-asin .panel-box > .form-row { display: grid; grid-template-columns: minmax(120px, 0.9fr) minmax(130px, 1fr) minmax(90px, 0.6fr) minmax(130px, 1fr) minmax(150px, 1.1fr) auto auto; gap: 12px; align-items: end; }
#panel-query-asin .panel-box > .form-row .form-group, #panel-skip-price-asin .panel-box > .form-row .form-group { min-width: 0; }
#panel-query-asin .panel-box > .form-row .btn, #panel-skip-price-asin .panel-box > .form-row .btn { justify-self: start; }
@media (max-width: 1400px) {
/* 轨道最小值之和超出内容宽度时折两列(同 .dedupe-filter-row 的窄屏策略),按钮跨整行 */
#panel-query-asin .panel-box > .form-row, #panel-skip-price-asin .panel-box > .form-row { grid-template-columns: repeat(2, minmax(150px, 1fr)); }
#panel-query-asin .panel-box > .form-row .btn, #panel-skip-price-asin .panel-box > .form-row .btn { grid-column: 1 / -1; justify-self: start; }
}
.category-table-scroll .category-path-value { display: block; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.category-table-scroll .category-path { display: none; }
.category-tag { background: #e7f0f8; border-color: #c2d5e6; color: var(--c-primary-strong); }
.tree-node-mark { background: #e7f0f8; color: var(--c-primary-strong); }
.tree-node-mark:not(.is-leaf):hover { background: #5f85ad; color: #ffffff; }
.column-permission-tree, .multi-select-panel, .column-permission-select { background: #ffffff; border-color: #cbd9e6; color: var(--c-text); color-scheme: light; }
.column-tree-group-toggle { background: #edf5fb; color: var(--c-text); }
.column-tree-group-toggle:hover, .column-tree-toggle:hover { background: #e1edf6; color: var(--c-primary-strong); }
.column-tree-node label { color: var(--c-text-2); }
.multi-select-option { color: var(--c-text-2); }
.multi-select-option:hover { background: #edf5fb; color: var(--c-text); }
.column-permission-card { background: #e7f0f8; color: var(--c-primary-strong); border-color: #bfd3e5; }
.image-video-card { background: #ffffff; border-color: var(--c-border); }
.image-video-card:hover { border-color: #a7bfd4; box-shadow: 0 14px 28px -18px rgba(39, 67, 94, 0.32); }
.image-video-status { background: #edf4fa; color: var(--c-text-2); }
.image-video-status.SUCCESS { color: #3e7258; background: #e8f2eb; }
.image-video-status.FAILED { color: #934a52; background: #f8ebeb; }
.image-video-status.RUNNING, .image-video-status.POLLING { color: #8c642b; background: #f7efdf; }
.image-video-info-row a, .image-video-copy-link { color: var(--c-primary-strong); }
.image-video-card-action { border-color: #a7bfd4; background: #ffffff; color: var(--c-primary-strong); }
.image-video-card-action:hover { background: #5f85ad; color: #ffffff; }
.image-video-empty { border-color: #b8cfe2; background: #edf5fb; color: var(--c-text-2); }
.shop-data-result-list, .shop-data-result { border-color: #dfe8ef; }
.shop-data-result.selected { background: #edf5fb; }
.image-video-permission-tabs { background: #edf4fa; border-color: #d5e1eb; }
.image-video-permission-tab { color: var(--c-text-2); }
.image-video-permission-tab:hover, .image-video-permission-tab:focus-visible { color: var(--c-text); background: #e3eff7; }
.image-video-permission-tab.active { background: #ffffff; color: var(--c-primary-strong); }
.image-video-permission-list { border-color: #d5e1eb; background: #ffffff; }
.image-video-permission-row { border-bottom-color: #e0e8ef; }
.image-video-permission-state { background: #f0f4f8; color: var(--c-text-2); }
.image-video-permission-state.granted { border-color: #b8cfe2; background: #e7f0f8; color: var(--c-primary-strong); }
.image-video-permission-state.pending { border-color: #e1c98d; background: #f8f0df; color: #8b652f; }
select option { background: #ffffff; color: var(--c-text); }
::-webkit-scrollbar-thumb { background: #b9cbe0; background-clip: content-box; }
::-webkit-scrollbar-thumb:hover { background: #95aec7; background-clip: content-box; }
:focus-visible { outline-color: #4f78a5; }
@media (max-width: 960px) {
.admin-content { padding: 24px 20px 44px; }
}
@media (max-width: 760px) {
.admin-content { padding: 18px 14px 32px; }
.table-scroll { border-radius: 9px; }
.shop-manage-table-scroll > table, .category-table-scroll > table, .query-asin-table-scroll > table, .skip-price-asin-table-scroll > table { min-width: 920px; }
#panel-query-asin .panel-box > .form-row, #panel-skip-price-asin .panel-box > .form-row { grid-template-columns: minmax(0, 1fr); align-items: stretch; }
}
/* 列表布局第二轮:桌面满宽,窄屏在容器内横向滚动,避免列被压成竖排 */
#panel-users .user-table-scroll > table { min-width: 860px; }
#panel-dedupe-total-data .dedupe-table-scroll > table { min-width: 900px; }
#panel-invalid-asin-data .invalid-asin-table-scroll > table { min-width: 760px; }
#panel-columns .columns-table-scroll > table { min-width: 820px; }
#panel-version .version-table-scroll > table { min-width: 760px; }
#panel-digital-human-version .digital-human-version-table-scroll > table { min-width: 1120px; }
#panel-history .history-table-scroll > table { min-width: 820px; }
#panel-shop-keys .shop-key-table-scroll > table { min-width: 980px; }
@media (max-width: 760px) {
#panel-users .user-table-scroll > table { min-width: 720px; }
#panel-dedupe-total-data .dedupe-table-scroll > table { min-width: 860px; }
#panel-invalid-asin-data .invalid-asin-table-scroll > table { min-width: 720px; }
#panel-columns .columns-table-scroll > table { min-width: 760px; }
#panel-version .version-table-scroll > table { min-width: 700px; }
#panel-digital-human-version .digital-human-version-table-scroll > table { min-width: 980px; }
#panel-history .history-table-scroll > table { min-width: 760px; }
#panel-shop-keys .shop-key-table-scroll > table { min-width: 900px; }
}
/* ===== 全局交互引导、反馈与确认 ===== */
.admin-operation-guide {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 18px;
margin: 0 0 18px;
padding: 15px 18px;
border: 1px solid #cbdced;
border-radius: 12px;
background: linear-gradient(110deg, #edf6ff, #f8fbff 68%);
color: #294968;
box-shadow: 0 9px 18px -18px rgba(47, 93, 139, 0.55);
}
.admin-operation-guide__main { display: flex; gap: 12px; min-width: 0; }
.admin-operation-guide__icon { display: inline-flex; align-items: center; justify-content: center; flex: 0 0 32px; width: 32px; height: 32px; border-radius: 10px; background: #dcecfb; color: #3f73a6; }
.admin-operation-guide__icon svg { width: 18px; height: 18px; }
.admin-operation-guide__title { font-size: 13px; font-weight: 700; color: #2d5f90; }
.admin-operation-guide__text { margin: 3px 0 0; color: #5a7189; font-size: 13px; line-height: 1.55; }
.admin-operation-guide__steps { display: flex; flex-wrap: wrap; gap: 8px 18px; margin: 9px 0 0; padding: 0; list-style: none; counter-reset: admin-guide-step; }
.admin-operation-guide__steps li { display: inline-flex; align-items: center; gap: 5px; color: #486780; font-size: 12px; }
.admin-operation-guide__steps li::before { counter-increment: admin-guide-step; content: counter(admin-guide-step); display: inline-flex; align-items: center; justify-content: center; width: 17px; height: 17px; border-radius: 50%; background: #cfe1f2; color: #315f8e; font-size: 10px; font-weight: 700; }
.admin-operation-guide__toggle { flex: 0 0 auto; min-height: 34px; padding: 6px 10px; border: 1px solid #bcd2e6; border-radius: 8px; background: #ffffff; color: #3b668f; cursor: pointer; font: inherit; font-size: 12px; font-weight: 600; }
.admin-operation-guide__toggle:hover { border-color: #8fb4d6; background: #f4f9fe; }
.admin-operation-guide.is-collapsed { padding: 10px 14px; align-items: center; }
.admin-operation-guide.is-collapsed .admin-operation-guide__text, .admin-operation-guide.is-collapsed .admin-operation-guide__steps { display: none; }
.admin-operation-guide.is-collapsed .admin-operation-guide__main { align-items: center; }
.admin-toast-region { position: fixed; top: 92px; right: 24px; z-index: 4100; display: grid; gap: 10px; width: min(390px, calc(100vw - 32px)); pointer-events: none; }
.admin-toast { display: flex; align-items: flex-start; gap: 10px; padding: 12px 14px; border: 1px solid #bfd3e6; border-radius: 11px; background: rgba(255, 255, 255, 0.98); color: #2f4f6d; box-shadow: 0 18px 36px -24px rgba(38, 69, 101, 0.45); animation: admin-toast-in 180ms ease both; }
.admin-toast::before { content: "✓"; display: inline-flex; align-items: center; justify-content: center; flex: 0 0 20px; width: 20px; height: 20px; border-radius: 50%; background: #e7f2ea; color: #3f7657; font-size: 12px; font-weight: 800; }
.admin-toast.is-error { border-color: #e5c2c8; color: #834650; }
.admin-toast.is-error::before { content: "!"; background: #faebed; color: #a9545d; }
.admin-toast.is-info::before { content: "i"; background: #e7f0f8; color: #3d6e9d; font-family: Georgia, serif; }
.admin-toast__text { min-width: 0; line-height: 1.5; font-size: 13px; }
@keyframes admin-toast-in { from { opacity: 0; transform: translateY(-8px); } to { opacity: 1; transform: translateY(0); } }
body.admin-confirm-open { overflow: hidden; }
.admin-confirm-mask { position: fixed; inset: 0; z-index: 4050; display: none; align-items: center; justify-content: center; padding: 20px; background: rgba(39, 65, 91, 0.32); backdrop-filter: blur(4px); }
.admin-confirm-mask.show { display: flex; }
.admin-confirm-dialog { display: grid; grid-template-columns: 42px minmax(0, 1fr); gap: 13px 14px; width: min(460px, calc(100vw - 36px)); padding: 22px; border: 1px solid #c5d8e9; border-radius: 16px; background: #ffffff; box-shadow: 0 28px 72px rgba(35, 62, 91, 0.24); }
.admin-confirm-dialog__icon { display: inline-flex; align-items: center; justify-content: center; width: 42px; height: 42px; border-radius: 12px; background: #fff2e7; color: #a66b36; }
.admin-confirm-dialog__icon svg { width: 22px; height: 22px; }
.admin-confirm-dialog__content h2 { margin: 1px 0 6px; color: #29435e; font-size: 17px; }
.admin-confirm-dialog__content p { margin: 0; color: #60758b; line-height: 1.6; font-size: 13px; white-space: pre-wrap; }
.admin-confirm-dialog__actions { grid-column: 1 / -1; display: flex; justify-content: flex-end; gap: 8px; margin-top: 4px; }
.btn.is-busy { position: relative; pointer-events: none; opacity: 0.72; }
.btn.is-busy::before { content: ""; display: inline-block; width: 13px; height: 13px; margin-right: 6px; border: 2px solid rgba(255,255,255,0.45); border-top-color: #ffffff; border-radius: 50%; vertical-align: -2px; animation: admin-action-spin 0.75s linear infinite; }
.btn-secondary.is-busy::before { border-color: rgba(67,103,143,0.25); border-top-color: #3f73a6; }
@keyframes admin-action-spin { to { transform: rotate(360deg); } }
.table-help { display: inline-flex; align-items: center; justify-content: center; width: 16px; height: 16px; margin-left: 4px; border-radius: 50%; background: #d9eaf8; color: #3f73a6; font-size: 10px; font-weight: 700; vertical-align: 1px; }
#panel-shop-keys .shop-key-table-scroll > table { min-width: 1140px; table-layout: fixed; }
#panel-shop-keys .shop-key-table-scroll th:nth-child(1) { width: 6%; }
#panel-shop-keys .shop-key-table-scroll th:nth-child(2) { width: 13%; }
#panel-shop-keys .shop-key-table-scroll th:nth-child(3) { width: 18%; }
#panel-shop-keys .shop-key-table-scroll th:nth-child(4) { width: 20%; }
#panel-shop-keys .shop-key-table-scroll th:nth-child(5) { width: 12%; }
#panel-shop-keys .shop-key-table-scroll th:nth-child(6), #panel-shop-keys .shop-key-table-scroll th:nth-child(7) { width: 12%; }
#panel-shop-keys .shop-key-table-scroll th:nth-child(8) { width: 15%; }
#panel-shop-keys .shop-key-table-scroll td { overflow: hidden; }
#panel-shop-keys .shop-key-whitelist-status { min-width: 82px; justify-content: center; padding: 5px 10px; }
#panel-shop-keys .shop-key-whitelist-status::before { content: ""; width: 6px; height: 6px; margin-right: 5px; border-radius: 50%; background: currentColor; }
#panel-shop-keys .table-ellipsis { max-width: 100%; }
.empty-tip[role="status"] { padding: 38px 18px; color: #70849a; }
@media (max-width: 760px) {
.admin-operation-guide { padding: 12px; gap: 10px; }
.admin-operation-guide__steps { gap: 7px 10px; }
.admin-operation-guide__toggle { min-height: 38px; }
.admin-toast-region { top: 80px; right: 16px; }
.admin-confirm-dialog { padding: 18px; }
}
</style>
</head>
<body>
<a class="skip-link" href="#adminContent">跳转到主要内容</a>
<!-- <div class="nav"><a href="/home">返回首页</a></div> -->
<div class="request-loading-bar" id="requestLoadingBar"></div>
<div class="request-loading" id="requestLoading">
<span class="request-spinner"></span>
<span>请求处理中...</span>
</div>
<div class="dedupe-export-wait-mask" id="dedupeTotalDataExportWait" role="dialog" aria-modal="true"
aria-labelledby="dedupeTotalDataExportWaitTitle" aria-hidden="true">
<div class="dedupe-export-wait">
<span class="request-spinner" aria-hidden="true"></span>
<div>
<div class="dedupe-export-wait-title" id="dedupeTotalDataExportWaitTitle">正在生成导出文件</div>
<div class="dedupe-export-wait-detail">
数据量较大,请耐心等待并保持页面打开。已等待 <span id="dedupeTotalDataExportWaitSeconds">0</span>
</div>
</div>
</div>
</div>
<div class="admin-layout">
<aside class="admin-sidebar">
<div class="admin-brand">
<span class="admin-brand-mark"><img class="admin-brand-logo" src="/static/logo.jpg" alt="数富AI"></span>
<span class="admin-brand-text">
<span class="admin-brand-name">数富AI</span>
<span class="admin-brand-sub">电商运营管理后台</span>
</span>
</div>
<nav class="admin-menu" id="adminMenu" aria-label="管理菜单"{% if admin_menu_rendered %} data-server-rendered="true"{% endif %}>
{% if admin_menu_rendered %}
{# 服务端按当前用户权限直接渲染菜单,避免客户端二次渲染造成"全量菜单→折叠"的闪烁 #}
{% set __adm_chevron %}<span class="menu-group-chevron" aria-hidden="true"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><path d="m6 9 6 6 6-6"></path></svg></span>{% endset %}
{% set __adm_icons = {
'users': '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"></path><circle cx="9" cy="7" r="4"></circle><path d="M22 21v-2a4 4 0 0 0-3-3.87"></path><path d="M16 3.13a4 4 0 0 1 0 7.75"></path></svg>',
'group-manage': '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"></path><circle cx="9" cy="7" r="4"></circle><path d="M22 21v-2a4 4 0 0 0-3-3.87"></path><path d="M16 3.13a4 4 0 0 1 0 7.75"></path></svg>',
'columns': '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect width="7" height="7" x="3" y="3" rx="1"></rect><rect width="7" height="7" x="14" y="3" rx="1"></rect><rect width="7" height="7" x="14" y="14" rx="1"></rect><rect width="7" height="7" x="3" y="14" rx="1"></rect></svg>',
'dedupe-total-data': '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m12.83 2.18a2 2 0 0 0-1.66 0L2.6 6.08a1 1 0 0 0 0 1.83l8.58 3.91a2 2 0 0 0 1.66 0l8.58-3.9a1 1 0 0 0 0-1.83Z"></path><path d="m22 17.65-9.17 4.16a2 2 0 0 1-1.66 0L2 17.65"></path><path d="m22 12.65-9.17 4.16a2 2 0 0 1-1.66 0L2 12.65"></path></svg>',
'invalid-asin-data': '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3Z"></path><path d="M12 9v4"></path><path d="M12 17h.01"></path></svg>',
'shop-keys': '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m15.5 7.5 2.3 2.3a1 1 0 0 0 1.4 0l2.1-2.1a1 1 0 0 0 0-1.4L19 4"></path><path d="m21 2-9.6 9.6"></path><circle cx="7.5" cy="15.5" r="5.5"></circle></svg>',
'shop-manage': '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m2 7 4.41-4.41A2 2 0 0 1 7.83 2h8.34a2 2 0 0 1 1.42.59L22 7"></path><path d="M4 12v8a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-8"></path><path d="M15 22v-4a2 2 0 0 0-2-2h-2a2 2 0 0 0-2 2v4"></path><path d="M2 7h20"></path><path d="M22 7v3a2 2 0 0 1-2 2 2.7 2.7 0 0 1-1.59-.63.7.7 0 0 0-.82 0A2.7 2.7 0 0 1 16 12a2.7 2.7 0 0 1-1.59-.63.7.7 0 0 0-.82 0A2.7 2.7 0 0 1 12 12a2.7 2.7 0 0 1-1.59-.63.7.7 0 0 0-.82 0A2.7 2.7 0 0 1 8 12a2.7 2.7 0 0 1-1.59-.63.7.7 0 0 0-.82 0A2.7 2.7 0 0 1 4 12a2 2 0 0 1-2-2V7"></path></svg>',
'skip-price-asin': '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="10"></circle><path d="m4.9 4.9 14.2 14.2"></path></svg>',
'query-asin': '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="11" cy="11" r="8"></circle><path d="m21 21-4.3-4.3"></path></svg>',
'product-categories': '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z"></path></svg>',
'image-video-tasks': '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m22 8-6 4 6 4V8Z"></path><rect width="14" height="12" x="2" y="6" rx="2" ry="2"></rect></svg>',
'shop-data-crawl-tasks': '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><ellipse cx="12" cy="5" rx="9" ry="3"></ellipse><path d="M3 5v14a9 3 0 0 0 18 0V5"></path><path d="M3 12a9 3 0 0 0 18 0"></path></svg>',
'shop-data-duplicate-check': '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M21.35 11.1h-9.17a2 2 0 0 1-1.75-2.98l1.67-2.79a2 2 0 0 0-1.75-2.98l-5.98-.01a2 2 0 0 0-2 2v1.5a2 2 0 0 0 2 2h3.36l-2.38 3.97a2 2 0 0 0 1.75 2.98h11.5a2 2 0 0 0 2-2v-1.5a2 2 0 0 0-2-2Z"></path><path d="M3 21h18"></path></svg>',
'history': '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8"></path><path d="M3 3v5h5"></path><path d="M12 7v5l4 2"></path></svg>',
'version': '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m7.5 4.27 9 5.15"></path><path d="M21 8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16Z"></path><path d="M3.3 7 12 12l8.7-5"></path><path d="M12 22V12"></path></svg>',
'digital-human-version': '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 8V4H8"></path><rect width="16" height="12" x="4" y="8" rx="2"></rect><path d="M2 14h2"></path><path d="M20 14h2"></path><path d="M15 13v2"></path><path d="M9 13v2"></path></svg>',
} %}
{% set __adm_fallback_icon %}<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="10"></circle><path d="M12 8v8"></path><path d="M8 12h8"></path></svg>{% endset %}
{% set __adm_groups = [
('account', '账号与权限', ['users', 'columns', 'group-manage']),
('data', '数据管理', ['dedupe-total-data', 'invalid-asin-data', 'query-asin', 'product-categories']),
('shop', '店铺管理', ['shop-keys', 'shop-manage', 'skip-price-asin', 'shop-data-crawl-tasks', 'shop-data-duplicate-check']),
('record', '记录与版本', ['history', 'version', 'digital-human-version', 'image-video-tasks']),
] %}
{% set __adm_allowed = admin_menu_items | map(attribute='route_path') | list %}
{% set __adm_has = namespace(v=False) %}
{% for __gk, __gt, __routes in __adm_groups %}
{% set __visible_routes = __routes | select('in', __adm_allowed) | list %}
{% if __visible_routes %}
{% set __adm_has.v = True %}
<div class="menu-group" data-menu-group="{{ __gk }}">
<button class="menu-group-title" type="button" aria-expanded="true">{{ __gt }}{{ __adm_chevron | safe }}</button>
<div class="menu-group-body">
{% for __r in __visible_routes %}
{% set __it = admin_menu_items | selectattr('route_path', 'equalto', __r) | first %}
{% set __label = ((__it.get('name') if __it and __it.get('name') else __r) | e) %}
<button class="tab" type="button" data-tab="{{ __r }}" aria-controls="panel-{{ __r }}" title="{{ __label }}"><span class="adm-icon">{{ __adm_icons.get(__r, __adm_fallback_icon) | safe }}</span><span class="adm-label">{{ __label }}</span></button>
{% endfor %}
</div>
</div>
{% endif %}
{% endfor %}
{% if not __adm_has.v %}
<div class="menu-empty">暂无可用菜单</div>
{% endif %}
{% else %}
{# 服务端不可用时的轻量占位:JS 加载后走接口渲染,避免闪出全量菜单 #}
<div class="menu-empty">菜单加载中...</div>
{% endif %}
</nav>
<div class="admin-sidebar-foot">数富AI · 管理控制台</div>
</aside>
<div class="admin-main">
<header class="admin-topbar">
<div class="admin-topbar-left">
<span class="admin-topbar-kicker">运营管理控制台</span>
<div class="admin-page-heading">
<span class="admin-page-section">管理后台</span>
<span class="admin-page-separator" aria-hidden="true">/</span>
<h1 class="admin-page-title" id="adminPageTitle">用户管理</h1>
</div>
</div>
<div class="admin-user-box">
<span class="admin-user-name" id="adminCurrentUsername">加载中...</span>
<span class="admin-user-role" id="adminCurrentUserRole" style="display:none;"></span>
<button class="admin-user-logout" id="btnAdminLogout" type="button">退出登录</button>
</div>
</header>
<main class="admin-content" id="adminContent">
<section class="admin-operation-guide" id="adminOperationGuide" aria-labelledby="adminOperationGuideTitle">
<div class="admin-operation-guide__main">
<span class="admin-operation-guide__icon" aria-hidden="true">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="9"></circle><path d="M12 10v6"></path><path d="M12 7h.01"></path></svg>
</span>
<div>
<div class="admin-operation-guide__title" id="adminOperationGuideTitle">当前页面操作提示</div>
<p class="admin-operation-guide__text" id="adminOperationGuideText">先查看数据,再使用上方筛选快速定位所需记录。</p>
<ol class="admin-operation-guide__steps" id="adminOperationGuideSteps"></ol>
</div>
</div>
<button class="admin-operation-guide__toggle" id="adminOperationGuideToggle" type="button" aria-expanded="true">收起提示</button>
</section>
<!-- 用户管理 -->
<div id="panel-users" class="tab-panel">
<div class="panel-box">
<div style="display:flex;align-items:center;justify-content:space-between;gap:12px;margin-bottom:16px;">
<h3 style="margin-bottom:0;">用户列表</h3>
<button class="btn" id="btnOpenCreateUser" type="button">新建用户</button>
</div>
<p style="margin-bottom:16px;font-size:13px;color:var(--c-text-2);">
当前没有开放注册入口,仅管理员可在此创建用户。层级关系:超级管理员 -&gt; 管理员 -&gt; 普通账号。</p>
<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>
<div class="table-scroll user-table-scroll">
<table>
<thead>
<tr>
<th>ID</th>
<th>用户名</th>
<th>角色</th>
<th>所属管理员</th>
<th>创建时间</th>
<th>操作</th>
</tr>
</thead>
<tbody id="userListBody"></tbody>
</table>
</div>
<div class="pagination" id="userPagination"></div>
</div>
</div>
<!-- 栏目权限配置 -->
<div id="panel-columns" class="tab-panel">
<div class="panel-box">
<div style="display:flex;align-items:center;justify-content:space-between;gap:12px;margin-bottom:16px;">
<h3 style="margin-bottom:0;">菜单列表</h3>
<button class="btn" id="btnOpenCreateColumn" type="button">新增菜单</button>
</div>
<p class="columns-drag-tip">拖动每行左侧的手柄可调整同级菜单的显示顺序,松开后自动保存。</p>
<div class="table-scroll columns-table-scroll">
<table>
<thead>
<tr>
<th class="column-drag-cell"><span class="visually-hidden">排序</span></th>
<th>ID</th>
<th>菜单名称</th>
<th>菜单类型</th>
<th>上级菜单</th>
<th>创建时间</th>
<th>操作</th>
</tr>
</thead>
<tbody id="columnListBody"></tbody>
</table>
</div>
</div>
</div>
<!-- 软件版本管理 -->
<div id="panel-version" class="tab-panel">
<div class="panel-box">
<div style="display:flex;align-items:center;justify-content:space-between;gap:12px;margin-bottom:16px;">
<h3 style="margin-bottom:0;">版本列表</h3>
<button class="btn" id="btnOpenCreateVersion" type="button">上传新版本</button>
</div>
<div class="table-scroll version-table-scroll">
<table>
<thead>
<tr>
<th style="width:120px;">版本号</th>
<th>下载链接</th>
<th style="width:160px;">创建时间</th>
<th style="width:120px;">操作</th>
</tr>
</thead>
<tbody id="versionListBody"></tbody>
</table>
</div>
<div class="pagination" id="versionPagination"></div>
</div>
</div>
<!-- 数字人版本管理 -->
<div id="panel-digital-human-version" class="tab-panel">
<div class="panel-box">
<div style="display:flex;align-items:center;justify-content:space-between;gap:12px;margin-bottom:16px;">
<h3 style="margin-bottom:0;">版本列表</h3>
<button class="btn" id="btnOpenCreateDigitalHumanVersion" type="button">上传新版本</button>
</div>
<div class="table-scroll digital-human-version-table-scroll">
<table>
<thead>
<tr>
<th style="width:80px;">版本号</th>
<th style="width:60px;">状态</th>
<th style="width:60px;">最新</th>
<th style="width:80px;">文件大小</th>
<th style="width:120px;">MD5</th>
<th>更新日志</th>
<th style="width:140px;">发布时间</th>
<th style="width:240px;">操作</th>
</tr>
</thead>
<tbody id="digitalHumanVersionListBody"></tbody>
</table>
</div>
<div class="pagination" id="digitalHumanVersionPagination"></div>
</div>
</div>
<!-- 分组管理(独立菜单:账号与权限 → 分组管理) -->
<div id="panel-group-manage" class="tab-panel">
<div class="form-box">
<div class="dedupe-group-access">
<div class="dedupe-group-access-main">
<span class="dedupe-group-access-title">数据权限分组</span>
<div class="dedupe-group-summary" id="shopManageGroupSummary">正在加载...</div>
</div>
</div>
</div>
<div class="panel-box">
<div style="display:flex;align-items:center;justify-content:space-between;gap:12px;margin-bottom:16px;">
<h3 style="margin-bottom:0;">分组列表</h3>
<button class="btn" id="btnOpenCreateShopManageGroup" type="button">新建分组</button>
</div>
<div class="table-scroll">
<table>
<colgroup>
<col style="width:58px;">
<col style="width:16%;">
<col style="width:12%;">
<col style="width:10%;">
<col>
<col style="width:16%;">
<col style="width:16%;">
<col style="width:124px;">
</colgroup>
<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>
</div>
</div>
<!-- 新建/编辑分组弹窗 -->
<div class="modal-mask" id="shopManageGroupModal">
<div class="modal">
<h3 id="shopManageGroupModalTitle">新建分组</h3>
<div class="shop-group-editor">
<input type="hidden" id="shopManageGroupEditId">
<input type="hidden" id="shopManageGroupLeaderUserId">
<div class="shop-group-basic">
<div class="form-group">
<label>组长</label>
<input type="text" id="shopManageGroupLeaderName" placeholder="当前登录用户" readonly
style="background:#f4f6fa;">
</div>
<div class="form-group">
<label>分组名称</label>
<input type="text" id="shopManageGroupInput" placeholder="请输入分组名称">
</div>
</div>
<div class="shop-group-members-panel">
<div class="shop-group-members-head">
<span class="shop-group-members-title">组员</span>
<span class="shop-group-members-count" id="shopManageGroupMemberCount">已选 0 人</span>
</div>
<div class="shop-group-member-search">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line></svg>
<input type="search" id="shopManageGroupMemberSearch" placeholder="搜索用户名或拼音首字母" autocomplete="off">
</div>
<div class="shop-group-contact" id="shopManageGroupContact">
<div class="shop-group-contact-empty" id="shopManageGroupMemberEmpty">正在加载...</div>
</div>
<div class="shop-group-contact-index" id="shopManageGroupMemberIndex" aria-hidden="true"></div>
<!-- 隐藏 select:仅作为选中数据交换层,保存逻辑(getSelectedShopManageGroupMemberIds)读取它 -->
<select id="shopManageGroupMemberSelect" class="shop-group-member-select" multiple size="6" hidden></select>
</div>
<div class="shop-group-help" id="shopManageGroupMemberHelp">可添加当前组长创建的普通员工账号,选中后点击「添加」。</div>
</div>
<p class="msg" id="msgShopManageGroup"></p>
<div style="margin-top:16px;display:flex;gap:8px;">
<button class="btn" id="btnSaveShopManageGroup" type="button">保存分组</button>
<button class="btn btn-secondary" id="btnCloseShopManageGroupModal" type="button">取消</button>
</div>
</div>
</div>
<!-- 数据去重总数据 -->
<div id="panel-dedupe-total-data" class="tab-panel">
<div class="form-box">
<div class="dedupe-group-access">
<div class="dedupe-group-access-main">
<span class="dedupe-group-access-title">数据权限分组</span>
<div class="dedupe-group-summary" id="dedupeGroupSummary">正在加载...</div>
</div>
</div>
</div>
<div class="panel-box">
<div style="display:flex;align-items:center;justify-content:space-between;gap:12px;margin-bottom:16px;">
<h3 style="margin-bottom:0;">ASIN列表</h3>
<div style="display:flex;gap:8px;">
<button class="btn" id="btnOpenCreateDedupeTotalData" type="button">新增导入</button>
<button class="btn" id="btnOpenDeleteDedupeTotalData" type="button">删除导入</button>
</div>
</div>
<div class="dedupe-filter-row">
<div class="form-group">
<label>ASIN</label>
<input type="text" id="searchDedupeTotalData" placeholder="输入 ASIN">
</div>
<div class="form-group">
<label>用户名(模糊搜索)</label>
<input type="text" id="searchDedupeTotalDataUsername" placeholder="输入用户名">
</div>
<div class="form-group">
<label>分组</label>
<select id="dedupeTotalDataGroupFilterId">
<option value="">全部分组</option>
</select>
</div>
<div class="form-group">
<label>国家</label>
<select id="dedupeTotalDataCountryFilter">
<option value="">全部国家</option>
<option value="DE">德国</option>
<option value="UK">英国</option>
<option value="FR">法国</option>
<option value="IT">意大利</option>
<option value="ES">西班牙</option>
</select>
</div>
<div class="dedupe-filter-actions">
<button class="btn" id="btnSearchDedupeTotalData">查询</button>
</div>
</div>
<div class="dedupe-filter-date-row">
<div class="form-group">
<label>开始日期</label>
<input type="date" id="exportDedupeTotalDataStartDate">
</div>
<div class="form-group">
<label>结束日期</label>
<input type="date" id="exportDedupeTotalDataEndDate">
</div>
<div class="dedupe-filter-actions">
<button class="btn btn-secondary" id="btnExportDedupeTotalData" type="button">导出 XLSX</button>
</div>
</div>
<div class="table-scroll dedupe-table-scroll">
<table>
<thead>
<tr>
<th>ID</th>
<th>ASIN</th>
<th>国家</th>
<th>用户名</th>
<th>分组</th>
<th>创建时间</th>
<th>操作</th>
</tr>
</thead>
<tbody id="dedupeTotalDataListBody"></tbody>
</table>
</div>
<div class="pagination" id="dedupeTotalDataPagination"></div>
</div>
</div>
<!-- 不符合ASIN数据 -->
<div id="panel-invalid-asin-data" class="tab-panel">
<div class="panel-box">
<div style="display:flex;align-items:center;justify-content:space-between;gap:12px;margin-bottom:16px;">
<h3 style="margin-bottom:0;">品牌数据列表</h3>
<button class="btn" id="btnOpenCreateInvalidAsinData" type="button">新增</button>
</div>
<div class="form-row" style="margin-bottom:16px;">
<div class="form-group" style="min-width:200px;">
<label>ASIN</label>
<input type="text" id="searchInvalidAsinData" placeholder="输入 ASIN">
</div>
<div class="form-group" style="min-width:200px;">
<label>品牌</label>
<input type="text" id="searchInvalidAsinDataBrand" placeholder="输入品牌">
</div>
<div class="form-group" style="min-width:180px;">
<label>分组</label>
<select id="invalidAsinDataFilterGroupId">
<option value="">全部分组</option>
</select>
</div>
<button class=" btn" id="btnSearchInvalidAsinData">查询</button>
</div>
<div class="table-scroll invalid-asin-table-scroll">
<table>
<thead>
<tr>
<th>ID</th>
<th>ASIN</th>
<th>品牌</th>
<th>分组</th>
<th>来源</th>
<th>创建时间</th>
<th>操作</th>
</tr>
</thead>
<tbody id="invalidAsinDataListBody"></tbody>
</table>
</div>
<div class="pagination" id="invalidAsinDataPagination"></div>
</div>
</div>
<!-- 店铺密钥管理 -->
<div id="panel-shop-keys" class="tab-panel">
<div class="panel-box">
<div style="display:flex;align-items:center;justify-content:space-between;gap:12px;margin-bottom:16px;">
<h3 style="margin-bottom:0;">店铺密钥列表</h3>
<button class="btn" id="btnOpenCreateShopKey" type="button">新增店铺密钥</button>
</div>
<div class="shop-key-table-scroll">
<table>
<thead>
<tr>
<th>序号</th>
<th>备注名</th>
<th>紫鸟账号名称</th>
<th>紫鸟令牌</th>
<th title="显示紫鸟账号当前 IP 白名单检测结果;悬停可查看检测详情">白名单状态 <span class="table-help" aria-hidden="true">?</span></th>
<th>创建时间</th>
<th>修改时间</th>
<th>操作</th>
</tr>
</thead>
<tbody id="shopKeyListBody"></tbody>
</table>
</div>
<div class="pagination" id="shopKeyPagination"></div>
</div>
</div>
<!-- 店铺管理 -->
<div id="panel-shop-manage" class="tab-panel">
<div class="panel-box">
<div style="display:flex;align-items:center;justify-content:space-between;gap:12px;margin-bottom:16px;">
<h3 style="margin-bottom:0;">店铺列表</h3>
<button class="btn" id="btnOpenCreateShopManage" type="button">新增店铺</button>
</div>
<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>
<div class="table-scroll shop-manage-table-scroll">
<table>
<thead>
<tr>
<th>序号</th>
<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>
<div class="pagination" id="shopManagePagination"></div>
</div>
</div>
<!-- 查看生成记录 -->
<div id="panel-skip-price-asin" class="tab-panel">
<div class="panel-box">
<div style="display:flex;align-items:center;justify-content:space-between;gap:12px;margin-bottom:16px;">
<h3 style="margin-bottom:0;">店铺列表</h3>
<div style="display:flex;gap:8px;">
<button class="btn" id="btnOpenCreateSkipPriceAsin" type="button">新增 ASIN</button>
<button class="btn btn-secondary" id="btnOpenImportSkipPriceAsin" type="button">导入文件新增</button>
<button class="btn" id="btnOpenDeleteImportSkipPriceAsin" type="button">导入文件删除</button>
</div>
</div>
<div class="form-row" style="margin-bottom:12px;">
<div class="form-group">
<label>分组</label>
<select id="skipPriceAsinFilterGroupId">
<option value="">全部分组</option>
</select>
</div>
<div class="form-group">
<label>店铺名</label>
<input type="text" id="skipPriceAsinFilterShopName" placeholder="请输入店铺名">
</div>
<div class="form-group">
<label>国家</label>
<select id="skipPriceAsinFilterCountry">
<option value="">全部国家</option>
<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">
<label>ASIN</label>
<input type="text" id="skipPriceAsinFilterAsin" placeholder="请输入 ASIN">
</div>
<div class="form-group">
<label>最低价范围</label>
<div style="display:flex;align-items:center;gap:6px;min-width:0;">
<input type="number" id="skipPriceAsinFilterPriceFrom" placeholder="最低价 ≥" min="0" step="0.01" style="flex:1;min-width:0;">
<span style="color:var(--c-text-3);flex:none;">-</span>
<input type="number" id="skipPriceAsinFilterPriceTo" placeholder="最高价 ≤" min="0" step="0.01" style="flex:1;min-width:0;">
</div>
</div>
<button class="btn" id="btnSearchSkipPriceAsin">查询</button>
<button class="btn btn-secondary" id="btnExportSkipPriceAsin" type="button">导出 XLSX</button>
</div>
<div class="table-scroll skip-price-asin-table-scroll">
<table>
<thead>
<tr>
<th>序号</th>
<th>分组</th>
<th>店铺名</th>
<th>ASIN</th>
<th>国家</th>
<th>最低价</th>
<th class="asin-col-actions">操作</th>
</tr>
</thead>
<tbody id="skipPriceAsinListBody"></tbody>
</table>
</div>
<div class="pagination" id="skipPriceAsinPagination"></div>
</div>
</div>
<div id="panel-query-asin" class="tab-panel">
<div class="panel-box">
<div style="display:flex;align-items:center;justify-content:space-between;gap:12px;margin-bottom:16px;">
<h3 style="margin-bottom:0;">店铺列表</h3>
<div style="display:flex;gap:8px;">
<button class="btn" id="btnOpenCreateQueryAsin" type="button">新增 ASIN</button>
<button class="btn btn-secondary" id="btnOpenImportQueryAsin" type="button">导入添加</button>
<button class="btn" id="btnOpenDeleteImportQueryAsin" type="button">导入删除</button>
</div>
</div>
<div class="form-row" style="margin-bottom:12px;">
<div class="form-group">
<label>分组</label>
<select id="queryAsinFilterGroupId">
<option value="">全部分组</option>
</select>
</div>
<div class="form-group">
<label>店铺名</label>
<input type="text" id="queryAsinFilterShopName" placeholder="请输入店铺名">
</div>
<div class="form-group">
<label>国家</label>
<select id="queryAsinFilterCountry">
<option value="">全部国家</option>
<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">
<label>ASIN</label>
<input type="text" id="queryAsinFilterAsin" placeholder="请输入 ASIN">
</div>
<button class="btn" id="btnSearchQueryAsin">查询</button>
<button class="btn btn-secondary" id="btnExportQueryAsin" type="button">导出 XLSX</button>
</div>
<div class="table-scroll query-asin-table-scroll">
<table>
<thead>
<tr>
<th>序号</th>
<th>分组</th>
<th>店铺名</th>
<th>ASIN</th>
<th>国家</th>
<th class="asin-col-actions">操作</th>
</tr>
</thead>
<tbody id="queryAsinListBody"></tbody>
</table>
</div>
<div class="pagination" id="queryAsinPagination"></div>
</div>
</div>
<div id="panel-product-categories" class="tab-panel">
<div class="panel-box">
<div style="display:flex;align-items:center;justify-content:space-between;gap:12px;margin-bottom:16px;">
<h3 style="margin-bottom:0;">商品类目</h3>
<div style="display:flex;align-items:center;gap:12px;">
<div class="form-row" style="gap:8px;margin:0;flex:0 1 420px;">
<div class="form-group" style="min-width:220px;flex:1;margin:0;">
<input type="text" id="productCategoryKeyword" placeholder="搜索类目名称、编码或备注">
</div>
<button class="btn btn-sm" id="btnSearchProductCategory" type="button">搜索</button>
<button class="btn btn-sm btn-secondary" id="btnClearProductCategorySearch"
type="button">清空</button>
<button class="btn btn-sm btn-secondary" id="btnExportProductCategory" type="button">导出</button>
</div>
<button class="btn" id="btnOpenCreateProductCategory" type="button">新增类目</button>
</div>
</div>
<div class="table-scroll category-table-scroll">
<table>
<thead>
<tr>
<th style="width:34%;">类目</th>
<th>层级路径</th>
<th style="width:90px;">排序</th>
<th style="width:120px;">来源</th>
<th>备注</th>
<th style="width:150px;">操作</th>
</tr>
</thead>
<tbody id="productCategoryListBody"></tbody>
</table>
</div>
</div>
</div>
<div id="panel-image-video-tasks" class="tab-panel">
<div class="form-box">
<h3 style="margin-bottom:16px;">视频任务记录筛选</h3>
<div class="form-row">
<div class="form-group" style="min-width:150px;"><label>用户名</label><input type="text"
id="imageVideoFilterUsername" placeholder="模糊搜索"></div>
<div class="form-group" style="min-width:170px;"><label>提交开始</label><input type="datetime-local"
id="imageVideoFilterFrom"></div>
<div class="form-group" style="min-width:170px;"><label>提交结束</label><input type="datetime-local"
id="imageVideoFilterTo"></div>
<button class="btn" id="btnFilterImageVideoTasks" type="button">查询</button>
<button class="btn btn-secondary" id="btnResetImageVideoTasks" type="button">重置</button>
</div>
</div>
<div class="panel-box image-video-panel-box">
<div class="image-video-results">
<div class="image-video-toolbar">
<div class="image-video-toolbar-main">
<button class="btn btn-secondary image-video-permission-btn" id="btnOpenImageVideoPermissions"
type="button">权限配置</button>
<button class="btn image-video-batch-btn" id="btnBatchDownloadImageVideos" type="button"
disabled>
<svg viewBox="0 0 24 24" aria-hidden="true">
<path d="M12 3v12m0 0 4-4m-4 4-4-4M5 21h14"></path>
</svg>
批量下载
</button>
<label class="image-video-select-all">
<input type="checkbox" id="imageVideoSelectAll">
全选当前页
</label>
<span class="image-video-download-progress" id="imageVideoDownloadProgress"
aria-live="polite"></span>
</div>
<span class="image-video-summary" id="imageVideoTaskTotal"></span>
</div>
<div class="image-video-grid" id="imageVideoTaskGrid" aria-live="polite"></div>
</div>
<div class="pagination" id="imageVideoTaskPagination"></div>
</div>
</div>
<div id="panel-shop-data-crawl-tasks" class="tab-panel">
<div class="form-box" id="shopDataTaskFilterBox">
<h3 style="margin-bottom:16px;">店铺数据记录</h3>
<div id="shopDataRecordsFilterFields">
<h4 style="margin-bottom:14px;">筛选条件</h4>
<div class="form-row">
<div class="form-group" style="min-width:150px;"><label>店铺</label><input type="text"
id="shopDataTaskFilterShop" placeholder="模糊搜索店铺名"></div>
<div class="form-group" style="min-width:140px;"><label>分组</label><input type="text"
id="shopDataTaskFilterGroup" placeholder="模糊搜索分组"></div>
<div class="form-group" style="min-width:120px;"><label>国家</label><select
id="shopDataTaskFilterCountry">
<option value="">全部国家</option>
<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:170px;"><label>创建开始</label><input
type="datetime-local" id="shopDataTaskFilterFrom"></div>
<div class="form-group" style="min-width:170px;"><label>创建结束</label><input
type="datetime-local" id="shopDataTaskFilterTo"></div>
<button class="btn" id="btnFilterShopDataTasks" type="button">查询</button>
<button class="btn btn-secondary" id="btnResetShopDataTasks" type="button">重置</button>
</div>
</div>
</div>
<div class="panel-box image-video-panel-box">
<div id="shopDataRecordsView">
<div class="image-video-results">
<div class="image-video-toolbar">
<div class="image-video-toolbar-main">
<button class="btn btn-secondary image-video-permission-btn"
id="btnOpenShopDataTaskPermissions" type="button">权限配置</button>
<button class="btn image-video-batch-btn" id="btnBatchDownloadShopDataTasks"
type="button" disabled>
<svg viewBox="0 0 24 24" aria-hidden="true">
<path d="M12 3v12m0 0 4-4m-4 4-4-4M5 21h14"></path>
</svg>
批量下载
</button>
<label class="image-video-select-all">
<input type="checkbox" id="shopDataTaskSelectAll">
全选当前页
</label>
<span class="image-video-download-progress" id="shopDataTaskDownloadProgress"
aria-live="polite"></span>
</div>
<span class="image-video-summary" id="shopDataTaskTotal"></span>
</div>
<div class="table-scroll shop-data-record-table-scroll" id="shopDataTaskGrid" aria-live="polite"></div>
</div>
<div class="pagination" id="shopDataTaskPagination"></div>
</div>
</div>
</div>
<div id="panel-shop-data-duplicate-check" class="tab-panel">
<div class="form-box" id="shopDataDuplicateFilterBox">
<h3 style="margin-bottom:16px;">店铺数据重复检查</h3>
<div id="shopDataDuplicateFilterFields">
<h4 style="margin-bottom:14px;">筛选条件</h4>
<div class="form-row">
<div class="form-group" style="min-width:150px;"><label>ASIN</label><input type="text"
id="shopDataDupFilterAsin" placeholder="模糊搜索 ASIN"></div>
<div class="form-group" style="min-width:150px;"><label>店铺</label><input type="text"
id="shopDataDupFilterShop" placeholder="模糊搜索店铺 / 分组"></div>
<div class="form-group" style="min-width:120px;"><label>国家</label><select
id="shopDataDupFilterCountry">
<option value="">全部国家</option>
<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:130px;"><label>站点</label><select
id="shopDataDupFilterSite">
<option value="">全部站点</option>
<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:170px;"><label>上架时间开始</label><input
type="datetime-local" id="shopDataDupFilterDateFrom"></div>
<div class="form-group" style="min-width:170px;"><label>上架时间结束</label><input
type="datetime-local" id="shopDataDupFilterDateTo"></div>
<button class="btn" id="btnFilterShopDataDuplicates" type="button">查询</button>
<button class="btn btn-secondary" id="btnResetShopDataDuplicates" type="button">重置</button>
</div>
</div>
</div>
<div class="panel-box image-video-panel-box">
<div id="shopDataDuplicatesView">
<div class="dup-check-overview">
<div class="dup-check-metrics" id="shopDataDuplicateMetrics"></div>
<div class="dup-check-distribution">
<div class="dup-check-distribution-head">
<span class="dup-check-distribution-title">店铺上架分布</span>
<span class="dup-check-distribution-sub" id="shopDataDuplicateDistributionNote"></span>
</div>
<div class="dup-check-distribution-bars" id="shopDataDuplicateDistribution"></div>
</div>
</div>
<div class="dup-check-table-wrap">
<div class="image-video-toolbar dup-check-toolbar">
<div class="image-video-toolbar-main">
<div class="dup-check-view-tabs" role="tablist" aria-label="重复检查视图">
<button class="dup-check-view-tab active" type="button" id="dupCheckTabMonitor"
role="tab" aria-selected="true">重复监控(<span id="dupCheckMonitorCount">0</span>)</button>
<button class="dup-check-view-tab" type="button" id="dupCheckTabAll"
role="tab" aria-selected="false">全部ASIN台账(<span id="dupCheckAllCount">0</span>)</button>
</div>
<button class="btn shop-data-refresh-btn" id="btnRefreshShopDataDuplicates"
type="button">重新分析</button>
<button class="btn btn-secondary" id="btnExportShopDataDuplicates" type="button">导出</button>
<span class="image-video-download-progress" id="shopDataDuplicateProgress"
aria-live="polite"></span>
</div>
<span class="image-video-summary" id="shopDataDuplicateTotal"></span>
</div>
<div class="table-scroll shop-data-record-table-scroll dup-check-table-scroll" id="shopDataDuplicateList" aria-live="polite"></div>
<div class="pagination" id="shopDataDuplicatePagination"></div>
</div>
<div class="dup-check-detail-block" id="dupCheckDetailBlock">
<div class="dup-check-detail-head">
<span class="dup-check-detail-title">撞款详情</span>
<span class="dup-check-detail-sub">同一 ASIN 在多条店铺的上架明细</span>
</div>
<div class="dup-check-detail-cards" id="dupCheckDetailCards"></div>
<div class="pagination" id="dupCheckDetailPagination" style="margin-top:12px;"></div>
</div>
<div class="drawer-mask" id="dupCheckDrawerMask">
<aside class="drawer" role="dialog" aria-modal="true" aria-label="ASIN 详情">
<div class="drawer-header">
<div>
<h3 id="dupCheckDrawerAsin">ASIN 详情</h3>
<p class="drawer-subtitle" id="dupCheckDrawerSubtitle"></p>
</div>
<button class="drawer-close" id="btnCloseDupCheckDrawer" type="button"
aria-label="关闭">×</button>
</div>
<div class="drawer-body" id="dupCheckDrawerBody"></div>
</aside>
</div>
</div>
</div>
</div>
<div id="panel-history" class="tab-panel">
<div class="form-box">
<h3 style="margin-bottom:16px;">筛选条件</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;">生成记录</h3>
<div class="table-scroll history-table-scroll">
<table>
<thead>
<tr>
<th>ID</th>
<th>用户</th>
<th>类型</th>
<th>创建时间</th>
<th>结果预览</th>
</tr>
</thead>
<tbody id="historyListBody"></tbody>
</table>
</div>
<div class="pagination" id="historyPagination"></div>
</div>
</div>
<!-- 编辑用户弹窗 -->
<!-- 创建用户弹窗 -->
<div class="modal-mask" id="createUserModal">
<div class="modal" style="max-width:760px;width:90%;max-height:85vh;overflow-y:auto;">
<form id="createUserForm" novalidate>
<h3>创建用户</h3>
<div class="form-group">
<label for="username">用户名</label>
<input type="text" id="username" autocomplete="username" placeholder="用户名(至少 2 个字符)">
</div>
<div class="form-group">
<label for="password">密码</label>
<input type="password" id="password" autocomplete="new-password" placeholder="密码(至少 6 个字符)">
</div>
<div class="form-group" id="formGroupRole">
<label for="createRole">角色</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 for="createCreatedBy">所属管理员</label>
<select id="createCreatedBy">
<option value="">请选择管理员</option>
</select>
</div>
<div class="form-group" id="formGroupCreateColumns">
<label id="createColumnPermissionLabel">菜单权限(勾选上级菜单会继承全部子菜单)</label>
<div id="createColumnCards" class="column-permission-cards"></div>
<div id="createColumnPermissionWrap" class="column-permission-tree" role="group" aria-labelledby="createColumnPermissionLabel"></div>
</div>
<p class="msg" id="msgCreate"></p>
<div style="margin-top:16px;display:flex;gap:8px;">
<button class="btn" id="btnCreate" type="button">创建用户</button>
<button class="btn btn-secondary" id="btnCloseCreateUserModal" type="button">取消</button>
</div>
</form>
</div>
</div>
<div class="modal-mask" id="editUserModal">
<div class="modal">
<form id="editUserForm" novalidate>
<h3>编辑用户</h3>
<input type="hidden" id="editUserId">
<div class="form-group">
<label for="editUsername">用户名</label>
<input type="text" id="editUsername" autocomplete="username" readonly style="background:#f4f6fa;">
</div>
<div class="form-group">
<label for="editPassword">新密码(不修改可留空)</label>
<input type="password" id="editPassword" autocomplete="new-password" placeholder="留空则不修改密码">
</div>
<div class="form-group" id="editFormGroupRole" style="display:none;">
<label for="editRole">角色</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:#f4f6fa;">
</div>
<div class="form-group">
<label id="editColumnPermissionLabel">菜单权限(勾选上级菜单会继承全部子菜单)</label>
<div id="editColumnCards" class="column-permission-cards"></div>
<div id="editColumnPermissionWrap" class="column-permission-tree" role="group" aria-labelledby="editColumnPermissionLabel"></div>
</div>
<p class="msg" id="msgEdit"></p>
<div style="margin-top:16px;display:flex;gap:8px;">
<button class="btn" id="btnSaveUser" type="button">保存</button>
<button class="btn btn-secondary" id="btnCloseEdit" type="button">取消</button>
</div>
</form>
</div>
</div>
<!-- 编辑总数据弹窗 -->
<div class="modal-mask" id="editDedupeTotalDataModal">
<div class="modal">
<h3>编辑ASIN</h3>
<input type="hidden" id="editDedupeTotalDataId">
<div class="form-group">
<label>ASIN</label>
<input type="text" id="editDedupeTotalDataValue" placeholder="请输入总数据值">
</div>
<div class="form-group">
<label>分组</label>
<select id="editDedupeTotalDataGroupId">
<option value="">请选择分组</option>
</select>
</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>
<!-- 编辑不符合ASIN数据弹窗 -->
<div class="modal-mask" id="editInvalidAsinDataModal">
<div class="modal">
<h3>编辑品牌数据</h3>
<input type="hidden" id="editInvalidAsinDataId">
<input type="hidden" id="editInvalidAsinDataRecordSource">
<div class="form-group">
<label>ASIN</label>
<input type="text" id="editInvalidAsinDataValue" placeholder="请输入 ASIN">
</div>
<div class="form-group">
<label>品牌</label>
<input type="text" id="editInvalidAsinDataBrand" placeholder="请输入品牌(可选)">
</div>
<div class="form-group">
<label>来源</label>
<input type="text" id="editInvalidAsinDataSourceLabel" readonly>
</div>
<div class="form-group" id="editInvalidAsinDataGroupForm">
<label>分组</label>
<select id="editInvalidAsinDataGroupSelect" style="min-width:200px;">
<option value="">请选择分组</option>
</select>
</div>
<p class=" msg" id="msgEditInvalidAsinData"></p>
<div style="margin-top:16px;display:flex;gap:8px;">
<button class="btn" id="btnSaveInvalidAsinData">保存</button>
<button class="btn btn-secondary" id="btnCloseEditInvalidAsinData">取消</button>
</div>
</div>
</div>
<!-- 新增菜单弹窗 -->
<div class="modal-mask" id="createColumnModal">
<div class="modal" style="max-width:640px;width:90%;max-height:85vh;overflow-y:auto;">
<h3>新增菜单</h3>
<div class="form-group">
<label>菜单名称</label>
<input type="text" id="columnName" placeholder="例如:店铺管理">
</div>
<div class="form-group">
<label>页面</label>
<select id="columnPageSelect">
<option value="">请选择页面</option>
</select>
</div>
<div class="form-group">
<label>菜单类型</label>
<select id="columnMenuType">
<option value="admin">后台</option>
<option value="app">软件</option>
</select>
</div>
<div class="form-group">
<label>上级菜单(可选)</label>
<select id="columnParentId"><option value="">无(一级菜单)</option></select>
</div>
<p class="msg" id="msgColumn"></p>
<div style="margin-top:16px;display:flex;gap:8px;">
<button class="btn" id="btnAddColumn" type="button">保存</button>
<button class="btn btn-secondary" id="btnCloseCreateColumnModal" type="button">取消</button>
</div>
</div>
</div>
<!-- 编辑栏目弹窗 -->
<div class="modal-mask" id="editColumnModal">
<div class="modal">
<h3>编辑栏目</h3>
<input type="hidden" id="editColumnId">
<input type="hidden" id="editColumnSortOrder">
<div class="form-group">
<label>菜单名称</label>
<input type="text" id="editColumnName" placeholder="菜单名称">
</div>
<div class="form-group">
<label>页面</label>
<select id="editColumnPageSelect">
<option value="">请选择页面</option>
</select>
</div>
<div class="form-group">
<label>菜单类型</label>
<select id="editColumnMenuType">
<option value="admin">后台</option>
<option value="app">软件</option>
</select>
</div>
<div class="form-group">
<label>上级菜单(可选)</label>
<select id="editColumnParentId"><option value="">无(一级菜单)</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="drawer-mask" id="skipPriceAsinDrawer">
<div class="drawer" role="dialog" aria-modal="true" aria-labelledby="skipPriceAsinDrawerTitle">
<div class="drawer-header">
<div>
<h3 id="skipPriceAsinDrawerTitle">配置最低价 ASIN</h3>
<p class="drawer-subtitle" id="skipPriceAsinDrawerSubtitle"></p>
</div>
<button class="drawer-close" id="btnCloseSkipPriceAsinDrawer" type="button" aria-label="关闭">×</button>
</div>
<div class="drawer-body">
<input type="hidden" id="skipPriceAsinDrawerId">
<p class="drawer-hint">留空表示删除该站点已有的 ASIN 与最低价。</p>
<div id="skipPriceAsinDrawerFields"></div>
<p class="msg" id="msgSkipPriceAsinDrawer"></p>
</div>
<div class="drawer-footer">
<button class="btn" id="btnSaveSkipPriceAsinDrawer" type="button">保存</button>
<button class="btn btn-secondary" id="btnCancelSkipPriceAsinDrawer" type="button">取消</button>
</div>
</div>
</div>
<div class="drawer-mask" id="queryAsinDrawer">
<div class="drawer" role="dialog" aria-modal="true" aria-labelledby="queryAsinDrawerTitle">
<div class="drawer-header">
<div>
<h3 id="queryAsinDrawerTitle">配置查询 ASIN</h3>
<p class="drawer-subtitle" id="queryAsinDrawerSubtitle"></p>
</div>
<button class="drawer-close" id="btnCloseQueryAsinDrawer" type="button" aria-label="关闭">×</button>
</div>
<div class="drawer-body">
<input type="hidden" id="queryAsinDrawerId">
<p class="drawer-hint">留空表示删除该站点已有的 ASIN。</p>
<div id="queryAsinDrawerFields"></div>
<p class="msg" id="msgQueryAsinDrawer"></p>
</div>
<div class="drawer-footer">
<button class="btn" id="btnSaveQueryAsinDrawer" type="button">保存</button>
<button class="btn btn-secondary" id="btnCancelQueryAsinDrawer" type="button">取消</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>
<select id="editShopManageGroupSelect" style="min-width:200px;">
<option value="">请选择分组</option>
</select>
</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="editShopManageZnUsername" maxlength="128" 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="editShopKeyRemarkName" maxlength="128" placeholder="备注名(可选)">
</div>
<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>
<!-- 上传软件版本弹窗 -->
<div class="modal-mask" id="createVersionModal">
<div class="modal">
<h3>上传新版本</h3>
<p style="margin-bottom:16px;font-size:13px;color:var(--c-text-2);">
上传软件 ZIP 包,系统会自动计算 MD5 并存储到 OSS。
</p>
<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">
<p style="font-size:12px;color:var(--c-text-3);margin-top:4px;">仅支持 .zip 格式</p>
</div>
<p class="msg" id="msgVersion"></p>
<div style="margin-top:16px;display:flex;gap:8px;">
<button class="btn" id="btnUploadVersion" type="button">上传软件版本</button>
<button class="btn btn-secondary" id="btnCloseCreateVersionModal" type="button">取消</button>
</div>
</div>
</div>
<!-- 上传数字人版本弹窗 -->
<div class="modal-mask" id="createDigitalHumanVersionModal">
<div class="modal">
<h3>上传新版本</h3>
<p style="margin-bottom:16px;font-size:13px;color:var(--c-text-2);">
上传数字人程序 ZIP 包,系统会自动计算 MD5 并存储到 OSS。上传后状态为草稿,需要手动发布。
</p>
<div class="form-row">
<div class="form-group" style="min-width:200px;flex:1;">
<label>版本号 *</label>
<input type="text" id="digitalHumanVersionNumber" placeholder="例如:1.0.0">
</div>
<div class="form-group" style="min-width:200px;flex:1;">
<label>最低客户端版本</label>
<input type="text" id="digitalHumanMinClientVersion" placeholder="例如:2.0.0(可选)">
</div>
</div>
<div class="form-group">
<label>更新日志</label>
<textarea id="digitalHumanVersionChangelog" rows="3" placeholder="例如:修复数字人口型同步问题,优化启动速度"
style="width:100%;padding:10px;border:1px solid var(--c-border);border-radius:var(--radius-sm);resize:vertical;"></textarea>
</div>
<div class="form-group">
<label>ZIP 压缩包 *</label>
<input type="file" id="digitalHumanVersionZip" accept=".zip">
</div>
<p class="msg" id="msgDigitalHumanVersion"></p>
<div style="margin-top:16px;display:flex;gap:8px;">
<button class="btn" id="btnUploadDigitalHumanVersion" type="button">上传(草稿状态)</button>
<button class="btn btn-secondary" id="btnCloseCreateDigitalHumanVersionModal" type="button">取消</button>
</div>
</div>
</div>
<!-- 新增品牌数据弹窗 -->
<div class="modal-mask" id="createInvalidAsinDataModal">
<div class="modal">
<h3>新增品牌数据</h3>
<div class="form-group">
<label>ASIN</label>
<input type="text" id="invalidAsinDataValue" placeholder="请输入 ASIN">
</div>
<div class="form-group">
<label>品牌</label>
<input type="text" id="invalidAsinDataBrand" placeholder="请输入品牌(可选)">
</div>
<div class="form-group">
<label>分组</label>
<select id="invalidAsinDataGroupSelect" style="min-width:200px;">
<option value="">请选择分组</option>
</select>
</div>
<p class="msg" id="msgInvalidAsinData"></p>
<div style="margin-top:16px;display:flex;gap:8px;">
<button class="btn" id="btnAddInvalidAsinData" type="button">新增</button>
<button class="btn btn-secondary" id="btnCloseCreateInvalidAsinData" type="button">取消</button>
</div>
</div>
</div>
<!-- 新增店铺密钥弹窗 -->
<div class="modal-mask" id="createShopKeyModal">
<div class="modal">
<h3>新增店铺密钥</h3>
<div class="form-group">
<label>备注名</label>
<input type="text" id="shopKeyRemarkName" maxlength="128" placeholder="请输入备注名(可选)">
</div>
<div class="form-group">
<label>紫鸟账号名称</label>
<input type="text" id="shopKeyZiniaoAccountName" placeholder="请输入紫鸟账号名称">
</div>
<div class="form-group">
<label>紫鸟令牌</label>
<input type="text" id="shopKeyZiniaoToken" placeholder="请输入紫鸟令牌">
</div>
<p class="msg" id="msgShopKey"></p>
<div style="margin-top:16px;display:flex;gap:8px;">
<button class="btn" id="btnCreateShopKey" type="button">新增店铺密钥</button>
<button class="btn btn-secondary" id="btnCloseCreateShopKey" type="button">取消</button>
</div>
</div>
</div>
<!-- 新增店铺弹窗 -->
<div class="modal-mask" id="createShopManageModal">
<div class="modal">
<h3>新增店铺</h3>
<div class="form-group">
<label>分组</label>
<select id="shopManageGroupSelect" style="min-width:200px;">
<option value="">请选择分组</option>
</select>
</div>
<div class="form-group">
<label>店铺名</label>
<input type="text" id="shopManageShopName" placeholder="请输入店铺名称">
</div>
<div class="form-group">
<label>店铺商城名</label>
<input type="text" id="shopManageMallName" placeholder="请输入店铺商城名">
</div>
<div class="form-group">
<label>自动化账号</label>
<input type="text" id="shopManageZnUsername" maxlength="128" placeholder="请输入自动化账号(可选)">
</div>
<div class="form-group">
<label>账号</label>
<input type="text" id="shopManageAccount" placeholder="请输入账号">
</div>
<div class="form-group">
<label>密码</label>
<input type="text" id="shopManagePassword" placeholder="请输入密码">
</div>
<p class="msg" id="msgShopManage"></p>
<div style="margin-top:16px;display:flex;gap:8px;">
<button class="btn" id="btnCreateShopManage" type="button">新增店铺</button>
<button class="btn btn-secondary" id="btnCloseCreateShopManage" type="button">取消</button>
</div>
</div>
</div>
<!-- 新增/编辑商品类目弹窗 -->
<div class="modal-mask" id="createProductCategoryModal">
<div class="modal">
<h3 id="createProductCategoryModalTitle">新增商品类目</h3>
<div class="form-group">
<label>上级类目</label>
<select id="productCategoryParentId" style="min-width:200px;">
<option value="">顶级类目</option>
</select>
</div>
<div class="form-group">
<label>类目名称</label>
<input type="text" id="productCategoryName" placeholder="例如:洁面">
</div>
<div class="form-group">
<label>排序</label>
<input type="number" id="productCategorySortOrder" placeholder="例如:10">
</div>
<div class="form-group">
<label>备注</label>
<input type="text" id="productCategoryDescription" placeholder="可填写风险说明或使用场景">
</div>
<p class="msg" id="msgProductCategory"></p>
<div style="margin-top:16px;display:flex;gap:8px;">
<button class="btn" id="btnCreateProductCategory">新增类目</button>
<button class="btn btn-secondary" id="btnCancelProductCategoryEdit" type="button"
style="display:none;">取消编辑</button>
<button class="btn btn-secondary" id="btnCloseCreateProductCategoryModal" type="button">关闭</button>
</div>
</div>
</div>
<!-- 去重汇总:新增ASIN弹窗 -->
<div class="modal-mask" id="createDedupeTotalDataModal">
<div class="modal">
<h3>新增导入</h3>
<div class="form-group">
<label>上传 Excel 文件(读取 ASIN 列,可选 国家 列)</label>
<input type="file" id="dedupeTotalDataFile" accept=".xlsx,.xls">
</div>
<div class="form-group">
<label>分组</label>
<select id="dedupeTotalDataImportGroupId" style="min-width:200px;">
<option value="">请选择分组</option>
</select>
</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 style="margin-top:16px;display:flex;gap:8px;">
<button class="btn" id="btnAddDedupeTotalData">上传并导入</button>
<button class="btn btn-secondary" id="btnCloseCreateDedupeTotalDataModal" type="button">取消</button>
</div>
</div>
</div>
<!-- 去重汇总:导入删除弹窗 -->
<div class="modal-mask" id="deleteDedupeTotalDataModal">
<div class="modal">
<h3>删除导入</h3>
<div class="form-group">
<label>上传 Excel 文件(读取 ASIN 列)</label>
<input type="file" id="dedupeTotalDataDeleteFile" accept=".xlsx,.xls">
</div>
<div class="form-group">
<label>分组</label>
<select id="dedupeTotalDataDeleteGroupId" style="min-width:200px;">
<option value="">请选择分组</option>
</select>
</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 style="margin-top:16px;display:flex;gap:8px;">
<button class="btn btn-danger" id="btnDeleteImportDedupeTotalData">上传并删除</button>
<button class="btn btn-secondary" id="btnCloseDeleteDedupeTotalDataModal" type="button">取消</button>
</div>
</div>
</div>
<!-- 跳过跟价ASIN:新增ASIN弹窗 -->
<div class="modal-mask" id="createSkipPriceAsinModal">
<div class="modal">
<h3>新增 ASIN</h3>
<div class="form-row">
<div class="form-group">
<label for="skipPriceAsinGroupSelect">分组</label>
<select id="skipPriceAsinGroupSelect">
<option value="">请选择分组</option>
</select>
</div>
<div class="form-group">
<label for="skipPriceAsinShopName">店铺名</label>
<select id="skipPriceAsinShopName" disabled>
<option value="">请先选择分组</option>
</select>
</div>
<div class="form-group">
<label for="skipPriceAsinCountry">国家</label>
<select id="skipPriceAsinCountry">
<option value="">请选择国家</option>
<option value="DE">德国</option>
<option value="UK">英国</option>
<option value="FR">法国</option>
<option value="IT">意大利</option>
<option value="ES">西班牙</option>
</select>
</div>
</div>
<div class="form-row">
<div class="form-group">
<label for="skipPriceAsinValue">ASIN</label>
<input type="text" id="skipPriceAsinValue" placeholder="请输入 ASIN">
</div>
<div class="form-group" style="flex:0 0 180px;">
<label for="skipPriceAsinMinimumPrice">最低价</label>
<input type="number" id="skipPriceAsinMinimumPrice" min="0" step="0.01" placeholder="选填">
</div>
</div>
<p class="msg" id="msgSkipPriceAsin"></p>
<div style="margin-top:16px;display:flex;gap:8px;">
<button class="btn" id="btnCreateSkipPriceAsin">保存</button>
<button class="btn btn-secondary" id="btnCloseCreateSkipPriceAsinModal" type="button">取消</button>
</div>
</div>
</div>
<!-- 跳过跟价ASIN:导入文件新增弹窗 -->
<div class="modal-mask" id="importSkipPriceAsinModal">
<div class="modal">
<h3>导入文件新增</h3>
<div class="form-group">
<label>分组</label>
<select id="skipPriceAsinImportGroupId" style="min-width:200px;">
<option value="">请选择分组</option>
</select>
</div>
<div class="form-group">
<label>上传 Excel(文件名必须是该分组下的店铺名;表头为 国家 / 删除ASIN / 最低价)</label>
<input type="file" id="skipPriceAsinImportFile" accept=".xlsx,.xls">
</div>
<p class="msg" id="msgSkipPriceAsinImport"></p>
<div class="progress-wrap" id="skipPriceAsinImportProgressWrap" style="display:none;">
<div class="progress-bar">
<div class="progress-fill" id="skipPriceAsinImportProgressFill"></div>
</div>
<div class="progress-text" id="skipPriceAsinImportProgressText"></div>
</div>
<div style="margin-top:16px;display:flex;gap:8px;">
<button class="btn" id="btnImportSkipPriceAsin">上传并新增</button>
<button class="btn btn-secondary" id="btnCloseImportSkipPriceAsinModal" type="button">取消</button>
</div>
</div>
</div>
<!-- 跳过跟价ASIN:导入文件删除弹窗 -->
<div class="modal-mask" id="deleteImportSkipPriceAsinModal">
<div class="modal">
<h3>导入文件删除</h3>
<div class="form-group">
<label>分组</label>
<select id="skipPriceAsinDeleteImportGroupId" style="min-width:200px;">
<option value="">请选择分组</option>
</select>
</div>
<div class="form-group">
<label>上传 Excel(文件名必须是该分组下的店铺名;按各国家删除ASIN列匹配删除)</label>
<input type="file" id="skipPriceAsinDeleteImportFile" accept=".xlsx,.xls">
</div>
<p class="msg" id="msgSkipPriceAsinDeleteImport"></p>
<div class="progress-wrap" id="skipPriceAsinDeleteImportProgressWrap" style="display:none;">
<div class="progress-bar">
<div class="progress-fill" id="skipPriceAsinDeleteImportProgressFill"></div>
</div>
<div class="progress-text" id="skipPriceAsinDeleteImportProgressText"></div>
</div>
<div style="margin-top:16px;display:flex;gap:8px;">
<button class="btn btn-danger" id="btnDeleteImportSkipPriceAsin">上传并删除</button>
<button class="btn btn-secondary" id="btnCloseDeleteImportSkipPriceAsinModal" type="button">取消</button>
</div>
</div>
</div>
<!-- 查询ASIN:新增ASIN弹窗 -->
<div class="modal-mask" id="createQueryAsinModal">
<div class="modal">
<h3>新增查询 ASIN</h3>
<div class="form-row">
<div class="form-group">
<label for="queryAsinGroupSelect">分组</label>
<select id="queryAsinGroupSelect">
<option value="">请选择分组</option>
</select>
</div>
<div class="form-group">
<label for="queryAsinShopName">店铺名</label>
<select id="queryAsinShopName" disabled>
<option value="">请先选择分组</option>
</select>
</div>
<div class="form-group">
<label for="queryAsinCountry">国家</label>
<select id="queryAsinCountry">
<option value="">请选择国家</option>
<option value="DE">德国</option>
<option value="UK">英国</option>
<option value="FR">法国</option>
<option value="IT">意大利</option>
<option value="ES">西班牙</option>
</select>
</div>
</div>
<div class="form-group">
<label for="queryAsinValue">ASIN</label>
<input type="text" id="queryAsinValue" placeholder="请输入 ASIN">
</div>
<p class="msg" id="msgQueryAsin"></p>
<div style="margin-top:16px;display:flex;gap:8px;">
<button class="btn" id="btnCreateQueryAsin">保存</button>
<button class="btn btn-secondary" id="btnCloseCreateQueryAsinModal" type="button">取消</button>
</div>
</div>
</div>
<!-- 查询ASIN:导入添加弹窗 -->
<div class="modal-mask" id="importQueryAsinModal">
<div class="modal">
<h3>导入添加</h3>
<div class="form-group">
<label>分组</label>
<select id="queryAsinImportGroupId" style="min-width:200px;">
<option value="">请选择分组(可选)</option>
</select>
</div>
<div class="form-group">
<label>上传 Excel(支持两种格式:1. 分组/店铺名/德国-西班牙;2. 店铺账号/ASIN/中文国家名。文件无分组列时取页面当前分组)</label>
<input type="file" id="queryAsinImportFile" accept=".xlsx,.xls">
</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 style="margin-top:16px;display:flex;gap:8px;">
<button class="btn" id="btnImportQueryAsin">上传并添加</button>
<button class="btn btn-secondary" id="btnCloseImportQueryAsinModal" type="button">取消</button>
</div>
</div>
</div>
<!-- 查询ASIN:导入删除弹窗 -->
<div class="modal-mask" id="deleteImportQueryAsinModal">
<div class="modal">
<h3>导入删除</h3>
<div class="form-group">
<label>分组</label>
<select id="queryAsinDeleteImportGroupId" style="min-width:200px;">
<option value="">请选择分组(可选)</option>
</select>
</div>
<div class="form-group">
<label>上传 Excel(支持按 分组/店铺名/国家列 或 店铺账号/ASIN/中文国家名 删除;文件无分组列时取页面当前分组)</label>
<input type="file" id="queryAsinDeleteImportFile" accept=".xlsx,.xls">
</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 style="margin-top:16px;display:flex;gap:8px;">
<button class="btn btn-danger" id="btnDeleteImportQueryAsin">上传并删除</button>
<button class="btn btn-secondary" id="btnCloseDeleteImportQueryAsinModal" type="button">取消</button>
</div>
</div>
</div>
<div class="modal-mask" id="imageVideoPermissionModal">
<div class="modal image-video-permission-modal" role="dialog" aria-modal="true"
aria-labelledby="imageVideoPermissionTitle">
<div class="image-video-permission-head">
<h3 id="imageVideoPermissionTitle">视频任务记录权限配置</h3>
<button class="btn btn-secondary btn-sm" id="btnCloseImageVideoPermissions" type="button">关闭</button>
</div>
<div class="image-video-permission-tabs" role="tablist" aria-label="视频权限用户范围">
<button class="image-video-permission-tab active" type="button" role="tab" aria-selected="true"
data-image-video-permission-view="granted">已分配用户 <span
id="imageVideoPermissionGrantedCount">(0)</span></button>
<button class="image-video-permission-tab" type="button" role="tab" aria-selected="false"
data-image-video-permission-view="all">全部用户 <span
id="imageVideoPermissionAllCount">(0)</span></button>
</div>
<div class="image-video-permission-tools">
<input class="image-video-permission-search" id="imageVideoPermissionSearch" type="search"
placeholder="搜索用户名">
<label class="image-video-select-all">
<input type="checkbox" id="imageVideoPermissionSelectAll">
全选搜索结果
</label>
</div>
<p class="image-video-permission-summary" id="imageVideoPermissionSummary"></p>
<div class="image-video-permission-list" id="imageVideoPermissionList" aria-live="polite"></div>
<p class="msg" id="imageVideoPermissionMessage" aria-live="polite"></p>
<div class="image-video-permission-actions">
<button class="btn btn-secondary" id="btnCancelImageVideoPermissions" type="button">取消</button>
<button class="btn" id="btnSaveImageVideoPermissions" type="button">保存</button>
</div>
</div>
</div>
<div class="modal-mask" id="shopDataTaskPermissionModal">
<div class="modal image-video-permission-modal" role="dialog" aria-modal="true"
aria-labelledby="shopDataTaskPermissionTitle">
<div class="image-video-permission-head">
<h3 id="shopDataTaskPermissionTitle">店铺数据记录权限配置</h3>
<button class="btn btn-secondary btn-sm" id="btnCloseShopDataTaskPermissions"
type="button">关闭</button>
</div>
<div class="image-video-permission-tabs" role="tablist" aria-label="店铺数据记录权限用户范围">
<button class="image-video-permission-tab active" type="button" role="tab"
aria-selected="true" data-shop-data-permission-view="granted">已分配用户 <span
id="shopDataTaskPermissionGrantedCount">(0)</span></button>
<button class="image-video-permission-tab" type="button" role="tab" aria-selected="false"
data-shop-data-permission-view="all">全部用户 <span
id="shopDataTaskPermissionAllCount">(0)</span></button>
</div>
<div class="image-video-permission-tools">
<input class="image-video-permission-search" id="shopDataTaskPermissionSearch" type="search"
placeholder="搜索用户名">
<label class="image-video-select-all">
<input type="checkbox" id="shopDataTaskPermissionSelectAll">
全选搜索结果
</label>
</div>
<p class="image-video-permission-summary" id="shopDataTaskPermissionSummary"></p>
<div class="image-video-permission-list" id="shopDataTaskPermissionList" aria-live="polite"></div>
<p class="msg" id="shopDataTaskPermissionMessage" aria-live="polite"></p>
<div class="image-video-permission-actions">
<button class="btn btn-secondary" id="btnCancelShopDataTaskPermissions" type="button">取消</button>
<button class="btn" id="btnSaveShopDataTaskPermissions" type="button">保存</button>
</div>
</div>
</div>
</main>
</div>
</div>
<script>
// 侧边栏分组折叠:独立于 admin.js,即使主 JS 加载失败也可用;admin.js 重渲染菜单后也会调用 __initAdminMenuCollapse
(function () {
var KEY = 'shufuAdminMenuCollapsed';
function readState() {
try { return JSON.parse(localStorage.getItem(KEY) || '{}') || {}; } catch (e) { return {}; }
}
function bindGroup(group) {
var titleEl = group.querySelector('.menu-group-title');
if (!titleEl || titleEl._bound) return;
titleEl._bound = true;
var key = group.getAttribute('data-menu-group') || '';
var initiallyCollapsed = !!(key && readState()[key]);
if (initiallyCollapsed) group.classList.add('is-collapsed');
titleEl.setAttribute('aria-expanded', initiallyCollapsed ? 'false' : 'true');
titleEl.addEventListener('click', function () {
var collapsed = group.classList.toggle('is-collapsed');
titleEl.setAttribute('aria-expanded', collapsed ? 'false' : 'true');
if (key) {
var state = readState();
if (collapsed) state[key] = true; else delete state[key];
try { localStorage.setItem(KEY, JSON.stringify(state)); } catch (e) { }
}
});
}
// 为已有的相邻表单标签补上 for 关联;嵌套复选框标签保持原生包裹语义。
function associateFormLabels(root) {
root.querySelectorAll('.form-group').forEach(function (group) {
var label = Array.prototype.find.call(group.children, function (child) {
return child.tagName === 'LABEL' && !child.htmlFor && !child.querySelector('input, select, textarea');
});
if (!label) return;
var control = Array.prototype.find.call(group.querySelectorAll('input[id], select[id], textarea[id]'), function (item) {
return !item.classList.contains('multi-select-native');
});
if (control) label.htmlFor = control.id;
});
}
associateFormLabels(document);
window.__initAdminMenuCollapse = function () {
document.querySelectorAll('#adminMenu .menu-group').forEach(bindGroup);
};
window.__initAdminMenuCollapse();
})();
</script>
<script src="/static/admin.js?v=shop-data-duplicate-check"></script>
<div class="admin-toast-region" id="adminToastRegion" role="status" aria-live="polite" aria-atomic="true"></div>
<div class="admin-confirm-mask" id="adminConfirmModal" aria-hidden="true">
<section class="admin-confirm-dialog" role="alertdialog" aria-modal="true" aria-labelledby="adminConfirmTitle" aria-describedby="adminConfirmMessage">
<div class="admin-confirm-dialog__icon" aria-hidden="true">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round"><path d="M12 9v4"></path><path d="M12 17h.01"></path><path d="m10.3 3.9-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.7-3.1l-8-14a2 2 0 0 0-3.4 0Z"></path></svg>
</div>
<div class="admin-confirm-dialog__content">
<h2 id="adminConfirmTitle">请确认操作</h2>
<p id="adminConfirmMessage">该操作可能影响已有数据。</p>
</div>
<div class="admin-confirm-dialog__actions">
<button class="btn btn-secondary" id="adminConfirmCancel" type="button">取消</button>
<button class="btn btn-danger" id="adminConfirmAccept" type="button">确认删除</button>
</div>
</section>
</div>
<script src="/static/admin-interactions.js?v=admin-interactions-v1"></script>
</body>
</html>