791 lines
25 KiB
Vue
791 lines
25 KiB
Vue
<template>
|
||
<div class="aksrt-page-container content-page">
|
||
<PageHeader title="底部Tab配置" desc="自定义移动端底部导航栏的按钮,支持排序、图标配置和返回顶部功能。">
|
||
<template #actions>
|
||
<n-button type="primary" :loading="saving" @click="handleSave">保存</n-button>
|
||
</template>
|
||
</PageHeader>
|
||
|
||
<n-spin :show="loading" style="width: 100%; min-height: 200px;">
|
||
<!-- 基础设置 -->
|
||
<n-card class="aksrt-section-card">
|
||
<template #header>
|
||
<div class="aksrt-card-header-row">
|
||
<div class="aksrt-card-header-row__left">
|
||
<span class="header-icon"><i class="fas fa-cog"></i></span>
|
||
<strong class="aksrt-card-header-row__title">基础设置</strong>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<n-form :model="form" label-placement="left" label-width="140" class="aksrt-form">
|
||
<n-form-item label="启用底部Tab">
|
||
<n-switch v-model:value="form.enabled" />
|
||
<span class="aksrt-hint-text" style="margin-left: 12px;">移动端底部导航栏总开关</span>
|
||
</n-form-item>
|
||
<n-form-item label="向下滚动隐藏">
|
||
<n-switch v-model:value="form.scroll_down_hide" />
|
||
<span class="aksrt-hint-text" style="margin-left: 12px;">页面向下滚动时隐藏,向上滚动时显示</span>
|
||
</n-form-item>
|
||
<n-form-item label="文章页专属Tab">
|
||
<n-select
|
||
v-model:value="form.footer_tabbar_single"
|
||
:options="[
|
||
{ label: '默认按钮', value: 'default' },
|
||
{ label: '文章扩展按钮(评论/收藏/分享)', value: 'extend' },
|
||
]"
|
||
style="width: 240px;"
|
||
/>
|
||
<span class="aksrt-hint-text" style="margin-left: 12px;">文章详情页底部显示的Tab按钮类型</span>
|
||
</n-form-item>
|
||
</n-form>
|
||
</n-card>
|
||
|
||
<!-- Tab按钮配置 -->
|
||
<n-card class="aksrt-section-card">
|
||
<template #header>
|
||
<div class="aksrt-card-header-row">
|
||
<div class="aksrt-card-header-row__left">
|
||
<span class="header-icon"><i class="fas fa-list-ul"></i></span>
|
||
<strong class="aksrt-card-header-row__title">Tab按钮配置 ({{ form.items.length }})</strong>
|
||
</div>
|
||
<n-button type="primary" size="small" @click="addItem">
|
||
<template #icon><i class="fas fa-plus"></i></template>
|
||
添加按钮
|
||
</n-button>
|
||
</div>
|
||
</template>
|
||
<p class="section-desc">配置移动端底部导航栏显示的按钮,支持拖拽排序。建议按钮数量不超过5个。</p>
|
||
<div class="section-desc section-desc--tight">当前已启用 {{ enabledPreviewItems.length }} 个按钮,最多允许 5 个。</div>
|
||
|
||
<draggable
|
||
v-if="form.items.length"
|
||
v-model="form.items"
|
||
item-key="id"
|
||
handle=".tabbar-sort__handle"
|
||
ghost-class="tabbar-sort__ghost"
|
||
animation="180"
|
||
class="tabbar-sort"
|
||
@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="fas fa-grip-vertical"></i>
|
||
</button>
|
||
<span class="tabbar-sort__icon">
|
||
<i v-if="element.icon" :class="element.icon"></i>
|
||
</span>
|
||
<div class="tabbar-sort__meta">
|
||
<div class="tabbar-sort__title-row">
|
||
<strong>{{ element.text || '按钮' }}</strong>
|
||
<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>
|
||
</div>
|
||
<div class="tabbar-sort__actions">
|
||
<n-button size="tiny" quaternary type="primary" @click="editItem(index)">
|
||
<template #icon><i class="fas fa-edit" style="font-size:12px;" /></template>
|
||
编辑
|
||
</n-button>
|
||
<n-button size="tiny" quaternary type="error" @click="deleteItem(index)">
|
||
<template #icon><i class="fas fa-trash-alt" style="font-size:12px;" /></template>
|
||
删除
|
||
</n-button>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
</draggable>
|
||
<n-empty v-else description="暂无Tab按钮,请点击上方按钮添加" />
|
||
</n-card>
|
||
|
||
<div class="aksrt-form-actions">
|
||
<n-button type="primary" :loading="saving" @click="handleSave">保存底部Tab配置</n-button>
|
||
</div>
|
||
</n-spin>
|
||
|
||
<!-- 编辑弹窗 -->
|
||
<n-modal
|
||
v-model:show="showModal"
|
||
:mask-closable="false"
|
||
preset="card"
|
||
:title="editingIndex >= 0 ? '编辑Tab按钮' : '添加Tab按钮'"
|
||
class="aksrt-modal-card"
|
||
style="max-width: 520px;"
|
||
>
|
||
<n-form :model="editingItem" label-placement="top" class="aksrt-form">
|
||
<n-form-item label="按钮类型">
|
||
<n-select
|
||
v-model:value="editingItem.type"
|
||
:options="typeOptions"
|
||
placeholder="选择按钮类型"
|
||
@update:value="handleTypeChange"
|
||
/>
|
||
</n-form-item>
|
||
|
||
<n-form-item label="图标 (Font Awesome)">
|
||
<n-input v-model:value="editingItem.icon" placeholder="例如: fas fa-home" />
|
||
<template #feedback>
|
||
<div class="icon-preview-row">
|
||
<span>预览:</span>
|
||
<i v-if="editingItem.icon" :class="editingItem.icon" style="font-size: 20px;"></i>
|
||
<span v-else class="muted-text">无图标</span>
|
||
<a href="https://fontawesome.com/search?m=free" target="_blank" rel="noopener" style="margin-left: 12px;">
|
||
<i class="fas fa-external-link-alt"></i> Font Awesome 图标库
|
||
</a>
|
||
</div>
|
||
</template>
|
||
</n-form-item>
|
||
|
||
<n-form-item label="显示文字">
|
||
<n-input v-model:value="editingItem.text" placeholder="例如: 首页" />
|
||
</n-form-item>
|
||
|
||
<n-form-item label="徽标文本">
|
||
<n-input v-model:value="editingItem.badge" placeholder="例如: NEW, 9+,留空不显示" maxlength="4" />
|
||
</n-form-item>
|
||
|
||
<n-form-item label="徽标模式">
|
||
<n-radio-group v-model:value="editingItem.badge_mode">
|
||
<n-space>
|
||
<n-radio value="static">静态文本</n-radio>
|
||
<n-radio value="dynamic">动态徽标</n-radio>
|
||
</n-space>
|
||
</n-radio-group>
|
||
</n-form-item>
|
||
|
||
<n-alert v-if="editingItem.badge_mode === 'dynamic'" type="info" :show-icon="false" style="margin-bottom: 16px;">
|
||
<template v-if="editingItem.type === 'auth'">未登录时显示提醒徽标,登录后自动隐藏。</template>
|
||
<template v-else-if="editingItem.type === 'user'">登录用户显示评论数徽标,游客不显示。</template>
|
||
<template v-else-if="editingItem.type === 'search'">搜索结果页显示 ON 徽标,其它页面隐藏。</template>
|
||
<template v-else>当前类型不提供动态徽标,前端将回退为静态徽标。</template>
|
||
</n-alert>
|
||
|
||
<n-form-item v-if="editingItem.type === 'category'" label="指定分类">
|
||
<n-select
|
||
v-model:value="editingItem.category_id"
|
||
:options="categoryOptions"
|
||
placeholder="留空则跳转分类聚合页"
|
||
clearable
|
||
filterable
|
||
/>
|
||
</n-form-item>
|
||
|
||
<n-form-item v-if="editingItem.type === 'search'" label="默认搜索类型">
|
||
<n-radio-group v-model:value="editingItem.search_default_type">
|
||
<n-space>
|
||
<n-radio value="post">文章</n-radio>
|
||
<n-radio value="category">分类</n-radio>
|
||
<n-radio value="tag">标签</n-radio>
|
||
</n-space>
|
||
</n-radio-group>
|
||
</n-form-item>
|
||
|
||
<n-form-item v-if="['link', 'msg', 'pay_vip', 'shop_cart'].includes(editingItem.type)" label="链接地址">
|
||
<n-input v-model:value="editingItem.link" :placeholder="editingItem.type === 'msg' ? '留空使用默认消息中心地址' : editingItem.type === 'pay_vip' ? '留空使用默认会员页地址' : editingItem.type === 'shop_cart' ? '留空使用默认购物车地址' : 'https:// 或 /path'" />
|
||
</n-form-item>
|
||
|
||
<n-form-item v-if="editingItem.type === 'link'" label="新窗口打开">
|
||
<n-switch v-model:value="editingItem.new_window" />
|
||
</n-form-item>
|
||
|
||
<div v-if="editingItem.type === 'add'" class="add-items-editor">
|
||
<div class="add-items-editor__head">
|
||
<strong>发布菜单项</strong>
|
||
<n-button size="small" type="primary" @click="appendAddItem">
|
||
<template #icon><i class="fas fa-plus" /></template>
|
||
添加子项
|
||
</n-button>
|
||
</div>
|
||
<div class="add-items-editor__presets">
|
||
<span class="add-items-editor__presets-label">快速添加:</span>
|
||
<n-button
|
||
v-for="preset in addPresets"
|
||
:key="preset.url"
|
||
size="tiny"
|
||
secondary
|
||
:type="editingItem.add_items.some((item) => item.url === preset.url) ? 'success' : 'default'"
|
||
:disabled="editingItem.add_items.some((item) => item.url === preset.url)"
|
||
@click="appendAddPreset(preset)"
|
||
>
|
||
{{ preset.label }}
|
||
</n-button>
|
||
</div>
|
||
<div v-if="editingItem.add_items.length" class="add-items-editor__list">
|
||
<draggable
|
||
v-model="editingItem.add_items"
|
||
item-key="id"
|
||
handle=".add-items-editor__drag"
|
||
ghost-class="add-items-editor__ghost"
|
||
animation="180"
|
||
>
|
||
<template #item="{ element, index: subIndex }">
|
||
<div class="add-items-editor__row">
|
||
<button type="button" class="add-items-editor__drag" aria-label="拖拽排序">
|
||
<i class="fas fa-grip-vertical"></i>
|
||
</button>
|
||
<n-input v-model:value="element.label" placeholder="名称,例如:写文章" />
|
||
<n-input v-model:value="element.icon" placeholder="图标,例如:fas fa-pen" />
|
||
<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="fas fa-trash-alt" /></template>
|
||
</n-button>
|
||
</div>
|
||
</template>
|
||
</draggable>
|
||
</div>
|
||
<div v-else class="muted-text">当前没有子项,前端将回退为默认发布入口。</div>
|
||
</div>
|
||
|
||
<n-form-item v-if="editingItem.type === 'home'" label="返回顶部功能">
|
||
<n-switch v-model:value="editingItem.ontop" />
|
||
<span class="aksrt-hint-text" style="margin-left: 12px;">页面滚动后切换为返回顶部按钮</span>
|
||
</n-form-item>
|
||
|
||
<n-form-item label="启用状态">
|
||
<n-switch v-model:value="editingItem.enabled" />
|
||
</n-form-item>
|
||
</n-form>
|
||
|
||
<template #footer>
|
||
<div class="aksrt-modal-footer">
|
||
<n-button @click="showModal = false">取消</n-button>
|
||
<n-button type="primary" @click="saveItem">保存</n-button>
|
||
</div>
|
||
</template>
|
||
</n-modal>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { computed, onMounted, reactive, ref } from 'vue'
|
||
import { NButton, NTag, useDialog, useMessage } from 'naive-ui'
|
||
import draggable from 'vuedraggable'
|
||
import api from '@/api'
|
||
import PageHeader from '@/components/PageHeader.vue'
|
||
|
||
const message = useMessage()
|
||
const dialog = useDialog()
|
||
const loading = ref(true)
|
||
const saving = ref(false)
|
||
const showModal = ref(false)
|
||
const editingIndex = ref(-1)
|
||
const categoryOptions = ref([])
|
||
|
||
const form = reactive({
|
||
enabled: true,
|
||
scroll_hide: false,
|
||
scroll_down_hide: true,
|
||
footer_tabbar_single: 'default',
|
||
items: [],
|
||
})
|
||
|
||
const editingItem = reactive({
|
||
id: '',
|
||
type: 'link',
|
||
icon: '',
|
||
text: '',
|
||
link: '',
|
||
badge: '',
|
||
badge_mode: 'static',
|
||
category_id: null,
|
||
search_default_type: 'post',
|
||
new_window: false,
|
||
add_items: [],
|
||
enabled: true,
|
||
sort_order: 0,
|
||
ontop: false,
|
||
})
|
||
|
||
const typeOptions = [
|
||
{ label: '首页', value: 'home' },
|
||
{ label: '分类页', value: 'category' },
|
||
{ label: '链接', value: 'link' },
|
||
{ label: '中央发布按钮', value: 'add' },
|
||
{ label: '用户中心', value: 'user' },
|
||
{ label: '登录/我的', value: 'auth' },
|
||
{ label: '搜索', value: 'search' },
|
||
{ label: '消息中心', value: 'msg' },
|
||
{ label: '开通会员', value: 'pay_vip' },
|
||
{ label: '购物车', value: 'shop_cart' },
|
||
]
|
||
|
||
const typeLabelMap = {
|
||
home: '首页',
|
||
category: '分类页',
|
||
link: '链接',
|
||
add: '中央发布按钮',
|
||
user: '用户中心',
|
||
auth: '登录/我的',
|
||
search: '搜索',
|
||
msg: '消息中心',
|
||
pay_vip: '开通会员',
|
||
shop_cart: '购物车',
|
||
}
|
||
|
||
const typeDefaults = {
|
||
home: { icon: 'fas fa-home', text: '首页' },
|
||
category: { icon: 'fas fa-th-large', text: '分类' },
|
||
link: { icon: 'fas fa-link', text: '链接' },
|
||
add: { icon: 'fas fa-plus', text: '发布' },
|
||
user: { icon: 'fas fa-user', text: '用户中心' },
|
||
auth: { icon: 'fas fa-user', text: '我的' },
|
||
search: { icon: 'fas fa-search', text: '搜索' },
|
||
msg: { icon: 'fas fa-bell', text: '消息' },
|
||
pay_vip: { icon: 'fas fa-crown', text: '会员' },
|
||
shop_cart: { icon: 'fas fa-shopping-cart', text: '购物车' },
|
||
}
|
||
|
||
const enabledPreviewItems = computed(() => form.items.filter((item) => item.enabled))
|
||
|
||
function getItemSummary(item) {
|
||
if (item.type === 'home') return item.ontop ? '首页按钮,滚动后切换返回顶部' : '首页按钮'
|
||
if (item.type === 'category') {
|
||
if (item.category_id) {
|
||
const target = categoryOptions.value.find((option) => option.value === item.category_id)
|
||
return target ? `跳转到分类: ${target.label}` : `跳转到分类 ID ${item.category_id}`
|
||
}
|
||
return '跳转到分类聚合页'
|
||
}
|
||
if (item.type === 'add') {
|
||
return item.add_items?.length ? `展开 ${item.add_items.length} 个发布入口` : '点击后打开默认发布入口'
|
||
}
|
||
if (item.type === 'search') return `打开顶部搜索面板,默认搜索${item.search_default_type === 'category' ? '分类' : item.search_default_type === 'tag' ? '标签' : '文章'}`
|
||
if (item.type === 'user') return '自动跳转到用户中心或登录页'
|
||
if (item.type === 'auth') return '登录用户显示我的,游客显示登录'
|
||
if (item.type === 'msg') return '跳转到消息中心页面'
|
||
if (item.type === 'pay_vip') return item.link || '跳转到会员开通页'
|
||
if (item.type === 'shop_cart') return item.link || '跳转到购物车页面'
|
||
return item.link || '自定义链接按钮'
|
||
}
|
||
|
||
const addPresets = [
|
||
{ label: '写文章', icon: 'fas fa-pen-fancy', url: '/post-new' },
|
||
{ label: '投稿页', icon: 'fas fa-file-alt', url: '/submit' },
|
||
{ label: '发布资源', icon: 'fas fa-cloud-upload-alt', url: '/resource-new' },
|
||
{ label: '创作中心', icon: 'fas fa-palette', url: '/creator' },
|
||
]
|
||
|
||
function appendAddPreset(preset) {
|
||
if (editingItem.add_items.some((item) => item.url === preset.url)) return
|
||
editingItem.add_items.push({
|
||
id: `add_${Date.now()}_${editingItem.add_items.length}`,
|
||
label: preset.label,
|
||
icon: preset.icon,
|
||
url: preset.url,
|
||
})
|
||
}
|
||
|
||
function appendAddItem() {
|
||
editingItem.add_items.push({
|
||
id: `add_${Date.now()}_${editingItem.add_items.length}`,
|
||
label: '',
|
||
icon: 'fas fa-pen',
|
||
url: '',
|
||
})
|
||
}
|
||
|
||
function removeAddItem(index) {
|
||
editingItem.add_items.splice(index, 1)
|
||
}
|
||
|
||
function resetSortOrder() {
|
||
form.items = form.items.map((item, index) => ({
|
||
...item,
|
||
sort_order: index,
|
||
}))
|
||
}
|
||
|
||
function applyTypeDefaults(type, force = false) {
|
||
const preset = typeDefaults[type] || typeDefaults.link
|
||
if (force || !editingItem.icon) editingItem.icon = preset.icon
|
||
if (force || !editingItem.text) editingItem.text = preset.text
|
||
if (!['link', 'msg', 'pay_vip', 'shop_cart'].includes(type)) editingItem.link = ''
|
||
if (type !== 'category') editingItem.category_id = null
|
||
if (type !== 'search') editingItem.search_default_type = 'post'
|
||
if (type !== 'add') editingItem.add_items = []
|
||
if (type !== 'home') editingItem.ontop = false
|
||
}
|
||
|
||
function handleTypeChange(value) {
|
||
applyTypeDefaults(value, false)
|
||
}
|
||
|
||
function addItem() {
|
||
if (form.items.length >= 5) {
|
||
message.warning('底部Tab最多只能配置 5 个按钮')
|
||
return
|
||
}
|
||
|
||
Object.assign(editingItem, {
|
||
id: `tab_${Date.now()}`,
|
||
type: 'link',
|
||
icon: 'fas fa-link',
|
||
text: '链接',
|
||
link: '',
|
||
badge: '',
|
||
badge_mode: 'static',
|
||
category_id: null,
|
||
search_default_type: 'post',
|
||
new_window: false,
|
||
add_items: [],
|
||
enabled: true,
|
||
sort_order: form.items.length,
|
||
ontop: false,
|
||
})
|
||
editingIndex.value = -1
|
||
showModal.value = true
|
||
}
|
||
|
||
function editItem(index) {
|
||
const item = form.items[index]
|
||
Object.assign(editingItem, {
|
||
id: item.id,
|
||
type: item.type || 'link',
|
||
icon: item.icon || '',
|
||
text: item.text || '',
|
||
link: item.link || '',
|
||
badge: item.badge || '',
|
||
badge_mode: item.badge_mode || 'static',
|
||
category_id: item.category_id || null,
|
||
search_default_type: item.search_default_type || 'post',
|
||
new_window: !!item.new_window,
|
||
add_items: Array.isArray(item.add_items) ? item.add_items.map((sub) => ({ ...sub })) : [],
|
||
enabled: item.enabled !== false,
|
||
sort_order: item.sort_order ?? index,
|
||
ontop: item.ontop || false,
|
||
})
|
||
editingIndex.value = index
|
||
showModal.value = true
|
||
}
|
||
|
||
function saveItem() {
|
||
if (editingIndex.value < 0 && form.items.length >= 5) {
|
||
message.warning('底部Tab最多只能配置 5 个按钮')
|
||
return
|
||
}
|
||
if (!editingItem.icon) {
|
||
message.warning('请填写图标')
|
||
return
|
||
}
|
||
if (!editingItem.text) {
|
||
message.warning('请填写显示文字')
|
||
return
|
||
}
|
||
|
||
const item = {
|
||
id: editingItem.id || `tab_${Date.now()}`,
|
||
type: editingItem.type,
|
||
icon: editingItem.icon,
|
||
text: editingItem.text,
|
||
link: ['link', 'msg', 'pay_vip', 'shop_cart'].includes(editingItem.type) ? editingItem.link : '',
|
||
badge: (editingItem.badge || '').trim(),
|
||
badge_mode: editingItem.badge_mode,
|
||
category_id: editingItem.type === 'category' ? (editingItem.category_id || 0) : 0,
|
||
search_default_type: editingItem.type === 'search' ? editingItem.search_default_type : 'post',
|
||
new_window: editingItem.type === 'link' ? !!editingItem.new_window : false,
|
||
add_items: editingItem.type === 'add'
|
||
? editingItem.add_items.filter((sub) => sub.label && sub.url).map((sub) => ({
|
||
id: sub.id || `add_${Date.now()}`,
|
||
label: sub.label,
|
||
icon: sub.icon || 'fas fa-pen',
|
||
url: sub.url,
|
||
}))
|
||
: [],
|
||
enabled: editingItem.enabled,
|
||
sort_order: editingItem.sort_order,
|
||
ontop: editingItem.type === 'home' ? editingItem.ontop : false,
|
||
}
|
||
|
||
if (editingIndex.value >= 0) {
|
||
form.items[editingIndex.value] = item
|
||
} else {
|
||
form.items.push(item)
|
||
}
|
||
|
||
showModal.value = false
|
||
message.success(editingIndex.value >= 0 ? '已更新' : '已添加')
|
||
}
|
||
|
||
async function loadCategories() {
|
||
try {
|
||
const res = await api.getCategories()
|
||
if (res.code === 'success' && Array.isArray(res.data)) {
|
||
categoryOptions.value = res.data.map((item) => ({
|
||
label: item.name,
|
||
value: Number(item.id),
|
||
}))
|
||
}
|
||
} catch (e) {
|
||
console.error('[FooterTabbar] load categories failed:', e)
|
||
}
|
||
}
|
||
|
||
function deleteItem(index) {
|
||
dialog.warning({
|
||
title: '确认删除',
|
||
content: `确定删除「${form.items[index].text}」按钮吗?`,
|
||
positiveText: '删除',
|
||
negativeText: '取消',
|
||
onPositiveClick: () => {
|
||
form.items.splice(index, 1)
|
||
resetSortOrder()
|
||
message.success('已删除')
|
||
},
|
||
})
|
||
}
|
||
|
||
async function loadSettings() {
|
||
loading.value = true
|
||
try {
|
||
const [res] = await Promise.all([api.getFooterTabbarSettings(), loadCategories()])
|
||
if (res.code === 'success' && res.data) {
|
||
const data = res.data
|
||
form.enabled = data.enabled !== false
|
||
form.scroll_hide = data.scroll_hide || false
|
||
form.scroll_down_hide = data.scroll_down_hide !== false
|
||
form.footer_tabbar_single = data.footer_tabbar_single || 'default'
|
||
form.items = (data.items || []).map((item, idx) => ({
|
||
...item,
|
||
badge: item.badge || '',
|
||
badge_mode: item.badge_mode || 'static',
|
||
category_id: item.category_id || 0,
|
||
search_default_type: item.search_default_type || 'post',
|
||
new_window: !!item.new_window,
|
||
add_items: Array.isArray(item.add_items) ? item.add_items : [],
|
||
sort_order: item.sort_order ?? idx,
|
||
}))
|
||
}
|
||
} catch (e) {
|
||
console.error('[FooterTabbar] load failed:', e)
|
||
message.error('加载配置失败')
|
||
} finally {
|
||
loading.value = false
|
||
}
|
||
}
|
||
|
||
async function handleSave() {
|
||
if (form.items.length > 5) {
|
||
message.warning('底部Tab最多只能配置 5 个按钮')
|
||
return
|
||
}
|
||
|
||
saving.value = true
|
||
try {
|
||
const payload = {
|
||
enabled: form.enabled,
|
||
scroll_hide: form.scroll_hide,
|
||
scroll_down_hide: form.scroll_down_hide,
|
||
footer_tabbar_single: form.footer_tabbar_single,
|
||
items: form.items.map((item, idx) => ({
|
||
...item,
|
||
sort_order: idx,
|
||
})),
|
||
}
|
||
const res = await api.saveFooterTabbarSettings(payload)
|
||
if (res.code === 'success') {
|
||
message.success(res.message || '保存成功')
|
||
} else {
|
||
message.error(res.message || '保存失败')
|
||
}
|
||
} catch (e) {
|
||
console.error('[FooterTabbar] save failed:', e)
|
||
message.error('保存失败')
|
||
} finally {
|
||
saving.value = false
|
||
}
|
||
}
|
||
|
||
onMounted(loadSettings)
|
||
</script>
|
||
|
||
<style scoped>
|
||
.icon-preview-row {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
margin-top: 4px;
|
||
font-size: 13px;
|
||
}
|
||
|
||
.icon-preview-row a {
|
||
color: var(--n-primary-color);
|
||
text-decoration: none;
|
||
}
|
||
|
||
.icon-preview-row a:hover {
|
||
text-decoration: underline;
|
||
}
|
||
|
||
.muted-text {
|
||
color: #999;
|
||
font-size: 12px;
|
||
}
|
||
|
||
.aksrt-modal-footer {
|
||
display: flex;
|
||
justify-content: flex-end;
|
||
gap: 10px;
|
||
}
|
||
|
||
.section-desc {
|
||
color: var(--n-text-color-3);
|
||
font-size: 13px;
|
||
margin: 0 0 16px;
|
||
}
|
||
|
||
.section-desc--tight {
|
||
margin-top: -6px;
|
||
}
|
||
|
||
.add-items-editor {
|
||
margin-bottom: 16px;
|
||
}
|
||
|
||
.add-items-editor__head {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
margin-bottom: 10px;
|
||
}
|
||
|
||
.add-items-editor__presets {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 6px;
|
||
margin-bottom: 12px;
|
||
flex-wrap: wrap;
|
||
}
|
||
|
||
.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;
|
||
}
|
||
|
||
.add-items-editor__row {
|
||
display: grid;
|
||
grid-template-columns: 32px 1fr 1fr 1.2fr 40px;
|
||
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: #18a058;
|
||
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;
|
||
}
|
||
}
|
||
</style>
|