daily-stat.vue 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232
  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-pager {
  15. background-color: var(--el-color-white);
  16. height: 24px;
  17. }
  18. </style>
  19. <template>
  20. <div class="system-container layout-padding">
  21. <el-card shadow="hover" class="layout-padding-auto">
  22. <el-form
  23. :model="state.formQuery"
  24. ref="queryRef"
  25. size="default" label-width="0px" class="mt5 mb5">
  26. <ext-select
  27. v-model="state.formQuery.stationId"
  28. placeholder="站点"
  29. url="washStation/list"
  30. url-method="post"
  31. label-key="stationName"
  32. value-key="stationId"
  33. data-key="list"
  34. clearable
  35. class="wd200 ml10"/>
  36. <el-date-picker
  37. v-model="state.formQuery.startDate"
  38. type="date"
  39. placeholder="开始日期"
  40. value-format="YYYY-MM-DD"
  41. clearable
  42. class="wd200 ml10"/>
  43. <el-date-picker
  44. v-model="state.formQuery.endDate"
  45. type="date"
  46. placeholder="结束日期"
  47. value-format="YYYY-MM-DD"
  48. clearable
  49. class="wd200 ml10"/>
  50. <el-button class="ml10" plain size="default" type="success" @click="loadData(true)">
  51. <SvgIcon name="ele-Search"/>
  52. 查询
  53. </el-button>
  54. <el-date-picker
  55. v-model="state.syncDateRange"
  56. type="daterange"
  57. start-placeholder="开始日期"
  58. end-placeholder="结束日期"
  59. value-format="YYYY-MM-DD"
  60. :disabled-date="disabledSyncDate"
  61. class="wd260 ml10"/>
  62. <el-button class="ml10" plain size="default" type="warning" @click="handleSyncDailyStat" :loading="state.syncing">
  63. <SvgIcon name="ele-RefreshRight"/>
  64. 重新同步
  65. </el-button>
  66. </el-form>
  67. <el-table
  68. border
  69. stripe="stripe"
  70. :height="state.tableData.height"
  71. :data="state.tableData.data"
  72. v-loading="state.tableData.loading">
  73. <template #empty>
  74. <el-empty></el-empty>
  75. </template>
  76. <el-table-column
  77. v-for="field in state.columns"
  78. :key="field.prop"
  79. :label="field.label"
  80. :column-key="field.prop"
  81. :width="field.width"
  82. :min-width="field.minWidth"
  83. :fixed="field.fixed"
  84. :show-overflow-tooltip="!field.fixed&&field.width>150">
  85. <template #default="{row}">
  86. <template v-if="['totalIncome','totalRefund','crossIncome','crossExpend','consumption'].includes(field.prop)">
  87. {{ u.fmt.fmtMoney(row[field.prop]) }}
  88. </template>
  89. <template v-else-if="['createTime','updateTime'].includes(field.prop)">
  90. {{ u.fmt.fmtDateTime(row[field.prop]) }}
  91. </template>
  92. <template v-else>
  93. <div>{{ row[field.prop] }}</div>
  94. </template>
  95. </template>
  96. </el-table-column>
  97. </el-table>
  98. <ext-page class="page-pager" v-model:value="state.pageQuery" @change="loadData(false)"/>
  99. </el-card>
  100. </div>
  101. </template>
  102. <script setup lang="ts" name="adminStatisticsDailyStat">
  103. import {reactive, onMounted, ref, nextTick} from 'vue';
  104. import {$body} from "/@/utils/request";
  105. import u from '/@/utils/u'
  106. import {Msg} from "/@/utils/message";
  107. import ExtPage from '/@/components/form/ExtPage.vue'
  108. import ExtSelect from "/@/components/form/ExtSelect.vue";
  109. import {useStationScope} from '/@/hooks/useStationScope';
  110. const queryRef = ref();
  111. const state = reactive({
  112. syncDateRange: [] as string[],
  113. syncing: false,
  114. formQuery: {
  115. stationId: '',
  116. startDate: '',
  117. endDate: ''
  118. },
  119. pageQuery: {
  120. pageNum: 1,
  121. pageSize: 10,
  122. total: 0
  123. },
  124. tableData: {
  125. height: 500,
  126. data: [] as Array<any>,
  127. loading: false
  128. },
  129. columns: [
  130. {label: '统计日期', width: 120, prop: 'statDate', fixed: 'left', resizable: true},
  131. {label: '站点名称', width: 180, prop: 'stationName', resizable: true},
  132. {label: '总收入(元)', width: 140, prop: 'totalIncome', resizable: true},
  133. {label: '退款金额(元)', width: 140, prop: 'totalRefund', resizable: true},
  134. {label: '跨店收入(元)', width: 140, prop: 'crossIncome', resizable: true},
  135. {label: '跨店支出(元)', width: 140, prop: 'crossExpend', resizable: true},
  136. {label: '消费金额(元)', width: 140, prop: 'consumption', resizable: true},
  137. {label: '充值笔数', width: 100, prop: 'rechargeCount', resizable: true},
  138. {label: '退款笔数', width: 100, prop: 'refundCount', resizable: true},
  139. {label: '注册人数', width: 100, prop: 'registrations', resizable: true},
  140. {label: '活跃用户数', width: 110, prop: 'activeUserCount', resizable: true},
  141. {label: '订单数量', width: 100, prop: 'ordersCount', resizable: true},
  142. {label: '创建时间', width: 180, prop: 'createTime', resizable: true},
  143. ],
  144. })
  145. //站点筛选与全局当前站点联动(回调懒引用,规避 loadData 声明顺序的 TDZ 错误)
  146. useStationScope(state.formQuery, (refresh?: boolean) => loadData(refresh));
  147. onMounted(() => {
  148. // 同步日期默认昨天
  149. const yesterday = new Date();
  150. yesterday.setDate(yesterday.getDate() - 1);
  151. const yesterdayStr = yesterday.toISOString().split('T')[0];
  152. state.syncDateRange = [yesterdayStr, yesterdayStr];
  153. loadData();
  154. nextTick(() => {
  155. let bodyHeight = document.body.clientHeight;
  156. let queryHeight = queryRef.value.$el.clientHeight;
  157. state.tableData.height = bodyHeight - queryHeight - 320
  158. })
  159. });
  160. const loadData = (refresh: boolean = false) => {
  161. if (refresh) {
  162. state.pageQuery.pageNum = 1;
  163. }
  164. state.tableData.loading = true;
  165. $body(`/daily-stat/list`, {...state.formQuery, ...state.pageQuery}).then((res: any) => {
  166. let {list, total} = res;
  167. state.tableData.data = list || [];
  168. state.pageQuery.total = total || 0;
  169. state.tableData.loading = false;
  170. }).catch(e => {
  171. console.error(e)
  172. state.tableData.loading = false;
  173. })
  174. };
  175. const disabledSyncDate = (date: Date) => {
  176. const today = new Date();
  177. today.setHours(0, 0, 0, 0);
  178. // 不允许选择今天及以后
  179. return date.getTime() >= today.getTime();
  180. };
  181. const handleSyncDailyStat = () => {
  182. if (!state.syncDateRange || state.syncDateRange.length !== 2) {
  183. Msg.message('请选择同步日期范围', 'warning');
  184. return;
  185. }
  186. const [startDate, endDate] = state.syncDateRange;
  187. const days = Math.floor((new Date(endDate).getTime() - new Date(startDate).getTime()) / 86400000) + 1;
  188. if (days > 30) {
  189. Msg.message('同步日期范围不能超过30天', 'warning');
  190. return;
  191. }
  192. const rangeLabel = startDate === endDate ? startDate : `${startDate} ~ ${endDate}`;
  193. Msg.confirm(
  194. `确认重新同步 ${rangeLabel}(共${days}天)的统计数据吗?将同步所有站点的数据。`,
  195. '批量同步日统计'
  196. ).then(() => {
  197. state.syncing = true;
  198. Msg.showLoading(`批量同步中(共${days}天)...`);
  199. $body(`/daily-stat/batch-sync`, { startDate, endDate }).then(() => {
  200. Msg.message(`同步完成(共${days}天)`);
  201. Msg.hideLoading();
  202. state.syncing = false;
  203. loadData(true);
  204. }).catch(e => {
  205. Msg.hideLoading();
  206. state.syncing = false;
  207. })
  208. })
  209. };
  210. </script>