Files
wordpress-theme-aksrt/assets/admin/src/components/form/repeater-rows.js
T
kate522andClaude Fable 5 116b6b2223 feat(admin): make all sortable/repeater cards collapsible, collapsed by default
- Add useCollapsibleItems: key-based open state, clickable/keyboard head, aria-expanded
- sortable items with sub-fields and every inline repeater/group row now collapse
- Repeater head summary falls back to the first filled field when no listPreview
- Drop per-schema collapsible/defaultCollapsed for repeater groups (JS + PHP)
- Align FloatBtnItemPanel head with the shared collapse attrs

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-10-28 14:12:13 +08:00

77 lines
2.8 KiB
JavaScript

/**
* repeater / group 行的纯函数:新行默认值、行 ID、listPreview 摘要与预览。
* inline 与 list-modal 两种渲染共用。
*/
let rowIdCounter = 0
export function createRepeaterRowId() {
return `rpt_${Date.now()}_${++rowIdCounter}`
}
export function createRepeaterRow(fields) {
const row = { _id: createRepeaterRowId() }
for (const field of (Array.isArray(fields) ? fields : [])) {
// UI-only chrome rows (notice / alert) are presentation only.
if (field.ui_only) continue
if (field.default !== undefined) {
row[field.key] = field.default
} else if (field.type === 'switch') {
row[field.key] = false
} else if (field.type === 'checkbox' || field.type === 'repeater') {
row[field.key] = []
} else {
row[field.key] = ''
}
}
return row
}
/** time/unit 形态的时长摘要:0 视为永久,其余按 day/month 补单位。 */
function rowDuration(row, cfg) {
if (!cfg.descKey) return ''
const raw = row[cfg.descKey]
if (raw === '' || raw == null) return ''
const amount = Number(raw)
if (!Number.isFinite(amount)) return String(raw)
if (amount <= 0) return '永久'
if (!cfg.descUnitKey) return String(raw)
const unit = String(row[cfg.descUnitKey] || '')
if (unit === 'month') return `${amount} 个月`
if (unit === 'day') return `${amount} 天`
return String(raw)
}
/** 未配置 titleKey 时,取首个值为非空字符串/数字的字段作为标题。 */
function fallbackTitleKey(row, fields) {
if (row.title != null && String(row.title).trim() !== '') return 'title'
for (const field of (Array.isArray(fields) ? fields : [])) {
if (field.ui_only) continue
const value = row[field.key]
if ((typeof value === 'string' && value.trim() !== '') || typeof value === 'number') return field.key
}
return 'title'
}
/** 收起态标题行上的一行摘要,取自 listPreview 配置。 */
export function repeaterRowSummary(row, cfg = {}, fields = []) {
if (!row) return ''
const title = String(row[cfg.titleKey || fallbackTitleKey(row, fields)] ?? '').trim()
const prefix = String(cfg.prefix || '')
if (title === '') return prefix
const duration = rowDuration(row, cfg)
return duration ? `${prefix}${title} · ${duration}` : `${prefix}${title}`
}
/** list-modal 卡片预览:图标 / 自定义图标 HTML / 图标类 / 标题 / 描述。 */
export function repeaterRowPreview(row, cfg = {}) {
const read = (key) => String(row?.[key] || '').trim()
const desc = read(cfg.descKey || 'desc') || read(cfg.urlKey || 'url')
return {
icon: read(cfg.iconKey || 'icon'),
customIcon: read(cfg.customIconKey || 'custom_icon'),
iconClass: read(cfg.iconClassKey || 'icon_class'),
title: read(cfg.titleKey || 'title') || '未命名卡片',
desc: desc || '暂无描述',
}
}