Commit 0480ece5 by zhangxingmin

Merge remote-tracking branch 'origin/dev' into prod

# Conflicts:
#	nginx.conf
parents b6642184 f11f221a
...@@ -25,6 +25,7 @@ ...@@ -25,6 +25,7 @@
"axios": "1.9.0", "axios": "1.9.0",
"clipboard": "2.0.11", "clipboard": "2.0.11",
"dayjs": "^1.11.18", "dayjs": "^1.11.18",
"decimal.js": "^10.6.0",
"echarts": "5.6.0", "echarts": "5.6.0",
"element-plus": "^2.13.5", "element-plus": "^2.13.5",
"file-saver": "^2.0.5", "file-saver": "^2.0.5",
...@@ -36,6 +37,7 @@ ...@@ -36,6 +37,7 @@
"lodash": "^4.18.1", "lodash": "^4.18.1",
"nprogress": "0.2.0", "nprogress": "0.2.0",
"p-limit": "^7.3.0", "p-limit": "^7.3.0",
"pdfjs-dist": "^5.7.284",
"pinia": "3.0.2", "pinia": "3.0.2",
"spark-md5": "^3.0.2", "spark-md5": "^3.0.2",
"splitpanes": "^4.0.4", "splitpanes": "^4.0.4",
......
This source diff could not be displayed because it is too large. You can view the blob instead.
...@@ -360,7 +360,7 @@ export function updateCommissionRecord(data) { ...@@ -360,7 +360,7 @@ export function updateCommissionRecord(data) {
}) })
} }
// 新增出账检核记录 // 出账检核---新增出账检核记录
export function addCheckRecordaddBatch(data) { export function addCheckRecordaddBatch(data) {
return request({ return request({
url: '/csf/api/fortune/addBatch', url: '/csf/api/fortune/addBatch',
...@@ -490,7 +490,6 @@ export function billBatchSave(data) { ...@@ -490,7 +490,6 @@ export function billBatchSave(data) {
}) })
} }
// 拆分出账查询-计算目标金额 // 拆分出账查询-计算目标金额
export function billCalculateToAmount(data) { export function billCalculateToAmount(data) {
return request({ return request({
...@@ -499,11 +498,68 @@ export function billCalculateToAmount(data) { ...@@ -499,11 +498,68 @@ export function billCalculateToAmount(data) {
data: data data: data
}) })
} }
// 应收款管理--明细列表
export function newQueryCommissionExpectedByPage(data) {
return request({
url: 'csf/api/CommissionExpected/queryCommissionExpectedByPage/new',
method: 'post',
data: data
})
}
// 应收款管理修改应收记录状态 // 应收款管理修改应收记录状态
export function CommissionExpectedChangeStatus(data) { export function CommissionExpectedChangeStatus(data) {
return request({ return request({
url: 'csf/api/CommissionExpected/change_status', url: 'csf/api/CommissionExpected/edit/status',
method: 'put',
data: data
})
}
// 出账检核--分期出账
export function billSplitApi(data) {
return request({
url: 'csf/api/fortune/split',
method: 'post',
data: data
})
}
//用保单查询结算汇率
export function commissionExchangeRateApi(data) {
return request({
url: 'csf/api/commission/commission_exchange_rate',
method: 'post',
data: data
})
}
//出账检核--设置出账年月(单个)
export function actualPayoutDateApi(data) {
return request({
url: 'csf/api/fortune/edit/actual_payout_date',
method: 'post',
data: data
})
}
//入账检核--修改入账状态
export function editStatusApi(data) {
return request({
url: 'csf/api/commission/edit/status',
method: 'put',
data: data
})
}
//出账检核--修改结算汇率
export function editExchangeRateApi(data) {
return request({
url: 'csf/api/fortune/edit/exchange_rate',
method: 'post',
data: data
})
}
// 批量设置出账年月实
export function batchActualPayoutDate(data) {
return request({
url: '/csf/api/fortune/edit/actual_payout_date/batch',
method: 'post', method: 'post',
data: data data: data
}) })
......
...@@ -139,3 +139,10 @@ export function addSinglePremiumRemittance(data) { ...@@ -139,3 +139,10 @@ export function addSinglePremiumRemittance(data) {
data: data data: data
}) })
} }
// 保单详情
export function policyDetail(policyNo) {
return request({
url: `/csf/api/policy_follow/detail?policyNo=${policyNo}`,
method: 'get'
})
}
...@@ -256,3 +256,4 @@ export function getProductList(data) { ...@@ -256,3 +256,4 @@ export function getProductList(data) {
data: data data: data
}) })
} }
<!-- filePreview -->
<template>
<el-dialog
v-model="dialogVisible"
:title="fileTitle"
width="90%"
:close-on-click-modal="false"
destroy-on-close
@close="handleClose"
>
<div class="preview-container">
<!-- 图片预览(增加 loading 和错误处理) -->
<div
v-if="fileType === 'image'"
class="preview-image-wrapper"
v-loading="imageLoading"
element-loading-text="图片加载中..."
>
<div v-if="imageError" class="image-error">
<el-icon :size="48"><Picture /></el-icon>
<p>图片加载失败</p>
<el-button size="small" type="primary" @click="retryImage">重试</el-button>
</div>
<img
v-else
:src="fileUrl"
:key="imageKey"
class="preview-image"
alt="预览图片"
@load="onImageLoad"
@error="onImageError"
/>
</div>
<!-- PDF 预览(滚动多页) -->
<div v-else-if="fileType === 'pdf'" class="pdf-viewer">
<div class="pdf-toolbar">
<el-button-group>
<el-button size="small" @click="zoomOut">
<el-icon><ZoomOut /></el-icon> 缩小
</el-button>
<el-button size="small" @click="zoomIn">
<el-icon><ZoomIn /></el-icon> 放大
</el-button>
</el-button-group>
<span class="page-info">{{ pdfTotalPages }}</span>
</div>
<div
class="pdf-scroll-wrapper"
v-loading="pdfLoading"
element-loading-text="正在渲染页面..."
>
<div ref="pdfScrollContainer" class="pdf-scroll-container"></div>
</div>
</div>
<!-- 不支持预览的文件类型 -->
<div v-else class="preview-unsupported">
<el-icon :size="48" color="#909399"><Document /></el-icon>
<p>暂不支持预览此类型文件</p>
<el-button type="primary" @click="dialogVisible = false">关闭</el-button>
</div>
</div>
</el-dialog>
</template>
<script setup>
import { ref, computed, watch, nextTick, onBeforeUnmount, shallowRef } from 'vue'
import { ElMessage } from 'element-plus'
import { ZoomIn, ZoomOut, Document, Picture } from '@element-plus/icons-vue'
import * as PDFJS from 'pdfjs-dist'
// 配置 PDF.js worker
PDFJS.GlobalWorkerOptions.workerSrc = '/js/pdf.worker.min.mjs' //在public/js/pdf.worker.min.mjs
// ---------- 内部状态 ----------
const dialogVisible = ref(false) // 弹窗显示状态
const fileUrl = ref('') // 当前预览的文件 URL
const fileName = ref('') // 当前预览的文件名
// 图片预览相关状态
const imageLoading = ref(false) // 图片加载中
const imageError = ref(false) // 图片加载失败
const imageKey = ref(0) // 强制重新加载图片的 key
// PDF 相关
const pdfDoc = shallowRef(null) // pdf 文档实例
const pdfScrollContainer = ref(null) // 滚动容器
const pdfTotalPages = ref(0) // 总页数
const pdfScale = ref(1.2) // 缩放比例
const pdfLoading = ref(false) // 加载状态
let pdfLoadingCanceled = false // 取消标志
let isRendering = false // 防止重复渲染
// ---------- 辅助函数 ----------
// 根据文件名获取文件类型(image / pdf / unsupported)
const getFileType = name => {
if (!name) return 'unsupported'
const ext = name.split('.').pop().toLowerCase()
if (['jpg', 'jpeg', 'png', 'webp', 'gif', 'bmp', 'svg'].includes(ext)) {
return 'image'
} else if (ext === 'pdf') {
return 'pdf'
}
return 'unsupported'
}
const fileType = computed(() => getFileType(fileName.value))
const fileTitle = computed(() => fileName.value || '文件预览')
// ---------- 图片预览事件处理 ----------
const onImageLoad = () => {
imageLoading.value = false
imageError.value = false
}
const onImageError = () => {
imageLoading.value = false
imageError.value = true
ElMessage.error('图片加载失败,请检查文件链接')
}
const retryImage = () => {
imageError.value = false
imageLoading.value = true
imageKey.value++ // 改变 key 强制重新加载图片
}
// ---------- 清理 PDF 资源 ----------
const clearPdf = async () => {
pdfLoadingCanceled = true // 取消任何进行中的渲染
pdfLoading.value = false
isRendering = false
if (pdfDoc.value) {
try {
await pdfDoc.value.destroy()
} catch (e) {
// 忽略销毁错误
}
pdfDoc.value = null
}
pdfTotalPages.value = 0
pdfScale.value = 1.2
if (pdfScrollContainer.value) {
pdfScrollContainer.value.innerHTML = ''
}
}
// ---------- 渲染 PDF 所有页面(滚动多页)----------
const renderAllPages = async scale => {
if (!pdfDoc.value || pdfLoadingCanceled || isRendering) return
isRendering = true
pdfLoading.value = true
const container = pdfScrollContainer.value
if (!container) {
isRendering = false
pdfLoading.value = false
return
}
// 清空之前的 canvas
container.innerHTML = ''
try {
const promises = []
for (let pageNum = 1; pageNum <= pdfTotalPages.value; pageNum++) {
if (pdfLoadingCanceled) break
const page = await pdfDoc.value.getPage(pageNum)
const viewport = page.getViewport({ scale })
// 创建 canvas 元素
const canvas = document.createElement('canvas')
canvas.className = 'pdf-page-canvas'
const context = canvas.getContext('2d')
canvas.height = viewport.height
canvas.width = viewport.width
canvas.style.marginBottom = '16px'
canvas.style.boxShadow = '0 2px 8px rgba(0,0,0,0.1)'
container.appendChild(canvas)
// 渲染当前页
const renderTask = page.render({
canvasContext: context,
viewport: viewport
})
promises.push(renderTask.promise)
}
await Promise.all(promises)
} catch (err) {
if (!pdfLoadingCanceled) {
console.error('渲染 PDF 失败', err)
ElMessage.error('渲染 PDF 页面失败')
}
} finally {
isRendering = false
pdfLoading.value = false
}
}
// 加载 PDF 文档
const loadPdf = async url => {
pdfLoadingCanceled = false
pdfLoading.value = true
try {
// 清理旧文档
if (pdfDoc.value) {
await pdfDoc.value.destroy().catch(() => {})
pdfDoc.value = null
}
if (pdfScrollContainer.value) {
pdfScrollContainer.value.innerHTML = ''
}
const loadingTask = PDFJS.getDocument(url)
pdfDoc.value = await loadingTask.promise
if (pdfLoadingCanceled) {
if (pdfDoc.value) pdfDoc.value.destroy()
pdfLoading.value = false
return
}
pdfTotalPages.value = pdfDoc.value.numPages
await renderAllPages(pdfScale.value)
pdfLoading.value = false
} catch (err) {
if (!pdfLoadingCanceled) {
console.error('PDF 加载失败', err)
ElMessage.error('PDF 文件加载失败,请检查文件链接')
dialogVisible.value = false
}
pdfLoading.value = false
}
}
// ---------- 缩放控制 ----------
const zoomIn = () => {
if (fileType.value !== 'pdf') return
pdfScale.value = Math.min(pdfScale.value + 0.2, 3.0)
renderAllPages(pdfScale.value)
}
const zoomOut = () => {
if (fileType.value !== 'pdf') return
pdfScale.value = Math.max(pdfScale.value - 0.2, 0.5)
renderAllPages(pdfScale.value)
}
// ---------- 关闭弹窗 ----------
const handleClose = async () => {
// 重置图片相关状态
imageLoading.value = false
imageError.value = false
await clearPdf()
dialogVisible.value = false
}
// ---------- 对外暴露的方法 ----------
/**
* 打开预览弹窗
* @param {Object} file - 文件对象,需包含 url 和 name 属性(或 originalName)
* @example
* open({ url: 'https://example.com/file.pdf', name: '文档.pdf' })
*/
const open = file => {
if (!file || !file.url) {
ElMessage.warning('无法预览:文件地址不存在')
return
}
// 重置状态
fileUrl.value = file.url
fileName.value = file.name || file.originalName || '文件'
// 根据文件类型重置对应的加载状态
const type = getFileType(fileName.value)
if (type === 'image') {
// 重置图片状态并强制重新加载
imageLoading.value = true
imageError.value = false
imageKey.value++ // 改变 key 确保重新加载图片
} else if (type === 'pdf') {
// PDF 相关状态重置已在 loadPdf 中处理
pdfLoadingCanceled = false
}
dialogVisible.value = true
// 根据文件类型处理
nextTick(() => {
if (fileType.value === 'pdf') {
loadPdf(fileUrl.value)
}
// 图片无需额外处理,通过 imageKey 变化自动重新加载
})
}
// 组件卸载时清理 PDF 资源
onBeforeUnmount(() => {
if (pdfDoc.value) {
pdfDoc.value.destroy().catch(() => {})
pdfDoc.value = null
}
pdfLoadingCanceled = true
})
defineExpose({ open })
</script>
<style lang="scss" scoped>
.preview-container {
display: flex;
justify-content: center;
align-items: center;
min-height: 400px;
}
.preview-image-wrapper {
width: 100%;
position: relative;
min-height: 200px;
text-align: center;
}
.preview-image {
max-width: 100%;
max-height: 70vh;
object-fit: contain;
}
.image-error {
text-align: center;
padding: 40px;
color: #909399;
p {
margin: 16px 0;
}
}
.preview-unsupported {
text-align: center;
padding: 40px;
p {
margin: 16px 0;
color: #909399;
}
}
.pdf-viewer {
display: flex;
flex-direction: column;
height: 70vh;
}
.pdf-toolbar {
display: flex;
justify-content: center;
align-items: center;
gap: 16px;
padding: 8px 0;
border-bottom: 1px solid #e4e7ed;
margin-bottom: 12px;
.page-info {
font-size: 14px;
color: #606266;
}
}
.pdf-scroll-wrapper {
width: 100%;
flex: 1;
overflow-y: auto;
border: 1px solid #ebeef5;
border-radius: 4px;
background: #f9fafc;
}
.pdf-scroll-container {
display: flex;
flex-direction: column;
align-items: center;
padding: 16px;
}
.pdf-page-canvas {
display: block;
max-width: 100%;
height: auto;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
margin-bottom: 16px;
}
</style>
<!-- searchForm -->
<template> <template>
<el-form <div>
ref="formRef" <el-form
:model="localModel" ref="formRef"
:rules="formRules" :model="localModel"
label-width="auto" :rules="formRules"
v-bind="$attrs" label-width="auto"
:validate-on-rule-change="false" 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-row :gutter="20">
<el-form-item <el-col v-for="item in visibleConfig" :key="item.prop" :span="item.span || 6">
:label="item.label" <el-form-item
:prop="item.prop" :label="item.label"
:class="{ 'search-form-item': isSearch }" :prop="item.prop"
:label-position="item.labelPosition || 'top'" :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)"
:formatter="value => {
if (!item.inputType || !value) return value;
const str = String(value);
// 如果包含小数点
if (str.indexOf('.') > -1) {
const [int, dec] = str.split('.');
// 只格式化整数部分,保留小数部分原样
return int.replace(/\B(?=(\d{3})+(?!\d))/g, ',') + '.' + dec;
}
// 如果没有小数点,直接格式化
return str.replace(/\B(?=(\d{3})+(?!\d))/g, ',');
}"
:parser="value => value.replace(/\$\s?|(,*)/g, '')"
/>
<!-- 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 <!-- Input -->
v-for="opt in getSelectOptions(item)" <el-input
:key="opt.value" v-if="item.type === 'input'"
:label="opt.label" v-model="localModel[item.prop]"
:value="opt.value" :placeholder="item.placeholder || `请输入${item.label}`"
:clearable="true"
:disabled="item.disabled"
@input="val => handleNumberInput(val, item)"
@change="val => handleModelChange(val, item)"
:formatter="
value => {
if (!item.inputType || !value) return value
const str = String(value)
// 如果包含小数点
if (str.indexOf('.') > -1) {
const [int, dec] = str.split('.')
// 只格式化整数部分,保留小数部分原样
return int.replace(/\B(?=(\d{3})+(?!\d))/g, ',') + '.' + dec
}
// 如果没有小数点,直接格式化
return str.replace(/\B(?=(\d{3})+(?!\d))/g, ',')
}
"
:parser="value => value.replace(/\$\s?|(,*)/g, '')"
/> />
</el-select>
<!-- Date --> <!-- Select (支持 dictType / api / options) -->
<el-date-picker <!-- <el-select
v-else-if="item.type === 'date'" v-else-if="item.type === 'select'"
v-model="localModel[item.prop]" v-model="localModel[item.prop]"
type="date" :multiple="!!item.multiple"
:placeholder="`选择${item.label}`" :placeholder="item.placeholder || `请选择${item.label}`"
:disabled="item.disabled" :clearable="true"
:value-format="item.valueFormat || 'YYYY-MM-DD'" filterable
style="width: 100%" :disabled="item.disabled"
:disabled-date="getDisabledDateFn(item)" :loading="remoteLoading[item.prop] || false"
@change="val => handleModelChange(val, item)" @change="val => handleModelChange(val, item)"
/> @focus="() => loadRemoteOptions(item)"
<!-- Month --> @filter-change="keyword => handleFilterChange(keyword, item)"
<el-date-picker >
v-else-if="item.type === 'month'" <el-option
v-model="localModel[item.prop]" v-for="opt in getSelectOptions(item)"
type="month" :key="opt.value"
:placeholder="`选择${item.label}`" :label="opt.label"
:value-format="item.valueFormat || 'YYYY-MM'" :value="opt.value"
style="width: 100%" />
:disabled="item.disabled" </el-select> -->
:disabled-date="getDisabledDateFn(item)" <!-- @focus="() => loadRemoteOptions(item, '')" -->
@change="val => handleModelChange(val, item)" <el-select
/> v-else-if="item.type === 'select'"
:ref="
<!-- Daterange --> el => {
<el-date-picker if (el) selectRefs[item.prop] = el
v-else-if="item.type === 'daterange'" }
v-model="localModel[item.prop]" "
type="daterange" v-model="localModel[item.prop]"
range-separator="至" :multiple="!!item.multiple"
:start-placeholder="item.startPlaceholder || '开始日期'" :placeholder="item.placeholder || `请选择${item.label}`"
:end-placeholder="item.endPlaceholder || '结束日期'" :clearable="true"
:value-format="item.valueFormat || 'YYYY-MM-DD'" filterable
:disabled="item.disabled" :remote="!!item.api"
:disabled-date="getDisabledDateFn(item)" :remote-method="query => handleRemoteSearch(query, item)"
style="width: 100%" :loading="remoteLoading[item.prop] || false"
@change="val => handleModelChange(val, item)" :disabled="item.disabled"
/> @change="val => handleModelChange(val, item)"
<!-- Monthrange (新增) --> >
<el-date-picker <el-option
v-else-if="item.type === 'monthrange'" v-for="opt in getSelectOptions(item)"
v-model="localModel[item.prop]" :key="opt.value"
type="monthrange" :label="opt.label"
range-separator="至" :value="opt.value"
:start-placeholder="item.startPlaceholder || '开始月份'" />
:end-placeholder="item.endPlaceholder || '结束月份'" </el-select>
:value-format="item.valueFormat || 'YYYY-MM'" <!-- Date -->
:disabled="item.disabled" <el-date-picker
:disabled-date="getDisabledDateFn(item)" v-else-if="item.type === 'date'"
style="width: 100%" v-model="localModel[item.prop]"
@change="val => handleModelChange(val, item)" type="date"
/> :placeholder="`选择${item.label}`"
:disabled="item.disabled"
<!-- Checkbox Group --> :value-format="item.valueFormat || 'YYYY-MM-DD'"
<el-checkbox-group style="width: 100%"
v-else-if="item.type === 'checkbox-group'" :disabled-date="getDisabledDateFn(item)"
v-model="localModel[item.prop]" @change="val => handleModelChange(val, item)"
:disabled="item.disabled" />
@change="val => handleModelChange(val, item)" <!-- Month -->
> <el-date-picker
<el-checkbox v-for="opt in getSelectOptions(item)" :key="opt.value" :label="opt.value"> v-else-if="item.type === 'month'"
{{ opt.label }} v-model="localModel[item.prop]"
</el-checkbox> type="month"
</el-checkbox-group> :placeholder="`选择${item.label}`"
<!-- textarea --> :value-format="item.valueFormat || 'YYYY-MM'"
<el-input style="width: 100%"
v-else-if="item.type === 'textarea'" :disabled="item.disabled"
v-model="localModel[item.prop]" :disabled-date="getDisabledDateFn(item)"
style="width: 240px" @change="val => handleModelChange(val, item)"
autosize />
:disabled="item.disabled"
type="textarea" <!-- Daterange -->
placeholder="请输入" <el-date-picker
:clearable="true" v-else-if="item.type === 'daterange'"
@change="val => handleModelChange(val, item)" v-model="localModel[item.prop]"
/> type="daterange"
<!-- Upload 回显值得时候数据格式至少是[{url: '必须要传', name: 'name不是必须的根据需要传值'}]--> range-separator="至"
<el-upload :start-placeholder="item.startPlaceholder || '开始日期'"
v-else-if="item.type === 'upload'" :end-placeholder="item.endPlaceholder || '结束日期'"
v-model:file-list="localModel[item.prop]" :value-format="item.valueFormat || 'YYYY-MM-DD'"
: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" :disabled="item.disabled"
:disabled-date="getDisabledDateFn(item)"
style="width: 100%"
@change="val => handleModelChange(val, item)"
/>
<!-- Monthrange (新增) -->
<el-date-picker
v-else-if="item.type === 'monthrange'"
v-model="localModel[item.prop]"
type="monthrange"
range-separator="至"
:start-placeholder="item.startPlaceholder || '开始月份'"
:end-placeholder="item.endPlaceholder || '结束月份'"
:value-format="item.valueFormat || 'YYYY-MM'"
: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
</el-button> v-for="opt in getSelectOptions(item)"
<template #tip v-if="item.maxSize || item.accept"> :key="opt.value"
<div class="el-upload__tip"> :label="opt.value"
<span v-if="item.maxSize">大小不超过 {{ formatFileSize(item.maxSize) }}</span> >
<span v-if="item.accept">支持格式:{{ item.accept }}</span> {{ opt.label }}
</el-checkbox>
</el-checkbox-group>
<!-- textarea -->
<el-input
v-else-if="item.type === 'textarea'"
v-model="localModel[item.prop]"
style="width: 240px"
autosize
:disabled="item.disabled"
type="textarea"
placeholder="请输入"
:clearable="true"
@change="val => handleModelChange(val, item)"
/>
<!-- Upload 回显值得时候数据格式至少是[{url: '必须要传', name: 'name不是必须的根据需要传值'}]-->
<template v-else-if="item.type === 'upload'">
<!-- 🔽 默认模式:完整使用 el-upload(含自带文件列表) -->
<el-upload
v-if="!item.customFileList"
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 !== false"
: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"
>
点击上传文件
</el-button>
<template #tip v-if="item.maxSize || item.accept">
<div class="el-upload__tip">
<span v-if="item.maxSize">大小不超过 {{ formatFileSize(item.maxSize) }}</span>
<span v-if="item.accept">支持格式:{{ item.accept }}</span>
</div>
</template>
</el-upload>
<!-- 🔽 自定义列表模式:只保留上传触发器,自己渲染文件列表 -->
<div v-else class="custom-upload-wrapper">
<el-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"
:disabled="item.disabled"
:auto-upload="true"
:show-file-list="false"
:on-exceed="handleExceed"
:before-upload="file => beforeUpload(file, item)"
:on-success="
(res, file, fileList) => handleUploadSuccess(res, file, fileList, item)
"
:on-error="(err, file, fileList) => handleUploadError(err, file, fileList, item)"
:on-remove="(file, fileList) => handleUploadRemove(file, fileList, item)"
>
<el-button type="primary" size="small" :disabled="item.disabled">
{{ item.customUploadButtonText || '点击上传文件' }}
</el-button>
<template #tip v-if="item.maxSize || item.accept">
<div class="el-upload__tip">
<span v-if="item.maxSize">大小不超过 {{ formatFileSize(item.maxSize) }}</span>
<span v-if="item.accept">支持格式:{{ item.accept }}</span>
</div>
</template>
</el-upload>
<!-- 自定义文件列表 -->
<div v-if="(localModel[item.prop] || []).length" class="custom-file-list">
<div
v-for="(file, idx) in localModel[item.prop]"
:key="file.uid || idx"
class="file-item"
>
<span class="file-name" :title="file.name">{{ file.name }}</span>
<div class="file-actions">
<el-button link type="primary" size="small" @click="previewFile(file, item)">
查看
</el-button>
<el-button link type="danger" size="small" @click="removeFile(file, item)">
删除
</el-button>
</div>
</div>
</div>
</div> </div>
</template> </template>
</el-upload> <span v-else>不支持的类型: {{ item.type }}</span>
<span v-else>不支持的类型: {{ item.type }}</span> </el-form-item>
</el-form-item> </el-col>
</el-col> </el-row>
</el-row> </el-form>
</el-form> <!-- 文件预览弹窗(页面内查看,不打开新窗口) -->
<el-dialog
v-model="previewDialogVisible"
:title="previewFileName"
width="70%"
:close-on-click-modal="false"
destroy-on-close
@close="previewUrl = ''"
>
<div class="preview-container">
<!-- 图片预览 -->
<div v-if="previewFileType === 'image'" class="preview-image-wrapper">
<img :src="previewUrl" class="preview-image" alt="预览图片" />
</div>
<!-- PDF 预览(使用 iframe 内嵌) -->
<iframe
v-else-if="previewFileType === 'pdf'"
:src="previewUrl"
class="preview-pdf"
frameborder="0"
></iframe>
<!-- 不支持预览的文件类型 -->
<div v-else-if="previewFileType === 'unsupported'" class="preview-unsupported">
<el-icon :size="48" color="#909399"><Document /></el-icon>
<p>暂不支持预览此类型文件</p>
<el-button type="primary" @click="previewDialogVisible = false"> 关闭 </el-button>
</div>
</div>
</el-dialog>
<!-- 文件预览弹窗 -->
<FilePreview ref="filePreviewRef" />
</div>
</template> </template>
<script setup> <script setup>
import { ref, watch, onMounted, nextTick, computed } from 'vue' import { ref, watch, onMounted, nextTick, computed } from 'vue'
import { ElMessage } from 'element-plus' import { ElMessage } from 'element-plus'
import { Upload } from '@element-plus/icons-vue' import { Upload, Document } from '@element-plus/icons-vue'
// 🔑 引入你的字典方法 // 🔑 引入你的字典方法
import useDictStore from '@/store/modules/dict' import useDictStore from '@/store/modules/dict'
import { getDicts } from '@/api/system/dict/data' import { getDicts } from '@/api/system/dict/data'
import request from '@/utils/request' import request from '@/utils/request'
import FilePreview from '@/components/Preview/filePreview.vue'
import dayjs from 'dayjs' import dayjs from 'dayjs'
// ==================== 上传文件 ==================== import * as PDFJS from 'pdfjs-dist'
PDFJS.GlobalWorkerOptions.workerSrc = '/js/pdf.worker.min.mjs'
// ==================== select ====================
const selectRefs = ref({})
// 新增远程搜索入口(带防抖)
let remoteSearchTimer = null
function handleRemoteSearch(query, item) {
if (!item.api) return
// 🔥 关键修改:如果 query 为空且当前已有选项数据,则不重新加载
if (query === '' && remoteOptions.value[item.prop] && remoteOptions.value[item.prop].length > 0) {
return
}
clearTimeout(remoteSearchTimer)
remoteSearchTimer = setTimeout(async () => {
await loadRemoteOptions(item, query)
}, item.debounceWait || 300)
}
// ==================== 文件预览弹窗 ====================
const filePreviewRef = ref(null)
const previewDialogVisible = ref(false)
const previewUrl = ref('')
const previewFileName = ref('')
const previewFileType = ref('') // 'image', 'pdf', 'unsupported'
function previewFile(file, item) {
// 确保传入对象包含 url 和 name 属性
filePreviewRef.value?.open({
url: file.url || file.response?.data?.url,
name: file.name
})
}
// 删除文件(复用原有删除逻辑)
function removeFile(file, item) {
const fileList = localModel.value[item.prop] || []
const newList = fileList.filter(f => f.uid !== file.uid)
handleUploadRemove(file, newList, item) // 调用原有的删除处理函数
}
// 文件大小格式化 // 文件大小格式化
function formatFileSize(bytes) { function formatFileSize(bytes) {
if (bytes === 0) return '0 Bytes' if (bytes === 0) return '0 Bytes'
...@@ -200,6 +363,7 @@ function formatFileSize(bytes) { ...@@ -200,6 +363,7 @@ function formatFileSize(bytes) {
const i = Math.floor(Math.log(bytes) / Math.log(k)) const i = Math.floor(Math.log(bytes) / Math.log(k))
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i] return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]
} }
function beforeUpload(file, item) { function beforeUpload(file, item) {
// 检查文件大小 // 检查文件大小
if (item.maxSize && file.size > item.maxSize) { if (item.maxSize && file.size > item.maxSize) {
...@@ -219,10 +383,8 @@ function beforeUpload(file, item) { ...@@ -219,10 +383,8 @@ function beforeUpload(file, item) {
return true return true
} }
function handleUploadSuccess(response, file, fileList, item) {
// 假设你的后端返回格式:{ code: 200, data: { url: '...', name: 'xxx.pdf' } }
// 你可以通过 item.responseMap 自定义映射,这里先用通用方式
function handleUploadSuccess(response, file, fileList, item) {
const data = response.data || response const data = response.data || response
const url = data.url || data.fileUrl || data.path const url = data.url || data.fileUrl || data.path
const name = data.name || data.fileName || file.name const name = data.name || data.fileName || file.name
...@@ -242,9 +404,8 @@ function handleUploadSuccess(response, file, fileList, item) { ...@@ -242,9 +404,8 @@ function handleUploadSuccess(response, file, fileList, item) {
// 触发 model 更新 // 触发 model 更新
handleModelChange([...fileList], item) handleModelChange([...fileList], item)
ElMessage.success(`文件 ${file.name} 上传成功`) ElMessage.success(`文件 ${file.name} 上传成功`)
// console.log('上传成功', item)
} }
function handleExceed(files, fileList) { function handleExceed(files, fileList) {
ElMessage.warning('超出文件数量限制') ElMessage.warning('超出文件数量限制')
} }
...@@ -258,6 +419,7 @@ function handleUploadRemove(file, fileList, item) { ...@@ -258,6 +419,7 @@ function handleUploadRemove(file, fileList, item) {
// 用户删除文件时,同步更新 model // 用户删除文件时,同步更新 model
handleModelChange([...fileList], item) handleModelChange([...fileList], item)
} }
// ==================== 工具函数:深拷贝配置(保留函数) ==================== // ==================== 工具函数:深拷贝配置(保留函数) ====================
function deepCloneConfig(obj) { function deepCloneConfig(obj) {
if (obj === null || typeof obj !== 'object') return obj if (obj === null || typeof obj !== 'object') return obj
...@@ -271,6 +433,7 @@ function deepCloneConfig(obj) { ...@@ -271,6 +433,7 @@ function deepCloneConfig(obj) {
} }
return cloned return cloned
} }
function parseToDate(str) { function parseToDate(str) {
if (!str) return null if (!str) return null
if (str === 'today') { if (str === 'today') {
...@@ -285,6 +448,7 @@ function parseToDate(str) { ...@@ -285,6 +448,7 @@ function parseToDate(str) {
} }
return null return null
} }
// ==================== 生成 disabledDate 函数 ==================== // ==================== 生成 disabledDate 函数 ====================
function getDisabledDateFn(item) { function getDisabledDateFn(item) {
const { minDate, maxDate } = item const { minDate, maxDate } = item
...@@ -329,6 +493,7 @@ function getDisabledDateFn(item) { ...@@ -329,6 +493,7 @@ function getDisabledDateFn(item) {
return false return false
} }
} }
// ==================== Props & Emits ==================== // ==================== Props & Emits ====================
const props = defineProps({ const props = defineProps({
modelValue: { type: Object, default: () => ({}) }, modelValue: { type: Object, default: () => ({}) },
...@@ -336,13 +501,17 @@ const props = defineProps({ ...@@ -336,13 +501,17 @@ const props = defineProps({
isSearch: { type: Boolean, default: false } isSearch: { type: Boolean, default: false }
}) })
const emit = defineEmits(['update:modelValue', 'update', 'selectChange', 'uploadSuccess']) const emit = defineEmits([
'update:modelValue',
'update',
'selectChange',
'uploadSuccess',
'inputChange'
])
// ==================== Refs ==================== // ==================== Refs ====================
const formRef = ref(null) const formRef = ref(null)
// 使用 shallowRef 避免深层响应式(表单通常扁平)
const localModel = ref({ ...props.modelValue }) const localModel = ref({ ...props.modelValue })
// 记录哪些字段的字典已加载
const dictLoaded = ref(new Set()) const dictLoaded = ref(new Set())
const internalConfig = ref([]) const internalConfig = ref([])
const remoteOptions = ref({}) // { prop: [options] } const remoteOptions = ref({}) // { prop: [options] }
...@@ -366,7 +535,34 @@ const formRules = computed(() => { ...@@ -366,7 +535,34 @@ const formRules = computed(() => {
return rules return rules
}) })
// 1. 外部 modelValue 变化时,安全同步(仅当内容不同时) // 同步 extra 字段:根据当前选中的 option 填充 extra 字段(不触发外部 emit)
function syncExtraFieldsForProp(prop, value) {
const item = internalConfig.value.find(i => i.prop === prop)
if (!item || item.type !== 'select' || !item.onChangeExtraFields) return false
const options = getSelectOptions(item)
const selectedOption = options.find(opt => String(opt.value) === String(value))
if (selectedOption && selectedOption.raw) {
let needUpdate = false
const newModel = { ...localModel.value }
for (const [targetProp, sourceKey] of Object.entries(item.onChangeExtraFields)) {
const extraValue = selectedOption.raw[sourceKey]
if (newModel[targetProp] !== extraValue) {
newModel[targetProp] = extraValue
needUpdate = true
}
}
if (needUpdate) {
localModel.value = newModel
console.log('====================================')
console.log('localModel.value', localModel.value)
console.log('====================================')
return true
}
}
return false
}
// 监听 config 变化(支持动态 config) // 监听 config 变化(支持动态 config)
watch( watch(
() => props.config, () => props.config,
...@@ -379,37 +575,34 @@ watch( ...@@ -379,37 +575,34 @@ watch(
const initialModel = {} const initialModel = {}
for (const item of internalConfig.value) { for (const item of internalConfig.value) {
const key = item.prop const key = item.prop
// 优先用父传值,否则用默认值
if (props.modelValue?.[key] !== undefined) { if (props.modelValue?.[key] !== undefined) {
initialModel[key] = props.modelValue[key] initialModel[key] = props.modelValue[key]
} else if (item.multiple || ['checkbox-group', 'daterange','monthrange'].includes(item.type)) { } else if (
item.multiple ||
['checkbox-group', 'daterange', 'monthrange'].includes(item.type)
) {
initialModel[key] = item.defaultValue ?? [] initialModel[key] = item.defaultValue ?? []
} else { } else {
initialModel[key] = item.defaultValue ?? '' initialModel[key] = item.defaultValue ?? ''
} }
} }
// ✅ 在这里同步 modelValue(包括 extra 字段)
localModel.value = syncModelFromProps(props.modelValue, internalConfig.value) localModel.value = syncModelFromProps(props.modelValue, internalConfig.value)
console.log('子组件监测config变化', localModel.value)
}, },
{ immediate: true } { immediate: true }
) )
// console.log('🚀 子组件 props.modelValue 初始值:', props.modelValue)
// 监听 modelValue(用于后续外部更新) // 监听 modelValue(用于后续外部更新)
watch( watch(
() => props.modelValue, () => props.modelValue,
newVal => { newVal => {
if (!newVal || !internalConfig.value) return if (!newVal || !internalConfig.value) return
// ✅ 同样使用 sync 函数
localModel.value = syncModelFromProps(newVal, internalConfig.value) localModel.value = syncModelFromProps(newVal, internalConfig.value)
console.log('子组件监测 modelValue 变化:', localModel.value)
}, },
{ deep: true } { deep: true }
) )
// 提取同步逻辑 // 从 props 同步模型数据
function syncModelFromProps(newModelValue, newConfig) { function syncModelFromProps(newModelValue, newConfig) {
if (!newModelValue || !newConfig) return {} if (!newModelValue || !newConfig) return {}
...@@ -419,7 +612,7 @@ function syncModelFromProps(newModelValue, newConfig) { ...@@ -419,7 +612,7 @@ function syncModelFromProps(newModelValue, newConfig) {
const key = item.prop const key = item.prop
if (newModelValue.hasOwnProperty(key)) { if (newModelValue.hasOwnProperty(key)) {
synced[key] = newModelValue[key] synced[key] = newModelValue[key]
} else if (item.multiple || ['checkbox-group', 'daterange','monthrange'].includes(item.type)) { } else if (item.multiple || ['checkbox-group', 'daterange', 'monthrange'].includes(item.type)) {
synced[key] = item.defaultValue ?? [] synced[key] = item.defaultValue ?? []
} else { } else {
synced[key] = item.defaultValue ?? '' synced[key] = item.defaultValue ?? ''
...@@ -431,22 +624,17 @@ function syncModelFromProps(newModelValue, newConfig) { ...@@ -431,22 +624,17 @@ function syncModelFromProps(newModelValue, newConfig) {
if (!extraMap || typeof extraMap !== 'object') continue if (!extraMap || typeof extraMap !== 'object') continue
const prop = item.prop const prop = item.prop
const idValue = newModelValue[prop] // e.g. 2 const idValue = newModelValue[prop]
let sourceObj = null let sourceObj = null
// 情况1: 如果 newModelValue[prop] 本身就是对象 → 直接用(兼容旧逻辑)
if (idValue && typeof idValue === 'object') { if (idValue && typeof idValue === 'object') {
sourceObj = idValue sourceObj = idValue
} } else if (Array.isArray(item.options) && idValue !== undefined && idValue !== null) {
// 情况2: 如果是 primitive(string/number),且有 options → 反查
else if (Array.isArray(item.options) && idValue !== undefined && idValue !== null) {
// 默认用 option.value 匹配,可配置 valueKey
const valueKey = item.valueKey || 'value' const valueKey = item.valueKey || 'value'
sourceObj = item.options.find(opt => opt[valueKey] === idValue) sourceObj = item.options.find(opt => opt[valueKey] === idValue)
} }
// 如果找到了 sourceObj,就提取 extra 字段
if (sourceObj && typeof sourceObj === 'object') { if (sourceObj && typeof sourceObj === 'object') {
for (const [targetKey, subPath] of Object.entries(extraMap)) { for (const [targetKey, subPath] of Object.entries(extraMap)) {
const val = getNestedValue(sourceObj, subPath) const val = getNestedValue(sourceObj, subPath)
...@@ -463,9 +651,7 @@ function syncModelFromProps(newModelValue, newConfig) { ...@@ -463,9 +651,7 @@ function syncModelFromProps(newModelValue, newConfig) {
const extraMap = item.onChangeExtraFields const extraMap = item.onChangeExtraFields
if (!extraMap || typeof extraMap !== 'object') continue if (!extraMap || typeof extraMap !== 'object') continue
// 如果 newModelValue 中没有 sourceField,说明没有重新计算
if (newModelValue[sourceField] === undefined) { if (newModelValue[sourceField] === undefined) {
// 那么保留 localModel 中对应的 extra 字段
for (const [targetKey, subPath] of Object.entries(extraMap)) { for (const [targetKey, subPath] of Object.entries(extraMap)) {
if (localModel.value.hasOwnProperty(targetKey)) { if (localModel.value.hasOwnProperty(targetKey)) {
synced[targetKey] = localModel.value[targetKey] synced[targetKey] = localModel.value[targetKey]
...@@ -476,77 +662,73 @@ function syncModelFromProps(newModelValue, newConfig) { ...@@ -476,77 +662,73 @@ function syncModelFromProps(newModelValue, newConfig) {
// 4. 保留 newModelValue 中已有的 extra 字段和其他额外字段 // 4. 保留 newModelValue 中已有的 extra 字段和其他额外字段
for (const key in newModelValue) { for (const key in newModelValue) {
// 如果已经同步过了(比如主字段或第2步写入的extra),跳过
if (synced.hasOwnProperty(key)) continue if (synced.hasOwnProperty(key)) continue
// 判断是否是某个 extra 目标字段
const isExtraTarget = newConfig.some( const isExtraTarget = newConfig.some(
item => item.onChangeExtraFields && item.onChangeExtraFields.hasOwnProperty(key) item => item.onChangeExtraFields && item.onChangeExtraFields.hasOwnProperty(key)
) )
// 如果是 extra 字段,且 newModelValue 里有值 → 保留它!
if (isExtraTarget) { if (isExtraTarget) {
synced[key] = newModelValue[key] synced[key] = newModelValue[key]
} } else if (!newConfig.some(item => item.prop === key)) {
// 如果不是 extra,也不是主字段 → 也保留(兼容 hidden 字段等)
else if (!newConfig.some(item => item.prop === key)) {
synced[key] = newModelValue[key] synced[key] = newModelValue[key]
} }
} }
console.log('🚀 子组件 进行modelvalue处理:', synced)
return synced return synced
} }
function getNestedValue(obj, path) { function getNestedValue(obj, path) {
return path.split('.').reduce((current, key) => current?.[key], obj) return path.split('.').reduce((current, key) => current?.[key], obj)
} }
// 当字典加载完成时,触发同步
function markDictLoaded(prop) { function markDictLoaded(prop) {
dictLoaded.value.add(prop) dictLoaded.value.add(prop)
// 尝试同步该字段
if (props.modelValue?.[prop] !== undefined) { if (props.modelValue?.[prop] !== undefined) {
localModel.value[prop] = props.modelValue[prop] localModel.value[prop] = props.modelValue[prop]
} }
} }
// 2. 用户操作导致 localModel 变化时,emit(防抖可选)
// 用户操作导致 localModel 变化时,emit
function handleModelChange(value, item) { function handleModelChange(value, item) {
console.group('用户操作导致 localModel 变化时,emit(防抖可选)')
const newModel = { ...localModel.value, [item.prop]: value } const newModel = { ...localModel.value, [item.prop]: value }
if (item?.type === 'select' && item.onChangeExtraFields) { if (item?.type === 'select' && item.onChangeExtraFields) {
const options = getSelectOptions(item) const options = getSelectOptions(item)
// console.log('可用 options:', options) const opt = options.find(o => String(o.value) === String(value))
const opt = options.find(o => o.value === value)
// console.log('匹配的 opt:', opt)
if (opt?.raw) { if (opt?.raw) {
for (const [targetProp, sourceKey] of Object.entries(item.onChangeExtraFields)) { for (const [targetProp, sourceKey] of Object.entries(item.onChangeExtraFields)) {
const extraValue = opt.raw[sourceKey] newModel[targetProp] = opt.raw[sourceKey]
newModel[targetProp] = extraValue
// console.log(`✅ 设置 ${targetProp} =`, extraValue)
} }
} }
} }
localModel.value = newModel localModel.value = newModel
// console.log('子组件用户操作后,modelvalue值==', newModel) // 🔥 新增:多选远程 select,选中后清空搜索关键词
if (item.type === 'select' && item.multiple && item.api && selectRefs.value[item.prop]) {
const selectInstance = selectRefs.value[item.prop]
// 获取 el-select 内部的 input 元素
const inputElement = selectInstance.$el?.querySelector('input')
if (inputElement && inputElement.value) {
inputElement.value = ''
// 触发 input 事件,让组件内部同步状态
inputElement.dispatchEvent(new Event('input', { bubbles: true }))
loadRemoteOptions(item, '')
}
}
nextTick(() => { nextTick(() => {
if (!isEqualShallow(props.modelValue, newModel)) { if (!isEqualShallow(props.modelValue, newModel)) {
// console.log('如果新旧值不一样,反馈给父组件', newModel)
emit('update:modelValue', newModel) emit('update:modelValue', newModel)
} else {
console.log('🚫 跳过 emit:认为相等')
} }
}) })
if (item.type === 'select') { if (item.type === 'select') {
// console.log('如果是select类型,反馈给父组件', item.prop, value, item)
emit('selectChange', item.prop, value, item) emit('selectChange', item.prop, value, item)
} else if (item.type == 'upload') { } else if (item.type == 'upload') {
// 传给父组件最新的上传值newModel
emit('uploadSuccess', item.prop, newModel) emit('uploadSuccess', item.prop, newModel)
} else if (item.type == 'input') {
emit('inputChange', item.prop, value, item)
} }
console.groupEnd()
} }
// 辅助函数:浅比较两个对象
function isEqualShallow(a, b) { function isEqualShallow(a, b) {
const keysA = Object.keys(a) const keysA = Object.keys(a)
const keysB = Object.keys(b) const keysB = Object.keys(b)
...@@ -576,84 +758,34 @@ async function loadDictOptions(dictType) { ...@@ -576,84 +758,34 @@ async function loadDictOptions(dictType) {
dictStore.setDict(dictType, options) dictStore.setDict(dictType, options)
return options return options
} catch (err) { } catch (err) {
// console.error(`加载字典 ${dictType} 失败`, err)
ElMessage.error(`字典 ${dictType} 加载失败`) ElMessage.error(`字典 ${dictType} 加载失败`)
return [] return []
} }
} }
// ==================== 初始化 ====================
onMounted(async () => {
internalConfig.value = deepCloneConfig(props.config)
const initialData = {}
const dictTypePromises = []
const apiPromises = [] // ← 新增:收集 api 加载 promise
for (const item of internalConfig.value) {
const key = item.prop
if (localModel.value[key] == null) {
if (item.multiple) {
initialData[key] = item.defaultValue ?? []
} else if (['checkbox-group', 'daterange','monthrange'].includes(item.type)) {
initialData[key] = item.defaultValue ?? []
} else {
initialData[key] = item.defaultValue ?? ''
}
}
// 预加载 dictType
if (item.type === 'select' && item.dictType) {
dictTypePromises.push(
loadDictOptions(item.dictType).then(opts => {
remoteOptions.value[key] = opts
markDictLoaded(key) // ← 立即标记已加载
})
)
} // 预加载 api(远程接口)← 关键新增!
else if (item.type === 'select' && item.api) {
apiPromises.push(
loadRemoteOptionsForInit(item) // ← 专门用于初始化的加载函数
)
} else if (item.type === 'select' && item.options) {
remoteOptions.value[key] = [...item.options]
markDictLoaded(key)
}
// api 类型:延迟加载(focus 时)
}
if (Object.keys(initialData).length > 0) {
localModel.value = { ...localModel.value, ...initialData }
}
// 等待所有字典加载完成
await Promise.allSettled(dictTypePromises)
})
// ==================== 获取 select 选项 ==================== // ==================== 获取 select 选项 ====================
function getSelectOptions(item) { function getSelectOptions(item) {
const key = item.prop const key = item.prop
// 字典选项
if (item.dictType || item.api) { if (item.dictType || item.api) {
// 字典选项
return (remoteOptions.value[key] || []).map(opt => ({ return (remoteOptions.value[key] || []).map(opt => ({
value: opt.value, value: opt.value,
label: opt.label, label: opt.label,
raw: opt.raw // ← 必须存 raw raw: opt.raw
})) }))
} else if (item.options) { } else if (item.options) {
// 静态选项s
return item.options.map(opt => ({ return item.options.map(opt => ({
value: opt.value, value: opt.value,
label: opt.label, label: opt.label,
raw: opt.raw // 保留原始 raw: opt.raw
})) }))
} }
return [] return []
} }
// 初始化加载远程 API 选项(无搜索词)
async function loadRemoteOptionsForInit(item) { async function loadRemoteOptionsForInit(item) {
const { prop, api, requestParams } = item const { prop, api, requestParams } = item
try { try {
// 构造请求体:只传 requestParams,不传 keyword
const payload = { const payload = {
...(typeof requestParams === 'function' ? requestParams() : requestParams || {}) ...(typeof requestParams === 'function' ? requestParams() : requestParams || {})
} }
...@@ -669,54 +801,105 @@ async function loadRemoteOptionsForInit(item) { ...@@ -669,54 +801,105 @@ async function loadRemoteOptionsForInit(item) {
? item.transform(res) ? item.transform(res)
: res.data?.records || res.data || [] : res.data?.records || res.data || []
// 建议:统一转成字符串(或根据 item.valueType 判断)
const newOptions = list.map(i => ({ const newOptions = list.map(i => ({
value: String(i[item.valueKey || 'value']), // ← 强制转字符串 value: String(i[item.valueKey || 'value']),
label: i[item.labelKey || 'label'], label: i[item.labelKey || 'label'],
raw: i raw: i
})) }))
remoteOptions.value[prop] = newOptions remoteOptions.value[prop] = newOptions
markDictLoaded(prop) // ← 关键:标记已加载 markDictLoaded(prop)
// 同步 extra 字段
const currentVal = localModel.value[prop]
if (currentVal !== undefined && currentVal !== null && currentVal !== '') {
syncExtraFieldsForProp(prop, currentVal)
}
} catch (err) { } catch (err) {
ElMessage.error(`预加载 ${item.label} 失败`) ElMessage.error(`预加载 ${item.label} 失败`)
remoteOptions.value[prop] = [] remoteOptions.value[prop] = []
} }
} }
// ==================== 加载远程 API 选项(首次 focus 时加载,无搜索词) ====================
async function loadRemoteOptions(item) { // 加载远程 API 选项(focus 时调用,无搜索词)
const { prop, api, requestParams, keywordField, debounceWait, ...rest } = item // async function loadRemoteOptions(item) {
// const { prop, api, requestParams } = item
// if (!api) return
// // 如果已经有选项且不是强制刷新,可跳过;但为了保证初次加载,remoteOptions[prop] 为空时才加载
// if (remoteOptions.value[prop] && remoteOptions.value[prop].length > 0) return
// try {
// remoteLoading.value[prop] = true
// const payload = {
// ...(typeof requestParams === 'function' ? requestParams() : requestParams || {})
// }
// const res = await request({
// url: api,
// method: 'post',
// data: payload
// })
// const list =
// typeof item.transform === 'function'
// ? item.transform(res)
// : res.data?.records || res.data || []
// const newOptions = list.map(i => ({
// value: String(i[item.valueKey || 'value']),
// label: i[item.labelKey || 'label'],
// raw: i
// }))
// remoteOptions.value[prop] = newOptions
// markDictLoaded(prop)
// // 同步 extra 字段
// const currentVal = localModel.value[prop]
// if (currentVal !== undefined && currentVal !== null && currentVal !== '') {
// syncExtraFieldsForProp(prop, currentVal)
// }
// } catch (err) {
// ElMessage.error(`加载 ${item.label} 失败`)
// remoteOptions.value[prop] = []
// } finally {
// remoteLoading.value[prop] = false
// }
// }
// 修改原有的 loadRemoteOptions,使其支持关键词参数
async function loadRemoteOptions(item, keyword = '') {
const { prop, api, requestParams, keywordField = 'keyword' } = item
if (!api) return if (!api) return
try { try {
remoteLoading.value[prop] = true remoteLoading.value[prop] = true
// 构造请求体:合并 requestParams + 分页(默认第一页) let params = typeof requestParams === 'function' ? requestParams() : requestParams || {}
const payload = { if (keyword) {
...(typeof requestParams === 'function' ? requestParams() : requestParams || {}) params = { ...params, [keywordField]: keyword }
// 注意:此时无 keyword,所以不加 keywordField } else {
// 可选:重置分页参数为第一页
params = { ...params, pageNo: 1 }
} }
const res = await request({ const res = await request({ url: api, method: 'post', data: params })
url: api,
method: 'post', // ← 改为 POST
data: payload // ← 参数放 body
})
const list = const list =
typeof item.transform === 'function' typeof item.transform === 'function'
? item.transform(res) ? item.transform(res)
: res.data?.records || res.data || [] : res.data?.records || res.data || []
// 建议:统一转成字符串(或根据 item.valueType 判断) remoteOptions.value[prop] = list.map(i => ({
const newOptions = list.map(i => ({ value: String(i[item.valueKey || 'value']),
value: String(i[item.valueKey || 'value']), // ← 强制转字符串
label: i[item.labelKey || 'label'], label: i[item.labelKey || 'label'],
raw: i raw: i
})) }))
remoteOptions.value[prop] = newOptions
// ✅ 关键:标记该字段字典已加载
markDictLoaded(prop) markDictLoaded(prop)
const currentVal = localModel.value[prop]
if (currentVal !== undefined && currentVal !== null && currentVal !== '') {
syncExtraFieldsForProp(prop, currentVal)
}
} catch (err) { } catch (err) {
ElMessage.error(`加载 ${item.label} 失败`) ElMessage.error(`加载 ${item.label} 失败`)
remoteOptions.value[prop] = [] remoteOptions.value[prop] = []
...@@ -724,8 +907,7 @@ async function loadRemoteOptions(item) { ...@@ -724,8 +907,7 @@ async function loadRemoteOptions(item) {
remoteLoading.value[prop] = false remoteLoading.value[prop] = false
} }
} }
// 远程搜索(带关键词,防抖)
// ==================== 远程搜索(带关键词,防抖) ====================
let searchTimeout = null let searchTimeout = null
function handleFilterChange(keyword, item) { function handleFilterChange(keyword, item) {
const { prop, api, requestParams, keywordField = 'keyword', debounceWait = 300 } = item const { prop, api, requestParams, keywordField = 'keyword', debounceWait = 300 } = item
...@@ -736,16 +918,15 @@ function handleFilterChange(keyword, item) { ...@@ -736,16 +918,15 @@ function handleFilterChange(keyword, item) {
try { try {
remoteLoading.value[prop] = true remoteLoading.value[prop] = true
// 构造请求体:requestParams + 分页 + 动态关键词字段
const payload = { const payload = {
...(typeof requestParams === 'function' ? requestParams() : requestParams || {}), ...(typeof requestParams === 'function' ? requestParams() : requestParams || {}),
[keywordField]: keyword // ← 动态字段名,如 name / companyName [keywordField]: keyword
} }
const res = await request({ const res = await request({
url: api, url: api,
method: 'post', // ← POST 请求 method: 'post',
data: payload // ← body 传参 data: payload
}) })
const list = const list =
...@@ -754,9 +935,9 @@ function handleFilterChange(keyword, item) { ...@@ -754,9 +935,9 @@ function handleFilterChange(keyword, item) {
: res.data?.records || res.data || [] : res.data?.records || res.data || []
remoteOptions.value[prop] = list.map(i => ({ remoteOptions.value[prop] = list.map(i => ({
value: i[item.valueKey || 'value'], value: String(i[item.valueKey || 'value']),
label: i[item.labelKey || 'label'], label: i[item.labelKey || 'label'],
raw: i // ← 保存完整对象 raw: i
})) }))
} catch (err) { } catch (err) {
ElMessage.error(`搜索 ${item.label} 失败`) ElMessage.error(`搜索 ${item.label} 失败`)
...@@ -767,46 +948,6 @@ function handleFilterChange(keyword, item) { ...@@ -767,46 +948,6 @@ function handleFilterChange(keyword, item) {
} }
// ==================== 数字输入处理 ==================== // ==================== 数字输入处理 ====================
// function handleNumberInput(value, item) {
// const { inputType = 'text', decimalDigits = 2, prop } = item
// if (!prop) return
// let result = String(value ?? '').trim()
// if (inputType === 'integer') {
// // 只保留数字
// result = result.replace(/[^\d]/g, '')
// } else if (inputType === 'decimal') {
// // 1. 只保留数字和小数点
// result = result.replace(/[^\d.]/g, '')
// // 2. 去掉开头的小数点(不允许 ".5" → 改为 "0.5" 更好,但这里先简单处理)
// if (result.startsWith('.')) {
// result = '0.' + result.slice(1)
// }
// // 3. 保证最多一个小数点
// const parts = result.split('.')
// if (parts.length > 2) {
// result = parts[0] + '.' + parts.slice(1).join('')
// }
// // 4. 限制小数位数(但保留结尾的小数点!)
// if (result.includes('.')) {
// const [intPart, decPart] = result.split('.')
// // 如果小数部分超过限制,截断
// if (decPart.length > decimalDigits) {
// result = intPart + '.' + decPart.slice(0, decimalDigits)
// }
// // ✅ 不再删除结尾的 '.'
// }
// }
// // 防止重复赋值(可选优化)
// if (localModel.value[prop] !== result) {
// localModel.value = { ...localModel.value, [prop]: result }
// }
// }
function handleNumberInput(value, item) { function handleNumberInput(value, item) {
const { inputType = 'text', decimalDigits = 2, prop } = item const { inputType = 'text', decimalDigits = 2, prop } = item
if (!prop) return if (!prop) return
...@@ -814,82 +955,118 @@ function handleNumberInput(value, item) { ...@@ -814,82 +955,118 @@ function handleNumberInput(value, item) {
let result = String(value ?? '').trim() let result = String(value ?? '').trim()
if (inputType === 'integer') { if (inputType === 'integer') {
// 只保留数字和负号
result = result.replace(/[^-\d]/g, '') result = result.replace(/[^-\d]/g, '')
// 如果有多个负号或者负号不在开头,则移除多余的负号
if ((result.match(/-/g) || []).length > 1) { if ((result.match(/-/g) || []).length > 1) {
result = result.replace(/-/g, '').replace(/^/, '-') // 仅保留一个负号在最前面 result = result.replace(/-/g, '').replace(/^/, '-')
} }
} else if (inputType === 'decimalNumber') { } else if (inputType === 'decimalNumber') {
// 可以输入正数,负数,小数
// 1. 只保留数字、小数点和负号
result = result.replace(/[^-\d.]/g, '') result = result.replace(/[^-\d.]/g, '')
// 2. 处理负号:确保最多只有一个负号且必须在开头
if ((result.match(/-/g) || []).length > 1) { if ((result.match(/-/g) || []).length > 1) {
result = result.replace(/-/g, '') // 移除所有负号 result = result.replace(/-/g, '')
if (result.startsWith('-')) { if (result.startsWith('-')) {
result = '-' + result.slice(1) // 确保负号在最前面 result = '-' + result.slice(1)
} else { } else {
result = '-' + result // 如果原本没有负号但需要保留数值,可以省略此步骤 result = '-' + result
} }
} }
// 3. 去掉开头的小数点(不允许 ".5" → 改为 "0.5" 更好)
if (result.startsWith('.')) { if (result.startsWith('.')) {
result = '0' + result result = '0' + result
} else if (result.startsWith('-.')) { } else if (result.startsWith('-.')) {
result = '-0' + result.slice(2) result = '-0' + result.slice(2)
} }
// 4. 保证最多一个小数点
const parts = result.split('.') const parts = result.split('.')
if (parts.length > 2) { if (parts.length > 2) {
result = parts[0] + '.' + parts.slice(1).join('') result = parts[0] + '.' + parts.slice(1).join('')
} }
// 5. 限制小数位数(但保留结尾的小数点!)
if (result.includes('.')) { if (result.includes('.')) {
const [intPart, decPart] = result.split('.') const [intPart, decPart] = result.split('.')
// 如果小数部分超过限制,截断
if (decPart.length > decimalDigits) { if (decPart.length > decimalDigits) {
result = intPart + '.' + decPart.slice(0, decimalDigits) result = intPart + '.' + decPart.slice(0, decimalDigits)
} }
// ✅ 不再删除结尾的 '.'
} }
} else if (inputType === 'decimal') { } else if (inputType === 'decimal') {
// 可以输入正整数和小数
// 1. 只保留数字和小数点
result = result.replace(/[^\d.]/g, '') result = result.replace(/[^\d.]/g, '')
// 2. 去掉开头的小数点(不允许 ".5" → 改为 "0.5" 更好,但这里先简单处理)
if (result.startsWith('.')) { if (result.startsWith('.')) {
result = '0.' + result.slice(1) result = '0.' + result.slice(1)
} }
// 3. 保证最多一个小数点
const parts = result.split('.') const parts = result.split('.')
if (parts.length > 2) { if (parts.length > 2) {
result = parts[0] + '.' + parts.slice(1).join('') result = parts[0] + '.' + parts.slice(1).join('')
} }
// 4. 限制小数位数(但保留结尾的小数点!)
if (result.includes('.')) { if (result.includes('.')) {
const [intPart, decPart] = result.split('.') const [intPart, decPart] = result.split('.')
// 如果小数部分超过限制,截断
if (decPart.length > decimalDigits) { if (decPart.length > decimalDigits) {
result = intPart + '.' + decPart.slice(0, decimalDigits) result = intPart + '.' + decPart.slice(0, decimalDigits)
} }
// ✅ 不再删除结尾的 '.'
} }
} }
// 防止重复赋值(可选优化)
if (localModel.value[prop] !== result) { if (localModel.value[prop] !== result) {
localModel.value = { ...localModel.value, [prop]: result } localModel.value = { ...localModel.value, [prop]: result }
} }
} }
// 同步所有 select 的 extra 字段
function syncAllExtraFields() {
for (const item of internalConfig.value) {
if (item.type === 'select' && item.onChangeExtraFields) {
const currentVal = localModel.value[item.prop]
if (currentVal !== undefined && currentVal !== null && currentVal !== '') {
syncExtraFieldsForProp(item.prop, currentVal)
}
}
}
}
// ==================== 初始化 ====================
onMounted(async () => {
internalConfig.value = deepCloneConfig(props.config)
const initialData = {}
const dictTypePromises = []
const apiPromises = []
for (const item of internalConfig.value) {
const key = item.prop
if (localModel.value[key] == null) {
if (item.multiple) {
initialData[key] = item.defaultValue ?? []
} else if (['checkbox-group', 'daterange', 'monthrange'].includes(item.type)) {
initialData[key] = item.defaultValue ?? []
} else {
initialData[key] = item.defaultValue ?? ''
}
}
if (item.type === 'select' && item.dictType) {
dictTypePromises.push(
loadDictOptions(item.dictType).then(opts => {
remoteOptions.value[key] = opts
markDictLoaded(key)
})
)
} else if (item.type === 'select' && item.api) {
apiPromises.push(loadRemoteOptions(item, ''))
} else if (item.type === 'select' && item.options) {
remoteOptions.value[key] = [...item.options]
markDictLoaded(key)
}
}
if (Object.keys(initialData).length > 0) {
localModel.value = { ...localModel.value, ...initialData }
}
// 等待所有字典和远程选项加载完成
await Promise.allSettled([...dictTypePromises, ...apiPromises])
// 所有 select 选项加载完成后,为有 onChangeExtraFields 且有值的字段填充 extra 字段
for (const item of internalConfig.value) {
if (item.type === 'select' && item.onChangeExtraFields) {
const currentVal = localModel.value[item.prop]
if (currentVal !== undefined && currentVal !== null && currentVal !== '') {
syncExtraFieldsForProp(item.prop, currentVal)
}
}
}
})
// ==================== 暴露方法 ==================== // ==================== 暴露方法 ====================
defineExpose({ defineExpose({
...@@ -908,66 +1085,130 @@ defineExpose({ ...@@ -908,66 +1085,130 @@ defineExpose({
const resetData = {} const resetData = {}
internalConfig.value.forEach(item => { internalConfig.value.forEach(item => {
const key = item.prop const key = item.prop
if (['checkbox-group', 'daterange','monthrange'].includes(item.type) || item.multiple) { if (['checkbox-group', 'daterange', 'monthrange'].includes(item.type) || item.multiple) {
resetData[key] = item.defaultValue ?? [] resetData[key] = item.defaultValue ?? []
} else if (item.type === 'upload') { } else if (item.type === 'upload') {
resetData[key] = item.defaultValue ?? [] // upload 也是数组 resetData[key] = item.defaultValue ?? []
} else { } else {
resetData[key] = item.defaultValue ?? '' resetData[key] = item.defaultValue ?? ''
} }
}) })
localModel.value = { ...resetData } localModel.value = { ...resetData }
nextTick(() => formRef.value?.clearValidate()) // 关键:重置后重新同步额外字段
nextTick(() => {
syncAllExtraFields()
formRef.value?.clearValidate()
})
}, },
// ✅ 新增:强制刷新某个字段的远程选项 // resetForm() {
// const resetData = {}
// internalConfig.value.forEach(item => {
// const key = item.prop
// if (['checkbox-group', 'daterange', 'monthrange'].includes(item.type) || item.multiple) {
// resetData[key] = item.defaultValue ?? []
// } else if (item.type === 'upload') {
// resetData[key] = item.defaultValue ?? []
// } else {
// resetData[key] = item.defaultValue ?? ''
// }
// })
// localModel.value = { ...resetData }
// nextTick(() => formRef.value?.clearValidate())
// },
// async refreshRemoteOptions(targetProp) {
// const item = internalConfig.value.find(i => i.prop === targetProp)
// if (!item) {
// console.warn(`[SearchForm] 未找到 prop 为 ${targetProp} 的配置项`)
// return
// }
// if (item.type !== 'select' || !item.api) {
// console.warn(`[SearchForm] 字段 ${targetProp} 不是远程 Select 或没有 API`)
// return
// }
// remoteOptions.value[targetProp] = []
// remoteLoading.value[targetProp] = true
// try {
// await loadRemoteOptions(item)
// } catch (error) {
// console.error(`[SearchForm] ${targetProp} 加载失败`, error)
// throw error
// } finally {
// remoteLoading.value[targetProp] = false
// }
// }
async refreshRemoteOptions(targetProp) { async refreshRemoteOptions(targetProp) {
console.log(`[SearchForm] 收到刷新请求: ${targetProp}`)
// 1. 查找配置项
const item = internalConfig.value.find(i => i.prop === targetProp) const item = internalConfig.value.find(i => i.prop === targetProp)
if (!item || item.type !== 'select' || !item.api) return
if (!item) { await loadRemoteOptions(item, '')
console.warn(`[SearchForm] 未找到 prop 为 ${targetProp} 的配置项`)
return
}
if (item.type !== 'select' || !item.api) {
console.warn(`[SearchForm] 字段 ${targetProp} 不是远程 Select 或没有 API`)
return
}
console.log(`[SearchForm] 开始强制加载 ${targetProp} 的数据,API: ${item.api}`)
// 2. 关键:在调用前,先清空旧数据,防止子组件内部的 "已加载则跳过" 逻辑生效
// 如果你的 loadRemoteOptions 里有 "if (remoteOptions.value[prop]?.length > 0) return"
// 这里必须先清空
remoteOptions.value[targetProp] = []
remoteLoading.value[targetProp] = true // 手动开启 loading
try {
// 3. 调用内部加载函数
// 注意:直接调用 loadRemoteOptions,它会读取最新的 requestParams
await loadRemoteOptions(item)
console.log(`[SearchForm] ${targetProp} 加载完成`)
} catch (error) {
console.error(`[SearchForm] ${targetProp} 加载失败`, error)
throw error
} finally {
remoteLoading.value[targetProp] = false
}
} }
}) })
</script> </script>
<style scoped> <style scoped>
.preview-container {
display: flex;
justify-content: center;
align-items: center;
min-height: 400px;
}
.preview-image-wrapper {
text-align: center;
}
.preview-image {
max-width: 100%;
max-height: 70vh;
object-fit: contain;
}
.preview-pdf {
width: 100%;
height: 70vh;
}
.preview-unsupported {
text-align: center;
padding: 40px;
}
.preview-unsupported p {
margin: 16px 0;
color: #909399;
}
.custom-upload-wrapper {
width: 100%;
}
.custom-file-list {
margin-top: 12px;
border: 1px solid #dcdfe6;
border-radius: 4px;
background-color: #fff;
}
.file-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px 12px;
border-bottom: 1px solid #ebeef5;
}
.file-item:last-child {
border-bottom: none;
}
.file-name {
flex: 1;
font-size: 14px;
color: #606266;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
margin-right: 16px;
}
.file-actions {
display: flex;
gap: 12px;
}
.formBox { .formBox {
box-sizing: border-box; box-sizing: border-box;
} }
.search-form-item { .search-form-item {
margin-bottom: 20px; margin-bottom: 20px;
} }
.iconStyle { .iconStyle {
color: #409eff; color: #409eff;
} }
......
<template>
<div class="common-table">
<div class="addBox">
<el-button v-if="showAdd" type="primary" @click="addTableData">
{{ addBtnTxt }}
</el-button>
</div>
<el-table v-loading="loading" :data="tableData" border style="width: 100%" v-bind="$attrs">
<!-- 动态列 -->
<el-table-column
v-for="col in visibleColumns"
:key="col.prop"
:prop="col.prop"
:width="col.width"
:min-width="col.minWidth"
:fixed="col.fixed"
:align="col.align || 'left'"
:show-overflow-tooltip="col.showOverflowTooltip"
>
<!-- 自定义表头:显示必填红色星号 -->
<template #header>
<span v-if="checkColumnRequired(col)" class="required-star"> *</span>
<span>{{ col.label }}</span>
</template>
<template #default="{ row, $index }">
<!-- 可编辑列:始终显示编辑组件 -->
<div v-if="col.editable !== false">
<el-form-item
:rules="getColumnRules(col)"
:error="getRowError(row, col.prop)"
style="margin-bottom: 0"
>
<!-- 输入框(文本/数字/textarea) -->
<el-input
v-if="col.editType === 'input'"
:model-value="getInputDisplayValue(row, col)"
:placeholder="col.placeholder || `请输入${col.label}`"
:clearable="true"
:type="col.inputType === 'textarea' ? 'textarea' : 'text'"
:rows="col.rows || 2"
:disabled="col.disabled"
@update:model-value="val => handleInputChange(row, col, val, $index)"
@blur="handleInputBlur(row, col, $index)"
/>
<!-- 下拉选择 -->
<el-select
v-else-if="col.editType === 'select'"
:model-value="row[col.prop]"
:placeholder="col.placeholder || `请选择${col.label}`"
:clearable="true"
filterable
:allow-create="col.allowCreate"
:default-first-option="col.allowCreate"
:remote="!!col.api"
:remote-method="keyword => handleRemoteSearch(keyword, col)"
:loading="remoteLoading[col.prop]"
:disabled="col.disabled"
@update:model-value="val => handleSelectChange(row, col, val, $index)"
@focus="() => loadRemoteOptions(col)"
>
<el-option
v-for="opt in getSelectOptions(col)"
:key="opt.value"
:label="opt.label"
:value="opt.value"
/>
</el-select>
<!-- 日期选择器 -->
<el-date-picker
v-else-if="col.editType === 'date'"
:model-value="row[col.prop]"
type="date"
:placeholder="`选择${col.label}`"
:value-format="col.valueFormat || 'YYYY-MM-DD'"
style="width: 100%"
:disabled="col.disabled"
:disabled-date="getDisabledDateFn(col)"
@update:model-value="val => handleCellChange(row, col, val, $index)"
/>
<!-- 月份选择器 -->
<el-date-picker
v-else-if="col.editType === 'month'"
:model-value="row[col.prop]"
type="month"
:placeholder="`选择${col.label}`"
:value-format="col.valueFormat || 'YYYY-MM'"
style="width: 100%"
:disabled="col.disabled"
:disabled-date="getDisabledDateFn(col)"
@update:model-value="val => handleCellChange(row, col, val, $index)"
/>
<!-- 日期范围 -->
<el-date-picker
v-else-if="col.editType === 'daterange'"
:model-value="row[col.prop]"
type="daterange"
range-separator="至"
:start-placeholder="col.startPlaceholder || '开始日期'"
:end-placeholder="col.endPlaceholder || '结束日期'"
:value-format="col.valueFormat || 'YYYY-MM-DD'"
style="width: 100%"
:disabled="col.disabled"
:disabled-date="getDisabledDateFn(col)"
@update:model-value="val => handleCellChange(row, col, val, $index)"
/>
<!-- 月份范围 -->
<el-date-picker
v-else-if="col.editType === 'monthrange'"
:model-value="row[col.prop]"
type="monthrange"
range-separator="至"
:start-placeholder="col.startPlaceholder || '开始月份'"
:end-placeholder="col.endPlaceholder || '结束月份'"
:value-format="col.valueFormat || 'YYYY-MM'"
style="width: 100%"
:disabled="col.disabled"
:disabled-date="getDisabledDateFn(col)"
@update:model-value="val => handleCellChange(row, col, val, $index)"
/>
<!-- 上传组件 -->
<div v-else-if="col.editType === 'upload'" class="table-upload-wrapper">
<el-upload
:file-list="getFileListDisplay(row[col.prop])"
:action="col.action || '/api/upload'"
:headers="col.headers"
:multiple="col.multiple || false"
:limit="col.limit || 1"
:accept="col.accept"
:disabled="col.disabled"
:auto-upload="true"
:show-file-list="false"
:before-upload="file => beforeUpload(file, col)"
:on-success="
(res, file, fileList) => handleUploadSuccess(row, col, res, file, fileList)
"
:on-error="
(err, file, fileList) => handleUploadError(row, col, err, file, fileList)
"
>
<el-button size="small" type="primary">点击上传</el-button>
</el-upload>
<div class="upload-file-list" v-if="getFileListDisplay(row[col.prop]).length">
<div
v-for="(file, idx) in getFileListDisplay(row[col.prop])"
:key="idx"
class="file-item"
>
<span class="file-name">{{ file.name || file.url }}</span>
<el-button link type="danger" size="small" @click="removeFile(row, col, idx)"
>删除</el-button
>
</div>
</div>
</div>
<!-- 自定义渲染(插槽) -->
<slot
v-else
:name="`edit-${col.prop}`"
:row="row"
:column="col"
:updateValue="val => handleCellChange(row, col, val, $index)"
/>
</el-form-item>
</div>
<!-- 不可编辑列:只读显示 -->
<div v-else>
<slot :name="`column-${col.prop}`" :row="row" :column="col" :value="row[col.prop]">
<span v-if="col.editType === 'select'">
{{ getSelectLabel(col, row[col.prop]) }}
</span>
<span v-else-if="col.editType === 'upload' && row[col.prop]">
<span v-for="(file, idx) in getFileListDisplay(row[col.prop])" :key="idx">
<el-link type="primary" @click="previewFile(file)">{{
file.name || file.url
}}</el-link>
<span v-if="idx < getFileListDisplay(row[col.prop]).length - 1">, </span>
</span>
</span>
<span v-else-if="col.formatter">
{{ col.formatter(row[col.prop], row) }}
</span>
<span v-else>{{ row[col.prop] || '-' }}</span>
</slot>
</div>
</template>
</el-table-column>
<!-- 操作栏 -->
<el-table-column
v-if="showOperation"
:label="operationLabel"
:width="operationWidth"
:fixed="operationFixed"
align="center"
>
<template #default="{ row, $index }">
<div class="operation-buttons">
<slot name="operation" :row="row" :index="$index">
<el-button link type="danger" size="small" @click="handleDelete(row)">删除</el-button>
</slot>
</div>
</template>
</el-table-column>
<!-- 额外插槽列 -->
<slot name="append" />
</el-table>
<!-- 分页组件 -->
<div v-if="showPagination" class="table-pagination">
<el-pagination
v-model:current-page="currentPage"
v-model:page-size="pageSize"
:page-sizes="pageSizes"
:background="paginationBackground"
layout="total, sizes, prev, pager, next, jumper"
:total="total"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
/>
</div>
<!-- 文件预览弹窗 -->
<el-dialog v-model="previewDialogVisible" :title="previewFileName" width="60%" destroy-on-close>
<div class="preview-container">
<img v-if="previewFileType === 'image'" :src="previewUrl" style="max-width: 100%" />
<iframe
v-else-if="previewFileType === 'pdf'"
:src="previewUrl"
style="width: 100%; height: 70vh"
></iframe>
<div v-else class="preview-unsupported">暂不支持预览此类型文件</div>
</div>
</el-dialog>
</div>
</template>
<script setup>
import { ref, computed, watch, onMounted } from 'vue'
import { ElMessage } from 'element-plus'
import { getDicts } from '@/api/system/dict/data'
import useDictStore from '@/store/modules/dict'
import request from '@/utils/request'
import dayjs from 'dayjs'
// ==================== Props ====================
const props = defineProps({
tableData: { type: Array, default: () => [] },
columns: { type: Array, default: () => [] },
rowIdKey: { type: String, default: 'id' },
showOperation: { type: Boolean, default: true },
operationLabel: { type: String, default: '操作' },
operationWidth: { type: String, default: '120' },
operationFixed: { type: String, default: 'right' },
showPagination: { type: Boolean, default: false },
total: { type: Number, default: 0 },
page: { type: Number, default: 1 },
limit: { type: Number, default: 20 },
pageSizes: { type: Array, default: () => [10, 20, 30, 50] },
paginationBackground: { type: Boolean, default: true },
loading: { type: Boolean, default: false },
showAdd: { type: Boolean, default: true },
addBtnTxt: { type: String, default: '新增' }
})
const emit = defineEmits([
'update:page',
'update:limit',
'update:tableData',
'delete',
'selectChange',
'uploadSuccess',
'add',
'cell-change'
])
// ==================== 内部状态 ====================
// 行级错误信息 { [rowId]: { [prop]: errorMsg } }
const rowErrors = ref({})
// 远程选项存储
const remoteOptions = ref({})
const remoteLoading = ref({})
// 字典加载标记
const dictLoaded = ref(new Set())
// 当前页码/每页条数
const currentPage = ref(props.page)
const pageSize = ref(props.limit)
// 预览弹窗状态
const previewDialogVisible = ref(false)
const previewUrl = ref('')
const previewFileName = ref('')
const previewFileType = ref('')
// ==================== 计算属性 ====================
const visibleColumns = computed(() => {
return props.columns.filter(col => {
if (typeof col.visible === 'function') return col.visible()
return col.visible !== false
})
})
// 检查列是否必填
function checkColumnRequired(col) {
if (col.required === true) return true
if (col.rules && Array.isArray(col.rules)) {
return col.rules.some(rule => rule.required === true)
}
return false
}
// 获取行的错误信息
function getRowError(row, prop) {
const rowId = row[props.rowIdKey]
return rowErrors.value[rowId]?.[prop] || ''
}
// 获取列校验规则
function getColumnRules(col) {
if (col.rules) return col.rules
if (col.required) {
return [{ required: true, message: `${col.label}不能为空`, trigger: 'blur' }]
}
return []
}
// 获取 Select 选项列表
function getSelectOptions(col) {
const key = col.prop
if (col.dictType) {
const dictStore = useDictStore()
const dictOpts = dictStore.getDict(col.dictType) || []
return dictOpts.map(opt => ({ value: opt.value, label: opt.label, raw: opt }))
}
if (col.api) {
return (remoteOptions.value[key] || []).map(opt => ({
value: opt.value,
label: opt.label,
raw: opt.raw
}))
}
if (col.options) {
return col.options.map(opt => ({ value: opt.value, label: opt.label, raw: opt.raw }))
}
return []
}
// 获取 Select 选中项的 label
function getSelectLabel(col, value) {
const options = getSelectOptions(col)
if (value === undefined || value === null) return '-'
if (col.multiple && Array.isArray(value)) {
return value.map(v => options.find(opt => opt.value === v)?.label || v).join(', ')
}
const found = options.find(opt => opt.value === value)
return found ? found.label : value
}
// ==================== 数字输入格式化与解析 ====================
// 修改 getInputDisplayValue:所有类型优先使用临时编辑值
function getInputDisplayValue(row, col) {
const rowId = row[props.rowIdKey]
const editingKey = `${rowId}_${col.prop}`
// 1. 正在编辑中 → 显示临时输入值
if (editingTempValue.value[editingKey] !== undefined) {
return editingTempValue.value[editingKey]
}
const { inputType } = col
let rawValue = row[col.prop]
// 2. 非数字类型直接返回原始值
if (!['integer', 'decimalNumber', 'decimal'].includes(inputType)) {
return rawValue ?? ''
}
// 3. 数字类型:失焦状态下格式化显示(千分位 + 小数位)
if (rawValue === undefined || rawValue === null) return ''
let num = typeof rawValue === 'number' ? rawValue : parseFloat(rawValue)
if (isNaN(num)) return ''
const { decimalDigits = 2 } = col
num = parseFloat(num.toFixed(decimalDigits))
const parts = num.toString().split('.')
parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ',')
return parts.join('.')
}
// 临时编辑值存储(用于聚焦时保留用户输入原文)
const editingTempValue = ref({})
// 输入时实时处理(过滤非法字符,并保留临时编辑值)
function handleInputChange(row, col, rawValue, rowIndex) {
const { inputType, decimalDigits = 2 } = col
let filtered = rawValue
if (inputType === 'integer') {
filtered = rawValue.replace(/[^-\d]/g, '')
if (filtered === '-') {
// 保留负号
} else if (filtered !== '') {
let num = parseInt(filtered, 10)
if (isNaN(num)) filtered = ''
else filtered = num.toString()
}
} else if (inputType === 'decimalNumber') {
let temp = rawValue.replace(/[^-\d.]/g, '')
if (temp.startsWith('-')) {
temp = '-' + temp.slice(1).replace(/-/g, '')
} else {
temp = temp.replace(/-/g, '')
}
if (temp === '-') {
filtered = '-'
} else {
const pointIndex = temp.indexOf('.')
if (pointIndex !== -1) {
const intPart = temp.slice(0, pointIndex).replace(/[^\d-]/g, '')
let decPart = temp.slice(pointIndex + 1).replace(/\D/g, '')
if (decPart.length > decimalDigits) decPart = decPart.slice(0, decimalDigits)
filtered = intPart + (decPart ? '.' + decPart : '.')
} else {
filtered = temp
}
}
} else if (inputType === 'decimal') {
let temp = rawValue.replace(/[^\d.]/g, '')
const pointIndex = temp.indexOf('.')
if (pointIndex !== -1) {
const intPart = temp.slice(0, pointIndex).replace(/\D/g, '')
let decPart = temp.slice(pointIndex + 1).replace(/\D/g, '')
if (decPart.length > decimalDigits) decPart = decPart.slice(0, decimalDigits)
filtered = intPart + (decPart ? '.' + decPart : '.')
} else {
filtered = temp
}
} else {
// textarea 或普通文本
filtered = rawValue
}
// const rowId = row[props.rowIdKey]
// const editingKey = `${rowId}_${col.prop}`
// // 保证响应式更新
// editingTempValue.value = {
// ...editingTempValue.value,
// [editingKey]: filtered
// }
const rowId = row[props.rowIdKey]
const editingKey = `${rowId}_${col.prop}`
// 直接赋值,无需展开整个对象
editingTempValue.value[editingKey] = filtered
}
// 失焦时:将临时编辑值转换为真实值并更新到表格数据
function handleInputBlur(row, col, rowIndex) {
const rowId = row[props.rowIdKey]
const editingKey = `${rowId}_${col.prop}`
const rawStr = editingTempValue.value[editingKey]
// 没有临时编辑值,说明用户未输入任何字符,直接返回
if (rawStr === undefined) {
return
}
delete editingTempValue.value[editingKey] // 清除临时值
// 用户清空了输入框
if (rawStr === '') {
const newTableData = [...props.tableData]
newTableData[rowIndex] = { ...row, [col.prop]: null }
emit('update:tableData', newTableData)
validateField(newTableData[rowIndex], col.prop)
return
}
const { inputType, decimalDigits = 2 } = col
let finalValue = null
if (inputType === 'integer') {
const match = rawStr.match(/^-?\d+/)
if (match) {
const num = parseInt(match[0], 10)
if (!isNaN(num)) finalValue = num
}
} else if (inputType === 'decimalNumber') {
const num = parseFloat(rawStr)
if (!isNaN(num)) finalValue = parseFloat(num.toFixed(decimalDigits))
} else if (inputType === 'decimal') {
const num = parseFloat(rawStr)
if (!isNaN(num) && num >= 0) finalValue = parseFloat(num.toFixed(decimalDigits))
} else {
// textarea 或普通文本
finalValue = rawStr
}
const newTableData = [...props.tableData]
newTableData[rowIndex] = { ...row, [col.prop]: finalValue }
emit('update:tableData', newTableData)
emit('cell-change', {
row: newTableData[rowIndex],
col,
newValue: finalValue,
oldValue: row[col.prop],
rowIndex,
prop: col.prop
})
validateField(newTableData[rowIndex], col.prop)
}
// ==================== 数据更新与校验 ====================
// 处理单元格值变更
function handleCellChange(row, col, newValue, rowIndex) {
const rowId = row[props.rowIdKey]
const oldValue = row[col.prop]
if (oldValue === newValue) return
// 更新数据
const newTableData = [...props.tableData]
newTableData[rowIndex] = { ...row, [col.prop]: newValue }
emit('update:tableData', newTableData)
emit('cell-change', {
row: newTableData[rowIndex],
col,
newValue,
oldValue,
rowIndex,
prop: col.prop
})
// 触发字段校验
validateField(newTableData[rowIndex], col.prop)
}
// 处理 Select 变化(支持额外字段赋值)
function handleSelectChange(row, col, value, rowIndex) {
const newTableData = [...props.tableData]
const updatedRow = { ...row, [col.prop]: value }
// 处理额外字段赋值
if (col.onChangeExtraFields) {
const options = getSelectOptions(col)
const opt = options.find(o => o.value === value)
if (opt?.raw) {
for (const [targetProp, sourceKey] of Object.entries(col.onChangeExtraFields)) {
updatedRow[targetProp] = opt.raw[sourceKey]
}
}
}
newTableData[rowIndex] = updatedRow
emit('update:tableData', newTableData)
emit('selectChange', col.prop, value, col)
validateField(updatedRow, col.prop)
}
// 校验单个字段
async function validateField(row, prop) {
const col = props.columns.find(c => c.prop === prop)
if (!col) return true
const rules = getColumnRules(col)
if (!rules.length) return true
const value = row[prop]
const rowId = row[props.rowIdKey]
let errorMsg = ''
for (const rule of rules) {
if (rule.required && (value === undefined || value === null || value === '')) {
errorMsg = rule.message || `${col.label}不能为空`
break
}
if (rule.pattern && !rule.pattern.test(String(value))) {
errorMsg = rule.message || `${col.label}格式不正确`
break
}
if (rule.validator && typeof rule.validator === 'function') {
try {
await rule.validator(null, value, () => {})
} catch (err) {
errorMsg = err.message
break
}
}
}
if (!rowErrors.value[rowId]) {
rowErrors.value[rowId] = {}
}
rowErrors.value[rowId][prop] = errorMsg
return !errorMsg
}
// ==================== 全局校验方法(暴露给父组件) ====================
function validateAll() {
const errors = []
const allValid = []
for (let i = 0; i < props.tableData.length; i++) {
const row = props.tableData[i]
const rowId = row[props.rowIdKey]
let rowValid = true
for (const col of visibleColumns.value) {
if (col.editable === false) continue
const rules = getColumnRules(col)
if (rules.length === 0) continue
const value = row[col.prop]
let errorMsg = ''
for (const rule of rules) {
if (rule.required && (value === undefined || value === null || value === '')) {
errorMsg = rule.message || `${col.label}不能为空`
break
}
if (rule.pattern && !rule.pattern.test(String(value))) {
errorMsg = rule.message || `${col.label}格式不正确`
break
}
}
if (errorMsg) {
rowValid = false
errors.push({
rowIndex: i,
rowId,
prop: col.prop,
label: col.label,
message: errorMsg,
row
})
if (!rowErrors.value[rowId]) rowErrors.value[rowId] = {}
rowErrors.value[rowId][col.prop] = errorMsg
} else {
if (rowErrors.value[rowId]) {
rowErrors.value[rowId][col.prop] = ''
}
}
}
allValid.push(rowValid)
}
const valid = allValid.every(v => v === true)
return { valid, errors }
}
async function validateAndShow() {
const { valid, errors } = validateAll()
if (!valid && errors.length > 0) {
const firstError = errors[0]
ElMessage.warning(firstError.message)
return false
}
return true
}
// ==================== 远程加载 ====================
async function loadDictOptions(dictType) {
const dictStore = useDictStore()
let options = dictStore.getDict(dictType)
if (options && options.length > 0) return options
try {
const resp = await getDicts(dictType)
options = resp.data.map(p => ({ label: p.itemLabel, value: p.itemValue, raw: p }))
dictStore.setDict(dictType, options)
return options
} catch (err) {
ElMessage.error(`字典 ${dictType} 加载失败`)
return []
}
}
async function loadRemoteOptions(col) {
const { prop, api, requestParams } = col
if (!api) return
if (remoteOptions.value[prop]?.length > 0) return
try {
remoteLoading.value[prop] = true
const payload = {
...(typeof requestParams === 'function' ? requestParams() : requestParams || {})
}
const res = await request({ url: api, method: 'post', data: payload })
const list =
typeof col.transform === 'function' ? col.transform(res) : res.data?.records || res.data || []
remoteOptions.value[prop] = list.map(i => ({
value: String(i[col.valueKey || 'value']),
label: i[col.labelKey || 'label'],
raw: i
}))
} catch (err) {
ElMessage.error(`加载 ${col.label} 失败`)
remoteOptions.value[prop] = []
} finally {
remoteLoading.value[prop] = false
}
}
let searchTimeouts = {}
async function handleRemoteSearch(keyword, col) {
const { prop, api, requestParams, keywordField = 'keyword', debounceWait = 300 } = col
if (!api) return
clearTimeout(searchTimeouts[prop])
searchTimeouts[prop] = setTimeout(async () => {
try {
remoteLoading.value[prop] = true
const payload = {
...(typeof requestParams === 'function' ? requestParams() : requestParams || {}),
[keywordField]: keyword
}
const res = await request({ url: api, method: 'post', data: payload })
const list =
typeof col.transform === 'function'
? col.transform(res)
: res.data?.records || res.data || []
remoteOptions.value[prop] = list.map(i => ({
value: String(i[col.valueKey || 'value']),
label: i[col.labelKey || 'label'],
raw: i
}))
} catch (err) {
ElMessage.error(`搜索 ${col.label} 失败`)
} finally {
remoteLoading.value[prop] = false
}
}, debounceWait)
}
function getDisabledDateFn(col) {
const { minDate, maxDate } = col
if (minDate == null && maxDate == null) return () => false
return date => {
const current = dayjs(date).startOf('day')
let minD = null,
maxD = null
if (minDate != null) {
const val = typeof minDate === 'function' ? minDate() : minDate
minD = val ? dayjs(val).startOf('day') : null
}
if (maxDate != null) {
const val = typeof maxDate === 'function' ? maxDate() : maxDate
maxD = val ? dayjs(val).startOf('day') : null
}
return (minD && current.isBefore(minD)) || (maxD && current.isAfter(maxD))
}
}
// ==================== 上传相关 ====================
function getFileListDisplay(val) {
if (!val) return []
if (Array.isArray(val)) return val
try {
return JSON.parse(val)
} catch {
return [{ url: val, name: '文件' }]
}
}
function beforeUpload(file, col) {
if (col.maxSize && file.size > col.maxSize) {
ElMessage.error(`文件 ${file.name} 超出大小限制`)
return false
}
if (col.accept) {
const ext = '.' + file.name.split('.').pop().toLowerCase()
const allowed = col.accept.split(',').map(s => s.trim().toLowerCase())
if (!allowed.includes(ext)) {
ElMessage.error(`文件类型不支持,仅支持:${col.accept}`)
return false
}
}
return true
}
function handleUploadSuccess(row, col, response, file, fileList) {
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
}
const currentList = getFileListDisplay(row[col.prop])
const newFile = { uid: file.uid, url, name }
const newList = [...currentList, newFile]
const rowIndex = props.tableData.findIndex(r => r[props.rowIdKey] === row[props.rowIdKey])
if (rowIndex !== -1) {
const newTableData = [...props.tableData]
newTableData[rowIndex] = { ...row, [col.prop]: newList }
emit('update:tableData', newTableData)
}
ElMessage.success(`${file.name} 上传成功`)
emit('uploadSuccess', col.prop, newList)
}
function handleUploadError(row, col, error, file, fileList) {
ElMessage.error(`文件 ${file.name} 上传失败`)
}
function removeFile(row, col, index) {
const currentList = getFileListDisplay(row[col.prop])
const newList = [...currentList]
newList.splice(index, 1)
const rowIndex = props.tableData.findIndex(r => r[props.rowIdKey] === row[props.rowIdKey])
if (rowIndex !== -1) {
const newTableData = [...props.tableData]
newTableData[rowIndex] = { ...row, [col.prop]: newList }
emit('update:tableData', newTableData)
}
}
function previewFile(file) {
const url = file.url
if (!url) return
const ext = (file.name || '').split('.').pop().toLowerCase()
previewUrl.value = url
previewFileName.value = file.name || '文件'
if (['jpg', 'jpeg', 'png', 'gif', 'webp'].includes(ext)) {
previewFileType.value = 'image'
previewDialogVisible.value = true
} else if (ext === 'pdf') {
previewFileType.value = 'pdf'
previewDialogVisible.value = true
} else {
window.open(url, '_blank')
}
}
// ==================== 行操作 ====================
function handleDelete(row) {
emit('delete', row)
}
//新增表格数据
const addTableData = () => {
emit('add')
}
// ==================== 分页处理 ====================
function handleSizeChange(val) {
pageSize.value = val
emit('update:limit', val)
emit('update:page', currentPage.value)
}
function handleCurrentChange(val) {
currentPage.value = val
emit('update:page', val)
}
watch(
() => props.page,
val => {
currentPage.value = val
}
)
watch(
() => props.limit,
val => {
pageSize.value = val
}
)
// ==================== 初始化 ====================
onMounted(async () => {
for (const col of props.columns) {
if (col.editType === 'select') {
if (col.dictType) {
const opts = await loadDictOptions(col.dictType)
remoteOptions.value[col.prop] = opts
dictLoaded.value.add(col.prop)
} else if (col.api && col.preload !== false) {
await loadRemoteOptions(col)
} else if (col.options) {
remoteOptions.value[col.prop] = [...col.options]
}
}
}
})
// 暴露方法给父组件
defineExpose({
validateAll,
validateAndShow,
refreshRemoteOptions: async prop => {
const col = props.columns.find(c => c.prop === prop)
if (col && col.api) {
remoteOptions.value[prop] = []
await loadRemoteOptions(col)
}
},
clearErrors: () => {
rowErrors.value = {}
}
})
</script>
<style scoped>
.common-table {
width: 100%;
}
.addBox {
margin-bottom: 10px;
}
.table-pagination {
margin-top: 16px;
display: flex;
justify-content: flex-end;
}
.operation-buttons {
display: flex;
gap: 8px;
justify-content: center;
}
.table-upload-wrapper {
display: flex;
flex-direction: column;
gap: 8px;
}
.upload-file-list {
max-height: 80px;
overflow-y: auto;
border: 1px solid #dcdfe6;
border-radius: 4px;
padding: 4px 8px;
}
.file-item {
display: flex;
justify-content: space-between;
align-items: center;
font-size: 12px;
padding: 2px 0;
}
.file-name {
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.preview-container {
text-align: center;
}
.preview-unsupported {
padding: 40px;
color: #909399;
}
.required-star {
color: #f56c6c;
margin-left: 2px;
}
</style>
<template>
<el-dialog
v-model="dialogVisible"
:title="displayFileName"
width="70%"
:close-on-click-modal="false"
destroy-on-close
@close="handleClose"
>
<div class="preview-container">
<!-- 图片预览 -->
<div v-if="previewType === 'image'" class="preview-image-wrapper">
<img :src="previewUrl" class="preview-image" alt="预览图片" />
</div>
<!-- PDF 预览 -->
<iframe
v-else-if="previewType === 'pdf'"
:src="previewUrl"
class="preview-pdf"
frameborder="0"
></iframe>
<!-- 不支持预览 -->
<div v-else-if="previewType === 'unsupported'" class="preview-unsupported">
<el-icon :size="48" color="#909399"><Document /></el-icon>
<p>暂不支持预览此类型文件</p>
<el-button type="primary" @click="handleClose">关闭</el-button>
</div>
</div>
</el-dialog>
</template>
<script setup lang="ts">
import { computed, ref, watch } from 'vue'
import { Document } from '@element-plus/icons-vue'
// --- Props 定义 ---
interface Props {
modelValue: boolean // 控制弹窗显示/隐藏(支持 v-model)
fileUrl: string // 文件 URL 或 Blob URL
fileName?: string // 文件名(用于弹窗标题及类型推断)
fileType?: 'image' | 'pdf' | 'unsupported' // 手动指定文件类型(可选)
}
const props = withDefaults(defineProps<Props>(), {
fileName: '文件预览',
fileType: undefined
})
// --- Emits 定义 ---
const emit = defineEmits<{
(e: 'update:modelValue', value: boolean): void
(e: 'close'): void
}>()
// --- 内部状态 ---
// 用于实际绑定到 img/iframe 的 URL,关闭时会清空
const previewUrl = ref('')
// 弹窗显示状态(双向绑定)
const dialogVisible = computed({
get: () => props.modelValue,
set: val => emit('update:modelValue', val)
})
// 最终显示的标题
const displayFileName = computed(() => props.fileName || '文件预览')
// --- 文件类型推断 ---
const previewType = computed<'image' | 'pdf' | 'unsupported'>(() => {
// 1. 若外部显式传入 fileType,优先使用
if (props.fileType) {
return props.fileType
}
// 2. 根据 fileUrl 或 fileName 的后缀名自动判断
const url = props.fileUrl || ''
const name = props.fileName || ''
const lowerUrl = url.toLowerCase()
const lowerName = name.toLowerCase()
const imageExts = ['.jpg', '.jpeg', '.png', '.gif', '.webp', '.bmp', '.svg']
const isImage = (str: string) => imageExts.some(ext => str.endsWith(ext))
const isPdf = (str: string) => str.endsWith('.pdf')
if (isImage(lowerUrl) || isImage(lowerName)) return 'image'
if (isPdf(lowerUrl) || isPdf(lowerName)) return 'pdf'
return 'unsupported'
})
// --- 同步外部 URL 到预览 URL ---
// 当弹窗打开时,将 fileUrl 赋值给 previewUrl
// 当弹窗关闭时,清空 previewUrl,释放资源
watch(
() => props.modelValue,
visible => {
if (visible && props.fileUrl) {
previewUrl.value = props.fileUrl
} else if (!visible) {
previewUrl.value = ''
}
},
{ immediate: true }
)
// 如果弹窗已打开但 fileUrl 发生变化(例如父组件动态修改),同步更新预览内容
watch(
() => props.fileUrl,
newUrl => {
if (dialogVisible.value && newUrl) {
previewUrl.value = newUrl
}
}
)
// --- 关闭处理 ---
const handleClose = () => {
dialogVisible.value = false // 关闭弹窗
previewUrl.value = '' // 清空预览地址
emit('close') // 通知父组件
}
</script>
<style scoped>
.preview-container {
width: 100%;
min-height: 400px;
display: flex;
justify-content: center;
align-items: center;
}
.preview-image-wrapper {
width: 100%;
text-align: center;
}
.preview-image {
max-width: 100%;
max-height: 70vh;
object-fit: contain;
}
.preview-pdf {
width: 100%;
height: 70vh;
}
.preview-unsupported {
text-align: center;
padding: 40px;
}
.preview-unsupported p {
margin: 20px 0;
color: #909399;
}
</style>
...@@ -34,7 +34,7 @@ ...@@ -34,7 +34,7 @@
:type="btn.type || 'primary'" :type="btn.type || 'primary'"
:icon="btn.icon" :icon="btn.icon"
:size="btn.size || 'default'" :size="btn.size || 'default'"
@click="handleButtonClick(btn)" @click.stop="handleButtonClick(btn)"
:disabled="btn.disabled" :disabled="btn.disabled"
:loading="btn.loading" :loading="btn.loading"
:class="btn.customClass" :class="btn.customClass"
...@@ -54,7 +54,7 @@ ...@@ -54,7 +54,7 @@
:type="btn.type || 'primary'" :type="btn.type || 'primary'"
:icon="btn.icon" :icon="btn.icon"
:size="btn.size || 'default'" :size="btn.size || 'default'"
@click="handleButtonClick(btn)" @click.stop="handleButtonClick(btn)"
:disabled="btn.disabled" :disabled="btn.disabled"
:loading="btn.loading" :loading="btn.loading"
:class="btn.customClass" :class="btn.customClass"
...@@ -316,6 +316,9 @@ const checkConditions = () => { ...@@ -316,6 +316,9 @@ const checkConditions = () => {
// 处理按钮点击 // 处理按钮点击
const handleButtonClick = async (btn: OperationButton) => { const handleButtonClick = async (btn: OperationButton) => {
console.log('====================================')
console.log('按钮')
console.log('====================================')
// 触发通用按钮点击事件 // 触发通用按钮点击事件
emit('btn-click', btn) emit('btn-click', btn)
...@@ -362,6 +365,9 @@ const handleSizeChange = (size: number) => { ...@@ -362,6 +365,9 @@ const handleSizeChange = (size: number) => {
const handleCurrentChange = (page: number) => { const handleCurrentChange = (page: number) => {
currentPage.value = page currentPage.value = page
console.log('====================================')
console.log('当前页')
console.log('====================================')
emit('current-change', page) emit('current-change', page)
} }
......
...@@ -11,6 +11,7 @@ const useUserStore = defineStore('user', { ...@@ -11,6 +11,7 @@ const useUserStore = defineStore('user', {
state: () => ({ state: () => ({
token: getToken(), token: getToken(),
id: '', id: '',
realName:'',
name: '', name: '',
nickName: '', nickName: '',
avatar: '', avatar: '',
...@@ -74,6 +75,7 @@ const useUserStore = defineStore('user', { ...@@ -74,6 +75,7 @@ const useUserStore = defineStore('user', {
this.id = user.userId this.id = user.userId
this.name = user.userName this.name = user.userName
this.realName = user.realName
this.nickName = user.nickName this.nickName = user.nickName
this.avatar = avatar this.avatar = avatar
this.isSuperAdmin = user.isSuperAdmin this.isSuperAdmin = user.isSuperAdmin
......
{ {
"paymentMethod": "CHECK", "id": 2,
"paymentAmount": "111", "fortuneBizId": "fortune_LCESmX7iy1TJRcaE",
"paymentCurrency": "HKD", "reconciliationYearMonth": null,
"paymentRel": "SBR", "fortuneBizType": "R",
"payer": "111", "isPart": 0,
"payingBank": "bank_1002", "expectedFortuneBizId": "expected_fortune_kVBKKtNB38QcFKI6",
"paymentAccount": "1111", "payableNo": "R-CSF26000001",
"currency": "", "policyNo": "111",
"paymentVoucherList": [ "policyCurrency": "美元",
{ "premium": 10000,
"fileName": "icon5.png", "productName": "来佣000",
"fileType": "png", "insuranceCompany": "友邦保險",
"fileUrl": "https://yd-ali-oss.oss-cn-shanghai-finance-1-pub.aliyuncs.com/png/2026/01/13/b4241a95a39d4655a79c706d7ec37f85.png", "commissionBizId": "commission_b9els2m9Lkocu4RB",
"url": "https://yd-ali-oss.oss-cn-shanghai-finance-1-pub.aliyuncs.com/png/2026/01/13/b4241a95a39d4655a79c706d7ec37f85.png" "commissionExpectedBizId": "commission_expected_mUlJXrNqmpioXbsY",
}, "commissionPaidAmount": 38981,
{ "commissionPaidRatio": 50,
"fileName": "cardSix1.png", "fortunePeriod": 1,
"fileType": "png", "fortuneTotalPeriod": 1,
"fileUrl": "https://yd-ali-oss.oss-cn-shanghai-finance-1-pub.aliyuncs.com/png/2026/01/13/41bd22390f6a4a69a5ed8d9c8758ef94.png", "broker": "张平",
"url": "https://yd-ali-oss.oss-cn-shanghai-finance-1-pub.aliyuncs.com/png/2026/01/13/41bd22390f6a4a69a5ed8d9c8758ef94.png" "brokerBizId": "client_user_mdeCtfYb9oRbYnAF",
} "team": "AGHS",
], "teamBizId": "dept_fgUS1281YFdsq3",
"accountVerificationList": [ "fortuneName": "销售佣金",
{ "fortuneType": "1",
"fileName": "icon6.png", "ruleAmount": -100,
"fileType": "png", "ruleCurrency": "HKD",
"fileUrl": "https://yd-ali-oss.oss-cn-shanghai-finance-1-pub.aliyuncs.com/png/2026/01/13/ce6f4781b8f3443f92eddbf13ecefe42.png", "exchangeRate": 1,
"url": "https://yd-ali-oss.oss-cn-shanghai-finance-1-pub.aliyuncs.com/png/2026/01/13/ce6f4781b8f3443f92eddbf13ecefe42.png" "originalCurrency": null,
}, "originalAmount": null,
{ "originalToHkdRate": null,
"fileName": "icon4.png", "payoutCurrency": null,
"fileType": "png", "payoutAmount": null,
"fileUrl": "https://yd-ali-oss.oss-cn-shanghai-finance-1-pub.aliyuncs.com/png/2026/01/13/24e9786aa52d48caba0bfc48e7f4f146.png", "hkdToPayoutRate": null,
"url": "https://yd-ali-oss.oss-cn-shanghai-finance-1-pub.aliyuncs.com/png/2026/01/13/24e9786aa52d48caba0bfc48e7f4f146.png" "hkdAmount": -100,
} "fortunePaidAmount": 0,
], "fortuneUnpaidAmount": -100,
"apiPremiumRemittanceFileDtoList": [ "currentPaymentAmount": -100,
{ "currentPaymentHkdAmount": -100,
"fileName": "icon5.png", "currentPaymentRatio": 0,
"fileType": "png", "fortuneUnpaidRatio": 100,
"fileUrl": "https://yd-ali-oss.oss-cn-shanghai-finance-1-pub.aliyuncs.com/png/2026/01/13/bca0400a90ae4c1da2373e3cf4de0fc7.png", "status": "0",
"url": "https://yd-ali-oss.oss-cn-shanghai-finance-1-pub.aliyuncs.com/png/2026/01/13/bca0400a90ae4c1da2373e3cf4de0fc7.png" "payoutDate": null,
}, "actualPayoutDate": null,
{ "isTax": 0,
"fileName": "homeSelect1.png", "taxAmount": null,
"fileType": "png", "netAmount": null,
"fileUrl": "https://yd-ali-oss.oss-cn-shanghai-finance-1-pub.aliyuncs.com/png/2026/01/13/138932043c6244cb8a006c2ab81b4bab.png", "salaryBizId": null,
"url": "https://yd-ali-oss.oss-cn-shanghai-finance-1-pub.aliyuncs.com/png/2026/01/13/138932043c6244cb8a006c2ab81b4bab.png" "baseRuleBizId": null,
} "settlementBizId": "1",
], "calculationFormula": null,
"id": 1768282536268 "remark": null,
"creatorId": null,
"reconciliationOperator": null,
"updaterId": null,
"createTime": "2026-04-17 15:47:21",
"updateTime": "2026-04-27 14:53:11"
} }
import { ElMessage } from 'element-plus' import { ElMessage } from 'element-plus'
export function copyToClipboard(text) { // 传统降级复制方法
// 方案1:使用现代 Clipboard API
if (navigator.clipboard && navigator.clipboard.writeText) {
return navigator.clipboard.writeText(text).catch((err) => {
console.error('Clipboard API 写入失败:', err);
ElMessage.error('复制失败')
// 降级到传统方案
fallbackCopyText(text);
});
}
// 方案2:降级使用传统方法
else {
fallbackCopyText(text);
}
}
// 传统复制方法
function fallbackCopyText(text, typeName = '内容') { function fallbackCopyText(text, typeName = '内容') {
const textarea = document.createElement('textarea'); const textarea = document.createElement('textarea');
textarea.value = text; textarea.value = text;
// 防止页面滚动或闪烁
textarea.style.position = 'fixed'; textarea.style.position = 'fixed';
textarea.style.opacity = '0'; textarea.style.opacity = '0';
textarea.style.left = '-9999px';
document.body.appendChild(textarea); document.body.appendChild(textarea);
try { try {
textarea.select(); textarea.select();
textarea.setSelectionRange(0, 99999); // 对于移动设备 textarea.setSelectionRange(0, 99999); // 兼容移动端
document.execCommand('copy'); const successful = document.execCommand('copy');
console.log('传统方法复制成功'); if (successful) {
ElMessage.success(`${typeName}已复制`) ElMessage.success(`${typeName}已复制`);
} else {
throw new Error('execCommand 返回失败');
}
} catch (err) { } catch (err) {
console.error('传统方法复制失败:', err) console.error('传统方法复制失败:', err);
ElMessage.error('复制失败'); ElMessage.error('复制失败,请手动复制');
} finally { } finally {
document.body.removeChild(textarea); document.body.removeChild(textarea);
} }
} }
export default { // 主导出函数,接收 text 和 typeName 两个参数
copyToClipboard export default function copyToClipboard(text, typeName = '内容') {
} // 1. 判断是否在安全上下文(HTTPS/localhost) 且 浏览器支持 Clipboard API
if (window.isSecureContext && navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard.writeText(text).then(() => {
ElMessage.success(`${typeName}已复制`);
}).catch((err) => {
console.error('Clipboard API 写入失败,降级处理:', err);
// API 调用失败(如权限被拒),自动降级到传统方案
fallbackCopyText(text, typeName);
});
} else {
// 2. 非安全环境(如 HTTP)或不支持新 API,直接使用传统降级方案
fallbackCopyText(text, typeName);
}
}
\ No newline at end of file
import Decimal from 'decimal.js'
// 格式化金额为货币格式 // 格式化金额为货币格式
export function formatCurrency(value, currency = '', fixedDigits = 2) { export function formatCurrency(value, currency = '', fixedDigits = 2) {
if (value === undefined || value === null) return currency + '0.00' if (value === undefined || value === null) return currency + '0.00'
...@@ -206,11 +207,57 @@ export function inputThousands(value) { ...@@ -206,11 +207,57 @@ export function inputThousands(value) {
parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ',') parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ',')
return parts.join('.') return parts.join('.')
} }
/**
* 金额计算(乘/除),保留指定位数小数(四舍五入)
* @param {number|string|Decimal} a - 被乘数(或被除数)
* @param {number|string|Decimal} b - 乘数(或除数)
* @param {number} digits - 小数位数(默认2)
* @param {string} [type] - 'Divided' 表示除法,否则乘法
* @returns {number|null} 计算结果(数字类型)
*/
export function calculateAmount(a, b, digits = 2, type) {
if (a == null || b == null) return null
try {
const da = new Decimal(a)
const db = new Decimal(b)
let result
if (type === 'Divided') {
if (db.isZero()) return null // 除零保护
result = da.dividedBy(db)
} else {
result = da.times(db)
}
// 四舍五入到指定位数,并转为 Number(若需要字符串可用 .toFixed(digits))
// return result.toDecimalPlaces(digits, Decimal.ROUND_HALF_UP).toNumber()
return result.toFixed(digits, Decimal.ROUND_HALF_UP)
} catch (e) {
return null
}
}
/**
* 格式化数字,保留最多 maxDigits 位小数,自动去除尾随零
* @param {number|null|undefined} value - 原始数值
* @param {number} maxDigits - 最多保留的小数位数,默认 5
* @param {string} defaultValue - 无效值时的占位符,默认 '-'
* @returns {string} 格式化后的字符串
*/
export function formatNumberToMaxDigits(value, maxDigits = 5, defaultValue = '-') {
if (value === undefined || value === null || isNaN(Number(value))) {
return defaultValue;
}
const num = Number(value);
// 先固定保留 maxDigits 位小数(四舍五入)
const fixed = num.toFixed(maxDigits);
// 移除末尾的0,如果小数部分全是0则连小数点一起移除
const trimmed = fixed.replace(/\.?0+$/, '');
return trimmed;
}
export default { export default {
formatCurrency, formatCurrency,
numberFormat, numberFormat,
formatThousands, formatThousands,
formatThousandsSimple, formatThousandsSimple,
inputThousands inputThousands,
calculateAmount
} }
...@@ -597,7 +597,42 @@ export const validateIdCardSimple = (rule, value, callback) => { ...@@ -597,7 +597,42 @@ export const validateIdCardSimple = (rule, value, callback) => {
callback() callback()
} }
/**
* 英文姓名校验规则
*/
export const validateEnglish2 = (value) => {
if (!value) {
// 如果值为空且字段不是必填的,直接通过校验
return
}
// 1. 基本字符检查
if (!/^[A-Za-z\s\-'.]+$/.test(value)) {
return "包含非法字符,只允许英文字母、空格、-、'和."
}
// 2. 首字母大写检查
// if (!/^[A-Z]/.test(value)) {
// return callback(new Error('名字应以大写字母开头'))
// }
// 3. 长度检查
if (value.length < 2) {
return '名字至少需要2个字符'
}
// 4. 连续特殊字符检查
if (/[\s\-'.]{2,}/.test(value)) {
return '不能连续使用特殊字符'
}
// 5. 开头或结尾不能是特殊字符
if (/^[\s\-'.]|[\s\-'.]$/.test(value.trim())) {
return '名字不能以特殊字符开头或结尾'
}
return ''
}
// 将身份证验证方法添加到默认导出对象中 // 将身份证验证方法添加到默认导出对象中
export default { export default {
validateEnglish, validateEnglish,
...@@ -605,5 +640,6 @@ export default { ...@@ -605,5 +640,6 @@ export default {
validatePhone, validatePhone,
validateBMI, validateBMI,
validateIdCard, validateIdCard,
validateIdCardSimple validateIdCardSimple,
validateEnglish2
} }
...@@ -15,33 +15,56 @@ ...@@ -15,33 +15,56 @@
<template #searchForm> <template #searchForm>
<SearchForm ref="searchFormRef" :config="searchConfig" /> <SearchForm ref="searchFormRef" :config="searchConfig" />
</template> </template>
<!-- 列表区域 --> <!-- 列表区域 -->
<template #table> <template #table>
<!-- 统计信息卡片 v-if="statisticsData.totalInAmount > 0"--> <!-- 统计信息卡片 v-if="statisticsData.totalInAmount > 0"-->
<div class="statistics-container"> <div class="statistics-container">
<el-row :gutter="20"> <el-row :gutter="20">
<el-col :xs="24" :sm="12" :md="4" class="text-center mb-4"> <el-col :xs="24" :sm="12" :md="4" class="text-center mb-4">
<el-statistic title="入账金额" :value="statisticsData.totalInAmount" :formatter="value=>formatCurrency(value)" /> <el-statistic
title="入账金额"
:value="statisticsData.totalInAmount"
:formatter="value => formatCurrency(value)"
/>
</el-col> </el-col>
<el-col :xs="24" :sm="12" :md="4" class="text-center mb-4"> <el-col :xs="24" :sm="12" :md="4" class="text-center mb-4">
<el-statistic title="总保单数" :value="statisticsData.totalPolicyCount" /> <el-statistic title="总保单数" :value="statisticsData.totalPolicyCount" />
</el-col> </el-col>
<el-col :xs="24" :sm="12" :md="4" class="text-center mb-4"> <el-col :xs="24" :sm="12" :md="4" class="text-center mb-4">
<el-statistic title="总保费(HKD)" :value="statisticsData.totalPremium" :formatter="value=>formatCurrency(value)" /> <el-statistic
title="总保费(HKD)"
:value="statisticsData.totalPremium"
:formatter="value => formatCurrency(value)"
/>
</el-col> </el-col>
<el-col :xs="24" :sm="12" :md="4" class="text-center mb-4"> <el-col :xs="24" :sm="12" :md="4" class="text-center mb-4">
<el-statistic title="待出账金额" :value="statisticsData.pendingOutAmount" :formatter="value=>formatCurrency(value)" /> <el-statistic
title="待出账金额"
:value="statisticsData.pendingOutAmount"
:formatter="value => formatCurrency(value)"
/>
</el-col> </el-col>
<el-col :xs="24" :sm="12" :md="4" class="text-center mb-4"> <el-col :xs="24" :sm="12" :md="4" class="text-center mb-4">
<el-statistic title="可出账金额" :value="statisticsData.availableOutAmount" :formatter="value=>formatCurrency(value)" /> <el-statistic
title="可出账金额"
:value="statisticsData.availableOutAmount"
:formatter="value => formatCurrency(value)"
/>
</el-col> </el-col>
<el-col :xs="24" :sm="12" :md="4" class="text-center mb-4"> <el-col :xs="24" :sm="12" :md="4" class="text-center mb-4">
<el-statistic title="差额(估)" :value="statisticsData.differenceAmount" :formatter="value=>formatCurrency(value)" /> <el-statistic
title="差额(估)"
:value="statisticsData.differenceAmount"
:formatter="value => formatCurrency(value)"
/>
</el-col> </el-col>
</el-row> </el-row>
</div> </div>
<el-table <el-table
ref="multipleTableRef"
:data="tableData" :data="tableData"
row-key="fortuneBizId"
@selection-change="handleSelectionChange" @selection-change="handleSelectionChange"
height="400" height="400"
border border
...@@ -49,7 +72,18 @@ ...@@ -49,7 +72,18 @@
style="width: 100%" style="width: 100%"
v-loading="loading" v-loading="loading"
> >
<el-table-column type="selection" width="40" /> <el-table-column type="selection" width="40" fixed="left" :selectable="isSelectable" />
<el-table-column prop="payableNo" label="业务编号" width="150" fixed="left" />
<el-table-column prop="status" label="出账状态" width="160" sortable>
<template #default="{ row }">
{{ selectDictLabel(csf_fortune_status, row.status) }}
</template>
</el-table-column>
<el-table-column prop="isPart" label="是否拆分出账" width="120">
<template #default="scope">
<dict-tag :options="sys_no_yes" :value="scope.row.isPart" />
</template>
</el-table-column>
<el-table-column prop="fortuneBizType" label="应付单类型" width="120" sortable> <el-table-column prop="fortuneBizType" label="应付单类型" width="120" sortable>
<template #default="{ row }"> <template #default="{ row }">
{{ getFortuneBizTypeLabel(row.fortuneBizType) }} {{ getFortuneBizTypeLabel(row.fortuneBizType) }}
...@@ -62,32 +96,64 @@ ...@@ -62,32 +96,64 @@
label="累积已入账金额" label="累积已入账金额"
width="120" width="120"
sortable sortable
:formatter="row=>formatCurrency(row.commissionPaidAmount || 0)" :formatter="row => formatCurrency(row.commissionPaidAmount || 0)"
/> />
<el-table-column <!-- <el-table-column
prop="commissionPaidRatio" prop="commissionPaidRatio"
label="累积实佣率" label="累积实佣率"
width="120" width="120"
sortable sortable
:formatter="row => `${row.commissionPaidRatio ? row.commissionPaidRatio : 0}%`" :formatter="row => `${row.commissionPaidRatio ? row.commissionPaidRatio : 0}%`"
/> -->
<el-table-column
prop="commissionPendingRatio"
label="达成率缺口"
width="120"
sortable
:formatter="row => `${row.commissionPendingRatio ? row.commissionPendingRatio : 0}%`"
/> />
<el-table-column prop="fortuneName" label="出账项目" width="130" sortable /> <el-table-column prop="fortuneName" label="出账项目" width="130" sortable />
<el-table-column prop="fortunePeriod" label="出账期数" width="130" sortable /> <el-table-column prop="fortunePeriod" label="出账期数" width="130" sortable />
<el-table-column prop="fortuneTotalPeriod" label="总期数" width="120" sortable /> <el-table-column prop="fortuneTotalPeriod" label="总期数" width="120" sortable />
<el-table-column prop="broker" label="转介人" width="130" sortable /> <el-table-column prop="broker" label="转介人" width="130" sortable />
<el-table-column prop="brokerGradeName" label="转介人职级" width="100" />
<el-table-column prop="team" label="所属团队" width="120" sortable /> <el-table-column prop="team" label="所属团队" width="120" sortable />
<el-table-column prop="exchangeRate" label="结算汇率(估)" width="140" sortable /> <el-table-column
<el-table-column prop="hkdAmount" label="应出账金额(HKD)" width="160" sortable :formatter="row=>formatCurrency(row.hkdAmount || 0)" /> prop="exchangeRate"
<el-table-column prop="currency" label="出账币种" width="130" sortable /> label="结算汇率(估)"
<el-table-column prop="fortunePaidAmount" label="已出账金额" width="160" sortable :formatter="row=>formatCurrency(row.fortunePaidAmount || 0)"/> width="140"
<el-table-column prop="fortuneUnpaidAmount" label="剩余出账金额" width="160" sortable :formatter="row=>formatCurrency(row.fortuneUnpaidAmount || 0)"/> :formatter="row => formatNumberToMaxDigits(row.exchangeRate, 5, '-')"
>
</el-table-column>
<el-table-column
prop="hkdAmount"
label="应出账金额(HKD)"
width="160"
sortable
:formatter="row => formatCurrency(row.hkdAmount || 0)"
/>
<el-table-column prop="payoutCurrency" label="出账币种" width="130" sortable />
<el-table-column
prop="fortunePaidAmount"
label="已出账金额"
width="160"
sortable
:formatter="row => formatCurrency(row.fortunePaidAmount || 0)"
/>
<el-table-column
prop="fortuneUnpaidAmount"
label="剩余出账金额"
width="160"
sortable
:formatter="row => formatCurrency(row.fortuneUnpaidAmount || 0)"
/>
<!-- <el-table-column prop="currentPaymentAmount" label="本期出账金额" width="120" sortable/> --> <!-- <el-table-column prop="currentPaymentAmount" label="本期出账金额" width="120" sortable/> -->
<el-table-column <el-table-column
prop="currentPaymentHkdAmount" prop="currentPaymentHkdAmount"
label="本期出账金额(HKD)" label="本期出账金额(HKD)"
width="160" width="160"
sortable sortable
:formatter="row=>formatCurrency(row.currentPaymentHkdAmount || 0)" :formatter="row => formatCurrency(row.currentPaymentHkdAmount || 0)"
/> />
<el-table-column <el-table-column
prop="fortuneUnpaidRatio" prop="fortuneUnpaidRatio"
...@@ -96,15 +162,17 @@ ...@@ -96,15 +162,17 @@
sortable sortable
:formatter="row => `${row.fortuneUnpaidRatio}%`" :formatter="row => `${row.fortuneUnpaidRatio}%`"
/> />
<el-table-column prop="status" label="出账状态" width="160" sortable>
<template #default="{ row }"> <el-table-column
{{ selectDictLabel(csf_fortune_status, row.status) }} prop="premium"
</template> label="期交保费"
</el-table-column> width="120"
<el-table-column prop="premium" label="期交保费" width="120" sortable :formatter="row=>formatCurrency(row.premium || 0)"/> sortable
:formatter="row => formatCurrency(row.premium || 0)"
/>
<el-table-column prop="policyCurrency" label="保单币种" width="120" sortable /> <el-table-column prop="policyCurrency" label="保单币种" width="120" sortable />
<el-table-column prop="payoutDate" label="出账(估)" width="120" sortable /> <el-table-column prop="payoutDate" label="出账(估)" width="120" sortable />
<el-table-column prop="actualPayoutDate" label="出账(实)" width="120" sortable /> <el-table-column prop="actualPayoutDate" label="出账(实)" width="120" sortable />
<el-table-column prop="remark" label="备注" width="120" sortable /> <el-table-column prop="remark" label="备注" width="120" sortable />
<el-table-column fixed="right" label="操作" min-width="120"> <el-table-column fixed="right" label="操作" min-width="120">
<template #default="{ row }"> <template #default="{ row }">
...@@ -117,7 +185,7 @@ ...@@ -117,7 +185,7 @@
<el-menu @select="handleSelect($event, row)" popper-class="custom-menu"> <el-menu @select="handleSelect($event, row)" popper-class="custom-menu">
<el-menu-item <el-menu-item
:index="item.value" :index="item.value"
v-for="item in dropdownItems" v-for="item in getOperateItems(row)"
:key="item.value" :key="item.value"
>{{ item.label }}</el-menu-item >{{ item.label }}</el-menu-item
> >
...@@ -130,6 +198,13 @@ ...@@ -130,6 +198,13 @@
<div class="tableOptionContainer"> <div class="tableOptionContainer">
<el-button <el-button
type="primary" type="primary"
:icon="EditPen"
:disabled="updatePayRollStatusDisable"
@click="batchSettingBillYearMonth"
>批量设置出账年月(实)</el-button
>
<el-button
type="primary"
:icon="Select" :icon="Select"
:disabled="updatePayRollStatusDisable" :disabled="updatePayRollStatusDisable"
@click="downloadPolicyFortuneAccountapi" @click="downloadPolicyFortuneAccountapi"
...@@ -152,6 +227,7 @@ ...@@ -152,6 +227,7 @@
ref="addCheckRecordFormRef" ref="addCheckRecordFormRef"
:config="addCheckRecordConfig" :config="addCheckRecordConfig"
v-model="addCheckRecordFormModel" v-model="addCheckRecordFormModel"
@inputChange="(prop, value, item) => handleInputChange('addCheckRecord', prop, value, item)"
/> />
</CommonDialog> </CommonDialog>
<!-- 导入出账检核页面 --> <!-- 导入出账检核页面 -->
...@@ -187,29 +263,100 @@ ...@@ -187,29 +263,100 @@
v-model="setPayoutAmountFormModel" v-model="setPayoutAmountFormModel"
/> />
</CommonDialog> </CommonDialog>
<!-- 分期出账弹窗 -->
<CommonDialog
dialogTitle="分期出账"
dialogWidth="90%"
confirmText="提交"
:openDialog="installmentsBillFlag"
:showAction="true"
:showClose="true"
@close="installmentsBillFlag = false"
@confirm="handleSpiltSubmit"
>
<editTable
ref="editTableRef"
addBtnTxt="添加出账"
v-model:table-data="splitTableData"
:columns="splitTableColumns"
row-id-key="id"
show-operation
@delete="handleSpiltDelete"
@add="addSpiltRecord"
@cell-change="handleSplitCellChange"
/>
</CommonDialog>
<!-- 设置出账年月弹窗 -->
<CommonDialog
dialogTitle="设置本期出账年月(实)"
dialogWidth="80%"
:openDialog="settingBillYearMonthFlag"
:showAction="true"
:showClose="true"
@close="settingBillYearMonthFlag = false"
@confirm="submitSettingBillYearMonth"
>
<SearchForm
ref="settingBillYearMonthRef"
:config="settingBillTimeConfig"
v-model="settingBillTimeForm"
/>
</CommonDialog>
<!-- 修改结算汇率 -->
<CommonDialog
dialogTitle="修改结算汇率"
dialogWidth="80%"
:openDialog="rateExchangeFlag"
:showAction="true"
:showClose="true"
@close="rateExchangeFlag = false"
@confirm="confirmRateExchange"
>
<SearchForm
ref="rateExchangeFormRef"
:config="rateExchangeConfig"
v-model="rateExchangeForm"
@inputChange="(prop, value, item) => handleInputChange('rateExchange', prop, value, item)"
/>
</CommonDialog>
</div> </div>
</template> </template>
<script setup> <script setup>
import { ref, reactive } from 'vue' import { round } from 'lodash-es' // 或者自己实现小数保留
import { ref, reactive, nextTick, watch } from 'vue'
import CommonPage from '@/components/commonPage' import CommonPage from '@/components/commonPage'
import editTable from '@/components/Table/editTable.vue'
import CommonDialog from '@/components/commonDialog' import CommonDialog from '@/components/commonDialog'
import SearchForm from '@/components/SearchForm/SearchForm.vue' import SearchForm from '@/components/SearchForm/SearchForm.vue'
import { ElMessage } from 'element-plus' import { ElMessage } from 'element-plus'
import { formatCurrency } from '@/utils/number' import { formatCurrency, calculateAmount, formatNumberToMaxDigits } from '@/utils/number'
import { Select } from '@element-plus/icons-vue' import { Select, EditPen } from '@element-plus/icons-vue'
// 接口 // 接口
import { import {
getPolicyFortuneList, getPolicyFortuneList,
addCheckRecordaddBatch, addCheckRecordaddBatch,
updatePayoutAmount, updatePayoutAmount,
downloadPolicyFortuneAccount, downloadPolicyFortuneAccount,
billSplitApi,
commissionExchangeRateApi,
actualPayoutDateApi,
editExchangeRateApi,
batchActualPayoutDate
} from '@/api/financial/commission' } from '@/api/financial/commission'
import useUserStore from '@/store/modules/user' import useUserStore from '@/store/modules/user'
const rateExchangeFormRef = ref(null)
const rateExchangeFlag = ref(false)
const rateExchangeForm = ref({})
const billTimeType = ref('single')
const settingBillTimeForm = ref({})
const splitTableData = ref([])
const installmentsBillFlag = ref(false)
const settingBillYearMonthFlag = ref(false)
const { proxy } = getCurrentInstance() const { proxy } = getCurrentInstance()
const { csf_fortune_status } = proxy.useDict('csf_fortune_status') const { csf_fortune_status, sys_no_yes } = proxy.useDict('csf_fortune_status', 'sys_no_yes')
const multipleTableRef = ref(null)
const userStore = useUserStore() const userStore = useUserStore()
// 分页相关 // 分页相关
const currentPage = ref(1) const currentPage = ref(1)
...@@ -219,6 +366,31 @@ const loading = ref(false) ...@@ -219,6 +366,31 @@ const loading = ref(false)
const selectedRow = ref(null) const selectedRow = ref(null)
const searchFormRef = ref(null) const searchFormRef = ref(null)
const searchParams = ref({}) const searchParams = ref({})
// 表格操作菜单
const dropdownItems = [
// { label: '设置本期出账金额', value: 'setPayRoll' }
{ label: '分期出账', value: 'billInstallments' },
{ label: '设置出账年月(实)', value: 'settingBillYearMonth' },
{ label: '修改结算汇率', value: 'editExchangeRate' }
]
// 动态生成操作菜单项(根据行数据)
const getOperateItems = row => {
const items = []
// 条件:isPart == 1 时不显示分期出账(使用宽松相等,兼容字符串 '1')
if (!(row.isPart == 1)) {
items.push({ label: '分期出账', value: 'billInstallments' })
}
// 出账状态不为已出帐的都可以修改结算汇率
if (row.status !== '2') {
items.push({ label: '修改结算汇率', value: 'editExchangeRate' })
}
// 始终显示的菜单项(保持原始顺序)
items.push({ label: '设置出账年月(实)', value: 'settingBillYearMonth' })
return items
}
const searchConfig = ref([ const searchConfig = ref([
{ {
type: 'input', type: 'input',
...@@ -226,6 +398,11 @@ const searchConfig = ref([ ...@@ -226,6 +398,11 @@ const searchConfig = ref([
label: '保单号' label: '保单号'
}, },
{ {
type: 'input',
prop: 'brokerName',
label: '转介人'
},
{
type: 'select', type: 'select',
prop: 'statusList', prop: 'statusList',
label: '出账状态', label: '出账状态',
...@@ -273,9 +450,9 @@ const searchConfig = ref([ ...@@ -273,9 +450,9 @@ const searchConfig = ref([
} }
}, },
{ {
type: 'daterange', type: 'monthrange',
prop: 'payoutDate', prop: 'payoutDate',
label: '出账(估)', label: '出账(估)',
startPlaceholder: '开始时间', startPlaceholder: '开始时间',
endPlaceholder: '结束时间' endPlaceholder: '结束时间'
}, },
...@@ -287,17 +464,599 @@ const searchConfig = ref([ ...@@ -287,17 +464,599 @@ const searchConfig = ref([
dictType: 'csf_expected_commission_status' dictType: 'csf_expected_commission_status'
} }
]) ])
// 表格操作菜单 const editTableRef = ref(null)
const dropdownItems = [ const splitTableColumns = ref([
{ label: '设置本期出账金额', value: 'setPayRoll' } {
// { label: '更新', value: 'editRecord' }, prop: 'splitRatio',
// { label: '查看记录', value: 'viewRecord' } label: '出账比例(%)',
editType: 'input',
inputType: 'decimal', // integer / decimalNumber / decimal
decimalDigits: 2, // 小数位数,默认2
required: true,
placeholder: '请输入出账比例',
width: 150
},
{
editType: 'select',
prop: 'ruleCurrency',
label: '保单币种',
dictType: 'bx_currency_type',
required: true,
width: 150
},
{
editType: 'select',
prop: 'originalCurrency',
label: '原币种',
dictType: 'bx_currency_type',
required: true,
width: 150
},
{
editType: 'select',
prop: 'payoutCurrency',
label: '发放币种',
dictType: 'bx_currency_type',
required: true,
width: 150
},
{
editType: 'input',
prop: 'originalAmount',
label: '原币种金额',
inputType: 'decimal',
required: true,
width: 150
},
// 汇率3(原币种->港币)
{
editType: 'input',
prop: 'originalToHkdRate',
label: '原币种->港币(汇率)',
inputType: 'decimal',
decimalDigits: 5,
required: true,
width: 150
},
{
editType: 'input',
prop: 'hkdAmount',
label: '港币金额',
inputType: 'decimal',
required: true,
width: 150
},
// 汇率1(港币->发放币种)
{
editType: 'input',
prop: 'hkdToPayoutRate',
label: '港币->发放币种(汇率)',
inputType: 'decimal',
decimalDigits: 5,
required: true,
width: 150
},
{
editType: 'input',
prop: 'payoutAmount',
label: '实际发放金额',
inputType: 'decimal',
required: true,
width: 150
},
// 汇率2(保单币种->港币)入账检核汇率
{
editType: 'input',
prop: 'exchangeRate',
label: '保单币种->港币(入账检核汇率)',
inputType: 'decimal',
decimalDigits: 5,
required: true,
width: 150
// defaultValue: 1
},
{
editType: 'input',
prop: 'ruleAmount',
label: '保单币种金额',
inputType: 'decimal',
required: true,
width: 150
},
{
prop: 'payoutYearMonth',
label: '出账年月(估)',
editType: 'month',
required: true,
width: 150
},
{
prop: 'remark',
label: '备注',
editType: 'input',
inputType: 'textarea',
required: false,
width: 150
}
// {
// prop: 'userId',
// label: '负责人',
// editType: 'select',
// api: '/insurance/base/api/insuranceCompany/page', // 接口地址
// valueKey: 'insuranceCompanyBizId', // 选项 value 字段名
// labelKey: 'fullName', // 选项 label 字段名
// preload: true, // 初始化时自动加载
// requestParams: { pageNo: 1, pageSize: 20 }, // 固定参数
// // 远程搜索配置
// filterable: true,
// keywordField: 'keyword',
// debounceWait: 300
// },
// {
// prop: 'status',
// label: '状态',
// editType: 'select',
// dictType: 'sys_status', // 系统字典类型
// required: true
// }
])
const settingBillTimeConfig = ref([
{
type: 'month',
prop: 'actualPayoutDate',
label: '出账年月(实)',
placeholder: '出账年月(实)',
rules: [{ required: true, message: '请选择出账年月(实)', trigger: 'blur' }]
}
])
const rateExchangeConfig = [
{
type: 'select',
prop: 'ruleCurrency',
label: '保单币种',
dictType: 'bx_currency_type',
rules: [{ required: true, message: '必填项', trigger: 'blur' }]
},
{
type: 'select',
prop: 'originalCurrency',
label: '原币种',
dictType: 'bx_currency_type',
rules: [{ required: true, message: '必填项', trigger: 'blur' }]
},
{
type: 'select',
prop: 'payoutCurrency',
label: '发放币种',
dictType: 'bx_currency_type',
rules: [{ required: true, message: '必填项', trigger: 'blur' }]
},
{
type: 'input',
prop: 'originalAmount',
label: '原币种金额',
inputType: 'decimal',
rules: [{ required: true, message: '只能输入正整数和小数', trigger: 'blur' }]
},
// 汇率3(原币种->港币)
{
type: 'input',
prop: 'originalToHkdRate',
label: '原币种->港币(汇率)',
inputType: 'decimal',
decimalDigits: 5,
rules: [{ required: true, message: '只能输入正整数和小数', trigger: 'blur' }]
// defaultValue: 1
},
{
type: 'input',
prop: 'hkdAmount',
label: '港币金额',
inputType: 'decimal',
rules: [{ required: true, message: '只能输入正整数和小数', trigger: 'blur' }]
},
// 汇率1(港币->发放币种)
{
type: 'input',
prop: 'hkdToPayoutRate',
label: '港币->发放币种(汇率)',
inputType: 'decimal',
decimalDigits: 5,
rules: [{ required: true, message: '只能输入正整数和小数', trigger: 'blur' }]
// defaultValue: 1
},
{
type: 'input',
prop: 'payoutAmount',
label: '实际发放金额',
inputType: 'decimal',
rules: [{ required: true, message: '只能输入正整数和小数', trigger: 'blur' }]
},
//汇率2(保单币种->港币)入账检核汇率
{
type: 'input',
prop: 'exchangeRate',
label: '保单币种->港币(入账检核汇率)',
inputType: 'decimal',
decimalDigits: 5,
rules: [{ required: true, message: '只能输入正整数和小数', trigger: 'blur' }]
// defaultValue: 1
},
{
type: 'input',
prop: 'ruleAmount',
label: '保单币种金额',
inputType: 'decimal',
rules: [{ required: true, message: '只能输入正整数和小数', trigger: 'blur' }]
}
] ]
// 应收单类型 // 应收单类型
const fortuneBizTypeOptions = [ const fortuneBizTypeOptions = [
{ value: 'R', label: '关联保单应付单' }, { value: 'R', label: '关联保单应付单' },
{ value: 'U', label: '非关联保单应付单' } { value: 'U', label: '非关联保单应付单' }
] ]
// 可选性校验
const isSelectable = row => {
// status 为 6 的行禁止勾选
return row.status !== '6'
}
const confirmRateExchange = async () => {
let formData = await rateExchangeFormRef.value.validate()
formData.fortuneBizId = selectedRow.value.fortuneBizId
try {
//等待后端接口
const res = await editExchangeRateApi(formData)
ElMessage.success('结算汇率修改成功')
rateExchangeFlag.value = false
const params = searchFormRef.value.getFormData()
loadTableData(params)
} catch (error) {
ElMessage.error('结算汇率修改失败')
rateExchangeFlag.value = true
if (error.message && error.message.includes('Validation')) {
ElMessage.error('必填项不能为空')
}
}
}
const handleInputChange = async (formType, prop, value, item) => {
if (formType == 'rateExchange') {
// 1.计算港币金额
if (prop === 'originalToHkdRate' && value && rateExchangeForm.value.originalAmount) {
// 计算港币金额 原币种金额*原币种->港币
const originalAmount = rateExchangeForm.value.originalAmount
const originalToHkdRate = value
const hkdAmount = calculateAmount(originalAmount, originalToHkdRate, 2)
await nextTick()
rateExchangeForm.value.hkdAmount = hkdAmount
const hkdToPayoutRate = rateExchangeForm.value.hkdToPayoutRate
if (hkdToPayoutRate) {
const payoutAmount = calculateAmount(hkdAmount, hkdToPayoutRate, 2)
await nextTick()
rateExchangeForm.value.payoutAmount = payoutAmount
}
const exchangeRate = rateExchangeForm.value.exchangeRate
if (exchangeRate) {
const ruleAmount = calculateAmount(hkdAmount, exchangeRate, 2, 'Divided')
await nextTick()
rateExchangeForm.value.ruleAmount = ruleAmount
}
} else if (prop === 'originalAmount' && value && rateExchangeForm.value.originalToHkdRate) {
// 计算港币金额
const originalAmount = value
const originalToHkdRate = rateExchangeForm.value.originalToHkdRate
const hkdAmount = calculateAmount(originalAmount, originalToHkdRate, 2)
await nextTick()
rateExchangeForm.value.hkdAmount = hkdAmount
const hkdToPayoutRate = rateExchangeForm.value.hkdToPayoutRate
if (hkdToPayoutRate) {
const payoutAmount = calculateAmount(hkdAmount, hkdToPayoutRate, 2)
await nextTick()
rateExchangeForm.value.payoutAmount = payoutAmount
}
const exchangeRate = rateExchangeForm.value.exchangeRate
if (exchangeRate) {
const ruleAmount = calculateAmount(hkdAmount, exchangeRate, 2, 'Divided')
await nextTick()
rateExchangeForm.value.ruleAmount = ruleAmount
}
}
//2.计算实际发放金额 港币金额*港币->发放币种汇率
if (prop == 'hkdAmount' && value && rateExchangeForm.value.hkdToPayoutRate) {
// 计算实际发放金额
const hkdToPayoutRate = rateExchangeForm.value.hkdToPayoutRate
const hkdAmount = value
const payoutAmount = calculateAmount(hkdAmount, hkdToPayoutRate, 2)
await nextTick()
rateExchangeForm.value.payoutAmount = payoutAmount
} else if (prop == 'hkdToPayoutRate' && value && rateExchangeForm.value.hkdAmount) {
// 计算实际发放金额
const hkdToPayoutRate = value
const hkdAmount = rateExchangeForm.value.hkdAmount
const payoutAmount = calculateAmount(hkdAmount, hkdToPayoutRate, 2)
await nextTick()
rateExchangeForm.value.payoutAmount = payoutAmount
}
//3.计算保单币种金额 港币金额*保单币种->港币汇率
if (prop == 'hkdAmount' && value && rateExchangeForm.value.exchangeRate) {
// 计算保单币种金额
const exchangeRate = rateExchangeForm.value.exchangeRate
const hkdAmount = value
const ruleAmount = calculateAmount(hkdAmount, exchangeRate, 2, 'Divided')
await nextTick()
rateExchangeForm.value.ruleAmount = ruleAmount
} else if (prop == 'exchangeRate' && value && rateExchangeForm.value.hkdAmount) {
// 计算保单币种金额
const exchangeRate = value
const hkdAmount = rateExchangeForm.value.hkdAmount
const ruleAmount = calculateAmount(hkdAmount, exchangeRate, 2, 'Divided')
await nextTick()
rateExchangeForm.value.ruleAmount = ruleAmount
}
} else if (formType == 'addCheckRecord') {
await nextTick()
let policyNo = addCheckRecordFormModel.value.policyNo
let fortunePeriod = addCheckRecordFormModel.value.fortunePeriod
if ((prop == 'policyNo' || prop == 'fortunePeriod') && fortunePeriod && policyNo) {
//等待后端接口
const res = await commissionExchangeRateApi({
policyNo: policyNo,
commissionPeriod: fortunePeriod
})
if (res.code == 200) {
addCheckRecordFormModel.value.exchangeRate = res.data
} else {
ElMessage.error('查询结算汇率失败')
}
}
// 1.计算港币金额
if (prop === 'originalToHkdRate' && value && addCheckRecordFormModel.value.originalAmount) {
// 计算港币金额 原币种金额*原币种->港币
const originalAmount = addCheckRecordFormModel.value.originalAmount
const originalToHkdRate = value
const hkdAmount = calculateAmount(originalAmount, originalToHkdRate, 2)
await nextTick()
addCheckRecordFormModel.value.hkdAmount = hkdAmount
const hkdToPayoutRate = addCheckRecordFormModel.value.hkdToPayoutRate
if (hkdToPayoutRate) {
const payoutAmount = calculateAmount(hkdAmount, hkdToPayoutRate, 2)
await nextTick()
addCheckRecordFormModel.value.payoutAmount = payoutAmount
}
const exchangeRate = addCheckRecordFormModel.value.exchangeRate
if (exchangeRate) {
const ruleAmount = calculateAmount(hkdAmount, exchangeRate, 2, 'Divided')
await nextTick()
addCheckRecordFormModel.value.ruleAmount = ruleAmount
}
} else if (
prop === 'originalAmount' &&
value &&
addCheckRecordFormModel.value.originalToHkdRate
) {
// 计算港币金额
const originalAmount = value
const originalToHkdRate = addCheckRecordFormModel.value.originalToHkdRate
const hkdAmount = calculateAmount(originalAmount, originalToHkdRate, 2)
console.log('新增', hkdAmount)
await nextTick()
addCheckRecordFormModel.value.hkdAmount = hkdAmount
const hkdToPayoutRate = addCheckRecordFormModel.value.hkdToPayoutRate
if (hkdToPayoutRate) {
const payoutAmount = calculateAmount(hkdAmount, hkdToPayoutRate, 2)
await nextTick()
addCheckRecordFormModel.value.payoutAmount = payoutAmount
}
const exchangeRate = addCheckRecordFormModel.value.exchangeRate
if (exchangeRate) {
const ruleAmount = calculateAmount(hkdAmount, exchangeRate, 2, 'Divided')
await nextTick()
addCheckRecordFormModel.value.ruleAmount = ruleAmount
}
}
//2.计算实际发放金额 港币金额*港币->发放币种汇率
if (prop == 'hkdAmount' && value && addCheckRecordFormModel.value.hkdToPayoutRate) {
// 计算实际发放金额
const hkdToPayoutRate = addCheckRecordFormModel.value.hkdToPayoutRate
const hkdAmount = value
const payoutAmount = calculateAmount(hkdAmount, hkdToPayoutRate, 2)
await nextTick()
addCheckRecordFormModel.value.payoutAmount = payoutAmount
} else if (prop == 'hkdToPayoutRate' && value && addCheckRecordFormModel.value.hkdAmount) {
// 计算实际发放金额
const hkdToPayoutRate = value
const hkdAmount = addCheckRecordFormModel.value.hkdAmount
const payoutAmount = calculateAmount(hkdAmount, hkdToPayoutRate, 2)
await nextTick()
addCheckRecordFormModel.value.payoutAmount = payoutAmount
}
//3.计算保单币种金额 港币金额*保单币种->港币汇率
if (prop == 'hkdAmount' && value && addCheckRecordFormModel.value.exchangeRate) {
// 计算保单币种金额
const exchangeRate = addCheckRecordFormModel.value.exchangeRate
const hkdAmount = value
const ruleAmount = calculateAmount(hkdAmount, exchangeRate, 2, 'Divided')
await nextTick()
addCheckRecordFormModel.value.ruleAmount = ruleAmount
} else if (prop == 'exchangeRate' && value && addCheckRecordFormModel.value.hkdAmount) {
// 计算保单币种金额
const exchangeRate = value
const hkdAmount = addCheckRecordFormModel.value.hkdAmount
const ruleAmount = calculateAmount(hkdAmount, exchangeRate, 2, 'Divided')
await nextTick()
addCheckRecordFormModel.value.ruleAmount = ruleAmount
}
}
}
// 精确乘法并保留小数位数
const multiply = (a, b, digits = 2, calculatedType) => {
if (a == null || b == null) return null
const numA = parseFloat(a)
const numB = parseFloat(b)
if (isNaN(numA) || isNaN(numB)) return null
if (calculatedType && calculatedType == 'Divided') {
return round(numA / numB, digits)
}
return round(numA * numB, digits)
}
const handleSplitCellChange = ({ row, col, newValue, oldValue, rowIndex, prop }) => {
// 避免同一字段重复触发(如修改 chuAmount 会触发 hkdAmount 计算,该计算会再次触发 cell-change)
const currentRow = splitTableData.value[rowIndex]
if (!currentRow) return
let needUpdate = false
const updatedRow = { ...currentRow }
// 联动规则1:计算原币种金额 应出账比例*应出账金额(HKD)
if (prop === 'splitRatio' && selectedRow.value.originalAmount) {
let ratio = prop === 'splitRatio' ? newValue : currentRow.splitRatio
if (ratio) {
ratio = ratio / 100
}
console.log('出账', selectedRow.value.originalAmount, ratio)
const originalAmount = calculateAmount(selectedRow.value.originalAmount, ratio, 2)
updatedRow.originalAmount = originalAmount
// 原币种金额发生变化更新港币金额
if (updatedRow.originalToHkdRate) {
updatedRow.hkdAmount = calculateAmount(
updatedRow.originalAmount,
updatedRow.originalToHkdRate,
2
)
}
//港币金额发生变化 更新实际发放金额
if (updatedRow.hkdToPayoutRate && updatedRow.hkdAmount) {
updatedRow.payoutAmount = calculateAmount(updatedRow.hkdAmount, updatedRow.hkdToPayoutRate, 2)
}
//港币金额发生变化 更新保单币种金额
if (updatedRow.exchangeRate && updatedRow.hkdAmount) {
updatedRow.ruleAmount = calculateAmount(
updatedRow.hkdAmount,
updatedRow.exchangeRate,
2,
'Divided'
)
}
needUpdate = true
}
// 联动规则2:原币种->港币 或 原币种金额 变化 → 重算 港币金额 = 原币种金额*原币种->港币
if (prop === 'originalToHkdRate' || prop === 'originalAmount') {
const originalToHkdRate = prop === 'originalToHkdRate' ? newValue : currentRow.originalToHkdRate
const originalAmount = prop === 'originalAmount' ? newValue : currentRow.originalAmount
const hkdAmount = calculateAmount(originalAmount, originalToHkdRate, 2)
updatedRow.hkdAmount = hkdAmount
//港币金额发生变化 更新实际发放金额
if (updatedRow.hkdToPayoutRate && updatedRow.hkdAmount) {
updatedRow.payoutAmount = calculateAmount(updatedRow.hkdAmount, updatedRow.hkdToPayoutRate, 2)
}
//港币金额发生变化 更新保单币种金额
if (updatedRow.exchangeRate && updatedRow.hkdAmount) {
updatedRow.ruleAmount = calculateAmount(
updatedRow.hkdAmount,
updatedRow.exchangeRate,
2,
'Divided'
)
}
needUpdate = true
}
// 联动规则3:港币->发放币种 或 港币金额 变化 → 重算 实际发放金额 = 港币金额*港币->发放币种汇率
if (prop === 'hkdToPayoutRate' || prop === 'hkdAmount') {
const hkdToPayoutRate = prop === 'hkdToPayoutRate' ? newValue : currentRow.hkdToPayoutRate
const hkdAmount = prop === 'hkdAmount' ? newValue : currentRow.hkdAmount
const payoutAmount = calculateAmount(hkdAmount, hkdToPayoutRate, 2)
updatedRow.payoutAmount = payoutAmount
needUpdate = true
}
// 联动规则4:保单币种->港币 或 港币金额 变化 → 重算 保单币种金额 = 港币金额/保单币种->港币汇率
if (prop === 'exchangeRate' || prop === 'hkdAmount') {
const exchangeRate = prop === 'exchangeRate' ? newValue : currentRow.exchangeRate
const hkdAmount = prop === 'hkdAmount' ? newValue : currentRow.hkdAmount
const ruleAmount = calculateAmount(hkdAmount, exchangeRate, 2, 'Divided')
updatedRow.ruleAmount = ruleAmount
needUpdate = true
}
// 如果用户手动修改了 hkdAmount,不做反向联动(符合需求)
if (needUpdate) {
// 更新表格数据(整体替换,触发视图更新)
const newData = [...splitTableData.value]
newData[rowIndex] = updatedRow
splitTableData.value = newData
}
}
const handleSpiltSubmit = async () => {
// 调用组件的校验方法
const isValid = await editTableRef.value.validateAndShow()
if (isValid) {
try {
let params = {
fortuneBizId: selectedRow.value.fortuneBizId,
fortuneSplitDtoList: splitTableData.value
}
console.log('分期出账参数', params)
const res = await billSplitApi(params)
console.log('分期出账结果', res)
installmentsBillFlag.value = false
ElMessage.success('分期出账已保存')
const searchParams = searchFormRef.value.getFormData()
loadTableData(searchParams)
} catch (error) {
installmentsBillFlag.value = true
console.log('分期出账错误', error)
ElMessage.error('分期出账失败')
}
}
}
const handleSpiltDelete = row => {
let index = splitTableData.value.findIndex(item => item.id == row.id)
splitTableData.value.splice(index, 1)
}
// 生成临时唯一ID
let nextId = 1
const generateId = () => Date.now() + nextId++
// 添加出账
const addSpiltRecord = () => {
if (splitTableData.value.length > 0) {
let ratio = 0
splitTableData.value.forEach(item => {
ratio += Number(item.splitRatio)
})
console.log('比例', ratio)
if (ratio > 100 || ratio == 100) {
ElMessage.error('出账比例不能大于100%')
return
}
}
const newRow = {
id: generateId(), // 必须有 rowIdKey 对应的字段(默认 'id')
payoutCurrency: 'HKD',
ruleCurrency: selectedRow.value.ruleCurrency,
originalCurrency: selectedRow.value.originalCurrency,
hkdToPayoutRate: '1',
exchangeRate: selectedRow.value.exchangeRate
}
if (selectedRow.value.originalCurrency && selectedRow.value.originalCurrency == 'HKD') {
newRow.originalToHkdRate = '1'
}
console.log('====================================')
console.log('newRow', newRow)
console.log('====================================')
// 插入到表格数据最前面
splitTableData.value.push(newRow)
}
// 应付单类型通过value转成label // 应付单类型通过value转成label
const getFortuneBizTypeLabel = value => { const getFortuneBizTypeLabel = value => {
const item = fortuneBizTypeOptions.find(item => item.value === value) const item = fortuneBizTypeOptions.find(item => item.value === value)
...@@ -308,17 +1067,25 @@ const addCheckRecordFormModel = ref({}) ...@@ -308,17 +1067,25 @@ const addCheckRecordFormModel = ref({})
const addCheckRecordFormRef = ref(null) const addCheckRecordFormRef = ref(null)
const addCheckRecordFormDialogFlag = ref(false) const addCheckRecordFormDialogFlag = ref(false)
const addCheckRecordConfig = [ const addCheckRecordConfig = [
// {
// type: 'month',
// prop: 'reconciliationYearMonth',
// label: '检核年月',
// placeholder: '检核年月'
// },
{ {
type: 'select', type: 'select',
prop: 'fortuneBizType', prop: 'fortuneBizType',
label: '应付单类型', label: '应付单类型',
options: fortuneBizTypeOptions options: fortuneBizTypeOptions,
rules: [{ required: true, message: '应付单类型必填', trigger: 'blur' }]
}, },
{ {
type: 'select', type: 'select',
prop: 'status', prop: 'status',
label: '出账状态', label: '出账状态',
dictType: 'csf_expected_fortune_status' dictType: 'csf_expected_fortune_status',
rules: [{ required: true, message: '出账状态必填', trigger: 'blur' }]
}, },
{ {
type: 'input', type: 'input',
...@@ -332,7 +1099,7 @@ const addCheckRecordConfig = [ ...@@ -332,7 +1099,7 @@ const addCheckRecordConfig = [
label: '佣金期数', label: '佣金期数',
inputType: 'decimal', inputType: 'decimal',
visible: formData => formData.fortuneBizType === 'R', visible: formData => formData.fortuneBizType === 'R',
rules: [{ pattern: /^\d+$/, message: '只能输入正整数', trigger: 'blur' }] rules: [{ required: true, pattern: /^\d+$/, message: '只能输入正整数', trigger: 'blur' }]
}, },
{ {
type: 'input', type: 'input',
...@@ -342,32 +1109,52 @@ const addCheckRecordConfig = [ ...@@ -342,32 +1109,52 @@ const addCheckRecordConfig = [
visible: formData => formData.fortuneBizType === 'R', visible: formData => formData.fortuneBizType === 'R',
rules: [{ pattern: /^\d+$/, message: '只能输入正整数', trigger: 'blur' }] rules: [{ pattern: /^\d+$/, message: '只能输入正整数', trigger: 'blur' }]
}, },
{ {
type: 'date', type: 'month',
prop: 'actualPayoutDate', prop: 'payoutDate',
label: '出账日(实)', label: '出账月(估)',
placeholder: '请选择', placeholder: '请选择',
maxDate: 'today' maxDate: 'today',
}, rules: [{ required: true, message: '出账月(估)必填', trigger: 'blur' }]
{
type: 'input',
prop: 'amount',
label: '出账金额',
inputType: 'decimalNumber',
decimalDigits: 4,
rules: [{ pattern: /^-?\d+(\.\d+)?$/, message: '请输入有效的数字', trigger: 'blur' }]
}, },
{ {
type: 'select', type: 'month',
prop: 'currency', prop: 'actualPayoutDate',
label: '出账币种', label: '出账月(实)',
dictType: 'bx_currency_type' placeholder: '请选择',
maxDate: 'today',
rules: [{ required: true, message: '出账月(实)必填', trigger: 'blur' }]
}, },
// {
// type: 'input',
// prop: 'amount',
// label: '出账金额',
// inputType: 'decimalNumber',
// decimalDigits: 4,
// rules: [{ pattern: /^-?\d+(\.\d+)?$/, message: '请输入有效的数字', trigger: 'blur' }]
// },
// {
// type: 'select',
// prop: 'currency',
// label: '出账币种',
// dictType: 'bx_currency_type'
// },
// {
// type: 'input',
// prop: 'fortuneName',
// label: '出账项目名称',
// rules: [{ required: true, message: '出账项目名称必填', trigger: 'blur' }]
// },
{ {
type: 'select', type: 'select',
prop: 'fortuneType', prop: 'fortuneType',
label: '出账项目', label: '出账项目类型',
dictType: 'csf_fortune_type' dictType: 'csf_fortune_type',
rules: [{ required: true, message: '出账项目类型必填', trigger: 'blur' }],
onChangeExtraFields: {
fortuneName: 'itemLabel'
}
}, },
{ {
type: 'select', type: 'select',
...@@ -382,13 +1169,122 @@ const addCheckRecordConfig = [ ...@@ -382,13 +1169,122 @@ const addCheckRecordConfig = [
labelKey: 'realName', labelKey: 'realName',
onChangeExtraFields: { onChangeExtraFields: {
broker: 'realName', // 自动同步 raw.name 到 reconciliationCompany broker: 'realName', // 自动同步 raw.name 到 reconciliationCompany
reconciliationCompanyCode: 'code', reconciliationCompanyCode: 'code'
team:'deptName', // team: 'deptName',
teamBizId:'deptBizId', // teamBizId: 'deptBizId'
},
transform: res => {
return res?.data.records || []
},
rules: [{ required: true, message: '转介人必填', trigger: 'blur' }]
},
{
type: 'select',
prop: 'teamBizId',
label: '所属团队',
api: '/csf/api/team/page',
keywordField: 'teamName',
requestParams: { pageNo: 1, pageSize: 200 },
placeholder: '输入所属团队名称搜索',
debounceWait: 500, // 自定义防抖时间
valueKey: 'teamBizId',
labelKey: 'teamName',
onChangeExtraFields: {
// broker: 'realName', // 自动同步 raw.name 到 reconciliationCompany
// reconciliationCompanyCode: 'code',
team: 'teamName',
teamBizId: 'teamBizId'
}, },
transform: res => { transform: res => {
return res?.data.records || [] return res?.data.records || []
} }
// rules: [{ required: true, message: '所属团队必填', trigger: 'blur' }]
},
{
type: 'select',
prop: 'ruleCurrency',
label: '保单币种',
dictType: 'bx_currency_type',
rules: [{ required: true, message: '保单币种必填', trigger: 'blur' }]
},
{
type: 'select',
prop: 'originalCurrency',
label: '原币种',
dictType: 'bx_currency_type',
rules: [{ required: true, message: '原币种必填', trigger: 'blur' }]
},
{
type: 'select',
prop: 'payoutCurrency',
label: '发放币种',
dictType: 'bx_currency_type',
rules: [{ required: true, message: '发放币种必填', trigger: 'blur' }]
},
{
type: 'input',
prop: 'originalAmount',
label: '原币种金额',
inputType: 'decimal',
rules: [{ required: true, message: '只能输入正整数和小数', trigger: 'blur' }]
},
// 汇率3(原币种->港币)
{
type: 'input',
prop: 'originalToHkdRate',
label: '原币种->港币(汇率)',
decimalDigits: 5,
inputType: 'decimal',
rules: [{ required: true, message: '只能输入正整数和小数', trigger: 'blur' }]
// defaultValue: 1
},
{
type: 'input',
prop: 'hkdAmount',
label: '港币金额',
inputType: 'decimal',
rules: [{ required: true, message: '只能输入正整数和小数', trigger: 'blur' }]
},
// 汇率1(港币->发放币种)
{
type: 'input',
prop: 'hkdToPayoutRate',
label: '港币->发放币种(汇率)',
inputType: 'decimal',
decimalDigits: 5,
rules: [{ required: true, message: '只能输入正整数和小数', trigger: 'blur' }]
// defaultValue: 1
},
{
type: 'input',
prop: 'payoutAmount',
label: '实际发放金额',
inputType: 'decimal',
rules: [{ required: true, message: '只能输入正整数和小数', trigger: 'blur' }]
},
// 汇率2(保单币种->港币)入账检核汇率
{
type: 'input',
prop: 'exchangeRate',
label: '保单币种->港币(入账检核汇率)',
inputType: 'decimal',
decimalDigits: 5,
rules: [{ required: true, message: '只能输入正整数和小数', trigger: 'blur' }]
// defaultValue: 1
},
{
type: 'input',
prop: 'ruleAmount',
label: '保单币种金额',
inputType: 'decimal',
rules: [{ required: true, message: '只能输入正整数和小数', trigger: 'blur' }]
},
{
type: 'input',
prop: 'remark',
label: '备注'
} }
] ]
...@@ -431,6 +1327,7 @@ const dialogFlag = ref(false) ...@@ -431,6 +1327,7 @@ const dialogFlag = ref(false)
// 按钮事件处理 // 按钮事件处理
const handleAdd = () => { const handleAdd = () => {
addCheckRecordFormModel.value = {}
addCheckRecordFormDialogFlag.value = true addCheckRecordFormDialogFlag.value = true
} }
const handleImport = () => { const handleImport = () => {
...@@ -442,9 +1339,11 @@ const handleExport = () => { ...@@ -442,9 +1339,11 @@ const handleExport = () => {
const handleReset = () => { const handleReset = () => {
// 重置搜索表单 // 重置搜索表单
searchFormRef.value.resetForm() searchFormRef.value.resetForm()
loadTableData()
console.log('表单已重置') console.log('表单已重置')
} }
const handleQuery = async () => { const handleQuery = async () => {
currentPage.value = 1
const params = searchFormRef.value.getFormData() const params = searchFormRef.value.getFormData()
loadTableData(params) loadTableData(params)
} }
...@@ -503,11 +1402,13 @@ loadTableData() ...@@ -503,11 +1402,13 @@ loadTableData()
// 分页事件 // 分页事件
const handleSizeChange = val => { const handleSizeChange = val => {
pageSize.value = val pageSize.value = val
loadTableData() const params = searchFormRef.value.getFormData()
loadTableData(params)
} }
const handleCurrentChange = val => { const handleCurrentChange = val => {
currentPage.value = val currentPage.value = val
loadTableData() const params = searchFormRef.value.getFormData()
loadTableData(params)
} }
// 表格数据 // 表格数据
const tableData = ref([]) const tableData = ref([])
...@@ -517,11 +1418,34 @@ const handleSelect = (e, row) => { ...@@ -517,11 +1418,34 @@ const handleSelect = (e, row) => {
selectedRow.value = row selectedRow.value = row
if (e === 'setPayRoll') { if (e === 'setPayRoll') {
setPayoutAmountDialogFlag.value = true setPayoutAmountDialogFlag.value = true
} else if (e == 'billInstallments') {
splitTableData.value = []
installmentsBillFlag.value = true
} else if (e == 'settingBillYearMonth') {
billTimeType.value = 'single'
settingBillTimeForm.value = {}
settingBillYearMonthFlag.value = true
} else if (e == 'editExchangeRate') {
rateExchangeForm.value = {
originalAmount: row.originalAmount ? Number(row.originalAmount).toFixed(2) : '',
ruleCurrency: row.ruleCurrency,
originalCurrency: row.originalCurrency,
payoutCurrency: row.payoutCurrency,
hkdToPayoutRate: '',
exchangeRate: row.exchangeRate ? row.exchangeRate : '',
originalToHkdRate: '',
payoutAmount: row.payoutAmount ? Number(row.payoutAmount).toFixed(2) : '',
ruleAmount: row.ruleAmount ? Number(row.ruleAmount).toFixed(2) : '',
hkdAmount: row.hkdAmount ? Number(row.hkdAmount).toFixed(2) : ''
}
rateExchangeFlag.value = true
} }
} }
const addCheckRecordaddBatchapi = async data => { const addCheckRecordaddBatchapi = async data => {
const formData = addCheckRecordFormRef.value.getFormData() let formData = await addCheckRecordFormRef.value.validate()
// const formData = addCheckRecordFormRef.value.getFormData()
console.log('新增出账检核记录:', formData) console.log('新增出账检核记录:', formData)
// return
const params = [{ ...formData }] const params = [{ ...formData }]
try { try {
const res = await addCheckRecordaddBatch(params) const res = await addCheckRecordaddBatch(params)
...@@ -568,16 +1492,58 @@ const handleSelectionChange = val => { ...@@ -568,16 +1492,58 @@ const handleSelectionChange = val => {
// 完成检核按钮是否禁用 // 完成检核按钮是否禁用
updatePayRollStatusDisable.value = val.length === 0 updatePayRollStatusDisable.value = val.length === 0
} }
const submitSettingBillYearMonth = async () => {
if (billTimeType.value == 'multiple') {
//等后端接口
try {
const res = await batchActualPayoutDate({
fortuneBizIdList: multipleSelection.value.map(item => item.fortuneBizId),
actualPayoutDate: settingBillTimeForm.value.actualPayoutDate
})
if (res.code === 200) {
settingBillYearMonthFlag.value = false
ElMessage.success(res.data)
const params = await searchFormRef.value.getFormData()
loadTableData(params)
}
} catch (error) {
console.error('检核失败:', error)
settingBillYearMonthFlag.value = true
ElMessage.error(error.response?.data?.msg || '检核失败')
}
} else if (billTimeType.value == 'single') {
const params = {
fortuneBizId: selectedRow.value.fortuneBizId,
actualPayoutDate: settingBillTimeForm.value.actualPayoutDate
}
try {
const singleRes = await actualPayoutDateApi(params)
ElMessage.success('设置出账年月成功')
settingBillYearMonthFlag.value = false
const searchParams = await searchFormRef.value.getFormData()
loadTableData(searchParams)
} catch (error) {
ElMessage.error('设置出账年月失败')
settingBillYearMonthFlag.value = true
}
}
}
const batchSettingBillYearMonth = () => {
settingBillYearMonthFlag.value = true
settingBillTimeForm.value = {}
billTimeType.value = 'multiple'
}
const downloadPolicyFortuneAccountapi = async data => { const downloadPolicyFortuneAccountapi = async data => {
console.log('生成出账清单:', data) console.log('生成出账清单:', data)
try { try {
const res = await downloadPolicyFortuneAccount({ const res = await downloadPolicyFortuneAccount({
fortuneBizIdList: multipleSelection.value.map(item => item.fortuneBizId) fortuneBizIdList: multipleSelection.value.map(item => item.fortuneBizId)
}) })
if (res.code === 200) { if (res.code === 200) {
ElMessage.success('完成检核,等待关账') ElMessage.success('完成检核,等待关账')
loadTableData() const searchParams = await searchFormRef.value.getFormData()
loadTableData(searchParams)
} }
} catch (error) { } catch (error) {
console.error('检核失败:', error) console.error('检核失败:', error)
...@@ -602,6 +1568,17 @@ const onSubmit = data => { ...@@ -602,6 +1568,17 @@ const onSubmit = data => {
} }
onMounted(async () => {}) onMounted(async () => {})
// 数据变化时清空选中
watch(
() => tableData,
() => {
nextTick(() => {
multipleTableRef.value?.clearSelection()
multipleSelection.value = []
})
},
{ deep: true }
)
</script> </script>
<style scoped> <style scoped>
......
<template> <template>
<div class='container'> <div class="container">
<CommonPage :operationBtnList='operationBtnList' :visibleDefaultButtons="visibleDefaultButtons" <CommonPage
:showSearchForm='true' :show-pagination='true' :total='pageTotal' :current-page='currentPage' :operationBtnList="operationBtnList"
:page-size='pageSize' @size-change='handleSizeChange' @current-change='handleCurrentChange'> :visibleDefaultButtons="visibleDefaultButtons"
:showSearchForm="true"
:show-pagination="true"
:total="pageTotal"
:current-page="currentPage"
:page-size="pageSize"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
>
<!-- 搜索区域 --> <!-- 搜索区域 -->
<template #searchForm> <template #searchForm>
<SearchForm ref="searchFormRef" :config="searchConfig" /> <SearchForm ref="searchFormRef" :config="searchConfig" />
...@@ -12,47 +20,87 @@ ...@@ -12,47 +20,87 @@
<!-- 统计信息卡片 --> <!-- 统计信息卡片 -->
<div class="statistics-container" v-if="statisticsData.totalPolicyCount > 0"> <div class="statistics-container" v-if="statisticsData.totalPolicyCount > 0">
<el-row :gutter="20"> <el-row :gutter="20">
<el-col :xs="24" :sm="12" :md="4" class="text-center mb-4"> <el-col :xs="24" :sm="12" :md="4" class="text-center mb-4">
<el-statistic title="总保单数" :value="statisticsData.totalPolicyCount" /> <el-statistic title="总保单数" :value="statisticsData.totalPolicyCount" />
</el-col> </el-col>
<el-col :xs="24" :sm="12" :md="4" class="text-center mb-4"> <el-col :xs="24" :sm="12" :md="4" class="text-center mb-4">
<el-statistic title="总保费(HKD)" :value="statisticsData.totalPremium" :formatter="value=>formatCurrency(value)" /> <el-statistic
title="总保费(HKD)"
:value="statisticsData.totalPremium"
:formatter="value => formatCurrency(value)"
/>
</el-col> </el-col>
<el-col :xs="24" :sm="12" :md="4" class="text-center mb-4"> <el-col :xs="24" :sm="12" :md="4" class="text-center mb-4">
<el-statistic title="对账公司数" :value="statisticsData.reconciliationCompanyCount" /> <el-statistic title="对账公司数" :value="statisticsData.reconciliationCompanyCount" />
</el-col> </el-col>
<el-col :xs="24" :sm="12" :md="4" class="text-center mb-4"> <el-col :xs="24" :sm="12" :md="4" class="text-center mb-4">
<el-statistic title="比对记录总条数" :value="statisticsData.totalCompareCommissionCount" /> <el-statistic
title="比对记录总条数"
:value="statisticsData.totalCompareCommissionCount"
/>
</el-col> </el-col>
<el-col :xs="24" :sm="12" :md="4" class="text-center mb-4"> <el-col :xs="24" :sm="12" :md="4" class="text-center mb-4">
<el-statistic title="比对成功数" :value="statisticsData.successCompareCommissionCount" /> <el-statistic
title="比对成功数"
:value="statisticsData.successCompareCommissionCount"
/>
</el-col> </el-col>
<el-col :xs="24" :sm="12" :md="4" class="text-center mb-4"> <el-col :xs="24" :sm="12" :md="4" class="text-center mb-4">
<el-statistic title="比对失败数" :value="statisticsData.failedCompareCommissionCount" /> <el-statistic
title="比对失败数"
:value="statisticsData.failedCompareCommissionCount"
/>
</el-col> </el-col>
<el-col :xs="24" :sm="12" :md="4" class="text-center mb-4"> <el-col :xs="24" :sm="12" :md="4" class="text-center mb-4">
<el-statistic title="入账金额(实)" :value="statisticsData.totalPaidAmount" :formatter="value=>formatCurrency(value)" /> <el-statistic
title="入账金额(实)"
:value="statisticsData.totalPaidAmount"
:formatter="value => formatCurrency(value)"
/>
</el-col> </el-col>
<el-col :xs="24" :sm="12" :md="4" class="text-center mb-4"> <el-col :xs="24" :sm="12" :md="4" class="text-center mb-4">
<el-statistic title="入账金额(估)" :value="statisticsData.expectePaidAmount" :formatter="value=>formatCurrency(value)" /> <el-statistic
title="入账金额(估)"
:value="statisticsData.expectePaidAmount"
:formatter="value => formatCurrency(value)"
/>
</el-col> </el-col>
<el-col :xs="24" :sm="12" :md="4" class="text-center mb-4"> <el-col :xs="24" :sm="12" :md="4" class="text-center mb-4">
<el-statistic title="差额(估)" :value="statisticsData.differenceAmount" :formatter="value=>formatCurrency(value)" /> <el-statistic
title="差额(估)"
:value="statisticsData.differenceAmount"
:formatter="value => formatCurrency(value)"
/>
</el-col> </el-col>
</el-row> </el-row>
</div> </div>
<el-row> <el-row>
<el-col :span="6"> <el-col :span="6">
<el-text tag="mark" class="mx-1">实佣率=实际入账金额/结算汇率/每期保费</el-text> <el-text tag="mark" class="mx-1">实佣率=实际入账金额/结算汇率/每期保费</el-text>
</el-col> </el-col>
<el-col :span="6"> <el-col :span="6">
<el-text tag="mark" class="mx-1"> 达成率缺口= 1 - 本期实佣率/产品本期来佣率</el-text> <el-text tag="mark" class="mx-1"> 达成率缺口= 1 - 本期实佣率/保单本期来佣率 </el-text>
</el-col> </el-col>
</el-row> </el-row>
<el-table :data="tableData" ref="multipleTableRef" height="400" row-key="id" border highlight-current-row <el-table
style="width: 100%" v-loading="loading" @selection-change="handleSelectionChange"> :data="tableData"
ref="multipleTableRef"
height="400"
row-key="id"
border
highlight-current-row
style="width: 100%"
v-loading="loading"
@selection-change="handleSelectionChange"
>
<el-table-column type="selection" width="40" :selectable="selectableFn" /> <el-table-column type="selection" width="40" :selectable="selectableFn" />
<el-table-column prop="commissionStatus" label="比对状态" width="120" sortable fixed="left"> <el-table-column
prop="commissionStatus"
label="比对状态"
width="120"
sortable
fixed="left"
>
<template #default="{ row }"> <template #default="{ row }">
{{ selectDictLabel(csf_commission_status, row.commissionStatus) }} {{ selectDictLabel(csf_commission_status, row.commissionStatus) }}
</template> </template>
...@@ -65,24 +113,67 @@ ...@@ -65,24 +113,67 @@
<el-table-column prop="reconciliationYearMonth" label="检核年月" width="120" sortable /> <el-table-column prop="reconciliationYearMonth" label="检核年月" width="120" sortable />
<el-table-column prop="policyNo" label="保单号" width="120" sortable /> <el-table-column prop="policyNo" label="保单号" width="120" sortable />
<el-table-column prop="reconciliationCompany" label="对账公司" width="120" sortable /> <el-table-column prop="reconciliationCompany" label="对账公司" width="120" sortable />
<el-table-column prop="commissionDate" label="入账" width="120" sortable /> <el-table-column prop="commissionDate" label="入账" width="120" sortable />
<el-table-column prop="commissionExpectedStatus" label="入账状态" width="120" sortable> <el-table-column prop="commissionExpectedStatus" label="入账状态" width="120" sortable>
<template #default="{ row }"> <template #default="{ row }">
{{ selectDictLabel(csf_expected_commission_status, row.commissionExpectedStatus) }} {{ selectDictLabel(csf_expected_commission_status, row.commissionExpectedStatus) }}
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="currentCommissionRatio" label="本次实佣率" width="130" sortable :formatter="formatRatio" /> <el-table-column
<el-table-column prop="paidRatio" label="累积实佣率" width="130" sortable :formatter="formatRatio" /> prop="commissionRatio"
<el-table-column prop="pendingRatio" label="达成率缺口" width="120" sortable :formatter="formatRatio" /> label="保单本期来佣率"
<el-table-column prop="amount" label="本次入账金额" width="130" sortable :formatter="formatCurrencyUtil" /> width="120"
:formatter="formatRatio"
>
</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"
/>
<el-table-column
prop="amount"
label="本次入账金额"
width="130"
sortable
:formatter="formatCurrencyUtil"
/>
<el-table-column prop="currency" label="入账币种" width="120" sortable /> <el-table-column prop="currency" label="入账币种" width="120" sortable />
<el-table-column prop="exchangeRate" label="结算汇率(实)" width="140" sortable /> <el-table-column prop="exchangeRate" label="结算汇率(实)" width="140" sortable />
<el-table-column prop="commissionPeriod" label="本次入账期数" width="130" sortable /> <el-table-column prop="commissionPeriod" label="本次入账期数" width="130" sortable />
<el-table-column prop="totalPeriod" label="总期数" width="120" sortable /> <el-table-column prop="totalPeriod" label="总期数" width="120" sortable />
<el-table-column prop="commissionName" label="入账项目" width="120" sortable /> <el-table-column prop="commissionName" label="入账项目" width="120" sortable />
<el-table-column prop="premium" label="期交保费" width="120" sortable :formatter="formatCurrencyUtil" /> <el-table-column
prop="premium"
label="期交保费"
width="120"
sortable
:formatter="formatCurrencyUtil"
/>
<el-table-column prop="policyCurrency" label="保单币种" width="120" sortable /> <el-table-column prop="policyCurrency" label="保单币种" width="120" sortable />
<el-table-column prop="remark" label="备注" width="120" sortable /> <el-table-column prop="productName" label="产品名称" width="120" />
<el-table-column prop="policyHolder" label="投保人" width="120" />
<el-table-column prop="policyHolderEn" label="投保人(英文)" width="120" />
<el-table-column prop="broker" label="转介人" width="120" />
<el-table-column prop="insuranceCompany" label="保险公司" width="120" />
<el-table-column prop="manualRemark" label="备注" width="120" sortable />
<el-table-column prop="isDeleted" label="记录状态" width="120" sortable> <el-table-column prop="isDeleted" label="记录状态" width="120" sortable>
<template #default="{ row }"> <template #default="{ row }">
{{ row.isDeleted === 1 ? '无效' : '有效' }} {{ row.isDeleted === 1 ? '无效' : '有效' }}
...@@ -99,9 +190,17 @@ ...@@ -99,9 +190,17 @@
<el-menu @select="handleSelect($event, row)" popper-class="custom-menu"> <el-menu @select="handleSelect($event, row)" popper-class="custom-menu">
<el-menu-item :index="item.value" v-for="item in dropdownItems" :key="item.value"> <el-menu-item :index="item.value" v-for="item in dropdownItems" :key="item.value">
<el-text class="mx-1" v-if="!item.confirm">{{ item.label }}</el-text> <el-text class="mx-1" v-if="!item.confirm">{{ item.label }}</el-text>
<el-popconfirm v-if="item.confirm" confirm-button-text="Yes" cancel-button-text="No" <el-popconfirm
:icon="InfoFilled" icon-color="#626AEF" :title="item.confirm" v-if="item.confirm"
@confirm="handleMenuConfirm(item.value, row)" width="300" placement="left-end"> confirm-button-text="Yes"
cancel-button-text="No"
:icon="InfoFilled"
icon-color="#626AEF"
:title="item.confirm"
@confirm="handleMenuConfirm(item.value, row)"
width="300"
placement="left-end"
>
<template #reference> <template #reference>
<el-text class="mx-1">{{ item.label }}</el-text> <el-text class="mx-1">{{ item.label }}</el-text>
</template> </template>
...@@ -119,17 +218,27 @@ ...@@ -119,17 +218,27 @@
<el-text type="danger">*非关联保单应收单以及比对失败的不可生成可出账记录</el-text> <el-text type="danger">*非关联保单应收单以及比对失败的不可生成可出账记录</el-text>
</el-col> </el-col>
<el-col :span="4"> <el-col :span="4">
<el-button type="primary" :icon="Select" :disabled="updatePayRollStatusDisable" <el-button
@click="generateCommissionRecordapi">{{ multipleSelection.length }} 条数据,生成可出账记录</el-button> type="primary"
:icon="Select"
:disabled="updatePayRollStatusDisable"
@click="generateCommissionRecordapi"
>{{ multipleSelection.length }} 条数据,生成可出账记录</el-button
>
</el-col> </el-col>
</el-row> </el-row>
</div> </div>
</template> </template>
</CommonPage> </CommonPage>
<!-- 开始检核弹窗--> <!-- 开始检核弹窗-->
<CommonDialog dialogTitle='开始检核' dialogWidth='80%' :openDialog=dialogFlag :showAction='false' :showClose='true' <CommonDialog
@close="closthDialog()"> dialogTitle="开始检核"
dialogWidth="80%"
:openDialog="dialogFlag"
:showAction="false"
:showClose="true"
@close="closthDialog()"
>
<el-row> <el-row>
<el-col :xs="24" :sm="24" :md="24" :lg="24"> <el-col :xs="24" :sm="24" :md="24" :lg="24">
<SearchForm ref="checkFormRef" :config="checkConfig" v-model="checkFormData" /> <SearchForm ref="checkFormRef" :config="checkConfig" v-model="checkFormData" />
...@@ -137,12 +246,22 @@ ...@@ -137,12 +246,22 @@
</el-row> </el-row>
<el-row :gutter="10"> <el-row :gutter="10">
<el-col :xs="24" :sm="12" :md="3" :lg="3"> <el-col :xs="24" :sm="12" :md="3" :lg="3">
<el-button type="primary" :icon="Plus" :disabled="!checkFormData?.reconciliationYearMonth" <el-button
@click="handleAddCheckList()">新增</el-button> type="primary"
:icon="Plus"
:disabled="!checkFormData?.reconciliationYearMonth"
@click="handleAddCheckList()"
>新增</el-button
>
</el-col> </el-col>
<el-col :xs="24" :sm="24" :md="3" :lg="3"> <el-col :xs="24" :sm="24" :md="3" :lg="3">
<el-button type="primary" :icon="Upload" :disabled="!checkFormData?.reconciliationYearMonth" <el-button
@click="fileUploadDialogFlag = true;">导入</el-button> type="primary"
:icon="Upload"
:disabled="!checkFormData?.reconciliationYearMonth"
@click="fileUploadDialogFlag = true"
>导入</el-button
>
</el-col> </el-col>
<el-col :xs="24" :sm="24" :md="3" :lg="3"> <el-col :xs="24" :sm="24" :md="3" :lg="3">
<el-button type="primary" link @click="downloadTemplate">下载模板</el-button> <el-button type="primary" link @click="downloadTemplate">下载模板</el-button>
...@@ -151,7 +270,7 @@ ...@@ -151,7 +270,7 @@
<el-row :gutter="10"> <el-row :gutter="10">
<el-col :xs="24" :sm="24" :md="24" :lg="24"> <el-col :xs="24" :sm="24" :md="24" :lg="24">
<el-table :data="checkRecordTableData" style="width: 100%" height="400"> <el-table :data="checkRecordTableData" style="width: 100%" height="400">
<el-table-column prop="commissionBizType" label="应收款类型" width="120"> <el-table-column prop="commissionBizType" label="应收款类型" width="150">
<template #default="{ row }"> <template #default="{ row }">
{{ getCommissionBizTypeLabel(row.commissionBizType) }} {{ getCommissionBizTypeLabel(row.commissionBizType) }}
</template> </template>
...@@ -165,8 +284,12 @@ ...@@ -165,8 +284,12 @@
</el-table-column> </el-table-column>
<el-table-column prop="commissionPeriod" label="佣金期数" width="120" /> <el-table-column prop="commissionPeriod" label="佣金期数" width="120" />
<el-table-column prop="totalPeriod" label="总期数" width="120" /> <el-table-column prop="totalPeriod" label="总期数" width="120" />
<el-table-column prop="commissionDate" label="入账日(实)" width="120" /> <el-table-column prop="commissionDate" label="入账年月(实)" width="120" />
<el-table-column prop="amount" label="入账金额" width="120" /> <el-table-column prop="amount" label="入账金额" width="120">
<template #default="{ row }">
{{ formatCurrency(row.amount) }}
</template>
</el-table-column>
<el-table-column prop="currency" label="入账币种" width="120" /> <el-table-column prop="currency" label="入账币种" width="120" />
<el-table-column prop="exchangeRate" label="结算汇率" width="120" /> <el-table-column prop="exchangeRate" label="结算汇率" width="120" />
<el-table-column prop="commissionName" label="入账项目" width="120" /> <el-table-column prop="commissionName" label="入账项目" width="120" />
...@@ -180,11 +303,16 @@ ...@@ -180,11 +303,16 @@
<el-button type="primary" text size="small" @click="checkRecordEdit(row)"> <el-button type="primary" text size="small" @click="checkRecordEdit(row)">
修改 修改
</el-button> </el-button>
<el-popconfirm confirm-button-text="Yes" cancel-button-text="No" :icon="InfoFilled" icon-color="#626AEF" <el-popconfirm
title="确认要删除吗?" @confirm="deletePolicyCommissionApi(row)"> confirm-button-text="Yes"
cancel-button-text="No"
:icon="InfoFilled"
icon-color="#626AEF"
title="确认要删除吗?"
@confirm="deletePolicyCommissionApi(row)"
>
<template #reference> <template #reference>
<el-button text type="danger" size="small">删除</el-button> <el-button text type="danger" size="small">删除</el-button>
</template> </template>
</el-popconfirm> </el-popconfirm>
</template> </template>
...@@ -195,78 +323,194 @@ ...@@ -195,78 +323,194 @@
<el-row :gutter="10"> <el-row :gutter="10">
<el-col :xs="24" :sm="24" :md="24" :lg="24"> <el-col :xs="24" :sm="24" :md="24" :lg="24">
<div class="pagination-container"> <div class="pagination-container">
<el-pagination v-model:current-page="checkRecordPageInfo.currentPage" <el-pagination
v-model:page-size="checkRecordPageInfo.pageSize" :page-sizes="[50, 100, 200, 300]" v-model:current-page="checkRecordPageInfo.currentPage"
:total="checkRecordPageInfo.total" @size-change="checkRecordPageHandleSizeChange" v-model:page-size="checkRecordPageInfo.pageSize"
@current-change="checkRecordPageHandleCurrentChange" layout="total, sizes, prev, pager, next, jumper" /> :page-sizes="[50, 100, 200, 300]"
:total="checkRecordPageInfo.total"
@size-change="checkRecordPageHandleSizeChange"
@current-change="checkRecordPageHandleCurrentChange"
layout="total, sizes, prev, pager, next, jumper"
/>
</div> </div>
</el-col> </el-col>
</el-row> </el-row>
</CommonDialog> </CommonDialog>
<!-- 新增检核记录弹窗 --> <!-- 新增检核记录弹窗 -->
<CommonDialog :dialogTitle='editStatus.value == "add" ? "新增检核记录" : "修改检核记录"' dialogWidth='80%' <CommonDialog
:openDialog=addCheckRecordDialogFlag :showAction='true' :showClose='true' @close='closeDialog()' :dialogTitle="editStatus == 'add' ? '新增检核记录' : '修改检核记录'"
@confirm='handleAddCheckRecord()'> dialogWidth="80%"
:openDialog="addCheckRecordDialogFlag"
:showAction="true"
:showClose="true"
@close="closeDialog()"
@confirm="handleAddCheckRecord()"
>
<el-row> <el-row>
<el-col :xs="24" :sm="24" :md="24" :lg="24"> <el-col :xs="24" :sm="24" :md="24" :lg="24">
<SearchForm ref="addCheckRecordFormRef" :config="addCheckRecordConfig" v-model="addReceivablesFormModel" /> <SearchForm
ref="addCheckRecordFormRef"
:config="addCheckRecordConfig"
v-model="addReceivablesFormModel"
/>
</el-col> </el-col>
</el-row> </el-row>
</CommonDialog> </CommonDialog>
<CommonDialog dialogTitle='文件导入' dialogWidth='80%' :openDialog=fileUploadDialogFlag :showAction='true' <CommonDialog
:showClose='true' @close='fileUploadDialogFlag = false; files = ""' dialogTitle="文件导入"
@confirm='fileUploadDialogFlag = false; files = ""'> dialogWidth="80%"
<FileUpload v-model="files" :data="{ reconciliationYearMonth: checkFormData.reconciliationYearMonth }" :openDialog="fileUploadDialogFlag"
:file-type="['xlsx', 'xls']" :action="'/csf/api/commission/upload/excel'" :limit="1" :fileSize="15" :showAction="true"
@uploadEnd="handleUploadEnd" /> :showClose="true"
@close="((fileUploadDialogFlag = false), (files = ''))"
@confirm="((fileUploadDialogFlag = false), (files = ''))"
>
<FileUpload
v-model="files"
:data="{ reconciliationYearMonth: checkFormData.reconciliationYearMonth }"
:file-type="['xlsx', 'xls']"
:action="'/csf/api/commission/upload/excel'"
:limit="1"
:fileSize="15"
@uploadEnd="handleUploadEnd"
/>
</CommonDialog> </CommonDialog>
<!-- 设置比对状态 --> <!-- 设置比对状态 -->
<CommonDialog dialogTitle='设置比对状态' dialogWidth='80%' :openDialog=setCompareStatusDialogFlag :showAction='true' <CommonDialog
:showClose='true' @close='setCompareStatusDialogFlag = false;' @confirm='setCompareStatusapi'> dialogTitle="设置比对状态"
<SearchForm ref="setCompareStatusFormRef" :config="setCompareStatusConfig" v-model="setCompareStatusFormModel" /> dialogWidth="80%"
:openDialog="setCompareStatusDialogFlag"
:showAction="true"
:showClose="true"
@close="setCompareStatusDialogFlag = false"
@confirm="setCompareStatusapi"
>
<SearchForm
ref="setCompareStatusFormRef"
:config="setCompareStatusConfig"
v-model="setCompareStatusFormModel"
/>
</CommonDialog> </CommonDialog>
<!-- 更新数据 --> <!-- 更新数据 -->
<CommonDialog dialogTitle='更新数据' dialogWidth='80%' :openDialog=updateDataDialogFlag :showAction='true' <CommonDialog
:showClose='true' @close='updateDataDialogFlag = false;' @confirm='updateDataapi'> dialogTitle="更新数据"
<SearchForm ref="updateDataFormRef" :config="updateDataConfig" v-model="updateDataFormModel" /> dialogWidth="80%"
:openDialog="updateDataDialogFlag"
:showAction="true"
:showClose="true"
@close="updateDataDialogFlag = false"
@confirm="updateDataapi"
>
<SearchForm
ref="updateDataFormRef"
:config="updateDataConfig"
v-model="updateDataFormModel"
/>
</CommonDialog> </CommonDialog>
<!-- 查看记录 --> <!-- 查看记录 -->
<CommonDialog dialogTitle='查看记录' dialogWidth='80%' :openDialog=viewRecordDialogFlag :showAction='true' <CommonDialog
:showClose='true' @close='viewRecordDialogFlag = false;'> dialogTitle="查看记录"
dialogWidth="80%"
:openDialog="viewRecordDialogFlag"
:showAction="true"
:showClose="true"
@close="viewRecordDialogFlag = false"
>
</CommonDialog>
<!-- 修改检核 -->
<CommonDialog
dialogTitle="修改检核"
dialogWidth="80%"
:openDialog="recordCheckDialogFlag"
:showAction="true"
:showClose="true"
@close="recordCheckDialogFlag = false"
@confirm="confirmRecordCheck"
>
<SearchForm
ref="updateDataFormRef"
:config="updateDataConfig"
v-model="updateDataFormModel"
/>
</CommonDialog>
<!-- 修改入账状态 -->
<CommonDialog
dialogTitle="修改入账状态"
dialogWidth="80%"
:openDialog="accountedStatusFlag"
:showAction="true"
:showClose="true"
@close="accountedStatusFlag = false"
@confirm="confirmAccountedStatus"
>
<SearchForm
ref="accountedStatusFormRef"
:config="accountedStatusConfig"
v-model="accountedStatusFormModel"
/>
</CommonDialog> </CommonDialog>
</div> </div>
</template> </template>
<script setup> <script setup>
import { ref, reactive, onMounted } from 'vue' import { ref, reactive, onMounted, watch } from 'vue'
import CommonPage from '@/components/commonPage' import CommonPage from '@/components/commonPage'
import CommonDialog from '@/components/commonDialog' import CommonDialog from '@/components/commonDialog'
import SearchForm from '@/components/SearchForm/SearchForm.vue' import SearchForm from '@/components/SearchForm/SearchForm.vue'
import { ElMessage } from 'element-plus' import { ElMessage, ElMessageBox } from 'element-plus'
import { formatCurrency } from '@/utils/number' import { formatCurrency } from '@/utils/number'
// 接口 // 接口
import { import {
getPolicyCommissionList, generateCommissionRecord, getPolicyCommissionList,
addPayrollCheckRecord, commissionExpectedRecord, updateCompareStatus, updateCommissionRecord, deletePolicyCommission, syncExpectedCommission, compareCommissionEntry generateCommissionRecord,
addPayrollCheckRecord,
commissionExpectedRecord,
updateCompareStatus,
updateCommissionRecord,
deletePolicyCommission,
syncExpectedCommission,
compareCommissionEntry,
editStatusApi
} from '@/api/financial/commission' } from '@/api/financial/commission'
import { InfoFilled, Select, Upload, Plus } from '@element-plus/icons-vue' import { InfoFilled, Select, Upload, Plus } from '@element-plus/icons-vue'
import FileUpload from '@/components/FileUpload/index.vue' import FileUpload from '@/components/FileUpload/index.vue'
import { loadDicts, getDictLabel } from '@/utils/useDict' import { loadDicts, getDictLabel } from '@/utils/useDict'
const accountedStatusFormModel = ref({})
const accountedStatusFlag = ref(false)
const accountedStatusFormRef = ref(null)
const { proxy } = getCurrentInstance() const { proxy } = getCurrentInstance()
const { csf_expected_commission_status } = proxy.useDict('csf_expected_commission_status') const { csf_expected_commission_status } = proxy.useDict('csf_expected_commission_status')
const { csf_commission_status } = proxy.useDict('csf_commission_status') const { csf_commission_status } = proxy.useDict('csf_commission_status')
const downloadTemplateUrl = 'https://yd-ali-oss.oss-cn-shanghai-finance-1-pub.aliyuncs.com/xlsx/2026/01/26/33f0637762144d58a256f5d8d3c2d2b6.xlsx' const downloadTemplateUrl =
'https://yd-ali-oss.oss-cn-shanghai-finance-1-pub.aliyuncs.com/xlsx/2026/01/26/33f0637762144d58a256f5d8d3c2d2b6.xlsx'
const recordCheckDialogFlag = ref(false)
const accountedStatusConfig = ref([
{
type: 'select',
prop: 'status',
label: '入账状态',
dictType: 'csf_expected_commission_status',
rules: [{ required: true, message: '请选择入账状态', trigger: 'blur' }]
// defaultValue: selectedRow.value.status || ''
},
{
type: 'textarea',
prop: 'statusDesc',
label: '修改理由',
rules: [{ required: true, message: '请填写修改理由', trigger: 'blur' }]
// defaultValue: selectedRow.value.statusDesc || ''
}
])
const downloadTemplate = () => { const downloadTemplate = () => {
const link = document.createElement('a') const link = document.createElement('a')
link.href = downloadTemplateUrl // 注意:以 / 开头,指向 public 下的文件 link.href = downloadTemplateUrl // 注意:以 / 开头,指向 public 下的文件
...@@ -275,7 +519,7 @@ const downloadTemplate = () => { ...@@ -275,7 +519,7 @@ const downloadTemplate = () => {
} }
const formatRatio = (row, column, cellValue, index) => { const formatRatio = (row, column, cellValue, index) => {
if (cellValue == null || cellValue == '' && cellValue !== 0) { if (cellValue == null || (cellValue == '' && cellValue !== 0)) {
return '-' return '-'
} }
return cellValue + '%' return cellValue + '%'
...@@ -298,11 +542,11 @@ const checkRecordPageInfo = reactive({ ...@@ -298,11 +542,11 @@ const checkRecordPageInfo = reactive({
pageSize: 10, pageSize: 10,
total: 0 total: 0
}) })
const checkRecordPageHandleSizeChange = (val) => { const checkRecordPageHandleSizeChange = val => {
checkRecordPageInfo.pageSize = val checkRecordPageInfo.pageSize = val
checkRecordQuery() checkRecordQuery()
} }
const checkRecordPageHandleCurrentChange = (val) => { const checkRecordPageHandleCurrentChange = val => {
checkRecordPageInfo.currentPage = val checkRecordPageInfo.currentPage = val
checkRecordQuery() checkRecordQuery()
} }
...@@ -313,13 +557,15 @@ const searchConfig = ref([ ...@@ -313,13 +557,15 @@ const searchConfig = ref([
type: 'input', type: 'input',
prop: 'policyNo', prop: 'policyNo',
label: '保单号' label: '保单号'
}, { },
{
type: 'select', type: 'select',
prop: 'statusList', prop: 'statusList',
label: '比对状态', label: '比对状态',
multiple: true, multiple: true,
dictType: 'csf_commission_status' dictType: 'csf_commission_status'
}, { },
{
type: 'select', type: 'select',
prop: 'reconciliationCompanyBizIdList', prop: 'reconciliationCompanyBizIdList',
label: '对账公司', label: '对账公司',
...@@ -331,22 +577,25 @@ const searchConfig = ref([ ...@@ -331,22 +577,25 @@ const searchConfig = ref([
multiple: true, multiple: true,
valueKey: 'reconciliationCompanyBizId', valueKey: 'reconciliationCompanyBizId',
labelKey: 'name', labelKey: 'name',
transform: (res) => { transform: res => {
console.log(res) console.log(res)
return res?.data.records || [] return res?.data.records || []
} }
}, { },
type: 'date', {
type: 'month',
prop: 'expectedDate', prop: 'expectedDate',
label: '入账(估)', label: '入账(估)',
placeholder: '请选择' placeholder: '请选择'
}, { },
type: 'daterange', {
type: 'monthrange',
prop: 'commissionDate', prop: 'commissionDate',
label: '入账(实)', label: '入账(实)',
startPlaceholder: '开始时间', startPlaceholder: '开始时间',
endPlaceholder: '结束时间' endPlaceholder: '结束时间'
}, { },
{
type: 'select', type: 'select',
prop: 'insuranceCompanyBizIdList', prop: 'insuranceCompanyBizIdList',
label: '保险公司', label: '保险公司',
...@@ -358,15 +607,16 @@ const searchConfig = ref([ ...@@ -358,15 +607,16 @@ const searchConfig = ref([
multiple: true, multiple: true,
valueKey: 'insuranceCompanyBizId', valueKey: 'insuranceCompanyBizId',
labelKey: 'fullName', labelKey: 'fullName',
transform: (res) => { transform: res => {
console.log(res) console.log(res)
return res?.data.records || [] return res?.data.records || []
} }
}, { },
{
type: 'month', type: 'month',
prop: 'reconciliationYearMonth', prop: 'reconciliationYearMonth',
label: '检核年月', label: '检核年月',
placeholder: '检核年月', placeholder: '检核年月'
} }
]) ])
const selectedRow = ref(null) const selectedRow = ref(null)
...@@ -385,7 +635,7 @@ const setCompareStatusFormModel = reactive({ ...@@ -385,7 +635,7 @@ const setCompareStatusFormModel = reactive({
status: '' status: ''
}) })
// 当比对状态为成功时可以选择 // 当比对状态为成功时可以选择
const selectableFn = (row) => row.commissionStatus == '1' && row.commissionBizType == 'R' const selectableFn = row => row.commissionStatus == '1' && row.commissionBizType == 'R'
// 设置编辑状态,是新增还是修改 // 设置编辑状态,是新增还是修改
const editStatus = ref('add') const editStatus = ref('add')
// 更新数据 // 更新数据
...@@ -393,8 +643,6 @@ const updateDataDialogFlag = ref(false) ...@@ -393,8 +643,6 @@ const updateDataDialogFlag = ref(false)
// 查看记录 // 查看记录
const viewRecordDialogFlag = ref(false) const viewRecordDialogFlag = ref(false)
const fileUploadDialogFlag = ref(false) const fileUploadDialogFlag = ref(false)
const checkRecordTableData = ref([]) const checkRecordTableData = ref([])
...@@ -415,11 +663,12 @@ const checkConfig = ref([ ...@@ -415,11 +663,12 @@ const checkConfig = ref([
placeholder: '保单号' placeholder: '保单号'
} }
]) ])
const checkRecordEdit = (row) => { const checkRecordEdit = row => {
console.log('查看记录', row) console.log('查看记录', row)
selectedRowCheck.value = { ...row } selectedRowCheck.value = { ...row }
editStatus.value = 'edit' editStatus.value = 'edit'
addReceivablesFormModel.value = { ...row } addReceivablesFormModel.value = { ...row }
// addReceivablesFormModel.value.currency = 'HKD'
addCheckRecordDialogFlag.value = true addCheckRecordDialogFlag.value = true
console.log('父组件赋值', addReceivablesFormModel.value) console.log('父组件赋值', addReceivablesFormModel.value)
} }
...@@ -431,7 +680,7 @@ const checkFormData = ref({ ...@@ -431,7 +680,7 @@ const checkFormData = ref({
// 监听 checkFormData 变化,自动重新查询 // 监听 checkFormData 变化,自动重新查询
watch( watch(
() => ({ ...checkFormData.value }), // 深度监听对象内容 () => ({ ...checkFormData.value }), // 深度监听对象内容
(newVal) => { newVal => {
// console.log('搜索条件变化:', newVal) // console.log('搜索条件变化:', newVal)
// ✅ 在这里调用查询接口 或 触发列表刷新 // ✅ 在这里调用查询接口 或 触发列表刷新
...@@ -441,9 +690,32 @@ watch( ...@@ -441,9 +690,32 @@ watch(
}, },
{ immediate: true } // 首次加载也执行(可选) { immediate: true } // 首次加载也执行(可选)
) )
// 确认修改检核
const confirmRecordCheck = async () => {
//等待后端接口
try {
const formData = updateDataFormRef.value.getFormData()
console.log('===========', formData)
let params
params = {
...formData, // ←←← 使用 formData,不是 addReceivablesFormModel.value
commissionBizId: selectedRow.value.commissionBizId
}
await updateCommissionRecord(params)
ElMessage.success('修改检核记录成功')
recordCheckDialogFlag.value = false
updateDataFormRef.value.resetForm()
const searchParams = searchFormRef.value.getFormData()
loadTableData(searchParams)
} catch (error) {
console.error('操作失败', error)
ElMessage.error('操作失败')
}
}
// 删除检核记录 // 删除检核记录
const deletePolicyCommissionApi = async (row) => { const deletePolicyCommissionApi = async row => {
console.log(row) console.log(row)
try { try {
const res = await deletePolicyCommission({ commissionBizId: row.commissionBizId }) const res = await deletePolicyCommission({ commissionBizId: row.commissionBizId })
...@@ -464,9 +736,7 @@ const deletePolicyCommissionApi = async (row) => { ...@@ -464,9 +736,7 @@ const deletePolicyCommissionApi = async (row) => {
// 生成可出账记录按钮 // 生成可出账记录按钮
const updatePayRollStatusDisable = ref(true) const updatePayRollStatusDisable = ref(true)
onMounted(async () => { onMounted(async () => {})
})
// 应收单类型 // 应收单类型
const commissionBizTypeOptions = [ const commissionBizTypeOptions = [
...@@ -474,7 +744,7 @@ const commissionBizTypeOptions = [ ...@@ -474,7 +744,7 @@ const commissionBizTypeOptions = [
{ value: 'U', label: '非关联保单应收单' } { value: 'U', label: '非关联保单应收单' }
] ]
// 应收单类型通过value转成label // 应收单类型通过value转成label
const getCommissionBizTypeLabel = (value) => { const getCommissionBizTypeLabel = value => {
const item = commissionBizTypeOptions.find(item => item.value === value) const item = commissionBizTypeOptions.find(item => item.value === value)
return item?.label || '' return item?.label || ''
} }
...@@ -489,48 +759,43 @@ const addCheckRecordConfig = ref([ ...@@ -489,48 +759,43 @@ const addCheckRecordConfig = ref([
label: '应收单类型', label: '应收单类型',
placeholder: '应收单类型', placeholder: '应收单类型',
options: commissionBizTypeOptions, options: commissionBizTypeOptions,
rules: [ rules: [{ required: true, message: '请选择应收单类型', trigger: 'blur' }]
{ required: true, message: '请选择应收单类型', trigger: 'blur' },
]
}, },
{ {
type: 'input', type: 'input',
prop: 'policyNo', prop: 'policyNo',
label: '保单号', label: '保单号',
visible: (formData) => formData.commissionBizType == 'R', visible: formData => formData.commissionBizType == 'R',
rules: [ rules: [{ required: true, message: '请输入保单号', trigger: 'blur' }]
{ required: true, message: '请输入保单号', trigger: 'blur' },
]
}, },
{ {
type: 'input', type: 'input',
prop: 'commissionPeriod', prop: 'commissionPeriod',
label: '佣金期数', label: '佣金期数',
inputType: 'decimal', inputType: 'decimal',
visible: (formData) => formData.commissionBizType == 'R', visible: formData => formData.commissionBizType == 'R',
rules: [ rules: [
{ required: true, message: '请输入佣金期数', trigger: 'blur' }, { required: true, message: '请输入佣金期数', trigger: 'blur' },
{ pattern: /^\d+$/, message: '只能输入正整数', trigger: 'blur' } { pattern: /^\d+$/, message: '只能输入正整数', trigger: 'blur' }
] ]
}, { },
{
type: 'input', type: 'input',
prop: 'totalPeriod', prop: 'totalPeriod',
label: '总期数', label: '总期数',
inputType: 'decimal', inputType: 'decimal',
visible: (formData) => formData.commissionBizType == 'R', visible: formData => formData.commissionBizType == 'R',
rules: [ rules: [{ pattern: /^\d+$/, message: '只能输入正整数', trigger: 'blur' }]
{ pattern: /^\d+$/, message: '只能输入正整数', trigger: 'blur' } },
] {
}, { type: 'month',
type: 'date',
prop: 'commissionDate', prop: 'commissionDate',
label: '入账(实)', label: '入账(实)',
placeholder: '请选择', placeholder: '请选择',
maxDate: 'today', maxDate: 'today',
rules: [ rules: [{ required: true, message: '请选择入账月(实)', trigger: 'blur' }]
{ required: true, message: '请选择入账日(实)', trigger: 'blur' }, },
] {
}, {
type: 'input', type: 'input',
prop: 'amount', prop: 'amount',
label: '入账金额', label: '入账金额',
...@@ -540,26 +805,34 @@ const addCheckRecordConfig = ref([ ...@@ -540,26 +805,34 @@ const addCheckRecordConfig = ref([
{ required: true, message: '请输入金额', trigger: 'blur' }, { required: true, message: '请输入金额', trigger: 'blur' },
{ pattern: /^-?\d+(\.\d+)?$/, message: '请输入有效的数字', trigger: 'blur' } { pattern: /^-?\d+(\.\d+)?$/, message: '请输入有效的数字', trigger: 'blur' }
] ]
}, { },
{
type: 'select', type: 'select',
prop: 'currency', prop: 'currency',
label: '入账币种', label: '入账币种',
dictType: 'bx_currency_type', dictType: 'bx_currency_type',
defaultValue: 'HKD',
disabled: true,
rules: [ rules: [
{ required: true, message: '请选择入账币种', trigger: 'blur' }, {
required: true,
message: '请选择入账币种',
trigger: 'blur'
}
] ]
}, { },
{
type: 'select', type: 'select',
prop: 'commissionType', prop: 'commissionType',
label: '入账项目', label: '入账项目',
dictType: 'csf_commission_type', dictType: 'csf_commission_type',
rules: [ defaultValue: '1',
{ required: true, message: '请选择入账项目', trigger: 'blur' }, rules: [{ required: true, message: '请选择入账项目', trigger: 'blur' }],
],
onChangeExtraFields: { onChangeExtraFields: {
commissionName: 'itemLabel' commissionName: 'itemLabel'
}, }
}, { },
{
type: 'select', type: 'select',
prop: 'reconciliationCompanyBizId', prop: 'reconciliationCompanyBizId',
label: '对账公司', label: '对账公司',
...@@ -569,33 +842,37 @@ const addCheckRecordConfig = ref([ ...@@ -569,33 +842,37 @@ const addCheckRecordConfig = ref([
placeholder: '输入对账公司名称搜索', placeholder: '输入对账公司名称搜索',
debounceWait: 500, // 自定义防抖时间 debounceWait: 500, // 自定义防抖时间
onChangeExtraFields: { onChangeExtraFields: {
reconciliationCompany: 'name',// 自动同步 raw.name 到 reconciliationCompany reconciliationCompany: 'name', // 自动同步 raw.name 到 reconciliationCompany
reconciliationCompanyCode: 'code' reconciliationCompanyCode: 'code'
}, },
valueKey: 'reconciliationCompanyBizId', valueKey: 'reconciliationCompanyBizId',
labelKey: 'name', labelKey: 'name',
transform: (res) => { transform: res => {
console.log(res) console.log(res)
return res?.data.records || [] return res?.data.records || []
}, },
rules: [ rules: [{ required: true, message: '请选择对账公司', trigger: 'blur' }]
{ required: true, message: '请选择对账公司', trigger: 'blur' }, },
] {
}, {
type: 'input', type: 'input',
prop: 'exchangeRate', prop: 'exchangeRate',
label: '结算汇率(实)', label: '结算汇率(实)',
inputType: 'decimal', inputType: 'decimal',
decimalDigits: 4, decimalDigits: 5,
visible: formData => formData.commissionBizType == 'R',
rules: [ rules: [
{ required: true, message: '请输入金额', trigger: 'blur' }, { required: true, message: '请输入金额', trigger: 'blur' },
{ pattern: /^\d+(\.\d{1,4})?$/, message: '最多四位小数', trigger: 'blur' } { pattern: /^\d+(\.\d{1,4})?$/, message: '最多四位小数', trigger: 'blur' }
] ]
}, },
{
type: 'input',
prop: 'manualRemark',
label: '备注'
}
]) ])
const addCheckRecordDialogFlag = ref(false) const addCheckRecordDialogFlag = ref(false)
const handleAddCheckRecord = async () => { const handleAddCheckRecord = async () => {
try { try {
// ✅ 统一从子组件获取完整表单数据(含 extra 字段) // ✅ 统一从子组件获取完整表单数据(含 extra 字段)
await nextTick() // 确保子组件已同步 await nextTick() // 确保子组件已同步
...@@ -625,14 +902,12 @@ const handleAddCheckRecord = async () => { ...@@ -625,14 +902,12 @@ const handleAddCheckRecord = async () => {
ElMessage.error('操作失败') ElMessage.error('操作失败')
} }
} }
const clearForm = (type) => { const clearForm = type => {
if (type === 'addReceivablesFormModel') if (type === 'addReceivablesFormModel')
if (addCheckRecordFormRef.value) { if (addCheckRecordFormRef.value) {
addReceivablesFormModel.value = {} addReceivablesFormModel.value = {}
addCheckRecordFormRef.value.resetForm() addCheckRecordFormRef.value.resetForm()
} }
} }
const closeDialog = () => { const closeDialog = () => {
...@@ -655,14 +930,13 @@ const generateCommissionRecordapi = async () => { ...@@ -655,14 +930,13 @@ const generateCommissionRecordapi = async () => {
console.error('生成可出账记录失败', error) console.error('生成可出账记录失败', error)
ElMessage.error('生成可出账记录失败') ElMessage.error('生成可出账记录失败')
} }
} }
// 表格操作菜单 // 表格操作菜单
const dropdownItems = [ const dropdownItems = [
{ label: '设置比对状态', value: 'setStatus', confirm: '' }, { label: '设置比对状态', value: 'setStatus', confirm: '' },
{ label: '同步到应收款管理', value: 'syncToReceivable', confirm: '确认要同步到应收款管理吗?' }, { label: '同步到应收款管理', value: 'syncToReceivable', confirm: '确认要同步到应收款管理吗?' },
// { label: '更新检核数据', value: 'editRecord' }, { label: '修改检核记录', value: 'editCheckRecord' },
// { label: '查看记录', value: 'viewRecord' } { label: '修改入账状态', value: 'setAccountedStatus' }
] ]
const handleMenuConfirm = async (action, row) => { const handleMenuConfirm = async (action, row) => {
console.log('点击了操作菜单', action, row) console.log('点击了操作菜单', action, row)
...@@ -705,11 +979,13 @@ const handleReset = () => { ...@@ -705,11 +979,13 @@ const handleReset = () => {
// 重置搜索表单 // 重置搜索表单
searchFormRef.value.resetForm() searchFormRef.value.resetForm()
checkFormData.value = {} checkFormData.value = {}
loadTableData()
console.log('表单已重置') console.log('表单已重置')
} }
const handleQuery = async () => { const handleQuery = async () => {
currentPage.value = 1
const params = searchFormRef.value.getFormData() const params = searchFormRef.value.getFormData()
console.log('父组件发起查询:', params)
loadTableData(params) loadTableData(params)
} }
...@@ -718,7 +994,7 @@ const checkRecordQuery = async () => { ...@@ -718,7 +994,7 @@ const checkRecordQuery = async () => {
try { try {
const params = { const params = {
reconciliationYearMonth: checkFormData.value.reconciliationYearMonth, reconciliationYearMonth: checkFormData.value.reconciliationYearMonth,
policyNo:checkFormData.value.policyNo, policyNo: checkFormData.value.policyNo,
pageNo: checkRecordPageInfo.currentPage, pageNo: checkRecordPageInfo.currentPage,
pageSize: checkRecordPageInfo.pageSize pageSize: checkRecordPageInfo.pageSize
} }
...@@ -731,11 +1007,10 @@ const checkRecordQuery = async () => { ...@@ -731,11 +1007,10 @@ const checkRecordQuery = async () => {
console.error('查询检核记录失败', error) console.error('查询检核记录失败', error)
ElMessage.error('查询检核记录失败') ElMessage.error('查询检核记录失败')
} }
} }
const multipleSelection = ref([]) const multipleSelection = ref([])
const handleSelectionChange = (val) => { const handleSelectionChange = val => {
multipleSelection.value = val multipleSelection.value = val
console.log('全选:', val) console.log('全选:', val)
// 生成可出账记录按钮是否禁用 // 生成可出账记录按钮是否禁用
...@@ -770,13 +1045,26 @@ const operationBtnList = ref([ ...@@ -770,13 +1045,26 @@ const operationBtnList = ref([
// 加载表格数据 // 加载表格数据
const loadTableData = async (searchParams = {}) => { const loadTableData = async (searchParams = {}) => {
loading.value = true loading.value = true
try { try {
if (searchParams.commissionDate && searchParams.commissionDate.length > 0) {
searchParams.commissionDateStart = `${searchParams.commissionDate[0]}-01`
searchParams.commissionDateEnd = `${searchParams.commissionDate[1]}-01`
} else {
searchParams.commissionDateStart = ''
searchParams.commissionDateEnd = ''
}
if (searchParams.expectedDate) {
searchParams.expectedDate = `${searchParams.expectedDate}-01`
} else {
searchParams.expectedDate = ''
}
const params = { const params = {
pageNo: currentPage.value, pageNo: currentPage.value,
pageSize: pageSize.value, pageSize: pageSize.value,
...searchParams, ...searchParams,
commissionDateStart: searchParams.commissionDate?.[0] || undefined, // commissionDateStart: searchParams.commissionDate?.[0] || undefined,
commissionDateEnd: searchParams.commissionDate?.[1] || undefined, // commissionDateEnd: searchParams.commissionDate?.[1] || undefined,
commissionDate: undefined commissionDate: undefined
} }
const res = await getPolicyCommissionList(params) const res = await getPolicyCommissionList(params)
...@@ -794,13 +1082,15 @@ const loadTableData = async (searchParams = {}) => { ...@@ -794,13 +1082,15 @@ const loadTableData = async (searchParams = {}) => {
} }
loadTableData() loadTableData()
// 分页事件 // 分页事件
const handleSizeChange = (val) => { const handleSizeChange = val => {
pageSize.value = val pageSize.value = val
loadTableData() const params = searchFormRef.value.getFormData()
loadTableData(params)
} }
const handleCurrentChange = (val) => { const handleCurrentChange = val => {
currentPage.value = val currentPage.value = val
loadTableData() const params = searchFormRef.value.getFormData()
loadTableData(params)
} }
// 表格数据 // 表格数据
const tableData = ref([]) const tableData = ref([])
...@@ -818,12 +1108,33 @@ const handleSelect = (e, row) => { ...@@ -818,12 +1108,33 @@ const handleSelect = (e, row) => {
viewRecordDialogFlag.value = true viewRecordDialogFlag.value = true
return return
} else if (e === 'syncToReceivable') { } else if (e === 'syncToReceivable') {
return
} else if (e === 'editCheckRecord') {
updateDataFormModel.value = { ...selectedRow.value }
recordCheckDialogFlag.value = true
return
} else if (e === 'setAccountedStatus') {
accountedStatusFormModel.value = {
statusDesc: selectedRow.value.statusDesc,
status: selectedRow.value.commissionExpectedStatus
}
accountedStatusFlag.value = true
return return
} }
} }
const handleAddCheckList = () => { const handleAddCheckList = () => {
editStatus.value = 'add' editStatus.value = 'add'
// addCheckRecordConfig.value = addCheckRecordConfig.value
// addCheckRecordConfig.value = addCheckRecordConfig.value.map(item => {
// if (item.prop == 'currency') {
// item.defaultValue = 'HKD'
// item.disabled = true
// } else if (item.prop == 'commissionType') {
// item.defaultValue = '1'
// }
// return item
// })
addReceivablesFormModel.value = { ...selectedRow.value } addReceivablesFormModel.value = { ...selectedRow.value }
addCheckRecordDialogFlag.value = true addCheckRecordDialogFlag.value = true
clearForm('addReceivablesFormModel') clearForm('addReceivablesFormModel')
...@@ -841,7 +1152,8 @@ const setCompareStatusapi = async () => { ...@@ -841,7 +1152,8 @@ const setCompareStatusapi = async () => {
ElMessage.success('设置比对状态成功') ElMessage.success('设置比对状态成功')
setCompareStatusDialogFlag.value = false setCompareStatusDialogFlag.value = false
setCompareStatusFormRef.value.resetForm() setCompareStatusFormRef.value.resetForm()
loadTableData() const searchParams = searchFormRef.value.getFormData()
loadTableData(searchParams)
} else { } else {
ElMessage.error(res.msg || '设置比对状态失败') ElMessage.error(res.msg || '设置比对状态失败')
} }
...@@ -877,7 +1189,7 @@ const updateDataapi = async () => { ...@@ -877,7 +1189,7 @@ const updateDataapi = async () => {
ElMessage.error('更新数据失败') ElMessage.error('更新数据失败')
} }
} }
const handleUploadEnd = (code) => { const handleUploadEnd = code => {
if (code === 200) { if (code === 200) {
ElMessage.success('上传文件成功') ElMessage.success('上传文件成功')
fileUploadDialogFlag.value = false fileUploadDialogFlag.value = false
...@@ -894,10 +1206,12 @@ const closthDialog = () => { ...@@ -894,10 +1206,12 @@ const closthDialog = () => {
checkFormRef.value.resetForm() checkFormRef.value.resetForm()
checkFormData.value = {} checkFormData.value = {}
checkRecordTableData.value = [] checkRecordTableData.value = []
const params = searchFormRef.value.getFormData()
loadTableData(params)
} }
// 重新比对 // 重新比对
const compareCommissionEntryapi = async (row) => { const compareCommissionEntryapi = async row => {
try { try {
const res = await compareCommissionEntry(row.commissionBizId) const res = await compareCommissionEntry(row.commissionBizId)
if (res.code === 200) { if (res.code === 200) {
...@@ -911,6 +1225,46 @@ const compareCommissionEntryapi = async (row) => { ...@@ -911,6 +1225,46 @@ const compareCommissionEntryapi = async (row) => {
ElMessage.error('重新比对失败') ElMessage.error('重新比对失败')
} }
} }
const confirmAccountedStatus = () => {
const formData = accountedStatusFormRef.value.getFormData()
console.log('====================================')
console.log('formData', formData)
console.log('====================================')
if (!formData.status) {
return ElMessage.warning('请选择入账状态')
}
if (!formData.statusDesc) {
return ElMessage.warning('请输入修改理由')
}
ElMessageBox.confirm('确定要修改入账状态吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
})
.then(async () => {
try {
const res = await editStatusApi({
commissionExpectedBizId: selectedRow.value.commissionExpectedBizId,
statusDesc: formData.statusDesc,
status: formData.status
})
if (res.code === 200) {
ElMessage.success('状态修改成功')
accountedStatusFlag.value = false
const searchParams = searchFormRef.value.getFormData()
loadTableData(searchParams)
} else {
ElMessage.error(res.msg || '状态修改失败')
}
} catch (error) {
console.error('修改状态失败:', error)
ElMessage.error('修改状态失败')
}
})
.catch(() => {
ElMessage.info('已取消修改')
})
}
</script> </script>
<style scoped> <style scoped>
...@@ -922,4 +1276,4 @@ const compareCommissionEntryapi = async (row) => { ...@@ -922,4 +1276,4 @@ const compareCommissionEntryapi = async (row) => {
.el-col { .el-col {
margin-bottom: 5px; margin-bottom: 5px;
} }
</style> </style>
\ No newline at end of file
...@@ -42,7 +42,8 @@ ...@@ -42,7 +42,8 @@
:border="true" :border="true"
> >
<el-table-column type="selection" width="55" :reserve-selection="true" /> <el-table-column type="selection" width="55" :reserve-selection="true" />
<el-table-column prop="fortuneAccountBizId" label="业务ID" min-width="120" sortable /> <el-table-column prop="businessNo" label="业务编号" min-width="120" />
<!-- <el-table-column prop="fortuneAccountBizId" label="业务ID" min-width="120" sortable /> -->
<el-table-column prop="broker" label="转介人" min-width="120" sortable /> <el-table-column prop="broker" label="转介人" min-width="120" sortable />
<el-table-column prop="team" label="所属团队" min-width="120" sortable /> <el-table-column prop="team" label="所属团队" min-width="120" sortable />
<el-table-column prop="hkdAmount" label="出账金额" width="120" sortable> <el-table-column prop="hkdAmount" label="出账金额" width="120" sortable>
...@@ -62,11 +63,17 @@ ...@@ -62,11 +63,17 @@
</el-tag> </el-tag>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column <!-- <el-table-column
prop="fortuneAccountDate" prop="fortuneAccountDate"
label="出账日" label="出账日"
min-width="150" min-width="150"
show-overflow-tooltip show-overflow-tooltip
/> -->
<el-table-column
prop="fortuneAccountMonth"
label="出账年月"
min-width="150"
show-overflow-tooltip
/> />
<!-- <el-table-column prop="remark" label="备注" min-width="150" show-overflow-tooltip /> --> <!-- <el-table-column prop="remark" label="备注" min-width="150" show-overflow-tooltip /> -->
<el-table-column fixed="right" label="操作" min-width="120"> <el-table-column fixed="right" label="操作" min-width="120">
...@@ -174,8 +181,23 @@ ...@@ -174,8 +181,23 @@
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="出账机构" prop="billOrg" width="150"> <el-table-column label="出账机构" prop="billOrg" width="150">
<template #default="scope"> <!-- <template #default="scope">
<el-input v-model="scope.row.billOrg" placeholder="请输入" clearable /> <el-input v-model="scope.row.billOrg" placeholder="请输入" clearable />
</template> -->
<template #default="scope">
<el-select
v-model="scope.row.billOrg"
style="width: 100%"
placeholder="请选择"
clearable
>
<el-option
v-for="item in csf_bill_org"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="原币种金额" prop="fromAmount" width="150"> <el-table-column label="原币种金额" prop="fromAmount" width="150">
...@@ -303,9 +325,9 @@ ...@@ -303,9 +325,9 @@
:showAction="true" :showAction="true"
:showClose="true" :showClose="true"
@close="showSalarySetting = false" @close="showSalarySetting = false"
@confirm = "salaryDataSetting" @confirm="salaryDataSetting"
> >
<el-date-picker <el-date-picker
v-model="fortuneAccountDate" v-model="fortuneAccountDate"
type="date" type="date"
placeholder="选择薪资日" placeholder="选择薪资日"
...@@ -341,7 +363,7 @@ import { debounce } from 'lodash-es' ...@@ -341,7 +363,7 @@ import { debounce } from 'lodash-es'
const amountInput = usePositiveDecimal(4) // 默认2位小数 const amountInput = usePositiveDecimal(4) // 默认2位小数
const { proxy } = getCurrentInstance() const { proxy } = getCurrentInstance()
const { bx_currency_type } = proxy.useDict('bx_currency_type') const { bx_currency_type, csf_bill_org } = proxy.useDict('bx_currency_type', 'csf_bill_org')
const searchFormRef = ref(null) const searchFormRef = ref(null)
const searchParams = ref({}) const searchParams = ref({})
const searchConfig = ref([ const searchConfig = ref([
...@@ -351,7 +373,7 @@ const searchConfig = ref([ ...@@ -351,7 +373,7 @@ const searchConfig = ref([
label: '转介人', label: '转介人',
api: '/insurance/base/api/userSaleExpand/page', api: '/insurance/base/api/userSaleExpand/page',
keywordField: 'realName', keywordField: 'realName',
requestParams: { pageNo: 1, pageSize: 20 }, requestParams: { pageNo: 1, pageSize: 9999 },
placeholder: '输入转介人名称搜索', placeholder: '输入转介人名称搜索',
debounceWait: 500, // 自定义防抖时间 debounceWait: 500, // 自定义防抖时间
valueKey: 'clientUserBizId', valueKey: 'clientUserBizId',
...@@ -420,8 +442,8 @@ const debounceChangeRateMap = new WeakMap() ...@@ -420,8 +442,8 @@ const debounceChangeRateMap = new WeakMap()
const debounceChangeToAmountMap = new WeakMap() const debounceChangeToAmountMap = new WeakMap()
// 表格操作菜单 // 表格操作菜单
const dropdownItems = [ const dropdownItems = [
{ label: '拆分出账', value: 'splitBilling' }, { label: '拆分出账', value: 'splitBilling' }
{ label: '设置出账日', value: 'settingSalaryDate' }, // { label: '设置出账日', value: 'settingSalaryDate' }
// { label: '查看记录', value: 'viewRecord' } // { label: '查看记录', value: 'viewRecord' }
] ]
//=============拆分出账开始================ //=============拆分出账开始================
...@@ -470,7 +492,7 @@ const handleFromAmountBlur = row => { ...@@ -470,7 +492,7 @@ const handleFromAmountBlur = row => {
// 汇率输入处理 // 汇率输入处理
const handleExchangeRateInput = (val, row) => { const handleExchangeRateInput = (val, row) => {
row.exchangeRate = amountInput.filterInput(val, 4) row.exchangeRate = amountInput.filterInput(val, 5)
getDebouncedChangeToAmount(row)(row) getDebouncedChangeToAmount(row)(row)
} }
...@@ -684,39 +706,40 @@ const handleSelect = (e, row) => { ...@@ -684,39 +706,40 @@ const handleSelect = (e, row) => {
billCurrentPage.value = 1 billCurrentPage.value = 1
billTableList.value = [] billTableList.value = []
getSplitTableList() getSplitTableList()
}else if(e==='settingSalaryDate'){ } else if (e === 'settingSalaryDate') {
console.log('更新薪资数据') console.log('更新薪资数据')
fortuneAccountDate.value= currentRow.value.fortuneAccountDate || '' fortuneAccountDate.value = currentRow.value.fortuneAccountDate || ''
showSalarySetting.value = true; showSalarySetting.value = true
} }
} }
const salaryDataSetting = async (e)=>{ const salaryDataSetting = async e => {
try{ try {
const params = { const params = {
fortuneAccountBizId : currentRow.value.fortuneAccountBizId, fortuneAccountBizId: currentRow.value.fortuneAccountBizId,
fortuneAccountDate:fortuneAccountDate.value fortuneAccountDate: fortuneAccountDate.value
}; }
const response = await updatePolicyFortuneAccount(params) const response = await updatePolicyFortuneAccount(params)
if(response.code==200){ if (response.code == 200) {
showSalarySetting.value = false; showSalarySetting.value = false
ElMessage.success('修改成功') ElMessage.success('修改成功')
getList() getList()
} }
}catch (error) { } catch (error) {
console.error('获取数据失败:', error) console.error('获取数据失败:', error)
ElMessage.error('修改失败') ElMessage.error('修改失败')
} }
} }
// 分页事件 // 分页事件
const handleSizeChange = val => { const handleSizeChange = val => {
pageSize.value = val pageSize.value = val
getList() const params = searchFormRef.value.getFormData()
getList(params)
} }
const handleCurrentChange = val => { const handleCurrentChange = val => {
currentPage.value = val currentPage.value = val
getList() const params = searchFormRef.value.getFormData()
getList(params)
} }
// 设置当前页的选中状态 // 设置当前页的选中状态
...@@ -793,7 +816,7 @@ const getList = async (searchParams = {}) => { ...@@ -793,7 +816,7 @@ const getList = async (searchParams = {}) => {
payoutDate: undefined, payoutDate: undefined,
pageNo: currentPage.value, pageNo: currentPage.value,
pageSize: pageSize.value, pageSize: pageSize.value,
statusList:searchParams.statusList || ['6'] statusList: searchParams.statusList || ['6']
} }
const response = await getReferrerFortuneList(params) const response = await getReferrerFortuneList(params)
...@@ -829,6 +852,7 @@ const getStatusType = status => { ...@@ -829,6 +852,7 @@ const getStatusType = status => {
// 查询 // 查询
const handleQuery = () => { const handleQuery = () => {
currentPage.value = 1
const params = searchFormRef.value.getFormData() const params = searchFormRef.value.getFormData()
console.log('父组件发起查询:', params) console.log('父组件发起查询:', params)
clearAllSelection() clearAllSelection()
......
<template> <template>
<div> <div>
<CommonPage :operationBtnList="operationBtnList" :visibleDefaultButtons="visibleDefaultButtons" <CommonPage
:showSearchForm="true" :show-pagination="true" :total="pageTotal" :current-page="currentPage" :operationBtnList="operationBtnList"
:page-size="pageSize" @size-change="handleSizeChange" @current-change="handleCurrentChange"> :visibleDefaultButtons="visibleDefaultButtons"
:showSearchForm="true"
:show-pagination="true"
:total="pageTotal"
:current-page="currentPage"
:page-size="pageSize"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
>
<!-- 搜索区域 --> <!-- 搜索区域 -->
<template #searchForm> <template #searchForm>
<SearchForm ref="searchFormRef" :config="searchConfig" /> <SearchForm ref="searchFormRef" :config="searchConfig" />
...@@ -12,30 +20,64 @@ ...@@ -12,30 +20,64 @@
<!-- 统计信息卡片 --> <!-- 统计信息卡片 -->
<div class="statistics-container" v-if="statisticsData.totalPolicyCount > 0"> <div class="statistics-container" v-if="statisticsData.totalPolicyCount > 0">
<el-row :gutter="20"> <el-row :gutter="20">
<el-col :xs="24" :sm="12" :md="4" class="text-center mb-4"> <el-col :xs="24" :sm="12" :md="4" class="text-center mb-4">
<el-statistic title="应出账总金额" :value="statisticsData.totalExpectedAmount" :formatter="value=>formatCurrency(value)" /> <el-statistic
title="应出账总金额"
:value="statisticsData.totalExpectedAmount"
:formatter="value => formatCurrency(value)"
/>
</el-col> </el-col>
<el-col :xs="24" :sm="12" :md="4" class="text-center mb-4"> <el-col :xs="24" :sm="12" :md="4" class="text-center mb-4">
<el-statistic title="已出账金额" :value="statisticsData.totalPaidAmount" :formatter="value=>formatCurrency(value)" /> <el-statistic
title="已出账金额"
:value="statisticsData.totalPaidAmount"
:formatter="value => formatCurrency(value)"
/>
</el-col> </el-col>
<el-col :xs="24" :sm="12" :md="4" class="text-center mb-4"> <el-col :xs="24" :sm="12" :md="4" class="text-center mb-4">
<el-statistic title="待出账金额" :value="statisticsData.totalUnpaidAmount" :formatter="value=>formatCurrency(value)" /> <el-statistic
title="待出账金额"
:value="statisticsData.totalUnpaidAmount"
:formatter="value => formatCurrency(value)"
/>
</el-col> </el-col>
<el-col :xs="24" :sm="12" :md="4" class="text-center mb-4"> <el-col :xs="24" :sm="12" :md="4" class="text-center mb-4">
<el-statistic title="已出账比例" :value="statisticsData.paidAmountRatio" :formatter="value=>formatCurrency(value,'',2)+'%'" /> <el-statistic
title="已出账比例"
:value="statisticsData.paidAmountRatio"
:formatter="value => formatCurrency(value, '', 2) + '%'"
/>
</el-col> </el-col>
<el-col :xs="24" :sm="12" :md="4" class="text-center mb-4"> <el-col :xs="24" :sm="12" :md="4" class="text-center mb-4">
<el-statistic title="总保单数" :value="statisticsData.totalPolicyCount"/> <el-statistic title="总保单数" :value="statisticsData.totalPolicyCount" />
</el-col> </el-col>
<el-col :xs="24" :sm="12" :md="4" class="text-center mb-4"> <el-col :xs="24" :sm="12" :md="4" class="text-center mb-4">
<el-statistic title="总保费" :value="statisticsData.totalPremiumAmount" :formatter="value=>formatCurrency(value)" /> <el-statistic
title="总保费"
:value="statisticsData.totalPremiumAmount"
:formatter="value => formatCurrency(value)"
/>
</el-col> </el-col>
</el-row> </el-row>
</div> </div>
<!-- 应付款管理列表 --> <!-- 应付款管理列表 -->
<el-table :data="tableData" height="400" border highlight-current-row style="width: 100%" v-loading="loading"> <el-table
<el-table-column v-for="(column, index) in payableReportTableColumns" :key="index" :prop="column.prop" :data="tableData"
:label="column.label" :width="column.width" :sortable="column.sortable" :formatter="column.formatter" /> height="400"
border
highlight-current-row
style="width: 100%"
v-loading="loading"
>
<el-table-column
v-for="(column, index) in payableReportTableColumns"
: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"> <el-table-column fixed="right" label="操作" min-width="120">
<template #default="scope"> <template #default="scope">
<el-button link type="primary" size="small" @click="viewDetail(scope.row)"> <el-button link type="primary" size="small" @click="viewDetail(scope.row)">
...@@ -47,36 +89,74 @@ ...@@ -47,36 +89,74 @@
</template> </template>
</CommonPage> </CommonPage>
<!-- 查看明细列表 --> <!-- 查看明细列表 -->
<CommonDialog dialogTitle="应付明细" dialogWidth="80%" :openDialog="detailDialogVisible" :showAction="true" <CommonDialog
:showClose="true" @close="detailDialogVisible = false" @confirm="detailDialogVisible = false"> dialogTitle="应付明细"
dialogWidth="80%"
:openDialog="detailDialogVisible"
:showAction="true"
:showClose="true"
@close="detailDialogVisible = false"
@confirm="detailDialogVisible = false"
>
<div class="statistics-container" v-if="detailRecordStatistics.totalPolicyCount > 0"> <div class="statistics-container" v-if="detailRecordStatistics.totalPolicyCount > 0">
<el-row :gutter="20"> <el-row :gutter="20">
<el-col :xs="24" :sm="12" :md="4" class="text-center mb-4"> <el-col :xs="24" :sm="12" :md="4" class="text-center mb-4">
<el-statistic title="应出账总金额" :value="detailRecordStatistics.totalExpectedAmount" :formatter="value=>formatCurrency(value)" /> <el-statistic
</el-col> title="应出账总金额"
<el-col :xs="24" :sm="12" :md="4" class="text-center mb-4"> :value="detailRecordStatistics.totalExpectedAmount"
<el-statistic title="已出账金额" :value="detailRecordStatistics.totalPaidAmount" :formatter="value=>formatCurrency(value)" /> :formatter="value => formatCurrency(value)"
</el-col> />
<el-col :xs="24" :sm="12" :md="4" class="text-center mb-4"> </el-col>
<el-statistic title="待出账金额" :value="detailRecordStatistics.totalUnpaidAmount" :formatter="value=>formatCurrency(value)" /> <el-col :xs="24" :sm="12" :md="4" class="text-center mb-4">
</el-col> <el-statistic
<el-col :xs="24" :sm="12" :md="4" class="text-center mb-4"> title="已出账金额"
<el-statistic title="已出账比例" :value="detailRecordStatistics.paidAmountRatio" :formatter="value=>formatCurrency(value,'',2)+'%'" /> :value="detailRecordStatistics.totalPaidAmount"
</el-col> :formatter="value => formatCurrency(value)"
<el-col :xs="24" :sm="12" :md="4" class="text-center mb-4"> />
<el-statistic title="总保单数" :value="detailRecordStatistics.totalPolicyCount"/> </el-col>
</el-col> <el-col :xs="24" :sm="12" :md="4" class="text-center mb-4">
<el-col :xs="24" :sm="12" :md="4" class="text-center mb-4"> <el-statistic
<el-statistic title="总保费" :value="detailRecordStatistics.totalPremiumAmount" :formatter="value=>formatCurrency(value)" /> title="待出账金额"
</el-col> :value="detailRecordStatistics.totalUnpaidAmount"
:formatter="value => formatCurrency(value)"
/>
</el-col>
<el-col :xs="24" :sm="12" :md="4" class="text-center mb-4">
<el-statistic
title="已出账比例"
:value="detailRecordStatistics.paidAmountRatio"
:formatter="value => formatCurrency(value, '', 2) + '%'"
/>
</el-col>
<el-col :xs="24" :sm="12" :md="4" class="text-center mb-4">
<el-statistic title="总保单数" :value="detailRecordStatistics.totalPolicyCount" />
</el-col>
<el-col :xs="24" :sm="12" :md="4" class="text-center mb-4">
<el-statistic
title="总保费"
:value="detailRecordStatistics.totalPremiumAmount"
:formatter="value => formatCurrency(value)"
/>
</el-col>
</el-row> </el-row>
</div> </div>
<el-table :data="payableReportTableData" border style="width: 100%;margin-bottom: 10px;"> <el-table
<el-table-column v-for="item in payableReportListTableColumns" :key="item.property" :property="item.property" :data="payableReportTableData"
:label="item.label" :width="item.width" :formatter="item.formatter" /> border
style="width: 100%; margin-bottom: 10px"
:row-class-name="getRowClassName"
>
<el-table-column
v-for="item in payableReportListTableColumns"
:key="item.property"
:property="item.property"
:label="item.label"
:width="item.width"
:formatter="item.formatter"
/>
<el-table-column fixed="right" label="操作" min-width="120"> <el-table-column fixed="right" label="操作" min-width="120">
<template #default="{ row }"> <template #default="{ row }">
<el-popover placement="right" :width="200" trigger="click"> <el-popover placement="right" :width="200" trigger="click" v-if="row.type == '1'">
<template #reference> <template #reference>
<el-icon> <el-icon>
<MoreFilled /> <MoreFilled />
...@@ -91,66 +171,226 @@ ...@@ -91,66 +171,226 @@
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
<el-pagination v-model:current-page="detailPageInfo.currentPage" v-model:page-size="detailPageInfo.pageSize" <el-pagination
:page-sizes="[50, 100, 200, 300]" size="default" layout="total, sizes, prev, pager, next, jumper" v-model:current-page="detailPageInfo.currentPage"
:total="detailPageInfo.total" @size-change="handleSizeChangeDetailRecord" v-model:page-size="detailPageInfo.pageSize"
@current-change="handleCurrentChangeDetailRecord" /> :page-sizes="[50, 100, 200, 300]"
size="default"
layout="total, sizes, prev, pager, next, jumper"
:total="detailPageInfo.total"
@size-change="handleSizeChangeDetailRecord"
@current-change="handleCurrentChangeDetailRecord"
/>
</CommonDialog> </CommonDialog>
<!-- 出账记录表格弹窗--> <!-- 出账记录表格弹窗-->
<CommonDialog dialogTitle="出账记录" dialogWidth="80%" :openDialog="payRecordDialogTableVisible" :showAction="true" <CommonDialog
:showClose="true" @close="payRecordDialogTableVisible = false" @confirm="payRecordDialogTableVisible = false"> dialogTitle="出账记录"
dialogWidth="80%"
:openDialog="payRecordDialogTableVisible"
:showAction="true"
:showClose="true"
@close="payRecordDialogTableVisible = false"
@confirm="payRecordDialogTableVisible = false"
>
<el-table :data="payRecordDialogTableData" border style="width: 100%"> <el-table :data="payRecordDialogTableData" border style="width: 100%">
<el-table-column v-for="item in payRecordDialogTableColumns" :key="item.property" :property="item.property" <el-table-column
:label="item.label" :width="item.width" :formatter="item.formatter" /> v-for="item in payRecordDialogTableColumns"
:key="item.property"
:property="item.property"
:label="item.label"
:width="item.width"
:formatter="item.formatter"
/>
</el-table> </el-table>
</CommonDialog> </CommonDialog>
<!-- 新增出账记录 --> <!-- 新增出账记录 -->
<CommonDialog :dialogTitle="editStatus === 'add' ? '新增出账记录' : '修改出账记录'" dialogWidth="80%" <CommonDialog
:openDialog="addPayRecordDialogVisible" :showAction="true" :showClose="true" @close="resetAddPayRecordForm" :dialogTitle="editStatus === 'add' ? '新增出账记录' : '修改出账记录'"
@confirm="handleConfirmAddPayRecord"> dialogWidth="80%"
<SearchForm ref="addPayRecordFormRef" :config="addPayRecordFormConfig" v-model="addPayRecordFormModel" /> :openDialog="addPayRecordDialogVisible"
:showAction="true"
:showClose="true"
@close="resetAddPayRecordForm"
@confirm="handleConfirmAddPayRecord"
>
<SearchForm
ref="addPayRecordFormRef"
:config="addPayRecordFormConfig"
v-model="addPayRecordFormModel"
@inputChange="(prop, value, item) => handleInputChange('addPayRecord', prop, value, item)"
/>
</CommonDialog>
<!-- 修改出账记录 -->
<CommonDialog
dialogTitle="修改出账记录"
dialogWidth="80%"
:openDialog="updatePayRecordDialogVisible"
:showAction="true"
:showClose="true"
@close="updatePayRecordDialogVisible = false"
@confirm="handleConfirmUpdatePayRecord"
>
<SearchForm
ref="updatePayRecordFormRef"
:config="updatePayRecordFormConfig"
v-model="updatePayRecordFormModel"
@inputChange="
(prop, value, item) => handleInputChange('updatePayRecord', prop, value, item)
"
/>
</CommonDialog> </CommonDialog>
<!-- 设置出账状态 --> <!-- 设置出账状态 -->
<CommonDialog dialogTitle="设置出账状态" dialogWidth="80%" :openDialog="setPayRecordStatusDialogVisible" <CommonDialog
:showAction="true" :showClose="true" @close="setPayRecordStatusDialogVisible = false" dialogTitle="设置出账状态"
@confirm="handleConfirmSetPayRecordStatus"> dialogWidth="80%"
:openDialog="setPayRecordStatusDialogVisible"
:showAction="true"
:showClose="true"
@close="setPayRecordStatusDialogVisible = false"
@confirm="handleConfirmSetPayRecordStatus"
>
<SearchForm ref="setPayRecordStatusFormRef" :config="setPayRecordStatusFormConfig" /> <SearchForm ref="setPayRecordStatusFormRef" :config="setPayRecordStatusFormConfig" />
</CommonDialog> </CommonDialog>
</div> </div>
</template> </template>
<script setup name="Payables"> <script setup name="Payables">
import CommonPage from '@/components/commonPage' import CommonPage from '@/components/commonPage'
import { ref, reactive,nextTick } from 'vue' import { ref, reactive, nextTick } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus' import { ElMessage, ElMessageBox } from 'element-plus'
import { formatCurrency } from '@/utils/number' import { formatCurrency, calculateAmount, formatNumberToMaxDigits } from '@/utils/number'
import { expectedFortuneList, payRecordList, addPayRecord, updatePayRecord, exportPayRecord, payableReport } from '@/api/financial/commission' import {
expectedFortuneList,
payRecordList,
addPayRecord,
updatePayRecord,
exportPayRecord,
payableReport,
commissionExchangeRateApi
} from '@/api/financial/commission'
import SearchForm from '@/components/SearchForm/SearchForm.vue' import SearchForm from '@/components/SearchForm/SearchForm.vue'
import CommonDialog from '@/components/commonDialog' import CommonDialog from '@/components/commonDialog'
import { loadDicts, getDictLabel } from '@/utils/useDict' import { loadDicts, getDictLabel } from '@/utils/useDict'
import useUserStore from '@/store/modules/user' import useUserStore from '@/store/modules/user'
import { safeDownload } from '@/utils/safeDownload' import { safeDownload } from '@/utils/safeDownload'
const updatePayRecordFormModel = ref({})
const updatePayRecordFormRef = ref(null)
const updatePayRecordDialogVisible = ref(false)
const payableReportTableData = ref([]) const payableReportTableData = ref([])
const payableReportTableColumns = ref([ const payableReportTableColumns = ref([
{ prop: 'policyNo', label: '保单号', sortable: true, width: '150', formatter: (row) => row.policyNo || '-' }, {
{ prop: 'broker', label: '转介人(主)', sortable: true, width: '150', formatter: (row) => row.broker || '-' }, prop: 'policyNo',
{ prop: 'brokerGradeName', label: '职级', sortable: true, width: '80', formatter: (row) => row.brokerGradeName || '-' }, label: '保单号',
{ prop: 'fortunePeriod', label: '出账期数', sortable: true, width: '120', formatter: (row) => row.fortunePeriod || '-' }, sortable: true,
{ prop: 'fortuneTotalPeriod', label: '出账总期数', sortable: true, width: '120', formatter: (row) => row.fortuneTotalPeriod || '-' }, width: '150',
{ prop: 'payoutDate', label: '出账日(估)', sortable: true, width: '130', }, formatter: row => row.policyNo || '-'
{ prop: 'paidRatio', label: '已出账比例', sortable: true, width: '120', formatter: (row) => (row.paidRatio || 0) + '%' || '-' }, },
{ prop: 'unpaidRatio', label: '待出账比例', sortable: true, width: '120', formatter: (row) => (row.unpaidRatio || 0) + '%' || '-' }, {
{ prop: 'paidAmount', label: '已出账金额', sortable: true, width: '120', formatter: (row) => formatCurrency(row.paidAmount || 0) }, prop: 'policyHolder',
{ prop: 'unpaidAmount', label: '待出账金额', sortable: true, width: '120', formatter: (row) => formatCurrency(row.unpaidAmount || 0) }, label: '投保人',
{ prop: 'hkdAmount', label: '应出账金额(估)', sortable: true, width: '120', formatter: (row) => formatCurrency(row.hkdAmount || 0) }, sortable: true,
{ prop: 'currency', label: '出账币种', sortable: true, width: '120', formatter: (row) => row.currency || '-' }, width: '120',
{ prop: 'premium', label: '期交保费', sortable: true, width: '120', formatter: (row) => formatCurrency(row.premium || 0) }, formatter: row => row.policyHolder || '-'
{ prop: 'insuranceCompany', label: '保险公司', sortable: true, width: '120', formatter: (row) => row.insuranceCompany || '-' }, },
{ prop: 'productName', label: '产品计划', sortable: true, width: '120', formatter: (row) => row.productName || '-' }, {
{ prop: 'policyCurrency', label: '保单币种', sortable: true, width: '120', formatter: (row) => row.policyCurrency || '-' } prop: 'broker',
label: '转介人(主)',
sortable: true,
width: '150',
formatter: row => row.broker || '-'
},
{
prop: 'brokerGradeName',
label: '职级',
sortable: true,
width: '80',
formatter: row => row.brokerGradeName || '-'
},
{
prop: 'fortunePeriod',
label: '出账期数',
sortable: true,
width: '120',
formatter: row => row.fortunePeriod || '-'
},
{
prop: 'fortuneTotalPeriod',
label: '出账总期数',
sortable: true,
width: '120',
formatter: row => row.fortuneTotalPeriod || '-'
},
{ prop: 'payoutDate', label: '出账年月(估)', sortable: true, width: '130' },
{
prop: 'paidRatio',
label: '已出账比例',
sortable: true,
width: '120',
formatter: row => (row.paidRatio || 0) + '%' || '-'
},
{
prop: 'unpaidRatio',
label: '待出账比例',
sortable: true,
width: '120',
formatter: row => (row.unpaidRatio || 0) + '%' || '-'
},
{
prop: 'paidAmount',
label: '已出账金额',
sortable: true,
width: '120',
formatter: row => formatCurrency(row.paidAmount || 0)
},
{
prop: 'unpaidAmount',
label: '待出账金额',
sortable: true,
width: '120',
formatter: row => formatCurrency(row.unpaidAmount || 0)
},
{
prop: 'hkdAmount',
label: '应出账金额(估)',
sortable: true,
width: '120',
formatter: row => formatCurrency(row.hkdAmount || 0)
},
{
prop: 'currency',
label: '出账币种',
sortable: true,
width: '120',
formatter: row => row.currency || '-'
},
{
prop: 'premium',
label: '期交保费',
sortable: true,
width: '120',
formatter: row => formatCurrency(row.premium || 0)
},
{
prop: 'insuranceCompany',
label: '保险公司',
sortable: true,
width: '120',
formatter: row => row.insuranceCompany || '-'
},
{
prop: 'productName',
label: '产品计划',
sortable: true,
width: '120',
formatter: row => row.productName || '-'
},
{
prop: 'policyCurrency',
label: '保单币种',
sortable: true,
width: '120',
formatter: row => row.policyCurrency || '-'
}
]) ])
const detailDialogVisible = ref(false) const detailDialogVisible = ref(false)
...@@ -158,32 +398,222 @@ const detailDialogVisible = ref(false) ...@@ -158,32 +398,222 @@ const detailDialogVisible = ref(false)
const payableReportListTableColumns = ref([ const payableReportListTableColumns = ref([
// { prop: 'fortuneBizType', label: '应付款类型', sortable: true, width: '120', formatter: (row) => getFortuneBizTypeLabel(row.fortuneBizType) || '-' }, // { prop: 'fortuneBizType', label: '应付款类型', sortable: true, width: '120', formatter: (row) => getFortuneBizTypeLabel(row.fortuneBizType) || '-' },
// { prop: 'payableNo', label: '应付账款编号', sortable: true, width: '120', formatter: (row) => row.payableNo || '-' }, // { prop: 'payableNo', label: '应付账款编号', sortable: true, width: '120', formatter: (row) => row.payableNo || '-' },
{ prop: 'policyNo', label: '保单号', sortable: true, width: '130', formatter: (row) => row.policyNo || '-' }, {
{ prop: 'broker', label: '转介人', sortable: true, width: '120', formatter: (row) => row.broker || '-' }, prop: 'type',
{ prop: 'brokerGradeName', label: '职级', sortable: true, width: '120', formatter: (row) => row.brokerGradeName || '-' }, label: '是否实际出账',
{ prop: 'fortuneName', label: '出账项目', sortable: true, width: '120', formatter: (row) => row.fortuneName || '-' }, sortable: true,
{ prop: 'status', label: '出账状态', sortable: true, width: '120', formatter: (row) => getDictLabel('csf_expected_fortune_status', row.status) || '-' }, width: '80',
{ prop: 'currencyName', label: '出账币种', sortable: true, width: '100', formatter: (row) => row.currencyName || '-' }, formatter: row => getTypeLabel(row.type) || '-'
{ prop: 'fortunePeriod', label: '出账期数', sortable: true, width: '100', formatter: (row) => row.fortunePeriod || '-' }, },
{ prop: 'fortuneTotalPeriod', label: '出账总期数', sortable: true, width: '100', formatter: (row) => row.fortuneTotalPeriod || '-' },
{ prop: 'payoutDate', label: '出账日(估)', sortable: true, width: '120', formatter: (row) => row.payoutDate || '-' },
{ prop: 'actualPayoutDate', label: '出账日(实)', sortable: true, width: '120', formatter: (row) => row.actualPayoutDate || '-' },
// { prop: 'commissionRatio', label: '职级对应积分比例', sortable: true, width: '120', formatter: (row) => (row.commissionRatio || 0) || '-' },
{ prop: 'hkdAmount', label: '应出账金额(估)', sortable: true, width: '120', formatter: (row) => formatCurrency(row.hkdAmount || 0) },
{ prop: 'paidRatio', label: '已出账比例', sortable: true, width: '120', formatter: (row) => (row.paidRatio || 0) + '%' || '-' },
{ prop: 'paidAmount', label: '已出账金额', sortable: true, width: '120', formatter: (row) => formatCurrency(row.paidAmount || 0) },
{ prop: 'unpaidRatio', label: '待出账比例', sortable: true, width: '120', formatter: (row) => (row.unpaidRatio || 0) + '%' || '-' },
{ prop: 'unpaidAmount', label: '待出账金额(估)', sortable: true, width: '120', formatter: (row) => formatCurrency(row.unpaidAmount || 0) },
{ prop: 'brokerRatio', label: '持有比例', sortable: true, width: '100', formatter: (row) => (row.brokerRatio || 0) + '%' || '-' },
{ prop: 'premium', label: '期交保费', sortable: true, width: '120', formatter: (row) => formatCurrency(row.premium || 0) },
{ prop: 'insuranceCompany', label: '保险公司', sortable: true, width: '120', formatter: (row) => row.insuranceCompany || '-' },
{ prop: 'productName', label: '产品计划', sortable: true, width: '120', formatter: (row) => row.productName || '-' },
{ prop: 'statusDesc', label: '修改理由', sortable: true, width: '120', formatter: (row) => row.statusDesc || '-' },
{ prop: 'remark', label: '备注', sortable: true, width: '120', formatter: (row) => row.remark || '-' },
])
{
prop: 'payableNo',
label: '业务编号',
sortable: true,
width: '130',
formatter: row => row.payableNo || '-'
},
// {
// prop: 'policyNo',
// label: '关联业务编号',
// sortable: true,
// width: '130',
// formatter: row => row.policyNo || '-'
// },
{
prop: 'policyNo',
label: '保单号',
sortable: true,
width: '130',
formatter: row => row.policyNo || '-'
},
{
prop: 'broker',
label: '转介人',
sortable: true,
width: '120',
formatter: row => row.broker || '-'
},
{
prop: 'brokerGradeName',
label: '职级',
sortable: true,
width: '120',
formatter: row => row.brokerGradeName || '-'
},
{
prop: 'fortuneName',
label: '出账项目',
sortable: true,
width: '120',
formatter: row => row.fortuneName || '-'
},
{
prop: 'status',
label: '出账状态',
sortable: true,
width: '120',
formatter: row => getDictLabel('csf_expected_fortune_status', row.status) || '-'
},
{
prop: 'payoutCurrency',
label: '出账币种',
sortable: true,
width: '100',
formatter: row => row.payoutCurrency || '-'
},
{
prop: 'fortunePeriod',
label: '出账期数',
sortable: true,
width: '100',
formatter: row => row.fortunePeriod || '-'
},
{
prop: 'fortuneTotalPeriod',
label: '出账总期数',
sortable: true,
width: '100',
formatter: row => row.fortuneTotalPeriod || '-'
},
{
prop: 'payoutDate',
label: '出账年月(估)',
sortable: true,
width: '120',
formatter: row => row.payoutDate || '-'
},
{
prop: 'actualPayoutDate',
label: '出账年月(实)',
sortable: true,
width: '120',
formatter: row => row.actualPayoutDate || '-'
},
{
prop: 'paidRatio',
label: '已出账比例',
sortable: true,
width: '120',
formatter: row => (row.paidRatio || 0) + '%' || '-'
},
{
prop: 'unpaidRatio',
label: '待出账比例',
sortable: true,
width: '120',
formatter: row => (row.unpaidRatio || 0) + '%' || '-'
},
// { prop: 'commissionRatio', label: '职级对应积分比例', sortable: true, width: '120', formatter: (row) => (row.commissionRatio || 0) || '-' },
{
prop: 'hkdAmount',
label: 'HKD应出账金额',
sortable: true,
width: '120',
formatter: row => formatCurrency(row.hkdAmount || 0)
},
// {
// prop: 'exchangeRate',
// label: '保单币种->HKD(汇率)',
// sortable: true,
// width: '140',
// formatter: row => formatCurrency(row.exchangeRate || 0, '', 5)
// },
{
prop: 'exchangeRate',
label: '保单币种->HKD(汇率)',
sortable: true,
width: '140',
formatter: row => formatNumberToMaxDigits(row.exchangeRate, 5, '-')
},
{
prop: 'paidAmount',
label: '已出账金额(HKD)',
sortable: true,
width: '120',
formatter: row => formatCurrency(row.paidAmount || 0)
},
{
prop: 'unpaidAmount',
label: '待出账金额(估)',
sortable: true,
width: '120',
formatter: row => formatCurrency(row.unpaidAmount || 0)
},
{
prop: 'currentPaymentHkdAmount',
label: '本期待出账金额',
sortable: true,
width: '120',
formatter: row => formatCurrency(row.currentPaymentHkdAmount || 0)
},
{
prop: 'exchangeRate',
label: '本期结算汇率(实)',
sortable: true,
width: '120',
formatter: row => formatNumberToMaxDigits(row.exchangeRate, 5, '-')
},
{
prop: 'brokerRatio',
label: '持有比例',
sortable: true,
width: '100',
formatter: row => (row.brokerRatio || 0) + '%' || '-'
},
{
prop: 'premium',
label: '期交保费',
sortable: true,
width: '120',
formatter: row => formatCurrency(row.premium || 0)
},
{
prop: 'insuranceCompany',
label: '保险公司',
sortable: true,
width: '120',
formatter: row => row.insuranceCompany || '-'
},
{
prop: 'productName',
label: '产品计划',
sortable: true,
width: '120',
formatter: row => row.productName || '-'
},
{
prop: 'remark',
label: '备注',
sortable: true,
width: '120',
formatter: row => row.remark || '-'
},
// {
// prop: 'statusDesc',
// label: '修改理由',
// sortable: true,
// width: '120',
// formatter: row => row.statusDesc || '-'
// },
{
prop: 'creatorName',
label: '创建人',
sortable: true,
width: '120',
formatter: row => row.creatorName || '-'
},
{
prop: 'createTime',
label: '创建时间',
sortable: true,
width: '180',
formatter: row => row.createTime || '-'
}
])
// 设置出账状态 // 设置出账状态
const setPayRecordStatusDialogVisible = ref(false) const setPayRecordStatusDialogVisible = ref(false)
...@@ -195,15 +625,14 @@ const setPayRecordStatusFormConfig = ref([ ...@@ -195,15 +625,14 @@ const setPayRecordStatusFormConfig = ref([
prop: 'status', prop: 'status',
label: '出账状态', label: '出账状态',
dictType: 'csf_expected_fortune_status' dictType: 'csf_expected_fortune_status'
}, { },
{
type: 'textarea', type: 'textarea',
prop: 'statusDesc', prop: 'statusDesc',
label: '修改理由', label: '修改理由'
}, }
])
const visibleDefaultButtons = ref([
'add', 'export', 'reset', 'query'
]) ])
const visibleDefaultButtons = ref(['add', 'export', 'reset', 'query'])
const userStore = useUserStore() const userStore = useUserStore()
// 应收单类型 // 应收单类型
const fortuneBizTypeOptions = [ const fortuneBizTypeOptions = [
...@@ -211,7 +640,7 @@ const fortuneBizTypeOptions = [ ...@@ -211,7 +640,7 @@ const fortuneBizTypeOptions = [
{ value: 'U', label: '非关联保单应付单' } { value: 'U', label: '非关联保单应付单' }
] ]
// 应付单类型通过value转成label // 应付单类型通过value转成label
const getFortuneBizTypeLabel = (value) => { const getFortuneBizTypeLabel = value => {
const item = fortuneBizTypeOptions.find(item => item.value === value) const item = fortuneBizTypeOptions.find(item => item.value === value)
return item?.label || '' return item?.label || ''
} }
...@@ -224,9 +653,30 @@ const searchConfig = ref([ ...@@ -224,9 +653,30 @@ const searchConfig = ref([
label: '保单号' label: '保单号'
}, },
{ {
type: 'daterange', type: 'input',
prop: 'policyHolder',
label: '投保人'
},
{
type: 'select',
prop: 'brokerBizIdList',
label: '转介人',
api: '/insurance/base/api/userSaleExpand/page',
keywordField: 'realName',
requestParams: { pageNo: 1, pageSize: 20 },
placeholder: '输入转介人名称搜索',
debounceWait: 500, // 自定义防抖时间
valueKey: 'clientUserBizId',
labelKey: 'realName',
multiple: true,
transform: res => {
return res?.data.records || []
}
},
{
type: 'monthrange',
prop: 'payoutDate', prop: 'payoutDate',
label: '出账(估)', label: '出账(估)',
startPlaceholder: '开始时间', startPlaceholder: '开始时间',
endPlaceholder: '结束时间' endPlaceholder: '结束时间'
}, },
...@@ -242,9 +692,7 @@ const searchConfig = ref([ ...@@ -242,9 +692,7 @@ const searchConfig = ref([
prop: 'fortunePeriod', prop: 'fortunePeriod',
label: '出账期数', label: '出账期数',
inputType: 'decimal', inputType: 'decimal',
rules: [ rules: [{ pattern: /^\d+$/, message: '只能输入正整数', trigger: 'blur' }]
{ pattern: /^\d+$/, message: '只能输入正整数', trigger: 'blur' }
]
}, },
{ {
type: 'select', type: 'select',
...@@ -264,11 +712,12 @@ const searchConfig = ref([ ...@@ -264,11 +712,12 @@ const searchConfig = ref([
valueKey: 'insuranceCompanyBizId', valueKey: 'insuranceCompanyBizId',
labelKey: 'fullName', labelKey: 'fullName',
multiple: true, multiple: true,
transform: (res) => { transform: res => {
console.log(res) console.log(res)
return res?.data.records || [] return res?.data.records || []
} }
}, { },
{
type: 'select', type: 'select',
prop: 'productLaunchBizIdList', prop: 'productLaunchBizIdList',
label: '产品计划', label: '产品计划',
...@@ -276,17 +725,22 @@ const searchConfig = ref([ ...@@ -276,17 +725,22 @@ const searchConfig = ref([
keywordField: 'productName', keywordField: 'productName',
requestParams: { requestParams: {
tenantBizId: userStore.projectInfo.tenantBizId || '', tenantBizId: userStore.projectInfo.tenantBizId || '',
projectBizId: userStore.projectInfo.projectBizId || '', fieldBizId: 'field_olk1qZe81qHHKXbw', fieldValueBizId: 'field_value_uOfJH5ucA2YwJpbn', pageNo: 1, pageSize: 20 projectBizId: userStore.projectInfo.projectBizId || '',
fieldBizId: 'field_olk1qZe81qHHKXbw',
fieldValueBizId: 'field_value_uOfJH5ucA2YwJpbn',
pageNo: 1,
pageSize: 20
}, },
placeholder: '输入产品计划名称搜索', placeholder: '输入产品计划名称搜索',
debounceWait: 500, // 自定义防抖时间 debounceWait: 500, // 自定义防抖时间
valueKey: 'productLaunchBizId', valueKey: 'productLaunchBizId',
labelKey: 'productName', labelKey: 'productName',
multiple: true, multiple: true,
transform: (res) => { transform: res => {
return res?.data.records || [] return res?.data.records || []
} }
}, { },
{
type: 'select', type: 'select',
prop: 'fortuneBizType', prop: 'fortuneBizType',
label: '应付单类型', label: '应付单类型',
...@@ -294,7 +748,8 @@ const searchConfig = ref([ ...@@ -294,7 +748,8 @@ const searchConfig = ref([
{ value: 'R', label: '关联保单应付单' }, { value: 'R', label: '关联保单应付单' },
{ value: 'U', label: '非关联保单应付单' } { value: 'U', label: '非关联保单应付单' }
] ]
}, { },
{
type: 'select', type: 'select',
prop: 'teamBizIdList', prop: 'teamBizIdList',
label: '出单团队', label: '出单团队',
...@@ -306,21 +761,20 @@ const searchConfig = ref([ ...@@ -306,21 +761,20 @@ const searchConfig = ref([
multiple: true, multiple: true,
valueKey: 'teamBizId', valueKey: 'teamBizId',
labelKey: 'teamName', labelKey: 'teamName',
transform: (res) => { transform: res => {
return res?.data.records || [] return res?.data.records || []
} }
}, }
]) ])
const payRecordDialogTableVisible = ref(false) const payRecordDialogTableVisible = ref(false)
// 新增出账记录 // 新增出账记录
const addPayRecordFormModel = ref({ const addPayRecordFormModel = ref({
fortuneBizType: 'U', fortuneBizType: 'R'
}) })
const addPayRecordDialogVisible = ref(false) const addPayRecordDialogVisible = ref(false)
const addPayRecordFormRef = ref() const addPayRecordFormRef = ref()
const addPayRecordFormConfig = [ const updatePayRecordFormConfig = [
{ {
type: 'select', type: 'select',
prop: 'fortuneBizType', prop: 'fortuneBizType',
...@@ -329,67 +783,421 @@ const addPayRecordFormConfig = [ ...@@ -329,67 +783,421 @@ const addPayRecordFormConfig = [
{ value: 'R', label: '关联保单应付单' }, { value: 'R', label: '关联保单应付单' },
{ value: 'U', label: '非关联保单应付单' } { value: 'U', label: '非关联保单应付单' }
] ]
}, { },
{
type: 'input', type: 'input',
prop: 'policyNo', prop: 'policyNo',
label: '关联保单号', label: '关联保单号',
visible: (formData) => formData.fortuneBizType === 'R', visible: formData => formData.fortuneBizType === 'R',
}, { rules: [{ required: true, message: '关联保单号必填', trigger: 'blur' }]
},
{
type: 'month',
prop: 'payoutDate',
label: '出账月(估)',
placeholder: '请选择'
},
{
type: 'month',
prop: 'actualPayoutDate',
label: '出账月(实)',
placeholder: '请选择',
maxDate: 'today'
},
// {
// type: 'input',
// prop: 'fortuneName',
// label: '出账项目'
// },
{
type: 'select',
prop: 'fortuneType',
label: '出账项目类型',
dictType: 'csf_fortune_type',
onChangeExtraFields: {
fortuneName: 'itemLabel'
}
},
{
type: 'input', type: 'input',
prop: 'fortunePeriod', prop: 'fortunePeriod',
label: '佣金期数', label: '佣金期数',
inputType: 'decimal', inputType: 'decimal',
visible: (formData) => formData.fortuneBizType === 'R', visible: formData => formData.fortuneBizType === 'R'
rules: [ // rules: [{ pattern: /^\d+$/, message: '只能输入正整数', trigger: 'blur' }]
{ pattern: /^\d+$/, message: '只能输入正整数', trigger: 'blur' } },
] {
}, {
type: 'input', type: 'input',
prop: 'fortuneTotalPeriod', prop: 'fortuneTotalPeriod',
label: '总期数', label: '总期数',
inputType: 'decimal', inputType: 'decimal',
visible: (formData) => formData.fortuneBizType === 'R', visible: formData => formData.fortuneBizType === 'R'
rules: [ // rules: [{ pattern: /^\d+$/, message: '只能输入正整数', trigger: 'blur' }]
{ pattern: /^\d+$/, message: '只能输入正整数', trigger: 'blur' } },
] {
}, {
type: 'date',
prop: 'payoutDate',
label: '出账日(估)',
placeholder: '请选择'
}, {
type: 'date',
prop: 'actualPayoutDate',
label: '出账日(实)',
placeholder: '请选择',
maxDate: 'today'
}, {
type: 'input',
prop: 'hkdAmount',
label: '出账金额',
rules: [
{ required: true, message: '请输入', trigger: 'blur' },
{ pattern: /^-?\d+(\.\d{1,2})?$/, message: '小数(最多两位)', trigger: 'blur' }
]
}, {
type: 'select', type: 'select',
prop: 'currency', prop: 'currency',
label: '出账币种', label: '出账币种',
dictType: 'bx_currency_type' dictType: 'bx_currency_type',
}, { defaultValue: 'HKD'
},
{
type: 'select',
prop: 'brokerBizId',
label: '转介人',
api: '/insurance/base/api/userSaleExpand/page',
keywordField: 'realName',
requestParams: { pageNo: 1, pageSize: 200 },
placeholder: '输入转介人名称搜索',
debounceWait: 500, // 自定义防抖时间
valueKey: 'clientUserBizId',
labelKey: 'realName',
onChangeExtraFields: {
broker: 'realName', // 自动同步 raw.name 到 reconciliationCompany
reconciliationCompanyCode: 'code'
},
transform: res => {
return res?.data.records || []
}
},
{
type: 'select',
prop: 'teamBizId',
label: '所属团队',
api: '/csf/api/team/page',
keywordField: 'teamName',
requestParams: { pageNo: 1, pageSize: 200 },
placeholder: '输入所属团队名称搜索',
debounceWait: 500, // 自定义防抖时间
valueKey: 'teamBizId',
labelKey: 'teamName',
onChangeExtraFields: {
// broker: 'realName', // 自动同步 raw.name 到 reconciliationCompany
// reconciliationCompanyCode: 'code',
team: 'teamName',
teamBizId: 'teamBizId'
},
transform: res => {
return res?.data.records || []
},
rules: [{ required: false, message: '所属团队必填', trigger: 'blur' }]
},
{
type: 'select',
prop: 'ruleCurrency',
label: '保单币种',
dictType: 'bx_currency_type',
rules: [{ required: true, message: '保单币种必填', trigger: 'blur' }]
},
{
type: 'select',
prop: 'originalCurrency',
label: '原币种',
dictType: 'bx_currency_type',
rules: [{ required: true, message: '原币种必填', trigger: 'blur' }]
},
{
type: 'select',
prop: 'payoutCurrency',
label: '发放币种',
dictType: 'bx_currency_type',
rules: [{ required: true, message: '发放币种必填', trigger: 'blur' }]
},
{
type: 'input', type: 'input',
prop: 'defaultExchangeRate', prop: 'originalAmount',
label: '结算汇率(入账检核时的汇率)', label: '原币种金额',
rules: [ inputType: 'decimal',
{ required: true, message: '请输入', trigger: 'blur' }, rules: [{ required: true, message: '只能输入正整数和小数', trigger: 'blur' }]
{ pattern: /^-?\d+(\.\d{1,6})?$/, message: '小数(最多6位)', trigger: 'blur' } },
] // 汇率3(原币种->港币)
},{ {
type: 'input',
prop: 'originalToHkdRate',
label: '原币种->港币(汇率)',
inputType: 'decimal',
decimalDigits: 5,
rules: [{ required: true, message: '只能输入正整数和小数', trigger: 'blur' }]
// defaultValue: 1
},
{
type: 'input',
prop: 'hkdAmount',
label: '港币金额',
inputType: 'decimal',
rules: [{ required: true, message: '只能输入正整数和小数', trigger: 'blur' }]
},
// 汇率1(港币->发放币种)
{
type: 'input',
prop: 'hkdToPayoutRate',
label: '港币->发放币种(汇率)',
inputType: 'decimal',
decimalDigits: 5,
rules: [{ required: true, message: '只能输入正整数和小数', trigger: 'blur' }]
// defaultValue: 1
},
{
type: 'input',
prop: 'payoutAmount',
label: '实际发放金额',
inputType: 'decimal',
rules: [{ required: true, message: '只能输入正整数和小数', trigger: 'blur' }]
},
// 汇率2(保单币种->港币)入账检核汇率
{
type: 'input',
prop: 'exchangeRate',
label: '保单币种->港币(入账检核汇率)',
inputType: 'decimal',
decimalDigits: 5,
rules: [{ required: true, message: '只能输入正整数和小数', trigger: 'blur' }]
// defaultValue: 1
},
{
type: 'input',
prop: 'ruleAmount',
label: '保单币种金额',
inputType: 'decimal',
rules: [{ required: true, message: '只能输入正整数和小数', trigger: 'blur' }]
},
{
type: 'input',
prop: 'remark',
label: '备注'
}
// {
// type: 'input',
// prop: 'hkdAmount',
// label: '出账金额',
// rules: [
// { required: true, message: '请输入', trigger: 'blur' },
// { pattern: /^-?\d+(\.\d{1,2})?$/, message: '小数(最多两位)', trigger: 'blur' }
// ]
// },
// {
// type: 'input',
// prop: 'defaultExchangeRate',
// label: '结算汇率(入账检核时的汇率)',
// rules: [
// { required: true, message: '请输入', trigger: 'blur' },
// { pattern: /^-?\d+(\.\d{1,6})?$/, message: '小数(最多6位)', trigger: 'blur' }
// ]
// },
// {
// type: 'select',
// prop: 'status',
// label: '出账状态',
// dictType: 'csf_expected_fortune_status'
// },
// {
// type: 'input',
// prop: 'exchangeRate',
// label: '结算汇率',
// inputType: 'decimal',
// rules: [{ required: true, message: '只能输入正整数和小数', trigger: 'blur' }]
// // defaultValue: 1
// },
// {
// type: 'input',
// prop: 'remark',
// label: '备注'
// }
]
// const addPayRecordFormConfig = [
// {
// type: 'select',
// prop: 'fortuneBizType',
// label: '应付单类型',
// options: [
// { value: 'R', label: '关联保单应付单' },
// { value: 'U', label: '非关联保单应付单' }
// ]
// },
// {
// type: 'input',
// prop: 'policyNo',
// label: '关联保单号',
// visible: formData => formData.fortuneBizType === 'R'
// },
// {
// type: 'input',
// prop: 'fortunePeriod',
// label: '佣金期数',
// inputType: 'decimal',
// visible: formData => formData.fortuneBizType === 'R',
// rules: [{ pattern: /^\d+$/, message: '只能输入正整数', trigger: 'blur' }]
// },
// {
// type: 'input',
// prop: 'fortuneTotalPeriod',
// label: '总期数',
// inputType: 'decimal',
// visible: formData => formData.fortuneBizType === 'R',
// rules: [{ pattern: /^\d+$/, message: '只能输入正整数', trigger: 'blur' }]
// },
// {
// type: 'date',
// prop: 'payoutDate',
// label: '出账日(估)',
// placeholder: '请选择'
// },
// {
// type: 'date',
// prop: 'actualPayoutDate',
// label: '出账日(实)',
// placeholder: '请选择',
// maxDate: 'today'
// },
// {
// type: 'input',
// prop: 'hkdAmount',
// label: '出账金额',
// rules: [
// { required: true, message: '请输入', trigger: 'blur' },
// { pattern: /^-?\d+(\.\d{1,2})?$/, message: '小数(最多两位)', trigger: 'blur' }
// ]
// },
// {
// type: 'select',
// prop: 'currency',
// label: '出账币种',
// dictType: 'bx_currency_type',
// defaultValue: 'HKD'
// },
// {
// type: 'input',
// prop: 'defaultExchangeRate',
// label: '结算汇率(入账检核时的汇率)',
// rules: [
// { required: true, message: '请输入', trigger: 'blur' },
// { pattern: /^-?\d+(\.\d{1,6})?$/, message: '小数(最多6位)', trigger: 'blur' }
// ]
// },
// {
// type: 'select',
// prop: 'fortuneType',
// label: '出账项目',
// dictType: 'csf_fortune_type'
// },
// {
// type: 'select',
// prop: 'brokerBizId',
// label: '转介人',
// api: '/insurance/base/api/userSaleExpand/page',
// keywordField: 'realName',
// requestParams: { pageNo: 1, pageSize: 200 },
// placeholder: '输入转介人名称搜索',
// debounceWait: 500, // 自定义防抖时间
// valueKey: 'clientUserBizId',
// labelKey: 'realName',
// onChangeExtraFields: {
// broker: 'realName', // 自动同步 raw.name 到 reconciliationCompany
// reconciliationCompanyCode: 'code'
// },
// transform: res => {
// return res?.data.records || []
// }
// },
// {
// type: 'select',
// prop: 'status',
// label: '出账状态',
// dictType: 'csf_expected_fortune_status'
// },
// {
// type: 'input',
// prop: 'exchangeRate',
// label: '结算汇率',
// inputType: 'decimal',
// rules: [{ required: true, message: '只能输入正整数和小数', trigger: 'blur' }]
// // defaultValue: 1
// },
// {
// type: 'input',
// prop: 'remark',
// label: '备注'
// }
// ]
const addPayRecordFormConfig = [
{
type: 'select',
prop: 'fortuneBizType',
label: '应付单类型',
options: fortuneBizTypeOptions,
rules: [{ required: true, message: '应付单类型必填', trigger: 'blur' }]
},
{
type: 'select',
prop: 'status',
label: '出账状态',
dictType: 'csf_expected_fortune_status',
rules: [{ required: true, message: '出账状态必填', trigger: 'blur' }]
},
{
type: 'input',
prop: 'policyNo',
label: '关联保单号',
visible: formData => formData.fortuneBizType === 'R'
},
{
type: 'month',
prop: 'payoutDate',
label: '出账月(估)',
placeholder: '请选择',
maxDate: 'today',
rules: [{ required: true, message: '出账月(估)必填', trigger: 'blur' }]
},
{
type: 'month',
prop: 'actualPayoutDate',
label: '出账月(实)',
placeholder: '请选择',
maxDate: 'today',
rules: [{ required: true, message: '出账月(实)必填', trigger: 'blur' }]
},
{
type: 'input',
prop: 'statusDesc',
label: '修改理由'
},
// {
// type: 'input',
// prop: 'fortuneName',
// label: '出账项目名称',
// rules: [{ required: true, message: '出账项目名称必填', trigger: 'blur' }]
// },
{
type: 'select', type: 'select',
prop: 'fortuneType', prop: 'fortuneType',
label: '出账项目', label: '出账项目类型',
dictType: 'csf_fortune_type' dictType: 'csf_fortune_type',
}, { rules: [{ required: true, message: '出账项目类型必填', trigger: 'blur' }],
onChangeExtraFields: {
fortuneName: 'itemLabel'
}
},
{
type: 'input',
prop: 'fortunePeriod',
label: '佣金期数',
inputType: 'decimal',
visible: formData => formData.fortuneBizType === 'R',
rules: [{ required: true, pattern: /^\d+$/, message: '只能输入正整数', trigger: 'blur' }]
},
{
type: 'input',
prop: 'fortuneTotalPeriod',
label: '总期数',
inputType: 'decimal',
visible: formData => formData.fortuneBizType === 'R',
rules: [{ pattern: /^\d+$/, message: '只能输入正整数', trigger: 'blur' }]
},
{
type: 'select', type: 'select',
prop: 'brokerBizId', prop: 'brokerBizId',
label: '转介人', label: '转介人',
...@@ -401,19 +1209,342 @@ const addPayRecordFormConfig = [ ...@@ -401,19 +1209,342 @@ const addPayRecordFormConfig = [
valueKey: 'clientUserBizId', valueKey: 'clientUserBizId',
labelKey: 'realName', labelKey: 'realName',
onChangeExtraFields: { onChangeExtraFields: {
broker: 'realName',// 自动同步 raw.name 到 reconciliationCompany broker: 'realName', // 自动同步 raw.name 到 reconciliationCompany
reconciliationCompanyCode: 'code' reconciliationCompanyCode: 'code'
// team: 'deptName',
// teamBizId: 'deptBizId'
},
transform: res => {
return res?.data.records || []
},
rules: [{ required: true, message: '转介人必填', trigger: 'blur' }]
},
{
type: 'select',
prop: 'teamBizId',
label: '所属团队',
api: '/csf/api/team/page',
keywordField: 'teamName',
requestParams: { pageNo: 1, pageSize: 20 },
placeholder: '输入所属团队名称搜索',
debounceWait: 500, // 自定义防抖时间
valueKey: 'teamBizId',
labelKey: 'teamName',
onChangeExtraFields: {
// broker: 'realName', // 自动同步 raw.name 到 reconciliationCompany
// reconciliationCompanyCode: 'code',
team: 'teamName',
teamBizId: 'teamBizId'
}, },
transform: (res) => { transform: res => {
return res?.data.records || [] return res?.data.records || []
} }
}, { // rules: [{ required: true, message: '所属团队必填', trigger: 'blur' }]
},
// {
// type: 'input',
// prop: 'defaultExchangeRate',
// label: '结算汇率(入账检核时的汇率)',
// rules: [
// { required: true, message: '请输入', trigger: 'blur' },
// { pattern: /^-?\d+(\.\d{1,6})?$/, message: '小数(最多6位)', trigger: 'blur' }
// ]
// },
{
type: 'select', type: 'select',
prop: 'status', prop: 'ruleCurrency',
label: '出账状态', label: '保单币种',
dictType: 'csf_expected_fortune_status' dictType: 'bx_currency_type',
rules: [{ required: true, message: '保单币种必填', trigger: 'blur' }]
},
{
type: 'select',
prop: 'originalCurrency',
label: '原币种',
dictType: 'bx_currency_type',
rules: [{ required: true, message: '原币种必填', trigger: 'blur' }]
},
{
type: 'select',
prop: 'payoutCurrency',
label: '发放币种',
dictType: 'bx_currency_type',
rules: [{ required: true, message: '发放币种必填', trigger: 'blur' }]
},
{
type: 'input',
prop: 'originalAmount',
label: '原币种金额',
inputType: 'decimal',
rules: [{ required: true, message: '只能输入正整数和小数', trigger: 'blur' }]
},
// 汇率3(原币种->港币)
{
type: 'input',
prop: 'originalToHkdRate',
label: '原币种->港币(汇率)',
inputType: 'decimal',
decimalDigits: 5,
rules: [{ required: true, message: '只能输入正整数和小数', trigger: 'blur' }]
// defaultValue: 1
},
{
type: 'input',
prop: 'hkdAmount',
label: '港币金额',
inputType: 'decimal',
rules: [{ required: true, message: '只能输入正整数和小数', trigger: 'blur' }]
},
// '汇率1(港币->发放币种)
{
type: 'input',
prop: 'hkdToPayoutRate',
label: '港币->发放币种(汇率)',
inputType: 'decimal',
decimalDigits: 5,
rules: [{ required: true, message: '只能输入正整数和小数', trigger: 'blur' }]
// defaultValue: 1
},
{
type: 'input',
prop: 'payoutAmount',
label: '实际发放金额',
inputType: 'decimal',
rules: [{ required: true, message: '只能输入正整数和小数', trigger: 'blur' }]
},
// 汇率2(保单币种->港币)入账检核汇率
{
type: 'input',
prop: 'defaultExchangeRate',
label: '保单币种->港币(入账检核汇率)',
inputType: 'decimal',
decimalDigits: 5,
rules: [{ required: true, message: '只能输入正整数和小数', trigger: 'blur' }]
// defaultValue: 1
},
{
type: 'input',
prop: 'ruleAmount',
label: '保单币种金额',
inputType: 'decimal',
rules: [{ required: true, message: '只能输入正整数和小数', trigger: 'blur' }]
},
{
type: 'input',
prop: 'remark',
label: '备注'
} }
] ]
//是否实收
const typeOptions = [
{ value: 1, label: '预计' },
{ value: 2, label: '实际' }
]
// 是否实收通过value转成label
const getTypeLabel = value => {
const item = typeOptions.find(item => item.value === value)
return item?.label || ''
}
// 定义行类名方法
const getRowClassName = ({ row }) => {
// type == 1 表示“预计”,添加高亮类名
return row.type === 1 ? 'estimated-row' : ''
}
const handleInputChange = async (formType, prop, value, item) => {
if (formType == 'addPayRecord') {
await nextTick()
let policyNo = addPayRecordFormModel.value.policyNo
let fortunePeriod = addPayRecordFormModel.value.fortunePeriod
if ((prop == 'policyNo' || prop == 'fortunePeriod') && fortunePeriod && policyNo) {
const res = await commissionExchangeRateApi({
policyNo: policyNo,
commissionPeriod: fortunePeriod
})
if (res.code == 200) {
addPayRecordFormModel.value.defaultExchangeRate = res.data
} else {
ElMessage.error('查询结算汇率失败')
}
}
// 1.计算港币金额
if (prop === 'originalToHkdRate' && value && addPayRecordFormModel.value.originalAmount) {
// 计算港币金额 原币种金额*原币种->港币
const originalAmount = addPayRecordFormModel.value.originalAmount
const originalToHkdRate = value
const hkdAmount = calculateAmount(originalAmount, originalToHkdRate, 2)
await nextTick()
addPayRecordFormModel.value.hkdAmount = hkdAmount
const hkdToPayoutRate = addPayRecordFormModel.value.hkdToPayoutRate
if (hkdToPayoutRate) {
const payoutAmount = calculateAmount(hkdAmount, hkdToPayoutRate, 2)
await nextTick()
addPayRecordFormModel.value.payoutAmount = payoutAmount
}
const defaultExchangeRate = addPayRecordFormModel.value.defaultExchangeRate
if (defaultExchangeRate) {
const ruleAmount = calculateAmount(hkdAmount, defaultExchangeRate, 2, 'Divided')
await nextTick()
addPayRecordFormModel.value.ruleAmount = ruleAmount
}
} else if (
prop === 'originalAmount' &&
value &&
addPayRecordFormModel.value.originalToHkdRate
) {
// 计算港币金额
const originalAmount = value
const originalToHkdRate = addPayRecordFormModel.value.originalToHkdRate
const hkdAmount = calculateAmount(originalAmount, originalToHkdRate, 2)
console.log('新增', hkdAmount)
await nextTick()
addPayRecordFormModel.value.hkdAmount = hkdAmount
const hkdToPayoutRate = addPayRecordFormModel.value.hkdToPayoutRate
if (hkdToPayoutRate) {
const payoutAmount = calculateAmount(hkdAmount, hkdToPayoutRate, 2)
await nextTick()
addPayRecordFormModel.value.payoutAmount = payoutAmount
}
const defaultExchangeRate = addPayRecordFormModel.value.defaultExchangeRate
if (defaultExchangeRate) {
const ruleAmount = calculateAmount(hkdAmount, defaultExchangeRate, 2, 'Divided')
await nextTick()
addPayRecordFormModel.value.ruleAmount = ruleAmount
}
}
//2.计算实际发放金额 港币金额*港币->发放币种汇率
if (prop == 'hkdAmount' && value && addPayRecordFormModel.value.hkdToPayoutRate) {
// 计算实际发放金额
const hkdToPayoutRate = addPayRecordFormModel.value.hkdToPayoutRate
const hkdAmount = value
const payoutAmount = calculateAmount(hkdAmount, hkdToPayoutRate, 2)
await nextTick()
addPayRecordFormModel.value.payoutAmount = payoutAmount
} else if (prop == 'hkdToPayoutRate' && value && addPayRecordFormModel.value.hkdAmount) {
// 计算实际发放金额
const hkdToPayoutRate = value
const hkdAmount = addPayRecordFormModel.value.hkdAmount
const payoutAmount = calculateAmount(hkdAmount, hkdToPayoutRate, 2)
await nextTick()
addPayRecordFormModel.value.payoutAmount = payoutAmount
}
//3.计算保单币种金额 港币金额*保单币种->港币汇率
if (prop == 'hkdAmount' && value && addPayRecordFormModel.value.defaultExchangeRate) {
// 计算保单币种金额
const defaultExchangeRate = addPayRecordFormModel.value.defaultExchangeRate
const hkdAmount = value
const ruleAmount = calculateAmount(hkdAmount, defaultExchangeRate, 2, 'Divided')
await nextTick()
addPayRecordFormModel.value.ruleAmount = ruleAmount
} else if (prop == 'defaultExchangeRate' && value && addPayRecordFormModel.value.hkdAmount) {
// 计算保单币种金额
const defaultExchangeRate = value
const hkdAmount = addPayRecordFormModel.value.hkdAmount
const ruleAmount = calculateAmount(hkdAmount, defaultExchangeRate, 2, 'Divided')
await nextTick()
addPayRecordFormModel.value.ruleAmount = ruleAmount
}
} else if (formType == 'updatePayRecord') {
await nextTick()
let policyNo = updatePayRecordFormModel.value.policyNo
let fortunePeriod = updatePayRecordFormModel.value.fortunePeriod
if ((prop == 'policyNo' || prop == 'fortunePeriod') && fortunePeriod && policyNo) {
const res = await commissionExchangeRateApi({
policyNo: policyNo,
commissionPeriod: fortunePeriod
})
if (res.code == 200) {
updatePayRecordFormModel.value.exchangeRate = res.data
} else {
ElMessage.error('查询结算汇率失败')
}
}
// 1.计算港币金额
if (prop === 'originalToHkdRate' && value && updatePayRecordFormModel.value.originalAmount) {
// 计算港币金额 原币种金额*原币种->港币
const originalAmount = updatePayRecordFormModel.value.originalAmount
const originalToHkdRate = value
const hkdAmount = calculateAmount(originalAmount, originalToHkdRate, 2)
await nextTick()
updatePayRecordFormModel.value.hkdAmount = hkdAmount
const hkdToPayoutRate = updatePayRecordFormModel.value.hkdToPayoutRate
if (hkdToPayoutRate) {
const payoutAmount = calculateAmount(hkdAmount, hkdToPayoutRate, 2)
await nextTick()
updatePayRecordFormModel.value.payoutAmount = payoutAmount
}
const exchangeRate = updatePayRecordFormModel.value.exchangeRate
if (exchangeRate) {
const ruleAmount = calculateAmount(hkdAmount, exchangeRate, 2, 'Divided')
await nextTick()
updatePayRecordFormModel.value.ruleAmount = ruleAmount
}
} else if (
prop === 'originalAmount' &&
value &&
updatePayRecordFormModel.value.originalToHkdRate
) {
// 计算港币金额
const originalAmount = value
const originalToHkdRate = updatePayRecordFormModel.value.originalToHkdRate
const hkdAmount = calculateAmount(originalAmount, originalToHkdRate, 2)
console.log('新增', hkdAmount)
await nextTick()
updatePayRecordFormModel.value.hkdAmount = hkdAmount
const hkdToPayoutRate = updatePayRecordFormModel.value.hkdToPayoutRate
if (hkdToPayoutRate) {
const payoutAmount = calculateAmount(hkdAmount, hkdToPayoutRate, 2)
await nextTick()
updatePayRecordFormModel.value.payoutAmount = payoutAmount
}
const exchangeRate = updatePayRecordFormModel.value.exchangeRate
if (exchangeRate) {
const ruleAmount = calculateAmount(hkdAmount, exchangeRate, 2, 'Divided')
await nextTick()
updatePayRecordFormModel.value.ruleAmount = ruleAmount
}
}
//2.计算实际发放金额 港币金额*港币->发放币种汇率
if (prop == 'hkdAmount' && value && updatePayRecordFormModel.value.hkdToPayoutRate) {
// 计算实际发放金额
const hkdToPayoutRate = updatePayRecordFormModel.value.hkdToPayoutRate
const hkdAmount = value
const payoutAmount = calculateAmount(hkdAmount, hkdToPayoutRate, 2)
await nextTick()
updatePayRecordFormModel.value.payoutAmount = payoutAmount
} else if (prop == 'hkdToPayoutRate' && value && updatePayRecordFormModel.value.hkdAmount) {
// 计算实际发放金额
const hkdToPayoutRate = value
const hkdAmount = updatePayRecordFormModel.value.hkdAmount
const payoutAmount = calculateAmount(hkdAmount, hkdToPayoutRate, 2)
await nextTick()
updatePayRecordFormModel.value.payoutAmount = payoutAmount
}
//3.计算保单币种金额 港币金额*保单币种->港币汇率
if (prop == 'hkdAmount' && value && updatePayRecordFormModel.value.exchangeRate) {
// 计算保单币种金额
const exchangeRate = updatePayRecordFormModel.value.exchangeRate
const hkdAmount = value
const ruleAmount = calculateAmount(hkdAmount, exchangeRate, 2, 'Divided')
await nextTick()
updatePayRecordFormModel.value.ruleAmount = ruleAmount
} else if (prop == 'exchangeRate' && value && updatePayRecordFormModel.value.hkdAmount) {
// 计算保单币种金额
const exchangeRate = value
const hkdAmount = updatePayRecordFormModel.value.hkdAmount
const ruleAmount = calculateAmount(hkdAmount, exchangeRate, 2, 'Divided')
await nextTick()
updatePayRecordFormModel.value.ruleAmount = ruleAmount
}
}
}
const handleConfirmAddPayRecord = async () => { const handleConfirmAddPayRecord = async () => {
if (editStatus.value === 'add') { if (editStatus.value === 'add') {
const handleConfirmAddPayRecordparams = addPayRecordFormRef.value.getFormData() const handleConfirmAddPayRecordparams = addPayRecordFormRef.value.getFormData()
...@@ -434,16 +1565,44 @@ const handleConfirmAddPayRecord = async () => { ...@@ -434,16 +1565,44 @@ const handleConfirmAddPayRecord = async () => {
} }
await updatePayRecord(params) await updatePayRecord(params)
ElMessage.success('更新出账记录成功') ElMessage.success('更新出账记录成功')
addPayRecordDialogVisible.value = false updatePayRecordDialogVisible.value = false
addPayRecordFormRef.value.resetForm() updatePayRecordFormRef.value.resetForm()
editStatus.value = 'add';
loadPayRecordTableData(selectedRow.value.expectedFortuneBizId) loadPayRecordTableData(selectedRow.value.expectedFortuneBizId)
expectedFortuneListData() expectedFortuneListData()
} catch (error) { } catch (error) {
ElMessage.error(error.message) ElMessage.error(error.message)
} }
} }
}
const handleConfirmUpdatePayRecord = async () => {
if (selectedRow.value.type == '1') {
try {
const formData = await updatePayRecordFormRef.value.validate()
console.log('====================================')
console.log('formData', formData)
console.log('====================================')
if (!formData) {
return
}
const params = {
...formData,
expectedFortuneBizId: selectedRow.value.expectedFortuneBizId
}
await updatePayRecord(params)
ElMessage.success('更新出账记录成功')
updatePayRecordDialogVisible.value = false
updatePayRecordFormRef.value.resetForm()
loadPayRecordTableData(selectedRow.value.expectedFortuneBizId)
expectedFortuneListData()
loadTableData()
} catch (error) {
if (error.message && error.message.includes('Validation')) {
ElMessage.error('必填项不能为空')
}
ElMessage.error('更新失败')
}
}
} }
// 弹窗表单重置 // 弹窗表单重置
const resetAddPayRecordForm = () => { const resetAddPayRecordForm = () => {
...@@ -452,7 +1611,6 @@ const resetAddPayRecordForm = () => { ...@@ -452,7 +1611,6 @@ const resetAddPayRecordForm = () => {
addPayRecordDialogVisible.value = false addPayRecordDialogVisible.value = false
} }
const editStatus = ref('add') const editStatus = ref('add')
// 出账记录 // 出账记录
const payRecordDialogTableData = ref([]) const payRecordDialogTableData = ref([])
...@@ -461,14 +1619,29 @@ const handleSelect = async (e, row) => { ...@@ -461,14 +1619,29 @@ const handleSelect = async (e, row) => {
selectedRow.value = { ...row } selectedRow.value = { ...row }
if (e === 'payRecord') { if (e === 'payRecord') {
payRecordDialogTableVisible.value = true payRecordDialogTableVisible.value = true
loadPayRecordTableData(selectedRow.value.expectedFortuneBizId); loadPayRecordTableData(selectedRow.value.expectedFortuneBizId)
payRecordDialogTableColumns.value = [ payRecordDialogTableColumns.value = [
{ property: 'broker', label: '转介人', width: '100', }, { property: 'broker', label: '转介人', width: '100' },
{ property: 'fortuneName', label: '出账项目', width: '150' }, { property: 'fortuneName', label: '出账项目', width: '150' },
{ property: 'currentPaymentHkdAmount', label: '出账金额', width: '150',formatter:(row)=> formatCurrency(row.currentPaymentHkdAmount || 0)}, {
property: 'currentPaymentHkdAmount',
label: '出账金额',
width: '150',
formatter: row => formatCurrency(row.currentPaymentHkdAmount || 0)
},
{ property: 'currency', label: '出账币种', width: '150' }, { property: 'currency', label: '出账币种', width: '150' },
{ property: 'currentPaymentRatio', label: '出账比例', width: '150', formatter: (row) => `${row.currentPaymentRatio}%` }, {
{ property: 'fortuneUnpaidRatio', label: '待出账比例', width: '150', formatter: (row) => `${row.fortuneUnpaidRatio}%` }, property: 'currentPaymentRatio',
label: '出账比例',
width: '150',
formatter: row => `${row.currentPaymentRatio}%`
},
{
property: 'fortuneUnpaidRatio',
label: '待出账比例',
width: '150',
formatter: row => `${row.fortuneUnpaidRatio}%`
},
{ property: 'fortunePeriod', label: '佣金期数', width: '150' }, { property: 'fortunePeriod', label: '佣金期数', width: '150' },
{ property: 'fortuneTotalPeriod', label: '总期数', width: '150' }, { property: 'fortuneTotalPeriod', label: '总期数', width: '150' },
{ property: 'reconciliationOperator', label: '操作人', width: '150' }, { property: 'reconciliationOperator', label: '操作人', width: '150' },
...@@ -483,29 +1656,27 @@ const handleSelect = async (e, row) => { ...@@ -483,29 +1656,27 @@ const handleSelect = async (e, row) => {
label: '出账状态', label: '出账状态',
dictType: 'csf_expected_fortune_status', dictType: 'csf_expected_fortune_status',
defaultValue: selectedRow.value.status || '' defaultValue: selectedRow.value.status || ''
}, { },
{
type: 'textarea', type: 'textarea',
prop: 'statusDesc', prop: 'statusDesc',
label: '修改理由', label: '修改理由',
defaultValue: selectedRow.value.statusDesc || '' defaultValue: selectedRow.value.statusDesc || ''
}, }
] ]
} else if (e === 'updateData') { } else if (e === 'updateData') {
editStatus.value = 'edit' updatePayRecordDialogVisible.value = true
addPayRecordDialogVisible.value = true // 2. 使用 nextTick 等待 DOM 更新
// 2. 使用 nextTick 等待 DOM 更新 nextTick(() => {
nextTick(() => { // 3. 此时 addRecordRef.value 一定存在了
// 3. 此时 addRecordRef.value 一定存在了 if (updatePayRecordFormRef.value && selectedRow.value) {
if (addPayRecordFormRef.value && selectedRow.value) { updatePayRecordFormModel.value = { ...selectedRow.value }
addPayRecordFormModel.value = { ...selectedRow.value }; }
} })
});
console.log(addPayRecordFormModel.value) console.log(addPayRecordFormModel.value)
} }
} }
// 分页相关 // 分页相关
const currentPage = ref(1) const currentPage = ref(1)
const pageSize = ref(10) const pageSize = ref(10)
...@@ -540,11 +1711,7 @@ const handleExport = async () => { ...@@ -540,11 +1711,7 @@ const handleExport = async () => {
const response = await exportPayRecord(params) const response = await exportPayRecord(params)
// 文件名设置为应收款导出_yyyy-MM-dd hh:mm:ss.xlsx,不需要-,用字符串 // 文件名设置为应收款导出_yyyy-MM-dd hh:mm:ss.xlsx,不需要-,用字符串
const fileName = `应付款导出_${new Date().toLocaleString().replace(/\//g, '').replace(/:/g, '').replace(/\s/g, '')}.xlsx` const fileName = `应付款导出_${new Date().toLocaleString().replace(/\//g, '').replace(/:/g, '').replace(/\s/g, '')}.xlsx`
await safeDownload( await safeDownload(response, fileName, 'application/vnd.ms-excel;charset=utf-8')
response,
fileName,
'application/vnd.ms-excel;charset=utf-8'
)
} }
const handleReset = () => { const handleReset = () => {
...@@ -557,6 +1724,7 @@ const handleReset = () => { ...@@ -557,6 +1724,7 @@ const handleReset = () => {
} }
const handleQuery = async () => { const handleQuery = async () => {
currentPage.value = 1
loadTableData() loadTableData()
} }
...@@ -590,25 +1758,36 @@ const operationBtnList = ref([ ...@@ -590,25 +1758,36 @@ const operationBtnList = ref([
]) ])
// 分页事件 // 分页事件
const handleSizeChange = (val) => { const handleSizeChange = val => {
pageSize.value = val pageSize.value = val
loadTableData() const params = searchFormRef.value.getFormData()
loadTableData(params)
} }
const handleCurrentChange = (val) => { const handleCurrentChange = val => {
currentPage.value = val currentPage.value = val
loadTableData() const params = searchFormRef.value.getFormData()
loadTableData(params)
} }
// 加载表格数据 // 加载表格数据
const loadTableData = async () => { const loadTableData = async () => {
const searchParams = searchFormRef.value.getFormData() || {} const searchParams = (await searchFormRef.value.getFormData()) || {}
loading.value = true loading.value = true
if (searchParams.payoutDate.length > 0) {
searchParams.payoutDateStart = `${searchParams.payoutDate[0]}-01`
searchParams.payoutDateEnd = `${searchParams.payoutDate[1]}-01`
} else {
searchParams.payoutDateStart = ''
searchParams.payoutDateEnd = ''
}
try { try {
const params = { const params = {
...searchParams, ...searchParams,
payoutDateStart: searchParams.payoutDate?.[0] || undefined, // payoutDateStart: searchParams.payoutDate?.[0] || undefined,
payoutDateEnd: searchParams.payoutDate?.[1] || undefined, // payoutDateEnd: searchParams.payoutDate?.[1] || undefined,
payoutDate: undefined, payoutDate: undefined,
pageNo: currentPage.value, pageNo: currentPage.value,
pageSize: pageSize.value pageSize: pageSize.value
...@@ -627,7 +1806,6 @@ const loadTableData = async () => { ...@@ -627,7 +1806,6 @@ const loadTableData = async () => {
totalPolicyCount: response.data.statisticsVO.totalPolicyCount, totalPolicyCount: response.data.statisticsVO.totalPolicyCount,
totalPremiumAmount: response.data.statisticsVO.totalPremiumAmount totalPremiumAmount: response.data.statisticsVO.totalPremiumAmount
} }
} catch (error) { } catch (error) {
console.error('加载数据失败:', error) console.error('加载数据失败:', error)
// ElMessage.error('加载数据失败') // ElMessage.error('加载数据失败')
...@@ -637,7 +1815,7 @@ const loadTableData = async () => { ...@@ -637,7 +1815,7 @@ const loadTableData = async () => {
} }
// 出账记录查询 // 出账记录查询
const loadPayRecordTableData = async (expectedFortuneBizId) => { const loadPayRecordTableData = async expectedFortuneBizId => {
loading.value = true loading.value = true
try { try {
const params = { const params = {
...@@ -645,7 +1823,6 @@ const loadPayRecordTableData = async (expectedFortuneBizId) => { ...@@ -645,7 +1823,6 @@ const loadPayRecordTableData = async (expectedFortuneBizId) => {
} }
const response = await payRecordList(params) const response = await payRecordList(params)
payRecordDialogTableData.value = response.data.records payRecordDialogTableData.value = response.data.records
} catch (error) { } catch (error) {
console.error('加载数据失败:', error) console.error('加载数据失败:', error)
// ElMessage.error('加载数据失败') // ElMessage.error('加载数据失败')
...@@ -735,23 +1912,22 @@ const expectedFortuneListData = async () => { ...@@ -735,23 +1912,22 @@ const expectedFortuneListData = async () => {
} }
} }
const viewDetail = (row) => { const viewDetail = row => {
selectedDetailRecordRow.value = row selectedDetailRecordRow.value = row
detailDialogVisible.value = true detailDialogVisible.value = true
expectedFortuneListData() expectedFortuneListData()
} }
// 分页事件 // 分页事件
const handleSizeChangeDetailRecord = (val) => { const handleSizeChangeDetailRecord = val => {
detailPageInfo.value.pageSize = val detailPageInfo.value.pageSize = val
expectedFortuneListData() expectedFortuneListData()
} }
// 分页事件 // 分页事件
const handleCurrentChangeDetailRecord = (val) => { const handleCurrentChangeDetailRecord = val => {
detailPageInfo.value.currentPage = val detailPageInfo.value.currentPage = val
expectedFortuneListData() expectedFortuneListData()
} }
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
...@@ -768,5 +1944,8 @@ const handleCurrentChangeDetailRecord = (val) => { ...@@ -768,5 +1944,8 @@ const handleCurrentChangeDetailRecord = (val) => {
border-top: 1px solid #ebeef5; border-top: 1px solid #ebeef5;
text-align: right; text-align: right;
} }
/* 使用 :deep 穿透 scoped 样式,保证对 el-table__row 生效 */
</style> :deep(.estimated-row) {
\ No newline at end of file background-color: #f0f9ff; /* 浅蓝色,可根据需要修改 */
}
</style>
<template> <template>
<div class='app-container'> <div class="app-container">
<CommonPage :operationBtnList='operationBtnList' :visibleDefaultButtons='visibleDefaultButtons' <CommonPage
:showSearchForm='true' :show-pagination='true' :total='pageTotal' :current-page='currentPage' :operationBtnList="operationBtnList"
:page-size='pageSize' @size-change='handleSizeChange' @current-change='handleCurrentChange'> :visibleDefaultButtons="visibleDefaultButtons"
:showSearchForm="true"
:show-pagination="true"
:total="pageTotal"
:current-page="currentPage"
:page-size="pageSize"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
>
<!-- 搜索区域 --> <!-- 搜索区域 -->
<template #searchForm> <template #searchForm>
<SearchForm ref="searchFormRef" :config="searchConfig" /> <SearchForm ref="searchFormRef" :config="searchConfig" />
</template> </template>
<!-- 列表区域 --> <!-- 列表区域 -->
<template #table> <template #table>
<el-table :data="tableData" v-loading="loading" ref="tableRef" <el-table
row-key="salarySplitNo" :reserve-selection="true" :border="true"> :data="tableData"
<el-table-column v-for="(column, index) in tableColumns" :key="index" :fixed="column.fixed" v-loading="loading"
:prop="column.prop" :label="column.label" :width="column.width" :sortable="column.sortable" ref="tableRef"
:formatter="column.formatter" /> row-key="salarySplitNo"
:reserve-selection="true"
:border="true"
>
<el-table-column
v-for="(column, index) in tableColumns"
:key="index"
:fixed="column.fixed"
:prop="column.prop"
:label="column.label"
:width="column.width"
:sortable="column.sortable"
:formatter="column.formatter"
/>
</el-table> </el-table>
</template> </template>
</CommonPage> </CommonPage>
</div> </div>
</template> </template>
<script setup> <script setup>
import { ref, reactive } from 'vue' import { ref, reactive } from 'vue'
import CommonPage from '@/components/commonPage' import CommonPage from '@/components/commonPage'
import {salarySummary,exportPayRoll} from '@/api/financial/commission' import { salarySummary, exportPayRoll } from '@/api/financial/commission'
import { ElMessageBox, ElMessage } from 'element-plus' import { ElMessageBox, ElMessage } from 'element-plus'
import SearchForm from '@/components/SearchForm/SearchForm.vue' import SearchForm from '@/components/SearchForm/SearchForm.vue'
import { formatNumberToMaxDigits } from '@/utils/number'
const searchFormRef = ref(null) const searchFormRef = ref(null)
const searchParams = ref({}) const searchParams = ref({})
const searchConfig = ref([ const searchConfig = ref([
...@@ -37,13 +57,13 @@ const searchConfig = ref([ ...@@ -37,13 +57,13 @@ const searchConfig = ref([
label: '转介人', label: '转介人',
api: '/insurance/base/api/userSaleExpand/page', api: '/insurance/base/api/userSaleExpand/page',
keywordField: 'realName', keywordField: 'realName',
requestParams: { pageNo: 1, pageSize: 20 }, requestParams: { pageNo: 1, pageSize: 9999 },
placeholder: '输入转介人名称搜索', placeholder: '输入转介人名称搜索',
debounceWait: 500, // 自定义防抖时间 debounceWait: 500, // 自定义防抖时间
valueKey: 'clientUserBizId', valueKey: 'clientUserBizId',
labelKey: 'realName', labelKey: 'realName',
multiple: true, multiple: true,
transform: (res) => { transform: res => {
return res?.data.records || [] return res?.data.records || []
} }
}, },
...@@ -57,25 +77,36 @@ const searchConfig = ref([ ...@@ -57,25 +77,36 @@ const searchConfig = ref([
{ {
type: 'input', type: 'input',
prop: 'billOrg', prop: 'billOrg',
label: '出账机构', label: '出账机构'
},
{
type: 'input',
prop: 'businessNo',
label: '业务编号'
} }
]) ])
const tableColumns = ref([ const tableColumns = ref([
{ prop: 'salarySplitNo', label: '发放编号', sortable: true, width: '150',fixed:'left'}, { prop: 'salarySplitNo', label: '发放编号', sortable: true, width: '150', fixed: 'left' },
{ prop: 'brokerName', label: '转介人', sortable: true, width: '150',fixed:'left'}, { prop: 'businessNo', label: '业务编号', width: '150', fixed: 'left' },
{ prop: 'internalNumber', label: '内部编号', sortable: true, width: '150'}, { prop: 'brokerName', label: '转介人', sortable: true, width: '150', fixed: 'left' },
{ prop: 'team', label: '所属团队', sortable: true, width: '150'}, { prop: 'internalNumber', label: '内部编号', sortable: true, width: '150' },
{ prop: 'fromAmount', label: '原币种金额', sortable: true, width: '150'}, { prop: 'team', label: '所属团队', sortable: true, width: '150' },
{ prop: 'currency', label: '原币种', sortable: true, width: '150'}, { prop: 'fromAmount', label: '原币种金额', sortable: true, width: '150' },
{ prop: 'exchangeRate', label: '汇率(原币种->目标币种)', sortable: true, width: '150'}, { prop: 'currency', label: '原币种', sortable: true, width: '150' },
{ prop: 'toAmount', label: '目标金额', sortable: true, width: '150'}, {
{ prop: 'toCurrency', label: '目标币种', sortable: true, width: '150'}, prop: 'exchangeRate',
{ prop: 'fortuneAccountMonth', label: '出账月(实)', sortable: true, width: '150'}, label: '汇率(原币种->目标币种)',
{ prop: 'billOrg', label: '出账机构', sortable: true, width: '150'}, sortable: true,
{ prop: 'hkdAmount', label: '本期总出账金额(原币种)', sortable: true, width: '150'}, width: '150',
{ prop: 'fortuneAccountBizId', label: '出账记录业务id', sortable: true, width: '150'}, formatter: row => formatNumberToMaxDigits(row.exchangeRate, 5, '-')
},
{ prop: 'toAmount', label: '目标金额', sortable: true, width: '150' },
{ prop: 'toCurrency', label: '目标币种', sortable: true, width: '150' },
{ prop: 'fortuneAccountMonth', label: '出账月(实)', sortable: true, width: '150' },
{ prop: 'billOrg', label: '出账机构', sortable: true, width: '150' },
{ prop: 'hkdAmount', label: '本期总出账金额(原币种)', sortable: true, width: '150' }
// { prop: 'fortuneAccountBizId', label: '出账记录业务id', sortable: true, width: '150'},
]) ])
// 添加表格引用 // 添加表格引用
const tableRef = ref() const tableRef = ref()
...@@ -89,27 +120,25 @@ const currentPage = ref(1) ...@@ -89,27 +120,25 @@ const currentPage = ref(1)
const pageSize = ref(10) const pageSize = ref(10)
const pageTotal = ref(0) const pageTotal = ref(0)
// 分页事件 // 分页事件
const handleSizeChange = (val) => { const handleSizeChange = val => {
pageSize.value = val pageSize.value = val
getList() getList()
} }
const handleCurrentChange = (val) => { const handleCurrentChange = val => {
currentPage.value = val currentPage.value = val
getList() getList()
} }
// 获取数据列表 // 获取数据列表
const getList = async (searchParams = {}) => { const getList = async (searchParams = {}) => {
loading.value = true loading.value = true
try { try {
const params = { const params = {
...searchParams, ...searchParams,
startMonth:searchParams.payoutMonth ? searchParams.payoutMonth[0] : '', startMonth: searchParams.payoutMonth ? searchParams.payoutMonth[0] : '',
endMonth:searchParams.payoutMonth ? searchParams.payoutMonth[1] :'', endMonth: searchParams.payoutMonth ? searchParams.payoutMonth[1] : '',
payoutMonth:undefined, payoutMonth: undefined,
pageNo: currentPage.value, pageNo: currentPage.value,
pageSize: pageSize.value pageSize: pageSize.value
} }
...@@ -129,6 +158,7 @@ getList() ...@@ -129,6 +158,7 @@ getList()
// 查询 // 查询
const handleQuery = () => { const handleQuery = () => {
currentPage.value = 1
const params = searchFormRef.value.getFormData() const params = searchFormRef.value.getFormData()
console.log('父组件发起查询:', params) console.log('父组件发起查询:', params)
getList(params) getList(params)
...@@ -147,23 +177,20 @@ const handleExport = async () => { ...@@ -147,23 +177,20 @@ const handleExport = async () => {
// 获取搜索参数 // 获取搜索参数
let params = searchFormRef.value?.getFormData() let params = searchFormRef.value?.getFormData()
params = { params = {
...params, ...params,
startMonth:params.payoutMonth ? params.payoutMonth[0] : '', startMonth: params.payoutMonth ? params.payoutMonth[0] : '',
endMonth:params.payoutMonth ? params.payoutMonth[1] :'', endMonth: params.payoutMonth ? params.payoutMonth[1] : '',
payoutMonth:undefined, payoutMonth: undefined
}
}
const response = await exportPayRoll(params) const response = await exportPayRoll(params)
if(response.data && response.data.url){ if (response.data && response.data.url) {
window.open(response.data.url) window.open(response.data.url)
}else{ } else {
ElMessage.error('导出失败') ElMessage.error('导出失败')
} }
} }
const visibleDefaultButtons = ref(['export', 'reset', 'query'])
const visibleDefaultButtons = ref(['export','reset', 'query'])
// 按钮配置 // 按钮配置
const operationBtnList = ref([ const operationBtnList = ref([
{ {
...@@ -176,13 +203,12 @@ const operationBtnList = ref([ ...@@ -176,13 +203,12 @@ const operationBtnList = ref([
direction: 'right', direction: 'right',
click: handleQuery click: handleQuery
}, },
{ {
key: 'export', key: 'export',
direction: 'right', direction: 'right',
click: handleExport click: handleExport
} }
]) ])
</script> </script>
<style scoped></style> <style scoped></style>
\ No newline at end of file
<template> <template>
<div> <div>
<CommonPage :operationBtnList="operationBtnList" :visibleDefaultButtons="visibleDefaultButtons" <CommonPage
:showSearchForm="true" :show-pagination="true" :total="pageTotal" :current-page="currentPage" :operationBtnList="operationBtnList"
:page-size="pageSize" @size-change="handleSizeChange" @current-change="handleCurrentChange"> :visibleDefaultButtons="visibleDefaultButtons"
<!-- 搜索区域 --> :showSearchForm="true"
<template #searchForm> :show-pagination="true"
<SearchForm ref="searchFormRef" :config="searchConfig" /> :total="pageTotal"
</template> :current-page="currentPage"
:page-size="pageSize"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
>
<!-- 搜索区域 -->
<template #searchForm>
<SearchForm ref="searchFormRef" :config="searchConfig" />
</template>
<!-- 列表区域 --> <!-- 列表区域 -->
<template #table> <template #table>
<div class="statistics-container" v-if="statisticsData.totalPolicyCount > 0"> <div class="statistics-container" v-if="statisticsData.totalPolicyCount > 0">
<el-row :gutter="20"> <el-row :gutter="20">
<el-col :xs="24" :sm="12" :md="4" class="text-center mb-4"> <el-col :xs="24" :sm="12" :md="4" class="text-center mb-4">
<el-statistic :value="statisticsData.totalAmount" :formatter="value=>formatCurrency(value)"> <el-statistic
<template #title> :value="statisticsData.totalAmount"
<div style="display: inline-flex; align-items: center"> :formatter="value => formatCurrency(value)"
应收款总金额 >
</div> <template #title>
</template> <div style="display: inline-flex; align-items: center">应收款总金额</div>
</el-statistic> </template>
</el-col> </el-statistic>
<el-col :xs="24" :sm="12" :md="4" class="text-center mb-4"> </el-col>
<el-statistic :value="statisticsData.totalPaidAmount" :formatter="value=>formatCurrency(value)"> <el-col :xs="24" :sm="12" :md="4" class="text-center mb-4">
<template #title> <el-statistic
<div style="display: inline-flex; align-items: center"> :value="statisticsData.totalPaidAmount"
已入账金额 :formatter="value => formatCurrency(value)"
</div> >
</template> <template #title>
</el-statistic> <div style="display: inline-flex; align-items: center">已入账金额</div>
</el-col> </template>
<el-col :xs="24" :sm="12" :md="4" class="text-center mb-4"> </el-statistic>
<el-statistic :value="statisticsData.pendingPaidAmount" :formatter="value=>formatCurrency(value)"> </el-col>
<template #title> <el-col :xs="24" :sm="12" :md="4" class="text-center mb-4">
<div style="display: inline-flex; align-items: center"> <el-statistic
待入账金额(估) :value="statisticsData.pendingPaidAmount"
</div> :formatter="value => formatCurrency(value)"
</template> >
</el-statistic> <template #title>
</el-col> <div style="display: inline-flex; align-items: center">待入账金额(估)</div>
<el-col :xs="24" :sm="12" :md="4" class="text-center mb-4"> </template>
<el-statistic :value="statisticsData.paidAmountRatio" :formatter="value=>formatCurrency(value,'',2)+'%'"> </el-statistic>
<template #title> </el-col>
<div style="display: inline-flex; align-items: center"> <el-col :xs="24" :sm="12" :md="4" class="text-center mb-4">
已入账比例 <el-statistic
</div> :value="statisticsData.paidAmountRatio"
</template> :formatter="value => formatCurrency(value, '', 2) + '%'"
</el-statistic> >
</el-col> <template #title>
<el-col :xs="24" :sm="12" :md="4" class="text-center mb-4"> <div style="display: inline-flex; align-items: center">已入账比例</div>
<el-statistic title="总保单数" :value="statisticsData.totalPolicyCount" /> </template>
</el-col> </el-statistic>
</el-row> </el-col>
</div> <el-col :xs="24" :sm="12" :md="4" class="text-center mb-4">
<!-- 应收款管理列表 --> <el-statistic title="总保单数" :value="statisticsData.totalPolicyCount" />
<el-table :data="tableData" height="400" border highlight-current-row style="width: 100%" </el-col>
v-loading="loading"> </el-row>
<el-table-column v-for="(column, index) in receivableReportTableColumns" :key="index" :fixed="column.fixed" </div>
:prop="column.prop" :label="column.label" :width="column.width" :sortable="column.sortable" <!-- 应收款管理列表 -->
:formatter="column.formatter" /> <el-table
<el-table-column fixed="right" label="操作" min-width="120"> :data="tableData"
<template #default="scope"> height="400"
<el-button link type="primary" size="small" @click="viewDetail(scope.row)"> border
查看明细 highlight-current-row
</el-button> style="width: 100%"
</template> v-loading="loading"
</el-table-column> >
</el-table> <el-table-column
v-for="(column, index) in receivableReportTableColumns"
:key="index"
:fixed="column.fixed"
: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="scope">
<el-button link type="primary" size="small" @click="viewDetail(scope.row)">
查看明细
</el-button>
</template> </template>
</CommonPage> </el-table-column>
</el-table>
<CommonDialog dialogTitle="应收明细" dialogWidth="80%" :openDialog="detailDialogVisible" :showAction="false" </template>
:showClose="true" @close="detailDialogVisible = false"> </CommonPage>
<div class="statistics-container" v-if="detailRecordStatistics.totalPolicyCount > 0"> <!-- 应收明细 -->
<el-row :gutter="20"> <CommonDialog
<el-col :xs="24" :sm="12" :md="4" class="text-center mb-4"> dialogTitle="应收明细"
<el-statistic :value="detailRecordStatistics.totalAmount" :formatter="value=>formatCurrency(value)"> dialogWidth="80%"
<template #title> :openDialog="detailDialogVisible"
<div style="display: inline-flex; align-items: center"> :showAction="false"
应入账总金额 :showClose="true"
</div> @close="detailDialogVisible = false"
</template> >
</el-statistic> <div class="statistics-container" v-if="detailRecordStatistics.totalPolicyCount > 0">
</el-col> <el-row :gutter="20">
<el-col :xs="24" :sm="12" :md="4" class="text-center mb-4"> <el-col :xs="24" :sm="12" :md="4" class="text-center mb-4">
<el-statistic :value="detailRecordStatistics.totalPaidAmount" :formatter="value=>formatCurrency(value)"> <el-statistic
<template #title> :value="detailRecordStatistics.totalAmount"
<div style="display: inline-flex; align-items: center"> :formatter="value => formatCurrency(value)"
已入账金额 >
</div> <template #title>
</template> <div style="display: inline-flex; align-items: center">HKD$应入账总金额</div>
</el-statistic> </template>
</el-col> </el-statistic>
<el-col :xs="24" :sm="12" :md="4" class="text-center mb-4"> </el-col>
<el-statistic :value="detailRecordStatistics.totalUnpaidAmount" :formatter="value=>formatCurrency(value)"> <el-col :xs="24" :sm="12" :md="4" class="text-center mb-4">
<template #title> <el-statistic
<div style="display: inline-flex; align-items: center"> :value="detailRecordStatistics.totalPaidAmount"
待入账金额 :formatter="value => formatCurrency(value)"
</div> >
</template> <template #title>
</el-statistic> <div style="display: inline-flex; align-items: center">HKD$已入账金额</div>
</el-col> </template>
<el-col :xs="24" :sm="12" :md="4" class="text-center mb-4"> </el-statistic>
<el-statistic :value="detailRecordStatistics.paidAmountRatio" :formatter="value=>formatCurrency(value,'',2)+'%'"> </el-col>
<template #title> <el-col :xs="24" :sm="12" :md="4" class="text-center mb-4">
<div style="display: inline-flex; align-items: center"> <el-statistic
已入账比例 :value="detailRecordStatistics.pendingPaidAmount"
</div> :formatter="value => formatCurrency(value)"
</template> >
</el-statistic> <template #title>
</el-col> <div style="display: inline-flex; align-items: center">HKD$待入账金额</div>
<el-col :xs="24" :sm="12" :md="4" class="text-center mb-4"> </template>
<el-statistic :value="detailRecordStatistics.totalPolicyCount" > </el-statistic>
<template #title> </el-col>
<div style="display: inline-flex; align-items: center"> <el-col :xs="24" :sm="12" :md="4" class="text-center mb-4">
总保单数 <el-statistic
</div> :value="detailRecordStatistics.paidAmountRatio"
</template> :formatter="value => formatCurrency(value, '', 2) + '%'"
</el-statistic> >
</el-col> <template #title>
<el-col :xs="24" :sm="12" :md="4" class="text-center mb-4"> <div style="display: inline-flex; align-items: center">已入账比例</div>
<el-statistic :value="detailRecordStatistics.totalPremium" :formatter="value=>formatCurrency(value)"> </template>
<template #title> </el-statistic>
<div style="display: inline-flex; align-items: center"> </el-col>
总保费 <!-- <el-col :xs="24" :sm="12" :md="4" class="text-center mb-4">
</div> <el-statistic :value="detailRecordStatistics.totalPolicyCount">
</template> <template #title>
</el-statistic> <div style="display: inline-flex; align-items: center">总保单数</div>
</el-col> </template>
</el-row> </el-statistic>
</div> </el-col> -->
<el-table :data="receivableReportTableData" border style="width: 100%;margin-bottom: 10px;min-height: 300px;"> <el-col :xs="24" :sm="12" :md="4" class="text-center mb-4">
<el-table-column v-for="item in receivableReportItemTableColumns" :key="item.property" :prop="item.prop" <el-statistic
:label="item.label" :width="item.width" :formatter="item.formatter" /> :value="detailRecordStatistics.totalPremium"
<el-table-column fixed="right" label="操作" min-width="120"> :formatter="value => formatCurrency(value)"
<template #default="{ row }"> >
<el-popover placement="right" :width="200" trigger="click"> <template #title>
<template #reference> <div style="display: inline-flex; align-items: center">HKD$总保费</div>
<el-icon> </template>
<MoreFilled /> </el-statistic>
</el-icon> </el-col>
</template> <el-col :xs="24" :sm="12" :md="4" class="text-center mb-4">
<el-menu @select="handleSelect($event, row)" popper-class="custom-menu"> <el-statistic
<el-menu-item :index="item.value" v-for="item in dropdownItems" :key="item.value"> :value="detailRecordStatistics.fromTotalPremium"
{{ item.label }} :formatter="value => formatCurrency(value)"
</el-menu-item> >
</el-menu> <template #title>
</el-popover> <div style="display: inline-flex; align-items: center">原币种保费</div>
</template> </template>
</el-table-column> </el-statistic>
</el-table> </el-col>
<el-pagination v-model:current-page="detailPageInfo.currentPage" v-model:page-size="detailPageInfo.pageSize" </el-row>
:page-sizes="[50, 100, 200, 300]" size="default" layout="total, sizes, prev, pager, next, jumper" </div>
:total="detailPageInfo.total" @size-change="handleSizeChangeDetailRecord" <el-table
@current-change="handleCurrentChangeDetailRecord" /> :data="receivableReportTableData"
</CommonDialog> border
<CommonDialog dialogTitle="入账记录" dialogWidth="80%" :openDialog="entryRecordDialogTableVisible" style="width: 100%; margin-bottom: 10px; min-height: 300px"
:showAction="false" :showClose="true" @close="entryRecordDialogTableVisible = false"> :row-class-name="getRowClassName"
<el-table :data="entryRecordDialogTableData" border style="width: 100%"> >
<el-table-column v-for="item in entryRecordDialogTableColumns" :key="item.property" <el-table-column
:prop="item.property" :label="item.label" :width="item.width" :formatter="item.formatter" /> v-for="item in receivableReportItemTableColumns"
<el-table-column fixed="right" label="操作" min-width="120"> :key="item.property"
<template #default="scope"> :prop="item.prop"
<el-button link type="primary" size="small" @click="handleClick(scope.row)"> :label="item.label"
查看比对记录 :width="item.width"
</el-button> :formatter="item.formatter"
</template> />
</el-table-column>
</el-table> <el-table-column fixed="right" label="操作" min-width="120">
</CommonDialog> <template #default="{ row }">
<el-popover placement="right" :width="200" trigger="click" v-if="row.type == '1'">
<CommonDialog dialogTitle="操作记录" dialogWidth="80%" :openDialog="actionRecordsDialogVisible" :showAction="false" <template #reference>
:showClose="true" @close="actionRecordsDialogVisible = false"> <el-icon>
<el-table :data="actionRecordsDialogTableData" border style="width: 100%"> <MoreFilled />
<el-table-column v-for="item in actionRecordsDialogTableColumns" :key="item.property" </el-icon>
:prop="item.property" :label="item.label" :width="item.width"/> </template>
</el-table> <el-menu @select="handleSelect($event, row)" popper-class="custom-menu">
</CommonDialog> <!-- <el-menu-item :index="item.value" v-for="item in dropdownItems" :key="item.value">
{{ item.label }}
<CommonDialog dialogTitle="设置入账状态" dialogWidth="80%" :openDialog="setStatusDialogTableVisible" </el-menu-item> -->
@close="setStatusDialogTableVisible = false" @confirm="handleConfirmSetStatus"> <el-menu-item
<SearchForm ref="setCommissionRecordStatusFormRef" :config="setCommissionRecordStatusFormConfig" /> :index="item.value"
</CommonDialog> v-for="item in getOperateItems(row)"
:key="item.value"
<!-- 新增应收款管理 --> >{{ item.label }}</el-menu-item
<CommonDialog :dialogTitle="editStatus === 'add' ? '新增应收款' : '更新应收款'" dialogWidth="80%" >
:openDialog="addReceivablesDialogVisible" </el-menu>
@close="addReceivablesDialogVisible = false; resetAddReceivablesForm()" </el-popover>
@confirm="handleConfirmAddReceivables"> </template>
<SearchForm ref="addRecordRef" :config="addReceivablesFormConfig" v-model="addReceivablesFormModel" /> </el-table-column>
</CommonDialog> </el-table>
</div> <el-pagination
v-model:current-page="detailPageInfo.currentPage"
v-model:page-size="detailPageInfo.pageSize"
:page-sizes="[50, 100, 200, 300]"
size="default"
layout="total, sizes, prev, pager, next, jumper"
:total="detailPageInfo.total"
@size-change="handleSizeChangeDetailRecord"
@current-change="handleCurrentChangeDetailRecord"
/>
</CommonDialog>
<CommonDialog
dialogTitle="入账记录"
dialogWidth="80%"
:openDialog="entryRecordDialogTableVisible"
:showAction="false"
:showClose="true"
@close="entryRecordDialogTableVisible = false"
>
<el-table :data="entryRecordDialogTableData" border style="width: 100%">
<el-table-column
v-for="item in entryRecordDialogTableColumns"
:key="item.property"
:prop="item.property"
:label="item.label"
:width="item.width"
:formatter="item.formatter"
/>
<el-table-column fixed="right" label="操作" min-width="120">
<template #default="scope">
<el-button link type="primary" size="small" @click="handleClick(scope.row)">
查看比对记录
</el-button>
</template>
</el-table-column>
</el-table>
</CommonDialog>
<CommonDialog
dialogTitle="操作记录"
dialogWidth="80%"
:openDialog="actionRecordsDialogVisible"
:showAction="false"
:showClose="true"
@close="actionRecordsDialogVisible = false"
>
<el-table :data="actionRecordsDialogTableData" border style="width: 100%">
<el-table-column
v-for="item in actionRecordsDialogTableColumns"
:key="item.property"
:prop="item.property"
:label="item.label"
:width="item.width"
/>
</el-table>
</CommonDialog>
<CommonDialog
dialogTitle="设置入账状态"
dialogWidth="80%"
:openDialog="setStatusDialogTableVisible"
@close="setStatusDialogTableVisible = false"
@confirm="handleConfirmSetStatus"
>
<SearchForm
ref="setCommissionRecordStatusFormRef"
:config="setCommissionRecordStatusFormConfig"
/>
</CommonDialog>
<!-- 新增应收款管理 -->
<CommonDialog
:dialogTitle="editStatus === 'add' ? '新增应收款' : '更新应收款'"
dialogWidth="80%"
:openDialog="addReceivablesDialogVisible"
@close="((addReceivablesDialogVisible = false), resetAddReceivablesForm())"
@confirm="handleConfirmAddReceivables"
>
<SearchForm
ref="addRecordRef"
:config="addReceivablesFormConfig"
v-model="addReceivablesFormModel"
@inputChange="(prop, value, item) => handleInputChange('addReceivables', prop, value, item)"
/>
</CommonDialog>
</div>
</template> </template>
<script setup name="Receivables"> <script setup name="Receivables">
import { validateEnglish2 } from '@/utils/validate'
import CommonPage from '@/components/commonPage' import CommonPage from '@/components/commonPage'
import CommonDialog from '@/components/commonDialog' import CommonDialog from '@/components/commonDialog'
import { ref, reactive, onMounted, computed,nextTick } from 'vue' import { ref, reactive, onMounted, computed, nextTick } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus' import { ElMessage, ElMessageBox } from 'element-plus'
import { MoreFilled } from '@element-plus/icons-vue' import { MoreFilled } from '@element-plus/icons-vue'
import { import {
receivedFortuneList, updateCommissionExpected, commissionEntryEditRecords, newQueryCommissionExpectedByPage,
exportReceivedFortune, commissionExpectedRecord, addReceivedFortune, receivableReport,CommissionExpectedChangeStatus receivedFortuneList,
updateCommissionExpected,
commissionEntryEditRecords,
exportReceivedFortune,
commissionExpectedRecord,
addReceivedFortune,
receivableReport,
CommissionExpectedChangeStatus,
commissionExchangeRateApi
} from '@/api/financial/commission' } from '@/api/financial/commission'
import { numberWithCommas } from '@/utils/index' import { numberWithCommas } from '@/utils/index'
import SearchForm from '@/components/SearchForm/SearchForm.vue' import SearchForm from '@/components/SearchForm/SearchForm.vue'
...@@ -213,277 +329,305 @@ import { formatCurrency } from '@/utils/number' ...@@ -213,277 +329,305 @@ import { formatCurrency } from '@/utils/number'
const userStore = useUserStore() const userStore = useUserStore()
// 应收单类型 // 应收单类型
const commissionBizTypeOptions = [ const commissionBizTypeOptions = [
{ value: 'R', label: '关联保单应收单' }, { value: 'R', label: '关联保单应收单' },
{ value: 'U', label: '非关联保单应收单' } { value: 'U', label: '非关联保单应收单' }
]
//是否实收
const typeOptions = [
{ value: 1, label: '预计' },
{ value: 2, label: '实收' }
] ]
const editStatus = ref('add') const editStatus = ref('add')
// 新增应收款管理 // 新增应收款管理
const addReceivablesFormModel = ref({ const addReceivablesFormModel = ref({
commissionBizType: 'U', commissionBizType: 'U'
}) })
const addReceivablesDialogVisible = ref(false) const addReceivablesDialogVisible = ref(false)
const addRecordRef = ref(null) const addRecordRef = ref(null)
const addReceivablesFormConfig = [ const addReceivablesFormConfig = [
{ {
type: 'select', type: 'select',
prop: 'commissionBizType', prop: 'commissionBizType',
label: '应收单类型', label: '应收单类型',
placeholder: '应收单类型', placeholder: '应收单类型',
options: commissionBizTypeOptions options: commissionBizTypeOptions
}, },
{ {
type: 'input', type: 'input',
prop: 'policyNo', prop: 'policyNo',
label: '保单号', label: '保单号',
visible: (formData) => formData.commissionBizType == 'R' visible: formData => formData.commissionBizType == 'R'
}, { },
type: 'input', {
prop: 'commissionPeriod', type: 'input',
label: '佣金期数', prop: 'commissionPeriod',
inputType: 'decimal', label: '佣金期数',
visible: (formData) => formData.commissionBizType === 'R', inputType: 'decimal',
rules: [ visible: formData => formData.commissionBizType === 'R',
{ pattern: /^\d+$/, message: '只能输入正整数', trigger: 'blur' } rules: [{ pattern: /^\d+$/, message: '只能输入正整数', trigger: 'blur' }]
] },
}, {
{ type: 'input',
type: 'input', prop: 'totalPeriod',
prop: 'totalPeriod', label: '总期数',
label: '总期数', inputType: 'decimal',
inputType: 'decimal', visible: formData => formData.commissionBizType === 'R',
visible: (formData) => formData.commissionBizType === 'R', rules: [{ pattern: /^\d+$/, message: '只能输入正整数', trigger: 'blur' }]
rules: [ },
{ pattern: /^\d+$/, message: '只能输入正整数', trigger: 'blur' } {
] type: 'month',
}, { prop: 'commissionDate',
type: 'date', label: '入账月(估)',
prop: 'commissionDate', placeholder: '请选择'
label: '入账日(估)', },
placeholder: '请选择' // {
}, // type: 'date',
// { // prop: 'actualCommissionDate',
// type: 'date', // label: '入账日(实)',
// prop: 'actualCommissionDate', // placeholder: '请选择',
// label: '入账日(实)', // maxDate: 'today'
// placeholder: '请选择', // },
// maxDate: 'today' {
// }, type: 'input',
{ prop: 'amount',
type: 'input', label: '入账金额',
prop: 'amount', inputType: 'decimal',
label: '入账金额', decimalDigits: 4,
inputType: 'decimal', visible: formData => formData.commissionBizType === 'U',
decimalDigits: 4, rules: [
visible: (formData) => formData.commissionBizType === 'U', { required: true, message: '请输入金额', trigger: 'blur' },
rules: [ { pattern: /^\d+(\.\d{1,4})?$/, message: '最多四位小数', trigger: 'blur' }
{ required: true, message: '请输入金额', trigger: 'blur' }, ]
{ pattern: /^\d+(\.\d{1,4})?$/, message: '最多四位小数', trigger: 'blur' } },
] {
}, { type: 'input',
type: 'input', prop: 'commissionRatio',
prop: 'commissionRatio', label: '入账比例(%)',
label: '入账比例(%)', visible: formData => formData.commissionBizType === 'R',
visible: (formData) => formData.commissionBizType === 'R', rules: [
rules: [ { required: true, message: '请输入入账比例', trigger: 'blur' },
{ required: true, message: '请输入入账比例', trigger: 'blur' }, { pattern: /^-?\d+(\.\d{1,4})?$/, message: '最多四位小数', trigger: 'blur' }
{ pattern: /^-?\d+(\.\d{1,4})?$/, message: '最多四位小数', trigger: 'blur' } ]
] },
}, { {
type: 'select', type: 'select',
prop: 'currency', prop: 'currency',
label: '入账币种', label: '入账币种',
dictType: 'bx_currency_type' dictType: 'bx_currency_type'
}, { },
type: 'select', {
prop: 'commissionType', type: 'select',
label: '入账项目', prop: 'commissionType',
dictType: 'csf_commission_type', label: '入账项目',
onChangeExtraFields: { dictType: 'csf_commission_type',
commissionName: 'itemLabel', onChangeExtraFields: {
}, commissionName: 'itemLabel'
}, { }
type: 'select', },
prop: 'reconciliationCompanyBizId', {
label: '对账公司', type: 'select',
api: '/insurance/base/api/insuranceReconciliationCompany/page', prop: 'reconciliationCompany',
keywordField: 'name', label: '对账公司',
requestParams: { pageNo: 1, pageSize: 20 }, api: '/insurance/base/api/insuranceReconciliationCompany/page',
placeholder: '输入对账公司名称搜索', keywordField: 'name',
debounceWait: 500, // 自定义防抖时间 requestParams: { pageNo: 1, pageSize: 20 },
valueKey: 'reconciliationCompanyBizId', placeholder: '输入对账公司名称搜索',
labelKey: 'name', debounceWait: 500, // 自定义防抖时间
onChangeExtraFields: { valueKey: 'reconciliationCompanyBizId',
reconciliationCompany: 'name',// 自动同步 raw.name 到 reconciliationCompany labelKey: 'name',
reconciliationCompanyCode: 'code' onChangeExtraFields: {
}, reconciliationCompany: 'name', // 自动同步 raw.name 到 reconciliationCompany
transform: (res) => { reconciliationCompanyCode: 'code'
console.log('对账公司', res)
return res.data.records || []
}
}, },
{ transform: res => {
type: 'input', console.log('对账公司', res)
prop: 'remark', return res.data.records || []
label: '备注',
} }
},
{
type: 'input',
prop: 'manualRemark',
label: '备注'
}
] ]
// 弹窗表单重置 // 弹窗表单重置
const resetAddReceivablesForm = () => { const resetAddReceivablesForm = () => {
addRecordRef.value.resetForm() addRecordRef.value.resetForm()
editStatus.value = 'add' editStatus.value = 'add'
} }
const handleConfirmAddReceivables = async () => { const handleConfirmAddReceivables = async () => {
if (editStatus.value === 'add') { if (editStatus.value === 'add') {
const p = addRecordRef.value.getFormData() const p = addRecordRef.value.getFormData()
try { try {
await addReceivedFortune({ await addReceivedFortune({
commissionExpectedAddDtoList: [p] commissionExpectedAddDtoList: [p]
}) })
ElMessage.success('新增应收款成功') ElMessage.success('新增应收款成功')
addReceivablesDialogVisible.value = false addReceivablesDialogVisible.value = false
resetAddReceivablesForm() resetAddReceivablesForm()
handleQuery() handleQuery()
} catch (error) { } catch (error) {
ElMessage.error(error.message) ElMessage.error(error.message)
}
} else {
try {
const res = await updateCommissionExpected({
commissionExpectedBizId: selectedRow.value.commissionExpectedBizId,
...addReceivablesFormModel.value
})
if (res.code === 200) {
ElMessage.success('应收款修改成功')
addReceivablesDialogVisible.value = false
resetAddReceivablesForm()
loadTableData() // 重新加载表格
} else {
ElMessage.error(res.msg || '应收款修改失败')
}
} catch (error) {
console.error('修改应收款失败:', error)
ElMessage.error('修改应收款失败')
}
} }
} else {
try {
const res = await updateCommissionExpected({
commissionExpectedBizId: selectedRow.value.commissionExpectedBizId,
...addReceivablesFormModel.value
})
if (res.code === 200) {
ElMessage.success('应收款修改成功')
addReceivablesDialogVisible.value = false
resetAddReceivablesForm()
receivedFortuneListData()
loadTableData() // 重新加载表格
} else {
ElMessage.error(res.msg || '应收款修改失败')
}
} catch (error) {
console.error('修改应收款失败:', error)
ElMessage.error('修改应收款失败')
}
}
} }
const searchFormRef = ref(null) const searchFormRef = ref(null)
const searchParams = ref({}) const searchParams = ref({})
const searchConfig = ref([ const searchConfig = ref([
{ {
type: 'input', type: 'input',
prop: 'policyNo', prop: 'policyNo',
label: '保单号' label: '保单号'
}, },
{ {
type: 'daterange', type: 'monthrange',
prop: 'entryDate', prop: 'entryDate',
label: '入账日(估)', label: '入账月(估)',
startPlaceholder: '开始时间', startPlaceholder: '开始时间',
endPlaceholder: '结束时间' endPlaceholder: '结束时间'
}, },
{ {
type: 'select', type: 'select',
prop: 'statusList', prop: 'statusList',
label: '入账状态', label: '入账状态',
multiple: true, multiple: true,
dictType: 'csf_expected_commission_status' dictType: 'csf_expected_commission_status'
}, },
{ {
type: 'input', type: 'input',
prop: 'commissionPeriod', prop: 'commissionPeriod',
label: '入账期数', label: '入账期数',
inputType: 'decimal', inputType: 'decimal',
rules: [ rules: [{ pattern: /^\d+$/, message: '只能输入正整数', trigger: 'blur' }]
{ pattern: /^\d+$/, message: '只能输入正整数', trigger: 'blur' } },
] {
type: 'select',
prop: 'fortuneName',
label: '入账项目',
dictType: 'csf_commission_type'
},
{
type: 'select',
prop: 'reconciliationCompanyBizIdList',
label: '对账公司',
api: '/insurance/base/api/insuranceReconciliationCompany/page',
keywordField: 'name',
requestParams: { pageNo: 1, pageSize: 20 },
placeholder: '输入对账公司名称搜索',
debounceWait: 500, // 自定义防抖时间
multiple: true,
valueKey: 'reconciliationCompanyBizId',
labelKey: 'name',
transform: res => {
console.log(res)
return res?.data.records || []
}
},
{
type: 'select',
prop: 'insurerCompanyBizIdList',
label: '保险公司',
api: '/insurance/base/api/insuranceCompany/page',
keywordField: 'queryContent',
requestParams: { pageNo: 1, pageSize: 20 },
placeholder: '输入保险公司名称搜索',
debounceWait: 500, // 自定义防抖时间
multiple: true,
valueKey: 'insuranceCompanyBizId',
labelKey: 'fullName',
transform: res => {
return res?.data.records || []
}
},
{
type: 'select',
prop: 'productLaunchBizId',
label: '产品计划',
api: '/product/api/relProjectProductLaunch/parameter/page',
keywordField: 'productName',
requestParams: {
tenantBizId: userStore.projectInfo.tenantBizId || '',
projectBizId: userStore.projectInfo.projectBizId || '',
fieldBizId: 'field_olk1qZe81qHHKXbw',
fieldValueBizId: 'field_value_uOfJH5ucA2YwJpbn',
pageNo: 1,
pageSize: 20
}, },
{ placeholder: '输入产品计划名称搜索',
type: 'select', debounceWait: 500, // 自定义防抖时间
prop: 'fortuneName', valueKey: 'productLaunchBizId',
label: '入账项目', labelKey: 'productName',
dictType: 'csf_commission_type' transform: res => {
}, return res?.data.records || []
{
type: 'select',
prop: 'reconciliationCompanyBizIdList',
label: '对账公司',
api: '/insurance/base/api/insuranceReconciliationCompany/page',
keywordField: 'name',
requestParams: { pageNo: 1, pageSize: 20 },
placeholder: '输入对账公司名称搜索',
debounceWait: 500, // 自定义防抖时间
multiple: true,
valueKey: 'reconciliationCompanyBizId',
labelKey: 'name',
transform: (res) => {
console.log(res)
return res?.data.records || []
}
},
{
type: 'select',
prop: 'insurerCompanyBizIdList',
label: '保险公司',
api: '/insurance/base/api/insuranceCompany/page',
keywordField: 'queryContent',
requestParams: { pageNo: 1, pageSize: 20 },
placeholder: '输入保险公司名称搜索',
debounceWait: 500, // 自定义防抖时间
multiple: true,
valueKey: 'insuranceCompanyBizId',
labelKey: 'fullName',
transform: (res) => {
return res?.data.records || []
}
}, {
type: 'select',
prop: 'productLaunchBizId',
label: '产品计划',
api: '/product/api/relProjectProductLaunch/parameter/page',
keywordField: 'productName',
requestParams: {
tenantBizId: userStore.projectInfo.tenantBizId || '',
projectBizId: userStore.projectInfo.projectBizId || '',
fieldBizId: 'field_olk1qZe81qHHKXbw',
fieldValueBizId: 'field_value_uOfJH5ucA2YwJpbn',
pageNo: 1,
pageSize: 20
},
placeholder: '输入产品计划名称搜索',
debounceWait: 500, // 自定义防抖时间
valueKey: 'productLaunchBizId',
labelKey: 'productName',
transform: (res) => {
return res?.data.records || []
}
}, {
type: 'select',
prop: 'commissionBizType',
label: '应收单类型',
options: commissionBizTypeOptions,
}, {
type: 'select',
prop: 'teamBizId',
label: '出单团队',
api: '/csf/api/team/page',
keywordField: 'teamName',
requestParams: { pageNo: 1, pageSize: 20 },
placeholder: '输入出单团队名称搜索',
debounceWait: 500, // 自定义防抖时间
valueKey: 'teamBizId',
labelKey: 'teamName',
transform: (res) => {
return res?.data.records || []
}
}, },
onChangeExtraFields: {
productName: 'productName'
}
},
{
type: 'select',
prop: 'commissionBizType',
label: '应收单类型',
options: commissionBizTypeOptions
},
{
type: 'select',
prop: 'teamBizId',
label: '出单团队',
api: '/csf/api/team/page',
keywordField: 'teamName',
requestParams: { pageNo: 1, pageSize: 20 },
placeholder: '输入出单团队名称搜索',
debounceWait: 500, // 自定义防抖时间
valueKey: 'teamBizId',
labelKey: 'teamName',
transform: res => {
return res?.data.records || []
}
},
{
type: 'input',
prop: 'policyHolder',
label: '投保人(中文/英文)'
},
{
type: 'input',
prop: 'insured',
label: '受保人(中文/英文)'
},
{
type: 'input',
prop: 'signer',
label: '签单员'
},
{
type: 'input',
prop: 'brokerName',
label: '转介人(主)'
}
]) ])
// 分页相关 // 分页相关
...@@ -492,19 +636,38 @@ const pageSize = ref(10) ...@@ -492,19 +636,38 @@ const pageSize = ref(10)
const pageTotal = ref(0) const pageTotal = ref(0)
const loading = ref(false) const loading = ref(false)
// 应收单类型通过value转成label // 应收单类型通过value转成label
const getCommissionBizTypeLabel = (value) => { const getCommissionBizTypeLabel = value => {
const item = commissionBizTypeOptions.find(item => item.value === value) const item = commissionBizTypeOptions.find(item => item.value === value)
return item?.label || '' return item?.label || ''
}
// 是否实收通过value转成label
const getTypeLabel = value => {
const item = typeOptions.find(item => item.value === value)
return item?.label || ''
} }
// 表格操作菜单 // 表格操作菜单
const dropdownItems = [ const dropdownItems = [
{ label: '入账记录', value: 'entryRecord' }, // { label: '入账记录', value: 'entryRecord' },
{ label: '设置状态', value: 'setStatus' }, { label: '设置状态', value: 'setStatus' },
{ label: '更新数据', value: 'updateData' }, { label: '更新数据', value: 'updateData' }
] ]
// 动态生成操作菜单项(根据行数据)
const getOperateItems = row => {
const items = []
// 条件:isPart == 1 时不显示分期出账(使用宽松相等,兼容字符串 '1')
if (row.type == 1) {
items.push({ label: '设置状态', value: 'setStatus' })
items.push({ label: '更新数据', value: 'updateData' })
}
// 始终显示的菜单项(保持原始顺序)
// items.push({ label: '设置出账年月(实)', value: 'settingBillYearMonth' })
return items
}
// 弹窗状态 // 弹窗状态
const entryRecordDialogTableVisible = ref(false) const entryRecordDialogTableVisible = ref(false)
...@@ -527,360 +690,758 @@ const tableData = ref([]) ...@@ -527,360 +690,758 @@ const tableData = ref([])
// 统计信息 // 统计信息
const statisticsData = ref({ const statisticsData = ref({
totalAmount: 0, totalAmount: 0,
totalPaidAmount: 0, totalPaidAmount: 0,
pendingPaidAmount: 0, pendingPaidAmount: 0,
paidAmountRatio: 0, paidAmountRatio: 0,
totalPolicyCount: 0 totalPolicyCount: 0
}) })
const handleInputChange = async (formType, prop, value, item) => {
if (formType == 'addReceivables') {
// await nextTick()
// let policyNo = addReceivablesFormModel.value.policyNo
// let commissionPeriod = addReceivablesFormModel.value.commissionPeriod
// if ((prop == 'policyNo' || prop == 'commissionPeriod') && commissionPeriod && policyNo) {
// //等待后端接口
// const res = await commissionExchangeRateApi({
// policyNo: policyNo,
// commissionPeriod: commissionPeriod
// })
// if (res.code == 200) {
// addReceivablesFormModel.value.exchangeRate = res.data
// } else {
// ElMessage.error('查询结算汇率失败')
// }
// }
}
}
// 按钮事件处理 // 按钮事件处理
const handleAdd = () => { const handleAdd = () => {
addReceivablesDialogVisible.value = true addReceivablesDialogVisible.value = true
if (addRecordRef.value) { if (addRecordRef.value) {
addRecordRef.value.resetForm() addRecordRef.value.resetForm()
} }
addReceivablesFormModel.value = {} addReceivablesFormModel.value = {}
} }
const handleImport = () => ElMessage.info('点击导入按钮') const handleImport = () => ElMessage.info('点击导入按钮')
const handleExport = async () => { const handleExport = async () => {
// 获取搜索参数 // 获取搜索参数
const params = searchFormRef.value?.getFormData() || {} const params = searchFormRef.value?.getFormData() || {}
const response = await exportReceivedFortune(params) const response = await exportReceivedFortune(params)
// 文件名设置为应收款导出_yyyy-MM-dd hh:mm:ss.xlsx,不需要-,用字符串 // 文件名设置为应收款导出_yyyy-MM-dd hh:mm:ss.xlsx,不需要-,用字符串
const fileName = `应收款导出_${new Date().toLocaleString().replace(/\//g, '').replace(/:/g, '').replace(/\s/g, '')}.xlsx` const fileName = `应收款导出_${new Date().toLocaleString().replace(/\//g, '').replace(/:/g, '').replace(/\s/g, '')}.xlsx`
await safeDownload( await safeDownload(response, fileName, 'application/vnd.ms-excel;charset=utf-8')
response,
fileName,
'application/vnd.ms-excel;charset=utf-8'
)
} }
const handleReset = () => { const handleReset = () => {
// 重置搜索表单 // 重置搜索表单
searchFormRef.value.resetForm() searchFormRef.value.resetForm()
searchParams.value = {} searchParams.value = {}
console.log('表单已重置') console.log('表单已重置')
loadTableData() loadTableData()
} }
const handleQuery = () => { const handleQuery = () => {
loadTableData() currentPage.value = 1
loadTableData()
} }
const visibleDefaultButtons = ref([ const visibleDefaultButtons = ref(['add', 'export', 'reset', 'query'])
'add', 'export', 'reset', 'query'
])
// 按钮配置 // 按钮配置
const operationBtnList = ref([ const operationBtnList = ref([
{ key: 'add', direction: 'left', click: handleAdd }, { key: 'add', direction: 'left', click: handleAdd },
// { key: 'import', direction: 'left', click: handleImport }, // { key: 'import', direction: 'left', click: handleImport },
{ key: 'export', direction: 'right', click: handleExport }, { key: 'export', direction: 'right', click: handleExport },
{ key: 'reset', direction: 'right', click: handleReset }, { key: 'reset', direction: 'right', click: handleReset },
{ key: 'query', direction: 'right', click: handleQuery } { key: 'query', direction: 'right', click: handleQuery }
]) ])
// 分页事件 // 分页事件
const handleSizeChange = (val) => { const handleSizeChange = val => {
pageSize.value = val pageSize.value = val
loadTableData() const params = searchFormRef.value.getFormData()
loadTableData(params)
} }
const handleCurrentChange = (val) => { const handleCurrentChange = val => {
currentPage.value = val currentPage.value = val
loadTableData() const params = searchFormRef.value.getFormData()
loadTableData(params)
} }
// 加载表格数据 // 加载表格数据
const loadTableData = async () => { const loadTableData = async () => {
const searchParams = searchFormRef.value.getFormData() || {} const searchParams = searchFormRef.value.getFormData() || {}
loading.value = true loading.value = true
try { try {
const params = { console.log('searchParams.entryDate', searchParams.entryDate)
...searchParams,
commissionDateStart: searchParams?.entryDate?.[0] || undefined, if (searchParams.entryDate.length > 0) {
commissionDateEnd: searchParams?.entryDate?.[1] || undefined, searchParams.commissionDateStart = `${searchParams.entryDate[0]}-01`
entryDate: undefined, searchParams.commissionDateEnd = `${searchParams.entryDate[1]}-01`
pageNo: currentPage.value, } else {
pageSize: pageSize.value searchParams.commissionDateStart = ''
} searchParams.commissionDateEnd = ''
const response = await receivableReport(params)
tableData.value = response.data.page.records || []
pageTotal.value = response.data.page.total || 0
pageSize.value = response.data.page.size || 50
// 统计信息
statisticsData.value = {
totalAmount: response.data.statisticsVO.totalAmount,
totalPaidAmount: response.data.statisticsVO.totalPaidAmount,
pendingPaidAmount: response.data.statisticsVO.pendingPaidAmount,
paidAmountRatio: response.data.statisticsVO.paidAmountRatio,
totalPolicyCount: response.data.statisticsVO.totalPolicyCount
}
} catch (error) {
console.error('加载数据失败:', error)
ElMessage.error('加载数据失败')
} finally {
loading.value = false
} }
const params = {
...searchParams,
// commissionDateStart: searchParams?.entryDate?.[0] + '-01' || undefined,
// commissionDateEnd: searchParams?.entryDate?.[1] + '-01' || undefined,
commissionDateStart: searchParams.commissionDateStart || undefined,
commissionDateEnd: searchParams.commissionDateEnd || undefined,
entryDate: undefined,
pageNo: currentPage.value,
pageSize: pageSize.value
}
const response = await receivableReport(params)
tableData.value = response.data.page.records || []
pageTotal.value = response.data.page.total || 0
pageSize.value = response.data.page.size || 50
// 统计信息
statisticsData.value = {
totalAmount: response.data.statisticsVO.totalAmount,
totalPaidAmount: response.data.statisticsVO.totalPaidAmount,
pendingPaidAmount: response.data.statisticsVO.pendingPaidAmount,
paidAmountRatio: response.data.statisticsVO.paidAmountRatio,
totalPolicyCount: response.data.statisticsVO.totalPolicyCount
}
} catch (error) {
console.error('加载数据失败:', error)
ElMessage.error('加载数据失败')
} finally {
loading.value = false
}
} }
// 入账记录查询 // 入账记录查询
const loadEntryRecordData = async (cbd) => { const loadEntryRecordData = async cbd => {
loading.value = true loading.value = true
try { try {
const params = { commissionExpectedBizId: cbd } const params = { commissionExpectedBizId: cbd }
const response = await commissionExpectedRecord(params) const response = await commissionExpectedRecord(params)
return response.data.records || [] return response.data.records || []
} catch (error) { } catch (error) {
console.error('加载入账记录失败:', error) console.error('加载入账记录失败:', error)
ElMessage.error('加载入账记录失败') ElMessage.error('加载入账记录失败')
return [] return []
} finally { } finally {
loading.value = false loading.value = false
} }
} }
// 入账操作记录查询 // 入账操作记录查询
const loadEntryEditRecordData = async (cbd) => { const loadEntryEditRecordData = async cbd => {
loading.value = true loading.value = true
try { try {
const params = { commissionBizId: cbd } const params = { commissionBizId: cbd }
const response = await commissionEntryEditRecords(params) const response = await commissionEntryEditRecords(params)
return response.data.records || [] return response.data.records || []
} catch (error) { } catch (error) {
console.error('加载操作记录失败:', error) console.error('加载操作记录失败:', error)
ElMessage.error('加载操作记录失败') ElMessage.error('加载操作记录失败')
return [] return []
} finally { } finally {
loading.value = false loading.value = false
} }
} }
// 操作菜单选择事件 // 操作菜单选择事件
const handleSelect = async (e, row) => { const handleSelect = async (e, row) => {
selectedRow.value = { ...row } selectedRow.value = { ...row }
if (e === 'entryRecord') { if (e === 'entryRecord') {
entryRecordDialogTableVisible.value = true entryRecordDialogTableVisible.value = true
entryRecordDialogTableColumns.value = [ entryRecordDialogTableColumns.value = [
{ property: 'reconciliationYearMonth', label: '检核年月', width: '100' }, { property: 'reconciliationYearMonth', label: '检核年月', width: '100' },
{ property: 'commissionPeriod', label: '佣金期数', width: '100' }, { property: 'commissionPeriod', label: '佣金期数', width: '100' },
{ property: 'totalPeriod', label: '总期数', width: '150' }, { property: 'totalPeriod', label: '总期数', width: '150' },
{ property: 'exchangeRate', label: '结算汇率(实)', width: '150' }, { property: 'exchangeRate', label: '结算汇率(实)', width: '150' },
{ property: 'currency', label: '入账币种', width: '150' }, { property: 'currency', label: '入账币种', width: '150' },
{ property: 'amount', label: '入账金额', width: '150',formatter:(row)=> formatCurrency(row.amount || 0) }, {
{ property: 'currentCommissionRatio', label: '入账比例', width: '150', formatter: (row) => `${row.currentCommissionRatio || ''}%` }, property: 'amount',
{ property: 'commissionDate', label: '入账日', width: '150' }, label: '入账金额',
{ property: 'commissionStatusName', label: '入账状态', width: '150' } width: '150',
] formatter: row => formatCurrency(row.amount || 0)
// 加载真实数据 },
const records = await loadEntryRecordData(row.commissionExpectedBizId) {
entryRecordDialogTableData.value = records.length ? records : [] property: 'currentCommissionRatio',
} else if (e === 'setStatus') { label: '入账比例',
console.log(selectedRow.value.status) width: '150',
setCommissionRecordStatusFormConfig.value = [ formatter: row => `${row.currentCommissionRatio || ''}%`
{ },
type: 'select', { property: 'commissionDate', label: '入账日', width: '150' },
prop: 'status', { property: 'commissionStatusName', label: '入账状态', width: '150' }
label: '入账状态', ]
dictType: 'csf_expected_commission_status', // 加载真实数据
defaultValue: selectedRow.value.status || '' const records = await loadEntryRecordData(row.commissionExpectedBizId)
}, { entryRecordDialogTableData.value = records.length ? records : []
type: 'textarea', } else if (e === 'setStatus') {
prop: 'statusDesc', console.log(selectedRow.value.status)
label: '修改理由', setCommissionRecordStatusFormConfig.value = [
defaultValue: selectedRow.value.statusDesc || '' {
}, type: 'select',
] prop: 'status',
setStatusDialogTableVisible.value = true label: '入账状态',
} else if (e === 'updateData') { dictType: 'csf_expected_commission_status',
editStatus.value = 'update' defaultValue: selectedRow.value.status || ''
addReceivablesDialogVisible.value = true },
// 2. 使用 nextTick 等待 DOM 更新 {
nextTick(() => { type: 'textarea',
// 3. 此时 addRecordRef.value 一定存在了 prop: 'statusDesc',
if (addRecordRef.value && selectedRow.value) { label: '修改理由',
addReceivablesFormModel.value = { ...selectedRow.value }; defaultValue: selectedRow.value.statusDesc || ''
console.log('赋值成功:', addReceivablesFormModel.value); }
} ]
}); setStatusDialogTableVisible.value = true
console.log('更新数据', selectedRow.value) } else if (e === 'updateData') {
} editStatus.value = 'update'
addReceivablesDialogVisible.value = true
// 2. 使用 nextTick 等待 DOM 更新
nextTick(() => {
// 3. 此时 addRecordRef.value 一定存在了
if (addRecordRef.value && selectedRow.value) {
addReceivablesFormModel.value = { ...selectedRow.value }
addReceivablesFormModel.value.commissionDate = selectedRow.value.commissionDateMonth
console.log('赋值成功:', addReceivablesFormModel.value)
}
})
console.log('更新数据', selectedRow.value)
}
} }
// 查看比对记录 // 查看比对记录
const handleClick = async (row) => { const handleClick = async row => {
actionRecordsDialogVisible.value = true actionRecordsDialogVisible.value = true
actionRecordsDialogTableColumns.value = [ actionRecordsDialogTableColumns.value = [
{ property: 'checkMonth', label: '检核年月', width: '100' }, { property: 'checkMonth', label: '检核年月', width: '100' },
{ property: 'compareStatus', label: '比对状态', width: '150' }, { property: 'compareStatus', label: '比对状态', width: '150' },
{ property: 'entryRatio', label: '入账比例', width: '150' }, { property: 'entryRatio', label: '入账比例', width: '150' },
{ property: 'shouldEntryRatio', label: '应入账比例', width: '150' }, { property: 'shouldEntryRatio', label: '应入账比例', width: '150' },
{ property: 'reconciliationCompany', label: '对账公司', width: '150' }, { property: 'reconciliationCompany', label: '对账公司', width: '150' },
{ property: 'createTime', label: '比对时间', width: '150' }, { property: 'createTime', label: '比对时间', width: '150' },
{ property: 'userName', label: '比对人', width: '150' } { property: 'userName', label: '比对人', width: '150' }
] ]
// 加载真实数据 // 加载真实数据
const records = await loadEntryEditRecordData(row.commissionBizId) const records = await loadEntryEditRecordData(row.commissionBizId)
actionRecordsDialogTableData.value = records.length ? records : [] actionRecordsDialogTableData.value = records.length ? records : []
} }
// 新增:设置状态确认事件 // 新增:设置状态确认事件
const handleConfirmSetStatus = () => { const handleConfirmSetStatus = () => {
const formData = setCommissionRecordStatusFormRef.value.getFormData() const formData = setCommissionRecordStatusFormRef.value.getFormData()
if (!formData.status) { if (!formData.status) {
return ElMessage.warning('请选择入账状态') return ElMessage.warning('请选择入账状态')
} }
if (!formData.statusDesc) { if (!formData.statusDesc) {
return ElMessage.warning('请输入修改理由') return ElMessage.warning('请输入修改理由')
} }
// 调用接口修改状态(示例) // 调用接口修改状态(示例)
ElMessageBox.confirm('确定要修改入账状态吗?', '提示', { ElMessageBox.confirm('确定要修改入账状态吗?', '提示', {
confirmButtonText: '确定', confirmButtonText: '确定',
cancelButtonText: '取消', cancelButtonText: '取消',
type: 'warning' type: 'warning'
}).then(async () => { })
try { .then(async () => {
const res = await CommissionExpectedChangeStatus({ try {
commissionExpectedBizId: selectedRow.value.commissionExpectedBizId, const res = await CommissionExpectedChangeStatus({
...formData commissionExpectedBizId: selectedRow.value.commissionExpectedBizId,
}) ...formData
if (res.code === 200) { })
ElMessage.success('状态修改成功') if (res.code === 200) {
setStatusDialogTableVisible.value = false ElMessage.success('状态修改成功')
loadTableData() // 重新加载表格 setStatusDialogTableVisible.value = false
} else { receivedFortuneListData()
ElMessage.error(res.msg || '状态修改失败') loadTableData() // 重新加载表格
} } else {
} catch (error) { ElMessage.error(res.msg || '状态修改失败')
console.error('修改状态失败:', error)
ElMessage.error('修改状态失败')
} }
}).catch(() => { } catch (error) {
ElMessage.info('已取消修改') console.error('修改状态失败:', error)
ElMessage.error('修改状态失败')
}
})
.catch(() => {
ElMessage.info('已取消修改')
}) })
} }
// 获取入账状态,字典值转化方法 // 获取入账状态,字典值转化方法
onMounted(async () => { onMounted(async () => {
try { try {
await loadDicts(['csf_expected_commission_status']) await loadDicts(['csf_expected_commission_status'])
loadTableData() loadTableData()
} catch (error) { } catch (error) {
console.error('字典加载失败', error) console.error('字典加载失败', error)
} finally { } finally {
loading.value = false loading.value = false
} }
}) })
// 格式化函数(每次渲染都会调用,所以能拿到最新字典) // 格式化函数(每次渲染都会调用,所以能拿到最新字典)
const formatStatus = (row,column) => { const formatStatus = (row, column) => {
return getDictLabel('csf_expected_commission_status', row.status) // 实时查缓存 return getDictLabel('csf_expected_commission_status', row.status) // 实时查缓存
} }
const detailDialogVisible = ref(false) const detailDialogVisible = ref(false)
const receivableReportTableData = ref([]) const receivableReportTableData = ref([])
const receivableReportTableColumns = ref([ const receivableReportTableColumns = ref([
{ prop: 'policyNo', label: '保单号', sortable: true, width: '150',fixed:'left', formatter: (row) => row.policyNo || '-' }, {
{ prop: 'reconciliationCompany', label: '对账公司', sortable: true, width: '150', formatter: (row) => row.reconciliationCompany || '-' }, prop: 'policyNo',
{ prop: 'commissionPeriod', label: '入账期数', sortable: true, width: '100', formatter: (row) => row.commissionPeriod || '-' }, label: '保单号',
{ prop: 'totalPeriod', label: '入账总期数', sortable: true, width: '100', formatter: (row) => row.totalPeriod || '-' }, sortable: true,
{ prop: 'commissionDate', label: '入账日(估)', sortable: true, width: '130', }, width: '150',
{ prop: 'commissionRatio', label: '产品对应来佣率', sortable: true, width: '120', formatter: (row) => (row.commissionRatio || 0) + '%' || '-' }, fixed: 'left',
{ prop: 'hkdAmount', label: '预估入账金额HKD', sortable: true, width: '120', formatter: (row) => formatCurrency(row.hkdAmount || 0) }, formatter: row => row.policyNo || '-'
{ prop: 'paidRatio', label: '已入账比例', sortable: true, width: '120', formatter: (row) => (row.paidRatio || 0) + '%' || '-' }, },
{ prop: 'paidAmount', label: '已入账金额HKD', sortable: true, width: '120', formatter: (row) => formatCurrency(row.paidAmount || 0) }, {
{ prop: 'unpaidRatio', label: '待入账比例', sortable: true, width: '120', formatter: (row) => (row.unpaidRatio || 0) + '%' || '-' }, prop: 'reconciliationCompany',
{ prop: 'unpaidAmount', label: '待入账金额HKD', sortable: true, width: '120', formatter: (row) => formatCurrency(row.unpaidAmount || 0) }, label: '对账公司',
{ prop: 'exchangeRate', label: '结算汇率(估)', sortable: true, width: '120'}, sortable: true,
{ prop: 'insuranceCompany', label: '保险公司', sortable: true, width: '120', formatter: (row) => row.insuranceCompany || '-' }, width: '150',
{ prop: 'productName', label: '产品计划', sortable: true, width: '120', formatter: (row) => row.productName || '-' }, formatter: row => row.reconciliationCompany || '-'
{ prop: 'premium', label: '期交保费', sortable: true, width: '120', formatter: (row) => formatCurrency(row.premium || 0) }, },
{ prop: 'policyCurrency', label: '保单币种', sortable: true, width: '120', formatter: (row) => row.policyCurrency || '-' }, {
prop: 'commissionPeriod',
label: '入账期数',
sortable: true,
width: '100',
formatter: row => row.commissionPeriod || '-'
},
{
prop: 'totalPeriod',
label: '入账总期数',
sortable: true,
width: '100',
formatter: row => row.totalPeriod || '-'
},
{ prop: 'commissionDate', label: '入账年月(估)', sortable: true, width: '130' },
{
prop: 'commissionRatio',
label: '保单来佣率',
sortable: true,
width: '120',
formatter: row => (row.commissionRatio || 0) + '%' || '-'
},
{
prop: 'paidRatio',
label: '已入账比例',
sortable: true,
width: '120',
formatter: row => (row.paidRatio || 0) + '%' || '-'
},
{
prop: 'unpaidRatio',
label: '待入账比例',
sortable: true,
width: '120',
formatter: row => (row.unpaidRatio || 0) + '%' || '-'
},
{
prop: 'paidAmount',
label: '已入账金额HKD',
sortable: true,
width: '120',
formatter: row => formatCurrency(row.paidAmount || 0)
},
{
prop: 'hkdAmount',
label: '待入账金额HKD(估)',
sortable: true,
width: '120',
formatter: row => formatCurrency(row.hkdAmount || 0)
},
{ prop: 'exchangeRate', label: '结算汇率(估)', sortable: true, width: '120' },
{
prop: 'insuranceCompany',
label: '保险公司',
sortable: true,
width: '120',
formatter: row => row.insuranceCompany || '-'
},
{
prop: 'productName',
label: '产品计划',
sortable: true,
width: '120',
formatter: row => row.productName || '-'
},
{
prop: 'premium',
label: '期交保费',
sortable: true,
width: '120',
formatter: row => formatCurrency(row.premium || 0)
},
{
prop: 'policyHolder',
label: '投保人',
sortable: true,
width: '120',
formatter: row => row.policyHolder || '-'
},
{
prop: 'policyHolderEn',
label: '投保人(英文)',
sortable: true,
width: '120',
formatter: row => row.policyHolderEn || '-'
},
{
prop: 'insured',
label: '受保人',
sortable: true,
width: '120',
formatter: row => row.insured || '-'
},
{
prop: 'insuredEn',
label: '受保人(英文)',
sortable: true,
width: '120',
formatter: row => row.insuredEn || '-'
},
{
prop: 'policyCurrency',
label: '保单币种',
sortable: true,
width: '120',
formatter: row => row.policyCurrency || '-'
}
// {
// prop: 'unpaidAmount',
// label: '待入账金额HKD',
// sortable: true,
// width: '120',
// formatter: row => formatCurrency(row.unpaidAmount || 0)
// },
// {
// prop: 'manualRemark',
// label: '备注',
// width: '150',
// formatter: row => row.manualRemark || '-'
// }
]) ])
// 应收明细 // 应收明细
const receivableReportItemTableColumns = ref([ const receivableReportItemTableColumns = ref([
{ prop: 'commissionBizType', label: '应收单类型', sortable: true, width: '150', formatter: (row) => getCommissionBizTypeLabel(row.commissionBizType) || '-' }, {
{ prop: 'receivableNo', label: '应收款编号', sortable: true, width: '150', formatter: (row) => row.receivableNo || '-' }, prop: 'type',
{ prop: 'policyNo', label: '保单号', sortable: true, width: '150', formatter: (row) => row.policyNo || '-' }, label: '是否实收',
{ prop: 'reconciliationCompany', label: '对账公司', sortable: true, width: '150', formatter: (row) => row.reconciliationCompany || '-' }, sortable: true,
{ prop: 'status', label: '入账状态', sortable: true, width: '120', formatter: (row) => formatStatus(row) || '-' }, width: '80',
{ prop: 'commissionPeriod', label: '入账期数', sortable: true, width: '120', formatter: (row) => row.commissionPeriod || '-' }, formatter: row => getTypeLabel(row.type) || '-'
{ prop: 'totalPeriod', label: '入账总期数', sortable: true, width: '120', formatter: (row) => row.totalPeriod || '-' }, },
{ prop: 'commissionName', label: '入账项目', sortable: true, width: '130', formatter: (row) => row.commissionName || '-' }, {
{ prop: 'commissionDate', label: '入账日(估)', sortable: true, width: '130', formatter: (row) => row.commissionDate || '-' }, prop: 'commissionBizType',
{ prop: 'commissionRatio', label: '产品对应来佣率', sortable: true, width: '120', formatter: (row) => (row.commissionRatio || 0) + '%' || '-' }, label: '应收单类型',
{ prop: 'expectedAmount', label: '预估入账金额', sortable: true, width: '120', formatter: (row) => formatCurrency(row.expectedAmount || 0) }, sortable: true,
{ prop: 'paidRatio', label: '实佣率', sortable: true, width: '120', formatter: (row) => (row.paidRatio || 0) + '%' || '-' }, width: '150',
{ prop: 'paidAmount', label: '已入账金额', sortable: true, width: '120', formatter: (row) => formatCurrency(row.paidAmount || 0) }, formatter: row => getCommissionBizTypeLabel(row.commissionBizType) || '-'
{ prop: 'pendingRatio', label: '实佣率缺口', sortable: true, width: '120', formatter: (row) => (row.pendingRatio || 0) + '%' || '-' }, },
{ prop: 'pendingAmount', label: '待入账金额', sortable: true, width: '120', formatter: (row) => formatCurrency(row.pendingAmount || 0) }, {
{ prop: 'defaultExchangeRate', label: '结算汇率(估)', sortable: true, width: '120'}, prop: 'no',
{ prop: 'insuranceCompany', label: '保险公司', sortable: true, width: '120', formatter: (row) => row.insuranceCompany || '-' }, label: '应收单编号',
{ prop: 'productName', label: '产品计划', sortable: true, width: '120', formatter: (row) => row.productName || '-' }, sortable: true,
{ prop: 'premium', label: '期交保费', sortable: true, width: '120', formatter: (row) => formatCurrency(row.premium || 0) }, width: '180',
{ prop: 'policyCurrency', label: '保单币种', sortable: true, width: '120', formatter: (row) => row.policyCurrency || '-' }, formatter: row => row.no || '-'
{ prop: 'currency', label: '入账币种', sortable: true, width: '120', formatter: (row) => row.currency || '-' }, },
{ prop: 'statusDesc', label: '入账状态修改理由', sortable: true, width: '120', formatter: (row) => row.statusDesc || '-' }, // {
{ prop: 'remark', label: '备注', sortable: true, width: '120', formatter: (row) => row.remark || '-' }, // prop: 'receivableNo',
// label: '应收款编号',
// sortable: true,
// width: '150',
// formatter: row => row.receivableNo || '-'
// },
{
prop: 'policyNo',
label: '保单号',
sortable: true,
width: '150',
formatter: row => row.policyNo || '-'
},
{
prop: 'reconciliationCompany',
label: '对账公司',
sortable: true,
width: '100',
formatter: row => row.reconciliationCompany || '-'
},
{
prop: 'status',
label: '入账状态',
sortable: true,
width: '120',
formatter: row => formatStatus(row) || '-'
},
{
prop: 'commissionPeriod',
label: '入账期数',
sortable: true,
width: '80',
formatter: row => row.commissionPeriod || '-'
},
{
prop: 'totalPeriod',
label: '入账总期数',
sortable: true,
width: '100',
formatter: row => row.totalPeriod || '-'
},
{
prop: 'commissionName',
label: '入账项目',
sortable: true,
width: '130',
formatter: row => row.commissionName || '-'
},
{
prop: 'commissionDateMonth',
label: '入账年月(估)',
sortable: true,
width: '120',
formatter: row => row.commissionDateMonth || '-'
},
{
prop: 'commissionDate',
label: '入账年月(实)',
sortable: true,
width: '120',
formatter: row => row.commissionDate || '-'
},
{
prop: 'totalRevenueRatio',
label: '累积达成率(%)',
sortable: true,
width: '130',
formatter: row => (row.totalRevenueRatio || 0) + '%' || '-'
},
{
prop: 'commissionRatio',
label: '保单对应来佣率',
sortable: true,
width: '120',
formatter: row => (row.commissionRatio || 0) + '%' || '-'
},
{
prop: 'realRate',
label: '实佣率',
sortable: true,
width: '120',
formatter: row => (row.realRate || 0) + '%' || '-'
},
{
prop: 'gapRate',
label: '达成率缺口',
sortable: true,
width: '120',
formatter: row => (row.gapRate || 0) + '%' || '-'
},
{
prop: 'realExchangeRate',
label: '结算汇率实',
sortable: true,
width: '120',
formatter: row => row.realExchangeRate || '-'
},
{
prop: 'realAmount',
label: '已入账金额',
sortable: true,
width: '120',
formatter: row => row.realAmount || '-'
},
{
prop: 'realReconciliationYearMonth',
label: '检核年月',
sortable: true,
width: '120',
formatter: row => row.realReconciliationYearMonth || '-'
},
{
prop: 'revenueRatio',
label: '本次入账比例',
sortable: true,
width: '120',
formatter: row => (row.revenueRatio ? row.revenueRatio + '%' : '-')
},
{
prop: 'pendingAmount',
label: '待入账金额',
sortable: true,
width: '120',
formatter: row => formatCurrency(row.pendingAmount || 0)
},
{
prop: 'expectedAmount',
label: '预计入账金额',
sortable: true,
width: '120',
formatter: row => formatCurrency(row.expectedAmount || 0)
},
{
prop: 'defaultExchangeRate',
label: '结算汇率估',
sortable: true,
width: '120',
formatter: row => row.defaultExchangeRate || '-'
},
{
prop: 'insuranceCompany',
label: '保险公司',
sortable: true,
width: '120',
formatter: row => row.insuranceCompany || '-'
},
{
prop: 'productName',
label: '产品计划',
sortable: true,
width: '120',
formatter: row => row.productName || '-'
},
{
prop: 'issueNumber',
label: '年期',
sortable: true,
width: '80',
formatter: row => row.issueNumber || '-'
},
{
prop: 'policyCurrency',
label: '保单币种',
sortable: true,
width: '120',
formatter: row => row.policyCurrency || '-'
},
{
prop: 'premium',
label: '期交保费',
sortable: true,
width: '120',
formatter: row => formatCurrency(row.premium || 0)
},
{
prop: 'manualRemark',
label: '人工备注',
sortable: true,
width: '120',
formatter: row => row.manualRemark || '-'
},
{
prop: 'remark',
label: '备注',
sortable: true,
width: '120',
formatter: row => row.remark || '-'
},
{
prop: 'realUpdaterName',
label: '操作人',
sortable: true,
width: '120',
formatter: row => row.realUpdaterName || '-'
},
{
prop: 'realUpdateTime',
label: '操作时间',
sortable: true,
width: '120',
formatter: row => row.realUpdateTime || '-'
}
// {
// prop: 'pendingRatio',
// label: '实佣率缺口',
// sortable: true,
// width: '120',
// formatter: row => (row.pendingRatio || 0) + '%' || '-'
// },
// { prop: 'defaultExchangeRate', label: '结算汇率(估)', sortable: true, width: '120' },
// {
// prop: 'currency',
// label: '入账币种',
// sortable: true,
// width: '120',
// formatter: row => row.currency || '-'
// },
// {
// prop: 'statusDesc',
// label: '入账状态修改理由',
// sortable: true,
// width: '120',
// formatter: row => row.statusDesc || '-'
// },
]) ])
const detailRecordStatistics = ref({}) const detailRecordStatistics = ref({})
const detailPageInfo = ref({ const detailPageInfo = ref({
pageNo: 1, pageNo: 1,
pageSize: 10, pageSize: 10,
currentPage: 1, currentPage: 1,
total: 0 total: 0
}) })
// 获取入账报告 // 获取应收明细
const receivedFortuneListData = async () => { const receivedFortuneListData = async () => {
loading.value = true loading.value = true
try { try {
const params = { const params = {
policyNo: selectedDetailRecordRow.value.policyNo, policyNo: selectedDetailRecordRow.value.policyNo,
commissionPeriod: selectedDetailRecordRow.value.commissionPeriod, commissionPeriod: selectedDetailRecordRow.value.commissionPeriod,
receivableNo: selectedDetailRecordRow.value.receivableNo, receivableNo: selectedDetailRecordRow.value.receivableNo,
pageNo: detailPageInfo.value.currentPage, pageNo: detailPageInfo.value.currentPage,
pageSize: detailPageInfo.value.pageSize pageSize: detailPageInfo.value.pageSize
}
const response = await receivedFortuneList(params)
receivableReportTableData.value = response.data.page.records || []
detailPageInfo.value.total = response.data.page.total || 0
detailPageInfo.value.pageSize = response.data.page.size || 50
// 统计信息
detailRecordStatistics.value = {
totalAmount: response.data.expectedStatisticsVO.totalAmount,
totalPaidAmount: response.data.expectedStatisticsVO.totalPaidAmount,
pendingPaidAmount: response.data.expectedStatisticsVO.pendingPaidAmount,
paidAmountRatio: response.data.expectedStatisticsVO.paidAmountRatio,
totalPolicyCount: response.data.expectedStatisticsVO.totalPolicyCount,
totalPremium: response.data.expectedStatisticsVO.totalPremium
}
} catch (error) {
console.error('加载数据失败:', error)
ElMessage.error('加载数据失败')
} finally {
loading.value = false
} }
const response = await newQueryCommissionExpectedByPage(params)
receivableReportTableData.value = response.data.page.records || []
detailPageInfo.value.total = response.data.page.total || 0
detailPageInfo.value.pageSize = response.data.page.size || 50
// 统计信息
// detailRecordStatistics.value = {
// totalAmount: response.data.expectedStatisticsVO.totalAmount,
// totalPaidAmount: response.data.expectedStatisticsVO.totalPaidAmount,
// pendingPaidAmount: response.data.expectedStatisticsVO.pendingPaidAmount,
// paidAmountRatio: response.data.expectedStatisticsVO.paidAmountRatio,
// totalPolicyCount: response.data.expectedStatisticsVO.totalPolicyCount,
// totalPremium: response.data.expectedStatisticsVO.totalPremium
// }
if (response.data.expectedStatisticsVO) {
detailRecordStatistics.value = response.data.expectedStatisticsVO
}
} catch (error) {
console.error('加载数据失败:', error)
ElMessage.error('加载数据失败')
} finally {
loading.value = false
}
} }
const selectedDetailRecordRow = ref({}) const selectedDetailRecordRow = ref({})
const viewDetail = (row) => { const viewDetail = row => {
selectedDetailRecordRow.value = row selectedDetailRecordRow.value = row
detailDialogVisible.value = true detailDialogVisible.value = true
receivedFortuneListData() receivedFortuneListData()
} }
// 分页事件 // 分页事件
const handleSizeChangeDetailRecord = (val) => { const handleSizeChangeDetailRecord = val => {
detailPageInfo.value.pageSize = val detailPageInfo.value.pageSize = val
receivedFortuneListData() receivedFortuneListData()
} }
// 分页事件 // 分页事件
const handleCurrentChangeDetailRecord = (val) => { const handleCurrentChangeDetailRecord = val => {
detailPageInfo.value.currentPage = val detailPageInfo.value.currentPage = val
receivedFortuneListData() receivedFortuneListData()
}
// 定义行类名方法
const getRowClassName = ({ row }) => {
// type == 1 表示“预计”,添加高亮类名
return row.type === 1 ? 'estimated-row' : ''
} }
</script> </script>
<style scoped lang="scss"></style> <style scoped lang="scss">
\ No newline at end of file /* 使用 :deep 穿透 scoped 样式,保证对 el-table__row 生效 */
:deep(.estimated-row) {
background-color: #f0f9ff; /* 浅蓝色,可根据需要修改 */
}
</style>
...@@ -369,13 +369,14 @@ ...@@ -369,13 +369,14 @@
<template #table> <template #table>
<el-table v-loading="tableLoading" :data="tableData" @row-click="handleExport"> <el-table v-loading="tableLoading" :data="tableData" @row-click="handleExport">
<!-- <el-table-column type="selection" width="55" align="center" /> --> <!-- <el-table-column type="selection" width="55" align="center" /> -->
<el-table-column label="姓名" align="center" prop="nameCn" width="200" /> <el-table-column label="姓名" align="center" prop="nameCn" width="200" fixed="left" />
<el-table-column label="姓名(英文)" align="center" prop="namePyEn" width="200" />
<el-table-column label="性别" align="center" prop="gender"> <el-table-column label="性别" align="center" prop="gender">
<template #default="scope"> <template #default="scope">
<dict-tag :options="fetchDictData('sys_gender')" :value="scope.row.gender" /> <dict-tag :options="fetchDictData('sys_gender')" :value="scope.row.gender" />
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="证件类型" align="center" prop="documentType"> <el-table-column label="证件类型" align="center" prop="documentType" width="150">
<template #default="scope"> <template #default="scope">
<dict-tag <dict-tag
:options="fetchDictData('csf_id_type')" :options="fetchDictData('csf_id_type')"
...@@ -384,8 +385,8 @@ ...@@ -384,8 +385,8 @@
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="证件号码" align="center" prop="idNumber" /> <el-table-column label="证件号码" align="center" prop="idNumber" width="200" />
<el-table-column label="电话号码" align="center" prop="mobile" /> <el-table-column label="电话号码" align="center" prop="mobile" width="200" />
<el-table-column label="出生日期" align="center" width="180" prop="birthday"> <el-table-column label="出生日期" align="center" width="180" prop="birthday">
<template #default="scope"> <template #default="scope">
<span>{{ scope.row.birthday ? formatToDate(scope.row.birthday) : '--' }}</span> <span>{{ scope.row.birthday ? formatToDate(scope.row.birthday) : '--' }}</span>
......
...@@ -12,7 +12,7 @@ ...@@ -12,7 +12,7 @@
<span class="iconfont icon-yanqiweiwancheng"></span> <span class="iconfont icon-yanqiweiwancheng"></span>
<span <span
>{{ parseTime(processInfo.createTime) }}{{ >{{ parseTime(processInfo.createTime) }}{{
processInfo.userBizId || '--' processInfo.creatorName || '--'
}}创建</span }}创建</span
> >
</div> </div>
...@@ -157,11 +157,13 @@ const route = useRoute() ...@@ -157,11 +157,13 @@ const route = useRoute()
const router = useRouter() const router = useRouter()
const activeName = ref() const activeName = ref()
const loading = ref(false) const loading = ref(false)
console.log('userStore', userStore)
const processInfo = ref({ const processInfo = ref({
fnaNo: '--', fnaNo: '--',
status: '未保存', status: '未保存',
createTime: proxy.parseTime(new Date()), createTime: proxy.parseTime(new Date()),
customerName: userStore.name creatorName: userStore.realName
}) // 流程详情信息 }) // 流程详情信息
const updateStatus = ref(false) const updateStatus = ref(false)
const dictTypeLists = ref([]) const dictTypeLists = ref([])
...@@ -321,7 +323,7 @@ const getDictsData = async () => { ...@@ -321,7 +323,7 @@ const getDictsData = async () => {
projectBizId: userStore.projectInfo.projectBizId, projectBizId: userStore.projectInfo.projectBizId,
tenantBizId: userStore.projectInfo.tenantBizId, tenantBizId: userStore.projectInfo.tenantBizId,
fieldBizId: 'field_olk1qZe81qHHKXbw', fieldBizId: 'field_olk1qZe81qHHKXbw',
fieldValueBizId: 'field_value_yXzTigvgUdRMFpoR', fieldValueBizId: 'field_value_yXzTigvgUdRMFpoR'
} }
const response6 = await secondAdditonalList(params6) const response6 = await secondAdditonalList(params6)
if (response6.code == 200) { if (response6.code == 200) {
...@@ -399,6 +401,9 @@ function getProcessInfo(fnaBizId, changeTab, currentTab) { ...@@ -399,6 +401,9 @@ function getProcessInfo(fnaBizId, changeTab, currentTab) {
getProcessDetail(fnaBizId).then(async res => { getProcessDetail(fnaBizId).then(async res => {
if (res.code == 200) { if (res.code == 200) {
processInfo.value = res.data processInfo.value = res.data
// if (!processInfo.value.creatorName) {
// processInfo.value.creatorName = userStore.realName
// }
tabsList.value.forEach(item => { tabsList.value.forEach(item => {
if (res.data[item.key] && item.status) { if (res.data[item.key] && item.status) {
item.status = '1' item.status = '1'
......
...@@ -61,6 +61,16 @@ ...@@ -61,6 +61,16 @@
</el-select> </el-select>
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :sm="12" :lg="6" :xs="24">
<el-form-item label="创建人" prop="creatorName">
<el-input
v-model="queryParams.creatorName"
placeholder="请输入创建人"
clearable
@keyup.enter="handleQuery"
/>
</el-form-item>
</el-col>
</el-row> </el-row>
</el-form> </el-form>
</template> </template>
...@@ -76,22 +86,26 @@ ...@@ -76,22 +86,26 @@
max-height="380px" max-height="380px"
> >
<el-table-column type="index" width="100" label="序号" /> <el-table-column type="index" width="100" label="序号" />
<el-table-column label="客户姓名" align="center" prop="customerName" width="180" > <el-table-column label="客户姓名" align="center" prop="customerName" width="180">
<template #default="scope"> <template #default="scope">
<el-button link size="default"> <el-button link size="default">
<span @click="copyToClipboard(scope.row.customerName,'客户姓名')">{{ scope.row.customerName }}</span> <span @click="copyToClipboard(scope.row.customerName, '客户姓名')">{{
scope.row.customerName
}}</span>
</el-button> </el-button>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="中文姓名" align="center" prop="customerNameCn" width="180" > <el-table-column label="中文姓名" align="center" prop="customerNameCn" width="180">
<template #default="scope"> <template #default="scope">
<el-button link size="default"> <el-button link size="default">
<span @click="copyToClipboard(scope.row.customerNameCn,'中文姓名')">{{ scope.row.customerNameCn }}</span> <span @click="copyToClipboard(scope.row.customerNameCn, '中文姓名')">{{
</el-button> scope.row.customerNameCn
</template> }}</span>
</el-table-column> </el-button>
</template>
</el-table-column>
<!-- <el-table-column label="状态" align="center" prop="status" width="100" :formatter="getDictLabel('csf_fna_status')"/> --> <!-- <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"> <el-table-column label="流程状态" sortable align="center" prop="status" width="200">
<template #default="scope"> <template #default="scope">
<span>{{ selectDictLabel(csf_fna_status, scope.row.status) }}</span> <span>{{ selectDictLabel(csf_fna_status, scope.row.status) }}</span>
</template> </template>
...@@ -101,13 +115,16 @@ ...@@ -101,13 +115,16 @@
align="center" align="center"
prop="fnaNo" prop="fnaNo"
width="240" width="240"
show-overflow-tooltip> show-overflow-tooltip
<template #default="scope"> >
<template #default="scope">
<el-button link size="default"> <el-button link size="default">
<span @click="copyToClipboard(scope.row.fnaNo,'流程编号')">{{ scope.row.fnaNo }}</span> <span @click="copyToClipboard(scope.row.fnaNo, '流程编号')">{{
scope.row.fnaNo
}}</span>
</el-button> </el-button>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column <el-table-column
label="预约编号" label="预约编号"
align="center" align="center"
...@@ -117,12 +134,14 @@ ...@@ -117,12 +134,14 @@
> >
<template #default="scope"> <template #default="scope">
<el-button link size="default"> <el-button link size="default">
<span @click="copyToClipboard(scope.row.appointmentNo,'预约编号')">{{ scope.row.appointmentNo }}</span> <span @click="copyToClipboard(scope.row.appointmentNo, '预约编号')">{{
scope.row.appointmentNo
}}</span>
</el-button> </el-button>
</template> </template>
</el-table-column> </el-table-column>
<!-- <el-table-column label="新单编号" align="center" prop="policyId" /> -->
<el-table-column label="保单号" align="center" prop="policyNo" width="180" /> <el-table-column label="保单号" align="center" prop="policyNo" width="180" />
<el-table-column label="创建人" align="center" prop="creatorName" width="180" />
<el-table-column label="创建时间" sortable align="center" prop="createTime" width="200"> <el-table-column label="创建时间" sortable align="center" prop="createTime" width="200">
<template #default="scope"> <template #default="scope">
...@@ -181,7 +200,7 @@ import { getFnaList, deleteFna, subProcess } from '@/api/sign/fna' ...@@ -181,7 +200,7 @@ import { getFnaList, deleteFna, subProcess } from '@/api/sign/fna'
import useUserStore from '@/store/modules/user' import useUserStore from '@/store/modules/user'
import { MoreFilled, InfoFilled } from '@element-plus/icons-vue' import { MoreFilled, InfoFilled } from '@element-plus/icons-vue'
import useDictStore from '@/store/modules/dict' import useDictStore from '@/store/modules/dict'
import copyToClipboard from '@/utils/copyToClipboard'; import copyToClipboard from '@/utils/copyToClipboard'
const dictStore = useDictStore() const dictStore = useDictStore()
const userStore = useUserStore() const userStore = useUserStore()
const router = useRouter() const router = useRouter()
...@@ -395,7 +414,6 @@ function handleUpdate(row) { ...@@ -395,7 +414,6 @@ function handleUpdate(row) {
} }
getList() getList()
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.bottomBtn { .bottomBtn {
......
...@@ -487,7 +487,7 @@ const processInfo = ref({ ...@@ -487,7 +487,7 @@ const processInfo = ref({
fnaNo: '暂无', fnaNo: '暂无',
status: '未完成', status: '未完成',
createTime: proxy.parseTime(new Date()), createTime: proxy.parseTime(new Date()),
customerName: userStore.name creatorName: userStore.realName
}) })
const editStatus = ref(null) //编辑状态 const editStatus = ref(null) //编辑状态
const execlDialog = ref(false) const execlDialog = ref(false)
......
<!-- 预约附件代码 -->
<template> <template>
<div class="uploadContainer"> <div class="uploadContainer">
<CardOne title="材料信息"> <CardOne title="材料信息">
<template #headerRight> <template #headerRight>
<div style="margin-top: 20px;"> <div style="margin-top: 20px">
<el-button <el-button type="primary" :loading="downloading" @click="handleBatchDownloadSelected">
type="primary"
:loading="downloading"
@click="handleBatchDownloadSelected"
>
{{ downloading ? '正在打包中...' : '下载材料包' }} {{ downloading ? '正在打包中...' : '下载材料包' }}
</el-button> </el-button>
<div v-if="downloading" style="margin-top: 10px;"> <div v-if="downloading" style="margin-top: 10px">
<el-progress <el-progress :percentage="progressPercentage" :format="progressFormat" />
:percentage="progressPercentage" <div style="font-size: 12px; color: #666; margin-top: 5px">
:format="progressFormat"
/>
<div style="font-size: 12px; color: #666; margin-top: 5px;">
已处理文件:{{ currentCount }} / {{ totalCount }} 已处理文件:{{ currentCount }} / {{ totalCount }}
</div> </div>
</div> </div>
...@@ -108,6 +102,7 @@ ...@@ -108,6 +102,7 @@
:headers="headers" :headers="headers"
multiple multiple
:limit="limit" :limit="limit"
:accept="acceptUploadType"
:show-file-list="false" :show-file-list="false"
:on-exceed="handleExceed" :on-exceed="handleExceed"
:on-success="uploadSuccess" :on-success="uploadSuccess"
...@@ -120,7 +115,7 @@ ...@@ -120,7 +115,7 @@
</div> </div>
</el-upload> </el-upload>
</div> </div>
<div class="tip">(支持Word,Excel,PDF,图片格式)</div> <div class="tip">(支持PDF,图片格式)</div>
</div> </div>
<div class="dialogRight"> <div class="dialogRight">
<div <div
...@@ -135,15 +130,70 @@ ...@@ -135,15 +130,70 @@
:key="file.fileBizId" :key="file.fileBizId"
class="uploaded-file-item" class="uploaded-file-item"
> >
<div class="fileName">{{ file.originalName }}</div> <div class="fileNameBox">{{ file.originalName }}</div>
<el-icon color="red" size="18" @click="removeUploadedFile(file, index)" <div>
<el-button type="primary" size="small" link @click="previewFile(file)">
查看
</el-button>
<el-button type="danger" size="small" link @click="removeUploadedFile(file, index)">
删除
</el-button>
</div>
<!-- <el-icon color="red" size="18" @click="removeUploadedFile(file, index)"
><Delete ><Delete
/></el-icon> /></el-icon> -->
</div> </div>
</el-scrollbar> </el-scrollbar>
</div> </div>
</div> </div>
</CommonDialog> </CommonDialog>
<!-- 文件预览弹窗 -->
<FilePreview ref="filePreviewRef" />
<!-- <el-dialog
v-model="previewDialogVisible"
:title="previewFileName"
width="90%"
:close-on-click-modal="false"
destroy-on-close
@close="closePreview"
>
<div class="preview-container">
<div v-if="previewFileType === 'image'" class="preview-image-wrapper">
<img :src="previewUrl" class="preview-image" alt="预览图片" />
</div>
<div v-else-if="previewFileType === 'pdf'" class="pdf-viewer">
<div class="pdf-toolbar">
<el-button-group>
<el-button size="small" @click="zoomOut">
<el-icon><ZoomOut /></el-icon> 缩小
</el-button>
<el-button size="small" @click="zoomIn">
<el-icon><ZoomIn /></el-icon> 放大
</el-button>
</el-button-group>
<span class="page-info">共 {{ pdfTotalPages }} 页</span>
</div>
<div
class="pdf-scroll-wrapper"
v-loading="pdfLoading"
element-loading-text="正在渲染页面..."
>
<div ref="pdfScrollContainer" class="pdf-scroll-container"></div>
</div>
</div>
<div v-else-if="previewFileType === 'unsupported'" class="preview-unsupported">
<el-icon :size="48" color="#909399"><Document /></el-icon>
<p>暂不支持预览此类型文件</p>
<el-button type="primary" @click="previewDialogVisible = false"> 关闭 </el-button>
</div>
</div>
</el-dialog> -->
<el-dialog v-model="imageViewerVisible" title="图片预览" width="60%"> <el-dialog v-model="imageViewerVisible" title="图片预览" width="60%">
<div style="text-align: center"> <div style="text-align: center">
<el-image :src="imageUrl" fit="contain" /> <el-image :src="imageUrl" fit="contain" />
...@@ -153,15 +203,17 @@ ...@@ -153,15 +203,17 @@
</template> </template>
<script setup name="FileUpload"> <script setup name="FileUpload">
import { ref } from 'vue'; import { ref, nextTick, shallowRef } from 'vue'
import { ElMessage } from 'element-plus'; import { ElMessage } from 'element-plus'
import { downloadFilesAsZip } from '@/utils/zipDownload'; // 引入刚才封装的工具 import { downloadFilesAsZip } from '@/utils/zipDownload' // 引入刚才封装的工具
import CommonDialog from '@/components/commonDialog' import CommonDialog from '@/components/commonDialog'
import FilePreview from '@/components/Preview/filePreview.vue'
import CardOne from '@/components/formCard/cardOne' import CardOne from '@/components/formCard/cardOne'
import { getToken } from '@/utils/auth' import { getToken } from '@/utils/auth'
import { addFile, getAppointmentFile, delFile, editAppointmentFile } from '@/api/sign/appointment' import { addFile, getAppointmentFile, delFile, editAppointmentFile } from '@/api/sign/appointment'
import useDictStore from '@/store/modules/dict' import useDictStore from '@/store/modules/dict'
import useUserStore from '@/store/modules/user' import useUserStore from '@/store/modules/user'
import { ArrowLeft, ArrowRight, ZoomIn, ZoomOut, Document, Loading } from '@element-plus/icons-vue'
const userStore = useUserStore() const userStore = useUserStore()
import { import {
uploadMaterialList, uploadMaterialList,
...@@ -172,11 +224,18 @@ import { ...@@ -172,11 +224,18 @@ import {
downloadCompressedFile, downloadCompressedFile,
delMaterial delMaterial
} from '@/api/common' } from '@/api/common'
import * as PDFJS from 'pdfjs-dist'
PDFJS.GlobalWorkerOptions.workerSrc = '/js/pdf.worker.min.mjs'
const props = defineProps({ const props = defineProps({
activeName: { type: String, default: '' }, //tab名称 activeName: { type: String, default: '' }, //tab名称
idsObj: { type: Object, default: () => ({}) }, //父组件传递过来的id对象 idsObj: { type: Object, default: () => ({}) }, //父组件传递过来的id对象
pageSource: { type: String, default: '' } //页面来源 pageSource: { type: String, default: '' } //页面来源
}) })
const filePreviewRef = ref(null)
// 在定义其他响应式变量的附近添加
const pdfLoading = ref(false) // PDF 加载状态
const acceptUploadType = ref('.pdf,.jpg,.jpeg,.png,.bmp,.gif,.svg')
const uploadRef = ref(null) const uploadRef = ref(null)
const dictStore = useDictStore() //获取字典数据 const dictStore = useDictStore() //获取字典数据
const { proxy } = getCurrentInstance() const { proxy } = getCurrentInstance()
...@@ -188,6 +247,191 @@ const limit = ref(10) ...@@ -188,6 +247,191 @@ const limit = ref(10)
const fileSize = ref(10) const fileSize = ref(10)
const headers = ref({ Authorization: 'Bearer ' + getToken() }) const headers = ref({ Authorization: 'Bearer ' + getToken() })
const uploadImgUrl = ref(import.meta.env.VITE_APP_BASE_API + '/oss/api/oss/upload') // 上传的服务器地址 const uploadImgUrl = ref(import.meta.env.VITE_APP_BASE_API + '/oss/api/oss/upload') // 上传的服务器地址
// PDF 预览相关
const pdfCanvasRef = ref(null) // canvas 元素引用
// 修改 pdfDoc 的定义
const pdfDoc = shallowRef(null) // pdf 文档实例
const pdfScale = ref(1.2) // 缩放比例
// 新增标志:是否取消PDF加载
let pdfLoadingCanceled = false
// ==================== 文件预览弹窗 ====================
const previewDialogVisible = ref(false)
const previewUrl = ref('')
const previewFileName = ref('')
const previewFileType = ref('') // 'image', 'pdf', 'unsupported'
const pdfScrollContainer = ref(null) // 滚动容器的 ref
const pdfTotalPages = ref(0) // 总页数(仅供显示)
const isRendering = ref(false) // 防止重复渲染
const loadPdf = async url => {
pdfLoadingCanceled = false
pdfLoading.value = true
try {
if (pdfDoc.value) {
await pdfDoc.value.destroy().catch(() => {})
pdfDoc.value = null
}
// 清空滚动容器
if (pdfScrollContainer.value) {
pdfScrollContainer.value.innerHTML = ''
}
const loadingTask = PDFJS.getDocument(url)
pdfDoc.value = await loadingTask.promise
if (pdfLoadingCanceled) {
if (pdfDoc.value) pdfDoc.value.destroy()
pdfLoading.value = false
return
}
pdfTotalPages.value = pdfDoc.value.numPages
await renderAllPages(pdfScale.value) // 渲染所有页面
pdfLoading.value = false
} catch (err) {
if (!pdfLoadingCanceled) {
console.error('PDF 加载失败', err)
ElMessage.error('PDF 文件加载失败,请检查文件链接')
previewDialogVisible.value = false
}
pdfLoading.value = false
}
}
// 修改 renderPdfPage,增加有效性检查
const renderPdfPage = async pageNum => {
if (!pdfDoc.value || pdfLoadingCanceled) return
try {
const page = await pdfDoc.value.getPage(pageNum)
const viewport = page.getViewport({ scale: pdfScale.value })
const canvas = pdfCanvasRef.value
if (!canvas) return
const context = canvas.getContext('2d')
canvas.height = viewport.height
canvas.width = viewport.width
const renderContext = {
canvasContext: context,
viewport: viewport
}
await page.render(renderContext).promise
} catch (err) {
if (!pdfLoadingCanceled) {
console.error('渲染PDF页失败', err)
}
}
}
const renderAllPages = async scale => {
if (!pdfDoc.value || pdfLoadingCanceled || isRendering.value) return
isRendering.value = true
pdfLoading.value = true // 显示加载状态
const container = pdfScrollContainer.value
if (!container) {
isRendering.value = false
return
}
// 清空之前的 canvas
container.innerHTML = ''
try {
const promises = []
for (let pageNum = 1; pageNum <= pdfTotalPages.value; pageNum++) {
if (pdfLoadingCanceled) break
const page = await pdfDoc.value.getPage(pageNum)
const viewport = page.getViewport({ scale })
// 创建 canvas 元素
const canvas = document.createElement('canvas')
canvas.className = 'pdf-page-canvas'
const context = canvas.getContext('2d')
canvas.height = viewport.height
canvas.width = viewport.width
// 添加一些底部间距,便于区分页面
canvas.style.marginBottom = '16px'
canvas.style.boxShadow = '0 2px 8px rgba(0,0,0,0.1)'
container.appendChild(canvas)
// 渲染该页
const renderTask = page.render({
canvasContext: context,
viewport: viewport
})
promises.push(renderTask.promise)
}
await Promise.all(promises)
} catch (err) {
if (!pdfLoadingCanceled) {
console.error('渲染多页 PDF 失败', err)
ElMessage.error('渲染 PDF 页面失败')
}
} finally {
isRendering.value = false
pdfLoading.value = false
}
}
// 修改 previewFile 中的 PDF 分支
// function previewFile(file) {
// console.log('====================================')
// console.log('file', file)
// console.log('====================================')
// const url = file.url || file.fileUrl
// if (!url) {
// ElMessage.warning('文件地址不存在')
// return
// }
// const ext = (file.originalName || '').split('.').pop().toLowerCase()
// previewUrl.value = url
// previewFileName.value = file.originalName || '文件'
// if (['jpg', 'jpeg', 'png', 'webp', 'gif', 'bmp', 'svg'].includes(ext)) {
// previewFileType.value = 'image'
// previewDialogVisible.value = true
// } else if (ext === 'pdf') {
// previewFileType.value = 'pdf'
// previewDialogVisible.value = true
// // 先清理旧的资源
// closePreview()
// nextTick(() => {
// loadPdf(previewUrl.value)
// })
// } else {
// previewFileType.value = 'unsupported'
// previewDialogVisible.value = true
// }
// }
// 在需要预览文件的地方调用(例如原来的 previewFile 函数)
function previewFile(file) {
// 确保传入对象包含 url 和 name 属性
filePreviewRef.value?.open({
url: file.url || file.fileUrl,
name: file.originalName || file.name
})
}
const closePreview = () => {
pdfLoadingCanceled = true // 取消任何进行中的渲染
pdfLoading.value = false
isRendering.value = false
if (pdfDoc.value) {
pdfDoc.value.destroy().catch(() => {})
pdfDoc.value = null
}
pdfTotalPages.value = 0
pdfScale.value = 1.2
if (pdfScrollContainer.value) {
pdfScrollContainer.value.innerHTML = ''
}
}
const zoomIn = () => {
pdfScale.value = Math.min(pdfScale.value + 0.2, 3.0)
renderAllPages(pdfScale.value)
}
const zoomOut = () => {
pdfScale.value = Math.max(pdfScale.value - 0.2, 0.5)
renderAllPages(pdfScale.value)
}
// 图片查看相关状态 // 图片查看相关状态
const imageViewerVisible = ref(false) const imageViewerVisible = ref(false)
const imageUrl = ref('') const imageUrl = ref('')
...@@ -201,35 +445,39 @@ const data = reactive({ ...@@ -201,35 +445,39 @@ const data = reactive({
// 下载材料包 // 下载材料包
// 状态管理 // 状态管理
const downloading = ref(false); const downloading = ref(false)
const currentCount = ref(0); const currentCount = ref(0)
const totalCount = ref(0); const totalCount = ref(0)
const viewFile = file => {
console.log('====================================')
console.log('file', file)
console.log('====================================')
}
const progressPercentage = computed(() => { const progressPercentage = computed(() => {
if (totalCount.value === 0) return 0; if (totalCount.value === 0) return 0
return Math.floor((currentCount.value / totalCount.value) * 100); return Math.floor((currentCount.value / totalCount.value) * 100)
}); })
const progressFormat = (percentage) => `${currentCount.value}/${totalCount.value}`; const progressFormat = percentage => `${currentCount.value}/${totalCount.value}`
// 2. 核心处理方法 // 2. 核心处理方法
const handleBatchDownloadSelected = async () => { const handleBatchDownloadSelected = async () => {
let apiMaterialDtoList = [] let apiMaterialDtoList = []
if (!props.idsObj.appointmentBizId) { if (!props.idsObj.appointmentBizId) {
apiMaterialDtoList = fileTableList.value.map(item => { apiMaterialDtoList = fileTableList.value.map(item => {
return { return {
dataPerson: item.dataPerson, //资料人(字典) dataPerson: item.dataPerson, //资料人(字典)
dataPersonName: item.dataPersonName, //资料人(字典) dataPersonName: item.dataPersonName, //资料人(字典)
dataType: item.dataType, //资料类型(字典) dataType: item.dataType, //资料类型(字典)
dataTypeName: item.dataTypeName, //资料类型(字典) dataTypeName: item.dataTypeName, //资料类型(字典)
fileUrlList: item.fileBizIdList.map(item2 => ({ fileUrlList: item.fileBizIdList.map(item2 => ({
fileUrl: item2.url, fileUrl: item2.url,
fileName: item2.originalName fileName: item2.originalName
})) }))
} }
}) })
}else{ } else {
apiMaterialDtoList = fileTableList.value.map(item => { apiMaterialDtoList = fileTableList.value.map(item => {
return { return {
dataPerson: item.dataPerson, //资料人(字典) dataPerson: item.dataPerson, //资料人(字典)
dataPersonName: item.dataPersonName, //资料人(字典) dataPersonName: item.dataPersonName, //资料人(字典)
...@@ -238,85 +486,80 @@ if (!props.idsObj.appointmentBizId) { ...@@ -238,85 +486,80 @@ if (!props.idsObj.appointmentBizId) {
fileUrlList: item.fileUrlList fileUrlList: item.fileUrlList
} }
}) })
} }
if (!apiMaterialDtoList || apiMaterialDtoList.length === 0) { if (!apiMaterialDtoList || apiMaterialDtoList.length === 0) {
ElMessage.warning('没有要下载的材料'); ElMessage.warning('没有要下载的材料')
return; return
} }
// --- 步骤 1: 数据清洗与扁平化 --- // --- 步骤 1: 数据清洗与扁平化 ---
const flatFileList = []; const flatFileList = []
let hasFiles = false; let hasFiles = false
apiMaterialDtoList.forEach((item,index) => { apiMaterialDtoList.forEach((item, index) => {
// 安全检查:确保 fileUrlList 存在且是数组 // 安全检查:确保 fileUrlList 存在且是数组
const urls = item.fileUrlList; const urls = item.fileUrlList
if (!urls || !Array.isArray(urls) || urls.length === 0) { if (!urls || !Array.isArray(urls) || urls.length === 0) {
return; // 跳过没有文件的行 return // 跳过没有文件的行
} }
hasFiles = true; hasFiles = true
// 生成安全的业务前缀 // 生成安全的业务前缀
// 规则:[人员类型]_[资料类型]_[业务ID] // 规则:[人员类型]_[资料类型]_[业务ID]
// 例如:POLICYHOLDER_FRONT_2216 // 例如:POLICYHOLDER_FRONT_2216
const safePerson = (item.dataPersonName || 'UNKNOWN').replace(/[\/\\:*?"<>|]/g, '_'); const safePerson = (item.dataPersonName || 'UNKNOWN').replace(/[\/\\:*?"<>|]/g, '_')
const safeType = (item.dataTypeName || 'FILE').replace(/[\/\\:*?"<>|]/g, '_'); const safeType = (item.dataTypeName || 'FILE').replace(/[\/\\:*?"<>|]/g, '_')
const filePrefix = `${safePerson}_${safeType}`; const filePrefix = `${safePerson}_${safeType}`
urls.forEach((fileItem, fIndex) => { urls.forEach((fileItem, fIndex) => {
// 兼容 fileUrlList 可能是字符串数组 或 对象数组 // 兼容 fileUrlList 可能是字符串数组 或 对象数组
let fileUrl = fileItem.fileUrl; let fileUrl = fileItem.fileUrl
let originalFileName = fileItem.fileName; let originalFileName = fileItem.fileName
// --- 关键:构建最终文件名 --- // --- 关键:构建最终文件名 ---
const finalFileName = `${filePrefix}_${originalFileName}`; const finalFileName = `${filePrefix}_${originalFileName}`
if (fileUrl) { if (fileUrl) {
flatFileList.push({ flatFileList.push({
url: fileUrl, url: fileUrl,
name: finalFileName, name: finalFileName,
// 可选:保留元数据用于调试 // 可选:保留元数据用于调试
_meta: { _meta: {
type: item.dataType, type: item.dataType,
note: item.precautions note: item.precautions
} }
}); })
} }
}); })
}); })
if (!hasFiles) { if (!hasFiles) {
ElMessage.warning('选中的项中没有包含任何附件'); ElMessage.warning('选中的项中没有包含任何附件')
return; return
} }
// --- 步骤 2: 执行下载 --- // --- 步骤 2: 执行下载 ---
totalCount.value = flatFileList.length; totalCount.value = flatFileList.length
currentCount.value = 0; currentCount.value = 0
downloading.value = true; downloading.value = true
try { try {
const zipName = `预约附件包_${new Date().toISOString().slice(0, 10)}.zip`; const zipName = `预约附件包_${new Date().toISOString().slice(0, 10)}.zip`
await downloadFilesAsZip(flatFileList, zipName, (current, total) => { await downloadFilesAsZip(flatFileList, zipName, (current, total) => {
currentCount.value = current; currentCount.value = current
}); })
ElMessage.success(`成功打包 ${flatFileList.length} 个文件`); ElMessage.success(`成功打包 ${flatFileList.length} 个文件`)
} catch (error) { } catch (error) {
ElMessage.error('下载过程中出现异常,请查看控制台'); ElMessage.error('下载过程中出现异常,请查看控制台')
console.error(error); console.error(error)
} finally { } finally {
downloading.value = false; downloading.value = false
} }
}; }
const { queryParams, form } = toRefs(data) const { queryParams, form } = toRefs(data)
// 新增:用于存储已上传成功的文件列表 // 新增:用于存储已上传成功的文件列表
...@@ -444,7 +687,7 @@ const handleView = row => { ...@@ -444,7 +687,7 @@ const handleView = row => {
const downloadFile = () => { const downloadFile = () => {
let apiMaterialDtoList = [] let apiMaterialDtoList = []
let params = { let params = {
projectBizId:userStore.projectInfo.projectBizId || '', projectBizId: userStore.projectInfo.projectBizId || '',
objectName: '预约附件材料包', //对象名(包名) objectName: '预约附件材料包', //对象名(包名)
objectBizId: '' //对象业务ID objectBizId: '' //对象业务ID
} }
...@@ -508,6 +751,7 @@ function handleBeforeUpload(file) { ...@@ -508,6 +751,7 @@ function handleBeforeUpload(file) {
// proxy.$modal.msgError('文件名不正确,不能包含英文逗号!') // proxy.$modal.msgError('文件名不正确,不能包含英文逗号!')
// return false // return false
// } // }
const isLt = file.size / 1024 / 1024 < fileSize.value const isLt = file.size / 1024 / 1024 < fileSize.value
if (!isLt) { if (!isLt) {
proxy.$modal.msgError(`上传文件大小不能超过 ${fileSize.value} MB!`) proxy.$modal.msgError(`上传文件大小不能超过 ${fileSize.value} MB!`)
...@@ -522,7 +766,7 @@ function handleExceed() { ...@@ -522,7 +766,7 @@ function handleExceed() {
// 文件上传成功回调 // 文件上传成功回调
const uploadSuccess = (res, file, fileList) => { const uploadSuccess = (res, file, fileList) => {
console.log('上传成功', res, file) console.log('上传成功', res, file)
proxy.$modal.closeLoading(); proxy.$modal.closeLoading()
if (res.code === 200) { if (res.code === 200) {
// 构造前端使用的文件对象(保留原始 file 信息 + 后端返回的 url 等) // 构造前端使用的文件对象(保留原始 file 信息 + 后端返回的 url 等)
const uploadedFile = { const uploadedFile = {
...@@ -614,6 +858,126 @@ defineExpose({ ...@@ -614,6 +858,126 @@ defineExpose({
}) })
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.fileNameBox {
width: 70%;
/* 核心三件套: 强制单行 + 溢出隐藏 + 省略号 */
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.pdf-viewer {
display: flex;
flex-direction: column;
height: 70vh;
}
.pdf-toolbar {
display: flex;
justify-content: center;
align-items: center;
gap: 16px;
padding: 8px 0;
border-bottom: 1px solid #e4e7ed;
margin-bottom: 12px;
.page-info {
font-size: 14px;
color: #606266;
}
}
.pdf-scroll-wrapper {
flex: 1;
overflow-y: auto; /* 垂直滚动 */
border: 1px solid #ebeef5;
border-radius: 4px;
background: #f9fafc;
}
.pdf-scroll-container {
display: flex;
flex-direction: column;
align-items: center; /* 页面居中显示 */
padding: 16px;
}
.pdf-page-canvas {
display: block;
max-width: 100%; /* 自适应宽度,防止溢出容器 */
height: auto;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
margin-bottom: 16px;
}
.pdf-loading {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-height: 300px;
color: #409eff;
gap: 12px;
.el-icon {
font-size: 32px;
}
}
.pdf-viewer {
display: flex;
flex-direction: column;
height: 70vh;
}
.pdf-toolbar {
display: flex;
justify-content: center;
align-items: center;
gap: 16px;
padding: 8px 0;
border-bottom: 1px solid #e4e7ed;
margin-bottom: 12px;
.page-info {
font-size: 14px;
color: #606266;
}
}
.pdf-canvas-wrapper {
flex: 1;
overflow: auto;
text-align: center;
}
.pdf-canvas {
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
border-radius: 4px;
margin: 0 auto;
}
.preview-container {
display: flex;
justify-content: center;
align-items: center;
min-height: 400px;
}
.preview-image-wrapper {
text-align: center;
}
.preview-image {
max-width: 100%;
max-height: 70vh;
object-fit: contain;
}
.preview-pdf {
width: 100%;
height: 70vh;
}
.preview-unsupported {
text-align: center;
padding: 40px;
}
.preview-unsupported p {
margin: 16px 0;
color: #909399;
}
.uploadContainer { .uploadContainer {
padding-left: 10px; padding-left: 10px;
padding-top: 10px; padding-top: 10px;
......
...@@ -232,7 +232,7 @@ import { ...@@ -232,7 +232,7 @@ import {
getItineraryExprot getItineraryExprot
} from '@/api/sign/appointment' } from '@/api/sign/appointment'
import useUserStore from '@/store/modules/user' import useUserStore from '@/store/modules/user'
import { copyToClipboard } from '@/utils/copyToClipboard' import copyToClipboard from '@/utils/copyToClipboard'
const dictStore = useDictStore() const dictStore = useDictStore()
const userStore = useUserStore() const userStore = useUserStore()
const router = useRouter() const router = useRouter()
......
...@@ -386,6 +386,7 @@ const handleReset = () => { ...@@ -386,6 +386,7 @@ const handleReset = () => {
loadTableData() loadTableData()
} }
const handleQuery = async () => { const handleQuery = async () => {
currentPage.value = 1
const params = searchFormRef.value.getFormData() const params = searchFormRef.value.getFormData()
console.log('params', params) console.log('params', params)
...@@ -421,6 +422,8 @@ const operationBtnList = ref([ ...@@ -421,6 +422,8 @@ const operationBtnList = ref([
// 加载表格数据 // 加载表格数据
const loadTableData = async (searchParams = {}) => { const loadTableData = async (searchParams = {}) => {
loading.value = true loading.value = true
// console.log('searchFormRef.value', searchFormRef.value)
// const searchParams = searchFormRef.value.getFormData() || {}
try { try {
const params = { const params = {
pageNo: currentPage.value, pageNo: currentPage.value,
...@@ -443,11 +446,13 @@ loadTableData() ...@@ -443,11 +446,13 @@ loadTableData()
// 分页事件 // 分页事件
const handleSizeChange = val => { const handleSizeChange = val => {
pageSize.value = val pageSize.value = val
loadTableData() const params = searchFormRef.value.getFormData()
loadTableData(params)
} }
const handleCurrentChange = val => { const handleCurrentChange = val => {
currentPage.value = val currentPage.value = val
loadTableData() const params = searchFormRef.value.getFormData()
loadTableData(params)
} }
// 表格数据 // 表格数据
const tableData = ref([]) const tableData = ref([])
......
...@@ -98,7 +98,9 @@ ...@@ -98,7 +98,9 @@
</el-table-column> </el-table-column>
<el-table-column prop="payer" label="付款人" width="150" /> <el-table-column prop="payer" label="付款人" width="150" />
<el-table-column prop="policyHolder" label="投保人" width="150" /> <el-table-column prop="policyHolder" label="投保人" width="150" />
<el-table-column prop="policyHolderEn" label="投保人(英文)" width="120" />
<el-table-column prop="insured" label="受保人" width="150" /> <el-table-column prop="insured" label="受保人" width="150" />
<el-table-column prop="insuredEn" label="受保人(英文)" width="150" />
<el-table-column prop="brokerName" label="转介人" width="150" /> <el-table-column prop="brokerName" label="转介人" width="150" />
<el-table-column prop="createTime" label="创建时间" width="150"> <el-table-column prop="createTime" label="创建时间" width="150">
<template #default="{ row }"> <template #default="{ row }">
...@@ -151,7 +153,7 @@ ...@@ -151,7 +153,7 @@
ref="addCheckRecordFormRef" ref="addCheckRecordFormRef"
:config="addCheckRecordConfig" :config="addCheckRecordConfig"
v-model="addCheckRecordFormModel" v-model="addCheckRecordFormModel"
@select-change="onSelectChange" @select-change="(prop, value, item) => onSelectChange('addCheckRecord', prop, value, item)"
/> />
<div> <div>
<el-button type="primary" icon="plus" @click="addRemittance" style="margin-bottom: 10px" <el-button type="primary" icon="plus" @click="addRemittance" style="margin-bottom: 10px"
...@@ -244,9 +246,10 @@ ...@@ -244,9 +246,10 @@
:config="remittanceConfig" :config="remittanceConfig"
v-model="remittanceFormModel" v-model="remittanceFormModel"
@uploadSuccess="handleUploadSuccess" @uploadSuccess="handleUploadSuccess"
@select-change="(prop, value, item) => onSelectChange('remittance', prop, value, item)"
/> />
<div <div
v-if="remittanceFormModel.apiPremiumRemittanceFileDtoList?.length" v-if="remittanceFormModel.apiPremiumRemittanceFileDtoList.length > 0"
class="otherFileBox" class="otherFileBox"
> >
<el-table border :data="tempOtherFileList" size="small"> <el-table border :data="tempOtherFileList" size="small">
...@@ -260,6 +263,7 @@ ...@@ -260,6 +263,7 @@
</el-table-column> </el-table-column>
<el-table-column label="操作" width="100"> <el-table-column label="操作" width="100">
<template #default="{ row, $index }"> <template #default="{ row, $index }">
<el-button type="primary" link @click="viewFile(row, $index)"> 查看 </el-button>
<el-button type="danger" link @click="removeOtherFile(row, $index)"> <el-button type="danger" link @click="removeOtherFile(row, $index)">
删除 删除
</el-button> </el-button>
...@@ -270,13 +274,24 @@ ...@@ -270,13 +274,24 @@
</div> </div>
</el-scrollbar> </el-scrollbar>
</CommonDialog> </CommonDialog>
<!-- <filePreviewDialog
v-model="previewVisible"
:file-url="currentOtherFile.fileUrl"
:file-name="currentOtherFile.fileName"
ref="filePreviewDialogRef"
/> -->
<!-- 文件预览弹窗 -->
<FilePreview ref="filePreviewRef" />
</div> </div>
</template> </template>
<script setup> <script setup>
import FilePreview from '@/components/Preview/filePreview.vue'
import { validateEnglish2 } from '@/utils/validate'
import { ref, reactive, watch } from 'vue' import { ref, reactive, watch } from 'vue'
import CommonPage from '@/components/commonPage' import CommonPage from '@/components/commonPage'
import CommonDialog from '@/components/commonDialog' import CommonDialog from '@/components/commonDialog'
import filePreviewDialog from '@/components/commonDialog/filePreviewDialog.vue'
import SearchForm from '@/components/SearchForm/SearchForm.vue' import SearchForm from '@/components/SearchForm/SearchForm.vue'
import { ElMessage } from 'element-plus' import { ElMessage } from 'element-plus'
import { formatCurrency } from '@/utils/number' import { formatCurrency } from '@/utils/number'
...@@ -296,13 +311,20 @@ import { ...@@ -296,13 +311,20 @@ import {
editPremiumReconciliation, editPremiumReconciliation,
getRemainingUnpaidAmount, getRemainingUnpaidAmount,
submitResult, submitResult,
addSinglePremiumRemittance addSinglePremiumRemittance,
policyDetail
} from '@/api/sign/policy' } from '@/api/sign/policy'
import { getBankList } from '@/api/common' import { getBankList } from '@/api/common'
import useUserStore from '@/store/modules/user' import useUserStore from '@/store/modules/user'
import { loadDicts, getDictLabel } from '@/utils/useDict' import { loadDicts, getDictLabel } from '@/utils/useDict'
import { getToken } from '@/utils/auth' import { getToken } from '@/utils/auth'
import { getNowTime, formatToDate, formatToDateTime } from '@/utils/date' import { getNowTime, formatToDate, formatToDateTime } from '@/utils/date'
const filePreviewRef = ref(null)
let tempPolicyNo = ref('')
const previewVisible = ref(false)
const currentFileUrl = ref('')
const currentFileName = ref('')
const currentOtherFile = ref({ fileUrl: '', fileName: '' })
const { proxy } = getCurrentInstance() const { proxy } = getCurrentInstance()
const userStore = useUserStore() const userStore = useUserStore()
// 分页相关 // 分页相关
...@@ -374,7 +396,7 @@ const searchConfig = ref([ ...@@ -374,7 +396,7 @@ const searchConfig = ref([
{ {
type: 'input', type: 'input',
prop: 'policyHolder', prop: 'policyHolder',
label: '投保人' label: '投保人(中文/英文)'
}, },
{ {
type: 'select', type: 'select',
...@@ -384,6 +406,11 @@ const searchConfig = ref([ ...@@ -384,6 +406,11 @@ const searchConfig = ref([
dictType: 'csf_ap_first_issue' dictType: 'csf_ap_first_issue'
}, },
{ {
type: 'input',
prop: 'insured',
label: '受保人(中文/英文)'
},
{
type: 'select', type: 'select',
prop: 'policyFollowStatus', prop: 'policyFollowStatus',
label: '新单状态', label: '新单状态',
...@@ -408,10 +435,15 @@ const searchConfig = ref([ ...@@ -408,10 +435,15 @@ const searchConfig = ref([
type: 'date', type: 'date',
prop: 'paymentDate', prop: 'paymentDate',
label: '付款日期', label: '付款日期',
maxDate: 'today' maxDate: 'today'
} }
// {
// type: 'input',
// prop: 'eng',
// label: '英文名字'
// }
]) ])
// 动态生成操作菜单项 // 动态生成操作菜单项
const getMenuItems = row => { const getMenuItems = row => {
const items = [{ label: '编辑', value: 'editRecord' }] const items = [{ label: '编辑', value: 'editRecord' }]
...@@ -426,6 +458,17 @@ const getMenuItems = row => { ...@@ -426,6 +458,17 @@ const getMenuItems = row => {
return items return items
} }
const viewFile = row => {
console.log('====================================')
console.log('其他附件', row)
console.log('====================================')
// previewVisible.value = true
// currentOtherFile.value = JSON.parse(JSON.stringify(row))
filePreviewRef.value?.open({
url: row.url,
name: row.fileName
})
}
//获取远程搜索下拉框数据,回显数据用 //获取远程搜索下拉框数据,回显数据用
const getSelectOptions = async () => { const getSelectOptions = async () => {
const params4 = { const params4 = {
...@@ -515,7 +558,7 @@ const affirmConfig = [ ...@@ -515,7 +558,7 @@ const affirmConfig = [
type: 'select', type: 'select',
prop: 'status', prop: 'status',
label: '保单状态', label: '保单状态',
dictType: 'csf_policy_status_new', dictType: 'csf_policy_follow_status_new',
visible: formData => visible: formData =>
(formData.remainingUnpaidAmount && Number(formData.remainingUnpaidAmount) < 0) || (formData.remainingUnpaidAmount && Number(formData.remainingUnpaidAmount) < 0) ||
Number(formData.remainingUnpaidAmount) == 0 Number(formData.remainingUnpaidAmount) == 0
...@@ -748,13 +791,16 @@ const remittanceConfig = [ ...@@ -748,13 +791,16 @@ const remittanceConfig = [
multiple: true, multiple: true,
limit: 10, limit: 10,
maxSize: 10 * 1024 * 1024, // 10MB maxSize: 10 * 1024 * 1024, // 10MB
accept: '.png,.jpg,.jpeg,.webp,.pdf,.doc,.docx,.xls,.xlsx,.txt', // ✅ 改成扩展名! // accept: '.png,.jpg,.jpeg,.webp,.pdf,.doc,.docx,.xls,.xlsx,.txt', // ✅ 改成扩展名!
accept: '.png,.jpg,.jpeg,.webp,.pdf', // ✅ 改成扩展名!
action: import.meta.env.VITE_APP_BASE_API + '/oss/api/oss/upload', action: import.meta.env.VITE_APP_BASE_API + '/oss/api/oss/upload',
headers: { Authorization: 'Bearer ' + getToken() }, headers: { Authorization: 'Bearer ' + getToken() },
listType: 'text', listType: 'text',
defaultValue: [], defaultValue: [],
span: 24, span: 24,
rules: [{ required: true, message: '请上传支付凭证', trigger: 'blur' }] rules: [{ required: true, message: '请上传支付凭证', trigger: 'blur' }],
customFileList: true, // ✅ 开启自定义列表
customUploadButtonText: '点击上传文件' // 可选,默认“点击上传文件”
}, },
{ {
type: 'upload', type: 'upload',
...@@ -764,13 +810,15 @@ const remittanceConfig = [ ...@@ -764,13 +810,15 @@ const remittanceConfig = [
multiple: true, multiple: true,
limit: 10, limit: 10,
maxSize: 10 * 1024 * 1024, // 10MB maxSize: 10 * 1024 * 1024, // 10MB
accept: '.png,.jpg,.jpeg,.webp,.pdf,.doc,.docx,.xls,.xlsx,.txt', // ✅ 改成扩展名! accept: '.png,.jpg,.jpeg,.webp,.pdf', // ✅ 改成扩展名!
action: import.meta.env.VITE_APP_BASE_API + '/oss/api/oss/upload', action: import.meta.env.VITE_APP_BASE_API + '/oss/api/oss/upload',
headers: { Authorization: 'Bearer ' + getToken() }, headers: { Authorization: 'Bearer ' + getToken() },
listType: 'text', listType: 'text',
defaultValue: [], defaultValue: [],
span: 24, span: 24,
rules: [{ required: true, message: '请上传账户证明', trigger: 'blur' }] rules: [{ required: true, message: '请上传账户证明', trigger: 'blur' }],
customFileList: true, // ✅ 开启自定义列表
customUploadButtonText: '点击上传文件' // 可选,默认“点击上传文件”
}, },
{ {
type: 'upload', type: 'upload',
...@@ -780,7 +828,7 @@ const remittanceConfig = [ ...@@ -780,7 +828,7 @@ const remittanceConfig = [
multiple: true, multiple: true,
limit: 10, limit: 10,
maxSize: 10 * 1024 * 1024, // 5MB maxSize: 10 * 1024 * 1024, // 5MB
accept: '.png,.jpg,.jpeg,.webp,.pdf,.doc,.docx,.xls,.xlsx,.txt', // ✅ 支持多种格式 accept: '.png,.jpg,.jpeg,.webp,.pdf', // ✅ 支持多种格式
action: import.meta.env.VITE_APP_BASE_API + '/oss/api/oss/upload', action: import.meta.env.VITE_APP_BASE_API + '/oss/api/oss/upload',
headers: { Authorization: 'Bearer ' + getToken() }, headers: { Authorization: 'Bearer ' + getToken() },
listType: 'text', // ← 按钮样式(非图片卡片) listType: 'text', // ← 按钮样式(非图片卡片)
...@@ -809,12 +857,49 @@ const handleUploadSuccess = (prop, Model) => { ...@@ -809,12 +857,49 @@ const handleUploadSuccess = (prop, Model) => {
} }
} }
// 下拉框改变 // 下拉框改变
function onSelectChange(prop, value, item) { async function onSelectChange(type, prop, value, item) {
console.log('【Select 变更】字段:', prop, '新值:', value, '配置:', item) console.log('【Select 变更】字段:', prop, '新值:', value, '配置:', item)
// 汇款弹窗
// 示例:你可以在这里做任何事 if (type == 'remittance') {
if (prop === 'policyNo') { // 示例:你可以在这里做任何事
checkPolicyNo(value) if (prop === 'policyNo') {
checkPolicyNo(value)
} else if (prop == 'paymentRel') {
if (value !== 'TBR' && value !== 'SBR') {
await nextTick() // 等待 Vue 完成本轮 DOM 更新
remittanceFormModel.value.payer = ''
}
if (value == 'TBR' || value == 'SBR') {
getPolicyInfo(tempPolicyNo.value, 'remittance', value)
}
}
} else if (type == 'addCheckRecord') {
//新增保费对账弹窗
if (prop === 'policyNo' && value) {
tempPolicyNo.value = value
}
}
}
// 检查此保单号是否已经提交过保费对账
const getPolicyInfo = async (policyNo, type, valueType) => {
try {
const res = await policyDetail(policyNo)
if (type == 'remittance') {
if (valueType == 'TBR') {
remittanceFormModel.value.payer = res.data.policyHolder
} else if (valueType == 'SBR') {
remittanceFormModel.value.payer = res.data.insured
}
} else if (type == 'affirm') {
affirmFormModel.value.effectiveDate = res.data.effectiveDate
affirmFormModel.value.underwritingDate = res.data.underwritingDate
affirmFormModel.value.status = res.data.status
affirmFormModel.value.policyExpirationDate = res.data.policyExpirationDate
affirmFormModel.value.coolingOffEndDate = res.data.coolingOffEndDate
}
} catch (error) {
console.error('加载数据失败:', error)
ElMessage.error(error.message || '加载数据失败')
} }
} }
// 新增汇款 // 新增汇款
...@@ -908,6 +993,7 @@ const confirmRemittance = async data => { ...@@ -908,6 +993,7 @@ const confirmRemittance = async data => {
const formData = await remittanceFormRef.value.validate() const formData = await remittanceFormRef.value.validate()
let newFormData = JSON.parse(JSON.stringify(formData)) let newFormData = JSON.parse(JSON.stringify(formData))
// 因为汇款记录回显了保费对账的一些字段,但提交汇款记录的时候这些回显的字段是不能提交的,所以在这里做删除 // 因为汇款记录回显了保费对账的一些字段,但提交汇款记录的时候这些回显的字段是不能提交的,所以在这里做删除
for (const key in newFormData) { for (const key in newFormData) {
if (key == deleteObjkeys[key]) delete newFormData[key] if (key == deleteObjkeys[key]) delete newFormData[key]
// 文件上传的数据格式在这里统一处理 // 文件上传的数据格式在这里统一处理
...@@ -934,6 +1020,9 @@ const confirmRemittance = async data => { ...@@ -934,6 +1020,9 @@ const confirmRemittance = async data => {
} }
} }
let res = {} let res = {}
console.log('====================================')
console.log('newFormData', newFormData)
console.log('====================================')
if (currentRow.value.premiumReconciliationBizId && !formData.premiumRemittanceBizId) { if (currentRow.value.premiumReconciliationBizId && !formData.premiumRemittanceBizId) {
//编辑保费对账状态下新增汇款记录 //编辑保费对账状态下新增汇款记录
const params = { const params = {
...@@ -986,8 +1075,11 @@ const confirmRemittance = async data => { ...@@ -986,8 +1075,11 @@ const confirmRemittance = async data => {
} }
} }
// 修改汇款记录 // 修改汇款记录
const editRemittance = row => { const editRemittance = async row => {
let newObj = JSON.parse(JSON.stringify(row)) let newObj = JSON.parse(JSON.stringify(row))
console.log('====================================')
console.log('修改', row)
console.log('====================================')
// 添加保费对账弹窗得form表单便于汇款回显 // 添加保费对账弹窗得form表单便于汇款回显
newObj.reconciliationType = addCheckRecordFormModel.value.reconciliationType newObj.reconciliationType = addCheckRecordFormModel.value.reconciliationType
newObj.applicant = addCheckRecordFormModel.value.applicant newObj.applicant = addCheckRecordFormModel.value.applicant
...@@ -997,28 +1089,47 @@ const editRemittance = row => { ...@@ -997,28 +1089,47 @@ const editRemittance = row => {
// 上传回显得格式是[{url:''}]所以要处理一下 // 上传回显得格式是[{url:''}]所以要处理一下
for (const key in newObj) { for (const key in newObj) {
if ( if (
(key == 'paymentVoucherList' || key == 'accountVerificationDtoList' ||
key == 'accountVerificationList' || (key == 'paymentVoucherDtoList' && newObj[key] && newObj[key].length > 0)
key == 'apiPremiumRemittanceFileDtoList') &&
newObj[key] &&
newObj[key].length > 0
) { ) {
console.log('key', key)
newObj[key] = newObj[key].map(item => {
return {
url: item.fileUrl,
name: item.fileName
}
})
}
if (key == 'apiPremiumRemittanceFileDtoList' && newObj[key] && newObj[key].length > 0) {
newObj[key] = newObj[key].map(item => { newObj[key] = newObj[key].map(item => {
if (key == 'apiPremiumRemittanceFileDtoList') { if (key == 'apiPremiumRemittanceFileDtoList') {
return { return {
...item, ...item,
url: item.fileUrl url: item.fileUrl
} }
} else {
return {
url: item
}
} }
// else {
// return {
// url: item.fileUrl,
// name: item.fileName
// }
// }
}) })
} }
} }
remittanceFormModel.value = newObj remittanceFormModel.value = newObj
console.log('remittanceFormModel.value', remittanceFormModel.value)
if (newObj.accountVerificationDtoList && newObj.accountVerificationDtoList.length > 0) {
remittanceFormModel.value.accountVerificationList = newObj.accountVerificationDtoList
}
if (newObj.paymentVoucherDtoList && newObj.paymentVoucherDtoList.length > 0) {
remittanceFormModel.value.paymentVoucherList = newObj.paymentVoucherDtoList
}
if (remittanceFormModel.value.apiPremiumRemittanceFileDtoList.length > 0) {
tempOtherFileList.value = remittanceFormModel.value.apiPremiumRemittanceFileDtoList
}
showRemittance.value = true showRemittance.value = true
} }
...@@ -1147,8 +1258,13 @@ const handleReset = () => { ...@@ -1147,8 +1258,13 @@ const handleReset = () => {
} }
const handleQuery = async () => { const handleQuery = async () => {
const params = searchFormRef.value.getFormData() const params = searchFormRef.value.getFormData()
currentPage.value = 1
console.log('params', params) console.log('params', params)
// let msg = validateEnglish2(params.eng)
// if (params.eng && msg) {
// ElMessage.error(`英文名字:${msg}`)
// return
// }
loadTableData(params) loadTableData(params)
} }
const visibleDefaultButtons = ref(['add', 'reset', 'query']) const visibleDefaultButtons = ref(['add', 'reset', 'query'])
...@@ -1181,6 +1297,7 @@ const operationBtnList = ref([ ...@@ -1181,6 +1297,7 @@ const operationBtnList = ref([
// 加载表格数据 // 加载表格数据
const loadTableData = async (searchParams = {}) => { const loadTableData = async (searchParams = {}) => {
loading.value = true loading.value = true
try { try {
const params = { const params = {
pageNo: currentPage.value, pageNo: currentPage.value,
...@@ -1203,17 +1320,21 @@ const loadTableData = async (searchParams = {}) => { ...@@ -1203,17 +1320,21 @@ const loadTableData = async (searchParams = {}) => {
// 分页事件 // 分页事件
const handleSizeChange = val => { const handleSizeChange = val => {
pageSize.value = val pageSize.value = val
loadTableData() const params = searchFormRef.value.getFormData()
loadTableData(params)
} }
const handleCurrentChange = val => { const handleCurrentChange = val => {
currentPage.value = val currentPage.value = val
loadTableData() const params = searchFormRef.value.getFormData()
loadTableData(params)
} }
// 表格数据 // 表格数据
const tableData = ref([]) const tableData = ref([])
const handleSelect = (command, row) => { const handleSelect = (command, row) => {
selectedRow.value = row selectedRow.value = JSON.parse(JSON.stringify(row))
tempPolicyNo.value = selectedRow.value.policyNo
if (command === 'editRecord') { if (command === 'editRecord') {
currentRow.value = JSON.parse(JSON.stringify(row)) currentRow.value = JSON.parse(JSON.stringify(row))
getPremiumReconciliationDetail(row) getPremiumReconciliationDetail(row)
...@@ -1222,8 +1343,10 @@ const handleSelect = (command, row) => { ...@@ -1222,8 +1343,10 @@ const handleSelect = (command, row) => {
settingAffirmLoading.value = false settingAffirmLoading.value = false
showAffirm.value = true showAffirm.value = true
currentRow.value = JSON.parse(JSON.stringify(row)) currentRow.value = JSON.parse(JSON.stringify(row))
getPolicyInfo(row.policyNo, 'affirm')
} }
} }
const addReceipts = async () => { const addReceipts = async () => {
try { try {
// ✅ 正确:await validate(),成功时返回表单数据 // ✅ 正确:await validate(),成功时返回表单数据
......
<template> <template>
<div> <div>
<CommonPage :operationBtnList='operationBtnList' :visibleDefaultButtons="visibleDefaultButtons" v-loading.fullscreen.lock="statusLoading" <CommonPage
:showSearchForm='true' :show-pagination='true' :total='pageTotal' :current-page='currentPage' :operationBtnList="operationBtnList"
:page-size='pageSize' @size-change='handleSizeChange' @current-change='handleCurrentChange'> :visibleDefaultButtons="visibleDefaultButtons"
v-loading.fullscreen.lock="statusLoading"
:showSearchForm="true"
:show-pagination="true"
:total="pageTotal"
:current-page="currentPage"
:page-size="pageSize"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
>
<!-- 搜索区域 --> <!-- 搜索区域 -->
<template #searchForm> <template #searchForm>
<SearchForm ref="searchFormRef" :config="searchConfig" /> <SearchForm ref="searchFormRef" :config="searchConfig" />
</template> </template>
<!-- 列表区域 --> <!-- 列表区域 -->
<template #table> <template #table>
<el-table :data="tableData" height="500" border highlight-current-row style="width: 100%" v-loading="loading"> <el-table
:data="tableData"
height="500"
border
highlight-current-row
style="width: 100%"
v-loading="loading"
>
<el-table-column prop="policyNo" label="保单号" width="200" sortable /> <el-table-column prop="policyNo" label="保单号" width="200" sortable />
<el-table-column prop="status" label="新单状态" width="120" sortable> <el-table-column prop="status" label="新单状态" width="120" sortable>
<template #default="{ row }"> <template #default="{ row }">
...@@ -25,6 +41,10 @@ ...@@ -25,6 +41,10 @@
<el-table-column prop="insuranceCompany" label="保险公司" width="200" sortable /> <el-table-column prop="insuranceCompany" label="保险公司" width="200" sortable />
<el-table-column prop="productName" label="产品计划" width="200" sortable /> <el-table-column prop="productName" label="产品计划" width="200" sortable />
<el-table-column prop="issueNumber" label="缴费年期" width="120" sortable /> <el-table-column prop="issueNumber" label="缴费年期" width="120" sortable />
<el-table-column prop="brokerName" label="转介人(主)" width="120" />
<el-table-column prop="eachIssuePremium" label="保费" width="120" />
<el-table-column prop="signer" label="签单员" width="120" />
<el-table-column prop="coolingOffEndDate" label="冷静期结束日" width="150" sortable />
<el-table-column fixed="right" label="操作" min-width="80"> <el-table-column fixed="right" label="操作" min-width="80">
<template #default="{ row }"> <template #default="{ row }">
<el-popover placement="right" :width="200" trigger="click"> <el-popover placement="right" :width="200" trigger="click">
...@@ -34,9 +54,12 @@ ...@@ -34,9 +54,12 @@
</el-icon> </el-icon>
</template> </template>
<el-menu @select="handleSelect($event, row)" popper-class="custom-menu"> <el-menu @select="handleSelect($event, row)" popper-class="custom-menu">
<el-menu-item :index="item.value" v-for="item in dropdownItems" :key="item.value">{{ <el-menu-item
item.label :index="item.value"
}}</el-menu-item> v-for="item in dropdownItems"
:key="item.value"
>{{ item.label }}</el-menu-item
>
</el-menu> </el-menu>
</el-popover> </el-popover>
</template> </template>
...@@ -45,14 +68,31 @@ ...@@ -45,14 +68,31 @@
</template> </template>
</CommonPage> </CommonPage>
<!-- 弹窗--> <!-- 弹窗-->
<CommonDialog dialogTitle='修改状态' dialogWidth='80%' :openDialog=editStatusDialogFlag :showAction='true' <CommonDialog
:showClose='true' @close='editStatusDialogFlag = false' @confirm='handleEditStatusSubmit'> dialogTitle="修改状态"
<SearchForm ref="editStatusFormRef" :config="editStatusFormConfig" v-model="editStatusFormData" /> dialogWidth="80%"
:openDialog="editStatusDialogFlag"
:showAction="true"
:showClose="true"
@close="editStatusDialogFlag = false"
@confirm="handleEditStatusSubmit"
>
<SearchForm
ref="editStatusFormRef"
:config="editStatusFormConfig"
v-model="editStatusFormData"
/>
</CommonDialog> </CommonDialog>
<!-- 查看关联记录 --> <!-- 查看关联记录 -->
<CommonDialog dialogTitle='查看关联记录' dialogWidth='80%' :openDialog=viewRelatedDialogFlag :showAction='false' <CommonDialog
:showClose='true' @close='viewRelatedDialogFlag = false'> dialogTitle="查看关联记录"
dialogWidth="80%"
:openDialog="viewRelatedDialogFlag"
:showAction="false"
:showClose="true"
@close="viewRelatedDialogFlag = false"
>
<el-table :data="relateRecordTableData" style="width: 100%"> <el-table :data="relateRecordTableData" style="width: 100%">
<el-table-column fixed prop="followDate" label="流程编号" width="150" /> <el-table-column fixed prop="followDate" label="流程编号" width="150" />
<el-table-column prop="name" label="客户姓名" width="120" /> <el-table-column prop="name" label="客户姓名" width="120" />
...@@ -69,21 +109,31 @@ ...@@ -69,21 +109,31 @@
</CommonDialog> </CommonDialog>
<!-- 查看详情、更新数据 --> <!-- 查看详情、更新数据 -->
<CommonDialog :dialogTitle='mode === "viewDetail" ? "查看详情" : "更新数据"' dialogWidth='80%' <CommonDialog
:openDialog=viewDetailDialogFlag :showAction='false' :showClose='true' @close='viewDetailDialogFlag=false' :dialogTitle="mode === 'viewDetail' ? '查看详情' : '更新数据'"
@confirm='handleUpdateSubmit'> dialogWidth="80%"
<PolicyDetail v-model="policyDetailFormData" :policyBizId="selectedRow.policyBizId" :mode="mode" :openDialog="viewDetailDialogFlag"
ref="policyDetailFormRef" @submit="onSubmit" @cancel="viewDetailDialogFlag = false" :showAction="false"
v-if="viewDetailDialogFlag" /> :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> </CommonDialog>
</div> </div>
</template> </template>
<script setup> <script setup>
import { ref, reactive, computed, watch, nextTick } from 'vue' import { ref, reactive, computed, watch, nextTick } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus' import { ElMessage, ElMessageBox } from 'element-plus'
import CommonPage from '@/components/commonPage' import CommonPage from '@/components/commonPage'
import SearchForm from '@/components/SearchForm/SearchForm.vue' import SearchForm from '@/components/SearchForm/SearchForm.vue'
import CommonDialog from '@/components/commonDialog' import CommonDialog from '@/components/commonDialog'
...@@ -95,9 +145,13 @@ import { ...@@ -95,9 +145,13 @@ import {
getExpectedCommissionList, getExpectedCommissionList,
changePolicyStatus, changePolicyStatus,
policyFollowReport, policyFollowReport,
saveMailingInfo, updatePolicyfollow, batchSaveBrokers, saveInitialPayment,updatePolicyProduct saveMailingInfo,
updatePolicyfollow,
batchSaveBrokers,
saveInitialPayment,
updatePolicyProduct
} from '@/api/sign/underwritingMain' } from '@/api/sign/underwritingMain'
import {copyToClipboard} from '@/utils/copyToClipboard' import copyToClipboard from '@/utils/copyToClipboard';
import PolicyDetail from './policyDetail.vue' import PolicyDetail from './policyDetail.vue'
const policyDetailFormRef = ref(null) const policyDetailFormRef = ref(null)
const policyDetailFormData = ref({}) const policyDetailFormData = ref({})
...@@ -108,7 +162,7 @@ const pageSize = ref(10) ...@@ -108,7 +162,7 @@ const pageSize = ref(10)
const pageTotal = ref(0) const pageTotal = ref(0)
const loading = ref(false) const loading = ref(false)
const statusLoading = ref(false) const statusLoading = ref(false)
// 搜索表单数据 // 搜索表单数据
const searchFormData = reactive({}) const searchFormData = reactive({})
const selectedRow = ref(null) const selectedRow = ref(null)
// 弹窗相关 // 弹窗相关
...@@ -123,26 +177,27 @@ const editStatusFormConfig = ref([ ...@@ -123,26 +177,27 @@ const editStatusFormConfig = ref([
prop: 'status', prop: 'status',
label: '新单状态', label: '新单状态',
dictType: 'csf_policy_follow_status_new' dictType: 'csf_policy_follow_status_new'
}, },
{ {
type: 'date', type: 'date',
prop: 'effectiveDate', prop: 'effectiveDate',
label: '保单生效日', label: '保单生效日'
}, { },
{
type: 'date', type: 'date',
prop: 'underwritingDate', prop: 'underwritingDate',
label: '保单核保日', label: '保单核保日'
}, }
]) ])
// 查看关联记录 // 查看关联记录
const viewRelatedDialogFlag = ref(false) const viewRelatedDialogFlag = ref(false)
const relateRecordTableData = ref([]) const relateRecordTableData = ref([])
const viewRelatedDetail = (row) => { const viewRelatedDetail = row => {
ElMessage.info(`查看关联记录详情:${JSON.stringify(row)}`) ElMessage.info(`查看关联记录详情:${JSON.stringify(row)}`)
} }
// 提交修改状态 // 提交修改状态
const handleEditStatusSubmit = async () => { const handleEditStatusSubmit = async () => {
statusLoading.value = true; statusLoading.value = true
try { try {
await editStatusFormRef.value.validate() await editStatusFormRef.value.validate()
const res = await changePolicyStatus({ const res = await changePolicyStatus({
...@@ -150,16 +205,16 @@ const handleEditStatusSubmit = async () => { ...@@ -150,16 +205,16 @@ const handleEditStatusSubmit = async () => {
...editStatusFormData.value ...editStatusFormData.value
}) })
if (res.code === 200) { if (res.code === 200) {
statusLoading.value = false; statusLoading.value = false
ElMessage.success('修改状态成功') ElMessage.success('修改状态成功')
editStatusDialogFlag.value = false editStatusDialogFlag.value = false
loadTableData() loadTableData()
} else { } else {
statusLoading.value = false; statusLoading.value = false
ElMessage.error(res.msg || '修改状态失败') ElMessage.error(res.msg || '修改状态失败')
} }
} catch (error) { } catch (error) {
statusLoading.value = false; statusLoading.value = false
console.error('修改状态失败', error) console.error('修改状态失败', error)
} }
} }
...@@ -167,7 +222,7 @@ const handleEditStatusSubmit = async () => { ...@@ -167,7 +222,7 @@ const handleEditStatusSubmit = async () => {
// 查看详情、更新数据 // 查看详情、更新数据
const viewDetailDialogFlag = ref(false) const viewDetailDialogFlag = ref(false)
const onSubmit = async (data) => { const onSubmit = async data => {
console.log('提交的数据:', data) console.log('提交的数据:', data)
// 调用 API 保存 // 调用 API 保存
// alert('提交成功!') // alert('提交成功!')
...@@ -193,24 +248,26 @@ const onSubmit = async (data) => { ...@@ -193,24 +248,26 @@ const onSubmit = async (data) => {
const res = await updatePolicyfollow(params) const res = await updatePolicyfollow(params)
if (res.code === 200) { if (res.code === 200) {
ElMessage.success(data.activeTab === 'basic' ? '保存基本信息成功' : '保存产品计划成功') ElMessage.success(data.activeTab === 'basic' ? '保存基本信息成功' : '保存产品计划成功')
loadTableData() loadTableData()
// viewDetailDialogFlag.value = false // viewDetailDialogFlag.value = false
} else { } else {
ElMessage.error(res.msg || (data.activeTab === 'basic' ? '保存基本信息失败' : '保存产品计划失败')) ElMessage.error(
res.msg || (data.activeTab === 'basic' ? '保存基本信息失败' : '保存产品计划失败')
)
} }
} else if (data.activeTab === 'productPlan') { } else if (data.activeTab === 'productPlan') {
params = { params = {
policyBizId: selectedRow.value.policyBizId, policyBizId: selectedRow.value.policyBizId,
apiProductPlanMainInfoDto:{...data}, apiProductPlanMainInfoDto: { ...data },
activeTab: undefined activeTab: undefined
} }
const res = await updatePolicyProduct(params) const res = await updatePolicyProduct(params)
if (res.code === 200) { if (res.code === 200) {
ElMessage.success('保存产品计划成功') ElMessage.success('保存产品计划成功')
loadTableData() loadTableData()
// viewDetailDialogFlag.value = false // viewDetailDialogFlag.value = false
} else { } else {
ElMessage.error(res.msg || ('保存产品计划失败')) ElMessage.error(res.msg || '保存产品计划失败')
} }
} else if (data.activeTab === 'introducer') { } else if (data.activeTab === 'introducer') {
params = { params = {
...@@ -225,8 +282,7 @@ const onSubmit = async (data) => { ...@@ -225,8 +282,7 @@ const onSubmit = async (data) => {
} else { } else {
ElMessage.error(res.msg || '保存转介人失败') ElMessage.error(res.msg || '保存转介人失败')
} }
} else if (data.activeTab === 'firstPayment') {
} else if (data.activeTab === 'firstPayment') {
try { try {
params = { params = {
policyBizId: selectedRow.value.policyBizId, policyBizId: selectedRow.value.policyBizId,
...@@ -244,15 +300,13 @@ const onSubmit = async (data) => { ...@@ -244,15 +300,13 @@ const onSubmit = async (data) => {
console.error('首期缴费表单验证失败', error) console.error('首期缴费表单验证失败', error)
return return
} }
} else if (data.activeTab === 'attachment') {
}else if(data.activeTab === 'attachment'){ viewDetailDialogFlag.value = false
viewDetailDialogFlag.value = false
} }
} }
// 处理转介人数据格式 // 处理转介人数据格式
const normalizeIntroducerData = (data) => { const normalizeIntroducerData = data => {
if (Array.isArray(data)) return data if (Array.isArray(data)) return data
if (data && typeof data === 'object') { if (data && typeof data === 'object') {
const keys = Object.keys(data).filter(k => /^\d+$/.test(k)) const keys = Object.keys(data).filter(k => /^\d+$/.test(k))
...@@ -292,6 +346,7 @@ const handleReset = () => { ...@@ -292,6 +346,7 @@ const handleReset = () => {
loadTableData() loadTableData()
} }
const handleQuery = async () => { const handleQuery = async () => {
currentPage.value = 1
loadTableData() loadTableData()
} }
const visibleDefaultButtons = ref(['reset', 'query']) const visibleDefaultButtons = ref(['reset', 'query'])
...@@ -310,11 +365,11 @@ const operationBtnList = ref([ ...@@ -310,11 +365,11 @@ const operationBtnList = ref([
]) ])
// 分页事件 // 分页事件
const handleSizeChange = (val) => { const handleSizeChange = val => {
pageSize.value = val pageSize.value = val
loadTableData() loadTableData()
} }
const handleCurrentChange = (val) => { const handleCurrentChange = val => {
currentPage.value = val currentPage.value = val
loadTableData() loadTableData()
} }
...@@ -338,7 +393,8 @@ const searchConfig = ref([ ...@@ -338,7 +393,8 @@ const searchConfig = ref([
type: 'input', type: 'input',
prop: 'appointmentNo', prop: 'appointmentNo',
label: '预约编号' label: '预约编号'
}, { },
{
type: 'daterange', type: 'daterange',
prop: 'signDate', prop: 'signDate',
label: '签单日', label: '签单日',
...@@ -364,10 +420,11 @@ const searchConfig = ref([ ...@@ -364,10 +420,11 @@ const searchConfig = ref([
valueKey: 'insuranceCompanyBizId', valueKey: 'insuranceCompanyBizId',
labelKey: 'fullName', labelKey: 'fullName',
multiple: true, multiple: true,
transform: (res) => { transform: res => {
return res?.data.records || [] return res?.data.records || []
} }
}, { },
{
type: 'select', type: 'select',
prop: 'productLaunchBizIdList', prop: 'productLaunchBizIdList',
label: '产品计划', label: '产品计划',
...@@ -375,28 +432,50 @@ const searchConfig = ref([ ...@@ -375,28 +432,50 @@ const searchConfig = ref([
keywordField: 'productName', keywordField: 'productName',
requestParams: { requestParams: {
tenantBizId: userStore.projectInfo.tenantBizId || '', tenantBizId: userStore.projectInfo.tenantBizId || '',
projectBizId: userStore.projectInfo.projectBizId || '', fieldBizId: 'field_olk1qZe81qHHKXbw', fieldValueBizId: 'field_value_uOfJH5ucA2YwJpbn', pageNo: 1, pageSize: 20 projectBizId: userStore.projectInfo.projectBizId || '',
fieldBizId: 'field_olk1qZe81qHHKXbw',
fieldValueBizId: 'field_value_uOfJH5ucA2YwJpbn',
pageNo: 1,
pageSize: 20
}, },
placeholder: '输入产品计划名称搜索', placeholder: '输入产品计划名称搜索',
debounceWait: 500, // 自定义防抖时间 debounceWait: 500, // 自定义防抖时间
valueKey: 'productLaunchBizId', valueKey: 'productLaunchBizId',
labelKey: 'productName', labelKey: 'productName',
multiple: true, multiple: true,
transform: (res) => { transform: res => {
return res?.data.records || [] return res?.data.records || []
} }
}, { },
{
type: 'input', type: 'input',
prop: 'issueNumber', prop: 'issueNumber',
label: '缴费年期', label: '缴费年期',
inputType: 'decimal', inputType: 'decimal',
rules: [ rules: [{ pattern: /^\d+$/, message: '只能输入正整数', trigger: 'blur' }]
{ pattern: /^\d+$/, message: '只能输入正整数', trigger: 'blur' } },
] {
type: 'input',
prop: 'policyHolder',
label: '投保人'
},
{
type: 'input',
prop: 'signer',
label: '签单员'
},
{
type: 'input',
prop: 'brokerName',
label: '转介人'
},
{
type: 'input',
prop: 'insured',
label: '受保人'
} }
]) ])
const loadTableData = async () => { const loadTableData = async () => {
loading.value = true loading.value = true
searchParams.value = searchFormRef.value.getFormData() || {} searchParams.value = searchFormRef.value.getFormData() || {}
...@@ -421,13 +500,12 @@ const loadTableData = async () => { ...@@ -421,13 +500,12 @@ const loadTableData = async () => {
} }
} }
// 表格操作菜单 // 表格操作菜单
const dropdownItems = [ const dropdownItems = [
{ label: '查看保单详情', value: 'viewDetail' }, { label: '查看保单详情', value: 'viewDetail' },
{ label: '补充保单信息', value: 'updateData' }, { label: '补充保单信息', value: 'updateData' },
{ label: '生成签单报告', value: 'generateReport' }, { label: '生成签单报告', value: 'generateReport' },
{ label: '设置新单状态', value: 'setNewSingleStatus' }, { label: '设置新单状态', value: 'setNewSingleStatus' }
// { label: '查看关联', value: 'viewRelated' }, // { label: '查看关联', value: 'viewRelated' },
// { label: '查看记录', value: 'viewRecord' }, // { label: '查看记录', value: 'viewRecord' },
] ]
...@@ -449,15 +527,15 @@ const handleSelect = async (e, row) => { ...@@ -449,15 +527,15 @@ const handleSelect = async (e, row) => {
viewDetailDialogFlag.value = true viewDetailDialogFlag.value = true
break break
case 'setNewSingleStatus': case 'setNewSingleStatus':
if (selectedRow.value.status === '生效') { if (selectedRow.value.status === '生效') {
ElMessage.warning('保单已生效,不能更新新单状态') ElMessage.warning('保单已生效,不能更新新单状态')
return return
} }
editStatusDialogFlag.value = true editStatusDialogFlag.value = true
editStatusFormData.value = { editStatusFormData.value = {
status: row.status, status: row.status,
effectiveDate:row.effectiveDate, effectiveDate: row.effectiveDate,
underwritingDate:row.underwritingDate underwritingDate: row.underwritingDate
} }
break break
case 'viewRelated': case 'viewRelated':
...@@ -474,7 +552,7 @@ const handleSelect = async (e, row) => { ...@@ -474,7 +552,7 @@ const handleSelect = async (e, row) => {
} }
} }
const generateReport = async (row) => { const generateReport = async row => {
if (!selectedRow.value) { if (!selectedRow.value) {
ElMessage.warning('请选择要生成报告的新单') ElMessage.warning('请选择要生成报告的新单')
return return
...@@ -483,14 +561,10 @@ const generateReport = async (row) => { ...@@ -483,14 +561,10 @@ const generateReport = async (row) => {
const response = await policyFollowReport(row.policyBizId) const response = await policyFollowReport(row.policyBizId)
// 文件名设置为应收款导出_yyyy-MM-dd hh:mm:ss.xlsx,不需要-,用字符串 // 文件名设置为应收款导出_yyyy-MM-dd hh:mm:ss.xlsx,不需要-,用字符串
const fileName = `签单报告_${new Date().toLocaleString().replace(/\//g, '').replace(/:/g, '').replace(/\s/g, '')}.pdf` const fileName = `签单报告_${new Date().toLocaleString().replace(/\//g, '').replace(/:/g, '').replace(/\s/g, '')}.pdf`
await safeDownload( await safeDownload(response, fileName, 'application/pdf;charset=utf-8')
response,
fileName,
'application/pdf;charset=utf-8'
)
if (res.code === 200) { if (res.code === 200) {
ElMessage.success('报告生成成功') ElMessage.success('报告生成成功')
}else{ } else {
ElMessage.error(res.msg || '报告生成失败') ElMessage.error(res.msg || '报告生成失败')
} }
...@@ -529,7 +603,7 @@ const saveMailingInfoapi = async () => { ...@@ -529,7 +603,7 @@ const saveMailingInfoapi = async () => {
mailingMethod: postalFormData.value.mailingMethod, mailingMethod: postalFormData.value.mailingMethod,
deliveryNo: postalFormData.value.deliveryNo, deliveryNo: postalFormData.value.deliveryNo,
brokerSignDate: postalFormData.value.brokerSignDate, brokerSignDate: postalFormData.value.brokerSignDate,
customerSignDate: postalFormData.value.customerSignDate, customerSignDate: postalFormData.value.customerSignDate
} }
const res = await savePostalInfo(params) const res = await savePostalInfo(params)
if (res.code === 200) { if (res.code === 200) {
...@@ -540,16 +614,8 @@ const saveMailingInfoapi = async () => { ...@@ -540,16 +614,8 @@ const saveMailingInfoapi = async () => {
} catch (error) { } catch (error) {
console.error('保存邮寄信息失败', error) console.error('保存邮寄信息失败', error)
} finally { } finally {
} }
} }
</script> </script>
<style scoped></style> <style scoped></style>
\ No newline at end of file
...@@ -157,15 +157,14 @@ ...@@ -157,15 +157,14 @@
/> />
<el-table-column fixed="right" label="操作" min-width="120"> <el-table-column fixed="right" label="操作" min-width="120">
<template #default="scope"> <template #default="scope">
<div style="display: flex; gap: 8px;"> <div style="display: flex; gap: 8px">
<!-- 👇 新增:查看按钮 --> <!-- 👇 新增:查看按钮 -->
<!-- <el-button <!-- <el-button
link link
type="primary" type="primary"
size="small" size="small"
@click="viewFile(scope.row)" @click="viewFile(scope.row)"
:disabled="!scope.row.fileUrl" :disabled="!scope.row.fileUrl"
> >
查看 查看
</el-button> --> </el-button> -->
...@@ -183,7 +182,6 @@ ...@@ -183,7 +182,6 @@
</el-button> </el-button>
</template> </template>
</el-popconfirm> </el-popconfirm>
</div> </div>
</template> </template>
</el-table-column> </el-table-column>
...@@ -209,14 +207,14 @@ ...@@ -209,14 +207,14 @@
:showClose="true" :showClose="true"
@close="handleDialogClose" @close="handleDialogClose"
> >
<FileUploader <FileUploader
:tenant-biz-id="userStore.projectInfo.tenantBizId" :tenant-biz-id="userStore.projectInfo.tenantBizId"
:project-biz-id="userStore.projectInfo.projectBizId" :project-biz-id="userStore.projectInfo.projectBizId"
:object-biz-id="props.policyBizId" :object-biz-id="props.policyBizId"
:object-type="'DOCUMENT'" :object-type="'DOCUMENT'"
:project-type="'pc'" :project-type="'pc'"
:upload-concurrency="3" :upload-concurrency="3"
ref="uploaderRef" ref="uploaderRef"
/> />
<!-- <FileUpload <!-- <FileUpload
v-model="files" v-model="files"
...@@ -239,7 +237,7 @@ import CommonDialog from '@/components/commonDialog' ...@@ -239,7 +237,7 @@ import CommonDialog from '@/components/commonDialog'
import { ElMessage, ElMessageBox } from 'element-plus' import { ElMessage, ElMessageBox } from 'element-plus'
import { Delete, Edit, Search, Share, Upload } from '@element-plus/icons-vue' import { Delete, Edit, Search, Share, Upload } from '@element-plus/icons-vue'
import { getPolicyfollow, getProductList } from '@/api/sign/underwritingMain' import { getPolicyfollow, getProductList } from '@/api/sign/underwritingMain'
import { uploadOssFileList,delUploadFile } from '@/api/common' import { uploadOssFileList, delUploadFile } from '@/api/common'
import { getProcessDetail } from '@/api/sign/fna' import { getProcessDetail } from '@/api/sign/fna'
import { premiumReconciliationList } from '@/api/sign/policy' import { premiumReconciliationList } from '@/api/sign/policy'
import { loadDicts, getDictLabel } from '@/utils/useDict' import { loadDicts, getDictLabel } from '@/utils/useDict'
...@@ -247,7 +245,7 @@ import { getNowTime, formatToDate, formatToDateTime } from '@/utils/date' ...@@ -247,7 +245,7 @@ import { getNowTime, formatToDate, formatToDateTime } from '@/utils/date'
import EditableTable from '@/components/csf-common/EditableTable.vue' import EditableTable from '@/components/csf-common/EditableTable.vue'
import useUserStore from '@/store/modules/user' import useUserStore from '@/store/modules/user'
import FileUploader from '@/components/LargeFileUploader/index.vue'; import FileUploader from '@/components/LargeFileUploader/index.vue'
const { proxy } = getCurrentInstance() const { proxy } = getCurrentInstance()
proxy.useDictLists([ proxy.useDictLists([
...@@ -289,17 +287,17 @@ const props = defineProps({ ...@@ -289,17 +287,17 @@ const props = defineProps({
default: '' default: ''
} }
}) })
const uploaderRef = ref(null); const uploaderRef = ref(null)
// 【关键】当弹窗关闭时,强制清空上传组件列表 // 【关键】当弹窗关闭时,强制清空上传组件列表
const handleDialogClose = () => { const handleDialogClose = () => {
if (uploaderRef.value) { if (uploaderRef.value) {
// 调用子组件暴露的清空方法 // 调用子组件暴露的清空方法
// 你需要在子组件中使用 defineExpose 暴露 clearList 方法 // 你需要在子组件中使用 defineExpose 暴露 clearList 方法
uploaderRef.value.clearList(); uploaderRef.value.clearList()
fileUploadDialogFlag.value = false fileUploadDialogFlag.value = false
getAttachmentListDetail(props.policyBizId) getAttachmentListDetail(props.policyBizId)
} }
}; }
const emit = defineEmits(['update:modelValue', 'submit', 'cancel', 'saveRow']) const emit = defineEmits(['update:modelValue', 'submit', 'cancel', 'saveRow'])
const introducerTableData = ref([]) const introducerTableData = ref([])
...@@ -317,9 +315,9 @@ const introducerConfig = [ ...@@ -317,9 +315,9 @@ const introducerConfig = [
broker: 'realName', // 选中后自动填 broker = raw.realName broker: 'realName', // 选中后自动填 broker = raw.realName
brokerName: 'realName', brokerName: 'realName',
internalCode: 'internalNumber', internalCode: 'internalNumber',
team: 'deptName', team: 'teamName',
phone: 'phone', phone: 'phone',
teamBizId:'deptBizId' teamBizId: 'teamBizId'
}, },
transform: res => { transform: res => {
return (res?.data.records || []).map(item => ({ return (res?.data.records || []).map(item => ({
...@@ -541,7 +539,7 @@ const policyInfoFormConfig = ref([ ...@@ -541,7 +539,7 @@ const policyInfoFormConfig = ref([
type: 'select', type: 'select',
prop: 'professionalInvestor', prop: 'professionalInvestor',
label: '专业投资者', label: '专业投资者',
defaultValue :'No', defaultValue: 'No',
options: [ options: [
{ label: '是', value: 'Yes' }, { label: '是', value: 'Yes' },
{ label: '否', value: 'No' } { label: '否', value: 'No' }
...@@ -692,7 +690,8 @@ const basicPlanFormConfig = ref([ ...@@ -692,7 +690,8 @@ const basicPlanFormConfig = ref([
categoryCodeList: [basicPlanFormData.value.insuranceCategoryCode || ''], categoryCodeList: [basicPlanFormData.value.insuranceCategoryCode || ''],
insuranceCompanyBizIdList: [basicPlanFormData.value.insuranceCompanyBizId || ''], insuranceCompanyBizIdList: [basicPlanFormData.value.insuranceCompanyBizId || ''],
pageNo: 1, pageNo: 1,
pageSize: 20 pageSize: 20,
productName: 'productName'
}), }),
placeholder: '请选择产品名称', placeholder: '请选择产品名称',
debounceWait: 500, debounceWait: 500,
...@@ -1042,7 +1041,9 @@ const activeTab = ref('basic') ...@@ -1042,7 +1041,9 @@ const activeTab = ref('basic')
const handleBeforeLeave = async (newTabName, oldTabName) => { const handleBeforeLeave = async (newTabName, oldTabName) => {
// console.log('切换前确认-----------------------', newTabName, oldTabName) // console.log('切换前确认-----------------------', newTabName, oldTabName)
// console.log(tabDirty.value) // console.log(tabDirty.value)
if(props.mode === 'viewDetail'){return;} if (props.mode === 'viewDetail') {
return
}
if (tabDirty.value[oldTabName]) { if (tabDirty.value[oldTabName]) {
try { try {
await ElMessageBox.confirm(`“${getTabLabel(oldTabName)}” 未提交,确定要切换吗?`, '提示', { await ElMessageBox.confirm(`“${getTabLabel(oldTabName)}” 未提交,确定要切换吗?`, '提示', {
...@@ -1127,7 +1128,7 @@ const handleSubmit = () => { ...@@ -1127,7 +1128,7 @@ const handleSubmit = () => {
activeTab: activeTab.value, activeTab: activeTab.value,
...localData.additionalPlans ...localData.additionalPlans
}) })
}else if (activeTab.value === 'attachment'){ } else if (activeTab.value === 'attachment') {
tabDirty.value.attachment = false tabDirty.value.attachment = false
emit('submit', { emit('submit', {
activeTab: activeTab.value activeTab: activeTab.value
...@@ -1141,17 +1142,16 @@ const handleCancel = () => { ...@@ -1141,17 +1142,16 @@ const handleCancel = () => {
emit('cancel') emit('cancel')
} }
// 删除附件 // 删除附件
const deleteFile = (row)=>{ const deleteFile = row => {
console.log(row) console.log(row)
const fileBizId = row.fileBizId || ''; const fileBizId = row.fileBizId || ''
delUploadFile(fileBizId,userStore.projectInfo.projectBizId).then(res=>{ delUploadFile(fileBizId, userStore.projectInfo.projectBizId).then(res => {
console.log(res) console.log(res)
if(res.code===200){ if (res.code === 200) {
getAttachmentListDetail(props.policyBizId) getAttachmentListDetail(props.policyBizId)
}else{ } else {
ElMessage.error('删除文件失败') ElMessage.error('删除文件失败')
} }
}) })
} }
......
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