fix(vip): restore the upgrade-price accordion and space out structured groups

升级价格 declared three reference-theme blocks through the accordion sections option, but the section builder handed the computed the whole field object instead of its sections array, so the guard always saw an empty length and the renderer silently fell back to a flat list. The three blocks ran into each other with no separation at all.

- Move the section expansion into a pure buildFieldsetSections() helper so the mistake is now covered by a real contract test that runs the actual schema through it, instead of a regex over the component source.
- Apply dependsOn to fields inside each block, so 天单价/促销标签 hide when 月费会员升级月费会员 is switched off, matching the flat path.
- Give structured groups (会员商品/续费商品/积分兑换明细/升级价格) their own bottom margin and widen the gap between accordion blocks, so two adjacent groups no longer sit flush against each other.

Verified: 657 Node contract tests passing, the admin SPA builds.
This commit is contained in:
kate522 committed 2026-10-18 19:24:00 +08:00
1 parent 05f16d5fd7
commit 4b5098a753
3 files changed
+74 -14

No files matched your search

@@ -448,3 +448,35 @@ test('SPA 镜像 schema 与 PHP schema 同构', async () => {
assert.ok(!vipSchemaJs.includes(key), `vip.js 不应再含 ${key}`);
}
});
test('升级价格 sections 能展开成三块手风琴,且块内字段遵循 dependsOn', async () => {
const { buildFieldsetSections } = await import(
pathToFileURL(path.join(root, 'assets/admin/src/components/form/fieldset-sections.js')).href
);
const mod = await import(pathToFileURL(path.join(root, 'assets/admin/src/schema/sections/payment/vip.js')).href);
const upgrade = findGroup(mod.vipSchema, 'vip_upgrade_product');
const sections = buildFieldsetSections(upgrade);
assert.strictEqual(sections.length, 3, '升级价格应展开成三块');
assert.deepStrictEqual(sections.map((s) => s.title), [
'月费会员升级月费会员',
'月费会员升级永久会员',
'永久会员升级永久会员',
]);
// 每块的字段并集恰好等于声明的全部字段,且顺序与 schema.fields 一致。
const expanded = sections.flatMap((s) => s.fields.map((f) => f.key));
assert.deepStrictEqual(expanded, upgrade.fields.map((f) => f.key));
// 第一块带「已设置为永久会员」之外的提示与开关,字段定义必须是真实对象而非占位。
assert.ok(sections[0].fields.every((f) => f && typeof f === 'object' && f.key));
// 没有 sections 声明的 fieldset 保持平铺,不产生假块。
assert.deepStrictEqual(buildFieldsetSections({ type: 'fieldset', fields: [{ key: 'a' }] }), []);
assert.deepStrictEqual(buildFieldsetSections(undefined), []);
// 渲染层把 sections 交给纯函数,并对块内字段套用 dependsOn。
assert.match(fieldVue, /const fieldsetSections = computed\(\(\) => \{[\s\S]*?buildFieldsetSections\(props\.schema\)/);
assert.match(fieldVue, /function sectionFields\(section\) \{[\s\S]*?matchDependency\(field\.dependsOn, props\.modelValue\)/);
// 结构化分组之间留出间隔,两个分组不会上下贴在一起。
assert.match(fieldVue, /\.aksrt-field--group,\s*\.aksrt-field--repeater,\s*\.aksrt-field--accordion,\s*\.aksrt-field--fieldset \{[\s\S]*?margin-bottom: 26px;/);
assert.match(fieldVue, /\.aksrt-field__accordion-section \+ \.aksrt-field__accordion-section \{[\s\S]*?margin-top: 14px;/);
});