diff --git a/src/api/system1.ts b/src/api/system1.ts index 146412c..64380b3 100644 --- a/src/api/system1.ts +++ b/src/api/system1.ts @@ -402,8 +402,31 @@ export const fileApi = { return request({ url: '/sys/file/upload', method: 'post', - data: formData, - headers: { 'Content-Type': 'multipart/form-data' } + data: formData + // headers: { 'Content-Type': 'multipart/form-data' } + }) + }, + + uploadAndDingding(file: File, path?: string, groupId?: number | null): Promise{ + const formData = new FormData() + formData.append('file', file) + if (path) { + formData.append('path', path) + } + if (groupId !== undefined && groupId !== null && groupId > 0) { + formData.append('groupId', groupId.toString()) + } + return request({ + url: '/sys/file/uploadAndDingding', + method: 'post', + data: formData + }) + }, + + getApprovalSpaceId() { + return request({ + url:'sys/file/getApprovalSpaceId', + method:'get', }) }, diff --git a/src/utils/request.ts b/src/utils/request.ts index 3dedbd0..b499d5e 100644 --- a/src/utils/request.ts +++ b/src/utils/request.ts @@ -125,6 +125,12 @@ service.interceptors.request.use( if (userStore.token) { config.headers['Authorization'] = userStore.token } + + if (config.data instanceof FormData) { + // 文件上传:不设置 JSON 请求头、不序列化,交给浏览器自动处理 multipart + return config + } + config.headers["Content-Type"] = 'application/json;charset=utf-8' config.data = config.data instanceof Object ? JSON.stringify(config.data) : config.data return config diff --git a/src/views/biz/concessionApply/index.vue b/src/views/biz/concessionApply/index.vue index 78c5cda..a81d5f3 100644 --- a/src/views/biz/concessionApply/index.vue +++ b/src/views/biz/concessionApply/index.vue @@ -49,13 +49,28 @@ :columns="columns" :data="tableData" :loading="loading" - :pagination="pagination" - :row-key="(row) => row.id" - :scroll-x="1200" - @update:page="handlePageChange" - @update:page-size="handlePageSizeChange" - @update:checked-row-keys="handleCheck" + size="small" + remote + :border="false" + :single-line="false" + striped /> +
+ + + +
@@ -125,13 +140,13 @@ - - + +
审批编号: {{ detailModel?.businessId }}
- +
@@ -214,6 +229,12 @@ import { SearchOutline, RefreshOutline, AddOutline, TrashOutline, CreateOutline, import { concessionApplyApi, DetailModel, type ConcessionApply } from '@/api/concessionApply' import { dictDataApi } from '@/api/org' +import html2canvas from 'html2canvas' +import { jsPDF } from 'jspdf' +import * as dd from 'dingtalk-jsapi'; + + + const message = useMessage() const dialog = useDialog() @@ -551,6 +572,149 @@ const getRemark=(item) =>{ } +async function downloadOutline() { + const model = detailModel.value + if (!model) { + alert("暂无审批数据") + return + } + + // 1. 动态拼接审批记录行 HTML(彻底修复表格结构) + let recordHtml = "" + const recordList = model.approveRecord || [] + recordList.forEach((item, index) => { + const remark = item.remark ?? "" + // 第一条记录需要带左侧的「审批人」单元格,后续记录只显示右侧内容 + if (index === 0) { + recordHtml += ` + + 审批人 + +
+
+ ${remark ? remark + "
" : ""} + ${item.username ?? "未知人员"} ${item.operationResult ?? ""} +
+ ${item.date ?? ""} +
+ + + ` + } else { + // 后续记录只需要右侧的内容单元格,左侧被 rowspan 合并 + recordHtml += ` + + +
+
+ ${remark ? remark + "
" : ""} + ${item.username ?? "未知人员"} ${item.operationResult ?? ""} +
+ ${item.date ?? ""} +
+ + + ` + } + }) + + // 2. 动态印章 + let sealText = "" + let sealBorder = "#00b42a" + let sealColor = "#00b42a" + if (model.approveResult === "已通过") { + sealText = "已通过" + } else if (model.approveResult === "已驳回") { + sealText = "已驳回" + sealBorder = "#f53f3f" + sealColor = "#f53f3f" + } else if (model.approveResult === "已撤销") { + sealText = "已撤销" + sealBorder = "#ff7d00" + sealColor = "#ff7d00" + } + + const createUser = recordList[0]?.username ?? "" + const nowTime = new Date().toLocaleString() + + // 3. 完整 HTML(修复了表格结构) + const html = ` +
+
通用审批
+
+ ${model.title ?? ""} + 创建时间:${model.createTime ?? ""} +
+ + + + + + + + + + + + + + + + + + + + + + + + ${recordHtml} +
审批编号${model.businessId ?? ""}
创建人${createUser}创建人部门${model.deptName ?? ""}
申请内容${model.content ?? ""}
审批详情${model.approveResult ?? ""}
附件
+
+ 打印时间:${nowTime} + 打印人:${createUser} +
+ ${sealText ? ` +
+ ${sealText} +
+ ` : ""} +
+ ` + + // ========== 导出PDF逻辑 ========== + const tempDiv = document.createElement('div') + tempDiv.innerHTML = html + tempDiv.style.position = 'absolute' + tempDiv.style.left = '-9999px' + tempDiv.style.top = '0' + document.body.appendChild(tempDiv) + + try { + const canvas = await html2canvas(tempDiv, { + scale: 2, + useCORS: true, + logging: false, + backgroundColor: '#ffffff', + allowTaint: true + }) + + const imgData = canvas.toDataURL('image/jpeg', 1.0) + const pdf = new jsPDF('p', 'mm', 'a4') + const a4W = 210 + const imgH = (canvas.height * a4W) / canvas.width + + pdf.addImage(imgData, 'JPEG', 0, 0, a4W, imgH) + pdf.save(`通用审批_${model.businessId || "审批记录"}.pdf`) + } catch (err) { + console.error("导出失败:", err) + alert("导出PDF失败") + } finally { + document.body.removeChild(tempDiv) + } +} + + onMounted(() => { loadData() loadDictOptions() diff --git a/src/views/biz/qualityTesting/index.vue b/src/views/biz/qualityTesting/index.vue index efb0b86..23bc07d 100644 --- a/src/views/biz/qualityTesting/index.vue +++ b/src/views/biz/qualityTesting/index.vue @@ -384,6 +384,7 @@ import {fileApi} from '@/api/system1' import value = Language.value; import {QcResultDetail} from "@/api/qcResultDetail.ts"; import router from "@/router"; +import dd from 'dingtalk-jsapi' const message = useMessage() const dialog = useDialog() @@ -1014,20 +1015,25 @@ async function handleSubmitResultDetail() { //提交部分判定明细 function submitBFResultDetail(row) { - Object.assign(formDetailOKData, reactive({ ...defaultFormDetailData, resultType: 1,handleAction:'下一道工序',actionStatus:1 })) - Object.assign(formDetailScrapData, reactive({ ...defaultFormDetailData,resultType: 2,handleAction:'报废次品',actionStatus:1 })) - Object.assign(formDetailReworkData, reactive({ ...defaultFormDetailData,resultType: 3,handleAction:'返工',actionStatus:1 })) - Object.assign(formDetailConcessionData, reactive({ ...defaultFormDetailData,resultType: 4,handleAction:'让步接收',actionStatus:1 })) + // Object.assign(formDetailOKData, reactive({ ...defaultFormDetailData, resultType: 1,handleAction:'下一道工序',actionStatus:1 })) + // Object.assign(formDetailScrapData, reactive({ ...defaultFormDetailData,resultType: 2,handleAction:'报废次品',actionStatus:1 })) + // Object.assign(formDetailReworkData, reactive({ ...defaultFormDetailData,resultType: 3,handleAction:'返工',actionStatus:1 })) + // Object.assign(formDetailConcessionData, reactive({ ...defaultFormDetailData,resultType: 4,handleAction:'让步接收',actionStatus:1 })) if (row.traceType != 1) { traceCodeShow.value = true } submitFlag.value = false - submitDetailModalVisible.value = true - formDetailOKData.qcId = row.id - formDetailScrapData.qcId = row.id - formDetailReworkData.qcId = row.id - formDetailConcessionData.qcId = row.id + // submitDetailModalVisible.value = true + // formDetailOKData.qcId = row.id + // formDetailScrapData.qcId = row.id + // formDetailReworkData.qcId = row.id + // formDetailConcessionData.qcId = row.id + + + //version-2 + let path = `/qc/submitDetail/${row.qcNo}/${row.id}/${submitFlag.value}/${traceCodeShow.value}` + router.push(path) } @@ -1058,8 +1064,14 @@ function handleCancelQuality(row) { } +const isDingTalkEnv = () => { + // 确保 dd 对象存在,并且其 platform 属性不是 'notInDingTalk' + return typeof dd !== 'undefined' && dd.env && dd.env.platform !== 'notInDingTalk'; +}; + //上传附件 async function handleUploadChange ({ file, fileList: list }: any) { + // 允许的后缀白名单 const allowExt = ['pdf','doc','docx','xls','xlsx','jpg','jpeg','png','gif','bmp'] const invalidFiles:any[] = [] @@ -1081,7 +1093,7 @@ async function handleUploadChange ({ file, fileList: list }: any) { fileList.value = list console.log('选中的文件列表', fileList.value) - // ✅关键:只筛选【pending等待上传】的文件,success/error/removed全部跳过,不再重复上传 + const validList = list.filter(item => item.status === 'pending' && item.file && item.status !== 'error' ) @@ -1096,19 +1108,20 @@ async function handleUploadChange ({ file, fileList: list }: any) { const uploadPromises = validList.map(async (item: any) => { try { - const res = await fileApi.upload(item.file, null, null) + const res = await fileApi.uploadAndDingding(item.file, null, null) uploadFiles.value.push(res) const targetFile = fileList.value.find(f => f.id === item.id) if (targetFile) { targetFile.status = 'success' targetFile.percentage = 100 - } + } } catch (error) { console.error('文件上传失败', error) const targetFile = fileList.value.find(f => f.id === item.id) if (targetFile) targetFile.status = 'error' } }) + await Promise.all(uploadPromises) } diff --git a/src/views/biz/qualityTesting/submitDetail.vue b/src/views/biz/qualityTesting/submitDetail.vue new file mode 100644 index 0000000..c316535 --- /dev/null +++ b/src/views/biz/qualityTesting/submitDetail.vue @@ -0,0 +1,281 @@ + + + \ No newline at end of file