整合前端
BIN
ruoyi-ui/src/components/Folder/icons/code.png
Normal file
|
After Width: | Height: | Size: 3.8 KiB |
BIN
ruoyi-ui/src/components/Folder/icons/config.png
Normal file
|
After Width: | Height: | Size: 3.9 KiB |
BIN
ruoyi-ui/src/components/Folder/icons/database.png
Normal file
|
After Width: | Height: | Size: 4.4 KiB |
BIN
ruoyi-ui/src/components/Folder/icons/email.png
Normal file
|
After Width: | Height: | Size: 2.9 KiB |
BIN
ruoyi-ui/src/components/Folder/icons/excel.png
Normal file
|
After Width: | Height: | Size: 3.1 KiB |
BIN
ruoyi-ui/src/components/Folder/icons/exe.png
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
BIN
ruoyi-ui/src/components/Folder/icons/folder.png
Normal file
|
After Width: | Height: | Size: 1.7 KiB |
BIN
ruoyi-ui/src/components/Folder/icons/gif.png
Normal file
|
After Width: | Height: | Size: 3.9 KiB |
BIN
ruoyi-ui/src/components/Folder/icons/link.png
Normal file
|
After Width: | Height: | Size: 4.8 KiB |
BIN
ruoyi-ui/src/components/Folder/icons/music.png
Normal file
|
After Width: | Height: | Size: 3.2 KiB |
BIN
ruoyi-ui/src/components/Folder/icons/obj.png
Normal file
|
After Width: | Height: | Size: 3.9 KiB |
BIN
ruoyi-ui/src/components/Folder/icons/pdf.png
Normal file
|
After Width: | Height: | Size: 4.2 KiB |
BIN
ruoyi-ui/src/components/Folder/icons/ppt.png
Normal file
|
After Width: | Height: | Size: 3.3 KiB |
BIN
ruoyi-ui/src/components/Folder/icons/question.png
Normal file
|
After Width: | Height: | Size: 4.2 KiB |
BIN
ruoyi-ui/src/components/Folder/icons/tupian.png
Normal file
|
After Width: | Height: | Size: 3.3 KiB |
BIN
ruoyi-ui/src/components/Folder/icons/txt.png
Normal file
|
After Width: | Height: | Size: 2.9 KiB |
BIN
ruoyi-ui/src/components/Folder/icons/vedio.png
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
BIN
ruoyi-ui/src/components/Folder/icons/word.png
Normal file
|
After Width: | Height: | Size: 3.2 KiB |
BIN
ruoyi-ui/src/components/Folder/icons/wps.png
Normal file
|
After Width: | Height: | Size: 4.6 KiB |
BIN
ruoyi-ui/src/components/Folder/icons/yasuobao.png
Normal file
|
After Width: | Height: | Size: 3.4 KiB |
25
ruoyi-ui/src/components/Folder/index.js
Normal file
@@ -0,0 +1,25 @@
|
||||
import Folder from './index.vue'
|
||||
|
||||
export default {
|
||||
install (Vue) {
|
||||
// 1. 全局注册组件(必须确保页面中仅挂载一次)
|
||||
Vue.component(Folder.name, Folder)
|
||||
|
||||
// 2. 全局挂载方法:通过 $root.$refs 找到组件实例
|
||||
Vue.prototype.$folder = {
|
||||
openDialog () {
|
||||
// 关键:通过根实例的 refs 定位 Folder 组件(需在页面给组件加 ref="folder")
|
||||
const folderInstance = Vue.prototype.$root.$refs.folder
|
||||
if (folderInstance) {
|
||||
folderInstance.openDialog()
|
||||
} else {
|
||||
console.error('未找到 Folder 组件实例,请给组件添加 ref="folder"')
|
||||
}
|
||||
},
|
||||
closeDialog () {
|
||||
const folderInstance = Vue.prototype.$root.$refs.folder
|
||||
folderInstance?.closeDialog()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
236
ruoyi-ui/src/components/Folder/index.vue
Normal file
@@ -0,0 +1,236 @@
|
||||
<template>
|
||||
<el-dialog title="文件夹" :visible.sync="dialogVisible" append-to-body width="90vw" height="80vh">
|
||||
<template slot="title">
|
||||
<div class="dialog-header" style="display: flex; align-items: center; justify-content: center; gap: 10px;">
|
||||
<span>文件预览</span>
|
||||
<el-popover placement="top" width="200px" trigger="hover">
|
||||
<template slot="reference">
|
||||
<el-tag type="warning">提示</el-tag>
|
||||
</template>
|
||||
<div class="popover-content">
|
||||
<p>1. 在预览区域按住shift可以横向左右滚动</p>
|
||||
<p>2. xlsx与xls格式预览效果不同</p>
|
||||
<p>3. 预览效果仅供参考,不代表实际内容</p>
|
||||
</div>
|
||||
</el-popover>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 左侧文件列表 -->
|
||||
<div class="file-dialog-container">
|
||||
<div class="file-list" v-if="!simplePreview">
|
||||
<div v-for="(file, index) in files" :key="file.ossId"
|
||||
:class="{ 'selected-file': selectedFile.ossId === file.ossId }" @click="handleFileClick(file)">
|
||||
<div class="file-info">
|
||||
<div class="file-name">{{ file.originalName }}</div>
|
||||
</div>
|
||||
<div class="file-meta">
|
||||
<div>类型: {{ file.fileSuffix.slice(1) }}</div>
|
||||
<div>创建人: {{ file.createBy }}</div>
|
||||
<div>时间: {{ file.createTime }}</div>
|
||||
</div>
|
||||
<el-button type="text" size="mini" @click.stop="handleDownload(file)">下载</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 右侧预览区域 -->
|
||||
<div class="file-preview">
|
||||
<div v-if="!selectedFile" class="preview-placeholder">
|
||||
<div class="placeholder-text">请选择文件进行预览</div>
|
||||
</div>
|
||||
<!-- 图片预览 -->
|
||||
<div
|
||||
v-else-if="selectedFile.fileSuffix === '.png' || selectedFile.fileSuffix === '.jpg' || selectedFile.fileSuffix === '.jpeg' || selectedFile.fileSuffix === '.webp'">
|
||||
<img :src="selectedFile.url" alt="图片预览" class="preview-img">
|
||||
</div>
|
||||
<!-- DOCX预览(基于vue-office) -->
|
||||
<div v-else-if="selectedFile.fileSuffix === '.docx'">
|
||||
<vue-office-docx :src="selectedFile.url" @render-error="handleDocxError" />
|
||||
</div>
|
||||
<!-- XLSX预览 -->
|
||||
<div style="width: 100%; height: 100%;" v-else-if="selectedFile.fileSuffix === '.xlsx'">
|
||||
<vue-office-excel :src="selectedFile.url" />
|
||||
</div>
|
||||
<!-- XLS预览 -->
|
||||
<div style="width: 100%; height: 100%;" v-else-if="selectedFile.fileSuffix === '.xls'">
|
||||
<XLSXPreview :src="selectedFile.url" />
|
||||
</div>
|
||||
<iframe v-else-if="selectedFile.fileSuffix === '.pdf'" :src="selectedFile.url" class="image-preview"></iframe>
|
||||
<!-- 其他格式提示 -->
|
||||
<div v-else class="other-file-tip">
|
||||
<el-icon class="tip-icon">
|
||||
<Warning />
|
||||
</el-icon>
|
||||
<div>暂不支持该格式预览,后续更新会逐渐支持,请点击
|
||||
<span style="color: #409eff; cursor: pointer;" @click="handleDownload(selectedFile)">下载</span>
|
||||
查看
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// 引入vue-office相关组件(需先安装依赖)
|
||||
import VueOfficeDocx from '@vue-office/docx';
|
||||
import VueOfficeExcel from '@vue-office/excel';
|
||||
import XLSXPreview from './preview/xlsx/index.vue';
|
||||
|
||||
import '@vue-office/docx/lib/index.css';
|
||||
import '@vue-office/excel/lib/index.css';
|
||||
|
||||
export default {
|
||||
name: "Folder",
|
||||
components: {
|
||||
VueOfficeDocx, // 注册docx预览组件
|
||||
VueOfficeExcel, // 注册excel预览组件
|
||||
XLSXPreview, // 注册excel预览组件, 因为vue-office不支持xls文件
|
||||
// VueOfficePdf // 注册pdf预览组件
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
dialogVisible: false,
|
||||
files: [],
|
||||
selectedFile: null, // 当前选中文件
|
||||
simplePreview: false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
openDialog () {
|
||||
this.simplePreview = false
|
||||
this.dialogVisible = true
|
||||
},
|
||||
closeDialog () {
|
||||
this.dialogVisible = false
|
||||
this.selectedFile = null // 关闭时清空选中状态
|
||||
},
|
||||
setFiles (files) {
|
||||
this.files = files
|
||||
// 默认选中第一个文件(可选)
|
||||
if (files.length > 0) {
|
||||
this.selectedFile = files[0]
|
||||
}
|
||||
},
|
||||
// 选中文件
|
||||
handleFileClick (file) {
|
||||
console.log(file, '切换文件')
|
||||
this.selectedFile = file
|
||||
},
|
||||
// 下载文件
|
||||
handleDownload (file) {
|
||||
// window.open(file.url, '_blank')
|
||||
this.$download.oss(file.ossId);
|
||||
},
|
||||
// docx预览错误处理
|
||||
handleDocxError (err) {
|
||||
console.error('docx预览失败:', err)
|
||||
this.$message.error('文档预览失败,请下载查看')
|
||||
},
|
||||
// excel预览错误处理
|
||||
handleExcelError (err) {
|
||||
console.error('excel预览失败:', err)
|
||||
this.$message.error('excel预览失败,请下载查看')
|
||||
},
|
||||
// pdf预览错误处理
|
||||
handlePdfError (err) {
|
||||
console.error('pdf预览失败:', err)
|
||||
this.$message.error('pdf预览失败,请下载查看')
|
||||
},
|
||||
previewSimple (file) {
|
||||
this.simplePreview = true
|
||||
this.dialogVisible = true
|
||||
this.selectedFile = file
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.file-dialog-container {
|
||||
display: flex;
|
||||
height: calc(80vh - 60px);
|
||||
gap: 20px;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.file-list {
|
||||
width: 320px;
|
||||
overflow-y: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.selected-file {
|
||||
border: 2px solid #1989fa;
|
||||
}
|
||||
|
||||
.file-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.file-name {
|
||||
font-weight: 500;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.file-meta {
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
line-height: 1.4;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.file-preview {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
border: 1px solid #e6e6e6;
|
||||
}
|
||||
|
||||
.preview-placeholder,
|
||||
.other-file-tip {
|
||||
text-align: center;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.placeholder-icon,
|
||||
.tip-icon {
|
||||
font-size: 48px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.image-preview {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.preview-img {
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
object-fit: contain;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.docx-preview {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.docx-content {
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
background: #fff;
|
||||
min-height: 100%;
|
||||
}
|
||||
</style>
|
||||
238
ruoyi-ui/src/components/Folder/preview/xlsx/index.vue
Normal file
@@ -0,0 +1,238 @@
|
||||
<template>
|
||||
<div class="xlsx-preview">
|
||||
<!-- 加载状态 -->
|
||||
<div v-if="loading" class="loading">
|
||||
<div class="spinner"></div>
|
||||
<p>正在加载Excel文件...</p>
|
||||
</div>
|
||||
|
||||
<!-- 错误信息 -->
|
||||
<div v-if="error" class="error">{{ error }}</div>
|
||||
|
||||
<!-- Excel内容展示 -->
|
||||
<div v-if="workbook" class="workbook-container">
|
||||
<!-- 工作表标签 -->
|
||||
<div class="sheet-tabs">
|
||||
<button v-for="(sheet, index) in workbook.SheetNames" :key="index"
|
||||
:class="{ active: activeSheetIndex === index }" @click="activeSheetIndex = index">
|
||||
{{ sheet }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- 表格内容区域 -->
|
||||
<div class="sheet-content">
|
||||
<table v-if="activeSheetData">
|
||||
<tbody>
|
||||
<tr v-for="(row, rowIndex) in activeSheetData" :key="rowIndex">
|
||||
<td v-for="(cell, colIndex) in row" :key="colIndex" :class="{
|
||||
'header-cell': rowIndex === 0,
|
||||
'odd-row': rowIndex % 2 === 1
|
||||
}">
|
||||
{{ cell || '' }}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import * as XLSX from 'xlsx';
|
||||
|
||||
export default {
|
||||
name: "XlsxPreview",
|
||||
props: {
|
||||
src: {
|
||||
type: String,
|
||||
required: true,
|
||||
description: "Excel文件的URL或路径"
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
workbook: null, // 解析后的Excel工作簿
|
||||
activeSheetIndex: 0, // 当前激活的工作表索引
|
||||
loading: false, // 加载状态
|
||||
error: null // 错误信息
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
src: {
|
||||
immediate: true,
|
||||
handler () {
|
||||
this.loadAndParseExcel();
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 获取当前激活的工作表数据
|
||||
activeSheetData () {
|
||||
if (!this.workbook || !this.workbook.SheetNames.length) return null;
|
||||
|
||||
const sheetName = this.workbook.SheetNames[this.activeSheetIndex];
|
||||
const worksheet = this.workbook.Sheets[sheetName];
|
||||
|
||||
// 将工作表转换为二维数组
|
||||
return XLSX.utils.sheet_to_json(worksheet, { header: 1 });
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* 加载并解析Excel文件
|
||||
*/
|
||||
async loadAndParseExcel () {
|
||||
this.loading = true;
|
||||
this.error = null;
|
||||
this.workbook = null;
|
||||
|
||||
try {
|
||||
// 验证URL格式
|
||||
if (!this.src || (this.src.startsWith('http') && !this.isValidUrl(this.src))) {
|
||||
throw new Error('无效的文件路径');
|
||||
}
|
||||
|
||||
// 加载文件
|
||||
const response = await fetch(this.src);
|
||||
if (!response.ok) {
|
||||
throw new Error(`加载失败: ${response.status} ${response.statusText}`);
|
||||
}
|
||||
|
||||
// 转换为ArrayBuffer
|
||||
const arrayBuffer = await response.arrayBuffer();
|
||||
|
||||
// 解析Excel
|
||||
this.workbook = XLSX.read(arrayBuffer, {
|
||||
type: 'array',
|
||||
cellDates: true, // 解析日期类型
|
||||
cellText: false // 保持单元格原始类型
|
||||
});
|
||||
|
||||
// 重置到第一个工作表
|
||||
this.activeSheetIndex = 0;
|
||||
} catch (err) {
|
||||
this.error = err.message;
|
||||
console.error('Excel处理错误:', err);
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 验证URL格式
|
||||
*/
|
||||
isValidUrl (url) {
|
||||
try {
|
||||
new URL(url);
|
||||
return true;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.xlsx-preview {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
||||
border: 1px solid #e5e7eb;
|
||||
border-radius: 6px;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
/* 加载状态样式 */
|
||||
.loading {
|
||||
padding: 40px 20px;
|
||||
text-align: center;
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.spinner {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
margin: 0 auto 16px;
|
||||
border: 4px solid #e5e7eb;
|
||||
border-top-color: #3b82f6;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
/* 错误信息样式 */
|
||||
.error {
|
||||
padding: 20px;
|
||||
color: #dc2626;
|
||||
background-color: #fee2e2;
|
||||
border-bottom: 1px solid #fecaca;
|
||||
}
|
||||
|
||||
/* 工作表标签样式 */
|
||||
.sheet-tabs {
|
||||
display: flex;
|
||||
background-color: #f9fafb;
|
||||
border-bottom: 1px solid #e5e7eb;
|
||||
overflow-x: auto;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.sheet-tabs button {
|
||||
padding: 8px 16px;
|
||||
border: none;
|
||||
background: none;
|
||||
font-size: 14px;
|
||||
color: #4b5563;
|
||||
cursor: pointer;
|
||||
border-bottom: 2px solid transparent;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.sheet-tabs button:hover:not(.active) {
|
||||
background-color: #f3f4f6;
|
||||
color: #1f2937;
|
||||
}
|
||||
|
||||
.sheet-tabs button.active {
|
||||
border-bottom-color: #3b82f6;
|
||||
color: #3b82f6;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* 表格内容样式 */
|
||||
.sheet-content {
|
||||
overflow: auto;
|
||||
max-height: 600px;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
min-width: max-content;
|
||||
}
|
||||
|
||||
td {
|
||||
padding: 8px 12px;
|
||||
border: 1px solid #e5e7eb;
|
||||
min-width: 80px;
|
||||
font-size: 14px;
|
||||
color: #1f2937;
|
||||
}
|
||||
|
||||
.header-cell {
|
||||
background-color: #f3f4f6;
|
||||
font-weight: 500;
|
||||
color: #111827;
|
||||
}
|
||||
|
||||
.odd-row {
|
||||
background-color: #f9fafb;
|
||||
}
|
||||
</style>
|
||||