feat(payment): 支付&付费菜单按参考主题重构并新增提现策略页

- 子菜单重组为知识付费/余额充值/VIP会员/创作分成/推广返佣/收款接口/提现策略
- 收款接口页 1:1 复刻 Zibll 双下拉结构,仅保留支付宝/微信/PayPal/Stripe/易支付
- 订单前缀/流水号前缀/提现单号前缀配置保留并移入收款接口页
- 知识付费/推广返佣按参考主题全字段复刻,余额充值并入充值卡配置
- 新增 tabbed/between_number/page_select 表单控件及后端 pages 搜索源
- 清理旧 SPA 组件、无消费者 REST 端点与废弃配置键(不写双路兼容)
- 更新存量契约测试至新 schema 文件
This commit is contained in:
2026-09-16 19:18:28 +08:00
parent fb5cdf247d
commit a48007a44d
36 changed files with 2042 additions and 2273 deletions
+49 -13
View File
@@ -192,7 +192,7 @@ class SchemaProvider {
continue;
}
if (in_array($type, ['fieldset', 'accordion', 'sortable', 'link'], true) && !is_array($value)) {
if (in_array($type, ['fieldset', 'accordion', 'sortable', 'link', 'between_number'], true) && !is_array($value)) {
$errors[] = sprintf('%s must be an object', $field['label'] ?? $key);
continue;
}
@@ -732,23 +732,46 @@ class SchemaProvider {
continue;
}
foreach ($group['fields'] ?? [] as $field) {
if (!is_array($field) || empty($field['key'])) {
continue;
}
// UI-only controls never become persisted settings keys.
if (!empty($field['ui_only'])) {
continue;
}
// Notice/help-only rows are UI chrome, not stored settings keys.
if (($field['type'] ?? '') === 'notice') {
continue;
}
$fields[] = $field;
$fields = array_merge($fields, $this->collect_persisted_fields(is_array($field) ? $field : []));
}
}
return $fields;
}
/**
* Flatten one field definition into persisted keys. Tabbed is a UI-only
* container: its sub-fields are flat top-level option keys, so they are
* expanded into the persisted field list (notice / ui_only rows skipped).
*
* @return array<int,array<string,mixed>>
*/
private function collect_persisted_fields(array $field): array {
if (($field['type'] ?? '') === 'tabbed') {
$fields = [];
foreach ((array) ($field['tabs'] ?? []) as $tab) {
if (!is_array($tab)) {
continue;
}
foreach ((array) ($tab['fields'] ?? []) as $sub) {
$fields = array_merge($fields, $this->collect_persisted_fields(is_array($sub) ? $sub : []));
}
}
return $fields;
}
if (empty($field['key'])) {
return [];
}
// UI-only controls never become persisted settings keys.
if (!empty($field['ui_only'])) {
return [];
}
// Notice/help-only rows are UI chrome, not stored settings keys.
if (($field['type'] ?? '') === 'notice') {
return [];
}
return [$field];
}
/**
* Normalize legacy numeric money settings without accepting lossy precision.
*
@@ -848,6 +871,17 @@ class SchemaProvider {
return $this->sanitize_object_fields($value, (array) ($field['fields'] ?? []));
}
if ($type === 'between_number') {
$defaults = is_array($field['default'] ?? null) ? $field['default'] : ['min' => 0, 'max' => 0];
if (!is_array($value)) {
return ['min' => (int) ($defaults['min'] ?? 0), 'max' => (int) ($defaults['max'] ?? 0)];
}
return [
'min' => is_numeric($value['min'] ?? null) ? (int) $value['min'] : (int) ($defaults['min'] ?? 0),
'max' => is_numeric($value['max'] ?? null) ? (int) $value['max'] : (int) ($defaults['max'] ?? 0),
];
}
if (in_array($type, ['group', 'repeater'], true)) {
if (!is_array($value)) {
return [];
@@ -1006,6 +1040,8 @@ class SchemaProvider {
'accordion'=> 'array',
'sortable' => 'array',
'link' => 'array',
'between_number' => 'array',
'page_select' => 'int',
'async_select' => 'array',
'json' => 'array',
// 自定义面板(验证码服务商等)按对象存储