index.vue 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721
  1. <template>
  2. <ContentWrap>
  3. <!-- 搜索工作栏 -->
  4. <el-form
  5. :model="queryParams"
  6. ref="queryFormRef"
  7. :inline="true"
  8. label-width="68px"
  9. >
  10. <el-form-item :label="t('work.selectStore')" prop="orderId">
  11. <el-select
  12. v-model="queryParams.shopId"
  13. class="!w-240px"
  14. >
  15. <el-option
  16. v-for="item in shopList"
  17. :key="item.id"
  18. :label="item.name"
  19. :value="item.id"
  20. />
  21. </el-select>
  22. </el-form-item>
  23. <el-form-item>
  24. <el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> {{t('common.search')}}</el-button>
  25. <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> {{t('common.reset')}}</el-button>
  26. </el-form-item>
  27. </el-form>
  28. </ContentWrap>
  29. <!-- 列表 -->
  30. <ContentWrap>
  31. <div style="display: flex;flex-direction: row;width: 100%;">
  32. <div>
  33. <!-- <el-row >
  34. <el-col :span="5" style="min-height:700px;min-width:310px"> -->
  35. <div class="cart-container" >
  36. <div class="title">
  37. <div class="logo">
  38. {{ t('mall.cashier') }}:
  39. </div>
  40. <div class="member-info">
  41. <span class="name">{{userStore.getUser.nickname}}</span>
  42. </div>
  43. </div>
  44. <div class="carts">
  45. <div>
  46. <div class="tab">
  47. <div class="cart-list" v-if="cartList.length > 0">
  48. <div class="cart-item" v-for="(cart,idx) in cartList" :key="idx">
  49. <img class="image" :src="cart.image"/>
  50. <div class="info">
  51. <div class="name">{{cart.storeName}}</div>
  52. <div class="spec">
  53. <span class="item" >{{cart.sku}}</span>
  54. </div>
  55. <div class="num"><el-input-number class="input" @change="changeBuyNum(cart)" v-model="cart.cartNum" :min="1" :max="1000"/></div>
  56. </div>
  57. <div class="option">
  58. <div class="remove" @click="removeFromCart(cart.id)"><Icon icon="ep:delete" class="mr-5px" /> </div>
  59. <div class="total">¥{{cart.price}}</div>
  60. </div>
  61. </div>
  62. </div>
  63. <div class="empty" v-else>
  64. <el-empty :description="t('mall.noItemsAvailableAtThisTime')" :image-size="40" />
  65. </div>
  66. </div>
  67. </div>
  68. </div>
  69. <div class="footer">
  70. <div class="number">
  71. <div class="total-num">{{t('mall.totalNumberOfItems')}}:<b class="num">{{getCartGoodsNumber}}</b></div>
  72. <div class="total-price">{{t('mall.totalAmount')}}:<b class="num">¥{{getCartGoodsPrice}}</b></div>
  73. </div>
  74. <div class="options">
  75. <div style="width:300px;margin-top:10px">
  76. <el-button type="primary" round @click="hangOff()">>{{t('mall.orderPickup')}}</el-button>
  77. <el-button type="warning" round @click="hangUp()">>{{t('mall.pendingOrders')}}</el-button>
  78. <el-button type="danger" round @click="doSettlement()">>{{t('mall.settleMattersSubmit')}}</el-button>
  79. </div>
  80. </div>
  81. </div>
  82. </div>
  83. </div>
  84. <!-- </el-col>
  85. <el-col :span="15"> -->
  86. <div>
  87. <div class="main-list">
  88. <div class="title">
  89. <el-form class="search-form" ref="searchForm" :inline="true">
  90. <el-form-item class="form-item" label="" prop="keyword">
  91. <el-input prefix-icon="el-icon-full-screen" v-model="queryParams2.storeName" class="input-item" :placeholder="t('mall.pleaseEnterTheProductKeywordProductName')" clearable maxlength="100" />
  92. </el-form-item>
  93. <el-form-item>
  94. <el-button class="search-goods" type="danger" @click="doQueryGoods()"><Icon icon="ep:search" class="mr-5px" />{{ t('mall.checkProducts')}}</el-button>
  95. </el-form-item>
  96. </el-form>
  97. <el-tabs class="tab-box" v-model="activeName" type="card" @tab-click="switchTab">
  98. <el-tab-pane :label="t('public.all')" :name="0" />
  99. <el-tab-pane :label="item.name" :name="item.id" :key="idx" v-for="(item,idx) in cateList" />
  100. </el-tabs>
  101. </div>
  102. <div class="goods-list">
  103. <div class="goods-item" :key="idx" v-for="(item,idx) in productList">
  104. <div class="item" @click="clickGoods(item.id)" >
  105. <img class="image" lazy :src="item.image" />
  106. <div class="goods-name">{{item.storeName}}</div>
  107. <div class="goods-price">¥{{item.price}}</div>
  108. </div>
  109. </div>
  110. <el-dialog :title="t('mall.selectProductSpecification')" v-model="dialogVisible" class="common-dialog" append-to-body>
  111. <div class="goods-info">
  112. <div class="name">{{productInfo.storeName}}</div>
  113. <div class="price">¥{{productInfo.price}}</div>
  114. <div class="num"><el-input-number class="input" v-model="goodsNum" :min="1" :max="1000"/></div>
  115. <div class="spec-list">
  116. <div class="spec-item" v-for="(attr,index) in productInfo.productAttr" :key="index">
  117. <div class="spec-name">{{attr.attrName}}</div>
  118. <div class="values">
  119. <span class="value" :class="{'active': value == newValue[index]}" v-for="(value,key) in attr.attrValueArr" :key="key" @click="changePropertyDefault(index, key,false)">{{value}}</span>
  120. </div>
  121. </div>
  122. </div>
  123. </div>
  124. <template #footer>
  125. <div class="dialog-footer">
  126. <el-button type="primary" class="main-button" @click="addToCart()">{{t('mall.settleMattersAdding')}}</el-button>
  127. <el-button @click="closeGoodsDialog()">{{t('common.cancel')}}</el-button>
  128. </div>
  129. </template>
  130. </el-dialog>
  131. <el-empty v-if="productList.length == 0" :description="t('mall.noProducts')" />
  132. </div>
  133. <div>
  134. <Pagination
  135. :total="total"
  136. v-model:page="queryParams2.pageNo"
  137. v-model:limit="queryParams2.pageSize"
  138. @pagination="getProductList"
  139. />
  140. </div>
  141. </div>
  142. </div>
  143. <!-- </el-col>
  144. </el-row> -->
  145. </div>
  146. </ContentWrap>
  147. <settlement ref="settlementRef" :cart-data="cartList" :shop-id="shopId" @get-cart-list="getCartList" />
  148. <!-- <payResult ref="payResultRef" />
  149. <scanPay ref="scanPayRef" /> -->
  150. <hangList ref="hangListRef" @get-cart-list="getCartList" />
  151. </template>
  152. <script setup lang="ts" name="Cashier">
  153. import * as ProductCategoryApi from '@/api/mall/product/category'
  154. import * as StoreProductApi from '@/api/mall/product/product'
  155. import * as ShopApi from '@/api/mall/store/shop'
  156. import * as CashierApi from '@/api/mall/cashier'
  157. import type { TabsPaneContext } from 'element-plus'
  158. import { formatPast,formatDate } from '@/utils/formatTime'
  159. import settlement from './settlement.vue'
  160. // import payResult from './payResult.vue'
  161. // import scanPay from './scanPay.vue'
  162. import hangList from './hangList.vue'
  163. import { useUserStore } from '@/store/modules/user'
  164. const { t } = useI18n()
  165. const userStore = useUserStore()
  166. const message = useMessage() // 消息弹窗
  167. const shopId = ref(0)
  168. const dialogVisible = ref(false)
  169. const loading = ref(true) // 列表的加载中
  170. const total = ref(1) // 列表的总页数
  171. const list = ref([]) // 列表的数据
  172. const queryParams = reactive({
  173. name: undefined,
  174. shopId: undefined
  175. })
  176. const queryParams2 = reactive({
  177. pageNo: 1,
  178. pageSize: 8,
  179. storeName: null,
  180. cateId: null,
  181. isShow: 1,
  182. shopId: undefined
  183. })
  184. const queryFormRef = ref() // 搜索的表单
  185. const activeName = ref(0)
  186. const shopList = ref([])
  187. const cateList = ref([])
  188. const productList = ref([])
  189. const memberInfo = ref({})
  190. const productInfo = ref({})
  191. const goodsNum = ref(1)
  192. const newValue = ref([])
  193. const thisProductSku = ref({})
  194. const cartList = ref([])
  195. const getCartGoodsNumber = computed(() => { //计算购物车总数
  196. return cartList.value.reduce((acc, cur) => acc + cur.cartNum, 0)
  197. })
  198. const getCartGoodsPrice = computed(() =>{ //计算购物车总价
  199. let price = cartList.value.reduce((acc, cur) => acc + cur.cartNum * cur.price, 0);
  200. return parseFloat(price).toFixed(2);
  201. })
  202. const handleClick = (tab: TabsPaneContext, event: Event) => {
  203. console.log(tab.paneName, event)
  204. }
  205. const getShopList = async () => {
  206. try {
  207. const data = await ShopApi.getShopList()
  208. shopList.value = data
  209. if(userStore.getUser.shopId > 0){
  210. shopId.value = userStore.getUser.shopId
  211. }else{
  212. shopId.value = shopList.value[0].id
  213. }
  214. if(shopId.value == 0){
  215. message.error(t('mall.pleaseAddAStoreOrSelectAStore'))
  216. return
  217. }
  218. queryParams.shopId = shopId.value
  219. queryParams2.shopId = shopId.value
  220. console.log('shopId:',shopId.value)
  221. getCateList()
  222. getProductList()
  223. getCartList(shopId.value)
  224. } finally {
  225. }
  226. }
  227. const getCateList = async () => {
  228. try {
  229. const data = await ProductCategoryApi.getCategoryList(queryParams)
  230. cateList.value = data
  231. } finally {
  232. }
  233. }
  234. const getProductList = async () => {
  235. try {
  236. const data = await StoreProductApi.getStoreProductPage(queryParams2)
  237. productList.value = data.list
  238. total.value = data.total
  239. } finally {
  240. }
  241. }
  242. const switchTab = (tab: TabsPaneContext, event: Event) => {
  243. console.log(tab.paneName)
  244. if(tab.paneName > 0){
  245. queryParams2.cateId = tab.paneName
  246. getProductList()
  247. }else{
  248. queryParams2.cateId = null
  249. getProductList()
  250. }
  251. }
  252. const doQueryGoods = () => {
  253. getProductList()
  254. }
  255. const clickGoods = async(id) => {
  256. dialogVisible.value = true
  257. const data = await CashierApi.getProductInfo({id:id})
  258. productInfo.value = data
  259. newValue.value = []
  260. }
  261. const changePropertyDefault = (index, key, isDefault) => { //改变默认属性值
  262. let valueStr = ''
  263. if(isDefault){
  264. newValue.value = []
  265. for(let i = 0;i < productInfo.value.productAttr.length;i++){
  266. newValue.value[i] = productInfo.value.productAttr[i].attrValueArr[0]
  267. }
  268. }else{
  269. newValue.value[index] = productInfo.value.productAttr[index].attrValueArr[key]
  270. //valueStr = newValue.value.join(',')
  271. }
  272. valueStr = newValue.value.join(',')
  273. console.log('newValue:',newValue.value)
  274. let productValue = productInfo.value.productValue[valueStr]
  275. console.log('productValue:',productValue)
  276. if(!productValue) {
  277. let skukey = JSON.parse(JSON.stringify(newValue.value))
  278. skukey.sort((a, b) => a.localeCompare(b))
  279. //console.log('skukey:',skukey)
  280. valueStr = skukey.join(',')
  281. productValue = productInfo.value.productValue[valueStr]
  282. }
  283. console.log('valueStr:',valueStr)
  284. console.log('productValue:',productValue)
  285. if(productValue){
  286. thisProductSku.value = productValue
  287. productInfo.value.price = parseFloat(productValue.price).toFixed(2);
  288. }
  289. }
  290. const closeGoodsDialog = () => {
  291. dialogVisible.value = false
  292. }
  293. const addToCart = async() => {
  294. if(Object.keys(thisProductSku.value).length == 0) {
  295. message.error(t('mall.pleaseSelectTheSpecification'))
  296. return
  297. }
  298. console.log('user:',thisProductSku.value)
  299. //let uid = memberInfo.value.id ? memberInfo.value.id : 0
  300. if(shopId.value == 0){
  301. message.error(t('mall.pleaseSelectTheStore'))
  302. return
  303. }
  304. let data = {uid:0,shopId:shopId.value,productId:thisProductSku.value.productId,
  305. productAttrUnique:thisProductSku.value.unique,
  306. cartNum:goodsNum.value}
  307. const res = await CashierApi.addCart(data)
  308. getCartList(shopId.value)
  309. closeGoodsDialog()
  310. }
  311. const getCartList = async(id) => {
  312. const res = await CashierApi.getCarts({shopId:id})
  313. cartList.value = res
  314. }
  315. const changeBuyNum = async(cart) => {
  316. console.log('cart:',cart)
  317. if(cart.cartNum < 1){
  318. message.error(t('mall.quantityCannotBeLessThan1'))
  319. return
  320. }
  321. await CashierApi.updateCartNum(cart)
  322. }
  323. const removeFromCart = async(id) => {
  324. await CashierApi.delCart({id:id})
  325. getCartList(shopId.value)
  326. }
  327. const hangUp = async() => {
  328. let ids = cartList.value.map(item => item.id);
  329. console.log('ids:',ids)
  330. if(ids.length == 0){
  331. message.error(t('mall.noProductsListed'))
  332. return
  333. }
  334. //console.log('ids:',ids.toString())
  335. await CashierApi.hangUp({ids:ids.toString()})
  336. getCartList(shopId.value)
  337. }
  338. const hangListRef = ref()
  339. const hangOff = () => {
  340. hangListRef.value.open(shopId.value)
  341. }
  342. const settlementRef = ref()
  343. // const payResultRef = ref()
  344. // const scanPayRef = ref()
  345. const doSettlement = () => {
  346. if(cartList.value.length == 0){
  347. message.error(t('mall.pleaseAddTheProductFirst'))
  348. return
  349. }
  350. settlementRef.value.open()
  351. //payResultRef.value.open()
  352. //scanPayRef.value.open()
  353. }
  354. /** 初始化 **/
  355. onMounted(() => {
  356. getShopList()
  357. })
  358. </script>
  359. <style lang="scss" scoped >
  360. .cart-container {
  361. width: 310px;
  362. min-height:560px;
  363. background: #FFFFFF;
  364. display: block;
  365. position: relative;
  366. // overflow-x: hidden;
  367. // overflow-y: auto;
  368. left: 0px;
  369. top: 0px;
  370. border: #cccccc solid 1px;
  371. .title {
  372. height: 40px;
  373. width: 310px;
  374. background: #6c757d;
  375. border-bottom: #CCCCCC 2px solid;
  376. color: #FFFFFF;
  377. padding-top: 16px;
  378. padding-left: 1px;
  379. display: block;
  380. position: absolute;
  381. z-index: 999;
  382. clear: both;
  383. .logo {
  384. float: left;
  385. margin-left: 5px;
  386. }
  387. .member-info {
  388. float: left;
  389. .name {
  390. margin-left: 2px;
  391. margin-right: 3px;
  392. }
  393. .none {
  394. margin-left: 2px;
  395. margin-right: 5px;
  396. font-size: 13px;
  397. }
  398. .switch {
  399. padding: 8px 8px 8px 4px;
  400. }
  401. }
  402. }
  403. .carts {
  404. position:absolute;
  405. width:310px;
  406. height: 100%;
  407. overflow-x: hidden;
  408. overflow-y: auto;
  409. display: block;
  410. top:70px;
  411. bottom:0px;
  412. color: #666666;
  413. padding: 0px;
  414. .tab {
  415. // width: 50%;
  416. .cart-list {
  417. margin-bottom: 60px;
  418. .cart-item {
  419. border-bottom: dashed 1px #cccccc;
  420. height: 110px;
  421. // width: 310px;
  422. padding-top: 5px;
  423. .image {
  424. width: 50px;
  425. height: 50px;
  426. margin-left: 5px;
  427. border-radius: 5px;
  428. border: solid 1px #ccc;
  429. float: left;
  430. margin-top: 20px;
  431. }
  432. .info {
  433. float: left;
  434. padding-left: 5px;
  435. margin-top: 5px;
  436. .name {
  437. font-weight: bold;
  438. font-size: 12px;
  439. width: 160px;
  440. max-height: 30px;
  441. overflow: hidden;
  442. display: -webkit-box;
  443. -webkit-box-orient: vertical;
  444. -webkit-line-clamp: 2;
  445. }
  446. .spec {
  447. font-size: 12px;
  448. width: 160px;
  449. height: 20px;
  450. margin-top: 2px;
  451. .item {
  452. margin-right: 2px;
  453. border-radius: 5px;
  454. text-align: center;
  455. height: 20px;
  456. line-height: 20px;
  457. float: left;
  458. display: block;
  459. color: #606266;
  460. cursor: pointer;
  461. background: #cceeee;
  462. padding: 0px 3px 0px 3px;
  463. white-space: nowrap;
  464. text-overflow: ellipsis;
  465. }
  466. }
  467. .input {
  468. width: 120px;
  469. margin-top: 2px;
  470. }
  471. }
  472. .option {
  473. margin-top: 5px;
  474. float: right;
  475. text-align: right;
  476. margin-right: 10px;
  477. .remove {
  478. font-size: 12px;
  479. cursor: pointer;
  480. }
  481. .total {
  482. margin-top: 25px;
  483. font-size: 16px;
  484. color: #ff5b57;
  485. }
  486. }
  487. }
  488. }
  489. .empty {
  490. margin-top: 200px;
  491. width: 310px;
  492. }
  493. }
  494. }
  495. .footer {
  496. position: absolute;
  497. z-index: 999;
  498. background: #6c757d;
  499. bottom: -120px;
  500. height: 120px;
  501. left:-2px;
  502. padding-top: 5px;
  503. padding-right: 10px;
  504. color: #FFFFFF;
  505. display: block;
  506. border: solid 3px #cccccc;
  507. .number {
  508. float: right;
  509. margin: 5px;
  510. font-size: 13px;
  511. .total-price {
  512. margin-top: 3px;
  513. .num {
  514. color: #ff5b57;
  515. font-size: 20px;
  516. }
  517. }
  518. }
  519. .options {
  520. text-align: center;
  521. cursor: pointer;
  522. float: left;
  523. color: #FFFFFF;
  524. margin:0 auto;
  525. }
  526. }
  527. }
  528. .main-list {
  529. min-height: 700px;
  530. width: 100%;
  531. min-width: 760px;
  532. margin-left: 20px;
  533. margin-right: 2px;
  534. overflow: auto;
  535. display: block;
  536. background: #FFFFFF;
  537. margin-bottom: 10px;
  538. .title {
  539. height: 106px;
  540. min-width: 700px;
  541. background: #ffffff;
  542. padding: 5px;
  543. color: red;
  544. top: 0px;
  545. .search-form {
  546. height: 50px;
  547. .form-item {
  548. margin-right: -2px;
  549. }
  550. .input-item {
  551. min-width: 456px;
  552. height: 50px;
  553. }
  554. .search-goods {
  555. height: 50px;
  556. margin-left: 3px;
  557. }
  558. }
  559. .tab-box {
  560. margin-top: 8px;
  561. width: 100%;
  562. }
  563. }
  564. .goods-list {
  565. height: 100%;
  566. width: 100%;
  567. margin-left: 2px;
  568. margin-bottom: 20px;
  569. .goods-item {
  570. width: 23%;
  571. min-height: 300px;
  572. padding: 3px;
  573. float: left;
  574. background: #ffffff;
  575. text-align: left;
  576. cursor: pointer;
  577. .item {
  578. background: #ffffff;
  579. padding: 5px;
  580. border-radius: 5px;
  581. border: solid 1px #cccccc;
  582. margin: 0px;
  583. .goods-name {
  584. margin-top: 10px;
  585. font-size: 18px;
  586. color: #666666;
  587. height: 30px;
  588. overflow: hidden;
  589. white-space: nowrap;
  590. text-overflow: ellipsis;
  591. }
  592. .goods-price {
  593. color: #ff5b57;
  594. font-size: 18px;
  595. font-weight: bold;
  596. }
  597. .image {
  598. width: 100%;
  599. height: 220px;
  600. border-radius: 3px;
  601. }
  602. }
  603. }
  604. }
  605. }
  606. @media (min-width: 768px) {
  607. .container {
  608. max-width: 750px;
  609. }
  610. }
  611. .goods-info {
  612. border: solid 1px #ccc;
  613. padding: 30px;
  614. border-radius: 5px;
  615. .name {
  616. height: 40px;
  617. font-weight: bold;
  618. font-size: 20px;
  619. }
  620. .price {
  621. height: 40px;
  622. color: #ff5b57;
  623. font-size: 16px;
  624. }
  625. .spec-list {
  626. border: solid 1px #ccc;
  627. padding: 20px;
  628. margin-top: 10px;
  629. border-radius: 6px;
  630. .spec-item {
  631. margin-bottom: 20px;
  632. .spec-name {
  633. font-weight: bold;
  634. font-size: 16px;
  635. }
  636. .values {
  637. display: block;
  638. padding-top: 10px;
  639. margin-left: 0px;
  640. padding-left: 0px;
  641. font-size: 12px;
  642. .value {
  643. border: solid 1px #cceeee;
  644. margin-right: 10px;
  645. padding: 8px 15px 5px 15px;
  646. cursor: pointer;
  647. border-radius: 4px;
  648. background: rgba(0, 172, 172, 0.1);
  649. color: #666666;
  650. }
  651. .active {
  652. border: solid 1px #ff5891;
  653. background: #ff5b57;
  654. color: #FFFFFF;
  655. }
  656. }
  657. }
  658. }
  659. }
  660. </style>