| | |
| | | <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" |
| | | controls |
| | | |
| | | <view class="video-preview" v-else-if="formData.videoContentType === 'video'"> |
| | | <video |
| | | :src="videoInfo.url" |
| | | :object-fit="formData.videoFit" |
| | | class="video-player" |
| | | :poster="videoInfo.cover || ''" |
| | | ></video> |
| | | <view class="progress-box"> |
| | | <progress style="width: 100%;" :percent="videoUploadProgress" active-mode="forwards" show-info stroke-width="6" :active="true" active-color="#ff573e" /> |
| | | </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">选择封面</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" |
| | | placeholder="请输入视频标题" |
| | | maxlength="30" |
| | | <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" |
| | | @blur="addTag" |
| | | @input="searchHotTopics" |
| | | @input="searchTags" |
| | | ></u-input> |
| | | <!-- 已选话题展示 --> |
| | | <view class="tags-display" v-if="formData.tags.length > 0"> |
| | | <u-tag |
| | | <my-tag |
| | | v-for="(tag, index) in formData.tags" |
| | | :key="index" |
| | | :text="tag" |
| | | closable |
| | | :text="tag.tagName" |
| | | :index="index" |
| | | type="success" |
| | | @close="removeTag(index)" |
| | | size="medium" |
| | | type="primary" |
| | | style="margin-right: 10rpx; margin-bottom: 10rpx;" |
| | | /> |
| | | </view> |
| | | <text class="tags-count" v-if="formData.tags.length > 0"> |
| | | 已选 {{ formData.tags.length }}/5 |
| | | </text> |
| | | </view> |
| | | </u-form-item> |
| | | |
| | | |
| | | <!-- 话题推荐 --> |
| | | <view class="hot-topics" v-if="showTopicRecommendations"> |
| | | <text class="section-title">{{ tagInput ? '推荐话题' : '热门话题' }}</text> |
| | | <view class="topic-list"> |
| | | <u-tag |
| | | v-for="(topic, index) in recommendedTopics" |
| | | :key="index" |
| | | :text="topic" |
| | | size="medium" |
| | | type="info" |
| | | @click="selectTopic(topic)" |
| | | style="margin-right: 10rpx; margin-bottom: 10rpx;" |
| | | /> |
| | | <!-- 话题推荐 --> |
| | | <view class="hot-topics" v-if="showTopicRecommendations"> |
| | | <text class="section-title">{{ tagInput ? '推荐话题' : '热门话题' }}</text> |
| | | <view class="topic-list"> |
| | | <my-tag |
| | | v-for="(tag, index) in recommendedTags" |
| | | :key="index" |
| | | :text="tag.tagName" |
| | | :index="index" |
| | | type="success" |
| | | :closeable="false" |
| | | @click="selectTopic(index)" |
| | | /> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | |
| | | </u-form-item> |
| | | |
| | | |
| | | <!-- 商品链接 --> |
| | | <u-form-item label="商品" prop="goodsLink" borderBottom> |
| | | <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> |
| | | <view class="goods-preview" v-if="selectedGoods"> |
| | | <image :src="selectedGoods.image" class="goods-image"></image> |
| | | <view class="goods-preview" @click="chooseGoods" v-for="goods in selectedGoodsList" :key="goods.id"> |
| | | <image :src="goods.thumbnail" class="goods-image"></image> |
| | | <view class="goods-info"> |
| | | <text class="goods-name">{{ selectedGoods.name }}</text> |
| | | <text class="goods-price">¥{{ selectedGoods.price }}</text> |
| | | <text class="goods-name">{{ goods.goodsName }}</text> |
| | | <view style="display: flex;"> |
| | | <view class="goods-price" style="flex: 1;">¥{{ goods.price }}</view> |
| | | <view @click.stop="() => {}" style="flex: 1;display: flex;justify-content: center;align-items: center;"> |
| | | <view style="width: 90rpx">数量:</view> |
| | | <uni-number-box v-model="goods.goodsNum" :min="0"/> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | <u-icon |
| | | name="close" |
| | | size="20" |
| | | @click="clearGoods" |
| | | <u-icon |
| | | style="position: absolute;right: 8rpx;top: 8rpx" |
| | | name="close" |
| | | size="24" |
| | | @click.stop="clearGoods(goods)" |
| | | ></u-icon> |
| | | </view> |
| | | </view> |
| | | </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" |
| | |
| | | {{ loading ? '发布中...' : '立即发布' }} |
| | | </u-button> |
| | | </view> |
| | | |
| | | |
| | | <!-- 商品选择弹窗 --> |
| | | <u-popup v-model="showGoodsPicker" mode="bottom" round="20" height="70%"> |
| | | <view class="goods-picker"> |
| | |
| | | <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="goodsQuery.keyword" |
| | | placeholder="搜索商品" |
| | | :showAction="false" |
| | | @change="handlerGoodsSearch" |
| | | ></u-search> |
| | | </view> |
| | | <scroll-view class="goods-list" scroll-y> |
| | | <view |
| | | class="goods-item" |
| | | v-for="goods in filteredGoods" |
| | | <scroll-view class="goods-list" @scrolltolower="loadMoreGoods" scroll-y :show-scrollbar="false"> |
| | | <view |
| | | class="goods-item" |
| | | v-for="(goods, index) in goodsList" |
| | | :key="goods.id" |
| | | @click="selectGoods(goods)" |
| | | @click="selectGoods(goods, index)" |
| | | > |
| | | <image :src="goods.image" class="goods-image"></image> |
| | | <image :src="goods.thumbnail" class="goods-image"></image> |
| | | <view class="goods-info"> |
| | | <text class="goods-name">{{ goods.name }}</text> |
| | | <text class="goods-name">{{ goods.goodsName }}</text> |
| | | <text class="goods-price">¥{{ goods.price }}</text> |
| | | <!-- <view>{{ goods.sellingPoint }}</view> --> |
| | | </view> |
| | | <u-icon |
| | | name="checkmark" |
| | | size="24" |
| | | :color="selectedGoods && selectedGoods.id === goods.id ? '#2979ff' : '#ccc'" |
| | | <u-icon |
| | | v-if="goods.selected" |
| | | name="checkmark" |
| | | size="36" |
| | | :color="'#2979ff'" |
| | | ></u-icon> |
| | | </view> |
| | | </scroll-view> |
| | | </view> |
| | | </u-popup> |
| | | |
| | | |
| | | <custom-tabbar bgColor="#ffffff" selected="video"></custom-tabbar> |
| | | </view> |
| | | </template> |
| | | |
| | | <script> |
| | | import '@/components/uview-components/uview-ui'; |
| | | import MyTag from '@/components/my-tag.vue' |
| | | |
| | | import { getSTSToken, getFilePreviewUrl } from "@/api/common.js"; |
| | | import { publish } from "@/api/video.js"; |
| | | import { getRecommendTag3 } from "@/api/video-tag.js"; |
| | | import { getFileKey } from "@/utils/file.js"; |
| | | import { getVideoGoodsList } from "@/api/goods.js"; |
| | | |
| | | export default { |
| | | components: {MyTag}, |
| | | data() { |
| | | return { |
| | | fileTypeShow: false, |
| | | cosClient: null, |
| | | bucket: '', |
| | | region: '', |
| | | endpoint: '', |
| | | videoUploadProgress: 0, |
| | | loading: false, |
| | | showGoodsPicker: false, |
| | | goodsSearch: '', |
| | | tagInput: '', |
| | | videoPreviewImgs: [], // 预览图片地址 |
| | | videoInfo: { |
| | | url: '', |
| | | cover: '', |
| | | duration: 0, |
| | | size: 0 |
| | | }, |
| | | url: '', |
| | | fileKey: '', |
| | | fileType: '', |
| | | fileSize: 0, |
| | | originalFileName: '', |
| | | cover: '' |
| | | }, |
| | | goodsQuery: { |
| | | keyword: '', |
| | | searchFromSelfStore: false, // 是否是查询自家店铺商品 |
| | | pageNumber: 1, |
| | | pageSize: 5 |
| | | }, |
| | | formData: { |
| | | id: '', |
| | | title: '', |
| | | goodsLink: '', |
| | | tags: [] |
| | | cover: '', |
| | | videoFileKey: '', |
| | | videoDuration: 0, |
| | | videoFit: 'cover', |
| | | goodsId: '', |
| | | videoContentType: 'video', |
| | | videoImgs: [], |
| | | tags: [], |
| | | fileInfo: {} |
| | | }, |
| | | selectedGoods: null, |
| | | goodsList: [ |
| | | { |
| | | id: '1', |
| | | name: '新款无线蓝牙耳机', |
| | | price: '199.00', |
| | | image: 'https://via.placeholder.com/100' |
| | | }, |
| | | { |
| | | id: '2', |
| | | name: '智能手环运动手表', |
| | | price: '299.00', |
| | | image: 'https://via.placeholder.com/100' |
| | | }, |
| | | { |
| | | id: '3', |
| | | name: '便携式充电宝10000mAh', |
| | | price: '99.00', |
| | | image: 'https://via.placeholder.com/100' |
| | | }, |
| | | { |
| | | id: '4', |
| | | name: '高清广角手机镜头', |
| | | price: '59.00', |
| | | image: 'https://via.placeholder.com/100' |
| | | }, |
| | | { |
| | | id: '5', |
| | | name: '多功能折叠键盘', |
| | | price: '159.00', |
| | | image: 'https://via.placeholder.com/100' |
| | | } |
| | | ], |
| | | hotTopics: [ |
| | | '#五一旅行打卡', |
| | | '#美食探店', |
| | | '#科技新品', |
| | | '#健身日常', |
| | | '#宠物萌宠' |
| | | ], |
| | | recommendedTopics: [], |
| | | selectedGoodsList: [], |
| | | goodsList: [], |
| | | noMoreGoods: false, // 没有更多商品了 |
| | | recommendedTags: [], |
| | | rules: { |
| | | title: [ |
| | | { required: true, message: '请输入视频标题', trigger: 'blur' }, |
| | | { min: 2, max: 30, message: '标题长度在2到30个字符', trigger: 'blur' } |
| | | ], |
| | | goodsLink: [ |
| | | { required: true, message: '请选择商品链接', trigger: 'change' } |
| | | ], |
| | | tags: [ |
| | | { validator: (rule, value, callback) => { |
| | | if (value.length === 0) { |
| | | callback(new Error('请至少添加一个话题')); |
| | | } else { |
| | | callback(); |
| | | } |
| | | }, trigger: 'change' } |
| | | { min: 1, max: 20, message: '标题长度在1到20个字符', trigger: 'blur' } |
| | | ] |
| | | } |
| | | }, |
| | | screenWidth: 375, |
| | | gap: 10 // 图片间距 |
| | | }; |
| | | }, |
| | | computed: { |
| | | canPublish() { |
| | | return this.videoInfo.url && this.formData.title && this.formData.goodsLink && this.formData.tags.length > 0; |
| | | }, |
| | | filteredGoods() { |
| | | if (!this.goodsSearch) return this.goodsList; |
| | | return this.goodsList.filter(goods => |
| | | goods.name.toLowerCase().includes(this.goodsSearch.toLowerCase()) |
| | | ); |
| | | 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; |
| | | } |
| | | }, |
| | | showTopicRecommendations() { |
| | | return (this.tagInput === '' || this.recommendedTopics.length > 0) && this.formData.tags.length < 5; |
| | | } |
| | | return (this.tagInput === '' || this.recommendedTags.length > 0) && this.formData.tags.length < 5; |
| | | }, |
| | | // 计算每个图片项的宽度(考虑间距) |
| | | itemWidth() { |
| | | return (this.screenWidth - (this.gap * 4) - 20) / 3 |
| | | } |
| | | }, |
| | | created() { |
| | | // 初始化推荐话题 |
| | | this.recommendedTopics = this.hotTopics.slice(0, 3); |
| | | onLoad() { |
| | | // 获取屏幕宽度 |
| | | const systemInfo = uni.getSystemInfoSync() |
| | | this.screenWidth = systemInfo.windowWidth |
| | | this.goodsQuery.pageNumber = 1 |
| | | this.goodsQuery.pageSize = 10 |
| | | this.getVideoGoodsByEs() |
| | | }, |
| | | onShow() { |
| | | this.initCOS() |
| | | // 初始化推荐标签 |
| | | this.getRecommendTags() |
| | | }, |
| | | methods: { |
| | | // 加载更多商品 |
| | | loadMoreGoods() { |
| | | if(this.noMoreGoods) { |
| | | return |
| | | } |
| | | this.goodsQuery.pageNumber += 1; |
| | | this.goodsQuery.pageSize = 5; |
| | | this.getVideoGoodsByEs() |
| | | }, |
| | | // 处理商品搜索值 |
| | | handlerGoodsSearch() { |
| | | this.goodsQuery.pageNumber = 1 |
| | | this.goodsQuery.pageSize = 10 |
| | | this.getVideoGoodsByEs() |
| | | }, |
| | | // 获取商品分页 |
| | | async getVideoGoodsByEs() { |
| | | getVideoGoodsList(this.goodsQuery).then(res => { |
| | | |
| | | if(this.goodsQuery.pageNumber === 1) { |
| | | this.goodsList = res.data.data |
| | | } else { |
| | | this.goodsList = [ |
| | | ...this.goodsList, |
| | | ...res.data.data.filter( |
| | | (newItem) => !this.goodsList.some((oldItem) => oldItem.id === newItem.id) |
| | | ), |
| | | ]; |
| | | } |
| | | if(res.data.data.length < this.goodsQuery.pageSize) { |
| | | this.noMoreGoods = true; |
| | | } |
| | | }) |
| | | }, |
| | | // 获取推荐标签 |
| | | async getRecommendTags(type) { |
| | | const params = { |
| | | tagName: this.tagInput.trim(), |
| | | searchType: type |
| | | } |
| | | getRecommendTag3(params).then(res => { |
| | | this.recommendedTags = res.data.data |
| | | }) |
| | | }, |
| | | // 初始化腾讯云cos客户端 |
| | | initCOS() { |
| | | // 调用后端获取sts临时访问凭证 |
| | | 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 |
| | | SecretKey: res.data.data.tmpSecretKey, // sts 服务下发的临时 secretKey |
| | | SecurityToken: res.data.data.sessionToken, // sts 服务下发的临时 SessionToken |
| | | StartTime: res.data.data.stsStartTime, // 建议传入服务端时间,可避免客户端时间不准导致的签名错误 |
| | | ExpiredTime: res.data.data.stsEndTime, // 临时密钥过期时间 |
| | | SimpleUploadMethod: 'putObject', // 强烈建议,高级上传、批量上传内部对小文件做简单上传时使用 putObject,sdk 版本至少需要v1.3.0 |
| | | }); |
| | | 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, |
| | | camera: 'back', |
| | | success: (res) => { |
| | | this.videoInfo = { |
| | | url: res.tempFilePath, |
| | | duration: res.duration, |
| | | size: res.size, |
| | | cover: res.thumbTempFilePath |
| | | }; |
| | | this.videoUploadProgress = 0 |
| | | // 获取文件名 |
| | | const tempPath = res.tempFilePath; |
| | | let fileName = tempPath.substring(tempPath.lastIndexOf('/') + 1); |
| | | |
| | | // 处理安卓可能的URI编码 |
| | | if(fileName.indexOf('%') > -1) { |
| | | fileName = decodeURIComponent(fileName); |
| | | } |
| | | const fileKey = getFileKey(fileName); |
| | | this.videoInfo = { |
| | | url: res.tempFilePath, |
| | | fileKey: fileKey, |
| | | fileType: fileKey.split('/')[0], |
| | | fileSize: res.size, |
| | | originalFileName: fileName, |
| | | cover: '' |
| | | }; |
| | | this.formData.videoFileKey = fileKey; |
| | | 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, |
| | | SliceSize: 1024 * 1024 * 5, /* 触发分块上传的阈值,5M */ |
| | | onProgress: (progressData) => { |
| | | console.log(progressData.percent); |
| | | this.videoUploadProgress = progressData.percent * 100 |
| | | } |
| | | }, (err, data) => { |
| | | if (err) { |
| | | console.log('上传失败', err); |
| | | this.videoInfo = { |
| | | url: '', |
| | | fileKey: '', |
| | | fileType: '', |
| | | fileSize: 0, |
| | | originalFileName: '', |
| | | cover: '' |
| | | } |
| | | } else { |
| | | console.log(this.videoInfo); |
| | | } |
| | | }); |
| | | }, |
| | | fail: (err) => { |
| | | uni.showToast({ |
| | | title: '选择视频失败', |
| | | title: '未选择视频', |
| | | icon: 'none' |
| | | }); |
| | | console.error(err); |
| | | } |
| | | }); |
| | | }, |
| | | |
| | | // 根据宽高比选择视频填充模式 |
| | | 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 |
| | | url: '', |
| | | fileKey: '', |
| | | fileType: '', |
| | | fileSize: 0, |
| | | originalFileName: '', |
| | | cover: '' |
| | | }; |
| | | this.chooseVideo(); |
| | | this.formData.videoFileKey = '' |
| | | this.formData.cover = '' |
| | | this.formData.videoFit = 'cover' |
| | | this.formData.videoDuration = 0 |
| | | this.formData.videoImgs = [] |
| | | this.formData.fileInfo = {} |
| | | this.formData.videoContentType = 'video' |
| | | this.videoPreviewImgs = [] |
| | | 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); |
| | | // 处理安卓可能的URI编码 |
| | | 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({ |
| | |
| | | sizeType: ['compressed'], |
| | | sourceType: ['album'], |
| | | success: (res) => { |
| | | let fileName = res.tempFilePaths[0].substring(res.tempFilePaths[0].lastIndexOf('/') + 1); |
| | | // 处理安卓可能的URI编码 |
| | | if(fileName.indexOf('%') > -1) { |
| | | fileName = decodeURIComponent(fileName); |
| | | } |
| | | const fileKey = getFileKey(fileName); |
| | | this.videoInfo.cover = res.tempFilePaths[0]; |
| | | this.cosClient.uploadFile({ |
| | | Bucket: this.bucket, |
| | | Region: this.region, |
| | | Key: fileKey, |
| | | FilePath: res.tempFilePaths[0], |
| | | SliceSize: 1024 * 1024 * 5 /* 触发分块上传的阈值,5M */ |
| | | }, (err, data) => { |
| | | if (err) { |
| | | console.log('上传失败', err); |
| | | } else { |
| | | this.videoInfo.cover = this.endpoint + '/' + fileKey |
| | | this.formData.cover = fileKey |
| | | } |
| | | }); |
| | | } |
| | | }); |
| | | }, |
| | | |
| | | |
| | | // 选择商品 |
| | | chooseGoods() { |
| | | if(this.selectedGoodsList.length > 0) { |
| | | const selectedGoodsIds = new Set(this.selectedGoodsList.map(i => i.goodsId)); |
| | | console.log(selectedGoodsIds, "mimade"); |
| | | this.goodsList?.forEach(goods => { |
| | | this.$set(goods, 'selected', selectedGoodsIds.has(goods.goodsId)); |
| | | }); |
| | | } |
| | | this.showGoodsPicker = true; |
| | | }, |
| | | |
| | | |
| | | // 选择具体商品 |
| | | selectGoods(goods) { |
| | | this.selectedGoods = goods; |
| | | this.formData.goodsLink = `https://example.com/goods/${goods.id}`; |
| | | this.showGoodsPicker = false; |
| | | selectGoods(goods, index) { |
| | | if(! this.selectedGoodsList.some(item => item.id === goods.id)) { |
| | | goods["goodsNum"] = 1 |
| | | this.selectedGoodsList.push(goods) |
| | | this.goodsList[index].selected = true |
| | | } else { |
| | | this.goodsList[index].selected = false |
| | | this.selectedGoodsList = this.selectedGoodsList.filter(item => item.id !== goods.id); |
| | | } |
| | | }, |
| | | |
| | | |
| | | // 清除商品 |
| | | clearGoods() { |
| | | this.selectedGoods = null; |
| | | this.formData.goodsLink = ''; |
| | | clearGoods(goods) { |
| | | this.selectedGoodsList = this.selectedGoodsList.filter(item => item.id !== goods.id); |
| | | this.goodsList.forEach(item => { |
| | | if(item.id === goods.id) { |
| | | item.selected = false |
| | | } |
| | | }) |
| | | }, |
| | | |
| | | |
| | | // 搜索热门话题 |
| | | searchHotTopics() { |
| | | if (this.tagInput.trim() === '') { |
| | | // 显示热门话题 |
| | | this.recommendedTopics = this.hotTopics.slice(0, 3); |
| | | } else { |
| | | // 模拟搜索推荐话题 |
| | | const input = this.tagInput.trim().toLowerCase(); |
| | | this.recommendedTopics = this.hotTopics |
| | | .filter(topic => topic.toLowerCase().includes(input)) |
| | | .slice(0, 3); |
| | | searchTags() { |
| | | if (this.tagInput.trim() !== '') { |
| | | this.getRecommendTags("SEARCH") |
| | | } |
| | | }, |
| | | |
| | | // 添加标签 |
| | | addTag() { |
| | | const newTag = this.tagInput.trim(); |
| | | if(!this.tagInput.trim()) { |
| | | return |
| | | } |
| | | const newTag = {'id': '', 'tagName': this.tagInput.trim()}; |
| | | if (newTag && this.formData.tags.length < 5) { |
| | | if (!this.formData.tags.includes(newTag)) { |
| | | if (this.formData.tags.filter(item => item.tagName === newTag.tagName).length < 1) { |
| | | this.formData.tags.push(newTag); |
| | | this.tagInput = ''; |
| | | this.recommendedTopics = this.hotTopics.slice(0, 3); // 重置推荐 |
| | | this.getRecommendTags() // 重置推荐 |
| | | } else { |
| | | uni.showToast({ |
| | | title: '话题已添加', |
| | | title: '该话题已添加过了~', |
| | | icon: 'none' |
| | | }); |
| | | } |
| | | } else if (this.formData.tags.length >= 5) { |
| | | uni.showToast({ |
| | | title: '最多添加5个话题', |
| | | title: '最多添加5个话题~', |
| | | icon: 'none' |
| | | }); |
| | | } |
| | | }, |
| | | |
| | | |
| | | // 选择推荐话题 |
| | | selectTopic(topic) { |
| | | selectTopic(index) { |
| | | const tag = this.recommendedTags[index] |
| | | if (this.formData.tags.length >= 5) { |
| | | uni.showToast({ |
| | | title: '最多添加5个话题', |
| | | title: '最多添加5个话题~', |
| | | icon: 'none' |
| | | }); |
| | | return; |
| | | } |
| | | |
| | | if (!this.formData.tags.includes(topic)) { |
| | | this.formData.tags.push(topic); |
| | | |
| | | if (this.formData.tags.filter(item => item.tagName === tag.tagName).length < 1) { |
| | | this.formData.tags.push(tag); |
| | | this.tagInput = ''; |
| | | } else { |
| | | uni.showToast({ |
| | | title: '话题已添加', |
| | | title: '该话题已添加过了~', |
| | | icon: 'none' |
| | | }); |
| | | } |
| | | }, |
| | | |
| | | |
| | | // 移除标签 |
| | | removeTag(index) { |
| | | this.formData.tags.splice(index, 1); |
| | | }, |
| | | |
| | | |
| | | // 处理发布 |
| | | handlePublish() { |
| | | this.$refs.formRef.validate(valid => { |
| | | if (valid && this.canPublish) { |
| | | this.loading = true; |
| | | |
| | | // 这里应该是上传视频到服务器的逻辑 |
| | | // 模拟上传过程 |
| | | setTimeout(() => { |
| | | this.loading = false; |
| | | uni.showToast({ |
| | | title: '发布成功', |
| | | icon: 'success' |
| | | }); |
| | | |
| | | // 重置表单 |
| | | this.videoInfo = { |
| | | url: '', |
| | | cover: '', |
| | | duration: 0, |
| | | size: 0 |
| | | }; |
| | | this.formData = { |
| | | title: '', |
| | | goodsLink: '', |
| | | tags: [] |
| | | }; |
| | | this.selectedGoods = null; |
| | | this.tagInput = ''; |
| | | this.recommendedTopics = this.hotTopics.slice(0, 3); |
| | | |
| | | // 返回上一页 |
| | | setTimeout(() => { |
| | | uni.navigateBack(); |
| | | }, 1500); |
| | | }, 2000); |
| | | this.formData.fileInfo = this.videoInfo; |
| | | this.formData["goodsList"] = this.selectedGoodsList.map(item => {return {goodsId: item.goodsId, goodsNum: item.goodsNum}}); |
| | | publish(this.formData).then(res => { |
| | | uni.showToast({ |
| | | title: '视频已提交审核~', |
| | | icon: 'success' |
| | | }); |
| | | this.loading = false |
| | | // 重置表单 |
| | | this.resetData(); |
| | | this.selectedGoods = null; |
| | | this.tagInput = ''; |
| | | this.recommendedTags = []; |
| | | |
| | | // TODO 先跳首页,后面跳我的视频页面 |
| | | setTimeout(() => { |
| | | uni.switchTab({ |
| | | url: '/pages/tabbar/index/home' |
| | | }); |
| | | }, 1500); |
| | | }) |
| | | } else { |
| | | uni.showToast({ |
| | | title: '请完善视频信息', |
| | | title: '请完善视频信息~', |
| | | icon: 'none' |
| | | }); |
| | | } |
| | | }); |
| | | } |
| | | }, |
| | | 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 = [] |
| | | this.selectedGoodsList = [] |
| | | } |
| | | } |
| | | }; |
| | | </script> |
| | | |
| | | <style lang="scss" scoped> |
| | | <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; |
| | |
| | | } |
| | | |
| | | .video-actions { |
| | | width: 100%; |
| | | margin-top: 20rpx; |
| | | display: flex; |
| | | justify-content: center; |
| | | align-items: center; |
| | | gap: 20rpx; |
| | | } |
| | | |
| | |
| | | background-color: #f9f9f9; |
| | | border-radius: 8rpx; |
| | | margin-top: 15rpx; |
| | | |
| | | .goods-image { |
| | | width: 80rpx; |
| | | height: 80rpx; |
| | | border-radius: 8rpx; |
| | | margin-right: 15rpx; |
| | | } |
| | | |
| | | .goods-info { |
| | | flex: 1; |
| | | display: flex; |
| | | flex-direction: column; |
| | | |
| | | .goods-name { |
| | | font-size: 26rpx; |
| | | color: #333; |
| | | overflow: hidden; |
| | | text-overflow: ellipsis; |
| | | white-space: nowrap; |
| | | } |
| | | |
| | | .goods-price { |
| | | font-size: 28rpx; |
| | | color: #f44; |
| | | font-weight: bold; |
| | | } |
| | | } |
| | | position: relative; |
| | | } |
| | | |
| | | .goods-preview .goods-image { |
| | | width: 80rpx; |
| | | height: 80rpx; |
| | | border-radius: 8rpx; |
| | | margin-right: 15rpx; |
| | | } |
| | | |
| | | .goods-preview .goods-info { |
| | | flex: 1; |
| | | display: flex; |
| | | flex-direction: column; |
| | | } |
| | | |
| | | .goods-preview .goods-info .goods-name { |
| | | font-size: 26rpx; |
| | | color: #333; |
| | | overflow: hidden; |
| | | text-overflow: ellipsis; |
| | | white-space: nowrap; |
| | | } |
| | | |
| | | .goods-preview .goods-info .goods-price { |
| | | font-size: 28rpx; |
| | | color: #f44; |
| | | font-weight: bold; |
| | | } |
| | | |
| | | .topic-list { |
| | | display: flex; |
| | | flex-direction: row; |
| | | flex-wrap: wrap; |
| | | line-height: 22px; |
| | | } |
| | | |
| | | .tags-input-container { |
| | | width: 100%; |
| | | |
| | | .tags-display { |
| | | display: flex; |
| | | flex-wrap: wrap; |
| | | margin-top: 15rpx; |
| | | } |
| | | |
| | | .hot-topics { |
| | | margin-top: 15rpx; |
| | | |
| | | .section-title { |
| | | font-size: 24rpx; |
| | | color: #999; |
| | | display: block; |
| | | margin-bottom: 10rpx; |
| | | } |
| | | |
| | | .topic-list { |
| | | display: flex; |
| | | flex-wrap: wrap; |
| | | } |
| | | } |
| | | |
| | | .tags-count { |
| | | display: block; |
| | | font-size: 24rpx; |
| | | color: #999; |
| | | margin-top: 10rpx; |
| | | text-align: right; |
| | | } |
| | | } |
| | | |
| | | .tags-display { |
| | | display: flex; |
| | | flex-wrap: wrap; |
| | | margin-top: 15rpx; |
| | | line-height: 22px; |
| | | } |
| | | |
| | | .hot-topics { |
| | | display: flex; |
| | | flex-direction: column; |
| | | margin-top: 15rpx; |
| | | margin-bottom: 15rpx; |
| | | } |
| | | |
| | | .section-title { |
| | | font-size: 12px; |
| | | color: #999; |
| | | line-height: 12px; |
| | | margin-bottom: 6rpx; |
| | | } |
| | | |
| | | .tags-count { |
| | | display: block; |
| | | font-size: 12px; |
| | | line-height: 12px; |
| | | color: #999; |
| | | margin-top: 10rpx; |
| | | text-align: right; |
| | | } |
| | | |
| | | .publish-btn { |
| | | position: fixed; |
| | | /* position: fixed; |
| | | bottom: 100rpx; |
| | | left: 20rpx; |
| | | right: 20rpx; |
| | | right: 20rpx; */ |
| | | margin-top: 40rpx; |
| | | } |
| | | |
| | | .goods-picker { |
| | |
| | | height: 100%; |
| | | display: flex; |
| | | flex-direction: column; |
| | | |
| | | .picker-header { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | margin-bottom: 30rpx; |
| | | |
| | | .picker-title { |
| | | font-size: 36rpx; |
| | | font-weight: bold; |
| | | } |
| | | } |
| | | |
| | | .search-bar { |
| | | margin-bottom: 20rpx; |
| | | } |
| | | |
| | | .goods-list { |
| | | flex: 1; |
| | | overflow: hidden; |
| | | |
| | | .goods-item { |
| | | display: flex; |
| | | align-items: center; |
| | | padding: 20rpx 0; |
| | | border-bottom: 1rpx solid #f5f5f5; |
| | | |
| | | .goods-image { |
| | | width: 100rpx; |
| | | height: 100rpx; |
| | | border-radius: 8rpx; |
| | | margin-right: 20rpx; |
| | | } |
| | | |
| | | .goods-info { |
| | | flex: 1; |
| | | |
| | | .goods-name { |
| | | font-size: 28rpx; |
| | | color: #333; |
| | | margin-bottom: 10rpx; |
| | | display: -webkit-box; |
| | | -webkit-line-clamp: 2; |
| | | -webkit-box-orient: vertical; |
| | | overflow: hidden; |
| | | } |
| | | |
| | | .goods-price { |
| | | font-size: 28rpx; |
| | | color: #f44; |
| | | font-weight: bold; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| | | </style> |
| | | |
| | | .goods-picker .picker-header { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | margin-bottom: 30rpx; |
| | | } |
| | | |
| | | .goods-picker .picker-header .picker-title { |
| | | font-size: 36rpx; |
| | | font-weight: bold; |
| | | } |
| | | |
| | | .goods-picker .search-bar { |
| | | margin-bottom: 20rpx; |
| | | } |
| | | |
| | | .goods-picker .goods-list { |
| | | flex: 1; |
| | | overflow: hidden; |
| | | } |
| | | |
| | | .goods-picker .goods-list .goods-item { |
| | | display: flex; |
| | | align-items: center; |
| | | padding: 20rpx 0; |
| | | border-bottom: 1rpx solid #f5f5f5; |
| | | } |
| | | |
| | | .goods-picker .goods-list .goods-item .goods-image { |
| | | width: 100rpx; |
| | | height: 100rpx; |
| | | border-radius: 8rpx; |
| | | margin-right: 20rpx; |
| | | } |
| | | |
| | | .goods-picker .goods-list .goods-item .goods-info { |
| | | flex: 1; |
| | | } |
| | | |
| | | .goods-picker .goods-list .goods-item .goods-info .goods-name { |
| | | font-size: 28rpx; |
| | | color: #333; |
| | | margin-bottom: 10rpx; |
| | | display: -webkit-box; |
| | | -webkit-line-clamp: 2; |
| | | -webkit-box-orient: vertical; |
| | | overflow: hidden; |
| | | } |
| | | |
| | | .goods-picker .goods-list .goods-item .goods-info .goods-price { |
| | | font-size: 28rpx; |
| | | color: #f44; |
| | | font-weight: bold; |
| | | } |
| | | |
| | | .progress-box { |
| | | width: 100%; |
| | | display: flex; |
| | | height: 25px; |
| | | margin-top: 10px; |
| | | } |
| | | |
| | | .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> |