Pārlūkot izejas kodu

feat(CustomTable): 新增图表slot

fxs 2 mēneši atpakaļ
vecāks
revīzija
f96e8e5797

+ 4 - 2
.env

@@ -1,6 +1,8 @@
 # 开发
-VITE_API_URL_DEV = "http://192.168.1.139:8000"
+#VITE_API_URL_DEV="http://192.168.1.139:8000"
+VITE_API_URL_DEV='http://service.ichunhao.cn'
 # 测试服和本地开发
-VITE_API_URL_TEST='http://server.ichunhao.cn'
+#VITE_API_URL_TEST='http://server.ichunhao.cn'
+VITE_API_URL_TEST='http://service.ichunhao.cn'
 # 线上
 VITE_API_URL_PRODUCT='http://service.ichunhao.cn'

+ 3 - 0
components.d.ts

@@ -34,6 +34,8 @@ declare module 'vue' {
     ElPageHeader: typeof import('element-plus/es')['ElPageHeader']
     ElPagination: typeof import('element-plus/es')['ElPagination']
     ElPopover: typeof import('element-plus/es')['ElPopover']
+    ElRadioButton: typeof import('element-plus/es')['ElRadioButton']
+    ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup']
     ElSelect: typeof import('element-plus/es')['ElSelect']
     ElSubMenu: typeof import('element-plus/es')['ElSubMenu']
     ElSwitch: typeof import('element-plus/es')['ElSwitch']
@@ -59,6 +61,7 @@ declare module 'vue' {
     IconTablerPointFilled: typeof import('~icons/tabler/point-filled')['default']
     MyButton: typeof import('./src/components/form/MyButton.vue')['default']
     MyInput: typeof import('./src/components/form/MyInput.vue')['default']
+    PieBorderRadius: typeof import('./src/components/echarts/PieBorderRadius.vue')['default']
     RefreshBtn: typeof import('./src/components/toolsBtn/RefreshBtn.vue')['default']
     RouterLink: typeof import('vue-router')['RouterLink']
     RouterView: typeof import('vue-router')['RouterView']

+ 2 - 1
index.html

@@ -2,9 +2,10 @@
 <html lang="en">
   <head>
     <meta charset="UTF-8" />
+      <meta name="viewport" content="width=device-width, initial-scale=1.0" />
      <link rel="icon" href="/logoTest.svg" />
 <!--    <link rel="icon" href="/logo.svg" />-->
-    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+
 <!--    <title>淳皓游戏管理平台</title>-->
      <title>测试库-游戏管理平台</title>
   </head>

+ 93 - 0
src/components/echarts/PieBorderRadius.vue

@@ -0,0 +1,93 @@
+<script setup lang="ts">
+// 图表Ref
+import { nextTick, onMounted, ref, watch } from 'vue'
+import echarts from '@/components/echarts/index.ts'
+import { debounceFunc } from '@/utils/common'
+import type { EChartsOption } from 'echarts/types/dist/shared'
+
+interface PieProps {
+  options: EChartsOption
+}
+
+const props = withDefaults(defineProps<PieProps>(), {})
+
+const chart = ref<any>(null)
+
+const pieChart = ref<echarts.EChartsType | null>(null)
+
+// 尺寸变化
+const changeSize = () => {
+  nextTick(() => {
+    pieChart.value?.resize()
+  })
+}
+
+const initOptions = () => {
+  let options = props.options
+  const series = options.series
+  let hasData = true
+  if (!series) {
+    hasData = false
+  } else if (Array.isArray(props.options.series)) {
+    if (props.options.series.length === 0) hasData = false
+  } else if (Object.keys(series).length === 0) {
+    hasData = false
+  }
+  if (!hasData) {
+    options = {
+      title: {
+        text: '暂无数据',
+        left: 'center',
+        top: 'center',
+        textStyle: {
+          fontSize: 16,
+          fontWeight: 'normal'
+        }
+      }
+    }
+  }
+
+  pieChart.value?.setOption(options, true)
+}
+
+watch(
+  () => props.options,
+  () => {
+    initOptions()
+  },
+  {
+    deep: true
+  }
+)
+
+onMounted(() => {
+  if (!chart.value) {
+    console.error('chart is null')
+    return
+  }
+  // 开始的时候,不要去直接初始化,先展示加载状态,等到数据加载完状态改变,再去设置
+  pieChart.value = echarts.init(chart.value)
+
+  nextTick(() => {
+    initOptions()
+  })
+
+  // if (!attrs['loading-state']) initOptions()
+
+  /**
+   * @description: 只监听window会导致当侧边栏缩放时,dom大小变化但无法resize
+   *              所以需要使用observer对整个dom进行监听
+   * @return {*}
+   */
+  // window.addEventListener('resize', debounceFunc(changeSize, 500))
+  const debounceChangeSize = debounceFunc(changeSize, 200)
+  const ro = new ResizeObserver(debounceChangeSize)
+  ro.observe(chart.value)
+})
+</script>
+
+<template>
+  <div class="chart" ref="chart" style="width: 100%; height: 365px"></div>
+</template>
+
+<style scoped></style>

+ 49 - 1
src/components/table/CustomTable.vue

@@ -15,7 +15,7 @@ import { fuzzySearch, throttleFunc } from '@/utils/common'
 
 import { useTable } from '@/hooks/useTable.ts'
 import { useRequest } from '@/hooks/useRequest.ts'
-import { computed, type ComputedRef, onMounted, reactive, ref, toRaw, watch } from 'vue'
+import { computed, type ComputedRef, nextTick, onMounted, reactive, ref, toRaw, watch } from 'vue'
 
 import TableFilterForm from '@/components/table/TableFilterForm/TableFilterForm.vue'
 import axiosInstance from '@/utils/axios/axiosInstance.ts'
@@ -35,6 +35,7 @@ const throttleTime = 500
 const props = withDefaults(defineProps<PropsParams>(), {
   needRowindex: true,
   needAverage: false,
+  needTotal: false,
   openFilterQuery: true,
   openPageQuery: true,
   loadingState: false,
@@ -387,6 +388,13 @@ const outGetTableData = () => {
   return toRaw(tableData).flat()
 }
 
+const getTotal = () => {
+  if (!props.needTotal || !props.totalFunc) {
+    return []
+  }
+  return props.totalFunc(tableData, paginationConfig.total)
+}
+
 // 定义暴露出去的方法
 defineExpose({
   updateTableData,
@@ -402,6 +410,10 @@ onMounted(() => {
   initReqConfig(reqConfig, props.requestConfig)
 
   checkPropsConfig()
+
+  nextTick(() => {
+    // changeTableFooterPos()
+  })
 })
 </script>
 
@@ -420,6 +432,10 @@ onMounted(() => {
         ></TableFilterForm>
       </div>
     </div>
+    <!--    <div class="chartContainer" v-if="needChart && props.chartOptions">-->
+    <!--      <PieBorderRadius :options="props.chartOptions"></PieBorderRadius>-->
+    <!--    </div>-->
+    <slot name="chart" v-if="$slots.chart"> </slot>
 
     <div class="tableTools">
       <TableTools
@@ -432,7 +448,22 @@ onMounted(() => {
     </div>
 
     <div class="tableBox">
+      <p class="totalRow" v-if="props.needTotal">
+        <span class="totalItemLabel totalItem"> 总计 </span>
+        <span class="totalItem" :key="'totalKey' + item" v-for="item in getTotal()">
+          {{ item }}
+        </span>
+      </p>
+
       <!-- 没有分页的时候需要重新计算一下data -->
+      <!--      :show-summary="props.needTotal"-->
+      <!--      :summary-method="-->
+      <!--      () => {-->
+      <!--      if (props.totalFunc) {-->
+      <!--      return props.totalFunc(tableData, paginationConfig.total)-->
+      <!--      }-->
+      <!--      return []    "-->
+      <!--      }-->
       <el-table
         :data="
           openRemoteReqData && openRemoteQuery
@@ -618,4 +649,21 @@ onMounted(() => {
 .leftToolBtn {
   margin-right: 5px;
 }
+
+.totalRow {
+  padding: 10px 0px;
+  display: flex;
+  /*justify-content: space-between;*/
+}
+
+.totalRow > .totalItemLabel {
+  width: 60px;
+  font-weight: bold;
+}
+
+.totalItem {
+  width: 130px;
+  margin-right: 15px;
+  text-align: center;
+}
 </style>

+ 2 - 0
src/components/table/TableColumn/TableColumn.vue

@@ -79,6 +79,8 @@ onMounted(() => {
       {{ columnConfig.specialEffect.otherInfo.render(row[columnConfig.name]) }}
     </span>
 
+    <!--    <el-text></el-text>-->
+
     <el-text v-else>
       <!-- 其他列按默认方式显示 -->
 

+ 6 - 1
src/types/table.ts

@@ -11,6 +11,8 @@ import type { ReqConfig } from './dataAnalysis'
 import type { ValueTypes } from './form'
 import type { SpecialEffect } from './tableText'
 
+// import type { EChartsOption } from 'echarts/types/dist/shared'
+
 // 颜色类型
 // export enum ColorType {
 //   PRIMARY = 'primary',
@@ -86,7 +88,10 @@ export interface PropsParams {
   loadingState?: boolean // 外部控制表格加载状态
   needRowindex?: boolean // 是否需要行号
   needAverage?: boolean // 是否需要均值功能
-
+  needTotal?: boolean // 是否需要总计
+  // needChart?: boolean // 是否需要图表功能
+  // chartOptions?: EChartsOption // 图表配置
+  totalFunc?: (data: any, total: number) => any[] // 计算总计的函数
   openFilterQuery?: boolean // 是否开启上方查询功能,关闭后queryInfo无效
   queryInfo?: Array<QueryInfo> // 上方查询功能所需要的信息
 

+ 18 - 1
src/views/Home/AdvertisingData/AdvertisingList.vue

@@ -34,6 +34,7 @@ import {
 } from '@/types/table'
 import { FieldSpecialEffectType, TextType } from '@/types/tableText'
 import { formatDate } from '@/utils/common'
+import Table from '@/components/table/CustomTable.vue'
 
 const { selectInfo } = useCommonStore()
 const { AllApi } = useRequest()
@@ -237,11 +238,25 @@ const updateSelect = (pf: string, gid: string) => {
 const backupSelect = reactive([])
 
 watchPageChange(() => [selectInfo.pf, selectInfo.gid], backupSelect, updateSelect)
+
+/**
+ * 获取合计数据
+ * @param _data 当前页表格数据,如果不是远程分页查询,则是全部数据
+ * @param total 表格总数
+ * @param needOther 是否需要自定义其他内容,为false则只返回数据总条数
+ * @returns 合计数据数组
+ */
+const getSummary = (_data: any, total: number, needOther = false) => {
+  if (!needOther) return [total + '条数据']
+  // 如需其他内容则在此处补充
+  return []
+}
 </script>
 
 <template>
   <div class="advertisingListContainer">
-    <div class="adHeader">
+    <!--    adHeader 会被浏览器默认样式表覆盖-->
+    <div class="adHeaderTest">
       <HeaderCard
         :title="headerCardInfo.title"
         :open-date-select="headerCardInfo.openDateSelect"
@@ -256,6 +271,8 @@ watchPageChange(() => [selectInfo.pf, selectInfo.gid], backupSelect, updateSelec
         :query-info="queryInfo"
         :open-filter-query="true"
         :tools="tableToolsConfig"
+        :need-total="true"
+        :total-func="getSummary"
       ></CustomTable>
     </div>
   </div>

+ 111 - 2
src/views/Home/Analysis/UserBehavior.vue

@@ -18,7 +18,7 @@ import type {
 import { FilterType } from '@/types/table'
 import { CustomFilterValueType } from '@/types/customFilter'
 
-import { reactive, ref } from 'vue'
+import { computed, reactive, ref } from 'vue'
 import { useRequest } from '@/hooks/useRequest'
 import { useAnalysis } from '@/hooks/useAnalysis'
 import { useCommonStore } from '@/stores/useCommon'
@@ -28,6 +28,7 @@ import { usePage } from '@/hooks/usePage'
 
 import Table from '@/components/table/CustomTable.vue'
 import { FieldSpecialEffectType, TagType, TextType } from '@/types/tableText'
+import type { EChartsOption } from 'echarts/types/dist/shared'
 
 type TableType = InstanceType<typeof Table>
 
@@ -310,6 +311,86 @@ const updateAllReq = (pf: string, gid: string) => {
 const backupSelect = reactive([]) // 保存选择数据
 
 watchPageChange(() => [selectInfo.pf, selectInfo.gid], backupSelect, updateAllReq)
+
+/**
+ * 获取合计数据
+ * @param _data 当前页表格数据,如果不是远程分页查询,则是全部数据
+ * @param total 表格总数
+ * @param needOther 是否需要自定义其他内容,为false则只返回数据总条数
+ * @returns 合计数据数组
+ */
+const getSummary = (_data: any, total: number, needOther = false) => {
+  if (!needOther) return [total + '条数据']
+  // 如需其他内容则在此处补充
+  return []
+}
+
+const pieChartOptions: EChartsOption = {
+  tooltip: {
+    trigger: 'item'
+  },
+  legend: {
+    top: '5%',
+    left: 'center'
+  },
+  series: [
+    {
+      name: 'Access From',
+      type: 'pie',
+      radius: ['40%', '70%'],
+      avoidLabelOverlap: false,
+      itemStyle: {
+        borderRadius: 10,
+        borderColor: '#fff',
+        borderWidth: 2
+      },
+      label: {
+        show: false,
+        position: 'center'
+      },
+      emphasis: {
+        label: {
+          show: true,
+          fontSize: 40,
+          fontWeight: 'bold'
+        }
+      },
+      labelLine: {
+        show: false
+      },
+      data: [
+        { value: 1048, name: 'Search Engine' },
+        { value: 735, name: 'Direct' },
+        { value: 580, name: 'Email' },
+        { value: 484, name: 'Union Ads' },
+        { value: 300, name: 'Video Ads' }
+      ]
+    }
+  ]
+}
+
+const barChartOptions: EChartsOption = {
+  xAxis: {
+    type: 'category',
+    data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
+  },
+  yAxis: {
+    type: 'value'
+  },
+  series: [
+    {
+      data: [120, 200, 150, 80, 70, 110, 130],
+      type: 'bar'
+    }
+  ]
+}
+
+const isPie = ref(false)
+
+const chartOptions = computed(() => {
+  console.log(isPie.value)
+  return isPie.value ? pieChartOptions : barChartOptions
+})
 </script>
 
 <template>
@@ -328,7 +409,23 @@ watchPageChange(() => [selectInfo.pf, selectInfo.gid], backupSelect, updateAllRe
         :open-page-query="true"
         :open-filter-query="true"
         :tools="tableToolsConfig"
-      ></Table>
+        :need-total="true"
+        :total-func="getSummary"
+      >
+        <template #chart>
+          <div class="chartContainer">
+            <div class="chartsTools">
+              <el-radio-group class="formChangeRadioGroup" v-model="isPie" size="small">
+                <el-radio-button label="饼图" :value="true" />
+                <el-radio-button label="柱状图" :value="false" />
+              </el-radio-group>
+            </div>
+            <div class="chartDisplay">
+              <PieBorderRadius :options="chartOptions"></PieBorderRadius>
+            </div>
+          </div>
+        </template>
+      </Table>
     </div>
   </div>
 </template>
@@ -347,4 +444,16 @@ watchPageChange(() => [selectInfo.pf, selectInfo.gid], backupSelect, updateAllRe
   padding: 0 24px;
   background-color: white;
 }
+
+.chartContainer {
+  width: 100%;
+  position: relative;
+}
+
+.chartsTools {
+  width: 100%;
+  position: relative;
+  display: flex;
+  justify-content: flex-end;
+}
 </style>

+ 1 - 1
src/views/IndexView.vue

@@ -244,7 +244,7 @@ onMounted(() => {
         <div class="gameSelect">
           <el-icon class="gameIcon" :size="20">
             <icon-icon-park-game-three></icon-icon-park-game-three>
-            <!--             <icon-icon-park-solid-ad></icon-icon-park-solid-ad> -->
+            <!--            <icon-icon-park-solid-ad></icon-icon-park-solid-ad>-->
           </el-icon>
           <DropDownSelection
             :default-select="gameSelectInfo.defaultSelect"

+ 1 - 1
vite.config.ts

@@ -85,7 +85,7 @@ const DEFAULT_OPTIONS = {
   cache: false,
   cacheLocation: undefined
 }
-
+// drop: mode === 'production' || mode === 'test' ? ['console', 'debugger'] : []
 export default defineConfig(({ mode }) => {
   return {
     build: {