SWX\10484 5 days ago
commit 480c0c6531
  1. 85
      src/utils/requestMinio.js
  2. 147
      src/views/cases/detail.vue

@ -49,8 +49,14 @@ export function getMinioClient() {
*/ */
export async function uploadFile(bucket, objectName, file, metaData = {}, onProgress) { export async function uploadFile(bucket, objectName, file, metaData = {}, onProgress) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
// 检查 MinIO 客户端是否初始化
if (!s3Client) { if (!s3Client) {
return reject(new Error('MinIO client not initialized')) console.error('❌ MinIO client not initialized, attempting fallback upload...')
// 回退到使用 HTTP 表单上传
return fallbackUpload(bucket, objectName, file, metaData, onProgress)
.then(resolve)
.catch(reject)
} }
console.log('=== MinIO Upload ===') console.log('=== MinIO Upload ===')
@ -90,9 +96,82 @@ export async function uploadFile(bucket, objectName, file, metaData = {}, onProg
}) })
}) })
.catch((err) => { .catch((err) => {
console.error('❌ Upload failed:', err) console.error('❌ AWS SDK Upload failed:', err)
reject(new Error('Upload failed: ' + err.message)) console.log('Attempting fallback upload...')
// 尝试回退上传
fallbackUpload(bucket, objectName, file, metaData, onProgress)
.then(resolve)
.catch((fallbackErr) => {
reject(new Error('Upload failed: ' + fallbackErr.message))
})
})
})
}
/**
* 回退上传方法 - 使用 HTTP POST 表单上传
* @param {string} bucket - 存储桶名称
* @param {string} objectName - 对象名称
* @param {File} file - 文件对象
* @param {object} metaData - 元数据
* @param {function} onProgress - 进度回调
* @returns {Promise} - 上传结果
*/
async function fallbackUpload(bucket, objectName, file, metaData, onProgress) {
return new Promise((resolve, reject) => {
const config = window._globalConfig || {}
const uploadUrl = config.MINIO_UPLOAD_URL || '/api/upload/minio'
console.log('=== Fallback Upload ===')
console.log('Upload URL:', uploadUrl)
console.log('Bucket:', bucket)
console.log('Object:', objectName)
const formData = new FormData()
formData.append('bucket', bucket)
formData.append('objectName', objectName)
formData.append('file', file, file.name)
// 添加元数据
for (const key in metaData) {
formData.append('meta_' + key, metaData[key])
}
const xhr = new XMLHttpRequest()
xhr.upload.addEventListener('progress', (event) => {
if (event.lengthComputable && onProgress) {
const percent = Math.round((event.loaded / event.total) * 100)
onProgress(percent)
}
})
xhr.addEventListener('load', () => {
try {
const response = JSON.parse(xhr.responseText)
if (xhr.status >= 200 && xhr.status < 300) {
console.log('✅ Fallback upload successful:', response)
resolve({
bucket,
objectName: response.objectName || objectName,
etag: response.etag,
url: response.url || `${uploadUrl}/${bucket}/${objectName}`
})
} else {
reject(new Error(response.message || 'Upload failed'))
}
} catch (e) {
reject(new Error('Failed to parse response: ' + e.message))
}
}) })
xhr.addEventListener('error', () => {
reject(new Error('Network error during upload'))
})
xhr.open('POST', uploadUrl, true)
xhr.send(formData)
}) })
} }

@ -283,7 +283,7 @@
v-model="form.text_comment" v-model="form.text_comment"
type="textarea" type="textarea"
:rows="9" :rows="9"
:disabled=" :readonly="
form.status != 1 && form.status != 1 &&
form.status != 5 && form.status != 5 &&
form.status != 15 form.status != 15
@ -297,7 +297,7 @@
v-model="form.text_conclusion" v-model="form.text_conclusion"
type="textarea" type="textarea"
:rows="4" :rows="4"
:disabled=" :readonly="
form.status != 1 && form.status != 1 &&
form.status != 5 && form.status != 5 &&
form.status != 15 form.status != 15
@ -319,10 +319,12 @@
v-model="item.user_comment" v-model="item.user_comment"
type="textarea" type="textarea"
:rows="3" :rows="3"
:disabled=" :readonly="
form.status != 1 && (form.status != 1 &&
form.status != 5 && form.status != 5 &&
form.status != 15 form.status != 15) ||
item.confirm > 0 ||
item.expert_id != userInfo.id
" "
/> />
<div class="expert-btns"> <div class="expert-btns">
@ -330,10 +332,13 @@
type="text" type="text"
icon="el-icon-document-copy" icon="el-icon-document-copy"
:disabled=" :disabled="
form.status != 1 && (form.status != 1 &&
form.status != 5 && form.status != 5 &&
form.status != 15 form.status != 15) ||
!item.user_comment ||
!item.user_comment.trim()
" "
@click="handleCopyAllExpertComments(item)"
> >
全部复制 全部复制
</el-button> </el-button>
@ -345,18 +350,25 @@
form.status != 5 && form.status != 5 &&
form.status != 15 form.status != 15
" "
v-if="item.confirm == 0" v-if="
item.confirm == 0 && item.expert_id == userInfo.id
"
> >
一键同意 一键同意
</el-button> </el-button>
<el-button
type="text"
icon="el-icon-check"
disabled
v-else-if="item.confirm > 0"
>
专家已确认
</el-button>
<el-button <el-button
type="text" type="text"
icon="el-icon-warning" icon="el-icon-warning"
:disabled=" disabled
form.status != 1 && v-else
form.status != 5 &&
form.status != 15
"
> >
待确认 待确认
</el-button> </el-button>
@ -1484,6 +1496,110 @@ export default {
} }
}); });
}, },
async handleCopyAllExpertComments(item) {
const text = item.user_comment || "";
if (!text || !text.trim()) {
// this.$modal.msgWarning('');
return;
}
try {
if (navigator.clipboard && window.isSecureContext) {
console.log("复制策略 - 使用 Clipboard API");
await navigator.clipboard.writeText(text);
this.$modal.msgSuccess("复制成功");
} else {
console.log("复制策略 - 使用 execCommand fallback");
const textarea = document.createElement("textarea");
textarea.value = text;
textarea.style.position = "fixed";
textarea.style.left = "-9999px";
document.body.appendChild(textarea);
textarea.select();
const success = document.execCommand("copy");
document.body.removeChild(textarea);
if (success) {
console.log("execCommand 返回 true");
this.$modal.msgSuccess("复制成功");
} else {
console.log("execCommand 返回 false,使用兜底方案");
this.showCopyFallback(text);
}
}
} catch (err) {
console.error("复制失败 - 捕获异常:", err);
this.showCopyFallback(text);
}
},
showCopyFallback(text) {
const dialog = document.createElement("div");
dialog.style.cssText = `
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: #fff;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 12px rgba(0,0,0,0.15);
z-index: 9999;
max-width: 80%;
max-height: 80vh;
overflow-y: auto;
`;
const title = document.createElement("h3");
title.textContent = "复制内容";
title.style.margin = "0 0 12px 0";
title.style.fontSize = "16px";
dialog.appendChild(title);
const textarea = document.createElement("textarea");
textarea.value = text;
textarea.style.width = "400px";
textarea.style.height = "200px";
textarea.style.marginBottom = "12px";
textarea.style.padding = "8px";
textarea.style.border = "1px solid #e4e7ed";
textarea.style.borderRadius = "4px";
textarea.addEventListener("focus", () => textarea.select());
dialog.appendChild(textarea);
const btn = document.createElement("button");
btn.textContent = "确定";
btn.style.cssText = `
display: block;
margin: 0 auto;
padding: 8px 24px;
background: #409eff;
color: #fff;
border: none;
border-radius: 4px;
cursor: pointer;
`;
btn.addEventListener("click", () => {
document.body.removeChild(dialog);
document.body.removeChild(mask);
});
dialog.appendChild(btn);
const mask = document.createElement("div");
mask.style.cssText = `
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0,0,0,0.5);
z-index: 9998;
`;
mask.addEventListener("click", () => {
document.body.removeChild(dialog);
document.body.removeChild(mask);
});
document.body.appendChild(mask);
document.body.appendChild(dialog);
textarea.select();
},
}, },
}; };
</script> </script>
@ -1613,9 +1729,4 @@ export default {
display: inline-block; display: inline-block;
cursor: pointer; cursor: pointer;
} }
//
::v-deep .el-textarea__inner:disabled {
color: #000;
}
</style> </style>
Loading…
Cancel
Save