list.vue 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581
  1. <template>
  2. <div class="orders-lists">
  3. <el-card class="!border-none" shadow="never">
  4. <el-form :inline="true" class="mb-[-16px]">
  5. <el-form-item label="订单类型" class="w-[280px]">
  6. <el-select class="w-[280px]" v-model="queryParams.type">
  7. <el-option label="全部" value />
  8. <el-option label="吧台点单" value="0" />
  9. <el-option label="扫码点单" value="1" />
  10. <el-option label="商城订单" value="2" />
  11. </el-select>
  12. </el-form-item>
  13. <el-form-item label="订单状态" class="w-[280px]">
  14. <el-select class="w-[280px]" v-model="queryParams.status">
  15. <el-option label="全部" value />
  16. <!-- <el-option label="待下单" value="0" />-->
  17. <!-- <el-option label="待结帐" value="1" />-->
  18. <el-option label="已下单" value="2" />
  19. <el-option label="已退款" value="4" />
  20. <el-option label="已核销" value="8" />
  21. </el-select>
  22. </el-form-item>
  23. <!-- <el-form-item label="创建时间" class="w-[280px]">-->
  24. <!-- <el-date-picker v-model="createTime" @change="setCreateTime" type="datetimerange"-->
  25. <!-- range-separator="-" start-placeholder="开始时间" end-placeholder="结束时间" />-->
  26. <!-- </el-form-item>-->
  27. <el-form-item label="下单日期" class="w-[340px]">
  28. <el-date-picker
  29. v-model="checkoutTime"
  30. @change="setCheckoutTime"
  31. type="daterange"
  32. range-separator="-"
  33. start-placeholder="开始日期"
  34. end-placeholder="结束日期"
  35. />
  36. </el-form-item>
  37. <el-form-item class="w-[280px]">
  38. <el-button type="primary" @click="resetPage">查询</el-button>
  39. <el-button @click="resetParams">重置</el-button>
  40. <el-button type="success" @click="download">导出</el-button>
  41. </el-form-item>
  42. </el-form>
  43. </el-card>
  44. <el-card class="!border-none mt-4" shadow="never">
  45. <!-- :expand-row-keys="expandRowKeys" @expand-change="handleExpandChange"-->
  46. <el-table v-loading="pager.loading" :data="pager.lists" row-key="id">
  47. <!-- <el-table-column type="expand" >-->
  48. <!-- <template #default="{ row }">-->
  49. <!-- <div class="order-detail-wrapper" v-loading="!orderDetails[row.id]">-->
  50. <!-- <template v-if="orderDetails[row.id]">-->
  51. <!-- <div class="detail-header">-->
  52. <!-- <span>订单详情</span>-->
  53. <!-- <span class="time">下单时间:{{ timeFormat(orderDetails[row.id].createTime) }}</span>-->
  54. <!-- </div>-->
  55. <!-- <el-table :data="orderDetails[row.id].dishes" border class="detail-table">-->
  56. <!-- <el-table-column label="菜品图片" width="120" align="center">-->
  57. <!-- <template #default="{ row }">-->
  58. <!-- <el-image :src="row.image" :preview-src-list="[row.image]"-->
  59. <!-- class="dish-image" fit="cover" />-->
  60. <!-- </template>-->
  61. <!-- </el-table-column>-->
  62. <!-- <el-table-column prop="name" label="菜品名称">-->
  63. <!-- <template #default="{ row }">-->
  64. <!-- <div>-->
  65. <!-- {{ row.name }}-->
  66. <!-- <div v-if="row.specsList && row.specsList.length" class="mt-1">-->
  67. <!-- <el-tag-->
  68. <!-- v-for="spec in row.specsList"-->
  69. <!-- :key="spec.id"-->
  70. <!-- size="small"-->
  71. <!-- class="mr-1 mb-1"-->
  72. <!-- >-->
  73. <!-- {{ spec.name }}: {{ spec.value }}-->
  74. <!-- </el-tag>-->
  75. <!-- </div>-->
  76. <!-- </div>-->
  77. <!-- </template>-->
  78. <!-- </el-table-column>-->
  79. <!-- <el-table-column prop="amount" label="单价">-->
  80. <!-- <template #default="{ row }">-->
  81. <!-- ¥{{ row.amount }}-->
  82. <!-- </template>-->
  83. <!-- </el-table-column>-->
  84. <!-- <el-table-column prop="number" label="数量" width="120" align="center" />-->
  85. <!-- <el-table-column label="小计" width="120" align="right">-->
  86. <!-- <template #default="{ row }">-->
  87. <!-- ¥{{ row.amount * row.number}}-->
  88. <!-- </template>-->
  89. <!-- </el-table-column>-->
  90. <!-- </el-table>-->
  91. <!-- <div class="detail-footer">-->
  92. <!-- <div class="total-info">-->
  93. <!-- <span>共 {{ getTotalCount(orderDetails[row.id].dishes) }} 件商品</span>-->
  94. <!-- <span class="total-price">-->
  95. <!-- 订单总价:<em>¥{{ orderDetails[row.id].amount}}</em>-->
  96. <!-- </span>-->
  97. <!-- </div>-->
  98. <!-- </div>-->
  99. <!-- </template>-->
  100. <!-- </div>-->
  101. <!-- </template>-->
  102. <!-- </el-table-column>-->
  103. <el-table-column type="index" min-width="80" />
  104. <el-table-column
  105. label="下单时间"
  106. prop="checkoutTime"
  107. min-width="130"
  108. ></el-table-column>
  109. <el-table-column
  110. label="订单号"
  111. prop="number"
  112. min-width="150"
  113. show-overflow-tooltip
  114. ></el-table-column>
  115. <el-table-column
  116. label="类型"
  117. prop="type"
  118. :formatter="(row: any) => (row.type == 0 ? '吧台点单' : row.type == 1 ? '扫码点单' : '在线购物')"
  119. ></el-table-column>
  120. <el-table-column label="桌号/餐号" prop="deskName">
  121. <template #default="{ row }">{{
  122. row.deskName ? row.deskName : row.mealCode
  123. }}</template>
  124. </el-table-column>
  125. <el-table-column label="产品清单" min-width="120">
  126. <template #default="{ row }">
  127. <div>
  128. <div v-for="orderDish in row.orderDishes" :key="orderDish.id">
  129. {{ orderDish.name }}<span style="margin: 0 15px 0 15px">x</span
  130. >{{ orderDish.number }}
  131. <div v-if="orderDish.specsList && orderDish.specsList.length > 0">
  132. <div
  133. v-for="spec in orderDish.specsList"
  134. :key="spec.id"
  135. class="refund-info"
  136. style="margin-left: 20px; margin-top: 0"
  137. >
  138. <span v-if="spec.name && spec.value"
  139. >- {{ spec.name }}: {{ spec.value }}</span
  140. >
  141. </div>
  142. </div>
  143. </div>
  144. </div>
  145. </template>
  146. </el-table-column>
  147. <el-table-column
  148. label="订单金额(元)"
  149. prop="amount"
  150. min-width="100"
  151. ></el-table-column>
  152. <el-table-column
  153. label="优惠金额(元)"
  154. prop="ticketAmount"
  155. min-width="100"
  156. ></el-table-column>
  157. <el-table-column
  158. label="实付金额(元)"
  159. prop="payAmount"
  160. min-width="100"
  161. ></el-table-column>
  162. <el-table-column
  163. label="备注"
  164. prop="remark"
  165. min-width="100"
  166. show-tooltip-when-overflow
  167. ></el-table-column>
  168. <el-table-column label="状态" min-width="160" prop="status">
  169. <template #default="{ row }">
  170. <div>
  171. {{
  172. row.status == 4
  173. ? '已退款'
  174. : row.status == 2
  175. ? '已下单'
  176. : row.status == 0
  177. ? '待下单'
  178. : row.status == 1
  179. ? '待支付'
  180. : row.status == 5
  181. ? '退款中'
  182. : row.status == 3
  183. ? '支付失败'
  184. : row.status == 6
  185. ? '已关闭'
  186. : row.status == 7
  187. ? '部分核销'
  188. : row.status == 8
  189. ? '已核销'
  190. : '未知'
  191. }}
  192. <div v-if="row.refundStatus == 'SUCCESS'" class="refund-info">
  193. <div>退款时间:{{ row.refundTime }}</div>
  194. <div>退款金额:{{ row.refundAmount }}元</div>
  195. </div>
  196. </div>
  197. </template>
  198. </el-table-column>
  199. <el-table-column label="操作" width="60" fixed="right">
  200. <template #default="{ row }">
  201. <!-- <el-button type="primary" link @click="showDetail(row)">查看已点菜品</el-button>-->
  202. <el-button
  203. v-if="row.status == '2' && !row.refundAmount"
  204. type="danger"
  205. link
  206. @click="refund(row)"
  207. >退款</el-button
  208. >
  209. </template>
  210. </el-table-column>
  211. </el-table>
  212. <div class="flex justify-end mt-4">
  213. <pagination v-model="pager" @change="getLists" />
  214. </div>
  215. </el-card>
  216. <el-dialog
  217. v-model="refundDialogVisible"
  218. title="退款"
  219. width="400px"
  220. @close="refundDialogVisible = false"
  221. >
  222. <div class="refund-dialog-content">
  223. <el-input v-model="refundAmount" placeholder="请输入退款金额" type="number" :disabled="refundAmountDisabled">
  224. <template #prepend><icon :size="25" name="el-icon-money" /></template>
  225. <template #append>元</template>
  226. </el-input>
  227. <div v-if="payMethod === 0" class="payment-description">
  228. 请现金退款,本操作只做记录,不做真正的退款动作
  229. </div>
  230. </div>
  231. <template #footer>
  232. <span class="dialog-footer">
  233. <el-button @click="refundDialogVisible = false">取 消</el-button>
  234. <el-button type="primary" :loading="refundLoading" @click="confirmRefund"
  235. >确 定</el-button
  236. >
  237. </span>
  238. </template>
  239. </el-dialog>
  240. <!-- 在 el-card 之后添加 loading dialog -->
  241. <el-dialog
  242. v-model="downloadLoading"
  243. :show-close="false"
  244. :close-on-click-modal="false"
  245. :close-on-press-escape="false"
  246. width="300px"
  247. class="download-dialog"
  248. >
  249. <div class="download-loading-content">
  250. <el-icon class="is-loading" color="#409EFF" size="40">
  251. <Loading />
  252. </el-icon>
  253. <div class="loading-text">下载中...</div>
  254. <div class="loading-subtext">正在生成文件,请稍候</div>
  255. </div>
  256. </el-dialog>
  257. </div>
  258. </template>
  259. <script setup lang="ts">
  260. import { ordersList, getOrderDetail, refundReq, downloadOrders } from '@/api/orders'
  261. import { usePaging } from '@/hooks/usePaging'
  262. import { timeFormat } from '@/utils/util'
  263. import feedback from '@/utils/feedback'
  264. // import Money from '@/utils/money'
  265. // 在其他 import 语句后添加 Loading 组件导入
  266. import { Loading } from '@element-plus/icons-vue'
  267. const downloadLoading = ref(false)
  268. const createTime = ref()
  269. const checkoutTime = ref()
  270. const queryParams = reactive({
  271. type: '',
  272. status: '',
  273. checkoutTime: '',
  274. createTime: ''
  275. })
  276. const { pager, getLists, resetPage } = usePaging({
  277. fetchFun: ordersList,
  278. params: queryParams
  279. })
  280. const resetParams = () => {
  281. queryParams.type = ''
  282. queryParams.status = ''
  283. checkoutTime.value = []
  284. queryParams.checkoutTime = ''
  285. getLists()
  286. }
  287. const setCreateTime = (v: any) => {
  288. queryParams.createTime =
  289. Math.round(v[0].getTime() / 1000).toString() +
  290. ',' +
  291. Math.round(v[1].getTime() / 1000).toString()
  292. }
  293. const setCheckoutTime = (v: any) => {
  294. queryParams.checkoutTime = ''
  295. if (v && v.length === 2) {
  296. // 开始时间保持不变
  297. const startTime = Math.round(v[0].getTime() / 1000).toString()
  298. // 结束时间设置为当天的23:59:59
  299. const endDate = new Date(v[1])
  300. endDate.setHours(23, 59, 59, 999)
  301. console.log('End Date:', endDate) // 调试输出
  302. const endTime = Math.round(endDate.getTime() / 1000).toString()
  303. queryParams.checkoutTime = startTime + ',' + endTime
  304. }
  305. }
  306. // watch(queryParams, (newV, oldV) => {
  307. // console.log(JSON.stringify(newV))
  308. // })
  309. const expandRowKeys = ref<string[]>([])
  310. const orderDetails = ref<Record<string, any>>({})
  311. const handleExpandChange = (row: any, expandedRows: any) => {
  312. // console.log('当前行展开状态改变:', row, expandedRows);
  313. if (!orderDetails.value[row.id]) {
  314. showDetail(row)
  315. }
  316. }
  317. // 添加这些变量到组件顶层
  318. const refundDialogVisible = ref(false)
  319. const refundAmount = ref('')
  320. const payMethod = ref('')
  321. const refundAmountDisabled = ref(true)
  322. let refundOrder: {
  323. refundTime: string
  324. refundAmount: number
  325. payAmount: number
  326. amount: number
  327. status: number
  328. id: any
  329. }
  330. // 添加loading状态变量
  331. const refundLoading = ref(false)
  332. // 修改refund函数
  333. const refund = async (row: any) => {
  334. refundOrder = row
  335. payMethod.value = row.payMethod
  336. refundAmountDisabled.value = row.type === 2
  337. refundAmount.value = ''
  338. if (row.type === 2) {
  339. refundAmount.value = row.payAmount
  340. }
  341. refundDialogVisible.value = true
  342. }
  343. const confirmRefund = async () => {
  344. try {
  345. if (!refundAmount.value || refundAmount.value <= 0) {
  346. return feedback.msgError('退款金额不合法')
  347. }
  348. if (refundAmount.value > (refundOrder.payAmount || refundOrder.amount)) {
  349. return feedback.msgError('退款金额不能大于订单实付金额')
  350. }
  351. refundLoading.value = true
  352. const res = await refundReq({ orderId: refundOrder.id, payAmount: refundAmount.value })
  353. if (res) {
  354. feedback.msgSuccess('退款成功')
  355. // refundOrder.status = 5;
  356. // refundOrder.refundAmount = refundAmount.value;
  357. // refundOrder.refundTime = "待处理";
  358. setTimeout(() => {
  359. getLists()
  360. }, 200)
  361. } else {
  362. feedback.msgError('退款失败')
  363. }
  364. } catch (error) {
  365. console.error('退款失败:', error)
  366. } finally {
  367. refundLoading.value = false
  368. refundDialogVisible.value = false
  369. }
  370. }
  371. // 展示订单详情
  372. const showDetail = async (row: any) => {
  373. try {
  374. // 如果已经加载过该订单详情,直接展开
  375. if (orderDetails.value[row.id]) {
  376. expandRowKeys.value = [row.id]
  377. return
  378. }
  379. // 调用获取订单详情接口
  380. const res = await getOrderDetail({ id: row.id })
  381. console.warn('***getOrderDetail***', res)
  382. if (res) {
  383. // 定义正确的类型
  384. const orderDetail: Record<string, any> = {}
  385. orderDetail.id = res.orderItem.id
  386. orderDetail.amount = res.orderItem.amount //总价
  387. orderDetail.createTime = res.orderItem.createTime
  388. orderDetail.dishes = res.orderDishList
  389. // 保存订单详情
  390. orderDetails.value[row.id] = orderDetail
  391. // 展开当前行
  392. expandRowKeys.value = [row.id]
  393. } else {
  394. feedback.msgError('获取订单详情失败')
  395. }
  396. } catch (error) {
  397. console.error('获取订单详情失败:', error)
  398. feedback.msgError('获取订单详情失败')
  399. }
  400. }
  401. // 计算商品总数
  402. const getTotalCount = (dishes: any[]) => {
  403. return dishes.reduce((total, dish) => total + dish.number, 0)
  404. }
  405. // 计算订单总价
  406. // const getTotalPrice = (dishes: any[]) => {
  407. // return dishes.reduce((total, dish) => {
  408. // return Money.add(total, Money.multiply(dish.price, dish.count))
  409. // }, 0)
  410. // }
  411. const download = async () => {
  412. try {
  413. downloadLoading.value = true
  414. const res = await downloadOrders(queryParams)
  415. console.warn('***downloadOrders***', res)
  416. if (res) {
  417. // 生成带当前时间的文件名
  418. const timestamp = new Date()
  419. const year = timestamp.getFullYear()
  420. const month = String(timestamp.getMonth() + 1).padStart(2, '0')
  421. const day = String(timestamp.getDate()).padStart(2, '0')
  422. const hours = String(timestamp.getHours()).padStart(2, '0')
  423. const minutes = String(timestamp.getMinutes()).padStart(2, '0')
  424. const seconds = String(timestamp.getSeconds()).padStart(2, '0')
  425. const filename = `订单列表_${year}${month}${day}_${hours}${minutes}${seconds}.xlsx`
  426. downloadFile(res)
  427. }
  428. } catch (error) {
  429. console.error('下载失败:', error)
  430. feedback.msgError('下载失败')
  431. } finally {
  432. downloadLoading.value = false
  433. }
  434. }
  435. const downloadFile = (url: string) => {
  436. const link = document.createElement('a')
  437. link.href = url
  438. document.body.appendChild(link)
  439. link.click()
  440. document.body.removeChild(link)
  441. }
  442. getLists()
  443. </script>
  444. <style lang="scss" scoped>
  445. /* 在样式部分添加下载对话框的样式*/
  446. .download-dialog {
  447. :deep(.el-dialog__header) {
  448. display: none;
  449. }
  450. :deep(.el-dialog__body) {
  451. padding: 30px 20px;
  452. }
  453. }
  454. .download-loading-content {
  455. text-align: center;
  456. .loading-text {
  457. margin-top: 15px;
  458. font-size: 16px;
  459. color: #333;
  460. font-weight: 500;
  461. }
  462. .loading-subtext {
  463. margin-top: 8px;
  464. font-size: 12px;
  465. color: #999;
  466. }
  467. }
  468. .order-detail-wrapper {
  469. padding: 20px;
  470. background: #f8f8f8;
  471. .detail-header {
  472. margin-bottom: 20px;
  473. display: flex;
  474. justify-content: space-between;
  475. align-items: center;
  476. span {
  477. font-size: 14px;
  478. color: #333;
  479. &.time {
  480. color: #999;
  481. }
  482. }
  483. }
  484. .detail-table {
  485. margin-bottom: 20px;
  486. :deep(.dish-image) {
  487. width: 60px;
  488. height: 60px;
  489. border-radius: 4px;
  490. }
  491. }
  492. .detail-footer {
  493. display: flex;
  494. justify-content: flex-end;
  495. .total-info {
  496. text-align: right;
  497. span {
  498. margin-left: 20px;
  499. font-size: 14px;
  500. color: #666;
  501. &.total-price {
  502. em {
  503. font-style: normal;
  504. font-size: 16px;
  505. color: #f56c6c;
  506. font-weight: bold;
  507. }
  508. }
  509. }
  510. }
  511. }
  512. }
  513. .refund-dialog-content {
  514. padding: 20px 0px;
  515. :deep(.el-input) {
  516. width: 100%;
  517. }
  518. }
  519. .dialog-footer {
  520. text-align: right;
  521. .el-button + .el-button {
  522. margin-left: 12px;
  523. }
  524. }
  525. .refund-info {
  526. font-size: 12px;
  527. color: #999;
  528. margin-top: 4px;
  529. }
  530. .remark-text {
  531. display: -webkit-box;
  532. -webkit-box-orient: vertical;
  533. -webkit-line-clamp: 2;
  534. overflow: hidden;
  535. text-overflow: ellipsis;
  536. word-break: break-all;
  537. line-height: 1.5;
  538. max-height: 3em; // 2行的高度
  539. }
  540. .payment-description {
  541. margin-top: 20px;
  542. padding: 10px;
  543. background-color: #f5f5f5;
  544. border-radius: 4px;
  545. text-align: center;
  546. color: #666;
  547. font-size: 14px;
  548. }
  549. </style>