<template>
|
<PageLayout class="serciceDetail-page-wrapper" :title="detail?.name ?? ''" :need-auth="false">
|
<LoadingLayout :loading="isLoading" :error="isError" :loadError="refetch">
|
<InfiniteLoading
|
commonMode
|
:refetch="refetch"
|
:isLoading="isLoading"
|
:isError="isError"
|
:showMoreText="false"
|
scrollViewClassName="common-infinite-scroll-list-no-padding"
|
>
|
<div class="serciceDetail-top-view">
|
<SquareView>
|
<nut-swiper
|
:auto-play="3000"
|
v-if="detail?.files?.length > 0"
|
class="serciceDetail-swiper"
|
>
|
<nut-swiper-item
|
v-for="(item, index) in detail.files"
|
:key="item"
|
class="serciceDetail-swiper-item"
|
>
|
<img
|
:src="setOSSLink(item)"
|
class="serciceDetail-swiper-item-img"
|
draggable="false"
|
/>
|
</nut-swiper-item>
|
</nut-swiper>
|
</SquareView>
|
<div class="serciceDetail-top-view-title-wrapper">
|
<div class="serciceDetail-price-wrapper">
|
<div class="serciceDetail-price">{{ toThousand(minPrice) }}</div>
|
<div class="serciceDetail-price-unit">元起</div>
|
</div>
|
<div class="serciceDetail-top-view-title">{{ detail?.name ?? '' }}</div>
|
</div>
|
</div>
|
|
<List class="serciceDetail-content-list">
|
<ListItem title="规格" @click="openSkuDialog()">
|
<template #extra>
|
<div>共{{ detail.specs?.length }}类</div>
|
</template>
|
</ListItem>
|
</List>
|
<ProTabs
|
v-model="tab"
|
name="serciceDetail-content-tab"
|
class="serciceDetail-content-tabs"
|
flexTitle
|
>
|
<ProTabPane :title="`服务详情`" pane-key="1">
|
<RichEditorContent :content="detail?.description ?? ''"></RichEditorContent>
|
</ProTabPane>
|
<ProTabPane :title="`客户评价`" pane-key="2">
|
<NoData />
|
</ProTabPane>
|
<ProTabPane :title="`相关推荐`" pane-key="3">
|
<InfiniteLoading
|
scrollViewClassName="common-infinite-scroll-list serciceDetail-recommend-list"
|
commonMode
|
v-bind="infiniteLoadingProps"
|
>
|
<template #renderItem="{ item }">
|
<StandardServiceCard
|
:file="item.file"
|
:name="item.name"
|
:minSpecPrice="item.minSpecPrice"
|
@click="goSerciceDetail(item)"
|
/>
|
</template>
|
</InfiniteLoading>
|
</ProTabPane>
|
</ProTabs>
|
</InfiniteLoading>
|
<Sku
|
v-model:visible="skuState.visible"
|
:sku="skuState.sku"
|
v-model:goods="skuState.goods"
|
@clickBtnOperate="clickBtnOperate"
|
>
|
</Sku>
|
<PageFooter>
|
<PageFooterAction
|
:icon="detail.isCollection ? IconAttentionActive : IconAttention"
|
text="收藏"
|
:isFlex="false"
|
@click="handleAttention"
|
></PageFooterAction>
|
<PageFooterAction
|
:icon="IconShare"
|
text="客服"
|
:isFlex="false"
|
:open-type="'contact'"
|
></PageFooterAction>
|
<PageFooterBtn type="primary" @click="openSkuDialog()">预约下单</PageFooterBtn>
|
</PageFooter>
|
</LoadingLayout>
|
</PageLayout>
|
</template>
|
|
<script setup lang="ts">
|
import { useInfiniteLoading, useStandardServiceDetail } from '@12333/hooks';
|
import Taro from '@tarojs/taro';
|
import * as standardOrderServices from '@12333/services/apiV2/standardOrder';
|
import * as standardServiceServices from '@12333/services/apiV2/standardService';
|
import { toThousand, setOSSLink, Message } from '@12333/utils';
|
import {
|
Sku,
|
Goods,
|
SkuItem,
|
SkuUtils,
|
List,
|
ListItem,
|
ProTabs,
|
ProTabPane,
|
SquareView,
|
} from '@12333/components';
|
import { useAccessLogin } from '@/hooks';
|
import IconShare from '@/assets/flexJob/icon-share.png';
|
import IconAttention from '@/assets/flexJob/icon-attention-lg.png';
|
import IconAttentionActive from '@/assets/flexJob/icon-attention-lg-active.png';
|
import { EnumPagedListOrder } from '@12333/constants';
|
|
defineOptions({
|
name: 'serciceDetail',
|
});
|
|
const router = Taro.useRouter();
|
const id = router.params?.id ?? '';
|
|
const tab = ref('1');
|
|
const { isLoading, isError, detail, refetch, minPrice } = useStandardServiceDetail({
|
id,
|
onSuccess(res) {
|
skuState.sku = [
|
{
|
id: SkuUtils.DefaultSkuSpecId,
|
name: '规格',
|
list: res.specs.map((item, index) => ({
|
id: item.id,
|
name: item.name,
|
price: item.price,
|
active: index === 0,
|
disable: false,
|
})),
|
},
|
];
|
skuState.goods = {
|
skuId: SkuUtils.DefaultSkuSpecId,
|
price: res.specs[0].price,
|
imagePath: setOSSLink(res.files[0]),
|
name: res.name,
|
};
|
queryState.jobCode = res.jobCode;
|
},
|
});
|
|
const skuState = reactive({
|
visible: false,
|
sku: [] as SkuItem[],
|
goods: {} as Goods,
|
});
|
|
// 底部操作按钮触发
|
const clickBtnOperate = (op: { type: string; value: number }) => {
|
goAddStandardOrder(op.value);
|
};
|
|
const openSkuDialog = () => {
|
skuState.visible = true;
|
};
|
|
const goAddStandardOrder = useAccessLogin((specNumber: number) => {
|
const spec = SkuUtils.getCurrentActiveSpec(skuState.sku);
|
Taro.navigateTo({
|
url: `${RouterPath.addStandardOrder}?specNumber=${specNumber}&specId=${spec.id}&id=${id}`,
|
});
|
});
|
|
const handleAttention = useAccessLogin(async () => {
|
try {
|
let params: API.CollectionStandardServiceCommand = {
|
ids: [id],
|
isCollect: !detail.value.isCollection,
|
};
|
let res = await standardServiceServices.collectionStandardService(params);
|
if (res) {
|
refetch({ type: 'inactive' });
|
}
|
} catch (error) {}
|
});
|
|
const queryState = reactive({
|
ignoreId: id,
|
jobCode: '',
|
});
|
|
const { infiniteLoadingProps, invalidateQueries } = useInfiniteLoading(
|
({ pageParam }) => {
|
let params: API.GetStandardServicesQuery = {
|
pageModel: {
|
rows: 20,
|
page: pageParam,
|
orderInput: [{ property: 'createdTime', order: EnumPagedListOrder.Desc }],
|
},
|
ignoreId: queryState.ignoreId,
|
jobCode: queryState.jobCode,
|
};
|
|
return standardServiceServices.getOpenStandardServices(params, {
|
showLoading: false,
|
});
|
},
|
{
|
queryKey: ['standardServiceServices/getOpenStandardServices', queryState],
|
enabled: computed(() => !!queryState.jobCode),
|
}
|
);
|
|
function goSerciceDetail(item: API.GetStandardServicesQueryResultItem) {
|
Taro.navigateTo({
|
url: `${RouterPath.serciceDetail}?id=${item.id}`,
|
});
|
}
|
</script>
|
|
<style lang="scss">
|
@import '@/styles/common.scss';
|
|
.serciceDetail-page-wrapper {
|
.serciceDetail-swiper {
|
width: 100%;
|
height: 100%;
|
|
.serciceDetail-swiper-item-img {
|
width: 100%;
|
height: 100%;
|
object-fit: cover;
|
/* 可选:调整图片裁剪的对齐方式(默认居中) */
|
object-position: center center;
|
}
|
}
|
|
.serciceDetail-top-view {
|
margin-bottom: 20px;
|
background-color: #fff;
|
|
.serciceDetail-top-view-title-wrapper {
|
padding: 24px boleGetCssVar('size', 'body-padding-h') 32px;
|
|
.serciceDetail-price-wrapper {
|
display: flex;
|
align-items: flex-end;
|
margin-bottom: 24px;
|
|
.serciceDetail-price {
|
font-weight: 600;
|
font-size: 48px;
|
color: #ff6414;
|
line-height: 52px;
|
margin-right: 8px;
|
}
|
|
.serciceDetail-price-unit {
|
font-weight: 400;
|
font-size: 28px;
|
color: #ff6414;
|
line-height: 40px;
|
}
|
}
|
|
.serciceDetail-top-view-title {
|
font-weight: 500;
|
font-size: 32px;
|
color: boleGetCssVar('text-color', 'primary');
|
line-height: 44px;
|
}
|
}
|
}
|
|
.serciceDetail-content-list {
|
margin-bottom: 20px;
|
}
|
|
.serciceDetail-recommend-list {
|
.infinite-list-inner {
|
display: grid;
|
grid-template-columns: repeat(2, 1fr);
|
grid-gap: 20px;
|
}
|
}
|
}
|
</style>
|