index.tsx 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371
  1. import request from '@/helpers/request'
  2. import { moneyFormat } from '@/helpers/utils'
  3. import {
  4. Swipe,
  5. SwipeItem,
  6. Image,
  7. CellGroup,
  8. Cell,
  9. ImagePreview,
  10. RadioGroup,
  11. Radio,
  12. Tag,
  13. Row,
  14. Col,
  15. Sticky,
  16. ActionBar,
  17. ActionBarButton,
  18. ActionBarIcon,
  19. Icon,
  20. Badge,
  21. Toast,
  22. Popup,
  23. SubmitBar
  24. } from 'vant'
  25. import { defineComponent } from 'vue'
  26. import styles from './index.module.less'
  27. import iconShopCart from '../images/icon-shop-cart.png'
  28. import AddGoodsCart from '../modal/add-goods-cart'
  29. export default defineComponent({
  30. name: 'goods-detail',
  31. data() {
  32. const query = this.$route.query
  33. return {
  34. id: query.id,
  35. albumPics: [] as any[],
  36. product: {} as any,
  37. radio: 0,
  38. skuStockListTemp: [],
  39. detailMobileHtml: '',
  40. loading: false,
  41. addGoodsShow: false,
  42. selectGoodsItem: {},
  43. cartCount: 0,
  44. showType: 'cart'
  45. }
  46. },
  47. computed: {
  48. skuStockList() {
  49. // 处理规格
  50. const product = this.product
  51. const skuStockList: any =
  52. this.skuStockListTemp.length > 0
  53. ? this.skuStockListTemp
  54. : [
  55. {
  56. id: -1,
  57. price: product.price,
  58. pic: product.pic,
  59. stock: product.stock,
  60. spData: null
  61. }
  62. ]
  63. skuStockList.forEach((item: any) => {
  64. if (item.spData) {
  65. const spData = JSON.parse(item.spData)
  66. let str = ''
  67. spData.forEach((sp: any) => {
  68. str += `${sp.value}`
  69. })
  70. item.spDataJson = str
  71. } else {
  72. item.spDataJson = '默认'
  73. }
  74. })
  75. return skuStockList
  76. },
  77. getPrice() {
  78. let item = this.skuStockList.filter(n => n.id == this.radio) as any
  79. if (item && Array.isArray(item) && item.length) {
  80. return item[0].price
  81. }
  82. return 0
  83. }
  84. },
  85. async mounted() {
  86. try {
  87. this.loading = true
  88. const res = await request.get(
  89. `/api-mall-portal/product/detail/${this.id}`
  90. )
  91. this.loading = false
  92. const result = res.data || {}
  93. this.albumPics = [result.product.pic]
  94. .concat(result.product.albumPics.split(','))
  95. .filter(n => n)
  96. this.product = result.product
  97. this.skuStockListTemp = result.skuStockList || []
  98. if (this.skuStockListTemp.length) {
  99. let len = this.skuStockListTemp.length
  100. for (let i = 0; i < len; i++) {
  101. let item = this.skuStockListTemp[i] as any
  102. if (item.stock >= 0) {
  103. this.radio = item.id
  104. break
  105. }
  106. }
  107. }
  108. this.detailMobileHtml = result.product.detailMobileHtml
  109. } catch {}
  110. this.getCartCount()
  111. },
  112. methods: {
  113. onPreview(index: number) {
  114. // 图片预览
  115. ImagePreview({
  116. images: this.albumPics,
  117. startPosition: index,
  118. closeable: true
  119. })
  120. },
  121. onShowImg(target: any) {
  122. const { localName } = target.srcElement
  123. if (localName !== 'img') {
  124. return
  125. }
  126. let startPosition = 0
  127. const domList = document.querySelectorAll('.msgWrap img')
  128. let imgList = Array.from(domList).map((item: any, index: number) => {
  129. if (target.srcElement == item) {
  130. startPosition = index
  131. }
  132. return item.src
  133. })
  134. ImagePreview({
  135. images: imgList,
  136. startPosition: startPosition,
  137. closeable: true
  138. })
  139. },
  140. onShowCart(type = 'cart') {
  141. this.selectGoodsItem = {
  142. price: this.product.pic,
  143. stock: this.product.stock,
  144. skuStockList: this.skuStockListTemp.length
  145. ? this.skuStockListTemp
  146. : undefined,
  147. brandName: this.product.brandName,
  148. productCategoryId: this.product.productCategoryId,
  149. name: this.product.name,
  150. productSn: this.product.productSn,
  151. productSubTitle: this.product.subTitle,
  152. id: this.product.id
  153. }
  154. this.showType = type
  155. // 打开购物弹框
  156. this.addGoodsShow = true
  157. },
  158. onBuy() {
  159. // 购买
  160. if (!this.radio) {
  161. return Toast('请选择规格')
  162. }
  163. console.log(true)
  164. },
  165. async getCartCount() {
  166. try {
  167. let { code, data } = await request.get('/api-mall-portal/cart/list')
  168. if (code === 200) {
  169. this.cartCount = data.length
  170. }
  171. } catch (err) {}
  172. }
  173. },
  174. render() {
  175. const product = this.product
  176. return (
  177. <div class={styles.goodsDetail}>
  178. <Swipe
  179. class={styles.swipe}
  180. lazyRender
  181. v-slots={{
  182. indicator: (item: any) =>
  183. item.total > 1 && (
  184. <div class={styles['custom-indicator']}>
  185. {(item.active || 0) + 1} / {item.total}
  186. </div>
  187. )
  188. }}
  189. >
  190. {this.albumPics.map((item: string, index: number) => (
  191. <SwipeItem>
  192. <Image
  193. class={styles.swipeItemImg}
  194. src={item}
  195. onClick={() => this.onPreview(index)}
  196. fit="cover"
  197. />
  198. </SwipeItem>
  199. ))}
  200. </Swipe>
  201. <CellGroup border={false} class={[styles.goodsHead, 'mb12']}>
  202. <Cell
  203. center
  204. border={false}
  205. title={product.name}
  206. titleClass={[styles.goodsName, 'van-ellipsis']}
  207. />
  208. <Cell
  209. center
  210. border={false}
  211. v-slots={{
  212. title: () => (
  213. <div class={styles.priceGroup}>
  214. <span class={styles.price}>
  215. <i>¥</i>
  216. {moneyFormat(this.getPrice)}
  217. </span>
  218. <del class={styles.delPrice}>
  219. ¥{moneyFormat(product.originalPrice)}
  220. </del>
  221. </div>
  222. )
  223. // default: () => <div class={styles.stock}>销量4件</div>
  224. }}
  225. />
  226. </CellGroup>
  227. <Row class={[styles.row, 'mb12']}>
  228. <Col span={4} class={styles.col}>
  229. 规格
  230. </Col>
  231. <Col span={20}>
  232. <RadioGroup
  233. class={styles['radio-group']}
  234. modelValue={this.radio}
  235. onUpdate:modelValue={val => (this.radio = val)}
  236. >
  237. {this.skuStockList.map((item: any) => {
  238. const isActive = item.id === this.radio
  239. const type = isActive ? 'primary' : 'default'
  240. return (
  241. <Badge
  242. position="top-right"
  243. content={item.stock <= 0 ? '缺货' : ''}
  244. color={'#999999'}
  245. class={styles.badge}
  246. offset={[-20, 0]}
  247. >
  248. <Radio
  249. class={styles.radio}
  250. name={item.id}
  251. disabled={item.stock <= 0}
  252. onClick={() => {
  253. // 判断是否有库存
  254. if (item.stock <= 0) {
  255. return
  256. }
  257. this.radio = item.id
  258. }}
  259. >
  260. <Tag size="large" plain={isActive} type={type}>
  261. {item.spDataJson}
  262. </Tag>
  263. </Radio>
  264. </Badge>
  265. )
  266. })}
  267. </RadioGroup>
  268. </Col>
  269. </Row>
  270. {this.detailMobileHtml && (
  271. <div class={[styles.section]}>
  272. <div class={styles.detail}>
  273. <span>图文详情</span>
  274. </div>
  275. <div
  276. class={[styles.photoDetail, 'msgWrap']}
  277. onClick={this.onShowImg}
  278. v-html={this.detailMobileHtml}
  279. ></div>
  280. </div>
  281. )}
  282. {!this.loading && (
  283. <>
  284. {/* <ActionBar class={styles.actionBar}>
  285. <ActionBarIcon
  286. icon="cart-o"
  287. // text="购物车"
  288. onClick={() => {
  289. this.$router.push('/cart')
  290. }}
  291. v-slots={{
  292. icon: () => (
  293. <Badge content={this.cartCount} showZero={false}>
  294. <Icon name={iconShopCart} size={30} />
  295. </Badge>
  296. )
  297. }}
  298. />
  299. <div class={styles.buyGroup}>
  300. <ActionBarButton
  301. type="primary"
  302. class={styles.addCertBtn}
  303. text="加入购物车"
  304. onClick={() => this.onShowCart()}
  305. />
  306. <ActionBarButton
  307. type="primary"
  308. text="立即购买"
  309. onClick={() => this.onShowCart('cartConfirm')}
  310. />
  311. </div>
  312. </ActionBar> */}
  313. <SubmitBar
  314. class={styles.actionBar}
  315. safe-area-inset-bottom
  316. v-slots={{
  317. button: () => (
  318. <div class={styles.buyGroup}>
  319. <ActionBarButton
  320. type="primary"
  321. class={styles.addCertBtn}
  322. text="加入购物车"
  323. onClick={() => this.onShowCart()}
  324. />
  325. <ActionBarButton
  326. type="primary"
  327. text="立即购买"
  328. onClick={() => this.onShowCart('cartConfirm')}
  329. />
  330. </div>
  331. )
  332. }}
  333. >
  334. <Badge
  335. content={this.cartCount}
  336. showZero={false}
  337. onClick={() => {
  338. this.$router.push('/cart')
  339. }}
  340. >
  341. <Icon name={iconShopCart} size={30} />
  342. </Badge>
  343. </SubmitBar>
  344. </>
  345. )}
  346. <Popup
  347. show={this.addGoodsShow}
  348. closeable
  349. position="bottom"
  350. round
  351. onClose={() => {
  352. this.addGoodsShow = false
  353. }}
  354. >
  355. <AddGoodsCart
  356. onGetCartCount={() => this.getCartCount()}
  357. item={this.selectGoodsItem}
  358. defaultRadio={this.radio}
  359. showType={this.showType}
  360. />
  361. </Popup>
  362. </div>
  363. )
  364. }
  365. })