promoProduct.vue 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747
  1. <template>
  2. <view class="scrollPage">
  3. <!-- head -->
  4. <view class="scrollPage-header">
  5. <view :style="{height:statusBarHeight+'px'}"></view>
  6. <view class="header-title">
  7. <view class="header-left" @click="goBack">
  8. <image style="width: 16px; height: 16px;margin-right:3px;" mode="aspectFit" src="./static/arrow-left.png"></image>
  9. <text>返回</text>
  10. </view>
  11. <view class="header-title"><text class="ellipsis-one" overflow="ellipsis">{{title}}</text></view>
  12. <view class="header-right"></view>
  13. </view>
  14. </view>
  15. <!-- body -->
  16. <scroll-view
  17. class="scrollPage-body"
  18. :style="{height: (screenHeight - statusBarHeight - safeAreaBottom - 94) + 'px'}"
  19. scroll-y="true"
  20. type="custom"
  21. @scrolltolower="reachBottom">
  22. <!-- 搜索,吸顶 -->
  23. <sticky-header>
  24. <view class="search-head">
  25. <uni-search-bar radius="100" placeholder="请输入产品名称/轮胎规格" clearButton="auto" cancelButton="none" @confirm="searchList" @clear="searchList" />
  26. </view>
  27. </sticky-header>
  28. <!-- 产品列表 -->
  29. <productItem
  30. v-for="(item,index) in list"
  31. :key="index"
  32. :list="item"
  33. :index="index"
  34. :itemWidth="(screenWidth*0.94-screenWidth*0.03)*0.5"
  35. :gap="screenWidth*0.03"
  36. @chooseProduct="chooseProduct"
  37. ></productItem>
  38. <!-- loading -->
  39. <view class="loading-bar" v-if="list.length && (loading||loadEnd)">{{loadText}}</view>
  40. <view class="empty-bar" v-if="total==0&&!loading">
  41. <image mode="aspectFit" :src="empty.imgUrl"></image>
  42. <view>{{empty.tip}}</view>
  43. </view>
  44. </scroll-view>
  45. <!-- footer -->
  46. <view class="scrollPage-footer">
  47. <view>
  48. <view class="bottom-bar-left">
  49. <view class="bottom-bar-left-item">
  50. <view class="bottom-bar-left-item-icon" @click="openChoose">
  51. <image style="width: 20px; height: 20px;" mode="aspectFit" src="./static/cart.png"></image>
  52. <text class="badge" v-if="totalNum">{{totalNum>99?'99+':totalNum}}</text>
  53. </view>
  54. </view>
  55. <view class="bottom-bar-left-item">
  56. <!-- 合计:<view class="price-txt">¥<text>{{totalAmount}}</text></view> -->
  57. </view>
  58. </view>
  59. <view :class="'bottom-bar-right'" @click="submitOrder">
  60. <view class="bottom-bar-right-item">去结算</view>
  61. </view>
  62. </view>
  63. <view :style="{height:safeAreaBottom+'px'}"></view>
  64. </view>
  65. <!-- 已选产品 -->
  66. <page-container
  67. :show="showChoosePopu"
  68. :round="true"
  69. :z-index="10"
  70. position="bottom"
  71. custom-style="height:80%"
  72. @afterenter="openRender"
  73. @afterleave="closePopu">
  74. <view class="popup-body" v-if="showSwipeAction">
  75. <scroll-view
  76. :style="{width:screenWidth + 'px',height:'100%'}"
  77. scroll-y="true"
  78. type="custom"
  79. >
  80. <sticky-header>
  81. <view class="choose-title">
  82. <view class="choose-left">
  83. <view>已选<text class="p-nums">{{chooseLength}}</text>款产品</view>
  84. <button @click="claerChoose" size="mini" type="warn" plain>清空列表</button>
  85. <button class="editBtn" @click="handleEditChoose" size="mini" type="primary" plain>{{editChoose?'完成':'编辑'}}</button>
  86. </view>
  87. <view class="choose-close" @click="showChoosePopu=false">
  88. <icon type="clear" size="24"></icon>
  89. </view>
  90. </view>
  91. </sticky-header>
  92. <chooseProductItem
  93. v-for="(item, index) in showList"
  94. :key="item.id"
  95. :index="index"
  96. :list="item"
  97. :edit="editChoose"
  98. @changeQty="changeQty"
  99. @remove="removeChoose">
  100. </chooseProductItem>
  101. <view style="min-height: 180rpx;"></view>
  102. </scroll-view>
  103. <view class="choose-footer">
  104. <view>
  105. <view class="choose-footer-left">
  106. <view class="choose-footer-left-item">
  107. <!-- 总数:<view><text class="p-nums">{{totalNum}}</text></view> -->
  108. </view>
  109. <view class="choose-footer-left-item">
  110. 合计:<view class="price-txt">¥<text>{{totalAmount}}</text></view>
  111. </view>
  112. </view>
  113. <view class="choose-footer-right bg-1" @click="submitOrder">
  114. <view class="choose-footer-right-item">确认采购</view>
  115. </view>
  116. </view>
  117. <view :style="{height:safeAreaBottom+'px'}"></view>
  118. </view>
  119. </view>
  120. </page-container>
  121. </view>
  122. </template>
  123. <script>
  124. import {
  125. mapState,
  126. mapMutations,
  127. } from 'vuex'
  128. import { queryPromoProductByPage } from '@/api/video.js'
  129. import { purchaseSave } from '@/api/purchase.js'
  130. import productItem from '@/pagesB/components/productItemSkline.vue'
  131. import chooseProductItem from '@/pagesB/components/chooseProductItemSkline.vue'
  132. export default {
  133. components:{
  134. productItem,
  135. chooseProductItem
  136. },
  137. data() {
  138. return {
  139. title: '促销产品列表',
  140. pageNo:1,
  141. pageSize:16,
  142. total:0,
  143. list:[],
  144. loading:false,
  145. loadEnd: false,
  146. loadText: '加载中...',
  147. empty: {
  148. tip: '暂无产品',
  149. imgUrl: '/static/nodata.png'
  150. },
  151. queryWord:'',
  152. promoActiveSn:'',
  153. screenWidth: 750,
  154. screenHeight: 0,
  155. statusBarHeight: 44,
  156. safeAreaBottom: 0,
  157. // 已选弹框
  158. showChoosePopu: false,
  159. showSwipeAction: false,
  160. chooseList:[],
  161. editChoose: false,
  162. choosePageSize: 12,
  163. // 数量,合计
  164. chooseLength: 0, //已选产品总款数
  165. totalAmount: 0, //已选产品总金额
  166. totalNum: 0, //已选产品总数量
  167. };
  168. },
  169. computed: {
  170. ...mapState(['hasLogin']),
  171. userInfo(){
  172. return this.$store.state.vuex_userInfo
  173. },
  174. fristLength(){
  175. return this.chooseList.length > 0 ? this.chooseList[0].length : 0
  176. },
  177. showList(){
  178. return this.showSwipeAction ? this.chooseList : []
  179. }
  180. },
  181. onLoad(opts) {
  182. this.title = opts.title||'促销产品列表'
  183. this.statusBarHeight = uni.getSystemInfoSync().statusBarHeight
  184. this.screenWidth = uni.getSystemInfoSync().windowWidth
  185. this.screenHeight = uni.getSystemInfoSync().windowHeight
  186. this.safeAreaBottom = uni.getSystemInfoSync().safeAreaInsets.bottom
  187. // 清空临时数据
  188. this.$store.state.vuex_tempData = null
  189. this.promoActiveSn = opts.promoActiveSn
  190. // 查询列表
  191. this.getList()
  192. this.chooseList = this.getCacheList()
  193. this.getChooseHeji()
  194. },
  195. onUnload(){
  196. // 存储已选列表值
  197. const hasCache = this.$store.state.vuex_cartList.find(k => k.sn == this.promoActiveSn)
  198. if(hasCache){
  199. hasCache.list = this.chooseList
  200. }else{
  201. this.$store.state.vuex_cartList.push({sn:this.promoActiveSn,list: this.chooseList})
  202. }
  203. this.$u.vuex('vuex_cartList', this.$store.state.vuex_cartList)
  204. // 清空数据
  205. this.chooseList = null
  206. this.list = null
  207. },
  208. methods: {
  209. goBack() {
  210. uni.navigateBack();
  211. },
  212. // 从缓存获取数据
  213. getCacheList(){
  214. const cacheList = this.$store.state.vuex_cartList.find(k => k.sn == this.promoActiveSn)
  215. return cacheList ? JSON.parse(JSON.stringify(cacheList.list)) : []
  216. },
  217. // 滚动到底部
  218. reachBottom() {
  219. if(!this.loading && !this.loadEnd){
  220. this.pageNo++
  221. this.getList()
  222. }
  223. },
  224. // 搜索
  225. searchList(event){
  226. this.loadEnd = false
  227. this.loadText = '加载中...'
  228. this.queryWord = event.value
  229. this.list = []
  230. this.pageNo = 1
  231. this.getList()
  232. },
  233. // 列表查询
  234. getList(){
  235. this.loading = true
  236. queryPromoProductByPage({
  237. pageNo: this.pageNo,
  238. pageSize: this.pageSize,
  239. queryWord: this.queryWord,
  240. promoActiveSn: this.promoActiveSn
  241. }).then(res => {
  242. this.loading = false
  243. if(res.status == 200){
  244. this.total = res.data.count
  245. if(this.total){
  246. const list = res.data.list || []
  247. const ret = []
  248. // 更新已选状态
  249. list.forEach(k => {
  250. // 如果筛选或分页后,更新页面列表产品数量
  251. const row = this.getChooseItemById(k.id)
  252. const hasChecked = row.p>=0 && row.s>=0
  253. ret.push({
  254. id: k.id,
  255. cost: k.cost,
  256. checked: hasChecked ? true : false,
  257. qty: hasChecked ? this.chooseList[row.p][row.s].qty : 0,
  258. productCode: k.productCode,
  259. productSn: k.productSn,
  260. productImage: k.productImage,
  261. productName: k.productName,
  262. productOrigCode: k.productOrigCode,
  263. promoActiveSn: k.promoActiveSn,
  264. promoRuleSn: k.promoRuleSn,
  265. promoRuleValue: k.promoRuleValue,
  266. promoRuleType: k.promoRuleType,
  267. })
  268. })
  269. // 追加数据
  270. this.list.push(ret)
  271. // 判断是否最后一页
  272. const maxPages = Math.ceil(this.total / this.pageSize)
  273. if(this.pageNo >= maxPages){
  274. this.loadEnd = true
  275. this.loadText = '没有更多了'
  276. }
  277. }else{
  278. this.loadEnd = false
  279. this.loadText = '暂无产品'
  280. }
  281. }
  282. }).catch(err => {
  283. this.loading = false
  284. })
  285. },
  286. // 根据id查找chooseList
  287. getChooseItemById(id){
  288. const a = this.chooseList.findIndex(k => k.find(s=> s.id == id))
  289. const b = a>=0 ? this.chooseList[a].findIndex(s=> s.id == id) : -1
  290. return {p:a,s:b}
  291. },
  292. // 计算总款数、合计、数量
  293. getChooseHeji(){
  294. this.chooseLength = 0
  295. this.totalAmount = 0
  296. this.totalNum = 0
  297. this.chooseList.forEach(key => {
  298. this.chooseLength += key.length // 总款数
  299. key.forEach(item => {
  300. this.totalAmount += item.cost * item.qty // 总金额
  301. this.totalNum += item.qty // 总数量
  302. })
  303. })
  304. },
  305. // 选择产品
  306. chooseProduct(item,index){
  307. const rowItem = this.list[index].find(k => k.id == item.id)
  308. // 新加
  309. if(!rowItem.checked){
  310. rowItem.checked = true
  311. rowItem.qty = 1
  312. // 存入二维数组中
  313. const chooselens = this.chooseList.length
  314. if(chooselens){
  315. const firstRow = this.chooseList[0]
  316. if(firstRow.length<this.choosePageSize){
  317. firstRow.unshift(this.$u.deepClone(rowItem))
  318. }else{
  319. this.chooseList.unshift([this.$u.deepClone(rowItem)])
  320. }
  321. }else{
  322. this.chooseList.unshift([this.$u.deepClone(rowItem)])
  323. }
  324. }else{
  325. rowItem.qty = rowItem.qty + 1
  326. // 已添加,则增加已选列表中的数量
  327. const row = this.getChooseItemById(rowItem.id)
  328. if(row.p>=0 && row.s>=0){
  329. this.chooseList[row.p][row.s].qty = this.chooseList[row.p][row.s].qty + 1
  330. this.chooseList[row.p].splice()
  331. }
  332. }
  333. // 合计
  334. this.getChooseHeji()
  335. },
  336. // 打开已选产品列表
  337. openChoose(){
  338. this.submitOk = false
  339. if(this.chooseLength){
  340. uni.showLoading({
  341. title:'加载中...',
  342. mask: true
  343. })
  344. this.showChoosePopu = true
  345. }else{
  346. this.$u.toast(`请选择产品`);
  347. }
  348. },
  349. // 渲染已选产品列表
  350. openRender(){
  351. this.showSwipeAction = true
  352. setTimeout(()=>{
  353. uni.hideLoading()
  354. },300)
  355. },
  356. // 关闭后
  357. closePopu(){
  358. setTimeout(()=>{
  359. this.showSwipeAction = false
  360. },300)
  361. this.showChoosePopu = false
  362. this.editChoose = false
  363. uni.hideLoading()
  364. // 提交成功
  365. if(this.submitOk){
  366. uni.navigateTo({
  367. url: "/pagesB/procureOrder"
  368. })
  369. }
  370. },
  371. // 确定清空已选列表
  372. claerChoose(){
  373. const _this = this
  374. uni.showModal({
  375. title: '提示',
  376. content: '确定清空已选列表?',
  377. confirmText: '确定',
  378. success(res) {
  379. if(res.confirm){
  380. uni.showLoading({
  381. title: '正在清空...',
  382. mask: true
  383. })
  384. _this.clearChooseData()
  385. }
  386. }
  387. })
  388. },
  389. // 清空已选数据
  390. clearChooseData(){
  391. this.showChoosePopu = false
  392. this.chooseList = []
  393. // 合计
  394. this.getChooseHeji()
  395. // 重置已选状态
  396. this.list.forEach(key => {
  397. key.filter(item => item.checked).forEach(item=>{
  398. item.checked = false
  399. item.qty = 0
  400. })
  401. })
  402. },
  403. // 编辑已选产品
  404. handleEditChoose(){
  405. this.editChoose=!this.editChoose
  406. uni.showLoading({
  407. title: '加载中...',
  408. mask: true
  409. })
  410. this.$nextTick(()=>{
  411. uni.hideLoading()
  412. })
  413. },
  414. // 根据id 获取页面list项
  415. getListItemById(id){
  416. const aindex = this.list.findIndex(key => key.find(a => a.id == id))
  417. const aindex2 = aindex >=0 ? this.list[aindex].findIndex(a => a.id == id) : -1
  418. return {p:aindex,s:aindex2}
  419. },
  420. // 已选产品变更数量
  421. changeQty(val,id){
  422. const crow = this.getChooseItemById(id)
  423. if(crow.p>=0&&crow.s>=0){
  424. this.chooseList[crow.p][crow.s].qty = val
  425. this.chooseList[crow.p].splice()
  426. // 合计
  427. this.getChooseHeji()
  428. }
  429. // 更新页面产品数据
  430. const row = this.getListItemById(id)
  431. if(row.p>=0 && row.s>=0){
  432. this.list[row.p][row.s].qty = val
  433. this.list[row.p].splice()
  434. }
  435. },
  436. //已选产品删除
  437. removeChoose(id){
  438. // 删除项
  439. const crow = this.getChooseItemById(id)
  440. if(crow.p>=0&&crow.s>=0){
  441. this.chooseList[crow.p].splice(crow.s, 1);
  442. // 合计
  443. this.getChooseHeji()
  444. }
  445. // 如果没有数据了
  446. if(this.chooseList.length==0){
  447. this.clearChooseData()
  448. }
  449. // 更新列表已选状态
  450. const row = this.getListItemById(id)
  451. if(row.p>=0 && row.s>=0){
  452. this.list[row.p][row.s].qty = 0
  453. this.list[row.p][row.s].checked = false
  454. this.list[row.p].splice()
  455. }
  456. },
  457. // 去结算
  458. submitOrder(){
  459. if(this.showChoosePopu){
  460. uni.showLoading({
  461. title: '提交中...',
  462. mask: true
  463. })
  464. // 展开成一维数组
  465. const detailList = []
  466. this.chooseList.forEach(key => {
  467. key.forEach(item => {
  468. detailList.push({
  469. promoActiveSn: this.promoActiveSn,
  470. promoRuleSn:item.promoRuleSn,
  471. productSn:item.productSn,
  472. productCode:item.productCode,
  473. qty:item.qty,
  474. price:item.cost,
  475. giveAmount:item.promoRuleValue
  476. })
  477. })
  478. })
  479. purchaseSave({
  480. promoActiveSn: this.promoActiveSn,
  481. detailList: detailList
  482. }).then(res => {
  483. if(res.status == 200){
  484. res.data.detailList = []
  485. this.$store.state.vuex_tempData = res.data
  486. this.clearChooseData()
  487. this.submitOk = true
  488. }else{
  489. uni.showToast({
  490. title: res.message,
  491. icon: 'none'
  492. })
  493. }
  494. })
  495. }else{
  496. this.openChoose()
  497. }
  498. }
  499. }
  500. }
  501. </script>
  502. <style lang="less">
  503. /* 显示一行省略号 */
  504. .ellipsis-one{
  505. white-space: nowrap;
  506. text-overflow: ellipsis;
  507. overflow: hidden;
  508. word-break: break-all;
  509. }
  510. .scrollPage{
  511. height: 100vh;
  512. display: flex;
  513. flex-direction: column;
  514. .loading-bar{
  515. text-align: center;
  516. padding: 10px 0;
  517. color: #999999;
  518. }
  519. .empty-bar{
  520. display: flex;
  521. flex-direction: column;
  522. align-items: center;
  523. justify-content: center;
  524. padding: 20px 0;
  525. image{
  526. width: 100px;
  527. height: 100px;
  528. }
  529. view{
  530. font-size: 14px;
  531. color: #999999;
  532. margin-top: 10px;
  533. }
  534. }
  535. .scrollPage-header{
  536. z-index: 1;
  537. .header-title{
  538. display: flex;
  539. align-items: center;
  540. justify-content: space-between;
  541. padding: 0 10px;
  542. height: 44px;
  543. background-color: #FFFFFF;
  544. box-shadow: 0px 1px 0px 0px #E6E6E6;
  545. .header-title{
  546. font-size: 16px;
  547. color: #333333;
  548. max-width: 70%;
  549. }
  550. .header-left{
  551. display: flex;
  552. align-items: center;
  553. text{
  554. font-size: 14px;
  555. color: #333333;
  556. margin-left: 5px;
  557. }
  558. }
  559. .header-right{
  560. width: 50px;
  561. }
  562. }
  563. }
  564. .scrollPage-body{
  565. flex-grow: 1;
  566. height: 100%;
  567. .search-head{
  568. background: #fff;
  569. }
  570. }
  571. .scrollPage-footer{
  572. background: #fff;
  573. z-index: 200;
  574. > view{
  575. height: 50px;
  576. display: flex;
  577. align-items: center;
  578. justify-content: space-between;
  579. font-size: 32upx;
  580. }
  581. .bottom-bar-left{
  582. padding: 0 30upx;
  583. flex-grow: 1;
  584. display: flex;
  585. align-items: center;
  586. }
  587. .bottom-bar-left-item{
  588. padding-right: 20rpx;
  589. font-size: 24rpx;
  590. display: flex;
  591. align-items: center;
  592. .price-txt{
  593. color: red;
  594. display: flex;
  595. align-items: center;
  596. text{
  597. font-size: 36rpx;
  598. }
  599. }
  600. }
  601. .bottom-bar-left-item-icon{
  602. position: relative;
  603. background: #2196F3;
  604. width: 80rpx;
  605. height: 80rpx;
  606. border-radius: 100%;
  607. display: flex;
  608. align-items: center;
  609. justify-content: center;
  610. .badge{
  611. background: #f44336;
  612. color: #fff;
  613. position: absolute;
  614. left: 50rpx;
  615. top: -3px;
  616. font-size: 12px;
  617. display: flex;
  618. align-items: center;
  619. justify-content: center;
  620. border-radius: 50rpx;
  621. padding: 0 10rpx;
  622. }
  623. }
  624. .bottom-bar-right{
  625. width: 35%;
  626. height: 100%;
  627. background: #066cff;
  628. display: flex;
  629. align-items: center;
  630. justify-content: center;
  631. .bottom-bar-right-item{
  632. color: #fff;
  633. }
  634. }
  635. .bg-0{
  636. background: #066cff;
  637. }
  638. .bg-1{
  639. background: #f44336;
  640. }
  641. }
  642. .choose-title{
  643. display: flex;
  644. align-items:center;
  645. justify-content: space-between;
  646. background: #fff;
  647. width: 100%;
  648. border-bottom: 1px solid #eee;
  649. border-radius: 40rpx 40rpx 0 0;
  650. > view{
  651. padding: 20rpx 30rpx;
  652. }
  653. .choose-left{
  654. display: flex;
  655. align-items:center;
  656. flex-grow: 1;
  657. > view{
  658. display: flex;
  659. align-items:center;
  660. flex-grow: 1;
  661. }
  662. button{
  663. padding: 0 10px;
  664. height: auto;
  665. font-size: 12px;
  666. margin-left: 10px;
  667. }
  668. .editBtn{
  669. color: #2196F3;
  670. border-color: #2196F3;
  671. }
  672. }
  673. .p-nums{
  674. color: #2196F3;
  675. margin: 0 6rpx;
  676. }
  677. }
  678. .popup-body{
  679. height: 100%;
  680. position: relative;
  681. }
  682. .choose-footer{
  683. position: fixed;
  684. bottom: 0;
  685. left: 0;
  686. background: #fff;
  687. z-index: 100;
  688. width: 100%;
  689. > view{
  690. width: 100%;
  691. height: 100rpx;
  692. display: flex;
  693. align-items: center;
  694. justify-content: space-between;
  695. }
  696. .choose-footer-left{
  697. flex-grow: 1;
  698. display: flex;
  699. align-items: center;
  700. padding: 0 30rpx;
  701. > view{
  702. font-size: 28rpx;
  703. }
  704. .choose-footer-left-item{
  705. display: flex;
  706. align-items: center;
  707. margin-right: 10px;
  708. .price-txt{
  709. color: red;
  710. display: flex;
  711. align-items: center;
  712. text{
  713. font-size: 36rpx;
  714. }
  715. }
  716. }
  717. }
  718. .choose-footer-right{
  719. width: 35%;
  720. height: 100%;
  721. background: #2196F3;
  722. display: flex;
  723. align-items: center;
  724. justify-content: center;
  725. .choose-footer-right-item{
  726. color: #fff;
  727. }
  728. }
  729. .bg-1{
  730. background: #f44336;
  731. }
  732. }
  733. }
  734. </style>