From 7acc4367b3320e2a49265112fc8051f285a870bf Mon Sep 17 00:00:00 2001 From: spring <2396852758@qq.com> Date: 星期二, 05 八月 2025 14:05:51 +0800 Subject: [PATCH] 文件预览组件封装使用 --- src/main.js | 129 +++++++++++--------- package.json | 2 src/assets/styles/element-ui.scss | 18 +- src/views/salesManagement/salesLedger/fileList.vue | 9 + src/components/filePreview/index.vue | 201 +++++++++++++++++++++++++++++++++ 5 files changed, 291 insertions(+), 68 deletions(-) diff --git a/package.json b/package.json index 871409b..15334fa 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,8 @@ }, "dependencies": { "@element-plus/icons-vue": "2.3.1", + "@vue-office/docx": "^1.6.3", + "@vue-office/excel": "^1.7.14", "@vueup/vue-quill": "1.2.0", "@vueuse/core": "10.11.0", "axios": "0.28.1", diff --git a/src/assets/styles/element-ui.scss b/src/assets/styles/element-ui.scss index 0c4c3de..4eae8b2 100644 --- a/src/assets/styles/element-ui.scss +++ b/src/assets/styles/element-ui.scss @@ -56,18 +56,18 @@ padding: 0 !important; } .el-dialog__header { - background: #F5F6F7; + background: #f5f6f7; padding: 12px 16px; border-radius: 8px 8px 0 0; } .el-dialog__title { font-weight: 400; font-size: 16px; - color: #2E3033; + color: #2e3033; } .el-dialog__body { padding: 16px 40px 0 40px; - max-height: 680px; + max-height: 90vh; overflow-y: auto; } .el-dialog__footer { @@ -79,14 +79,14 @@ border-radius: 8px; } .el-message-box__header { - background: #F5F6F7; + background: #f5f6f7; padding: 12px 16px; border-radius: 8px 8px 0 0; } .el-message-box__title { font-weight: 400; font-size: 16px; - color: #2E3033; + color: #2e3033; } .el-message-box__content { padding: 16px 40px 0 40px; @@ -108,7 +108,7 @@ .el-table__expanded-cell { padding: 0 !important; .el-table__header-wrapper { - background-color: #F5F8FF !important; + background-color: #f5f8ff !important; } } @@ -127,7 +127,7 @@ // dropdown .el-dropdown-menu { a { - display: block + display: block; } } @@ -149,6 +149,6 @@ display: none; } -.el-dropdown .el-dropdown-link{ +.el-dropdown .el-dropdown-link { color: var(--el-color-primary) !important; -} \ No newline at end of file +} diff --git a/src/components/filePreview/index.vue b/src/components/filePreview/index.vue new file mode 100644 index 0000000..7221b25 --- /dev/null +++ b/src/components/filePreview/index.vue @@ -0,0 +1,201 @@ +<template> + <el-dialog v-model="dialogVisible" title="棰勮" width="100%" fullscreen align-center :before-close="handleClose" append-to-body> + <div> + <!-- 鍥剧墖棰勮 --> + <div v-if="isImage"> + <img :src="imgUrl" alt="Image Preview" /> + </div> + + <!-- PDF棰勮鎻愮ず --> + <div v-if="isPdf" style="height: 100vh; display: flex; align-items: center; justify-content: center;"> + <p>姝e湪鍑嗗PDF棰勮...</p> + </div> + + <!-- Word鏂囨。棰勮 --> + <div v-if="isDoc"> + <p v-if="!isDocShow">鏂囨。鏃犳硶鐩存帴棰勮锛岃涓嬭浇鏌ョ湅銆�</p> + <a :href="fileUrl" v-if="!isDocShow">涓嬭浇鏂囦欢</a> + <vue-office-docx + v-else + :src="fileUrl" + style="height: 100vh;" + @rendered="renderedHandler" + @error="errorHandler" + /> + </div> + + <!-- Excel鏂囨。棰勮 --> + <div v-if="isXls"> + <p v-if="!isDocShow">鏂囨。鏃犳硶鐩存帴棰勮锛岃涓嬭浇鏌ョ湅銆�</p> + <a :href="fileUrl" v-if="!isDocShow">涓嬭浇鏂囦欢</a> + <vue-office-excel + v-else + :src="fileUrl" + :options="options" + style="height: 100vh;" + @rendered="renderedHandler" + @error="errorHandler" + /> + </div> + + <!-- 鍘嬬缉鏂囦欢澶勭悊 --> + <div v-if="isZipOrRar"> + <p>鍘嬬缉鏂囦欢鏃犳硶鐩存帴棰勮锛岃涓嬭浇鏌ョ湅銆�</p> + <a :href="fileUrl">涓嬭浇鏂囦欢</a> + </div> + + <!-- 涓嶆敮鎸佺殑鏍煎紡 --> + <div v-if="!isSupported"> + <p>涓嶆敮鎸佺殑鏂囦欢鏍煎紡</p> + </div> + </div> + </el-dialog> +</template> + +<script setup> +import { ref, computed, getCurrentInstance, watch } from 'vue'; +import VueOfficeDocx from '@vue-office/docx'; +import '@vue-office/docx/lib/index.css'; +import VueOfficeExcel from '@vue-office/excel'; +import '@vue-office/excel/lib/index.css'; + +// 鍝嶅簲寮忓彉閲� +const fileUrl = ref('') +const dialogVisible = ref(false) +const { proxy } = getCurrentInstance(); +const javaApi = proxy.javaApi; + +// 鏂囨。棰勮鐘舵�� +const isDocShow = ref(true); +const imgUrl = ref(''); +const options = ref({ + xls: false, + minColLength: 0, + minRowLength: 0, + widthOffset: 10, + heightOffset: 10, + beforeTransformData: (workbookData) => workbookData, + transformData: (workbookData) => workbookData, +}); + +// 璁$畻灞炴�� - 鍒ゆ柇鏂囦欢绫诲瀷 +const isImage = computed(() => { + const state = /\.(jpg|jpeg|png|gif)$/i.test(fileUrl.value); + if (state) { + imgUrl.value = fileUrl.value.replaceAll('word', 'img'); + } + return state; +}); + +const isPdf = computed(() => { + return /\.pdf$/i.test(fileUrl.value); +}); + +const isDoc = computed(() => { + return /\.(doc|docx)$/i.test(fileUrl.value); +}); + +const isXls = computed(() => { + const state = /\.(xls|xlsx)$/i.test(fileUrl.value); + if (state) { + options.value.xls = /\.(xls)$/i.test(fileUrl.value); + } + return state; +}); + +const isZipOrRar = computed(() => { + return /\.(zip|rar)$/i.test(fileUrl.value); +}); + +const isSupported = computed(() => { + return isImage.value || isPdf.value || isDoc.value || isXls.value || isZipOrRar.value; +}); + +// 鍔ㄦ�佸垱寤篴鏍囩骞惰烦杞瑙圥DF +const previewPdf = (url) => { + // 鍒涘缓a鏍囩 + const link = document.createElement('a'); + // 璁剧疆PDF鏂囦欢URL + link.href = url; + // 鍦ㄦ柊鏍囩椤垫墦寮� + link.target = '_blank'; + // 瀹夊叏灞炴�э紝闃叉鏂伴〉闈㈣闂師椤甸潰 + link.rel = 'noopener noreferrer'; + // 鍙�夛細璁剧疆閾炬帴鏂囨湰 + link.textContent = '棰勮PDF'; + // 灏哸鏍囩娣诲姞鍒伴〉闈紙閮ㄥ垎娴忚鍣ㄨ姹傚繀椤诲湪DOM涓級 + document.body.appendChild(link); + // 瑙﹀彂鐐瑰嚮浜嬩欢 + link.click(); + // 绉婚櫎a鏍囩锛屾竻鐞咲OM + document.body.removeChild(link); +}; + + +// 鐩戝惉PDF鐘舵�佸彉鍖栵紝鑷姩瑙﹀彂璺宠浆 +watch( + () => isPdf.value, + (newVal) => { + + // 褰撶‘璁ゆ槸PDF涓旀枃浠禪RL鏈夋晥鏃� + if (newVal && fileUrl.value) { + // 鍏抽棴瀵硅瘽妗� + dialogVisible.value = false; + // 鍔犱釜灏忓欢杩熺‘淇濈姸鎬佹洿鏂板畬鎴� + setTimeout(() => { + previewPdf(fileUrl.value); + fileUrl.value = ''; + }, 100); + } + } +); + +// 鏂规硶瀹氫箟 +const renderedHandler = () => { + console.log("娓叉煋瀹屾垚"); + isDocShow.value = true; + resetStyle(); +}; + +const errorHandler = () => { + console.log("娓叉煋澶辫触"); + isDocShow.value = false; +}; + +const open = (url) => { + fileUrl.value = javaApi + url; + dialogVisible.value = true; +}; +const handleClose = () => { + dialogVisible.value = false; +}; + +const resetStyle = () => { + const elements = document.querySelectorAll('[style*="pt"]'); + for (const element of elements) { + const style = element.getAttribute('style'); + if (style) { + element.setAttribute('style', style.replace(/pt/g, 'px')); + } + } +}; + +// 鏆撮湶open鏂规硶渚涘閮ㄨ皟鐢� +defineExpose({ + open +}) +</script> + +<style scoped> +img { + max-width: 100%; + display: block; + margin: 0 auto; +} + +.oneLine { + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; +} +</style> diff --git a/src/main.js b/src/main.js index 48ee15c..014954c 100644 --- a/src/main.js +++ b/src/main.js @@ -1,68 +1,81 @@ -import { createApp } from 'vue' +import { createApp } from "vue"; -import Cookies from 'js-cookie' +import Cookies from "js-cookie"; -import ElementPlus from 'element-plus' -import 'element-plus/dist/index.css' -import 'element-plus/theme-chalk/dark/css-vars.css' -import locale from 'element-plus/es/locale/lang/zh-cn' +import ElementPlus from "element-plus"; +import "element-plus/dist/index.css"; +import "element-plus/theme-chalk/dark/css-vars.css"; +import locale from "element-plus/es/locale/lang/zh-cn"; -import '@/assets/styles/index.scss' // global css +import "@/assets/styles/index.scss"; // global css -import App from './App' -import store from './store' -import router from './router' -import directive from './directive' // directive +import App from "./App"; +import store from "./store"; +import router from "./router"; +import directive from "./directive"; // directive // 娉ㄥ唽鎸囦护 -import plugins from './plugins' // plugins -import { download } from '@/utils/request' +import plugins from "./plugins"; // plugins +import { download } from "@/utils/request"; // svg鍥炬爣 -import 'virtual:svg-icons-register' -import SvgIcon from '@/components/SvgIcon' -import elementIcons from '@/components/SvgIcon/svgicon' +import "virtual:svg-icons-register"; +import SvgIcon from "@/components/SvgIcon"; +import elementIcons from "@/components/SvgIcon/svgicon"; -import './permission' // permission control +import "./permission"; // permission control -import { useDict } from '@/utils/dict' -import { parseTime, resetForm, addDateRange, handleTree, selectDictLabel, selectDictLabels } from '@/utils/ruoyi' +import { useDict } from "@/utils/dict"; +import { + parseTime, + resetForm, + addDateRange, + handleTree, + selectDictLabel, + selectDictLabels, +} from "@/utils/ruoyi"; // 鍒嗛〉缁勪欢 -import Pagination from '@/components/Pagination' +import Pagination from "@/components/Pagination"; // 鑷畾涔夎〃鏍煎伐鍏风粍浠� -import RightToolbar from '@/components/RightToolbar' +import RightToolbar from "@/components/RightToolbar"; // 瀵屾枃鏈粍浠� -import Editor from "@/components/Editor" +import Editor from "@/components/Editor"; // 鏂囦欢涓婁紶缁勪欢 -import FileUpload from "@/components/FileUpload" +import FileUpload from "@/components/FileUpload"; // 鍥剧墖涓婁紶缁勪欢 -import ImageUpload from "@/components/ImageUpload" +import ImageUpload from "@/components/ImageUpload"; // 鍥剧墖棰勮缁勪欢 -import ImagePreview from "@/components/ImagePreview" +import ImagePreview from "@/components/ImagePreview"; // 瀛楀吀鏍囩缁勪欢 -import DictTag from '@/components/DictTag' +import DictTag from "@/components/DictTag"; // 琛ㄦ牸缁勪欢 import PIMTable from "@/components/PIMTable/PIMTable.vue"; import { getToken } from "@/utils/auth"; -import {calculateTaxExclusiveTotalPrice, summarizeTable,calculateTaxIncludeTotalPrice} from "@/utils/summarizeTable.js"; +import { + calculateTaxExclusiveTotalPrice, + summarizeTable, + calculateTaxIncludeTotalPrice, +} from "@/utils/summarizeTable.js"; -const app = createApp(App) +const app = createApp(App); // 鍏ㄥ眬鏂规硶鎸傝浇 -app.config.globalProperties.useDict = useDict -app.config.globalProperties.download = download -app.config.globalProperties.parseTime = parseTime -app.config.globalProperties.resetForm = resetForm -app.config.globalProperties.summarizeTable = summarizeTable -app.config.globalProperties.calculateTaxExclusiveTotalPrice = calculateTaxExclusiveTotalPrice -app.config.globalProperties.calculateTaxIncludeTotalPrice = calculateTaxIncludeTotalPrice -app.config.globalProperties.handleTree = handleTree -app.config.globalProperties.addDateRange = addDateRange -app.config.globalProperties.selectDictLabel = selectDictLabel -app.config.globalProperties.selectDictLabels = selectDictLabels -app.config.globalProperties.javaApi = 'http://114.132.189.42:8078' +app.config.globalProperties.useDict = useDict; +app.config.globalProperties.download = download; +app.config.globalProperties.parseTime = parseTime; +app.config.globalProperties.resetForm = resetForm; +app.config.globalProperties.summarizeTable = summarizeTable; +app.config.globalProperties.calculateTaxExclusiveTotalPrice = + calculateTaxExclusiveTotalPrice; +app.config.globalProperties.calculateTaxIncludeTotalPrice = + calculateTaxIncludeTotalPrice; +app.config.globalProperties.handleTree = handleTree; +app.config.globalProperties.addDateRange = addDateRange; +app.config.globalProperties.selectDictLabel = selectDictLabel; +app.config.globalProperties.selectDictLabels = selectDictLabels; +app.config.globalProperties.javaApi = "http://114.132.189.42:8099"; app.config.globalProperties.HaveJson = (val) => { return JSON.parse(JSON.stringify(val)); }; @@ -71,29 +84,29 @@ }; // 鍏ㄥ眬缁勪欢鎸傝浇 -app.component('DictTag', DictTag) -app.component('Pagination', Pagination) -app.component('FileUpload', FileUpload) -app.component('ImageUpload', ImageUpload) -app.component('ImagePreview', ImagePreview) -app.component('RightToolbar', RightToolbar) -app.component('Editor', Editor) -app.component('PIMTable', PIMTable) +app.component("DictTag", DictTag); +app.component("Pagination", Pagination); +app.component("FileUpload", FileUpload); +app.component("ImageUpload", ImageUpload); +app.component("ImagePreview", ImagePreview); +app.component("RightToolbar", RightToolbar); +app.component("Editor", Editor); +app.component("PIMTable", PIMTable); -app.use(router) -app.use(store) -app.use(plugins) -app.use(elementIcons) -app.component('svg-icon', SvgIcon) +app.use(router); +app.use(store); +app.use(plugins); +app.use(elementIcons); +app.component("svg-icon", SvgIcon); -directive(app) +directive(app); // 浣跨敤element-plus 骞朵笖璁剧疆鍏ㄥ眬鐨勫ぇ灏� app.use(ElementPlus, { locale: locale, // 鏀寔 large銆乨efault銆乻mall - size: Cookies.get('size') || 'default' -}) -app._context.components.ElDialog.props.closeOnClickModal.default = false + size: Cookies.get("size") || "default", +}); +app._context.components.ElDialog.props.closeOnClickModal.default = false; -app.mount('#app') +app.mount("#app"); diff --git a/src/views/salesManagement/salesLedger/fileList.vue b/src/views/salesManagement/salesLedger/fileList.vue index d1c0cdd..da37db2 100644 --- a/src/views/salesManagement/salesLedger/fileList.vue +++ b/src/views/salesManagement/salesLedger/fileList.vue @@ -1,22 +1,26 @@ <template> - <el-dialog v-model="dialogVisible" title="闄勪欢" width="30%" :before-close="handleClose"> + <el-dialog v-model="dialogVisible" title="闄勪欢" width="40%" :before-close="handleClose"> <el-table :data="tableData" border height="40vh"> <el-table-column label="闄勪欢鍚嶇О" prop="name" min-width="400" show-overflow-tooltip /> <el-table-column fixed="right" label="鎿嶄綔" width="100" align="center"> <template #default="scope"> <el-button link type="primary" size="small" @click="downLoadFile(scope.row)">涓嬭浇</el-button> + <el-button link type="primary" size="small" @click="lookFile(scope.row)">棰勮</el-button> </template> </el-table-column> </el-table> </el-dialog> + <filePreview ref="filePreviewRef" /> </template> <script setup> import { ref } from 'vue' +import filePreview from '@/components/filePreview/index.vue' const dialogVisible = ref(false) const tableData = ref([]) const { proxy } = getCurrentInstance(); +const filePreviewRef = ref() const handleClose = () => { dialogVisible.value = false } @@ -28,6 +32,9 @@ proxy.$download.name(row.url); } +const lookFile = (row) => { + filePreviewRef.value.open(row.url) +} defineExpose({ open }) -- Gitblit v1.9.3