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>
这个提交包含在:
父节点
ee078b61bc
当前提交
810ae2724d
1
.claude/scheduled_tasks.lock
普通文件
1
.claude/scheduled_tasks.lock
普通文件
@ -0,0 +1 @@
|
||||
{"sessionId":"c823a359-416f-43c5-8fad-22f6a5ddd8bc","pid":59167,"acquiredAt":1774346188338}
|
||||
423
pages/admin/content/about.vue
普通文件
423
pages/admin/content/about.vue
普通文件
@ -0,0 +1,423 @@
|
||||
<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>
|
||||
@ -24,6 +24,8 @@
|
||||
<n-gi><n-form-item label="功能 Slogan"><n-input v-model:value="data.slogan.functional" /></n-form-item></n-gi>
|
||||
<n-gi><n-form-item label="ICP 主体"><n-input v-model:value="data.icpEntity" /></n-form-item></n-gi>
|
||||
<n-gi><n-form-item label="ICP 备案号"><n-input v-model:value="data.icp" /></n-form-item></n-gi>
|
||||
<n-gi><n-form-item label="ICP 链接"><n-input v-model:value="data.icpUrl" placeholder="https://beian.miit.gov.cn" /></n-form-item></n-gi>
|
||||
<n-gi><n-form-item label="电子合同说明"><n-input v-model:value="data.eContract" /></n-form-item></n-gi>
|
||||
</n-grid>
|
||||
</n-form>
|
||||
</n-card>
|
||||
@ -54,25 +56,73 @@
|
||||
</n-space>
|
||||
</n-space>
|
||||
</n-card>
|
||||
|
||||
<n-card title="CTA 按钮">
|
||||
<n-form label-placement="top">
|
||||
<div v-for="(btnKey, btnLabel) in ctaLabels" :key="btnKey" style="margin-bottom: 12px; padding-bottom: 12px; border-bottom: 1px solid #f0f0f0;">
|
||||
<div style="font-size: 13px; font-weight: 500; color: #555; margin-bottom: 6px;">{{ btnLabel }}</div>
|
||||
<n-grid :cols="2" :x-gap="12">
|
||||
<n-gi><n-form-item label="按钮文字" :show-feedback="false"><n-input v-model:value="data.ctaButtons[btnKey].text" /></n-form-item></n-gi>
|
||||
<n-gi><n-form-item label="链接地址" :show-feedback="false"><n-input v-model:value="data.ctaButtons[btnKey].to" /></n-form-item></n-gi>
|
||||
</n-grid>
|
||||
</div>
|
||||
</n-form>
|
||||
</n-card>
|
||||
|
||||
<n-card title="转化路径">
|
||||
<template #header-extra>
|
||||
<n-button size="small" type="primary" @click="addConversionStep">添加</n-button>
|
||||
</template>
|
||||
<n-space vertical :size="8">
|
||||
<n-space v-for="(c, i) in data.conversionPath" :key="i" align="center">
|
||||
<n-input-number v-model:value="c.step" placeholder="步骤" style="width: 80px" size="small" :min="1" />
|
||||
<n-input v-model:value="c.label" placeholder="标签" style="width: 140px" />
|
||||
<n-input v-model:value="c.to" placeholder="链接" style="width: 180px" />
|
||||
<n-button size="tiny" type="error" quaternary @click="data.conversionPath.splice(i, 1)">删</n-button>
|
||||
</n-space>
|
||||
</n-space>
|
||||
</n-card>
|
||||
</n-space>
|
||||
</n-spin>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { NButton, NCard, NSpace, NInput, NForm, NFormItem, NSpin, NGrid, NGi } from 'naive-ui'
|
||||
import { NButton, NCard, NSpace, NInput, NInputNumber, NForm, NFormItem, NSpin, NGrid, NGi } from 'naive-ui'
|
||||
import { useMessage } from 'naive-ui'
|
||||
|
||||
definePageMeta({ layout: 'admin', middleware: 'admin' })
|
||||
const { adminFetch } = useAdmin()
|
||||
const message = useMessage()
|
||||
const data = ref({ slogan: {}, trustStats: [], differentiators: [] })
|
||||
const data = ref({ slogan: {}, trustStats: [], differentiators: [], ctaButtons: { primary: { text: '', to: '' }, secondary: { text: '', to: '' }, pricing: { text: '', to: '' }, customize: { text: '', to: '' }, contact: { text: '', to: '' } }, conversionPath: [], icpUrl: '', eContract: '' })
|
||||
const loaded = ref(false)
|
||||
const saving = ref(false)
|
||||
|
||||
const ctaLabels = {
|
||||
primary: '主按钮 (primary)',
|
||||
secondary: '次按钮 (secondary)',
|
||||
pricing: '价格按钮 (pricing)',
|
||||
customize: '定制按钮 (customize)',
|
||||
contact: '联系按钮 (contact)'
|
||||
}
|
||||
|
||||
function addConversionStep() {
|
||||
const nextStep = data.value.conversionPath.length ? Math.max(...data.value.conversionPath.map(c => c.step || 0)) + 1 : 1
|
||||
data.value.conversionPath.push({ step: nextStep, label: '', to: '' })
|
||||
}
|
||||
|
||||
async function load() {
|
||||
const res = await adminFetch('/api/admin/site-content?key=brand')
|
||||
data.value = res.data
|
||||
const d = res.data
|
||||
if (!d.ctaButtons) d.ctaButtons = {}
|
||||
const defaultBtn = { text: '', to: '' }
|
||||
;['primary', 'secondary', 'pricing', 'customize', 'contact'].forEach(k => {
|
||||
if (!d.ctaButtons[k]) d.ctaButtons[k] = { ...defaultBtn }
|
||||
})
|
||||
if (!d.conversionPath) d.conversionPath = []
|
||||
if (!d.icpUrl) d.icpUrl = ''
|
||||
if (!d.eContract) d.eContract = ''
|
||||
data.value = d
|
||||
loaded.value = true
|
||||
}
|
||||
|
||||
|
||||
@ -29,6 +29,55 @@
|
||||
</n-form-item>
|
||||
</n-card>
|
||||
|
||||
<n-card title="快速推荐" style="margin-bottom: 20px;">
|
||||
<template #header-extra>
|
||||
<n-button size="small" type="primary" @click="data.meta.quickRecommendations.push({ label: '', months: '', reason: '' })">添加</n-button>
|
||||
</template>
|
||||
<n-space vertical :size="10">
|
||||
<n-space v-for="(r, ri) in data.meta.quickRecommendations" :key="ri" align="center">
|
||||
<n-input v-model:value="r.label" placeholder="标签" style="width: 120px" />
|
||||
<n-input v-model:value="r.months" placeholder="月份" style="width: 140px" />
|
||||
<n-input v-model:value="r.reason" placeholder="原因" style="width: 360px" />
|
||||
<n-button size="tiny" type="error" quaternary @click="data.meta.quickRecommendations.splice(ri, 1)">删</n-button>
|
||||
</n-space>
|
||||
</n-space>
|
||||
</n-card>
|
||||
|
||||
<n-card title="出行建议" style="margin-bottom: 20px;">
|
||||
<template #header-extra>
|
||||
<n-button size="small" type="primary" @click="data.meta.travelAdvice.push({ period: '', level: '', levelText: '', description: '' })">添加</n-button>
|
||||
</template>
|
||||
<n-space vertical :size="10">
|
||||
<n-card v-for="(a, ai) in data.meta.travelAdvice" :key="ai" size="small" style="background: #fafafa;">
|
||||
<n-space align="center" justify="end" style="margin-bottom: 6px;">
|
||||
<n-button size="tiny" type="error" quaternary @click="data.meta.travelAdvice.splice(ai, 1)">删除</n-button>
|
||||
</n-space>
|
||||
<n-grid :cols="2" :x-gap="12">
|
||||
<n-gi>
|
||||
<n-form-item label="时段" :show-feedback="false" style="margin-bottom: 8px;">
|
||||
<n-input v-model:value="a.period" size="small" />
|
||||
</n-form-item>
|
||||
</n-gi>
|
||||
<n-gi>
|
||||
<n-form-item label="推荐等级标识" :show-feedback="false" style="margin-bottom: 8px;">
|
||||
<n-input v-model:value="a.level" size="small" placeholder="如 best_value / peak / not_recommended" />
|
||||
</n-form-item>
|
||||
</n-gi>
|
||||
<n-gi>
|
||||
<n-form-item label="等级文字" :show-feedback="false" style="margin-bottom: 8px;">
|
||||
<n-input v-model:value="a.levelText" size="small" />
|
||||
</n-form-item>
|
||||
</n-gi>
|
||||
<n-gi>
|
||||
<n-form-item label="描述" :show-feedback="false" style="margin-bottom: 8px;">
|
||||
<n-input v-model:value="a.description" size="small" />
|
||||
</n-form-item>
|
||||
</n-gi>
|
||||
</n-grid>
|
||||
</n-card>
|
||||
</n-space>
|
||||
</n-card>
|
||||
|
||||
<n-grid :cols="3" :x-gap="16" :y-gap="16" responsive="screen" :collapsed-rows="999">
|
||||
<n-gi v-for="m in data.months" :key="m.month">
|
||||
<n-card
|
||||
@ -64,6 +113,15 @@
|
||||
@input="m.highlights = $event.split('\n').filter(Boolean)"
|
||||
/>
|
||||
</n-form-item>
|
||||
<n-form-item label="推荐产品(每行一个)" :show-feedback="false" style="margin-bottom: 8px;">
|
||||
<n-input
|
||||
type="textarea"
|
||||
:rows="2"
|
||||
size="small"
|
||||
:value="(m.recommendedProducts||[]).join('\n')"
|
||||
@input="m.recommendedProducts = $event.split('\n').filter(Boolean)"
|
||||
/>
|
||||
</n-form-item>
|
||||
<n-space align="center" style="margin-top: 4px;">
|
||||
<n-tag
|
||||
:type="m.isPeakSeason ? 'success' : 'default'"
|
||||
@ -89,11 +147,24 @@ import { useMessage } from 'naive-ui'
|
||||
definePageMeta({ layout: 'admin', middleware: 'admin' })
|
||||
const { adminFetch } = useAdmin()
|
||||
const message = useMessage()
|
||||
const data = ref({ meta: {}, months: [] })
|
||||
const data = ref({ meta: { quickRecommendations: [], travelAdvice: [] }, months: [] })
|
||||
const loaded = ref(false)
|
||||
const saving = ref(false)
|
||||
|
||||
async function load() { const res = await adminFetch('/api/admin/site-content?key=calendar'); data.value = res.data; loaded.value = true }
|
||||
async function load() {
|
||||
const res = await adminFetch('/api/admin/site-content?key=calendar')
|
||||
const d = res.data
|
||||
if (!d.meta) d.meta = {}
|
||||
if (!d.meta.quickRecommendations) d.meta.quickRecommendations = []
|
||||
if (!d.meta.travelAdvice) d.meta.travelAdvice = []
|
||||
if (d.months) {
|
||||
d.months.forEach(m => {
|
||||
if (!m.recommendedProducts) m.recommendedProducts = []
|
||||
})
|
||||
}
|
||||
data.value = d
|
||||
loaded.value = true
|
||||
}
|
||||
async function save() {
|
||||
saving.value = true
|
||||
try {
|
||||
|
||||
@ -98,6 +98,45 @@
|
||||
</template>
|
||||
</n-space>
|
||||
</div>
|
||||
|
||||
<n-card v-if="s.months" title="每月穿搭" size="small" style="margin-top: 12px;">
|
||||
<n-collapse>
|
||||
<n-collapse-item v-for="(m, mi) in s.months" :key="mi" :name="mi">
|
||||
<template #header>{{ m.name }}</template>
|
||||
<n-form-item label="描述" :show-feedback="false" style="margin-bottom: 8px;">
|
||||
<n-input v-model:value="m.desc" type="textarea" :rows="2" />
|
||||
</n-form-item>
|
||||
<n-grid :cols="2" :x-gap="12">
|
||||
<n-gi>
|
||||
<n-form-item label="日间温度" :show-feedback="false" style="margin-bottom: 8px;">
|
||||
<n-input v-model:value="m.tempDay" />
|
||||
</n-form-item>
|
||||
</n-gi>
|
||||
<n-gi>
|
||||
<n-form-item label="夜间温度" :show-feedback="false" style="margin-bottom: 8px;">
|
||||
<n-input v-model:value="m.tempNight" />
|
||||
</n-form-item>
|
||||
</n-gi>
|
||||
<n-gi>
|
||||
<n-form-item label="成人穿搭" :show-feedback="false" style="margin-bottom: 8px;">
|
||||
<n-input v-model:value="m.adultWear" type="textarea" :rows="2" />
|
||||
</n-form-item>
|
||||
</n-gi>
|
||||
<n-gi>
|
||||
<n-form-item label="儿童穿搭" :show-feedback="false" style="margin-bottom: 8px;">
|
||||
<n-input v-model:value="m.kidsWear" type="textarea" :rows="2" />
|
||||
</n-form-item>
|
||||
</n-gi>
|
||||
</n-grid>
|
||||
<n-form-item label="亮点" :show-feedback="false" style="margin-bottom: 8px;">
|
||||
<n-input v-model:value="m.highlight" />
|
||||
</n-form-item>
|
||||
<n-form-item label="备注" :show-feedback="false" style="margin-bottom: 8px;">
|
||||
<n-input v-model:value="m.note" />
|
||||
</n-form-item>
|
||||
</n-collapse-item>
|
||||
</n-collapse>
|
||||
</n-card>
|
||||
</div>
|
||||
</n-card>
|
||||
</template>
|
||||
@ -105,7 +144,7 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { NButton, NCard, NSpace, NInput, NFormItem, NSpin, NGrid, NGi, NTag } from 'naive-ui'
|
||||
import { NButton, NCard, NSpace, NInput, NFormItem, NSpin, NGrid, NGi, NTag, NCollapse, NCollapseItem } from 'naive-ui'
|
||||
import { useMessage } from 'naive-ui'
|
||||
|
||||
definePageMeta({ layout: 'admin', middleware: 'admin' })
|
||||
|
||||
162
pages/admin/content/pricing.vue
普通文件
162
pages/admin/content/pricing.vue
普通文件
@ -0,0 +1,162 @@
|
||||
<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="/pricing" 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="['intro']">
|
||||
<!-- intro -->
|
||||
<n-collapse-item title="价格说明" name="intro">
|
||||
<n-card>
|
||||
<n-form label-placement="top">
|
||||
<n-form-item label="简介">
|
||||
<n-input v-model:value="data.intro" type="textarea" :autosize="{ minRows: 2, maxRows: 4 }" />
|
||||
</n-form-item>
|
||||
</n-form>
|
||||
</n-card>
|
||||
</n-collapse-item>
|
||||
|
||||
<!-- products -->
|
||||
<n-collapse-item title="产品列表" name="products">
|
||||
<n-card>
|
||||
<div style="width: 100%">
|
||||
<n-card v-for="(p, i) in data.products" :key="i" size="small" style="margin-bottom: 12px">
|
||||
<template #header>
|
||||
<n-space align="center">
|
||||
<span style="font-size: 13px; font-weight: 500; color: #374151">{{ p.name || '新产品' }}</span>
|
||||
<span style="font-size: 12px; color: #3a7d44">{{ p.priceLabel }}</span>
|
||||
<n-button size="tiny" type="error" quaternary @click="data.products.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="产品ID"><n-input v-model:value="p.id" /></n-form-item></n-gi>
|
||||
<n-gi><n-form-item label="产品名称"><n-input v-model:value="p.name" /></n-form-item></n-gi>
|
||||
<n-gi><n-form-item label="价格标签"><n-input v-model:value="p.priceLabel" /></n-form-item></n-gi>
|
||||
<n-gi><n-form-item label="链接"><n-input v-model:value="p.url" /></n-form-item></n-gi>
|
||||
</n-grid>
|
||||
<n-form-item label="价格说明">
|
||||
<n-input v-model:value="p.priceNote" type="textarea" :autosize="{ minRows: 1, maxRows: 3 }" />
|
||||
</n-form-item>
|
||||
<n-grid :cols="3" :x-gap="12">
|
||||
<n-gi><n-form-item label="标签"><n-input v-model:value="p.tag" /></n-form-item></n-gi>
|
||||
<n-gi><n-form-item label="目标人群"><n-input v-model:value="p.audience" /></n-form-item></n-gi>
|
||||
<n-gi>
|
||||
<n-form-item label="天/晚">
|
||||
<n-space>
|
||||
<n-input-number v-model:value="p.days" placeholder="天" :min="0" style="width: 80px" />
|
||||
<n-input-number v-model:value="p.nights" placeholder="晚" :min="0" style="width: 80px" />
|
||||
</n-space>
|
||||
</n-form-item>
|
||||
</n-gi>
|
||||
</n-grid>
|
||||
<n-form-item label="亮点">
|
||||
<n-dynamic-input v-model:value="p.highlights" :min="0" />
|
||||
</n-form-item>
|
||||
</n-form>
|
||||
</n-card>
|
||||
<n-button size="small" type="primary" dashed @click="data.products.push({ id: '', name: '', priceLabel: '', priceNote: '', highlights: [], url: '', tag: '', audience: '', days: null, nights: null })">添加产品</n-button>
|
||||
</div>
|
||||
</n-card>
|
||||
</n-collapse-item>
|
||||
|
||||
<!-- included / excluded -->
|
||||
<n-collapse-item title="费用包含/不包含" name="fees">
|
||||
<n-card>
|
||||
<n-form label-placement="top">
|
||||
<n-form-item label="费用包含">
|
||||
<n-dynamic-input v-model:value="data.included" :min="1" />
|
||||
</n-form-item>
|
||||
<n-form-item label="费用不包含">
|
||||
<n-dynamic-input v-model:value="data.excluded" :min="1" />
|
||||
</n-form-item>
|
||||
</n-form>
|
||||
</n-card>
|
||||
</n-collapse-item>
|
||||
|
||||
<!-- philosophy -->
|
||||
<n-collapse-item title="价格理念" name="philosophy">
|
||||
<n-card>
|
||||
<n-form label-placement="top">
|
||||
<n-form-item label="标题">
|
||||
<n-input v-model:value="data.philosophy.title" />
|
||||
</n-form-item>
|
||||
<n-form-item label="内容">
|
||||
<n-input v-model:value="data.philosophy.content" type="textarea" :autosize="{ minRows: 2, maxRows: 5 }" />
|
||||
</n-form-item>
|
||||
<n-form-item label="承诺">
|
||||
<n-input v-model:value="data.philosophy.promise" />
|
||||
</n-form-item>
|
||||
</n-form>
|
||||
</n-card>
|
||||
</n-collapse-item>
|
||||
|
||||
<!-- cta -->
|
||||
<n-collapse-item title="行动号召" name="cta">
|
||||
<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.cta.title" /></n-form-item></n-gi>
|
||||
<n-gi><n-form-item label="按钮文字"><n-input v-model:value="data.cta.button" /></n-form-item></n-gi>
|
||||
</n-grid>
|
||||
<n-form-item label="描述">
|
||||
<n-input v-model:value="data.cta.description" />
|
||||
</n-form-item>
|
||||
<n-form-item label="跳转链接">
|
||||
<n-input v-model:value="data.cta.to" />
|
||||
</n-form-item>
|
||||
</n-form>
|
||||
</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({
|
||||
intro: '',
|
||||
products: [],
|
||||
included: [],
|
||||
excluded: [],
|
||||
philosophy: { title: '', content: '', promise: '' },
|
||||
cta: { title: '', description: '', to: '', button: '' }
|
||||
})
|
||||
const loaded = ref(false)
|
||||
const saving = ref(false)
|
||||
|
||||
async function load() {
|
||||
const res = await adminFetch('/api/admin/site-content?key=pricing')
|
||||
data.value = res.data
|
||||
loaded.value = true
|
||||
}
|
||||
|
||||
async function save() {
|
||||
saving.value = true
|
||||
try {
|
||||
await adminFetch('/api/admin/site-content', { method: 'PUT', body: { key: 'pricing', data: data.value } })
|
||||
message.success('已保存')
|
||||
} catch (e) {
|
||||
message.error('保存失败')
|
||||
} finally {
|
||||
saving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(load)
|
||||
</script>
|
||||
@ -59,6 +59,49 @@
|
||||
<n-button dashed block @click="addTo('awards', { year: '', title: '', detail: '', issuer: '' })">+ 添加荣誉奖项</n-button>
|
||||
</n-card>
|
||||
|
||||
<!-- 企业矩阵 -->
|
||||
<n-card title="企业矩阵" style="margin-bottom: 16px;">
|
||||
<n-grid :cols="2" :x-gap="12" :y-gap="12">
|
||||
<n-gi>
|
||||
<n-form-item label="企业总数">
|
||||
<n-input-number v-model:value="data.enterpriseMatrix.totalCount" style="width:100%" />
|
||||
</n-form-item>
|
||||
</n-gi>
|
||||
<n-gi>
|
||||
<n-form-item label="描述">
|
||||
<n-input v-model:value="data.enterpriseMatrix.description" />
|
||||
</n-form-item>
|
||||
</n-gi>
|
||||
</n-grid>
|
||||
<div style="margin-top:12px; font-weight:500; margin-bottom:8px;">企业列表</div>
|
||||
<div v-for="(c, i) in data.enterpriseMatrix.companies || []" :key="i" style="border: 1px solid #e5e7eb; border-radius: 6px; padding: 10px; margin-bottom: 8px;">
|
||||
<n-grid :cols="4" :x-gap="8">
|
||||
<n-gi>
|
||||
<n-form-item label="名称" :show-feedback="false">
|
||||
<n-input v-model:value="c.name" size="small" placeholder="企业名称" />
|
||||
</n-form-item>
|
||||
</n-gi>
|
||||
<n-gi>
|
||||
<n-form-item label="标签" :show-feedback="false">
|
||||
<n-input v-model:value="c.tag" size="small" placeholder="如:核心旅行社" />
|
||||
</n-form-item>
|
||||
</n-gi>
|
||||
<n-gi>
|
||||
<n-form-item label="成立时间" :show-feedback="false">
|
||||
<n-input v-model:value="c.established" size="small" placeholder="如:2023" />
|
||||
</n-form-item>
|
||||
</n-gi>
|
||||
<n-gi>
|
||||
<n-form-item label="角色" :show-feedback="false">
|
||||
<n-input v-model:value="c.role" size="small" placeholder="职能描述" />
|
||||
</n-form-item>
|
||||
</n-gi>
|
||||
</n-grid>
|
||||
<n-button size="tiny" type="error" quaternary style="margin-top:4px;" @click="data.enterpriseMatrix.companies.splice(i, 1)">删除</n-button>
|
||||
</div>
|
||||
<n-button dashed block @click="data.enterpriseMatrix.companies.push({ name: '', tag: '', established: '', role: '' })">添加企业</n-button>
|
||||
</n-card>
|
||||
|
||||
<!-- 保险信息 -->
|
||||
<n-card v-if="data.insurance" title="保险信息" style="margin-bottom: 16px;">
|
||||
<n-grid :cols="2" :x-gap="12" :y-gap="12">
|
||||
@ -95,7 +138,7 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { NButton, NCard, NSpace, NInput, NFormItem, NSpin, NGrid, NGi } from 'naive-ui'
|
||||
import { NButton, NCard, NSpace, NInput, NInputNumber, NFormItem, NSpin, NGrid, NGi } from 'naive-ui'
|
||||
import { useMessage } from 'naive-ui'
|
||||
|
||||
definePageMeta({ layout: 'admin', middleware: 'admin' })
|
||||
@ -108,6 +151,12 @@ const saving = ref(false)
|
||||
async function load() {
|
||||
const res = await adminFetch('/api/admin/site-content?key=qualifications')
|
||||
data.value = res.data
|
||||
if (!data.value.enterpriseMatrix) {
|
||||
data.value.enterpriseMatrix = { totalCount: 0, description: '', companies: [] }
|
||||
}
|
||||
if (!data.value.enterpriseMatrix.companies) {
|
||||
data.value.enterpriseMatrix.companies = []
|
||||
}
|
||||
loaded.value = true
|
||||
}
|
||||
|
||||
|
||||
205
pages/admin/content/versions.vue
普通文件
205
pages/admin/content/versions.vue
普通文件
@ -0,0 +1,205 @@
|
||||
<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="/versions" 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="['stats']">
|
||||
<!-- stats -->
|
||||
<n-collapse-item title="统计数据" name="stats">
|
||||
<n-card>
|
||||
<n-form label-placement="top">
|
||||
<n-grid :cols="3" :x-gap="12">
|
||||
<n-gi><n-form-item label="迭代次数"><n-input-number v-model:value="data.stats.iterations" :min="0" style="width: 100%" /></n-form-item></n-gi>
|
||||
<n-gi><n-form-item label="年数"><n-input-number v-model:value="data.stats.years" :min="0" style="width: 100%" /></n-form-item></n-gi>
|
||||
<n-gi><n-form-item label="接待人次"><n-input v-model:value="data.stats.guests" /></n-form-item></n-gi>
|
||||
</n-grid>
|
||||
</n-form>
|
||||
</n-card>
|
||||
</n-collapse-item>
|
||||
|
||||
<!-- upgrades2026 -->
|
||||
<n-collapse-item title="2026升级亮点" name="upgrades2026">
|
||||
<n-card>
|
||||
<div style="width: 100%">
|
||||
<n-card v-for="(u, i) in data.upgrades2026" :key="i" size="small" style="margin-bottom: 12px">
|
||||
<template #header>
|
||||
<n-space align="center">
|
||||
<span style="font-size: 13px; color: #6b7280">{{ u.tag || '新亮点' }} - {{ u.name }}</span>
|
||||
<n-button size="tiny" type="error" quaternary @click="data.upgrades2026.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="u.tag" /></n-form-item></n-gi>
|
||||
<n-gi><n-form-item label="名称"><n-input v-model:value="u.name" /></n-form-item></n-gi>
|
||||
</n-grid>
|
||||
<n-form-item label="描述">
|
||||
<n-input v-model:value="u.description" type="textarea" :autosize="{ minRows: 2, maxRows: 4 }" />
|
||||
</n-form-item>
|
||||
<n-form-item label="升级原因">
|
||||
<n-input v-model:value="u.reason" type="textarea" :autosize="{ minRows: 1, maxRows: 3 }" />
|
||||
</n-form-item>
|
||||
</n-form>
|
||||
</n-card>
|
||||
<n-button size="small" type="primary" dashed @click="data.upgrades2026.push({ tag: '', name: '', description: '', reason: '' })">添加亮点</n-button>
|
||||
</div>
|
||||
</n-card>
|
||||
</n-collapse-item>
|
||||
|
||||
<!-- highlights -->
|
||||
<n-collapse-item title="产品亮点" name="highlights">
|
||||
<n-card>
|
||||
<div style="width: 100%">
|
||||
<div v-for="(h, i) in data.highlights" :key="i" style="display: flex; gap: 8px; align-items: center; margin-bottom: 8px">
|
||||
<n-input v-model:value="h.label" placeholder="标签" style="width: 100px" />
|
||||
<n-input v-model:value="h.text" placeholder="描述" style="flex: 1" />
|
||||
<n-button size="tiny" type="error" quaternary @click="data.highlights.splice(i, 1)">删</n-button>
|
||||
</div>
|
||||
<n-button size="small" type="primary" dashed @click="data.highlights.push({ label: '', text: '' })">添加亮点</n-button>
|
||||
</div>
|
||||
</n-card>
|
||||
</n-collapse-item>
|
||||
|
||||
<!-- compareV8V9 -->
|
||||
<n-collapse-item title="版本对比表" name="compareV8V9">
|
||||
<n-card>
|
||||
<n-form label-placement="top">
|
||||
<n-form-item label="表头">
|
||||
<n-space>
|
||||
<n-input v-for="(h, i) in data.compareV8V9.headers" :key="i" v-model:value="data.compareV8V9.headers[i]" :placeholder="'表头' + (i + 1)" style="width: 280px" />
|
||||
</n-space>
|
||||
</n-form-item>
|
||||
<n-form-item label="对比行">
|
||||
<div style="width: 100%">
|
||||
<div v-for="(row, i) in data.compareV8V9.rows" :key="i" style="display: flex; gap: 8px; align-items: center; margin-bottom: 8px">
|
||||
<n-input v-model:value="row[0]" :placeholder="data.compareV8V9.headers[0]" style="flex: 1" />
|
||||
<span style="color: #9ca3af">→</span>
|
||||
<n-input v-model:value="row[1]" :placeholder="data.compareV8V9.headers[1]" style="flex: 1" />
|
||||
<n-button size="tiny" type="error" quaternary @click="data.compareV8V9.rows.splice(i, 1)">删</n-button>
|
||||
</div>
|
||||
<n-button size="small" type="primary" dashed @click="data.compareV8V9.rows.push(['', ''])">添加行</n-button>
|
||||
</div>
|
||||
</n-form-item>
|
||||
</n-form>
|
||||
</n-card>
|
||||
</n-collapse-item>
|
||||
|
||||
<!-- timeline -->
|
||||
<n-collapse-item title="版本时间线" name="timeline">
|
||||
<n-card>
|
||||
<div style="width: 100%">
|
||||
<n-card v-for="(t, i) in data.timeline" :key="i" size="small" style="margin-bottom: 12px">
|
||||
<template #header>
|
||||
<n-space align="center">
|
||||
<span style="font-size: 13px; font-weight: 600; color: #3a7d44">{{ t.version }}</span>
|
||||
<span style="font-size: 12px; color: #9ca3af">{{ t.date }}</span>
|
||||
<n-button size="tiny" type="error" quaternary @click="data.timeline.splice(i, 1)">删除</n-button>
|
||||
</n-space>
|
||||
</template>
|
||||
<n-form label-placement="top">
|
||||
<n-grid :cols="3" :x-gap="12">
|
||||
<n-gi><n-form-item label="版本号"><n-input v-model:value="t.version" /></n-form-item></n-gi>
|
||||
<n-gi><n-form-item label="日期"><n-input v-model:value="t.date" /></n-form-item></n-gi>
|
||||
<n-gi><n-form-item label="标题"><n-input v-model:value="t.title" /></n-form-item></n-gi>
|
||||
</n-grid>
|
||||
<n-form-item label="变更项">
|
||||
<div style="width: 100%">
|
||||
<div v-for="(c, ci) in t.changes" :key="ci" style="display: flex; gap: 8px; align-items: center; margin-bottom: 8px">
|
||||
<n-input v-model:value="c.type" placeholder="类型(add/update/remove)" style="width: 160px" />
|
||||
<n-input v-model:value="c.text" placeholder="变更内容" style="flex: 1" />
|
||||
<n-button size="tiny" type="error" quaternary @click="t.changes.splice(ci, 1)">删</n-button>
|
||||
</div>
|
||||
<n-button size="small" dashed @click="t.changes.push({ type: 'add', text: '' })">添加变更</n-button>
|
||||
</div>
|
||||
</n-form-item>
|
||||
<n-form-item label="迭代原因">
|
||||
<n-input v-model:value="t.reason" type="textarea" :autosize="{ minRows: 2, maxRows: 5 }" />
|
||||
</n-form-item>
|
||||
</n-form>
|
||||
</n-card>
|
||||
<n-button size="small" type="primary" dashed @click="data.timeline.push({ version: '', date: '', title: '', changes: [{ type: 'add', text: '' }], reason: '' })">添加版本</n-button>
|
||||
</div>
|
||||
</n-card>
|
||||
</n-collapse-item>
|
||||
|
||||
<!-- philosophy -->
|
||||
<n-collapse-item title="迭代理念" name="philosophy">
|
||||
<n-card>
|
||||
<div style="width: 100%">
|
||||
<div v-for="(p, i) in data.philosophy" :key="i" style="display: flex; gap: 8px; align-items: center; margin-bottom: 8px">
|
||||
<n-input v-model:value="p.label" placeholder="标签" style="width: 120px" />
|
||||
<n-input v-model:value="p.text" placeholder="描述" style="flex: 1" />
|
||||
<n-button size="tiny" type="error" quaternary @click="data.philosophy.splice(i, 1)">删</n-button>
|
||||
</div>
|
||||
<n-button size="small" type="primary" dashed @click="data.philosophy.push({ label: '', text: '' })">添加理念</n-button>
|
||||
</div>
|
||||
</n-card>
|
||||
</n-collapse-item>
|
||||
|
||||
<!-- quote -->
|
||||
<n-collapse-item title="引用语" name="quote">
|
||||
<n-card>
|
||||
<n-form label-placement="top">
|
||||
<n-form-item label="引用文字">
|
||||
<n-input v-model:value="data.quote.text" type="textarea" :autosize="{ minRows: 2, maxRows: 4 }" />
|
||||
</n-form-item>
|
||||
<n-form-item label="作者">
|
||||
<n-input v-model:value="data.quote.author" />
|
||||
</n-form-item>
|
||||
</n-form>
|
||||
</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({
|
||||
stats: { iterations: 0, years: 0, guests: '' },
|
||||
upgrades2026: [],
|
||||
highlights: [],
|
||||
compareV8V9: { headers: ['', ''], rows: [] },
|
||||
timeline: [],
|
||||
philosophy: [],
|
||||
quote: { text: '', author: '' }
|
||||
})
|
||||
const loaded = ref(false)
|
||||
const saving = ref(false)
|
||||
|
||||
async function load() {
|
||||
const res = await adminFetch('/api/admin/site-content?key=versions')
|
||||
data.value = res.data
|
||||
loaded.value = true
|
||||
}
|
||||
|
||||
async function save() {
|
||||
saving.value = true
|
||||
try {
|
||||
await adminFetch('/api/admin/site-content', { method: 'PUT', body: { key: 'versions', data: data.value } })
|
||||
message.success('已保存')
|
||||
} catch (e) {
|
||||
message.error('保存失败')
|
||||
} finally {
|
||||
saving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(load)
|
||||
</script>
|
||||
@ -68,6 +68,9 @@
|
||||
<n-form-item label="摘要" :show-feedback="false" style="margin-bottom: 6px;">
|
||||
<n-input v-model:value="n.summary" placeholder="摘要" size="small" />
|
||||
</n-form-item>
|
||||
<n-form-item label="标签(逗号分隔)" :show-feedback="false" style="margin-bottom: 6px;">
|
||||
<n-input :value="(n.tags||[]).join(', ')" @update:value="v => n.tags = v.split(',').map(s=>s.trim()).filter(Boolean)" placeholder="呼伦贝尔, 亲子游, 草原" size="small" />
|
||||
</n-form-item>
|
||||
<n-form-item label="版式" :show-feedback="false" style="margin-bottom: 6px;">
|
||||
<n-select
|
||||
v-model:value="n.ratio"
|
||||
|
||||
@ -11,6 +11,41 @@
|
||||
</n-space>
|
||||
</n-space>
|
||||
|
||||
<!-- 页面配置 -->
|
||||
<n-card title="页面配置" style="margin-bottom: 20px">
|
||||
<template #header-extra>
|
||||
<n-button type="primary" size="small" :loading="configSaving" @click="saveConfig">保存配置</n-button>
|
||||
</template>
|
||||
<n-spin :show="!configLoaded">
|
||||
<template v-if="configLoaded">
|
||||
<!-- 介绍描述 -->
|
||||
<n-form-item label="旅拍简介" style="margin-bottom: 12px">
|
||||
<n-input v-model:value="config.intro.description" type="textarea" :rows="3" placeholder="旅拍服务简介文字" />
|
||||
</n-form-item>
|
||||
|
||||
<!-- 数据亮点 -->
|
||||
<div style="font-weight: 500; margin-bottom: 8px; color: #374151">数据亮点(stats)</div>
|
||||
<div v-for="(s, i) in config.intro.stats" :key="i" style="display: flex; gap: 8px; align-items: center; margin-bottom: 8px;">
|
||||
<n-input v-model:value="s.value" placeholder="数值,如 200+" style="width: 100px" size="small" />
|
||||
<n-input v-model:value="s.label" placeholder="标签,如 件蒙古服饰" style="width: 130px" size="small" />
|
||||
<n-input v-model:value="s.note" placeholder="说明文字" style="flex: 1" size="small" />
|
||||
<n-button size="tiny" type="error" quaternary @click="config.intro.stats.splice(i, 1)">删</n-button>
|
||||
</div>
|
||||
<n-button dashed size="small" style="margin-bottom: 16px" @click="config.intro.stats.push({ value: '', label: '', note: '' })">+ 添加数据项</n-button>
|
||||
|
||||
<!-- 分类 -->
|
||||
<div style="font-weight: 500; margin-bottom: 8px; color: #374151">分类筛选(categories)</div>
|
||||
<div v-for="(cat, i) in config.categories" :key="i" style="display: flex; gap: 8px; align-items: center; margin-bottom: 8px;">
|
||||
<n-input v-model:value="cat.key" placeholder="key,如 family" style="width: 120px" size="small" />
|
||||
<n-input v-model:value="cat.label" placeholder="显示名称,如 亲子家庭" style="width: 130px" size="small" />
|
||||
<n-button size="tiny" type="error" quaternary :disabled="cat.key === 'all'" @click="config.categories.splice(i, 1)">删</n-button>
|
||||
</div>
|
||||
<n-button dashed size="small" @click="config.categories.push({ key: '', label: '' })">+ 添加分类</n-button>
|
||||
</template>
|
||||
</n-spin>
|
||||
</n-card>
|
||||
|
||||
<!-- 照片列表 -->
|
||||
<n-card>
|
||||
<n-space style="margin-bottom: 16px">
|
||||
<n-input v-model:value="search" placeholder="搜索标题、分类..." clearable style="width: 260px" @keyup.enter="loadData">
|
||||
@ -42,7 +77,7 @@
|
||||
|
||||
<script setup>
|
||||
import { h } from 'vue'
|
||||
import { NButton, NCard, NSpace, NInput, NDataTable, NTag, NPagination, NImage, NPopconfirm } from 'naive-ui'
|
||||
import { NButton, NCard, NSpace, NInput, NDataTable, NTag, NPagination, NImage, NPopconfirm, NSpin, NFormItem } from 'naive-ui'
|
||||
import { useMessage } from 'naive-ui'
|
||||
|
||||
definePageMeta({ layout: 'admin', middleware: 'admin' })
|
||||
@ -56,6 +91,46 @@ const totalPages = ref(1)
|
||||
const loading = ref(false)
|
||||
const search = ref('')
|
||||
|
||||
// 页面配置(site_content gallery key)
|
||||
const config = ref({ intro: { description: '', stats: [] }, categories: [] })
|
||||
const configLoaded = ref(false)
|
||||
const configSaving = ref(false)
|
||||
|
||||
async function loadConfig() {
|
||||
try {
|
||||
const res = await adminFetch('/api/admin/site-content?key=gallery')
|
||||
const d = res.data || {}
|
||||
config.value = {
|
||||
intro: {
|
||||
description: d.intro?.description || '',
|
||||
stats: d.intro?.stats || [],
|
||||
},
|
||||
categories: d.categories || [],
|
||||
}
|
||||
} catch {
|
||||
message.error('页面配置加载失败')
|
||||
} finally {
|
||||
configLoaded.value = true
|
||||
}
|
||||
}
|
||||
|
||||
async function saveConfig() {
|
||||
configSaving.value = true
|
||||
try {
|
||||
// 先获取完整数据再合并,避免覆盖 works 等其他字段
|
||||
const res = await adminFetch('/api/admin/site-content?key=gallery')
|
||||
const full = res.data || {}
|
||||
full.intro = { ...(full.intro || {}), description: config.value.intro.description, stats: config.value.intro.stats }
|
||||
full.categories = config.value.categories
|
||||
await adminFetch('/api/admin/site-content', { method: 'PUT', body: { key: 'gallery', data: full } })
|
||||
message.success('页面配置已保存')
|
||||
} catch {
|
||||
message.error('保存失败')
|
||||
} finally {
|
||||
configSaving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const columns = [
|
||||
{ title: 'ID', key: 'id', width: 50, render: (row) => h('span', { style: 'color: #9ca3af; font-size: 12px' }, row.id) },
|
||||
{
|
||||
@ -131,5 +206,5 @@ async function handleDelete(item) {
|
||||
} catch { message.error('删除失败') }
|
||||
}
|
||||
|
||||
onMounted(loadData)
|
||||
onMounted(() => { loadData(); loadConfig() })
|
||||
</script>
|
||||
|
||||
@ -78,11 +78,12 @@
|
||||
<n-gi><n-form-item label="名称"><n-input v-model:value="ver.name" /></n-form-item></n-gi>
|
||||
<n-gi><n-form-item label="标签"><n-input v-model:value="ver.tag" /></n-form-item></n-gi>
|
||||
</n-grid>
|
||||
<n-grid :cols="4" :x-gap="12">
|
||||
<n-grid :cols="5" :x-gap="12">
|
||||
<n-gi><n-form-item label="天数"><n-input-number v-model:value="ver.days" :min="1" style="width:100%" /></n-form-item></n-gi>
|
||||
<n-gi><n-form-item label="晚数"><n-input-number v-model:value="ver.nights" :min="0" style="width:100%" /></n-form-item></n-gi>
|
||||
<n-gi><n-form-item label="线路"><n-input v-model:value="ver.line" placeholder="南线/北线" /></n-form-item></n-gi>
|
||||
<n-gi><n-form-item label="受众"><n-input v-model:value="ver.audience" /></n-form-item></n-gi>
|
||||
<n-gi><n-form-item label="主推产品"><n-switch v-model:value="ver.isMainProduct" /></n-form-item></n-gi>
|
||||
</n-grid>
|
||||
<n-form-item label="路线"><n-input v-model:value="ver.route" /></n-form-item>
|
||||
<n-form-item label="描述"><n-input v-model:value="ver.description" type="textarea" :rows="3" /></n-form-item>
|
||||
@ -134,15 +135,46 @@
|
||||
<n-button size="tiny" type="error" quaternary @click="data.selectionGuide.byVacationLength.splice(i,1)">删</n-button>
|
||||
</div>
|
||||
</n-card>
|
||||
<n-card title="版本迭代时间线" style="margin-top:16px">
|
||||
<template #header-extra><n-button size="small" @click="addToArray(data, 'timeline', {year:'',title:'',description:''})">添加</n-button></template>
|
||||
<div v-for="(t, i) in data.timeline || []" :key="i" style="margin-bottom:8px;display:flex;gap:8px;align-items:center;">
|
||||
<n-input v-model:value="t.year" placeholder="年份" style="width:80px" />
|
||||
<n-input v-model:value="t.title" placeholder="标题" style="width:160px" />
|
||||
<n-input v-model:value="t.description" placeholder="描述" style="flex:1" />
|
||||
<n-button size="tiny" type="error" quaternary @click="data.timeline.splice(i,1)">删</n-button>
|
||||
<n-card title="按孩子年龄推荐" style="margin-top:16px">
|
||||
<template #header-extra><n-button size="small" @click="ensureArray(data, 'selectionGuide.byChildAge'); data.selectionGuide.byChildAge.push({condition:'',recommendation:'',reason:''})">添加</n-button></template>
|
||||
<div v-for="(g, i) in data.selectionGuide?.byChildAge || []" :key="i" style="margin-bottom:8px;display:flex;gap:8px;align-items:center;">
|
||||
<n-input v-model:value="g.condition" placeholder="条件" style="width:120px" />
|
||||
<n-input v-model:value="g.recommendation" placeholder="推荐产品ID" style="width:160px" />
|
||||
<n-input v-model:value="g.reason" placeholder="原因" style="flex:1" />
|
||||
<n-button size="tiny" type="error" quaternary @click="data.selectionGuide.byChildAge.splice(i,1)">删</n-button>
|
||||
</div>
|
||||
</n-card>
|
||||
<n-card title="按偏好推荐" style="margin-top:16px">
|
||||
<template #header-extra><n-button size="small" @click="ensureArray(data, 'selectionGuide.byPreference'); data.selectionGuide.byPreference.push({condition:'',recommendation:'',reason:''})">添加</n-button></template>
|
||||
<div v-for="(g, i) in data.selectionGuide?.byPreference || []" :key="i" style="margin-bottom:8px;display:flex;gap:8px;align-items:center;">
|
||||
<n-input v-model:value="g.condition" placeholder="条件" style="width:120px" />
|
||||
<n-input v-model:value="g.recommendation" placeholder="推荐产品ID" style="width:160px" />
|
||||
<n-input v-model:value="g.reason" placeholder="原因" style="flex:1" />
|
||||
<n-button size="tiny" type="error" quaternary @click="data.selectionGuide.byPreference.splice(i,1)">删</n-button>
|
||||
</div>
|
||||
</n-card>
|
||||
<n-card title="版本迭代时间线" style="margin-top:16px">
|
||||
<template #header-extra><n-button size="small" @click="addToArray(data, 'timeline', {version:'',date:'',reason:'',changes:[]})">添加</n-button></template>
|
||||
<n-collapse>
|
||||
<n-collapse-item v-for="(t, i) in data.timeline || []" :key="i" :name="i">
|
||||
<template #header><strong>{{ t.version || '版本 '+(i+1) }}</strong> <span style="color:#9ca3af;font-size:12px;margin-left:8px">{{ t.date }}</span></template>
|
||||
<template #header-extra><n-button size="tiny" type="error" quaternary @click.stop="data.timeline.splice(i,1)">删除</n-button></template>
|
||||
<n-grid :cols="2" :x-gap="12">
|
||||
<n-gi><n-form-item label="版本号"><n-input v-model:value="t.version" /></n-form-item></n-gi>
|
||||
<n-gi><n-form-item label="日期"><n-input v-model:value="t.date" /></n-form-item></n-gi>
|
||||
</n-grid>
|
||||
<n-form-item label="更新原因"><n-input v-model:value="t.reason" /></n-form-item>
|
||||
<n-form-item label="变更列表">
|
||||
<div v-for="(c, ci) in t.changes || []" :key="ci" style="display:flex;gap:8px;margin-bottom:6px;">
|
||||
<n-input v-model:value="c.type" placeholder="类型" style="width:100px" />
|
||||
<n-input v-model:value="c.text" placeholder="变更内容" style="flex:1" />
|
||||
<n-button size="tiny" type="error" quaternary @click="t.changes.splice(ci,1)">删</n-button>
|
||||
</div>
|
||||
<n-button size="tiny" dashed @click="if(!t.changes) t.changes=[]; t.changes.push({type:'',text:''})">添加变更</n-button>
|
||||
</n-form-item>
|
||||
</n-collapse-item>
|
||||
</n-collapse>
|
||||
</n-card>
|
||||
</n-tab-pane>
|
||||
|
||||
<n-tab-pane name="faq" tab="常见问答">
|
||||
@ -155,6 +187,7 @@
|
||||
<n-tab-pane name="basic" tab="基本信息">
|
||||
<n-card>
|
||||
<n-form label-placement="top">
|
||||
<n-form-item label="营地名称"><n-input v-model:value="data.name" /></n-form-item>
|
||||
<n-form-item label="营地定位"><n-input v-model:value="data.positioning" type="textarea" :rows="3" /></n-form-item>
|
||||
<n-grid :cols="4" :x-gap="12">
|
||||
<n-gi><n-form-item label="天数"><n-input-number v-model:value="data.days" :min="1" style="width:100%" /></n-form-item></n-gi>
|
||||
@ -244,6 +277,12 @@
|
||||
<n-input v-model:value="data.serviceConfig[i]" style="flex:1" /><n-button size="tiny" type="error" quaternary @click="data.serviceConfig.splice(i,1)">删</n-button>
|
||||
</div>
|
||||
</n-card>
|
||||
<n-card v-if="data.campEssentials !== undefined" title="营地必备清单" style="margin-top:16px">
|
||||
<template #header-extra><n-button size="small" @click="data.campEssentials.push('')">添加</n-button></template>
|
||||
<div v-for="(_, i) in data.campEssentials" :key="i" style="margin-bottom:8px;display:flex;gap:8px;">
|
||||
<n-input v-model:value="data.campEssentials[i]" style="flex:1" /><n-button size="tiny" type="error" quaternary @click="data.campEssentials.splice(i,1)">删</n-button>
|
||||
</div>
|
||||
</n-card>
|
||||
<n-card v-if="data.coreActivities !== undefined" title="核心体验" style="margin-top:16px">
|
||||
<template #header-extra><n-button size="small" @click="data.coreActivities.push('')">添加</n-button></template>
|
||||
<div v-for="(_, i) in data.coreActivities" :key="i" style="margin-bottom:8px;display:flex;gap:8px;">
|
||||
@ -359,7 +398,7 @@ import { h } from 'vue'
|
||||
import {
|
||||
NButton, NCard, NSpace, NTag, NGrid, NGi, NSpin, NForm, NFormItem,
|
||||
NInput, NInputNumber, NBreadcrumb, NBreadcrumbItem,
|
||||
NTabs, NTabPane, NCollapse, NCollapseItem, NDynamicInput, NEmpty,
|
||||
NTabs, NTabPane, NCollapse, NCollapseItem, NDynamicInput, NEmpty, NSwitch,
|
||||
} from 'naive-ui'
|
||||
import { useMessage } from 'naive-ui'
|
||||
|
||||
@ -462,7 +501,7 @@ function normalizeVersion(v) {
|
||||
return {
|
||||
id: v.id || '', name: v.name || '', tag: v.tag || '',
|
||||
days: v.days || 0, nights: v.nights || 0, line: v.line || '', route: v.route || '',
|
||||
audience: v.audience || '', description: v.description || '',
|
||||
audience: v.audience || '', description: v.description || '', isMainProduct: v.isMainProduct || false,
|
||||
highlights: Array.isArray(v.highlights) ? v.highlights : [],
|
||||
itinerary: normalizeItinerary(v.itinerary || []),
|
||||
pricing: {
|
||||
|
||||
正在加载...
x
在新工单中引用
屏蔽一个用户