Commit b567c4f0 by Sweet Zhang

新单跟进对接

parent d1d8bca2
......@@ -247,3 +247,12 @@ export function batchSaveBrokers(data) {
data: data
})
}
// 通过保险公司、险种查询产品列表及参数
export function getProductList(data) {
return request({
url: '/csf/prodcut/api/relProjectProductLaunch/parameter/page',
method: 'post',
data: data
})
}
<template>
<div class="editable-table">
<el-form
ref="formRef"
:model="{}"
label-width="120px"
size="small"
>
<el-table
:data="internalData"
border
style="width: 100%"
:row-style="{ height: '48px' }"
:cell-style="{ padding: '6px 0' }"
>
<el-table-column
v-for="field in rowConfig"
:key="field.prop"
:label="field.label"
:width="field.width"
:min-width="field.minWidth"
>
<template #default="{ row }">
<component
:is="getFieldComponent(field.type)"
v-bind="getFieldProps(field, row.data)"
@update:model-value="val => handleFieldChange(val, field, row)"
@option-change="option => handleOptionChange(option, field, row)"
@focus="() => loadRemoteOptions(field)"
@filter-change="keyword => handleFilterChange(keyword, field)"
:disabled="!!field.disabled || disabled"
/>
</template>
</el-table-column>
<el-table-column label="操作" width="100" fixed="right">
<template #default="{ $index }">
<el-button
type="danger"
size="small"
link
@click="removeRow($index)"
:disabled="disabled"
>
删除
<div class="editable-table">
<el-form ref="formRef" :model="{}" label-width="120px" size="small">
<el-button type="primary" size="default" style="margin: 12px 0" @click="addRow" :disabled="disabled">
添加一行
</el-button>
</template>
</el-table-column>
</el-table>
<el-button
type="primary"
size="small"
style="margin-top: 12px"
@click="addRow"
:disabled="disabled"
>
添加一行
</el-button>
<!-- <el-button
<el-table :data="internalData" border style="width: 100%" :row-style="{ height: '48px' }"
:cell-style="{ padding: '6px 0' }">
<el-table-column v-for="field in rowConfig" :key="field.prop" :label="field.label" :width="field.width"
:min-width="field.minWidth">
<template #default="{ row }">
<component :is="getFieldComponent(field.type)" v-bind="getFieldProps(field, row.data)"
@update:model-value="val => handleFieldChange(val, field, row)"
@option-change="option => handleOptionChange(option, field, row)"
@focus="() => loadRemoteOptions(field)"
@filter-change="keyword => handleFilterChange(keyword, field)"
:disabled="!!field.disabled || disabled" />
</template>
</el-table-column>
<el-table-column label="操作" width="100" fixed="right">
<template #default="{ $index }">
<el-button type="danger" size="small" link @click="removeRow($index)" :disabled="disabled">
删除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- <el-button
type="success"
size="small"
style="margin-left: 12px"
......@@ -66,8 +37,8 @@
>
批量保存
</el-button> -->
</el-form>
</div>
</el-form>
</div>
</template>
<script setup>
......@@ -78,18 +49,18 @@ import UploadField from '@/components/csf-form/fields/UploadField.vue'
import { deepEqual } from '@/utils/csf-deepEqual'
import request from '@/utils/request'
const props = defineProps({
modelValue: {
type: Array,
default: () => []
},
rowConfig: {
type: Array,
required: true
},
disabled: {
type: Boolean,
default: false
}
modelValue: {
type: Array,
default: () => []
},
rowConfig: {
type: Array,
required: true
},
disabled: {
type: Boolean,
default: false
}
})
const emit = defineEmits(['update:modelValue', 'batch-save'])
......@@ -103,181 +74,181 @@ const optionsCache = ref(new Map()) // prop -> options
// 初始化 internalData
watch(
() => props.modelValue,
(newVal) => {
if (!Array.isArray(newVal)) {
console.warn('[EditableTable] modelValue is not an array, reset to empty array')
internalData.value = []
return
}
const currentPlain = internalData.value.map(row => {
const { id, ...rest } = row.data
return { ...rest, id: row.id }
})
if (!deepEqual(newVal, currentPlain)) {
internalData.value = newVal.map((item, index) => ({
id: item.id || Symbol(`row-${index}`),
data: { ...(item || {}) }
}))
}
},
{ immediate: true }
() => props.modelValue,
(newVal) => {
if (!Array.isArray(newVal)) {
console.warn('[EditableTable] modelValue is not an array, reset to empty array')
internalData.value = []
return
}
const currentPlain = internalData.value.map(row => {
const { id, ...rest } = row.data
return { ...rest, id: row.id }
})
if (!deepEqual(newVal, currentPlain)) {
internalData.value = newVal.map((item, index) => ({
id: item.id || Symbol(`row-${index}`),
data: { ...(item || {}) }
}))
}
},
{ immediate: true }
)
// 同步 internalData → modelValue
let isEmitting = false
watch(
internalData,
(newVal) => {
if (isEmitting) return
const plainData = newVal.map(row => {
const { id, ...rest } = row.data
return { ...rest, id: row.id }
})
if (!deepEqual(plainData, props.modelValue)) {
isEmitting = true
emit('update:modelValue', plainData)
nextTick(() => {
isEmitting = false
})
}
},
{ deep: true }
internalData,
(newVal) => {
if (isEmitting) return
const plainData = newVal.map(row => {
const { id, ...rest } = row.data
return { ...rest, id: row.id }
})
if (!deepEqual(plainData, props.modelValue)) {
isEmitting = true
emit('update:modelValue', plainData)
nextTick(() => {
isEmitting = false
})
}
},
{ deep: true }
)
// 字段组件映射
function getFieldComponent(type) {
switch (type) {
case 'select':
return SelectField
case 'input':
return InputField
case 'upload':
return UploadField
default:
return InputField
}
switch (type) {
case 'select':
return SelectField
case 'input':
return InputField
case 'upload':
return UploadField
default:
return InputField
}
}
// 获取字段属性
function getFieldProps(field, rowData) {
const base = {
modelValue: rowData[field.prop],
'onUpdate:modelValue': (val) => {
rowData[field.prop] = val
const base = {
modelValue: rowData[field.prop],
'onUpdate:modelValue': (val) => {
rowData[field.prop] = val
}
}
}
switch (field.type) {
case 'select':
return {
...base,
multiple: !!field.multiple,
clearable: true,
filterable: true,
loading: remoteLoading.value[field.prop] || false,
options: optionsCache.value.get(field.prop) || field.options || [],
placeholder: field.placeholder || `请选择${field.label}`
}
case 'input':
return {
...base,
placeholder: field.placeholder || `请输入${field.label}`
}
case 'upload':
return {
...base,
uploadUrl: field.uploadUrl,
maxCount: field.maxCount || 1,
showFileList: field.showFileList !== false
}
default:
return base
}
switch (field.type) {
case 'select':
return {
...base,
multiple: !!field.multiple,
clearable: true,
filterable: true,
loading: remoteLoading.value[field.prop] || false,
options: optionsCache.value.get(field.prop) || field.options || [],
placeholder: field.placeholder || `请选择${field.label}`
}
case 'input':
return {
...base,
placeholder: field.placeholder || `请输入${field.label}`
}
case 'upload':
return {
...base,
uploadUrl: field.uploadUrl,
maxCount: field.maxCount || 1,
showFileList: field.showFileList !== false
}
default:
return base
}
}
// 处理字段变更(基础)
function handleFieldChange(val, field, row) {
row.data[field.prop] = val
row.data[field.prop] = val
}
// 处理 select 选中(带完整 option)
function handleOptionChange(option, field, row) {
if (!option || !field.onChangeExtraFields) return
// 兼容对象和数组格式
let extras = []
if (Array.isArray(field.onChangeExtraFields)) {
extras = field.onChangeExtraFields
} else if (typeof field.onChangeExtraFields === 'object') {
extras = Object.entries(field.onChangeExtraFields).map(([targetProp, sourceKey]) => ({
targetProp,
sourceKey
}))
}
if (!option || !field.onChangeExtraFields) return
// 兼容对象和数组格式
let extras = []
if (Array.isArray(field.onChangeExtraFields)) {
extras = field.onChangeExtraFields
} else if (typeof field.onChangeExtraFields === 'object') {
extras = Object.entries(field.onChangeExtraFields).map(([targetProp, sourceKey]) => ({
targetProp,
sourceKey
}))
}
for (const { targetProp, sourceKey } of extras) {
if (!targetProp || sourceKey == null) continue
const getValue = (obj, path) => path.split('.').reduce((o, k) => o?.[k], obj)
const extraValue = getValue(option, sourceKey)
row.data[targetProp] = extraValue
}
for (const { targetProp, sourceKey } of extras) {
if (!targetProp || sourceKey == null) continue
const getValue = (obj, path) => path.split('.').reduce((o, k) => o?.[k], obj)
const extraValue = getValue(option, sourceKey)
row.data[targetProp] = extraValue
}
}
// 加载远程选项(初始加载)
async function loadRemoteOptions(field) {
if (!field.api || optionsCache.value.has(field.prop)) return
const key = field.prop
remoteLoading.value[key] = true
try {
const params = {
...(field.requestParams || {}),
[field.keywordField || 'keyword']: ''
if (!field.api || optionsCache.value.has(field.prop)) return
const key = field.prop
remoteLoading.value[key] = true
try {
const params = {
...(field.requestParams || {}),
[field.keywordField || 'keyword']: ''
}
const res = await request(field.api, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
data: JSON.stringify(params)
}).then(r => r)
const list = field.transform ? field.transform(res) : (res?.data?.records || [])
optionsCache.value.set(key, list)
} catch (err) {
console.error(`Failed to load options for ${field.prop}:`, err)
} finally {
remoteLoading.value[key] = false
}
const res = await request(field.api, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
data: JSON.stringify(params)
}).then(r => r)
const list = field.transform ? field.transform(res) : (res?.data?.records || [])
optionsCache.value.set(key, list)
} catch (err) {
console.error(`Failed to load options for ${field.prop}:`, err)
} finally {
remoteLoading.value[key] = false
}
}
// 搜索过滤(暂不实现,可扩展)
function handleFilterChange(keyword, field) {
// 可在此实现远程搜索
// 可在此实现远程搜索
}
// 行操作
function addRow() {
internalData.value.push({
data: {}
})
internalData.value.push({
data: {}
})
}
function removeRow(index) {
internalData.value.splice(index, 1)
internalData.value.splice(index, 1)
}
function batchSave() {
emit('batch-save', internalData.value.map(row => ({ ...row.data, id: row.id })))
emit('batch-save', internalData.value.map(row => ({ ...row.data, id: row.id })))
}
</script>
<style scoped>
.editable-table :deep(.el-input__wrapper),
.editable-table :deep(.el-select__wrapper) {
box-shadow: none !important;
box-shadow: none !important;
}
</style>
\ No newline at end of file
......@@ -75,19 +75,12 @@
border
max-height="380px"
>
<el-table-column type="index" width="50" label="序号" />
<el-table-column type="index" width="60" label="序号" />
<el-table-column label="客户姓名" align="center" prop="customerName" width="100" />
<el-table-column label="状态" align="center" width="150">
<!-- <el-table-column label="状态" align="center" prop="status" width="100" :formatter="getDictLabel('csf_fna_status')"/> -->
<el-table-column label="状态" sortable align="center" prop="status" width="200">
<template #default="scope">
<span v-if="scope.row.status == 'UNCOMPLETED'">
<span style="color: #ff7d00" class="iconfont icon-yanqiweiwancheng"></span> 未完成
</span>
<span v-if="scope.row.status == 'COMPLETED'"
><span style="color: #43cf7c" class="iconfont icon-yiwancheng"></span> 已完成
</span>
<span v-if="scope.row.status == 'DRAFT'"
><span style="color: #86909c" class="iconfont icon-genjinjilu"></span> 草稿
</span>
<span>{{ selectDictLabel(csf_fna_status, scope.row.status) }}</span>
</template>
</el-table-column>
<el-table-column
......
......@@ -71,8 +71,9 @@
<CommonDialog :dialogTitle='mode === "viewDetail" ? "查看详情" : "更新数据"' dialogWidth='80%'
:openDialog=viewDetailDialogFlag :showAction='false' :showClose='true' @close='viewDetailDialogFlag = false'
@confirm='handleUpdateSubmit'>
<PolicyDetail v-model="policyDetailFormData" ref="policyDetailFormRef" @submit="onSubmit"
@cancel="viewDetailDialogFlag = false" />
<PolicyDetail v-model="policyDetailFormData" :policyBizId="selectedRow.policyBizId" :mode="mode"
ref="policyDetailFormRef" @submit="onSubmit" @cancel="viewDetailDialogFlag = false"
v-if="viewDetailDialogFlag" />
</CommonDialog>
......@@ -172,7 +173,6 @@ const onSubmit = async (data) => {
const res = await saveMailingInfo(params)
if (res.code === 200) {
ElMessage.success('保存邮寄信息成功')
// viewDetailDialogFlag.value = false
} else {
ElMessage.error(res.msg || '保存邮寄信息失败')
}
......@@ -184,10 +184,10 @@ const onSubmit = async (data) => {
}
const res = await updatePolicyfollow(params)
if (res.code === 200) {
ElMessage.success('保存基本信息成功')
ElMessage.success(data.activeTab === 'basic' ? '保存基本信息成功' : '保存产品计划成功')
// viewDetailDialogFlag.value = false
} else {
ElMessage.error(res.msg || '保存基本信息失败')
ElMessage.error(res.msg || (data.activeTab === 'basic' ? '保存基本信息失败' : '保存产品计划失败'))
}
} else if (data.activeTab === 'introducer') {
params = {
......@@ -228,10 +228,7 @@ const onSubmit = async (data) => {
}
const handleClose = () => {
// 可选:清空数据
}
// 处理转介人数据格式
const normalizeIntroducerData = (data) => {
if (Array.isArray(data)) return data
if (data && typeof data === 'object') {
......@@ -407,44 +404,40 @@ const dropdownItems = [
{ label: '更新数据', value: 'updateData' },
{ label: '生成签单报告', value: 'generateReport' },
{ label: '设置新单状态', value: 'setNewSingleStatus' },
{ label: '查看关联', value: 'viewRelated' },
// { label: '查看关联', value: 'viewRelated' },
// { label: '查看记录', value: 'viewRecord' },
]
const mode = ref('viewDetail')
const handleSelect = async (e, row) => {
selectedRow.value = { ...row }
console.log('列表点击操作', e, selectedRow.value)
mode.value = e
switch (e) {
case 'viewDetail':
// ElMessage.info('查看详情')
viewDetailDialogFlag.value = true
// 等待 DOM 更新完成
await nextTick()
if (policyDetailFormRef.value) {
// 调用子组件详情查询接口
const data = await policyDetailFormRef.value.getPolicyfollowDetail(row.policyBizId)
}
break
case 'updateData':
ElMessage.info('更新数据')
if (selectedRow.value.status === '生效') {
ElMessage.warning('非新单状态,不能更新数据')
return
}
viewDetailDialogFlag.value = true
break
case 'setNewSingleStatus':
// ElMessage.info('设置新单状态')
editStatusDialogFlag.value = true
editStatusFormData.value = {
status: row.status
}
break
case 'viewRelated':
// ElMessage.info('查看关联')
viewRelatedDialogFlag.value = true
break
case 'generateReport':
generateReport(row)
break
case 'viewRecord':
ElMessage.info('查看记录')
// ElMessage.info('查看记录')
break
default:
break
......@@ -465,10 +458,15 @@ const generateReport = async (row) => {
fileName,
'application/pdf;charset=utf-8'
)
ElMessage.success('报告生成成功')
if (res.code === 200) {
ElMessage.success('报告生成成功')
}else{
ElMessage.error(res.msg || '报告生成失败')
}
// 可以添加下载报告的逻辑
} catch (error) {
ElMessage.error(error.message || '报告生成失败')
console.error(error.message || '报告生成失败')
}
}
......
......@@ -40,52 +40,8 @@
<!-- 附加计划(可编辑表格) -->
<div class="section">
<h3 class="sectionTitle">附加计划</h3>
<el-button type="primary" size="default" @click="addAdditionalPlan">+ 新增</el-button>
<el-table :data="localData.additionalPlans || []" style="width: 100%; margin-top: 10px" border>
<el-table-column prop="product" label="产品名称" width="180">
<template #default="{ row }">
<el-input v-model="row.product" size="default" />
</template>
</el-table-column>
<el-table-column prop="frequency" label="付款频率" width="150">
<template #default="{ row }">
<el-select v-model="row.frequency" size="default" placeholder="选择">
<el-option label="月付" value="monthly" />
<el-option label="年付" value="yearly" />
</el-select>
</template>
</el-table-column>
<el-table-column prop="term" label="保障期限" width="150">
<template #default="{ row }">
<el-input v-model="row.term" size="default" />
</template>
</el-table-column>
<el-table-column prop="periods" label="供款期数" width="150">
<template #default="{ row }">
<el-input v-model="row.periods" size="default" />
</template>
</el-table-column>
<el-table-column prop="currency" label="保单币种" width="150">
<template #default="{ row }">
<el-input v-model="row.currency" size="default" />
</template>
</el-table-column>
<el-table-column prop="premium" label="每期保费" width="150">
<template #default="{ row }">
<el-input v-model="row.premium" size="default" />
</template>
</el-table-column>
<el-table-column prop="fee" label="保单征费" width="150">
<template #default="{ row }">
<el-input v-model="row.fee" size="default" />
</template>
</el-table-column>
<el-table-column label="操作" width="120">
<template #default="{ $index }">
<el-button size="default" type="danger" @click="deleteRow($index)">删除</el-button>
</template>
</el-table-column>
</el-table>
<EditableTable v-model="localData.additionalPlans" :row-config="additionalPlansConfig"
@batch-save="handleBatchSave" :disabled="props.mode === 'viewDetail'" />
</div>
</div>
<!-- 首期缴费 Tab 内容 -->
......@@ -114,7 +70,7 @@
<h3 class="sectionTitle">介绍人信息</h3>
<h5>第一位默认是客户主要负责人,客户资料出现在介绍人(主)账号下,其他介绍人不会看到客户信息</h5>
<EditableTable v-model="introducerTableData" :row-config="introducerConfig"
@batch-save="handleBatchSave" />
@batch-save="handleBatchSave" :disabled="props.mode === 'viewDetail'" />
</div>
</div>
<!-- 邮寄信息 Tab 内容 -->
......@@ -136,7 +92,7 @@
:formatter="(row) => formatToDate(row.createTime)" />
<el-table-column fixed="right" label="操作" min-width="120">
<template #default>
<el-button link type="primary" size="small" @click="handleClick">
<el-button link type="primary" size="small" @click="viewRecordDetail">
查看
</el-button>
</template>
......@@ -148,21 +104,22 @@
<div v-else-if="activeTab === 'attachment'" class="tab-content">
<div class="section">
<h3 class="sectionTitle">附件</h3>
<el-button type="primary">
<el-button type="primary" @click="handleUpload" :disabled="props.mode === 'viewDetail'">
<el-icon class="el-icon--right">
<Upload />
</el-icon>上传附件
</el-button>
<el-table :data="attachmentTableData" border style="width: 100%">
<el-table-column prop="date" label="文件名" width="180" />
<el-table-column prop="name" label="文件类型" width="180" />
<el-table-column prop="address" label="上传时间" width="180" />
<el-table-column prop="address" label="上传人" width="180" />
<el-table-column v-for="(column, index) in attachmentTableColumns" :key="index"
:prop="column.prop" :label="column.label" :width="column.width" :sortable="column.sortable"
:formatter="column.formatter" />
<el-table-column fixed="right" label="操作" min-width="120">
<template #default>
<el-button link type="primary" size="small" @click="handleClick">
<el-button link type="primary" size="small" @click="handleClick"
:disabled="props.mode === 'viewDetail'">
修改
</el-button><el-button link type="danger" size="small" @click="handleClick">
</el-button><el-button link type="danger" size="small" @click="handleClick"
:disabled="props.mode === 'viewDetail'">
删除
</el-button>
</template>
......@@ -184,15 +141,37 @@
</template>
<script setup>
import { ref, reactive, watch, nextTick, onMounted } from 'vue'
import { ref, reactive, watch, nextTick, onMounted, computed } from 'vue'
import SearchForm from '@/components/SearchForm/SearchForm.vue'
import { Delete, Edit, Search, Share, Upload } from '@element-plus/icons-vue'
import { getPolicyfollow, getAttachmentList } from '@/api/sign/underwritingMain'
import { getPolicyfollow, getProductList } from '@/api/sign/underwritingMain'
import {uploadOssFileList} from '@/api/common'
import { getProcessDetail } from '@/api/sign/fna'
import { premiumReconciliationList } from '@/api/sign/policy'
import { loadDicts, getDictLabel } from '@/utils/useDict'
import { formatToDate } from '@/utils/date'
import EditableTable from '@/components/csf-common/EditableTable.vue'
import useUserStore from '@/store/modules/user'
const router = useRouter()
const userStore = useUserStore()
// ===== Props & Emits =====
const props = defineProps({
modelValue: {
type: Object,
default: () => ({})
},
mode: {
type: String,
default: 'viewDetail'
},
policyBizId: {
type: String,
default: ''
}
})
const emit = defineEmits(['update:modelValue', 'submit', 'cancel', 'saveRow'])
const introducerTableData = ref([])
const introducerConfig = [
{
......@@ -212,7 +191,7 @@ const introducerConfig = [
},
transform: (res) => {
return (res?.data.records || []).map(item => ({
value: item.userSaleBizId,
value: item.clientUserBizId,
label: item.realName,
// 👇 把 extra 字段挂到 option 上
realName: item.realName,
......@@ -277,12 +256,74 @@ const introducerConfig = [
},
]
// 附加计划表格
const additionalPlansConfig = [
{
type: 'input',
prop: 'product',
label: '产品名称',
span: 4,
}, {
type: 'input',
prop: 'frequency',
label: '付款频率',
span: 4,
}, {
type: 'input',
prop: 'term',
label: '保障期限',
span: 4,
}, {
type: 'input',
prop: 'phone',
label: '付款频率',
span: 4,
}, {
type: 'input',
prop: 'periods',
label: '供款期数',
span: 4,
}, {
type: 'input',
prop: 'currency',
label: '保单币种',
span: 4,
}, {
type: 'input',
prop: 'premium',
label: '每期保费',
span: 4,
}, {
type: 'input',
prop: 'fee',
label: '保单征费',
span: 4,
},
]
function handleBatchSave(validRows) {
console.log('批量提交:', validRows)
// 调用 API
introducerTableData.value = validRows
}
function applyViewMode(fields, mode) {
if (mode === 'viewDetail') {
return fields.map(item => ({
...item,
disabled: true
}));
}
// 非查看模式:保留原对象,可选择性移除 disabled
return fields.map(item => {
// 如果你希望编辑模式下完全不禁用,可以删除 disabled 属性
const newItem = { ...item };
delete newItem.disabled;
return newItem;
});
}
const newOrderData = ref({})
......@@ -314,7 +355,7 @@ const basicInfoFormConfig = ref([
},
rules: [
{ required: true, message: '请选择签单员', trigger: 'blur' },
]
],
}, {
type: 'input',
......@@ -325,11 +366,12 @@ const basicInfoFormConfig = ref([
type: 'select',
prop: 'signLocation',
label: '签单地点',
dictType: 'csf_ap_meeting_point'
dictType: 'csf_ap_meeting_point',
},
])
// 保单信息
const policyInfoFormRef = ref()
const policyInfoFormData = ref({})
......@@ -358,12 +400,12 @@ const policyInfoFormConfig = ref([
type: 'select',
prop: 'receiptStatus',
label: '回执状态',
dictType: 'receipt_status'
dictType: 'receipt_status',
}, {
type: 'select',
prop: 'directPaymentEnabled',
label: '是否开通直接支付',
dictType: 'sys_no_yes'
dictType: 'sys_no_yes',
}, {
type: 'input',
prop: 'policyHolder',
......@@ -388,112 +430,153 @@ const policyInfoFormConfig = ref([
type: 'select',
prop: 'isJoin',
label: '是否参加递增保障权益',
dictType: 'sys_no_yes'
dictType: 'sys_no_yes',
},
])
// 基本计划
const basicPlanFormRef = ref()
const basicPlanFormData = ref({})
const basicPlanFormConfig = ref([
{
type: 'select',
prop: 'insuranceCompanyBizId',
label: '保险公司',
api: '/insurance/base/api/insuranceCompany/page',
keywordField: 'queryContent',
requestParams: { pageNo: 1, pageSize: 20 },
placeholder: '输入保险公司名称搜索',
debounceWait: 500, // 自定义防抖时间
valueKey: 'insuranceCompanyBizId',
labelKey: 'fullName',
transform: (res) => {
console.log(res)
return res?.data.records || []
}
}, {
type: 'select',
prop: 'productCate',
label: '保险险种',
}, {
type: 'select',
prop: 'productName',
label: '产品名称',
}, {
type: 'select',
prop: 'paymentFrequency',
label: '付款频率',
dictType: 'csf_ap_frequency'
}, {
type: 'select',
prop: 'guaranteePeriod',
label: '保障期限',
}, {
type: 'input',
prop: 'sumInsured',
label: '保额(重疾险)',
}, {
type: 'select',
prop: 'issueNumber',
label: '供款期数',
}, {
type: 'select',
prop: 'policyCurrency',
label: '保单币种',
dictType: 'bx_currency_type'
}, {
type: 'input',
prop: 'initialPremium',
label: '每期保费',
inputType: 'decimal',
decimalDigits: 2,
visible: (formData) => formData.commissionBizType == 'R',
rules: [
{ required: true, message: '请输入金额', trigger: 'blur' },
{ pattern: /^\d+(\.\d{1,2})?$/, message: '最多两位小数', trigger: 'blur' }
]
}, {
type: 'input',
prop: 'policyLevy',
label: '保单征费',
inputType: 'decimal',
decimalDigits: 2,
visible: (formData) => formData.commissionBizType == 'R',
rules: [
{ required: true, message: '请输入金额', trigger: 'blur' },
{ pattern: /^\d+(\.\d{1,2})?$/, message: '最多两位小数', trigger: 'blur' }
]
}, {
type: 'select',
prop: 'isPrepay',
label: '是否预缴',
dictType: 'sys_no_yes'
}, {
type: 'select',
prop: 'isTraceable',
label: '是否追溯',
dictType: 'sys_no_yes'
}, {
type: 'date',
prop: 'retroactiveDate',
label: '回溯日期',
}, {
type: 'select',
prop: 'initialPaymentMethod',
label: '首期付款方式',
dictType: 'csf_ap_first_issue'
}, {
type: 'select',
prop: 'renewalPaymentMethod',
label: '续期付款方式',
dictType: 'csf_ap_first_issue'
}, {
type: 'select',
prop: 'dividendPaymentMethod',
label: '红利付款方式',
dictType: 'csf_ap_dividend'
},
])
const basicPlanFormConfig = computed(() => {
return [
{
type: 'select',
prop: 'insuranceCompanyBizId',
label: '保险公司',
api: '/insurance/base/api/insuranceCompany/page',
keywordField: 'queryContent',
requestParams: { pageNo: 1, pageSize: 500 },
placeholder: '输入保险公司名称搜索',
debounceWait: 500, // 自定义防抖时间
valueKey: 'insuranceCompanyBizId',
labelKey: 'fullName',
transform: (res) => {
console.log(res)
return res?.data.records || []
},
onChangeExtraFields: {
insuranceCompany: 'fullName',
},
}, {
type: 'select',
prop: 'productCate',
label: '保险险种',
api: '/insurance/base/api/insuranceCategory/page',
keywordField: 'name',
requestParams: { pageNo: 1, pageSize: 20 },
placeholder: '输入保险险种名称搜索',
debounceWait: 500, // 自定义防抖时间
valueKey: 'insuranceCategoryBizId',
labelKey: 'name',
transform: (res) => {
return res?.data.records || []
},
},
{
type: 'select',
prop: 'productLaunchBizId',
label: '产品名称',
api: '/product/api/relProjectProductLaunch/parameter/page',
keywordField: 'productName',
// ✅ 关键:requestParams 必须是函数,才能响应式获取最新值!
requestParams: {
tenantBizId: userStore.projectInfo.tenantBizId || '',
projectBizId: userStore.projectInfo.projectBizId || '',
fieldBizId: 'field_olk1qZe81qHHKXbw',
fieldValueBizId: 'field_value_uOfJH5ucA2YwJpbn',
categoryCodeList: [basicPlanFormData.value.productCate || ''],
insuranceCompanyBizIdList: [basicPlanFormData.value.insuranceCompanyBizId || ''],
pageNo: 1,
pageSize: 20
},
placeholder: '请选择产品名称',
debounceWait: 500,
valueKey: 'productLaunchBizId',
labelKey: 'productName',
transform: (res) => res?.data.records || [],
// ✅ 关键:当选择产品时,自动回填其他字段
onChangeExtraFields: {
// 格式:targetProp: sourcePathInRawObject
apiAttributeSettingDtoList: 'apiAttributeSettingDtoList',
}
},
{
type: 'select',
prop: 'paymentFrequency',
label: '付款频率',
dictType: 'csf_ap_frequency',
}, {
type: 'input',
prop: 'guaranteePeriod',
label: '保障期限',
}, {
type: 'input',
prop: 'sumInsured',
label: '保额(重疾险)',
}, {
type: 'select',
prop: 'issueNumber',
label: '供款期数',
}, {
type: 'select',
prop: 'policyCurrency',
label: '保单币种',
dictType: 'bx_currency_type'
}, {
type: 'input',
prop: 'initialPremium',
label: '每期保费',
inputType: 'decimal',
decimalDigits: 2,
visible: (formData) => formData.commissionBizType == 'R',
rules: [
{ required: true, message: '请输入金额', trigger: 'blur' },
{ pattern: /^\d+(\.\d{1,2})?$/, message: '最多两位小数', trigger: 'blur' }
]
}, {
type: 'input',
prop: 'policyLevy',
label: '保单征费',
inputType: 'decimal',
decimalDigits: 2,
visible: (formData) => formData.commissionBizType == 'R',
rules: [
{ required: true, message: '请输入金额', trigger: 'blur' },
{ pattern: /^\d+(\.\d{1,2})?$/, message: '最多两位小数', trigger: 'blur' }
]
}, {
type: 'select',
prop: 'isPrepay',
label: '是否预缴',
dictType: 'sys_no_yes'
}, {
type: 'select',
prop: 'isTraceable',
label: '是否追溯',
dictType: 'sys_no_yes'
}, {
type: 'date',
prop: 'retroactiveDate',
label: '回溯日期',
}, {
type: 'select',
prop: 'initialPaymentMethod',
label: '首期付款方式',
dictType: 'csf_ap_first_issue'
}, {
type: 'select',
prop: 'renewalPaymentMethod',
label: '续期付款方式',
dictType: 'csf_ap_first_issue'
}, {
type: 'select',
prop: 'dividendPaymentMethod',
label: '红利付款方式',
dictType: 'csf_ap_dividend'
},
]
})
// 首期保费
const firstPremiumTableData = ref([])
......@@ -616,16 +699,11 @@ const postalFormConfig = ref([
// 关联记录
const relatedTableData = ref([])
const attachmentTableData = ref([])
// ===== Props & Emits =====
const props = defineProps({
modelValue: {
type: Object,
default: () => ({})
}
})
const emit = defineEmits(['update:modelValue', 'submit', 'cancel', 'saveRow'])
const attachmentTableColumns = ref([
{ prop: 'objectName', label: '文件名', sortable: true, width: '150', formatter: (row) => row.objectName || '-' },
{ prop: 'fileType', label: '文件类型', sortable: true, width: '150', formatter: (row) => row.fileType || '-' },
{ prop: 'createTime', label: '上传时间', sortable: true, width: '150', formatter: (row) => row.createTime || '-' },
])
// ===== 本地响应式数据 =====
const defaultFormData = () => ({})
......@@ -635,11 +713,23 @@ const defaultFormData = () => ({})
const localData = ref(defaultFormData())
// ✅ 只在挂载时初始化一次(避免 watch 形成闭环)
onMounted(() => {
onMounted(async () => {
if (props.modelValue) {
// 深拷贝 + 合并默认值,防止缺失字段
localData.value = { ...defaultFormData(), ...props.modelValue }
}
try {
await loadDicts(['sys_no_yes', 'csf_ap_frequency', 'bx_currency_type', 'csf_ap_first_issue', 'csf_ap_dividend', 'csf_fortune_status', 'csf_ap_mailing_method', 'receipt_status'])
getPolicyfollowDetail()
} catch (error) {
console.error('字典加载失败', error)
} finally {
}
// 应用查看模式到基本信息表格
basicInfoFormConfig.value = basicInfoFormConfig ? applyViewMode(basicInfoFormConfig.value, props.mode) : []
policyInfoFormConfig.value = policyInfoFormConfig ? applyViewMode(policyInfoFormConfig.value, props.mode) : []
})
// ✅ 监听 localData 变化,emit 出去(用于 v-model)
......@@ -665,14 +755,21 @@ const deleteRow = (index) => {
const handleTabClick = (tab) => {
if (tab.props.name === 'firstPayment') {
firstPremiumFormConfig.value = firstPremiumFormConfig ? applyViewMode(firstPremiumFormConfig.value, props.mode) : []
if (!basicInfoFormData.value.policyNo) return
getPremiumReconciliationList(basicInfoFormData.value.policyNo)
} else if (tab.props.name === 'postal') {
postalFormConfig.value = postalFormConfig ? applyViewMode(postalFormConfig.value, props.mode) : []
} else if (tab.props.name === 'related') {
getRelationRecord(newOrderData.value.fnaBizId)
} else if (tab.props.name === 'attachment') {
getAttachmentListDetail(newOrderData.value.policyBizId)
} else if (tab.props.name === 'productPlan') {
basicPlanFormConfig.value = basicPlanFormConfig ? applyViewMode(basicPlanFormConfig.value, props.mode) : []
} else if (tab.props.name === 'introducer') {
// introducerConfig.value = introducerConfig ? applyViewMode(introducerConfig.value, props.mode) : []
} else if (tab.props.name === 'basic') {
basicInfoFormConfig.value = basicInfoFormConfig ? applyViewMode(basicInfoFormConfig.value, props.mode) : []
}
}
......@@ -717,23 +814,19 @@ watch(
)
// 查询新单跟进详情
// /csf/api/policy_follow/get/vo?policyBizId=${policyBizId}
const getPolicyfollowDetail = (policyBizId) => {
getPolicyfollow(policyBizId).then(res => {
const getPolicyfollowDetail = () => {
if (!props.policyBizId) {
return
}
getPolicyfollow(props.policyBizId).then(res => {
if (res.code === 200) {
Object.assign(localData, defaultFormData(), res.data)
console.log('localData', localData.value)
newOrderData.value = res.data
policyInfoFormData.value = transformToFormData(res.data, policyInfoFormConfig.value);
basicPlanFormData.value = transformToFormData(res.data, basicPlanFormConfig.value);
basicInfoFormData.value = transformToFormData(res.data, basicInfoFormConfig.value);
firstPremiumFormData.value = transformToFormData(res.data, firstPremiumFormConfig.value);
console.log('policyInfoFormData', policyInfoFormData.value)
console.log('basicPlanFormData', basicPlanFormData.value)
console.log('basicInfoFormData', basicInfoFormData.value)
console.log('firstPremiumFormData', firstPremiumFormData.value)
}
})
}
......@@ -743,11 +836,11 @@ const getAttachmentListDetail = (policyBizId) => {
return
}
const params = {
policyBizId: policyBizId,
objectBizId: policyBizId,
pageNo: 1,
pageSize: 100,
}
getAttachmentList(params).then(res => {
uploadOssFileList(params).then(res => {
if (res.code === 200) {
attachmentTableData.value = res.data.records || []
console.log('attachmentTableData', res.data)
......@@ -788,9 +881,6 @@ const transformToFormData = (apiData, formConfig) => {
};
// 获取首期保费对账列表
const getPremiumReconciliationList = (policyNo) => {
// if (!policyNo) {
// return
// }
const params = {
policyNo: policyNo,
pageNo: 1,
......@@ -799,8 +889,6 @@ const getPremiumReconciliationList = (policyNo) => {
premiumReconciliationList(params).then(res => {
if (res.code === 200) {
firstPremiumTableData.value = res.data.records || []
console.log('premiumReconciliationList', res.data)
// premiumReconciliationListData.value = res.data || []
}
})
}
......@@ -815,16 +903,28 @@ const getRelationRecord = (fnaBizId) => {
})
}
// 页面加载时调用
onMounted(async () => {
try {
await loadDicts(['sys_no_yes', 'csf_ap_frequency', 'bx_currency_type', 'csf_ap_first_issue', 'csf_ap_dividend', 'csf_fortune_status', 'csf_ap_mailing_method', 'receipt_status'])
} catch (error) {
console.error('字典加载失败', error)
} finally {
const viewRecordDetail = (e) => {
router.push({
path: '/sign/FnaList/edit',
query: {
fnaBizId: newOrderData.value.fnaBizId,
type: 'edit',
status: newOrderData.value.status,
source: 'newOrder',
appointmentBizId: newOrderData.value.appointmentBizId
}
})
}
}
})
// 获取产品列表
const getProductLists = (params) => {
getProductList(params).then(res => {
if (res.code === 200) {
console.log('productList', res.data.records || [])
// productList.value = res.data.records || []
}
})
}
defineExpose({
getPolicyfollowDetail
......
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