/* Admin 样式表。手写 CSS,不引入框架、不引入构建流程。 见 admin/AGENTS.md 前端约束。 六个主列表共用同一套三段式布局的样式: .toolbar(顶部工具条)/ .table-wrap(中间表格)/ .statusbar(底部状态条)。 改这里会同时影响六个主列表,这是有意的——不要为某个页面复制一份。 */ * { box-sizing: border-box; } /* 视觉上隐藏,但继续让屏幕阅读器读取。表单控件不能只剩占位符或装饰文字。 */ .visually-hidden { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0; } .ai-config-table { min-width: 1320px; } .ai-config-table input { max-width: 100%; } .ai-config-endpoint { min-width: 300px; } .ai-config-params { min-width: 130px; } .ai-config-params input { width: 88px; display: block; margin-bottom: 4px; } .ai-config-table td { vertical-align: top; } .ai-config-table p { margin: 0 0 6px; } .ai-config-toolbar input[type="url"] { min-width: 220px; } .input-short { width: 70px; } body { margin: 0; font-family: "Microsoft YaHei", "PingFang SC", system-ui, sans-serif; font-size: 14px; color: #222; background: #f5f6f8; /* 底部状态条是固定的,给它留出空间 */ padding-bottom: 40px; } /* ── 导航 ─────────────────────────────── */ .nav { display: flex; align-items: center; gap: 4px; background: #2b3440; padding: 0 16px; } .nav-brand { color: #fff; font-weight: 600; margin-right: 24px; padding: 12px 0; } .nav a { color: #c8cdd4; text-decoration: none; padding: 12px 16px; } .nav a:hover { background: #3a4552; color: #fff; } .nav a.active { background: #1f6feb; color: #fff; } .nav-user { margin-left: auto; color: #fff; padding: 0 8px; } .nav-account { margin: 0; } .nav-logout { margin: 0; } .nav-account, .nav-logout button { color: #c8cdd4; border-color: #56616d; background: transparent; } .nav-account:hover, .nav-logout button:hover { color: #fff; background: #3a4552; } /* ── 首次初始化 / 登录 ────────────────── */ .auth-page { min-height: 100vh; display: grid; place-items: center; padding: 24px 16px; } .auth-card { width: 100%; max-width: 420px; padding: 24px; background: #fff; border: 1px solid #e1e4e8; border-radius: 6px; box-shadow: 0 8px 24px rgba(0, 0, 0, .08); } .auth-card h1 { margin: 0 0 8px; font-size: 22px; } .auth-card form { display: grid; gap: 16px; margin-top: 20px; } .auth-card input { width: 100%; } .auth-card small { color: #777; } .auth-submit { width: 100%; min-height: 38px; } /* ── 页面主体 ─────────────────────────── */ .page { padding: 16px; } /* 顺运宝是高密度操作表:桌面端让主区域占满导航和固定状态栏之间的空间, 工具条或条件提示高度变化时,表格自动取得剩余高度,不依赖固定像素扣减。 */ body.syb-page-body { height: 100vh; height: 100dvh; display: flex; flex-direction: column; overflow: hidden; } body.syb-page-body > .nav { flex: 0 0 auto; } body.syb-page-body > .page { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; } /* ── 第一段:顶部工具条 ───────────────── */ .toolbar { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; background: #fff; border: 1px solid #e1e4e8; border-radius: 4px; padding: 10px 12px; margin-bottom: 12px; } .toolbar .inline { display: flex; align-items: center; gap: 6px; margin: 0; } /* 搜索表单占据剩余空间,把删除按钮挤到右边 */ .toolbar .grow { flex: 1; } .toolbar label { color: #555; white-space: nowrap; } .toolbar .sync-range-form input[type="date"] { width: 138px; min-width: 138px; } .sync-range-message { margin: -4px 0 10px; } .filter-feedback { margin: -4px 2px 10px; white-space: normal; overflow-wrap: anywhere; } /* 搜索框收窄到 30%。目前 PDD 商品页、顺运宝数据页、采集采购页在用; 蝦皮数据页和客户端列表页仍是撑满的。 这样搜索表单里就没有会伸展的元素了,多余空间留在按钮之后, 「搜索」按钮自然贴着输入框,不会被顶到很右边。 用 flex 而不是 width:工具条是 flex 布局,width 会被 flex-grow 盖掉。 min-width 由下面那条继承,窄屏时不会缩到没法输入。 */ .toolbar input[type="text"].search-narrow { flex: 0 0 30%; } /* 顺运宝一行需要容纳两个搜索框和操作按钮,固定宽度比百分比更稳定。 */ .toolbar input[type="text"].syb-search-input { flex: 0 0 140px; min-width: 140px; } .toolbar textarea.syb-search-input { flex: 0 0 140px; width: 140px; min-width: 140px; } .syb-order-search-input { height: 30px; min-height: 30px; padding: 5px 8px; border: 1px solid #ccd1d6; border-radius: 3px; font: inherit; line-height: 18px; resize: vertical; } .syb-order-search-input[aria-invalid="true"] { border-color: #b42318; } /* SYB 工具条在 1366px 桌面窗口压成一行。所有规则都限定在本页面,避免影响 其他主列表;窄于该断点时继续使用通用 toolbar 的自然换行。 */ .syb-date-separator { color: #667085; } @media (min-width: 1360px) { .syb-toolbar { flex-wrap: nowrap; gap: 4px; padding: 8px 10px; } .syb-toolbar .inline { gap: 4px; } .syb-toolbar button, .syb-toolbar .button-link { padding-right: 7px; padding-left: 7px; } .syb-toolbar input, .syb-toolbar select, .syb-toolbar textarea, .syb-toolbar button, .syb-toolbar .button-link { font-size: 13px; } .syb-toolbar .sync-range-form input[type="date"] { width: 116px; min-width: 116px; } .syb-toolbar .syb-filter-form { flex: 1 1 auto; min-width: 0; } .syb-toolbar .syb-filter-form select { width: 138px; min-width: 138px; } } /* 蝦皮工具条同时放商品和店铺两个搜索框,各占剩余工具条的约 20%。 min-width 继续由通用输入框规则提供,窄窗口时交给工具条自然换行。 */ .toolbar input[type="text"].shopee-search-field { flex: 0 1 20%; } .toolbar input[type="text"] { flex: 1; min-width: 160px; padding: 5px 8px; border: 1px solid #ccd1d6; border-radius: 3px; } button { padding: 5px 14px; border: 1px solid #ccd1d6; border-radius: 3px; background: #fff; cursor: pointer; white-space: nowrap; } button:hover { background: #f0f2f4; } button.danger { color: #b42318; border-color: #f0b4ae; } button.danger:hover { background: #fdf1f0; } button:disabled { opacity: .5; cursor: not-allowed; } /* ── 第二段:表格 ─────────────────────── */ /* 列多了横向滚动,不要压缩列宽把字挤成两行 */ .table-wrap { overflow-x: auto; background: #fff; border: 1px solid #e1e4e8; border-radius: 4px; } table { width: 100%; border-collapse: collapse; } th, td { padding: 8px 10px; text-align: left; border-bottom: 1px solid #eef0f2; white-space: nowrap; } th { background: #fafbfc; font-weight: 600; color: #444; } tbody tr:hover { background: #f7f9fb; } .col-check { width: 36px; } /* 解析失败的行标黄,提示需要人工补 */ tr.row-warn { background: #fffbe6; } tr.row-warn:hover { background: #fff6d0; } /* PDD 链接未填写,标红提醒 */ .missing { color: #b42318; font-weight: 600; } /* 空状态:文案要分情况,不要都写"暂无数据" */ tr.empty td { text-align: center; color: #888; padding: 40px 10px; white-space: normal; } tr.empty small { color: #aaa; } .hint { color: #777; font-size: 13px; margin: 12px 2px; } /* PDD 批量导入结果:统计、下一步和失败明细放在同一区域, 采购员不需要从底部状态条猜本批数据发生了什么。 */ .import-result { margin-bottom: 12px; padding: 12px; background: #fff; border: 1px solid #c9d8ee; border-left: 4px solid #1f6feb; border-radius: 4px; } .import-result-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; } .import-result h2 { margin: 0 0 6px; font-size: 16px; } .import-result p { margin: 0; } .import-result .hint { margin-top: 8px; } .import-success { color: #176b36; font-weight: 600; } .import-failure-table { max-height: 280px; margin-top: 10px; } .import-failure-table .import-raw, .import-failure-table .import-reason { min-width: 260px; max-width: 520px; white-space: normal; overflow-wrap: anywhere; } @media (max-width: 760px) { .import-result-head { align-items: flex-start; flex-direction: column; } } /* ── 第三段:底部状态条 ───────────────── */ .statusbar { position: fixed; left: 0; right: 0; bottom: 0; display: flex; align-items: center; justify-content: flex-end; background: #2b3440; color: #c8cdd4; padding: 10px 16px; font-size: 13px; gap: 12px; } .statusbar > span:first-child { flex: 0 1 auto; min-width: 0; text-align: right; } /* 链接和标题这类长内容截断显示,鼠标悬停看全文(title 属性)。 不截断的话一列能把表格撑到屏幕外面去。 */ .truncate { max-width: 280px; overflow: hidden; text-overflow: ellipsis; } /* 顺运宝列很多:限制高频短字段宽度,并让表头和横向滚动条在浏览数据时 始终容易找到。syb_id 仍保留在复选框和详情入口中,不作为独立列展示。 */ .syb-col-shop { width: 224px; max-width: 224px; } .syb-col-spec { width: 140px; max-width: 140px; } .syb-table-wrap { flex: 1 1 auto; min-height: 0; max-height: none; overflow: auto; scrollbar-gutter: stable; } .syb-table-wrap thead th { position: sticky; top: 0; z-index: 1; } @media (max-width: 760px) { body.syb-page-body { height: auto; min-height: 100dvh; display: block; overflow: auto; } body.syb-page-body > .page { display: block; } .syb-table-wrap { max-height: none; } } /* 蝦皮数据页商品名称列收窄,配合 .truncate 一起用(见工单 #214)。 用独立类设 max-width,不用 flex——.search-narrow 那套是给工具条用的, 表格单元格不是 flex 布局,flex 在这里不生效。放在 .truncate 之后, 同优先级下按 CSS 层叠顺序覆盖 .truncate 的 280px。 固定 120px 约等于原 18% 宽度的 45%,同时避免百分比列宽随浏览器 表格算法和窗口宽度大幅波动。蝦皮标题普遍很长(实测最长 60+ 字), 完整标题靠悬停的 title 属性看。 */ .col-title { width: 120px; max-width: 120px; } .col-shop { max-width: 150px; } .thumb-placeholder, .thumb-fallback { color: #6b7280; font-size: 12px; white-space: nowrap; } .detail-product-image { width: 120px; max-height: 120px; object-fit: contain; border: 1px solid #ccd1d6; border-radius: 4px; } /* 蝦皮 SKU 默认保持只读摘要,只在当前行展开编辑,避免规格多时整张表 被输入框淹没。表格自身负责横向滚动。 */ .sku-editor > summary { color: #1f5f99; cursor: pointer; } .sku-editor[open] > summary { margin-bottom: 8px; } .sku-edit-form { min-width: 360px; white-space: normal; } .sku-edit-grid { display: grid; grid-template-columns: repeat(2, minmax(140px, 1fr)); gap: 8px; margin-bottom: 8px; } .sku-edit-field { display: flex; flex-direction: column; gap: 4px; color: #555; } .sku-edit-field input { min-width: 0; width: 100%; } .sku-edit-advice { grid-column: 1 / -1; } .sku-edit-form small { display: block; color: #777; margin-bottom: 8px; } /* ── 弹窗 ─────────────────────────────── */ /* 用 hidden 属性控制显示隐藏,JS 只改这一个属性,不拼样式 */ .modal-backdrop[hidden] { display: none; } .modal-backdrop { position: fixed; inset: 0; background: rgba(20, 24, 30, .45); display: flex; align-items: flex-start; justify-content: center; padding: 40px 16px; overflow-y: auto; z-index: 10; } .modal { background: #fff; border-radius: 6px; box-shadow: 0 8px 32px rgba(0, 0, 0, .25); width: 100%; max-width: 720px; } .modal-narrow { max-width: 520px; } .modal-wide { max-width: 1160px; } .field-row { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 16px; } @media (max-width: 560px) { .field-row { grid-template-columns: 1fr; } } .modal-head { display: flex; align-items: center; padding: 12px 16px; border-bottom: 1px solid #e1e4e8; } .modal-head h2 { margin: 0; font-size: 16px; flex: 1; } .modal-x { border: none; background: none; font-size: 20px; line-height: 1; padding: 0 6px; color: #666; } .modal-body { padding: 16px; } .modal-body h3 { font-size: 14px; margin: 20px 0 8px; } .form-stack { display: grid; gap: 16px; } .purchase-confirm-row { display: grid; grid-template-columns: minmax(0, 1fr) 220px; align-items: center; gap: 16px; padding: 12px; border: 1px solid #d9dde3; border-radius: 6px; } .purchase-confirm-row[hidden] { display: none; } .purchase-confirm-row label { display: grid; gap: 6px; } .execution-mode-fieldset { display: grid; gap: 10px; margin: 0; padding: 12px; border: 1px solid #d0d5dd; border-radius: 6px; } .choice-row { display: flex; align-items: flex-start; gap: 8px; } .choice-row span { display: grid; gap: 2px; } .choice-row small { color: #667085; font-weight: 400; } .live-confirm-panel { display: grid; gap: 10px; padding: 12px; border: 2px solid #b42318; border-radius: 6px; background: #fff5f3; } .live-confirm-panel[hidden] { display: none; } .live-confirm-panel p { margin: 0; } @media (max-width: 760px) { .purchase-confirm-row { grid-template-columns: 1fr; } } .modal-foot { display: flex; align-items: center; gap: 8px; justify-content: flex-end; padding: 12px 16px; border-top: 1px solid #e1e4e8; background: #fafbfc; } /* 把左边的按钮和右边的按钮推开 */ .modal-foot .grow { flex: 1; } .button-link { display: inline-block; padding: 5px 12px; border: 1px solid #ccd1d6; border-radius: 3px; background: #fff; color: #24292f; text-decoration: none; white-space: nowrap; } .button-link:hover { background: #f0f2f4; } .button-link.disabled { color: #999; background: #f5f6f8; } .button-link.primary { background: #1f6feb; border-color: #1f6feb; color: #fff; } .button-link.primary:hover { background: #1a5fd0; } .sync-history-table { max-height: 460px; } .sync-history-table td { vertical-align: top; } .sync-history-table .sync-error { min-width: 220px; max-width: 360px; white-space: normal; word-break: break-word; } .sync-history-page { color: #555; white-space: nowrap; } .sync-history-foot { flex-wrap: wrap; } .sync-history-loading { margin: 0; white-space: nowrap; } .sync-history-load-error { margin-top: 0; } .sync-history-foot [aria-disabled="true"] { opacity: .5; pointer-events: none; } .sync-status { display: inline-block; min-width: 52px; padding: 2px 6px; border-radius: 10px; text-align: center; font-weight: 600; } .status-running { color: #7a4b00; background: #fff3cd; } .status-success { color: #176b36; background: #dff3e5; } .status-failed { color: #9b1c1c; background: #fde8e8; } .status-interrupted { color: #555; background: #eceff2; } button.primary { background: #1f6feb; border-color: #1f6feb; color: #fff; } button.primary:hover { background: #1a5fd0; } /* 详情弹窗里折叠展开的完整结果(result_data),见任务详情弹窗 */ .result-data { white-space: pre-wrap; word-break: break-all; background: #f5f6f8; border: 1px solid #e1e4e8; border-radius: 4px; padding: 10px; max-height: 300px; overflow-y: auto; font-size: 12px; } /* 弹窗里的字段表:左边标签,右边值 */ .detail { display: grid; grid-template-columns: 88px 1fr; gap: 8px 12px; margin: 0; align-items: baseline; } .detail dt { color: #555; } .detail dd { margin: 0; word-break: break-all; } .detail dd small { color: #999; margin-left: 6px; } /* 创建弹窗里的单个字段,标签在输入框上面 */ .field { display: flex; flex-direction: column; gap: 6px; } .field label { color: #555; } .field small { color: #777; } .field .field-error { color: #b42318; font-weight: 600; } input[type="text"], input[type="password"] { padding: 5px 8px; border: 1px solid #ccd1d6; border-radius: 3px; } input[type="date"] { width: 100%; padding: 5px 8px; border: 1px solid #ccd1d6; border-radius: 3px; background: #fff; } input.wide { width: 100%; } .row-actions { display: flex; align-items: center; gap: 6px; white-space: nowrap; } .row-actions form { margin: 0; } .status-pill { display: inline-block; padding: 2px 8px; border-radius: 999px; border: 1px solid currentColor; } .status-active { color: #1a7f37; background: #f0fff4; } .status-disabled { color: #777; background: #f3f4f6; } select { padding: 5px 8px; border: 1px solid #ccd1d6; border-radius: 3px; background: #fff; } /* ── 分页控件 ─────────────────────────── */ /* 见 docs/admin/05-ui-specification.md §3.2。全项目第一个分页页面 (蝦皮数据页,工单 #43)定的样式,其余主列表复用。 控件本身是 ,不是按钮/JS,样式借用 button 的观感即可。 */ .pagination { display: flex; align-items: center; gap: 6px; margin-left: 0; flex: 0 0 auto; } .pagination a, .pagination span.disabled { padding: 4px 10px; border: 1px solid #ccd1d6; border-radius: 3px; text-decoration: none; font-size: 13px; } /* 窄窗口把状态和分页分成两行,并让底栏回到文档流。 这样无论状态文字换几行,都不会遮住表格最后一行。 */ @media (max-width: 760px) { body { padding-bottom: 0; } .statusbar { position: static; flex-wrap: wrap; gap: 6px 12px; } .statusbar > span:first-child { flex-basis: 100%; } .pagination { width: 100%; justify-content: flex-end; margin-left: 0; } } .pagination a { color: #1f6feb; background: #fff; } .pagination a:hover { background: #f0f2f4; } /* 首页/末页时对应按钮禁用:用 而不是不可点的 (语义上不再是链接), 再配色区分,不能只靠颜色(见 05 §3.2)。 */ .pagination span.disabled { color: #aaa; background: #f5f6f8; cursor: not-allowed; } /* ── 顺运宝登录弹窗:验证码图片、表格缩略图 ─────── */ /* 工单 #46。缩略图故意限制到很小的尺寸——表格一行放不下大图, 要看清楚还是得点开顺运宝或蝦皮后台自己的图。 */ .captcha-img { display: block; height: 40px; border: 1px solid #ccd1d6; border-radius: 3px; margin-bottom: 6px; cursor: pointer; } .thumb { width: 32px; height: 32px; object-fit: cover; border-radius: 3px; vertical-align: middle; } .table-title-action, .thumb-action { border: 0; background: transparent; color: inherit; } .table-title-action { display: block; max-width: 100%; padding: 2px 0; overflow: hidden; color: #0969da; text-align: left; text-overflow: ellipsis; white-space: nowrap; } .table-title-action:hover { text-decoration: underline; } .thumb-action { padding: 3px; border-radius: 4px; line-height: 0; } .thumb-action:hover { background: #e8edf3; } .table-title-action:focus-visible, .thumb-action:focus-visible { outline: 2px solid #0969da; outline-offset: 2px; } .image-preview-modal { max-width: min(1120px, calc(100vw - 32px)); } .image-preview-body { display: flex; min-height: 160px; align-items: center; justify-content: center; } .image-preview-body img { display: block; max-width: 100%; max-height: calc(100vh - 190px); object-fit: contain; } .image-preview-body img[hidden] { display: none; } .image-preview-status { margin: 0; } /* ── 错误页 ───────────────────────────── */ .error-box { background: #fff; border: 1px solid #f0b4ae; border-left: 4px solid #b42318; border-radius: 4px; padding: 16px 20px; max-width: 720px; } .error-box h2 { margin-top: 0; color: #b42318; font-size: 16px; }