tangning пре 2 година
родитељ
комит
bb207e44ee

+ 2 - 2
.env.development

@@ -6,14 +6,14 @@ VITE_PUBLIC_PATH = ./
 
 # Cross-domain proxy, you can configure multiple
 # Please note that no line breaks
-VITE_PROXY = [["/basic-api","http://localhost:3000"],["/upload","https://v4-uat.4dkankan.com/service/manage/common/upload/files"],["/service","https://v4-uat.4dkankan.com"]]
+VITE_PROXY = [["/qjkankan","https://test.4dkankan.com/qjkankan"],["/upload","https://v4-uat.4dkankan.com/service/manage/common/upload/files"],["/service","https://v4-uat.4dkankan.com/service"]]
 # VITE_PROXY=[["/api","https://vvbin.cn/test"]]
 
 # Delete console
 VITE_DROP_CONSOLE = false
 
 # Basic interface address SPA
-VITE_GLOB_API_URL=/service
+VITE_GLOB_API_URL=
 
 # File upload address, optional
 VITE_GLOB_UPLOAD_URL=/upload

+ 1 - 1
.env.production

@@ -5,7 +5,7 @@ VITE_USE_MOCK = false
 VITE_PUBLIC_PATH = ./
 
 # VITE_PROXY = [["/basic-api","http://localhost:3000"],["/upload","http://v4-test.4dkankan.com/service/manage/common/upload/files"],["/service","http://v4-test.4dkankan.com/service/"]]
-VITE_PROXY = [["/basic-api","http://localhost:3000"],["/upload","https://v4-uat.4dkankan.com/service/manage/common/upload/files"],["/service","https://v4-uat.4dkankan.com"]]
+VITE_PROXY = [["/qjkankan","https://test.4dkankan.com/qjkankan"],["/upload","https://v4-uat.4dkankan.com/service/manage/common/upload/files"],["/service","https://v4-uat.4dkankan.com/service"]]
 
 # Delete console
 VITE_DROP_CONSOLE = true

+ 2 - 1
.vscode/settings.json

@@ -95,7 +95,8 @@
   "[vue]": {
     "editor.codeActionsOnSave": {
       "source.fixAll.eslint": false
-    }
+    },
+    "editor.defaultFormatter": "esbenp.prettier-vscode"
   },
   "i18n-ally.localesPaths": ["src/locales/lang"],
   "i18n-ally.keystyle": "nested",

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

@@ -16,6 +16,10 @@ enum Api {
   cameraExport = '/service/manage/order/camera/export',
   downExport = '/service/manage/order/down/export',
   incrementExport = '/service/manage/order/increment/export',
+  workType = '/qjkankan/api/age/report/work',
+  workTrend = '/qjkankan/api/age/report/workTrend',
+  qjuserTrend = '/qjkankan/api/age/report/userTrend',
+  volumeTrend = '/qjkankan/api/age/report/volumeTrend',
 }
 
 /**
@@ -122,7 +126,51 @@ export const buryPointList = (params: PageParams) =>
     },
   });
   
+  export const workType = (params) =>
+  defHttp.get<Result>({
+    url: Api.workType,
+    params,
+    data: params,
+    headers: {
+      // @ts-ignore
+      appId: 'BDA385EC848C1A425F746869011C8D23',
+      ignoreCancelToken: true,
+    },
+  });
     
+  export const workTrend = (params) =>
+  defHttp.post<Result>({
+    url: Api.workTrend,
+    params,
+    data: params,
+    headers: {
+      // @ts-ignore
+      appId: 'BDA385EC848C1A425F746869011C8D23',
+      ignoreCancelToken: true,
+    },
+  });    
+  export const qjuserTrend = (params) =>
+  defHttp.post<Result>({
+    url: Api.qjuserTrend,
+    params,
+    data: params,
+    headers: {
+      // @ts-ignore
+      appId: 'BDA385EC848C1A425F746869011C8D23',
+      ignoreCancelToken: true,
+    },
+  });    
+  export const volumeTrend = (params) =>
+  defHttp.post<Result>({
+    url: Api.volumeTrend,
+    params,
+    data: params,
+    headers: {
+      // @ts-ignore
+      appId: 'BDA385EC848C1A425F746869011C8D23',
+      ignoreCancelToken: true,
+    },
+  });
   export const cameraExport = (params) =>
   defHttp.downloadFile<FileStream>({
     url: Api.cameraExport,

+ 1 - 0
src/views/account/overview/interesModal.vue

@@ -149,6 +149,7 @@
           onOk: async () => {
             await await IncrementDelayApi({id:record.id,year:1})
             createMessage.success(t('common.optSuccess'));
+            otherInfo.updateOverviewInfo()
             reload()
           },
         });

+ 16 - 22
src/views/customer/scene.vue

@@ -13,7 +13,17 @@
         <template #toolbar>
           <a-button type="primary" @click="back">返回</a-button>
         </template>
-        
+        <template #status="{ record }">
+          <span v-if="record.status != '-1'">{{record.statusString}}</span>
+          <Tooltip v-else placement="right">
+            <template #title>
+              <span>{{record.buildErrorReason}}</span>
+            </template>
+          <span>{{record.statusString}}
+            <Icon icon="mdi:warning-octagon-outline" />
+          </span>
+        </Tooltip>
+        </template>
         <template #herf="{ record }">
           <a :href="record.thumb" target="_blank">{{record.sceneName}}</a>
         </template>
@@ -97,7 +107,8 @@
   import DownLoadModal from './modal/DownLoadModal.vue';
   import MoveModal from './modal/MoveModal.vue';
   import { Time } from '/@/components/Time';
-  import { Descriptions, Tabs, Progress } from 'ant-design-vue';
+  import { Icon } from '/@/components/Icon';
+  import { Descriptions, Tabs, Progress, Tooltip, message } from 'ant-design-vue';
   import { useI18n } from '/@/hooks/web/useI18n';
   import { useMessage } from '/@/hooks/web/useMessage';
   import { useModal } from '/@/components/Modal';
@@ -114,7 +125,6 @@
     sceneCopy,
     rebuildScene,
   } from '/@/api/operate';
-  import { message } from 'ant-design-vue';
   import { func } from 'vue-types';
   export default defineComponent({
     components: {
@@ -123,9 +133,10 @@
       BasicTable,
       TableAction,
       PageWrapper,
+      Tooltip,
       [Descriptions.name]: Descriptions,
       [Descriptions.Item.name]: Descriptions.Item,
-
+      Icon,
       [Tabs.name]: Tabs,
       [Tabs.TabPane.name]: Tabs.TabPane,
     },
@@ -232,24 +243,7 @@
           title: '状态',
           dataIndex: 'statusString',
           width: 80,
-          // customRender: ({ record }) => {
-          //   let str;
-          //   switch (record.status - 0) {
-          //     case 0:
-          //       str = '计算中';
-          //       break;
-          //     case 1:
-          //       str = '计算成功';
-          //       break;
-          //     case -2:
-          //       str = '计算成功';
-          //       break;
-          //     case -1:
-          //       str = '计算失败';
-          //       break;
-          //   }
-          //   return record.payStatus == 1 ? '封存' : str;
-          // },
+          slots: { customRender: 'status' },
         },
         {
           title: '操作',

+ 2 - 2
src/views/dealer/components/financeModal.vue

@@ -66,7 +66,7 @@
         },{
           field: 'highAddNum',
           component: 'InputNumber',
-          label: '高级会员()剩余可售 0 个 新增',
+          label: '高级会员()剩余可售 0 个 新增',
           defaultValue: 0,
           ifShow:false,
           suffix:'个',
@@ -117,7 +117,7 @@
             label: `专业会员(年)剩余可售 ${data.majorSubNum || 0} 个 新增`,
           },{
             field: 'highAddNum',
-            label: `高级会员()剩余可售 ${data.highSubNum || 0} 个 新增`,
+            label: `高级会员()剩余可售 ${data.highSubNum || 0} 个 新增`,
             ifShow: isDev.value,
           },{
             field: 'downAddNum',

+ 20 - 24
src/views/productOperation/cameraScene.vue

@@ -14,7 +14,17 @@
         <template #toolbar>
           <!-- <a-button type="primary" @click="exportExcel"> 导出1</a-button> -->
         </template>
-        
+        <template #status="{ record }">
+          <span v-if="record.status != '-1'">{{record.statusString}}</span>
+          <Tooltip v-else placement="right">
+            <template #title>
+              <span>{{record.buildErrorReason}}</span>
+            </template>
+          <span>{{record.statusString}}
+            <Icon icon="mdi:warning-octagon-outline" />
+          </span>
+        </Tooltip>
+        </template>
         <template #href="{ record }">
           <a v-if="record.sceneName && record.thumb" target="_blank" :href="record.thumb">{{record.sceneName}}</a>
           <span v-else-if="record.sceneName">{{record.sceneName}}</span>
@@ -98,7 +108,8 @@
   import DownLoadModal from './modal/DownLoadModal.vue';
   import MoveModal from './modal/MoveModal.vue';
   import { Time } from '/@/components/Time';
-  import { Descriptions, Tabs, Progress } from 'ant-design-vue';
+  import { Icon } from '/@/components/Icon';
+  import { Descriptions, Tabs, Progress, Tooltip } from 'ant-design-vue';
   import { useI18n } from '/@/hooks/web/useI18n';
   import { useMessage } from '/@/hooks/web/useMessage';
   import { useModal } from '/@/components/Modal';
@@ -116,7 +127,7 @@
   import { message } from 'ant-design-vue';
   import { usePermissionStore } from '/@/store/modules/permission';
   import { func } from 'vue-types';
-import { truncate } from 'fs/promises';
+  import { truncate } from 'fs/promises';
   export default defineComponent({
     components: {
       DownLoadModal,
@@ -124,6 +135,8 @@ import { truncate } from 'fs/promises';
       BasicTable,
       TableAction,
       PageWrapper,
+      Icon,
+      Tooltip,
       [Descriptions.name]: Descriptions,
       [Descriptions.Item.name]: Descriptions.Item,
       [Tabs.name]: Tabs,
@@ -153,7 +166,7 @@ import { truncate } from 'fs/promises';
         },
         {
           title: '拍摄时间',
-          dataIndex: 'createTime',
+          dataIndex: 'timeList',
           sorter: true,
           width: 180,
           customRender: ({ record }) => {
@@ -241,26 +254,9 @@ import { truncate } from 'fs/promises';
         },
         {
           title: '状态',
-          dataIndex: 'status',
-          width: 80,
-          customRender: ({ record }) => {
-            let str;
-            switch (record.status - 0) {
-              case 0:
-                str = '计算中';
-                break;
-              case 1:
-                str = '计算成功';
-                break;
-              case -2:
-                str = '计算成功';
-                break;
-              case -1:
-                str = '计算失败';
-                break;
-            }
-            return record.payStatus == -2 ? '封存' : str;
-          },
+          dataIndex: 'statusString',
+          width: 120,
+          slots: { customRender: 'status' },
         },
         {
           title: '操作',

+ 2 - 0
src/views/productOperation/livestream.vue

@@ -96,6 +96,7 @@
         {
           title: '创建时间',
           dataIndex: 'createTime',
+          sorter: true,
           width: 180,
           customRender: ({ record }) => {
             return record.createTime
@@ -149,6 +150,7 @@
         {
           title: '浏览量',
           dataIndex: 'roomViewCount',
+          sorter: true,
           width: 80,
         },
         {

+ 32 - 1
src/views/productOperation/viewKankan.vue

@@ -84,6 +84,7 @@
         {
           title: '创建时间',
           dataIndex: 'createTime',
+          sorter: true,
           width: 180,
           customRender: ({ record }) => {
             return record.createTime
@@ -118,6 +119,7 @@
         {
           title: '浏览量',
           dataIndex: 'visit',
+          sorter: true,
           width: 80,
         },
         {
@@ -133,6 +135,10 @@
       const searchForm: Partial<FormProps> = {
         labelWidth: 100,
         autoSubmitOnEnter:true,
+        autoAdvancedLine:1,
+        actionColOptions: {
+          span: 24,
+        },
         schemas: [
           {
             field: 'sceneName',
@@ -145,8 +151,18 @@
               xl: 7,
               xxl: 7,
             },
+          },{
+            field: 'snCode',
+            label: '作品码',
+            component: 'Input',
+            componentProps: {
+              maxLength: 100,
+            },
+            colProps: {
+              xl: 7,
+              xxl: 7,
+            },
           },
-
           {
             field: 'userName',
             label: '用户账号',
@@ -158,6 +174,21 @@
               xl: 6,
               xxl: 6,
             },
+          },{
+            field: 'timeList',
+            label: '创建时间',
+            component: 'RangePicker',
+            componentProps: {
+              maxLength: 100,
+              width:'385px',
+              format: 'YYYY-MM-DD',
+              valueFormat: 'YYYY-MM-DD',
+              showTime: true,
+            },
+            colProps: {
+              xl: 8,
+              xxl: 8,
+            },
           },
         ],
       };

+ 1 - 1
src/views/statistics/components/orderEchart.vue

@@ -1,4 +1,4 @@
-<template>
+ <template>
   <Card :title="title||'订单数据统计'">
     <template #extra>
       <condition type="2" :typeShow="title=='相机出库数量统计'" :name="title=='订单数据统计'?{1:'金额',0:'数量'}:{}" @change="Search" @expor="expor" />

+ 73 - 0
src/views/statistics/viewKankan/index.vue

@@ -0,0 +1,73 @@
+<template>
+  <div class="p-4">
+    <!-- <GrowCard :loading="loading" :list="growCardList" class="enter-y" /> -->
+    <div class="md:flex !my-4 enter-y">
+      <pieEchart class="md:w-1/3 w-full !md:mt-0 !mt-4 !md:mr-4"></pieEchart>
+      <lineEcharts title="管理中心用户活跃度统计" class="md:w-2/3 mx-4 w-full" />
+    </div>
+    <orderEchart title="作品趋势分析" class="!my-4 enter-y" @change="Search" :echartData="worksData" :loading="loading" />
+    <lineEcharts title="云容量趋势分析" class="!my-4 enter-y" />
+  </div>
+</template>
+<script lang="ts" setup>
+import { ref, onMounted, reactive } from 'vue';
+import { workTrend, sceneTrend } from '/@/api/statistics/index';
+import orderEchart from './orderEchart.vue';
+import pieEchart from './pieEchart.vue'
+import sceneEchart from '../components/sceneEchart.vue';
+import lineEcharts from './lineEcharts.vue';
+const loading = ref(true);
+// const growCardList = ref<GrowCardItem[]>([]);
+const worksData = reactive({
+  xdata:[],
+  downOrder:[],
+  incrementOrder:[],
+  partOrder:[],
+  echartTypr:'bar',
+});
+const SearchData = reactive({
+  startTime: '',
+  endTime: '',
+  dataType: 0,
+  type: 2,
+  timeType: 'month',
+  infoType: 'add',
+});
+onMounted(() => {
+  getData();
+  getList();
+});
+async function getList() {
+  let downlist = [], xdata = [], downOrder = [], partOrder = [], incrementOrder = [];
+  let res =  await workTrend(SearchData);
+  res.map((ele) => {
+    xdata.push(ele.groupKey);
+    // downlist.push(ele.count);
+    downOrder.push(ele.pano);
+    partOrder.push(ele.mix);
+    incrementOrder.push(ele.age);
+  });
+  worksData.xdata = xdata
+  worksData.downOrder = downOrder
+  worksData.incrementOrder = incrementOrder
+  worksData.partOrder = partOrder
+}
+function Search(val) {
+  const { startTime, endTime, dataType, type } = val;
+  let timeStr = {
+      0:'day',
+      1:'week',
+      2:'month',
+    }
+  SearchData.startTime = startTime;
+  SearchData.endTime = endTime;
+  SearchData.dataType = type;
+  SearchData.type = dataType;
+  SearchData.infoType = type?'add':'all';
+  SearchData.timeType = timeStr[dataType] || 'month'
+  getList();
+}
+async function getData() {
+}
+</script>
+

+ 146 - 0
src/views/statistics/viewKankan/lineEcharts.vue

@@ -0,0 +1,146 @@
+<template>
+  <Card :title="title" :loading="loading">
+    <template #extra>
+      <condition type="2" @change="Search" :typeShow="true"  @expor="handleExport" />
+    </template>
+      <div ref="chartRef" :style="{ height, width }"></div>
+  </Card>
+</template>
+<script lang="ts">
+  import { basicProps } from '../components/props';
+  import { Card, Select } from 'ant-design-vue';
+  import type { SelectProps } from 'ant-design-vue';
+</script>
+<script lang="ts" setup>
+  import { ref, Ref, watch, onMounted, reactive } from 'vue';
+  import { volumeTrend, qjuserTrend } from '/@/api/statistics/index';
+  import condition from '../components/condition.vue';
+  import { useECharts } from '/@/hooks/web/useECharts';
+  import { exportElsxFile, } from '/@/utils/file/download';
+  const props = defineProps({
+  loading: Boolean,
+    ...basicProps,
+  });
+  const value = ref(1);
+  const SearchData = reactive({
+    startTime: '',
+    endTime: '',
+    dataType: 2,
+    type: 2,
+    timeType: 'month',
+  });
+  const options = ref<SelectProps['options']>([
+        {
+          value: 1,
+          label: '日',
+        },
+        {
+          value: 2,
+          label: '周',
+        },
+        {
+          value: 3,
+          label: '月',
+        },
+      ]);
+  const viewStaticsData = ref<number[]>([]);
+  const shareStaticsData = ref<number[]>([]);
+  const yixStringData = ref<string[]>([]);
+  const maxSize = ref(0);
+  const chartRef = ref<HTMLDivElement | null>(null);
+  const { setOptions } = useECharts(chartRef as Ref<HTMLDivElement>);
+  onMounted(() => {
+    getAddUser();
+  });
+  function handleChange(val){
+    console.log('handleChange',val)
+    SearchData.value = val
+    getAddUser()
+  }
+  function Search(val) {
+    console.log('Search',val)
+    const { startTime, endTime, dataType, type } = val;
+    SearchData.startTime = startTime;
+    SearchData.endTime = endTime;
+    SearchData.type = dataType;
+    getAddUser();
+  }
+  function handleExport(){
+  console.log('props',props.title)
+  let fields  = {
+    'time':'日期',
+    'num':'数量',
+  }
+    let data = yixStringData.value.map((ele,index) => {
+      return {
+        'time':ele,
+        'num':viewStaticsData.value && viewStaticsData.value[index] || 0,
+      }
+    })
+    exportElsxFile(data, fields,  props.title=='云容量趋势分析'?'容量数':'用户活跃度')
+}
+  async function getAddUser() {
+    let xdata = [], yData=[]
+    let timeStr = {
+      0:'day',
+      1:'week',
+      2:'month',
+    }
+    SearchData.timeType = timeStr[SearchData.type] || 'month'
+    let apiSrc = props.title=='云容量趋势分析'?qjuserTrend:volumeTrend
+      const data = await apiSrc(SearchData);
+      data.map(ele => {
+        xdata.push(ele.groupKey)
+        yData.push(ele.count)
+      })
+      yixStringData.value = xdata
+      viewStaticsData.value = yData
+      handlesetOptions()
+  }
+  function handlesetOptions() {
+    setOptions({
+      tooltip: {
+        trigger: 'axis',
+        axisPointer: {
+          lineStyle: {
+            width: 1,
+            color: '#019680',
+          },
+        },
+      },
+      legend: {
+        orient: 'horizontal',
+        bottom: 0,
+      },
+      xAxis: {
+        type: 'category',
+        data: yixStringData.value,
+      },
+      yAxis: {
+        type: 'value',
+        splitNumber: 4,
+      },
+      series: [
+        {
+          data: viewStaticsData.value,
+          type: 'line',
+          itemStyle: { color: '#38a0ff' },
+          name: props.title=='云容量趋势分析'?'容量数':'用户活跃度',
+        },
+      ],
+    });
+  }
+  watch(
+    () => props.loading,
+    () => {
+      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;
+      handlesetOptions();
+    },
+    {
+      immediate: true,
+      deep: true,
+    },
+  );
+</script>

+ 129 - 0
src/views/statistics/viewKankan/orderEchart.vue

@@ -0,0 +1,129 @@
+ <template>
+  <Card :title="title||'订单数据统计'">
+    <template #extra>
+      <condition type="2" :typeShow="title=='相机出库数量统计'" :name="title=='订单数据统计'?{1:'金额',0:'数量'}:{}" @change="Search" @expor="expor" />
+    </template>
+    <div ref="chartRef" :style="{ height, width }"></div>
+  </Card>
+</template>
+<script lang="ts" setup>
+  import { basicProps } from '../components/props';
+  import condition from '../components/condition.vue';
+  import { Card, DatePicker } from 'ant-design-vue';
+  import { ref, Ref, watch, defineEmits } from 'vue';
+  import { useECharts } from '/@/hooks/web/useECharts';
+  import { exportElsxFile, } from '/@/utils/file/download';
+  const props = defineProps({
+  loading: Boolean,
+    ...basicProps,
+  });
+  const emit = defineEmits(["alertSome"])
+  const downOrderData = ref<number[]>([]);
+  const incrementOrderData = ref<number[]>([]);
+  const partsOrderData = ref<number[]>([]);
+  const yixStringData = ref<string[]>([]);
+  const echartTypr = ref('line')
+  const nameList = ref<string[]>(['全景图','三维场景','综合作品']);
+  const maxSize = ref(0);
+  const chartRef = ref<HTMLDivElement | null>(null);
+  const { setOptions } = useECharts(chartRef as Ref<HTMLDivElement>)
+
+  function Search(val){
+    emit('change',val)
+  }
+  function expor(val){
+    // emit('expor',val)
+    let hader = ['时间', ...nameList.value]
+    console.log('数量',hader,val)
+    let fields  = {
+      'time':'日期',
+      '1':hader[1],
+      '2':hader[2],
+      '3':hader[3],
+    }
+    if(props.title=='订单数据统计' && val?.value){
+      fields.time = `${val.value == 0?'数量/':'金额/'}` + fields.time
+    }
+    let data = yixStringData.value.map((ele,index) => {
+      return {
+        'time':ele,
+        '1':downOrderData.value && downOrderData.value[index] || 0,
+        '2':incrementOrderData.value && incrementOrderData.value[index] || 0,
+        '3':partsOrderData.value && partsOrderData.value[index] || 0,
+      }
+    })
+    exportElsxFile(data,fields,props.title)
+  }
+  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: downOrderData.value,
+          type: 'bar',
+          itemStyle: { color: '#38a0ff' },
+          barMaxWidth: 40,
+          name: nameList.value[0],
+          "stack": "总量",
+        },
+        {
+          data: incrementOrderData.value,
+          type: 'bar',
+          itemStyle: { color: '#4cca73' },
+          barMaxWidth: 40,
+          name: nameList.value[1],
+          "stack": "总量",
+        },
+        {
+          data: partsOrderData.value,
+          type: 'bar',
+          itemStyle: { color: '#FDD56A' },
+          barMaxWidth: 40,
+          name: nameList.value[2],
+          "stack": "总量",
+        },
+      ],
+    });
+  }
+  watch(
+    () => props.echartData,
+    (echartData) => {
+      console.log('workTrendechartData', echartData);
+      downOrderData.value = echartData.downOrder ||[]
+      incrementOrderData.value = echartData.incrementOrder ||[]
+      partsOrderData.value = echartData.partOrder ||[]
+      yixStringData.value = echartData.xdata ||[]
+      if(echartData.echartTypr){
+        echartTypr.value = echartData.echartTypr
+      }
+      handlesetOptions();
+    },
+    {
+      immediate: true,
+      deep: true,
+    },
+  );
+</script>

+ 119 - 0
src/views/statistics/viewKankan/pieEchart.vue

@@ -0,0 +1,119 @@
+<template>
+  <Card title="作品类型统计">
+    <div class="piechart" ref="chartPieRef" :style="{ height: '280px', width: '100%' }"></div>
+  </Card>
+</template>
+<script lang="ts" setup>
+  import { Card } from 'ant-design-vue';
+  import { ref, Ref, onMounted } from 'vue';
+  import { workType } from '/@/api/statistics/index';
+  import { useECharts } from '/@/hooks/web/useECharts';
+  const chartPieRef = ref<HTMLDivElement | null>(null);
+  const { setOptions } = useECharts(chartPieRef as Ref<HTMLDivElement>);
+  const colorList = ['#38a0ff', '#4cca73', '#FDD56A', '#d58b55', '#c8ffff'];
+  let pieData = ref([])
+  var rich = {
+    name: {
+      color: '#666666',
+      fontSize: 14,
+      padding: [8, 30, 0, 30],
+      fontWeight: '400',
+      align: 'left',
+    },
+    value: {
+      color: '#333',
+      fontSize: 15,
+      padding: [0, 30, 8, 30],
+      fontWeight: '500',
+      align: 'left',
+    },
+    percent: {
+      color: '#FFF',
+      align: 'right',
+      fontSize: 15,
+      fontWeight: '500',
+      //padding: [0, 5]
+    },
+    hr: {
+      borderColor: '#DFDFDF',
+      width: '100%',
+      borderWidth: 1,
+      height: 0,
+    },
+    cir: {
+      fontSize: 26,
+    },
+  };
+  function handlesetOptions() {
+    setOptions({
+      tooltip: {
+        trigger: 'item',
+        formatter: '{b}<br/>数量 : {c} ({d}%)',
+      },
+      legend: {
+        orient: 'horizontal',
+        bottom: 0,
+      },
+      series: [
+        {
+          name: '库存情况',
+          type: 'pie',
+          radius: '68%',
+          center: ['50%', '50%'],
+          clockwise: false,
+          data: pieData.value,
+          label: {
+            normal: {
+              position: 'inner',
+              formatter: (params) => {
+                return '{percent|' + params.percent.toFixed(0) + '%}';
+              },
+              rich: rich,
+            },
+          },
+          labelLine: {
+            normal: {
+              position: 'inner',
+              formatter: (params) => {
+                return '{percent|' + params.percent.toFixed(0) + '%}';
+              },
+              rich: rich,
+            },
+          },
+          itemStyle: {
+            borderWidth: 5,
+            borderColor: '#fff',
+          },
+        },
+      ],
+      color: colorList,
+      backgroundColor: '#fff',
+    });
+  }
+  async function getList() {
+    const res = await workType({});
+    let zhStr = {
+      "4dkk": '三维场景',    //四维看看作品
+      "mix": '综合作品', //   混合作品
+      "pano": '全景图'  //全景看看作品
+    }
+    pieData.value = res.map(ele => {
+      return {
+        ...ele,
+        value: ele.count,
+        name: zhStr[ele.groupKey],
+      }
+    })
+    handlesetOptions();
+  }
+
+  onMounted(() => {
+    getList();
+  });
+</script>
+<style lang="less" scoped>
+  .piechart {
+    width: 100%;
+    height: 100%;
+  }
+</style>