DialogInsuranceCancel.vue 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255
  1. <template>
  2. <el-dialog title="整单退保" width="1000" v-model="isDialogVisible" @close="handleClose">
  3. <div class="">
  4. <div class="mb-4">
  5. <div class="text-[#333333] text-base font-medium">车辆{{ props.data.licenseNo }}整单退保</div>
  6. <div class="text-[#666666]">退保后,将重新计算该保单的应收,应付金额。</div>
  7. </div>
  8. <el-table :data="policyItems" border stripe
  9. header-cell-class-name="!bg-gray-100 !text-gray-600"
  10. @selection-change="handleSelectionChange">
  11. <el-table-column type="selection" width="55" align="center" />
  12. <el-table-column prop="kindName" label="保险" />
  13. <el-table-column prop="policyNumber" label="保单号" width="220" />
  14. <el-table-column prop="expiryDate" label="终保日期" width="180" />
  15. <el-table-column prop="premium" label="保费" width="120">
  16. <template #default="{ row }">
  17. ¥ {{ row.premium }}
  18. </template>
  19. </el-table-column>
  20. <el-table-column label="退款金额" width="250">
  21. <template #default="{ row, $index }">
  22. <el-input v-model="row.refundAmount"
  23. type="number"
  24. placeholder="选中后可输入"
  25. :disabled="!isRowSelected(row)"
  26. :style="[
  27. row.errors?.refundAmount ? {
  28. '--el-border-color': '#F56C6C',
  29. '--el-border-color-hover': '#F56C6C',
  30. '--el-color-primary': '#F56C6C'
  31. } : {}
  32. ]"
  33. @blur="currentIndex = undefined"
  34. @focus="currentIndex = $index"
  35. @input="validateInput(row)"
  36. />
  37. <!-- 错误提示 -->
  38. <div v-if="row.errors?.refundAmount && currentIndex === $index" class="error-message">
  39. {{ row.errors.refundAmount }}
  40. </div>
  41. </template>
  42. </el-table-column>
  43. </el-table>
  44. </div>
  45. <template #footer>
  46. <div class="px-1">
  47. <el-button class="border-[#0083FF] color-[#0083FF]" plain @click="isDialogVisible = false">取 消</el-button>
  48. <el-button type="primary" @click="handleSubmit">提交退保申请</el-button>
  49. </div>
  50. </template>
  51. </el-dialog>
  52. </template>
  53. <script setup lang="ts">
  54. import api from '@/api';
  55. import { ElMessage } from 'element-plus';
  56. // 组件属性与事件
  57. const props = defineProps({
  58. data: {
  59. type: Object,
  60. default: {}
  61. }
  62. });
  63. const isDialogVisible = defineModel({
  64. default: false
  65. });
  66. const emits = defineEmits(['complete']);
  67. const isSubmit = ref(false);
  68. watch(isDialogVisible, (newVal) => {
  69. if (newVal) isSubmit.value = false;
  70. });
  71. // 表格的原始数据
  72. const policyItems = ref<any>([]);
  73. watch(() => props.data.tableList, (val) => {
  74. policyItems.value = val;
  75. }, {
  76. immediate: false,
  77. });
  78. // 当前操作行的索引
  79. const currentIndex = ref();
  80. // 存储当前所有被选中的行的数据
  81. interface TableRow {
  82. [key: string]: any
  83. }
  84. const selectedRows = ref<TableRow[]>([]);
  85. // 关闭弹窗
  86. const handleClose = () => {
  87. isDialogVisible.value = false;
  88. clearData();
  89. if (!isSubmit.value) {
  90. api.insurancePolicyApi.adjustRemove(props.data.orderNo);
  91. }
  92. };
  93. // 清空数据,以防残留数据
  94. const clearData = () => {
  95. policyItems.value = [];
  96. };
  97. // selection参数是所有被选中行的数据组成的数组
  98. const handleSelectionChange = (selection: any) => {
  99. selectedRows.value = selection;
  100. // 当某一行被取消选中时,清空其退款金额
  101. policyItems.value.forEach((item: any) => {
  102. if (!selection.some((selected: any) => selected.id === item.id)) {
  103. item.refundAmount = null;
  104. }
  105. });
  106. };
  107. // 辅助函数:判断某一行是否被选中
  108. const isRowSelected = (row: any): boolean => {
  109. return selectedRows.value.some((item: any) => item.id === row.id);
  110. };
  111. // 校验规则
  112. const validateRules = {
  113. refundAmount: (value: any, compare: any) => {
  114. if (value > compare) {
  115. return '退款金额不能大于保费';
  116. }
  117. if (value < 0 || value > 99999.99) {
  118. return '请输入0-99999.99之间的数字';
  119. } else {
  120. // 检查小数位数是否超过2位
  121. const decimalPart = value.toString().split('.')[1];
  122. if (decimalPart && decimalPart.length > 2) {
  123. return '最多支持两位小数';
  124. }
  125. }
  126. return ''; // 校验通过
  127. }
  128. }
  129. // 输入时实时校验格式
  130. const validateInput = (row: any) => {
  131. if (row.refundAmount === null || row.refundAmount === '') return;
  132. const field = 'refundAmount';
  133. const errorMsg = validateRules[field](row[field], row.premium);
  134. row.errors = {
  135. [field]: errorMsg
  136. };
  137. // // 清除非数字和非小数点的字符,但保留第一个小数点
  138. // let value = row.refundAmount.replace(/[^\d.]/g, '');
  139. // const parts = value.split('.');
  140. // if (parts.length > 2) {
  141. // value = parts[0] + '.' + parts.slice(1).join('');
  142. // }
  143. // // 限制小数位数
  144. // if (parts[1] && parts[1].length > 2) {
  145. // parts[1] = parts[1].substring(0, 2);
  146. // value = parts.join('.');
  147. // }
  148. // // 限制整数位数
  149. // if (parts[0].length > 5) {
  150. // parts[0] = parts[0].substring(0, 5);
  151. // value = parts.join('.');
  152. // }
  153. // row.refundAmount = value;
  154. };
  155. // 提交退保申请
  156. const handleSubmit = () => {
  157. // 检查是否至少选择了一项
  158. if (selectedRows.value.length === 0) {
  159. ElMessage.warning('请至少选择一个需要退保的险种。');
  160. return;
  161. }
  162. // 遍历所有选中的行进行校验
  163. for (const row of selectedRows.value) {
  164. const refundAmountStr = row.refundAmount;
  165. // 1. 校验是否为空
  166. if (refundAmountStr === null || refundAmountStr.trim() === '') {
  167. ElMessage.error(`保险“${row.kindName}”:请输入退款金额`);
  168. return; // 中断提交
  169. }
  170. // 2. 校验格式:必须是数字,最多5位整数,2位小数
  171. const regex = /^\d{1,5}(\.\d{1,2})?$/;
  172. if (!regex.test(refundAmountStr)) {
  173. ElMessage.error(`保险“${row.kindName}”:请输入0-99999.99之间的数字(仅支持2位小数)`);
  174. return; // 中断提交
  175. }
  176. const refundAmountNum = parseFloat(refundAmountStr);
  177. // 3. 校验退款金额是否大于保费
  178. if (refundAmountNum > row.premium) {
  179. ElMessage.error(`保险“${row.kindName}”:退款金额不能大于保费(¥ ${row.premium})`);
  180. return; // 中断提交
  181. }
  182. }
  183. // 提交数据
  184. let params = {
  185. id: props.data.adjustId,
  186. orderNo: props.data.orderNo,
  187. infoVosList: selectedRows.value.map((item: any) => {
  188. let { errors, ...datas } = item;
  189. return {
  190. ...datas,
  191. // 退款金额
  192. refundAmount: item.refundAmount - 0,
  193. }
  194. })
  195. };
  196. console.log('params:', params);
  197. console.log('校验通过,准备提交的数据:', selectedRows.value);
  198. api.insurancePolicyApi.refundSubmit(params).then((res: any) => {
  199. if (res.code === 200) {
  200. ElMessage.success(res.msg || '退保申请已提交!');
  201. // 关闭弹窗
  202. isSubmit.value = true;
  203. isDialogVisible.value = false;
  204. // emits
  205. emits('complete');
  206. }
  207. });
  208. };
  209. onUnmounted(() => {
  210. if (isDialogVisible.value) {
  211. console.log('处理页面刷新时,单子没关闭')
  212. clearData();
  213. api.insurancePolicyApi.adjustRemove(props.data.orderNo);
  214. }
  215. });
  216. </script>
  217. <style scoped lang="scss">
  218. .error-message {
  219. width: fit-content;
  220. font-size: 12px;
  221. color: #fff;
  222. padding: 3px 6px;
  223. text-align: justify;
  224. line-height: 14px;
  225. border-radius: 2px;
  226. position: absolute;
  227. background: #F56C6C;
  228. left: 12px;
  229. right: 12px;
  230. bottom: calc(100% - 8px);
  231. }
  232. </style>