index.vue 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720
  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. 收银员:
  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="暂无结算商品" :image-size="40" />
  65. </div>
  66. </div>
  67. </div>
  68. </div>
  69. <div class="footer">
  70. <div class="number">
  71. <div class="total-num">总件数:<b class="num">{{getCartGoodsNumber}}</b></div>
  72. <div class="total-price">总金额:<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()">取单</el-button>
  77. <el-button type="warning" round @click="hangUp()">挂单</el-button>
  78. <el-button type="danger" round @click="doSettlement()">提交结算</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="请输入商品关键字:商品名称" 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" />查询商品</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="全部" :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="选择商品规格" 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()">加入结算</el-button>
  127. <el-button @click="closeGoodsDialog()">取 消</el-button>
  128. </div>
  129. </template>
  130. </el-dialog>
  131. <el-empty v-if="productList.length == 0" description="暂无商品..." />
  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 userStore = useUserStore()
  165. const message = useMessage() // 消息弹窗
  166. const shopId = ref(0)
  167. const dialogVisible = ref(false)
  168. const loading = ref(true) // 列表的加载中
  169. const total = ref(1) // 列表的总页数
  170. const list = ref([]) // 列表的数据
  171. const queryParams = reactive({
  172. name: undefined,
  173. shopId: undefined
  174. })
  175. const queryParams2 = reactive({
  176. pageNo: 1,
  177. pageSize: 8,
  178. storeName: null,
  179. cateId: null,
  180. isShow: 1,
  181. shopId: undefined
  182. })
  183. const queryFormRef = ref() // 搜索的表单
  184. const activeName = ref(0)
  185. const shopList = ref([])
  186. const cateList = ref([])
  187. const productList = ref([])
  188. const memberInfo = ref({})
  189. const productInfo = ref({})
  190. const goodsNum = ref(1)
  191. const newValue = ref([])
  192. const thisProductSku = ref({})
  193. const cartList = ref([])
  194. const getCartGoodsNumber = computed(() => { //计算购物车总数
  195. return cartList.value.reduce((acc, cur) => acc + cur.cartNum, 0)
  196. })
  197. const getCartGoodsPrice = computed(() =>{ //计算购物车总价
  198. let price = cartList.value.reduce((acc, cur) => acc + cur.cartNum * cur.price, 0);
  199. return parseFloat(price).toFixed(2);
  200. })
  201. const handleClick = (tab: TabsPaneContext, event: Event) => {
  202. console.log(tab.paneName, event)
  203. }
  204. const getShopList = async () => {
  205. try {
  206. const data = await ShopApi.getShopList()
  207. shopList.value = data
  208. if(userStore.getUser.shopId > 0){
  209. shopId.value = userStore.getUser.shopId
  210. }else{
  211. shopId.value = shopList.value[0].id
  212. }
  213. if(shopId.value == 0){
  214. message.error('请添加门店或者选择门店')
  215. return
  216. }
  217. queryParams.shopId = shopId.value
  218. queryParams2.shopId = shopId.value
  219. console.log('shopId:',shopId.value)
  220. getCateList()
  221. getProductList()
  222. getCartList(shopId.value)
  223. } finally {
  224. }
  225. }
  226. const getCateList = async () => {
  227. try {
  228. const data = await ProductCategoryApi.getCategoryList(queryParams)
  229. cateList.value = data
  230. } finally {
  231. }
  232. }
  233. const getProductList = async () => {
  234. try {
  235. const data = await StoreProductApi.getStoreProductPage(queryParams2)
  236. productList.value = data.list
  237. total.value = data.total
  238. } finally {
  239. }
  240. }
  241. const switchTab = (tab: TabsPaneContext, event: Event) => {
  242. console.log(tab.paneName)
  243. if(tab.paneName > 0){
  244. queryParams2.cateId = tab.paneName
  245. getProductList()
  246. }else{
  247. queryParams2.cateId = null
  248. getProductList()
  249. }
  250. }
  251. const doQueryGoods = () => {
  252. getProductList()
  253. }
  254. const clickGoods = async(id) => {
  255. dialogVisible.value = true
  256. const data = await CashierApi.getProductInfo({id:id})
  257. productInfo.value = data
  258. newValue.value = []
  259. }
  260. const changePropertyDefault = (index, key, isDefault) => { //改变默认属性值
  261. let valueStr = ''
  262. if(isDefault){
  263. newValue.value = []
  264. for(let i = 0;i < productInfo.value.productAttr.length;i++){
  265. newValue.value[i] = productInfo.value.productAttr[i].attrValueArr[0]
  266. }
  267. }else{
  268. newValue.value[index] = productInfo.value.productAttr[index].attrValueArr[key]
  269. //valueStr = newValue.value.join(',')
  270. }
  271. valueStr = newValue.value.join(',')
  272. console.log('newValue:',newValue.value)
  273. let productValue = productInfo.value.productValue[valueStr]
  274. console.log('productValue:',productValue)
  275. if(!productValue) {
  276. let skukey = JSON.parse(JSON.stringify(newValue.value))
  277. skukey.sort((a, b) => a.localeCompare(b))
  278. //console.log('skukey:',skukey)
  279. valueStr = skukey.join(',')
  280. productValue = productInfo.value.productValue[valueStr]
  281. }
  282. console.log('valueStr:',valueStr)
  283. console.log('productValue:',productValue)
  284. if(productValue){
  285. thisProductSku.value = productValue
  286. productInfo.value.price = parseFloat(productValue.price).toFixed(2);
  287. }
  288. }
  289. const closeGoodsDialog = () => {
  290. dialogVisible.value = false
  291. }
  292. const addToCart = async() => {
  293. if(Object.keys(thisProductSku.value).length == 0) {
  294. message.error('请选择规格!')
  295. return
  296. }
  297. console.log('user:',thisProductSku.value)
  298. //let uid = memberInfo.value.id ? memberInfo.value.id : 0
  299. if(shopId.value == 0){
  300. message.error('请选择门店!')
  301. return
  302. }
  303. let data = {uid:0,shopId:shopId.value,productId:thisProductSku.value.productId,
  304. productAttrUnique:thisProductSku.value.unique,
  305. cartNum:goodsNum.value}
  306. const res = await CashierApi.addCart(data)
  307. getCartList(shopId.value)
  308. closeGoodsDialog()
  309. }
  310. const getCartList = async(id) => {
  311. const res = await CashierApi.getCarts({shopId:id})
  312. cartList.value = res
  313. }
  314. const changeBuyNum = async(cart) => {
  315. console.log('cart:',cart)
  316. if(cart.cartNum < 1){
  317. message.error('数量不能小于1哦')
  318. return
  319. }
  320. await CashierApi.updateCartNum(cart)
  321. }
  322. const removeFromCart = async(id) => {
  323. await CashierApi.delCart({id:id})
  324. getCartList(shopId.value)
  325. }
  326. const hangUp = async() => {
  327. let ids = cartList.value.map(item => item.id);
  328. console.log('ids:',ids)
  329. if(ids.length == 0){
  330. message.error('暂无商品挂单')
  331. return
  332. }
  333. //console.log('ids:',ids.toString())
  334. await CashierApi.hangUp({ids:ids.toString()})
  335. getCartList(shopId.value)
  336. }
  337. const hangListRef = ref()
  338. const hangOff = () => {
  339. hangListRef.value.open(shopId.value)
  340. }
  341. const settlementRef = ref()
  342. // const payResultRef = ref()
  343. // const scanPayRef = ref()
  344. const doSettlement = () => {
  345. if(cartList.value.length == 0){
  346. message.error('请先加入商品!')
  347. return
  348. }
  349. settlementRef.value.open()
  350. //payResultRef.value.open()
  351. //scanPayRef.value.open()
  352. }
  353. /** 初始化 **/
  354. onMounted(() => {
  355. getShopList()
  356. })
  357. </script>
  358. <style lang="scss" scoped >
  359. .cart-container {
  360. width: 310px;
  361. min-height:560px;
  362. background: #FFFFFF;
  363. display: block;
  364. position: relative;
  365. // overflow-x: hidden;
  366. // overflow-y: auto;
  367. left: 0px;
  368. top: 0px;
  369. border: #cccccc solid 1px;
  370. .title {
  371. height: 40px;
  372. width: 310px;
  373. background: #6c757d;
  374. border-bottom: #CCCCCC 2px solid;
  375. color: #FFFFFF;
  376. padding-top: 16px;
  377. padding-left: 1px;
  378. display: block;
  379. position: absolute;
  380. z-index: 999;
  381. clear: both;
  382. .logo {
  383. float: left;
  384. margin-left: 5px;
  385. }
  386. .member-info {
  387. float: left;
  388. .name {
  389. margin-left: 2px;
  390. margin-right: 3px;
  391. }
  392. .none {
  393. margin-left: 2px;
  394. margin-right: 5px;
  395. font-size: 13px;
  396. }
  397. .switch {
  398. padding: 8px 8px 8px 4px;
  399. }
  400. }
  401. }
  402. .carts {
  403. position:absolute;
  404. width:310px;
  405. height: 100%;
  406. overflow-x: hidden;
  407. overflow-y: auto;
  408. display: block;
  409. top:70px;
  410. bottom:0px;
  411. color: #666666;
  412. padding: 0px;
  413. .tab {
  414. // width: 50%;
  415. .cart-list {
  416. margin-bottom: 60px;
  417. .cart-item {
  418. border-bottom: dashed 1px #cccccc;
  419. height: 110px;
  420. // width: 310px;
  421. padding-top: 5px;
  422. .image {
  423. width: 50px;
  424. height: 50px;
  425. margin-left: 5px;
  426. border-radius: 5px;
  427. border: solid 1px #ccc;
  428. float: left;
  429. margin-top: 20px;
  430. }
  431. .info {
  432. float: left;
  433. padding-left: 5px;
  434. margin-top: 5px;
  435. .name {
  436. font-weight: bold;
  437. font-size: 12px;
  438. width: 160px;
  439. max-height: 30px;
  440. overflow: hidden;
  441. display: -webkit-box;
  442. -webkit-box-orient: vertical;
  443. -webkit-line-clamp: 2;
  444. }
  445. .spec {
  446. font-size: 12px;
  447. width: 160px;
  448. height: 20px;
  449. margin-top: 2px;
  450. .item {
  451. margin-right: 2px;
  452. border-radius: 5px;
  453. text-align: center;
  454. height: 20px;
  455. line-height: 20px;
  456. float: left;
  457. display: block;
  458. color: #606266;
  459. cursor: pointer;
  460. background: #cceeee;
  461. padding: 0px 3px 0px 3px;
  462. white-space: nowrap;
  463. text-overflow: ellipsis;
  464. }
  465. }
  466. .input {
  467. width: 120px;
  468. margin-top: 2px;
  469. }
  470. }
  471. .option {
  472. margin-top: 5px;
  473. float: right;
  474. text-align: right;
  475. margin-right: 10px;
  476. .remove {
  477. font-size: 12px;
  478. cursor: pointer;
  479. }
  480. .total {
  481. margin-top: 25px;
  482. font-size: 16px;
  483. color: #ff5b57;
  484. }
  485. }
  486. }
  487. }
  488. .empty {
  489. margin-top: 200px;
  490. width: 310px;
  491. }
  492. }
  493. }
  494. .footer {
  495. position: absolute;
  496. z-index: 999;
  497. background: #6c757d;
  498. bottom: -120px;
  499. height: 120px;
  500. left:-2px;
  501. padding-top: 5px;
  502. padding-right: 10px;
  503. color: #FFFFFF;
  504. display: block;
  505. border: solid 3px #cccccc;
  506. .number {
  507. float: right;
  508. margin: 5px;
  509. font-size: 13px;
  510. .total-price {
  511. margin-top: 3px;
  512. .num {
  513. color: #ff5b57;
  514. font-size: 20px;
  515. }
  516. }
  517. }
  518. .options {
  519. text-align: center;
  520. cursor: pointer;
  521. float: left;
  522. color: #FFFFFF;
  523. margin:0 auto;
  524. }
  525. }
  526. }
  527. .main-list {
  528. min-height: 700px;
  529. width: 100%;
  530. min-width: 760px;
  531. margin-left: 20px;
  532. margin-right: 2px;
  533. overflow: auto;
  534. display: block;
  535. background: #FFFFFF;
  536. margin-bottom: 10px;
  537. .title {
  538. height: 106px;
  539. min-width: 700px;
  540. background: #ffffff;
  541. padding: 5px;
  542. color: red;
  543. top: 0px;
  544. .search-form {
  545. height: 50px;
  546. .form-item {
  547. margin-right: -2px;
  548. }
  549. .input-item {
  550. min-width: 456px;
  551. height: 50px;
  552. }
  553. .search-goods {
  554. height: 50px;
  555. margin-left: 3px;
  556. }
  557. }
  558. .tab-box {
  559. margin-top: 8px;
  560. width: 100%;
  561. }
  562. }
  563. .goods-list {
  564. height: 100%;
  565. width: 100%;
  566. margin-left: 2px;
  567. margin-bottom: 20px;
  568. .goods-item {
  569. width: 23%;
  570. min-height: 300px;
  571. padding: 3px;
  572. float: left;
  573. background: #ffffff;
  574. text-align: left;
  575. cursor: pointer;
  576. .item {
  577. background: #ffffff;
  578. padding: 5px;
  579. border-radius: 5px;
  580. border: solid 1px #cccccc;
  581. margin: 0px;
  582. .goods-name {
  583. margin-top: 10px;
  584. font-size: 18px;
  585. color: #666666;
  586. height: 30px;
  587. overflow: hidden;
  588. white-space: nowrap;
  589. text-overflow: ellipsis;
  590. }
  591. .goods-price {
  592. color: #ff5b57;
  593. font-size: 18px;
  594. font-weight: bold;
  595. }
  596. .image {
  597. width: 100%;
  598. height: 220px;
  599. border-radius: 3px;
  600. }
  601. }
  602. }
  603. }
  604. }
  605. @media (min-width: 768px) {
  606. .container {
  607. max-width: 750px;
  608. }
  609. }
  610. .goods-info {
  611. border: solid 1px #ccc;
  612. padding: 30px;
  613. border-radius: 5px;
  614. .name {
  615. height: 40px;
  616. font-weight: bold;
  617. font-size: 20px;
  618. }
  619. .price {
  620. height: 40px;
  621. color: #ff5b57;
  622. font-size: 16px;
  623. }
  624. .spec-list {
  625. border: solid 1px #ccc;
  626. padding: 20px;
  627. margin-top: 10px;
  628. border-radius: 6px;
  629. .spec-item {
  630. margin-bottom: 20px;
  631. .spec-name {
  632. font-weight: bold;
  633. font-size: 16px;
  634. }
  635. .values {
  636. display: block;
  637. padding-top: 10px;
  638. margin-left: 0px;
  639. padding-left: 0px;
  640. font-size: 12px;
  641. .value {
  642. border: solid 1px #cceeee;
  643. margin-right: 10px;
  644. padding: 8px 15px 5px 15px;
  645. cursor: pointer;
  646. border-radius: 4px;
  647. background: rgba(0, 172, 172, 0.1);
  648. color: #666666;
  649. }
  650. .active {
  651. border: solid 1px #ff5891;
  652. background: #ff5b57;
  653. color: #FFFFFF;
  654. }
  655. }
  656. }
  657. }
  658. }
  659. </style>