整合前端

This commit is contained in:
砂糖
2026-04-13 17:04:38 +08:00
parent 69609a2cb1
commit 5d4794c9bd
915 changed files with 144259 additions and 0 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

View 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()
}
}
}
}

View 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>

View 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>