@lambo-design/pro-layout 1.0.0-beta.384 → 1.0.0-beta.388

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lambo-design/pro-layout",
3
- "version": "1.0.0-beta.384",
3
+ "version": "1.0.0-beta.388",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "author": "lambo",
@@ -10,8 +10,8 @@
10
10
  "registry": "https://registry.npmjs.org/"
11
11
  },
12
12
  "devDependencies": {
13
- "@lambo-design/core": "^4.7.1-beta.162",
14
- "@lambo-design/shared": "^1.0.0-beta.264"
13
+ "@lambo-design/core": "^4.7.1-beta.167",
14
+ "@lambo-design/shared": "^1.0.0-beta.280"
15
15
  },
16
16
  "scripts": {
17
17
  "release-pro-layout": "pnpm release-beta && git push --follow-tags && pnpm re-publish",
@@ -12,7 +12,9 @@
12
12
  </div>
13
13
  <div class="nav-box" v-show="!systemInfo || !systemInfo.navType || systemInfo.navType == 'dropdown'">
14
14
  <slot name="pro-layout-nav">
15
- <LamboProNav></LamboProNav>
15
+ <LamboProNav
16
+ :available-width="availableWidth"
17
+ ></LamboProNav>
16
18
  </slot>
17
19
  </div>
18
20
  <div class="tools-box">
@@ -25,8 +27,11 @@
25
27
  </template>
26
28
  </LamboProTools>
27
29
  </div>
28
- <div v-show="systemInfo && systemInfo.navType && systemInfo.navType == 'slide'">
29
- <LamboProNavSilde></LamboProNavSilde>
30
+ <div class="nav-slide-container" v-show="systemInfo && systemInfo.navType && systemInfo.navType == 'slide'" ref="slideContainer">
31
+ <LamboProNavSilde
32
+ :available-width="availableWidth"
33
+ @width-change="handleSlideWidthChange"
34
+ ></LamboProNavSilde>
30
35
  </div>
31
36
  </div>
32
37
  </template>
@@ -38,6 +43,7 @@ import LamboProNav from './pro-layout-nav'
38
43
  import LamboProNavSilde from './pro-layout-nav/index-slide'
39
44
  import LamboProTools from './pro-layout-tools'
40
45
  import Bus from "@lambo-design/shared/utils/bus";
46
+
41
47
  export default {
42
48
  name: "pro-layout-header",
43
49
  props:{
@@ -53,6 +59,8 @@ export default {
53
59
  data(){
54
60
  return {
55
61
  systemInfo: {},
62
+ availableWidth: 0,
63
+ resizeTimer: null,
56
64
  }
57
65
  },
58
66
  components: {
@@ -72,16 +80,92 @@ export default {
72
80
  Bus.$on('system-info',(data)=>{
73
81
  this.initSystem(data)
74
82
  });
83
+
84
+ // 监听窗口大小变化
85
+ window.addEventListener('resize', this.handleResize);
75
86
  },
76
87
  destroyListener(){
77
- Bus.$off('system-info')
88
+ Bus.$off('system-info');
89
+ window.removeEventListener('resize', this.handleResize);
78
90
  },
79
91
  initSystem(data){
80
92
  if (data) {
81
93
  this.systemInfo = data;
94
+ // 当系统信息加载完成后,如果是slide模式,计算可用宽度
95
+ this.$nextTick(() => {
96
+ this.calculateAvailableWidth();
97
+ });
98
+ }
99
+ },
100
+ // 计算slide导航可用宽度
101
+ calculateAvailableWidth() {
102
+ if (!this.systemInfo) {
103
+ return;
104
+ }
105
+
106
+ try {
107
+ // 获取页面总宽度
108
+ const docWidth = document.getElementById("app")?.clientWidth || window.innerWidth;
109
+
110
+ // 获取各个组件的宽度
111
+ const triggerBoxWidth = this.getTriggerBoxWidth();
112
+ const logoBoxWidth = this.getLogoBoxWidth();
113
+ const toolsBoxWidth = this.getToolsBoxWidth();
114
+
115
+ // 预留一些空间给箭头按钮和边距
116
+ const reservedWidth = 150;
117
+
118
+ // 计算可用宽度
119
+ this.availableWidth = docWidth - triggerBoxWidth - logoBoxWidth - toolsBoxWidth - reservedWidth;
120
+
121
+ // 确保最小宽度
122
+ this.availableWidth = Math.max(this.availableWidth, 200);
123
+
124
+ } catch (error) {
125
+ console.warn('Error calculating available width:', error);
126
+ this.availableWidth = 800; // 默认值
82
127
  }
128
+ },
129
+
130
+ // 获取trigger组件宽度
131
+ getTriggerBoxWidth() {
132
+ const triggerBox = document.querySelector('.trigger-box');
133
+ return triggerBox ? triggerBox.clientWidth : 0;
134
+ },
135
+
136
+ // 获取logo组件宽度
137
+ getLogoBoxWidth() {
138
+ const logoBox = document.querySelector('.logo-box');
139
+ return logoBox ? logoBox.clientWidth : 0;
140
+ },
141
+
142
+ // 获取tools组件宽度
143
+ getToolsBoxWidth() {
144
+ const toolsBox = document.querySelector('.tools-box');
145
+ return toolsBox ? toolsBox.clientWidth : 0;
146
+ },
147
+
148
+ // 处理窗口大小变化
149
+ handleResize() {
150
+ // 防抖处理
151
+ clearTimeout(this.resizeTimer);
152
+ this.resizeTimer = setTimeout(() => {
153
+ this.calculateAvailableWidth();
154
+ }, 150);
155
+ },
156
+
157
+ // 处理slide组件宽度变化
158
+ handleSlideWidthChange(data) {
159
+ // console.log('Slide width change:', data);
160
+ // 可以在这里处理slide组件宽度变化的逻辑
83
161
  }
84
162
  },
163
+ mounted() {
164
+ // 组件挂载后计算一次宽度
165
+ this.$nextTick(() => {
166
+ this.calculateAvailableWidth();
167
+ });
168
+ },
85
169
  created(){
86
170
  this.initListener();
87
171
  },
@@ -105,6 +189,11 @@ export default {
105
189
  .nav-box{
106
190
  float: left;
107
191
  }
192
+ .nav-slide-container{
193
+ float: left;
194
+ flex: 1;
195
+ overflow: hidden;
196
+ }
108
197
  .tools-box{
109
198
  float: right;
110
199
  }
@@ -1,12 +1,17 @@
1
1
  <template>
2
- <div class="menu-list" >
2
+ <div class="menu-list" ref="menuList">
3
3
  <ul class="top-menu" :style="layoutSize === 'default' ? {height: '64px'} : {height: '50px'}" ref="topNav">
4
- <li class="top-menu-item" v-show="pointer <= index && index < pointer + topMenuNum && flag" :class="{ 'active': activeName === item.appId }" v-for="(item,index) in topMenList" :key="item.appId" @click="selectApp(item.appId)">
5
- <div class="menu-item" :style="layoutSize === 'default' ? {paddingTop: '10px'} : {paddingTop: '2px'}">
6
- <p class="menu-icon" v-show="systemInfo.navLogo==='1'"><Icon :type="item.icon" :size="20"></Icon></p>
7
- <p class="menu-txt" :title="item.name">{{ item.name }}</p>
8
- </div>
9
- </li>
4
+ <template v-for="(item,index) in topMenList" >
5
+ <li class="top-menu-item"
6
+ :class="{ 'active': activeName === item.appId }"
7
+ :key="item.appId"
8
+ @click="selectApp(item.appId)">
9
+ <div class="menu-item" :style="layoutSize === 'default' ? {paddingTop: '10px'} : {paddingTop: '2px'}">
10
+ <p class="menu-icon" v-show="systemInfo.navLogo==='1'"><Icon :type="item.icon" :size="20"></Icon></p>
11
+ <p class="menu-txt" :title="item.name">{{ item.name }}</p>
12
+ </div>
13
+ </li>
14
+ </template>
10
15
  </ul>
11
16
  </div>
12
17
  </template>
@@ -24,13 +29,16 @@ export default {
24
29
  topMenListNum: {
25
30
  type: Number,
26
31
  default: 0
32
+ },
33
+ availableWidth: {
34
+ type: Number,
35
+ default: 800
27
36
  }
28
37
  },
29
38
  data() {
30
39
  return {
31
40
  systemInfo:{},
32
41
  pointer:0,
33
- flag:true,
34
42
  arrowFlag: true,
35
43
  acceptAppId: '',
36
44
  navList: [],
@@ -39,9 +47,12 @@ export default {
39
47
  otherList: [],
40
48
  activeName: '',
41
49
  topMenuNum: 7,
50
+ displayMenuNum: 7, // 实际显示的菜单数量
42
51
  lastTopMenuNum:-1,
43
52
  originMenuList: [],
44
- layoutSize:"default"
53
+ layoutSize:"default",
54
+ // menuItemWidths: [], // 存储每个菜单项的宽度
55
+ resizeObserver: null,
45
56
  }
46
57
  },
47
58
  methods: {
@@ -79,18 +90,13 @@ export default {
79
90
  }
80
91
  this.navList = data
81
92
  this.lastTopMenuNum = this.topMenuNum
82
- if (data.length > this.topMenuNum) {
83
- let navList = deepCopy(data)
84
- this.topMenList = navList
85
- this.$emit('topMen-list', this.topMenList);
86
- this.$emit('topMen-num', this.topMenuNum);
87
- this.$emit('topMen-true', true);
88
- // this.topMenList = navList.splice(0, this.topMenuNum)
89
- // this.otherList = navList
90
- } else {
91
- this.topMenList = this.navList
92
- this.$emit('topMen-true', false);
93
- }
93
+
94
+ this.topMenList = data
95
+ this.$emit('topMen-list', this.topMenList);
96
+
97
+ // 计算可以显示的菜单数量
98
+ this.calculateDisplayMenuNum();
99
+
94
100
  if (this.topMenList.length > 0) {
95
101
  let appId = this.topMenList[0].appId
96
102
  for (let i = 0; i < this.topMenList.length; i++) {
@@ -104,6 +110,94 @@ export default {
104
110
  this.selectApp(appId)
105
111
  }
106
112
  },
113
+ // 计算可以显示的菜单数量
114
+ calculateDisplayMenuNum() {
115
+ this.$nextTick(() => {
116
+ if (!this.$refs.menuList) return;
117
+
118
+ // 获取单个菜单项的平均宽度
119
+ // const menuItems = this.$refs.menuList.querySelectorAll('.top-menu-item');
120
+ // if (menuItems.length === 0) return;
121
+
122
+ let totalWidth = 0;
123
+ let itemWidths = [];
124
+
125
+ // 临时显示所有菜单项以测量宽度
126
+ // menuItems.forEach((item, index) => {
127
+ // item.style.display = 'block';
128
+ // const width = item.offsetWidth;
129
+ // itemWidths.push(width);
130
+ // totalWidth += width;
131
+ // });
132
+
133
+ // this.menuItemWidths = itemWidths;
134
+
135
+ // 计算可以显示的菜单数量
136
+ let displayCount = 0;
137
+ let accumulatedWidth = 0;
138
+ const reservedWidth = 40; // 预留一些边距
139
+
140
+ this.navList.forEach((item, index) => {
141
+ const estimatedWidth = this.estimateMenuItemWidth(item);
142
+ if (accumulatedWidth + estimatedWidth <= this.availableWidth - reservedWidth) {
143
+ accumulatedWidth += estimatedWidth;
144
+ displayCount++;
145
+ }
146
+ })
147
+ //
148
+ // for (let i = 0; i < itemWidths.length; i++) {
149
+ // if (accumulatedWidth + itemWidths[i] <= this.availableWidth - reservedWidth) {
150
+ // accumulatedWidth += itemWidths[i];
151
+ // displayCount++;
152
+ // } else {
153
+ // break;
154
+ // }
155
+ // }
156
+
157
+ // 确保至少显示一个菜单项
158
+ let displayMenuNum = Math.max(1, displayCount);
159
+
160
+ // 如果配置的数量更小则依赖配置
161
+ if(this.topMenuNum < displayMenuNum){
162
+ this.displayMenuNum = this.topMenuNum
163
+ }else{
164
+ this.displayMenuNum = displayMenuNum
165
+ }
166
+
167
+ // this.displayMenuNum = Math.max(1, displayCount);
168
+ // 判断是否需要显示箭头
169
+ const needArrows = this.topMenList.length > this.displayMenuNum;
170
+ this.$emit('topMen-num', this.displayMenuNum);
171
+ this.$emit('topMen-true', needArrows);
172
+
173
+ // 向父组件报告实际宽度
174
+ this.$emit('menu-width-change', totalWidth);
175
+
176
+ //重新调整样式
177
+ menuItems.forEach((item, index) => {
178
+ if(!(this.pointer <= index && index < (this.pointer + this.displayMenuNum))){
179
+ item.style.display = 'none';
180
+ }else{
181
+ item.style.display = 'block'
182
+ }
183
+ });
184
+ });
185
+ },
186
+ estimateMenuItemWidth(item) {
187
+ // 基础宽度:内边距 + 边框
188
+ let baseWidth = 30;
189
+
190
+ // 图标宽度
191
+ if (item.icon && this.systemInfo.navLogo === '1') {
192
+ baseWidth += 30;
193
+ }
194
+
195
+ // 文字宽度估算(每个字符约14px)
196
+
197
+ const textWidth = (item.name || '').length * 14;
198
+ return baseWidth + textWidth;
199
+ },
200
+
107
201
  selectApp(appId) {
108
202
  if (appId) {
109
203
  this.activeName = appId
@@ -115,18 +209,60 @@ export default {
115
209
  if (appId) {
116
210
  this.activeName = appId
117
211
  }
212
+ },
213
+ // 监听菜单容器大小变化
214
+ observeResize() {
215
+ if (window.ResizeObserver) {
216
+ this.resizeObserver = new ResizeObserver(() => {
217
+ this.calculateDisplayMenuNum();
218
+ this.controlVisable()
219
+ });
220
+
221
+ if (this.$refs.menuList) {
222
+ this.resizeObserver.observe(this.$refs.menuList);
223
+ }
224
+ }
225
+ },
226
+ controlVisable(){
227
+ //重新调整样式
228
+ const menuItems = this.$refs.menuList.querySelectorAll('.top-menu-item');
229
+ menuItems.forEach((item, index) => {
230
+ if(!(this.pointer <= index && index < (this.pointer + this.displayMenuNum))){
231
+ item.style.display = 'none';
232
+ }else{
233
+ item.style.display = 'block';
234
+ }
235
+ });
118
236
  }
119
237
  },
120
238
  watch: {
121
239
  acceptInt(val){
122
240
  this.pointer = val;
241
+ this.controlVisable()
242
+ },
243
+ availableWidth(newVal, oldVal) {
244
+ if (newVal !== oldVal) {
245
+ this.calculateDisplayMenuNum();
246
+ }
247
+ },
248
+ topMenList() {
249
+ this.calculateDisplayMenuNum();
123
250
  }
124
251
  },
252
+ mounted() {
253
+ this.calculateDisplayMenuNum();
254
+ this.$nextTick(() => {
255
+ this.observeResize();
256
+ });
257
+ },
125
258
  created() {
126
259
  this.initListener()
127
260
  },
128
261
  beforeDestroy() {
129
- this.destroyListener()
262
+ this.destroyListener();
263
+ if (this.resizeObserver) {
264
+ this.resizeObserver.disconnect();
265
+ }
130
266
  }
131
267
  }
132
268
  </script>
@@ -136,58 +272,54 @@ export default {
136
272
  .menu-list {
137
273
  height: 100%;
138
274
  line-height: 24px;
139
- //color: #ffffff;
140
275
  cursor: pointer;
141
276
  font-size: 16px;
142
277
  margin-left: 15px;
278
+ overflow: hidden;
279
+
143
280
  .top-menu {
144
281
  overflow: hidden;
282
+ white-space: nowrap;
283
+
145
284
  .top-menu-item {
146
285
  padding-left: 15px;
147
286
  padding-right: 15px;
148
287
  position: relative;
149
288
  height: 100%;
150
- //color: #FFFFFF;
151
289
  list-style: none;
152
290
  float: left;
291
+ transition: all 0.3s ease;
292
+
153
293
  &:hover {
154
- //background: transparent;
155
294
  .menu-item {
156
- //color: #fff;
295
+ // hover styles
157
296
  }
158
297
  }
159
298
  &.active {
160
- //background-image: linear-gradient(to bottom, rgba(255, 255, 255, 0.5), rgba(255, 255, 255, 0)) !important;
161
- //border-bottom: 2px solid var(--primary-color-tint-5, @_primary-color-tint-5);
162
299
  .menu-item {
163
- //color: #fff;
164
- //width: 135px;
300
+ // active styles
165
301
  }
166
302
  }
167
303
  .menu-item {
168
304
  display: flex;
169
305
  margin-top: 10px;
170
306
  text-align: center;
171
- //padding-right: 10px;
172
- //width: 135px;
173
-
174
307
  }
175
308
  .menu-icon {
176
309
  height: 20px;
177
310
  line-height: 20px;
178
311
  text-align: center;
179
312
  margin-right: 10px;
180
- //margin-left: 10px;
181
313
  margin-top: 3px;
182
314
  }
183
315
  .menu-txt {
184
316
  text-align: center;
185
317
  font-size: 14px;
186
318
  line-height: 2;
187
- //width: 135px;
188
319
  white-space: nowrap;
189
320
  overflow: hidden;
190
321
  text-overflow: ellipsis;
322
+ max-width: 120px; // 限制最大宽度
191
323
  }
192
324
  }
193
325
  }
@@ -1,10 +1,17 @@
1
1
  <template>
2
- <div class="pro-layout-nav-slide-wrapper">
3
- <div class="nav-box-slide">
4
- <LamboProNavSlideMenu :accept-int="pointer" @topMen-list="handleCustomEvent" @topMen-num="topMen" @topMen-true="topMenTrue"></LamboProNavSlideMenu>
2
+ <div class="pro-layout-nav-slide-wrapper" ref="slideWrapper">
3
+ <div class="nav-box-slide" ref="navBox" :style="{ width: availableWidth + 'px' }">
4
+ <LamboProNavSlideMenu
5
+ :accept-int="pointer"
6
+ :available-width="availableWidth"
7
+ @topMen-list="handleCustomEvent"
8
+ @topMen-num="topMen"
9
+ @topMen-true="topMenTrue"
10
+ @menu-width-change="handleMenuWidthChange"
11
+ ></LamboProNavSlideMenu>
5
12
  </div>
6
13
  <!--slide按钮-->
7
- <div class="tools-box-slide">
14
+ <div class="tools-box-slide" v-if="shouldShowArrows">
8
15
  <div style="margin-right: 50px;">
9
16
  <Icon
10
17
  class="more-menu"
@@ -12,12 +19,14 @@
12
19
  type="md-arrow-dropleft-circle"
13
20
  v-if="arrowFlag"
14
21
  @click="moveMenu('left')"
22
+ :style="{ opacity: canMoveLeft ? 1 : 0.3 }"
15
23
  />
16
24
  <Icon
17
25
  type="md-arrow-dropright-circle"
18
26
  class="more-menu"
19
27
  v-if="arrowFlag"
20
28
  @click="moveMenu('right')"
29
+ :style="{ opacity: canMoveRight ? 1 : 0.3 }"
21
30
  />
22
31
  </div>
23
32
  </div>
@@ -26,13 +35,29 @@
26
35
 
27
36
  <script>
28
37
  import LamboProNavSlideMenu from './components/pro-layout-nav-slide-menu'
38
+
29
39
  export default {
40
+ props: {
41
+ availableWidth: {
42
+ type: Number,
43
+ default: 800
44
+ },
45
+ autoCalculateWidth: {
46
+ type: Boolean,
47
+ default: true
48
+ }
49
+ },
30
50
  data(){
31
51
  return {
32
- pointer:0,
33
- topList:[],
34
- topNum:0,
35
- arrowFlag: true,
52
+ pointer: 0,
53
+ topList: [],
54
+ topNum: 0,
55
+ arrowFlag: false,
56
+ menuActualWidth: 0,
57
+ shouldShowArrows: false,
58
+ canMoveLeft: false,
59
+ canMoveRight: false,
60
+ resizeObserver: null,
36
61
  }
37
62
  },
38
63
  components: {
@@ -42,48 +67,154 @@ export default {
42
67
  handleCustomEvent(data) {
43
68
  // 接收子组件传递的数据
44
69
  this.topList = data;
70
+ this.checkWidthAndArrows();
45
71
  },
46
72
  topMen(data){
47
73
  this.topNum = data;
74
+ this.checkWidthAndArrows();
48
75
  },
49
76
  topMenTrue(data){
50
- this.arrowFlag = data
77
+ this.arrowFlag = data;
78
+ this.checkWidthAndArrows();
79
+ },
80
+ // 处理菜单宽度变化
81
+ handleMenuWidthChange(width) {
82
+ this.menuActualWidth = width;
83
+ this.checkWidthAndArrows();
84
+ },
85
+ // 检查宽度并决定是否显示箭头
86
+ checkWidthAndArrows() {
87
+ this.$nextTick(() => {
88
+ if (!this.autoCalculateWidth) {
89
+ this.shouldShowArrows = this.arrowFlag;
90
+ return;
91
+ }
92
+
93
+ // 计算实际菜单宽度
94
+ const navBox = this.$refs.navBox;
95
+ if (navBox) {
96
+ const menuContainer = navBox.querySelector('.menu-list');
97
+ if (menuContainer) {
98
+ this.menuActualWidth = menuContainer.scrollWidth;
99
+ }
100
+ }
101
+
102
+ // 预留箭头按钮空间
103
+ const arrowSpace = 80;
104
+ const effectiveWidth = this.availableWidth - arrowSpace;
105
+
106
+ // 判断是否需要显示箭头
107
+ this.shouldShowArrows = this.menuActualWidth > effectiveWidth;
108
+
109
+ if (this.shouldShowArrows) {
110
+ this.updateArrowStates();
111
+ }
112
+
113
+ // 触发宽度变化事件
114
+ this.$emit('width-change', {
115
+ available: this.availableWidth,
116
+ actual: this.menuActualWidth,
117
+ needArrows: this.shouldShowArrows
118
+ });
119
+
120
+ });
121
+ },
122
+ // 更新箭头状态
123
+ updateArrowStates() {
124
+ if (!this.shouldShowArrows) {
125
+ this.canMoveLeft = false;
126
+ this.canMoveRight = false;
127
+ return;
128
+ }
129
+
130
+ // 计算最大可移动位置
131
+ const maxPointer = Math.max(0, this.topList.length - this.topNum);
132
+
133
+ this.canMoveLeft = this.pointer > 0;
134
+ this.canMoveRight = this.pointer < maxPointer;
51
135
  },
52
136
  moveMenu: function (direction) {
137
+ if (!this.shouldShowArrows) return;
138
+
139
+ const maxPointer = Math.max(0, this.topList.length - this.topNum);
140
+
53
141
  if (direction === "right") {
54
- if (this.pointer + this.topNum === this.topList.length) {
55
- return;
142
+ if (this.pointer < maxPointer) {
143
+ this.pointer++;
56
144
  }
57
- this.pointer++;
58
145
  } else {
59
- if (this.pointer === 0) {
60
- return;
146
+ if (this.pointer > 0) {
147
+ this.pointer--;
61
148
  }
62
- this.pointer--;
63
149
  }
150
+
151
+ this.updateArrowStates();
152
+
64
153
  this.flag = false;
65
154
  let self = this;
66
155
  setTimeout(() => {
67
156
  self.flag = true;
68
157
  }, 0);
69
158
  },
159
+ // 监听容器大小变化
160
+ observeResize() {
161
+ if (window.ResizeObserver) {
162
+ this.resizeObserver = new ResizeObserver(() => {
163
+ this.checkWidthAndArrows();
164
+ });
70
165
 
166
+ if (this.$refs.slideWrapper) {
167
+ this.resizeObserver.observe(this.$refs.slideWrapper);
168
+ }
169
+ }
170
+ },
71
171
  },
172
+ watch: {
173
+ availableWidth(newVal) {
174
+ this.checkWidthAndArrows();
175
+ },
176
+ pointer() {
177
+ this.updateArrowStates();
178
+ }
179
+ },
180
+ mounted() {
181
+ this.$nextTick(() => {
182
+ this.checkWidthAndArrows();
183
+ this.observeResize();
184
+ });
185
+ },
186
+ beforeDestroy() {
187
+ if (this.resizeObserver) {
188
+ this.resizeObserver.disconnect();
189
+ }
190
+ }
72
191
  }
73
192
  </script>
74
193
 
75
194
  <style scoped lang="less">
195
+ .pro-layout-nav-slide-wrapper {
196
+ display: flex;
197
+ align-items: center;
198
+ height: 100%;
199
+ width: 100%;
200
+
201
+ .nav-box-slide{
202
+ overflow: hidden;
203
+ flex: 1;
204
+ }
205
+
206
+ .tools-box-slide{
207
+ flex-shrink: 0;
208
+ }
209
+ }
76
210
 
77
211
  .more-menu {
78
- //color: #d9eeec;
79
212
  font-size: 22px;
80
213
  cursor: pointer;
81
- }
214
+ transition: opacity 0.3s ease;
82
215
 
83
- .nav-box-slide{
84
- float: left;
85
- }
86
- .tools-box-slide{
87
- float: right;
216
+ &:hover {
217
+ opacity: 0.8 !important;
218
+ }
88
219
  }
89
220
  </style>
@@ -1,55 +1,55 @@
1
1
  <template>
2
2
  <div class="pro-layout-nav-wrapper">
3
3
  <Menu
4
- ref="topNav"
5
- mode="horizontal"
6
- theme="dark"
7
- :active-name="activeName"
8
- @on-select="selectApp"
4
+ ref="topNav"
5
+ mode="horizontal"
6
+ theme="dark"
7
+ :active-name="activeName"
8
+ @on-select="selectApp"
9
9
  >
10
10
  <template v-for="item in topMenList">
11
11
  <MenuItem v-if="!item.children" :key="item.appId" :name="item.appId">
12
12
  <Icon
13
- v-bind="getIcon(item)"
14
- :size="20"
15
- v-show="systemInfo.navLogo === '1'"
13
+ v-bind="getIcon(item)"
14
+ :size="20"
15
+ v-show="systemInfo.navLogo === '1'"
16
16
  ></Icon
17
17
  >{{ item.name }}
18
18
  <div class="line"></div>
19
19
  </MenuItem>
20
20
  <Submenu
21
- class="ibp-nav-sub-menu"
22
- v-else
23
- :name="item.name"
24
- :key="item.name"
21
+ class="ibp-nav-sub-menu"
22
+ v-else
23
+ :name="item.name"
24
+ :key="item.name"
25
25
  >
26
26
  <template slot="title">
27
27
  {{ item.name }}
28
28
  </template>
29
29
  <MenuItem
30
- v-for="itemL2 in item.children"
31
- :key="itemL2.appId"
32
- :name="itemL2.appId"
33
- ><Icon
30
+ v-for="itemL2 in item.children"
31
+ :key="itemL2.appId"
32
+ :name="itemL2.appId"
33
+ ><Icon
34
34
  :type="itemL2.icon"
35
35
  :size="20"
36
36
  v-show="systemInfo.navLogo === '1'"
37
- ></Icon
38
- >{{ itemL2.name }}</MenuItem
37
+ ></Icon
38
+ >{{ itemL2.name }}</MenuItem
39
39
  >
40
40
  </Submenu>
41
41
  </template>
42
42
  <Submenu name="other" v-if="otherList.length > 0">
43
43
  <template slot="title"> ... </template>
44
44
  <MenuItem
45
- :name="item.appId"
46
- v-for="item in otherList"
47
- :key="item.appId"
45
+ :name="item.appId"
46
+ v-for="item in otherList"
47
+ :key="item.appId"
48
48
  >
49
49
  <Icon
50
- v-bind="getIcon(item)"
51
- :size="20"
52
- v-show="systemInfo.navLogo === '1'"
50
+ v-bind="getIcon(item)"
51
+ :size="20"
52
+ v-show="systemInfo.navLogo === '1'"
53
53
  ></Icon
54
54
  >{{ item.name }}
55
55
  </MenuItem>
@@ -66,7 +66,12 @@ import _ from "lodash";
66
66
 
67
67
  export default {
68
68
  name: "pro-layout-nav",
69
- props: {},
69
+ props: {
70
+ availableWidth: {
71
+ type: Number,
72
+ default: 800
73
+ }
74
+ },
70
75
  data() {
71
76
  return {
72
77
  systemInfo: {},
@@ -79,6 +84,11 @@ export default {
79
84
  lastTopMenuNum: -1,
80
85
  acceptAppId: "",
81
86
  originMenuList: [],
87
+ // 新增响应式相关状态
88
+ resizeObserver: null,
89
+ menuActualWidth: 0,
90
+ shouldShowMore: false,
91
+ autoCalculateWidth: true,
82
92
  };
83
93
  },
84
94
  methods: {
@@ -95,12 +105,19 @@ export default {
95
105
  Bus.$on("menu-list", (data) => {
96
106
  this.initMenu(data);
97
107
  });
108
+ // 初始化响应式监听
109
+ this.initResizeObserver();
98
110
  },
99
111
  destroyListener() {
100
112
  Bus.$off("system-info");
101
113
  Bus.$off("nav-list");
102
114
  Bus.$off("menu-list");
103
115
  Bus.$off("change-app");
116
+ // 清理 ResizeObserver
117
+ if (this.resizeObserver) {
118
+ this.resizeObserver.disconnect();
119
+ this.resizeObserver = null;
120
+ }
104
121
  },
105
122
  initSystemInfo(data) {
106
123
  if (data) {
@@ -115,9 +132,9 @@ export default {
115
132
  },
116
133
  initNav(data) {
117
134
  if (
118
- _.some(data, (app) => app.children?.length) || // 分组以后会死循环调用,中断一下
119
- (arraysEqual(this.originNavList, data) &&
120
- this.topMenuNum === this.lastTopMenuNum)
135
+ _.some(data, (app) => app.children?.length) || // 分组以后会死循环调用,中断一下
136
+ (arraysEqual(this.originNavList, data) &&
137
+ this.topMenuNum === this.lastTopMenuNum)
121
138
  ) {
122
139
  return;
123
140
  }
@@ -137,25 +154,25 @@ export default {
137
154
  // 如果组织了二级菜单展现,则对菜单进行分组
138
155
  if (!_.isEmpty(parentNameEnum)) {
139
156
  let rlt = _.reduce(
140
- dataClone,
141
- (acc, cur) => {
142
- let parentName = parentNameEnum[cur.appId];
143
- if (parentName) {
144
- let curApp = _.find(acc, { name: parentName });
145
- if (curApp) {
146
- curApp.children.push(cur);
157
+ dataClone,
158
+ (acc, cur) => {
159
+ let parentName = parentNameEnum[cur.appId];
160
+ if (parentName) {
161
+ let curApp = _.find(acc, { name: parentName });
162
+ if (curApp) {
163
+ curApp.children.push(cur);
164
+ } else {
165
+ acc.push({
166
+ name: parentName,
167
+ children: [cur],
168
+ });
169
+ }
147
170
  } else {
148
- acc.push({
149
- name: parentName,
150
- children: [cur],
151
- });
171
+ acc.push(cur);
152
172
  }
153
- } else {
154
- acc.push(cur);
155
- }
156
- return acc;
157
- },
158
- []
173
+ return acc;
174
+ },
175
+ []
159
176
  );
160
177
  this.navList = rlt;
161
178
  } else {
@@ -208,39 +225,166 @@ export default {
208
225
  },
209
226
  // 顶部导航的导航配置多的话,分辨率低的场景下,需要计算一下剩余空间能展示多少,动态计算topMenuNum
210
227
  calcTopMenus() {
211
- let docWidth = document.getElementById("app").clientWidth;
212
- let triggerBoxWidth =
213
- document.getElementsByClassName("trigger-box")[0].clientWidth;
214
- let logoBoxWidth =
215
- document.getElementsByClassName("logo-box")[0].clientWidth;
216
- let toolsBoxWidth =
217
- document.getElementsByClassName("tools-box")[0].clientWidth;
218
- let othersWidth = 80; // 其他按钮的宽度
219
- let remainSpace =
220
- docWidth - triggerBoxWidth - logoBoxWidth - toolsBoxWidth - othersWidth;
221
-
222
- let topMenuNum = 0
223
- let sumWidth = 0
224
- // document.getElementsByClassName('pro-layout-nav-wrapper')?.[0]?.getElementsByTagName('li')?.forEach(dom => {
225
- document.querySelectorAll('.pro-layout-nav-wrapper>.ivu-menu>li')?.forEach((dom) => {
226
- sumWidth += dom.clientWidth
227
- if (sumWidth < remainSpace) {
228
- topMenuNum++
228
+ // this.$nextTick(() => {
229
+ if (!this.autoCalculateWidth) {
230
+ return;
231
+ }
232
+
233
+ // 计算可用宽度
234
+ // this.calculateAvailableWidth();
235
+
236
+ // 计算实际菜单宽度
237
+ this.calculateMenuActualWidth();
238
+
239
+ // 预留"更多"按钮空间
240
+ const moreButtonSpace = 80;
241
+ const effectiveWidth = this.availableWidth - moreButtonSpace;
242
+
243
+ // 判断是否需要显示"更多"按钮
244
+ this.shouldShowMore = this.menuActualWidth > effectiveWidth;
245
+
246
+ // 根据可用宽度计算能显示的菜单数量
247
+ this.calculateTopMenuNum();
248
+
249
+ // 更新显示列表
250
+ this.updateMenuLists();
251
+
252
+ // 触发宽度变化事件
253
+ this.$emit('width-change', {
254
+ available: this.availableWidth,
255
+ actual: this.menuActualWidth,
256
+ needMore: this.shouldShowMore
257
+ });
258
+ // });
259
+ },
260
+
261
+ // 计算可用宽度
262
+ // calculateAvailableWidth() {
263
+ // const appElement = document.getElementById("app");
264
+ // if (!appElement) return;
265
+ //
266
+ // const docWidth = appElement.clientWidth;
267
+ // const triggerBoxWidth = this.getElementWidth("trigger-box");
268
+ // const logoBoxWidth = this.getElementWidth("logo-box");
269
+ // const toolsBoxWidth = this.getElementWidth("tools-box");
270
+ // const othersWidth = 80; // 其他按钮的宽度
271
+ //
272
+ // this.availableWidth = docWidth - triggerBoxWidth - logoBoxWidth - toolsBoxWidth - othersWidth;
273
+ // },
274
+
275
+ // 计算实际菜单宽度
276
+ calculateMenuActualWidth() {
277
+ const navWrapper = this.$el;
278
+ if (!navWrapper) return;
279
+
280
+ const menuContainer = navWrapper.querySelector('.ivu-menu');
281
+ if (menuContainer) {
282
+ // 计算所有菜单项的实际宽度
283
+ let totalWidth = 0;
284
+ const menuItems = menuContainer.querySelectorAll('li');
285
+ menuItems.forEach(item => {
286
+ totalWidth += item.offsetWidth;
287
+ });
288
+ this.menuActualWidth = totalWidth;
289
+ }
290
+ },
291
+
292
+ // 计算顶部菜单数量
293
+ calculateTopMenuNum() {
294
+ if (!this.navList || this.navList.length === 0) return;
295
+
296
+ let topMenuNum = 0;
297
+ let sumWidth = 0;
298
+
299
+ // 模拟计算每个菜单项的宽度
300
+ this.navList.forEach((item, index) => {
301
+ // 估算每个菜单项的宽度(包括图标、文字、边距等)
302
+ const estimatedWidth = this.estimateMenuItemWidth(item);
303
+
304
+ if (sumWidth + estimatedWidth < this.availableWidth) {
305
+ topMenuNum++;
306
+ sumWidth += estimatedWidth;
229
307
  }
230
- })
308
+ });
309
+
231
310
  // 如果计算值为0,就不要触发事件,如果系统配置要展示少量,也不要触发事件
232
- if (topMenuNum && this.systemInfo?.topMenu > topMenuNum) {
311
+ if (topMenuNum && (!this.systemInfo?.topMenu || this.systemInfo?.topMenu > topMenuNum)) {
233
312
  this.topMenuNum = topMenuNum;
234
- if (this.navList.length > this.topMenuNum) {
235
- let navList = deepCopy(this.navList);
236
- this.topMenList = navList.splice(0, this.topMenuNum);
237
- this.otherList = navList;
238
- } else {
239
- this.topMenList = this.navList;
240
- this.otherList = [];
313
+ }else{
314
+ this.topMenuNum = this.systemInfo.topMenu;
315
+ }
316
+ },
317
+
318
+ // 估算菜单项宽度
319
+ estimateMenuItemWidth(item) {
320
+ // 基础宽度:内边距 + 边框
321
+ let baseWidth = 40;
322
+
323
+ // 图标宽度
324
+ if (item.icon && this.systemInfo.navLogo === '1') {
325
+ baseWidth += 26;
326
+ }
327
+
328
+ // 文字宽度估算(每个字符约14px)
329
+ // const fontSize = parseInt(window.getComputedStyle(document.querySelector('#customVars')).getPropertyValue('--font-size-base').replace("px",""))
330
+ var customVars=window.getComputedStyle(document.querySelector('#customVars'));
331
+ var fontSizeVar=customVars.getPropertyValue('--font-size-base');
332
+ var fontSize=parseInt(fontSizeVar.replace("px",""));
333
+
334
+ const textWidth = (item.name || '').length * fontSize;
335
+
336
+ return baseWidth + textWidth;
337
+ },
338
+
339
+ // 更新菜单列表
340
+ updateMenuLists() {
341
+ if (this.navList.length > this.topMenuNum) {
342
+ const navList = deepCopy(this.navList);
343
+ this.topMenList = navList.splice(0, this.topMenuNum);
344
+ this.otherList = navList;
345
+ } else {
346
+ this.topMenList = this.navList;
347
+ this.otherList = [];
348
+ }
349
+ console.log("topMenList",this.topMenList.length)
350
+ console.log("otherList",this.otherList.length)
351
+ },
352
+
353
+ // 获取元素宽度的辅助方法
354
+ getElementWidth(className) {
355
+ const element = document.getElementsByClassName(className)[0];
356
+ return element ? element.clientWidth : 0;
357
+ },
358
+
359
+ // 初始化 ResizeObserver
360
+ initResizeObserver() {
361
+ if (window.ResizeObserver) {
362
+ this.resizeObserver = new ResizeObserver(() => {
363
+ this.calcTopMenus();
364
+ });
365
+ }
366
+ },
367
+
368
+ // 开始监听容器大小变化
369
+ startResizeObserving() {
370
+ if (this.resizeObserver && this.$el) {
371
+ this.resizeObserver.observe(this.$el);
372
+
373
+ // 同时监听父容器
374
+ const appElement = document.getElementById("app");
375
+ if (appElement) {
376
+ this.resizeObserver.observe(appElement);
241
377
  }
242
378
  }
243
379
  },
380
+
381
+ // 停止监听容器大小变化
382
+ stopResizeObserving() {
383
+ if (this.resizeObserver) {
384
+ this.resizeObserver.disconnect();
385
+ }
386
+ },
387
+
244
388
  //获取icon图标
245
389
  getIcon(item){
246
390
  const icon = item.icon
@@ -261,12 +405,31 @@ export default {
261
405
  this.$refs.topNav.updateActiveName();
262
406
  });
263
407
  },
408
+ // 监听可用宽度变化
409
+ availableWidth(newVal) {
410
+ console.log("availableWidth",newVal)
411
+ this.calcTopMenus();
412
+ },
413
+ // 监听系统信息变化
414
+ systemInfo: {
415
+ handler() {
416
+ this.calcTopMenus();
417
+ },
418
+ deep: true
419
+ }
264
420
  },
265
421
  created() {
266
422
  this.initListener();
267
423
  },
424
+ mounted() {
425
+ this.$nextTick(() => {
426
+ this.calcTopMenus();
427
+ this.startResizeObserving();
428
+ });
429
+ },
268
430
  beforeDestroy() {
269
431
  this.destroyListener();
432
+ this.stopResizeObserving();
270
433
  },
271
434
  };
272
435
  </script>
@@ -53,7 +53,6 @@ export default {
53
53
  },
54
54
  methods: {
55
55
  initListener() {
56
- debugger
57
56
  Bus.$on('system-info',(data) => {
58
57
  this.initSystemInfo(data);
59
58
  })
@@ -62,7 +61,6 @@ export default {
62
61
  Bus.$off('system-info')
63
62
  },
64
63
  initSystemInfo(data){
65
- debugger
66
64
  if (data && data.rightTopOptButtonList) {
67
65
  this.rightTopOptButtonList = deepCopy(data.rightTopOptButtonList)
68
66
  }