feat: 新增文件管理

This commit is contained in:
2024-04-12 22:10:30 +08:00
parent 9c9a29ae05
commit df14bd00dd
22 changed files with 1203 additions and 0 deletions

View File

@@ -0,0 +1,140 @@
<template>
<transition name="slide-dynamic-origin">
<div class="audio-box" ref="audioRef" :style="audioStyle" v-show="visible">
<section style="padding: 10px 14px 14px 14px">
<div class="audio-box__header" ref="audioHeadRef">
<div class="audio-name">
<icon-music :size="16" spin />
<span>{{ props.data?.name }}.{{ props.data?.extension }}</span>
</div>
<div class="close-icon" @click="close">
<icon-close :size="12" />
</div>
</div>
<!-- 音频组件 -->
<audio class="audio" :src="audioSrc" controls autoplay></audio>
</section>
</div>
</transition>
</template>
<script setup lang="ts">
import { useDraggable, useWindowSize, useElementSize } from '@vueuse/core'
import type { FileItem } from '@/apis'
interface Props {
data: FileItem
onClose: () => void
}
const props = withDefaults(defineProps<Props>(), {})
const visible = ref(false)
const audioRef = ref<HTMLElement | null>(null)
const audioHeadRef = ref<HTMLElement | null>(null)
const audioSrc = computed(() => {
return props.data?.url || ''
})
onMounted(() => {
visible.value = true
})
const { width: windowWidth, height: windowHeight } = useWindowSize()
const { width: boxWidth, height: boxHeight } = useElementSize(audioRef)
const axis = ref({ top: 40, left: windowWidth.value - boxWidth.value })
const obj = JSON.parse(sessionStorage.getItem('AudioDialogXY') as string)
if (obj && obj.top && obj.left) {
axis.value.top = obj.top
axis.value.left = obj.left
}
const { x, y } = useDraggable(audioRef, {
initialValue: { x: axis.value.left - boxWidth.value, y: axis.value.top }
})
const audioStyle = computed(() => {
let left: number | string = x.value
let top: number | string = y.value
if (x.value > windowWidth.value - boxWidth.value) {
left = windowWidth.value - boxWidth.value
}
if (x.value < 0) {
left = 0
}
if (y.value > windowHeight.value - boxHeight.value) {
top = windowHeight.value - boxHeight.value
}
if (y.value < 0) {
top = 0
}
sessionStorage.setItem('AudioDialogXY', JSON.stringify({ top, left }))
return {
left: left + 'px',
top: top + 'px'
}
})
const close = () => {
visible.value = false
props.onClose && props.onClose()
}
</script>
<style lang="scss" scoped>
.audio-box {
width: 300px;
position: fixed;
border-radius: 8px;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
background: linear-gradient(to right, $color-theme, rgb(var(--primary-2)));
z-index: 9999;
&__header {
color: #fff;
font-size: 16px;
margin-bottom: 10px;
display: flex;
justify-content: space-between;
align-items: center;
cursor: move;
user-select: none;
&:active {
cursor: move;
}
.audio-name {
display: flex;
align-items: center;
> span {
margin-left: 8px;
}
}
.close-icon {
width: 24px;
height: 24px;
display: flex;
justify-content: center;
align-items: center;
border-radius: 50%;
background: rgba(0, 0, 0, 0);
transition: all 0.2s;
cursor: pointer;
svg {
transition: all 0.2s;
}
&:hover {
background: rgba(0, 0, 0, 0.1);
svg {
transform: scale(1.3);
}
}
}
}
.audio {
width: 100%;
&::-webkit-media-controls-enclosure {
background: #fff;
}
}
}
</style>

View File

@@ -0,0 +1,45 @@
import type { Component } from 'vue'
import { createApp } from 'vue'
import ArcoVueIcon from '@arco-design/web-vue/es/icon'
import ArcoVue from '@arco-design/web-vue'
import type { FileItem } from '@/apis'
import ModalContent from './ModalContent.vue'
function createModal<T extends { callback?: () => void }>(component: Component, options?: T) {
// 创建一个挂载容器
const el: HTMLElement = document.createElement('div')
// 挂载组件
document.body.appendChild(el)
// 实例化组件, createApp 第二个参数是 props
const instance = createApp(component, {
...options,
onClose: () => {
setTimeout(() => {
instance.unmount()
document.body.removeChild(el)
options?.callback && options?.callback()
}, 350)
}
})
instance.use(ArcoVue)
instance.use(ArcoVueIcon)
instance.mount(el)
}
type TFileOptions = { data: FileItem; callback?: () => void }
/** 预览 音频文件 弹窗 */
let fileAudioId = ''
export function previewFileAudioModal(data: FileItem) {
if (fileAudioId) return // 防止重复打开
fileAudioId = data.id
return createModal<TFileOptions>(ModalContent, {
data: data,
// 关闭的回调
callback: () => {
fileAudioId = ''
}
})
}

View File

@@ -0,0 +1,48 @@
<template>
<a-row justify="center" align="center">
<div style="height: 100px">
<FileImage :data="data" style="border-radius: 5px" />
</div>
</a-row>
<a-row style="margin-top: 15px">
<a-descriptions :column="1" title="详细信息" layout="inline-vertical">
<a-descriptions-item :label="data.name">{{ formatFileSize(data.size) }}</a-descriptions-item>
<a-descriptions-item label="创建时间">{{ data.createTime }}</a-descriptions-item>
<a-descriptions-item label="修改时间">{{ data.updateTime }}</a-descriptions-item>
</a-descriptions>
</a-row>
</template>
<script setup lang="ts">
import type { FileItem } from '@/apis'
import FileImage from '../../main/FileMain/FileImage.vue'
import { formatFileSize } from '@/utils'
interface Props {
data: FileItem
}
withDefaults(defineProps<Props>(), {})
</script>
<style lang="less" scoped>
.label {
color: var(--color-text-2);
}
:deep(.arco-form-item) {
margin-bottom: 0;
}
:deep(.arco-form-item-label-col > label) {
white-space: nowrap;
}
:deep(.arco-descriptions-title) {
font-size: 14px;
}
:deep(.arco-descriptions-item-label-inline) {
font-size: 12px;
}
:deep(.arco-descriptions-item-value-inline) {
font-size: 12px;
margin-bottom: 10px;
}
</style>

View File

@@ -0,0 +1,17 @@
import type { FileItem } from '@/apis'
import { h } from 'vue'
import { Modal } from '@arco-design/web-vue'
import ModalContent from './ModalContent.vue'
/** 打开 详情 弹窗 */
export function openFileDetailModal(fileItem: FileItem) {
return Modal.open({
title: fileItem.extension ? `${fileItem.name}.${fileItem.extension}` : `${fileItem.name}`,
titleAlign: 'start',
modalAnimationName: 'el-fade',
modalStyle: { maxWidth: '320px' },
width: '90%',
footer: false,
content: () => h(ModalContent, { data: fileItem })
})
}

View File

@@ -0,0 +1,27 @@
<template>
<a-row justify="center" align="center" style="padding: 0 5%">
<a-form ref="formRef" :model="form" auto-label-width class="w-full">
<a-form-item
label="文件名称"
field="name"
:rules="[{ required: true, message: '请输入文件名称' }]"
style="margin-bottom: 0"
>
<a-input v-model="form.name" placeholder="请输入文件名称" allow-clear />
</a-form-item>
</a-form>
</a-row>
</template>
<script lang="ts" setup>
import type { FormInstance } from '@arco-design/web-vue'
const formRef = ref<FormInstance>()
const form = reactive({
name: ''
})
defineExpose({ formRef })
</script>
<style lang="scss" scoped></style>

View File

@@ -0,0 +1,27 @@
import { updateFile, type FileItem } from '@/apis'
import { ref, h } from 'vue'
import { Modal, Message } from '@arco-design/web-vue'
import ModalContent from './ModalContent.vue'
export function openFileRenameModal(data: FileItem) {
const ModalContentRef = ref<InstanceType<typeof ModalContent>>()
return Modal.open({
title: '重命名',
modalAnimationName: 'el-fade',
modalStyle: { maxWidth: '450px' },
width: '90%',
content: () =>
h(ModalContent, {
ref: (e) => {
ModalContentRef.value = e as any
}
}),
onBeforeOk: async () => {
const isInvalid = await ModalContentRef.value?.formRef?.validate()
if (isInvalid) return false
await updateFile({ name: data.name }, data.id)
Message.success('重命名成功')
return true
}
})
}

View File

@@ -0,0 +1,26 @@
<template>
<div id="videoId"></div>
</template>
<script lang="ts" setup>
import Player from 'xgplayer'
import type { FileItem } from '@/apis'
interface Props {
data: FileItem
}
const props = withDefaults(defineProps<Props>(), {})
onMounted(() => {
new Player({
id: 'videoId',
url: props.data?.url ?? '',
lang: 'zh-cn',
autoplay: true,
closeVideoClick: true,
videoInit: true
})
})
</script>
<style lang="scss" scoped></style>

View File

@@ -0,0 +1,13 @@
import { h } from 'vue'
import { Modal } from '@arco-design/web-vue'
import ModalContent from './ModalContent.vue'
import type { FileItem } from '@/apis'
export function previewFileVideoModal(data: FileItem) {
return Modal.open({
title: '视频播放',
width: 'auto',
modalStyle: {},
content: () => h(ModalContent, { data: data })
})
}

View File

@@ -0,0 +1,4 @@
export * from '../components/FileDetailModal/index'
export * from '../components/FileRenameModal/index'
export * from '../components/FileVideoModal/index'
export * from '../components/FileAudioModal/index'