Browse Source

feat(组件): 大数据图表

tangning 2 years ago
parent
commit
c4a420e82c

+ 63 - 0
src/api/statistics/index.ts

@@ -0,0 +1,63 @@
+import { defHttp } from '/@/utils/http/axios';
+import { PageParams, ListGetResultModel, DelParams, roleParams } from './model';
+import { Result } from '/#/axios';
+
+enum Api {
+  buryPointList = '/manage/service/manage/buryPoint/list',
+  buryPointAdd = '/manage/service/manage/buryPoint/add',
+  buryPointDlt = '/manage/service/manage/buryPoint/delete',
+  userTotal = '/manage/service/manage/data/userTotal',
+  orderTotal = '/manage/service/manage/data/orderTotal',
+}
+
+/**
+ * @description: Get sample list value
+ */
+
+export const buryPointList = (params: PageParams) =>
+  defHttp.post<Result>({
+    url: Api.buryPointList,
+    params,
+    data: params,
+    headers: {
+      // @ts-ignore
+      ignoreCancelToken: true,
+    },
+  });
+
+  export const buryPointAdd = (params: PageParams) =>
+  defHttp.post<Result>({
+    url: Api.buryPointAdd,
+    params,
+    data: params,
+    headers: {
+      // @ts-ignore
+      ignoreCancelToken: true,
+    },
+  });
+  export const buryPointDlt = (params: PageParams) =>
+  defHttp.post<Result>({
+    url: Api.buryPointDlt,
+    params,
+    data: params,
+    headers: {
+      // @ts-ignore
+      ignoreCancelToken: true,
+    },
+  });
+  export const userTotal = () =>
+  defHttp.get<Result>({
+    url: Api.userTotal,
+    headers: {
+      // @ts-ignore
+      ignoreCancelToken: true,
+    },
+  });
+  export const orderTotal = () =>
+  defHttp.get<Result>({
+    url: Api.orderTotal,
+    headers: {
+      // @ts-ignore
+      ignoreCancelToken: true,
+    },
+  });

+ 30 - 0
src/api/statistics/model.ts

@@ -0,0 +1,30 @@
+import { BasicPageParams, BasicFetchResult } from '/@/api/model/baseModel';
+/**
+ * @description: Request list interface parameters
+ */
+export type PageParams = BasicPageParams;
+
+export interface DelParams {
+  userId: number;
+  toUserId: number;
+  toUserPhone: string;
+}
+
+export interface roleParams {
+  type: number;
+  roleId: number;
+}
+export interface StaffListItem {
+  id: number;
+
+  name: string;
+  company: string;
+  phone: string;
+  status: string;
+  role: string;
+  createTime: string;
+}
+/**
+ * @description: Request list return value
+ */
+export type ListGetResultModel = BasicFetchResult<StaffListItem>;

+ 106 - 0
src/views/anchor/detailsModal.vue

@@ -0,0 +1,106 @@
+<template>
+  <BasicModal
+    v-bind="$attrs"
+    @cancel="resetFields"
+    @register="register"
+    :title="title"
+    @ok="handleOk"
+  >
+    <div class="pt-3px pr-3px">
+      <BasicForm @register="registerForm" :model="modelRef" />
+    </div>
+  </BasicModal>
+</template>
+<script lang="ts">
+  import { defineComponent, ref } from 'vue';
+  import { buryPointAdd, } from '/@/api/statistics/index';
+  import { BasicModal, useModalInner } from '/@/components/Modal';
+  import { BasicForm, FormSchema, useForm } from '/@/components/Form/index';
+  import { useI18n } from '/@/hooks/web/useI18n';
+  import { useMessage } from '/@/hooks/web/useMessage';
+  // import { useUserStore } from '/@/store/modules/user';
+  const { t } = useI18n();
+  export default defineComponent({
+    components: { BasicModal, BasicForm },
+    props: {
+      userData: { type: Object },
+    },
+    emits: ['ok'],
+    setup(_, context) {
+      const modelRef = ref({});
+      // const userStore = useUserStore();
+      // const userinfo = computed(() => userStore.getUserInfo);
+      const schemas: FormSchema[] = [
+        {
+          field: 'applicationName',
+          component: 'Input',
+          label: '应用名称',
+          colProps: {
+            span: 18,
+          },
+          helpMessage:'创建应用,将自动生成应用key。',
+          componentProps: {
+            maxLength: 15,
+          },
+          rules: [
+            {
+              required: true,
+              // @ts-ignore
+              validator: async (rule, value) => {
+                var reg_tel = /^[a-zA-Z\u4e00-\u9fa5]+$/;
+                // var reg = /\S+@\S+\.\S+/;
+                if (!value) {
+                  return Promise.reject('请输入应用名称');
+                }
+                if (!reg_tel.test(value)) {
+                  /* eslint-disable-next-line */
+                  return Promise.reject('请输入正确的应用名称');
+                }
+                return Promise.resolve();
+              },
+              trigger: 'change',
+            },
+          ],
+        },
+        {
+          field: 'id',
+          component: 'Input',
+          label: 'id',
+          show: false,
+        },
+      ];
+      const title = ref('新 增');
+      const { createMessage } = useMessage();
+      const [registerForm, { validate, resetFields }] = useForm({
+        labelWidth: 120,
+        schemas,
+        showActionButtonGroup: false,
+        actionColOptions: {
+          span: 24,
+        },
+      });
+
+      const [register, { closeModal }] = useModalInner((_) => {
+        resetFields();
+      });
+      async function handleOk() {
+        let data = await validate();
+        let res = await buryPointAdd(data);
+        context && context.emit('ok', res);
+        createMessage.success(t('common.optSuccess'));
+        closeModal();
+        resetFields();
+      }
+
+      return {
+        register,
+        title,
+        schemas,
+        registerForm,
+        modelRef,
+        handleOk,
+        resetFields,
+      };
+    },
+  });
+</script>

+ 208 - 0
src/views/anchor/index.vue

@@ -0,0 +1,208 @@
+<template>
+  <div class="p-4">
+    <BasicTable @register="registerTable">
+      <template #toolbar>
+        <a-button
+          type="primary"
+          @click="handleCreate"
+          >新增</a-button
+        >
+      </template>
+      <!-- , -->
+      <template #action="{ record }">
+        <TableAction
+          :actions="[
+            {
+              label: '删除',
+              color: 'error',
+              onClick:handDelconfirm.bind(null, record),
+            },
+          ]"
+        />
+      </template>
+    </BasicTable>
+    <DetailsModal @register="registerDetail" @ok="reload" />
+    <!--<DelListModal @register="registerDelList" @reload="reload" /> -->
+  </div>
+</template>
+<script lang="ts">
+  import { defineComponent, computed, onMounted, ref, } from 'vue';
+  import { BasicTable, useTable, BasicColumn, FormProps, TableAction } from '/@/components/Table';
+  import { useMessage } from '/@/hooks/web/useMessage';
+  import { useModal } from '/@/components/Modal';
+  import { uploadApi } from '/@/api/sys/upload';
+  import DetailsModal from './detailsModal.vue';
+  import { buryPointList, buryPointDlt } from '/@/api/statistics/index';
+  import { useI18n } from '/@/hooks/web/useI18n';
+  import { RoleEnum } from '/@/enums/roleEnum';
+  import { useGo } from '/@/hooks/web/usePage';
+  import { Time } from '/@/components/Time';
+  import { useUserStore } from '/@/store/modules/user';
+  export default defineComponent({
+    components: {
+      BasicTable,
+      TableAction,
+      Time,
+      DetailsModal,
+      // DelListModal,
+    },
+    setup() {
+      const [register, { openModal }] = useModal();
+      const surplusSubNum = ref({
+        lookNum: 0,
+        shotNum: 0,
+      });
+      const [registerDetail, { openModal: openDetaileModal }] = useModal();
+      const [registerDelList, { openModal: openDelListeModal }] = useModal();
+      const { createConfirm, createMessage } = useMessage();
+      const userStore = useUserStore();
+      // const { getCheckRole } = userStore;
+      const roleList = computed(() => userStore.getRoleList);
+      console.log('getRoleList', roleList);
+      const go = useGo();
+      const { t } = useI18n();
+      onMounted(() => {
+        // getNumByStaffData();
+      });
+
+      const columns: BasicColumn[] = [
+        {
+          title: '应用名称',
+          dataIndex: 'nickName',
+        },
+        {
+          title: '应用key',
+          dataIndex: 'userName',
+        },
+        {
+          title: '操作',
+          align:  'center',
+          dataIndex: '',
+          flag:'ACTION',
+          // ifShow: !getCheckRole('tourist'),
+          slots: { customRender: 'action' },
+          fixed: 'right',
+          width: 150,
+        },
+      ];
+
+      const searchForm: Partial<FormProps> = {
+        labelWidth: 100,
+        schemas: [
+          {
+            field: 'staffName',
+            label: '应用名称',
+            component: 'Input',
+            componentProps: {
+              maxLength: 15,
+            },
+            colProps: {
+              xl: 6,
+              xxl: 6,
+            },
+          },
+        ],
+      };
+
+      const [registerTable, { reload }] = useTable({
+        title: '应用列表',
+        api: buryPointList,
+        columns: columns,
+        useSearchForm: true,
+        formConfig: searchForm,
+        showTableSetting: true,
+        tableSetting: { fullScreen: true },
+        showIndexColumn: false,
+        rowKey: 'id',
+        fetchSetting: {
+          pageField: 'pageNum',
+          sizeField: 'pageSize',
+          listField: 'list',
+          totalField: 'total',
+        },
+        pagination: { pageSize: 20 },
+        afterFetch: (T) => {
+          return T;
+        },
+        bordered: true,
+        sortFn: (sortInfo) => {
+          let order = sortInfo.order && sortInfo.order.replace('end', '');
+          return { ...sortInfo, sidx: sortInfo.field, order: order };
+        },
+      });
+
+      function renderRoleType(type: number): string {
+        switch (type) {
+          case 0:
+            return t('routes.staff.roleType.0');
+          case 1:
+            return t('routes.staff.roleType.1');
+          default:
+            return '';
+        }
+      }
+      function renderStatus(type: number): string {
+        switch (type) {
+          case 1:
+            return t('common.normal');
+          case 0:
+            return t('common.unNormal');
+          default:
+            return '';
+        }
+      }
+      function handleOpenModal(record: Recordable) {
+        openModal(true, record);
+      }
+      function handleCreate() {
+        // if (
+        //   getCheckRole([RoleEnum.COMPANY_ADMIN]) &&
+        //   surplusSubNum.value.lookNum == 0 &&
+        //   surplusSubNum.value.shotNum == 0
+        // ) {
+        //   return createMessage.error('新增失败,可添加员工数量为0个');
+        // }
+        openDetaileModal(true, {});
+      }
+      function handleEdit(record: Recordable) {
+        openDetaileModal(true, {
+          ...record,
+          phone:record.userName,
+        });
+      }
+      function handDelconfirm(record) {
+        createConfirm({
+          iconType: 'warning',
+          title: '警告',
+          content: `此操作将对${record.userName}进行删除, 是否继续?`,
+          onOk: async () => {
+            await buryPointDlt({ userId: record.id });
+            reload();
+            createMessage.success(t('common.optSuccess'));
+          },
+        });
+      }
+      return {
+        registerTable,
+        registerDetail,
+        registerDelList,
+        openDelListeModal,
+        createMessage,
+        handDelconfirm,
+        t,
+        reload,
+        go,
+        renderRoleType,
+        renderStatus,
+        handleCreate,
+        handleOpenModal,
+        register,
+        handleEdit,
+        uploadApi: uploadApi as any,
+        RoleEnum,
+        surplusSubNum,
+        // getCheckRole,
+      };
+    },
+  });
+</script>

+ 40 - 8
src/views/dashboard/analysis/components/lineEcharts.vue

@@ -1,11 +1,28 @@
 <template>
   <Card title="成交占比" :loading="loading">
+    <template #extra>
+      <div class="condition">
+          <div class="selct" style="display: inline-block;">
+            <span style="margin-right:15px">颗粒度</span>
+            <Select
+            v-model:value="value"
+            label-in-value
+            style="width: 100px;margin-right:30px"
+            placeholder="请选择颗粒度"
+            :options="options"
+            @change="handleChange"
+          ></Select>
+          </div>
+          <a-button type="primary" >导出</a-button>
+      </div>
+    </template>
     <div ref="chartRef1" :style="{ width, height }"></div>
   </Card>
 </template>
 <script lang="ts" setup>
+import type { SelectProps } from 'ant-design-vue';
 import { Ref, ref, watch } from 'vue';
-import { Card } from 'ant-design-vue';
+import { Card, Select } from 'ant-design-vue';
 import { useECharts } from '/@/hooks/web/useECharts';
 const colorList = ['#9E87FF', '#73DDFF', '#fe9a8b', '#F56948', '#9E87FF'];
 const shadowColor = ['rgba(158,135,255, 0.3)','rgba(115,221,255, 0.3)','rgba(254,154,139, 0.3)']
@@ -23,13 +40,28 @@ const props = defineProps({
     type:Object,
     default:{
       xData:['北京', '上海', '广州', '深圳', '香港', '澳门', '台湾'],
-      yData:['北京', '上海', '广州', '深圳', '香港', '澳门', '台湾'],
+      yData:[10, 10, 30, 12, 15, 3, 7],
     }
   }
 });
-
-const chartRef = ref<HTMLDivElement | null>(null);
-const { setOptions } = useECharts(chartRef as Ref<HTMLDivElement>);
+const value = ref('a1');
+const options = ref<SelectProps['options']>([
+      {
+        value: 'jack',
+        label: 'Jack (100)',
+      },
+      {
+        value: 'lucy',
+        label: 'Lucy (101)',
+      },
+    ]);
+const chartRef1 = ref<HTMLDivElement | null>(null);
+const xData = ref<string[]>(['北京', '上海', '广州', '深圳', '香港', '澳门', '台湾']);
+const yData = ref<number[]>([10, 10, 30, 12, 15, 3, 7]);
+const { setOptions } = useECharts(chartRef1 as Ref<HTMLDivElement>);
+function handleChange(val){
+  console.log('handleChange',val)
+}
 watch(
   () => props.loading,
   () => {
@@ -60,7 +92,7 @@ watch(
       xAxis: [
         {
           type: 'category',
-          data: props.xAxisData,
+          data: xData.value,
           axisLine: {
             lineStyle: {
               color: '#DCE2E8',
@@ -150,10 +182,10 @@ watch(
         {
           name: 'Adidas',
           type: 'line',
-          data: [10, 10, 30, 12, 15, 3, 7],
+          data: yData.value,
           symbolSize: 1,
-          symbol: 'circle',
           smooth: true,
+          symbol:'none', //加这个
           // yAxisIndex: 0,
           showSymbol: false,
           lineStyle: {

+ 108 - 201
src/views/dashboard/analysis/components/lineEcharts2.vue

@@ -1,224 +1,131 @@
 <template>
   <Card title="成交占比" :loading="loading">
-    <div ref="chartRef2" :style="{ width, height }"></div>
+    <template #extra>
+      <div class="condition">
+          <div class="selct" style="display: inline-block;">
+            <span style="margin-right:15px">颗粒度</span>
+            <Select
+            v-model:value="value"
+            label-in-value
+            style="width: 100px;margin-right:30px"
+            placeholder="请选择颗粒度"
+            :options="options"
+            @change="handleChange"
+          ></Select>
+          </div>
+          <a-button type="primary" >导出</a-button>
+      </div>
+    </template>
+      <div ref="chartRef" :style="{ height, width }"></div>
   </Card>
 </template>
+<script lang="ts">
+  import { basicProps } from './props';
+  // import { dateUtil } from '/@/utils/dateUtil';
+  import { Card, Select } from 'ant-design-vue';
+</script>
 <script lang="ts" setup>
-import { Ref, ref, watch } from 'vue';
-import { Card } from 'ant-design-vue';
-import { useECharts } from '/@/hooks/web/useECharts';
-const colorList = ['#9E87FF', '#73DDFF', '#fe9a8b', '#F56948', '#9E87FF'];
-const shadowColor = ['rgba(158,135,255, 0.3)','rgba(115,221,255, 0.3)','rgba(254,154,139, 0.3)']
-const props = defineProps({
+  import { ref, Ref, watch } from 'vue';
+  // import type { dataItemType } from './props';
+  import { useECharts } from '/@/hooks/web/useECharts';
+  const props = defineProps({
   loading: Boolean,
-  width: {
-    type: String as PropType<string>,
-    default: '100%',
-  },
-  height: {
-    type: String as PropType<string>,
-    default: '300px',
-  },
-  propsData:{
-    type:Object,
-    default:{
-      xData:['北京', '上海', '广州', '深圳', '香港', '澳门', '台湾'],
-      yData:['北京', '上海', '广州', '深圳', '香港', '澳门', '台湾'],
-    }
+    ...basicProps,
+  });
+  const value = ref('a1');
+  const options = ref<SelectProps['options']>([
+        {
+          value: 'jack',
+          label: 'Jack (100)',
+        },
+        {
+          value: 'lucy',
+          label: 'Lucy (101)',
+        },
+      ]);
+  const viewStaticsData = ref<number[]>([1,5,6,8,55,1,5,6,8,1]);
+  const shareStaticsData = ref<number[]>([2,55,10,2,6,1,5,6,8,1]);
+  const yixStringData = ref<string[]>(['11','22','33','44','ss','11','22','33','44','ss']);
+  const maxSize = ref(0);
+  const chartRef = ref<HTMLDivElement | null>(null);
+  const { setOptions } = useECharts(chartRef as Ref<HTMLDivElement>);
+  function handleChange(val){
+    console.log('handleChange',val)
   }
-});
-
-const chartRef = ref<HTMLDivElement | null>(null);
-const { setOptions } = useECharts(chartRef as Ref<HTMLDivElement>);
-watch(
-  () => props.propsData,
-  (value) => {
+  function handlesetOptions() {
     setOptions({
       tooltip: {
         trigger: 'axis',
         axisPointer: {
-          label: {
-            show: true,
-            backgroundColor: '#fff',
-            color: '#556677',
-            borderColor: 'rgba(0,0,0,0)',
-            shadowColor: 'rgba(0,0,0,0)',
-            shadowOffsetY: 0,
-          },
           lineStyle: {
-            width: 0,
+            width: 1,
+            color: '#019680',
           },
         },
-        backgroundColor: '#fff',
-        textStyle: {
-          color: '#5c6c7c',
-        },
-        padding: [10, 10],
-        extraCssText: 'box-shadow: 1px 0 2px 0 rgba(163,163,163,0.5)',
       },
-
-      xAxis: [
-        {
-          type: 'category',
-          data: props.xAxisData,
-          axisLine: {
-            lineStyle: {
-              color: '#DCE2E8',
-            },
-          },
-          axisTick: {
-            show: false,
-          },
-          axisLabel: {
-            interval: 0,
-            textStyle: {
-              color: '#556677',
-            },
-            // 默认x轴字体大小
-            fontSize: 12,
-            // margin:文字到x轴的距离
-            margin: 15,
-          },
-          axisPointer: {
-            label: {
-              // padding: [11, 5, 7],
-              padding: [0, 0, 10, 0],
-              margin: 15,
-              // 移入时的字体大小
-              fontSize: 12,
-              backgroundColor: {
-                type: 'linear',
-                x: 0,
-                y: 0,
-                x2: 0,
-                y2: 1,
-                colorStops: [
-                  {
-                    offset: 0,
-                    color: '#fff', // 0% 处的颜色
-                  },
-                  {
-                    // offset: 0.9,
-                    offset: 0.86,
-                    color: '#fff', // 0% 处的颜色
-                  },
-                  {
-                    offset: 0.86,
-                    color: '#33c0cd', // 0% 处的颜色
-                  },
-                  {
-                    offset: 1,
-                    color: '#33c0cd', // 100% 处的颜色
-                  },
-                ],
-                global: false, // 缺省为 false
-              },
-            },
-          },
-          boundaryGap: false,
-        },
-      ],
-      yAxis: [
-        {
-          type: 'value',
-          axisTick: {
-            show: false,
-          },
-          splitLine: {
-            show: true,
-            lineStyle: {
-              color: ['#f1f4f8'],
-              width: 1,
-              type: 'solid',
-            },
-          },
-          axisLabel: {
-            textStyle: {
-              color: '#556677',
-            },
-            formatter: '{value}',
-          },
-          axisLine: {
-            show: false,
-            lineStyle: {
-              color: '#DCE2E8',
-            },
-          },
-        },
-      ],
+      legend: {
+        orient: 'horizontal',
+        bottom: 0,
+      },
+      // grid: { left: '2%', right: '2%', top: '10%', bottom: '10%', containLabel: true },
+      xAxis: {
+        type: 'category',
+        // data: [...new Array(30)].map((_item, index) => `${index + 1}日`),
+        data: yixStringData.value,
+      },
+      yAxis: {
+        type: 'value',
+        max: maxSize.value,
+        splitNumber: 4,
+      },
       series: [
         {
-          name: 'Adidas',
-          type: 'line',
-          data: [10, 10, 30, 12, 15, 3, 7],
-          symbolSize: 1,
-          symbol: 'circle',
-          smooth: true,
-          // yAxisIndex: 0,
-          showSymbol: false,
-          lineStyle: {
-            width: 5,
-            color: colorList[0],
-            shadowColor: shadowColor[0],
-            shadowBlur: 10,
-            shadowOffsetY: 20,
-          },
-          itemStyle: {
-            // normal: {
-            //     color: colorList[0],
-            //     borderColor: colorList[0]
-            // }
-          },
-        },
-        {
-          name: 'Nike',
-          type: 'line',
-          data: [5, 12, 11, 14, 25, 16, 10],
-          symbolSize: 1,
-          symbol: 'circle',
-          smooth: true,
-          // yAxisIndex: 0,
-          showSymbol: false,
-          lineStyle: {
-            width: 5,
-            color: colorList[1],
-            shadowColor: shadowColor[1],
-            shadowBlur: 10,
-            shadowOffsetY: 20,
-          },
-          itemStyle: {
-            // normal: {
-            //     color: colorList[1],
-            //     borderColor: colorList[1]
-            // }
-          },
+          data: viewStaticsData.value,
+          type: 'bar',
+          itemStyle: { color: '#38a0ff' },
+          barMaxWidth: 80,
+          name: '用户浏览量',
         },
         {
-          name: '老北京布鞋',
-          type: 'line',
-          data: [150, 120, 170, 140, 500, 160, 110],
-          symbolSize: 1,
-          // yAxisIndex: 1,
-          symbol: 'circle',
-          smooth: true,
-          showSymbol: false,
-          lineStyle: {
-            width: 5,
-            color: colorList[2],
-            shadowColor: shadowColor[2],
-            shadowBlur: 10,
-            shadowOffsetY: 20,
-          },
-          itemStyle: {
-            // normal: {
-            //     color: colorList[2],
-            //     borderColor: colorList[2]
-            // }
-          },
+          data: shareStaticsData.value,
+          type: 'bar',
+          itemStyle: { color: '#4cca73' },
+          barMaxWidth: 80,
+          name: '用户分享数',
         },
       ],
     });
-  },
-  { immediate: true,deep:true },
-);
+  }
+  // props.viewStatics,
+  watch(
+    // () => [props.viewStatics, props.shareStatics],
+    // ([data1, data2]) => {
+    () => props.loading,
+    () => {
+      console.log('viewStatics-data');
+      // viewStaticsData.value = data1.reduce<number[]>(
+      //   (prev: number[], current) => prev.concat(Number(current.amount)),
+      //   [],
+      // );
+
+      // yixStringData.value = data1.reduce<string[]>(
+      //   (prev: string[], current) => prev.concat(current.date),
+      //   [],
+      // );
+      // shareStaticsData.value = data2.reduce<number[]>(
+      //   (prev: number[], current) => prev.concat(Number(current.amount)),
+      //   [],
+      // );
+
+      const maxNumber = Math.max(...viewStaticsData.value.concat(shareStaticsData.value));
+      const pow = Math.pow(10, maxNumber.toString().length - 1);
+      maxSize.value = maxNumber > 10 ? Math.floor(maxNumber / 10) * 10 + pow * 2 : 10;
+      console.log('maxSize', maxSize.value);
+      handlesetOptions();
+    },
+    {
+      immediate: true,
+      deep: true,
+    },
+  );
 </script>

+ 20 - 0
src/views/dashboard/analysis/components/props.ts

@@ -4,6 +4,10 @@ export interface BasicProps {
   width: string;
   height: string;
 }
+export type dataItemType = {
+  date: string;
+  amount: string | number;
+};
 export const basicProps = {
   width: {
     type: String as PropType<string>,
@@ -13,4 +17,20 @@ export const basicProps = {
     type: String as PropType<string>,
     default: '280px',
   },
+  viewStatics: {
+    type: Array as PropType<Array<dataItemType>>,
+    default: [],
+  },
+  shareStatics: {
+    type: Array as PropType<Array<dataItemType>>,
+    default: [],
+  },
+  bulletChatAmounts: {
+    type: Array as PropType<Array<dataItemType>>,
+    default: [],
+  },
+  userAmount: {
+    type: Array as PropType<Array<dataItemType>>,
+    default: [],
+  },
 };

+ 27 - 5
src/views/dashboard/analysis/index.vue

@@ -1,9 +1,9 @@
 <template>
   <div class="p-4">
-    <GrowCard :loading="loading" class="enter-y" />
+    <GrowCard :loading="loading" class="enter-y" :list="list" />
     <div class="md:flex !my-4 enter-y">
-      <lineEcharts class="md:w-1/2 w-full" name="chartRef1" :loading="loading"  />
-      <lineEcharts2  name="chartRef2"  class="md:w-1/2 !md:mx-4 !md:my-0 !my-4 w-full" :loading="loading" />
+      <lineEcharts class="md:w-1/2 w-full !md:mt-0 !mt-4 !md:mr-4" name="chartRef1" :loading="loading"  />
+      <lineEcharts2  name="chartRef2" class="md:w-1/2 mx-4 w-full" :loading="loading" />
     </div>
     <SiteAnalysis class="!my-4 enter-y" :loading="loading" />
     <div class="md:flex enter-y">
@@ -14,7 +14,8 @@
   </div>
 </template>
 <script lang="ts" setup>
-  import { ref } from 'vue';
+  import { ref, onMounted } from 'vue';
+  import { userTotal } from '/@/api/statistics/index';
   import GrowCard from './components/GrowCard.vue';
   import SiteAnalysis from './components/SiteAnalysis.vue';
   import VisitSource from './components/VisitSource.vue';
@@ -24,7 +25,28 @@
   import SalesProductPie from './components/SalesProductPie.vue';
   import { Row, Col  } from 'ant-design-vue';
   const loading = ref(true);
-
+  interface GrowCardItem {
+    icon: string;
+    title: string;
+    value: number;
+    unit: string;
+    color: string;
+    action: string;
+  }
+  onMounted(() => {
+    getData();
+  });
+  const list = ref<GrowCardItem[]>([]);
+  async function getData() {
+    try {
+      loading.value = true;
+      const Total = await userTotal();
+      console.log('sData', Total);
+      loading.value = false;
+    } catch (error) {
+      loading.value = false;
+    }
+  }
   setTimeout(() => {
     loading.value = false;
   }, 1500);

+ 132 - 0
src/views/operate/agent.vue

@@ -0,0 +1,132 @@
+<template>
+  <PageWrapper  contentBackground>
+    <template #footer>
+      <a-tabs v-model:activeKey="state" @change="changeTable">
+        <a-tab-pane :key="1" tab="待处理" />
+        <a-tab-pane :key="0" tab="已处理" />
+      </a-tabs>
+    </template>
+
+    <div class="pt-4 m-4 desc-wrap">
+      <BasicTable @register="registerTimeTable" >
+        
+        <template #action="{ record }">
+          <TableAction
+            stopButtonPropagation
+            :actions="[
+              {
+                label: '处理',
+                icon: 'icon-park-outline:door-handle',
+                ifShow:record.state == 1,
+                onClick: handleWithdraw.bind(null, record),
+              },
+            ]"
+          />
+      </template>
+      </BasicTable>
+    </div>
+    <addMessgeModal @register="register" @update="reload" />
+  </PageWrapper>
+</template>
+<script lang="ts">
+  import { defineComponent, ref } from 'vue';
+  import { BasicTable, useTable, TableAction, FormProps } from '/@/components/Table';
+  import { PageWrapper } from '/@/components/Page';
+  import { Divider, Card, Empty, Descriptions, Steps, Tabs } from 'ant-design-vue';
+  import { intercomMessageList, intercomMessageHandle } from '/@/api/operate'
+  import { useI18n } from '/@/hooks/web/useI18n';
+  import addMessgeModal from './components/message/addModal.vue'
+  import { useModal } from '/@/components/Modal';
+  import { refundTimeTableSchema, refundTimeTableData } from './data';
+  export default defineComponent({
+    components: {
+      BasicTable,
+      PageWrapper,
+      TableAction,
+      addMessgeModal,
+      [Divider.name]: Divider,
+      [Card.name]: Card,
+      Empty,
+      [Descriptions.name]: Descriptions,
+      [Descriptions.Item.name]: Descriptions.Item,
+      [Steps.name]: Steps,
+      [Steps.Step.name]: Steps.Step,
+      [Tabs.name]: Tabs,
+      [Tabs.TabPane.name]: Tabs.TabPane,
+    },
+    setup() {
+      const { t } = useI18n();
+      const [register, { openModal }] = useModal();
+      const state = ref<number>(1); //未处理,0已处理(默认1)
+      const searchForm: Partial<FormProps> = {
+        labelWidth: 100,
+        schemas: [
+          {
+            field: 'sceneName',
+            label: '提交时间',
+            component: 'RangePicker',
+            componentProps: {
+              maxLength: 100,
+              format: 'YYYY-MM-DD HH:mm',
+              showTime: true,
+            },
+            colProps: {
+              xl: 11,
+              xxl: 11,
+            },
+          },
+          {
+            field: 'type',
+            label: t('routes.operate.newsTitle'),
+            component: 'Input',
+            colProps: {
+              xl: 6,
+              xxl: 6,
+            },
+          },
+        ],
+      };
+      const [registerTimeTable,{reload}] = useTable({
+        api: intercomMessageList,
+        // title: '',
+        rowKey: 'id',
+        fetchSetting: {
+          pageField: 'pageNum',
+          sizeField: 'pageSize',
+          listField: 'list',
+          totalField: 'total',
+        },
+        searchInfo: { state },
+        columns: refundTimeTableSchema,
+        formConfig: searchForm,
+        dataSource: refundTimeTableData,
+        showIndexColumn: false,
+        // pagination: { pageSize: 20 },
+        scroll: { y: 300 },
+        actionColumn: {
+          width: 100,
+          title: '操作',
+          ifShow:state.value == 1,
+          dataIndex: 'action',
+          slots: { customRender: 'action' },
+        },
+      });
+      function changeTable(val: string) {
+        state.value = val;
+        reload();
+      }
+      async function handleWithdraw(record: Recordable) {
+        openModal(true,record)
+      }
+      return {
+        registerTimeTable,
+        handleWithdraw,
+        changeTable,
+        state,
+        reload,
+        register,
+        openModal,
+      };
+    },
+  });
+</script>

+ 132 - 0
src/views/operate/sceneList.vue

@@ -0,0 +1,132 @@
+<template>
+  <PageWrapper  contentBackground>
+    <template #footer>
+      <a-tabs v-model:activeKey="state" @change="changeTable">
+        <a-tab-pane :key="1" tab="待处理" />
+        <a-tab-pane :key="0" tab="已处理" />
+      </a-tabs>
+    </template>
+
+    <div class="pt-4 m-4 desc-wrap">
+      <BasicTable @register="registerTimeTable" >
+        
+        <template #action="{ record }">
+          <TableAction
+            stopButtonPropagation
+            :actions="[
+              {
+                label: '处理',
+                icon: 'icon-park-outline:door-handle',
+                ifShow:record.state == 1,
+                onClick: handleWithdraw.bind(null, record),
+              },
+            ]"
+          />
+      </template>
+      </BasicTable>
+    </div>
+    <addMessgeModal @register="register" @update="reload" />
+  </PageWrapper>
+</template>
+<script lang="ts">
+  import { defineComponent, ref } from 'vue';
+  import { BasicTable, useTable, TableAction, FormProps } from '/@/components/Table';
+  import { PageWrapper } from '/@/components/Page';
+  import { Divider, Card, Empty, Descriptions, Steps, Tabs } from 'ant-design-vue';
+  import { intercomMessageList, intercomMessageHandle } from '/@/api/operate'
+  import { useI18n } from '/@/hooks/web/useI18n';
+  import addMessgeModal from './components/message/addModal.vue'
+  import { useModal } from '/@/components/Modal';
+  import { refundTimeTableSchema, refundTimeTableData } from './data';
+  export default defineComponent({
+    components: {
+      BasicTable,
+      PageWrapper,
+      TableAction,
+      addMessgeModal,
+      [Divider.name]: Divider,
+      [Card.name]: Card,
+      Empty,
+      [Descriptions.name]: Descriptions,
+      [Descriptions.Item.name]: Descriptions.Item,
+      [Steps.name]: Steps,
+      [Steps.Step.name]: Steps.Step,
+      [Tabs.name]: Tabs,
+      [Tabs.TabPane.name]: Tabs.TabPane,
+    },
+    setup() {
+      const { t } = useI18n();
+      const [register, { openModal }] = useModal();
+      const state = ref<number>(1); //未处理,0已处理(默认1)
+      const searchForm: Partial<FormProps> = {
+        labelWidth: 100,
+        schemas: [
+          {
+            field: 'sceneName',
+            label: '提交时间',
+            component: 'RangePicker',
+            componentProps: {
+              maxLength: 100,
+              format: 'YYYY-MM-DD HH:mm',
+              showTime: true,
+            },
+            colProps: {
+              xl: 11,
+              xxl: 11,
+            },
+          },
+          {
+            field: 'type',
+            label: t('routes.operate.newsTitle'),
+            component: 'Input',
+            colProps: {
+              xl: 6,
+              xxl: 6,
+            },
+          },
+        ],
+      };
+      const [registerTimeTable,{reload}] = useTable({
+        api: intercomMessageList,
+        // title: '',
+        rowKey: 'id',
+        fetchSetting: {
+          pageField: 'pageNum',
+          sizeField: 'pageSize',
+          listField: 'list',
+          totalField: 'total',
+        },
+        searchInfo: { state },
+        columns: refundTimeTableSchema,
+        formConfig: searchForm,
+        dataSource: refundTimeTableData,
+        showIndexColumn: false,
+        // pagination: { pageSize: 20 },
+        scroll: { y: 300 },
+        actionColumn: {
+          width: 100,
+          title: '操作',
+          ifShow:state.value == 1,
+          dataIndex: 'action',
+          slots: { customRender: 'action' },
+        },
+      });
+      function changeTable(val: string) {
+        state.value = val;
+        reload();
+      }
+      async function handleWithdraw(record: Recordable) {
+        openModal(true,record)
+      }
+      return {
+        registerTimeTable,
+        handleWithdraw,
+        changeTable,
+        state,
+        reload,
+        register,
+        openModal,
+      };
+    },
+  });
+</script>

+ 26 - 0
src/views/statistics/camera/index.vue

@@ -0,0 +1,26 @@
+<template>
+  <div class="p-4">
+    <GrowCard :loading="loading" :list="growCardList" class="enter-y" />
+    <!-- <div class="md:flex !my-4 enter-y">
+      <lineEcharts class="md:w-1/2 w-full !md:mt-0 !mt-4 !md:mr-4" name="chartRef1" :loading="loading"  />
+      <lineEcharts2  name="chartRef2" class="md:w-1/2 mx-4 w-full" :loading="loading" />
+    </div> -->
+    <lineEcharts2 class="!my-4 enter-y" :loading="loading" />
+    <!-- <div class="md:flex enter-y">
+      <VisitRadar class="md:w-1/3 w-full" :loading="loading" />
+      <VisitSource class="md:w-1/3 !md:mx-4 !md:my-0 !my-4 w-full" :loading="loading" />
+      <SalesProductPie class="md:w-1/3 w-full" :loading="loading" />
+    </div> -->
+  </div>
+</template>
+<script lang="ts" setup>
+  import { ref } from 'vue';
+  import { growCardList } from '../data';
+  import GrowCard from '../components/GrowCard.vue';
+  import lineEcharts2 from '../components/lineEcharts2.vue';
+  const loading = ref(true);
+
+  setTimeout(() => {
+    loading.value = false;
+  }, 1500);
+</script>

+ 44 - 0
src/views/statistics/components/GrowCard.vue

@@ -0,0 +1,44 @@
+<template>
+  <div class="md:flex">
+    <template v-for="(item, index) in list" :key="item.title">
+      <Card
+        size="small"
+        :loading="loading"
+        :title="item.title"
+        class="md:w-1/4 w-full !md:mt-0 !mt-4"
+        :class="[index + 1 < 6 && '!md:mr-4']"
+        :canExpan="false"
+      >
+        <template #extra>
+          <Tag :color="item.color">{{ item.action }}</Tag>
+        </template>
+
+        <div class="py-4 px-4 flex justify-between">
+          <CountTo :prefix="item.unit" :startVal="1" :endVal="item.value" class="text-2xl" />
+          <Icon :icon="item.icon" :size="40" />
+        </div>
+
+        <!-- <div class="p-2 px-4 flex justify-between">
+          <span>总{{ item.title }}</span>
+          <CountTo prefix="$" :startVal="1" :endVal="item.total" />
+        </div> -->
+      </Card>
+    </template>
+  </div>
+</template>
+<script lang="ts" setup>
+  import { CountTo } from '/@/components/CountTo/index';
+  import { Icon } from '/@/components/Icon';
+  import { Tag, Card } from 'ant-design-vue';
+  import { GrowCardItem } from '../data';
+  
+  defineProps({
+    loading: {
+      type: Boolean,
+    },
+    list: {
+      type: Array as PropType<Array<GrowCardItem>>,
+      default: [],
+    },
+  });
+</script>

+ 63 - 0
src/views/statistics/components/SalesProductPie.vue

@@ -0,0 +1,63 @@
+<template>
+  <Card title="成交占比" :loading="loading">
+    <div ref="chartRef" :style="{ width, height }"></div>
+  </Card>
+</template>
+<script lang="ts" setup>
+  import { Ref, ref, watch } from 'vue';
+  import { Card } from 'ant-design-vue';
+  import { useECharts } from '/@/hooks/web/useECharts';
+
+  const props = defineProps({
+    loading: Boolean,
+    width: {
+      type: String as PropType<string>,
+      default: '100%',
+    },
+    height: {
+      type: String as PropType<string>,
+      default: '300px',
+    },
+  });
+
+  const chartRef = ref<HTMLDivElement | null>(null);
+  const { setOptions } = useECharts(chartRef as Ref<HTMLDivElement>);
+  watch(
+    () => props.loading,
+    () => {
+      if (props.loading) {
+        return;
+      }
+      setOptions({
+        tooltip: {
+          trigger: 'item',
+        },
+
+        series: [
+          {
+            name: '访问来源',
+            type: 'pie',
+            radius: '80%',
+            center: ['50%', '50%'],
+            color: ['#5ab1ef', '#b6a2de', '#67e0e3', '#2ec7c9'],
+            data: [
+              { value: 500, name: '电子产品' },
+              { value: 310, name: '服装' },
+              { value: 274, name: '化妆品' },
+              { value: 400, name: '家居' },
+            ].sort(function (a, b) {
+              return a.value - b.value;
+            }),
+            roseType: 'radius',
+            animationType: 'scale',
+            animationEasing: 'exponentialInOut',
+            animationDelay: function () {
+              return Math.random() * 400;
+            },
+          },
+        ],
+      });
+    },
+    { immediate: true },
+  );
+</script>

+ 38 - 0
src/views/statistics/components/SiteAnalysis.vue

@@ -0,0 +1,38 @@
+<template>
+  <Card
+    :tab-list="tabListTitle"
+    v-bind="$attrs"
+    :active-tab-key="activeKey"
+    @tabChange="onTabChange"
+  >
+    <p v-if="activeKey === 'tab1'">
+      <VisitAnalysis />
+    </p>
+    <p v-if="activeKey === 'tab2'">
+      <VisitAnalysisBar />
+    </p>
+  </Card>
+</template>
+<script lang="ts" setup>
+  import { ref } from 'vue';
+  import { Card } from 'ant-design-vue';
+  import VisitAnalysis from './VisitAnalysis.vue';
+  import VisitAnalysisBar from './VisitAnalysisBar.vue';
+
+  const activeKey = ref('tab1');
+
+  const tabListTitle = [
+    {
+      key: 'tab1',
+      tab: '流量趋势',
+    },
+    {
+      key: 'tab2',
+      tab: '访问量',
+    },
+  ];
+
+  function onTabChange(key) {
+    activeKey.value = key;
+  }
+</script>

+ 106 - 0
src/views/statistics/components/VisitAnalysis.vue

@@ -0,0 +1,106 @@
+<template>
+  <div ref="chartRef" :style="{ height, width }"></div>
+</template>
+<script lang="ts" setup>
+  import { onMounted, ref, Ref } from 'vue';
+  import { useECharts } from '/@/hooks/web/useECharts';
+  import { basicProps } from './props';
+
+  defineProps({
+    ...basicProps,
+  });
+  const chartRef = ref<HTMLDivElement | null>(null);
+  const { setOptions } = useECharts(chartRef as Ref<HTMLDivElement>);
+
+  onMounted(() => {
+    setOptions({
+      tooltip: {
+        trigger: 'axis',
+        axisPointer: {
+          lineStyle: {
+            width: 1,
+            color: '#019680',
+          },
+        },
+      },
+      xAxis: {
+        type: 'category',
+        boundaryGap: false,
+        data: [
+          '6:00',
+          '7:00',
+          '8:00',
+          '9:00',
+          '10:00',
+          '11:00',
+          '12:00',
+          '13:00',
+          '14:00',
+          '15:00',
+          '16:00',
+          '17:00',
+          '18:00',
+          '19:00',
+          '20:00',
+          '21:00',
+          '22:00',
+          '23:00',
+        ],
+        splitLine: {
+          show: true,
+          lineStyle: {
+            width: 1,
+            type: 'solid',
+            color: 'rgba(226,226,226,0.5)',
+          },
+        },
+        axisTick: {
+          show: false,
+        },
+      },
+      yAxis: [
+        {
+          type: 'value',
+          max: 80000,
+          splitNumber: 4,
+          axisTick: {
+            show: false,
+          },
+          splitArea: {
+            show: true,
+            areaStyle: {
+              color: ['rgba(255,255,255,0.2)', 'rgba(226,226,226,0.2)'],
+            },
+          },
+        },
+      ],
+      grid: { left: '1%', right: '1%', top: '2  %', bottom: 0, containLabel: true },
+      series: [
+        {
+          smooth: true,
+          data: [
+            111, 222, 4000, 18000, 33333, 55555, 66666, 33333, 14000, 36000, 66666, 44444, 22222,
+            11111, 4000, 2000, 500, 333, 222, 111,
+          ],
+          type: 'line',
+          areaStyle: {},
+          itemStyle: {
+            color: '#5ab1ef',
+          },
+        },
+        {
+          smooth: true,
+          data: [
+            33, 66, 88, 333, 3333, 5000, 18000, 3000, 1200, 13000, 22000, 11000, 2221, 1201, 390,
+            198, 60, 30, 22, 11,
+          ],
+          type: 'line',
+          areaStyle: {},
+          itemStyle: {
+            color: '#019680',
+          },
+        },
+      ],
+    });
+  });
+</script>

+ 58 - 0
src/views/statistics/components/VisitAnalysisBar.vue

@@ -0,0 +1,58 @@
+<template>
+  <div ref="chartRef" :style="{ height, width }"></div>
+</template>
+<script lang="ts" setup>
+  import { onMounted, ref, Ref } from 'vue';
+  import { useECharts } from '/@/hooks/web/useECharts';
+  import { basicProps } from './props';
+
+  defineProps({
+    ...basicProps,
+  });
+
+  const chartRef = ref<HTMLDivElement | null>(null);
+  const { setOptions } = useECharts(chartRef as Ref<HTMLDivElement>);
+  onMounted(() => {
+    setOptions({
+      tooltip: {
+        trigger: 'axis',
+        axisPointer: {
+          lineStyle: {
+            width: 1,
+            color: '#019680',
+          },
+        },
+      },
+      grid: { left: '1%', right: '1%', top: '2  %', bottom: 0, containLabel: true },
+      xAxis: {
+        type: 'category',
+        data: [
+          '1月',
+          '2月',
+          '3月',
+          '4月',
+          '5月',
+          '6月',
+          '7月',
+          '8月',
+          '9月',
+          '10月',
+          '11月',
+          '12月',
+        ],
+      },
+      yAxis: {
+        type: 'value',
+        max: 8000,
+        splitNumber: 4,
+      },
+      series: [
+        {
+          data: [3000, 2000, 3333, 5000, 3200, 4200, 3200, 2100, 3000, 5100, 6000, 3200, 4800],
+          type: 'bar',
+          barMaxWidth: 80,
+        },
+      ],
+    });
+  });
+</script>

+ 100 - 0
src/views/statistics/components/VisitRadar.vue

@@ -0,0 +1,100 @@
+<template>
+  <Card title="转化率" :loading="loading">
+    <div ref="chartRef" :style="{ width, height }"></div>
+  </Card>
+</template>
+<script lang="ts" setup>
+  import { Ref, ref, watch } from 'vue';
+  import { Card } from 'ant-design-vue';
+  import { useECharts } from '/@/hooks/web/useECharts';
+
+  const props = defineProps({
+    loading: Boolean,
+    width: {
+      type: String as PropType<string>,
+      default: '100%',
+    },
+    height: {
+      type: String as PropType<string>,
+      default: '300px',
+    },
+  });
+
+  const chartRef = ref<HTMLDivElement | null>(null);
+  const { setOptions } = useECharts(chartRef as Ref<HTMLDivElement>);
+  watch(
+    () => props.loading,
+    () => {
+      if (props.loading) {
+        return;
+      }
+      setOptions({
+        legend: {
+          bottom: 0,
+          data: ['访问', '购买'],
+        },
+        tooltip: {},
+        radar: {
+          radius: '60%',
+          splitNumber: 8,
+          indicator: [
+            {
+              text: '电脑',
+              max: 100,
+            },
+            {
+              text: '充电器',
+              max: 100,
+            },
+            {
+              text: '耳机',
+              max: 100,
+            },
+            {
+              text: '手机',
+              max: 100,
+            },
+            {
+              text: 'Ipad',
+              max: 100,
+            },
+            {
+              text: '耳机',
+              max: 100,
+            },
+          ],
+        },
+        series: [
+          {
+            type: 'radar',
+            symbolSize: 0,
+            areaStyle: {
+              shadowBlur: 0,
+              shadowColor: 'rgba(0,0,0,.2)',
+              shadowOffsetX: 0,
+              shadowOffsetY: 10,
+              opacity: 1,
+            },
+            data: [
+              {
+                value: [90, 50, 86, 40, 50, 20],
+                name: '访问',
+                itemStyle: {
+                  color: '#b6a2de',
+                },
+              },
+              {
+                value: [70, 75, 70, 76, 20, 85],
+                name: '购买',
+                itemStyle: {
+                  color: '#5ab1ef',
+                },
+              },
+            ],
+          },
+        ],
+      });
+    },
+    { immediate: true },
+  );
+</script>

+ 80 - 0
src/views/statistics/components/VisitSource.vue

@@ -0,0 +1,80 @@
+<template>
+  <Card title="访问来源" :loading="loading">
+    <div ref="chartRef" :style="{ width, height }"></div>
+  </Card>
+</template>
+<script lang="ts" setup>
+  import { Ref, ref, watch } from 'vue';
+  import { Card } from 'ant-design-vue';
+  import { useECharts } from '/@/hooks/web/useECharts';
+  const props = defineProps({
+    loading: Boolean,
+    width: {
+      type: String as PropType<string>,
+      default: '100%',
+    },
+    height: {
+      type: String as PropType<string>,
+      default: '300px',
+    },
+  });
+  const chartRef = ref<HTMLDivElement | null>(null);
+  const { setOptions } = useECharts(chartRef as Ref<HTMLDivElement>);
+  watch(
+    () => props.loading,
+    () => {
+      if (props.loading) {
+        return;
+      }
+      setOptions({
+        tooltip: {
+          trigger: 'item',
+        },
+        legend: {
+          bottom: '1%',
+          left: 'center',
+        },
+        series: [
+          {
+            color: ['#5ab1ef', '#b6a2de', '#67e0e3', '#2ec7c9'],
+            name: '访问来源',
+            type: 'pie',
+            radius: ['40%', '70%'],
+            avoidLabelOverlap: false,
+            itemStyle: {
+              borderRadius: 10,
+              borderColor: '#fff',
+              borderWidth: 2,
+            },
+            label: {
+              show: false,
+              position: 'center',
+            },
+            emphasis: {
+              label: {
+                show: true,
+                fontSize: '12',
+                fontWeight: 'bold',
+              },
+            },
+            labelLine: {
+              show: false,
+            },
+            data: [
+              { value: 1048, name: '搜索引擎' },
+              { value: 735, name: '直接访问' },
+              { value: 580, name: '邮件营销' },
+              { value: 484, name: '联盟广告' },
+            ],
+            animationType: 'scale',
+            animationEasing: 'exponentialInOut',
+            animationDelay: function () {
+              return Math.random() * 100;
+            },
+          },
+        ],
+      });
+    },
+    { immediate: true },
+  );
+</script>

+ 210 - 0
src/views/statistics/components/lineEcharts.vue

@@ -0,0 +1,210 @@
+<template>
+  <Card title="成交占比" :loading="loading">
+    <template #extra>
+      <div class="condition">
+          <div class="selct" style="display: inline-block;">
+            <span style="margin-right:15px">颗粒度</span>
+            <Select
+            v-model:value="value"
+            label-in-value
+            style="width: 100px;margin-right:30px"
+            placeholder="请选择颗粒度"
+            :options="options"
+            @change="handleChange"
+          ></Select>
+          </div>
+          <a-button type="primary" >导出</a-button>
+      </div>
+    </template>
+    <div ref="chartRef1" :style="{ width, height }"></div>
+  </Card>
+</template>
+<script lang="ts" setup>
+import type { SelectProps } from 'ant-design-vue';
+import { Ref, ref, watch } from 'vue';
+import { Card, Select } from 'ant-design-vue';
+import { useECharts } from '/@/hooks/web/useECharts';
+const colorList = ['#9E87FF', '#73DDFF', '#fe9a8b', '#F56948', '#9E87FF'];
+const shadowColor = ['rgba(158,135,255, 0.3)','rgba(115,221,255, 0.3)','rgba(254,154,139, 0.3)']
+const props = defineProps({
+  loading: Boolean,
+  width: {
+    type: String as PropType<string>,
+    default: '100%',
+  },
+  height: {
+    type: String as PropType<string>,
+    default: '300px',
+  },
+  propsData:{
+    type:Object,
+    default:{
+      xData:['北京', '上海', '广州', '深圳', '香港', '澳门', '台湾'],
+      yData:[10, 10, 30, 12, 15, 3, 7],
+    }
+  }
+});
+const value = ref('a1');
+const options = ref<SelectProps['options']>([
+      {
+        value: 'jack',
+        label: 'Jack (100)',
+      },
+      {
+        value: 'lucy',
+        label: 'Lucy (101)',
+      },
+    ]);
+const chartRef1 = ref<HTMLDivElement | null>(null);
+const xData = ref<string[]>(['北京', '上海', '广州', '深圳', '香港', '澳门', '台湾']);
+const yData = ref<number[]>([10, 10, 30, 12, 15, 3, 7]);
+const { setOptions } = useECharts(chartRef1 as Ref<HTMLDivElement>);
+function handleChange(val){
+  console.log('handleChange',val)
+}
+watch(
+  () => props.loading,
+  () => {
+    setOptions({
+      tooltip: {
+        trigger: 'axis',
+        axisPointer: {
+          label: {
+            show: true,
+            backgroundColor: '#fff',
+            color: '#556677',
+            borderColor: 'rgba(0,0,0,0)',
+            shadowColor: 'rgba(0,0,0,0)',
+            shadowOffsetY: 0,
+          },
+          lineStyle: {
+            width: 0,
+          },
+        },
+        backgroundColor: '#fff',
+        textStyle: {
+          color: '#5c6c7c',
+        },
+        padding: [10, 10],
+        extraCssText: 'box-shadow: 1px 0 2px 0 rgba(163,163,163,0.5)',
+      },
+
+      xAxis: [
+        {
+          type: 'category',
+          data: xData.value,
+          axisLine: {
+            lineStyle: {
+              color: '#DCE2E8',
+            },
+          },
+          axisTick: {
+            show: false,
+          },
+          axisLabel: {
+            interval: 0,
+            textStyle: {
+              color: '#556677',
+            },
+            // 默认x轴字体大小
+            fontSize: 12,
+            // margin:文字到x轴的距离
+            margin: 15,
+          },
+          axisPointer: {
+            label: {
+              // padding: [11, 5, 7],
+              padding: [0, 0, 10, 0],
+              margin: 15,
+              // 移入时的字体大小
+              fontSize: 12,
+              backgroundColor: {
+                type: 'linear',
+                x: 0,
+                y: 0,
+                x2: 0,
+                y2: 1,
+                colorStops: [
+                  {
+                    offset: 0,
+                    color: '#fff', // 0% 处的颜色
+                  },
+                  {
+                    // offset: 0.9,
+                    offset: 0.86,
+                    color: '#fff', // 0% 处的颜色
+                  },
+                  {
+                    offset: 0.86,
+                    color: '#33c0cd', // 0% 处的颜色
+                  },
+                  {
+                    offset: 1,
+                    color: '#33c0cd', // 100% 处的颜色
+                  },
+                ],
+                global: false, // 缺省为 false
+              },
+            },
+          },
+          boundaryGap: false,
+        },
+      ],
+      yAxis: [
+        {
+          type: 'value',
+          axisTick: {
+            show: false,
+          },
+          splitLine: {
+            show: true,
+            lineStyle: {
+              color: ['#f1f4f8'],
+              width: 1,
+              type: 'solid',
+            },
+          },
+          axisLabel: {
+            textStyle: {
+              color: '#556677',
+            },
+            formatter: '{value}',
+          },
+          axisLine: {
+            show: false,
+            lineStyle: {
+              color: '#DCE2E8',
+            },
+          },
+        },
+      ],
+      series: [
+        {
+          name: 'Adidas',
+          type: 'line',
+          data: yData.value,
+          symbolSize: 1,
+          smooth: true,
+          symbol:'none', //加这个
+          // yAxisIndex: 0,
+          showSymbol: false,
+          lineStyle: {
+            width: 5,
+            color: colorList[0],
+            shadowColor: shadowColor[0],
+            shadowBlur: 10,
+            shadowOffsetY: 20,
+          },
+          itemStyle: {
+            // normal: {
+            //     color: colorList[0],
+            //     borderColor: colorList[0]
+            // }
+          },
+        },
+      ],
+    });
+  },
+  { immediate: true,deep:true },
+);
+</script>

+ 131 - 0
src/views/statistics/components/lineEcharts2.vue

@@ -0,0 +1,131 @@
+<template>
+  <Card title="成交占比" :loading="loading">
+    <template #extra>
+      <div class="condition">
+          <div class="selct" style="display: inline-block;">
+            <span style="margin-right:15px">颗粒度</span>
+            <Select
+            v-model:value="value"
+            label-in-value
+            style="width: 100px;margin-right:30px"
+            placeholder="请选择颗粒度"
+            :options="options"
+            @change="handleChange"
+          ></Select>
+          </div>
+          <a-button type="primary" >导出</a-button>
+      </div>
+    </template>
+      <div ref="chartRef" :style="{ height, width }"></div>
+  </Card>
+</template>
+<script lang="ts">
+  import { basicProps } from './props';
+  // import { dateUtil } from '/@/utils/dateUtil';
+  import { Card, Select } from 'ant-design-vue';
+</script>
+<script lang="ts" setup>
+  import { ref, Ref, watch } from 'vue';
+  // import type { dataItemType } from './props';
+  import { useECharts } from '/@/hooks/web/useECharts';
+  const props = defineProps({
+  loading: Boolean,
+    ...basicProps,
+  });
+  const value = ref('a1');
+  const options = ref<SelectProps['options']>([
+        {
+          value: 'jack',
+          label: 'Jack (100)',
+        },
+        {
+          value: 'lucy',
+          label: 'Lucy (101)',
+        },
+      ]);
+  const viewStaticsData = ref<number[]>([1,5,6,8,55,1,5,6,8,1]);
+  const shareStaticsData = ref<number[]>([2,55,10,2,6,1,5,6,8,1]);
+  const yixStringData = ref<string[]>(['11','22','33','44','ss','11','22','33','44','ss']);
+  const maxSize = ref(0);
+  const chartRef = ref<HTMLDivElement | null>(null);
+  const { setOptions } = useECharts(chartRef as Ref<HTMLDivElement>);
+  function handleChange(val){
+    console.log('handleChange',val)
+  }
+  function handlesetOptions() {
+    setOptions({
+      tooltip: {
+        trigger: 'axis',
+        axisPointer: {
+          lineStyle: {
+            width: 1,
+            color: '#019680',
+          },
+        },
+      },
+      legend: {
+        orient: 'horizontal',
+        bottom: 0,
+      },
+      // grid: { left: '2%', right: '2%', top: '10%', bottom: '10%', containLabel: true },
+      xAxis: {
+        type: 'category',
+        // data: [...new Array(30)].map((_item, index) => `${index + 1}日`),
+        data: yixStringData.value,
+      },
+      yAxis: {
+        type: 'value',
+        max: maxSize.value,
+        splitNumber: 4,
+      },
+      series: [
+        {
+          data: viewStaticsData.value,
+          type: 'bar',
+          itemStyle: { color: '#38a0ff' },
+          barMaxWidth: 80,
+          name: '用户浏览量',
+        },
+        {
+          data: shareStaticsData.value,
+          type: 'bar',
+          itemStyle: { color: '#4cca73' },
+          barMaxWidth: 80,
+          name: '用户分享数',
+        },
+      ],
+    });
+  }
+  // props.viewStatics,
+  watch(
+    // () => [props.viewStatics, props.shareStatics],
+    // ([data1, data2]) => {
+    () => props.loading,
+    () => {
+      console.log('viewStatics-data');
+      // viewStaticsData.value = data1.reduce<number[]>(
+      //   (prev: number[], current) => prev.concat(Number(current.amount)),
+      //   [],
+      // );
+
+      // yixStringData.value = data1.reduce<string[]>(
+      //   (prev: string[], current) => prev.concat(current.date),
+      //   [],
+      // );
+      // shareStaticsData.value = data2.reduce<number[]>(
+      //   (prev: number[], current) => prev.concat(Number(current.amount)),
+      //   [],
+      // );
+
+      const maxNumber = Math.max(...viewStaticsData.value.concat(shareStaticsData.value));
+      const pow = Math.pow(10, maxNumber.toString().length - 1);
+      maxSize.value = maxNumber > 10 ? Math.floor(maxNumber / 10) * 10 + pow * 2 : 10;
+      console.log('maxSize', maxSize.value);
+      handlesetOptions();
+    },
+    {
+      immediate: true,
+      deep: true,
+    },
+  );
+</script>

+ 36 - 0
src/views/statistics/components/props.ts

@@ -0,0 +1,36 @@
+import { PropType } from 'vue';
+
+export interface BasicProps {
+  width: string;
+  height: string;
+}
+export type dataItemType = {
+  date: string;
+  amount: string | number;
+};
+export const basicProps = {
+  width: {
+    type: String as PropType<string>,
+    default: '100%',
+  },
+  height: {
+    type: String as PropType<string>,
+    default: '280px',
+  },
+  viewStatics: {
+    type: Array as PropType<Array<dataItemType>>,
+    default: [],
+  },
+  shareStatics: {
+    type: Array as PropType<Array<dataItemType>>,
+    default: [],
+  },
+  bulletChatAmounts: {
+    type: Array as PropType<Array<dataItemType>>,
+    default: [],
+  },
+  userAmount: {
+    type: Array as PropType<Array<dataItemType>>,
+    default: [],
+  },
+};

+ 137 - 0
src/views/statistics/components/scenelineEcharts.vue

@@ -0,0 +1,137 @@
+<template>
+  <Card title="成交占比" :loading="loading">
+    <template #extra>
+      <div class="condition">
+          <div class="selct" style="display: inline-block;">
+            <span style="margin-right:15px">颗粒度</span>
+            <Select
+            v-model:value="value"
+            label-in-value
+            style="width: 100px;margin-right:30px"
+            placeholder="请选择颗粒度"
+            :options="options"
+            :defaultValue="1"
+            @change="handleChange"
+          ></Select>
+          </div>
+          <a-button type="primary" >导出</a-button>
+      </div>
+    </template>
+      <div ref="chartRef" :style="{ height, width }"></div>
+  </Card>
+</template>
+<script lang="ts">
+  import { basicProps } from './props';
+  // import { dateUtil } from '/@/utils/dateUtil';
+  import { Card, Select } from 'ant-design-vue';
+  import type { SelectProps } from 'ant-design-vue';
+</script>
+<script lang="ts" setup>
+  import { ref, Ref, watch } from 'vue';
+  // import type { dataItemType } from './props';
+  import { useECharts } from '/@/hooks/web/useECharts';
+  const props = defineProps({
+  loading: Boolean,
+    ...basicProps,
+  });
+  const value = ref(1);
+  const options = ref<SelectProps['options']>([
+        {
+          value: 1,
+          label: '日',
+        },
+        {
+          value: 2,
+          label: '周',
+        },
+        {
+          value: 3,
+          label: '月',
+        },
+      ]);
+  const viewStaticsData = ref<number[]>([1,5,6,8,55,1,5,6,8,1]);
+  const shareStaticsData = ref<number[]>([2,55,10,2,6,1,5,6,8,1]);
+  const yixStringData = ref<string[]>(['11','22','33','44','ss','11','22','33','44','ss']);
+  const maxSize = ref(0);
+  const chartRef = ref<HTMLDivElement | null>(null);
+  const { setOptions } = useECharts(chartRef as Ref<HTMLDivElement>);
+  function handleChange(val){
+    console.log('handleChange',val)
+  }
+  function handlesetOptions() {
+    setOptions({
+      tooltip: {
+        trigger: 'axis',
+        axisPointer: {
+          lineStyle: {
+            width: 1,
+            color: '#019680',
+          },
+        },
+      },
+      legend: {
+        orient: 'horizontal',
+        bottom: 0,
+      },
+      // grid: { left: '2%', right: '2%', top: '10%', bottom: '10%', containLabel: true },
+      xAxis: {
+        type: 'category',
+        // data: [...new Array(30)].map((_item, index) => `${index + 1}日`),
+        data: yixStringData.value,
+      },
+      yAxis: {
+        type: 'value',
+        max: maxSize.value,
+        splitNumber: 4,
+      },
+      series: [
+        {
+          data: viewStaticsData.value,
+          type: 'line',
+          itemStyle: { color: '#38a0ff' },
+          // barMaxWidth: 80,
+          name: '用户浏览量',
+        },
+        {
+          data: shareStaticsData.value,
+          type: 'line',
+          itemStyle: { color: '#4cca73' },
+          // barMaxWidth: 80,
+          name: '用户分享数',
+        },
+      ],
+    });
+  }
+  // props.viewStatics,
+  watch(
+    // () => [props.viewStatics, props.shareStatics],
+    // ([data1, data2]) => {
+    () => props.loading,
+    () => {
+      console.log('viewStatics-data');
+      // viewStaticsData.value = data1.reduce<number[]>(
+      //   (prev: number[], current) => prev.concat(Number(current.amount)),
+      //   [],
+      // );
+
+      // yixStringData.value = data1.reduce<string[]>(
+      //   (prev: string[], current) => prev.concat(current.date),
+      //   [],
+      // );
+      // shareStaticsData.value = data2.reduce<number[]>(
+      //   (prev: number[], current) => prev.concat(Number(current.amount)),
+      //   [],
+      // );
+
+      const maxNumber = Math.max(...viewStaticsData.value.concat(shareStaticsData.value));
+      const pow = Math.pow(10, maxNumber.toString().length - 1);
+      maxSize.value = maxNumber > 10 ? Math.floor(maxNumber / 10) * 10 + pow * 2 : 10;
+      console.log('maxSize', maxSize.value);
+      handlesetOptions();
+    },
+    {
+      immediate: true,
+      deep: true,
+    },
+  );
+</script>

+ 60 - 0
src/views/statistics/data.ts

@@ -0,0 +1,60 @@
+export interface GrowCardItem {
+  icon: string;
+  title: string;
+  value: number;
+  unit: string;
+  color: string;
+  action: string;
+}
+
+export const growCardList: GrowCardItem[] = [
+  {
+    title: '累计用户',
+    // icon: 'fa6-solid:users-gear',
+    icon: 'visit-count|svg',
+    value: 2000,
+    unit: '人',
+    color: 'green',
+    action: '年',
+  },
+  {
+    title: '上月新增用户',
+    icon: 'akar-icons:person-add',
+    value: 20000,
+    unit: '人',
+    color: 'blue',
+    action: '月',
+  },
+  {
+    title: '今日新增用户',
+    icon: 'carbon:user-role',
+    value: 8000,
+    unit: '人',
+    color: 'orange',
+    action: '日',
+  },
+  {
+    title: '上月权益订单数',
+    icon: 'fxemoji:notchedrightsemi3dot',
+    value: 20000,
+    unit: '笔',
+    color: 'blue',
+    action: '月',
+  },
+  {
+    title: '上月下载订单数',
+    icon: 'download-count|svg',
+    value: 8000,
+    unit: '笔',
+    color: 'orange',
+    action: '月',
+  },
+  {
+    title: '上月配件订单数',
+    icon: 'transaction|svg',
+    value: 5000,
+    unit: '人',
+    color: 'purple',
+    action: '月',
+  },
+];

+ 40 - 0
src/views/statistics/order/index.vue

@@ -0,0 +1,40 @@
+<template>
+  <div class="p-4">
+    <!-- <GrowCard :loading="loading" :list="growCardList" class="enter-y" /> -->
+    <!-- <div class="md:flex !my-4 enter-y">
+      <lineEcharts class="md:w-1/2 w-full !md:mt-0 !mt-4 !md:mr-4" name="chartRef1" :loading="loading"  />
+      <lineEcharts2  name="chartRef2" class="md:w-1/2 mx-4 w-full" :loading="loading" />
+    </div> -->
+    <lineEcharts2 class="!my-4 enter-y" :loading="loading" />
+    <!-- <div class="md:flex enter-y">
+      <VisitRadar class="md:w-1/3 w-full" :loading="loading" />
+      <VisitSource class="md:w-1/3 !md:mx-4 !md:my-0 !my-4 w-full" :loading="loading" />
+      <SalesProductPie class="md:w-1/3 w-full" :loading="loading" />
+    </div> -->
+  </div>
+</template>
+<script lang="ts" setup>
+  import { ref, onMounted } from 'vue';
+  import { orderTotal } from '/@/api/statistics/index';
+  import { GrowCardItem } from '../data';
+  import GrowCard from '../components/GrowCard.vue';
+  import lineEcharts2 from '../components/lineEcharts2.vue';
+  const loading = ref(true);
+  const growCardList = ref<GrowCardItem[]>([]);
+  onMounted(() => {
+    getData();
+  });
+  async function getData() {
+    try {
+      loading.value = true;
+      const Total = await orderTotal();
+      console.log('orderTotal', Total);
+      loading.value = false;
+    } catch (error) {
+      loading.value = false;
+    }
+  }
+  setTimeout(() => {
+    loading.value = false;
+  }, 1500);
+</script>

+ 26 - 0
src/views/statistics/register/index.vue

@@ -0,0 +1,26 @@
+<template>
+  <div class="p-4">
+    <GrowCard :loading="loading" :list="growCardList" class="enter-y" />
+    <!-- <div class="md:flex !my-4 enter-y">
+      <lineEcharts class="md:w-1/2 w-full !md:mt-0 !mt-4 !md:mr-4" name="chartRef1" :loading="loading"  />
+      <lineEcharts2  name="chartRef2" class="md:w-1/2 mx-4 w-full" :loading="loading" />
+    </div> -->
+    <scenelineEcharts class="!my-4 enter-y" :loading="loading" />
+    <!-- <div class="md:flex enter-y">
+      <VisitRadar class="md:w-1/3 w-full" :loading="loading" />
+      <VisitSource class="md:w-1/3 !md:mx-4 !md:my-0 !my-4 w-full" :loading="loading" />
+      <SalesProductPie class="md:w-1/3 w-full" :loading="loading" />
+    </div> -->
+  </div>
+</template>
+<script lang="ts" setup>
+  import { ref } from 'vue';
+  import { growCardList } from '../data';
+  import GrowCard from '../components/GrowCard.vue';
+  import scenelineEcharts from '../components/scenelineEcharts.vue';
+  const loading = ref(true);
+
+  setTimeout(() => {
+    loading.value = false;
+  }, 1500);
+</script>

+ 28 - 0
src/views/statistics/scene/index.vue

@@ -0,0 +1,28 @@
+<template>
+  <div class="p-4">
+    <GrowCard :loading="loading" :list="growCardList" class="enter-y" />
+    <!-- <div class="md:flex !my-4 enter-y">
+      <lineEcharts class="md:w-1/2 w-full !md:mt-0 !mt-4 !md:mr-4" name="chartRef1" :loading="loading"  />
+    </div> -->
+    <lineEcharts2  name="chartRef2" class="!my-4 enter-y" :loading="loading" />
+
+    <scenelineEcharts class="!my-4 enter-y" :loading="loading" />
+    <!-- <div class="md:flex enter-y">
+      <VisitRadar class="md:w-1/3 w-full" :loading="loading" />
+      <VisitSource class="md:w-1/3 !md:mx-4 !md:my-0 !my-4 w-full" :loading="loading" />
+      <SalesProductPie class="md:w-1/3 w-full" :loading="loading" />
+    </div> -->
+  </div>
+</template>
+<script lang="ts" setup>
+  import { ref } from 'vue';
+  import { growCardList } from '../data';
+  import GrowCard from '../components/GrowCard.vue';
+  import lineEcharts2 from '../components/lineEcharts2.vue';
+  import scenelineEcharts from '../components/scenelineEcharts.vue';
+  const loading = ref(true);
+
+  setTimeout(() => {
+    loading.value = false;
+  }, 1500);
+</script>