Commit e42dc030 by yuzhenWang

Merge branch 'test' into 'dev'

Test

See merge request !101
parents 769c83d5 7a3d422b
......@@ -21,17 +21,19 @@
"@element-plus/icons-vue": "^2.3.1",
"@vueup/vue-quill": "1.2.0",
"@vueuse/core": "13.3.0",
"ali-oss": "^6.23.0",
"axios": "1.9.0",
"clipboard": "2.0.11",
"dayjs": "^1.11.18",
"echarts": "5.6.0",
"element-plus": "^2.9.9",
"element-plus": "^2.13.5",
"file-saver": "2.0.5",
"fuse.js": "6.6.2",
"js-beautify": "1.14.11",
"js-cookie": "3.0.5",
"jsencrypt": "3.3.2",
"nprogress": "0.2.0",
"p-limit": "^7.3.0",
"pinia": "3.0.2",
"spark-md5": "^3.0.2",
"splitpanes": "^4.0.4",
......
import request from '@/utils/request'
// 修改上传图片方法,添加正确的请求配置
export function uploadImage(data) {
console.log('data', data)
......@@ -168,9 +167,9 @@ export function uploadMaterialList(data) {
})
}
// 删除上传文件
export function delUploadFile(fileBizId) {
export function delUploadFile(fileBizId,projectBizId) {
return request({
url: `/oss/api/ossFile/del?fileBizId=${fileBizId}`,
url: `/oss/api/ossFile/del?fileBizId=${fileBizId}&projectBizId=${projectBizId}`,
method: 'delete'
})
}
......@@ -221,3 +220,19 @@ export function insuranceReconciliationCompany(data) {
method: 'post'
})
}
// 获取STS凭证
export function getStstoken(projectBizId) {
return request({
url: `/oss/api/sts/sts-token?projectBizId=${projectBizId}`,
method: 'get'
})
}
// 大文件分片上传接口
export function batchSaveFiles(data) {
return request({
url: '/oss/api/sts/batch/save/files',
data: data,
method: 'post'
})
}
<template>
<div class="upload-container">
<h2>多文件分片上传 (Vue3 + Element Plus)</h2>
<el-card shadow="hover">
<div class="big-file-uploader">
<!--
关键修改:
1. 添加 ref="uploadRef" 以便直接访问内部文件列表
2. 监听 @change 获取原生文件
-->
<el-upload
ref="uploadRef"
class="multi-uploader"
action="#"
:auto-upload="false"
class="upload-demo"
drag
multiple
:limit="10"
:on-change="handleFileChange"
:on-remove="handleRemove"
:file-list="fileList"
:auto-upload="false"
:on-change="handleFileSelect"
:file-list="uiFileList"
:disabled="isUploading"
:limit="100"
>
<template #trigger>
<el-button type="primary">选择文件</el-button>
</template>
<!-- 自定义文件列表项 -->
<template #file="{ file }">
<div class="el-upload-list__item-custom">
<div class="file-info">
<el-icon><Document /></el-icon>
<span class="file-name">{{ file.name }}</span>
<span class="file-size">{{ formatSize(file.size) }}</span>
<el-icon class="el-icon--upload"><upload-filled /></el-icon>
<div class="el-upload__text">
拖拽文件到此处 或 <em>点击上传</em>
</div>
<!-- 状态与进度区域 -->
<div class="file-action-area">
<!-- 等待上传状态 -->
<div v-if="getTaskStatus(file.uid)?.status === 'idle' || !getTaskStatus(file.uid)" class="status-wait">
<el-tag size="small">待上传</el-tag>
<el-button link type="primary" @click="startSingleUpload(file)">开始</el-button>
<template #tip>
<div class="el-upload__tip">
支持大文件切片上传,最大并发数:{{ uploadConcurrency }}
</div>
</template>
</el-upload>
<!-- 进行中/暂停/合并状态 -->
<div v-else-if="['checking', 'uploading', 'merging', 'paused'].includes(getTaskStatus(file.uid)?.status)" class="status-progress">
<el-progress
:percentage="getTaskStatus(file.uid)?.progress || 0"
:status="getTaskStatus(file.uid)?.status === 'paused' ? 'exception' : undefined"
:format="(percent) => getTaskStatus(file.uid)?.message || `${percent}%`"
/>
<div class="btn-group">
<el-button
v-if="getTaskStatus(file.uid)?.status !== 'paused'"
link type="warning"
@click="handlePause(file.uid)"
>暂停</el-button>
<!-- 控制栏 -->
<div class="control-bar" v-if="fileList.length > 0">
<el-button
v-else
link type="success"
@click="handleResume(file.uid)"
>继续</el-button>
<el-button link type="danger" @click="handleCancel(file.uid)">取消</el-button>
type="primary"
@click="startUpload"
:disabled="isUploading || !hasPendingFiles"
:loading="isUploading"
size="default"
>
{{ isUploading ? '正在上传...' : '开始上传' }}
</el-button>
<el-button @click="clearList" :disabled="isUploading" size="default">
清空列表
</el-button>
<div class="stats">
共 {{ fileList.length }} 个文件 |
成功: <span class="text-success">{{ successCount }}</span> |
失败: <span class="text-danger">{{ failCount }}</span>
</div>
</div>
<!-- 成功状态 -->
<div v-else-if="getTaskStatus(file.uid)?.status === 'success'" class="status-success">
<el-tag type="success" effect="plain">完成</el-tag>
<span class="success-text">{{ getTaskStatus(file.uid)?.message }}</span>
<!-- 文件列表 -->
<div class="file-list-container">
<div
v-for="(item, index) in fileList"
:key="item.uid"
class="file-item"
:class="{ 'is-error': item.status === 'fail' }"
>
<div class="file-info">
<el-icon class="file-icon"><document /></el-icon>
<div class="file-meta">
<div class="file-name" :title="item.name">{{ item.name }}</div>
<div class="file-size">{{ formatSize(item.size) }}</div>
</div>
</div>
<!-- 失败状态 -->
<div v-else-if="getTaskStatus(file.uid)?.status === 'error'" class="status-error">
<el-tag type="danger" effect="plain">失败</el-tag>
<span class="error-text">{{ getTaskStatus(file.uid)?.message }}</span>
<el-button link type="primary" @click="startSingleUpload(file)">重试</el-button>
<div class="progress-wrapper">
<el-progress
:percentage="item.progress"
:status="getProgressStatus(item.status)"
:stroke-width="16"
>
<template v-if="item.status === 'uploading'">
<span class="speed-text">{{ item.speed }}</span>
</template>
<template v-else-if="item.status === 'success'">
<el-icon><check /></el-icon>
</template>
<template v-else-if="item.status === 'fail'">
<el-icon><close /></el-icon>
</template>
</el-progress>
</div>
<div class="action-area">
<el-tag :type="getStatusType(item.status)" size="small">
{{ statusMap[item.status] }}
</el-tag>
<el-button
v-if="item.status === 'fail'"
link
type="primary"
size="small"
@click="retryUpload(item)"
>
重试
</el-button>
<el-button
v-if="item.status === 'waiting'"
link
type="danger"
size="small"
@click="removeFile(item)"
>
移除
</el-button>
</div>
</div>
</template>
</el-upload>
<div class="global-actions" v-if="fileList.length > 0">
<el-button type="success" @click="startAllUploads" :disabled="isUploadingAny">全部开始</el-button>
<el-button type="info" @click="clearAll">清空列表</el-button>
</div>
</el-card>
</div>
</template>
<script setup>
import { ref, reactive } from 'vue';
import { Document } from '@element-plus/icons-vue';
import { ref, computed, nextTick } from 'vue';
import {
UploadFilled,
Document,
Check,
Close
} from '@element-plus/icons-vue';
import { ElMessage } from 'element-plus';
import { useMultiFileUpload } from '@/composables/useMultiFileUpload';
import OSS from 'ali-oss';
import pLimit from 'p-limit';
// 请确保路径正确
import { getStstoken, batchSaveFiles } from '@/api/common';
const props = defineProps({
tenantBizId: { type: String, required: true,default: '' },
projectBizId: { type: String, required: true, default: '' },
objectBizId: { type: String, required: true, default: '' },
projectType: { type: String, default: '' },
objectType: { type: String, default: '' },
objectTableName: { type: String, default: '' },
objectName: { type: String, default: '' },
uploadConcurrency: { type: Number, default: 3 },
partSize: { type: Number, default: 1 * 1024 * 1024 },
});
const uploadRef = ref(null);
const fileList = ref([]); // Element Plus 管理的文件列表
const fileList = ref([]);
const uiFileList = ref([]);
const isUploading = ref(false);
const ossClient = ref(null);
const statusMap = {
waiting: '等待上传',
uploading: '上传中',
success: '成功',
fail: '失败',
};
const hasPendingFiles = computed(() => {
return fileList.value.some(f => f.status === 'waiting' || f.status === 'fail');
});
// 引入我们的逻辑钩子
const { startUpload, pauseFile, resumeFile, cancelFile, getTaskStatus } = useMultiFileUpload();
const successCount = computed(() => fileList.value.filter(f => f.status === 'success').length);
const failCount = computed(() => fileList.value.filter(f => f.status === 'fail').length);
// 格式化大小
const formatSize = (bytes) => {
if (!bytes) return '';
if (!bytes) return '0 B';
const k = 1024;
const sizes = ['B', 'KB', 'MB', 'GB'];
const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
};
// 当用户选择文件时
const handleFileChange = (uploadFile, uploadFiles) => {
fileList.value = uploadFiles;
// 初始化状态为 idle (实际上 useMultiFileUpload 会在 start 时初始化)
const getProgressStatus = (status) => {
if (status === 'success') return 'success';
if (status === 'fail') return 'exception';
return undefined;
};
// 移除文件
const handleRemove = (uploadFile, uploadFiles) => {
fileList.value = uploadFiles;
cancelFile(uploadFile.uid); // 如果正在传,先取消
const getStatusType = (status) => {
const map = { waiting: 'info', uploading: 'warning', success: 'success', fail: 'danger' };
return map[status] || 'info';
};
// 单个开始上传
const startSingleUpload = (file) => {
// 这里的 file 是 element-plus 的 UploadFile 对象,raw 才是原生 File 对象
if (!file.raw) return;
// 【优化】处理文件选择(增加关闭再打开时的去重清理)
const handleFileSelect = (rawFile, uploadFiles) => {
// 如果是重新选择文件,建议先检查是否需要清理“失败”或“成功”的旧状态,视业务需求而定
// 这里保持原有逻辑,但增强了对 raw 对象的获取
// 定义回调,用于强制刷新视图(虽然 uploadTasks 是响应式的,但有时 EL 的 slot 更新不及时,可加 forceUpdate 逻辑,此处依赖响应式)
const updateCallback = (uid, status, progress, msg) => {
// 如果需要更激进的 UI 更新,可以在这里操作 fileList 中的对应项
// 但通常响应式对象足够
};
const filesToProcess = Array.isArray(rawFile) ? rawFile : [rawFile];
startUpload(file.raw, updateCallback);
};
filesToProcess.forEach(file => {
let nativeFile = null;
// 全部开始 (简单实现:遍历所有未成功的文件)
const startAllUploads = () => {
fileList.value.forEach(file => {
const status = getTaskStatus(file.uid)?.status;
if (!status || status === 'error' || status === 'idle' || status === 'paused') {
// 如果是 paused,调用 resume 逻辑可能需要特殊处理,这里简化为重新触发 start (我们的 logic 支持断点)
if (status === 'paused') {
resumeFile(file.uid);
// 优先从 file.raw 获取
if (file.raw && (file.raw instanceof File || file.raw instanceof Blob)) {
nativeFile = file.raw;
} else if (file instanceof File || file instanceof Blob) {
nativeFile = file;
} else {
startSingleUpload(file);
// 尝试从 uploadRef 中找回原生对象(防止 element-plus 包装丢失)
if (uploadRef.value && uploadRef.value.files) {
const found = uploadRef.value.files.find(f => f.name === file.name && f.size === file.size);
if (found && found.raw) {
nativeFile = found.raw;
}
}
}
if (!nativeFile) {
console.warn('Skipping invalid file:', file.name);
return;
}
// 【关键去重逻辑】
// 如果列表中已存在同名同大小的文件,且状态不是 success(允许重新上传成功的?通常不允许),则跳过
// 如果你的需求是:关闭弹窗再打开,之前的列表应该被清空。请确保在弹窗关闭事件 (@close) 中调用了 clearList()
const exists = fileList.value.some(
f => f.name === nativeFile.name && f.size === nativeFile.size && f.status !== 'success'
);
if (!exists) {
const uid = nativeFile.uid || Date.now() + Math.random();
const newItem = {
uid,
name: nativeFile.name,
size: nativeFile.size,
raw: nativeFile,
status: 'waiting',
progress: 0,
speed: '0 KB/s',
url: '',
startTime: 0,
};
fileList.value.push(newItem);
// 同步到 uiFileList,确保 el-upload 显示
// 注意:不要直接 push 到 uploadRef.value.files,而是更新绑定的 uiFileList
const uiExists = uiFileList.value.some(f => f.uid === uid);
if (!uiExists) {
uiFileList.value.push({
uid,
name: nativeFile.name,
size: nativeFile.size,
status: 'ready',
raw: nativeFile
});
}
}
});
};
const handlePause = (uid) => pauseFile(uid);
const handleResume = (uid) => resumeFile(uid);
const handleCancel = (uid) => {
cancelFile(uid);
// 从列表中移除视觉上的文件(可选,看需求是保留记录还是直接删掉)
// fileList.value = fileList.value.filter(f => f.uid !== uid);
// 【修复】移除单个文件方法
const removeFile = (item) => {
if (isUploading.value && item.status === 'uploading') {
ElMessage.warning('文件正在上传,无法移除');
return;
}
// 1. 从逻辑列表移除
fileList.value = fileList.value.filter(f => f.uid !== item.uid);
// 2. 从 UI 列表移除
uiFileList.value = uiFileList.value.filter(f => f.uid !== item.uid);
// 3. 同步清除 el-upload 内部对应的文件记录
if (uploadRef.value) {
// el-upload 的 clearFiles 通常清空所有,若要移除单个,可以直接操作 its internal files 数组
// 但最简单可靠的方法是:既然我们控制了 uiFileList,让 el-upload 依赖 uiFileList 即可
// 如果 UI 没刷新,可以强制触发一次 update
const uploadInstance = uploadRef.value;
if (uploadInstance.files) {
uploadInstance.files = uploadInstance.files.filter(f => f.uid !== item.uid);
}
}
};
// 【修复】清空列表方法
const clearList = () => {
if (isUploading.value) {
ElMessage.warning('上传进行中,无法清空');
return;
}
const clearAll = () => {
// 1. 清空内部逻辑列表
fileList.value = [];
// 清理内部状态逻辑略
};
const isUploadingAny = computed(() => {
return fileList.value.some(f => {
const s = getTaskStatus(f.uid)?.status;
return s === 'uploading' || s === 'checking' || s === 'merging';
});
});
</script>
// 2. 清空 UI 绑定列表
uiFileList.value = [];
<style scoped>
.upload-container {
max-width: 800px;
margin: 20px auto;
}
.el-upload-list__item-custom {
display: flex;
flex-direction: column;
padding: 10px;
border-bottom: 1px solid #ebeef5;
transition: background-color 0.2s;
}
// 3. 【关键】调用 Element Plus 组件实例的方法清除内部状态
if (uploadRef.value) {
uploadRef.value.clearFiles();
}
.el-upload-list__item-custom:hover {
background-color: #f5f7fa;
}
// 4. 如果是在弹窗中使用,且关闭弹窗时调用此方法,确保父组件也能感知变化(如果需要)
// emit('update:modelValue', []); // 如果有双向绑定
};
.file-info {
display: flex;
align-items: center;
margin-bottom: 8px;
}
const initOssClient = async () => {
try {
const res = await getStstoken(props.projectBizId);
if (res.code !== 200 && res.data?.code !== 200) {
throw new Error(res.msg || res.data?.msg || '获取 STS 凭证失败');
}
.file-info .el-icon {
margin-right: 8px;
color: #409eff;
font-size: 20px;
}
const data = res.data || res;
const rawData = data.data || data;
const { region, accessKeyId, accessKeySecret, stsToken, bucket, endpoint,filePrefix } = rawData;
if (!accessKeyId || !bucket) throw new Error('STS 凭证信息不完整');
let ossEndpoint = endpoint;
if (!ossEndpoint) ossEndpoint = `oss-${region}.aliyuncs.com`;
ossClient.value = new OSS({
region,
accessKeyId,
accessKeySecret,
stsToken,
bucket,
endpoint: ossEndpoint,
secure: true,
timeout: 60000,
filePrefix
});
return ossClient.value;
} catch (error) {
console.error('Init OSS Error:', error);
throw error;
}
};
.file-name {
font-weight: bold;
color: #606266;
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
margin-right: 10px;
}
const uploadSingleFile = async (item) => {
// 【核心修复】再次校验 raw 是否为原生 File
const file = item.raw;
.file-size {
font-size: 12px;
color: #909399;
}
if (!(file instanceof File) && !(file instanceof Blob)) {
console.error('Invalid file object for upload:', file);
throw new Error('文件对象无效,必须是 File 或 Blob 类型');
}
console.log(ossClient.value)
const objectName = `${ossClient.value.options.filePrefix}${props.projectBizId}${Date.now()}_${item.uid}_${file.name}`;
item.status = 'uploading';
item.startTime = Date.now();
try {
const client = ossClient.value;
await client.multipartUpload(objectName, file, {
parallel: 4,
partSize: props.partSize,
progress: (p) => {
item.progress = Math.floor(p * 100);
const duration = (Date.now() - item.startTime) / 1000;
if (duration > 0 && p > 0) {
const speed = (file.size * p) / duration;
item.speed = `${formatSize(speed)}/s`;
}
},
});
.file-action-area {
display: flex;
align-items: center;
justify-content: space-between;
}
item.status = 'success';
item.progress = 100;
item.speed = '-';
const endpoint = client.options.endpoint.host;
item.url = `https://${client.options.bucket}.${endpoint}/${objectName}`;
.status-wait, .status-success, .status-error {
display: flex;
align-items: center;
gap: 10px;
width: 100%;
}
return {
fileName: item.name,
fileSize: item.size,
fileUrl: item.url,
};
} catch (error) {
console.error(`Upload failed: ${item.name}`, error);
item.status = 'fail';
item.speed = '-';
return null;
}
};
.status-progress {
width: 100%;
display: flex;
align-items: center;
gap: 15px;
}
const startUpload = async () => {
if (!hasPendingFiles.value) return;
isUploading.value = true;
const successResults = [];
try {
await initOssClient();
const pendingItems = fileList.value.filter(f => f.status === 'waiting' || f.status === 'fail');
if (pendingItems.length === 0) return;
const limit = pLimit(props.uploadConcurrency);
const tasks = pendingItems.map(item => limit(() => uploadSingleFile(item)));
const results = await Promise.all(tasks);
results.forEach(res => { if (res) successResults.push(res); });
if (successResults.length > 0) {
const payload = {
tenantBizId: props.tenantBizId,
projectBizId: props.projectBizId,
objectBizId: props.objectBizId,
projectType:props.projectType,
objectType: props.objectType,
objectTableName: props.objectTableName,
objectName: props.objectName,
apiOssFileDtoList: successResults,
};
await batchSaveFiles(payload);
ElMessage.success(`上传完成!成功 ${successResults.length} 个文件`);
} else {
ElMessage.error('所有文件上传均失败');
}
} catch (globalError) {
console.error('Global Upload Error:', globalError);
ElMessage.error(`上传初始化失败: ${globalError.message}`);
} finally {
isUploading.value = false;
}
};
.status-progress .el-progress {
flex: 1;
}
const retryUpload = async (item) => {
if (isUploading.value) return;
item.status = 'uploading';
item.progress = 0;
try {
if (!ossClient.value) await initOssClient();
await uploadSingleFile(item);
if (item.status === 'success') ElMessage.success(`${item.name} 重试成功`);
} catch (e) { /* ignored */ }
};
.btn-group {
display: flex;
gap: 5px;
}
// 暴露方法给父组件调用
defineExpose({
clearList,
startUpload, // 如果父组件需要主动触发上传也可以暴露
fileList // 如果需要访问文件列表也可以暴露
});
</script>
.global-actions {
margin-top: 20px;
text-align: right;
border-top: 1px solid #eee;
padding-top: 15px;
<style scoped>
/* 样式保持不变 */
.big-file-uploader { width: 100%; max-width: 900px; margin: 0 auto; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; }
.control-bar { display: flex; align-items: center; gap: 15px; margin: 20px 0; padding: 15px; background: #f5f7fa; border-radius: 8px; }
.stats { margin-left: auto; font-size: 14px; color: #606266; }
.text-success { color: #67c23a; font-weight: bold; }
.text-danger { color: #f56c6c; font-weight: bold; }
.file-list-container { display: flex; flex-direction: column; gap: 12px; }
.file-item { display: grid; grid-template-columns: 250px 1fr 120px; align-items: center; gap: 20px; padding: 15px; border: 1px solid #ebeef5; border-radius: 6px; background: #fff; transition: all 0.3s; }
.file-item.is-error { border-color: #fde2e2; background-color: #fef0f0; }
.file-info { display: flex; align-items: center; gap: 10px; overflow: hidden; }
.file-icon { font-size: 24px; color: #409eff; flex-shrink: 0; }
.file-meta { display: flex; flex-direction: column; overflow: hidden; }
.file-name { font-size: 14px; color: #303133; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; font-weight: 500; }
.file-size { font-size: 12px; color: #909399; margin-top: 2px; }
.progress-wrapper { width: 100%; }
.speed-text { font-size: 12px; color: #909399; }
.action-area { display: flex; flex-direction: column; align-items: flex-end; gap: 5px; }
@media (max-width: 600px) {
.file-item { grid-template-columns: 1fr; gap: 10px; }
.action-area { flex-direction: row; justify-content: space-between; align-items: center; }
.control-bar { flex-direction: column; align-items: stretch; }
.stats { margin-left: 0; margin-top: 10px; }
}
</style>
\ No newline at end of file
......@@ -627,6 +627,9 @@ const handleAppendInput = child => {
const handleInputChange = (val, child) => {
let newVal = numberFormat(val, child)
form.value[child.key] = newVal
if (child.key == 'nameCn' && !form.value['nameCn']) {
form.value['namePyEn'] = ''
}
}
const handleInputBlur = child => {
if (
......@@ -755,7 +758,9 @@ const handleDateChange = child => {
let age = null
if (child.key == 'birthday') {
age = calculateExactAge(proxy.formatToDate(form.value.birthday))
if (age >= 18) {
if (age >= 18 && props.activeName !== 'insurantInfo') {
form.value.age = age
} else if (props.activeName == 'insurantInfo') {
form.value.age = age
}
}
......@@ -1120,7 +1125,7 @@ const setFormValue = (obj, formData, exportValue) => {
// 深拷贝原始数据
form.value = JSON.parse(JSON.stringify(obj))
// 新增预约,是受保人,与投保人关系默认值是本人
if (props.activeName == 'insurantInfo' && !props.idsObj.appointmentBizId) {
if (props.activeName == 'insurantInfo' && !props.idsObj.appointmentBizId && !exportValue) {
form.value['policyholderRel'] = 'MYSELF'
}
// 深拷贝原始数据
......@@ -1269,7 +1274,10 @@ const setFormValue = (obj, formData, exportValue) => {
} else {
form.value.age = null
}
// 使用历史客户导入的,要保持原来的与投保人关系的值
if (exportValue) {
form.value['policyholderRel'] = oldObjInfo.value['policyholderRel']
}
phoneQuickList.value = removeDuplicates(tempPhoneList, 'phoneString')
addressQuickList.value = removeDuplicates(tempAddressList, 'addressString')
if (addressQuickList.value.length > 0) {
......@@ -1398,6 +1406,18 @@ const handleFormValues = source => {
//处理表单数据
for (const key1 in form.value) {
if (key1 == 'nameCn' && submitObj[key1]) {
if (submitObj[key1].length < 2) {
errorFields.value.push({
message: `${tipName}-名字至少为2个字符`
})
}
if (submitObj[key1].length > 6) {
errorFields.value.push({
message: `${tipName}-名字长度不能超过6个字符`
})
}
}
for (const key2 in saveKey.value) {
//要判断drawerType
switch (saveKey.value[key2].objType) {
......
......@@ -12,7 +12,7 @@
<span class="iconfont icon-yanqiweiwancheng"></span>
<span
>{{ parseTime(processInfo.createTime) }}{{
processInfo.customerName || '--'
processInfo.userBizId || '--'
}}创建</span
>
</div>
......@@ -321,7 +321,7 @@ const getDictsData = async () => {
projectBizId: userStore.projectInfo.projectBizId,
tenantBizId: userStore.projectInfo.tenantBizId,
fieldBizId: 'field_olk1qZe81qHHKXbw',
fieldValueBizId: 'field_value_yXzTigvgUdRMFpoR'
fieldValueBizId: 'field_value_yXzTigvgUdRMFpoR',
}
const response6 = await secondAdditonalList(params6)
if (response6.code == 200) {
......
......@@ -695,27 +695,6 @@ const handleFileChange = async file => {
return false // 阻止自动上传
}
const execlUpload = () => {
// exprotLoading.value = true
// let data = {
// // appointmentBizId: idsObj.value.appointmentBizId,
// file: currentFile.value
// }
// try {
// if (currentFile.value) {
// uploadExcel(data).then(res => {
// if (res.code == 200) {
// proxy.$message.success('预约导入成功')
// execlDialog.value = false
// exprotLoading.value = false
// }
// })
// }
// } catch (error) {
// proxy.$message.error(error.message)
// exprotLoading.value = false
// }
}
// 获取预约详情
function getAppointmentInfo(appointmentBizId, changeTab) {
getAppointmentDetail(appointmentBizId).then(res => {
......@@ -1061,8 +1040,9 @@ const handleSubmit = async type => {
if (type == 'submit') {
submitLoading.value = true
// 新增预约单
addAppointment(submitAppointmentObj.value).then(res => {
if (res.code == 200) {
try {
const res = await addAppointment(submitAppointmentObj.value)
if (res.code === 200) {
submitLoading.value = false
proxy.$message.success('预约成功')
editStatus.value = false
......@@ -1071,11 +1051,16 @@ const handleSubmit = async type => {
type: 'add'
})
} else {
console.log('出错了', res)
submitLoading.value = false
}
})
} catch (error) {
console.error('新增请求异常:', error)
submitLoading.value = false
}
}
return
}
// 代表修改预约单 因为编辑暂存也会有预约id
if (idsObj.value.appointmentBizId && type == 'submit') {
// 已生成行程单的方式预约提交
......@@ -1084,7 +1069,18 @@ const handleSubmit = async type => {
}
submitLoading.value = true
// 编辑预约单
editAppointmentDetail(submitAppointmentObj.value).then(res => {
// editAppointmentDetail(submitAppointmentObj.value).then(res => {
// if (res.code == 200) {
// submitLoading.value = false
// editStatus.value = true
// getAppointmentInfo(idsObj.value.appointmentBizId)
// proxy.$message.success('修改预约单成功')
// } else {
// submitLoading.value = false
// }
// })
try {
const res = await editAppointmentDetail(submitAppointmentObj.value)
if (res.code == 200) {
submitLoading.value = false
editStatus.value = true
......@@ -1093,7 +1089,11 @@ const handleSubmit = async type => {
} else {
submitLoading.value = false
}
})
} catch (error) {
console.error('修改请求异常:', error)
submitLoading.value = false
}
return
}
// 新增预约暂存
if (type == 'storage') {
......@@ -1103,7 +1103,17 @@ const handleSubmit = async type => {
submitAppointmentObj.value.apiAppointmentInfoDto.fnaNo = props.processDetail.fnaNo
submitLoading.value = true
// 暂存预约单
storageAppointment(submitAppointmentObj.value).then(res => {
// storageAppointment(submitAppointmentObj.value).then(res => {
// if (res.code == 200) {
// submitLoading.value = false
// proxy.$message.success('预约暂存成功')
// router.push({ path: '/sign/appointment' })
// } else {
// submitLoading.value = false
// }
// })
try {
const res = await storageAppointment(submitAppointmentObj.value)
if (res.code == 200) {
submitLoading.value = false
proxy.$message.success('预约暂存成功')
......@@ -1111,13 +1121,28 @@ const handleSubmit = async type => {
} else {
submitLoading.value = false
}
})
} catch (error) {
console.error('暂存请求异常:', error)
submitLoading.value = false
}
}
// 编辑状态下预约暂存
if (type == 'editStorage') {
submitLoading.value = true
// 暂存预约单
appointmentEditStorage(submitAppointmentObj.value).then(res => {
// appointmentEditStorage(submitAppointmentObj.value).then(res => {
// if (res.code == 200) {
// submitLoading.value = false
// proxy.$message.success('预约暂存提交成功')
// editStatus.value = true
// getAppointmentInfo(idsObj.value.appointmentBizId)
// } else {
// submitLoading.value = false
// }
// })
try {
const res = await appointmentEditStorage(submitAppointmentObj.value)
if (res.code == 200) {
submitLoading.value = false
proxy.$message.success('预约暂存提交成功')
......@@ -1126,7 +1151,10 @@ const handleSubmit = async type => {
} else {
submitLoading.value = false
}
})
} catch (error) {
console.error('预约暂存请求异常:', error)
submitLoading.value = false
}
}
}
const handleEdit = () => {
......
......@@ -867,7 +867,7 @@ const viewHistory = () => {
}
const handleTableSelectChange = (father, row, key) => {
searchOptions.value[key].forEach(item => {
console.log('item', item)
// console.log('item', item)
if (row[key] == item.value) {
row[key] = item.label
// row.userBizId = item.userBizId
......@@ -879,7 +879,10 @@ const handleTableSelectChange = (father, row, key) => {
row.email = item.email
row.cardType = item.cardType
} else if (key == 'realName') {
// console.log('转介人',item)
row.userSaleBizId = item.value
row.phone = item.phone
row.email = item.email
} else if (key == 'contractingCompanyName') {
row.contractingCompanyId = item.value
}
......@@ -963,7 +966,7 @@ const searchSelectList = async (query, key) => {
} else if (key == 'realName') {
const params5 = {
pageNo: 1,
pageSize: 10,
pageSize: 999,
realName: queryString
}
const response5 = await getUserSaleExpandList(params5)
......@@ -980,7 +983,7 @@ const searchSelectList = async (query, key) => {
} else if (key == 'name') {
const params5 = {
pageNo: 1,
pageSize: 10,
pageSize: 999,
realName: queryString
}
const response5 = await getAllSignList(params5)
......
......@@ -57,13 +57,13 @@
@click="handleUpdate(scope.row)"
>查看</el-button
>
<el-button
<!-- <el-button
v-if="props.pageSource !== 'policyList'"
type="danger"
link
@click="handleDetele(scope.row)"
>删除</el-button
>
> -->
</template>
</el-table-column>
</el-table>
......@@ -85,6 +85,7 @@
<div class="fileUploadBox">
<el-upload
:action="uploadImgUrl"
:data="{ projectBizId: userStore.projectInfo.projectBizId }"
:headers="headers"
multiple
:limit="limit"
......@@ -138,6 +139,8 @@ 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'
const userStore = useUserStore()
import {
uploadMaterialList,
delUploadFile,
......@@ -395,7 +398,7 @@ const uploadSuccess = (res, file, fileList) => {
// 删除已上传的文件(仅从列表移除,不调用后端删除)
const removeUploadedFile = (file, index) => {
try {
delUploadFile(file.fileBizId).then(response => {
delUploadFile(file.fileBizId, userStore.projectInfo.projectBizId).then(response => {
uploadedFiles.value.splice(index, 1)
})
} catch (error) {
......
......@@ -38,7 +38,7 @@
:type="child.inputType"
v-model="form[father.key][child.key]"
:placeholder="child.placeholder"
:maxlength="child.maxlength|| 50"
:maxlength="child.maxlength || 50"
:disabled="editStatus"
:style="{ width: child.inputWidth ? child.inputWidth : '100%' }"
@input="val => handleInputChange(val, child, father)"
......@@ -310,7 +310,7 @@ const data = reactive({
dialogForm: {},
queryParams: {
pageNo: 1,
pageSize: 4,
pageSize: 10,
name: undefined
}
})
......@@ -463,6 +463,7 @@ const handleSearchSelectChange = (father, key) => {
}
form.value['apiProductPlanMainInfoDto']['insuranceTypeName'] = item.label
form.value['apiProductPlanMainInfoDto']['insuranceTypeId'] = item.value
form.value['apiProductPlanMainInfoDto']['insuranceTypeCode'] = item.code
}
})
}
......@@ -497,7 +498,7 @@ const searchSelectList = async (query, fieldKey) => {
loginTenantBizId: userStore.projectInfo.tenantBizId,
productName: query.trim(),
pageNo: 1,
pageSize: 10
pageSize: 999
}
const response = await getInsuranceProductList(params)
......@@ -517,16 +518,21 @@ const searchSelectList = async (query, fieldKey) => {
const params = {
productName: query.trim(),
pageNo: 1,
pageSize: 10,
pageSize: 999,
projectBizId: userStore.projectInfo.projectBizId,
tenantBizId: userStore.projectInfo.tenantBizId,
fieldBizId: 'field_olk1qZe81qHHKXbw',
fieldValueBizId:
fieldKey === 'productLaunchName'
? 'field_value_yXzTigvgUdRMFpoR'
: 'field_value_uOfJH5ucA2YwJpbn'
: 'field_value_uOfJH5ucA2YwJpbn',
categoryCodeList: form.value.apiProductPlanMainInfoDto.insuranceTypeCode
? [form.value.apiProductPlanMainInfoDto.insuranceTypeCode]
: [],
insuranceCompanyBizIdList: form.value.apiProductPlanMainInfoDto.companyId
? [form.value.apiProductPlanMainInfoDto.companyId]
: []
}
const response = await secondAdditonalList(params)
if (response.code == 200) {
response.data.records = response.data.records.map(item => {
......@@ -541,7 +547,7 @@ const searchSelectList = async (query, fieldKey) => {
} else if (fieldKey === 'companyName') {
const params9 = {
pageNo: 1,
pageSize: 10,
pageSize: 999,
queryContent: query.trim()
}
const response9 = await getInsuranceCompany(params9)
......@@ -566,7 +572,7 @@ const searchSelectList = async (query, fieldKey) => {
}
const params1 = {
pageNo: 1,
pageSize: 10,
pageSize: 999,
name: query.trim()
}
const response1 = await getInsuranceCategory(params1)
......@@ -583,7 +589,7 @@ const searchSelectList = async (query, fieldKey) => {
} else if (fieldKey == 'reconciliationCompanyName') {
const params1 = {
pageNo: 1,
pageSize: 10,
pageSize: 999,
name: query.trim()
}
const response1 = await insuranceReconciliationCompany(params1)
......
......@@ -523,7 +523,7 @@ const affirmConfig = [
{
type: 'date',
prop: 'policyExpirationDate',
label: '保单截日',
label: '保单截日',
placeholder: '请选择',
// maxDate: 'today',
visible: formData =>
......@@ -582,6 +582,7 @@ const confirmAffirm = async () => {
currentRow.value = {}
}
} catch (error) {
settingAffirmLoading.value = false
console.error('加载数据失败:', error)
ElMessage.error('必填项不能为空' || '认定失败')
}
......
......@@ -423,8 +423,8 @@ const loadTableData = async () => {
// 表格操作菜单
const dropdownItems = [
{ label: '查看详情', value: 'viewDetail' },
{ label: '更新数据', value: 'updateData' },
{ label: '查看保单详情', value: 'viewDetail' },
{ label: '补充保单信息', value: 'updateData' },
{ label: '生成签单报告', value: 'generateReport' },
{ label: '设置新单状态', value: 'setNewSingleStatus' },
// { label: '查看关联', value: 'viewRelated' },
......
......@@ -157,6 +157,20 @@
/>
<el-table-column fixed="right" label="操作" min-width="120">
<template #default="scope">
<div style="display: flex; gap: 8px;">
<!-- 👇 新增:查看按钮 -->
<!-- <el-button
link
type="primary"
size="small"
@click="viewFile(scope.row)"
:disabled="!scope.row.fileUrl"
>
查看
</el-button> -->
<!-- 原有:删除按钮 (带确认框) -->
<el-popconfirm title="确定删除吗?" @confirm="deleteFile(scope.row)">
<template #reference>
<el-button
......@@ -169,6 +183,8 @@
</el-button>
</template>
</el-popconfirm>
</div>
</template>
</el-table-column>
</el-table>
......@@ -189,13 +205,20 @@
dialogTitle="文件导入"
dialogWidth="80%"
:openDialog="fileUploadDialogFlag"
:showAction="true"
:showAction="false"
:showClose="true"
@close="((fileUploadDialogFlag = false), (files = ''))"
@confirm="((fileUploadDialogFlag = false), (files = ''))"
@close="handleDialogClose"
>
<!-- <FileUploader/> -->
<FileUpload
<FileUploader
:tenant-biz-id="userStore.projectInfo.tenantBizId"
:project-biz-id="userStore.projectInfo.projectBizId"
:object-biz-id="props.policyBizId"
:object-type="'DOCUMENT'"
:project-type="'pc'"
:upload-concurrency="3"
ref="uploaderRef"
/>
<!-- <FileUpload
v-model="files"
:data="{ obiectTableName: 'policy_follow', objectBizId: props.policyBizId }"
:file-type="['xlsx', 'xls', 'doc', 'docx', 'pdf', 'txt', 'jpg', 'jpeg', 'png', 'gif']"
......@@ -204,7 +227,7 @@
:fileSize="15"
:name="'files'"
@uploadEnd="handleUploadEnd"
/>
/> -->
</CommonDialog>
</div>
</template>
......@@ -266,7 +289,17 @@ const props = defineProps({
default: ''
}
})
const uploaderRef = ref(null);
// 【关键】当弹窗关闭时,强制清空上传组件列表
const handleDialogClose = () => {
if (uploaderRef.value) {
// 调用子组件暴露的清空方法
// 你需要在子组件中使用 defineExpose 暴露 clearList 方法
uploaderRef.value.clearList();
fileUploadDialogFlag.value = false
getAttachmentListDetail(props.policyBizId)
}
};
const emit = defineEmits(['update:modelValue', 'submit', 'cancel', 'saveRow'])
const introducerTableData = ref([])
......@@ -656,7 +689,7 @@ const basicPlanFormConfig = ref([
valueKey: 'productName',
labelKey: 'productName',
transform: res => {
console.log('======子组件选择选项后,父组件接收的值产品名称:', res?.data.records || [])
// console.log('======子组件选择选项后,父组件接收的值产品名称:', res?.data.records || [])
return res?.data.records || []
},
onChangeExtraFields: {
......@@ -896,7 +929,7 @@ const attachmentTableColumns = ref([
prop: 'originalName',
label: '文件名',
sortable: true,
width: '150',
width: '300',
formatter: row => row.originalName || '-'
},
{
......@@ -910,8 +943,8 @@ const attachmentTableColumns = ref([
prop: 'createTime',
label: '上传时间',
sortable: true,
width: '150',
formatter: row => row.createTime || '-'
width: '200',
formatter: row => formatToDateTime(row.createTime || '-')
},
{
prop: 'creatorName',
......@@ -1100,7 +1133,7 @@ const handleCancel = () => {
const deleteFile = (row)=>{
console.log(row)
const fileBizId = row.fileBizId || '';
delUploadFile(fileBizId).then(res=>{
delUploadFile(fileBizId,userStore.projectInfo.projectBizId).then(res=>{
console.log(res)
if(res.code===200){
getAttachmentListDetail(props.policyBizId)
......
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