+
+
+
+
+
+
+
+
+
+
+
+
+
+ 编辑
+
+
+
+ 删除
+
+
+
+
+
+
+
+ {{ schema.emptyText || '暂无数据' }}
+
+
+
+ {{ schema.addText || '添加一项' }}
+
+
+
+
+
+
+
+
@@ -421,7 +529,7 @@
* AksField — renders a single form field based on its schema definition.
*
* Supported types: switch, input/text, textarea, number, select, radio,
- * checkbox, color, icon, media, slider, code, alert, custom (slot).
+ * checkbox, color, icon, media, slider, code, alert, repeater (inline|list-modal), custom (slot).
*/
import { computed, ref, onMounted, watch } from 'vue'
import FaIcon from './FaIcon.vue'
@@ -586,17 +694,29 @@ function updateBackground(part, value) {
// ============ Repeater ============
let repeaterIdCounter = 0
+const repeaterModalShow = ref(false)
+const repeaterEditingIndex = ref(-1)
+const repeaterDraft = ref({})
-function addRepeaterItem() {
- const current = Array.isArray(props.modelValue) ? [...props.modelValue] : []
+function createRepeaterItem() {
const newItem = { _id: `rpt_${Date.now()}_${++repeaterIdCounter}` }
- // Initialize with defaults from schema.fields
for (const field of (props.schema.fields || [])) {
if (field.default !== undefined) {
newItem[field.key] = field.default
+ } else if (field.type === 'switch') {
+ newItem[field.key] = false
+ } else if (field.type === 'checkbox' || field.type === 'repeater') {
+ newItem[field.key] = []
+ } else {
+ newItem[field.key] = ''
}
}
- current.push(newItem)
+ return newItem
+}
+
+function addRepeaterItem() {
+ const current = Array.isArray(props.modelValue) ? [...props.modelValue] : []
+ current.push(createRepeaterItem())
emit('update:modelValue', current)
}
@@ -611,6 +731,93 @@ function updateRepeaterItem(index, fieldKey, value) {
current[index] = { ...current[index], [fieldKey]: value }
emit('update:modelValue', current)
}
+
+function moveRepeaterItem(index, delta) {
+ const current = Array.isArray(props.modelValue) ? [...props.modelValue] : []
+ const target = index + delta
+ if (target < 0 || target >= current.length) return
+ const tmp = current[index]
+ current[index] = current[target]
+ current[target] = tmp
+ emit('update:modelValue', current)
+}
+
+function getRepeaterPreviewConfig() {
+ return props.schema.listPreview || {}
+}
+
+function getRepeaterPreviewIcon(item) {
+ const cfg = getRepeaterPreviewConfig()
+ const key = cfg.iconKey || 'icon'
+ return String(item?.[key] || '').trim()
+}
+
+function getRepeaterPreviewCustomIcon(item) {
+ const cfg = getRepeaterPreviewConfig()
+ const key = cfg.customIconKey || 'custom_icon'
+ return String(item?.[key] || '').trim()
+}
+
+function getRepeaterPreviewIconClass(item) {
+ const cfg = getRepeaterPreviewConfig()
+ const colorKey = cfg.iconClassKey || 'icon_class'
+ return String(item?.[colorKey] || '').trim()
+}
+
+function getRepeaterPreviewTitle(item) {
+ const cfg = getRepeaterPreviewConfig()
+ const key = cfg.titleKey || 'title'
+ const title = String(item?.[key] || '').trim()
+ return title || '未命名卡片'
+}
+
+function getRepeaterPreviewDesc(item) {
+ const cfg = getRepeaterPreviewConfig()
+ const key = cfg.descKey || 'desc'
+ const desc = String(item?.[key] || '').trim()
+ if (desc) return desc
+ const urlKey = cfg.urlKey || 'url'
+ const url = String(item?.[urlKey] || '').trim()
+ return url || '暂无描述'
+}
+
+function openRepeaterModal(index) {
+ if (props.schema.disabled) return
+ if (index >= 0) {
+ const item = (props.modelValue || [])[index] || {}
+ repeaterDraft.value = { ...createRepeaterItem(), ...item }
+ repeaterEditingIndex.value = index
+ } else {
+ repeaterDraft.value = createRepeaterItem()
+ repeaterEditingIndex.value = -1
+ }
+ repeaterModalShow.value = true
+}
+
+function closeRepeaterModal() {
+ repeaterModalShow.value = false
+ repeaterEditingIndex.value = -1
+ repeaterDraft.value = {}
+}
+
+function updateRepeaterDraft(fieldKey, value) {
+ repeaterDraft.value = { ...repeaterDraft.value, [fieldKey]: value }
+}
+
+function confirmRepeaterModal() {
+ const current = Array.isArray(props.modelValue) ? [...props.modelValue] : []
+ const item = { ...repeaterDraft.value }
+ if (!item._id) {
+ item._id = `rpt_${Date.now()}_${++repeaterIdCounter}`
+ }
+ if (repeaterEditingIndex.value >= 0 && repeaterEditingIndex.value < current.length) {
+ current[repeaterEditingIndex.value] = item
+ } else {
+ current.push(item)
+ }
+ emit('update:modelValue', current)
+ closeRepeaterModal()
+}
diff --git a/assets/admin/src/schema/sections/page-display/index.js b/assets/admin/src/schema/sections/page-display/index.js
index 28db59bf..b8b741d5 100644
--- a/assets/admin/src/schema/sections/page-display/index.js
+++ b/assets/admin/src/schema/sections/page-display/index.js
@@ -712,6 +712,17 @@ export const pageDisplaySchemas = {
key: 'header_components_icon_cards',
label: '图标卡片',
type: 'repeater',
+ ui: 'list-modal',
+ listPreview: {
+ iconKey: 'icon',
+ customIconKey: 'custom_icon',
+ iconClassKey: 'icon_class',
+ titleKey: 'title',
+ descKey: 'desc',
+ urlKey: 'url',
+ },
+ editText: '编辑卡片',
+ emptyText: '暂无图标卡片',
sanitize_type: 'array',
default: [],
dependsOn: { key: 'header_components_cards_enabled', value: true },