component_ryl_v3 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,572 @@
1
+ <template>
2
+ <ElContainer class="handHeaderContent">
3
+ <!-- 左右两边分 -->
4
+ <div class="ieBro flex flex_row" v-if="isIE">
5
+ <span>
6
+ 当前浏览器版本过低,使用效果可能欠佳,建议前往下载
7
+ <a href="http://chrome.wlxb49.cn/" target="_blank" class="color"
8
+ >谷歌浏览器</a
9
+ ></span
10
+ >
11
+ <span>
12
+ <a href="javascript:;" @click="closeIETips">x</a>
13
+ </span>
14
+ </div>
15
+ <!-- el-aside -->
16
+ <ElAside class="aside" :class="{'hideMenu': hideMenu}" :width="completeWidth" :style="`backgroundColor:${menuColor.backgroundColor}`">
17
+ <!-- 侧边栏系统名称展示区域 -->
18
+ <slot name="headElseTitle"></slot>
19
+ <ElMenu
20
+ class="el-menu-vertical-demo init-el-menu"
21
+ :text-color="menuColor.color"
22
+ :active-text-color="menuColor.activeColor"
23
+ :background-color="menuColor.backgroundColor"
24
+ :default-active="localDefaultActive"
25
+ router
26
+ :default-openeds="defaultOpen"
27
+ :collapse="hideMenu"
28
+ :collapse-transition="false"
29
+ >
30
+ <sidebar-item v-for="menu in newMenuList" :key="menu.path" :item="menu" :hideMenu="hideMenu" />
31
+ </ElMenu>
32
+ </ElAside>
33
+ <ElMain :class="['main main_container',{ scroll: addClass, scrollX: isScrollX}]" ref="mainRef">
34
+ <ElHeader class="header flex flex_row inner_header">
35
+ <div class="imLeft flex flex_row">
36
+ <template v-if="collapse">
37
+ <div class="colSpan flex flex_row" @click="toggleHide">
38
+ <i :class="['hide', { rotate: hideMenu }]"></i>
39
+ </div>
40
+ </template>
41
+ <!-- 头部信息补充展示区域 -- 左边 -->
42
+ <slot name="headElseInfo"></slot>
43
+ </div>
44
+ <ul class="saasInfo flex flex_row">
45
+ <!-- 头部信息补充展示区域 -- 右边 -->
46
+ <slot name="headElseInfoRight"></slot>
47
+ <li class="img flex flex_row">
48
+ <img src="@/assets/img/portrait.jpg" alt />
49
+ <i class="el-icon-arrow-down"></i>
50
+ <!-- 下拉框 -->
51
+ <div class="dropDown">
52
+ <em class="san"></em>
53
+ <div class="user flex flex_row">
54
+ <img src="@/assets/img/portrait.jpg" alt />
55
+ <span>{{ userInfo.name }}</span>
56
+ </div>
57
+ <!-- 头部信息操作展示区域 -->
58
+ <slot name="headElseHand"></slot>
59
+ <div class="exit" @click="outSystem">退出</div>
60
+ </div>
61
+ </li>
62
+ </ul>
63
+ </ElHeader>
64
+ <div class="content_mar">
65
+ <div class="content inner_content">
66
+ <!-- 内容展示区域 -->
67
+ <slot name="content"></slot>
68
+ </div>
69
+ <div class="footer flex flex_col inner_footer" v-html="userInfo.copyRight" v-if="showFooter"></div>
70
+ </div>
71
+ </ElMain>
72
+ </ElContainer>
73
+ </template>
74
+
75
+ <script setup>
76
+ /**
77
+ * 头部布局组件
78
+ * 功能:包含侧边栏菜单、头部导航、用户信息和内容区域的完整布局
79
+ */
80
+
81
+ // 导入Vue响应式API
82
+ import { ref, computed, onMounted, nextTick } from 'vue'
83
+ // 导入Element Plus组件
84
+ import { ElContainer, ElAside, ElMenu, ElMain, ElHeader } from 'element-plus'
85
+ // 导入子组件
86
+ import sidebarItem from './sidebarItem.vue'
87
+
88
+ /**
89
+ * 组件属性定义
90
+ * @property {Object} menuList - 导航列表(包含name系统名称和list菜单列表)
91
+ * @property {Object} menuColor - 菜单栏颜色配置(color/activeColor/backgroundColor)
92
+ * @property {string} defaultActive - 默认选中的菜单路径
93
+ * @property {boolean} collapse - 是否启用折叠功能
94
+ * @property {number} width - 侧边栏宽度
95
+ * @property {Object} userInfo - 用户信息
96
+ * @property {boolean} showDefaltOpen - 是否默认展开所有菜单项
97
+ */
98
+ const props = defineProps({
99
+ menuList: {
100
+ type: Object,
101
+ default: () => ({})
102
+ },
103
+ menuColor: {
104
+ type: Object,
105
+ default: () => ({
106
+ color: "#C0C5C7",
107
+ activeColor: "#fff",
108
+ backgroundColor: "#1e222d",
109
+ })
110
+ },
111
+ defaultActive: {
112
+ type: String,
113
+ default: "/"
114
+ },
115
+ collapse: {
116
+ type: Boolean,
117
+ default: false
118
+ },
119
+ width: {
120
+ type: Number,
121
+ default: 180
122
+ },
123
+ userInfo: {
124
+ type: Object,
125
+ default: () => ({})
126
+ },
127
+ showDefaltOpen: {
128
+ type: Boolean,
129
+ default: true
130
+ }
131
+ })
132
+
133
+ /**
134
+ * 事件定义
135
+ * @event toggleHide - 折叠/展开事件
136
+ * @event exitSystem - 退出系统事件
137
+ */
138
+ const emit = defineEmits(['toggleHide', 'exitSystem'])
139
+
140
+ // 响应式数据定义
141
+ const addClass = ref(false) // 动态添加是否有滚动
142
+ const isScrollX = ref(false) // 判断是否出现横向滚动条
143
+ const newMenuList = ref([]) // 获取新的菜单
144
+ const defaultOpen = ref([]) // 默认打开的菜单
145
+ const isIE = ref(false) // 是否是ie浏览器
146
+ const hideMenu = ref(false) // 是否隐藏菜单
147
+ const showFooter = ref(false) // 效果展示优化
148
+ const minWidth = ref(0) // 内容模块的默认最小宽度
149
+ const localDefaultActive = ref(props.defaultActive) // 本地默认选中菜单
150
+ const mainRef = ref(null) // main容器引用
151
+
152
+ /**
153
+ * 收起和展开时 aside 的宽度
154
+ * @returns {string} 宽度值
155
+ */
156
+ const completeWidth = computed(() => {
157
+ return hideMenu.value ? '64px' : `${props.width}px`
158
+ })
159
+
160
+ /**
161
+ * 主模块默认宽度
162
+ * @type {number}
163
+ */
164
+ let mainDefalutWidth = document.body.clientWidth - props.width
165
+
166
+ /**
167
+ * 函数防抖
168
+ * @param {Function} fn - 需要防抖的函数
169
+ * @param {number} delay - 延迟时间(默认200ms)
170
+ * @returns {Function} 防抖后的函数
171
+ */
172
+ const debounce = (fn, delay = 200) => {
173
+ let timer = null
174
+ return function () {
175
+ clearTimeout(timer)
176
+ timer = setTimeout(() => {
177
+ fn.apply(this, arguments)
178
+ }, delay)
179
+ }
180
+ }
181
+
182
+ /**
183
+ * 过滤隐藏的菜单项并递归处理子菜单
184
+ * @param {Array} datas - 菜单数据
185
+ * @returns {Array} 过滤后的菜单列表
186
+ */
187
+ const getAllName = (datas) => {
188
+ return datas.filter(item => !item.hidden).map(item => {
189
+ item = Object.assign({}, item)
190
+ if (item.children) {
191
+ item.children = getAllName(item.children)
192
+ }
193
+ return item
194
+ })
195
+ }
196
+
197
+ /**
198
+ * 处理传入的导航菜单
199
+ */
200
+ const getMenu = () => {
201
+ newMenuList.value = getAllName(props.menuList.list || [])
202
+ defaultOpen.value = []
203
+ getOpen()
204
+ }
205
+
206
+ /**
207
+ * 折叠/展开菜单
208
+ */
209
+ const toggleHide = () => {
210
+ hideMenu.value = !hideMenu.value
211
+ defaultOpen.value = []
212
+ getOpen()
213
+ emit('toggleHide', hideMenu.value)
214
+ }
215
+
216
+ /**
217
+ * 获取默认打开的菜单项
218
+ * @param {Array} datas - 菜单数据(默认为newMenuList)
219
+ */
220
+ const getOpen = (datas = newMenuList.value) => {
221
+ if (props.showDefaltOpen) {
222
+ for (const i in datas) {
223
+ defaultOpen.value.push(datas[i].path)
224
+ if (datas[i].children) {
225
+ getOpen(datas[i].children)
226
+ }
227
+ }
228
+ }
229
+ }
230
+
231
+ /**
232
+ * 等待图片加载完成后获取主容器高度
233
+ * @param {string} content - HTML内容
234
+ */
235
+ const loadImg = (content) => {
236
+ let mulitImg = []
237
+ if (content.includes("<img")) {
238
+ content.replace(
239
+ /<img [^>]*src=['"]([^'"]+)[^>]*>/gi,
240
+ (matcherStr, capture) => {
241
+ mulitImg.push(capture)
242
+ }
243
+ )
244
+ let promiseAll = [], img = []
245
+ for (let i = 0; i < mulitImg.length; i++) {
246
+ promiseAll[i] = new Promise((resolve) => {
247
+ img[i] = new Image()
248
+ img[i].src = mulitImg[i]
249
+ img[i].onload = function () {
250
+ resolve(img[i])
251
+ }
252
+ })
253
+ }
254
+ Promise.all(promiseAll).then(() => {
255
+ getMainHeight()
256
+ })
257
+ } else {
258
+ getMainHeight()
259
+ }
260
+ }
261
+
262
+ /**
263
+ * 获取内容模块的滚动条状态
264
+ * @param {boolean} flag - 是否跳过滚动条置顶
265
+ */
266
+ const getMainHeight = (flag) => {
267
+ addClass.value = false
268
+ setTimeout(() => {
269
+ const mainContainer = document.querySelector(".main_container")
270
+ if (mainContainer) {
271
+ const innerContent = document.querySelector(".main_container .inner_content")
272
+ const contentDiv = document.querySelector(".main_container .inner_content>div")
273
+
274
+ if (innerContent && contentDiv) {
275
+ const mainHeight = innerContent.offsetHeight
276
+ const conHeight = contentDiv.offsetHeight
277
+
278
+ addClass.value = conHeight > mainHeight
279
+ showFooter.value = true
280
+
281
+ if (!flag && addClass.value && mainRef.value) {
282
+ mainRef.value.scrollTop = 0
283
+ }
284
+ }
285
+ }
286
+ }, 0)
287
+ }
288
+
289
+ /**
290
+ * 设置内容模块最小宽度
291
+ * @param {number} width - 当前窗口宽度
292
+ */
293
+ const setMinWidth = (width) => {
294
+ isScrollX.value = width < minWidth.value
295
+
296
+ const innerHeader = document.querySelector(".main_container .inner_header")
297
+ const contentMar = document.querySelector(".main_container .content_mar")
298
+
299
+ if (isScrollX.value) {
300
+ if (innerHeader) {
301
+ innerHeader.setAttribute("style", `min-width:${minWidth.value}px;`)
302
+ }
303
+ if (contentMar) {
304
+ contentMar.setAttribute("style", `min-width:${minWidth.value - 36}px;`)
305
+ }
306
+ } else {
307
+ if (innerHeader) {
308
+ innerHeader.removeAttribute("style")
309
+ }
310
+ if (contentMar) {
311
+ contentMar.removeAttribute("style")
312
+ }
313
+ }
314
+ }
315
+
316
+ /**
317
+ * 设置菜单选中状态
318
+ * @param {string} path - 菜单路径
319
+ */
320
+ const setMenuActive = (path) => {
321
+ localDefaultActive.value = ''
322
+ nextTick(() => {
323
+ localDefaultActive.value = path
324
+ })
325
+ }
326
+
327
+ /**
328
+ * 判断是否在 content inner_content 类名上添加 normal 类名
329
+ */
330
+ const isAddNormal = () => {
331
+ const innerContent = document.querySelector(".main_container .inner_content")
332
+ if (innerContent) {
333
+ if (document.getElementById('setNormal')) {
334
+ innerContent.classList.add('normal')
335
+ } else {
336
+ innerContent.classList.remove('normal')
337
+ }
338
+ }
339
+ }
340
+
341
+ /**
342
+ * 退出系统
343
+ */
344
+ const outSystem = () => {
345
+ emit('exitSystem')
346
+ }
347
+
348
+ /**
349
+ * 判断是否是 IE 浏览器
350
+ */
351
+ const browser = () => {
352
+ isIE.value = !!window.ActiveXObject || "ActiveXObject" in window
353
+ }
354
+
355
+ /**
356
+ * 关闭 IE 提示
357
+ */
358
+ const closeIETips = () => {
359
+ isIE.value = false
360
+ }
361
+
362
+ /**
363
+ * 组件挂载时初始化
364
+ */
365
+ onMounted(() => {
366
+ const windowWidth = window.screen.availWidth
367
+ minWidth.value = windowWidth > 1800 ? (1800 - props.width) : (windowWidth - props.width)
368
+
369
+ browser()
370
+
371
+ nextTick(() => {
372
+ getMenu()
373
+ setMinWidth(mainDefalutWidth)
374
+ // 监听窗口大小变化
375
+ window.addEventListener('resize', debounce(() => {
376
+ mainDefalutWidth = document.body.clientWidth - props.width
377
+ setMinWidth(mainDefalutWidth)
378
+ setTimeout(() => {
379
+ getMainHeight()
380
+ }, 0)
381
+ }))
382
+ })
383
+ })
384
+
385
+ /**
386
+ * 暴露方法给父组件调用
387
+ */
388
+ defineExpose({
389
+ setMenuActive,
390
+ isAddNormal
391
+ })
392
+ </script>
393
+
394
+ <style lang="scss">
395
+ .handHeaderContent {
396
+ position: relative;
397
+ z-index: 999;
398
+ height: 100vh;
399
+ .el-container.is-vertical{
400
+ position: relative;
401
+ }
402
+ .inner_header {
403
+ padding: 0;
404
+ background: #fff;
405
+ justify-content: space-between;
406
+ height: 50px !important;
407
+ .colSpan {
408
+ width: 60px;
409
+ background-color: transparent;
410
+ justify-content: center;
411
+ align-items: center;
412
+ .hide {
413
+ width: 16px;
414
+ height: 16px;
415
+ background: url("@/assets/img/hide.png") no-repeat;
416
+ background-size: contain;
417
+ cursor: pointer;
418
+ transition: all 0.2s;
419
+ &.rotate {
420
+ transform: rotateZ(180deg);
421
+ }
422
+ }
423
+ }
424
+ .saasInfo {
425
+ .img {
426
+ align-items: center;
427
+ justify-content: center;
428
+ height: 50px;
429
+ margin-right: 20px;
430
+ cursor: pointer;
431
+ &:hover .dropDown {
432
+ display: block;
433
+ }
434
+ img {
435
+ width: 33px;
436
+ height: 33px;
437
+ border-radius: 50%;
438
+ margin-right: 5px;
439
+ border: 1px solid #eff2f5;
440
+ }
441
+ i {
442
+ font-size: 14px;
443
+ font-weight: bold;
444
+ color: #5a5a5a;
445
+ }
446
+ }
447
+ }
448
+ }
449
+ .dropDown {
450
+ position: absolute;
451
+ right: 10px;
452
+ top: 50px;
453
+ width: 170px;
454
+ background: #ffffff;
455
+ box-shadow: 0px 0px 4px 0px rgba(25, 25, 25, 0.1);
456
+ border-radius: 2px;
457
+ display: none;
458
+ z-index: 999;
459
+ .san {
460
+ display: inline-block;
461
+ width: 10px;
462
+ height: 10px;
463
+ border: 1px solid #fff;
464
+ border-top-style: none;
465
+ border-right-style: none;
466
+ transform: rotate(135deg);
467
+ position: absolute;
468
+ top: -4px;
469
+ right: 38px;
470
+ background: #fff;
471
+ }
472
+ .user {
473
+ padding: 11px;
474
+ align-items: center;
475
+ border-bottom: 1px solid #f5f5f5;
476
+ span {
477
+ line-height: normal;
478
+ font-size: 14px;
479
+ }
480
+ }
481
+ .exit {
482
+ font-size: 14px;
483
+ line-height: 39px;
484
+ height: 39px;
485
+ border-top: 1px solid #EFF2F5;
486
+ text-align: center;
487
+ padding: 0;
488
+ color: #5a5a5a;
489
+ }
490
+ }
491
+ .aside {
492
+ height: 100%;
493
+ transition: all .2s;
494
+ overflow: overlay;
495
+ .init-el-menu{
496
+ padding-bottom: 38px;
497
+ }
498
+ &.hideMenu{
499
+ .el-tooltip{
500
+ text-align: center;
501
+ }
502
+ .el-submenu__title{
503
+ text-align: center !important;
504
+ }
505
+ i.iconfont{
506
+ margin-right: 0;
507
+ }
508
+ .deldom,.el-submenu__icon-arrow{
509
+ display: none;
510
+ }
511
+ }
512
+ i.iconfont {
513
+ font-size: 16px;
514
+ display: inline-block;
515
+ width: 18px;
516
+ margin-right: 4px;
517
+ color: #747C81;
518
+ }
519
+ }
520
+ .main {
521
+ padding: 0;
522
+ background-color: #eff2f5;
523
+ &.scroll {
524
+ overflow-y: scroll;
525
+ .inner_content {
526
+ height: auto;
527
+ &.normal {
528
+ height: auto;
529
+ }
530
+ }
531
+ }
532
+ &.scrollX {
533
+ overflow-x: scroll;
534
+ }
535
+ .content_mar{
536
+ padding: 18px;
537
+ }
538
+ .inner_content {
539
+ background-color: #fff;
540
+ height: calc(100vh - 50px - 36px);
541
+ border-radius: 4px;
542
+ &.normal {
543
+ background-color: transparent;
544
+ }
545
+ }
546
+ }
547
+ .inner_footer {
548
+ color: #666666;
549
+ text-align: center;
550
+ padding-top: 18px;
551
+ justify-content: center;
552
+ }
553
+ }
554
+
555
+ @keyframes changeRun {
556
+ 0% {
557
+ transform: translateY(0) scale(.9);
558
+ }
559
+ 25% {
560
+ transform: translateY(-6px) scale(.9);
561
+ }
562
+ 50% {
563
+ transform: translateY(0) scale(.9);
564
+ }
565
+ 75% {
566
+ transform: translateY(-2px) scale(.9);
567
+ }
568
+ 100% {
569
+ transform: translateY(0) scale(.9);
570
+ }
571
+ }
572
+ </style>