123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879 |
- <template>
- <div class="w-full h-auto min-h-[100%] bg-[#fff] pb-[20px] detail" v-loading="loading">
- <div class="main-container flex justify-between" v-if="Object.keys(detail).length">
- <div class="flex-1">
- <div class="bg-[#fff] py-[22px] pr-[36px] flex">
- <div class="w-[350px] flex flex-col">
- <el-image class="w-[350px] h-[350px]" :src="img(detail.goods.goods_image_thumb_small.length ? detail.goods.goods_image_thumb_small[imgActive] : '')" fit="contain">
- <template #error>
- <img src="@/assets/images/goods_default.png" class="w-[350px] h-[350px]">
- </template>
- </el-image>
- <div class="flex justify-between mt-4">
- <div class="h-[80px] py-[15px] w-[17px] flex items-center justify-center" @click="imgListClick('left')">
- <el-icon color="#ccc" size="14">
- <ArrowLeftBold />
- </el-icon>
- </div>
- <div class="relative flex-1 overflow-hidden h-[80px]">
- <div class="absolute flex items-center h-[100%] transition-all ease-in-out duration-500" :style="{ 'left': imgListLeft + 'px' }">
- <template v-if="detail.goods.goods_image_thumb_small">
- <div v-for="(item, index) in detail.goods.goods_image_thumb_small" :key="index" @mouseover="imgListHover(index)">
- <el-image :class="['w-[54px] h-[54px] mx-[5px] box-border border-[1px] border-solid cursor-pointer', imgActive == index ? 'border-[var(--el-color-primary)]' : 'border-transparent']" :src="img(item)" alt="" fit="contain">
- </el-image>
- </div>
- </template>
- </div>
- </div>
- <div class="h-[80px] py-[15px] w-[17px] flex items-center justify-center" @click="imgListClick('right')">
- <el-icon color="#ccc" size="14">
- <ArrowRightBold />
- </el-icon>
- </div>
- </div>
- </div>
- <div class="flex-1 ml-[30px]">
- <div class="text-[18px] leading-[24px] text-[#333] truncate max-w-[530px] mb-[16px]">
- {{ detail.goods.goods_name }}
- </div>
- <div class="text-[14px] leading-[18px] text-[#999] truncate max-w-[530px] mb-[30px]" v-if="detail.goods.sub_title">
- {{ detail.goods.sub_title }}
- </div>
- <div v-if="priceType == 'discount_price'" class="discount-bg h-[80px] mb-[10px] box-border px-[10px] py-[15px] flex items-center justify-between">
- <div class="flex-1 flex items-center justify-between">
- <div class="text-[#fff]">
- <span class="text-[16px] mr-[2px] price-font">¥</span>
- <span class=" text-[26px] font-bold leading-[32px] price-font">{{parseFloat(goodsPrice).toFixed(2) }}</span>
- </div>
- <div>
- <div class="h-[16px] mb-[10px] flex justify-end">
- <img class="h-[16px] w-auto" src="@/assets/images/addon/discount_price.png" alt="">
- </div>
- <div class="flex items-baseline text-[#fff] leading-[18px]">
- <span class="text-[14px] mr-[6px] ">{{ t('endOfRange') }}</span>
- <el-countdown :value="discountTime" format="HH:mm:ss" :value-style="{ fontSize: '14px',color:'#fff',letterSpacing: '1px' }"/>
- </div>
- </div>
- </div>
- <div class="w-[1px] h-[36px] bg-[#E7E7E7] mx-[22px] opacity-60"></div>
- <div class="text-[#fff]">
- <div class="text-[14px] mb-[6px] oppoSans-R">{{ t('totalSales') }}</div>
- <div class=" text-center text-[20px] leading-[26px] oppoSans-R">{{ detail.goods.sale_num }}{{ detail.goods.unit }}</div>
- </div>
- </div>
- <div class="px-[10px] py-[20px] border-box bg-img mb-[30px]">
- <div class="flex items-center justify-between flex-1 mb-[20px]" v-if="priceType == 'discount_price'">
- <div class="flex items-baseline">
- <span class="text-[14px] w-[70px] mr-[10px] text-[#999]">{{ t('goodsPrice') }}</span>
- <span class="text-[14px] mr-[2px] text-[#EF000C] price-font">¥</span>
- <span class="text-[#EF000C] text-[24px] leading-[32px] price-font">{{parseFloat(detail.price).toFixed(2) }}</span>
- </div>
- </div>
- <div class="flex items-center justify-between flex-1 mb-[20px]" v-if="priceType != 'discount_price'">
- <div class="flex items-baseline">
- <span class="text-[14px] w-[70px] mr-[10px] text-[#999]">{{ t('goodsPrice') }}</span>
- <span class="text-[14px] mr-[2px] text-[#EF000C] price-font">¥</span>
- <span class="text-[#EF000C] text-[24px] leading-[32px] price-font">{{parseFloat(goodsPrice).toFixed(2) }}</span>
- <img v-if="priceType == 'member_price'" class="h-[16px] ml-[6px] w-[54px]" src="@/assets/images/addon/VIP.png" />
- </div>
- <div class="text-[12px] leading-[16px]">
- <div class="text-[#999] mb-[2px] oppoSans-R">{{ t('totalSales') }}</div>
- <div class="text-[var(--el-color-primary)] text-center oppoSans-R">{{ detail.goods.sale_num }}{{ detail.goods.unit }}</div>
- </div>
- </div>
- <div class="flex items-center mb-[20px]" v-if="Number(detail.market_price)">
- <span class="text-[14px] w-[70px] mr-[10px] text-[#999] ">{{ t('marketPrice') }}</span>
- <span class="text-[12px] text-[#666] line-through price-font">¥{{ detail.market_price }}</span>
- </div>
- <div class="flex items-center" :class="{'mb-[20px]':detail.label_info}" v-if="couponList.length">
- <span class="text-[14px] w-[70px] mr-[10px] text-[#999]">{{ t('coupon') }}</span>
- <div class="max-w-[430px] flex-nowrap flex items-center overflow-hidden">
- <template v-for="(item, index) in couponList" :key="index">
- <div v-if="index < 5" class="text-[12px] whitespace-nowrap px-[10px] py-[3px] text-[rgba(239,0,12,0.85)] mr-[10px] coupon-bg cursor-pointer" @click="couponListShow = true" >
- {{ item.title }}
- </div>
- </template>
- </div>
- </div>
- <div class="flex items-center" v-if="detail.label_info">
- <span class="text-[14px] w-[70px] mr-[10px] text-[#999]">{{ t('goodsLabel') }}</span>
- <div class="flex-wrap flex items-center ">
- <template v-for="(item, index) in detail.label_info" :key="index">
- <div class="mb-[10px] px-[10px] py-[2px] text-[12px] leading-[16px] text-[#fff] mr-[10px] rounded-[2px]" :class="{'bg-[#FF520D]': (index + 1) % 3 == 1, 'bg-[#E72120]': (index + 1) % 3 == 2, 'bg-[#133B87]': (index + 1) % 3 == 0}">
- {{ item.label_name }}
- </div>
- </template>
- </div>
- </div>
- </div>
- <div class="flex items-center mb-[18px]" v-if="detail.brand_info">
- <span class="text-[14px] w-[80px] pl-[10px] mr-[10px] text-[#999]">{{ t('goodsBrand') }}</span>
- <div class="flex-1 text-[14px] text-[#666]">{{detail.brand_info.brand_name}}</div>
- </div>
- <div class="flex items-center" v-if="detail.service && detail.service.length">
- <span class="text-[14px] w-[80px] pl-[10px] mr-[10px] text-[#999]">{{t('service')}}</span>
- <div class="flex-1 text-[14px] text-[#666] truncate max-w-[443px]">
- <template v-for="(item,index) in detail.service">
- <span>{{item.service_name}}</span>
- <span v-if="index < detail.service.length-1">,</span>
- </template>
- </div>
- </div>
- <!-- 规格 -->
- <template v-for="(item, index) in goodsDetail.goodsSpec" :key="index">
- <div class="flex mt-[20px]">
- <div class="h-[32px] leading-[32px] text-[14px] text-[#999] w-[80px] pl-[10px] pt-[2px] box-border flex-shrink-0 mr-[10px] truncate">{{ item.spec_name }}</div>
- <div class="flex items-center flex-wrap">
- <template v-for="(subItem, subIndex) in item.values" :key="subIndex">
- <div class="px-[24px] h-[32px] text-[12px] leading-[32px] box-border border-[1px] border-solid mr-[12px] mb-[16px] relative cursor-pointer"
- :class="{ 'border-[var(--el-color-primary)] text-[var(--el-color-primary)] ': subItem.selected }"
- @click="change(subItem, index)">
- <span>{{ subItem.name }}</span>
- <span v-if="subItem.selected" class="iconfont icon-icon-selected absolute right-[-1px] bottom-[-8px] text-[var(--el-color-primary)]"></span>
- </div>
- </template>
- </div>
- </div>
- </template>
- <div class="flex mt-[18px]">
- <div class="h-[32px] leading-[32px] text-[14px] text-[#999] w-[80px] pl-[10px] pt-[2px] box-border flex-shrink-0 mr-[10px]">{{ t('num') }}</div>
- <div class="flex items-center">
- <el-input-number v-model.number="buyNum" :max="detail.stock <= 0 ? 1 : detail.stock " :min="1" :step="1" step-strictly @blur="inputNum(buyNum)"/>
- <span class="text-[12px] ml-[10px] text-[#999]">{{t('stock')}}{{ detail.stock }}{{ t('unit') }}</span>
- </div>
- </div>
- <div class="mt-[46px] flex items-center ">
- <div class="flex items-center" v-if="detail.goods.status == 1">
- <el-button v-if="isShowSingleSku" @click="buyFn('buy_now')" class="w-[138px] !h-[50px] border-[1px] !border-[var(--el-color-primary)] border-solid !text-[var(--el-color-primary)] !ml-[0] mr-[10px] ">{{t('buyNow')}}</el-button>
- <el-button v-if="goodsDetail.goods.goods_type == 'real' || (goodsDetail.goods.goods_type == 'virtual' && goodsDetail.goods.virtual_receive_type != 'verify')" class="!h-[50px] !w-[138px] !m-0 !bg-[var(--el-color-primary)] !border-[var(--el-color-primary)]" @click="buyFn('join_cart')">
- <span class="text-[#fff]">{{t('addCart')}}</span>
- </el-button>
- <el-button v-if="!isShowSingleSku" class="w-[138px] !h-[50px] border-[1px] !border-[#ccc] border-solid !bg-[#ccc] !ml-[10px] !text-[#fff]">{{ t('soldOut') }}</el-button>
- </div>
- <div v-else>
- <el-button class="w-[138px] !h-[50px] border-[1px] !border-[#ccc] border-solid !bg-[#ccc] !m-0 ">
- <span class="text-[#fff]">{{ t('goodsDelist') }}</span>
- </el-button>
- </div>
- <div class="ml-[10px] border-[1px] border-solid border-[var(--el-color-primary)] rounded-[4px]">
- <el-popover placement="bottom" :width="170" trigger="hover">
- <template #reference>
- <div class="h-[46px] w-[46px] flex items-center justify-center cursor-pointer">
- <span class="iconfont icon-fenxiang !text-[24px] !text-[var(--el-color-primary)] "></span>
- </div>
- </template>
- <div class="text-center flex items-center justify-center">
- <span class="mx-[10px]">{{ t('codesShare') }}</span>
- </div>
- <div class="promote-img flex justify-center items-center bg-[#f8f8f8] w-[146px] h-[146px]">
- <el-image :src="wapImage" />
- </div>
- </el-popover>
- </div>
- <div class="mx-[20px] w-[1px] h-[16px] bg-[#dcdfe6]"></div>
- <div>
- <div class="cursor-pointer" v-if="!isCollect" @click="collectFn">
- <span class="iconfont icon-shoucang1 !text-[16px] text-[#999] mr-[6px]"></span>
- <span class="text-[14px] text-[#999] oppoSans-R">{{ t('collect') }}</span>
- </div>
- <div class="cursor-pointer" v-else @click="collectFn">
- <span class="iconfont icon-yishoucang !text-[16px] text-[var(--el-color-primary)] mr-[6px]"></span>
- <span class="text-[14px] text-[var(--el-color-primary)] oppoSans-R ">{{ t('collected') }}</span>
- </div>
- </div>
- </div>
- </div>
- </div>
- <div class="bg-[#fff] mt-[20px] mb-[62px] pb-5">
- <el-tabs v-model="detailActiveName" class="detail-active-wrap" type="card">
- <el-tab-pane :label="t('goodsDetail')" name="detail">
- <div class="">
- <div v-html="detail.goods.goods_desc" v-if="detail.goods.goods_desc"></div>
- </div>
- </el-tab-pane>
- <el-tab-pane :label="t('goodsArguments')" name="attribute" v-if="detail.goods && detail.goods.attr_format && Object.keys(detail.goods.attr_format).length">
- <el-row>
- <template v-for="(item,index) in detail.goods.attr_format" :key="index">
- <el-col :span="12">
- <div class="flex items-center mb-[12px] leading-[32px] text-[14px]">
- <div class="w-[150px] text-[#999] mr-[12px] text-right truncate">{{item.attr_value_name}}</div>
- <div class="input-width">{{ Array.isArray(item.attr_child_value_name) ? item.attr_child_value_name.join(',') : item.attr_child_value_name }}</div>
- </div>
- </el-col>
- </template>
- </el-row>
- </el-tab-pane>
- <el-tab-pane :label="t('goodsEvaluate')" name="evalate">
- <div class="pt-[22px]">
- <div class="flex items-center justify-between">
- <div class="flex">
- <span v-for="(item, index) in evaluateSort" :key="index" @click="handelClick(item)"
- class="px-[27px] py-[7px] text-[14px] text-[#282828] leading-[20px] mr-[14px] cursor-pointer bg-[#f7f7f7]"
- :class="{ '!bg-[var(--el-color-primary)] !text-[#fff]': item.status == evaluateTableData.searchParam.currentIndex }">{{item.name }}</span>
- </div>
- </div>
- <div v-loading="evaluateTableData.loading" class="min-h-[300px]">
- <div v-if="evaluateTableData.data.length">
- <div v-for="(item, index) in evaluateTableData.data" :key="index"
- class="py-[32px] flex box-border border-0 border-b-[2px] border-solid border-[#F2F2F2] border-box">
- <img src="@/assets/images/user.png"
- v-if="item.is_anonymous == 1 || item.member_head == ''"
- class="rounded-full mr-[20rpx] w-[40px] h-[40px]">
- <el-image :src="img(item.member_head ? item.member_head : '')" v-else
- fit="contain" lazy class="rounded-full mr-[20rpx] w-[40px] h-[40px]">
- <template #error>
- <img src="@/assets/images/user.png" class="rounded-full w-[40px] h-[40px]">
- </template>
- </el-image>
- <div class="max-w-[900px] ml-[12px]">
- <div class="text-[14px] text-[#000] leading-[20px] flex items-center">
- <span class="mr-[8px]">{{ item.member_name }}</span>
- <el-rate v-model="item.scores" text-color="#FA6400" :colors="['#FA6400', '#FA6400', '#FA6400']" size="small" disabled />
- </div>
- <div class="">
- <span class="text-[14px] text-[#999] leading-[20px]">
- {{ item.create_time }}
- </span>
- </div>
- <div class="mt-[2px] text-[14px] text-[#000] leading-[20px]">
- {{ item.content }}</div>
- <div class="mt-[10px] flex flex-wrap" v-if="item.images.length">
- <div v-for="(subItem, subIndex) in item.images" :key="subIndex"
- class="mr-[20px] mb-[10px]">
- <el-image style="width: 100px; height: 100px" :src="img(subItem)"
- :zoom-rate="1.2" :max-scale="7" :min-scale="0.2" :initial-index="subIndex"
- :preview-src-list="item.imagesList" fit="cover" lazy :hide-on-click-modal="true" />
- </div>
- </div>
- <div class="mt-[15px] text-[14px] break-all leading-[20px]" v-if="item.explain_first">
- <span class="text-[#ff7f5b]">{{ t('merchantReply') }}</span>
- <span>{{ item.explain_first}}</span>
- </div>
- </div>
- </div>
- </div>
- <div v-else class="min-h-[300px]">
- <el-empty :description="t('noEvaluate')" :image-size="200" :image="img('static/resource/images/system/empty.png')" />
- </div>
- </div>
- <div class="mt-[16px] flex justify-end" v-if="evaluateTableData.data.length">
- <el-pagination v-model:current-page="evaluateTableData.page"
- v-model:page-size="evaluateTableData.limit" :total="evaluateTableData.total"
- @current-change="getEvaluateListFn" />
- </div>
- </div>
- </el-tab-pane>
- </el-tabs>
- </div>
- </div>
- <div class="w-[210px] ml-[40px]">
- <div class="flex flex-col items-center justify-center w-full h-[350px] mt-[32px] px-[20px] bg-[#fff] border-[1px] border-[#efefef] border-solid box-border">
- <div class="flex flex-col items-center justify-center h-[130px] border-0 border-b-[1px] border-[#efefef] border-dashed">
- <el-image class="w-[61px] h-[61px] rounded-[50%] mb-[19px]" :src="img(detail.shop_info.icon ? detail.shop_info.icon : '')" fit="contain" lazy>
- <template #error>
- <img src="@/assets/images/shop_default.png" alt="">
- </template>
- </el-image>
- <div class="w-[120px] text-[14px] truncate text-center">{{ detail.shop_info.site_name }}</div>
- </div>
- <div class="mt-[8px]">
- <div class="border-0 border-b-[1px] border-[#efefef] border-dashed">
- <div class="flex items-center text-[12px] mt-[8px] mb-[15px]">
- <span class="text-[#999] mr-[15px]">{{ t('shopType') }}</span>
- <span class="text-[#333]">{{ detail.shop_info.category_name }}</span>
- </div>
- <div class="flex items-center text-[12px] mt-[8px] mb-[15px]" v-if="detail.shop_info.is_self">
- <span class="text-[#999] mr-[15px]">{{ t('isSelf') }}</span>
- <span class="w-[32px] h-[18px] leading-[18px] text-center text-[#fff] bg-[var(--el-color-primary)] rounded-[2px] mr-[3px] text-[12px]">{{ t('self') }}</span>
- </div>
- <div class="flex items-center text-[12px] mb-[15px]">
- <span class="text-[#999] mr-[15px]">{{ t('phone') }}</span>
- <span class="text-[#333]">{{ detail.shop_info.phone }}</span>
- </div>
- </div>
- <div class="flex items-center justify-between mt-[14px]">
- <el-button
- class="w-[60px] !h-[30px] border-[1px] rounded-[2px] border-solid !border-[#dcdfe6] !mr-[4px] !text-[12px]"
- @click="goShopDetail(detail.shop_info.site_id)">{{ t('shopAround') }}</el-button>
- <el-button class="w-[60px] !h-[30px] border-[1px] rounded-[2px] border-solid !m-0 !border-[#dcdfe6] !text-[12px]"
- v-if="!isFollow" @click="collectShop(detail.shop_info.site_id, 1)">{{ t('followShop') }}</el-button>
- <el-button
- class="w-[60px] !h-[30px] border-[1px] rounded-[2px] border-solid !m-0 !border-[var(--el-color-primary)] !bg-[var(--el-color-primary)] !text-[#fff] !text-[12px]"
- v-else @click="collectShop(detail.shop_info.site_id, 0)">{{ t('followed') }}</el-button>
- </div>
- </div>
- </div>
- <div class="w-full box-border mt-[10px] px-[20px] border-[1px] border-solid border-[#efefef] rounede-[4px]">
- <div class="flex items-center justify-between h-[60px] text-[]">
- <div class="w-[35px] h-[1px] bg-[#efefef]"></div>
- <div class="text-[#5a5a5a]">{{ t('shopRecommend')}}</div>
- <div class="w-[35px] h-[1px] bg-[#efefef]"></div>
- </div>
- <div>
- <div class="mb-[20px] cursor-pointer" v-for="(item,index) in recommendList" :key="index" @click="toDetail(item.goods_id)">
- <div class="w-[170px] h-[170px]">
- <el-image class="w-[170px] h-[170px]" :src="img(item.goods_cover_thumb_mid ? item.goods_cover_thumb_mid : '')" fit="cover" />
- </div>
- <div>
- <div class="my-[10px] text-[13px] text-[#666] truncate">{{ item.goods_name }}</div>
- <div class="flex justify-between items-center">
- <div class="text-[var(--el-price)] font-700">
- <span class="text-[14px]">¥</span>
- <span class="text-[18px]">{{ item.goodsSku.price }}</span>
- </div>
- <div class="text-[#888] text-[12px]">{{ t('saled') }}{{ item.sale_num }}{{item.unit || t('unit')}}</div>
- </div>
- </div>
- </div>
- </div>
- </div>
- </div>
- </div>
- <!-- 优惠券列表 -->
- <el-drawer v-model="couponListShow" size="400" direction="rtl" custom-class="!bg-[#F6F6F6]">
- <template #header>
- <div>{{t('coupon')}}</div>
- </template>
- <template #default>
- <div>
- <div class="mb-[15px] flex items-center bg-[#fff] rounded-[8px] " v-for="(item, index) in couponList" :key="index">
- <div class="flex flex-col items-center justify-center bg-primary w-[86px] h-[80px] rounded-[8px] relative coupon-item">
- <div class="text-[#fff] ">
- <span class="text-[14px] price-font">¥</span>
- <span class="text-[26px] price-font">{{ item.price }}</span>
- </div>
- <span class="text-[12px] mt-[3px] text-[#fff] truncate max-w-[86px] border-box px-[4px]">{{ Number(item.min_condition_money) ? ('满' + item.min_condition_money + '元可以使用') : '无门槛优惠券' }}</span>
- </div>
- <div class="ml-[10px] flex-1 flex flex-col">
- <span class="text-[18px] text-[#333] leading-[24px]">{{ item.title }}</span>
- <span class="text-[14px] text-[#999] mt-[8px] leading-[18px]">{{ item.valid_type == 1 &&('领取之日起' + item.length + '天内有效') || item.valid_type == 2 && ('领取之日起至' + item.valid_end_time) }}</span>
- </div>
- <span v-if="item.btnType === 'collecting'" class="bg-primary w-[54px] text-center rounded-2xl text-[#fff] text-[12px] mr-[10px] py-[4px] cursor-pointer" @click="getCouponFn(item, index)">领取</span>
- <span v-else class="!bg-[#E28B8F] w-[54px] text-center rounded-2xl text-[#fff] text-[12px] mr-[10px] py-[4px]">{{item.btnType === 'collected' ? '已领完' : '已领取' }}</span>
- </div>
- </div>
- </template>
- </el-drawer>
- </div>
- </template>
- <script lang="ts" setup>
- import { ref, reactive, computed, watch } from 'vue'
- import storage from '@/utils/storage'
- import { getGoodsDetail, getEvaluateList, collect, cancelCollect, getRecommendGoods } from '@/addon/mall/api/goods'
- import { addBrowse } from '@/addon/mall/api/member'
- import { getMallGoodsCoupon, getCoupon } from '@/addon/mall/api/coupon'
- import { editShopCollect } from '@/addon/mall/api/shop'
- import { useRoute, useRouter } from 'vue-router'
- import useCartStore from '@/addon/mall/stores/cart'
- import useMemberStore from '@/stores/member'
- import { img ,getToken} from '@/utils/common'
- import QRCode from 'qrcode'
- const route = useRoute()
- const router = useRouter()
- // 会员信息
- const memberStore = useMemberStore()
- const userInfo = computed(() => memberStore.info)
- // 购物车数量
- const cartStore = useCartStore();
- cartStore.getList()
- const cartList = computed(() => cartStore.cartList)
- const detail = ref<any>({})
- let imgActive = ref(0)
- let loading = ref(true);
- let imgListLeft = ref(0)
- let detailActiveName = ref('detail')
- let discountTime = ref(0) //限时折扣倒计时
- // 商品详情
- const getGoodsDetailFn = (id: any) => {
- getGoodsDetail({ goods_id: id }).then((res: any) => {
- if (JSON.stringify(res.data) === '[]' || !res.data.goods) {
- ElMessage.error('找不到该商品')
- setTimeout(() => {
- router.push({path:'/'})
- }, 1000)
- return false
- }
- detail.value = res.data
- // 商品属性
- if(detail.value.goods.attr_format){
- detail.value.goods.attr_format = detail.value.goods.attr_format.filter((item, index) => {
- return Array.isArray(item.attr_child_value_name) ? item.attr_child_value_name.length : item.attr_child_value_name
- })
- }
- // 折扣信息
- if(Object.keys(detail.value.goods).length && detail.value.goods.is_discount && Object.keys(detail.value.discount_info).length){
- let now = new Date();
- let timestamp = now.getTime();
- discountTime.value = Date.now() + (detail.value.discount_info.active.end_time * 1000 - timestamp)
- }
- isFollow.value = detail.value.member_info.is_follow //关注店铺
- isCollect.value = detail.value.goods.is_collect // 收藏商品
- loading.value = false
- evaluateTableData.searchParam.goods_id = detail.value.goods_id
- page.value = detail.value.goods_id
- getMallCouponListFn() // 获取优惠劵
- getEvaluateListFn()//评价列表
- getRecommendFn() // 店铺推荐
- addBrowseFn() // 添加足迹
- loadQrcode()
- })
- }
- getGoodsDetailFn(route.query.id)
- // 判断单规格库存是否为0
- const isShowSingleSku = computed(() => {
- let isSingleSpec = false // 是否为单规格,true:多规格,false:单规格
- detail.value.skuList.forEach((item, index) => {
- if (item.sku_spec_format) {
- isSingleSpec = true
- }
- })
- // 单规格,库存为0,显示已售罄
- if (!isSingleSpec && detail.value.stock <= 0) {
- return false;
- } else if (!isSingleSpec && detail.value.stock > 0) {
- // 单规格,库存大于0,可以购买
- return true;
- }
- return true;
- })
- // 数量改变
- const inputNum = (val) => {
- if (val == undefined || val == null) {
- buyNum.value = 1
- }
- }
- // 当前选中值
- let currSpec = ref({
- skuId: "",
- name: []
- })
- let buyNum = ref(1)
- const goodsDetail = computed(() => {
- let data = JSON.parse(JSON.stringify(detail.value))
- if (Object.keys(data).length) {
- if (!Object.keys(currSpec.value.name).length) currSpec.value.name = data.sku_spec_format.split(",")
- data.goodsSpec.forEach((item: any, index: number) => {
- let specName = item.spec_values.split(",");
- item.values = [];
- specName.forEach((specItem: any, specIndex: number) => {
- item.values[specIndex] = {};
- item.values[specIndex].name = specItem;
- item.values[specIndex].selected = false;
- item.values[specIndex].disabled = false;
- // 选中规格
- currSpec.value.name.forEach((currSpecItem, currSpecIndex) => {
- if (currSpecIndex == index && currSpecItem == specItem) {
- item.values[specIndex].selected = true;
- }
- })
- getSkuId()
- // 当前详情内容
- if (data.skuList && Object.keys(data.skuList).length) {
- data.skuList.forEach((idItem: any, idIndex: number) => {
- if (idItem.sku_id == currSpec.value.skuId) {
- data.detail = idItem;
- }
- })
- }
- })
- })
- }
- return data;
- })
- const getSkuId = () => {
- detail.value.skuList.forEach((skuItem, skuIndex) => {
- if (skuItem.sku_spec_format == currSpec.value.name.toString()) {
- currSpec.value.skuId = skuItem.sku_id
- detail.value.skuList.forEach((item, index) => {
- if (item.sku_id == skuItem.sku_id) {
- Object.assign(detail.value, item);
- }
- })
- }
- })
- }
- // 切换规格
- const change = (data, index) => {
- currSpec.value.name[index] = data.name;
- getSkuId();
- }
- //图片list hover事件
- const imgListHover = (index) => {
- imgActive.value = index
- }
- //图片list 上一页下一页点击事件
- const imgListClick = (val) => {
- let maxLeft = (-64 * detail.value.goods.goods_image_thumb_small.length) + 320 > 0 ? 0 : (-64 * detail.value.goods.goods_image_thumb_small.length) + 320
- if (val == 'right') {
- imgListLeft.value = imgListLeft.value - 320 < maxLeft ? maxLeft : imgListLeft.value - 320
- } else {
- imgListLeft.value = imgListLeft.value + 320 > 0 ? 0 : imgListLeft.value + 320
- }
- }
- // 去店铺详情
- const goShopDetail = (id: number) => {
- router.push({
- path: '/shop/index',
- query: {
- site_id: id
- }
- })
- }
- // 加入购物车
- const buyFn = (type: string) => {
- // 检测是否登录
- if (!userInfo.value) {
- memberStore.logOpen()
- return false
- }
- // 加入购物车
- if (type == 'join_cart') {
- let num = 0;
- let cartId = ""
- if (cartList.value['goods_' + goodsDetail.value.goods_id] && cartList.value['goods_' + goodsDetail.value.goods_id]['sku_' + goodsDetail.value.sku_id]) {
- num = toRaw(cartList.value['goods_' + goodsDetail.value.goods_id]['sku_' + goodsDetail.value.sku_id].num);
- cartId = toRaw(cartList.value['goods_' + goodsDetail.value.goods_id]['sku_' + goodsDetail.value.sku_id].id)
- }
- num += buyNum.value;
- cartStore.increase({
- id: cartId || '',
- goods_id: goodsDetail.value.goods_id,
- sku_id: goodsDetail.value.sku_id,
- stock: goodsDetail.value.stock,
- sale_price: goodsDetail.value.sale_price,
- site_id: goodsDetail.value.site_id,
- num: num
- }, 0, () => {
- ElMessage({
- message: '加入购物车成功',
- type: 'success',
- })
- })
- } else if (type == 'buy_now') {
- var data = {
- sku_id: goodsDetail.value.sku_id,
- num: buyNum.value
- }
- storage.set({ key: 'orderCreateData', data: { body: { [goodsDetail.value.shop_info.site_id]: { sku_data: [data] } } } })
- router.push('/order/payment')
- }
- }
- // 收藏店铺
- let isFollow = ref(0);
- const collectShop = (id: any, is_follow: any) => {
- // 检测是否登录
- if (!userInfo.value) {
- memberStore.logOpen()
- return false
- }
- editShopCollect({
- site_id: id,
- is_follow: is_follow
- }).then(res => {
- isFollow.value = !isFollow.value
- if (isFollow.value) {
- ElMessage({
- message: '收藏成功',
- type: 'success',
- })
- } else {
- ElMessage({
- message: '取消收藏',
- type: 'success',
- })
- }
- })
- }
- // 收藏商品
- let isCollect = ref(0)
- const collectFn = () => {
- // 检测是否登录
- if (!userInfo.value) {
- memberStore.logOpen()
- }
- let api = isCollect.value ? cancelCollect(detail.value.goods_id) : collect(detail.value.goods_id);
- api.then(res => {
- isCollect.value = !isCollect.value;
- if (isCollect.value) {
- ElMessage({
- message: '收藏成功',
- type: 'success',
- })
- } else {
- ElMessage({
- message: '取消收藏成功',
- type: 'success',
- })
- }
- })
- }
- // 价格类型
- let priceType = ref('') //''=>原价,discount_price=>折扣价,member_price=>会员价
- // 商品价格
- let goodsPrice = computed(() =>{
- let price = "0.00";
- if(Object.keys(detail.value).length && Object.keys(detail.value.goods).length && detail.value.goods.is_discount){
- // 折扣价
- price = detail.value.sale_price ? detail.value.sale_price : detail.value.price;
- priceType.value = 'discount_price'
- }else if(Object.keys(detail.value).length && Object.keys(detail.value.goods).length && detail.value.goods.member_discount && getToken()){
- // 会员价
- price = detail.value.member_price ? detail.value.member_price : detail.value.price;
- priceType.value = 'member_price'
- }else{
- price = detail.value.price
- priceType.value = ''
- }
- return price;
- })
- // 领取优惠劵
- let couponListShow = ref<boolean>(false) //优惠券
- let couponList = ref([]);
- const getMallCouponListFn = () => {
- getMallGoodsCoupon({
- site_id: detail.value.shop_info.site_id,
- category_id: detail.value.goods.goods_category || '',
- mall_category_id: goodsDetail.value.goods.goods_mall_category || '',
- goods_id: detail.value.goods_id || '',
- brand_id: goodsDetail.value.goods.brand_id || ''
- }).then(res => {
- couponList.value = res.data.data.map((el: any) => {
- if (!userInfo.value) {
- if (el.sum_count != -1 && el.receive_count === el.sum_count) {
- el.btnType = 'collected'//已领完
- } else {
- el.btnType = 'collecting'//领用
- }
- } else {
- if (el.is_receive) {
- if (el.member_receive_count < el.limit_count) {
- if (el.need_receive) {
- el.btnType = 'collecting'//领用
- } else {
- el.btnType = 'using'//去使用
- }
- } else {
- if (el.need_receive) {
- el.btnType = 'used'//已使用
- } else {
- el.btnType = 'using'//去使用
- }
- }
- } else {
- if (el.sum_count != -1 && el.receive_count === el.sum_count) {
- el.btnType = 'collected'//已领完
- } else {
- el.btnType = 'collecting'//领用
- }
- }
- }
- return el
- });
- })
- }
- // 领取优惠券
- const getCouponFn = (data, index) => {
- // 检测是否登录
- if (!userInfo.value) {
- memberStore.logOpen()
- couponListShow.value = false
- return false
- }
- getCoupon({
- coupon_id: data.id || '',
- number: 1,
- }).then(res => {
- getMallCouponListFn();
- couponListShow.value = false
- })
- }
- let page = ref('')
- const wapImage = ref('')
- const wapPreview = ref('')
- const loadQrcode = () => {
- wapPreview.value = `${location.origin}/wap/addon/mall/pages/goods/detail?goods_id=${page.value}`
- // errorCorrectionLevel:密度容错率L(低)H(高)
- QRCode.toDataURL(wapPreview.value, { errorCorrectionLevel: 'L', margin: 0, width: 120 }).then(url => {
- wapImage.value = url
- })
- }
- // 评论 start
- let evaluateSort = ref<Array<any>>([{
- name: '全部',
- status: '',
- scores: []
- },
- {
- name: '好评',
- status: 'good_evaluate',
- scores: [4, 5]
- },
- {
- name: '中评',
- status: 'center_evaluate',
- scores: [2, 3]
- },
- {
- name: '差评',
- status: 'bad_evaluate',
- scores: [1]
- }])
- let evaluateTableData = reactive<any>({
- data: [],
- page: 1,
- limit: 10,
- total: 0,
- loading: false,
- searchParam:{
- goods_id: 0,
- currentIndex: '',
- scores: [],
- }
- })
- let rate = ref(5)
- let goodsRate = ref(0)
- // 切换tab
- const handelClick = (data: any) => {
- evaluateTableData.data = []
- evaluateTableData.searchParam.currentIndex = data.status
- evaluateTableData.searchParam.scores = []
- evaluateTableData.searchParam.scores = data.scores
- evaluateTableData.page = 1
- getEvaluateListFn()
- }
- // 评价列表
- const getEvaluateListFn = (page:number = 1) => {
- evaluateTableData.page = page
- evaluateTableData.loading = true
- getEvaluateList({
- page: evaluateTableData.page,
- limit: evaluateTableData.limit,
- ...evaluateTableData.searchParam
- }).then((res: any) => {
- evaluateTableData.total = res.data.total
- evaluateTableData.data = res.data.data.map((item: any) => {
- item.imagesList = item.images.map((el: any) => {
- return img(el)
- })
- return item
- });
- evaluateTableData.loading = false
- }).catch(() => {
- evaluateTableData.loading = false
- })
- }
- // 店铺推荐
- let recommendList = ref<Array<any>>([])
- const getRecommendFn = () => {
- getRecommendGoods({ site_id: detail.value.site_id, limit: 4 }).then(res => {
- recommendList.value = res.data
- })
- }
- //添加足迹
- const addBrowseFn = () => {
- addBrowse({ goods_id:detail.value.goods_id,sku_id:detail.value.sku_id }).then(res => {})
- }
- //路由跳转不刷新页面,重新加载数据
- watch(route, (to, from) => {
- router.go(0)
- })
- const toDetail = (goods_id: number) => {
- router.push(`/goods/detail?id=${goods_id}`)
- }
- </script>
- <style lang="scss" scoped>
- // .detail {
- // :deep(.el-tabs__header .el-tabs__nav-wrap .el-tabs__item) {
- // height: 50px;
- // }
- // }
- .discount-bg{
- background-image: url(@/assets/images/addon/discount-bg.png);
- background-position: center center;
- background-size: 100% 100%;
- background-repeat: no-repeat;
- }
- .bg-img{
- background-image: url(@/assets/images/addon/detail-bg.png);
- background-position: center center;
- background-size: 100% 100%;
- background-repeat: no-repeat;
- }
- .coupon-bg{
- background-image: url(@/assets/images/addon/coupon-bg.png);
- background-position: center center;
- background-size: 100% 100%;
- background-repeat: no-repeat;
- }
- :deep(.page-form .el-form-item__label){
- color: #999;
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
- }
- /* 多行超出隐藏 */
- .multi-hidden {
- word-break: break-all;
- text-overflow: ellipsis;
- overflow: hidden;
- display: -webkit-box;
- -webkit-line-clamp: 2;
- -webkit-box-orient: vertical;
- }
- .coupon-item{
- :before{
- content: '';
- display: block;
- width: 10px;
- height: 10px;
- background-color: #f5f5f5;
- position: absolute;
- top: 50%;
- left: -5px;
- border-radius:5px;
- transform: translateY(-50%);
- }
- :after{
- content: '';
- display: block;
- width: 10px;
- height: 10px;
- background-color: #fff;
- position: absolute;
- top: 50%;
- right: -5px;
- border-radius:5px;
- transform: translateY(-50%);
- }
- }
- </style>
|