index.vue 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267
  1. <template>
  2. <div :class="{content: !fullScreen}">
  3. <!-- 全屏 -->
  4. <template v-if="fullScreen">
  5. <keep-alive>
  6. <router-view v-if="$route.meta.keepAlive" />
  7. </keep-alive>
  8. <router-view v-if="!$route.meta.keepAlive" />
  9. </template>
  10. <template v-else>
  11. <navbar :left="navigationWidth" />
  12. <div class="body" ref="body">
  13. <side
  14. class="stick"
  15. :is-mini="navigationWidth === 60"
  16. :style="`height: calc(100% - ${this.top}px)`"
  17. />
  18. <div class="view d-flex flex-column" :style="`padding-left: ${navigationWidth}px;`">
  19. <div class="breadcrumbs_sticky">
  20. <div class=" d-flex align-center justify-space-between">
  21. <v-breadcrumbs :items="title" elevation="3">
  22. <template v-slot:item="{ item }">
  23. <span class="text" :class="{active: !item.disabled}" @click="toPath(item)">{{ item.text }}</span>
  24. </template>
  25. </v-breadcrumbs>
  26. <v-btn v-if="$route.meta.editModules" text color="primary" class="mr-5" @click="handleClickModules">{{ $t('common.moduleEditing') }}</v-btn>
  27. </div>
  28. <v-divider></v-divider>
  29. <!-- <div v-if="list.length && showChip">
  30. <scroll-tabs class="px-3" :items="list" @handleClick="$event => handleToHref(`${$event.name}${$event.id}`)"></scroll-tabs>
  31. <v-divider></v-divider>
  32. </div> -->
  33. </div>
  34. <div class="box pa-3 d-flex" :style="`width: calc(100vw - ${navigationWidth}px); position: relative`">
  35. <!-- 避免容器因为文字影响 设置字体大小为0 -->
  36. <div class="box-content" style="font-size: 0;">
  37. <keep-alive>
  38. <router-view v-if="$route.meta.keepAlive" />
  39. </keep-alive>
  40. <router-view v-if="!$route.meta.keepAlive" />
  41. </div>
  42. </div>
  43. </div>
  44. </div>
  45. </template>
  46. <!-- 智能助手 -->
  47. <assistant-iframe
  48. v-if="assistantWebhookUrl"
  49. title="数据智能助手"
  50. subTitle="我可以帮助您管理元数据和数据订单。"
  51. inputPlaceholder="请输入您的需求,例如:查看所有元数据"
  52. :iframe-url="assistantWebhookUrl"
  53. />
  54. </div>
  55. </template>
  56. <script>
  57. // import ScrollTabs from '@/components/ScrollTabs'
  58. import Navbar from '@/components/Header/navbar.vue'
  59. import Side from '@/components/Side/index.vue'
  60. import { mapGetters } from 'vuex'
  61. // import cloneDeep from 'lodash/cloneDeep'
  62. import AssistantIframe from '@/components/AssistantIframe'
  63. export default {
  64. name: 'layoutIndex',
  65. components: { Navbar, Side, AssistantIframe },
  66. data () {
  67. return {
  68. width: 230,
  69. selectTabs: 0,
  70. list: [],
  71. scrollTop: null,
  72. showChip: true,
  73. top: 0
  74. }
  75. },
  76. computed: {
  77. ...mapGetters(['lang', 'userInfo']),
  78. assistantWebhookUrl () {
  79. return window?.g?.VUE_APP_N8N_CHAT_URL ?? process.env.VUE_APP_N8N_CHAT_URL ?? ''
  80. },
  81. navigationWidth () {
  82. return this.$vuetify.breakpoint.xs ? 60 : this.width
  83. },
  84. fullScreen () {
  85. return this.$route.meta.fullScreen
  86. },
  87. title () {
  88. const route = this.$route.matched.map((item, index) => {
  89. if (item.path === this.$route.matched[index - 1]?.path) return false
  90. const text = this.lang === 'zh' ? item.meta.title : item.meta.enName
  91. const obj = {
  92. text,
  93. to: item.path
  94. }
  95. if (!item.meta?.allowClick) {
  96. obj.disabled = true
  97. obj.link = false
  98. }
  99. return obj
  100. }).filter(e => e)
  101. route[route.length - 1].disabled = true
  102. route[route.length - 1].link = true
  103. // route.unshift({ text: '首页', to: '/home' })
  104. return route
  105. }
  106. },
  107. watch: {
  108. $route: {
  109. handler: function (val) {
  110. this.selectTabs = 0
  111. this.list = []
  112. const roles = val.meta.roles.filter(e => e.active === '1').sort((a, b) => a.sort - b.sort)
  113. if (!['/home'].includes(val.path) && roles && roles.length > 1) {
  114. this.list = roles.map(e => ({ label: e.label, name: e.name, id: e.id }))
  115. }
  116. },
  117. immediate: true
  118. }
  119. },
  120. created () {
  121. this.$eventBus.$on('scroll-top', (num) => {
  122. // console.log('scroll-top', num)
  123. this.scrollTop = num
  124. })
  125. this.$eventBus.$on('show-chip', (show) => {
  126. // console.log('show-chip', show)
  127. this.showChip = show
  128. })
  129. // this.getMessageUnRead()
  130. // if (this.timer) {
  131. // clearInterval(this.timer)
  132. // }
  133. // this.timer = setInterval(() => {
  134. // this.getMessageUnRead()
  135. // }, 10000)
  136. },
  137. mounted () {
  138. if (!this.$refs.body) return
  139. this.top = this.$refs.body.offsetTop
  140. window.addEventListener('scroll', this.handleScroll)
  141. },
  142. beforeDestroy () {
  143. if (!this.$refs.body) return
  144. // 在组件销毁前移除滚动事件监听
  145. window.removeEventListener('scroll', this.handleScroll)
  146. },
  147. methods: {
  148. handleScroll () {
  149. if (!this.list.length) return
  150. this.list.forEach((section, index) => {
  151. const selector = document.getElementById(section.name + section.id)
  152. if (!selector) return
  153. if (window.scrollY - selector.offsetTop > -400) {
  154. this.selectTabs = index
  155. }
  156. })
  157. },
  158. handleToHref (id) {
  159. // 获取需要跳转到的元素
  160. const selector = document.getElementById(id)
  161. if (!selector) return
  162. // 使用scrollIntoView方法滚动到指定元素
  163. // selector.scrollIntoView({ behavior: 'smooth', block: 'center' })
  164. window.scrollTo({
  165. top: selector.offsetTop - (this.scrollTop ? this.scrollTop : 188), // 滚动到距离顶部280的位置
  166. behavior: 'smooth' // 平滑滚动
  167. })
  168. },
  169. handleToggle () {
  170. this.width = 300 - this.width
  171. },
  172. handleClick () {
  173. const path = this.$route.path
  174. this.$router.push(`${path}/edit`)
  175. },
  176. toPath ({ disabled, to }) {
  177. if (disabled) {
  178. event.preventDefault()
  179. return
  180. }
  181. this.$router.push(to)
  182. },
  183. handleClickModules () {
  184. this.$emit('$HANDLE_EDIT_MODULES')
  185. }
  186. // 查询是否存在未读消息
  187. // async getMessageUnRead () {
  188. // try {
  189. // const { data } = await getMessageList({ readState: 0, userId: this.userInfo.id })
  190. // this.bellMessage = data.total > 0
  191. // } catch (error) {
  192. // this.$snackbar.error(error)
  193. // }
  194. // }
  195. }
  196. }
  197. </script>
  198. <style lang="scss" scoped>
  199. .stick {
  200. position: fixed;
  201. overflow: auto;
  202. background: #FFF;
  203. transition: .1s;
  204. /* 针对 WebKit 浏览器(如 Chrome 和 Safari)使用伪元素 */
  205. &::-webkit-scrollbar {
  206. width: 0;
  207. }
  208. overflow: -moz-scrollbars-none; /* 针对 Firefox */
  209. // -ms-overflow-style: none; /* 针对 IE 10+ */
  210. scrollbar-width: none; /* 针对 Firefox */
  211. -webkit-overflow-scrolling: touch; /* 针对 iOS Safari */
  212. }
  213. .breadcrumbs_sticky {
  214. background: #FFF;
  215. z-index: var(--zIndex-breadcrumbs);
  216. border-left: 1px solid #eaecef;
  217. }
  218. .content {
  219. height: 100%;
  220. display: flex;
  221. flex-direction: column;
  222. flex: 1;
  223. .body {
  224. position: relative;
  225. overflow: hidden;
  226. height: 0;
  227. flex: 1;
  228. display: flex;
  229. .view {
  230. background: #f0f2f5;
  231. box-sizing: border-box;
  232. .box {
  233. height: 0;
  234. flex: 1;
  235. // overflow-x: hidden;
  236. &-content {
  237. width: 100%;
  238. min-height: 100%;
  239. position: relative;
  240. overflow-y: auto;
  241. overflow-x: hidden;
  242. }
  243. }
  244. }
  245. }
  246. }
  247. .text {
  248. color: #999;
  249. font-size: 14px;
  250. &.active {
  251. color: #1867c0;
  252. cursor: pointer;
  253. }
  254. }
  255. // .item {
  256. // padding: 0 8px;
  257. // cursor: auto;
  258. // &:focus {
  259. // color: inherit !important;
  260. // }
  261. // }
  262. </style>