classfiy.vue 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923
  1. <script setup lang="ts">
  2. import type { LoadMoreState } from 'wot-design-uni/components/wd-loadmore/types'
  3. import selectSku from '../../components/select-sku/index.vue'
  4. import { StaticUrl } from '@/config'
  5. import router from '@/router'
  6. const props = defineProps<{ categoryList: Api.xsbCategories[], hotText: Api.xsbSearchTerm[] }>()
  7. const { statusBarHeight, MenuButtonHeight } = storeToRefs(useSysStore())
  8. const { topNavActive, leftActive, SelectShopInfo } = storeToRefs(useSysXsbStore())
  9. const { userInfo, token } = storeToRefs(useUserStore())
  10. const { getShopTotalNum } = storeToRefs(useSmqjhCartStore())
  11. const classfiylist = computed(() => props.categoryList)
  12. const sortClassBtn = ref(1)
  13. const show = ref(false)
  14. const cartList = ref<Api.xsbCategoriesCartList[]>([])
  15. const cartPopup = ref(false)
  16. const totalProduct = ref<Api.shoppingCartOrderConfirm>()
  17. const priceDetailPopup = ref(false)
  18. const cartIds = computed(() => cartList.value.filter(it => it.isDelete !== '1' && it.shopSkuStocks !== '0').map(it => it.id))
  19. // 勾选状态:只计算勾选商品的价格
  20. const selectedIds = ref<number[]>([])
  21. const isAllSelected = computed(() => cartIds.value.length > 0 && cartIds.value.every(id => selectedIds.value.includes(id)))
  22. const selectedCount = computed(() => selectedIds.value.length)
  23. function toggleSelectAll() {
  24. if (isAllSelected.value) {
  25. selectedIds.value = []
  26. }
  27. else {
  28. selectedIds.value = [...cartIds.value]
  29. }
  30. getGoodsPrice()
  31. }
  32. async function handleClearCart() {
  33. useGlobalMessage().confirm({
  34. title: '提示',
  35. msg: '确认清空购物车?',
  36. success: async () => {
  37. uni.showLoading({ mask: true })
  38. try {
  39. const ids = cartList.value.map(it => it.id).join(',')
  40. if (ids) {
  41. await Apis.common.deleteShoppingCart({ pathParams: { ids } })
  42. }
  43. cartPopup.value = false
  44. selectedIds.value = []
  45. totalProduct.value = undefined
  46. useSmqjhCartStore().getCartList('XSB')
  47. await getCartCategorList()
  48. setProductNum()
  49. }
  50. finally {
  51. uni.hideLoading()
  52. }
  53. },
  54. })
  55. }
  56. const showball = ref(false)
  57. const _this = getCurrentInstance()
  58. const selectGoods = ref(false)
  59. // 多规格商品skuid
  60. const selectSkuId = ref(0)
  61. const SelectGoodsNum = ref(1)
  62. const goodsInTo = ref()
  63. const goodsInfo = ref<Api.xsbCategoryProductList | Api.xsbProductDetail | undefined>()
  64. const isTopLoading = ref(false)
  65. const basllObj = ref({
  66. left: 0,
  67. top: 0,
  68. x: -402,
  69. y: 773,
  70. })
  71. const x = computed(() => `${basllObj.value.x}px`)
  72. const y = computed(() => `${basllObj.value.y}px`)
  73. const left = computed(() => `${basllObj.value.left}px`)
  74. const top = computed(() => `${basllObj.value.top}px`)
  75. const productList = ref<Api.xsbCategoryProductList[]>([])
  76. const topScrollView = ref()
  77. const scrollTop = ref<number | undefined>(0)
  78. const goodsLoading = ref<LoadMoreState>()
  79. const navHeight = computed(() => {
  80. return (`${Number(MenuButtonHeight.value) + Number(statusBarHeight.value)}px`)
  81. })
  82. function handleTopNavChange(item: Api.xsbCategoriesChildren) {
  83. topNavActive.value = item.code
  84. if (!item.children)
  85. return
  86. goodsLoading.value = 'loading'
  87. leftActive.value = item.children[0].code
  88. show.value = false
  89. topScrollView.value = null
  90. nextTick(() => {
  91. topScrollView.value = item.code
  92. })
  93. getProductList()
  94. }
  95. const categories = computed(() => {
  96. return classfiylist.value.find(it => it.code === topNavActive.value)?.children || []
  97. })
  98. const categoriesId = computed(() => {
  99. return categories.value.find(it => it.code === leftActive.value)?.id
  100. })
  101. async function getCartCategorList() {
  102. return new Promise((resolve, reject) => {
  103. Apis.xsb.myShoppingCartCategory({
  104. data: {
  105. businessType: 'XSB',
  106. channelId: unref(userInfo).channelId,
  107. shopId: unref(SelectShopInfo).shopId,
  108. },
  109. }).then((res) => {
  110. cartList.value = res.data
  111. console.log(cartList.value, '===================')
  112. resolve(res)
  113. }).catch((err) => {
  114. reject(err)
  115. })
  116. })
  117. }
  118. function handleChange({ value }: { value: string }) {
  119. leftActive.value = value
  120. getProductList()
  121. }
  122. function handleLeftChange(item: Api.xsbCategories) {
  123. goodsLoading.value = 'loading'
  124. handleChange({ value: item.code })
  125. }
  126. async function getProductList() {
  127. const res = await Apis.xsb.getCategoryProductList({
  128. data: {
  129. categoryId: Number(categoriesId.value),
  130. shopId: Number(SelectShopInfo.value?.shopId) || 1,
  131. channelId: unref(userInfo).channelId || 1,
  132. },
  133. })
  134. productList.value = res.data
  135. if (!res.data.length) {
  136. scrollTop.value = undefined
  137. nextTick(() => {
  138. scrollTop.value = 0
  139. })
  140. }
  141. if (!isTopLoading.value) {
  142. scrollTop.value = undefined
  143. nextTick(() => {
  144. scrollTop.value = 0
  145. })
  146. }
  147. else {
  148. goodsInTo.value = null
  149. nextTick(() => {
  150. goodsInTo.value = res.data.length > 4 ? res.data[res.data.length - 4].prodId : null
  151. })
  152. }
  153. goodsLoading.value = 'finished'
  154. isTopLoading.value = false
  155. setProductNum()
  156. }
  157. function handlScrollBottom() {
  158. goodsLoading.value = 'loading'
  159. const index = categories?.value.findIndex(it => it.code === leftActive.value)
  160. console.log(index, '-================')
  161. if (index + 1 === categories.value.length) {
  162. goodsLoading.value = 'finished'
  163. return
  164. }
  165. if (index !== -1) {
  166. handleChange({ value: categories.value[index + 1].code })
  167. }
  168. }
  169. function handleSrollTop() {
  170. isTopLoading.value = true
  171. const index = categories?.value.findIndex(it => it.code === leftActive.value)
  172. if (index !== -1) {
  173. if (index === 0) {
  174. nextTick(() => isTopLoading.value = false)
  175. }
  176. else {
  177. handleChange({ value: categories.value[index - 1].code })
  178. }
  179. }
  180. }
  181. async function handleAddCart(event: WechatMiniprogram.TouchEvent, item: Api.xsbCategoryProductList) {
  182. await useUserStore().checkLogin()
  183. if (item.skuList.length > 1) {
  184. goodsInfo.value = item
  185. selectGoods.value = true
  186. return
  187. }
  188. if (showball.value)
  189. return
  190. basllObj.value.left = event.detail.x
  191. basllObj.value.top = event.detail.y
  192. showball.value = true
  193. setTimeout(() => {
  194. showball.value = false
  195. }, 500)
  196. const skuId = item.skuList[0].skuId
  197. await useSmqjhCartStore().addCart(skuId, 1, Number(item.shopId), 'XSB')
  198. await getCartCategorList()
  199. setProductNum()
  200. // 自动选中刚加入的商品,并重新计算价格
  201. const cartItem = cartList.value.find(it => it.skuId === skuId && it.isDelete !== '1' && it.shopSkuStocks !== '0')
  202. if (cartItem && !selectedIds.value.includes(cartItem.id)) {
  203. selectedIds.value.push(cartItem.id)
  204. }
  205. await getGoodsPrice()
  206. }
  207. async function handleSubCart(event: WechatMiniprogram.TouchEvent, item: Api.xsbCategoryProductList) {
  208. const skuId = item.skuList[0].skuId
  209. if (item.num === 1) {
  210. useGlobalMessage().confirm({
  211. msg: '是否删除该商品?',
  212. success: async () => {
  213. // 删除前记录 cartId,用于从 selectedIds 中移除
  214. const cartItem = cartList.value.find(it => it.skuId === skuId)
  215. await useSmqjhCartStore().addCart(skuId, -1, Number(item.shopId), 'XSB')
  216. await getCartCategorList()
  217. setProductNum()
  218. if (cartItem) {
  219. selectedIds.value = selectedIds.value.filter(id => id !== cartItem.id)
  220. }
  221. await getGoodsPrice()
  222. },
  223. })
  224. }
  225. else {
  226. await useSmqjhCartStore().addCart(skuId, -1, Number(item.shopId), 'XSB')
  227. await getCartCategorList()
  228. setProductNum()
  229. await getGoodsPrice()
  230. }
  231. }
  232. onMounted(async () => {
  233. if (token.value) {
  234. await getCartCategorList()
  235. }
  236. if (!topNavActive.value || !leftActive.value) {
  237. const firstWithChildren = props.categoryList?.find(it => it.children && it.children.length > 0)
  238. if (firstWithChildren) {
  239. topNavActive.value = firstWithChildren.code
  240. leftActive.value = firstWithChildren.children![0].code
  241. }
  242. }
  243. goodsLoading.value = 'loading'
  244. if (leftActive.value) {
  245. handleChange({ value: leftActive.value })
  246. }
  247. else {
  248. goodsLoading.value = 'finished'
  249. }
  250. if (topNavActive.value) {
  251. topScrollView.value = null
  252. nextTick(() => {
  253. topScrollView.value = topNavActive.value
  254. })
  255. }
  256. getCartBox()
  257. })
  258. function getCartBox() {
  259. const query = uni.createSelectorQuery().in(_this)
  260. query.select('.cart-box').boundingClientRect()
  261. query.exec((res) => {
  262. basllObj.value.y = res[0].top
  263. uni.getSystemInfo().then((info) => {
  264. basllObj.value.x = -(info.screenWidth - res[0].left - 15)
  265. console.log(basllObj.value.x, basllObj.value.y)
  266. })
  267. })
  268. }
  269. function handleGo(item: Api.xsbCategoryProductList) {
  270. if (!item.prodId) {
  271. useGlobalToast().show('后端数据异常!')
  272. return
  273. }
  274. router.push({ name: 'xsb-goods', params: { id: String(item.prodId) } })
  275. }
  276. async function getGoodsPrice() {
  277. if (selectedIds.value.length) {
  278. const res = await useSmqjhCartStore().getCartAddGoodsPrice(selectedIds.value.join(','))
  279. totalProduct.value = res
  280. }
  281. else {
  282. totalProduct.value = undefined
  283. }
  284. }
  285. async function handleSub(item: Api.xsbCategoriesCartList) {
  286. if (item.num === 1) {
  287. useGlobalMessage().confirm({
  288. msg: '是否删除该商品?',
  289. success: async () => {
  290. cartPopup.value = false
  291. await useSmqjhCartStore().addCart(item.skuId, -1, item.shopId, 'XSB')
  292. await getCartCategorList()
  293. setProductNum()
  294. totalProduct.value = undefined
  295. },
  296. })
  297. }
  298. else {
  299. await useSmqjhCartStore().addCart(item.skuId, -1, item.shopId, 'XSB')
  300. getGoodsPrice()
  301. await getCartCategorList()
  302. setProductNum()
  303. }
  304. }
  305. async function handleAdd(item: Api.xsbCategoriesCartList) {
  306. await useSmqjhCartStore().addCart(item.skuId, 1, item.shopId, 'XSB')
  307. getGoodsPrice()
  308. await getCartCategorList()
  309. setProductNum()
  310. }
  311. onUnmounted(() => {
  312. console.log('组件卸载')
  313. topNavActive.value = ''
  314. leftActive.value = ''
  315. })
  316. async function handleDelCartGoods(item: Api.xsbCategoriesCartList) {
  317. const msg = item.isDelete ? '商品已被商家删除,是否确认删除该门店下的商品?' : '门店商品库存不足,是否删除该门店下的商品?'
  318. useGlobalMessage().confirm({
  319. title: '提示',
  320. msg,
  321. success: async () => {
  322. uni.showLoading({ mask: true })
  323. try {
  324. await Apis.common.deleteShoppingCart({
  325. pathParams: {
  326. ids: String(item.id),
  327. },
  328. })
  329. await getCartCategorList()
  330. uni.hideLoading()
  331. }
  332. catch {
  333. uni.hideLoading()
  334. }
  335. },
  336. })
  337. }
  338. function setProductNum() {
  339. if (!cartList.value.length) {
  340. productList.value = productList.value.map((goods) => {
  341. goods.num = undefined
  342. return goods
  343. })
  344. return
  345. }
  346. cartList.value.forEach((it) => {
  347. productList.value = productList.value.map((goods) => {
  348. if (goods.skuList.length && goods.skuList[0].skuId === it.skuId) {
  349. goods.num = it.num
  350. }
  351. return goods
  352. })
  353. })
  354. }
  355. async function handleSkuAddCart() {
  356. await useSmqjhCartStore().addCart(unref(selectSkuId), unref(SelectGoodsNum), Number(goodsInfo.value?.shopId), 'XSB')
  357. getGoodsPrice()
  358. await getCartCategorList()
  359. }
  360. function handlePay() {
  361. if (!totalProduct.value) {
  362. useGlobalToast().show('请选择商品')
  363. return
  364. }
  365. const shopSkuStock = totalProduct.value.skuList.filter(it => it.num > Number(it.shopSkuStocks))
  366. if (shopSkuStock.length) {
  367. useGlobalToast().show({ msg: `${shopSkuStock[0].skuName}库存不足` })
  368. return
  369. }
  370. cartPopup.value = false
  371. router.push({ name: 'xsb-confirmOrder', params: { data: JSON.stringify(totalProduct.value) } })
  372. }
  373. function handleOpen() {
  374. if (!getShopTotalNum.value)
  375. return
  376. cartPopup.value = true
  377. priceDetailPopup.value = false
  378. }
  379. </script>
  380. <script lang="ts">
  381. export default {
  382. options: {
  383. styleIsolation: 'shared',
  384. },
  385. }
  386. </script>
  387. <template>
  388. <view class="page-xsb">
  389. <wd-navbar
  390. title="" custom-style="background-color:#F4FFD1" :bordered="false" :z-index="9999" safe-area-inset-top
  391. fixed
  392. >
  393. <template #left>
  394. <view class="flex items-center" @click="router.push({ name: 'xsb-search' })">
  395. <view
  396. class="h60rpx w-474rpx flex items-center justify-between border-2rpx border-[var(--them-color)] rounded-40rpx border-solid bg-white pr6rpx"
  397. >
  398. <view class="flex items-center pb14rpx pl24rpx pt16rpx">
  399. <wd-icon name="search" size="14" color="#ccc" />
  400. <view class="search ml12rpx h-full w-full overflow-hidden">
  401. <wd-notice-bar
  402. :text="hotText.map((it) => it.searchName)" custom-class="notice-bar" color="#ccc"
  403. background-color="#fff" direction="vertical"
  404. />
  405. </view>
  406. </view>
  407. <view class="flex items-center pr28rpx">
  408. <wd-divider vertical />
  409. <view class="ml6rpx text-28rpx text-[var(--them-color)] font-semibold">
  410. 搜索
  411. </view>
  412. </view>
  413. </view>
  414. </view>
  415. </template>
  416. </wd-navbar>
  417. <view
  418. class="h162rpx flex items-center justify-between bg-#F4FFD1 pl24rpx"
  419. :style="{ paddingTop: `${(Number(statusBarHeight) || 44) + MenuButtonHeight + 12}px` }"
  420. >
  421. <scroll-view
  422. :scroll-into-view-offset="-150" scroll-x enable-passive scroll-with-animation
  423. class="scrollx w-90% flex-shrink-0 whitespace-nowrap" :scroll-into-view="`id${topScrollView}`"
  424. >
  425. <view class="flex items-end pb10rpx">
  426. <view
  427. v-for="item in classfiylist" :id="`id${item.code}`" :key="item.code"
  428. class="mr24rpx flex flex-col items-center justify-center" @click="handleTopNavChange(item)"
  429. >
  430. <view class="relative">
  431. <view
  432. class="box-border"
  433. :class="[topNavActive == item.code ? 'overflow-hidden border-solid border-[var(--them-color)] border-2rpx rounded-26rpx h84rpx w-84rpx' : 'h72rpx w-72rpx']"
  434. >
  435. <image :src="item.icon" class="h-full w-full" />
  436. </view>
  437. <view
  438. v-if="!item.children"
  439. class="absolute left-0 top-0 h-full w-full flex items-center justify-center rounded-26rpx bg-[rgba(0,0,0,0.6)] text-16rpx text-white"
  440. >
  441. 敬请期待
  442. </view>
  443. </view>
  444. <view
  445. class="mt16rpx text-22rpx"
  446. :class="[topNavActive == item.code ? 'bg-[var(--them-color)] rounded-18rpx px-8rpx py2rpx text-white text-24rpx' : '']"
  447. >
  448. {{ item.name }}
  449. </view>
  450. </view>
  451. </view>
  452. </scroll-view>
  453. <view
  454. class="right-nav box-border h144rpx w64rpx flex flex-shrink-0 flex-col items-center justify-center px20rpx text-24rpx font-semibold"
  455. @click="show = true"
  456. >
  457. 展开
  458. <image :src="`${StaticUrl}/xia.png`" class="mt20rpx h20rpx w20rpx" />
  459. </view>
  460. <wd-popup v-model="show" :z-index="9998" position="top" custom-style="border-radius:32rpx;">
  461. <view
  462. class="box-border bg-#F4FFD1 p24rpx"
  463. :style="{ paddingTop: `${(Number(statusBarHeight) || 44) + MenuButtonHeight + 12}px` }"
  464. >
  465. <view class="mb24rpx mt24rpx text-28rpx font-semibold">
  466. 全部一级分类
  467. </view>
  468. <view class="h400rpx overflow-y-scroll">
  469. <view class="grid grid-cols-5 box-border gap-x-24rpx gap-y-24rpx">
  470. <view
  471. v-for="item in classfiylist" :key="item.code"
  472. class="box-border h150rpx w114rpx flex flex-col items-center justify-center"
  473. @click="handleTopNavChange(item)"
  474. >
  475. <image
  476. :src="item.icon"
  477. :class="[topNavActive == item.code ? 'overflow-hidden border-solid border-[var(--them-color)] border-2rpx rounded-26rpx h88rpx w-88rpx' : 'h76rpx w-76rpx']"
  478. />
  479. <view
  480. class="mt16rpx whitespace-nowrap text-nowrap text-22rpx"
  481. :class="[topNavActive == item.code ? 'bg-[var(--them-color)] rounded-18rpx px-8rpx py2rpx text-white text-24rpx' : '']"
  482. >
  483. {{ item.name }}
  484. </view>
  485. </view>
  486. </view>
  487. </view>
  488. <view class="mt24rpx w-full flex items-center justify-center" @click="show = false">
  489. <view class="mr8rpx text-24rpx">
  490. 点击收起
  491. </view>
  492. <wd-icon name="arrow-down" size="18px" />
  493. </view>
  494. </view>
  495. </wd-popup>
  496. </view>
  497. <view class="wraper">
  498. <scroll-view
  499. class="w200rpx" :scroll-into-view-offset="-150" enable-passive scroll-with-animation scroll-y
  500. :scroll-into-view="`id${leftActive}`"
  501. >
  502. <view
  503. v-for="item in categories" :id="`id${item.code}`" :key="item.code"
  504. :class="[item.code == leftActive ? 'bg-white' : '']"
  505. class="relative h100rpx flex items-center justify-center whitespace-nowrap text-28rpx"
  506. @click="handleLeftChange(item)"
  507. >
  508. {{ item.name }}
  509. <view
  510. v-if="item.code == leftActive"
  511. class="absolute left-0 top-20rpx h60rpx w8rpx rounded-4rpx bg-[var(--them-color)]"
  512. />
  513. </view>
  514. </scroll-view>
  515. <view class="content">
  516. <view class="p20rpx">
  517. <image :src="`${StaticUrl}/class.png`" class="h144rpx w-full" @click="useGlobalToast().show('敬请期待 !')" />
  518. <view class="my20rpx flex items-center justify-end rounded-16rpx bg-#F6F6F6 px20rpx py8rpx">
  519. <view class="mr40rpx text-24rpx">
  520. 销量
  521. </view>
  522. <wd-sort-button v-model="sortClassBtn" :line="false" title="价格" />
  523. </view>
  524. </view>
  525. <view class="relative">
  526. <scroll-view
  527. :lower-threshold="100" :refresher-triggered="isTopLoading" :scroll-top="scrollTop"
  528. :style="{ height: `calc(100vh - ${navHeight} - 700rpx)` }" enable-passive scroll-y scroll-anchoring
  529. refresher-enabled :throttle="false" :scroll-into-view="`class${goodsInTo}`"
  530. @refresherrefresh="handleSrollTop" @scrolltolower="handlScrollBottom"
  531. >
  532. <view v-if="productList.length" class="p20rpx">
  533. <view v-for="item in productList" :id="`class${item.prodId}`" :key="item.id" class="relative">
  534. <view class="flex" @click="handleGo(item)">
  535. <view class="relative mr20rpx h172rpx w172rpx flex-shrink-0 overflow-hidden rounded-16rpx bg-#F6F6F6">
  536. <image :src="item.pic" lazy-load class="h-full w-full" />
  537. <image :src="`${StaticUrl}/xsb-shui-class.png`" class="absolute left-0 top-0 h-full w-full" />
  538. </view>
  539. <view class="flex-1">
  540. <view class="text-left text-28rpx font-semibold">
  541. <!-- <view v-for="i in 2" :key="i" class="mr5px inline-block">
  542. <wd-tag type="primary">
  543. 新品{{ i }}
  544. </wd-tag>
  545. </view> -->
  546. {{ item.prodName }}
  547. </view>
  548. <view class="text-22rpx text-#AAAAAA">
  549. <view class="mt10rpx flex items-center">
  550. <view>{{ item.spec }}</view>
  551. </view>
  552. <view class="mt6rpx">
  553. 已售 {{ item.soldNum }}+
  554. </view>
  555. </view>
  556. <view class="flex items-center justify-between">
  557. <view class="text-#FF4D3A font-semibold">
  558. <text class="text-24rpx">
  559. </text> <text class="text-36rpx">
  560. {{ item.channelProdPrice }}
  561. </text>
  562. </view>
  563. <view v-if="!item.num">
  564. <view v-if="item.spuStock" @click.stop="handleAddCart($event, item)">
  565. <!-- <image :src="`${StaticUrl}/cart-yes.png`" class="h52rpx w52rpx" /> -->
  566. <i class="iconfont text-52rpx text-[var(--them-color)]">&#xe64c;</i>
  567. </view>
  568. <view v-else>
  569. <!-- <image :src="`${StaticUrl}/cart-no.png`" class="h52rpx w52rpx" /> -->
  570. <i class="iconfont text-52rpx text-#ccc">&#xe64c;</i>
  571. </view>
  572. </view>
  573. <view v-else>
  574. <view class="flex items-center">
  575. <image
  576. :src="`${StaticUrl}/sub-cart.png`" class="h44rpx w44rpx"
  577. @click.stop="handleSubCart($event, item)"
  578. />
  579. <view
  580. class="box-border h44rpx w84rpx flex items-center justify-center border border-#F0F0F0 border-solid text-24rpx text-#AAAAAA"
  581. >
  582. {{ item.num }}
  583. </view>
  584. <image
  585. :src="`${StaticUrl}/add-cart.png`" class="h44rpx w44rpx"
  586. @click.stop="handleAddCart($event, item)"
  587. />
  588. </view>
  589. </view>
  590. </view>
  591. </view>
  592. </view>
  593. <view class="line">
  594. <wd-divider color="#F0F0F0" />
  595. </view>
  596. <view
  597. v-if="!item.spuStock"
  598. class="absolute left-0 top-0 z-1 h-full w-full flex items-center bg-[rgba(255,255,255,.6)]"
  599. >
  600. <view class="h172rpx w172rpx flex items-center justify-center">
  601. <view
  602. class="h36rpx w112rpx flex items-center justify-center rounded-16rpx bg-[rgba(0,0,0,.6)] text-28rpx text-white"
  603. >
  604. 已售罄
  605. </view>
  606. </view>
  607. </view>
  608. <view
  609. v-if="!item.skuList.some((it) => it.saleStatus)"
  610. class="absolute left-0 top-0 z-1 h-full w-full flex items-center bg-[rgba(255,255,255,.6)]"
  611. >
  612. <view class="h172rpx w172rpx flex items-center justify-center">
  613. <view
  614. class="h36rpx w112rpx flex items-center justify-center rounded-16rpx bg-[rgba(0,0,0,.6)] text-28rpx text-white"
  615. >
  616. 不可售
  617. </view>
  618. </view>
  619. </view>
  620. </view>
  621. </view>
  622. <StatusTip v-else tip="暂无内容" />
  623. <view v-if="goodsLoading == 'finished' || isTopLoading" class="h-40vh" />
  624. </scroll-view>
  625. <view
  626. v-if="goodsLoading == 'loading' || isTopLoading"
  627. class="absolute left-0 top-0 z-10 h-full w-full flex items-center justify-center bg-white"
  628. >
  629. <wd-loading color="#9ED605" :size="20" />
  630. </view>
  631. </view>
  632. </view>
  633. </view>
  634. <view
  635. class="fixedShadow fixed bottom-60rpx left-0 z-100 box-border w-full flex items-center justify-between rounded-t-16rpx bg-white px24rpx pb60rpx pt10rpx"
  636. >
  637. <view class="ios w-full flex items-center justify-between">
  638. <view class="flex items-center">
  639. <view class="flex items-center" @click="handleOpen">
  640. <wd-badge :model-value="getShopTotalNum" :top="20">
  641. <image v-if="getShopTotalNum" :src="`${StaticUrl}/cart-lanzi.png`" class="cart-box h100rpx w100rpx" />
  642. <image v-else :src="`${StaticUrl}/xsb-cart-disabled.png`" class="cart-box h100rpx w100rpx" />
  643. </wd-badge>
  644. </view>
  645. <view v-if="getShopTotalNum" class="ml40rpx">
  646. <view class="flex items-center">
  647. <view class="font-semibold">
  648. ¥ {{ totalProduct?.amount || 0 }}
  649. </view>
  650. <view v-if="totalProduct?.coupon" class="ml10rpx text-24rpx text-#FF4A39">
  651. 共减¥{{ totalProduct?.coupon }}
  652. </view>
  653. <view
  654. v-if="totalProduct?.amount" class="ml10rpx flex items-center text-24rpx text-gray"
  655. @click="priceDetailPopup = !priceDetailPopup, cartPopup = false"
  656. >
  657. 明细 <view :class="[priceDetailPopup ? 'rotate-180' : '']" class="flex items-center">
  658. <wd-icon name="arrow-up" size="24rpx" color="#aaa" />
  659. </view>
  660. </view>
  661. </view>
  662. <view class="mt10rpx text-24rpx text-gray">
  663. 配送费:¥{{ totalProduct?.transfee || 0 }}
  664. </view>
  665. </view>
  666. <view v-else class="ml40rpx text-24rpx text-#AAA">
  667. 您还没有添加商品
  668. </view>
  669. </view>
  670. <view class="flex items-center">
  671. <!-- <view class="flex items-center font-semibold">
  672. <view class="text-22rpx text-#222">
  673. 总计:
  674. </view>
  675. <view class="flex items-baseline text-24rpx text-#FF4A39">
  676. <text class="text-36rpx">
  677. {{ totalProduct?.price || '0.00' }}
  678. </text>
  679. </view>
  680. </view> -->
  681. <view class="ml20rpx w160rpx">
  682. <wd-button block size="large" :disabled="!getShopTotalNum" :type="getShopTotalNum ? 'primary' : 'info'" @click="handlePay">
  683. 结算
  684. </wd-button>
  685. </view>
  686. </view>
  687. </view>
  688. </view>
  689. <!-- 价格明细弹窗 -->
  690. <Zpopup v-model="priceDetailPopup" :zindex="10" bg="#fff">
  691. <view class="ios box-border w-full px-40rpx pb-60rpx pt-36rpx">
  692. <view class="mb-40rpx text-center text-32rpx font-semibold">
  693. 价格明细
  694. </view>
  695. <!-- 商品合计 -->
  696. <view class="flex items-center justify-between py-20rpx">
  697. <view class="text-30rpx font-semibold">
  698. 商品合计
  699. </view>
  700. <view class="text-30rpx font-semibold">
  701. ¥{{ totalProduct?.amount }}
  702. </view>
  703. </view>
  704. <!-- 商品总价 -->
  705. <view class="flex items-center justify-between py-20rpx">
  706. <view class="text-28rpx text-#333">
  707. 商品总价
  708. </view>
  709. <view class="text-28rpx text-#333">
  710. ¥{{ totalProduct?.price }}
  711. </view>
  712. </view>
  713. <!-- 下单用券共减 -->
  714. <template v-if="totalProduct?.coupon">
  715. <view class="flex items-center justify-between py-20rpx">
  716. <view class="text-28rpx text-#333">
  717. 下单用券共减
  718. </view>
  719. <view class="text-28rpx text-#FF4A39 font-semibold">
  720. -¥{{ totalProduct?.coupon }}
  721. </view>
  722. </view>
  723. <!-- 券名称子行 -->
  724. <view v-if="totalProduct?.activityName || totalProduct?.couponName" class="flex items-center justify-between pb-20rpx">
  725. <view class="text-24rpx text-#AAAAAA">
  726. {{ totalProduct?.activityName || totalProduct?.couponName }}
  727. </view>
  728. <view class="text-24rpx text-#AAAAAA">
  729. -¥{{ totalProduct?.coupon }}
  730. </view>
  731. </view>
  732. </template>
  733. <!-- 配送费 -->
  734. <view class="flex items-center justify-between py-20rpx">
  735. <view class="text-28rpx text-#333">
  736. 配送费
  737. </view>
  738. <view class="text-28rpx text-#333">
  739. ¥{{ totalProduct?.transfee }}
  740. </view>
  741. </view>
  742. </view>
  743. <view class="h200rpx" />
  744. </Zpopup>
  745. <Zpopup v-model="cartPopup" :zindex="99">
  746. <view class="ios overflow-hidden">
  747. <!-- 头部:全选 + 已选件数 + 清空购物车 -->
  748. <view class="flex items-center justify-between border-b border-[#F5F5F5] border-solid px-24rpx py-24rpx">
  749. <view class="flex items-center">
  750. <wd-checkbox :model-value="isAllSelected" size="large" @change="toggleSelectAll">
  751. 全选
  752. </wd-checkbox>
  753. <text class="ml-16rpx text-24rpx text-[#AAAAAA]">
  754. 已选{{ selectedCount }}件,总重量约为{{ totalProduct?.totalWeight || 0 }}kg
  755. </text>
  756. </view>
  757. <view class="text-24rpx text-[#AAAAAA]" @click="handleClearCart">
  758. <wd-icon name="delete" size="24rpx" /> 清空购物车
  759. </view>
  760. </view>
  761. <!-- 商品列表 -->
  762. <view class="h-700rpx overflow-y-scroll">
  763. <view class="p-24rpx">
  764. <wd-checkbox-group v-model="selectedIds" custom-class="custom-checkbox-group" @change="getGoodsPrice">
  765. <view
  766. v-for="item in cartList" :key="item.id"
  767. class="relative mt-20rpx box-border flex items-center overflow-hidden rounded-16rpx bg-white p-24rpx"
  768. >
  769. <!-- 复选框 -->
  770. <wd-checkbox
  771. v-if="item.shopSkuStocks !== '0' && item.isDelete !== '1'" :model-value="item.id"
  772. size="large" class="mr-16rpx flex-shrink-0"
  773. />
  774. <view v-else class="mr-16rpx h-40rpx w-40rpx flex-shrink-0" />
  775. <view class="flex flex-1">
  776. <image
  777. :src="item.pic" class="h206rpx w200rpx flex-shrink-0"
  778. @click.stop="router.push({ name: 'xsb-goods', params: { id: String(item.prodId) } })"
  779. />
  780. <view class="ml20rpx flex-1">
  781. <view class="text-left text-28rpx font-semibold">
  782. {{ item.skuName }}
  783. </view>
  784. <view class="mt14rpx text-24rpx text-#AAAAAA">
  785. 规格:{{ item.spec }}
  786. </view>
  787. <view class="mt14rpx flex items-center justify-between">
  788. <view class="text-36rpx text-#FF4A39 font-semibold">
  789. ¥{{ item.price }}
  790. </view>
  791. <view v-if="item.shopSkuStocks !== '0' && item.isDelete !== '1'" class="flex items-center">
  792. <image
  793. :src="` ${StaticUrl}/sub-cart.png`" class="h44rpx w44rpx"
  794. @click.stop="handleSub(item)"
  795. />
  796. <view
  797. class="box-border h44rpx w84rpx flex items-center justify-center border border-#F0F0F0 border-solid text-24rpx text-#AAAAAA"
  798. >
  799. {{ item.num }}
  800. </view>
  801. <image
  802. :src="` ${StaticUrl}/add-cart.png`" class="h44rpx w44rpx"
  803. @click.stop="handleAdd(item)"
  804. />
  805. </view>
  806. </view>
  807. </view>
  808. </view>
  809. <view
  810. v-if="item.shopSkuStocks == '0' || item.isDelete == '1'"
  811. class="absolute left-0 top-0 z-1 h-full w-full bg-[rgba(255,255,255,.6)]"
  812. >
  813. <view class="relative w-full flex items-center justify-center">
  814. <view class="rounded-16rpx bg-[rgba(0,0,0,.5)] p20rpx text-white">
  815. {{ item.shopSkuStocks == '0' ? '商品已售罄' : '商品已删除' }}
  816. </view>
  817. <view class="absolute bottom-20rpx right-20rpx">
  818. <wd-button @click="handleDelCartGoods(item)">
  819. 删除商品
  820. </wd-button>
  821. </view>
  822. </view>
  823. </view>
  824. </view>
  825. </wd-checkbox-group>
  826. </view>
  827. <StatusTip v-if="!cartList.length" tip="暂无内容" />
  828. <view class="h320rpx" />
  829. </view>
  830. </view>
  831. </Zpopup>
  832. <selectSku
  833. v-model:show="selectGoods" v-model:sku-id="selectSkuId" v-model:select-goods-num="SelectGoodsNum"
  834. :goods-info="goodsInfo!"
  835. >
  836. <template #footer>
  837. <view class="box-border w-full flex items-center justify-between py20rpx">
  838. <view class="w-48%">
  839. <wd-button plain hairline block @click="selectGoods = false">
  840. 取消
  841. </wd-button>
  842. </view>
  843. <view class="w-48%">
  844. <wd-button block @click="handleSkuAddCart">
  845. 加入购物车
  846. </wd-button>
  847. </view>
  848. </view>
  849. </template>
  850. </selectSku>
  851. <view v-if="showball" class="ball">
  852. <image :src="`${StaticUrl}/cart-yes.png`" class="cart-img h52rpx w52rpx" />
  853. </view>
  854. </view>
  855. </template>
  856. <style scoped lang="scss">
  857. .right-nav {
  858. background: linear-gradient(180deg, #FAFFEC 0%, #F6FFDE 11%, #E7FFA5 100%);
  859. box-shadow: -10rpx 0rpx 12rpx 2rpx rgba(0, 0, 0, 0.07);
  860. }
  861. .fixedShadow {
  862. box-shadow: 0rpx -6rpx 12rpx 2rpx rgba(0, 0, 0, 0.05);
  863. }
  864. .wraper {
  865. display: flex;
  866. height: calc(100vh - var(--window-top) - v-bind(navHeight) - 390rpx - var(--window-bottom));
  867. height: calc(100vh - var(--window-top) - constant(safe-area-inset-bottom) - v-bind(navHeight) - 390rpx - var(--window-bottom));
  868. height: calc(100vh - var(--window-top) - env(safe-area-inset-bottom) - v-bind(navHeight) - 390rpx - var(--window-bottom));
  869. }
  870. .content {
  871. flex: 1;
  872. background: #fff;
  873. }
  874. @keyframes moveX {
  875. to {
  876. transform: translateX(v-bind(x));
  877. }
  878. }
  879. @keyframes moveY {
  880. to {
  881. transform: translateY(v-bind(y));
  882. }
  883. }
  884. .ball {
  885. position: fixed;
  886. z-index: 80;
  887. left: v-bind(left);
  888. top: v-bind(top);
  889. animation: moveX .5s linear forwards;
  890. }
  891. .cart-img {
  892. animation: moveY .5s cubic-bezier(1, -1.26, 1, 1) forwards;
  893. }
  894. :deep(.wd-checkbox-group){
  895. background-color: transparent !important;
  896. }
  897. </style>