Commit 574b9891 by yuzhenWang

做到了保费对账编辑

parent d4982277
import request from '@/utils/request'
// 分页获取保单业务回执列表
export function getPolicyReceiptList(data) {
return request({
url: '/csf/api/policyReceipt/page',
method: 'post',
data: data
})
}
// 添加保单业务回执
export function addPolicyReceipt(data) {
return request({
url: '/csf/api/policyReceipt/add',
method: 'post',
data: data
})
}
//编辑保单业务回执
export function EditPolicyReceipt(data) {
return request({
url: '/csf/api/policyReceipt/edit',
method: 'post',
data: data
})
}
//获取保单业务回执详情
export function getPolicyReceipt(policyReceiptBizId) {
return request({
url: '/csf/api/policyReceipt/detail?policyReceiptBizId=' + policyReceiptBizId,
method: 'get'
})
}
//保费对账列表
export function premiumReconciliationList(data) {
return request({
url: '/csf/api/premiumReconciliation/page',
method: 'post',
data: data
})
}
//获取保费对账详情
export function getPremiumReconciliationInfo(premiumReconciliationBizId) {
return request({
url: `/csf/api/premiumReconciliation/detail?premiumReconciliationBizId=${premiumReconciliationBizId}`,
method: 'get'
})
}
//新增保费对账
export function addPremiumReconciliation(data) {
return request({
url: '/csf/api/premiumReconciliation/add',
method: 'post',
data: data
})
}
//保费对账编辑单个汇款记录
export function editPremiumRemittance(data) {
return request({
url: '/csf/api/premiumRemittance/edit',
method: 'put',
data: data
})
}
//保费对账删除一条汇款记录
export function deletePremiumRemittance(premiumRemittanceBizId) {
return request({
url: `/csf/api/premiumRemittance/del?premiumRemittanceBizId=${premiumRemittanceBizId}`,
method: 'delete'
})
}
//保费对账删除汇款记录中得其他资料附件
export function deletePremiumRemittanceFile(premiumRemittanceFileBizId) {
return request({
url: `/csf/api/premiumRemittanceFile/del?premiumRemittanceFileBizId=${premiumRemittanceFileBizId}`,
method: 'delete'
})
}
//保费对账其他资料附件列表
export function getPremiumRemittanceFileList(data) {
return request({
url: '/csf/api/premiumRemittanceFile/page',
method: 'post',
data: data
})
}
//编辑单个保费对账汇款记录
export function editSiglePremiumRemittance(data) {
return request({
url: '/csf/api/premiumRemittance/edit',
method: 'put',
data: data
})
}
//保费对账汇款记录列表
export function getPremiumRemittanceListApi(data) {
return request({
url: '/csf/api/premiumRemittance/page',
method: 'post',
data: data
})
}
<template>
<el-form ref="formRef" :model="localModel" :rules="formRules" label-width="auto" v-bind="$attrs"
:validate-on-rule-change="false">
<el-row :gutter="20">
<el-col v-for="item in visibleConfig" :key="item.prop" :span="item.span || 6">
<el-form-item :label="item.label" :prop="item.prop" :class="{ 'search-form-item': isSearch }"
:label-position="item.labelPosition || 'top'">
<!-- Input -->
<el-input v-if="item.type === 'input'" v-model="localModel[item.prop]"
:placeholder="item.placeholder || `请输入${item.label}`" :clearable="true"
@input="(val) => handleNumberInput(val, item)"
@change="(val) => handleModelChange(val, item)" />
<!-- Select (支持 dictType / api / options) -->
<el-select v-else-if="item.type === 'select'" v-model="localModel[item.prop]"
:multiple="!!item.multiple" :placeholder="item.placeholder || `请选择${item.label}`"
:clearable="true" filterable :loading="remoteLoading[item.prop] || false"
@change="(val) => handleModelChange(val, item)" @focus="() => loadRemoteOptions(item)"
@filter-change="(keyword) => handleFilterChange(keyword, item)">
<el-option v-for="opt in getSelectOptions(item)" :key="opt.value" :label="opt.label"
:value="opt.value" />
</el-select>
<!-- Date -->
<el-date-picker v-else-if="item.type === 'date'" v-model="localModel[item.prop]" type="date"
:placeholder="`选择${item.label}`" :value-format="item.valueFormat || 'YYYY-MM-DD'"
style="width: 100%" :disabled-date="getDisabledDateFn(item)"
@change="(val) => handleModelChange(val, item)" />
<!-- Month -->
<el-date-picker v-else-if="item.type === 'month'" v-model="localModel[item.prop]" type="month"
:placeholder="`选择${item.label}`" :value-format="item.valueFormat || 'YYYY-MM'"
style="width: 100%" :disabled-date="getDisabledDateFn(item)"
@change="(val) => handleModelChange(val, item)" />
<!-- Daterange -->
<el-date-picker v-else-if="item.type === 'daterange'" v-model="localModel[item.prop]"
type="daterange" range-separator="至" start-placeholder="开始日期" end-placeholder="结束日期"
:value-format="item.valueFormat || 'YYYY-MM-DD'" :disabled-date="getDisabledDateFn(item)"
style="width: 100%" @change="(val) => handleModelChange(val, item)" />
<!-- Checkbox Group -->
<el-checkbox-group v-else-if="item.type === 'checkbox-group'" v-model="localModel[item.prop]"
@change="(val) => handleModelChange(val, item)">
<el-checkbox v-for="opt in getSelectOptions(item)" :key="opt.value" :label="opt.value">
{{ opt.label }}
</el-checkbox>
</el-checkbox-group>
<!-- textarea -->
<el-input v-else-if="item.type === 'textarea'" v-model="localModel[item.prop]" style="width: 240px"
autosize type="textarea" placeholder="请输入" :clearable="true"
@change="(val) => handleModelChange(val, item)" />
<span v-else>不支持的类型: {{ item.type }}</span>
</el-form-item>
</el-col>
</el-row>
</el-form>
<el-form
ref="formRef"
:model="localModel"
:rules="formRules"
label-width="auto"
v-bind="$attrs"
:validate-on-rule-change="false"
>
<el-row :gutter="20">
<el-col v-for="item in visibleConfig" :key="item.prop" :span="item.span || 6">
<el-form-item
:label="item.label"
:prop="item.prop"
:class="{ 'search-form-item': isSearch }"
:label-position="item.labelPosition || 'top'"
>
<!-- Input -->
<el-input
v-if="item.type === 'input'"
v-model="localModel[item.prop]"
:placeholder="item.placeholder || `请输入${item.label}`"
:clearable="true"
:disabled="item.disabled"
@input="val => handleNumberInput(val, item)"
@change="val => handleModelChange(val, item)"
/>
<!-- Select (支持 dictType / api / options) -->
<el-select
v-else-if="item.type === 'select'"
v-model="localModel[item.prop]"
:multiple="!!item.multiple"
:placeholder="item.placeholder || `请选择${item.label}`"
:clearable="true"
filterable
:disabled="item.disabled"
:loading="remoteLoading[item.prop] || false"
@change="val => handleModelChange(val, item)"
@focus="() => loadRemoteOptions(item)"
@filter-change="keyword => handleFilterChange(keyword, item)"
>
<el-option
v-for="opt in getSelectOptions(item)"
:key="opt.value"
:label="opt.label"
:value="opt.value"
/>
</el-select>
<!-- Date -->
<el-date-picker
v-else-if="item.type === 'date'"
v-model="localModel[item.prop]"
type="date"
:placeholder="`选择${item.label}`"
:disabled="item.disabled"
:value-format="item.valueFormat || 'YYYY-MM-DD'"
style="width: 100%"
:disabled-date="getDisabledDateFn(item)"
@change="val => handleModelChange(val, item)"
/>
<!-- Month -->
<el-date-picker
v-else-if="item.type === 'month'"
v-model="localModel[item.prop]"
type="month"
:placeholder="`选择${item.label}`"
:value-format="item.valueFormat || 'YYYY-MM'"
style="width: 100%"
:disabled="item.disabled"
:disabled-date="getDisabledDateFn(item)"
@change="val => handleModelChange(val, item)"
/>
<!-- Daterange -->
<el-date-picker
v-else-if="item.type === 'daterange'"
v-model="localModel[item.prop]"
type="daterange"
range-separator="至"
start-placeholder="开始日期"
end-placeholder="结束日期"
:value-format="item.valueFormat || 'YYYY-MM-DD'"
:disabled="item.disabled"
:disabled-date="getDisabledDateFn(item)"
style="width: 100%"
@change="val => handleModelChange(val, item)"
/>
<!-- Checkbox Group -->
<el-checkbox-group
v-else-if="item.type === 'checkbox-group'"
v-model="localModel[item.prop]"
:disabled="item.disabled"
@change="val => handleModelChange(val, item)"
>
<el-checkbox v-for="opt in getSelectOptions(item)" :key="opt.value" :label="opt.value">
{{ opt.label }}
</el-checkbox>
</el-checkbox-group>
<!-- textarea -->
<el-input
v-else-if="item.type === 'textarea'"
v-model="localModel[item.prop]"
style="width: 240px"
autosize
:disabled="item.disabled"
type="textarea"
placeholder="请输入"
:clearable="true"
@change="val => handleModelChange(val, item)"
/>
<!-- Upload 回显值得时候数据格式至少是[{url: '必须要传', name: 'name不是必须的根据需要传值'}]-->
<el-upload
v-else-if="item.type === 'upload'"
v-model:file-list="localModel[item.prop]"
:action="item.action"
:headers="item.headers"
:multiple="!!item.multiple"
:limit="item.limit || (item.multiple ? 999 : 1)"
:accept="item.accept"
:list-type="item.listType || 'text'"
:disabled="item.disabled"
:auto-upload="true"
:show-file-list="item.showFileList"
:on-exceed="handleExceed"
:before-upload="file => beforeUpload(file, item)"
:on-success="(res, file, fileList) => handleUploadSuccess(res, file, fileList, item)"
:on-error="(err, file, fileList) => handleUploadError(err, file, fileList, item)"
:on-remove="(file, fileList) => handleUploadRemove(file, fileList, item)"
>
<el-button size="small" type="primary" :link="item.link" :disabled="item.disabled">
{{ item.uploadType === 'image' ? '点击上传图片' : '点击上传文件' }}
</el-button>
<template #tip v-if="item.maxSize || item.accept">
<div class="el-upload__tip">
<span v-if="item.maxSize">大小不超过 {{ formatFileSize(item.maxSize) }}</span>
<span v-if="item.accept">支持格式:{{ item.accept }}</span>
</div>
</template>
</el-upload>
<span v-else>不支持的类型: {{ item.type }}</span>
</el-form-item>
</el-col>
</el-row>
</el-form>
</template>
<script setup>
import { ref, watch, onMounted, nextTick, computed } from 'vue'
import { ElMessage } from 'element-plus'
import { Upload } from '@element-plus/icons-vue'
// 🔑 引入你的字典方法
import useDictStore from '@/store/modules/dict'
import { getDicts } from '@/api/system/dict/data'
import request from '@/utils/request'
import dayjs from 'dayjs'
// ==================== 上传文件 ====================
// 文件大小格式化
function formatFileSize(bytes) {
if (bytes === 0) return '0 Bytes'
const k = 1024
const sizes = ['Bytes', 'KB', 'MB', 'GB']
const i = Math.floor(Math.log(bytes) / Math.log(k))
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]
}
function beforeUpload(file, item) {
// 检查文件大小
if (item.maxSize && file.size > item.maxSize) {
ElMessage.error(`文件 ${file.name} 超出大小限制(最大 ${formatFileSize(item.maxSize)})`)
return false
}
// 检查类型(accept 已由浏览器限制,但可二次校验)
if (item.accept) {
const allowed = item.accept.split(',').map(ext => ext.trim().toLowerCase())
const fileExt = '.' + file.name.split('.').pop().toLowerCase()
if (!allowed.includes(fileExt)) {
ElMessage.error(`文件类型不支持,仅支持:${item.accept}`)
return false
}
}
return true
}
function handleUploadSuccess(response, file, fileList, item) {
// 假设你的后端返回格式:{ code: 200, data: { url: '...', name: 'xxx.pdf' } }
// 你可以通过 item.responseMap 自定义映射,这里先用通用方式
const data = response.data || response
const url = data.url || data.fileUrl || data.path
const name = data.name || data.fileName || file.name
if (!url) {
ElMessage.error('上传成功但未返回文件地址')
return
}
// 找到刚上传的文件(通过 uid),替换其 url
const targetFile = fileList.find(f => f.uid === file.uid)
if (targetFile) {
targetFile.url = url
targetFile.name = name
}
// 触发 model 更新
handleModelChange([...fileList], item)
ElMessage.success(`文件 ${file.name} 上传成功`)
}
function handleExceed(files, fileList) {
ElMessage.warning('超出文件数量限制')
}
function handleUploadError(error, file, fileList, item) {
ElMessage.error(`文件 ${file.name} 上传失败`)
console.error('Upload error:', error)
}
function handleUploadRemove(file, fileList, item) {
// 用户删除文件时,同步更新 model
handleModelChange([...fileList], item)
}
// ==================== 工具函数:深拷贝配置(保留函数) ====================
function deepCloneConfig(obj) {
if (obj === null || typeof obj !== 'object') return obj
if (Array.isArray(obj)) return obj.map(deepCloneConfig)
const cloned = {}
for (const key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
const val = obj[key]
cloned[key] = typeof val === 'function' ? val : deepCloneConfig(val)
}
if (obj === null || typeof obj !== 'object') return obj
if (Array.isArray(obj)) return obj.map(deepCloneConfig)
const cloned = {}
for (const key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
const val = obj[key]
cloned[key] = typeof val === 'function' ? val : deepCloneConfig(val)
}
return cloned
}
return cloned
}
function parseToDate(str) {
if (!str) return null
if (str === 'today') {
return dayjs().startOf('day')
}
if (typeof str === 'string') {
const d = dayjs(str)
return d.isValid() ? d.startOf('day') : null
}
if (str instanceof Date) {
return dayjs(str).startOf('day')
}
return null
if (!str) return null
if (str === 'today') {
return dayjs().startOf('day')
}
if (typeof str === 'string') {
const d = dayjs(str)
return d.isValid() ? d.startOf('day') : null
}
if (str instanceof Date) {
return dayjs(str).startOf('day')
}
return null
}
// ==================== 生成 disabledDate 函数 ====================
function getDisabledDateFn(item) {
const { minDate, maxDate } = item
const { minDate, maxDate } = item
// 如果都没有限制,返回 null(不禁用任何日期)
if (minDate == null && maxDate == null) {
return () => false
}
// 如果都没有限制,返回 null(不禁用任何日期)
if (minDate == null && maxDate == null) {
return () => false
}
return (date) => {
const currentDate = dayjs(date).startOf('day')
let minD = null
let maxD = null
// 解析最小日期
if (minDate != null) {
if (typeof minDate === 'function') {
const val = minDate(localModel.value)
minD = parseToDate(val)
} else {
minD = parseToDate(minDate)
}
}
return date => {
const currentDate = dayjs(date).startOf('day')
let minD = null
let maxD = null
// 解析最小日期
if (minDate != null) {
if (typeof minDate === 'function') {
const val = minDate(localModel.value)
minD = parseToDate(val)
} else {
minD = parseToDate(minDate)
}
}
// 解析最大日期
if (maxDate != null) {
if (typeof maxDate === 'function') {
const val = maxDate(localModel.value)
maxD = parseToDate(val)
} else {
maxD = parseToDate(maxDate)
}
}
// 解析最大日期
if (maxDate != null) {
if (typeof maxDate === 'function') {
const val = maxDate(localModel.value)
maxD = parseToDate(val)
} else {
maxD = parseToDate(maxDate)
}
}
// 判断是否被禁用
if (minD && currentDate.isBefore(minD)) {
return true
}
if (maxD && currentDate.isAfter(maxD)) {
return true
}
return false
// 判断是否被禁用
if (minD && currentDate.isBefore(minD)) {
return true
}
if (maxD && currentDate.isAfter(maxD)) {
return true
}
return false
}
}
// ==================== Props & Emits ====================
const props = defineProps({
modelValue: { type: Object, default: () => ({}) },
config: { type: Array, default: () => [] },
isSearch: { type: Boolean, default: false }
modelValue: { type: Object, default: () => ({}) },
config: { type: Array, default: () => [] },
isSearch: { type: Boolean, default: false }
})
const emit = defineEmits(['update:modelValue', 'update'])
......@@ -157,61 +312,61 @@ const remoteLoading = ref({}) // { prop: boolean }
// ==================== 条件显隐 & 规则 ====================
const visibleConfig = computed(() => {
return internalConfig.value.filter(item => {
if (typeof item.visible === 'function') {
return item.visible(localModel.value)
}
return true
})
return internalConfig.value.filter(item => {
if (typeof item.visible === 'function') {
return item.visible(localModel.value)
}
return true
})
})
const formRules = computed(() => {
const rules = {}
visibleConfig.value.forEach(item => {
if (item.rules) rules[item.prop] = item.rules
})
return rules
const rules = {}
visibleConfig.value.forEach(item => {
if (item.rules) rules[item.prop] = item.rules
})
return rules
})
// 1. 外部 modelValue 变化时,安全同步(仅当内容不同时)
// 监听 config 变化(支持动态 config)
watch(
() => props.config,
(newConfig) => {
if (!newConfig || newConfig.length === 0) return
internalConfig.value = deepCloneConfig(newConfig)
// 构建初始模型
const initialModel = {}
for (const item of internalConfig.value) {
const key = item.prop
// 优先用父传值,否则用默认值
if (props.modelValue?.[key] !== undefined) {
initialModel[key] = props.modelValue[key]
} else if (item.multiple || ['checkbox-group', 'daterange'].includes(item.type)) {
initialModel[key] = item.defaultValue ?? []
} else {
initialModel[key] = item.defaultValue ?? ''
}
}
() => props.config,
newConfig => {
if (!newConfig || newConfig.length === 0) return
// ✅ 在这里同步 modelValue(包括 extra 字段)
localModel.value = syncModelFromProps(props.modelValue, internalConfig.value)
},
{ immediate: true }
internalConfig.value = deepCloneConfig(newConfig)
// 构建初始模型
const initialModel = {}
for (const item of internalConfig.value) {
const key = item.prop
// 优先用父传值,否则用默认值
if (props.modelValue?.[key] !== undefined) {
initialModel[key] = props.modelValue[key]
} else if (item.multiple || ['checkbox-group', 'daterange'].includes(item.type)) {
initialModel[key] = item.defaultValue ?? []
} else {
initialModel[key] = item.defaultValue ?? ''
}
}
// ✅ 在这里同步 modelValue(包括 extra 字段)
localModel.value = syncModelFromProps(props.modelValue, internalConfig.value)
},
{ immediate: true }
)
console.log('🚀 子组件 props.modelValue 初始值:', props.modelValue)
// 监听 modelValue(用于后续外部更新)
watch(
() => props.modelValue,
(newVal) => {
if (!newVal || !internalConfig.value) return
// ✅ 同样使用 sync 函数
localModel.value = syncModelFromProps(newVal, internalConfig.value)
},
{ deep: true }
() => props.modelValue,
newVal => {
if (!newVal || !internalConfig.value) return
// ✅ 同样使用 sync 函数
localModel.value = syncModelFromProps(newVal, internalConfig.value)
},
{ deep: true }
)
// 提取同步逻辑
......@@ -219,7 +374,8 @@ function syncModelFromProps(newModelValue, newConfig) {
if (!newModelValue || !newConfig) return {}
const synced = {}
console.log('newConfig---------------------', newConfig)
console.log('newModelValue---------------------', newModelValue)
// 1. 同步主字段
for (const item of newConfig) {
const key = item.prop
......@@ -231,16 +387,29 @@ function syncModelFromProps(newModelValue, newConfig) {
synced[key] = item.defaultValue ?? ''
}
}
// 2. 同步 extra 字段(从 newModelValue 中的 sourceField 重新计算)
// 2. 同步 extra 字段(通过 options 反查)
for (const item of newConfig) {
const sourceField = item.prop
const extraMap = item.onChangeExtraFields
if (!extraMap || typeof extraMap !== 'object') continue
const sourceObj = newModelValue[sourceField]
const prop = item.prop
const idValue = newModelValue[prop] // e.g. 2
let sourceObj = null
// 情况1: 如果 newModelValue[prop] 本身就是对象 → 直接用(兼容旧逻辑)
if (idValue && typeof idValue === 'object') {
sourceObj = idValue
}
// 情况2: 如果是 primitive(string/number),且有 options → 反查
else if (Array.isArray(item.options) && idValue !== undefined && idValue !== null) {
// 默认用 option.value 匹配,可配置 valueKey
const valueKey = item.valueKey || 'value'
sourceObj = item.options.find(opt => opt[valueKey] === idValue)
}
// 如果找到了 sourceObj,就提取 extra 字段
if (sourceObj && typeof sourceObj === 'object') {
// newModelValue 中有 sourceField → 重新计算 extra
for (const [targetKey, subPath] of Object.entries(extraMap)) {
const val = getNestedValue(sourceObj, subPath)
if (val !== undefined) {
......@@ -267,35 +436,38 @@ function syncModelFromProps(newModelValue, newConfig) {
}
}
// 4. 保留其他不在 config 中的字段
// 4. 保留 newModelValue 中已有的 extra 字段和其他额外字段
for (const key in newModelValue) {
if (
!synced.hasOwnProperty(key) &&
!newConfig.some(item => item.prop === key)
) {
const isExtraTarget = newConfig.some(item =>
item.onChangeExtraFields && item.onChangeExtraFields.hasOwnProperty(key)
)
if (!isExtraTarget) {
synced[key] = newModelValue[key]
}
// 如果已经同步过了(比如主字段或第2步写入的extra),跳过
if (synced.hasOwnProperty(key)) continue
// 判断是否是某个 extra 目标字段
const isExtraTarget = newConfig.some(
item => item.onChangeExtraFields && item.onChangeExtraFields.hasOwnProperty(key)
)
// 如果是 extra 字段,且 newModelValue 里有值 → 保留它!
if (isExtraTarget) {
synced[key] = newModelValue[key]
}
// 如果不是 extra,也不是主字段 → 也保留(兼容 hidden 字段等)
else if (!newConfig.some(item => item.prop === key)) {
synced[key] = newModelValue[key]
}
}
console.log('🚀 子组件 props.modelValue 同步后:', synced)
return synced
}
function getNestedValue(obj, path) {
return path.split('.').reduce((current, key) => current?.[key], obj)
return path.split('.').reduce((current, key) => current?.[key], obj)
}
// 当字典加载完成时,触发同步
function markDictLoaded(prop) {
dictLoaded.value.add(prop)
// 尝试同步该字段
if (props.modelValue?.[prop] !== undefined) {
localModel.value[prop] = props.modelValue[prop]
}
dictLoaded.value.add(prop)
// 尝试同步该字段
if (props.modelValue?.[prop] !== undefined) {
localModel.value[prop] = props.modelValue[prop]
}
}
// 2. 用户操作导致 localModel 变化时,emit(防抖可选)
......@@ -339,294 +511,299 @@ function handleModelChange(value, item) {
}
// 辅助函数:浅比较两个对象
function isEqualShallow(a, b) {
const keysA = Object.keys(a)
const keysB = Object.keys(b)
if (keysA.length !== keysB.length) return false
for (let key of keysA) {
if (a[key] !== b[key]) return false
}
return true
const keysA = Object.keys(a)
const keysB = Object.keys(b)
if (keysA.length !== keysB.length) return false
for (let key of keysA) {
if (a[key] !== b[key]) return false
}
return true
}
// ==================== 加载字典选项 ====================
async function loadDictOptions(dictType) {
const dictStore = useDictStore()
let options = dictStore.getDict(dictType)
const dictStore = useDictStore()
let options = dictStore.getDict(dictType)
if (options && options.length > 0) {
return options
}
if (options && options.length > 0) {
return options
}
try {
const resp = await getDicts(dictType)
options = resp.data.map(p => ({
label: p.itemLabel,
value: p.itemValue,
raw: p
}))
dictStore.setDict(dictType, options)
return options
} catch (err) {
console.error(`加载字典 ${dictType} 失败`, err)
ElMessage.error(`字典 ${dictType} 加载失败`)
return []
}
try {
const resp = await getDicts(dictType)
options = resp.data.map(p => ({
label: p.itemLabel,
value: p.itemValue,
raw: p
}))
dictStore.setDict(dictType, options)
return options
} catch (err) {
console.error(`加载字典 ${dictType} 失败`, err)
ElMessage.error(`字典 ${dictType} 加载失败`)
return []
}
}
// ==================== 初始化 ====================
onMounted(async () => {
internalConfig.value = deepCloneConfig(props.config)
const initialData = {}
const dictTypePromises = []
const apiPromises = [] // ← 新增:收集 api 加载 promise
for (const item of internalConfig.value) {
const key = item.prop
if (localModel.value[key] == null) {
if (item.multiple) {
initialData[key] = item.defaultValue ?? []
} else if (['checkbox-group', 'daterange'].includes(item.type)) {
initialData[key] = item.defaultValue ?? []
} else {
initialData[key] = item.defaultValue ?? ''
}
}
internalConfig.value = deepCloneConfig(props.config)
// 预加载 dictType
if (item.type === 'select' && item.dictType) {
dictTypePromises.push(
loadDictOptions(item.dictType).then(opts => {
remoteOptions.value[key] = opts
markDictLoaded(key) // ← 立即标记已加载
})
)
} // 预加载 api(远程接口)← 关键新增!
else if (item.type === 'select' && item.api) {
apiPromises.push(
loadRemoteOptionsForInit(item) // ← 专门用于初始化的加载函数
)
} else if (item.type === 'select' && item.options) {
remoteOptions.value[key] = [...item.options]
markDictLoaded(key)
}
// api 类型:延迟加载(focus 时)
const initialData = {}
const dictTypePromises = []
const apiPromises = [] // ← 新增:收集 api 加载 promise
for (const item of internalConfig.value) {
const key = item.prop
if (localModel.value[key] == null) {
if (item.multiple) {
initialData[key] = item.defaultValue ?? []
} else if (['checkbox-group', 'daterange'].includes(item.type)) {
initialData[key] = item.defaultValue ?? []
} else {
initialData[key] = item.defaultValue ?? ''
}
}
if (Object.keys(initialData).length > 0) {
localModel.value = { ...localModel.value, ...initialData }
// 预加载 dictType
if (item.type === 'select' && item.dictType) {
dictTypePromises.push(
loadDictOptions(item.dictType).then(opts => {
remoteOptions.value[key] = opts
markDictLoaded(key) // ← 立即标记已加载
})
)
} // 预加载 api(远程接口)← 关键新增!
else if (item.type === 'select' && item.api) {
apiPromises.push(
loadRemoteOptionsForInit(item) // ← 专门用于初始化的加载函数
)
} else if (item.type === 'select' && item.options) {
remoteOptions.value[key] = [...item.options]
markDictLoaded(key)
}
// api 类型:延迟加载(focus 时)
}
if (Object.keys(initialData).length > 0) {
localModel.value = { ...localModel.value, ...initialData }
}
// 等待所有字典加载完成
await Promise.allSettled(dictTypePromises)
// 等待所有字典加载完成
await Promise.allSettled(dictTypePromises)
})
// ==================== 获取 select 选项 ====================
function getSelectOptions(item) {
const key = item.prop
const key = item.prop
// 字典选项
if (item.dictType || item.api) {
// 字典选项
if (item.dictType || item.api) {
// 字典选项
return (remoteOptions.value[key] || []).map(opt => ({
value: opt.value,
label: opt.label,
raw: opt.raw // ← 必须存 raw
}))
} else if (item.options) {
// 静态选项s
return item.options.map(opt => ({
value: opt.value,
label: opt.label,
raw: opt.raw // 保留原始
}))
}
return []
return (remoteOptions.value[key] || []).map(opt => ({
value: opt.value,
label: opt.label,
raw: opt.raw // ← 必须存 raw
}))
} else if (item.options) {
// 静态选项s
return item.options.map(opt => ({
value: opt.value,
label: opt.label,
raw: opt.raw // 保留原始
}))
}
return []
}
async function loadRemoteOptionsForInit(item) {
const { prop, api, requestParams = {} } = item
try {
// 构造请求体:只传 requestParams,不传 keyword
const payload = {
...(requestParams || {})
}
const res = await request({
url: api,
method: 'post',
data: payload
})
const list = typeof item.transform === 'function'
? item.transform(res)
: res.data?.records || res.data || []
const { prop, api, requestParams = {} } = item
try {
// 构造请求体:只传 requestParams,不传 keyword
const payload = {
...(requestParams || {})
}
// 建议:统一转成字符串(或根据 item.valueType 判断)
const newOptions = list.map(i => ({
value: String(i[item.valueKey || 'value']), // ← 强制转字符串
label: i[item.labelKey || 'label'],
raw: i
}))
const res = await request({
url: api,
method: 'post',
data: payload
})
remoteOptions.value[prop] = newOptions
markDictLoaded(prop) // ← 关键:标记已加载
} catch (err) {
ElMessage.error(`预加载 ${item.label} 失败`)
remoteOptions.value[prop] = []
}
const list =
typeof item.transform === 'function'
? item.transform(res)
: res.data?.records || res.data || []
// 建议:统一转成字符串(或根据 item.valueType 判断)
const newOptions = list.map(i => ({
value: String(i[item.valueKey || 'value']), // ← 强制转字符串
label: i[item.labelKey || 'label'],
raw: i
}))
remoteOptions.value[prop] = newOptions
markDictLoaded(prop) // ← 关键:标记已加载
} catch (err) {
ElMessage.error(`预加载 ${item.label} 失败`)
remoteOptions.value[prop] = []
}
}
// ==================== 加载远程 API 选项(首次 focus 时加载,无搜索词) ====================
async function loadRemoteOptions(item) {
const { prop, api, requestParams = {}, keywordField, debounceWait, ...rest } = item
if (!api || remoteOptions.value[prop]?.length > 0) return
const { prop, api, requestParams = {}, keywordField, debounceWait, ...rest } = item
if (!api || remoteOptions.value[prop]?.length > 0) return
try {
remoteLoading.value[prop] = true
try {
remoteLoading.value[prop] = true
// 构造请求体:合并 requestParams + 分页(默认第一页)
const payload = {
...(requestParams || {})
// 注意:此时无 keyword,所以不加 keywordField
}
// 构造请求体:合并 requestParams + 分页(默认第一页)
const payload = {
...(requestParams || {})
// 注意:此时无 keyword,所以不加 keywordField
}
const res = await request({
url: api,
method: 'post', // ← 改为 POST
data: payload // ← 参数放 body
})
const res = await request({
url: api,
method: 'post', // ← 改为 POST
data: payload // ← 参数放 body
})
const list = typeof item.transform === 'function'
? item.transform(res)
: res.data?.records || res.data || []
// 建议:统一转成字符串(或根据 item.valueType 判断)
const newOptions = list.map(i => ({
value: String(i[item.valueKey || 'value']), // ← 强制转字符串
label: i[item.labelKey || 'label'],
raw: i
}))
remoteOptions.value[prop] = newOptions
// ✅ 关键:标记该字段字典已加载
markDictLoaded(prop)
} catch (err) {
ElMessage.error(`加载 ${item.label} 失败`)
remoteOptions.value[prop] = []
} finally {
remoteLoading.value[prop] = false
}
const list =
typeof item.transform === 'function'
? item.transform(res)
: res.data?.records || res.data || []
// 建议:统一转成字符串(或根据 item.valueType 判断)
const newOptions = list.map(i => ({
value: String(i[item.valueKey || 'value']), // ← 强制转字符串
label: i[item.labelKey || 'label'],
raw: i
}))
remoteOptions.value[prop] = newOptions
// ✅ 关键:标记该字段字典已加载
markDictLoaded(prop)
} catch (err) {
ElMessage.error(`加载 ${item.label} 失败`)
remoteOptions.value[prop] = []
} finally {
remoteLoading.value[prop] = false
}
}
// ==================== 远程搜索(带关键词,防抖) ====================
let searchTimeout = null
function handleFilterChange(keyword, item) {
const { prop, api, requestParams = {}, keywordField = 'keyword', debounceWait = 300 } = item
if (!api) return
clearTimeout(searchTimeout)
searchTimeout = setTimeout(async () => {
try {
remoteLoading.value[prop] = true
// 构造请求体:requestParams + 分页 + 动态关键词字段
const payload = {
...(requestParams || {}),
[keywordField]: keyword // ← 动态字段名,如 name / companyName
}
const res = await request({
url: api,
method: 'post', // ← POST 请求
data: payload // ← body 传参
})
const list = typeof item.transform === 'function'
? item.transform(res)
: res.data?.records || res.data || []
remoteOptions.value[prop] = list.map(i => ({
value: i[item.valueKey || 'value'],
label: i[item.labelKey || 'label'],
raw: i // ← 保存完整对象
}))
} catch (err) {
ElMessage.error(`搜索 ${item.label} 失败`)
} finally {
remoteLoading.value[prop] = false
}
}, debounceWait)
const { prop, api, requestParams = {}, keywordField = 'keyword', debounceWait = 300 } = item
if (!api) return
clearTimeout(searchTimeout)
searchTimeout = setTimeout(async () => {
try {
remoteLoading.value[prop] = true
// 构造请求体:requestParams + 分页 + 动态关键词字段
const payload = {
...(requestParams || {}),
[keywordField]: keyword // ← 动态字段名,如 name / companyName
}
const res = await request({
url: api,
method: 'post', // ← POST 请求
data: payload // ← body 传参
})
const list =
typeof item.transform === 'function'
? item.transform(res)
: res.data?.records || res.data || []
remoteOptions.value[prop] = list.map(i => ({
value: i[item.valueKey || 'value'],
label: i[item.labelKey || 'label'],
raw: i // ← 保存完整对象
}))
} catch (err) {
ElMessage.error(`搜索 ${item.label} 失败`)
} finally {
remoteLoading.value[prop] = false
}
}, debounceWait)
}
// ==================== 数字输入处理 ====================
function handleNumberInput(value, item) {
const { inputType = 'text', decimalDigits = 2, prop } = item
if (!prop) return
let result = String(value ?? '').trim()
const { inputType = 'text', decimalDigits = 2, prop } = item
if (!prop) return
if (inputType === 'integer') {
// 只保留数字
result = result.replace(/[^\d]/g, '')
} else if (inputType === 'decimal') {
// 1. 只保留数字和小数点
result = result.replace(/[^\d.]/g, '')
let result = String(value ?? '').trim()
// 2. 去掉开头的小数点(不允许 ".5" → 改为 "0.5" 更好,但这里先简单处理)
if (result.startsWith('.')) {
result = '0.' + result.slice(1)
}
if (inputType === 'integer') {
// 只保留数字
result = result.replace(/[^\d]/g, '')
} else if (inputType === 'decimal') {
// 1. 只保留数字和小数点
result = result.replace(/[^\d.]/g, '')
// 3. 保证最多一个小数点
const parts = result.split('.')
if (parts.length > 2) {
result = parts[0] + '.' + parts.slice(1).join('')
}
// 2. 去掉开头的小数点(不允许 ".5" → 改为 "0.5" 更好,但这里先简单处理)
if (result.startsWith('.')) {
result = '0.' + result.slice(1)
}
// 4. 限制小数位数(但保留结尾的小数点!)
if (result.includes('.')) {
const [intPart, decPart] = result.split('.')
// 如果小数部分超过限制,截断
if (decPart.length > decimalDigits) {
result = intPart + '.' + decPart.slice(0, decimalDigits)
}
// ✅ 不再删除结尾的 '.'
}
// 3. 保证最多一个小数点
const parts = result.split('.')
if (parts.length > 2) {
result = parts[0] + '.' + parts.slice(1).join('')
}
// 防止重复赋值(可选优化)
if (localModel.value[prop] !== result) {
localModel.value = { ...localModel.value, [prop]: result }
// 4. 限制小数位数(但保留结尾的小数点!)
if (result.includes('.')) {
const [intPart, decPart] = result.split('.')
// 如果小数部分超过限制,截断
if (decPart.length > decimalDigits) {
result = intPart + '.' + decPart.slice(0, decimalDigits)
}
// ✅ 不再删除结尾的 '.'
}
}
// 防止重复赋值(可选优化)
if (localModel.value[prop] !== result) {
localModel.value = { ...localModel.value, [prop]: result }
}
}
// ==================== 暴露方法 ====================
defineExpose({
getFormData() {
return { ...localModel.value }
},
async validate() {
return new Promise((resolve, reject) => {
formRef.value?.validate((valid) => {
if (valid) resolve(localModel.value)
else reject(new Error('Validation failed'))
})
})
},
resetForm() {
const resetData = {}
internalConfig.value.forEach(item => {
const key = item.prop
if (['checkbox-group', 'daterange'].includes(item.type) || item.multiple) {
resetData[key] = item.defaultValue ?? []
} else {
resetData[key] = item.defaultValue ?? ''
}
})
localModel.value = { ...resetData }
nextTick(() => formRef.value?.clearValidate())
}
getFormData() {
return { ...localModel.value }
},
async validate() {
return new Promise((resolve, reject) => {
formRef.value?.validate(valid => {
if (valid) resolve(localModel.value)
else reject(new Error('Validation failed'))
})
})
},
resetForm() {
const resetData = {}
internalConfig.value.forEach(item => {
const key = item.prop
if (['checkbox-group', 'daterange'].includes(item.type) || item.multiple) {
resetData[key] = item.defaultValue ?? []
} else if (item.type === 'upload') {
resetData[key] = item.defaultValue ?? [] // upload 也是数组
} else {
resetData[key] = item.defaultValue ?? ''
}
})
localModel.value = { ...resetData }
nextTick(() => formRef.value?.clearValidate())
}
})
</script>
<style scoped>
.search-form-item {
margin-bottom: 20px;
margin-bottom: 20px;
}
</style>
\ No newline at end of file
</style>
{
"apiAppointmentInfoDto": {
"isReferrerAccompany": "1",
"isUseCar": "1",
"isOpenAccount": "1",
"applyType": "INVESTMENT",
"signDate": "2026-01-26 00:00:00",
"arrivalTime": "2026-01-28 09:40:00",
"departureTime": "2026-02-05 09:40:00",
"meetingPoint": "INSURANCE_COMPANY",
"signingAddress": "886",
"hkMobile": "886",
"bankName": "中国工商银行",
"bankId": "bank_1001",
"materials": "个人开户: 带好 身份证 和 实名手机 直接前往网点即可,多数情况十分钟内办结。\r\n\r\n对公开户:\r\n\r\n第一步: 致电目标银行网点对公业务部,预约并获取最新资料清单。\r\n\r\n第二步: 对照清单,准备好所有 原件 和 公章。\r\n\r\n第三步: 法定代表人(或授权经办人)携带所有材料,按约定时间前往银行办理。",
"bankBranchName": "886",
"openAccountStartTime": "2026-01-05 00:00:00",
"openAccountEndTime": "2026-01-20 00:00:00",
"openAccountLocation": "886",
"isBuy": "1",
"isTj": "0",
"openAccountNotice": "886",
"hkMobileCode": "+244",
"objType": "phone",
"key": "hkMobile",
"phoneString": "+244 886",
"referrerDtoList": [
{
"id": 1768182062294,
"span": 24,
"email": "886",
"phone": "886",
"realName": "赵风",
"userBizId": "user_1Y3A18QwDYGMwZHp",
"userSaleBizId": "user_sale_expand_6LipZ2uQYqIpv2vK"
}
],
"userSignDtoList": [
{
"id": 1768182068586,
"span": 24,
"name": "SW",
"practiceCode": "886",
"phone": "886",
"cardType": "idCard",
"cardNo": "886",
"email": "886",
"userBizId": "user_HNpzB3A0A72JMLVN",
"userSignBizId": "user_sign_rrrrsae445556er"
}
],
"isSecond": 1,
"customerBizId": "customer_OgR6wHpkgvvlpsht",
"fnaBizId": "fna_QK3X3ygfsaOKcYQW",
"fnaNo": "CSF-B-20260112-0926"
},
"apiProductPlanInfoDto": {
"apiProductPlanMainInfoDto": {
"companyName": "太平洋保险有限公司",
"companyId": "insurance_company_MLXRsUzPBhG76P1X",
"insuranceTypeName": "储蓄险",
"insuranceTypeId": "2aba7e865b9b45deba77e0bcc7d6fa67",
"productLaunchMainName": "附加产品001",
"productLaunchBizId": "product_launch_1LcCgSXEsf0KfmQM",
"issueNumber": "3",
"policyCurrency": "GBP",
"paymentFrequency": "YEAR",
"eachIssuePremium": "886",
"policyLevy": "886",
"isPrepay": 0
},
"apiProductPlanAdditionalInfoDtoList": []
},
"apiPolicyholderInfoDto": {
"id": 23,
"customerBizId": "customer_OgR6wHpkgvvlpsht",
"nameCn": "测试二",
"namePyEn": "CESHIER",
"documentType": "idCard",
"idNumber": "411424199604041622",
"gender": "2",
"birthday": "1996-04-04 00:00:00",
"age": 29,
"nationality": "中国台湾",
"birthplace": "886",
"isOtherCountry": "1",
"apiTaxationDtoList": [
{
"taxCountry": "中国",
"taxId": "886",
"show": true
}
],
"smokingStatus": "0",
"maritalStatus": "SINGLE",
"educationLevel": "UNIVERSITY",
"isRetirement": "0",
"retirementAge": null,
"height": "160",
"weight": "50",
"bmi": "19.53",
"riskAppetite": "LOW",
"dependentsNum": 1,
"mobileCode": "+994",
"mobile": "886",
"residenceMobileCode": "+675",
"residenceMobile": "886",
"landlineCode": null,
"landline": "886",
"email": "886",
"certificateAddress": "886",
"mailingAddressCode": "886",
"employmentStatus": "1",
"csName": "886",
"industry": "886",
"currentMonthlyIncome": 886,
"totalWorkingYears": 886,
"currentTenure": 886,
"position": "886",
"companyMobileCode": "+359",
"companyMobile": "886",
"companyAddressCode": "886",
"monthIncome": 886,
"monthExpenditure": 886,
"totalCurrentAssets": 886,
"totalDebt": 885,
"travel": "886",
"exercise": "VOLLEYBALL",
"game": "STIMULATE",
"movieDrama": "FANTASY",
"delicacy": "886",
"addressList": [
{
"type": "mailingAddress",
"region": "886",
"city": "886",
"street": "886",
"location": "886",
"addressString": "886886886886",
"objType": "address"
},
{
"type": "residentialAddress",
"region": "886",
"city": "886",
"street": "886",
"location": "886",
"addressString": "886886886886",
"objType": "address"
},
{
"type": "companyAddress",
"region": "886",
"city": "886",
"street": "886",
"location": "886",
"addressString": "886886886886",
"objType": "address"
}
],
"remark": null,
"countryName": "中国台湾",
"phoneString": "+359 886",
"key": "companyMobile",
"phoneCode": "companyMobileCode",
"code": "+359"
},
"apiInsurantInfoDto": {
"id": 23,
"customerBizId": "customer_OgR6wHpkgvvlpsht",
"nameCn": "测试二",
"namePyEn": "CESHIER",
"documentType": "idCard",
"idNumber": "411424199604041622",
"gender": "2",
"birthday": "1996-04-04 00:00:00",
"age": 29,
"nationality": "中国台湾",
"birthplace": "886",
"isOtherCountry": "1",
"apiTaxationDtoList": [
{
"taxCountry": "中国",
"taxId": "886",
"show": true
}
],
"smokingStatus": "0",
"maritalStatus": "SINGLE",
"educationLevel": "UNIVERSITY",
"isRetirement": "0",
"retirementAge": null,
"height": "160",
"weight": "50",
"bmi": "19.53",
"riskAppetite": "LOW",
"dependentsNum": 1,
"mobileCode": "+994",
"mobile": "886",
"residenceMobileCode": "+675",
"residenceMobile": "886",
"landlineCode": null,
"landline": "886",
"email": "886",
"certificateAddress": "886",
"mailingAddressCode": "886",
"employmentStatus": "1",
"csName": "886",
"industry": "886",
"currentMonthlyIncome": 886,
"totalWorkingYears": 886,
"currentTenure": 886,
"position": "886",
"companyMobileCode": "+359",
"companyMobile": "886",
"companyAddressCode": "886",
"monthIncome": 886,
"monthExpenditure": 886,
"totalCurrentAssets": 886,
"totalDebt": 885,
"travel": "886",
"exercise": "VOLLEYBALL",
"game": "STIMULATE",
"movieDrama": "FANTASY",
"delicacy": "886",
"addressList": [
{
"type": "mailingAddress",
"region": "886",
"city": "886",
"street": "886",
"location": "886",
"addressString": "886886886886",
"objType": "address"
},
{
"type": "residentialAddress",
"region": "886",
"city": "886",
"street": "886",
"location": "886",
"addressString": "886886886886",
"objType": "address"
},
{
"type": "companyAddress",
"region": "886",
"city": "886",
"street": "886",
"location": "886",
"addressString": "886886886886",
"objType": "address"
}
],
"remark": null,
"policyholderRel": "MYSELF",
"countryName": "中国台湾",
"phoneString": "+359 886",
"key": "companyMobile",
"phoneCode": "companyMobileCode",
"code": "+359"
},
"apiBeneficiaryInfoFzDto": {
"isLegalBeneficiary": 1
},
"apiSecondHolderInfoDto": {
"isSecond": 1,
"insurantRel": "MYSELF",
"nameCn": "测试二",
"namePyEn": "CESHIER",
"documentType": "idCard",
"idNumber": "411424199604041622",
"gender": "2",
"birthday": "1996-04-04 00:00:00",
"benefitRatio": ""
},
"apiAnswerSaveRequest": {
"questionnaireBizId": "questionnaires_1001",
"objectBizId": "",
"answerSessionsDtoList": []
},
"materialDtoList": [
{
"materialBizId": "material_rPc0sDZurdDFfwSE",
"fileBizIdList": [
"oss_file_jhoezRHKCMf5AHbU"
]
},
{
"materialBizId": "material_NOM56VZmKXa8rwcf",
"fileBizIdList": []
},
{
"materialBizId": "material_Clinlz7VnBLbg151",
"fileBizIdList": []
},
{
"materialBizId": "material_IaSKzxQ73K5qQPUW",
"fileBizIdList": []
},
{
"materialBizId": "material_pOLgtaGwIR5IkUBv",
"fileBizIdList": []
},
{
"materialBizId": "material_h0sIbNWnklHI3dFL",
"fileBizIdList": []
},
"paymentMethod": "CHECK",
"paymentAmount": "111",
"paymentCurrency": "HKD",
"paymentRel": "SBR",
"payer": "111",
"payingBank": "bank_1002",
"paymentAccount": "1111",
"currency": "",
"paymentVoucherList": [
{
"fileName": "icon5.png",
"fileType": "png",
"fileUrl": "https://yd-ali-oss.oss-cn-shanghai-finance-1-pub.aliyuncs.com/png/2026/01/13/b4241a95a39d4655a79c706d7ec37f85.png",
"url": "https://yd-ali-oss.oss-cn-shanghai-finance-1-pub.aliyuncs.com/png/2026/01/13/b4241a95a39d4655a79c706d7ec37f85.png"
},
{
"fileName": "cardSix1.png",
"fileType": "png",
"fileUrl": "https://yd-ali-oss.oss-cn-shanghai-finance-1-pub.aliyuncs.com/png/2026/01/13/41bd22390f6a4a69a5ed8d9c8758ef94.png",
"url": "https://yd-ali-oss.oss-cn-shanghai-finance-1-pub.aliyuncs.com/png/2026/01/13/41bd22390f6a4a69a5ed8d9c8758ef94.png"
}
],
"accountVerificationList": [
{
"materialBizId": "material_Txh02cNvT21QPOZH",
"fileBizIdList": []
"fileName": "icon6.png",
"fileType": "png",
"fileUrl": "https://yd-ali-oss.oss-cn-shanghai-finance-1-pub.aliyuncs.com/png/2026/01/13/ce6f4781b8f3443f92eddbf13ecefe42.png",
"url": "https://yd-ali-oss.oss-cn-shanghai-finance-1-pub.aliyuncs.com/png/2026/01/13/ce6f4781b8f3443f92eddbf13ecefe42.png"
},
{
"materialBizId": "material_IeS4kTopn06fGzbe",
"fileBizIdList": []
},
"fileName": "icon4.png",
"fileType": "png",
"fileUrl": "https://yd-ali-oss.oss-cn-shanghai-finance-1-pub.aliyuncs.com/png/2026/01/13/24e9786aa52d48caba0bfc48e7f4f146.png",
"url": "https://yd-ali-oss.oss-cn-shanghai-finance-1-pub.aliyuncs.com/png/2026/01/13/24e9786aa52d48caba0bfc48e7f4f146.png"
}
],
"apiPremiumRemittanceFileDtoList": [
{
"materialBizId": "material_ykfOPMkoBvxsy1Y8",
"fileBizIdList": []
"fileName": "icon5.png",
"fileType": "png",
"fileUrl": "https://yd-ali-oss.oss-cn-shanghai-finance-1-pub.aliyuncs.com/png/2026/01/13/bca0400a90ae4c1da2373e3cf4de0fc7.png",
"url": "https://yd-ali-oss.oss-cn-shanghai-finance-1-pub.aliyuncs.com/png/2026/01/13/bca0400a90ae4c1da2373e3cf4de0fc7.png"
},
{
"materialBizId": "material_dGGFvKISJBz9OhZg",
"fileBizIdList": []
"fileName": "homeSelect1.png",
"fileType": "png",
"fileUrl": "https://yd-ali-oss.oss-cn-shanghai-finance-1-pub.aliyuncs.com/png/2026/01/13/138932043c6244cb8a006c2ab81b4bab.png",
"url": "https://yd-ali-oss.oss-cn-shanghai-finance-1-pub.aliyuncs.com/png/2026/01/13/138932043c6244cb8a006c2ab81b4bab.png"
}
]
],
"id": 1768282536268
}
......@@ -430,6 +430,7 @@ const handleRemoteSelectChange = async (row, column, father) => {
row.showSumInsured = false
}
row.insuranceType = item.label
row.insuranceCategoryBizId = item.code
}
})
}
......@@ -750,9 +751,16 @@ const setFormValue = (obj, formData) => {
loading.value = true
let processedData = JSON.parse(JSON.stringify(formData))
// 重疾险要加上row.showSumInsured = true便于控制重疾险保额输入框的显示
// 重疾险要加上row.showSumInsured = true便于控制重疾险保额输入框的显示
for (const section of processedData) {
if (section.keyType == 'Array') {
if (section.key == 'existingSecurityOwner') {
obj[section.key].forEach(item => {
if (item.insuranceCategoryBizId == 'CI') {
item.showSumInsured = true
}
})
}
section.data = obj[section.key] ? obj[section.key] : []
} else if (section.keyType == 'Object') {
obj[section.key].premiumFundingSource = obj[section.key].premiumFundingSource
......
......@@ -516,7 +516,10 @@ const searchSelectList = async (query, fieldKey) => {
projectBizId: userStore.projectInfo.projectBizId,
tenantBizId: userStore.projectInfo.tenantBizId,
fieldBizId: 'field_olk1qZe81qHHKXbw',
fieldValueBizId: 'field_value_yXzTigvgUdRMFpoR'
fieldValueBizId:
fieldKey === 'productLaunchName'
? 'field_value_yXzTigvgUdRMFpoR'
: 'field_value_uOfJH5ucA2YwJpbn'
}
const response = await secondAdditonalList(params)
......
......@@ -17,8 +17,6 @@
</template>
<!-- 列表区域 -->
<template #table>
<!-- 统计信息卡片 -->
<el-table
:data="tableData"
@selection-change="handleSelectionChange"
......@@ -29,39 +27,31 @@
v-loading="loading"
>
<el-table-column type="selection" width="40" />
<el-table-column prop="fortuneBizType" label="应付单类型" width="120" sortable>
<!-- <el-table-column prop="fortuneBizType" label="应付单类型" width="120" sortable>
<template #default="{ row }">
{{ getFortuneBizTypeLabel(row.fortuneBizType) }}
</template>
</el-table-column> -->
<el-table-column prop="policyNo" label="保单号" />
<el-table-column prop="receiptStatus" label="回执状态">
<template #default="{ row }">
{{ getDictLabel('receipt_status', row.receiptStatus) }}
</template>
</el-table-column>
<el-table-column prop="policyNo" label="保单号" width="120" sortable />
<el-table-column prop="insuranceCompany" label="保险公司" width="120" sortable />
<el-table-column
prop="commissionPaidAmount"
label="累积已入账金额"
width="120"
sortable
/>
<el-table-column prop="commissionPaidRatio" label="累积已入账比例" width="120" sortable />
<el-table-column prop="fortuneName" label="出账项目" width="130" sortable />
<el-table-column prop="fortunePeriod" label="出账期数" width="130" sortable />
<el-table-column prop="fortuneTotalPeriod" label="总期数" width="120" sortable />
<el-table-column prop="broker" label="转介人" width="130" sortable />
<el-table-column prop="team" label="所属团队" width="120" sortable />
<el-table-column prop="amount" label="应出账金额" width="140" sortable />
<el-table-column prop="currency" label="出账币种" width="130" sortable />
<el-table-column prop="fortunePaidAmount" label="已出账金额" width="120" sortable />
<el-table-column prop="fortuneUnpaidAmount" label="剩余出账金额" width="120" sortable />
<el-table-column prop="currentPaymentAmount" label="本期出账金额" width="120" sortable />
<el-table-column prop="fortuneUnpaidRatio" label="剩余出账比例" width="120" sortable />
<el-table-column prop="status" label="出账状态" width="120" sortable>
<el-table-column prop="insurer" label="保险公司" />
<el-table-column prop="policyHolder" label="保单持有人" />
<el-table-column prop="createTime" label="创建时间">
<template #default="{ row }">
{{ row.createTime ? formatToDate(row.createTime) : '' }}
</template>
</el-table-column>
<el-table-column prop="receiptDate" label="回执日期">
<template #default="{ row }">
{{ getDictLabel('csf_fortune_status', row.status) }}
{{ row.receiptDate ? formatToDate(row.receiptDate) : '' }}
</template>
</el-table-column>
<el-table-column prop="premium" label="期交保费" width="120" sortable />
<el-table-column prop="payoutDate" label="出账日(实)" width="120" sortable />
<el-table-column prop="remark" label="备注" width="120" sortable />
<el-table-column fixed="right" label="操作" min-width="120">
<template #default="{ row }">
<el-popover placement="right" :width="200" trigger="click">
......@@ -84,15 +74,15 @@
</el-table>
</template>
</CommonPage>
<!-- 新增出账检核页面-->
<!-- 新增保单回执页面-->
<CommonDialog
dialogTitle="新增出账检核"
:dialogTitle="receiptsDialogTitle"
dialogWidth="80%"
:openDialog="addCheckRecordFormDialogFlag"
:openDialog="receiptsFlag"
:showAction="true"
:showClose="true"
@close="addCheckRecordFormDialogFlag = false"
@confirm="addCheckRecordaddBatchapi"
@close="receiptsFlag = false"
@confirm="addReceipts"
>
<SearchForm
ref="addCheckRecordFormRef"
......@@ -137,7 +127,7 @@
</template>
<script setup>
import { ref, reactive } from 'vue'
import { ref, reactive, watch } from 'vue'
import CommonPage from '@/components/commonPage'
import CommonDialog from '@/components/commonDialog'
import SearchForm from '@/components/SearchForm/SearchForm.vue'
......@@ -145,14 +135,18 @@ import { ElMessage } from 'element-plus'
import { formatCurrency } from '@/utils/number'
// 接口
import {
getPolicyFortuneList,
addCheckRecordaddBatch,
updatePayoutAmount,
downloadPolicyFortuneAccount
} from '@/api/financial/commission'
import {
getPolicyReceiptList,
addPolicyReceipt,
getPolicyReceipt,
EditPolicyReceipt
} from '@/api/sign/policy'
import useUserStore from '@/store/modules/user'
import { loadDicts, getDictLabel } from '@/utils/useDict'
import { getToken } from '@/utils/auth'
const { proxy } = getCurrentInstance()
const userStore = useUserStore()
// 分页相关
const currentPage = ref(1)
......@@ -162,29 +156,36 @@ const loading = ref(false)
const selectedRow = ref(null)
const searchFormRef = ref(null)
const searchParams = ref({})
const receiptsFlag = ref(false) //回执弹窗
const receiptsDialogTitle = ref('新增保单回执')
const searchConfig = ref([
{
type: 'input',
prop: 'appointmentNo',
label: '预约编号'
},
{
type: 'input',
prop: 'policyNo',
label: '保单号'
},
{
type: 'select',
prop: 'statusList',
label: '出账状态',
multiple: true,
dictType: 'csf_fortune_status'
prop: 'receiptStatus',
label: '回执状态',
multiple: false,
dictType: 'receipt_status'
},
{
type: 'select',
prop: 'insuranceCompanyBizIdList',
prop: 'insurer',
label: '保险公司',
api: '/insurance/base/api/insuranceCompany/page',
keywordField: 'queryContent',
requestParams: { pageNo: 1, pageSize: 20 },
placeholder: '输入保险公司名称搜索',
debounceWait: 500, // 自定义防抖时间
multiple: true,
multiple: false,
valueKey: 'insuranceCompanyBizId',
labelKey: 'abbreviation',
transform: res => {
......@@ -193,47 +194,15 @@ const searchConfig = ref([
}
},
{
type: 'select',
prop: 'productLaunchBizIdList',
label: '产品计划',
api: '/product/api/relProjectProductLaunch/parameter/page',
keywordField: 'productName',
requestParams: {
tenantBizId: userStore.projectInfo.tenantBizId || '',
projectBizId: userStore.projectInfo.projectBizId || '',
fieldBizId: 'field_olk1qZe81qHHKXbw',
fieldValueBizId: 'field_value_uOfJH5ucA2YwJpbn',
pageNo: 1,
pageSize: 20
},
placeholder: '输入产品计划名称搜索',
debounceWait: 500, // 自定义防抖时间
multiple: true,
valueKey: 'productLaunchBizId',
labelKey: 'productName',
transform: res => {
return res?.data.records || []
}
},
{
type: 'daterange',
prop: 'payoutDate',
label: '出账日(估)',
startPlaceholder: '开始时间',
endPlaceholder: '结束时间'
type: 'input',
prop: 'policyHolder',
label: '保单持有人'
}
// {
// type: 'select',
// prop: 'status',
// label: '入账状态',
// multiple: true,
// dictType: 'csf_expected_commission_status'
// },
])
// 表格操作菜单
const dropdownItems = [
{ label: '设置本期出账金额', value: 'setPayRoll' }
// { label: '更新', value: 'editRecord' },
// { label: '新增回执', value: 'addReceipts' },
{ label: '更新回执', value: 'editRecord' }
// { label: '查看记录', value: 'viewRecord' }
]
// 应收单类型
......@@ -250,85 +219,106 @@ const getFortuneBizTypeLabel = value => {
const addCheckRecordFormModel = ref({})
const addCheckRecordFormRef = ref(null)
const addCheckRecordFormDialogFlag = ref(false)
const addCheckRecordConfig = [
{
type: 'select',
prop: 'fortuneBizType',
label: '应付单类型',
options: fortuneBizTypeOptions
prop: 'policyNo',
label: '保单号码',
api: '/csf/api/policy/list/page/vo',
keywordField: 'policyNo',
requestParams: { pageNo: 1, pageSize: 20 },
placeholder: '输入转介人名称搜索',
debounceWait: 500, // 自定义防抖时间
valueKey: 'policyNo',
labelKey: 'policyNo',
onChangeExtraFields: {
insured: 'insured', // 选择了保单号码,自动填充保单受保人
policyHolder: 'policyHolder',
insuranceCompany: 'insuranceCompany',
productName: 'productName',
currency: 'currency',
paymentPremium: 'paymentPremium',
paymentTerm: 'paymentTerm'
},
transform: res => {
return res?.data.records || []
},
rules: [{ required: true, message: '请输入保单号码', trigger: 'blur' }]
},
{
type: 'select',
prop: 'status',
label: '出账状态',
dictType: 'csf_expected_fortune_status'
type: 'input',
prop: 'insured',
label: '保单受保人',
disabled: true
},
{
type: 'input',
prop: 'policyNo',
label: '关联保单号',
visible: formData => formData.fortuneBizType === 'R'
prop: 'policyHolder',
label: '保单持有人姓名',
disabled: true
},
{
type: 'input',
prop: 'fortunePeriod',
label: '佣金期数',
inputType: 'decimal',
visible: formData => formData.fortuneBizType === 'R',
rules: [{ pattern: /^\d+$/, message: '只能输入正整数', trigger: 'blur' }]
prop: 'insuranceCompany',
label: '保险公司名称',
disabled: true
},
{
type: 'input',
prop: 'fortuneTotalPeriod',
label: '总期数',
inputType: 'decimal',
visible: formData => formData.fortuneBizType === 'R',
rules: [{ pattern: /^\d+$/, message: '只能输入正整数', trigger: 'blur' }]
prop: 'productName',
label: '保单产品名称',
disabled: true
},
{
type: 'date',
prop: 'actualPayoutDate',
label: '出账日(实)',
placeholder: '请选择',
maxDate: 'today'
type: 'input',
prop: 'paymentTerm',
label: '缴费年限',
disabled: true
},
{
type: 'input',
prop: 'amount',
label: '出账金额',
inputType: 'decimal',
rules: [{ pattern: /^\d+$/, message: '只能输入正整数', trigger: 'blur' }]
prop: 'paymentPremium',
label: '保单年缴保费',
disabled: true
},
{
type: 'select',
prop: 'currency',
label: '出账币种',
dictType: 'bx_currency_type'
label: '保单货币',
dictType: 'bx_currency_type',
disabled: true
},
{
type: 'select',
prop: 'fortuneType',
label: '出账项目',
dictType: 'csf_fortune_type'
prop: 'receiptStatus',
label: '回执状态',
dictType: 'receipt_status',
rules: [{ required: true, message: '请输入保单号码', trigger: 'blur' }]
},
{
type: 'select',
prop: 'brokerBizId',
label: '转介人',
api: '/insurance/base/api/userSaleExpand/page',
keywordField: 'realName',
requestParams: { pageNo: 1, pageSize: 20 },
placeholder: '输入转介人名称搜索',
debounceWait: 500, // 自定义防抖时间
valueKey: 'userSaleBizId',
labelKey: 'realName',
onChangeExtraFields: {
broker: 'realName', // 自动同步 raw.name 到 reconciliationCompany
reconciliationCompanyCode: 'code'
},
transform: res => {
return res?.data.records || []
}
type: 'date',
prop: 'receiptDate',
label: '回执日期',
placeholder: '请选择',
maxDate: 'today',
rules: [{ required: true, message: '请输入保单号码', trigger: 'blur' }]
},
{
type: 'upload',
prop: 'policyHolderSignatureList',
label: '签名照片',
uploadType: 'image',
multiple: true,
limit: 3,
maxSize: 5 * 1024 * 1024, // 5MB
accept: '.png,.jpg,.jpeg,.webp', // ✅ 改成扩展名!
action: import.meta.env.VITE_APP_BASE_API + '/oss/api/oss/upload',
headers: { Authorization: 'Bearer ' + getToken() },
listType: 'picture-card',
defaultValue: [],
span: 24
}
]
......@@ -362,10 +352,28 @@ const setPayoutAmountConfig = [
const statisticsData = ref({})
// 弹窗相关
const dialogFlag = ref(false)
// 加载表格数据
const getReceiptsDetail = async row => {
try {
const res = await getPolicyReceipt(row.policyReceiptBizId)
addCheckRecordFormModel.value = {
...res.data,
receiptDate: proxy.formatToDate(row.receiptDate),
policyHolderSignatureList: res.data.policyHolderSignatureList.map(item => ({ url: item }))
}
receiptsFlag.value = true
receiptsDialogTitle.value = '编辑保单回执'
} catch (error) {
console.error('加载数据失败:', error)
ElMessage.error(error.message || '加载数据失败')
receiptsFlag.value = false
}
}
// 按钮事件处理
const handleAdd = () => {
addCheckRecordFormDialogFlag.value = true
console.log(addCheckRecordFormModel.value)
receiptsFlag.value = true
}
const handleExport = () => {
......@@ -375,26 +383,30 @@ const handleReset = () => {
// 重置搜索表单
searchFormRef.value.resetForm()
console.log('表单已重置')
currentPage.value = 1
loadTableData()
}
const handleQuery = async () => {
const params = searchFormRef.value.getFormData()
console.log('params', params)
loadTableData(params)
}
const visibleDefaultButtons = ref(['add', 'import', 'export', 'reset', 'query'])
const visibleDefaultButtons = ref(['add', 'reset', 'query'])
// 按钮配置
const operationBtnList = ref([
{
key: 'add',
direction: 'left',
label: '新增出账',
label: '新增回执',
click: handleAdd
},
{
key: 'export',
direction: 'right',
click: handleExport
},
// {
// key: 'export',
// direction: 'right',
// click: handleExport
// },
{
key: 'reset',
direction: 'right',
......@@ -414,17 +426,13 @@ const loadTableData = async (searchParams = {}) => {
const params = {
pageNo: currentPage.value,
pageSize: pageSize.value,
...searchParams,
payoutDateStart: searchParams.payoutDate?.[0] || undefined,
payoutDateEnd: searchParams.payoutDate?.[1] || undefined,
payoutDate: undefined
...searchParams
}
const res = await getPolicyFortuneList(params)
tableData.value = res.data.page.records || []
pageTotal.value = res.data.page.total || 0
pageSize.value = res.data.page.size || 0
// 统计信息
statisticsData.value = res.data.statisticsVO || {}
const res = await getPolicyReceiptList(params)
tableData.value = res.data.records || []
pageTotal.value = res.data.total || 0
pageSize.value = res.data.size || 0
} catch (error) {
console.error('加载数据失败:', error)
ElMessage.error(error.message || '加载数据失败')
......@@ -448,27 +456,51 @@ const tableData = ref([])
const handleSelect = (e, row) => {
console.log('选中行:', e, row)
selectedRow.value = row
if (e === 'setPayRoll') {
setPayoutAmountDialogFlag.value = true
if (e === 'addReceipts') {
receiptsFlag.value = true
receiptsDialogTitle.value = '新增保单回执'
} else if (e == 'editRecord') {
getReceiptsDetail(row)
}
}
const addCheckRecordaddBatchapi = async data => {
const formData = addCheckRecordFormRef.value.getFormData()
console.log('新增出账检核记录:', formData)
const params = [{ ...formData }]
const addReceipts = async () => {
try {
const res = await addCheckRecordaddBatch(params)
// ✅ 正确:await validate(),成功时返回表单数据
const formData = await addCheckRecordFormRef.value.validate()
console.log('新增保单回执:', formData)
const params = {
policyNo: formData.policyNo,
receiptStatus: formData.receiptStatus,
receiptDate: proxy.formatToDateTime(formData.receiptDate),
policyHolderSignatureList:
formData.policyHolderSignatureList.length > 0
? formData.policyHolderSignatureList.map(item => item.url)
: []
}
let res = {}
if (formData.policyReceiptBizId) {
params.policyReceiptBizId = formData.policyReceiptBizId
res = await EditPolicyReceipt(params)
} else {
res = await addPolicyReceipt(params)
}
if (res.code === 200) {
ElMessage.success('新增出账检核记录成功')
addCheckRecordFormDialogFlag.value = false
ElMessage.success(`${receiptsDialogTitle.value}成功`)
receiptsFlag.value = false // ✅ 校验 & 提交成功后才关闭
addCheckRecordFormRef.value.resetForm()
loadTableData()
} else {
ElMessage.error(res.msg || '新增出账检核记录失败')
ElMessage.error(res.msg || `${receiptsDialogTitle.value}失败`)
}
} catch (error) {
console.error('新增出账检核记录失败:', error)
ElMessage.error(error.message || '新增出账检核记录失败')
// ❌ 校验失败或提交异常都会进入这里
console.error('操作失败:', error)
// ⚠️ 不设置 receiptsFlag = false,弹窗保持打开
ElMessage.warning('请检查表单填写是否正确')
// 注意:Element Plus 的 validate 失败时,错误信息已自动显示在表单项下方
}
}
......@@ -501,32 +533,9 @@ const handleSelectionChange = val => {
updatePayRollStatusDisable.value = val.length === 0
}
const downloadPolicyFortuneAccountapi = async data => {
console.log('生成出账清单:', data)
try {
const res = await downloadPolicyFortuneAccount({
fortuneBizIdList: multipleSelection.value.map(item => item.fortuneBizId)
})
if (res.code === 200) {
ElMessage.success('完成检核,等待关账')
loadTableData()
}
} catch (error) {
console.error('检核失败:', error)
ElMessage.error(error.response?.data?.msg || '检核失败')
}
}
import FileUploadPreview from '@/components/fileUploadPreview/fileUploadPreview.vue'
const importCheckRecordFlag = ref(false)
// 如果后端接收的就是当前格式,可直接透传
const formatForBackend = rows => {
return rows.map(row => ({
...row,
amount: Number(row.amount) || 0,
exchangeRate: Number(row.exchangeRate) || 1
}))
}
const onSubmit = data => {
console.log('提交给后端的数据:', data)
......@@ -536,13 +545,19 @@ const onSubmit = data => {
// 获取入账状态,字典值转化方法
onMounted(async () => {
try {
await loadDicts(['csf_fortune_status'])
await loadDicts(['receipt_status'])
} catch (error) {
console.error('字典加载失败', error)
} finally {
loading.value = false
}
})
watch(receiptsFlag, newVal => {
if (!newVal) {
addCheckRecordFormModel.value = {}
receiptsDialogTitle.value = ''
}
})
</script>
<style scoped>
......
<template>
<div class="container">
<CommonPage
:operationBtnList="operationBtnList"
:visibleDefaultButtons="visibleDefaultButtons"
:showSearchForm="true"
:show-pagination="true"
:total="pageTotal"
:current-page="currentPage"
:page-size="pageSize"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
>
<!-- 搜索区域 -->
<template #searchForm>
<SearchForm ref="searchFormRef" :config="searchConfig" />
</template>
<!-- 列表区域 -->
<template #table>
<el-table
:data="tableData"
@selection-change="handleSelectionChange"
height="400"
border
highlight-current-row
style="width: 100%"
v-loading="loading"
>
<el-table-column type="selection" width="40" />
<el-table-column prop="policyNo" label="保单号" width="150" fixed="left" />
<el-table-column prop="insuranceCompany" label="保险公司" width="150" />
<el-table-column prop="policyFollowStatus" label="新单状态" width="150">
<template #default="{ row }">
{{ getDictLabel('csf_policy_follow_status_new', row.policyFollowStatus) }}
</template>
</el-table-column>
<el-table-column prop="reconciliationStatus" label="对账状态" width="150">
<template #default="{ row }">
{{ getDictLabel('reconciliation_status', row.reconciliationStatus) }}
</template>
</el-table-column>
<el-table-column prop="policyStatus" label="保单状态" width="150">
<template #default="{ row }">
{{ getDictLabel('csf_policy_status_new', row.policyStatus) }}
</template>
</el-table-column>
<el-table-column prop="remainingUnpaidAmount" label="待付款金额" width="150">
<template #default="{ row }">
{{ row.remainingUnpaidAmount ? formatCurrency(row.remainingUnpaidAmount) : '' }}
</template>
</el-table-column>
<el-table-column prop="remainingUnpaidCurrency" label="待付款币种" width="150">
<template #default="{ row }">
{{ getDictLabel('bx_currency_type', row.remainingUnpaidCurrency) }}
</template>
</el-table-column>
<el-table-column prop="paymentAmount" label="付款金额" width="150">
<template #default="{ row }">
{{ row.paymentAmount ? formatCurrency(row.paymentAmount) : '' }}
</template>
</el-table-column>
<el-table-column prop="paymentCurrency" label="付款币种" width="150">
<template #default="{ row }">
{{ getDictLabel('bx_currency_type', row.paymentCurrency) }}
</template>
</el-table-column>
<el-table-column prop="paymentMethod" label="缴费方式" width="150">
<template #default="{ row }">
{{ getDictLabel('csf_ap_first_issue', row.paymentMethod) }}
</template>
</el-table-column>
<el-table-column prop="recognizedAmount" label="保司认定金额" width="150">
<template #default="{ row }">
{{ row.recognizedAmount ? formatCurrency(row.recognizedAmount) : '' }}
</template>
</el-table-column>
<el-table-column prop="recognizedCurrency" label="保司对账币种" width="150">
<template #default="{ row }">
{{ getDictLabel('bx_currency_type', row.recognizedCurrency) }}
</template>
</el-table-column>
<el-table-column prop="payingBank" label="付款银行" width="150" />
<el-table-column prop="payer" label="付款人" width="150" />
<el-table-column prop="policyHolder" label="投保人" width="150" />
<el-table-column prop="insured" label="受保人" width="150" />
<el-table-column prop="brokerName" label="转介人" width="150" />
<el-table-column prop="createTime" label="创建时间" width="150">
<template #default="{ row }">
{{ row.createTime ? formatToDate(row.createTime) : '' }}
</template>
</el-table-column>
<el-table-column prop="updateTime" label="更新时间" width="150">
<template #default="{ row }">
{{ row.updateTime ? formatToDate(row.updateTime) : '' }}
</template>
</el-table-column>
<el-table-column prop="reconciliationCompany" label="对账公司" width="150">
</el-table-column>
<el-table-column fixed="right" label="操作" min-width="120">
<template #default="{ row }">
<el-popover placement="right" :width="200" trigger="click">
<template #reference>
<el-icon>
<MoreFilled />
</el-icon>
</template>
<el-menu @select="handleSelect($event, row)" popper-class="custom-menu">
<el-menu-item
:index="item.value"
v-for="item in dropdownItems"
:key="item.value"
>{{ item.label }}</el-menu-item
>
</el-menu>
</el-popover>
</template>
</el-table-column>
</el-table>
</template>
</CommonPage>
<!-- 新增保单对账-->
<CommonDialog
:dialogTitle="receiptsDialogTitle"
dialogWidth="80%"
:openDialog="receiptsFlag"
:showAction="true"
:showClose="true"
@close="receiptsFlag = false"
@confirm="addReceipts"
>
<SearchForm
ref="addCheckRecordFormRef"
:config="addCheckRecordConfig"
v-model="addCheckRecordFormModel"
/>
<div>
<el-button type="primary" icon="plus" @click="addRemittance" style="margin-bottom: 10px"
>新增汇款记录</el-button
>
<div
v-if="
addCheckRecordFormModel.apiPremiumRemittanceDtoList &&
addCheckRecordFormModel.apiPremiumRemittanceDtoList.length > 0
"
class="otherFileBox"
>
<el-table border :data="addCheckRecordFormModel.apiPremiumRemittanceDtoList" size="small">
<el-table-column label="缴费方式" prop="paymentMethod">
<template #default="{ row }">
{{ getDictLabel('csf_ap_first_issue', row.paymentMethod) }}
</template>
</el-table-column>
<el-table-column label="付款金额" prop="paymentAmount" />
<el-table-column label="付款币种" prop="paymentCurrency">
<template #default="{ row }">
{{ getDictLabel('bx_currency_type', row.paymentCurrency) }}
</template>
</el-table-column>
<el-table-column label="付款人与保单关系" prop="paymentRel">
<template #default="{ row }">
{{ getDictLabel('rel_pay_policy', row.paymentRel) }}
</template>
</el-table-column>
<el-table-column label="付款人" prop="payer" />
<el-table-column label="付款银行" prop="payingBank" />
<el-table-column label="付款账号" prop="paymentAccount" />
<el-table-column label="操作" width="100">
<template #default="{ row, $index }">
<el-button type="primary" link @click="editRemittance(row, $index)">
修改
</el-button>
<el-button type="danger" link @click="removeRemittance(row, $index)">
删除
</el-button>
</template>
</el-table-column>
</el-table>
</div>
</div>
</CommonDialog>
<!-- 设置认定结果 -->
<CommonDialog
dialogTitle="设置认定结果"
dialogWidth="80%"
:openDialog="showAffirm"
:showAction="true"
:showClose="true"
@close="showAffirm = false"
>
<SearchForm ref="affirmFormRef" :config="affirmConfig" v-model="affirmFormModel" />
</CommonDialog>
<!-- 汇款弹窗 -->
<CommonDialog
:dialogTitle="remittanceDialogTitle"
dialogWidth="80%"
:openDialog="showRemittance"
:showAction="true"
:showClose="true"
@close="showRemittance = false"
@confirm="confirmRemittance"
>
<el-scrollbar max-height="500px">
<div
:style="{
marginBottom: remittanceFormModel.apiPremiumRemittanceFileDtoList?.length
? '0px'
: '100px'
}"
>
<SearchForm
ref="remittanceFormRef"
:config="remittanceConfig"
v-model="remittanceFormModel"
/>
<div
v-if="remittanceFormModel.apiPremiumRemittanceFileDtoList?.length"
class="otherFileBox"
>
<el-table border :data="tempOtherFileList" size="small">
<el-table-column prop="fileName" label="文件名" />
<el-table-column prop="fileType" label="文件类型" />
<el-table-column prop="creatorName" label="上传人" />
<el-table-column prop="createTime" label="上传时间">
<template #default="{ row }">
{{ row.createTime ? formatToDate(row.createTime) : '' }}
</template>
</el-table-column>
<el-table-column label="操作" width="100">
<template #default="{ row, $index }">
<el-button type="danger" link @click="removeOtherFile(row, $index)">
删除
</el-button>
</template>
</el-table-column>
</el-table>
</div>
</div>
</el-scrollbar>
</CommonDialog>
</div>
</template>
<script setup>
import { ref, reactive, watch } from 'vue'
import CommonPage from '@/components/commonPage'
import CommonDialog from '@/components/commonDialog'
import SearchForm from '@/components/SearchForm/SearchForm.vue'
import { ElMessage } from 'element-plus'
import { formatCurrency } from '@/utils/number'
// 接口
import { updatePayoutAmount } from '@/api/financial/commission'
import {
premiumReconciliationList,
EditPolicyReceipt,
editPremiumRemittance,
addPremiumReconciliation,
getPremiumReconciliationInfo,
deletePremiumRemittanceFile,
getPremiumRemittanceFileList,
editSiglePremiumRemittance,
getPremiumRemittanceListApi
} from '@/api/sign/policy'
import useUserStore from '@/store/modules/user'
import { loadDicts, getDictLabel } from '@/utils/useDict'
import { getToken } from '@/utils/auth'
const { proxy } = getCurrentInstance()
const userStore = useUserStore()
// 分页相关
const currentPage = ref(1)
const pageSize = ref(10)
const pageTotal = ref(0)
const loading = ref(false)
const selectedRow = ref(null)
const searchFormRef = ref(null)
const searchParams = ref({})
const receiptsFlag = ref(false) //回执弹窗
const receiptsDialogTitle = ref('新增保单回执')
const remittanceDialogTitle = ref('新增汇款')
const showRemittance = ref(false)
const remittanceFormRef = ref(null)
const searchConfig = ref([
{
type: 'select',
prop: 'insurer',
label: '保险公司',
api: '/insurance/base/api/insuranceCompany/page',
keywordField: 'queryContent',
requestParams: { pageNo: 1, pageSize: 20 },
placeholder: '输入保险公司名称搜索',
debounceWait: 500, // 自定义防抖时间
multiple: false,
valueKey: 'insuranceCompanyBizId',
labelKey: 'abbreviation',
transform: res => {
console.log(res)
return res?.data.records || []
}
},
{
type: 'select',
prop: 'insurer',
label: '付款银行',
api: '/insurance/base/api/insuranceCompany/page',
keywordField: 'queryContent',
requestParams: { pageNo: 1, pageSize: 20 },
placeholder: '输入保险公司名称搜索',
debounceWait: 500, // 自定义防抖时间
multiple: false,
valueKey: 'insuranceCompanyBizId',
labelKey: 'abbreviation',
transform: res => {
console.log(res)
return res?.data.records || []
}
},
{
type: 'input',
prop: 'appointmentNo',
label: '付款人'
},
{
type: 'input',
prop: 'policyNo',
label: '保单号'
},
{
type: 'input',
prop: 'policyNo',
label: '预约编号'
},
{
type: 'input',
prop: 'policyNo',
label: '投保人'
},
{
type: 'select',
prop: 'receiptStatus',
label: '缴费方式',
multiple: false,
dictType: 'receipt_status'
},
{
type: 'select',
prop: 'receiptStatus',
label: '新单状态',
multiple: false,
dictType: 'receipt_status'
},
{
type: 'select',
prop: 'receiptStatus',
label: '保单状态',
multiple: false,
dictType: 'receipt_status'
},
{
type: 'select',
prop: 'receiptStatus',
label: '对账类型',
multiple: false,
dictType: 'receipt_status'
},
{
type: 'date',
prop: 'receiptDate',
label: '付款日期',
placeholder: '请选择',
maxDate: 'today'
}
])
// 表格操作菜单
const dropdownItems = [
{ label: '设置认定结果', value: 'settingResult' },
{ label: '编辑', value: 'editRecord' }
// { label: '查看记录', value: 'viewRecord' }
]
// 应收单类型
const fortuneBizTypeOptions = [
{ value: 'R', label: '关联保单应付单' },
{ value: 'U', label: '非关联保单应付单' }
]
// ==============设置认定结果开始============
const showAffirm = ref(false)
const affirmFormRef = ref(null)
const affirmFormModel = ref({})
const affirmConfig = [
{
type: 'input',
prop: 'paymentAmount',
label: '保司认定金额',
inputType: 'decimal',
rules: [
{ required: true, message: '请输入', trigger: 'blur' },
{ pattern: /^\d+$/, message: '只能输入正整数', trigger: 'blur' }
]
},
{
type: 'select',
prop: 'paymentCurrency',
label: '保司认定币种',
dictType: 'bx_currency_type',
rules: [{ required: true, message: '请输入认定币种', trigger: 'blur' }]
},
{
type: 'input',
prop: 'insured',
label: '此保单剩余待付金额',
disabled: true
},
{
type: 'input',
prop: 'insurede',
label: '此保单剩余待付币种',
disabled: true
}
]
// ==============设置认定结果结束============
// ==============新增对账开始============
const addCheckRecordFormModel = ref({ apiPremiumRemittanceDtoList: [] })
const addCheckRecordFormRef = ref(null)
// 设置汇款
const remittanceFormModel = ref({ apiPremiumRemittanceFileDtoList: [] })
const tempOtherFileList = ref([]) //临时存储其他资料数据
const deleteObjkeys = {
reconciliationType: 'reconciliationType',
applicant: 'applicant',
policyNo: 'policyNo',
insured: 'insured',
insuranceCompany: 'insuranceCompany'
}
const remittanceConfig = [
{
type: 'select',
prop: 'paymentMethod',
label: '缴费方式',
dictType: 'csf_ap_first_issue',
rules: [{ required: true, message: '请输入缴费方式', trigger: 'blur' }]
},
{
type: 'input',
prop: 'paymentAmount',
label: '付款金额',
inputType: 'decimal',
rules: [
{ required: true, message: '请输入', trigger: 'blur' },
{ pattern: /^\d+$/, message: '只能输入正整数', trigger: 'blur' }
]
},
{
type: 'select',
prop: 'paymentCurrency',
label: '付款币种',
dictType: 'bx_currency_type',
rules: [{ required: true, message: '请输入付款币种', trigger: 'blur' }]
},
{
type: 'select',
prop: 'paymentRel',
label: '付款人与保单关系',
dictType: 'rel_pay_policy',
rules: [{ required: true, message: '请输入', trigger: 'blur' }]
},
{
type: 'input',
prop: 'payer',
label: '付款人',
rules: [{ required: true, message: '请输入', trigger: 'blur' }]
},
{
type: 'select',
prop: 'payingBank',
label: '付款银行',
api: '/base/api/bank/page',
keywordField: 'policyNo',
requestParams: { pageNo: 1, pageSize: 20 },
placeholder: '输入转介人名称搜索',
debounceWait: 500, // 自定义防抖时间
valueKey: 'bankBizId',
labelKey: 'bankName',
// onChangeExtraFields: {
// insured: 'insured', // 选择了保单号码,自动填充保单受保人
// policyHolder: 'policyHolder',
// insuranceCompany: 'insuranceCompany',
// productName: 'productName',
// currency: 'currency',
// paymentPremium: 'paymentPremium',
// paymentTerm: 'paymentTerm'
// },
transform: res => {
return res?.data.records || []
}
},
{
type: 'input',
prop: 'paymentAccount',
label: '付款账号'
},
{
type: 'input',
prop: 'applicant',
label: '申请人',
disabled: true
},
{
type: 'select',
prop: 'policyNo',
label: '保单号码',
api: '/csf/api/policy/list/page/vo',
keywordField: 'policyNo',
requestParams: { pageNo: 1, pageSize: 20 },
placeholder: '输入转介人名称搜索',
debounceWait: 500, // 自定义防抖时间
valueKey: 'policyNo',
labelKey: 'policyNo',
onChangeExtraFields: {
insured: 'insured', // 选择了保单号码,自动填充保单受保人
policyHolder: 'policyHolder',
insuranceCompany: 'insuranceCompany',
productName: 'productName',
currency: 'currency',
paymentPremium: 'paymentPremium',
paymentTerm: 'paymentTerm'
},
transform: res => {
return res?.data.records || []
},
disabled: true
},
{
type: 'input',
prop: 'insured',
label: '被保人',
disabled: true
},
{
type: 'input',
prop: 'insuranceCompany',
label: '保险公司',
disabled: true
},
{
type: 'select',
prop: 'reconciliationType',
label: '对账类型',
disabled: true,
dictType: 'reconciliation_type'
},
{
type: 'upload',
prop: 'paymentVoucherList',
label: '支付凭证',
uploadType: 'image',
multiple: true,
limit: 5,
maxSize: 5 * 1024 * 1024, // 5MB
accept: '.png,.jpg,.jpeg,.webp', // ✅ 改成扩展名!
action: import.meta.env.VITE_APP_BASE_API + '/oss/api/oss/upload',
headers: { Authorization: 'Bearer ' + getToken() },
listType: 'picture-card',
defaultValue: [],
span: 24,
rules: [{ required: true, message: '请上传支付凭证', trigger: 'blur' }]
},
{
type: 'upload',
prop: 'accountVerificationList',
label: '账户证明',
uploadType: 'image',
multiple: true,
limit: 5,
maxSize: 5 * 1024 * 1024, // 5MB
accept: '.png,.jpg,.jpeg,.webp', // ✅ 改成扩展名!
action: import.meta.env.VITE_APP_BASE_API + '/oss/api/oss/upload',
headers: { Authorization: 'Bearer ' + getToken() },
listType: 'picture-card',
defaultValue: [],
span: 24,
rules: [{ required: true, message: '请上传账户证明', trigger: 'blur' }]
},
{
type: 'upload',
prop: 'apiPremiumRemittanceFileDtoList',
label: '其他资料',
uploadType: 'file',
multiple: true,
limit: 5,
maxSize: 10 * 1024 * 1024, // 5MB
accept: '.png,.jpg,.jpeg,.webp,.pdf,.doc,.docx,.xls,.xlsx,.txt', // ✅ 支持多种格式
action: import.meta.env.VITE_APP_BASE_API + '/oss/api/oss/upload',
headers: { Authorization: 'Bearer ' + getToken() },
listType: 'text', // ← 按钮样式(非图片卡片)
defaultValue: [],
span: 24,
link: false,
showFileList: false
}
]
// 新增汇款
const addRemittance = async () => {
try {
// 汇款记录详情也需要回显保费对账得一些字段
const formData = await addCheckRecordFormRef.value.validate()
remittanceFormModel.value = {
...formData
}
remittanceFormModel.value.apiPremiumRemittanceFileDtoList = []
tempOtherFileList.value = []
remittanceDialogTitle.value = '新增汇款'
showRemittance.value = true
} catch (error) {
// ❌ 校验失败或提交异常都会进入这里
console.error('操作失败:', error)
ElMessage.warning('必填项不能为空')
}
}
// 删除其他资料附件函数
function removeOtherFile(row, index) {
const list = [...remittanceFormModel.value.apiPremiumRemittanceFileDtoList]
proxy.$modal
.confirm('是否确认删除这个附件?')
.then(function () {
if (row.premiumRemittanceFileBizId) {
console.log('远程删除')
return deletePremiumRemittanceFile(row.premiumRemittanceFileBizId)
} else {
// 本地临时数据,直接从列表中移除
list.splice(index, 1)
// ⚠️ 关键:直接赋值触发响应式更新(SearchForm 会收到新值)
remittanceFormModel.value.apiPremiumRemittanceFileDtoList = tempOtherFileList.value = list
// 注意:这里不需要 return Promise,因为是同步操作
// 但为了统一 then 链,可以 return 一个成功标识
return { code: 200 } // 模拟成功
}
})
.then(res => {
if (res && res.code == 200) {
// 调用附件接口,更新附件列表
otherFileList()
}
})
.catch(() => {})
}
// 获取汇款记录列表
const getPremiumRemittanceList = async () => {
try {
const params = {
premiumReconciliationBizId: addCheckRecordFormModel.value.premiumReconciliationBizId,
pageNo: 1,
pageSize: 9999
}
const res = await getPremiumRemittanceListApi(params)
addCheckRecordFormModel.value.apiPremiumRemittanceDtoList = res.data.records
ElMessage.success(`${remittanceDialogTitle.value}成功`)
showRemittance.value = false // ✅ 校验 & 提交成功后才关闭
remittanceFormRef.value.resetForm()
proxy.$modal.msgSuccess('汇款记录修改成功')
} catch (error) {
console.error('加载数据失败:', error)
ElMessage.error(error.message || '加载数据失败')
}
}
// 获取其他资料得附件列表
const otherFileList = async () => {
try {
const params = {
premiumRemittanceBizId: remittanceFormModel.value.premiumRemittanceBizId,
pageNo: 1,
pageSize: 9999
}
const res = await getPremiumRemittanceFileList(params)
remittanceFormModel.value.apiPremiumRemittanceFileDtoList = tempOtherFileList.value =
res.data.records
proxy.$modal.msgSuccess('附件删除成功')
} catch (error) {
console.error('加载数据失败:', error)
ElMessage.error(error.message || '加载数据失败')
}
}
// 确认汇款记录
const confirmRemittance = async data => {
try {
// ✅ 正确:await validate(),成功时返回表单数据
const formData = await remittanceFormRef.value.validate()
let newFormData = JSON.parse(JSON.stringify(formData))
// 因为汇款记录回显了保费对账的一些字段,但提交汇款记录的时候这些回显的字段是不能提交的,所以在这里做删除
for (const key in newFormData) {
if (key == deleteObjkeys[key]) delete newFormData[key]
// 文件上传的数据格式在这里统一处理
if (key == 'apiPremiumRemittanceFileDtoList' && newFormData[key].length > 0) {
newFormData[key] = newFormData[key].map(item => {
// 有response说明是新上传得,要处理返回格式和字段
if (item.response) {
let newObj = item.response.data
return {
fileName: newObj.originalName, //文件名
fileType: newObj.fileType, //文件类型
fileUrl: newObj.url, //文件URL,
url: newObj.url //文件URL
}
} else {
return item
}
})
} else if (
(key == 'paymentVoucherList' || key == 'accountVerificationList') &&
newFormData[key].length > 0
) {
newFormData[key] = newFormData[key].map(item => item.url)
}
}
let res = {}
// 编辑状态下修改汇款记录
if (formData.premiumRemittanceBizId) {
//编辑单个对账的先写到这里,等会在对接
const params = {
...newFormData
}
res = await editSiglePremiumRemittance(params)
if (res.code === 200) {
// 更新保费对账详情
getPremiumReconciliationDetail(addCheckRecordFormModel.value)
// getPremiumRemittanceList()
} else {
ElMessage.error(res.msg || `${remittanceDialogTitle.value}失败`)
}
} else {
// 新增汇款记录状态修改或新增
if (!newFormData.addRemittanceId) {
// 新增汇款记录自己家id,便于新增情况下做修改
newFormData.addRemittanceId = new Date().getTime()
}
let index = addCheckRecordFormModel.value.apiPremiumRemittanceDtoList.findIndex(
item => newFormData.addRemittanceId == item.addRemittanceId
)
if (index == -1) {
// 添加新得汇款记录
addCheckRecordFormModel.value.apiPremiumRemittanceDtoList.push(newFormData)
} else {
// 已经新增过汇款记录只是做修改
addCheckRecordFormModel.value.apiPremiumRemittanceDtoList[index] = newFormData
}
}
showRemittance.value = false
} catch (error) {
// ❌ 校验失败或提交异常都会进入这里
console.error('操作失败:', error)
// ⚠️ 不设置 receiptsFlag = false,弹窗保持打开
ElMessage.warning('请检查表单填写是否正确')
}
}
// 修改汇款记录
const editRemittance = row => {
let newObj = JSON.parse(JSON.stringify(row))
// 添加保费对账弹窗得form表单便于汇款回显
newObj.reconciliationType = addCheckRecordFormModel.value.reconciliationType
newObj.applicant = addCheckRecordFormModel.value.applicant
newObj.policyNo = addCheckRecordFormModel.value.policyNo
newObj.insured = addCheckRecordFormModel.value.insured
newObj.insuranceCompany = addCheckRecordFormModel.value.insuranceCompany
// 上传回显得格式是[{url:''}]所以要处理一下
for (const key in newObj) {
if (
(key == 'paymentVoucherList' ||
key == 'accountVerificationList' ||
key == 'apiPremiumRemittanceFileDtoList') &&
newObj[key] &&
newObj[key].length > 0
) {
newObj[key] = newObj[key].map(item => {
if (key == 'apiPremiumRemittanceFileDtoList') {
return {
...item,
url: item.fileUrl
}
} else {
return {
url: item
}
}
})
}
}
console.log('====================================')
console.log('汇款修改', newObj)
console.log('====================================')
remittanceFormModel.value = newObj
showRemittance.value = true
}
// 删除汇款记录
function removeRemittance(row, index) {
const list = [...addCheckRecordFormModel.value.apiPremiumRemittanceDtoList]
proxy.$modal
.confirm('是否确认删除此行数据?')
.then(function () {
if (row.premiumRemittanceBizId) {
console.log('远程删除')
return deletePremiumRemittance(row.premiumRemittanceBizId)
} else {
// 本地临时数据,直接从列表中移除
const index = list.findIndex(item => item.id === row.id)
if (index !== -1) {
list.splice(index, 1)
}
addCheckRecordFormModel.value.apiPremiumRemittanceDtoList = list
console.log(
'addCheckRecordFormModel.value.apiPremiumRemittanceDtoList',
addCheckRecordFormModel.value
)
// 注意:这里不需要 return Promise,因为是同步操作
// 但为了统一 then 链,可以 return 一个成功标识
return { code: 200 } // 模拟成功
}
})
.then(res => {
if (res && res.code == 200) {
proxy.$modal.msgSuccess('汇款删除成功')
// 删除成功要调用保费对账的详情接口,重新汇款记录数据,这里还没做等会做
}
})
.catch(() => {})
}
// ==============新增对账结束============
// 应付单类型通过value转成label
const getFortuneBizTypeLabel = value => {
const item = fortuneBizTypeOptions.find(item => item.value === value)
return item?.label || ''
}
const addCheckRecordConfig = [
{
type: 'select',
prop: 'reconciliationType',
label: '对账类型',
dictType: 'reconciliation_type',
rules: [{ required: true, message: '请输入对账类型', trigger: 'blur' }]
},
{
type: 'input',
prop: 'applicant',
label: '申请人',
rules: [{ required: true, message: '请输入申请人', trigger: 'blur' }]
},
{
type: 'select',
prop: 'policyNo',
label: '保单号码',
api: '/csf/api/policy/list/page/vo',
keywordField: 'policyNo',
requestParams: { pageNo: 1, pageSize: 20 },
placeholder: '输入转介人名称搜索',
debounceWait: 500, // 自定义防抖时间
valueKey: 'policyNo',
labelKey: 'policyNo',
onChangeExtraFields: {
insured: 'insured', // 选择了保单号码,自动填充保单被保人
insuranceCompany: 'insuranceCompany'
},
transform: res => {
return res?.data.records || []
},
rules: [{ required: true, message: '请输入保单号码', trigger: 'blur' }]
},
{
type: 'input',
prop: 'insured',
label: '被保人',
disabled: true
},
{
type: 'input',
prop: 'insuranceCompany',
label: '保险公司',
disabled: true
}
]
const apiPremiumReconciliationDto = ref({})
// 获取对账详情
const getPremiumReconciliationDetail = async row => {
try {
const res = await getPremiumReconciliationInfo(row.premiumReconciliationBizId)
apiPremiumReconciliationDto.value = res.data.apiPremiumReconciliationDto
addCheckRecordFormModel.value = {
...res.data.apiPremiumReconciliationDto,
apiPremiumRemittanceDtoList: res.data.apiPremiumRemittanceDtoList
}
receiptsFlag.value = true
receiptsDialogTitle.value = '编辑保单对账'
} catch (error) {
console.error('加载数据失败:', error)
ElMessage.error(error.message || '加载数据失败')
receiptsFlag.value = false
}
}
// 按钮事件处理
const handleAdd = () => {
console.log(addCheckRecordFormModel.value)
receiptsDialogTitle.value = '新增保单对账'
receiptsFlag.value = true
}
const handleExport = () => {
ElMessage.info('点击导出按钮')
}
const handleReset = () => {
// 重置搜索表单
searchFormRef.value.resetForm()
console.log('表单已重置')
currentPage.value = 1
loadTableData()
}
const handleQuery = async () => {
const params = searchFormRef.value.getFormData()
console.log('params', params)
loadTableData(params)
}
const visibleDefaultButtons = ref(['add', 'reset', 'query'])
// 按钮配置
const operationBtnList = ref([
{
key: 'add',
direction: 'left',
label: '新增保费对账',
click: handleAdd
},
// {
// key: 'export',
// direction: 'right',
// click: handleExport
// },
{
key: 'reset',
direction: 'right',
click: handleReset
},
{
key: 'query',
direction: 'right',
click: handleQuery
}
])
// 加载表格数据
const loadTableData = async (searchParams = {}) => {
loading.value = true
try {
const params = {
pageNo: currentPage.value,
pageSize: pageSize.value,
...searchParams
}
const res = await premiumReconciliationList(params)
tableData.value = res.data.records || []
pageTotal.value = res.data.total || 0
pageSize.value = res.data.size || 0
} catch (error) {
console.error('加载数据失败:', error)
ElMessage.error(error.message || '加载数据失败')
} finally {
loading.value = false
}
}
// 分页事件
const handleSizeChange = val => {
pageSize.value = val
loadTableData()
}
const handleCurrentChange = val => {
currentPage.value = val
loadTableData()
}
// 表格数据
const tableData = ref([])
const handleSelect = (e, row) => {
console.log('选中行:', e, row)
selectedRow.value = row
if (e == 'editRecord') {
getPremiumReconciliationDetail(row)
} else if (e == 'settingResult') {
affirmFormModel.value = {}
showAffirm.value = true
}
}
const addReceipts = async () => {
try {
// ✅ 正确:await validate(),成功时返回表单数据
const formData = await addCheckRecordFormRef.value.validate()
let apiPremiumReconciliationDto = {}
let apiPremiumRemittanceDtoList = JSON.parse(
JSON.stringify(formData.apiPremiumRemittanceDtoList)
)
for (const key in formData) {
if (!Array.isArray(formData[key])) {
apiPremiumReconciliationDto[key] = formData[key]
}
}
// if (apiPremiumRemittanceDtoList.length > 0) {
// apiPremiumRemittanceDtoList.forEach(item => {
// if (item.paymentVoucherList && item.paymentVoucherList.length > 0) {
// item.paymentVoucherList = item.paymentVoucherList.map(item1 => item1.url)
// }
// if (item.accountVerificationList && item.accountVerificationList.length > 0) {
// item.accountVerificationList = item.accountVerificationList.map(item1 => item1.url)
// }
// })
// }
const params = {
apiPremiumReconciliationDto: apiPremiumReconciliationDto,
apiPremiumRemittanceDtoList: apiPremiumRemittanceDtoList
}
// console.log('新增保单对账:', params)
// return
let res = {}
if (formData.policyReceiptBizId) {
params.policyReceiptBizId = formData.policyReceiptBizId
res = await EditPolicyReceipt(params)
} else {
res = await addPremiumReconciliation(params)
}
if (res.code === 200) {
ElMessage.success(`${receiptsDialogTitle.value}成功`)
receiptsFlag.value = false // ✅ 校验 & 提交成功后才关闭
addCheckRecordFormRef.value.resetForm()
loadTableData()
} else {
ElMessage.error(res.msg || `${receiptsDialogTitle.value}失败`)
}
} catch (error) {
// ❌ 校验失败或提交异常都会进入这里
console.error('操作失败:', error)
// ⚠️ 不设置 receiptsFlag = false,弹窗保持打开
ElMessage.warning('请检查表单填写是否正确')
// 注意:Element Plus 的 validate 失败时,错误信息已自动显示在表单项下方
}
}
const updatePayRollStatusDisable = ref(true)
const multipleSelection = ref([])
const handleSelectionChange = val => {
multipleSelection.value = val
console.log('全选:', val)
// 完成检核按钮是否禁用
updatePayRollStatusDisable.value = val.length === 0
}
import FileUploadPreview from '@/components/fileUploadPreview/fileUploadPreview.vue'
const onSubmit = data => {
console.log('提交给后端的数据:', data)
// 调用保存 API
}
// 获取入账状态,字典值转化方法
onMounted(async () => {
try {
await loadDicts([
'csf_ap_first_issue',
'csf_policy_follow_status_new',
'csf_policy_status_new',
'reconciliation_status',
'bx_currency_type',
'reconciliation_type',
'rel_pay_policy'
])
loadTableData()
} catch (error) {
console.error('字典加载失败', error)
} finally {
loading.value = false
}
})
watch(
() => remittanceFormModel.value.apiPremiumRemittanceFileDtoList,
newVal => {
console.log('newVal', newVal)
if (newVal && newVal.length > 0) {
newVal.forEach(item => {
// 代表是从新增保单对账开始
if (item.response) {
let newObj = JSON.parse(JSON.stringify(item.response.data))
// tempOtherFileList这个变量便于其他资料文件表格得展示使用
tempOtherFileList.value.push({
fileName: newObj.originalName, //文件名
fileType: newObj.fileType, //文件类型
fileUrl: newObj.url, //文件URL,
url: newObj.url //文件URL
})
} else {
// 从编辑对账或已经新增过汇款要修改汇款
tempOtherFileList.value = JSON.parse(JSON.stringify(newVal))
}
})
}
},
{ deep: true }
)
watch(receiptsFlag, newVal => {
if (!newVal) {
addCheckRecordFormModel.value = {}
receiptsDialogTitle.value = ''
}
})
</script>
<style scoped>
.tableOptionContainer {
display: flex;
justify-content: flex-end;
margin-top: 10px;
}
.otherFileBox {
margin-bottom: 50px;
}
</style>
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment