Mimingguang 810ae2724d fix: 后台编辑器字段全覆盖 + 新建 about/versions/pricing 专用编辑器
- tour 编辑器补全:selectionGuide 3维度、isMainProduct、timeline 正确结构
- camp 编辑器补全:name、campEssentials
- calendar 补全:quickRecommendations、travelAdvice、recommendedProducts
- guides 补全:穿搭章节 months[] 每月数据
- brand 补全:ctaButtons、conversionPath、icpUrl、eContract
- qualifications 补全:enterpriseMatrix
- youji 补全:notes[].tags
- gallery 补全:页面配置(intro/stats/categories)
- 新建 about.vue 专用编辑器(14个分区)
- 新建 versions.vue 专用编辑器(8个分区)
- 新建 pricing.vue 专用编辑器(5个分区)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 09:06:50 +08:00

424 行
21 KiB
Vue

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

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

<template>
<div style="max-width: 1100px">
<n-space justify="space-between" align="center" style="margin-bottom: 20px">
<div>
<h1 style="margin: 0 0 4px; font-size: 20px; font-weight: 600; color: #111827">关于我们</h1>
<p style="margin: 0; font-size: 13px; color: #9ca3af">管理关于页面的所有内容品牌故事团队资质荣誉等</p>
</div>
<n-space>
<n-button tag="a" href="/about" target="_blank" secondary>查看前台 </n-button>
<n-button type="primary" :loading="saving" @click="save">保存</n-button>
</n-space>
</n-space>
<n-spin :show="!loaded">
<n-collapse :default-expanded-names="['story']">
<!-- story -->
<n-collapse-item title="品牌故事" name="story">
<n-card>
<n-form label-placement="top">
<n-form-item label="标题">
<n-input v-model:value="data.story.title" />
</n-form-item>
<n-form-item label="段落">
<n-dynamic-input v-model:value="data.story.paragraphs" :min="1">
<template #default="{ value, index }">
<n-input
type="textarea"
:autosize="{ minRows: 2, maxRows: 5 }"
:value="value"
@update:value="v => data.story.paragraphs[index] = v"
placeholder="段落内容"
style="width: 100%"
/>
</template>
</n-dynamic-input>
</n-form-item>
</n-form>
</n-card>
</n-collapse-item>
<!-- serviceJourney -->
<n-collapse-item title="服务旅程" name="serviceJourney">
<n-card>
<n-form label-placement="top">
<n-grid :cols="2" :x-gap="12">
<n-gi><n-form-item label="标题"><n-input v-model:value="data.serviceJourney.title" /></n-form-item></n-gi>
<n-gi><n-form-item label="副标题"><n-input v-model:value="data.serviceJourney.subtitle" /></n-form-item></n-gi>
</n-grid>
<n-form-item label="服务时刻">
<div style="width: 100%">
<div v-for="(m, i) in data.serviceJourney.moments" :key="i" style="display: flex; gap: 8px; align-items: flex-start; margin-bottom: 8px">
<n-input-number v-model:value="m.step" placeholder="步骤" style="width: 80px" :min="1" />
<n-input v-model:value="m.name" placeholder="名称" style="width: 140px" />
<n-input v-model:value="m.detail" placeholder="详情" style="flex: 1" />
<n-button size="tiny" type="error" quaternary @click="data.serviceJourney.moments.splice(i, 1)">删</n-button>
</div>
<n-button size="small" type="primary" dashed @click="data.serviceJourney.moments.push({ step: data.serviceJourney.moments.length + 1, name: '', detail: '' })">添加时刻</n-button>
</div>
</n-form-item>
</n-form>
</n-card>
</n-collapse-item>
<!-- team -->
<n-collapse-item title="团队" name="team">
<n-card>
<n-form label-placement="top">
<n-form-item label="团队介绍">
<n-input v-model:value="data.team.summary" type="textarea" :autosize="{ minRows: 3, maxRows: 8 }" />
</n-form-item>
</n-form>
</n-card>
</n-collapse-item>
<!-- heritage -->
<n-collapse-item title="成长纪事" name="heritage">
<n-card>
<n-form label-placement="top">
<n-form-item label="标题">
<n-input v-model:value="data.heritage.title" />
</n-form-item>
<n-form-item label="里程碑">
<div style="width: 100%">
<n-card v-for="(ms, i) in data.heritage.milestones" :key="i" size="small" style="margin-bottom: 12px">
<template #header>
<n-space align="center">
<n-input v-model:value="ms.year" placeholder="年份" style="width: 100px" />
<n-button size="tiny" type="error" quaternary @click="data.heritage.milestones.splice(i, 1)">删除此年</n-button>
</n-space>
</template>
<n-dynamic-input v-model:value="ms.paragraphs" :min="1">
<template #default="{ value, index }">
<n-input
type="textarea"
:autosize="{ minRows: 2, maxRows: 5 }"
:value="value"
@update:value="v => ms.paragraphs[index] = v"
placeholder="段落内容"
style="width: 100%"
/>
</template>
</n-dynamic-input>
</n-card>
<n-button size="small" type="primary" dashed @click="data.heritage.milestones.push({ year: '', paragraphs: [''] })">添加年份</n-button>
</div>
</n-form-item>
</n-form>
</n-card>
</n-collapse-item>
<!-- culture -->
<n-collapse-item title="企业文化" name="culture">
<n-card>
<n-form label-placement="top">
<n-grid :cols="2" :x-gap="12">
<n-gi><n-form-item label="核心价值观"><n-input v-model:value="data.culture.core" /></n-form-item></n-gi>
<n-gi><n-form-item label="信念"><n-input v-model:value="data.culture.belief" /></n-form-item></n-gi>
</n-grid>
<n-form-item label="价值观">
<div style="width: 100%">
<n-card v-for="(val, i) in data.culture.values" :key="i" size="small" style="margin-bottom: 12px">
<template #header>
<n-space align="center">
<n-input v-model:value="val.name" placeholder="名称" style="width: 120px" />
<n-button size="tiny" type="error" quaternary @click="data.culture.values.splice(i, 1)">删除</n-button>
</n-space>
</template>
<n-dynamic-input v-model:value="val.points" :min="1">
<template #default="{ value, index }">
<n-input
type="textarea"
:autosize="{ minRows: 1, maxRows: 3 }"
:value="value"
@update:value="v => val.points[index] = v"
placeholder="要点"
style="width: 100%"
/>
</template>
</n-dynamic-input>
</n-card>
<n-button size="small" type="primary" dashed @click="data.culture.values.push({ name: '', points: [''] })">添加价值观</n-button>
</div>
</n-form-item>
<n-form-item label="Slogan">
<div style="width: 100%">
<div v-for="(line, i) in data.culture.slogan.lines" :key="i" style="display: flex; gap: 8px; align-items: center; margin-bottom: 8px">
<n-input v-model:value="line.label" placeholder="标签" style="width: 100px" />
<n-input v-model:value="line.text" placeholder="文字" style="flex: 1" />
<n-button size="tiny" type="error" quaternary @click="data.culture.slogan.lines.splice(i, 1)">删</n-button>
</div>
<n-button size="small" type="primary" dashed @click="data.culture.slogan.lines.push({ label: '', text: '' })">添加行</n-button>
</div>
</n-form-item>
</n-form>
</n-card>
</n-collapse-item>
<!-- xiaohongshu -->
<n-collapse-item title="小红书" name="xiaohongshu">
<n-card>
<n-form label-placement="top">
<n-grid :cols="2" :x-gap="12">
<n-gi><n-form-item label="账号"><n-input v-model:value="data.xiaohongshu.account" /></n-form-item></n-gi>
<n-gi><n-form-item label="认证类型"><n-input v-model:value="data.xiaohongshu.verifiedType" /></n-form-item></n-gi>
<n-gi><n-form-item label="粉丝数"><n-input v-model:value="data.xiaohongshu.followers" /></n-form-item></n-gi>
<n-gi><n-form-item label="点赞数"><n-input v-model:value="data.xiaohongshu.likes" /></n-form-item></n-gi>
</n-grid>
<n-form-item label="一句话介绍">
<n-input v-model:value="data.xiaohongshu.tagline" />
</n-form-item>
<n-form-item label="描述">
<n-input v-model:value="data.xiaohongshu.description" type="textarea" :autosize="{ minRows: 2, maxRows: 5 }" />
</n-form-item>
<n-form-item label="标签">
<n-dynamic-input v-model:value="data.xiaohongshu.tags" :min="1" />
</n-form-item>
<n-form-item label="荣誉">
<n-dynamic-input v-model:value="data.xiaohongshu.awards" :min="0" />
</n-form-item>
</n-form>
</n-card>
</n-collapse-item>
<!-- subsidiaries -->
<n-collapse-item title="子公司" name="subsidiaries">
<n-card>
<div style="width: 100%">
<n-card v-for="(sub, i) in data.subsidiaries" :key="i" size="small" style="margin-bottom: 12px">
<template #header>
<n-space align="center">
<span style="font-size: 13px; color: #6b7280">{{ i + 1 }}.</span>
<n-button size="tiny" type="error" quaternary @click="data.subsidiaries.splice(i, 1)">删除</n-button>
</n-space>
</template>
<n-form label-placement="top">
<n-grid :cols="2" :x-gap="12">
<n-gi><n-form-item label="名称"><n-input v-model:value="sub.name" /></n-form-item></n-gi>
<n-gi><n-form-item label="成立时间"><n-input v-model:value="sub.established" /></n-form-item></n-gi>
</n-grid>
<n-form-item label="角色">
<n-input v-model:value="sub.role" type="textarea" :autosize="{ minRows: 1, maxRows: 3 }" />
</n-form-item>
</n-form>
</n-card>
<n-button size="small" type="primary" dashed @click="data.subsidiaries.push({ name: '', role: '', established: '' })">添加子公司</n-button>
</div>
</n-card>
</n-collapse-item>
<!-- trademark -->
<n-collapse-item title="商标" name="trademark">
<n-card>
<n-form label-placement="top">
<n-grid :cols="2" :x-gap="12">
<n-gi><n-form-item label="商标名"><n-input v-model:value="data.trademark.name" /></n-form-item></n-gi>
<n-gi><n-form-item label="注册范围"><n-input v-model:value="data.trademark.scope" /></n-form-item></n-gi>
<n-gi :span="2"><n-form-item label="持有者"><n-input v-model:value="data.trademark.holder" /></n-form-item></n-gi>
</n-grid>
<n-form-item label="描述">
<n-input v-model:value="data.trademark.description" type="textarea" :autosize="{ minRows: 2, maxRows: 5 }" />
</n-form-item>
</n-form>
</n-card>
</n-collapse-item>
<!-- copyrights -->
<n-collapse-item title="版权" name="copyrights">
<n-card>
<div style="width: 100%">
<n-card v-for="(c, i) in data.copyrights" :key="i" size="small" style="margin-bottom: 12px">
<template #header>
<n-space align="center">
<span style="font-size: 13px; color: #6b7280">{{ c.name || '新版权' }}</span>
<n-button size="tiny" type="error" quaternary @click="data.copyrights.splice(i, 1)">删除</n-button>
</n-space>
</template>
<n-form label-placement="top">
<n-grid :cols="2" :x-gap="12">
<n-gi><n-form-item label="作品名"><n-input v-model:value="c.name" /></n-form-item></n-gi>
<n-gi><n-form-item label="登记号"><n-input v-model:value="c.regNo" /></n-form-item></n-gi>
<n-gi><n-form-item label="类别"><n-input v-model:value="c.category" /></n-form-item></n-gi>
<n-gi><n-form-item label="登记日期"><n-input v-model:value="c.date" /></n-form-item></n-gi>
<n-gi :span="2"><n-form-item label="持有者"><n-input v-model:value="c.holder" /></n-form-item></n-gi>
</n-grid>
<n-form-item label="描述">
<n-input v-model:value="c.description" type="textarea" :autosize="{ minRows: 1, maxRows: 3 }" />
</n-form-item>
</n-form>
</n-card>
<n-button size="small" type="primary" dashed @click="data.copyrights.push({ name: '', regNo: '', category: '', holder: '', date: '', description: '' })">添加版权</n-button>
</div>
</n-card>
</n-collapse-item>
<!-- founder -->
<n-collapse-item title="创始人" name="founder">
<n-card>
<n-form label-placement="top">
<n-grid :cols="2" :x-gap="12">
<n-gi><n-form-item label="姓名"><n-input v-model:value="data.founder.name" /></n-form-item></n-gi>
<n-gi><n-form-item label="头衔"><n-input v-model:value="data.founder.title" /></n-form-item></n-gi>
<n-gi><n-form-item label="深耕年限"><n-input v-model:value="data.founder.yearsInHulunbuir" /></n-form-item></n-gi>
</n-grid>
<n-form-item label="背景">
<n-input v-model:value="data.founder.background" type="textarea" :autosize="{ minRows: 1, maxRows: 3 }" />
</n-form-item>
<n-form-item label="专长">
<n-dynamic-input v-model:value="data.founder.expertise" :min="1" />
</n-form-item>
</n-form>
</n-card>
</n-collapse-item>
<!-- guarantees -->
<n-collapse-item title="保障承诺" name="guarantees">
<n-card>
<div style="width: 100%">
<div v-for="(g, i) in data.guarantees" :key="i" style="margin-bottom: 12px; padding: 12px; border: 1px solid #e5e7eb; border-radius: 6px">
<n-space justify="space-between" style="margin-bottom: 8px">
<span style="font-size: 13px; font-weight: 500; color: #374151">{{ i + 1 }}.</span>
<n-button size="tiny" type="error" quaternary @click="data.guarantees.splice(i, 1)">删除</n-button>
</n-space>
<n-form label-placement="top">
<n-form-item label="标题"><n-input v-model:value="g.title" /></n-form-item>
<n-form-item label="详情"><n-input v-model:value="g.detail" type="textarea" :autosize="{ minRows: 1, maxRows: 3 }" /></n-form-item>
</n-form>
</div>
<n-button size="small" type="primary" dashed @click="data.guarantees.push({ title: '', detail: '' })">添加保障</n-button>
</div>
</n-card>
</n-collapse-item>
<!-- licenses -->
<n-collapse-item title="许可证" name="licenses">
<n-card>
<div style="width: 100%">
<n-card v-for="(l, i) in data.licenses" :key="i" size="small" style="margin-bottom: 12px">
<template #header>
<n-space align="center">
<span style="font-size: 13px; color: #6b7280">{{ l.licenseNo || '新许可证' }}</span>
<n-button size="tiny" type="error" quaternary @click="data.licenses.splice(i, 1)">删除</n-button>
</n-space>
</template>
<n-form label-placement="top">
<n-grid :cols="2" :x-gap="12">
<n-gi><n-form-item label="许可证号"><n-input v-model:value="l.licenseNo" /></n-form-item></n-gi>
<n-gi><n-form-item label="持有者"><n-input v-model:value="l.holder" /></n-form-item></n-gi>
<n-gi><n-form-item label="发证机关"><n-input v-model:value="l.issuer" /></n-form-item></n-gi>
<n-gi><n-form-item label="有效期"><n-input v-model:value="l.validPeriod" /></n-form-item></n-gi>
</n-grid>
<n-form-item label="备注">
<n-input v-model:value="l.note" />
</n-form-item>
</n-form>
</n-card>
<n-button size="small" type="primary" dashed @click="data.licenses.push({ licenseNo: '', holder: '', issuer: '', validPeriod: '', note: '' })">添加许可证</n-button>
</div>
</n-card>
</n-collapse-item>
<!-- insurance -->
<n-collapse-item title="保险" name="insurance">
<n-card>
<n-form label-placement="top">
<n-grid :cols="2" :x-gap="12">
<n-gi><n-form-item label="标题"><n-input v-model:value="data.insurance.title" /></n-form-item></n-gi>
<n-gi><n-form-item label="承保公司"><n-input v-model:value="data.insurance.insurer" /></n-form-item></n-gi>
</n-grid>
<n-form-item label="描述">
<n-input v-model:value="data.insurance.description" type="textarea" :autosize="{ minRows: 2, maxRows: 4 }" />
</n-form-item>
<n-form-item label="保障范围">
<n-input v-model:value="data.insurance.policyScope" />
</n-form-item>
<n-form-item label="理赔联系方式">
<n-input v-model:value="data.insurance.claimContact" />
</n-form-item>
</n-form>
</n-card>
</n-collapse-item>
<!-- awards -->
<n-collapse-item title="荣誉奖项" name="awards">
<n-card>
<div style="width: 100%">
<n-card v-for="(a, i) in data.awards" :key="i" size="small" style="margin-bottom: 12px">
<template #header>
<n-space align="center">
<span style="font-size: 13px; color: #6b7280">{{ a.year }} {{ a.title || '新奖项' }}</span>
<n-button size="tiny" type="error" quaternary @click="data.awards.splice(i, 1)">删除</n-button>
</n-space>
</template>
<n-form label-placement="top">
<n-grid :cols="2" :x-gap="12">
<n-gi><n-form-item label="年份"><n-input v-model:value="a.year" /></n-form-item></n-gi>
<n-gi><n-form-item label="颁发机构"><n-input v-model:value="a.issuer" /></n-form-item></n-gi>
</n-grid>
<n-form-item label="奖项名称">
<n-input v-model:value="a.title" />
</n-form-item>
<n-form-item label="详情">
<n-input v-model:value="a.detail" />
</n-form-item>
</n-form>
</n-card>
<n-button size="small" type="primary" dashed @click="data.awards.push({ year: '', title: '', detail: '', issuer: '' })">添加奖项</n-button>
</div>
</n-card>
</n-collapse-item>
</n-collapse>
</n-spin>
</div>
</template>
<script setup>
import { NButton, NCard, NSpace, NInput, NForm, NFormItem, NSpin, NGrid, NGi, NCollapse, NCollapseItem, NDynamicInput, NInputNumber } from 'naive-ui'
import { useMessage } from 'naive-ui'
definePageMeta({ layout: 'admin', middleware: 'admin' })
const { adminFetch } = useAdmin()
const message = useMessage()
const data = ref({
story: { title: '', paragraphs: [] },
serviceJourney: { title: '', subtitle: '', moments: [] },
team: { summary: '' },
heritage: { title: '', milestones: [] },
culture: { core: '', belief: '', values: [], slogan: { lines: [] } },
xiaohongshu: { account: '', verified: true, verifiedType: '', followers: '', likes: '', tagline: '', description: '', tags: [], awards: [] },
subsidiaries: [],
trademark: { name: '', scope: '', holder: '', description: '' },
copyrights: [],
founder: { name: '', title: '', background: '', expertise: [], yearsInHulunbuir: '' },
guarantees: [],
licenses: [],
insurance: { title: '', description: '', insurer: '', policyScope: '', claimContact: '' },
awards: []
})
const loaded = ref(false)
const saving = ref(false)
async function load() {
const res = await adminFetch('/api/admin/site-content?key=about')
data.value = res.data
loaded.value = true
}
async function save() {
saving.value = true
try {
await adminFetch('/api/admin/site-content', { method: 'PUT', body: { key: 'about', data: data.value } })
message.success('已保存')
} catch (e) {
message.error('保存失败')
} finally {
saving.value = false
}
}
onMounted(load)
</script>