index.vue 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428
  1. <template>
  2. <div class="flex flex-col">
  3. <el-row :gutter="16" class="summary">
  4. <el-col :sm="6" :xs="12">
  5. <TradeStatisticValue
  6. tooltip="昨日订单数量"
  7. title="昨日订单数量"
  8. :value="summary?.value?.yesterdayOrderCount || 0"
  9. :percent="
  10. calculateRelativeRate(
  11. summary?.value?.yesterdayOrderCount,
  12. summary?.reference?.yesterdayOrderCount
  13. )
  14. "
  15. />
  16. </el-col>
  17. <el-col :sm="6" :xs="12">
  18. <TradeStatisticValue
  19. tooltip="本月订单数量"
  20. title="本月订单数量"
  21. :value="summary?.value?.monthOrderCount || 0"
  22. :percent="
  23. calculateRelativeRate(
  24. summary?.value?.monthOrderCount,
  25. summary?.reference?.monthOrderCount
  26. )
  27. "
  28. />
  29. </el-col>
  30. <el-col :sm="6" :xs="12">
  31. <TradeStatisticValue
  32. tooltip="昨日支付金额"
  33. title="昨日支付金额"
  34. prefix="¥"
  35. :decimals="2"
  36. :value="fenToYuan(summary?.value?.yesterdayPayPrice || 0)"
  37. :percent="
  38. calculateRelativeRate(
  39. summary?.value?.yesterdayPayPrice,
  40. summary?.reference?.yesterdayPayPrice
  41. )
  42. "
  43. />
  44. </el-col>
  45. <el-col :sm="6" :xs="12">
  46. <TradeStatisticValue
  47. tooltip="本月支付金额"
  48. title="本月支付金额"
  49. prefix="¥"
  50. ::decimals="2"
  51. :value="fenToYuan(summary?.value?.monthPayPrice || 0)"
  52. :percent="
  53. calculateRelativeRate(summary?.value?.monthPayPrice, summary?.reference?.monthPayPrice)
  54. "
  55. />
  56. </el-col>
  57. </el-row>
  58. <el-card shadow="never">
  59. <template #header>
  60. <!-- 标题 -->
  61. <div class="flex flex-row items-center justify-between">
  62. <span>交易状况</span>
  63. <!-- 查询条件 -->
  64. <div class="flex flex-row items-center gap-2">
  65. <el-radio-group v-model="shortcutDays" @change="handleDateTypeChange">
  66. <el-radio-button :label="1">昨天</el-radio-button>
  67. <el-radio-button :label="7">最近7天</el-radio-button>
  68. <el-radio-button :label="30">最近30天</el-radio-button>
  69. </el-radio-group>
  70. <el-date-picker
  71. v-model="queryParams.times"
  72. value-format="YYYY-MM-DD HH:mm:ss"
  73. type="daterange"
  74. start-placeholder="开始日期"
  75. end-placeholder="结束日期"
  76. :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
  77. :shortcuts="shortcuts"
  78. class="!w-240px"
  79. @change="getTradeTrendData"
  80. />
  81. <el-button
  82. class="ml-4"
  83. @click="handleExport"
  84. :loading="exportLoading"
  85. v-hasPermi="['statistics:trade:export']"
  86. >
  87. <Icon icon="ep:download" class="mr-1" />导出
  88. </el-button>
  89. </div>
  90. </div>
  91. </template>
  92. <!-- 统计值 -->
  93. <el-row :gutter="16">
  94. <el-col :md="6" :sm="12" :xs="24">
  95. <TradeTrendValue
  96. title="营业额"
  97. tooltip="商品支付金额、充值金额"
  98. icon="fa-solid:yen-sign"
  99. icon-color="bg-blue-100"
  100. icon-bg-color="text-blue-500"
  101. prefix="¥"
  102. :decimals="2"
  103. :value="fenToYuan(trendSummary?.value?.turnover || 0)"
  104. :percent="
  105. calculateRelativeRate(
  106. trendSummary?.value?.turnover,
  107. trendSummary?.reference?.turnover
  108. )
  109. "
  110. />
  111. </el-col>
  112. <el-col :md="6" :sm="12" :xs="24">
  113. <TradeTrendValue
  114. title="商品支付金额"
  115. tooltip="用户购买商品的实际支付金额,包括微信支付、余额支付、支付宝支付、线下支付金额(拼团商品在成团之后计入,线下支付订单在后台确认支付后计入)"
  116. icon="fa-solid:shopping-cart"
  117. icon-color="bg-purple-100"
  118. icon-bg-color="text-purple-500"
  119. prefix="¥"
  120. :decimals="2"
  121. :value="fenToYuan(trendSummary?.value?.orderPayPrice || 0)"
  122. :percent="
  123. calculateRelativeRate(
  124. trendSummary?.value?.orderPayPrice,
  125. trendSummary?.reference?.orderPayPrice
  126. )
  127. "
  128. />
  129. </el-col>
  130. <el-col :md="6" :sm="12" :xs="24">
  131. <TradeTrendValue
  132. title="充值金额"
  133. tooltip="用户成功充值的金额"
  134. icon="fa-solid:money-check-alt"
  135. icon-color="bg-yellow-100"
  136. icon-bg-color="text-yellow-500"
  137. prefix="¥"
  138. :decimals="2"
  139. :value="fenToYuan(trendSummary?.value?.rechargePrice || 0)"
  140. :percent="
  141. calculateRelativeRate(
  142. trendSummary?.value?.rechargePrice,
  143. trendSummary?.reference?.rechargePrice
  144. )
  145. "
  146. />
  147. </el-col>
  148. <el-col :md="6" :sm="12" :xs="24">
  149. <TradeTrendValue
  150. title="支出金额"
  151. tooltip="余额支付金额、支付佣金金额、商品退款金额"
  152. icon="ep:warning-filled"
  153. icon-color="bg-green-100"
  154. icon-bg-color="text-green-500"
  155. prefix="¥"
  156. :decimals="2"
  157. :value="fenToYuan(trendSummary?.value?.expensePrice || 0)"
  158. :percent="
  159. calculateRelativeRate(
  160. trendSummary?.value?.expensePrice,
  161. trendSummary?.reference?.expensePrice
  162. )
  163. "
  164. />
  165. </el-col>
  166. <el-col :md="6" :sm="12" :xs="24">
  167. <TradeTrendValue
  168. title="余额支付金额"
  169. tooltip="用户下单时使用余额实际支付的金额"
  170. icon="fa-solid:wallet"
  171. icon-color="bg-cyan-100"
  172. icon-bg-color="text-cyan-500"
  173. prefix="¥"
  174. :decimals="2"
  175. :value="fenToYuan(trendSummary?.value?.balancePrice || 0)"
  176. :percent="
  177. calculateRelativeRate(
  178. trendSummary?.value?.balancePrice,
  179. trendSummary?.reference?.balancePrice
  180. )
  181. "
  182. />
  183. </el-col>
  184. <el-col :md="6" :sm="12" :xs="24">
  185. <TradeTrendValue
  186. title="支付佣金金额"
  187. tooltip="后台给推广员支付的推广佣金,以实际支付为准"
  188. icon="fa-solid:award"
  189. icon-color="bg-yellow-100"
  190. icon-bg-color="text-yellow-500"
  191. prefix="¥"
  192. :decimals="2"
  193. :value="fenToYuan(trendSummary?.value?.brokerageSettlementPrice || 0)"
  194. :percent="
  195. calculateRelativeRate(
  196. trendSummary?.value?.brokerageSettlementPrice,
  197. trendSummary?.reference?.brokerageSettlementPrice
  198. )
  199. "
  200. />
  201. </el-col>
  202. <el-col :md="6" :sm="12" :xs="24">
  203. <TradeTrendValue
  204. title="商品退款金额"
  205. tooltip="用户成功退款的商品金额"
  206. icon="fa-solid:times-circle"
  207. icon-color="bg-blue-100"
  208. icon-bg-color="text-blue-500"
  209. prefix="¥"
  210. :decimals="2"
  211. :value="fenToYuan(trendSummary?.value?.orderRefundPrice || 0)"
  212. :percent="
  213. calculateRelativeRate(
  214. trendSummary?.value?.orderRefundPrice,
  215. trendSummary?.reference?.orderRefundPrice
  216. )
  217. "
  218. />
  219. </el-col>
  220. </el-row>
  221. <!-- 拆线图 -->
  222. <el-skeleton :loading="trendLoading" animated>
  223. <Echart :height="500" :options="lineChartOptions" />
  224. </el-skeleton>
  225. </el-card>
  226. </div>
  227. </template>
  228. <script lang="ts" setup>
  229. import * as TradeStatisticsApi from '@/api/statistics/trade'
  230. import TradeStatisticValue from './components/TradeStatisticValue.vue'
  231. import TradeTrendValue from './components/TradeTrendValue.vue'
  232. import { EChartsOption } from 'echarts'
  233. import {
  234. TradeStatisticsComparisonRespVO,
  235. TradeSummaryRespVO,
  236. TradeTrendReqVO,
  237. TradeTrendSummaryRespVO
  238. } from '@/api/statistics/trade'
  239. import dayjs from 'dayjs'
  240. import { fenToYuan } from '@/utils'
  241. import * as DateUtil from '@/utils/formatTime'
  242. import download from '@/utils/download'
  243. /** 交易统计 */
  244. defineOptions({ name: 'TradeStatistics' })
  245. const message = useMessage() // 消息弹窗
  246. const loading = ref(true) // 加载中
  247. const trendLoading = ref(true) // 交易状态加载中
  248. const exportLoading = ref(false) // 导出的加载中
  249. const queryParams = reactive<TradeTrendReqVO>({ times: ['', ''] }) // 交易状况查询参数
  250. const shortcutDays = ref(7) // 日期快捷天数(单选按钮组), 默认7天
  251. const summary = ref<TradeStatisticsComparisonRespVO<TradeSummaryRespVO>>() // 交易统计数据
  252. const trendSummary = ref<TradeStatisticsComparisonRespVO<TradeTrendSummaryRespVO>>() // 交易状况统计数据
  253. /** 日期快捷选择 */
  254. const shortcuts = [
  255. {
  256. text: '昨天',
  257. value: () => DateUtil.getDayRange(new Date(), -1)
  258. },
  259. {
  260. text: '最近7天',
  261. value: () => DateUtil.getLast7Days()
  262. },
  263. {
  264. text: '本月',
  265. value: () => [dayjs().startOf('M'), dayjs().subtract(1, 'd')]
  266. },
  267. {
  268. text: '最近30天',
  269. value: () => DateUtil.getLast30Days()
  270. },
  271. {
  272. text: '最近1年',
  273. value: () => DateUtil.getLast1Year()
  274. }
  275. ]
  276. /** 折线图配置 */
  277. const lineChartOptions = reactive<EChartsOption>({
  278. dataset: {
  279. dimensions: ['date', 'turnover', 'orderPayPrice', 'rechargePrice', 'expensePrice'],
  280. source: []
  281. },
  282. grid: {
  283. left: 20,
  284. right: 20,
  285. bottom: 20,
  286. top: 80,
  287. containLabel: true
  288. },
  289. legend: {
  290. top: 50
  291. },
  292. series: [
  293. { name: '营业额', type: 'line', smooth: true },
  294. { name: '商品支付金额', type: 'line', smooth: true },
  295. { name: '充值金额', type: 'line', smooth: true },
  296. { name: '支出金额', type: 'line', smooth: true }
  297. ],
  298. toolbox: {
  299. feature: {
  300. // 数据区域缩放
  301. dataZoom: {
  302. yAxisIndex: false // Y轴不缩放
  303. },
  304. brush: {
  305. type: ['lineX', 'clear'] // 区域缩放按钮、还原按钮
  306. },
  307. saveAsImage: { show: true, name: '交易状况' } // 保存为图片
  308. }
  309. },
  310. tooltip: {
  311. trigger: 'axis',
  312. axisPointer: {
  313. type: 'cross'
  314. },
  315. padding: [5, 10]
  316. },
  317. xAxis: {
  318. type: 'category',
  319. boundaryGap: false,
  320. axisTick: {
  321. show: false
  322. }
  323. },
  324. yAxis: {
  325. axisTick: {
  326. show: false
  327. }
  328. }
  329. }) as EChartsOption
  330. /** 计算环比 */
  331. const calculateRelativeRate = (value?: number, reference?: number) => {
  332. // 防止除0
  333. if (!reference) return 0
  334. return ((100 * ((value || 0) - reference)) / reference).toFixed(0)
  335. }
  336. /** 设置时间范围 */
  337. function setTimes() {
  338. const beginDate = dayjs().subtract(shortcutDays.value, 'd')
  339. const yesterday = dayjs().subtract(1, 'd')
  340. queryParams.times = DateUtil.getDateRange(beginDate, yesterday)
  341. }
  342. /** 处理交易状况查询(日期单选按钮组选择后) */
  343. const handleDateTypeChange = async () => {
  344. // 设置时间范围
  345. setTimes()
  346. // 查询数据
  347. await getTradeTrendData()
  348. }
  349. /** 处理交易状况查询 */
  350. const getTradeTrendData = async () => {
  351. trendLoading.value = true
  352. await Promise.all([getTradeTrendSummary(), getTradeTrendList()])
  353. trendLoading.value = false
  354. }
  355. /** 查询交易统计 */
  356. const getTradeStatisticsSummary = async () => {
  357. summary.value = await TradeStatisticsApi.getTradeStatisticsSummary()
  358. }
  359. /** 查询交易状况数据统计 */
  360. const getTradeTrendSummary = async () => {
  361. loading.value = true
  362. trendSummary.value = await TradeStatisticsApi.getTradeTrendSummary(queryParams)
  363. loading.value = false
  364. }
  365. /** 查询交易状况数据列表 */
  366. const getTradeTrendList = async () => {
  367. const times = queryParams.times
  368. // 开始与截止在同一天的, 折线图出不来, 需要延长一天
  369. if (DateUtil.isSameDay(times[0], times[1])) {
  370. // 前天
  371. times[0] = DateUtil.formatDate(dayjs(times[0]).subtract(1, 'd'))
  372. }
  373. // 查询数据
  374. const list = await TradeStatisticsApi.getTradeTrendList({ times })
  375. // 处理数据
  376. for (let item of list) {
  377. item.turnover = fenToYuan(item.turnover)
  378. item.orderPayPrice = fenToYuan(item.orderPayPrice)
  379. item.rechargePrice = fenToYuan(item.rechargePrice)
  380. item.expensePrice = fenToYuan(item.expensePrice)
  381. }
  382. // 更新 Echarts 数据
  383. if (lineChartOptions.dataset && lineChartOptions.dataset['source']) {
  384. lineChartOptions.dataset['source'] = list
  385. }
  386. }
  387. /** 导出按钮操作 */
  388. const handleExport = async () => {
  389. try {
  390. // 导出的二次确认
  391. await message.exportConfirm()
  392. // 发起导出
  393. exportLoading.value = true
  394. const data = await TradeStatisticsApi.exportTradeTrend(queryParams)
  395. download.excel(data, '交易状况.xls')
  396. } catch {
  397. } finally {
  398. exportLoading.value = false
  399. }
  400. }
  401. /** 初始化 **/
  402. onMounted(async () => {
  403. await getTradeStatisticsSummary()
  404. await handleDateTypeChange()
  405. })
  406. </script>
  407. <style lang="scss" scoped>
  408. .summary {
  409. .el-col {
  410. margin-bottom: 1rem;
  411. }
  412. }
  413. </style>