Commit ac965f5d by zhangxingmin

Merge remote-tracking branch 'origin/test' into test

parents 6e15fd7c 5f8ce1c2
......@@ -37,6 +37,7 @@
"lodash": "^4.18.1",
"nprogress": "0.2.0",
"p-limit": "^7.3.0",
"pdfjs-dist": "^5.7.284",
"pinia": "3.0.2",
"spark-md5": "^3.0.2",
"splitpanes": "^4.0.4",
......
This source diff could not be displayed because it is too large. You can view the blob instead.
......@@ -510,8 +510,8 @@ export function newQueryCommissionExpectedByPage(data) {
// 应收款管理修改应收记录状态
export function CommissionExpectedChangeStatus(data) {
return request({
url: 'csf/api/CommissionExpected/change_status',
method: 'post',
url: 'csf/api/CommissionExpected/edit/status',
method: 'put',
data: data
})
}
......@@ -547,3 +547,12 @@ export function editStatusApi(data) {
data: data
})
}
//出账检核--修改结算汇率
export function editExchangeRateApi(data) {
return request({
url: 'csf/api/fortune/edit/exchange_rate',
method: 'post',
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'
// ---------- 内部状态 ----------
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>
......@@ -146,44 +146,6 @@
@change="val => handleModelChange(val, item)"
/>
<!-- Upload 回显值得时候数据格式至少是[{url: '必须要传', name: 'name不是必须的根据需要传值'}]-->
<!-- <el-upload
v-else-if="item.type === 'upload'"
v-model:file-list="localModel[item.prop]"
:action="item.action"
:headers="item.headers"
:multiple="!!item.multiple"
:limit="item.limit || (item.multiple ? 999 : 1)"
:accept="item.accept"
:list-type="item.listType || 'text'"
:disabled="item.disabled"
:auto-upload="true"
:show-file-list="item.showFileList"
:on-exceed="handleExceed"
: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> -->
<!-- Upload 回显值时数据格式至少是 [{url: '必须', name: '可选'}] -->
<template v-else-if="item.type === 'upload'">
<!-- 🔽 默认模式:完整使用 el-upload(含自带文件列表) -->
<el-upload
......@@ -325,14 +287,12 @@ import useDictStore from '@/store/modules/dict'
import { getDicts } from '@/api/system/dict/data'
import request from '@/utils/request'
import dayjs from 'dayjs'
// ==================== 上传文件 ====================
// ==================== 文件预览弹窗 ====================
const previewDialogVisible = ref(false)
const previewUrl = ref('')
const previewFileName = ref('')
const previewFileType = ref('') // 'image', 'pdf', 'unsupported'
//新增文件上传自定义方法开始
// 预览文件(支持图片和PDF)
// 预览文件(页面内弹窗,不打开新窗口)
function previewFile(file, item) {
......@@ -364,7 +324,6 @@ function removeFile(file, item) {
const newList = fileList.filter(f => f.uid !== file.uid)
handleUploadRemove(file, newList, item) // 调用原有的删除处理函数
}
//新增文件上传自定义方法结束
// 文件大小格式化
function formatFileSize(bytes) {
......@@ -374,6 +333,7 @@ function formatFileSize(bytes) {
const i = Math.floor(Math.log(bytes) / Math.log(k))
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]
}
function beforeUpload(file, item) {
// 检查文件大小
if (item.maxSize && file.size > item.maxSize) {
......@@ -393,10 +353,8 @@ function beforeUpload(file, item) {
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 url = data.url || data.fileUrl || data.path
const name = data.name || data.fileName || file.name
......@@ -416,9 +374,8 @@ function handleUploadSuccess(response, file, fileList, item) {
// 触发 model 更新
handleModelChange([...fileList], item)
ElMessage.success(`文件 ${file.name} 上传成功`)
// console.log('上传成功', item)
}
function handleExceed(files, fileList) {
ElMessage.warning('超出文件数量限制')
}
......@@ -432,6 +389,7 @@ function handleUploadRemove(file, fileList, item) {
// 用户删除文件时,同步更新 model
handleModelChange([...fileList], item)
}
// ==================== 工具函数:深拷贝配置(保留函数) ====================
function deepCloneConfig(obj) {
if (obj === null || typeof obj !== 'object') return obj
......@@ -445,6 +403,7 @@ function deepCloneConfig(obj) {
}
return cloned
}
function parseToDate(str) {
if (!str) return null
if (str === 'today') {
......@@ -459,6 +418,7 @@ function parseToDate(str) {
}
return null
}
// ==================== 生成 disabledDate 函数 ====================
function getDisabledDateFn(item) {
const { minDate, maxDate } = item
......@@ -503,6 +463,7 @@ function getDisabledDateFn(item) {
return false
}
}
// ==================== Props & Emits ====================
const props = defineProps({
modelValue: { type: Object, default: () => ({}) },
......@@ -520,9 +481,7 @@ const emit = defineEmits([
// ==================== Refs ====================
const formRef = ref(null)
// 使用 shallowRef 避免深层响应式(表单通常扁平)
const localModel = ref({ ...props.modelValue })
// 记录哪些字段的字典已加载
const dictLoaded = ref(new Set())
const internalConfig = ref([])
const remoteOptions = ref({}) // { prop: [options] }
......@@ -546,7 +505,31 @@ const formRules = computed(() => {
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
return true
}
}
return false
}
// 监听 config 变化(支持动态 config)
watch(
() => props.config,
......@@ -559,7 +542,6 @@ watch(
const initialModel = {}
for (const item of internalConfig.value) {
const key = item.prop
// 优先用父传值,否则用默认值
if (props.modelValue?.[key] !== undefined) {
initialModel[key] = props.modelValue[key]
} else if (
......@@ -572,27 +554,22 @@ watch(
}
}
// ✅ 在这里同步 modelValue(包括 extra 字段)
localModel.value = syncModelFromProps(props.modelValue, internalConfig.value)
// console.log('子组件监测config变化', localModel.value)
},
{ immediate: true }
)
// console.log('🚀 子组件 props.modelValue 初始值:', props.modelValue)
// 监听 modelValue(用于后续外部更新)
watch(
() => props.modelValue,
newVal => {
if (!newVal || !internalConfig.value) return
// ✅ 同样使用 sync 函数
localModel.value = syncModelFromProps(newVal, internalConfig.value)
// console.log('子组件监测 modelValue 变化:', localModel.value)
},
{ deep: true }
)
// 提取同步逻辑
// 从 props 同步模型数据
function syncModelFromProps(newModelValue, newConfig) {
if (!newModelValue || !newConfig) return {}
......@@ -614,22 +591,17 @@ function syncModelFromProps(newModelValue, newConfig) {
if (!extraMap || typeof extraMap !== 'object') continue
const prop = item.prop
const idValue = newModelValue[prop] // e.g. 2
const idValue = newModelValue[prop]
let sourceObj = null
// 情况1: 如果 newModelValue[prop] 本身就是对象 → 直接用(兼容旧逻辑)
if (idValue && typeof idValue === 'object') {
sourceObj = idValue
}
// 情况2: 如果是 primitive(string/number),且有 options → 反查
else if (Array.isArray(item.options) && idValue !== undefined && idValue !== null) {
// 默认用 option.value 匹配,可配置 valueKey
} else if (Array.isArray(item.options) && idValue !== undefined && idValue !== null) {
const valueKey = item.valueKey || 'value'
sourceObj = item.options.find(opt => opt[valueKey] === idValue)
}
// 如果找到了 sourceObj,就提取 extra 字段
if (sourceObj && typeof sourceObj === 'object') {
for (const [targetKey, subPath] of Object.entries(extraMap)) {
const val = getNestedValue(sourceObj, subPath)
......@@ -646,9 +618,7 @@ function syncModelFromProps(newModelValue, newConfig) {
const extraMap = item.onChangeExtraFields
if (!extraMap || typeof extraMap !== 'object') continue
// 如果 newModelValue 中没有 sourceField,说明没有重新计算
if (newModelValue[sourceField] === undefined) {
// 那么保留 localModel 中对应的 extra 字段
for (const [targetKey, subPath] of Object.entries(extraMap)) {
if (localModel.value.hasOwnProperty(targetKey)) {
synced[targetKey] = localModel.value[targetKey]
......@@ -659,79 +629,60 @@ function syncModelFromProps(newModelValue, newConfig) {
// 4. 保留 newModelValue 中已有的 extra 字段和其他额外字段
for (const key in newModelValue) {
// 如果已经同步过了(比如主字段或第2步写入的extra),跳过
if (synced.hasOwnProperty(key)) continue
// 判断是否是某个 extra 目标字段
const isExtraTarget = newConfig.some(
item => item.onChangeExtraFields && item.onChangeExtraFields.hasOwnProperty(key)
)
// 如果是 extra 字段,且 newModelValue 里有值 → 保留它!
if (isExtraTarget) {
synced[key] = newModelValue[key]
}
// 如果不是 extra,也不是主字段 → 也保留(兼容 hidden 字段等)
else if (!newConfig.some(item => item.prop === key)) {
} else if (!newConfig.some(item => item.prop === key)) {
synced[key] = newModelValue[key]
}
}
// console.log('🚀 子组件 进行modelvalue处理:', synced)
return synced
}
function getNestedValue(obj, path) {
return path.split('.').reduce((current, key) => current?.[key], obj)
}
// 当字典加载完成时,触发同步
function markDictLoaded(prop) {
dictLoaded.value.add(prop)
// 尝试同步该字段
if (props.modelValue?.[prop] !== undefined) {
localModel.value[prop] = props.modelValue[prop]
}
}
// 2. 用户操作导致 localModel 变化时,emit(防抖可选)
// 用户操作导致 localModel 变化时,emit
function handleModelChange(value, item) {
console.group('用户操作导致 localModel 变化时,emit(防抖可选)')
const newModel = { ...localModel.value, [item.prop]: value }
if (item?.type === 'select' && item.onChangeExtraFields) {
const options = getSelectOptions(item)
// console.log('可用 options:', options)
const opt = options.find(o => o.value === value)
// console.log('匹配的 opt:', opt)
const opt = options.find(o => String(o.value) === String(value))
if (opt?.raw) {
for (const [targetProp, sourceKey] of Object.entries(item.onChangeExtraFields)) {
const extraValue = opt.raw[sourceKey]
newModel[targetProp] = extraValue
// console.log(`✅ 设置 ${targetProp} =`, extraValue)
newModel[targetProp] = opt.raw[sourceKey]
}
}
}
localModel.value = newModel
// console.log('子组件用户操作后,modelvalue值==', newModel)
nextTick(() => {
if (!isEqualShallow(props.modelValue, newModel)) {
// console.log('如果新旧值不一样,反馈给父组件', newModel)
emit('update:modelValue', newModel)
} else {
console.log('🚫 跳过 emit:认为相等')
}
})
if (item.type === 'select') {
// console.log('如果是select类型,反馈给父组件', item.prop, value, item)
emit('selectChange', item.prop, value, item)
} else if (item.type == 'upload') {
// 传给父组件最新的上传值newModel
emit('uploadSuccess', item.prop, newModel)
} else if (item.type == 'input') {
emit('inputChange', item.prop, value, item)
}
console.groupEnd()
}
// 辅助函数:浅比较两个对象
function isEqualShallow(a, b) {
const keysA = Object.keys(a)
const keysB = Object.keys(b)
......@@ -761,84 +712,34 @@ async function loadDictOptions(dictType) {
dictStore.setDict(dictType, options)
return options
} catch (err) {
// console.error(`加载字典 ${dictType} 失败`, err)
ElMessage.error(`字典 ${dictType} 加载失败`)
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 选项 ====================
function getSelectOptions(item) {
const key = item.prop
// 字典选项
if (item.dictType || item.api) {
// 字典选项
return (remoteOptions.value[key] || []).map(opt => ({
value: opt.value,
label: opt.label,
raw: opt.raw // ← 必须存 raw
raw: opt.raw
}))
} else if (item.options) {
// 静态选项s
return item.options.map(opt => ({
value: opt.value,
label: opt.label,
raw: opt.raw // 保留原始
raw: opt.raw
}))
}
return []
}
// 初始化加载远程 API 选项(无搜索词)
async function loadRemoteOptionsForInit(item) {
const { prop, api, requestParams } = item
try {
// 构造请求体:只传 requestParams,不传 keyword
const payload = {
...(typeof requestParams === 'function' ? requestParams() : requestParams || {})
}
......@@ -854,38 +755,45 @@ async function loadRemoteOptionsForInit(item) {
? item.transform(res)
: res.data?.records || res.data || []
// 建议:统一转成字符串(或根据 item.valueType 判断)
const newOptions = list.map(i => ({
value: String(i[item.valueKey || 'value']), // ← 强制转字符串
value: String(i[item.valueKey || 'value']),
label: i[item.labelKey || 'label'],
raw: i
}))
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) {
ElMessage.error(`预加载 ${item.label} 失败`)
remoteOptions.value[prop] = []
}
}
// ==================== 加载远程 API 选项(首次 focus 时加载,无搜索词) ====================
// 加载远程 API 选项(focus 时调用,无搜索词)
async function loadRemoteOptions(item) {
const { prop, api, requestParams, keywordField, debounceWait, ...rest } = 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
// 构造请求体:合并 requestParams + 分页(默认第一页)
const payload = {
...(typeof requestParams === 'function' ? requestParams() : requestParams || {})
// 注意:此时无 keyword,所以不加 keywordField
}
const res = await request({
url: api,
method: 'post', // ← 改为 POST
data: payload // ← 参数放 body
method: 'post',
data: payload
})
const list =
......@@ -893,15 +801,19 @@ async function loadRemoteOptions(item) {
? item.transform(res)
: res.data?.records || res.data || []
// 建议:统一转成字符串(或根据 item.valueType 判断)
const newOptions = list.map(i => ({
value: String(i[item.valueKey || 'value']), // ← 强制转字符串
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] = []
......@@ -910,7 +822,7 @@ async function loadRemoteOptions(item) {
}
}
// ==================== 远程搜索(带关键词,防抖) ====================
// 远程搜索(带关键词,防抖)
let searchTimeout = null
function handleFilterChange(keyword, item) {
const { prop, api, requestParams, keywordField = 'keyword', debounceWait = 300 } = item
......@@ -921,16 +833,15 @@ function handleFilterChange(keyword, item) {
try {
remoteLoading.value[prop] = true
// 构造请求体:requestParams + 分页 + 动态关键词字段
const payload = {
...(typeof requestParams === 'function' ? requestParams() : requestParams || {}),
[keywordField]: keyword // ← 动态字段名,如 name / companyName
[keywordField]: keyword
}
const res = await request({
url: api,
method: 'post', // ← POST 请求
data: payload // ← body 传参
method: 'post',
data: payload
})
const list =
......@@ -939,9 +850,9 @@ function handleFilterChange(keyword, item) {
: res.data?.records || res.data || []
remoteOptions.value[prop] = list.map(i => ({
value: i[item.valueKey || 'value'],
value: String(i[item.valueKey || 'value']),
label: i[item.labelKey || 'label'],
raw: i // ← 保存完整对象
raw: i
}))
} catch (err) {
ElMessage.error(`搜索 ${item.label} 失败`)
......@@ -952,46 +863,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) {
const { inputType = 'text', decimalDigits = 2, prop } = item
if (!prop) return
......@@ -999,83 +870,109 @@ function handleNumberInput(value, item) {
let result = String(value ?? '').trim()
if (inputType === 'integer') {
// 只保留数字和负号
result = result.replace(/[^-\d]/g, '')
// 如果有多个负号或者负号不在开头,则移除多余的负号
if ((result.match(/-/g) || []).length > 1) {
result = result.replace(/-/g, '').replace(/^/, '-') // 仅保留一个负号在最前面
result = result.replace(/-/g, '').replace(/^/, '-')
}
} else if (inputType === 'decimalNumber') {
// 可以输入正数,负数,小数
// 1. 只保留数字、小数点和负号
result = result.replace(/[^-\d.]/g, '')
// 2. 处理负号:确保最多只有一个负号且必须在开头
if ((result.match(/-/g) || []).length > 1) {
result = result.replace(/-/g, '') // 移除所有负号
result = result.replace(/-/g, '')
if (result.startsWith('-')) {
result = '-' + result.slice(1) // 确保负号在最前面
result = '-' + result.slice(1)
} else {
result = '-' + result // 如果原本没有负号但需要保留数值,可以省略此步骤
result = '-' + result
}
}
// 3. 去掉开头的小数点(不允许 ".5" → 改为 "0.5" 更好)
if (result.startsWith('.')) {
result = '0' + result
} else if (result.startsWith('-.')) {
result = '-0' + result.slice(2)
}
// 4. 保证最多一个小数点
const parts = result.split('.')
if (parts.length > 2) {
result = parts[0] + '.' + parts.slice(1).join('')
}
// 5. 限制小数位数(但保留结尾的小数点!)
if (result.includes('.')) {
const [intPart, decPart] = result.split('.')
// 如果小数部分超过限制,截断
if (decPart.length > decimalDigits) {
result = intPart + '.' + decPart.slice(0, decimalDigits)
}
// ✅ 不再删除结尾的 '.'
}
} 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 }
}
}
// ==================== 初始化 ====================
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(loadRemoteOptionsForInit(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({
getFormData() {
......@@ -1096,7 +993,7 @@ defineExpose({
if (['checkbox-group', 'daterange', 'monthrange'].includes(item.type) || item.multiple) {
resetData[key] = item.defaultValue ?? []
} else if (item.type === 'upload') {
resetData[key] = item.defaultValue ?? [] // upload 也是数组
resetData[key] = item.defaultValue ?? []
} else {
resetData[key] = item.defaultValue ?? ''
}
......@@ -1104,36 +1001,20 @@ defineExpose({
localModel.value = { ...resetData }
nextTick(() => formRef.value?.clearValidate())
},
// ✅ 新增:强制刷新某个字段的远程选项
async refreshRemoteOptions(targetProp) {
console.log(`[SearchForm] 收到刷新请求: ${targetProp}`)
// 1. 查找配置项
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
}
console.log(`[SearchForm] 开始强制加载 ${targetProp} 的数据,API: ${item.api}`)
// 2. 关键:在调用前,先清空旧数据,防止子组件内部的 "已加载则跳过" 逻辑生效
// 如果你的 loadRemoteOptions 里有 "if (remoteOptions.value[prop]?.length > 0) return"
// 这里必须先清空
remoteOptions.value[targetProp] = []
remoteLoading.value[targetProp] = true // 手动开启 loading
remoteLoading.value[targetProp] = true
try {
// 3. 调用内部加载函数
// 注意:直接调用 loadRemoteOptions,它会读取最新的 requestParams
await loadRemoteOptions(item)
console.log(`[SearchForm] ${targetProp} 加载完成`)
} catch (error) {
console.error(`[SearchForm] ${targetProp} 加载失败`, error)
throw error
......@@ -1145,34 +1026,28 @@ defineExpose({
</script>
<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;
......@@ -1180,14 +1055,12 @@ defineExpose({
.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;
......@@ -1195,11 +1068,9 @@ defineExpose({
padding: 8px 12px;
border-bottom: 1px solid #ebeef5;
}
.file-item:last-child {
border-bottom: none;
}
.file-name {
flex: 1;
font-size: 14px;
......@@ -1209,7 +1080,6 @@ defineExpose({
white-space: nowrap;
margin-right: 16px;
}
.file-actions {
display: flex;
gap: 12px;
......@@ -1217,11 +1087,9 @@ defineExpose({
.formBox {
box-sizing: border-box;
}
.search-form-item {
margin-bottom: 20px;
}
.iconStyle {
color: #409eff;
}
......
......@@ -34,7 +34,7 @@
:type="btn.type || 'primary'"
:icon="btn.icon"
:size="btn.size || 'default'"
@click="handleButtonClick(btn)"
@click.stop="handleButtonClick(btn)"
:disabled="btn.disabled"
:loading="btn.loading"
:class="btn.customClass"
......@@ -54,7 +54,7 @@
:type="btn.type || 'primary'"
:icon="btn.icon"
:size="btn.size || 'default'"
@click="handleButtonClick(btn)"
@click.stop="handleButtonClick(btn)"
:disabled="btn.disabled"
:loading="btn.loading"
:class="btn.customClass"
......@@ -316,6 +316,9 @@ const checkConditions = () => {
// 处理按钮点击
const handleButtonClick = async (btn: OperationButton) => {
console.log('====================================')
console.log('按钮')
console.log('====================================')
// 触发通用按钮点击事件
emit('btn-click', btn)
......@@ -362,6 +365,9 @@ const handleSizeChange = (size: number) => {
const handleCurrentChange = (page: number) => {
currentPage.value = page
console.log('====================================')
console.log('当前页')
console.log('====================================')
emit('current-change', page)
}
......
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 = '内容') {
const textarea = document.createElement('textarea');
textarea.value = text;
// 防止页面滚动或闪烁
textarea.style.position = 'fixed';
textarea.style.opacity = '0';
textarea.style.left = '-9999px';
document.body.appendChild(textarea);
try {
textarea.select();
textarea.setSelectionRange(0, 99999); // 对于移动设备
document.execCommand('copy');
console.log('传统方法复制成功');
ElMessage.success(`${typeName}已复制`)
textarea.setSelectionRange(0, 99999); // 兼容移动端
const successful = document.execCommand('copy');
if (successful) {
ElMessage.success(`${typeName}已复制`);
} else {
throw new Error('execCommand 返回失败');
}
} catch (err) {
console.error('传统方法复制失败:', err)
ElMessage.error('复制失败');
console.error('传统方法复制失败:', err);
ElMessage.error('复制失败,请手动复制');
} finally {
document.body.removeChild(textarea);
}
}
export default {
copyToClipboard
// 主导出函数,接收 text 和 typeName 两个参数
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
......@@ -62,7 +62,9 @@
</el-row>
</div>
<el-table
ref="multipleTableRef"
:data="tableData"
row-key="fortuneBizId"
@selection-change="handleSelectionChange"
height="400"
border
......@@ -70,8 +72,8 @@
style="width: 100%"
v-loading="loading"
>
<el-table-column type="selection" width="40" />
<el-table-column prop="policyNo" label="业务编号" width="120" sortable />
<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) }}
......@@ -103,17 +105,18 @@
sortable
:formatter="row => `${row.commissionPaidRatio ? row.commissionPaidRatio : 0}%`"
/> -->
<!-- <el-table-column
prop="commissionPaidRatio"
<el-table-column
prop="commissionPendingRatio"
label="达成率缺口"
width="120"
sortable
:formatter="row => `${row.commissionPaidRatio ? row.commissionPaidRatio : 0}%`"
/> -->
:formatter="row => `${row.commissionPendingRatio ? row.commissionPendingRatio : 0}%`"
/>
<el-table-column prop="fortuneName" label="出账项目" width="130" sortable />
<el-table-column prop="fortunePeriod" label="出账期数" width="130" sortable />
<el-table-column prop="fortuneTotalPeriod" label="总期数" width="120" sortable />
<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="exchangeRate" label="结算汇率(估)" width="140" sortable />
<el-table-column
......@@ -162,8 +165,8 @@
:formatter="row => formatCurrency(row.premium || 0)"
/>
<el-table-column prop="policyCurrency" 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="payoutDate" 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 fixed="right" label="操作" min-width="120">
<template #default="{ row }">
......@@ -272,7 +275,7 @@
</CommonDialog>
<!-- 设置出账年月弹窗 -->
<CommonDialog
dialogTitle="设置本期出账金额"
dialogTitle="设置本期出账年月(实)"
dialogWidth="80%"
:openDialog="settingBillYearMonthFlag"
:showAction="true"
......@@ -339,7 +342,7 @@ const installmentsBillFlag = ref(false)
const settingBillYearMonthFlag = ref(false)
const { proxy } = getCurrentInstance()
const { csf_fortune_status, sys_no_yes } = proxy.useDict('csf_fortune_status', 'sys_no_yes')
const multipleTableRef = ref(null)
const userStore = useUserStore()
// 分页相关
const currentPage = ref(1)
......@@ -382,7 +385,7 @@ const searchConfig = ref([
},
{
type: 'input',
prop: 'broker',
prop: 'brokerName',
label: '转介人'
},
{
......@@ -433,9 +436,9 @@ const searchConfig = ref([
}
},
{
type: 'daterange',
type: 'monthrange',
prop: 'payoutDate',
label: '出账(估)',
label: '出账(估)',
startPlaceholder: '开始时间',
endPlaceholder: '结束时间'
},
......@@ -451,7 +454,7 @@ const editTableRef = ref(null)
const splitTableColumns = ref([
{
prop: 'splitRatio',
label: '出账比例',
label: '出账比例(%)',
editType: 'input',
inputType: 'decimal', // integer / decimalNumber / decimal
decimalDigits: 2, // 小数位数,默认2
......@@ -541,38 +544,7 @@ const splitTableColumns = ref([
required: true,
width: 150
},
// {
// prop: 'amount',
// label: '原币种金额',
// editType: 'input',
// inputType: 'decimalNumber', // integer / decimalNumber / decimal
// decimalDigits: 2, // 小数位数,默认2
// required: true
// },
// {
// prop: 'originalAmount',
// label: '本次出账原币种金额',
// editType: 'input',
// inputType: 'decimalNumber', // integer / decimalNumber / decimal
// decimalDigits: 2, // 小数位数,默认2
// required: true
// },
// {
// prop: 'exchangeRate',
// label: '结算汇率',
// editType: 'input',
// inputType: 'decimal', // integer / decimalNumber / decimal
// decimalDigits: 2, // 小数位数,默认2
// required: true
// },
// {
// prop: 'hkdAmount',
// label: '港币出账金额',
// editType: 'input',
// inputType: 'decimalNumber', // integer / decimalNumber / decimal
// decimalDigits: 2, // 小数位数,默认2
// required: true
// },
{
prop: 'payoutYearMonth',
label: '出账年月(估)',
......@@ -701,6 +673,11 @@ const fortuneBizTypeOptions = [
{ value: 'R', 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
......@@ -709,9 +686,10 @@ const confirmRateExchange = async () => {
const res = await editExchangeRateApi(formData)
ElMessage.success('结算汇率修改成功')
rateExchangeFlag.value = false
loadTableData()
const params = searchFormRef.value.getFormData()
loadTableData(params)
} catch (error) {
ElMessage.success('结算汇率修改失败')
ElMessage.error('结算汇率修改失败')
rateExchangeFlag.value = true
if (error.message && error.message.includes('Validation')) {
ElMessage.error('必填项不能为空')
......@@ -1005,7 +983,8 @@ const handleSpiltSubmit = async () => {
console.log('分期出账结果', res)
installmentsBillFlag.value = false
ElMessage.success('分期出账已保存')
loadTableData()
const searchParams = searchFormRef.value.getFormData()
loadTableData(searchParams)
} catch (error) {
installmentsBillFlag.value = true
console.log('分期出账错误', error)
......@@ -1038,9 +1017,19 @@ const addSpiltRecord = () => {
id: generateId(), // 必须有 rowIdKey 对应的字段(默认 'id')
payoutCurrency: 'HKD',
ruleCurrency: selectedRow.value.ruleCurrency,
originalCurrency: selectedRow.value.originalCurrency
originalCurrency: selectedRow.value.originalCurrency,
hkdToPayoutRate: '1',
exchangeRate: selectedRow.value.exchangeRate
}
if (selectedRow.value.originalCurrency && selectedRow.value.originalCurrency == 'HKD') {
newRow.originalToHkdRate = '1'
}
// if (selectedRow.value.payoutCurrency && selectedRow.value.payoutCurrency == 'HKD') {
// newRow.hkdToPayoutRate = '1'
// }
console.log('====================================')
console.log('newRow', newRow)
console.log('====================================')
// 插入到表格数据最前面
splitTableData.value.push(newRow)
}
......@@ -1098,12 +1087,20 @@ const addCheckRecordConfig = [
},
{
type: 'date',
type: 'month',
prop: 'payoutDate',
label: '出账日期',
label: '出账月(估)',
placeholder: '请选择',
maxDate: 'today',
rules: [{ required: true, message: '出账月(估)必填', trigger: 'blur' }]
},
{
type: 'month',
prop: 'actualPayoutDate',
label: '出账月(实)',
placeholder: '请选择',
maxDate: 'today',
rules: [{ required: true, message: '出账日期必填', trigger: 'blur' }]
rules: [{ required: true, message: '出账月(实)必填', trigger: 'blur' }]
},
// {
// type: 'input',
......@@ -1173,8 +1170,8 @@ const addCheckRecordConfig = [
},
transform: res => {
return res?.data.records || []
},
rules: [{ required: true, message: '所属团队必填', trigger: 'blur' }]
}
// rules: [{ required: true, message: '所属团队必填', trigger: 'blur' }]
},
{
......@@ -1312,6 +1309,7 @@ const handleReset = () => {
console.log('表单已重置')
}
const handleQuery = async () => {
currentPage.value = 1
const params = searchFormRef.value.getFormData()
loadTableData(params)
}
......@@ -1370,11 +1368,13 @@ loadTableData()
// 分页事件
const handleSizeChange = val => {
pageSize.value = val
loadTableData()
const params = searchFormRef.value.getFormData()
loadTableData(params)
}
const handleCurrentChange = val => {
currentPage.value = val
loadTableData()
const params = searchFormRef.value.getFormData()
loadTableData(params)
}
// 表格数据
const tableData = ref([])
......@@ -1397,15 +1397,13 @@ const handleSelect = (e, row) => {
ruleCurrency: row.ruleCurrency,
originalCurrency: row.originalCurrency,
payoutCurrency: row.payoutCurrency,
hkdToPayoutRate: row.hkdToPayoutRate ? row.hkdToPayoutRate : '',
hkdToPayoutRate: '',
exchangeRate: row.exchangeRate ? row.exchangeRate : '',
originalToHkdRate: row.originalToHkdRate ? row.originalToHkdRate : '',
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
}
}
......@@ -1471,7 +1469,8 @@ const submitSettingBillYearMonth = async () => {
if (res.code === 200) {
settingBillYearMonthFlag.value = false
ElMessage.success('完成检核,等待关账')
loadTableData()
const params = await searchFormRef.value.getFormData()
loadTableData(params)
}
} catch (error) {
console.error('检核失败:', error)
......@@ -1487,7 +1486,8 @@ const submitSettingBillYearMonth = async () => {
const singleRes = await actualPayoutDateApi(params)
ElMessage.success('设置出账年月成功')
settingBillYearMonthFlag.value = false
loadTableData()
const searchParams = await searchFormRef.value.getFormData()
loadTableData(searchParams)
} catch (error) {
ElMessage.error('设置出账年月失败')
settingBillYearMonthFlag.value = true
......@@ -1530,6 +1530,17 @@ const onSubmit = data => {
}
onMounted(async () => {})
// 数据变化时清空选中
watch(
() => tableData,
() => {
nextTick(() => {
multipleTableRef.value?.clearSelection()
multipleSelection.value = []
})
},
{ deep: true }
)
</script>
<style scoped>
......
......@@ -113,13 +113,20 @@
<el-table-column prop="reconciliationYearMonth" 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="commissionDate" label="入账" width="120" sortable />
<el-table-column prop="commissionDate" label="入账" width="120" sortable />
<el-table-column prop="commissionExpectedStatus" label="入账状态" width="120" sortable>
<template #default="{ row }">
{{ selectDictLabel(csf_expected_commission_status, row.commissionExpectedStatus) }}
</template>
</el-table-column>
<el-table-column
prop="commissionRatio"
label="保单本期来佣率"
width="120"
:formatter="formatRatio"
>
</el-table-column>
<el-table-column
prop="currentCommissionRatio"
label="本次实佣率"
width="130"
......@@ -161,12 +168,12 @@
/>
<el-table-column prop="policyCurrency" label="保单币种" width="120" sortable />
<el-table-column prop="productName" label="产品名称" width="120" />
<el-table-column prop="commissionRatio" 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="remark" label="备注" width="120" sortable />
<el-table-column prop="manualRemark" label="备注" width="120" sortable />
<el-table-column prop="isDeleted" label="记录状态" width="120" sortable>
<template #default="{ row }">
{{ row.isDeleted === 1 ? '无效' : '有效' }}
......@@ -263,7 +270,7 @@
<el-row :gutter="10">
<el-col :xs="24" :sm="24" :md="24" :lg="24">
<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 }">
{{ getCommissionBizTypeLabel(row.commissionBizType) }}
</template>
......@@ -277,8 +284,12 @@
</el-table-column>
<el-table-column prop="commissionPeriod" label="佣金期数" width="120" />
<el-table-column prop="totalPeriod" label="总期数" width="120" />
<el-table-column prop="commissionDate" label="入账日(实)" width="120" />
<el-table-column prop="amount" label="入账金额" width="120" />
<el-table-column prop="commissionDate" 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="exchangeRate" label="结算汇率" width="120" />
<el-table-column prop="commissionName" label="入账项目" width="120" />
......@@ -327,7 +338,7 @@
</CommonDialog>
<!-- 新增检核记录弹窗 -->
<CommonDialog
:dialogTitle="editStatus.value == 'add' ? '新增检核记录' : '修改检核记录'"
:dialogTitle="editStatus == 'add' ? '新增检核记录' : '修改检核记录'"
dialogWidth="80%"
:openDialog="addCheckRecordDialogFlag"
:showAction="true"
......@@ -335,6 +346,7 @@
@close="closeDialog()"
@confirm="handleAddCheckRecord()"
>
{{}}
<el-row>
<el-col :xs="24" :sm="24" :md="24" :lg="24">
<SearchForm
......@@ -449,7 +461,7 @@
</template>
<script setup>
import { ref, reactive, onMounted } from 'vue'
import { ref, reactive, onMounted, watch } from 'vue'
import CommonPage from '@/components/commonPage'
import CommonDialog from '@/components/commonDialog'
import SearchForm from '@/components/SearchForm/SearchForm.vue'
......@@ -572,15 +584,15 @@ const searchConfig = ref([
}
},
{
type: 'date',
type: 'month',
prop: 'expectedDate',
label: '入账(估)',
label: '入账(估)',
placeholder: '请选择'
},
{
type: 'daterange',
type: 'monthrange',
prop: 'commissionDate',
label: '入账(实)',
label: '入账(实)',
startPlaceholder: '开始时间',
endPlaceholder: '结束时间'
},
......@@ -657,6 +669,7 @@ const checkRecordEdit = row => {
selectedRowCheck.value = { ...row }
editStatus.value = 'edit'
addReceivablesFormModel.value = { ...row }
// addReceivablesFormModel.value.currency = 'HKD'
addCheckRecordDialogFlag.value = true
console.log('父组件赋值', addReceivablesFormModel.value)
}
......@@ -682,10 +695,9 @@ watch(
const confirmRecordCheck = async () => {
//等待后端接口
try {
// ✅ 统一从子组件获取完整表单数据(含 extra 字段)
// await nextTick() // 确保子组件已同步
const formData = updateDataFormRef.value.getFormData()
console.log('===========', formData)
let params
params = {
...formData, // ←←← 使用 formData,不是 addReceivablesFormModel.value
......@@ -777,12 +789,12 @@ const addCheckRecordConfig = ref([
rules: [{ pattern: /^\d+$/, message: '只能输入正整数', trigger: 'blur' }]
},
{
type: 'date',
type: 'month',
prop: 'commissionDate',
label: '入账(实)',
label: '入账(实)',
placeholder: '请选择',
maxDate: 'today',
rules: [{ required: true, message: '请选择入账(实)', trigger: 'blur' }]
rules: [{ required: true, message: '请选择入账(实)', trigger: 'blur' }]
},
{
type: 'input',
......@@ -800,13 +812,22 @@ const addCheckRecordConfig = ref([
prop: 'currency',
label: '入账币种',
dictType: 'bx_currency_type',
rules: [{ required: true, message: '请选择入账币种', trigger: 'blur' }]
defaultValue: 'HKD',
disabled: true,
rules: [
{
required: true,
message: '请选择入账币种',
trigger: 'blur'
}
]
},
{
type: 'select',
prop: 'commissionType',
label: '入账项目',
dictType: 'csf_commission_type',
defaultValue: '1',
rules: [{ required: true, message: '请选择入账项目', trigger: 'blur' }],
onChangeExtraFields: {
commissionName: 'itemLabel'
......@@ -839,6 +860,7 @@ const addCheckRecordConfig = ref([
label: '结算汇率(实)',
inputType: 'decimal',
decimalDigits: 4,
visible: formData => formData.commissionBizType == 'R',
rules: [
{ required: true, message: '请输入金额', trigger: 'blur' },
{ pattern: /^\d+(\.\d{1,4})?$/, message: '最多四位小数', trigger: 'blur' }
......@@ -846,7 +868,7 @@ const addCheckRecordConfig = ref([
},
{
type: 'input',
prop: 'remark',
prop: 'manualRemark',
label: '备注'
}
])
......@@ -961,8 +983,9 @@ const handleReset = () => {
console.log('表单已重置')
}
const handleQuery = async () => {
currentPage.value = 1
const params = searchFormRef.value.getFormData()
console.log('父组件发起查询:', params)
console.log('查询:', params)
loadTableData(params)
}
......@@ -1022,13 +1045,26 @@ const operationBtnList = ref([
// 加载表格数据
const loadTableData = async (searchParams = {}) => {
loading.value = true
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 = {
pageNo: currentPage.value,
pageSize: pageSize.value,
...searchParams,
commissionDateStart: searchParams.commissionDate?.[0] || undefined,
commissionDateEnd: searchParams.commissionDate?.[1] || undefined,
// commissionDateStart: searchParams.commissionDate?.[0] || undefined,
// commissionDateEnd: searchParams.commissionDate?.[1] || undefined,
commissionDate: undefined
}
const res = await getPolicyCommissionList(params)
......@@ -1048,11 +1084,13 @@ loadTableData()
// 分页事件
const handleSizeChange = val => {
pageSize.value = val
loadTableData()
const params = searchFormRef.value.getFormData()
loadTableData(params)
}
const handleCurrentChange = val => {
currentPage.value = val
loadTableData()
const params = searchFormRef.value.getFormData()
loadTableData(params)
}
// 表格数据
const tableData = ref([])
......@@ -1086,6 +1124,16 @@ const handleSelect = (e, row) => {
}
const handleAddCheckList = () => {
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 }
addCheckRecordDialogFlag.value = true
clearForm('addReceivablesFormModel')
......
......@@ -71,7 +71,7 @@
/> -->
<el-table-column
prop="fortuneAccountMonth"
label="出账月"
label="出账月"
min-width="150"
show-overflow-tooltip
/>
......@@ -442,8 +442,8 @@ const debounceChangeRateMap = new WeakMap()
const debounceChangeToAmountMap = new WeakMap()
// 表格操作菜单
const dropdownItems = [
{ label: '拆分出账', value: 'splitBilling' },
{ label: '设置出账日', value: 'settingSalaryDate' }
{ label: '拆分出账', value: 'splitBilling' }
// { label: '设置出账日', value: 'settingSalaryDate' }
// { label: '查看记录', value: 'viewRecord' }
]
//=============拆分出账开始================
......@@ -733,11 +733,13 @@ const salaryDataSetting = async e => {
// 分页事件
const handleSizeChange = val => {
pageSize.value = val
getList()
const params = searchFormRef.value.getFormData()
getList(params)
}
const handleCurrentChange = val => {
currentPage.value = val
getList()
const params = searchFormRef.value.getFormData()
getList(params)
}
// 设置当前页的选中状态
......@@ -814,7 +816,7 @@ const getList = async (searchParams = {}) => {
payoutDate: undefined,
pageNo: currentPage.value,
pageSize: pageSize.value,
statusList:searchParams.statusList || ['6']
statusList: searchParams.statusList || ['6']
}
const response = await getReferrerFortuneList(params)
......@@ -850,6 +852,7 @@ const getStatusType = status => {
// 查询
const handleQuery = () => {
currentPage.value = 1
const params = searchFormRef.value.getFormData()
console.log('父组件发起查询:', params)
clearAllSelection()
......
......@@ -140,7 +140,12 @@
</el-col>
</el-row>
</div>
<el-table :data="payableReportTableData" border style="width: 100%; margin-bottom: 10px">
<el-table
:data="payableReportTableData"
border
style="width: 100%; margin-bottom: 10px"
:row-class-name="getRowClassName"
>
<el-table-column
v-for="item in payableReportListTableColumns"
:key="item.property"
......@@ -151,7 +156,7 @@
/>
<el-table-column fixed="right" label="操作" min-width="120">
<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>
<el-icon>
<MoreFilled />
......@@ -215,6 +220,25 @@
@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
dialogTitle="设置出账状态"
......@@ -234,7 +258,7 @@
import CommonPage from '@/components/commonPage'
import { ref, reactive, nextTick } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { formatCurrency } from '@/utils/number'
import { formatCurrency, calculateAmount } from '@/utils/number'
import {
expectedFortuneList,
payRecordList,
......@@ -249,7 +273,9 @@ import CommonDialog from '@/components/commonDialog'
import { loadDicts, getDictLabel } from '@/utils/useDict'
import useUserStore from '@/store/modules/user'
import { safeDownload } from '@/utils/safeDownload'
const updatePayRecordFormModel = ref({})
const updatePayRecordFormRef = ref(null)
const updatePayRecordDialogVisible = ref(false)
const payableReportTableData = ref([])
const payableReportTableColumns = ref([
{
......@@ -294,7 +320,7 @@ const payableReportTableColumns = ref([
width: '120',
formatter: row => row.fortuneTotalPeriod || '-'
},
{ prop: 'payoutDate', label: '出账(估)', sortable: true, width: '130' },
{ prop: 'payoutDate', label: '出账年月(估)', sortable: true, width: '130' },
{
prop: 'paidRatio',
label: '已出账比例',
......@@ -373,6 +399,28 @@ const payableReportListTableColumns = ref([
// { prop: 'fortuneBizType', label: '应付款类型', sortable: true, width: '120', formatter: (row) => getFortuneBizTypeLabel(row.fortuneBizType) || '-' },
// { prop: 'payableNo', label: '应付账款编号', sortable: true, width: '120', formatter: (row) => row.payableNo || '-' },
{
prop: 'type',
label: '是否实际出账',
sortable: true,
width: '80',
formatter: row => getTypeLabel(row.type) || '-'
},
{
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,
......@@ -430,53 +478,77 @@ const payableReportListTableColumns = ref([
},
{
prop: 'payoutDate',
label: '出账(估)',
label: '出账年月(估)',
sortable: true,
width: '120',
formatter: row => row.payoutDate || '-'
},
{
prop: 'actualPayoutDate',
label: '出账(实)',
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: '应出账金额(估)',
label: 'HKD应出账金额',
sortable: true,
width: '120',
formatter: row => formatCurrency(row.hkdAmount || 0)
},
{
prop: 'paidRatio',
label: '已出账比例',
prop: 'exchangeRate',
label: '保单币种->HKD汇率',
sortable: true,
width: '120',
formatter: row => (row.paidRatio || 0) + '%' || '-'
width: '140',
formatter: row => formatCurrency(row.exchangeRate || 0)
},
{
prop: 'paidAmount',
label: '已出账金额',
label: '已出账金额(HKD)',
sortable: true,
width: '120',
formatter: row => formatCurrency(row.paidAmount || 0)
},
{
prop: 'unpaidRatio',
label: '待出账比例',
prop: 'unpaidAmount',
label: '待出账金额(估)',
sortable: true,
width: '120',
formatter: row => (row.unpaidRatio || 0) + '%' || '-'
formatter: row => formatCurrency(row.unpaidAmount || 0)
},
{
prop: 'unpaidAmount',
label: '待出账金额(估)',
prop: 'currentPaymentHkdAmount',
label: '本期待出账金额',
sortable: true,
width: '120',
formatter: row => formatCurrency(row.unpaidAmount || 0)
formatter: row => formatCurrency(row.currentPaymentHkdAmount || 0)
},
{
prop: 'exchangeRate',
label: '本期结算汇率(实)',
sortable: true,
width: '120',
formatter: row => formatCurrency(row.exchangeRate || 0)
},
{
prop: 'brokerRatio',
......@@ -507,32 +579,32 @@ const payableReportListTableColumns = ref([
formatter: row => row.productName || '-'
},
{
prop: 'statusDesc',
label: '修改理由',
prop: 'remark',
label: '备注',
sortable: true,
width: '120',
formatter: row => row.statusDesc || '-'
formatter: row => row.remark || '-'
},
// {
// prop: 'statusDesc',
// label: '修改理由',
// sortable: true,
// width: '120',
// formatter: row => row.statusDesc || '-'
// },
{
prop: 'creatorName',
label: '操作人',
label: '创建人',
sortable: true,
width: '120',
formatter: row => row.creatorName || '-'
},
{
prop: 'updateTime',
label: '操作时间',
prop: 'createTime',
label: '创建时间',
sortable: true,
width: '180',
formatter: row => row.updateTime || '-'
},
{
prop: 'remark',
label: '备注',
sortable: true,
width: '120',
formatter: row => row.remark || '-'
formatter: row => row.createTime || '-'
}
])
......@@ -579,9 +651,9 @@ const searchConfig = ref([
label: '投保人'
},
{
type: 'daterange',
type: 'monthrange',
prop: 'payoutDate',
label: '出账(估)',
label: '出账(估)',
startPlaceholder: '开始时间',
endPlaceholder: '结束时间'
},
......@@ -675,11 +747,11 @@ const searchConfig = ref([
const payRecordDialogTableVisible = ref(false)
// 新增出账记录
const addPayRecordFormModel = ref({
fortuneBizType: 'U'
fortuneBizType: 'R'
})
const addPayRecordDialogVisible = ref(false)
const addPayRecordFormRef = ref()
const addPayRecordFormConfig = [
const updatePayRecordFormConfig = [
{
type: 'select',
prop: 'fortuneBizType',
......@@ -693,56 +765,402 @@ const addPayRecordFormConfig = [
type: 'input',
prop: 'policyNo',
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'
},
{
type: 'input',
prop: 'fortunePeriod',
label: '佣金期数',
inputType: 'decimal',
visible: formData => formData.fortuneBizType === 'R',
rules: [{ pattern: /^\d+$/, message: '只能输入正整数', trigger: 'blur' }]
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' }]
visible: formData => formData.fortuneBizType === 'R'
// rules: [{ pattern: /^\d+$/, message: '只能输入正整数', trigger: 'blur' }]
},
{
type: 'select',
prop: 'currency',
label: '出账币种',
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: 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' }]
},
{
type: 'input',
prop: 'originalToHkdRate',
label: '汇率3(原币种->港币)',
inputType: 'decimal',
rules: [{ required: true, message: '只能输入正整数和小数', trigger: 'blur' }]
// defaultValue: 1
},
{
type: 'input',
prop: 'hkdAmount',
label: '港币金额',
inputType: 'decimal',
rules: [{ required: true, message: '只能输入正整数和小数', trigger: 'blur' }]
},
{
type: 'input',
prop: 'hkdToPayoutRate',
label: '汇率1(港币->发放币种)',
inputType: 'decimal',
rules: [{ required: true, message: '只能输入正整数和小数', trigger: 'blur' }]
// defaultValue: 1
},
{
type: 'input',
prop: 'payoutAmount',
label: '实际发放金额',
inputType: 'decimal',
rules: [{ required: true, message: '只能输入正整数和小数', trigger: 'blur' }]
},
{
type: 'input',
prop: 'exchangeRate',
label: '汇率2(保单币种->港币)入账检核汇率',
inputType: 'decimal',
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: 'date',
type: 'month',
prop: 'payoutDate',
label: '出账日(估)',
placeholder: '请选择'
label: '出账月(估)',
placeholder: '请选择',
maxDate: 'today',
rules: [{ required: true, message: '出账月(估)必填', trigger: 'blur' }]
},
{
type: 'date',
type: 'month',
prop: 'actualPayoutDate',
label: '出账日(实)',
label: '出账月(实)',
placeholder: '请选择',
maxDate: 'today'
maxDate: 'today',
rules: [{ required: true, message: '出账月(实)必填', trigger: 'blur' }]
},
{
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'
prop: 'statusDesc',
label: '修改理由'
},
{
type: 'input',
prop: 'fortuneName',
label: '出账项目名称',
rules: [{ required: true, message: '出账项目名称必填', trigger: 'blur' }]
},
{
type: 'select',
prop: 'fortuneType',
label: '出账项目',
dictType: 'csf_fortune_type'
label: '出账项目类型',
dictType: 'csf_fortune_type',
rules: [{ required: true, message: '出账项目类型必填', trigger: 'blur' }]
},
{
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',
......@@ -758,31 +1176,143 @@ const addPayRecordFormConfig = [
onChangeExtraFields: {
broker: 'realName', // 自动同步 raw.name 到 reconciliationCompany
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: 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: 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',
prop: 'status',
label: '出账状态',
dictType: 'csf_expected_fortune_status'
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: 'exchangeRate',
label: '结算汇率',
prop: 'originalAmount',
label: '原币种金额',
inputType: 'decimal',
rules: [{ required: true, message: '只能输入正整数和小数', trigger: 'blur' }]
},
{
type: 'input',
prop: 'originalToHkdRate',
label: '汇率3(原币种->港币)',
inputType: 'decimal',
rules: [{ required: true, message: '只能输入正整数和小数', trigger: 'blur' }]
// defaultValue: 1
},
{
type: 'input',
prop: 'hkdAmount',
label: '港币金额',
inputType: 'decimal',
rules: [{ required: true, message: '只能输入正整数和小数', trigger: 'blur' }]
},
{
type: 'input',
prop: 'hkdToPayoutRate',
label: '汇率1(港币->发放币种)',
inputType: 'decimal',
rules: [{ required: true, message: '只能输入正整数和小数', trigger: 'blur' }]
// defaultValue: 1
},
{
type: 'input',
prop: 'payoutAmount',
label: '实际发放金额',
inputType: 'decimal',
rules: [{ required: true, message: '只能输入正整数和小数', trigger: 'blur' }]
},
{
type: 'input',
prop: 'defaultExchangeRate',
label: '汇率2(保单币种->港币)入账检核汇率',
inputType: 'decimal',
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()
......@@ -794,11 +1324,184 @@ const handleInputChange = async (formType, prop, value, item) => {
commissionPeriod: fortunePeriod
})
if (res.code == 200) {
addPayRecordFormModel.value.exchangeRate = res.data
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 () => {
......@@ -821,9 +1524,8 @@ const handleConfirmAddPayRecord = async () => {
}
await updatePayRecord(params)
ElMessage.success('更新出账记录成功')
addPayRecordDialogVisible.value = false
addPayRecordFormRef.value.resetForm()
editStatus.value = 'add'
updatePayRecordDialogVisible.value = false
updatePayRecordFormRef.value.resetForm()
loadPayRecordTableData(selectedRow.value.expectedFortuneBizId)
expectedFortuneListData()
} catch (error) {
......@@ -831,6 +1533,35 @@ const handleConfirmAddPayRecord = async () => {
}
}
}
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()
} catch (error) {
if (error.message && error.message.includes('Validation')) {
ElMessage.error('必填项不能为空')
}
ElMessage.error('更新失败')
}
}
}
// 弹窗表单重置
const resetAddPayRecordForm = () => {
addPayRecordFormRef.value.resetForm()
......@@ -892,13 +1623,12 @@ const handleSelect = async (e, row) => {
}
]
} else if (e === 'updateData') {
editStatus.value = 'edit'
addPayRecordDialogVisible.value = true
updatePayRecordDialogVisible.value = true
// 2. 使用 nextTick 等待 DOM 更新
nextTick(() => {
// 3. 此时 addRecordRef.value 一定存在了
if (addPayRecordFormRef.value && selectedRow.value) {
addPayRecordFormModel.value = { ...selectedRow.value }
if (updatePayRecordFormRef.value && selectedRow.value) {
updatePayRecordFormModel.value = { ...selectedRow.value }
}
})
console.log(addPayRecordFormModel.value)
......@@ -952,6 +1682,7 @@ const handleReset = () => {
}
const handleQuery = async () => {
currentPage.value = 1
loadTableData()
}
......@@ -987,23 +1718,34 @@ const operationBtnList = ref([
// 分页事件
const handleSizeChange = val => {
pageSize.value = val
loadTableData()
const params = searchFormRef.value.getFormData()
loadTableData(params)
}
const handleCurrentChange = val => {
currentPage.value = val
loadTableData()
const params = searchFormRef.value.getFormData()
loadTableData(params)
}
// 加载表格数据
const loadTableData = async () => {
const searchParams = searchFormRef.value.getFormData() || {}
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 {
const params = {
...searchParams,
payoutDateStart: searchParams.payoutDate?.[0] || undefined,
payoutDateEnd: searchParams.payoutDate?.[1] || undefined,
// payoutDateStart: searchParams.payoutDate?.[0] || undefined,
// payoutDateEnd: searchParams.payoutDate?.[1] || undefined,
payoutDate: undefined,
pageNo: currentPage.value,
pageSize: pageSize.value
......@@ -1160,4 +1902,8 @@ const handleCurrentChangeDetailRecord = val => {
border-top: 1px solid #ebeef5;
text-align: right;
}
/* 使用 :deep 穿透 scoped 样式,保证对 el-table__row 生效 */
:deep(.estimated-row) {
background-color: #f0f9ff; /* 浅蓝色,可根据需要修改 */
}
</style>
......@@ -75,7 +75,7 @@ const tableColumns = ref([
{ 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'},
// { prop: 'fortuneAccountBizId', label: '出账记录业务id', sortable: true, width: '150'},
])
// 添加表格引用
......@@ -130,6 +130,7 @@ getList()
// 查询
const handleQuery = () => {
currentPage.value = 1
const params = searchFormRef.value.getFormData()
console.log('父组件发起查询:', params)
getList(params)
......
......@@ -94,7 +94,7 @@
</el-table>
</template>
</CommonPage>
<!-- 应收明细 -->
<CommonDialog
dialogTitle="应收明细"
dialogWidth="80%"
......@@ -188,22 +188,29 @@
: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 }">
<el-popover placement="right" :width="200" trigger="click">
<el-popover placement="right" :width="200" trigger="click" v-if="row.type == '1'">
<template #reference>
<el-icon>
<MoreFilled />
</el-icon>
</template>
<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">
{{ item.label }}
</el-menu-item>
</el-menu-item> -->
<el-menu-item
:index="item.value"
v-for="item in getOperateItems(row)"
:key="item.value"
>{{ item.label }}</el-menu-item
>
</el-menu>
</el-popover>
</template>
</el-table-column> -->
</el-table-column>
</el-table>
<el-pagination
v-model:current-page="detailPageInfo.currentPage"
......@@ -370,9 +377,9 @@ const addReceivablesFormConfig = [
rules: [{ pattern: /^\d+$/, message: '只能输入正整数', trigger: 'blur' }]
},
{
type: 'date',
type: 'month',
prop: 'commissionDate',
label: '入账(估)',
label: '入账(估)',
placeholder: '请选择'
},
// {
......@@ -421,7 +428,7 @@ const addReceivablesFormConfig = [
},
{
type: 'select',
prop: 'reconciliationCompanyBizId',
prop: 'reconciliationCompany',
label: '对账公司',
api: '/insurance/base/api/insuranceReconciliationCompany/page',
keywordField: 'name',
......@@ -441,15 +448,7 @@ const addReceivablesFormConfig = [
},
{
type: 'input',
prop: 'exchangeRate',
label: '结算汇率',
inputType: 'decimal',
rules: [{ required: true, message: '只能输入正整数和小数', trigger: 'blur' }]
// defaultValue: 1
},
{
type: 'input',
prop: 'remark',
prop: 'manualRemark',
label: '备注'
}
]
......@@ -483,6 +482,7 @@ const handleConfirmAddReceivables = async () => {
ElMessage.success('应收款修改成功')
addReceivablesDialogVisible.value = false
resetAddReceivablesForm()
receivedFortuneListData()
loadTableData() // 重新加载表格
} else {
ElMessage.error(res.msg || '应收款修改失败')
......@@ -503,9 +503,9 @@ const searchConfig = ref([
label: '保单号'
},
{
type: 'daterange',
type: 'monthrange',
prop: 'entryDate',
label: '入账(估)',
label: '入账(估)',
startPlaceholder: '开始时间',
endPlaceholder: '结束时间'
},
......@@ -650,6 +650,21 @@ const dropdownItems = [
{ label: '设置状态', value: 'setStatus' },
{ 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)
......@@ -726,6 +741,7 @@ const handleReset = () => {
}
const handleQuery = () => {
currentPage.value = 1
loadTableData()
}
const visibleDefaultButtons = ref(['add', 'export', 'reset', 'query'])
......@@ -741,12 +757,14 @@ const operationBtnList = ref([
// 分页事件
const handleSizeChange = val => {
pageSize.value = val
loadTableData()
const params = searchFormRef.value.getFormData()
loadTableData(params)
}
const handleCurrentChange = val => {
currentPage.value = val
loadTableData()
const params = searchFormRef.value.getFormData()
loadTableData(params)
}
// 加载表格数据
......@@ -754,10 +772,23 @@ const loadTableData = async () => {
const searchParams = searchFormRef.value.getFormData() || {}
loading.value = true
try {
console.log('searchParams.entryDate', searchParams.entryDate)
if (searchParams.entryDate.length > 0) {
searchParams.commissionDateStart = `${searchParams.entryDate[0]}-01`
searchParams.commissionDateEnd = `${searchParams.entryDate[1]}-01`
} else {
searchParams.commissionDateStart = ''
searchParams.commissionDateEnd = ''
}
const params = {
...searchParams,
commissionDateStart: searchParams?.entryDate?.[0] || undefined,
commissionDateEnd: searchParams?.entryDate?.[1] || undefined,
// 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
......@@ -870,6 +901,7 @@ const handleSelect = async (e, row) => {
// 3. 此时 addRecordRef.value 一定存在了
if (addRecordRef.value && selectedRow.value) {
addReceivablesFormModel.value = { ...selectedRow.value }
addReceivablesFormModel.value.commissionDate = selectedRow.value.commissionDateMonth
console.log('赋值成功:', addReceivablesFormModel.value)
}
})
......@@ -918,6 +950,7 @@ const handleConfirmSetStatus = () => {
if (res.code === 200) {
ElMessage.success('状态修改成功')
setStatusDialogTableVisible.value = false
receivedFortuneListData()
loadTableData() // 重新加载表格
} else {
ElMessage.error(res.msg || '状态修改失败')
......@@ -1070,7 +1103,7 @@ const receivableReportTableColumns = ref([
sortable: true,
width: '120',
formatter: row => row.policyCurrency || '-'
},
}
// {
// prop: 'unpaidAmount',
// label: '待入账金额HKD',
......@@ -1078,12 +1111,12 @@ const receivableReportTableColumns = ref([
// width: '120',
// formatter: row => formatCurrency(row.unpaidAmount || 0)
// },
{
prop: 'remark',
label: '备注',
width: '150',
formatter: row => row.remark || '-'
}
// {
// prop: 'manualRemark',
// label: '备注',
// width: '150',
// formatter: row => row.manualRemark || '-'
// }
])
// 应收明细
const receivableReportItemTableColumns = ref([
......@@ -1105,7 +1138,7 @@ const receivableReportItemTableColumns = ref([
prop: 'no',
label: '应收单编号',
sortable: true,
width: '150',
width: '180',
formatter: row => row.no || '-'
},
// {
......@@ -1126,7 +1159,7 @@ const receivableReportItemTableColumns = ref([
prop: 'reconciliationCompany',
label: '对账公司',
sortable: true,
width: '150',
width: '100',
formatter: row => row.reconciliationCompany || '-'
},
{
......@@ -1181,7 +1214,7 @@ const receivableReportItemTableColumns = ref([
{
prop: 'commissionRatio',
label: '产品对应来佣率',
label: '保单对应来佣率',
sortable: true,
width: '120',
formatter: row => (row.commissionRatio || 0) + '%' || '-'
......@@ -1214,7 +1247,6 @@ const receivableReportItemTableColumns = ref([
width: '120',
formatter: row => row.realAmount || '-'
},
//还不确定字段
{
prop: 'realReconciliationYearMonth',
label: '检核年月',
......@@ -1227,7 +1259,7 @@ const receivableReportItemTableColumns = ref([
label: '本次入账比例',
sortable: true,
width: '120',
formatter: row => (row.revenueRatio || 0) + '%' || '-'
formatter: row => (row.revenueRatio ? row.revenueRatio + '%' : '-')
},
{
prop: 'pendingAmount',
......@@ -1264,7 +1296,6 @@ const receivableReportItemTableColumns = ref([
width: '120',
formatter: row => row.productName || '-'
},
//还不确定字段
{
prop: 'issueNumber',
label: '年期',
......@@ -1287,11 +1318,18 @@ const receivableReportItemTableColumns = ref([
formatter: row => formatCurrency(row.premium || 0)
},
{
prop: 'realRemark',
prop: 'manualRemark',
label: '人工备注',
sortable: true,
width: '120',
formatter: row => row.manualRemark || '-'
},
{
prop: 'remark',
label: '备注',
sortable: true,
width: '120',
formatter: row => row.realRemark || '-'
formatter: row => row.remark || '-'
},
{
prop: 'realUpdaterName',
......
<!-- 预约附件代码 -->
<template>
<div class="uploadContainer">
<CardOne title="材料信息">
<template #headerRight>
<div style="margin-top: 20px;">
<el-button
type="primary"
:loading="downloading"
@click="handleBatchDownloadSelected"
>
<div style="margin-top: 20px">
<el-button type="primary" :loading="downloading" @click="handleBatchDownloadSelected">
{{ downloading ? '正在打包中...' : '下载材料包' }}
</el-button>
<div v-if="downloading" style="margin-top: 10px;">
<el-progress
:percentage="progressPercentage"
:format="progressFormat"
/>
<div style="font-size: 12px; color: #666; margin-top: 5px;">
<div v-if="downloading" style="margin-top: 10px">
<el-progress :percentage="progressPercentage" :format="progressFormat" />
<div style="font-size: 12px; color: #666; margin-top: 5px">
已处理文件:{{ currentCount }} / {{ totalCount }}
</div>
</div>
......@@ -108,6 +102,7 @@
:headers="headers"
multiple
:limit="limit"
:accept="acceptUploadType"
:show-file-list="false"
:on-exceed="handleExceed"
:on-success="uploadSuccess"
......@@ -120,7 +115,7 @@
</div>
</el-upload>
</div>
<div class="tip">(支持Word,Excel,PDF,图片格式)</div>
<div class="tip">(支持PDF,图片格式)</div>
</div>
<div class="dialogRight">
<div
......@@ -135,15 +130,70 @@
:key="file.fileBizId"
class="uploaded-file-item"
>
<div class="fileName">{{ file.originalName }}</div>
<el-icon color="red" size="18" @click="removeUploadedFile(file, index)"
<div class="fileNameBox">{{ file.originalName }}</div>
<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
/></el-icon>
/></el-icon> -->
</div>
</el-scrollbar>
</div>
</div>
</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%">
<div style="text-align: center">
<el-image :src="imageUrl" fit="contain" />
......@@ -153,15 +203,17 @@
</template>
<script setup name="FileUpload">
import { ref } from 'vue';
import { ElMessage } from 'element-plus';
import { downloadFilesAsZip } from '@/utils/zipDownload'; // 引入刚才封装的工具
import { ref, nextTick, shallowRef } from 'vue'
import { ElMessage } from 'element-plus'
import { downloadFilesAsZip } from '@/utils/zipDownload' // 引入刚才封装的工具
import CommonDialog from '@/components/commonDialog'
import FilePreview from '@/components/Preview/filePreview.vue'
import CardOne from '@/components/formCard/cardOne'
import { getToken } from '@/utils/auth'
import { addFile, getAppointmentFile, delFile, editAppointmentFile } from '@/api/sign/appointment'
import useDictStore from '@/store/modules/dict'
import useUserStore from '@/store/modules/user'
import { ArrowLeft, ArrowRight, ZoomIn, ZoomOut, Document, Loading } from '@element-plus/icons-vue'
const userStore = useUserStore()
import {
uploadMaterialList,
......@@ -172,11 +224,18 @@ import {
downloadCompressedFile,
delMaterial
} from '@/api/common'
import * as PDFJS from 'pdfjs-dist'
PDFJS.GlobalWorkerOptions.workerSrc = '/js/pdf.worker.min.mjs'
const props = defineProps({
activeName: { type: String, default: '' }, //tab名称
idsObj: { type: Object, default: () => ({}) }, //父组件传递过来的id对象
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 dictStore = useDictStore() //获取字典数据
const { proxy } = getCurrentInstance()
......@@ -188,6 +247,191 @@ const limit = ref(10)
const fileSize = ref(10)
const headers = ref({ Authorization: 'Bearer ' + getToken() })
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 imageUrl = ref('')
......@@ -201,21 +445,25 @@ const data = reactive({
// 下载材料包
// 状态管理
const downloading = ref(false);
const currentCount = ref(0);
const totalCount = ref(0);
const downloading = ref(false)
const currentCount = ref(0)
const totalCount = ref(0)
const viewFile = file => {
console.log('====================================')
console.log('file', file)
console.log('====================================')
}
const progressPercentage = computed(() => {
if (totalCount.value === 0) return 0;
return Math.floor((currentCount.value / totalCount.value) * 100);
});
if (totalCount.value === 0) return 0
return Math.floor((currentCount.value / totalCount.value) * 100)
})
const progressFormat = (percentage) => `${currentCount.value}/${totalCount.value}`;
const progressFormat = percentage => `${currentCount.value}/${totalCount.value}`
// 2. 核心处理方法
const handleBatchDownloadSelected = async () => {
let apiMaterialDtoList = []
if (!props.idsObj.appointmentBizId) {
if (!props.idsObj.appointmentBizId) {
apiMaterialDtoList = fileTableList.value.map(item => {
return {
dataPerson: item.dataPerson, //资料人(字典)
......@@ -228,7 +476,7 @@ if (!props.idsObj.appointmentBizId) {
}))
}
})
}else{
} else {
apiMaterialDtoList = fileTableList.value.map(item => {
return {
dataPerson: item.dataPerson, //资料人(字典)
......@@ -238,40 +486,40 @@ if (!props.idsObj.appointmentBizId) {
fileUrlList: item.fileUrlList
}
})
}
}
if (!apiMaterialDtoList || apiMaterialDtoList.length === 0) {
ElMessage.warning('没有要下载的材料');
return;
ElMessage.warning('没有要下载的材料')
return
}
// --- 步骤 1: 数据清洗与扁平化 ---
const flatFileList = [];
let hasFiles = false;
apiMaterialDtoList.forEach((item,index) => {
const flatFileList = []
let hasFiles = false
apiMaterialDtoList.forEach((item, index) => {
// 安全检查:确保 fileUrlList 存在且是数组
const urls = item.fileUrlList;
const urls = item.fileUrlList
if (!urls || !Array.isArray(urls) || urls.length === 0) {
return; // 跳过没有文件的行
return // 跳过没有文件的行
}
hasFiles = true;
hasFiles = true
// 生成安全的业务前缀
// 规则:[人员类型]_[资料类型]_[业务ID]
// 例如:POLICYHOLDER_FRONT_2216
const safePerson = (item.dataPersonName || 'UNKNOWN').replace(/[\/\\:*?"<>|]/g, '_');
const safeType = (item.dataTypeName || 'FILE').replace(/[\/\\:*?"<>|]/g, '_');
const safePerson = (item.dataPersonName || 'UNKNOWN').replace(/[\/\\:*?"<>|]/g, '_')
const safeType = (item.dataTypeName || 'FILE').replace(/[\/\\:*?"<>|]/g, '_')
const filePrefix = `${safePerson}_${safeType}`;
const filePrefix = `${safePerson}_${safeType}`
urls.forEach((fileItem, fIndex) => {
// 兼容 fileUrlList 可能是字符串数组 或 对象数组
let fileUrl = fileItem.fileUrl;
let originalFileName = fileItem.fileName;
let fileUrl = fileItem.fileUrl
let originalFileName = fileItem.fileName
// --- 关键:构建最终文件名 ---
const finalFileName = `${filePrefix}_${originalFileName}`;
const finalFileName = `${filePrefix}_${originalFileName}`
if (fileUrl) {
flatFileList.push({
......@@ -282,41 +530,36 @@ if (!props.idsObj.appointmentBizId) {
type: item.dataType,
note: item.precautions
}
});
})
}
});
});
})
})
if (!hasFiles) {
ElMessage.warning('选中的项中没有包含任何附件');
return;
ElMessage.warning('选中的项中没有包含任何附件')
return
}
// --- 步骤 2: 执行下载 ---
totalCount.value = flatFileList.length;
currentCount.value = 0;
downloading.value = true;
totalCount.value = flatFileList.length
currentCount.value = 0
downloading.value = true
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) => {
currentCount.value = current;
});
currentCount.value = current
})
ElMessage.success(`成功打包 ${flatFileList.length} 个文件`);
ElMessage.success(`成功打包 ${flatFileList.length} 个文件`)
} catch (error) {
ElMessage.error('下载过程中出现异常,请查看控制台');
console.error(error);
ElMessage.error('下载过程中出现异常,请查看控制台')
console.error(error)
} finally {
downloading.value = false;
downloading.value = false
}
};
}
const { queryParams, form } = toRefs(data)
// 新增:用于存储已上传成功的文件列表
......@@ -444,7 +687,7 @@ const handleView = row => {
const downloadFile = () => {
let apiMaterialDtoList = []
let params = {
projectBizId:userStore.projectInfo.projectBizId || '',
projectBizId: userStore.projectInfo.projectBizId || '',
objectName: '预约附件材料包', //对象名(包名)
objectBizId: '' //对象业务ID
}
......@@ -508,6 +751,7 @@ function handleBeforeUpload(file) {
// proxy.$modal.msgError('文件名不正确,不能包含英文逗号!')
// return false
// }
const isLt = file.size / 1024 / 1024 < fileSize.value
if (!isLt) {
proxy.$modal.msgError(`上传文件大小不能超过 ${fileSize.value} MB!`)
......@@ -522,7 +766,7 @@ function handleExceed() {
// 文件上传成功回调
const uploadSuccess = (res, file, fileList) => {
console.log('上传成功', res, file)
proxy.$modal.closeLoading();
proxy.$modal.closeLoading()
if (res.code === 200) {
// 构造前端使用的文件对象(保留原始 file 信息 + 后端返回的 url 等)
const uploadedFile = {
......@@ -614,6 +858,126 @@ defineExpose({
})
</script>
<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 {
padding-left: 10px;
padding-top: 10px;
......
......@@ -232,7 +232,7 @@ import {
getItineraryExprot
} from '@/api/sign/appointment'
import useUserStore from '@/store/modules/user'
import { copyToClipboard } from '@/utils/copyToClipboard'
import copyToClipboard from '@/utils/copyToClipboard'
const dictStore = useDictStore()
const userStore = useUserStore()
const router = useRouter()
......
......@@ -386,6 +386,7 @@ const handleReset = () => {
loadTableData()
}
const handleQuery = async () => {
currentPage.value = 1
const params = searchFormRef.value.getFormData()
console.log('params', params)
......@@ -421,6 +422,8 @@ const operationBtnList = ref([
// 加载表格数据
const loadTableData = async (searchParams = {}) => {
loading.value = true
// console.log('searchFormRef.value', searchFormRef.value)
// const searchParams = searchFormRef.value.getFormData() || {}
try {
const params = {
pageNo: currentPage.value,
......@@ -443,11 +446,13 @@ loadTableData()
// 分页事件
const handleSizeChange = val => {
pageSize.value = val
loadTableData()
const params = searchFormRef.value.getFormData()
loadTableData(params)
}
const handleCurrentChange = val => {
currentPage.value = val
loadTableData()
const params = searchFormRef.value.getFormData()
loadTableData(params)
}
// 表格数据
const tableData = ref([])
......
......@@ -1250,6 +1250,7 @@ const handleReset = () => {
}
const handleQuery = async () => {
const params = searchFormRef.value.getFormData()
currentPage.value = 1
console.log('params', params)
// let msg = validateEnglish2(params.eng)
// if (params.eng && msg) {
......@@ -1288,6 +1289,7 @@ const operationBtnList = ref([
// 加载表格数据
const loadTableData = async (searchParams = {}) => {
loading.value = true
try {
const params = {
pageNo: currentPage.value,
......@@ -1310,11 +1312,14 @@ const loadTableData = async (searchParams = {}) => {
// 分页事件
const handleSizeChange = val => {
pageSize.value = val
loadTableData()
const params = searchFormRef.value.getFormData()
loadTableData(params)
}
const handleCurrentChange = val => {
currentPage.value = val
loadTableData()
const params = searchFormRef.value.getFormData()
loadTableData(params)
}
// 表格数据
const tableData = ref([])
......
......@@ -151,7 +151,7 @@ import {
saveInitialPayment,
updatePolicyProduct
} from '@/api/sign/underwritingMain'
import { copyToClipboard } from '@/utils/copyToClipboard'
import copyToClipboard from '@/utils/copyToClipboard';
import PolicyDetail from './policyDetail.vue'
const policyDetailFormRef = ref(null)
const policyDetailFormData = ref({})
......@@ -346,6 +346,7 @@ const handleReset = () => {
loadTableData()
}
const handleQuery = async () => {
currentPage.value = 1
loadTableData()
}
const visibleDefaultButtons = ref(['reset', 'query'])
......
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