hejianhao
2026-03-16 28a26bfe8ff2045dbdb1ef1effff159489c991d5
feat(客服审核): 完善人员资质与保单申请审核功能

- 为审核模态框添加国际化支持并优化样式
- 重构保单申请列表,使用统一搜索面板并优化表格展示
- 重构人员资质列表,改进头像显示和证件图片预览
- 添加中英文翻译键值,统一审核相关术语
- 优化表格列宽和响应式布局,提升用户体验
6个文件已修改
832 ■■■■■ 已修改文件
locales/en_US.json 57 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
locales/zh_CN.json 61 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/styles/robotaxi.scss 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/service/review/personnel/components/ReviewModal.vue 116 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/service/review/personnel/index.vue 363 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/service/review/policyApplication/index.vue 227 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
locales/en_US.json
@@ -200,5 +200,62 @@
  "transaction": {
    "export": "Export order",
    "orderNo": "orderNo"
  },
  "serviceReview": {
    "common": {
      "review": "Review",
      "batchReview": "Batch review",
      "delete": "Delete",
      "deleteTitle": "Confirm delete",
      "deletePolicyContent": "Are you sure you want to delete this policy application? This action cannot be undone.",
      "deletePersonnelContent": "Are you sure you want to delete this employee info? This action cannot be undone.",
      "deleteSuccess": "Deleted successfully",
      "deleteFail": "Delete failed",
      "reviewSuccess": "Review succeeded",
      "reviewFail": "Review failed",
      "selectPolicyToReview": "Please select policy applications to review",
      "selectPersonnelToReview": "Please select personnel to review",
      "pending": "Pending",
      "approved": "Approved",
      "rejected": "Rejected",
      "auditResult": "Review",
      "pass": "Pass",
      "reject": "Reject",
      "remark": "Remark",
      "chooseResult": "Please choose a result",
      "remarkRequired": "Please enter a remark",
      "save": "Save",
      "validateFail": "Validation failed"
    },
    "policyApplication": {
      "plateNumber": "Plate number",
      "applicant": "Applicant",
      "status": "Status",
      "applyTime": "Apply time",
      "vehicleCode": "Vehicle code",
      "phoneNumber": "Phone number",
      "auditStatus": "Review status",
      "auditUserName": "Reviewer",
      "auditTime": "Review time",
      "auditRemarks": "Review remarks",
      "actions": "Actions",
      "index": "No."
    },
    "personnel": {
      "name": "Name",
      "id": "ID",
      "phoneNumber": "Phone number",
      "role": "Role",
      "idCard": "ID card",
      "driverLicense": "Driver license",
      "noCriminalRecord": "No criminal record",
      "status": "Status",
      "uploadTime": "Upload time",
      "actions": "Actions",
      "index": "No.",
      "audit": "Review",
      "searchName": "Employee name",
      "avatarFallback": "A"
    }
  }
}
locales/zh_CN.json
@@ -197,8 +197,8 @@
    "operationsMessage": "消息",
    "service": "客服",
    "serviceReview": "客服审核",
    "serviceReviewPersonnel": "客服人员",
    "serviceReviewPolicyApplication": "政策申请"
    "serviceReviewPersonnel": "人员资质",
    "serviceReviewPolicyApplication": "保单申请"
  },
  "transaction": {
    "export": "导出订单",
@@ -222,5 +222,62 @@
    "affiliatedFleet": "归属车队",
    "orderAmount": "订单金额",
    "status": "状态"
  },
  "serviceReview": {
    "common": {
      "review": "审核",
      "batchReview": "批量审核",
      "delete": "删除",
      "deleteTitle": "确认删除",
      "deletePolicyContent": "确定要删除该条保单申请记录?此操作不可恢复。",
      "deletePersonnelContent": "确定要删除该员工信息?此操作不可恢复。",
      "deleteSuccess": "删除成功",
      "deleteFail": "删除失败",
      "reviewSuccess": "审核成功",
      "reviewFail": "审核失败",
      "selectPolicyToReview": "请先选择要审核的保单申请",
      "selectPersonnelToReview": "请先选择要审核的人员",
      "pending": "待审核",
      "approved": "已通过",
      "rejected": "已驳回",
      "auditResult": "审核",
      "pass": "通过",
      "reject": "不通过",
      "remark": "备注",
      "chooseResult": "请选择审核结果",
      "remarkRequired": "请输入备注",
      "save": "保存",
      "validateFail": "验证失败"
    },
    "policyApplication": {
      "plateNumber": "车牌号",
      "applicant": "申请人",
      "status": "状态",
      "applyTime": "申请时间",
      "vehicleCode": "车辆编码",
      "phoneNumber": "联系电话",
      "auditStatus": "运营状态",
      "auditUserName": "审核人",
      "auditTime": "审核时间",
      "auditRemarks": "审核备注",
      "actions": "操作",
      "index": "序号"
    },
    "personnel": {
      "name": "姓名",
      "id": "编号",
      "phoneNumber": "联系电话",
      "role": "角色",
      "idCard": "身份证",
      "driverLicense": "驾驶证",
      "noCriminalRecord": "无犯罪记录证明",
      "status": "状态",
      "uploadTime": "上传时间",
      "actions": "操作",
      "index": "序号",
      "audit": "审核",
      "searchName": "员工姓名",
      "avatarFallback": "头"
    }
  }
}
src/styles/robotaxi.scss
@@ -172,3 +172,11 @@
//     display: none;
//   }
// }
.n-input--textarea {
  .n-input-word-count {
    font-size: 12px;
    color: rgba(23, 31, 38, 0.3);
    line-height: 18px;
  }
}
src/views/service/review/personnel/components/ReviewModal.vue
@@ -1,105 +1,117 @@
<script setup lang="ts">
import { ref, computed } from 'vue'
import type { FormInst, FormRules } from 'naive-ui'
import { ref, computed } from "vue";
import type { FormInst, FormRules } from "naive-ui";
interface FormModel {
  result: 'pass' | 'reject'
  auditRemarks: string
  result: "pass" | "reject";
  auditRemarks: string;
}
const { t } = useI18n();
const props = defineProps<{
  show: boolean
}>()
  show: boolean;
}>();
const emit = defineEmits<{
  'update:show': [value: boolean]
  confirm: [data: FormModel]
}>()
  "update:show": [value: boolean];
  confirm: [data: FormModel];
}>();
const show = computed({
  get: () => props.show,
  set: (value) => emit('update:show', value)
})
  set: (value) => emit("update:show", value),
});
const formRef = ref<FormInst | null>(null)
const formRef = ref<FormInst | null>(null);
const formModel = ref<FormModel>({
  result: 'pass',
  auditRemarks: ''
})
  result: "pass",
  auditRemarks: "",
});
const rules: FormRules = {
  result: [
    { required: true, message: '请选择审核结果', trigger: 'change' }
    { required: true, message: t("serviceReview.common.chooseResult"), trigger: "change" },
  ],
  auditRemarks: [
    {
      validator: (_rule, value) => {
        if (formModel.value.result === 'reject' && (!value || !String(value).trim())) {
          return new Error('请输入备注')
        if (
          formModel.value.result === "reject" &&
          (!value || !String(value).trim())
        ) {
          return new Error(t("serviceReview.common.remarkRequired"));
        }
        return true
        return true;
      },
      trigger: ['input', 'blur']
    }
  ]
}
      trigger: ["input", "blur"],
    },
  ],
};
const handleCancel = () => {
  show.value = false
  resetForm()
}
  show.value = false;
  resetForm();
};
const handleConfirm = async () => {
  try {
    await formRef.value?.validate()
    emit('confirm', formModel.value)
    show.value = false
    resetForm()
    await formRef.value?.validate();
    emit("confirm", formModel.value);
    show.value = false;
    resetForm();
  } catch (error) {
    console.log('验证失败', error)
    console.log(t("serviceReview.common.validateFail"), error);
  }
}
};
const resetForm = () => {
  formModel.value = {
    result: 'pass',
    auditRemarks: ''
  }
}
    result: "pass",
    auditRemarks: "",
  };
};
</script>
<template>
  <n-modal
    v-model:show="show"
    :mask-closable="false"
    preset="card"
    title="审核"
    class="w-600px"
  <n-modal
    v-model:show="show"
    :mask-closable="false"
    preset="card"
    :title="t('serviceReview.common.review')"
    class="w-400px"
    :auto-focus="false"
    :segmented="{
      content: true,
      action: true,
    }">
    }"
  >
    <n-form
      ref="formRef"
      :model="formModel"
      :rules="rules"
      label-placement="left"
      label-width="80"
      require-mark-placement="left"
    >
      <n-form-item label="审核" path="result" required>
      <n-form-item :label="t('serviceReview.common.auditResult')" path="result" required>
        <n-radio-group v-model:value="formModel.result">
          <n-radio value="pass">通过</n-radio>
          <n-radio value="reject">不通过</n-radio>
          <n-radio value="pass">{{ t("serviceReview.common.pass") }}</n-radio>
          <n-radio value="reject">{{ t("serviceReview.common.reject") }}</n-radio>
        </n-radio-group>
      </n-form-item>
      <n-form-item label="备注" path="auditRemarks" :required="formModel.result === 'reject'">
      <n-form-item
        :label="t('serviceReview.common.remark')"
        path="auditRemarks"
        label-placement="top"
        :required="formModel.result === 'reject'"
      >
        <n-input
          v-model:value="formModel.auditRemarks"
          type="textarea"
          placeholder="请输入"
          :placeholder="t('common.inputPlaceholder')"
          :rows="4"
          show-count
          maxlength="200"
        />
      </n-form-item>
@@ -107,8 +119,10 @@
    <template #action>
      <n-space justify="end">
        <n-button @click="handleCancel">取消</n-button>
        <n-button type="primary" @click="handleConfirm">保存</n-button>
        <n-button @click="handleCancel">{{ t("common.cancel") }}</n-button>
        <n-button type="primary" @click="handleConfirm">
          {{ t("serviceReview.common.save") }}
        </n-button>
      </n-space>
    </template>
  </n-modal>
src/views/service/review/personnel/index.vue
@@ -1,7 +1,7 @@
<script setup lang="tsx">
import { ref, onMounted } from "vue";
import type { DataTableColumns } from "naive-ui";
import { NButton, NTag, NAvatar, NImage } from "naive-ui";
import { NButton, NTag, NAvatar, NImage, NTooltip, NSpace } from "naive-ui";
import { useBoolean } from "@/hooks";
import {
  auditQualification,
@@ -10,12 +10,14 @@
} from "@/service/api/serviceCenter";
import { fetchRoleList } from "@/service/api/fleet";
import ReviewModal from "./components/ReviewModal.vue";
import dayjs from "dayjs";
const {
  bool: loading,
  setTrue: startLoading,
  setFalse: endLoading,
} = useBoolean(false);
const { t } = useI18n();
const initialSearchForm = {
  nickName: "",
@@ -28,17 +30,17 @@
const roleOptions = ref<any[]>([]);
const statusOptions = [
  { label: "待审核", value: 1 },
  { label: "审核通过", value: 2 },
  { label: "审核驳回", value: 3 },
  { label: t("serviceReview.common.pending"), value: 1 },
  { label: t("serviceReview.common.approved"), value: 2 },
  { label: t("serviceReview.common.rejected"), value: 3 },
];
const tableData = ref<any[]>([]);
const auditStatusLabelMap: Record<number, string> = {
  1: "待审核",
  2: "审核通过",
  3: "审核驳回",
  1: t("serviceReview.common.pending"),
  2: t("serviceReview.common.approved"),
  3: t("serviceReview.common.rejected"),
};
const auditStatusTagTypeMap: Record<number, "warning" | "success" | "error"> = {
  1: "warning",
@@ -52,93 +54,76 @@
    disabled: (row) => row.auditStatus !== 1,
  },
  {
    title: "序号",
    title: t("serviceReview.personnel.index"),
    key: "serialNumber",
    width: 80,
    align: "center",
    resizable: true,
  },
  {
    title: "姓名",
    title: t("serviceReview.personnel.name"),
    key: "nickName",
    width: 120,
    align: "center",
    width: 150,
    resizable: true,
    render: (row) => {
      const displayName = row.nickName || "-";
      return (
        <div class="flex items-center justify-center gap-2">
          <NAvatar
            round
            size={32}
            style={{ backgroundColor: "#e0e0e0" }}
            src={row.avatar || undefined}
          >
            {displayName.charAt(0)}
          </NAvatar>
          <span>{displayName}</span>
        <div class="flex items-center gap-2">
        {row.avatar && <img src={JSON.parse(row.avatar).url} alt="头像" class="w-8 h-8 rounded-full" />}
        {!row.avatar && <div class="w-8 h-8 rounded-full bg-gray-300 flex items-center justify-center text-sm">{t("serviceReview.personnel.avatarFallback")}</div>}
        <NTooltip trigger="hover" placement="top" max-width="500px">
          {{
            trigger: () => <div class="flex-1 overflow-hidden text-ellipsis whitespace-nowrap">{row.nickName}</div>,
            default: () => row.nickName,
          }}
        </NTooltip>
        </div>
      );
      )
    },
  },
  {
    title: "编号",
    title: t("serviceReview.personnel.id"),
    key: "operationsUserId",
    width: 120,
    align: "center",
    width: 100,
    resizable: true,
  },
  {
    title: "联系电话",
    title: t("serviceReview.personnel.phoneNumber"),
    key: "phoneNumber",
    width: 140,
    align: "center",
    width: 110,
    resizable: true,
  },
  {
    title: "角色",
    title: t("serviceReview.personnel.role"),
    key: "roleNames",
    width: 150,
    align: "center",
    width: 300,
    resizable: true,
    render: (row) => {
      const roleName = Array.isArray(row.roleNames)
        ? row.roleNames.join("、")
        : row.roleNames || "-";
      return (
        <NTag type="info" size="small">
          {roleName}
        </NTag>
      );
      return <NSpace>
        {
          row.roleNames.map((roleName:string) => (
            <NTag type="info" bordered={false} size="small">
              {roleName}
            </NTag>
          ))
      }
      </NSpace>
    },
  },
  {
    title: "审核状态",
    key: "auditStatus",
    width: 120,
    align: "center",
    render: (row) => {
      const statusText = auditStatusLabelMap[row.auditStatus];
      const tagType = auditStatusTagTypeMap[row.auditStatus];
      return (
        <NTag type={tagType as any} size="small">
          {statusText}
        </NTag>
      );
    },
  },
  {
    title: "身份证",
    title: t("serviceReview.personnel.idCard"),
    key: "idCard",
    width: 150,
    align: "center",
    width: 160,
    resizable: true,
    render: (row) => {
      if (row.idCardFront && row.idCardBack) {
        const idCardFront = JSON.parse(row.idCardFront).url;
        const idCardBack = JSON.parse(row.idCardBack).url;
        return (
          <div class="flex gap-1 justify-center">
          <div class="flex gap-1">
            <NImage
              width={60}
              height={40}
              src={idCardFront}
              objectFit="cover"
              style={{ borderRadius: "4px" }}
              previewSrc={idCardFront}
            />
            <NImage
@@ -146,49 +131,19 @@
              height={40}
              src={idCardBack}
              objectFit="cover"
              style={{ borderRadius: "4px" }}
              previewSrc={idCardBack}
            />
          </div>
        );
      }
      return (
        <div class="flex gap-1 justify-center">
          <div
            style={{
              width: "60px",
              height: "40px",
              backgroundColor: "#f0f0f0",
              borderRadius: "4px",
              display: "flex",
              alignItems: "center",
              justifyContent: "center",
            }}
          >
            📷
          </div>
          <div
            style={{
              width: "60px",
              height: "40px",
              backgroundColor: "#f0f0f0",
              borderRadius: "4px",
              display: "flex",
              alignItems: "center",
              justifyContent: "center",
            }}
          >
            📷
          </div>
        </div>
      );
      return '-'
    },
  },
  {
    title: "驾驶证",
    title: t("serviceReview.personnel.driverLicense"),
    key: "driverLicense",
    width: 100,
    align: "center",
    resizable: true,
    render: (row) => {
      if (row.driverLicense) {
        const driverLicense = JSON.parse(row.driverLicense).url;
@@ -198,34 +153,18 @@
            height={40}
            src={driverLicense}
            objectFit="cover"
            style={{ borderRadius: "4px" }}
            previewSrc={driverLicense}
          />
        );
      }
      return (
        <div
          style={{
            width: "60px",
            height: "40px",
            backgroundColor: "#f0f0f0",
            borderRadius: "4px",
            display: "flex",
            alignItems: "center",
            justifyContent: "center",
            margin: "0 auto",
          }}
        >
          📷
        </div>
      );
      return '-'
    },
  },
  {
    title: "无犯罪记录证明",
    title: t("serviceReview.personnel.noCriminalRecord"),
    key: "certificateOfNoCriminalRecord",
    width: 140,
    align: "center",
    resizable: true,
    render: (row) => {
      if (row.certificateOfNoCriminalRecord) {
        const certificateOfNoCriminalRecord = JSON.parse(
@@ -237,44 +176,45 @@
            height={40}
            src={certificateOfNoCriminalRecord}
            objectFit="cover"
            style={{ borderRadius: "4px" }}
            previewSrc={certificateOfNoCriminalRecord}
          />
        );
      }
      return '-'
    },
  },
  {
    title: t("serviceReview.personnel.status"),
    key: "auditStatus",
    width: 120,
    resizable: true,
    render: (row) => {
      const statusText = auditStatusLabelMap[row.auditStatus];
      const tagType = auditStatusTagTypeMap[row.auditStatus];
      return (
        <div
          style={{
            width: "60px",
            height: "40px",
            backgroundColor: "#f0f0f0",
            borderRadius: "4px",
            display: "flex",
            alignItems: "center",
            justifyContent: "center",
            margin: "0 auto",
          }}
        >
          📷
        </div>
        <NTag type={tagType as any} bordered={false} size="small">
          {statusText}
        </NTag>
      );
    },
  },
  {
    title: "上传时间",
    title: t("serviceReview.personnel.uploadTime"),
    key: "createTime",
    width: 180,
    align: "center",
    resizable: true,
    render: (row) => {
      return row.createTime ? dayjs(row.createTime).format("YYYY/MM/DD HH:mm:ss") : "-";
    }
  },
  {
    title: "操作",
    title: t("serviceReview.personnel.actions"),
    key: "actions",
    width: 150,
    align: "center",
    width: 100,
    fixed: "right",
    render: (row) => {
      return (
        <div class="flex items-center justify-center gap-2">
        <NSpace>
          {row.auditStatus === 1 ? (
            <NButton
              v-permission="80112"
@@ -282,7 +222,7 @@
              type="primary"
              onClick={() => handleApprove(row.id)}
            >
              审核
              {t("serviceReview.personnel.audit")}
            </NButton>
          ) : null}
          <NButton
@@ -291,9 +231,9 @@
            type="error"
            onClick={() => handleDelete(row.id)}
          >
            删除
            {t("serviceReview.common.delete")}
          </NButton>
        </div>
        </NSpace>
      );
    },
  },
@@ -321,7 +261,7 @@
const handleBatchReview = () => {
  if (selectedIds.value.length === 0) {
    window.$message?.warning("请先选择要审核的人员");
    window.$message?.warning(t("serviceReview.common.selectPersonnelToReview"));
    return;
  }
  showReviewModal.value = true;
@@ -334,17 +274,17 @@
const handleDelete = (id: number) => {
  window.$dialog?.warning({
    title: "确认删除",
    content: "确定要删除该员工信息?此操作不可恢复。",
    positiveText: "确定",
    negativeText: "取消",
    title: t("serviceReview.common.deleteTitle"),
    content: t("serviceReview.common.deletePersonnelContent"),
    positiveText: t("common.confirm"),
    negativeText: t("common.cancel"),
    onPositiveClick: async () => {
      const res = await deleteQualificationAuditById(id);
      if (res.code === 200) {
        window.$message?.success("删除成功");
        window.$message?.success(t("serviceReview.common.deleteSuccess"));
        getPersonnelList();
      } else {
        window.$message?.error("删除失败");
        window.$message?.error(t("serviceReview.common.deleteFail"));
      }
    },
  });
@@ -358,11 +298,11 @@
    auditRemarks: data?.auditRemarks || "",
  });
  if (res.code === 200) {
    window.$message?.success("审核成功");
    window.$message?.success(t("serviceReview.common.reviewSuccess"));
    selectedIds.value = [];
    getPersonnelList();
  } else {
    window.$message?.error("审核失败");
    window.$message?.error(t("serviceReview.common.reviewFail"));
  }
};
@@ -417,90 +357,71 @@
  getRoleOptions();
  getPersonnelList();
});
const filterItems = computed(() => [
  {
    label: t("serviceReview.personnel.searchName"),
    path: 'nickName',
    render: () => <n-input v-model:value={searchForm.value.nickName} placeholder={t("common.inputPlaceholder")} />,
  },
  {
    label: t("serviceReview.personnel.phoneNumber"),
    path: 'phoneNumber',
    render: () => <n-input v-model:value={searchForm.value.phoneNumber} placeholder={t("common.inputPlaceholder")} />,
  },
  {
    label: t("serviceReview.personnel.role"),
    path: 'roleId',
    render: () => <n-select options={roleOptions.value} v-model:value={searchForm.value.roleId} placeholder={t("common.selectPlaceholder")} clearable />,
  },
  {
    label: t("serviceReview.policyApplication.status"),
    path: 'auditStatus',
    render: () => <n-select options={statusOptions} v-model:value={searchForm.value.auditStatus} placeholder={t("common.selectPlaceholder")} clearable />,
  },
])
</script>
<template>
  <div class="h-full flex flex-col">
    <n-card style="border-radius: 6px" content-style="padding: 12px">
  <div class="roboTaxi">
    <div ref="formRef" class="roboTaxi-form-card mb-12px">
      <n-form
        :model="searchForm"
        label-placement="left"
        inline
        :show-feedback="false"
      >
        <div class="flex justify-between items-center w-full">
          <div class="flex gap-4">
            <n-form-item label="员工姓名">
              <n-input
                v-model:value="searchForm.nickName"
                placeholder="请输入"
                clearable
                style="width: 200px"
              />
        <SearchPanel>
          <n-gi v-for="item in filterItems" :key="item.path">
            <n-form-item :label="item.label" :path="item.path">
              <component :is="item.render" />
            </n-form-item>
            <n-form-item label="联系电话">
              <n-input
                v-model:value="searchForm.phoneNumber"
                placeholder="请输入"
                clearable
                style="width: 200px"
              />
            </n-form-item>
            <n-form-item label="角色">
              <n-select
                v-model:value="searchForm.roleId"
                placeholder="请选择"
                clearable
                style="width: 200px"
                :options="roleOptions"
              />
            </n-form-item>
            <n-form-item label="审核状态">
              <n-select
                v-model:value="searchForm.auditStatus"
                placeholder="请选择"
                clearable
                style="width: 200px"
                :options="statusOptions"
              />
            </n-form-item>
          </div>
          <div class="flex gap-2">
            <n-button style="width: 84px; height: 32px" @click="handleReset"
              >重置</n-button
            >
            <n-button
              type="primary"
              style="width: 84px; height: 32px"
              @click="handleSearch"
              >查询</n-button
            >
          </div>
        </div>
          </n-gi>
          <template #action>
            <NButton strong @click="handleReset">
              {{ $t("common.reset") }}
            </NButton>
            <NButton type="primary" @click="handleSearch">
              {{ $t("common.search") }}
            </NButton>
          </template>
        </SearchPanel>
      </n-form>
    </n-card>
    </div>
    <n-card
      style="border-radius: 6px; margin-top: 12px"
      content-style="padding: 12px"
    >
      <div class="mb-4 flex justify-end gap-2">
        <n-button
         v-permission="80111"
          style="
            width: 84px;
            height: 32px;
            color: #0068ff;
            background-color: #e6f4ff;
            border: 1px solid #7abdff;
          "
          @click="handleBatchReview"
          >批量审核</n-button
        >
      </div>
    <n-card class="flex-1 robotaxi-table-card">
      <template #header>
        <n-flex v-permission="80111">
          <NButton strong @click="handleBatchReview">
            {t("serviceReview.common.batchReview")}
          </NButton>
        </n-flex>
      </template>
      <NSpace vertical>
      <div class="flex-1 flex flex-col h-full">
        <n-data-table
          class="flex-1"
          flex-height
          :columns="columns"
          :data="tableData"
          :loading="loading"
@@ -508,8 +429,14 @@
          :row-key="(row: any) => row.id"
          @update:checked-row-keys="handleCheck"
        />
        <Pagination :count="count" :page="pagination.page" :page-size="pagination.pageSize" @change="changePage" />
      </NSpace>
        <Pagination
          :count="count"
          :page="pagination.page"
          :page-size="pagination.pageSize"
          @change="changePage"
          class="robotaxi-pagination mt-12px"
        />
      </div>
    </n-card>
    <ReviewModal
src/views/service/review/policyApplication/index.vue
@@ -16,6 +16,7 @@
  setTrue: startLoading,
  setFalse: endLoading,
} = useBoolean(false);
const { t } = useI18n();
const initialSearchForm = {
  plateNumber: "",
@@ -26,17 +27,17 @@
const searchForm = ref({ ...initialSearchForm });
const statusOptions = [
  { label: "待审核", value: 1 },
  { label: "已通过", value: 2 },
  { label: "已驳回", value: 3 },
  { label: t("serviceReview.common.pending"), value: 1 },
  { label: t("serviceReview.common.approved"), value: 2 },
  { label: t("serviceReview.common.rejected"), value: 3 },
];
const tableData = ref<any[]>([]);
const auditStatusLabelMap: Record<number, string> = {
  1: "待审核",
  2: "已通过",
  3: "已驳回",
  1: t("serviceReview.common.pending"),
  2: t("serviceReview.common.approved"),
  3: t("serviceReview.common.rejected"),
};
const auditStatusTagTypeMap: Record<number, "warning" | "success" | "error"> = {
  1: "warning",
@@ -59,10 +60,10 @@
    disabled: (row) => row.auditStatus !== 1,
  },
  {
    title: "序号",
    title: t("serviceReview.policyApplication.index"),
    key: "index",
    width: 80,
    align: "center",
    resizable: true,
    render: (_row, index) =>
      h(
        "span",
@@ -70,47 +71,40 @@
      ),
  },
  {
    title: "车辆编码",
    title: t("serviceReview.policyApplication.vehicleCode"),
    key: "vehicleCode",
    width: 120,
    align: "center",
    resizable: true,
  },
  {
    title: "车牌号",
    title: t("serviceReview.policyApplication.plateNumber"),
    key: "plateNumber",
    width: 120,
    align: "center",
    resizable: true,
  },
  {
    title: "申请人",
    title: t("serviceReview.policyApplication.applicant"),
    key: "nickName",
    width: 120,
    align: "center",
    resizable: true,
  },
  {
    title: "电话",
    title: t("serviceReview.policyApplication.phoneNumber"),
    key: "phoneNumber",
    width: 140,
    align: "center",
    resizable: true,
  },
  {
    title: "申请时间",
    key: "applyTime",
    width: 160,
    align: "center",
    render: (row) => row.applyTime || row.createTime || "-",
  },
  {
    title: "状态",
    title: t("serviceReview.policyApplication.auditStatus"),
    key: "auditStatus",
    width: 120,
    align: "center",
    resizable: true,
    render: (row) => {
      const statusText = auditStatusLabelMap[row.auditStatus];
      const tagType = auditStatusTagTypeMap[row.auditStatus];
      return h(
        NTag,
        { type: tagType as any, size: "small" },
        { type: tagType as any, size: "small", bordered: false },
        {
          default: () => statusText,
        }
@@ -118,24 +112,40 @@
    },
  },
  {
    title: "审核人",
    title: t("serviceReview.policyApplication.applyTime"),
    key: "applyTime",
    width: 180,
    resizable: true,
    render: (row) => {
      return row.applyTime ? dayjs(row.applyTime).format("YYYY/MM/DD HH:mm:ss") : "-";
    }
  },
  {
    title: t("serviceReview.policyApplication.auditUserName"),
    key: "auditUserName",
    width: 120,
    align: "center",
    resizable: true,
    render: (row) => {
      return row.auditUserName || "-";
    }
  },
  {
    title: "审核时间",
    title: t("serviceReview.policyApplication.auditTime"),
    key: "auditTime",
    width: 160,
    align: "center",
    resizable: true,
    render: (row) => {
      return row.auditTime ? dayjs(row.auditTime).format("YYYY/MM/DD HH:mm:ss") : "-";
    }
  },
  {
    title: "审核备注",
    title: t("serviceReview.policyApplication.auditRemarks"),
    key: "auditRemarks",
    width: 160,
    align: "center",
    resizable: true,
    render: (row) => {
      const fullText = row.auditRemarks ?? "-";
      const fullText = row.auditRemarks || "-";
      if (fullText === "-") return fullText;
      const normalized = String(fullText);
      const displayText =
        normalized.length > 200 ? `${normalized.slice(0, 200)}...` : normalized;
@@ -167,10 +177,9 @@
    },
  },
  {
    title: "操作",
    title: t("serviceReview.policyApplication.actions"),
    key: "actions",
    width: 150,
    align: "center",
    fixed: "right",
    render: (row) => {
      return (
@@ -182,7 +191,7 @@
              type="primary"
              onClick={() => handleApprove(row.id)}
            >
              审核
              {t("serviceReview.common.review")}
            </NButton>
          ) : null}
          <NButton
@@ -191,7 +200,7 @@
            type="error"
            onClick={() => handleDelete(row.id)}
          >
            删除
            {t("serviceReview.common.delete")}
          </NButton>
        </div>
      );
@@ -212,7 +221,7 @@
const handleBatchReview = () => {
  if (selectedIds.value.length === 0) {
    window.$message?.warning("请先选择要审核的保单申请");
    window.$message?.warning(t("serviceReview.common.selectPolicyToReview"));
    return;
  }
  showReviewModal.value = true;
@@ -225,17 +234,17 @@
const handleDelete = (id: number) => {
  window.$dialog?.warning({
    title: "确认删除",
    content: "确定要删除该条保单申请记录?此操作不可恢复。",
    positiveText: "确定",
    negativeText: "取消",
    title: t("serviceReview.common.deleteTitle"),
    content: t("serviceReview.common.deletePolicyContent"),
    positiveText: t("common.confirm"),
    negativeText: t("common.cancel"),
    onPositiveClick: async () => {
      const res = await deletePolicyApplicationById(id);
      if (res.code === 200) {
        window.$message?.success("删除成功");
        window.$message?.success(t("serviceReview.common.deleteSuccess"));
        getPolicyApplicationList();
      } else {
        window.$message?.error("删除失败");
        window.$message?.error(t("serviceReview.common.deleteFail"));
      }
    },
  });
@@ -249,11 +258,11 @@
    auditRemarks: data?.auditRemarks || "",
  });
  if (res.code === 200) {
    window.$message?.success("审核成功");
    window.$message?.success(t("serviceReview.common.reviewSuccess"));
    selectedIds.value = [];
    getPolicyApplicationList();
  } else {
    window.$message?.error("审核失败");
    window.$message?.error(t("serviceReview.common.reviewFail"));
  }
};
@@ -299,92 +308,71 @@
onMounted(() => {
  getPolicyApplicationList();
});
const filterItems = computed(() => [
  {
    label: t("serviceReview.policyApplication.plateNumber"),
    path: 'plateNumber',
    render: () => <n-input v-model:value={searchForm.value.plateNumber} placeholder={t("common.inputPlaceholder")} />,
  },
  {
    label: t("serviceReview.policyApplication.applicant"),
    path: 'nickName',
    render: () => <n-input v-model:value={searchForm.value.nickName} placeholder={t("common.inputPlaceholder")} />,
  },
  {
    label: t("serviceReview.policyApplication.status"),
    path: 'auditStatus',
    render: () => <n-select options={statusOptions} v-model:value={searchForm.value.auditStatus} placeholder={t("common.selectPlaceholder")} clearable />,
  },
  {
    label: t("serviceReview.policyApplication.applyTime"),
    path: 'dateRange',
    render: () => <n-date-picker v-model:value={searchForm.value.dateRange} type="daterange" clearable />,
  },
])
</script>
<template>
  <div class="h-full flex flex-col">
    <n-card style="border-radius: 6px" content-style="padding: 12px">
  <div class="roboTaxi">
    <div ref="formRef" class="roboTaxi-form-card mb-12px">
      <n-form
        :model="searchForm"
        label-placement="left"
        inline
        :show-feedback="false"
      >
        <div class="flex justify-between items-center w-full">
          <div class="flex gap-4">
            <n-form-item label="车牌号">
              <n-input
                v-model:value="searchForm.plateNumber"
                placeholder="请输入"
                clearable
                style="width: 200px"
              />
        <SearchPanel>
          <n-gi v-for="item in filterItems" :key="item.path">
            <n-form-item :label="item.label" :path="item.path">
              <component :is="item.render" />
            </n-form-item>
            <n-form-item label="申请人">
              <n-input
                v-model:value="searchForm.nickName"
                placeholder="请输入"
                clearable
                style="width: 200px"
              />
            </n-form-item>
            <n-form-item label="状态">
              <n-select
                v-model:value="searchForm.auditStatus"
                placeholder="请选择"
                clearable
                style="width: 200px"
                :options="statusOptions"
              />
            </n-form-item>
            <n-form-item label="申请时间">
              <n-date-picker
                v-model:value="searchForm.dateRange"
                type="daterange"
                clearable
                value-format="yyyy-MM-dd"
                start-placeholder="开始时间"
                end-placeholder="结束时间"
                style="width: 240px"
              />
            </n-form-item>
          </div>
          <div class="flex gap-2">
            <n-button style="width: 84px; height: 32px" @click="handleReset"
              >重置</n-button
            >
            <n-button
              type="primary"
              style="width: 84px; height: 32px"
              @click="handleSearch"
              >查询</n-button
            >
          </div>
        </div>
          </n-gi>
          <template #action>
            <NButton strong @click="handleReset">
              {{ $t("common.reset") }}
            </NButton>
            <NButton type="primary" @click="handleSearch">
              {{ $t("common.search") }}
            </NButton>
          </template>
        </SearchPanel>
      </n-form>
    </n-card>
    </div>
    <n-card
      style="border-radius: 6px; margin-top: 12px"
      content-style="padding: 12px"
    >
      <div class="mb-4 flex justify-end gap-2">
        <n-button
        v-permission="80121"
          style="
            width: 84px;
            height: 32px;
            color: #0068ff;
            background-color: #e6f4ff;
            border: 1px solid #7abdff;
          "
          @click="handleBatchReview"
          >批量审核</n-button
        >
      </div>
    <n-card class="flex-1 robotaxi-table-card">
      <template #header>
        <n-flex v-permission="80111">
          <NButton strong @click="handleBatchReview">
            {t("serviceReview.common.batchReview")}
          </NButton>
        </n-flex>
      </template>
      <NSpace vertical>
      <div class="flex-1 flex flex-col h-full">
        <n-data-table
          class="flex-1"
          flex-height
          :columns="columns"
          :data="tableData"
          :loading="loading"
@@ -397,8 +385,9 @@
          :page="pagination.page"
          :page-size="pagination.pageSize"
          @change="changePage"
          class="robotaxi-pagination mt-12px"
        />
      </NSpace>
      </div>
    </n-card>
    <ReviewModal