Commit Graph
184 Commits
Author SHA1 Message Date
kate522 05f16d5fd7 feat(vip): collapse the structured membership groups into accordions
The structured groups introduced in the previous commit rendered every row
fully expanded, which buried the fields an admin actually came to edit.
They now behave like accordions and start collapsed.

- 会员商品, 一级/二级会员续费 and 积分兑换明细 opt in via `collapsible`
  on the group schema. Each row shows only its header, carrying a summary
  line built from `listPreview` (价格:99.00 · 3 个月 / 积分:500 · 30 天),
  and expands on click. `defaultCollapsed` keeps the collapsed default;
  setting it to false restores the previous always-open behaviour.
- 升级价格 splits into its three reference-theme blocks
  (月费升月费 / 月费升永久 / 永久升永久) through a new `sections` option
  on the accordion type, each block independently collapsible.
- AksField keeps the existing open state keyed by index so adding or
  removing a row does not disturb the others, and the drag handle, delete
  button and chevron are excluded from the header toggle.
- Non-collapsible groups are untouched: the accordion markup only renders
  when the schema opts in.

Verified: php -l on the changed PHP, 656 Node contract tests passing
(including new assertions that every collapsible group is opted in, that
the upgrade sections cover exactly the declared fields, and that the SPA
mirror matches), and the admin SPA builds.
2026-10-18 17:51:28 +08:00
kate522 af8dccdc45 feat(vip): restructure membership settings into structured groups
Replace the pipe-delimited VIP configuration with structured groups and
accordions, matching the reference theme (Zibll) 1:1.

Settings:
- 会员等级 keeps only the nav buttons and the two level switches, plus the
  "must enable level 1 first" guard. Level identity fields (display name,
  intro, benefits HTML, icon) and 会员商品 move into the 一级会员/二级会员
  tabs of 会员购买选项, so each level is configured in one place.
- Level tab titles bind to the configured display name via a new
  titleKey/titleFallback hook in AksFormRenderer.
- 会员商品 is a repeater with exactly the reference fields: 执行价/原价/
  促销标签/标签颜色/会员有效时间/时长单位. The tag colour uses the full
  b+jb palette and now reaches the product card instead of a hardcoded
  jb-yellow.
- 会员续费 gains the 自定义商品 type backed by per-level renew-product
  groups; the discount becomes a 0.1-9.9 slider with float sanitizing.
- 会员升级 becomes the reference three-block accordion (month-to-month
  day-rate, jump to permanent, permanent to permanent), each with its own
  price, strikethrough price, promo tag and tag colour.
- 积分兑换明细 is a repeater (points/level/duration/unit) where the
  duration accepts the literal Permanent.

Runtime:
- PaymentRuntime normalizes the structured groups into purchasable rows
  (money normalization, palette validation, zero-price and out-of-range
  rows dropped) and no longer emits any *_raw strings.
- The catalog builder carries tag_class through and routes renewals to the
  renew-product group under 自定义商品.
- The points-exchange order note carries time/unit so month durations use
  calendar math instead of a 30-day approximation.

Dead code removed: parse_level_products, parse_packages,
parse_upgrade_product_line and their pipe-splitting, plus nine zero-call
PaymentRuntime proxy methods in HomeCms. No dual-format or migration path
was added; SchemaProvider already falls back to defaults when a stored
group value is not an array, so existing pipe strings degrade safely.

Verified: php -l on every changed PHP file, 655 Node contract tests passing
(14 new ones in vip-settings-structured-contract.test.cjs exercising the
real schema build and runtime normalization via a PHP harness), and the
admin SPA builds.
2026-10-18 17:14:00 +08:00
kate522 dcb80cff6e fix(vip): let 积分兑换说明 support HTML like the reference theme
The points-exchange desc was stripped at all three layers: no sanitize_type on the schema fields (save ran sanitize_textarea_field), the runtime re-ran the same strip, and the points modal escaped it with esc_html. The reference theme declares sanitize=false and concatenates the value raw into the modal.

- Schema (SPA + PHP): sanitize_type html, rows 2, help text noting HTML support

- PaymentRuntime: output via wp_kses_post

- VipServiceModal points modal: render via wp_kses_post instead of esc_html

Verification: new contract test covers schema/runtime/modal layers; node --test tests/**/*.test.cjs 641/641 pass; php -l clean on both PHP files.
2026-10-18 15:06:29 +08:00
kate522 7f556892ad fix(vip): align modal copy fields with reference theme backend
Rename and reposition the two VIP modal copy settings to match the zibll reference backend, drop dead renew/upgrade desc config, and align copy defaults.

- pay_user_vip_desc: modal top intro -> 会员介绍, textarea -> input (one-line), zibll help text, default copy from zibll

- pay_user_vip_more: bottom note -> 开通会员更多内容, rows 4 + sanitize_type html (HTML was stripped on save), zibll help text and three-div default

- Both fields move from the member-level group to a new 界面文案 (modal-copy) group at the end of the VIP section; dependsOn any-of-two-levels adapted from zibll's pay_user_vip_1_s dependency

- Copy defaults synced in three places (SPA schema, PHP schema, PaymentRuntime fallbacks, since Settings::get_all() does not merge schema defaults): 黄金会员/钻石会员, 尊享专属特权, zibll four-item equity list

- Remove vip_renew_desc / vip_upgrade_desc: AKSRT has no renew/upgrade card render surface (render_vip_card takes no type) and zero consumers repo-wide; zibll renders them on page-level cards only

- pay_user_vip_1/2_equity and pay_vip_pass_charge_desc gain sanitize_type html so admins no longer lose list/HTML markup on save

Verification: new tests/widget-system/vip-modal-copy-fields.test.cjs (6 contract tests); node --test tests/**/*.test.cjs 634/634 pass; php -l clean on both changed PHP files; assets/admin npm run build succeeds
2026-10-18 14:30:15 +08:00
kate522 cf39e72126 feat(extensions): align Baidu push with reference theme
与参考主题(zibll)1:1 对齐百度推送,并清掉存量双路与死代码:

- 新增普通/快速收录通道开关,快速收录走 &type=daily;自动推送 JS 与参考实现一致

- 分类法 saved_term 推送(可推送类型推导公开分类法,排除 post_format,aksrt_baidu_push_taxonomies 可过滤)

- 推送结果写入 meta 去重,成功后不重复提交;编辑页 Metabox 展示结果并提供「重新提交」(nonce 校验后强制推送)

- 配置面收敛到扩展页单一 canonical 键:删除 SEO 页 seo_baidu_* 重复面与两处合并逻辑,不写迁移兜底

- 清理死代码:push_batch(零调用)及 batch_size、从不写入的 baidu_push_verified、零消费的熊掌号 xzh_* 七键;baidu_push_log_enabled 接入 log()

新增 PHP harness 驱动真实逻辑(通道/去重/重试/重新提交/术语/面板渲染)+ 参考主题契约测试;tests/admin 与 tests/widget-system 全量通过。
2026-10-13 21:01:03 +08:00
kate522 740bd48f42 refactor(users): drop dead WeChat notify settings surface
users/wechat schema 页写入 aksrt_settings 的 wechat_notify_* / wechat_app_* / wechat_template_* 等 15 个键全仓无任何消费方,与活配置面 aksrt_wechat_settings(WeChatNotify 读取、/users/wechat-template 页管理)构成双路;PHP 与 JS 两份 schema 的字段集早已不一致(15 vs 10),是死面腐烂的直接证据。

移除 SchemaUsers 的 wechat 分组、JS schema 文件及其注册、菜单 schema 页条目,微信配置收敛为单一自定义页入口;契约测试锁定死键零残留与活配置面唯一。
2026-10-13 21:00:49 +08:00
kate522 cdab7a5e5f refactor(admin): drop AI connection config page for WP AI Connectors
Remove the "AI 连接配置" page under 扩展&增强 together with the theme's
own OpenAI-compatible provider path. AI generation (SEO, summary, tags)
now runs solely on the WordPress AI Client (wp_ai_client_prompt), with
connections configured through WordPress AI Connectors.

- AI.php keeps only the WP AI Client path: no provider config storage,
  no theme fallback, no rate limiting
- Drop /ai/status, /ai/settings and legacy /ai/seo-suggest routes plus
  their handlers
- Remove dead compat code: AISEO::suggest, AIPrompt::build_seo_prompt
  and the front-submit AI assist flow (its buttons were never rendered
  and its endpoints were never registered)
- Update SEO schema/meta-box guidance that pointed at the deleted page
- Add ai-config-removal contract test and refresh the toast and
  user-features contracts affected by the cleanup
2026-10-13 19:32:34 +08:00
kate522 8023e2e472 feat(after-sale): 支持撤销售后工单并接入人机验证 2026-10-07 09:20:59 +08:00
kate522 1a9a2762f3 feat(payment): 新增「售后策略」配置与前台售后弹窗链路
- 后台「支付&付费 → 售后策略」:售后功能开关、申请时效、超时效提示(HTML)、
  预置原因、申请售后说明、所需材料、申请说明占位文案、可上传图片开关;
  PHP schema 与 SPA schema 1:1 对齐,group 字段支持拖拽排序
- PaymentRuntime 新增售后设置解析(缺键回退 schema 默认、HTML 走 kses、
  中文原因稳定 slug)与 after_sale_expired 时效判定(支付时间起算)
- 售后工单支持举证图片(order_aftersale.images 列,最多 3 张,
  复用评论图片上传校验)与上传路由;订单视图模型输出 after_sale 按钮状态
- 前台申请 / 超时效提示 / 售后详细三个独立弹窗(modal-mini + 蓝色炫彩头),
  订单详情弹窗不再内嵌售后表单并清理相关死代码
- 新增 after-sale-policy 契约测试与运行时 harness,527 项契约测试通过
2026-10-06 22:21:47 +08:00
kate522 194280af72 feat(admin): 移除支付中心的“下载记录”子菜单页面
下载不做记录,支付中心下的“下载记录”页面及相关链路整体删除。

删除的内容:
- src/Admin/Native/PayDownloadLogs.php(页面渲染、批量删除、
  令牌查询)整文件移除。
- PayMenus 中的 download_logs 子菜单注册(概览页快捷入口由同一
  份 pages() 数组生成,随之消失)。
- functions.php 中的 PayDownloadLogs::init() 接线。
- 各入口跳转链接:付费文章列表的操作按钮、用户列表“积分分成”
  列的操作链接、用户列表行操作。
- “保存用户下载记录(条) / 保存文章下载记录(条)”两个设置项及其
  消费链:SchemaPayment.php、assets/admin 侧 knowledge.js 的字段
  与默认值、PaymentRuntime.php 的 user_log / post_log。
- 前台下载中心登录提示里的“管理下载记录”文案(该处本就没有
  可跳转的管理页面)。

未改动内容:
- aksrt_dl_token 数据表仍由 DBMigrator 创建、uninstall.php 仍会
  删除:存量站点可能留有历史令牌行,属数据而非功能,迁移风险
  高于收益。
- 独立下载页面开关(down_alone_page)与远程文件转本地等下载
  行为配置保留。
- 资源下载量(_aksrt_res_download_count)仍是只读口径:下载走
  普通外链,主题内没有写入方,本次不新增下载统计。

download-direct-contract.test.cjs 中原有“下载记录页面展示历史
令牌”用例反向改写为三项断言:页面与接线已删除、记录数设置项
已注销、下载仍不做记录。

验证:node --test "tests/*/*.test.cjs" 517 项全部通过;
php -l 全部改动文件无语法错误;重新构建 admin SPA,确认产物
中不含被移除的字段与菜单。
2026-10-06 14:35:14 +08:00
kate522 2595bb02db fix(frontend): 统一计数缩写口径并将流水号改为订单号
前台各处评论、浏览、点赞、收藏、已售数量统一走
aksrt_format_number() 缩写(≥10000 → 1.2W+,≥1000 → 1.5K),
缩写后缀统一为大写 K,与既有 W+ 保持一致。PHP 渲染缩写文本的
同时保留 data-base-count 原始整数,JS 增量计算不受影响;
article.js / comment-ajax.js 内的 formatStatCount / formatCount
与 PHP 规则保持同一套,避免点赞收藏后文本回跳成全量数字。

文章详情页购买信息卡、用户中心创作者收益明细、后台分成与提现
管理页、支付设置页以及四封订单/提现邮件模板中,面向用户展示的
「流水号」字样统一改为「订单号」;代码注释、内部错误码与
WP_Error 文案保留原表述。

验证:node --test "tests/*/*.test.cjs" 508 项全部通过;
php -l 检查全部改动文件无语法错误;重新构建 admin SPA 与
前台 runtime bundle。
2026-10-06 14:13:51 +08:00
kate522 b75bf197a3 feat(extensions): 新增系统工具页面与前台投稿分片上传 2026-10-06 11:09:05 +08:00
kate522 af72364078 feat(security): 新增网站安全扩展模块并支持修改手机脱敏验证 2026-10-06 09:26:55 +08:00
kate522 7b55cfb190 feat(payment): rebuild order details and asset records 2026-10-05 16:42:25 +08:00
kate522 979639342a feat(rewards): add toast and captcha flow 2026-10-05 03:51:18 +08:00
kate522 6037ca184b fix(sms): collapse provider settings by default 2026-10-05 02:29:42 +08:00
kate522 5091da6a60 feat(sms): align provider and verification flow 2026-10-05 02:13:52 +08:00
kate522 88661e1574 fix(identity): preserve HTML in application intro 2026-10-04 22:02:20 +08:00
kate522 59901b9e10 feat(identity): align auth flow with reference theme 2026-10-04 21:10:12 +08:00
kate522 0374de3fec fix(revenue): drop dead affiliate_settlement_days field from JS schema
The immediate-credit model removed settlement, but only the PHP schema
side was cleaned. The JS admin schema still rendered a savable-but-unread
"结算观察期" field. Remove it from both the defaults and field list, and
rebuild the admin SPA bundle so it no longer surfaces.
2026-09-28 21:50:10 +08:00
kate522 bd0abf891c fix(share): register share schema in global registry
The share schema was imported but never added to globalSchemas, so
getSchema('global', 'share') returned null and the page fell back to
route meta with empty groups, rendering "暂无配置项".
2026-09-27 14:19:19 +08:00
kate522 cf6c11852e feat(share): align social sharing runtime 2026-09-27 13:49:32 +08:00
kate522 6f2adfaef1 feat(float): rebuild right floating buttons 1:1 with reference theme
- Storage: aksrt_floating_buttons now uses reference float_btn shape
  (fixed 8 sortable types, more-buttons group); single source of
  defaults/sanitize in AKSRT\Settings\FloatButtons
- Admin SPA: CSF-parity page split into components/floating/*
- Frontend: aksrt_float_right() on wp_footer mirrors zib_float_right()
  (wp_is_mobile, PC tooltip, toggle-theme, ontop/scrollTopTo, qrcode,
  wechat/qq, pay_vip, add, more)
- CSS: floating-buttons.css rewritten from reference rules; legacy float
  rules removed from main/part/shell/overlay-primitives
- Remove dead code: reorder route/API, get_floating_buttons_items,
  floatingSchema, floatActionsId, float-theme-toggle bindings,
  common/floating.css, aksrt-runtime-win.php, .aksrt-float-btn
2026-09-26 20:44:50 +08:00
kate522 1c1d3f17b7 refactor(admin): remove download config settings page and chain
- Drop the download config menu entry, route and schema from the settings SPA
- Remove the PHP schema section, the server-side alias validation and the
  reserved-alias list that only served that section
- Unregister aksrt_download_config from standalone import/export keys
- Drop the contract tests that locked the removed page; the Download.php
  runtime contracts stay untouched
2026-09-24 18:29:38 +08:00
kate522 66f8bb933d feat(admin): replicate reference post-page settings page 1:1
- rewrite post_list.page schema (JS+PHP 2.0.0) with the reference field set:
  three cover types with option fieldsets (image 20-200% / slide CFS_Module::slide
  full config with nested dependsOn / video 0-200%), hide-time, p_indent,
  breadcrumbs (home-text as switcher), prevnext, authordesc, maxheight slider,
  yiyan block, copyright, button toptext, front/after content, related posts
- migrate share_s to share page basic group and post_rewards_* to users
  features rewards group (runtime keys unchanged)
- remap get_post_page_settings() to reference keys, normalize slide cover
  option, hardcode non-reference runtime flags (reading progress, favorites,
  author card), drop dual-path legacy aliases
- wire slide option into aksrt_render_slider (effect/direction/spaceBetween/
  speed/interval/loop/autoplay/nav/pagination, scale vs fixed-height modes),
  clamp image scale 20-200 and video scale 0-200 with 16:9 fallback,
  wire p_indent container class + CSS, resync frontend-tags fallback and
  FacadeBulk fallback, remap PostMetaBox cover keys
- add post-page reference contract test; update stale cover-height and
  user-features contract tests

Validation: node --test 464/464 pass, audit-admin-contracts errors=0,
vite build ok, build-public-assets ok, php -l clean.
2026-09-24 16:13:00 +08:00
kate522 a8060fa71d fix(admin): replicate reference advanced-filter settings page 1:1
- Rewrite post_list/filter schema (PHP + JS) with the reference theme's
  field set, order, defaults and dependencies; topics fields excluded
  (no topics taxonomy), AKSRT private filter_* toggles dropped
- Support dynamic checkbox options in AksField (options as formData key)
  so cat_custom_filter.custom_filters lists the custom_filter dimensions
- Map PostDisplay filter settings to the new keys without legacy fallback;
  cat_custom_filter now carries lists/dropdown fieldsets and array filters
- Wire the archive chain to reference semantics: cat_custom_filter
  overrides per-category filter projects, custom_filter_show gates the
  default display, dimension chip groups render on category/tag archives
- Filter archive queries (main query + AJAX endpoint) by _aksrt_cf_{key}
  meta with exact-quoted LIKE matching
- Add advanced-filter settings reference contract test
2026-09-24 15:39:25 +08:00
kate522 4d6b2c6045 fix(admin): replicate reference post-feature settings page 1:1
- Rewrite post_list/feature schema (PHP + JS) with the reference theme's
  field set, order, defaults and dependencies; print/PDF toggles dropped
- Move the migrated reference fields off the post-page schema (lightbox
  six switches, post like, default view/like ranges) to kill the dual path
- Map PostDisplay::get_post_page_settings() to the new keys without legacy
  fallback: imagelightbox* (pc_s/m_s checkboxes merged), post_like_s,
  post_default_mate ranges, article_nav + article_nav_mobile_nav_s
- Wire article_nav runtime: content data-nav/no-nav gate with per-post
  override, mobile drawer TOC box, imgbox_type config
- Seed random views/likes on new posts from post_default_mate ranges
- Keep the print stylesheet enqueued unconditionally (no switch); drop the
  dead print/PDF settings and needs_print requirement
- Add post-feature settings reference contract test
2026-09-24 14:29:04 +08:00
kate522 c331b2aeef fix(admin): replicate reference comment settings page 1:1
- Rewrite post_list/comment schema (PHP + JS) with the reference theme's
  field set, order, defaults and dependencies; smilie, comment location
  and quick-reply fields excluded
- Map PostDisplay::get_comment_display_settings() to the new keys without
  legacy fallback: close_comments, comment_paginate_type (ajax_lists),
  user_edit_comment master switch, WP comment_order for sorting
- Wire comment_box_hide visibility gate (signin / vip / vip_2 / auth)
- Drop dead comment settings (order/per_page/depth/avatar/hot threshold/
  empty text/city), the dead post_close_comments field, and city readers
  now fully owned by the IP-geo extension
- Add comment settings reference contract test
2026-09-24 13:43:51 +08:00
kate522 264f2c51b2 fix(admin): replicate reference front-submit settings page 1:1
- Rewrite post_list/submit schema (PHP + JS) with the reference theme's
  field set, order, defaults and dependencies; topics fields excluded
- Map PostDisplay::get_submit_settings() and the FrontSubmit runtime to
  the new keys without legacy fallback
- Guest submission follows the capability matrix; category required on
  submit; pay limits read from the cash/points fieldset
- Drop dead front_submit_* settings, template branches and JS config
- Add submit settings reference contract test
2026-09-24 12:29:19 +08:00
kate522 bd7586a3b7 refactor(attachment): 头像与背景图上限并入前端图片上传限制
- 移除独立的「用户头像与资料卡图片」分组与 avatar_max_size 字段,
  与参考主题保持一致:头像/资料卡背景图与投稿评论图片共用
  upload_img_size 同一套用户类型 × 大小配置
- 5 处消费方(REST 头像/背景上传、后台头像字段、用户中心头像与
  封面配置)全部改读 UploadLimits::limit_bytes('image'),提示语
  改用 size_format 与实际上限对齐
- 附件配置仅剩上传大小/上传格式/上传行为三组;管理端 SPA 已重建,
  421 项契约测试通过
2026-09-24 11:19:47 +08:00
kate522 95cdad5c5d fix(attachment): 补充上传大小配置的单位与标签
- SPA Schema 的大小限制 repeat 子字段补上 unit: 'M',避免数字被
误读为数量;PHP Schema 早已带单位,两侧一致
- 标签「可上传」改为「可上传大小」,与单位 M 搭配后语义明确
- 管理端 SPA 已重新构建,421 项契约测试通过
2026-09-24 11:12:09 +08:00
kate522 d4bb43a275 feat(attachment): 按参考主题重构后台上传配置套件
- 新增 AKSRT\Attachment\UploadLimits:按用户类型解析图片/视频/文件
  上传上限(取最大值+默认兜底,钳制到 PHP 上限)、允许/禁止格式过滤、
  上传自动重命名、非管理员附件页 404;不含论坛角色
- 后台「附件配置」重做:大小限制/格式限制/上传行为三组配置,
  PHP Schema 与 SPA Schema 逐字段镜像(v1.2.0)
- 定制页 AttachmentPermissions.vue 改为标准 schemaPage,清理失效的
  API 包装、组件加载器与旧配置键(submit_image_max_size 等)
- 投稿/评论上传、批量上传数量全部切换到新链路;AksField 数字组件
  支持单位展示
- 更新 captcha harness 与 front-submit 契约测试;421 项测试通过
2026-09-24 11:04:49 +08:00
kate522 3dd3f71c97 feat(invite-code): one-time invite codes with rewards, usage log, import/export
- 一次性邀请码:批量生成(次数/有效期/备注/四类奖励)、原子消费防超发、
  开启注册验证时奖励延迟发放(账本幂等键)
- Zibll 兼容文本导入(points/balance/vip/vip_time/level_integral,分隔符可自定义,
  第三段标识存为备注)、CSV 全字段导出与可回流文本导出
- 使用记录表 aksrt_invite_code_usage + 后台使用者列;注册表单 400ms 防抖实时校验
- 注册门控三模式(close/open/must)设置链路(PHP schema + admin SPA schema)
- 契约测试覆盖建表/消费竞态/导入导出/备注全链路
2026-09-21 20:53:36 +08:00
kate522 20387fdb06 feat(affiliate): attribution modes, settlement hardening, trusted proxies, QR and poster
- 归因模式可配置(first_touch/last_touch)、停佣开关、懒结算与佣金基数降级复查
- aksrt_get_client_ip 接入 aksrt_trusted_proxies 信任代理名单,防 CDN 头伪造
- 收益页 Canvas 二维码 + 750x1000 推广海报(本地打包 qrcode.min.js,无 CDN)
- 契约测试固化归因/海报/信任代理链路
2026-09-21 20:53:22 +08:00
kate522 381256ed59 feat(admin): host the ban/report settings page in the SPA schema
The users-interaction sidebar entry 禁封/举报 was a redirect stub; it is
now a schema-driven settings page replicating the reference theme
field-for-field:

- user_ban_s / user_report_s switches, ban_preset_reason and
  ban_appeal_keys as repeatable group rows ({t}), ban_appeal_desc and
  user_report_desc HTML textareas with the reference default copy, and
  user_report_img_s — keys, labels, help texts, defaults and ordering
  all mirrored in SchemaUsers.php and the SPA report.js schema
- UserRuntime::ban_settings()/report_settings() and
  BanAppeal::settings() read the new group-array values with fallback
  to the previous line/comma formats
- delete the self-built SPA queue pages (UsersBan, UsersReport,
  ReportDetailDrawer, UsersMessages) that the reference theme does not
  have; legacy /users/report bookmarks still redirect to the native page
- contract tests: lock the schema keys/labels, the schemaPage mount and
  the deleted-file state; drop reads of removed components
2026-09-19 22:24:55 +08:00
kate522 ce21cc2b9b revert fa476af70e
revert refactor(pay): 充值商品以充值金额为唯一真值,移除销售金额与折扣

充值商品行此前是双值模型:price 充值金额决定到账、pay_price 销售金额决定支付,
pay_price 为 0 时回落到 price,否则即「付更少拿更多」的折扣。该模型把三处耦合在
一起——后台 schema 暴露销售金额输入、线格式首段携带支付额、下单时计价与到账分离。
只改其中一处就会出现「后台不再可配但前端仍在展示折扣」或「展示已合并但下单仍按
旧价扣款」的静默不一致,故本次三层一并收口,充值金额即实际支付金额。

后台 schema 有两份且必须同步:SPA 侧 recharge.js 负责渲染表单,PHP 侧 SchemaPayment
负责默认值与清洗。只改一份会让界面不再可配而默认值仍在写 pay_price。

线格式三段 "支付|到账|标题" 保持不动,仅为兼容历史已存的字符串配置:支付段不再读取
任何配置来源,恒等于充值金额,故历史配置里的折扣价会被忽略。旧数据无需迁移,pay_price
键留在库中只是不再被读取,从重建后的后台保存一次即自然消失。

行为变化(需知悉):
- 已配置折扣的站点,其用户将按充值金额全额支付,即对这些用户是涨价。这是「不做优惠
  和促销」的必然结果。
- parse_packages 的校验口径随之变松:有效性只由到账段决定,故 "abc|10|x" 这类首段
  非法、到账段合法的行会被解析为 10 元档,而旧实现两段都作金额校验、会丢弃。理由是
  支付段已不表达任何含义,因一个无用字段是垃圾就丢弃一条本来有效的档位只会让运营困惑。
  既有数据不受影响——旧格式化器写出的首段恒为数字,只有手改出的脏数据才可能命中。
  该取舍已写入方法文档。

- recharge.js / SchemaPayment:删除 pay_price 字段与三档默认值中的该键,help 去掉
  「同时支持设置折扣」
- PaymentRuntime::format_product_lines:不再读取 pay_price,支付段由充值金额充当
- BalanceServiceCharge::parse_packages:不再产出 pay_price 键,金额唯一来源为到账段
- BalanceServiceCharge::create_charge_order:支付额与到账额同源于 price
- credits-wallet.php:移除销售金额行、「省X」折扣徽标及仅服务折扣的差额计算
- BalanceServiceRest:仅注释改写,说明积分路径的 pay_price 是售价而非折扣
- 新增 tests/widget-system/recharge-product-price-contract.test.cjs:5 条跨层契约

范围边界:积分商品 pay_points_product 的 pay_price 是「积分|支付|标签」里的实际售价,
本身不含折扣语义,保留不动。契约测试以正向断言固定该边界,避免日后被误当作同类问题
一并删掉。

验证:新增契约测试 5 项通过;node --test 全量 375 项 / 375 通过(原 370 项);PHPUnit
44 项 / 106 断言通过;改动的 PHP 文件 php -l 全部通过。变异验证:把 create_charge_order
改回按 pay_price 计价、把 pay_price 重新注入模板,对应断言分别转红,证明回归测试拦得
住回退。另以反射调用真实代码做端到端执行验证 11 项全过,覆盖结构化数组 -> 线格式 ->
商品数组全链路、数组内残存 pay_price 被忽略、历史折扣字符串(98|100|旧折扣档 取 100)、
到账段为 0 或非法时跳过等边界。

部署提示:assets/admin/build 为部署时构建、未入库,后台表单的改动需在目标机执行
cd assets/admin && npm ci && npm run build 后才会生效。前端 credits.js 走 filemtime
版本号,无需手工清缓存。
2026-09-19 03:25:12 +00:00
kate522 fa476af70e refactor(pay): 充值商品以充值金额为唯一真值,移除销售金额与折扣
充值商品行此前是双值模型:price 充值金额决定到账、pay_price 销售金额决定支付,
pay_price 为 0 时回落到 price,否则即「付更少拿更多」的折扣。该模型把三处耦合在
一起——后台 schema 暴露销售金额输入、线格式首段携带支付额、下单时计价与到账分离。
只改其中一处就会出现「后台不再可配但前端仍在展示折扣」或「展示已合并但下单仍按
旧价扣款」的静默不一致,故本次三层一并收口,充值金额即实际支付金额。

后台 schema 有两份且必须同步:SPA 侧 recharge.js 负责渲染表单,PHP 侧 SchemaPayment
负责默认值与清洗。只改一份会让界面不再可配而默认值仍在写 pay_price。

线格式三段 "支付|到账|标题" 保持不动,仅为兼容历史已存的字符串配置:支付段不再读取
任何配置来源,恒等于充值金额,故历史配置里的折扣价会被忽略。旧数据无需迁移,pay_price
键留在库中只是不再被读取,从重建后的后台保存一次即自然消失。

行为变化(需知悉):
- 已配置折扣的站点,其用户将按充值金额全额支付,即对这些用户是涨价。这是「不做优惠
  和促销」的必然结果。
- parse_packages 的校验口径随之变松:有效性只由到账段决定,故 "abc|10|x" 这类首段
  非法、到账段合法的行会被解析为 10 元档,而旧实现两段都作金额校验、会丢弃。理由是
  支付段已不表达任何含义,因一个无用字段是垃圾就丢弃一条本来有效的档位只会让运营困惑。
  既有数据不受影响——旧格式化器写出的首段恒为数字,只有手改出的脏数据才可能命中。
  该取舍已写入方法文档。

- recharge.js / SchemaPayment:删除 pay_price 字段与三档默认值中的该键,help 去掉
  「同时支持设置折扣」
- PaymentRuntime::format_product_lines:不再读取 pay_price,支付段由充值金额充当
- BalanceServiceCharge::parse_packages:不再产出 pay_price 键,金额唯一来源为到账段
- BalanceServiceCharge::create_charge_order:支付额与到账额同源于 price
- credits-wallet.php:移除销售金额行、「省X」折扣徽标及仅服务折扣的差额计算
- BalanceServiceRest:仅注释改写,说明积分路径的 pay_price 是售价而非折扣
- 新增 tests/widget-system/recharge-product-price-contract.test.cjs:5 条跨层契约

范围边界:积分商品 pay_points_product 的 pay_price 是「积分|支付|标签」里的实际售价,
本身不含折扣语义,保留不动。契约测试以正向断言固定该边界,避免日后被误当作同类问题
一并删掉。

验证:新增契约测试 5 项通过;node --test 全量 375 项 / 375 通过(原 370 项);PHPUnit
44 项 / 106 断言通过;改动的 PHP 文件 php -l 全部通过。变异验证:把 create_charge_order
改回按 pay_price 计价、把 pay_price 重新注入模板,对应断言分别转红,证明回归测试拦得
住回退。另以反射调用真实代码做端到端执行验证 11 项全过,覆盖结构化数组 -> 线格式 ->
商品数组全链路、数组内残存 pay_price 被忽略、历史折扣字符串(98|100|旧折扣档 取 100)、
到账段为 0 或非法时跳过等边界。

部署提示:assets/admin/build 为部署时构建、未入库,后台表单的改动需在目标机执行
cd assets/admin && npm ci && npm run build 后才会生效。前端 credits.js 走 filemtime
版本号,无需手工清缓存。
2026-09-18 23:56:31 +08:00
kate522 3bec2a5777 fix(icons): 图标类名统一为 FA5 语法并修正一处失效图标名
主题打包的是 Font Awesome 5,solid 字形必须带样式类 fas。全站有 18 处仍写成
FA4 的裸 fa fa-*,FA5 下并不存在对应字形,表现为图标位置空白;而 FA5-only
契约此前只扫描部分运行时文件,没有覆盖模板与渲染器,这些写法一直没被拦下。

同时发现一处真实的失效图标名:profile-level.php 用的是 fa-line-chart。这是
FA4 名称,FA5 把该系列重命名为 chart-*,正确写法是 fa-chart-line。

- 18 处 "fa fa-*" 补上 fas:首页模板、组件标记、评论表单、用户等级页、菜单
  装饰、SchemaPayment 促销标签默认值、VIP 服务弹窗、ContentRenderer /
  GeneralRenderer、后台卡片样式校验、pay-vip.js
- profile-level.php 的 fa-line-chart 修正为 fa-chart-line
- 三处注释里的示例值同步为合法形态("fa fas fa-star"),避免被当成正确写法
  抄走;credits.css 注释中直接写出旧字体族名的一处改为描述性表述,否则会被
  FA5-only 契约当作违规命中
- legacy-navigation-contract:FA 资产断言改为跟随 aksrt_enqueue_fontawesome()
  这一间接层(注册点已从脚本文件移到 inc/helpers/aksrt-icons.php),并把
  fa-zibsvg-<key> 识别为项目自有 SVG sprite 族,而非 Font Awesome 字形
- vip-points-exchange-modal 契约同步断言 fa fas fa-angle-right

验证:改动前在干净 HEAD 上跑 node --test(已追踪用例)为 327 项 / 325 通过 /
2 失败,两项失败正是本次修复的失效图标名断言与 FA 版本号断言;改动后 333 项 /
333 通过(含 5 个本地专用用例共 370 项全部通过)。php -l 全部改动的 PHP 文件
通过。
2026-09-18 23:17:05 +08:00
kate522 aad83072a8 refactor(pay): 统一流水号,移除订单/流水/提现三类号码前缀
订单号、资金流水号、提现单号此前各自带前缀配置,同一笔业务在不同界面会
呈现多个号码口径,对账与客诉排查都要先做一次前缀换算。现收敛为单一流水号
分配器,不再按业务类型区分号码形态。

- 删除 payment_order_prefix / payment_flow_prefix /
  payment_withdrawal_prefix 三项配置项及其生成、校验链路
- BusinessNumber::allocate() 成为唯一入口,输出 {YmdHis}{6 位随机},
  共 20 位无前缀,满足 VARCHAR(32) 与网关 out_trade_no ≤ 32 的限制
- 订单、资金流水、提现、卡密渠道交易号四处生成点全部改走该分配器
- 后台支付中心表头「订单号 / 提现单号」统一为「流水号」
- 前台用户中心与订单卡片文案同步统一;前台订单详情合并为单行流水号,
  账本流水号不再下发到前台,避免同一单据出现两个对外号码
- 同步更新 financial-flow / pay-card / card-pass-order-numbers 契约测试
  与 BusinessNumberTest 断言

按用户要求不做双路逻辑与数据迁移,直接替换为统一方案,历史行不做转换。

验证:node --test tests/**/*.test.cjs 362/364 通过,新增 37 项契约;失败的
2 项在改动前的干净 HEAD 基线上同样失败(基线 327 项 / 失败 2 项),分别命中
遗留的 fa fa- 标记与 FA 版本号断言,与本次改动无关。phpunit 44 项 / 103
断言全部通过;php -l 全部改动的 PHP 文件通过。
2026-09-18 22:14:27 +08:00
kate522 d2447f8aa6 fix(pay): 修复后台收款接口显示顺序不可用并接入收银台排序
后台「收款接口 → 收款接口显示顺序」有三处缺陷叠加:
1) sortable 渲染组件读取 item.label,而 schema 写的是 title,导致 6 行
   只剩上下箭头;
2) SchemaProvider 的 sortable 分支遍历 items 定义重建数组,而该字段的
   键顺序本身就是配置值,用户排序被静默改回 schema 静态顺序;
3) 真正构建收银台支付方式列表的 aksrt_list_cash_payment_methods() 使用
   硬编码顺序,Gateway::get_available()/apply_sdk_order() 那套排序逻辑
   无任何调用方,配置对前台毫无影响。

改为:schema 项统一使用 label(JS 与 PHP 两处对齐);sortable 持久化按
提交键顺序重建,items 定义仅作白名单;新增唯一排序实现
aksrt_sort_payment_methods()(语义对齐参考主题 zibpay_get_payment_methods
+ pay_sdk_order 键顺序),并接入内容收银台、余额充值、购买积分与会员
购买四个入口。卡密输入盒改为按渠道存在与否判断,不再假定卡密固定在末位。

清理死代码:无调用方的 Gateway::get_available/apply_sdk_order/
get_status_summary、VipServicePackages 中不可达的支付方式列表回退分支、
零引用的 settings-defaults 模块及 schema/index.js 未使用的导出、支付
sortable 的 fields 死元数据、无拖拽实现却声明的 cursor:move。

未配置 pay_sdk_order 时顺序保持不变;未写数据迁移。

验证:php -l 8 个改动文件全部通过;新增 pay-sdk-order-contract 契约测试
(6 项);node --test tests/widget-system/*.test.cjs 301 项全部通过;
后台 SPA 已重新构建。
2026-09-18 19:01:19 +08:00
kate522 db0d63b99f fix(pay): 会员卡密单密码默认关闭,卡密盒与其他弹窗一致显示卡号+密码
会员购买/续费弹窗卡密盒此前仅显示卡密输入,与其他两个弹窗(卡号+密码)
不一致。根因是 pay_vip_pass_charge_only_password 的默认值偏离参考主题:

- PaymentRuntime:vip 类型 only_password 由「未设置即视为开启」改为
  !empty(),与其他两类型同构(参考主题 default false)
- ChargeCard:get_public_options 的 vip 回退值 only_password true→false
- SchemaPayment + recharge.js:会员卡密单密码模式开关 default true→false
- 契约测试同步锁定默认关闭与无 isset 回退
- admin SPA 重新构建

已保存过设置且该键被持久化为开启的站点,需在后台「余额充值 → 卡密
兑换 → 会员卡密单密码模式」手动关闭一次。
2026-09-18 17:26:50 +08:00
kate522 b88eab0bfe feat(pay): 卡密兑换入口收敛到三个购买弹窗并对齐参考主题
- 移除余额卡片独立「卡密兑换」通用弹窗,兑换入口分布到充值/积分/会员三个弹窗的支付方式中
- 充值弹窗补齐卡密 logo 与仅卡密时的金额选择隐藏逻辑
- 积分购买弹窗 1:1 复刻参考主题 points-pay modal(商品档位/自定义/支付方式/立即支付),REST 改为 product+custom 契约
- 会员弹窗注入卡密渠道:视图模型透传 card_pass,前端渲染卡密盒并直兑 /pay/card-pass/redeem type=vip
- 后台卡密兑换拆分为余额/积分/会员三个独立开关,VIP 段旧会员卡密组清理
- REST type 改为必填(balance/vip/credits),三类型独立闸门
- 测试重写为 card-pass-modal-redeem 契约,321 项全部通过
2026-09-18 12:25:18 +08:00
kate522 64ed47165d fix(payment): 卡密兑换开关收敛为唯一总开关
- 问题:积分卡密兑换报「积分卡密兑换未开启」,因 asset_points_card_enabled
  与总开关并存为双重闸门;会员卡同样存在隐藏的第二开关 pay_vip_pass_charge_s。
- 修复:ChargeCard 类型闸门统一只消费后台「余额充值 → 卡密兑换」总开关;
  积分卡仅额外要求积分系统可用(系统级依赖,非独立开关)。
- 死代码清理:PaymentRuntime 删除 benefit.pass_charge/pass_charge_desc 映射;
  后台 schema 删除「卡密兑换会员」独立开关组与「启用积分卡密兑换」开关(双源同步);
  vip options 收敛为仅 only_password 展示配置。
- 验证:php -l 4 文件无错;契约测试 59/59 通过;容器内探针实测
  credits 类型闸门放行;SPA 已重建并同步运行副本。
2026-09-17 19:18:54 +08:00
kate522 3481620ed0 feat(payment): 通用卡密兑换弹窗并修正核销订单实付与订单名称
- /user/balance/ 余额卡片入口改为通用「卡密兑换」弹窗:请求不带类型,
  后端按卡密自动识别余额/会员/积分;卡号输入恒展示且选填
- 后台「余额充值 → 卡密充值」语义更新为「卡密兑换」,开关更名
  「启用卡密兑换功能」(设置键不变),双源 schema 同步并重建 SPA
- 删除死代码:credits-vip.php 死文件、credits-balance.php 内联积分
  兑换块、/pay/card-pass/options 路由与 rest_options、labels 数组、
  asset_points_card_description/password_only 死字段及内联块专属 CSS
- 卡密核销订单实付恒 0:create_redeem_order 源头写 0,UserOrders
  对 card_pass 订单视图层归零覆盖历史行(不迁移数据)
- 订单名称按 note.meta 生成:卡密兑换-余额充值X / 卡密兑换-积分充值X /
  购入[会员档位名]X天(永久)
- 新增契约 card-pass-universal-redeem.test.cjs,收紧 user-assets-layout
  契约(禁 .uc-card-pass 根类回潮)
2026-09-17 19:04:47 +08:00
kate522 678143e7b9 feat(payment): 卡密管理改为独立页面并对齐参考主题
- 移除「余额充值」设置中的充值卡(card_pass_*)配置组,PHP schema 与 SPA mirror 双轨同步
- 重写后台卡密管理页:列表过滤/排序/搜索/批量删除/同款分页/点击复制、自动生成与导入、text/xls 导出
- ChargeCard 服务新增 generate_cards/rand_number/rand_password/delete_by_ids/get_export_rows,get_list 支持参考主题排序别名与标识过滤
- 删除无消费方的 /payment/charge-cards REST 路由及 batch_create/disable 死代码,数据层零迁移
2026-09-16 22:05:37 +08:00
kate522 ff11f3b156 fix(payment): 结算货币旧小写值导致枚举校验失败无法保存
paypal_currency/stripe_currency 由自由文本改为 select 后,旧存储值(如
stripe_currency=usd 小写)不通过枚举校验,收款接口页保存被 422 拦截。
为两个货币字段增加 case_insensitive 归一化:读取时归一为选项规范写法、
写入时归一化、校验时大小写不敏感放行,匹配不到回退默认 USD。
2026-09-16 19:56:51 +08:00
kate522 14f9534651 fix(payment): 充值商品选项旧字符串值导致假条目且不可删除
- SchemaProvider 读取时 group/repeater 字段忽略非数组旧值(textarea 时代的
  字符串),回退默认数组,避免前端按字符渲染出 20+ 条假条目
- AksField group/repeater 渲染改为数组安全视图(非数组一律按空列表)
- 充值商品选项默认条数调整为 3 条(PHP 与前端 schema 同步)
2026-09-16 19:46:49 +08:00
kate522 2c139e5c20 fix(payment): 修复推广返佣与收款接口 schema 顶层常量 TDZ 报错
affiliate.js 的 rebateTypeOptions 与 gateway.js 的 currencyOptions 定义在
文件底部,而 schema 对象在模块顶层即引用,ES 模块顶层代码立即执行导致
const 声明前访问触发 ReferenceError(压缩后表现为 Cannot access 'He')。
将常量上移至 schema 对象之前。
2026-09-16 19:36:27 +08:00
kate522 a48007a44d feat(payment): 支付&付费菜单按参考主题重构并新增提现策略页
- 子菜单重组为知识付费/余额充值/VIP会员/创作分成/推广返佣/收款接口/提现策略
- 收款接口页 1:1 复刻 Zibll 双下拉结构,仅保留支付宝/微信/PayPal/Stripe/易支付
- 订单前缀/流水号前缀/提现单号前缀配置保留并移入收款接口页
- 知识付费/推广返佣按参考主题全字段复刻,余额充值并入充值卡配置
- 新增 tabbed/between_number/page_select 表单控件及后端 pages 搜索源
- 清理旧 SPA 组件、无消费者 REST 端点与废弃配置键(不写双路兼容)
- 更新存量契约测试至新 schema 文件
2026-09-16 19:18:28 +08:00
kate522 5b5db0f8f5 feat(admin): back the SPA icon picker with the server catalog
FaIconPicker drops its duplicated hard-coded FA subset and loads
GET /aksrt/v1/icons/catalog: full solid/regular/brands sets, the
built-in zibsvg sprite tab and an inline custom SVG tab. Props/emit
surface is unchanged; the system-notice title icon schema drift
(input vs icon) is fixed on the JS side.
2026-09-06 12:59:30 +08:00