144 行
4.9 KiB
Vue

此文件含有模棱两可的 Unicode 字符

此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。

<template>
<div style="max-width: 900px;">
<n-space justify="space-between" align="center" style="margin-bottom: 20px;">
<div>
<div style="font-size: 20px; font-weight: 600; color: #111827; margin-bottom: 4px;">景点详情</div>
<div style="font-size: 13px; color: #9ca3af;">管理各景点的详细介绍 · 对应官网目的地详情页</div>
</div>
<n-space>
<n-button v-if="!sortMode" @click="enterSortMode">调整排序</n-button>
<template v-else>
<n-button @click="cancelSort">取消</n-button>
<n-button type="primary" :loading="saving" @click="saveSort">保存排序</n-button>
</template>
<a href="/destinations" target="_blank">
<n-button>查看前台</n-button>
</a>
</n-space>
</n-space>
<n-alert v-if="sortMode" type="info" :show-icon="false" style="margin-bottom: 12px;">
排序模式:使用 ↑ ↓ 按钮调整顺序,完成后点「保存排序」。取消则不保存。
</n-alert>
<n-spin v-if="!loaded" style="display:block; padding: 60px; text-align: center;" />
<template v-else>
<n-space vertical :size="8">
<template v-for="(item, idx) in items" :key="item._originalIndex">
<n-card hoverable>
<n-space justify="space-between" align="center">
<div style="flex: 1; min-width: 0;">
<div style="font-size: 15px; font-weight: 600; color: #111827; margin-bottom: 4px;">
<span v-if="sortMode" style="color: #9ca3af; margin-right: 8px; font-weight: 400;">#{{ idx + 1 }}</span>
{{ item.name || '未命名' }}
</div>
<div v-if="item.subtitle" style="font-size: 13px; color: #6b7280;">{{ item.subtitle }}</div>
</div>
<n-space align="center">
<n-tag v-if="item.tag" type="success" size="small">{{ item.tag }}</n-tag>
<!-- 排序模式显示上下移动按钮 -->
<template v-if="sortMode">
<n-button size="tiny" :disabled="idx === 0" @click="moveUp(idx)"></n-button>
<n-button size="tiny" :disabled="idx === items.length - 1" @click="moveDown(idx)"></n-button>
</template>
<!-- 编辑模式显示跳转箭头 -->
<NuxtLink
v-else
:to="`/admin/content/destinations-detail/${item._originalIndex}`"
style="text-decoration: none; color: #d1d5db; font-size: 20px; padding: 0 8px;"
>&rsaquo;</NuxtLink>
</n-space>
</n-space>
</n-card>
</template>
</n-space>
</template>
</div>
</template>
<script setup>
import { NButton, NCard, NSpace, NTag, NSpin, NAlert } from 'naive-ui'
import { useMessage } from 'naive-ui'
definePageMeta({ layout: 'admin', middleware: 'admin' })
const { adminFetch } = useAdmin()
const message = useMessage()
const items = ref([])
const loaded = ref(false)
const sortMode = ref(false)
const saving = ref(false)
// 原始完整数据(用于保存时回写完整结构)
const rawDestinations = ref([])
const originalOrder = ref([])
async function loadData() {
loaded.value = false
try {
const res = await adminFetch('/api/admin/site-content?key=destinations-detail')
const data = res.data
const arr = Array.isArray(data) ? data : (data.destinations || [])
rawDestinations.value = arr
items.value = arr.map((d, i) => ({
_originalIndex: i,
name: d.name || '',
subtitle: d.subtitle || '',
tag: d.tag || '',
id: d.id || '',
}))
} catch (e) {
message.error('加载失败')
}
loaded.value = true
}
function enterSortMode() {
originalOrder.value = items.value.map(i => i._originalIndex)
sortMode.value = true
}
function cancelSort() {
// 按原始顺序还原
const restored = originalOrder.value.map(origIdx => {
return items.value.find(i => i._originalIndex === origIdx)
})
items.value = restored
sortMode.value = false
}
function moveUp(idx) {
if (idx === 0) return
const arr = [...items.value]
;[arr[idx - 1], arr[idx]] = [arr[idx], arr[idx - 1]]
items.value = arr
}
function moveDown(idx) {
if (idx >= items.value.length - 1) return
const arr = [...items.value]
;[arr[idx], arr[idx + 1]] = [arr[idx + 1], arr[idx]]
items.value = arr
}
async function saveSort() {
saving.value = true
try {
// 按当前显示顺序重组完整数据
const reordered = items.value.map(i => rawDestinations.value[i._originalIndex])
await adminFetch('/api/admin/site-content', {
method: 'PUT',
body: { key: 'destinations-detail', data: { destinations: reordered } },
})
message.success('排序已保存')
sortMode.value = false
await loadData()
} catch (e) {
message.error('保存失败:' + (e.data?.message || e.message))
}
saving.value = false
}
onMounted(loadData)
</script>