index.vue 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413
  1. <template>
  2. <div class="order-index">
  3. <el-card shadow="never">
  4. <el-tabs v-model="activeName" @tab-change="tabChange">
  5. <el-tab-pane label="全部餐桌" name="all"></el-tab-pane>
  6. <el-tab-pane
  7. v-for="item in areaList"
  8. :key="item.id"
  9. :label="item.name"
  10. :name="item.name"
  11. ></el-tab-pane>
  12. </el-tabs>
  13. <div
  14. v-for="i in deskList"
  15. :key="i"
  16. class="p-1 flex-wrap float-left text-center md:w-28 h-28 w-1/3"
  17. >
  18. <a href="javascript:void(0);" @click="order_(i)"
  19. ><div
  20. class="rounded w-full h-full pt-3 relative"
  21. :style="
  22. i.status == 2
  23. ? 'background-color: rgba(243, 244, 246);color:rgba(75, 85, 99)'
  24. : 'background-color: rgba(91,136,73);color:rgba(255, 255, 255)'
  25. "
  26. >
  27. <p class="font-semibold text-xl">
  28. {{ i.name }}
  29. </p>
  30. <el-tag round>{{
  31. i.status == 2 ? '空闲' : i.status == 0 ? '点餐中' : '就餐中'
  32. }}</el-tag>
  33. <div class="text-xl font-semibold">
  34. <!-- {{ i.status == 2 ? ' ' : '¥888.88' }} -->
  35. {{ i.amount ? '¥' + i.amount.toFixed(2) : ' ' }}
  36. </div>
  37. <div
  38. class="w-full"
  39. :style="'background-color: rgba(255, 255, 255, 0.4);position: absolute;bottom: 0px;'"
  40. >
  41. {{ (i.userNum ? i.userNum : '0') + '/' + i.num }}
  42. </div>
  43. </div></a
  44. >
  45. </div>
  46. </el-card>
  47. <footer-btns v-perms="['setting:website:save']">
  48. <el-button icon="RefreshRight" :loading="isLoading" type="primary" @click="refresh()"
  49. >刷新桌台</el-button
  50. >
  51. </footer-btns>
  52. <el-dialog
  53. width="300px"
  54. v-model="dialogVisible"
  55. :show-close="false"
  56. center
  57. top="0px"
  58. :title="currentDeskStatus == 2 ? '选择就餐人数' : ''"
  59. >
  60. <div v-if="currentDeskStatus != 2">
  61. <el-button class="w-full" type="primary" plain @click="reorder()">
  62. 继续点餐
  63. </el-button>
  64. <br />
  65. <!-- <el-button-->
  66. <!-- class="w-full mt-1"-->
  67. <!-- plain-->
  68. <!-- :disabled="currentDeskStatus == 2"-->
  69. <!-- @click="checkout()"-->
  70. <!-- >-->
  71. <!-- 结账-->
  72. <!-- </el-button>-->
  73. <!-- <br />-->
  74. </div>
  75. <div v-if="currentDeskStatus == 2">
  76. <el-radio-group v-model="data.userNum" size="small">
  77. <el-radio
  78. class="!mr-1 mb-1"
  79. v-for="item in currentDeskMaxNum"
  80. :key="item"
  81. :label="item"
  82. border
  83. :v-text="item"
  84. ></el-radio>
  85. </el-radio-group>
  86. <el-button class="w-full" plain @click="order()">开始点餐</el-button>
  87. </div>
  88. <el-button v-if="currentDeskStatus != 2" class="w-full mt-1" plain @click="rockover()"
  89. >翻台</el-button
  90. >
  91. </el-dialog>
  92. <el-dialog
  93. v-model="scanDialogVisible"
  94. title="请引导顾客扫码支付"
  95. width="300px"
  96. center
  97. :show-close="false"
  98. :close-on-click-modal="false"
  99. :close-on-press-escape="false"
  100. >
  101. <div class="text-center">
  102. <img
  103. src="@/assets/images/scan.gif"
  104. alt="扫码"
  105. class="mx-auto mb-4"
  106. style="width: 150px"
  107. />
  108. <b style="font-size: 20px">金额 {{ currentDeskAmount }} 元</b>
  109. </div>
  110. <template #footer>
  111. <div class="dialog-footer">
  112. <el-button @click="scanDialogVisible = false">取消支付</el-button>
  113. </div>
  114. </template>
  115. </el-dialog>
  116. <el-dialog
  117. v-model="payingDialogVisible"
  118. title="支付处理中"
  119. width="300px"
  120. :show-close="false"
  121. :close-on-click-modal="false"
  122. :close-on-press-escape="false"
  123. >
  124. <div class="flex flex-col items-center justify-center py-4">
  125. <el-icon class="is-loading mb-4" :size="32">
  126. <Loading />
  127. </el-icon>
  128. <p class="text-gray-600">正在支付中,请不要关闭或退出界面</p>
  129. </div>
  130. </el-dialog>
  131. </div>
  132. <console @init="init()" ref="consoleRef"></console>
  133. </template>
  134. <script lang="ts" setup>
  135. // 在 script setup 部分添加
  136. import { Loading } from '@element-plus/icons-vue'
  137. import { postAll } from '@/api/org/post'
  138. import {
  139. orderDeskList,
  140. createOrders,
  141. orderCheckout,
  142. orderPay,
  143. queryPayStatus,
  144. cancelOrder,
  145. rockoverDesk
  146. } from '@/api/order'
  147. import Console from './console.vue'
  148. import { getOrdersCurrent } from '@/api/orders'
  149. import feedback from '@/utils/feedback'
  150. import useUserStore from '@/stores/modules/user'
  151. const userStore = useUserStore()
  152. console.log(userStore.userInfo.id + '开始链接客户端')
  153. // const socketClient = new WebSocket('ws://localhost:8082/backSocket/' + userStore.userInfo.id)
  154. const activeName = ref<any>('all')
  155. const dialogVisible = ref(false)
  156. const isLoading = ref(false)
  157. const currentDeskStatus = ref()
  158. const currentDeskMaxNum = ref<number>()
  159. const currentDeskAmount = ref<number>()
  160. const areaList = ref<any[]>([])
  161. const deskList = ref<any[]>([])
  162. let currentDesk: any = {}
  163. let deskListAll: any[] = []
  164. const consoleRef = ref<InstanceType<typeof Console>>()
  165. const data = reactive({
  166. userNum: 1, //当前就餐人数
  167. deskId: null,
  168. type: 0, //后台点餐类型:0
  169. canScan: false
  170. })
  171. const order_ = (i: any) => {
  172. dialogVisible.value = true
  173. currentDesk = i
  174. currentDeskStatus.value = i.status
  175. data.deskId = i.id
  176. currentDeskMaxNum.value = i.num
  177. }
  178. const order = () => {
  179. dialogVisible.value = false
  180. createOrders(data).then((res) => {
  181. consoleRef.value?.open(currentDesk, data.userNum, res)
  182. init()
  183. })
  184. console.warn('***order***', currentDesk)
  185. // router.push({ path: '/order/console', query: { deskID: id } })
  186. }
  187. const rockover = () => {
  188. feedback
  189. .confirm('将重置桌号状态, 确定翻台吗?')
  190. .then(() => {
  191. rockoverDesk(data).then(() => {
  192. feedback.notifySuccess('翻台成功!')
  193. dialogVisible.value = false
  194. init()
  195. })
  196. })
  197. .catch(() => {
  198. dialogVisible.value = false
  199. })
  200. }
  201. const reorder = () => {
  202. dialogVisible.value = false
  203. consoleRef.value?.open(currentDesk) //继续下单时会把当前餐桌上的订单号ordersId传进去
  204. console.warn('***reorder***', currentDesk)
  205. }
  206. const refresh = () => {
  207. isLoading.value = true
  208. init()
  209. }
  210. const scanDialogVisible = ref(false)
  211. const scanCode = ref('')
  212. // 添加支付中对话框的控制变量
  213. const payingDialogVisible = ref(false)
  214. // 添加扫码事件监听函数
  215. const handleScanInput = (event: KeyboardEvent) => {
  216. if (event.key === 'Enter') {
  217. const code = scanCode.value
  218. console.log('扫码内容:', code)
  219. if (!data.canScan) {
  220. feedback.notifyWarning('正在处理支付,请勿重复扫码')
  221. return
  222. }
  223. data.canScan = false
  224. payingDialogVisible.value = true // 显示支付中对话框
  225. document.removeEventListener('keydown', handleScanInput)
  226. // 修改支付处理逻辑
  227. orderPay({ oid: params.number, code: code })
  228. .then((res) => {
  229. console.log(res)
  230. // 实际上支付成功之后,res返回的是[],如果不是实时成功,返回的是个对象, 形如:{orderId: '123456789', sn: '7895004131583689'}
  231. if (res == null || res.length == 0) {
  232. feedback.notifySuccess('成功结账' + orderData.payAmount + '元')
  233. scanDialogVisible.value = false
  234. payingDialogVisible.value = false // 隐藏支付中对话框
  235. showOrderConsole.value = false
  236. emit('init')
  237. } else {
  238. console.log('支付中')
  239. return queryPayStatusWithRetry(res)
  240. .then((paySuccess) => {
  241. if (paySuccess) {
  242. feedback.notifySuccess('成功结账' + orderData.payAmount + '元')
  243. scanDialogVisible.value = false
  244. payingDialogVisible.value = false // 隐藏支付中对话框
  245. showOrderConsole.value = false
  246. emit('init')
  247. }
  248. })
  249. }
  250. })
  251. .catch((e) => {
  252. console.log(e)
  253. // 支付失败
  254. payingDialogVisible.value = false // 隐藏支付中对话框
  255. feedback.confirm('支付失败,请重试').then(() => {
  256. data.canScan = true
  257. scanDialogVisible.value = true
  258. // 添加键盘事件监听
  259. document.addEventListener('keydown', handleScanInput)
  260. })
  261. })
  262. scanCode.value = ''
  263. } else {
  264. scanCode.value += event.key
  265. }
  266. }
  267. // 定义查询支付状态的函数
  268. const queryPayStatusWithRetry = (data): Promise<boolean> => {
  269. const intervals = [3000, 5000, 10000, 10000, 10000, 10000, 10000]
  270. let currentIndex = 0
  271. payingDialogVisible.value = true
  272. const tryQuery = () => {
  273. return new Promise<boolean>((resolve) => {
  274. setTimeout(() => {
  275. queryPayStatus({ orderId: data.orderId, sn: data.sn })
  276. .then((result) => {
  277. if (
  278. result.result_code == '200' &&
  279. result.biz_response.data.order_status == 'SUCCESS'
  280. ) {
  281. resolve(true)
  282. } else if (currentIndex < intervals.length - 1) {
  283. currentIndex++
  284. tryQuery().then(resolve)
  285. } else {
  286. resolve(false)
  287. }
  288. })
  289. .catch(() => {
  290. if (currentIndex < intervals.length - 1) {
  291. currentIndex++
  292. tryQuery().then(resolve)
  293. } else {
  294. resolve(false)
  295. }
  296. })
  297. }, intervals[currentIndex])
  298. })
  299. }
  300. return tryQuery().then((success) => {
  301. payingDialogVisible.value = false
  302. if (!success) {
  303. return cancelOrder({ orderId: data.orderId, sn: data.sn })
  304. .then(() => {
  305. feedback.notifyWarning('支付超时,已自动撤销本次支付')
  306. return false
  307. })
  308. .catch((error) => {
  309. console.error('撤单失败:', error)
  310. feedback.notifyError('支付失败,请重试')
  311. return false
  312. })
  313. }
  314. return success
  315. })
  316. }
  317. const checkout = () => {
  318. refresh()
  319. //结账
  320. console.warn('***结账***', currentDesk)
  321. if (currentDesk && !currentDesk.amount) {
  322. feedback.notify('当前桌未出单,无法结账')
  323. return false
  324. }
  325. orderCheckout({ did: currentDesk.id }).then((res) => {
  326. console.log('结账', res)
  327. currentDesk.oid = res.id
  328. feedback
  329. .confirm(currentDesk.name + ' 结账金额为' + currentDesk.amount + ',确定要结账吗?')
  330. .then(() => {
  331. data.canScan = true
  332. currentDeskAmount.value = currentDesk.amount
  333. scanDialogVisible.value = true
  334. // 添加键盘事件监听
  335. document.addEventListener('keydown', handleScanInput)
  336. })
  337. })
  338. }
  339. // 在组件卸载时移除事件监听
  340. onUnmounted(() => {
  341. document.removeEventListener('keydown', handleScanInput)
  342. })
  343. const tabChange = (name: any) => {
  344. if (name != 'all') {
  345. deskList.value = deskListAll.filter((ele: any) => {
  346. return ele.area == name
  347. })
  348. } else {
  349. deskList.value = deskListAll
  350. }
  351. }
  352. const init = async () => {
  353. let _list: any[] = []
  354. await orderDeskList().then((res: any) => {
  355. // deskList.value = res
  356. deskListAll = res
  357. _list = res
  358. getOrdersCurrent().then((res) => {
  359. deskList.value = _list.map((item) => {
  360. res.forEach((element: any) => {
  361. if (item.id == element.deskId) {
  362. item.status = element.status
  363. item.userNum = element.userNum
  364. // item.amount = element.amount
  365. item.ordersId = element.number
  366. if (item.amount) {
  367. item.amount += Number.parseFloat(element.amount)
  368. } else {
  369. item.amount = Number.parseFloat(element.amount)
  370. }
  371. }
  372. })
  373. return item
  374. })
  375. })
  376. })
  377. await postAll().then((res) => {
  378. areaList.value = res
  379. })
  380. // socketClient.onopen = () => {
  381. // console.log('websocket成功连接服务器')
  382. // }
  383. // socketClient.onmessage = (msg: any) => {
  384. // console.log('收到消息:' + msg.data)
  385. // const message = JSON.parse(msg.data)
  386. // }
  387. isLoading.value = false
  388. }
  389. onMounted(() => {
  390. init()
  391. })
  392. </script>
  393. <style scoped>
  394. .scan-dialog ::v-deep(.el-dialog__header) {
  395. margin-right: 0;
  396. padding: 20px;
  397. text-align: center;
  398. }
  399. .scan-dialog ::v-deep(.el-dialog__title) {
  400. font-size: 16px;
  401. font-weight: 500;
  402. }
  403. </style>