feat: 新增空格键快速预览docx和excel文件

This commit is contained in:
jmal 2024-08-01 16:04:22 +08:00
parent 48881a860f
commit 06cad76ce0
8 changed files with 164 additions and 45 deletions

View File

@ -15,6 +15,9 @@
"svgo": "svgo -f src/icons/svg --config=src/icons/svgo.yml" "svgo": "svgo -f src/icons/svg --config=src/icons/svgo.yml"
}, },
"dependencies": { "dependencies": {
"@vue-office/docx": "^1.6.2",
"@vue-office/excel": "^1.7.11",
"@vue/composition-api": "^1.7.2",
"ant-design-vue": "^1.6.5", "ant-design-vue": "^1.6.5",
"clipboard": "^2.0.6", "clipboard": "^2.0.6",
"cropperjs": "^1.5.6", "cropperjs": "^1.5.6",
@ -52,6 +55,7 @@
"vditor": "3.10.4", "vditor": "3.10.4",
"vue-aplayer": "^1.6.1", "vue-aplayer": "^1.6.1",
"vue-color": "^2.8.1", "vue-color": "^2.8.1",
"vue-demi": "^0.14.6",
"vue-runtime-helpers": "^1.1.2", "vue-runtime-helpers": "^1.1.2",
"vue-simple-uploader": "0.7.6", "vue-simple-uploader": "0.7.6",
"vuedraggable": "^2.23.2", "vuedraggable": "^2.23.2",

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -5,7 +5,7 @@
import { Icon } from 'ant-design-vue' import { Icon } from 'ant-design-vue'
const IconFont = Icon.createFromIconfontCN({ const IconFont = Icon.createFromIconfontCN({
scriptUrl: document.location.protocol + '/resource/font_2024484_jfax4gppvg.js' scriptUrl: document.location.protocol + '/resource/font_2024484_yd6mpe5qfy.js'
}) })
// 把此段代码贴到iconfont项目页面的控制台中, 以获取图标列表 // 把此段代码贴到iconfont项目页面的控制台中, 以获取图标列表

View File

@ -93,7 +93,7 @@
</div> </div>
<div v-else-if="(item.contentType && item.contentType.startsWith('image')) || item.showCover"> <div v-else-if="(item.contentType && item.contentType.startsWith('image')) || item.showCover">
<el-image v-if="grid || grid === 'details'" :style="{'height':details?'110px':(gridWidth-35) + 'px'}" <el-image v-if="grid || grid === 'details'" :style="{'height':details?'110px':(gridWidth-35) + 'px'}"
fit="contain" :src="item.fileId ? (imageUrl+item.fileId) : (imageUrl+item.id)"> fit="contain" :src="item.fileId ? (imageUrl+item.fileId) : (imageUrl+item.id)" :class="item.showCover ? 'cover' : ''">
<div slot="error" class="image-slot-error"> <div slot="error" class="image-slot-error">
<svg-icon icon-class="image"/> <svg-icon icon-class="image"/>
</div> </div>
@ -309,5 +309,8 @@ export default {
padding: 0 2px; padding: 0 2px;
line-height: 12px; line-height: 12px;
} }
>>> .el-image.cover {
box-shadow: 0 2px 12px 0 rgba(0,0,0,.1);
}
</style> </style>

View File

@ -603,7 +603,7 @@
:status.sync="iframePreviewVisible" :status.sync="iframePreviewVisible"
></iframe-preview> ></iframe-preview>
<file-details :audio-cover-url="audioCoverUrl" :image-url="imageUrl" :row-context-data="rowContextData" :visible.sync="drawer"></file-details> <file-details :audio-cover-url="audioCoverUrl" :image-url="imageUrl" :file="rowContextData" :visible.sync="drawer" @openFile="fileClick"></file-details>
<el-dialog <el-dialog
class="open-file-dialog" class="open-file-dialog"
@ -1159,7 +1159,7 @@ export default {
if (item.isFolder || !this.grid) { if (item.isFolder || !this.grid) {
return filename; return filename;
} }
const singleLine = item.contentType && item.contentType.startsWith('image') const singleLine = (item.contentType && item.contentType.startsWith('image')) || item.showCover
const gridFilenameLength = singleLine ? 14 : 32 const gridFilenameLength = singleLine ? 14 : 32
// 分离文件名和后缀 // 分离文件名和后缀
let parts = filename.split('.'); let parts = filename.split('.');
@ -1376,15 +1376,19 @@ export default {
}, },
// 获取字符的长度。中文字符长度为2,其他字符长度为1 // 获取字符的长度。中文字符长度为2,其他字符长度为1
getCharLength(char) { getCharLength(char) {
return this.isChineseChar(char) ? 2 : 1 //return this.isChineseChar(char) ? 2 : 1
// 简单判断字符长度:中文字符和全角字符长度为2,其他为1
if (this.isChineseChar(char)) {
return 2;
} else if (char === ' ') {
return 2; // 空格作为一个字符处理
} else {
return 1;
}
}, },
// 获取有效长度。如果有后缀,则包括后缀和点的长度;否则是基础名称和后7位的长度 // 获取有效长度。如果有后缀,则包括后缀和点的长度;否则是基础名称和后7位的长度
getEffectiveLength(base, suffix) { getEffectiveLength(base, suffix) {
let chineseLength = Array.from(base).reduce( let chineseLength = Array.from(base).reduce((count, char) => count + this.getCharLength(char), 0)
(count, char) => count + this.getCharLength(char),
0
)
return suffix ? chineseLength + suffix.length + 1 : chineseLength return suffix ? chineseLength + suffix.length + 1 : chineseLength
}, },
checkCmdKey(event) { checkCmdKey(event) {
@ -1397,9 +1401,13 @@ export default {
const isCmd = this.checkCmdKey(event) const isCmd = this.checkCmdKey(event)
const checkPreviewVisible = this.checkPreviewVisible() const checkPreviewVisible = this.checkPreviewVisible()
// space // space
if (keyCode === 32 && this.selectRowData.length > 0 && !this.drawer && !checkPreviewVisible) { if (keyCode === 32 && this.selectRowData.length > 0 && !checkPreviewVisible) {
this.drawer = true if (!this.drawer) {
this.drawerShowTime = Date.now() this.drawer = true
this.drawerShowTime = Date.now()
} else {
this.drawer = false
}
event.preventDefault() event.preventDefault()
event.stopPropagation() event.stopPropagation()
} }
@ -4206,6 +4214,7 @@ export default {
}, },
// 点击文件或文件夹 // 点击文件或文件夹
fileClick(row, event) { fileClick(row, event) {
this.drawer = false
if (this.queryFileType === 'trash') { if (this.queryFileType === 'trash') {
return return
} }
@ -4582,5 +4591,4 @@ export default {
.mt-5 { .mt-5 {
margin-top: 5px; margin-top: 5px;
} }
</style> </style>

View File

@ -1,16 +1,31 @@
<template> <template>
<div> <div>
<!--文件详细信息--> <el-dialog :visible.sync="visible" class="details-content" :custom-class="officePreview ? 'dialog-office':''">
<!-- <el-drawer :title="rowContextData.name" :visible.sync="visible">--> <div v-if="officePreview" class="office-preview">
<!-- --> <vue-office-docx
<!-- </el-drawer>--> v-if="file.suffix === 'docx' && visible"
<el-dialog :visible.sync="visible" width="420px"> :src="fleUrl"
<div class="drawer-icon"> @rendered="rendered"
/>
<vue-office-excel
v-if="file.suffix === 'xlsx' && visible"
:options="excelOptions"
:src="fleUrl"
@rendered="rendered"
/>
</div>
<div v-else class="drawer-icon">
<el-image v-if="file && file.showCover" fit="contain" :src="imageUrl + file.id + '&showCover=true'" class="cover">
<div slot="error" class="image-slot-error">
<svg-icon icon-class="image"/>
</div>
</el-image>
<icon-file <icon-file
v-else
class="drawer-icon-font" class="drawer-icon-font"
:grid="true" :grid="true"
:details="true" :details="true"
:item="rowContextData" :item="file"
:image-url="imageUrl" :image-url="imageUrl"
:audio-cover-url="audioCoverUrl" :audio-cover-url="audioCoverUrl"
></icon-file> ></icon-file>
@ -26,7 +41,7 @@
</p> </p>
<div style="text-align: right; margin: 0"> <div style="text-align: right; margin: 0">
<el-button size="mini" type="text" @click="cancelScan()">取消</el-button> <el-button size="mini" type="text" @click="cancelScan()">取消</el-button>
<el-button type="primary" size="mini" @click="scanDirectory(rowContextData)">确定</el-button> <el-button type="primary" size="mini" @click="scanDirectory(file)">确定</el-button>
</div> </div>
<el-button slot="reference" id="scanDirectoryBtn-file" type="primary" size="small" :loading="syncLoading"><i class="el-icon-refresh"></i></el-button> <el-button slot="reference" id="scanDirectoryBtn-file" type="primary" size="small" :loading="syncLoading"><i class="el-icon-refresh"></i></el-button>
</el-popover> </el-popover>
@ -36,57 +51,68 @@
<el-scrollbar wrap-class="scrollbar-wrapper" class="details-form-list"> <el-scrollbar wrap-class="scrollbar-wrapper" class="details-form-list">
<el-form-item label="名称:"> <el-form-item label="名称:">
<span>{{ rowContextData.name }}</span> <span>{{ file.name }}</span>
</el-form-item> </el-form-item>
<el-form-item label="类型:"> <el-form-item label="类型:">
<span>{{ <span>{{
rowContextData.isFolder ? '文件夹' : rowContextData.contentType file.isFolder ? '文件夹' : file.contentType
}}</span> }}</span>
</el-form-item> </el-form-item>
<div v-if="rowContextData.music"> <div v-if="file.music">
<el-form-item label="🎵 歌手:"> <el-form-item label="🎵 歌手:">
<span>{{ rowContextData.music.singer }}</span> <span>{{ file.music.singer }}</span>
</el-form-item> </el-form-item>
<el-form-item label="🎵 专辑:"> <el-form-item label="🎵 专辑:">
<span>{{ '《' + rowContextData.music.album + '》' }}</span> <span>{{ '《' + file.music.album + '》' }}</span>
</el-form-item> </el-form-item>
<el-form-item label="🎵 歌名:"> <el-form-item label="🎵 歌名:">
<span>{{ '《' + rowContextData.music.songName + '》' }}</span> <span>{{ '《' + file.music.songName + '》' }}</span>
</el-form-item> </el-form-item>
</div> </div>
<el-form-item <el-form-item
v-show="rowContextData.w && rowContextData.h" v-show="file.w && file.h"
label="尺寸:" label="尺寸:"
class="details-resolution" class="details-resolution"
> >
<span>{{ rowContextData.w + ' x ' + rowContextData.h }}</span> <span>{{ file.w + ' x ' + file.h }}</span>
</el-form-item> </el-form-item>
<el-form-item label="大小:"> <el-form-item label="大小:">
<span> {{ formatSize(rowContextData.size) }}</span> <span> {{ formatSize(file.size) }}</span>
</el-form-item> </el-form-item>
<el-form-item label="位置:" class="details-position"> <el-form-item label="位置:" class="details-position">
<a :href="'/?path=' + rowContextData.path + '&highlight=' + rowContextData.name">{{ rowContextData.path }}</a> <a :href="'/?path=' + file.path + '&highlight=' + file.name">{{ file.path }}</a>
</el-form-item> </el-form-item>
<el-form-item label="创建时间:"> <el-form-item label="创建时间:">
<span>{{ rowContextData.uploadDate }}</span> <span>{{ file.uploadDate }}</span>
</el-form-item> </el-form-item>
<el-form-item label="修改时间:"> <el-form-item label="修改时间:">
<span>{{ rowContextData.updateDate }}</span> <span>{{ file.updateDate }}</span>
</el-form-item> </el-form-item>
<el-form-item v-if="rowContextData.exif" label=""> <el-form-item v-if="file.exif" label="">
<span style="white-space: break-spaces;">{{ formatExif(rowContextData.exif) }}</span> <span style="white-space: break-spaces;">{{ formatExif(file.exif) }}</span>
</el-form-item> </el-form-item>
<el-form-item v-if="rowContextData.video" label=""> <el-form-item v-if="file.video" label="">
<span style="white-space: break-spaces;">{{ formatVideo(rowContextData.video) }}</span> <span style="white-space: break-spaces;">{{ formatVideo(file.video) }}</span>
</el-form-item> </el-form-item>
</el-scrollbar> </el-scrollbar>
<el-button v-if="allowOpenFile" type="primary" size="small" @click="openFile" class="open-file">打开文件</el-button>
</el-form> </el-form>
</el-dialog> </el-dialog>
</div> </div>
</template> </template>
<script> <script>
// 引入VueOfficeDocx 组件
import VueOfficeDocx from '@vue-office/docx'
// 引入VueOfficeDocx 相关样式
import '@vue-office/docx/lib/index.css'
// 引入VueOfficeExcel 组件
import VueOfficeExcel from '@vue-office/excel'
// 引入VueOfficeExcel 相关样式
import '@vue-office/excel/lib/index.css'
import fileConfig from '@/utils/file-config'
import { formatSize } from '@/utils/number' import { formatSize } from '@/utils/number'
import IconFile from '@/components/Icon/IconFile' import IconFile from '@/components/Icon/IconFile'
import {mapState} from "vuex"; import {mapState} from "vuex";
@ -96,14 +122,16 @@ import settingApi from "@/api/setting-api";
export default { export default {
name: 'FileDetails', name: 'FileDetails',
components: { components: {
IconFile IconFile,
VueOfficeDocx,
VueOfficeExcel
}, },
props: { props: {
visible: { visible: {
type: Boolean, type: Boolean,
required: true, required: true,
}, },
rowContextData: { file: {
type: Object, type: Object,
required: true, required: true,
}, },
@ -122,12 +150,28 @@ export default {
clickSync: false, clickSync: false,
indexingPercent: 100, indexingPercent: 100,
syncPercent: 100, syncPercent: 100,
excelOptions: {
minColLength: 10,
minRowLength: 10,
widthOffset: 10,
heightOffset: 10,
excel: window.location.origin + fileConfig.previewUrl(this.$store.state.user.name, this.file, this.$store.getters.token)
}
} }
}, },
mounted() { mounted() {
}, },
computed: { computed: {
...mapState(['message']), ...mapState(['message']),
fleUrl() {
return window.location.origin + fileConfig.previewUrl(this.$store.state.user.name, this.file, this.$store.getters.token)
},
officePreview() {
return ['docx', 'xlsx'].includes(this.file.suffix) && this.file.size > 0 && this.file.size < 1024 * 1024 * 5
},
allowOpenFile() {
return (this.file.contentType.indexOf('office') > -1 || ['pdf', 'epub'].includes(this.file.suffix))
}
}, },
watch: { watch: {
message(msg) { message(msg) {
@ -143,6 +187,12 @@ export default {
} }
}, },
methods: { methods: {
openFile() {
this.$emit('openFile', this.file)
},
rendered() {
console.log('rendered')
},
formatSize(size) { formatSize(size) {
return formatSize(size) return formatSize(size)
}, },
@ -204,7 +254,8 @@ export default {
} }
.details-form { .details-form {
padding: 20px 0; padding-bottom: 20px;
width: 280px;
>>> .el-scrollbar__view { >>> .el-scrollbar__view {
max-height: 50vh; max-height: 50vh;
@ -254,8 +305,14 @@ export default {
} }
.drawer-icon { .drawer-icon {
margin-right: 20px;
text-align: center; text-align: center;
position: relative; position: relative;
flex: 1;
display: flex;
align-content: center;
flex-wrap: wrap;
justify-content: center;
>>> .icon-favorite { >>> .icon-favorite {
display: none; display: none;
} }
@ -267,7 +324,52 @@ export default {
} }
} }
.drawer-icon-font >>> .svg-icon { .drawer-icon-font {
font-size: 8rem; margin-bottom: 40px;
>>> .svg-icon {
font-size: 8rem;
}
}
.details-content {
display: flex;
align-items: center;
justify-content: center;
>>> .el-dialog__body {
display: flex;
}
}
>>> .el-image {
height: 200px !important;
}
>>> .el-image.cover {
height: unset !important;
margin-bottom: 20px;
}
>>> .el-dialog {
margin-top: 0 !important;
width: fit-content;
min-width: 500px;
max-width: 800px;
}
>>> .dialog-office {
max-width: 1400px;
min-width: 1225px;
height: 90vh;
}
.office-preview {
flex: 20;
height: 80vh;
margin-right: 20px;
>>> .docx-wrapper {
padding: 10px;
}
}
.open-file {
margin-top: 10px;
} }
</style> </style>

View File

@ -91,6 +91,8 @@ iconClass.set('plist','file-plist')
iconClass.set('iso','file-iso') iconClass.set('iso','file-iso')
iconClass.set('glb','file-glb') iconClass.set('glb','file-glb')
iconClass.set('gltf','file-glb') iconClass.set('gltf','file-glb')
iconClass.set('epub','epub')
iconClass.set('dwg','dwg')
export function findIconClass(file){ export function findIconClass(file){
if(!file){ if(!file){