list.vue 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572
  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 == 8
  185. ? '已核销'
  186. : '未知'
  187. }}
  188. <div v-if="row.refundStatus == 'SUCCESS'" class="refund-info">
  189. <div>退款时间:{{ row.refundTime }}</div>
  190. <div>退款金额:{{ row.refundAmount }}元</div>
  191. </div>
  192. </div>
  193. </template>
  194. </el-table-column>
  195. <el-table-column label="操作" width="60" fixed="right">
  196. <template #default="{ row }">
  197. <!-- <el-button type="primary" link @click="showDetail(row)">查看已点菜品</el-button>-->
  198. <el-button
  199. v-if="row.status == '2' && !row.refundAmount"
  200. type="danger"
  201. link
  202. @click="refund(row)"
  203. >退款</el-button
  204. >
  205. </template>
  206. </el-table-column>
  207. </el-table>
  208. <div class="flex justify-end mt-4">
  209. <pagination v-model="pager" @change="getLists" />
  210. </div>
  211. </el-card>
  212. <el-dialog
  213. v-model="refundDialogVisible"
  214. title="退款"
  215. width="400px"
  216. @close="refundDialogVisible = false"
  217. >
  218. <div class="refund-dialog-content">
  219. <el-input v-model="refundAmount" placeholder="请输入退款金额" type="number">
  220. <template #prepend><icon :size="25" name="el-icon-money" /></template>
  221. <template #append>元</template>
  222. </el-input>
  223. <div v-if="payMethod === 0" class="payment-description">
  224. 请现金退款,本操作只做记录,不做真正的退款动作
  225. </div>
  226. </div>
  227. <template #footer>
  228. <span class="dialog-footer">
  229. <el-button @click="refundDialogVisible = false">取 消</el-button>
  230. <el-button type="primary" :loading="refundLoading" @click="confirmRefund"
  231. >确 定</el-button
  232. >
  233. </span>
  234. </template>
  235. </el-dialog>
  236. <!-- 在 el-card 之后添加 loading dialog -->
  237. <el-dialog
  238. v-model="downloadLoading"
  239. :show-close="false"
  240. :close-on-click-modal="false"
  241. :close-on-press-escape="false"
  242. width="300px"
  243. class="download-dialog"
  244. >
  245. <div class="download-loading-content">
  246. <el-icon class="is-loading" color="#409EFF" size="40">
  247. <Loading />
  248. </el-icon>
  249. <div class="loading-text">下载中...</div>
  250. <div class="loading-subtext">正在生成文件,请稍候</div>
  251. </div>
  252. </el-dialog>
  253. </div>
  254. </template>
  255. <script setup lang="ts">
  256. import { ordersList, getOrderDetail, refundReq, downloadOrders } from '@/api/orders'
  257. import { usePaging } from '@/hooks/usePaging'
  258. import { timeFormat } from '@/utils/util'
  259. import feedback from '@/utils/feedback'
  260. // import Money from '@/utils/money'
  261. // 在其他 import 语句后添加 Loading 组件导入
  262. import { Loading } from '@element-plus/icons-vue'
  263. const downloadLoading = ref(false)
  264. const createTime = ref()
  265. const checkoutTime = ref()
  266. const queryParams = reactive({
  267. type: '',
  268. status: '',
  269. checkoutTime: '',
  270. createTime: ''
  271. })
  272. const { pager, getLists, resetPage } = usePaging({
  273. fetchFun: ordersList,
  274. params: queryParams
  275. })
  276. const resetParams = () => {
  277. queryParams.type = ''
  278. queryParams.status = ''
  279. checkoutTime.value = []
  280. queryParams.checkoutTime = ''
  281. getLists()
  282. }
  283. const setCreateTime = (v: any) => {
  284. queryParams.createTime =
  285. Math.round(v[0].getTime() / 1000).toString() +
  286. ',' +
  287. Math.round(v[1].getTime() / 1000).toString()
  288. }
  289. const setCheckoutTime = (v: any) => {
  290. queryParams.checkoutTime = ''
  291. if (v && v.length === 2) {
  292. // 开始时间保持不变
  293. const startTime = Math.round(v[0].getTime() / 1000).toString()
  294. // 结束时间设置为当天的23:59:59
  295. const endDate = new Date(v[1])
  296. endDate.setHours(23, 59, 59, 999)
  297. console.log('End Date:', endDate) // 调试输出
  298. const endTime = Math.round(endDate.getTime() / 1000).toString()
  299. queryParams.checkoutTime = startTime + ',' + endTime
  300. }
  301. }
  302. // watch(queryParams, (newV, oldV) => {
  303. // console.log(JSON.stringify(newV))
  304. // })
  305. const expandRowKeys = ref<string[]>([])
  306. const orderDetails = ref<Record<string, any>>({})
  307. const handleExpandChange = (row: any, expandedRows: any) => {
  308. // console.log('当前行展开状态改变:', row, expandedRows);
  309. if (!orderDetails.value[row.id]) {
  310. showDetail(row)
  311. }
  312. }
  313. // 添加这些变量到组件顶层
  314. const refundDialogVisible = ref(false)
  315. const refundAmount = ref('')
  316. const payMethod = ref('')
  317. let refundOrder: {
  318. refundTime: string
  319. refundAmount: number
  320. payAmount: number
  321. amount: number
  322. status: number
  323. id: any
  324. }
  325. // 添加loading状态变量
  326. const refundLoading = ref(false)
  327. // 修改refund函数
  328. const refund = async (row: any) => {
  329. refundOrder = row
  330. payMethod.value = row.payMethod
  331. refundAmount.value = ''
  332. refundDialogVisible.value = true
  333. }
  334. const confirmRefund = async () => {
  335. try {
  336. if (!refundAmount.value || refundAmount.value <= 0) {
  337. return feedback.msgError('退款金额不合法')
  338. }
  339. if (refundAmount.value > (refundOrder.payAmount || refundOrder.amount)) {
  340. return feedback.msgError('退款金额不能大于订单实付金额')
  341. }
  342. refundLoading.value = true
  343. const res = await refundReq({ orderId: refundOrder.id, payAmount: refundAmount.value })
  344. if (res) {
  345. feedback.msgSuccess('退款成功')
  346. // refundOrder.status = 5;
  347. // refundOrder.refundAmount = refundAmount.value;
  348. // refundOrder.refundTime = "待处理";
  349. setTimeout(() => {
  350. getLists()
  351. }, 200)
  352. } else {
  353. feedback.msgError('退款失败')
  354. }
  355. } catch (error) {
  356. console.error('退款失败:', error)
  357. } finally {
  358. refundLoading.value = false
  359. refundDialogVisible.value = false
  360. }
  361. }
  362. // 展示订单详情
  363. const showDetail = async (row: any) => {
  364. try {
  365. // 如果已经加载过该订单详情,直接展开
  366. if (orderDetails.value[row.id]) {
  367. expandRowKeys.value = [row.id]
  368. return
  369. }
  370. // 调用获取订单详情接口
  371. const res = await getOrderDetail({ id: row.id })
  372. console.warn('***getOrderDetail***', res)
  373. if (res) {
  374. // 定义正确的类型
  375. const orderDetail: Record<string, any> = {}
  376. orderDetail.id = res.orderItem.id
  377. orderDetail.amount = res.orderItem.amount //总价
  378. orderDetail.createTime = res.orderItem.createTime
  379. orderDetail.dishes = res.orderDishList
  380. // 保存订单详情
  381. orderDetails.value[row.id] = orderDetail
  382. // 展开当前行
  383. expandRowKeys.value = [row.id]
  384. } else {
  385. feedback.msgError('获取订单详情失败')
  386. }
  387. } catch (error) {
  388. console.error('获取订单详情失败:', error)
  389. feedback.msgError('获取订单详情失败')
  390. }
  391. }
  392. // 计算商品总数
  393. const getTotalCount = (dishes: any[]) => {
  394. return dishes.reduce((total, dish) => total + dish.number, 0)
  395. }
  396. // 计算订单总价
  397. // const getTotalPrice = (dishes: any[]) => {
  398. // return dishes.reduce((total, dish) => {
  399. // return Money.add(total, Money.multiply(dish.price, dish.count))
  400. // }, 0)
  401. // }
  402. const download = async () => {
  403. try {
  404. downloadLoading.value = true
  405. const res = await downloadOrders(queryParams)
  406. console.warn('***downloadOrders***', res)
  407. if (res) {
  408. // 生成带当前时间的文件名
  409. const timestamp = new Date()
  410. const year = timestamp.getFullYear()
  411. const month = String(timestamp.getMonth() + 1).padStart(2, '0')
  412. const day = String(timestamp.getDate()).padStart(2, '0')
  413. const hours = String(timestamp.getHours()).padStart(2, '0')
  414. const minutes = String(timestamp.getMinutes()).padStart(2, '0')
  415. const seconds = String(timestamp.getSeconds()).padStart(2, '0')
  416. const filename = `订单列表_${year}${month}${day}_${hours}${minutes}${seconds}.xlsx`
  417. downloadFile(res)
  418. }
  419. } catch (error) {
  420. console.error('下载失败:', error)
  421. feedback.msgError('下载失败')
  422. } finally {
  423. downloadLoading.value = false
  424. }
  425. }
  426. const downloadFile = (url: string) => {
  427. const link = document.createElement('a')
  428. link.href = url
  429. document.body.appendChild(link)
  430. link.click()
  431. document.body.removeChild(link)
  432. }
  433. getLists()
  434. </script>
  435. <style lang="scss" scoped>
  436. /* 在样式部分添加下载对话框的样式*/
  437. .download-dialog {
  438. :deep(.el-dialog__header) {
  439. display: none;
  440. }
  441. :deep(.el-dialog__body) {
  442. padding: 30px 20px;
  443. }
  444. }
  445. .download-loading-content {
  446. text-align: center;
  447. .loading-text {
  448. margin-top: 15px;
  449. font-size: 16px;
  450. color: #333;
  451. font-weight: 500;
  452. }
  453. .loading-subtext {
  454. margin-top: 8px;
  455. font-size: 12px;
  456. color: #999;
  457. }
  458. }
  459. .order-detail-wrapper {
  460. padding: 20px;
  461. background: #f8f8f8;
  462. .detail-header {
  463. margin-bottom: 20px;
  464. display: flex;
  465. justify-content: space-between;
  466. align-items: center;
  467. span {
  468. font-size: 14px;
  469. color: #333;
  470. &.time {
  471. color: #999;
  472. }
  473. }
  474. }
  475. .detail-table {
  476. margin-bottom: 20px;
  477. :deep(.dish-image) {
  478. width: 60px;
  479. height: 60px;
  480. border-radius: 4px;
  481. }
  482. }
  483. .detail-footer {
  484. display: flex;
  485. justify-content: flex-end;
  486. .total-info {
  487. text-align: right;
  488. span {
  489. margin-left: 20px;
  490. font-size: 14px;
  491. color: #666;
  492. &.total-price {
  493. em {
  494. font-style: normal;
  495. font-size: 16px;
  496. color: #f56c6c;
  497. font-weight: bold;
  498. }
  499. }
  500. }
  501. }
  502. }
  503. }
  504. .refund-dialog-content {
  505. padding: 20px 0px;
  506. :deep(.el-input) {
  507. width: 100%;
  508. }
  509. }
  510. .dialog-footer {
  511. text-align: right;
  512. .el-button + .el-button {
  513. margin-left: 12px;
  514. }
  515. }
  516. .refund-info {
  517. font-size: 12px;
  518. color: #999;
  519. margin-top: 4px;
  520. }
  521. .remark-text {
  522. display: -webkit-box;
  523. -webkit-box-orient: vertical;
  524. -webkit-line-clamp: 2;
  525. overflow: hidden;
  526. text-overflow: ellipsis;
  527. word-break: break-all;
  528. line-height: 1.5;
  529. max-height: 3em; // 2行的高度
  530. }
  531. .payment-description {
  532. margin-top: 20px;
  533. padding: 10px;
  534. background-color: #f5f5f5;
  535. border-radius: 4px;
  536. text-align: center;
  537. color: #666;
  538. font-size: 14px;
  539. }
  540. </style>