- 后台全面引入 Naive UI 组件库,统一 UI 规范 - 前台 usePublicApi 切换到 API 调用(GET /api/content/[key]) - 前后台数据源统一(site_content 表) - 产品线统一管理(tour/camp/course 三套编辑器) - 产品数据归一化(itinerary/faq/pricing 格式统一) - 表单提交 API 改写入 submissions 表 - 新增 submissions 标记已读 API - site-content PUT 支持 upsert - 修复前台 bug(stories 路由冲突、占位符警告、selector breadcrumb) - 消除 PageHero 类型警告(useSEO reactive 修复) - 25 个前台页面全部 HTTP 200,展示效果不变 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
95 行
3.0 KiB
Vue
95 行
3.0 KiB
Vue
<template>
|
|
<div style="max-width: 900px;">
|
|
<NBreadcrumb style="margin-bottom: 16px;">
|
|
<NBreadcrumbItem>
|
|
<NuxtLink to="/admin/content/selector">产品选择器</NuxtLink>
|
|
</NBreadcrumbItem>
|
|
<NBreadcrumbItem>推荐理由</NBreadcrumbItem>
|
|
</NBreadcrumb>
|
|
|
|
<NSpace justify="space-between" align="center" style="margin-bottom: 16px;">
|
|
<div style="font-size: 20px; font-weight: 600; color: #111827;">推荐理由</div>
|
|
<NSpace :size="8">
|
|
<NuxtLink to="/admin/content/selector">
|
|
<NButton>返回</NButton>
|
|
</NuxtLink>
|
|
<NButton type="primary" :loading="saving" @click="handleSave">保存</NButton>
|
|
</NSpace>
|
|
</NSpace>
|
|
|
|
<NSpin :show="!loaded">
|
|
<template v-if="loaded">
|
|
<div style="font-size: 13px; color: #6b7280; margin-bottom: 20px;">
|
|
每个产品在不同匹配情况下展示的推荐文案。
|
|
</div>
|
|
|
|
<NCard
|
|
v-for="(reasons, pid) in matchReasons"
|
|
:key="pid"
|
|
style="margin-bottom: 16px;"
|
|
>
|
|
<template #header>
|
|
<span style="font-size: 14px; font-weight: 600; color: #374151;">{{ pid }}</span>
|
|
</template>
|
|
<NForm label-placement="top">
|
|
<NFormItem
|
|
v-for="(text, rKey) in reasons"
|
|
:key="rKey"
|
|
:label="rKey"
|
|
:label-style="{ fontFamily: 'monospace', fontSize: '12px', color: '#9ca3af' }"
|
|
>
|
|
<NInput v-model:value="matchReasons[pid][rKey]" />
|
|
</NFormItem>
|
|
</NForm>
|
|
</NCard>
|
|
|
|
<div class="save-bar">
|
|
<NuxtLink to="/admin/content/selector">
|
|
<NButton>返回</NButton>
|
|
</NuxtLink>
|
|
<NButton type="primary" :loading="saving" @click="handleSave">保存</NButton>
|
|
</div>
|
|
</template>
|
|
</NSpin>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { NButton, NCard, NSpace, NInput, NForm, NFormItem, NSpin, NBreadcrumb, NBreadcrumbItem } from 'naive-ui'
|
|
import { useMessage } from 'naive-ui'
|
|
|
|
definePageMeta({ layout: 'admin', middleware: 'admin' })
|
|
const message = useMessage()
|
|
const { adminFetch } = useAdmin()
|
|
const fullData = ref({})
|
|
const matchReasons = ref({})
|
|
const loaded = ref(false)
|
|
const saving = ref(false)
|
|
|
|
async function load() {
|
|
const res = await adminFetch('/api/admin/site-content?key=selector')
|
|
fullData.value = res.data
|
|
matchReasons.value = JSON.parse(JSON.stringify(res.data.matchReasons || {}))
|
|
loaded.value = true
|
|
}
|
|
|
|
async function handleSave() {
|
|
saving.value = true
|
|
try {
|
|
fullData.value.matchReasons = matchReasons.value
|
|
await adminFetch('/api/admin/site-content', { method: 'PUT', body: { key: 'selector', data: fullData.value } })
|
|
message.success('保存成功')
|
|
} catch {
|
|
message.error('保存失败')
|
|
} finally {
|
|
saving.value = false
|
|
}
|
|
}
|
|
|
|
onMounted(load)
|
|
</script>
|
|
|
|
<style scoped>
|
|
.save-bar { position: sticky; bottom: 0; display: flex; justify-content: flex-end; gap: 8px; padding: 16px 0; margin-top: 24px; background: #f0f2f5; border-top: 1px solid #e5e7eb; }
|
|
</style>
|