list.vue 20 KB

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