From e220ffde8924dbfd2319012f5aef83865244bc22 Mon Sep 17 00:00:00 2001 From: xiangpei <xiangpei@timesnew.cn> Date: 星期四, 12 六月 2025 16:10:18 +0800 Subject: [PATCH] 发布视频支持图片 --- pages/tabbar/video/video.vue | 305 +++++++++++++++++++++++++------------ pages/tabbar/index/home1.vue | 144 ++++++++++++++++++ 2 files changed, 348 insertions(+), 101 deletions(-) diff --git a/pages/tabbar/index/home1.vue b/pages/tabbar/index/home1.vue new file mode 100644 index 0000000..1a5edcc --- /dev/null +++ b/pages/tabbar/index/home1.vue @@ -0,0 +1,144 @@ +<template> + <!-- 鑷畾涔夋帶鍒舵潯 --> + <view + @touchstart="handleTouchStart" + @touchmove="handleTouchMove" + @touchend="handleTouchEnd" + class="container"> + <!-- 杩涘害鏉� - 鏁翠釜鍖哄煙鍙嫋鍔� --> + <view class="process-warp" :style="{ opacity: showProcess ? 1 : 0 }"> + <!-- 鏄剧ず褰撳墠杩涘害 --> + <view class="progress-text">{{ hasPlayTime }}/{{formartDuration}}</view> + <view + class="progress-bar" + id="progressBar" + > + + <!-- 宸插~鍏呴儴鍒� --> + <view class="progress-fill" :style="{ width: progress + '%' }"></view> + </view> + </view> + </view> +</template> + +<script> +export default { + data() { + return { + startX: 0, + progress: 0, // 瑙嗛杩涘害 + startProgress : 0, // 寮�濮嬫粦鍔ㄦ椂鐨勮繘搴� + barLeft: 0, // 杩涘害鏉″乏杈圭晫浣嶇疆 + barWidth: 0, // 杩涘害鏉″搴� + isDragging: false, // 鏄惁姝e湪鎷栧姩 + processHidenTimer: null, // 杩涘害鏉¢殣钘忓畾鏃跺櫒 + showProcess: false, // 鏄惁鏄剧ず杩涘害鏉� + }; + }, + mounted() { + // 鑾峰彇杩涘害鏉$殑灏哄鍜屼綅缃俊鎭� + this.getBarRect(); + }, + methods: { + // 鑾峰彇杩涘害鏉$殑浣嶇疆鍜屽昂瀵� + getBarRect() { + const query = uni.createSelectorQuery().in(this); + query.select('#progressBar').boundingClientRect(rect => { + if (rect) { + this.barLeft = rect.left; + this.barWidth = rect.width; + } + }).exec(); + }, + + // 瑙︽懜寮�濮� + handleTouchStart(e) { + this.isDragging = true; + this.showProcess = true; + this.startProgress = this.progress; // 璁板綍寮�濮嬫椂鐨勮繘搴� + this.startX = e.touches[0].pageX; + console.log("璁板綍寮�濮嬫椂鐨勮繘搴�", this.startProgress); + this.videoContexts[this.currentIndex].pause() + // this.updateProgress(e); + }, + + // 瑙︽懜绉诲姩 + handleTouchMove(e) { + if (!this.isDragging || !this.barWidth) return; + clearTimeout(this.processHidenTimer) + this.videoContexts[this.currentIndex].pause() + this.updateProgress(e); + }, + + // 瑙︽懜缁撴潫 + handleTouchEnd() { + this.isDragging = false; + console.log("婊戝姩缁撴潫", this.duration * this.progress); + this.videoContexts[this.currentIndex].seek(this.duration * this.progress / 100) + this.videoContexts[this.currentIndex].play() + this.processHidenTimer = setTimeout(() => { + this.showProcess = false; + }, 1000); + }, + + // 鏇存柊杩涘害 + updateProgress(e) { + // 鑾峰彇褰撳墠瑙︽懜鐐筙鍧愭爣 + const currentX = e.touches[0].pageX; + + // 璁$畻婊戝姩璺濈(鍍忕礌) + const deltaX = currentX - this.startX; + + // 灏嗗儚绱犺窛绂昏浆鎹负杩涘害澧為噺 + const deltaProgress = (deltaX / this.barWidth) * 100; + console.log("杩涘害澧為噺", deltaProgress); + // 璁$畻鏂拌繘搴� = 寮�濮嬫椂鐨勮繘搴� + 婊戝姩澧為噺 + let newProgress = this.startProgress + deltaProgress; + + // 闄愬埗鑼冨洿鍦�0-100涔嬮棿 + newProgress = Math.max(0, Math.min(100, newProgress)); + + this.progress = newProgress; + } + } +}; +</script> + +<style> +.container { + display: flex; + flex-direction: column; + align-items: center; + position: absolute; + bottom: 0; + width: 100%; + } + + .progress-bar { + position: relative; + width: 100%; + height: 16px; + background-color: #eee; + overflow: hidden; + } + + .progress-fill { + position: absolute; + left: 0; + top: 0; + height: 100%; + background-color: lightgray; + transition: width 0.1s; + } + .process-warp { + width: 100%; + display: flex; + flex-direction: column; + align-items: center; + } + .progress-text { + margin-top: 10px; + font-size: 14px; + color: #666; + } +</style> \ No newline at end of file diff --git a/pages/tabbar/video/video.vue b/pages/tabbar/video/video.vue index 18d38ae..b996395 100644 --- a/pages/tabbar/video/video.vue +++ b/pages/tabbar/video/video.vue @@ -1,16 +1,22 @@ <template> <view class="publish-container"> + <u-popup v-model="fileTypeShow" mode="bottom" round="20" height="35%"> + <view style="width: 100%;height:100%;display: flex;flex-direction: column;justify-content: center;align-items: center;"> + <view>璇烽�夋嫨瑕佸彂甯冪殑绫诲瀷</view> + <u-button style="width: 50%;margin-bottom: 30rpx;margin-top: 20rpx;" type="success" @click="chooseVideo">瑙嗛</u-button> + <u-button style="width: 50%;" type="success" @click="chooseImgs">鍥剧墖</u-button> + </view> + </u-popup> <!-- 瑙嗛涓婁紶鍖哄煙 --> <view class="upload-section"> - <view class="upload-btn" @click="chooseVideo" v-if="!videoInfo.url"> + <view class="upload-btn" @click="this.fileTypeShow = true" v-if="!formData.videoFileKey && formData.videoImgs.length < 1"> <u-icon name="plus" size="40" color="#999"></u-icon> - <text class="upload-text">鐐瑰嚮涓婁紶瑙嗛</text> - <text class="upload-tips">鏀寔MP4鏍煎紡锛屾渶闀�60绉�</text> + <text class="upload-text">鐐瑰嚮涓婁紶</text> </view> - - <view class="video-preview" v-else> - <video - :src="videoInfo.url" + + <view class="video-preview" v-else-if="formData.videoContentType === 'video'"> + <video + :src="videoInfo.url" :object-fit="formData.videoFit" class="video-player" :poster="videoInfo.cover || ''" @@ -20,30 +26,48 @@ </view> <view class="video-actions"> <u-button type="error" size="mini" @click="reUpload">閲嶆柊涓婁紶</u-button> - <u-button type="primary" size="mini" @click="chooseCover" v-if="videoInfo.url">{{formData.cover ? '鏇存崲灏侀潰' : '璇烽�夋嫨灏侀潰'}}</u-button> + <u-button type="primary" size="mini" @click="chooseCover" v-if="formData.videoFileKey">{{formData.cover ? '鏇存崲灏侀潰' : '璇烽�夋嫨灏侀潰'}}</u-button> </view> </view> + + <view class="image-list" v-else-if="formData.videoContentType === 'img'"> + <view + v-for="item in videoPreviewImgs" + :key="item" + class="image-item" + :style="{width: itemWidth + 'px', height: itemWidth + 'px'}" + > + <image + :src="item" + mode="aspectFill" + class="image" + /> + </view> + <view class="video-actions"> + <u-button type="error" size="mini" @click="reUpload">閲嶆柊涓婁紶</u-button> + </view> + </view> </view> - + <!-- 瑙嗛淇℃伅琛ㄥ崟 --> <view class="form-section"> <u-form :model="formData" ref="formRef" labelWidth="80"> <!-- 鏍囬杈撳叆 --> <u-form-item label="鏍囬" prop="title" borderBottom> - <u-input - v-model="formData.title" + <u-input + v-model="formData.title" placeholder="璇疯緭鍏ヨ棰戞爣棰�,20瀛椾互鍐�" maxlength="20" show-word-limit clearable /> </u-form-item> - + <!-- 璇濋杈撳叆 --> <u-form-item label="璇濋" prop="tags" borderBottom> <view class="tags-input-container"> - <u-input - v-model="tagInput" + <u-input + v-model="tagInput" placeholder="杈撳叆璇濋锛屽洖杞︾‘璁�" clearable @confirm="addTag" @@ -52,7 +76,7 @@ ></u-input> <!-- 宸查�夎瘽棰樺睍绀� --> <view class="tags-display" v-if="formData.tags.length > 0"> - <my-tag + <my-tag v-for="(tag, index) in formData.tags" :key="index" :text="tag.tagName" @@ -69,7 +93,7 @@ <view class="hot-topics" v-if="showTopicRecommendations"> <text class="section-title">{{ tagInput ? '鎺ㄨ崘璇濋' : '鐑棬璇濋' }}</text> <view class="topic-list"> - <my-tag + <my-tag v-for="(tag, index) in recommendedTags" :key="index" :text="tag.tagName" @@ -81,22 +105,22 @@ </view> </view> </u-form-item> - - + + <!-- 鍟嗗搧閾炬帴 --> <u-form-item label="鍟嗗搧" prop="goodsId" borderBottom> <view class="goods-link-container"> - <u-input + <u-input placeholder="鍙�夋嫨鎺ㄨ崘鍟嗗搧" clearable v-if="!selectedGoods" @click="chooseGoods" disabled > - <u-icon - slot="right" - name="search" - size="24" + <u-icon + slot="right" + name="search" + size="24" @click="chooseGoods" ></u-icon> </u-input> @@ -106,9 +130,9 @@ <text class="goods-name">{{ selectedGoods.name }}</text> <text class="goods-price">楼{{ selectedGoods.price }}</text> </view> - <u-icon - name="close" - size="20" + <u-icon + name="close" + size="20" @click="clearGoods" ></u-icon> </view> @@ -116,12 +140,12 @@ </u-form-item> </u-form> </view> - + <!-- 鍙戝竷鎸夐挳 --> <view class="publish-btn"> - <u-button - type="primary" - shape="circle" + <u-button + type="success" + shape="circle" :loading="loading" @click="handlePublish" :disabled="!canPublish" @@ -129,7 +153,7 @@ {{ loading ? '鍙戝竷涓�...' : '绔嬪嵆鍙戝竷' }} </u-button> </view> - + <!-- 鍟嗗搧閫夋嫨寮圭獥 --> <u-popup v-model="showGoodsPicker" mode="bottom" round="20" height="70%"> <view class="goods-picker"> @@ -138,16 +162,16 @@ <u-icon name="close" size="24" @click="showGoodsPicker = false"></u-icon> </view> <view class="search-bar"> - <u-search - v-model="goodsSearch" - placeholder="鎼滅储鍟嗗搧鍚嶇О" + <u-search + v-model="goodsSearch" + placeholder="鎼滅储鍟嗗搧鍚嶇О" :showAction="false" ></u-search> </view> <scroll-view class="goods-list" scroll-y> - <view - class="goods-item" - v-for="goods in filteredGoods" + <view + class="goods-item" + v-for="goods in filteredGoods" :key="goods.id" @click="selectGoods(goods)" > @@ -156,16 +180,16 @@ <text class="goods-name">{{ goods.name }}</text> <text class="goods-price">楼{{ goods.price }}</text> </view> - <u-icon - name="checkmark" - size="24" + <u-icon + name="checkmark" + size="24" :color="selectedGoods && selectedGoods.id === goods.id ? '#2979ff' : '#ccc'" ></u-icon> </view> </scroll-view> </view> </u-popup> - + <custom-tabbar bgColor="#ffffff" selected="video"></custom-tabbar> </view> </template> @@ -188,14 +212,17 @@ components: {MyTag,UIcon,UButton,UForm,UFormItem,UInput,USearch,UPopup}, data() { return { + fileTypeShow: false, cosClient: null, bucket: '', region: '', + endpoint: '', videoUploadProgress: 0, loading: false, showGoodsPicker: false, goodsSearch: '', tagInput: '', + videoPreviewImgs: [], // 棰勮鍥剧墖鍦板潃 videoInfo: { url: '', fileKey: '', @@ -212,6 +239,8 @@ videoDuration: 0, videoFit: 'cover', goodsId: '', + videoContentType: 'video', + videoImgs: [], tags: [], fileInfo: {} }, @@ -236,25 +265,37 @@ { required: true, message: '璇疯緭鍏ヨ棰戞爣棰�', trigger: 'blur' }, { min: 1, max: 20, message: '鏍囬闀垮害鍦�1鍒�20涓瓧绗�', trigger: 'blur' } ] - } + }, + screenWidth: 375, + gap: 10 // 鍥剧墖闂磋窛 }; }, computed: { canPublish() { - return this.formData.videoFileKey && this.formData.title && this.formData.cover; + if(this.formData.videoContentType === 'video') { + return this.formData.videoFileKey && this.formData.title && this.formData.cover; + } else if(this.formData.videoContentType === 'img') { + return this.formData.videoImgs.length > 0 && this.formData.title; + } }, filteredGoods() { if (!this.goodsSearch) return this.goodsList; - return this.goodsList.filter(goods => + return this.goodsList.filter(goods => goods.name.toLowerCase().includes(this.goodsSearch.toLowerCase()) ); }, showTopicRecommendations() { return (this.tagInput === '' || this.recommendedTags.length > 0) && this.formData.tags.length < 5; - } + }, + // 璁$畻姣忎釜鍥剧墖椤圭殑瀹藉害锛堣�冭檻闂磋窛锛� + itemWidth() { + return (this.screenWidth - (this.gap * 4) - 20) / 3 + } }, - created() { - + onLoad() { + // 鑾峰彇灞忓箷瀹藉害 + const systemInfo = uni.getSystemInfoSync() + this.screenWidth = systemInfo.windowWidth }, onShow() { this.initCOS() @@ -278,7 +319,7 @@ getSTSToken().then(res => { const COS = require('@/lib/cos-wx-sdk-v5.js'); // 寮�鍙戞椂浣跨敤 // const COS = require('./lib/cos-wx-sdk-v5.min.js'); // 涓婄嚎鏃朵娇鐢ㄥ帇缂╁寘 - + // console.log(COS.version); sdk 鐗堟湰闇�瑕佷笉浣庝簬 1.7.2 this.cosClient = new COS({ SecretId: res.data.data.tmpSecretId, // sts 鏈嶅姟涓嬪彂鐨勪复鏃� secretId @@ -290,11 +331,17 @@ }); this.bucket = res.data.data.bucket this.region = res.data.data.region + this.endpoint = res.data.data.endpoint }) - + }, // 閫夋嫨瑙嗛 chooseVideo() { + this.fileTypeShow = false; + // 娓呯┖閫夋嫨鐨勫浘鐗� + this.videoPreviewImgs = []; + this.formData.videoImgs = []; + this.formData.videoContentType = 'video' uni.chooseVideo({ sourceType: ['album', 'camera'], maxDuration: 60, @@ -304,7 +351,7 @@ // 鑾峰彇鏂囦欢鍚� const tempPath = res.tempFilePath; let fileName = tempPath.substring(tempPath.lastIndexOf('/') + 1); - + // 澶勭悊瀹夊崜鍙兘鐨刄RI缂栫爜 if(fileName.indexOf('%') > -1) { fileName = decodeURIComponent(fileName); @@ -322,12 +369,12 @@ this.formData.videoDuration = res.duration; // 鍒ゆ柇瑙嗛鐨勫~鍏呮ā寮� this.formData.videoFit = this.calculateVideoFit(res.width, res.height) - + this.cosClient.uploadFile({ Bucket: this.bucket, Region: this.region, Key: fileKey, - FilePath: res.tempFilePath, + FilePath: res.tempFilePath, SliceSize: 1024 * 1024 * 5, /* 瑙﹀彂鍒嗗潡涓婁紶鐨勯槇鍊�,5M */ onProgress: (progressData) => { console.log(progressData.percent); @@ -362,27 +409,60 @@ calculateVideoFit(width, height) { const viewportRatio = uni.getSystemInfoSync().windowWidth / uni.getSystemInfoSync().windowHeight; const videoRatio = width / height; - + // 瑙勫垯1锛氳秴瀹借棰戯紙濡傜數褰�21:9锛� if (videoRatio > 2) return 'contain'; - + // 瑙勫垯2锛氱珫灞忚棰戯紙濡�9:16锛� if (videoRatio < 0.8) return 'cover'; - + // 瑙勫垯3锛氭帴杩戝睆骞曟瘮渚嬬殑妯睆瑙嗛 return Math.abs(videoRatio - viewportRatio) > 0.3 ? 'contain' : 'cover'; }, // 閲嶆柊涓婁紶 reUpload() { - this.videoInfo = { - url: '', - cover: '', - duration: 0, - size: 0 - }; - this.chooseVideo(); + this.resetData() + this.fileTypeShow = true }, - + // 閫夋嫨瑙嗛鍥鹃泦 + chooseImgs() { + this.fileTypeShow = false + // 娓呯┖閫夋嫨鐨勮棰� + this.formData.videoFileKey = ''; + this.formData.cover = ''; + this.formData.videoContentType = 'img' + uni.chooseImage({ + count: 9, + sizeType: ['compressed'], + sourceType: ['album'], + success: (res) => { + res.tempFilePaths.forEach(tmpImg => { + let fileName = tmpImg.substring(tmpImg.lastIndexOf('/') + 1); + // 澶勭悊瀹夊崜鍙兘鐨刄RI缂栫爜 + if(fileName.indexOf('%') > -1) { + fileName = decodeURIComponent(fileName); + } + const fileKey = getFileKey(fileName); + this.cosClient.uploadFile({ + Bucket: this.bucket, + Region: this.region, + Key: fileKey, + FilePath: tmpImg, + SliceSize: 1024 * 1024 * 5 /* 瑙﹀彂鍒嗗潡涓婁紶鐨勯槇鍊�,5M */ + }, (err, data) => { + if (err) { + console.log('涓婁紶澶辫触', err); + } else { + // 鑾峰彇灏侀潰鐨勮闂湴鍧� + this.videoPreviewImgs.push(this.endpoint + '/' + fileKey); + this.formData.videoImgs.push(fileKey); + } + }); + }) + + } + }); + }, // 閫夋嫨灏侀潰 chooseCover() { uni.chooseImage({ @@ -401,41 +481,38 @@ Bucket: this.bucket, Region: this.region, Key: fileKey, - FilePath: res.tempFilePaths[0], + FilePath: res.tempFilePaths[0], SliceSize: 1024 * 1024 * 5 /* 瑙﹀彂鍒嗗潡涓婁紶鐨勯槇鍊�,5M */ }, (err, data) => { if (err) { console.log('涓婁紶澶辫触', err); } else { - // 鑾峰彇灏侀潰鐨勮闂湴鍧� - getFilePreviewUrl(fileKey).then(res => { - this.videoInfo.cover = res.data.data - this.formData.cover = fileKey - }) + this.videoInfo.cover = this.endpoint + '/' + fileKey + this.formData.cover = fileKey } }); } }); }, - + // 閫夋嫨鍟嗗搧 chooseGoods() { this.showGoodsPicker = true; }, - + // 閫夋嫨鍏蜂綋鍟嗗搧 selectGoods(goods) { this.selectedGoods = goods; this.formData.goodsId = goods.id; this.showGoodsPicker = false; }, - + // 娓呴櫎鍟嗗搧 clearGoods() { this.selectedGoods = null; this.formData.goodsId = ''; }, - + // 鎼滅储鐑棬璇濋 searchTags() { if (this.tagInput.trim() !== '') { @@ -466,7 +543,7 @@ }); } }, - + // 閫夋嫨鎺ㄨ崘璇濋 selectTopic(index) { const tag = this.recommendedTags[index] @@ -477,7 +554,7 @@ }); return; } - + if (this.formData.tags.filter(item => item.tagName === tag.tagName).length < 1) { this.formData.tags.push(tag); this.tagInput = ''; @@ -488,12 +565,12 @@ }); } }, - + // 绉婚櫎鏍囩 removeTag(index) { this.formData.tags.splice(index, 1); }, - + // 澶勭悊鍙戝竷 handlePublish() { this.$refs.formRef.validate(valid => { @@ -508,29 +585,11 @@ }); this.loading = false // 閲嶇疆琛ㄥ崟 - this.videoInfo = { - url: '', - fileKey: '', - fileType: '', - fileSize: 0, - originalFileName: '', - cover: '' - }; - this.formData = { - id: '', - title: '', - videoFileKey: '', - cover: '', - videoFit: 'cover', - videoDuration: 0, - goodsId: '', - tags: [], - fileInfo: {} - }; + this.resetData(); this.selectedGoods = null; this.tagInput = ''; this.recommendedTags = []; - + // TODO 鍏堣烦棣栭〉,鍚庨潰璺虫垜鐨勮棰戦〉闈� setTimeout(() => { uni.switchTab({ @@ -545,21 +604,45 @@ }); } }); - } + }, + resetData() { + // 閲嶇疆琛ㄥ崟 + this.videoInfo = { + url: '', + fileKey: '', + fileType: '', + fileSize: 0, + originalFileName: '', + cover: '' + }; + this.formData = { + id: '', + title: '', + videoFileKey: '', + cover: '', + videoFit: 'cover', + videoDuration: 0, + goodsId: '', + videoContentType: 'video', + videoImgs: [], + tags: [], + fileInfo: {} + }; + this.videoPreviewImgs = [] + } } }; </script> <style scoped> .publish-container { - padding: 20rpx; + padding: 10px; padding-bottom: 120rpx; } .upload-section { background-color: #f8f8f8; border-radius: 16rpx; - padding: 40rpx; margin-bottom: 30rpx; display: flex; justify-content: center; @@ -599,9 +682,11 @@ } .video-actions { + width: 100%; margin-top: 20rpx; display: flex; justify-content: center; + align-items: center; gap: 20rpx; } @@ -767,4 +852,22 @@ height: 25px; margin-top: 10px; } -</style> \ No newline at end of file + +.image-list { + display: flex; + flex-wrap: wrap; + justify-content: flex-start; +} + +.image-item { + margin: 5px; + overflow: hidden; + border-radius: 8px; + box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1); +} + +.image { + width: 100%; + height: 100%; +} +</style> -- Gitblit v1.8.0