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
@@ -51,11 +51,9 @@
|
||||
<div class="float-settings__desc">设置并排序需要显示的按钮</div>
|
||||
<draggable
|
||||
v-model="form.items"
|
||||
v-bind="SORTABLE_LIST_ATTRS"
|
||||
item-key="type"
|
||||
handle=".float-btn-panel__handle"
|
||||
ghost-class="float-settings__ghost"
|
||||
animation="180"
|
||||
class="float-settings__list"
|
||||
class="aksrt-sortable"
|
||||
>
|
||||
<template #item="{ element, index }">
|
||||
<FloatBtnItemPanel :model-value="element" @update:model-value="updateItem(index, $event)" />
|
||||
@@ -76,6 +74,7 @@ import draggable from 'vuedraggable'
|
||||
import api from '@/api'
|
||||
import PageHeader from '@/components/PageHeader.vue'
|
||||
import AksSaveBar from '@/components/form/AksSaveBar.vue'
|
||||
import { SORTABLE_LIST_ATTRS } from '@/components/form/sortable-options.js'
|
||||
import FloatBtnItemPanel from '@/components/floating/FloatBtnItemPanel.vue'
|
||||
import { defaultSettings, normalizeItems } from '@/components/floating/floating-defaults.js'
|
||||
|
||||
@@ -163,13 +162,13 @@ onMounted(loadSettings)
|
||||
|
||||
.float-settings__desc {
|
||||
font-size: 12px;
|
||||
color: var(--n-text-color-3, #999);
|
||||
color: var(--aksrt-color-muted, #6F7A72);
|
||||
}
|
||||
|
||||
.float-settings__label {
|
||||
margin-left: 12px;
|
||||
font-size: 13px;
|
||||
color: var(--n-text-color-3, #999);
|
||||
color: var(--aksrt-color-muted, #6F7A72);
|
||||
}
|
||||
|
||||
.float-settings__section {
|
||||
@@ -183,14 +182,4 @@ onMounted(loadSettings)
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.float-settings__list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.float-settings__ghost {
|
||||
opacity: 0.45;
|
||||
}
|
||||
</style>
|
||||
@@ -61,42 +61,38 @@
|
||||
<draggable
|
||||
v-if="form.items.length"
|
||||
v-model="form.items"
|
||||
v-bind="SORTABLE_LIST_ATTRS"
|
||||
item-key="id"
|
||||
handle=".tabbar-sort__handle"
|
||||
ghost-class="tabbar-sort__ghost"
|
||||
animation="180"
|
||||
class="tabbar-sort"
|
||||
class="aksrt-sortable"
|
||||
@end="resetSortOrder"
|
||||
>
|
||||
<template #item="{ element, index }">
|
||||
<div class="tabbar-sort__item">
|
||||
<div class="tabbar-sort__main">
|
||||
<button type="button" class="tabbar-sort__handle" aria-label="拖拽排序">
|
||||
<i class="fa fas fa-bars"></i>
|
||||
</button>
|
||||
<span class="tabbar-sort__icon">
|
||||
<div class="aksrt-sortable__item">
|
||||
<div class="aksrt-sortable__head">
|
||||
<AksSortHandle />
|
||||
<span class="aksrt-sortable__icon">
|
||||
<IconValue v-if="element.icon" :value="element.icon" />
|
||||
</span>
|
||||
<div class="tabbar-sort__meta">
|
||||
<div class="tabbar-sort__title-row">
|
||||
<strong>{{ element.text || '按钮' }}</strong>
|
||||
<div class="aksrt-sortable__main">
|
||||
<div class="aksrt-sortable__title-row">
|
||||
<span class="aksrt-sortable__title">{{ element.text || '按钮' }}</span>
|
||||
<n-tag size="small" :bordered="false">{{ typeLabelMap[element.type] || element.type }}</n-tag>
|
||||
<n-tag v-if="element.badge" size="small" type="error" :bordered="false">徽标 {{ element.badge }}</n-tag>
|
||||
<n-tag v-if="element.type === 'home' && element.ontop" size="small" type="success" :bordered="false">返回顶部</n-tag>
|
||||
<n-tag size="small" :type="element.enabled ? 'success' : 'default'" :bordered="false">{{ element.enabled ? '启用' : '禁用' }}</n-tag>
|
||||
</div>
|
||||
<div class="tabbar-sort__desc">{{ getItemSummary(element) }}</div>
|
||||
<div class="aksrt-sortable__desc">{{ getItemSummary(element) }}</div>
|
||||
</div>
|
||||
<div class="aksrt-sortable__actions">
|
||||
<n-button size="tiny" quaternary type="primary" @click="editItem(index)">
|
||||
<template #icon><i class="fa fas fa-edit" style="font-size:12px;" /></template>
|
||||
编辑
|
||||
</n-button>
|
||||
<n-button size="tiny" quaternary type="error" @click="deleteItem(index)">
|
||||
<template #icon><i class="fa fas fa-trash-alt" style="font-size:12px;" /></template>
|
||||
删除
|
||||
</n-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tabbar-sort__actions">
|
||||
<n-button size="tiny" quaternary type="primary" @click="editItem(index)">
|
||||
<template #icon><i class="fa fas fa-edit" style="font-size:12px;" /></template>
|
||||
编辑
|
||||
</n-button>
|
||||
<n-button size="tiny" quaternary type="error" @click="deleteItem(index)">
|
||||
<template #icon><i class="fa fas fa-trash-alt" style="font-size:12px;" /></template>
|
||||
删除
|
||||
</n-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -216,22 +212,21 @@
|
||||
<div v-if="editingItem.add_items.length" class="add-items-editor__list">
|
||||
<draggable
|
||||
v-model="editingItem.add_items"
|
||||
v-bind="SORTABLE_LIST_ATTRS"
|
||||
item-key="id"
|
||||
handle=".add-items-editor__drag"
|
||||
ghost-class="add-items-editor__ghost"
|
||||
animation="180"
|
||||
class="aksrt-sortable aksrt-sortable--compact"
|
||||
>
|
||||
<template #item="{ element, index: subIndex }">
|
||||
<div class="add-items-editor__row">
|
||||
<button type="button" class="add-items-editor__drag" aria-label="拖拽排序">
|
||||
<i class="fa fas fa-bars"></i>
|
||||
</button>
|
||||
<n-input v-model:value="element.label" placeholder="名称,例如:写文章" />
|
||||
<FaIconPicker v-model="element.icon" placeholder="选择图标" />
|
||||
<n-input v-model:value="element.url" placeholder="链接,例如:/wp-admin/post-new.php" />
|
||||
<n-button quaternary type="error" @click="removeAddItem(subIndex)">
|
||||
<template #icon><i class="fa fas fa-trash-alt" /></template>
|
||||
</n-button>
|
||||
<div class="aksrt-sortable__item">
|
||||
<div class="aksrt-sortable__head add-items-editor__row">
|
||||
<AksSortHandle />
|
||||
<n-input v-model:value="element.label" placeholder="名称,例如:写文章" />
|
||||
<FaIconPicker v-model="element.icon" placeholder="选择图标" />
|
||||
<n-input v-model:value="element.url" placeholder="链接,例如:/wp-admin/post-new.php" />
|
||||
<n-button quaternary type="error" aria-label="删除子项" @click="removeAddItem(subIndex)">
|
||||
<template #icon><i class="fa fas fa-trash-alt" /></template>
|
||||
</n-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</draggable>
|
||||
@@ -267,6 +262,8 @@ import api from '@/api'
|
||||
import PageHeader from '@/components/PageHeader.vue'
|
||||
import FaIconPicker from '@/components/form/FaIconPicker.vue'
|
||||
import IconValue from '@/components/form/IconValue.vue'
|
||||
import AksSortHandle from '@/components/form/AksSortHandle.vue'
|
||||
import { SORTABLE_LIST_ATTRS } from '@/components/form/sortable-options.js'
|
||||
|
||||
const message = useMessage()
|
||||
const dialog = useDialog()
|
||||
@@ -609,7 +606,7 @@ onMounted(loadSettings)
|
||||
}
|
||||
|
||||
.icon-preview-row a {
|
||||
color: var(--n-primary-color);
|
||||
color: var(--aksrt-color-primary, #18A058);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
@@ -618,18 +615,12 @@ onMounted(loadSettings)
|
||||
}
|
||||
|
||||
.muted-text {
|
||||
color: #999;
|
||||
color: var(--aksrt-color-muted, #6F7A72);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.aksrt-modal-footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.section-desc {
|
||||
color: var(--n-text-color-3);
|
||||
color: var(--aksrt-color-muted, #6F7A72);
|
||||
font-size: 13px;
|
||||
margin: 0 0 16px;
|
||||
}
|
||||
@@ -659,127 +650,19 @@ onMounted(loadSettings)
|
||||
|
||||
.add-items-editor__presets-label {
|
||||
font-size: 12px;
|
||||
color: var(--n-text-color-3, #999);
|
||||
}
|
||||
|
||||
.add-items-editor__list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
color: var(--aksrt-color-muted, #6F7A72);
|
||||
}
|
||||
|
||||
.add-items-editor__row {
|
||||
display: grid;
|
||||
grid-template-columns: 32px 1fr 1fr 1.2fr 40px;
|
||||
grid-template-columns: 28px 1fr 1fr 1.2fr 34px;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.add-items-editor__drag {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border: 0;
|
||||
border-radius: 10px;
|
||||
background: #f3f5f7;
|
||||
color: #6b7280;
|
||||
cursor: grab;
|
||||
}
|
||||
|
||||
.add-items-editor__ghost {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.tabbar-sort {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.tabbar-sort__item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
padding: 14px 16px;
|
||||
border: 1px solid var(--n-border-color);
|
||||
border-radius: 14px;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.tabbar-sort__main {
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.tabbar-sort__handle {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border: 0;
|
||||
border-radius: 10px;
|
||||
background: #f3f5f7;
|
||||
color: #6b7280;
|
||||
cursor: grab;
|
||||
}
|
||||
|
||||
.tabbar-sort__icon {
|
||||
width: 38px;
|
||||
height: 38px;
|
||||
border-radius: 12px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: rgba(24, 160, 88, 0.12);
|
||||
color: var(--aksrt-color-primary);
|
||||
font-size: 18px;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.tabbar-sort__meta {
|
||||
min-width: 0;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.tabbar-sort__title-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.tabbar-sort__desc {
|
||||
color: var(--n-text-color-3);
|
||||
font-size: 13px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.tabbar-sort__actions {
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.tabbar-sort__ghost {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
@media (max-width: 1023px) {
|
||||
.tabbar-sort__item {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.tabbar-sort__actions {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.add-items-editor__row {
|
||||
grid-template-columns: 32px 1fr;
|
||||
grid-template-columns: 28px 1fr;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -19,20 +19,23 @@
|
||||
<draggable
|
||||
v-else
|
||||
v-model="rows"
|
||||
v-bind="SORTABLE_LIST_ATTRS"
|
||||
item-key="id"
|
||||
handle=".aksrt-ticket-prereply__handle"
|
||||
:animation="160"
|
||||
class="aksrt-ticket-prereply"
|
||||
class="aksrt-sortable"
|
||||
>
|
||||
<template #item="{ element, index }">
|
||||
<div class="aksrt-ticket-prereply__row">
|
||||
<span class="aksrt-ticket-prereply__handle" title="拖动排序">⋮⋮</span>
|
||||
<span class="aksrt-ticket-prereply__index">{{ index + 1 }}</span>
|
||||
<span class="aksrt-ticket-prereply__content">{{ element.content }}</span>
|
||||
<n-space size="small">
|
||||
<n-button size="tiny" type="primary" quaternary @click="openEdit(element)">编辑</n-button>
|
||||
<n-button size="tiny" type="error" quaternary @click="handleDelete(element)">删除</n-button>
|
||||
</n-space>
|
||||
<div class="aksrt-sortable__item">
|
||||
<div class="aksrt-sortable__head">
|
||||
<AksSortHandle />
|
||||
<span class="aksrt-sortable__index">{{ index + 1 }}</span>
|
||||
<div class="aksrt-sortable__main">
|
||||
<span class="aksrt-ticket-prereply__content">{{ element.content }}</span>
|
||||
</div>
|
||||
<div class="aksrt-sortable__actions">
|
||||
<n-button size="tiny" type="primary" quaternary @click="openEdit(element)">编辑</n-button>
|
||||
<n-button size="tiny" type="error" quaternary @click="handleDelete(element)">删除</n-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</draggable>
|
||||
@@ -71,6 +74,8 @@ import { computed, onMounted, ref } from 'vue'
|
||||
import { NButton, NSpace, useDialog, useMessage } from 'naive-ui'
|
||||
import draggable from 'vuedraggable'
|
||||
import api from '@/api'
|
||||
import AksSortHandle from '@/components/form/AksSortHandle.vue'
|
||||
import { SORTABLE_LIST_ATTRS } from '@/components/form/sortable-options.js'
|
||||
|
||||
const message = useMessage()
|
||||
const dialog = useDialog()
|
||||
@@ -188,38 +193,11 @@ defineExpose({ reload: load })
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.aksrt-ticket-prereply {
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.aksrt-ticket-prereply__row {
|
||||
display: grid;
|
||||
grid-template-columns: 20px 28px 1fr auto;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 10px 12px;
|
||||
border: 1px var(--n-border-color, #efeff5) solid;
|
||||
border-radius: 8px;
|
||||
background: var(--n-color, #fff);
|
||||
}
|
||||
|
||||
.aksrt-ticket-prereply__handle {
|
||||
color: var(--aksrt-color-muted);
|
||||
cursor: grab;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.aksrt-ticket-prereply__index {
|
||||
color: var(--aksrt-color-muted);
|
||||
font-size: 12px;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
.aksrt-ticket-prereply__content {
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
color: var(--aksrt-color-text, #1F2923);
|
||||
font-size: 13px;
|
||||
line-height: 1.6;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
}
|
||||
</style>
|
||||
Reference in new issue
Block a user