list.vue 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484
  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-[440px]">
  26. <el-date-picker v-model="checkoutTime" @change="setCheckoutTime" type="datetimerange"
  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">
  110. <div v-for="spec in orderDish.specsList" :key="spec.id" class="refund-info" style="margin-left: 20px;margin-top: 0;">
  111. - {{ spec.name }}: {{ spec.value }}
  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.length > 0) {
  222. queryParams.checkoutTime =
  223. Math.round(v[0].getTime() / 1000).toString() +
  224. ',' +
  225. Math.round(v[1].getTime() / 1000).toString()
  226. }
  227. }
  228. // watch(queryParams, (newV, oldV) => {
  229. // console.log(JSON.stringify(newV))
  230. // })
  231. const expandRowKeys = ref<string[]>([])
  232. const orderDetails = ref<Record<string, any>>({})
  233. const handleExpandChange = (row: any, expandedRows: any) => {
  234. // console.log('当前行展开状态改变:', row, expandedRows);
  235. if (!orderDetails.value[row.id]) {
  236. showDetail(row);
  237. }
  238. }
  239. // 添加这些变量到组件顶层
  240. const refundDialogVisible = ref(false)
  241. const refundAmount = ref('')
  242. var refundOrder: {
  243. refundTime: string;
  244. refundAmount: number;
  245. payAmount: number;
  246. amount: number;
  247. status: number; id: any;
  248. };
  249. // 添加loading状态变量
  250. const refundLoading = ref(false)
  251. // 修改refund函数
  252. const refund = async (row: any) => {
  253. refundDialogVisible.value = true
  254. refundAmount.value = ''
  255. refundOrder = row;
  256. }
  257. const confirmRefund = async () => {
  258. try {
  259. if(!refundAmount.value || refundAmount.value<=0){
  260. return feedback.msgError('退款金额不合法')
  261. }
  262. if(refundAmount.value > (refundOrder.payAmount || refundOrder.amount)){
  263. return feedback.msgError('退款金额不能大于订单实付金额')
  264. }
  265. refundLoading.value = true
  266. const res = await refundReq({ orderId: refundOrder.id, payAmount: refundAmount.value })
  267. if (res) {
  268. feedback.msgSuccess('退款成功')
  269. refundOrder.status = 5;
  270. // refundOrder.refundAmount = refundAmount.value;
  271. // refundOrder.refundTime = "待处理";
  272. setTimeout(()=>{
  273. getLists();
  274. },200)
  275. } else {
  276. feedback.msgError('退款失败')
  277. }
  278. } catch (error) {
  279. console.error('退款失败:', error)
  280. } finally {
  281. refundLoading.value = false
  282. refundDialogVisible.value = false
  283. }
  284. }
  285. // 展示订单详情
  286. const showDetail = async (row : any) => {
  287. try {
  288. // 如果已经加载过该订单详情,直接展开
  289. if (orderDetails.value[row.id]) {
  290. expandRowKeys.value = [row.id]
  291. return
  292. }
  293. // 调用获取订单详情接口
  294. const res = await getOrderDetail({id:row.id})
  295. console.warn("***getOrderDetail***",res)
  296. if (res) {
  297. // 定义正确的类型
  298. let orderDetail: Record<string, any> = {};
  299. orderDetail.id = res.orderItem.id;
  300. orderDetail.amount = res.orderItem.amount;//总价
  301. orderDetail.createTime = res.orderItem.createTime;
  302. orderDetail.dishes = res.orderDishList;
  303. // 保存订单详情
  304. orderDetails.value[row.id] = orderDetail
  305. // 展开当前行
  306. expandRowKeys.value = [row.id]
  307. } else {
  308. feedback.msgError('获取订单详情失败')
  309. }
  310. } catch (error) {
  311. console.error('获取订单详情失败:', error)
  312. feedback.msgError('获取订单详情失败')
  313. }
  314. }
  315. // 计算商品总数
  316. const getTotalCount = (dishes : any[]) => {
  317. return dishes.reduce((total, dish) => total + dish.number, 0)
  318. }
  319. // 计算订单总价
  320. // const getTotalPrice = (dishes: any[]) => {
  321. // return dishes.reduce((total, dish) => {
  322. // return Money.add(total, Money.multiply(dish.price, dish.count))
  323. // }, 0)
  324. // }
  325. const download = async () => {
  326. try {
  327. downloadLoading.value = true
  328. const res = await downloadOrders(queryParams)
  329. console.warn("***downloadOrders***",res)
  330. if (res) {
  331. // 生成带当前时间的文件名
  332. const timestamp = new Date()
  333. const year = timestamp.getFullYear()
  334. const month = String(timestamp.getMonth() + 1).padStart(2, '0')
  335. const day = String(timestamp.getDate()).padStart(2, '0')
  336. const hours = String(timestamp.getHours()).padStart(2, '0')
  337. const minutes = String(timestamp.getMinutes()).padStart(2, '0')
  338. const seconds = String(timestamp.getSeconds()).padStart(2, '0')
  339. const filename = `订单列表_${year}${month}${day}_${hours}${minutes}${seconds}.xlsx`
  340. downloadFile(res)
  341. }
  342. } catch (error) {
  343. console.error('下载失败:', error)
  344. feedback.msgError('下载失败')
  345. } finally {
  346. downloadLoading.value = false
  347. }
  348. }
  349. const downloadFile = (url: string) => {
  350. const link = document.createElement('a');
  351. link.href = url;
  352. document.body.appendChild(link);
  353. link.click();
  354. document.body.removeChild(link);
  355. };
  356. getLists()
  357. </script>
  358. <style lang="scss" scoped>
  359. /* 在样式部分添加下载对话框的样式*/
  360. .download-dialog {
  361. :deep(.el-dialog__header) {
  362. display: none;
  363. }
  364. :deep(.el-dialog__body) {
  365. padding: 30px 20px;
  366. }
  367. }
  368. .download-loading-content {
  369. text-align: center;
  370. .loading-text {
  371. margin-top: 15px;
  372. font-size: 16px;
  373. color: #333;
  374. font-weight: 500;
  375. }
  376. .loading-subtext {
  377. margin-top: 8px;
  378. font-size: 12px;
  379. color: #999;
  380. }
  381. }
  382. .order-detail-wrapper {
  383. padding: 20px;
  384. background: #f8f8f8;
  385. .detail-header {
  386. margin-bottom: 20px;
  387. display: flex;
  388. justify-content: space-between;
  389. align-items: center;
  390. span {
  391. font-size: 14px;
  392. color: #333;
  393. &.time {
  394. color: #999;
  395. }
  396. }
  397. }
  398. .detail-table {
  399. margin-bottom: 20px;
  400. :deep(.dish-image) {
  401. width: 60px;
  402. height: 60px;
  403. border-radius: 4px;
  404. }
  405. }
  406. .detail-footer {
  407. display: flex;
  408. justify-content: flex-end;
  409. .total-info {
  410. text-align: right;
  411. span {
  412. margin-left: 20px;
  413. font-size: 14px;
  414. color: #666;
  415. &.total-price {
  416. em {
  417. font-style: normal;
  418. font-size: 16px;
  419. color: #f56c6c;
  420. font-weight: bold;
  421. }
  422. }
  423. }
  424. }
  425. }
  426. }
  427. .refund-dialog-content {
  428. padding: 20px 0px;
  429. :deep(.el-input) {
  430. width: 100%;
  431. }
  432. }
  433. .dialog-footer {
  434. text-align: right;
  435. .el-button + .el-button {
  436. margin-left: 12px;
  437. }
  438. }
  439. .refund-info {
  440. font-size: 12px;
  441. color: #999;
  442. margin-top: 4px;
  443. }
  444. .remark-text {
  445. display: -webkit-box;
  446. -webkit-box-orient: vertical;
  447. -webkit-line-clamp: 2;
  448. overflow: hidden;
  449. text-overflow: ellipsis;
  450. word-break: break-all;
  451. line-height: 1.5;
  452. max-height: 3em; // 2行的高度
  453. }
  454. </style>