generated from Leo_Ding/web-template
修改视频部分内容
This commit is contained in:
parent
c1696191c3
commit
1ad5ad954c
351
src/components/Upload/UploadVideo.vue
Normal file
351
src/components/Upload/UploadVideo.vue
Normal file
@ -0,0 +1,351 @@
|
||||
<template>
|
||||
<div ref="uploadVideoRef" class="x-upload x-upload-video" :class="{
|
||||
'x-upload--disabled': disabled
|
||||
}">
|
||||
<a-upload
|
||||
v-if="showUploadBtn"
|
||||
:show-upload-list="false"
|
||||
:multiple="multiple"
|
||||
:before-upload="onBeforeUpload"
|
||||
:custom-request="({ file }) => customRequest(file)"
|
||||
:accept="accept"
|
||||
:disabled="disabled"
|
||||
>
|
||||
<slot>
|
||||
<div class="x-upload-btn" :class="{ 'x-upload-btn--hover': !disabled }" :style="{ width: `${width}px`, height: `${height}px` }">
|
||||
<div class="x-upload-btn__icon">
|
||||
<slot name="icon">
|
||||
<plus-outlined />
|
||||
</slot>
|
||||
</div>
|
||||
<div v-if="text" class="x-upload-btn__txt">
|
||||
<slot name="text">
|
||||
{{ text }}
|
||||
</slot>
|
||||
</div>
|
||||
</div>
|
||||
</slot>
|
||||
</a-upload>
|
||||
|
||||
<div
|
||||
v-for="(item, index) in fileList"
|
||||
:key="item.key"
|
||||
class="x-upload-item j-upload-item"
|
||||
:class="{ 'x-upload-item--error': STATUS_ENUM.is('error', item.status) }"
|
||||
:style="{ width: `${width}px`, height: `${height}px` }"
|
||||
>
|
||||
<video :src="item.src" controls :width="width" :height="height" />
|
||||
|
||||
<template v-if="['error', 'done'].includes(STATUS_ENUM.getKey(item.status))">
|
||||
<div class="x-upload-actions">
|
||||
<div v-if="!disabled" class="x-upload-action" @click="handleRemove(index)">
|
||||
<delete-outlined />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template v-else>
|
||||
<div class="x-upload-status">
|
||||
<template v-if="STATUS_ENUM.is('uploading', item.status)">
|
||||
<div>{{ item.percent }}%</div>
|
||||
<a-progress :show-info="false" :stroke-width="4" :percent="item.percent" />
|
||||
</template>
|
||||
<template v-if="STATUS_ENUM.is('wait', item.status)">
|
||||
<div>{{ STATUS_ENUM.getDesc(item.status) }}</div>
|
||||
<span class="x-upload-action" @click="handleCancel(item)">取消上传</span>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted, computed, watch } from 'vue'
|
||||
import { PlusOutlined, DeleteOutlined } from '@ant-design/icons-vue'
|
||||
import { message } from 'ant-design-vue'
|
||||
import { nanoid } from 'nanoid'
|
||||
import { filesize } from 'filesize'
|
||||
import filesizeParser from 'filesize-parser'
|
||||
import { findIndex, includes, some } from 'lodash-es'
|
||||
import { STATUS_ENUM } from './config'
|
||||
import apis from '@/apis'
|
||||
import { config } from '@/config'
|
||||
defineOptions({
|
||||
name: 'XUploadVideo',
|
||||
})
|
||||
defineProps({
|
||||
modelValue: {
|
||||
type: [String, Array],
|
||||
default: '',
|
||||
},
|
||||
multiple: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
width: {
|
||||
type: Number,
|
||||
default: 320,
|
||||
},
|
||||
height: {
|
||||
type: Number,
|
||||
default: 180,
|
||||
},
|
||||
text: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
maxSize: {
|
||||
type: [String, Number],
|
||||
default: '50M',
|
||||
},
|
||||
accept: {
|
||||
type: String,
|
||||
default: 'video/*',
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update:modelValue'])
|
||||
|
||||
const fileList = ref([])
|
||||
const uploadVideoRef = ref()
|
||||
|
||||
const loading = computed(() => fileList.value.some((o) => STATUS_ENUM.is('uploading', o.status)))
|
||||
const showUploadBtn = computed(() => props.multiple || !fileList.value.length)
|
||||
|
||||
watch(() => props.modelValue, () => init())
|
||||
onMounted(() => init())
|
||||
|
||||
function init() {
|
||||
const currentValue = Array.isArray(props.modelValue) ? props.modelValue : [props.modelValue]
|
||||
fileList.value = currentValue.filter(Boolean).map((src) => getItem({ src }))
|
||||
}
|
||||
|
||||
function onBeforeUpload(file) {
|
||||
const maxFileSize = typeof props.maxSize === 'number' ? props.maxSize : filesizeParser(props.maxSize)
|
||||
const isVideo = file.type.startsWith('video/')
|
||||
const isTooLarge = file.size > maxFileSize
|
||||
|
||||
if (!isVideo) {
|
||||
message.error('只能上传视频文件')
|
||||
return false
|
||||
}
|
||||
|
||||
if (isTooLarge) {
|
||||
message.error(`文件过大,最大不能超过 ${filesize(maxFileSize)}`)
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
function customRequest(file) {
|
||||
const record = getItem({
|
||||
key: file.uid,
|
||||
status: STATUS_ENUM.getValue('wait'),
|
||||
percent: 0,
|
||||
file,
|
||||
})
|
||||
|
||||
// 预览地址设置
|
||||
const reader = new FileReader()
|
||||
reader.onload = (e) => {
|
||||
record.src = e.target.result
|
||||
}
|
||||
reader.readAsDataURL(file)
|
||||
|
||||
if (props.multiple) {
|
||||
fileList.value.push(record)
|
||||
} else {
|
||||
fileList.value = [record]
|
||||
}
|
||||
|
||||
if (!loading.value) {
|
||||
doUpload()
|
||||
}
|
||||
}
|
||||
|
||||
async function doUpload() {
|
||||
const index = findIndex(fileList.value, { status: STATUS_ENUM.getValue('wait') })
|
||||
if (index === -1) return
|
||||
|
||||
const record = fileList.value[index]
|
||||
record.status = STATUS_ENUM.getValue('uploading')
|
||||
|
||||
// 模拟进度
|
||||
await simulateProgress(record)
|
||||
|
||||
// 模拟上传请求
|
||||
const formData = new FormData()
|
||||
formData.append('file', record.file)
|
||||
const { success, data } = await apis.common.uploadFile(formData)
|
||||
|
||||
if (config('http.code.success') === success) {
|
||||
record.status = STATUS_ENUM.getValue('done')
|
||||
record.src = data.url || record.src // 替换为真实地址
|
||||
emitValue()
|
||||
await doUpload()
|
||||
} else {
|
||||
record.status = STATUS_ENUM.getValue('error')
|
||||
}
|
||||
}
|
||||
|
||||
async function simulateProgress(record) {
|
||||
return new Promise((resolve) => {
|
||||
let percent = 0
|
||||
const interval = setInterval(() => {
|
||||
percent += Math.random() * 10 + 5
|
||||
if (percent >= 95) {
|
||||
clearInterval(interval)
|
||||
record.percent = 95
|
||||
resolve()
|
||||
} else {
|
||||
record.percent = Math.floor(percent)
|
||||
}
|
||||
}, 200)
|
||||
})
|
||||
}
|
||||
function handleRemove(index) {
|
||||
fileList.value.splice(index, 1)
|
||||
emitValue()
|
||||
}
|
||||
|
||||
function handleCancel({ key }) {
|
||||
const index = fileList.value.findIndex((o) => o.key === key)
|
||||
fileList.value.splice(index, 1)
|
||||
}
|
||||
|
||||
function getItem(obj) {
|
||||
return {
|
||||
key: nanoid(),
|
||||
src: '',
|
||||
status: STATUS_ENUM.getValue('done'),
|
||||
percent: 100,
|
||||
...obj,
|
||||
}
|
||||
}
|
||||
|
||||
function emitValue() {
|
||||
const value = props.multiple
|
||||
? fileList.value.filter((item) => STATUS_ENUM.is('done', item.status)).map((item) => item?.src ?? item)
|
||||
: (fileList.value[0]?.src ?? '') || ''
|
||||
emit('update:modelValue', value)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.x-upload {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px;
|
||||
|
||||
&--disabled {
|
||||
.x-upload-btn {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
|
||||
&-btn {
|
||||
border: 1px dashed #ccc;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
border-radius: 8px;
|
||||
|
||||
&--hover:hover {
|
||||
border-color: #1890ff;
|
||||
color: #1890ff;
|
||||
}
|
||||
|
||||
&__icon {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
&__txt {
|
||||
margin-top: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
&-item {
|
||||
position: relative;
|
||||
background: #f5f5f5;
|
||||
overflow: hidden;
|
||||
border-radius: 8px;
|
||||
|
||||
video {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.x-upload-actions {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
&--error::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: 1px dashed red;
|
||||
z-index: 2;
|
||||
}
|
||||
}
|
||||
|
||||
&-actions {
|
||||
position: absolute;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 4px;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
left: 0;
|
||||
top: 0;
|
||||
background: rgba(0, 0, 0, 0.25);
|
||||
opacity: 0;
|
||||
transition: all 0.15s;
|
||||
}
|
||||
|
||||
&-action {
|
||||
min-width: 24px;
|
||||
height: 24px;
|
||||
color: #fff;
|
||||
border-radius: 2px;
|
||||
cursor: pointer;
|
||||
background: rgba(0, 0, 0, 0.3);
|
||||
padding: 0 4px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
&:hover {
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
}
|
||||
|
||||
&-status {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
left: 0;
|
||||
top: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: rgba(0, 0, 0, 0.4);
|
||||
color: #fff;
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -16,6 +16,7 @@ import QrCode from './QrCode/QrCode.vue'
|
||||
import ResizeBox from './ResizeBox/ResizeBox.vue'
|
||||
import SearchBar from './SearchBar/SearchBar.vue'
|
||||
import UploadImage from './Upload/UploadImage.vue'
|
||||
import UploadVideo from './Upload/UploadVideo.vue'
|
||||
import UploadInput from './Upload/UploadInput.vue'
|
||||
import Scrollbar from './Scrollbar/Scrollbar.vue'
|
||||
import Cascader from './Cascader/Cascader.vue'
|
||||
@ -38,6 +39,7 @@ const componentList = [
|
||||
ResizeBox,
|
||||
SearchBar,
|
||||
UploadImage,
|
||||
UploadVideo,
|
||||
UploadInput,
|
||||
Scrollbar,
|
||||
Cascader,
|
||||
|
||||
@ -45,27 +45,32 @@
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="24">
|
||||
<a-form-item label="视频" name="videoUrl">
|
||||
<a-upload
|
||||
:action="uploadUrl"
|
||||
:show-upload-list="false"
|
||||
:before-upload="beforeUpload"
|
||||
:on-success="handleUploadSuccess"
|
||||
:accept="'video/*'"
|
||||
>
|
||||
<a-button type="primary">上传视频</a-button>
|
||||
</a-upload>
|
||||
|
||||
<video
|
||||
v-if="formData.videoUrl"
|
||||
:src="formData.videoUrl"
|
||||
@imgChange="(val) => imgChange(val, 'videoUrl')"
|
||||
controls
|
||||
style="margin-top: 10px; width: 100%; max-width: 600px;"
|
||||
/>
|
||||
<a-form-item :label="'视频'" name="videoUrl">
|
||||
<x-upload-video v-model="formData.videoUrl" @imgChange="(val) => imgChange(val, 'videoUrl')" />
|
||||
</a-form-item>
|
||||
|
||||
</a-col>
|
||||
<!-- <a-col :span="24">-->
|
||||
<!-- <a-form-item label="视频" name="videoUrl">-->
|
||||
<!-- <a-upload-->
|
||||
<!-- :action="uploadUrl"-->
|
||||
<!-- :show-upload-list="false"-->
|
||||
<!-- :before-upload="beforeUpload"-->
|
||||
<!-- :on-success="handleUploadSuccess"-->
|
||||
<!-- :accept="'video/*'"-->
|
||||
<!-- >-->
|
||||
<!-- <a-button type="primary">上传视频</a-button>-->
|
||||
<!-- </a-upload>-->
|
||||
|
||||
<!-- <video-->
|
||||
<!-- v-if="formData.videoUrl"-->
|
||||
<!-- :src="formData.videoUrl"-->
|
||||
<!-- @imgChange="(val) => imgChange(val, 'videoUrl')"-->
|
||||
<!-- controls-->
|
||||
<!-- style="margin-top: 10px; width: 100%; max-width: 600px;"-->
|
||||
<!-- />-->
|
||||
<!-- </a-form-item>-->
|
||||
|
||||
<!-- </a-col>-->
|
||||
</a-row>
|
||||
</a-card>
|
||||
</a-form>
|
||||
@ -241,31 +246,6 @@ defineExpose({
|
||||
handleCreate,
|
||||
handleEdit,
|
||||
})
|
||||
/**
|
||||
* 视频上传
|
||||
*/
|
||||
function beforeUpload(file) {
|
||||
const isVideo = file.type.startsWith('video/')
|
||||
const isLt100M = file.size / 1024 / 1024 < 100
|
||||
if (!isVideo) {
|
||||
message.error('只能上传视频文件')
|
||||
return false
|
||||
}
|
||||
if (!isLt100M) {
|
||||
message.error('视频大小不能超过 100MB')
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
function handleUploadSuccess(response) {
|
||||
formData.value.videoUrl = response.url
|
||||
message.success('视频上传成功')
|
||||
}
|
||||
|
||||
function videoChange(url) {
|
||||
console.log('视频改变了:', url)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user