refactor(download): 统一附件下载逻辑到 download 插件
| | |
| | | |
| | | <script> |
| | | import request from '@/utils/request' |
| | | import { resolveAttachmentUrl } from '@/utils/attachment' |
| | | import filePreview from '@/components/Preview/filePreview.vue' |
| | | |
| | | /** |
| | |
| | | this.previewVisible = true |
| | | }, |
| | | downloadFile(row) { |
| | | this.$download.saveAs(this.fullUrl(row.fileUrl), row.fileName) |
| | | return this.$download.attachment(row, row.fileName) |
| | | }, |
| | | removeFile(row) { |
| | | this.$confirm('æ¯å¦å é¤è¯¥éä»¶ï¼', 'æç¤º', { type: 'warning' }).then(() => { |
| | |
| | | }, |
| | | // éæå°åæ¼æ¥ï¼ä¸å端ä¸ä¼ ç»ä¸è·¯ç±ä¿æä¸è´ï¼å¾çå¨ /imgï¼å
¶å®é½å¨ /word |
| | | fullUrl(fileUrl) { |
| | | const base = /\.(jpg|jpeg|png|gif)$/i.test(fileUrl) ? '/img/' : '/word/' |
| | | return this.javaApi + base + fileUrl |
| | | return resolveAttachmentUrl(fileUrl) |
| | | } |
| | | } |
| | | } |
| | |
| | | <!-- æä»¶å表 --> |
| | | <transition-group ref="uploadFileList" class="upload-file-list el-upload-list el-upload-list--text" name="el-fade-in-linear" tag="ul"> |
| | | <li :key="file.uid" class="el-upload-list__item ele-upload-list__item-content" v-for="(file, index) in fileList"> |
| | | <el-link :href="`${baseUrl}${file.url}`" underline="never" target="_blank"> |
| | | <el-link @click="proxy.$download.saveAs(file.url, getFileName(file.name))" underline="never"> |
| | | <lims-icon name="el-icon-document" /> {{ getFileName(file.name) }} |
| | | </el-link> |
| | | <div class="ele-upload-list__item-content-action"> |
| | |
| | | const emit = defineEmits() |
| | | const number = ref(0) |
| | | const uploadList = ref([]) |
| | | const baseUrl = import.meta.env.VITE_APP_BASE_API |
| | | const uploadFileUrl = ref(import.meta.env.VITE_APP_BASE_API + props.action) // ä¸ä¼ æä»¶æå¡å¨å°å |
| | | const headers = ref({ Authorization: "Bearer " + getToken() }) |
| | | const fileList = ref([]) |
| | |
| | | import { getToken } from '@/utils/auth' |
| | | import errorCode from '@/utils/errorCode' |
| | | import { blobValidate } from '@/utils/ruoyi' |
| | | import { getJavaApi } from '@/utils/java-api' |
| | | import { resolveAttachmentUrl, attachmentFileName, alternateImageAttachmentUrl, validateAttachmentBlob } from '@/utils/attachment' |
| | | |
| | | const baseURL = import.meta.env.VITE_APP_BASE_API |
| | | let downloadLoadingInstance |
| | |
| | | }) |
| | | }, |
| | | saveAs(text, name, opts) { |
| | | saveAs(text, name, opts) |
| | | if (typeof text === 'string') return this.url(text, name, opts) |
| | | return saveAs(text, name, opts) |
| | | }, |
| | | attachment(file, name) { |
| | | try { |
| | | return this.url(resolveAttachmentUrl(file?.fileUrl, file?.type), name || file?.fileName, undefined, |
| | | ![1, 2].includes(Number(file?.type))) |
| | | } catch (error) { |
| | | ElMessage.error(error.message) |
| | | return Promise.resolve(false) |
| | | } |
| | | }, |
| | | async url(path, name, opts, allowAlternative = true) { |
| | | try { |
| | | const url = resolveAttachmentUrl(path) |
| | | const backend = new URL(getJavaApi(), window.location.origin) |
| | | const target = new URL(url, window.location.origin) |
| | | const isBackend = target.origin === backend.origin |
| | | && (target.pathname === backend.pathname || target.pathname.startsWith(backend.pathname.replace(/\/$/, '') + '/')) |
| | | const filename = attachmentFileName(url, name) |
| | | // å¤é¨éä»¶æ²¿ç¨æµè§å¨ä¸è½½ï¼å
¼å®¹ä¸å
许跨å读åçæä»¶æå¡ã |
| | | if (!isBackend && /^https?:$/.test(target.protocol)) { |
| | | saveAs(url, filename, opts) |
| | | return true |
| | | } |
| | | const headers = isBackend && getToken() ? { Authorization: 'Bearer ' + getToken() } : {} |
| | | let response |
| | | try { |
| | | response = await axios.get(url, { responseType: 'blob', headers }) |
| | | } catch (error) { |
| | | const alternative = isBackend && allowAlternative && error.response?.status === 404 |
| | | ? alternateImageAttachmentUrl(url) : null |
| | | if (!alternative) throw error |
| | | response = await axios.get(alternative, { responseType: 'blob', headers }) |
| | | } |
| | | const { data } = response |
| | | await validateAttachmentBlob(data, filename) |
| | | saveAs(data, filename, opts) |
| | | return true |
| | | } catch (error) { |
| | | const status = error.response?.status |
| | | ElMessage.error(status === 404 ? 'éä»¶ä¸åå¨æå·²è¢«å é¤' : status === 401 || status === 403 |
| | | ? 'ç»å½å·²è¿æææ²¡æéä»¶ä¸è½½æé' : error.message || 'éä»¶ä¸è½½å¤±è´¥ï¼è¯·ç¨åéè¯') |
| | | return false |
| | | } |
| | | }, |
| | | async printErrMsg(data) { |
| | | const resText = await data.text() |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | import { getJavaApi } from '@/utils/java-api' |
| | | |
| | | const imageExtension = /\.(jpg|jpeg|png|gif|bmp|webp|svg|ico|tiff?|avif|heic)$/i |
| | | |
| | | function decodePart(value) { |
| | | try { |
| | | return decodeURIComponent(value) |
| | | } catch { |
| | | return value |
| | | } |
| | | } |
| | | |
| | | // æ°æ®åºå¯è½ä¿åæä»¶åãéæè·¯å¾æå®æ´å°åï¼åªä¸ºæä»¶åè¡¥å
åå¨ç®å½ã |
| | | export function resolveAttachmentUrl(value, type, base = getJavaApi()) { |
| | | if (typeof value !== 'string' || !value.trim()) throw new Error('éä»¶å°å为空') |
| | | let path = value.replace(/\\/g, '/') |
| | | base = base.replace(/\/+$/, '') |
| | | if (/^(blob:|data:)/i.test(path)) return path |
| | | if (path.startsWith(base + '/')) path = path.slice(base.length) |
| | | else if (/^(https?:)?\/\//i.test(path)) return new URL(path, window.location.origin).href |
| | | else if (/^[a-z][a-z\d+.-]*:/i.test(path)) throw new Error('éä»¶å°åæ ¼å¼ä¸æ£ç¡®') |
| | | |
| | | // å
¼å®¹å·²ç»å¸¦æ /dev-api ç代çåç¼çå°åï¼é¿å
é夿¼æ¥ã |
| | | const basePath = new URL(base, window.location.origin).pathname.replace(/\/$/, '') |
| | | if (basePath && path.startsWith(basePath + '/')) path = path.slice(basePath.length) |
| | | path = path.replace(/^\/+/, '') |
| | | if (!/^(img|word|profile)\//i.test(path)) { |
| | | const isImage = Number(type) === 1 || (Number(type) !== 2 && imageExtension.test(decodePart(path))) |
| | | path = (isImage ? 'img/' : 'word/') + path |
| | | } |
| | | // éæ®µç¼ç ï¼ä¿çç®å½åå·²æç¼ç ï¼æä»¶åéç #ã?ãç©ºæ ¼ä¸ä¸æåä½ä¸ºæä»¶åå¤çã |
| | | const encodedPath = path.split('/').map(part => encodeURIComponent(decodePart(part))).join('/') |
| | | return base + '/' + encodedPath |
| | | } |
| | | |
| | | export function attachmentFileName(url, name) { |
| | | const path = new URL(url, window.location.origin).pathname |
| | | const storedName = decodePart(path.split('/').pop() || '') |
| | | const filename = (name || storedName || 'éä»¶').replace(/\\/g, '/').split('/').pop() |
| | | const extension = storedName.match(/\.[a-z\d]{1,10}$/i)?.[0] || '' |
| | | return extension && !/\.[a-z\d]{1,10}$/i.test(filename) ? filename + extension : filename |
| | | } |
| | | |
| | | // åå²ä¸ä¼ æ¥å£å¯¹ BMP/WebP çæ ¼å¼çç®å½å¤æä¸ä¸è´ï¼404 æ¶å
¼å®¹å¦ä¸åå¨ç®å½ã |
| | | export function alternateImageAttachmentUrl(url) { |
| | | const parsed = new URL(url, window.location.origin) |
| | | if (!imageExtension.test(decodePart(parsed.pathname)) || !/\/(img|word)\//.test(parsed.pathname)) return null |
| | | parsed.pathname = parsed.pathname.replace(/\/(img|word)\//, (_, directory) => directory === 'img' ? '/word/' : '/img/') |
| | | return parsed.href |
| | | } |
| | | |
| | | // ä¸è½½å¤±è´¥ç JSON/HTML ååºä¸è½ä¿åæ WordãPDF çâæåçéä»¶âã |
| | | export async function validateAttachmentBlob(blob, filename) { |
| | | const head = (await blob.slice(0, 512).text()).trim() |
| | | if (head.startsWith('{') && blob.size < 1024 * 1024) { |
| | | let result |
| | | try { |
| | | result = JSON.parse(await blob.text()) |
| | | } catch { |
| | | return |
| | | } |
| | | if (result && ((result.code != null && Number(result.code) !== 200) || result.success === false)) { |
| | | throw new Error(result.msg || result.message || 'éä»¶ä¸è½½å¤±è´¥') |
| | | } |
| | | } |
| | | if (/^(<!doctype html|<html)/i.test(head) && !/\.html?$/i.test(filename)) { |
| | | throw new Error('æªè·åå°éä»¶å
容ï¼è¯·ç¡®è®¤æä»¶å卿鿰ç»å½åéè¯') |
| | | } |
| | | } |
| | |
| | | }, |
| | | // ä¸è½½ |
| | | upload (row) { |
| | | let url = ''; |
| | | if(row.type==1){ |
| | | url = this.javaApi+'/img/'+row.fileUrl |
| | | file.downloadIamge(url,row.fileName) |
| | | }else{ |
| | | url = this.javaApi+'/word/'+row.fileUrl |
| | | const link = document.createElement('a'); |
| | | link.href = url; |
| | | link.download = row.fileName; |
| | | link.click(); |
| | | } |
| | | return this.$download.attachment(row, row.fileName) |
| | | }, |
| | | // å é¤ |
| | | delete (row) { |
| | |
| | | </template> |
| | | |
| | | <script> |
| | | import fileDownload from '@/utils/file' |
| | | |
| | | import { |
| | | addAnnex, |
| | | addBasicInfoWork, |
| | |
| | | }, |
| | | // ä¸è½½ |
| | | upload(row) { |
| | | let url = ''; |
| | | if (row.type == 1) { |
| | | url = this.javaApi + '/img/' + row.fileUrl |
| | | fileDownload.downloadIamge(url, row.fileName) |
| | | } else { |
| | | url = this.javaApi + '/word/' + row.fileUrl |
| | | const link = document.createElement('a'); |
| | | link.href = url; |
| | | link.download = row.fileName; |
| | | link.click(); |
| | | } |
| | | return this.$download.attachment(row, row.fileName) |
| | | }, |
| | | // å é¤ |
| | | delete(row) { |
| | |
| | | } |
| | | }, |
| | | downloadFile(fileName) { |
| | | let state = /\.(jpg|jpeg|png|gif)$/i.test(fileName) |
| | | if (state) { |
| | | let url = this.javaApi + '/img/' + fileName; |
| | | fileDownload.downloadIamge(url, fileName) |
| | | } else { |
| | | const url = this.javaApi + '/word/' + fileName |
| | | const link = document.createElement('a'); |
| | | link.href = url; |
| | | link.download = fileName; |
| | | link.click(); |
| | | this.$message.success('ä¸è½½æå') |
| | | } |
| | | return this.$download.saveAs(fileName, fileName) |
| | | }, |
| | | async onSuccess(response, file, fileList, entityVal) { |
| | | if (entityVal == 'fileName') { |
| | |
| | | }, |
| | | // ä¸è½½ |
| | | upload (row) { |
| | | let url = ''; |
| | | if(row.type==1){ |
| | | url = this.javaApi+'/img/'+row.fileUrl |
| | | file.downloadIamge(url,row.fileName) |
| | | }else{ |
| | | url = this.javaApi+'/word/'+row.fileUrl |
| | | const link = document.createElement('a'); |
| | | link.href = url; |
| | | link.download = row.fileName; |
| | | link.click(); |
| | | } |
| | | return this.$download.attachment(row, row.fileName) |
| | | }, |
| | | // å é¤ |
| | | delete (row) { |
| | |
| | | }, |
| | | // ä¸è½½ |
| | | upload (row) { |
| | | let url = ''; |
| | | if(row.type==1){ |
| | | url = this.javaApi+'/img/'+row.fileUrl |
| | | file.downloadIamge(url,row.fileName) |
| | | }else{ |
| | | url = this.javaApi+'/word/'+row.fileUrl |
| | | const link = document.createElement('a'); |
| | | link.href = url; |
| | | link.download = row.fileName; |
| | | link.click(); |
| | | } |
| | | return this.$download.attachment(row, row.fileName) |
| | | }, |
| | | // å é¤ |
| | | delete (row) { |
| | |
| | | <script> |
| | | import filePreview from '@/components/Preview/filePreview.vue'; |
| | | import limsTable from '@/components/Table/lims-table.vue' |
| | | import file from '@/utils/file'; |
| | | |
| | | import { getCurrentCalibrationFiles } from '@/api/cnas/process/method/methodVerification' |
| | | |
| | | export default { |
| | |
| | | }, |
| | | // ä¸è½½ |
| | | upload (row) { |
| | | let url = ''; |
| | | if (/\.(png|jpe?g|gif|bmp|webp)$/i.test(row.fileUrl || '')) { |
| | | url = this.javaApi+'/img/'+row.fileUrl |
| | | file.downloadIamge(url,row.fileName) |
| | | }else{ |
| | | url = this.javaApi+'/word/'+row.fileUrl |
| | | const link = document.createElement('a'); |
| | | link.href = url; |
| | | link.download = row.fileName; |
| | | link.click(); |
| | | } |
| | | return this.$download.attachment(row, row.fileName) |
| | | }, |
| | | } |
| | | }; |
| | |
| | | </template> |
| | | |
| | | <script> |
| | | import file from '@/utils/file'; |
| | | |
| | | import filePreview from '@/components/Preview/filePreview.vue'; |
| | | import limsTable from '@/components/Table/lims-table.vue' |
| | | import {getCurrentWorkFiles} from '@/api/cnas/process/method/methodVerification' |
| | |
| | | this.$message.warning('该人åå·²æä»»èææè®°å½ï¼ä½å°æªä¸ä¼ ä¸å²è¯ä¹¦æä»¶') |
| | | return |
| | | } |
| | | let url = ''; |
| | | if (/\.(png|jpe?g|gif|bmp|webp)$/i.test(row.fileUrl || '')) { |
| | | url = this.javaApi + '/img/' + row.fileUrl |
| | | file.downloadIamge(url, row.fileName) |
| | | } else { |
| | | url = this.javaApi + '/word/' + row.fileUrl |
| | | const link = document.createElement('a'); |
| | | link.href = url; |
| | | link.download = row.fileName; |
| | | link.click(); |
| | | } |
| | | return this.$download.attachment({ fileUrl: row.fileUrl }, row.fileName) |
| | | }, |
| | | }, |
| | | }; |
| | |
| | | }, |
| | | // end |
| | | handleAttachmentClick(row) { |
| | | // 模æä¸è½½éä»¶ |
| | | const imageUrl = this.javaApi + '/img/' + row.systemFileName; // å¾ç URL |
| | | file.downloadIamge(imageUrl, row.fileName) |
| | | // downloadImage(imageUrl, row.fileName); |
| | | return this.$download.attachment({ fileUrl: row.systemFileName }, row.fileName) |
| | | }, |
| | | // æ·»å æ ¸æ¥è®°å½ |
| | | add(type) { |
| | |
| | | } |
| | | } |
| | | |
| | | function downloadImage(url, name) { |
| | | const link = document.createElement('a'); |
| | | link.href = url; |
| | | link.download = name; // æä»¶å |
| | | document.body.appendChild(link); |
| | | link.click(); |
| | | document.body.removeChild(link); |
| | | } |
| | | </script> |
| | | <style scoped> |
| | | .dialog-content { |
| | |
| | | if (!this.form.fileUrl) { |
| | | return |
| | | } |
| | | const isImage = /\.(jpg|jpeg|png|gif)$/i.test(this.form.fileUrl) |
| | | window.open(this.javaApi + (isImage ? '/img/' : '/word/') + this.form.fileUrl, '_blank') |
| | | return this.$download.saveAs(this.form.fileUrl, this.form.fileName) |
| | | }, |
| | | }, |
| | | computed: { |
| | |
| | | }, |
| | | // æå¼éä»¶ |
| | | openAttachment(row) { |
| | | const isImage = /\.(jpg|jpeg|png|gif)$/i.test(row.fileUrl) |
| | | window.open(this.javaApi + (isImage ? '/img/' : '/word/') + row.fileUrl, '_blank') |
| | | return this.$download.saveAs(row.fileUrl, row.fileName) |
| | | }, |
| | | // å表éä»¶ååªè¯»æ¥çï¼ä¸ä¼ /å é¤å
¥å£åªå¨ç¼è¾ãæ°å¢å¼¹çªå
ï¼ |
| | | handleAttachment(row) { |
| | |
| | | </template> |
| | | |
| | | <script> |
| | | import fileDownload from "@/utils/file"; |
| | | |
| | | import { |
| | | approvalOfHomeworkInstructionManual, |
| | | homeworkGuidebook, |
| | |
| | | }); |
| | | }, |
| | | downloadFile(fileName) { |
| | | let state = /\.(jpg|jpeg|png|gif)$/i.test(fileName) |
| | | if (state) { |
| | | let url = this.javaApi + '/img/' + fileName; |
| | | fileDownload.downloadIamge(url, fileName) |
| | | } else { |
| | | const url = this.javaApi + '/word/' + fileName |
| | | const link = document.createElement('a'); |
| | | link.href = url; |
| | | link.download = fileName; |
| | | link.click(); |
| | | this.$message.success('ä¸è½½æå') |
| | | } |
| | | return this.$download.saveAs(fileName, fileName) |
| | | }, |
| | | instructionEditFun(row) { |
| | | this.dialogVisible = true |
| | |
| | | downFile({ |
| | | id: row.id, |
| | | }).then(res => { |
| | | this.$download.saveAs(res.data.fileUrl, row.fileName); |
| | | return this.$download.attachment(res.data, row.fileName); |
| | | }).catch(error => { |
| | | |
| | | }) |
| | |
| | | }, |
| | | handleDown(row) { |
| | | downFile({ id: row.id }).then((res) => { |
| | | const resourcePath = Number(res.data.type) === 1 ? "/img/" : "/word/"; |
| | | const fileUrl = this.javaApi + resourcePath + encodeURIComponent(res.data.fileUrl); |
| | | this.$download.saveAs(fileUrl, row.fileName); |
| | | return this.$download.attachment(res.data, row.fileName); |
| | | }) |
| | | .catch((error) => { }); |
| | | }, |
| | |
| | | }, |
| | | handleDown(row) { |
| | | downFile({ id: row.id }).then((res) => { |
| | | const resourcePath = Number(res.data.type) === 1 ? "/img/" : "/word/"; |
| | | const fileUrl = this.javaApi + resourcePath + encodeURIComponent(res.data.fileUrl); |
| | | this.$download.saveAs(fileUrl, row.fileName); |
| | | return this.$download.attachment(res.data, row.fileName); |
| | | }) |
| | | .catch((error) => { }); |
| | | }, |
| | |
| | | // åæ¢æ°æ®æ¥çtabæ |
| | | handleDataVisibleTab(m, i) { |
| | | this.dataVisibleIndex = i |
| | | this.pageFile.current = 1 |
| | | this.getFileList() |
| | | }, |
| | | // æ¥è¯¢åè° |
| | |
| | | this.entity.insOrderId = this.filesLookInfo.quarterOrderId |
| | | } |
| | | this.tableLoadingFile = true |
| | | const params = { ...this.entity } |
| | | const params = { ...this.entity, current: this.pageFile.current, size: this.pageFile.size } |
| | | getFileList(params).then(res => { |
| | | this.tableLoadingFile = false |
| | | if (res.code === 200) { |
| | |
| | | }) |
| | | }, |
| | | paginationFile(page) { |
| | | this.pageFile.current = page.page |
| | | this.pageFile.size = page.limit |
| | | this.getFileList() |
| | | }, |
| | | // ä¸è½½ |
| | | handleDown(row) { |
| | | downFile({ id: row.id, }).then(res => { |
| | | this.$download.saveAs(res.data.fileUrl, row.fileName); |
| | | return this.$download.attachment(res.data, row.fileName); |
| | | }).catch(error => { |
| | | |
| | | }) |
| | |
| | | verifyDialogVisible: false, |
| | | quashDialogVisible: false, |
| | | filesDialogVisible: false, // éä»¶æ¥çå¼¹æ¡ |
| | | attachmentOrderId: null, |
| | | tableDataFile: [], |
| | | tableLoadingFile: false, |
| | | columnFile: [ |
| | |
| | | }, |
| | | // éä»¶æ¥ç |
| | | handleFileLook(row) { |
| | | this.attachmentOrderId = row.id |
| | | this.pageFile.current = 1 |
| | | this.tableDataFile = [] |
| | | this.filesDialogVisible = true |
| | | this.getFileList(row) |
| | | this.getFileList() |
| | | }, |
| | | getFileList(row) { |
| | | getFileList() { |
| | | this.tableLoadingFile = true |
| | | getFileList({ insOrderId: row.id }).then(res => { |
| | | getFileList({ insOrderId: this.attachmentOrderId, current: this.pageFile.current, size: this.pageFile.size }).then(res => { |
| | | this.tableLoadingFile = false |
| | | if (res.code === 200) { |
| | | this.tableDataFile = res.data.records |
| | |
| | | }) |
| | | }, |
| | | paginationFile(page) { |
| | | this.pageFile.current = page.page |
| | | this.pageFile.size = page.limit |
| | | this.getFileList() |
| | | }, |
| | |
| | | downFile({ |
| | | id: row.id, |
| | | }).then(res => { |
| | | this.$download.saveAs(res.data.fileUrl, row.fileName); |
| | | return this.$download.attachment(res.data, row.fileName); |
| | | }).catch(error => { |
| | | |
| | | }) |
| | |
| | | downFile({ |
| | | id: row.id, |
| | | }).then(res => { |
| | | this.$download.saveAs(res.data.fileUrl, row.fileName); |
| | | return this.$download.attachment(res.data, row.fileName); |
| | | }).catch(error => { |
| | | |
| | | }) |
| | |
| | | downFile({ |
| | | id: row.id, |
| | | }).then(res => { |
| | | this.$download.saveAs(res.data.fileUrl, row.fileName); |
| | | return this.$download.attachment(res.data, row.fileName); |
| | | }).catch(error => { |
| | | |
| | | }) |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | import test from 'node:test' |
| | | import assert from 'node:assert/strict' |
| | | import fs from 'node:fs' |
| | | import vm from 'node:vm' |
| | | import { parse as parseVue } from '@vue/compiler-sfc' |
| | | import { parse as parseJs } from 'acorn' |
| | | |
| | | const backend = 'https://lims.example/dev-api' |
| | | const read = path => fs.readFileSync(new URL(path, import.meta.url), 'utf8') |
| | | |
| | | function withoutImports(source) { |
| | | const imports = parseJs(source, { sourceType: 'module', ecmaVersion: 'latest' }).body |
| | | .filter(node => node.type === 'ImportDeclaration') |
| | | for (const node of imports.reverse()) source = source.slice(0, node.start) + source.slice(node.end) |
| | | return source |
| | | } |
| | | |
| | | function downloader() { |
| | | const saved = [], requests = [], errors = [] |
| | | const state = { data: new Blob(['original bytes']), error: null, missingUrls: [] } |
| | | const context = vm.createContext({ |
| | | URL, Blob, window: { location: { origin: 'https://lims.example' } }, |
| | | getJavaApi: () => backend, getToken: () => 'test-token', |
| | | saveAs: (...args) => saved.push(args), |
| | | ElMessage: { error: message => errors.push(message) }, |
| | | axios: { get: async (url, options) => { |
| | | requests.push({ url, ...options }) |
| | | if (state.missingUrls.includes(url)) throw { response: { status: 404 } } |
| | | if (state.error) throw state.error |
| | | return { data: state.data } |
| | | } }, |
| | | }) |
| | | const helper = withoutImports(read('../src/utils/attachment.js')).replace(/^export /mg, '') |
| | | vm.runInContext(helper, context) |
| | | const plugin = withoutImports(read('../src/plugins/download.js')) |
| | | .replace('import.meta.env.VITE_APP_BASE_API', "'/dev-api'") |
| | | .replace('export default', 'globalThis.download =') |
| | | vm.runInContext(plugin, context) |
| | | return { context, saved, requests, errors, state, download: context.download } |
| | | } |
| | | |
| | | test('æä»¶åè¡¥å
ææ¡£/å¾çç®å½ï¼å¹¶ä¼å
éµå¾ªéä»¶çåå¨ç±»å', () => { |
| | | const { context: c } = downloader() |
| | | for (const [file, type, directory] of [ |
| | | ['report.docx', undefined, 'word'], ['report.pdf', 2, 'word'], |
| | | ['photo.PNG', undefined, 'img'], ['photo.webp', undefined, 'img'], |
| | | ['scan.bin', 1, 'img'], ['photo.png', 2, 'word'], ['photo.bmp', 0, 'img'], |
| | | ]) assert.equal(c.resolveAttachmentUrl(file, type), `${backend}/${directory}/${file}`) |
| | | }) |
| | | |
| | | test('å·²æéæè·¯å¾ä¸ä»£çåç¼ä¸ä¼é夿¼æ¥', () => { |
| | | const { context: c } = downloader() |
| | | for (const path of ['word/a.docx', '/word/a.docx', '/dev-api/word/a.docx', `${backend}/word/a.docx`]) { |
| | | assert.equal(c.resolveAttachmentUrl(path), `${backend}/word/a.docx`) |
| | | } |
| | | assert.equal(c.resolveAttachmentUrl('/profile/upload/2026/a.pdf'), `${backend}/profile/upload/2026/a.pdf`) |
| | | assert.equal(c.resolveAttachmentUrl('word\\folder\\a.pdf'), `${backend}/word/folder/a.pdf`) |
| | | }) |
| | | |
| | | test('䏿ãç©ºæ ¼ã#ã?ãç¾åå·åå·²æç¼ç åæç宿件åä¸è½½', () => { |
| | | const { context: c } = downloader() |
| | | for (const name of ['å§æéä»¶ #1?.docx', 'æµåº¦100%.pdf', 'a+b&c.pdf']) { |
| | | const expected = `${backend}/word/${encodeURIComponent(name)}` |
| | | assert.equal(c.resolveAttachmentUrl(name), expected) |
| | | assert.equal(c.resolveAttachmentUrl(expected), expected) |
| | | } |
| | | assert.equal(c.resolveAttachmentUrl('/word/folder/a%20b.pdf'), `${backend}/word/folder/a%20b.pdf`) |
| | | }) |
| | | |
| | | test('ä¿çå¤é¨å°åæ¥è¯¢åæ°ï¼æç»ç©ºå°ååæ æåè®®', () => { |
| | | const { context: c } = downloader() |
| | | const external = 'https://files.example/a.pdf?signature=abc#page=1' |
| | | assert.equal(c.resolveAttachmentUrl(external), external) |
| | | for (const value of [null, '', ' ', 'javascript:alert(1)']) assert.throws(() => c.resolveAttachmentUrl(value)) |
| | | }) |
| | | |
| | | test('ä¸è½½åä¿çåå§æ©å±åï¼æ åç§°æ¶ä½¿ç¨å卿件å', () => { |
| | | const { context: c } = downloader() |
| | | assert.equal(c.attachmentFileName(`${backend}/word/test.pdf`, 'æ£éªéä»¶'), 'æ£éªéä»¶.pdf') |
| | | assert.equal(c.attachmentFileName(`${backend}/word/test.docx`, 'åå§è®°å½.docx'), 'åå§è®°å½.docx') |
| | | assert.equal(c.attachmentFileName(`${backend}/word/a%20b.pdf`), 'a b.pdf') |
| | | }) |
| | | |
| | | test('å¾çåææ¡£ä¸è½½åå§ Blobï¼å¹¶ä»
åä¸å¡æå¡å¨åéç»å½åæ®', async () => { |
| | | const { download, saved, requests, state } = downloader() |
| | | assert.equal(await download.attachment({ fileUrl: 'åå¾.jpg', type: 1 }, 'åå¾.jpg'), true) |
| | | assert.equal(saved[0][0], state.data) |
| | | assert.equal(saved[0][1], 'åå¾.jpg') |
| | | assert.equal(requests[0].url, `${backend}/img/${encodeURIComponent('åå¾.jpg')}`) |
| | | assert.equal(requests[0].responseType, 'blob') |
| | | assert.equal(requests[0].headers.Authorization, 'Bearer test-token') |
| | | await download.saveAs('https://files.example/a.docx?signature=abc', 'éä»¶.docx') |
| | | assert.equal(requests.length, 1) |
| | | assert.equal(saved[1][0], 'https://files.example/a.docx?signature=abc') |
| | | }) |
| | | |
| | | test('æ¥å£é误ä¸é误ç½é¡µä¸ä¿åæéä»¶ï¼åæ³ JSON/HTML é件坿£å¸¸ä¸è½½', async () => { |
| | | const { download, saved, errors, state } = downloader() |
| | | state.data = new Blob(['{"code":500,"msg":"éä»¶ä¸åå¨"}'], { type: 'application/json' }) |
| | | assert.equal(await download.saveAs('missing.docx', 'missing.docx'), false) |
| | | assert.equal(errors.pop(), 'éä»¶ä¸åå¨') |
| | | state.data = new Blob(['<!doctype html><html>Not found</html>'], { type: 'text/html' }) |
| | | assert.equal(await download.saveAs('missing.pdf'), false) |
| | | assert.equal(saved.length, 0) |
| | | state.data = new Blob(['{"hello":"world"}'], { type: 'application/json' }) |
| | | assert.equal(await download.saveAs('data.json'), true) |
| | | state.data = new Blob(['<!doctype html><html>Document</html>'], { type: 'text/html' }) |
| | | assert.equal(await download.saveAs('document.html'), true) |
| | | }) |
| | | |
| | | test('404ãç»å½å¤±æå空æä»¶å°åæç¤ºå¤±è´¥ï¼ä¸çæä¸è½½æä»¶', async () => { |
| | | const { download, saved, errors, requests, state } = downloader() |
| | | for (const status of [404, 401, 403]) { |
| | | state.error = { response: { status } } |
| | | assert.equal(await download.saveAs('a.pdf'), false) |
| | | } |
| | | const count = requests.length |
| | | assert.equal(await download.attachment({ fileUrl: null }), false) |
| | | assert.equal(requests.length, count) |
| | | assert.equal(saved.length, 0) |
| | | assert.equal(errors.length, 4) |
| | | }) |
| | | |
| | | test('ç°æå¯¼åºæ¥å£ä¼ å
¥ Blob æ¶ç´æ¥ä¿åï¼ä¸é¢å¤å起请æ±', () => { |
| | | const { download, saved, requests } = downloader() |
| | | const data = new Blob(['exported bytes']) |
| | | download.saveAs(data, '导åº.xlsx') |
| | | assert.equal(saved[0][0], data) |
| | | assert.equal(saved[0][1], '导åº.xlsx') |
| | | assert.equal(requests.length, 0) |
| | | }) |
| | | |
| | | test('å
¼å®¹åå²å¾çä¿åç®å½å·®å¼ï¼æç¡®æå®åå¨ç±»åçéä»¶ä¸çæµå
¶ä»ç®å½', async () => { |
| | | const { download, saved, requests, state } = downloader() |
| | | state.missingUrls = [`${backend}/img/scan.bmp`] |
| | | assert.equal(await download.saveAs('scan.bmp', 'åæ«æä»¶.bmp'), true) |
| | | assert.equal(requests[1].url, `${backend}/word/scan.bmp`) |
| | | assert.equal(saved[0][1], 'åæ«æä»¶.bmp') |
| | | const count = requests.length |
| | | assert.equal(await download.attachment({ fileUrl: 'scan.bmp', type: 1 }), false) |
| | | assert.equal(requests.length, count + 1) |
| | | }) |
| | | |
| | | function orderComponent(getFileList) { |
| | | const { descriptor } = parseVue(read('../src/views/business/productOrder/index.vue')) |
| | | let source = descriptor.script.content |
| | | const ast = parseJs(source, { sourceType: 'module', ecmaVersion: 'latest' }) |
| | | const edits = ast.body.filter(n => n.type === 'ImportDeclaration' || n.type === 'ExportDefaultDeclaration') |
| | | .map(n => n.type === 'ImportDeclaration' ? { start: n.start, end: n.end, text: '' } |
| | | : { start: n.start, end: n.declaration.start, text: 'globalThis.component = ' }) |
| | | for (const edit of edits.sort((a, b) => b.start - a.start)) source = source.slice(0, edit.start) + edit.text + source.slice(edit.end) |
| | | const context = vm.createContext({ vueQr: {}, PrintDialog: {}, limsTable: {}, mapGetters: () => ({}), getFileList }) |
| | | vm.runInContext(source, context) |
| | | return { ...context.component.data(), ...context.component.methods } |
| | | } |
| | | |
| | | test('ä¸åé件翻页ä¿çå§æIDï¼åæ¢å§ææ¶é置页ç åæ§å表', async () => { |
| | | const requests = [] |
| | | const instance = orderComponent(params => { |
| | | requests.push(params) |
| | | return Promise.resolve({ code: 200, data: { records: [], total: 30 } }) |
| | | }) |
| | | instance.handleFileLook({ id: 123 }) |
| | | instance.paginationFile({ page: 2, limit: 10 }) |
| | | assert.equal(requests[1].insOrderId, 123) |
| | | assert.equal(requests[1].current, 2) |
| | | assert.equal(requests[1].size, 10) |
| | | instance.handleFileLook({ id: 456 }) |
| | | assert.equal(requests[2].insOrderId, 456) |
| | | assert.equal(requests[2].current, 1) |
| | | assert.equal(instance.tableDataFile.length, 0) |
| | | }) |