修改视频部分内容

This commit is contained in:
qingyu 2025-06-26 13:30:23 +08:00
parent c1696191c3
commit 1ad5ad954c
3 changed files with 377 additions and 44 deletions

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

View File

@ -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,

View File

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