feat(客服审核): 完善人员资质与保单申请审核功能
- 为审核模态框添加国际化支持并优化样式
- 重构保单申请列表,使用统一搜索面板并优化表格展示
- 重构人员资质列表,改进头像显示和证件图片预览
- 添加中英文翻译键值,统一审核相关术语
- 优化表格列宽和响应式布局,提升用户体验
| | |
| | | "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" |
| | | } |
| | | } |
| | | } |
| | |
| | | "operationsMessage": "消息", |
| | | "service": "客服", |
| | | "serviceReview": "客服审核", |
| | | "serviceReviewPersonnel": "客服人员", |
| | | "serviceReviewPolicyApplication": "政策申请" |
| | | "serviceReviewPersonnel": "人员资质", |
| | | "serviceReviewPolicyApplication": "保单申请" |
| | | }, |
| | | "transaction": { |
| | | "export": "导出订单", |
| | |
| | | "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": "头" |
| | | } |
| | | } |
| | | } |
| | |
| | | // display: none; |
| | | // } |
| | | // } |
| | | |
| | | .n-input--textarea { |
| | | .n-input-word-count { |
| | | font-size: 12px; |
| | | color: rgba(23, 31, 38, 0.3); |
| | | line-height: 18px; |
| | | } |
| | | } |
| | |
| | | <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> |
| | |
| | | |
| | | <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> |
| | |
| | | <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, |
| | |
| | | } 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: "", |
| | |
| | | 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", |
| | |
| | | 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 |
| | |
| | | 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; |
| | |
| | | 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( |
| | |
| | | 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" |
| | |
| | | type="primary" |
| | | onClick={() => handleApprove(row.id)} |
| | | > |
| | | 审核 |
| | | {t("serviceReview.personnel.audit")} |
| | | </NButton> |
| | | ) : null} |
| | | <NButton |
| | |
| | | type="error" |
| | | onClick={() => handleDelete(row.id)} |
| | | > |
| | | 删除 |
| | | {t("serviceReview.common.delete")} |
| | | </NButton> |
| | | </div> |
| | | </NSpace> |
| | | ); |
| | | }, |
| | | }, |
| | |
| | | |
| | | const handleBatchReview = () => { |
| | | if (selectedIds.value.length === 0) { |
| | | window.$message?.warning("请先选择要审核的人员"); |
| | | window.$message?.warning(t("serviceReview.common.selectPersonnelToReview")); |
| | | return; |
| | | } |
| | | showReviewModal.value = true; |
| | |
| | | |
| | | 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")); |
| | | } |
| | | }, |
| | | }); |
| | |
| | | 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")); |
| | | } |
| | | }; |
| | | |
| | |
| | | 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" |
| | |
| | | :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 |
| | |
| | | setTrue: startLoading, |
| | | setFalse: endLoading, |
| | | } = useBoolean(false); |
| | | const { t } = useI18n(); |
| | | |
| | | const initialSearchForm = { |
| | | plateNumber: "", |
| | |
| | | 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", |
| | |
| | | disabled: (row) => row.auditStatus !== 1, |
| | | }, |
| | | { |
| | | title: "序号", |
| | | title: t("serviceReview.policyApplication.index"), |
| | | key: "index", |
| | | width: 80, |
| | | align: "center", |
| | | resizable: true, |
| | | render: (_row, index) => |
| | | h( |
| | | "span", |
| | |
| | | ), |
| | | }, |
| | | { |
| | | 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, |
| | | } |
| | |
| | | }, |
| | | }, |
| | | { |
| | | 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; |
| | |
| | | }, |
| | | }, |
| | | { |
| | | title: "操作", |
| | | title: t("serviceReview.policyApplication.actions"), |
| | | key: "actions", |
| | | width: 150, |
| | | align: "center", |
| | | fixed: "right", |
| | | render: (row) => { |
| | | return ( |
| | |
| | | type="primary" |
| | | onClick={() => handleApprove(row.id)} |
| | | > |
| | | 审核 |
| | | {t("serviceReview.common.review")} |
| | | </NButton> |
| | | ) : null} |
| | | <NButton |
| | |
| | | type="error" |
| | | onClick={() => handleDelete(row.id)} |
| | | > |
| | | 删除 |
| | | {t("serviceReview.common.delete")} |
| | | </NButton> |
| | | </div> |
| | | ); |
| | |
| | | |
| | | const handleBatchReview = () => { |
| | | if (selectedIds.value.length === 0) { |
| | | window.$message?.warning("请先选择要审核的保单申请"); |
| | | window.$message?.warning(t("serviceReview.common.selectPolicyToReview")); |
| | | return; |
| | | } |
| | | showReviewModal.value = true; |
| | |
| | | |
| | | 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")); |
| | | } |
| | | }, |
| | | }); |
| | |
| | | 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")); |
| | | } |
| | | }; |
| | | |
| | |
| | | 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" |
| | |
| | | :page="pagination.page" |
| | | :page-size="pagination.pageSize" |
| | | @change="changePage" |
| | | class="robotaxi-pagination mt-12px" |
| | | /> |
| | | </NSpace> |
| | | </div> |
| | | </n-card> |
| | | |
| | | <ReviewModal |