Commit 90164b0b by yuzhenWang

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

Test

See merge request !46
parents 48ce4bc3 b1596447
...@@ -412,8 +412,15 @@ export function exportPayRecord(data) { ...@@ -412,8 +412,15 @@ export function exportPayRecord(data) {
responseType: 'blob' responseType: 'blob'
}) })
} }
// 入账检核重新比对
export function compareCommissionEntry(data){
return request({
url: '/csf/api/commission/compare?commissionBizId=' + data,
method: 'get',
})
}
// 应付款报表 // 应付款查询,按照保单期数维度
export function payableReport(data) { export function payableReport(data) {
return request({ return request({
url: '/csf/api/expectedFortune/payable_report', url: '/csf/api/expectedFortune/payable_report',
...@@ -422,11 +429,11 @@ export function payableReport(data) { ...@@ -422,11 +429,11 @@ export function payableReport(data) {
}) })
} }
// 应收款报表 // 应收款报表查询,按照保单期数维度
export function receivableReport(data) { export function receivableReport(data) {
return request({ return request({
url: '/csf/api/CommissionExpected/receivable_report', url: '/csf/api/CommissionExpected/receivable_report',
method: 'post', method: 'post',
data: data, data: data
}) })
} }
\ No newline at end of file
...@@ -27,7 +27,7 @@ export function updateToPolicyLib(data) { ...@@ -27,7 +27,7 @@ export function updateToPolicyLib(data) {
data: data data: data
}) })
} }
// 更新新单跟进 // 更新新单跟进-基本信息
export function updatePolicyfollow(data) { export function updatePolicyfollow(data) {
return request({ return request({
url: '/csf/api/policy_follow/update', url: '/csf/api/policy_follow/update',
...@@ -221,3 +221,38 @@ export function signName(data) { ...@@ -221,3 +221,38 @@ export function signName(data) {
data: data data: data
}) })
} }
// 保存首期缴费
export function saveInitialPayment(data) {
return request({
url: '/csf/api/policy_follow/save_initial_payment',
method: 'post',
data: data
})
}
// 保存邮寄信息
export function saveMailingInfo(data) {
return request({
url: '/csf/api/policy_follow/save_mailing_info',
method: 'post',
data: data
})
}
// 批量保存介绍人
export function batchSaveBrokers(data) {
return request({
url: '/csf/api/policy_follow/batch_save_brokers',
method: 'post',
data: data
})
}
// 通过保险公司、险种查询产品列表及参数
export function getProductList(data) {
return request({
url: '/product/api/relProjectProductLaunch/parameter/page',
method: 'post',
data: data
})
}
...@@ -15,6 +15,7 @@ ...@@ -15,6 +15,7 @@
class="upload-file-uploader" class="upload-file-uploader"
ref="fileUpload" ref="fileUpload"
:drag="drag" :drag="drag"
:name="name"
v-if="!disabled" v-if="!disabled"
> >
<!-- 上传按钮 --> <!-- 上传按钮 -->
...@@ -49,6 +50,11 @@ import Sortable from 'sortablejs' ...@@ -49,6 +50,11 @@ import Sortable from 'sortablejs'
export default { export default {
name: "FileUpload", name: "FileUpload",
props: { props: {
// 上传文件名称
name: {
type: String,
default: "file"
},
// 上传按钮文字 // 上传按钮文字
uploadBtnText: { uploadBtnText: {
type: String, type: String,
......
<template> <template>
<el-form <el-form ref="formRef" :model="localModel" :rules="formRules" label-width="auto" v-bind="$attrs"
ref="formRef" :validate-on-rule-change="false">
: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 <el-form-item :label="item.label" :prop="item.prop" :class="{ 'search-form-item': isSearch }"
:label="item.label" :label-position="item.labelPosition || 'top'">
:prop="item.prop"
:class="{ 'search-form-item': isSearch }"
:label-position="item.labelPosition || 'top'"
>
<!-- Input --> <!-- Input -->
<el-input <el-input v-if="item.type === 'input'" v-model="localModel[item.prop]"
v-if="item.type === 'input'" :placeholder="item.placeholder || `请输入${item.label}`" :clearable="true" :disabled="item.disabled"
v-model="localModel[item.prop]" @input="val => handleNumberInput(val, item)" @change="val => handleModelChange(val, item)" />
:placeholder="item.placeholder || `请输入${item.label}`"
:clearable="true"
:disabled="item.disabled"
@input="val => handleNumberInput(val, item)"
@change="val => handleModelChange(val, item)"
/>
<!-- Select (支持 dictType / api / options) --> <!-- Select (支持 dictType / api / options) -->
<el-select <el-select v-else-if="item.type === 'select'" v-model="localModel[item.prop]" :multiple="!!item.multiple"
v-else-if="item.type === 'select'" :placeholder="item.placeholder || `请选择${item.label}`" :clearable="true" filterable :disabled="item.disabled"
v-model="localModel[item.prop]" :loading="remoteLoading[item.prop] || false" @change="val => handleModelChange(val, item)"
:multiple="!!item.multiple" @focus="() => loadRemoteOptions(item)" @filter-change="keyword => handleFilterChange(keyword, item)">
:placeholder="item.placeholder || `请选择${item.label}`" <el-option v-for="opt in getSelectOptions(item)" :key="opt.value" :label="opt.label" :value="opt.value" />
:clearable="true"
filterable
:disabled="item.disabled"
:loading="remoteLoading[item.prop] || false"
@change="val => handleModelChange(val, item)"
@focus="() => loadRemoteOptions(item)"
@filter-change="keyword => handleFilterChange(keyword, item)"
>
<el-option
v-for="opt in getSelectOptions(item)"
:key="opt.value"
:label="opt.label"
:value="opt.value"
/>
</el-select> </el-select>
<!-- Date --> <!-- Date -->
<el-date-picker <el-date-picker v-else-if="item.type === 'date'" v-model="localModel[item.prop]" type="date"
v-else-if="item.type === 'date'" :placeholder="`选择${item.label}`" :disabled="item.disabled" :value-format="item.valueFormat || 'YYYY-MM-DD'"
v-model="localModel[item.prop]" style="width: 100%" :disabled-date="getDisabledDateFn(item)"
type="date" @change="val => handleModelChange(val, item)" />
:placeholder="`选择${item.label}`"
:disabled="item.disabled"
:value-format="item.valueFormat || 'YYYY-MM-DD'"
style="width: 100%"
:disabled-date="getDisabledDateFn(item)"
@change="val => handleModelChange(val, item)"
/>
<!-- Month --> <!-- Month -->
<el-date-picker <el-date-picker v-else-if="item.type === 'month'" v-model="localModel[item.prop]" type="month"
v-else-if="item.type === 'month'" :placeholder="`选择${item.label}`" :value-format="item.valueFormat || 'YYYY-MM'" style="width: 100%"
v-model="localModel[item.prop]" :disabled="item.disabled" :disabled-date="getDisabledDateFn(item)"
type="month" @change="val => handleModelChange(val, item)" />
:placeholder="`选择${item.label}`"
:value-format="item.valueFormat || 'YYYY-MM'"
style="width: 100%"
:disabled="item.disabled"
:disabled-date="getDisabledDateFn(item)"
@change="val => handleModelChange(val, item)"
/>
<!-- Daterange --> <!-- Daterange -->
<el-date-picker <el-date-picker v-else-if="item.type === 'daterange'" v-model="localModel[item.prop]" type="daterange"
v-else-if="item.type === 'daterange'" range-separator="至" start-placeholder="开始日期" end-placeholder="结束日期"
v-model="localModel[item.prop]" :value-format="item.valueFormat || 'YYYY-MM-DD'" :disabled="item.disabled"
type="daterange" :disabled-date="getDisabledDateFn(item)" style="width: 100%"
range-separator="至" @change="val => handleModelChange(val, item)" />
start-placeholder="开始日期"
end-placeholder="结束日期"
:value-format="item.valueFormat || 'YYYY-MM-DD'"
:disabled="item.disabled"
:disabled-date="getDisabledDateFn(item)"
style="width: 100%"
@change="val => handleModelChange(val, item)"
/>
<!-- Checkbox Group --> <!-- Checkbox Group -->
<el-checkbox-group <el-checkbox-group v-else-if="item.type === 'checkbox-group'" v-model="localModel[item.prop]"
v-else-if="item.type === 'checkbox-group'" :disabled="item.disabled" @change="val => handleModelChange(val, item)">
v-model="localModel[item.prop]"
:disabled="item.disabled"
@change="val => handleModelChange(val, item)"
>
<el-checkbox v-for="opt in getSelectOptions(item)" :key="opt.value" :label="opt.value"> <el-checkbox v-for="opt in getSelectOptions(item)" :key="opt.value" :label="opt.value">
{{ opt.label }} {{ opt.label }}
</el-checkbox> </el-checkbox>
</el-checkbox-group> </el-checkbox-group>
<!-- textarea --> <!-- textarea -->
<el-input <el-input v-else-if="item.type === 'textarea'" v-model="localModel[item.prop]" style="width: 240px" autosize
v-else-if="item.type === 'textarea'" :disabled="item.disabled" type="textarea" placeholder="请输入" :clearable="true"
v-model="localModel[item.prop]" @change="val => handleModelChange(val, item)" />
style="width: 240px"
autosize
:disabled="item.disabled"
type="textarea"
placeholder="请输入"
:clearable="true"
@change="val => handleModelChange(val, item)"
/>
<!-- Upload 回显值得时候数据格式至少是[{url: '必须要传', name: 'name不是必须的根据需要传值'}]--> <!-- Upload 回显值得时候数据格式至少是[{url: '必须要传', name: 'name不是必须的根据需要传值'}]-->
<el-upload <el-upload v-else-if="item.type === 'upload'" v-model:file-list="localModel[item.prop]" :action="item.action"
v-else-if="item.type === 'upload'" :headers="item.headers" :multiple="!!item.multiple" :limit="item.limit || (item.multiple ? 999 : 1)"
v-model:file-list="localModel[item.prop]" :accept="item.accept" :list-type="item.listType || 'text'" :disabled="item.disabled" :auto-upload="true"
:action="item.action" :show-file-list="item.showFileList" :on-exceed="handleExceed"
:headers="item.headers"
:multiple="!!item.multiple"
:limit="item.limit || (item.multiple ? 999 : 1)"
:accept="item.accept"
:list-type="item.listType || 'text'"
:disabled="item.disabled"
:auto-upload="true"
:show-file-list="item.showFileList"
:on-exceed="handleExceed"
:before-upload="file => beforeUpload(file, item)" :before-upload="file => beforeUpload(file, item)"
:on-success="(res, file, fileList) => handleUploadSuccess(res, file, fileList, item)" :on-success="(res, file, fileList) => handleUploadSuccess(res, file, fileList, item)"
:on-error="(err, file, fileList) => handleUploadError(err, file, fileList, item)" :on-error="(err, file, fileList) => handleUploadError(err, file, fileList, item)"
:on-remove="(file, fileList) => handleUploadRemove(file, fileList, item)" :on-remove="(file, fileList) => handleUploadRemove(file, fileList, item)">
> <el-icon class="iconStyle" :size="20" v-if="item.uploadType === 'image'">
<el-icon class="iconStyle" :size="20" v-if="item.uploadType === 'image'" <Upload />
><Upload </el-icon>
/></el-icon> <el-button v-else size="small" type="primary" :link="item.link" :disabled="item.disabled">
<el-button
v-else
size="small"
type="primary"
:link="item.link"
:disabled="item.disabled"
>
{{ '点击上传文件' }} {{ '点击上传文件' }}
</el-button> </el-button>
<template #tip v-if="item.maxSize || item.accept"> <template #tip v-if="item.maxSize || item.accept">
...@@ -216,7 +136,7 @@ function handleUploadSuccess(response, file, fileList, item) { ...@@ -216,7 +136,7 @@ function handleUploadSuccess(response, file, fileList, item) {
handleModelChange([...fileList], item) handleModelChange([...fileList], item)
ElMessage.success(`文件 ${file.name} 上传成功`) ElMessage.success(`文件 ${file.name} 上传成功`)
console.log('上传成功', item) // console.log('上传成功', item)
} }
function handleExceed(files, fileList) { function handleExceed(files, fileList) {
ElMessage.warning('超出文件数量限制') ElMessage.warning('超出文件数量限制')
...@@ -364,10 +284,11 @@ watch( ...@@ -364,10 +284,11 @@ watch(
// ✅ 在这里同步 modelValue(包括 extra 字段) // ✅ 在这里同步 modelValue(包括 extra 字段)
localModel.value = syncModelFromProps(props.modelValue, internalConfig.value) localModel.value = syncModelFromProps(props.modelValue, internalConfig.value)
console.log('子组件监测config变化', localModel.value)
}, },
{ immediate: true } { immediate: true }
) )
console.log('🚀 子组件 props.modelValue 初始值:', props.modelValue) // console.log('🚀 子组件 props.modelValue 初始值:', props.modelValue)
// 监听 modelValue(用于后续外部更新) // 监听 modelValue(用于后续外部更新)
watch( watch(
...@@ -376,6 +297,7 @@ watch( ...@@ -376,6 +297,7 @@ watch(
if (!newVal || !internalConfig.value) return if (!newVal || !internalConfig.value) return
// ✅ 同样使用 sync 函数 // ✅ 同样使用 sync 函数
localModel.value = syncModelFromProps(newVal, internalConfig.value) localModel.value = syncModelFromProps(newVal, internalConfig.value)
console.log('子组件监测 modelValue 变化:', localModel.value)
}, },
{ deep: true } { deep: true }
) )
...@@ -385,8 +307,6 @@ function syncModelFromProps(newModelValue, newConfig) { ...@@ -385,8 +307,6 @@ function syncModelFromProps(newModelValue, newConfig) {
if (!newModelValue || !newConfig) return {} if (!newModelValue || !newConfig) return {}
const synced = {} const synced = {}
console.log('newConfig---------------------', newConfig)
console.log('newModelValue---------------------', newModelValue)
// 1. 同步主字段 // 1. 同步主字段
for (const item of newConfig) { for (const item of newConfig) {
const key = item.prop const key = item.prop
...@@ -466,7 +386,7 @@ function syncModelFromProps(newModelValue, newConfig) { ...@@ -466,7 +386,7 @@ function syncModelFromProps(newModelValue, newConfig) {
synced[key] = newModelValue[key] synced[key] = newModelValue[key]
} }
} }
console.log('🚀 子组件 props.modelValue 同步后:', synced) console.log('🚀 子组件 进行modelvalue处理:', synced)
return synced return synced
} }
function getNestedValue(obj, path) { function getNestedValue(obj, path) {
...@@ -483,42 +403,35 @@ function markDictLoaded(prop) { ...@@ -483,42 +403,35 @@ function markDictLoaded(prop) {
// 2. 用户操作导致 localModel 变化时,emit(防抖可选) // 2. 用户操作导致 localModel 变化时,emit(防抖可选)
function handleModelChange(value, item) { function handleModelChange(value, item) {
console.group('🔄 handleModelChange') console.group('用户操作导致 localModel 变化时,emit(防抖可选)')
console.log('输入 value:', value)
console.log('item:', item)
console.log('当前 localModel:', localModel.value)
const newModel = { ...localModel.value, [item.prop]: 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)
console.log('可用 options:', options) // console.log('可用 options:', options)
const opt = options.find(o => o.value === value) const opt = options.find(o => o.value === value)
console.log('匹配的 opt:', opt) // console.log('匹配的 opt:', opt)
if (opt?.raw) { if (opt?.raw) {
for (const [targetProp, sourceKey] of Object.entries(item.onChangeExtraFields)) { for (const [targetProp, sourceKey] of Object.entries(item.onChangeExtraFields)) {
const extraValue = opt.raw[sourceKey] const extraValue = opt.raw[sourceKey]
newModel[targetProp] = extraValue newModel[targetProp] = extraValue
console.log(`✅ 设置 ${targetProp} =`, extraValue) // console.log(`✅ 设置 ${targetProp} =`, extraValue)
} }
} }
} }
console.log('🆕 newModel:', newModel)
console.log('📦 props.modelValue:', props.modelValue)
console.log('isEqualShallow?', isEqualShallow(props.modelValue, newModel))
localModel.value = newModel localModel.value = newModel
console.log('子组件用户操作后,modelvalue值==', newModel)
nextTick(() => { nextTick(() => {
if (!isEqualShallow(props.modelValue, newModel)) { if (!isEqualShallow(props.modelValue, newModel)) {
console.log('📤 emit update:modelValue:', newModel) console.log('如果新旧值不一样,反馈给父组件', newModel)
emit('update:modelValue', newModel) emit('update:modelValue', newModel)
} else { } else {
console.log('🚫 跳过 emit:认为相等') console.log('🚫 跳过 emit:认为相等')
} }
}) })
if (item.type === 'select') { if (item.type === 'select') {
console.log('如果是select类型,反馈给父组件', item.prop, value, item)
emit('selectChange', item.prop, value, item) emit('selectChange', item.prop, value, item)
} else if (item.type == 'upload') { } else if (item.type == 'upload') {
// 传给父组件最新的上传值newModel // 传给父组件最新的上传值newModel
...@@ -631,11 +544,11 @@ function getSelectOptions(item) { ...@@ -631,11 +544,11 @@ function getSelectOptions(item) {
return [] return []
} }
async function loadRemoteOptionsForInit(item) { async function loadRemoteOptionsForInit(item) {
const { prop, api, requestParams = {} } = item const { prop, api, requestParams } = item
try { try {
// 构造请求体:只传 requestParams,不传 keyword // 构造请求体:只传 requestParams,不传 keyword
const payload = { const payload = {
...(requestParams || {}) ...(typeof requestParams === 'function' ? requestParams() : requestParams || {})
} }
const res = await request({ const res = await request({
...@@ -665,7 +578,7 @@ async function loadRemoteOptionsForInit(item) { ...@@ -665,7 +578,7 @@ async function loadRemoteOptionsForInit(item) {
} }
// ==================== 加载远程 API 选项(首次 focus 时加载,无搜索词) ==================== // ==================== 加载远程 API 选项(首次 focus 时加载,无搜索词) ====================
async function loadRemoteOptions(item) { async function loadRemoteOptions(item) {
const { prop, api, requestParams = {}, keywordField, debounceWait, ...rest } = item const { prop, api, requestParams, keywordField, debounceWait, ...rest } = item
if (!api || remoteOptions.value[prop]?.length > 0) return if (!api || remoteOptions.value[prop]?.length > 0) return
try { try {
...@@ -673,7 +586,7 @@ async function loadRemoteOptions(item) { ...@@ -673,7 +586,7 @@ async function loadRemoteOptions(item) {
// 构造请求体:合并 requestParams + 分页(默认第一页) // 构造请求体:合并 requestParams + 分页(默认第一页)
const payload = { const payload = {
...(requestParams || {}) ...(typeof requestParams === 'function' ? requestParams() : requestParams || {})
// 注意:此时无 keyword,所以不加 keywordField // 注意:此时无 keyword,所以不加 keywordField
} }
...@@ -708,7 +621,7 @@ async function loadRemoteOptions(item) { ...@@ -708,7 +621,7 @@ async function loadRemoteOptions(item) {
// ==================== 远程搜索(带关键词,防抖) ==================== // ==================== 远程搜索(带关键词,防抖) ====================
let searchTimeout = null let searchTimeout = null
function handleFilterChange(keyword, item) { function handleFilterChange(keyword, item) {
const { prop, api, requestParams = {}, keywordField = 'keyword', debounceWait = 300 } = item const { prop, api, requestParams, keywordField = 'keyword', debounceWait = 300 } = item
if (!api) return if (!api) return
clearTimeout(searchTimeout) clearTimeout(searchTimeout)
...@@ -718,7 +631,7 @@ function handleFilterChange(keyword, item) { ...@@ -718,7 +631,7 @@ function handleFilterChange(keyword, item) {
// 构造请求体:requestParams + 分页 + 动态关键词字段 // 构造请求体:requestParams + 分页 + 动态关键词字段
const payload = { const payload = {
...(requestParams || {}), ...(typeof requestParams === 'function' ? requestParams() : requestParams || {}),
[keywordField]: keyword // ← 动态字段名,如 name / companyName [keywordField]: keyword // ← 动态字段名,如 name / companyName
} }
...@@ -823,9 +736,11 @@ defineExpose({ ...@@ -823,9 +736,11 @@ defineExpose({
.formBox { .formBox {
box-sizing: border-box; box-sizing: border-box;
} }
.search-form-item { .search-form-item {
margin-bottom: 20px; margin-bottom: 20px;
} }
.iconStyle { .iconStyle {
color: #409eff; color: #409eff;
} }
......
<template>
<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>
<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"
@click="batchSave"
:disabled="disabled"
>
批量保存
</el-button> -->
</el-form>
</div>
</template>
<script setup>
import { ref, watch, nextTick } from 'vue'
import SelectField from '@/components/csf-form/fields/SelectField.vue'
import InputField from '@/components/csf-form/fields/InputField.vue'
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
}
})
const emit = defineEmits(['update:modelValue', 'batch-save'])
const formRef = ref()
const internalData = ref([])
// 远程加载状态 & 缓存
const remoteLoading = ref({})
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 }
)
// 同步 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 }
)
// 字段组件映射
function getFieldComponent(type) {
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
}
}
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
}
// 处理 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
}))
}
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']: ''
}
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: {}
})
}
function removeRow(index) {
internalData.value.splice(index, 1)
}
function batchSave() {
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;
}
</style>
\ No newline at end of file
<template>
<el-form
ref="formRef"
:model="localModel"
:rules="formRules"
label-width="auto"
v-bind="$attrs"
:validate-on-rule-change="false"
>
<el-row :gutter="20">
<el-col
v-for="item in visibleConfig"
:key="item.prop"
:span="item.span || 6"
:class="{ 'search-form-item': isSearch }"
>
<el-form-item
:label="item.label"
:prop="item.prop"
:label-position="item.labelPosition || 'top'"
>
<component
:is="getFieldComponent(item.type)"
v-bind="getFieldProps(item)"
@update:model-value="val => handleModelChange(val, item)"
@focus="() => loadRemoteOptions(item)"
@filter-change="keyword => handleFilterChange(keyword, item)"
/>
</el-form-item>
</el-col>
</el-row>
</el-form>
</template>
<script setup>
import { onMounted } from 'vue'
import { useSearchFormLogic } from '@/composables/useSearchFormLogic'
// 字段组件映射
import InputField from './fields/InputField.vue'
import SelectField from './fields/SelectField.vue'
import DateField from './fields/DateField.vue'
import MonthField from './fields/MonthField.vue'
import DateRangeField from './fields/DateRangeField.vue'
import CheckboxGroupField from './fields/CheckboxGroupField.vue'
import TextareaField from './fields/TextareaField.vue'
import UploadField from './fields/UploadField.vue'
const fieldMap = {
input: InputField,
select: SelectField,
date: DateField,
month: MonthField,
daterange: DateRangeField,
'checkbox-group': CheckboxGroupField,
textarea: TextareaField,
upload: UploadField
}
const props = defineProps({
modelValue: { type: Object, default: () => ({}) },
config: { type: Array, default: () => [] },
isSearch: { type: Boolean, default: false }
})
const emit = defineEmits([
'update:modelValue',
'selectChange',
'uploadSuccess'
])
const {
formRef,
localModel,
visibleConfig,
formRules,
handleModelChange,
getSelectOptions,
getDisabledDateFn,
loadRemoteOptions,
handleFilterChange,
remoteLoading,
init,
getFormData,
validate,
resetForm
} = useSearchFormLogic(props, emit)
// 暴露方法给父组件
defineExpose({ getFormData, validate, resetForm })
// 初始化
onMounted(() => {
init()
})
// 获取字段组件
function getFieldComponent(type) {
return fieldMap[type] || 'span'
}
// 构建字段 props
function getFieldProps(item) {
const base = {
modelValue: localModel.value[item.prop],
disabled: item.disabled,
placeholder: item.placeholder
}
switch (item.type) {
case 'input':
return {
...base,
inputType: item.inputType,
decimalDigits: item.decimalDigits
}
case 'select':
return {
...base,
multiple: !!item.multiple,
clearable: true,
filterable: true,
loading: remoteLoading.value[item.prop] || false,
options: getSelectOptions(item),
placeholder: item.placeholder || `请选择${item.label}`
}
case 'date':
return {
...base,
valueFormat: item.valueFormat || 'YYYY-MM-DD',
disabledDate: getDisabledDateFn(item),
placeholder: `选择${item.label}`
}
case 'month':
return {
...base,
valueFormat: item.valueFormat || 'YYYY-MM',
disabledDate: getDisabledDateFn(item),
placeholder: `选择${item.label}`
}
case 'daterange':
return {
...base,
valueFormat: item.valueFormat || 'YYYY-MM-DD',
disabledDate: getDisabledDateFn(item)
}
case 'checkbox-group':
return {
...base,
options: getSelectOptions(item)
}
case 'textarea':
return {
...base,
clearable: true
}
case 'upload':
return {
...base,
action: item.action,
headers: item.headers,
multiple: !!item.multiple,
limit: item.limit || (item.multiple ? 999 : 1),
accept: item.accept,
listType: item.listType || 'text',
showFileList: item.showFileList,
uploadType: item.uploadType,
link: item.link,
maxSize: item.maxSize
}
default:
return base
}
}
</script>
<style scoped>
.search-form-item {
margin-bottom: 20px;
}
</style>
\ No newline at end of file
<template>
<el-checkbox-group :model-value="modelValue" :disabled="disabled" @update:model-value="handleChange">
<el-checkbox v-for="opt in options" :key="opt.value" :label="opt.value">
{{ opt.label }}
</el-checkbox>
</el-checkbox-group>
</template>
<script setup>
const props = defineProps({
modelValue: String,
disabled: Boolean,
options: { type: Array, default: () => [] }
})
const emit = defineEmits(['update:modelValue'])
function handleChange(value) {
emit('update:modelValue', value)
}
</script>
\ No newline at end of file
<!-- src/components/search-form/fields/DateField.vue -->
<template>
<el-date-picker
:model-value="modelValue"
type="date"
:placeholder="placeholder"
:disabled="disabled"
:value-format="valueFormat"
:disabled-date="disabledDate"
style="width: 100%"
@update:model-value="handleChange"
/>
</template>
<script setup>
const props = defineProps({
modelValue: String, // 注意:不能是 required,因为可能为 null
placeholder: String,
disabled: Boolean,
valueFormat: String,
disabledDate: Function
})
const emit = defineEmits(['update:modelValue'])
function handleChange(value) {
emit('update:modelValue', value)
}
</script>
\ No newline at end of file
<template>
<el-date-picker
:model-value="modelValue"
type="daterange"
range-separator="至"
start-placeholder="开始日期"
end-placeholder="结束日期"
:value-format="valueFormat"
:disabled="disabled"
:disabled-date="disabledDate"
style="width: 100%"
@update:model-value="handleChange"
/>
</template>
<script setup>
const props = defineProps({
modelValue: Array,
valueFormat: String,
disabled: Boolean,
disabledDate: Function
})
const emit = defineEmits(['update:modelValue'])
function handleChange(value) {
emit('update:modelValue', value)
}
</script>
\ No newline at end of file
<template>
<el-input
:model-value="innerValue"
:placeholder="placeholder"
:clearable="clearable"
:disabled="disabled"
@input="handleInput"
@update:model-value="handleChange"
/>
</template>
<script setup>
import { ref, watch } from 'vue'
const props = defineProps({
modelValue: String,
placeholder: String,
clearable: Boolean,
disabled: Boolean,
inputType: { type: String, default: 'text' },
decimalDigits: { type: Number, default: 2 }
})
const emit = defineEmits(['update:modelValue'])
const innerValue = ref(props.modelValue)
watch(() => props.modelValue, val => {
innerValue.value = val
})
function handleInput(value) {
let result = String(value ?? '').trim()
if (props.inputType === 'integer') {
result = result.replace(/[^\d]/g, '')
} else if (props.inputType === 'decimal') {
result = result.replace(/[^\d.]/g, '')
if (result.startsWith('.')) result = '0.' + result.slice(1)
const parts = result.split('.')
if (parts.length > 2) result = parts[0] + '.' + parts.slice(1).join('')
if (result.includes('.')) {
const [int, dec] = result.split('.')
if (dec.length > props.decimalDigits) {
result = int + '.' + dec.slice(0, props.decimalDigits)
}
}
}
innerValue.value = result
emit('update:modelValue', result)
}
function handleChange(value) {
emit('update:modelValue', value)
}
</script>
\ No newline at end of file
<template>
<el-date-picker
:model-value="modelValue"
type="month"
:placeholder="placeholder"
:disabled="disabled"
:value-format="valueFormat"
:disabled-date="disabledDate"
style="width: 100%"
@update:model-value="handleChange"
/>
</template>
<script setup>
const props = defineProps({
modelValue: String,
placeholder: String,
disabled: Boolean,
valueFormat: String,
disabledDate: Function
})
const emit = defineEmits(['update:modelValue'])
function handleChange(value) {
emit('update:modelValue', value)
}
</script>
\ No newline at end of file
<template>
<el-select
:model-value="modelValue"
:multiple="multiple"
:placeholder="placeholder"
:clearable="clearable"
:filterable="filterable"
:disabled="disabled"
:loading="loading"
@update:model-value="handleChange"
@change="handleChangeWithOption"
@focus="$emit('focus')"
@filter-change="$emit('filter-change', $event)"
>
<el-option
v-for="opt in options"
:key="opt.value"
:label="opt.label"
:value="opt.value"
/>
</el-select>
</template>
<script setup>
const props = defineProps({
modelValue: [String, Number, Array, null],
multiple: Boolean,
placeholder: String,
clearable: Boolean,
filterable: Boolean,
disabled: Boolean,
loading: Boolean,
options: {
type: Array,
default: () => []
}
})
const emit = defineEmits([
'update:modelValue',
'focus',
'filter-change',
'option-change' // 新增:emit 完整 option 对象
])
function handleChange(value) {
emit('update:modelValue', value)
}
function handleChangeWithOption(value) {
// 单选:value 是 primitive;多选:value 是 array
if (props.multiple) {
// 多选暂不支持 extra fields(可按需扩展)
return
}
const option = props.options.find(opt => opt.value === value)
emit('option-change', option)
}
</script>
\ No newline at end of file
<template>
<el-input
:model-value="modelValue"
type="textarea"
:autosize="{ minRows: 2 }"
:disabled="disabled"
:clearable="clearable"
placeholder="请输入"
style="width: 240px"
@update:model-value="handleChange"
/>
</template>
<script setup>
const props = defineProps({
modelValue: String,
disabled: Boolean,
clearable: Boolean
})
const emit = defineEmits(['update:modelValue'])
function handleChange(value) {
emit('update:modelValue', value)
}
</script>
\ No newline at end of file
<!-- src/components/search-form/fields/UploadField.vue -->
<template>
<el-upload
:file-list="innerFileList"
:action="action"
:headers="headers"
:multiple="multiple"
:limit="limit"
:accept="accept"
:list-type="listType"
:disabled="disabled"
:auto-upload="true"
:show-file-list="showFileList"
:on-exceed="handleExceed"
:before-upload="beforeUpload"
:on-success="handleSuccess"
:on-error="handleError"
:on-remove="handleRemove"
>
<el-icon v-if="uploadType === 'image'" class="iconStyle" :size="20">
<Upload />
</el-icon>
<el-button v-else size="small" type="primary" :link="link" :disabled="disabled">
点击上传文件
</el-button>
<template #tip v-if="maxSize || accept">
<div class="el-upload__tip">
<span v-if="maxSize">大小不超过 {{ formatFileSize(maxSize) }}</span>
<span v-if="accept">支持格式:{{ accept }}</span>
</div>
</template>
</el-upload>
</template>
<script setup>
import { ref, watch } from 'vue'
import { ElMessage } from 'element-plus'
import { Upload } from '@element-plus/icons-vue'
import { deepEqual } from '@/utils/csf-deepEqual'
const props = defineProps({
modelValue: {
type: Array,
default: () => []
},
action: String,
headers: Object,
multiple: Boolean,
limit: Number,
accept: String,
listType: { type: String, default: 'text' },
disabled: Boolean,
showFileList: Boolean,
uploadType: String,
link: Boolean,
maxSize: Number
})
const emit = defineEmits(['update:modelValue'])
// 内部状态:不能直接改 props.modelValue
const innerFileList = ref([...props.modelValue])
// 监听外部 modelValue 变化(如重置表单)
watch(
() => props.modelValue,
(newVal, oldVal) => {
if (!deepEqual(newVal, oldVal)) {
innerFileList.value = [...(newVal || [])]
}
},
{ deep: true }
)
// 格式化文件大小
function formatFileSize(bytes) {
if (bytes === 0) return '0 Bytes'
const k = 1024
const sizes = ['Bytes', 'KB', 'MB', 'GB']
const i = Math.floor(Math.log(bytes) / Math.log(k))
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]
}
// 文件上传前校验
function beforeUpload(file) {
if (props.maxSize && file.size > props.maxSize) {
ElMessage.error(`文件 ${file.name} 超出大小限制(最大 ${formatFileSize(props.maxSize)})`)
return false
}
if (props.accept) {
const allowed = props.accept.split(',').map(ext => ext.trim().toLowerCase())
const fileExt = '.' + file.name.split('.').pop().toLowerCase()
if (!allowed.includes(fileExt)) {
ElMessage.error(`文件类型不支持,仅支持:${props.accept}`)
return false
}
}
return true
}
// 上传成功
function handleSuccess(response, file) {
const data = response.data || response
const url = data.url || data.fileUrl || data.path
const name = data.name || data.fileName || file.name
if (!url) {
ElMessage.error('上传成功但未返回文件地址')
return
}
// 找到当前文件并更新 url 和 name
const target = innerFileList.value.find(f => f.uid === file.uid)
if (target) {
target.url = url
target.name = name
}
// 同步回父组件
emit('update:modelValue', [...innerFileList.value])
ElMessage.success(`文件 ${file.name} 上传成功`)
}
// 超出数量限制
function handleExceed() {
ElMessage.warning('超出文件数量限制')
}
// 上传失败
function handleError(err, file) {
ElMessage.error(`文件 ${file.name} 上传失败`)
console.error('Upload error:', err)
}
// 删除文件
function handleRemove(file) {
emit('update:modelValue', [...innerFileList.value])
}
// 暴露内部 fileList(可选,用于高级控制)
defineExpose({
fileList: innerFileList
})
</script>
<style scoped>
.iconStyle {
color: #409eff;
}
</style>
\ No newline at end of file
// src/composables/useSearchFormLogic.js
import { ref, watch, computed, nextTick } from 'vue'
import { ElMessage } from 'element-plus'
import useDictStore from '@/store/modules/dict'
import { getDicts } from '@/api/system/dict/data'
import request from '@/utils/request'
import dayjs from 'dayjs'
// ==================== 工具函数 ====================
function deepCloneConfig(obj) {
if (obj === null || typeof obj !== 'object') return obj
if (Array.isArray(obj)) return obj.map(deepCloneConfig)
const cloned = {}
for (const key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
const val = obj[key]
cloned[key] = typeof val === 'function' ? val : deepCloneConfig(val)
}
}
return cloned
}
function parseToDate(str) {
if (!str) return null
if (str === 'today') return dayjs().startOf('day')
if (typeof str === 'string') {
const d = dayjs(str)
return d.isValid() ? d.startOf('day') : null
}
if (str instanceof Date) return dayjs(str).startOf('day')
return null
}
function getNestedValue(obj, path) {
return path.split('.').reduce((current, key) => current?.[key], obj)
}
function isEqualShallow(a, b) {
const keysA = Object.keys(a)
const keysB = Object.keys(b)
if (keysA.length !== keysB.length) return false
for (let key of keysA) {
if (a[key] !== b[key]) return false
}
return true
}
// ==================== 主逻辑 ====================
export function useSearchFormLogic(props, emit) {
// === Refs ===
const formRef = ref(null)
const localModel = ref({ ...props.modelValue })
const dictLoaded = ref(new Set())
const internalConfig = ref([])
const remoteOptions = ref({})
const remoteLoading = ref({})
// === Computed ===
const visibleConfig = computed(() => {
return internalConfig.value.filter(item => {
if (typeof item.visible === 'function') return item.visible(localModel.value)
return true
})
})
const formRules = computed(() => {
const rules = {}
visibleConfig.value.forEach(item => {
if (item.rules) rules[item.prop] = item.rules
})
return rules
})
// === 模型同步 ===
function syncModelFromProps(newModelValue, newConfig) {
if (!newModelValue || !newConfig) return {}
const synced = {}
// 1. 主字段
for (const item of newConfig) {
const key = item.prop
if (newModelValue.hasOwnProperty(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 字段(通过 options 反查)
for (const item of newConfig) {
const extraMap = item.onChangeExtraFields
if (!extraMap || typeof extraMap !== 'object') continue
const prop = item.prop
const idValue = newModelValue[prop]
let sourceObj = null
if (idValue && typeof idValue === 'object') {
sourceObj = idValue
} else if (Array.isArray(item.options) && idValue !== undefined && idValue !== null) {
const valueKey = item.valueKey || 'value'
sourceObj = item.options.find(opt => opt[valueKey] === idValue)
}
if (sourceObj && typeof sourceObj === 'object') {
for (const [targetKey, subPath] of Object.entries(extraMap)) {
const val = getNestedValue(sourceObj, subPath)
if (val !== undefined) synced[targetKey] = val
}
}
}
// 3. 保留 localModel 中的 extra(当 sourceField 未更新)
for (const item of newConfig) {
const sourceField = item.prop
const extraMap = item.onChangeExtraFields
if (!extraMap || typeof extraMap !== 'object') continue
if (newModelValue[sourceField] === undefined) {
for (const [targetKey, subPath] of Object.entries(extraMap)) {
if (localModel.value.hasOwnProperty(targetKey)) {
synced[targetKey] = localModel.value[targetKey]
}
}
}
}
// 4. 保留其他字段
for (const key in newModelValue) {
if (synced.hasOwnProperty(key)) continue
const isExtraTarget = newConfig.some(
item => item.onChangeExtraFields && item.onChangeExtraFields.hasOwnProperty(key)
)
if (isExtraTarget || !newConfig.some(item => item.prop === key)) {
synced[key] = newModelValue[key]
}
}
return synced
}
// === Watchers ===
watch(
() => props.config,
newConfig => {
if (!newConfig || newConfig.length === 0) return
internalConfig.value = deepCloneConfig(newConfig)
localModel.value = syncModelFromProps(props.modelValue, internalConfig.value)
},
{ immediate: true }
)
watch(
() => props.modelValue,
newVal => {
if (!newVal || !internalConfig.value) return
localModel.value = syncModelFromProps(newVal, internalConfig.value)
},
{ deep: true }
)
// === 核心方法 ===
function handleModelChange(value, item) {
const newModel = { ...localModel.value, [item.prop]: value }
if (item?.type === 'select' && item.onChangeExtraFields) {
const options = getSelectOptions(item)
const opt = options.find(o => o.value === value)
if (opt?.raw) {
for (const [targetProp, sourceKey] of Object.entries(item.onChangeExtraFields)) {
newModel[targetProp] = opt.raw[sourceKey]
}
}
}
localModel.value = newModel
nextTick(() => {
if (!isEqualShallow(props.modelValue, newModel)) {
emit('update:modelValue', newModel)
}
})
if (item.type === 'select') {
emit('selectChange', item.prop, value, item)
} else if (item.type === 'upload') {
emit('uploadSuccess', item.prop, newModel)
}
}
function getSelectOptions(item) {
const key = item.prop
if (item.dictType || item.api) {
return (remoteOptions.value[key] || []).map(opt => ({
value: opt.value,
label: opt.label,
raw: opt.raw
}))
} else if (item.options) {
return item.options.map(opt => ({
value: opt.value,
label: opt.label,
raw: opt.raw
}))
}
return []
}
function getDisabledDateFn(item) {
const { minDate, maxDate } = item
if (minDate == null && maxDate == null) return () => false
return date => {
const currentDate = dayjs(date).startOf('day')
let minD = null, maxD = null
if (minDate != null) {
const val = typeof minDate === 'function' ? minDate(localModel.value) : minDate
minD = parseToDate(val)
}
if (maxDate != null) {
const val = typeof maxDate === 'function' ? maxDate(localModel.value) : maxDate
maxD = parseToDate(val)
}
if (minD && currentDate.isBefore(minD)) return true
if (maxD && currentDate.isAfter(maxD)) return true
return false
}
}
// === 远程加载 ===
async function loadDictOptions(dictType) {
const dictStore = useDictStore()
let options = dictStore.getDict(dictType)
if (options?.length) return options
try {
const resp = await getDicts(dictType)
options = resp.data.map(p => ({ label: p.itemLabel, value: p.itemValue, raw: p }))
dictStore.setDict(dictType, options)
return options
} catch (err) {
console.error(`加载字典 ${dictType} 失败`, err)
ElMessage.error(`字典 ${dictType} 加载失败`)
return []
}
}
function markDictLoaded(prop) {
dictLoaded.value.add(prop)
if (props.modelValue?.[prop] !== undefined) {
localModel.value[prop] = props.modelValue[prop]
}
}
async function loadRemoteOptionsForInit(item) {
const { prop, api, requestParams = {} } = item
try {
const res = await request({ url: api, method: 'post', data: requestParams })
const list = typeof item.transform === 'function'
? item.transform(res)
: res.data?.records || res.data || []
remoteOptions.value[prop] = list.map(i => ({
value: String(i[item.valueKey || 'value']),
label: i[item.labelKey || 'label'],
raw: i
}))
markDictLoaded(prop)
} catch (err) {
ElMessage.error(`预加载 ${item.label} 失败`)
remoteOptions.value[prop] = []
}
}
async function loadRemoteOptions(item) {
const { prop, api } = item
if (!api || remoteOptions.value[prop]?.length > 0) return
try {
remoteLoading.value[prop] = true
const res = await request({ url: api, method: 'post', data: item.requestParams || {} })
const list = typeof item.transform === 'function'
? item.transform(res)
: res.data?.records || res.data || []
remoteOptions.value[prop] = list.map(i => ({
value: String(i[item.valueKey || 'value']),
label: i[item.labelKey || 'label'],
raw: i
}))
markDictLoaded(prop)
} catch (err) {
ElMessage.error(`加载 ${item.label} 失败`)
remoteOptions.value[prop] = []
} finally {
remoteLoading.value[prop] = false
}
}
let searchTimeout = null
function handleFilterChange(keyword, item) {
const { prop, api, requestParams = {}, keywordField = 'keyword', debounceWait = 300 } = item
if (!api) return
clearTimeout(searchTimeout)
searchTimeout = setTimeout(async () => {
try {
remoteLoading.value[prop] = true
const res = await request({
url: api,
method: 'post',
data: { ...(requestParams || {}), [keywordField]: keyword }
})
const list = typeof item.transform === 'function'
? item.transform(res)
: res.data?.records || res.data || []
remoteOptions.value[prop] = list.map(i => ({
value: i[item.valueKey || 'value'],
label: i[item.labelKey || 'label'],
raw: i
}))
} catch (err) {
ElMessage.error(`搜索 ${item.label} 失败`)
} finally {
remoteLoading.value[prop] = false
}
}, debounceWait)
}
// === 初始化 ===
async function init() {
internalConfig.value = deepCloneConfig(props.config)
const dictPromises = []
const apiPromises = []
for (const item of internalConfig.value) {
const key = item.prop
if (localModel.value[key] == null) {
if (item.multiple || ['checkbox-group', 'daterange'].includes(item.type)) {
localModel.value[key] = item.defaultValue ?? []
} else {
localModel.value[key] = item.defaultValue ?? ''
}
}
if (item.type === 'select') {
if (item.dictType) {
dictPromises.push(loadDictOptions(item.dictType).then(opts => {
remoteOptions.value[key] = opts
markDictLoaded(key)
}))
} else if (item.api) {
apiPromises.push(loadRemoteOptionsForInit(item))
} else if (item.options) {
remoteOptions.value[key] = [...item.options]
markDictLoaded(key)
}
}
}
await Promise.allSettled([...dictPromises, ...apiPromises])
}
// === 暴露给 UI 和父组件 ===
return {
// refs
formRef,
localModel,
// computed
visibleConfig,
formRules,
// methods for FieldRenderer
handleModelChange,
getSelectOptions,
getDisabledDateFn,
loadRemoteOptions,
handleFilterChange,
remoteLoading,
// exposed methods for parent
getFormData: () => ({ ...localModel.value }),
async validate() {
return new Promise((resolve, reject) => {
if (!formRef.value) return resolve(localModel.value)
formRef.value.validate(valid => {
valid ? resolve({ ...localModel.value }) : reject(new Error('Validation failed'))
})
})
},
resetForm() {
const resetData = {}
internalConfig.value.forEach(item => {
const key = item.prop
if (['checkbox-group', 'daterange'].includes(item.type) || item.multiple || item.type === 'upload') {
resetData[key] = item.defaultValue ?? []
} else {
resetData[key] = item.defaultValue ?? ''
}
})
localModel.value = { ...resetData }
nextTick(() => formRef.value?.clearValidate())
},
// init
init
}
}
\ No newline at end of file
...@@ -24,7 +24,11 @@ const appointmentInfo = [ ...@@ -24,7 +24,11 @@ const appointmentInfo = [
{ {
label: '签单日', label: '签单日',
key: 'signDate', key: 'signDate',
domType: 'DatePicker', domType: 'datetimePicker',
dateValue: '', //YYYY-MM-DD
timeValue: '', //HH:mm
compositionTime: true, //是否组合时间
finishTime: '',
required: true, required: true,
disabled: false, disabled: false,
placeholder: '请选择', placeholder: '请选择',
......
// utils/deepEqual.js
export function deepEqual(a, b) {
if (a === b) return true
if (a == null || b == null) return false
if (typeof a !== typeof b) return false
if (Array.isArray(a) && Array.isArray(b)) {
if (a.length !== b.length) return false
for (let i = 0; i < a.length; i++) {
if (!deepEqual(a[i], b[i])) return false
}
return true
}
if (typeof a === 'object' && typeof b === 'object') {
const keysA = Object.keys(a)
const keysB = Object.keys(b)
if (keysA.length !== keysB.length) return false
for (const key of keysA) {
if (!deepEqual(a[key], b[key])) return false
}
return true
}
return false
}
\ No newline at end of file
...@@ -32,6 +32,7 @@ export function useDict(...args) { ...@@ -32,6 +32,7 @@ export function useDict(...args) {
*/ */
export function useDictLists(typeLists) { export function useDictLists(typeLists) {
let params = { typeList: typeLists } let params = { typeList: typeLists }
console.log(params)
let dictArray = [] let dictArray = []
return (() => { return (() => {
getMoreDicts(params).then(resp => { getMoreDicts(params).then(resp => {
......
...@@ -152,22 +152,22 @@ watch(drawer, val => { ...@@ -152,22 +152,22 @@ watch(drawer, val => {
}) })
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
::v-deep .el-drawer__header { :deep(.el-drawer__header ) {
margin-bottom: 0 !important; margin-bottom: 0 !important;
} }
::v-deep .el-drawer__footer { :deep(.el-drawer__footer ) {
padding: 0; padding: 0;
} }
.addressInput ::v-deep .el-input__wrapper { .addressInput :deep(.el-input__wrapper ) {
box-shadow: none !important; box-shadow: none !important;
} }
.addressInput ::v-deep .el-input__inner::placeholder { .addressInput :deep(.el-input__inner::placeholder ) {
text-align: right; text-align: right;
} }
.addressInput ::v-deep .el-input__inner { .addressInput :deep(.el-input__inner ) {
text-align: right; text-align: right;
} }
.addressInput ::v-deep .el-input__wrapper { .addressInput :deep(.el-input__wrapper ) {
padding: 0 !important; padding: 0 !important;
} }
.drawerContent { .drawerContent {
......
...@@ -250,17 +250,17 @@ watch(countryDrawer, val => { ...@@ -250,17 +250,17 @@ watch(countryDrawer, val => {
}) })
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
::v-deep .el-drawer__header { :deep(.el-drawer__header ) {
margin-bottom: 0 !important; margin-bottom: 0 !important;
} }
.searchInput ::v-deep .el-input__wrapper { .searchInput :deep(.el-input__wrapper ) {
box-shadow: none !important; box-shadow: none !important;
background-color: #fafbfd !important; background-color: #fafbfd !important;
} }
.searchInput ::v-deep .el-input__inner::placeholder { .searchInput :deep(.el-input__inner::placeholder ) {
text-align: left !important; text-align: left !important;
} }
.searchInput ::v-deep .el-input__inner { .searchInput :deep(.el-input__inner ) {
text-align: left !important; text-align: left !important;
} }
.countryContent { .countryContent {
...@@ -339,7 +339,7 @@ watch(countryDrawer, val => { ...@@ -339,7 +339,7 @@ watch(countryDrawer, val => {
box-sizing: border-box; box-sizing: border-box;
margin-left: 20px; margin-left: 20px;
border-left: 2px solid #d8ebff; border-left: 2px solid #d8ebff;
::v-deep(.el-anchor) { :deep(.el-anchor) {
.el-anchor__link { .el-anchor__link {
padding: 4px 0; padding: 4px 0;
...@@ -354,7 +354,7 @@ watch(countryDrawer, val => { ...@@ -354,7 +354,7 @@ watch(countryDrawer, val => {
} }
} }
} }
::v-deep(.el-anchor__marker) { :deep(.el-anchor__marker) {
height: 10px !important; height: 10px !important;
left: -6px !important; left: -6px !important;
border-radius: 50% !important; border-radius: 50% !important;
......
...@@ -174,22 +174,22 @@ watch( ...@@ -174,22 +174,22 @@ watch(
) )
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
::v-deep .el-drawer__header { :deep(.el-drawer__header ) {
margin-bottom: 0 !important; margin-bottom: 0 !important;
} }
::v-deep .el-drawer__footer { :deep(.el-drawer__footer ) {
padding: 0; padding: 0;
} }
.phoneInput ::v-deep .el-input__wrapper { .phoneInput :deep(.el-input__wrapper ) {
box-shadow: none !important; box-shadow: none !important;
} }
.phoneInput ::v-deep .el-input__inner::placeholder { .phoneInput :deep(.el-input__inner::placeholder ) {
text-align: right; text-align: right;
} }
.phoneInput ::v-deep .el-input__inner { .phoneInput :deep(.el-input__inner ) {
text-align: right; text-align: right;
} }
.phoneInput ::v-deep .el-input__wrapper { .phoneInput :deep(.el-input__wrapper ) {
padding: 0 !important; padding: 0 !important;
} }
.drawerContent { .drawerContent {
......
...@@ -9,14 +9,14 @@ ...@@ -9,14 +9,14 @@
</template> </template>
<!-- 列表区域 --> <!-- 列表区域 -->
<template #table> <template #table>
<!-- 统计信息卡片 --> <!-- 统计信息卡片 v-if="statisticsData.totalInAmount > 0"-->
<div class="statistics-container" v-if="statisticsData.totalInAmount > 0"> <div class="statistics-container">
<el-row :gutter="20"> <el-row :gutter="20">
<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">{{ statisticsData.totalInAmount }}</div> <div class="card-value">{{ statisticsData.totalInAmount ? formatCurrency(statisticsData.totalInAmount) : 0 }}</div>
</div> </div>
</el-card> </el-card>
</el-col> </el-col>
...@@ -24,7 +24,7 @@ ...@@ -24,7 +24,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">{{ statisticsData.totalPolicyCount ? statisticsData.totalPolicyCount : 0 }}</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">总保费(HKD)</div> <div class="card-label">总保费(HKD)</div>
<div class="card-value">{{ statisticsData.totalPremium }}</div> <div class="card-value">{{ statisticsData.totalPremium ? formatCurrency(statisticsData.totalPremium) : 0 }}</div>
</div> </div>
</el-card> </el-card>
</el-col> </el-col>
...@@ -40,7 +40,7 @@ ...@@ -40,7 +40,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.pendingOutAmount }}</div> <div class="card-value">{{ statisticsData.pendingOutAmount ? formatCurrency(statisticsData.pendingOutAmount) : 0 }}</div>
</div> </div>
</el-card> </el-card>
</el-col> </el-col>
...@@ -48,7 +48,7 @@ ...@@ -48,7 +48,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.availableOutAmount }}</div> <div class="card-value">{{ statisticsData.availableOutAmount ? formatCurrency(statisticsData.availableOutAmount) : 0 }}</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">{{ formatCurrency(statisticsData.differenceAmount) }}</div> <div class="card-value">{{ statisticsData.differenceAmount ? formatCurrency(statisticsData.differenceAmount) : 0 }}</div>
</div> </div>
</el-card> </el-card>
</el-col> </el-col>
...@@ -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 :formatter="(row) => `${row.commissionPaidRatio }%`" /> <el-table-column prop="commissionPaidRatio" label="累积已入账比例" width="120" sortable :formatter="(row) => `${row.commissionPaidRatio ? row.commissionPaidRatio : 0 }%`" />
<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 />
...@@ -87,7 +87,7 @@ ...@@ -87,7 +87,7 @@
<el-table-column prop="fortuneUnpaidRatio" label="剩余出账比例" width="120" sortable :formatter="(row) => `${row.fortuneUnpaidRatio }%`" /> <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) }} {{ selectDictLabel(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 />
...@@ -148,7 +148,10 @@ import { Select } from '@element-plus/icons-vue' ...@@ -148,7 +148,10 @@ 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'
import { loadDicts, getDictLabel } from '@/utils/useDict'
const { proxy } = getCurrentInstance()
const { csf_fortune_status } = proxy.useDict('csf_fortune_status')
const userStore = useUserStore() const userStore = useUserStore()
// 分页相关 // 分页相关
...@@ -182,7 +185,7 @@ const searchConfig = ref([ ...@@ -182,7 +185,7 @@ const searchConfig = ref([
debounceWait: 500, // 自定义防抖时间 debounceWait: 500, // 自定义防抖时间
multiple: true, multiple: true,
valueKey: 'insuranceCompanyBizId', valueKey: 'insuranceCompanyBizId',
labelKey: 'abbreviation', labelKey: 'fullName',
transform: (res) => { transform: (res) => {
console.log(res) console.log(res)
return res?.data.records || [] return res?.data.records || []
...@@ -535,15 +538,8 @@ const onSubmit = (data) => { ...@@ -535,15 +538,8 @@ const onSubmit = (data) => {
// 调用保存 API // 调用保存 API
} }
// 获取入账状态,字典值转化方法
onMounted(async () => { onMounted(async () => {
try {
await loadDicts(['csf_fortune_status'])
} catch (error) {
console.error('字典加载失败', error)
} finally {
loading.value = false
}
}) })
</script> </script>
......
...@@ -91,7 +91,7 @@ ...@@ -91,7 +91,7 @@
<el-table-column type="selection" width="40" :selectable="selectableFn" /> <el-table-column type="selection" width="40" :selectable="selectableFn" />
<el-table-column prop="commissionStatus" label="比对状态" width="120" sortable fixed="left"> <el-table-column prop="commissionStatus" label="比对状态" width="120" sortable fixed="left">
<template #default="{ row }"> <template #default="{ row }">
{{ getDictLabel('csf_commission_status', row.commissionStatus) }} {{ selectDictLabel(csf_commission_status, row.commissionStatus) }}
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="commissionBizType" label="业务类型" width="120" sortable> <el-table-column prop="commissionBizType" label="业务类型" width="120" sortable>
...@@ -103,8 +103,11 @@ ...@@ -103,8 +103,11 @@
<el-table-column prop="policyNo" label="保单号" width="120" sortable /> <el-table-column prop="policyNo" label="保单号" width="120" sortable />
<el-table-column prop="reconciliationCompany" label="对账公司" width="120" sortable /> <el-table-column prop="reconciliationCompany" label="对账公司" width="120" sortable />
<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" /> <template #default="{ row }">
{{ selectDictLabel(csf_expected_commission_status, row.commissionExpectedStatus) }}
</template>
</el-table-column>
<el-table-column prop="currentCommissionRatio" label="本次入账比例" width="130" sortable :formatter="formatRatio" /> <el-table-column prop="currentCommissionRatio" label="本次入账比例" width="130" sortable :formatter="formatRatio" />
<el-table-column prop="paidRatio" label="累积入账比例" width="130" sortable :formatter="formatRatio" /> <el-table-column prop="paidRatio" label="累积入账比例" width="130" sortable :formatter="formatRatio" />
<el-table-column prop="pendingRatio" label="待入账比例" width="120" sortable :formatter="formatRatio" /> <el-table-column prop="pendingRatio" label="待入账比例" width="120" sortable :formatter="formatRatio" />
...@@ -194,7 +197,7 @@ ...@@ -194,7 +197,7 @@
<el-table-column prop="policyNo" label="关联保单号" width="120" /> <el-table-column prop="policyNo" label="关联保单号" width="120" />
<el-table-column prop="commissionStatus" label="比对状态" width="120" fixed="left"> <el-table-column prop="commissionStatus" label="比对状态" width="120" fixed="left">
<template #default="{ row }"> <template #default="{ row }">
{{ getDictLabel('csf_commission_status', row.commissionStatus) }} {{ selectDictLabel(csf_commission_status, row.commissionStatus) }}
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="commissionPeriod" label="佣金期数" width="120" /> <el-table-column prop="commissionPeriod" label="佣金期数" width="120" />
...@@ -206,15 +209,19 @@ ...@@ -206,15 +209,19 @@
<el-table-column prop="commissionName" label="入账项目" width="120" /> <el-table-column prop="commissionName" label="入账项目" width="120" />
<el-table-column prop="reconciliationCompany" label="对账公司" width="120" /> <el-table-column prop="reconciliationCompany" label="对账公司" width="120" />
<!-- <el-table-column prop="zip" label="入账状态" width="120" /> --> <!-- <el-table-column prop="zip" label="入账状态" width="120" /> -->
<el-table-column fixed="right" label="操作" min-width="180"> <el-table-column fixed="right" label="操作" min-width="240">
<template #default="{ row }"> <template #default="{ row }">
<el-button type="primary" size="default" @click="checkRecordEdit(row)"> <el-button type="primary" text size="small" @click="compareCommissionEntryapi(row)">
重新比对
</el-button>
<el-button type="primary" text size="small" @click="checkRecordEdit(row)">
修改 修改
</el-button> </el-button>
<el-popconfirm confirm-button-text="Yes" cancel-button-text="No" :icon="InfoFilled" icon-color="#626AEF" <el-popconfirm confirm-button-text="Yes" cancel-button-text="No" :icon="InfoFilled" icon-color="#626AEF"
title="确认要删除吗?" @confirm="deletePolicyCommissionApi(row)"> title="确认要删除吗?" @confirm="deletePolicyCommissionApi(row)">
<template #reference> <template #reference>
<el-button>删除</el-button> <el-button text type="danger" size="small">删除</el-button>
</template> </template>
</el-popconfirm> </el-popconfirm>
</template> </template>
...@@ -286,12 +293,18 @@ import { formatCurrency } from '@/utils/number' ...@@ -286,12 +293,18 @@ import { formatCurrency } from '@/utils/number'
// 接口 // 接口
import { import {
getPolicyCommissionList, generateCommissionRecord, getPolicyCommissionList, generateCommissionRecord,
addPayrollCheckRecord, commissionExpectedRecord, updateCompareStatus, updateCommissionRecord, deletePolicyCommission, syncExpectedCommission addPayrollCheckRecord, commissionExpectedRecord, updateCompareStatus, updateCommissionRecord, deletePolicyCommission, syncExpectedCommission, compareCommissionEntry
} from '@/api/financial/commission' } from '@/api/financial/commission'
import { InfoFilled, Select, Upload, Plus } 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 { proxy } = getCurrentInstance()
const { csf_expected_commission_status } = proxy.useDict('csf_expected_commission_status')
const { csf_commission_status } = proxy.useDict('csf_commission_status')
const formatRatio = (row, column, cellValue, index) => { const formatRatio = (row, column, cellValue, index) => {
if (cellValue == null || cellValue == '' || cellValue == 0) { if (cellValue == null || cellValue == '' || cellValue == 0) {
return '-' return '-'
...@@ -374,7 +387,7 @@ const searchConfig = ref([ ...@@ -374,7 +387,7 @@ const searchConfig = ref([
debounceWait: 500, // 自定义防抖时间 debounceWait: 500, // 自定义防抖时间
multiple: true, multiple: true,
valueKey: 'insuranceCompanyBizId', valueKey: 'insuranceCompanyBizId',
labelKey: 'abbreviation', labelKey: 'fullName',
transform: (res) => { transform: (res) => {
console.log(res) console.log(res)
return res?.data.records || [] return res?.data.records || []
...@@ -474,20 +487,11 @@ const deletePolicyCommissionApi = async (row) => { ...@@ -474,20 +487,11 @@ const deletePolicyCommissionApi = async (row) => {
// 生成可出账记录按钮 // 生成可出账记录按钮
const updatePayRollStatusDisable = ref(true) const updatePayRollStatusDisable = ref(true)
// 获取入账状态,字典值转化方法
onMounted(async () => { onMounted(async () => {
try {
await loadDicts(['csf_expected_commission_status', 'csf_commission_status'])
} catch (error) {
console.error('字典加载失败', error)
} finally {
loading.value = false
}
}) })
// 格式化函数(每次渲染都会调用,所以能拿到最新字典)
const formatStatus = (row, column, cellValue, index) => {
return getDictLabel('csf_expected_commission_status', cellValue) // 实时查缓存
}
// 应收单类型 // 应收单类型
const commissionBizTypeOptions = [ const commissionBizTypeOptions = [
{ value: 'R', label: '关联保单应收单' }, { value: 'R', label: '关联保单应收单' },
...@@ -620,7 +624,7 @@ const handleAddCheckRecord = async () => { ...@@ -620,7 +624,7 @@ const handleAddCheckRecord = async () => {
// ✅ 统一从子组件获取完整表单数据(含 extra 字段) // ✅ 统一从子组件获取完整表单数据(含 extra 字段)
await nextTick() // 确保子组件已同步 await nextTick() // 确保子组件已同步
const formData = addCheckRecordFormRef.value.getFormData() const formData = addCheckRecordFormRef.value.getFormData()
console.log('======',formData) console.log('===========', formData)
let params let params
if (editStatus.value === 'edit') { if (editStatus.value === 'edit') {
params = { params = {
...@@ -635,20 +639,23 @@ const handleAddCheckRecord = async () => { ...@@ -635,20 +639,23 @@ const handleAddCheckRecord = async () => {
} }
await addPayrollCheckRecord([params]) await addPayrollCheckRecord([params])
} }
ElMessage.success(editStatus.value === 'edit' ? '更新成功' : '新增成功') ElMessage.success(editStatus.value === 'edit' ? '更新成功' : '新增成功')
addCheckRecordDialogFlag.value = false addCheckRecordDialogFlag.value = false
resetForm('addReceivablesFormModel') addCheckRecordFormRef.value.resetForm()
checkRecordQuery() checkRecordQuery()
} catch (error) { } catch (error) {
console.error('操作失败', error) console.error('操作失败', error)
ElMessage.error('操作失败') ElMessage.error('操作失败')
} }
} }
const resetForm = (type) => { const clearForm = (type) => {
if (type === 'addReceivablesFormModel') if (type === 'addReceivablesFormModel')
if (addCheckRecordFormRef.value) {
addReceivablesFormModel.value = {} addReceivablesFormModel.value = {}
addCheckRecordFormRef.value.resetForm() addCheckRecordFormRef.value.resetForm()
}
} }
...@@ -842,7 +849,7 @@ const handleAddCheckList = () => { ...@@ -842,7 +849,7 @@ const handleAddCheckList = () => {
editStatus.value = 'add' editStatus.value = 'add'
addReceivablesFormModel.value = { ...selectedRow.value } addReceivablesFormModel.value = { ...selectedRow.value }
addCheckRecordDialogFlag.value = true addCheckRecordDialogFlag.value = true
resetForm('addReceivablesFormModel') clearForm('addReceivablesFormModel')
} }
// 设置比对状态api // 设置比对状态api
...@@ -911,6 +918,22 @@ const closthDialog = () => { ...@@ -911,6 +918,22 @@ const closthDialog = () => {
checkFormData.value = {} checkFormData.value = {}
checkRecordTableData.value = [] checkRecordTableData.value = []
} }
// 重新比对
const compareCommissionEntryapi = async (row) => {
try {
const res = await compareCommissionEntry(row.commissionBizId)
if (res.code === 200) {
ElMessage.success('重新比对成功')
checkRecordQuery()
} else {
ElMessage.error(res.msg || '重新比对失败')
}
} catch (error) {
console.error('重新比对失败', error)
ElMessage.error('重新比对失败')
}
}
</script> </script>
<style scoped> <style scoped>
......
...@@ -319,7 +319,7 @@ const searchConfig = ref([ ...@@ -319,7 +319,7 @@ const searchConfig = ref([
placeholder: '输入保险公司名称搜索', placeholder: '输入保险公司名称搜索',
debounceWait: 500, // 自定义防抖时间 debounceWait: 500, // 自定义防抖时间
valueKey: 'insuranceCompanyBizId', valueKey: 'insuranceCompanyBizId',
labelKey: 'abbreviation', labelKey: 'fullName',
transform: (res) => { transform: (res) => {
console.log(res) console.log(res)
return res?.data.records || [] return res?.data.records || []
...@@ -421,9 +421,8 @@ const addPayRecordFormConfig = [ ...@@ -421,9 +421,8 @@ const addPayRecordFormConfig = [
type: 'input', type: 'input',
prop: 'amount', prop: 'amount',
label: '出账金额', label: '出账金额',
inputType: 'decimal',
rules: [ rules: [
{ pattern: /^\d+$/, message: '只能输入正整数', trigger: 'blur' } { pattern: /^-?\d+(\.\d{1,2})?$/, message: '小数(最多两位)', trigger: 'blur' }
] ]
}, { }, {
type: 'select', type: 'select',
...@@ -726,6 +725,7 @@ const handleConfirmSetPayRecordStatus = async () => { ...@@ -726,6 +725,7 @@ const handleConfirmSetPayRecordStatus = async () => {
onMounted(async () => { onMounted(async () => {
try { try {
await loadDicts(['csf_expected_fortune_status']) await loadDicts(['csf_expected_fortune_status'])
loadTableData()
} catch (error) { } catch (error) {
console.error('字典加载失败', error) console.error('字典加载失败', error)
} finally { } finally {
......
...@@ -410,7 +410,7 @@ const searchConfig = ref([ ...@@ -410,7 +410,7 @@ const searchConfig = ref([
debounceWait: 500, // 自定义防抖时间 debounceWait: 500, // 自定义防抖时间
multiple: true, multiple: true,
valueKey: 'insuranceCompanyBizId', valueKey: 'insuranceCompanyBizId',
labelKey: 'abbreviation', labelKey: 'fullName',
transform: (res) => { transform: (res) => {
return res?.data.records || [] return res?.data.records || []
} }
...@@ -736,6 +736,7 @@ const handleConfirmSetStatus = () => { ...@@ -736,6 +736,7 @@ const handleConfirmSetStatus = () => {
onMounted(async () => { onMounted(async () => {
try { try {
await loadDicts(['csf_expected_commission_status']) await loadDicts(['csf_expected_commission_status'])
loadTableData()
} catch (error) { } catch (error) {
console.error('字典加载失败', error) console.error('字典加载失败', error)
} finally { } finally {
......
...@@ -529,11 +529,11 @@ const handleSuccess = info => { ...@@ -529,11 +529,11 @@ const handleSuccess = info => {
getDictsData() getDictsData()
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
::v-deep .el-card { :deep(.el-card ) {
border: none !important; border: none !important;
} }
::v-deep .el-input-group__append, :deep(.el-input-group__append ),
.el-input-group__prepend { :deep(.el-input-group__prepend ) {
background-color: #fff !important; background-color: #fff !important;
} }
......
...@@ -50,7 +50,7 @@ ...@@ -50,7 +50,7 @@
</el-col> </el-col>
<el-col :sm="12" :lg="6" :xs="24"> <el-col :sm="12" :lg="6" :xs="24">
<el-form-item label="状态" prop="status"> <el-form-item label="流程状态" prop="status">
<el-select v-model="queryParams.status" placeholder="请选择" clearable> <el-select v-model="queryParams.status" placeholder="请选择" clearable>
<el-option <el-option
v-for="dict in csf_fna_status" v-for="dict in csf_fna_status"
...@@ -75,19 +75,13 @@ ...@@ -75,19 +75,13 @@
border border
max-height="380px" 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" prop="customerName" width="100" />
<el-table-column label="状态" align="center" width="150"> <el-table-column label="中文姓名" align="center" prop="customerNameCn" width="100" />
<!-- <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"> <template #default="scope">
<span v-if="scope.row.status == 'UNCOMPLETED'"> <span>{{ selectDictLabel(csf_fna_status, scope.row.status) }}</span>
<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>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column <el-table-column
......
...@@ -39,7 +39,7 @@ ...@@ -39,7 +39,7 @@
>取消预约</el-button >取消预约</el-button
> >
</div> </div>
<div v-else="appointmentSummeryInfo.status !== '2'" style="margin-right: 10px"> <div v-else-if="appointmentSummeryInfo.status !== '2'" style="margin-right: 10px">
<el-button <el-button
v-if="pageSource !== 'policyList' && editStatus" v-if="pageSource !== 'policyList' && editStatus"
type="primary" type="primary"
...@@ -123,7 +123,7 @@ ...@@ -123,7 +123,7 @@
</el-row> </el-row>
<el-tabs v-model="activeName" type="card" class="demo-tabs" :before-leave="beforeTabLeave"> <el-tabs v-model="activeName" type="card" class="demo-tabs" :before-leave="beforeTabLeave">
<el-tab-pane <el-tab-pane
v-for="(tab, index) in tabsList" v-for="tab in tabsList"
:key="tab.name" :key="tab.name"
:label="tab.label" :label="tab.label"
:name="tab.name" :name="tab.name"
...@@ -914,9 +914,6 @@ const handleSubmit = async type => { ...@@ -914,9 +914,6 @@ const handleSubmit = async type => {
if (appointmentInfoRef.value) { if (appointmentInfoRef.value) {
const result = await appointmentInfoRef.value[0].handleFormValues() const result = await appointmentInfoRef.value[0].handleFormValues()
submitAppointmentObj.value.apiAppointmentInfoDto = result submitAppointmentObj.value.apiAppointmentInfoDto = result
console.log('====================================')
console.log('预约', result)
console.log('====================================')
if (!submitAppointmentObj.value.apiAppointmentInfoDto) return if (!submitAppointmentObj.value.apiAppointmentInfoDto) return
} }
if (productPlanRef.value) { if (productPlanRef.value) {
...@@ -1450,11 +1447,11 @@ onUnmounted(() => { ...@@ -1450,11 +1447,11 @@ onUnmounted(() => {
}) })
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
::v-deep .el-card { :deep(.el-card ) {
border: none !important; border: none !important;
} }
::v-deep .el-input-group__append, :deep(.el-input-group__append ),
.el-input-group__prepend { :deep(.el-input-group__prepend ) {
background-color: #fff !important; background-color: #fff !important;
} }
.noembed-container { .noembed-container {
......
...@@ -5,15 +5,8 @@ ...@@ -5,15 +5,8 @@
<div style="position: relative; top: -100%; left: -1100%"> <div style="position: relative; top: -100%; left: -1100%">
{{ appointmentRef }} {{ appointmentRef }}
</div> </div>
<CommonForm <CommonForm :anchorList="anchorList" :affixOffset="affixOffset" :anchorOffset="10"
:anchorList="anchorList" :scrollContainerSelector="anchorContainer" :domIndex="tabIndex" :activeName="activeName" v-if="appointmentRef">
:affixOffset="affixOffset"
:anchorOffset="10"
:scrollContainerSelector="anchorContainer"
:domIndex="tabIndex"
:activeName="activeName"
v-if="appointmentRef"
>
<template #form-right> <template #form-right>
<el-form ref="appointmentInfoFormRef" :model="form" :rules="rules" label-width="120px"> <el-form ref="appointmentInfoFormRef" :model="form" :rules="rules" label-width="120px">
<div v-for="father in processedAppointmentData"> <div v-for="father in processedAppointmentData">
...@@ -22,156 +15,71 @@ ...@@ -22,156 +15,71 @@
<CardOne :title="father.fatherTitle"> <CardOne :title="father.fatherTitle">
<template #mainTitCustom v-if="props.idsObj.appointmentBizId"> <template #mainTitCustom v-if="props.idsObj.appointmentBizId">
<div style="margin-left: 10px"> <div style="margin-left: 10px">
<el-button <el-button @click="viewHistory" type="primary" link
@click="viewHistory" v-if="father.key == 'administration'">历史记录</el-button>
type="primary"
link
v-if="father.key == 'administration'"
>历史记录</el-button
>
</div> </div>
</template> </template>
<template #headerRight v-if="father.key == 'administration'"> <template #headerRight v-if="father.key == 'administration'">
<div> <div>
<el-button <el-button v-if="idsObj.appointmentBizId" @click="handleExprot" type="primary"
v-if="idsObj.appointmentBizId" link>导出预约表</el-button>
@click="handleExprot" <el-button v-if="appointmentStatus >= 2" @click="getItineraryInfo" type="primary"
type="primary" link>行程单预览</el-button>
link
>导出预约表</el-button
>
<el-button
v-if="appointmentStatus >= 2"
@click="getItineraryInfo"
type="primary"
link
>行程单预览</el-button
>
</div> </div>
</template> </template>
<template #content> <template #content>
<!-- 不是表格 --> <!-- 不是表格 -->
<el-row :gutter="20" v-if="!father.showTable"> <el-row :gutter="20" v-if="!father.showTable">
<template v-for="child in father.data" :key="child.key"> <template v-for="child in father.data" :key="child.key">
<el-col <el-col :sm="child.sm" :lg="child.lg" class="formItemBox" v-if="child.show">
:sm="child.sm"
:lg="child.lg"
class="formItemBox"
v-if="child.show"
>
<div> <div>
<el-form-item <el-form-item :label-width="child.labelWidth" :label="child.label" :prop="child.key"
:label-width="child.labelWidth" :key="child.key" :label-position="child.labelPosition" class="button-form-item">
:label="child.label" <el-input v-if="child.domType === 'Input'" :type="child.inputType"
:prop="child.key" v-model="form[child.key]" :placeholder="child.placeholder" maxlength="30"
:key="child.key" :rows="child.rows" :disabled="editStatus" />
:label-position="child.labelPosition" <el-select v-if="child.domType === 'Select'" v-model="form[child.key]"
class="button-form-item" :placeholder="child.placeholder" @change="handleSelectChange(father, child)"
> :disabled="editStatus">
<el-input <el-option v-for="item in child.options" :key="item.value" :label="item.label"
v-if="child.domType === 'Input'" :value="item.value" />
:type="child.inputType"
v-model="form[child.key]"
:placeholder="child.placeholder"
maxlength="30"
:rows="child.rows"
:disabled="editStatus"
/>
<el-select
v-if="child.domType === 'Select'"
v-model="form[child.key]"
:placeholder="child.placeholder"
@change="handleSelectChange(father, child)"
:disabled="editStatus"
>
<el-option
v-for="item in child.options"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select> </el-select>
<!-- 带时分的时间框 --> <!-- 带时分的时间框 -->
<div <div v-if="child.domType === 'datetimePicker'" class="compositionDateTime">
v-if="child.domType === 'datetimePicker'" <el-date-picker type="date" placeholder="选择日期" style="width: 100%"
class="compositionDateTime" v-model="form[child.key]" :disabled="editStatus" @change="handleDateChange(child)"
> :disabled-date="time => disabledDate(time, child)" format="YYYY-MM-DD"
<el-date-picker value-format="YYYY-MM-DD" @clear="handleDateClear(child)" />
type="date"
placeholder="选择日期"
style="width: 100%"
v-model="form[child.key]"
:disabled="editStatus"
@change="handleDateChange(child)"
:disabled-date="time => disabledDate(time, child)"
format="YYYY-MM-DD"
value-format="YYYY-MM-DD"
@clear="handleDateClear(child)"
/>
<span class="text-gray-500">-</span> <span class="text-gray-500">-</span>
<el-time-picker <el-time-picker placeholder="选择时分" style="width: 100%" v-model="child.timeValue"
placeholder="选择时分" :disabled="editStatus" format="HH:mm" value-format="HH:mm"
style="width: 100%" @change="timeValueChange(child)" />
v-model="child.timeValue"
:disabled="editStatus"
format="HH:mm"
value-format="HH:mm"
@change="timeValueChange(child)"
/>
</div> </div>
<!-- 不带时分 --> <!-- 不带时分 -->
<el-date-picker <el-date-picker style="width: 100%" v-if="child.domType === 'DatePicker'"
style="width: 100%" v-model="form[child.key]" type="date" format="YYYY-MM-DD" value-format="YYYY-MM-DD"
v-if="child.domType === 'DatePicker'" :placeholder="child.placeholder" :disabled="editStatus"
v-model="form[child.key]" :disabled-date="time => disabledDate(time, child)" @change="handleDateChange(child)"
type="date" @clear="handleDateClear(child)" />
format="YYYY-MM-DD" <el-input v-if="child.domType === 'arrowRight'" v-model="form[child.key]"
value-format="YYYY-MM-DD" :placeholder="child.placeholder" :suffix-icon="ArrowRight" readonly
:placeholder="child.placeholder" :disabled="editStatus" @focus="handleFoucs(child)">
:disabled="editStatus"
:disabled-date="time => disabledDate(time, child)"
@change="handleDateChange(child)"
@clear="handleDateClear(child)"
/>
<el-input
v-if="child.domType === 'arrowRight'"
v-model="form[child.key]"
:placeholder="child.placeholder"
:suffix-icon="ArrowRight"
readonly
:disabled="editStatus"
@focus="handleFoucs(child)"
>
</el-input> </el-input>
<el-select <el-select v-model="form[child.key]" v-if="child.domType === 'SearchSelect'" filterable
v-model="form[child.key]" :allow-create="child.allowCreate" reserve-keyword
v-if="child.domType === 'SearchSelect'"
filterable
:allow-create="child.allowCreate"
reserve-keyword
@change="handleSelectChange(father, child)" @change="handleSelectChange(father, child)"
:remote-method="query => searchSelectList(query, child.key)" :remote-method="query => searchSelectList(query, child.key)" placeholder="请输入关键词搜索"
placeholder="请输入关键词搜索" :loading="searchLoadingStates[child.key]" :disabled="editStatus">
:loading="searchLoadingStates[child.key]" <el-option v-for="item in searchOptions[child.key] || []" :key="item.id"
:disabled="editStatus" :label="item.label" :value="item.value" />
>
<el-option
v-for="item in searchOptions[child.key] || []"
:key="item.id"
:label="item.label"
:value="item.value"
/>
</el-select> </el-select>
</el-form-item> </el-form-item>
<div v-if="child.domType === 'Div'" class="divClass"> <div v-if="child.domType === 'Div'" class="divClass">
<div v-if="child.key == 'information'" class="desBox"> <div v-if="child.key == 'information'" class="desBox">
<div class="title">{{ child.title }}</div> <div class="title">{{ child.title }}</div>
<div class="informationBox"> <div class="informationBox">
<div <div v-for="(item, index) in child.informationList" :key="index">
v-for="(item, index) in child.informationList"
:key="index"
>
{{ item.name }} {{ item.name }}
</div> </div>
</div> </div>
...@@ -184,15 +92,9 @@ ...@@ -184,15 +92,9 @@
<!-- 是表格数据 --> <!-- 是表格数据 -->
<el-row v-if="father.showTable"> <el-row v-if="father.showTable">
<el-col :span="24" v-if="father.addChildren"> <el-col :span="24" v-if="father.addChildren">
<el-button <el-button :disabled="editStatus" type="primary" icon="Plus" size="small"
:disabled="editStatus" style="margin-bottom: 10px" @click="addChildren(father)">{{ father.addChildrenTxt
type="primary" }}</el-button>
icon="Plus"
size="small"
style="margin-bottom: 10px"
@click="addChildren(father)"
>{{ father.addChildrenTxt }}</el-button
>
</el-col> </el-col>
<el-table :data="father.data" border v-if="father.data.length > 0"> <el-table :data="father.data" border v-if="father.data.length > 0">
<template v-if="father.key == 'referrerDtoList'"> <template v-if="father.key == 'referrerDtoList'">
...@@ -201,23 +103,12 @@ ...@@ -201,23 +103,12 @@
<span class="required-label">姓名</span> <span class="required-label">姓名</span>
</template> </template>
<template #default="scope"> <template #default="scope">
<el-select <el-select v-model="scope.row.realName" filterable remote reserve-keyword
v-model="scope.row.realName" placeholder="请输入关键词搜索" :remote-method="query => searchSelectList(query, 'realName')"
filterable :loading="searchLoadingStates['realName']" :disabled="editStatus"
remote @change="handleTableSelectChange(father, scope.row, 'realName')">
reserve-keyword <el-option v-for="item in searchOptions['realName'] || []" :key="item.id"
placeholder="请输入关键词搜索" :label="item.label" :value="item.value" />
:remote-method="query => searchSelectList(query, 'realName')"
:loading="searchLoadingStates['realName']"
:disabled="editStatus"
@change="handleTableSelectChange(father, scope.row, 'realName')"
>
<el-option
v-for="item in searchOptions['realName'] || []"
:key="item.id"
:label="item.label"
:value="item.value"
/>
</el-select> </el-select>
</template> </template>
</el-table-column> </el-table-column>
...@@ -226,12 +117,8 @@ ...@@ -226,12 +117,8 @@
<span class="required-label">手机号</span> <span class="required-label">手机号</span>
</template> </template>
<template #default="scope"> <template #default="scope">
<el-input <el-input v-model="scope.row.phone" size="default" placeholder="请输入"
v-model="scope.row.phone" :disabled="editStatus" />
size="default"
placeholder="请输入"
:disabled="editStatus"
/>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="邮箱" prop="email" align="center"> <el-table-column label="邮箱" prop="email" align="center">
...@@ -239,21 +126,15 @@ ...@@ -239,21 +126,15 @@
<span class="required-label">邮箱</span> <span class="required-label">邮箱</span>
</template> </template>
<template #default="scope"> <template #default="scope">
<el-input <el-input v-model="scope.row.email" size="default" placeholder="请输入"
v-model="scope.row.email" :disabled="editStatus" />
size="default"
placeholder="请输入"
:disabled="editStatus"
/>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column width="60px" align="center" label="操作"> <el-table-column width="60px" align="center" label="操作">
<template #default="scope"> <template #default="scope">
<el-icon <el-icon class="deleteIcon" @click="deleteChildren(father, scope.$index)">
class="deleteIcon" <Delete />
@click="deleteChildren(father, scope.$index)" </el-icon>
><Delete
/></el-icon>
</template> </template>
</el-table-column> </el-table-column>
</template> </template>
...@@ -263,23 +144,12 @@ ...@@ -263,23 +144,12 @@
<span class="required-label">姓名</span> <span class="required-label">姓名</span>
</template> </template>
<template #default="scope"> <template #default="scope">
<el-select <el-select v-model="scope.row.name" filterable remote reserve-keyword placeholder="请选择"
v-model="scope.row.name"
filterable
remote
reserve-keyword
placeholder="请选择"
:remote-method="query => searchSelectList(query, 'name')" :remote-method="query => searchSelectList(query, 'name')"
:loading="searchLoadingStates['name']" :loading="searchLoadingStates['name']" :disabled="editStatus"
:disabled="editStatus" @change="handleTableSelectChange(father, scope.row, 'name')">
@change="handleTableSelectChange(father, scope.row, 'name')" <el-option v-for="item in searchOptions['name'] || []" :key="item.id"
> :label="item.label" :value="item.value" />
<el-option
v-for="item in searchOptions['name'] || []"
:key="item.id"
:label="item.label"
:value="item.value"
/>
</el-select> </el-select>
</template> </template>
</el-table-column> </el-table-column>
...@@ -288,12 +158,8 @@ ...@@ -288,12 +158,8 @@
<span class="required-label">手机号</span> <span class="required-label">手机号</span>
</template> </template>
<template #default="scope"> <template #default="scope">
<el-input <el-input v-model="scope.row.phone" size="default" placeholder="请输入"
v-model="scope.row.phone" :disabled="editStatus" />
size="default"
placeholder="请输入"
:disabled="editStatus"
/>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="执业编码" prop="practiceCode" align="center"> <el-table-column label="执业编码" prop="practiceCode" align="center">
...@@ -301,59 +167,37 @@ ...@@ -301,59 +167,37 @@
<span class="required-label">执业编码</span> <span class="required-label">执业编码</span>
</template> </template>
<template #default="scope"> <template #default="scope">
<el-input <el-input v-model="scope.row.practiceCode" size="default" placeholder="请输入"
v-model="scope.row.practiceCode" :disabled="editStatus" />
size="default"
placeholder="请输入"
:disabled="editStatus"
/>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="邮箱" prop="email" align="center"> <el-table-column label="邮箱" prop="email" align="center">
<template #default="scope"> <template #default="scope">
<el-input <el-input v-model="scope.row.email" size="default" placeholder="请输入"
v-model="scope.row.email" :disabled="editStatus" />
size="default"
placeholder="请输入"
:disabled="editStatus"
/>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="证件类型" prop="cardType" align="center"> <el-table-column label="证件类型" prop="cardType" align="center">
<template #default="scope"> <template #default="scope">
<el-select <el-select v-model="scope.row.cardType" placeholder="请选择" :disabled="editStatus"
v-model="scope.row.cardType" clearable>
placeholder="请选择" <el-option v-for="item in fetchDictData('csf_id_type')" :key="item.value"
:disabled="editStatus" :label="item.label" :value="item.value" />
clearable
>
<el-option
v-for="item in fetchDictData('csf_id_type')"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select> </el-select>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="证件号" prop="cardNo" align="center"> <el-table-column label="证件号" prop="cardNo" align="center">
<template #default="scope"> <template #default="scope">
<el-input <el-input v-model="scope.row.cardNo" size="default" placeholder="请输入"
v-model="scope.row.cardNo" :disabled="editStatus" />
size="default"
placeholder="请输入"
:disabled="editStatus"
/>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column width="60px" align="center" label="操作"> <el-table-column width="60px" align="center" label="操作">
<template #default="scope"> <template #default="scope">
<el-icon <el-icon @click="deleteChildren(father, scope.$index)" class="deleteIcon">
@click="deleteChildren(father, scope.$index)" <Delete />
class="deleteIcon" </el-icon>
><Delete
/></el-icon>
</template> </template>
</el-table-column> </el-table-column>
</template> </template>
...@@ -369,26 +213,12 @@ ...@@ -369,26 +213,12 @@
</CommonForm> </CommonForm>
</div> </div>
<!-- 历史签约记录 --> <!-- 历史签约记录 -->
<CommonDialog <CommonDialog dialogTitle="历史记录" :showConfirm="false" cancleText="关闭" dialogWidth="70%" :openDialog="openList"
dialogTitle="历史记录" :showClose="true" @close="openList = false">
:showConfirm="false"
cancleText="关闭"
dialogWidth="70%"
:openDialog="openList"
:showClose="true"
@close="openList = false"
>
<div class="dialogBox"> <div class="dialogBox">
<CommonPage <CommonPage :showSearchForm="false" :show-pagination="true" :currentPage="queryParams.pageNo" :total="total"
:showSearchForm="false" :pageSize="queryParams.pageSize" @current-change="changePageNo" @size-change="changePageSize"
:show-pagination="true" :showOperationBtn="false">
:currentPage="queryParams.pageNo"
:total="total"
:pageSize="queryParams.pageSize"
@current-change="changePageNo"
@size-change="changePageSize"
:showOperationBtn="false"
>
<template #table> <template #table>
<el-table v-loading="tableLoading" :data="tableData" border height="350px"> <el-table v-loading="tableLoading" :data="tableData" border height="350px">
<el-table-column label="创建人" align="center" prop="creatorName" /> <el-table-column label="创建人" align="center" prop="creatorName" />
...@@ -400,13 +230,8 @@ ...@@ -400,13 +230,8 @@
}}</span> }}</span>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column <el-table-column label="操作" align="center" width="100" class-name="small-padding fixed-width"
label="操作" fixed="right">
align="center"
width="100"
class-name="small-padding fixed-width"
fixed="right"
>
<template #default="scope"> <template #default="scope">
<el-button link type="primary" @click="viewDetail(scope.row)">查看</el-button> <el-button link type="primary" @click="viewDetail(scope.row)">查看</el-button>
</template> </template>
...@@ -417,15 +242,8 @@ ...@@ -417,15 +242,8 @@
</div> </div>
</CommonDialog> </CommonDialog>
<!-- 签约详情 --> <!-- 签约详情 -->
<CommonDialog <CommonDialog :dialogTitle="detailTitle" :showConfirm="false" cancleText="关闭" dialogWidth="70%"
:dialogTitle="detailTitle" :openDialog="showDetail" :showClose="true" @close="showDetail = false">
:showConfirm="false"
cancleText="关闭"
dialogWidth="70%"
:openDialog="showDetail"
:showClose="true"
@close="showDetail = false"
>
<el-scrollbar max-height="600px"> <el-scrollbar max-height="600px">
<div class="dialogBox"> <div class="dialogBox">
<AppointmentDetail :detailInfo="detailInfo" :editSatus="true"></AppointmentDetail> <AppointmentDetail :detailInfo="detailInfo" :editSatus="true"></AppointmentDetail>
...@@ -433,52 +251,22 @@ ...@@ -433,52 +251,22 @@
</el-scrollbar> </el-scrollbar>
</CommonDialog> </CommonDialog>
<!-- 行程单详情 --> <!-- 行程单详情 -->
<CommonDialog <CommonDialog dialogTitle="香港行程安排" confirmText="下载行程单" dialogWidth="70%" :openDialog="showItinerary"
dialogTitle="香港行程安排" @confirm="handleExprotPdf" @close="showItinerary = false" :showClose="true" :showCancle="false"
confirmText="下载行程单" :confirmLoading="exportLoading">
dialogWidth="70%"
:openDialog="showItinerary"
@confirm="handleExprotPdf"
@close="showItinerary = false"
:showClose="true"
:showCancle="false"
:confirmLoading="exportLoading"
>
<div class="itineraryDialogBox"> <div class="itineraryDialogBox">
<div class="dialogItem" v-for="item in itineraryData"> <div class="dialogItem" v-for="item in itineraryData">
<div class="dialogItemTitle">{{ item.title }}</div> <div class="dialogItemTitle">{{ item.title }}</div>
<DetailPanel <DetailPanel :data="item.data" :row-span="24" col-gap="10px" label-width="120px" row-height="45px"
:data="item.data" :show-col-border="true" />
:row-span="24"
col-gap="10px"
label-width="120px"
row-height="45px"
:show-col-border="true"
/>
</div> </div>
</div> </div>
</CommonDialog> </CommonDialog>
<Phone <Phone @close="handleCloseDrawer" :showDrawer="showPhoneDrawer" :drawerInfo="drawerInfo"
@close="handleCloseDrawer" :phoneMenuList="phoneMenuList" :phoneQuickList="phoneQuickList" @confirmDrawer="confirmDrawer" />
:showDrawer="showPhoneDrawer" <Address @close="handleCloseDrawer" :showAddressDrawer="showAddressDrawer" :drawerInfo="drawerInfo"
:drawerInfo="drawerInfo" :addressMenuList="addressMenuList" :addressQuickList="addressQuickList" @confirmDrawer="confirmDrawer" />
:phoneMenuList="phoneMenuList" <Country :showCountryDrawer="showCountryDrawer" @close="handleCloseDrawer" @confirmCountry="confirmDrawer" />
:phoneQuickList="phoneQuickList"
@confirmDrawer="confirmDrawer"
/>
<Address
@close="handleCloseDrawer"
:showAddressDrawer="showAddressDrawer"
:drawerInfo="drawerInfo"
:addressMenuList="addressMenuList"
:addressQuickList="addressQuickList"
@confirmDrawer="confirmDrawer"
/>
<Country
:showCountryDrawer="showCountryDrawer"
@close="handleCloseDrawer"
@confirmCountry="confirmDrawer"
/>
</div> </div>
</template> </template>
<script setup name="appointmentInfo"> <script setup name="appointmentInfo">
...@@ -793,6 +581,7 @@ const timeValueChange = child => { ...@@ -793,6 +581,7 @@ const timeValueChange = child => {
proxy.$message.warning(`请先选择${child.label}日期`) proxy.$message.warning(`请先选择${child.label}日期`)
child.timeValue = '' child.timeValue = ''
} }
console.log('signDate',child.finishTime)
} }
// 签约详情 // 签约详情
const viewDetail = row => { const viewDetail = row => {
...@@ -824,17 +613,25 @@ const viewHistory = () => { ...@@ -824,17 +613,25 @@ const viewHistory = () => {
} }
} }
const handleTableSelectChange = (father, row, key) => { const handleTableSelectChange = (father, row, key) => {
console.log('选择了', row, key)
searchOptions.value[key].forEach(item => { searchOptions.value[key].forEach(item => {
console.log('item', item)
if (row[key] == item.value) { if (row[key] == item.value) {
row[key] = item.label row[key] = item.label
row.userBizId = item.userBizId row.userBizId = item.userBizId
if (key == 'name') { if (key == 'name') {
row.userSignBizId = item.value row.userSignBizId = item.value
row.practiceCode = item.practiceCode
row.phone = item.phone
row.cardNo = item.cardNo
row.email = item.email
row.cardType = item.cardType
} else if (key == 'realName') { } else if (key == 'realName') {
row.userSaleBizId = item.value row.userSaleBizId = item.value
} }
} }
}) })
} }
// 添加表单子级dom // 添加表单子级dom
const addChildren = father => { const addChildren = father => {
...@@ -1002,6 +799,7 @@ const handleDateClear = child => { ...@@ -1002,6 +799,7 @@ const handleDateClear = child => {
} }
} }
const handleDateChange = child => { const handleDateChange = child => {
// debugger;
if (child.key == 'openAccountStartTime' && form.value['openAccountStartTime']) { if (child.key == 'openAccountStartTime' && form.value['openAccountStartTime']) {
resetShow({ type: 'child', key: 'openAccountEndTime', status: false, flag: 'disabled' }) resetShow({ type: 'child', key: 'openAccountEndTime', status: false, flag: 'disabled' })
disabledDate(form.value['openAccountStartTime'], { key: 'openAccountEndTime' }) disabledDate(form.value['openAccountStartTime'], { key: 'openAccountEndTime' })
...@@ -1009,12 +807,11 @@ const handleDateChange = child => { ...@@ -1009,12 +807,11 @@ const handleDateChange = child => {
// 拼接日期与时分 // 拼接日期与时分
if (child.key == 'arrivalTime' && child.finishTime && child.finishTime.split(' ').length == 2) { if (child.key == 'arrivalTime' && child.finishTime && child.finishTime.split(' ').length == 2) {
child.finishTime = `${form.value[child.key]} ${child.timeValue}:00` child.finishTime = `${form.value[child.key]} ${child.timeValue}:00`
} else if ( } else if (child.key == 'departureTime' && child.finishTime && child.finishTime.split(' ').length == 2 ) {
child.key == 'departureTime' &&
child.finishTime &&
child.finishTime.split(' ').length == 2
) {
child.finishTime = `${form.value[child.key]} ${child.timeValue}:00` child.finishTime = `${form.value[child.key]} ${child.timeValue}:00`
} else if (child.key == 'signDate' && child.finishTime && child.finishTime.split(' ').length == 2) {
child.finishTime = `${form.value[child.key]} ${child.timeValue}:00`
console.log('signDate',child.finishTime)
} }
// 离港时间在到港时间之前 // 离港时间在到港时间之前
if ( if (
...@@ -1196,8 +993,7 @@ const confirmDrawer = info => { ...@@ -1196,8 +993,7 @@ const confirmDrawer = info => {
newObj.objType = drawerInfo.value.drawerType newObj.objType = drawerInfo.value.drawerType
// 因为电话有多个,根据点击的电话类型在抽屉里回显,所以要用drawerInfo.value.code来控制 // 因为电话有多个,根据点击的电话类型在抽屉里回显,所以要用drawerInfo.value.code来控制
newObj.key = drawerInfo.value.key newObj.key = drawerInfo.value.key
form.value[info.key] = newObj.phoneString = `${newObj[drawerInfo.value.code]} ${ form.value[info.key] = newObj.phoneString = `${newObj[drawerInfo.value.code]} ${newObj[drawerInfo.value.key]
newObj[drawerInfo.value.key]
}` }`
saveKey.value[drawerInfo.value.key] = newObj saveKey.value[drawerInfo.value.key] = newObj
// 检查数组中是否已存在key的电话 // 检查数组中是否已存在key的电话
...@@ -1369,7 +1165,11 @@ const setFormValue = (obj, formData) => { ...@@ -1369,7 +1165,11 @@ const setFormValue = (obj, formData) => {
if (field.key == 'confirmAppointmentTime' && props.appointmentStatus !== 0) { if (field.key == 'confirmAppointmentTime' && props.appointmentStatus !== 0) {
field.show = true field.show = true
} }
if(field.compositionTime && obj[field.key]){
field.finishTime = obj[field.key]
form.value[field.key] = proxy.formatToDate(obj[field.key])
field.timeValue = obj[field.key].split(' ')[1]
}
// 处理时间 // 处理时间
if (field.key == 'openAccountEndTime' && obj[field.key]) { if (field.key == 'openAccountEndTime' && obj[field.key]) {
// 开始时间是否在结束时间之前 // 开始时间是否在结束时间之前
...@@ -1392,7 +1192,7 @@ const setFormValue = (obj, formData) => { ...@@ -1392,7 +1192,7 @@ const setFormValue = (obj, formData) => {
} }
} }
// 给到港时间的时分赋值 // 给到港时间的时分赋值
if (field.key == 'arrivalTime') { if (field.key == 'arrivalTime' && obj[field.key]) {
if (obj[field.key] && obj[field.key].split(' ').length > 1) { if (obj[field.key] && obj[field.key].split(' ').length > 1) {
form.value[field.key] = obj[field.key].split(' ')[0] form.value[field.key] = obj[field.key].split(' ')[0]
field.timeValue = obj[field.key].split(' ')[1] field.timeValue = obj[field.key].split(' ')[1]
...@@ -1420,8 +1220,7 @@ const setFormValue = (obj, formData) => { ...@@ -1420,8 +1220,7 @@ const setFormValue = (obj, formData) => {
} }
if (phoneObj[field.key]) { if (phoneObj[field.key]) {
phoneObj.phoneString = form.value[field.key] = `${phoneObj[field.code]} ${ phoneObj.phoneString = form.value[field.key] = `${phoneObj[field.code]} ${phoneObj[field.key]
phoneObj[field.key]
}` }`
tempPhoneList.push(phoneObj) tempPhoneList.push(phoneObj)
} }
...@@ -1497,7 +1296,7 @@ const setFormValue = (obj, formData) => { ...@@ -1497,7 +1296,7 @@ const setFormValue = (obj, formData) => {
addressQuickList.value = removeDuplicates(tempAddressList, 'addressString') addressQuickList.value = removeDuplicates(tempAddressList, 'addressString')
processedAppointmentData.value = oldAppointmentData.value = processedData processedAppointmentData.value = oldAppointmentData.value = processedData
oldObjInfo.value = JSON.parse(JSON.stringify(form.value)) oldObjInfo.value = JSON.parse(JSON.stringify(form.value))
console.log('form.value', form.value) console.log('辉县', form.value)
} }
// 数组去重 // 数组去重
function removeDuplicates(arr, key) { function removeDuplicates(arr, key) {
...@@ -1586,6 +1385,19 @@ const handleFormValues = () => { ...@@ -1586,6 +1385,19 @@ const handleFormValues = () => {
practiceCode: '签单员执业编码' practiceCode: '签单员执业编码'
} }
processedAppointmentData.value.forEach(item => { processedAppointmentData.value.forEach(item => {
if(item.data){
item.data.forEach(item1=>{
if(item1.key=='signDate'&&!item1.timeValue){
errorFields.value.push({
message: '请选择签单日的时分'
})
}
if (item1.compositionTime) {
submitObj[item1.key] = item1.finishTime
}
})
}
if (item.key == 'referrerDtoList') { if (item.key == 'referrerDtoList') {
if (form.value['isReferrerAccompany'] == 1 && item.data.length > 0) { if (form.value['isReferrerAccompany'] == 1 && item.data.length > 0) {
item.data.forEach((item1, index) => { item.data.forEach((item1, index) => {
...@@ -1604,13 +1416,7 @@ const handleFormValues = () => { ...@@ -1604,13 +1416,7 @@ const handleFormValues = () => {
}) })
} }
} }
if (item.key == 'position') {
item.data.forEach((item1, index) => {
if (item1.compositionTime) {
submitObj[item1.key] = item1.finishTime
}
})
}
if (item.key == 'userSignDtoList' && item.data.length > 0) { if (item.key == 'userSignDtoList' && item.data.length > 0) {
item.data.forEach((item1, index) => { item.data.forEach((item1, index) => {
for (const key in obj1) { for (const key in obj1) {
...@@ -1624,9 +1430,7 @@ const handleFormValues = () => { ...@@ -1624,9 +1430,7 @@ const handleFormValues = () => {
submitObj['userSignDtoList'] = item.data submitObj['userSignDtoList'] = item.data
} }
}) })
if (submitObj['signDate']) {
submitObj['signDate'] = proxy.formatToDateTime(submitObj['signDate'])
}
console.log('submitObj', submitObj) console.log('submitObj', submitObj)
if (submitObj['departureTime']) { if (submitObj['departureTime']) {
// 开始时间是否在结束时间之前 // 开始时间是否在结束时间之前
...@@ -1725,6 +1529,7 @@ defineExpose({ ...@@ -1725,6 +1529,7 @@ defineExpose({
color: #f56c6c; color: #f56c6c;
margin-right: 4px; margin-right: 4px;
} }
.domEmpty { .domEmpty {
width: 100%; width: 100%;
height: 100%; height: 100%;
...@@ -1736,25 +1541,30 @@ defineExpose({ ...@@ -1736,25 +1541,30 @@ defineExpose({
color: #a8abb2; color: #a8abb2;
margin-top: 100px; margin-top: 100px;
} }
.topBtn { .topBtn {
width: 100%; width: 100%;
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
margin-bottom: 10px; margin-bottom: 10px;
} }
.formBox { .formBox {
width: 100%; width: 100%;
.fatherLable { .fatherLable {
font-size: 18px; font-size: 18px;
border-left: 4px solid #165dff; border-left: 4px solid #165dff;
padding-left: 5px; padding-left: 5px;
} }
.fatherDes { .fatherDes {
font-size: 14px; font-size: 14px;
color: #a8abb2; color: #a8abb2;
margin-top: 5px; margin-top: 5px;
margin-bottom: 20px; margin-bottom: 20px;
} }
.inputBox { .inputBox {
width: 100%; width: 100%;
border: 1px solid #dcdfe6; border: 1px solid #dcdfe6;
...@@ -1764,11 +1574,13 @@ defineExpose({ ...@@ -1764,11 +1574,13 @@ defineExpose({
justify-content: space-between; justify-content: space-between;
min-height: 32px; min-height: 32px;
padding: 0px 11px; padding: 0px 11px;
.rightArrow { .rightArrow {
font-size: 14px; font-size: 14px;
color: #a8abb2; color: #a8abb2;
} }
} }
.desBox { .desBox {
.title { .title {
width: 120px; width: 120px;
...@@ -1778,6 +1590,7 @@ defineExpose({ ...@@ -1778,6 +1590,7 @@ defineExpose({
color: #606266; color: #606266;
font-weight: 700; font-weight: 700;
} }
.informationBox { .informationBox {
font-size: 14px; font-size: 14px;
border: 1px solid #dcdfe6; border: 1px solid #dcdfe6;
...@@ -1788,6 +1601,7 @@ defineExpose({ ...@@ -1788,6 +1601,7 @@ defineExpose({
} }
} }
} }
.tabButton { .tabButton {
box-shadow: 0 -1px 14px #00000014; box-shadow: 0 -1px 14px #00000014;
width: 100%; width: 100%;
...@@ -1796,6 +1610,7 @@ defineExpose({ ...@@ -1796,6 +1610,7 @@ defineExpose({
justify-content: center; justify-content: center;
/* padding-right: 20px; */ /* padding-right: 20px; */
padding-top: 20px; padding-top: 20px;
.sumbitBtn { .sumbitBtn {
display: flex; display: flex;
align-items: center; align-items: center;
...@@ -1805,34 +1620,40 @@ defineExpose({ ...@@ -1805,34 +1620,40 @@ defineExpose({
background-color: #165dff; background-color: #165dff;
color: #fff; color: #fff;
padding: 0 30px; padding: 0 30px;
.buttonIcon { .buttonIcon {
font-size: 16px; font-size: 16px;
color: #fff; color: #fff;
} }
} }
} }
.customerBox { .customerBox {
.customerItem { .customerItem {
padding: 10px; padding: 10px;
border-radius: 5px; border-radius: 5px;
margin-bottom: 20px; margin-bottom: 20px;
box-shadow: 0 -1px 14px #00000014; box-shadow: 0 -1px 14px #00000014;
.top { .top {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
width: 100%; width: 100%;
.left { .left {
width: 40%; width: 40%;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: flex-start; justify-content: flex-start;
.gender { .gender {
display: flex; display: flex;
align-items: center; align-items: center;
} }
} }
} }
.bottom { .bottom {
.infoItem { .infoItem {
display: flex; display: flex;
...@@ -1841,20 +1662,24 @@ defineExpose({ ...@@ -1841,20 +1662,24 @@ defineExpose({
} }
} }
} }
.customerItem:last-child { .customerItem:last-child {
margin-bottom: 0px; margin-bottom: 0px;
} }
} }
.deleteIcon { .deleteIcon {
color: red; color: red;
font-size: 18px; font-size: 18px;
padding-top: 10px; padding-top: 10px;
} }
.compositionDateTime { .compositionDateTime {
display: flex; display: flex;
width: 100%; width: 100%;
box-sizing: border-box; box-sizing: border-box;
} }
.itineraryDialogBox { .itineraryDialogBox {
.dialogItem { .dialogItem {
.dialogItemTitle { .dialogItemTitle {
...@@ -1865,6 +1690,7 @@ defineExpose({ ...@@ -1865,6 +1690,7 @@ defineExpose({
} }
} }
} }
@media only screen and (min-width: 768px) { @media only screen and (min-width: 768px) {
.formBtn { .formBtn {
margin-top: 0 !important; margin-top: 0 !important;
......
...@@ -302,6 +302,7 @@ import { getBankList } from '@/api/common' ...@@ -302,6 +302,7 @@ import { getBankList } from '@/api/common'
import useUserStore from '@/store/modules/user' import useUserStore from '@/store/modules/user'
import { loadDicts, getDictLabel } from '@/utils/useDict' import { loadDicts, getDictLabel } from '@/utils/useDict'
import { getToken } from '@/utils/auth' import { getToken } from '@/utils/auth'
import { getNowTime, formatToDate, formatToDateTime } from '@/utils/date'
const { proxy } = getCurrentInstance() const { proxy } = getCurrentInstance()
const userStore = useUserStore() const userStore = useUserStore()
// 分页相关 // 分页相关
...@@ -493,7 +494,7 @@ const affirmConfig = [ ...@@ -493,7 +494,7 @@ const affirmConfig = [
placeholder: '请选择', placeholder: '请选择',
maxDate: 'today', maxDate: 'today',
visible: formData => visible: formData =>
formData.remainingUnpaidAmount && Number(formData.remainingUnpaidAmount) <= 0, formData.remainingUnpaidAmount && Number(formData.remainingUnpaidAmount) < 0 || Number(formData.remainingUnpaidAmount) == 0,
rules: [{ required: true, message: '请输入', trigger: 'blur' }] rules: [{ required: true, message: '请输入', trigger: 'blur' }]
}, },
{ {
...@@ -504,7 +505,7 @@ const affirmConfig = [ ...@@ -504,7 +505,7 @@ const affirmConfig = [
maxDate: 'today', maxDate: 'today',
rules: [{ required: true, message: '请输入', trigger: 'blur' }], rules: [{ required: true, message: '请输入', trigger: 'blur' }],
visible: formData => visible: formData =>
formData.remainingUnpaidAmount && Number(formData.remainingUnpaidAmount) <= 0 formData.remainingUnpaidAmount && Number(formData.remainingUnpaidAmount) < 0 || Number(formData.remainingUnpaidAmount) == 0,
}, },
{ {
type: 'select', type: 'select',
...@@ -512,7 +513,7 @@ const affirmConfig = [ ...@@ -512,7 +513,7 @@ const affirmConfig = [
label: '保单状态', label: '保单状态',
dictType: 'csf_policy_status_new', dictType: 'csf_policy_status_new',
visible: formData => visible: formData =>
formData.remainingUnpaidAmount && Number(formData.remainingUnpaidAmount) <= 0 formData.remainingUnpaidAmount && Number(formData.remainingUnpaidAmount) < 0 || Number(formData.remainingUnpaidAmount) == 0,
} }
] ]
//计算待付金额 //计算待付金额
...@@ -534,6 +535,7 @@ const calculateAmount = async () => { ...@@ -534,6 +535,7 @@ const calculateAmount = async () => {
const confirmAffirm = async () => { const confirmAffirm = async () => {
try { try {
const formData = await affirmFormRef.value.validate() const formData = await affirmFormRef.value.validate()
console.log(formData.value)
let newObj = JSON.parse(JSON.stringify(formData)) let newObj = JSON.parse(JSON.stringify(formData))
for (const key in newObj) { for (const key in newObj) {
if (/Date/.test(key)) { if (/Date/.test(key)) {
...@@ -667,10 +669,10 @@ const remittanceConfig = [ ...@@ -667,10 +669,10 @@ const remittanceConfig = [
type: 'select', type: 'select',
prop: 'policyNo', prop: 'policyNo',
label: '保单号码', label: '保单号码',
api: '/csf/api/policy/list/page/vo', api: '/csf/api/policy_follow/policyNos',
keywordField: 'policyNo', keywordField: 'policyNo',
requestParams: { pageNo: 1, pageSize: 20 }, requestParams: { pageNo: 1, pageSize: 100 },
placeholder: '输入转介人名称搜索', placeholder: '输入保单号码搜索',
debounceWait: 500, // 自定义防抖时间 debounceWait: 500, // 自定义防抖时间
valueKey: 'policyNo', valueKey: 'policyNo',
labelKey: 'policyNo', labelKey: 'policyNo',
...@@ -1046,10 +1048,10 @@ const addCheckRecordConfig = [ ...@@ -1046,10 +1048,10 @@ const addCheckRecordConfig = [
type: 'select', type: 'select',
prop: 'policyNo', prop: 'policyNo',
label: '保单号码', label: '保单号码',
api: '/csf/api/policy/list/page/vo', api: '/csf/api/policy_follow/policyNos',
keywordField: 'policyNo', keywordField: 'policyNo',
requestParams: { pageNo: 1, pageSize: 20 }, requestParams: { pageNo: 1, pageSize: 100 },
placeholder: '输入转介人名称搜索', placeholder: '输入保单号码搜索',
debounceWait: 500, // 自定义防抖时间 debounceWait: 500, // 自定义防抖时间
valueKey: 'policyNo', valueKey: 'policyNo',
labelKey: 'policyNo', labelKey: 'policyNo',
......
...@@ -177,11 +177,11 @@ if (route.query.type == 'edit') { ...@@ -177,11 +177,11 @@ if (route.query.type == 'edit') {
getDictsData() getDictsData()
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
::v-deep .el-card { :deep(.el-card ) {
border: none !important; border: none !important;
} }
::v-deep .el-input-group__append, :deep(.el-input-group__append ),
.el-input-group__prepend { :deep(.el-input-group__prepend ) {
background-color: #fff !important; background-color: #fff !important;
} }
......
...@@ -13,17 +13,17 @@ ...@@ -13,17 +13,17 @@
<el-table-column prop="policyNo" label="保单号" width="200" sortable /> <el-table-column prop="policyNo" label="保单号" width="200" sortable />
<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_policy_follow_status', row.status) }} {{ getDictLabel('csf_policy_follow_status_new', row.status) }}
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="appointmentNo" label="预约编号" width="200" sortable /> <el-table-column prop="appointmentNo" label="预约编号" width="200" sortable />
<el-table-column prop="signDate" label="签单日" width="200" sortable /> <el-table-column prop="signDate" label="签单日" width="200" sortable />
<el-table-column prop="policyBizId" label="最晚缴费日" width="200" sortable /> <el-table-column prop="latestPaymentDate" label="最晚缴费日" width="200" sortable />
<el-table-column prop="policyHolder" label="投保人" width="150" sortable /> <el-table-column prop="policyHolder" label="投保人" width="150" sortable />
<el-table-column prop="policyNo" label="受保人" width="150" sortable /> <el-table-column prop="insured" label="受保人" width="150" sortable />
<el-table-column prop="insuranceCompany" label="保险公司" width="200" sortable /> <el-table-column prop="insuranceCompany" label="保险公司" width="200" sortable />
<el-table-column prop="productName" label="产品计划" width="200" sortable /> <el-table-column prop="productName" label="产品计划" width="200" sortable />
<el-table-column prop="paymentTerm" label="缴费年期" width="120" sortable /> <el-table-column prop="issueNumber" label="缴费年期" width="120" sortable />
<el-table-column fixed="right" label="操作" min-width="80"> <el-table-column fixed="right" label="操作" min-width="80">
<template #default="{ row }"> <template #default="{ row }">
<el-popover placement="right" :width="200" trigger="click"> <el-popover placement="right" :width="200" trigger="click">
...@@ -68,9 +68,12 @@ ...@@ -68,9 +68,12 @@
</CommonDialog> </CommonDialog>
<!-- 查看详情、更新数据 --> <!-- 查看详情、更新数据 -->
<CommonDialog :dialogTitle='mode === "viewDetail" ? "查看详情" : "更新数据"' dialogWidth='80%' :openDialog=viewDetailDialogFlag :showAction='false' <CommonDialog :dialogTitle='mode === "viewDetail" ? "查看详情" : "更新数据"' dialogWidth='80%'
:showClose='true' @close='viewDetailDialogFlag = false' @confirm='handleUpdateSubmit'> :openDialog=viewDetailDialogFlag :showAction='false' :showClose='true' @close='viewDetailDialogFlag = false'
<PolicyDetail v-model="policyDetailFormData" @submit="onSubmit" @cancel="showDialog = false"/> @confirm='handleUpdateSubmit'>
<PolicyDetail v-model="policyDetailFormData" :policyBizId="selectedRow.policyBizId" :mode="mode"
ref="policyDetailFormRef" @submit="onSubmit" @cancel="viewDetailDialogFlag = false"
v-if="viewDetailDialogFlag" />
</CommonDialog> </CommonDialog>
...@@ -78,7 +81,7 @@ ...@@ -78,7 +81,7 @@
</template> </template>
<script setup> <script setup>
import { ref, reactive, computed, watch } from 'vue' import { ref, reactive, computed, watch, nextTick } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus' import { ElMessage, ElMessageBox } from 'element-plus'
import CommonPage from '@/components/commonPage' import CommonPage from '@/components/commonPage'
import SearchForm from '@/components/SearchForm/SearchForm.vue' import SearchForm from '@/components/SearchForm/SearchForm.vue'
...@@ -90,11 +93,12 @@ import { ...@@ -90,11 +93,12 @@ import {
getPolicyFollowList, getPolicyFollowList,
getExpectedCommissionList, getExpectedCommissionList,
changePolicyStatus, changePolicyStatus,
policyFollowReport policyFollowReport,
saveMailingInfo, updatePolicyfollow, batchSaveBrokers, saveInitialPayment
} from '@/api/sign/underwritingMain' } from '@/api/sign/underwritingMain'
import PolicyDetail from './policyDetail.vue' import PolicyDetail from './policyDetail.vue'
const policyDetailFormRef = ref(null)
const policyDetailFormData = ref({}) const policyDetailFormData = ref({})
const userStore = useUserStore() const userStore = useUserStore()
// 分页相关 // 分页相关
...@@ -115,14 +119,15 @@ const editStatusFormConfig = ref([ ...@@ -115,14 +119,15 @@ const editStatusFormConfig = ref([
type: 'select', type: 'select',
prop: 'status', prop: 'status',
label: '新单状态', label: '新单状态',
dictType: 'csf_policy_follow_status' dictType: 'csf_policy_follow_status_new'
}, { },
{
type: 'date', type: 'date',
prop: 'policyEffectiveDate', prop: 'effectiveDate',
label: '保单生效日', label: '保单生效日',
}, { }, {
type: 'date', type: 'date',
prop: 'policyInsureDate', prop: 'underwritingDate',
label: '保单核保日', label: '保单核保日',
}, },
]) ])
...@@ -155,20 +160,91 @@ const handleEditStatusSubmit = async () => { ...@@ -155,20 +160,91 @@ const handleEditStatusSubmit = async () => {
// 查看详情、更新数据 // 查看详情、更新数据
const viewDetailDialogFlag = ref(false) const viewDetailDialogFlag = ref(false)
const onSubmit = (data) => { const onSubmit = async (data) => {
console.log('提交的数据:', data) console.log('提交的数据:', data)
// 调用 API 保存 // 调用 API 保存
alert('提交成功!') // alert('提交成功!')
viewDetailDialogFlag.value = false let params = {}
if (data.activeTab === 'postal') {
params = {
policyBizId: selectedRow.value.policyBizId,
...data,
activeTab: undefined
}
const res = await saveMailingInfo(params)
if (res.code === 200) {
ElMessage.success('保存邮寄信息成功')
} else {
ElMessage.error(res.msg || '保存邮寄信息失败')
}
} else if (data.activeTab === 'basic' || data.activeTab === 'productPlan') {
params = {
policyBizId: selectedRow.value.policyBizId,
...data,
activeTab: undefined
}
const res = await updatePolicyfollow(params)
if (res.code === 200) {
ElMessage.success(data.activeTab === 'basic' ? '保存基本信息成功' : '保存产品计划成功')
// viewDetailDialogFlag.value = false
} else {
ElMessage.error(res.msg || (data.activeTab === 'basic' ? '保存基本信息失败' : '保存产品计划失败'))
}
} else if (data.activeTab === 'introducer') {
params = {
policyBizId: selectedRow.value.policyBizId,
brokerList: normalizeIntroducerData(data),
activeTab: undefined
}
const res = await batchSaveBrokers(params)
if (res.code === 200) {
ElMessage.success('保存转介人成功')
// viewDetailDialogFlag.value = false
} else {
ElMessage.error(res.msg || '保存转介人失败')
}
} else if (data.activeTab === 'attachment') {
} else if (data.activeTab === 'firstPayment') {
try {
params = {
policyBizId: selectedRow.value.policyBizId,
...data,
activeTab: undefined
}
const res = await saveInitialPayment(params)
if (res.code === 200) {
ElMessage.success('保存首期缴费成功')
// viewDetailDialogFlag.value = false
} else {
ElMessage.error(res.msg || '保存首期缴费失败')
}
} catch (error) {
console.error('首期缴费表单验证失败', error)
return
}
}
} }
const handleClose = () => { // 处理转介人数据格式
// 可选:清空数据 const normalizeIntroducerData = (data) => {
if (Array.isArray(data)) return data
if (data && typeof data === 'object') {
const keys = Object.keys(data).filter(k => /^\d+$/.test(k))
if (keys.length > 0) {
return keys.sort((a, b) => a - b).map(k => data[k])
}
}
return []
} }
// 获取新单状态,字典值转化方法 // 获取新单状态,字典值转化方法
onMounted(async () => { onMounted(async () => {
try { try {
await loadDicts(['csf_policy_follow_status']) await loadDicts(['csf_policy_follow_status_new'])
loadTableData()
} catch (error) { } catch (error) {
console.error('字典加载失败', error) console.error('字典加载失败', error)
} finally { } finally {
...@@ -234,7 +310,7 @@ const searchConfig = ref([ ...@@ -234,7 +310,7 @@ const searchConfig = ref([
type: 'select', type: 'select',
prop: 'status', prop: 'status',
label: '新单状态', label: '新单状态',
dictType: 'csf_policy_follow_status' dictType: 'csf_policy_follow_status_new'
}, },
{ {
type: 'input', type: 'input',
...@@ -264,10 +340,9 @@ const searchConfig = ref([ ...@@ -264,10 +340,9 @@ const searchConfig = ref([
placeholder: '输入保险公司名称搜索', placeholder: '输入保险公司名称搜索',
debounceWait: 500, // 自定义防抖时间 debounceWait: 500, // 自定义防抖时间
valueKey: 'insuranceCompanyBizId', valueKey: 'insuranceCompanyBizId',
labelKey: 'abbreviation', labelKey: 'fullName',
multiple: true, multiple: true,
transform: (res) => { transform: (res) => {
console.log(res)
return res?.data.records || [] return res?.data.records || []
} }
}, { }, {
...@@ -303,7 +378,7 @@ const searchConfig = ref([ ...@@ -303,7 +378,7 @@ const searchConfig = ref([
const loadTableData = async () => { const loadTableData = async () => {
loading.value = true loading.value = true
searchParams.value = searchFormRef.value.getFormData() || {} searchParams.value = searchFormRef.value.getFormData() || {}
console.log(searchParams.value) // console.log(searchParams.value)
try { try {
const params = { const params = {
...searchParams.value, ...searchParams.value,
...@@ -331,42 +406,44 @@ const dropdownItems = [ ...@@ -331,42 +406,44 @@ const dropdownItems = [
{ label: '更新数据', value: 'updateData' }, { label: '更新数据', value: 'updateData' },
{ label: '生成签单报告', value: 'generateReport' }, { label: '生成签单报告', value: 'generateReport' },
{ label: '设置新单状态', value: 'setNewSingleStatus' }, { label: '设置新单状态', value: 'setNewSingleStatus' },
{ label: '查看关联', value: 'viewRelated' }, // { label: '查看关联', value: 'viewRelated' },
// { label: '查看记录', value: 'viewRecord' }, // { label: '查看记录', value: 'viewRecord' },
] ]
const mode = ref('viewDetail') const mode = ref('viewDetail')
const handleSelect = async (e, row) => { const handleSelect = async (e, row) => {
selectedRow.value = { ...row } selectedRow.value = { ...row }
console.log('列表点击操作', e, selectedRow.value)
mode.value = e mode.value = e
console.log(e, row)
switch (e) { switch (e) {
case 'viewDetail': case 'viewDetail':
// ElMessage.info('查看详情')
viewDetailDialogFlag.value = true viewDetailDialogFlag.value = true
// viewDetailFormData.value = JSON.parse(JSON.stringify(mockEditData))
// 赋值已有数据(深拷贝避免引用问题)
// Object.assign(formData, JSON.parse(JSON.stringify(mockEditData)))
break break
case 'updateData': case 'updateData':
ElMessage.info('更新数据') if (selectedRow.value.status === '生效') {
ElMessage.warning('非新单状态,不能更新数据')
return
}
viewDetailDialogFlag.value = true
break break
case 'setNewSingleStatus': case 'setNewSingleStatus':
// ElMessage.info('设置新单状态') if (selectedRow.value.status === '生效') {
ElMessage.warning('保单已生效,不能更新新单状态')
return
}
editStatusDialogFlag.value = true editStatusDialogFlag.value = true
editStatusFormData.value = { editStatusFormData.value = {
status: row.status status: row.status
} }
break break
case 'viewRelated': case 'viewRelated':
// ElMessage.info('查看关联')
viewRelatedDialogFlag.value = true viewRelatedDialogFlag.value = true
break break
case 'generateReport': case 'generateReport':
generateReport(row) generateReport(row)
break break
case 'viewRecord': case 'viewRecord':
ElMessage.info('查看记录') // ElMessage.info('查看记录')
break break
default: default:
break break
...@@ -387,10 +464,15 @@ const generateReport = async (row) => { ...@@ -387,10 +464,15 @@ const generateReport = async (row) => {
fileName, fileName,
'application/pdf;charset=utf-8' 'application/pdf;charset=utf-8'
) )
if (res.code === 200) {
ElMessage.success('报告生成成功') ElMessage.success('报告生成成功')
}else{
ElMessage.error(res.msg || '报告生成失败')
}
// 可以添加下载报告的逻辑 // 可以添加下载报告的逻辑
} catch (error) { } catch (error) {
ElMessage.error(error.message || '报告生成失败') console.error(error.message || '报告生成失败')
} }
} }
...@@ -411,7 +493,34 @@ const handleUpdateSubmit = async () => { ...@@ -411,7 +493,34 @@ const handleUpdateSubmit = async () => {
} }
} }
// 保存邮寄信息
const saveMailingInfoapi = async () => {
if (!basicInfoFormData.value.policyNo) {
ElMessage.error('请先输入保单号')
return
}
try {
const params = {
policyBizId: basicInfoFormData.value.policyBizId,
mailingMethod: postalFormData.value.mailingMethod,
deliveryNo: postalFormData.value.deliveryNo,
brokerSignDate: postalFormData.value.brokerSignDate,
customerSignDate: postalFormData.value.customerSignDate,
}
const res = await savePostalInfo(params)
if (res.code === 200) {
ElMessage.success('保存邮寄信息成功')
} else {
ElMessage.error(res.msg || '保存邮寄信息失败')
}
} catch (error) {
console.error('保存邮寄信息失败', error)
} finally {
}
}
......
...@@ -3,8 +3,9 @@ ...@@ -3,8 +3,9 @@
<div class="form-page"> <div class="form-page">
<el-form ref="formRef" :model="localData" label-width="120px" size="default"> <el-form ref="formRef" :model="localData" label-width="120px" size="default">
<!-- Tabs --> <!-- Tabs -->
<el-tabs v-model="activeTab" @tab-click="handleTabClick"> <el-tabs v-model="activeTab" @tab-click="handleTabClick" @before-leave="handleBeforeLeave">
<el-tab-pane label="基础信息" name="basic"></el-tab-pane> <el-tab-pane label="基础信息" name="basic"></el-tab-pane>
<el-tab-pane label="产品计划" name="productPlan"></el-tab-pane>
<el-tab-pane label="首期缴费" name="firstPayment"></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="introducer"></el-tab-pane>
<el-tab-pane label="邮寄信息" name="postal"></el-tab-pane> <el-tab-pane label="邮寄信息" name="postal"></el-tab-pane>
...@@ -26,64 +27,24 @@ ...@@ -26,64 +27,24 @@
<SearchForm ref="policyInfoFormRef" :config="policyInfoFormConfig" v-model="policyInfoFormData" /> <SearchForm ref="policyInfoFormRef" :config="policyInfoFormConfig" v-model="policyInfoFormData" />
</div> </div>
</div>
<!-- 产品计划 Tab 内容 -->
<div v-else-if="activeTab === 'productPlan'" class="tab-content">
<!-- 基本计划 --> <!-- 基本计划 -->
<div class="section"> <div class="section">
<h3 class="sectionTitle">基本计划</h3> <h3 class="sectionTitle">基本计划</h3>
<SearchForm ref="basicPlanFormRef" :config="basicPlanFormConfig" v-model="basicPlanFormData" /> <SearchForm ref="basicPlanFormRef" :config="basicPlanFormConfig" v-model="basicPlanFormData"
@selectChange="(prop, value, item) => handleSelectChange(prop, value, item, 'basicPlan')" />
</div> </div>
<!-- 附加计划(可编辑表格) --> <!-- 附加计划(可编辑表格) -->
<div class="section"> <div class="section">
<h3 class="sectionTitle">附加计划</h3> <h3 class="sectionTitle">附加计划</h3>
<el-button type="primary" size="default" @click="addAdditionalPlan">+ 新增</el-button> <EditableTable v-model="localData.additionalPlans" :row-config="additionalPlansConfig"
<el-table :data="localData.additionalPlans || []" style="width: 100%; margin-top: 10px" border> @batch-save="handleBatchSave" :disabled="props.mode === 'viewDetail'" />
<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>
</div> </div>
<!-- 首期缴费 Tab 内容 --> <!-- 首期缴费 Tab 内容 -->
<div v-else-if="activeTab === 'firstPayment'" class="tab-content"> <div v-else-if="activeTab === 'firstPayment'" class="tab-content">
<!-- 签单信息 --> <!-- 签单信息 -->
...@@ -93,13 +54,14 @@ ...@@ -93,13 +54,14 @@
v-model="firstPremiumFormData" /> v-model="firstPremiumFormData" />
<h3 class="sectionTitle">首期对账信息</h3> <h3 class="sectionTitle">首期对账信息</h3>
<el-table :data="firstPremiumTableData" border style="width: 100%"> <el-table :data="firstPremiumTableData" border style="width: 100%">
<el-table-column prop="date" label="申请对账日期" width="180" /> <el-table-column prop="createTime" label="申请对账日期" width="180"
<el-table-column prop="name" label="对账状态" width="180" /> :formatter="(row, col, val) => formatToDateTime(val, 'YYYY-MM-DD HH:mm:ss')" />
<el-table-column prop="address" label="缴费方式" width="180" /> <el-table-column prop="reconciliationStatus" label="对账状态" width="180" />
<el-table-column prop="address" label="汇款币种" width="180" /> <el-table-column prop="paymentMethod" label="缴费方式" width="180" />
<el-table-column prop="address" label="汇款日期" width="180" /> <el-table-column prop="paymentAmount" label="汇款金额" width="180" />
<el-table-column prop="address" label="认定金额" width="180" /> <el-table-column prop="paymentCurrency" label="汇款币种" width="180" />
<el-table-column prop="address" label="认定币种" width="180" /> <el-table-column prop="recognizedAmount" label="认定金额" width="180" />
<el-table-column prop="recognizedCurrency" label="认定币种" width="180" />
</el-table> </el-table>
</div> </div>
</div> </div>
...@@ -109,27 +71,8 @@ ...@@ -109,27 +71,8 @@
<div class="section"> <div class="section">
<h3 class="sectionTitle">介绍人信息</h3> <h3 class="sectionTitle">介绍人信息</h3>
<h5>第一位默认是客户主要负责人,客户资料出现在介绍人(主)账号下,其他介绍人不会看到客户信息</h5> <h5>第一位默认是客户主要负责人,客户资料出现在介绍人(主)账号下,其他介绍人不会看到客户信息</h5>
<el-button type="primary"> <EditableTable v-model="introducerTableData" :row-config="introducerConfig"
<el-icon class="el-icon--right"> @batch-save="handleBatchSave" :disabled="props.mode === 'viewDetail'" />
<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>
</div> </div>
<!-- 邮寄信息 Tab 内容 --> <!-- 邮寄信息 Tab 内容 -->
...@@ -144,13 +87,15 @@ ...@@ -144,13 +87,15 @@
<div class="section"> <div class="section">
<h3 class="sectionTitle">关联记录</h3> <h3 class="sectionTitle">关联记录</h3>
<el-table :data="relatedTableData" border style="width: 100%"> <el-table :data="relatedTableData" border style="width: 100%">
<el-table-column prop="date" label="流程编号" width="180" /> <el-table-column prop="fnaNo" label="流程编号" width="180" />
<el-table-column prop="name" label="客户姓名" width="180" /> <el-table-column prop="policyNo" label="保单号" width="180" />
<el-table-column prop="address" label="创建时间" width="180" /> <el-table-column prop="customerName" label="客户姓名" width="180" />
<el-table-column prop="createTime" label="创建时间" width="180"
:formatter="(row) => formatToDate(row.createTime)" />
<el-table-column fixed="right" label="操作" min-width="120"> <el-table-column fixed="right" label="操作" min-width="120">
<template #default> <template #default>
<el-button link type="primary" size="small" @click="handleClick"> <el-button link type="primary" size="small" @click="viewRecordDetail">
Detail 查看
</el-button> </el-button>
</template> </template>
</el-table-column> </el-table-column>
...@@ -161,21 +106,23 @@ ...@@ -161,21 +106,23 @@
<div v-else-if="activeTab === 'attachment'" class="tab-content"> <div v-else-if="activeTab === 'attachment'" class="tab-content">
<div class="section"> <div class="section">
<h3 class="sectionTitle">附件</h3> <h3 class="sectionTitle">附件</h3>
<el-button type="primary"> <el-button type="primary" @click="fileUploadDialogFlag = true"
:disabled="props.mode === 'viewDetail'">
<el-icon class="el-icon--right"> <el-icon class="el-icon--right">
<Upload /> <Upload />
</el-icon>上传附件 </el-icon>上传附件
</el-button> </el-button>
<el-table :data="attachmentTableData" border style="width: 100%"> <el-table :data="attachmentTableData" border style="width: 100%">
<el-table-column prop="date" label="文件名" width="180" /> <el-table-column v-for="(column, index) in attachmentTableColumns" :key="index"
<el-table-column prop="name" label="文件类型" width="180" /> :prop="column.prop" :label="column.label" :width="column.width" :sortable="column.sortable"
<el-table-column prop="address" label="上传时间" width="180" /> :formatter="column.formatter" />
<el-table-column prop="address" label="上传人" width="180" />
<el-table-column fixed="right" label="操作" min-width="120"> <el-table-column fixed="right" label="操作" min-width="120">
<template #default> <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> </el-button>
</template> </template>
...@@ -193,13 +140,219 @@ ...@@ -193,13 +140,219 @@
<el-button type="primary" size="default" @click="handleSubmit">确认</el-button> <el-button type="primary" size="default" @click="handleSubmit">确认</el-button>
</div> </div>
</el-form> </el-form>
<CommonDialog dialogTitle='文件导入' dialogWidth='80%' :openDialog=fileUploadDialogFlag :showAction='true'
:showClose='true' @close='fileUploadDialogFlag = false; files = ""'
@confirm='fileUploadDialogFlag = false; files = ""'>
<FileUpload v-model="files" :data="{ obiectTableName: 'policy_follow', objectBizId: props.policyBizId }"
:file-type="['xlsx', 'xls', 'doc', 'docx', 'pdf', 'txt', 'jpg', 'jpeg', 'png', 'gif']"
:action="'/oss/api/oss/batch/upload'" :limit="10" :fileSize="15" :name="'files'"
@uploadEnd="handleUploadEnd" />
</CommonDialog>
</div> </div>
</template> </template>
<script setup> <script setup>
import { ref, reactive, watch, nextTick } from 'vue' import { ref, reactive, watch, nextTick, onMounted, computed } from 'vue'
import SearchForm from '@/components/SearchForm/SearchForm.vue' import SearchForm from '@/components/SearchForm/SearchForm.vue'
import CommonDialog from '@/components/commonDialog'
import { ElMessage, ElMessageBox } from 'element-plus'
import { Delete, Edit, Search, Share, Upload } from '@element-plus/icons-vue' import { Delete, Edit, Search, Share, Upload } from '@element-plus/icons-vue'
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 { getNowTime, formatToDate, formatToDateTime } from '@/utils/date'
import EditableTable from '@/components/csf-common/EditableTable.vue'
import useUserStore from '@/store/modules/user'
const { proxy } = getCurrentInstance()
proxy.useDictLists(['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'])
const router = useRouter()
const userStore = useUserStore()
const files = ref('')
const fileUploadDialogFlag = ref(false)
// 记录每个 tab 是否有未保存的修改(dirty 状态)
const tabDirty = ref({
basic: false,
productPlan: false,
firstPayment: false,
introducer: false,
postal: false,
related: false,
attachment: false
})
// ===== 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 = [
{
type: 'select',
prop: 'brokerBizId',
label: '转介人',
api: '/insurance/base/api/userSaleExpand/page',
keywordField: 'realName',
requestParams: { pageNo: 1, pageSize: 20 },
placeholder: '输入转介人名称搜索',
debounceWait: 500, // 自定义防抖时间
onChangeExtraFields: {
broker: 'realName',// 选中后自动填 broker = raw.realName
brokerName: 'realName',
internalCode: 'code',
team: 'teamName',
phone: 'phone',
},
transform: (res) => {
return (res?.data.records || []).map(item => ({
value: item.clientUserBizId,
label: item.realName,
...item
}))
},
},
// {
// type: 'select',
// prop: 'gender',
// label: '性别',
// span: 4,
// options: [
// { value: 'male', label: '男' },
// { value: 'female', label: '女' }
// ]
// },
{
type: 'input',
prop: 'phone',
label: '手机号码',
span: 4,
disabled: true
},
{
type: 'input',
prop: 'internalCode',
label: '内部编号',
span: 4,
disabled: true
},
{
type: 'input',
prop: 'team',
label: '所属团队',
span: 4,
disabled: true
}, {
type: 'input',
prop: 'brokerRatio',
label: '分配比例%',
span: 4,
inputType: 'integer',
rules: [
{ required: true, message: '请输入比例' },
{
validator: (rule, value, cb) => {
const n = Number(value)
if (isNaN(n) || n < 0 || n > 100) cb(new Error('0~100'))
else cb()
}
}
]
}, {
type: 'textarea',
prop: 'remark',
label: '备注',
span: 4
},
]
// 附加计划表格
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({})
const basicInfoFormRef = ref() const basicInfoFormRef = ref()
const basicInfoFormData = ref({}) const basicInfoFormData = ref({})
const basicInfoFormConfig = ref([ const basicInfoFormConfig = ref([
...@@ -209,30 +362,48 @@ const basicInfoFormConfig = ref([ ...@@ -209,30 +362,48 @@ const basicInfoFormConfig = ref([
label: '签单日', label: '签单日',
}, { }, {
type: 'select', type: 'select',
prop: 'currency', prop: 'signer',
label: '签单员', label: '签单员',
dictType: 'bx_currency_type', api: '/insurance/base/api/userSignExpand/page',
keywordField: 'realName',
requestParams: { pageNo: 1, pageSize: 20 },
placeholder: '输入签单员姓名搜索',
debounceWait: 500, // 自定义防抖时间
onChangeExtraFields: {
signerBizId: 'userSignBizId',
practiceCode: 'practiceCode',
},
valueKey: 'realName',
labelKey: 'realName',
transform: (res) => {
return res?.data.records || []
},
rules: [
{ required: true, message: '请选择签单员', trigger: 'blur' },
],
}, { }, {
type: 'input', type: 'input',
prop: 'policyNo', prop: 'practiceCode',
label: '签单员执业编号', label: '签单员执业编号',
}, { }, {
type: 'select', type: 'select',
prop: 'currency', prop: 'signLocation',
label: '签单地点', label: '签单地点',
dictType: 'bx_currency_type' dictType: 'csf_ap_meeting_point',
}, },
]) ])
// 保单信息 // 保单信息
const policyInfoFormRef = ref() const policyInfoFormRef = ref()
const policyInfoFormData = ref({}) const policyInfoFormData = ref({})
const policyInfoFormConfig = ref([ const policyInfoFormConfig = ref([
{ {
type: 'date', type: 'input',
prop: 'endDate', prop: 'policyNo',
label: '保单号', label: '保单号',
}, { }, {
type: 'date', type: 'date',
...@@ -240,7 +411,7 @@ const policyInfoFormConfig = ref([ ...@@ -240,7 +411,7 @@ const policyInfoFormConfig = ref([
label: '保单生效日', label: '保单生效日',
}, { }, {
type: 'date', type: 'date',
prop: 'effectiveDate', prop: 'policyExpirationDate',
label: '保单截止日', label: '保单截止日',
}, { }, {
type: 'date', type: 'date',
...@@ -248,36 +419,43 @@ const policyInfoFormConfig = ref([ ...@@ -248,36 +419,43 @@ const policyInfoFormConfig = ref([
label: '保单核保日', label: '保单核保日',
}, { }, {
type: 'date', type: 'date',
prop: 'underwritingDate', prop: 'receiptDate',
label: '保单回执日', label: '保单回执日',
}, { }, {
type: 'select', type: 'select',
prop: 'paymentFrequency', prop: 'receiptStatus',
label: '回执状态', label: '回执状态',
dictType: 'receipt_status',
}, { }, {
type: 'select', type: 'select',
prop: 'paymentFrequency', prop: 'directPaymentEnabled',
label: '是否开通直接支付', label: '是否开通直接支付',
dictType: 'sys_no_yes',
}, { }, {
type: 'input', type: 'input',
prop: 'insurer', prop: 'policyHolder',
label: '保单持有人', label: '保单持有人',
}, { }, {
type: 'input', type: 'input',
prop: 'insurer', prop: 'insured',
label: '保单受保人', label: '保单受保人',
}, { }, {
type: 'select', type: 'input',
prop: 'insuranceType', prop: 'insuredAge',
label: '受保人年龄', label: '受保人年龄',
}, { }, {
type: 'select', type: 'input',
prop: 'paymentFrequency', prop: 'gracePeriod',
label: '宽限期', label: '宽限期(天)',
inputType: 'decimal',
rules: [
{ pattern: /^\d+$/, message: '只能输入正整数', trigger: 'blur' }
]
}, { }, {
type: 'select', type: 'select',
prop: 'paymentFrequency', prop: 'isJoin',
label: '是否参加递增保障权益', label: '是否参加递增保障权益',
dictType: 'sys_no_yes',
}, },
]) ])
...@@ -287,71 +465,149 @@ const basicPlanFormData = ref({}) ...@@ -287,71 +465,149 @@ const basicPlanFormData = ref({})
const basicPlanFormConfig = ref([ const basicPlanFormConfig = ref([
{ {
type: 'select', type: 'select',
prop: 'paymentFrequency', prop: 'insuranceCompanyBizId',
label: '保险公司', label: '保险公司',
api: '/insurance/base/api/insuranceCompany/page',
keywordField: 'queryContent',
requestParams: { pageNo: 1, pageSize: 500 },
placeholder: '输入保险公司名称搜索',
debounceWait: 500, // 自定义防抖时间
valueKey: 'insuranceCompanyBizId',
labelKey: 'fullName',
transform: (res) => {
return res?.data.records || []
},
onChangeExtraFields: {
insuranceCompany: 'fullName',
},
}, { }, {
type: 'select', type: 'select',
prop: 'paymentFrequency', prop: 'productCate',
label: '保险险种', 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 || []
},
onChangeExtraFields: {
insuranceCategoryCode: 'code',
},
},
{
type: 'select', type: 'select',
prop: 'paymentFrequency', prop: 'productName',
label: '产品名称', 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',
// ✅ 确认这里用的是正确的字段(code 或 bizId)
categoryCodeList: [basicPlanFormData.value.insuranceCategoryCode || ''],
insuranceCompanyBizIdList: [basicPlanFormData.value.insuranceCompanyBizId || ''],
pageNo: 1,
pageSize: 20
}),
placeholder: '请选择产品名称',
debounceWait: 500,
valueKey: 'productName',
labelKey: 'productName',
transform: (res) => {
console.log('======子组件选择选项后,父组件接收的值 :', res?.data.records || [])
return res?.data.records || []
},
onChangeExtraFields: {
apiAttributeSettingDtoList: 'apiAttributeSettingDtoList',
productLaunchBizId: 'productLaunchBizId',
insuranceCategoryCode: 'code',
// 如果需要,也可以回填其他字段
},
},
{
type: 'select', type: 'select',
prop: 'paymentFrequency', prop: 'paymentFrequency',
label: '付款频率', label: '付款频率',
dictType: 'csf_ap_frequency',
}, { }, {
type: 'select', type: 'input',
prop: 'paymentFrequency', prop: 'guaranteePeriod',
label: '保障期限', label: '保障期限',
}, { }, {
type: 'select', type: 'input',
prop: 'paymentFrequency', prop: 'sumInsured',
label: '保额(重疾险)', label: '保额(重疾险)',
}, { }, {
type: 'select', type: 'select',
prop: 'paymentFrequency', prop: 'issueNumber',
label: '供款期数', label: '供款期数',
}, { }, {
type: 'select', type: 'select',
prop: 'paymentFrequency', prop: 'policyCurrency',
label: '保单币种', label: '保单币种',
dictType: 'bx_currency_type'
}, { }, {
type: 'select', type: 'input',
prop: 'paymentFrequency', prop: 'initialPremium',
label: '每期保费', 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', type: 'input',
prop: 'paymentFrequency', prop: 'policyLevy',
label: '保单征费', 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', type: 'select',
prop: 'paymentFrequency', prop: 'isPrepay',
label: '是否预缴', label: '是否预缴',
dictType: 'sys_no_yes'
}, { }, {
type: 'select', type: 'select',
prop: 'paymentFrequency', prop: 'isTraceable',
label: '是否追溯', label: '是否追溯',
dictType: 'sys_no_yes'
}, { }, {
type: 'select', type: 'date',
prop: 'paymentFrequency', prop: 'retroactiveDate',
label: '回溯日期', label: '回溯日期',
}, { }, {
type: 'select', type: 'select',
prop: 'paymentFrequency', prop: 'initialPaymentMethod',
label: '首期付款方式', label: '首期付款方式',
dictType: 'csf_ap_first_issue'
}, { }, {
type: 'select', type: 'select',
prop: 'paymentFrequency', prop: 'renewalPaymentMethod',
label: '续期付款方式', label: '续期付款方式',
dictType: 'csf_ap_first_issue'
}, { }, {
type: 'select', type: 'select',
prop: 'paymentFrequency', prop: 'dividendDistributionMethod',
label: '红利付款方式', label: '红利付款方式',
dictType: 'csf_ap_dividend'
}, },
]) ])
// 首期保费 // 首期保费
const firstPremiumTableData = ref([]) const firstPremiumTableData = ref([])
const firstPremiumFormRef = ref() const firstPremiumFormRef = ref()
...@@ -359,7 +615,7 @@ const firstPremiumFormData = ref({}) ...@@ -359,7 +615,7 @@ const firstPremiumFormData = ref({})
const firstPremiumFormConfig = ref([ const firstPremiumFormConfig = ref([
{ {
type: 'input', type: 'input',
prop: 'amount', prop: 'initialPremium',
label: '首期保费', label: '首期保费',
inputType: 'decimal', inputType: 'decimal',
decimalDigits: 2, decimalDigits: 2,
...@@ -369,7 +625,7 @@ const firstPremiumFormConfig = ref([ ...@@ -369,7 +625,7 @@ const firstPremiumFormConfig = ref([
] ]
}, { }, {
type: 'input', type: 'input',
prop: 'amount', prop: 'policyLevy',
label: '保单征费', label: '保单征费',
inputType: 'decimal', inputType: 'decimal',
decimalDigits: 2, decimalDigits: 2,
...@@ -379,12 +635,12 @@ const firstPremiumFormConfig = ref([ ...@@ -379,12 +635,12 @@ const firstPremiumFormConfig = ref([
] ]
}, { }, {
type: 'select', type: 'select',
prop: 'currency', prop: 'initialPaymentMethod',
label: '首期缴费方式', label: '首期缴费方式',
dictType: 'bx_currency_type' dictType: 'csf_ap_first_issue'
}, { }, {
type: 'input', type: 'input',
prop: 'amount', prop: 'initialPremiumPaid',
label: '首期已缴保费', label: '首期已缴保费',
inputType: 'decimal', inputType: 'decimal',
decimalDigits: 2, decimalDigits: 2,
...@@ -394,7 +650,7 @@ const firstPremiumFormConfig = ref([ ...@@ -394,7 +650,7 @@ const firstPremiumFormConfig = ref([
] ]
}, { }, {
type: 'input', type: 'input',
prop: 'amount', prop: 'initialPremiumDue',
label: '首期待缴保费', label: '首期待缴保费',
inputType: 'decimal', inputType: 'decimal',
decimalDigits: 2, decimalDigits: 2,
...@@ -404,7 +660,7 @@ const firstPremiumFormConfig = ref([ ...@@ -404,7 +660,7 @@ const firstPremiumFormConfig = ref([
] ]
}, { }, {
type: 'input', type: 'input',
prop: 'amount', prop: 'initialPremiumTotal',
label: '首期缴费总额', label: '首期缴费总额',
inputType: 'decimal', inputType: 'decimal',
decimalDigits: 2, decimalDigits: 2,
...@@ -412,21 +668,29 @@ const firstPremiumFormConfig = ref([ ...@@ -412,21 +668,29 @@ const firstPremiumFormConfig = ref([
{ required: true, message: '请输入金额', trigger: 'blur' }, { required: true, message: '请输入金额', trigger: 'blur' },
{ pattern: /^\d+(\.\d{1,2})?$/, message: '最多两位小数', trigger: 'blur' } { pattern: /^\d+(\.\d{1,2})?$/, message: '最多两位小数', trigger: 'blur' }
] ]
}, { },
type: 'select', // {
prop: 'statusList', // type: 'select',
label: '缴费状态', // prop: 'initialPaymentStatus',
multiple: true, // label: '缴费状态',
dictType: 'csf_fortune_status' // multiple: true,
}, { // dictType: 'reconciliation_status'
// },
{
type: 'date', type: 'date',
prop: 'commissionDate', prop: 'latestPaymentDate',
label: '最晚缴费日', label: '最晚缴费日',
placeholder: '请选择' placeholder: '请选择'
}, { }, {
type: 'textarea', type: 'input',
prop: 'policyNo', prop: 'initialPremiumDiscount',
label: '首期保费优惠' label: '首期保费优惠金额',
inputType: 'decimal',
decimalDigits: 2,
rules: [
{ required: true, message: '请输入金额', trigger: 'blur' },
{ pattern: /^\d+(\.\d{1,2})?$/, message: '最多两位小数', trigger: 'blur' }
]
}, },
]) ])
...@@ -435,25 +699,25 @@ const postalFormRef = ref(null) ...@@ -435,25 +699,25 @@ const postalFormRef = ref(null)
const postalFormData = ref({}) const postalFormData = ref({})
const postalFormConfig = ref([ const postalFormConfig = ref([
{ {
type: 'input', type: 'select',
prop: 'postalCode', prop: 'mailingMethod',
label: '寄送方式', label: '寄送方式',
dictType: 'csf_mailing_method'
}, { }, {
type: 'input', type: 'input',
prop: 'address', prop: 'deliveryNo',
label: '快递单号', label: '快递单号',
visible: (formData) => formData.mailingMethod == '2'
}, { }, {
type: 'date', type: 'date',
prop: 'address', prop: 'brokerSignDate',
label: '我司签收日', label: '我司签收日',
rules: [ rules: [
{ required: true, message: '请输入我司签收日', trigger: 'blur' } { required: true, message: '请输入我司签收日', trigger: 'blur' }
] ]
}, { }, {
type: 'date', type: 'date',
prop: 'address', prop: 'customerSignDate',
label: '客户签收日', label: '客户签收日',
rules: [ rules: [
{ required: true, message: '请输入客户签收日', trigger: 'blur' } { required: true, message: '请输入客户签收日', trigger: 'blur' }
...@@ -465,49 +729,59 @@ const postalFormConfig = ref([ ...@@ -465,49 +729,59 @@ const postalFormConfig = ref([
// 关联记录 // 关联记录
const relatedTableData = ref([]) const relatedTableData = ref([])
const attachmentTableData = ref([]) const attachmentTableData = ref([])
// ===== Props & Emits ===== const attachmentTableColumns = ref([
{ prop: 'originalName', label: '文件名', sortable: true, width: '150', formatter: (row) => row.originalName || '-' },
const props = defineProps({ { prop: 'fileType', label: '文件类型', sortable: true, width: '150', formatter: (row) => row.fileType || '-' },
modelValue: { { prop: 'createTime', label: '上传时间', sortable: true, width: '150', formatter: (row) => row.createTime || '-' },
type: Object, { prop: 'creatorName', label: '上传人', sortable: true, width: '150', formatter: (row) => row.creatorName || '-' },
default: () => ({}) ])
}
})
const emit = defineEmits(['update:modelValue', 'submit', 'cancel'])
// ===== 本地响应式数据 ===== // ===== 本地响应式数据 =====
const defaultFormData = () => ({ const defaultFormData = () => ({})
signDate: '',
agentId: '',
location: '',
policyNo: '',
effectiveDate: '',
endDate: '',
underwritingDate: '',
insurer: '',
insuranceType: '',
productName: '',
paymentFrequency: '',
additionalPlans: []
})
// ✅ 使用 ref 而不是 reactive // ✅ 使用 ref 而不是 reactive
const localData = ref(defaultFormData()) const localData = ref(defaultFormData())
// ✅ 只在挂载时初始化一次(避免 watch 形成闭环) // ✅ 只在挂载时初始化一次(避免 watch 形成闭环)
onMounted(() => { onMounted(async () => {
if (props.modelValue) { if (props.modelValue) {
console.log('🚀 父组件 props.modelValue 挂载时:', props.modelValue)
// 深拷贝 + 合并默认值,防止缺失字段 // 深拷贝 + 合并默认值,防止缺失字段
localData.value = { ...defaultFormData(), ...props.modelValue } localData.value = { ...defaultFormData(), ...props.modelValue }
} }
}) try {
getPolicyfollowDetail()
} catch (error) {
console.error('字典加载失败', error)
} finally {
}
// 应用查看模式到基本信息表格
basicInfoFormConfig.value = basicInfoFormConfig ? applyViewMode(basicInfoFormConfig.value, props.mode) : []
policyInfoFormConfig.value = policyInfoFormConfig ? applyViewMode(policyInfoFormConfig.value, props.mode) : []
})
watch(
() => ({
...basicInfoFormData.value,
...policyInfoFormData.value,
...firstPremiumFormData.value,
...postalFormData.value,
...basicPlanFormData.value
}),
() => {
console.log('========父组件监听basicPlanFormData的变化', basicPlanFormData.value)
tabDirty.value.basic = true
},
{ deep: true }
)
// ✅ 监听 localData 变化,emit 出去(用于 v-model) // ✅ 监听 localData 变化,emit 出去(用于 v-model)
watch( watch(
() => localData.value, () => localData.value,
(newVal) => { (newVal) => {
console.log('========父组件监测的localData:', newVal)
emit('update:modelValue', newVal) emit('update:modelValue', newVal)
}, },
{ deep: true } { deep: true }
...@@ -515,32 +789,80 @@ watch( ...@@ -515,32 +789,80 @@ watch(
// ===== 表单引用 & 验证规则 ===== // ===== 表单引用 & 验证规则 =====
const formRef = ref() const formRef = ref()
const activeTab = ref('basic') const activeTab = ref('basic')
// ========================
// ===== 添加附加险计划方法 ===== // ✅ 新增:切换前确认
const addAdditionalPlan = () => { // ========================
localData.additionalPlans.push({ const handleBeforeLeave = async (newTabName, oldTabName) => {
product: '', console.log('切换前确认', newTabName, oldTabName)
frequency: '', if (tabDirty.value[oldTabName]) {
term: '', try {
periods: '', await ElMessageBox.confirm(
currency: '', `“${getTabLabel(oldTabName)}” 有未保存的内容,确定要切换吗?`,
premium: '', '提示',
fee: '' {
}) confirmButtonText: '继续切换',
cancelButtonText: '取消',
type: 'warning'
}
)
return true // 允许切换
} catch {
return false // 阻止切换
}
}
return true // 无未保存内容,允许切换
} }
const getTabLabel = (name) => {
const deleteRow = (index) => { const labels = {
localData.additionalPlans.splice(index, 1) basic: '基础信息',
productPlan: '产品计划',
firstPayment: '首期缴费',
introducer: '介绍人',
postal: '邮寄信息',
related: '关联记录',
attachment: '附件'
}
return labels[name] || name
} }
const handleTabClick = (tab) => { const handleTabClick = (tab) => {
console.log('切换到 Tab:', tab.name) if (tab.props.name === 'firstPayment') {
firstPremiumFormConfig.value = firstPremiumFormConfig ? applyViewMode(firstPremiumFormConfig.value, props.mode) : []
if (!policyInfoFormData.value.policyNo) return
getPremiumReconciliationList(policyInfoFormData.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) : []
}
} }
const handleSubmit = () => { const handleSubmit = () => {
formRef.value?.validate((valid) => { formRef.value?.validate((valid) => {
if (valid) { if (valid) {
emit('submit', { ...localData }) console.log('提交数据', introducerTableData.value)
savTabList.value.push(activeTab.value)
if (activeTab.value === 'postal') {
emit('submit', { ...postalFormData.value, activeTab: activeTab.value })
} else if (activeTab.value === 'firstPayment') {
emit('submit', { ...firstPremiumFormData.value, activeTab: activeTab.value })
} else if (activeTab.value === 'attachment') {
emit('submit', { activeTab: activeTab.value })
} else if (activeTab.value === 'introducer') {
emit('submit', { ...introducerTableData.value, activeTab: activeTab.value })
} else if (activeTab.value === 'basic') {
emit('submit', { ...basicInfoFormData.value, activeTab: activeTab.value, ...policyInfoFormData.value })
} else if (activeTab.value === 'productPlan') {
emit('submit', { ...basicPlanFormData.value, activeTab: activeTab.value, ...localData.additionalPlans })
}
} }
}) })
} }
...@@ -563,6 +885,155 @@ watch( ...@@ -563,6 +885,155 @@ watch(
}, },
{ deep: true } { deep: true }
) )
// 查询新单跟进详情
const getPolicyfollowDetail = () => {
if (!props.policyBizId) {
return
}
getPolicyfollow(props.policyBizId).then(res => {
if (res.code === 200) {
console.log('====父组件在赋值basicPlanFormData前', basicPlanFormData.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) };
introducerTableData.value = res.data.brokerList || []
console.log('====父组件在赋值basicPlanFormData后', basicPlanFormData.value)
}
})
}
// 查询附件列表
const getAttachmentListDetail = (policyBizId) => {
if (!policyBizId) {
return
}
const params = {
objectBizId: policyBizId,
pageNo: 1,
pageSize: 100,
}
uploadOssFileList(params).then(res => {
if (res.code === 200) {
attachmentTableData.value = res.data || []
console.log('attachmentTableData', res.data)
}
})
}
// 附件上传方法
const handleUploadEnd = (code) => {
if (code === 200) {
ElMessage.success('上传文件成功')
fileUploadDialogFlag.value = false
files.value = ''
getAttachmentListDetail(props.policyBizId)
} else {
ElMessage.error('上传文件失败')
}
}
// 组装表单数据
const transformToFormData = (apiData, formConfig) => {
const formData = {};
formConfig.forEach(item => {
const { prop, dictType } = item;
let value = null;
// 特殊映射(按需添加)
switch (prop) {
case 'signLocation':
value = apiData.signLocation;
break;
default:
value = apiData[prop];
}
// 处理字典字段:如果配置了 dictType,且值是 number,转为 string
if (dictType && typeof value === 'number') {
value = String(value);
}
formData[prop] = value ?? null;
});
return formData;
};
// 获取首期保费对账列表
const getPremiumReconciliationList = (policyNo) => {
const params = {
policyNo: policyNo,
pageNo: 1,
pageSize: 100,
}
premiumReconciliationList(params).then(res => {
if (res.code === 200) {
firstPremiumTableData.value = res.data.records || []
}
})
}
// 获取关联流程记录
const getRelationRecord = (fnaBizId) => {
getProcessDetail(fnaBizId).then(res => {
if (res.code === 200) {
relatedTableData.value = [res.data] || []
console.log('relationRecordData', res.data)
}
})
}
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 handleSelectChange = async (prop, value, item, type) => {
await nextTick()
// console.log('======子组件选择选项后,父组件接收的值 :', basicPlanFormData.value)
if (type === 'basicPlan') {
const params = {
tenantBizId: userStore.projectInfo.tenantBizId,
projectBizId: userStore.projectInfo.projectBizId,
fieldBizId: 'field_olk1qZe81qHHKXbw',
fieldValueBizId: 'field_value_uOfJH5ucA2YwJpbn',
insuranceCompanyBizIdList: [basicPlanFormData.value.insuranceCompanyBizId],
categoryCodeList: [basicPlanFormData.value.insuranceCategoryCode],
pageNo: 1,
pageSize: 100,
}
console.log('====父组件拿到值,去调用产品列表查询接口', params)
getProductLists(params)
}
}
// 获取产品列表
const getProductLists = (params) => {
getProductList(params).then(res => {
if (res.code === 200) {
console.log('productList', res.data.records || [])
// productList.value = res.data.records || []
}
})
}
defineExpose({
getPolicyfollowDetail
})
</script> </script>
<style scoped> <style scoped>
...@@ -601,13 +1072,14 @@ watch( ...@@ -601,13 +1072,14 @@ watch(
background-color: #007bff; background-color: #007bff;
border-radius: 5px; border-radius: 5px;
} }
.section h5 { .section h5 {
margin: 0 0 15px 0; margin: 0;
font-size: 14px; font-size: 14px;
line-height: 1; line-height: 1;
position: relative; position: relative;
background-color: rgba(0, 119, 238, 0.05); background-color: rgba(0, 119, 238, 0.05);
color:#383838; color: #383838;
padding: 15px 10px; padding: 15px 10px;
border-radius: 4px; border-radius: 4px;
} }
......
...@@ -1226,20 +1226,20 @@ getUserList() ...@@ -1226,20 +1226,20 @@ getUserList()
} }
/* 选项卡头部:消除默认样式,重构凹陷效果 */ /* 选项卡头部:消除默认样式,重构凹陷效果 */
::v-deep .el-tabs__header { :deep(.el-tabs__header ) {
line-height: 1; /* 重置行高,避免影响高度计算 */ line-height: 1; /* 重置行高,避免影响高度计算 */
padding: 0; /* 清除默认内边距 */ padding: 0; /* 清除默认内边距 */
} }
/* 选项卡导航容器:核心凹陷逻辑 */ /* 选项卡导航容器:核心凹陷逻辑 */
::v-deep .el-tabs__nav { :deep(.el-tabs__nav ) {
display: flex; display: flex;
margin: 0; /* 清除默认 margin */ margin: 0; /* 清除默认 margin */
border-bottom: 1px solid var(--border-color); /* 底部边框 */ border-bottom: 1px solid var(--border-color); /* 底部边框 */
} }
/* 选项卡 item 基础样式 */ /* 选项卡 item 基础样式 */
::v-deep .el-tabs__item { :deep(.el-tabs__item ) {
position: relative; position: relative;
padding: 12px 24px; padding: 12px 24px;
margin-right: 0; /* 清除默认间距 */ margin-right: 0; /* 清除默认间距 */
...@@ -1253,7 +1253,7 @@ getUserList() ...@@ -1253,7 +1253,7 @@ getUserList()
} }
/* 激活态:凹陷 + 高亮 */ /* 激活态:凹陷 + 高亮 */
::v-deep .el-tabs__item.is-active { :deep(.el-tabs__item.is-active ) {
color: var(--active-tab-color); color: var(--active-tab-color);
background-color: var(--active-tab-bg); background-color: var(--active-tab-bg);
/* 上、左、右边框 + 底部无边框,模拟“凹陷” */ /* 上、左、右边框 + 底部无边框,模拟“凹陷” */
...@@ -1266,23 +1266,23 @@ getUserList() ...@@ -1266,23 +1266,23 @@ getUserList()
} }
/* 未激活态:悬浮效果 */ /* 未激活态:悬浮效果 */
::v-deep .el-tabs__item:not(.is-active):hover { :deep(.el-tabs__item:not(.is-active):hover ) {
color: #409eff; color: #409eff;
background-color: #eaf2ff; background-color: #eaf2ff;
} }
/* 左侧边框处理(仅第一个 tab 左侧圆角) */ /* 左侧边框处理(仅第一个 tab 左侧圆角) */
::v-deep .el-tabs__item:first-child { :deep(.el-tabs__item:first-child ) {
border-top-left-radius: var(--radius); border-top-left-radius: var(--radius);
} }
/* 右侧边框处理(仅最后一个 tab 右侧圆角) */ /* 右侧边框处理(仅最后一个 tab 右侧圆角) */
::v-deep .el-tabs__item:last-child { :deep(.el-tabs__item:last-child ) {
border-top-right-radius: var(--radius); border-top-right-radius: var(--radius);
} }
/* 激活态:覆盖父容器的底部边框,实现“凹陷” */ /* 激活态:覆盖父容器的底部边框,实现“凹陷” */
::v-deep .el-tabs__item.is-active::before { :deep(.el-tabs__item.is-active::before ) {
content: ""; content: "";
position: absolute; position: absolute;
bottom: -1px; /* 覆盖父容器的 border-bottom */ bottom: -1px; /* 覆盖父容器的 border-bottom */
...@@ -1293,7 +1293,7 @@ getUserList() ...@@ -1293,7 +1293,7 @@ getUserList()
} }
/* 内容区域:和选项卡无缝衔接 */ /* 内容区域:和选项卡无缝衔接 */
::v-deep .el-tabs__content { :deep(.el-tabs__content ) {
padding: 20px; padding: 20px;
background-color: #fff; background-color: #fff;
border: 1px solid var(--border-color); border: 1px solid var(--border-color);
......
...@@ -1617,20 +1617,20 @@ getList() ...@@ -1617,20 +1617,20 @@ getList()
} }
/* 选项卡头部:消除默认样式,重构凹陷效果 */ /* 选项卡头部:消除默认样式,重构凹陷效果 */
::v-deep .el-tabs__header { :deep(.el-tabs__header ) {
line-height: 1; /* 重置行高,避免影响高度计算 */ line-height: 1; /* 重置行高,避免影响高度计算 */
padding: 0; /* 清除默认内边距 */ padding: 0; /* 清除默认内边距 */
} }
/* 选项卡导航容器:核心凹陷逻辑 */ /* 选项卡导航容器:核心凹陷逻辑 */
::v-deep .el-tabs__nav { :deep(.el-tabs__nav ) {
display: flex; display: flex;
margin: 0; /* 清除默认 margin */ margin: 0; /* 清除默认 margin */
border-bottom: 1px solid var(--border-color); /* 底部边框 */ border-bottom: 1px solid var(--border-color); /* 底部边框 */
} }
/* 选项卡 item 基础样式 */ /* 选项卡 item 基础样式 */
::v-deep .el-tabs__item { :deep(.el-tabs__item ) {
position: relative; position: relative;
padding: 12px 24px; padding: 12px 24px;
margin-right: 0; /* 清除默认间距 */ margin-right: 0; /* 清除默认间距 */
...@@ -1644,7 +1644,7 @@ getList() ...@@ -1644,7 +1644,7 @@ getList()
} }
/* 激活态:凹陷 + 高亮 */ /* 激活态:凹陷 + 高亮 */
::v-deep .el-tabs__item.is-active { :deep(.el-tabs__item.is-active ) {
color: var(--active-tab-color); color: var(--active-tab-color);
background-color: var(--active-tab-bg); background-color: var(--active-tab-bg);
/* 上、左、右边框 + 底部无边框,模拟“凹陷” */ /* 上、左、右边框 + 底部无边框,模拟“凹陷” */
...@@ -1657,23 +1657,23 @@ getList() ...@@ -1657,23 +1657,23 @@ getList()
} }
/* 未激活态:悬浮效果 */ /* 未激活态:悬浮效果 */
::v-deep .el-tabs__item:not(.is-active):hover { :deep(.el-tabs__item:not(.is-active):hover ) {
color: #409eff; color: #409eff;
background-color: #eaf2ff; background-color: #eaf2ff;
} }
/* 左侧边框处理(仅第一个 tab 左侧圆角) */ /* 左侧边框处理(仅第一个 tab 左侧圆角) */
::v-deep .el-tabs__item:first-child { :deep(.el-tabs__item:first-child ) {
border-top-left-radius: var(--radius); border-top-left-radius: var(--radius);
} }
/* 右侧边框处理(仅最后一个 tab 右侧圆角) */ /* 右侧边框处理(仅最后一个 tab 右侧圆角) */
::v-deep .el-tabs__item:last-child { :deep(.el-tabs__item:last-child ) {
border-top-right-radius: var(--radius); border-top-right-radius: var(--radius);
} }
/* 激活态:覆盖父容器的底部边框,实现“凹陷” */ /* 激活态:覆盖父容器的底部边框,实现“凹陷” */
::v-deep .el-tabs__item.is-active::before { :deep(.el-tabs__item.is-active::before ) {
content: ''; content: '';
position: absolute; position: absolute;
bottom: -1px; /* 覆盖父容器的 border-bottom */ bottom: -1px; /* 覆盖父容器的 border-bottom */
...@@ -1684,7 +1684,7 @@ getList() ...@@ -1684,7 +1684,7 @@ getList()
} }
/* 内容区域:和选项卡无缝衔接 */ /* 内容区域:和选项卡无缝衔接 */
::v-deep .el-tabs__content { :deep(.el-tabs__content ) {
padding: 20px; padding: 20px;
background-color: #fff; background-color: #fff;
border: 1px solid var(--border-color); border: 1px solid var(--border-color);
......
...@@ -375,10 +375,10 @@ const gap = ref('10px') ...@@ -375,10 +375,10 @@ const gap = ref('10px')
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
::v-deep .el-card { :deep(.el-card ) {
border: none !important; border: none !important;
} }
::v-deep .el-carousel__button { :deep(.el-carousel__button ) {
width: 10px !important ; width: 10px !important ;
} }
.app-container { .app-container {
......
...@@ -62,6 +62,16 @@ export default defineConfig(({ mode, command }) => { ...@@ -62,6 +62,16 @@ export default defineConfig(({ mode, command }) => {
// proxyReq.setHeader('host', '139.224.145.34:9002') // proxyReq.setHeader('host', '139.224.145.34:9002')
// }) // })
// } // }
},'/product': {
target: 'http://139.224.145.34:9002',
changeOrigin: true,
secure: false,
// 如果后端需要 host 头
// configure: (proxy, options) => {
// proxy.on('proxyReq', (proxyReq, req, res) => {
// proxyReq.setHeader('host', '139.224.145.34:9002')
// })
// }
}, },
// springdoc proxy // springdoc proxy
'^/v3/api-docs/(.*)': { '^/v3/api-docs/(.*)': {
......
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