Explorar o código

上传原始文件

tangning hai 2 semanas
pai
achega
0061ac7bd5

+ 6 - 10
src/api/operate/index.ts

@@ -94,7 +94,7 @@ enum Api {
   sceneList = '/service/manage/jy/userShare/sceneList',
   getCaseByNum = '/service/manage/case/getCaseByNum',
   getSceneBuildLog = '/service/manage/scene/getSceneBuildLog/',
-  uploadSceneOrig = '/service/manage/scene/uploadSceneOrig',
+  uploadSceneOrig = '/service/manage/scene/uploadScene',
   uploadSceneCheck = '/service/manage/scene/uploadSceneCheck',
   caseFusionList = '/service/manage/caseFusion/list',
 }
@@ -619,7 +619,7 @@ export const getByRyId = (params) =>
     },
   });
 // 根据警员ID获取用户列表
-export const getinnerByRyId = (params, isTransformResponse= false) =>
+export const getinnerByRyId = (params, isTransformResponse = false) =>
   defHttp.post<Result>(
     {
       url: Api.getinnerByRyId,
@@ -738,23 +738,19 @@ export const getSceneBuildLog = (num) =>
     },
   });
 
-export const uploadSceneOrig = (filePath) =>
+export const uploadSceneOrig = (params) =>
   defHttp.post<Result>({
     url: Api.uploadSceneOrig,
-    params: {
-      filePath,
-    },
+    params,
     headers: {
       // @ts-ignore
       ignoreCancelToken: true,
     },
   });
-export const uploadSceneCheck = (filePath) =>
+export const uploadSceneCheck = (params) =>
   defHttp.post<Result>({
     url: Api.uploadSceneCheck,
-    params: {
-      filePath,
-    },
+    params,
     headers: {
       // @ts-ignore
       ignoreCancelToken: true,

+ 5 - 5
src/store/modules/permission.ts

@@ -27,7 +27,7 @@ import { intersection } from 'lodash-es';
 
 interface PermissionState {
   // Permission code list
-  permCodeList: string[] ;
+  permCodeList: string[];
   // Whether the route has been dynamically added
   isDynamicAddedRoute: boolean;
   // To trigger a menu update
@@ -70,7 +70,7 @@ export const usePermissionStore = defineStore({
     setPermCodeList(codeList: string[]) {
       this.permCodeList = codeList;
     },
-    getCheckPerm(value:string|string[]): boolean{
+    getCheckPerm(value: string | string[]): boolean {
       const permCodeList = this.permCodeList;
       if (!value) {
         return false;
@@ -78,7 +78,7 @@ export const usePermissionStore = defineStore({
       if (!isArray(value)) {
         return permCodeList?.includes(value);
       }
-      return (intersection(value, permCodeList)).length > 0;
+      return intersection(value, permCodeList).length > 0;
     },
     setBackMenuList(list: Menu[]) {
       this.backMenuList = list;
@@ -104,7 +104,7 @@ export const usePermissionStore = defineStore({
     },
     async changePermissionCode() {
       const codeList = await getPermCode();
-      const permsList = codeList.map(ele => ele.perms)
+      const permsList = codeList.map((ele) => ele.perms);
       this.setPermCodeList(permsList);
     },
     async buildRoutesAction(): Promise<AppRouteRecordRaw[]> {
@@ -209,7 +209,7 @@ export const usePermissionStore = defineStore({
           //  Background routing to menu structure
           const backMenuList = transformRouteToMenu(routeList);
           this.setBackMenuList(backMenuList);
-
+          console.log('routeList菜单', routeList);
           // remove meta.ignoreRoute item
           routeList = filter(routeList, routeRemoveIgnoreFilter);
           routeList = routeList.filter(routeRemoveIgnoreFilter);

+ 141 - 29
src/views/productOperation/modal/uploadModal.vue

@@ -11,13 +11,29 @@
     :confirmLoading="loading"
     :min-height="0"
   >
-    <div class="pt-2px pr-3px">
+    <div class="pt-2px pr-3px myzdyfrom">
       <BasicForm @register="registerForm" :model="model">
         <template #text="{ model, field }">
           {{ model[field] }}
         </template>
+        <template #elupload="{ model, field }">
+          <Upload
+            style="width: 300px"
+            :file-list="fileFlow.list"
+            :before-upload="beforeUpload"
+            name="file"
+            ref="uploadRef"
+            accept=".zip"
+            :customRequest="handleCustomRequest"
+            @progress="handleProgress"
+            @remove="handleRemove"
+            @change="handleChange"
+          >
+            <a-button type="primary"> 上传</a-button>
+          </Upload>
+        </template>
       </BasicForm>
-      <!-- <span>注意:迁移后该场景的权限配置将被清空,如需保留,请复制后再做迁移</span> -->
+      <!-- :headers="headers" <span>注意:迁移后该场景的权限配置将被清空,如需保留,请复制后再做迁移</span> -->
     </div>
   </BasicModal>
 </template>
@@ -27,12 +43,13 @@
   import { BasicForm, FormSchema, useForm } from '/@/components/Form/index';
   import { useMessage } from '/@/hooks/web/useMessage';
   import { uploadSceneOrig, uploadSceneCheck } from '/@/api/operate';
+  import { Upload } from 'ant-design-vue';
   import { useI18n } from '/@/hooks/web/useI18n';
   import { uploadApi } from '/@/api/product/index';
 
   const { t } = useI18n();
   export default defineComponent({
-    components: { BasicModal, BasicForm },
+    components: { BasicModal, BasicForm, Upload },
     props: {
       userData: { type: Object },
     },
@@ -40,33 +57,46 @@
     setup(props, { emit }) {
       const modelRef = ref({});
       const fileFlow = reactive({
+        list: [],
+        percent: 0,
         filePath: null,
       });
       const loading = ref(false);
+      const uploadRef = ref(null);
       const { createMessage, createConfirm } = useMessage();
+      const valiFile = async (_rule: Rule, value: string) => {
+        console.log(fileFlow, 'fileFlow');
+        if (fileFlow.list && fileFlow.list.length == 0) {
+          return Promise.reject(t('common.uploadMessge'));
+        } else {
+          return Promise.resolve();
+        }
+      };
       const schemas: FormSchema[] = [
         {
+          field: 'sourceType',
+          component: 'RadioGroup',
+          label: '类型',
+          defaultValue: 'orig',
+          required: true,
+          colProps: {
+            span: 24,
+          },
+          componentProps: {
+            options: [
+              { label: '原始数据', value: 'orig' },
+              { label: '离线包', value: 'offline' },
+            ],
+          },
+        },
+        {
           field: 'filePath',
           component: 'Upload',
+          slot: 'elupload',
           label: t('routes.product.file'),
           required: true,
-          rules: [{ required: true, message: t('common.uploadMessge') }],
+          rules: [{ required: true, validator: valiFile, trigger: 'change' }],
           // helpMessage: t('routes.corporation.uploadHelp'),
-          itemProps: {
-            validateTrigger: 'onBlur',
-          },
-          componentProps: {
-            api: uploadApi,
-            maxNumber: 1,
-            sizeUnit: 'GB',
-            maxSize: 5,
-            // accept: ['xls', 'xlsx'],
-            afterFetch: function (data) {
-              fileFlow.filePath = data.file;
-              return data;
-            },
-          },
-
           colProps: {
             span: 22,
           },
@@ -90,15 +120,45 @@
       function onDataReceive(data) {
         modelRef.value = data;
         resetFields();
-        setFieldsValue({
-          type: data.sceneName,
+        // setFieldsValue({
+        //   type: data.sceneName,
+        // });
+      }
+      function beforeUpload(file: File) {
+        return new Promise((resolve, reject) => {
+          const { size, name } = file;
+          let maxSize = 5; // 单位MB
+          let accept = ['zip']; // 单位MB
+          let sizeUnit = 'GB'; // 单位MB
+          const type = name.split('.').pop() || '';
+          console.log('beforeUpload', type, name);
+          if (accept && accept.length > 0 && !accept.includes(type.toLowerCase())) {
+            createMessage.error(t('component.upload.accept', [accept.join(',')]));
+            (file.status = 'error'), (fileFlow.list = []);
+            uploadRef.value.clearFiles();
+            return reject(false);
+          }
+          let newMaxSize =
+            sizeUnit == 'GB' ? maxSize * 1024 : sizeUnit == 'KB' ? maxSize / 1024 : maxSize;
+          // 设置最大值,则判断
+          if (maxSize && file.size / 1024 / 1024 >= newMaxSize) {
+            createMessage.error(
+              t('component.upload.maxSizeMultiple', { number: maxSize, unit: sizeUnit || 'MB' }),
+            );
+            (file.status = 'error'), (fileFlow.list = []);
+            uploadRef.value.clearFiles();
+            return reject(false);
+          }
+          fileFlow.list = [file];
+          return resolve(true);
         });
       }
       const handleSubmit = async () => {
         try {
           const params = await validate();
-          let filePath = params.filePath[0];
-          const resCheck = await uploadSceneCheck(filePath);
+          const filePath = fileFlow.filePath
+          console.log('params', params, filePath, fileFlow.list);
+          const resCheck = await uploadSceneCheck({filePath, sourceType: params.sourceType});
           if (resCheck.code == 60042) {
             return createConfirm({
               iconType: 'warning',
@@ -112,25 +172,33 @@
                   h('div', null, `确定继续吗?`),
                 ]),
               onOk: async () => {
-                Submit(filePath)
+                Submit(filePath);
               },
             });
           }
-          if(resCheck.code == 60043) {
+          if (resCheck.code == 60043) {
             return createConfirm({
               iconType: 'warning',
               title: '提示',
               content: `此场景此前已上传过‌,继续上传将重新计算并覆盖原场景,场景内由用户手动添加的空间模型也会清空,确定继续吗?`,
               onOk: async () => {
-                Submit(filePath)
+                Submit(filePath);
               },
             });
           }
-          Submit(filePath)
+          Submit(filePath);
         } catch (error) {
           console.log('not passing', error);
         }
       };
+      function handleChange(value){
+          console.log('handleChange', value);
+      }
+      function handleRemove(file){
+          fileFlow.list = fileFlow.list.filter(item => item.uid !== file.uid);
+          console.log('handleRemove', file);
+      }
+      
       function handleVisibleChange(v) {
         // console.log(v);
         // v && props.userData && nextTick(() => onDataReceive(props.userData));
@@ -138,7 +206,8 @@
       async function Submit(filePath) {
         loading.value = true;
         try {
-          const res = await uploadSceneOrig(filePath);
+          const params = await validate();
+          const res = await uploadSceneOrig({filePath, sourceType: params.sourceType});
           loading.value = false;
           console.log('res', res, filePath);
           closeModal();
@@ -148,7 +217,37 @@
         } catch (error) {
           loading.value = false;
         }
-
+      }
+      function handleProgress(file) {
+        // file 对象包含了上传进度信息
+        console.log('上传进度:', file.percent);
+        // antd-vue Upload 组件会自动处理进度条显示
+      }
+      async function handleCustomRequest(option) {
+        if(!option){
+          return;
+        }
+        const params = await validate();
+        let file = fileFlow.list && fileFlow.list[0];
+        const apiData = {
+          file: file,
+          data: {
+            dictId: params.dictId,
+          },
+        };
+        loading.value = true;
+        fileFlow.complete = 0;
+        function onUploadProgress(progressEvent: ProgressEvent) {
+          const complete = ((progressEvent.loaded / progressEvent.total) * 100) | 0;
+          fileFlow.complete = complete;
+        }
+        let url = await uploadApi(apiData, onUploadProgress);
+        option.onSuccess && option.onSuccess();
+        console.log('uploadApi', url);
+        fileFlow.filePath = url;
+        loading.value = false;
+        createMessage.success('上传成功。');
+        return url;
       }
       return {
         register,
@@ -156,13 +255,26 @@
         registerForm,
         model: modelRef,
         fileFlow,
+        handleProgress,
+        handleCustomRequest,
         handleVisibleChange,
         handleSubmit,
         addListFunc,
         loading,
         resetFields,
+        beforeUpload,
+        handleChange,
+        handleRemove,
+        uploadRef,
         t,
       };
     },
   });
 </script>
+<style lang="less">
+  .myzdyfrom {
+    .ant-upload-list {
+      width: 300px;
+    }
+  }
+</style>

+ 2 - 1
src/views/statistics/components/GrowCard.vue

@@ -13,8 +13,9 @@
           <Tag :color="item.color">{{ item.action }}</Tag>
         </template>
 
-        <div class="py-4 px-4 flex justify-between">
+        <div class="py-4 px-4 flex items-end ">
           <CountTo :prefix="item.unit" :startVal="1" :endVal="item.value" class="text-2xl" />
+          <span v-if="item.total">/{{ item.total }}{{ item.unit }}</span>
           <!-- <Icon :icon="item.icon" :size="40" /> -->
         </div>
 

+ 55 - 22
src/views/statistics/components/VisitSource.vue

@@ -1,11 +1,29 @@
 <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="type"
+            style="width: 100px; margin-right: 8px"
+            placeholder="全部地区"
+            :options="typeOptions"
+            @change="handleType"
+          />
+        </div>
+        <div class="selct" style="display: inline-block; margin-right: 0px">
+          <RangePicker v-model:value="selectTime" :picker="picker" />
+        </div>
+      </div>
+    </template>
     <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 { Card, Select, DatePicker } from 'ant-design-vue';
+  const { RangePicker } = DatePicker;
   import { useECharts } from '/@/hooks/web/useECharts';
   const props = defineProps({
     loading: Boolean,
@@ -18,6 +36,17 @@
       default: '300px',
     },
   });
+  const options = ref([
+    {
+      value: 'jack',
+      label: 'Jack (100)',
+    },
+    {
+      value: 'lucy',
+      label: 'Lucy (101)',
+    },
+  ]);
+  var colorList = ['#73DDFF', '#73ACFF', '#FDD56A', '#FDB36A', '#FD866A', '#9E87FF', '#58D5FF'];
   const chartRef = ref<HTMLDivElement | null>(null);
   const { setOptions } = useECharts(chartRef as Ref<HTMLDivElement>);
   watch(
@@ -32,33 +61,42 @@
         },
         legend: {
           bottom: '1%',
+          show: false,
           left: 'center',
         },
         series: [
           {
-            color: ['#5ab1ef', '#b6a2de', '#67e0e3', '#2ec7c9'],
-            name: '访问来源',
             type: 'pie',
-            radius: ['40%', '70%'],
-            avoidLabelOverlap: false,
+            center: ['50%', '50%'],
+            radius: ['40%', '60%'],
+            clockwise: true,
+            avoidLabelOverlap: true,
+            hoverOffset: 15,
             itemStyle: {
-              borderRadius: 10,
-              borderColor: '#fff',
-              borderWidth: 2,
+              normal: {
+                color: function (params) {
+                  return colorList[params.dataIndex];
+                },
+              },
             },
             label: {
-              show: false,
-              position: 'center',
-            },
-            emphasis: {
-              label: {
-                show: true,
-                fontSize: '12',
-                fontWeight: 'bold',
+              show: true,
+              position: 'outside',
+              formatter: '{a|{b}:{d}%}\n{hr|}',
+              rich: {
+                a: {
+                  padding: [-30, 15, -20, 15],
+                },
               },
             },
             labelLine: {
-              show: false,
+              normal: {
+                length: 20,
+                length2: 10,
+                lineStyle: {
+                  width: 1,
+                },
+              },
             },
             data: [
               { value: 1048, name: '搜索引擎' },
@@ -66,11 +104,6 @@
               { value: 580, name: '邮件营销' },
               { value: 484, name: '联盟广告' },
             ],
-            animationType: 'scale',
-            animationEasing: 'exponentialInOut',
-            animationDelay: function () {
-              return Math.random() * 100;
-            },
           },
         ],
       });

+ 4 - 4
src/views/statistics/components/lineEcharts.vue

@@ -3,17 +3,17 @@
     <template #extra>
       <div class="condition">
           <div class="selct" style="display: inline-block;">
-            <span style="margin-right:15px">颗粒度</span>
+            <!-- <span style="margin-right:15px">颗粒度</span> -->
             <Select
             v-model:value="value"
             label-in-value
-            style="width: 100px;margin-right:30px"
+            style="width: 100px;margin-right:8px"
             placeholder="请选择颗粒度"
             :options="options"
             @change="handleChange"
           ></Select>
           </div>
-          <a-button @click="export" type="primary" >导出</a-button>
+          <!-- <a-button @click="export" type="primary" >导出</a-button> -->
       </div>
     </template>
     <div ref="chartRef1" :style="{ width, height }"></div>
@@ -181,7 +181,7 @@ watch(
       series: [
         {
           name: 'Adidas',
-          type: 'line',
+          type: 'pie',
           data: yData.value,
           symbolSize: 1,
           smooth: true,

+ 60 - 62
src/views/statistics/components/lineEcharts2.vue

@@ -1,36 +1,34 @@
 <template>
-  <Card title="订单数据统计" :loading="loading">
+  <Card title="采集趋势" :loading="loading">
     <template #extra>
       <div class="condition">
-          <div class="selct" style="display: inline-block;">
-            <!-- <span style="margin-right:15px"></span> -->
-            <Select
+        <div class="selct" style="display: inline-block">
+          <!-- <span style="margin-right:15px"></span> -->
+          <Select
             v-model:value="type"
-            style="width: 100px;margin-right:30px"
-            placeholder="请选择颗粒度"
+            style="width: 100px; margin-right: 8px"
+            placeholder="全部地区"
             :options="typeOptions"
             @change="handleType"
-          ></Select>
-          </div>
-          
-          <div class="selct" style="display: inline-block;margin-right:15px">
-            <RangePicker v-model:value="selectTime" :picker="picker" />
-          </div>
-          
-          <div class="selct" style="display: inline-block;">
-            <!-- <span style="margin-right:15px">颗粒度</span> -->
-            <Select
+          />
+        </div>
+
+        <div class="selct" style="display: inline-block">
+          <!-- <span style="margin-right:15px">颗粒度</span> -->
+          <Select
             v-model:value="value"
-            style="width: 100px;margin-right:30px"
-            placeholder="请选择颗粒度"
+            style="width: 100px; margin-right: 8px"
+            placeholder="全部警种"
             :options="options"
             @change="handleData"
-          ></Select>
-          </div>
-          <a-button type="primary" >导出</a-button>
+          />
+        </div>
+        <div class="selct" style="display: inline-block; margin-right: 0px">
+          <RangePicker v-model:value="selectTime" :picker="picker" />
+        </div>
       </div>
     </template>
-      <div ref="chartRef" :style="{ height, width }"></div>
+    <div ref="chartRef" :style="{ height, width }"></div>
   </Card>
 </template>
 <script lang="ts">
@@ -39,61 +37,61 @@
 </script>
 <script lang="ts" setup>
   import { Card, Select, DatePicker } from 'ant-design-vue';
-  const {RangePicker} = DatePicker;
+  const { RangePicker } = DatePicker;
   import { ref, Ref, watch } from 'vue';
   // import type { dataItemType } from './props';
   import { useECharts } from '/@/hooks/web/useECharts';
   import type { Dayjs } from 'dayjs';
   const props = defineProps({
-  loading: Boolean,
+    loading: Boolean,
     ...basicProps,
   });
-  
+
   type RangeValue = [Dayjs, Dayjs];
-  const picker = ref('date')
-  const value = ref('1');
+  const picker = ref('date');
+  const value = ref(null);
   const selectTime = ref<RangeValue>();
   const options = ref<SelectProps['options']>([
-        {
-          value: '1',
-          label: '日',
-        },
-        {
-          value: '2',
-          label: '周',
-        },
-        {
-          value: '2',
-          label: '月',
-        },
-      ]);
-  const type = ref('1');
+    {
+      value: '1',
+      label: '日',
+    },
+    {
+      value: '2',
+      label: '周',
+    },
+    {
+      value: '2',
+      label: '月',
+    },
+  ]);
+  const type = ref(null);
   const typeOptions = ref<SelectProps['options']>([
-        {
-          value: '1',
-          label: '数量',
-        },
-        {
-          value: '2',
-          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']);
+    {
+      value: '1',
+      label: '数量',
+    },
+    {
+      value: '2',
+      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 handleData(val){
-    let obj ={
-      1:'date',
-      2:'week',
-      3:'month',
-    }
-    console.log('handleChange',val)
+  function handleData(val) {
+    let obj = {
+      1: 'date',
+      2: 'week',
+      3: 'month',
+    };
+    console.log('handleChange', val);
   }
-  function handleType(val){
-    console.log('handleChange',val)
+  function handleType(val) {
+    console.log('handleChange', val);
   }
   function handlesetOptions() {
     setOptions({

+ 69 - 78
src/views/statistics/components/orderEchart.vue

@@ -1,7 +1,13 @@
 <template>
-  <Card :title="title||'订单数据统计'">
+  <Card :title="title || '订单数据统计'">
     <template #extra>
-      <condition type="2" :typeShow="title=='相机出库数量统计'" :name="title=='订单数据统计'?{1:'金额',0:'数量'}:{}" @change="Search" @expor="expor" />
+      <condition
+        type="2"
+        :typeShow="title == '相机出库数量统计'"
+        :name="title == '订单数据统计' ? { 1: '金额', 0: '数量' } : {}"
+        @change="Search"
+        @expor="expor"
+      />
     </template>
     <div ref="chartRef" :style="{ height, width }"></div>
   </Card>
@@ -10,49 +16,49 @@
   import { basicProps } from './props';
   import condition from './condition.vue';
   import { Card, DatePicker } from 'ant-design-vue';
-  import { ref, Ref, watch, defineEmits } from 'vue';
+  import { ref, Ref, watch, defineEmits, onMounted } from 'vue';
   import { useECharts } from '/@/hooks/web/useECharts';
-  import { exportElsxFile, } from '/@/utils/file/download';
+  import { exportElsxFile } from '/@/utils/file/download';
   const props = defineProps({
-  loading: Boolean,
+    loading: Boolean,
     ...basicProps,
   });
-  const emit = defineEmits(["alertSome"])
-  const downOrderData = ref<number[]>([]);
+  const emit = defineEmits(['alertSome']);
+  const downOrderData = ref<number[]>([200, 100, 150, 200]);
   const incrementOrderData = ref<number[]>([]);
   const partsOrderData = ref<number[]>([]);
-  const yixStringData = ref<string[]>([]);
-  const echartTypr = ref('line')
-  const nameList = ref<string[]>(['下载订单','权益订单','配件订单']);
+  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>)
+  const { setOptions } = useECharts(chartRef as Ref<HTMLDivElement>);
 
-  function Search(val){
-    emit('change',val)
+  function Search(val) {
+    emit('change', val);
   }
-  function expor(val){
+  function expor(val) {
     // emit('expor',val)
-    console.log('数量',val.value)
-    let hader = ['时间', ...nameList.value]
-    let fields  = {
-      'time':'日期',
-      '1':hader[1],
-      '2':hader[2],
-      '3':hader[3],
+    console.log('数量', val.value);
+    let hader = ['时间', ...nameList.value];
+    let fields = {
+      time: '日期',
+      '1': hader[1],
+      '2': hader[2],
+      '3': hader[3],
+    };
+    if (props.title == '订单数据统计' && val?.value) {
+      fields.time = `${val.value == 0 ? '数量/' : '金额/'}` + fields.time;
     }
-    if(props.title=='订单数据统计' && val?.value){
-      fields.time = `${val.value == 0?'数量/':'金额/'}` + fields.time
-    }
-    let data = yixStringData.value.map((ele,index) => {
+    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)
+        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({
@@ -67,6 +73,7 @@
       },
       legend: {
         orient: 'horizontal',
+        show: false,
         bottom: 0,
       },
       // grid: { left: '2%', right: '2%', top: '10%', bottom: '10%', containLabel: true },
@@ -80,52 +87,36 @@
         // max: maxSize.value,
         splitNumber: 4,
       },
-      series: [
-        {
-          data: downOrderData.value,
-          type: echartTypr.value,
-          itemStyle: { color: '#38a0ff' },
-          barMaxWidth: 80,
-          name: nameList.value[0],
-        },
-        {
-          data: incrementOrderData.value,
-          type: echartTypr.value,
-          itemStyle: { color: '#4cca73' },
-          barMaxWidth: 80,
-          name: nameList.value[1],
-        },
-        {
-          data: partsOrderData.value,
-          type: echartTypr.value,
-          itemStyle: { color: '#FDD56A' },
-          barMaxWidth: 80,
-          name: nameList.value[2],
-        },
-      ],
+      series: {
+        data: downOrderData.value,
+        type: 'bar',
+        itemStyle: { color: '#38a0ff' },
+        barMaxWidth: 80,
+        name: 'nameList.value',
+      },
     });
   }
-  watch(
-    () => props.echartData,
-    (echartData) => {
-      downOrderData.value = echartData.downOrder ||[]
-      incrementOrderData.value = echartData.incrementOrder ||[]
-      partsOrderData.value = echartData.partOrder ||[]
-      yixStringData.value = echartData.xdata ||[]
-      if(echartData.nameList){
-        nameList.value = echartData.nameList
-      }
-      if(echartData.echartTypr){
-        echartTypr.value = echartData.echartTypr
-      }
-      const maxNumber = Math.max(...echartData.downOrder.concat(echartData.incrementOrder));
-      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,
-    },
-  );
+  onMounted(() => {
+    handlesetOptions();
+  })
+  // watch(
+  //   () => props.echartData,
+  //   (echartData) => {
+  //     downOrderData.value = echartData.downOrder || [];
+  //     incrementOrderData.value = echartData.incrementOrder || [];
+  //     partsOrderData.value = echartData.partOrder || [];
+  //     yixStringData.value = echartData.xdata || [];
+  //     if (echartData.nameList) {
+  //       nameList.value = echartData.nameList;
+  //     }
+  //     if (echartData.echartTypr) {
+  //       echartTypr.value = echartData.echartTypr;
+  //     }
+  //     handlesetOptions();
+  //   },
+  //   {
+  //     immediate: true,
+  //     deep: true,
+  //   },
+  // );
 </script>

+ 194 - 0
src/views/statistics/scene/addModal.vue

@@ -0,0 +1,194 @@
+<template>
+  <BasicModal
+    v-bind="$attrs"
+    @register="register"
+    title="权限"
+    @cancel="resetFields"
+    @ok="handleSubmit"
+  >
+    <div class="pt-2px pr-3px">
+      <BasicForm @register="registerForm">
+        <template #text="{ model, field }">
+          {{ model[field] }}
+        </template>
+      </BasicForm>
+    </div>
+  </BasicModal>
+</template>
+<script lang="ts">
+  import { defineComponent, ref, onMounted, reactive } from 'vue';
+  import { BasicModal, useModalInner } from '/@/components/Modal';
+  import { BasicForm, FormSchema, useForm } from '/@/components/Form/index';
+  import { useMessage } from '/@/hooks/web/useMessage';
+  import { useI18n } from '/@/hooks/web/useI18n';
+  import { getinnerByRyId, userShareAdd } from '/@/api/operate';
+
+  const { t } = useI18n();
+  export default defineComponent({
+    components: { BasicModal, BasicForm },
+    props: {
+      userData: { type: Object },
+    },
+    emits: ['ok'],
+    setup(_, { emit }) {
+      const modelRef = ref(false);
+      const fileFlow = reactive({
+        file: null,
+      });
+      const { createMessage } = useMessage();
+      const optionsName = ref([]);
+      const schemas: FormSchema[] = [
+        {
+          field: 'id',
+          component: 'Input',
+          show: false,
+          label: 'id',
+          defaultValue: '111',
+          required: false,
+        },
+        {
+          field: 'ryNo',
+          component: 'Input',
+          label: '人员编号',
+          required: true,
+          rules: [
+            {
+              required: true,
+              // @ts-ignore
+              validator: async (rule, value) => {
+                let myData = await getFieldsValue();
+                if (!value) {
+                  return Promise.reject('请输入人员编号');
+                }
+                let res = await getinnerByRyId({ ryNo: value });
+                console.log('value', value, res);
+                if (res && !res.data) {
+                  return Promise.reject('人员编号不存在');
+                }
+                Promise.resolve();
+                let ryNickName = res && res.data && res.data.ryNickName;
+                if(myData.ryNickName != ryNickName || !myData.ryNickName){
+                  setFieldsValue({
+                    ryNickName: res && res.data ? res.data.ryNickName : '',
+                    id: res && res.data ? res.data.id : '',
+                  });
+                }
+              },
+              trigger: 'blur',
+            },
+          ],
+          colProps: {
+            span: 20,
+          },
+        },
+        // {
+        //   field: 'ryNo',
+        //   component: 'AutoComplete',
+        //   label: '人员编号',
+        //   required: true,
+        //   componentProps: {
+        //     filterOption: onFilterOption,
+        //     onSearch: onSearch,
+        //     onChange: (data) => {
+        //       setTimeout(() => {
+        //         const item = optionsName.value && optionsName.value.find((ele) => ele.ryNo == data);
+        //         setFieldsValue({
+        //           ryNickName: item && item.ryNickName ? item.ryNickName : '',
+        //           id: item && item.id ? item.id : '',
+        //         });
+        //       }, 100);
+        //     },
+        //   },
+        //   colProps: {
+        //     span: 20,
+        //   },
+        // },
+        {
+          field: 'ryNickName',
+          component: 'Input',
+          label: '姓名',
+          // rules: [
+          //   {
+          //     required: true,
+          //     // @ts-ignore
+          //     validator: async (rule, value) => {
+          //       if (!value) {
+          //         return Promise.reject('请输入正确的人员编号');
+          //       }
+          //       return Promise.resolve();
+          //     },
+          //     trigger: 'change',
+          //   },
+          // ],
+          colProps: {
+            span: 20,
+          },
+          componentProps: {
+            disabled: true,
+          },
+        },
+      ];
+      const [
+        registerForm,
+        { validate, resetFields, setFieldsValue, getFieldsValue, updateSchema },
+      ] = useForm({
+        labelWidth: 110,
+        schemas,
+        showActionButtonGroup: false,
+        actionColOptions: {
+          span: 24,
+        },
+      });
+
+      onMounted(() => {});
+      let addListFunc = () => {};
+      const [register, { closeModal }] = useModalInner(async (data) => {
+        console.log('option', data);
+      });
+      function onFilterOption(inputText: string, option) {
+        console.log('option', inputText, option.value);
+        if (option.value) {
+          return option.value.indexOf(inputText) >= 0;
+        }
+        // return option.value.indexOf(inputText.toUpperCase()) >= 0;
+      }
+      async function onSearch(searchText) {
+        const res = await getinnerByRyId({ ryNo: searchText });
+        console.log('res', res.data);
+        if (!res.data) return;
+        optionsName.value = res.data || {};
+        updateSchema({
+          field: 'ryNo',
+          componentProps: {
+            options: [optionsName.value],
+          },
+        });
+      }
+      const handleSubmit = async () => {
+        const params = await validate();
+        try {
+          console.log('params', params);
+          await userShareAdd(params);
+          closeModal();
+          resetFields();
+          emit('ok');
+          createMessage.success('操作成功');
+        } catch (error) {
+          console.log('not passing', error);
+        }
+      };
+
+      return {
+        register,
+        schemas,
+        registerForm,
+        modelRef,
+        fileFlow,
+        handleSubmit,
+        addListFunc,
+        resetFields,
+        t,
+      };
+    },
+  });
+</script>

+ 178 - 0
src/views/statistics/scene/exportModal.vue

@@ -0,0 +1,178 @@
+<template>
+  <BasicModal
+    v-bind="$attrs"
+    @register="register"
+    title="查询并导出"
+    @cancel="resetFields"
+    :min-height="300"
+    @ok="handleSubmit"
+  >
+    <div class="pt-2px pr-3px">
+      <BasicForm @register="registerForm">
+        <template #text="{ model, field }">
+          {{ model[field] }}
+        </template>
+      </BasicForm>
+    </div>
+  </BasicModal>
+</template>
+<script lang="ts">
+  import { defineComponent, ref, onMounted, reactive } from 'vue';
+  import { BasicModal, useModalInner } from '/@/components/Modal';
+  import { BasicForm, FormSchema, useForm } from '/@/components/Form/index';
+  import { useMessage } from '/@/hooks/web/useMessage';
+  import { useI18n } from '/@/hooks/web/useI18n';
+  import { getinnerByRyId, userShareAdd } from '/@/api/operate';
+  import { sceneGroupCount } from '/@/api/jyUserPlatform/index'; //roleLIstApi
+
+  const { t } = useI18n();
+  export default defineComponent({
+    components: { BasicModal, BasicForm },
+    props: {
+      userData: { type: Object },
+    },
+    emits: ['ok'],
+    setup(_, { emit }) {
+      const modelRef = ref(false);
+      const fileFlow = reactive({
+        file: null,
+      });
+      const { createMessage } = useMessage();
+      const optionsName = ref([]);
+      const schemas: FormSchema[] = [
+        {
+          field: 'd3',
+          component: 'Select',
+          label: '地区',
+          componentProps: {
+            options: [
+              {
+                label: '1个月',
+                value: 1,
+                key: '1',
+              },
+              {
+                label: '2个月',
+                value: 2,
+                key: '2',
+              },
+            ],
+          },
+          colProps: {
+            span: 20,
+          },
+        },
+        {
+          field: 'd2',
+          component: 'Select',
+          label: '警种',
+          componentProps: {
+            options: [
+              {
+                label: '1个月',
+                value: 1,
+                key: '1',
+              },
+              {
+                label: '2个月',
+                value: 2,
+                key: '2',
+              },
+            ],
+          },
+          colProps: {
+            span: 20,
+          },
+        },
+        {
+          field: 'type',
+          label: '相机类型',
+          component: 'ApiSelect',
+          componentProps: {
+            style: { maxWidth: '250px' },
+            placeholder: '全部',
+            api: sceneGroupCount,
+            immediate: false,
+            resultField: 'list',
+            labelField: 'name',
+            valueField: 'id',
+            params: { type: 'camera' },
+          },
+          colProps: {
+            xl: 12,
+            xxl: 12,
+          },
+        },
+        {
+          field: 'riq',
+          component: 'RangePicker',
+          label: '时间',
+          colProps: {
+            span: 20,
+          },
+        },
+      ];
+      const [
+        registerForm,
+        { validate, resetFields, setFieldsValue, getFieldsValue, updateSchema },
+      ] = useForm({
+        labelWidth: 110,
+        schemas,
+        showActionButtonGroup: false,
+        actionColOptions: {
+          span: 24,
+        },
+      });
+
+      onMounted(() => {});
+      let addListFunc = () => {};
+      const [register, { closeModal }] = useModalInner(async (data) => {
+        console.log('option', data);
+      });
+      function onFilterOption(inputText: string, option) {
+        console.log('option', inputText, option.value);
+        if (option.value) {
+          return option.value.indexOf(inputText) >= 0;
+        }
+        // return option.value.indexOf(inputText.toUpperCase()) >= 0;
+      }
+      async function onSearch(searchText) {
+        const res = await getinnerByRyId({ ryNo: searchText });
+        console.log('res', res.data);
+        if (!res.data) return;
+        optionsName.value = res.data || {};
+        updateSchema({
+          field: 'ryNo',
+          componentProps: {
+            options: [optionsName.value],
+          },
+        });
+      }
+      const handleSubmit = async () => {
+        const params = await validate();
+        try {
+          console.log('params', params);
+          await userShareAdd(params);
+          closeModal();
+          resetFields();
+          emit('ok');
+          createMessage.success('操作成功');
+        } catch (error) {
+          console.log('not passing', error);
+        }
+      };
+
+      return {
+        register,
+        schemas,
+        registerForm,
+        modelRef,
+        fileFlow,
+        handleSubmit,
+        addListFunc,
+        resetFields,
+        t,
+      };
+    },
+  });
+</script>

+ 109 - 9
src/views/statistics/scene/index.vue

@@ -1,22 +1,106 @@
 <template>
   <div class="p-4">
+    <div class="home flex justify-between">
+      <div class="homeLeft" style="margin-bottom: 10px">
+        <Select
+          v-model:value="value"
+          style="width: 100px; margin-right: 30px"
+          placeholder="全部相机"
+          :options="options"
+          @change="handleChange"
+        />
+        <span>当前账号采集总数:152 | 更新于 2025-09-22 04:11:27</span>
+      </div>
+      <div class="homeright">
+        <a-button style="margin-right: 15px" type="primary" @click="openModal(true, { })">
+          查询并导出</a-button
+        >
+        <a-button @click="openAddModal(true, {})"> 页面权限</a-button>
+      </div>
+    </div>
     <GrowCard :loading="loading" :list="growCardList" class="enter-y" />
+    <div class="md:flex !my-4 enter-y">
+      <lineEcharts2
+        :options="optionsList"
+        name="chartRef2"
+        class="md:w-1/2 w-full !md:mt-0 !mt-4 !md:mr-4"
+        @export="handleExport"
+        @change="Search"
+        :echartData="orderData"
+      />
+      <VisitSource
+        class="md:w-1/2 mx-4 w-full"
+        name="chartRef1"
+        @change="Search"
+        :propsData="echartData"
+        @export="handleExport"
+      />
+    </div>
+    <lineEcharts2
+      name="chartRef2"
+      class="!my-4 enter-y"
+      @export="handleExport"
+      @change="Search"
+      :echartData="orderData"
+    />
+    <orderEchart
+      title="近半年场景新增趋势"
+      class="!my-4 enter-y"
+      @export="handleExport"
+      @change="Search"
+      :echartData="scenetData"
+    />
     <!-- <sceneEchart
-      title="场景趋势分析"
+      title="采集趋势"
       class="!my-4 enter-y"
       @change="Search"
       :echartData="echartData"
     /> -->
+    <addModal @register="registerAdd" @ok="reload" />
+    <exportModal @register="register" @ok="reload" />
   </div>
 </template>
 <script lang="ts" setup>
   import { ref, onMounted, reactive } from 'vue';
+  import { sceneGroupCount } from '/@/api/jyUserPlatform/index'; //roleLIstApi
   import { sceneTotal, sceneTrend } from '/@/api/statistics/index';
+  import VisitSource from '../components/VisitSource.vue';
+  import lineEcharts2 from '../components/lineEcharts2.vue';
+  import { useModal } from '/@/components/Modal';
+  import addModal from './addModal.vue';
+  import exportModal from './exportModal.vue';
+  import { Select } from 'ant-design-vue';
   import { GrowCardItem } from '../data';
   import GrowCard from '../components/GrowCard.vue';
   import sceneEchart from '../components/sceneEchart.vue';
+  import orderEchart from '../components/orderEchart.vue';
   const loading = ref(true);
   const growCardList = ref<GrowCardItem[]>([]);
+  const value = ref(null);
+  const options = ref([]);
+  const optionsList = ref({
+    region: [
+      // { value: '', label: '全部地区' },
+      { value: '1', label: '珠海' },
+      { value: '2', label: '江门' },
+    ],
+    police: [
+      // { value: '', label: '全部警种' },
+      { value: '1', label: '民警' },
+      { value: '2', label: '刑警' },
+    ],
+  });
+  const scenetData = reactive({
+    xdata: [],
+    kjList: [],
+    kkList: [],
+    ssList: [],
+    ssobjList: [],
+    echartTypr: 'bar',
+  });
+  sceneGroupCount({ type: 'camera' }).then((res) => {
+    options.value = res.map((ele) => ({ label: ele.name, value: ele.id }));
+  });
   const echartData = reactive({
     xdata: [],
     kjList: [],
@@ -33,6 +117,8 @@
     dataType: 0,
     type: 2,
   });
+  const [registerAdd, { openModal: openAddModal }] = useModal();
+  const [register, { openModal }] = useModal();
   onMounted(() => {
     getData();
     // getList();
@@ -40,7 +126,9 @@
   async function getList() {
     let downlist = [],
       xdata = [];
-    const { kjList, kkList, ssList, ssobjList, sgList, sgobjList, yzlList } = await sceneTrend(SearchData);
+    const { kjList, kkList, ssList, ssobjList, sgList, sgobjList, yzlList } = await sceneTrend(
+      SearchData,
+    );
     kjList.map((ele) => {
       xdata.push(ele.groupKey);
       downlist.push(ele.count);
@@ -69,15 +157,15 @@
       const { totalSceneCount = 0, preMonthAddCount = 0, todayAddCount = 0 } = await sceneTotal();
       let list: GrowCardItem[] = [
         {
-          title: '累计场景数量',
+          title: '今日采集数量',
           icon: 'fxemoji:notchedrightsemi3dot',
           value: totalSceneCount,
           unit: '个',
-          color: 'green',
-          action: '',
+          color: 'orange',
+          action: '',
         },
         {
-          title: '上月新增场景数量',
+          title: '30日采集数量',
           icon: 'download-count|svg',
           value: preMonthAddCount,
           unit: '个',
@@ -85,12 +173,21 @@
           action: '月',
         },
         {
-          title: '今日新增场景数量',
+          title: '累计采集数量',
           icon: 'transaction|svg',
           value: todayAddCount,
           unit: '个',
-          color: 'orange',
-          action: '日',
+          color: 'green',
+          action: '年',
+        },
+        {
+          title: '总采集用户数/总用户数',
+          icon: 'transaction|svg',
+          value: todayAddCount,
+          total: 1600,
+          unit: '个',
+          color: 'green',
+          action: '年',
         },
       ];
       loading.value = false;
@@ -99,4 +196,7 @@
       loading.value = false;
     }
   }
+  function handleChange(val) {
+    console.log('val', val);
+  }
 </script>