zjk
2026-04-27 c5d58d390857cbb1385fc3326fb35da3ae28b25b
Merge branch 'main' into deploy-1.0
1个文件已添加
10个文件已修改
12023 ■■■■■ 已修改文件
package-lock.json 11415 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
package.json 1 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/custom/Editor/AiRichTextEditor/index.vue 381 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/assetManagement/vehicleManagement/operatingVehicle/components/PreparationDetailModal.vue 31 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/businessOperations/geofenceManagement/detail.vue 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/transaction/pricingManagement/rules.vue 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/userContent/active/coupon/add.vue 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/userContent/contentCenter/components/AgreementForm.vue 48 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/userContent/contentCenter/components/OperationAppAgreementForm.vue 36 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/userContent/contentCenter/components/SimpleRichTextForm.vue 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/userContent/contentCenter/components/VehiclePrepareForm.vue 42 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
package-lock.json
Diff too large
package.json
@@ -18,6 +18,7 @@
  },
  "dependencies": {
    "@vueuse/core": "^13.6.0",
    "aieditor": "^1.4.2",
    "alova": "^3.3.4",
    "colord": "^2.9.3",
    "crypto-js": "^4.2.0",
src/components/custom/Editor/AiRichTextEditor/index.vue
New file
@@ -0,0 +1,381 @@
<script setup lang="ts">
import type { AiEditor as AiEditorInstance, AiEditorOptions, Uploader } from 'aieditor'
import { nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue'
import { AiEditor } from 'aieditor'
import 'aieditor/dist/style.css'
import { uploadSingleFile } from '@/utils/upload'
defineOptions({
  name: 'AiRichTextEditor',
})
interface Props {
  disabled?: boolean
  readOnly?: boolean
  value?: string
  placeholder?: string
  height?: string
  toolbarKeys?: AiEditorOptions['toolbarKeys']
}
const props = withDefaults(defineProps<Props>(), {
  disabled: false,
  readOnly: false,
  value: '',
  placeholder: '请输入内容...',
  height: '360px',
  toolbarKeys: () => [
    'undo',
    'redo',
    'divider',
    'heading',
    'font-family',
    'font-size',
    'divider',
    'bold',
    'italic',
    'underline',
    'strike',
    'link',
    'divider',
    'align',
    'bullet-list',
    'ordered-list',
    'divider',
    'image',
    'video',
    'attachment',
    'quote',
    'table',
    'fullscreen',
  ],
})
const emit = defineEmits<{
  change: [value: string]
  input: [value: string]
  focus: []
  blur: []
  ready: [editor: AiEditorInstance]
}>()
const model = defineModel<string>()
const editorRef = ref<HTMLElement | null>(null)
const editor = ref<AiEditorInstance | null>(null)
const lastHtml = ref('')
const isUnmounted = ref(false)
const isApplyingExternalContent = ref(false)
let applyContentVersion = 0
const commonUploader: Uploader = async (file) => {
  const res = await uploadSingleFile(file)
  return {
    errorCode: 0,
    data: {
      src: res.url,
      href: res.url,
      fileName: res.name || file.name,
      alt: res.name || file.name,
      poster: '',
    },
  }
}
function createUploadConfig() {
  return {
    uploadUrl: '/api/common/upload',
    uploadHeaders: {},
    uploadFormName: 'file',
    uploader: commonUploader,
  }
}
function appendStyle(originStyle: string | null, styleText: string) {
  const style = (originStyle || '').trim()
  if (!style)
    return styleText
  return style.endsWith(';') ? `${style}${styleText}` : `${style};${styleText}`
}
function removeStyleProperties(originStyle: string | null, properties: string[]) {
  let style = (originStyle || '').trim()
  for (const property of properties) {
    const reg = new RegExp(`${property}\\s*:\\s*[^;]+;?`, 'gi')
    style = style.replace(reg, '')
  }
  return style.replace(/\s{2,}/g, ' ').trim()
}
function formatTableForMiniProgram(html: string) {
  if (!html || typeof document === 'undefined')
    return html || ''
  const container = document.createElement('div')
  container.innerHTML = html
  container.querySelectorAll('table').forEach((table) => {
    table.removeAttribute('width')
    table.setAttribute('border', '1')
    table.setAttribute('cellspacing', '0')
    table.setAttribute('cellpadding', '0')
    table.setAttribute(
      'style',
      appendStyle(
        removeStyleProperties(table.getAttribute('style'), ['width', 'min-width', 'max-width']),
        'width:100%;max-width:100%;border-collapse:collapse;table-layout:fixed;box-sizing:border-box;',
      ),
    )
    table.querySelectorAll('colgroup, col').forEach((col) => {
      col.removeAttribute('width')
      const style = removeStyleProperties(col.getAttribute('style'), ['width', 'min-width', 'max-width'])
      if (style)
        col.setAttribute('style', style)
      else
        col.removeAttribute('style')
    })
    table.querySelectorAll('th, td').forEach((cell) => {
      cell.removeAttribute('width')
      cell.setAttribute(
        'style',
        appendStyle(
          removeStyleProperties(cell.getAttribute('style'), ['width', 'min-width', 'max-width']),
          'width:auto;max-width:100%;border:1px solid #d9d9d9;padding:8px;vertical-align:top;word-break:break-all;overflow-wrap:anywhere;box-sizing:border-box;',
        ),
      )
    })
    table.querySelectorAll('th').forEach((headerCell) => {
      headerCell.setAttribute(
        'style',
        appendStyle(
          headerCell.getAttribute('style'),
          'font-weight:600;background:#f5f5f5;',
        ),
      )
    })
  })
  return container.innerHTML
}
function syncModelValue(html: string) {
  const formattedHtml = formatTableForMiniProgram(html)
  if (isApplyingExternalContent.value) {
    lastHtml.value = formattedHtml
    return
  }
  if (formattedHtml === lastHtml.value)
    return
  lastHtml.value = formattedHtml
  if (model.value !== undefined)
    model.value = formattedHtml
  emit('input', formattedHtml)
  emit('change', formattedHtml)
}
function destroyEditor() {
  editor.value?.destroy()
  editor.value = null
  if (editorRef.value)
    editorRef.value.innerHTML = ''
}
function getIncomingContent() {
  return model.value ?? props.value ?? ''
}
function initEditor(content = getIncomingContent()) {
  if (!editorRef.value || editor.value || isUnmounted.value)
    return
  const initialContent = formatTableForMiniProgram(content || '')
  const instance = new AiEditor({
    element: editorRef.value,
    placeholder: props.placeholder,
    content: initialContent,
    editable: !(props.disabled || props.readOnly),
    toolbarKeys: props.toolbarKeys,
    toolbarExcludeKeys: ['ai'],
    ai: false,
    draggable: false,
    image: createUploadConfig(),
    video: createUploadConfig(),
    attachment: createUploadConfig(),
    onChange: (currentEditor) => {
      syncModelValue(currentEditor.getHtml())
    },
    onFocus: () => {
      emit('focus')
    },
    onBlur: () => {
      emit('blur')
    },
    onCreated: (currentEditor) => {
      emit('ready', currentEditor)
    },
  })
  editor.value = instance
  lastHtml.value = initialContent
}
async function rebuildEditor(content: string, focus = false) {
  if (isUnmounted.value)
    return
  const formattedContent = formatTableForMiniProgram(content || '')
  if (formattedContent === lastHtml.value && editor.value) {
    lastHtml.value = formattedContent
    return
  }
  const version = ++applyContentVersion
  isApplyingExternalContent.value = true
  destroyEditor()
  await nextTick()
  if (isUnmounted.value || version !== applyContentVersion)
    return
  initEditor(formattedContent)
  if (focus)
    editor.value?.focus()
  requestAnimationFrame(() => {
    if (version === applyContentVersion)
      isApplyingExternalContent.value = false
  })
}
async function setContent(content: string, focus = false) {
  await rebuildEditor(content, focus)
}
function clear() {
  destroyEditor()
  if (!isUnmounted.value && editorRef.value)
    initEditor('')
  lastHtml.value = ''
  syncModelValue('')
}
function focus() {
  editor.value?.focus()
}
function blur() {
  editor.value?.blur()
}
function getContent() {
  return editor.value?.getHtml() || ''
}
function getEditor() {
  return editor.value
}
watch(
  () => model.value,
  (value) => {
    if (value === undefined)
      return
    const html = value || ''
    if (html === lastHtml.value)
      return
    void rebuildEditor(html)
  },
  { flush: 'post' },
)
watch(
  () => props.value,
  (value) => {
    if (model.value !== undefined)
      return
    const html = value || ''
    if (html === lastHtml.value)
      return
    void rebuildEditor(html)
  },
  { flush: 'post' },
)
watch(
  () => [props.disabled, props.readOnly],
  (value) => {
    const [disabled, readOnly] = value
    editor.value?.setEditable(!(disabled || readOnly))
  },
)
watch(
  () => props.placeholder,
  () => {
    const html = getIncomingContent() || lastHtml.value
    void rebuildEditor(html)
  },
)
onMounted(() => {
  isUnmounted.value = false
  void nextTick(() => {
    initEditor()
  })
})
onBeforeUnmount(() => {
  isUnmounted.value = true
  destroyEditor()
})
defineExpose({
  clear,
  blur,
  focus,
  setContent,
  getContent,
  getEditor,
})
</script>
<template>
  <div class="ai-editor-container">
    <div
      ref="editorRef"
      class="ai-editor-host"
      :style="{ minHeight: height }"
    />
  </div>
</template>
<style scoped>
.ai-editor-container {
  width: 100%;
  border-radius: 4px;
  background: #fff;
}
.ai-editor-host {
  width: 100%;
}
:deep(aie-footer) {
  display: none !important;
}
:deep(.aie-container) {
  border-radius: 4px;
}
:deep(.aie-main) {
  min-height: v-bind(height);
}
</style>
src/views/assetManagement/vehicleManagement/operatingVehicle/components/PreparationDetailModal.vue
@@ -78,7 +78,7 @@
          <div class="value">{{ detailData.operator || "-" }}</div>
        </div>
        <div style="flex: 1">
          <div class="label">送修时间</div>
          <div class="label">整备时间</div>
          <div class="value">
            {{
              detailData.operationTime
@@ -127,7 +127,7 @@
              </div>
            </div>
          </div>
          <div style="flex: 1">
          <!-- <div style="flex: 1">
            <div class="label">雾灯情况</div>
            <div
              class="flex gap-16px"
@@ -148,9 +148,32 @@
                />
              </div>
            </div>
          </div> -->
          <div style="flex: 1">
            <div class="label">转向灯情况</div>
            <div
              class="flex gap-16px"
              v-if="
                detailData.turnSignalImages &&
                detailData.turnSignalImages.length
              "
            >
              <div
                class="pic"
                v-for="(img, idx) in detailData.turnSignalImages"
                :key="idx"
              >
                <NImage
                  :src="img"
                  width="56"
                  height="56"
                  fallback-src="https://huishou-1323682843.cos.ap-nanjing.myqcloud.com/images/9bed493b-a6aa-4ef6-ac10-7f67f5c6adc5.png"
                />
              </div>
            </div>
          </div>
        </div>
        <div class="flex mt-12px">
        <!-- <div class="flex mt-12px">
          <div style="flex: 1">
            <div class="label">转向灯情况</div>
            <div
@@ -197,7 +220,7 @@
              </div>
            </div>
          </div>
        </div>
        </div> -->
        <div class="flex mt-12px">
          <div style="flex: 1">
src/views/businessOperations/geofenceManagement/detail.vue
@@ -55,6 +55,7 @@
const isDrawing = ref(false) // 是否正在绘制
const isEditing = ref(false) // 是否正在编辑
const isDisallowedAreaDrawing = ref(false) // 是否正在绘制禁行区围栏
const fenceEditorClosed = ref(false) // 围栏编辑器是否已关闭
// ==================== 电子围栏相关 ====================
// 电子围栏详情
@@ -230,8 +231,10 @@
  // 添加地图点击事件监听器,用于处理点击运营区域时的编辑状态切换
  map.on('click', (e: any) => {
    // layerOptions.isActive = false
    if (isDrawing.value)
    if (isDrawing.value) {
      stopPoint()
      return
    }
    // 检查是否点击在运营区域(主围栏)内或者点击在禁行区内
    const point = [e.lnglat.lng, e.lnglat.lat]
    const isPointInFence = fence && (!isDrawing.value || modalType.value === 'detail') && isPointInPolygon(point, fence.getPath())
@@ -359,6 +362,19 @@
  fence.setPath(path)
}
function stopPoint() {
  if (!isDisallowedAreaDrawing.value)
    return
  const fence = mouseTool?.polygonTemp?.obj
  const path = fence?.getPath() || []
  if (path.length >= MAX_VERTICES) {
    message.error(`禁行区顶点数量不能超过${MAX_VERTICES}个`)
    path.splice(path.length - 2, 2)
    path.push(path[path.length - 1])
    fence.setPath(path)
  }
}
function cancelDrawing() {
  const fence = mouseTool?.polygonTemp?.obj
  const path = fence.getPath()
@@ -436,6 +452,7 @@
    return
  }
  isDrawing.value = true
  isDisallowedAreaDrawing.value = true
  mouseTool = new AMap.MouseTool(map)
  // 绘制多边形
  mouseTool.polygon({
@@ -453,6 +470,7 @@
    mouseTool?.close()
    mouseTool = null
    isDrawing.value = false
    isDisallowedAreaDrawing.value = false
    // 获取围栏坐标
    const path = e.obj.getPath().map((p: any) => [p.lng, p.lat])
    if (path.length < 3) {
@@ -1695,7 +1713,7 @@
  fetchElectronicFenceAreaList().then((res) => {
    if (res.code === 200) {
      if (modalType.value !== 'add') {
        fenceAreaList.value = res.data.filter((item: any) => item.electronicId !== Number(routeId.value))
        fenceAreaList.value = res.data.filter((item: any) => item.electronicId !== routeId.value && item.electronicId !== Number(routeId.value))
      }
      else {
        fenceAreaList.value = res.data
@@ -2153,7 +2171,7 @@
      </div>
    </template>
    <div v-if="isActiceDisallowedArea && isDrawing" class="tips">
      请在地图上【蓝色运营区域】绘制多边形区域,注意禁行区顶点数量不能超过15个,双击闭合
      请在地图上【蓝色运营区域】绘制多边形区域,双击闭合
    </div>
    <!-- 抽屉 -->
    <div
src/views/transaction/pricingManagement/rules.vue
@@ -41,10 +41,10 @@
<template>
  <div class="w-full h-full relative content">
    <div class="p-12px h-full">
      <div class="box py-16px! title mb-12px h-full">
      <div class="box py-16px! mb-12px h-full">
        <div class="head">计费规则说明</div>
        <div class="h-400px">
          <RichTextEditor v-model="text" :disabled="false" />
        <div class="rich-text-wrapper">
          <AiRichTextEditor v-model="text" height="300px" />
        </div>
      </div>
    </div>
@@ -80,12 +80,6 @@
  width: 100%;
  box-sizing: border-box;
  --n-label-text-color: rgba(23, 31, 38, 0.8);
}
.title {
  font-size: 20px;
  color: #171F26;
  font-weight: 600;
}
.head {
@@ -125,4 +119,12 @@
  padding: 16px 24px;
}
</style>
.rich-text-wrapper :deep(.aie-container) {
  min-height: 300px;
  font-size: 14px;
}
.rich-text-wrapper :deep(.aie-main) {
  min-height: 300px;
}
</style>
src/views/userContent/active/coupon/add.vue
@@ -3,7 +3,7 @@
import { useRoute, useRouter } from 'vue-router'
import type { FormInst, FormRules } from 'naive-ui'
import { useDialog, useMessage } from 'naive-ui'
import RichTextEditor from '@/components/custom/Editor/RichTextEditor/index.vue'
import AiRichTextEditor from '@/components/custom/Editor/AiRichTextEditor/index.vue'
import { addCoupon, fetchCouponDetail } from '@/service/api/coupon'
import { useTabStore } from '@/store'
@@ -589,9 +589,9 @@
            <div class="form-row full-width">
              <n-form-item label="优惠券说明" path="description" :required="!isReadonly" label-placement="top">
                <div class="w-full">
                <div class="w-full rich-text-wrapper">
                  <template v-if="!isReadonly">
                    <RichTextEditor v-model="formModel.description" style="height: 300px" />
                    <AiRichTextEditor v-model="formModel.description" height="300px" />
                  </template>
                  <div v-else class="rich-text-content" v-html="formModel.description" />
                </div>
@@ -771,4 +771,13 @@
.rich-text-content :deep(p:last-child) {
  margin-bottom: 0;
}
.rich-text-wrapper :deep(.aie-container) {
  min-height: 300px;
  font-size: 14px;
}
.rich-text-wrapper :deep(.aie-main) {
  min-height: 300px;
}
</style>
src/views/userContent/contentCenter/components/AgreementForm.vue
@@ -1,7 +1,7 @@
<script setup lang="ts">
import { computed, ref } from 'vue'
import type { FormInst, FormRules } from 'naive-ui'
import RichTextEditor from '@/components/custom/Editor/RichTextEditor/index.vue'
import AiRichTextEditor from '@/components/custom/Editor/AiRichTextEditor/index.vue'
interface FormModel {
  privacyPolicy: string
@@ -51,6 +51,14 @@
  return r
})
const currentContent = computed({
  get: () => formModel.value[activeSubTab.value as keyof FormModel],
  set: (value: string) => {
    const key = activeSubTab.value as keyof FormModel
    formModel.value[key] = value
  },
})
async function validate() {
  return await formRef.value?.validate()
}
@@ -98,39 +106,9 @@
      label-width="80"
      require-mark-placement="left"
    >
      <n-form-item v-show="activeSubTab === 'privacyPolicy'" label="" path="privacyPolicy" required>
      <n-form-item label="" :path="activeSubTab" required>
        <div class="rich-text-wrapper">
          <RichTextEditor v-model="formModel.privacyPolicy" />
        </div>
      </n-form-item>
      <n-form-item v-show="activeSubTab === 'personalInfoList'" label="" path="personalInfoList" required>
        <div class="rich-text-wrapper">
          <RichTextEditor v-model="formModel.personalInfoList" />
        </div>
      </n-form-item>
      <n-form-item v-show="activeSubTab === 'sensitiveInfoDesc'" label="" path="sensitiveInfoDesc" required>
        <div class="rich-text-wrapper">
          <RichTextEditor v-model="formModel.sensitiveInfoDesc" />
        </div>
      </n-form-item>
      <n-form-item v-show="activeSubTab === 'thirdPartyInfoDesc'" label="" path="thirdPartyInfoDesc" required>
        <div class="rich-text-wrapper">
          <RichTextEditor v-model="formModel.thirdPartyInfoDesc" />
        </div>
      </n-form-item>
      <n-form-item v-show="activeSubTab === 'rideNotice'" label="" path="rideNotice" required>
        <div class="rich-text-wrapper">
          <RichTextEditor v-model="formModel.rideNotice" />
        </div>
      </n-form-item>
      <n-form-item v-show="activeSubTab === 'permissionNotice'" label="" path="permissionNotice" required>
        <div class="rich-text-wrapper">
          <RichTextEditor v-model="formModel.permissionNotice" />
          <AiRichTextEditor v-model="currentContent" height="300px" />
        </div>
      </n-form-item>
    </n-form>
@@ -142,12 +120,12 @@
  width: 100%;
}
.rich-text-wrapper :deep(.ql-container) {
.rich-text-wrapper :deep(.aie-container) {
  min-height: 300px;
  font-size: 14px;
}
.rich-text-wrapper :deep(.ql-editor) {
.rich-text-wrapper :deep(.aie-main) {
  min-height: 300px;
}
.tabs {
src/views/userContent/contentCenter/components/OperationAppAgreementForm.vue
@@ -1,7 +1,7 @@
<script setup lang="ts">
import { computed, ref } from 'vue'
import type { FormInst, FormRules } from 'naive-ui'
import RichTextEditor from '@/components/custom/Editor/RichTextEditor/index.vue'
import AiRichTextEditor from '@/components/custom/Editor/AiRichTextEditor/index.vue'
interface FormModel {
  operationPrivacyPolicy: string
@@ -41,6 +41,14 @@
    r[key] = [{ required: true, message: map[key], trigger: 'blur' }]
  }
  return r
})
const currentContent = computed({
  get: () => formModel.value[activeSubTab.value as keyof FormModel],
  set: (value: string) => {
    const key = activeSubTab.value as keyof FormModel
    formModel.value[key] = value
  },
})
async function validate() {
@@ -86,27 +94,9 @@
      label-width="80"
      require-mark-placement="left"
    >
      <n-form-item v-show="activeSubTab === 'operationPrivacyPolicy'" label="" path="operationPrivacyPolicy" required>
      <n-form-item label="" :path="activeSubTab" required>
        <div class="rich-text-wrapper">
          <RichTextEditor v-model="formModel.operationPrivacyPolicy" />
        </div>
      </n-form-item>
      <n-form-item v-show="activeSubTab === 'thirdPartyShareList'" label="" path="thirdPartyShareList" required>
        <div class="rich-text-wrapper">
          <RichTextEditor v-model="formModel.thirdPartyShareList" />
        </div>
      </n-form-item>
      <n-form-item v-show="activeSubTab === 'personalInfoCollectList'" label="" path="personalInfoCollectList" required>
        <div class="rich-text-wrapper">
          <RichTextEditor v-model="formModel.personalInfoCollectList" />
        </div>
      </n-form-item>
      <n-form-item v-show="activeSubTab === 'permissionCall'" label="" path="permissionCall" required>
        <div class="rich-text-wrapper">
          <RichTextEditor v-model="formModel.permissionCall" />
          <AiRichTextEditor v-model="currentContent" height="300px" />
        </div>
      </n-form-item>
    </n-form>
@@ -118,12 +108,12 @@
  width: 100%;
}
.rich-text-wrapper :deep(.ql-container) {
.rich-text-wrapper :deep(.aie-container) {
  min-height: 300px;
  font-size: 14px;
}
.rich-text-wrapper :deep(.ql-editor) {
.rich-text-wrapper :deep(.aie-main) {
  min-height: 300px;
}
.tabs {
src/views/userContent/contentCenter/components/SimpleRichTextForm.vue
@@ -1,7 +1,7 @@
<script setup lang="ts">
import { ref } from 'vue'
import type { FormInst, FormRules } from 'naive-ui'
import RichTextEditor from '@/components/custom/Editor/RichTextEditor/index.vue'
import AiRichTextEditor from '@/components/custom/Editor/AiRichTextEditor/index.vue'
interface FormModel {
  content: string
@@ -45,7 +45,7 @@
    >
      <n-form-item label="" path="content" required>
        <div class="rich-text-wrapper">
          <RichTextEditor v-model="formModel.content" />
          <AiRichTextEditor v-model="formModel.content" height="300px" />
        </div>
      </n-form-item>
    </n-form>
@@ -57,12 +57,12 @@
  width: 100%;
}
.rich-text-wrapper :deep(.ql-container) {
.rich-text-wrapper :deep(.aie-container) {
  min-height: 300px;
  font-size: 14px;
}
.rich-text-wrapper :deep(.ql-editor) {
.rich-text-wrapper :deep(.aie-main) {
  min-height: 300px;
}
</style>
src/views/userContent/contentCenter/components/VehiclePrepareForm.vue
@@ -1,7 +1,7 @@
<script setup lang="ts">
import { computed, ref } from 'vue'
import type { FormInst, FormRules } from 'naive-ui'
import RichTextEditor from '@/components/custom/Editor/RichTextEditor/index.vue'
import AiRichTextEditor from '@/components/custom/Editor/AiRichTextEditor/index.vue'
interface FormModel {
  tirePressure: string
@@ -45,6 +45,14 @@
    r[key] = [{ required: true, message: map[key], trigger: 'blur' }]
  }
  return r
})
const currentContent = computed({
  get: () => formModel.value[activeSubTab.value as keyof FormModel],
  set: (value: string) => {
    const key = activeSubTab.value as keyof FormModel
    formModel.value[key] = value
  },
})
async function validate() {
@@ -92,33 +100,9 @@
      label-width="80"
      require-mark-placement="left"
    >
      <n-form-item v-show="activeSubTab === 'tirePressure'" label="" path="tirePressure" required>
      <n-form-item label="" :path="activeSubTab" required>
        <div class="rich-text-wrapper">
          <RichTextEditor v-model="formModel.tirePressure" />
        </div>
      </n-form-item>
      <n-form-item v-show="activeSubTab === 'lighting'" label="" path="lighting" required>
        <div class="rich-text-wrapper">
          <RichTextEditor v-model="formModel.lighting" />
        </div>
      </n-form-item>
      <n-form-item v-show="activeSubTab === 'bodyClean'" label="" path="bodyClean" required>
        <div class="rich-text-wrapper">
          <RichTextEditor v-model="formModel.bodyClean" />
        </div>
      </n-form-item>
      <n-form-item v-show="activeSubTab === 'cabinClean'" label="" path="cabinClean" required>
        <div class="rich-text-wrapper">
          <RichTextEditor v-model="formModel.cabinClean" />
        </div>
      </n-form-item>
      <n-form-item v-show="activeSubTab === 'lostItems'" label="" path="lostItems" required>
        <div class="rich-text-wrapper">
          <RichTextEditor v-model="formModel.lostItems" />
          <AiRichTextEditor v-model="currentContent" height="300px" />
        </div>
      </n-form-item>
    </n-form>
@@ -130,12 +114,12 @@
  width: 100%;
}
.rich-text-wrapper :deep(.ql-container) {
.rich-text-wrapper :deep(.aie-container) {
  min-height: 300px;
  font-size: 14px;
}
.rich-text-wrapper :deep(.ql-editor) {
.rich-text-wrapper :deep(.aie-main) {
  min-height: 300px;
}
.tabs {