ソースを参照

Merge branch 'ETA_2.0' of eta_front/eta_mobile_front into master

leichen 1 ヶ月 前
コミット
5cac416511

+ 1 - 0
package.json

@@ -40,6 +40,7 @@
     "vconsole": "^3.15.0",
     "vue": "^3.2.47",
     "vue-qr": "^4.0.9",
+    "vue-i18n": "^10.0.1",
     "vue-router": "^4.1.6",
     "vue3-clipboard": "^1.0.0",
     "vue3-tree-org": "^4.2.2",

+ 8 - 2
src/api/index.js

@@ -18,6 +18,10 @@ const LOADINGWHITELIST=[
   '/datamanage/edb_info/refresh',
   '/datamanage/edb_info/refresh/all'
 ]
+//l 下载请求列表
+const DownloadRequestslist = [
+  '/resource/file/download',
+]
 // 请求数
 let LOADINGCOUNT = 0;
 let LOADING;
@@ -45,6 +49,7 @@ _axios.interceptors.request.use(
     }
     
     config.headers.Authorization=localStorage.getItem('token')||''
+    if(DownloadRequestslist.includes(config.url)) config.responseType = 'blob'
     return config;
   },
   function (error) {
@@ -58,12 +63,13 @@ _axios.interceptors.response.use(
   function (response) {
     // Do something with response data
     let data
-    if(import.meta.env.MODE==='production'){
+    if(import.meta.env.MODE==='production' && !DownloadRequestslist.includes(config.url)){  // 生产环境解密 --下载数据请求不需要解密
       const headKeyStr=response.headers.dk
       const desKey=CryptoJS.Des3Decrypt(headKeyStr,'JMCqSoUrTAmyNNIRb0TtlrPk') 
       data=JSON.parse(CryptoJS.Des3Decrypt(response.data,desKey));//解密
     }else{
-      data=response.data
+      if(DownloadRequestslist.includes(config.url)) data = response
+      else data=response.data
     }
     //关闭loading
     if(!LOADINGWHITELIST.includes(response.config.url)){

+ 60 - 1
src/api/sheet.js

@@ -39,5 +39,64 @@ export default {
     // 平衡表中图表删除
     balanceSheetChartDel(params){
         return post('/datamanage/excel_info/balance/chart_del',params)
-    }
+    },
+
+    /**
+     * 表格分类列表
+     * @param Source 表格类型
+     * @param IsShowMe 是否只显示我创建的
+    */
+       excelClassifyList(params){
+        return get('/datamanage/excel_classify/list',params)
+    },
+
+    /**
+     * 表格详情
+     * @param ExcelInfoId 表格ID
+    */
+    excelDetail(params){
+        return get('/datamanage/excel_info/detail',params)
+    },
+
+    /**
+     * 删除表格
+     * @param ExcelClassifyId 表格分类ID
+     * @param ExcelInfoId 表格ID
+     * @param Source 表格来源
+    */
+    excelDelete(params){
+        return post('/datamanage/excel_classify/delete',params)
+    },
+    
+    /**
+     *  下载文件公共接口
+    */
+    apiDownloadResource(params){
+        return get('/resource/file/download',params)
+    },
+
+    /**
+     * 获取平衡表图表数据
+     * @param ExcelInfoId 表格ID
+    */
+    getBalanceChartData(params){
+        return get('/datamanage/excel_info/balance/chart_list',params)
+    },
+
+    /**
+     * 平衡表获取版本列表
+     * @param ExcelInfoId 表格ID
+    */
+    balanceTableVersion(params){
+        return get('/datamanage/excel_info/balance/version',params)
+    },
+
+    /**
+     * 平衡表获取子表列表
+     * @param ParentId 表格ID
+    */
+    getBalanceChildTable(params){
+        return get('/datamanage/excel_info/child_table',params)
+    },
+    
 }

BIN
src/assets/imgs/table/chart.png


BIN
src/assets/imgs/table/delete.png


BIN
src/assets/imgs/table/download.png


BIN
src/assets/imgs/table/flushed.png


+ 36 - 0
src/hooks/useAuthBtn.js

@@ -148,6 +148,42 @@ export const myETABtn={
     myChart_classifyOpt_delete:'myChart:classifyOpt:delete',//删除
 }
 
+/*
+ * --------------------------------------------------------------------------ETA表格------------------------------------------------
+*/
+export const etaTablePermission = {
+    /*-----------混合表格页面--------- */
+    etaTable_customize_mix_classifyOpt_edit: 'etaTable:customize:mix:classifyOpt:edit',//混合表格分类操作
+    etaTable_customize_mix_classifyOpt_delete: 'etaTable:customize:mix:classifyOpt:delete',//混合表格分类删除
+    etaTable_customize_mix_refresh:'etaTable:customize:mix:refresh',//刷新
+    etaTable_customize_mix_download:'etaTable:customize:mix:download',//下载
+    etaTable_customize_mix_save:'etaTable:customize:mix:save',//保存
+    etaTable_customize_mix_del:'etaTable:customize:mix:del',//删除
+
+    /*-----------时间序列表格数据表格页面--------- */
+    etaTable_customize_data_classifyOpt_edit: 'etaTable:customize:data:classifyOpt:edit',//数据表格分类操作
+    etaTable_customize_data_classifyOpt_delete: 'etaTable:customize:data:classifyOpt:delete',//数据表格分类删除
+    etaTable_customize_data_refresh:'etaTable:customize:data:refresh',//刷新
+    etaTable_customize_data_download:'etaTable:customize:data:download',//下载
+    etaTable_customize_data_del:'etaTable:customize:data:del',//删除
+    etaTable_customize_data_save:'etaTable:customize:data:save',//保存
+
+    /*-----------excel共享表格页面--------- */
+    etaTable_excel_classifyOpt_edit:'etaTable:excel:classifyOpt:edit',//添加编辑表格
+    etaTable_excel_classifyOpt_delete:'etaTable:excel:classifyOpt:delete',//删除表格
+    etaTable_excel_del:'etaTable:excel:del',
+    etaTable_excel_download:'etaTable:excel:download',
+    etaTable_excel_save:'etaTable:excel:save',//保存
+    etaTable_excel_edit:'etaTable:excel:edit',
+
+    /*-----------excel平衡表页面--------- */
+    etaTable_customize_balance_sheetAdd:'etaTable:customize:balance:sheetAdd',//添加
+    etaTable_customize_balance_del:'etaTable:customize:balance:del',//删除
+    etaTable_customize_balance_download:'etaTable:customize:balance:download',//下载
+    etaTable_customize_balance_refresh:'etaTable:customize:balance:refresh',//刷新
+    etaTable_customize_balance_edit:'etaTable:customize:balance:edit',//编辑
+    etaTable_customize_balance_classifyOpt_delete:'etaTable:customize:balance:classifyOpt:delete',//删除分类
+}
 
 export function useAuthBtn(){
     const isShowBtn = ()=>{}

+ 49 - 0
src/lang/cn.js

@@ -0,0 +1,49 @@
+export default {
+  // tabbar
+  tabbar: {
+    home: '首页',
+    my: '我的',
+  },
+  // 我的
+  my: {
+    company_name: '公司名称',
+    department: '所属部门',
+    role: '角色',
+    log_out: '退出登录',
+  },
+  // 表格tabbar
+  tableTabbar: {
+    shared_table: '共享表格',
+    timeline_table: '时间序列',
+    mixed_table: '混合表格',
+    balance_sheet: '平衡表',
+  },
+  // 表格
+  shared_table: {
+    enter_table_name: '请输入表格名称',
+    tables_total: '共{totalNum}张表格',
+    just_look_mine: '只看我的',
+    just_look_edit: '只看可编辑',
+    no_table: '暂无表格',
+    no_more: '没有更多了',
+    choose_category: '选择分类',
+    author: '作者',
+    recent_save_time: '最近保存时间',
+    add_my_gallery: '加入我的图库',
+    added_my_successfully: '加入我的图库成功',
+    close: '关闭',
+    add_category: '添加分类',
+    confirm: '确定',
+    please_enter_category: '请输入分类名称',
+    cancel: '取消',
+    chart: '关联图表',
+    refresh: '刷新',
+    download: '下载',
+    delete: '删除',
+    note: '提示',
+    once_deleted: '删除后表格将不能再引用,确认删除吗?',
+    deletion_success: '删除成功',
+    refresh_success: '刷新成功',
+    download_success: '下载成功',
+  },
+}

+ 48 - 0
src/lang/en.js

@@ -0,0 +1,48 @@
+export default {
+  tabbar: {
+    home: 'home',
+    my: 'my',
+  },
+  // 我的
+  my: {
+    company_name: 'Company Name',
+    department: 'Department',
+    role: 'Role',
+    log_out: 'Log out',
+  },
+   // 表格tabbar
+   tableTabbar: {
+    shared_table: 'Shared table',
+    timeline_table: 'Timeline table',
+    mixed_table: 'Mixed Table',
+    balance_sheet: 'Balance Sheet',
+  },
+  // 表格
+  shared_table: {
+    enter_table_name: 'Please enter the table name',
+    tables_total: '{totalNum} tables in total',
+    just_look_mine: 'Just look at mine',
+    just_look_edit: 'Just look at edit',
+    no_table: 'No table available',
+    no_more: 'No more',
+    choose_category: 'Choose a category',
+    author: 'Author',
+    recent_save_time: 'Recent save time',
+    add_my_gallery: 'Add to my gallery',
+    added_my_successfully: 'Added to my gallery successfully',
+    close: 'Close',
+    add_category: 'Add Category',
+    confirm: 'Confirm',
+    please_enter_category: 'Please enter the category name',
+    chart: 'Chart',
+    cancel: 'Cancel',
+    refresh: 'Refresh',
+    download: 'Download',
+    delete: 'Delete',
+    note: 'Note',
+    once_deleted: 'Once deleted, this table will no longer be referable. Are you sure you want to delete it?',
+    deletion_success: 'Deletion Successful',
+    refresh_success: 'Refresh Successful',
+    download_success: 'Download Successful',
+  },
+}

+ 15 - 0
src/lang/index.js

@@ -0,0 +1,15 @@
+import { createI18n } from 'vue-i18n'
+// 语言包
+import zhCn from './cn'
+import en from './en'
+
+const i18n = createI18n({
+  globalInjection: true, //全局生效$t
+  legacy: false, // 设置为 false,启用 composition API 模式
+  locale: sessionStorage.getItem('localeLang') || 'zhCn',
+  messages: {
+    zhCn,
+    en,
+  },
+})
+export default i18n

+ 4 - 0
src/main.js

@@ -17,6 +17,9 @@ import svgIcon from "@/components/SvgIcon.vue";
 //引入注册脚本
 import 'virtual:svg-icons-register'
 
+// i18n 国际化
+import i18n from '@/lang/index'
+
 /* import { Buffer } from 'buffer'; */
 import * as buffer from 'buffer'
 if(typeof(window.global)==="undefined"){
@@ -42,4 +45,5 @@ setupStore(app)
 app.component('svg-icon', svgIcon)
 app.use(vue3TreeOrg)
 app.use(router)
+app.use(i18n)
 app.mount('#app')

+ 5 - 2
src/router/index.js

@@ -20,6 +20,7 @@ import {reportRoutes} from './report'
 import {reportEnRoutes} from './reportEn'
 import {chartETARoutes} from './chartETA'
 import {dataEDBRoutes} from './dataEDB'
+import {sheetListRoutes} from './sheetList'
 import { storeToRefs } from "pinia";
 
 const routes = [
@@ -69,10 +70,12 @@ const routes = [
 			...reportRoutes,
 			// 英文研报模块
 			...reportEnRoutes,
-            //ETA图库模块
-            ...chartETARoutes,
+			//ETA图库模块
+			...chartETARoutes,
 			//ETA指标库模块
 			...dataEDBRoutes,
+			//表格模块
+			...sheetListRoutes
 		]
 	},
 	

+ 62 - 0
src/router/sheetList.js

@@ -0,0 +1,62 @@
+// ETA指标库模块
+export const sheetListRoutes=[
+    {
+        path:"/sheetList/index",
+        name:"sheetListIndex",
+        redirect: "/shared/list",
+        component: () => import("@/views/sheetList/index.vue"),
+        children: [
+            {
+                path: "/shared/list",
+                name: "sharedList",
+                component: () => import("@/views/sheetList/sharedList.vue"),
+                meta: { title: "共享表格",noBack:true, noHead:true },
+            },
+            {
+                path: "/timeline/list",
+                name: "timelineList",
+                component: () => import("@/views/sheetList/timelineList.vue"),
+                meta: { title: "时间排序表格",noBack:true, noHead:true },
+            },
+            {
+                path: "/mixed/list",
+                name: "mixedList",
+                component: () => import("@/views/sheetList/mixedList.vue"),
+                meta: { title: "混合表格",noBack:true, noHead:true },
+            },
+            {
+                path: "/balance/list",
+                name: "balanceList",
+                component: () => import("@/views/sheetList/balanceList.vue"),
+                meta: { title: "平衡表",noBack:true, noHead:true},
+            },
+        ],
+        meta: { 
+            title: "ETA表格",
+        },
+    },
+    {
+        path: "/shared/search",
+        name: "sharedSearch",
+        component: () => import("@/views/sheetList/sharedSearch.vue"),
+        meta: { title: "ETA表格", noHead:true },
+    },
+    {
+        path: "/shared/detail",
+        name: "sharedDetail",
+        component: () => import("@/views/sheetList/sharedDetail.vue"),
+        meta: { title: "共享表格", noHead:true  },
+    },
+    {
+        path: "/balance/detail",
+        name: "balanceDetail",
+        component: () => import("@/views/sheetList/balanceDetail.vue"),
+        meta: { title: "平衡表", noHead:true},
+    },
+    {
+        path: "/balance/chart",
+        name: "balanceChart",
+        component: () => import("@/views/sheetList/balanceChart.vue"),
+        meta: { title: "关联图表", noHead:true},
+    }
+]

+ 20 - 0
src/store/modules/i18n.js

@@ -0,0 +1,20 @@
+import { defineStore } from 'pinia'
+import i18n from "../../lang/index.js";
+
+const useLocaleStore = defineStore('locale', {
+  state: () => {
+    return {
+      locale: localStorage.getItem('lang') || 'zhCn',
+    }
+  },
+  actions: {
+    SET_LOCALE(locale) {
+      this.locale = locale
+      localStorage.setItem('lang', locale)
+      i18n.global.locale.value = locale
+    },
+  },
+  getters: {},
+})
+
+export default useLocaleStore

+ 156 - 0
src/views/sheetList/Index.vue

@@ -0,0 +1,156 @@
+<script setup>
+import {getStaticImg} from '@/hooks/common.js'
+import { useRouter } from 'vue-router'
+import {getCurrentInstance, computed} from 'vue'
+const { appContext : { config: { globalProperties } } } = getCurrentInstance();
+
+const router=useRouter()
+
+const tabbarList = computed(() => {
+    return [
+        {
+            name:"shared",
+            // icon:getStaticImg('tabbar/home.png'),
+            // iconActive:getStaticImg('tabbar/home-s.png'),
+            text: globalProperties.$t('tableTabbar.shared_table'),
+            path:"/shared/list"
+        },
+        {
+            name:"timeline",
+            // icon:getStaticImg('tabbar/user.png'),
+            // iconActive:getStaticImg('tabbar/user-s.png'),
+            text:globalProperties.$t('tableTabbar.timeline_table'),
+            path:"/timeline/list"
+        },
+        {
+            name:"mixed",
+            // icon:getStaticImg('tabbar/home.png'),
+            // iconActive:getStaticImg('tabbar/home-s.png'),
+            text: globalProperties.$t('tableTabbar.mixed_table'),
+            path:"/mixed/list"
+        },
+        {
+            name:"balance",
+            // icon:getStaticImg('tabbar/user.png'),
+            // iconActive:getStaticImg('tabbar/user-s.png'),
+            text:globalProperties.$t('tableTabbar.balance_sheet'),
+            path:"/balance/list"
+        }
+    ]
+})
+
+function goPage(item){
+    router.replace(item.path)
+}
+
+</script>
+
+<template>
+    <div class="tabbar-page">
+        <router-view></router-view>
+        <div class="tabbar-box">
+            <div 
+                :class="['tabbar-item',$route.path==item.path?'tabbar-item_active':'']"
+                v-for="item in tabbarList"
+                :key="item.name"
+                @click="goPage(item)"
+            >   
+                <div class="tabbar-item-con">
+                    <!-- <img class="icon" :src="$route.path==item.path?item.iconActive:item.icon" /> -->
+                    <div class="text">{{item.text}}</div>
+                </div>
+            </div>
+        </div>
+    </div>
+</template>
+
+<style lang="scss" scoped>
+.tabbar-box{
+    position: fixed;
+    left: 0;
+    right: 0;
+    bottom: 0;
+    z-index: 99;
+    background: #FFFFFF;
+    box-shadow: 0px -1px 12px rgba(0, 0, 0, 0.08);
+    height: 112px;
+    display: flex;
+    align-items: center;
+    .tabbar-item{
+        flex: 1;
+        padding: 16px;
+        height: 100%;
+        position: relative;
+        &::after{
+            content: '';
+            width: 1px;
+            height: calc(100% - 32px);
+            background-color: $border-color;
+            position: absolute;
+            top: 16px;
+            right: 0;
+        }
+        .tabbar-item-con{
+            display: flex;
+            flex-direction: column;
+            justify-content: center;
+            align-items: center;
+            height: 100%;
+            .icon{
+                width: 48px;
+                height: 48px;
+            }
+            .text{
+                // color: rgba(0, 82, 217, 1);
+                font-size: 32px;
+                font-weight: 400;
+                margin-top: 5px;
+            }
+        }
+        &:last-child::after{
+            display: none;
+        }
+    }
+    .tabbar-item_active{
+        .tabbar-item-con{
+            background-color: #F2F3FF;
+            border-radius: 112px;
+            .text{
+                color: $theme-color;
+                font-weight: 500;
+            }
+        }
+    }
+}
+@media screen and (min-width:$media-width){
+    .tabbar-box{
+        height: 56px;
+        .tabbar-item{
+            padding: 8px;
+            height: 100%;
+            &::after{
+                width: 1px;
+                height: calc(100% - 16px);
+                top: 8px;
+            }
+            .tabbar-item-con{
+                .icon{
+                    width: 24px;
+                    height: 24px;
+                }
+                .text{
+                    // color: rgba(0, 82, 217, 1);
+                    font-size: 16px;
+                    font-weight: 400;
+                    margin-top: 2px;
+                }
+            }
+        }
+        .tabbar-item_active{
+            .tabbar-item-con{
+                border-radius: 56px;
+            }
+        }
+    }
+}
+</style>

+ 117 - 0
src/views/sheetList/balanceChart.vue

@@ -0,0 +1,117 @@
+<script setup name="ChartETAChartDetail">
+import AddChartToMyETA from './components/AddChartToMyETA.vue';
+import { useRoute, useRouter } from "vue-router";
+import { ref, onMounted, nextTick } from 'vue';
+import apiSheet from '@/api/sheet'
+import { useWindowSize } from '@vueuse/core'
+import {useChartRender} from '@/hooks/chart/render'
+const {chartRender}=useChartRender()
+const chartList = ref([]);
+const isShowAddToMyETADialog = ref(false);
+const route = useRoute()
+const { width } = useWindowSize()
+const chartItem = ref({});
+const isLoading = ref(false);
+
+onMounted(() => {
+    getChartList('init')
+})
+
+// 获取图表数据
+async function getChartList(){
+    isLoading.value = true
+    const res = await apiSheet.getBalanceChartData({
+        ExcelInfoId: Number(route.query.id)
+    })
+    isLoading.value = false
+    if(res.Ret!==200) return 
+    chartList.value = res.Data.List||[]
+    nextTick(()=>{
+        chartList.value.forEach(element => {
+            chartRender({
+                data:{
+                    ...element,
+                    ChartInfo:{
+                        ...element.ChartInfo,
+                    },
+                },
+                renderId: 'container' + element.ChartInfo.ChartInfoId,
+                changeLangIsCheck:false,
+                showChartTitle:false,
+                shouldUseSelfLimit:true,
+            })
+        });
+    })
+}
+
+// 加入我的图库
+function addChart(item){
+    isShowAddToMyETADialog.value = true;
+    chartItem.value = item
+}
+</script>
+
+<template>
+    <div class="sheet-chart-page">
+        <template v-if="chartList.length">
+            <div class="sheet-name">{{ route.query.name }}</div>
+            <!-- 图渲染区域 -->
+            <div class="chart-box" v-for="item in chartList" :key="item.ChartInfo.ChartInfoId">
+                <div class="chart-name">{{ item.ChartInfo.ChartName }}</div>
+                <div class="chart" :id="'container' + item.ChartInfo.ChartInfoId"></div>
+                <div class="add-chart" @click="addChart(item)">加入我的图库</div>
+            </div>
+            <!-- 加入我的图库弹窗 -->
+            <AddChartToMyETA 
+                :isShowDialog="isShowAddToMyETADialog"
+                :dialogPosition="width>650?'center':'bottom'"
+                :chartInfo="chartItem.ChartInfo"
+                @close="isShowAddToMyETADialog=false"
+            />
+        </template>
+        <template v-if="!chartList.length && !isLoading">
+            <img class="list-empty-img" src="https://hzstatic.hzinsights.com/static/ETA_mobile/empty_img.png" alt="">
+        </template>
+    </div>
+</template>
+
+<style lang="scss" scoped>
+.sheet-chart-page {
+    padding: 20px;
+    .sheet-name {
+        margin: 0 0 20px 0;
+    }
+    .chart-box {
+        width: 100%;
+        height: 50vh;
+        padding: 20px;
+        margin-top: 10px;
+        // padding: 14px 0px 0px 0px;
+        // gap: 14px;
+        border-radius: 4px;
+        border: 1px solid rgba(220, 220, 220, 1);
+        background: rgba(255, 255, 255, 1);
+        .chart-name {
+            margin: 0 0 20px 0;
+            line-height: 30px;
+        }
+        .chart {
+            width: 100%;
+            height: calc(100% - 110px);
+        }
+        .add-chart {
+            margin-top: 20px;
+            width: 100%;
+            height: 40px;
+            text-align: right;
+            color:  rgba(54, 121, 224, 1);
+        }
+    }
+    @media screen and (min-width: 650px) {
+        .chart-box {
+            height: 500px;
+        }
+    }
+}
+</style>
+

+ 562 - 0
src/views/sheetList/balanceDetail.vue

@@ -0,0 +1,562 @@
+<script setup name="sharedDetail">
+import {nextTick, onMounted, ref, computed, getCurrentInstance} from 'vue'
+import { useRoute, useRouter } from "vue-router";
+import apiSheet from '@/api/sheet'
+import { useWindowSize } from '@vueuse/core'
+import { showToast, showDialog  } from "vant";
+import {usePublicSettingStore} from '@/store/modules/publicSetting'
+import {getStaticImg} from '@/hooks/common.js'
+import {etaTablePermission,useAuthBtn} from '@/hooks/useAuthBtn'
+const {checkAuthBtn} = useAuthBtn()
+const publicSettingStore = usePublicSettingStore()
+// import { useDownLoadFile } from '@/hooks/useDownLoadFile'
+const { appContext : { config: { globalProperties } } } = getCurrentInstance();
+const { width } = useWindowSize()
+const route = useRoute()
+const router = useRouter()
+const queryData = ref({})
+//显示更多操作栏
+let showMoreAction = ref(false)
+let showPicker = ref(false)
+let showVersionPicker = ref(false)
+const chartList = ref([])
+const sheetActions = computed(() => {
+    const list = [
+        // { label: globalProperties.$t('shared_table.refresh'), types:'flushed', src: getStaticImg('table/flushed.png'), isAuth: etaTablePermission.etaTable_customize_balance_refresh},
+        { label: globalProperties.$t('shared_table.download'), types:'download', src: getStaticImg('table/download.png'), isAuth: queryData.value.HaveOperaAuth && etaTablePermission.etaTable_customize_balance_download},
+        { label: globalProperties.$t('shared_table.delete'), types:'delete', src: getStaticImg('table/delete.png'), isAuth: queryData.value.Button && queryData.value.Button.DeleteButton && etaTablePermission.etaTable_customize_balance_del},
+    ]
+    if (chartList.value && chartList.value.length > 0) list.unshift({ label: globalProperties.$t('shared_table.chart'), types:'chart', src: getStaticImg('table/chart.png'), isAuth: true })
+    return list
+})
+const customFieldName = {
+    text: 'ExcelName',
+    value: 'ExcelInfoId',
+};
+const customVersionFieldName = {
+    text: 'VersionName',
+    value: 'UniqueCode',
+};
+const downExcelFileUrl = computed(() => {
+    let url = `${
+    import.meta.env.VITE_APP_API_URL
+    }/datamanage/excel_info/table/download?${localStorage.getItem("token")}`;
+    return url;
+})
+const link = computed(() => {
+    return publicSettingStore.publicSetting.ChartViewUrl;
+})
+
+onMounted(() => {
+    getVersionList()
+    getChildTable()
+})
+//获取表格列表
+async function getExcelDetail( source = 'refresh', ExcelInfoId = '' ){
+    const detailReq = {
+        ExcelInfoId: ExcelInfoId,
+    }
+    if(source==='delete') detailReq.ExcelInfoId = ExcelInfoId
+    const res = await apiSheet.excelDetail(detailReq)
+    if(res.Ret!==200) return 
+    if(source==='refresh') showToast({ type: 'success', message: globalProperties.$t('shared_table.refresh_success') })
+    queryData.value = res.Data
+    document.title = queryData.value.ExcelName
+}
+
+// 获取关联图库列表
+
+async function getChartList(ExcelInfoId){
+    const res = await apiSheet.getBalanceChartData({
+        ExcelInfoId
+    })
+    if(res.Ret!==200) return 
+    chartList.value = res.Data.List||[]
+}
+
+//更多操作
+function handleActionClick(item){
+    switch(item.types){
+        case 'flushed':
+            getChildTable()
+            showMoreAction.value = false
+            break;
+        case 'download':
+            downloadExcel(queryData.value)
+            showMoreAction.value = false
+            break;
+        case 'delete':
+            showDialog({
+                title: globalProperties.$t('shared_table.note'),
+                message: globalProperties.$t('shared_table.once_deleted'),
+                showCancelButton: true
+            }).then(() => {
+                apiSheet.excelDelete({
+                    ExcelClassifyId: queryData.value.ExcelClassifyId,
+                    ExcelInfoId: queryData.value.ExcelInfoId,
+                    Source: queryData.Source,
+                }).then(res=>{
+                    if(res.Ret!==200) return 
+                    showToast({message: globalProperties.$t('shared_table.deletion_success'),type:'success'})
+                    showMoreAction.value = false
+                    getExcelDetail('delete', res.Data.ExcelInfoId)
+                })
+            }).catch(()=>{})
+            break;
+        case 'chart':
+            goChart()
+            break;
+    }
+}
+
+/* 下载数据 */
+function downloadExcel(cell) {
+    const { Source, FileUrl, ExcelInfoId, ExcelName } = cell;
+    
+    let value = "";
+    if (Source === 1) {
+        handleDownloadResource(FileUrl, ExcelName)
+    } else if ([2, 3,5].includes(Source)) {
+        value = `${downExcelFileUrl.value}&ExcelInfoId=${route.query.id}`;
+        
+        const a = document.createElement("a");
+        a.href = value;
+        a.target = "_blank";
+        a.download = ExcelName;
+        a.style.display = "none";
+        document.body.append(a);
+        a.click();
+    }
+}
+// 下载文件
+function handleDownloadResource(url,fileName,successCb,faileCb){
+    const b = new Base64() 
+    const arr = url.split('/')
+    const _fileName = arr[arr.length-1]
+    const fileNameTypeArr = fileName.split('.')
+    const _fileNameTypeArr = _fileName.split('.')
+    const fileNameType = fileNameTypeArr.length > 1 ? fileNameTypeArr[fileNameTypeArr.length-1] : ''
+    const _fileNameType = _fileNameTypeArr.length > 1 ? _fileNameTypeArr[_fileNameTypeArr.length-1] : ''
+    apiSheet.apiDownloadResource({
+        FileName:/* fileName||_fileName */'',
+        FileUrl:b.encode(url) 
+    }).then(res=>{
+        //bus.$parseData(response);
+        const content = res
+        const blob = new Blob([content])
+        
+        if ('download' in document.createElement('a')) {
+            const elink = document.createElement('a')
+            elink.download = fileNameType?fileName:(fileName+'.'+_fileNameType)
+            elink.style.display = 'none'
+            elink.href = window.URL.createObjectURL(blob)
+            document.body.appendChild(elink)
+            elink.click()
+            window.URL.revokeObjectURL(elink.href)
+            document.body.removeChild(elink)
+        } else {
+            navigator.msSaveBlob(blob, fileNameType?fileName:(fileName+'.'+_fileNameType))
+        }
+        successCb&&successCb()
+    }).catch(()=>{
+        showToast({message: '下载失败',type:'warning'})
+        faileCb&&faileCb()
+    })
+}
+
+function Base64() {
+    // private property
+    var _keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
+    // public method for encoding
+    this.encode = function (input) {
+        var output = "";
+        var chr1, chr2, chr3, enc1, enc2, enc3, enc4;
+        var i = 0;
+        input = this._utf8_encode(input);
+        while (i < input.length) {
+            chr1 = input.charCodeAt(i++);
+            chr2 = input.charCodeAt(i++);
+            chr3 = input.charCodeAt(i++);
+            enc1 = chr1 >> 2;
+            enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
+            enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
+            enc4 = chr3 & 63;
+            if (isNaN(chr2)) {
+                enc3 = enc4 = 64;
+            } else if (isNaN(chr3)) {
+                enc4 = 64;
+            }
+            output = output +
+                _keyStr.charAt(enc1) + _keyStr.charAt(enc2) +
+                _keyStr.charAt(enc3) + _keyStr.charAt(enc4);
+        }
+        return output;
+    }
+    // private method for UTF-8 encoding
+    this._utf8_encode = function (string) {
+        string = string.replace(/\r\n/g, "\n");
+        var utftext = "";
+        for (var n = 0; n < string.length; n++) {
+            var c = string.charCodeAt(n);
+            if (c < 128) {
+                utftext += String.fromCharCode(c);
+            } else if ((c > 127) && (c < 2048)) {
+                utftext += String.fromCharCode((c >> 6) | 192);
+                utftext += String.fromCharCode((c & 63) | 128);
+            } else {
+                utftext += String.fromCharCode((c >> 12) | 224);
+                utftext += String.fromCharCode(((c >> 6) & 63) | 128);
+                utftext += String.fromCharCode((c & 63) | 128);
+            }
+        }
+        return utftext;
+    }
+}
+
+const versionOpts = ref([])
+const childTableOpts = ref([])
+const fieldValue = ref('表格名');
+const fieldValueVersion = ref('版本');
+
+// 平衡表获取版本列表
+function getVersionList(){
+    apiSheet.balanceTableVersion({ExcelInfoId: route.query.id,}).then(res=>{
+        if(res.Ret === 200){
+            const arr = res.Data.List || []
+            versionOpts.value = arr
+            fieldValueVersion.value = arr[0].VersionName
+        }
+    })
+}
+
+// 平衡表获取子表列表
+function getChildTable(ParentId = route.query.id){
+    apiSheet.getBalanceChildTable({ParentId}).then(res=>{
+        if(res.Ret === 200){
+            const arr = res.Data.List || []
+            if(arr.length === 0) return; // 修正逻辑错误
+            childTableOpts.value = arr;
+            const firstItemId = arr[0]?.ExcelInfoId || route.query.id; // 使用可选链操作符简化逻辑
+            getExcelDetail('load', firstItemId);
+            getChartList(firstItemId);
+            fieldValue.value = arr[0]?.ExcelName || '默认表格名';
+        }
+    })
+}
+
+// 平衡表选择器
+const onConfirm = ({ selectedOptions }) => {
+    const firstItemId = selectedOptions[0].ExcelInfoId
+    showPicker.value = false;
+    fieldValue.value = selectedOptions[0].ExcelName;
+    getExcelDetail( 'load',firstItemId)
+    getChartList(firstItemId);
+};
+
+// 版本选择器
+const onConfirmVersion = ({ selectedOptions }) => {
+    const firstItemId = selectedOptions[0].ExcelInfoId
+    showVersionPicker.value = false;
+    fieldValueVersion.value = selectedOptions[0].VersionName;
+    // getExcelDetail( 'load', selectedOptions[0].ExcelInfoId)
+    getChildTable(firstItemId);
+    getChartList(firstItemId);
+};
+
+// 关联图表页
+function goChart () {
+    router.push({ path: '/balance/chart', query: { id: queryData.value.ExcelInfoId, name: queryData.value.ExcelName } });
+}
+</script>
+
+<template>
+    <div class="page">
+        <div class="table-title">
+            <div class="table-name">{{ queryData.ExcelName }}</div>
+            <div class="top-box">
+                <div class="top-item">{{ $t('shared_table.author') }}:{{ queryData.SysUserRealName }}</div>
+                <div class="top-item">{{ $t('shared_table.recent_save_time') }}:{{ queryData.CreateTime?.slice(0,10) }} </div>
+                <!-- <div class="top-item"></div> -->
+            </div>
+        </div>
+        <div class="filter-box">
+            <div class="filter-item">
+                <div class="filter-select" @click.stop="showPicker=true">
+                    <div class="filter-value">
+                        {{ fieldValue }} 
+                    </div> 
+                    <van-icon name="arrow"/>
+                </div>
+            </div>
+            <div class="filter-item">
+                <div class="filter-select"  @click.stop="showVersionPicker=true">
+                    <div class="filter-value">
+                        {{ fieldValueVersion }} 
+                    </div> 
+                    <van-icon name="arrow"/>
+                </div>
+            </div>
+            <!-- <div class="top-item"><van-icon name="chart-trending-o" size="22" @click.stop="goChart"v-if="queryData.Source === 5" /> <van-icon name="more-o" size="22" @click.stop="showMoreAction=true"/></div> -->
+        </div>
+        <div class="sheet-box" v-if="queryData.UniqueCode">
+            <iframe :src="link + '/sheetshow?code=' + queryData.UniqueCode" frameborder="0" width="100%" height="100%"></iframe>
+        </div>
+        <div class="bottom">
+            <template v-for="item in sheetActions" :key="item.types">
+                <div class="bottom-item" @click="handleActionClick(item)" v-if="item.isAuth">
+                    <img :src="item.src" alt="">
+                    <div>{{item.label}}</div>
+                </div>
+            </template>
+        </div>
+        <!-- 更多设置弹窗 -->
+        <van-popup
+            v-model:show="showMoreAction"
+            :position="width>650?'center':'bottom'"
+            round
+            closeable
+            :style="width>650?{ width: '400px'}:''"
+        >
+            <div class="global-pop-wrap_mobile sheet-more-action-wrap">
+                <div class="head-box">
+                    <div class="title van-ellipsis">{{queryData.ExcelName}}</div>
+                </div>
+                <div class="action-box">
+                    <template v-for="item in sheetActions" :key="item.types">
+                        <div class="action-item" @click="handleActionClick(item)">
+                            {{item.label}}
+                        </div>
+                    </template>
+                </div>
+            </div>
+        </van-popup>
+
+        <!-- 平衡表选择器 -->
+        <van-popup v-model:show="showPicker" round position="bottom">
+            <van-picker
+                :columns="childTableOpts"
+                @cancel="showPicker = false"
+                @confirm="onConfirm"
+                :columns-field-names="customFieldName"
+            />
+        </van-popup>
+        <!-- 版本选择器 -->
+        <van-popup v-model:show="showVersionPicker" round position="bottom">
+            <van-picker
+                :columns="versionOpts"
+                @cancel="showVersionPicker = false"
+                @confirm="onConfirmVersion"
+                :columns-field-names="customVersionFieldName"
+            />
+        </van-popup>
+    </div>
+</template>
+
+<style lang="scss" scoped>
+.page{
+    width: 100%;
+    // height: 100vh;
+    padding: 24px;
+    .table-name {
+        max-height: 100px;
+        font-family: PingFang SC;
+        font-size: 36px;
+        font-weight: 400;
+        line-height: 52px;
+        // text-align: left;
+    }
+    .filter-box {
+        font-size: 28px;
+        width: 100%;
+        // height: 84px;
+        display: flex;
+        justify-content: space-between;
+        align-items: center;
+        .filter-item {
+            margin: 10px 0;
+            width: 49%;
+            height: 84px;
+            display: flex;
+            align-items: center;
+            border-radius: 12px;
+            justify-content: space-between;
+            .filter-select {
+                width: 100%;
+                height: 72px;
+                border-radius: 12px;
+                font-size: 28px;
+                font-weight: 400;
+                padding: 0 16px;
+                display: flex;
+                align-items: center;
+                justify-content: space-between;
+                color: rgba(0, 82, 217, 1);
+                background: rgba(242, 243, 255, 1);
+                .filter-value {
+                    line-height: 72px;
+                    text-align: center;
+                }
+            }
+        }
+        .top-item {
+            width: 10%;
+            height: 60px;
+            display: flex;
+            justify-content: space-around;
+            align-items: center;
+        }
+    }
+    .top-box {
+        width: 100%;
+        height: 60px;
+        display: flex;
+        justify-content: space-between;
+        font-family: PingFang SC;
+        font-size: 24px;
+        font-weight: 400;
+        line-height: 32px;
+        text-align: center;
+        color: rgba(102, 102, 102, 1);
+        .top-item {
+            line-height: 60px;
+        }
+    }
+    .sheet-box{
+        width: 100%;
+        height: calc(100vh - 400px);
+    }
+    .sheet-more-action-wrap{
+        .head-box{
+            .title{
+                width: 100%;
+                padding:34px 100px;
+                box-sizing: border-box;
+                font-size: 36px;
+                font-weight: 600;
+                text-align: center;
+            }
+        }
+        .action-box{
+            .action-item{
+                text-align: center;
+                padding:32px 84px;
+                border-bottom: 1px solid #DCDFE6;
+                &:last-child{
+                    border-bottom: none;
+                }
+            }
+        }
+    }
+    .bottom {
+        position: fixed;
+        bottom: 0;
+        right: 0;
+        z-index: 99;
+        display: flex;
+        background-color: #fff;
+        justify-content: space-around;
+        .bottom-item {
+            color: rgba(51, 51, 51, 1);
+            font-size: 20px;
+            font-weight: 400;
+            line-height: 32px;
+            text-align: center;
+            img {
+                width: 40px;
+                height: 40px;
+            }
+        }
+    }
+    @media screen and (max-width: 650px) {
+        .bottom {
+            left: 0;
+        }
+    }
+    @media screen and (min-width: 650px) {
+        .filter-box {
+            padding-top: 5px;
+            background-color: #fff;
+            width: 50%;
+            font-size: 16px;
+            height: 35px;
+            position: fixed;
+            left: 12px;
+            bottom: 18px;
+            .filter-item {
+                width: 49%;
+                height: 30px;
+                .filter-select {
+                    height: 30px;
+                    border-radius: 6px;
+                    font-size: 14px;
+                }
+            }
+            .top-item {
+                height: 30px;
+            }
+        }
+    }
+}
+@media screen and (min-width: 650px) {
+    .page{
+        width: 100%;
+        padding: 12px;
+        .table-title {
+            width: 100%;
+            display: flex;
+            justify-content: space-between;
+            .table-name {
+                width: 50%;
+                height: 50px;
+                font-family: PingFang SC;
+                font-size: 18px;
+                font-weight: 400;
+                line-height: 25px;
+                text-align: left;
+            }
+            .top-box {
+                width: 50%;
+                height: 50px;
+                display:block;
+                font-family: PingFang SC;
+                font-size: 12px;
+                font-weight: 400;
+                text-align: right;
+                color: rgba(102, 102, 102, 1);
+                .top-item {
+                    line-height: 25px;
+                }
+            }
+        }
+        .sheet-box{
+            margin-top: 10px;
+            width: 100%;
+            height: calc(100vh - 190px);
+        }
+        .bottom {
+            width: 49%;
+            // position: absolute;
+            // right: 12px;
+            // bottom: 12px;
+            position: fixed;
+            background-color: #fff;
+            bottom: 12px;
+            right: 12px;
+            z-index: 99;
+            display: flex;
+            justify-content: space-around;
+            .bottom-item {
+                color: rgba(51, 51, 51, 1);
+                font-size: 10px;
+                font-weight: 400;
+                line-height: 16px;
+                text-align: center;
+                img {
+                    width: 20px;
+                    height: 20px;
+                }
+            }
+        }
+    }
+}
+</style> 

+ 443 - 0
src/views/sheetList/balanceList.vue

@@ -0,0 +1,443 @@
+<script setup name="SheetETAList">
+import apiSheet from '@/api/sheet'
+import {ref,reactive,watch,computed} from 'vue'
+import { useRouter } from "vue-router";
+import useLocaleStore from "@/store/modules/i18n";
+import CatalogTree from './components/CatalogTree.vue';
+import CatalogItem from './components/CatalogItem.vue';
+import { showToast } from "vant";
+import { useNoAuth } from '@/hooks/useNoAuth'
+//激活的目录路径
+const catalogMenu = ref('')
+const router = useRouter()
+//表格列表
+const listState = reactive({
+    cid:0,
+    list:[],
+    page:1,
+    pageSize:15,
+    finished:false,
+    loading:false,
+    total:0,
+    IsShowMe:false
+})
+//是否展示目录列表
+const IsShowCatalog = ref(false)
+const localeStore = useLocaleStore()
+const temp = localeStore.locale === 'zhCn' ? 'zhCn' : 'en'; //根据当前i18n语言设置表格默认语言
+localeStore.SET_LOCALE(temp)
+
+watch(()=>listState.IsShowMe,()=>{
+    window.scrollTo({top:0})
+    listState.list=[]
+    listState.page=1
+    //设置数据已加载完毕,因为当滚动条不在顶部时,清空列表内容会触发onLoad
+    listState.finished = true 
+    //这个函数调用完成后,会把finished重置成正确的值
+    getSheetList()
+})
+
+getSheetList()
+//获取表格列表
+async function getSheetList(){
+    const {pageSize,cid,page,IsShowMe} = listState
+    const res = await apiSheet.sheetList({
+        PageSize: pageSize,
+        CurrentIndex: page,
+        Source: 5,
+        ExcelClassifyId: cid,
+        IsShowMe,
+    })
+    if(res.Ret!==200) return 
+    const arr = res.Data?res.Data.List:[]
+    listState.list = [...listState.list,...arr]
+    listState.total = res.Data?res.Data.Paging.Totals:0
+    listState.finished = res.Data?res.Data.Paging.IsEnd:true
+    listState.loading=false
+}
+
+//目录被点击 type:['top'一级目录,'node'二级目录,'item'三级目录]
+function catalogItemClick({item,type='node',parent={}}){
+    // console.log(item);
+    // console.log(type);
+    // console.log(parent);
+    
+    const topMenu = type==='top'?item.ExcelClassifyName:type==='node'?item.parentName:parent.parentName
+    const nodeMenu = type==='node'?item.ExcelClassifyName:type==='item'?parent.ExcelClassifyName:''
+    const itemMenu = type==='item'?item.ExcelClassifyName:''
+
+    catalogMenu.value = `${topMenu}${nodeMenu.length?`/${nodeMenu}`:''}${itemMenu.length?`/${itemMenu}`:''}`
+    
+    listState.cid = item.ExcelClassifyId
+    listState.list=[]
+    listState.page=1
+    getSheetList()
+}
+
+//展示目录列表
+function showCatalog(){
+    IsShowCatalog.value = true
+}
+
+//下拉加载
+function onLoad(){
+    if(IsShowCatalog.value) return
+    listState.page++
+    getSheetList()
+}
+
+//展开的目录
+const activeCatalogs = ref([])
+
+//目录列表
+const catalogNodes = ref([])
+const dataNodes = ref([])
+getCatalogList()
+//获取目录列表
+async function getCatalogList(){
+    const {IsShowMe} = listState
+    const res = await apiSheet.excelClassifyList({
+        Source: 5,
+        IsShowMe: IsShowMe
+    })
+    if(res.Ret!==200) return 
+    dataNodes.value = res.Data?res.Data.AllNodes:[]||[]
+    catalogNodes.value = dataNodes.value.map(node=>{
+        if(node.Children){
+            node.Children = node.Children.map(child=>{
+                child.parentName = node.ExcelClassifyName //添加子分类时需要显示父级分类名称
+                if(child.Children){//改成三级目录了
+                    child.Children = child.Children.map(_child=>{
+                        _child.parentName = child.ExcelClassifyName
+                        return _child
+                    })
+                }
+                return child
+            })
+        }
+        return node
+    })
+}
+
+function showFileOpt(item){
+    console.log(item);
+}
+//跳转至图表详情页
+const goSheetDetail = (item)=>{
+    if(!item.HaveOperaAuth) return showToast(useNoAuth().sheet)
+    router.push({
+        path:'/balance/detail',
+        query:{
+            id:item.ExcelInfoId,
+        }
+    })
+}
+
+function goSheetSearch(){
+    router.push({
+        path:'/shared/search',
+        query:{
+            Source: 5,
+        }
+    })
+}
+</script>
+
+<template>
+  <div class="excel-eta-list-wrap">
+    <div class="select-wrap">
+      <div class="search-box">
+        <van-search 
+          shape="round" 
+          readonly 
+          :placeholder="$t('shared_table.enter_table_name')"
+          style="flex:1;padding-left:0"
+          @click-input="goSheetSearch"
+        />
+        <div class="list-icon icon" @click="showCatalog">
+          <img src="@/assets/imgs/chartETA/list-icon.png" alt="">
+        </div>
+      </div>
+      <p style="font-weight: bold;word-break: break-all;margin-bottom: 5px;">{{ catalogMenu }}</p>
+      <div class="select-box">
+        <span>{{ $t('shared_table.tables_total', {totalNum: listState.total}) }}</span>
+        <span> <van-checkbox v-model="listState.IsShowMe">{{ $t('shared_table.just_look_edit') }}</van-checkbox></span>
+      </div>
+    </div>
+    <div class="excel-list-wrap">
+      <van-list
+          v-model:loading="listState.loading"
+          :finished="listState.finished"
+          :finished-text="listState.list.length > 0 ? $t('shared_table.no_more') : $t('shared_table.no_table')"
+          :immediate-check="false"
+          @load="onLoad"
+      >
+          <img v-if="listState.list.length==0&&listState.finished" class="list-empty-img" src="https://hzstatic.hzinsights.com/static/ETA_mobile/empty_img.png" alt="">
+          <ul class="excel-list">
+              <li 
+                  class="excel-list-item" 
+                  v-for="item in listState.list" 
+                  :key="item.ChartInfoId"
+                  @click="goSheetDetail(item)"
+              >
+                  <div class="title">{{item.ExcelName}}</div>
+                  <img class="img" :src="!item.HaveOperaAuth?useNoAuth().noAuthImg:item.ExcelImage" alt="">  
+                  <div class="time">
+                      <span>{{item.CreateTime.slice(0,10)}}</span>
+                  </div>
+              </li>
+          </ul>
+      </van-list>
+    </div>
+     <!-- 目录列表 -->
+     <van-popup v-model:show="IsShowCatalog" position="right" class="catalog-list-wrap" style="height:100%">
+        <div class="catalog-list">
+            <div class="top sticky-part">
+                <h3>{{ $t('shared_table.choose_category') }}</h3>
+                <van-icon name="cross" @click.stop="IsShowCatalog=false"/>
+            </div>
+            <!-- 将目录改为三级 -->
+            <div class="list-box catalog-tree-wrap">
+                <van-collapse v-model="activeCatalogs" :border="false">
+                    <van-collapse-item 
+                        v-for="catalog in catalogNodes" :key="catalog.UniqueCode" :name="catalog.UniqueCode"
+                        @click.stop="catalogItemClick({item:catalog,type:'top'})">
+                        <template #title>
+                            <CatalogItem
+                                :node="catalog" 
+                                @showPopup="showFileOpt"/>
+                        </template>
+                        <CatalogTree 
+                            :catalog-nodes="catalog.Children"
+                            :showFileOpt="showFileOpt"
+                            :activeId="listState.cid"
+                            @handleCatalogItemClick="catalogItemClick"
+                        />
+                    </van-collapse-item>
+                </van-collapse>
+            </div>
+        </div>
+    </van-popup>
+  </div>
+</template>
+<style lang="scss">
+.excel-eta-list-wrap{
+    .catalog-list-wrap{
+        width: 65%;
+    }
+    .rename-wrap{
+        padding:48px;
+        input{
+            padding: 24px 32px;
+            border-radius: 12px;
+            background-color: #F6F6F6;
+            width: 100%;
+        }
+        .label{
+            color: #666666;
+            margin-bottom: 32px;
+            text-align: center;
+        }
+    }
+    @media screen and (min-width:$media-width){
+        .catalog-list-wrap{
+            width: 30%;
+        }
+        .move-popup{
+            width:375px;
+        }
+        .rename-wrap{
+            padding:24px;
+            input{
+                padding: 12px 16px;
+                border-radius: 6px;
+                background-color: #F6F6F6;
+                width: 100%;
+            }
+            .label{
+                margin-bottom: 16px;
+            }
+        }
+    }
+}
+</style>
+<style scoped lang="scss">
+.excel-eta-list-wrap{
+  .select-wrap{
+      padding: 0 30px 30px 30px;
+      position: sticky;
+      top:0;
+      background-color: #fff;
+      .search-box{
+          display: flex;
+          align-items: center;
+          .icon{
+              margin-left: 10px;
+              width: 70px;
+              height:70px;
+              background-color: #F2F3FF;
+              border-radius: 50%;
+              display: flex;
+              align-items: center;
+              justify-content: center;
+              img{
+                  width:45px;
+                  height:45px;
+              }
+          }
+      }
+      .select-box{
+          display: flex;
+          justify-content: space-between;
+          flex-wrap: wrap;
+      }
+  }
+  .excel-list-wrap{
+      //margin-top:15px;
+      padding: 0 30px; 
+      padding-bottom: 30px;
+      .excel-list{
+          display: flex;
+          justify-content: space-between;
+          flex-wrap: wrap;
+          gap: 30px 0;
+          .excel-list-item{
+              box-sizing: border-box;
+              width: 330px;
+              padding: 10px 14px;
+              border: 1px solid $border-color;
+              border-radius: 12px;
+              .title{
+                  min-height: 70px;
+                  overflow: hidden;
+                  -webkit-line-clamp: 2;
+                  text-overflow: ellipsis;
+                  display:-webkit-box !important;
+                  -webkit-box-orient:vertical;
+                  word-break: break-word;
+              }
+              .img{
+                      width: 100%;
+                      height: 220px;
+                      display: block;
+                      margin: 10px 0;
+                  }
+              .time{
+                  display: flex;
+                  justify-content: space-between;
+                  font-size: 28px;
+                  color: $font-grey_999;
+                  .tool-icon{
+                      width:30px;
+                      text-align: right;
+                      img{
+                          width:5px;
+                      }
+                  }
+              }
+              
+          }
+      }
+  }
+  .catalog-list{
+        box-sizing: border-box;
+        height: 100%;
+        .sticky-part{
+            position:sticky;
+            background-color: white;
+            z-index: 99;
+            left:0;
+            right:0;
+        }
+        .top{
+            display: flex;
+            justify-content: space-between;
+            border-bottom: 1px solid #DCDFE6;
+            padding: 0 15px;
+            align-items: center;
+            top:0;
+        }
+        .bottom{
+            display: flex;
+            padding:48px;
+            bottom:0;
+            span{
+                flex: 1;
+                background-color: #0052D9;
+                color: white;
+                text-align: center;
+                border-radius: 6px;
+                padding:16px;
+                box-sizing: border-box;
+            }
+        }
+    }
+  @media screen and (min-width:$media-width){
+    .select-wrap{
+        // top:60px;
+        padding:30px;
+        .search-box{
+            .icon{
+                margin-left: 10px;
+                width: 40px;
+                height:40px;
+                background-color: #F2F3FF;
+                border-radius: 50%;
+                img{
+                    width:25px;
+                    height:25px;
+                }
+            }
+        }
+        .select-box{
+            margin-top:20px;
+        }
+    }
+    .excel-list-wrap{
+        padding:0 30px;
+        .excel-list{
+            gap: 20px 4%;
+            justify-content:flex-start;
+            .excel-list-item{
+                box-sizing: border-box;
+                width: 22%;
+                padding: 10px 14px;
+                border: 1px solid $border-color;
+                border-radius: 6px;
+                .title{
+                    min-height: 36px;
+                }
+                .img{
+                    width: 100%;
+                    height: 120px;
+                    display: block;
+                    margin: 10px 0;
+                }
+                .time{
+                    font-size: 14px;
+                    .tool-icon>img{
+                        width:3px;
+                    }
+                }
+            }
+        }
+    }
+    .catalog-list{
+        .bottom{
+            display: flex;
+            padding:24px;
+            bottom:0;
+            span{
+                flex: 1;
+                background-color: #0052D9;
+                color: white;
+                text-align: center;
+                border-radius: 6px;
+                padding:8px;
+                box-sizing: border-box;
+            }
+        }
+    }
+  }
+}
+</style>

+ 217 - 0
src/views/sheetList/components/AddChartToMyETA.vue

@@ -0,0 +1,217 @@
+<script setup>
+//加入我的图库
+import {ref,watch} from 'vue'
+import {showToast} from 'vant'
+import{apiMyClassifyList,apiMyChartAdd,apiAddClassify} from '@/api/myETA'
+
+const props = defineProps({
+    isShowDialog:{//是否展示弹窗
+        type:Boolean,
+        default:false
+    },
+    dialogPosition:{//弹窗显示的位置,根据屏幕大小决定
+        type:String,
+        default:'bottom'
+    },
+    chartInfo:{//图表信息
+        type:Object,
+        default:{}
+    }
+})
+
+//是否展示添加分类弹窗
+const showAddClassify = ref(false)
+//新添加的分类名称
+const addClassifyName = ref('')
+
+//我的图库分类列表
+const classifyList = ref([])
+//chartInfo中,已选择的分类ids
+const choosedIds = ref([])
+
+const showAddPop = ref(false)
+watch(()=>props.isShowDialog,()=>{
+    if(props.isShowDialog){
+        getMyClassifyList()
+    }
+    showAddPop.value = props.isShowDialog
+})
+const emits = defineEmits(['close'])
+watch(showAddPop,()=>{
+    if(!showAddPop.value){
+        emits('close')
+    }
+})
+//获取我的图库列表
+async function getMyClassifyList(){
+    const res = await apiMyClassifyList()
+    if(res.Ret!==200) return 
+    classifyList.value = res.Data.List||[]
+    choosedIds.value = props.chartInfo.MyChartClassifyId?props.chartInfo.MyChartClassifyId.split(',').map(e => Number(e)):[]
+
+}
+function closeDialog(){
+    emits('close')
+}
+//选择分类
+function handleChooseClassify(item){
+    if(choosedIds.value.includes(item.MyChartClassifyId)){
+        const index=choosedIds.value.indexOf(item.MyChartClassifyId)
+        choosedIds.value.splice(index,1)
+    }else{
+        choosedIds.value.push(item.MyChartClassifyId)
+    }
+    
+}
+//确认加入的分类
+function handleConfirmAddChart(){
+    if(choosedIds.value.length===0){
+        showToast('请选择分类')
+        return
+    }
+    apiMyChartAdd({
+        ChartInfoId:props.chartInfo.ChartInfoId,
+        MyChartClassifyId:choosedIds.value
+    }).then(res=>{
+        if(res.Ret!==200) return 
+        showToast({message:'加入我的图库成功',type:'success'})
+        closeDialog()
+
+    })
+}
+//添加新的分类
+function handleConfirmEditClassify(){
+    if(!addClassifyName.value){
+        showToast('请填写分类名称!')
+        return
+    }
+    apiAddClassify({MyChartClassifyName:addClassifyName.value}).then(res=>{
+        if(res.Ret!==200) return
+        showAddClassify.value=false
+        getMyClassifyList()
+    })
+}
+</script>
+
+<template>
+    <van-popup 
+        v-model:show="showAddPop" 
+        :position="dialogPosition"
+        round
+    >
+        <div class="select-classify-wrap">
+            <div class="top-box">
+                <span class="close" @click="closeDialog">关闭</span>
+                <span class="title">加入我的图库</span>
+                <span class="add-btn" @click="showAddClassify=true,addClassifyName=''">添加分类</span>
+            </div>
+            <ul class="classify-list">
+                <li 
+                    :class="['item',choosedIds.includes(item.MyChartClassifyId)?'active':'']" 
+                    v-for="item in classifyList" 
+                    :key="item.MyChartClassifyId"
+                    @click="handleChooseClassify(item)"
+                >{{item.MyChartClassifyName}}</li>
+            </ul>
+            <div class="block"></div>
+            <div class="confirm-btn" @click="handleConfirmAddChart">
+                <span>确定</span>
+            </div>
+        </div>
+    </van-popup>
+    <van-dialog 
+        v-model:show="showAddClassify" 
+        title="添加分类" 
+        show-cancel-button
+        confirmButtonText="确定"
+        @confirm="handleConfirmEditClassify"
+    >
+        <div class="rename-wrap">
+            <input type="text" placeholder="请输入分类名称!" v-model="addClassifyName">
+        </div>
+    </van-dialog>
+</template>
+
+<style scoped lang="scss">
+.select-classify-wrap{
+    .top-box{
+        padding:32px;
+        display: flex;
+        justify-content: space-between;
+        border-bottom: 1px solid #DCDFE6;
+        .close{
+            color:#666666;
+        }
+        .title{
+            font-size: 36px;
+        }
+        .add-btn{
+            color:$theme-color;
+        }
+    }
+    .classify-list{
+        max-height: 600px;
+        overflow-y: auto;
+        .item{
+            padding:32px;
+            border-bottom: 1px solid #DCDFE6;
+            
+            &.active{
+                color:$theme-color;
+                position: relative;
+                &::after{
+                    content: '';
+                    display: block;
+                    width: 36px;
+                    height: 36px;
+                    background-image: url('@/assets/imgs/icon_select2.png');
+                    background-size: cover;
+                    background-repeat: no-repeat;
+                    position: absolute;
+                    top: 50%;
+                    transform: translateY(-50%);
+                    right: $page-padding;
+                }
+            }
+        }
+    }
+    .confirm-btn{
+        padding:32px;
+        text-align: center;
+        color: $theme-color;
+    }
+    .block{
+        background-color: #F6F6F6;
+        height:16px;
+    }
+    
+    @media screen and (min-width:$media-width){
+        width: 375px;
+        .top-box{
+            padding:16px;
+            .title{
+                font-size: 18px;
+            }
+        }
+        .classify-list{
+            max-height: 500px;
+            overflow-y: auto;
+            .item{
+                padding:16px;
+                &.active{
+                    &::after{
+                        width:18px;
+                        height:18px;
+                    }
+                }
+            }
+        }
+        .confirm-btn{
+            padding:16px;
+        }
+        .block{
+            height:8px;
+        }
+    }
+}
+</style>

+ 96 - 0
src/views/sheetList/components/CatalogItem.vue

@@ -0,0 +1,96 @@
+<script setup>
+import {ref} from 'vue'
+import { useNoAuth } from '@/hooks/useNoAuth'
+const props = defineProps({
+    showFileImg:{
+        type:Boolean,
+        default:true
+    },
+    node:{
+        type:Object,
+        default:{}
+    },
+    activeId:{
+        type:Number,
+        default:0
+    }
+})
+console.log(props.node);
+
+
+const emits=defineEmits(['showFileOptClick','showPopup'])
+function showPopup(){
+    emits('showPopup',{node:props.node,optArr:props.optArr})
+}
+</script>
+
+<template>
+    <div class="catalog-item" :class="{'leaf-padding':!showFileImg}">
+        <img :src="useNoAuth().noAuthIco" width="18" height="18" v-if="!props.node.HaveOperaAuth">
+        <span class="van-ellipsis" :class="{'leaf-padding':!showFileImg,'choosed':activeId===node.ExcelClassifyId}">{{node.ExcelClassifyName||''}}</span>
+        <!-- <div @click.stop="showPopup">
+            <div class="menu-icon"  v-if="node.HaveOperaAuth">
+                <img class="icon" src="@/assets/imgs/ppt/ppt_icon_menu.png" alt="">
+            </div>
+        </div> -->
+    </div>
+</template>
+
+<style scoped lang="scss">
+.catalog-item{
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    overflow: hidden;
+    &.leaf-padding{
+        padding: var(--van-cell-vertical-padding) 0;
+    }
+    span{
+        flex: 1;
+        text-align: left;
+        box-sizing: border-box;
+        padding: 0 15px;
+        font-size: 30px;
+        &.leaf-padding{
+            padding:0 60px;
+            //color:#969799;
+            color: #323233;
+        }
+        &.choosed{
+            color:#969799;
+        }
+    }
+    img{
+        display: inline-block;
+        width: 48px;
+        height: auto;
+        &.icon{
+            width: 6px;
+            margin-right: 0;
+        }
+    }
+    .menu-icon{
+        width: 30px;
+        text-align: center;
+        img{
+            transform: rotate(90deg);
+            transform-origin: left;
+        }
+        
+    }
+    @media screen and (min-width:$media-width){
+        span{
+            font-size: 16px;
+            &.leaf-padding{
+                padding:0 60px;
+            }
+        }
+        img{
+            width: 32px;
+            &.icon{
+                width:4px;
+            }
+        }
+    }
+}
+</style>

+ 81 - 0
src/views/sheetList/components/CatalogTree.vue

@@ -0,0 +1,81 @@
+<script setup>
+import {ref} from 'vue'
+// import {useCatalogList} from '../hooks/useCatalogList'
+import CatalogItem from './CatalogItem.vue';
+const props = defineProps({
+    catalogNodes:{
+        type:Array,
+        default:[]
+    },
+    activeId:{
+        type:Number,
+        default:0
+    },
+    showFileOpt:Function
+})
+const emits=defineEmits(['handleCatalogItemClick'])
+//展开的目录
+const activeCatalogs = ref([])
+//目录操作栏
+// const {optArrNode,optArrItem,authOptArr} = useCatalogList()
+//点击目录
+function handleCatalogItemClick(item,type,node){
+    emits('handleCatalogItemClick',{item,type,parent:node})
+}
+</script>
+
+<template>
+    <div class="catalog-tree-wrap">
+        <van-collapse v-model="activeCatalogs" :border="false">
+            <van-collapse-item 
+                v-for="node in catalogNodes"
+                :key="node.UniqueCode"
+                :name="node.UniqueCode"
+                :is-link="true"
+                @click.stop="handleCatalogItemClick(node)"
+            >
+            <template #title>
+                <CatalogItem 
+                    :node="node" 
+                    @showPopup="showFileOpt"/>
+            </template>
+            <template #right-icon v-if="!node.Children">
+                <div></div>
+            </template>
+            <div 
+                class="list-item"
+                v-for="item in node.Children" 
+                :key="item.UniqueCode"
+                @click.stop="handleCatalogItemClick(item,'item',node)"
+            >
+                <CatalogItem 
+                    :node="item" 
+                    :showFileImg="false" 
+                    :activeId="activeId"
+                    @showPopup="showFileOpt"/>
+            </div>
+            </van-collapse-item>
+        </van-collapse>
+    </div>
+</template>
+
+<style lang="scss">
+.catalog-tree-wrap{
+    .van-collapse{
+        .van-collapse-item{
+            >.van-cell{
+                // flex-direction: row-reverse;
+                .van-cell__title{
+                    overflow: hidden;
+                }
+            }
+            .van-collapse-item__wrapper{
+                .van-collapse-item__content{
+                    padding-top: 0;
+                    padding-bottom: 0;
+                }
+            }
+        }
+    }
+}
+</style>

+ 446 - 0
src/views/sheetList/mixedList.vue

@@ -0,0 +1,446 @@
+<script setup name="SheetETAList">
+import apiSheet from '@/api/sheet'
+import {ref,reactive,watch,computed} from 'vue'
+import { useRouter } from "vue-router";
+import useLocaleStore from "@/store/modules/i18n";
+import CatalogTree from './components/CatalogTree.vue';
+import CatalogItem from './components/CatalogItem.vue';
+import { showToast } from "vant";
+import { useNoAuth } from '@/hooks/useNoAuth'
+//激活的目录路径
+const catalogMenu = ref('')
+const router = useRouter()
+//表格列表
+const listState = reactive({
+    cid:0,
+    list:[],
+    page:1,
+    pageSize:15,
+    finished:false,
+    loading:false,
+    total:0,
+    IsShowMe:false
+})
+//是否展示目录列表
+const IsShowCatalog = ref(false)
+const localeStore = useLocaleStore()
+const temp = localeStore.locale === 'zhCn' ? 'zhCn' : 'en'; //根据当前i18n语言设置表格默认语言
+localeStore.SET_LOCALE(temp)
+
+watch(()=>listState.IsShowMe,()=>{
+    window.scrollTo({top:0})
+    listState.list=[]
+    listState.page=1
+    //设置数据已加载完毕,因为当滚动条不在顶部时,清空列表内容会触发onLoad
+    listState.finished = true 
+    //这个函数调用完成后,会把finished重置成正确的值
+    getSheetList()
+})
+
+getSheetList()
+//获取表格列表
+async function getSheetList(){
+    const {pageSize,cid,page,IsShowMe} = listState
+    const res = await apiSheet.sheetList({
+        PageSize: pageSize,
+        CurrentIndex: page,
+        Source: 3,
+        ExcelClassifyId: cid,
+        IsShowMe,
+    })
+    if(res.Ret!==200) return 
+    const arr = res.Data?res.Data.List:[]
+    listState.list = [...listState.list,...arr]
+    listState.total = res.Data?res.Data.Paging.Totals:0
+    listState.finished = res.Data?res.Data.Paging.IsEnd:true
+    listState.loading=false
+}
+
+//目录被点击 type:['top'一级目录,'node'二级目录,'item'三级目录]
+function catalogItemClick({item,type='node',parent={}}){
+    console.log(item);
+    console.log(type);
+    console.log(parent);
+    
+    const topMenu = type==='top'?item.ExcelClassifyName:type==='node'?item.parentName:parent.parentName
+    const nodeMenu = type==='node'?item.ExcelClassifyName:type==='item'?parent.ExcelClassifyName:''
+    const itemMenu = type==='item'?item.ExcelClassifyName:''
+
+    catalogMenu.value = `${topMenu}${nodeMenu.length?`/${nodeMenu}`:''}${itemMenu.length?`/${itemMenu}`:''}`
+    console.log();
+    
+    listState.cid = item.ExcelClassifyId
+    listState.list=[]
+    listState.page=1
+    getSheetList()
+}
+
+//展示目录列表
+function showCatalog(){
+    IsShowCatalog.value = true
+}
+
+//下拉加载
+function onLoad(){
+    if(IsShowCatalog.value) return
+    listState.page++
+    getSheetList()
+}
+
+//展开的目录
+const activeCatalogs = ref([])
+
+//目录列表
+const catalogNodes = ref([])
+const dataNodes = ref([])
+getCatalogList()
+//获取目录列表
+async function getCatalogList(){
+    const {IsShowMe} = listState
+    const res = await apiSheet.excelClassifyList({
+        Source: 3,
+        IsShowMe: IsShowMe
+    })
+    if(res.Ret!==200) return 
+    console.log(res);
+    dataNodes.value = res.Data?res.Data.AllNodes:[]||[]
+    catalogNodes.value = dataNodes.value.map(node=>{
+        if(node.Children){
+            node.Children = node.Children.map(child=>{
+                child.parentName = node.ExcelClassifyName //添加子分类时需要显示父级分类名称
+                if(child.Children){//改成三级目录了
+                    child.Children = child.Children.map(_child=>{
+                        _child.parentName = child.ExcelClassifyName
+                        return _child
+                    })
+                }
+                return child
+            })
+        }
+        return node
+    })
+}
+
+function showFileOpt(item){
+    console.log(item);
+}
+//跳转至图表详情页
+const goSheetDetail = (item)=>{
+    if(!item.HaveOperaAuth) return showToast(useNoAuth().sheet)
+    router.push({
+        path:'/shared/detail',
+        query:{
+            id:item.ExcelInfoId,
+            Source:item.Source,
+        }
+    })
+}
+
+function goSheetSearch(){
+    router.push({
+        path:'/shared/search',
+        query:{
+            Source: 3,
+        }
+    })
+}
+</script>
+
+<template>
+  <div class="excel-eta-list-wrap">
+    <div class="select-wrap">
+      <div class="search-box">
+        <van-search 
+          shape="round" 
+          readonly 
+          :placeholder="$t('shared_table.enter_table_name')"
+          style="flex:1;padding-left:0"
+          @click-input="goSheetSearch"
+        />
+        <div class="list-icon icon" @click="showCatalog">
+          <img src="@/assets/imgs/chartETA/list-icon.png" alt="">
+        </div>
+      </div>
+      <p style="font-weight: bold;word-break: break-all;margin-bottom: 5px;">{{ catalogMenu }}</p>
+      <div class="select-box">
+        <span>{{ $t('shared_table.tables_total', {totalNum: listState.total}) }}</span>
+        <span> <van-checkbox v-model="listState.IsShowMe">{{ $t('shared_table.just_look_mine') }}</van-checkbox></span>
+      </div>
+    </div>
+    <div class="excel-list-wrap">
+      <van-list
+          v-model:loading="listState.loading"
+          :finished="listState.finished"
+          :finished-text="listState.list.length > 0 ? $t('shared_table.no_more') : $t('shared_table.no_table')"
+          :immediate-check="false"
+          @load="onLoad"
+      >
+          <img v-if="listState.list.length==0&&listState.finished" class="list-empty-img" src="https://hzstatic.hzinsights.com/static/ETA_mobile/empty_img.png" alt="">
+          <ul class="excel-list">
+              <li 
+                  class="excel-list-item" 
+                  v-for="item in listState.list" 
+                  :key="item.ChartInfoId"
+                  @click="goSheetDetail(item)"
+              >
+                  <div class="title">{{item.ExcelName}}</div>
+                  <img class="img" :src="!item.HaveOperaAuth?useNoAuth().noAuthImg:item.ExcelImage" alt="">
+                  <div class="time">
+                      <span>{{item.CreateTime.slice(0,10)}}</span>
+                  </div>
+              </li>
+          </ul>
+      </van-list>
+    </div>
+     <!-- 目录列表 -->
+     <van-popup v-model:show="IsShowCatalog" position="right" class="catalog-list-wrap" style="height:100%">
+        <div class="catalog-list">
+            <div class="top sticky-part">
+                <h3>{{ $t('shared_table.choose_category') }}</h3>
+                <van-icon name="cross" @click.stop="IsShowCatalog=false"/>
+            </div>
+            <!-- 将目录改为三级 -->
+            <div class="list-box catalog-tree-wrap">
+                <van-collapse v-model="activeCatalogs" :border="false">
+                    <van-collapse-item 
+                        v-for="catalog in catalogNodes" :key="catalog.UniqueCode" :name="catalog.UniqueCode"
+                        @click.stop="catalogItemClick({item:catalog,type:'top'})">
+                        <template #title>
+                            <CatalogItem
+                                :node="catalog" 
+                                @showPopup="showFileOpt"/>
+                        </template>
+                        <CatalogTree 
+                            :catalog-nodes="catalog.Children"
+                            :showFileOpt="showFileOpt"
+                            :activeId="listState.cid"
+                            @handleCatalogItemClick="catalogItemClick"
+                        />
+                    </van-collapse-item>
+                </van-collapse>
+            </div>
+        </div>
+    </van-popup>
+  </div>
+</template>
+<style lang="scss">
+.excel-eta-list-wrap{
+    .catalog-list-wrap{
+        width: 65%;
+    }
+    .rename-wrap{
+        padding:48px;
+        input{
+            padding: 24px 32px;
+            border-radius: 12px;
+            background-color: #F6F6F6;
+            width: 100%;
+        }
+        .label{
+            color: #666666;
+            margin-bottom: 32px;
+            text-align: center;
+        }
+    }
+    @media screen and (min-width:$media-width){
+        .catalog-list-wrap{
+            width: 30%;
+        }
+        .move-popup{
+            width:375px;
+        }
+        .rename-wrap{
+            padding:24px;
+            input{
+                padding: 12px 16px;
+                border-radius: 6px;
+                background-color: #F6F6F6;
+                width: 100%;
+            }
+            .label{
+                margin-bottom: 16px;
+            }
+        }
+    }
+}
+</style>
+<style scoped lang="scss">
+.excel-eta-list-wrap{
+  .select-wrap{
+      padding: 0 30px 30px 30px;
+      position: sticky;
+      top:0;
+      background-color: #fff;
+      .search-box{
+          display: flex;
+          align-items: center;
+          .icon{
+              margin-left: 10px;
+              width: 70px;
+              height:70px;
+              background-color: #F2F3FF;
+              border-radius: 50%;
+              display: flex;
+              align-items: center;
+              justify-content: center;
+              img{
+                  width:45px;
+                  height:45px;
+              }
+          }
+      }
+      .select-box{
+          display: flex;
+          justify-content: space-between;
+          flex-wrap: wrap;
+      }
+  }
+  .excel-list-wrap{
+      //margin-top:15px;
+      padding: 0 30px; 
+      padding-bottom: 30px;
+      .excel-list{
+          display: flex;
+          justify-content: space-between;
+          flex-wrap: wrap;
+          gap: 30px 0;
+          .excel-list-item{
+              box-sizing: border-box;
+              width: 330px;
+              padding: 10px 14px;
+              border: 1px solid $border-color;
+              border-radius: 12px;
+              .title{
+                  min-height: 70px;
+                  overflow: hidden;
+                  -webkit-line-clamp: 2;
+                  text-overflow: ellipsis;
+                  display:-webkit-box !important;
+                  -webkit-box-orient:vertical;
+                  word-break: break-word;
+              }
+              .img{
+                      width: 100%;
+                      height: 220px;
+                      display: block;
+                      margin: 10px 0;
+                  }
+              .time{
+                  display: flex;
+                  justify-content: space-between;
+                  font-size: 28px;
+                  color: $font-grey_999;
+                  .tool-icon{
+                      width:30px;
+                      text-align: right;
+                      img{
+                          width:5px;
+                      }
+                  }
+              }
+              
+          }
+      }
+  }
+  .catalog-list{
+        box-sizing: border-box;
+        height: 100%;
+        .sticky-part{
+            position:sticky;
+            background-color: white;
+            z-index: 99;
+            left:0;
+            right:0;
+        }
+        .top{
+            display: flex;
+            justify-content: space-between;
+            border-bottom: 1px solid #DCDFE6;
+            padding: 0 15px;
+            align-items: center;
+            top:0;
+        }
+        .bottom{
+            display: flex;
+            padding:48px;
+            bottom:0;
+            span{
+                flex: 1;
+                background-color: #0052D9;
+                color: white;
+                text-align: center;
+                border-radius: 6px;
+                padding:16px;
+                box-sizing: border-box;
+            }
+        }
+    }
+  @media screen and (min-width:$media-width){
+    .select-wrap{
+        // top:60px;
+        padding:30px;
+        .search-box{
+            .icon{
+                margin-left: 10px;
+                width: 40px;
+                height:40px;
+                background-color: #F2F3FF;
+                border-radius: 50%;
+                img{
+                    width:25px;
+                    height:25px;
+                }
+            }
+        }
+        .select-box{
+            margin-top:20px;
+        }
+    }
+    .excel-list-wrap{
+        padding:0 30px;
+        .excel-list{
+            gap: 20px 4%;
+            justify-content:flex-start;
+            .excel-list-item{
+                box-sizing: border-box;
+                width: 22%;
+                padding: 10px 14px;
+                border: 1px solid $border-color;
+                border-radius: 6px;
+                .title{
+                    min-height: 36px;
+                }
+                .img{
+                    width: 100%;
+                    height: 120px;
+                    display: block;
+                    margin: 10px 0;
+                }
+                .time{
+                    font-size: 14px;
+                    .tool-icon>img{
+                        width:3px;
+                    }
+                }
+            }
+        }
+    }
+    .catalog-list{
+        .bottom{
+            display: flex;
+            padding:24px;
+            bottom:0;
+            span{
+                flex: 1;
+                background-color: #0052D9;
+                color: white;
+                text-align: center;
+                border-radius: 6px;
+                padding:8px;
+                box-sizing: border-box;
+            }
+        }
+    }
+  }
+}
+</style>

+ 407 - 0
src/views/sheetList/sharedDetail.vue

@@ -0,0 +1,407 @@
+<script setup name="sharedDetail">
+import {nextTick, onMounted, ref, computed, getCurrentInstance} from 'vue'
+import {getStaticImg} from '@/hooks/common.js'
+import { useRoute, useRouter } from "vue-router";
+import apiSheet from '@/api/sheet'
+import { useWindowSize } from '@vueuse/core'
+import { showToast, showDialog  } from "vant";
+import {usePublicSettingStore} from '@/store/modules/publicSetting'
+import {etaTablePermission,useAuthBtn} from '@/hooks/useAuthBtn'
+const {checkAuthBtn} = useAuthBtn()
+const publicSettingStore = usePublicSettingStore()
+// import { useDownLoadFile } from '@/hooks/useDownLoadFile'
+const { appContext : { config: { globalProperties } } } = getCurrentInstance();
+const { width } = useWindowSize()
+const route = useRoute()
+const router = useRouter()
+const queryData = ref({})
+//显示更多操作栏
+let showMoreAction = ref(false)
+
+const sheetActions = computed(() => {
+    const authList = getAuthList();
+    return [
+        { label: globalProperties.$t('shared_table.download'), types:'download', src: getStaticImg('table/download.png'), isAuth: checkAuthBtn(authList.download) },
+        { label: globalProperties.$t('shared_table.delete'), types:'delete', src: getStaticImg('table/delete.png'), isAuth: queryData.value.Button && queryData.value.Button.DeleteButton && checkAuthBtn(authList.delete)},
+        // 若有需要,可以添加其他操作项
+    ];
+});
+
+function getAuthList() {
+    const Source = route.query.Source;
+    let authList = {};
+    if(Source === '1') {
+        authList = {
+            download: etaTablePermission.etaTable_customize_mix_download,
+            delete: etaTablePermission.etaTable_customize_mix_del,
+            // 可以继续添加其他权限
+        };
+    } else if (Source === '2') {
+        authList = {
+            download: etaTablePermission.etaTable_customize_data_download,
+            delete: etaTablePermission.etaTable_customize_data_del,
+            // 可以继续添加其他权限
+        };
+    } else {
+        authList = {
+            download: etaTablePermission.etaTable_excel_download,
+            delete: etaTablePermission.etaTable_excel_del,
+            // 可以继续添加其他权限
+        };
+    }
+    return authList;
+}
+
+const downExcelFileUrl = computed(() => {
+    let url = `${
+    import.meta.env.VITE_APP_API_URL
+    }/datamanage/excel_info/table/download?${localStorage.getItem("token")}`;
+    return url;
+})
+
+const link = computed(() => {
+    return publicSettingStore.publicSetting.ChartViewUrl;
+})
+
+onMounted(() => {
+    getExcelDetail( 'load' )
+})
+//获取表格列表
+async function getExcelDetail( source = 'refresh', ExcelInfoId = '' ){
+    const detailReq = {
+        ExcelInfoId: route.query.id,
+    }
+    if(source==='delete') detailReq.ExcelInfoId = ExcelInfoId
+    const res = await apiSheet.excelDetail(detailReq)
+    if(res.Ret!==200) return 
+    if(source==='refresh') showToast({ type: 'success', message: globalProperties.$t('shared_table.refresh_success') })
+    queryData.value = res.Data
+    document.title = queryData.value.ExcelName
+}
+
+//更多操作
+function handleActionClick(item){
+    switch(item.types){
+        case 'flushed':
+            getExcelDetail( 'refresh' )
+            showMoreAction.value = false
+            break;
+        case 'download':
+            downloadExcel(queryData.value)
+            showMoreAction.value = false
+            break;
+        case 'delete':
+            showDialog({
+                title: globalProperties.$t('shared_table.note'),
+                message: globalProperties.$t('shared_table.once_deleted'),
+                showCancelButton: true
+            }).then(() => {
+                apiSheet.excelDelete({
+                    ExcelClassifyId: queryData.value.ExcelClassifyId,
+                    ExcelInfoId: queryData.value.ExcelInfoId,
+                    Source: queryData.Source,
+                }).then(res=>{
+                    if(res.Ret!==200) return 
+                    showToast({message: globalProperties.$t('shared_table.deletion_success'),type:'success'})
+                    showMoreAction.value = false
+                    getExcelDetail('delete', res.Data.ExcelInfoId)
+                })
+            }).catch(()=>{})
+            break;
+        case 'cancel':
+            showMoreAction.value = false
+            break;
+    }
+}
+
+/* 下载数据 */
+function downloadExcel(cell) {
+    const { Source, FileUrl, ExcelInfoId, ExcelName } = cell;
+    // console.log(cell);
+    
+    let value = "";
+    if (Source === 1) {
+        handleDownloadResource(FileUrl, ExcelName)
+    } else if ([2, 3,5].includes(Source)) {
+        value = `${downExcelFileUrl.value}&ExcelInfoId=${ExcelInfoId}`;
+        // console.log(value);
+        
+        const a = document.createElement("a");
+        a.href = value;
+        a.target = "_blank";
+        a.download = ExcelName;
+        a.style.display = "none";
+        document.body.append(a);
+        a.click();
+    }
+}
+// 下载文件
+function handleDownloadResource(url,fileName,successCb,faileCb){
+    const b = new Base64() 
+    const arr = url.split('/')
+    const _fileName = arr[arr.length-1]
+    const fileNameTypeArr = fileName.split('.')
+    const _fileNameTypeArr = _fileName.split('.')
+    const fileNameType = fileNameTypeArr.length > 1 ? fileNameTypeArr[fileNameTypeArr.length-1] : ''
+    const _fileNameType = _fileNameTypeArr.length > 1 ? _fileNameTypeArr[_fileNameTypeArr.length-1] : ''
+    apiSheet.apiDownloadResource({
+        FileName:/* fileName||_fileName */'',
+        FileUrl:b.encode(url) 
+    }).then(res=>{
+        //bus.$parseData(response);
+        const content = res
+        const blob = new Blob([content])
+        // console.log(blob);
+        
+        if ('download' in document.createElement('a')) {
+            const elink = document.createElement('a')
+            elink.download = fileNameType?fileName:(fileName+'.'+_fileNameType)
+            elink.style.display = 'none'
+            elink.href = window.URL.createObjectURL(blob)
+            document.body.appendChild(elink)
+            elink.click()
+            window.URL.revokeObjectURL(elink.href)
+            document.body.removeChild(elink)
+        } else {
+            navigator.msSaveBlob(blob, fileNameType?fileName:(fileName+'.'+_fileNameType))
+        }
+        successCb&&successCb()
+    }).catch(()=>{
+        showToast({message: '下载失败',type:'warning'})
+        faileCb&&faileCb()
+    })
+}
+
+function Base64() {
+    // private property
+    var _keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
+    // public method for encoding
+    this.encode = function (input) {
+        var output = "";
+        var chr1, chr2, chr3, enc1, enc2, enc3, enc4;
+        var i = 0;
+        input = this._utf8_encode(input);
+        while (i < input.length) {
+            chr1 = input.charCodeAt(i++);
+            chr2 = input.charCodeAt(i++);
+            chr3 = input.charCodeAt(i++);
+            enc1 = chr1 >> 2;
+            enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
+            enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
+            enc4 = chr3 & 63;
+            if (isNaN(chr2)) {
+                enc3 = enc4 = 64;
+            } else if (isNaN(chr3)) {
+                enc4 = 64;
+            }
+            output = output +
+                _keyStr.charAt(enc1) + _keyStr.charAt(enc2) +
+                _keyStr.charAt(enc3) + _keyStr.charAt(enc4);
+        }
+        return output;
+    }
+    // private method for UTF-8 encoding
+    this._utf8_encode = function (string) {
+        string = string.replace(/\r\n/g, "\n");
+        var utftext = "";
+        for (var n = 0; n < string.length; n++) {
+            var c = string.charCodeAt(n);
+            if (c < 128) {
+                utftext += String.fromCharCode(c);
+            } else if ((c > 127) && (c < 2048)) {
+                utftext += String.fromCharCode((c >> 6) | 192);
+                utftext += String.fromCharCode((c & 63) | 128);
+            } else {
+                utftext += String.fromCharCode((c >> 12) | 224);
+                utftext += String.fromCharCode(((c >> 6) & 63) | 128);
+                utftext += String.fromCharCode((c & 63) | 128);
+            }
+        }
+        return utftext;
+    }
+}
+</script>
+
+<template>
+    <div class="page">
+        <div class="table-title">
+            <div class="table-name">{{ queryData.ExcelName }}</div>
+            <div class="top-box">
+                <div class="top-item">{{ $t('shared_table.author') }}:{{ queryData.SysUserRealName }}</div>
+                <div class="top-item">{{ $t('shared_table.recent_save_time') }}:{{ queryData.CreateTime?.slice(0,10) }} </div>
+                <!-- <van-icon name="more-o" @click.stop="showMoreAction=true"/> -->
+                <!-- <div class="top-item"></div> -->
+            </div>
+        </div>
+        <div class="sheet-box" v-if="queryData.UniqueCode">
+            <iframe v-if="link" :src="link + '/sheetshow?code=' + queryData.UniqueCode" frameborder="0" width="100%" height="100%"></iframe>
+        </div>
+        <div class="bottom">
+            <template v-for="item in sheetActions" :key="item.types">
+                <div class="bottom-item" @click="handleActionClick(item)" v-if="item.isAuth">
+                    <img :src="item.src" alt="">
+                    <div>{{item.label}}</div>
+                </div>
+            </template>
+        </div>
+        <!-- 更多设置弹窗 -->
+        <van-popup
+            v-model:show="showMoreAction"
+            :position="width>650?'center':'bottom'"
+            round
+            closeable
+            :style="width>650?{ width: '400px'}:''"
+        >
+            <div class="global-pop-wrap_mobile sheet-more-action-wrap">
+                <div class="head-box">
+                    <div class="title van-ellipsis">{{queryData.ExcelName}}</div>
+                </div>
+                <div class="action-box">
+                    <template v-for="item in sheetActions" :key="item.types">
+                        <div class="action-item" @click="handleActionClick(item)">
+                            {{item.label}}
+                        </div>
+                    </template>
+                </div>
+            </div>
+        </van-popup>
+    </div>
+</template>
+
+<style lang="scss" scoped>
+.page{
+    width: 100%;
+    // height: 100vh;
+    padding: 24px;
+    .table-name {
+        max-height: 100px;
+        font-family: PingFang SC;
+        font-size: 36px;
+        font-weight: 400;
+        line-height: 52px;
+        // text-align: left;
+    }
+    .top-box {
+        width: 100%;
+        height: 60px;
+        display: flex;
+        justify-content: space-between;
+        font-family: PingFang SC;
+        font-size: 24px;
+        font-weight: 400;
+        line-height: 32px;
+        text-align: center;
+        color: rgba(102, 102, 102, 1);
+        .top-item {
+            line-height: 60px;
+        }
+    }
+    .sheet-box{
+        width: 100%;
+        height: calc(100vh - 400px);
+    }
+    .sheet-more-action-wrap{
+        .head-box{
+            .title{
+                width: 100%;
+                padding:34px 100px;
+                box-sizing: border-box;
+                font-size: 36px;
+                font-weight: 600;
+                text-align: center;
+            }
+        }
+        .action-box{
+            .action-item{
+                text-align: center;
+                padding:32px 84px;
+                border-bottom: 1px solid #DCDFE6;
+                &:last-child{
+                    border-bottom: none;
+                }
+            }
+        }
+    }
+    .bottom {
+        padding-top: 20px;
+        height: 120px;
+        position: fixed;
+        left: 0;
+        bottom: 0;
+        right: 0;
+        z-index: 99;
+        margin-top: 10px;
+        display: flex;
+        background-color: #fff;
+        justify-content: space-around;
+        .bottom-item {
+            color: rgba(51, 51, 51, 1);
+            font-size: 20px;
+            font-weight: 400;
+            line-height: 32px;
+            text-align: center;
+            img {
+                width: 40px;
+                height: 40px;
+            }
+        }
+    }
+}
+@media screen and (min-width: 650px) {
+    .page{
+        width: 100%;
+        // height: 100vh;
+        padding: 12px;
+        .table-title {
+            width: 100%;
+            display: flex;
+            justify-content: space-between;
+            .table-name {
+                width: 50%;
+                height: 50px;
+                font-family: PingFang SC;
+                font-size: 18px;
+                font-weight: 400;
+                line-height: 25px;
+                text-align: left;
+            }
+            .top-box {
+                width: 50%;
+                height: 50px;
+                display:block;
+                font-family: PingFang SC;
+                font-size: 12px;
+                font-weight: 400;
+                text-align: right;
+                color: rgba(102, 102, 102, 1);
+                .top-item {
+                    line-height: 25px;
+                }
+            }
+        }
+        .sheet-box{
+            margin-top: 10px;
+            width: 100%;
+            height: calc(100vh - 190px);
+        }
+        .bottom {
+            margin-top: 5px;
+            padding-top: 10px;
+            display: flex;
+            height: 60px;
+            justify-content: space-around;
+            .bottom-item {
+                color: rgba(51, 51, 51, 1);
+                font-size: 10px;
+                font-weight: 400;
+                line-height: 16px;
+                text-align: center;
+                img {
+                    width: 20px;
+                    height: 20px;
+                }
+            }
+        }
+    }
+}
+</style> 

+ 445 - 0
src/views/sheetList/sharedList.vue

@@ -0,0 +1,445 @@
+<script setup name="SheetETAList">
+import apiSheet from '@/api/sheet'
+import {ref,reactive,watch,computed} from 'vue'
+import { useRouter } from "vue-router";
+import useLocaleStore from "@/store/modules/i18n";
+import CatalogTree from './components/CatalogTree.vue';
+import CatalogItem from './components/CatalogItem.vue';
+import { useNoAuth } from '@/hooks/useNoAuth'
+import { showToast } from "vant";
+//激活的目录路径
+const catalogMenu = ref('')
+const router = useRouter()
+//表格列表
+const listState = reactive({
+    cid:0,
+    list:[],
+    page:1,
+    pageSize:15,
+    finished:false,
+    loading:false,
+    total:0,
+    IsShowMe:false
+})
+//是否展示目录列表
+const IsShowCatalog = ref(false)
+const localeStore = useLocaleStore()
+const temp = localeStore.locale === 'zhCn' ? 'zhCn' : 'en'; //根据当前i18n语言设置表格默认语言
+localeStore.SET_LOCALE(temp)
+
+watch(()=>listState.IsShowMe,()=>{
+    window.scrollTo({top:0})
+    listState.list=[]
+    listState.page=1
+    //设置数据已加载完毕,因为当滚动条不在顶部时,清空列表内容会触发onLoad
+    listState.finished = true 
+    //这个函数调用完成后,会把finished重置成正确的值
+    getSheetList()
+})
+
+getSheetList()
+//获取表格列表
+async function getSheetList(){
+    const {pageSize,cid,page,IsShowMe} = listState
+    const res = await apiSheet.sheetList({
+        PageSize: pageSize,
+        CurrentIndex: page,
+        Source: 1,
+        ExcelClassifyId: cid,
+        IsShowMe,
+    })
+    if(res.Ret!==200) return 
+    const arr = res.Data?res.Data.List:[]
+    listState.list = [...listState.list,...arr]
+    listState.total = res.Data?res.Data.Paging.Totals:0
+    listState.finished = res.Data?res.Data.Paging.IsEnd:true
+    listState.loading=false
+}
+
+//目录被点击 type:['top'一级目录,'node'二级目录,'item'三级目录]
+function catalogItemClick({item,type='node',parent={}}){
+    // console.log(item);
+    // console.log(type);
+    // console.log(parent);
+    
+    const topMenu = type==='top'?item.ExcelClassifyName:type==='node'?item.parentName:parent.parentName
+    const nodeMenu = type==='node'?item.ExcelClassifyName:type==='item'?parent.ExcelClassifyName:''
+    const itemMenu = type==='item'?item.ExcelClassifyName:''
+
+    catalogMenu.value = `${topMenu}${nodeMenu.length?`/${nodeMenu}`:''}${itemMenu.length?`/${itemMenu}`:''}`
+    
+    listState.cid = item.ExcelClassifyId
+    listState.list=[]
+    listState.page=1
+    getSheetList()
+}
+
+//展示目录列表
+function showCatalog(){
+    IsShowCatalog.value = true
+}
+
+//下拉加载
+function onLoad(){
+    if(IsShowCatalog.value) return
+    listState.page++
+    getSheetList()
+}
+
+//展开的目录
+const activeCatalogs = ref([])
+
+//目录列表
+const catalogNodes = ref([])
+const dataNodes = ref([])
+getCatalogList()
+//获取目录列表
+async function getCatalogList(){
+    const {IsShowMe} = listState
+    const res = await apiSheet.excelClassifyList({
+        Source: 1,
+        IsShowMe: IsShowMe
+    })
+    if(res.Ret!==200) return 
+    // console.log(res);
+    dataNodes.value = res.Data?res.Data.AllNodes:[]||[]
+    catalogNodes.value = dataNodes.value.map(node=>{
+        if(node.Children){
+            node.Children = node.Children.map(child=>{
+                child.parentName = node.ExcelClassifyName //添加子分类时需要显示父级分类名称
+                if(child.Children){//改成三级目录了
+                    child.Children = child.Children.map(_child=>{
+                        _child.parentName = child.ExcelClassifyName
+                        return _child
+                    })
+                }
+                return child
+            })
+        }
+        return node
+    })
+}
+
+function showFileOpt(item){
+    console.log(item);
+}
+//跳转至详情页
+const goSheetDetail = (item)=>{
+    if(!item.HaveOperaAuth) return showToast(useNoAuth().sheet)
+    router.push({
+        path:'/shared/detail',
+        query:{
+            id:item.ExcelInfoId,
+            Source:item.Source,
+        }
+    })
+}
+
+function goSheetSearch(){
+    router.push({
+        path:'/shared/search',
+        query:{
+            Source: 1,
+        }
+    })
+}
+</script>
+
+<template>
+  <div class="excel-eta-list-wrap">
+    <div class="select-wrap">
+      <div class="search-box">
+        <van-search 
+          shape="round" 
+          readonly 
+          :placeholder="$t('shared_table.enter_table_name')"
+          style="flex:1;padding-left:0"
+          @click-input="goSheetSearch"
+        />
+        <div class="list-icon icon" @click="showCatalog">
+          <img src="@/assets/imgs/chartETA/list-icon.png" alt="">
+        </div>
+      </div>
+      <p style="font-weight: bold;word-break: break-all;margin-bottom: 5px;">{{ catalogMenu }}</p>
+      <div class="select-box">
+        <span>{{ $t('shared_table.tables_total', {totalNum: listState.total}) }}</span>
+        <span> <van-checkbox v-model="listState.IsShowMe">{{ $t('shared_table.just_look_mine') }}</van-checkbox></span>
+      </div>
+    </div>
+    <div class="excel-list-wrap">
+      <van-list
+          v-model:loading="listState.loading"
+          :finished="listState.finished"
+          :finished-text="listState.list.length > 0 ? $t('shared_table.no_more') : $t('shared_table.no_table')"
+          :immediate-check="false"
+          @load="onLoad"
+      >
+          <img v-if="listState.list.length==0&&listState.finished" class="list-empty-img" src="https://hzstatic.hzinsights.com/static/ETA_mobile/empty_img.png" alt="">
+          <ul class="excel-list">
+              <li 
+                  class="excel-list-item" 
+                  v-for="item in listState.list" 
+                  :key="item.ChartInfoId"
+                  @click="goSheetDetail(item)"
+              >
+                  <div class="title">{{item.ExcelName}}</div>
+                  <img class="img" :src="!item.HaveOperaAuth?useNoAuth().noAuthImg:item.ExcelImage" alt="">
+                  <div class="time">
+                      <span>{{item.CreateTime.slice(0,10)}}</span>
+                  </div>
+              </li>
+          </ul>
+      </van-list>
+    </div>
+     <!-- 目录列表 -->
+     <van-popup v-model:show="IsShowCatalog" position="right" class="catalog-list-wrap" style="height:100%">
+        <div class="catalog-list">
+            <div class="top sticky-part">
+                <h3>{{ $t('shared_table.choose_category') }}</h3>
+                <van-icon name="cross" @click.stop="IsShowCatalog=false"/>
+            </div>
+            <!-- 将目录改为三级 -->
+            <div class="list-box catalog-tree-wrap">
+                <van-collapse v-model="activeCatalogs" :border="false">
+                    <van-collapse-item 
+                        v-for="catalog in catalogNodes" :key="catalog.UniqueCode" :name="catalog.UniqueCode"
+                        @click.stop="catalogItemClick({item:catalog,type:'top'})">
+                        <template #title>
+                            <CatalogItem
+                                :node="catalog" 
+                                @showPopup="showFileOpt"/>
+                        </template>
+                        <CatalogTree 
+                            :catalog-nodes="catalog.Children"
+                            :showFileOpt="showFileOpt"
+                            :activeId="listState.cid"
+                            @handleCatalogItemClick="catalogItemClick"
+                        />
+                    </van-collapse-item>
+                </van-collapse>
+            </div>
+        </div>
+    </van-popup>
+  </div>
+</template>
+<style lang="scss">
+.excel-eta-list-wrap{
+    .catalog-list-wrap{
+        width: 65%;
+    }
+    .rename-wrap{
+        padding:48px;
+        input{
+            padding: 24px 32px;
+            border-radius: 12px;
+            background-color: #F6F6F6;
+            width: 100%;
+        }
+        .label{
+            color: #666666;
+            margin-bottom: 32px;
+            text-align: center;
+        }
+    }
+    @media screen and (min-width:$media-width){
+        .catalog-list-wrap{
+            width: 30%;
+        }
+        .move-popup{
+            width:375px;
+        }
+        .rename-wrap{
+            padding:24px;
+            input{
+                padding: 12px 16px;
+                border-radius: 6px;
+                background-color: #F6F6F6;
+                width: 100%;
+            }
+            .label{
+                margin-bottom: 16px;
+            }
+        }
+    }
+}
+</style>
+<style scoped lang="scss">
+.excel-eta-list-wrap{
+  .select-wrap{
+      padding: 0 30px 30px 30px;
+      position: sticky;
+      top:0;
+      background-color: #fff;
+      .search-box{
+          display: flex;
+          align-items: center;
+          .icon{
+              margin-left: 10px;
+              width: 70px;
+              height:70px;
+              background-color: #F2F3FF;
+              border-radius: 50%;
+              display: flex;
+              align-items: center;
+              justify-content: center;
+              img{
+                  width:45px;
+                  height:45px;
+              }
+          }
+      }
+      .select-box{
+          display: flex;
+          justify-content: space-between;
+          flex-wrap: wrap;
+      }
+  }
+  .excel-list-wrap{
+      //margin-top:15px;
+      padding: 0 30px; 
+      padding-bottom: 30px;
+      .excel-list{
+          display: flex;
+          justify-content: space-between;
+          flex-wrap: wrap;
+          gap: 30px 0;
+          .excel-list-item{
+              box-sizing: border-box;
+              width: 330px;
+              padding: 10px 14px;
+              border: 1px solid $border-color;
+              border-radius: 12px;
+              .title{
+                  min-height: 70px;
+                  overflow: hidden;
+                  -webkit-line-clamp: 2;
+                  text-overflow: ellipsis;
+                  display:-webkit-box !important;
+                  -webkit-box-orient:vertical;
+                  word-break: break-word;
+              }
+              .img{
+                      width: 100%;
+                      height: 220px;
+                      display: block;
+                      margin: 10px 0;
+                  }
+              .time{
+                  display: flex;
+                  justify-content: space-between;
+                  font-size: 28px;
+                  color: $font-grey_999;
+                  .tool-icon{
+                      width:30px;
+                      text-align: right;
+                      img{
+                          width:5px;
+                      }
+                  }
+              }
+              
+          }
+      }
+  }
+  .catalog-list{
+        box-sizing: border-box;
+        height: 100%;
+        .sticky-part{
+            position:sticky;
+            background-color: white;
+            z-index: 99;
+            left:0;
+            right:0;
+        }
+        .top{
+            display: flex;
+            justify-content: space-between;
+            border-bottom: 1px solid #DCDFE6;
+            padding: 0 15px;
+            align-items: center;
+            top:0;
+        }
+        .bottom{
+            display: flex;
+            padding:48px;
+            bottom:0;
+            span{
+                flex: 1;
+                background-color: #0052D9;
+                color: white;
+                text-align: center;
+                border-radius: 6px;
+                padding:16px;
+                box-sizing: border-box;
+            }
+        }
+    }
+  @media screen and (min-width:$media-width){
+    .select-wrap{
+        // top:60px;
+        padding:30px;
+        .search-box{
+            .icon{
+                margin-left: 10px;
+                width: 40px;
+                height:40px;
+                background-color: #F2F3FF;
+                border-radius: 50%;
+                img{
+                    width:25px;
+                    height:25px;
+                }
+            }
+        }
+        .select-box{
+            margin-top:20px;
+        }
+    }
+    .excel-list-wrap{
+        padding:0 30px;
+        .excel-list{
+            gap: 20px 4%;
+            justify-content:flex-start;
+            .excel-list-item{
+                box-sizing: border-box;
+                width: 22%;
+                padding: 10px 14px;
+                border: 1px solid $border-color;
+                border-radius: 6px;
+                .title{
+                    min-height: 36px;
+                }
+                .img{
+                    width: 100%;
+                    height: 120px;
+                    display: block;
+                    margin: 10px 0;
+                }
+                .time{
+                    font-size: 14px;
+                    .tool-icon>img{
+                        width:3px;
+                    }
+                }
+            }
+        }
+    }
+    .catalog-list{
+        .bottom{
+            display: flex;
+            padding:24px;
+            bottom:0;
+            span{
+                flex: 1;
+                background-color: #0052D9;
+                color: white;
+                text-align: center;
+                border-radius: 6px;
+                padding:8px;
+                box-sizing: border-box;
+            }
+        }
+    }
+  }
+}
+</style>

+ 168 - 0
src/views/sheetList/sharedSearch.vue

@@ -0,0 +1,168 @@
+<script setup name="ChartETASearch">
+import {ref,reactive} from 'vue'
+import apiSheet from '@/api/sheet'
+import { showToast } from 'vant'
+import moment from 'moment'
+import { useRouter, useRoute } from 'vue-router'
+import { useNoAuth } from '@/hooks/useNoAuth'
+const router = useRouter()
+const route = useRoute()
+
+const keyword=ref('')
+const listState = reactive({
+    list:[],
+    page:1,
+    pageSize:20,
+    finished:false,
+    loading:false
+})
+async function getList(){
+    const res=await apiSheet.sheetList({
+        CurrentIndex: listState.page,
+        PageSize: listState.pageSize,
+        Keyword: keyword.value,
+        IsShowMe: false,
+        Source: route.query.Source
+    })
+    if(res.Ret===200){
+        listState.loading=false
+        if(!res.Data){
+            listState.finished=true
+            return
+        }
+        
+        listState.finished=res.Data.Paging.IsEnd
+        const arr=res.Data.List||[]
+        listState.list=[...listState.list,...arr]
+    }
+}
+function onLoad(){
+    listState.page++
+    getList()
+}
+
+function handleSearch(){
+    if(!keyword.value){
+        showToast('请输入关键词')
+        return
+    }
+    listState.page=1
+    listState.list=[]
+    listState.finished=false
+    getList()
+}
+
+function goDetail(item){
+    if(!item.HaveOperaAuth) return showToast(useNoAuth().sheet)
+    let path = '/shared/detail'
+    if(item.Source === 5) path = '/balance/detail'
+    router.push({
+        path,
+        query:{
+            id:item.ExcelInfoId,
+        }
+    })
+}
+
+</script>
+
+<template>
+    <div class="excel-search-list-page">
+        <div class="search-box">
+            <van-search 
+                shape="round"
+                :placeholder="$t('shared_table.enter_table_name')"
+                v-model="keyword"
+                @search="handleSearch"
+            />
+        </div>
+        <img v-if="listState.list.length==0&&listState.finished&&keyword" class="list-empty-img" src="https://hzstatic.hzinsights.com/static/ETA_mobile/empty_img.png" alt="">
+        <van-list
+            v-model:loading="listState.loading"
+            :finished="listState.finished"
+            :finished-text="listState.list.length > 0 ? $t('shared_table.no_more') : $t('shared_table.no_table')"
+            :immediate-check="false"
+            @load="onLoad"
+        >
+            <ul class="list-wrap">
+                <li class="item" v-for="item in listState.list" :key="item.ChartInfoId" @click="goDetail(item)">
+                    <div class="van-ellipsis name">{{item.ExcelName}}</div>
+                    <img class="img" :src="!item.HaveOperaAuth?useNoAuth().noAuthImg:item.ExcelImage" alt="">
+                    <div class="time">
+                        <span>{{moment(item.CreateTime).format('YYYY-MM-DD')}}</span>
+                        <span>{{item.SysUserRealName}}</span>
+                    </div>
+                </li>
+                <li class="item" style="height:0;padding:0;border:none"></li>
+                <li class="item" style="height:0;padding:0;border:none"></li>
+                <li class="item" style="height:0;padding:0;border:none"></li>
+            </ul>
+        </van-list>
+    </div>
+</template>
+
+<style lang="scss" scoped>
+.search-box{
+    position: sticky;
+    top: 0;
+    background-color: #fff;
+    z-index: 99;
+}
+.list-wrap{
+    padding: $page-padding;
+    display: flex;
+    flex-wrap: wrap;
+    justify-content: space-between;
+    .item{
+        width: 326px;
+        padding: 14px;
+        background: #FFFFFF;
+        border: 1px solid $border-color;
+        box-shadow: 0px 0px 12px rgba(0, 0, 0, 0.03);
+        border-radius: 12px;
+        box-sizing: border-box;
+        margin-bottom: 30px;
+        .img{
+            margin: 14px 0;
+            width: 100%;
+            height: 220px;
+        }
+        .time{
+            display: flex;
+            justify-content: space-between;
+            color: $font-grey_999;
+            font-size: 28px;
+        }
+    }
+}
+@media screen and (min-width:$media-width){
+    // .search-box{
+    //     top: 60px;
+    // }
+    .list-wrap{
+        padding: 20px 30px;
+        justify-content: center;
+        .item{
+            width: 260px;
+            padding: 14px;
+            border-radius: 6px;
+            margin-left: 8px;
+            margin-right: 8px;
+            margin-bottom: 15px;
+            .img{
+                margin: 14px 0;
+                width: 100%;
+                height: 200px;
+            }
+            .time{
+                font-size: 14px;
+                justify-content: flex-start;
+                span{
+                    margin-right: 20px;
+                }
+            }
+        }
+        
+    }
+}
+</style>

+ 445 - 0
src/views/sheetList/timelineList.vue

@@ -0,0 +1,445 @@
+<script setup name="SheetETAList">
+import apiSheet from '@/api/sheet'
+import {ref,reactive,watch,computed} from 'vue'
+import { useRouter } from "vue-router";
+import useLocaleStore from "@/store/modules/i18n";
+import CatalogTree from './components/CatalogTree.vue';
+import CatalogItem from './components/CatalogItem.vue';
+import { useNoAuth } from '@/hooks/useNoAuth'
+import { showToast } from "vant";
+//激活的目录路径
+const catalogMenu = ref('')
+const router = useRouter()
+//表格列表
+const listState = reactive({
+    cid:0,
+    list:[],
+    page:1,
+    pageSize:15,
+    finished:false,
+    loading:false,
+    total:0,
+    IsShowMe:false
+})
+//是否展示目录列表
+const IsShowCatalog = ref(false)
+const localeStore = useLocaleStore()
+const temp = localeStore.locale === 'zhCn' ? 'zhCn' : 'en'; //根据当前i18n语言设置表格默认语言
+localeStore.SET_LOCALE(temp)
+
+watch(()=>listState.IsShowMe,()=>{
+    window.scrollTo({top:0})
+    listState.list=[]
+    listState.page=1
+    //设置数据已加载完毕,因为当滚动条不在顶部时,清空列表内容会触发onLoad
+    listState.finished = true 
+    //这个函数调用完成后,会把finished重置成正确的值
+    getSheetList()
+})
+
+getSheetList()
+//获取表格列表
+async function getSheetList(){
+    const {pageSize,cid,page,IsShowMe} = listState
+    const res = await apiSheet.sheetList({
+        PageSize: pageSize,
+        CurrentIndex: page,
+        Source: 2,
+        ExcelClassifyId: cid,
+        IsShowMe,
+    })
+    if(res.Ret!==200) return 
+    const arr = res.Data?res.Data.List:[]
+    listState.list = [...listState.list,...arr]
+    listState.total = res.Data?res.Data.Paging.Totals:0
+    listState.finished = res.Data?res.Data.Paging.IsEnd:true
+    listState.loading=false
+}
+
+//目录被点击 type:['top'一级目录,'node'二级目录,'item'三级目录]
+function catalogItemClick({item,type='node',parent={}}){
+    // console.log(item);
+    // console.log(type);
+    // console.log(parent);
+    
+    const topMenu = type==='top'?item.ExcelClassifyName:type==='node'?item.parentName:parent.parentName
+    const nodeMenu = type==='node'?item.ExcelClassifyName:type==='item'?parent.ExcelClassifyName:''
+    const itemMenu = type==='item'?item.ExcelClassifyName:''
+
+    catalogMenu.value = `${topMenu}${nodeMenu.length?`/${nodeMenu}`:''}${itemMenu.length?`/${itemMenu}`:''}`
+    
+    listState.cid = item.ExcelClassifyId
+    listState.list=[]
+    listState.page=1
+    getSheetList()
+}
+
+//展示目录列表
+function showCatalog(){
+    IsShowCatalog.value = true
+}
+
+//下拉加载
+function onLoad(){
+    if(IsShowCatalog.value) return
+    listState.page++
+    getSheetList()
+}
+
+//展开的目录
+const activeCatalogs = ref([])
+
+//目录列表
+const catalogNodes = ref([])
+const dataNodes = ref([])
+getCatalogList()
+//获取目录列表
+async function getCatalogList(){
+    const {IsShowMe} = listState
+    const res = await apiSheet.excelClassifyList({
+        Source: 2,
+        IsShowMe: IsShowMe
+    })
+    if(res.Ret!==200) return 
+    console.log(res);
+    dataNodes.value = res.Data?res.Data.AllNodes:[]||[]
+    catalogNodes.value = dataNodes.value.map(node=>{
+        if(node.Children){
+            node.Children = node.Children.map(child=>{
+                child.parentName = node.ExcelClassifyName //添加子分类时需要显示父级分类名称
+                if(child.Children){//改成三级目录了
+                    child.Children = child.Children.map(_child=>{
+                        _child.parentName = child.ExcelClassifyName
+                        return _child
+                    })
+                }
+                return child
+            })
+        }
+        return node
+    })
+}
+
+function showFileOpt(item){
+    console.log(item);
+}
+//跳转至图表详情页
+const goSheetDetail = (item)=>{
+    if(!item.HaveOperaAuth) return showToast(useNoAuth().sheet)
+    router.push({
+        path:'/shared/detail',
+        query:{
+            id:item.ExcelInfoId,
+            Source:item.Source,
+        }
+    })
+}
+
+function goSheetSearch(){
+    router.push({
+        path:'/shared/search',
+        query:{
+            Source: 2,
+        }
+    })
+}
+</script>
+
+<template>
+  <div class="excel-eta-list-wrap">
+    <div class="select-wrap">
+      <div class="search-box">
+        <van-search 
+          shape="round" 
+          readonly 
+          :placeholder="$t('shared_table.enter_table_name')"
+          style="flex:1;padding-left:0"
+          @click-input="goSheetSearch"
+        />
+        <div class="list-icon icon" @click="showCatalog">
+          <img src="@/assets/imgs/chartETA/list-icon.png" alt="">
+        </div>
+      </div>
+      <p style="font-weight: bold;word-break: break-all;margin-bottom: 5px;">{{ catalogMenu }}</p>
+      <div class="select-box">
+        <span>{{ $t('shared_table.tables_total', {totalNum: listState.total}) }}</span>
+        <span> <van-checkbox v-model="listState.IsShowMe">{{ $t('shared_table.just_look_mine') }}</van-checkbox></span>
+      </div>
+    </div>
+    <div class="excel-list-wrap">
+      <van-list
+          v-model:loading="listState.loading"
+          :finished="listState.finished"
+          :finished-text="listState.list.length > 0 ? $t('shared_table.no_more') : $t('shared_table.no_table')"
+          :immediate-check="false"
+          @load="onLoad"
+      >
+          <img v-if="listState.list.length==0&&listState.finished" class="list-empty-img" src="https://hzstatic.hzinsights.com/static/ETA_mobile/empty_img.png" alt="">
+          <ul class="excel-list">
+              <li 
+                  class="excel-list-item" 
+                  v-for="item in listState.list" 
+                  :key="item.ChartInfoId"
+                  @click="goSheetDetail(item)"
+              >
+                  <div class="title">{{item.ExcelName}}</div>
+                  <img class="img" :src="!item.HaveOperaAuth?useNoAuth().noAuthImg:item.ExcelImage" alt="">
+                  <div class="time">
+                      <span>{{item.CreateTime.slice(0,10)}}</span>
+                  </div>
+              </li>
+          </ul>
+      </van-list>
+    </div>
+     <!-- 目录列表 -->
+     <van-popup v-model:show="IsShowCatalog" position="right" class="catalog-list-wrap" style="height:100%">
+        <div class="catalog-list">
+            <div class="top sticky-part">
+                <h3>{{ $t('shared_table.choose_category') }}</h3>
+                <van-icon name="cross" @click.stop="IsShowCatalog=false"/>
+            </div>
+            <!-- 将目录改为三级 -->
+            <div class="list-box catalog-tree-wrap">
+                <van-collapse v-model="activeCatalogs" :border="false">
+                    <van-collapse-item 
+                        v-for="catalog in catalogNodes" :key="catalog.UniqueCode" :name="catalog.UniqueCode"
+                        @click.stop="catalogItemClick({item:catalog,type:'top'})">
+                        <template #title>
+                            <CatalogItem
+                                :node="catalog" 
+                                @showPopup="showFileOpt"/>
+                        </template>
+                        <CatalogTree 
+                            :catalog-nodes="catalog.Children"
+                            :showFileOpt="showFileOpt"
+                            :activeId="listState.cid"
+                            @handleCatalogItemClick="catalogItemClick"
+                        />
+                    </van-collapse-item>
+                </van-collapse>
+            </div>
+        </div>
+    </van-popup>
+  </div>
+</template>
+<style lang="scss">
+.excel-eta-list-wrap{
+    .catalog-list-wrap{
+        width: 65%;
+    }
+    .rename-wrap{
+        padding:48px;
+        input{
+            padding: 24px 32px;
+            border-radius: 12px;
+            background-color: #F6F6F6;
+            width: 100%;
+        }
+        .label{
+            color: #666666;
+            margin-bottom: 32px;
+            text-align: center;
+        }
+    }
+    @media screen and (min-width:$media-width){
+        .catalog-list-wrap{
+            width: 30%;
+        }
+        .move-popup{
+            width:375px;
+        }
+        .rename-wrap{
+            padding:24px;
+            input{
+                padding: 12px 16px;
+                border-radius: 6px;
+                background-color: #F6F6F6;
+                width: 100%;
+            }
+            .label{
+                margin-bottom: 16px;
+            }
+        }
+    }
+}
+</style>
+<style scoped lang="scss">
+.excel-eta-list-wrap{
+  .select-wrap{
+      padding: 0 30px 30px 30px;
+      position: sticky;
+      top:0;
+      background-color: #fff;
+      .search-box{
+          display: flex;
+          align-items: center;
+          .icon{
+              margin-left: 10px;
+              width: 70px;
+              height:70px;
+              background-color: #F2F3FF;
+              border-radius: 50%;
+              display: flex;
+              align-items: center;
+              justify-content: center;
+              img{
+                  width:45px;
+                  height:45px;
+              }
+          }
+      }
+      .select-box{
+          display: flex;
+          justify-content: space-between;
+          flex-wrap: wrap;
+      }
+  }
+  .excel-list-wrap{
+      //margin-top:15px;
+      padding: 0 30px; 
+      padding-bottom: 30px;
+      .excel-list{
+          display: flex;
+          justify-content: space-between;
+          flex-wrap: wrap;
+          gap: 30px 0;
+          .excel-list-item{
+              box-sizing: border-box;
+              width: 330px;
+              padding: 10px 14px;
+              border: 1px solid $border-color;
+              border-radius: 12px;
+              .title{
+                  min-height: 70px;
+                  overflow: hidden;
+                  -webkit-line-clamp: 2;
+                  text-overflow: ellipsis;
+                  display:-webkit-box !important;
+                  -webkit-box-orient:vertical;
+                  word-break: break-word;
+              }
+              .img{
+                      width: 100%;
+                      height: 220px;
+                      display: block;
+                      margin: 10px 0;
+                  }
+              .time{
+                  display: flex;
+                  justify-content: space-between;
+                  font-size: 28px;
+                  color: $font-grey_999;
+                  .tool-icon{
+                      width:30px;
+                      text-align: right;
+                      img{
+                          width:5px;
+                      }
+                  }
+              }
+              
+          }
+      }
+  }
+  .catalog-list{
+        box-sizing: border-box;
+        height: 100%;
+        .sticky-part{
+            position:sticky;
+            background-color: white;
+            z-index: 99;
+            left:0;
+            right:0;
+        }
+        .top{
+            display: flex;
+            justify-content: space-between;
+            border-bottom: 1px solid #DCDFE6;
+            padding: 0 15px;
+            align-items: center;
+            top:0;
+        }
+        .bottom{
+            display: flex;
+            padding:48px;
+            bottom:0;
+            span{
+                flex: 1;
+                background-color: #0052D9;
+                color: white;
+                text-align: center;
+                border-radius: 6px;
+                padding:16px;
+                box-sizing: border-box;
+            }
+        }
+    }
+  @media screen and (min-width:$media-width){
+    .select-wrap{
+        // top:60px;
+        padding:30px;
+        .search-box{
+            .icon{
+                margin-left: 10px;
+                width: 40px;
+                height:40px;
+                background-color: #F2F3FF;
+                border-radius: 50%;
+                img{
+                    width:25px;
+                    height:25px;
+                }
+            }
+        }
+        .select-box{
+            margin-top:20px;
+        }
+    }
+    .excel-list-wrap{
+        padding:0 30px;
+        .excel-list{
+            gap: 20px 4%;
+            justify-content:flex-start;
+            .excel-list-item{
+                box-sizing: border-box;
+                width: 22%;
+                padding: 10px 14px;
+                border: 1px solid $border-color;
+                border-radius: 6px;
+                .title{
+                    min-height: 36px;
+                }
+                .img{
+                    width: 100%;
+                    height: 120px;
+                    display: block;
+                    margin: 10px 0;
+                }
+                .time{
+                    font-size: 14px;
+                    .tool-icon>img{
+                        width:3px;
+                    }
+                }
+            }
+        }
+    }
+    .catalog-list{
+        .bottom{
+            display: flex;
+            padding:24px;
+            bottom:0;
+            span{
+                flex: 1;
+                background-color: #0052D9;
+                color: white;
+                text-align: center;
+                border-radius: 6px;
+                padding:8px;
+                box-sizing: border-box;
+            }
+        }
+    }
+  }
+}
+</style>

+ 197 - 24
src/views/tabbar/Home.vue

@@ -1,21 +1,22 @@
 <script setup>
 import {ref,computed} from 'vue'
 import { useRouter } from "vue-router";
-import {apiMenuList} from '@/api/user'
+import {apiMenuList, _apiLogin} from '@/api/user'
 import {getStaticImg} from '@/hooks/common'
 import {useCachedViewsStore} from '@/store/modules/cachedViews'
 import { showToast } from 'vant';
 import { useWindowSize } from '@vueuse/core'
 import {usePublicSettingStore} from '@/store/modules/publicSetting'
-import {_apiLogin} from '@/api/user'
-const publicSettingStore = usePublicSettingStore()
-
+import useLocaleStore from "@/store/modules/i18n";
 
+const publicSettingStore = usePublicSettingStore()
 const { width } = useWindowSize()
 const cachedViewsStore=useCachedViewsStore()
 cachedViewsStore.removeCaches(-1)
-
+const localeStore = useLocaleStore()
 const router=useRouter()
+const temp = localeStore.locale === 'zhCn' ? 'zh' : 'en'; //根据当前i18n语言设置表格默认语言
+const language_version=ref(temp)//语言版本 zh en 
 
 function goNext(path){
     if(!path){
@@ -30,12 +31,13 @@ if(!localStorage.getItem('token')){
     router.replace('/login')
 }
 
-const language_version=ref('zh')//语言版本 zh en 
 function languageVersionChange(){
     if(language_version.value==='zh'){
         language_version.value='en'
+        localeStore.SET_LOCALE('en')
     }else{
         language_version.value='zh'
+        localeStore.SET_LOCALE('zhCn')
     }
 }
 const topImg=computed(()=>{
@@ -56,11 +58,141 @@ const topImg=computed(()=>{
     return url
 })
 /**
- * name 显示的名称
+ * zhName 显示的中文版名称
+ * enName 显示的英文版名称
  * key 对应接口中的 name
  * level 说明该菜单在数据中的第几级查找
- * type 类型 zh中文 en英文
+ * type 类型 zh中文 en英文 ==> 默认中文点击切换
  */
+// const menuConfig=[
+//     // {
+//     //     zhName:'中文研报',
+//     //     zhDes:'研报一体化管理',
+//     //     key:'研报列表',
+//     //     type:'zh',
+//     //     level:2,
+//     //     path:'/report/list',
+//     //     icon:getStaticImg('tabbar/icon_report.png'),
+//     //     backgroundColor:'#FFFBF6',
+//     //     show:false
+//     // },
+//     // {
+//     //     zhName:'英文研报',
+//     //     zhDes:'支持共享协作编辑',
+//     //     enName:'English Research Report',
+//     //     enDes:'Integrated Research Report Management',
+//     //     key:'英文研报',
+//     //     type:'zh',
+//     //     level:2,
+//     //     path:'/reportEn/list',
+//     //     icon:getStaticImg('tabbar/icon_report.png'),
+//     //     backgroundColor:'#FFFBF6',
+//     //     show:false
+//     // },
+//     {
+//         zhName:'智能PPT',
+//         zhDes:"支持共享协作编辑",
+//         enName:'PPT Slides',
+//         enDes:'Support for collaborative editing',
+//         key:'智能ppt',
+//         type:'zh',
+//         level:1,
+//         path:'/ppt/index',
+//         icon:getStaticImg('tabbar/icon_PPT.png'),
+//         backgroundColor:'#FFF5F3',
+//         show:false
+//     },
+//     {
+//         zhName:'英文ppt',
+//         zhDes:'支持共享协作编辑',
+//         enName:'ppt in English',
+//         enDes:'Support for collaborative editing',
+//         key:'英文ppt',
+//         type:'zh',
+//         level:1,
+//         path:'/ppten/index',
+//         icon:getStaticImg('tabbar/icon_PPT.png'),
+//         backgroundColor:'#FFF5F3',
+//         show:false
+//     },
+//     // {
+//     //     zhName:'数据源',
+//     //     zhDes:'数据对接与整合',
+//     //     key:'数据源',
+//     //     type:'zh',
+//     //     level:1,
+//     //     path:'',
+//     //     icon:getStaticImg('tabbar/icon_dataSource.png'),
+//     //     backgroundColor:'#F8F9FF',
+//     //     show:false
+//     // },
+//     {
+//         zhName:'指标库',
+//         zhDes:'数据归类与分析',
+//         enName:'Indics',
+//         enDes:'Support for collaborative editing',
+//         key:'指标库',
+//         type:'zh',
+//         level:1,
+//         path:'/dataEDB/index',
+//         icon:getStaticImg('tabbar/icon_EDB.png'),
+//         backgroundColor:'#F5FAFF',
+//         show:false
+//     },
+//     // {
+//     //     zhName:'预测指标',
+//     //     zhDes:'模型预测趋势',
+//     //     key:'预测指标',
+//     //     type:'zh',
+//     //     level:1,
+//     //     path:'',
+//     //     icon:getStaticImg('tabbar/icon_preEDB.png'),
+//     //     backgroundColor:'#FFF5F3',
+//     //     show:false
+//     // },
+//     {
+//         zhName:'图库',
+//         zhDes:'数据可视化平台',
+//         enName:'Charts',
+//         enDes:'Support for collaborative editing',
+//         key:'图库',
+//         type:'zh',
+//         level:1,
+//         path:'/chartETA/list',
+//         icon:getStaticImg('tabbar/icon_chart.png'),
+//         backgroundColor:'#F8F9FF',
+//         show:false
+//     },
+//     {
+//         zhName:'我的图库',
+//         zhDes:'图表收藏',
+//         enName:'My Charts',
+//         enDes:'Support for collaborative editing',
+//         key:'我的投研',
+//         type:'zh',
+//         level:1,
+//         path:'/myETA/index',
+//         icon:getStaticImg('tabbar/icon_myChart.png'),
+//         backgroundColor:'#F5FAFF',
+//         show:false
+//     },
+//     {
+//         zhName:'表格',
+//         zhDes:'快速建立平衡表',
+//         enName:'Tables',
+//         enDes:'Quickly establish a balance sheet',
+//         key:'表格',
+//         type:'zh',
+//         level:1,
+//         path:'/sheetList/index',
+//         icon:getStaticImg('tabbar/icon_table.png'),
+//         backgroundColor:'#FFFBF6',
+//         show:false
+//     },
+// ]
+
+// 新增一个用于映射语言版本的函数
+
 const menuConfig=[
     {
         name:'战研中心研报',
@@ -88,7 +220,7 @@ const menuConfig=[
         name:'English Research Report',
         des:'Integrated Research Report Management',
         key:'英文研报',
-        type:'en',
+        type:'zh',
         level:2,
         path:'/reportEn/list',
         icon:getStaticImg('tabbar/icon_report.png'),
@@ -118,10 +250,10 @@ const menuConfig=[
         show:false
     },
     {
-        name:'PPT in English',
-        des:'Support for collaborative editing',
+        name:'英文ppt',
+        des:'支持共享协作编辑',
         key:'英文ppt',
-        type:'en',
+        type:'zh',
         level:1,
         path:'/ppten/index',
         icon:getStaticImg('tabbar/icon_PPT.png'),
@@ -183,18 +315,58 @@ const menuConfig=[
         backgroundColor:'#F5FAFF',
         show:false
     },
-    // {
-    //     name:'表格',
-    //     des:'快速建立平衡表',
-    //     key:'表格',
-    //     type:'zh',
-    //     level:1,
-    //     path:'',
-    //     icon:getStaticImg('tabbar/icon_table.png'),
-    //     backgroundColor:'#FFFBF6',
-    //     show:false
-    // },
-]
+    {
+        name:'表格',
+        des:'快速建立平衡表',
+        key:'表格',
+        type:'zh',
+        level:1,
+        path:'/sheetList/index',
+        icon:getStaticImg('tabbar/icon_table.png'),
+        backgroundColor:'#FFFBF6',
+        show:false
+    },
+    {
+        name:'Tables',
+        des:'Quickly establish a balance sheet',
+        key:'表格',
+        type:'en',
+        level:1,
+        path:'/sheetList/index',
+        icon:getStaticImg('tabbar/icon_table.png'),
+        backgroundColor:'#FFFBF6',
+        show:false
+    },
+
+function mapMenuToLanguage(item, language) {
+    if (language === 'zh') {
+        return { ...item, name: item.zhName, des: item.zhDes, type: 'zh' };
+    } else {
+        return { ...item, name: item.enName, des: item.enDes, type: 'en' };
+    }
+}
+
+// 过滤出有权限的菜单项
+const filteredMenuConfig = computed(() => {
+    return menuConfig.filter(item => {
+        // 根据菜单数据权限过滤
+        const hasPermission = resMenuList.value.some(f => {
+            if (item.level === 1) {
+                return f.name.trim() === item.key;
+            } else if (item.level === 2) {
+                return (f.children || []).some(s => s.name.trim() === item.key);
+            }
+            return false;
+        });
+        return hasPermission;
+    });
+});
+
+// 根据当前语言版本过滤并映射菜单项
+// const menuOpts = computed(() => {
+//     return filteredMenuConfig.value.map(item => mapMenuToLanguage(item, language_version.value));
+// });
+
 const menuOpts=computed(()=>{
     // 过滤中英文
     let arr=menuConfig.filter(item=>item.type===language_version.value)
@@ -224,6 +396,7 @@ const menuOpts=computed(()=>{
     return arr||[]
 })
 
+
 // 获取菜单权限数据
 const resMenuList=ref([])
 async function getMenuList(){

+ 20 - 16
src/views/tabbar/Index.vue

@@ -1,25 +1,29 @@
 <script setup>
 import {getStaticImg} from '@/hooks/common.js'
 import { useRouter } from 'vue-router'
+import {getCurrentInstance, computed} from 'vue'
+const { appContext : { config: { globalProperties } } } = getCurrentInstance();
 
 const router=useRouter()
 
-const tabbarList=[
-    {
-        name:"home",
-        icon:getStaticImg('tabbar/home.png'),
-        iconActive:getStaticImg('tabbar/home-s.png'),
-        text:'首页',
-        path:"/tabbar/home"
-    },
-    {
-        name:"user",
-        icon:getStaticImg('tabbar/user.png'),
-        iconActive:getStaticImg('tabbar/user-s.png'),
-        text:'我的',
-        path:"/tabbar/user"
-    }
-]
+const tabbarList = computed(() => {
+    return [
+        {
+            name:"home",
+            icon:getStaticImg('tabbar/home.png'),
+            iconActive:getStaticImg('tabbar/home-s.png'),
+            text: globalProperties.$t('tabbar.home'),
+            path:"/tabbar/home"
+        },
+        {
+            name:"user",
+            icon:getStaticImg('tabbar/user.png'),
+            iconActive:getStaticImg('tabbar/user-s.png'),
+            text:globalProperties.$t('tabbar.my'),
+            path:"/tabbar/user"
+        }
+    ]
+})
 
 function goPage(item){
     router.replace(item.path)

+ 4 - 4
src/views/tabbar/User.vue

@@ -36,15 +36,15 @@ getSetTitle()
         </div>
         <div class="info-list">
             <div class="item">
-                <span>公司名称:</span>
+                <span>{{ $t('my.company_name') }}:</span>
                 <span>{{ CompanyName }}</span>
             </div>
             <div class="item">
-                <span>所属部门:</span>
+                <span>{{ $t('my.department') }}:</span>
                 <span>{{userInfo.GroupName || userInfo.DepartmentName}}</span>
             </div>
             <div class="item">
-                <span>角色:</span>
+                <span>{{ $t('my.role') }}:</span>
                 <span>{{userInfo.RoleName}}</span>
             </div>
         </div>
@@ -54,7 +54,7 @@ getSetTitle()
             block 
             type="primary"
             @click="handleLoginOut"
-        >退出登录</van-button>
+        >{{ $t('my.log_out') }}</van-button>
     </div>
 </template>