ai-coding
2026-07-29 e32d17213665f032280db2f5fe85f3812670ec2d
Merge origin/feat/730-xywang into feat/deploy-uat

# Conflicts:
# package-lock.json

Co-Authored-By: 王心悦 <wangxy84@xiaopeng.com>
1个文件已添加
8个文件已修改
259 ■■■■■ 已修改文件
package-lock.json 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/custom/Editor/AiRichTextEditor/index.vue 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/service/api/operations.ts 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/userContent/contentCenter/agreementVersion.vue 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/userContent/contentCenter/components/AgreementListForm.vue 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/userContent/contentCenter/components/AgreementVersionModal.vue 174 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/userContent/contentCenter/constants/agreementCategory.ts 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/userContent/contentCenter/opsApp.vue 1 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/userContent/contentCenter/passenger.vue 1 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
package-lock.json
@@ -1,12 +1,12 @@
{
  "name": "xp-robotaxi-management",
  "version": "1.1.27",
  "version": "1.2.5",
  "lockfileVersion": 3,
  "requires": true,
  "packages": {
    "": {
      "name": "xp-robotaxi-management",
      "version": "1.1.27",
      "version": "1.2.5",
      "license": "MIT",
      "dependencies": {
        "@vueuse/core": "^13.6.0",
src/components/custom/Editor/AiRichTextEditor/index.vue
@@ -762,7 +762,6 @@
    <div
      ref="editorRef"
      class="ai-editor-host"
      :style="{ minHeight: height }"
    />
    <div
      v-if="showCount"
@@ -778,6 +777,10 @@
.ai-editor-container {
  position: relative;
  width: 100%;
  height: v-bind(height);
  display: flex;
  flex-direction: column;
  min-height: 0;
  border-radius: 4px;
  background: #fff;
  --aie-border-color: rgba(23, 31, 38, 0.15);
@@ -795,7 +798,8 @@
.ai-editor-host {
  width: 100%;
  min-height: v-bind(height);
  flex: 1;
  min-height: 0;
}
.ai-editor-count {
@@ -827,12 +831,14 @@
  overflow: hidden;
  border: 1px solid rgba(23, 31, 38, 0.15);
  box-sizing: border-box;
  min-height: 100%;
  height: v-bind(height);
  max-height: v-bind(height);
}
:deep(.aie-container aie-header) {
  background: #f4f6f8;
  border-bottom: 1px solid rgba(23, 31, 38, 0.15);
  flex-shrink: 0;
}
:deep(.aie-container aie-header > div) {
@@ -848,14 +854,14 @@
}
:deep(.aie-main) {
  flex: 1;
   min-height: v-bind(height);
  flex: 1 1 auto;
  min-height: 0;
  overflow: auto;
}
:deep(.aie-content) {
  min-height: 100%;
  padding-bottom: 28px;
  padding-bottom: 40px;
  box-sizing: border-box;
}
src/service/api/operations.ts
@@ -11,6 +11,7 @@
  version?: string
  status?: number
  sort?: number
  agreementCategory?: number | null
}) {
  return request.Post<Service.ResponseResult<any>>('/operation/api/v1/agreement/saveLoginAgreement', data)
}
@@ -28,6 +29,7 @@
    updateBy: string
    updateTime: string
    version: string
    latestAgreementCategory: number | null
  }>>>(`/operation/api/v1/agreement/getLoginAgreementPageList/${type}`)
}
@@ -54,6 +56,7 @@
      updateBy: string
      updateTime: string
      version: string
      agreementCategory: number | null
    }>
    size: number
    total: number
@@ -77,6 +80,7 @@
    updateBy: string
    updateTime: string
    version: string
    agreementCategory: number | null
  }>>(`/operation/api/v1/agreement/loginAgreement/detail/${id}`)
}
src/views/userContent/contentCenter/agreementVersion.vue
@@ -7,6 +7,7 @@
import OperationLogModal from '@/components/custom/OperationLogModal.vue'
import AgreementVersionModal from './components/AgreementVersionModal.vue'
import type { AgreementVersionFormValue } from './components/AgreementVersionModal.vue'
import { getAgreementCategoryLabel } from './constants/agreementCategory'
import {
  copyLoginAgreement,
  disableLoginAgreement,
@@ -27,6 +28,7 @@
  sort: number
  isNew?: boolean
  status?: 0 | 1 | 2
  agreementCategory?: number | null
}
const route = useRoute()
@@ -145,6 +147,7 @@
      id: row.id,
      version: row.version,
      content: row.content,
      agreementCategory: row.agreementCategory ?? null,
    }
  }
  const res = await getLoginAgreementDetail(row.id)
@@ -183,6 +186,13 @@
    resizable: true,
  },
  {
    title: '协议类型',
    key: 'agreementCategory',
    width: 100,
    resizable: true,
    render: (row: AgreementVersionRow) => getAgreementCategoryLabel(row.agreementCategory),
  },
  {
    title: '状态',
    key: 'status',
    width: 140,
@@ -200,6 +210,7 @@
    title: '操作',
    key: 'actions',
    width: 120,
    fixed: 'right',
    render: (row, index) => (
      <div class="flex items-center gap-12px">
        {isPendingStatus(row, index)
@@ -291,6 +302,7 @@
      sort: Number(item.sort || index + 1),
      isNew: false,
      status: [0, 1, 2].includes(Number(item.status)) ? Number(item.status) as 0 | 1 | 2 : undefined,
      agreementCategory: item.agreementCategory ?? null,
    }))
}
@@ -375,6 +387,7 @@
  openVersionModal('新增版本', {
    version,
    content: '',
    agreementCategory: null,
  })
}
@@ -391,6 +404,7 @@
    id: Number(detail.id),
    version: detail.version || row.version,
    content: detail.content || '',
    agreementCategory: detail.agreementCategory ?? null,
  })
}
@@ -411,6 +425,7 @@
    id: Number(detail.id),
    version: detail.version || row.version,
    content: detail.content || '',
    agreementCategory: detail.agreementCategory ?? null,
  }, true)
}
@@ -519,6 +534,7 @@
      type: agreementType.value,
      version: payload.version,
      status: 0,
      agreementCategory: payload.agreementCategory,
    })
    if (res?.code !== 200) {
      return
@@ -639,7 +655,7 @@
              :bordered="false"
              :row-key="(row: AgreementVersionRow) => row.id || row.version"
              flex-height
              :scroll-x="900"
              :scroll-x="920"
            >
              <template #empty>
                <div class="flex-center">
@@ -699,6 +715,7 @@
.page-body {
  height: calc(100vh - 88px - 28px);
  overflow-y: auto;
  overflow-x: hidden;
  padding: 12px;
  scrollbar-width: none;
}
src/views/userContent/contentCenter/components/AgreementListForm.vue
@@ -13,6 +13,7 @@
  getLoginAgreementPageList,
  saveLoginAgreement,
} from "@/service/api/operations";
import { getAgreementCategoryLabel } from "../constants/agreementCategory";
import dayjs from "dayjs";
const props = defineProps<{
@@ -28,6 +29,7 @@
  updateTime: string;
  sort: number;
  version: string;
  agreementCategory?: number | null;
}
const router = useRouter();
@@ -66,7 +68,14 @@
      ]);
    },
  },
  { title: titleLabel, resizable: true, key: "title" },
  { title: titleLabel, resizable: true, key: "title", width: 260, ellipsis: { tooltip: true } },
  {
    title: "协议类型",
    resizable: true,
    key: "agreementCategory",
    width: 140,
    render: (row: AgreementItem) => getAgreementCategoryLabel(row.agreementCategory),
  },
  { title: "启用中版本号", resizable: true, key: "version", width: 180 },
  { title: "创建人", resizable: true, key: "creator", width: 180 },
  {
@@ -81,6 +90,7 @@
    title: "操作",
    key: "actions",
    width: 120,
    fixed: "right",
    render: (row: AgreementItem) => {
      return h(
        NSpace,
@@ -124,6 +134,8 @@
      updateTime: item.updateTime || item.createTime || "",
      sort: Number(item.sort || 0),
      version: item.latestVersion || "-",
      // 协议列表展示的协议类型取"生效中版本"的协议类型,与启用中版本号同源
      agreementCategory: item.latestAgreementCategory ?? null,
    }));
}
@@ -381,6 +393,7 @@
      :bottom-bordered="true"
      :row-key="(row) => row.id ?? row.nameId"
      :row-props="getRowProps"
      :scroll-x="1200"
      class="custom-table"
    >
      <template #empty>
src/views/userContent/contentCenter/components/AgreementVersionModal.vue
@@ -1,13 +1,15 @@
<script setup lang="ts">
import { computed, reactive, ref, watch } from 'vue'
import type { FormInst, FormRules } from 'naive-ui'
import { NButton, NForm, NFormItem, NInput, NModal, NSpace } from 'naive-ui'
import { NButton, NForm, NFormItem, NInput, NModal, NSelect, NSpace } from 'naive-ui'
import AiRichTextEditor from '@/components/custom/Editor/AiRichTextEditor/index.vue'
import { AGREEMENT_CATEGORY_OPTIONS } from '../constants/agreementCategory'
export interface AgreementVersionFormValue {
  id?: number
  version: string
  content: string
  agreementCategory?: number | null
}
const props = withDefaults(defineProps<{
@@ -32,7 +34,9 @@
const formModel = reactive<AgreementVersionFormValue>({
  version: '',
  content: '',
  agreementCategory: null,
})
const contentErrorMessage = ref('')
const plainTextLength = computed(() => {
  return formModel.content
@@ -43,17 +47,12 @@
})
const rules: FormRules = {
  content: [
  agreementCategory: [
    {
      required: true,
      validator: () => {
        if (!plainTextLength.value)
          return new Error('请输入协议内容')
        // if (plainTextLength.value > 20000)
        //   return new Error('协议内容最多输入20000个字符')
        return true
      },
      trigger: ['blur', 'change', 'input'],
      type: 'number',
      message: '请选择协议类型',
      trigger: ['blur', 'change'],
    },
  ],
}
@@ -62,6 +61,8 @@
  formModel.id = props.rowData?.id
  formModel.version = props.rowData?.version || ''
  formModel.content = props.rowData?.content || ''
  formModel.agreementCategory = props.rowData?.agreementCategory ?? null
  contentErrorMessage.value = ''
  formRef.value?.restoreValidation()
}
@@ -72,7 +73,9 @@
async function handleSave() {
  if (props.readonly)
    return
  contentErrorMessage.value = plainTextLength.value ? '' : '请输入协议内容'
  try {
    await formRef.value?.validate()
  } catch (errors) {
@@ -80,12 +83,21 @@
    return
  }
  if (contentErrorMessage.value)
    return
  emit('save', {
    id: formModel.id,
    version: formModel.version,
    content: formModel.content,
    agreementCategory: formModel.agreementCategory,
  })
}
watch(() => formModel.content, () => {
  if (contentErrorMessage.value && plainTextLength.value)
    contentErrorMessage.value = ''
})
watch(() => props.visible, (visible) => {
  if (visible)
@@ -100,30 +112,52 @@
    preset="card"
    :title="title"
    class="agreement-version-modal"
    :style="{ width: '1000px' }"
    :style="{ width: '1000px', height: '90vh', maxHeight: '90vh', display: 'flex', flexDirection: 'column' }"
    :header-style="{ flexShrink: '0' }"
    :segmented="{ content: true, action: true }"
    :content-style="{ padding: '24px 24px 0 24px' }"
    :content-style="{ padding: '24px 24px 0 24px', display: 'flex', flexDirection: 'column', flex: '1', minHeight: '0', overflow: 'hidden' }"
    @close="handleClose"
  >
    <NForm
      ref="formRef"
      :model="formModel"
      :rules="rules"
      label-placement="top"
      require-mark-placement="left"
    >
      <NFormItem label="版本号" required>
        <NInput v-model:value="formModel.version" disabled placeholder="系统自动生成" />
      </NFormItem>
      <NFormItem label="协议内容" path="content" required>
    <div class="agreement-version-body">
      <NForm
        ref="formRef"
        :model="formModel"
        :rules="rules"
        label-placement="top"
        require-mark-placement="left"
        class="agreement-fixed-form"
      >
        <div class="form-row">
          <NFormItem label="版本号" required class="form-row-item">
            <NInput v-model:value="formModel.version" disabled placeholder="系统自动生成" />
          </NFormItem>
          <NFormItem label="协议类型" path="agreementCategory" required class="form-row-item">
            <NSelect
              v-model:value="formModel.agreementCategory"
              :options="AGREEMENT_CATEGORY_OPTIONS"
              :disabled="readonly"
              placeholder="请选择协议类型"
            />
          </NFormItem>
        </div>
      </NForm>
      <div class="agreement-content-label">
        协议内容
        <span v-if="!readonly" class="agreement-content-required">*</span>
      </div>
      <div class="agreement-content-area">
        <div class="rich-text-wrapper">
          <div class="editor-font-tip">
            推荐字号:大标题(21)小标题(15)正文(14)
          </div>
          <AiRichTextEditor v-model="formModel.content" :read-only="readonly" height="360px" />
          <AiRichTextEditor v-model="formModel.content" :read-only="readonly" height="100%" />
        </div>
      </NFormItem>
    </NForm>
        <div v-if="contentErrorMessage" class="agreement-content-error">
          {{ contentErrorMessage }}
        </div>
      </div>
    </div>
    <template #action>
      <NSpace justify="end" :size="8">
@@ -139,9 +173,67 @@
</template>
<style scoped lang="scss">
.agreement-version-body {
  display: flex;
  flex-direction: column;
  flex: 1;
  min-height: 0;
  overflow: hidden;
}
.agreement-fixed-form {
  flex-shrink: 0;
}
.form-row {
  display: flex;
  gap: 24px;
}
.form-row-item {
  flex: 1;
  min-width: 0;
}
.agreement-content-label {
  flex-shrink: 0;
  margin-bottom: 8px;
  font-size: 14px;
  color: #171f26;
}
.agreement-content-required {
  margin-left: 4px;
  color: #d03050;
}
.agreement-content-area {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
  padding-bottom: 24px;
}
.agreement-content-error {
  flex-shrink: 0;
  padding-top: 6px;
  font-size: 12px;
  color: #d03050;
}
.rich-text-wrapper {
  position: relative;
  width: 100%;
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
}
.rich-text-wrapper :deep(.ai-editor-container) {
  flex: 1;
  min-height: 0;
}
.editor-font-tip {
@@ -159,34 +251,30 @@
}
.rich-text-wrapper :deep(.aie-container) {
  min-height: 360px;
  font-size: 14px;
}
</style>
.rich-text-wrapper :deep(.aie-main) {
  min-height: 360px;
}
.content-count {
  width: 100%;
  margin-top: 8px;
  text-align: right;
  font-size: 12px;
  color: rgba(23, 31, 38, 0.45);
}
:deep(.agreement-version-modal .n-card-header) {
<style lang="scss">
/*
 * 以下规则不能加 scoped:preset="card" 的 NModal 实际渲染的 NCard/NCard 内部结构
 * 是由 naive-ui 在组件外部独立创建的 DOM 节点,不带当前组件的 data-v-xxx 属性,
 * scoped 下的 :deep() 选择器无法命中,必须用全局样式规则。
 */
.agreement-version-modal .n-card-header {
  padding: 20px 24px;
  flex-shrink: 0;
}
:deep(.agreement-version-modal .n-card-header__main) {
.agreement-version-modal .n-card-header__main {
  font-size: 16px;
  font-weight: 600;
  color: #171f26;
}
:deep(.agreement-version-modal .n-card__action) {
.agreement-version-modal .n-card__action {
  background-color: transparent !important;
  padding: 16px 24px;
  flex-shrink: 0;
}
</style>
src/views/userContent/contentCenter/constants/agreementCategory.ts
New file
@@ -0,0 +1,23 @@
/** 登录协议-协议类型:主协议/子协议/共享协议 */
export const AGREEMENT_CATEGORY_MAIN = 1
export const AGREEMENT_CATEGORY_SUB = 2
export const AGREEMENT_CATEGORY_SHARED = 3
export const AGREEMENT_CATEGORY_OPTIONS = [
  { label: '主协议', value: AGREEMENT_CATEGORY_MAIN },
  { label: '子协议', value: AGREEMENT_CATEGORY_SUB },
  { label: '共享协议', value: AGREEMENT_CATEGORY_SHARED },
]
const AGREEMENT_CATEGORY_LABEL_MAP: Record<number, string> = {
  [AGREEMENT_CATEGORY_MAIN]: '主协议',
  [AGREEMENT_CATEGORY_SUB]: '子协议',
  [AGREEMENT_CATEGORY_SHARED]: '共享协议',
}
/** 存量协议未设置协议类型时返回空字符串,用于列表回显 */
export function getAgreementCategoryLabel(value?: number | null): string {
  if (value === null || value === undefined)
    return '-'
  return AGREEMENT_CATEGORY_LABEL_MAP[value] || '-'
}
src/views/userContent/contentCenter/opsApp.vue
@@ -192,6 +192,7 @@
.tab-scroll-area {
  height: calc(100vh - 304px - 28px);
  overflow-y: auto;
  overflow-x: hidden;
  padding-bottom: 40px;
  scrollbar-width: thin;
  scrollbar-color: rgba(23, 31, 38, 0.2) transparent;
src/views/userContent/contentCenter/passenger.vue
@@ -303,6 +303,7 @@
.tab-scroll-area {
  height: calc(100vh - 304px - 28px);
  overflow-y: auto;
  overflow-x: hidden;
  padding-bottom: 40px;
  scrollbar-width: thin;
  scrollbar-color: rgba(23, 31, 38, 0.2) transparent;