fix(payment): 充值商品选项旧字符串值导致假条目且不可删除

- SchemaProvider 读取时 group/repeater 字段忽略非数组旧值(textarea 时代的
  字符串),回退默认数组,避免前端按字符渲染出 20+ 条假条目
- AksField group/repeater 渲染改为数组安全视图(非数组一律按空列表)
- 充值商品选项默认条数调整为 3 条(PHP 与前端 schema 同步)
This commit is contained in:
2026-09-16 19:46:49 +08:00
parent 2c139e5c20
commit 14f9534651
4 changed files with 16 additions and 10 deletions
@@ -507,7 +507,7 @@
<!-- Repeater (array of objects) — inline expand (default) --> <!-- Repeater (array of objects) — inline expand (default) -->
<div v-else-if="(schema.type === 'repeater' || schema.type === 'group') && schema.ui !== 'list-modal'" class="aksrt-field__repeater"> <div v-else-if="(schema.type === 'repeater' || schema.type === 'group') && schema.ui !== 'list-modal'" class="aksrt-field__repeater">
<div <div
v-for="(item, index) in (modelValue || [])" v-for="(item, index) in repeaterItems"
:key="item._id || index" :key="item._id || index"
class="aksrt-field__repeater-item" class="aksrt-field__repeater-item"
> >
@@ -548,9 +548,9 @@
<!-- Repeater list-modal: compact list + modal editor --> <!-- Repeater list-modal: compact list + modal editor -->
<div v-else-if="(schema.type === 'repeater' || schema.type === 'group') && schema.ui === 'list-modal'" class="aksrt-field__repeater aksrt-field__repeater--list-modal"> <div v-else-if="(schema.type === 'repeater' || schema.type === 'group') && schema.ui === 'list-modal'" class="aksrt-field__repeater aksrt-field__repeater--list-modal">
<div v-if="(modelValue || []).length" class="aksrt-field__repeater-list"> <div v-if="repeaterItems.length" class="aksrt-field__repeater-list">
<div <div
v-for="(item, index) in (modelValue || [])" v-for="(item, index) in repeaterItems"
:key="item._id || index" :key="item._id || index"
class="aksrt-field__repeater-list-item" class="aksrt-field__repeater-list-item"
> >
@@ -565,7 +565,7 @@
<FaIcon icon="fa fas fa-arrow-up" :size="12" /> <FaIcon icon="fa fas fa-arrow-up" :size="12" />
</n-button> </n-button>
<n-button <n-button
v-if="index < (modelValue || []).length - 1" v-if="index < repeaterItems.length - 1"
size="tiny" size="tiny"
quaternary quaternary
:disabled="schema.disabled" :disabled="schema.disabled"
@@ -708,6 +708,8 @@ function toggleSecretClear() {
} }
const repeaterLength = computed(() => Array.isArray(props.modelValue) ? props.modelValue.length : 0) const repeaterLength = computed(() => Array.isArray(props.modelValue) ? props.modelValue.length : 0)
/** 数组安全视图:非数组(如旧版字符串值)一律按空列表渲染,避免逐字符假条目。 */
const repeaterItems = computed(() => Array.isArray(props.modelValue) ? props.modelValue : [])
const reachedRepeaterMinimum = computed(() => repeaterLength.value <= Number(props.schema.min || 0)) const reachedRepeaterMinimum = computed(() => repeaterLength.value <= Number(props.schema.min || 0))
const reachedRepeaterMaximum = computed(() => Number(props.schema.max || 0) > 0 && repeaterLength.value >= Number(props.schema.max)) const reachedRepeaterMaximum = computed(() => Number(props.schema.max || 0) > 0 && repeaterLength.value >= Number(props.schema.max))
@@ -1083,7 +1085,7 @@ function openRepeaterModal(index) {
if (index < 0 && reachedRepeaterMaximum.value) return if (index < 0 && reachedRepeaterMaximum.value) return
if (props.schema.disabled) return if (props.schema.disabled) return
if (index >= 0) { if (index >= 0) {
const item = (props.modelValue || [])[index] || {} const item = (Array.isArray(props.modelValue) ? props.modelValue : [])[index] || {}
repeaterDraft.value = { ...createRepeaterItem(), ...item } repeaterDraft.value = { ...createRepeaterItem(), ...item }
repeaterEditingIndex.value = index repeaterEditingIndex.value = index
} else { } else {
@@ -18,7 +18,6 @@ export const rechargeSchema = {
{ price: 50, pay_price: 0, tag: '', tag_class: '' }, { price: 50, pay_price: 0, tag: '', tag_class: '' },
{ price: 100, pay_price: 98, tag: '', tag_class: '' }, { price: 100, pay_price: 98, tag: '', tag_class: '' },
{ price: 200, pay_price: 188, tag: '推荐', tag_class: 'jb-cyan' }, { price: 200, pay_price: 188, tag: '推荐', tag_class: 'jb-cyan' },
{ price: 500, pay_price: 468, tag: '特惠', tag_class: 'jb-pink' },
], ],
pay_balance_product_custom_s: true, pay_balance_product_custom_s: true,
pay_balance_product_custom_limit: 9999, pay_balance_product_custom_limit: 9999,
@@ -94,7 +93,6 @@ export const rechargeSchema = {
{ price: 50, pay_price: 0, tag: '', tag_class: '' }, { price: 50, pay_price: 0, tag: '', tag_class: '' },
{ price: 100, pay_price: 98, tag: '', tag_class: '' }, { price: 100, pay_price: 98, tag: '', tag_class: '' },
{ price: 200, pay_price: 188, tag: '推荐', tag_class: 'jb-cyan' }, { price: 200, pay_price: 188, tag: '推荐', tag_class: 'jb-cyan' },
{ price: 500, pay_price: 468, tag: '特惠', tag_class: 'jb-pink' },
], ],
help: '用户充值时候,可快速选择的充值金额,同时支持设置折扣', help: '用户充值时候,可快速选择的充值金额,同时支持设置折扣',
fields: [ fields: [
-1
View File
@@ -145,7 +145,6 @@ class SchemaPayment {
['price' => 50, 'pay_price' => 0, 'tag' => '', 'tag_class' => ''], ['price' => 50, 'pay_price' => 0, 'tag' => '', 'tag_class' => ''],
['price' => 100, 'pay_price' => 98, 'tag' => '', 'tag_class' => ''], ['price' => 100, 'pay_price' => 98, 'tag' => '', 'tag_class' => ''],
['price' => 200, 'pay_price' => 188, 'tag' => '推荐', 'tag_class' => 'jb-cyan'], ['price' => 200, 'pay_price' => 188, 'tag' => '推荐', 'tag_class' => 'jb-cyan'],
['price' => 500, 'pay_price' => 468, 'tag' => '特惠', 'tag_class' => 'jb-pink'],
], 'help' => '用户充值时候,可快速选择的充值金额,同时支持设置折扣', 'fields' => [ ], 'help' => '用户充值时候,可快速选择的充值金额,同时支持设置折扣', 'fields' => [
['key' => 'price', 'label' => '充值金额', 'type' => 'number', 'default' => 100, 'min' => 0, 'suffix' => '元'], ['key' => 'price', 'label' => '充值金额', 'type' => 'number', 'default' => 100, 'min' => 0, 'suffix' => '元'],
['key' => 'pay_price', 'label' => '销售金额', 'type' => 'number', 'default' => 0, 'min' => 0, 'suffix' => '元', 'help' => '选填,设置不同的销售金额实现充值折扣的功能,设置为0则与充值金额一致'], ['key' => 'pay_price', 'label' => '销售金额', 'type' => 'number', 'default' => 0, 'min' => 0, 'suffix' => '元', 'help' => '选填,设置不同的销售金额实现充值折扣的功能,设置为0则与充值金额一致'],
+9 -2
View File
@@ -535,7 +535,14 @@ class SchemaProvider {
$data = $this->collect_defaults($schema); $data = $this->collect_defaults($schema);
foreach ($this->get_all_fields($schema) as $field) { foreach ($this->get_all_fields($schema) as $field) {
if (array_key_exists($field['key'], $stored)) { $field_type = (string) ($field['type'] ?? 'text');
$has_stored = array_key_exists($field['key'], $stored);
// group/repeater 只接受数组;旧标量值(如 textarea 时代的字符串)
// 直接忽略并回退默认,避免前端把字符串按字符渲染成大量假条目。
if ($has_stored && in_array($field_type, ['group', 'repeater'], true) && !is_array($stored[$field['key']])) {
$has_stored = false;
}
if ($has_stored) {
$data[$field['key']] = $stored[$field['key']]; $data[$field['key']] = $stored[$field['key']];
} }
if (($field['sanitize_type'] ?? '') === 'money_decimal' && array_key_exists($field['key'], $data)) { if (($field['sanitize_type'] ?? '') === 'money_decimal' && array_key_exists($field['key'], $data)) {
@@ -545,7 +552,7 @@ class SchemaProvider {
} }
} }
$stored_value = array_key_exists($field['key'], $stored) ? $stored[$field['key']] : ($data[$field['key']] ?? ''); $stored_value = $has_stored ? $stored[$field['key']] : ($data[$field['key']] ?? '');
if (!empty($field['secret'])) { if (!empty($field['secret'])) {
$data[$field['key']] = trim((string) $stored_value) !== '' ? '***' : ''; $data[$field['key']] = trim((string) $stored_value) !== '' ? '***' : '';