Ver código fonte

车险出单-订单管理列表,table增加相关字段,增加订单批改逻辑;
车险出单-订单详情,增加批改审核提示,修改投保方案的数据展示,增加订单批改操作;
车险出单,增加批单记录,增加批单详情;
批单记录详情,增加审核操作;
批增驾意险弹窗组件,增加特定判断处理;

郭鹏飞 11 meses atrás
pai
commit
c8594c500c

+ 12 - 0
src/api/modules/insurancePolicy.ts

@@ -20,6 +20,18 @@ const insurancePolicyApi = (axiosInstance: ApiInstance) => ({
   deleteTaskImages: (data: string) => axiosInstance.get(`/order/taskImages/delete?id=${data}`), // 影像资料删除
   destroyQrCode: (data: any) => axiosInstance.post('/order/qrCode/destroyQrCode', data), // 撤销支付码
   getClausePage: (data: any) => axiosInstance.post('manager/clause/getClausePage', data), //特别约定列表查询
+
+  adjustAdd: (data: any) => axiosInstance.post('/adjust/adjustAdd', data), // 创建批改订单
+  adjustDel: (data: any) => axiosInstance.delete(`/adjust/adjustDel?id=${data}`), // 删除批改订单
+  adjustRemove: (data: any) => axiosInstance.delete(`/adjust/adjustRemove?orderNo=${data}`), // 取消批改订单
+  changeSubmit: (data: any) => axiosInstance.post(`/adjust/changeSubmit`, data), // 批改提交
+  refundSubmit: (data: any) => axiosInstance.post(`/adjust/refundSubmit`, data), // 整单退提交
+  getBusinessInsuranceByKindType: (data: any) => axiosInstance.get('/scheme/getBusinessInsuranceByKindType', { params: data }), // 获取对应保险的保额
+  adjustList: (data: any) => axiosInstance.post(`/adjust/adjustList`, data), // 订单批改列表分页
+  adjustGetById: (data: any) => axiosInstance.get(`/adjust/adjustGet`, { params: data }), // 订单批改详情
+  exportAdjustList: (data: any) => axiosInstance.post(`/adjust/exportAdjustList`, data, {responseType: 'blob'}), // 订单批改导出
+  verifyOrder: (data: any) => axiosInstance.post(`/adjust/verifyOrder`, data), // 审核批改订单
+
 })
 
 export default insurancePolicyApi

BIN
src/assets/images/insurancePolicy/tips_tail.png


+ 51 - 9
src/components/SearchForm/index.vue

@@ -1,12 +1,12 @@
 <template>
   <ElForm :inline="false" :model="formInline" class="demo-form-inline" label-width="100px">
     <ElRow :gutter="20">
-      <ElCol v-for="(field, index) in fields" :key="index" :md="8">
+      <ElCol v-for="(field, index) in fields" :key="index" :md="span">
         <ElFormItem :label="field.label">
           <component v-if="field.type === 'input'" is="el-input" v-model="formInline[field.model]"
             :placeholder="field.placeholder" clearable class="el-inputs" />
           <component v-else-if="field.type === 'select'" is="el-select" v-model="formInline[field.model]"
-            :placeholder="field.placeholder" clearable class="el-inputs">
+            :placeholder="field.placeholder" clearable class="el-inputs" v-bind="field">
             <el-option v-for="(item, index) in field.options" :key="index" :label="item.label" :value="item.value" />
           </component>
           <component v-else-if="field.type === 'cascader'" is="el-cascader" v-model="formInline[field.model]"
@@ -18,7 +18,7 @@
             :end-placeholder="field.endplaceholder" clearable @change="field.onChange" />
         </ElFormItem>
       </ElCol>
-      <ElCol :md="4" >
+      <ElCol :md="isOffset ? span : span / 2" :offset="btnOffset">
         <ElFormItem>
           <div class="flex j-end" style="width: 100%;">
             <el-button type="primary" @click="handleSearch">
@@ -36,9 +36,7 @@
 </template>
 
 <script lang="ts" setup>
-import { defineProps } from "vue";
-
-const props = defineProps<{
+const props = withDefaults(defineProps<{
   fields: Array<{
     label: string; // 字段标签
     model: string; // v-model 绑定的字段名
@@ -53,14 +51,58 @@ const props = defineProps<{
   formInline: any; // 表单数据
   onSearch: () => void; // 查询事件
   onReset: () => void; // 重置事件
-}>();
+  span?: number;
+  isOffset?: boolean; // 是否正规化按钮位置,而不是紧贴formItem后面
+}>(), {
+  span: 8,
+  isOffset: false,
+});
+const windowWidth = ref(window.innerWidth);
+
 onBeforeMount(() => {
   console.log(props)
-})
+});
+
+onMounted(() => {
+  window.addEventListener('resize', updateWindowWidth)
+});
+
+onUnmounted(() => {
+  window.removeEventListener('resize', updateWindowWidth)
+});
+
+// 定义更新窗口宽度的函数
+const updateWindowWidth = () => {
+  windowWidth.value = window.innerWidth;
+};
+
+// 计算按钮组的左侧偏移量
+const btnOffset = computed(() => {
+  // 为了保持原有页面的样子
+  if (!props.isOffset) return 0;
+  // 因为栅格布局,代码使用的md,所以监听宽度
+  if (windowWidth.value < 992) {
+    return 0;
+  }
+  // 计算逻辑
+	const items = props.fields.length;
+	const columns = 24 / props.span; // 列布局
+	const spanPerItem = props.span; // 每列的span
+	const itemsOnLastRow = items % columns;
+
+	if (itemsOnLastRow === 0) {
+		// 如果刚好占满最后一行,按钮就需要另起一行,并偏移3个位置
+		return (columns - 1) * spanPerItem; 
+	} else {
+		// 如果最后一行没占满,计算需要空出的位置数量
+		const emptySlots = columns - 1 - itemsOnLastRow;
+		return emptySlots * spanPerItem;
+	}
+});
+
 const handleSearch = () => {
   props.onSearch(); // 调用父组件的查询事件
 };
-
 const handleReset = () => {
   props.onReset(); // 调用父组件的重置事件
 };

+ 9 - 3
src/layouts/components/DrivingType/index.vue

@@ -152,7 +152,8 @@ const handleDel = (item, index) => {
 
 // 编辑
 let editIndex = ref(0)
-const initEditData = (data, index, id) => {
+// isCode为新增加参数,为的是不破坏原有逻辑
+const initEditData = (data, index, id, isCode = false) => {
   console.log('驾意险回显', data)
   editIndex.value = index
   // JYXList.value.forEach(item => {
@@ -179,8 +180,13 @@ const initEditData = (data, index, id) => {
         //   return obj || a
         // })
         // selectedList.value = JYXList.value.filter(a => a.checked)
-        selectedList.value = JSON.parse(JSON.stringify(data))
-        selectedKeys.value = selectedList.value.map(a => a.drivingIntentionId)
+        if(!isCode) {
+          selectedList.value = JSON.parse(JSON.stringify(data))
+          selectedKeys.value = selectedList.value.map(a => a.drivingIntentionId)
+        } else {
+          selectedList.value = res.data.filter((item: any) => data.indexOf(item.productCode) !== -1).map((item: any) => item);
+          selectedKeys.value = res.data.filter((item: any) => data.indexOf(item.productCode) !== -1).map((item: any) => item.id);
+        }
       }
     } else {
       ElMessage({

+ 20 - 0
src/router/modules/insurancePolicy.ts

@@ -62,6 +62,26 @@ const routes: RouteRecordRaw = {
           },
         },
       ]
+    },
+    {
+      path: 'record',
+      name: 'InsurancePolicyRecord',
+      component: () => import('@/views/insurancePolicy/record.vue'),
+      meta: {
+        title: '批改记录',
+      },
+      children: [
+        {
+          path: 'detail',
+          name: 'InsurancePolicyRecordDetail',
+          component: () => import('@/views/insurancePolicy/recordDetail.vue'),
+          meta: {
+            title: '记录详情',
+            menu: false,
+            activeMenu: '/insurancePolicy/record',
+          },
+        }
+      ]
     }
   ],
 }

+ 63 - 0
src/utils/composables/download.ts

@@ -0,0 +1,63 @@
+/**
+ * 根据后台接口文件流进行下载
+ * @param {*} data
+ * @param {*} filename
+ * @param {*} mime
+ * @param {*} bom 标识文件的字符编码
+ */
+export function downloadByData(data: BlobPart, filename: string, mime?: string, bom?: BlobPart) {
+  const blobData = typeof bom !== 'undefined' ? [bom, data] : [data];
+  const blob = new Blob(blobData, { type: mime || 'application/octet-stream' });
+  if (typeof window.navigator.msSaveBlob !== 'undefined') {
+    window.navigator.msSaveBlob(blob, filename);
+  } else {
+    const blobURL = window.URL.createObjectURL(blob);
+    const tempLink = document.createElement('a');
+    tempLink.style.display = 'none';
+    tempLink.href = blobURL;
+    tempLink.setAttribute('download', filename);
+    if (typeof tempLink.download === 'undefined') {
+      tempLink.setAttribute('target', '_blank');
+    }
+    document.body.appendChild(tempLink);
+    tempLink.click();
+    document.body.removeChild(tempLink);
+    window.URL.revokeObjectURL(blobURL);
+  }
+}
+
+/**
+ * 根据文件地址下载文件
+ * @param {*} sUrl
+ */
+export function downloadByUrl({ url, target = '_blank', fileName }: { url: string; target?: string; fileName?: string }): boolean {
+  const isChrome = window.navigator.userAgent.toLowerCase().indexOf('chrome') > -1;
+  const isSafari = window.navigator.userAgent.toLowerCase().indexOf('safari') > -1;
+
+  if (/(iP)/g.test(window.navigator.userAgent)) {
+    console.error('Your browser does not support download!');
+    return false;
+  }
+  if (isChrome || isSafari) {
+    const link = document.createElement('a');
+    link.href = url;
+    link.target = target;
+
+    if (link.download !== undefined) {
+      link.download = fileName || url.substring(url.lastIndexOf('/') + 1, url.length);
+    }
+
+    if (document.createEvent) {
+      const e = document.createEvent('MouseEvents');
+      e.initEvent('click', true, true);
+      link.dispatchEvent(e);
+      return true;
+    }
+  }
+  if (url.indexOf('?') === -1) {
+    url += '?download';
+  }
+
+  window.open(url, target);
+  return true;
+}

+ 255 - 0
src/views/insurancePolicy/DialogInsuranceCancel.vue

@@ -0,0 +1,255 @@
+<template>
+	<el-dialog title="整单退保" width="1000" v-model="isDialogVisible" @close="handleClose">
+		<div class="">
+			<div class="mb-4">
+				<div class="text-[#333333] text-base font-medium">车辆{{ props.data.licenseNo }}整单退保</div>
+				<div class="text-[#666666]">退保后,将重新计算该保单的应收,应付金额。</div>
+			</div>
+			<el-table :data="policyItems" border stripe 
+				header-cell-class-name="!bg-gray-100 !text-gray-600"
+				@selection-change="handleSelectionChange">
+				<el-table-column type="selection" width="55" align="center" />
+				<el-table-column prop="kindName" label="保险" />
+				<el-table-column prop="policyNumber" label="保单号" width="220" />
+				<el-table-column prop="expiryDate" label="终保日期" width="180" />
+				<el-table-column prop="premium" label="保费" width="120">
+					<template #default="{ row }">
+						¥ {{ row.premium }}
+					</template>
+				</el-table-column>
+				<el-table-column label="退款金额" width="250">
+					<template #default="{ row, $index }">
+						<el-input v-model="row.refundAmount"
+							type="number"
+							placeholder="选中后可输入" 
+							:disabled="!isRowSelected(row)"
+							:style="[
+								row.errors?.refundAmount ? {
+									'--el-border-color': '#F56C6C',
+									'--el-border-color-hover': '#F56C6C',
+									'--el-color-primary': '#F56C6C'
+								} : {}
+							]"
+							@blur="currentIndex = undefined"
+							@focus="currentIndex = $index"
+							@input="validateInput(row)" 
+						/>
+
+						<!-- 错误提示 -->
+						<div v-if="row.errors?.refundAmount && currentIndex === $index" class="error-message">
+							{{ row.errors.refundAmount }}
+						</div>
+					</template>
+				</el-table-column>
+			</el-table>
+		</div>
+
+		<template #footer>
+			<div class="px-1">
+				<el-button @click="isDialogVisible = false">取 消</el-button>
+				<el-button type="primary" @click="handleSubmit">提交退保申请</el-button>
+			</div>
+		</template>
+	</el-dialog>
+</template>
+
+<script setup lang="ts">
+import api from '@/api';
+import { ElMessage } from 'element-plus';
+
+// 组件属性与事件
+const props = defineProps({
+	data: {
+		type: Object,
+		default: {}
+	}
+});
+const isDialogVisible = defineModel({ 
+	default: false 
+});
+const emits = defineEmits(['complete']);
+
+const isSubmit = ref(false);
+watch(isDialogVisible, (newVal) => {
+  	if (newVal) isSubmit.value = false;
+});
+
+// 表格的原始数据
+const policyItems = ref<any>([]);
+watch(() => props.data.tableList, (val) => {
+	policyItems.value = val;
+}, {
+	immediate: false,
+});
+
+// 当前操作行的索引
+const currentIndex = ref();
+// 存储当前所有被选中的行的数据
+interface TableRow {
+	[key: string]: any
+}
+const selectedRows = ref<TableRow[]>([]);
+
+// 关闭弹窗
+const handleClose = () => {
+	isDialogVisible.value = false;
+	clearData();
+	if (!isSubmit.value) {
+		api.insurancePolicyApi.adjustRemove(props.data.orderNo);
+	}
+};
+// 清空数据,以防残留数据
+const clearData = () => {
+	policyItems.value = [];
+};
+
+// selection参数是所有被选中行的数据组成的数组
+const handleSelectionChange = (selection: any) => {
+	selectedRows.value = selection;
+	// 当某一行被取消选中时,清空其退款金额
+	policyItems.value.forEach((item: any) => {
+		if (!selection.some((selected: any) => selected.id === item.id)) {
+			item.refundAmount = null;
+		}
+	});
+};
+
+// 辅助函数:判断某一行是否被选中
+const isRowSelected = (row: any): boolean => {
+	return selectedRows.value.some((item: any) => item.id === row.id);
+};
+
+// 校验规则
+const validateRules = {
+	refundAmount: (value: any, compare: any) => {
+		if (value > compare) {
+			return '退款金额不能大于保费';
+		}
+		if (value < 0 || value > 99999.99) {
+			return '请输入0-99999.99之间的数字';
+		} else {
+			// 检查小数位数是否超过2位
+			const decimalPart = value.toString().split('.')[1];
+			if (decimalPart && decimalPart.length > 2) {
+				return '最多支持两位小数';
+			}
+		}
+		return ''; // 校验通过
+	}
+}
+
+// 输入时实时校验格式
+const validateInput = (row: any) => {
+	if (row.refundAmount === null || row.refundAmount === '') return;
+	const field = 'refundAmount';
+	const errorMsg = validateRules[field](row[field], row.premium);
+	row.errors = {
+		[field]: errorMsg
+	};
+	// // 清除非数字和非小数点的字符,但保留第一个小数点
+	// let value = row.refundAmount.replace(/[^\d.]/g, '');
+	// const parts = value.split('.');
+	// if (parts.length > 2) {
+	// 	value = parts[0] + '.' + parts.slice(1).join('');
+	// }
+
+	// // 限制小数位数
+	// if (parts[1] && parts[1].length > 2) {
+	// 	parts[1] = parts[1].substring(0, 2);
+	// 	value = parts.join('.');
+	// }
+
+	// // 限制整数位数
+	// if (parts[0].length > 5) {
+	// 	parts[0] = parts[0].substring(0, 5);
+	// 	value = parts.join('.');
+	// }
+
+	// row.refundAmount = value;
+};
+
+// 提交退保申请
+const handleSubmit = () => {
+	// 检查是否至少选择了一项
+	if (selectedRows.value.length === 0) {
+		ElMessage.warning('请至少选择一个需要退保的险种。');
+		return;
+	}
+
+	// 遍历所有选中的行进行校验
+	for (const row of selectedRows.value) {
+		const refundAmountStr = row.refundAmount;
+
+		// 1. 校验是否为空
+		if (refundAmountStr === null || refundAmountStr.trim() === '') {
+			ElMessage.error(`保险“${row.kindName}”:请输入退款金额`);
+			return; // 中断提交
+		}
+
+		// 2. 校验格式:必须是数字,最多5位整数,2位小数
+		const regex = /^\d{1,5}(\.\d{1,2})?$/;
+		if (!regex.test(refundAmountStr)) {
+			ElMessage.error(`保险“${row.kindName}”:请输入0-99999.99之间的数字(仅支持2位小数)`);
+			return; // 中断提交
+		}
+
+		const refundAmountNum = parseFloat(refundAmountStr);
+
+		// 3. 校验退款金额是否大于保费
+		if (refundAmountNum > row.premium) {
+			ElMessage.error(`保险“${row.kindName}”:退款金额不能大于保费(¥ ${row.premium})`);
+			return; // 中断提交
+		}
+	}
+	// 提交数据
+	let params = {
+		id: props.data.adjustId,
+		orderNo: props.data.orderNo,
+		infoVosList: selectedRows.value.map((item: any) => {
+			let { errors, ...datas } = item;
+			return {
+				...datas,
+				// 退款金额
+				refundAmount: item.refundAmount - 0,
+			}
+		})
+	};
+	console.log('params:', params);
+	console.log('校验通过,准备提交的数据:', selectedRows.value);
+	api.insurancePolicyApi.refundSubmit(params).then((res: any) => {
+		if (res.code === 200) {
+			ElMessage.success(res.msg || '退保申请已提交!');	
+			// 关闭弹窗
+			isSubmit.value = true;
+			isDialogVisible.value = false;
+			// emits
+			emits('complete');
+		}
+	});
+};
+
+onUnmounted(() => {
+	if (isDialogVisible.value) {
+		console.log('处理页面刷新时,单子没关闭')
+		clearData();
+		api.insurancePolicyApi.adjustRemove(props.data.orderNo);
+	}
+});
+</script>
+
+<style scoped lang="scss">
+.error-message {
+	width: fit-content;
+	font-size: 12px;
+    color: #fff;
+    padding: 3px 6px;
+    text-align: justify;
+    line-height: 14px;
+    border-radius: 2px;
+    position: absolute;
+    background: #F56C6C;
+    left: 12px;
+    right: 12px;
+    bottom: calc(100% - 8px);
+}
+</style>

+ 671 - 0
src/views/insurancePolicy/DialogInsuranceEdit.vue

@@ -0,0 +1,671 @@
+<template>
+	<el-dialog title="险种批改" width="1000" v-model="isDialogVisible" @close="handleClose">
+		<div class="">
+			<div class="">
+				<div class="text-[#333333] text-base font-medium">车辆{{ props.data.licenseNo }}险种批改</div>
+				<div class="text-[#666666]">支持增加或减少险种以及调整险种保额</div>
+			</div>
+			<el-divider />
+			<div class="">
+				<div class="flex items-center pb-[8px] pr-[24px]">
+					<div class="text-[#333333] font-medium mr-[34px]">保险产品</div>
+					<el-button link type="primary" class="p-1" 
+						v-if="props.isAdd"
+						@click="handleAddInsurance('main')">
+						+ 批增险种
+					</el-button>
+				</div>
+				<div class="flex-1">
+					<el-table class="w-full" :data="mainInsuranceProducts" border stripe 
+						max-height="200" header-cell-class-name="!bg-gray-100 !text-gray-600">
+						<el-table-column prop="kindName" label="险种" width="250" />
+						<el-table-column prop="amount" label="保额" width="150">
+							<template #default="{ row }">
+								<el-select v-model="row.amount" disabled placeholder="请选择保额" class="w-full"
+									:style="{
+										'--el-select-disabled-color': row.type !== 1 ? '#a8abb2' : '#F56C6C' 
+									}">
+									<el-option :label="row.amount" :value="row.amount" />
+								</el-select>
+							</template>
+						</el-table-column>
+						<el-table-column prop="premium" label="保费">
+							<template #default="{ row }">
+								<span>¥ {{ row.premium }}</span>
+							</template>
+						</el-table-column>
+						<el-table-column prop="type" label="批改信息">
+							<template #default="{ row }">
+								<span>{{ getStateText('type', row) }}</span>
+							</template>
+						</el-table-column>
+						<el-table-column label="操作" width="120">
+							<template #default="{ row, $index }">
+								<el-button type="primary" link 
+									v-if="row.type !== 3" 
+									@click="openFormDialog('main', row)"
+								>批改</el-button>
+								<el-popconfirm width="170" title="确定删除此险种吗?" 
+									confirm-button-text="确定" cancel-button-text="取消"
+									v-if="row.type === 3"
+									@confirm="handleDelete('main', $index)">
+									<template #reference>
+										<el-button type="danger" link>删除</el-button>
+									</template>
+								</el-popconfirm>
+							</template>
+						</el-table-column>
+						<template #empty>
+							<div class="py-4 text-gray-500">没有可批改的保险产品~</div>
+						</template>
+					</el-table>
+				</div>
+			</div>
+
+			<div class="mt-6">
+				<div class="flex items-center pb-[8px] pr-[24px]">
+					<div class="text-[#333333] font-medium mr-[20px]">驾意险产品</div>
+					<el-button link type="primary" class="p-1" 
+						v-if="props.isAdd"
+						@click="handleAddInsurance('driver')">
+						+ 批增驾意险
+					</el-button>
+				</div>
+				<div class="flex-1">
+					<el-table class="w-full" :data="driverAccidentProducts" border 
+						max-height="200" header-cell-class-name="!bg-gray-100 !text-gray-600">
+						<el-table-column prop="kindName" label="产品名称" width="250" />
+						<el-table-column prop="amount" label="份数" width="150">
+							<template #default="{ row }">
+								<el-input-number :disabled="row.type !== 3" 
+									class="w-full" controls-position="right"
+									v-model="row.amount" :min="1">
+									<template #suffix>
+										<span>份</span>
+									</template>
+								</el-input-number>
+							</template>
+						</el-table-column>
+						<el-table-column prop="premium" label="保费">
+							<template #default="{ row }">
+								<span>¥ {{ row.premium }}</span>
+							</template>
+						</el-table-column>
+						<el-table-column prop="endorsementInfo" label="批改信息">
+							<template #default="{ row }">
+								<span>{{ getStateText('type', row) }}</span>
+							</template>
+						</el-table-column>
+						<el-table-column label="操作" width="120">
+							<template #default="{ row, $index }">
+								<el-button type="primary" link 
+									v-if="row.type !== 3" 
+									@click="openFormDialog('driver', row)"
+								>批改</el-button>
+								<el-popconfirm title="确定删除此产品吗?" 
+									confirm-button-text="确定" cancel-button-text="取消"
+									v-if="row.type === 3"
+									@confirm="handleDelete('driver', $index)">
+									<template #reference>
+										<el-button type="danger" link>删除</el-button>
+									</template>
+								</el-popconfirm>
+							</template>
+						</el-table-column>
+						<template #empty>
+							<div class="py-4 text-gray-500">没有可批改的驾意险产品~</div>
+						</template>
+					</el-table>
+				</div>
+			</div>
+		</div>
+
+		<template #footer>
+			<div class="px-1">
+				<el-button @click="isDialogVisible = false">取 消</el-button>
+				<el-button type="primary" @click="handleSubmit">提交批改申请</el-button>
+			</div>
+		</template>
+	</el-dialog>
+	<!-- 单项批改 -->
+	<el-dialog v-model="isFormDialogVisible" width="500" :title="form.title" append-to-body>
+		<el-form ref="formRef" :model="formData" :rules="rules" label-width="120px" label-position="top">
+			<el-form-item label="保险产品" prop="kindCode">
+				<el-select v-model="formData.kindCode" placeholder="请选择" @change="handleChange">
+					<el-option v-for="(item, i) in tableData" :key="i" 
+						:value="item.kindCode" :label="`${item.kindName}/${item.amount}`"
+					></el-option>
+				</el-select>
+			</el-form-item>
+			<el-form-item label="批改类型" prop="formtype">
+				<el-select v-model="formData.formtype" placeholder="请选择" @change="handleTypeChange">
+					<el-option :value="2" label="退保"></el-option>
+					<el-option :value="1" label="保额批改" :disabled="formData.state === 0"></el-option>
+				</el-select>
+			</el-form-item>
+
+			<template v-if="formData.formtype === 1 && formData.kindType !== 2">
+				<el-form-item label="保额" prop="afterAmount">
+					<el-select v-model="formData.afterAmount" placeholder="请选择保额" class="w-full">
+						<el-option 
+							v-for="item in insuranceData" :key="item.value" 
+							:label="item.label" :value="item.value" 
+							:disabled="item.value === formData.amount"
+						/>
+					</el-select>
+				</el-form-item>
+			</template>
+			<template v-if="formData.kindType === 2">
+				<el-form-item label="份数" prop="quantity">
+					<el-input v-model="formData.quantity" type="number" placeholder="请输入份数" />
+				</el-form-item>
+			</template>
+
+			<template v-if="getItemShow(formData, 'tu')">
+				<el-form-item label="退款金额(元)" prop="tmoney">
+					<el-input v-model="formData.tmoney" type="number" placeholder="请输入金额" />
+				</el-form-item>
+			</template>
+			<template v-if="getItemShow(formData, 'bu')">
+				<el-form-item label="补缴保费(元)" prop="tmoney">
+					<el-input v-model="formData.tmoney" type="number" placeholder="请输入金额" />
+				</el-form-item>
+			</template>
+		</el-form>
+		<template #footer>
+			<el-button @click="isFormDialogVisible = false">取 消</el-button>
+			<el-button type="primary" @click="handleFormSubmit">确 定</el-button>
+		</template>
+	</el-dialog>
+	<!-- 选择保险 -->
+	<el-dialog v-model="isMainVisible" width="500" title="险种批增" append-to-body>
+		<el-form ref="formRef" :model="formData" :rules="rules" label-width="120px" label-position="top">
+			<el-form-item label="保险产品" prop="kindCode">
+				<el-cascader class="w-full" 
+					ref="cascaderRef"
+					:props="{
+						children: 'options'
+					}" 
+					:options="insuranceAllData" 
+					v-model="formData.kindCode" 
+				/>
+			</el-form-item>
+			<el-form-item label="批改类型" prop="formtype">
+				<el-select v-model="formData.formtype" disabled placeholder="请选择">
+					<el-option :value="3" label="批增"></el-option>
+				</el-select>
+			</el-form-item>
+
+			<el-form-item label="补缴保费(元)" prop="tmoney">
+				<el-input v-model="formData.tmoney" type="number" placeholder="请输入金额" />
+			</el-form-item>
+		</el-form>
+		<template #footer>
+			<el-button @click="isMainVisible = false">取 消</el-button>
+			<el-button type="primary" @click="handleAddSubmit($event, 'main')">确 定</el-button>
+		</template>
+	</el-dialog>
+	<!-- 选择驾意险 -->
+    <el-dialog
+		width="700"
+		title="选择驾意险"
+		append-to-body
+		v-model="isDrivingVisible"
+	>
+		<Driving ref="drivingTypeRef" @close="handleAddSubmit($event, 'driver')" />
+	</el-dialog>
+</template>
+
+<script setup lang="ts">
+import api from '@/api';
+import { ElMessage } from 'element-plus';
+import type { FormInstance, FormRules } from 'element-plus';
+import Driving from '@/layouts/components/DrivingType/index.vue';
+
+// 组件属性与事件
+const props = defineProps({
+	data: {
+		type: Object,
+		default: {}
+	},
+	// 是否可以批增
+	isAdd: {
+		type: Boolean,
+		default: true
+	}
+});
+const isDialogVisible = defineModel({ 
+	default: false 
+});
+const emits = defineEmits(['complete']);
+
+// 数据
+const isSubmit = ref(false);
+const tableData = ref<any>([]);
+const insuranceData = ref<any>([]);
+const insuranceAllData = ref<any>([]);
+const mainInsuranceProducts = ref<any>([]);
+const driverAccidentProducts = ref<any>([]);
+
+watch(isDialogVisible, (newVal) => {
+  	if (newVal) isSubmit.value = false;
+});
+watch(() => props.data.tableList, (val) => {
+	// table数据监听
+	const a = val.filter((item: any) => item.kindType === 0 || item.kindType === 1).map((item: any) => {
+		// 对筛选后的元素进行处理,返回新对象
+		return {
+			...item,
+			amount: !item.amount ? '投保' : item.amount,
+			// state: 0 退保 
+			state: item.kindType === 0 || (item.kindCode === 'A' || item.kindCode === 'SY_WBDWGZ') ? 0 : 1
+		}
+	});
+	const b = val.filter((item: any) => item.kindType === 2);
+	mainInsuranceProducts.value = [...a];
+	driverAccidentProducts.value = [...b];
+	tableData.value = [...a, ...b];
+	console.log(tableData.value)
+}, {
+	immediate: false,
+});
+
+// 表单弹窗
+const isFormDialogVisible = ref(false);
+const form = reactive({
+	type: 'main', // 'main' or 'driver'
+	isEditing: false,
+	title: '',
+	editingId: null as number | null, 
+});
+const formData = ref<any>({}); 
+const formRef = ref<FormInstance>();
+const cascaderRef = ref();
+const rules = reactive<FormRules>({
+	kindCode: [
+		{
+			required: true,
+			message: '请选择保险产品',
+			trigger: 'change',
+		},
+	],
+	formtype: [
+		{
+			required: true,
+			message: '请选择批改类型',
+			trigger: 'change',
+		},
+	],
+	afterAmount: [
+		{
+			required: true,
+			message: '请选择保额',
+			trigger: 'change',
+		},
+	],
+	quantity: [
+		{ 
+			required: true, 
+			message: '请输入1-99之间的整数', 
+			trigger: 'blur' 
+		},
+		{
+			pattern: /^([1-9]\d?|99)$/, 
+			message: '请输入1-99之间的整数',
+			trigger: ['blur', 'change']
+		}
+	],
+	premium: [
+		{ 
+			required: true, 
+			message: '请输入0-999999.99之间的数字(支持两位小数)', 
+			trigger: 'blur' 
+		},
+		{
+			validator: (rule, value, callback) => {
+				// 检查是否在0-99999.99之间
+				if (value < 0 || value > 99999.99) {
+					callback(new Error('请输入0-99999.99之间的数字'));
+				} else {
+					// 检查小数位数是否超过2位
+					const decimalPart = value.toString().split('.')[1];
+					if (decimalPart && decimalPart.length > 2) {
+						callback(new Error('最多支持两位小数'));
+					} else {
+						callback();
+					}
+				}
+			},
+			trigger: ['blur', 'change']
+		}
+	],
+	tmoney: [
+		{ 
+			required: true, 
+			message: '请输入0-999999.99之间的数字(支持两位小数)', 
+			trigger: 'blur' 
+		},
+		{
+			validator: (rule, value, callback) => {
+				// 检查是否在0-99999.99之间
+				if (value < 0 || value > 99999.99) {
+					callback(new Error('请输入0-99999.99之间的数字'));
+				} else {
+					// 检查小数位数是否超过2位
+					const decimalPart = value.toString().split('.')[1];
+					if (decimalPart && decimalPart.length > 2) {
+						callback(new Error('最多支持两位小数'));
+					} else {
+						callback();
+					}
+				}
+			},
+			trigger: ['blur', 'change']
+		}
+	],
+});
+
+// 批改弹窗的部分item判断控制
+const getItemShow = (data: any, key: string) => {
+	let flag = false;
+	let feild = data.kindType === 2 ? 'quantity' : 'afterAmount';
+	if (key === 'tu') {
+		flag = data.formtype === 2 || (data.formtype === 1 && (data[feild] && data.amount > data[feild]));
+	}
+	if (key === 'bu') {
+		flag = data.formtype === 1 && data.amount < data[feild];
+	}
+	return flag;
+};
+
+// 关闭弹窗
+const handleClose = () => {
+	isDialogVisible.value = false;
+	clearData();
+	if (!isSubmit.value) {
+		api.insurancePolicyApi.adjustRemove(props.data.orderNo);
+	}
+};
+// 清空数据,以防残留数据
+const clearData = () => {
+	mainInsuranceProducts.value = [];
+	driverAccidentProducts.value = [];
+};
+
+// 批增操作
+const isMainVisible = ref(false);
+const isDrivingVisible = ref(false);
+const drivingTypeRef: any = ref(null);
+const handleAddInsurance = (type: 'main' | 'driver', item?: any) => {
+	if (type === 'main') {
+		api.projectApi.getBusinessInsurance().then((res: any) => {
+			if(res.code == 200) {
+				let arr = mainInsuranceProducts.value.map((item: any) => item.kindCode);
+				insuranceAllData.value = res.data.map((item: any) => ({
+					...item,
+					label: item.kindName,
+					value: item.kindCode,
+					disabled: arr.indexOf(item.kindCode) !== -1
+				}));
+			}
+		});
+		formData.value.formtype = 3;
+		isMainVisible.value = true;
+	}
+	if (type === 'driver') {
+		isDrivingVisible.value = true;
+		nextTick(()=>{
+			let arr = driverAccidentProducts.value.map((item: any) => item.kindCode);
+			// 参数:当前值, 索引, props.data.companyId
+			drivingTypeRef.value.initEditData(arr, 0, '1738811735599347566', true);
+		});
+	}
+};
+const handleAddSubmit = async (e: any, type: 'main' | 'driver') => {
+	if (type === 'main') {
+		if (!formRef.value) return
+		await formRef.value.validate((valid) => {
+			if (valid) {
+				// 获取选中的节点信息(包含label)
+				const checkedNodes = cascaderRef.value.getCheckedNodes();
+				// push数据
+				mainInsuranceProducts.value.push({
+					id: Date.now(), 
+					kindCode: formData.value.kindCode[0],
+					kindName: checkedNodes[0].parent.label,
+					// 险种类型 0.交强 1.商业 2.非车
+					kindType: formData.value.kindCode[0] === 'J1' ? 0 : 1,
+					amount: formData.value.kindCode[1],
+					premium: formData.value.tmoney,
+					type: 3,
+				});
+				isMainVisible.value = false;
+			}
+		});
+	}
+	
+	if (type === 'driver') {
+		console.log('add', e);
+		const data = e.list;
+		if (data.length > 0) {
+			let arr = driverAccidentProducts.value.map((item: any) => item.kindCode);
+			let newData = data.filter((item: any) => arr.indexOf(item.productCode) === -1);
+			newData.map((item: any) => {
+				driverAccidentProducts.value.push({
+					id: item.id, 
+					kindCode: item.productCode,
+					kindName: item.productName,
+					// 险种类型 0.交强 1.商业 2.非车
+					kindType: 2,
+					amount: 1,
+					premium: item.premium - 0,
+					type: 3,
+				});
+			});
+		}
+		isDrivingVisible.value = false;
+	}
+};
+
+// 获取state文本
+const getStateText = (field: string, item?: any) => {
+	let text = '--';
+	// 批改信息
+	if (field === 'type') {
+		if (item[field] === 1) {
+			text = '保额批改';
+		} else if (item[field] === 2) {
+			text = '退保';
+		} else if (item[field] === 3) {
+			text = '批增';
+		}
+	}
+	return text;
+};
+// 批改操作
+const openFormDialog = (type: 'main' | 'driver', item?: any) => {
+	form.type = type;
+	if (item) {
+		form.isEditing = true;
+		form.editingId = item.kindCode;
+		form.title = type === 'main' ? '批改险种' : '批改驾意险';
+		formData.value = { 
+			...item,
+		}; 
+		if (item.kindType === 2) {
+			formData.value.quantity = item.amount;
+		}
+		console.log(item, formData.value)
+	}
+	isFormDialogVisible.value = true;
+};
+const handleChange = (e: any) => {
+	// 重置数据
+	formRef.value && formRef.value.resetFields();
+	const selectedItem = mainInsuranceProducts.value.find((option: any) => option.kindCode === e);
+	console.log(e, selectedItem)
+	formData.value = {
+		...formData.value,
+		...selectedItem,
+		quantity: '',
+		afterAmount: ''
+	};
+	insuranceData.value = [];
+	// 保险产品不支持「保额批改」1,则默认为「退保」2
+	if (selectedItem?.state === 0) {
+		formData.value.formtype = 2;
+	}
+};
+const handleTypeChange = (e: any) => {
+	if (e === 1 && formData.value.kindType !== 2) {
+		getInsuranceData(formData.value)
+	}
+};
+// 获取保额数据
+const getInsuranceData = (item: any) => {
+	api.insurancePolicyApi.getBusinessInsuranceByKindType({
+		kindCode: item.kindCode
+	}).then((res: any) => {
+		if (res.code === 200) {
+			insuranceData.value = res.data[0].options;
+		}
+	});
+};
+// 批改的提交逻辑
+const handleFormSubmit = async () => {
+	if (!formRef.value) return
+	await formRef.value.validate((valid, fields) => {
+		if (valid) {
+			console.log('submit!')
+			const data = formData.value;
+			if (form.isEditing) {
+				// 更新逻辑
+				const targetArray = form.type === 'main' ? mainInsuranceProducts.value : driverAccidentProducts.value;
+				const itemIndex = targetArray.findIndex((p: any) => p.kindCode === form.editingId);
+				if (itemIndex !== -1) {
+					targetArray[itemIndex] = { 
+						...targetArray[itemIndex], 
+						...data, 
+						type: data.formtype
+					};
+				}
+				console.log(targetArray[itemIndex])
+			}
+			isFormDialogVisible.value = false;
+			ElMessage.success(form.isEditing ? '修改成功' : '新增成功');
+			// 重置数据
+			formRef.value && formRef.value.resetFields();
+		} else {
+			console.log('error submit!', fields)
+		}
+	});
+};
+
+/**
+ * 从表格中删除一项
+ * @param type - 'main' or 'driver'
+ * @param index - 删除的项在数组中的索引
+ */
+const handleDelete = (type: 'main' | 'driver', index: number) => {
+	if (type === 'main') {
+		mainInsuranceProducts.value.splice(index, 1);
+	} else {
+		driverAccidentProducts.value.splice(index, 1);
+	}
+	ElMessage.success('删除成功');
+};
+
+// 提交所有批改申请
+const handleSubmit = () => {
+	// 检查所有险种的批改信息
+	let allData = [...mainInsuranceProducts.value, ...driverAccidentProducts.value];
+	if (allData.length === 0) {
+		ElMessage.error(`数据为空,不能提交!`);
+		return;
+	}
+
+	const endorsementValid = allData.some((item: any) => !!item.type);
+  	const index = allData.findIndex((item: any) => !item.type);
+	
+	if (!endorsementValid) {
+		ElMessage.error(`请对${allData[index].kindType === 2 ? '驾意险' : '险种'}【${allData[index].kindName}】进行批改!`);
+		return;
+	}
+
+	// table数据转化
+	let vosList = allData.map((item: any) => {
+		let adjustType = null, afterPremium = null, curItem = { ...item };
+		// 为什么用quantity, 因为输入框方式不一样
+		let feild = item.kindType === 2 ? 'quantity' : 'afterAmount';
+		// 三种批改方式的字段再计算
+		if (item.type === 2 || (item.type === 1 && (item[feild] && item.amount > item[feild]))) {
+			adjustType = 1;
+			afterPremium = item.premium - item.tmoney;
+		} else if (item.amount < item[feild] && item.type === 1) {
+			adjustType = 0;
+			afterPremium = item.premium + (item.tmoney - 0);
+		}
+		if (item.type === 3) {
+			adjustType = 0;
+			curItem.afterAmount = item.amount;
+			afterPremium = item.premium - 0;
+			curItem.tmoney = item.kindType === 2 ? item.amount * item.premium : item.premium;
+			curItem.amount = 0;
+			curItem.premium = 0;
+		}
+		if (item.type !== 3 && item.kindType === 2) {
+			curItem.afterAmount = item.quantity;
+		}
+		return {
+			adjustId: props.data.adjustId,
+			kindType: curItem.kindType,
+			kindCode: curItem.kindCode,
+			kindName: curItem.kindName,
+			// 金额or数量
+			amount: curItem.amount === '投保' ? null : curItem.amount,
+			afterAmount: curItem.afterAmount ? curItem.afterAmount - 0 : null,
+			// 保费
+			premium: curItem.premium,
+			// 批改类型 0.未批改 1.保额批改 2.退保 3.批增
+			type: curItem.type,
+			// 调整类型 0.补缴 1.退款
+			adjustType: adjustType,
+			// 调整金额
+			adjustAmount: curItem.tmoney ? curItem.tmoney - 0 : null,
+			afterPremium: afterPremium
+		}
+	});
+	// 提交数据
+	let params = {
+		id: props.data.adjustId,
+		orderNo: props.data.orderNo,
+		changeInfoVosList: [...vosList]
+	};
+	console.log('pa', params);
+	console.log('Submitting Main Insurance Products:', mainInsuranceProducts.value.map((p: any) => ({ ...p })));
+	console.log('Submitting Driver Accident Products:', driverAccidentProducts.value.map((p: any) => ({ ...p })));
+	
+	api.insurancePolicyApi.changeSubmit(params).then((res: any) => {
+		if (res.code === 200) {
+			ElMessage.success(res.msg || '批改申请已提交!');	
+			// 关闭弹窗
+			isSubmit.value = true;
+			isDialogVisible.value = false;
+			// emits
+			emits('complete');
+		}
+	});
+};
+
+onUnmounted(() => {
+	if (isDialogVisible.value) {
+		console.log('处理页面刷新时,单子没关闭')
+		clearData();
+		api.insurancePolicyApi.adjustRemove(props.data.orderNo);
+	}
+});
+</script>
+
+<style scoped lang="scss">
+
+</style>

+ 127 - 31
src/views/insurancePolicy/order.vue

@@ -24,18 +24,25 @@
             />
           </el-form-item>
         </el-col>
-        <el-col :span="8"></el-col>
+        <el-col :span="8">
+          <el-form-item label="批改状态">
+            <el-select v-model="orderForm.adjustStatus" placeholder="请选择">
+              <el-option v-for="item in dictData.ADJUST_STATUSDATA" :key="item.value" :value="item.value" :label="item.label"></el-option>
+            </el-select>
+          </el-form-item>
+        </el-col>
         <el-col :span="8"></el-col>
         <el-col :span="8" style="display: flex; justify-content: flex-end">
-          <el-button plain @click="handleCancel">取消</el-button>
-          <el-button type="primary" @click="handleSearch">搜索</el-button>
+          <el-button plain @click="handleCancel">重置</el-button>
+          <el-button type="primary" @click="handleSearch">查询</el-button>
         </el-col>
       </el-row>
     </el-form>
     <el-table
       :data="tableData"
+      v-loading="loading"
+      height="calc(100% - 180px)"
       style="margin-top: 24px; width: 100%"
-      height="640"
     >
       <el-table-column label="订单号" prop="orderNo" width="200"></el-table-column>
       <el-table-column label="保险公司" prop="companyName" width="200"></el-table-column>
@@ -48,12 +55,31 @@
       </el-table-column>
       <el-table-column label="报价时间" prop="createTime" width="200"></el-table-column>
       <el-table-column label="险种" prop="productName" width="200"></el-table-column>
-      <el-table-column label="保费计算" prop="sumPremium" width="200"></el-table-column>
+      <el-table-column label="保费计算" prop="sumPremium" width="200">
+        <template #default="scope">
+          <el-tooltip
+            effect="dark"
+            content="红色保费是订单批改后的保费"
+            placement="top"
+            :disabled="!scope.row.adjustPremium"
+          >
+            <span :style="{
+              color: !scope.row.adjustPremium ? 'inherit' : '#F56C6C'
+            }">{{ scope.row.sumPremium }}</span>
+          </el-tooltip>
+        </template>
+      </el-table-column>
+      <el-table-column label="批改状态" prop="status" width="200">
+        <template #default="scope">
+          <el-link type="primary" v-if="scope.row.adjustStatus" @click="openDetail(scope.row)">{{ getStateText(dictData.ADJUST_STATUSDATA, scope.row.adjustStatus) }}</el-link>
+          <span v-else>{{ getStateText(dictData.ADJUST_STATUSDATA, scope.row.adjustStatus) }}</span>
+        </template>
+      </el-table-column>
       <el-table-column label="业务员信息" prop="salesmanInfo" width="200"></el-table-column>
       <el-table-column label="业务员类型" prop="salesmanType" width="200">
-          <template #default="scope">
-              {{scope.row.salesmanType}}
-          </template>
+        <template #default="scope">
+          {{ scope.row.salesmanType }}
+        </template>
       </el-table-column>
       <el-table-column label="出单机构" prop="deptName" width="200"></el-table-column>
       <el-table-column label="出单协议" prop="agreementName" width="200"></el-table-column>
@@ -78,6 +104,20 @@
           <el-button link type="primary" v-if="scope.row.orderStatus === '5'">查看电子保单</el-button>
           <el-button link type="primary" v-if="scope.row.orderStatus === '4'" @click="payCode(scope)">支付码</el-button>
           <el-button link type="danger" v-if="scope.row.orderStatus === '6' || scope.row.orderStatus === '8'">删除订单</el-button>
+          <el-dropdown trigger="click" class="dropdownBtn" v-if="scope.row.orderStatus === '5'">
+            <el-button link type="primary">
+              订单批改
+              <el-icon class="el-icon--right">
+                <arrow-down />
+              </el-icon>
+            </el-button>
+            <template #dropdown>
+              <el-dropdown-menu>
+                <el-dropdown-item @click="onClick(1, scope.row)">险种批改</el-dropdown-item>
+                <el-dropdown-item @click="onClick(0, scope.row)">整单退保</el-dropdown-item>
+              </el-dropdown-menu>
+            </template>
+          </el-dropdown>
         </template>
       </el-table-column>
     </el-table>
@@ -92,6 +132,7 @@
         @current-change="handleCurrentChange"
       ></el-pagination>
     </div>
+    <!-- 支付码 -->
     <el-dialog
       v-model="payCodeShow"
       width="400"
@@ -104,45 +145,60 @@
         <el-button plain type="primary">复制链接</el-button>
       </template>
     </el-dialog>
+    <!-- 批改 -->
+    <DialogInsuranceEdit v-model="isEditVisible" :data="formData" />
+    <DialogInsuranceCancel v-model="isCancelVisible" :data="formData" />
   </div>
 </template>
 
 <script setup lang="ts">
-import api from '@/api'
+import api from '@/api';
+import DialogInsuranceEdit from './DialogInsuranceEdit.vue';
+import DialogInsuranceCancel from './DialogInsuranceCancel.vue';
+import { dictData } from '@/views/localDict/index';
 import { ElMessage } from "element-plus";
-import insurancePolicyApi from "@/api/modules/insurancePolicy.ts";
-
-import { useRouter, useRoute } from 'vue-router'
+import { useRouter } from 'vue-router';
 
 const router = useRouter()
-const route = useRoute()
+const loading = ref(false)
+const formData = ref({})
+const isEditVisible = ref(false)
+const isCancelVisible = ref(false)
 
-let orderForm = ref({
-  orderNo: '',
+let initialForm = {
+  orderNo: '1966320579920617472',
   licenseNo: '',
-  range: []
-})
+  range: [],
+  adjustStatus: ''
+}
+let orderForm = ref({ ...initialForm })
 let tableData = ref([])
 // 分页
 let pages = ref(1)
 let size = ref(10)
 let total = ref(0)
-const handleSizeChange = (size) => {
-  size.value = size
+
+onMounted(() => {
+  initData()
+});
+
+const handleSizeChange = (e: number) => {
+  size.value = e
   initData()
 }
-const handleCurrentChange = (page) => {
+const handleCurrentChange = (page: number) => {
   pages.value = page
   initData()
 }
 const initData = () => {
+  loading.value = true;
+  let { range, ...params } = orderForm.value;
   api.insurancePolicyApi.queryList({
-    orderNo: orderForm.value.orderNo,
-    licenseNo: orderForm.value.licenseNo,
+    pages: pages.value,
+    size: size.value,
+    ...params,
     startDate: orderForm.value.range?.[0],
     endDate: orderForm.value.range?.[1],
-    pages: pages.value,
-    size: size.value
   }).then((res: any) => {
     if(res.code == 200) {
       tableData.value = res.data.records
@@ -153,7 +209,9 @@ const initData = () => {
         type: 'warning'
       })
     }
-  })
+  }).finally(() => {
+    loading.value = false;
+  });
 }
 
 const handleSearch = () => {
@@ -164,9 +222,7 @@ const handleSearch = () => {
 const handleCancel = () => {
   pages.value = 1
   size.value = 10
-  orderForm.value.orderNo = ''
-  orderForm.value.licenseNo = ''
-  orderForm.value.range = []
+  orderForm.value = { ...initialForm }
   initData()
 }
 // 详情
@@ -222,10 +278,42 @@ const uploadStatus = (row) => {
   })
 }
 
-onMounted(() => {
-  initData()
-})
+// 订单批改
+const onClick = (type: any, row: any) => {
+  api.insurancePolicyApi.adjustAdd({
+    type: type,
+    orderNo: row.orderNo,
+  }).then((res: any) => {
+    if(res.code == 200) {
+      formData.value = {
+        ...row,
+        adjustId: res.data.id,
+        tableList: type === 1 ? res.data.changeInfoVosList : res.data.infoVosList
+      };
+      if (type === 1) {
+        isEditVisible.value = true;
+      }
+      if (type === 0) {
+        isCancelVisible.value = true;
+      }
+    } else {
+      ElMessage({
+        message: res.msg,
+        type: 'warning'
+      })
+    }
+  });
+}
+
+const getStateText = (d: any, v: any) => {
+	let label = d.find((item: any) => item.value == v)?.label;
+	return label || '--';
+};
 
+// 批改记录
+const openDetail = (e: any) => {
+  window.open(`/#/insurancePolicy/record?orderNo=${e.orderNo}`, '_blank');
+};
 </script>
 
 <style scoped lang="scss">
@@ -246,5 +334,13 @@ onMounted(() => {
     height: 140px;
     margin: 10px auto;
   }
+  .dropdownBtn {
+    margin-left: 12px;
+    vertical-align: middle; 
+  }
+}
+:deep(.el-dialog__body) {
+	height: 500px;
+	overflow: auto;
 }
 </style>

+ 177 - 14
src/views/insurancePolicy/orderDetail.vue

@@ -11,6 +11,28 @@
                     <span class="tips">{{ detailData?.order?.orderStatus === '0'?'订单报价中,请耐心等待':detailData?.order?.orderStatus === '1'?'订单已完成报价,请立即核保!':detailData?.order?.orderStatus === '2'?'订单核保中,请耐心等待':detailData?.order?.orderStatus === '3'?'订单人工处理中,请耐心等待':detailData?.order?.orderStatus === '4'?'订单核保已通过,请尽快支付!':detailData?.order?.orderStatus === '5'?'已承保':detailData?.order?.orderStatus === '6'?'计算保费失败,请按要求修改订单':detailData?.order?.orderStatus === '7'?'核保不通过,请按意见修改订单':detailData?.order?.orderStatus === '8'?'订单已关闭,有投保需求请重新报价':'--' }}</span>
                 </el-col>
             </el-row>
+            
+            <div class="authBox" v-if="detailData?.insOrderAdjustVo && [1, 3].indexOf(detailData?.insOrderAdjustVo.status) !== -1">
+                <div class="left">
+                    <el-icon class="mr-[16px]" size="24" color="#E6A23C"><WarningFilled /></el-icon>
+                    <div class="infoBox">
+                        <!-- 批单状态 = 待审核 -->
+                        <div class="name" v-if="detailData?.insOrderAdjustVo.status === 1">{{ `订单申请${ detailData?.insOrderAdjustVo.type === 0 ? '整单退保' : '险种批改' },等待审核处理` }}</div>
+                        <!-- 批单状态 = 退回修改 -->
+                        <div class="name" v-if="detailData?.insOrderAdjustVo.status === 3">{{ `订单申请${ detailData?.insOrderAdjustVo.type === 0 ? '整单退保' : '险种批改' }被退回,等修改后重新提交审核` }}</div>
+                        <div class="desc" style="color: var(--el-text-color-regular)">
+                            {{ `申请批改${ handleCompute(detailData?.insOrderAdjustVo.afterPremium, detailData?.insOrderAdjustVo.premium) > 0 ? '补缴保费金额' : '退款金额' }:¥${Math.abs(handleCompute(detailData?.insOrderAdjustVo.afterPremium, detailData?.insOrderAdjustVo.premium))}` }}
+                            <template v-if="detailData?.insOrderAdjustVo.adjustInfoList.length > 0">
+                                {{ `(${detailData?.insOrderAdjustVo.adjustInfoList[0].createTime}由${detailData?.insOrderAdjustVo.adjustInfoList[0].createBy}申请批改)` }}
+                            </template>
+                        </div>
+                    </div>
+                </div>
+                <div class="right items-center">
+                    <el-button type="primary" @click="openDetail">详情</el-button>
+                </div>
+            </div>
+            
             <el-row :gutter="10">
                 <el-col :span="6">
                     <span class="label">业务员</span>
@@ -229,13 +251,25 @@
                         >
                             <el-table-column label="险种" prop="kindName" align="center"></el-table-column>
                             <el-table-column label="保额" align="center">
-                                <template #default="props">
-                                    <el-select v-model="props.row.amount" disabled>
-                                        <el-option v-for="item in props.row.options" :label="item.label" :value="item.value"></el-option>
-                                    </el-select>
+                                <template #default="scope">
+                                    <el-tooltip
+                                        effect="dark"
+                                        content="红色保费是订单批改后的保费"
+                                        placement="top"
+                                        :disabled="!scope.row.afterPremium"
+                                    >
+                                        <span :style="{
+                                            color: !scope.row.afterPremium ? 'inherit' : '#F56C6C'
+                                        }">{{ scope.row.amount }}</span>
+                                    </el-tooltip>
                                 </template>
                             </el-table-column>
                             <el-table-column label="保费" prop="unitAmount" align="center"></el-table-column>
+                            <el-table-column label="状态" prop="type" align="center" v-if="detailData?.insOrderAdjustVo">
+                                <template #default="scope">
+                                    <el-link type="primary" @click="openDetail">{{ getStateText('type', scope.row) }}</el-link>
+                                </template>
+                            </el-table-column>
                         </el-table>
                     </el-col>
                     <el-col :span="12">
@@ -250,15 +284,16 @@
                                 </template>
                             </el-table-column>
                             <el-table-column label="数量" align="center">
-                                <template #default="props">
-                                    <el-input disabled v-model="props.row.quantity" style="width: 140px">
-                                        <template #append>
-                                            <span>份</span>
-                                        </template>
-                                    </el-input>
+                                <template #default="scope">
+                                    <span v-if="scope.row.quantity">{{ scope.row.quantity }}份</span>
                                 </template>
                             </el-table-column>
                             <el-table-column label="保费" prop="premium" align="center"></el-table-column>
+                            <el-table-column label="状态" prop="type" align="center" v-if="detailData?.insOrderAdjustVo">
+                                <template #default="scope">
+                                    <el-link type="primary" @click="openDetail">{{ getStateText('type', scope.row) }}</el-link>
+                                </template>
+                            </el-table-column>
                         </el-table>
                     </el-col>
                 </el-row>
@@ -917,6 +952,20 @@
             <el-button type="primary" @click="handleQuote" v-if="detailData?.order?.orderStatus === '6'">计算保费</el-button>
             <el-button type="primary" plain @click="handleUnderWriting" v-if="detailData?.order?.orderStatus === '1' || detailData?.order?.orderStatus === '7'">提交核保</el-button>
             <el-button type="primary" plain v-if="detailData?.order?.orderStatus === '3' || detailData?.order?.orderStatus === '4' || detailData?.order?.orderStatus === '7'">同步订单</el-button>
+            <el-dropdown trigger="click" class="ml-[12px]" v-if="detailData?.insOrderAdjustVo.status === 2">
+                <el-button plain type="primary">
+                    订单批改
+                    <el-icon class="el-icon--right">
+                        <arrow-down />
+                    </el-icon>
+                </el-button>
+                <template #dropdown>
+                    <el-dropdown-menu>
+                        <el-dropdown-item @click="onClick(0)">险种批改</el-dropdown-item>
+                        <el-dropdown-item @click="onClick(1)">整单退保</el-dropdown-item>
+                    </el-dropdown-menu>
+                </template>
+            </el-dropdown>
         </div>
         <el-dialog
             v-model="JYXDetailShow"
@@ -939,6 +988,10 @@
         >
             <costView :agreementInfo="agreementInfo" :costitemEditInfo="costInfo"></costView>
         </el-drawer>
+
+        <!-- 批改 -->
+        <DialogInsuranceEdit v-model="isEditVisible" :data="formData" />
+        <DialogInsuranceCancel v-model="isCancelVisible" :data="formData" />
     </div>
 </template>
 
@@ -965,9 +1018,15 @@ import { useRoute, useRouter } from 'vue-router'
 import { ElMessage } from "element-plus";
 
 import QrcodeVue from 'qrcode.vue';
+import DialogInsuranceEdit from './DialogInsuranceEdit.vue';
+import DialogInsuranceCancel from './DialogInsuranceCancel.vue';
 
 const myRoute = useRoute()
 const myRouter = useRouter()
+// 订单批改
+const formData = ref({})
+const isEditVisible = ref(false)
+const isCancelVisible = ref(false)
 
 let JYXDetail = ref()
 
@@ -1036,7 +1095,8 @@ const toAgreementDetail = () => {
 
 // 驾意险闲情
 let JYXDetailShow = ref(false)
-const getJYXDetail = (row) => {
+const getJYXDetail = (row: any) => {
+    if (!row.drivingIntentionId) return;
     JYXDetailShow.value = true
     nextTick(() => {
         JYXDetail.value.initData(row.drivingIntentionId)
@@ -1535,7 +1595,7 @@ const getQuoteNumber = (licenseNo, vinNo, engineNo) => {
 }
 
 // 获取详情
-let detailData = ref(null)
+const detailData = ref<any>(null)
 const initDetail = () => {
     api.insurancePolicyApi.getOrderDetail(myRoute.query.orderNo).then((res: any) => {
         if(res.code == 200) {
@@ -1571,8 +1631,26 @@ const initDetail = () => {
                     taxList.value[3].bool = true
                     break;
             }
+            // 投保方案-左侧
             insuranceData.value = detailData.value.kindInfoVo
+            // 投保方案-右侧
             drivingData.value = detailData.value.accidentalDrivings
+            // 批改数据的判断
+            if (detailData.value.insOrderAdjustVo && detailData.value.insOrderAdjustVo.adjustInfoList.length > 0) {
+                console.log(123)
+                let adjustInfoList = detailData.value.insOrderAdjustVo.adjustInfoList;
+                insuranceData.value = adjustInfoList.filter((item: any) => item.kindType !== 2).map((item: any) => ({
+                    ...item,
+                    amount: item.afterAmount,
+                    unitAmount: item.afterPremium
+                }));
+                drivingData.value = adjustInfoList.filter((item: any) => item.kindType === 2).map((item: any) => ({
+                    ...item,
+                    premium: item.afterPremium,
+                    quantity: item.afterAmount,
+                    productName: item.kindName,
+                }));
+            }
             if(res.data.imageVoList && res.data.imageVoList.length > 0) {
                 images.value = res.data.imageVoList.map(a => {
                     return {
@@ -1730,6 +1808,60 @@ const handleUnderWriting = () => {
     })
 }
 
+// 获取计算值
+const handleCompute = (a: any, b: any) => {
+	let val = (((a * 100) - (b * 100)) / 100);
+	return val || 0;
+};
+// 获取state文本
+const getStateText = (field: string, item?: any) => {
+	let text = '--';
+	// 批改信息
+	if (field === 'type') {
+		if (item[field] === 1) {
+			text = '保额批改';
+		} else if (item[field] === 2) {
+			text = '退保';
+		} else if (item[field] === 3) {
+			text = '批增';
+		}
+	}
+	return text;
+};
+// 批改详情
+const openDetail = () => {
+    let id = detailData.value?.insOrderAdjustVo.id;
+    window.open(`/#/insurancePolicy/record/detail?id=${id}&orderNo=${myRoute.query.orderNo}`, '_blank');
+};
+// 订单批改
+const onClick = (type: any) => {
+    api.insurancePolicyApi.adjustAdd({
+        type: type,
+        orderNo: myRoute.query.orderNo,
+    }).then((res: any) => {
+        if(res.code == 200) {
+            formData.value = {
+                orderNo: myRoute.query.orderNo,
+                companyId: detailData.value?.insCompanyVo.companyId,
+                licenseNo: detailData.value?.carInfoVo?.licenseNo,
+                adjustId: res.data.id,
+                tableList: type === 1 ? res.data.changeInfoVosList : res.data.infoVosList
+            };
+            if (type === 1) {
+                isEditVisible.value = true;
+            }
+            if (type === 0) {
+                isCancelVisible.value = true;
+            }
+        } else {
+            ElMessage({
+                message: res.msg,
+                type: 'warning'
+            })
+        }
+    });
+}
+
 onMounted(() => {
     initDetail();
     initOption();
@@ -1748,6 +1880,35 @@ onMounted(() => {
         padding: 10px 0;
         background: #FFFFFF;
         border-radius: 6px;
+        .authBox {
+            margin: 0 24px 10px;
+            padding: 16px 24px;
+            background: #FFFAEF;
+            border-radius: 4px;
+            border: 1px solid #FAA21E;
+            display: flex;
+            align-items: center;
+            justify-content: space-between;
+            .flex {
+                margin: 0;
+            }
+            .left, .right {
+                display: flex;
+            }
+            .infoBox {
+                .name {
+                    font-weight: 500;
+                    font-size: 16px;
+                    color: #333333;
+                }
+                .desc {
+                    font-weight: 400;
+                    font-size: 14px;
+                    color: #333333;
+                    margin-top: 10px;
+                }
+            }
+        }
         .flex{
             display: flex;
             align-items: center;
@@ -1806,14 +1967,15 @@ onMounted(() => {
             color: #333333;
         }
         .tipsList{
+            width: 100%;
+            padding: 12px 24px;
             display: flex;
             align-items: center;
             justify-content: space-between;
-            padding: 12px 24px;
             flex-wrap: nowrap;
             overflow-x: auto;
-            width: 100%;
             .tipsItem{
+                flex: none;
                 display: flex;
                 align-items: center;
                 justify-content: center;
@@ -2079,6 +2241,7 @@ onMounted(() => {
         position: fixed;
         right: 0;
         bottom: 0;
+        z-index: 3;
         display: flex;
         align-items: center;
         justify-content: flex-end;

+ 241 - 0
src/views/insurancePolicy/record.vue

@@ -0,0 +1,241 @@
+<script setup lang="ts">
+import api from '@/api';
+import { ref, reactive } from 'vue';
+import { useRoute, useRouter } from 'vue-router';
+import { dictData } from '@/views/localDict/index';
+import { loadDicts, getDict } from '@/utils/composables/dictService';
+import { downloadByData } from '@/utils/composables/download';
+
+interface PageInfo {
+  pageNum: number;
+  pageSize: number;
+  sizes: number[];
+  total: number;
+}
+// router
+const route = useRoute();
+const router = useRouter();
+// state
+const typeData = dictData.ADJUST_TYPEDATA;
+const statusData = dictData.ADJUST_STATUSDATA;
+const companyData = ref([]);
+// table
+const tableData = ref([]);
+const pageInfo = ref<PageInfo>({
+	pageNum: 1,
+	pageSize: 10,
+	sizes: [10, 20, 30, 40, 50],
+	total: 0,
+});
+const columns = [
+	{
+		prop: "orderNo", label: "订单号", width: 200,
+	},
+	{ 
+		prop: "licenseNo", label: "车牌号", width: 100,
+	},
+	{ 
+		prop: "companyName", label: "保险公司", width: 200,
+	},
+	{
+		prop: "premium", label: "保费", width: 150,
+		formatter: (row: any) => {
+			return '¥' + row.premium
+		}
+	},
+	{
+		prop: "status", label: "批单状态", width: 150, component: "Tag",
+		formatter: (row: any) => {
+			// let name = getDict('is_enable').find(val => val.value == 1).label;
+			// 批单状态 0.订单批改中 1.待审核 2.审核通过 3.退回修改 4.拒绝批改
+			let name = statusData.find(val => val.value == row.status)?.label;
+			let type = 'primary';
+			if (row.status === 4) {
+				type = 'danger';
+			} else if (row.status === 3) {
+				type = 'warning';
+			}
+			return { type: type, text: name };
+		}
+	},
+	{ 
+		prop: "type", label: "批改类型", width: 150,
+		formatter: (row: any) => {
+			let name = typeData.find(val => val.value == row.type)?.label;
+			return name;
+		}
+	},
+	{ 
+		prop: "changePremium", label: "保费变化", width: 150,
+		formatter: (row: any) => {
+			let val = (((row.premium * 100) - (row.afterPremium * 100)) / 100);
+			return val > 0 ? `-¥${val}` : `¥${Math.abs(val)}`;
+		}
+	},
+	{ 
+		prop: "afterPremium", label: "批改后保费", width: 150,
+		formatter: (row: any) => {
+			return row.afterPremium ? `¥${row.afterPremium}` : '--';
+		}
+	},
+	{ 
+		prop: "createBy", label: "批改人", width: 150,
+	},
+	{ 
+		prop: "createTime", label: "批改时间", width: 200,
+	},
+];
+// search
+const fields = computed(() => {
+	return [
+		{
+			label: "订单号", model: "orderNo", type: "input", placeholder: "输入订单号查找",
+		},
+		{
+			label: "车牌号", model: "licenseNo", type: "input", placeholder: "输入车牌号查找",
+		},
+		{
+			label: "保险公司", model: "companyId", type: "select", filterable: true, options: companyData.value,
+		},
+		{
+			label: "业务员信息", model: "salesman", type: "input", placeholder: "输入业务员姓名、手机号查找",
+		},
+		{
+			label: "批改状态", model: "status", type: "select", options: statusData,
+		},
+		{
+			label: "批改类型", model: "type", type: "select", options: typeData,
+		},
+		{
+			label: "批改时间", model: "time", type: "date", dateType: "datetimerange", startplaceholder: "开始时间", endplaceholder: "结束时间",
+			onChange: (value: any) => {
+				console.log(value)
+				if (value) {
+					formInline.value.endDate = value[1];
+					formInline.value.startDate = value[0];
+				} else {
+					formInline.value.endDate = '';
+					formInline.value.startDate = '';
+				}
+			}
+		},
+	]
+});
+const initialForm = {
+	time: [],
+	endDate: '',
+	startDate: '',
+};
+const formInline = ref<any>({ ...initialForm });
+
+// 生命周期
+onBeforeMount(async () => {
+	// 路由参数
+	if (route.query.orderNo) formInline.value.orderNo = route.query.orderNo;
+	// 获取多个字典
+	// await loadDicts(['valid_status', 'is_enable', 'audit_status', 'insurance_type']);
+	api.insuranceApi.leftList('').then((res: any) => {
+        if(res.code == 200) {
+            companyData.value = res.data.map((item: any) => ({
+				label: item.name,
+				value: item.id
+			}));
+        }
+    });
+	// table数据
+	getData();
+});
+
+// 分页数据
+const getData = () => {
+	const { time, ...params } = formInline.value;
+	api.insurancePolicyApi.adjustList({ 
+		pages: pageInfo.value.pageNum, 
+		size: pageInfo.value.pageSize, 
+		...params
+	}).then((res: any) => {
+		if (res.code == '200') {
+			tableData.value = res.data.records;
+			// 分页
+			pageInfo.value.total = res.data.total;
+			pageInfo.value.pageNum = res.data.current;
+			pageInfo.value.pageSize = res.data.size;
+		}
+	});
+};
+// 搜索事件
+const searchForm = () => {
+	getData();
+}
+const resetForm = () => {
+	formInline.value = { ...initialForm };
+	getData();
+}
+// 分页事件
+const getList = (item: PageInfo) => {
+	pageInfo.value.pageNum = item.pageNum;
+	pageInfo.value.pageSize = item.pageSize;
+	getData();
+};
+// 多选事件
+const selectionChange = (val: any) => {
+	console.log('sel', val)
+};
+// 导出事件
+const exportLoading = ref(false);
+const handleExport = () => {
+	exportLoading.value = true;
+	const { time, ...params } = formInline.value;
+    api.insurancePolicyApi.exportAdjustList(params).then((res: any) => {
+        const fileName = '批改明细' + new Date().getTime() + '.xlsx';
+		downloadByData(res, fileName, 'application/vnd.ms-excel');
+    }).finally(() => {
+		exportLoading.value = false;
+	});
+}
+// 详情
+const toDetail = (e: any) => {
+    router.push({
+        path: '/insurancePolicy/record/detail',
+        query: {
+            id: e.operationData.id,
+			orderNo: e.operationData.orderNo,
+        }
+    });
+	// window.open(`/#/insurancePolicy/record/detail?id=${e.operationData.id}&orderNo=${e.operationData.orderNo}`, '_blank');
+}
+
+defineOptions({
+	name: 'InsurancePolicyRecord',
+})
+</script>
+
+<template>
+	<PageMain class="vh100">
+		<SearchForm :isOffset="true"
+			:fields="fields" :formInline="formInline" 
+			:onSearch="searchForm" :onReset="resetForm" 
+		/>
+		<ComplexTable 
+			:columns="columns" :columnwidth="200" 
+			:showIndex="false" :showSelect="true"
+			:pageInfo="pageInfo" 
+			:tableData="tableData" 
+			@getList="getList" 
+			@selectionChange="selectionChange">
+			<template v-slot:table-title-btn>
+				<el-button class="radius-2px width-100" type="primary" :loading="exportLoading" @click="handleExport">导出</el-button>
+			</template>
+			<template v-slot:operation="scope">
+				<el-button type="primary" link 
+					@click="toDetail(scope)">
+					详情
+				</el-button>
+			</template>
+		</ComplexTable>
+	</PageMain>
+</template>
+
+<style lang="scss" scoped>
+/* 样式 */
+</style>

+ 454 - 0
src/views/insurancePolicy/recordDetail.vue

@@ -0,0 +1,454 @@
+<script setup lang="ts">
+import api from '@/api';
+import DialogInsuranceEdit from './DialogInsuranceEdit.vue';
+import { dictData } from '@/views/localDict/index';
+import { useRoute, useRouter } from 'vue-router';
+import { ElMessage } from 'element-plus';
+import type { FormInstance } from 'element-plus';
+import { loadDicts, getDict } from '@/utils/composables/dictService';
+
+// router
+const route = useRoute();
+const router = useRouter();
+// popover
+const apopRef = ref();
+const tpopRef = ref();
+const jpopRef = ref();
+// form
+const tformRef = ref<FormInstance>();
+const jformRef = ref<FormInstance>();
+const form = reactive({
+	desc: '',
+	mark: ''
+});
+const rules = reactive({
+	desc: [
+		{ required: true, message: '请输入要修改的内容', trigger: 'blur' },
+	],
+	mark: [
+		{ required: true, message: '请输入拒绝批改的原因', trigger: 'blur' },
+	],
+});
+// 批改框
+const formData = ref({});
+const isEditVisible = ref(false);
+const isCancelVisible = ref(false);
+// data
+const recordData = ref<any>({});
+// table
+const mTableData = ref<any>([]);
+const dTableData = ref<any>([]);
+const aTableData = ref<any>([]);
+
+// 生命周期
+onBeforeMount(async () => {
+	// 获取多个字典
+	// await loadDicts(['valid_status', 'is_enable', 'audit_status', 'insurance_type']);
+	// 数据
+	initData();
+
+	// 1待审核: 订单批改审核中,请等待。取消批改,拒绝批改,退回修改,审核通过
+	// 2审核通过: 订单批改审核通过
+	// 3已退回: 订单批改退回修改,请按退回原因,修改后重新提交。取消批改,拒绝批改,退回修改,审核通过
+	// 4已拒绝: 订单批改被拒绝,请查看原因
+});
+
+// 数据
+const getData = () => {
+	api.insurancePolicyApi.adjustGetById({ 
+		id: route.query.id, 
+	}).then((res: any) => {
+		if (res.code == '200') {
+			recordData.value = res.data.orderAdjustVo;
+			mTableData.value = res.data.adjustInfos.filter((item: any) => item.kindType !== 2 && item.type !== 0);
+			dTableData.value = res.data.adjustInfos.filter((item: any) => item.kindType === 2 && item.type !== 0);
+		}
+	});
+};
+const getTrackData = () => {
+	api.insurancePolicyApi.orderTrack({ 
+		module: '批改订单',
+		orderNo: route.query.orderNo, 
+	}).then((res: any) => {
+		if (res.code == '200') {
+			aTableData.value = res.data;
+		}
+	});
+};
+const initData = () => {
+	// 订单数据
+	getData();
+	// 轨迹数据
+	getTrackData();
+};
+const getReviewText = () => {
+	let text = '订单批改审核中,请等待';
+	if (recordData.value.status === 2) {
+		text = '订单批改审核通过';
+	} else if (recordData.value.status === 3) {
+		text = '订单批改退回修改,请按退回原因,修改后重新提交';
+	} else if (recordData.value.status === 4) {
+		text = '订单批改被拒绝,请查看原因';
+	}
+	return text;
+};
+const getStateText = (d: any, v: any) => {
+	let label = d.find((item: any) => item.value == v)?.label;
+	return label || '--';
+};
+// 提交操作
+const submitForm = async (formEl: FormInstance | undefined, status: any, popover: any) => {
+	if (!formEl) {
+		if (status === 2) {
+			let params = {
+				id: route.query.id,
+				// 审核状态 2.审核通过 3.退回修改 4.拒绝批改
+				status: status,
+			};
+			console.log('submit', params)
+			netAudit(params, popover);
+		}
+		return;
+	}
+	await formEl.validate((valid, fields) => {
+		if (valid) {
+			let params = {
+				id: route.query.id,
+				// 审核状态 2.审核通过 3.退回修改 4.拒绝批改
+				status: status,
+				refuse: form.desc,
+			};
+			console.log('submit', params)
+			netAudit(params, popover);
+		} else {
+			console.log('error submit', fields)
+		}
+	})
+}
+const resetForm = (formEl: FormInstance | undefined, popover: any) => {
+	if (!formEl) return;
+	formEl.resetFields();
+	// 关闭弹窗
+	handlePopClose(popover);
+}
+// 审核请求
+const netAudit = (params: any, popover: any) => {
+	api.insurancePolicyApi.verifyOrder(params).then((res: any) => {
+		if (res.code === 200) {
+			ElMessage.success(res.msg || '操作成功');	
+			// 刷新数据
+			initData();
+			// 关闭弹窗
+			handlePopClose(popover);
+		} else {
+			ElMessage.error(res.msg);	
+		}
+	});
+};
+// 关闭popover
+const handlePopClose = (popover: any) => {
+	popover.hide();
+};
+// 获取计算值
+const handleCompute = (a: any, b: any) => {
+	let val = (((a * 100) - (b * 100)) / 100);
+	return val || 0;
+};
+// 重新订单批改
+const onClick = (type: any) => {
+	api.insurancePolicyApi.adjustDel(recordData.value.id).then((res: any) => {
+		if(res.code == 200) {
+			api.insurancePolicyApi.adjustAdd({
+				type: type,
+				orderNo: recordData.value.orderNo,
+			}).then((res: any) => {
+				if(res.code == 200) {
+					formData.value = {
+						...recordData.value,
+						adjustId: res.data.id,
+						tableList: type === 1 ? res.data.changeInfoVosList : res.data.infoVosList
+					};
+					if (type === 1) {
+						isEditVisible.value = true;
+					}
+					if (type === 0) {
+						isCancelVisible.value = true;
+					}
+				} else {
+					ElMessage({
+						message: res.msg,
+						type: 'warning'
+					})
+				}
+			});
+		}
+	});
+};
+// 重新批改后续操作
+const onComplete = () => {
+	router.push({
+        path: '/insurancePolicy/record',
+    });
+};
+
+defineOptions({
+	name: 'InsurancePolicyRecordDetail',
+})
+</script>
+
+<template>
+	<div class="page-container">
+		<PageMain class="vh100 pb-[40px] overflow-auto">
+			<div class="tips">{{ getReviewText() }}</div>
+			<el-descriptions :column="4">
+				<template #title>
+					<div class="flex items-center">
+						<img class="mr-[16px]" src="@/assets/images/insurancePolicy/order.png" style="width: 26px; height: 26px;" />
+						<span>订单号 {{ route.query.id }}</span>
+					</div>
+				</template>
+				<el-descriptions-item label="批单号" width="25%">{{ recordData.id || '--' }}</el-descriptions-item>
+				<el-descriptions-item label="订单号" width="25%">{{ route.query.id }}</el-descriptions-item>
+				<el-descriptions-item label="保险公司" width="25%">{{ recordData.companyName || '--' }}</el-descriptions-item>
+				<el-descriptions-item label="批改类型" width="25%">
+					<el-tag size="small">{{ getStateText(dictData.ADJUST_TYPEDATA, recordData.type) }}</el-tag>
+				</el-descriptions-item>
+				<el-descriptions-item label="批单人">{{ recordData.createBy || '--' }}</el-descriptions-item>
+				<el-descriptions-item label="批单时间">{{ recordData.createTime || '--' }}</el-descriptions-item>
+				<el-descriptions-item label="审核人">{{ [0, 1].indexOf(recordData.status) === -1 && recordData.updateBy ? recordData.updateBy : '--' }}</el-descriptions-item>
+				<el-descriptions-item label="审核时间">{{ [0, 1].indexOf(recordData.status) === -1 && recordData.updateTime ? recordData.updateTime : '--' }}</el-descriptions-item>
+			</el-descriptions>
+			<div class="main-box mt-[24px]">
+				<div class="section-title">批改信息</div>
+				<div class="my-[24px]">
+					<el-table class="w-full" :data="mTableData"
+						header-cell-class-name="!bg-gray-100 !text-gray-600">
+						<el-table-column prop="kindName" label="险种" />
+						<el-table-column prop="amount" label="批改前">
+							<template #default="scope">
+								<span v-if="scope.row.type !== 3">{{ scope.row.amount ? scope.row.amount : '投保' }}</span>
+								<span v-if="scope.row.type === 3">不投保</span>
+							</template>
+						</el-table-column>
+						<el-table-column prop="adjustAmount" label="批改后">
+							<template #default="scope">
+								<span v-if="scope.row.type === 1">{{ `保额变更为${ scope.row.afterAmount },补缴保费 ¥${ scope.row.adjustAmount }` }}</span>
+								<span v-if="scope.row.type === 2 && recordData.type === 0">退保</span>
+								<span v-if="scope.row.type === 2 && recordData.type === 1">退保,退款金额 ¥{{ scope.row.adjustAmount }}</span>
+								<span v-if="scope.row.type === 3">投保,补缴保费 ¥{{ scope.row.adjustAmount }}</span>
+							</template>
+						</el-table-column>
+					</el-table>
+					<el-table class="w-full mt-[12px]" :data="dTableData"
+						header-cell-class-name="!bg-gray-100 !text-gray-600">
+						<el-table-column prop="kindName" label="产品名称" />
+						<el-table-column prop="b" label="批改前">
+							<template #default="scope">
+								<span>{{ scope.row.amount }}份</span>
+							</template>
+						</el-table-column>
+						<el-table-column prop="adjustAmount" label="批改后">
+							<template #default="scope">
+								<span v-if="scope.row.type === 2 && recordData.type === 0">退保</span>
+								<span v-if="scope.row.type === 2 && recordData.type === 1">{{ `退保${ scope.row.afterAmount || '--' }份,退款金额 ¥${ scope.row.adjustAmount }` }}</span>
+								<span v-if="scope.row.type !== 2">{{ `投保${ scope.row.afterAmount || '--' }份,补缴金额 ¥${ scope.row.adjustAmount }` }}</span>
+							</template>
+						</el-table-column>
+					</el-table>
+					<div class="total mt-[12px] flex justify-end">
+						<el-descriptions :column="1">
+							<el-descriptions-item label="订单保费:">¥{{ recordData.premium }}</el-descriptions-item>
+							<el-descriptions-item label="退款金额:">
+								{{ (recordData.premium - recordData.afterPremium) > 0 ? `-¥${handleCompute(recordData.premium, recordData.afterPremium)}` : '¥0' }}
+							</el-descriptions-item>
+							<el-descriptions-item label="补缴金额:">
+								{{ (recordData.afterPremium - recordData.premium) > 0 ? `¥${handleCompute(recordData.afterPremium, recordData.premium)}` : `¥0` }}
+							</el-descriptions-item>
+							<el-descriptions-item label="保费变更:">¥{{ Math.abs(handleCompute(recordData.premium, recordData.afterPremium)) }}</el-descriptions-item>
+							<el-descriptions-item label="批改后的保费:" label-class-name="font-bold">
+								<span class="font-bold" style="color: #F56C6C;">¥{{ recordData.afterPremium }}</span>
+							</el-descriptions-item>
+						</el-descriptions>
+					</div>
+				</div>
+				<div class="section-title">订单记录</div>
+				<div class="my-[24px]">
+					<el-table class="w-full" :data="aTableData" max-height="400"
+						header-cell-class-name="!bg-gray-100 !text-gray-600">
+						<el-table-column prop="createBy" label="操作信息">
+							<template #default="scope">
+								<span>{{ `${scope.row.createBy}:${scope.row.createTime}` }}</span>
+							</template>
+						</el-table-column>
+						<el-table-column prop="optContent" label="操作内容" />
+						<el-table-column prop="changeAfter" label="操作反馈">
+							<template #default="scope">
+								<span v-if="scope.row.changeAfter">{{ scope.row.changeAfter }}</span>
+								<span v-if="scope.row.result">{{ `:${scope.row.result}` }}</span>
+							</template>
+						</el-table-column>
+					</el-table>
+				</div>
+			</div>
+		</PageMain>
+		<FixedActionBar class="text-right" style="box-shadow: 0 0 3px 0 var(--g-box-shadow-color);">
+			<!-- 已退回 -->
+			<template v-if="recordData.status === 3">
+				<el-dropdown trigger="click" class="dropdownBtn">
+					<el-button type="primary">
+						修改并重新提交
+						<el-icon class="el-icon--right">
+							<arrow-down />
+						</el-icon>
+					</el-button>
+					<template #dropdown>
+						<el-dropdown-menu>
+							<el-dropdown-item @click="onClick(1)">险种批改</el-dropdown-item>
+							<el-dropdown-item @click="onClick(0)">整单退保</el-dropdown-item>
+						</el-dropdown-menu>
+					</template>
+				</el-dropdown>
+			</template>
+			<!-- 待审核 -->
+			<template v-if="recordData.status === 1">
+				<el-popover ref="jpopRef" width="auto" trigger="click" placement="top-start">
+					<div class="w-80">
+						<div class="flex items-center mt-[12px]">
+							<el-form ref="jformRef" class="w-full" 
+								:model="form" :rules="rules" 
+								label-width="auto"
+								label-position="top">
+								<el-form-item label="拒绝批改" prop="mark">
+									<el-input v-model="form.mark" type="textarea" placeholder="请输入原因" />
+								</el-form-item>
+							</el-form>
+						</div>
+						<div style="text-align: right; margin: 0">
+							<el-button size="small" @click="resetForm(jformRef, jpopRef)">取消</el-button>
+							<el-button size="small" type="danger" @click="submitForm(jformRef, 4, jpopRef)">
+								确定拒绝
+							</el-button>
+						</div>
+					</div>
+					<template #reference>
+						<el-button type="danger">拒绝批改</el-button>
+					</template>
+				</el-popover>
+			</template>
+			<template v-if="recordData.status === 1">
+				<el-popover ref="tpopRef" width="auto" trigger="click" placement="top-start">
+					<div class="w-80">
+						<div class="flex items-center mt-[12px]">
+							<el-form ref="tformRef" class="w-full" 
+								:model="form" :rules="rules" 
+								label-width="auto"
+								label-position="top">
+								<el-form-item label="退回修改" prop="desc">
+									<el-input v-model="form.desc" type="textarea" placeholder="请输入内容" />
+								</el-form-item>
+							</el-form>
+						</div>
+						<div style="text-align: right; margin: 0">
+							<el-button size="small" @click="resetForm(tformRef, tpopRef)">取消</el-button>
+							<el-button size="small" type="danger" @click="submitForm(tformRef, 3, tpopRef)">
+								确定退回
+							</el-button>
+						</div>
+					</div>
+					<template #reference>
+						<el-button type="danger">退回修改</el-button>
+					</template>
+				</el-popover>
+			</template>
+			<template v-if="recordData.status === 1">
+				<el-popover ref="apopRef" width="auto" trigger="click" placement="top-start" 
+					@before-enter="">
+					<div class="w-70">
+						<div class="flex items-center mt-[12px] mb-[24px]">
+							<el-icon color="#FF9900"><WarningFilled /></el-icon>
+							<span class="ml-[12px]">确定审核通过?</span>
+						</div>
+						<div style="text-align: right; margin: 0">
+							<el-button size="small" @click="handlePopClose(apopRef)">取消</el-button>
+							<el-button size="small" type="primary" @click="submitForm(undefined, 2, apopRef)">
+								确定
+							</el-button>
+						</div>
+					</div>
+					<template #reference>
+						<el-button type="primary">审核通过</el-button>
+					</template>
+				</el-popover>
+			</template>
+		</FixedActionBar>
+		<!-- 重新批改 -->
+		<DialogInsuranceEdit v-model="isEditVisible" :data="formData" @complete="onComplete" />
+	</div>
+</template>
+
+<style lang="scss" scoped>
+.page-container {
+	:deep(.el-alert__title) {
+		font-size: 16px;
+		font-weight: bold;
+	}
+}
+.main-box {
+	.section-title {
+		line-height: 18px;
+		padding: 0 12px 12px;
+		position: relative;
+		border-bottom: 1px solid #eee;
+		display: flex;
+    	align-items: center;
+		&::before {
+			content: '';
+			width: 4px;
+			height: 15px;
+			border-radius: 2px;
+			background-color: #00a0f4;
+			position: absolute;
+			left: 0;
+		}
+	}
+	:deep(.total) {
+		.el-descriptions__cell {
+			padding-bottom: 4px;
+		}
+		.el-descriptions__label {
+			width: 120px;
+			text-align: right;
+			display: inline-block;
+		}
+		.el-descriptions__content {
+			width: 90px;
+			text-align: right;
+			display: inline-block;
+		}
+	}
+}
+.tips {
+	height: 32px;
+	font-size: 14px;
+	color: #FFFFFF;
+	text-align: center;
+	line-height: 32px;
+	padding: 0 16px;
+	background: linear-gradient(87deg, #4FCAFF 0%, #3B8EFF 100%);
+	border-radius: 30px 0px 0px 30px;
+	position: absolute;
+	top: 20px;
+	right: 0;
+	z-index: 1;
+	&::after {
+		content: '';
+		width: 16px;
+		height: 36px;
+		background: url("@/assets/images/insurancePolicy/tips_tail.png") no-repeat;
+		background-size: 100% 100%;
+		position: absolute;
+		right: 0;
+
+	}
+}
+</style>

+ 34 - 0
src/views/localDict/index.ts

@@ -0,0 +1,34 @@
+export const dictData = {
+    ADJUST_TYPEDATA: [
+        {
+            label: '整单退保',
+            value: 0,
+        },
+        {
+            label: '险种批改',
+            value: 1,
+        },
+    ],
+    ADJUST_STATUSDATA: [
+        {
+            label: '订单批改中',
+            value: 0,
+        },
+        {
+            label: '待审核',
+            value: 1,
+        },
+        {
+            label: '审核通过',
+            value: 2,
+        },
+        {
+            label: '退回修改',
+            value: 3,
+        },
+        {
+            label: '拒绝批改',
+            value: 4,
+        },
+    ],
+};