From 2c55b1a8a0700df79268550335506637b41610ce Mon Sep 17 00:00:00 2001 From: seatonwan9 Date: 星期日, 24 八月 2025 20:36:36 +0800 Subject: [PATCH] 提交源码 --- src/views/tradeManage/evaluate/index.vue | 935 ++++++++++++++++++++++++++++++++++++++++++++------------- 1 files changed, 714 insertions(+), 221 deletions(-) diff --git a/src/views/tradeManage/evaluate/index.vue b/src/views/tradeManage/evaluate/index.vue index 0493cb9..f607372 100644 --- a/src/views/tradeManage/evaluate/index.vue +++ b/src/views/tradeManage/evaluate/index.vue @@ -142,6 +142,8 @@ </el-table> </div> + <!-- 绉婚櫎鍘熸潵鐨勮〃鏍煎簳閮ㄤ俊鎭紝鍥犱负宸茬粡绉诲埌琛ㄦ牸鏈�鍚庝竴琛� --> + <!-- 浜ゆ槗鏂囦欢锛堢Щ鍔ㄥ埌璁㈠崟璇︽儏涓嬮潰锛屽悓涓�鍗$墖鍐咃級 --> <div class="file-section" v-if="fileList.length > 0"> <el-table @@ -168,103 +170,189 @@ {{ formatFileSize(row.size) }} </template> </el-table-column> - <el-table-column label="鎿嶄綔" width="100"> + <el-table-column label="鎿嶄綔" width="180"> <template #default="{ row }"> - <el-button - type="text" - size="small" - class="preview-btn" - @click="handlePreview(row)" - > - 棰勮 - </el-button> - </template> - </el-table-column> - </el-table> - </div> - - <!-- 浜ゆ槗淇℃伅澶囨敞锛堢Щ鍔ㄥ埌浜ゆ槗鏂囦欢涓嬮潰锛屽悓涓�鍗$墖鍐咃級 --> - <div class="remark-section" v-if="remarkItems.length > 0"> - <el-table - :data="remarkItems" - border - class="remark-table" - :header-cell-style="remarkTableHeaderStyle" - :cell-style="remarkTableCellStyle" - > - <el-table-column min-width="200"> - <template #header> - <el-icon class="header-icon"><Document /></el-icon> - <span>浜ゆ槗淇℃伅澶囨敞</span> - </template> - <template #default="{ row }"> - <div class="remark-name"> - <span>{{ row.name }}</span> + <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-column label="鎺堟潈寮�濮嬫椂闂�" width="200"> - <template #default="{ row }"> - <div class="remark-date">{{ row.start }}</div> - </template> - </el-table-column> - <el-table-column label="鎺堟潈缁撴潫鏃堕棿" width="200"> - <template #default="{ row }"> - <div class="remark-date"> - {{ row.forever ? '姘镐箙' : row.end }} - </div> - </template> - </el-table-column> - <el-table-column label="澶囨敞" min-width="300"> - <template #default="{ row }"> - <div class="remark-content">{{ row.remark }}</div> </template> </el-table-column> </el-table> </div> </el-card> - <!-- 浜ゆ槗璇勪环 --> + <!-- 浜ゆ槗淇℃伅澶囨敞 --> <el-card class="mt15" shadow="never"> - <div class="title">浜ゆ槗璇勪环</div> - <el-form :model="form" label-width="120px" class="mt10" :rules="rules" ref="formRef"> - <el-form-item label="缁煎悎璇勫垎" prop="score"> - <el-rate v-model="form.score" /> - </el-form-item> - <el-form-item label="璇勪环鍐呭" prop="content"> - <el-input v-model="form.content" type="textarea" :autosize="{ minRows: 4 }" placeholder="璇疯緭鍏ヨ瘎浠峰唴瀹�" /> - </el-form-item> - </el-form> - <div class="action-buttons"> - <el-button @click="goBack">杩斿洖</el-button> - <el-button type="primary" @click="submit">鎻愪氦璇勪环</el-button> - </div> - </el-card> - </div> -</template> + <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="鎺堟潈寮�濮嬫椂闂�" width="200"> + <template #default="{ row, $index }"> + <span>{{ form.items[$index].start || '-' }}</span> + </template> + </el-table-column> + <el-table-column label="鎺堟潈缁撴潫鏃堕棿" width="200"> + <template #default="{ row, $index }"> + <div class="end-time-wrapper"> + <span v-if="form.items[$index].forever" class="forever-text">姘镐箙</span> + <span v-else>{{ form.items[$index].end || '-' }}</span> + </div> + </template> + </el-table-column> + <el-table-column label="澶囨敞" min-width="300"> + <template #default="{ row, $index }"> + <span>{{ form.items[$index].remark || '-' }}</span> + </template> + </el-table-column> + </el-table> + </el-card> + + <!-- 浜ゆ槗璇勪环 --> + <el-card class="mt15" shadow="never"> + <div class="title">浜ゆ槗璇勪环</div> + <div class="evaluation-content"> + <div class="evaluation-form"> + <!-- 璇勫垎閮ㄥ垎 --> + <div class="rating-section"> + <div class="rating-items"> + <div class="rating-row"> + <div class="rating-item"> + <label class="required">缁煎悎璇勫垎:</label> + <el-rate + v-model="evaluationForm.overallRating" + :max="5" + :texts="['寰堝樊', '杈冨樊', '涓�鑸�', '杈冨ソ', '寰堝ソ']" + show-text + :colors="['#99A9BF', '#F7BA2A', '#FF9900']" + /> + </div> + <div class="rating-item"> + <label class="required">鏈嶅姟璇勫垎:</label> + <el-rate + v-model="evaluationForm.serviceRating" + :max="5" + :texts="['寰堝樊', '杈冨樊', '涓�鑸�', '杈冨ソ', '寰堝ソ']" + show-text + :colors="['#99A9BF', '#F7BA2A', '#FF9900']" + /> + </div> + </div> + <div class="rating-row"> + <div class="rating-item"> + <label class="required">璐ㄩ噺璇勫垎:</label> + <el-rate + v-model="evaluationForm.qualityRating" + :max="5" + :texts="['寰堝樊', '杈冨樊', '涓�鑸�', '杈冨ソ', '寰堝ソ']" + show-text + :colors="['#99A9BF', '#F7BA2A', '#FF9900']" + /> + </div> + <div class="rating-item"> + <label class="required">閫熷害璇勫垎:</label> + <el-rate + v-model="evaluationForm.speedRating" + :max="5" + :texts="['寰堝樊', '杈冨樊', '涓�鑸�', '杈冨ソ', '寰堝ソ']" + show-text + :colors="['#99A9BF', '#F7BA2A', '#FF9900']" + /> + </div> + </div> + </div> + </div> + + <!-- 璇勪环鍐呭 --> + <div class="form-item"> + <label class="required">璇勪环鍐呭:</label> + <el-input + v-model="evaluationForm.content" + type="textarea" + :rows="4" + placeholder="璇疯緭鍏ヨ瘎浠峰唴瀹�" + maxlength="500" + show-word-limit + /> + </div> + + <!-- 鍖垮悕鍙戝竷閫夐」 --> + <div class="form-item"> + <label></label> + <div class="anonymous-option"> + <el-checkbox v-model="evaluationForm.isAnonymous"> + 鍖垮悕鍙戝竷璇勪环 + </el-checkbox> + <div class="anonymous-tip"> + <el-icon><InfoFilled /></el-icon> + <span>閫夋嫨鍖垮悕鍙戝竷鍚庯紝鎮ㄧ殑濮撳悕灏嗕笉浼氬湪璇勪环涓樉绀�</span> + </div> + </div> + </div> + </div> + <div class="evaluation-actions"> + <el-button @click="goBack">杩斿洖</el-button> + <el-button + type="primary" + @click="handleSubmitEvaluation" + :loading="evaluationLoading" + > + 鎻愪氦璇勪环 + </el-button> + </div> + </div> + </el-card> + </div> + </template> <script setup lang="ts"> import { onMounted, reactive, ref, computed, type CSSProperties } from 'vue' import { useRoute, useRouter } from 'vue-router' -import { Document, User, Goods, List } from '@element-plus/icons-vue' -import { fetchOrderDetail, submitEvaluate } from '@/api/tradeManage' -import { ElMessage, FormInstance } from 'element-plus' +import { Document, User, Goods, List, InfoFilled } from '@element-plus/icons-vue' +import { ElMessage, ElMessageBox } from 'element-plus' +import orderApi from '@/api/orderApi' +import { useUserInfo } from '@/stores/modules/userInfo' +import createAxios from '@/utils/axios' const route = useRoute() const router = useRouter() +const userStore = useUserInfo() const detail = reactive<any>({ items: [] }) -const formRef = ref<FormInstance>() -const form = reactive({ score: 0, content: '' }) +const form = reactive<any>({ items: [] }) const fileList = ref<any[]>([]) -const remarkItems = ref<any[]>([]) const orderTableWrapRef = ref<HTMLElement | null>(null) const labelStyle = { width: '180px', maxWidth: '180px' } const contentStyle = { width: 'calc(50% - 180px)' } -const rules = { - score: [{ required: true, message: '璇疯瘎鍒�', trigger: 'change' }], - content: [{ required: true, message: '璇疯緭鍏ヨ瘎浠峰唴瀹�', trigger: 'blur' }], -} +// 璇勪环琛ㄥ崟鏁版嵁 +const evaluationForm = reactive({ + content: '', + overallRating: 0, // 缁煎悎璇勫垎 + serviceRating: 0, // 鏈嶅姟璇勫垎 + qualityRating: 0, // 璐ㄩ噺璇勫垎 + speedRating: 0, // 閫熷害璇勫垎 + isAnonymous: false // 鏄惁鍖垮悕鍙戝竷 +}) + +// 璇勪环loading鐘舵�� +const evaluationLoading = ref(false) // 璁$畻琛ㄦ牸鏁版嵁锛屾坊鍔犳眹鎬昏 const tableData = computed(() => { @@ -284,126 +372,169 @@ return [...detail.items, summaryRow] }) +// 鐘舵�佹槧灏勶紙鍚庣涓枃 -> 鍓嶇鏋氫妇锛� +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 () => { - // 浣跨敤鍓嶇妯℃嫙鏁版嵁浠ヤ究寮�鍙� UI锛堜笉鏀瑰姩鍚庣鏈嶅姟锛� - const mockDetail = { - orderNo: '4348442557619205545', - resourceTypeName: '杞欢浜у搧', - status: 'FINISH', - statusName: '宸插畬鎴�', - applyTime: '2025-05-21 10:00:00', - unitName: '涓氦鏂硅繙寤鸿鏈夐檺鍏徃', - userName: '寮犻潤', - userAccount: 'L20159922', - userDept: '淇℃伅涓績', - userPhone: '13800000000', - productName: '涓氦鏂硅繙鏅鸿兘瀹炴祴瀹為噺绠$悊绯荤粺', - supplier: '涓氦鏂硅繙绉戞妧鏈夐檺鍏徃', - industry: '寤虹瓚宸ョ▼', - projectUnit: '鍦熷缓宸ョ▼', - productType: '杞欢搴�', - productDesc: - '闈㈠悜宸ョ▼椤圭洰鐨勮川閲忓疄娴嬪疄閲忔暟瀛楀寲绠$悊绯荤粺锛屾敮鎸佹爣鍑嗗寲閲囬泦銆佽嚜鍔ㄧ粺璁′笌杩囩▼绠℃帶銆�', - items: [ - { - id: '1', - name: '浼佷笟绉佹湁SaaS鐗堣鍙�', - saleType: '涔版柇', - accountCount: 50, - customerTarget: '浼佷笟', - concurrentNodes: 50, - pricePoint: 50000, - priceCash: 0, - quantity: 1, - period: 0, - }, - { - id: '2', - name: '浼佷笟绉佹湁SaaS鐗圤TA鍗囩骇鏈嶅姟', - saleType: 'OTA鏈嶅姟', - accountCount: 50, - customerTarget: '浼佷笟', - concurrentNodes: 50, - pricePoint: 0, - priceCash: 7500, - quantity: 1, - period: 1, - }, - { - id: '3', - name: '浼佷笟绉佹湁SaaS鐗堢敤鎴峰閲忓寘', - saleType: '绉佹湁澧為噺鍖�', - accountCount: 100, - customerTarget: '浼佷笟', - concurrentNodes: 100, - pricePoint: 0, - priceCash: 0, - priceProtocol: true, - quantity: 1, - period: 1, - }, - { - id: '4', - name: '涓汉鍏湁SaaS鐗堣鍙�', - saleType: '绉佹湁澧為噺鍖�', - accountCount: 50, - customerTarget: '涓汉', - concurrentNodes: 50, - pricePoint: 0, - priceCash: 0, - quantity: 1, - period: 0, - }, - ], - pointTotal: '50,000', - cashTotal: '7,500', - } - - Object.assign(detail, mockDetail) + const orderId = String(route.params.id || '') + if (!orderId) return - // 娣诲姞妯℃嫙鏂囦欢鏁版嵁鐢ㄤ簬灞曠ず - fileList.value = [ - { - name: '绛惧瓧鐩栫珷鏂囦欢.pdf', - size: 2621440, // 2.5MB - uid: '1', - status: 'success' - }, - { - name: 'API Keys.txt', - size: 354, // 354 Bytes - uid: '2', - status: 'success' + try { + const res = (await orderApi.getOrderDetail(orderId)) as any + const data = res?.data || {} + + const statusName: string = data.orderStatus || '' + const uiStatus = statusServerToUi[statusName] || 'INFO' + + // 鏄犲皠璁㈠崟璇︽儏澶撮儴淇℃伅 + 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 || '-', } - ] - // 娣诲姞妯℃嫙浜ゆ槗淇℃伅澶囨敞鏁版嵁 - remarkItems.value = (detail.items || []).map((item: any, index: number) => ({ - name: item.name, - start: '2025-06-01', - end: index === 0 || index === 3 ? '' : '2025-06-01', - forever: index === 0 || index === 3, - remark: index === 3 ? '寮�閫氱鐞嗗憳璐﹀彿1涓�,璐﹀彿admin' : '寮�閫氱鐞嗗憳璐﹀彿1涓�,璐﹀彿admin,鐧诲綍绠$悊鍛樿处鍙峰彲绠$悊鏅�氱敤鎴�' - })) - - // 娉ㄩ噴鎺夊師鏈夌殑API璋冪敤锛屼娇鐢ㄦā鎷熸暟鎹� - // const { data } = (await fetchOrderDetail({ id: route.params.id })) as any - // Object.assign(detail, data || {}) + // 鏄庣粏椤规槧灏� + 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(), + }) + + // 鍒濆鍖栬〃鍗曟暟鎹� + form.items = (detail.items || []).map((item: any, index: number) => { + // 璁$畻鎺堟潈缁撴潫鏃堕棿 + let endDate = '' + if (item.period > 0) { + // 濡傛灉鏈夋湡闄愶紝璁$畻缁撴潫鏃堕棿 + const startDate = new Date(data.applyTime || new Date()) + const endDateObj = new Date(startDate) + endDateObj.setFullYear(endDateObj.getFullYear() + item.period) + endDate = endDateObj.toISOString().split('T')[0] // 鏍煎紡鍖栦负 YYYY-MM-DD + } + + return { + name: item.name, + start: data.applyTime ? data.applyTime.split('T')[0] : '', // 浣跨敤璁㈠崟鐢宠鏃堕棿 + end: endDate, + forever: item.period === 0, // 鏈熼檺涓�0鏃惰缃负姘镐箙 + 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 submit = async () => { - await formRef.value?.validate() - - // 妯℃嫙鎻愪氦鎴愬姛鍝嶅簲 - const mockResponse = { code: 200 } - - // 娉ㄩ噴鎺夊師鏈夌殑API璋冪敤锛屼娇鐢ㄦā鎷熸暟鎹� - // const { code } = (await submitEvaluate({ id: route.params.id, ...form })) as any - - if (mockResponse.code === 200) { - ElMessage.success('鎻愪氦鎴愬姛') - router.back() + try { + const orderId = String(route.params.id || '') + const userId = userStore.getUserId ? Number(userStore.getUserId) : undefined + + if (!orderId || !userId) { + ElMessage.error('璁㈠崟ID鎴栫敤鎴稩D涓嶈兘涓虹┖') + return + } + + // 纭鎿嶄綔 + await ElMessageBox.confirm('纭畾瑕佺‘璁や氦鏄撳苟鎻愪氦瀹℃壒鍚楋紵', '纭鎿嶄綔', { + confirmButtonText: '纭畾', + cancelButtonText: '鍙栨秷', + type: 'warning' + }) + + // 璋冪敤鏇存柊璁㈠崟璇︽儏API锛屽皢鐘舵�佹洿鏂颁负"宸茶瘎浠�" + const updateData = { + orderId: orderId, + orderStatus: '宸茶瘎浠�', // 鏇存柊涓哄凡璇勪环鐘舵�� + orderDetails: detail.items.map((item: any) => ({ + id: item.id, + remarks: item.remarks || '' // 浣跨敤濂椾欢淇℃伅涓殑remarks瀛楁 + })) + } + + const res = (await orderApi.updateOrderDetail(updateData)) as any + + if (res?.code === 200) { + ElMessage.success('浜ゆ槗纭鎴愬姛锛屽凡鎻愪氦瀹℃壒') + router.back() + } else { + ElMessage.error(res?.msg || '浜ゆ槗纭澶辫触') + } + } catch (error) { + if (error !== 'cancel') { + console.error('浜ゆ槗纭澶辫触:', error) + ElMessage.error('浜ゆ槗纭澶辫触') + } } } @@ -481,15 +612,6 @@ // 鏂囦欢鍒楄〃琛ㄦ牸琛ㄤ綋鏂囧瓧澶у皬 const fileTableCellStyle: CSSProperties = { fontSize: '12px' } -// 浜ゆ槗淇℃伅澶囨敞琛ㄦ牸琛ㄥご鏂囧瓧灞呬腑锛屼絾绗竴鍒楃殑"浜ゆ槗淇℃伅澶囨敞"鏂囧瓧闈犲乏瀵归綈 -const remarkTableHeaderStyle: CSSProperties = { - textAlign: 'center', - fontSize: '14px', - background: '#f3f6fb' -} -// 浜ゆ槗淇℃伅澶囨敞琛ㄦ牸琛ㄤ綋鏂囧瓧澶у皬 -const remarkTableCellStyle: CSSProperties = { fontSize: '12px' } - // 鏂囦欢澶у皬鏍煎紡鍖� const formatFileSize = (size: number) => { if (!size || size === 0) return '0 Bytes'; @@ -499,10 +621,265 @@ return parseFloat((size / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; }; -// 鏂囦欢棰勮澶勭悊 -const handlePreview = (file: any) => { - ElMessage.info(`棰勮鏂囦欢锛�${file.name}`) +// 鍒ゆ柇鏂囦欢鏄惁鍙瑙� +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 + 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('涓嬭浇澶辫触锛岃閲嶈瘯') + } +} + +// 鎻愪氦璇勪环澶勭悊 +const handleSubmitEvaluation = async () => { + // 楠岃瘉璇勫垎鏄惁宸插~鍐� + if (!evaluationForm.overallRating || !evaluationForm.serviceRating || + !evaluationForm.qualityRating || !evaluationForm.speedRating) { + ElMessage.warning('璇峰畬鎴愭墍鏈夎瘎鍒嗛」鐩�') + return + } + + if (!evaluationForm.content.trim()) { + ElMessage.warning('璇疯緭鍏ヨ瘎浠峰唴瀹�') + return + } + + try { + evaluationLoading.value = true + await ElMessageBox.confirm('纭畾瑕佹彁浜よ瘎浠峰悧锛�', '纭鎿嶄綔', { + confirmButtonText: '纭畾', + cancelButtonText: '鍙栨秷', + type: 'warning' + }) + + const orderId = String(route.params.id || '') + const userId = userStore.getUserId ? Number(userStore.getUserId) : undefined + const content = evaluationForm.content.trim() + + if (!orderId || !userId) { + ElMessage.error('璁㈠崟ID鎴栫敤鎴稩D涓嶈兘涓虹┖') + evaluationLoading.value = false + return + } + + // 璋冪敤鎻愪氦璇勪环API + const result = await orderApi.submitEvaluation({ + orderId: orderId, + evaluationContent: content, + evaluatorId: userId, + evaluatorName: evaluationForm.isAnonymous ? '鍖垮悕鐢ㄦ埛' : (userStore.getUserDetail || '绠$悊鍛�'), + overallRating: evaluationForm.overallRating, + serviceRating: evaluationForm.serviceRating, + qualityRating: evaluationForm.qualityRating, + speedRating: evaluationForm.speedRating, + isAnonymous: evaluationForm.isAnonymous + }) + + if (result && result.code === 200) { + // 鏇存柊璁㈠崟鐘舵�佽繘鍏ヤ笅涓�涓姸鎬� + await orderApi.updateOrderStatusToNext(orderId) + ElMessage.success('璇勪环鎻愪氦鎴愬姛') + router.back() + evaluationLoading.value = false + } else { + ElMessage.error(result?.msg || '璇勪环鎻愪氦澶辫触') + } + } catch (error) { + if (error !== 'cancel') { + console.error('璇勪环鎻愪氦澶辫触:', error) + ElMessage.error('璇勪环鎻愪氦澶辫触') + } + } finally { + evaluationLoading.value = false + } +} + // 鐥囩粨涓庝慨澶嶈鏄庯細 // 1) Element Plus 鐨� el-table 瀛愬垪 width 鐧惧垎姣旀槸鐩稿浜庤〃鏍煎鍣ㄧ殑鍍忕礌瀹藉害璁$畻锛屼絾鍙湁鍦ㄨ〃鏍煎鍣ㄦ湁鏄庣‘瀹藉害鏃舵墠鐢熸晥銆� @@ -706,36 +1083,152 @@ text-align: left !important; } -/* 浜ゆ槗淇℃伅澶囨敞琛ㄦ牸鏍峰紡 */ -.remark-section { - margin-top: 15px; - .remark-table { - width: 100%; - .remark-name { - font-weight: 500; +/* 鏂囦欢鎿嶄綔鎸夐挳鏍峰紡 */ +.file-actions { + display: flex; + gap: 8px; + align-items: center; + justify-content: center; + + .preview-btn { + color: #409eff; + &:hover { + text-decoration: underline; } - .remark-date { - color: #606266; + &:disabled { + color: #c0c4cc; + cursor: not-allowed; } - .remark-content { - color: #606266; - line-height: 1.4; + } + + .download-btn { + color: #67c23a; + &:hover { + text-decoration: underline; + } + &:disabled { + color: #c0c4cc; + cursor: not-allowed; } } } -/* 浜ゆ槗淇℃伅澶囨敞琛ㄦ牸琛ㄥご绗竴鍒�"浜ゆ槗淇℃伅澶囨敞"鏂囧瓧闈犲乏瀵归綈 */ -.remark-table :deep(.el-table__header-wrapper thead tr th:first-child) { - text-align: left !important; +/* 浜ゆ槗淇℃伅澶囨敞琛ㄦ牸鏍峰紡 */ +.remark-table { + width: 100%; + .end-time-wrapper { + display: flex; + align-items: center; + gap: 10px; + .forever-text { + color: #409eff; + font-weight: 500; + } + } } -/* 鎿嶄綔鎸夐挳鏍峰紡 */ -.action-buttons { - display: flex; - justify-content: flex-end; - margin-top: 15px; - .el-button { - margin-left: 10px; +/* 浜ゆ槗璇勪环鏍峰紡 */ +.evaluation-content { + .evaluation-form { + /* 璇勫垎閮ㄥ垎鏍峰紡 */ + .rating-section { + margin-bottom: 30px; + + .rating-title { + font-weight: 600; + margin-bottom: 15px; + color: #303133; + } + + .rating-items { + display: flex; + flex-direction: column; + gap: 20px; + + .rating-row { + display: flex; + gap: 40px; + + .rating-item { + flex: 1; + display: flex; + align-items: center; + + label { + width: 100px; + line-height: 32px; + margin-right: 15px; + font-weight: 500; + white-space: nowrap; + + &.required::before { + content: '*'; + color: #f56c6c; + margin-right: 4px; + } + } + + .el-rate { + flex: 1; + } + } + } + } + } + + .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; + } + + /* 鍖垮悕閫夐」鏍峰紡 */ + .anonymous-option { + display: flex; + align-items: center; + gap: 15px; + + .anonymous-tip { + display: flex; + align-items: center; + gap: 6px; + color: #909399; + font-size: 12px; + + .el-icon { + color: #409eff; + font-size: 14px; + } + } + } + } + } + + .evaluation-actions { + display: flex; + justify-content: center; + gap: 15px; + margin-top: 30px; + + .el-button { + min-width: 100px; + } } } </style> -- Gitblit v1.8.0