604 lines
18 KiB
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>
|