@@ -131,6 +131,37 @@ ul.todo-list { list-style: none; margin: 0; padding: 0; }
}
. reject-date { color : #ef9999 ; font-size : 12 px ; }
/* ── 确认闸(方案/改动说明)── */
. gate-block { margin : 10 px 0 4 px ; padding : 10 px 14 px ; border-radius : 8 px ; font-size : 13 px ; }
. gate-pending { background : #fff7ed ; border : 1 px solid #fdba74 ; }
. gate-granted { background : #f0fdf4 ; border : 1 px solid #bbf7d0 ; }
. gate-info { background : #f1f5f9 ; border : 1 px solid #e2e8f0 ; }
. gate-head { display : flex ; align-items : center ; gap : 8 px ; flex-wrap : wrap ; margin-bottom : 4 px ; }
. gate-badge { padding : 2 px 9 px ; border-radius : 10 px ; font-size : 11.5 px ; 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 : 12 px ; color : #52606d ; }
. gate-date { font-size : 12 px ; color : #8aa3c4 ; margin-left : auto ; }
. gate-note { color : #52606d ; margin : 4 px 0 ; white-space : pre-wrap ; }
. gate-ref { font-size : 12.5 px ; color : #52606d ; margin-top : 4 px ; }
. gate-note code , . gate-ref code {
background : #fff ; padding : 1 px 5 px ; border-radius : 3 px ;
font-family : "JetBrains Mono" , monospace ; font-size : 12 px ; color : #b91c1c ;
}
. approve-btn {
margin-top : 8 px ; padding : 5 px 14 px ; border-radius : 6 px ; border : none ;
background : #16a34a ; color : #fff ; font-size : 12.5 px ; font-weight : 600 ; cursor : pointer ;
transition : background .15 s ;
}
. approve-btn : hover { background : #15803d ; }
. approve-cmd { margin-top : 10 px ; padding : 10 px 12 px ; background : #1e293b ; border-radius : 8 px ; }
. approve-cmd-label { font-size : 12 px ; color : #94a3b8 ; margin-bottom : 6 px ; }
. approve-cmd-code { font-family : "JetBrains Mono" , monospace ; font-size : 13 px ; color : #86efac ; display : block ; word-break : break-all ; }
. approve-copy-btn { margin-top : 8 px ; padding : 4 px 12 px ; border-radius : 6 px ; background : #334155 ; color : #e2e8f0 ; border : none ; font-size : 12 px ; cursor : pointer ; }
. approve-copy-btn : hover { background : #475569 ; }
. stat-pill . gate-stat { background : #f97316 ; }
/* ── 子任务区 ── */
. subtask-block {
margin-top : 12 px ; padding : 10 px 14 px ;
@@ -221,13 +252,14 @@ ul.todo-list { list-style: none; margin: 0; padding: 0; }
< header >
< div class = "wrap" >
< h1 > 酒库管理系统 — 项目 TODO< / h1 >
< div class = "header-meta" > 生成于 2026-06-11 · 真相源 todo/todo.json< / div >
< div class = "header-meta" > 生成于 2026-06-14 · 真相源 todo/todo.json< / div >
< div class = "stats" >
< div class = "stat-pill" > < strong > 46 < / strong > 全部< / div >
< 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 > 31 < / strong > 待验收< / div >
< div class = "stat-pill" > < strong > 32 < / strong > 待验收< / div >
< div class = "stat-pill" > < strong > 14< / strong > 已验收< / div >
< / div >
< / div >
< / header >
@@ -276,28 +308,29 @@ ul.todo-list { list-style: none; margin: 0; padding: 0; }
< ul class = "todo-list" id = "list-open" >
< li class = "todo-card s-open"
data-id = "50 "
data-id = "51 "
data-level = "mid"
data-status = "open"
data-tier = "3 "
data-tags = "后端,数据库 " >
data-tier = "1 "
data-tags = "iOS,CI/CD " >
< div class = "card-header" >
< span class = "item-title" > License model 的 license_key 索引超 InnoDB 3072 字节限制需改用前缀索引 < / span >
< 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-3 " > 三 级< / span >
< span class = "tag tier-1 " > 一 级< / span >
< / 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 = "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 = "后端 " > 后端 < / span > < span class = "tag t-tag" data-tag = "数据库 " > 数据库 < / span > < / div >
< 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-11 < / span >
< span class = "meta-date" > 🕐 2026-06-13 < / span >
< / div >
< / div >
@@ -318,7 +351,7 @@ ul.todo-list { list-style: none; margin: 0; padding: 0; }
< / div >
< div class = "section-block" id = "section-done" >
< div class = "section-title st-done" data-toggle = "done" >
🔍 待验收 < span class = "s-count" > 31 < / span >
🔍 待验收 < span class = "s-count" > 32 < / span >
< span class = "s-arrow" > ▴ 收起< / span >
< / div >
< div class = "section-list-wrap " id = "list-wrap-done" >
@@ -343,6 +376,7 @@ ul.todo-list { list-style: none; margin: 0; padding: 0; }
< 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" >
@@ -371,6 +405,7 @@ ul.todo-list { list-style: none; margin: 0; padding: 0; }
< 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" >
@@ -399,6 +434,7 @@ ul.todo-list { list-style: none; margin: 0; padding: 0; }
< 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" >
@@ -427,6 +463,7 @@ ul.todo-list { list-style: none; margin: 0; padding: 0; }
< 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" >
@@ -455,6 +492,7 @@ ul.todo-list { list-style: none; margin: 0; padding: 0; }
< 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" >
@@ -483,6 +521,7 @@ ul.todo-list { list-style: none; margin: 0; padding: 0; }
< 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" >
@@ -511,6 +550,7 @@ ul.todo-list { list-style: none; margin: 0; padding: 0; }
< 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" >
@@ -539,6 +579,7 @@ ul.todo-list { list-style: none; margin: 0; padding: 0; }
< 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" >
@@ -567,6 +608,7 @@ ul.todo-list { list-style: none; margin: 0; padding: 0; }
< 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" >
@@ -595,6 +637,7 @@ ul.todo-list { list-style: none; margin: 0; padding: 0; }
< 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" >
@@ -623,6 +666,7 @@ ul.todo-list { list-style: none; margin: 0; padding: 0; }
< 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" >
@@ -651,6 +695,7 @@ ul.todo-list { list-style: none; margin: 0; padding: 0; }
< 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" >
@@ -679,6 +724,7 @@ ul.todo-list { list-style: none; margin: 0; padding: 0; }
< 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" >
@@ -707,6 +753,7 @@ ul.todo-list { list-style: none; margin: 0; padding: 0; }
< 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" >
@@ -735,6 +782,7 @@ ul.todo-list { list-style: none; margin: 0; padding: 0; }
< 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" >
@@ -763,6 +811,7 @@ ul.todo-list { list-style: none; margin: 0; padding: 0; }
< 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" >
@@ -791,6 +840,7 @@ ul.todo-list { list-style: none; margin: 0; padding: 0; }
< 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" >
@@ -800,6 +850,35 @@ ul.todo-list { list-style: none; margin: 0; padding: 0; }
< / 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"
@@ -819,6 +898,7 @@ ul.todo-list { list-style: none; margin: 0; padding: 0; }
< 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" >
@@ -846,6 +926,7 @@ ul.todo-list { list-style: none; margin: 0; padding: 0; }
< / div >
< div class = "item-desc" > 完整方案设计,涵盖:界面设计与交互、前端实现方案、后端接口与数据模型、安全策略(防盗用、防滥用、授权校验)等。需产出设计文档后再进入开发阶段。< / div >
< div class = "subtask-block" >
< div class = "subtask-header" >
< span class = "subtask-label" > 子任务< / span >
@@ -929,6 +1010,7 @@ ul.todo-list { list-style: none; margin: 0; padding: 0; }
< 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" >
@@ -957,6 +1039,7 @@ ul.todo-list { list-style: none; margin: 0; padding: 0; }
< 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" >
@@ -985,6 +1068,7 @@ ul.todo-list { list-style: none; margin: 0; padding: 0; }
< 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" >
@@ -1013,6 +1097,7 @@ ul.todo-list { list-style: none; margin: 0; padding: 0; }
< 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" >
@@ -1041,6 +1126,7 @@ ul.todo-list { list-style: none; margin: 0; padding: 0; }
< 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" >
@@ -1069,6 +1155,7 @@ ul.todo-list { list-style: none; margin: 0; padding: 0; }
< 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" >
@@ -1097,6 +1184,7 @@ ul.todo-list { list-style: none; margin: 0; padding: 0; }
< 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" >
@@ -1125,6 +1213,7 @@ ul.todo-list { list-style: none; margin: 0; padding: 0; }
< 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" >
@@ -1153,6 +1242,7 @@ ul.todo-list { list-style: none; margin: 0; padding: 0; }
< 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" >
@@ -1181,6 +1271,7 @@ ul.todo-list { list-style: none; margin: 0; padding: 0; }
< 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" >
@@ -1209,6 +1300,7 @@ ul.todo-list { list-style: none; margin: 0; padding: 0; }
< 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" >
@@ -1237,6 +1329,7 @@ ul.todo-list { list-style: none; margin: 0; padding: 0; }
< 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" >
@@ -1275,6 +1368,7 @@ ul.todo-list { list-style: none; margin: 0; padding: 0; }
< 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" >
@@ -1303,6 +1397,7 @@ ul.todo-list { list-style: none; margin: 0; padding: 0; }
< 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" >
@@ -1331,6 +1426,7 @@ ul.todo-list { list-style: none; margin: 0; padding: 0; }
< 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" >
@@ -1359,6 +1455,7 @@ ul.todo-list { list-style: none; margin: 0; padding: 0; }
< 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" >
@@ -1387,6 +1484,7 @@ ul.todo-list { list-style: none; margin: 0; padding: 0; }
< 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" >
@@ -1415,6 +1513,7 @@ ul.todo-list { list-style: none; margin: 0; padding: 0; }
< 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" >
@@ -1443,6 +1542,7 @@ ul.todo-list { list-style: none; margin: 0; padding: 0; }
< 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" >
@@ -1471,6 +1571,7 @@ ul.todo-list { list-style: none; margin: 0; padding: 0; }
< 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" >
@@ -1499,6 +1600,7 @@ ul.todo-list { list-style: none; margin: 0; padding: 0; }
< 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" >
@@ -1527,6 +1629,7 @@ ul.todo-list { list-style: none; margin: 0; padding: 0; }
< 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" >
@@ -1555,6 +1658,7 @@ ul.todo-list { list-style: none; margin: 0; padding: 0; }
< 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" >
@@ -1583,6 +1687,7 @@ ul.todo-list { list-style: none; margin: 0; padding: 0; }
< 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" >
@@ -1611,6 +1716,7 @@ ul.todo-list { list-style: none; margin: 0; padding: 0; }
< 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" >
@@ -1639,6 +1745,7 @@ ul.todo-list { list-style: none; margin: 0; padding: 0; }
< 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" >
@@ -1680,6 +1787,16 @@ ul.todo-list { list-style: none; margin: 0; padding: 0; }
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 ;
@@ -1800,8 +1917,13 @@ ul.todo-list { list-style: none; margin: 0; padding: 0; }
reasonInput . focus ( ) ;
return ;
}
const escaped = reason . replace ( /\/g, '\\').replace(/ "/g, '\" ');
const cmd = ' / todo reject ' + rejectId + ' ' + escaped;
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' ;
@@ -1819,6 +1941,32 @@ ul.todo-list { list-style: none; margin: 0; padding: 0; }
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 >