help.vue 21 KB

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