console.vue 45 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325
  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. if (!data.canScan) {
  464. feedback.notifyWarning('正在处理支付,请勿重复扫码')
  465. return
  466. }
  467. data.canScan = false
  468. document.removeEventListener('keydown', handleScanInput)
  469. const code = scanCode.value
  470. console.log('扫码内容:', code)
  471. payingDialogVisible.value = true // 显示支付中对话框
  472. // 修改支付处理逻辑
  473. orderPay({ oid: params.number, code: code })
  474. .then((res) => {
  475. console.log(res)
  476. // 实际上支付成功之后,res返回的是[],如果不是实时成功,返回的是个对象, 形如:{orderId: '123456789', sn: '7895004131583689'}
  477. if (res == null || res.length == 0) {
  478. feedback.notifySuccess('成功结账' + orderData.payAmount + '元')
  479. scanDialogVisible.value = false
  480. payingDialogVisible.value = false // 隐藏支付中对话框
  481. showOrderConsole.value = false
  482. emit('init')
  483. } else {
  484. console.log('支付中')
  485. return queryPayStatusWithRetry(res)
  486. .then((paySuccess) => {
  487. if (paySuccess) {
  488. feedback.notifySuccess('成功结账' + orderData.payAmount + '元')
  489. scanDialogVisible.value = false
  490. payingDialogVisible.value = false // 隐藏支付中对话框
  491. showOrderConsole.value = false
  492. emit('init')
  493. }
  494. })
  495. }
  496. })
  497. .catch((e) => {
  498. console.log("=====---", e)
  499. // 支付失败
  500. payingDialogVisible.value = false // 隐藏支付中对话框
  501. feedback.notifySuccess('支付失败,请重试!')
  502. data.canScan = true
  503. scanDialogVisible.value = true
  504. // 添加键盘事件监听
  505. setTimeout(()=>{
  506. scanCode.value = ''
  507. document.removeEventListener('keydown', handleScanInput)
  508. document.addEventListener('keydown', handleScanInput)
  509. }, 2000)
  510. })
  511. scanCode.value = ''
  512. } else {
  513. scanCode.value += event.key
  514. }
  515. }
  516. // 定义查询支付状态的函数
  517. const queryPayStatusWithRetry = (data): Promise<boolean> => {
  518. const intervals = [3000, 5000, 10000, 10000, 10000, 10000, 10000]
  519. let currentIndex = 0
  520. payingDialogVisible.value = true
  521. const tryQuery = () => {
  522. return new Promise<boolean>((resolve) => {
  523. setTimeout(() => {
  524. queryPayStatus({ orderId: data.orderId, sn: data.sn })
  525. .then((result) => {
  526. if (
  527. result.result_code == '200' &&
  528. result.biz_response.data.order_status == 'SUCCESS'
  529. ) {
  530. resolve(true)
  531. } else if (currentIndex < intervals.length - 1) {
  532. currentIndex++
  533. tryQuery().then(resolve)
  534. } else {
  535. resolve(false)
  536. }
  537. })
  538. .catch(() => {
  539. if (currentIndex < intervals.length - 1) {
  540. currentIndex++
  541. tryQuery().then(resolve)
  542. } else {
  543. resolve(false)
  544. }
  545. })
  546. }, intervals[currentIndex])
  547. })
  548. }
  549. return tryQuery().then((success) => {
  550. payingDialogVisible.value = false
  551. if (!success) {
  552. return cancelOrder({ orderId: data.orderId, sn: data.sn })
  553. .then(() => {
  554. feedback.notifyWarning('支付超时,已自动撤销本次支付')
  555. return false
  556. })
  557. .catch((error) => {
  558. console.error('撤单失败:', error)
  559. feedback.notifyError('支付失败,请重试')
  560. return false
  561. })
  562. }
  563. return success
  564. })
  565. }
  566. const ticketConfirm = () => {
  567. if (data.phone == '') {
  568. params.couponId = null
  569. params.couponAmount = '-'
  570. params.userId = null
  571. // 计算支付金额
  572. orderData.payAmount = Number(orderData.sumPriceSum).toFixed(2)
  573. }
  574. showCouponDialog.value = false
  575. }
  576. const checkout = () => {
  577. refreshdishes()
  578. //结账
  579. console.warn('***结账***')
  580. // if (data.selectGoods || data.selectGoods.length == 0) {
  581. // feedback.notify('请点击商品开始点单吧!')
  582. // return false
  583. // }
  584. // feedback.confirm('结账金额为' + orderData.payAmount + ',确定要结账吗?').then(() => {
  585. scanCode.value = ''
  586. data.canScan = true
  587. scanDialogVisible.value = true
  588. // 添加键盘事件监听
  589. document.removeEventListener('keydown', handleScanInput)
  590. document.addEventListener('keydown', handleScanInput)
  591. // })
  592. }
  593. // 添加优惠券点击处理方法
  594. const handleCouponClick = (coupon: any) => {
  595. if (orderData.sumPriceSum < coupon.consumeScore / 100 || orderData.sumPriceSum < (coupon.useThreshold / 100)) {
  596. return
  597. }
  598. if (params.couponId === coupon.ticketRecordId) {
  599. params.couponId = ''
  600. params.couponAmount = '-'
  601. orderData.payAmount = orderData.sumPriceSum
  602. return
  603. }
  604. params.couponId = coupon.ticketRecordId
  605. params.couponAmount = (coupon.consumeScore / 100).toFixed(2)
  606. // 计算支付金额
  607. orderData.payAmount = (
  608. Number(orderData.sumPriceSum) - Number(coupon.consumeScore / 100)
  609. ).toFixed(2)
  610. }
  611. const open = (item: any, num?: number, orderNumber?: any) => {
  612. console.log('***open***', item, num, orderNumber)
  613. data.couponList = []
  614. showOrderConsole.value = true
  615. data.deskId = item.id
  616. data.deskName = item.name
  617. data.deskCap = item.num
  618. data.currentNum = item.userNum ? item.userNum : num
  619. params.number = item.ordersId ? item.ordersId : orderNumber
  620. params.userId = ''
  621. params.couponId = ''
  622. //查询当前订单下的所有菜品
  623. deskOrderedDishListAll({ id: params.number }).then((res) => {
  624. res.forEach((good: { id: number; ordersDishId: number }) => {
  625. good.ordersDishId = good.id
  626. })
  627. console.log('***deskOrderedDishListAll***', res)
  628. data.selectGoods = res
  629. data.oldGoods = JSON.parse(JSON.stringify(res)) //记录下进入时当前餐桌已有的食物
  630. if (res.length > 0) {
  631. data.phone = res[0].mobile
  632. params.remark = res[0].remark
  633. params.userId = res[0].userId || null
  634. params.couponId = res[0].ticketId || null
  635. params.diningMethods = res[0].diningMethods || 1
  636. if (params.couponId && params.userId) {
  637. searchUserCoupons()
  638. }
  639. params.couponAmount = res[0].ticketAmount
  640. ? Number(res[0].ticketAmount / 100).toFixed(2)
  641. : '-'
  642. const _list = res.filter((it: { status: number }) => it.status != 1) //找到没有出单的餐品来计算价格
  643. orderData.priceSum = _list.reduce(
  644. (accumulator: number, currentValue: { summary: number; num: number }) =>
  645. accumulator + currentValue.summary * currentValue.num,
  646. 0
  647. )
  648. orderData.numSum = _list.reduce(
  649. (accumulator: number, currentValue: { summary: number; num: number }) =>
  650. accumulator + currentValue.num,
  651. 0
  652. )
  653. //所有餐品的总价以及总数量
  654. orderData.sumPriceSum = res.reduce(
  655. (accumulator: number, currentValue: { summary: number; num: number }) =>
  656. accumulator + currentValue.summary * currentValue.num,
  657. 0
  658. )
  659. orderData.sumNumSum = res.reduce(
  660. (accumulator: number, currentValue: { summary: number; num: number }) =>
  661. accumulator + currentValue.num,
  662. 0
  663. )
  664. orderData.payAmount = Number(
  665. orderData.sumPriceSum -
  666. (params.couponAmount === '-' ? 0 : Number(params.couponAmount))
  667. ).toFixed(2)
  668. } else {
  669. orderData.priceSum = 0
  670. orderData.numSum = 0
  671. orderData.sumPriceSum = 0
  672. orderData.sumNumSum = 0
  673. data.phone = ''
  674. params.remark = ''
  675. params.userId = ''
  676. params.couponId = ''
  677. params.couponAmount = '-'
  678. orderData.payAmount = 0
  679. data.couponList = []
  680. }
  681. })
  682. dishList.value = dishListall //显示所有菜品
  683. }
  684. const refreshdishes = async () => {
  685. orderLoading.value = true
  686. await deskOrderedDishListAll({ id: params.number })
  687. .then((res) => {
  688. res.forEach((good: { id: number; ordersDishId: number }) => {
  689. good.ordersDishId = good.id
  690. })
  691. console.log('***refreshdishes***', res)
  692. data.selectGoods = res
  693. console.log('***res.length***', res.length)
  694. if (res.length > 0) {
  695. // const _list = res.filter((it: { status: number }) => it.status != 1) //找到没有出单的餐品来计算价格
  696. const _list = res
  697. orderData.priceSum = _list.reduce(
  698. (accumulator: number, currentValue: { summary: number; num: number }) =>
  699. accumulator + currentValue.summary * currentValue.num,
  700. 0
  701. )
  702. orderData.numSum = _list.reduce(
  703. (accumulator: number, currentValue: { summary: number; num: number }) =>
  704. accumulator + currentValue.num,
  705. 0
  706. )
  707. //所有餐品的总价以及总数量
  708. orderData.sumPriceSum = res.reduce(
  709. (accumulator: number, currentValue: { summary: number; num: number }) =>
  710. accumulator + currentValue.summary * currentValue.num,
  711. 0
  712. )
  713. orderData.sumNumSum = res.reduce(
  714. (accumulator: number, currentValue: { summary: number; num: number }) =>
  715. accumulator + currentValue.num,
  716. 0
  717. )
  718. console.log(
  719. '***res[0].payAmount***',
  720. res[0].payAmount,
  721. Number(res[0].payAmount / 100).toFixed(2)
  722. )
  723. orderData.payAmount = res[0].payAmount
  724. ? Number(res[0].payAmount / 100).toFixed(2)
  725. : Number(orderData.sumPriceSum).toFixed(2)
  726. console.log('***orderData***', orderData)
  727. } else {
  728. orderData.priceSum = 0
  729. orderData.numSum = 0
  730. orderData.sumPriceSum = 0
  731. orderData.sumNumSum = 0
  732. orderData.payAmount = 0
  733. }
  734. })
  735. .finally(() => {
  736. orderLoading.value = false
  737. })
  738. }
  739. watch(search, (value) => {
  740. if (!value) {
  741. if (buttonType.value == 'all') {
  742. dishList.value = dishListall
  743. } else {
  744. dishList.value = dishListall.filter((ele) => {
  745. return ele.category == buttonType.value
  746. })
  747. }
  748. console.warn('***watch***', value, buttonType.value, dishList.value)
  749. } else {
  750. dishList.value = dishList.value.filter((ele) => {
  751. return ele.title.includes(value)
  752. })
  753. }
  754. })
  755. const clickSortButton = (name: any) => {
  756. //重复点击不处理
  757. if (buttonType.value == name) {
  758. return false
  759. }
  760. search.value = ''
  761. buttonType.value = name
  762. if (name == 'all') {
  763. dishList.value = dishListall
  764. } else {
  765. dishList.value = dishListall.filter((ele) => {
  766. return ele.category == name
  767. })
  768. }
  769. }
  770. //清空订单下的菜品
  771. const toEmpty = () => {
  772. if (data.selectGoods.length != 0) {
  773. orderLoading.value = true
  774. toEmptyy({ id: params.number }).then(() => {
  775. //清空未出单的餐品-保留出单餐品
  776. data.selectGoods = []
  777. orderData.sumPriceSum = data.selectGoods.reduce(
  778. (accumulator: number, currentValue: { summary: number; num: number }) =>
  779. accumulator + currentValue.summary * currentValue.num,
  780. 0
  781. )
  782. orderData.sumNumSum = data.selectGoods.reduce(
  783. (accumulator: number, currentValue: { summary: number; num: number }) =>
  784. accumulator + currentValue.num,
  785. 0
  786. )
  787. // data.selectGoods.length = 0
  788. orderData.priceSum = 0
  789. orderData.numSum = 0
  790. // orderData.sumPriceSum = 0
  791. // orderData.sumNumSum = 0
  792. // 检查优惠券条件
  793. if (params.couponId && orderData.sumPriceSum < Number(params.couponAmount)) {
  794. // 如果订单金额小于优惠券金额,清空优惠券
  795. params.couponId = ''
  796. params.couponAmount = '-'
  797. orderData.payAmount = orderData.sumPriceSum
  798. }
  799. orderData.payAmount = Number(
  800. orderData.sumPriceSum -
  801. (params.couponAmount === '-' ? 0 : Number(params.couponAmount))
  802. ).toFixed(2)
  803. }).finally(()=>{
  804. orderLoading.value = false
  805. })
  806. }
  807. }
  808. const isEqual = (obj1: { [x: string]: any } | null, obj2: { [x: string]: any } | null) => {
  809. // 如果两个对象是同一个引用,直接返回 true
  810. if (obj1 === obj2) return true
  811. // 如果其中一个为 null 或者不是对象,返回 false
  812. if (obj1 === null || typeof obj1 !== 'object' || obj2 === null || typeof obj2 !== 'object') {
  813. return false
  814. }
  815. // 获取两个对象的键
  816. const keys1 = Object.keys(obj1)
  817. const keys2 = Object.keys(obj2)
  818. // 如果键的数量不同,返回 false
  819. if (keys1.length !== keys2.length) return false
  820. // 遍历所有键,递归比较每个键的值
  821. for (const key of keys1) {
  822. if (!keys2.includes(key) || !isEqual(obj1[key], obj2[key])) {
  823. return false
  824. }
  825. }
  826. // 如果所有键的值都相等,返回 true
  827. return true
  828. }
  829. const beforeClose = () => {
  830. //data.oldGoods
  831. // if(isEqual(data.selectGoods,data.oldGoods)){
  832. // showOrderConsole.value = false;
  833. // emit('init')
  834. // return false;
  835. // }
  836. // if (data.selectGoods && data.selectGoods.length > 0) {
  837. // feedback.confirm('所点菜品信息将不被保存,确认关闭?').then(() => {
  838. // toEmpty()
  839. // })
  840. // }
  841. showOrderConsole.value = false
  842. search.value = ''
  843. buttonType.value = 'all'
  844. emit('init')
  845. }
  846. const add = (item: any) => {
  847. // addGoods(item)
  848. console.log('***add***', item)
  849. orderLoading.value = true
  850. dishInc({ id: item.ordersDishId })
  851. .then(() => {
  852. data.selectGoods[data.selectGoods.indexOf(item)].num++
  853. addHandle(item)
  854. })
  855. .finally(() => {
  856. orderLoading.value = false
  857. nextTick(() => {
  858. scrollbarRef.value!.setScrollTop(innerRef.value!.clientHeight)
  859. })
  860. })
  861. }
  862. // 添加新的响应式变量
  863. const specDialogVisible = ref(false)
  864. const currentItem = ref<any>(null)
  865. const selectedSpecs = ref<any>({})
  866. // 添加新的方法
  867. const groupSpecsByName = (specsList: any[]) => {
  868. const groups: any = {}
  869. specsList?.forEach((spec) => {
  870. if (!groups[spec.name]) {
  871. groups[spec.name] = []
  872. }
  873. groups[spec.name].push(spec)
  874. })
  875. return groups
  876. }
  877. const handleSpecDialogClose = () => {
  878. specDialogVisible.value = false
  879. currentItem.value = null
  880. selectedSpecs.value = {}
  881. }
  882. const handleSpecConfirm = () => {
  883. // 检查是否所有规格都已选择
  884. const allSpecsSelected = Object.keys(groupSpecsByName(currentItem.value.specsList)).every(
  885. (name) => selectedSpecs.value[name]
  886. )
  887. if (!allSpecsSelected) {
  888. feedback.msgError('请选择所有规格')
  889. return
  890. }
  891. // 将选中的规格ID添加到商品中
  892. const item = { ...currentItem.value, selectedSpecIds: Object.values(selectedSpecs.value) }
  893. handleAddGoods(item)
  894. handleSpecDialogClose()
  895. }
  896. // 修改 addGoods 方法
  897. const addGoods = (item: any) => {
  898. if (item.specsList?.length > 0) {
  899. currentItem.value = item
  900. selectedSpecs.value = {}
  901. specDialogVisible.value = true
  902. return
  903. }
  904. handleAddGoods(item)
  905. }
  906. // 将原来的 addGoods 逻辑移到这个新方法中
  907. const handleAddGoods = (item: any) => {
  908. console.log('handleAddGoods', item)
  909. orderLoading.value = true
  910. //查找未出单的餐品列表
  911. const list = data.selectGoods.filter((element: any) => {
  912. return element.status != 1
  913. })
  914. console.log('list', list)
  915. let tempGoods: string | any[] = []
  916. //先从历史下单但是没有结单的餐品中找出要操作的餐品
  917. tempGoods = list.filter((element: any) => {
  918. //如果有specsIds,说明是有规格的餐品
  919. const specsIds: number[] = []
  920. element.specsList?.forEach((spec: any) => {
  921. specsIds.push(spec.specsId)
  922. })
  923. return (
  924. element.artId &&
  925. element.artId == item.id &&
  926. specsIds.sort((a: number, b: number) => a - b).join(',') ==
  927. (item.selectedSpecIds || []).sort((a: number, b: number) => a - b).join(',')
  928. )
  929. })
  930. console.log('tempGoods', tempGoods)
  931. if (tempGoods.length == 0) {
  932. item.num = 1
  933. dishAdd({
  934. orderId: params.number,
  935. dishId: item.id,
  936. specsIds: (item.selectedSpecIds || []).join(',') // 将数组转换为逗号分隔的字符串
  937. })
  938. .then((res) => {
  939. //拿到orders_dish的id
  940. item.ordersDishId = res
  941. item.specsList = item.selectedSpecIds?.map((specId: any) => {
  942. const o = item.specsList.find((spec: any) => spec.id == specId)
  943. o.specsId = specId
  944. return o
  945. })
  946. item.artId = item.id
  947. data.selectGoods.push(item)
  948. console.log('data.selectGoods', data.selectGoods)
  949. feedback.msgSuccess('成功添加商品' + item.title)
  950. addHandle(item)
  951. })
  952. .finally(() => {
  953. orderLoading.value = false
  954. nextTick(() => {
  955. scrollbarRef.value!.setScrollTop(innerRef.value!.clientHeight)
  956. })
  957. })
  958. } else {
  959. dishInc({ id: tempGoods[0].ordersDishId })
  960. .then(() => {
  961. data.selectGoods[data.selectGoods.indexOf(tempGoods[0])].num++
  962. addHandle(item)
  963. })
  964. .finally(() => {
  965. orderLoading.value = false
  966. nextTick(() => {
  967. scrollbarRef.value!.setScrollTop(innerRef.value!.clientHeight)
  968. })
  969. })
  970. }
  971. }
  972. //只有正常执行了接口才触发
  973. const addHandle = (item: { summary: any }) => {
  974. console.warn('***addGoods-dish***', item, data.selectGoods)
  975. console.warn('***orderData***', orderData)
  976. orderData.numSum++
  977. orderData.sumNumSum++
  978. orderData.priceSum += Number(item.summary)
  979. orderData.sumPriceSum += Number(item.summary)
  980. orderData.payAmount = Number(
  981. orderData.sumPriceSum - (params.couponAmount === '-' ? 0 : Number(params.couponAmount))
  982. ).toFixed(2)
  983. }
  984. const reduce = (item: any) => {
  985. orderLoading.value = true
  986. //查找未出单的餐品列表
  987. // const list = data.selectGoods.filter((element: any) => {return element.status != 1})
  988. // let tempGoods: string | any[] = []
  989. // //先从历史下单但是没有结单的餐品中找出要操作的餐品
  990. // tempGoods = list.filter((element: any) => {
  991. // const specsIds: number[] = [];
  992. // element.specsList?.forEach((spec: any) => {
  993. // specsIds.push(spec.specsId)
  994. // })
  995. // return element.artId && element.artId == item.artId &&
  996. // specsIds.sort((a: number, b: number) => a - b).join(',') == (item.selectedSpecIds || []).sort((a: number, b: number) => a - b).join(',')
  997. // })
  998. // if(tempGoods.length==0){//历史没有,则从当次下单的餐品列表中找
  999. // tempGoods = list.filter((element: any) => {
  1000. // return element.id == item.id && !element.artId
  1001. // })
  1002. // }
  1003. // console.log('tempGoods', tempGoods)
  1004. console.log('data.selectGoods', data.selectGoods)
  1005. const t = data.selectGoods.indexOf(item)
  1006. // const n = data.selectGoods[t].num
  1007. console.log('reduce', t, item)
  1008. if (item.num > 1) {
  1009. dishDec({ id: item.ordersDishId })
  1010. .then(() => {
  1011. data.selectGoods[t].num--
  1012. reduceHandle(item)
  1013. })
  1014. .finally(() => {
  1015. orderLoading.value = false
  1016. })
  1017. } else {
  1018. //餐品数量为0,要删减该餐品
  1019. dishDel({ id: item.ordersDishId })
  1020. .then(() => {
  1021. data.selectGoods.splice(t, 1)
  1022. reduceHandle(item)
  1023. })
  1024. .finally(() => {
  1025. orderLoading.value = false
  1026. })
  1027. }
  1028. }
  1029. //只有正常执行了接口才触发
  1030. const reduceHandle = (item: { summary: number }) => {
  1031. console.warn('***reduce-dish***', item, data.selectGoods)
  1032. orderData.numSum--
  1033. orderData.sumNumSum--
  1034. orderData.priceSum -= item.summary
  1035. orderData.sumPriceSum -= item.summary
  1036. orderData.payAmount = Number(
  1037. (
  1038. orderData.sumPriceSum - (params.couponAmount === '-' ? 0 : Number(params.couponAmount))
  1039. ).toFixed(2)
  1040. )
  1041. if (orderData.priceSum <= 0) {
  1042. orderData.priceSum = 0
  1043. }
  1044. if (orderData.numSum <= 0) {
  1045. orderData.numSum = 0
  1046. }
  1047. if (orderData.sumPriceSum <= 0) {
  1048. orderData.sumPriceSum = 0
  1049. }
  1050. if (orderData.sumNumSum <= 0) {
  1051. orderData.sumNumSum = 0
  1052. }
  1053. if (orderData.payAmount <= 0) {
  1054. orderData.payAmount = 0
  1055. }
  1056. }
  1057. // 添加搜索用户优惠券的方法
  1058. // ... 其他代码保持不变 ...
  1059. // 添加 loading 变量
  1060. const searchLoading = ref(false)
  1061. // 修改搜索方法
  1062. const searchUserCoupons = () => {
  1063. if (!data.phone) {
  1064. feedback.msgError('请输入手机号')
  1065. return
  1066. }
  1067. searchLoading.value = true
  1068. searchCoupons({ mobile: data.phone })
  1069. .then((res: any) => {
  1070. if (res) {
  1071. data.couponList = res ? res.couponList || [] : []
  1072. params.userId = res ? res.userId || null : null
  1073. if (data.couponList.length == 0) {
  1074. feedback.msgError('暂无可用优惠券')
  1075. return false
  1076. }
  1077. }
  1078. })
  1079. .finally(() => {
  1080. searchLoading.value = false
  1081. })
  1082. }
  1083. onMounted(() => {
  1084. dishCateAll().then((res) => {
  1085. dishCate.value = res
  1086. })
  1087. dishListAll().then((res) => {
  1088. dishList.value = res
  1089. dishListall = res
  1090. })
  1091. })
  1092. defineExpose({
  1093. open
  1094. })
  1095. </script>
  1096. <style lang="scss" scoped>
  1097. .el-alert__content {
  1098. width: 100%;
  1099. }
  1100. .scrollbar {
  1101. height: calc(100vh - 54px - 32px - 16px - 61px - 20px - 52px - 49px - 80px);
  1102. }
  1103. :deep(.el-divider--horizontal) {
  1104. margin: 8px 0;
  1105. }
  1106. .cate-buttons :deep(.el-button) {
  1107. margin: 0;
  1108. width: 100%;
  1109. justify-content: flex-start;
  1110. padding-left: 1rem;
  1111. }
  1112. .cate-buttons :deep(.el-button.is-text:focus),
  1113. .cate-buttons :deep(.el-button.is-text:hover),
  1114. .cate-buttons :deep(.el-button.is-text.is-active) {
  1115. color: var(--el-button-hover-border-color);
  1116. border-color: transparent;
  1117. background-color: var(--el-color-primary-light-9);
  1118. }
  1119. .cate-buttons :deep(.el-button.is-text) {
  1120. color: var(--el-text-color-regular);
  1121. }
  1122. .price-summary {
  1123. box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.05);
  1124. }
  1125. .el-button.el-button--danger {
  1126. background-color: #ff4d4f;
  1127. border-color: #ff4d4f;
  1128. }
  1129. .el-button.el-button--danger:hover {
  1130. background-color: #ff7875;
  1131. border-color: #ff7875;
  1132. }
  1133. </style>
  1134. <style>
  1135. .coupon-card {
  1136. display: flex;
  1137. align-items: center;
  1138. padding: 8px;
  1139. }
  1140. .coupon-item {
  1141. display: flex;
  1142. margin-bottom: 12px;
  1143. cursor: pointer;
  1144. position: relative;
  1145. background: #fff;
  1146. border-radius: 4px;
  1147. transition: all 0.3s;
  1148. border: 1px solid #e8e8e8;
  1149. &:last-child {
  1150. margin-bottom: 0;
  1151. }
  1152. &.is-selected {
  1153. border-color: coral;
  1154. .left-part {
  1155. background: coral;
  1156. }
  1157. }
  1158. &.is-disabled {
  1159. cursor: not-allowed;
  1160. opacity: 0.6;
  1161. .left-part {
  1162. background: #999;
  1163. }
  1164. }
  1165. }
  1166. .left-part {
  1167. width: 90px;
  1168. background: coral;
  1169. color: #fff;
  1170. padding: 12px 8px;
  1171. text-align: center;
  1172. position: relative;
  1173. display: flex;
  1174. align-items: center;
  1175. justify-content: center;
  1176. .amount-wrapper {
  1177. display: flex;
  1178. align-items: center;
  1179. justify-content: center;
  1180. height: 100%;
  1181. }
  1182. &::after {
  1183. content: '';
  1184. position: absolute;
  1185. right: 0;
  1186. top: 0;
  1187. bottom: 0;
  1188. width: 4px;
  1189. background-image: radial-gradient(circle at 0 6px, transparent 3px, #fff 3px);
  1190. background-size: 4px 12px;
  1191. background-repeat: repeat-y;
  1192. }
  1193. .amount {
  1194. font-size: 24px;
  1195. font-weight: bold;
  1196. line-height: 1;
  1197. }
  1198. .unit {
  1199. font-size: 12px;
  1200. margin-top: 2px;
  1201. }
  1202. .condition {
  1203. font-size: 12px;
  1204. margin-top: 6px;
  1205. opacity: 0.9;
  1206. }
  1207. }
  1208. .right-part {
  1209. flex: 1;
  1210. padding: 12px;
  1211. display: flex;
  1212. flex-direction: column;
  1213. justify-content: space-between;
  1214. .name {
  1215. font-size: 14px;
  1216. color: #333;
  1217. font-weight: 500;
  1218. }
  1219. .date {
  1220. font-size: 12px;
  1221. color: #999;
  1222. margin-top: 4px;
  1223. }
  1224. .check {
  1225. position: absolute;
  1226. right: 12px;
  1227. top: 50%;
  1228. transform: translateY(-50%);
  1229. color: #cd8e5f;
  1230. font-size: 12px;
  1231. }
  1232. }
  1233. .coupon-container {
  1234. border: 1px solid #ebeef5;
  1235. border-radius: 4px;
  1236. :deep(.el-scrollbar__wrap) {
  1237. padding: 8px;
  1238. }
  1239. }
  1240. .action-btn {
  1241. height: 28px;
  1242. padding: 5px 10px;
  1243. font-size: 12px;
  1244. border: 1px solid #dcdfe6;
  1245. border-radius: 4px;
  1246. background: transparent;
  1247. color: #606266;
  1248. }
  1249. .action-btn:hover {
  1250. color: var(--el-color-primary);
  1251. border-color: var(--el-color-primary);
  1252. background-color: var(--el-color-primary-light-9);
  1253. }
  1254. .submit-btn {
  1255. width: 100%;
  1256. height: 40px;
  1257. background-color: #ff4d4f;
  1258. border-color: #ff4d4f;
  1259. }
  1260. .submit-btn:hover {
  1261. background-color: #ff7875;
  1262. border-color: #ff7875;
  1263. }
  1264. </style>