Browse Source

大屏首页

ZaiZai 1 year ago
parent
commit
d6bdaf9d94

+ 4 - 4
package.json

@@ -13,15 +13,15 @@
         "lint:fix": "eslint . --fix"
     },
     "dependencies": {
-        "axios": "^1.6.3",
+        "axios": "^1.6.7",
         "crypto-js": "^4.2.0",
         "dayjs": "^1.11.10",
         "echarts": "^5.4.2",
-        "element-plus": "^2.5.1",
+        "element-plus": "^2.5.3",
         "hc-vue3-ui": "^2.9.6",
-        "js-base64": "^3.7.5",
+        "js-base64": "^3.7.6",
         "js-cookie": "^3.0.5",
-        "js-fast-way": "^0.3.8",
+        "js-fast-way": "^0.4.3",
         "js-md5": "^0.8.3",
         "js-web-screen-shot": "^1.9.9",
         "nprogress": "^0.2.0",

+ 1 - 1
public/version.json

@@ -1,3 +1,3 @@
 {
-  "value": "20240125152607"
+  "value": "20240125154314"
 }

+ 48 - 0
src/api/modules/project.js

@@ -0,0 +1,48 @@
+import { httpApi } from '../request/httpApi'
+
+export default {
+    //获取项目和合同段
+    async getProjectAndContract() {
+        return httpApi({
+            url: '/api/blade-business/userViewProjectContract/queryUserViewProjectAndContract',
+            method: 'get',
+            params: {},
+        })
+    },
+    //获取默认项目
+    async getDefaultProject(form) {
+        return httpApi({
+            url: '/api/blade-business/defaultProject/queryUserDefault',
+            method: 'get',
+            params: form,
+        })
+    },
+    //获取项目详情
+    async getProjectInfo(id) {
+        return httpApi({
+            url: '/api/blade-manager/projectInfo/detail',
+            method: 'get',
+            params: {
+                id: id ? id + '' : '',
+            },
+        })
+    },
+    //获取合同段详情
+    async getContractInfo(id) {
+        return httpApi({
+            url: '/api/blade-manager/contractInfo/detail',
+            method: 'get',
+            params: {
+                id: id ? id + '' : '',
+            },
+        })
+    },
+    //设置默认项目
+    async setDefaultProject(form) {
+        return httpApi({
+            url: '/api/blade-business/defaultProject/save',
+            method: 'post',
+            data: form,
+        })
+    },
+}

+ 52 - 49
src/api/modules/using/query.js

@@ -1,42 +1,45 @@
 import { httpApi } from '../../request/httpApi'
 
 export default {
-  //分页
-async getarchiveQueryPage(form, msg = true) {
-    return httpApi({
-        url: '/api/blade-archive/archivesauto/pageByArchivesAuto',
-        method: 'get',
-        params: form,
-
-    }, msg)
-  },
-  //获取目录树 /blade-manager/archiveTreeContract/getArchiveTreeByNodeType
-  async getArchiveTreeByNodeType(form, msg = true) {
-    return httpApi({
-        url: '/api/blade-manager/archiveTreeContract/getArchiveTreeByNodeType',
-        method: 'get',
-        params: form,
-
-    }, msg)
-  },
-//获取目录树子节点 GET/blade-manager/archiveTreeContract/getChildrenNodeByNodeId
-  async getChildrenNodeByNodeId(form, msg = true) {
-    return httpApi({
-        url: '/api/blade-manager/archiveTreeContract/getChildrenNodeByNodeId',
-        method: 'get',
-        params: form,
-
-    }, msg)
-  },
-  //获取档案查询类别/blade-archive/archivesauto/getCarrierTypeByDict
-  async getCarrierTypeByDict(form, msg = true) {
-    return httpApi({
-        url: '/api/blade-archive/archivesauto/getCarrierTypeByDict',
-        method: 'get',
-        params: form,
-
-    }, msg)
-  },
+    //分页
+    async getarchiveQueryPage(form, msg = true) {
+        return httpApi({
+            url: '/api/blade-archive/archivesauto/pageByArchivesAuto',
+            method: 'get',
+            params: form,
+        }, msg)
+    },
+    async getarchiveQueryPage2(form, msg = true) {
+        return httpApi({
+            url: '/api/blade-archive/archivesauto/pageByArchivesAuto2',
+            method: 'get',
+            params: form,
+        }, msg)
+    },
+    //获取目录树 /blade-manager/archiveTreeContract/getArchiveTreeByNodeType
+    async getArchiveTreeByNodeType(form, msg = true) {
+        return httpApi({
+            url: '/api/blade-manager/archiveTreeContract/getArchiveTreeByNodeType',
+            method: 'get',
+            params: form,
+        }, msg)
+    },
+    //获取目录树子节点 GET/blade-manager/archiveTreeContract/getChildrenNodeByNodeId
+    async getChildrenNodeByNodeId(form, msg = true) {
+        return httpApi({
+            url: '/api/blade-manager/archiveTreeContract/getChildrenNodeByNodeId',
+            method: 'get',
+            params: form,
+        }, msg)
+    },
+    //获取档案查询类别/blade-archive/archivesauto/getCarrierTypeByDict
+    async getCarrierTypeByDict(form, msg = true) {
+        return httpApi({
+            url: '/api/blade-archive/archivesauto/getCarrierTypeByDict',
+            method: 'get',
+            params: form,
+        }, msg)
+    },
     //档案柜切换档案查看权限
     async getArchivesAuthByUser(form, msg = true) {
         return httpApi({
@@ -55,20 +58,20 @@ async getarchiveQueryPage(form, msg = true) {
     },
     //查询案卷里的文件
     async getArchiveFileList(form, msg = true) {
-      return httpApi({
-          url: '/api/blade-archive/archivesauto/getArchiveFileList',
-          method: 'get',
-          params: form,
-      }, msg)
-  },
+        return httpApi({
+            url: '/api/blade-archive/archivesauto/getArchiveFileList',
+            method: 'get',
+            params: form,
+        }, msg)
+    },
     //批量下载档案
     async batchDownloadFileToZip(form, msg = true) {
-      return httpApi({
-          url: '/api/blade-archive/archivesauto/batchDownloadFileToZip',
-          method: 'get',
-          params: form,
-          responseType: 'blob',
-      }, msg)
-  },
+        return httpApi({
+            url: '/api/blade-archive/archivesauto/batchDownloadFileToZip',
+            method: 'get',
+            params: form,
+            responseType: 'blob',
+        }, msg)
+    },
 }
 

+ 21 - 14
src/api/modules/using/stats.js

@@ -1,4 +1,4 @@
-import {httpApi} from "../../request/httpApi";
+import { httpApi } from '../../request/httpApi'
 
 export default {
     //档案统计-已组案卷
@@ -7,17 +7,17 @@ export default {
             url: '/api/blade-archive/archivesauto/allArchiveByContractType',
             method: 'get',
             params: form,
-        
-        }, msg);
+
+        }, msg)
     },
-    //获取档案年限占比 
+    //获取档案年限占比
     async getallArchiveAge(form, msg = true) {
         return httpApi({
             url: '/api/blade-archive/archivesauto/allArchiveAgeByContractType',
             method: 'get',
             params: form,
-        
-        }, msg);
+
+        }, msg)
     },
     //获取已销毁案卷
     async getallArchiveDestory(form, msg = true) {
@@ -25,8 +25,8 @@ export default {
             url: '/api/blade-archive/archivesauto/allDeletedArchiveByContractType',
             method: 'get',
             params: form,
-        
-        }, msg);
+
+        }, msg)
     },
     //获取原生文件数量getallnativeChartData
     async getallnativeChartData(form, msg = true) {
@@ -34,8 +34,8 @@ export default {
             url: '/api/blade-archive/archiveFile/allArchiveFileByContractType',
             method: 'get',
             params: form,
-        
-        }, msg);
+
+        }, msg)
     },
     //获取总存储getallArchiveSize
     async getallArchiveSize(form, msg = true) {
@@ -43,8 +43,7 @@ export default {
             url: '/api/blade-archive/archiveFile/allArchiveFileSize',
             method: 'get',
             params: form,
-        
-        }, msg);
+        }, msg)
     },
     //获取归档目录文件夹
     async getArchiveTreeAndArchiveCount(form, msg = true) {
@@ -52,8 +51,16 @@ export default {
             url: '/api/blade-manager/archiveTreeContract/getArchiveTreeAndArchiveCount',
             method: 'get',
             params: form,
-        
-        }, msg);
+
+        }, msg)
+    },
+    //获取项目内外页台账完成比例
+    async getProjectStat(form, msg = true) {
+        return httpApi({
+            url: '/api/blade-manager/wbsTreeContract/getProjectStat',
+            method: 'get',
+            params: form,
+        }, msg)
     },
 }
 

BIN
src/assets/datav/bg15.jpg


BIN
src/assets/datav/header1.png


+ 1 - 0
src/assets/datav/header2.svg

@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 406.35 71.38"><defs><style>.cls-1{fill:none;stroke:#00b4eb;stroke-miterlimit:10;}.cls-2{fill:#00b4eb;}</style></defs><title>资源 3</title><g id="图层_2" data-name="图层 2"><g id="Layer_2" data-name="Layer 2"><polygon class="cls-1" points="0.39 71.02 57.65 0.5 348.7 0.5 405.96 71.02 339.23 5.25 67.99 5.25 0.39 71.02"/><path class="cls-2" d="M88.25,13.45H318.1a20.1,20.1,0,0,0,9-2.13L348.7.5h-291L79.24,11.32A20.1,20.1,0,0,0,88.25,13.45Z"/></g></g></svg>

File diff suppressed because it is too large
+ 0 - 0
src/assets/datav/header3.svg


+ 1 - 1
src/config/index.json

@@ -1,6 +1,6 @@
 {
     "version": "202304141558",
-    "target": "http://192.168.0.109:8090",
+    "target": "http://192.168.0.152:8090",
     "smsPhone": "",
     "vite": {
         "port": 5175,

+ 1 - 0
src/config/theme.js

@@ -2,6 +2,7 @@
 export default {
     color: [
         { name: 'blue', color: '#204DA0', label: '深蓝' },
+        { name: 'blue1', color: '#0D3253', label: '暗蓝' },
         { name: 'light-blue', color: '#409eff', label: '浅蓝' },
         { name: 'black1', color: '#2c3643', label: '黑色' },
     ],

+ 105 - 0
src/global/components/echarts/echarts.vue

@@ -0,0 +1,105 @@
+<template>
+    <div class="hc-echarts-box">
+        <div ref="echart" class="hc-echarts" :style="`width : ${chart?.clientWidth}px`" />
+    </div>
+</template>
+
+<script setup>
+import * as echarts from 'echarts'
+import { getObjValue } from 'js-fast-way'
+import { onMounted, onUnmounted, ref, watch } from 'vue'
+const props = defineProps({
+    option: {
+        type: Object,
+        default: () => ({}),
+    },
+    dark: {
+        type: Boolean,
+        default: false,
+    },
+})
+
+defineOptions({
+    name: 'HcCharts',
+})
+
+//初始变量
+let chart = null
+const echart = ref(null)
+const options = ref(getObjValue(props.option))
+
+//深度监听
+watch(() => [
+    props.option,
+], ([option]) => {
+    options.value = getObjValue(option)
+    setOptions(options.value)
+}, { deep: true })
+
+//初始化图表
+const initChart = () => {
+    chart = echarts.init(echart.value, props.dark ? 'dark' : 'light')
+    setOptions(options.value)
+}
+
+//监听浏览器窗口变化
+const windowResize = () => {
+    window.addEventListener('resize', resizeEvent)
+}
+
+const resizeEvent = () => {
+    window.requestAnimationFrame(() => {
+        chart.resize()
+    })
+}
+
+//设置图表
+const setOptions = (option) => {
+    chart?.setOption(option)
+}
+
+//渲染完成
+onMounted(() => {
+    initChart()
+    windowResize()
+})
+
+//被卸载
+onUnmounted(() => {
+    window.removeEventListener('resize', resizeEvent)
+    chart.dispose()
+    chart = null
+})
+
+const getWidth = () => {
+    return chart?.getWidth()
+}
+
+const onResize = () => {
+    chart?.resize()
+}
+
+// 暴露出去
+defineExpose({
+    onResize,
+    getWidth,
+})
+</script>
+
+<style lang="scss" scoped>
+.hc-echarts-box {
+    display: block;
+    height: 100%;
+    overflow: hidden;
+    position: relative;
+    .hc-echarts {
+        position: absolute;
+        bottom: 0;
+        left: 0;
+        right: 0;
+        z-index: 2;
+        width: 100%;
+        height: 100%;
+    }
+}
+</style>

+ 2 - 0
src/global/components/index.js

@@ -4,6 +4,7 @@ import HcReportModal from './hc-report-modal/index.vue'
 import HcReportExperts from './hc-report-experts/index.vue'
 import HcTasksUser from './hc-tasks-user/index.vue'
 import HcBorderNeon from './hc-border-neon/index.vue'
+import HcCharts from './echarts/echarts.vue'
 
 //注册全局组件
 export const setupComponents = (App) => {
@@ -13,4 +14,5 @@ export const setupComponents = (App) => {
     App.component('HcReportExperts', HcReportExperts)
     App.component('HcTasksUser', HcTasksUser)
     App.component('HcBorderNeon', HcBorderNeon)
+    App.component('HcCharts', HcCharts)
 }

+ 2 - 2
src/layout/index.vue

@@ -38,7 +38,7 @@
                     <div class="hc-main-body">
                         <router-view v-if="reloadRouter" v-slot="{ Component }">
                             <transition name="fade-transform">
-                                <keep-alive :max="10">
+                                <keep-alive :max="10" exclude="HcDataV">
                                     <component :is="Component" :msg-count="msgCount" :msg-change="msgChange" />
                                 </keep-alive>
                             </transition>
@@ -167,7 +167,7 @@ const cascaderChange = () => {
 
 //首页
 const logoClick = () => {
-    router.push({ name: store.homeUrl })
+    router.push({ name: 'datav' })
 }
 
 const userProjectClick = () => {

+ 12 - 3
src/layout/modules/Cascader.vue

@@ -46,11 +46,20 @@ watch(() => store.getProjectContract, (val) => {
 
 //渲染完成
 onMounted(() => {
-    initProjectContract()
-    const info = store.getProjectContract || []
-    projectContractData(info)
+    setInitData()
 })
 
+const setInitData = async () => {
+    try {
+        if (!store.projectId) {
+            await initProjectContract()
+        }
+        projectContractData(store.getProjectContract)
+    } catch (error) {
+        console.log(error)
+    }
+}
+
 //处理项目合同段数据
 const projectContractData = (projectContractData) => {
     if (projectContractData.length > 0) {

+ 8 - 1
src/layout/modules/HcTopMenu.vue

@@ -2,7 +2,14 @@
     <el-scrollbar>
         <div class="hc-header-top-menu-bar">
             <template v-for="(item, index) in topMenuData" :key="index">
-                <div class="item" :class="curKey === item?.code ? 'cur' : '' " @click="topMenuClick(item)">{{ item?.name }}</div>
+                <div
+                    v-if="item.isShowButton === 1"
+                    class="item"
+                    :class="curKey === item?.code ? 'cur' : '' "
+                    @click="topMenuClick(item)"
+                >
+                    {{ item?.name }}
+                </div>
             </template>
         </div>
     </el-scrollbar>

+ 12 - 1
src/layout/modules/RouterMenu.vue

@@ -1,6 +1,9 @@
 <template>
     <el-scrollbar>
         <div class="hc-router-tab-box">
+            <div :class="barRoutes.key === 'datav' ? 'cur' : ''" class="hc-router-tab-item" @click="barDatavClick">
+                <span>数据看板</span>
+            </div>
             <template v-for="(item, index) in barMenuData" :key="item.key">
                 <div :class="item.key === barRoutes.key ? 'cur' : ''" class="hc-router-tab-item" @click="barMenuClick(item)" @contextmenu.prevent="barMenuContextMenu($event, item, index)">
                     <span>{{ item.title }}</span>
@@ -53,7 +56,7 @@ watch(() => [
 //设置菜单数据
 const setBarMenuData = () => {
     const { key, path, title, query } = barRoutes.value
-    if (['home', 'home-index'].indexOf(key) === -1) {
+    if (['home', 'home-index', 'datav'].indexOf(key) === -1) {
         const index = barMenuData.value.findIndex(item => item.key === key)
         if (index === -1) {
             barMenuData.value.push({ path, key: key, title, query })
@@ -63,6 +66,14 @@ const setBarMenuData = () => {
     emit('load', barRoutes.value)
 }
 
+//跳转到数据看板
+const barDatavClick = () => {
+    const { key } = barRoutes.value
+    if (key !== 'datav') {
+        router.push({ name: 'datav' })
+    }
+}
+
 //菜单被点击
 const barMenuClick = (item) => {
     const { key } = barRoutes.value

+ 12 - 0
src/router/modules/base.js

@@ -30,6 +30,18 @@ export default [
         meta: { title: 'pdf文件预览' },
         component: () => import('~src/views/home/pdf.vue'),
     },
+    {
+        path: '/pdf',
+        name: 'pdf',
+        meta: { title: 'pdf文件预览' },
+        component: () => import('~src/views/home/pdf.vue'),
+    },
+    {
+        path: '/datav',
+        name: 'datav',
+        meta: { title: '数据看板' },
+        component: () => import('~src/views/home/datav.vue'),
+    },
     {
         path: '/home',
         name: 'home',

+ 75 - 6
src/store/modules/app.js

@@ -1,9 +1,9 @@
 import pinia from '~src/store/init'
 import { useAppStore } from '~src/store'
 import { getButtons } from '~api/menu'
-import { getProjectAndContract } from '~api/user'
+import projectApi from '~api/project'
 import { getStoreValue } from '~src/utils/storage'
-import { ArrToOneObj, getArrValue } from 'js-fast-way'
+import { ArrToOneObj, getArrValue, getObjValue, isNullES } from 'js-fast-way'
 import website from '~src/config/index'
 
 const store = useAppStore(pinia)
@@ -13,17 +13,86 @@ export const initProjectContract = async () => {
     const value = getStoreValue('projectContract')
     const userRoleId = getStoreValue('userRoleId')
     if (userRoleId !== website.role_id) {
-        const { error, data } = await getProjectAndContract()
+        const { error, data } = await getProjectContract()
         if (error) return Promise.reject('error')
-        const datas = getArrValue(data)
-        store.setProjectContract(datas)
         return Promise.resolve(data)
     } else {
         return Promise.resolve(value)
     }
-   
 }
 
+//获取默认项目信息
+export const getProjectContract = async () => {
+    const { error, data } = await projectApi.getProjectAndContract()
+    const projectList = getArrValue(data)
+    if (error || projectList.length <= 0) {
+        window.$message?.error('没有相关项目权限')
+        return { error: true, data: [] }
+    }
+    //获取默认项目合同段数据
+    const defaultProject = await getDefaultProject()
+    let projectInfo = {}, contractInfo = {}
+    if (defaultProject.code === 200) {
+        projectInfo = defaultProject.project
+        contractInfo = defaultProject.contract
+    } else {
+        //过滤空合同段的项目合同段数据
+        const projectArr = projectList.filter(({ contractInfoList }) => {
+            const contractList = getArrValue(contractInfoList)
+            return contractList.length > 0
+        })
+        if (projectArr.length <= 0) {
+            window.$message?.error('没有相关项目权限')
+            return { error: true, data: [] }
+        }
+        //获取第一个项目的第一个合同段数据
+        const contractList = projectArr[0].contractInfoList
+        projectInfo = projectList[0]
+        contractInfo = contractList[0]
+    }
+    //获取按钮权限
+    await initButtons()
+    //设置项目合同段数据
+    store.setProjectInfo(projectInfo)
+    store.setProjectId(projectInfo.id)
+    store.setContractInfo(contractInfo)
+    store.setContractId(contractInfo.id)
+    store.setProjectContract(projectList)
+    return { error: false, data: projectList }
+}
+
+//获取默认项目信息
+const getDefaultProject = async () => {
+    const { error, status, data } = await projectApi.getDefaultProject()
+    if (!error && status === 200 && !isNullES(data)) {
+        const { projectId, contractId } = getObjValue(data)
+        if (!projectId || !contractId) {
+            return { code: 300 }
+        }
+        const projectInfo = await getProjectInfo(projectId)
+        const contractInfo = await getContractInfo(contractId)
+        if (isNullES(projectInfo) || isNullES(contractInfo)) {
+            return { code: 300 }
+        }
+        return { code: 200, project: projectInfo, contract: contractInfo }
+    } else {
+        return { code: 300 }
+    }
+}
+
+//获取项目信息
+const getProjectInfo = async (projectId) => {
+    const { data } = await projectApi.getProjectInfo(projectId)
+    return getObjValue(data)
+}
+
+//获取合同段信息
+const getContractInfo = async (contractId) => {
+    const { data } = await projectApi.getContractInfo(contractId)
+    return getObjValue(data)
+}
+
+
 //按钮初始化
 export const initButtons = async () => {
     const value = getStoreValue('buttons')

+ 189 - 0
src/styles/view/datav.scss

@@ -0,0 +1,189 @@
+.hc-home-datav-box {
+    color: #E7E7E7;
+    background-color: #05080F;
+    overflow: hidden;
+    img {
+        -webkit-user-drag: none;
+        -khtml-user-drag: none;
+        -moz-user-drag: none;
+        -o-user-drag: none;
+        user-drag: none;
+    }
+    .datav-bg {
+        position: absolute;
+        flex-shrink: 0;
+        inset: 0;
+        opacity: 0.4;
+        img {
+            height: 100%;
+            width: 100%;
+            object-fit: cover;
+        }
+    }
+    .el-header {
+        .header-bg img {
+            width: 100%;
+        }
+        .header-img {
+            inset: 0;
+            display: flex;
+            justify-content: center;
+            #datav-header-img-1 {
+                height: 80%;
+                opacity: 0.5;
+            }
+            .datav-header-img-2 {
+                position: absolute;
+                right: 16px;
+                height: 44%;
+                top: 18%;
+                opacity: 0.3;
+                cursor: pointer;
+                &:hover {
+                    opacity: 0.5;
+                }
+            }
+            .project-name-box {
+                display: flex;
+                justify-content: center;
+                align-items: center;
+                padding-top: 1%;
+                height: 100%;
+                color: #41FCF6;
+                font-size: 35px;
+                font-weight: bold;
+                transform-origin: 0 center;
+            }
+            #project-name {
+                left: 50%;
+                position: relative;
+                transform: translate(-50%);
+                transform-origin: 0 center;
+                cursor: pointer;
+                &:hover {
+                    color: #428BE9;
+                }
+            }
+        }
+    }
+    .el-container {
+        padding: 14px;
+        .el-aside {
+            margin-right: 20px;
+            .aside-charts-1, .aside-charts-2 {
+                .title {
+                    color: #72EFF7;
+                    font-size: 18px;
+                    text-align: center;
+                    height: 40px;
+                    line-height: 40px;
+                }
+                .content {
+                    position: relative;
+                    height: calc(100% - 40px);
+                }
+            }
+            .aside-charts-1 {
+                height: calc(40% - 6px);
+                margin-bottom: 12px;
+            }
+            .aside-charts-2 {
+                height: calc(60% - 6px);
+            }
+        }
+        .el-main {
+            .main-border {
+                height: calc(30% - 6px);
+                margin-bottom: 12px;
+                .main-datav-progress {
+                    position: relative;
+                    display: flex;
+                    flex-direction: column;
+                    padding: 16px 0;
+                    width: 100%;
+                    height: 100%;
+                    .item {
+                        position: relative;
+                        flex: 1;
+                        display: flex;
+                        align-items: center;
+                        .name {
+                            position: relative;
+                            margin-left: 10px;
+                            margin-right: 20px;
+                            font-size: 26px;
+                        }
+                        .content {
+                            position: relative;
+                            flex: 1;
+                        }
+                    }
+                }
+            }
+            .main-gui {
+                height: calc(70% - 6px);
+                .datav-card-content {
+                    padding: 18px;
+                    overflow: hidden;
+                }
+                .main-gui-header {
+                    position: relative;
+                    display: flex;
+                    align-items: center;
+                    height: 21px;
+                    margin-bottom: 20px;
+                    .title {
+                        flex: 1;
+                        color: #72EFF7;
+                        font-size: 18px;
+                    }
+                    .extra {
+                        position: relative;
+                        display: flex;
+                        align-items: center;
+                        .tab {
+                            position: relative;
+                            font-weight: bold;
+                            cursor: pointer;
+                            &.cur {
+                                color: #0074f8;
+                                &::after {
+                                    content: '';
+                                    position: absolute;
+                                    bottom: -6px;
+                                    left: 0;
+                                    width: 100%;
+                                    height: 2px;
+                                    background-color: #0074f8;
+                                }
+                            }
+                        }
+                        .tab + .tab {
+                            margin-left: 10px;
+                        }
+                    }
+                }
+                .main-gui-content {
+                    position: relative;
+                    height: calc(100% - 41px);
+                    overflow: hidden;
+                }
+            }
+        }
+    }
+}
+.el-popper.is-dark.hc-datav-project-name-popover {
+    background: #0a1f32;
+    border: 2px solid #12ced1;
+    .el-popover__title {
+        margin-bottom: 0;
+    }
+    .hc-datav-project-gist {
+        position: relative;
+        margin-top: 12px;
+    }
+    .el-popper__arrow::before {
+        background: #0a1f32;
+        border: 2px solid #12ced1;
+    }
+}

+ 2 - 2
src/utils/storage.js

@@ -1,4 +1,4 @@
-import {setStoreData, getStoreData, delStoreData, clearStoreAll} from "js-fast-way"
+import { clearStoreAll, delStoreData, getStoreData, setStoreData } from 'js-fast-way'
 import website from '~src/config/index'
 
 //获取缓存
@@ -16,4 +16,4 @@ export const delStoreValue = (key, session = false) => {
     return delStoreData(website.key + '-' + key, session)
 }
 
-export {clearStoreAll}
+export { clearStoreAll }

+ 1 - 1
src/views/home/auth.vue

@@ -60,7 +60,7 @@ const loginByTokenApi = async (form) => {
             isErrorShow.value = false
             window?.$message?.success('授权登录成功')
             router.push({
-                name: store.homeUrl ?? 'home',
+                name: 'datav',
             })
         }, 1500)
     } else {

+ 85 - 0
src/views/home/components/border.vue

@@ -0,0 +1,85 @@
+<template>
+    <div class="hc-datav-border-box">
+        <svg ref="mainRef" class="hc-datav-svg">
+            <polygon fill="transparent" :points="`10, 22 ${mainWidth - 30}, 22 ${mainWidth - 30}, 64 ${mainHeight - 25}, ${mainHeight - 25} 10, 126`" />
+            <polyline :points="`8, 5 ${mainWidth - 5}, 5 ${mainWidth - 5}, ${mainHeight - 100} ${mainWidth - 100}, ${mainHeight - 4} 8, ${mainHeight - 4} 8, 5`" class="hc-datav-line-1" stroke="#83bff6" />
+            <polyline :points="`3, 5 ${mainWidth - 18}, 5 ${mainWidth - 18}, ${mainHeight - 74} ${mainWidth - 84}, ${mainHeight - 4} 3, ${mainHeight - 4} 3, 5`" class="hc-datav-line-2" stroke="#00CED1" />
+            <polyline :points="`50, 13 ${mainWidth - 30}, 13`" class="hc-datav-line-3" stroke="#00CED1" />
+            <polyline :points="`15, 20 ${mainWidth - 30}, 20`" class="hc-datav-line-4" stroke="#00CED1" />
+            <polyline :points="`15, ${mainHeight - 20} ${mainWidth - 108}, ${mainHeight - 20}`" class="hc-datav-line-5" stroke="#00CED1" />
+            <polyline :points="`15, ${mainHeight - 13} ${mainWidth - 108}, ${mainHeight - 13}`" class="hc-datav-line-6" stroke="#00CED1" />
+        </svg>
+        <div class="border-box-content" :style="{ width: `${mainWidth - 108}px` }">
+            <slot />
+        </div>
+    </div>
+</template>
+
+<script setup>
+import { nextTick, onMounted, ref } from 'vue'
+
+//渲染完成
+onMounted(() => {
+    windowResize()
+    nextTick(() => {
+        onWindowResize()
+        setTimeout(() => {
+            onWindowResize()
+        }, 400)
+    })
+})
+
+//监听浏览器窗口变化
+const windowResize = () => {
+    window.addEventListener('resize', resizeEvent)
+}
+const resizeEvent = () => {
+    window.requestAnimationFrame(() => {
+        onWindowResize()
+    })
+}
+
+//设置尺寸
+const mainRef = ref(null)
+const mainWidth = ref(0)
+const mainHeight = ref(0)
+const onWindowResize = () => {
+    const el = mainRef.value
+    mainWidth.value = el.clientWidth
+    mainHeight.value = el.clientHeight
+}
+</script>
+
+<style scoped lang="scss">
+.hc-datav-border-box {
+    position: relative;
+    width: 100%;
+    height: 100%;
+    .hc-datav-svg {
+        position: absolute;
+        inset: 0;
+        width: 100%;
+        height: 100%;
+        polyline {
+            fill: none;
+        }
+        .hc-datav-line-1, .hc-datav-line-2 {
+            stroke-width: 1;
+        }
+        .hc-datav-line-3, .hc-datav-line-6 {
+            stroke-width: 5;
+        }
+        .hc-datav-line-4, .hc-datav-line-5 {
+            stroke-width: 2;
+        }
+    }
+    .border-box-content {
+        position: relative;
+        width: 100%;
+        height: 100%;
+        padding-top: 28px;
+        padding-bottom: 28px;
+        padding-left: 15px;
+    }
+}
+</style>

+ 60 - 0
src/views/home/components/card.vue

@@ -0,0 +1,60 @@
+<template>
+    <div class="hc-datav-card-box">
+        <svg class="hc-datav-svg full">
+            <polygon fill="transparent" points="4, 0 1587, 0 1591, 4 1591, 862 1587, 866 4, 866 0, 862 0, 4" />
+        </svg>
+        <svg class="left-top hc-datav-svg">
+            <polygon points="40, 0 5, 0 0, 5 0, 16 3, 19 3, 7 7, 3 35, 3" fill="#00CED1" />
+        </svg>
+        <svg class="right-top hc-datav-svg">
+            <polygon points="40, 0 5, 0 0, 5 0, 16 3, 19 3, 7 7, 3 35, 3" fill="#00CED1" />
+        </svg>
+        <svg class="left-bottom hc-datav-svg">
+            <polygon points="40, 0 5, 0 0, 5 0, 16 3, 19 3, 7 7, 3 35, 3" fill="#00CED1" />
+        </svg>
+        <svg class="right-bottom hc-datav-svg">
+            <polygon points="40, 0 5, 0 0, 5 0, 16 3, 19 3, 7 7, 3 35, 3" fill="#00CED1" />
+        </svg>
+        <div class="datav-card-content">
+            <slot />
+        </div>
+    </div>
+</template>
+
+<style lang="scss" scoped>
+.hc-datav-card-box {
+    position: relative;
+    width: 100%;
+    height: 100%;
+    border-radius: 6px;
+    box-shadow: rgb(131, 191, 246) 0 0 25px 3px inset;
+    .hc-datav-svg {
+        position: absolute;
+        display: block;
+        width: 50px;
+        height: 30px;
+        &.full {
+            width: 100%;
+            height: 100%;
+        }
+        &.right-top {
+            right: 0;
+            transform: rotateY(180deg);
+        }
+        &.left-bottom {
+            bottom: 0;
+            transform: rotateX(180deg);
+        }
+        &.right-bottom {
+            right: 0;
+            bottom: 0;
+            transform: rotateX(180deg) rotateY(180deg);
+        }
+    }
+    .datav-card-content {
+        position: relative;
+        width: 100%;
+        height: 100%;
+    }
+}
+</style>

+ 76 - 0
src/views/home/components/classify.vue

@@ -0,0 +1,76 @@
+<template>
+    <hc-charts ref="classifyCharts" :option="classifyChartsOption" dark />
+</template>
+
+<script setup>
+import { onMounted, ref, watch } from 'vue'
+
+const props = defineProps({
+    datas: {
+        type: Array,
+        default: () => ([]),
+    },
+})
+
+//渲染完成
+onMounted(() => {
+    setClassifyChartsOption(props.datas)
+})
+
+//监听值变化
+watch(() => props.datas, (val) => {
+    setClassifyChartsOption(val)
+})
+
+//处理数据
+const classifyCharts = ref(null)
+const classifyChartsOption = ref({})
+const setClassifyChartsOption = (data) => {
+    const colors = ['#F9D949', '#EE8031', '#54B8BC']
+    classifyChartsOption.value = {
+        backgroundColor: '',
+        color: colors,
+        tooltip: {
+            trigger: 'item',
+        },
+        legend: {
+            top: '5%',
+            left: 'center',
+        },
+        series: [
+            {
+                name: '档案分类占比统计',
+                type: 'pie',
+                radius: '50%',
+                data: data.map((item, index) => {
+                    item.label = {
+                        color: colors[index],
+                        formatter(param) {
+                            const percent = ' (' + param.percent * 2 + '%)'
+                            return `${item.name}${percent}\n${item.value}`
+                        },
+                    }
+                    return item
+                }),
+                label: {
+                    alignTo: 'edge',
+                    minMargin: 25,
+                    edgeDistance: 25,
+                    lineHeight: 25,
+                },
+                labelLine: {
+                    length: 15,
+                    length2: 0,
+                    maxSurfaceAngle: 80,
+                },
+                labelLayout: function (params) {
+                    const isLeft = params.labelRect.x < classifyCharts.value?.getWidth() / 2
+                    const points = params.labelLinePoints
+                    points[2][0] = isLeft ? params.labelRect.x : params.labelRect.x + params.labelRect.width
+                    return { labelLinePoints: points }
+                },
+            },
+        ],
+    }
+}
+</script>

+ 79 - 0
src/views/home/components/gui.vue

@@ -0,0 +1,79 @@
+<template>
+    <div v-loading="isLoading" class="hc-datav-gui-box" element-loading-background="rgba(122, 122, 122, 0.8)">
+        <div class="hc-gui-info-img">
+            <el-carousel :autoplay="false" indicator-position="outside" arrow="always" trigger="click">
+                <el-carousel-item v-for="(item, index) in guiViewData" :key="index">
+                    <guiView :datas="item" :value="guiItemInfo.id" @change="guiItemChange" />
+                </el-carousel-item>
+            </el-carousel>
+        </div>
+        <div class="hc-gui-info-data">
+            <bookView :info="guiItemInfo" />
+        </div>
+    </div>
+</template>
+
+<script setup>
+import { onMounted, ref, watch } from 'vue'
+import guiView from './gui/guigui.vue'
+import bookView from './gui/book.vue'
+import queryApi from '~api/using/query'
+import { getArrValue } from 'js-fast-way'
+
+const props = defineProps({
+    type: {
+        type: [String, Number],
+        default: '',
+    },
+    projectId: {
+        type: [String, Number],
+        default: '',
+    },
+})
+
+//监听值变化
+const types = ref(props.type)
+const projectId = ref(props.projectId)
+watch(() => [props.type, props.projectId], ([type, pid]) => {
+    types.value = type
+    projectId.value = pid
+    getGuiData()
+})
+
+//渲染完成
+onMounted(() => {
+    getGuiData()
+})
+
+//获取柜子数量
+const isLoading = ref(true)
+const guiViewData = ref([])
+const getGuiData = async () => {
+    isLoading.value = true
+    guiViewData.value = []
+    guiItemInfo.value = {}
+    const { data } = await queryApi.getarchiveQueryPage2({
+        type: types.value,
+        projectId: projectId.value,
+    }, false)
+    //处理数据
+    const res = getArrValue(data)
+    guiViewData.value = res
+    if (res.length > 0) {
+        guiItemChange(res[0][0].children[0])
+    } else {
+        guiItemInfo.value = {}
+    }
+    isLoading.value = false
+}
+
+//柜子的案卷被点击
+const guiItemInfo = ref({})
+const guiItemChange = (item) => {
+    guiItemInfo.value = item
+}
+</script>
+
+<style lang="scss">
+@import './gui/gui';
+</style>

+ 75 - 0
src/views/home/components/gui/book.vue

@@ -0,0 +1,75 @@
+<template>
+    <div class="hc-gui-data-container">
+        <img :src="imageViewGui2" alt="">
+        <div class="hc-gui-info-item-box">
+            <div class="hc-gui-info-item">
+                <div class="info-item" style="top: 10px">
+                    <div class="title">
+                        <span>档</span>
+                        <span>号:</span>
+                    </div>
+                    <div class="text">{{ guiDataInfo.fileNumber }}</div>
+                </div>
+                <div class="info-item-name">{{ guiDataInfo.name }}</div>
+                <div class="hc-info-item-bottom">
+                    <div class="info-item">
+                        <div class="title">立卷单位:</div>
+                        <div class="text">{{ guiDataInfo.unit }}</div>
+                    </div>
+                    <div class="info-item">
+                        <div class="title">起止日期:</div>
+                        <div v-if="guiDataInfo?.endDate !== null || guiDataInfo?.startDate !== null" class="text">
+                            <div>{{ setTimeString (splitDate (guiDataInfo?.startDate)) }}~{{ setTimeString(splitDate(guiDataInfo?.endDate)) }}</div>
+                        </div>
+                        <div v-else class="text" />
+                    </div>
+                    <div class="info-item">
+                        <div class="title">保管期限:</div>
+                        <div class="text">{{ guiDataInfo.storageTimeValue }}</div>
+                    </div>
+                    <div class="info-item">
+                        <div class="title">
+                            <span>密</span>
+                            <span>级:</span>
+                        </div>
+                        <div class="text">{{ guiDataInfo.secretLevelValue }}</div>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
+</template>
+
+<script setup>
+import { onMounted, ref, watch } from 'vue'
+import imageViewGui2 from '~src/assets/view/gui2.png'
+import { setTimeString } from '~src/utils/tools'
+import { getObjValue } from 'js-fast-way'
+
+const props = defineProps({
+    info: {
+        type: Object,
+        default: () => ({}),
+    },
+})
+
+//渲染完成
+onMounted(() => {
+    guiDataInfo.value = getObjValue(props.info)
+})
+
+//监听值变化
+const guiDataInfo = ref([])
+watch(() => props.info, (data) => {
+    guiDataInfo.value = getObjValue(data)
+}, { deep: true })
+
+//截取日期
+const splitDate = (val)=>{
+    if (val) {
+        return val?.substring(0, 10)
+    } else {
+        return ''
+    }
+}
+</script>

+ 170 - 0
src/views/home/components/gui/gui.scss

@@ -0,0 +1,170 @@
+.hc-datav-gui-box {
+    position: relative;
+    height: 100%;
+    width: 100%;
+    display: flex;
+    overflow-y: hidden;
+    overflow-x: auto;
+    .hc-gui-info-img {
+        position: relative;
+        height: 100%;
+        display: flex;
+        align-items: center;
+        flex: 1.5;
+        .el-carousel {
+            height: 100%;
+            width: 100%;
+            padding: 0 24px 15px;
+            --el-carousel-indicator-out-color:#12ced1;
+            .el-carousel__container {
+                position: relative;
+                height: 100%;
+                .el-carousel__arrow {
+                    font-size: 24px;
+                    color: #12ced1;
+                    background-color: transparent;
+                    &:hover {
+                        color: #64a6fc;
+                    }
+                }
+                .el-carousel__arrow--left {
+                    left: -20px;
+                }
+                .el-carousel__arrow--right {
+                    right: -20px;
+                }
+            }
+            .el-carousel__item {
+                display: inline-flex;
+                justify-content: center;
+            }
+            .el-carousel__indicator--horizontal {
+                padding: 0 4px;
+            }
+        }
+        .gui-info-img {
+            position: relative;
+            height: 100%;
+            img {
+                position: relative;
+                height: 100%;
+            }
+            .gui-file-item-box {
+                position: absolute;
+                top: 0;
+                bottom: 0;
+                left: 0;
+                right: 0;
+                display: flex;
+                padding: 18px;
+                flex-direction: row;
+                flex-wrap: wrap;
+                align-items: center;
+                align-content: flex-start;
+                justify-content: flex-start;
+                .gui-file-item {
+                    position: relative;
+                    display: flex;
+                    width: 25%;
+                    height: 19.6%;
+                    padding: 1%;
+                    .item {
+                        position: relative;
+                        background: #927a6a;
+                        height: 100%;
+                        width: 12%;
+                        padding: 6px 3px;
+                        font-size: 13px;
+                        border: 1px solid #5d4848;
+                        border-radius: 3px;
+                        text-orientation: upright;
+                        text-overflow: ellipsis;
+                        white-space: nowrap;
+                        overflow: hidden;
+                        cursor: pointer;
+                        writing-mode: vertical-rl;
+                        display: flex;
+                        flex-direction: column;
+                        justify-content: center;
+                        transition: transform .2s;
+                        &.query {
+                            background: #ffddc6;
+                            color: #101010;
+                        }
+                        &.cur, &:hover {
+                            z-index: 22;
+                            color: white;
+                            background: #A16222;
+                            border: 1px solid #bbbbbb;
+                            transform: scale(1.2);
+                        }
+                    }
+                }
+            }
+        }
+    }
+    .hc-gui-info-data {
+        position: relative;
+        height: 100%;
+        flex: 1;
+        display: flex;
+        justify-content: center;
+        .hc-gui-data-container {
+            position: relative;
+            height: calc(100% - 70px);
+            img {
+                height: 550px;
+            }
+            .hc-gui-info-item-box {
+                position: absolute;
+                top: 58px;
+                left: 38px;
+                right: 28px;
+                height: 400px;
+                overflow: hidden;
+                .hc-gui-info-item {
+                    position: relative;
+                    height: 100%;
+                    width: 100%;
+                    .info-item {
+                        position: relative;
+                        display: flex;
+                        color: #101010;
+                        .title {
+                            margin-right: 14px;
+                            font-weight: bold;
+                            span + span {
+                                margin-left: 29px;
+                            }
+                        }
+                        .text {
+                            flex: 1;
+                        }
+                    }
+                    .info-item-name {
+                        color: #101010;
+                        margin-top: 30px;
+                        line-height: 1.8;
+                    }
+                    .hc-info-item-bottom {
+                        position: absolute;
+                        bottom: 4px;
+                        right: -2px;
+                        left: 4px;
+                    }
+                    .info-item + .info-item {
+                        margin-top: 14px;
+                    }
+                }
+            }
+        }
+        .btn-box {
+            position: relative;
+            height: 50px;
+            display: flex;
+            padding-right: 10px;
+            justify-content: flex-end;
+            align-items: flex-end;
+        }
+    }
+}

+ 58 - 0
src/views/home/components/gui/guigui.vue

@@ -0,0 +1,58 @@
+<template>
+    <div class="gui-info-img">
+        <img :src="imageViewGui1" alt="">
+        <div class="gui-file-item-box">
+            <div v-for="(item, index) in guiViewData" :key="index" class="gui-file-item">
+                <template v-for="(items, indexs) in item.children" :key="indexs">
+                    <div class="item" :class="[curKey === items.id ? 'cur' : '']" @click="guiViewClick(items)">{{ items.sideNumber }}</div>
+                </template>
+            </div>
+        </div>
+    </div>
+</template>
+
+<script setup>
+import { onMounted, ref, watch } from 'vue'
+import imageViewGui1 from '~src/assets/view/gui1.png'
+import { getArrValue } from 'js-fast-way'
+
+const props = defineProps({
+    datas: {
+        type: Array,
+        default: () => ([]),
+    },
+    value: {
+        type: [String, Number],
+        default: '',
+    },
+})
+
+//事件
+const emit = defineEmits(['change'])
+
+//渲染完成
+onMounted(() => {
+    curKey.value = props.value
+    guiViewData.value = getArrValue(props.datas)
+})
+
+//监听值变化
+const guiViewData = ref([])
+
+watch(() => props.datas, (data) => {
+    guiViewData.value = getArrValue(data)
+}, { deep: true })
+
+//柜子当前选中项
+const curKey = ref(props.value)
+watch(() => props.value, (val) => {
+    curKey.value = val
+}, { deep: true })
+
+//柜子被点击
+const guiViewClick = (item) => {
+    if (curKey.value === item.id) return
+    curKey.value = item.id
+    emit('change', item)
+}
+</script>

+ 105 - 0
src/views/home/components/paper.vue

@@ -0,0 +1,105 @@
+<template>
+    <hc-charts :option="chartsOption" dark />
+</template>
+
+<script setup>
+import { onMounted, ref, watch } from 'vue'
+import { getArrValue } from 'js-fast-way'
+import * as echarts from 'echarts'
+
+const props = defineProps({
+    datas: {
+        type: Array,
+        default: () => ([]),
+    },
+})
+
+//渲染完成
+onMounted(() => {
+    setDatas(props.datas)
+})
+
+//监听值变化
+watch(() => props.datas, (val) => {
+    setDatas(val)
+})
+
+//设置数据
+const setDatas = (data) => {
+    const arr = getArrValue(data)
+    let names = [], values1 = [], values2 = []
+    for (let i = 0; i < arr.length; i++) {
+        names.push(arr[i].title)
+        values1.push(arr[i].key1)
+        values2.push(arr[i].key2)
+    }
+    setOptions(names, values1, values2)
+}
+
+
+//处理数据
+const chartsOption = ref({})
+const setOptions = (names, data1, data2) => {
+    //创建图表
+    chartsOption.value = {
+        backgroundColor: '',
+        grid: {
+            top: '60px',
+            left: '20px',
+            right: '20px',
+            bottom: '30px',
+            containLabel: true,
+        },
+        legend: {
+            data: ['原生', '数字化'],
+        },
+        xAxis: {
+            type: 'category',
+            data: names,
+        },
+        yAxis: {
+            type: 'value',
+        },
+        tooltip: {
+            trigger: 'axis',
+            axisPointer: {
+                type: 'shadow',
+            },
+        },
+        series: [
+            {
+                name: '原生',
+                data: data1,
+                type: 'bar',
+                itemStyle: {
+                    borderRadius: [50, 50, 0, 0],
+                    color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+                        { offset: 0, color: '#75E3CC' },
+                        { offset: 1, color: '#54B9F8' },
+                    ]),
+                },
+                label: {
+                    show: true,
+                    position: 'top',
+                },
+            },
+            {
+                name: '数字化',
+                data: data2,
+                type: 'bar',
+                itemStyle: {
+                    borderRadius: [50, 50, 0, 0],
+                    color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+                        { offset: 0, color: '#8EBCF1' },
+                        { offset: 1, color: '#428BE9' },
+                    ]),
+                },
+                label: {
+                    show: true,
+                    position: 'top',
+                },
+            },
+        ],
+    }
+}
+</script>

+ 100 - 0
src/views/home/components/progress.vue

@@ -0,0 +1,100 @@
+<template>
+    <div class="hc-datav-progress">
+        <div class="body">
+            <div class="main">
+                <div
+                    v-for="item in cellData"
+                    :key="item.name" class="cell"
+                    :data-name="item.name"
+                    :style="{ background: cellValue >= item.name ? item.color : 'transparent' }"
+                />
+            </div>
+            <div class="text">{{ cellValue }}%</div>
+        </div>
+    </div>
+</template>
+
+<script setup>
+import { ref, watch } from 'vue'
+
+const props = defineProps({
+    value: {
+        type: [String, Number],
+        default: '0',
+    },
+})
+
+//监听值变化
+const cellValue = ref(props.value)
+watch(() => props.value, (val) => {
+    cellValue.value = val
+})
+
+//计算格子的渐变色
+const cellData = ref([])
+const getCellColors = () => {
+    let colors = [], cells = []
+    const startColor = [117, 227, 204], endColor = [84, 185, 248]
+    for (let i = 0; i < endColor.length; i++) {
+        colors.push(((endColor[i] - startColor[i]) / 100).toFixed(3))
+    }
+    //预设100个格子
+    for (let i = 0; i < 100; i++) {
+        cells.push({
+            name: i + 1,
+            color: `rgb(
+                ${startColor[0] + i * colors[0]},
+                ${startColor[1] + i * colors[1]},
+                ${startColor[2] + i * colors[2]}
+            )`,
+        })
+    }
+    cellData.value = cells
+}
+
+getCellColors()
+</script>
+
+<style scoped lang="scss">
+.hc-datav-progress {
+    position: relative;
+    width: 100%;
+    height: 50px;
+    padding: 4px;
+    border-style: solid;
+    border-width: 5px;
+    border-image: linear-gradient(90deg, #75E3CC, #54B9F8) 1 1;
+    clip-path: inset(0 round 5px);
+    .body {
+        position: relative;
+        width: 100%;
+        height: 100%;
+        .main {
+            position: relative;
+            width: 100%;
+            height: 100%;
+            display: flex;
+            align-items: center;
+            overflow: hidden;
+            .cell {
+                flex: 1;
+                position: relative;
+                height: 100%;
+                z-index: -1;
+                border: 1px solid transparent;
+                clip-path: inset(0.5px round 0);
+            }
+        }
+        .text {
+            position: absolute;
+            inset: 0;
+            display: flex;
+            justify-content: center;
+            align-items: center;
+            font-weight: bold;
+            font-size: 22px;
+            text-shadow: 2px 2px 6px #616161;
+        }
+    }
+}
+</style>

+ 256 - 0
src/views/home/datav.vue

@@ -0,0 +1,256 @@
+<template>
+    <el-container v-loading="isLoading" class="hc-home-datav-box relative h-full w-full">
+        <div class="datav-bg">
+            <img :src="bgImg" alt="背景图片">
+        </div>
+        <el-header id="datav-header" class="relative p-0">
+            <div class="header-bg relative h-full w-full">
+                <img id="datav-header-bg" :src="headerBg" alt="头部图" @load="headerBgLoad">
+            </div>
+            <div class="header-img absolute">
+                <img id="datav-header-img-1" :src="headerImg1" alt="头部装饰1">
+                <img class="datav-header-img-2" :src="headerImg2" alt="头部装饰2" @click="fullScreenClick">
+                <div class="project-name-box absolute">
+                    <el-popover
+                        effect="dark"
+                        placement="bottom"
+                        :title="project?.projectName"
+                        :width="460"
+                        trigger="hover"
+                        popper-class="hc-datav-project-name-popover"
+                    >
+                        <template #reference>
+                            <span id="project-name" @click="projectNameClick">{{ project.projectAlias?.substring(0, 10) }}</span>
+                        </template>
+                        <div v-if="project?.projectGist" class="hc-datav-project-gist">{{ project?.projectGist }}</div>
+                    </el-popover>
+                </div>
+            </div>
+        </el-header>
+        <el-container class="relative">
+            <el-aside width="25%" class="relative">
+                <hc-body padding="0">
+                    <datavCard class="aside-charts-1">
+                        <div class="title">档案分类占比统计</div>
+                        <div class="content">
+                            <datavClassify :datas="classifyCharts" />
+                        </div>
+                    </datavCard>
+                    <datavCard class="aside-charts-2">
+                        <div class="title">原生、数字化文件数量(份)</div>
+                        <div class="content">
+                            <datavPaper :datas="paperCharts" />
+                        </div>
+                    </datavCard>
+                </hc-body>
+            </el-aside>
+            <el-main class="relative">
+                <hc-body padding="0">
+                    <datavBorder class="main-border">
+                        <div class="main-datav-progress">
+                            <div class="item">
+                                <div class="name">内业</div>
+                                <div class="content">
+                                    <datavProgress :value="projectStat?.inner?.ratio ?? 0" />
+                                </div>
+                            </div>
+                            <div class="item">
+                                <div class="name">外业</div>
+                                <div class="content">
+                                    <datavProgress :value="projectStat?.outer?.ratio ?? 0" />
+                                </div>
+                            </div>
+                        </div>
+                    </datavBorder>
+                    <datavCard class="main-gui">
+                        <div class="main-gui-header">
+                            <div class="title">虚拟档案柜</div>
+                            <div class="extra">
+                                <template v-for="item in tabs" :key="item.key">
+                                    <div class="tab" :class="tabKey === item.key ? 'cur' : ''" @click="tabsClick(item)">
+                                        {{ item.label }}
+                                    </div>
+                                </template>
+                            </div>
+                        </div>
+                        <div class="main-gui-content">
+                            <datavGui :type="tabKey" :project-id="projectId" />
+                        </div>
+                    </datavCard>
+                </hc-body>
+            </el-main>
+        </el-container>
+    </el-container>
+</template>
+
+<script setup>
+import { onMounted, onUnmounted, ref } from 'vue'
+import { useRouter } from 'vue-router'
+import { useAppStore } from '~src/store'
+import { initProjectContract } from '~sto/app'
+import headerBg from '~ass/datav/header1.png'
+import headerImg1 from '~ass/datav/header2.svg'
+import headerImg2 from '~ass/datav/header3.svg'
+import bgImg from '~ass/datav/bg15.jpg'
+import datavCard from './components/card.vue'
+import datavBorder from './components/border.vue'
+import datavProgress from './components/progress.vue'
+import datavClassify from './components/classify.vue'
+import datavPaper from './components/paper.vue'
+import datavGui from './components/gui.vue'
+import { fullScreen, getArrValue, getObjValue } from 'js-fast-way'
+import statsApi from '~api/using/stats'
+
+//初始化组合式
+const router = useRouter()
+const store = useAppStore()
+
+defineOptions({
+    name: 'HcDataV',
+})
+
+//渲染完成
+onMounted(() => {
+    windowResize()
+    setInitData()
+})
+
+//加载
+const isLoading = ref(false)
+
+//获取项目信息
+const project = ref({})
+const projectId = ref(store.projectId)
+const setInitData = async () => {
+    isLoading.value = true
+    try {
+        if (!store.projectId) {
+            await initProjectContract()
+        }
+        project.value = getObjValue(store.getProjectInfo)
+        const project_id = store.projectId
+        projectId.value = project_id
+        //获取接口数据
+        getClassifyChartsData(project_id)
+        getPaperChartsData(project_id)
+        getProjectStat(project_id)
+    } catch (error) {
+        console.log(error)
+    }
+    isLoading.value = false
+}
+
+//监听浏览器窗口变化
+const windowResize = () => {
+    window.addEventListener('resize', resizeEvent)
+}
+const resizeEvent = () => {
+    window.requestAnimationFrame(() => {
+        onWindowResize()
+    })
+}
+
+//设置尺寸
+const onWindowResize = () => {
+    //设置头部背景
+    const height = document.getElementById('datav-header-bg').offsetHeight ?? 0
+    document.getElementById('datav-header').style.height = `${height}px`
+
+    //设置项目名称大小
+    const imgHeight = document.getElementById('datav-header').offsetHeight
+    const nameHeight = document.getElementById('project-name').clientHeight
+    const scale = (imgHeight / (nameHeight + 30)).toFixed(5)
+    if (imgHeight && scale < 1) {
+        document.getElementById('project-name').style.transform = `scale(${scale}) translateX(-50%)`
+    }
+}
+
+//图片加载完成
+const headerBgLoad = () => {
+    onWindowResize()
+}
+
+//档案分类占比统计
+const classifyCharts = ref([
+    { value: 0, name: '施工' },
+    { value: 0, name: '监理' },
+    { value: 0, name: '业主' },
+])
+const getClassifyChartsData = async (projectId) => {
+    if (!projectId) {
+        window?.$message.warning('项目异常')
+        return
+    }
+    const { data } = await statsApi.getallArchiveByContractType({ projectId })
+    const res = getObjValue(data)
+    classifyCharts.value = [
+        { value: res.key1 ?? 0, name: '施工' },
+        { value: res.key2 ?? 0, name: '监理' },
+        { value: res.key3 ?? 0, name: '业主' },
+    ]
+}
+
+//档案组卷统计
+const paperCharts = ref([
+    { title: '施工', key1: 0, key2: 0 },
+    { title: '监理', key1: 0, key2: 0 },
+    { title: '业主', key1: 0, key2: 0 },
+])
+//获取原生文件数量
+const getPaperChartsData = async (projectId) => {
+    if (!projectId) {
+        window?.$message.warning('项目异常')
+        return
+    }
+    const { data } = await statsApi.getallnativeChartData({ projectId })
+    paperCharts.value = getArrValue(data)
+}
+
+//获取内外业统计
+const projectStat = ref({
+    inner: { ratio:0, total:0 },
+    outer: { ratio:0, total:0 },
+})
+const getProjectStat = async (projectId) => {
+    if (!projectId) {
+        window?.$message.warning('项目异常')
+        return
+    }
+    const { data } = await statsApi.getProjectStat({ projectId })
+    projectStat.value = getObjValue(data)
+}
+
+
+//档案柜类型
+const tabKey = ref('1')
+const tabs = ref([
+    { label: '业主', key: '1' },
+    { label: '监理', key: '3' },
+    { label: '施工', key: '2' },
+])
+const tabsClick = (item) => {
+    if (tabKey.value === item.key) return
+    tabKey.value = item.key
+}
+
+//项目名称被点击
+const projectNameClick = () => {
+    router.push({ name: store.homeUrl })
+}
+
+//是否全屏
+const isFullScreen = ref(false)
+const fullScreenClick = () => {
+    isFullScreen.value = !isFullScreen.value
+    fullScreen(isFullScreen.value)
+}
+
+//被卸载
+onUnmounted(() => {
+    window.removeEventListener('resize', resizeEvent)
+})
+</script>
+
+<style lang="scss">
+@import "~src/styles/view/datav.scss";
+</style>

+ 1 - 1
src/views/login/index.vue

@@ -159,7 +159,7 @@ const formValidateClick = async () => {
     window?.$message?.success('登录成功')
     setTimeout(() => {
         loading.value = false
-        router.push({ name: userStore.homeUrl })
+        router.push({ name: 'datav' })
     }, 1500)
 }
 

+ 1 - 1
src/views/user/auth.vue

@@ -81,7 +81,7 @@ const useAppLoginApi = async (form) => {
         isErrorShow.value = false
         window?.$message?.success('授权登录成功')
         router.push({
-            name: store.homeUrl,
+            name: 'datav',
         })
     }, 1500)
 }

+ 3 - 4
src/views/using/query.vue

@@ -278,10 +278,10 @@
         <HcDrawer :show="isCarrySpotChecksDrawer" uis="hc-carry-spot-checks-target" to-id="carry-spot-checks-layout-target" @close="onCarrySpotChecksDrawerClose">
             <div class="hc-carry-spot-checks-pdf">
                 <!-- <HcPdf src="https://bladex-test-info.oss-cn-chengdu.aliyuncs.com//upload/20221212/ce9799c7d18efc03efefd6f242439f2e.pdf"/> -->
-                <tepmplate v-loading="pdfLoading"> 
+                <tepmplate v-loading="pdfLoading">
                     <HcPdf :src="pdfUrl" />
                 </tepmplate>
-             
+
                 <el-tooltip content="展开/收起 右侧目录" placement="top" :disabled="!isBubble">
                     <div class="hc-csc-pdf-btn" @click="onCarryDataShow">
                         <HcIcon v-show="isCarryDataShow" name="arrow-right-s" />
@@ -1047,7 +1047,6 @@ const tableFileData = ref([
 const getArchiveFileListData = async ()=>{
     const { error, code, msg, data } = await archiveQueryApi.getArchiveFileList({
         id: fileInfo.value.id, //案卷id
-
     })
     //处理返回数据
     if (!error && code === 200) {
@@ -1374,7 +1373,7 @@ const tabGuiChange = async ({ key }) => {
     const { error, code, msg } = await archiveQueryApi.getArchivesAuthByUser({
         archiveType: key, //1业主2施工3监理
         projectId: projectId.value,
-        contractId:contractId.value,
+        contractId: contractId.value,
     }, false)
     //处理返回数据
     if (!error && code === 200) {

+ 16 - 16
yarn.lock

@@ -543,10 +543,10 @@ autoprefixer@^10.4.17:
     picocolors "^1.0.0"
     postcss-value-parser "^4.2.0"
 
-axios@^1.6.3:
-  version "1.6.5"
-  resolved "http://39.108.216.210:9000/axios/-/axios-1.6.5.tgz#2c090da14aeeab3770ad30c3a1461bc970fb0cd8"
-  integrity sha512-Ii012v05KEVuUoFWmMW/UQv9aRIc3ZwkWDcM+h5Il8izZCtRVpDUfwpoFf7eOtajT3QiGR4yDUx7lPqHJULgbg==
+axios@^1.6.7:
+  version "1.6.7"
+  resolved "http://39.108.216.210:9000/axios/-/axios-1.6.7.tgz#7b48c2e27c96f9c68a2f8f31e2ab19f59b06b0a7"
+  integrity sha512-/hDJGff6/c7u0hDkvkGxR/oy6CbCs8ziCsC7SqmhjfozqiJGc8Z11wrv9z9lYfY4K8l+H9TpjcMDX0xOZmx+RA==
   dependencies:
     follow-redirects "^1.15.4"
     form-data "^4.0.0"
@@ -944,10 +944,10 @@ electron-to-chromium@^1.4.601:
   resolved "http://39.108.216.210:9000/electron-to-chromium/-/electron-to-chromium-1.4.626.tgz#c20e1706354a31721b65e81496800534dd04b222"
   integrity sha512-f7/be56VjRRQk+Ric6PmIrEtPcIqsn3tElyAu9Sh6egha2VLJ82qwkcOdcnT06W+Pb6RUulV1ckzrGbKzVcTHg==
 
-element-plus@^2.5.1:
-  version "2.5.1"
-  resolved "http://39.108.216.210:9000/element-plus/-/element-plus-2.5.1.tgz#ab54c77321728f82740c1adbce288a129147c64e"
-  integrity sha512-ylX9h2U125/nesPlLWgfPkI1rID9EiGROlgf0QkzBUjx+/d4w/YqS+IqZZZC5yvQPhKYu9aMDqEBzOurwn4Cnw==
+element-plus@^2.5.3:
+  version "2.5.3"
+  resolved "http://39.108.216.210:9000/element-plus/-/element-plus-2.5.3.tgz#d4f8988ea199ad28ef15a6b0b8f94b7639d262fb"
+  integrity sha512-wmtstxaMkD6UinIgD+45CjrhbRh4u0vt+/GgxfPeMLt5pDpIVwZFjkUaVcWqqxcxd5a80HP3XlDF74fW7wim9A==
   dependencies:
     "@ctrl/tinycolor" "^3.4.1"
     "@element-plus/icons-vue" "^2.3.1"
@@ -1438,20 +1438,20 @@ jiti@^1.19.1:
   resolved "http://39.108.216.210:9000/jiti/-/jiti-1.21.0.tgz#7c97f8fe045724e136a397f7340475244156105d"
   integrity sha512-gFqAIbuKyyso/3G2qhiO2OM6shY6EPP/R0+mkDbyspxKazh8BXDC5FiFsUjlczgdNz/vfra0da2y+aHrusLG/Q==
 
-js-base64@^3.7.5:
-  version "3.7.5"
-  resolved "http://39.108.216.210:9000/js-base64/-/js-base64-3.7.5.tgz#21e24cf6b886f76d6f5f165bfcd69cc55b9e3fca"
-  integrity sha512-3MEt5DTINKqfScXKfJFrRbxkrnk2AxPWGBL/ycjz4dK8iqiSJ06UxD8jh8xuh6p10TX4t2+7FsBYVxxQbMg+qA==
+js-base64@^3.7.6:
+  version "3.7.6"
+  resolved "http://39.108.216.210:9000/js-base64/-/js-base64-3.7.6.tgz#6ccb5d761b48381fd819f9ce04998866dbcbbc99"
+  integrity sha512-NPrWuHFxFUknr1KqJRDgUQPexQF0uIJWjeT+2KjEePhitQxQEx5EJBG1lVn5/hc8aLycTpXrDOgPQ6Zq+EDiTA==
 
 js-cookie@^3.0.5:
   version "3.0.5"
   resolved "http://39.108.216.210:9000/js-cookie/-/js-cookie-3.0.5.tgz#0b7e2fd0c01552c58ba86e0841f94dc2557dcdbc"
   integrity sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==
 
-js-fast-way@^0.3.8:
-  version "0.3.8"
-  resolved "http://39.108.216.210:9000/js-fast-way/-/js-fast-way-0.3.8.tgz#e8926058da1c5b2f6642c1dcee638d8f63e672f2"
-  integrity sha512-cB34JdkEPhrZAfwbiHXuqFypllgfqv605lfU7SCqwuudQ5zJcHghXvzj8i9nwdqTHIMZ+GI6DF4WnAWo1yNYMw==
+js-fast-way@^0.4.3:
+  version "0.4.3"
+  resolved "http://39.108.216.210:9000/js-fast-way/-/js-fast-way-0.4.3.tgz#1d47805741349e7379e3ddb64a6125543c7498e9"
+  integrity sha512-oOPwKgB23/wHDIwN/WBR8n5CdrBS5rnnlI3rcGqGKDpfD+ivdfEhHp7Fuk32T+OfwJiz5Vxl1h1UwcvasSba0A==
 
 js-md5@^0.8.3:
   version "0.8.3"

Some files were not shown because too many files changed in this diff