董国庆
2026-04-10 c0227fc84a06e0222f4b2425193487d3d89aeaf0
feat(内容中心): 拆分内容中心为乘客端和运维APP独立管理页面

- 新增乘客端内容管理页面,包含乘车指南、协议、常见问题等模块
- 新增运维APP内容管理页面,包含车辆整备、充电、检测、登录协议等模块
- 添加协议列表组件,支持拖拽排序、增删改查功能
- 更新路由配置和国际化文案,支持双端独立访问
1 文件已重命名
2个文件已添加
4个文件已修改
823 ■■■■■ 已修改文件
locales/en_US.json 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
locales/zh_CN.json 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/router/routes.static.ts 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/userContent/contentCenter/components/AgreementForm.vue 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/userContent/contentCenter/components/AgreementListForm.vue 354 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/userContent/contentCenter/opsApp.vue 297 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/userContent/contentCenter/passenger.vue 143 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
locales/en_US.json
@@ -142,7 +142,8 @@
    "whitelist": "Whitelist Management",
    "whitelistLog": "Whitelist Log",
    "contentCenter": "Content Center",
    "contentCenterIndex": "Content Center",
    "passengerContent": "Passenger App Content Management",
    "opsAppContent": "Operations App Content Management",
    "contentCenterActive": "Activity",
    "contentCenterConpon": "Coupon Management",
    "contentCenterConponAdd": "Add Coupon",
locales/zh_CN.json
@@ -289,7 +289,8 @@
    "whitelist": "白名单管理",
    "whitelistLog": "白名单日志",
    "contentCenter": "内容中心",
    "contentCenterIndex": "内容中心",
    "passengerContent": "乘客端内容管理",
    "opsAppContent": "运维APP内容管理",
    "contentCenterActive": "活动",
    "contentCenterConpon": "优惠券管理",
    "contentCenterConponAdd": "添加优惠券",
src/router/routes.static.ts
@@ -350,19 +350,32 @@
    pid: 3,
  },
  {
    name: 'contentCenterIndex',
    path: '/userContent/contentCenter/index',
    title: '内容中心',
    name: 'passengerContent',
    path: '/userContent/contentCenter/passenger',
    title: '乘客端内容管理',
    requiresAuth: true,
    icon: 'icon-park-outline:file-text',
    menuType: 'page',
    componentPath: '/userContent/contentCenter/index.vue',
    componentPath: '/userContent/contentCenter/passenger.vue',
    id: 3021,
    keepAlive: false,
    pid: 302,
    fullScreen: true,
  },
  {
    name: 'opsAppContent',
    path: '/userContent/contentCenter/opsApp',
    title: '运维APP内容管理',
    requiresAuth: true,
    icon: 'icon-park-outline:file-text',
    menuType: 'page',
    componentPath: '/userContent/contentCenter/opsApp.vue',
    id: 3024,
    keepAlive: false,
    pid: 302,
    fullScreen: true,
  },
  {
    name: 'contentCenterActive',
    path: '/userContent/active',
    title: '活动',
src/views/userContent/contentCenter/components/AgreementForm.vue
@@ -78,7 +78,7 @@
<template>
  <div>
    <div class="flex tabs mb-16px">
    <div class="flex tabs mb-16px ">
      <div
        v-for="tab in subTabs"
        :key="tab.key"
src/views/userContent/contentCenter/components/AgreementListForm.vue
New file
@@ -0,0 +1,354 @@
<script setup lang="ts">
import { h, ref } from 'vue'
import { NButton, NCard, NDataTable, NForm, NFormItem, NInput, NModal, NPopconfirm, NSpace, useMessage } from 'naive-ui'
import RichTextEditor from '@/components/custom/Editor/RichTextEditor/index.vue'
const props = defineProps<{
  type: 'passenger' | 'ops'
}>()
const message = useMessage()
const addBtnLabel = props.type === 'passenger' ? '新增协议' : '新增登录协议'
const titleLabel = '协议名称'
const modalTitleAdd = props.type === 'passenger' ? '新增协议' : '新增登录协议'
const modalTitleEdit = props.type === 'passenger' ? '编辑协议' : '编辑登录协议'
interface AgreementItem {
  id: string
  title: string
  content: string
  creator: string
  updateTime: string
}
const tableData = ref<AgreementItem[]>([
  {
    id: '1',
    title: props.type === 'passenger' ? '乘车须知' : '小鹏Robotaxi录音录像及个人信息采集说明',
    content: '',
    creator: '张三',
    updateTime: '2026-04-08 09:14:55',
  },
  {
    id: '2',
    title: props.type === 'passenger' ? '隐私政策' : '权限说明',
    content: '',
    creator: '张三',
    updateTime: '2026-04-08 09:14:58',
  },
])
const columns = [
  {
    title: '排序',
    key: 'sort',
    width: 80,
    render: () => {
      return h('div', { style: 'cursor: move; display: flex; align-items: center;' }, [
        h('img', {
          src: '/src/assets/point/union.png',
          alt: '拖动',
          style: 'width: 15px; height: 15px;',
          onError: (e: any) => {
            e.target.src = 'https://huishou-1323682843.cos.ap-nanjing.myqcloud.com/images/9bed493b-a6aa-4ef6-ac10-7f67f5c6adc5.png'
          }
        })
      ])
    },
  },
  { title: titleLabel, key: 'title' },
  { title: '创建人', key: 'creator' },
  { title: '创建/更新时间', key: 'updateTime' },
  {
    title: '操作',
    key: 'actions',
    width: 120,
    render: (row: AgreementItem) => {
      return h(NSpace, { size: 'small', align: 'center' }, {
        default: () => [
          h(
            NButton,
            { text: true, type: 'primary', onClick: () => handleEdit(row) },
            { default: () => '编辑' },
          ),
          h(
            NPopconfirm,
            { onPositiveClick: () => handleDelete(row) },
            {
              trigger: () => h(NButton, { text: true, type: 'primary' }, { default: () => '删除' }),
              default: () => '确认删除该协议吗?',
            },
          ),
        ],
      })
    },
  },
]
const showModal = ref(false)
const isEdit = ref(false)
const formRef = ref<any>(null)
const formModel = ref({
  id: '',
  title: '',
  content: '',
})
let draggedIndex: number | null = null
function handleDragStart(index: number, e: DragEvent) {
  draggedIndex = index
  if (e.target && (e.target as HTMLElement).classList) {
    (e.target as HTMLElement).classList.add('dragging')
  }
}
function handleDragEnd(e: DragEvent) {
  if (e.target && (e.target as HTMLElement).classList) {
    (e.target as HTMLElement).classList.remove('dragging')
  }
}
function handleDragOver(e: DragEvent) {
  e.preventDefault()
  e.dataTransfer!.dropEffect = 'move'
}
function handleDrop(index: number) {
  if (draggedIndex !== null && draggedIndex !== index) {
    const items = [...tableData.value]
    const draggedItem = items[draggedIndex]
    items.splice(draggedIndex, 1)
    items.splice(index, 0, draggedItem)
    tableData.value = items
  }
  draggedIndex = null
}
const rowProps = (row: AgreementItem, index: number) => {
  return {
    draggable: true,
    onDragstart: (e: DragEvent) => handleDragStart(index, e),
    onDragend: handleDragEnd,
    onDragover: handleDragOver,
    onDrop: () => handleDrop(index),
    class: 'draggable-row'
  }
}
const rules = {
  title: { required: true, message: `请输入${titleLabel}`, trigger: 'blur' },
  content: { required: true, message: '请输入协议内容', trigger: 'blur' },
}
function handleAdd() {
  isEdit.value = false
  formModel.value = {
    id: '',
    title: '',
    content: '',
  }
  showModal.value = true
}
function handleEdit(row: AgreementItem) {
  isEdit.value = true
  formModel.value = {
    id: row.id,
    title: row.title,
    content: row.content,
  }
  showModal.value = true
}
function handleDelete(row: AgreementItem) {
  tableData.value = tableData.value.filter(item => item.id !== row.id)
  message.success('删除成功')
}
function handleCancel() {
  showModal.value = false
}
async function handleSave() {
  try {
    await formRef.value?.validate()
    if (isEdit.value) {
      const index = tableData.value.findIndex(item => item.id === formModel.value.id)
      if (index > -1) {
        tableData.value[index].title = formModel.value.title
        tableData.value[index].content = formModel.value.content
        tableData.value[index].updateTime = new Date().toLocaleString().replace(/\//g, '-')
      }
    }
    else {
      tableData.value.unshift({
        id: String(Date.now()),
        title: formModel.value.title,
        content: formModel.value.content,
        creator: '当前用户',
        updateTime: new Date().toLocaleString().replace(/\//g, '-'),
      })
    }
    message.success('保存成功')
    showModal.value = false
  }
  catch (error) {
    // 验证失败
  }
}
async function validate() {
  return true
}
function getData() {
  return tableData.value
}
function setData(data: any) {
  // 暂时使用 mock 数据,不覆盖
}
defineExpose({
  validate,
  getData,
  setData,
})
</script>
<template>
  <div class="agreement-list-form roboTaxi">
    <div class="mb-16px">
      <n-button type="primary" secondary  @click="handleAdd">
        {{ addBtnLabel }}
      </n-button>
    </div>
    <n-data-table
      :columns="columns"
      :data="tableData"
      :bordered="false"
      :bottom-bordered="true"
      :row-key="row => row.id"
      :row-props="rowProps"
      class="custom-table"
    />
    <n-modal v-model:show="showModal" :mask-closable="false">
      <div class="modal-container">
        <div class="modal-header">
          <div class="modal-title">
            {{ isEdit ? modalTitleEdit : modalTitleAdd }}
          </div>
        </div>
        <div class="modal-body">
          <n-form
            ref="formRef"
            :model="formModel"
            :rules="rules"
            label-placement="left"
            label-width="auto"
            require-mark-placement="left"
          >
            <n-form-item :label="titleLabel" path="title" class="mb-8px">
              <n-input v-model:value="formModel.title" placeholder="请输入" style="width: 400px" />
            </n-form-item>
            <n-form-item label="协议内容" path="content" label-placement="top">
              <div class="rich-text-wrapper">
                <RichTextEditor v-model="formModel.content" />
              </div>
            </n-form-item>
          </n-form>
        </div>
        <div class="modal-footer">
          <n-button style="padding: 8px 24px; border-radius: 4px; font-size: 14px; margin-right: 12px;" @click="handleCancel">
            取消
          </n-button>
          <n-button type="primary" style="padding: 8px 24px; border-radius: 4px; font-size: 14px;" @click="handleSave">
            保存
          </n-button>
        </div>
      </div>
    </n-modal>
  </div>
</template>
<style scoped lang="scss">
.agreement-list-form {
  width: 100%;
  height: auto !important;
}
.rich-text-wrapper {
  width: 100%;
  border: 1px solid #e5e6eb;
  border-radius: 4px;
}
.rich-text-wrapper :deep(.ql-container) {
  min-height: 400px;
  font-size: 14px;
  border: none !important;
}
.rich-text-wrapper :deep(.ql-toolbar) {
  border: none !important;
  border-bottom: 1px solid #e5e6eb !important;
  background-color: #fafafa;
}
.rich-text-wrapper :deep(.ql-editor) {
  min-height: 400px;
}
:deep(.n-data-table-th) {
  background-color: #F7F8FA !important;
  font-weight: 500;
  color: #171F26;
}
:deep(.draggable-row) {
  cursor: move;
}
:deep(.draggable-row.dragging) {
  opacity: 0.5;
  background-color: #f5f5f5;
}
.modal-container {
  width: 1000px;
  background-color: #fff;
  border-radius: 8px;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}
.modal-header {
  padding: 20px 24px;
  border-bottom: 1px solid #f0f0f0;
}
.modal-title {
  font-size: 16px;
  font-weight: 600;
  color: #171F26;
}
.modal-body {
  padding: 24px;
  max-height: calc(100vh - 200px);
  overflow-y: auto;
}
.modal-footer {
  padding: 16px 24px;
  border-top: 1px solid #f0f0f0;
  display: flex;
  justify-content: flex-end;
  background-color: #fff;
}
</style>
src/views/userContent/contentCenter/opsApp.vue
New file
@@ -0,0 +1,297 @@
<script setup lang="ts">
import { nextTick, ref, watch } from 'vue'
import { useMessage } from 'naive-ui'
import {
  getAgreementContent,
  getVehicleDescription,
  saveAgreement,
  saveVehicleDescription,
} from '@/service/api/contentCenter'
import VehiclePrepareForm from './components/VehiclePrepareForm.vue'
import SimpleRichTextForm from './components/SimpleRichTextForm.vue'
import AgreementListForm from './components/AgreementListForm.vue'
const activeTab = ref('prepare')
const message = useMessage()
const loading = ref(false)
const tabs = [
  { key: 'prepare', label: '车辆整备' },
  { key: 'charge', label: '车辆充电' },
  { key: 'inspect', label: '车辆检测' },
  { key: 'insurance', label: '保险说明' },
  { key: 'annual', label: '年检说明' },
  { key: 'maintenance', label: '车辆保养' },
  { key: 'qualification', label: '运营资质' },
  { key: 'opsAppNotice', label: '登录协议' },
]
const contentFormRefs = ref<Record<string, any>>({})
async function handleSave() {
  try {
    const currentForm = contentFormRefs.value[activeTab.value]
    if (currentForm) {
      await currentForm.validate()
      const data = currentForm.getData()
      let saveRes: any = null
      if (activeTab.value === 'prepare') {
        const map: Record<string, number> = {
          tirePressure: 1,
          lighting: 2,
          bodyClean: 3,
          cabinClean: 4,
          lostItems: 5,
        }
        const activeKey = typeof currentForm.getActiveKey === 'function' ? currentForm.getActiveKey() : 'tirePressure'
        const t = map[activeKey] ?? 1
        const content = data[activeKey] || ''
        saveRes = await saveVehicleDescription({ descriptionType: t, descriptionContent: content })
      }
      else if (['charge', 'inspect', 'insurance', 'annual', 'maintenance', 'qualification'].includes(activeTab.value)) {
        const typeMap: Record<string, number> = {
          charge: 6,
          inspect: 7,
          insurance: 8,
          annual: 9,
          maintenance: 10,
          qualification: 11,
        }
        const t = typeMap[activeTab.value]
        saveRes = await saveVehicleDescription({ descriptionType: t, descriptionContent: data.content || '' })
      }
      else if (activeTab.value === 'opsAppNotice') {
        const map: Record<string, number> = {
          operationPrivacyPolicy: 8,
          thirdPartyShareList: 9,
          personalInfoCollectList: 10,
          permissionCall: 11,
        }
        const activeKey = typeof currentForm.getActiveKey === 'function' ? currentForm.getActiveKey() : 'operationPrivacyPolicy'
        const t = map[activeKey] ?? 8
        const content = data[activeKey] || ''
        saveRes = await saveAgreement({ content, contentType: t })
      }
      if (saveRes && saveRes.code === 200) {
        message.success('保存成功')
      }
      else if (saveRes) {
        message.error('保存失败')
      }
    }
  }
  catch {
    // message.error('验证失败')
  }
}
function handleCancel() {
}
async function loadContent(tabKey: string) {
  try {
    loading.value = true
    await nextTick()
    let currentForm = contentFormRefs.value[tabKey]
    if (!currentForm || typeof currentForm.setData !== 'function') {
      await nextTick()
      currentForm = contentFormRefs.value[tabKey]
    }
    if (!currentForm || typeof currentForm.setData !== 'function') {
      loading.value = false
      return
    }
    if (tabKey === 'prepare') {
      const types = [1, 2, 3, 4, 5]
      const res = await Promise.all(types.map(t => getVehicleDescription(t)))
      if (res.every(r => r && r.code === 200)) {
        const data: Record<string, string> = {
          tirePressure: res[0].data?.descriptionContent || '',
          lighting: res[1].data?.descriptionContent || '',
          bodyClean: res[2].data?.descriptionContent || '',
          cabinClean: res[3].data?.descriptionContent || '',
          lostItems: res[4].data?.descriptionContent || '',
        }
        currentForm.setData(data)
      }
    }
    else if (['charge', 'inspect', 'insurance', 'annual', 'maintenance', 'qualification'].includes(tabKey)) {
      const typeMap: Record<string, number> = {
        charge: 6,
        inspect: 7,
        insurance: 8,
        annual: 9,
        maintenance: 10,
        qualification: 11,
      }
      const t = typeMap[tabKey]
      const res = await getVehicleDescription(t)
      if (res && res.code === 200) {
        currentForm.setData({ content: res.data?.descriptionContent || '' })
      }
    }
    else if (tabKey === 'opsAppNotice') {
      const types = [8, 9, 10, 11]
      const res = await Promise.all(types.map(t => getAgreementContent(t)))
      if (res.every(r => r && r.code === 200)) {
        const data = {
          operationPrivacyPolicy: res[0].data?.content || '',
          thirdPartyShareList: res[1].data?.content || '',
          personalInfoCollectList: res[2].data?.content || '',
          permissionCall: res[3].data?.content || '',
        }
        currentForm.setData(data)
      }
    }
  }
  finally {
    loading.value = false
  }
}
watch(activeTab, (v) => {
  loadContent(v)
}, { immediate: true })
</script>
<template>
  <div class="h-full flex flex-col justify-between ">
    <div class="p-12px content-page">
      <n-card>
        <div>
          <div class="head">
            运维APP内容管理
          </div>
          <n-tabs
            v-model:value="activeTab"
            type="line"
            size="large"
            :theme-overrides="{
              tabTextColor: '#666666',
              tabTextColorActive: '#0068FF',
              tabBorderColorActive: '#0068FF',
              tabBorderRadius: '4px',
              tabPaddingHorizontal: '16px',
              tabPaddingVertical: '8px',
            }"
            class="tab-container"
          >
            <n-tab-pane v-for="tab in tabs" :key="tab.key" :name="tab.key" :tab="tab.label">
              <div style="height: calc(100vh - 304px); overflow-y: auto; padding-bottom: 40px;">
                <VehiclePrepareForm
                  v-if="tab.key === 'prepare'"
                  :ref="(el) => { if (el) contentFormRefs[tab.key] = el }"
                />
                <SimpleRichTextForm
                  v-else-if="['charge', 'inspect', 'insurance', 'annual', 'maintenance', 'qualification'].includes(tab.key)"
                  :ref="(el) => { if (el) contentFormRefs[tab.key] = el }"
                />
                <AgreementListForm
                  v-else-if="tab.key === 'opsAppNotice'"
                  type="ops"
                  :ref="(el) => { if (el) contentFormRefs[tab.key] = el }"
                />
              </div>
            </n-tab-pane>
          </n-tabs>
        </div>
      </n-card>
    </div>
    <div v-permission="30211" class="footer flex justify-end " style="background-color: white; padding: 16px 24px; border-radius: 8px; box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.05);">
      <n-button style="padding: 8px 24px; border-radius: 4px; font-size: 14px;margin-right: 12px;" @click="handleCancel">
        取消
      </n-button>
      <n-button type="primary" style="padding: 8px 24px; border-radius: 4px; font-size: 14px;" @click="handleSave">
        保存
      </n-button>
    </div>
  </div>
</template>
<style scoped lang="scss">
.content-page{
  height: calc(100vh - 302px);
}
.tab-container {
  // border-bottom: 1px solid #e8e8e8;
}
:deep(.n-tabs-wrapper){
  padding: 0 10px;
}
:deep(.n-tabs-nav--line .n-tabs-tab) {
  font-size: 14px;
  font-weight: 500;
  transition: all 0.3s ease;
}
:deep(.n-tabs-nav--line .n-tabs-tab:hover) {
  color: #0068FF;
}
:deep(.n-tabs-nav--line .n-tabs-tab--active) {
  font-weight: 600;
}
:deep(.n-tabs-nav--line .n-tabs-tab--active::after) {
  height: 2px;
  background-color: #0068FF;
  border-radius: 2px;
}
/* 修复富文本编辑器超链接弹窗被遮挡的问题 */
.ql-tooltip {
  z-index: 2000 !important;
  left:-10px !important;
}
/* 允许内容溢出,防止 tooltip 被裁剪 */
.n-tabs .n-tab-pane,
.n-card > .n-card__content {
  overflow: visible !important;
}
.n-tabs-tab__label{
  font-size: 14px;
}
.head {
  font-family: PingFang SC;
  font-size: 16px;
  font-weight: 600;
  line-height: 24px;
  letter-spacing: normal;
  color: #171F26;
  position: relative;
  text-indent: 11px;
  margin-bottom: 16px;
  &::before {
    content: '';
    position: absolute;
    top: 50%;
    left: 0;
    transform: translateY(-50%);
    width: 3px;
    height: 14px;
    z-index: 0;
    border-radius: 3px;
    background: #0068FF;
  }
}
.footer {
  position: absolute;
  bottom: 0;
  left: 0;
  width: 100%;
  height: 64px;
  background-color: #fff;
  box-shadow: 0px 0px 5px 0px rgba(23, 31, 38, 0.05);
  padding: 16px 24px;
}
</style>
src/views/userContent/contentCenter/passenger.vue
File was renamed from src/views/userContent/contentCenter/index.vue
@@ -5,16 +5,12 @@
  getAgreementContent,
  getFaqList,
  getGuideList,
  getVehicleDescription,
  saveAgreement,
  saveFaq,
  saveGuide,
  saveVehicleDescription,
} from '@/service/api/contentCenter'
import VehiclePrepareForm from './components/VehiclePrepareForm.vue'
import SimpleRichTextForm from './components/SimpleRichTextForm.vue'
import AgreementForm from './components/AgreementForm.vue'
import OperationAppAgreementForm from './components/OperationAppAgreementForm.vue'
import AgreementListForm from './components/AgreementListForm.vue'
import FaqForm from './components/FaqForm.vue'
import ContentForm from './components/ContentForm.vue'
@@ -24,16 +20,10 @@
const tabs = [
  { key: 'guide', label: '乘车指南' },
  { key: 'prepare', label: '车辆整备' },
  { key: 'charge', label: '车辆充电' },
  { key: 'inspect', label: '车辆检测' },
  { key: 'insurance', label: '保险说明' },
  { key: 'annual', label: '年检说明' },
  { key: 'maintenance', label: '车辆保养' },
  { key: 'qualification', label: '运营资质' },
  { key: 'notice', label: '协议' },
  { key: 'opsAppNotice', label: '运维APP' },
  { key: 'faq', label: '常见问题' },
  { key: 'prepaymentNotice', label: '预付款协议' },
  { key: 'paymentNotice', label: '付款协议' },
]
const contentFormRefs = ref<Record<string, any>>({})
@@ -45,32 +35,8 @@
      await currentForm.validate()
      const data = currentForm.getData()
      let saveRes: any = null
      if (activeTab.value === 'prepare') {
        const map: Record<string, number> = {
          tirePressure: 1,
          lighting: 2,
          bodyClean: 3,
          cabinClean: 4,
          lostItems: 5,
        }
        const activeKey = typeof currentForm.getActiveKey === 'function' ? currentForm.getActiveKey() : 'tirePressure'
        const t = map[activeKey] ?? 1
        const content = data[activeKey] || ''
        saveRes = await saveVehicleDescription({ descriptionType: t, descriptionContent: content })
      }
      else if (['charge', 'inspect', 'insurance', 'annual', 'maintenance', 'qualification'].includes(activeTab.value)) {
        const typeMap: Record<string, number> = {
          charge: 6,
          inspect: 7,
          insurance: 8,
          annual: 9,
          maintenance: 10,
          qualification: 11,
        }
        const t = typeMap[activeTab.value]
        saveRes = await saveVehicleDescription({ descriptionType: t, descriptionContent: data.content || '' })
      }
      else if (activeTab.value === 'notice') {
      if (activeTab.value === 'notice') {
        const map: Record<string, number> = {
          privacyPolicy: 1,
          personalInfoList: 2,
@@ -84,17 +50,14 @@
        const content = data[activeKey] || ''
        saveRes = await saveAgreement({ content, contentType: t })
      }
      else if (activeTab.value === 'opsAppNotice') {
        const map: Record<string, number> = {
          operationPrivacyPolicy: 8,
          thirdPartyShareList: 9,
          personalInfoCollectList: 10,
          permissionCall: 11,
      else if (['prepaymentNotice', 'paymentNotice'].includes(activeTab.value)) {
        // TODO: 确认预付款协议和付款协议的 contentType,这里暂定为 12 和 13
        const typeMap: Record<string, number> = {
          prepaymentNotice: 12,
          paymentNotice: 13,
        }
        const activeKey = typeof currentForm.getActiveKey === 'function' ? currentForm.getActiveKey() : 'operationPrivacyPolicy'
        const t = map[activeKey] ?? 8
        const content = data[activeKey] || ''
        saveRes = await saveAgreement({ content, contentType: t })
        const t = typeMap[activeTab.value]
        saveRes = await saveAgreement({ content: data.content || '', contentType: t })
      }
      else if (activeTab.value === 'guide') {
        if (data.contentType === 'steps') {
@@ -118,7 +81,6 @@
              guideStep: idx + 1,
              guideType: 1,
            }
            // if (s.recordId) obj.id = s.recordId
            return obj
          })
          saveRes = await saveGuide({ guideList })
@@ -143,7 +105,6 @@
            guideStep: 1,
            guideType: 2,
          }
          // if (data.recordId) obj.id = data.recordId
          const guideList = [obj]
          saveRes = await saveGuide({ guideList })
        }
@@ -162,6 +123,7 @@
          .filter((it: any) => it.questions || it.answer)
        saveRes = await saveFaq({ frequentlyAskedQuestionsList })
      }
      if (saveRes && saveRes.code === 200) {
        message.success('保存成功')
      }
@@ -192,36 +154,8 @@
      loading.value = false
      return
    }
    if (tabKey === 'prepare') {
      const types = [1, 2, 3, 4, 5]
      const res = await Promise.all(types.map(t => getVehicleDescription(t)))
      if (res.every(r => r && r.code === 200)) {
        const data: Record<string, string> = {
          tirePressure: res[0].data?.descriptionContent || '',
          lighting: res[1].data?.descriptionContent || '',
          bodyClean: res[2].data?.descriptionContent || '',
          cabinClean: res[3].data?.descriptionContent || '',
          lostItems: res[4].data?.descriptionContent || '',
        }
        currentForm.setData(data)
      }
    }
    else if (['charge', 'inspect', 'insurance', 'annual', 'maintenance', 'qualification'].includes(tabKey)) {
      const typeMap: Record<string, number> = {
        charge: 6,
        inspect: 7,
        insurance: 8,
        annual: 9,
        maintenance: 10,
        qualification: 11,
      }
      const t = typeMap[tabKey]
      const res = await getVehicleDescription(t)
      if (res && res.code === 200) {
        currentForm.setData({ content: res.data?.descriptionContent || '' })
      }
    }
    else if (tabKey === 'notice') {
    if (tabKey === 'notice') {
      const types = [1, 2, 3, 4, 5, 7]
      const res = await Promise.all(types.map(t => getAgreementContent(t)))
      if (res.every(r => r && r.code === 200)) {
@@ -236,17 +170,16 @@
        currentForm.setData(data)
      }
    }
    else if (tabKey === 'opsAppNotice') {
      const types = [8, 9, 10, 11]
      const res = await Promise.all(types.map(t => getAgreementContent(t)))
      if (res.every(r => r && r.code === 200)) {
        const data = {
          operationPrivacyPolicy: res[0].data?.content || '',
          thirdPartyShareList: res[1].data?.content || '',
          personalInfoCollectList: res[2].data?.content || '',
          permissionCall: res[3].data?.content || '',
        }
        currentForm.setData(data)
    else if (['prepaymentNotice', 'paymentNotice'].includes(tabKey)) {
      // TODO: 确认预付款协议和付款协议的 contentType,这里暂定为 12 和 13
      const typeMap: Record<string, number> = {
        prepaymentNotice: 12,
        paymentNotice: 13,
      }
      const t = typeMap[tabKey]
      const res = await getAgreementContent(t)
      if (res && res.code === 200) {
        currentForm.setData({ content: res.data?.content || '' })
      }
    }
    else if (tabKey === 'faq') {
@@ -271,7 +204,6 @@
          }
        }
        catch {
          // 兼容老数据:直接就是一个 URL 字符串
          const url = String(img)
          const name = url.split('/').pop() || '图片.jpg'
          return [{ id: '1', name, url }]
@@ -327,12 +259,12 @@
</script>
<template>
  <div class="h-full flex flex-col justify-between ">
  <div class="h-full flex flex-col justify-between">
    <div class="p-12px content-page">
      <n-card>
        <div>
          <div class="head">
            内容中心
            乘客端内容管理
          </div>
          <n-tabs
            v-model:value="activeTab"
@@ -350,26 +282,19 @@
          >
            <n-tab-pane v-for="tab in tabs" :key="tab.key" :name="tab.key" :tab="tab.label">
              <div style="height: calc(100vh - 304px); overflow-y: auto; padding-bottom: 40px;">
                <VehiclePrepareForm
                  v-if="tab.key === 'prepare'"
                  :ref="(el) => { if (el) contentFormRefs[tab.key] = el }"
                />
                <SimpleRichTextForm
                  v-else-if="tab.key === 'charge' || tab.key === 'inspect' || tab.key === 'insurance' || tab.key === 'annual' || tab.key === 'maintenance' || tab.key === 'qualification'"
                  :ref="(el) => { if (el) contentFormRefs[tab.key] = el }"
                />
                <AgreementForm
                  v-else-if="tab.key === 'notice'"
                  :ref="(el) => { if (el) contentFormRefs[tab.key] = el }"
                />
                <OperationAppAgreementForm
                  v-else-if="tab.key === 'opsAppNotice'"
                <AgreementListForm
                  v-if="tab.key === 'notice'"
                  type="passenger"
                  :ref="(el) => { if (el) contentFormRefs[tab.key] = el }"
                />
                <FaqForm
                  v-else-if="tab.key === 'faq'"
                  :ref="(el) => { if (el) contentFormRefs[tab.key] = el }"
                />
                <SimpleRichTextForm
                  v-else-if="['prepaymentNotice', 'paymentNotice'].includes(tab.key)"
                  :ref="(el) => { if (el) contentFormRefs[tab.key] = el }"
                />
                <ContentForm
                  v-else
                  :ref="(el) => { if (el) contentFormRefs[tab.key] = el }"