console.vue 45 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323
  1. <template>
  2. <div class="order-console">
  3. <el-drawer
  4. v-model="showOrderConsole"
  5. title="开始点餐"
  6. direction="rtl"
  7. size="100%"
  8. :before-close="beforeClose"
  9. >
  10. <div class="p-2 md:w-2/6 w-full md:h-full h-auto float-left">
  11. <el-card shadow="never" :body-style="{ padding: '10px' }">
  12. <template #header>
  13. <div
  14. class="justify-between items-center flex text-xl font-semibold font-mono"
  15. >
  16. <span>{{ (data.deskId > 0 ? '餐桌号:': '取餐号:') + data.deskName }}</span>
  17. <div class="flex items-center">
  18. <span v-if="data.deskId > 0">{{ data.currentNum + '/' + data.deskCap }}</span>
  19. <el-icon class="ml-2 cursor-pointer" @click="refreshdishes">
  20. <Refresh />
  21. </el-icon>
  22. </div>
  23. </div>
  24. </template>
  25. <el-input
  26. v-model="params.remark"
  27. maxlength="100"
  28. placeholder="订单备注"
  29. show-word-limit
  30. type="textarea"
  31. />
  32. <el-divider />
  33. <el-scrollbar ref="scrollbarRef" class="scrollbar">
  34. <div ref="innerRef" v-loading="orderLoading">
  35. <el-empty
  36. v-if="data.selectGoods.length == 0 ? true : false"
  37. description="啥也没有,快点击商品添加吧"
  38. />
  39. <!-- 已选商品 -->
  40. <goods-item
  41. v-for="item in data.selectGoods"
  42. :key="item"
  43. class="scrollbar-demo-item"
  44. :goods-item="item"
  45. @add="add"
  46. @reduce="reduce"
  47. >
  48. </goods-item>
  49. </div>
  50. </el-scrollbar>
  51. <div class="flex justify-between items-center mt-2">
  52. <div
  53. class="flex-1 h-12 border rounded-md flex items-center justify-center cursor-pointer relative mr-2"
  54. :class="{
  55. 'border-primary text-primary bg-[#EDEFFF]':
  56. params.diningMethods === 1
  57. }"
  58. @click="params.diningMethods = 1"
  59. >
  60. <span>店内就餐</span>
  61. <el-icon
  62. v-if="params.diningMethods === 1"
  63. class="absolute right-0 bottom-0 text-primary"
  64. style="font-size: 16px"
  65. >
  66. <Check />
  67. </el-icon>
  68. </div>
  69. <div
  70. class="flex-1 h-12 border rounded-md flex items-center justify-center cursor-pointer relative ml-2"
  71. :class="{
  72. 'border-primary text-primary bg-[#EDEFFF]':
  73. params.diningMethods === 2
  74. }"
  75. @click="params.diningMethods = 2"
  76. >
  77. <span>打包外带</span>
  78. <el-icon
  79. v-if="params.diningMethods === 2"
  80. class="absolute right-0 bottom-0 text-primary"
  81. style="font-size: 16px"
  82. >
  83. <Check />
  84. </el-icon>
  85. </div>
  86. </div>
  87. </el-card>
  88. </div>
  89. <div class="p-2 md:w-1/6 w-full md:h-full h-auto float-left flex-wrap">
  90. <el-scrollbar>
  91. <div class="flex flex-col space-y-2 p-2">
  92. <el-button
  93. size="large"
  94. :type="buttonType == 'all' ? 'primary' : ''"
  95. @click="clickSortButton('all')"
  96. text
  97. bg
  98. >全部商品</el-button
  99. >
  100. <el-button
  101. class="cate-button"
  102. v-for="button in dishCate"
  103. :key="button.name"
  104. size="large"
  105. style="margin-left: 0"
  106. :type="buttonType == button.name ? 'primary' : ''"
  107. @click="clickSortButton(button.name)"
  108. text
  109. bg
  110. >{{ button.name }}</el-button
  111. >
  112. </div></el-scrollbar
  113. >
  114. </div>
  115. <div class="p-2 md:w-1/2 w-full md:h-full h-auto float-left flex-wrap">
  116. <div class="w-full h-auto">
  117. <div class="justify-end flex">
  118. <el-input
  119. v-model="search"
  120. class="md:w-40 w-1/2 m-2"
  121. placeholder="搜索菜品名字"
  122. :suffix-icon="Search"
  123. />
  124. </div>
  125. </div>
  126. <div class="w-full md:h-5/6 h-auto inline-block" style="height: calc(100% - 40px)">
  127. <!-- 菜单内容 -->
  128. <el-scrollbar height="100%">
  129. <div
  130. v-for="item in dishList"
  131. :key="item.id"
  132. class="float-left md:w-40 w-1/2 md:h-38 h-auto p-1"
  133. style="user-select: none"
  134. >
  135. <a
  136. href="javascript:void(0);"
  137. @click="addGoods(item)"
  138. :class="{ 'pointer-events-none': item.isShow == 0 }"
  139. ><el-card
  140. shadow="never"
  141. :body-style="{ padding: '0px' }"
  142. :class="{ relative: item.isShow == 0 }"
  143. >
  144. <div
  145. v-if="item.isShow == 0"
  146. class="absolute inset-0 bg-white z-10 pointer-events-none flex items-center justify-center select-none"
  147. style="opacity: 0.5; color: red; font-size: 25px"
  148. >
  149. <span class="text-gray-600 font-medium">已售罄</span>
  150. </div>
  151. <el-image
  152. style="width: 100%; height: 100px"
  153. :src="item.image"
  154. fit="cover"
  155. />
  156. <div class="text-2xs font-semibold p-1">
  157. <p>{{ item.title }}</p>
  158. <p style="color: coral">
  159. {{ item.summary + '元/份' }}
  160. </p>
  161. </div>
  162. </el-card></a
  163. >
  164. </div>
  165. </el-scrollbar>
  166. </div>
  167. </div>
  168. <template #footer>
  169. <div class="flex items-center px-4 space-x-5">
  170. <div class="relative mr-auto">
  171. <el-badge :value="orderData.sumNumSum" :max="99" class="mr-3">
  172. <el-icon size="50" style="width: 32px; height: 32px; font-size: 32px"
  173. ><ShoppingCart
  174. /></el-icon>
  175. </el-badge>
  176. <span class="ml-5 text-base"
  177. >合计: ¥{{ orderData.sumPriceSum.toFixed(2) }}</span
  178. >
  179. </div>
  180. <el-button
  181. type="primary"
  182. class="action-btn !w-[50px] !h-[40px]"
  183. @click="toEmpty()"
  184. :disabled="data.selectGoods.length <= 0"
  185. >清空</el-button
  186. >
  187. <el-badge :value="params.couponId ? 1 : ''" :hidden="!params.couponId">
  188. <el-button
  189. type="primary"
  190. class="action-btn !w-[200px] !h-[40px]"
  191. @click="showCouponDialog = true"
  192. >
  193. 优惠 {{ params.couponAmount !== '-' ? `¥${params.couponAmount}` : '' }}
  194. </el-button>
  195. </el-badge>
  196. <el-button
  197. type="danger"
  198. class="submit-btn !w-[300px] !h-[40px]"
  199. @click="submit()"
  200. >去结算 ¥{{ orderData.payAmount }}</el-button
  201. >
  202. </div>
  203. </template>
  204. </el-drawer>
  205. </div>
  206. <el-dialog
  207. v-model="specDialogVisible"
  208. title="选择规格"
  209. width="50%"
  210. :before-close="handleSpecDialogClose"
  211. >
  212. <div v-if="currentItem">
  213. <div
  214. v-for="(specs, name) in groupSpecsByName(currentItem.specsList)"
  215. :key="name"
  216. class="mb-4"
  217. >
  218. <div class="font-bold mb-2">{{ name }}</div>
  219. <div class="flex flex-wrap gap-2">
  220. <div
  221. v-for="spec in specs"
  222. :key="spec.id"
  223. class="flex-1 h-12 border rounded-md flex items-center justify-center cursor-pointer relative"
  224. :class="{
  225. 'border-primary text-primary bg-[#EDEFFF]':
  226. selectedSpecs[name] === spec.id
  227. }"
  228. @click="selectedSpecs[name] = spec.id"
  229. >
  230. <span>{{ spec.value }}</span>
  231. <el-icon
  232. v-if="selectedSpecs[name] === spec.id"
  233. class="absolute right-0 bottom-0 text-primary"
  234. style="font-size: 16px"
  235. >
  236. <Check />
  237. </el-icon>
  238. </div>
  239. </div>
  240. </div>
  241. </div>
  242. <template #footer>
  243. <span class="dialog-footer">
  244. <el-button @click="specDialogVisible = false">取消</el-button>
  245. <el-button type="primary" @click="handleSpecConfirm">确认</el-button>
  246. </span>
  247. </template>
  248. </el-dialog>
  249. <!-- 优惠券弹框 -->
  250. <el-dialog
  251. v-model="showCouponDialog"
  252. title="选择优惠券"
  253. width="50%"
  254. :close-on-click-modal="false"
  255. >
  256. <div class="mb-4" style="margin-top: 5px">
  257. <div class="flex items-center">
  258. <el-input
  259. v-model="data.phone"
  260. placeholder="请输入手机号查询优惠券"
  261. class="flex-1"
  262. maxlength="11"
  263. >
  264. <template #append>
  265. <el-button
  266. :icon="Search"
  267. @click="searchUserCoupons"
  268. :loading="searchLoading"
  269. />
  270. </template>
  271. </el-input>
  272. </div>
  273. </div>
  274. <div class="mt-2 coupon-container" v-loading="searchLoading">
  275. <el-scrollbar height="200px">
  276. <el-empty
  277. v-if="!data.couponList.length"
  278. description="暂无可用优惠券"
  279. :image-size="60"
  280. />
  281. <div v-else class="coupon-list" style="padding: 7px">
  282. <div
  283. v-for="coupon in data.couponList"
  284. :key="coupon.id"
  285. class="coupon-item"
  286. :class="{
  287. 'is-selected': params.couponId === coupon.ticketRecordId,
  288. 'is-disabled': orderData.sumPriceSum < (coupon.consumeScore / 100) || orderData.sumPriceSum < (coupon.useThreshold / 100)
  289. }"
  290. @click="handleCouponClick(coupon)"
  291. >
  292. <div class="left-part">
  293. <div class="amount-wrapper">
  294. <div class="amount">{{ coupon.consumeScore / 100 }}</div>
  295. </div>
  296. <!-- <div class="condition">满{{ coupon.consumeScore / 100 }}可用</div> -->
  297. </div>
  298. <div class="right-part">
  299. <div class="name">
  300. {{ coupon.productName }}
  301. <el-tag v-if="coupon.useThreshold">满{{coupon.useThreshold / 100}}减</el-tag>
  302. </div>
  303. <div class="date">券码:{{ coupon.ticketNo }}</div>
  304. </div>
  305. </div>
  306. </div>
  307. </el-scrollbar>
  308. </div>
  309. <template #footer>
  310. <span class="dialog-footer">
  311. <el-button type="primary" @click="ticketConfirm">确认</el-button>
  312. </span>
  313. </template>
  314. </el-dialog>
  315. <el-dialog
  316. v-model="scanDialogVisible"
  317. title="请引导顾客扫码支付"
  318. width="300px"
  319. center
  320. :show-close="false"
  321. :close-on-click-modal="false"
  322. :close-on-press-escape="false"
  323. >
  324. <div class="text-center">
  325. <img
  326. src="@/assets/images/scan.gif"
  327. alt="扫码"
  328. class="mx-auto mb-4"
  329. style="width: 150px"
  330. />
  331. <b style="font-size: 20px">金额 {{ orderData.payAmount }} 元</b>
  332. </div>
  333. <template #footer>
  334. <div class="dialog-footer">
  335. <el-button @click="scanDialogVisible = false">取消支付</el-button>
  336. </div>
  337. </template>
  338. </el-dialog>
  339. <el-dialog
  340. v-model="payingDialogVisible"
  341. title="支付处理中"
  342. width="300px"
  343. :show-close="false"
  344. :close-on-click-modal="false"
  345. :close-on-press-escape="false"
  346. >
  347. <div class="flex flex-col items-center justify-center py-4">
  348. <el-icon class="is-loading mb-4" :size="32">
  349. <Loading />
  350. </el-icon>
  351. <p class="text-gray-600">正在支付中,请不要关闭或退出界面</p>
  352. </div>
  353. </el-dialog>
  354. </template>
  355. <script setup lang="ts">
  356. import { ElScrollbar } from 'element-plus'
  357. import { Search, ShoppingCart } from '@element-plus/icons-vue'
  358. import goodsItem from './goodsItem.vue'
  359. import {
  360. dishAdd,
  361. dishCateAll,
  362. dishDec,
  363. dishDel,
  364. dishInc,
  365. dishListAll,
  366. toEmptyy,
  367. orderSubmit,
  368. deskOrderedDishListAll,
  369. searchCoupons,
  370. orderPay,
  371. queryPayStatus,
  372. cancelOrder
  373. } from '@/api/order'
  374. import feedback from '@/utils/feedback'
  375. // const route = useRoute()
  376. const innerRef = ref<HTMLDivElement>()
  377. const dishCate = ref<any[]>([])
  378. const dishList = ref<any[]>([])
  379. const showCouponDialog = ref(false)
  380. let dishListall: any[] = []
  381. const orderLoading = ref(false)
  382. const scrollbarRef = ref<InstanceType<typeof ElScrollbar>>()
  383. const search = ref('')
  384. const emit = defineEmits(['init'])
  385. const buttonType = ref('all')
  386. const showOrderConsole = ref(false)
  387. const scanDialogVisible = ref(false)
  388. const scanCode = ref('')
  389. // 添加支付中对话框的控制变量
  390. const payingDialogVisible = ref(false)
  391. const data = reactive<any>({
  392. deskId: null,
  393. deskName: '',
  394. deskCap: 0,
  395. currentNum: 0,
  396. selectGoods: [],
  397. oldGoods: [],
  398. phone: '', // 手机号
  399. couponList: [], // 优惠券列表
  400. canScan: false
  401. })
  402. const orderData = reactive<any>({
  403. priceSum: 0,
  404. sumPriceSum: 0,
  405. numSum: 0,
  406. sumNumSum: 0,
  407. payAmount: 0 // 新增支付金额
  408. })
  409. const params = reactive({
  410. remark: '', //订单备注
  411. number: '', //订单id
  412. userId: '', //用户id
  413. couponId: '', //优惠券id
  414. couponAmount: '-',
  415. diningMethods: 1, // 就餐方式:1店内就餐 2打包外带
  416. })
  417. const submit = () => {
  418. //出单
  419. if (data.selectGoods.length == 0) {
  420. feedback.alert('未点菜品!')
  421. return false
  422. }
  423. if (orderData.payAmount <= 0) {
  424. feedback.alert('支付金额需大于0!')
  425. return false
  426. }
  427. feedback.loading('正在出单...')
  428. orderSubmit({
  429. ...params,
  430. userId: params.userId || null,
  431. couponId: params.couponId || null,
  432. mealCode: data.deskName || null
  433. })
  434. .then(() => {
  435. // data.selectGoods.length = 0
  436. // orderData.priceSum = 0
  437. // orderData.numSum = 0
  438. //把所有餐品的状态修改为出单
  439. data.selectGoods.forEach((good: { status: number }) => {
  440. good.status = 1
  441. })
  442. // emit('init')
  443. feedback.closeLoading()
  444. // feedback.notifySuccess('出单成功')
  445. checkout()
  446. }).catch((e)=>{
  447. if (e.msg === "券不存在" || e.msg === "券不存在或已过期") {
  448. params.couponId = null
  449. params.couponAmount = '-'
  450. // 计算支付金额
  451. orderData.payAmount = Number(orderData.sumPriceSum).toFixed(2)
  452. searchUserCoupons()
  453. }
  454. })
  455. .finally(() => {
  456. feedback.closeLoading()
  457. });
  458. }
  459. // 添加扫码事件监听函数
  460. const handleScanInput = (event: KeyboardEvent) => {
  461. console.log("event.key", event.key)
  462. if (event.key === 'Enter') {
  463. document.removeEventListener('keydown', handleScanInput)
  464. if (!data.canScan) {
  465. feedback.notifyWarning('正在处理支付,请勿重复扫码')
  466. return
  467. }
  468. const code = scanCode.value
  469. console.log('扫码内容:', code)
  470. payingDialogVisible.value = true // 显示支付中对话框
  471. // 修改支付处理逻辑
  472. orderPay({ oid: params.number, code: code })
  473. .then((res) => {
  474. console.log(res)
  475. // 实际上支付成功之后,res返回的是[],如果不是实时成功,返回的是个对象, 形如:{orderId: '123456789', sn: '7895004131583689'}
  476. if (res == null || res.length == 0) {
  477. feedback.notifySuccess('成功结账' + orderData.payAmount + '元')
  478. scanDialogVisible.value = false
  479. payingDialogVisible.value = false // 隐藏支付中对话框
  480. showOrderConsole.value = false
  481. emit('init')
  482. } else {
  483. console.log('支付中')
  484. return queryPayStatusWithRetry(res)
  485. .then((paySuccess) => {
  486. if (paySuccess) {
  487. feedback.notifySuccess('成功结账' + orderData.payAmount + '元')
  488. scanDialogVisible.value = false
  489. payingDialogVisible.value = false // 隐藏支付中对话框
  490. showOrderConsole.value = false
  491. emit('init')
  492. }
  493. })
  494. }
  495. })
  496. .catch((e) => {
  497. console.log("=====---", e)
  498. // 支付失败
  499. payingDialogVisible.value = false // 隐藏支付中对话框
  500. feedback.notifySuccess('支付失败,请重试!')
  501. data.canScan = true
  502. scanDialogVisible.value = true
  503. // 添加键盘事件监听
  504. setTimeout(()=>{
  505. scanCode.value = ''
  506. document.removeEventListener('keydown', handleScanInput)
  507. document.addEventListener('keydown', handleScanInput)
  508. }, 2000)
  509. })
  510. scanCode.value = ''
  511. } else {
  512. scanCode.value += event.key
  513. }
  514. }
  515. // 定义查询支付状态的函数
  516. const queryPayStatusWithRetry = (data): Promise<boolean> => {
  517. const intervals = [3000, 5000, 10000, 10000, 10000, 10000, 10000]
  518. let currentIndex = 0
  519. payingDialogVisible.value = true
  520. const tryQuery = () => {
  521. return new Promise<boolean>((resolve) => {
  522. setTimeout(() => {
  523. queryPayStatus({ orderId: data.orderId, sn: data.sn })
  524. .then((result) => {
  525. if (
  526. result.result_code == '200' &&
  527. result.biz_response.data.order_status == 'SUCCESS'
  528. ) {
  529. resolve(true)
  530. } else if (currentIndex < intervals.length - 1) {
  531. currentIndex++
  532. tryQuery().then(resolve)
  533. } else {
  534. resolve(false)
  535. }
  536. })
  537. .catch(() => {
  538. if (currentIndex < intervals.length - 1) {
  539. currentIndex++
  540. tryQuery().then(resolve)
  541. } else {
  542. resolve(false)
  543. }
  544. })
  545. }, intervals[currentIndex])
  546. })
  547. }
  548. return tryQuery().then((success) => {
  549. payingDialogVisible.value = false
  550. if (!success) {
  551. return cancelOrder({ orderId: data.orderId, sn: data.sn })
  552. .then(() => {
  553. feedback.notifyWarning('支付超时,已自动撤销本次支付')
  554. return false
  555. })
  556. .catch((error) => {
  557. console.error('撤单失败:', error)
  558. feedback.notifyError('支付失败,请重试')
  559. return false
  560. })
  561. }
  562. return success
  563. })
  564. }
  565. const ticketConfirm = () => {
  566. if (data.phone == '') {
  567. params.couponId = null
  568. params.couponAmount = '-'
  569. params.userId = null
  570. // 计算支付金额
  571. orderData.payAmount = Number(orderData.sumPriceSum).toFixed(2)
  572. }
  573. showCouponDialog.value = false
  574. }
  575. const checkout = () => {
  576. refreshdishes()
  577. //结账
  578. console.warn('***结账***')
  579. // if (data.selectGoods || data.selectGoods.length == 0) {
  580. // feedback.notify('请点击商品开始点单吧!')
  581. // return false
  582. // }
  583. // feedback.confirm('结账金额为' + orderData.payAmount + ',确定要结账吗?').then(() => {
  584. scanCode.value = ''
  585. data.canScan = true
  586. scanDialogVisible.value = true
  587. // 添加键盘事件监听
  588. document.removeEventListener('keydown', handleScanInput)
  589. document.addEventListener('keydown', handleScanInput)
  590. // })
  591. }
  592. // 添加优惠券点击处理方法
  593. const handleCouponClick = (coupon: any) => {
  594. if (orderData.sumPriceSum < coupon.consumeScore / 100 || orderData.sumPriceSum < (coupon.useThreshold / 100)) {
  595. return
  596. }
  597. if (params.couponId === coupon.ticketRecordId) {
  598. params.couponId = ''
  599. params.couponAmount = '-'
  600. orderData.payAmount = orderData.sumPriceSum
  601. return
  602. }
  603. params.couponId = coupon.ticketRecordId
  604. params.couponAmount = (coupon.consumeScore / 100).toFixed(2)
  605. // 计算支付金额
  606. orderData.payAmount = (
  607. Number(orderData.sumPriceSum) - Number(coupon.consumeScore / 100)
  608. ).toFixed(2)
  609. }
  610. const open = (item: any, num?: number, orderNumber?: any) => {
  611. console.log('***open***', item, num, orderNumber)
  612. data.couponList = []
  613. showOrderConsole.value = true
  614. data.deskId = item.id
  615. data.deskName = item.name
  616. data.deskCap = item.num
  617. data.currentNum = item.userNum ? item.userNum : num
  618. params.number = item.ordersId ? item.ordersId : orderNumber
  619. params.userId = ''
  620. params.couponId = ''
  621. //查询当前订单下的所有菜品
  622. deskOrderedDishListAll({ id: params.number }).then((res) => {
  623. res.forEach((good: { id: number; ordersDishId: number }) => {
  624. good.ordersDishId = good.id
  625. })
  626. console.log('***deskOrderedDishListAll***', res)
  627. data.selectGoods = res
  628. data.oldGoods = JSON.parse(JSON.stringify(res)) //记录下进入时当前餐桌已有的食物
  629. if (res.length > 0) {
  630. data.phone = res[0].mobile
  631. params.remark = res[0].remark
  632. params.userId = res[0].userId || null
  633. params.couponId = res[0].ticketId || null
  634. params.diningMethods = res[0].diningMethods || 1
  635. if (params.couponId && params.userId) {
  636. searchUserCoupons()
  637. }
  638. params.couponAmount = res[0].ticketAmount
  639. ? Number(res[0].ticketAmount / 100).toFixed(2)
  640. : '-'
  641. const _list = res.filter((it: { status: number }) => it.status != 1) //找到没有出单的餐品来计算价格
  642. orderData.priceSum = _list.reduce(
  643. (accumulator: number, currentValue: { summary: number; num: number }) =>
  644. accumulator + currentValue.summary * currentValue.num,
  645. 0
  646. )
  647. orderData.numSum = _list.reduce(
  648. (accumulator: number, currentValue: { summary: number; num: number }) =>
  649. accumulator + currentValue.num,
  650. 0
  651. )
  652. //所有餐品的总价以及总数量
  653. orderData.sumPriceSum = res.reduce(
  654. (accumulator: number, currentValue: { summary: number; num: number }) =>
  655. accumulator + currentValue.summary * currentValue.num,
  656. 0
  657. )
  658. orderData.sumNumSum = res.reduce(
  659. (accumulator: number, currentValue: { summary: number; num: number }) =>
  660. accumulator + currentValue.num,
  661. 0
  662. )
  663. orderData.payAmount = Number(
  664. orderData.sumPriceSum -
  665. (params.couponAmount === '-' ? 0 : Number(params.couponAmount))
  666. ).toFixed(2)
  667. } else {
  668. orderData.priceSum = 0
  669. orderData.numSum = 0
  670. orderData.sumPriceSum = 0
  671. orderData.sumNumSum = 0
  672. data.phone = ''
  673. params.remark = ''
  674. params.userId = ''
  675. params.couponId = ''
  676. params.couponAmount = '-'
  677. orderData.payAmount = 0
  678. data.couponList = []
  679. }
  680. })
  681. dishList.value = dishListall //显示所有菜品
  682. }
  683. const refreshdishes = async () => {
  684. orderLoading.value = true
  685. await deskOrderedDishListAll({ id: params.number })
  686. .then((res) => {
  687. res.forEach((good: { id: number; ordersDishId: number }) => {
  688. good.ordersDishId = good.id
  689. })
  690. console.log('***refreshdishes***', res)
  691. data.selectGoods = res
  692. console.log('***res.length***', res.length)
  693. if (res.length > 0) {
  694. // const _list = res.filter((it: { status: number }) => it.status != 1) //找到没有出单的餐品来计算价格
  695. const _list = res
  696. orderData.priceSum = _list.reduce(
  697. (accumulator: number, currentValue: { summary: number; num: number }) =>
  698. accumulator + currentValue.summary * currentValue.num,
  699. 0
  700. )
  701. orderData.numSum = _list.reduce(
  702. (accumulator: number, currentValue: { summary: number; num: number }) =>
  703. accumulator + currentValue.num,
  704. 0
  705. )
  706. //所有餐品的总价以及总数量
  707. orderData.sumPriceSum = res.reduce(
  708. (accumulator: number, currentValue: { summary: number; num: number }) =>
  709. accumulator + currentValue.summary * currentValue.num,
  710. 0
  711. )
  712. orderData.sumNumSum = res.reduce(
  713. (accumulator: number, currentValue: { summary: number; num: number }) =>
  714. accumulator + currentValue.num,
  715. 0
  716. )
  717. console.log(
  718. '***res[0].payAmount***',
  719. res[0].payAmount,
  720. Number(res[0].payAmount / 100).toFixed(2)
  721. )
  722. orderData.payAmount = res[0].payAmount
  723. ? Number(res[0].payAmount / 100).toFixed(2)
  724. : Number(orderData.sumPriceSum).toFixed(2)
  725. console.log('***orderData***', orderData)
  726. } else {
  727. orderData.priceSum = 0
  728. orderData.numSum = 0
  729. orderData.sumPriceSum = 0
  730. orderData.sumNumSum = 0
  731. orderData.payAmount = 0
  732. }
  733. })
  734. .finally(() => {
  735. orderLoading.value = false
  736. })
  737. }
  738. watch(search, (value) => {
  739. if (!value) {
  740. if (buttonType.value == 'all') {
  741. dishList.value = dishListall
  742. } else {
  743. dishList.value = dishListall.filter((ele) => {
  744. return ele.category == buttonType.value
  745. })
  746. }
  747. console.warn('***watch***', value, buttonType.value, dishList.value)
  748. } else {
  749. dishList.value = dishList.value.filter((ele) => {
  750. return ele.title.includes(value)
  751. })
  752. }
  753. })
  754. const clickSortButton = (name: any) => {
  755. //重复点击不处理
  756. if (buttonType.value == name) {
  757. return false
  758. }
  759. search.value = ''
  760. buttonType.value = name
  761. if (name == 'all') {
  762. dishList.value = dishListall
  763. } else {
  764. dishList.value = dishListall.filter((ele) => {
  765. return ele.category == name
  766. })
  767. }
  768. }
  769. //清空订单下的菜品
  770. const toEmpty = () => {
  771. if (data.selectGoods.length != 0) {
  772. orderLoading.value = true
  773. toEmptyy({ id: params.number }).finally(() => {
  774. //清空未出单的餐品-保留出单餐品
  775. data.selectGoods = []
  776. orderData.sumPriceSum = data.selectGoods.reduce(
  777. (accumulator: number, currentValue: { summary: number; num: number }) =>
  778. accumulator + currentValue.summary * currentValue.num,
  779. 0
  780. )
  781. orderData.sumNumSum = data.selectGoods.reduce(
  782. (accumulator: number, currentValue: { summary: number; num: number }) =>
  783. accumulator + currentValue.num,
  784. 0
  785. )
  786. // data.selectGoods.length = 0
  787. orderData.priceSum = 0
  788. orderData.numSum = 0
  789. // orderData.sumPriceSum = 0
  790. // orderData.sumNumSum = 0
  791. // 检查优惠券条件
  792. if (params.couponId && orderData.sumPriceSum < Number(params.couponAmount)) {
  793. // 如果订单金额小于优惠券金额,清空优惠券
  794. params.couponId = ''
  795. params.couponAmount = '-'
  796. orderData.payAmount = orderData.sumPriceSum
  797. }
  798. orderData.payAmount = Number(
  799. orderData.sumPriceSum -
  800. (params.couponAmount === '-' ? 0 : Number(params.couponAmount))
  801. ).toFixed(2)
  802. orderLoading.value = false
  803. })
  804. }
  805. }
  806. const isEqual = (obj1: { [x: string]: any } | null, obj2: { [x: string]: any } | null) => {
  807. // 如果两个对象是同一个引用,直接返回 true
  808. if (obj1 === obj2) return true
  809. // 如果其中一个为 null 或者不是对象,返回 false
  810. if (obj1 === null || typeof obj1 !== 'object' || obj2 === null || typeof obj2 !== 'object') {
  811. return false
  812. }
  813. // 获取两个对象的键
  814. const keys1 = Object.keys(obj1)
  815. const keys2 = Object.keys(obj2)
  816. // 如果键的数量不同,返回 false
  817. if (keys1.length !== keys2.length) return false
  818. // 遍历所有键,递归比较每个键的值
  819. for (const key of keys1) {
  820. if (!keys2.includes(key) || !isEqual(obj1[key], obj2[key])) {
  821. return false
  822. }
  823. }
  824. // 如果所有键的值都相等,返回 true
  825. return true
  826. }
  827. const beforeClose = () => {
  828. //data.oldGoods
  829. // if(isEqual(data.selectGoods,data.oldGoods)){
  830. // showOrderConsole.value = false;
  831. // emit('init')
  832. // return false;
  833. // }
  834. // if (data.selectGoods && data.selectGoods.length > 0) {
  835. // feedback.confirm('所点菜品信息将不被保存,确认关闭?').then(() => {
  836. // toEmpty()
  837. // })
  838. // }
  839. showOrderConsole.value = false
  840. search.value = ''
  841. buttonType.value = 'all'
  842. emit('init')
  843. }
  844. const add = (item: any) => {
  845. // addGoods(item)
  846. console.log('***add***', item)
  847. orderLoading.value = true
  848. dishInc({ id: item.ordersDishId })
  849. .then(() => {
  850. data.selectGoods[data.selectGoods.indexOf(item)].num++
  851. addHandle(item)
  852. })
  853. .finally(() => {
  854. orderLoading.value = false
  855. nextTick(() => {
  856. scrollbarRef.value!.setScrollTop(innerRef.value!.clientHeight)
  857. })
  858. })
  859. }
  860. // 添加新的响应式变量
  861. const specDialogVisible = ref(false)
  862. const currentItem = ref<any>(null)
  863. const selectedSpecs = ref<any>({})
  864. // 添加新的方法
  865. const groupSpecsByName = (specsList: any[]) => {
  866. const groups: any = {}
  867. specsList?.forEach((spec) => {
  868. if (!groups[spec.name]) {
  869. groups[spec.name] = []
  870. }
  871. groups[spec.name].push(spec)
  872. })
  873. return groups
  874. }
  875. const handleSpecDialogClose = () => {
  876. specDialogVisible.value = false
  877. currentItem.value = null
  878. selectedSpecs.value = {}
  879. }
  880. const handleSpecConfirm = () => {
  881. // 检查是否所有规格都已选择
  882. const allSpecsSelected = Object.keys(groupSpecsByName(currentItem.value.specsList)).every(
  883. (name) => selectedSpecs.value[name]
  884. )
  885. if (!allSpecsSelected) {
  886. feedback.msgError('请选择所有规格')
  887. return
  888. }
  889. // 将选中的规格ID添加到商品中
  890. const item = { ...currentItem.value, selectedSpecIds: Object.values(selectedSpecs.value) }
  891. handleAddGoods(item)
  892. handleSpecDialogClose()
  893. }
  894. // 修改 addGoods 方法
  895. const addGoods = (item: any) => {
  896. if (item.specsList?.length > 0) {
  897. currentItem.value = item
  898. selectedSpecs.value = {}
  899. specDialogVisible.value = true
  900. return
  901. }
  902. handleAddGoods(item)
  903. }
  904. // 将原来的 addGoods 逻辑移到这个新方法中
  905. const handleAddGoods = (item: any) => {
  906. console.log('handleAddGoods', item)
  907. orderLoading.value = true
  908. //查找未出单的餐品列表
  909. const list = data.selectGoods.filter((element: any) => {
  910. return element.status != 1
  911. })
  912. console.log('list', list)
  913. let tempGoods: string | any[] = []
  914. //先从历史下单但是没有结单的餐品中找出要操作的餐品
  915. tempGoods = list.filter((element: any) => {
  916. //如果有specsIds,说明是有规格的餐品
  917. const specsIds: number[] = []
  918. element.specsList?.forEach((spec: any) => {
  919. specsIds.push(spec.specsId)
  920. })
  921. return (
  922. element.artId &&
  923. element.artId == item.id &&
  924. specsIds.sort((a: number, b: number) => a - b).join(',') ==
  925. (item.selectedSpecIds || []).sort((a: number, b: number) => a - b).join(',')
  926. )
  927. })
  928. console.log('tempGoods', tempGoods)
  929. if (tempGoods.length == 0) {
  930. item.num = 1
  931. dishAdd({
  932. orderId: params.number,
  933. dishId: item.id,
  934. specsIds: (item.selectedSpecIds || []).join(',') // 将数组转换为逗号分隔的字符串
  935. })
  936. .then((res) => {
  937. //拿到orders_dish的id
  938. item.ordersDishId = res
  939. item.specsList = item.selectedSpecIds?.map((specId: any) => {
  940. const o = item.specsList.find((spec: any) => spec.id == specId)
  941. o.specsId = specId
  942. return o
  943. })
  944. item.artId = item.id
  945. data.selectGoods.push(item)
  946. console.log('data.selectGoods', data.selectGoods)
  947. feedback.msgSuccess('成功添加商品' + item.title)
  948. addHandle(item)
  949. })
  950. .finally(() => {
  951. orderLoading.value = false
  952. nextTick(() => {
  953. scrollbarRef.value!.setScrollTop(innerRef.value!.clientHeight)
  954. })
  955. })
  956. } else {
  957. dishInc({ id: tempGoods[0].ordersDishId })
  958. .then(() => {
  959. data.selectGoods[data.selectGoods.indexOf(tempGoods[0])].num++
  960. addHandle(item)
  961. })
  962. .finally(() => {
  963. orderLoading.value = false
  964. nextTick(() => {
  965. scrollbarRef.value!.setScrollTop(innerRef.value!.clientHeight)
  966. })
  967. })
  968. }
  969. }
  970. //只有正常执行了接口才触发
  971. const addHandle = (item: { summary: any }) => {
  972. console.warn('***addGoods-dish***', item, data.selectGoods)
  973. console.warn('***orderData***', orderData)
  974. orderData.numSum++
  975. orderData.sumNumSum++
  976. orderData.priceSum += Number(item.summary)
  977. orderData.sumPriceSum += Number(item.summary)
  978. orderData.payAmount = Number(
  979. orderData.sumPriceSum - (params.couponAmount === '-' ? 0 : Number(params.couponAmount))
  980. ).toFixed(2)
  981. }
  982. const reduce = (item: any) => {
  983. orderLoading.value = true
  984. //查找未出单的餐品列表
  985. // const list = data.selectGoods.filter((element: any) => {return element.status != 1})
  986. // let tempGoods: string | any[] = []
  987. // //先从历史下单但是没有结单的餐品中找出要操作的餐品
  988. // tempGoods = list.filter((element: any) => {
  989. // const specsIds: number[] = [];
  990. // element.specsList?.forEach((spec: any) => {
  991. // specsIds.push(spec.specsId)
  992. // })
  993. // return element.artId && element.artId == item.artId &&
  994. // specsIds.sort((a: number, b: number) => a - b).join(',') == (item.selectedSpecIds || []).sort((a: number, b: number) => a - b).join(',')
  995. // })
  996. // if(tempGoods.length==0){//历史没有,则从当次下单的餐品列表中找
  997. // tempGoods = list.filter((element: any) => {
  998. // return element.id == item.id && !element.artId
  999. // })
  1000. // }
  1001. // console.log('tempGoods', tempGoods)
  1002. console.log('data.selectGoods', data.selectGoods)
  1003. const t = data.selectGoods.indexOf(item)
  1004. // const n = data.selectGoods[t].num
  1005. console.log('reduce', t, item)
  1006. if (item.num > 1) {
  1007. dishDec({ id: item.ordersDishId })
  1008. .then(() => {
  1009. data.selectGoods[t].num--
  1010. reduceHandle(item)
  1011. })
  1012. .finally(() => {
  1013. orderLoading.value = false
  1014. })
  1015. } else {
  1016. //餐品数量为0,要删减该餐品
  1017. dishDel({ id: item.ordersDishId })
  1018. .then(() => {
  1019. data.selectGoods.splice(t, 1)
  1020. reduceHandle(item)
  1021. })
  1022. .finally(() => {
  1023. orderLoading.value = false
  1024. })
  1025. }
  1026. }
  1027. //只有正常执行了接口才触发
  1028. const reduceHandle = (item: { summary: number }) => {
  1029. console.warn('***reduce-dish***', item, data.selectGoods)
  1030. orderData.numSum--
  1031. orderData.sumNumSum--
  1032. orderData.priceSum -= item.summary
  1033. orderData.sumPriceSum -= item.summary
  1034. orderData.payAmount = Number(
  1035. (
  1036. orderData.sumPriceSum - (params.couponAmount === '-' ? 0 : Number(params.couponAmount))
  1037. ).toFixed(2)
  1038. )
  1039. if (orderData.priceSum <= 0) {
  1040. orderData.priceSum = 0
  1041. }
  1042. if (orderData.numSum <= 0) {
  1043. orderData.numSum = 0
  1044. }
  1045. if (orderData.sumPriceSum <= 0) {
  1046. orderData.sumPriceSum = 0
  1047. }
  1048. if (orderData.sumNumSum <= 0) {
  1049. orderData.sumNumSum = 0
  1050. }
  1051. if (orderData.payAmount <= 0) {
  1052. orderData.payAmount = 0
  1053. }
  1054. }
  1055. // 添加搜索用户优惠券的方法
  1056. // ... 其他代码保持不变 ...
  1057. // 添加 loading 变量
  1058. const searchLoading = ref(false)
  1059. // 修改搜索方法
  1060. const searchUserCoupons = () => {
  1061. if (!data.phone) {
  1062. feedback.msgError('请输入手机号')
  1063. return
  1064. }
  1065. searchLoading.value = true
  1066. searchCoupons({ mobile: data.phone })
  1067. .then((res: any) => {
  1068. if (res) {
  1069. data.couponList = res ? res.couponList || [] : []
  1070. params.userId = res ? res.userId || null : null
  1071. if (data.couponList.length == 0) {
  1072. feedback.msgError('暂无可用优惠券')
  1073. return false
  1074. }
  1075. }
  1076. })
  1077. .finally(() => {
  1078. searchLoading.value = false
  1079. })
  1080. }
  1081. onMounted(() => {
  1082. dishCateAll().then((res) => {
  1083. dishCate.value = res
  1084. })
  1085. dishListAll().then((res) => {
  1086. dishList.value = res
  1087. dishListall = res
  1088. })
  1089. })
  1090. defineExpose({
  1091. open
  1092. })
  1093. </script>
  1094. <style lang="scss" scoped>
  1095. .el-alert__content {
  1096. width: 100%;
  1097. }
  1098. .scrollbar {
  1099. height: calc(100vh - 54px - 32px - 16px - 61px - 20px - 52px - 49px - 80px);
  1100. }
  1101. :deep(.el-divider--horizontal) {
  1102. margin: 8px 0;
  1103. }
  1104. .cate-buttons :deep(.el-button) {
  1105. margin: 0;
  1106. width: 100%;
  1107. justify-content: flex-start;
  1108. padding-left: 1rem;
  1109. }
  1110. .cate-buttons :deep(.el-button.is-text:focus),
  1111. .cate-buttons :deep(.el-button.is-text:hover),
  1112. .cate-buttons :deep(.el-button.is-text.is-active) {
  1113. color: var(--el-button-hover-border-color);
  1114. border-color: transparent;
  1115. background-color: var(--el-color-primary-light-9);
  1116. }
  1117. .cate-buttons :deep(.el-button.is-text) {
  1118. color: var(--el-text-color-regular);
  1119. }
  1120. .price-summary {
  1121. box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.05);
  1122. }
  1123. .el-button.el-button--danger {
  1124. background-color: #ff4d4f;
  1125. border-color: #ff4d4f;
  1126. }
  1127. .el-button.el-button--danger:hover {
  1128. background-color: #ff7875;
  1129. border-color: #ff7875;
  1130. }
  1131. </style>
  1132. <style>
  1133. .coupon-card {
  1134. display: flex;
  1135. align-items: center;
  1136. padding: 8px;
  1137. }
  1138. .coupon-item {
  1139. display: flex;
  1140. margin-bottom: 12px;
  1141. cursor: pointer;
  1142. position: relative;
  1143. background: #fff;
  1144. border-radius: 4px;
  1145. transition: all 0.3s;
  1146. border: 1px solid #e8e8e8;
  1147. &:last-child {
  1148. margin-bottom: 0;
  1149. }
  1150. &.is-selected {
  1151. border-color: coral;
  1152. .left-part {
  1153. background: coral;
  1154. }
  1155. }
  1156. &.is-disabled {
  1157. cursor: not-allowed;
  1158. opacity: 0.6;
  1159. .left-part {
  1160. background: #999;
  1161. }
  1162. }
  1163. }
  1164. .left-part {
  1165. width: 90px;
  1166. background: coral;
  1167. color: #fff;
  1168. padding: 12px 8px;
  1169. text-align: center;
  1170. position: relative;
  1171. display: flex;
  1172. align-items: center;
  1173. justify-content: center;
  1174. .amount-wrapper {
  1175. display: flex;
  1176. align-items: center;
  1177. justify-content: center;
  1178. height: 100%;
  1179. }
  1180. &::after {
  1181. content: '';
  1182. position: absolute;
  1183. right: 0;
  1184. top: 0;
  1185. bottom: 0;
  1186. width: 4px;
  1187. background-image: radial-gradient(circle at 0 6px, transparent 3px, #fff 3px);
  1188. background-size: 4px 12px;
  1189. background-repeat: repeat-y;
  1190. }
  1191. .amount {
  1192. font-size: 24px;
  1193. font-weight: bold;
  1194. line-height: 1;
  1195. }
  1196. .unit {
  1197. font-size: 12px;
  1198. margin-top: 2px;
  1199. }
  1200. .condition {
  1201. font-size: 12px;
  1202. margin-top: 6px;
  1203. opacity: 0.9;
  1204. }
  1205. }
  1206. .right-part {
  1207. flex: 1;
  1208. padding: 12px;
  1209. display: flex;
  1210. flex-direction: column;
  1211. justify-content: space-between;
  1212. .name {
  1213. font-size: 14px;
  1214. color: #333;
  1215. font-weight: 500;
  1216. }
  1217. .date {
  1218. font-size: 12px;
  1219. color: #999;
  1220. margin-top: 4px;
  1221. }
  1222. .check {
  1223. position: absolute;
  1224. right: 12px;
  1225. top: 50%;
  1226. transform: translateY(-50%);
  1227. color: #cd8e5f;
  1228. font-size: 12px;
  1229. }
  1230. }
  1231. .coupon-container {
  1232. border: 1px solid #ebeef5;
  1233. border-radius: 4px;
  1234. :deep(.el-scrollbar__wrap) {
  1235. padding: 8px;
  1236. }
  1237. }
  1238. .action-btn {
  1239. height: 28px;
  1240. padding: 5px 10px;
  1241. font-size: 12px;
  1242. border: 1px solid #dcdfe6;
  1243. border-radius: 4px;
  1244. background: transparent;
  1245. color: #606266;
  1246. }
  1247. .action-btn:hover {
  1248. color: var(--el-color-primary);
  1249. border-color: var(--el-color-primary);
  1250. background-color: var(--el-color-primary-light-9);
  1251. }
  1252. .submit-btn {
  1253. width: 100%;
  1254. height: 40px;
  1255. background-color: #ff4d4f;
  1256. border-color: #ff4d4f;
  1257. }
  1258. .submit-btn:hover {
  1259. background-color: #ff7875;
  1260. border-color: #ff7875;
  1261. }
  1262. </style>