index.vue 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394
  1. <style scoped lang="scss">
  2. .system-container {
  3. :deep(.el-card__body) {
  4. display: flex;
  5. flex-direction: column;
  6. justify-content: space-between;
  7. flex: 1;
  8. overflow: auto;
  9. .el-table {
  10. flex: 1;
  11. }
  12. }
  13. }
  14. .page-content {
  15. margin-bottom: 20px;
  16. }
  17. .page-pager {
  18. background-color: var(--el-color-white);
  19. height: 24px;
  20. }
  21. .order-detail {
  22. margin-left: 20px;
  23. }
  24. .new-user-tag {
  25. margin-left: 6px;
  26. flex-shrink: 0;
  27. }
  28. .el-table {
  29. &:deep(.el-table__body-wrapper) {
  30. width: 100% !important;
  31. height: calc(100% - 108px) !important; // 表格高度减去表头的高度
  32. .el-scrollbar__bar.is-horizontal {
  33. height: 10px;
  34. left: 2px;
  35. }
  36. }
  37. }
  38. </style>
  39. <template>
  40. <div class="system-container layout-padding">
  41. <el-card shadow="hover" class="layout-padding-auto">
  42. <ext-query-form
  43. class="page-search"
  44. ref="queryRef"
  45. v-model="state.formQuery"
  46. :columns="state.columns"
  47. :import-config="state.importConfig"
  48. :export-config="state.exportConfig"
  49. @on-change="loadData(true)"
  50. @imported="loadData(true)">
  51. <!-- <template #extraQuery></template>
  52. <template #extraLeft></template>
  53. <template #extraRight></template>-->
  54. </ext-query-form>
  55. <el-table
  56. border
  57. stripe="stripe"
  58. :height="state.tableData.height"
  59. highlight-current-row
  60. current-row-key="id"
  61. row-key="id"
  62. @on-row-click="handleRowClick('view',$event)"
  63. :data="state.tableData.data"
  64. v-loading="state.tableData.loading">
  65. <template #empty>
  66. <el-empty></el-empty>
  67. </template>
  68. <el-table-column
  69. v-for="field in state.columns"
  70. :key="field.prop"
  71. :type="field.type"
  72. :label="field.label"
  73. :column-key="field.prop"
  74. :width="field.width"
  75. :min-width="field.minWidth"
  76. :fixed="field.fixed"
  77. :sortable="field.sortable"
  78. :show-overflow-tooltip="!field.fixed&&field.width>150">
  79. <template #default="{row}">
  80. <template v-if="field.type==='expand'">
  81. <div class="order-detail">
  82. <el-table
  83. border
  84. :data="row.detail"
  85. stripe="stripe">
  86. <el-table-column width="120" label="服务项目" prop="name"></el-table-column>
  87. <el-table-column width="120" label="服务时长" prop="seconds"></el-table-column>
  88. <el-table-column width="120" label="服务单价" prop="price"></el-table-column>
  89. <el-table-column width="120" label="小计金额" prop="amount"></el-table-column>
  90. </el-table>
  91. </div>
  92. </template>
  93. <template v-else-if="'stationId'===field.prop">
  94. <div class="flex-align-items-center text-align-center">
  95. <div>
  96. {{ row.stationId }}
  97. </div>
  98. <hr/>
  99. <div>
  100. {{ row.stationName }}
  101. </div>
  102. </div>
  103. </template>
  104. <template v-else-if="'userStationId'===field.prop">
  105. <div class="flex-align-items-center text-align-center">
  106. <div>
  107. {{ row.userStationId }}
  108. </div>
  109. <hr/>
  110. <div>
  111. {{ row.userStationName }}
  112. </div>
  113. </div>
  114. </template>
  115. <template v-else-if="field.prop==='mobilePhone'">
  116. <div class="flex-align-items-center">
  117. <span>{{ row.mobilePhone }}</span>
  118. <el-tag
  119. v-if="row.newUser"
  120. class="new-user-tag"
  121. type="danger"
  122. size="small"
  123. effect="light">
  124. 新用户
  125. </el-tag>
  126. </div>
  127. </template>
  128. <template v-else-if="field.prop==='discountType'">
  129. <ext-d-label type="Activity.discountType" :model-value="row[field.prop]"></ext-d-label>
  130. </template>
  131. <template v-else-if="field.prop==='cardType'">
  132. <ext-d-label type="OrderCard.type" :model-value="row[field.prop]"></ext-d-label>
  133. </template>
  134. <template v-else-if="field.prop==='invoiceStatus'">
  135. <ext-d-label type="Invoice.status" :model-value="row[field.prop]"></ext-d-label>
  136. </template>
  137. <template v-else-if="field.prop==='openType'">
  138. <ext-d-label type="Order.openType" :model-value="row[field.prop]"></ext-d-label>
  139. </template>
  140. <template v-else-if="field.prop==='orderStatus'">
  141. <ext-d-label type="Order.status" :model-value="row[field.prop]"></ext-d-label>
  142. </template>
  143. <template v-else-if="field.prop==='payStatus'">
  144. <ext-d-label type="Order.pay" :model-value="row[field.prop]"></ext-d-label>
  145. </template>
  146. <template v-else-if="field.prop==='closeType'">
  147. <ext-d-label type="Order.closeType" :model-value="row[field.prop]"></ext-d-label>
  148. </template>
  149. <template v-else-if="['prepayMoney','amount','amountReceivable','amountReceived','cardBalance','coinMoney','discountAmount','discountMoney'].includes(field.prop)">
  150. {{ u.fmt.fmtMoney(row[field.prop]) }}
  151. </template>
  152. <template v-else-if="field.prop==='idleRemainTime'||field.prop==='operationRemainTime'">
  153. {{ u.fmt.fmtDuration(row[field.prop]) }}
  154. </template>
  155. <template v-else-if="field.prop==='createTime'">
  156. {{ u.fmt.fmtDateTime(row[field.prop]) }}
  157. </template>
  158. <template v-else-if="field.prop==='updateTime'">
  159. {{ u.fmt.fmtDateTime(row[field.prop]) }}
  160. </template>
  161. <template v-else>
  162. <div>{{ row[field.prop] }}</div>
  163. </template>
  164. </template>
  165. </el-table-column>
  166. <el-table-column label="操作" width="100" fixed="right">
  167. <template #default="{row}">
  168. <el-button
  169. v-if="row.orderStatus === 0"
  170. type="danger"
  171. text
  172. size="small"
  173. @click="handleForceSettle(row)"
  174. >
  175. 强制结算
  176. </el-button>
  177. </template>
  178. </el-table-column>
  179. </el-table>
  180. <ext-page class="page-pager" v-model:value="state.pageQuery" @change="loadData(false)"/>
  181. </el-card>
  182. </div>
  183. </template>
  184. <script lang="ts">
  185. export default {
  186. name: 'adminOrdering'
  187. }
  188. </script>
  189. <script setup lang="ts" name="adminOrdering">
  190. import {reactive, onMounted, onBeforeMount, ref, nextTick, onBeforeUnmount} from 'vue';
  191. import {$body, $get} from "/@/utils/request";
  192. import u from '/@/utils/u'
  193. import {Msg} from "/@/utils/message";
  194. import {Session} from "/@/utils/storage";
  195. import {useRoute} from "vue-router";
  196. import {ElButton} from 'element-plus';
  197. const route = useRoute();
  198. import ExtPage from '/@/components/form/ExtPage.vue'
  199. import ExtQueryForm from "/@/components/form/ExtQueryForm.vue";
  200. import mittBus from '/@/utils/mitt';
  201. import ExtDLabel from "/@/components/form/ExtDLabel.vue";
  202. //定义引用
  203. const queryRef = ref();
  204. const washOrderDialogRef = ref();
  205. //定义变量
  206. const state = reactive({
  207. formQuery: {
  208. mobilePhone:null
  209. },
  210. pageQuery: {
  211. pageNum: 1,
  212. pageSize: 10,
  213. total: 0
  214. },
  215. tableData: {
  216. height: 500,
  217. data: [] as Array<any>,
  218. loading: false
  219. },
  220. importConfig: {},
  221. exportConfig: {},
  222. columns: [
  223. {type: 'expand', width: 60, align: 'center', fixed: 'left'},
  224. {label: '消费站点', prop: 'stationId', width: 140, query: true, type: 'select', resizable: true, conf: {url: 'washStation/list', valueKey: 'stationId', labelKey: 'stationName'}},
  225. {label: '归属站点', prop: 'userStationId', width: 140, query: true, type: 'select', resizable: true, conf: {url: 'washStation/list', valueKey: 'stationId', labelKey: 'stationName'}},
  226. {width: 220, label: '订单号', prop: 'orderId', query: true, type: 'text', resizable: true, fixed: 'left'},
  227. {width: 180, label: '用户手机号', prop: 'mobilePhone', query: true, type: 'text', fixed: 'left'},
  228. {width: 100, label: '设备编号', prop: 'shortId', query: false, type: 'text', resizable: true},
  229. {width: 160, label: '开始时间', prop: 'startTime', query: false, sortable: 'custom', type: 'datetime', resizable: true, conf: {format: (val: any) => u.fmt.fmtDate(val)}},
  230. {width: 160, label: '结束时间', prop: 'endTime', query: false, sortable: 'custom', type: 'datetime', resizable: true, conf: {format: (val: any) => u.fmt.fmtDateTime(val)}},
  231. {width: 100, label: '消费总额', prop: 'amount', query: false, resizable: true},
  232. {width: 100, label: '应收金额', prop: 'amountReceivable', query: false, type: '', resizable: true},
  233. {width: 100, label: '实收金额', prop: 'amountReceived', query: false, type: 'text', resizable: true},
  234. {
  235. width: 120, label: '关机方式',
  236. prop: 'closeType',
  237. query: false,
  238. type: 'dict',
  239. conf: {dict: 'Order.closeType'},
  240. resizable: true
  241. },
  242. {width: 100, label: '订单状态', prop: 'orderStatus', query: false, type: 'dict', conf: {dict: 'Order.status'}, resizable: true},
  243. {width: 100, label: '支付状态', prop: 'payStatus', query: true, type: 'dict', conf: {dict: 'Order.pay'}, resizable: true},
  244. {width: 100, label: '发票状态', prop: 'invoiceStatus', query: false, type: 'dict', conf: {dict: 'Invoice.status'}, resizable: true},
  245. {
  246. width: 160,
  247. label: '更新时间',
  248. fixed: 'right',
  249. prop: 'updateTime',
  250. query: false,
  251. sortable: 'custom',
  252. type: 'datetime',
  253. resizable: true,
  254. conf: {format: (val: any) => u.fmt.fmtDate(val)}
  255. },
  256. ],
  257. })
  258. //生命周期钩子
  259. onBeforeMount(() => {
  260. let token = Session.get("token")
  261. let encodeToken = encodeURIComponent(token)
  262. let exportUrl = `poi/export?type=washOrder&X-Token=${encodeToken}`
  263. //导入导出参数配置
  264. state.importConfig = {
  265. auths: ['washOrder.add'],
  266. url: `poi/import?type=washOrder&X-Token=${encodeToken}`,
  267. template: `${exportUrl}&isTemplate=true`
  268. }
  269. state.exportConfig = {url: exportUrl,}
  270. let query = route.query;
  271. console.log(route.params, route.query)
  272. if (query.mobilePhone) {
  273. state.formQuery.mobilePhone = query.mobilePhone;
  274. }
  275. })
  276. onMounted(() => {
  277. console.log("ordering mounted")
  278. loadData();
  279. nextTick(() => {
  280. let bodyHeight = document.body.clientHeight;
  281. let queryHeight = queryRef.value.$el.clientHeight;
  282. state.tableData.height = bodyHeight - queryHeight - 220
  283. })
  284. mittBus.on("washOrder.refresh", () => {
  285. loadData();
  286. })
  287. });
  288. onBeforeUnmount(() => {
  289. mittBus.off("washOrder.refresh")
  290. })
  291. //region 方法区
  292. // 初始化表格数据
  293. const loadData = (refresh: boolean = false) => {
  294. if (refresh) {
  295. state.pageQuery.pageNum = 1;
  296. }
  297. state.tableData.loading = true;
  298. $body(`/washOrder/list`, {...state.formQuery, ...state.pageQuery}).then((res: any) => {
  299. let {list, total} = res;
  300. list.forEach((item: any) => {
  301. if (item.detail) {
  302. // let parse = JSON.parse(item.detail);
  303. item.detail = item.detail.filter((k: any) => k.amount > 0 || k.seconds > 0).map((s: any) => {
  304. return {
  305. name: u.fmt.fmtDict(s.name, 'Order.feeType'),
  306. seconds: u.fmt.fmtDuration(s.seconds * 1000),
  307. price: u.fmt.fmtMoney(s.price),
  308. amount: u.fmt.fmtMoney(s.amount),
  309. }
  310. })
  311. } else {
  312. item.detail = {}
  313. }
  314. })
  315. state.tableData.data = list;
  316. state.pageQuery.total = total;
  317. state.tableData.loading = false;
  318. }).catch(e => {
  319. //console.error(e)
  320. state.tableData.loading = false;
  321. })
  322. };
  323. // 打开详情页弹窗
  324. const handleRowClick = (type: string, row: any) => {
  325. washOrderDialogRef.value.open(type, row);
  326. };
  327. // 删除点击
  328. const handleRowDelete = (row: any) => {
  329. Msg.confirm(`此操作将永久删除:『${row.name}』,是否继续?`).then(() => {
  330. $get(`/washOrder/delete/${row.id}`).then(() => {
  331. Msg.message("删除成功", 'success')
  332. }).catch(() => {
  333. Msg.message("删除失败", 'error')
  334. })
  335. });
  336. };
  337. // 强制结算
  338. const handleForceSettle = (row: any) => {
  339. Msg.confirm(`确定要对订单「${row.orderId}」进行强制结算吗?此操作将强制结束该订单并释放设备。`).then(() => {
  340. $body(`/washOrder/forceSettle/${row.orderId}`).then(() => {
  341. Msg.message("强制结算成功", 'success')
  342. loadData(true)
  343. }).catch(() => {
  344. Msg.message("强制结算失败", 'error')
  345. })
  346. });
  347. };
  348. //endregion
  349. // 暴露变量
  350. // defineExpose({
  351. // loadData,
  352. // });
  353. </script>