From b3fedd4221b807a07058be9d5d5d8ba8998adbcb Mon Sep 17 00:00:00 2001 From: Bang Hu <hu_bang@hotmail.com> Date: 星期四, 11 九月 2025 21:35:31 +0800 Subject: [PATCH] Bug修改代码提交 --- src/views/approveManage/tradeApproval/approve.vue | 1231 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 files changed, 1,182 insertions(+), 49 deletions(-) diff --git a/src/views/approveManage/tradeApproval/approve.vue b/src/views/approveManage/tradeApproval/approve.vue index b9fa7c0..5c64ccb 100644 --- a/src/views/approveManage/tradeApproval/approve.vue +++ b/src/views/approveManage/tradeApproval/approve.vue @@ -1,93 +1,1226 @@ <template> <div class="default-main"> + <!-- 璁㈠崟淇℃伅 + 鐢宠浜轰俊鎭� + 浜ゆ槗鍐呭锛堝悎骞朵负鍚屼竴鍗$墖锛� --> <el-card shadow="never"> - <div class="title">璁㈠崟淇℃伅</div> - <el-descriptions :column="3" border class="mt10"> + <el-descriptions + :column="2" + border + class="mt10 order-desc fixed-label" + label-width="180px" + :label-style="labelStyle" + :content-style="contentStyle" + > + <el-descriptions-item :span="2" class="section-header"> + <template #label> + <el-icon class="section-icon"><Document /></el-icon> + <span>璁㈠崟淇℃伅</span> + </template> + <template #default></template> + </el-descriptions-item> <el-descriptions-item label="璁㈠崟缂栧彿">{{ detail.orderNo }}</el-descriptions-item> <el-descriptions-item label="浜ゆ槗璧勬簮绫诲瀷">{{ detail.resourceTypeName }}</el-descriptions-item> - <el-descriptions-item label="浜ゆ槗鐘舵��">{{ detail.statusName }}</el-descriptions-item> <el-descriptions-item label="鐢宠鏃堕棿">{{ detail.applyTime }}</el-descriptions-item> - <el-descriptions-item label="鍗曚綅">{{ detail.unitName }}</el-descriptions-item> - <el-descriptions-item label="鐢ㄦ埛鍚�">{{ detail.userName }}</el-descriptions-item> + <el-descriptions-item label="浜ゆ槗鐘舵��"> + <el-tag :type="getStatusType(detail.status)" size="small">{{ detail.statusName }}</el-tag> + </el-descriptions-item> </el-descriptions> - </el-card> + + <!-- 鐢宠浜轰俊鎭紙涓庤鍗曚俊鎭悓鍗$墖锛屽鐢ㄥ垎闅旀爣棰樻牱寮忥級 --> + <el-descriptions + :column="2" + border + class="mt15 order-desc fixed-label" + label-width="180px" + :label-style="labelStyle" + :content-style="contentStyle" + > + <el-descriptions-item :span="2" class="section-header"> + <template #label> + <el-icon class="section-icon"><User /></el-icon> + <span>鐢宠浜轰俊鎭�</span> + </template> + <template #default></template> + </el-descriptions-item> + <el-descriptions-item label="濮撳悕">{{ detail.userName || '-' }}</el-descriptions-item> + <el-descriptions-item label="鍗曚綅">{{ detail.unitName || '-' }}</el-descriptions-item> + <el-descriptions-item label="閮ㄩ棬">{{ detail.userDept || '-' }}</el-descriptions-item> + <el-descriptions-item label="鐢ㄦ埛鍚�">{{ detail.userAccount || '-' }}</el-descriptions-item> + </el-descriptions> - <el-card class="mt15" shadow="never"> - <div class="title">浜ゆ槗鍐呭</div> - <el-table :data="detail.items" border class="mt10"> - <el-table-column label="璇︽儏" min-width="280"> - <template #default="{ row }"> - <div> - <div>{{ row.name }}</div> - <div class="gray">瀹㈡埛瀵硅薄锛歿{ row.customerTarget }}</div> - <div class="gray">骞跺彂鑺傜偣鏁帮細{{ row.concurrentNodes }}</div> - </div> + <!-- 浜ゆ槗鍐呭锛堢揣闅忕敵璇蜂汉淇℃伅锛屽悓鍗$墖锛屽鐢ㄥ垎闅旀爣棰樻牱寮忥級 --> + <el-descriptions + :column="2" + border + class="mt15 order-desc fixed-label" + label-width="180px" + :label-style="labelStyle" + :content-style="contentStyle" + > + <el-descriptions-item :span="2" class="section-header"> + <template #label> + <el-icon class="section-icon"><Goods /></el-icon> + <span>浜ゆ槗鍐呭</span> + </template> + <template #default></template> + </el-descriptions-item> + <el-descriptions-item label="浜у搧鍚嶇О"> + <el-link type="primary" :underline="false">{{ detail.productName }}</el-link> + </el-descriptions-item> + <el-descriptions-item label="鎻愪緵鑰�">{{ detail.supplier }}</el-descriptions-item> + <el-descriptions-item label="琛屼笟棰嗗煙">{{ detail.industry }}</el-descriptions-item> + <el-descriptions-item label="鍗曚綅宸ョ▼">{{ detail.projectUnit }}</el-descriptions-item> + <el-descriptions-item label="浜у搧绫诲瀷">{{ detail.productType || '-' }}</el-descriptions-item> + <el-descriptions-item label="浜у搧绠�浠�"> + <div class="desc-wrap">{{ detail.productDesc }}</div> + </el-descriptions-item> + </el-descriptions> + + <!-- 璁㈠崟璇︽儏锛堢Щ鍔ㄥ埌浜ゆ槗鍐呭涓嬮潰锛屽悓涓�鍗$墖鍐咃級 --> + <div ref="orderTableWrapRef"> + <el-table + :data="tableData" + border + class="mt10 order-table" + :header-cell-style="headerCenterStyle" + :cell-style="bodyCellStyle" + :row-class-name="getRowClassName" + :span-method="arraySpanMethod" + > + <el-table-column> + <template #header> + <el-icon class="header-icon"><List /></el-icon> + <span>璇︽儏</span> + </template> + <el-table-column label="" :width="colWidths.detail1"> + <template #default="{ row }"> + <div v-if="!row.isSummary">{{ row.name }}</div> + <div v-else class="summary-merged"> + <div class="summary-left"> + 鍏� <span class="count">{{ detail.items.length }}</span> 浠� + </div> + <div class="summary-right"> + 鎬昏锛�<span class="price">{{ detail.pointTotal }}</span> 绉垎 + <span class="ml20 price">{{ detail.cashTotal }}</span> 鍏� + </div> + </div> + </template> + </el-table-column> + <el-table-column label="" :width="colWidths.detail2"> + <template #default="{ row }"> + <div v-if="!row.isSummary" class="gray">閿�鍞舰寮忥細{{ row.saleType || '-' }}</div> + <div v-if="!row.isSummary" class="gray">璐︽埛鏁伴噺锛歿{ row.accountCount ?? '-' }}</div> + </template> + </el-table-column> + <el-table-column label="" :width="colWidths.detail3"> + <template #default="{ row }"> + <div v-if="!row.isSummary" class="gray">瀹㈡埛瀵硅薄锛歿{ row.customerTarget || '-' }}</div> + <div v-if="!row.isSummary" class="gray">骞跺彂鑺傜偣鏁伴噺锛歿{ row.concurrentNodes ?? '-' }}</div> + </template> + </el-table-column> + </el-table-column> + <el-table-column label="鍗曚环"> + <el-table-column label="" :width="colWidths.price"> + <template #default="{ row }"> + <div v-if="!row.isSummary">{{ formatPrice(row) }}</div> + </template> + </el-table-column> + </el-table-column> + <el-table-column label="鏁伴噺"> + <el-table-column label="" :width="colWidths.quantity"> + <template #default="{ row }"> + <div v-if="!row.isSummary">{{ row.quantity }}</div> + </template> + </el-table-column> + </el-table-column> + <el-table-column label="鏈熼檺(骞�)"> + <el-table-column label="" :width="colWidths.period"> + <template #default="{ row }"> + <div v-if="!row.isSummary">{{ formatPeriod(row) }}</div> + </template> + </el-table-column> + </el-table-column> + </el-table> + </div> + + <!-- 绉婚櫎鍘熸潵鐨勮〃鏍煎簳閮ㄤ俊鎭紝鍥犱负宸茬粡绉诲埌琛ㄦ牸鏈�鍚庝竴琛� --> + + <!-- 浜ゆ槗鏂囦欢锛堢Щ鍔ㄥ埌璁㈠崟璇︽儏涓嬮潰锛屽悓涓�鍗$墖鍐咃級 --> + <div class="file-section" v-if="isAgreementOrder && fileList.length > 0"> + <el-table + :data="fileList" + border + class="file-table" + :header-cell-style="fileTableHeaderStyle" + :cell-style="fileTableCellStyle" + > + <el-table-column min-width="200"> + <template #header> + <el-icon class="header-icon"><Document /></el-icon> + <span>浜ゆ槗鏂囦欢</span> + </template> + <template #default="{ row }"> + <div class="file-name"> + <el-icon class="file-icon"><Document /></el-icon> + <span>{{ row.name }}</span> + </div> + </template> + </el-table-column> + <el-table-column label="鏂囦欢澶у皬" prop="size" width="120"> + <template #default="{ row }"> + {{ formatFileSize(row.size) }} + </template> + </el-table-column> + <el-table-column label="鎿嶄綔" width="180"> + <template #default="{ row }"> + <div class="file-actions"> + <el-button + type="text" + size="small" + class="preview-btn" + @click="handlePreview(row)" + v-if="isPreviewable(row)" + :disabled="!isFileUploaded(row)" + > + 棰勮 + </el-button> + <el-button + type="text" + size="small" + class="download-btn" + @click="handleDownload(row)" + :disabled="!isFileUploaded(row)" + > + 涓嬭浇 + </el-button> + </div> + </template> + </el-table-column> + </el-table> + </div> + </el-card> + + <!-- 浜ゆ槗淇℃伅澶囨敞 --> + <el-card class="mt15" shadow="never"> + <div class="title">浜ゆ槗淇℃伅澶囨敞</div> + <el-table :data="form.items" border class="mt10 remark-table"> + <el-table-column label="璇︽儏" prop="name" min-width="200" /> + <el-table-column label="澶囨敞" min-width="400"> + <template #default="{ row, $index }"> + <el-input + v-model="form.items[$index].remark" + type="textarea" + :rows="1" + placeholder="寮�閫氳处鍙蜂俊鎭強涓暟锛岀櫥褰曟彁绀虹瓑淇℃伅" + maxlength="500" + show-word-limit + class="remark-input" + /> </template> </el-table-column> - <el-table-column label="鍗曚环" prop="priceName" width="140" /> - <el-table-column label="鏁伴噺" prop="quantity" width="80" /> - <el-table-column label="鏈熼檺(骞�)" prop="period" width="120" /> - </el-table> - - <div class="total"> - 鎬昏锛�<span class="price">{{ detail.pointTotal }}</span> 绉垎 - <span class="ml20 price">{{ detail.cashTotal }}</span> 鍏� - </div> + </el-table> </el-card> + <!-- 瀹℃壒鍐呭 --> <el-card class="mt15" shadow="never"> - <div class="title">瀹℃壒</div> - <el-form :model="form" label-width="100px" class="mt10" ref="formRef" :rules="rules"> - <el-form-item label="瀹℃壒鎰忚" prop="remark"> - <el-input v-model="form.remark" type="textarea" :autosize="{ minRows: 4 }" placeholder="璇疯緭鍏ュ鎵规剰瑙�" /> - </el-form-item> - </el-form> - <div class="ba-center mt15"> - <el-button @click="goBack">杩斿洖</el-button> - <el-button type="success" @click="approve(true)">瀹℃壒閫氳繃</el-button> - <el-button type="danger" @click="approve(false)">椹冲洖</el-button> + <div class="title">瀹℃壒鍐呭</div> + <div class="approval-content"> + <div class="approval-form"> + <div class="form-item"> + <label class="required">瀹℃壒鎰忚:</label> + <el-input + v-model="approvalForm.comments" + type="textarea" + :rows="4" + placeholder="璇疯緭鍏ュ鎵规剰瑙�" + maxlength="500" + show-word-limit + /> + </div> + </div> + <div class="approval-actions"> + <el-button @click="goBack">杩斿洖</el-button> + <el-button + type="primary" + @click="handleApprove" + :loading="approvalLoading" + > + {{ isAgreementOrder ? '瀹℃壒閫氳繃' : '瀹屾垚鎺堟潈' }} + </el-button> + <el-button + v-if="isAgreementOrder" + type="danger" + @click="handleReject" + :loading="approvalLoading" + > + 椹冲洖 + </el-button> + </div> </div> </el-card> </div> </template> <script setup lang="ts"> -import { onMounted, reactive, ref } from 'vue' +import { onMounted, reactive, ref, computed, type CSSProperties } from 'vue' import { useRoute, useRouter } from 'vue-router' -import { fetchApprovalDetail, submitApproval } from '@/api/approvalManage' -import { ElMessage, FormInstance } from 'element-plus' +import { Document, User, Goods, List } from '@element-plus/icons-vue' +import { ElMessage, ElMessageBox } from 'element-plus' +import orderApi from '@/api/orderApi' +import { approveOrder } from '@/api/approvalManage' +import { useUserInfo } from '@/stores/modules/userInfo' +import createAxios from '@/utils/axios' +import productApi from '@/api/productApi' +import sysUserService from "@/api/sysUser"; +import workFlowApi from "@/api/workFlowApi"; +import {queryUserDetail} from "@/api/userInfo"; const route = useRoute() const router = useRouter() -const formRef = ref<FormInstance>() +const userStore = useUserInfo() const detail = reactive<any>({ items: [] }) -const form = reactive({ remark: '' }) -const rules = { remark: [{ required: true, message: '璇疯緭鍏ュ鎵规剰瑙�', trigger: 'blur' }] } +const form = reactive<any>({ items: [] }) +const fileList = ref<any[]>([]) +const orderTableWrapRef = ref<HTMLElement | null>(null) +const labelStyle = { width: '180px', maxWidth: '180px' } +const contentStyle = { width: 'calc(50% - 180px)' } + +// 鏄惁涓哄崗璁鍗� +const isAgreementOrder = ref(false) + +// 瀹℃壒琛ㄥ崟鏁版嵁 +const approvalForm = reactive({ + comments: '' +}) + +// 瀹℃壒loading鐘舵�� +const approvalLoading = ref(false) + +// 璁$畻琛ㄦ牸鏁版嵁锛屾坊鍔犳眹鎬昏 +const tableData = computed(() => { + const summaryRow = { + id: 'summary', + isSummary: true, + name: '', + saleType: '', + accountCount: 0, + customerTarget: '', + concurrentNodes: 0, + pricePoint: 0, + priceCash: 0, + quantity: 0, + period: 0, + } + return [...detail.items, summaryRow] +}) + +const taskId = computed(() => { + return String(route.params.taskId || '') +}) + +// 鐘舵�佹槧灏勶紙鍚庣涓枃 -> 鍓嶇鏋氫妇锛� +const statusServerToUi: Record<string, string> = { + '寰呬笂浼犳枃浠�': 'WAIT_UPLOAD', + '寰呮巿鏉�': 'WAIT_AUTHORIZE', + '寰呬氦鏄撶‘璁�': 'WAIT_CONFIRM', + '宸插畬鎴�': 'COMPLETED', + '宸茶瘎浠�': 'EVALUATED', +} + +const formatDateTime = (val?: string) => (val ? val.replace('T', ' ').slice(0, 19) : '') + +const normalizePriceType = (val?: string): 'points' | 'currency' | 'agreement' | 'free' => { + if (!val) return 'currency' + const s = String(val) + if (/(绉垎|points)/i.test(s)) return 'points' + if (/(鍗忚|agreement)/i.test(s)) return 'agreement' + if (/(鍏嶈垂|free)/i.test(s)) return 'free' + return 'currency' +} onMounted(async () => { - const { data } = (await fetchApprovalDetail({ id: route.params.id })) as any - Object.assign(detail, data || {}) + const orderId = String(route.params.id || '') + console.log(route.params.taskId) + if (!orderId) return + + // 鑾峰彇鐢ㄦ埛淇℃伅 + if (!userStore.getUserId) { + try { + const res: any = await queryUserDetail() + if (res?.code === 200 && res.data) { + userStore.updateUserDetail(res.data) + } else { + ElMessage.error(res?.msg || '鏃犳硶鑾峰彇鐢ㄦ埛淇℃伅锛岃鍏堢櫥褰�') + return + } + } catch (e) { + console.error('鑾峰彇鐢ㄦ埛璇︽儏澶辫触:', e) + ElMessage.error('鑾峰彇鐢ㄦ埛淇℃伅澶辫触锛岃绋嶅悗閲嶈瘯') + return + } + } + + try { + // 骞惰鑾峰彇璁㈠崟璇︽儏鍜屽崗璁被鍨嬫鏌� + const [orderRes, agreementRes] = await Promise.all([ + orderApi.getOrderDetail(orderId), + orderApi.checkAgreementPriceType(orderId) + ]) + + const res = orderRes as any + const data = res?.data || {} + + // 璁剧疆鏄惁涓哄崗璁鍗� + const agreementResult = agreementRes as any + isAgreementOrder.value = agreementResult?.data === true + + const statusName: string = data.orderStatus || '' + const uiStatus = statusServerToUi[statusName] || 'INFO' + + // 鏍规嵁浜у搧id鑾峰彇浜у搧淇℃伅锛屾洿鏂板ご閮ㄥ睍绀� + try { + if (data.productId) { + const detailRes: any = await productApi.getProductById({ id: data.productId }) + if (detailRes?.code === 200 && detailRes.data) { + // 鐢ㄤ骇鍝佽鎯呰ˉ鍏ㄥご淇℃伅 + data.productName = detailRes.data.name || data.productName + data.providerName = detailRes.data.submissionUnit || data.providerName + data.industry = detailRes.data.industrialChainName || data.industry + data.productDesc = detailRes.data.describe || data.productDesc + data.projectUnit = detailRes.data.importantAreaName || data.productDesc + data.productType = detailRes.data.typeName || data.productDesc + } + } + } catch (e) { + // 蹇界暐浜у搧璇︽儏澶辫触锛屼笉闃诲璁㈠崟璇︽儏 + } + + // 鑾峰彇鐢ㄦ埛淇℃伅 + // 鑾峰彇鐢ㄦ埛淇℃伅 + try { + const userRes: any = await sysUserService.getUserdetail({ userId: data.userId }) + if (userRes?.code === 200 && userRes.data) { + // 鐢ㄤ骇鍝佽鎯呰ˉ鍏ㄥご淇℃伅 + data.unitName = userRes.data.unitName || data.unitName + data.userName = userRes.data.name || data.userName + data.userDept = userRes.data.departmentName || data.userDept + data.userPhone = userRes.data.phone || data.userPhone + data.userAccount = userRes.data.username || data.userAccount + } + }catch (e){ + + } + + // 鏄犲皠璁㈠崟璇︽儏澶撮儴淇℃伅 + const head = { + orderNo: data.orderId, + resourceTypeName: '杞欢浜у搧', + status: uiStatus, + statusName, + applyTime: formatDateTime(data.applyTime), + unitName: data.unitName || '-', + userName: data.userName || '-', + userAccount: data.userAccount || '-', + userDept: data.userDept || '-', + userPhone: data.userPhone || '-', + productName: data.productName || '-', + supplier: data.providerName || '-', + industry: data.industry || '-', + projectUnit: data.projectUnit || '-', + productType: data.productType || '-', + productDesc: data.productDesc || '-', + } + + // 鏄庣粏椤规槧灏� + const items: any[] = Array.isArray(data.orderDetails) + ? data.orderDetails.map((d: any, idx: number) => { + const pt = normalizePriceType(d.priceType) + return { + id: String(d.id ?? idx + 1), + name: d.suiteName, + saleType: d.salesForm, + accountCount: d.accountLimit, + customerTarget: d.customerType, + concurrentNodes: d.concurrentNodes, + pricePoint: pt === 'points' ? Number(d.unitPrice || 0) : 0, + priceCash: pt === 'currency' ? Number(d.unitPrice || 0) : 0, + priceProtocol: pt === 'agreement', + quantity: Number(d.quantity || 0), + period: Number(d.duration || 0), + remarks: d.remarks || '', // 鏂板 remarks 瀛楁 + } + }) + : [] + + // 姹囨�伙紙绠�鍗曠浉鍔狅細鍗曚环*鏁伴噺锛� + const pointTotalNum = items.reduce((sum, it) => sum + Number(it.pricePoint || 0) * Number(it.quantity || 0), 0) + const cashTotalNum = items.reduce((sum, it) => sum + Number(it.priceCash || 0) * Number(it.quantity || 0), 0) + + Object.assign(detail, head, { + items, + pointTotal: pointTotalNum.toLocaleString(), + cashTotal: cashTotalNum.toLocaleString(), + workflowId: data.workflowId || data.processinstId || '' + }) + + // 鍒濆鍖栬〃鍗曟暟鎹� + form.items = (detail.items || []).map((item: any, index: number) => { + return { + id: item.id, // 淇濆瓨order_detail鐨刬d + name: item.name, + remark: item.remarks || '' // 浣跨敤濂椾欢淇℃伅涓殑remarks瀛楁 + } + }) + + // 鑾峰彇璁㈠崟闄勪欢鍒楄〃锛堝鏋滄湁鐨勮瘽锛� + if (data.attachments && Array.isArray(data.attachments)) { + fileList.value = data.attachments.map((file: any) => ({ + name: file.fileName || file.originalName, + size: file.fileSize || 0, + uid: file.id, + status: 'success', + url: file.fileUrl, + uploaded: true + })) + } else { + fileList.value = [] + } + } catch (error) { + console.error('鑾峰彇璁㈠崟璇︽儏澶辫触:', error) + ElMessage.error('鑾峰彇璁㈠崟璇︽儏澶辫触') + } }) const goBack = () => router.back() -const approve = async (pass: boolean) => { - await formRef.value?.validate() - const { code } = (await submitApproval({ id: route.params.id, pass, remark: form.remark })) as any - if (code === 200) { - ElMessage.success('鎻愪氦鎴愬姛') - router.back() + +// 瀹℃壒閫氳繃澶勭悊 +const handleApprove = async () => { + if (!approvalForm.comments.trim()) { + ElMessage.warning('璇疯緭鍏ュ鎵规剰瑙�') + return + } + + try { + approvalLoading.value = true + await ElMessageBox.confirm('纭畾瑕佸鎵归�氳繃鍚楋紵', '纭鎿嶄綔', { + confirmButtonText: '纭畾', + cancelButtonText: '鍙栨秷', + type: 'warning' + }) + + const orderId = String(route.params.id || '') + console.log(String(route.params.taskId || '')) + const taskId1 = taskId.value + const userId = userStore.getUserId ? userStore.getUserId : undefined + const comments = approvalForm.comments.trim() + + if (!orderId || !userId) { + ElMessage.error('璁㈠崟ID鎴栫敤鎴稩D涓嶈兘涓虹┖') + approvalLoading.value = false + return + } + + // 璋冪敤瀹℃壒閫氳繃API + const result = await approveOrder({ + orderId: orderId, + taskId: taskId1, + approvalOpinion: comments, + approverId: userId, + approverName: userStore.getUserDetail || '绠$悊鍛�', + approvalType: isAgreementOrder.value ? '瀹℃壒' : '鎺堟潈', + approvalResult: '閫氳繃', + orderDetails: form.items.map((item: any) => ({ + id: item.id, + remarks: item.remark || '' // 浣跨敤琛ㄥ崟涓殑澶囨敞 + })) + }) + + if (result && result.code === 200) { + // 鏇存柊浜ゆ槗淇℃伅澶囨敞锛堝彧鏇存柊remarks锛屼笉鏇存柊璁㈠崟鐘舵�侊級 + // const updateData = { + // orderId: orderId, + // orderDetails: form.items.map((item: any) => ({ + // id: item.id, + // remarks: item.remark || '' // 浣跨敤琛ㄥ崟涓殑澶囨敞 + // })) + // } + // + // await orderApi.updateOrderDetailRemarksOnly(updateData) + // + // // 瀹℃壒閫氳繃鍚庯紝浣跨敤鏂扮殑API鎺ュ彛鏇存柊璁㈠崟鐘舵�佸埌涓嬩竴涓姸鎬� + // await orderApi.updateOrderStatusToNext(orderId) + ElMessage.success('瀹℃壒閫氳繃鎴愬姛') + // if(!detail.workflowId.value){ + // ElMessage.error("宸ヤ綔娴乮d涓虹┖涓嶈兘杩涜宸ヤ綔娴佷换鍔℃彁浜�") + // return + // } + // // 璋冪敤宸ヤ綔娴� + // const wfRes: any = await workFlowApi.completeWorkflow({ + // taskId: String(detail.workflowId.value), + // userid: userStore.getUserId, + // commponet: '瀹℃牳閫氳繃' + // }) + // if (wfRes?.code === 200 && wfRes.data?.processinstId) { + // console.log('宸ヤ綔娴佹彁浜ゆ垚鍔�') + // } + router.back() + } else { + ElMessage.error(result?.msg || '瀹℃壒閫氳繃澶辫触') + } + } catch (error) { + if (error !== 'cancel') { + console.error('瀹℃壒澶辫触:', error) + ElMessage.error('瀹℃壒澶辫触') + } + } finally { + approvalLoading.value = false } } + +// 椹冲洖澶勭悊 +const handleReject = async () => { + if (!approvalForm.comments.trim()) { + ElMessage.warning('璇疯緭鍏ュ鎵规剰瑙�') + return + } + + try { + approvalLoading.value = true + await ElMessageBox.confirm('纭畾瑕侀┏鍥炲悧锛�', '纭鎿嶄綔', { + confirmButtonText: '纭畾', + cancelButtonText: '鍙栨秷', + type: 'warning' + }) + + const orderId = String(route.params.id || '') + const userId = userStore.getUserId ? userStore.getUserId : undefined + const comments = approvalForm.comments.trim() + const taskId1 = String(route.params.taskId || '') + + if (!orderId || !userId) { + ElMessage.error('璁㈠崟ID鎴栫敤鎴稩D涓嶈兘涓虹┖') + approvalLoading.value = false + return + } + + // 璋冪敤瀹℃壒椹冲洖API + const result = await approveOrder({ + orderId: orderId, + approvalOpinion: comments, + approverId: userId, + taskId: taskId1, + approverName: userStore.getUserDetail || '绠$悊鍛�', + approvalType: isAgreementOrder.value ? '瀹℃壒' : '鎺堟潈', + approvalResult: '椹冲洖' + }) + + if (result && result.code === 200) { + // 椹冲洖璁㈠崟锛屾洿鏂拌鍗曠姸鎬佸埌涓婁竴涓姸鎬� + // await orderApi.updateOrderStatusToPrevious(orderId) + ElMessage.success('椹冲洖鎴愬姛') + // 璋冪敤宸ヤ綔娴� + // const wfRes: any = await workFlowApi.rejectStartNodeWorkflow({ + // taskId: String(detail.workflowId.value), + // userid: userStore.getUserId, + // commponet: '椹冲洖' + // }) + // if (wfRes?.code === 200 && wfRes.data?.processinstId) { + // console.log('宸ヤ綔娴侀┏鍥炴垚鍔�') + // } + router.back() + } else { + ElMessage.error(result?.msg || '椹冲洖澶辫触') + } + } catch (error) { + if (error !== 'cancel') { + console.error('椹冲洖澶辫触:', error) + ElMessage.error('椹冲洖澶辫触') + } + } finally { + approvalLoading.value = false + } +} + +// 涓庡垪琛ㄩ〉淇濇寔涓�鑷寸殑鐘舵�佺被鍨嬫槧灏勶紙UI灞曠ず鐢級 +const getStatusType = (status: string) => { + const statusMap: Record<string, 'warning' | 'danger' | 'success' | 'info'> = { + WAIT_APPROVAL: 'warning', + WAIT_UPLOAD: 'warning', + WAIT_CHECK: 'warning', + WAIT_CONFIRM: 'warning', + REJECTED: 'danger', + FINISH: 'success', + } + return statusMap[status] || 'info' +} + +// 璁㈠崟璇︽儏涓�"鍗曚环"鏄剧ず锛氫紭鍏堟樉绀虹Н鍒嗭紝鍏舵鏄剧ず璐у竵锛涙牸寮忕ず渚嬶細 +// "绉垎锛�50,000/濂�" 鎴� "璐у竵锛�7,500/濂�/骞�" 鎴� "鍏嶈垂锛�/骞�" +const formatPrice = (row: any) => { + const point = Number(row.pricePoint || 0) + const cash = Number(row.priceCash || 0) + const protocol = Boolean(row.priceProtocol) + + // 鍏嶈垂 + if (!point && !cash) { + return protocol ? '鍗忚锛�/骞�' : '鍏嶈垂锛�/骞�' + } + if (point) { + return `绉垎锛�${point.toLocaleString()}/濂梎 + } + // 浠呯幇閲� + return `璐у竵锛�${cash.toLocaleString()}/濂�/骞碻 +} + +// 鏈熼檺灞曠ず锛�0 琛ㄧず"姘镐箙"锛屽叾浠栨樉绀烘暟瀛� +const formatPeriod = (row: any) => { + const p = Number(row.period || 0) + return p === 0 ? '姘镐箙' : `${p}` +} + +// 琛ㄥご鏂囧瓧灞呬腑锛屼絾绗竴琛岀殑"璇︽儏"鏂囧瓧闈犲乏瀵归綈 +const headerCenterStyle: CSSProperties = { + textAlign: 'center', + fontSize: '14px', + background: '#f3f6fb' +} + +// 琛ㄤ綋鏂囧瓧澶у皬 +const bodyCellStyle: CSSProperties = { fontSize: '12px' } + +// 涓烘眹鎬昏娣诲姞鐗规畩鏍峰紡绫诲悕 +const getRowClassName = ({ row }: { row: any }) => { + return row.isSummary ? 'summary-row' : '' +} + +// 鍗曞厓鏍煎悎骞舵柟娉� +const arraySpanMethod = ({ row, column, rowIndex, columnIndex }: any) => { + if (row.isSummary) { + // 姹囨�昏锛氱涓�鍒楁樉绀哄悎骞跺唴瀹癸紝鍏朵粬鍒楅殣钘� + if (columnIndex === 0) { + return [1, 6] // 鍚堝苟1琛�6鍒� + } else { + return [0, 0] // 闅愯棌鍏朵粬鍒� + } + } + return [1, 1] // 鏅�氳姝e父鏄剧ず +} + +// 鏂囦欢鍒楄〃琛ㄦ牸琛ㄥご鏂囧瓧灞呬腑锛屼絾绗竴鍒楃殑"浜ゆ槗鏂囦欢"鏂囧瓧闈犲乏瀵归綈 +const fileTableHeaderStyle: CSSProperties = { + textAlign: 'center', + fontSize: '14px', + background: '#f3f6fb' +} +// 鏂囦欢鍒楄〃琛ㄦ牸琛ㄤ綋鏂囧瓧澶у皬 +const fileTableCellStyle: CSSProperties = { fontSize: '12px' } + +// 鏂囦欢澶у皬鏍煎紡鍖� +const formatFileSize = (size: number) => { + if (!size || size === 0) return '0 Bytes'; + const k = 1024; + const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB']; + const i = Math.floor(Math.log(size) / Math.log(k)); + return parseFloat((size / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; +}; + +// 鍒ゆ柇鏂囦欢鏄惁鍙瑙� +const isPreviewable = (file: any) => { + // 棣栧厛妫�鏌IME绫诲瀷 + const previewableTypes = [ + 'image/jpeg', 'image/jpg', 'image/png', 'image/gif', 'image/bmp', 'image/webp', + 'text/plain', 'text/html', 'text/css', 'text/javascript', + 'application/pdf', + 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', // .docx + 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', // .xlsx + 'application/vnd.openxmlformats-officedocument.presentationml.presentation', // .pptx + ] + + // 濡傛灉MIME绫诲瀷鍖归厤锛岀洿鎺ヨ繑鍥瀟rue + if (previewableTypes.includes(file.type || '')) { + return true + } + + // 濡傛灉MIME绫诲瀷涓虹┖鎴栦笉鍖归厤锛屾牴鎹枃浠舵墿灞曞悕鍒ゆ柇 + const fileName = file.name || '' + const fileExtension = fileName.toLowerCase().split('.').pop() + + const previewableExtensions = [ + 'jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', + 'txt', 'html', 'htm', 'css', 'js', + 'pdf', + 'docx', 'xlsx', 'pptx' + ] + + return previewableExtensions.includes(fileExtension) +} + +// 鍒ゆ柇鏂囦欢鏄惁宸蹭笂浼犳垚鍔� +const isFileUploaded = (file: any) => { + // 鏂囦欢鏈塽rl涓旂姸鎬佷负success琛ㄧず宸蹭笂浼犳垚鍔� + return file.url && (file.status === 'success' || file.uploaded) +} + +// 鏂囦欢棰勮 +const handlePreview = async (file: any) => { + if (!file.url) { + ElMessage.warning('鏂囦欢閾炬帴涓嶅瓨鍦�') + return + } + + // 鑾峰彇鏂囦欢鎵╁睍鍚� + const fileName = file.name || '' + const fileExtension = fileName.toLowerCase().split('.').pop() + + let previewUrl = file.url + + // 濡傛灉鏂囦欢瀛樺偍鍦∕inIO锛屼紭鍏堜娇鐢ㄩ瑙圲RL + if (file.url.includes('order-attachments')) { + try { + // 棣栧厛灏濊瘯鑾峰彇棰勮URL + const previewResponse = await createAxios({ + url: `/admin/file/preview`, + method: 'GET', + params: { + fileName: file.url + } + }) + + console.log('棰勮URL鍝嶅簲:', previewResponse) + + // 妫�鏌ュ搷搴旀牸寮� + const responseData = previewResponse as any + if (responseData && responseData.code === 200 && responseData.data) { + previewUrl = responseData.data.replaceAll('http://192.168.20.52:9000',import.meta.env.VITE_FILE_PREVIEW_URL) + console.log('浣跨敤棰勮URL:', previewUrl) + } else { + console.log('棰勮URL鑾峰彇澶辫触锛屼娇鐢ㄤ笅杞芥柟寮�') + // 濡傛灉棰勮URL鑾峰彇澶辫触锛屽洖閫�鍒颁笅杞芥柟寮� + const response = await createAxios({ + url: `/admin/file/download`, + method: 'GET', + responseType: 'blob', + params: { + fileName: file.url, + originalName: file.name + } + }) + + // 鍒涘缓棰勮URL + const blob = new Blob([response as any]) + previewUrl = window.URL.createObjectURL(blob) + console.log('浣跨敤Blob URL:', previewUrl) + } + } catch (error) { + console.error('鑾峰彇鏂囦欢澶辫触:', error) + ElMessage.error('鑾峰彇鏂囦欢澶辫触锛屾棤娉曢瑙�') + return + } + } + + // 鍥剧墖鏂囦欢鐩存帴鍦ㄦ柊绐楀彛鎵撳紑 + if ((file.type && file.type.startsWith('image/')) || + ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp'].includes(fileExtension)) { + console.log('棰勮鍥剧墖鏂囦欢:', previewUrl) + window.open(previewUrl, '_blank') + return + } + + // PDF鏂囦欢鍦ㄦ柊绐楀彛鎵撳紑 + if (file.type === 'application/pdf' || fileExtension === 'pdf') { + console.log('棰勮PDF鏂囦欢:', previewUrl) + window.open(previewUrl, '_blank') + return + } + + // 鏂囨湰鏂囦欢鍦ㄦ柊绐楀彛鎵撳紑 + if ((file.type && file.type.startsWith('text/')) || + ['txt', 'html', 'htm', 'css', 'js'].includes(fileExtension)) { + console.log('棰勮鏂囨湰鏂囦欢:', previewUrl) + window.open(previewUrl, '_blank') + return + } + + // Office鏂囨。鍜屽叾浠栨枃浠剁被鍨嬶紝灏濊瘯鍦ㄦ柊绐楀彛鎵撳紑 + try { + console.log('棰勮鍏朵粬鏂囦欢:', previewUrl) + window.open(previewUrl, '_blank') + } catch (error) { + console.error('棰勮澶辫触:', error) + ElMessage.error('棰勮澶辫触锛岃涓嬭浇鍚庢煡鐪�') + } +} + +// 鏂囦欢涓嬭浇 +const handleDownload = async (file: any) => { + if (!file.url) { + ElMessage.warning('鏂囦欢閾炬帴涓嶅瓨鍦�') + return + } + + console.log('寮�濮嬩笅杞芥枃浠�:', file.name, 'URL:', file.url) + + try { + // 濡傛灉鏂囦欢瀛樺偍鍦∕inIO锛屼娇鐢ㄥ悗绔洿鎺ヤ笅杞紸PI + if (file.url.includes('order-attachments')) { + console.log('浣跨敤MinIO涓嬭浇API') + + // 浣跨敤axios閫氳繃浠g悊璁块棶鍚庣API + const response = await createAxios({ + url: `/admin/file/download`, + method: 'GET', + responseType: 'blob', + params: { + fileName: file.url, + originalName: file.name + } + }) + + console.log('涓嬭浇鍝嶅簲:', response) + + // 鍒涘缓涓嬭浇閾炬帴 + const blob = new Blob([response as any]) + const downloadUrl = window.URL.createObjectURL(blob) + + console.log('鍒涘缓涓嬭浇閾炬帴:', downloadUrl) + + const link = document.createElement('a') + link.href = downloadUrl + link.download = file.name || 'download' + link.target = '_blank' + link.rel = 'noopener noreferrer' + + document.body.appendChild(link) + link.click() + document.body.removeChild(link) + + // 娓呯悊URL瀵硅薄 + window.URL.revokeObjectURL(downloadUrl) + + ElMessage.success('鏂囦欢涓嬭浇鎴愬姛') + } else { + console.log('浣跨敤鐩存帴URL涓嬭浇') + // 鍏朵粬鎯呭喌鐩存帴浣跨敤鍘烾RL + const link = document.createElement('a') + link.href = file.url + link.download = file.name || 'download' + link.target = '_blank' + link.rel = 'noopener noreferrer' + + document.body.appendChild(link) + link.click() + document.body.removeChild(link) + + ElMessage.success('寮�濮嬩笅杞芥枃浠�') + } + } catch (error) { + console.error('涓嬭浇澶辫触:', error) + ElMessage.error('涓嬭浇澶辫触锛岃閲嶈瘯') + } +} + + + +// 鐥囩粨涓庝慨澶嶈鏄庯細 +// 1) Element Plus 鐨� el-table 瀛愬垪 width 鐧惧垎姣旀槸鐩稿浜庤〃鏍煎鍣ㄧ殑鍍忕礌瀹藉害璁$畻锛屼絾鍙湁鍦ㄨ〃鏍煎鍣ㄦ湁鏄庣‘瀹藉害鏃舵墠鐢熸晥銆� +// 2) 鍦ㄧ埗鍒�/澶氱骇琛ㄥご涓嬶紝瀛愬垪 width 涓虹櫨鍒嗘瘮鏃讹紝鏇寸ǔ瀹氱殑鍋氭硶鏄皢鍏惰绠椾负鍍忕礌鍊肩粦瀹氱粰瀛愬垪銆� +// 3) 鍥犳鎴戜滑璇诲彇琛ㄦ牸澶栧眰瀹瑰櫒瀹藉害锛屾寜姣斾緥璁$畻鍍忕礌瀹藉害锛岄伩鍏嶅嚭鐜扮櫨鍒嗘瘮涓� table-layout 瀵艰嚧鐨勯敊浣嶄笌鎷変几銆� +const colWidths = computed(() => { + const containerWidth = orderTableWrapRef.value?.clientWidth || 0 + // 鐧惧垎姣斿垎鍒负锛氳鎯� 20% x 3锛屽崟浠� 15%锛屾暟閲� 10%锛屾湡闄� 15% => 鍚堣 100% + return { + detail1: Math.floor(containerWidth * 0.2), + detail2: Math.floor(containerWidth * 0.2), + detail3: Math.floor(containerWidth * 0.2), + price: Math.floor(containerWidth * 0.15), + quantity: Math.floor(containerWidth * 0.1), + period: Math.floor(containerWidth * 0.15), + } +}) </script> <style scoped lang="scss"> .title { font-weight: 600; } +.sub-title { font-weight: 600; margin: 10px 0; } .mt10 { margin-top: 10px; } .mt15 { margin-top: 15px; } .gray { color: #909399; font-size: 12px; } .total { text-align: right; margin-top: 10px; } .price { color: #f56c6c; font-weight: 600; } .ml20 { margin-left: 20px; } +.item-block { padding: 10px; border: 1px solid #ebeef5; border-radius: 4px; margin-bottom: 10px; } + +/* 缁熶竴琛ㄦ牸鍐呭鏂囧瓧澶у皬 */ +.order-table :deep(.el-table__body), +.order-table :deep(.el-table__header) { + font-size: 12px; +} +/* 琛ㄥご绗竴琛岃儗鏅壊锛堜笌浜ゆ槗鍐呭鏍囬琛屼繚鎸佷竴鑷达級 */ +.order-table :deep(.el-table__header-wrapper thead tr:first-child > th) { + background: #f3f6fb !important; + font-size: 14px !important; +} +/* 琛ㄥご绗簩琛岄珮搴︿笌鍐呰竟璺� */ +.order-table :deep(.el-table__header-wrapper thead tr:nth-child(2) > th) { + height: 5px !important; + padding-top: 0 !important; + padding-bottom: 0 !important; + padding-left: 0 !important; + padding-right: 0 !important; +} +/* 寮哄埗琛ㄦ牸鍒楀浐瀹氬竷灞�锛岄伩鍏嶅唴瀹瑰奖鍝嶅垪瀹� */ +.order-table :deep(table) { + table-layout: fixed; + width: 100% !important; +} + +/* 姹囨�昏鏍峰紡 */ +.summary-row { + font-weight: 600; + color: #f56c6c; +} +.summary-total { + text-align: right; + font-weight: 600; +} +.summary-merged { + display: flex; + justify-content: space-between; + align-items: center; + width: 100%; +} +.summary-left { + text-align: left; +} +.summary-left .count { + color: #f56c6c; + font-weight: 600; +} +.summary-right { + text-align: right; +} +.order-table :deep(.summary-row) { + background-color: #fafafa; +} +.order-table :deep(.summary-row td) { + border-top: 2px solid #e4e7ed; +} + +/* 琛ㄥご绗竴琛�"璇︽儏"鏂囧瓧闈犲乏瀵归綈 */ +.order-table :deep(.el-table__header-wrapper thead tr:first-child th:first-child) { + text-align: left !important; +} + +/* 琛ㄥご鍥炬爣鏍峰紡 */ +.header-icon { + margin-right: 6px; + color: #409eff; + vertical-align: middle; +} + +/* 鍒嗛殧鏍囬椋庢牸 */ +.section-header { + background: #f3f6fb; + font-weight: 600; + --el-border-color: #e4e7ed; +} +.section-header :deep(.el-descriptions__label) { + background: #f3f6fb; + border-right: none !important; + width: 100%; +} +.section-header :deep(.el-descriptions__cell) { + background: #f3f6fb; +} +.section-header :deep(.el-descriptions__content) { + display: none !important; + padding: 0 !important; + border: 0 !important; +} +.section-icon { + margin-right: 6px; + color: #409eff; +} + +/* 璋冩暣鎻忚堪缁勪欢杈规浠ヤ究鏍囬琛岄鑹茶鐩栦腑闂村垎闅旂嚎 */ +:deep(.el-descriptions--border .el-descriptions__body .el-descriptions__table .el-descriptions__cell) { + border-right: 1px solid var(--el-border-color); +} +.section-header :deep(.el-descriptions__cell) { + border-right-color: transparent !important; +} + +/* 寮哄埗 Element Plus 鎻忚堪椤圭殑 label 瀹藉害閬靛惊 label-width锛堥伩鍏嶅唴瀹规拺寮�锛� */ +/* 鍏滃簳锛氬嵆浣垮唴鑱旀牱寮忚瑕嗙洊锛屼篃鐢� important 寮哄埗鍥哄畾 */ +.fixed-label :deep(.el-descriptions__label) { + width: 180px !important; + max-width: 180px !important; +} +.fixed-label :deep(.el-descriptions__content) { + width: calc(50% - 180px) !important; +} + +/* 寮哄寲绗竴琛屽垎闅旀爣棰樼殑鑳屾櫙涓庤竟妗嗚鐩� */ +.order-desc :deep(.el-descriptions__table tr:first-child .el-descriptions__cell), +.order-desc :deep(.el-descriptions__table tr:first-child .el-descriptions__label), +.order-desc :deep(.el-descriptions__table tr:first-child .el-descriptions__content) { + background: #eef3fb !important; + border-top-color: transparent !important; + border-bottom-color: #dcdfe6 !important; +} +.order-desc :deep(.el-descriptions__table tr:first-child .el-descriptions__label) { + border-right-color: transparent !important; +} + +/* 缁熶竴涓や釜鎻忚堪琛ㄦ牸鐨勫垪瀵归綈锛堟爣绛惧垪鍥哄畾瀹藉害锛屽唴瀹瑰垪绛夊垎鍓╀綑瀹藉害锛� */ +.order-desc :deep(.el-descriptions__table) { + table-layout: fixed; + width: 100%; +} +/* 浣跨敤绫婚�夋嫨鍣ㄨ�岄潪 nth-child锛屾彁鍗囩ǔ瀹氭�э紝纭繚姣忚涓ゅ垪涓ユ牸瀵归綈 */ +.order-desc :deep(.el-descriptions__table tr:not(:first-child) .el-descriptions__label) { + width: 180px !important; + max-width: 180px !important; + box-sizing: border-box; +} +.order-desc :deep(.el-descriptions__table tr:not(:first-child) .el-descriptions__content) { + width: calc(50% - 180px) !important; +} + +.desc-wrap { + white-space: pre-wrap; + line-height: 22px; +} + +/* 鏂囦欢鍒楄〃琛ㄦ牸鏍峰紡 */ +.file-section { + margin-top: 15px; + .file-title { + font-weight: 600; + margin-bottom: 10px; + } + .file-table { + width: 100%; + .file-name { + display: flex; + align-items: center; + .file-icon { + margin-right: 8px; + color: #409eff; + } + } + .preview-btn { + color: #409eff; + &:hover { + text-decoration: underline; + } + } + } +} + +/* 鏂囦欢琛ㄦ牸琛ㄥご绗竴鍒�"浜ゆ槗鏂囦欢"鏂囧瓧闈犲乏瀵归綈 */ +.file-table :deep(.el-table__header-wrapper thead tr th:first-child) { + text-align: left !important; +} + +/* 鏂囦欢鎿嶄綔鎸夐挳鏍峰紡 */ +.file-actions { + display: flex; + gap: 8px; + align-items: center; + justify-content: center; + + .preview-btn { + color: #409eff; + &:hover { + text-decoration: underline; + } + &:disabled { + color: #c0c4cc; + cursor: not-allowed; + } + } + + .download-btn { + color: #67c23a; + &:hover { + text-decoration: underline; + } + &:disabled { + color: #c0c4cc; + cursor: not-allowed; + } + } +} + +/* 浜ゆ槗淇℃伅澶囨敞琛ㄦ牸鏍峰紡 */ +.remark-table { + width: 100%; + + .remark-input { + width: 100%; + + :deep(.el-textarea__inner) { + border: none; + box-shadow: none; + background: transparent; + resize: none; + padding: 8px 0; + + &:focus { + border: 1px solid #409eff; + box-shadow: 0 0 0 2px rgba(64, 158, 255, 0.2); + border-radius: 4px; + background: #fff; + padding: 8px 12px; + } + } + + :deep(.el-input__count) { + background: transparent; + bottom: 2px; + right: 8px; + } + } +} + +/* 瀹℃壒鍐呭鏍峰紡 */ +.approval-content { + .approval-form { + .form-item { + display: flex; + align-items: flex-start; + margin-bottom: 20px; + + label { + width: 120px; + line-height: 32px; + margin-right: 10px; + font-weight: 500; + + &.required::before { + content: '*'; + color: #f56c6c; + margin-right: 4px; + } + } + + .el-textarea { + flex: 1; + } + } + } + + .approval-actions { + display: flex; + justify-content: center; + gap: 15px; + margin-top: 30px; + + .el-button { + min-width: 100px; + } + } +} </style> -- Gitblit v1.8.0