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