Commit 3a0841e3 by yuzhenWang

优化2

parent 2b3989c6
<!-- 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>
......@@ -62,7 +62,9 @@
</el-row>
</div>
<el-table
ref="multipleTableRef"
:data="tableData"
row-key="fortuneBizId"
@selection-change="handleSelectionChange"
height="400"
border
......@@ -70,7 +72,7 @@
style="width: 100%"
v-loading="loading"
>
<el-table-column type="selection" width="40" fixed="left" />
<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 }">
......@@ -340,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)
......@@ -671,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
......@@ -1011,7 +1018,8 @@ const addSpiltRecord = () => {
payoutCurrency: 'HKD',
ruleCurrency: selectedRow.value.ruleCurrency,
originalCurrency: selectedRow.value.originalCurrency,
hkdToPayoutRate: '1'
hkdToPayoutRate: '1',
exchangeRate: selectedRow.value.exchangeRate
}
if (selectedRow.value.originalCurrency && selectedRow.value.originalCurrency == 'HKD') {
newRow.originalToHkdRate = '1'
......@@ -1445,7 +1453,6 @@ const updatePayoutAmountapi = async data => {
const updatePayRollStatusDisable = ref(true)
const multipleSelection = ref([])
const handleSelectionChange = val => {
multipleSelection.value = val
console.log('全选:', val)
// 完成检核按钮是否禁用
......@@ -1462,7 +1469,7 @@ const submitSettingBillYearMonth = async () => {
if (res.code === 200) {
settingBillYearMonthFlag.value = false
ElMessage.success('完成检核,等待关账')
const params = searchFormRef.value.getFormData()
const params = await searchFormRef.value.getFormData()
loadTableData(params)
}
} catch (error) {
......@@ -1479,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
......@@ -1522,6 +1530,17 @@ const onSubmit = data => {
}
onMounted(async () => {})
// 数据变化时清空选中
watch(
() => tableData,
() => {
nextTick(() => {
multipleTableRef.value?.clearSelection()
multipleSelection.value = []
})
},
{ deep: true }
)
</script>
<style scoped>
......
......@@ -173,7 +173,7 @@
<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 ? '无效' : '有效' }}
......
......@@ -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' }
]
//=============拆分出账开始================
......@@ -816,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)
......
......@@ -413,13 +413,13 @@ const payableReportListTableColumns = ref([
width: '130',
formatter: row => row.payableNo || '-'
},
{
prop: 'policyNo',
label: '关联业务编号',
sortable: true,
width: '130',
formatter: row => row.policyNo || '-'
},
// {
// prop: 'policyNo',
// label: '关联业务编号',
// sortable: true,
// width: '130',
// formatter: row => row.policyNo || '-'
// },
{
prop: 'policyNo',
label: '保单号',
......
......@@ -94,7 +94,7 @@
</el-table>
</template>
</CommonPage>
<!-- 应收明细 -->
<CommonDialog
dialogTitle="应收明细"
dialogWidth="80%"
......@@ -901,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)
}
})
......
<!-- 预约附件代码 -->
<template>
<div class="uploadContainer">
<CardOne title="材料信息">
......@@ -129,7 +130,7 @@
:key="file.fileBizId"
class="uploaded-file-item"
>
<div class="fileName">{{ file.originalName }}</div>
<div class="fileNameBox">{{ file.originalName }}</div>
<div>
<el-button type="primary" size="small" link @click="previewFile(file)">
查看
......@@ -148,37 +149,9 @@
</div>
</CommonDialog>
<!-- 文件预览弹窗(页面内查看,不打开新窗口) -->
<!-- <el-dialog
v-model="previewDialogVisible"
:title="previewFileName"
width="70%"
:close-on-click-modal="false"
destroy-on-close
@close="previewUrl = ''"
>
<div class="preview-container">
<div v-if="previewFileType === 'image'" class="preview-image-wrapper">
<img :src="previewUrl" class="preview-image" alt="预览图片" />
</div>
<iframe
v-else-if="previewFileType === 'pdf'"
:src="previewUrl"
class="preview-pdf"
frameborder="0"
></iframe>
<div v-else-if="previewFileType === 'unsupported'" class="preview-unsupported">
<el-icon :size="48" color="#909399"><Document /></el-icon>
<p>暂不支持预览此类型文件</p>
<el-button type="primary" @click="previewDialogVisible = false"> 关闭 </el-button>
</div>
</div>
</el-dialog> -->
<!-- 文件预览弹窗 -->
<el-dialog
<FilePreview ref="filePreviewRef" />
<!-- <el-dialog
v-model="previewDialogVisible"
:title="previewFileName"
width="90%"
......@@ -187,14 +160,14 @@
@close="closePreview"
>
<div class="preview-container">
<!-- 图片预览 -->
<div v-if="previewFileType === 'image'" class="preview-image-wrapper">
<img :src="previewUrl" class="preview-image" alt="预览图片" />
</div>
<!-- PDF 预览区域(滚动多页) -->
<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> 缩小
......@@ -213,14 +186,14 @@
<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> -->
<el-dialog v-model="imageViewerVisible" title="图片预览" width="60%">
<div style="text-align: center">
<el-image :src="imageUrl" fit="contain" />
......@@ -234,6 +207,7 @@ 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'
......@@ -258,6 +232,7 @@ const props = defineProps({
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')
......@@ -397,34 +372,42 @@ const renderAllPages = async scale => {
}
// 修改 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 || '文件'
// 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
}
// 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 // 取消任何进行中的渲染
......@@ -875,6 +858,13 @@ defineExpose({
})
</script>
<style lang="scss" scoped>
.fileNameBox {
width: 70%;
/* 核心三件套: 强制单行 + 溢出隐藏 + 省略号 */
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.pdf-viewer {
display: flex;
flex-direction: column;
......
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