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.
The status card always rendered the icon slot, so never-opened members saw a generic certificate glyph in front of 未开通会员 (level 0 has no configured icon and fell back to fa-certificate). Members with a real level keep their icon; only the level-0 case drops the slot.
Render the icon span only when vip_level_number > 0.
Verification: extended tests/widget-system/vip-tab-status-card.test.cjs (3 contract tests); node --test tests/**/*.test.cjs 638/638 pass; php -l clean.
The status card always rendered the expiry row, so never-opened members saw a redundant 到期时间:未开通 line under the 未开通 state label. The reference theme outputs no expiry info for never-opened users (only active, permanent, and expired states carry one).
Initialize the expiry text empty and render the row only when the text is non-empty; active/permanent/expired branches are unchanged.
Verification: new tests/widget-system/vip-tab-status-card.test.cjs (2 contract tests); node --test tests/**/*.test.cjs 637/637 pass; php -l clean.
The user-center VIP tab rendered pay_user_vip_more in a muted box below the level cards, which the reference theme does not do: zibpay_user_page_tab_content_vip only renders pay_user_vip_desc in the status box, and pay_user_vip_more appears solely in the two modal builders (purchase and existing-member renew/upgrade). The page block stayed invisible while the setting defaulted to empty and surfaced once the zibll default copy landed.
Remove the page-level block; the modal view model and pay-vip.js right-column rendering are untouched.
Verification: regression test locks vip-tab.php free of the more block while the modal path keeps carrying it; node --test tests/**/*.test.cjs 635/635 pass; php -l clean.
Restore the existing membership purchase cards and keep the UX refresh limited to the top membership summary.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Remove the shared dashboard card around the VIP panel and render membership details as title, level, intro, and expiry rows.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Remove profile identity chrome from the VIP header, keep the crown watermark, and flatten the summary into one thin card while preserving membership purchase cards.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>