list.vue 23 KB

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