feat: 官网代码全量更新 (2026-04-18)
这个提交包含在:
@@ -67,13 +67,18 @@
|
||||
@input="form.tips = $event.split('\n').filter(Boolean)"
|
||||
/>
|
||||
</n-form-item>
|
||||
<n-form-item v-if="form.seasons" label="可去季节(每行一个)">
|
||||
<n-input
|
||||
type="textarea"
|
||||
:rows="2"
|
||||
:value="(form.seasons||[]).join('\n')"
|
||||
@input="form.seasons = $event.split('\n').filter(Boolean)"
|
||||
/>
|
||||
<n-form-item v-if="form.seasons" label="可去季节">
|
||||
<n-checkbox-group
|
||||
:value="form.seasons || []"
|
||||
@update:value="form.seasons = $event"
|
||||
>
|
||||
<n-space>
|
||||
<n-checkbox value="spring">春季</n-checkbox>
|
||||
<n-checkbox value="summer">夏季</n-checkbox>
|
||||
<n-checkbox value="autumn">秋季</n-checkbox>
|
||||
<n-checkbox value="winter">冬季</n-checkbox>
|
||||
</n-space>
|
||||
</n-checkbox-group>
|
||||
</n-form-item>
|
||||
</n-card>
|
||||
|
||||
@@ -118,7 +123,7 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { NButton, NCard, NSpace, NInput, NForm, NFormItem, NSpin, NGrid, NGi, NBreadcrumb, NBreadcrumbItem } from 'naive-ui'
|
||||
import { NButton, NCard, NSpace, NInput, NForm, NFormItem, NSpin, NGrid, NGi, NBreadcrumb, NBreadcrumbItem, NCheckbox, NCheckboxGroup } from 'naive-ui'
|
||||
import { useMessage } from 'naive-ui'
|
||||
|
||||
definePageMeta({ layout: 'admin', middleware: 'admin', name: 'admin-content-destinations-edit' })
|
||||
|
||||
@@ -5,53 +5,139 @@
|
||||
<div style="font-size: 20px; font-weight: 600; color: #111827; margin-bottom: 4px;">景点详情</div>
|
||||
<div style="font-size: 13px; color: #9ca3af;">管理各景点的详细介绍 · 对应官网「目的地」详情页</div>
|
||||
</div>
|
||||
<a href="/destinations" target="_blank">
|
||||
<n-button>查看前台</n-button>
|
||||
</a>
|
||||
<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">
|
||||
<NuxtLink
|
||||
v-for="item in items"
|
||||
:key="item._index"
|
||||
:to="`/admin/content/destinations-detail/${item._index}`"
|
||||
style="text-decoration: none;"
|
||||
>
|
||||
<template v-for="(item, idx) in items" :key="item._originalIndex">
|
||||
<n-card hoverable>
|
||||
<n-space justify="space-between" align="center">
|
||||
<div>
|
||||
<div style="font-size: 15px; font-weight: 600; color: #111827; margin-bottom: 4px;">{{ item.name || '未命名' }}</div>
|
||||
<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>
|
||||
<span style="font-size: 20px; color: #d1d5db;">›</span>
|
||||
<!-- 排序模式:显示上下移动按钮 -->
|
||||
<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;"
|
||||
>›</NuxtLink>
|
||||
</n-space>
|
||||
</n-space>
|
||||
</n-card>
|
||||
</NuxtLink>
|
||||
</template>
|
||||
</n-space>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { NButton, NCard, NSpace, NTag, NSpin } from 'naive-ui'
|
||||
import { NButton, NCard, NSpace, NTag, NSpin, NAlert } from 'naive-ui'
|
||||
import { useMessage } from 'naive-ui'
|
||||
|
||||
definePageMeta({ layout: 'admin', middleware: 'admin' })
|
||||
const { adminFetch } = useAdmin()
|
||||
const items = ref([]); const loaded = ref(false)
|
||||
onMounted(async () => {
|
||||
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) => ({
|
||||
_index: i, name: d.name || '', subtitle: d.subtitle || '', tag: d.tag || '', id: d.id || '',
|
||||
_originalIndex: i,
|
||||
name: d.name || '',
|
||||
subtitle: d.subtitle || '',
|
||||
tag: d.tag || '',
|
||||
id: d.id || '',
|
||||
}))
|
||||
} catch {}
|
||||
} 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>
|
||||
|
||||
在新工单中引用
屏蔽一个用户