Merge remote-tracking branch 'origin/dev' into dev
| | |
| | | }) |
| | | } |
| | | |
| | | export function upGoods(params) { |
| | | return http.request({ |
| | | url: api.store + `/goods/goods/up`, |
| | | needToken: true, |
| | | method: Method.PUT, |
| | | params: params |
| | | }) |
| | | } |
| | | export function lowGoods(params) { |
| | | return http.request({ |
| | | url: api.store + `/goods/goods/under`, |
| | | needToken: true, |
| | | method: Method.PUT, |
| | | params: params |
| | | }) |
| | | } |
| | | export function getGoodsSkuData(params) { |
| | | return http.request({ |
| | | url: api.store + '/goods/goods/sku/list', |
| | | needToken: true, |
| | | method: Method.GET, |
| | | params: params |
| | | }) |
| | | } |
| | | |
| | | |
| | | export function updateStocks(params) { |
| | | return http.request({ |
| | | url: api.store + `/goods/goods/update/stocks`, |
| | | needToken: true, |
| | | method: Method.PUT, |
| | | header: { "content-type": "application/json" }, |
| | | data: params |
| | | }) |
| | | } |
| | | |
| | |
| | | method: Method.GET, |
| | | needToken: true |
| | | }); |
| | | } |
| | | |
| | | |
| | | /** |
| | | * 删除视频 |
| | | * |
| | | * @param params |
| | | */ |
| | | export function delVideo(id) { |
| | | return http.request({ |
| | | url: "/lmk/video/" + id, |
| | | method: Method.DELETE, |
| | | needToken: true |
| | | }); |
| | | } |
| | | |
| | | /** |
| | | * 修改视频 |
| | | * |
| | | * @param params |
| | | */ |
| | | export function updateVideo(data) { |
| | | return http.request({ |
| | | url: "/lmk/video", |
| | | method: Method.PUT, |
| | | needToken: true, |
| | | data: data |
| | | }); |
| | | } |
| | | |
| | | /** |
| | | * 用户下架视频 |
| | | * |
| | | * @param params |
| | | */ |
| | | export function userDownVideo(id) { |
| | | return http.request({ |
| | | url: "/lmk/video/down/" + id, |
| | | method: Method.POST, |
| | | needToken: true |
| | | }); |
| | | } |
| | |
| | | export default { |
| | | name: 'DropdownMenu', |
| | | props: { |
| | | // 业务数据,选中菜单后一同返回 |
| | | data: { |
| | | type: Object |
| | | }, |
| | | // 选项列表 |
| | | options: { |
| | | type: Array, |
| | |
| | | // 根据配置返回整个对象或value值 |
| | | const emitValue = typeof item === 'object' ? item[this.valueKey] : item |
| | | this.$emit('input', emitValue) |
| | | this.$emit('change', emitValue) |
| | | this.$emit('change', emitValue, this.data) |
| | | } |
| | | } |
| | | } |
| | |
| | | |
| | | .dropdown-menu { |
| | | position: absolute; |
| | | left: 0; |
| | | left: -50rpx; |
| | | display: inline-block; |
| | | white-space: nowrap; |
| | | background-color: #fff; |
| | |
| | | "easycom": { |
| | | "autoscan": true, |
| | | "custom": { |
| | | "^u-(.*)": "@/components/uview-components/uview-ui/components/u-$1/u-$1.vue", //uview, |
| | | "^uni-(.*)": "@/uni_modules/uni-$1/components/uni-$1/uni-$1.vue" // uniapp组件 |
| | | "^u-(.*)": "@/components/uview-components/uview-ui/components/u-$1/u-$1.vue" //uview, |
| | | // "^uni-(.*)": "@/uni_modules/uni-$1/components/uni-$1/uni-$1.vue" // uniapp组件 |
| | | } |
| | | }, |
| | | // "preloadRule": { |
| | |
| | | "navigationStyle": "custom" // 隐藏顶部导航栏 |
| | | } |
| | | }, |
| | | // { |
| | | // "path": "pages/tabbar/index/home1", |
| | | // "style": { |
| | | // "navigationBarTitleText": "" |
| | | // } |
| | | // }, |
| | | { |
| | | "path": "pages/tabbar/home/index", |
| | | "style": { |
| | |
| | | } |
| | | }, |
| | | { |
| | | "path": "pages/goods-manager/updateSkus/updateSkus", |
| | | "style": { |
| | | "enablePullDownRefresh": true, |
| | | "navigationBarTitleText": "调整库存", |
| | | "componentPlaceholder": { |
| | | "u-icon": "view", |
| | | "u-button": "view", |
| | | "u-form": "view", |
| | | "u-form-item": "view", |
| | | "u-input": "view", |
| | | "u-popup": "view" |
| | | } |
| | | } |
| | | }, |
| | | { |
| | | "path": "pages/userPermissions/userPermissions", |
| | | "style": { |
| | | "navigationBarTitleText": "用户权限", |
| | |
| | | <!-- 通过 uni-list--waterfall 类决定页面布局方向 --> |
| | | <!-- to 属性携带参数跳转详情页面,当前只为参考 --> |
| | | <view :border="!formData.waterfall" class="uni-list-item--waterfall" title="自定义商品列表" |
| | | v-for="item in data" :key="item.id" @click="addGoods(item.id)"> |
| | | v-for="item in data" :key="item.id" @click="toggle(item)"> |
| | | <!-- 通过header插槽定义列表左侧图片 --> |
| | | |
| | | <view class="uni-thumb shop-picture" :class="{ 'shop-picture-column': formData.waterfall }"> |
| | |
| | | item.buyCount || 0 }} |
| | | </view> |
| | | <view class="uni-note ellipsis"> |
| | | <text class="uni-link">上架</text> |
| | | <text :class="item.marketEnable == 'DOWN' ? 'market-down' : 'market-up'">{{ |
| | | item.marketEnable == 'DOWN' ? "已下架" : "已上架" }}</text> |
| | | |
| | | <text class="uni-link" style="color:red;">删除</text> |
| | | <!--<text class="uni-link" style="color:red;">删除</text> --> |
| | | </view> |
| | | </view> |
| | | </view> |
| | |
| | | |
| | | </view> |
| | | </view> |
| | | <view style="height: 80px;"></view> |
| | | </view> |
| | | |
| | | <button type="default" class="btn" @click="addGoods('')"> |
| | |
| | | </button> |
| | | <!-- 通过 loadMore 组件实现上拉加载效果,如需自定义显示内容,可参考:https://ext.dcloud.net.cn/plugin?id=29 --> |
| | | <uni-load-more v-if="loading || formData.status === 'noMore'" :status="formData.status" /> |
| | | <view> |
| | | <!-- 普通弹窗 --> |
| | | <uni-popup ref="popup" background-color="#fff"> |
| | | <view class="popup-content" :class="{ 'popup-height': type === 'left' || type === 'right' }"> |
| | | |
| | | <button type="default" @click="underOrup">{{ selectGoods.marketEnable == 'DOWN' ? '上架' : "下架" |
| | | }}</button> |
| | | |
| | | <button type="default" @click="stocks">调整库存</button> |
| | | </view> |
| | | </uni-popup> |
| | | </view> |
| | | |
| | | </view> |
| | | </template> |
| | |
| | | pageNumber: 1, |
| | | pageSize: 10, |
| | | }, |
| | | data: [ |
| | | { |
| | | "id": 122, |
| | | "goods_thumb": "https://lilishop-oss.oss-cn-beijing.aliyuncs.com/83df593e6ba448ddbe6685d928e6fa65.jpg", |
| | | "name": "【爆款】Apple iPhone 11 (A2223) 64GB 深空灰色 移动联通电信4G手机", |
| | | "goods_price": "699.00", |
| | | "goods_tip": "热卖中", |
| | | "tag": [ |
| | | "热卖中", |
| | | ] |
| | | }, |
| | | { |
| | | "id": 123, |
| | | "goods_thumb": "https://lilishop-oss.oss-cn-beijing.aliyuncs.com/83df593e6ba448ddbe6685d928e6fa65.jpg", |
| | | "name": "【爆款】Apple iPhone 11 (A2223) 64GB 深空灰色 移动联通电信4G手机", |
| | | "goods_price": "699.00", |
| | | "goods_tip": "热卖中", |
| | | "tag": [ |
| | | "热卖中", |
| | | ] |
| | | } |
| | | ], |
| | | data: [], |
| | | formData: { |
| | | status: 'loading', // 加载状态 |
| | | }, |
| | | selectGoods: {}, |
| | | tipShow: false // 是否显示顶部提示框 |
| | | }; |
| | | }, |
| | |
| | | } |
| | | }, |
| | | methods: { |
| | | underOrup() { |
| | | let params = { |
| | | goodsId: this.selectGoods.id, |
| | | }; |
| | | this.$refs.popup.close() |
| | | if (this.selectGoods.marketEnable == 'DOWN') { |
| | | API_GOODS.upGoods(params).then((res) => { |
| | | uni.showLoading({ |
| | | title: '上架成功' |
| | | }); |
| | | |
| | | setTimeout(function () { |
| | | uni.hideLoading(); |
| | | }, 2000); |
| | | this.getGoodsList(); |
| | | }) |
| | | } |
| | | else { |
| | | API_GOODS.lowGoods(params).then((res) => { |
| | | uni.showLoading({ |
| | | title: '下架成功' |
| | | }); |
| | | |
| | | setTimeout(function () { |
| | | uni.hideLoading(); |
| | | }, 2000); |
| | | this.getGoodsList(); |
| | | }) |
| | | } |
| | | }, |
| | | stocks() { |
| | | |
| | | this.$refs.popup.close() |
| | | var goodsId = this.selectGoods.id |
| | | uni.navigateTo({ |
| | | url: `/pages/goods-manager/updateSkus/updateSkus${"?goodsId=" + goodsId}`, |
| | | }); |
| | | }, |
| | | toggle(item) { |
| | | this.$refs.popup.open('bottom') |
| | | this.selectGoods = item; |
| | | }, |
| | | |
| | | getGoodsList() { |
| | | uni.showLoading(); |
| | |
| | | // 小程序 编译后会多一层标签,而其他平台没有,所以需要特殊处理一下 |
| | | /deep/ .uni-list { |
| | | /* #endif */ |
| | | height: calc(100vh - 100px - 80px - 60px); |
| | | display: flex; |
| | | flex-direction: row; |
| | | flex-wrap: wrap; |
| | |
| | | margin-right: 10rpx; |
| | | } |
| | | } |
| | | |
| | | @mixin flex { |
| | | /* #ifndef APP-NVUE */ |
| | | display: flex; |
| | | /* #endif */ |
| | | flex-direction: row; |
| | | } |
| | | |
| | | @mixin height { |
| | | /* #ifndef APP-NVUE */ |
| | | height: 100%; |
| | | /* #endif */ |
| | | /* #ifdef APP-NVUE */ |
| | | flex: 1; |
| | | /* #endif */ |
| | | } |
| | | |
| | | .popup-content { |
| | | @include flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | padding: 15px; |
| | | height: 150px; |
| | | background-color: #fff; |
| | | } |
| | | |
| | | .popup-height { |
| | | @include height; |
| | | width: 200px; |
| | | } |
| | | |
| | | .market-down { |
| | | color: red; |
| | | } |
| | | |
| | | .market-up { |
| | | color: green; |
| | | } |
| | | </style> |
New file |
| | |
| | | <template> |
| | | <view> |
| | | <view class="container"> |
| | | <view class="item" v-for="item in skuList" :key="item.id"> |
| | | <text class="label">{{ item.simpleSpecs }}</text> |
| | | <input class="input" placeholder="请输入库存数量" :value="item.quantity" @input="onInput" |
| | | :data-id="item.id" /> |
| | | </view> |
| | | </view> |
| | | <button type="default" class="btn" @click="updateSkus()"> |
| | | <u-icon name="plus-circle"></u-icon> |
| | | 更新 |
| | | </button> |
| | | </view> |
| | | </template> |
| | | <script> |
| | | |
| | | import * as API_GOODS from "@/api/goods.js"; |
| | | export default { |
| | | data() { |
| | | return { |
| | | routerVal: {}, |
| | | skuParams: [], |
| | | skuList: [] |
| | | } |
| | | }, |
| | | onShow() { |
| | | var goodsId = this.routerVal.goodsId |
| | | let params = { |
| | | goodsId: goodsId, |
| | | pageSize: 100 |
| | | }; |
| | | API_GOODS.getGoodsSkuData(params).then((res) => { |
| | | this.skuList = res.data.result.records |
| | | res.data.result.records.forEach(item => { |
| | | this.skuParams.push({ skuId: item.id, quantity: item.quantity }) |
| | | }); |
| | | }) |
| | | }, |
| | | onLoad(option) { |
| | | uni.showLoading({ |
| | | title: "加载中", |
| | | }); |
| | | this.routerVal = option; |
| | | |
| | | uni.hideLoading(); |
| | | }, |
| | | methods: { |
| | | onInput(e) { |
| | | var id = e.currentTarget.dataset.id |
| | | for (let index = 0; index < this.skuParams.length; index++) { |
| | | if (this.skuParams[index].skuId == id) |
| | | this.skuParams[index].quantity = e.detail.value |
| | | } |
| | | }, |
| | | updateSkus() { |
| | | API_GOODS.updateStocks(this.skuParams).then((res) => { |
| | | if (res.data.code == 200) { |
| | | uni.showToast({ |
| | | title: "更新成功", |
| | | icon: "success", |
| | | duration: 2000, |
| | | }); |
| | | setTimeout(() => { |
| | | uni.navigateBack({ |
| | | delta: 1, |
| | | }); |
| | | }) |
| | | } |
| | | }) |
| | | } |
| | | } |
| | | } |
| | | </script> |
| | | <style lang="scss" scoped> |
| | | /* WXSS 文件 */ |
| | | .container { |
| | | padding: 20px; |
| | | } |
| | | |
| | | .item { |
| | | display: flex; |
| | | align-items: center; |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | .label { |
| | | width: 80px; |
| | | text-align: right; |
| | | margin-right: 15px; |
| | | } |
| | | |
| | | .input { |
| | | flex: 1; |
| | | border: 1px solid #ddd; |
| | | padding: 10px; |
| | | border-radius: 4px; |
| | | } |
| | | |
| | | .btn { |
| | | background: $light-color; |
| | | position: fixed; |
| | | width: 690rpx; |
| | | bottom: 60rpx; |
| | | height: 80rpx; |
| | | left: 30rpx; |
| | | font-size: 30rpx; |
| | | line-height: 80rpx; |
| | | |
| | | .u-icon { |
| | | margin-right: 10rpx; |
| | | } |
| | | } |
| | | </style> |
| | |
| | | .video-info { |
| | | width: 70%; |
| | | position: absolute; |
| | | bottom: 70px; |
| | | bottom: 20px; |
| | | left: 20px; |
| | | color: #f8f8f8; |
| | | z-index: 10; |
| | |
| | | <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.selectNum" :min="0"/> |
| | | <uni-number-box v-model="goods.goodsNum" :min="0"/> |
| | | </view> |
| | | </view> |
| | | </view> |
| | |
| | | }, |
| | | // 重新上传 |
| | | reUpload() { |
| | | this.resetData() |
| | | this.videoInfo = { |
| | | url: '', |
| | | fileKey: '', |
| | | fileType: '', |
| | | fileSize: 0, |
| | | originalFileName: '', |
| | | cover: '' |
| | | }; |
| | | 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 |
| | | }, |
| | | // 选择视频图集 |
| | |
| | | |
| | | // 选择商品 |
| | | 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, index) { |
| | | if(! this.selectedGoodsList.some(item => item.id === goods.id)) { |
| | | goods["selectNum"] = 1 |
| | | goods["goodsNum"] = 1 |
| | | this.selectedGoodsList.push(goods) |
| | | this.goodsList[index].selected = true |
| | | } else { |
| | |
| | | if (valid && this.canPublish) { |
| | | this.loading = true; |
| | | this.formData.fileInfo = this.videoInfo; |
| | | this.formData["goodsList"] = this.selectedGoodsList.map(item => {return {goodsId: item.goodsId, goodsNum: item.selectNum}}); |
| | | this.formData["goodsList"] = this.selectedGoodsList.map(item => {return {goodsId: item.goodsId, goodsNum: item.goodsNum}}); |
| | | publish(this.formData).then(res => { |
| | | uni.showToast({ |
| | | title: '视频已提交审核~', |
| | |
| | | |
| | | <!-- 视频列表 --> |
| | | <scroll-view class="video-list" scroll-y :show-scrollbar="false" @scrolltolower="getPage" v-show="currentTab === 'works' && videoList.length > 0"> |
| | | <view class="video-container"> |
| | | <view |
| | | class="video-item" |
| | | v-for="(item, index) in videoList" |
| | | :key="item.id" |
| | | > |
| | | <image class="video-cover" @click="playAuthorVideo(index)" :src="item.coverUrl" mode="aspectFill"></image> |
| | | <image class="video-cover" @click="playAuthorVideo(index)" :src="item.videoContentType === 'video' ? item.coverUrl : item.imgs[0]" mode="aspectFill"></image> |
| | | <view class="video-info"> |
| | | <view class="video-stats"> |
| | | <view class="stat"> |
| | | <uni-icons type="heart" size="16" color="#fff"></uni-icons> |
| | | <text>{{item.collectNum}}</text> |
| | | <view class="more-op"> |
| | | <view class="more-op" v-if="userInfo.self"> |
| | | <dropdown-menu |
| | | :options="item.options" |
| | | :data="{id: item.id, title: item.title}" |
| | | placement="top" |
| | | theme-color="#07C160" |
| | | @change="handleChange" |
| | |
| | | </view> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | </scroll-view> |
| | | <scroll-view class="video-list" scroll-y :show-scrollbar="false" @scrolltolower="getPage" v-show="currentTab === 'likes' && collectVideoList.length > 0"> |
| | |
| | | :key="item.id" |
| | | @click="playCollectVideo(index)" |
| | | > |
| | | <image class="video-cover" :src="item.coverUrl" mode="aspectFill"></image> |
| | | <image class="video-cover" :src="item.videoContentType === 'video' ? item.coverUrl : item.imgs[0]" mode="aspectFill"></image> |
| | | <view class="video-info"> |
| | | <view class="video-stats"> |
| | | <view class="stat"> |
| | |
| | | <!-- <image src="/static/images/empty.png" mode="aspectFit" class="empty-image"></image> --> |
| | | <text class="empty-text">还没有点赞作品哦~</text> |
| | | </view> |
| | | |
| | | <!-- 删除视频提醒框 --> |
| | | <uni-popup ref="delDialog" type="dialog"> |
| | | <uni-popup-dialog type="error" cancelText="取消" confirmText="删除" title="提醒" :content="`您正在删除:${opVideo.title}`" @confirm="deleteVideo" |
| | | @close="dialogClose"></uni-popup-dialog> |
| | | </uni-popup> |
| | | |
| | | <!-- 下架视频提醒框 --> |
| | | <uni-popup ref="downDialog" type="dialog"> |
| | | <uni-popup-dialog type="error" cancelText="取消" confirmText="下架" title="提醒" :content="`您正在下架:${opVideo.title}`" @confirm="downVideo" |
| | | @close="dialogClose"></uni-popup-dialog> |
| | | </uni-popup> |
| | | </view> |
| | | </template> |
| | | |
| | |
| | | import DropdownMenu from '@/components/dropdown-menu.vue' |
| | | |
| | | import {getAuthorInfo, getAuthorVideoPage, getAuthorCollectVideoPage} from '@/api/user.js' |
| | | import {subscribe, unSubscribe} from '@/api/video.js' |
| | | import {subscribe, unSubscribe, delVideo, updateVideo, userDownVideo} from '@/api/video.js' |
| | | export default { |
| | | components: {DropdownMenu}, |
| | | data() { |
| | | return { |
| | | options: [ |
| | | { command: 1, label: '北京' }, |
| | | { command: 2, label: '上海' }, |
| | | { command: 3, label: '广州' } |
| | | ], |
| | | options: [ |
| | | { command: 1, label: '北京' }, |
| | | { command: 2, label: '上海' }, |
| | | { command: 3, label: '广州' } |
| | | ], |
| | | opVideo: { // 正在操作的视频 |
| | | id: '', |
| | | title: '' |
| | | }, |
| | | currentTab: 'works', // works: 作品, likes: 喜欢 |
| | | authorId: '', |
| | | userInfo: { |
| | |
| | | this.getAuthorVideoPage(); |
| | | }, |
| | | methods: { |
| | | handleChange(value) { |
| | | console.log('选中值:', value) |
| | | }, |
| | | dialogClose() { |
| | | this.opVideo = { |
| | | id: '', |
| | | title: '' |
| | | } |
| | | }, |
| | | // 下架视频 |
| | | downVideo() { |
| | | userDownVideo(this.opVideo.id).then(res => { |
| | | uni.showToast({ |
| | | title: '下架成功', |
| | | duration: 2000 |
| | | }); |
| | | // 刷新数据 |
| | | this.videoList = []; |
| | | this.videoQuery.pageNumber = 1; |
| | | this.getAuthorVideoPage(); |
| | | }) |
| | | }, |
| | | // 删除视频 |
| | | deleteVideo() { |
| | | delVideo(this.opVideo.id).then(res => { |
| | | uni.showToast({ |
| | | title: '删除成功', |
| | | duration: 2000 |
| | | }); |
| | | // 刷新数据 |
| | | this.videoList = []; |
| | | this.videoQuery.pageNumber = 1; |
| | | this.getAuthorVideoPage(); |
| | | }) |
| | | }, |
| | | // 触发视频操作 |
| | | handleChange(value, data) { |
| | | console.log('选中值:', value) |
| | | this.opVideo.id = data.id; |
| | | this.opVideo.title = data.title; |
| | | if (value === 'DELETE') { |
| | | this.$refs.delDialog.open() |
| | | } else if (value === 'DOWN') { |
| | | this.$refs.downDialog.open() |
| | | } else if (value === 'EDIT') { |
| | | // 跳转编辑视频页面 |
| | | uni.navigateTo({ |
| | | url: `/pages/video/video-edit?id=${this.opVideo.id}` |
| | | }); |
| | | } |
| | | }, |
| | | getPage() { |
| | | if(this.currentTab === 'works') { |
| | | if(this.nomoreVideo) { |
| | |
| | | }); |
| | | }, |
| | | // 播放收藏视频 |
| | | playAuthorVideo(index) { |
| | | playCollectVideo(index) { |
| | | const playInfo = { |
| | | videoList: this.collectVideoList, |
| | | nomore: this.nomoreCollectVideo, |
| | |
| | | } |
| | | |
| | | .video-list { |
| | | width: calc(100% - 20rpx); |
| | | padding: 0 10rpx; |
| | | height: calc(100vh - 554rpx); |
| | | background-color: #fff; |
| | | width: 100%; |
| | | padding: 0 10rpx; |
| | | height: calc(100vh - 554rpx); |
| | | background-color: #fff; |
| | | } |
| | | |
| | | .video-container { |
| | | display: flex; |
| | | flex-wrap: wrap; |
| | | justify-content: space-between; |
| | | } |
| | | |
| | | .video-item { |
| | | width: 50%; |
| | | padding: 10rpx; |
| | | box-sizing: border-box; |
| | | width: 49%; |
| | | margin-bottom: 20rpx; |
| | | position: relative; |
| | | } |
| | | |
| | |
| | | } |
| | | |
| | | .video-info { |
| | | display: flex; |
| | | height: 60rpx; |
| | | align-items: center; |
| | | font-size: 24rpx; |
| | | width: 100%; |
| | | padding-right: 20rpx; |
| | | box-sizing: border-box; |
| | | position: absolute; |
| | | bottom: 20rpx; |
| | | bottom: 10rpx; |
| | | left: 0; |
| | | right: 0; |
| | | padding: 0 10rpx; |
| | | box-sizing: border-box; |
| | | } |
| | | |
| | | .video-title { |
| | |
| | | } |
| | | } |
| | | |
| | | .video-container { |
| | | display: flex; |
| | | flex-wrap: wrap |
| | | } |
| | | </style> |
| | |
| | | <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 || ''" |
| | | ></video> |
| | | <view class="progress-box"> |
| | | <view class="progress-box" v-if="showUploadProgress"> |
| | | <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">{{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" |
| | |
| | | ></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" |
| | |
| | | <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" |
| | |
| | | </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> |
| | | <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> |
| | |
| | | import MyTag from '@/components/my-tag.vue' |
| | | |
| | | import { getSTSToken, getFilePreviewUrl } from "@/api/common.js"; |
| | | import { publish, getVideoDetail } from "@/api/video.js"; |
| | | import { updateVideo, getVideoDetail } 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 { |
| | | showUploadProgress: false, |
| | | fileTypeShow: false, |
| | | cosClient: null, |
| | | bucket: '', |
| | | region: '', |
| | | endpoint: '', |
| | | videoUploadProgress: 0, |
| | | loading: false, |
| | | showGoodsPicker: false, |
| | | goodsSearch: '', |
| | | tagInput: '', |
| | | videoPreviewImgs: [], // 预览图片地址 |
| | | videoInfo: { |
| | | url: '', |
| | | fileKey: '', |
| | |
| | | fileSize: 0, |
| | | originalFileName: '', |
| | | cover: '' |
| | | }, |
| | | goodsQuery: { |
| | | keyword: '', |
| | | searchFromSelfStore: false, // 是否是查询自家店铺商品 |
| | | pageNumber: 1, |
| | | pageSize: 5 |
| | | }, |
| | | formData: { |
| | | id: '', |
| | |
| | | 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' |
| | | } |
| | | ], |
| | | selectedGoodsList: [], |
| | | goodsList: [], |
| | | noMoreGoods: false, // 没有更多商品了 |
| | | recommendedTags: [], |
| | | rules: { |
| | | title: [ |
| | | { 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; |
| | | }, |
| | | 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.recommendedTags.length > 0) && this.formData.tags.length < 5; |
| | | } |
| | | }, |
| | | // 计算每个图片项的宽度(考虑间距) |
| | | itemWidth() { |
| | | return (this.screenWidth - (this.gap * 4) - 20) / 3 |
| | | } |
| | | }, |
| | | onLoad(option) { |
| | | this.getVideoDetail(option.id) |
| | | this.getVDetail(option.id) |
| | | // 获取屏幕宽度 |
| | | const systemInfo = uni.getSystemInfoSync() |
| | | this.screenWidth = systemInfo.windowWidth |
| | | this.goodsQuery.pageNumber = 1 |
| | | this.goodsQuery.pageSize = 10 |
| | | this.getVideoGoodsByEs() |
| | | |
| | | }, |
| | | onShow() { |
| | | this.initCOS() |
| | |
| | | this.getRecommendTags() |
| | | }, |
| | | methods: { |
| | | getVideoDetail(id) { |
| | | getVideoDetail(id).then(res => { |
| | | this.fileInfo.cover = res.data.data.coverUrl |
| | | this.fileInfo.url = res.data.data.videoUrl |
| | | this.formData.cover = res.data.data.coverFileKey |
| | | this.formData.id = res.data.data.id |
| | | this.formData.title = res.data.data.title |
| | | this.formData.videoFileKey = res.data.data.videoFileKey |
| | | this.formData.videoFit = res.data.data.videoFit |
| | | this.formData.videoDuration = res.data.data.videoDuration |
| | | this.formData.goodsId = res.data.data.goodsId |
| | | this.formData.tags = res.data.data.tags |
| | | }).catch(() => { |
| | | uni.navigateBack({ |
| | | delta: 1 |
| | | }); |
| | | }) |
| | | getVDetail(id) { |
| | | getVideoDetail(id).then(res => { |
| | | this.videoInfo.cover = res.data.data.coverUrl |
| | | this.videoInfo.url = res.data.data.videoUrl |
| | | this.formData.videoImgs = res.data.data.imgs |
| | | this.formData.videoContentType = res.data.data.videoContentType |
| | | this.formData.cover = res.data.data.coverFileKey |
| | | this.formData.id = res.data.data.id |
| | | this.formData.title = res.data.data.title |
| | | this.formData.videoFileKey = res.data.data.videoFileKey |
| | | this.formData.videoFit = res.data.data.videoFit |
| | | this.formData.videoDuration = res.data.data.videoDuration |
| | | this.selectedGoodsList = res.data.data.goodsList |
| | | this.formData.tags = res.data.data.tags |
| | | this.showUploadProgress = false |
| | | console.log("视频详情", this.formData); |
| | | }) |
| | | }, |
| | | // 加载更多商品 |
| | | 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) { |
| | |
| | | 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 |
| | |
| | | }); |
| | | 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.videoUploadProgress = 0 |
| | | |
| | | // 获取文件名 |
| | | const tempPath = res.tempFilePath; |
| | | let fileName = tempPath.substring(tempPath.lastIndexOf('/') + 1); |
| | | |
| | | |
| | | // 处理安卓可能的URI编码 |
| | | if(fileName.indexOf('%') > -1) { |
| | | fileName = decodeURIComponent(fileName); |
| | |
| | | this.formData.videoDuration = res.duration; |
| | | // 判断视频的填充模式 |
| | | this.formData.videoFit = this.calculateVideoFit(res.width, res.height) |
| | | |
| | | this.showUploadProgress = true |
| | | 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); |
| | |
| | | cover: '' |
| | | } |
| | | } else { |
| | | console.log(this.videoInfo); |
| | | |
| | | } |
| | | }); |
| | | }, |
| | |
| | | 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({ |
| | |
| | | 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() { |
| | | 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.goodsId = 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.goodsId = ''; |
| | | clearGoods(goods) { |
| | | this.selectedGoodsList = this.selectedGoodsList.filter(item => item.id !== goods.id); |
| | | this.goodsList.forEach(item => { |
| | | if(item.id === goods.id) { |
| | | item.selected = false |
| | | } |
| | | }) |
| | | }, |
| | | |
| | | |
| | | // 搜索热门话题 |
| | | searchTags() { |
| | | if (this.tagInput.trim() !== '') { |
| | |
| | | }); |
| | | } |
| | | }, |
| | | |
| | | |
| | | // 选择推荐话题 |
| | | selectTopic(index) { |
| | | const tag = this.recommendedTags[index] |
| | |
| | | }); |
| | | return; |
| | | } |
| | | |
| | | |
| | | if (this.formData.tags.filter(item => item.tagName === tag.tagName).length < 1) { |
| | | this.formData.tags.push(tag); |
| | | this.tagInput = ''; |
| | |
| | | }); |
| | | } |
| | | }, |
| | | |
| | | |
| | | // 移除标签 |
| | | removeTag(index) { |
| | | this.formData.tags.splice(index, 1); |
| | | }, |
| | | |
| | | |
| | | // 处理发布 |
| | | handlePublish() { |
| | | this.$refs.formRef.validate(valid => { |
| | | if (valid && this.canPublish) { |
| | | this.loading = true; |
| | | this.formData.fileInfo = this.videoInfo; |
| | | console.log(this.formData); |
| | | publish(this.formData).then(res => { |
| | | this.formData["goodsList"] = this.selectedGoodsList.map(item => {return {goodsId: item.goodsId, goodsNum: item.goodsNum}}); |
| | | updateVideo(this.formData).then(res => { |
| | | uni.showToast({ |
| | | title: '视频已提交审核~', |
| | | icon: 'success' |
| | | }); |
| | | 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({ |
| | |
| | | }); |
| | | } |
| | | }); |
| | | } |
| | | }, |
| | | 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 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; |
| | | position: relative; |
| | | } |
| | | |
| | | .goods-preview .goods-image { |
| | |
| | | } |
| | | |
| | | .publish-btn { |
| | | position: fixed; |
| | | /* position: fixed; |
| | | bottom: 100rpx; |
| | | left: 20rpx; |
| | | right: 20rpx; |
| | | right: 20rpx; */ |
| | | margin-top: 40rpx; |
| | | } |
| | | |
| | | .goods-picker { |
| | |
| | | height: 25px; |
| | | margin-top: 10px; |
| | | } |
| | | </style> |
| | | |
| | | .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> |
| | |
| | | @change="onSwiperChange" |
| | | > |
| | | <swiper-item v-for="(item, index) in videoList" :key="item.id"> |
| | | <!-- 播放按钮(仅当视频暂停时显示) --> |
| | | <view |
| | | class="play-icon" |
| | | @click="togglePlay(index)" |
| | | v-if="!currentVideoIsPlaying" |
| | | > |
| | | <image src="/static/video/play.png" style="width: 45px;height: 45px" mode="aspectFit"></image> |
| | | </view> |
| | | <video |
| | | :id="'video'+index" |
| | | :ref="'video'+index" |
| | | :src="item.videoUrl" |
| | | :autoplay="currentIndex === index" |
| | | :controls="false" |
| | | :loop="true" |
| | | :object-fit="item.objectFit" |
| | | class="video-item" |
| | | @play="onPlay(item.id, index)" |
| | | @pause="onPause(index)" |
| | | @ended="onEnded(index)" |
| | | @click="togglePlay(index)" |
| | | @timeupdate="onTimeUpdate($event)" |
| | | ></video> |
| | | |
| | | <!-- 悬挂商品链接层 --> |
| | | <view class="goods-link-warp"> |
| | | <view class="goods-link"> |
| | | <view class="goods-container"> |
| | | <!-- 商品图片 --> |
| | | <image class="goods-image" :src="item.goods.imageUrl" mode="aspectFill"></image> |
| | | |
| | | <!-- 商品信息 --> |
| | | <view class="goods-info"> |
| | | <text class="goods-name">{{item.goods.name}}</text> |
| | | <view class="price-section"> |
| | | <text class="current-price">¥{{item.goods.price}}</text> |
| | | <text class="original-price" v-if="item.goods.originalPrice">¥{{item.goods.originalPrice}}</text> |
| | | </view> |
| | | <text class="sales-count">{{item.goods.saleNum}}人已购</text> |
| | | </view> |
| | | |
| | | <!-- 购买按钮 --> |
| | | <view class="buy-button"> |
| | | <text>购买</text> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | |
| | | |
| | | <!-- 视频信息层 --> |
| | | <view class="video-info"> |
| | | <view> |
| | | <text class="video-author">@{{item.authorName}}</text> |
| | | </view> |
| | | <view style="width: 100%;word-wrap: break-word;white-space: normal;overflow-wrap: break-word;"> |
| | | <text class="video-title">{{item.title}}</text> |
| | | <text class="video-tag" v-for="(tag, index) in item.tagList" :key="tag.id">#{{tag.tagName}}</text> |
| | | </view> |
| | | </view> |
| | | |
| | | <!-- 右侧互动按钮 --> |
| | | <view class="action-buttons"> |
| | | <view class="avatar-container"> |
| | | <image class="avatar" @click="jumpToHomePage(item.authorId)" :src="item.authorAvatar" mode="aspectFill"></image> |
| | | <!-- 关注图标 - 使用绝对定位 --> |
| | | <view v-if="!item.subscribeThisAuthor" class="follow-icon" @click="subscribeAuth(index, item.authorId)"> |
| | | <text class="iconfont"></text> |
| | | </view> |
| | | </view> |
| | | <view class="action-item" @click="toggleCollect(item, index)"> |
| | | <text class="iconfont" v-if="item.collected"></text> |
| | | <text class="iconfont" v-else></text> |
| | | <text style="font-size: 10px;font-weight: lighter;">{{item.collectNum}}</text> |
| | | <view style="width: 100%;height: 100%;" v-if="item.videoContentType === 'video'"> |
| | | <!-- 播放按钮(仅当视频暂停时显示) --> |
| | | <view |
| | | class="play-icon" |
| | | @click="togglePlay(index)" |
| | | v-if="!currentVideoIsPlaying" |
| | | > |
| | | <image src="/static/video/play.png" style="width: 45px;height: 45px" mode="aspectFit"></image> |
| | | </view> |
| | | <video |
| | | :id="'video'+index" |
| | | :ref="'video'+index" |
| | | :src="item.videoUrl" |
| | | :autoplay="currentIndex === index" |
| | | :controls="false" |
| | | :loop="true" |
| | | :object-fit="item.objectFit" |
| | | :enable-progress-gesture="false" |
| | | class="video-item" |
| | | @play="onPlay(item.id, index)" |
| | | @pause="onPause(index)" |
| | | @ended="onEnded(index)" |
| | | @click="togglePlay(index)" |
| | | @timeupdate="onTimeUpdate($event)" |
| | | @loadedmetadata="onLoadedMetadata($event)" |
| | | |
| | | ></video> |
| | | <!-- 自定义控制条 --> |
| | | <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> |
| | | </view> |
| | | <view style="width: 100%; height: 100%;" v-else-if="item.videoContentType === 'img'"> |
| | | <uni-swiper-dot |
| | | :info="item.imgs" |
| | | :current="currentImgIndex" |
| | | mode="round" |
| | | style="width: 100%;height: 100%;display: flex;justify-content: center;align-items: center;" |
| | | :dots-styles="{width: 24, bottom: 24,selectedBackgroundColor: 'green', backgroundColor: 'gray'}" |
| | | > |
| | | <swiper class="swiper-box" @change="imgChange" :autoplay="true" :interval="3000"> |
| | | <swiper-item v-for="img in item.imgs" :key="img"> |
| | | <view class="swiper-item"> |
| | | <!-- 调整 image 样式,使其居中且按比例缩放 --> |
| | | <image |
| | | :src="img" |
| | | mode="aspectFit" |
| | | style="width: 100%; height: 100%; display: block; margin: 0 auto;" |
| | | ></image> |
| | | </view> |
| | | </swiper-item> |
| | | </swiper> |
| | | </uni-swiper-dot> |
| | | </view> |
| | | |
| | | |
| | | <!-- 悬挂商品链接层 --> |
| | | <view class="goods-link-warp" v-if="item.goodsList.length > 0"> |
| | | <view class="goods-link"> |
| | | <swiper @change="goodsChange" :autoplay="true" :interval="4000" style="height: 120rpx;"> |
| | | <swiper-item v-for="goods in item.goodsList" :key="goods.goodsId"> |
| | | <view class="goods-container" @click="jumpToPay(item.id)"> |
| | | <!-- 商品图片 --> |
| | | <image class="goods-image" :src="goods.thumbnail" mode="aspectFill"></image> |
| | | |
| | | <!-- 商品信息 --> |
| | | <view class="goods-info"> |
| | | <text class="goods-name">{{goods.goodsName}}</text> |
| | | <view class="price-section"> |
| | | <text class="current-price">¥{{goods.price}}</text> |
| | | <text class="original-price" v-if="goods.originalPrice">¥{{goods.originalPrice}}</text> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | </swiper-item> |
| | | </swiper> |
| | | </view> |
| | | </view> |
| | | |
| | | |
| | | <!-- 视频信息层 --> |
| | | <view class="video-info"> |
| | | <view> |
| | | <text class="video-author">@{{item.authorName}}</text> |
| | | </view> |
| | | <view style="width: 100%;word-wrap: break-word;white-space: normal;overflow-wrap: break-word;"> |
| | | <text class="video-title">{{item.title}}</text> |
| | | <text class="video-tag" v-for="(tag, index) in item.tagList" :key="tag.id">#{{tag.tagName}}</text> |
| | | </view> |
| | | </view> |
| | | <view class="action-item" @click="showComments(item)"> |
| | | <text class="iconfont"></text> |
| | | <text style="font-size: 10px;font-weight: lighter;">{{item.commentNum}}</text> |
| | | |
| | | <!-- 右侧互动按钮 --> |
| | | <view class="action-buttons"> |
| | | <view class="avatar-container"> |
| | | <image class="avatar" @click="jumpToHomePage(item.authorId)" :src="item.authorAvatar" mode="aspectFill"></image> |
| | | <!-- 关注图标 - 使用绝对定位 --> |
| | | <view v-if="!item.subscribeThisAuthor" class="follow-icon" @click="subscribeAuth(index, item.authorId)"> |
| | | <text class="iconfont"></text> |
| | | </view> |
| | | </view> |
| | | <view class="action-item" @click="toggleCollect(item, index)"> |
| | | <text class="iconfont" v-if="item.collected"></text> |
| | | <text class="iconfont" v-else></text> |
| | | <text style="font-size: 10px;font-weight: lighter;">{{item.collectNum}}</text> |
| | | </view> |
| | | <view class="action-item" @click="showComments(item)"> |
| | | <text class="iconfont"></text> |
| | | <text style="font-size: 10px;font-weight: lighter;">{{item.commentNum}}</text> |
| | | </view> |
| | | <view class="action-item"> |
| | | <button open-type="share" class="custom-share-btn" :data-obj="item"> |
| | | <text class="iconfont"></text> |
| | | </button> |
| | | |
| | | </view> |
| | | </view> |
| | | </view> |
| | | |
| | | </swiper-item> |
| | | </swiper> |
| | | |
| | | </swiper-item> |
| | | </swiper> |
| | | |
| | | <!-- 评论弹窗 --> |
| | | <uni-popup ref="commentPopup" type="bottom" :is-mask-click="true" @maskClick="closeCommentPopup"> |
| | |
| | | <script> |
| | | import { getRecommendVideos, savePlayRecord, subscribe, getVideoComments, addVideoComment, thubmsUpComment, cancelThubmsUpComment } from "@/api/video.js"; |
| | | import { changeCollect } from "@/api/collect.js"; |
| | | import { saveShare } from "@/api/share.js"; |
| | | import storage from "@/utils/storage.js"; |
| | | export default { |
| | | computed: { |
| | | hasPlayTime() { |
| | | return this.sliderFormatTime(this.progress > 0 ? this.duration * this.progress / 100 : 0); |
| | | } |
| | | }, |
| | | data() { |
| | | return { |
| | | currentImgIndex: 0, // 播放到第几张图--索引 |
| | | currentGoodsIndex: 0, // 播放到第几个商品--索引 |
| | | currentTime: 0, |
| | | formartDuration: '', |
| | | duration: 0, |
| | | startX: 0, |
| | | progress: 0, // 视频进度 |
| | | startProgress : 0, // 开始滑动时的进度 |
| | | barLeft: 0, // 进度条左边界位置 |
| | | barWidth: 0, // 进度条宽度 |
| | | isDragging: false, // 是否正在拖动 |
| | | processHidenTimer: null, // 进度条隐藏定时器 |
| | | showProcess: false, // 是否显示进度条 |
| | | videoNoMore: false, // 是否还有更多视频 |
| | | commentNoMore: false, // 是否还有更多评论 |
| | | commentQuery: { |
| | |
| | | const playInfo = uni.getStorageSync("playInfo", playInfo); |
| | | if(playInfo) { |
| | | this.videoList = playInfo.videoList; |
| | | console.log("拿到数据了",playInfo); |
| | | this.videoQuery.pageNumber = playInfo.pageNumber; |
| | | this.videoNoMore = playInfo.nomore; |
| | | this.videoQuery.authorId = option.authorId; |
| | |
| | | // 初始化视频上下文 |
| | | this.initVideoContexts(); |
| | | }, |
| | | onShareAppMessage(e) { |
| | | const userInfo = storage.getUserInfo(); |
| | | if(!userInfo) { |
| | | console.log("未登录不能分享"); |
| | | return |
| | | } |
| | | const videoInfo = e.target.dataset.obj; |
| | | // 保存分享记录 |
| | | const data = { |
| | | shareType: 'video', |
| | | refId: videoInfo.id, |
| | | shareUser: userInfo.id |
| | | } |
| | | saveShare(data) |
| | | return { |
| | | title: videoInfo.title, |
| | | path: `/pages/tabbar/index/home?videoId=${videoInfo.id}&userId=${userInfo.id}`, |
| | | imageUrl: videoInfo.coverUrl |
| | | } |
| | | }, |
| | | methods: { |
| | | // 点击商品跳转 |
| | | jumpToPay(videoId) { |
| | | uni.navigateTo({ |
| | | url: '/pages/video/video-goods-detail?videoId=' + videoId |
| | | }); |
| | | }, |
| | | // 轮播图变化 |
| | | imgChange(e) { |
| | | this.currentImgIndex = e.detail.current; |
| | | }, |
| | | // 商品轮播图变化 |
| | | goodsChange(e) { |
| | | this.currentGoodsIndex = e.detail.current; |
| | | }, |
| | | // 获取进度条的位置和尺寸 |
| | | getBarRect() { |
| | | const query = uni.createSelectorQuery().in(this); |
| | | query.select('#progressBar').boundingClientRect(rect => { |
| | | if (rect) { |
| | | this.barLeft = rect.left; |
| | | this.barWidth = rect.width; |
| | | } |
| | | }).exec(); |
| | | }, |
| | | // 跳转个人主页 |
| | | jumpToHomePage(authorId) { |
| | | uni.navigateTo({ |
| | |
| | | const input = this.$refs.commentInput; |
| | | if (input) input.focus(); |
| | | }); |
| | | }, |
| | | // 进度条时间格式化 (00:00) |
| | | sliderFormatTime(seconds) { |
| | | const mins = Math.floor(seconds / 60); |
| | | const secs = Math.floor(seconds % 60); |
| | | return `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`; |
| | | }, |
| | | // 格式化时间 |
| | | formatTime(time) { |
| | |
| | | this.videoContexts[this.currentIndex].play(); |
| | | } |
| | | }, |
| | | |
| | | // 获取进度条的位置和尺寸 |
| | | 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) { |
| | | // 获取当前触摸点X坐标 |
| | | 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; |
| | | }, |
| | | |
| | | // 收藏/取消收藏 |
| | | toggleCollect(item, index) { |
| | |
| | | }, |
| | | // 视频播放事件 |
| | | onPlay(id, index) { |
| | | console.log(id, index, "触发播放"); |
| | | this.getBarRect() |
| | | this.progress = 0 |
| | | if(index === this.currentIndex) { |
| | | this.currentVideoIsPlaying = true; |
| | | if(! this.duration) { |
| | | // 设置当前播放视频的总时长 |
| | | this.duration = this.videoList[this.currentIndex].videoDuration; |
| | | this.formartDuration = this.sliderFormatTime(this.duration); |
| | | } |
| | | } else { |
| | | this.currentVideoIsPlaying = false; |
| | | return |
| | |
| | | // 记录播放时长 |
| | | onTimeUpdate(e) { |
| | | this.playRecord.playAt = e.detail.currentTime |
| | | this.currentTime = e.detail.currentTime; |
| | | this.progress = (e.detail.currentTime / this.duration) * 100 |
| | | }, |
| | | |
| | | // 获取视频总时长 |
| | | onLoadedMetadata(e) { |
| | | // this.duration = e.detail.duration; |
| | | // this.formartDuration = this.sliderFormatTime(this.duration); |
| | | // console.log("视频总时长", this.duration); |
| | | }, |
| | | // 保存播放记录 |
| | | async savePlayRecord() { |
| | | console.log(Date.now(), this.playRecord.startPlayTime, this.totalHidenTime); |
| | |
| | | |
| | | .video-swiper { |
| | | width: 100%; |
| | | height: 100%; |
| | | height: calc(100% - 20rpx); |
| | | } |
| | | |
| | | .video-item { |
| | |
| | | .video-info { |
| | | width: 70%; |
| | | position: absolute; |
| | | bottom: 70px; |
| | | bottom: 20px; |
| | | left: 20px; |
| | | color: #f8f8f8; |
| | | z-index: 10; |
| | |
| | | } |
| | | .goods-link { |
| | | position: relative; |
| | | width: 450rpx; |
| | | margin: 20rpx 0; |
| | | padding: 12rpx; |
| | | background-color: rgba(255, 255, 255, 0.9); |
| | |
| | | } |
| | | |
| | | .goods-container { |
| | | width: 100%; |
| | | display: flex; |
| | | align-items: center; |
| | | } |
| | |
| | | font-size: 28rpx; |
| | | color: #333; |
| | | font-weight: bold; |
| | | display: -webkit-box; |
| | | -webkit-line-clamp: 2; |
| | | -webkit-box-orient: vertical; |
| | | overflow: hidden; |
| | | margin-bottom: 8rpx; |
| | | width: 280rpx; /* 需要指定宽度 */ |
| | | overflow: hidden; |
| | | white-space: nowrap; |
| | | text-overflow: ellipsis; |
| | | } |
| | | |
| | | .price-section { |
| | |
| | | .thumbs-num { |
| | | margin-left: 4rpx; |
| | | } |
| | | .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; |
| | | } |
| | | .swiper-box { |
| | | width: 100%; |
| | | height: 1200rpx; |
| | | } |
| | | .swiper-item { |
| | | display: flex; |
| | | justify-content: center; |
| | | align-items: center; |
| | | width: 100%; |
| | | height: 100%; |
| | | } |
| | | .custom-share-btn { |
| | | font-size: unset; |
| | | background: none; |
| | | padding: 0; |
| | | margin: 0; |
| | | line-height: normal; |
| | | border: none; |
| | | } |
| | | .custom-share-btn::after { |
| | | border: none; |
| | | } |
| | | </style> |
| | |
| | | ## 1.9.9(2025-06-11) |
| | | - 修复 uni-popup-dialog 中 setVal 方法报错的问题 |
| | | - 修复 uni-popup-dialog 数据双向绑定问题。 |
| | | ## 1.9.8(2025-04-16) |
| | | - 修复 更新组件示例 ,解决更新数据或保存项目导致弹窗消失的问题 |
| | | ## 1.9.7(2025-04-14) |
| | |
| | | } |
| | | }, |
| | | value(val) { |
| | | setVal(val) |
| | | this.setVal(val) |
| | | }, |
| | | // #ifdef VUE3 |
| | | modelValue(val) { |
| | | setVal(val) |
| | | this.setVal(val) |
| | | }, |
| | | // #endif |
| | | val(val) { |
| | |
| | | { |
| | | "id": "uni-popup", |
| | | "displayName": "uni-popup 弹出层", |
| | | "version": "1.9.8", |
| | | "description": " Popup 组件,提供常用的弹层", |
| | | "keywords": [ |
| | | "uni-ui", |
| | | "弹出层", |
| | | "弹窗", |
| | | "popup", |
| | | "弹框" |
| | | "id": "uni-popup", |
| | | "displayName": "uni-popup 弹出层", |
| | | "version": "1.9.9", |
| | | "description": " Popup 组件,提供常用的弹层", |
| | | "keywords": [ |
| | | "uni-ui", |
| | | "弹出层", |
| | | "弹窗", |
| | | "popup", |
| | | "弹框" |
| | | ], |
| | | "repository": "https://github.com/dcloudio/uni-ui", |
| | | "engines": { |
| | | "HBuilderX": "", |
| | | "uni-app": "^4.01", |
| | | "uni-app-x": "" |
| | | }, |
| | | "directories": { |
| | | "example": "../../temps/example_temps" |
| | | }, |
| | | "dcloudext": { |
| | | "sale": { |
| | | "regular": { |
| | | "price": "0.00" |
| | | }, |
| | | "sourcecode": { |
| | | "price": "0.00" |
| | | } |
| | | }, |
| | | "contact": { |
| | | "qq": "" |
| | | }, |
| | | "declaration": { |
| | | "ads": "无", |
| | | "data": "无", |
| | | "permissions": "无" |
| | | }, |
| | | "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui", |
| | | "type": "component-vue", |
| | | "darkmode": "x", |
| | | "i18n": "x", |
| | | "widescreen": "x" |
| | | }, |
| | | "uni_modules": { |
| | | "dependencies": [ |
| | | "uni-scss", |
| | | "uni-transition" |
| | | ], |
| | | "repository": "https://github.com/dcloudio/uni-ui", |
| | | "engines": { |
| | | "HBuilderX": "" |
| | | }, |
| | | "directories": { |
| | | "example": "../../temps/example_temps" |
| | | }, |
| | | "dcloudext": { |
| | | "sale": { |
| | | "regular": { |
| | | "price": "0.00" |
| | | }, |
| | | "sourcecode": { |
| | | "price": "0.00" |
| | | } |
| | | }, |
| | | "contact": { |
| | | "qq": "" |
| | | }, |
| | | "declaration": { |
| | | "ads": "无", |
| | | "data": "无", |
| | | "permissions": "无" |
| | | }, |
| | | "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui", |
| | | "type": "component-vue" |
| | | }, |
| | | "uni_modules": { |
| | | "dependencies": [ |
| | | "uni-scss", |
| | | "uni-transition" |
| | | ], |
| | | "encrypt": [], |
| | | "platforms": { |
| | | "cloud": { |
| | | "tcb": "y", |
| | | "aliyun": "y", |
| | | "alipay": "n" |
| | | }, |
| | | "client": { |
| | | "App": { |
| | | "app-vue": "y", |
| | | "app-nvue": "y", |
| | | "app-harmony": "u", |
| | | "app-uvue": "u" |
| | | }, |
| | | "H5-mobile": { |
| | | "Safari": "y", |
| | | "Android Browser": "y", |
| | | "微信浏览器(Android)": "y", |
| | | "QQ浏览器(Android)": "y" |
| | | }, |
| | | "H5-pc": { |
| | | "Chrome": "y", |
| | | "IE": "y", |
| | | "Edge": "y", |
| | | "Firefox": "y", |
| | | "Safari": "y" |
| | | }, |
| | | "小程序": { |
| | | "微信": "y", |
| | | "阿里": "y", |
| | | "百度": "y", |
| | | "字节跳动": "y", |
| | | "QQ": "y" |
| | | }, |
| | | "快应用": { |
| | | "华为": "u", |
| | | "联盟": "u" |
| | | }, |
| | | "Vue": { |
| | | "vue2": "y", |
| | | "vue3": "y" |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| | | "encrypt": [], |
| | | "platforms": { |
| | | "cloud": { |
| | | "tcb": "x", |
| | | "aliyun": "x", |
| | | "alipay": "x" |
| | | }, |
| | | "client": { |
| | | "uni-app": { |
| | | "vue": { |
| | | "vue2": "√", |
| | | "vue3": "√" |
| | | }, |
| | | "web": { |
| | | "safari": "√", |
| | | "chrome": "√" |
| | | }, |
| | | "app": { |
| | | "vue": "√", |
| | | "nvue": "√", |
| | | "android": "√", |
| | | "ios": "√", |
| | | "harmony": "x" |
| | | }, |
| | | "mp": { |
| | | "weixin": "√", |
| | | "alipay": "√", |
| | | "toutiao": "√", |
| | | "baidu": "√", |
| | | "kuaishou": "√", |
| | | "jd": "√", |
| | | "harmony": "√", |
| | | "qq": "√", |
| | | "lark": "√" |
| | | }, |
| | | "quickapp": { |
| | | "huawei": "-", |
| | | "union": "-" |
| | | } |
| | | }, |
| | | "uni-app-x": { |
| | | "web": { |
| | | "safari": "-", |
| | | "chrome": "-" |
| | | }, |
| | | "app": { |
| | | "android": "-", |
| | | "ios": "-", |
| | | "harmony": "-" |
| | | }, |
| | | "mp": { |
| | | "weixin": "-" |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| | |
| | | ## 1.3.5(2025-06-11) |
| | | - 修复 第一次执行不显示动画的问题 |
| | | ## 1.3.4(2025-04-16) |
| | | - 修复 页面数据更新到底动画复原的问题 |
| | | - 修复 示例页面打开报错的问题 |
| | |
| | | <template> |
| | | <!-- #ifndef APP-NVUE --> |
| | | <view v-show="isShow" ref="ani" :animation="animationData" :class="customClass" :style="transformStyles" @click="onClick"><slot></slot></view> |
| | | <!-- #endif --> |
| | | <!-- #ifdef APP-NVUE --> |
| | | <view v-if="isShow" ref="ani" :animation="animationData" :class="customClass" :style="transformStyles" @click="onClick"><slot></slot></view> |
| | | <!-- #endif --> |
| | | <!-- #ifndef APP-NVUE --> |
| | | <view v-show="isShow" ref="ani" :animation="animationData" :class="customClass" :style="transformStyles" @click="onClick"> |
| | | <slot></slot> |
| | | </view> |
| | | <!-- #endif --> |
| | | <!-- #ifdef APP-NVUE --> |
| | | <view v-if="isShow" ref="ani" :animation="animationData" :class="customClass" :style="transformStyles" @click="onClick"> |
| | | <slot></slot> |
| | | </view> |
| | | <!-- #endif --> |
| | | </template> |
| | | |
| | | <script> |
| | | import { createAnimation } from './createAnimation' |
| | | import { createAnimation } from './createAnimation' |
| | | |
| | | /** |
| | | * Transition 过渡动画 |
| | | * @description 简单过渡动画组件 |
| | | * @tutorial https://ext.dcloud.net.cn/plugin?id=985 |
| | | * @property {Boolean} show = [false|true] 控制组件显示或隐藏 |
| | | * @property {Array|String} modeClass = [fade|slide-top|slide-right|slide-bottom|slide-left|zoom-in|zoom-out] 过渡动画类型 |
| | | * @value fade 渐隐渐出过渡 |
| | | * @value slide-top 由上至下过渡 |
| | | * @value slide-right 由右至左过渡 |
| | | * @value slide-bottom 由下至上过渡 |
| | | * @value slide-left 由左至右过渡 |
| | | * @value zoom-in 由小到大过渡 |
| | | * @value zoom-out 由大到小过渡 |
| | | * @property {Number} duration 过渡动画持续时间 |
| | | * @property {Object} styles 组件样式,同 css 样式,注意带’-‘连接符的属性需要使用小驼峰写法如:`backgroundColor:red` |
| | | */ |
| | | export default { |
| | | name: 'uniTransition', |
| | | emits:['click','change'], |
| | | props: { |
| | | show: { |
| | | type: Boolean, |
| | | default: false |
| | | }, |
| | | modeClass: { |
| | | type: [Array, String], |
| | | default() { |
| | | return 'fade' |
| | | } |
| | | }, |
| | | duration: { |
| | | type: Number, |
| | | default: 300 |
| | | }, |
| | | styles: { |
| | | type: Object, |
| | | default() { |
| | | return {} |
| | | } |
| | | }, |
| | | customClass:{ |
| | | type: String, |
| | | default: '' |
| | | }, |
| | | onceRender:{ |
| | | type:Boolean, |
| | | default:false |
| | | }, |
| | | }, |
| | | data() { |
| | | return { |
| | | isShow: false, |
| | | transform: '', |
| | | opacity: 1, |
| | | animationData: {}, |
| | | durationTime: 300, |
| | | config: {} |
| | | } |
| | | }, |
| | | watch: { |
| | | show: { |
| | | handler(newVal) { |
| | | if (newVal) { |
| | | this.open() |
| | | } else { |
| | | // 避免上来就执行 close,导致动画错乱 |
| | | if (this.isShow) { |
| | | this.close() |
| | | } |
| | | /** |
| | | * Transition 过渡动画 |
| | | * @description 简单过渡动画组件 |
| | | * @tutorial https://ext.dcloud.net.cn/plugin?id=985 |
| | | * @property {Boolean} show = [false|true] 控制组件显示或隐藏 |
| | | * @property {Array|String} modeClass = [fade|slide-top|slide-right|slide-bottom|slide-left|zoom-in|zoom-out] 过渡动画类型 |
| | | * @value fade 渐隐渐出过渡 |
| | | * @value slide-top 由上至下过渡 |
| | | * @value slide-right 由右至左过渡 |
| | | * @value slide-bottom 由下至上过渡 |
| | | * @value slide-left 由左至右过渡 |
| | | * @value zoom-in 由小到大过渡 |
| | | * @value zoom-out 由大到小过渡 |
| | | * @property {Number} duration 过渡动画持续时间 |
| | | * @property {Object} styles 组件样式,同 css 样式,注意带’-‘连接符的属性需要使用小驼峰写法如:`backgroundColor:red` |
| | | */ |
| | | export default { |
| | | name: 'uniTransition', |
| | | emits: ['click', 'change'], |
| | | props: { |
| | | show: { |
| | | type: Boolean, |
| | | default: false |
| | | }, |
| | | modeClass: { |
| | | type: [Array, String], |
| | | default () { |
| | | return 'fade' |
| | | } |
| | | }, |
| | | immediate: true |
| | | } |
| | | }, |
| | | computed: { |
| | | // 生成样式数据 |
| | | stylesObject() { |
| | | let styles = { |
| | | ...this.styles, |
| | | 'transition-duration': this.duration / 1000 + 's' |
| | | } |
| | | let transform = '' |
| | | for (let i in styles) { |
| | | let line = this.toLine(i) |
| | | transform += line + ':' + styles[i] + ';' |
| | | } |
| | | return transform |
| | | }, |
| | | // 初始化动画条件 |
| | | transformStyles() { |
| | | return 'transform:' + this.transform + ';' + 'opacity:' + this.opacity + ';' + this.stylesObject |
| | | } |
| | | }, |
| | | created() { |
| | | // 动画默认配置 |
| | | this.config = { |
| | | duration: this.duration, |
| | | timingFunction: 'ease', |
| | | transformOrigin: '50% 50%', |
| | | delay: 0 |
| | | } |
| | | this.durationTime = this.duration |
| | | }, |
| | | methods: { |
| | | /** |
| | | * ref 触发 初始化动画 |
| | | */ |
| | | init(obj = {}) { |
| | | if (obj.duration) { |
| | | this.durationTime = obj.duration |
| | | } |
| | | this.animation = createAnimation(Object.assign(this.config, obj),this) |
| | | }, |
| | | /** |
| | | * 点击组件触发回调 |
| | | */ |
| | | onClick() { |
| | | this.$emit('click', { |
| | | detail: this.isShow |
| | | }) |
| | | }, |
| | | /** |
| | | * ref 触发 动画分组 |
| | | * @param {Object} obj |
| | | */ |
| | | step(obj, config = {}) { |
| | | if (!this.animation) return |
| | | for (let i in obj) { |
| | | try { |
| | | if(typeof obj[i] === 'object'){ |
| | | this.animation[i](...obj[i]) |
| | | }else{ |
| | | this.animation[i](obj[i]) |
| | | } |
| | | } catch (e) { |
| | | console.error(`方法 ${i} 不存在`) |
| | | duration: { |
| | | type: Number, |
| | | default: 300 |
| | | }, |
| | | styles: { |
| | | type: Object, |
| | | default () { |
| | | return {} |
| | | } |
| | | }, |
| | | customClass: { |
| | | type: String, |
| | | default: '' |
| | | }, |
| | | onceRender: { |
| | | type: Boolean, |
| | | default: false |
| | | }, |
| | | }, |
| | | data() { |
| | | return { |
| | | isShow: false, |
| | | transform: '', |
| | | opacity: 0, |
| | | animationData: {}, |
| | | durationTime: 300, |
| | | config: {} |
| | | } |
| | | this.animation.step(config) |
| | | return this |
| | | }, |
| | | /** |
| | | * ref 触发 执行动画 |
| | | */ |
| | | run(fn) { |
| | | if (!this.animation) return |
| | | this.animation.run(fn) |
| | | }, |
| | | // 开始过度动画 |
| | | open() { |
| | | clearTimeout(this.timer) |
| | | this.transform = '' |
| | | this.isShow = true |
| | | let { opacity, transform } = this.styleInit(false) |
| | | if (typeof opacity !== 'undefined') { |
| | | this.opacity = opacity |
| | | watch: { |
| | | show: { |
| | | handler(newVal) { |
| | | if (newVal) { |
| | | this.open() |
| | | } else { |
| | | // 避免上来就执行 close,导致动画错乱 |
| | | if (this.isShow) { |
| | | this.close() |
| | | } |
| | | } |
| | | }, |
| | | immediate: true |
| | | } |
| | | this.transform = transform |
| | | // 确保动态样式已经生效后,执行动画,如果不加 nextTick ,会导致 wx 动画执行异常 |
| | | this.$nextTick(() => { |
| | | // TODO 定时器保证动画完全执行,目前有些问题,后面会取消定时器 |
| | | this.timer = setTimeout(() => { |
| | | this.animation = createAnimation(this.config, this) |
| | | this.tranfromInit(false).step() |
| | | this.animation.run(() => { |
| | | this.transform = '' |
| | | this.opacity = opacity || 1 |
| | | }) |
| | | this.$emit('change', { |
| | | detail: this.isShow |
| | | }) |
| | | }, 20) |
| | | }) |
| | | }, |
| | | // 关闭过度动画 |
| | | close(type) { |
| | | if (!this.animation) return |
| | | this.tranfromInit(true) |
| | | .step() |
| | | .run(() => { |
| | | this.isShow = false |
| | | this.animationData = null |
| | | this.animation = null |
| | | let { opacity, transform } = this.styleInit(false) |
| | | this.opacity = opacity || 1 |
| | | this.transform = transform |
| | | this.$emit('change', { |
| | | detail: this.isShow |
| | | }) |
| | | }) |
| | | }, |
| | | // 处理动画开始前的默认样式 |
| | | styleInit(type) { |
| | | let styles = { |
| | | transform: '' |
| | | } |
| | | let buildStyle = (type, mode) => { |
| | | if (mode === 'fade') { |
| | | styles.opacity = this.animationType(type)[mode] |
| | | } else { |
| | | styles.transform += this.animationType(type)[mode] + ' ' |
| | | computed: { |
| | | // 生成样式数据 |
| | | stylesObject() { |
| | | let styles = { |
| | | ...this.styles, |
| | | 'transition-duration': this.duration / 1000 + 's' |
| | | } |
| | | } |
| | | if (typeof this.modeClass === 'string') { |
| | | buildStyle(type, this.modeClass) |
| | | } else { |
| | | this.modeClass.forEach(mode => { |
| | | buildStyle(type, mode) |
| | | }) |
| | | } |
| | | return styles |
| | | }, |
| | | // 处理内置组合动画 |
| | | tranfromInit(type) { |
| | | let buildTranfrom = (type, mode) => { |
| | | let aniNum = null |
| | | if (mode === 'fade') { |
| | | aniNum = type ? 0 : 1 |
| | | } else { |
| | | aniNum = type ? '-100%' : '0' |
| | | if (mode === 'zoom-in') { |
| | | aniNum = type ? 0.8 : 1 |
| | | } |
| | | if (mode === 'zoom-out') { |
| | | aniNum = type ? 1.2 : 1 |
| | | } |
| | | if (mode === 'slide-right') { |
| | | aniNum = type ? '100%' : '0' |
| | | } |
| | | if (mode === 'slide-bottom') { |
| | | aniNum = type ? '100%' : '0' |
| | | } |
| | | let transform = '' |
| | | for (let i in styles) { |
| | | let line = this.toLine(i) |
| | | transform += line + ':' + styles[i] + ';' |
| | | } |
| | | this.animation[this.animationMode()[mode]](aniNum) |
| | | return transform |
| | | }, |
| | | // 初始化动画条件 |
| | | transformStyles() { |
| | | return 'transform:' + this.transform + ';' + 'opacity:' + this.opacity + ';' + this.stylesObject |
| | | } |
| | | if (typeof this.modeClass === 'string') { |
| | | buildTranfrom(type, this.modeClass) |
| | | } else { |
| | | this.modeClass.forEach(mode => { |
| | | buildTranfrom(type, mode) |
| | | }, |
| | | created() { |
| | | // 动画默认配置 |
| | | this.config = { |
| | | duration: this.duration, |
| | | timingFunction: 'ease', |
| | | transformOrigin: '50% 50%', |
| | | delay: 0 |
| | | } |
| | | this.durationTime = this.duration |
| | | }, |
| | | methods: { |
| | | /** |
| | | * ref 触发 初始化动画 |
| | | */ |
| | | init(obj = {}) { |
| | | if (obj.duration) { |
| | | this.durationTime = obj.duration |
| | | } |
| | | this.animation = createAnimation(Object.assign(this.config, obj), this) |
| | | }, |
| | | /** |
| | | * 点击组件触发回调 |
| | | */ |
| | | onClick() { |
| | | this.$emit('click', { |
| | | detail: this.isShow |
| | | }) |
| | | } |
| | | }, |
| | | /** |
| | | * ref 触发 动画分组 |
| | | * @param {Object} obj |
| | | */ |
| | | step(obj, config = {}) { |
| | | if (!this.animation) return this |
| | | Object.keys(obj).forEach(key => { |
| | | const value = obj[key] |
| | | if (typeof this.animation[key] === 'function') { |
| | | Array.isArray(value) ? |
| | | this.animation[key](...value) : |
| | | this.animation[key](value) |
| | | } |
| | | }) |
| | | this.animation.step(config) |
| | | return this |
| | | }, |
| | | /** |
| | | * ref 触发 执行动画 |
| | | */ |
| | | run(fn) { |
| | | if (!this.animation) return |
| | | this.animation.run(fn) |
| | | }, |
| | | // 开始过度动画 |
| | | open() { |
| | | clearTimeout(this.timer) |
| | | this.isShow = true |
| | | // 新增初始状态重置逻辑(关键) |
| | | this.transform = this.styleInit(false).transform || '' |
| | | this.opacity = this.styleInit(false).opacity || 0 |
| | | |
| | | return this.animation |
| | | }, |
| | | animationType(type) { |
| | | return { |
| | | fade: type ? 0 : 1, |
| | | 'slide-top': `translateY(${type ? '0' : '-100%'})`, |
| | | 'slide-right': `translateX(${type ? '0' : '100%'})`, |
| | | 'slide-bottom': `translateY(${type ? '0' : '100%'})`, |
| | | 'slide-left': `translateX(${type ? '0' : '-100%'})`, |
| | | 'zoom-in': `scaleX(${type ? 1 : 0.8}) scaleY(${type ? 1 : 0.8})`, |
| | | 'zoom-out': `scaleX(${type ? 1 : 1.2}) scaleY(${type ? 1 : 1.2})` |
| | | // 确保动态样式已经生效后,执行动画,如果不加 nextTick ,会导致 wx 动画执行异常 |
| | | this.$nextTick(() => { |
| | | // TODO 定时器保证动画完全执行,目前有些问题,后面会取消定时器 |
| | | this.timer = setTimeout(() => { |
| | | this.animation = createAnimation(this.config, this) |
| | | this.tranfromInit(false).step() |
| | | this.animation.run(() => { |
| | | this.transform = '' |
| | | this.opacity = this.styleInit(false).opacity || 1 |
| | | this.$emit('change', { |
| | | detail: this.isShow |
| | | }) |
| | | }) |
| | | }, 80) |
| | | }) |
| | | }, |
| | | // 关闭过度动画 |
| | | close(type) { |
| | | if (!this.animation) return |
| | | this.tranfromInit(true) |
| | | .step() |
| | | .run(() => { |
| | | this.isShow = false |
| | | this.animationData = null |
| | | this.animation = null |
| | | let { opacity, transform } = this.styleInit(false) |
| | | this.opacity = opacity || 1 |
| | | this.transform = transform |
| | | this.$emit('change', { |
| | | detail: this.isShow |
| | | }) |
| | | }) |
| | | }, |
| | | // 处理动画开始前的默认样式 |
| | | styleInit(type) { |
| | | let styles = { transform: '', opacity: 1 } |
| | | const buildStyle = (type, mode) => { |
| | | const value = this.animationType(type)[mode] // 直接使用 type 控制状态 |
| | | if (mode.startsWith('fade')) { |
| | | styles.opacity = value |
| | | } else { |
| | | styles.transform += value + ' ' |
| | | } |
| | | } |
| | | |
| | | if (typeof this.modeClass === 'string') { |
| | | buildStyle(type, this.modeClass) |
| | | } else { |
| | | this.modeClass.forEach(mode => buildStyle(type, mode)) |
| | | } |
| | | return styles |
| | | }, |
| | | // 处理内置组合动画 |
| | | tranfromInit(type) { |
| | | let buildTranfrom = (type, mode) => { |
| | | let aniNum = null |
| | | if (mode === 'fade') { |
| | | aniNum = type ? 0 : 1 |
| | | } else { |
| | | aniNum = type ? '-100%' : '0' |
| | | if (mode === 'zoom-in') { |
| | | aniNum = type ? 0.8 : 1 |
| | | } |
| | | if (mode === 'zoom-out') { |
| | | aniNum = type ? 1.2 : 1 |
| | | } |
| | | if (mode === 'slide-right') { |
| | | aniNum = type ? '100%' : '0' |
| | | } |
| | | if (mode === 'slide-bottom') { |
| | | aniNum = type ? '100%' : '0' |
| | | } |
| | | } |
| | | this.animation[this.animationMode()[mode]](aniNum) |
| | | } |
| | | if (typeof this.modeClass === 'string') { |
| | | buildTranfrom(type, this.modeClass) |
| | | } else { |
| | | this.modeClass.forEach(mode => { |
| | | buildTranfrom(type, mode) |
| | | }) |
| | | } |
| | | |
| | | return this.animation |
| | | }, |
| | | animationType(type) { |
| | | return { |
| | | fade: type ? 1 : 0, |
| | | 'slide-top': `translateY(${type ? '0' : '-100%'})`, |
| | | 'slide-right': `translateX(${type ? '0' : '100%'})`, |
| | | 'slide-bottom': `translateY(${type ? '0' : '100%'})`, |
| | | 'slide-left': `translateX(${type ? '0' : '-100%'})`, |
| | | 'zoom-in': `scaleX(${type ? 1 : 0.8}) scaleY(${type ? 1 : 0.8})`, |
| | | 'zoom-out': `scaleX(${type ? 1 : 1.2}) scaleY(${type ? 1 : 1.2})` |
| | | } |
| | | }, |
| | | // 内置动画类型与实际动画对应字典 |
| | | animationMode() { |
| | | return { |
| | | fade: 'opacity', |
| | | 'slide-top': 'translateY', |
| | | 'slide-right': 'translateX', |
| | | 'slide-bottom': 'translateY', |
| | | 'slide-left': 'translateX', |
| | | 'zoom-in': 'scale', |
| | | 'zoom-out': 'scale' |
| | | } |
| | | }, |
| | | // 驼峰转中横线 |
| | | toLine(name) { |
| | | return name.replace(/([A-Z])/g, '-$1').toLowerCase() |
| | | } |
| | | }, |
| | | // 内置动画类型与实际动画对应字典 |
| | | animationMode() { |
| | | return { |
| | | fade: 'opacity', |
| | | 'slide-top': 'translateY', |
| | | 'slide-right': 'translateX', |
| | | 'slide-bottom': 'translateY', |
| | | 'slide-left': 'translateX', |
| | | 'zoom-in': 'scale', |
| | | 'zoom-out': 'scale' |
| | | } |
| | | }, |
| | | // 驼峰转中横线 |
| | | toLine(name) { |
| | | return name.replace(/([A-Z])/g, '-$1').toLowerCase() |
| | | } |
| | | } |
| | | } |
| | | </script> |
| | | |
| | | <style></style> |
| | |
| | | { |
| | | "id": "uni-transition", |
| | | "displayName": "uni-transition 过渡动画", |
| | | "version": "1.3.4", |
| | | "version": "1.3.5", |
| | | "description": "元素的简单过渡动画", |
| | | "keywords": [ |
| | | "uni-ui", |
| | |
| | | ], |
| | | "repository": "https://github.com/dcloudio/uni-ui", |
| | | "engines": { |
| | | "HBuilderX": "" |
| | | "HBuilderX": "", |
| | | "uni-app": "^4.01", |
| | | "uni-app-x": "" |
| | | }, |
| | | "directories": { |
| | | "example": "../../temps/example_temps" |
| | | }, |
| | | "dcloudext": { |
| | | "dcloudext": { |
| | | "sale": { |
| | | "regular": { |
| | | "price": "0.00" |
| | |
| | | "permissions": "无" |
| | | }, |
| | | "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui", |
| | | "type": "component-vue" |
| | | "type": "component-vue", |
| | | "darkmode": "x", |
| | | "i18n": "x", |
| | | "widescreen": "x" |
| | | }, |
| | | "uni_modules": { |
| | | "dependencies": ["uni-scss"], |
| | | "dependencies": [ |
| | | "uni-scss" |
| | | ], |
| | | "encrypt": [], |
| | | "platforms": { |
| | | "cloud": { |
| | | "tcb": "y", |
| | | "aliyun": "y", |
| | | "alipay": "n" |
| | | "tcb": "√", |
| | | "aliyun": "√", |
| | | "alipay": "x" |
| | | }, |
| | | "client": { |
| | | "App": { |
| | | "app-vue": "y", |
| | | "app-nvue": "y", |
| | | "app-harmony": "u", |
| | | "app-uvue": "n" |
| | | "uni-app": { |
| | | "vue": { |
| | | "vue2": "√", |
| | | "vue3": "√" |
| | | }, |
| | | "web": { |
| | | "safari": "√", |
| | | "chrome": "√" |
| | | }, |
| | | "app": { |
| | | "vue": "√", |
| | | "nvue": "√", |
| | | "android": "√", |
| | | "ios": "√", |
| | | "harmony": "-" |
| | | }, |
| | | "mp": { |
| | | "weixin": { |
| | | "extVersion": "1.0.2", |
| | | "minVersion": "" |
| | | }, |
| | | "alipay": { |
| | | "extVersion": "1.0.2", |
| | | "minVersion": "" |
| | | }, |
| | | "toutiao": { |
| | | "extVersion": "1.0.2", |
| | | "minVersion": "" |
| | | }, |
| | | "baidu": { |
| | | "extVersion": "1.0.2", |
| | | "minVersion": "" |
| | | }, |
| | | "kuaishou": { |
| | | "extVersion": "1.1.0", |
| | | "minVersion": "" |
| | | }, |
| | | "jd": { |
| | | "extVersion": "1.0.2", |
| | | "minVersion": "" |
| | | }, |
| | | "harmony": "x", |
| | | "qq": "-", |
| | | "lark": "-" |
| | | }, |
| | | "quickapp": { |
| | | "huawei": "-", |
| | | "union": "-" |
| | | } |
| | | }, |
| | | "H5-mobile": { |
| | | "Safari": "y", |
| | | "Android Browser": "y", |
| | | "微信浏览器(Android)": "y", |
| | | "QQ浏览器(Android)": "y" |
| | | }, |
| | | "H5-pc": { |
| | | "Chrome": "y", |
| | | "IE": "y", |
| | | "Edge": "y", |
| | | "Firefox": "y", |
| | | "Safari": "y" |
| | | }, |
| | | "小程序": { |
| | | "微信": "y", |
| | | "阿里": "y", |
| | | "百度": "y", |
| | | "字节跳动": "y", |
| | | "QQ": "y" |
| | | }, |
| | | "快应用": { |
| | | "华为": "u", |
| | | "联盟": "u" |
| | | }, |
| | | "Vue": { |
| | | "vue2": "y", |
| | | "vue3": "y" |
| | | "uni-app-x": { |
| | | "web": { |
| | | "safari": "-", |
| | | "chrome": "-" |
| | | }, |
| | | "app": { |
| | | "android": "-", |
| | | "ios": "-", |
| | | "harmony": "-" |
| | | }, |
| | | "mp": { |
| | | "weixin": "-" |
| | | } |
| | | } |
| | | } |
| | | } |