视频上传按钮修复

This commit is contained in:
丁杰 2026-06-12 13:59:52 +08:00
parent 0e9bca8d97
commit 79ee295c18
4 changed files with 264 additions and 81 deletions

View File

@ -19,9 +19,13 @@
mode="aspectFill"
@click="handlePreview(index)"
/>
<view v-if="deletable" class="delete-icon" @click.stop="handleDelete(index)">
×
</view>
<image
v-if="deletable"
class="delete-icon"
src="https://mrrplus.oss-cn-beijing.aliyuncs.com/photo/system/6ab3fc63-3bad-403d-bbfc-9609a93c048a.png"
mode="aspectFit"
@click.stop="handleDelete(index)"
></image>
</view>
<!-- 上传按钮 -->
<view class="preview-item">
@ -41,27 +45,34 @@
<!-- 视频预览区域 -->
<view class="preview-container" :style="'width:'+width" v-if="accept == 'video/*'">
<block v-if="showPreview">
<view
v-for="(file, index) in videoList"
:key="file || index"
class="preview-item"
v-if="oneFile"
>
<!-- <video
:src="oneFile"
:src="file"
class="preview-image"
@click="handlePreview(index)"
/> -->
<image class="preview-image" :src="oneFile + '?x-oss-process=video/snapshot,t_0,f_jpg'" mode="aspectFill"></image>
<view v-if="deletable" class="delete-icon" @click.stop="handleDelete()">
×
</view>
<image class="preview-image" :src="getVideoPoster(file)" mode="aspectFill"></image>
<image
v-if="deletable"
class="delete-icon"
src="https://mrrplus.oss-cn-beijing.aliyuncs.com/photo/system/6ab3fc63-3bad-403d-bbfc-9609a93c048a.png"
mode="aspectFit"
@click.stop="handleDelete(index)"
></image>
</view>
</block>
<view class="preview-item">
<!-- 上传按钮 -->
<view
class="upload-btn"
@click="handleUpload"
:style="btnStyle"
v-if="!oneFile"
v-if="videoCount < maxCount"
>
<slot name="button">
<text class="plus-icon">+</text>
@ -161,6 +172,7 @@ export default {
return {
fileList: [],
oneFile: '',
videoList: [],
loading: false,
dragIndex: -1,
isDragging: false,
@ -168,6 +180,12 @@ export default {
}
},
computed: {
videoCount() {
return this.showPreview ? this.videoList.length : 0
}
},
watch: {
value: {
immediate: true,
@ -179,6 +197,7 @@ export default {
immediate: true,
handler(newVal) {
this.oneFile = newVal
this.videoList = newVal ? [newVal] : []
}
}
},
@ -284,12 +303,15 @@ export default {
await this.uploadFile(file);
}
} else if (this.accept === 'video/*') {
const res = await uni.chooseVideo({
sourceType: [sourceType],
compressed: true,
maxDuration: 60
});
await this.uploadFile(res);
const count = this.maxCount - this.videoCount;
if (count <= 0) {
uni.showToast({ title: `最多上传${this.maxCount}个文件`, icon: 'none' });
return;
}
const files = await this.chooseVideoFiles(sourceType, count);
for (const file of files) {
await this.uploadFile(file);
}
}
} catch (error) {
console.error('选择文件失败:', error);
@ -305,7 +327,7 @@ export default {
})
return Promise.reject(new Error('文件大小超出限制'))
}
let date = new Date().getTime()
let date = `${new Date().getTime()}_${Math.floor(Math.random() * 100000)}`
const fileExt = this.getFileExt(file)
let artisan = this.type==1 ? 'yh' : this.type==2 ? 'syr' : this.type==3 ? 'sj' : '';
@ -327,8 +349,12 @@ export default {
}else if(this.accept == 'video/*') {
const newFile = fileUrl
this.oneFile = fileUrl
this.$emit('input', this.oneFile)
this.$emit('change', this.oneFile)
if (this.showPreview) {
this.videoList = this.videoList.concat([newFile]).slice(0, this.maxCount)
}
const emitValue = this.maxCount > 1 && this.showPreview ? this.videoList : this.oneFile
this.$emit('input', emitValue)
this.$emit('change', emitValue)
this.$emit('success', newFile)
return newFile
}
@ -369,6 +395,44 @@ export default {
return dir ? `${dir}/${name}` : name
},
getVideoPoster(url) {
if (!url) return ''
if (String(url).indexOf('x-oss-process=') > -1) return url
const connector = String(url).indexOf('?') > -1 ? '&' : '?'
return `${url}${connector}x-oss-process=video/snapshot,t_2000,f_jpg,m_fast`
},
chooseVideoFiles(sourceType, count) {
if (uni.chooseMedia) {
return new Promise((resolve, reject) => {
uni.chooseMedia({
count,
mediaType: ['video'],
sourceType: [sourceType],
maxDuration: 60,
success: (res) => {
const files = (res.tempFiles || []).map((item) => ({
...item,
tempFilePath: item.tempFilePath || item.path,
path: item.tempFilePath || item.path,
}));
resolve(files);
},
fail: reject,
});
});
}
return new Promise((resolve, reject) => {
uni.chooseVideo({
sourceType: [sourceType],
compressed: true,
maxDuration: 60,
success: (res) => resolve([res]),
fail: reject,
});
});
},
// OSS
uploadToOss(file, ossConfig, name) {
return new Promise((resolve, reject) => {
@ -512,11 +576,14 @@ export default {
this.$emit('change', this.fileList)
this.$emit('delete', deletedFile, index)
}else if(this.accept == 'video/*') {
const deletedFile = this.oneFile
this.oneFile = ''
this.$emit('input', this.oneFile)
this.$emit('change', this.oneFile)
this.$emit('delete', deletedFile)
const deleteIndex = index === undefined ? 0 : index
const deletedFile = this.videoList[deleteIndex]
this.videoList.splice(deleteIndex, 1)
this.oneFile = this.videoList[0] || ''
const emitValue = this.maxCount > 1 ? this.videoList : this.oneFile
this.$emit('input', emitValue)
this.$emit('change', emitValue)
this.$emit('delete', deletedFile, deleteIndex)
}
// }
@ -539,8 +606,11 @@ export default {
.preview-container {
display: grid;
grid-template-columns: repeat(3, 1fr);
justify-items: center;
grid-template-columns: repeat(3, 200rpx);
column-gap: 18rpx;
row-gap: 20rpx;
justify-content: flex-start;
justify-items: stretch;
align-items: center;
position: relative;
}
@ -579,7 +649,6 @@ export default {
position: relative;
width: 200rpx;
height: 200rpx;
margin-top: 20rpx;
}
.image-preview-item.dragging {
@ -597,15 +666,8 @@ export default {
position: absolute;
top: -10rpx;
right: -10rpx;
width: 40rpx;
height: 40rpx;
background-color: #ff4d4f;
color: white;
border-radius: 50%;
display: flex;
justify-content: center;
align-items: center;
font-size: 32rpx;
z-index: 1;
width: 42rpx;
height: 42rpx;
z-index: 5;
}
</style>

View File

@ -2,14 +2,23 @@
<view>
<view v-if="maxCount > 1" class="video-list">
<view v-for="(item, index) in currentList" :key="index" class="video-wrapper">
<video :src="item" class="preview-video" controls />
<view class="delete-btn" @click="deleteVideo(index)">
<text class="delete-icon">&times;</text>
<image :src="getVideoPoster(item)" class="preview-video" mode="aspectFill" @click="previewVideo(item)" />
<view class="play-mask" @click="previewVideo(item)">
<view class="play-circle">
<view class="play-icon"></view>
</view>
</view>
<ali-oss-uploader v-if="currentList.length < maxCount" :key="currentList.length"
<image
class="delete-btn"
src="https://mrrplus.oss-cn-beijing.aliyuncs.com/photo/system/6ab3fc63-3bad-403d-bbfc-9609a93c048a.png"
mode="aspectFit"
@click="deleteVideo(index)"
></image>
</view>
<ali-oss-uploader v-if="currentList.length < maxCount"
:max-count="maxCount - currentList.length"
:max-size="maxSize" accept="video/*" :userId="uploadUserId" :type="ossType" :kind="2" width="200rpx"
:show-preview="false"
@input="onMultiInput" @change="onChange" />
</view>
<view v-else>
@ -17,9 +26,25 @@
:userId="uploadUserId" :type="ossType" :kind="2" width="100%" :file-string="currentValue" @input="onInput"
@change="onChange" @success="onInput" @delete="deleteSingleVideo" />
<view v-else class="video-wrapper">
<video :src="currentValue" class="preview-video" controls />
<view class="delete-btn" @click="deleteSingleVideo">
<text class="delete-icon">&times;</text>
<image :src="getVideoPoster(currentValue)" class="preview-video" mode="aspectFill" @click="previewVideo(currentValue)" />
<view class="play-mask" @click="previewVideo(currentValue)">
<view class="play-circle">
<view class="play-icon"></view>
</view>
</view>
<image
class="delete-btn"
src="https://mrrplus.oss-cn-beijing.aliyuncs.com/photo/system/6ab3fc63-3bad-403d-bbfc-9609a93c048a.png"
mode="aspectFit"
@click="deleteSingleVideo"
></image>
</view>
</view>
<view v-if="showPlayer" class="player-mask" @click="closePlayer">
<view class="player-box" @click.stop>
<video :src="playingVideo" class="player-video" controls autoplay show-fullscreen-btn />
<view class="player-close" @click="closePlayer">
<text class="player-close-text">&times;</text>
</view>
</view>
</view>
@ -41,7 +66,9 @@ export default {
data: function () {
return {
currentValue: '',
currentList: []
currentList: [],
showPlayer: false,
playingVideo: ''
};
},
computed: {
@ -80,13 +107,33 @@ export default {
},
onMultiInput: function (val) {
var arr = Array.isArray(val) ? val : [val];
var list = this.currentList.concat(arr).slice(0, this.maxCount);
var existing = this.currentList.filter(function (item) {
return arr.indexOf(item) === -1;
});
var list = existing.concat(arr).filter(function (item, index, self) {
return item && self.indexOf(item) === index;
}).slice(0, this.maxCount);
this.currentList = list;
this.$emit('input', list);
},
onChange: function (val) {
this.$emit('change', val);
},
getVideoPoster: function (url) {
if (!url) return '';
if (String(url).indexOf('x-oss-process=') > -1) return url;
var connector = String(url).indexOf('?') > -1 ? '&' : '?';
return url + connector + 'x-oss-process=video/snapshot,t_2000,f_jpg,m_fast';
},
previewVideo: function (url) {
if (!url) return;
this.playingVideo = url;
this.showPlayer = true;
},
closePlayer: function () {
this.showPlayer = false;
this.playingVideo = '';
},
deleteSingleVideo: function () {
this.currentValue = '';
this.$emit('input', '');
@ -110,32 +157,100 @@ export default {
position: relative;
width: 200rpx;
height: 200rpx;
overflow: visible;
}
.preview-video {
width: 200rpx;
height: 200rpx;
border-radius: 8rpx;
background-color: #f5f5f5;
}
.play-mask {
position: absolute;
left: 0;
top: 0;
width: 200rpx;
height: 200rpx;
display: flex;
align-items: center;
justify-content: center;
background-color: rgba(0, 0, 0, 0.15);
border-radius: 8rpx;
z-index: 2;
}
.play-circle {
width: 56rpx;
height: 56rpx;
border-radius: 50%;
background-color: rgba(0, 0, 0, 0.55);
display: flex;
align-items: center;
justify-content: center;
}
.play-icon {
width: 0;
height: 0;
border-top: 14rpx solid transparent;
border-bottom: 14rpx solid transparent;
border-left: 22rpx solid #ffffff;
margin-left: 6rpx;
}
.delete-btn {
position: absolute;
top: -10rpx;
right: -10rpx;
width: 40rpx;
height: 40rpx;
background-color: #ff4d4f;
color: white;
border-radius: 50%;
display: flex;
justify-content: center;
align-items: center;
font-size: 32rpx;
z-index: 1;
width: 42rpx;
height: 42rpx;
z-index: 5;
}
.delete-icon {
.player-mask {
position: fixed;
left: 0;
top: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.82);
z-index: 9999;
display: flex;
align-items: center;
justify-content: center;
padding: 32rpx;
box-sizing: border-box;
}
.player-box {
position: relative;
width: 100%;
}
.player-video {
width: 100%;
height: 420rpx;
background-color: #000000;
}
.player-close {
position: absolute;
right: -12rpx;
top: -58rpx;
width: 48rpx;
height: 48rpx;
border-radius: 50%;
background-color: rgba(255, 255, 255, 0.22);
display: flex;
align-items: center;
justify-content: center;
}
.player-close-text {
color: #ffffff;
font-size: 32rpx;
font-size: 42rpx;
line-height: 48rpx;
}
</style>

View File

@ -12,18 +12,9 @@
<!-- 表单区域 -->
<view class="form-container">
<!-- 服务分类仅显示不可编辑 -->
<view class="form-item" v-if="firstClassName">
<text class="form-label">服务分类</text>
<view class="form-right">
<text class="form-value">{{ firstClassName }}</text>
</view>
</view>
<!-- 服务子类仅显示不可编辑 -->
<view class="form-item" v-if="secondClassName">
<text class="form-label">服务子类</text>
<view class="form-right">
<text class="form-value">{{ secondClassName }}</text>
</view>
<view class="form-item service-class-item" v-if="serviceClassName">
<text class="form-label">服务分类</text>
<text class="form-value service-class-value">{{ serviceClassName }}</text>
</view>
<!-- 动态表单区域 -->
<dynamic-form :fields="formFields" v-model="formData" :upload-user-id="formData.publish_user_id"
@ -209,6 +200,9 @@ export default {
return group.type === 'video';
});
},
serviceClassName() {
return [this.firstClassName, this.secondClassName].filter(Boolean).join('/');
},
},
methods: {
// -> dynamic-form
@ -1171,6 +1165,18 @@ export default {
margin-right: 16rpx;
}
.service-class-item {
justify-content: flex-start;
align-items: flex-start;
}
.service-class-value {
flex: 1;
margin-right: 0;
line-height: 40rpx;
word-break: break-all;
}
.placeholder {
font-weight: 400;
font-size: 28rpx;

View File

@ -120,13 +120,13 @@ const request = async (options, isUpdate) => {
if (process.env.NODE_ENV === "development") {
baseURL = url; // 发布到生产环境时,此处代码会被摇树移除掉。
// baseURL = "http://116.63.163.121:93"; // 发布到生产环境时,此处代码会被摇树移除掉。
baseURL = "http://116.63.163.121:96"; // 发布到生产环境时,此处代码会被摇树移除掉。
baseURL = "http://116.63.163.121:93"; // 发布到生产环境时,此处代码会被摇树移除掉。
// baseURL = "http://116.63.163.121:96"; // 发布到生产环境时,此处代码会被摇树移除掉。
// baseURL = "https://app.mrrweb.com.cn";
} else {
baseURL = url; // 发布到生产环境时,此处代码会被摇树移除掉。
// baseURL = "http://116.63.163.121:93"; // 发布到生产环境时,此处代码会被摇树移除掉。
baseURL = "http://116.63.163.121:96"; // 发布到生产环境时,此处代码会被摇树移除掉。
baseURL = "http://116.63.163.121:93"; // 发布到生产环境时,此处代码会被摇树移除掉。
// baseURL = "http://116.63.163.121:96"; // 发布到生产环境时,此处代码会被摇树移除掉。
// baseURL = "https://app.mrrweb.com.cn";
console.log("生产环境");