Commit bb5520a7d1fecac5351d8d679d3b72408bfd1cc1

Authored by RuoYi
1 parent 1e2058bd

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

ruoyi-ui/src/assets/styles/element-ui.scss
... ... @@ -31,7 +31,7 @@
31 31 .fixed-width {
32 32 .el-button--mini {
33 33 padding: 7px 10px;
34   - width: 60px;
  34 + min-width: 60px;
35 35 }
36 36 }
37 37  
... ...
ruoyi-ui/src/layout/components/TagsView/ScrollPane.vue
... ... @@ -19,12 +19,21 @@ export default {
19 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 28 methods: {
23 29 handleScroll(e) {
24 30 const eventDelta = e.wheelDelta || -e.deltaY * 40
25 31 const $scrollWrapper = this.scrollWrapper
26 32 $scrollWrapper.scrollLeft = $scrollWrapper.scrollLeft + eventDelta / 4
27 33 },
  34 + emitScroll() {
  35 + this.$emit('scroll')
  36 + },
28 37 moveToTarget(currentTag) {
29 38 const $container = this.$refs.scrollContainer.$el
30 39 const $containerWidth = $container.offsetWidth
... ...
ruoyi-ui/src/layout/components/TagsView/index.vue
1 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 27 </template>
32 28  
33 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 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 209 </script>
225 210  
226 211 <style lang="scss" scoped>
227 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 277 </style>
293 278  
294 279 <style lang="scss">
295 280 //reset element css of el-icon-close
296 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 303 </style>
... ...