36564571fa
- fix(test): 同步 listInventory 签名(补 series/spec 参数) - test(backend): 新增 import_parse_test.go - chore(backend): 新增 cmd/test_xls 调试工具 - docs: UI 筛选组件设计规范、Gemini 项目配置 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1973 lines
84 KiB
HTML
1973 lines
84 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="zh-CN">
|
||
<head>
|
||
<meta charset="UTF-8" />
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||
<title>酒库管理系统 — 项目 TODO</title>
|
||
<style>
|
||
* { box-sizing: border-box; }
|
||
body {
|
||
margin: 0; padding: 0 0 80px;
|
||
font-family: "Noto Sans SC", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
||
color: #1f2933; background: #f7f9fb; line-height: 1.6;
|
||
-webkit-font-smoothing: antialiased;
|
||
}
|
||
header {
|
||
background: linear-gradient(135deg, #0A1F3B 0%, #15407D 100%);
|
||
color: #fff; padding: 36px 32px 28px;
|
||
}
|
||
header h1 { margin: 0 0 6px; font-size: 24px; font-weight: 700; }
|
||
header .header-meta { color: #ADC9EA; font-size: 13px; margin-top: 4px; }
|
||
.stats { display: flex; gap: 16px; margin-top: 14px; flex-wrap: wrap; }
|
||
.stat-pill {
|
||
background: rgba(255,255,255,0.12); border-radius: 20px;
|
||
padding: 4px 14px; font-size: 13px; color: #fff;
|
||
}
|
||
.stat-pill strong { font-size: 18px; font-weight: 700; margin-right: 2px; }
|
||
|
||
/* ── 筛选栏 ── */
|
||
.filter-bar {
|
||
position: sticky; top: 0; z-index: 10;
|
||
background: #fff; border-bottom: 1px solid #e4e9ef;
|
||
padding: 10px 28px; display: flex; flex-wrap: wrap; gap: 10px; align-items: center;
|
||
}
|
||
.filter-group { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
|
||
.filter-label { font-size: 12px; color: #7b8794; white-space: nowrap; }
|
||
.filter-chip, .filter-btn {
|
||
padding: 3px 12px; border-radius: 14px; border: 1.5px solid #d1d9e6;
|
||
background: #f7f9fb; color: #52606d; font-size: 12.5px; cursor: pointer;
|
||
transition: all .15s; white-space: nowrap;
|
||
}
|
||
.filter-chip:hover, .filter-btn:hover { border-color: #2563AC; color: #2563AC; }
|
||
.filter-chip.active, .filter-btn.active {
|
||
background: #2563AC; border-color: #2563AC; color: #fff;
|
||
}
|
||
.filter-sep { width: 1px; height: 20px; background: #e4e9ef; margin: 0 4px; }
|
||
|
||
/* ── 内容区 ── */
|
||
.wrap { max-width: 980px; margin: 0 auto; padding: 0 24px; }
|
||
.section-block { margin-bottom: 8px; }
|
||
.section-title {
|
||
font-size: 15px; font-weight: 700;
|
||
padding: 10px 14px; border-radius: 8px;
|
||
display: flex; align-items: center; gap: 8px; cursor: pointer;
|
||
margin: 20px 0 8px; user-select: none;
|
||
}
|
||
.section-title .s-count {
|
||
font-size: 12px; font-weight: 500; padding: 1px 8px; border-radius: 10px;
|
||
background: rgba(0,0,0,0.08);
|
||
}
|
||
.section-title .s-arrow { margin-left: auto; font-size: 12px; color: inherit; opacity: .6; }
|
||
|
||
/* 状态主题色 */
|
||
.st-open { background: #eff6ff; color: #1e40af; border-left: 4px solid #3b82f6; }
|
||
.st-doing { background: #fff7ed; color: #9a3412; border-left: 4px solid #f97316; }
|
||
.st-done { background: #fefce8; color: #854d0e; border-left: 4px solid #eab308; }
|
||
.st-accepted{ background: #f0fdf4; color: #166534; border-left: 4px solid #22c55e; }
|
||
|
||
ul.todo-list { list-style: none; margin: 0; padding: 0; }
|
||
.todo-card {
|
||
background: #fff; border: 1px solid #e4e9ef; border-radius: 10px;
|
||
padding: 14px 18px; margin-bottom: 10px;
|
||
border-left-width: 4px;
|
||
}
|
||
.todo-card.s-open { border-left-color: #3b82f6; }
|
||
.todo-card.s-doing { border-left-color: #f97316; }
|
||
.todo-card.s-done { border-left-color: #eab308; }
|
||
.todo-card.s-accepted { border-left-color: #22c55e; background: #f0fdf4; opacity: .8; }
|
||
|
||
.card-header { display: flex; align-items: flex-start; gap: 10px; flex-wrap: wrap; }
|
||
.item-title { font-weight: 600; font-size: 15px; flex: 1; }
|
||
.todo-card.s-accepted .item-title { text-decoration: line-through; color: #52606d; }
|
||
.card-badges { display: flex; gap: 6px; flex-wrap: wrap; align-items: center; }
|
||
.item-desc { font-size: 13.5px; color: #52606d; margin-top: 6px; }
|
||
.item-desc code {
|
||
background: #f0f2f5; padding: 1px 5px; border-radius: 3px;
|
||
font-family: "JetBrains Mono", monospace; font-size: 12.5px; color: #b91c1c;
|
||
}
|
||
.card-footer { display: flex; align-items: center; gap: 10px; margin-top: 10px; flex-wrap: wrap; }
|
||
.tag-row { display: flex; flex-wrap: wrap; gap: 6px; flex: 1; }
|
||
.item-meta { display: flex; flex-wrap: wrap; gap: 8px; font-size: 12px; color: #8aa3c4; }
|
||
.meta-date { white-space: nowrap; }
|
||
|
||
/* ── 标签样式 ── */
|
||
.tag { display: inline-block; padding: 2px 9px; border-radius: 10px; font-size: 12px; font-weight: 600; white-space: nowrap; }
|
||
.t-block { background: #fde8e8; color: #b91c1c; }
|
||
.t-high { background: #fef0d8; color: #9a6700; }
|
||
.t-low { background: #e6f0fb; color: #1d4ed8; }
|
||
.t-tag { background: #efefef; color: #555; cursor: pointer; transition: .12s; }
|
||
.t-tag:hover { background: #d9e8fb; color: #1d4ed8; }
|
||
.ver-badge { font-weight: 700; color: #1f7a44; }
|
||
.sub-progress-badge { background: #f0f4ff; color: #3730a3; }
|
||
|
||
/* 状态徽章 */
|
||
.status-badge { font-size: 11.5px; }
|
||
.s-open { background: #dbeafe; color: #1e40af; }
|
||
.s-doing { background: #ffedd5; color: #9a3412; }
|
||
.s-done { background: #fef9c3; color: #854d0e; }
|
||
.s-accepted { background: #dcfce7; color: #166534; }
|
||
|
||
/* ── 折叠 ── */
|
||
.section-list-wrap.collapsed { display: none; }
|
||
.empty-tip { font-size: 14px; color: #8aa3c4; margin: 8px 0 24px; }
|
||
.hidden { display: none !important; }
|
||
|
||
/* ── 改动等级徽章 ── */
|
||
.tier-1 { background: #ede9fe; color: #6d28d9; }
|
||
.tier-2 { background: #ccfbf1; color: #0f766e; }
|
||
.tier-3 { background: #f1f5f9; color: #475569; }
|
||
|
||
/* ── 拒绝按钮 & 拒绝原因 ── */
|
||
.reject-btn {
|
||
padding: 3px 10px; border-radius: 6px; border: 1.5px solid #dc2626;
|
||
background: #fff; color: #dc2626; font-size: 12px; font-weight: 600;
|
||
cursor: pointer; transition: all .15s; white-space: nowrap;
|
||
}
|
||
.reject-btn:hover { background: #dc2626; color: #fff; }
|
||
.reject-note {
|
||
margin: 8px 0 4px; padding: 7px 12px; border-radius: 6px;
|
||
background: #fef2f2; border: 1px solid #fecaca;
|
||
font-size: 13px; color: #b91c1c; line-height: 1.5;
|
||
}
|
||
.reject-date { color: #ef9999; font-size: 12px; }
|
||
|
||
/* ── 确认闸(方案/改动说明)── */
|
||
.gate-block { margin: 10px 0 4px; padding: 10px 14px; border-radius: 8px; font-size: 13px; }
|
||
.gate-pending { background: #fff7ed; border: 1px solid #fdba74; }
|
||
.gate-granted { background: #f0fdf4; border: 1px solid #bbf7d0; }
|
||
.gate-info { background: #f1f5f9; border: 1px solid #e2e8f0; }
|
||
.gate-head { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-bottom: 4px; }
|
||
.gate-badge { padding: 2px 9px; border-radius: 10px; font-size: 11.5px; font-weight: 700; white-space: nowrap; }
|
||
.gate-badge.pending { background: #f97316; color: #fff; }
|
||
.gate-badge.granted { background: #22c55e; color: #fff; }
|
||
.gate-badge.info { background: #94a3b8; color: #fff; }
|
||
.gate-kind { font-size: 12px; color: #52606d; }
|
||
.gate-date { font-size: 12px; color: #8aa3c4; margin-left: auto; }
|
||
.gate-note { color: #52606d; margin: 4px 0; white-space: pre-wrap; }
|
||
.gate-ref { font-size: 12.5px; color: #52606d; margin-top: 4px; }
|
||
.gate-note code, .gate-ref code {
|
||
background: #fff; padding: 1px 5px; border-radius: 3px;
|
||
font-family: "JetBrains Mono", monospace; font-size: 12px; color: #b91c1c;
|
||
}
|
||
.approve-btn {
|
||
margin-top: 8px; padding: 5px 14px; border-radius: 6px; border: none;
|
||
background: #16a34a; color: #fff; font-size: 12.5px; font-weight: 600; cursor: pointer;
|
||
transition: background .15s;
|
||
}
|
||
.approve-btn:hover { background: #15803d; }
|
||
.approve-cmd { margin-top: 10px; padding: 10px 12px; background: #1e293b; border-radius: 8px; }
|
||
.approve-cmd-label { font-size: 12px; color: #94a3b8; margin-bottom: 6px; }
|
||
.approve-cmd-code { font-family: "JetBrains Mono", monospace; font-size: 13px; color: #86efac; display: block; word-break: break-all; }
|
||
.approve-copy-btn { margin-top: 8px; padding: 4px 12px; border-radius: 6px; background: #334155; color: #e2e8f0; border: none; font-size: 12px; cursor: pointer; }
|
||
.approve-copy-btn:hover { background: #475569; }
|
||
.stat-pill.gate-stat { background: #f97316; }
|
||
|
||
/* ── 子任务区 ── */
|
||
.subtask-block {
|
||
margin-top: 12px; padding: 10px 14px;
|
||
background: #f8fafc; border: 1px solid #e4e9ef; border-radius: 8px;
|
||
}
|
||
.subtask-header {
|
||
display: flex; align-items: center; gap: 8px; margin-bottom: 8px; flex-wrap: wrap;
|
||
}
|
||
.subtask-label {
|
||
font-size: 11px; font-weight: 700; color: #7b8794;
|
||
text-transform: uppercase; letter-spacing: .6px;
|
||
}
|
||
.subtask-progress-text { font-size: 12px; color: #52606d; }
|
||
.subtask-progress-bar {
|
||
flex: 1; height: 5px; background: #e4e9ef; border-radius: 3px; min-width: 60px;
|
||
}
|
||
.subtask-progress-fill { height: 100%; background: #22c55e; border-radius: 3px; transition: width .4s; }
|
||
.subtask-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 5px; }
|
||
.subtask-item {
|
||
display: flex; align-items: center; gap: 7px; flex-wrap: wrap;
|
||
padding: 6px 10px; border-radius: 6px; font-size: 13px;
|
||
background: #fff; border: 1px solid #e8edf2;
|
||
}
|
||
.subtask-item.s-done { background: #f0fdf4; border-color: #bbf7d0; }
|
||
.subtask-item.s-doing { background: #fff7ed; border-color: #fed7aa; }
|
||
.subtask-item.s-accepted { background: #f0fdf4; border-color: #bbf7d0; opacity: .75; }
|
||
.sub-icon {
|
||
width: 16px; text-align: center; font-size: 13px; font-weight: 700; flex-shrink: 0;
|
||
}
|
||
.sub-icon.s-done { color: #16a34a; }
|
||
.sub-icon.s-doing { color: #ea580c; }
|
||
.sub-icon.s-open { color: #93aec8; }
|
||
.sub-icon.s-accepted { color: #16a34a; }
|
||
.sub-sid {
|
||
font-family: "JetBrains Mono", monospace; font-size: 11.5px;
|
||
color: #52606d; font-weight: 700; min-width: 32px; flex-shrink: 0;
|
||
}
|
||
.sub-title { flex: 1; font-size: 13px; min-width: 120px; }
|
||
.subtask-item.s-done .sub-title,
|
||
.subtask-item.s-accepted .sub-title { text-decoration: line-through; color: #52606d; }
|
||
.sub-badges { display: flex; gap: 4px; flex-wrap: wrap; align-items: center; }
|
||
.dep-section { display: flex; align-items: center; gap: 3px; }
|
||
.dep-label { font-size: 11px; color: #8aa3c4; margin-right: 2px; }
|
||
.dep-badge {
|
||
padding: 1px 6px; border-radius: 8px; font-size: 11px;
|
||
font-weight: 700; font-family: monospace; cursor: default;
|
||
}
|
||
.dep-done { background: #dcfce7; color: #166534; }
|
||
.dep-pending { background: #fee2e2; color: #b91c1c; }
|
||
|
||
/* ── 拒绝 Modal ── */
|
||
.modal-overlay {
|
||
position: fixed; inset: 0; background: rgba(0,0,0,.45);
|
||
display: flex; align-items: center; justify-content: center; z-index: 999;
|
||
}
|
||
.modal-box {
|
||
background: #fff; border-radius: 12px; padding: 28px 32px;
|
||
width: min(480px, 94vw); box-shadow: 0 20px 60px rgba(0,0,0,.2);
|
||
}
|
||
.modal-box h3 { margin: 0 0 4px; font-size: 17px; color: #1f2933; }
|
||
.modal-subtitle { font-size: 13px; color: #52606d; margin: 0 0 18px; }
|
||
.modal-label { font-size: 13px; font-weight: 600; color: #374151; margin-bottom: 6px; display: block; }
|
||
.modal-label .req { color: #dc2626; margin-left: 2px; }
|
||
.modal-textarea {
|
||
width: 100%; border: 1.5px solid #d1d9e6; border-radius: 8px;
|
||
padding: 10px 12px; font-size: 14px; font-family: inherit; resize: vertical;
|
||
min-height: 88px; outline: none; transition: border-color .15s;
|
||
}
|
||
.modal-textarea:focus { border-color: #dc2626; }
|
||
.modal-textarea.error { border-color: #dc2626; background: #fff8f8; }
|
||
.modal-err { font-size: 12px; color: #dc2626; margin-top: 4px; display: none; }
|
||
.modal-err.show { display: block; }
|
||
.modal-actions { display: flex; justify-content: flex-end; gap: 10px; margin-top: 20px; }
|
||
.modal-btn { padding: 8px 20px; border-radius: 8px; font-size: 14px; font-weight: 600; cursor: pointer; border: none; transition: all .15s; }
|
||
.modal-btn-cancel { background: #f3f4f6; color: #374151; }
|
||
.modal-btn-cancel:hover { background: #e5e7eb; }
|
||
.modal-btn-reject { background: #dc2626; color: #fff; }
|
||
.modal-btn-reject:hover { background: #b91c1c; }
|
||
.modal-cmd-wrap { margin-top: 16px; padding: 12px 14px; background: #1e293b; border-radius: 8px; }
|
||
.modal-cmd-label { font-size: 12px; color: #94a3b8; margin-bottom: 8px; }
|
||
.modal-cmd-code { font-family: "JetBrains Mono", "Fira Code", monospace; font-size: 13px; color: #86efac; word-break: break-all; display: block; }
|
||
.modal-copy-btn { margin-top: 10px; padding: 5px 14px; border-radius: 6px; background: #334155; color: #e2e8f0; border: none; font-size: 12px; cursor: pointer; }
|
||
.modal-copy-btn:hover { background: #475569; }
|
||
</style>
|
||
</head>
|
||
<body>
|
||
|
||
<header>
|
||
<div class="wrap">
|
||
<h1>酒库管理系统 — 项目 TODO</h1>
|
||
<div class="header-meta">生成于 2026-06-14 · 真相源 todo/todo.json</div>
|
||
<div class="stats">
|
||
<div class="stat-pill"><strong>47</strong>全部</div>
|
||
<div class="stat-pill"><strong>1</strong>待开始</div>
|
||
<div class="stat-pill"><strong>0</strong>开发中</div>
|
||
<div class="stat-pill"><strong>32</strong>待验收</div>
|
||
<div class="stat-pill"><strong>14</strong>已验收</div>
|
||
|
||
</div>
|
||
</div>
|
||
</header>
|
||
|
||
<div class="filter-bar" id="filterBar">
|
||
<div class="filter-group">
|
||
<span class="filter-label">重要度</span>
|
||
<button class="filter-btn active" data-filter-level="all">全部</button>
|
||
<button class="filter-btn" data-filter-level="high">高优</button>
|
||
<button class="filter-btn" data-filter-level="mid">重要</button>
|
||
<button class="filter-btn" data-filter-level="low">一般</button>
|
||
</div>
|
||
<div class="filter-sep"></div>
|
||
<div class="filter-group">
|
||
<span class="filter-label">状态</span>
|
||
<button class="filter-btn active" data-filter-status="all">全部</button>
|
||
<button class="filter-btn" data-filter-status="open">待开始</button>
|
||
<button class="filter-btn" data-filter-status="doing">开发中</button>
|
||
<button class="filter-btn" data-filter-status="done">待验收</button>
|
||
<button class="filter-btn" data-filter-status="accepted">已验收</button>
|
||
</div>
|
||
<div class="filter-sep"></div>
|
||
<div class="filter-group">
|
||
<span class="filter-label">改动等级</span>
|
||
<button class="filter-btn active" data-filter-tier="all">全部</button>
|
||
<button class="filter-btn" data-filter-tier="1">一级</button>
|
||
<button class="filter-btn" data-filter-tier="2">二级</button>
|
||
<button class="filter-btn" data-filter-tier="3">三级</button>
|
||
</div>
|
||
<div class="filter-sep"></div>
|
||
<div class="filter-group">
|
||
<span class="filter-label">平台 / 标签</span>
|
||
<button class="filter-chip" data-filter-tag="Android">Android</button><button class="filter-chip" data-filter-tag="CI/CD">CI/CD</button><button class="filter-chip" data-filter-tag="Web">Web</button><button class="filter-chip" data-filter-tag="Windows">Windows</button><button class="filter-chip" data-filter-tag="iOS">iOS</button><button class="filter-chip" data-filter-tag="mac">mac</button><button class="filter-chip" data-filter-tag="前端">前端</button><button class="filter-chip" data-filter-tag="后端">后端</button><button class="filter-chip" data-filter-tag="安全">安全</button><button class="filter-chip" data-filter-tag="数据库">数据库</button><button class="filter-chip" data-filter-tag="文档">文档</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="wrap">
|
||
|
||
|
||
<div class="section-block" id="section-open">
|
||
<div class="section-title st-open" data-toggle="open">
|
||
📋 待开始 <span class="s-count">1</span>
|
||
<span class="s-arrow">▴ 收起</span>
|
||
</div>
|
||
<div class="section-list-wrap " id="list-wrap-open">
|
||
<ul class="todo-list" id="list-open">
|
||
|
||
<li class="todo-card s-open"
|
||
data-id="51"
|
||
data-level="mid"
|
||
data-status="open"
|
||
data-tier="1"
|
||
data-tags="iOS,CI/CD">
|
||
<div class="card-header">
|
||
<span class="item-title">iOS 上线 TestFlight 正式分发</span>
|
||
<div class="card-badges">
|
||
<span class="tag status-badge s-open">待开始</span>
|
||
<span class="tag t-high">重要</span>
|
||
<span class="tag tier-1">一级</span>
|
||
|
||
|
||
</div>
|
||
</div>
|
||
|
||
<div class="item-desc">路线已定:CI 构建签名 IPA → 上传 TestFlight。前置(用户侧):Apple Developer Program($99/年)+App Store Connect 建 App(com.yanmei.jiu)+三件套凭证(.p12/.mobileprovision/.p8)+7个 Forgejo secrets+TestFlight 公开链接填 version.yaml。代码缺口(我方):compile-ios.sh 需在 flutter build ipa 前向 Release.xcconfig 注入 manual 签名(CODE_SIGN_STYLE/DEVELOPMENT_TEAM/PROVISIONING_PROFILE_SPECIFIER/CODE_SIGN_IDENTITY),否则 archive 阶段因 Automatic 无 team 必失败。详见计划 luminous-hugging-platypus.md。暂不调度。</div>
|
||
|
||
|
||
<div class="card-footer">
|
||
<div class="tag-row"><span class="tag t-tag" data-tag="iOS">iOS</span> <span class="tag t-tag" data-tag="CI/CD">CI/CD</span></div>
|
||
<div class="item-meta">
|
||
<span class="meta-date">🕐 2026-06-13</span>
|
||
|
||
</div>
|
||
</div>
|
||
</li>
|
||
</ul>
|
||
</div>
|
||
</div>
|
||
<div class="section-block" id="section-doing">
|
||
<div class="section-title st-doing" data-toggle="doing">
|
||
🔨 开发中 <span class="s-count">0</span>
|
||
<span class="s-arrow">▴ 收起</span>
|
||
</div>
|
||
<div class="section-list-wrap " id="list-wrap-doing">
|
||
<ul class="todo-list" id="list-doing">
|
||
<p class="empty-tip">暂无条目</p>
|
||
</ul>
|
||
</div>
|
||
</div>
|
||
<div class="section-block" id="section-done">
|
||
<div class="section-title st-done" data-toggle="done">
|
||
🔍 待验收 <span class="s-count">32</span>
|
||
<span class="s-arrow">▴ 收起</span>
|
||
</div>
|
||
<div class="section-list-wrap " id="list-wrap-done">
|
||
<ul class="todo-list" id="list-done">
|
||
|
||
<li class="todo-card s-done"
|
||
data-id="19"
|
||
data-level="high"
|
||
data-status="done"
|
||
data-tier=""
|
||
data-tags="前端,iOS,Android">
|
||
<div class="card-header">
|
||
<span class="item-title">入库单新建表单增加产地/保质期/储存方式,选填项默认折叠</span>
|
||
<div class="card-badges">
|
||
<span class="tag status-badge s-done">待验收</span>
|
||
<span class="tag t-block">高优 · 紧急</span>
|
||
|
||
|
||
<button class="reject-btn" data-id="19" data-title="入库单新建表单增加产地/保质期/储存方式,选填项默认折叠">拒绝验收</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="item-desc">新建入库单的商品行增加产地、保质期、储存方式三个字典下拉选项(已有字典数据)。必填项(商品名、数量等)正常显示;选填项默认隐藏,点击展开按钮后在下一行展示。</div>
|
||
|
||
|
||
<div class="card-footer">
|
||
<div class="tag-row"><span class="tag t-tag" data-tag="前端">前端</span> <span class="tag t-tag" data-tag="iOS">iOS</span> <span class="tag t-tag" data-tag="Android">Android</span></div>
|
||
<div class="item-meta">
|
||
<span class="meta-date">🕐 2026-06-08</span>
|
||
|
||
</div>
|
||
</div>
|
||
</li>
|
||
|
||
<li class="todo-card s-done"
|
||
data-id="29"
|
||
data-level="high"
|
||
data-status="done"
|
||
data-tier="2"
|
||
data-tags="后端">
|
||
<div class="card-header">
|
||
<span class="item-title">修复库存扣减 TOCTOU 竞态:SUM 预检纳入事务且在 FOR UPDATE 加锁后执行</span>
|
||
<div class="card-badges">
|
||
<span class="tag status-badge s-done">待验收</span>
|
||
<span class="tag t-block">高优 · 紧急</span>
|
||
<span class="tag tier-2">二级</span>
|
||
|
||
<button class="reject-btn" data-id="29" data-title="修复库存扣减 TOCTOU 竞态:SUM 预检纳入事务且在 FOR UPDATE 加锁后执行">拒绝验收</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="item-desc">stock.go ApproveStockOut 中库存总量 SUM 在 FOR UPDATE 加锁前执行,存在 check-then-act 窗口,高并发下可超扣。需将预检 SUM 也放入事务并在加锁后执行</div>
|
||
|
||
|
||
<div class="card-footer">
|
||
<div class="tag-row"><span class="tag t-tag" data-tag="后端">后端</span></div>
|
||
<div class="item-meta">
|
||
<span class="meta-date">🕐 2026-06-10</span>
|
||
|
||
</div>
|
||
</div>
|
||
</li>
|
||
|
||
<li class="todo-card s-done"
|
||
data-id="30"
|
||
data-level="high"
|
||
data-status="done"
|
||
data-tier="2"
|
||
data-tags="后端">
|
||
<div class="card-header">
|
||
<span class="item-title">统一 handler Update 改用白名单字段更新,防止 GORM Save() 跨租户覆盖</span>
|
||
<div class="card-badges">
|
||
<span class="tag status-badge s-done">待验收</span>
|
||
<span class="tag t-block">高优 · 紧急</span>
|
||
<span class="tag tier-2">二级</span>
|
||
|
||
<button class="reject-btn" data-id="30" data-title="统一 handler Update 改用白名单字段更新,防止 GORM Save() 跨租户覆盖">拒绝验收</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="item-desc">partner.go/product_attr.go/warehouse.go 等多处用 db.Save(&object) 更新全字段,若中间件被绕过会造成 shop_id 被覆盖。改为 db.Model(&x).Where("id=? AND shop_id=?").Updates(fields) 白名单模式</div>
|
||
|
||
|
||
<div class="card-footer">
|
||
<div class="tag-row"><span class="tag t-tag" data-tag="后端">后端</span></div>
|
||
<div class="item-meta">
|
||
<span class="meta-date">🕐 2026-06-10</span>
|
||
|
||
</div>
|
||
</div>
|
||
</li>
|
||
|
||
<li class="todo-card s-done"
|
||
data-id="31"
|
||
data-level="high"
|
||
data-status="done"
|
||
data-tier="2"
|
||
data-tags="后端">
|
||
<div class="card-header">
|
||
<span class="item-title">添加库存与财务流水定期对账检查,防止事务中断导致不平账</span>
|
||
<div class="card-badges">
|
||
<span class="tag status-badge s-done">待验收</span>
|
||
<span class="tag t-block">高优 · 紧急</span>
|
||
<span class="tag tier-2">二级</span>
|
||
|
||
<button class="reject-btn" data-id="31" data-title="添加库存与财务流水定期对账检查,防止事务中断导致不平账">拒绝验收</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="item-desc">出库审批若 DB 断连,Rollback 后库存正确但财务记录可能未落地。需添加对账脚本:SUM(InventoryLog.quantity by direction) == SUM(Inventories.quantity),及告警机制</div>
|
||
|
||
|
||
<div class="card-footer">
|
||
<div class="tag-row"><span class="tag t-tag" data-tag="后端">后端</span></div>
|
||
<div class="item-meta">
|
||
<span class="meta-date">🕐 2026-06-10</span>
|
||
|
||
</div>
|
||
</div>
|
||
</li>
|
||
|
||
<li class="todo-card s-done"
|
||
data-id="41"
|
||
data-level="high"
|
||
data-status="done"
|
||
data-tier="3"
|
||
data-tags="后端">
|
||
<div class="card-header">
|
||
<span class="item-title">CheckInventoryAvailability Scan错误未检查导致所有商品误报库存不足</span>
|
||
<div class="card-badges">
|
||
<span class="tag status-badge s-done">待验收</span>
|
||
<span class="tag t-block">高优 · 紧急</span>
|
||
<span class="tag tier-3">三级</span>
|
||
|
||
<button class="reject-btn" data-id="41" data-title="CheckInventoryAvailability Scan错误未检查导致所有商品误报库存不足">拒绝验收</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="item-desc">service/stock.go:312 DB查询失败时sums为空,所有商品have=0,合法出库审核被全部拒绝</div>
|
||
|
||
|
||
<div class="card-footer">
|
||
<div class="tag-row"><span class="tag t-tag" data-tag="后端">后端</span></div>
|
||
<div class="item-meta">
|
||
<span class="meta-date">🕐 2026-06-11</span>
|
||
|
||
</div>
|
||
</div>
|
||
</li>
|
||
|
||
<li class="todo-card s-done"
|
||
data-id="49"
|
||
data-level="high"
|
||
data-status="done"
|
||
data-tier="2"
|
||
data-tags="CI/CD">
|
||
<div class="card-header">
|
||
<span class="item-title">修复 act_runner→Forgejo 任务状态上报丢失导致构建成功却标失败</span>
|
||
<div class="card-badges">
|
||
<span class="tag status-badge s-done">待验收</span>
|
||
<span class="tag t-block">高优 · 紧急</span>
|
||
<span class="tag tier-2">二级</span>
|
||
|
||
<button class="reject-btn" data-id="49" data-title="修复 act_runner→Forgejo 任务状态上报丢失导致构建成功却标失败">拒绝验收</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="item-desc">v1.0.27~v1.0.32 连续 6 个版本 CI 全部'失败',但日志显示所有 job 实际构建成功(🏁 Job succeeded),是 act_runner 最终状态上报丢失(Failed to write EOT),被 Forgejo 僵尸任务清理标为 failure。生产环境因此停留在 v1.0.26(6/8)。需排查 relay/GOAWAY race 或升级 act_runner/Forgejo。</div>
|
||
|
||
|
||
<div class="card-footer">
|
||
<div class="tag-row"><span class="tag t-tag" data-tag="CI/CD">CI/CD</span></div>
|
||
<div class="item-meta">
|
||
<span class="meta-date">🕐 2026-06-11</span>
|
||
|
||
</div>
|
||
</div>
|
||
</li>
|
||
|
||
<li class="todo-card s-done"
|
||
data-id="20"
|
||
data-level="mid"
|
||
data-status="done"
|
||
data-tier=""
|
||
data-tags="前端,iOS,Android">
|
||
<div class="card-header">
|
||
<span class="item-title">商品详情页重设计:展示所有字段,内容为公开页超集</span>
|
||
<div class="card-badges">
|
||
<span class="tag status-badge s-done">待验收</span>
|
||
<span class="tag t-high">重要</span>
|
||
|
||
|
||
<button class="reject-btn" data-id="20" data-title="商品详情页重设计:展示所有字段,内容为公开页超集">拒绝验收</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="item-desc">当前 product_detail_screen.dart 显示内容过于简单。需先做页面设计(UI 方案),内容要求是公开扫码页的超集:包含商品基本信息、参数(产地/保质期/储存方式)、价格、描述文档、图片、库存批次、入库记录等管理侧专属字段。另:商品公开链接展示为可点击超链接(点击可跳转公开扫码页)。</div>
|
||
|
||
|
||
<div class="card-footer">
|
||
<div class="tag-row"><span class="tag t-tag" data-tag="前端">前端</span> <span class="tag t-tag" data-tag="iOS">iOS</span> <span class="tag t-tag" data-tag="Android">Android</span></div>
|
||
<div class="item-meta">
|
||
<span class="meta-date">🕐 2026-06-08</span>
|
||
|
||
</div>
|
||
</div>
|
||
</li>
|
||
|
||
<li class="todo-card s-done"
|
||
data-id="27"
|
||
data-level="mid"
|
||
data-status="done"
|
||
data-tier=""
|
||
data-tags="Windows,mac">
|
||
<div class="card-header">
|
||
<span class="item-title">桌面客户端单实例限制,禁止同时运行多个进程</span>
|
||
<div class="card-badges">
|
||
<span class="tag status-badge s-done">待验收</span>
|
||
<span class="tag t-high">重要</span>
|
||
|
||
|
||
<button class="reject-btn" data-id="27" data-title="桌面客户端单实例限制,禁止同时运行多个进程">拒绝验收</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="item-desc">Windows 和 macOS 客户端启动时检测是否已有实例运行;若有,则激活已有窗口并退出新进程。Flutter desktop 可通过 window_single_instance 或 dart:io 锁文件实现。</div>
|
||
|
||
|
||
<div class="card-footer">
|
||
<div class="tag-row"><span class="tag t-tag" data-tag="Windows">Windows</span> <span class="tag t-tag" data-tag="mac">mac</span></div>
|
||
<div class="item-meta">
|
||
<span class="meta-date">🕐 2026-06-08</span>
|
||
|
||
</div>
|
||
</div>
|
||
</li>
|
||
|
||
<li class="todo-card s-done"
|
||
data-id="32"
|
||
data-level="mid"
|
||
data-status="done"
|
||
data-tier="2"
|
||
data-tags="后端,数据库">
|
||
<div class="card-header">
|
||
<span class="item-title">迁移 License 激活逻辑到 license_devices 表,废弃 licenses.device_id 字段</span>
|
||
<div class="card-badges">
|
||
<span class="tag status-badge s-done">待验收</span>
|
||
<span class="tag t-high">重要</span>
|
||
<span class="tag tier-2">二级</span>
|
||
|
||
<button class="reject-btn" data-id="32" data-title="迁移 License 激活逻辑到 license_devices 表,废弃 licenses.device_id 字段">拒绝验收</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="item-desc">licenses.device_id 已标记 deprecated 但激活/解绑逻辑仍在使用它,license_devices 表未被完整采用。需将 Activate/Deactivate 逻辑迁移至 license_devices,并写数据迁移脚本</div>
|
||
|
||
|
||
<div class="card-footer">
|
||
<div class="tag-row"><span class="tag t-tag" data-tag="后端">后端</span> <span class="tag t-tag" data-tag="数据库">数据库</span></div>
|
||
<div class="item-meta">
|
||
<span class="meta-date">🕐 2026-06-10</span>
|
||
|
||
</div>
|
||
</div>
|
||
</li>
|
||
|
||
<li class="todo-card s-done"
|
||
data-id="33"
|
||
data-level="mid"
|
||
data-status="done"
|
||
data-tier="2"
|
||
data-tags="后端">
|
||
<div class="card-header">
|
||
<span class="item-title">将 checkInventory 业务逻辑从 StockOutHandler 移到 Service 层</span>
|
||
<div class="card-badges">
|
||
<span class="tag status-badge s-done">待验收</span>
|
||
<span class="tag t-high">重要</span>
|
||
<span class="tag tier-2">二级</span>
|
||
|
||
<button class="reject-btn" data-id="33" data-title="将 checkInventory 业务逻辑从 StockOutHandler 移到 Service 层">拒绝验收</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="item-desc">checkInventory 实现在 handler/stock_out.go 中而非 service 层,导致单元测试困难、逻辑无法复用。应移至 StockService 或 InventoryService</div>
|
||
|
||
|
||
<div class="card-footer">
|
||
<div class="tag-row"><span class="tag t-tag" data-tag="后端">后端</span></div>
|
||
<div class="item-meta">
|
||
<span class="meta-date">🕐 2026-06-10</span>
|
||
|
||
</div>
|
||
</div>
|
||
</li>
|
||
|
||
<li class="todo-card s-done"
|
||
data-id="34"
|
||
data-level="mid"
|
||
data-status="done"
|
||
data-tier="2"
|
||
data-tags="后端">
|
||
<div class="card-header">
|
||
<span class="item-title">统一后端 API 错误响应格式为 {code, message},创建 util/response.go</span>
|
||
<div class="card-badges">
|
||
<span class="tag status-badge s-done">待验收</span>
|
||
<span class="tag t-high">重要</span>
|
||
<span class="tag tier-2">二级</span>
|
||
|
||
<button class="reject-btn" data-id="34" data-title="统一后端 API 错误响应格式为 {code, message},创建 util/response.go">拒绝验收</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="item-desc">各 handler 返回格式不一致:有的 {error: err.Error()},有的 {error: 硬编码字符串},有的直接 struct。前端解析困难。需定义 ErrorResponse struct 和 RespondError/RespondSuccess 辅助函数统一调用</div>
|
||
|
||
|
||
<div class="card-footer">
|
||
<div class="tag-row"><span class="tag t-tag" data-tag="后端">后端</span></div>
|
||
<div class="item-meta">
|
||
<span class="meta-date">🕐 2026-06-10</span>
|
||
|
||
</div>
|
||
</div>
|
||
</li>
|
||
|
||
<li class="todo-card s-done"
|
||
data-id="35"
|
||
data-level="mid"
|
||
data-status="done"
|
||
data-tier="3"
|
||
data-tags="后端">
|
||
<div class="card-header">
|
||
<span class="item-title">生产环境 CORS 强制校验:非 debug 模式禁止 Origin=*</span>
|
||
<div class="card-badges">
|
||
<span class="tag status-badge s-done">待验收</span>
|
||
<span class="tag t-high">重要</span>
|
||
<span class="tag tier-3">三级</span>
|
||
|
||
<button class="reject-btn" data-id="35" data-title="生产环境 CORS 强制校验:非 debug 模式禁止 Origin=*">拒绝验收</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="item-desc">config.go 默认 CORSOrigin="*",注释提示生产需改但无代码强制。应在 main.go 中添加:server.mode!=debug 且 CORSOrigin=="*" 时 log.Fatal 拒绝启动</div>
|
||
|
||
|
||
<div class="card-footer">
|
||
<div class="tag-row"><span class="tag t-tag" data-tag="后端">后端</span></div>
|
||
<div class="item-meta">
|
||
<span class="meta-date">🕐 2026-06-10</span>
|
||
|
||
</div>
|
||
</div>
|
||
</li>
|
||
|
||
<li class="todo-card s-done"
|
||
data-id="36"
|
||
data-level="mid"
|
||
data-status="done"
|
||
data-tier="3"
|
||
data-tags="后端">
|
||
<div class="card-header">
|
||
<span class="item-title">License 私钥未配置时改为 Fatal 而非静默跳过</span>
|
||
<div class="card-badges">
|
||
<span class="tag status-badge s-done">待验收</span>
|
||
<span class="tag t-high">重要</span>
|
||
<span class="tag tier-3">三级</span>
|
||
|
||
<button class="reject-btn" data-id="36" data-title="License 私钥未配置时改为 Fatal 而非静默跳过">拒绝验收</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="item-desc">license.go 中私钥未配置只打 log 并跳过,导致授权功能失效但程序正常运行。应改为 log.Fatal,确保运维人员知晓配置缺失</div>
|
||
|
||
|
||
<div class="card-footer">
|
||
<div class="tag-row"><span class="tag t-tag" data-tag="后端">后端</span></div>
|
||
<div class="item-meta">
|
||
<span class="meta-date">🕐 2026-06-10</span>
|
||
|
||
</div>
|
||
</div>
|
||
</li>
|
||
|
||
<li class="todo-card s-done"
|
||
data-id="37"
|
||
data-level="mid"
|
||
data-status="done"
|
||
data-tier="3"
|
||
data-tags="后端">
|
||
<div class="card-header">
|
||
<span class="item-title">统一各 handler 的 pageSize 上限校验,创建 util.ValidatePageSize()</span>
|
||
<div class="card-badges">
|
||
<span class="tag status-badge s-done">待验收</span>
|
||
<span class="tag t-high">重要</span>
|
||
<span class="tag tier-3">三级</span>
|
||
|
||
<button class="reject-btn" data-id="37" data-title="统一各 handler 的 pageSize 上限校验,创建 util.ValidatePageSize()">拒绝验收</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="item-desc">inventory.go 限制 pageSize 最大 500,其他 handler 无此限制,前端可传 pageSize=10000 打满 DB。创建 util.ValidatePageSize(n) 统一处理</div>
|
||
|
||
|
||
<div class="card-footer">
|
||
<div class="tag-row"><span class="tag t-tag" data-tag="后端">后端</span></div>
|
||
<div class="item-meta">
|
||
<span class="meta-date">🕐 2026-06-10</span>
|
||
|
||
</div>
|
||
</div>
|
||
</li>
|
||
|
||
<li class="todo-card s-done"
|
||
data-id="42"
|
||
data-level="mid"
|
||
data-status="done"
|
||
data-tier="3"
|
||
data-tags="后端">
|
||
<div class="card-header">
|
||
<span class="item-title">finance ListRecords 和 inventory Logs handler 缺 pageSize 上限</span>
|
||
<div class="card-badges">
|
||
<span class="tag status-badge s-done">待验收</span>
|
||
<span class="tag t-high">重要</span>
|
||
<span class="tag tier-3">三级</span>
|
||
|
||
<button class="reject-btn" data-id="42" data-title="finance ListRecords 和 inventory Logs handler 缺 pageSize 上限">拒绝验收</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="item-desc">finance.go:36 和 inventory.go:130 漏加 ValidatePageSize,#37 未覆盖这两个 handler,可被任意用户触发全表扫描</div>
|
||
|
||
|
||
<div class="card-footer">
|
||
<div class="tag-row"><span class="tag t-tag" data-tag="后端">后端</span></div>
|
||
<div class="item-meta">
|
||
<span class="meta-date">🕐 2026-06-11</span>
|
||
|
||
</div>
|
||
</div>
|
||
</li>
|
||
|
||
<li class="todo-card s-done"
|
||
data-id="43"
|
||
data-level="mid"
|
||
data-status="done"
|
||
data-tier="3"
|
||
data-tags="后端">
|
||
<div class="card-header">
|
||
<span class="item-title">ReconcileInventory 两条 Scan 错误未检查导致对账误判</span>
|
||
<div class="card-badges">
|
||
<span class="tag status-badge s-done">待验收</span>
|
||
<span class="tag t-high">重要</span>
|
||
<span class="tag tier-3">三级</span>
|
||
|
||
<button class="reject-btn" data-id="43" data-title="ReconcileInventory 两条 Scan 错误未检查导致对账误判">拒绝验收</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="item-desc">admin.go:122-123 invQuery/logQuery Scan错误被丢弃;invQuery失败→差异消失显示全部正常;logQuery失败→所有库存飘红为差异</div>
|
||
|
||
|
||
<div class="card-footer">
|
||
<div class="tag-row"><span class="tag t-tag" data-tag="后端">后端</span></div>
|
||
<div class="item-meta">
|
||
<span class="meta-date">🕐 2026-06-11</span>
|
||
|
||
</div>
|
||
</div>
|
||
</li>
|
||
|
||
<li class="todo-card s-done"
|
||
data-id="44"
|
||
data-level="mid"
|
||
data-status="done"
|
||
data-tier="3"
|
||
data-tags="后端">
|
||
<div class="card-header">
|
||
<span class="item-title">license Info 端点 device_count 统计错误且 ListDevices 错误被吞</span>
|
||
<div class="card-badges">
|
||
<span class="tag status-badge s-done">待验收</span>
|
||
<span class="tag t-high">重要</span>
|
||
<span class="tag tier-3">三级</span>
|
||
|
||
<button class="reject-btn" data-id="44" data-title="license Info 端点 device_count 统计错误且 ListDevices 错误被吞">拒绝验收</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="item-desc">handler/license.go:67-70 两个bug:1.devs,_ 错误被_吞掉DB故障时deviceCount=0;2.ListDevices(shopID)包含该店所有license设备而非当前license</div>
|
||
|
||
|
||
<div class="card-footer">
|
||
<div class="tag-row"><span class="tag t-tag" data-tag="后端">后端</span></div>
|
||
<div class="item-meta">
|
||
<span class="meta-date">🕐 2026-06-11</span>
|
||
|
||
</div>
|
||
</div>
|
||
</li>
|
||
|
||
<li class="todo-card s-done"
|
||
data-id="50"
|
||
data-level="mid"
|
||
data-status="done"
|
||
data-tier="3"
|
||
data-tags="后端,数据库">
|
||
<div class="card-header">
|
||
<span class="item-title">License model 的 license_key 索引超 InnoDB 3072 字节限制需改用前缀索引</span>
|
||
<div class="card-badges">
|
||
<span class="tag status-badge s-done">待验收</span>
|
||
<span class="tag t-high">重要</span>
|
||
<span class="tag tier-3">三级</span>
|
||
|
||
<button class="reject-btn" data-id="50" data-title="License model 的 license_key 索引超 InnoDB 3072 字节限制需改用前缀索引">拒绝验收</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="item-desc">license.go 的 LicenseKey 是 size:2048+uniqueIndex,utf8mb4 下 8192 字节超 MySQL InnoDB 索引上限,全新环境 AutoMigrate 必失败(线上已手动建 license_key(768) 前缀索引绕过)。需在 model 层固化:缩短列或 GORM 指定索引前缀长度,并同步 schema.sql。</div>
|
||
|
||
|
||
<div class="card-footer">
|
||
<div class="tag-row"><span class="tag t-tag" data-tag="后端">后端</span> <span class="tag t-tag" data-tag="数据库">数据库</span></div>
|
||
<div class="item-meta">
|
||
<span class="meta-date">🕐 2026-06-11</span>
|
||
|
||
</div>
|
||
</div>
|
||
</li>
|
||
|
||
<li class="todo-card s-done"
|
||
data-id="2"
|
||
data-level="low"
|
||
data-status="done"
|
||
data-tier=""
|
||
data-tags="Web">
|
||
<div class="card-header">
|
||
<span class="item-title">备案号为占位假值</span>
|
||
<div class="card-badges">
|
||
<span class="tag status-badge s-done">待验收</span>
|
||
<span class="tag t-low">一般 / 优化</span>
|
||
|
||
|
||
<button class="reject-btn" data-id="2" data-title="备案号为占位假值">拒绝验收</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="item-desc">现为沪ICP备2026000000号(全0),上线前必须替换真实备案号。</div>
|
||
|
||
|
||
<div class="card-footer">
|
||
<div class="tag-row"><span class="tag t-tag" data-tag="Web">Web</span></div>
|
||
<div class="item-meta">
|
||
<span class="meta-date">🕐 2026-06-08</span>
|
||
|
||
</div>
|
||
</div>
|
||
</li>
|
||
|
||
<li class="todo-card s-done"
|
||
data-id="21"
|
||
data-level="low"
|
||
data-status="done"
|
||
data-tier=""
|
||
data-tags="前端,后端,安全,文档">
|
||
<div class="card-header">
|
||
<span class="item-title">授权信息功能方案设计</span>
|
||
<div class="card-badges">
|
||
<span class="tag status-badge s-done">待验收</span>
|
||
<span class="tag t-low">一般 / 优化</span>
|
||
|
||
<span class="tag sub-progress-badge">8/8 子任务</span>
|
||
<button class="reject-btn" data-id="21" data-title="授权信息功能方案设计">拒绝验收</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="item-desc">完整方案设计,涵盖:界面设计与交互、前端实现方案、后端接口与数据模型、安全策略(防盗用、防滥用、授权校验)等。需产出设计文档后再进入开发阶段。</div>
|
||
|
||
<div class="subtask-block">
|
||
<div class="subtask-header">
|
||
<span class="subtask-label">子任务</span>
|
||
<span class="subtask-progress-text">8 / 8 完成</span>
|
||
<div class="subtask-progress-bar"><div class="subtask-progress-fill" style="width:100%"></div></div>
|
||
</div>
|
||
<ul class="subtask-list"><li class="subtask-item s-done" data-sid="21A">
|
||
<span class="sub-icon s-done">✓</span>
|
||
<span class="sub-sid">21A</span>
|
||
<span class="sub-title">后端:Ed25519 签发验签工具 + GenerateKey 修复</span>
|
||
<div class="sub-badges"><span class="tag tier-2">二级</span><span class="tag status-badge s-done">待验收</span></div>
|
||
</li>
|
||
<li class="subtask-item s-done" data-sid="21B">
|
||
<span class="sub-icon s-done">✓</span>
|
||
<span class="sub-sid">21B</span>
|
||
<span class="sub-title">数据库:license_devices 表 + licenses 表改造</span>
|
||
<div class="sub-badges"><span class="tag tier-2">二级</span><span class="tag status-badge s-done">待验收</span><span class="dep-section"><span class="dep-label">依赖:</span><span class="dep-badge dep-done" title="21A 已完成">21A</span></span></div>
|
||
</li>
|
||
<li class="subtask-item s-done" data-sid="21C">
|
||
<span class="sub-icon s-done">✓</span>
|
||
<span class="sub-sid">21C</span>
|
||
<span class="sub-title">后端:注册自动签发 30 天 trial license</span>
|
||
<div class="sub-badges"><span class="tag tier-2">二级</span><span class="tag status-badge s-done">待验收</span><span class="dep-section"><span class="dep-label">依赖:</span><span class="dep-badge dep-done" title="21A 已完成">21A</span></span></div>
|
||
</li>
|
||
<li class="subtask-item s-done" data-sid="21D">
|
||
<span class="sub-icon s-done">✓</span>
|
||
<span class="sub-sid">21D</span>
|
||
<span class="sub-title">后端:LicenseGuard 中间件 + JWT claim 扩展</span>
|
||
<div class="sub-badges"><span class="tag tier-2">二级</span><span class="tag status-badge s-done">待验收</span><span class="dep-section"><span class="dep-label">依赖:</span><span class="dep-badge dep-done" title="21B 已完成">21B</span><span class="dep-badge dep-done" title="21C 已完成">21C</span></span></div>
|
||
</li>
|
||
<li class="subtask-item s-done" data-sid="21E">
|
||
<span class="sub-icon s-done">✓</span>
|
||
<span class="sub-sid">21E</span>
|
||
<span class="sub-title">前端:license model/repository/device_id 采集</span>
|
||
<div class="sub-badges"><span class="tag tier-2">二级</span><span class="tag status-badge s-done">待验收</span><span class="dep-section"><span class="dep-label">依赖:</span><span class="dep-badge dep-done" title="21A 已完成">21A</span></span></div>
|
||
</li>
|
||
<li class="subtask-item s-done" data-sid="21F">
|
||
<span class="sub-icon s-done">✓</span>
|
||
<span class="sub-sid">21F</span>
|
||
<span class="sub-title">前端:激活与设备管理 UI(授权 Tab 重构)</span>
|
||
<div class="sub-badges"><span class="tag tier-2">二级</span><span class="tag status-badge s-done">待验收</span><span class="dep-section"><span class="dep-label">依赖:</span><span class="dep-badge dep-done" title="21D 已完成">21D</span><span class="dep-badge dep-done" title="21E 已完成">21E</span></span></div>
|
||
</li>
|
||
<li class="subtask-item s-done" data-sid="21G">
|
||
<span class="sub-icon s-done">✓</span>
|
||
<span class="sub-sid">21G</span>
|
||
<span class="sub-title">前端:gating 横幅 + 到期弹窗(app_shell 注入)</span>
|
||
<div class="sub-badges"><span class="tag tier-2">二级</span><span class="tag status-badge s-done">待验收</span><span class="dep-section"><span class="dep-label">依赖:</span><span class="dep-badge dep-done" title="21D 已完成">21D</span><span class="dep-badge dep-done" title="21E 已完成">21E</span></span></div>
|
||
</li>
|
||
<li class="subtask-item s-done" data-sid="21H">
|
||
<span class="sub-icon s-done">✓</span>
|
||
<span class="sub-sid">21H</span>
|
||
<span class="sub-title">端到端验证:phase 切换 + 签名拒绝 + 多设备上限</span>
|
||
<div class="sub-badges"><span class="tag tier-2">二级</span><span class="tag status-badge s-done">待验收</span><span class="dep-section"><span class="dep-label">依赖:</span><span class="dep-badge dep-done" title="21D 已完成">21D</span><span class="dep-badge dep-done" title="21F 已完成">21F</span><span class="dep-badge dep-done" title="21G 已完成">21G</span></span></div>
|
||
</li></ul>
|
||
</div>
|
||
<div class="card-footer">
|
||
<div class="tag-row"><span class="tag t-tag" data-tag="前端">前端</span> <span class="tag t-tag" data-tag="后端">后端</span> <span class="tag t-tag" data-tag="安全">安全</span> <span class="tag t-tag" data-tag="文档">文档</span></div>
|
||
<div class="item-meta">
|
||
<span class="meta-date">🕐 2026-06-08</span>
|
||
|
||
</div>
|
||
</div>
|
||
</li>
|
||
|
||
<li class="todo-card s-done"
|
||
data-id="22"
|
||
data-level="low"
|
||
data-status="done"
|
||
data-tier=""
|
||
data-tags="前端,Web,文档">
|
||
<div class="card-header">
|
||
<span class="item-title">关于我们页文档/更新日志链接更新,Web 新增版本历史页</span>
|
||
<div class="card-badges">
|
||
<span class="tag status-badge s-done">待验收</span>
|
||
<span class="tag t-low">一般 / 优化</span>
|
||
|
||
|
||
<button class="reject-btn" data-id="22" data-title="关于我们页文档/更新日志链接更新,Web 新增版本历史页">拒绝验收</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="item-desc">1. 客户端「关于我们」页面的「打开文档」「更新日志」链接指向需更新为正确地址。2. Web 营销站新增版本更新页面(/changelog 或 /download/),从 CHANGELOG.md 自动渲染,展示所有历史版本更新记录。</div>
|
||
|
||
|
||
<div class="card-footer">
|
||
<div class="tag-row"><span class="tag t-tag" data-tag="前端">前端</span> <span class="tag t-tag" data-tag="Web">Web</span> <span class="tag t-tag" data-tag="文档">文档</span></div>
|
||
<div class="item-meta">
|
||
<span class="meta-date">🕐 2026-06-08</span>
|
||
|
||
</div>
|
||
</div>
|
||
</li>
|
||
|
||
<li class="todo-card s-done"
|
||
data-id="23"
|
||
data-level="low"
|
||
data-status="done"
|
||
data-tier=""
|
||
data-tags="前端,Web">
|
||
<div class="card-header">
|
||
<span class="item-title">注册/联系入口「发邮件」升级为「加客服微信」并优化文案</span>
|
||
<div class="card-badges">
|
||
<span class="tag status-badge s-done">待验收</span>
|
||
<span class="tag t-low">一般 / 优化</span>
|
||
|
||
|
||
<button class="reject-btn" data-id="23" data-title="注册/联系入口「发邮件」升级为「加客服微信」并优化文案">拒绝验收</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="item-desc">当前注册成功页和网站联系支持入口引导用户「发邮件」,体验较弱。升级为扫码加客服微信,同时梳理相关文案措辞,使表达更专业、有亲和力。</div>
|
||
|
||
|
||
<div class="card-footer">
|
||
<div class="tag-row"><span class="tag t-tag" data-tag="前端">前端</span> <span class="tag t-tag" data-tag="Web">Web</span></div>
|
||
<div class="item-meta">
|
||
<span class="meta-date">🕐 2026-06-08</span>
|
||
|
||
</div>
|
||
</div>
|
||
</li>
|
||
|
||
<li class="todo-card s-done"
|
||
data-id="24"
|
||
data-level="low"
|
||
data-status="done"
|
||
data-tier=""
|
||
data-tags="Web,文档">
|
||
<div class="card-header">
|
||
<span class="item-title">服务条款与隐私政策页面</span>
|
||
<div class="card-badges">
|
||
<span class="tag status-badge s-done">待验收</span>
|
||
<span class="tag t-low">一般 / 优化</span>
|
||
|
||
|
||
<button class="reject-btn" data-id="24" data-title="服务条款与隐私政策页面">拒绝验收</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="item-desc">在营销网站(jiu.51yanmei.com)补充服务条款(Terms of Service)和隐私政策(Privacy Policy)页面,注册页底部添加链接。最低优先级,待其他功能稳定后处理。</div>
|
||
|
||
|
||
<div class="card-footer">
|
||
<div class="tag-row"><span class="tag t-tag" data-tag="Web">Web</span> <span class="tag t-tag" data-tag="文档">文档</span></div>
|
||
<div class="item-meta">
|
||
<span class="meta-date">🕐 2026-06-08</span>
|
||
|
||
</div>
|
||
</div>
|
||
</li>
|
||
|
||
<li class="todo-card s-done"
|
||
data-id="25"
|
||
data-level="low"
|
||
data-status="done"
|
||
data-tier=""
|
||
data-tags="前端,iOS,Android">
|
||
<div class="card-header">
|
||
<span class="item-title">移动端左侧抽屉菜单支持手势侧滑唤出</span>
|
||
<div class="card-badges">
|
||
<span class="tag status-badge s-done">待验收</span>
|
||
<span class="tag t-low">一般 / 优化</span>
|
||
|
||
|
||
<button class="reject-btn" data-id="25" data-title="移动端左侧抽屉菜单支持手势侧滑唤出">拒绝验收</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="item-desc">窄屏(手机)模式下,导航 Drawer 当前需点击汉堡按钮打开,需支持从屏幕左边缘向右滑动手势唤出菜单,符合移动端操作习惯。涉及 app_shell.dart 的 Drawer 配置。</div>
|
||
|
||
|
||
<div class="card-footer">
|
||
<div class="tag-row"><span class="tag t-tag" data-tag="前端">前端</span> <span class="tag t-tag" data-tag="iOS">iOS</span> <span class="tag t-tag" data-tag="Android">Android</span></div>
|
||
<div class="item-meta">
|
||
<span class="meta-date">🕐 2026-06-08</span>
|
||
|
||
</div>
|
||
</div>
|
||
</li>
|
||
|
||
<li class="todo-card s-done"
|
||
data-id="26"
|
||
data-level="low"
|
||
data-status="done"
|
||
data-tier=""
|
||
data-tags="前端,iOS,Android">
|
||
<div class="card-header">
|
||
<span class="item-title">入库单详情商品明细改为卡片式竖排布局</span>
|
||
<div class="card-badges">
|
||
<span class="tag status-badge s-done">待验收</span>
|
||
<span class="tag t-low">一般 / 优化</span>
|
||
|
||
|
||
<button class="reject-btn" data-id="26" data-title="入库单详情商品明细改为卡片式竖排布局">拒绝验收</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="item-desc">当前入库单详情的商品明细以横向表格展示,字段多时在窄屏上挤压严重。改为每件商品独占一个卡片区块,字段竖向排列(商品名、规格、数量、单价、产地、保质期等各占一行),信息层次清晰,移动端阅读体验更好。宽屏可保持表格,窄屏自动切换为卡片流(复用 MobileListCard 模式)。</div>
|
||
|
||
|
||
<div class="card-footer">
|
||
<div class="tag-row"><span class="tag t-tag" data-tag="前端">前端</span> <span class="tag t-tag" data-tag="iOS">iOS</span> <span class="tag t-tag" data-tag="Android">Android</span></div>
|
||
<div class="item-meta">
|
||
<span class="meta-date">🕐 2026-06-08</span>
|
||
|
||
</div>
|
||
</div>
|
||
</li>
|
||
|
||
<li class="todo-card s-done"
|
||
data-id="38"
|
||
data-level="low"
|
||
data-status="done"
|
||
data-tier="3"
|
||
data-tags="后端">
|
||
<div class="card-header">
|
||
<span class="item-title">将拼音回填从启动逻辑改为一次性迁移脚本</span>
|
||
<div class="card-badges">
|
||
<span class="tag status-badge s-done">待验收</span>
|
||
<span class="tag t-low">一般 / 优化</span>
|
||
<span class="tag tier-3">三级</span>
|
||
|
||
<button class="reject-btn" data-id="38" data-title="将拼音回填从启动逻辑改为一次性迁移脚本">拒绝验收</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="item-desc">main.go 每次启动执行 backfillPinyin 全表扫描,数据量大后拖慢启动。改为 go run ./cmd/migrate-pinyin 一次性迁移,后续新数据靠写入时自动生成</div>
|
||
|
||
|
||
<div class="card-footer">
|
||
<div class="tag-row"><span class="tag t-tag" data-tag="后端">后端</span></div>
|
||
<div class="item-meta">
|
||
<span class="meta-date">🕐 2026-06-10</span>
|
||
|
||
</div>
|
||
</div>
|
||
</li>
|
||
|
||
<li class="todo-card s-done"
|
||
data-id="39"
|
||
data-level="low"
|
||
data-status="done"
|
||
data-tier="3"
|
||
data-tags="后端">
|
||
<div class="card-header">
|
||
<span class="item-title">DB 连接池参数(MaxIdleConns/MaxOpenConns)改为可配置项</span>
|
||
<div class="card-badges">
|
||
<span class="tag status-badge s-done">待验收</span>
|
||
<span class="tag t-low">一般 / 优化</span>
|
||
<span class="tag tier-3">三级</span>
|
||
|
||
<button class="reject-btn" data-id="39" data-title="DB 连接池参数(MaxIdleConns/MaxOpenConns)改为可配置项">拒绝验收</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="item-desc">main.go 中硬编码 MaxIdleConns=10, MaxOpenConns=100,无法运维调优。改为读取 config(viper),并在文档中说明推荐值</div>
|
||
|
||
|
||
<div class="card-footer">
|
||
<div class="tag-row"><span class="tag t-tag" data-tag="后端">后端</span></div>
|
||
<div class="item-meta">
|
||
<span class="meta-date">🕐 2026-06-10</span>
|
||
|
||
</div>
|
||
</div>
|
||
</li>
|
||
|
||
<li class="todo-card s-done"
|
||
data-id="40"
|
||
data-level="low"
|
||
data-status="done"
|
||
data-tier="2"
|
||
data-tags="后端,数据库">
|
||
<div class="card-header">
|
||
<span class="item-title">明确 Inventories 反范式字段语义:是当前值还是入库快照</span>
|
||
<div class="card-badges">
|
||
<span class="tag status-badge s-done">待验收</span>
|
||
<span class="tag t-low">一般 / 优化</span>
|
||
<span class="tag tier-2">二级</span>
|
||
|
||
<button class="reject-btn" data-id="40" data-title="明确 Inventories 反范式字段语义:是当前值还是入库快照">拒绝验收</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="item-desc">inventories 表存了 product_name/warehouse_name/supplier_name 等,既可能是冗余(与 products JOIN 重复),也可能是历史快照(入库时锁定)。需明确语义并在 model 注释中说明;若是快照,应只在写入时固定,不随主表变化</div>
|
||
|
||
|
||
<div class="card-footer">
|
||
<div class="tag-row"><span class="tag t-tag" data-tag="后端">后端</span> <span class="tag t-tag" data-tag="数据库">数据库</span></div>
|
||
<div class="item-meta">
|
||
<span class="meta-date">🕐 2026-06-10</span>
|
||
|
||
</div>
|
||
</div>
|
||
</li>
|
||
|
||
<li class="todo-card s-done"
|
||
data-id="45"
|
||
data-level="low"
|
||
data-status="done"
|
||
data-tier="3"
|
||
data-tags="后端">
|
||
<div class="card-header">
|
||
<span class="item-title">license Activate 幂等路径 Update 错误未检查</span>
|
||
<div class="card-badges">
|
||
<span class="tag status-badge s-done">待验收</span>
|
||
<span class="tag t-low">一般 / 优化</span>
|
||
<span class="tag tier-3">三级</span>
|
||
|
||
<button class="reject-btn" data-id="45" data-title="license Activate 幂等路径 Update 错误未检查">拒绝验收</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="item-desc">service/license.go:70 s.db.Model(&existing).Update()失败时仍返回nil error,设备名未更新但调用方不知道</div>
|
||
|
||
|
||
<div class="card-footer">
|
||
<div class="tag-row"><span class="tag t-tag" data-tag="后端">后端</span></div>
|
||
<div class="item-meta">
|
||
<span class="meta-date">🕐 2026-06-11</span>
|
||
|
||
</div>
|
||
</div>
|
||
</li>
|
||
|
||
<li class="todo-card s-done"
|
||
data-id="46"
|
||
data-level="low"
|
||
data-status="done"
|
||
data-tier="3"
|
||
data-tags="后端">
|
||
<div class="card-header">
|
||
<span class="item-title">license Verify 加载 license 缺 shop_id 过滤(防御纵深)</span>
|
||
<div class="card-badges">
|
||
<span class="tag status-badge s-done">待验收</span>
|
||
<span class="tag t-low">一般 / 优化</span>
|
||
<span class="tag tier-3">三级</span>
|
||
|
||
<button class="reject-btn" data-id="46" data-title="license Verify 加载 license 缺 shop_id 过滤(防御纵深)">拒绝验收</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="item-desc">service/license.go:102 WHERE id=? AND is_active=1 应加 AND shop_id=? 防御数据异常时跨租户泄露</div>
|
||
|
||
|
||
<div class="card-footer">
|
||
<div class="tag-row"><span class="tag t-tag" data-tag="后端">后端</span></div>
|
||
<div class="item-meta">
|
||
<span class="meta-date">🕐 2026-06-11</span>
|
||
|
||
</div>
|
||
</div>
|
||
</li>
|
||
|
||
<li class="todo-card s-done"
|
||
data-id="47"
|
||
data-level="low"
|
||
data-status="done"
|
||
data-tier="2"
|
||
data-tags="后端">
|
||
<div class="card-header">
|
||
<span class="item-title">util/response.go 的 RespondError 等辅助函数从未被调用(#34 实际未完成)</span>
|
||
<div class="card-badges">
|
||
<span class="tag status-badge s-done">待验收</span>
|
||
<span class="tag t-low">一般 / 优化</span>
|
||
<span class="tag tier-2">二级</span>
|
||
|
||
<button class="reject-btn" data-id="47" data-title="util/response.go 的 RespondError 等辅助函数从未被调用(#34 实际未完成)">拒绝验收</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="item-desc">#34 只创建了util/response.go,没有将任何handler迁移过去,实际错误格式仍不统一,需完成迁移</div>
|
||
|
||
|
||
<div class="card-footer">
|
||
<div class="tag-row"><span class="tag t-tag" data-tag="后端">后端</span></div>
|
||
<div class="item-meta">
|
||
<span class="meta-date">🕐 2026-06-11</span>
|
||
|
||
</div>
|
||
</div>
|
||
</li>
|
||
|
||
<li class="todo-card s-done"
|
||
data-id="48"
|
||
data-level="low"
|
||
data-status="done"
|
||
data-tier="3"
|
||
data-tags="后端">
|
||
<div class="card-header">
|
||
<span class="item-title">清理 ErrDeviceMismatch 死代码(多设备迁移后残留)</span>
|
||
<div class="card-badges">
|
||
<span class="tag status-badge s-done">待验收</span>
|
||
<span class="tag t-low">一般 / 优化</span>
|
||
<span class="tag tier-3">三级</span>
|
||
|
||
<button class="reject-btn" data-id="48" data-title="清理 ErrDeviceMismatch 死代码(多设备迁移后残留)">拒绝验收</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="item-desc">service/license.go:24 ErrDeviceMismatch定义后从未返回,多设备迁移(#32)后已无单设备约束</div>
|
||
|
||
|
||
<div class="card-footer">
|
||
<div class="tag-row"><span class="tag t-tag" data-tag="后端">后端</span></div>
|
||
<div class="item-meta">
|
||
<span class="meta-date">🕐 2026-06-11</span>
|
||
|
||
</div>
|
||
</div>
|
||
</li>
|
||
</ul>
|
||
</div>
|
||
</div>
|
||
<div class="section-block" id="section-accepted">
|
||
<div class="section-title st-accepted" data-toggle="accepted">
|
||
✅ 已验收 <span class="s-count">14</span>
|
||
<span class="s-arrow">▾ 展开</span>
|
||
</div>
|
||
<div class="section-list-wrap collapsed" id="list-wrap-accepted">
|
||
<ul class="todo-list" id="list-accepted">
|
||
|
||
<li class="todo-card s-accepted"
|
||
data-id="13"
|
||
data-level="low"
|
||
data-status="accepted"
|
||
data-tier=""
|
||
data-tags="后端,iOS,Android">
|
||
<div class="card-header">
|
||
<span class="item-title">公开商品 API 补充 batch.quantity 和 product code/barcode</span>
|
||
<div class="card-badges">
|
||
<span class="tag status-badge s-accepted">已验收</span>
|
||
<span class="tag t-low">一般 / 优化</span>
|
||
|
||
|
||
|
||
</div>
|
||
</div>
|
||
|
||
<div class="item-desc">Flutter _AuthenticityCard 读取 batch['quantity'] 但后端 public.go batchData 未返回 quantity。另 product code/barcode 未暴露,参数卡无法显示商品编码。handler/public.go GetProduct</div>
|
||
|
||
|
||
<div class="card-footer">
|
||
<div class="tag-row"><span class="tag t-tag" data-tag="后端">后端</span> <span class="tag t-tag" data-tag="iOS">iOS</span> <span class="tag t-tag" data-tag="Android">Android</span></div>
|
||
<div class="item-meta">
|
||
<span class="meta-date">🕐 2026-06-08</span>
|
||
<span class="meta-date">✅ 验收 2026-06-08 · <span class="ver-badge">v1.0.25</span></span>
|
||
</div>
|
||
</div>
|
||
</li>
|
||
|
||
<li class="todo-card s-accepted"
|
||
data-id="6"
|
||
data-level="low"
|
||
data-status="accepted"
|
||
data-tier=""
|
||
data-tags="前端,Web">
|
||
<div class="card-header">
|
||
<span class="item-title">注册成功提示路径需核对</span>
|
||
<div class="card-badges">
|
||
<span class="tag status-badge s-accepted">已验收</span>
|
||
<span class="tag t-low">一般 / 优化</span>
|
||
|
||
|
||
|
||
</div>
|
||
</div>
|
||
|
||
<div class="item-desc">提示「忘记门店编码可在「系统设置→关于」查看」,需与实际客户端菜单路径一致。</div>
|
||
|
||
|
||
<div class="card-footer">
|
||
<div class="tag-row"><span class="tag t-tag" data-tag="前端">前端</span> <span class="tag t-tag" data-tag="Web">Web</span></div>
|
||
<div class="item-meta">
|
||
<span class="meta-date">🕐 2026-06-08</span>
|
||
<span class="meta-date">✅ 验收 2026-06-08 · <span class="ver-badge">v1.0.25</span></span>
|
||
</div>
|
||
</div>
|
||
</li>
|
||
|
||
<li class="todo-card s-accepted"
|
||
data-id="5"
|
||
data-level="low"
|
||
data-status="accepted"
|
||
data-tier=""
|
||
data-tags="前端,Web">
|
||
<div class="card-header">
|
||
<span class="item-title">页脚双份维护</span>
|
||
<div class="card-badges">
|
||
<span class="tag status-badge s-accepted">已验收</span>
|
||
<span class="tag t-low">一般 / 优化</span>
|
||
|
||
|
||
|
||
</div>
|
||
</div>
|
||
|
||
<div class="item-desc">njk 站点用 footer.njk 读变量,功能页把页脚硬编码各写一份,改一处不同步。根因同「两套模板」。</div>
|
||
|
||
|
||
<div class="card-footer">
|
||
<div class="tag-row"><span class="tag t-tag" data-tag="前端">前端</span> <span class="tag t-tag" data-tag="Web">Web</span></div>
|
||
<div class="item-meta">
|
||
<span class="meta-date">🕐 2026-06-08</span>
|
||
<span class="meta-date">✅ 验收 2026-06-08 · <span class="ver-badge">v1.0.25</span></span>
|
||
</div>
|
||
</div>
|
||
</li>
|
||
|
||
<li class="todo-card s-accepted"
|
||
data-id="3"
|
||
data-level="low"
|
||
data-status="accepted"
|
||
data-tier=""
|
||
data-tags="前端,Web">
|
||
<div class="card-header">
|
||
<span class="item-title">注册接口同源依赖问题</span>
|
||
<div class="card-badges">
|
||
<span class="tag status-badge s-accepted">已验收</span>
|
||
<span class="tag t-low">一般 / 优化</span>
|
||
|
||
|
||
|
||
</div>
|
||
</div>
|
||
|
||
<div class="item-desc">用 window.location.origin+'/api/v1/public/register',要求同源部署;分离部署时指错。需确认部署形态。</div>
|
||
|
||
|
||
<div class="card-footer">
|
||
<div class="tag-row"><span class="tag t-tag" data-tag="前端">前端</span> <span class="tag t-tag" data-tag="Web">Web</span></div>
|
||
<div class="item-meta">
|
||
<span class="meta-date">🕐 2026-06-08</span>
|
||
<span class="meta-date">✅ 验收 2026-06-08 · <span class="ver-badge">v1.0.25</span></span>
|
||
</div>
|
||
</div>
|
||
</li>
|
||
|
||
<li class="todo-card s-accepted"
|
||
data-id="18"
|
||
data-level="low"
|
||
data-status="accepted"
|
||
data-tier=""
|
||
data-tags="前端,iOS,Android">
|
||
<div class="card-header">
|
||
<span class="item-title">基础数据管理 UI 增加 4 个字典维护 Tab</span>
|
||
<div class="card-badges">
|
||
<span class="tag status-badge s-accepted">已验收</span>
|
||
<span class="tag t-low">一般 / 优化</span>
|
||
|
||
|
||
|
||
</div>
|
||
</div>
|
||
|
||
<div class="item-desc">products_screen.dart 基础数据管理页增加产地/保质期/储存方式/描述文档 4 个维护 Tab,复用现有 option 维护组件(增删改查);描述文档 Tab 含 title+content 多行输入</div>
|
||
|
||
|
||
<div class="card-footer">
|
||
<div class="tag-row"><span class="tag t-tag" data-tag="前端">前端</span> <span class="tag t-tag" data-tag="iOS">iOS</span> <span class="tag t-tag" data-tag="Android">Android</span></div>
|
||
<div class="item-meta">
|
||
<span class="meta-date">🕐 2026-06-08</span>
|
||
<span class="meta-date">✅ 验收 2026-06-08 · <span class="ver-badge">v1.0.24</span></span>
|
||
</div>
|
||
</div>
|
||
</li>
|
||
|
||
<li class="todo-card s-accepted"
|
||
data-id="14"
|
||
data-level="low"
|
||
data-status="accepted"
|
||
data-tier=""
|
||
data-tags="前端,iOS,Android">
|
||
<div class="card-header">
|
||
<span class="item-title">Footer「商品报错」「意见反馈」补充实际功能</span>
|
||
<div class="card-badges">
|
||
<span class="tag status-badge s-accepted">已验收</span>
|
||
<span class="tag t-low">一般 / 优化</span>
|
||
|
||
|
||
|
||
</div>
|
||
</div>
|
||
|
||
<div class="item-desc">public_product_screen.dart _Footer 底部两个链接无 url/action,点击静默。需设计方案:跳转反馈表单、弹出联系方式、或发邮件。line:1353-1355</div>
|
||
|
||
|
||
<div class="card-footer">
|
||
<div class="tag-row"><span class="tag t-tag" data-tag="前端">前端</span> <span class="tag t-tag" data-tag="iOS">iOS</span> <span class="tag t-tag" data-tag="Android">Android</span></div>
|
||
<div class="item-meta">
|
||
<span class="meta-date">🕐 2026-06-08</span>
|
||
<span class="meta-date">✅ 验收 2026-06-08 · <span class="ver-badge">v1.0.24</span></span>
|
||
</div>
|
||
</div>
|
||
</li>
|
||
|
||
<li class="todo-card s-accepted"
|
||
data-id="10"
|
||
data-level="mid"
|
||
data-status="accepted"
|
||
data-tier=""
|
||
data-tags="前端,后端,iOS,Android">
|
||
<div class="card-header">
|
||
<span class="item-title">公开商品 API 补充 sale_price 并在页面展示价格</span>
|
||
<div class="card-badges">
|
||
<span class="tag status-badge s-accepted">已验收</span>
|
||
<span class="tag t-high">重要</span>
|
||
|
||
|
||
|
||
</div>
|
||
</div>
|
||
|
||
<div class="item-desc">product model 有 sale_price 字段但 public.go GetProduct 未返回。需:1) 后端 batchData/productData 加 sale_price;2) Flutter _TitleBlock 或单独区块展示建议零售价。client: public_product_screen.dart, backend: handler/public.go</div>
|
||
|
||
|
||
<div class="card-footer">
|
||
<div class="tag-row"><span class="tag t-tag" data-tag="前端">前端</span> <span class="tag t-tag" data-tag="后端">后端</span> <span class="tag t-tag" data-tag="iOS">iOS</span> <span class="tag t-tag" data-tag="Android">Android</span></div>
|
||
<div class="item-meta">
|
||
<span class="meta-date">🕐 2026-06-08</span>
|
||
<span class="meta-date">✅ 验收 2026-06-08 · <span class="ver-badge">v1.0.23</span></span>
|
||
</div>
|
||
</div>
|
||
</li>
|
||
|
||
<li class="todo-card s-accepted"
|
||
data-id="9"
|
||
data-level="mid"
|
||
data-status="accepted"
|
||
data-tier=""
|
||
data-tags="前端,iOS,Android">
|
||
<div class="card-header">
|
||
<span class="item-title">设计并实现添加门店微信功能</span>
|
||
<div class="card-badges">
|
||
<span class="tag status-badge s-accepted">已验收</span>
|
||
<span class="tag t-high">重要</span>
|
||
|
||
|
||
|
||
</div>
|
||
</div>
|
||
|
||
<div class="item-desc">PublicProductScreen _ShopCard「添加门店微信」按钮 onPressed: () {} 为空。需设计方案:门店维护微信号字段 or 二维码图片,扫码页展示。涉及后端 shop model 扩展 + 前端展示。client/lib/screens/public/public_product_screen.dart:1255</div>
|
||
|
||
|
||
<div class="card-footer">
|
||
<div class="tag-row"><span class="tag t-tag" data-tag="前端">前端</span> <span class="tag t-tag" data-tag="iOS">iOS</span> <span class="tag t-tag" data-tag="Android">Android</span></div>
|
||
<div class="item-meta">
|
||
<span class="meta-date">🕐 2026-06-08</span>
|
||
<span class="meta-date">✅ 验收 2026-06-08 · <span class="ver-badge">v1.0.23</span></span>
|
||
</div>
|
||
</div>
|
||
</li>
|
||
|
||
<li class="todo-card s-accepted"
|
||
data-id="8"
|
||
data-level="mid"
|
||
data-status="accepted"
|
||
data-tier=""
|
||
data-tags="前端,iOS,Android">
|
||
<div class="card-header">
|
||
<span class="item-title">补全「查看本店其他商品」功能</span>
|
||
<div class="card-badges">
|
||
<span class="tag status-badge s-accepted">已验收</span>
|
||
<span class="tag t-high">重要</span>
|
||
|
||
|
||
|
||
</div>
|
||
</div>
|
||
|
||
<div class="item-desc">PublicProductScreen _ShopCard 底部按钮 onPressed: () {} 为空,需实现跳转至该门店商品列表页。client/lib/screens/public/public_product_screen.dart:1240</div>
|
||
|
||
|
||
<div class="card-footer">
|
||
<div class="tag-row"><span class="tag t-tag" data-tag="前端">前端</span> <span class="tag t-tag" data-tag="iOS">iOS</span> <span class="tag t-tag" data-tag="Android">Android</span></div>
|
||
<div class="item-meta">
|
||
<span class="meta-date">🕐 2026-06-08</span>
|
||
<span class="meta-date">✅ 验收 2026-06-08 · <span class="ver-badge">v1.0.23</span></span>
|
||
</div>
|
||
</div>
|
||
</li>
|
||
|
||
<li class="todo-card s-accepted"
|
||
data-id="17"
|
||
data-level="mid"
|
||
data-status="accepted"
|
||
data-tier=""
|
||
data-tags="前端,iOS,Android">
|
||
<div class="card-header">
|
||
<span class="item-title">入库表单关联 4 个商品属性字典</span>
|
||
<div class="card-badges">
|
||
<span class="tag status-badge s-accepted">已验收</span>
|
||
<span class="tag t-high">重要</span>
|
||
|
||
|
||
|
||
</div>
|
||
</div>
|
||
|
||
<div class="item-desc">stock_in_form_screen.dart 生产日期字段附近增加产地/保质期/储存方式/描述文档 4 个下拉选择(数据来自字典 List 接口);FindOrCreate/Update 接受并写入 4 个 id;前端新增对应 API client 方法</div>
|
||
|
||
|
||
<div class="card-footer">
|
||
<div class="tag-row"><span class="tag t-tag" data-tag="前端">前端</span> <span class="tag t-tag" data-tag="iOS">iOS</span> <span class="tag t-tag" data-tag="Android">Android</span></div>
|
||
<div class="item-meta">
|
||
<span class="meta-date">🕐 2026-06-08</span>
|
||
<span class="meta-date">✅ 验收 2026-06-08 · <span class="ver-badge">v1.0.23</span></span>
|
||
</div>
|
||
</div>
|
||
</li>
|
||
|
||
<li class="todo-card s-accepted"
|
||
data-id="16"
|
||
data-level="mid"
|
||
data-status="accepted"
|
||
data-tier=""
|
||
data-tags="后端,前端,iOS,Android">
|
||
<div class="card-header">
|
||
<span class="item-title">public.go 返回产地/保质期/储存/介绍,公开页去硬编码</span>
|
||
<div class="card-badges">
|
||
<span class="tag status-badge s-accepted">已验收</span>
|
||
<span class="tag t-high">重要</span>
|
||
|
||
|
||
|
||
</div>
|
||
</div>
|
||
|
||
<div class="item-desc">public.go GetProduct Preload 4 张新表 + 三级回退(描述文档→旧Description→通用中性兜底)+ 默认值常量(保质期/储存方式);Flutter public_product_screen.dart _ParamsCard/_DescriptionSection 改读真实数据,移除茅台 mock</div>
|
||
|
||
|
||
<div class="card-footer">
|
||
<div class="tag-row"><span class="tag t-tag" data-tag="后端">后端</span> <span class="tag t-tag" data-tag="前端">前端</span> <span class="tag t-tag" data-tag="iOS">iOS</span> <span class="tag t-tag" data-tag="Android">Android</span></div>
|
||
<div class="item-meta">
|
||
<span class="meta-date">🕐 2026-06-08</span>
|
||
<span class="meta-date">✅ 验收 2026-06-08 · <span class="ver-badge">v1.0.23</span></span>
|
||
</div>
|
||
</div>
|
||
</li>
|
||
|
||
<li class="todo-card s-accepted"
|
||
data-id="15"
|
||
data-level="mid"
|
||
data-status="accepted"
|
||
data-tier=""
|
||
data-tags="后端,数据库">
|
||
<div class="card-header">
|
||
<span class="item-title">4 张商品属性字典表 + 后端 CRUD</span>
|
||
<div class="card-badges">
|
||
<span class="tag status-badge s-accepted">已验收</span>
|
||
<span class="tag t-high">重要</span>
|
||
|
||
|
||
|
||
</div>
|
||
</div>
|
||
|
||
<div class="item-desc">新增 product_origin_options/product_shelf_life_options/product_storage_options/product_description_docs 4 张字典表(TenantBase+shop_id 隔离);Product 增加 4 个可空外键;AutoMigrate 注册;handler/product_attr.go CRUD(16 个方法);router.go 追加 4 组路由在 /product-options/ 下</div>
|
||
|
||
|
||
<div class="card-footer">
|
||
<div class="tag-row"><span class="tag t-tag" data-tag="后端">后端</span> <span class="tag t-tag" data-tag="数据库">数据库</span></div>
|
||
<div class="item-meta">
|
||
<span class="meta-date">🕐 2026-06-08</span>
|
||
<span class="meta-date">✅ 验收 2026-06-08 · <span class="ver-badge">v1.0.23</span></span>
|
||
</div>
|
||
</div>
|
||
</li>
|
||
|
||
<li class="todo-card s-accepted"
|
||
data-id="4"
|
||
data-level="low"
|
||
data-status="accepted"
|
||
data-tier=""
|
||
data-tags="前端,Web">
|
||
<div class="card-header">
|
||
<span class="item-title">scan.html / features 走 passthrough 不享模板</span>
|
||
<div class="card-badges">
|
||
<span class="tag status-badge s-accepted">已验收</span>
|
||
<span class="tag t-low">一般 / 优化</span>
|
||
|
||
|
||
|
||
</div>
|
||
</div>
|
||
|
||
<div class="item-desc">features/scan.html 直接复制,不经 njk 模板,页脚/备案/CSS 都得手动同步。建议长期改造为模板化。</div>
|
||
|
||
|
||
<div class="card-footer">
|
||
<div class="tag-row"><span class="tag t-tag" data-tag="前端">前端</span> <span class="tag t-tag" data-tag="Web">Web</span></div>
|
||
<div class="item-meta">
|
||
<span class="meta-date">🕐 2026-06-08</span>
|
||
<span class="meta-date">✅ 验收 2026-06-08 · <span class="ver-badge">v1.0.23</span></span>
|
||
</div>
|
||
</div>
|
||
</li>
|
||
|
||
<li class="todo-card s-accepted"
|
||
data-id="1"
|
||
data-level="mid"
|
||
data-status="accepted"
|
||
data-tier=""
|
||
data-tags="前端,Web">
|
||
<div class="card-header">
|
||
<span class="item-title">两套 CSS / 设计令牌并存</span>
|
||
<div class="card-badges">
|
||
<span class="tag status-badge s-accepted">已验收</span>
|
||
<span class="tag t-high">重要</span>
|
||
|
||
|
||
|
||
</div>
|
||
</div>
|
||
|
||
<div class="item-desc">主站 njk 用 color.css+style.css,功能页用 colors_and_type.css,导航/页脚各写一份,维护分裂。建议合并到一套,功能页走 base.njk 模板。</div>
|
||
|
||
|
||
<div class="card-footer">
|
||
<div class="tag-row"><span class="tag t-tag" data-tag="前端">前端</span> <span class="tag t-tag" data-tag="Web">Web</span></div>
|
||
<div class="item-meta">
|
||
<span class="meta-date">🕐 2026-06-08</span>
|
||
<span class="meta-date">✅ 验收 2026-06-08 · <span class="ver-badge">v1.0.23</span></span>
|
||
</div>
|
||
</div>
|
||
</li>
|
||
</ul>
|
||
</div>
|
||
</div>
|
||
|
||
</div>
|
||
|
||
<!-- 拒绝验收 Modal -->
|
||
<div id="reject-modal" class="modal-overlay" style="display:none">
|
||
<div class="modal-box">
|
||
<h3>拒绝验收</h3>
|
||
<p class="modal-subtitle" id="reject-modal-subtitle"></p>
|
||
<label class="modal-label">拒绝原因<span class="req">*</span></label>
|
||
<textarea class="modal-textarea" id="reject-reason-input" rows="3" placeholder="请说明具体问题,Claude 将以此为依据修复…"></textarea>
|
||
<div class="modal-err" id="reject-reason-err">请填写拒绝原因</div>
|
||
<div id="reject-cmd-wrap" class="modal-cmd-wrap" style="display:none">
|
||
<div class="modal-cmd-label">在终端运行以下命令:</div>
|
||
<code class="modal-cmd-code" id="reject-cmd-text"></code>
|
||
<button class="modal-copy-btn" id="reject-copy-btn">复制命令</button>
|
||
</div>
|
||
<div class="modal-actions">
|
||
<button class="modal-btn modal-btn-cancel" id="reject-cancel-btn">取消</button>
|
||
<button class="modal-btn modal-btn-reject" id="reject-confirm-btn">确认拒绝</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
(function() {
|
||
let curLevel = 'all';
|
||
let curStatus = 'all';
|
||
let curTier = 'all';
|
||
let curTags = new Set();
|
||
|
||
// 通过本地服务(http)打开时,按钮直接写库;用 file:// 直接打开则回退为「复制命令」
|
||
const SERVED = location.protocol === 'http:' || location.protocol === 'https:';
|
||
function postAction(url, payload) {
|
||
return fetch(url, {
|
||
method: 'POST',
|
||
headers: payload ? { 'Content-Type': 'application/json' } : undefined,
|
||
body: payload ? JSON.stringify(payload) : undefined,
|
||
}).then(r => r.json());
|
||
}
|
||
|
||
function applyFilter() {
|
||
document.querySelectorAll('.todo-card').forEach(card => {
|
||
const lvl = card.dataset.level;
|
||
const status = card.dataset.status;
|
||
const tier = card.dataset.tier;
|
||
const tags = card.dataset.tags ? card.dataset.tags.split(',') : [];
|
||
|
||
let show = true;
|
||
if (curLevel !== 'all' && lvl !== curLevel) show = false;
|
||
if (curStatus !== 'all' && status !== curStatus) show = false;
|
||
if (curTier !== 'all' && String(tier) !== curTier) show = false;
|
||
if (curTags.size > 0 && ![...curTags].some(t => tags.includes(t))) show = false;
|
||
|
||
card.classList.toggle('hidden', !show);
|
||
});
|
||
|
||
if (curStatus !== 'all') {
|
||
const wrap = document.getElementById('list-wrap-' + curStatus);
|
||
if (wrap) {
|
||
wrap.classList.remove('collapsed');
|
||
const title = document.querySelector('[data-toggle="' + curStatus + '"] .s-arrow');
|
||
if (title) title.textContent = '▴ 收起';
|
||
}
|
||
}
|
||
}
|
||
|
||
document.querySelectorAll('[data-filter-level]').forEach(btn => {
|
||
btn.addEventListener('click', () => {
|
||
curLevel = btn.dataset.filterLevel;
|
||
document.querySelectorAll('[data-filter-level]').forEach(b => b.classList.toggle('active', b === btn));
|
||
applyFilter();
|
||
});
|
||
});
|
||
|
||
document.querySelectorAll('[data-filter-tier]').forEach(btn => {
|
||
btn.addEventListener('click', () => {
|
||
curTier = btn.dataset.filterTier;
|
||
document.querySelectorAll('[data-filter-tier]').forEach(b => b.classList.toggle('active', b === btn));
|
||
applyFilter();
|
||
});
|
||
});
|
||
|
||
document.querySelectorAll('[data-filter-status]').forEach(btn => {
|
||
btn.addEventListener('click', () => {
|
||
curStatus = btn.dataset.filterStatus;
|
||
document.querySelectorAll('[data-filter-status]').forEach(b => b.classList.toggle('active', b === btn));
|
||
applyFilter();
|
||
});
|
||
});
|
||
|
||
document.querySelectorAll('[data-filter-tag]').forEach(btn => {
|
||
btn.addEventListener('click', () => {
|
||
const t = btn.dataset.filterTag;
|
||
if (curTags.has(t)) { curTags.delete(t); btn.classList.remove('active'); }
|
||
else { curTags.add(t); btn.classList.add('active'); }
|
||
applyFilter();
|
||
});
|
||
});
|
||
|
||
document.querySelectorAll('.t-tag').forEach(tag => {
|
||
tag.addEventListener('click', e => {
|
||
e.stopPropagation();
|
||
const t = tag.dataset.tag;
|
||
const chip = document.querySelector('[data-filter-tag="'+t+'"]');
|
||
if (chip) chip.click();
|
||
});
|
||
});
|
||
|
||
document.querySelectorAll('.section-title[data-toggle]').forEach(title => {
|
||
title.addEventListener('click', () => {
|
||
const st = title.dataset.toggle;
|
||
const wrap = document.getElementById('list-wrap-' + st);
|
||
const arrow = title.querySelector('.s-arrow');
|
||
if (!wrap) return;
|
||
const collapsed = wrap.classList.toggle('collapsed');
|
||
if (arrow) arrow.textContent = collapsed ? '▾ 展开' : '▴ 收起';
|
||
});
|
||
});
|
||
|
||
// ── 拒绝验收 Modal ──
|
||
const modal = document.getElementById('reject-modal');
|
||
const reasonInput = document.getElementById('reject-reason-input');
|
||
const reasonErr = document.getElementById('reject-reason-err');
|
||
const cmdWrap = document.getElementById('reject-cmd-wrap');
|
||
const cmdText = document.getElementById('reject-cmd-text');
|
||
const copyBtn = document.getElementById('reject-copy-btn');
|
||
let rejectId = null;
|
||
|
||
document.querySelectorAll('.reject-btn').forEach(btn => {
|
||
btn.addEventListener('click', e => {
|
||
e.stopPropagation();
|
||
rejectId = btn.dataset.id;
|
||
document.getElementById('reject-modal-subtitle').textContent = '#' + rejectId + ' ' + btn.dataset.title;
|
||
reasonInput.value = '';
|
||
reasonInput.classList.remove('error');
|
||
reasonErr.classList.remove('show');
|
||
cmdWrap.style.display = 'none';
|
||
document.getElementById('reject-confirm-btn').style.display = '';
|
||
copyBtn.textContent = '复制命令';
|
||
modal.style.display = 'flex';
|
||
setTimeout(() => reasonInput.focus(), 80);
|
||
});
|
||
});
|
||
|
||
document.getElementById('reject-cancel-btn').addEventListener('click', () => {
|
||
modal.style.display = 'none';
|
||
});
|
||
|
||
modal.addEventListener('click', e => {
|
||
if (e.target === modal) modal.style.display = 'none';
|
||
});
|
||
|
||
document.getElementById('reject-confirm-btn').addEventListener('click', () => {
|
||
const reason = reasonInput.value.trim();
|
||
if (!reason) {
|
||
reasonInput.classList.add('error');
|
||
reasonErr.classList.add('show');
|
||
reasonInput.focus();
|
||
return;
|
||
}
|
||
if (SERVED) {
|
||
postAction('/api/reject/' + rejectId, { reason })
|
||
.then(d => { if (d.ok) { location.reload(); } else { alert('拒绝失败:' + (d.error || '')); } })
|
||
.catch(() => alert('请求失败,确认本地服务是否在运行'));
|
||
return;
|
||
}
|
||
const cmd = '/todo reject ' + rejectId + ' ' + JSON.stringify(reason);
|
||
cmdText.textContent = cmd;
|
||
cmdWrap.style.display = 'block';
|
||
document.getElementById('reject-confirm-btn').style.display = 'none';
|
||
});
|
||
|
||
reasonInput.addEventListener('input', () => {
|
||
if (reasonInput.value.trim()) {
|
||
reasonInput.classList.remove('error');
|
||
reasonErr.classList.remove('show');
|
||
}
|
||
});
|
||
|
||
copyBtn.addEventListener('click', () => {
|
||
navigator.clipboard.writeText(cmdText.textContent).then(() => {
|
||
copyBtn.textContent = '已复制 ✓';
|
||
});
|
||
});
|
||
|
||
// ── 确认闸:服务模式直接写库;file:// 模式回退为展开命令 ──
|
||
document.querySelectorAll('.approve-btn').forEach(btn => {
|
||
btn.addEventListener('click', e => {
|
||
e.stopPropagation();
|
||
const id = btn.dataset.id;
|
||
if (SERVED) {
|
||
btn.disabled = true; btn.textContent = '确认中…';
|
||
postAction('/api/approve/' + id)
|
||
.then(d => {
|
||
if (d.ok) { location.reload(); }
|
||
else { btn.disabled = false; btn.textContent = '确认执行 ✓'; alert('确认失败:' + (d.error || '')); }
|
||
})
|
||
.catch(() => { btn.disabled = false; btn.textContent = '确认执行 ✓'; alert('请求失败,确认本地服务是否在运行'); });
|
||
} else {
|
||
const box = document.getElementById('approve-cmd-' + id);
|
||
if (box) box.style.display = box.style.display === 'none' ? 'block' : 'none';
|
||
}
|
||
});
|
||
});
|
||
document.querySelectorAll('.approve-copy-btn').forEach(btn => {
|
||
btn.addEventListener('click', e => {
|
||
e.stopPropagation();
|
||
navigator.clipboard.writeText(btn.dataset.cmd).then(() => { btn.textContent = '已复制 ✓'; });
|
||
});
|
||
});
|
||
})();
|
||
</script>
|
||
</body>
|
||
</html> |