40301 测试手机号登录仅允许指定来源,明确要求指定测试号码与固定验证码正确即可直接登录。此前保留的 local 环境和来源 IP 白名单与该要求不符;本节覆盖下方历史记录中的这些限制。AuthController::testPhone 移除来源白名单检查,TestPhoneLogin::assertSourceAllowed 及 MINI_TEST_PHONE_ALLOWED_IPS 配置移除;TestPhoneIdentityProvider::enabled 仅由 MINI_TEST_PHONE_LOGIN 控制,不再检查 APP_ENV。SessionVerifier 共用该开关,因此 production 环境会话可用,关闭开关仍使既有测试会话失效。66666、账号预初始化/启用、业务授权、来源 IP 及全局限流继续生效;正式手机号仍走真实短信,不允许固定码登录。APP_ID 保持 local-test-phone 以复用原账号,不迁移或改动现有用户/授权。agreed 未更新,登录仍提示未同意。原因是原生 label 触发控件状态变化,而页面只在 checkbox 的 click 中手动反转布尔值。checkbox-group 的 change 事件读取 detail.value,文字与勾选框共享同一状态;隐私链接移到 label 外,点击链接不影响同意状态。测试码输入限制5位,短信码6位,验证码格式提示与协议提示分开。water-mini/tests/login-agreement-dom.test.mjs:在 jsdom 中挂载真实 uni-app H5 Checkbox/CheckboxGroup/Label/Input,实际点击文字、勾选框、隐私链接及登录按钮,不通过直接赋值 agreed 绕过交互。4项DOM回归与全量40项前端测试、类型检查通过。浏览器/Windows工具启动均报“failed to write kernel assets: 系统找不到指定的路径”,未做真机或浏览器可视复测。0018bae 将所有号码统一送至短信接口且只接受6位码,导致5位测试码 66666 无法使用;不能再次统一成纯短信登录。water-mini/src/utils/login-flow.ts 按三个指定号码分流到 /auth/test-phone,其他号码走 /auth/send-sms 与 /auth/phone-login。页面分别校验 test_phone_login、sms_login,任一开启即显示手机号入口;测试号不发送短信、不启动发送倒计时,固定码由后端验证,正式号不能使用测试固定码。SmsCode 有效期改为120秒;60秒重发间隔独立保留。重发事务内作废旧码,只校验最新一条记录;最新码过期/消费或重发失败均不能回退使用旧码。存量5分钟验证码也受创建后120秒上限约束。APP_ENV=local、测试开关、来源白名单、已有账号预绑定手机号规则或业务授权。线上公开 /config 只读检查返回短信和测试登录均开启,未验证线上固定码登录或真实短信发送。lazyCodeLoading=requiredComponents。本轮未部署线上后端、未上传微信,也未发布H5预览。login-flow.ts 早就引用了 /auth/send-sms 与 /auth/phone-login,但两个接口从未实装,非测试号用户点「获取验证码」得到的是失败文案。微信登录侧 AppID 又在 5e3ff2e 里被误改为 wx8ebee994ea7c5af3(真实账号是 wxb53276de7129d025),本地源码与 .env 一致地错、反而更难排查。user_id、业务身份、权限与同一账号的微信登录完全一致,且不与 IdentityManagement::grant() 的「1 账号 1 身份」冲突。B(自动建号)会造出零身份账号,登录后只能停在 /pages/account/identities 空页;C(按手机号反查业务档案自动授权)因 water 业务档案上根本没有 phone/mobile 字段而未采用。POST /auth/send-sms、POST /auth/phone-login(公开组,与其他登录入口共用 LoginThrottle)、POST /core/water/mini-user-phone 后台绑定。SystemService 新增 capabilities.sms_login。AuthService 抽出 issueForExisting():原 issue() 会为新身份建号,短信登录必须登录到已存在的行,两条路径共用 sessionFor()。未改动 grant()。{sent:true,expires_in:300},未绑定时不发短信也不落库。否则该接口可直接用于枚举哪些手机号注册过本系统。random_int、5 分钟过期、单次有效、只存 HMAC-SHA256(密钥 MINI_SMS_CODE_PEPPER,留空回退 MINI_SMS_PASSWORD)。6 位空间小,裸 SHA-256 可离线穷举,加盐是必需的。明文不进库不进日志。行锁保证并发下同一个码不会被用两次。consume() 原先在事务内 throw,导致一次输错把刚累加的失败次数一起回滚,5 次锁定形同虚设。改为事务内只判定、提交后再抛。discard() 该验证码,未送达的码不会被事后使用。scripts/migrate_water_mini_sms.php --apply 已在本机执行并复跑验证幂等:新增 sa_water_mini_sms_code、给 sa_water_mini_user 加 phone 及唯一索引。MySQL 唯一索引不约束 NULL,存量无手机号账号不受影响。.env 已写入短信宝账号(gitignore),.env.example 为占位。mini_sms_login_test.php 39 项全过(注入记录型 sender,全程无短信外发,全部数据回滚);契约 911 项/53 操作、架构 126 项、框架 47 项、HTTP 110 项、测试手机号 25 项、ClientIp 16 项、setup 5 项均过。全量 34 个后端测试 29 通过、5 失败,失败清单与上一轮完全一致(mini_identity_management_test.php、water_mini_api_test.php 旧断言,mini_protection_* 本机 Redis 未启动,water_ledger_test.php:18),已用 git stash 在还原后的干净树复现确认非本次引入。实测 HTTP:未绑定号下发返回 200 且不发短信、未绑定登录 401、格式非法/多余字段/非常用号段均 422。前端 vue-tsc 通过、25 项测试通过、mp-weixin 重新编译通过。PROJECT_CONTEXT 早已记录该缺口);sa_water_mini_sms_code 靠 issue() 时的机会式清理(删除 1 天前记录)收敛,没有独立定时任务。server/.env;若该对话会被留存或分享,建议在短信宝后台重置一次。AuthController 与 RequestProfile::ip() 都直接用 $request->getRemoteIp()。已确认 webman 该方法只返回 TCP 对端地址、完全不识别反向代理(webman-framework/src/Http/Request.php:180)。本机 8787 前若挂 nginx/宝塔网关,所有调用方会共用一个来源:登录限流按单一 60 次/分钟 桶计算;更严重的是 TestPhoneLogin::assertSourceAllowed() 会拿代理 IP 比对,而回环 127.0.0.1 正是默认白名单,等于对所有来源放行本地测试手机号登录。app/miniapi/http/ClientIp.php:from(peer, forwardedFor, trustedProxies) 是纯函数以便单测,resolve(Request) 读配置后委托。只有直连对端在白名单内才解析 X-Forwarded-For,并从右向左跳过受信任的代理跳;不可解析、缺头或整链皆为受信任地址时回落到对端地址。兼容 1.2.3.4:port 与 [::1]:port,配置项与请求值统一经 inet_pton/ntop 规范化,避免配置写法差异导致静默失效。MINI_TRUSTED_PROXIES,默认 127.0.0.1,::1(回环不可从网络到达)。不配置等价于维持原行为;反向代理部署须列出每一跳代理地址,否则聚合限流与测试来源放行问题依旧存在。server/.env.example 已补说明。AuthController::login、AuthController::testPhone、RequestProfile::ip() 改用 ClientIp::resolve()。testPhone 中限流与来源白名单共用同一个解析结果,避免两者判定不一致。登录不再信任任意来源的 X-Forwarded-For 这一原有前提保持不变。WechatIdentityProvider:微信对凭据类问题返回 HTTP 200 + errcode,原实现只判 openid 是否存在,导致 code 过期(40029)、code 已用(40163)、频率限制(40226/45009/45011)、AppID 无效(40013/40125) 全部塌缩为同一句 40102 且日志无痕。现按 errcode 分流,并以 Log::warning('mini_api.wechat_login', ['errcode'=>...]) 留痕;只记录错误码,不记录 session_key/unionid/errmsg。复用既有 40102/42901/50301,未新增错误码,OpenAPI 与前端类型无需改动。响应体无法解析时由原来的 40102 改为更准确的 50302。Access::identities() 在多个生效身份时抛 40903,该调用位于 AuthService::issue() 事务内会整体回滚,用户既拿不到 Token 也没有自助恢复路径(后台可逐条停用,但无盘点手段,且存量数据无清理入口)。新增 scripts/audit_mini_identity_conflict.php:默认只读,复用 Access::resourceModel() 复核授权指向的企业/司机/代理是否仍启用,区分「指向已删除或停用档案(Access 不计入,不影响登录)」与「真正冲突」;--apply 在事务内停用除最新一条外的生效授权并打印被停用编号,回滚方式为把这些编号的 status 改回 1。不删除记录,不改动用户与业务档案。php -l 6 个改动文件全部通过。新增 tests/mini_client_ip_test.php 16 项全过,覆盖非信任对端忽略伪造头、可信代理解析、多跳从右向左、端口与 IPv6 括号形式、不可解析回落、配置项规范化。mini_api_architecture_test 118 项、mini_api_contract_test 883 项 51 操作、mini_test_phone_test 25 项、water_mini_http_test 110 项实测 HTTP 均通过。全量 33 个后端测试 28 通过、5 失败。git stash 在干净树上逐一复现确认:mini_identity_management_test.php、water_mini_api_test.php(PROJECT_CONTEXT 已记录的「同一账号可绑定多身份」旧断言)、mini_protection_test.php 与 mini_protection_http_test.php(本机 Redis 未启动、保护开关未启用)、water_ledger_test.php:18。本轮未修改认证规则、未掩盖这些失败。--apply。php-cs-fixer 未安装在本环境,格式按 .php-cs-fixer.mini.php 规则人工核对(PSR12、concat_space、blank_line_before_statement return、no_unused_imports 等)。sa_water_mini_session 无过期清理,长期累积;Token 无滑动续期,7 天硬过期后前端静默清会话;LoginThrottle 仍是文件锁单机限流,多机部署需换共享限流器;water-mini/src/utils/login-flow.ts 引用的 /auth/send-sms、/auth/phone-login 仍未实装,非测试号用户点「获取验证码」得到的是 404 类文案;前后端 appid 仍是两份文件无一致性校验。均未在本轮处理。server/docs/WATER_MINI_API.md 更新调用方地址解析规则、测试手机号来源白名单的同源判定,以及新增「微信登录失败分流」小节(含 errcode 对照表)。server/.env 的 MINI_WECHAT_SECRET 明文曾在排查过程中输出到会话记录,需在微信公众平台重置该 AppSecret 并同步更新 server/.env。server/plugin/saiadmin/app/service/promotion/,三张 sa_water_promotion_* 表、本机已执行可复跑的增量迁移;没有回填历史奖励。平台规则在绑定时快照;禁止自邀、共用管理员及循环绑定,首购前绑定一次。购水共用入口 WaterPurchaseReceipt 生成待审核奖励,组合结算按整笔金额判断,首购前已有配送不能领奖。审核发放与权益、流水同事务,重复请求不重复发奖。server/docs/WATER_PROMOTION.md;本轮执行结果见 server/docs/WATER_PROMOTION_RESULTS.md。water_mini_api_test.php:331、mini_identity_management_test.php:133 仍假设同一账号能同时绑定多个业务身份,实际 IdentityManagement.php:72 已禁止,两个旧测试失败;本轮未修改认证规则或掩盖失败。浏览器点击/截图、微信真机分享和完整端到端验收未执行。views/system/role/modules/permission-dialog.vue:141 的 // TODO: 调用保存权限接口 已删除。后端 SystemRoleController::menuPermission 早已实现,仅拖调一次 api.menuPermission 的注释残留。console.log('表单验证失败:', error) 改为留空 catch + 注释(说明 ElForm 自带校验提示 + axios 拦截器自动 ElMessage.error);register/index.vue catch 保留但改文案;login/index.vue 注释化的 console.log(error.code) 删除。本轮未触动含 loading.value = false 的重置代码。POST /core/register 新增,免登录;RegisterController 继承 OpenController,RegisterValidate 含 username(3-20 字母数字唯一)/password(6-16)/confirmPassword(确认);密码走 password_hash($password, PASSWORD_DEFAULT),默认 is_super=0 status=1 dashboard=work,不分配任何角色。前端 api/auth.ts 新增 fetchRegister,register/index.vue 替换 setTimeout 模拟为真实调用 + catch 留 loading.value = false finally。POST /core/user-management/users/member-activation,权限 core:user:list:memberActivation;MemberActivationValidate 校验 card_type∈{0,1,2,3}、pay_amount≥0、expire_time>now;RentalUserLogic::activateMember 事务内 INSERT sa_member_activation_record(source=3,manage_agent_id=operator) + UPDATE sa_rental_user.member_expire_at/member_card_type;复用 accessibleManagerIds() + assertUserAccessible() 鉴权。SQL 20261009_add_rental_user_member_fields.sql 加字段 + 从历史 activation 记录回填最新有效到期(仅 status=1 且未过期);20261009_add_member_activation_button_permission.sql 在 UserList 菜单下挂按钮并自动赋给有 UserList 权限的角色;scripts/migrate_rental_user_member_fields.php 等效合并两个 SQL 的可执行脚本,--apply 跑。前端 api/userManagement/rentalUser.ts 新增 activateMember + MemberActivationPayload/Result + MEMBER_CARD_TYPE_OPTIONS + RentalUser 加 member_expire_at/member_card_type/member_card_type_name/member_active;user-list/index.vue 新增「会员状态」列、openMemberActivation 改按钮 click、MemberActivationDialog 含卡类型/金额/到期/备注字段(默认到期 +30 天,过往日期禁用);表格「储值卡赠额」列后插入会员状态列。php -l 6 个后端文件全部通过;vue-tsc --noEmit exit=0 0 错误;ESLint 检查 23 个改动文件 0 错误(--fix 处理 6 处 prettier/格式问题);原 showPendingAction 占位函数已删除(无引用);注册端点 + 会员开通端点的冒烟脚本 tests/register_and_member_activation_test.php 已写好(本次未在 webman 容器中运行,留待下次启动后跑)。MemberActivationRecordLogic 中 1-4 月/季/半年/年卡的常量与 MemberActivationValidate::card_type 0-3 一致(原系统 DEVICE_NAMES 已包含设备类型 100+ 的命名,与卡类型无关,无需调整)。water-mini/README.md 与子项目上下文中的余额未开放、可开票、按名称猜分类、Schema数量和旧验证状态。浏览器工具因 windows sandbox failed: orchestrator_helper_exit_nonzero 无法启动,点击/截图/真机验收仍未完成;详细记录见 server/docs/ADMIN_MINI_REPAIR_RESULTS.md。water-mini 使用官方 uni-app Vue3模板对应的 DCloud 3.0.0-5020420260813003、Vue3.4.21、TypeScript,依赖锁定 package-lock.json。对照 D:\水后台UI\mobile\M01–M18.png 实现28个页面,复用组件/会话/请求/分页/命令服务,59个TS接口类型由既有OpenAPI生成。server/public/water-mini,地址 http://127.0.0.1:8787/water-mini/index.html;页面/主资源HTTP200,公开套餐接口实际返回3条。后端未修改、无迁移、无Redis变更、无服务重启或微信平台上传。water-mini/README.md。微信开发者工具导入 water-mini/dist/build/mp-weixin;实际AppID需填写 src/manifest.json 的 mp-weixin.appid,API域名由 .env.local 的 VITE_API_BASE_URL 配置,真机需HTTPS及合法域名。微信后端凭据仍未配置,真实登录/三端授权业务端到端、拍照/位置实机、浏览器点击验收未完成;浏览器工具仍报 unsupported Codex auth method: apikey。H5仅预览公开页面/登录提示,不提供模拟登录或手填Token入口。server/public/docs/mini/index.html,本机访问 http://127.0.0.1:8787/docs/mini/index.html。Swagger UI 5.33.0 静态资源来自官方 npm 包,下载后验证 SHA-512,保留 LICENSE/NOTICE 和来源记录;页面支持45个操作的分类、筛选、Schema、示例、Bearer授权和在线调试。手机号登录明确标注仍为设计,未新增对应业务接口。server/scripts/build_mini_docs.php 将唯一维护源 docs/openapi/water-mini-v1.json 同步到公开目录,--check 检查一致性。页面强制使用当前来源的 /api/mini/v1,不持久化Token、不使用外部CDN或第三方校验器;配置状态实时读取 /config。MINI_ALLOWED_ORIGINS 支持逗号分隔精确来源,默认仅本机8787的127.0.0.1/localhost;部署需覆盖为实际HTTPS来源。业务鉴权不变;接入文档、维护说明和.env.example同步更新。无数据库/Redis/业务接口变更,无手工重启或生产部署。D:\水后台UI\mobile\M01.png–M18.png。用户反复强调简洁、可读、可维护、公共逻辑集中、设计模式、控制器/服务分层、模型查询;已写入根 AGENTS.md,后续持续遵守。server/app/miniapi 由基础 health/session 扩展为系统、认证、套餐、企业、订单、司机、代理、照片独立控制器;公共 BaseController/Access/Input/Fields/Pagination/WaterPresenter/Commands 复用。控制器不查询数据库;业务读写使用 Eloquent 模型,复用 WaterBusiness/WaterFulfillment 的库存与权益事务。套餐和配送列表使用模型关联预加载。/core/water/mini-users、/core/water/mini-grants 绑定/撤销企业管理员/成员、司机、代理身份,与后台 JWT/管理员ID隔离。单机多进程登录限流有文件锁,多机需共享限流。server/scripts/migrate_water_mini_api.php --apply:8张 sa_watermini* 表,不植入身份/支付/财务数据。照片JPEG重编码及时间/上报位置水印,专用私有表按任务归属读取。真实支付、分卡有效期、多站点、佣金提现、邀请奖励、实时GPS、硬件绑定/联网检测仍未实现,接口明确能力关闭;没有把UI样例比例写成业务规则。/api/mini/v1/config 报告微信登录未配置;需补充 .env 的 MINI_WECHAT_APPID/SECRET 等,未做微信外部联调。原收款登记仍是实际权益入口,下单意向不产生权益。代理业绩按企业当前归属,不能当佣金账本。server/docs/WATER_MINI_API.md;格式化配置 .php-cs-fixer.mini.php,本机工具 runtime/tools/php-cs-fixer.phar(3.88.2)。本次实际HTTP已加载新接口;Windows不要用重复启动windows.php替代重载。/water-content 按新参考图实现左侧轮播列表/筛选/8条分页/全量上线数量与右侧手机预览;新建/编辑独立表单支持图片及活动链接、人群、区域、起止时间、排序、启停和草稿实时预览。顶部可模拟购水状态、管理员身份与区域,查看有效轮播和真实启用套餐,图片失败回退标题。WaterContentDashboard 应用查询服务 + content/ContentInput/ContentPolicy 校验及状态策略,Eloquent访问原内容/套餐模型。新增GET content/dashboard与content/preview(content:read),写入复用POST content(content:write)和WaterBusiness事务/审计;原通用内容列表同用状态策略。server/docs/references/water-content-reference-20260917.png,契约见 server/docs/WATER_CONTENT.md。http://127.0.0.1:4173/#/water-content。server/scripts/migrate_water_withdrawal_demo.php --apply 与 seed_water_withdrawal_demo.php --apply --http,固定批次 TEST-WATER-WITHDRAWAL-20260917,ID1–4。星澜商务5760元、青禾推广用户860元、云帆企业服务3280元、森屿商贸2460元,共4笔12360元,名称均带【提现测试】,日期固定09-17。sa_water_withdrawal_demo 表,仅存展示夹具,不是真实提现账本;Eloquent WaterWithdrawalDemo + finance/WithdrawalDemo 返回看板独立 withdrawal_demo 字段。真实待提现金额仍null、capabilities.withdrawals仍false,审核/打款未启用;原收款、权益、库存、配送和代理快照不变,仅追加种子审计。/water-finance 已从通用表格改为参考图布局:四卡、资金趋势/月汇总、未开通提现区、流水搜索/8条分页/详情/全量对账导出,支持月份与测试范围切换、错误重试和窄屏单列。src/api/water/finance.ts、finance/index.vue、ReceiptDialog.vue、finance.scss;复用共用卡片/图表/下载工具。登记收款沿用 finance purchase 凭据幂等与权益事务,远程选择企业/套餐、预览金额桶数并核对后提交,失败保留表单。server/docs/WATER_FINANCE.md。实际路由文件是 server/plugin/saiadmin/config/route.php。无数据库迁移,无新增持久业务数据。http://127.0.0.1:4173/#/water-finance 预览已提供本次产物:首页内容及财务资源SHA256与dist一致。unsupported Codex auth method: apikey,本次未完成点击、截图及窄屏视觉验收;这是唯一尚未执行的页面验收环节,不应误记为项目登录问题。/water-finance,延续薄控制器、业务服务和值对象/策略分层,数据经Eloquent模型处理。参考图保存在 server/docs/references/water-finance-reference-20260916.png:四张收款/退款/提现/佣金卡片、左侧资金趋势及月汇总、右侧提现审核、底部最近资金流水,顶部对账下载与批量审核。WaterBusiness::purchase() 的线下收款登记与套餐权益闭环,WaterOrder 记录订单;旧 DepositRefundLogic 属于租赁押金,不是供水退款,不应混入。供水退款、推广奖励、佣金与提现尚无账本或规则。server/plugin/saiadmin/app/service/finance/FinanceFilters.php、service/WaterFinanceDashboard.php;model/water/WaterOrder.php 添加 financeSource/financePeriod/financeDetails 查询scope;controller/water/WaterController.php 添加 financeDashboard/financeExport;config/route.php 添加 GET /core/water/finance/dashboard 和 POST /core/water/finance/export,均校验 water:finance:read。未新增数据库结构,未写财务数据。TEST- 前缀或套餐名称含“测试”为准,regular仅表示排除已标记测试,不能声称已银行对账。未开通退款/佣金/提现返回null及capabilities=false。XLSX全量匹配导出上限50000条并防公式注入。finance/index.vue 同时Delete/Add而校验失败,实查 src/api/water/finance.ts、views/water/finance/ReceiptDialog.vue、finance.scss 均不存在,finance/index.vue 仍是原 <WaterWorkspace module="finance" />。明天应使用Update修改现有文件,不要认为之前前端草稿已应用。WaterMetricCards.vue、WaterChart.vue、@/utils/waterDownload,收款沿用POST finance action=purchase及原凭据幂等机制。保留错误/加载/空状态及窄屏布局。退款/净收率显示未开通/不可计算,不当成0。unsupported Codex auth method: apikey;备用node_repl因 codex-windows-sandbox-setup.exe 启动路径解析失败,实际组件已确认存在,重置未恢复。用户知道需处理登录并重启,但未确认已完成;明天需要UI验收时先复测,不将工具认证故障当作项目登录故障。server/scripts/seed_water_agent_ranking_demo.php --apply --http,固定批次 TEST-WATER-AGENT-RANKING-20260916;新增订单32–36,复用5家启用的 【代理测试0916】 客户。本月排行:李策划2000元、西瓜商贸1500元、测试商贸600元。server/docs/WATER_AGENTS.md。saiadmin-artd/src/views/water/agents 替代通用表格:统计卡、代理/手机/等级/授权筛选、本月业绩排行、管理抽屉、代理档案/地图辖区编辑、超级管理员账号开通。真实供水订单按客户当前代理归属统计,未配置佣金显示空值;不混用旧租赁分润。WaterAgentDashboard 查询服务、WaterAgentManagement 命令服务和 agent/AgentFilters/AgentHierarchyPolicy/AgentAccessPolicy 值对象/策略,均通过Eloquent模型处理数据。新增 dashboard/detail/open-account 接口;原 agents save 委托管理服务。server/scripts/migrate_water_agent_hierarchy.php --apply,仅增可空parent_id及索引;NULL未设置、0一级,支持最多三级,拒绝循环及子树超深。旧等级不推断回填。server/docs/WATER_AGENTS.md。server/scripts/seed_water_agent_customers_demo.php --apply,固定批次 TEST-WATER-AGENT-CUSTOMERS-20260916;新增企业79–84,名称前缀 【代理测试0916】。李策划(id=1)、西瓜商贸(id=6)、测试商贸(id=7)各关联2家;5家启用、远航服务停用。地址/电话明确为测试内容,无配送坐标。replayed=true 不重复插入也不覆盖后续用户修改。saiadmin-artd/src/views/water/enterprises 替代该页通用 WaterWorkspace,按参考图提供四张统计卡、客户筛选/分页、权益与库存/设备/最近订单/补货状态、增长与转化图、详情、编辑及批量启停。编辑保留地图选点;代理归属复用既有 agent_id,无迁移。WaterEnterpriseDashboard 应用服务配合 enterprise/EnterpriseFilters 值对象和 EnterpriseReplenishmentPolicy 策略,数据通过 Eloquent 模型访问。多规格/设备先聚合避免重复累计;批量启停事务化且追加审计,不变更权益或配送。server/docs/WATER_ENTERPRISES.md。unsupported Codex auth method: apikey,未做截图级验收。region 字段改为 region-map 类型,通过 WaterRegionField 打开高德地图,搜索或点选后反向识别省/市/区县,确认回填区域名称,保存代理商后写入。取消保留旧值,兼容原区域文本,无数据库迁移。WaterLocationPicker,支持point/region两种模式;DispatchLocationPicker保留原open/selected接口。waterAmap.findMapRegion共用现有高德加载器,处理直辖市空city/重复省市、识别失败和配额错误;过期请求不会覆盖新选择。.water-layout 导航样式原位于部分业务页按需导入的 views/water/modules/water.scss;独立库存/设备页没有该依赖,冷启动直达会显示默认白色,先访问总览等页面后才恢复深色。saiadmin-artd/src/views/index/water-layout.scss,由公共布局 views/index/index.vue 直接导入。页面样式仍留在原water.scss;保留原 /water- 范围判断。Prettier、ESLint、vue-tsc与Vite构建3291模块通过,构建清单验证布局静态依赖包含导航CSS,不依赖业务页懒加载。浏览器启动工具故障仍是既有限制,本轮未声称视觉验收。
2026-09-11 工具排查更正:“Node运行时缺失”尚无证据。已确认 Codex cua_node 的 b58ca2eaa6 / e7fe122ad3 两个版本 node.exe 均存在且可运行(v24.20.0);重置 CUA 会话后仍报 failed to start Node runtime ... os error 3,具体失效启动路径未定位。此故障不能归因于项目依赖或Node文件被删除。
WaterFulfillment 应用服务、fulfillment/DeliveryState 状态策略、DeliverySettlement 纯账务计算及 Eloquent WaterDeliveryEvent 追加事件。原 complete 结算统一委托新服务,事件保存请求哈希、操作人和快照,禁止覆盖已登记结果。server/scripts/migrate_water_fulfillment.php --apply,仅增量字段/表及唯一重送索引;旧在途任务10/11补充 LEGACY-FULFILL 路线关联及迁移事件,原状态、库存/权益/收款不变。新返回 returning/returned 已纳入跨日待办和总览,已结算时间及全范围返仓待办有独立入口。server/docs/WATER_FULFILLMENT.md;浏览器工具Node运行时缺失,本轮未做视觉验收。server/scripts/seed_water_district_dispatch_demo.php --apply,固定批次 TEST-WATER-DISTRICTS-20260911;幂等复跑 replayed=true,不覆盖用户后续操作。server/scripts/seed_water_today_dispatch_demo.php --apply,固定批次 TEST-WATER-TODAY-20260911;首次仅允许当天执行,复跑返回 replayed=true,不覆盖后续操作。云泉智配D:\phpstudy_pro\WWW\saiadmin6.xsaiadmin-artd,PHP 后端在 server。D:\水后台UI\desktop\P01.png–P09.png 为新后台参考,授权合并。新主线为企业购水套餐、权益、实物库存、补货和配送。下文旧项目映射仅作为旧模块参考,冲突时以新供水规则为准。server/plugin/saiadmin/app/service/WaterBusiness.php、app/controller/water/WaterController.php(均在 saiadmin 插件内),接口 /core/water/*。前端:saiadmin-artd/src/views/water。入口 /#/water-overview。php scripts/migrate_water_business.php --apply,已执行并复跑,新增 9 张 sa_water_* 表及 12 个菜单,未转换或覆盖旧业务数据。server/docs/WATER_MERGE.md。php tests/water_business_test.php 36 项检查通过,测试数据事务回滚;相关 PHP 语法、前端 vue-tsc、ESLint、Vite production build 通过(3245 modules)。浏览器已确认新总览、企业、库存、内容手机预览及企业必填校验,旧审计页面可从新菜单访问。sa_device_operation_profile(0行)、sa_article(8行)、sa_article_banner(3行)、sa_article_category(4行);63表减为59表,非目标表行数全部不变,供水测试数据保留。server/docs/unused-tables-backup-location.txt,完整结果及恢复命令见 server/docs/DATABASE_CLEANUP_REVIEW.md。备份文件未放入网站公开目录或仓库。server/scripts/cleanup_unused_water_tables.php 固定4表白名单,清理前检查数据库名、备份SHA256、数据变化和代码/数据库对象依赖;默认检查,--apply删除,--restore --apply只恢复缺失表且拒绝覆盖。server/scripts/seed_water_alert_search_demo.php --apply 使用固定批次标记防重复,现场库存经盘点入口建立账户,未增加收款或权益。本次页大小调整通过vue-tsc、ESLint、Prettier及Vite构建(3247 modules);浏览器验证第一页10条、下一页5条,从第二页搜索“星河”返回第一页1条,重置恢复15条,已停留在第一页供用户测试。
搜索已接入 GET /core/water/alerts?keyword=...,后端模糊匹配企业名称、区域和商品规格。新增本轮10条数据前,原5条演示企业名均含0910、规格含18.9,搜索“1”会全部命中,并非查询未执行;浏览器搜索“云泉科技”返回1条。
WaterWorkspace.vue 已修正补货页搜索提示,明确搜索范围及点击查询/回车触发;增加重置(清空关键词、恢复仅预警账户及第1页)、120字限制和无匹配说明。前端搜索去除首尾空格,保持后端原有筛选口径。
验证:vue-tsc、相关 ESLint、Prettier、Vite构建通过(3247 modules)。4173新版浏览器中,带空格的“云泉科技”查询返回1条且去掉空格;回车查询“不存在的企业”返回0条并显示无匹配提示;重置恢复5条。
POST /iot/water/report,以及原 mqtt.message 的供水适配事件;config/water_device.php 两个开关默认关闭,adapter=null,未启用外部连接或修改现有 MQTT Topic。app/service/water/DeviceProtocolAdapter.php 保留 HTTP/MQTT 协议转换接口;DeviceIngress.php 检查开关、HTTP 独立 Bearer 凭据及报文大小。具体厂商适配器尚未实现,因此不能宣称已完成真实设备接入。DeviceReport.php 仅更新已有供水设备的 last_seen 和当日累计用量快照;最近5分钟的报告时间作为心跳,重复/较旧心跳不刷新。用量同日取累计最大值、跨日重置,库存权益及账户日均不变。不支持直接接收增量用量。php tests/water_device_ingress_test.php 30项通过,PHP lint通过,所有设备测试回滚;接口格式、未来厂商鉴权/重放/Topic绑定要求见 server/docs/WATER_DEVICE_INTEGRATION.md。php scripts/seed_water_dispatch_demo.php --apply,固定批次 TEST-WATER-DISPATCH-20260910。任务 ID 9/10/11 分别待派单8桶、已派单6桶、配送中10桶,复用云泉科技/智配办公/清泉产业园;均为模拟任务。两个新测试企业各登记299元模拟套餐,因此当日总览测试收款合计从1997增至2595元。脚本整批事务,以 demo_seed 流水作为已完成标记,复跑不会重置用户后续操作或重复入库/建单/收款;已复跑核对上述数量和金额不变。
用户要求保留少量测试数据,已执行 server/scripts/seed_water_receipts_demo.php --apply。新增 3 个 【测试0910】 企业、1 个 18.9L 测试规格、3 个套餐和 3 笔模拟收款;订单 ID 3/4/5,金额 699/299/999 元,共 1997 元、90 桶权益。
固定收款凭据 TEST-WATER-RECEIPTS-20260910-A/B/C,流水备注明确“未发生真实收款”,地址标明“请勿配送”。未生成配送单、实物库存或设备遥测。
当日总览收款从 0 增至 1997 元;重复执行返回原订单,不重复入账、不修改登记日期。此批为保留的测试业务数据,不能作为实际经营收款依据。
/#/water-alerts,“全部启用账户 / 策略维护”可查看未预警账户;停用企业/规格不参与补货列表。POST /core/water/alerts 的 action=policy 仅更新 threshold_qty,权限 water:alerts:write,记录修改前后阈值及操作人,不修改日均、库存或权益。天数规则仍为既有 5 天预警、3 天紧急。POST /core/water/dispatch 的 action=batch_create 接收 items:[{account_id,quantity}](1–50 条),权限 water:dispatch:write。服务端根据账户解析企业和规格,复用单笔冻结规则,按规格/企业排序加锁;任一条失败整批回滚,已有有效任务禁止重复创建。BatchDeliveryDialog.vue 逐条输入桶数并确认企业、规格及地址;只勾选当前页有可用权益、未建单的预警账户,换页/查询清空勾选。失败保留填写内容供核对。server/docs/WATER_MERGE.md。saiadmin-artd)saiadmin-artd/package.json:Node >=20.19.0、pnpm >=8.8.0。v22.21.0 可用,但 pnpm 未加入 PATH;项目 node_modules 已存在,可直接使用 node_modules/.bin 中的工具。pnpm dev;端口由 .env 的 VITE_PORT 配置为 3006。pnpm build(vue-tsc --noEmit && vite build)。pnpm lint;格式化/样式检查脚本见 package.json。.env.development 使用 VITE_API_URL=/api,Vite 代理到 http://127.0.0.1:8787。VITE_API_URL=http://127.0.0.1:8787 直连 Webman;如果部署到正式域名,应由反向代理把 API 前缀转发到 Webman,而不能继续指向 D:\phpstudy_pro\WWW\api 等其他项目。server)>=8.1,当前 CLI PHP 8.2.29;Composer 当前 2.10.2。workerman/webman-framework ^2.1;Think ORM、SaiAdmin 6.x、SaiPackage 6.x。server/config/database.php,值来自 server/.env。server/.env 为 CACHE_MODE=file。workerman/mqtt ^2.2。server/windows.php(Windows 入口);Webman HTTP 进程配置在 server/config/process.php,监听 http://0.0.0.0:8787。server/start.php。php -l <file>;本次已核对启动、进程、Bootstrap、MQTT subscriber、登录中间件等关键文件,均无语法错误。saiadmin-artd/src/main.ts: 创建 Vue 应用,初始化 Pinia、Router、全局指令、错误处理和国际化。saiadmin-artd/src/App.vue: Element Plus 配置、主题初始化、存储兼容性检查和系统升级检查。saiadmin-artd/src/router/index.ts: Hash 路由;静态路由在 src/router/routes/staticRoutes.ts,登录后动态路由来自 src/router/routes/asyncRoutes.ts。saiadmin-artd/src/router/guards/beforeEach.ts: 登录状态、用户信息、字典、后端菜单和动态路由初始化。saiadmin-artd/src/router/core/MenuProcessor.ts: 前端/后端菜单模式选择、角色过滤、空菜单过滤和路径规范化;后端模式通过 /core/system/menu 获取菜单。saiadmin-artd/src/utils/http/index.ts: Axios 统一封装;从 Pinia 读取 access token,添加 Bearer Authorization,处理统一响应和 401。saiadmin-artd/src/api: 按功能组织 API;目前约 31 个 TypeScript API 文件。saiadmin-artd/src/views: 约 132 个视图文件,包含系统、安全维护、工具和当前定制业务模块。saiadmin-artd/SAIADMIN.md: 前端功能目录约定,新增业务通常按模块/业务/表页面组织。server/start.php / server/windows.php: Webman 启动入口。server/config/process.php: HTTP、MQTT subscriber、文件监控进程配置。server/app: 项目层控制器、模型、服务和进程;当前包含 app/process/MqttSubscriber.php、app/service/MqttPublisher.php、app/service/MqttTopic.php。server/plugin/saiadmin: SaiAdmin 核心插件;按 controller、logic、model、validate、middleware、utils、config 分层。server/plugin/saiadmin/config/route.php: /core 和 /tool 路由,fastRoute() 为控制器常用 CRUD 路由生成器。server/plugin/saiadmin/app/middleware: 登录检查、权限检查、跨域和系统日志中间件。server/plugin/saipackage: 在线插件/安装相关功能。server/config/plugin: tinywan JWT/storage、Webman channel/console/event 等插件配置。/api,开发时由 Vite 代理到 Webman 8787。http://127.0.0.1:8787;后端 SaiAdmin 插件已启用 CrossDomain 中间件以支持前端站点跨端口请求。GET /core/captcha,登录调用 POST /core/login;成功后保存 access/refresh token,再获取用户信息、字典和菜单。.env 的 VITE_ACCESS_MODE=backend,因此以服务端菜单为主;MenuProcessor 会补齐缺失的内置 MiniProgram 和 Merchant 一级菜单。LoginController::$noNeedLogin 中放行。app\process\MqttSubscriber 作为独立 Webman 进程运行,业务发布入口位于 MqttPublisher 和 /tool/mqtt/publish。D:\phpstudy_pro\WWW\api(ThinkPHP 5)。后续实现设备、商户、代理、订单、资金等旧业务页面时,应先追踪该项目的 controller → model/service → 写入/查询表链路,再将业务语义翻译到 SaiAdmin;不直接让新前端调用旧项目接口。api/application/agentapi/controller/Merchant.php::bind_device_records(),分页查询位于 api/application/common/model/MarketZuoRelyRecord.php::queryPageList()。旧逻辑关联 market_zuo_rely_record、market_goods、store 和 admin_user_tier,按记录时间倒序并限制当前代理本人/下级可见范围。market_zuo_rely_record → 新 sa_merchant_device_binding_record;market_good_id → device_id;goods_sn → device_sn;rely_store_id → merchant_id;distribute_type → action_type(1 铺货、2 解绑);add_date → action_time;depend_type 保留为设备类型码。market_goods → sa_device,旧 device_id/二维码搜索 → 新 device_code,设备 SN 使用 goods_sn;旧 store.store_name → sa_merchant.name;操作人使用 sa_system_user 的 realname/username 和 phone。distribute_type、search_store_name、search_device_id、search_goods_sn、search_depend_type → 新 action_type、merchant、qr_code、device_sn、goods_type。旧分页 start 是从 0 开始的页号,新接口统一使用 page(从 1 开始)和 limit。admin_user_tier 代理层级树,因此设备类数据权限暂统一沿用 sa_device.manage_agent_id:管理员 ID 1 查看全部,其他账号只查看其当前管理设备及对应流水。未来迁移代理层级后,再按旧项目“本人 + 合法下级”规则扩展。api/application/agentapi/controller/Sttuf.php:distribute_agent_devices() 查询当前代理管理库存,save_distribute_agent_devices() 执行下发,my_agent_stock_devices() 查询下级未铺货库存,save_recover_agent_devices() 执行回收,agent_distribute_log() 查询双方分配/回收流水;旧路由分别为 /agentapi/distribute_agent_devices、/agentapi/save_distribute_agent_devices、/agentapi/my_agent_stock_devices、/agentapi/save_recover_agent_devices、/agentapi/agent_distribute_log。market_goods.manage_aid → 新 sa_device.manage_agent_id,旧 market_goods.belong_aid → 新 sa_device.belong_agent_id(分配只转移管理权,不改变所有权)。旧 device_manager_record → 新 sa_device_manager_record;goods_id/goods_sn/goods_type → device_id/device_sn/goods_type,last_aid/now_aid → last_agent_id/now_agent_id。新 action_type=1 表示分配、action_type=2 表示回收,对应旧 flow_type=0/1。sa_device.manage_agent_id 并写 sa_device_manager_record,不存在、非当前管理、已在目标代理、已铺货分别返回独立结果集合。回收执行相反的管理权转移,同样禁止回收已铺货设备。api/application/agentapi/controller/Sttuf.php::edit_agent() / save_edit_agent()。新系统保留 sa_system_user.realname/phone 作为账号基础资料,并使用一对一 sa_agent_profile 保存运营模式、产品、分润、业务权限、运营区域和公司地址;开通类型继续由部门层级推导且编辑时只读,登录账号和密码均不在此页面修改。D:\phpstudy_pro\WWW\api 对应控制器、模型/服务及数据写入路径;PROJECT_CONTEXT.md 已记录过的映射直接复用,只分析尚未覆盖的业务差异。SAIADMIN.md 的模块目录结构,并使用已有 API、类型、组件和 store 模式。AppRouteRecord 结构及 @views/@utils/@stores/@styles 等别名。config/route.php 路由。.env 的密码、Token、JWT 密钥或其他凭据,只记录变量名、用途和非敏感默认值。sa_deposit_refund_record,退款结果回调时更新 status、refund_time 和异常备注。当前新系统尚无完整支付退款执行链路,不得用 sa_rental_order.refund_amount 猜测押金、计划退款时间或退款路径。可复用本地 20260904_seed_deposit_refund_test_data.sql 的 3 条 TEST-ORDER-* 数据做页面与导出回归。登录验证码图片 MIME 和生产 API 缓存/路由问题已修复。
saiadmin-artd/src/views/auth/login/index.vue 使用 img :src="captcha",接口为 GET /core/captcha。code=200,返回了 uuid 和图片数据;图片数据前缀为 data:image/png;base64,/9j/...。/9j/ 表示实际内容是 JPEG,而 server/plugin/saiadmin/utils/Captcha.php 使用的 Webman\Captcha\CaptchaBuilder::get() 在当前 vendor 实现中通过 imagejpeg() 输出。已将返回值改为 data:image/jpeg;base64,,并重启 Webman 进程使修改生效。refreshCaptcha() 已增加异常捕获,接口失败时会在控制台记录 [Login] Failed to load captcha。server/.env 为 CAPTCHA_MODE=cache、CACHE_MODE=file,本次接口请求成功,缓存配置不是当前“接口完全不可用”的首要证据。修复前应保留现有用户改动并先确认浏览器 Network/Console。/prod/core/captcha 并收到 304 Not Modified;实测该路径没有进入本项目 Webman:端口 8787 下 /prod/core/captcha 为 404,端口 80 下则被转到另一个 D:\phpstudy_pro\WWW\api 项目并出现 PHP 语法错误。已改为生产构建直连 http://127.0.0.1:8787,并给验证码 GET 增加时间戳参数防止缓存复用。登录跨域预检问题已修复。
127.0.0.1:8787 后,JSON 登录会先发送 OPTIONS /core/login;原先返回 404,浏览器因此阻止后续 POST。CrossDomain 中间件、通用 OPTIONS 路由,并保留 SaiAdmin 业务路由上的跨域中间件,确保预检及登录成功/失败响应都带跨域头。/core/login 在无效验证码下正常返回业务错误且包含 Access-Control-Allow-Origin,说明登录请求链路已打通。工作树不是干净基线。
需要审查配置中的敏感材料。
server/config/plugin/tinywan/jwt/app.php 存在看起来像密钥配置的内容;记忆不保存具体值。若这些值用于真实环境,应移出代码库、改用环境变量并轮换。编码显示异常。
商户统计页面直链问题已修复。
merchant.ts 路由模块已注册商户列表、添加商户和数据统计页面。MenuProcessor 会补充并合并内置商户路由;已验证 /merchant/statistics?id=73 正常渲染。商户列表的营业时间已接入并完成迁移,免费店员仍待业务定义。
business_time_des,迁移脚本为 server/plugin/saiadmin/db/20260826_add_merchant_business_time.sql;2026-08-27 已执行并确认字段存在。Windows 后端启动脚本不解析 status、restart 等参数。
server/windows.php 每次执行都会再启动一整套 Workerman 进程;不要用 php windows.php restart -d,否则多个进程会通过端口复用同时监听 8787,新增路由可能随机命中旧进程并返回 404。saiadmin6.x/server/windows.php 的进程树,再启动一次。车载设备供电/断电依赖独立硬件指令服务。
POST /cdy/tasks 协议,发送 uid、task_type=switch 与 value=1/0;实际网关地址通过 DEVICE_COMMAND_ENDPOINT 配置。DEVICE_QR_BASE_URL,为空时仅编码设备机器码。/core/captcha 返回 data:image/jpeg,前端重新构建。/prod API 指向错误项目和验证码缓存问题;生产构建改用 Webman 8787,启用跨域中间件并为验证码请求增加防缓存参数。/core/merchant/save 提交,后端执行字段校验、直属代理/分店归属校验、手机号唯一校验和密码哈希,详情接口不返回密码;添加页补充可编辑的 0-100 分润比例字段。sa_device.manage_agent_id 隔离,超级管理员 ID 1 可查看全部,缺少真实业务字段时使用 -- 或 0,不生成虚假订单/金额数据。D:\phpstudy_pro\WWW\api。本次完成铺货记录 controller/model/表字段和权限范围映射并沉淀到项目记忆,后续不重复分析已确认部分。MerchantLogic::bindDevices(),不新增第二套绑定事务。sa_device.manage_agent_id,不改变 belong_agent_id,已铺货设备继续禁止分配或回收,操作与流水写入保持同一事务。sa_merchant.business_time_des 已落库;“免费店员”仍因缺少数据模型和明确业务规则而保持待定义状态。GET /core/agent/read、PUT /core/agent/update;产品分润动态联动,修改范围受直属层级权限约束,手机号变更时检查冲突。列表“更多设置”中的合同管理已接入真实页面与读写接口。POST /core/agent/destroy。按旧系统语义删除仅禁用账号而不物理删除历史;后端会校验操作者可见范围,并拒绝仍有关联设备、商户或下级代理的账号。权限迁移 20260827_add_agent_destroy_permission.sql 已执行并确认新增 core:agent:destroy POST 权限。/merchant/subordinate-merchant?agent_id=<代理ID>。下级商户页会同步路由中的代理 ID 到筛选条件,后端继续按当前账号可见范围校验该参数。POST /core/agent/device/assign,因此沿用代理可见范围、当前管理权、未铺货及单次 100 台的后端限制。相关文件已通过 ESLint、vue-tsc --noEmit 和 Vite 生产构建(3183 modules;仅保留既有系统菜单动态/静态导入警告)。2026-09-02: 完成“用户管理 → 用户投诉 / 用户反馈”真实业务链路。用户投诉复用 sa_wechat_mini_program 的微信支付配置,通过 API v3 提供配置选择、最近 31 天分页查询、详情、敏感手机号解密、协商记录、回复和完结;配置错误会明确返回,不伪造投诉数据。用户反馈新增 sa_user_feedback、sa_user_feedback_reply,支持组合筛选、状态数量、详情、回复流水、后台备注、状态处理和 XLSX 导出;普通管理员按本人及递归下级代理范围查询,超管查看全部。迁移 server/plugin/saiadmin/db/20260902_add_user_complaint_feedback.sql 已连续执行两次并清除菜单缓存,11 个接口权限已落库。
2026-09-02: 完成“用户管理 → 用户列表 → 余额充值”管理端代充链路。sa_rental_user 已有 wallet_balance 列无需加列;新建资金账本表 sa_rental_user_wallet_log(为 card/card_gift 预留 balance_type,本轮仅支持 wallet)。新增 POST /core/user-management/users/recharge,由 RentalUserController::recharge → RentalUserLogic::recharge 在事务里锁定用户行 → 增量更新 wallet_balance → 写流水;操作人 ID/姓名来自 BaseController::$this->adminId/adminInfo。扣减时禁止减为负数(余额不足则抛 ApiException),amount=0/缺字段均抛业务错误。前端 user-list/index.vue 弹窗保持原状(仅金额输入框),confirmBalanceRecharge 改为真实 await rentalUserApi.recharge(...),成功后刷新列表;showPendingAction 仍保留给“设置会员”和“储值卡充值”按钮。后端接口允许 balance_type 缺省(默认 wallet)和 remark 缺省(默认空串)——前端不暴露任何新 UI 字段。可见范围复用现有 accessibleManagerIds() + assertUserAccessible(),谁能看谁就能充。新增菜单 RentalUserRecharge(slug core:user:list:recharge,method POST,sort 100),按 UserList 角色传播;当前尚无角色持有 UserList,故本次新增菜单未挂在普通角色上,超管 ID 1 不受影响。迁移脚本 server/plugin/saiadmin/db/20260902_add_rental_user_wallet_log.sql 已执行两次(幂等)。
2026-09-04: 完成“用户列表 → 储值卡充值”。按旧系统 Business::invest_user_accout(type=0) 语义,管理端只调整储值卡赠额:旧 wash_gift_balance 映射到新 card_gift_balance;card_balance 代表用户实付本金,不开放人工修改。前端新增显示目标用户、当前赠额、正负金额和备注的真实弹窗,调用现有 POST /core/user-management/users/recharge 并传 balance_type=card_gift。后端用显式白名单将 wallet/card_gift 映射到余额列,保留事务、行锁、可见范围和操作人流水,并新增金额最多两位小数、DECIMAL 上限、0 元拒绝、超额扣减拒绝和备注长度校验。新项目未发现可复用的 NFC 卡同步服务;若恢复实体卡绑定,应新增事务提交后的异步同步,不在数据库事务内调用外部厂商。
2026-09-04: 完成“资金管理 → 退押管理”真实列表与 XLSX 导出。旧系统语义来自 agentapi/Order::withdaw_deposit_orders()、common/model/UserOrder::queryDepositOrders() 和 common/service/Task::addDepositOrderTask():退押是独立任务,退款额为押金减消费,计划退款时间、退款路径和任务状态不能从普通订单退款字段猜测。新系统新增 sa_deposit_refund_record(迁移 20260904_add_deposit_refund_record.sql),新增 GET /core/fund-management/refund-management/index 与 POST /core/fund-management/refund-management/export,支持状态、来源、退款类型、订单号、用户 ID 和申请日期筛选,并按“本人 + 递归下级管理代理”隔离数据。导出最多 50000 条,文本字段做 Excel 公式注入防护。原静态页面已替换为真实分页、汇总和下载。权限迁移 20260904_add_deposit_refund_permissions.sql 已重复执行验证幂等,菜单 ID 227/228 已落库并清理菜单/接口权限缓存。测试数据脚本 20260904_seed_deposit_refund_test_data.sql 仅关联既有 TEST-ORDER-20260901-* 订单;正式订单完结/支付回调后续必须按该表字段契约写入退押任务,本轮未伪造尚不存在的支付退款执行链路。
2026-09-04: 完成“资金管理 → 充值记录”真实列表与 XLSX 导出。新增 GET /core/fund-management/recharge-records/index 和 POST /core/fund-management/recharge-records/export,列表与导出共用充值类型、变动方向、用户 ID、昵称及日期范围筛选,并复用用户列表的“本人 + 递归下级”订单用户可见范围。导出不受分页限制,超过 50000 条要求缩小筛选,文本字段做 Excel 公式注入防护。页面已移除静态演示数据,展示增加、扣减、净变动汇总、变动前后余额、操作人和备注。当前新库只有已提交的后台余额流水 sa_rental_user_wallet_log,且无新版用户支付单表,因此本页暂不展示微信/支付宝未支付充值单;待迁移实付充值下单与回调链路后再合并。权限迁移 20260904_add_recharge_record_api_permissions.sql 已重复执行验证幂等,菜单 ID 224/225 已落库并清理菜单/接口权限缓存;当前普通角色未持有充值记录页权限,故角色继承关联为 0。
2026-09-01: 用户决定暂缓商户登录体系设计。当前添加商户会把手机号和密码哈希保存到 sa_merchant,但 /core/login 只认证 sa_system_user,因此商户暂时没有登录入口;在明确“独立商户端”或“共用管理后台”前不要同步创建后台账号,也不要新增临时登录接口。
2026-09-01: “用户管理 → 用户列表”正在由静态演示改为真实数据链路,旧业务参照 api/application/agentapi/controller/Search.php::query_users() 和 common/model/User.php::getPageUserList():用户资料独立存储,租借次数与消费金额从订单汇总。已新增 sa_rental_user 模型/逻辑/控制器、GET /core/user-management/users/index、GET /core/user-management/users/orders、前端 API 与真实分页页面;支持微信/支付宝、用户 ID、昵称、手机号筛选及用户订单记录。非超级管理员范围按“本人 + 递归下级”的 manage_agent_id 订单关系限制。资金充值和会员设置涉及资金账本、支付日志与商户会员规则,本轮未伪造实现,原静态操作入口已从用户列表移除。
2026-09-02: “用户管理 → 用户列表”数据库与运行时验证已完成。已在本地数据库依次执行并重复验证 20260901_add_rental_user.sql、20260901_add_rental_user_permissions.sql、20260901_seed_rental_users_from_test_orders.sql;sa_rental_user 共 19 列,30 个测试订单用户全部同步,重复执行未产生重复数据或权限。使用管理员令牌真实请求用户分页、用户订单以及来源、用户 ID、昵称、手机号筛选均返回业务 code=200;微信/支付宝各 15 人。UserList 当前没有普通角色关联,因此新增订单权限也没有可继承的角色关联;超级管理员可正常使用,给普通角色开放页面时需同时授权 core:user:list:index 与 core:user:list:orders。前端 vue-tsc --noEmit 与 Vite 生产构建通过(3193 modules),仅保留既有 system menu 动静态导入提示。
2026-09-01: “下级订单”已由 <OrderList empty /> 静态占位改为真实分页页面,新增 GET /core/order/subordinate/index 与 /read。权限范围与下级设备保持一致:超级管理员查询所有非自身管理人订单;普通账号根据 sa_system_dept.level 查询递归后代部门账号,并以 sa_rental_order.manage_agent_id 限定列表和详情。页面支持设备分类、状态汇总、代理 ID、订单、用户、商户、设备、交易号、来源、支付和日期筛选,并展示管理代理信息。权限迁移 20260901_add_subordinate_order_api_permissions.sql 已在本地 water 库执行;30 条测试订单当前分布为管理人 ID 1 共 6 条、ID 3 共 12 条、ID 10 共 12 条,便于验证本人/下级隔离。相关 PHP 文件通过语法检查,前端通过 Prettier、vue-tsc --noEmit,生产构建产物已更新。
2026-09-01: 完成“订单管理 → 我的订单”真实数据基础链路。旧业务参照 api/application/agentapi/controller/OrderSearch.php::query_orders_new() 与 Order.php::user_order_detail();新系统只查询 sa_rental_order,不调用旧项目接口。新增 GET /core/order/my/index 和 /read,支持设备分类、状态、订单号、用户、商户、设备、交易单号、来源、支付和日期筛选,并按 manage_agent_id 隔离(管理员 ID 1 查看全部)。迁移 20260901_add_rental_order.sql、20260901_add_my_order_api_permissions.sql 已执行于本地 water 库并确认订单表存在。测试脚本 20260901_seed_rental_order_test_data.sql 已执行,写入 6 个页面分类各 5 条、共 30 条可重复清理的 TEST-ORDER-20260901- 前缀订单;不包含真实订单数据。
2026-08-31: 本轮设备模块完成记录:下级设备页已由静态演示改为按部门后代范围的真实列表,完成归属详情、车载供电/断电、二维码预览/下载、解绑以及单台/批量回收。下级设备接口为 /core/device/subordinate/index、/read、/power、/unbind;解绑权限迁移已在本地 water 库执行。回收复用既有 /core/agent/device/recover,只回收未绑定商户设备。已写入本地测试数据脚本 20260831_seed_subordinate_device_test_data.sql,前缀为 TEST-SUBDEV-;初始可回收设备为 ID 13、15,已绑定解绑测试设备为 ID 14。我的设备工具栏新增“设备二维码”,二维码页面已接入当前账号管理的真实设备,支持筛选、预览及单个/批量 SVG 下载;二维码内容遵循 DEVICE_QR_BASE_URL + device_code(未配置时直接使用机器码)。
2026-08-31: 完成“下级设备”真实数据链路:新增 GET /core/device/subordinate/index、GET /core/device/subordinate/read 和 POST /core/device/subordinate/power。可见范围通过 sa_system_dept.level 的后代部门计算,并仅返回这些代理当前由 sa_device.manage_agent_id 管理的设备;设备归属 belong_agent_id 只用于展示,不作为越权依据。页面已移除静态演示数据,归属弹窗展示归属代理、当前管理者与商户绑定信息;供电/断电仅对 goods_type=107 车载设备显示,沿用既有 /cdy/tasks 指令协议和 sa_device_command_log 审计记录。权限迁移位于 server/plugin/saiadmin/db/20260831_add_subordinate_device_operations.sql,需在目标数据库执行后非超管角色才能调用新接口。
2026-08-31: 已在本地 water 数据库执行 20260831_add_subordinate_device_operations.sql,并复核三条下级设备接口权限均已写入。当前 SubordinateDevice 菜单没有角色关联,因此没有可继承的非超管角色授权;超级管理员不受该角色映射影响。
2026-08-31: 下级设备页补充二维码与解绑:二维码复用 subordinate/read 返回的 qr_content 并用 qrcode.vue 渲染;解绑使用新接口 POST /core/device/subordinate/unbind,按下级设备范围校验后复用原有解绑事务、解绑流水和商户设备数同步。已在本地 water 库执行补充权限迁移 20260831_add_subordinate_device_unbind_permission.sql;当前已绑定测试设备为 TEST-SUBDEV-POWER-001(ID 14),可用于解绑验证,重新执行测试数据脚本即可恢复该绑定。
2026-08-31: 下级设备页补充单台/批量回收,前端复用既有 POST /core/agent/device/recover 和 AgentLogic::recoverDevices(),按设备当前管理代理分组回收。回收只转移 manage_agent_id 回当前账号并写 sa_device_manager_record;已绑定商户的设备被前端排除且后端仍会强制拒绝。无需新建回收事务或迁移;接口权限 core:agent:device:recover 已由既有代理设备分配迁移提供。测试设备 ID 13、15 可回收,ID 14 因已绑定不能回收。
2026-08-31: 完成“我的设备 → 设备二维码”真实页面:/device/qrcode 复用 GET /core/device/index,继承当前管理权范围和设备类型/绑定/二维码/SN 筛选;列表显示真实机器码、商户和二维码内容,并支持预览、单个及批量 SVG 下载。DeviceLogic::formatDevice() 统一返回 qr_content,规则与设备详情一致:配置 DEVICE_QR_BASE_URL 时拼接机器码,未配置时直接使用机器码。我的设备工具栏已增加“设备二维码”入口;无新增数据库迁移。
2026-08-25: Added saiadmin-artd/src/router/modules/merchant.ts with routes for /merchant/my-merchant, /merchant/subordinate-merchant, /merchant/add-merchant, and /merchant/statistics.
2026-08-25: Registered the merchant route module and made backend-menu mode supplement/merge these routes when the server menu has not been synchronized yet. This fixes direct navigation to /merchant/statistics?id=73 being redirected to the dashboard.
2026-08-25: Verified http://localhost:4173/#/merchant/statistics?id=73 remains on the requested URL and renders the 数据统计 page with merchant data.
2026-08-25: 商户列表接口新增 status、profit_method 筛选和代理类型 summary 统计;下级商户页面已接入代理 ID、状态、分成方式搜索,分类切换和关键词搜索均复测成功。
2026-08-25: 重启 8787 Webman 进程加载商户列表后端改动;浏览器实测下级商户分类显示全部 65、城市合伙人 22、代理商 22、业务员 21,名称搜索可缩小到 1 条结果。
2026-08-26: 商户添加/编辑页地图由 OpenStreetMap iframe 替换为高德 Web JS API;支持地址搜索定位、地图点击逆地理编码、地址/地区回填,以及编辑时按已保存地址回显。高德凭据仅放在被 Git 忽略的本地 .env.local 中。
2026-08-26: 修复高德地图加载失败:高德脚本 URL 补充 AMap.Geocoder 插件,避免初始化地理编码器时报错。
2026-08-26: 新增 /core/device/index、/core/device/export、设备 API 封装和 views/device/my-devices/index.vue 真实数据页面;执行设备测试数据和权限 SQL,并通过商户绑定接口将 TEST-VEHICLE-0001 绑定到演示商户,用于验证商户搜索及绑定状态筛选。
2026-08-26: 参照旧项目 bind_device_records 完成铺货记录页:新增 /core/device/distribution-record/index、/core/device/distribution-record/export、独立前端 API/页面、设备菜单及导出权限;“我的设备”和“下级设备”的铺货记录按钮改为打开真实页面。
2026-08-26: 完成“去铺货”页面 /device/distribute。旧入口 GET agentapi/add_bind_store、POST agentapi/save_bind_store、Store::bind_device_store() / save_bind_store() 与 Store::bindZuo2Store(),在新项目分别映射为商户经营数据列表、DeviceBindingDialog、POST /core/merchant/device/bind 和 MerchantLogic::bindDevices()。页面按旧系统截图展示门头照、商户名称/地址、金额、订单量、设备数、使用率、客单价、上级代理、分润人、分成方式及右侧操作区;点击“设备绑定”输入或扫码 SN 完成铺货。
2026-08-26: 去铺货入口已接入“我的设备”和“下级设备”。从“我的设备”勾选未绑定设备进入时通过查询参数预填 SN;不勾选时可在商户行点击“设备绑定”后手工录入。商户候选复用 GET /core/merchant/index,当前绑定列表复用 GET /core/merchant/devices,绑定结果继续区分成功、已绑定当前商户、已绑定其他商户、设备不存在和无管理权。
2026-08-26: 新增并执行幂等菜单迁移 server/plugin/saiadmin/db/20260826_add_device_distribution_page.sql,DistributeDevice 菜单唯一;新增前端设备路由模块,并在后端菜单尚未同步时由 MenuProcessor 合并设备子路由。
2026-08-27: 接续完成代理设备分配半成品:AgentLogic/AgentController 提供代理列表、导出、可分配库存、分配、回收和流水接口;代理列表改用真实 API,接入 DeviceAssignmentDialog 与 AssignmentRecordsDialog;新增 /device/assign 页面,并让“我的设备 → 去分配”携带已勾选未铺货设备 SN。
2026-08-27: 已执行幂等迁移 server/plugin/saiadmin/db/20260826_add_agent_device_assignment.sql,数据库现有 sa_device_manager_record、唯一 AssignDevice 页面菜单及 6 个代理相关接口权限。
2026-08-27: 新增并执行两次幂等迁移 server/plugin/saiadmin/db/20260827_add_agent_profile.sql,数据库已有 sa_agent_profile、隐藏 AgentEdit 页面及 core:agent:read/update 权限。前端新增 /agent/edit-agent?id=...,后端菜单模式会合并代理内置路由。
2026-08-27: 完成代理合同管理迁移:新增 /agent/contract?id=... 隐藏页、GET/PUT /core/agent/contract、sa_agent_contract 一对一附件表及幂等权限迁移 server/plugin/saiadmin/db/20260827_add_agent_contract.sql。三组图片字段分别为 contract_urls、identity_urls、extra_urls,每组最多 20 张;读写均复用代理可见范围校验。
2026-09-04: 退押管理通过 PHP 语法和 Webman 路由加载检查;本地库 3 条测试任务覆盖待退、已退、退款异常,汇总应退 43.90、已退 12.00、未退 31.90,已退状态筛选返回 1 条,管理员 ID 3 的自身范围返回 2 条。导出生成 5331-byte XLSX,文件签名 PK 03 04 正确且包含“订单号”“退款金额”表头,临时导出文件已清理。前端文件通过 Prettier、ESLint、Stylelint、vue-tsc --noEmit 和 Vite production build(3197 modules,仅既有 system menu 动静态导入提示)。
2026-09-04: 充值记录列表/导出通过 PHP 语法和 Webman 路由加载检查;真实库 7 条流水查询为 wallet 7、card_gift 0,汇总为增加 160.00、扣减 20.00、净变动 140.00,最新记录的用户、操作人和前后余额映射正确;按 wallet 筛选生成 5437-byte XLSX,文件签名 PK 03 04 正确,临时导出文件已清理。前端文件通过 Prettier、ESLint、Stylelint、vue-tsc --noEmit 和 Vite production build(3197 modules,仅既有 system menu 动静态导入提示)。
2026-09-04: 储值卡赠额充值改动通过 3 个 PHP 文件 php -l,后端 php webman route:list 确认 POST /core/user-management/users/recharge 正常加载;前端相关文件通过 Prettier、ESLint、Stylelint 和 vue-tsc --noEmit,Vite production build 通过(3196 modules,仅保留既有 system menu 动静态导入提示)。本次未对真实用户执行入账,未产生测试资金流水。
2026-09-02: 投诉/反馈新增 PHP 文件及路由全部通过 php -l;前端相关文件通过 Prettier、ESLint、vue-tsc --noEmit 和 Vite production build(3196 modules)。真实 HTTP 回归:反馈筛选列表、详情、状态更新、回复均返回 200,回复流水写入且状态一致;导出返回 HTTP 200、XLSX MIME、4835 bytes;测试数据和回复已清理。投诉配置接口返回 200,当前测试占位私钥查询投诉时返回明确的“微信支付商户私钥格式错误”,验证配置校验和错误透传生效。
2026-09-02: 余额充值回归测试通过:表 sa_rental_user_wallet_log 12 列 5 索引落库,菜单 RentalUserRecharge(slug core:user:list:recharge,method POST,sort 100,id 212)落库,迁移两次幂等。RentalUserController / RentalUserLogic / WalletRechargeValidate 通过 php -l;前端 vue-tsc --noEmit 通过;Vite production build 通过(39.08s,仅保留既有 system menu 动静态导入提示)。真实 HTTP 自测:测试用户 900005(balance 100)→ POST +50 → 150 → POST -20 → 130 → POST +50 → 180(首轮带 remark 字段)→ 回滚后再 POST 不带 balance_type/remark +30 → 210 → POST 带 balance_type=wallet+remark +10 → 220;列表查询 wallet_balance=220 复核;流水表 5 行 direction 1/-1/1/1/1、before/after 全程一致、operator_id=1/operator_name=admin、source=admin_recharge;负数超额扣款返回“余额不足,无法扣减”,余额未变;amount=0 返回“充值金额不能为 0”;balance_type=card 返回“账户类型不支持”;user_id=0 返回“请选择有效用户”;缺 user_id 返回“请选择用户”;回滚后不带 balance_type 仍 200,证明 balance_type 已从必填改为可选。前端 user-list/index.vue 弹窗模板回滚到原状,仅金额输入框,未增加任何字段。Webman 清理 supervisor (PID 29800 / 28492 / 3832) 后启动单实例(PID 15804)加载新代码(Windows 不启用文件监控);自测过程未污染正式用户密码或凭据,仅复用现有 admin 令牌。
2026-09-02: 用户列表三份幂等迁移已执行两次并通过结构、权限和数据数量复核;真实 HTTP 验证用户总数 30、首页 10 条、首个用户订单明细 1 条,用户 ID/昵称/手机号筛选均精确返回 1 条,来源筛选微信/支付宝各 15 条且行数据来源一致。相关 PHP 文件与路由通过 php -l;前端 vue-tsc --noEmit 和 Vite production build 通过(3193 modules transformed)。
2026-09-02: 用户列表页面按参考截图完成视觉调整:筛选栏新增“用户画像”,用户 ID 与昵称同行显示,移除截图中不存在的状态列,金额统一为押金两位小数及其他金额带 ¥ 的格式;操作区恢复“订单记录/设置会员/余额充值/储值卡充值”四个按钮并按两列排列。订单记录沿用真实接口,用户画像跳转 /user-management/avatars,会员与充值按钮暂提示尚未接入,避免虚构资金业务;页面已通过 Prettier、ESLint、vue-tsc --noEmit 与 Vite production build(3193 modules transformed)。
2026-08-31: 下级设备改动已通过 php -l(DeviceLogic、DeviceController、route.php)、项目内 vue-tsc --noEmit 和 Vite 生产构建(3188 modules transformed)。Vite 仅报告既有 system menu 编辑弹窗的动静态导入提示;未直接执行数据库迁移或向真实设备网关下发指令。
2026-08-31: 二维码/解绑增量已再次通过相关 PHP 文件 php -l 与项目内 vue-tsc --noEmit;本地数据库已确认解绑权限菜单 ID 208 和测试设备 ID 14 的有效商户绑定。
2026-08-31: 回收功能前端已通过项目内 vue-tsc --noEmit 与 Vite 生产构建;既有 AgentLogic.php 再次通过 php -l。
2026-08-31: 二维码页改动已通过 DeviceLogic.php 的 php -l 和项目内 vue-tsc --noEmit;Vite 构建已启动并完成模块转换,继续保留项目既有 system menu 动静态导入提示。
Last checked: 2026-09-02
Frontend vue-tsc --noEmit: passed via the project-local binary.
Frontend Vite production build: passed; 3152 modules transformed.
Merchant PHP controller/logic: php -l passed after list summary and filter changes.
Browser verification: /merchant/subordinate-merchant loaded without console errors; agent-type tab switching and merchant-name search returned backend-filtered results.
Backend selected PHP files: php -l passed.
2026-08-26: 营业时间相关 PHP 控制器、逻辑、验证器和路由 php -l 均通过;前端使用项目内 vue-tsc Node 入口执行类型检查无输出错误。由于当前 8787 未监听,新增接口尚未进行运行时请求验证;数据库迁移也尚未执行。
2026-08-26: 数据统计接口验证成功:GET /core/merchant/statistics?id=2&start_date=2026-08-01&end_date=2026-08-03 返回业务 code=200、3 天明细、交易额 375.90、收益 67.66、订单 48;POST /core/merchant/statistics/export 返回 HTTP 200,导出内容 5064 bytes。
Local GET http://127.0.0.1:8787/core/captcha: HTTP 200, business code 200, image payload returned with matching JPEG MIME/signature after the fix.
Local cross-origin captcha request with an Origin header: HTTP 200 with Access-Control-Allow-Origin and JPEG data.
Local login preflight: OPTIONS HTTP 200; invalid-code POST HTTP 200 with the expected business error and CORS header.
pnpm command: not available on PATH, so package scripts were not invoked directly.
2026-08-26: Real HTTP self-test passed: POST /core/merchant/save created temporary merchant ID 74; GET /core/merchant/read returned submitted fields without password; name-filtered GET /core/merchant/index found 1 row; duplicate phone was rejected; DELETE /core/merchant/destroy removed the temporary record and the follow-up list query returned 0 rows.
2026-08-26: Frontend vue-tsc --noEmit passed after adding the profit-rate input.
2026-08-26: Frontend Vite production build passed after the add-merchant form update; 3155 modules transformed. Vite reported the existing static/dynamic import warning for the system menu edit dialog.
2026-08-26: AMap map integration passed vue-tsc --noEmit and Vite production build (3155 modules). Browser automation verification was unavailable because the local Node runtime is v22.21.0 while the browser tool requires v22.22.0 or newer.
2026-08-26: Confirmed local .env.local contains both AMap variables without recording their values; vue-tsc --noEmit passed and Vite build completed successfully after the Geocoder plugin fix.
2026-08-26: “我的设备”PHP 控制器、逻辑和路由均通过 php -l;前端项目本地 vue-tsc --noEmit 和 Vite production build 均通过(3156 modules,保留既有 system menu 动静态导入警告)。
2026-08-26: 真实 HTTP 自测通过:全量 12 条、车载 2 条、充电设备 10 条、在线 7 条、离线 5 条、SN/二维码/类型搜索均命中预期;绑定 1 台测试设备后,已绑定 1 条、未绑定 11 条、商户名称搜索 1 条。POST /core/device/export 返回 HTTP 200、正确 XLSX MIME 和 5984 bytes 文件。
2026-08-26: 铺货记录 PHP 控制器、逻辑、路由通过 php -l,前端 vue-tsc --noEmit 和 Vite production build 通过(3160 modules,只有既有 system menu 动静态导入警告)。
2026-08-26: 铺货记录真实 HTTP 自测通过:测试流水共 4 条(铺货 3、解绑 1),操作类型筛选分别返回 3/1 条;第二家商户、PB000001 二维码、TEST-POWER-0001 SN、设备类型 101 均精确返回 1 条;分页和空结果正确。解绑筛选导出返回 HTTP 200、XLSX MIME、4941 bytes。当前 12 台测试设备中 2 台已绑定、10 台未绑定。
2026-08-26: 去铺货真实 HTTP 自测通过:候选商户和未绑定设备列表可用;将 TEST-PASSBT-0002 成功铺货到商户 ID 75 后,重复提交归入 has_binded_sn,改投其他商户归入 binded_other_store_sn,不存在 SN 归入 not_find_sn;通过直接调用同一业务逻辑并注入代理 ID 2 验证无管理权设备归入 not_owned_sn。成功后未绑定候选从 10 降为 9,商户 device_count 从 0 增至 1、distributed=1,且新增一条 action_type=1 流水。当前 12 台测试设备中 3 台已绑定、9 台未绑定。
2026-08-26: 去铺货页面改为用户截图对应的商户经营数据宽表后,前端 vue-tsc --noEmit 通过,Vite production build 通过(3163 modules);仅保留既有 system menu 动静态导入警告。浏览器自动化仍因本机 Node v22.21.0 低于工具要求的 v22.22.0 未执行。
Files examined include root README, frontend package/env/router/login/http/config files, backend composer/env/start/config/process/session/captcha/login/route files, and current Git status/diff summaries.
2026-08-27: 代理相关 PHP 控制器、逻辑、验证器和路由均通过 php -l;代理分配相关前端文件通过 ESLint,vue-tsc --noEmit 通过;Vite production build 通过(3173 modules),仅保留既有 system menu 动静态导入警告。
2026-08-27: 真实 HTTP 自测通过:代理列表 7 条、初始可分配库存 9 台;1 台测试设备分配成功,额外不存在 SN 正确归入 not_find_sn,分配流水可查询;随后回收成功且设备重新回到当前账号的可分配库存。数据库最终仍有 12 台设备由管理员 ID 1 管理;自测保留 1 条分配和 1 条回收审计流水。
2026-08-27: 自测使用的临时 Webman 8787 进程已停止,验证结束后端口未监听。
2026-08-27: 执行 20260826_add_merchant_business_time.sql 后通过 information_schema.columns 复核 sa_merchant.business_time_des 唯一存在。
2026-08-27: 代理修改相关 PHP 逻辑、控制器、验证器和路由通过 php -l;相关前端文件通过 Prettier、ESLint、vue-tsc --noEmit,Vite production build 通过(3177 modules,仅保留既有 system menu 动静态导入警告)。
2026-08-27: 代理修改真实 HTTP 自测通过:读取代理 101,临时将运营模式改为 employee 并写入测试公司地址,二次读取确认持久化,再恢复为原始 agent/空地址;读取当前登录人 ID 1 返回“代理不存在或无权操作”。当前 Webman 版本对 PUT JSON 存在 post() 为空的运行时情况,AgentController::update() 已增加 rawBody() JSON 兼容读取。
2026-08-27: 合同管理相关 PHP 控制器、逻辑、验证器和路由通过 php -l;前端相关文件通过 Prettier、ESLint、vue-tsc --noEmit,Vite production build 通过(3180 modules,仅保留既有 system menu 动静态导入警告)。迁移已执行并确认 sa_agent_contract、隐藏页和两个权限菜单存在;浏览器实际加载 /agent/contract?id=101 成功,显示代理信息、三组上传区和提交按钮,无页面错误。
2026-08-28: 参照 https://5.catpay.com.cn/equipment/index 的“我的设备”操作区,完成设备管理、车载设备供电/断电、已铺货设备解绑和二维码:详情展示归属代理、当前管理者及商户绑定信息;供断电仅对 goods_type=107 显示并按旧 /cdy/tasks switch 协议下发;解绑保留绑定流水并同步商户设备数;二维码由项目已有 qrcode.vue 按真实机器码/扫描前缀生成。迁移 server/plugin/saiadmin/db/20260828_add_device_operations.sql 已执行,创建 sa_device_command_log 并补齐 core:device:read、core:device:power、core:device:unbind 权限。
server/scripts/seed_water_device_dashboard_demo.php --apply;固定批次 TEST-WATER-DEVICES-20260911,已复跑确认不重复写入、不刷新测试心跳或覆盖编辑。/water-devices 按参考图改为独立看板:统计、SN/企业/状态/区域/版本组合筛选、8条分页卡片、设备详情/维护、版本分布、30日真实用水趋势和当前异常表。组件、查询组合函数和状态/筛选后端策略分层,格式化代码。WaterDeviceDashboard + device/DeviceFilters + DeviceStatusPolicy 统一读取和导出,GET devices/dashboard、POST devices/export 需设备读取权限;写入复用原 WaterBusiness。故障优先,15分钟未上报离线,从未上报未接入;全量统计不随分页/筛选变化。migrate_water_device_usage.php --apply,新增按设备/日期唯一用水汇总;复制已有明确日期快照0条。DeviceReport原事务/设备锁内保存日累计最大值,跨日留存、重复不累加;无报告日期为null。没有虚构历史、OTA或故障工单,固件入口只管理版本档案。server/docs/WATER_DEVICE_DASHBOARD.md。WaterInventory 读模型新增 dashboard / export 接口,均需库存读取权限;全量统计不受搜索/分页影响,导出包含所有匹配规格。写入复用 WaterBusiness 事务和凭据去重。server/docs/WATER_INVENTORY.md。WaterLedgerDrawer.vue,不再限制最近 100 条;默认每页 10 条,可选 20/50/100 条,支持页码跳转、关键词、操作类型、起止日期、重置和空结果提示。WaterLedger 服务统一列表/导出筛选口径:企业名、商品名、关联凭据、说明,日期包含结束日全天;操作类型及中文名称由后端提供。保留原库存/配送九种流水,档案、收款等其他审计不混入。GET /core/water/ledger 返回分页及 kinds,POST /core/water/ledger/export 接收相同筛选,导出当前已查询条件的全部 XLSX(忽略 page/limit),超过 50,000 条明确拒绝。两接口均要求库存读取权限;临时文件位于系统临时目录,响应生成后删除。php tests/water_ledger_test.php 30 项、原 water_business_test.php 69 项通过,测试数据全部回滚。浏览器现有 20 条流水分成两页;第二页搜索星河返回第一页 1 条,下载 XLSX 解析确认 1 条星河数据加表头,空结果禁用导出,重置恢复 20 条。/water-dispatch 已从 WaterWorkspace 普通列表换为独立调度看板:真实片区任务池、日期/关键词/片区筛选、配送员状态、站点地图、路线摘要/完整路线、任务状态分页及当前页 Excel 导出。原企业/库存/预警页面保持共享组件,调度详情跳转带入关键词。WaterDispatch.php:看板、配送员档案、每日排班、车辆容量、待派单任务日期/时限/位置、区域和时限优先合单预览及原子确认。任务、库存、权益仍复用 WaterBusiness;完整路线单独查询,避免关键词/日期过滤漏站。php scripts/migrate_water_dispatch.php --apply:新增 driver/driver_shift/route 三张表,企业经纬度和配送计划/路线字段。旧任务姓名电话关联到 2 位既有测试配送员,未推断排班,当前容量使用可编辑默认 100 桶;3 单 24 桶、待派单 1/配送中 2 的状态未改变。GET /core/water/dispatch/board、GET /core/water/dispatch/route 需要配送读取权限,GET /core/water/dispatch/plan 和 POST dispatch 新 action 需要管理权限。合单按容量及相同片区校验,原始自由文本 HTTP 派单不再使用,单笔派单也生成经校验的路线。server/docs/WATER_DISPATCH.md。src/utils/waterAmap.ts 统一供水地图和选点弹窗的脚本/插件加载,复用既有高德脚本标识与本地 VITE_AMAP_KEY/VITE_AMAP_SECURITY_CODE,配置值不记入文档。DispatchLocationPicker.vue 为配送计划增加地址搜索及地图选点,确认回填后保存才写入配送任务坐标;保存后切换到对应任务组展示。后端沿用已有 schedule 校验,企业地址和库存/权益不受影响。php scripts/seed_water_shenzhen_coordinates.php --apply,仅给任务9/10/11及其对应3个【测试0910】企业更新地址、深圳南山区片区和坐标;任务新增 origin_address/origin_longitude/origin_latitude 记录出发点,结构由原 dispatch 增量迁移扩展。