UNPKG

9.35 kBPlain TextView Raw
1<script type="text/jsx">
2 import emitter from 'element-ui/src/mixins/emitter';
3 import Migrating from 'element-ui/src/mixins/migrating';
4 import Menubar from 'element-ui/src/utils/menu/aria-menubar';
5 import { addClass, removeClass, hasClass } from 'element-ui/src/utils/dom';
6
7 export default {
8 name: 'ElMenu',
9
10 render (h) {
11 const component = (
12 <ul
13 role="menubar"
14 key={ +this.collapse }
15 style={{ backgroundColor: this.backgroundColor || '' }}
16 class={{
17 'el-menu--horizontal': this.mode === 'horizontal',
18 'el-menu--collapse': this.collapse,
19 "el-menu": true
20 }}
21 >
22 { this.$slots.default }
23 </ul>
24 );
25
26 if (this.collapseTransition) {
27 return (
28 <el-menu-collapse-transition>
29 { component }
30 </el-menu-collapse-transition>
31 );
32 } else {
33 return component;
34 }
35 },
36
37 componentName: 'ElMenu',
38
39 mixins: [emitter, Migrating],
40
41 provide() {
42 return {
43 rootMenu: this
44 };
45 },
46
47 components: {
48 'el-menu-collapse-transition': {
49 functional: true,
50 render(createElement, context) {
51 const data = {
52 props: {
53 mode: 'out-in'
54 },
55 on: {
56 beforeEnter(el) {
57 el.style.opacity = 0.2;
58 },
59
60 enter(el) {
61 addClass(el, 'el-opacity-transition');
62 el.style.opacity = 1;
63 },
64
65 afterEnter(el) {
66 removeClass(el, 'el-opacity-transition');
67 el.style.opacity = '';
68 },
69
70 beforeLeave(el) {
71 if (!el.dataset) el.dataset = {};
72
73 if (hasClass(el, 'el-menu--collapse')) {
74 removeClass(el, 'el-menu--collapse');
75 el.dataset.oldOverflow = el.style.overflow;
76 el.dataset.scrollWidth = el.clientWidth;
77 addClass(el, 'el-menu--collapse');
78 } else {
79 addClass(el, 'el-menu--collapse');
80 el.dataset.oldOverflow = el.style.overflow;
81 el.dataset.scrollWidth = el.clientWidth;
82 removeClass(el, 'el-menu--collapse');
83 }
84
85 el.style.width = el.scrollWidth + 'px';
86 el.style.overflow = 'hidden';
87 },
88
89 leave(el) {
90 addClass(el, 'horizontal-collapse-transition');
91 el.style.width = el.dataset.scrollWidth + 'px';
92 }
93 }
94 };
95 return createElement('transition', data, context.children);
96 }
97 }
98 },
99
100 props: {
101 mode: {
102 type: String,
103 default: 'vertical'
104 },
105 defaultActive: {
106 type: String,
107 default: ''
108 },
109 defaultOpeneds: Array,
110 uniqueOpened: Boolean,
111 router: Boolean,
112 menuTrigger: {
113 type: String,
114 default: 'hover'
115 },
116 collapse: Boolean,
117 backgroundColor: String,
118 textColor: String,
119 activeTextColor: String,
120 collapseTransition: {
121 type: Boolean,
122 default: true
123 }
124 },
125 data() {
126 return {
127 activeIndex: this.defaultActive,
128 openedMenus: (this.defaultOpeneds && !this.collapse) ? this.defaultOpeneds.slice(0) : [],
129 items: {},
130 submenus: {}
131 };
132 },
133 computed: {
134 hoverBackground() {
135 return this.backgroundColor ? this.mixColor(this.backgroundColor, 0.2) : '';
136 },
137 isMenuPopup() {
138 return this.mode === 'horizontal' || (this.mode === 'vertical' && this.collapse);
139 }
140 },
141 watch: {
142 defaultActive(value){
143 if(!this.items[value]){
144 this.activeIndex = null
145 }
146 this.updateActiveIndex(value)
147 },
148
149 defaultOpeneds(value) {
150 if (!this.collapse) {
151 this.openedMenus = value;
152 }
153 },
154
155 collapse(value) {
156 if (value) this.openedMenus = [];
157 this.broadcast('ElSubmenu', 'toggle-collapse', value);
158 }
159 },
160 methods: {
161 updateActiveIndex(val) {
162 const item = this.items[val] || this.items[this.activeIndex] || this.items[this.defaultActive];
163 if (item) {
164 this.activeIndex = item.index;
165 this.initOpenedMenu();
166 } else {
167 this.activeIndex = null;
168 }
169 },
170
171 getMigratingConfig() {
172 return {
173 props: {
174 'theme': 'theme is removed.'
175 }
176 };
177 },
178 getColorChannels(color) {
179 color = color.replace('#', '');
180 if (/^[0-9a-fA-F]{3}$/.test(color)) {
181 color = color.split('');
182 for (let i = 2; i >= 0; i--) {
183 color.splice(i, 0, color[i]);
184 }
185 color = color.join('');
186 }
187 if (/^[0-9a-fA-F]{6}$/.test(color)) {
188 return {
189 red: parseInt(color.slice(0, 2), 16),
190 green: parseInt(color.slice(2, 4), 16),
191 blue: parseInt(color.slice(4, 6), 16)
192 };
193 } else {
194 return {
195 red: 255,
196 green: 255,
197 blue: 255
198 };
199 }
200 },
201 mixColor(color, percent) {
202 let { red, green, blue } = this.getColorChannels(color);
203 if (percent > 0) { // shade given color
204 red *= 1 - percent;
205 green *= 1 - percent;
206 blue *= 1 - percent;
207 } else { // tint given color
208 red += (255 - red) * percent;
209 green += (255 - green) * percent;
210 blue += (255 - blue) * percent;
211 }
212 return `rgb(${ Math.round(red) }, ${ Math.round(green) }, ${ Math.round(blue) })`;
213 },
214 addItem(item) {
215 this.$set(this.items, item.index, item);
216 },
217 removeItem(item) {
218 delete this.items[item.index];
219 },
220 addSubmenu(item) {
221 this.$set(this.submenus, item.index, item);
222 },
223 removeSubmenu(item) {
224 delete this.submenus[item.index];
225 },
226 openMenu(index, indexPath) {
227 let openedMenus = this.openedMenus;
228 if (openedMenus.indexOf(index) !== -1) return;
229 // 将不在该菜单路径下的其余菜单收起
230 // collapse all menu that are not under current menu item
231 if (this.uniqueOpened) {
232 this.openedMenus = openedMenus.filter(index => {
233 return indexPath.indexOf(index) !== -1;
234 });
235 }
236 this.openedMenus.push(index);
237 },
238 closeMenu(index) {
239 const i = this.openedMenus.indexOf(index);
240 if (i !== -1) {
241 this.openedMenus.splice(i, 1);
242 }
243 },
244 handleSubmenuClick(submenu) {
245 const { index, indexPath } = submenu;
246 let isOpened = this.openedMenus.indexOf(index) !== -1;
247
248 if (isOpened) {
249 this.closeMenu(index);
250 this.$emit('close', index, indexPath);
251 } else {
252 this.openMenu(index, indexPath);
253 this.$emit('open', index, indexPath);
254 }
255 },
256 handleItemClick(item) {
257 const { index, indexPath } = item;
258 const oldActiveIndex = this.activeIndex;
259 const hasIndex = item.index !== null;
260
261 if (hasIndex) {
262 this.activeIndex = item.index;
263 }
264
265 this.$emit('select', index, indexPath, item);
266
267 if (this.mode === 'horizontal' || this.collapse) {
268 this.openedMenus = [];
269 }
270
271 if (this.router && hasIndex) {
272 this.routeToItem(item, (error) => {
273 this.activeIndex = oldActiveIndex;
274 if (error) {
275 // vue-router 3.1.0+ push/replace cause NavigationDuplicated error
276 // https://github.com/ElemeFE/element/issues/17044
277 if (error.name === 'NavigationDuplicated') return
278 console.error(error)
279 }
280 });
281 }
282 },
283 // 初始化展开菜单
284 // initialize opened menu
285 initOpenedMenu() {
286 const index = this.activeIndex;
287 const activeItem = this.items[index];
288 if (!activeItem || this.mode === 'horizontal' || this.collapse) return;
289
290 let indexPath = activeItem.indexPath;
291
292 // 展开该菜单项的路径上所有子菜单
293 // expand all submenus of the menu item
294 indexPath.forEach(index => {
295 let submenu = this.submenus[index];
296 submenu && this.openMenu(index, submenu.indexPath);
297 });
298 },
299 routeToItem(item, onError) {
300 let route = item.route || item.index;
301 try {
302 this.$router.push(route, () => {}, onError);
303 } catch (e) {
304 console.error(e);
305 }
306 },
307 open(index) {
308 const { indexPath } = this.submenus[index.toString()];
309 indexPath.forEach(i => this.openMenu(i, indexPath));
310 },
311 close(index) {
312 this.closeMenu(index);
313 }
314 },
315 mounted() {
316 this.initOpenedMenu();
317 this.$on('item-click', this.handleItemClick);
318 this.$on('submenu-click', this.handleSubmenuClick);
319 if (this.mode === 'horizontal') {
320 new Menubar(this.$el); // eslint-disable-line
321 }
322 this.$watch('items', this.updateActiveIndex);
323 }
324 };
325</script>