作者 RuoYi

当tags-view滚动关闭右键菜单

@@ -31,7 +31,7 @@ @@ -31,7 +31,7 @@
31 .fixed-width { 31 .fixed-width {
32 .el-button--mini { 32 .el-button--mini {
33 padding: 7px 10px; 33 padding: 7px 10px;
34 - width: 60px; 34 + min-width: 60px;
35 } 35 }
36 } 36 }
37 37
@@ -19,12 +19,21 @@ export default { @@ -19,12 +19,21 @@ export default {
19 return this.$refs.scrollContainer.$refs.wrap 19 return this.$refs.scrollContainer.$refs.wrap
20 } 20 }
21 }, 21 },
  22 + mounted() {
  23 + this.scrollWrapper.addEventListener('scroll', this.emitScroll, true)
  24 + },
  25 + beforeDestroy() {
  26 + this.scrollWrapper.removeEventListener('scroll', this.emitScroll)
  27 + },
22 methods: { 28 methods: {
23 handleScroll(e) { 29 handleScroll(e) {
24 const eventDelta = e.wheelDelta || -e.deltaY * 40 30 const eventDelta = e.wheelDelta || -e.deltaY * 40
25 const $scrollWrapper = this.scrollWrapper 31 const $scrollWrapper = this.scrollWrapper
26 $scrollWrapper.scrollLeft = $scrollWrapper.scrollLeft + eventDelta / 4 32 $scrollWrapper.scrollLeft = $scrollWrapper.scrollLeft + eventDelta / 4
27 }, 33 },
  34 + emitScroll() {
  35 + this.$emit('scroll')
  36 + },
28 moveToTarget(currentTag) { 37 moveToTarget(currentTag) {
29 const $container = this.$refs.scrollContainer.$el 38 const $container = this.$refs.scrollContainer.$el
30 const $containerWidth = $container.offsetWidth 39 const $containerWidth = $container.offsetWidth
1 <template> 1 <template>
2 - <div id="tags-view-container" class="tags-view-container">  
3 - <scroll-pane ref="scrollPane" class="tags-view-wrapper">  
4 - <router-link  
5 - v-for="tag in visitedViews"  
6 - ref="tag"  
7 - :key="tag.path"  
8 - :class="isActive(tag)?'active':''"  
9 - :to="{ path: tag.path, query: tag.query, fullPath: tag.fullPath }"  
10 - tag="span"  
11 - class="tags-view-item"  
12 - :style="activeStyle(tag)"  
13 - @click.middle.native="!isAffix(tag)?closeSelectedTag(tag):''"  
14 - @contextmenu.prevent.native="openMenu(tag,$event)"  
15 - >  
16 - {{ tag.title }}  
17 - <span  
18 - v-if="!isAffix(tag)"  
19 - class="el-icon-close"  
20 - @click.prevent.stop="closeSelectedTag(tag)"  
21 - />  
22 - </router-link>  
23 - </scroll-pane>  
24 - <ul v-show="visible" :style="{left:left+'px',top:top+'px'}" class="contextmenu">  
25 - <li @click="refreshSelectedTag(selectedTag)">刷新页面</li>  
26 - <li v-if="!isAffix(selectedTag)" @click="closeSelectedTag(selectedTag)">关闭当前</li>  
27 - <li @click="closeOthersTags">关闭其他</li>  
28 - <li @click="closeAllTags(selectedTag)">关闭所有</li>  
29 - </ul>  
30 - </div> 2 + <div id="tags-view-container" class="tags-view-container">
  3 + <scroll-pane ref="scrollPane" class="tags-view-wrapper" @scroll="handleScroll">
  4 + <router-link
  5 + v-for="tag in visitedViews"
  6 + ref="tag"
  7 + :key="tag.path"
  8 + :class="isActive(tag)?'active':''"
  9 + :to="{ path: tag.path, query: tag.query, fullPath: tag.fullPath }"
  10 + tag="span"
  11 + class="tags-view-item"
  12 + :style="activeStyle(tag)"
  13 + @click.middle.native="!isAffix(tag)?closeSelectedTag(tag):''"
  14 + @contextmenu.prevent.native="openMenu(tag,$event)"
  15 + >
  16 + {{ tag.title }}
  17 + <span v-if="!isAffix(tag)" class="el-icon-close" @click.prevent.stop="closeSelectedTag(tag)" />
  18 + </router-link>
  19 + </scroll-pane>
  20 + <ul v-show="visible" :style="{left:left+'px',top:top+'px'}" class="contextmenu">
  21 + <li @click="refreshSelectedTag(selectedTag)">刷新页面</li>
  22 + <li v-if="!isAffix(selectedTag)" @click="closeSelectedTag(selectedTag)">关闭当前</li>
  23 + <li @click="closeOthersTags">关闭其他</li>
  24 + <li @click="closeAllTags(selectedTag)">关闭所有</li>
  25 + </ul>
  26 + </div>
31 </template> 27 </template>
32 28
33 <script> 29 <script>
34 -import ScrollPane from "./ScrollPane";  
35 -import path from "path"; 30 +import ScrollPane from './ScrollPane'
  31 +import path from 'path'
36 32
37 export default { 33 export default {
38 - components: { ScrollPane },  
39 - data() {  
40 - return {  
41 - visible: false,  
42 - top: 0,  
43 - left: 0,  
44 - selectedTag: {},  
45 - affixTags: []  
46 - }; 34 + components: { ScrollPane },
  35 + data() {
  36 + return {
  37 + visible: false,
  38 + top: 0,
  39 + left: 0,
  40 + selectedTag: {},
  41 + affixTags: []
  42 + }
  43 + },
  44 + computed: {
  45 + visitedViews() {
  46 + return this.$store.state.tagsView.visitedViews
  47 + },
  48 + routes() {
  49 + return this.$store.state.permission.routes
  50 + },
  51 + theme() {
  52 + return this.$store.state.settings.theme;
  53 + }
  54 + },
  55 + watch: {
  56 + $route() {
  57 + this.addTags()
  58 + this.moveToCurrentTag()
  59 + },
  60 + visible(value) {
  61 + if (value) {
  62 + document.body.addEventListener('click', this.closeMenu)
  63 + } else {
  64 + document.body.removeEventListener('click', this.closeMenu)
  65 + }
  66 + }
  67 + },
  68 + mounted() {
  69 + this.initTags()
  70 + this.addTags()
  71 + },
  72 + methods: {
  73 + isActive(route) {
  74 + return route.path === this.$route.path
  75 + },
  76 + activeStyle(tag) {
  77 + if (!this.isActive(tag)) return {};
  78 + return {
  79 + "background-color": this.theme,
  80 + "border-color": this.theme
  81 + };
  82 + },
  83 + isAffix(tag) {
  84 + return tag.meta && tag.meta.affix
47 }, 85 },
48 - computed: {  
49 - visitedViews() {  
50 - return this.$store.state.tagsView.visitedViews;  
51 - },  
52 - routes() {  
53 - return this.$store.state.permission.routes;  
54 - },  
55 - theme() {  
56 - return this.$store.state.settings.theme; 86 + filterAffixTags(routes, basePath = '/') {
  87 + let tags = []
  88 + routes.forEach(route => {
  89 + if (route.meta && route.meta.affix) {
  90 + const tagPath = path.resolve(basePath, route.path)
  91 + tags.push({
  92 + fullPath: tagPath,
  93 + path: tagPath,
  94 + name: route.name,
  95 + meta: { ...route.meta }
  96 + })
57 } 97 }
  98 + if (route.children) {
  99 + const tempTags = this.filterAffixTags(route.children, route.path)
  100 + if (tempTags.length >= 1) {
  101 + tags = [...tags, ...tempTags]
  102 + }
  103 + }
  104 + })
  105 + return tags
58 }, 106 },
59 - watch: {  
60 - $route() {  
61 - this.addTags();  
62 - this.moveToCurrentTag();  
63 - },  
64 - visible(value) {  
65 - if (value) {  
66 - document.body.addEventListener("click", this.closeMenu);  
67 - } else {  
68 - document.body.removeEventListener("click", this.closeMenu);  
69 - } 107 + initTags() {
  108 + const affixTags = this.affixTags = this.filterAffixTags(this.routes)
  109 + for (const tag of affixTags) {
  110 + // Must have tag name
  111 + if (tag.name) {
  112 + this.$store.dispatch('tagsView/addVisitedView', tag)
70 } 113 }
  114 + }
71 }, 115 },
72 - mounted() {  
73 - this.initTags();  
74 - this.addTags(); 116 + addTags() {
  117 + const { name } = this.$route
  118 + if (name) {
  119 + this.$store.dispatch('tagsView/addView', this.$route)
  120 + }
  121 + return false
75 }, 122 },
76 - methods: {  
77 - isActive(route) {  
78 - return route.path === this.$route.path;  
79 - },  
80 - activeStyle(tag) {  
81 - if (!this.isActive(tag)) return {};  
82 - return {  
83 - "background-color": this.theme,  
84 - "border-color": this.theme  
85 - };  
86 - },  
87 - isAffix(tag) {  
88 - return tag.meta && tag.meta.affix;  
89 - },  
90 - filterAffixTags(routes, basePath = "/") {  
91 - let tags = [];  
92 - routes.forEach(route => {  
93 - if (route.meta && route.meta.affix) {  
94 - const tagPath = path.resolve(basePath, route.path);  
95 - tags.push({  
96 - fullPath: tagPath,  
97 - path: tagPath,  
98 - name: route.name,  
99 - meta: { ...route.meta }  
100 - });  
101 - }  
102 - if (route.children) {  
103 - const tempTags = this.filterAffixTags(  
104 - route.children,  
105 - route.path  
106 - );  
107 - if (tempTags.length >= 1) {  
108 - tags = [...tags, ...tempTags];  
109 - }  
110 - }  
111 - });  
112 - return tags;  
113 - },  
114 - initTags() {  
115 - const affixTags = (this.affixTags = this.filterAffixTags(  
116 - this.routes  
117 - ));  
118 - for (const tag of affixTags) {  
119 - // Must have tag name  
120 - if (tag.name) {  
121 - this.$store.dispatch("tagsView/addVisitedView", tag);  
122 - } 123 + moveToCurrentTag() {
  124 + const tags = this.$refs.tag
  125 + this.$nextTick(() => {
  126 + for (const tag of tags) {
  127 + if (tag.to.path === this.$route.path) {
  128 + this.$refs.scrollPane.moveToTarget(tag)
  129 + // when query is different then update
  130 + if (tag.to.fullPath !== this.$route.fullPath) {
  131 + this.$store.dispatch('tagsView/updateVisitedView', this.$route)
123 } 132 }
124 - },  
125 - addTags() {  
126 - const { name } = this.$route;  
127 - if (name) {  
128 - this.$store.dispatch("tagsView/addView", this.$route);  
129 - }  
130 - return false;  
131 - },  
132 - moveToCurrentTag() {  
133 - const tags = this.$refs.tag;  
134 - this.$nextTick(() => {  
135 - for (const tag of tags) {  
136 - if (tag.to.path === this.$route.path) {  
137 - this.$refs.scrollPane.moveToTarget(tag);  
138 - // when query is different then update  
139 - if (tag.to.fullPath !== this.$route.fullPath) {  
140 - this.$store.dispatch(  
141 - "tagsView/updateVisitedView",  
142 - this.$route  
143 - );  
144 - }  
145 - break;  
146 - }  
147 - }  
148 - });  
149 - },  
150 - refreshSelectedTag(view) {  
151 - this.$store.dispatch("tagsView/delCachedView", view).then(() => {  
152 - const { fullPath } = view;  
153 - this.$nextTick(() => {  
154 - this.$router.replace({  
155 - path: "/redirect" + fullPath  
156 - });  
157 - });  
158 - });  
159 - },  
160 - closeSelectedTag(view) {  
161 - this.$store  
162 - .dispatch("tagsView/delView", view)  
163 - .then(({ visitedViews }) => {  
164 - if (this.isActive(view)) {  
165 - this.toLastView(visitedViews, view);  
166 - }  
167 - });  
168 - },  
169 - closeOthersTags() {  
170 - this.$router.push(this.selectedTag);  
171 - this.$store  
172 - .dispatch("tagsView/delOthersViews", this.selectedTag)  
173 - .then(() => {  
174 - this.moveToCurrentTag();  
175 - });  
176 - },  
177 - closeAllTags(view) {  
178 - this.$store  
179 - .dispatch("tagsView/delAllViews")  
180 - .then(({ visitedViews }) => {  
181 - if (this.affixTags.some(tag => tag.path === view.path)) {  
182 - return;  
183 - }  
184 - this.toLastView(visitedViews, view);  
185 - });  
186 - },  
187 - toLastView(visitedViews, view) {  
188 - const latestView = visitedViews.slice(-1)[0];  
189 - if (latestView) {  
190 - this.$router.push(latestView.fullPath);  
191 - } else {  
192 - // now the default is to redirect to the home page if there is no tags-view,  
193 - // you can adjust it according to your needs.  
194 - if (view.name === "Dashboard") {  
195 - // to reload home page  
196 - this.$router.replace({ path: "/redirect" + view.fullPath });  
197 - } else {  
198 - this.$router.push("/");  
199 - }  
200 - }  
201 - },  
202 - openMenu(tag, e) {  
203 - const menuMinWidth = 105;  
204 - const offsetLeft = this.$el.getBoundingClientRect().left; // container margin left  
205 - const offsetWidth = this.$el.offsetWidth; // container width  
206 - const maxLeft = offsetWidth - menuMinWidth; // left boundary  
207 - const left = e.clientX - offsetLeft + 15; // 15: margin right 133 + break
  134 + }
  135 + }
  136 + })
  137 + },
  138 + refreshSelectedTag(view) {
  139 + this.$store.dispatch('tagsView/delCachedView', view).then(() => {
  140 + const { fullPath } = view
  141 + this.$nextTick(() => {
  142 + this.$router.replace({
  143 + path: '/redirect' + fullPath
  144 + })
  145 + })
  146 + })
  147 + },
  148 + closeSelectedTag(view) {
  149 + this.$store.dispatch('tagsView/delView', view).then(({ visitedViews }) => {
  150 + if (this.isActive(view)) {
  151 + this.toLastView(visitedViews, view)
  152 + }
  153 + })
  154 + },
  155 + closeOthersTags() {
  156 + this.$router.push(this.selectedTag)
  157 + this.$store.dispatch('tagsView/delOthersViews', this.selectedTag).then(() => {
  158 + this.moveToCurrentTag()
  159 + })
  160 + },
  161 + closeAllTags(view) {
  162 + this.$store.dispatch('tagsView/delAllViews').then(({ visitedViews }) => {
  163 + if (this.affixTags.some(tag => tag.path === view.path)) {
  164 + return
  165 + }
  166 + this.toLastView(visitedViews, view)
  167 + })
  168 + },
  169 + toLastView(visitedViews, view) {
  170 + const latestView = visitedViews.slice(-1)[0]
  171 + if (latestView) {
  172 + this.$router.push(latestView.fullPath)
  173 + } else {
  174 + // now the default is to redirect to the home page if there is no tags-view,
  175 + // you can adjust it according to your needs.
  176 + if (view.name === 'Dashboard') {
  177 + // to reload home page
  178 + this.$router.replace({ path: '/redirect' + view.fullPath })
  179 + } else {
  180 + this.$router.push('/')
  181 + }
  182 + }
  183 + },
  184 + openMenu(tag, e) {
  185 + const menuMinWidth = 105
  186 + const offsetLeft = this.$el.getBoundingClientRect().left // container margin left
  187 + const offsetWidth = this.$el.offsetWidth // container width
  188 + const maxLeft = offsetWidth - menuMinWidth // left boundary
  189 + const left = e.clientX - offsetLeft + 15 // 15: margin right
208 190
209 - if (left > maxLeft) {  
210 - this.left = maxLeft;  
211 - } else {  
212 - this.left = left;  
213 - } 191 + if (left > maxLeft) {
  192 + this.left = maxLeft
  193 + } else {
  194 + this.left = left
  195 + }
214 196
215 - this.top = e.clientY;  
216 - this.visible = true;  
217 - this.selectedTag = tag;  
218 - },  
219 - closeMenu() {  
220 - this.visible = false;  
221 - } 197 + this.top = e.clientY
  198 + this.visible = true
  199 + this.selectedTag = tag
  200 + },
  201 + closeMenu() {
  202 + this.visible = false
  203 + },
  204 + handleScroll() {
  205 + this.closeMenu()
222 } 206 }
223 -}; 207 + }
  208 +}
224 </script> 209 </script>
225 210
226 <style lang="scss" scoped> 211 <style lang="scss" scoped>
227 .tags-view-container { 212 .tags-view-container {
228 - height: 34px;  
229 - width: 100%;  
230 - background: #fff;  
231 - border-bottom: 1px solid #d8dce5;  
232 - box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.12), 0 0 3px 0 rgba(0, 0, 0, 0.04);  
233 - .tags-view-wrapper {  
234 - .tags-view-item {  
235 - display: inline-block;  
236 - position: relative;  
237 - cursor: pointer;  
238 - height: 26px;  
239 - line-height: 26px;  
240 - border: 1px solid #d8dce5;  
241 - color: #495060;  
242 - background: #fff;  
243 - padding: 0 8px;  
244 - font-size: 12px;  
245 - margin-left: 5px;  
246 - margin-top: 4px;  
247 - &:first-of-type {  
248 - margin-left: 15px;  
249 - }  
250 - &:last-of-type {  
251 - margin-right: 15px;  
252 - }  
253 - &.active {  
254 - background-color: #42b983;  
255 - color: #fff;  
256 - border-color: #42b983;  
257 - &::before {  
258 - content: "";  
259 - background: #fff;  
260 - display: inline-block;  
261 - width: 8px;  
262 - height: 8px;  
263 - border-radius: 50%;  
264 - position: relative;  
265 - margin-right: 2px;  
266 - }  
267 - } 213 + height: 34px;
  214 + width: 100%;
  215 + background: #fff;
  216 + border-bottom: 1px solid #d8dce5;
  217 + box-shadow: 0 1px 3px 0 rgba(0, 0, 0, .12), 0 0 3px 0 rgba(0, 0, 0, .04);
  218 + .tags-view-wrapper {
  219 + .tags-view-item {
  220 + display: inline-block;
  221 + position: relative;
  222 + cursor: pointer;
  223 + height: 26px;
  224 + line-height: 26px;
  225 + border: 1px solid #d8dce5;
  226 + color: #495060;
  227 + background: #fff;
  228 + padding: 0 8px;
  229 + font-size: 12px;
  230 + margin-left: 5px;
  231 + margin-top: 4px;
  232 + &:first-of-type {
  233 + margin-left: 15px;
  234 + }
  235 + &:last-of-type {
  236 + margin-right: 15px;
  237 + }
  238 + &.active {
  239 + background-color: #42b983;
  240 + color: #fff;
  241 + border-color: #42b983;
  242 + &::before {
  243 + content: '';
  244 + background: #fff;
  245 + display: inline-block;
  246 + width: 8px;
  247 + height: 8px;
  248 + border-radius: 50%;
  249 + position: relative;
  250 + margin-right: 2px;
268 } 251 }
  252 + }
269 } 253 }
270 - .contextmenu {  
271 - margin: 0;  
272 - background: #fff;  
273 - z-index: 3000;  
274 - position: absolute;  
275 - list-style-type: none;  
276 - padding: 5px 0;  
277 - border-radius: 4px;  
278 - font-size: 12px;  
279 - font-weight: 400;  
280 - color: #333;  
281 - box-shadow: 2px 2px 3px 0 rgba(0, 0, 0, 0.3);  
282 - li {  
283 - margin: 0;  
284 - padding: 7px 16px;  
285 - cursor: pointer;  
286 - &:hover {  
287 - background: #eee;  
288 - }  
289 - } 254 + }
  255 + .contextmenu {
  256 + margin: 0;
  257 + background: #fff;
  258 + z-index: 3000;
  259 + position: absolute;
  260 + list-style-type: none;
  261 + padding: 5px 0;
  262 + border-radius: 4px;
  263 + font-size: 12px;
  264 + font-weight: 400;
  265 + color: #333;
  266 + box-shadow: 2px 2px 3px 0 rgba(0, 0, 0, .3);
  267 + li {
  268 + margin: 0;
  269 + padding: 7px 16px;
  270 + cursor: pointer;
  271 + &:hover {
  272 + background: #eee;
  273 + }
290 } 274 }
  275 + }
291 } 276 }
292 </style> 277 </style>
293 278
294 <style lang="scss"> 279 <style lang="scss">
295 //reset element css of el-icon-close 280 //reset element css of el-icon-close
296 .tags-view-wrapper { 281 .tags-view-wrapper {
297 - .tags-view-item {  
298 - .el-icon-close {  
299 - width: 16px;  
300 - height: 16px;  
301 - vertical-align: 2px;  
302 - border-radius: 50%;  
303 - text-align: center;  
304 - transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);  
305 - transform-origin: 100% 50%;  
306 - &:before {  
307 - transform: scale(0.6);  
308 - display: inline-block;  
309 - vertical-align: -3px;  
310 - }  
311 - &:hover {  
312 - background-color: #b4bccc;  
313 - color: #fff;  
314 - }  
315 - } 282 + .tags-view-item {
  283 + .el-icon-close {
  284 + width: 16px;
  285 + height: 16px;
  286 + vertical-align: 2px;
  287 + border-radius: 50%;
  288 + text-align: center;
  289 + transition: all .3s cubic-bezier(.645, .045, .355, 1);
  290 + transform-origin: 100% 50%;
  291 + &:before {
  292 + transform: scale(.6);
  293 + display: inline-block;
  294 + vertical-align: -3px;
  295 + }
  296 + &:hover {
  297 + background-color: #b4bccc;
  298 + color: #fff;
  299 + }
316 } 300 }
  301 + }
317 } 302 }
318 </style> 303 </style>