fuyuan-housekeeping-uniapp/src/pages/goods/index.vue

604 lines
18 KiB
Vue

<template>
<view style="height: 100vh; overflow: auto" v-show="isEmpty">
<!-- 轮播图 -->
<!-- <la-swiper
:content="goodsData.goodsImageList"
indicatorPos="bottomRight"
mode="number"
/> -->
<!-- <z-paging
auto-show-back-to-top
ref="paging"
v-model="serviceList"
@query="queryList"
:fixed="false"
height="100%"
> -->
<LaSwiper :content="goodsData?.goodsImageList" />
<!-- 商品信息 -->
<card :title="goodsData.name" :border_bottom="false">
<template #header-right>
<view class="flex" @click="handleCollection(goodsData.collect)">
<image
class="collection"
v-if="!goodsData.collect"
src="/static/images/icon_collection.png"
></image>
<image
class="collection"
v-else
src="/static/images/icon_collection_s2.png"
></image>
<text>{{ goodsData.collect ? '取消收藏' : '收藏' }}</text>
</view>
</template>
<view class="flex justify-between padding">
<view class="text-primary">
<price
:goodsData="goodsData"
:priceType="goodsData.priceType"
:desc="goodsData.unit"
:scribingPrice="goodsData.scribingPrice"
/>
</view>
<view class="text-xs normal" color="#707070">{{ goodsData.orderNum }}人预约过</view>
</view>
</card>
<!-- 规格选择 -->
<!-- v-if="goodsData.staffList?.length > 0" -->
<goods-spec
ref="goodsSpecRef"
v-model:goodsNum="goodsNum"
:goodsImage="goodsData.goodsImageList"
:goodsName="goodsData.name"
:goodsPrice="goodsData.price"
:unitDesc="goodsData.unit"
@confirm="handleOrder"
/>
<!-- 服务师傅 -->
<!-- 临时更改-屏蔽(初步上线) -->
<!-- <card title="服务师傅">
<view class="w-full overflow-hidden">
<scroll-view class="whitespace-nowrap pt-[20rpx] pl-[30rpx]" scroll-x="true">
<block v-for="item in goodsData.staffList" :key="item.id">
<view
class="w-[100rpx] mr-[54rpx] text-center inline-block"
@click="
goPage(`/bundle/pages/master_worker_detail/index?id=${item.id}`)
"
>
<u-image
:src="item?.userVo?.avatar"
width="100"
height="100"
border-radius="50%"
></u-image>
<view class="truncate normal mt-[20rpx] text-base">
{{ item.name }}
</view>
</view>
</block>
</scroll-view>
</view>
</card> -->
<!-- 用户评价 -->
<card
class="comment"
title="用户评价"
:url="`/bundle/pages/evaluate_goods/index?id=${goodsId}`"
>
<!-- <template #sub-title>
<span class="text-muted font-normal text-sm">
({{ goodsData.goods_comment_total
}}{{ goodsData.goods_comment_total > 0 ? '+' : '' }})
</span>
</template> -->
<template #header-right>
<view class="flex text-sm text-muted items-center">
<text class="mr-[14rpx]">全部评价</text>
<u-icon name="arrow-right" color="#707070" size="22"></u-icon>
</view>
</template>
<template v-if="goodsData.goods_comment.length">
<block v-for="commenItem in goodsData.goods_comment" :key="commenItem.id">
<view class="comment mt-[20rpx] pr-[30rpx]">
<view class="flex justify-between py-0.5">
<view class="flex p-[20rpx]">
<u-image
:src="commenItem?.avatarUrl || defaultAvatar"
width="80"
height="80"
border-radius="50%"
></u-image>
<view class="ml-[20rpx]">
<view class="text-base normal">
{{ commenItem?.nickname || FieldType.defalutNickname }}
</view>
<view class="text-xs text-muted">
{{ commenItem.updateTime }}
</view>
</view>
</view>
<u-rate
:count="5"
v-model="commenItem.serviceComment"
size="28"
inactive-icon="star-fill"
activeColor="#FBC02D"
disabled
class="mt-[20rpx]"
></u-rate>
</view>
<view class="text-base normal ml-[20rpx] line-3">
{{ commenItem.comment }}
</view>
<view
class="flex flex-wrap ml-[20rpx]"
v-if="commenItem.commentUrlList && commenItem.commentUrlList.length > 0"
>
<block v-for="(item, index) in commenItem.commentUrlList" :key="index">
<view
class="mt-[10rpx] comment-image"
:class="{ 'mr-percent': (index + 1) % 4 != 0 }"
@click.stop="previewImage(index, commenItem.commentUrlList)"
>
<u-image
:src="item"
width="100%"
height="150"
border-radius="15rpx"
></u-image>
</view>
</block>
</view>
</view>
</block>
</template>
<view class="text-center p-[40rpx] text-muted" v-else>暂无评价</view>
</card>
<!-- 服务详情 -->
<view>
<card title="商品详情页" :border_bottom="false">
<view class="p-[24rpx]" v-if="goodsData.content">
<!-- <mp-html :content="goodsData.content" /> -->
<u-parse :html="goodsData.content"></u-parse>
</view>
<view class="text-center p-[40rpx] text-muted" v-else></view>
</card>
</view>
<!-- <RecommendGoods :list="serviceList" /> -->
<!-- </z-paging> -->
<!-- <StickySpec
class="sticky-spec"
:goodsData="goodsData"
@scroll-offset="handleScrollOffset"
/> -->
<!-- 占位 -->
<view :style="{ height: `${footerHeight}rpx` }"></view>
<view class="footer">
<button class="customer" type="default" open-type="contact">
<image src="@/static/images/kefu_black.png" alt="" mode="widthFix" />
<view class="text-xs">客服</view>
</button>
<!-- 暂时隐藏该条件判断 -->
<button
class="bg-primary text-lg text-white leading-[80rpx] h-[80rpx] btn ml-[30rpx] rounded-3xl"
@click="onAppointment"
>
立即抢购
</button>
<!-- <button
class="text-lg text-[#696969] leading-[80rpx] h-[80rpx] btn ml-[30rpx] rounded-3xl"
v-else
>
暂无法预约
</button> -->
</view>
</view>
<view v-show="!isEmpty" class="empty">
<u-empty
text="抱歉,该服务项目不存在~"
:src="'/static/images/empty/collection.png'"
:icon-size="300"
color="#888888"
>
<template #bottom>
<view class="empty-bottom">
<button
class="bg-primary text-lg text-white leading-[80rpx] h-[80rpx]"
@click="goHome"
>
去看看其它
</button>
</view>
</template>
</u-empty>
</view>
</template>
<script setup lang="ts">
import {
ref,
reactive,
shallowRef,
unref,
nextTick,
onMounted,
computed,
getCurrentInstance
} from 'vue'
import { onLoad, onShow, onUnload, onShareAppMessage, onPageScroll } from '@dcloudio/uni-app'
import LaSwiper from '@/components/la-swiper/la-swiper.vue'
import Card from './components/card.vue'
import GoodsSpec from './components/goods-spec.vue'
import Price from '@/components/price/index.vue'
// import StickySpec from './components/sticky-spec.vue'
import RecommendGoods from './components/recommend-goods.vue'
import {
apiGoodsDetail,
apiGoodsCollection,
apiGoodsCollectionCancel,
apiRuleList
} from '@/api/goods'
import { apiEvaluateGoodsLists } from '@/api/goods'
import { getRect, removeStorageData } from '@/utils/util'
import { useUserStore } from '@/stores/user'
import { useAppStore } from '@/stores/app'
import defaultAvatar from '@/static/images/user/default_avatar.png'
import { FieldType } from '@/enums/appEnums'
import { storeToRefs } from 'pinia'
import { useTabs } from '@/hooks/useCoupon'
import { apiRecommendService } from '@/api/store'
import { PriceEnum } from '@/enums/appEnums'
interface Rule {
intervalTime: number
value: number
id: number
type: number
}
export type GOODS = {
name: string // 服务名称
remarks: string // 服务简介
unit: string // 单位描述
image: string // 主图
price: string // 价格
scribingPrice: string | number // 划线价格
content: string // 服务详情
orderNum: string // 预约人数
collect: string // 是否收藏
goodsImageList: any // 轮播图
goods_comment: any // 服务评价
staffList: any // 服务师傅
goods_comment_total: number //评价总量
[index: string]: string | number | any
rules: Rule[]
priceType: PriceEnum
maxPrice: number
minPrice: number
}
type TIME = {
year: string
date: string
start_time: string
end_time: string
}
const goodsData = reactive<GOODS>({
name: '',
remarks: '',
unit: '',
image: '',
price: '',
scribingPrice: '',
content: '',
orderNum: '',
collect: '',
collectId: '',
goodsImageList: [],
goods_comment: [],
staffList: [],
goods_comment_total: 0,
rules: [],
priceType: PriceEnum.CUSTOMER_PRICE,
maxPrice: 0,
minPrice: 0
})
const userStore = useUserStore()
const appStore = useAppStore()
const { userInfo } = storeToRefs(userStore)
const { cityInfo } = storeToRefs(appStore)
const isEmpty = ref(true)
const goodsId = ref<number | string>('')
const goodsSpecRef = shallowRef<InstanceType<typeof GoodsSpec> | any>()
const appointTime = ref<TIME>({
year: '',
date: '',
start_time: '',
end_time: ''
})
const goodsNum = ref(1)
const footerHeight = ref(0)
const paging = shallowRef()
const serviceList = ref<any>([])
// 获取商品详情
const initGoodaDetail = async (cityId?: number): Promise<void> => {
if (cityId || appStore.cityInfo.city_id) {
try {
const res: GOODS = await apiGoodsDetail({
id: goodsId.value,
cityId: cityId || appStore.cityInfo.city_id
})
for (const key in goodsData) {
if (res[key] != null && res[key] != undefined) {
goodsData[key] = res[key]
}
}
} catch (error) {
console.log(error)
isEmpty.value = false
}
getUserEvaluateLists()
getGoodRule()
}
}
// 获取用户评价
const getUserEvaluateLists = async () => {
const { count, lists } = await apiEvaluateGoodsLists({
goodsId: +goodsId.value
})
// 优先显示评价星级高,有填写评价内容,有晒图的评论
goodsData.goods_comment = lists
.sort((a, b) => b.serviceComment - a.serviceComment)
.filter(i => i.comment || (i.commentUrlList && i.commentUrlList.length > 0))
.slice(0, 2)
goodsData.goods_comment_total = count
if (goodsData.goods_comment.length == 0 && lists.length > 0) {
goodsData.goods_comment = lists.slice(0, 2)
}
console.log('评价:', goodsData.goods_comment)
}
/**获取退单规则 */
const getGoodRule = async () => {
try {
const res = await apiRuleList({ id: unref(goodsId) })
goodsData['rules'] = res
} catch (error) {}
}
// 收藏操作
const handleCollection = async (collect: boolean | string | null): Promise<void> => {
if (!userStore.isLogin) {
return uni.$u.toast('请先登录!')
}
try {
collect ? collectionCancel() : collection()
} catch (error) {
console.log('收藏请求错误', error)
}
}
// 取消收藏
const collectionCancel = async () => {
await apiGoodsCollectionCancel({
id: unref(goodsData.collectId)
})
initGoodaDetail()
}
// 收藏
const collection = async () => {
await apiGoodsCollection({
goodsId: unref(goodsId)
})
initGoodaDetail()
}
// 立即预约
const onAppointment = () => {
goodsSpecRef.value.showPop = true
handleOrder()
}
// 处理预约
const handleOrder = () => {
try {
const goods = {
goods_num: unref(goodsNum),
id: goodsId.value
}
// unref(appointTime).date = ''
// if (unref(appointTime).date === '') {
// return goPage(`/bundle/pages/appoint_time/index?params=${JSON.stringify(goods)}`)
// }
const params = {
goodsData: goods
// appointData: unref(appointTime)
}
goPage(`/pages/order_buy/index?params=${JSON.stringify(params)}`)
} catch (error) {
console.log('处理预约', error)
}
}
const goPage = (url: string) => {
uni.navigateTo({ url: url })
}
// 返回首页
const goHome = () => {
uni.reLaunch({ url: '/pages/index/index' })
}
onShareAppMessage(() => {
return {
title: goodsData?.name || '粤好生活',
imageUrl: '',
path: `/pages/goods/index?id=${unref(goodsId)}&cityId=${unref(cityInfo).city_id}&scene=${
unref(userInfo).distributorId
}`
}
})
// 查看评价图片
const previewImage = (current: number, urls: string[]) => {
uni.previewImage({
current,
urls
})
}
const handleScrollOffset = () => {
getRect('.sticky-spec').then(res => {
uni.pageScrollTo({
// scrollTop: screenHeight.value - res.top
})
})
}
onShow(() => {
removeStorageData('selectDate')
/**获取占位高度 */
getRect('.footer').then(res => {
footerHeight.value = (res.height + 20) * 2
})
})
onLoad(options => {
/**绑定分销商 */
if (options.scene) {
const { id, cityId, scene } = options
goodsId.value = Number(id)
initGoodaDetail(Number(cityId))
userStore.setDistId(Number(scene))
if (userStore.token) {
userStore.bindingDistId()
}
return
}
goodsId.value = options?.id || 0
initGoodaDetail()
// 监听上门时间选择
// uni.$on('appointTime', (event: TIME) => {
// appointTime.value = event
// })
const systemInfo = uni.getSystemInfoSync()
screenHeight.value = systemInfo.screenHeight
})
// 获取商品列表
const queryList = async (pageNo: number, pageSize: number) => {
if (!goodsId.value) return
try {
const { lists } = await apiRecommendService({
pageNo: pageNo,
pageSize: pageSize,
id: goodsId.value
})
paging.value.complete(lists)
} catch (e) {
console.log('报错=>', e)
//TODO handle the exception
paging.value.complete(false)
}
}
const screenHeight = ref(0)
const scrollOffset = ref(0)
uni.$on('refreshhome', () => {
// initGoodaDetail()
})
onUnload(() => {
// uni.$off(['appointTime'])
})
const offset = ref(0)
</script>
<style lang="scss" scoped>
// 收藏图标
.collection {
width: 44rpx;
height: 44rpx;
}
.padding {
padding: 0 30rpx;
}
.line-3 {
-webkit-line-clamp: 3;
overflow: hidden;
word-break: break-all;
text-overflow: ellipsis;
display: -webkit-box; // 弹性伸缩盒
-webkit-box-orient: vertical; // 设置伸缩盒子元素排列方式
}
// 底部按钮
.footer {
left: 0;
bottom: 0;
width: 100%;
position: fixed;
padding: 20rpx 30rpx;
background-color: #ffffff;
box-shadow: 2rpx 2rpx 22rpx rgba($color: #000000, $alpha: 0.2);
padding-bottom: calc(env(safe-area-inset-bottom) + 20rpx);
display: flex;
z-index: 10000;
.customer {
height: 80rpx;
margin: 0;
background: none;
display: flex;
flex-direction: column;
line-height: normal;
image {
width: 100%;
height: 50rpx;
}
}
.btn {
flex: 1;
}
}
// 服务下架或不存在时
.empty {
padding-top: 200rpx;
.empty-bottom {
width: 90vw;
margin-top: 130rpx;
}
}
.comment-image {
width: 23%;
&.mr-percent {
margin-right: 2.67%;
}
}
::v-deep .zp-back-to-top {
bottom: calc(env(safe-area-inset-bottom) + 150rpx) !important;
}
</style>