refactor(admin): unify sortable/draggable/repeater list UI
- Add shared sortable-list.css, SORTABLE_LIST_ATTRS and AksSortHandle - Migrate schema sortable, selected order, inline/modal repeater, sorter, floating buttons, footer tabbar and ticket prereply lists to the shared design - Split AksField.vue (1885 -> 969 lines) into focused components/helpers - Remove dead AksArrayTable/AksRepeaterField and move-up/down helpers - Replace hardcoded colors and --n-* vars with --aksrt-color-* tokens Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
135fb4eaa4
commit
8843ae4261
20 files changed
+1442
-1796
No files matched your search
@@ -0,0 +1,65 @@
|
||||
/**
|
||||
* 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)
|
||||
}
|
||||
|
||||
/** 收起态标题行上的一行摘要,取自 listPreview 配置。 */
|
||||
export function repeaterRowSummary(row, cfg = {}) {
|
||||
if (!row) return ''
|
||||
const title = String(row[cfg.titleKey || 'title'] ?? '').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 || '暂无描述',
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user