Commit d4982277 by yuzhenWang

Merge branch 'test' into 'feature-20250827wyz-写业务'

Test

See merge request !38
parents 92304c31 10d9b14a
...@@ -403,3 +403,12 @@ export function updatePayRecord(data){ ...@@ -403,3 +403,12 @@ export function updatePayRecord(data){
data: data data: data
}) })
} }
// 应付款导出
export function exportPayRecord(data) {
return request({
url: '/csf/api/expectedFortune/export',
method: 'post',
data: data,
responseType: 'blob'
})
}
<template> <template>
<el-form ref="formRef" :model="localModel" :rules="formRules" label-width="auto" v-bind="$attrs" :validate-on-rule-change="false"> <el-form ref="formRef" :model="localModel" :rules="formRules" label-width="auto" v-bind="$attrs"
:validate-on-rule-change="false">
<el-row :gutter="20"> <el-row :gutter="20">
<el-col v-for="item in visibleConfig" :key="item.prop" :span="item.span || 6"> <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 }"> <el-form-item :label="item.label" :prop="item.prop" :class="{ 'search-form-item': isSearch }"
:label-position="item.labelPosition || 'top'">
<!-- Input --> <!-- Input -->
<el-input v-if="item.type === 'input'" v-model="localModel[item.prop]" <el-input v-if="item.type === 'input'" v-model="localModel[item.prop]"
:placeholder="item.placeholder || `请输入${item.label}`" :clearable="true" :placeholder="item.placeholder || `请输入${item.label}`" :clearable="true"
...@@ -194,25 +196,99 @@ watch( ...@@ -194,25 +196,99 @@ watch(
} }
} }
localModel.value = initialModel // ✅ 在这里同步 modelValue(包括 extra 字段)
localModel.value = syncModelFromProps(props.modelValue, internalConfig.value)
}, },
{ immediate: true } { immediate: true }
) )
console.log('🚀 子组件 props.modelValue 初始值:', props.modelValue)
// 监听 modelValue(用于后续外部更新)
watch( watch(
() => props.modelValue, () => props.modelValue,
(newVal) => { (newVal) => {
if (!newVal) return if (!newVal || !internalConfig.value) return
// 只同步存在的字段,避免污染 // ✅ 同样使用 sync 函数
for (const item of internalConfig.value) { localModel.value = syncModelFromProps(newVal, internalConfig.value)
},
{ deep: true }
)
// 提取同步逻辑
function syncModelFromProps(newModelValue, newConfig) {
if (!newModelValue || !newConfig) return {}
const synced = {}
// 1. 同步主字段
for (const item of newConfig) {
const key = item.prop const key = item.prop
if (newVal.hasOwnProperty(key)) { if (newModelValue.hasOwnProperty(key)) {
localModel.value[key] = newVal[key] synced[key] = newModelValue[key]
} else if (item.multiple || ['checkbox-group', 'daterange'].includes(item.type)) {
synced[key] = item.defaultValue ?? []
} else {
synced[key] = item.defaultValue ?? ''
}
}
// 2. 同步 extra 字段(从 newModelValue 中的 sourceField 重新计算)
for (const item of newConfig) {
const sourceField = item.prop
const extraMap = item.onChangeExtraFields
if (!extraMap || typeof extraMap !== 'object') continue
const sourceObj = newModelValue[sourceField]
if (sourceObj && typeof sourceObj === 'object') {
// newModelValue 中有 sourceField → 重新计算 extra
for (const [targetKey, subPath] of Object.entries(extraMap)) {
const val = getNestedValue(sourceObj, subPath)
if (val !== undefined) {
synced[targetKey] = val
}
} }
} }
} }
)
// 3. 保留 localModel 中的 extra 字段(仅当 newModelValue 中没有对应的 sourceField 时)
for (const item of newConfig) {
const sourceField = item.prop
const extraMap = item.onChangeExtraFields
if (!extraMap || typeof extraMap !== 'object') continue
// 如果 newModelValue 中没有 sourceField,说明没有重新计算
if (newModelValue[sourceField] === undefined) {
// 那么保留 localModel 中对应的 extra 字段
for (const [targetKey, subPath] of Object.entries(extraMap)) {
if (localModel.value.hasOwnProperty(targetKey)) {
synced[targetKey] = localModel.value[targetKey]
}
}
}
}
// 4. 保留其他不在 config 中的字段
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]
}
}
}
return synced
}
function getNestedValue(obj, path) {
return path.split('.').reduce((current, key) => current?.[key], obj)
}
// 当字典加载完成时,触发同步 // 当字典加载完成时,触发同步
function markDictLoaded(prop) { function markDictLoaded(prop) {
dictLoaded.value.add(prop) dictLoaded.value.add(prop)
...@@ -222,27 +298,44 @@ function markDictLoaded(prop) { ...@@ -222,27 +298,44 @@ function markDictLoaded(prop) {
} }
} }
// 2. 用户操作导致 localModel 变化时,emit(防抖可选) // 2. 用户操作导致 localModel 变化时,emit(防抖可选)
function handleModelChange(value, item) { function handleModelChange(value, item) {
console.log('✅ handleModelChange 被调用', { prop: item?.prop, value }) console.group('🔄 handleModelChange')
// 同步额外字段 console.log('输入 value:', value)
console.log('item:', item)
console.log('当前 localModel:', localModel.value)
const newModel = { ...localModel.value, [item.prop]: value }
if (item?.type === 'select' && item.onChangeExtraFields) { if (item?.type === 'select' && item.onChangeExtraFields) {
const options = getSelectOptions(item) const options = getSelectOptions(item)
const opt = options.find(o => o.value === value) // ✅ 现在 value 和 o.value 类型一致 console.log('可用 options:', options)
if (opt) { const opt = options.find(o => o.value === value)
console.log('匹配的 opt:', opt)
if (opt?.raw) {
for (const [targetProp, sourceKey] of Object.entries(item.onChangeExtraFields)) { for (const [targetProp, sourceKey] of Object.entries(item.onChangeExtraFields)) {
localModel.value[targetProp] = opt.raw[sourceKey] const extraValue = opt.raw[sourceKey]
console.log(`✅ 同步 ${targetProp} =`, opt.raw[sourceKey]) newModel[targetProp] = extraValue
console.log(`✅ 设置 ${targetProp} =`, extraValue)
} }
} }
} }
// emit 更新 console.log('🆕 newModel:', newModel)
console.log('📦 props.modelValue:', props.modelValue)
console.log('isEqualShallow?', isEqualShallow(props.modelValue, newModel))
localModel.value = newModel
nextTick(() => { nextTick(() => {
if (!isEqualShallow(props.modelValue, localModel.value)) { if (!isEqualShallow(props.modelValue, newModel)) {
console.log('准备 emit modelValue:', localModel.value) console.log('📤 emit update:modelValue:', newModel)
emit('update:modelValue', { ...localModel.value }) emit('update:modelValue', newModel)
} else {
console.log('🚫 跳过 emit:认为相等')
} }
}) })
console.groupEnd()
} }
// 辅助函数:浅比较两个对象 // 辅助函数:浅比较两个对象
function isEqualShallow(a, b) { function isEqualShallow(a, b) {
......
...@@ -73,7 +73,7 @@ ...@@ -73,7 +73,7 @@
<el-table-column prop="policyNo" label="保单号" width="120" sortable /> <el-table-column prop="policyNo" label="保单号" width="120" sortable />
<el-table-column prop="insuranceCompany" 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="commissionPaidAmount" label="累积已入账金额" width="120" sortable />
<el-table-column prop="commissionPaidRatio" label="累积已入账比例" width="120" sortable /> <el-table-column prop="commissionPaidRatio" label="累积已入账比例" width="120" sortable :formatter="(row) => `${row.commissionPaidRatio }%`" />
<el-table-column prop="fortuneName" label="出账项目" width="130" sortable /> <el-table-column prop="fortuneName" label="出账项目" width="130" sortable />
<el-table-column prop="fortunePeriod" 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="fortuneTotalPeriod" label="总期数" width="120" sortable />
...@@ -83,14 +83,15 @@ ...@@ -83,14 +83,15 @@
<el-table-column prop="currency" label="出账币种" width="130" sortable /> <el-table-column prop="currency" label="出账币种" width="130" sortable />
<el-table-column prop="fortunePaidAmount" label="已出账金额" width="120" sortable /> <el-table-column prop="fortunePaidAmount" label="已出账金额" width="120" sortable />
<el-table-column prop="fortuneUnpaidAmount" 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="currentPaymentAmount" label="本期出账金额" width="120" sortable/>
<el-table-column prop="fortuneUnpaidRatio" label="剩余出账比例" width="120" sortable /> <el-table-column prop="fortuneUnpaidRatio" label="剩余出账比例" width="120" sortable :formatter="(row) => `${row.fortuneUnpaidRatio }%`" />
<el-table-column prop="status" label="出账状态" width="120" sortable> <el-table-column prop="status" label="出账状态" width="120" sortable>
<template #default="{ row }"> <template #default="{ row }">
{{ getDictLabel('csf_fortune_status', row.status) }} {{ getDictLabel('csf_fortune_status', row.status) }}
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="premium" label="期交保费" width="120" sortable /> <el-table-column prop="premium" label="期交保费" width="120" sortable />
<el-table-column prop="policyCurrency" label="保单币种" width="120" sortable />
<el-table-column prop="payoutDate" 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 prop="remark" label="备注" width="120" sortable />
<el-table-column fixed="right" label="操作" min-width="120"> <el-table-column fixed="right" label="操作" min-width="120">
...@@ -143,6 +144,7 @@ import CommonDialog from '@/components/commonDialog' ...@@ -143,6 +144,7 @@ import CommonDialog from '@/components/commonDialog'
import SearchForm from '@/components/SearchForm/SearchForm.vue' import SearchForm from '@/components/SearchForm/SearchForm.vue'
import { ElMessage } from 'element-plus' import { ElMessage } from 'element-plus'
import { formatCurrency } from '@/utils/number' import { formatCurrency } from '@/utils/number'
import { Select } from '@element-plus/icons-vue'
// 接口 // 接口
import { getPolicyFortuneList, addCheckRecordaddBatch, updatePayoutAmount, downloadPolicyFortuneAccount } from '@/api/financial/commission' import { getPolicyFortuneList, addCheckRecordaddBatch, updatePayoutAmount, downloadPolicyFortuneAccount } from '@/api/financial/commission'
import useUserStore from '@/store/modules/user' import useUserStore from '@/store/modules/user'
...@@ -162,7 +164,8 @@ const searchConfig = ref([ ...@@ -162,7 +164,8 @@ const searchConfig = ref([
type: 'input', type: 'input',
prop: 'policyNo', prop: 'policyNo',
label: '保单号' label: '保单号'
}, { },
{
type: 'select', type: 'select',
prop: 'statusList', prop: 'statusList',
label: '出账状态', label: '出账状态',
...@@ -209,13 +212,13 @@ const searchConfig = ref([ ...@@ -209,13 +212,13 @@ const searchConfig = ref([
startPlaceholder: '开始时间', startPlaceholder: '开始时间',
endPlaceholder: '结束时间' endPlaceholder: '结束时间'
}, },
// { {
// type: 'select', type: 'select',
// prop: 'status', prop: 'commissionStatusList',
// label: '入账状态', label: '入账状态',
// multiple: true, multiple: true,
// dictType: 'csf_expected_commission_status' dictType: 'csf_expected_commission_status'
// }, },
]) ])
// 表格操作菜单 // 表格操作菜单
const dropdownItems = [ const dropdownItems = [
...@@ -340,7 +343,13 @@ const setPayoutAmountConfig = [ ...@@ -340,7 +343,13 @@ const setPayoutAmountConfig = [
prop: 'status', prop: 'status',
label: '出账状态', label: '出账状态',
dictType: 'csf_expected_fortune_status' dictType: 'csf_expected_fortune_status'
} },
// {
// type: 'input',
// prop: 'exchangeRate',
// label: '结算汇率',
// defaultValue: 1
// }
] ]
...@@ -370,7 +379,7 @@ const handleQuery = async () => { ...@@ -370,7 +379,7 @@ const handleQuery = async () => {
const params = searchFormRef.value.getFormData() const params = searchFormRef.value.getFormData()
loadTableData(params) loadTableData(params)
} }
const visibleDefaultButtons = ref(['add', 'import', 'export', 'reset', 'query']) const visibleDefaultButtons = ref(['add', 'export', 'reset', 'query'])
// 按钮配置 // 按钮配置
const operationBtnList = ref([ const operationBtnList = ref([
{ {
...@@ -378,11 +387,6 @@ const operationBtnList = ref([ ...@@ -378,11 +387,6 @@ const operationBtnList = ref([
direction: 'left', direction: 'left',
label: '新增出账', label: '新增出账',
click: handleAdd click: handleAdd
}, {
key: 'import',
direction: 'left',
label: '导入出账',
click: handleImport
}, },
{ {
key: 'export', key: 'export',
...@@ -481,6 +485,7 @@ const updatePayoutAmountapi = async (data) => { ...@@ -481,6 +485,7 @@ const updatePayoutAmountapi = async (data) => {
if (res.code === 200) { if (res.code === 200) {
ElMessage.success('设置本期出账金额成功') ElMessage.success('设置本期出账金额成功')
loadTableData() loadTableData()
setPayoutAmountDialogFlag.value = false
} else { } else {
ElMessage.error(res.msg || '设置本期出账金额失败') ElMessage.error(res.msg || '设置本期出账金额失败')
} }
......
...@@ -105,16 +105,17 @@ ...@@ -105,16 +105,17 @@
<el-table-column prop="commissionDate" label="入账日" width="120" sortable /> <el-table-column prop="commissionDate" label="入账日" width="120" sortable />
<el-table-column prop="commissionExpectedStatus" label="入账状态" width="120" sortable <el-table-column prop="commissionExpectedStatus" label="入账状态" width="120" sortable
:formatter="formatStatus" /> :formatter="formatStatus" />
<el-table-column prop="currentCommissionRatio" label="本次入账比例" width="130" sortable /> <el-table-column prop="currentCommissionRatio" label="本次入账比例" width="130" sortable :formatter="formatRatio" />
<el-table-column prop="periodPaidRatio" label="累积入账比例" width="130" sortable /> <el-table-column prop="paidRatio" label="累积入账比例" width="130" sortable :formatter="formatRatio" />
<el-table-column prop="periodPendingRatio" label="待入账比例" width="120" sortable /> <el-table-column prop="pendingRatio" label="待入账比例" width="120" sortable :formatter="formatRatio" />
<el-table-column prop="paidAmount" label="本次入账金额" width="130" sortable /> <el-table-column prop="amount" label="本次入账金额" width="130" sortable :formatter="formatCurrencyUtil" />
<el-table-column prop="currency" label="入账币种" width="120" sortable /> <el-table-column prop="currency" label="入账币种" width="120" sortable />
<el-table-column prop="exchangeRate" label="结算汇率(实)" width="140" sortable /> <el-table-column prop="exchangeRate" label="结算汇率(实)" width="140" sortable />
<el-table-column prop="commissionPeriod" label="本次入账期数" width="130" sortable /> <el-table-column prop="commissionPeriod" label="本次入账期数" width="130" sortable />
<el-table-column prop="totalPeriod" label="总期数" width="120" sortable /> <el-table-column prop="totalPeriod" label="总期数" width="120" sortable />
<el-table-column prop="commissionName" label="入账项目" width="120" sortable /> <el-table-column prop="commissionName" label="入账项目" width="120" sortable />
<el-table-column prop="premium" label="期交保费" width="120" sortable /> <el-table-column prop="premium" label="期交保费" width="120" sortable :formatter="formatCurrencyUtil" />
<el-table-column prop="policyCurrency" label="保单币种" width="120" sortable />
<el-table-column prop="remark" label="备注" width="120" sortable /> <el-table-column prop="remark" label="备注" width="120" sortable />
<el-table-column prop="isDeleted" label="记录状态" width="120" sortable> <el-table-column prop="isDeleted" label="记录状态" width="120" sortable>
<template #default="{ row }"> <template #default="{ row }">
...@@ -131,11 +132,12 @@ ...@@ -131,11 +132,12 @@
</template> </template>
<el-menu @select="handleSelect($event, row)" popper-class="custom-menu"> <el-menu @select="handleSelect($event, row)" popper-class="custom-menu">
<el-menu-item :index="item.value" v-for="item in dropdownItems" :key="item.value"> <el-menu-item :index="item.value" v-for="item in dropdownItems" :key="item.value">
<el-text class="mx-1" v-if="!item.confirm">{{item.label}}</el-text> <el-text class="mx-1" v-if="!item.confirm">{{ item.label }}</el-text>
<el-popconfirm v-if="item.confirm" confirm-button-text="Yes" cancel-button-text="No" :icon="InfoFilled" icon-color="#626AEF" <el-popconfirm v-if="item.confirm" confirm-button-text="Yes" cancel-button-text="No"
:title="item.confirm" @confirm="handleMenuConfirm(item.value, row)" width="300" placement="left-end"> :icon="InfoFilled" icon-color="#626AEF" :title="item.confirm"
@confirm="handleMenuConfirm(item.value, row)" width="300" placement="left-end">
<template #reference> <template #reference>
<el-text class="mx-1">{{item.label}}</el-text> <el-text class="mx-1">{{ item.label }}</el-text>
</template> </template>
</el-popconfirm> </el-popconfirm>
</el-menu-item> </el-menu-item>
...@@ -170,7 +172,7 @@ ...@@ -170,7 +172,7 @@
<el-row :gutter="10"> <el-row :gutter="10">
<el-col :xs="24" :sm="12" :md="3" :lg="3"> <el-col :xs="24" :sm="12" :md="3" :lg="3">
<el-button type="primary" :icon="Plus" :disabled="!checkFormData?.reconciliationYearMonth" <el-button type="primary" :icon="Plus" :disabled="!checkFormData?.reconciliationYearMonth"
@click="addCheckRecordDialogFlag = true;">新增</el-button> @click="handleAddCheckList()">新增</el-button>
</el-col> </el-col>
<el-col :xs="24" :sm="24" :md="3" :lg="3"> <el-col :xs="24" :sm="24" :md="3" :lg="3">
<el-button type="primary" :icon="Upload" :disabled="!checkFormData?.reconciliationYearMonth" <el-button type="primary" :icon="Upload" :disabled="!checkFormData?.reconciliationYearMonth"
...@@ -235,8 +237,8 @@ ...@@ -235,8 +237,8 @@
</CommonDialog> </CommonDialog>
<!-- 新增检核记录弹窗 --> <!-- 新增检核记录弹窗 -->
<CommonDialog :dialogTitle='editStatus.value == "add" ? "新增检核记录" : "修改检核记录"' dialogWidth='80%' <CommonDialog :dialogTitle='editStatus.value == "add" ? "新增检核记录" : "修改检核记录"' dialogWidth='80%'
:openDialog=addCheckRecordDialogFlag :showAction='true' :showClose='true' :openDialog=addCheckRecordDialogFlag :showAction='true' :showClose='true' @close='closeDialog()'
@close='addCheckRecordDialogFlag = false;editStatus = "add"' @confirm='handleAddCheckRecord()'> @confirm='handleAddCheckRecord()'>
<el-row> <el-row>
<el-col :xs="24" :sm="24" :md="24" :lg="24"> <el-col :xs="24" :sm="24" :md="24" :lg="24">
<SearchForm ref="addCheckRecordFormRef" :config="addCheckRecordConfig" v-model="addReceivablesFormModel" /> <SearchForm ref="addCheckRecordFormRef" :config="addCheckRecordConfig" v-model="addReceivablesFormModel" />
...@@ -248,7 +250,8 @@ ...@@ -248,7 +250,8 @@
:showClose='true' @close='fileUploadDialogFlag = false; files = ""' :showClose='true' @close='fileUploadDialogFlag = false; files = ""'
@confirm='fileUploadDialogFlag = false; files = ""'> @confirm='fileUploadDialogFlag = false; files = ""'>
<FileUpload v-model="files" :data="{ reconciliationYearMonth: checkFormData.reconciliationYearMonth }" <FileUpload v-model="files" :data="{ reconciliationYearMonth: checkFormData.reconciliationYearMonth }"
:file-type="['xlsx', 'xls']" :action="'/csf/api/commission/upload/excel'" :limit="1" :fileSize="15" @uploadEnd="handleUploadEnd"/> :file-type="['xlsx', 'xls']" :action="'/csf/api/commission/upload/excel'" :limit="1" :fileSize="15"
@uploadEnd="handleUploadEnd" />
</CommonDialog> </CommonDialog>
<!-- 设置比对状态 --> <!-- 设置比对状态 -->
...@@ -285,9 +288,22 @@ import { ...@@ -285,9 +288,22 @@ import {
getPolicyCommissionList, generateCommissionRecord, getPolicyCommissionList, generateCommissionRecord,
addPayrollCheckRecord, commissionExpectedRecord, updateCompareStatus, updateCommissionRecord, deletePolicyCommission, syncExpectedCommission addPayrollCheckRecord, commissionExpectedRecord, updateCompareStatus, updateCommissionRecord, deletePolicyCommission, syncExpectedCommission
} from '@/api/financial/commission' } from '@/api/financial/commission'
import { Plus, Upload, Select } from '@element-plus/icons-vue'
import { InfoFilled, Select, Upload, Plus } from '@element-plus/icons-vue'
import FileUpload from '@/components/FileUpload/index.vue' import FileUpload from '@/components/FileUpload/index.vue'
import { loadDicts, getDictLabel } from '@/utils/useDict' import { loadDicts, getDictLabel } from '@/utils/useDict'
const formatRatio = (row, column, cellValue, index) => {
if (cellValue == null || cellValue == '' || cellValue == 0) {
return '-'
}
return cellValue + '%'
}
const formatCurrencyUtil = (row, column, cellValue, index) => {
if (cellValue == null || cellValue == '' || cellValue == 0) {
return '-'
}
return formatCurrency(cellValue)
}
const files = ref('') const files = ref('')
// 分页相关 // 分页相关
const currentPage = ref(1) const currentPage = ref(1)
...@@ -412,11 +428,11 @@ const checkConfig = ref([ ...@@ -412,11 +428,11 @@ const checkConfig = ref([
]) ])
const checkRecordEdit = (row) => { const checkRecordEdit = (row) => {
console.log('查看记录', row) console.log('查看记录', row)
selectedRowCheck.value = {...row} selectedRowCheck.value = { ...row }
editStatus.value = 'edit' editStatus.value = 'edit'
addReceivablesFormModel.value = { ...row } addReceivablesFormModel.value = { ...row }
addCheckRecordDialogFlag.value = true addCheckRecordDialogFlag.value = true
// console.log('父组件赋值',addReceivablesFormModel.value) console.log('父组件赋值', addReceivablesFormModel.value)
} }
const checkFormData = ref({ const checkFormData = ref({
...@@ -469,8 +485,8 @@ onMounted(async () => { ...@@ -469,8 +485,8 @@ onMounted(async () => {
} }
}) })
// 格式化函数(每次渲染都会调用,所以能拿到最新字典) // 格式化函数(每次渲染都会调用,所以能拿到最新字典)
const formatStatus = (row, column) => { const formatStatus = (row, column, cellValue, index) => {
return getDictLabel('csf_expected_commission_status', row.status) // 实时查缓存 return getDictLabel('csf_expected_commission_status', cellValue) // 实时查缓存
} }
// 应收单类型 // 应收单类型
const commissionBizTypeOptions = [ const commissionBizTypeOptions = [
...@@ -599,34 +615,48 @@ const addCheckRecordConfig = ref([ ...@@ -599,34 +615,48 @@ const addCheckRecordConfig = ref([
]) ])
const addCheckRecordDialogFlag = ref(false) const addCheckRecordDialogFlag = ref(false)
const handleAddCheckRecord = async () => { const handleAddCheckRecord = async () => {
try { try {
const params = ref({}) // ✅ 统一从子组件获取完整表单数据(含 extra 字段)
await nextTick() // 确保子组件已同步
const formData = addCheckRecordFormRef.value.getFormData()
console.log('======',formData)
let params
if (editStatus.value === 'edit') { if (editStatus.value === 'edit') {
params.value = { params = {
...addReceivablesFormModel.value, ...formData, // ←←← 使用 formData,不是 addReceivablesFormModel.value
commissionBizId: selectedRowCheck.value.commissionBizId commissionBizId: selectedRowCheck.value.commissionBizId
} }
console.log(params) await updateCommissionRecord(params)
await updateCommissionRecord(params.value)
ElMessage.success('更新检核记录成功')
} else { } else {
const addCheckSearchParams = addCheckRecordFormRef.value.getFormData() params = {
console.log('新增检核记录', addCheckSearchParams) ...formData,
params.value = {
...addCheckSearchParams,
reconciliationYearMonth: checkFormData.value.reconciliationYearMonth reconciliationYearMonth: checkFormData.value.reconciliationYearMonth
} }
await addPayrollCheckRecord([params.value]) await addPayrollCheckRecord([params])
ElMessage.success('新增检核记录成功')
addCheckRecordDialogFlag.value = false
checkRecordTableData.value = []
} }
ElMessage.success(editStatus.value === 'edit' ? '更新成功' : '新增成功')
addCheckRecordDialogFlag.value = false
resetForm('addReceivablesFormModel')
checkRecordQuery() checkRecordQuery()
} catch (error) { } catch (error) {
console.error('新增检核记录失败', error) console.error('操作失败', error)
ElMessage.error('新增检核记录失败') ElMessage.error('操作失败')
} }
} }
const resetForm = (type) => {
if (type === 'addReceivablesFormModel')
addReceivablesFormModel.value = {}
addCheckRecordFormRef.value.resetForm()
}
const closeDialog = () => {
addCheckRecordDialogFlag.value = false
editStatus.value = 'add'
handleQuery()
}
// 生成可出账记录 // 生成可出账记录
const generateCommissionRecordapi = async () => { const generateCommissionRecordapi = async () => {
try { try {
...@@ -646,8 +676,8 @@ const generateCommissionRecordapi = async () => { ...@@ -646,8 +676,8 @@ const generateCommissionRecordapi = async () => {
} }
// 表格操作菜单 // 表格操作菜单
const dropdownItems = [ const dropdownItems = [
{ label: '设置比对状态', value: 'setStatus' ,confirm:''}, { label: '设置比对状态', value: 'setStatus', confirm: '' },
{ label: '同步到应收款管理', value: 'syncToReceivable',confirm:'确认要同步到应收款管理吗?' }, { label: '同步到应收款管理', value: 'syncToReceivable', confirm: '确认要同步到应收款管理吗?' },
// { label: '更新数据', value: 'editRecord' }, // { label: '更新数据', value: 'editRecord' },
// { label: '查看记录', value: 'viewRecord' } // { label: '查看记录', value: 'viewRecord' }
] ]
...@@ -655,7 +685,7 @@ const handleMenuConfirm = async (action, row) => { ...@@ -655,7 +685,7 @@ const handleMenuConfirm = async (action, row) => {
console.log('点击了操作菜单', action, row) console.log('点击了操作菜单', action, row)
if (action === 'syncToReceivable') { if (action === 'syncToReceivable') {
try { try {
if(row.commissionStatus !== '3'){ if (row.commissionStatus !== '3') {
ElMessage.error('应收款管理已有这条记录,无需同步') ElMessage.error('应收款管理已有这条记录,无需同步')
return return
} }
...@@ -803,11 +833,17 @@ const handleSelect = (e, row) => { ...@@ -803,11 +833,17 @@ const handleSelect = (e, row) => {
} else if (e === 'viewRecord') { } else if (e === 'viewRecord') {
viewRecordDialogFlag.value = true viewRecordDialogFlag.value = true
return return
}else if (e === 'syncToReceivable') { } else if (e === 'syncToReceivable') {
return return
} }
} }
const handleAddCheckList = () => {
editStatus.value = 'add'
addReceivablesFormModel.value = { ...selectedRow.value }
addCheckRecordDialogFlag.value = true
resetForm('addReceivablesFormModel')
}
// 设置比对状态api // 设置比对状态api
......
<template> <template>
<div> <div>
<CommonPage :operationBtnList="operationBtnList" :showSearchForm="true" :show-pagination="true" :total="pageTotal" <CommonPage :operationBtnList="operationBtnList" :visibleDefaultButtons="visibleDefaultButtons" :showSearchForm="true" :show-pagination="true" :total="pageTotal"
:current-page="currentPage" :page-size="pageSize" @size-change="handleSizeChange" :current-page="currentPage" :page-size="pageSize" @size-change="handleSizeChange"
@current-change="handleCurrentChange"> @current-change="handleCurrentChange">
<!-- 搜索区域 --> <!-- 搜索区域 -->
...@@ -15,8 +15,8 @@ ...@@ -15,8 +15,8 @@
<el-col :xs="24" :sm="12" :md="4" :lg="4"> <el-col :xs="24" :sm="12" :md="4" :lg="4">
<el-card shadow="hover" class="statistics-card"> <el-card shadow="hover" class="statistics-card">
<div class="card-content"> <div class="card-content">
<div class="card-label">应出总金额</div> <div class="card-label">应出总金额</div>
<div class="card-value">{{ formatCurrency(statisticsData.totalAmount) }}</div> <div class="card-value">{{ formatCurrency(statisticsData.totalExpectedAmount) }}</div>
</div> </div>
</el-card> </el-card>
</el-col> </el-col>
...@@ -32,7 +32,7 @@ ...@@ -32,7 +32,7 @@
<el-card shadow="hover" class="statistics-card"> <el-card shadow="hover" class="statistics-card">
<div class="card-content"> <div class="card-content">
<div class="card-label">待出账金额</div> <div class="card-label">待出账金额</div>
<div class="card-value">{{ formatCurrency(statisticsData.pendingPaidAmount) }}</div> <div class="card-value">{{ formatCurrency(statisticsData.totalUnpaidAmount) }}</div>
</div> </div>
</el-card> </el-card>
</el-col> </el-col>
...@@ -56,7 +56,7 @@ ...@@ -56,7 +56,7 @@
<el-card shadow="hover" class="statistics-card"> <el-card shadow="hover" class="statistics-card">
<div class="card-content"> <div class="card-content">
<div class="card-label">总保费</div> <div class="card-label">总保费</div>
<div class="card-value">{{ statisticsData.totalPolicyCount }}</div> <div class="card-value">{{ formatCurrency(statisticsData.totalPremiumAmount) }}</div>
</div> </div>
</el-card> </el-card>
</el-col> </el-col>
...@@ -113,9 +113,13 @@ ...@@ -113,9 +113,13 @@
{{ formatCurrency(row.unpaidAmount) }} {{ formatCurrency(row.unpaidAmount) }}
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="broker_ratio" label="持有比例" width="120" sortable /> <el-table-column prop="brokerRatio" label="持有比例" width="120" sortable >
<el-table-column prop="insurerBizId" label="保险公司" width="120" sortable /> <template #default="{ row }">
<el-table-column prop="productLaunchBizId" label="产品计划" width="120" sortable /> {{ (row.brokerRatio || 0) + '%' }}
</template>
</el-table-column>
<el-table-column prop="insuranceCompany" label="保险公司" width="120" sortable />
<el-table-column prop="productName" label="产品计划" width="120" sortable />
<el-table-column prop="premium" label="期交保费" width="120" sortable> <el-table-column prop="premium" label="期交保费" width="120" sortable>
<template #default="{ row }"> <template #default="{ row }">
{{ formatCurrency(row.premium) }} {{ formatCurrency(row.premium) }}
...@@ -147,13 +151,13 @@ ...@@ -147,13 +151,13 @@
:showClose="true" @close="payRecordDialogTableVisible = false"> :showClose="true" @close="payRecordDialogTableVisible = false">
<el-table :data="payRecordDialogTableData" border style="width: 100%"> <el-table :data="payRecordDialogTableData" border style="width: 100%">
<el-table-column v-for="item in payRecordDialogTableColumns" :key="item.property" :property="item.property" <el-table-column v-for="item in payRecordDialogTableColumns" :key="item.property" :property="item.property"
:label="item.label" :width="item.width" /> :label="item.label" :width="item.width" :formatter="item.formatter" />
</el-table> </el-table>
</CommonDialog> </CommonDialog>
<!-- 新增出账记录 --> <!-- 新增出账记录 -->
<CommonDialog :dialogTitle="editStatus === 'add' ? '新增出账记录' : '修改出账记录'" dialogWidth="80%" <CommonDialog :dialogTitle="editStatus === 'add' ? '新增出账记录' : '修改出账记录'" dialogWidth="80%"
:openDialog="addPayRecordDialogVisible" :showAction="true" :showClose="true" :openDialog="addPayRecordDialogVisible" :showAction="true" :showClose="true"
@close="addPayRecordDialogVisible = false; editStatus = 'add'" @confirm="handleConfirmAddPayRecord"> @close="resetAddPayRecordForm" @confirm="handleConfirmAddPayRecord">
<SearchForm ref="addPayRecordFormRef" :config="addPayRecordFormConfig" v-model="addPayRecordFormModel" /> <SearchForm ref="addPayRecordFormRef" :config="addPayRecordFormConfig" v-model="addPayRecordFormModel" />
</CommonDialog> </CommonDialog>
<!-- 设置出账状态 --> <!-- 设置出账状态 -->
...@@ -171,11 +175,12 @@ import CommonPage from '@/components/commonPage' ...@@ -171,11 +175,12 @@ import CommonPage from '@/components/commonPage'
import { ref, reactive } from 'vue' import { ref, reactive } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus' import { ElMessage, ElMessageBox } from 'element-plus'
import { formatCurrency } from '@/utils/number' import { formatCurrency } from '@/utils/number'
import { expectedFortuneList, payRecordList, addPayRecord, updatePayRecord } from '@/api/financial/commission' import { expectedFortuneList, payRecordList, addPayRecord, updatePayRecord, exportPayRecord } from '@/api/financial/commission'
import SearchForm from '@/components/SearchForm/SearchForm.vue' import SearchForm from '@/components/SearchForm/SearchForm.vue'
import CommonDialog from '@/components/commonDialog' import CommonDialog from '@/components/commonDialog'
import { loadDicts, getDictLabel } from '@/utils/useDict' import { loadDicts, getDictLabel } from '@/utils/useDict'
import useUserStore from '@/store/modules/user' import useUserStore from '@/store/modules/user'
import { safeDownload } from '@/utils/safeDownload'
// 设置出账状态 // 设置出账状态
const setPayRecordStatusDialogVisible = ref(false) const setPayRecordStatusDialogVisible = ref(false)
const selectedRow = ref(null) const selectedRow = ref(null)
...@@ -192,7 +197,9 @@ const setPayRecordStatusFormConfig = ref([ ...@@ -192,7 +197,9 @@ const setPayRecordStatusFormConfig = ref([
label: '修改理由', label: '修改理由',
}, },
]) ])
const visibleDefaultButtons = ref([
'add', 'export', 'reset', 'query'
])
const userStore = useUserStore() const userStore = useUserStore()
// 应收单类型 // 应收单类型
const fortuneBizTypeOptions = [ const fortuneBizTypeOptions = [
...@@ -297,6 +304,8 @@ const searchConfig = ref([ ...@@ -297,6 +304,8 @@ const searchConfig = ref([
} }
}, },
]) ])
const payRecordDialogTableVisible = ref(false) const payRecordDialogTableVisible = ref(false)
// 新增出账记录 // 新增出账记录
const addPayRecordFormModel = ref({ const addPayRecordFormModel = ref({
...@@ -419,6 +428,12 @@ const handleConfirmAddPayRecord = async () => { ...@@ -419,6 +428,12 @@ const handleConfirmAddPayRecord = async () => {
} }
} }
// 弹窗表单重置
const resetAddPayRecordForm = () => {
addPayRecordFormRef.value.resetForm()
editStatus.value = 'add'
addPayRecordDialogVisible.value = false
}
const editStatus = ref('add') const editStatus = ref('add')
...@@ -433,13 +448,13 @@ const handleSelect = async (e, row) => { ...@@ -433,13 +448,13 @@ const handleSelect = async (e, row) => {
payRecordDialogTableColumns.value = [ payRecordDialogTableColumns.value = [
{ property: 'broker', label: '转介人', width: '100', }, { property: 'broker', label: '转介人', width: '100', },
{ property: 'fortuneName', label: '出账项目', width: '150' }, { property: 'fortuneName', label: '出账项目', width: '150' },
{ property: 'amount', label: '出账金额', width: '150' }, { property: 'currentPaymentAmount', label: '出账金额', width: '150' },
{ property: 'currency', label: '出账币种', width: '150' }, { property: 'currency', label: '出账币种', width: '150' },
{ property: 'amount', label: '出账比例', width: '150' }, { property: 'currentPaymentRatio', label: '出账比例', width: '150',formatter: (row) => `${row.currentPaymentRatio }%` },
{ property: 'currentCommissionRatio', label: '待出账比例', width: '150' }, { property: 'fortuneUnpaidRatio', label: '待出账比例', width: '150',formatter: (row) => `${row.fortuneUnpaidRatio }%` },
{ property: 'fortunePeriod', label: '佣金期数', width: '150' }, { property: 'fortunePeriod', label: '佣金期数', width: '150' },
{ property: 'fortuneTotalPeriod', label: '总期数', width: '150' }, { property: 'fortuneTotalPeriod', label: '总期数', width: '150' },
{ property: 'updaterId', label: '操作人', width: '150' }, { property: 'reconciliationOperator', label: '操作人', width: '150' },
{ property: 'updateTime', label: '操作时间', width: '150' } { property: 'updateTime', label: '操作时间', width: '150' }
] ]
} else if (e === 'setStatus') { } else if (e === 'setStatus') {
...@@ -501,8 +516,17 @@ const handleImport = () => { ...@@ -501,8 +516,17 @@ const handleImport = () => {
ElMessage.info('点击导入按钮') ElMessage.info('点击导入按钮')
} }
const handleExport = () => { const handleExport = async () => {
ElMessage.info('点击导出按钮') // 获取搜索参数
const params = searchFormRef.value?.getFormData() || {}
const response = await exportPayRecord(params)
// 文件名设置为应收款导出_yyyy-MM-dd hh:mm:ss.xlsx,不需要-,用字符串
const fileName = `应付款导出_${new Date().toLocaleString().replace(/\//g, '').replace(/:/g, '').replace(/\s/g, '')}.xlsx`
await safeDownload(
response,
fileName,
'application/vnd.ms-excel;charset=utf-8'
)
} }
const handleReset = () => { const handleReset = () => {
...@@ -525,11 +549,11 @@ const operationBtnList = ref([ ...@@ -525,11 +549,11 @@ const operationBtnList = ref([
direction: 'left', direction: 'left',
click: handleAdd click: handleAdd
}, },
{ // {
key: 'import', // key: 'import',
direction: 'left', // direction: 'left',
click: handleImport // click: handleImport
}, // },
{ {
key: 'export', key: 'export',
direction: 'right', direction: 'right',
......
...@@ -69,9 +69,9 @@ ...@@ -69,9 +69,9 @@
{{ (row.pendingRatio || 0) + '%' }} {{ (row.pendingRatio || 0) + '%' }}
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="pendingPaidAmount" label="待入账金额(估)" width="160" sortable> <el-table-column prop="pendingAmount" label="待入账金额(估)" width="160" sortable>
<template #default="{ row }"> <template #default="{ row }">
{{ numberWithCommas(row.pendingPaidAmount) }} {{ numberWithCommas(row.pendingAmount) }}
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="currency" label="入账币种" width="100" /> <el-table-column prop="currency" label="入账币种" width="100" />
...@@ -618,7 +618,7 @@ const handleSelect = async (e, row) => { ...@@ -618,7 +618,7 @@ const handleSelect = async (e, row) => {
{ property: 'amount', label: '入账金额', width: '150' }, { property: 'amount', label: '入账金额', width: '150' },
{ property: 'currentCommissionRatio', label: '入账比例', width: '150' }, { property: 'currentCommissionRatio', label: '入账比例', width: '150' },
{ property: 'commissionDate', label: '入账日', width: '150' }, { property: 'commissionDate', label: '入账日', width: '150' },
{ property: 'status', label: '入账状态', width: '150' } { property: 'commissionStatus', label: '入账状态', width: '150' }
] ]
// 加载真实数据 // 加载真实数据
const records = await loadEntryRecordData(row.commissionExpectedBizId) const records = await loadEntryRecordData(row.commissionExpectedBizId)
......
<template> <template>
<div class="data-management-page"> <div>
<!-- 查询区域 --> <CommonPage :operationBtnList='operationBtnList' :visibleDefaultButtons="visibleDefaultButtons"
<el-card class="search-card"> :showSearchForm='true' :show-pagination='true' :total='pageTotal' :current-page='currentPage'
<!-- 第一行筛选条件 --> :page-size='pageSize' @size-change='handleSizeChange' @current-change='handleCurrentChange'>
<el-row :gutter="20" class="search-row"> <!-- 搜索区域 -->
<el-col :span="8"> <template #searchForm>
<div class="form-item"> <SearchForm ref="searchFormRef" :config="searchConfig" />
<label class="form-label">保单号</label>
<el-input
v-model="searchForm.policyNo"
placeholder="请输入"
clearable
size="default"
@keyup.enter="handleSearch"
/>
</div>
</el-col>
<el-col :span="8">
<div class="form-item">
<label class="form-label">客户姓名</label>
<el-input
v-model="searchForm.customerName"
placeholder="请输入"
clearable
size="default"
@keyup.enter="handleSearch"
/>
</div>
</el-col>
<el-col :span="8">
<div class="form-item">
<label class="form-label">新单状态</label>
<el-select v-model="searchForm.status" placeholder="请选择" clearable size="default">
<!-- 增加全部,默认传空字符串 -->
<el-option label="全部" value=" " />
<el-option
v-for="item in policyFollowStatusList"
:key="item.itemValue"
:label="item.itemLabel"
:value="item.itemValue"
/>
</el-select>
</div>
</el-col>
</el-row>
<!-- 第二行筛选条件 -->
<el-row :gutter="20" class="search-row">
<el-col :span="8">
<div class="form-item">
<label class="form-label">签单时间</label>
<el-date-picker
v-model="searchForm.signDateRange"
type="daterange"
range-separator="至"
start-placeholder="开始日期"
end-placeholder="结束日期"
size="default"
/>
</div>
</el-col>
<!-- :icon="Search" -->
<el-col :span="8" class="search-buttons">
<el-button type="primary" @click="handleSearch" size="default" class="search-btn">
查询
</el-button>
<!-- :icon="RefreshLeft" -->
<el-button @click="resetForm" size="default"> 重置 </el-button>
</el-col>
</el-row>
</el-card>
<!-- Excel导入区域 -->
<div class="import-area">
<el-card class="import-card">
<div class="import-content">
<div class="import-actions">
<el-upload
class="upload-excel"
:auto-upload="false"
:on-change="handleFileChange"
:show-file-list="false"
accept=".xlsx, .xls"
>
<el-button type="success" size="default"> 上传Excel文件 </el-button>
</el-upload>
<el-button text @click="downloadTemplate" size="default" class="download-template-btn">
下载模板
</el-button>
</div>
<!-- 文件信息显示 -->
<div v-if="selectedFile" class="file-info">
<div class="file-info-content">
<el-icon><document /></el-icon>
<span class="file-name">{{ selectedFile.name }}</span>
<span class="file-size">({{ formatFileSize(selectedFile.size) }})</span>
</div>
<el-button
type="primary"
@click="handleImport"
size="default"
:loading="importLoading"
class="confirm-import-btn"
>
确认导入
</el-button>
</div>
</div>
</el-card>
</div>
<!-- 列表区域 -->
<el-card class="table-card">
<div class="table-actions">
<el-button type="primary" @click="handleUpdateToPolicyLib">更新至保单库</el-button>
<!-- <el-text class="mx-1" size="large">新单首期保费缴费完成后,勾选并点击更新至保单库,可前往保单中心查询</el-text> -->
</div>
<el-table
v-loading="tableLoading"
:data="tableData"
border
style="width: 100%"
height="350"
@selection-change="handleSelectionChange"
@sort-change="handleSortChange"
:row-class-name="tableRowClassName"
>
<el-table-column type="selection" width="55" align="center" />
<!-- 新单状态需要通过policyFollowStatusList和value匹配,显示label -->
<el-table-column prop="status" label="新单状态" width="100" align="center" sortable>
<template #default="scope">
<span>{{ convertStatusToDict(1, scope.row.status) }}</span>
</template> </template>
</el-table-column> <!-- 列表区域 -->
<el-table-column prop="coolingOffEndDate" label="冷静期结束日期" width="150" align="center"> <template #table>
<template #default="scope"> <el-table :data="tableData" height="500" border highlight-current-row style="width: 100%" v-loading="loading">
<span>{{ <el-table-column prop="policyNo" label="保单号" width="200" sortable />
scope.row.coolingOffEndDate ? parseTime(scope.row.coolingOffEndDate) : '--' <el-table-column prop="status" label="新单状态" width="120" sortable>
}}</span> <template #default="{ row }">
{{ getDictLabel('csf_policy_follow_status', row.status) }}
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="coolingOffDays" label="冷静期天数" width="150" align="center"> <el-table-column prop="appointmentNo" label="预约编号" width="200" sortable />
</el-table-column> <el-table-column prop="signDate" label="签单日" width="200" sortable />
<el-table-column prop="policyNo" label="保单号" width="150" align="center" sortable /> <el-table-column prop="policyBizId" label="最晚缴费日" width="200" sortable />
<el-table-column prop="customerName" label="客户名称" width="100" align="center" sortable /> <el-table-column prop="policyHolder" label="投保人" width="150" sortable />
<el-table-column prop="signDate" label="签单日期" width="150" align="center" sortable> <el-table-column prop="policyNo" label="受保人" width="150" sortable />
<template #default="scope"> <el-table-column prop="insuranceCompany" label="保险公司" width="200" sortable />
<span>{{ parseTime(scope.row.signDate) }}</span> <el-table-column prop="productName" label="产品计划" width="200" sortable />
<el-table-column prop="paymentTerm" label="缴费年期" width="120" sortable />
<el-table-column fixed="right" label="操作" min-width="80">
<template #default="{ row }">
<el-popover placement="right" :width="200" trigger="click">
<template #reference>
<el-icon>
<MoreFilled />
</el-icon>
</template> </template>
</el-table-column> <el-menu @select="handleSelect($event, row)" popper-class="custom-menu">
<el-table-column prop="signer" label="签单人" width="100" align="center" sortable /> <el-menu-item :index="item.value" v-for="item in dropdownItems" :key="item.value">{{
<el-table-column prop="paymentTerm" label="供款年期" width="100" align="center" sortable /> item.label
<el-table-column prop="productName" label="产品名称" width="150" align="center" sortable /> }}</el-menu-item>
<el-table-column prop="insurer" label="保险公司" width="150" align="center" sortable /> </el-menu>
<el-table-column </el-popover>
prop="reconciliationCompany"
label="对账公司"
width="150"
align="center"
sortable
/>
<el-table-column
prop="policyHolder"
label="保单持有人"
width="150"
align="center"
sortable
/>
<el-table-column prop="insured" label="受保人" width="100" align="center" sortable />
<el-table-column prop="currency" label="币种" width="100" align="center" sortable>
<template #default="scope">
<dict-tag :options="currencyTypeOptions" :value="scope.row.currency" />
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="initialPremium" label="首期保费" width="150" align="center" sortable> </el-table>
<template #default="scope">
{{ numberWithCommas(scope.row.initialPremium) }}
</template> </template>
</el-table-column> </CommonPage>
<el-table-column <!-- 弹窗-->
label="操作" <CommonDialog dialogTitle='修改状态' dialogWidth='80%' :openDialog=editStatusDialogFlag :showAction='true'
align="center" :showClose='true' @close='editStatusDialogFlag = false' @confirm='handleEditStatusSubmit'>
width="200" <SearchForm ref="editStatusFormRef" :config="editStatusFormConfig" v-model="editStatusFormData" />
class-name="small-padding fixed-width" </CommonDialog>
fixed="right"
> <!-- 查看关联记录 -->
<template #default="scope"> <CommonDialog dialogTitle='查看关联记录' dialogWidth='80%' :openDialog=viewRelatedDialogFlag :showAction='false'
<div class="btnCon"> :showClose='true' @close='viewRelatedDialogFlag = false'>
<el-button link type="primary" size="small" @click="handleReport(scope.row)"> <el-table :data="relateRecordTableData" style="width: 100%">
生成签约单完成报告 <el-table-column fixed prop="followDate" label="流程编号" width="150" />
<el-table-column prop="name" label="客户姓名" width="120" />
<el-table-column prop="state" label="创建时间" width="120" sortable />
<el-table-column fixed="right" label="操作" min-width="120">
<template #default>
<el-button link type="primary" size="small" @click="viewRelatedDetail(row)">
查看
</el-button> </el-button>
<el-dropdown placement="bottom" style="margin-left: 10px"> <el-button link type="danger" size="small">删除</el-button>
<el-button type="primary" link size="small">
更多 <el-icon><ArrowDown /></el-icon
></el-button>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item @click="handleView(scope.row)">查看</el-dropdown-item>
<el-dropdown-item @click="handleStatus(scope.row)">跟进</el-dropdown-item>
<el-dropdown-item @click="handleEditStatus(scope.row)"
>修改状态</el-dropdown-item
>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
<!-- <el-button text size="primary" @click="handleView(scope.row)"> 查看 </el-button> -->
<!-- <el-button text size="primary" @click="handleStatus(scope.row)"> 跟进 </el-button>
<el-button text type="warning" @click="handleEditStatus(scope.row)">
修改状态
</el-button> -->
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
</CommonDialog>
<!-- 查看详情、更新数据 -->
<CommonDialog :dialogTitle='mode === "viewDetail" ? "查看详情" : "更新数据"' dialogWidth='80%' :openDialog=viewDetailDialogFlag :showAction='false'
:showClose='true' @close='viewDetailDialogFlag = false' @confirm='handleUpdateSubmit'>
<PolicyDetail v-model="policyDetailFormData" @submit="onSubmit" @cancel="showDialog = false"/>
</CommonDialog>
<!-- 分页 -->
<div class="pagination">
<el-pagination
v-model:current-page="pagination.currentPage"
v-model:page-size="pagination.pageSize"
:page-sizes="[10, 20, 50, 100]"
:total="pagination.total"
layout="total, sizes, prev, pager, next, jumper"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
/>
</div>
</el-card>
<!-- 使用查看数据详情组件 -->
<PolicyDetailDialog
:visible="viewDialogVisible"
:detail-data="currentRow"
:policy-follow-status-list="policyFollowStatusList"
title="新单跟进详情"
:expected-commission-list="expectedCommissionList"
:policy-fortune-list="policyFortuneList"
@close="handleDetailClose"
/>
<el-dialog title="修改新单状态" v-model="editStatus" width="500px" append-to-body>
<div style="height: 10vh">
<el-form>
<el-row>
<el-col :span="12">
<el-form-item label="状态">
<el-select v-model="form.status" placeholder="请选择">
<el-option
v-for="item in currentPolicyRow.nextStatusList"
:label="item.itemLabel"
:value="item.itemValue"
/>
</el-select>
</el-form-item>
</el-col>
</el-row>
</el-form>
</div>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="submitForm">确 定</el-button>
<el-button @click="canelEdit">取 消</el-button>
</div>
</template>
</el-dialog>
</div> </div>
</template> </template>
<!-- {
"code": 200,
"msg": "生成预计发佣正在处理....,稍后查看预计发佣列表",
"data": null
} -->
<script setup> <script setup>
import { ref, reactive, onMounted } from 'vue' import { ref, reactive, computed, watch } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus' import { ElMessage, ElMessageBox } from 'element-plus'
import axios from 'axios' import CommonPage from '@/components/commonPage'
import SearchForm from '@/components/SearchForm/SearchForm.vue'
import CommonDialog from '@/components/commonDialog'
import { loadDicts, getDictLabel } from '@/utils/useDict'
import useUserStore from '@/store/modules/user'
import { safeDownload } from '@/utils/safeDownload'
import { import {
getPolicyFollowList, getPolicyFollowList,
getExpectedCommissionList, getExpectedCommissionList,
changePolicyStatus, changePolicyStatus,
policyFollowReport policyFollowReport
} from '@/api/sign/underwritingMain' } from '@/api/sign/underwritingMain'
import { getToken } from '@/utils/auth'
import { listType } from '@/api/system/dict/type'
import date from '@/utils/date'
import PolicyDetailDialog from '@/components/PolicyDetailDialog/index.vue'
import { numberWithCommas } from '@/utils/index.js'
import { getPolicyFortuneList } from '@/api/financial/commission'
const { proxy } = getCurrentInstance()
const router = useRouter()
// 通过dictType=csf_policy_follow_status获取新单状态字典值,获取对象中的dictItemList import PolicyDetail from './policyDetail.vue'
const policyFollowStatusList = ref([])
const commissionStatusList = ref([])
const fortuneStatusList = ref([])
const currencyTypeOptions = ref([])
const currentPolicyRow = ref({}) //当前保单
const editStatus = ref(false) //新单信息状态
const data = reactive({
form: {}
})
const { form } = toRefs(data)
const canelEdit = () => {
editStatus.value = false
form.value = {}
}
// 修改新单状态
const submitForm = () => {
let obj = {
policyBizId: currentPolicyRow.value.policyBizId,
status: form.value.status
}
changePolicyStatus(obj).then(response => {
if (response.code == 200) {
proxy.$modal.msgSuccess('新单状态修改成功')
editStatus.value = false
form.value = {}
fetchTableData()
}
})
}
const getLists = () => {
listType({
typeList: [
'csf_policy_follow_status',
'csf_expected_commission_status',
'csf_fortune_status',
'bx_currency_type'
]
})
.then(res => {
if (res.code === 200 && res.data) {
const statusData = res.data.find(item => item.dictType === 'csf_policy_follow_status')
policyFollowStatusList.value = statusData?.dictItemList || []
const commissionStatusData = res.data.find(
item => item.dictType === 'csf_expected_commission_status'
)
commissionStatusList.value = commissionStatusData?.dictItemList || []
const fortuneStatusData = res.data.find(item => item.dictType === 'csf_fortune_status')
fortuneStatusList.value = fortuneStatusData?.dictItemList || []
// 处理币种字典值
const currencyData = res.data.find(item => item.dictType === 'bx_currency_type')
if (currencyData) {
currencyData?.dictItemList.forEach(item => {
item.value = item.itemValue
item.label = item.itemLabel
})
}
currencyTypeOptions.value = currencyData?.dictItemList || []
} else {
policyFollowStatusList.value = []
commissionStatusList.value = []
fortuneStatusList.value = []
currencyTypeOptions.value = []
}
})
.catch(error => {
console.error('获取状态列表失败:', error)
policyFollowStatusList.value = []
})
}
// 返回数据中状态需要转换为字典值
const convertStatusToDict = (type = 1, status) => {
const arr =
type === 1 ? policyFollowStatusList : type === 2 ? commissionStatusList : fortuneStatusList
const dictItem = arr.value.find(item => item.itemValue == status)
return dictItem?.itemLabel ?? status
}
const uploadUrl = ref(import.meta.env.VITE_APP_BASE_API + '/csf/api/policy_follow/upload/excel')
const policyDetailFormData = ref({})
const userStore = useUserStore()
// 分页相关
const currentPage = ref(1)
const pageSize = ref(10)
const pageTotal = ref(0)
const loading = ref(false)
// 搜索表单数据 // 搜索表单数据
const searchForm = reactive({ const searchFormData = reactive({})
policyBizId: '', // 新单编号(对应接口的policyBizId) const selectedRow = ref(null)
policyNo: '', // 保单编号 // 弹窗相关
customerName: '', // 客户姓名 const editStatusDialogFlag = ref(false)
customerBizId: '', // 客户编号(对应接口的customerBizId) // 修改状态
signDateRange: [], // 签单时间范围 const editStatusFormData = ref({})
status: '', // 新单状态(对应接口的status) const editStatusFormRef = ref(null)
// 高级筛选字段 const editStatusFormConfig = ref([
insurer: '', // 保险公司 {
productCode: '' // 产品代码(对应接口的productCode) type: 'select',
}) prop: 'status',
label: '新单状态',
// 预计来佣列表 dictType: 'csf_policy_follow_status'
const expectedCommissionList = ref([]) }, {
const policyFortuneList = ref([]) type: 'date',
prop: 'policyEffectiveDate',
const getPolicyFortuneLists = async policyNo => { label: '保单生效日',
}, {
type: 'date',
prop: 'policyInsureDate',
label: '保单核保日',
},
])
// 查看关联记录
const viewRelatedDialogFlag = ref(false)
const relateRecordTableData = ref([])
const viewRelatedDetail = (row) => {
ElMessage.info(`查看关联记录详情:${JSON.stringify(row)}`)
}
// 提交修改状态
const handleEditStatusSubmit = async () => {
try { try {
const response = await getPolicyFortuneList({ policyNo }) await editStatusFormRef.value.validate()
if (response.code === 200 && response.data) { const res = await changePolicyStatus({
policyFortuneList.value = response.data.records || [] policyBizId: selectedRow.value.policyBizId,
policyFortuneList.value.forEach(item => { ...editStatusFormData.value
item.fortuneStatusLabel = convertStatusToDict(3, item.status)
}) })
if (res.code === 200) {
ElMessage.success('修改状态成功')
editStatusDialogFlag.value = false
loadTableData()
} else { } else {
policyFortuneList.value = [] ElMessage.error(res.msg || '修改状态失败')
} }
} catch (error) { } catch (error) {
console.error('获取预计来佣列表失败:', error) console.error('修改状态失败', error)
policyFortuneList.value = []
} }
} }
// 表格数据 // 查看详情、更新数据
const tableData = ref([]) const viewDetailDialogFlag = ref(false)
const tableLoading = ref(false)
const selectedRows = ref([])
// 查看详情弹窗相关
const viewDialogVisible = ref(false)
const currentRow = ref({})
// 分页数据
const pagination = reactive({
currentPage: 1, // 当前页码(对应接口的pageNo)
pageSize: 10, // 每页条数(对应接口的pageSize)
total: 0, // 总条数
sortField: '', // 排序字段
sortOrder: '' // 排序方向
})
// 处理查看 const onSubmit = (data) => {
const handleView = async row => { console.log('提交的数据:', data)
console.log('查看详情:', row) // 调用 API 保存
currentRow.value = { ...row } alert('提交成功!')
viewDialogVisible.value = true viewDetailDialogFlag.value = false
fetchExpectedCommissionList(row.policyNo)
getPolicyFortuneLists(row.policyNo)
} }
const handleReport = async row => {
try {
const res = await policyFollowReport(row.policyBizId)
console.log('pdf请求', res)
// 创建 Blob 对象
const blob = new Blob([res], { type: 'application/pdf' })
// 创建对象 URL
const url = window.URL.createObjectURL(blob)
// 在新窗口打开
window.open(url, '_blank')
//下载得方式
// const blob = new Blob([res], { type: 'application/pdf' })
// const url = window.URL.createObjectURL(blob)
// // 创建下载链接 const handleClose = () => {
// const link = document.createElement('a') // 可选:清空数据
// link.href = url
// link.download = '签约单.pdf' // 设置文件名
// link.click()
// // 清理 URL 对象
// window.URL.revokeObjectURL(url)
} catch (error) {
console.error('导出失败:', error)
}
}
// 处理详情弹窗关闭
const handleDetailClose = () => {
viewDialogVisible.value = false
console.log('详情弹窗已关闭')
} }
// 获取新单状态,字典值转化方法
// Excel导入相关 onMounted(async () => {
const selectedFile = ref(null)
const importLoading = ref(false)
// 页面加载时获取数据
onMounted(() => {
fetchTableData()
getLists()
})
// 获取列表数据
const fetchTableData = async () => {
tableLoading.value = true
try { try {
// 构造接口请求参数 await loadDicts(['csf_policy_follow_status'])
const params = {
pageNo: pagination.currentPage, // 注意:如果后端是从0开始的页码,需要减1
pageSize: pagination.pageSize,
sortField: pagination.sortField,
sortOrder: pagination.sortOrder,
status: searchForm.status,
policyBizId: searchForm.policyBizId,
policyNo: searchForm.policyNo,
customerName: searchForm.customerName,
customerBizId: searchForm.customerBizId,
insurer: searchForm.insurer,
productCode: searchForm.productCode,
// 签单时间范围需要根据后端要求的参数名进行调整
// 例如:如果后端需要startSignDate和endSignDate
...(searchForm.signDateRange &&
searchForm.signDateRange.length === 2 && {
startSignDate: date.formatToDate(searchForm.signDateRange[0]) + ' 00:00:00',
endSignDate: date.formatToDate(searchForm.signDateRange[1]) + ' 23:59:59'
})
}
// 调用后台接口
const response = await getPolicyFollowList(params)
// 处理接口响应
if (response.code === 200) {
const result = response.data
// 将接口返回的数据映射到表格
tableData.value = result.records.map(record => ({
...record
}))
// console.log('tableData',tableData.value)
// 更新分页信息
pagination.total = result.total
pagination.currentPage = result.current || pagination.currentPage
pagination.pageSize = result.size || pagination.pageSize
} else {
// 接口返回错误信息
ElMessage.error(`获取数据失败: ${response.data.msg || '未知错误'}`)
tableData.value = []
pagination.total = 0
}
} catch (error) { } catch (error) {
// 捕获网络或其他异常 console.error('字典加载失败', error)
console.error('请求失败:', error)
ElMessage.error('网络异常,请稍后重试')
tableData.value = []
pagination.total = 0
} finally { } finally {
tableLoading.value = false loading.value = false
}
} // 获取预计来佣列表
const fetchExpectedCommissionList = async (policyNo, pageNo = 1, pageSize = 100) => {
try {
// 构造接口请求参数
const params = {
pageNo: pageNo, // 注意:如果后端是从0开始的页码,需要减1
pageSize: pageSize,
policyNo: policyNo
}
// 调用后台接口
const response = await getExpectedCommissionList(params)
// 处理接口响应
if (response.code === 200) {
const result = response.data
// 将接口返回的数据映射到表格
expectedCommissionList.value = result.records.map(record => ({
...record,
commissionStatusLabel: convertStatusToDict(2, record.status)
}))
} else {
// 接口返回错误信息
ElMessage.error(`获取数据失败: ${response.data.msg || '未知错误'}`)
expectedCommissionList.value = []
} }
} catch (error) { })
// 捕获网络或其他异常 // 按钮事件处理
console.error('请求失败:', error) const handleAdd = () => {
ElMessage.error('网络异常,请稍后重试') ElMessage.info('点击新增按钮')
expectedCommissionList.value = [] }
} finally { const handleImport = () => {
ElMessage.info('点击导入按钮')
}
const handleExport = () => {
ElMessage.info('点击导出按钮')
}
const handleReset = () => {
// 重置搜索表单
searchFormRef.value.resetForm()
searchParams.value = {}
console.log('表单已重置')
// 重新加载数据
loadTableData()
}
const handleQuery = async () => {
loadTableData()
}
const visibleDefaultButtons = ref(['reset', 'query'])
// 按钮配置
const operationBtnList = ref([
{
key: 'reset',
direction: 'right',
click: handleReset
},
{
key: 'query',
direction: 'right',
click: handleQuery
} }
} ])
// 增加通过表格排序,排序字段sortField,升降序sortOrder
const handleSortChange = column => {
pagination.sortField = column.prop
pagination.sortOrder = column.order === 'ascending' ? 'ascend' : 'descend'
fetchTableData()
}
// 处理查询
const handleSearch = () => {
pagination.currentPage = 1
fetchTableData()
ElMessage.success('查询成功')
}
// 重置表单
const resetForm = () => {
searchForm.name = ''
searchForm.status = ''
searchForm.policyBizId = ''
searchForm.policyNo = ''
searchForm.customerName = ''
searchForm.customerBizId = ''
searchForm.insurer = ''
searchForm.productCode = ''
searchForm.signDateRange = []
}
// 处理分页大小变化 // 分页事件
const handleSizeChange = val => { const handleSizeChange = (val) => {
pagination.pageSize = val pageSize.value = val
fetchTableData() loadTableData()
} }
const handleCurrentChange = (val) => {
// 处理分页页码变化 currentPage.value = val
const handleCurrentChange = val => { loadTableData()
pagination.currentPage = val
fetchTableData()
}
// 处理表格选择变化
const handleSelectionChange = rows => {
selectedRows.value = rows
}
// 表格行样式
const tableRowClassName = ({ row }) => {
return row.status === 'inactive' ? 'row-inactive' : ''
}
// 处理文件选择
const handleFileChange = file => {
selectedFile.value = file.raw
} }
// 表格数据
const tableData = ref([])
const searchFormRef = ref(null)
const searchParams = ref({})
const searchConfig = ref([
{
type: 'input',
prop: 'policyNo',
label: '保单号'
},
{
type: 'select',
prop: 'status',
label: '新单状态',
dictType: 'csf_policy_follow_status'
},
{
type: 'input',
prop: 'appointmentNo',
label: '预约编号'
}, {
type: 'daterange',
prop: 'signDate',
label: '签单日',
startPlaceholder: '开始时间',
endPlaceholder: '结束时间'
},
{
type: 'date',
prop: 'latestPaymentDate',
label: '最晚缴费日',
placeholder: '请选择'
},
// 处理导入 - 调整为multipart/form-data格式 {
const handleImport = async () => { type: 'select',
if (!selectedFile.value) { prop: 'insuranceCompanyBizIdList',
ElMessage.warning('请先选择文件') label: '保险公司',
return api: '/insurance/base/api/insuranceCompany/page',
keywordField: 'queryContent',
requestParams: { pageNo: 1, pageSize: 20 },
placeholder: '输入保险公司名称搜索',
debounceWait: 500, // 自定义防抖时间
valueKey: 'insuranceCompanyBizId',
labelKey: 'abbreviation',
multiple: true,
transform: (res) => {
console.log(res)
return res?.data.records || []
}
}, {
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, // 自定义防抖时间
valueKey: 'productLaunchBizId',
labelKey: 'productName',
multiple: true,
transform: (res) => {
return res?.data.records || []
}
}, {
type: 'input',
prop: 'paymentTerm',
label: '缴费年期',
inputType: 'decimal',
rules: [
{ pattern: /^\d+$/, message: '只能输入正整数', trigger: 'blur' }
]
} }
])
// 二次确认
ElMessageBox.confirm(`确定要导入"${selectedFile.value.name}"吗?`, '导入确认', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'info'
})
.then(async () => {
importLoading.value = true
const loadTableData = async () => {
loading.value = true
searchParams.value = searchFormRef.value.getFormData() || {}
console.log(searchParams.value)
try { try {
// 创建FormData对象,用于multipart/form-data格式 const params = {
const formData = new FormData() ...searchParams.value,
// 将文件添加到FormData,注意参数名要与后端保持一致 signDateStart: searchParams.value?.signDate[0] || undefined,
formData.append('file', selectedFile.value) signDateEnd: searchParams.value?.signDate[1] || undefined,
signDate: undefined,
// 可以添加其他参数(如果后端需要) pageNo: currentPage.value,
// formData.append('otherParam', 'value') pageSize: pageSize.value
// 调用上传接口
const response = await axios.post(uploadUrl.value, formData, {
headers: {
'Content-Type': 'multipart/form-data', // 指定内容类型,
Authorization: 'Bearer ' + getToken()
}
})
// 处理接口响应
if (response.data.code === 200) {
ElMessage.success('导入成功')
selectedFile.value = null
// 重新获取表格数据
fetchTableData()
} else {
ElMessage.error(`导入失败: ${response.data.msg || '未知错误'}`)
// 如果有错误详情,可以展示
if (response.data.msg) {
console.error('导入错误详情:', response.data.msg)
}
} }
const response = await getPolicyFollowList(params)
tableData.value = response.data.records
pageTotal.value = response.data.total
pageSize.value = response.data.size
} catch (error) { } catch (error) {
console.error('上传失败:', error) ElMessage.error(error.message || '查询失败')
ElMessage.error('文件上传失败,请稍后重试')
} finally { } finally {
importLoading.value = false loading.value = false
} }
}) }
.catch(() => {
// 用户取消导入
ElMessage.info('已取消导入') // 表格操作菜单
}) const dropdownItems = [
} { label: '查看详情', value: 'viewDetail' },
// 下载模板 { label: '更新数据', value: 'updateData' },
const downloadTemplate = () => { { label: '生成签单报告', value: 'generateReport' },
// 下载地址 { label: '设置新单状态', value: 'setNewSingleStatus' },
const templateUrl = { label: '查看关联', value: 'viewRelated' },
'https://yd-ali-oss.oss-cn-shanghai-finance-1-pub.aliyuncs.com/xlsx/2025/10/14/54ce715eabab4f1abd8652ba0fca0c51.xlsx' // { label: '查看记录', value: 'viewRecord' },
// 修改下载文件名 ]
const fileName = '保单导入模板.xlsx' const mode = ref('viewDetail')
// 下载文件
window.open(templateUrl, '_blank', `download=${fileName}-${new Date().getTime()}`) const handleSelect = async (e, row) => {
} selectedRow.value = { ...row }
mode.value = e
import { updateToPolicyLib } from '@/api/sign/underwritingMain' console.log(e, row)
// 处理更新至保单库 switch (e) {
const handleUpdateToPolicyLib = () => { case 'viewDetail':
ElMessageBox.confirm( // ElMessage.info('查看详情')
`确定要更新选中的 ${selectedRows.value.length} 条数据至保单库吗?`, viewDetailDialogFlag.value = true
'更新确认', // viewDetailFormData.value = JSON.parse(JSON.stringify(mockEditData))
{ // 赋值已有数据(深拷贝避免引用问题)
confirmButtonText: '确定', // Object.assign(formData, JSON.parse(JSON.stringify(mockEditData)))
cancelButtonText: '取消', break
type: 'warning' case 'updateData':
ElMessage.info('更新数据')
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('查看记录')
break
default:
break
}
}
const generateReport = async (row) => {
if (!selectedRow.value) {
ElMessage.warning('请选择要生成报告的新单')
return
} }
try {
const response = await policyFollowReport(row.policyBizId)
// 文件名设置为应收款导出_yyyy-MM-dd hh:mm:ss.xlsx,不需要-,用字符串
const fileName = `签单报告_${new Date().toLocaleString().replace(/\//g, '').replace(/:/g, '').replace(/\s/g, '')}.pdf`
await safeDownload(
response,
fileName,
'application/pdf;charset=utf-8'
) )
.then(() => { ElMessage.success('报告生成成功')
// 调用更新至保单库接口 // 可以添加下载报告的逻辑
updateToPolicyLib({ } catch (error) {
policyNoList: selectedRows.value.map(row => row.policyNo) ElMessage.error(error.message || '报告生成失败')
}).then(response => {
if (response.code === 200) {
ElMessage.success('更新成功')
} else {
ElMessage.error(`更新失败: ${response.msg || '未知错误'}`)
}
})
})
.catch(() => {
ElMessage({
type: 'info',
message: '取消更新至保单库'
})
})
}
// 格式化文件大小
const formatFileSize = bytes => {
if (bytes === 0) return '0 B'
const k = 1024
const sizes = ['B', 'KB', 'MB', 'GB']
const i = Math.floor(Math.log(bytes) / Math.log(k))
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]
}
// 新单跟进详情
const handleStatus = row => {
router.push({
path: '/sign/underwritingMain/followUpDetail',
query: {
policyBizId: row.policyBizId,
policyNo: row.policyNo,
type: 'edit',
source: 'policyList',
embed: true,
appointmentNo: row.appointmentNo,
appointmentBizId: row.appointmentBizId
} }
})
}
const handleEditStatus = row => {
currentPolicyRow.value = JSON.parse(JSON.stringify(row))
editStatus.value = true
}
</script>
<style scoped>
.btnCon {
display: flex;
align-items: center;
justify-content: center;
}
.data-management-page {
padding: 20px;
/* max-width: 1600px; */
margin: 0 auto;
}
.page-header {
margin-bottom: 20px;
}
.search-card {
margin-bottom: 20px;
padding: 15px 20px;
}
.search-buttons {
display: flex;
gap: 10px;
}
.import-area {
margin-bottom: 20px;
}
.import-card {
padding: 10px;
background-color: #f6ffed;
border: 1px solid #b7eb8f;
}
.import-content {
display: flex;
flex-direction: column;
gap: 15px;
}
.import-actions {
display: flex;
align-items: center;
gap: 10px;
}
.upload-excel {
display: inline-block;
}
/* 文件信息显示样式 */
.file-info {
margin-top: 15px;
padding: 12px 16px;
background-color: #f5f7fa;
border-radius: 4px;
border: 1px solid #e4e7ed;
display: flex;
align-items: center;
justify-content: space-between;
}
.file-info-content {
display: flex;
align-items: center;
gap: 8px;
}
.file-info-content .el-icon {
color: #409eff;
font-size: 16px;
}
.file-name {
font-weight: 500;
color: #303133;
}
.file-size {
color: #909399;
font-size: 12px;
}
.confirm-import-btn {
margin-left: 12px;
} }
/* 下载模板按钮样式 */ const handleUpdateSubmit = async () => {
.download-template-btn { if (!selectedRow.value) {
color: #409eff; ElMessage.warning('请选择要更新的新单')
font-size: 12px; return
padding: 8px 12px;
}
.download-template-btn:hover {
background-color: #ecf5ff;
}
.table-card {
padding: 15px 20px;
}
.table-actions {
margin-bottom: 15px;
display: flex;
justify-content: space-between;
align-items: center;
}
.pagination {
margin-top: 15px;
text-align: right;
}
/* 禁用状态行样式 */
::v-deep .row-inactive {
background-color: #f5f5f5;
color: #9e9e9e;
}
.form-item {
display: flex;
flex-direction: column;
gap: 6px;
}
.form-label {
font-size: 14px;
color: #4e5969;
font-weight: 500;
line-height: 1;
padding-left: 2px;
}
.search-row {
margin-bottom: 18px;
}
.search-row:last-child {
margin-bottom: 0;
}
.search-buttons {
display: flex;
gap: 10px;
justify-content: flex-start;
align-items: flex-end;
padding-bottom: 2px;
}
.advanced-search {
margin-top: 18px;
border-top: 1px dashed #e5e7eb;
padding-top: 18px;
}
.el-collapse-item__content {
padding-top: 15px !important;
}
/* 转介人详情样式 */
.broker-details {
max-height: 200px;
overflow-y: auto;
}
.broker-item {
padding: 8px 0;
}
.broker-item p {
margin: 4px 0;
font-size: 12px;
line-height: 1.4;
}
.broker-item strong {
color: #606266;
}
.no-broker {
color: #909399;
font-size: 12px;
}
/* 响应式调整 */
@media (max-width: 1200px) {
.import-actions {
flex-wrap: wrap;
}
.download-template-btn {
margin-top: 10px;
}
.file-info {
flex-direction: column;
align-items: flex-start;
gap: 12px;
} }
try {
.confirm-import-btn { const response = await updatePolicyFollow(selectedRow.value)
margin-left: 0; ElMessage.success('更新成功')
align-self: flex-end; // 刷新当前页数据
loadTableData()
// 关闭弹窗
viewDetailDialogFlag.value = false
} catch (error) {
ElMessage.error(error.message || '更新失败')
} }
} }
@media (max-width: 992px) {
.search-card .el-row {
row-gap: 15px;
}
.search-card .el-col {
flex: 0 0 50%;
max-width: 50%;
}
.search-buttons {
justify-content: flex-start;
}
}
@media (max-width: 768px) {
.search-card .el-col {
flex: 0 0 100%;
max-width: 100%;
}
.table-actions {
flex-direction: column;
align-items: flex-start;
gap: 10px;
}
.import-actions {
flex-direction: column;
align-items: flex-start;
}
.download-template-btn {
margin-top: 10px;
}
}
/* 防止表头换行 */ </script>
::v-deep .el-table .el-table__header-wrapper .el-table__cell {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
/* 表头单元格内容不换行 */ <style scoped></style>
::v-deep .el-table .el-table__header-wrapper .el-table__cell .cell { \ No newline at end of file
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
</style>
<!-- components/FormPage.vue -->
<template>
<div class="form-page">
<el-form ref="formRef" :model="localData" label-width="120px" size="default">
<!-- Tabs -->
<el-tabs v-model="activeTab" @tab-click="handleTabClick">
<el-tab-pane label="基础信息" name="basic"></el-tab-pane>
<el-tab-pane label="首期缴费" name="firstPayment"></el-tab-pane>
<el-tab-pane label="介绍人" name="introducer"></el-tab-pane>
<el-tab-pane label="邮寄信息" name="postal"></el-tab-pane>
<el-tab-pane label="关联记录" name="related"></el-tab-pane>
<el-tab-pane label="附件" name="attachment"></el-tab-pane>
</el-tabs>
<!-- 基础信息 Tab 内容 -->
<div v-if="activeTab === 'basic'" class="tab-content">
<!-- 签单信息 -->
<div class="section">
<h3 class="sectionTitle">基础信息</h3>
<SearchForm ref="basicInfoFormRef" :config="basicInfoFormConfig" v-model="basicInfoFormData" />
</div>
<!-- 保单信息 -->
<div class="section">
<h3 class="sectionTitle">保单信息</h3>
<SearchForm ref="policyInfoFormRef" :config="policyInfoFormConfig" v-model="policyInfoFormData" />
</div>
<!-- 基本计划 -->
<div class="section">
<h3 class="sectionTitle">基本计划</h3>
<SearchForm ref="basicPlanFormRef" :config="basicPlanFormConfig" v-model="basicPlanFormData" />
</div>
<!-- 附加计划(可编辑表格) -->
<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>
</div>
</div>
<!-- 首期缴费 Tab 内容 -->
<div v-else-if="activeTab === 'firstPayment'" class="tab-content">
<!-- 签单信息 -->
<div class="section">
<h3 class="sectionTitle">缴费信息</h3>
<SearchForm ref="firstPremiumFormRef" :config="firstPremiumFormConfig"
v-model="firstPremiumFormData" />
<h3 class="sectionTitle">首期对账信息</h3>
<el-table :data="firstPremiumTableData" 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 prop="address" label="汇款日期" width="180" />
<el-table-column prop="address" label="认定金额" width="180" />
<el-table-column prop="address" label="认定币种" width="180" />
</el-table>
</div>
</div>
<!-- 介绍人 Tab 内容 -->
<div v-else-if="activeTab === 'introducer'" class="tab-content">
<!-- 签单信息 -->
<div class="section">
<h3 class="sectionTitle">介绍人信息</h3>
<h5>第一位默认是客户主要负责人,客户资料出现在介绍人(主)账号下,其他介绍人不会看到客户信息</h5>
<el-button type="primary">
<el-icon class="el-icon--right">
<Upload />
</el-icon>新增
</el-button>
<el-table :data="firstPremiumTableData" 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 prop="address" label="分配比例" width="180" />
<el-table-column prop="address" label="备注" width="180" />
<el-table-column fixed="right" label="操作" min-width="120">
<template #default>
<el-button link type="primary" size="small" @click="handleClick">
Detail
</el-button>
<el-button link type="primary" size="small">Edit</el-button>
</template>
</el-table-column>
</el-table>
</div>
</div>
<!-- 邮寄信息 Tab 内容 -->
<div v-else-if="activeTab === 'postal'" class="tab-content">
<div class="section">
<h3 class="sectionTitle">邮寄信息</h3>
<SearchForm ref="postalFormRef" :config="postalFormConfig" v-model="postalFormData" />
</div>
</div>
<!-- 关联记录 Tab 内容 -->
<div v-else-if="activeTab === 'related'" class="tab-content">
<div class="section">
<h3 class="sectionTitle">关联记录</h3>
<el-table :data="relatedTableData" 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 fixed="right" label="操作" min-width="120">
<template #default>
<el-button link type="primary" size="small" @click="handleClick">
Detail
</el-button>
</template>
</el-table-column>
</el-table>
</div>
</div>
<!-- 附件 Tab 内容 -->
<div v-else-if="activeTab === 'attachment'" class="tab-content">
<div class="section">
<h3 class="sectionTitle">附件</h3>
<el-button type="primary">
<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 fixed="right" label="操作" min-width="120">
<template #default>
<el-button link type="primary" size="small" @click="handleClick">
修改
</el-button><el-button link type="danger" size="small" @click="handleClick">
删除
</el-button>
</template>
</el-table-column>
</el-table>
</div>
</div>
<!-- 其他 Tab 占位(你可以按需填充) -->
<div v-else class="tab-placeholder">
<el-empty description="该 Tab 内容待开发" />
</div>
<!-- 底部按钮 -->
<div class="form-footer">
<el-button size="default" @click="handleCancel">取消</el-button>
<el-button type="primary" size="default" @click="handleSubmit">确认</el-button>
</div>
</el-form>
</div>
</template>
<script setup>
import { ref, reactive, watch, nextTick } from 'vue'
import SearchForm from '@/components/SearchForm/SearchForm.vue'
import { Delete, Edit, Search, Share, Upload } from '@element-plus/icons-vue'
const basicInfoFormRef = ref()
const basicInfoFormData = ref({})
const basicInfoFormConfig = ref([
{
type: 'date',
prop: 'signDate',
label: '签单日',
}, {
type: 'select',
prop: 'currency',
label: '签单员',
dictType: 'bx_currency_type',
}, {
type: 'input',
prop: 'policyNo',
label: '签单员执业编号',
}, {
type: 'select',
prop: 'currency',
label: '签单地点',
dictType: 'bx_currency_type'
},
])
// 保单信息
const policyInfoFormRef = ref()
const policyInfoFormData = ref({})
const policyInfoFormConfig = ref([
{
type: 'date',
prop: 'endDate',
label: '保单号',
}, {
type: 'date',
prop: 'effectiveDate',
label: '保单生效日',
}, {
type: 'date',
prop: 'effectiveDate',
label: '保单截止日',
}, {
type: 'date',
prop: 'underwritingDate',
label: '保单核保日',
}, {
type: 'date',
prop: 'underwritingDate',
label: '保单回执日',
}, {
type: 'select',
prop: 'paymentFrequency',
label: '回执状态',
}, {
type: 'select',
prop: 'paymentFrequency',
label: '是否开通直接支付',
}, {
type: 'input',
prop: 'insurer',
label: '保单持有人',
}, {
type: 'input',
prop: 'insurer',
label: '保单受保人',
}, {
type: 'select',
prop: 'insuranceType',
label: '受保人年龄',
}, {
type: 'select',
prop: 'paymentFrequency',
label: '宽限期',
}, {
type: 'select',
prop: 'paymentFrequency',
label: '是否参加递增保障权益',
},
])
// 基本计划
const basicPlanFormRef = ref()
const basicPlanFormData = ref({})
const basicPlanFormConfig = ref([
{
type: 'select',
prop: 'paymentFrequency',
label: '保险公司',
}, {
type: 'select',
prop: 'paymentFrequency',
label: '保险险种',
}, {
type: 'select',
prop: 'paymentFrequency',
label: '产品名称',
}, {
type: 'select',
prop: 'paymentFrequency',
label: '付款频率',
}, {
type: 'select',
prop: 'paymentFrequency',
label: '保障期限',
}, {
type: 'select',
prop: 'paymentFrequency',
label: '保额(重疾险)',
}, {
type: 'select',
prop: 'paymentFrequency',
label: '供款期数',
}, {
type: 'select',
prop: 'paymentFrequency',
label: '保单币种',
}, {
type: 'select',
prop: 'paymentFrequency',
label: '每期保费',
}, {
type: 'select',
prop: 'paymentFrequency',
label: '保单征费',
}, {
type: 'select',
prop: 'paymentFrequency',
label: '是否预缴',
}, {
type: 'select',
prop: 'paymentFrequency',
label: '是否追溯',
}, {
type: 'select',
prop: 'paymentFrequency',
label: '回溯日期',
}, {
type: 'select',
prop: 'paymentFrequency',
label: '首期付款方式',
}, {
type: 'select',
prop: 'paymentFrequency',
label: '续期付款方式',
}, {
type: 'select',
prop: 'paymentFrequency',
label: '红利付款方式',
},
])
// 首期保费
const firstPremiumTableData = ref([])
const firstPremiumFormRef = ref()
const firstPremiumFormData = ref({})
const firstPremiumFormConfig = ref([
{
type: 'input',
prop: 'amount',
label: '首期保费',
inputType: 'decimal',
decimalDigits: 2,
rules: [
{ required: true, message: '请输入金额', trigger: 'blur' },
{ pattern: /^\d+(\.\d{1,2})?$/, message: '最多两位小数', trigger: 'blur' }
]
}, {
type: 'input',
prop: 'amount',
label: '保单征费',
inputType: 'decimal',
decimalDigits: 2,
rules: [
{ required: true, message: '请输入金额', trigger: 'blur' },
{ pattern: /^\d+(\.\d{1,2})?$/, message: '最多两位小数', trigger: 'blur' }
]
}, {
type: 'select',
prop: 'currency',
label: '首期缴费方式',
dictType: 'bx_currency_type'
}, {
type: 'input',
prop: 'amount',
label: '首期已缴保费',
inputType: 'decimal',
decimalDigits: 2,
rules: [
{ required: true, message: '请输入金额', trigger: 'blur' },
{ pattern: /^\d+(\.\d{1,2})?$/, message: '最多两位小数', trigger: 'blur' }
]
}, {
type: 'input',
prop: 'amount',
label: '首期待缴保费',
inputType: 'decimal',
decimalDigits: 2,
rules: [
{ required: true, message: '请输入金额', trigger: 'blur' },
{ pattern: /^\d+(\.\d{1,2})?$/, message: '最多两位小数', trigger: 'blur' }
]
}, {
type: 'input',
prop: 'amount',
label: '首期缴费总额',
inputType: 'decimal',
decimalDigits: 2,
rules: [
{ required: true, message: '请输入金额', trigger: 'blur' },
{ pattern: /^\d+(\.\d{1,2})?$/, message: '最多两位小数', trigger: 'blur' }
]
}, {
type: 'select',
prop: 'statusList',
label: '缴费状态',
multiple: true,
dictType: 'csf_fortune_status'
}, {
type: 'date',
prop: 'commissionDate',
label: '最晚缴费日',
placeholder: '请选择'
}, {
type: 'textarea',
prop: 'policyNo',
label: '首期保费优惠'
},
])
// 邮寄信息
const postalFormRef = ref(null)
const postalFormData = ref({})
const postalFormConfig = ref([
{
type: 'input',
prop: 'postalCode',
label: '寄送方式',
}, {
type: 'input',
prop: 'address',
label: '快递单号',
}, {
type: 'date',
prop: 'address',
label: '我司签收日',
rules: [
{ required: true, message: '请输入我司签收日', trigger: 'blur' }
]
}, {
type: 'date',
prop: 'address',
label: '客户签收日',
rules: [
{ required: true, message: '请输入客户签收日', trigger: 'blur' }
]
},
])
// 关联记录
const relatedTableData = ref([])
const attachmentTableData = ref([])
// ===== Props & Emits =====
const props = defineProps({
modelValue: {
type: Object,
default: () => ({})
}
})
const emit = defineEmits(['update:modelValue', 'submit', 'cancel'])
// ===== 本地响应式数据 =====
const defaultFormData = () => ({
signDate: '',
agentId: '',
location: '',
policyNo: '',
effectiveDate: '',
endDate: '',
underwritingDate: '',
insurer: '',
insuranceType: '',
productName: '',
paymentFrequency: '',
additionalPlans: []
})
// ✅ 使用 ref 而不是 reactive
const localData = ref(defaultFormData())
// ✅ 只在挂载时初始化一次(避免 watch 形成闭环)
onMounted(() => {
if (props.modelValue) {
// 深拷贝 + 合并默认值,防止缺失字段
localData.value = { ...defaultFormData(), ...props.modelValue }
}
})
// ✅ 监听 localData 变化,emit 出去(用于 v-model)
watch(
() => localData.value,
(newVal) => {
emit('update:modelValue', newVal)
},
{ deep: true }
)
// ===== 表单引用 & 验证规则 =====
const formRef = ref()
const activeTab = ref('basic')
// ===== 添加附加险计划方法 =====
const addAdditionalPlan = () => {
localData.additionalPlans.push({
product: '',
frequency: '',
term: '',
periods: '',
currency: '',
premium: '',
fee: ''
})
}
const deleteRow = (index) => {
localData.additionalPlans.splice(index, 1)
}
const handleTabClick = (tab) => {
console.log('切换到 Tab:', tab.name)
}
const handleSubmit = () => {
formRef.value?.validate((valid) => {
if (valid) {
emit('submit', { ...localData })
}
})
}
const handleCancel = () => {
emit('cancel')
}
const handleClick = (tab) => {
activeTab.value = tab.name
}
// 如果外部 modelValue 更新(比如重新加载数据),同步到 localData
watch(
() => props.modelValue,
(newVal) => {
if (newVal) {
Object.assign(localData, defaultFormData(), newVal)
}
},
{ deep: true }
)
</script>
<style scoped>
.form-page {
padding: 10px;
background: #fff;
border-radius: 4px;
padding-top: 0;
}
.tab-placeholder {
padding: 40px 0;
}
.form-footer {
text-align: center;
margin-top: 30px;
}
.sectionTitle {
margin: 0 0 15px 0;
font-size: 16px;
line-height: 1;
position: relative;
padding-left: 16px;
}
.sectionTitle::before {
content: '';
position: absolute;
left: 0;
top: 50%;
transform: translateY(-50%);
width: 6px;
height: 20px;
background-color: #007bff;
border-radius: 5px;
}
.section h5 {
margin: 0 0 15px 0;
font-size: 14px;
line-height: 1;
position: relative;
background-color: rgba(0, 119, 238, 0.05);
color:#383838;
padding: 15px 10px;
border-radius: 4px;
}
</style>
\ No newline at end of file
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
<el-col :sm="12" :lg="12" :xs="24"> <el-col :sm="12" :lg="12" :xs="24">
<div class="headerLeft"> <div class="headerLeft">
<div class="top">欢迎!</div> <div class="top">欢迎!</div>
<div class="bottom">王力群,wangliqun@bytedance.com</div> <div class="bottom">王力群</div>
</div> </div>
</el-col> </el-col>
<el-col :sm="12" :lg="12" :xs="24" class="right"> <el-col :sm="12" :lg="12" :xs="24" class="right">
......
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