mirror of
https://github.com/continew-org/continew-admin-ui.git
synced 2025-09-10 20:57:10 +08:00
feat: 新增文件管理
This commit is contained in:
140
src/views/system/file/components/FileAudioModal/ModalContent.vue
Normal file
140
src/views/system/file/components/FileAudioModal/ModalContent.vue
Normal 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>
|
45
src/views/system/file/components/FileAudioModal/index.ts
Normal file
45
src/views/system/file/components/FileAudioModal/index.ts
Normal 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 = ''
|
||||
}
|
||||
})
|
||||
}
|
@@ -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>
|
17
src/views/system/file/components/FileDetailModal/index.ts
Normal file
17
src/views/system/file/components/FileDetailModal/index.ts
Normal 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 })
|
||||
})
|
||||
}
|
@@ -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>
|
27
src/views/system/file/components/FileRenameModal/index.ts
Normal file
27
src/views/system/file/components/FileRenameModal/index.ts
Normal 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
|
||||
}
|
||||
})
|
||||
}
|
@@ -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>
|
13
src/views/system/file/components/FileVideoModal/index.ts
Normal file
13
src/views/system/file/components/FileVideoModal/index.ts
Normal 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 })
|
||||
})
|
||||
}
|
4
src/views/system/file/components/index.ts
Normal file
4
src/views/system/file/components/index.ts
Normal 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'
|
Reference in New Issue
Block a user