耀极客论坛

 找回密码
 立即注册
查看: 725|回复: 0

vue+elementui+vuex+sessionStorage实现历史标签菜单的示例代码

[复制链接]

336

主题

318

帖子

22万

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
220555
发表于 2022-5-8 02:57:41 | 显示全部楼层 |阅读模式
  本文主要介绍了vue+elementui+vuex+sessionStorage实现历史标签菜单的示例代码,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
  一般是有左侧菜单后,然后要在页面上部分添加历史标签菜单需求。
借鉴其他项目,以及网上功能加以组合调整实现
按照标签实现方式步骤来(大致思路):

  1,写一个tagNav标签组件
  2,在路由文件上每个路由组件都添加meta属性 meta:{title:'组件中文名'}
  3,在store的mutation.js文件中写标签的添加/删除方法以及在方法中更新sessionStorage数据
  4,在主页面上添加组件以及router-view外层添加keep-alive组件,我这边是main.vue为登录后主要的主页面,其他菜单页面都基于该页面的路由上
  5,写一个mixins文件:beforeRouteLeave回调,因为貌似只在这回调中能找到子页面的缓存对象。在main.js中引入该文件并加入到vue.minxin()全局方法中,节省了在每个子页面都写改回调。
  6,左侧菜单也要添加路由监听,用于点标签菜单时左侧菜单能定位选中到对应的菜单选项
  7,如果点标签菜单是路由重定向菜单,则需要在触发重定向的路由页面添加路由监听获取meta.title的路由属性,然后在页面的created回调中循环存放标签的store数组,并把meta.title设置为当前重定向的标签名
开始代码说明
  写一个tagNav组件
  1. ‹style lang="less" scoped>
  2. @import "./base.less";
  3. .tags-nav {
  4.   display: flex;
  5.   align-items: stretch;
  6.   height: 40px;
  7.   padding: 2px 0;
  8.   background-color: @background-color;
  9.   a {
  10.     margin-right: 1px;
  11.     width: 24px;
  12.   }
  13.   a:hover {
  14.     color: @light-theme-color;
  15.   }
  16.   a:first-of-type {
  17.     margin-right: 4px;
  18.   }
  19.   a,
  20.   .dropdown-btn {
  21.     display: inline-block;
  22.     width:30px;
  23.     height: 36px;
  24.     color: @title-color;
  25.     background-color: @white;
  26.     text-align: center;
  27.     line-height: 36px;
  28.     position: relative;
  29.     z-index: 10;
  30.   }
  31.   .tags-wrapper {
  32.     flex: 1 1 auto;
  33.     position: relative;
  34.     .tags-wrapper-scroll {
  35.       position: absolute;
  36.       top: 0px;
  37.       left: 0;
  38.       z-index: 5;
  39.       height: 36px;
  40.       overflow: visible;
  41.       white-space: nowrap;
  42.       transition: all .3s ease-in-out;
  43.       .tag {
  44.         flex-shrink: 0;
  45.         cursor: pointer;
  46.       }
  47.     }
  48.   }
  49. }
  50. ‹/style>
  51. ‹template>
  52.   ‹div class="tags-nav">
  53.     ‹a href="javascript:void(0)" rel="external nofollow"  rel="external nofollow"  rel="external nofollow"  @click="handleScroll('left')">
  54.       ‹icon name="angle-left">‹/icon>
  55.     ‹/a>
  56.     ‹div class="tags-wrapper" ref="tagsWrapper">
  57.       ‹div class="tags-wrapper-scroll" ref="tagsWrapperScroll" :style="{ left: leftOffset + 'px' }">
  58.         ‹transition-group name="slide-fade">
  59.           ‹el-tag
  60.             class="tag slide-fade-item"
  61.             ref="tagsPageOpened"
  62.             v-for="(tag, index) in pageOpenedList"
  63.             :key="'tag_' + index"
  64.             :type="tag.selected ? '': 'info'"
  65.             :closable="tag.name!='basicDevice'"
  66.             :id="tag.name"
  67.             effect="dark"
  68.             :text="tag.name"
  69.             @close="closeTag(index, $event, tag.name)"
  70.             @click="tagSelected(index)"
  71.           >{{tag.title}}‹/el-tag>
  72.         ‹/transition-group>
  73.       ‹/div>
  74.     ‹/div>
  75.     ‹a href="javascript:void(0)" rel="external nofollow"  rel="external nofollow"  rel="external nofollow"  @click="handleScroll('right')">
  76.       ‹icon name="angle-right">‹/icon>
  77.     ‹/a>
  78.     ‹!-- ‹el-dropdown class="dropdown-btn" @command="closeTags">
  79.       ‹span class="el-dropdown-link">
  80.         ‹icon name="angle-down">‹/icon>
  81.       ‹/span>
  82.       ‹el-dropdown-menu slot="dropdown">
  83.         ‹el-dropdown-item command="closeOthers">关闭其他‹/el-dropdown-item>
  84.         ‹el-dropdown-item command="closeAll">关闭所有‹/el-dropdown-item>
  85.       ‹/el-dropdown-menu>
  86.     ‹/el-dropdown> -->
  87.     ‹!-- ‹Dropdown  placement="bottom-end" @on-click="closeTags">
  88.       ‹a href="javascript:void(0)" rel="external nofollow"  rel="external nofollow"  rel="external nofollow" >
  89.         ‹icon name="angle-down">‹/icon>
  90.       ‹/a>
  91.       ‹DropdownMenu slot="list">
  92.         ‹DropdownItem name="closeOthers">关闭其他‹/DropdownItem>
  93.         ‹DropdownItem name="closeAll">关闭所有‹/DropdownItem>
  94.       ‹/DropdownMenu>
  95.     ‹/Dropdown> -->
  96.   ‹/div>
  97. ‹/template>
  98. ‹script>
  99. export default {
  100.   data () {
  101.     return {
  102.       currentPageName: this.$route.name,
  103.       leftOffset: 0
  104.     }
  105.   },
  106.   props: {
  107.     pageOpenedList: {
  108.       type: Array
  109.     }
  110.   },
  111.   methods: {
  112.     closeTags (action) {
  113.       this.$emit('closeTags', action)
  114.       if (action === 'closeOthers') {
  115.         this.leftOffset = 0
  116.       }
  117.     },
  118.     closeTag (index, event, name) {
  119.       // 移除单个tag,且首页的tag无法移除
  120.       if (index !== 0) {
  121.         this.$emit('closeTags', index,name)
  122.       }
  123.       if (this.currentPageName !== name) {
  124.         this.leftOffset = Math.min(0, this.leftOffset + event.target.parentNode.offsetWidth)
  125.       }
  126.     },
  127.     tagSelected (index) {
  128.       this.$emit('tagSelected', index)
  129.     },
  130.     checkTagIsVisible (tag) {
  131.       let visible = {
  132.         isVisible: false,
  133.         position: 'left'
  134.       }
  135.       const leftDiffValue = tag.offsetLeft + this.leftOffset
  136.       if (leftDiffValue ‹ 0) {
  137.         return visible
  138.       }
  139.       const rightDiffValue = this.$refs.tagsWrapper.offsetWidth - this.leftOffset - tag.offsetWidth - tag.offsetLeft
  140.       if (leftDiffValue >= 0 && rightDiffValue >= 0) {
  141.         visible.isVisible = true
  142.       } else {
  143.         visible.position = 'right'
  144.       }
  145.       return visible
  146.     },
  147.     handleScroll (direaction) {
  148.       // 获取在可视区域临界的tag
  149.       let criticalTag = this.getCriticalTag(direaction)
  150.       switch (direaction) {
  151.         case 'left':
  152.           this.leftOffset = Math.min(this.$refs.tagsWrapper.offsetWidth - criticalTag.$el.offsetLeft, 0)
  153.           break
  154.         case 'right':
  155.           const diffValue1 = -(criticalTag.$el.offsetLeft + criticalTag.$el.clientWidth)
  156.           const diffvalue2 = -(this.$refs.tagsWrapperScroll.offsetWidth - this.$refs.tagsWrapper.offsetWidth)
  157.           this.leftOffset = Math.max(diffValue1, diffvalue2)
  158.           break
  159.         default:
  160.           break
  161.       }
  162.     },
  163.     getCriticalTag (direaction) {
  164.       let criticalTag
  165.       const refsTagList = this.$refs.tagsPageOpened
  166.       for (let tag of refsTagList) {
  167.         // 检查tag是否在可视区
  168.         if (this.checkTagIsVisible(tag.$el).isVisible) {
  169.           criticalTag = tag
  170.           if (direaction === 'left') {
  171.             break
  172.           }
  173.         }
  174.       }
  175.       return criticalTag
  176.     },
  177.     setTagsWrapperScrollPosition (tag) {
  178.       const visible = this.checkTagIsVisible(tag)
  179.       if (!visible.isVisible && visible.position === 'left') {
  180.         // 标签位于可视区域的左侧
  181.         this.leftOffset = -tag.offsetLeft
  182.       } else {
  183.         // 标签位于可视区域的右侧 or 可视区域
  184.         this.leftOffset = Math.min(0, -(tag.offsetWidth + tag.offsetLeft - this.$refs.tagsWrapper.offsetWidth + 4))
  185.       }
  186.     }
  187.   },
  188.   mounted () {
  189.     // 初始化当前打开页面的标签位置
  190.     const refsTag = this.$refs.tagsPageOpened
  191.     setTimeout(() => {
  192.       for (const tag of refsTag) {
  193.         if (tag.text === this.$route.name) {
  194.           const tagNode = tag.$el
  195.           this.setTagsWrapperScrollPosition(tagNode)
  196.           break
  197.         }
  198.       }
  199.     }, 1)
  200.   },
  201.   watch: {
  202.     $route (to) {
  203.       this.currentPageName = to.name
  204.       this.$nextTick(() => {
  205.         const refsTag = this.$refs.tagsPageOpened
  206.         for (const tag of refsTag) {
  207.           if (tag.text === this.$route.name) {
  208.             const tagNode = tag.$el
  209.             this.setTagsWrapperScrollPosition(tagNode)
  210.             break
  211.           }
  212.         }
  213.       })
  214.     }
  215.   }
  216. }
  217. ‹/script>
复制代码
  以及在同层目录下的less文件
  1. // color
  2. @theme1-color: #515a6e;
  3. @theme-color: #2d8cf0;
  4. @light-theme-color: #5cadff;
  5. @dark-theme-color: #2b85e4;
  6. @info-color: #2db7f5;
  7. @success-color: #19be6b;
  8. @warning-color: #ff9900;
  9. @error-color: #ed4014;
  10. @title-color: #17233d;
  11. @content-color: #515a6e;
  12. @sub-color: #808695;
  13. @disabled-color: #c5c8ce;
  14. @border-color: #dcdee2;
  15. @divider-color: #e8eaec;
  16. @background-color: #f8f8f9;
  17. @white: white;
  18. // 间距
  19. @padding: 16px;
  20. // 默认样式
  21. * {
  22.   box-sizing: border-box;
  23. }
  24. a {
  25.   color: @theme-color;
  26. }
  27. a:hover {
  28.   color: @light-theme-color;
  29. }
  30. .dark-a {
  31.   color: @title-color;
  32. }
  33. // 清除float
  34. .clear-float::after {
  35.   display: block;
  36.   clear: both;
  37.   content: "";
  38.   visibility: hidden;
  39.   height: 0;
  40. }
  41. // 动画
  42. .slide-fade-item {
  43.   transition: all 0.1s ease-in-out;
  44.   display: inline-block;
  45. }
  46. .slide-fade-enter, .slide-fade-leave-to
  47. /* .list-complete-leave-active for below version 2.1.8 */ {
  48.   opacity: 0;
  49.   transform: translateX(-10px);
  50. }
  51. // 滚动条样式
  52. .menu-scrollbar::-webkit-scrollbar,
  53. .common-scrollbar::-webkit-scrollbar {
  54.   /*滚动条整体样式*/
  55.   width: 11px;
  56.   /*高宽分别对应横竖滚动条的尺寸*/
  57.   height: 1px;
  58. }
  59. // 滚动条样式1
  60. .menu-scrollbar::-webkit-scrollbar-thumb {
  61.   /*滚动条里面小方块*/
  62.   border-radius: 2px;
  63.   box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.2);
  64.   background: @sub-color;
  65. }
  66. // 滚动条样式2
  67. .common-scrollbar::-webkit-scrollbar-thumb {
  68.   /*滚动条里面小方块*/
  69.   border-radius: 2px;
  70.   box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.2);
  71.   background: @border-color;
  72. }
复制代码
  -----------说明:由于这关闭所有跟关闭其他的功能只是单纯清除tagNav的标签并没关系到路由,所以获取不到清除的页面路由离开事件,只能先关闭这两功能------------
  在路由文件route.js中为每个路由添加meta属性
  1. {
  2.         path: 'auditManage',
  3.         name: 'auditManage',
  4.         meta:{title:'审计管理'},
  5.         component: function (resolve) {
  6.           require(['../page/sysConfig/audit/auditManageMain.vue'], resolve);
  7.         },
  8.         redirect: '/main/auditManage/trendStatistics',
  9.         children: [{
  10.           path: 'trendStatistics',
  11.           name: 'trendStatistics',
  12.           meta:{title:'趋势审计'},
  13.           component: function (resolve) {
  14.             require(['../page/sysConfig/audit/auditTrendStatisticsList.vue'], resolve);
  15.           }
  16.         }, {
  17.           path: 'search',
  18.           name: 'search',
  19.           meta:{title:'审计查询'},
  20.           component: function (resolve) {
  21.             require(['../page/sysConfig/audit/auditSearchList.vue'], resolve);
  22.           }
  23.         },
复制代码
  ------说明:这是路由片段包含设置的meta属性方式内容,以及路由重定向-------
  在store的mutation.js中写标签的添加/删除以及更新sessionStorage方法
  1. [setPageOpenedList](state,params = null){
  2.         // 设置前先读取本地保存的打开列表数据
  3.       state.pageOpenedList = sessionStorage.pageOpenedList
  4.       ? JSON.parse(sessionStorage.pageOpenedList) : [{
  5.         title: '基础设施',
  6.         name: 'basicDevice',
  7.         selected: true
  8.       }]
  9.     if (!params) {
  10.       return
  11.     }
  12.     if (params.index === -1) {
  13.       // 新打开一个页面
  14.       state.pageOpenedList.push({
  15.         title: params.route.meta.title,
  16.         name: params.route.name,
  17.         selected: false
  18.       })
  19.       params.index = state.pageOpenedList.length - 1
  20.     }
  21.     // 更新selected值
  22.     for (let i = 0; i ‹ state.pageOpenedList.length; i++) {
  23.       if (params.index === i) {
  24.         state.pageOpenedList[i].selected = true
  25.       } else {
  26.         state.pageOpenedList[i].selected = false
  27.       }
  28.     }
  29.     // 更新下本地新的打开页面列表数据
  30.     sessionStorage.pageOpenedList = JSON.stringify(state.pageOpenedList)
  31.     },
  32.     // 移除PageOpenedList
  33.     [removePageOpenedList] (state, params = null) {
  34.         if (!params) {
  35.           return
  36.         }
  37.         if (typeof params.action === 'number') {
  38.           state.pageOpenedList.splice(params.action, 1)
  39.         } else {
  40.         //进这里是已经选择删除所有tab,赋值一个初始选中的tab
  41.           state.pageOpenedList = [{
  42.             title: '基础设施',
  43.             name: 'basicDevice',
  44.             selected: true
  45.           }]
  46.           //如果是删除其他的则再加上当前路由下的tab
  47.           if (params.action === 'closeOthers' && params.route.name !== 'basicDevice') {
  48.             state.pageOpenedList[0].selected = false
  49.             state.pageOpenedList.push({
  50.               title: params.route.meta.title,
  51.               name: params.route.name,
  52.               selected: true
  53.             })
  54.           }
  55.         }
  56.         // 更新下本地新的打开页面列表数据
  57.         sessionStorage.pageOpenedList = JSON.stringify(state.pageOpenedList)
  58.       },
复制代码
  ------说明:由于有的项目中store写法不太一样,这里的[setPageOpenedList]以及[removePageOpenedList]是mutation-type.js中定义的常量---------
  在主页面main.vue中添加标签组件、keep-alive组件、组件的选中/删除方法,监听路由的变化,计算存放的标签list
  1. ‹div class="a-tag">
  2.             ‹tag-nav :pageOpenedList="pageOpenedList" ref="tagNavRef" @closeTags="closeTags"
  3.                      @tagSelected="tagSelected">‹/tag-nav>
  4.           ‹/div>
  5.           ‹div class="a-product">
  6.             ‹div class="loading">
  7.               ‹keep-alive :max="5">
  8.                 ‹router-view>‹/router-view>
  9.               ‹/keep-alive>
  10.             ‹/div>
  11.           ‹/div>
复制代码
  1. // 导航标签方法
  2.       closeTags (action,elId) {
  3.         let isRemoveSelected;
  4.         if (typeof action === 'number') { //移除单个
  5.           let elEvent = new Event('click');
  6.           document.getElementById(elId).dispatchEvent(elEvent);
  7.           //移除不管是不是当前的标签都默认设置为是当前的标签
  8.           for (let i = 0; i ‹ this.$store.state.pageOpenedList.length; i++) {
  9.             if (action === i) {
  10.               this.$store.state.pageOpenedList[i].selected = true
  11.             } else {
  12.               this.$store.state.pageOpenedList[i].selected = false
  13.             }
  14.           }
  15.           //并且是当前的标签页
  16.           isRemoveSelected = this.$store.state.pageOpenedList[action].selected
  17.         }
  18.         this.$store.commit('removePageOpenedList', { route: this.$route, action })
  19.         if (isRemoveSelected) {
  20.           // 移除单个tag,导航到最后一个tag的页面
  21.           this.$router.push({
  22.             name: this.$store.state.pageOpenedList[this.$store.state.pageOpenedList.length - 1].name
  23.           })
  24.         } else if (action === 'closeAll') {
  25.           this.$router.push('/main/basicDevice');
  26.         }
  27.       },
  28.       tagSelected (index) {
  29.         if (this.$store.state.pageOpenedList[index].name !== this.$route.name) {
  30.           this.$router.push({
  31.             name: this.$store.state.pageOpenedList[index].name
  32.           })
  33.         }
  34.       },
复制代码
  1. computed: {
  2.       pageOpenedList () {
  3.         return this.$store.getters.getPageOpenedList
  4.       },
  5.     },
复制代码
  1. watch: {
  2.       $route (to) {
  3.         // 路由变化,更新PageOpenedList
  4.         let index = this.$util.indexOfCurrentPageOpened(to.name, this.$store.state.pageOpenedList)
  5.         this.$store.commit('setPageOpenedList', { route: to, index })
  6.       },
  7.    }   
复制代码
  1. // 定位新打开的页面在pageOpenedList中位置
  2.     indexOfCurrentPageOpened(name, pageOpenedList) {
  3.     for (let index = 0; index ‹ pageOpenedList.length; index++) {
  4.       if (pageOpenedList[index].name === name) {
  5.         return index
  6.       }
  7.     }
  8.     return -1
  9.   },
复制代码
  ------说明:组件的导入就不贴了,缓存限制最多为5个标签,怕开太多导致浏览器内存爆炸卡死。-------
  写一个minxins文件:路由离开回调,并在main.js中全局混入
  1. /**
  2. * 用于历史标签菜单
  3. */
  4. export default {
  5.   
  6.     beforeRouteLeave(to, from, next) {
  7.         console.log("mixins-beforeRouteLeave:",from);
  8.         let flag = true
  9.         this.$store.state.pageOpenedList.forEach(e => {    // pageOpenedList存储打开的tabs的组件路由
  10.           if(from.name == e.name) {
  11.             flag = false
  12.           }
  13.         })
  14.         if(flag && this.$vnode.parent && this.$vnode.parent.componentInstance.cache) {
  15.         //   let key = this.$vnode.key   // 当前关闭的组件名
  16.           var key = (this.$vnode.key == null || this.$vnode.key == undefined) ? this.$vnode.componentOptions.Ctor.cid + (this.$vnode.componentOptions.tag ? `::${this.$vnode.componentOptions.tag}` : '') : this.$vnode.key;
  17.           let cache = this.$vnode.parent.componentInstance.cache  // 缓存的组件
  18.           let keys = this.$vnode.parent.componentInstance.keys  // 缓存的组件名
  19.           if(cache[key] != null) {
  20.             delete cache[key]
  21.             let index = keys.indexOf(key)
  22.             if(index > -1) {
  23.               keys.splice(index, 1)
  24.             }
  25.           }
  26.         }
  27.         next()
  28.       }
  29.   }
复制代码
  1. //引入混合方法
  2. import beforeLeave from './mixins/beforeLeave'
  3. Vue.mixin(beforeLeave)
复制代码
  ------说明:这里主要就是为在每个离开的路由页面都回调这个离开方法,并且判断当前离开的路由是切换到其他标签菜单还是关闭当前菜单标签,如果是关闭则删除对应的缓存---------
  左侧菜单也要添加路由监听,以便切换标签菜单时左侧菜单能正确的跳转并选中标签
  1. handleSelect(index,indexPath){
  2.             this.active = index;
  3.         },
复制代码
  1. watch:{
  2.         $route (to) {
  3.             console.log("accordion=============",to,to.path);
  4.             //进if判断说明是被重定向的地址,则使用重定向父级菜单路径去跳转,让父级页面自己去重定向
  5.             if(to.matched && to.matched[to.matched.length-1].parent.redirect != undefined){
  6.                 this.handleSelect(to.matched[to.matched.length-1].parent.path,null);
  7.             }else {
  8.                 this.handleSelect(to.path,null);
  9.             }
  10.         }
  11.     },
复制代码
  -----说明:第一个方法是el-menu选择菜单时触发的方法,watch路由时获取matched属性值去判断------
  如果切换的标签是重定向的路由菜单则需要在重定向页面初始化时获取存放标签数组的title设置对应的radio标签(我这边项目的重定向页面基本都是用radio去切换),如果是再切换后的就是缓存的了,那就要从路由上获取title
  1. ‹div class="tabsBox">
  2.           ‹el-radio-group class="radioStyle" v-for="item in menus" :key="item.route" v-model="activeName" @change="checkItem(item.route)">
  3.             ‹el-radio-button :label="item.text" @blur.native="goPath(item.route)">‹/el-radio-button>
  4.           ‹/el-radio-group>
  5.         ‹/div>
复制代码
  1. data(){
  2.       return {
  3.         activeName:'参与人',
  4.         menus:[
  5.           {route:"partyManageReport",text:"参与人"},
  6.           {route:"staffManageReport",text:"员工"},
  7.           {route:"partyAccountManageReport",text:"主账号"},
  8.           {route:"partyAccountGroupManageReport",text:"主账号组"},
  9.           {route:"partySubAccountManageReport",text:"从账号(web资产)"},
  10.           {route:"partySubAccountManageD",text:"从账号(基础设施)"}
  11.           ]
  12.       }
  13.     },
  14.     watch:{
  15.         $route (to){
  16.             this.activeName = to.meta.title;
  17.         }
  18.     },
  19. created(){
  20.       this.$store.state.pageOpenedList.forEach((item)=>{
  21.           if(item.selected){
  22.           this.activeName = item.title;
  23.           }
  24.       })
  25.      }   
复制代码
  总结:由于在关闭标签方法中有添加点击事件,把关闭是其他标签时设置为当前标签并关闭,这样才能获取到关闭标签对应的路由离开回调去清除缓存。但这样就会导致关闭其他标签后当前选中的标签页会跳到最后一个标签去。如果要求不是很高,这样也能勉强接受吧。不过感觉不是那么好。有没好的方式关闭其他标签时既能清除对应的缓存当前选中的标签tab又不会跳转到其他的标签选项上…求优化思路。

  另关闭所有跟关闭其他选项,还不知道怎么去清除被关闭的标签缓存,如上面所说获取不到关闭时触发路由离开方法,只是单纯重新设置标签数组而已。


  第一个是基础标签,所以把关闭按钮去了。
  到此这篇关于vue+elementui+vuex+sessionStorage实现历史标签菜单的示例代码的文章就介绍到这了,更多相关vue 历史标签菜单内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!


回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

Archiver|手机版|小黑屋|耀极客论坛 ( 粤ICP备2022052845号-2 )|网站地图

GMT+8, 2022-12-10 03:09 , Processed in 0.072570 second(s), 20 queries .

Powered by Discuz! X3.4

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表