xs-common-plugins 1.1.7 → 1.2.2

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/README.md CHANGED
@@ -278,4 +278,22 @@
278
278
  ```
279
279
  1.1.7
280
280
  1. 多余显示移除
281
+ ```
282
+ ```
283
+ 1.1.8
284
+ 1. common.js 增加 方法
285
+ 2. common.js commom.format() 拓展类型枚举
286
+ 3. 表单校验增加 min, max 参数
287
+ ```
288
+ ```
289
+ 1.2.0
290
+ 1. 项目增加多页签功能, 方便切换
291
+ ```
292
+ ```
293
+ 1.2.1
294
+ 1. 权益项目 渠道组件 商品组件修改
295
+ ```
296
+ ```
297
+ 1.2.2
298
+ 1. 组件样式
281
299
  ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "xs-common-plugins",
3
- "version": "1.1.7",
3
+ "version": "1.2.2",
4
4
  "private": false,
5
5
  "description": "向上公共代码部分",
6
6
  "author": "祝玲云 <1902733517@qq.com>",
@@ -5,7 +5,7 @@ import store from '@/store/index'
5
5
  import {OrgEnum} from '@/utils/enum'
6
6
  import filterRules from '@/utils/filterRules'
7
7
  import request from "xs-request";
8
-
8
+ import moduleCfg from '@/modules/module.config.js'
9
9
  const common = {}
10
10
 
11
11
  /**
@@ -400,28 +400,45 @@ common.requiredList = (list) => {
400
400
  /**
401
401
  * 格式化数据样式
402
402
  * @param {*} value 值
403
- * @param {*} type 要格式化的类型 [date, price]
403
+ * @param {*} type 要格式化的类型 [date, price, price, money, enum, list(前端自定义枚举)]
404
404
  * @returns
405
405
  */
406
406
  common.format = (value, type="date") => {
407
- switch (type) {
407
+ let newType = ['money', 'date', 'mdate'].includes(type) ? type : type.split('.').length > 1 ? 'enum' : 'list'
408
+ switch (newType) {
409
+ case 'money':
410
+ return Number(value).toFixed(4);
408
411
  case 'date':
409
- if(value) return value.replace("T", ' ').substr(0, 19)
410
- else return ''
411
- break;
412
+ if(value === '1970-01-01T00:00:00') return '/'
413
+ return value ? value.replace('T', ' ').substr(0, 19) : ''
414
+ case 'mdate':
415
+ if(value === '1970-01-01T00:00:00') return '/'
416
+ return value ? value.replace('T', ' ').substr(5, 19) : ''
412
417
  case 'price':
413
418
  if(typeof(value) === 'number') {
414
419
  return value.toFixed(2).toString().replace(/,/g,'').replace(/\d+/, function (n) { // 先提取整数部分
415
420
  return n.replace(/(\d)(?=(\d{3})+$)/g, function ($1) { // 对整数部分添加分隔符
416
- return $1 + ",";
421
+ return $1 + ",";
417
422
  });
418
423
  });
419
424
  }
420
425
  else return ''
421
- break;
426
+ case 'enum':
427
+ return getValueByType('enum', type, value)
428
+ case 'list':
429
+ return getValueByType('list', type, value)
422
430
  default:
423
431
  break;
424
432
  }
433
+ function getValueByType (type, name, value) {
434
+ if(type === 'enum') {
435
+ let row = OrgEnum[name].find(item => item.Id == value)
436
+ return row ? row.Text : ''
437
+ } else if(type === 'list') {
438
+ let row = moduleCfg.format ? moduleCfg.format[name].find(item => item.id == value) : null
439
+ return row ? row.name: ''
440
+ }
441
+ }
425
442
  }
426
443
 
427
444
  /**
@@ -476,7 +493,7 @@ common.getReportList = (that, url, query, callBack) => {
476
493
 
477
494
 
478
495
  /**
479
- * 字典表 里通过Id 查 name, 或者查询多条数据
496
+ * 字典表 里通过Id 查 name, 或者查询多条数据 (单条返回 name, 多条返回数组)
480
497
  * @param {Array} props ['服务名', '表名', '字段名']
481
498
  * @param {String, Number, Array} val 值, 传入id, 或者 [id1, id2, ...]
482
499
  *
@@ -518,5 +535,15 @@ common.dic = async (props, val) => {
518
535
  }
519
536
  }
520
537
  }
521
-
538
+ /**
539
+ * 快速给页面变量赋值
540
+ * @param {*} url 请求接口的地址
541
+ * @param {*} query 接口参数
542
+ * @param {*} prop 要为哪个字段名赋值
543
+ */
544
+ common.getLabelList = (that, prop, url, query) => {
545
+ common.getObject(url)(query).then(res => {
546
+ that[prop] = res.data
547
+ })
548
+ }
522
549
  export default common
@@ -150,6 +150,10 @@
150
150
  <HKCascader v-model="inputValue[optionItem.fieldName]" :placeholder="optionItem.defaultValue"
151
151
  :style="{ width: optionItem.itemWidth }" :optionItem="optionItem"></HKCascader>
152
152
  </el-form-item>
153
+ <el-form-item :label="optionItem.displayName" v-if="optionItem.component "
154
+ :style="{ width: optionItem.itemWidth }" class="dateRange">
155
+ <component :is="optionItem.component" v-model="inputValue[optionItem.fieldName]" :placeholder="optionItem.defaultValue" v-bind="optionItem" />
156
+ </el-form-item>
153
157
  </div>
154
158
 
155
159
  <el-form-item v-if="!!getBtnContentIsOut().length">
@@ -173,7 +177,7 @@
173
177
  </div>
174
178
  <slot name="search_custemBtn" />
175
179
  <!-- <div class="buttonBox">
176
-
180
+
177
181
  <el-form-item v-if="!!getBtnContentIsOut().length">
178
182
  <el-dropdown
179
183
  trigger="click"
@@ -3,7 +3,7 @@
3
3
  .select-empty-group {
4
4
  width: 660px;
5
5
  font-size: 14px;
6
-
6
+ padding: 10px 0;
7
7
  .row {
8
8
  position: relative;
9
9
  margin: 0 8px;
@@ -8,7 +8,7 @@
8
8
  <span class="title">商品品类</span>
9
9
  </div>
10
10
  <div class="body">
11
- <el-radio-group class="product-type" v-model="selectedType" size="mini">
11
+ <el-radio-group class="product-type" v-model="selectedType" size="mini" @change="changeProductType">
12
12
  <el-radio
13
13
  v-for="typeItem in $enumList.EnumProductType"
14
14
  :key="typeItem.Id"
@@ -391,13 +391,6 @@ export default {
391
391
  mixins: [MIX_VMODEL],
392
392
  components: {},
393
393
  watch: {
394
- selectedType: {
395
- handler(nVal) {
396
- this.loadSource();
397
- },
398
- immediate: true
399
- },
400
-
401
394
  selectedAreaCode: {
402
395
  handler(nVal) {
403
396
  console.log(nVal, "=====>");
@@ -7,7 +7,10 @@ export default {
7
7
  },
8
8
  mountedInit() {
9
9
  },
10
-
10
+ // 切换商品品类
11
+ changeProductType (value) {
12
+ this.loadSource();
13
+ },
11
14
  checkboxHandle(event, fieldName) {
12
15
  this.keys = []
13
16
  this.checkboxForm = {
@@ -36,12 +39,21 @@ export default {
36
39
  typeId: parseInt(this.selectedType)
37
40
  }).then(res => {
38
41
  if (res.data) {
39
- this.areaCodeDicList = res.data.areaCodeDic
40
- this.arsIdDicList = res.data.arsIdDic
41
- this.denomIncList = res.data.denomInc
42
+ this.areaCodeDicList = res.data.areaCodeDic // 地区/种类列表
43
+ this.arsIdDicList = res.data.arsIdDic // 运营商类目列表
44
+ this.denomIncList = res.data.denomInc // 面值列表
42
45
  } else {
43
46
  this.areaCodeDicList = this.arsIdDicList = this.denomIncList = []
44
47
  }
48
+ let list = []
49
+ this.areaCodeDicList.forEach(item => { list.push([item.value]) });
50
+ this.selectedAreaCode = list // selectedAreaCode 地区/种类
51
+ this.selectedArsId = this.formatData(this.arsIdDicList) // selectedArsId 运营商类目
52
+ this.selectedDenomInc = Object.values(this.denomIncList) // selectedDenomInc 面值
53
+ this.checkboxHandle()
45
54
  })
55
+ },
56
+ formatData (list) {
57
+ return list.map(item => item.value)
46
58
  }
47
59
  }
@@ -8,6 +8,17 @@ VueRouter.prototype.push = function push(location) {
8
8
  return originalPush.call(this, location).catch(err => err)
9
9
  }
10
10
  const routes = [
11
+ {
12
+ path: '/redirect',
13
+ component: () => import('../views/layout/index'),
14
+ hidden: true,
15
+ children: [
16
+ {
17
+ path: '/redirect/:path(.*)',
18
+ component: () => import('@/views/redirect/index')
19
+ }
20
+ ]
21
+ },
11
22
  {
12
23
  path: '/',
13
24
  name: 'layout',
@@ -20,7 +31,7 @@ const routes = [
20
31
  path: '/home',
21
32
  name: 'home',
22
33
  component: () => import('../views/layout/index'),
23
- meta: { title: '首页', icon: 'example' },
34
+ meta: { title: '首页', icon: 'example', affix: true,},
24
35
  children: [{
25
36
  path:'/',
26
37
  component: () => import('../views/home/index'),
@@ -5,6 +5,7 @@ import app from './modules/app'
5
5
  import user from './modules/user'
6
6
  import widgetdata from './modules/widgetdata'
7
7
  import dic from './modules/dic'
8
+ import tagsView from './modules/tagsView'
8
9
  import oss from './modules/oss'
9
10
 
10
11
  const globalCfg = require("../modules/module.config");
@@ -44,6 +45,7 @@ let storeCfg = {
44
45
 
45
46
  modules: {
46
47
  app,
48
+ tagsView,
47
49
  user,
48
50
  widgetdata,
49
51
  dic,
@@ -0,0 +1,160 @@
1
+ const state = {
2
+ visitedViews: [],
3
+ cachedViews: []
4
+ }
5
+
6
+ const mutations = {
7
+ ADD_VISITED_VIEW: (state, view) => {
8
+ if (state.visitedViews.some(v => v.path === view.path)) return
9
+ state.visitedViews.push(
10
+ Object.assign({}, view, {
11
+ title: view.meta.title || 'no-name'
12
+ })
13
+ )
14
+ },
15
+ ADD_CACHED_VIEW: (state, view) => {
16
+ if (state.cachedViews.includes(view.name)) return
17
+ if (!view.meta.noCache) {
18
+ state.cachedViews.push(view.name)
19
+ }
20
+ },
21
+
22
+ DEL_VISITED_VIEW: (state, view) => {
23
+ for (const [i, v] of state.visitedViews.entries()) {
24
+ if (v.path === view.path) {
25
+ state.visitedViews.splice(i, 1)
26
+ break
27
+ }
28
+ }
29
+ },
30
+ DEL_CACHED_VIEW: (state, view) => {
31
+ const index = state.cachedViews.indexOf(view.name)
32
+ index > -1 && state.cachedViews.splice(index, 1)
33
+ },
34
+
35
+ DEL_OTHERS_VISITED_VIEWS: (state, view) => {
36
+ state.visitedViews = state.visitedViews.filter(v => {
37
+ return v.meta.affix || v.path === view.path
38
+ })
39
+ },
40
+ DEL_OTHERS_CACHED_VIEWS: (state, view) => {
41
+ const index = state.cachedViews.indexOf(view.name)
42
+ if (index > -1) {
43
+ state.cachedViews = state.cachedViews.slice(index, index + 1)
44
+ } else {
45
+ // if index = -1, there is no cached tags
46
+ state.cachedViews = []
47
+ }
48
+ },
49
+
50
+ DEL_ALL_VISITED_VIEWS: state => {
51
+ // keep affix tags
52
+ const affixTags = state.visitedViews.filter(tag => tag.meta.affix)
53
+ state.visitedViews = affixTags
54
+ },
55
+ DEL_ALL_CACHED_VIEWS: state => {
56
+ state.cachedViews = []
57
+ },
58
+
59
+ UPDATE_VISITED_VIEW: (state, view) => {
60
+ for (let v of state.visitedViews) {
61
+ if (v.path === view.path) {
62
+ v = Object.assign(v, view)
63
+ break
64
+ }
65
+ }
66
+ }
67
+ }
68
+
69
+ const actions = {
70
+ addView({ dispatch }, view) {
71
+ dispatch('addVisitedView', view)
72
+ dispatch('addCachedView', view)
73
+ },
74
+ addVisitedView({ commit }, view) {
75
+ commit('ADD_VISITED_VIEW', view)
76
+ },
77
+ addCachedView({ commit }, view) {
78
+ commit('ADD_CACHED_VIEW', view)
79
+ },
80
+
81
+ delView({ dispatch, state }, view) {
82
+ return new Promise(resolve => {
83
+ dispatch('delVisitedView', view)
84
+ dispatch('delCachedView', view)
85
+ resolve({
86
+ visitedViews: [...state.visitedViews],
87
+ cachedViews: [...state.cachedViews]
88
+ })
89
+ })
90
+ },
91
+ delVisitedView({ commit, state }, view) {
92
+ return new Promise(resolve => {
93
+ commit('DEL_VISITED_VIEW', view)
94
+ resolve([...state.visitedViews])
95
+ })
96
+ },
97
+ delCachedView({ commit, state }, view) {
98
+ return new Promise(resolve => {
99
+ commit('DEL_CACHED_VIEW', view)
100
+ resolve([...state.cachedViews])
101
+ })
102
+ },
103
+
104
+ delOthersViews({ dispatch, state }, view) {
105
+ return new Promise(resolve => {
106
+ dispatch('delOthersVisitedViews', view)
107
+ dispatch('delOthersCachedViews', view)
108
+ resolve({
109
+ visitedViews: [...state.visitedViews],
110
+ cachedViews: [...state.cachedViews]
111
+ })
112
+ })
113
+ },
114
+ delOthersVisitedViews({ commit, state }, view) {
115
+ return new Promise(resolve => {
116
+ commit('DEL_OTHERS_VISITED_VIEWS', view)
117
+ resolve([...state.visitedViews])
118
+ })
119
+ },
120
+ delOthersCachedViews({ commit, state }, view) {
121
+ return new Promise(resolve => {
122
+ commit('DEL_OTHERS_CACHED_VIEWS', view)
123
+ resolve([...state.cachedViews])
124
+ })
125
+ },
126
+
127
+ delAllViews({ dispatch, state }, view) {
128
+ return new Promise(resolve => {
129
+ dispatch('delAllVisitedViews', view)
130
+ dispatch('delAllCachedViews', view)
131
+ resolve({
132
+ visitedViews: [...state.visitedViews],
133
+ cachedViews: [...state.cachedViews]
134
+ })
135
+ })
136
+ },
137
+ delAllVisitedViews({ commit, state }) {
138
+ return new Promise(resolve => {
139
+ commit('DEL_ALL_VISITED_VIEWS')
140
+ resolve([...state.visitedViews])
141
+ })
142
+ },
143
+ delAllCachedViews({ commit, state }) {
144
+ return new Promise(resolve => {
145
+ commit('DEL_ALL_CACHED_VIEWS')
146
+ resolve([...state.cachedViews])
147
+ })
148
+ },
149
+
150
+ updateVisitedView({ commit }, view) {
151
+ commit('UPDATE_VISITED_VIEW', view)
152
+ }
153
+ }
154
+
155
+ export default {
156
+ namespaced: true,
157
+ state,
158
+ mutations,
159
+ actions
160
+ }
@@ -2,11 +2,16 @@ const filterRules = ({ required, type, min, max }) => {
2
2
  let validate = [];
3
3
  if (required) {
4
4
  // 必填项
5
- validate.push({
6
- required: true,
7
- message: "该输入项为必填项",
8
- trigger: "change",
9
- });
5
+ validate.push({ required: true, message: "该输入项为必填项", trigger: "change",});
6
+ }
7
+ if(min&&max){
8
+ validate.push({ min:min,max:max, message: '字符长度在'+min+'至'+max+'之间!', trigger: 'change' })
9
+ }
10
+ if(min){
11
+ validate.push({ min:min, message: '最少输入'+min+'个字符!', trigger: 'change' })
12
+ }
13
+ if(max){
14
+ validate.push({ min:1,max:max, message: '最多输入'+max+'个字符!', trigger: 'change' })
10
15
  }
11
16
  if (type) {
12
17
  let message = "";
@@ -25,10 +25,10 @@ export default {
25
25
  .app-main {
26
26
  display: flex;
27
27
  /*50 = navbar */
28
- height: calc(100vh - 50px);
28
+ height: calc(100vh - 85px);
29
29
  width: 100%;
30
30
  position: relative;
31
- overflow: hidden;
31
+ overflow: auto;
32
32
  }
33
33
  .footer {
34
34
  position: absolute;
@@ -64,7 +64,7 @@
64
64
  </el-tag>
65
65
  </div>
66
66
  <!-- 租户 -->
67
- <div class="tenant" v-if="userProfile.TId">
67
+ <div class="tenant" v-if="userProfile && userProfile.TId">
68
68
  <el-dropdown @command="changeTenant">
69
69
  <span class="el-dropdown-link">
70
70
  {{tenantList && tenantList[userProfile.TId]}}
@@ -161,7 +161,7 @@ export default {
161
161
  if (userName === "admin") {
162
162
  this.LoginName = true;
163
163
  }
164
- if(this.userProfile.TId) this.getTenantList()
164
+ if(this.userProfile &&this.userProfile.TId) this.getTenantList()
165
165
  },
166
166
  methods: {
167
167
  changeTenant (id) {
@@ -0,0 +1,94 @@
1
+ <template>
2
+ <el-scrollbar ref="scrollContainer" :vertical="false" class="scroll-container" @wheel.native.prevent="handleScroll">
3
+ <slot />
4
+ </el-scrollbar>
5
+ </template>
6
+
7
+ <script>
8
+ const tagAndTagSpacing = 4 // tagAndTagSpacing
9
+
10
+ export default {
11
+ name: 'ScrollPane',
12
+ data() {
13
+ return {
14
+ left: 0
15
+ }
16
+ },
17
+ computed: {
18
+ scrollWrapper() {
19
+ return this.$refs.scrollContainer.$refs.wrap
20
+ }
21
+ },
22
+ mounted() {
23
+ this.scrollWrapper.addEventListener('scroll', this.emitScroll, true)
24
+ },
25
+ beforeDestroy() {
26
+ this.scrollWrapper.removeEventListener('scroll', this.emitScroll)
27
+ },
28
+ methods: {
29
+ handleScroll(e) {
30
+ const eventDelta = e.wheelDelta || -e.deltaY * 40
31
+ const $scrollWrapper = this.scrollWrapper
32
+ $scrollWrapper.scrollLeft = $scrollWrapper.scrollLeft + eventDelta / 4
33
+ },
34
+ emitScroll() {
35
+ this.$emit('scroll')
36
+ },
37
+ moveToTarget(currentTag) {
38
+ const $container = this.$refs.scrollContainer.$el
39
+ const $containerWidth = $container.offsetWidth
40
+ const $scrollWrapper = this.scrollWrapper
41
+ const tagList = this.$parent.$refs.tag
42
+
43
+ let firstTag = null
44
+ let lastTag = null
45
+
46
+ // find first tag and last tag
47
+ if (tagList.length > 0) {
48
+ firstTag = tagList[0]
49
+ lastTag = tagList[tagList.length - 1]
50
+ }
51
+
52
+ if (firstTag === currentTag) {
53
+ $scrollWrapper.scrollLeft = 0
54
+ } else if (lastTag === currentTag) {
55
+ $scrollWrapper.scrollLeft = $scrollWrapper.scrollWidth - $containerWidth
56
+ } else {
57
+ // find preTag and nextTag
58
+ const currentIndex = tagList.findIndex(item => item === currentTag)
59
+ const prevTag = tagList[currentIndex - 1]
60
+ const nextTag = tagList[currentIndex + 1]
61
+
62
+ // the tag's offsetLeft after of nextTag
63
+ const afterNextTagOffsetLeft = nextTag.$el.offsetLeft + nextTag.$el.offsetWidth + tagAndTagSpacing
64
+
65
+ // the tag's offsetLeft before of prevTag
66
+ const beforePrevTagOffsetLeft = prevTag.$el.offsetLeft - tagAndTagSpacing
67
+
68
+ if (afterNextTagOffsetLeft > $scrollWrapper.scrollLeft + $containerWidth) {
69
+ $scrollWrapper.scrollLeft = afterNextTagOffsetLeft - $containerWidth
70
+ } else if (beforePrevTagOffsetLeft < $scrollWrapper.scrollLeft) {
71
+ $scrollWrapper.scrollLeft = beforePrevTagOffsetLeft
72
+ }
73
+ }
74
+ }
75
+ }
76
+ }
77
+ </script>
78
+
79
+ <style lang="scss" scoped>
80
+ .scroll-container {
81
+ white-space: nowrap;
82
+ position: relative;
83
+ overflow: hidden;
84
+ width: 100%;
85
+ ::v-deep {
86
+ .el-scrollbar__bar {
87
+ bottom: 0px;
88
+ }
89
+ .el-scrollbar__wrap {
90
+ height: 49px;
91
+ }
92
+ }
93
+ }
94
+ </style>
@@ -0,0 +1,291 @@
1
+ <template>
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
+ @click.middle.native="!isAffix(tag)?closeSelectedTag(tag):''"
13
+ @contextmenu.prevent.native="openMenu(tag,$event)"
14
+ >
15
+ {{ tag.title }}
16
+ <span v-if="!isAffix(tag)" class="el-icon-close" @click.prevent.stop="closeSelectedTag(tag)" />
17
+ </router-link>
18
+ </scroll-pane>
19
+ <ul v-show="visible" :style="{left:left+'px',top:top+'px'}" class="contextmenu">
20
+ <li @click="refreshSelectedTag(selectedTag)">刷新</li>
21
+ <li v-if="!isAffix(selectedTag)" @click="closeSelectedTag(selectedTag)">关闭</li>
22
+ <li @click="closeOthersTags">关闭其他</li>
23
+ <li @click="closeAllTags(selectedTag)">关闭全部</li>
24
+ </ul>
25
+ </div>
26
+ </template>
27
+
28
+ <script>
29
+ import ScrollPane from './ScrollPane'
30
+ import path from 'path'
31
+ import router from '../../../../router/index'
32
+ export default {
33
+ components: { ScrollPane },
34
+ data() {
35
+ return {
36
+ visible: false,
37
+ top: 0,
38
+ left: 0,
39
+ selectedTag: {},
40
+ affixTags: []
41
+ }
42
+ },
43
+ computed: {
44
+ visitedViews() {
45
+ return this.$store.state.tagsView.visitedViews
46
+ },
47
+ routes() {
48
+ return router.options.routes
49
+ }
50
+ },
51
+ watch: {
52
+ $route() {
53
+ this.addTags()
54
+ this.moveToCurrentTag()
55
+ },
56
+ visible(value) {
57
+ if (value) {
58
+ document.body.addEventListener('click', this.closeMenu)
59
+ } else {
60
+ document.body.removeEventListener('click', this.closeMenu)
61
+ }
62
+ }
63
+ },
64
+ mounted() {
65
+ this.initTags()
66
+ this.addTags()
67
+ },
68
+ methods: {
69
+ isActive(route) {
70
+ return route.path === this.$route.path
71
+ },
72
+ isAffix(tag) {
73
+ return tag.meta && tag.meta.affix
74
+ },
75
+ filterAffixTags(routes, basePath = '/') {
76
+ let tags = []
77
+ routes.forEach(route => {
78
+ if (route.meta && route.meta.affix) {
79
+ const tagPath = path.resolve(basePath, route.path)
80
+ tags.push({
81
+ fullPath: tagPath,
82
+ path: tagPath,
83
+ name: route.name,
84
+ meta: { ...route.meta }
85
+ })
86
+ }
87
+ if (route.children) {
88
+ const tempTags = this.filterAffixTags(route.children, route.path)
89
+ if (tempTags.length >= 1) {
90
+ tags = [...tags, ...tempTags]
91
+ }
92
+ }
93
+ })
94
+ return tags
95
+ },
96
+ initTags() {
97
+ const affixTags = this.affixTags = this.filterAffixTags(this.routes)
98
+ for (const tag of affixTags) {
99
+ if (tag.name) {
100
+ this.$store.dispatch('tagsView/addVisitedView', tag)
101
+ }
102
+ }
103
+ },
104
+ addTags() {
105
+ const { meta } = this.$route
106
+ if (meta && meta.title) {
107
+ this.$store.dispatch('tagsView/addView', this.$route)
108
+ }
109
+ return false
110
+ },
111
+ moveToCurrentTag() {
112
+ const tags = this.$refs.tag
113
+ this.$nextTick(() => {
114
+ for (const tag of tags) {
115
+ if (tag.to.path === this.$route.path) {
116
+ this.$refs.scrollPane.moveToTarget(tag)
117
+ // when query is different then update
118
+ if (tag.to.fullPath !== this.$route.fullPath) {
119
+ this.$store.dispatch('tagsView/updateVisitedView', this.$route)
120
+ }
121
+ break
122
+ }
123
+ }
124
+ })
125
+ },
126
+ refreshSelectedTag(view) {
127
+ this.$store.dispatch('tagsView/delCachedView', view).then(() => {
128
+ const { fullPath } = view
129
+ this.$nextTick(() => {
130
+ this.$router.replace({
131
+ path: '/redirect' + fullPath
132
+ })
133
+ })
134
+ })
135
+ },
136
+ closeSelectedTag(view) {
137
+ this.$store.dispatch('tagsView/delView', view).then(({ visitedViews }) => {
138
+ if (this.isActive(view)) {
139
+ this.toLastView(visitedViews, view)
140
+ }
141
+ })
142
+ },
143
+ closeOthersTags() {
144
+ this.$router.push(this.selectedTag)
145
+ this.$store.dispatch('tagsView/delOthersViews', this.selectedTag).then(() => {
146
+ this.moveToCurrentTag()
147
+ })
148
+ },
149
+ closeAllTags(view) {
150
+ this.$store.dispatch('tagsView/delAllViews').then(({ visitedViews }) => {
151
+ if (this.affixTags.some(tag => tag.path === view.path)) {
152
+ return
153
+ }
154
+ this.toLastView(visitedViews, view)
155
+ })
156
+ },
157
+ toLastView(visitedViews, view) {
158
+ const latestView = visitedViews.slice(-1)[0]
159
+ if (latestView) {
160
+ this.$router.push(latestView.fullPath)
161
+ } else {
162
+ // now the default is to redirect to the home page if there is no tags-view,
163
+ // you can adjust it according to your needs.
164
+ if (view.name === 'Dashboard') {
165
+ // to reload home page
166
+ this.$router.replace({ path: '/redirect' + view.fullPath })
167
+ } else {
168
+ this.$router.push('/')
169
+ }
170
+ }
171
+ },
172
+ openMenu(tag, e) {
173
+ const menuMinWidth = 105
174
+ const offsetLeft = this.$el.getBoundingClientRect().left // container margin left
175
+ const offsetWidth = this.$el.offsetWidth // container width
176
+ const maxLeft = offsetWidth - menuMinWidth // left boundary
177
+ const left = e.clientX - offsetLeft + 15 // 15: margin right
178
+
179
+ if (left > maxLeft) {
180
+ this.left = maxLeft
181
+ } else {
182
+ this.left = left
183
+ }
184
+
185
+ this.top = e.clientY
186
+ this.visible = true
187
+ this.selectedTag = tag
188
+ },
189
+ closeMenu() {
190
+ this.visible = false
191
+ },
192
+ handleScroll() {
193
+ this.closeMenu()
194
+ }
195
+ }
196
+ }
197
+ </script>
198
+
199
+ <style lang="scss" scoped>
200
+ .tags-view-container {
201
+ height: 34px;
202
+ width: 100%;
203
+ background: #fff;
204
+ border-bottom: 1px solid #d8dce5;
205
+ box-shadow: 0 1px 3px 0 rgba(0, 0, 0, .12), 0 0 3px 0 rgba(0, 0, 0, .04);
206
+ .tags-view-wrapper {
207
+ .tags-view-item {
208
+ display: inline-block;
209
+ position: relative;
210
+ cursor: pointer;
211
+ height: 26px;
212
+ line-height: 26px;
213
+ border: 1px solid #d8dce5;
214
+ color: #495060;
215
+ background: #fff;
216
+ padding: 0 8px;
217
+ font-size: 12px;
218
+ margin-left: 5px;
219
+ margin-top: 4px;
220
+ &:first-of-type {
221
+ margin-left: 15px;
222
+ }
223
+ &:last-of-type {
224
+ margin-right: 15px;
225
+ }
226
+ &.active {
227
+ background-color: #409EFF;
228
+ color: #fff;
229
+ border-color: #409EFF;
230
+ &::before {
231
+ content: '';
232
+ background: #fff;
233
+ display: inline-block;
234
+ width: 8px;
235
+ height: 8px;
236
+ border-radius: 50%;
237
+ position: relative;
238
+ margin-right: 2px;
239
+ }
240
+ }
241
+ }
242
+ }
243
+ .contextmenu {
244
+ margin: 0;
245
+ background: #fff;
246
+ z-index: 3000;
247
+ position: absolute;
248
+ list-style-type: none;
249
+ padding: 5px 0;
250
+ border-radius: 4px;
251
+ font-size: 12px;
252
+ font-weight: 400;
253
+ color: #333;
254
+ box-shadow: 2px 2px 3px 0 rgba(0, 0, 0, .3);
255
+ li {
256
+ margin: 0;
257
+ padding: 7px 16px;
258
+ cursor: pointer;
259
+ &:hover {
260
+ background: #eee;
261
+ }
262
+ }
263
+ }
264
+ }
265
+ </style>
266
+
267
+ <style lang="scss">
268
+ //reset element css of el-icon-close
269
+ .tags-view-wrapper {
270
+ .tags-view-item {
271
+ .el-icon-close {
272
+ width: 16px;
273
+ height: 16px;
274
+ vertical-align: 2px;
275
+ border-radius: 50%;
276
+ text-align: center;
277
+ transition: all .3s cubic-bezier(.645, .045, .355, 1);
278
+ transform-origin: 100% 50%;
279
+ &:before {
280
+ transform: scale(.6);
281
+ display: inline-block;
282
+ vertical-align: -3px;
283
+ }
284
+ &:hover {
285
+ background-color: #b4bccc;
286
+ color: #fff;
287
+ }
288
+ }
289
+ }
290
+ }
291
+ </style>
@@ -1,3 +1,4 @@
1
1
  export { default as Navbar } from './Navbar'
2
2
  export { default as Sidebar } from './Sidebar'
3
3
  export { default as AppMain } from './AppMain'
4
+ export { default as TagsView } from './TagsView/index.vue'
@@ -4,6 +4,7 @@
4
4
  <div class="main-container">
5
5
  <div :class="{'fixed-header':fixedHeader}">
6
6
  <navbar />
7
+ <tags-view />
7
8
  </div>
8
9
  <app-main />
9
10
  </div>
@@ -11,14 +12,15 @@
11
12
  </template>
12
13
 
13
14
  <script>
14
- import { Navbar, Sidebar, AppMain } from "./components";
15
+ import { Navbar, Sidebar, AppMain, TagsView } from "./components";
15
16
  import { main } from '@/utils/getMenu'
16
17
  export default {
17
18
  name: "Layout",
18
19
  components: {
19
20
  Navbar,
20
21
  Sidebar,
21
- AppMain
22
+ AppMain,
23
+ TagsView
22
24
  },
23
25
  data() {
24
26
  return {
@@ -0,0 +1,12 @@
1
+ <script>
2
+ export default {
3
+ created() {
4
+ const { params, query } = this.$route
5
+ const { path } = params
6
+ this.$router.replace({ path: '/' + path, query })
7
+ },
8
+ render: function(h) {
9
+ return h() // avoid warning message
10
+ }
11
+ }
12
+ </script>