style:上下布局完成
Showing
17 changed files
with
1233 additions
and
19 deletions
src/layout1/components/AppMain.vue
0 → 100644
1 | <template> | ||
2 | <section class="app-main"> | ||
3 | <transition name="fade-transform" mode="out-in"> | ||
4 | <router-view /> | ||
5 | </transition> | ||
6 | </section> | ||
7 | </template> | ||
8 | <script> | ||
9 | export default { | ||
10 | name: 'AppMain', | ||
11 | computed: { | ||
12 | key () { | ||
13 | return this.$route.path | ||
14 | } | ||
15 | } | ||
16 | } | ||
17 | </script> | ||
18 | <style lang="scss" scoped> | ||
19 | .hasTagsView { | ||
20 | .app-main { | ||
21 | height: calc(100% - 41px); | ||
22 | overflow-x: auto; | ||
23 | padding: 5px; | ||
24 | box-sizing: border-box; | ||
25 | background-color: #EDF1F7; | ||
26 | box-sizing: border-box; | ||
27 | |||
28 | } | ||
29 | } | ||
30 | </style> | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
src/layout1/components/Navbar.vue
0 → 100644
1 | <template> | ||
2 | <div class="navbar-con"> | ||
3 | <div class="navbar"> | ||
4 | <div class="logo"> | ||
5 | {{ title }} | ||
6 | </div> | ||
7 | <div class="backdrop"> | ||
8 | <sidebar /> | ||
9 | </div> | ||
10 | <div class="right-menu"> | ||
11 | <div class="dataView pointer" @click="handleDataView">大屏展示</div> | ||
12 | <svg-icon class="function" icon-class='function' /> | ||
13 | <el-dropdown class="avatar-container right-menu-item hover-effect" trigger="hover" @command="handleCommand"> | ||
14 | <div class="avatar-wrapper"> | ||
15 | <span style="padding-right:10px">{{ name }}</span> | ||
16 | <img :src="avatar + '?imageView2/1/w/80/h/80'" class="user-avatar" /> | ||
17 | </div> | ||
18 | <el-dropdown-menu slot="dropdown"> | ||
19 | <el-dropdown-item command="a">个人中心</el-dropdown-item> | ||
20 | <el-dropdown-item command="b">退出</el-dropdown-item> | ||
21 | </el-dropdown-menu> | ||
22 | </el-dropdown> | ||
23 | </div> | ||
24 | </div> | ||
25 | </div> | ||
26 | </template> | ||
27 | <script> | ||
28 | import defaultSettings from '@/settings' | ||
29 | import Sidebar from './Sidebar' | ||
30 | import { mapGetters } from 'vuex' | ||
31 | export default { | ||
32 | components: { | ||
33 | Sidebar, | ||
34 | }, | ||
35 | computed: { | ||
36 | ...mapGetters(['sidebar', 'avatar', 'name']) | ||
37 | }, | ||
38 | data () { | ||
39 | return { | ||
40 | title: defaultSettings.title | ||
41 | } | ||
42 | }, | ||
43 | methods: { | ||
44 | handleDataView () { | ||
45 | const { href } = this.$router.resolve('/dataView'); | ||
46 | window.open(href, '_blank'); | ||
47 | }, | ||
48 | themeChange (val) { | ||
49 | this.$store.dispatch('app/updateTheme', val) | ||
50 | }, | ||
51 | handleCommand (command) { | ||
52 | if (command == 'a') { | ||
53 | } else { | ||
54 | |||
55 | } | ||
56 | } | ||
57 | } | ||
58 | } | ||
59 | </script> | ||
60 | <style lang="scss" scoped> | ||
61 | .navbar-con { | ||
62 | position: relative; | ||
63 | |||
64 | .logo { | ||
65 | color: #fff; | ||
66 | font-size: 26px; | ||
67 | font-weight: 700; | ||
68 | } | ||
69 | } | ||
70 | |||
71 | .dataView { | ||
72 | color: #fff; | ||
73 | } | ||
74 | |||
75 | .NoticeBar { | ||
76 | position: absolute; | ||
77 | bottom: 0; | ||
78 | } | ||
79 | |||
80 | .el-dropdown-menu { | ||
81 | padding: 0 !important; | ||
82 | border: 1px solid #EBEEF5; | ||
83 | box-shadow: 0 2px 10px 0 rgba(0, 0, 0, 0.12); | ||
84 | border-radius: 4px 0 0 4px 4px; | ||
85 | |||
86 | .el-dropdown-menu__item { | ||
87 | text-align: center; | ||
88 | margin-top: 0 !important; | ||
89 | font-size: 14px; | ||
90 | font-family: PingFangSC-Regular, PingFang SC; | ||
91 | font-weight: 400; | ||
92 | color: #4A4A4A; | ||
93 | width: 140px; | ||
94 | height: 36px; | ||
95 | line-height: 36px; | ||
96 | } | ||
97 | |||
98 | .el-dropdown-menu__item:nth-child(6) { | ||
99 | border-top: 1px solid #EBEEF5; | ||
100 | } | ||
101 | |||
102 | .popper__arrow { | ||
103 | top: -11px !important; | ||
104 | left: 110px !important; | ||
105 | transform: rotate(0deg) scale(2); | ||
106 | } | ||
107 | |||
108 | .el-dropdown-menu__item:not(.is-disabled):hover, | ||
109 | .el-dropdown-menu__item:focus { | ||
110 | background: #F6F7F9; | ||
111 | color: #4A4A4A; | ||
112 | } | ||
113 | } | ||
114 | |||
115 | .navbar { | ||
116 | height: $headerHeight; | ||
117 | overflow: hidden; | ||
118 | position: relative; | ||
119 | background: linear-gradient(270deg, #148CEE 0%, #1870E3 100%); //默认颜色 | ||
120 | box-shadow: 0 1px 0px rgba(0, 21, 41, 0.08); | ||
121 | display: flex; | ||
122 | align-items: center; | ||
123 | padding: 0 20px; | ||
124 | justify-content: space-between; | ||
125 | |||
126 | .header-logo { | ||
127 | width: 300px; | ||
128 | } | ||
129 | |||
130 | .backdrop { | ||
131 | flex: 1; | ||
132 | width: 60%; | ||
133 | background: url('../../image/backdrop.png'); | ||
134 | background-size: 100% 100%; | ||
135 | height: $headerHeight; | ||
136 | display: flex; | ||
137 | align-items: center; | ||
138 | padding-left: 20px; | ||
139 | } | ||
140 | |||
141 | .hamburger-container { | ||
142 | line-height: 43px; | ||
143 | height: 100%; | ||
144 | float: left; | ||
145 | cursor: pointer; | ||
146 | transition: background 0.3s; | ||
147 | -webkit-tap-highlight-color: transparent; | ||
148 | |||
149 | &:hover { | ||
150 | background: rgba(0, 0, 0, 0.025); | ||
151 | } | ||
152 | } | ||
153 | |||
154 | .breadcrumb-container { | ||
155 | float: left; | ||
156 | } | ||
157 | |||
158 | .right-menu { | ||
159 | float: right; | ||
160 | height: 100%; | ||
161 | line-height: 50px; | ||
162 | display: flex; | ||
163 | align-items: center; | ||
164 | |||
165 | .function { | ||
166 | margin: 0 15px; | ||
167 | cursor: pointer; | ||
168 | } | ||
169 | |||
170 | .shutdown { | ||
171 | font-size: 20px; | ||
172 | margin-left: 15px; | ||
173 | cursor: pointer; | ||
174 | } | ||
175 | |||
176 | .organization-item { | ||
177 | margin-right: 40px; | ||
178 | margin-top: -40px !important; | ||
179 | } | ||
180 | |||
181 | .item { | ||
182 | margin-right: 40px; | ||
183 | margin-top: -20px; | ||
184 | line-height: 18.4px; | ||
185 | cursor: pointer; | ||
186 | position: relative; | ||
187 | |||
188 | .item-box { | ||
189 | position: absolute; | ||
190 | top: -5px; | ||
191 | left: 3px; | ||
192 | width: 100%; | ||
193 | min-width: 25px; | ||
194 | height: 25px; | ||
195 | cursor: pointer; | ||
196 | z-index: 100; | ||
197 | } | ||
198 | } | ||
199 | |||
200 | &:focus { | ||
201 | outline: none; | ||
202 | } | ||
203 | |||
204 | .right-menu-item { | ||
205 | display: inline-block; | ||
206 | height: 100%; | ||
207 | font-size: 18px; | ||
208 | color: #fff; | ||
209 | vertical-align: text-bottom; | ||
210 | |||
211 | &.hover-effect { | ||
212 | cursor: pointer; | ||
213 | transition: background 0.3s; | ||
214 | display: flex; | ||
215 | align-items: center; | ||
216 | |||
217 | &:hover { | ||
218 | background: rgba(0, 0, 0, 0.025); | ||
219 | } | ||
220 | } | ||
221 | } | ||
222 | |||
223 | .avatar-wrapper { | ||
224 | position: relative; | ||
225 | display: flex; | ||
226 | height: 40px; | ||
227 | align-items: center; | ||
228 | |||
229 | .user-avatar { | ||
230 | cursor: pointer; | ||
231 | width: 35px; | ||
232 | height: 35px; | ||
233 | border-radius: 50%; | ||
234 | } | ||
235 | |||
236 | .el-icon-caret-bottom { | ||
237 | cursor: pointer; | ||
238 | position: absolute; | ||
239 | right: -15px; | ||
240 | top: 17px; | ||
241 | font-size: 12px; | ||
242 | } | ||
243 | } | ||
244 | } | ||
245 | } | ||
246 | </style> |
src/layout1/components/Sidebar/FixiOSBug.js
0 → 100644
1 | export default { | ||
2 | computed: { | ||
3 | device() { | ||
4 | return this.$store.state.app.device | ||
5 | } | ||
6 | }, | ||
7 | mounted() { | ||
8 | // In order to fix the click on menu on the ios device will trigger the mouseleave bug | ||
9 | // https://github.com/PanJiaChen/vue-element-admin/issues/1135 | ||
10 | this.fixBugIniOS() | ||
11 | }, | ||
12 | methods: { | ||
13 | fixBugIniOS() { | ||
14 | const $subMenu = this.$refs.subMenu | ||
15 | if ($subMenu) { | ||
16 | const handleMouseleave = $subMenu.handleMouseleave | ||
17 | $subMenu.handleMouseleave = (e) => { | ||
18 | if (this.device === 'mobile') { | ||
19 | return | ||
20 | } | ||
21 | handleMouseleave(e) | ||
22 | } | ||
23 | } | ||
24 | } | ||
25 | } | ||
26 | } |
src/layout1/components/Sidebar/Item.vue
0 → 100644
1 | <script> | ||
2 | export default { | ||
3 | name: 'MenuItem', | ||
4 | functional: true, | ||
5 | props: { | ||
6 | icon: { | ||
7 | type: String, | ||
8 | default: '' | ||
9 | }, | ||
10 | title: { | ||
11 | type: String, | ||
12 | default: '' | ||
13 | } | ||
14 | }, | ||
15 | render (h, context) { | ||
16 | const { icon, title } = context.props | ||
17 | const vnodes = [] | ||
18 | |||
19 | if (icon) { | ||
20 | if (icon.includes('el-icon')) { | ||
21 | vnodes.push(<i class={[icon, 'sub-el-icon']} />) | ||
22 | } else { | ||
23 | vnodes.push(<svg-icon icon-class={icon} />) | ||
24 | } | ||
25 | } | ||
26 | |||
27 | if (title) { | ||
28 | vnodes.push(<span slot='title'>{(title)}</span>) | ||
29 | } | ||
30 | return vnodes | ||
31 | } | ||
32 | } | ||
33 | </script> | ||
34 | |||
35 | <style scoped> | ||
36 | .sub-el-icon { | ||
37 | color: currentColor; | ||
38 | width: 1em; | ||
39 | height: 1em; | ||
40 | } | ||
41 | </style> |
src/layout1/components/Sidebar/Link.vue
0 → 100644
1 | <template> | ||
2 | <component :is="type" v-bind="linkProps(to)"> | ||
3 | <slot /> | ||
4 | </component> | ||
5 | </template> | ||
6 | |||
7 | <script> | ||
8 | import { isExternal } from '@/utils/validate' | ||
9 | |||
10 | export default { | ||
11 | props: { | ||
12 | to: { | ||
13 | type: String, | ||
14 | required: true | ||
15 | } | ||
16 | }, | ||
17 | computed: { | ||
18 | isExternal() { | ||
19 | return isExternal(this.to) | ||
20 | }, | ||
21 | type() { | ||
22 | if (this.isExternal) { | ||
23 | return 'a' | ||
24 | } | ||
25 | return 'router-link' | ||
26 | } | ||
27 | }, | ||
28 | methods: { | ||
29 | linkProps(to) { | ||
30 | if (this.isExternal) { | ||
31 | return { | ||
32 | href: to, | ||
33 | target: '_blank', | ||
34 | rel: 'noopener' | ||
35 | } | ||
36 | } | ||
37 | return { | ||
38 | to: to | ||
39 | } | ||
40 | } | ||
41 | } | ||
42 | } | ||
43 | </script> |
src/layout1/components/Sidebar/Logo.vue
0 → 100644
1 | <template> | ||
2 | <div class="sidebar-logo-container" :class="{ 'collapse': collapse }"> | ||
3 | <transition name="sidebarLogoFade"> | ||
4 | <router-link v-if="collapse" key="collapse" class="sidebar-logo-link" to="/"> | ||
5 | <img v-if="logo" :src="logo" class="sidebar-logo"> | ||
6 | <h1 v-else class="sidebar-title"> | ||
7 | {{ title }} | ||
8 | </h1> | ||
9 | </router-link> | ||
10 | <router-link v-else key="expand" class="sidebar-logo-link" to="/"> | ||
11 | <h1 class="sidebar-title"> | ||
12 | {{ title }} | ||
13 | </h1> | ||
14 | </router-link> | ||
15 | </transition> | ||
16 | </div> | ||
17 | </template> | ||
18 | |||
19 | <script> | ||
20 | import defaultSettings from '@/settings' | ||
21 | const { title } = defaultSettings | ||
22 | export default { | ||
23 | name: 'SidebarLogo', | ||
24 | props: { | ||
25 | collapse: { | ||
26 | type: Boolean, | ||
27 | required: true | ||
28 | } | ||
29 | }, | ||
30 | data () { | ||
31 | return { | ||
32 | title: title, | ||
33 | } | ||
34 | } | ||
35 | } | ||
36 | </script> | ||
37 | |||
38 | <style lang="scss" scoped> | ||
39 | .sidebarLogoFade-enter-active { | ||
40 | transition: opacity 1.5s; | ||
41 | } | ||
42 | |||
43 | .sidebarLogoFade-enter, | ||
44 | .sidebarLogoFade-leave-to { | ||
45 | opacity: 0; | ||
46 | } | ||
47 | |||
48 | .sidebar-logo-container { | ||
49 | position: relative; | ||
50 | width: 100%; | ||
51 | text-align: center; | ||
52 | overflow: hidden; | ||
53 | height: 100px; | ||
54 | |||
55 | & .sidebar-logo-link { | ||
56 | height: 100%; | ||
57 | width: 100%; | ||
58 | |||
59 | & .sidebar-logo { | ||
60 | width: 41px; | ||
61 | height: 39px; | ||
62 | vertical-align: middle; | ||
63 | // margin-left: 47px; | ||
64 | // margin-right: 48px; | ||
65 | margin-top: 22px; | ||
66 | } | ||
67 | |||
68 | & .sidebar-title { | ||
69 | margin: 0; | ||
70 | margin-top: 10px; | ||
71 | margin-bottom: 20px; | ||
72 | color: #fff; | ||
73 | font-weight: 600; | ||
74 | line-height: 25px; | ||
75 | font-size: 16px; | ||
76 | font-family: Avenir, Helvetica Neue, Arial, Helvetica, sans-serif; | ||
77 | vertical-align: middle; | ||
78 | } | ||
79 | } | ||
80 | |||
81 | &.collapse { | ||
82 | .sidebar-logo { | ||
83 | margin-right: 0px; | ||
84 | width: 32.8px; | ||
85 | height: 31.2px; | ||
86 | } | ||
87 | } | ||
88 | } | ||
89 | </style> |
1 | <template> | ||
2 | <div v-if="!item.hidden"> | ||
3 | <template | ||
4 | v-if="hasOneShowingChild(item.children, item) && (!onlyOneChild.children || onlyOneChild.noShowingChildren)"> | ||
5 | <app-link v-if="onlyOneChild.meta" :to="resolvePath(onlyOneChild.path)"> | ||
6 | <el-menu-item :index="resolvePath(onlyOneChild.path)" :class="{ 'submenu-title-noDropdown': !isNest }"> | ||
7 | <item :icon="onlyOneChild.meta.icon || (item.meta && item.meta.icon)" :title="onlyOneChild.meta.title" | ||
8 | class="menu-icon" /> | ||
9 | </el-menu-item> | ||
10 | </app-link> | ||
11 | </template> | ||
12 | |||
13 | <el-submenu v-else ref="subMenu" :index="resolvePath(item.path)" popper-append-to-body> | ||
14 | <template slot="title"> | ||
15 | <item v-if="item.meta" :icon="item.meta && item.meta.icon" :title="item.meta.title" /> | ||
16 | </template> | ||
17 | <sidebar-item v-for="child in item.children" :key="child.path" :is-nest="true" :item="child" | ||
18 | :base-path="resolvePath(child.path)" class="nest-menu" /> | ||
19 | </el-submenu> | ||
20 | </div> | ||
21 | </template> | ||
22 | |||
23 | <script> | ||
24 | import path from 'path' | ||
25 | import { isExternal } from '@/utils/validate' | ||
26 | import Item from './Item' | ||
27 | import AppLink from './Link' | ||
28 | import FixiOSBug from './FixiOSBug' | ||
29 | |||
30 | export default { | ||
31 | name: 'SidebarItem', | ||
32 | components: { Item, AppLink }, | ||
33 | mixins: [FixiOSBug], | ||
34 | props: { | ||
35 | // route object | ||
36 | item: { | ||
37 | type: Object, | ||
38 | required: true | ||
39 | }, | ||
40 | isNest: { | ||
41 | type: Boolean, | ||
42 | default: false | ||
43 | }, | ||
44 | basePath: { | ||
45 | type: String, | ||
46 | default: '' | ||
47 | } | ||
48 | }, | ||
49 | data () { | ||
50 | // To fix https://github.com/PanJiaChen/vue-admin-template/issues/237 | ||
51 | // TODO: refactor with render function | ||
52 | this.onlyOneChild = null | ||
53 | return {} | ||
54 | }, | ||
55 | methods: { | ||
56 | hasOneShowingChild (children = [], parent) { | ||
57 | const showingChildren = children.filter(item => { | ||
58 | if (item.hidden) { | ||
59 | return false | ||
60 | } else { | ||
61 | // Temp set(will be used if only has one showing child) | ||
62 | this.onlyOneChild = item | ||
63 | return true | ||
64 | } | ||
65 | }) | ||
66 | |||
67 | // When there is only one child router, the child router is displayed by default | ||
68 | if (showingChildren.length === 1) { | ||
69 | return true | ||
70 | } | ||
71 | |||
72 | // Show parent if there are no child router to display | ||
73 | if (showingChildren.length === 0) { | ||
74 | this.onlyOneChild = { ...parent, path: '', noShowingChildren: true } | ||
75 | return true | ||
76 | } | ||
77 | |||
78 | return false | ||
79 | }, | ||
80 | resolvePath (routePath) { | ||
81 | if (isExternal(routePath)) { | ||
82 | return routePath | ||
83 | } | ||
84 | if (isExternal(this.basePath)) { | ||
85 | return this.basePath | ||
86 | } | ||
87 | return path.resolve(this.basePath, routePath) | ||
88 | } | ||
89 | } | ||
90 | } | ||
91 | </script> | ||
92 | |||
93 | <style scoped> | ||
94 | /deep/.el-menu-item { | ||
95 | padding-left: 30px !important; | ||
96 | } | ||
97 | </style> | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
src/layout1/components/Sidebar/index.vue
0 → 100644
1 | <template> | ||
2 | <div> | ||
3 | <el-menu router :default-active="activeMenu" mode="horizontal"> | ||
4 | <!-- 权限菜单 --> | ||
5 | <!-- <sidebar-item v-for="route in permission_routes" :key="route.path" :item="route" :base-path="route.path" /> --> | ||
6 | <!-- 菜单全部展示 --> | ||
7 | <sidebar-item v-for="route in asyncRoutes" :key="route.path" :item="route" :base-path="route.path" /> | ||
8 | </el-menu> | ||
9 | </div> | ||
10 | </template> | ||
11 | |||
12 | <script> | ||
13 | import { mapGetters } from 'vuex' | ||
14 | import Logo from './Logo' | ||
15 | import SidebarItem from './SidebarItem' | ||
16 | import variables from '@/styles/variables.scss' | ||
17 | import { asyncRoutes1 } from '@/router' | ||
18 | export default { | ||
19 | components: { SidebarItem, Logo }, | ||
20 | computed: { | ||
21 | ...mapGetters(['permission_routes', 'sidebar']), | ||
22 | activeMenu () { | ||
23 | const route = this.$route | ||
24 | const { meta, path } = route | ||
25 | if (meta.activeMenu) { | ||
26 | return meta.activeMenu | ||
27 | } | ||
28 | return path | ||
29 | }, | ||
30 | variables () { | ||
31 | return variables | ||
32 | }, | ||
33 | asyncRoutes () { | ||
34 | return asyncRoutes1 | ||
35 | } | ||
36 | } | ||
37 | } | ||
38 | </script> | ||
39 | <style scoped lang="scss"> | ||
40 | .el-menu--horizontal { | ||
41 | display: flex; | ||
42 | background: none !important; | ||
43 | } | ||
44 | |||
45 | /deep/.el-menu-item { | ||
46 | color: #fff; | ||
47 | font-size: 18px; | ||
48 | } | ||
49 | |||
50 | /deep/.el-menu-item:hover { | ||
51 | background: none; | ||
52 | font-weight: 700; | ||
53 | color: #fff !important; | ||
54 | } | ||
55 | |||
56 | /deep/.el-submenu__title { | ||
57 | color: #fff; | ||
58 | font-size: 18px; | ||
59 | } | ||
60 | |||
61 | /deep/.el-submenu__title:hover { | ||
62 | background: none; | ||
63 | font-weight: 700; | ||
64 | } | ||
65 | |||
66 | /deep/.el-menu--horizontal .el-menu-item:not(.is-disabled):focus { | ||
67 | background: none; | ||
68 | color: #fff; | ||
69 | font-weight: 700 !important; | ||
70 | } | ||
71 | </style> | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
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 | height: 100%; | ||
86 | } | ||
87 | |||
88 | /deep/ .el-scrollbar__view { | ||
89 | display: inline-block !important; | ||
90 | } | ||
91 | |||
92 | /deep/ .el-scrollbar__wrap { | ||
93 | overflow-x: hidden !important; | ||
94 | } | ||
95 | </style> |
src/layout1/components/TagsView/index.vue
0 → 100644
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 v-for="tag in visitedViews" ref="tag" :key="tag.path" :class="isActive(tag)?'active':''" | ||
5 | :to="{ path: tag.path, query: tag.query, fullPath: tag.fullPath }" tag="span" class="tags-view-item" | ||
6 | @click.middle.native="!isAffix(tag)?closeSelectedTag(tag):''" | ||
7 | @contextmenu.prevent.native="openMenu(tag,$event)"> | ||
8 | {{ tag.title }} | ||
9 | <span v-if="!isAffix(tag)" class="el-icon-close" @click.prevent.stop="closeSelectedTag(tag)" /> | ||
10 | </router-link> | ||
11 | </scroll-pane> | ||
12 | <ul v-show="visible" :style="{left:left+'px',top:top+'px'}" class="contextmenu"> | ||
13 | <li @click="refreshSelectedTag(selectedTag)">Refresh</li> | ||
14 | <li v-if="!isAffix(selectedTag)" @click="closeSelectedTag(selectedTag)">Close</li> | ||
15 | <li @click="closeOthersTags">Close Others</li> | ||
16 | <li @click="closeAllTags(selectedTag)">Close All</li> | ||
17 | </ul> | ||
18 | </div> | ||
19 | </template> | ||
20 | |||
21 | <script> | ||
22 | import ScrollPane from './ScrollPane' | ||
23 | import path from 'path' | ||
24 | |||
25 | export default { | ||
26 | components: { ScrollPane }, | ||
27 | data () { | ||
28 | return { | ||
29 | visible: false, | ||
30 | top: 0, | ||
31 | left: 0, | ||
32 | selectedTag: {}, | ||
33 | affixTags: [] | ||
34 | } | ||
35 | }, | ||
36 | computed: { | ||
37 | visitedViews () { | ||
38 | return this.$store.state.tagsView.visitedViews | ||
39 | }, | ||
40 | routes () { | ||
41 | return this.$store.state.permission.routes | ||
42 | } | ||
43 | }, | ||
44 | watch: { | ||
45 | $route () { | ||
46 | this.addTags() | ||
47 | this.moveToCurrentTag() | ||
48 | }, | ||
49 | visible (value) { | ||
50 | if (value) { | ||
51 | document.body.addEventListener('click', this.closeMenu) | ||
52 | } else { | ||
53 | document.body.removeEventListener('click', this.closeMenu) | ||
54 | } | ||
55 | } | ||
56 | }, | ||
57 | mounted () { | ||
58 | this.initTags() | ||
59 | this.addTags() | ||
60 | }, | ||
61 | methods: { | ||
62 | isActive (route) { | ||
63 | return route.path === this.$route.path | ||
64 | }, | ||
65 | isAffix (tag) { | ||
66 | return tag.meta && tag.meta.affix | ||
67 | }, | ||
68 | filterAffixTags (routes, basePath = '/') { | ||
69 | let tags = [] | ||
70 | routes.forEach(route => { | ||
71 | if (route.meta && route.meta.affix) { | ||
72 | const tagPath = path.resolve(basePath, route.path) | ||
73 | tags.push({ | ||
74 | fullPath: tagPath, | ||
75 | path: tagPath, | ||
76 | name: route.name, | ||
77 | meta: { ...route.meta } | ||
78 | }) | ||
79 | } | ||
80 | if (route.children) { | ||
81 | const tempTags = this.filterAffixTags(route.children, route.path) | ||
82 | if (tempTags.length >= 1) { | ||
83 | tags = [...tags, ...tempTags] | ||
84 | } | ||
85 | } | ||
86 | }) | ||
87 | return tags | ||
88 | }, | ||
89 | initTags () { | ||
90 | const affixTags = this.affixTags = this.filterAffixTags(this.routes) | ||
91 | for (const tag of affixTags) { | ||
92 | // Must have tag name | ||
93 | if (tag.name) { | ||
94 | this.$store.dispatch('tagsView/addVisitedView', tag) | ||
95 | } | ||
96 | } | ||
97 | }, | ||
98 | addTags () { | ||
99 | const { name } = this.$route | ||
100 | if (name) { | ||
101 | this.$store.dispatch('tagsView/addView', this.$route) | ||
102 | } | ||
103 | return false | ||
104 | }, | ||
105 | moveToCurrentTag () { | ||
106 | const tags = this.$refs.tag | ||
107 | this.$nextTick(() => { | ||
108 | for (const tag of tags) { | ||
109 | if (tag.to.path === this.$route.path) { | ||
110 | this.$refs.scrollPane.moveToTarget(tag) | ||
111 | // when query is different then update | ||
112 | if (tag.to.fullPath !== this.$route.fullPath) { | ||
113 | this.$store.dispatch('tagsView/updateVisitedView', this.$route) | ||
114 | } | ||
115 | break | ||
116 | } | ||
117 | } | ||
118 | }) | ||
119 | }, | ||
120 | refreshSelectedTag (view) { | ||
121 | this.$store.dispatch('tagsView/delCachedView', view).then(() => { | ||
122 | const { fullPath } = view | ||
123 | this.$nextTick(() => { | ||
124 | this.$router.replace({ | ||
125 | path: '/redirect' + fullPath | ||
126 | }) | ||
127 | }) | ||
128 | }) | ||
129 | }, | ||
130 | closeSelectedTag (view) { | ||
131 | this.$store.dispatch('tagsView/delView', view).then(({ visitedViews }) => { | ||
132 | if (this.isActive(view)) { | ||
133 | this.toLastView(visitedViews, view) | ||
134 | } | ||
135 | }) | ||
136 | }, | ||
137 | closeOthersTags () { | ||
138 | this.$router.push(this.selectedTag) | ||
139 | this.$store.dispatch('tagsView/delOthersViews', this.selectedTag).then(() => { | ||
140 | this.moveToCurrentTag() | ||
141 | }) | ||
142 | }, | ||
143 | closeAllTags (view) { | ||
144 | this.$store.dispatch('tagsView/delAllViews').then(({ visitedViews }) => { | ||
145 | if (this.affixTags.some(tag => tag.path === view.path)) { | ||
146 | return | ||
147 | } | ||
148 | this.toLastView(visitedViews, view) | ||
149 | }) | ||
150 | }, | ||
151 | toLastView (visitedViews, view) { | ||
152 | const latestView = visitedViews.slice(-1)[0] | ||
153 | if (latestView) { | ||
154 | this.$router.push(latestView.fullPath) | ||
155 | } else { | ||
156 | // now the default is to redirect to the home page if there is no tags-view, | ||
157 | // you can adjust it according to your needs. | ||
158 | if (view.name === 'Dashboard') { | ||
159 | // to reload home page | ||
160 | this.$router.replace({ path: '/redirect' + view.fullPath }) | ||
161 | } else { | ||
162 | this.$router.push('/') | ||
163 | } | ||
164 | } | ||
165 | }, | ||
166 | openMenu (tag, e) { | ||
167 | const menuMinWidth = 105 | ||
168 | const offsetLeft = this.$el.getBoundingClientRect().left // container margin left | ||
169 | const offsetWidth = this.$el.offsetWidth // container width | ||
170 | const maxLeft = offsetWidth - menuMinWidth // left boundary | ||
171 | const left = e.clientX - offsetLeft + 15 // 15: margin right | ||
172 | |||
173 | if (left > maxLeft) { | ||
174 | this.left = maxLeft | ||
175 | } else { | ||
176 | this.left = left | ||
177 | } | ||
178 | |||
179 | this.top = e.clientY | ||
180 | this.visible = true | ||
181 | this.selectedTag = tag | ||
182 | }, | ||
183 | closeMenu () { | ||
184 | this.visible = false | ||
185 | }, | ||
186 | handleScroll () { | ||
187 | this.closeMenu() | ||
188 | } | ||
189 | } | ||
190 | } | ||
191 | </script> | ||
192 | |||
193 | <style lang="scss" scoped> | ||
194 | .tags-view-container { | ||
195 | height: 40px; | ||
196 | width: 100%; | ||
197 | background: #fff; | ||
198 | border-bottom: 1px solid #d8dce5; | ||
199 | box-sizing: border-box; | ||
200 | padding-top: 3px; | ||
201 | box-shadow: 0 1px 3px 0 rgba(0, 0, 0, .12), 0 0 3px 0 rgba(0, 0, 0, .04); | ||
202 | |||
203 | .tags-view-wrapper { | ||
204 | .tags-view-item { | ||
205 | display: inline-block; | ||
206 | position: relative; | ||
207 | cursor: pointer; | ||
208 | height: 26px; | ||
209 | line-height: 26px; | ||
210 | border: 1px solid #d8dce5; | ||
211 | color: #495060; | ||
212 | background: #fff; | ||
213 | padding: 0 8px; | ||
214 | font-size: 12px; | ||
215 | margin-left: 5px; | ||
216 | margin-top: 4px; | ||
217 | |||
218 | &:first-of-type { | ||
219 | margin-left: 15px; | ||
220 | } | ||
221 | |||
222 | &:last-of-type { | ||
223 | margin-right: 15px; | ||
224 | } | ||
225 | |||
226 | &.active { | ||
227 | background-color: #0794FF; | ||
228 | color: #fff; | ||
229 | border-color: #0794FF; | ||
230 | |||
231 | &::before { | ||
232 | content: ''; | ||
233 | background: #fff; | ||
234 | display: inline-block; | ||
235 | width: 8px; | ||
236 | height: 8px; | ||
237 | border-radius: 50%; | ||
238 | position: relative; | ||
239 | margin-right: 2px; | ||
240 | } | ||
241 | } | ||
242 | } | ||
243 | } | ||
244 | |||
245 | .contextmenu { | ||
246 | margin: 0; | ||
247 | background: #fff; | ||
248 | z-index: 3000; | ||
249 | position: absolute; | ||
250 | list-style-type: none; | ||
251 | padding: 5px 0; | ||
252 | border-radius: 4px; | ||
253 | font-size: 12px; | ||
254 | font-weight: 400; | ||
255 | color: #333; | ||
256 | box-shadow: 2px 2px 3px 0 rgba(0, 0, 0, .3); | ||
257 | |||
258 | li { | ||
259 | margin: 0; | ||
260 | padding: 7px 16px; | ||
261 | cursor: pointer; | ||
262 | |||
263 | &:hover { | ||
264 | background: #eee; | ||
265 | } | ||
266 | } | ||
267 | } | ||
268 | } | ||
269 | </style> | ||
270 | |||
271 | <style lang="scss"> | ||
272 | //reset element css of el-icon-close | ||
273 | .tags-view-wrapper { | ||
274 | .tags-view-item { | ||
275 | .el-icon-close { | ||
276 | width: 16px; | ||
277 | height: 16px; | ||
278 | vertical-align: 2px; | ||
279 | border-radius: 50%; | ||
280 | text-align: center; | ||
281 | transition: all .3s cubic-bezier(.645, .045, .355, 1); | ||
282 | transform-origin: 100% 50%; | ||
283 | |||
284 | &:before { | ||
285 | transform: scale(.6); | ||
286 | display: inline-block; | ||
287 | vertical-align: -3px; | ||
288 | } | ||
289 | |||
290 | &:hover { | ||
291 | background-color: #b4bccc; | ||
292 | color: #fff; | ||
293 | } | ||
294 | } | ||
295 | } | ||
296 | } | ||
297 | </style> |
src/layout1/components/index.js
0 → 100644
src/layout1/index.vue
0 → 100644
1 | <template> | ||
2 | <div class="app-wrapper"> | ||
3 | <navbar /> | ||
4 | <div class="appMain"> | ||
5 | <app-main /> | ||
6 | </div> | ||
7 | </div> | ||
8 | </template> | ||
9 | <script> | ||
10 | import { AppMain, Navbar, Sidebar, TagsView } from './components' | ||
11 | import ResizeMixin from './mixin/ResizeHandler' | ||
12 | import { mapState } from 'vuex' | ||
13 | export default { | ||
14 | name: 'Layout', | ||
15 | components: { | ||
16 | AppMain, | ||
17 | Navbar, | ||
18 | Sidebar, | ||
19 | TagsView | ||
20 | }, | ||
21 | mixins: [ResizeMixin], | ||
22 | computed: { | ||
23 | ...mapState({ | ||
24 | sidebar: state => state.app.sidebar, | ||
25 | needTagsView: state => state.settings.tagsView, | ||
26 | fixedHeader: state => state.settings.fixedHeader | ||
27 | }) | ||
28 | } | ||
29 | } | ||
30 | </script> | ||
31 | <style lang="scss" scoped> | ||
32 | @import "~@/styles/mixin.scss"; | ||
33 | |||
34 | .app-wrapper { | ||
35 | @include clearfix; | ||
36 | position: relative; | ||
37 | height: 100%; | ||
38 | width: 100%; | ||
39 | background-color: #F2F6FC; | ||
40 | |||
41 | &.mobile.openSidebar { | ||
42 | position: fixed; | ||
43 | top: 0; | ||
44 | } | ||
45 | } | ||
46 | |||
47 | .appMain { | ||
48 | height: calc(100vh - 80px); | ||
49 | width: 80%; | ||
50 | margin: 0 auto; | ||
51 | background-color: #fff; | ||
52 | |||
53 | .app-main { | ||
54 | height: 100%; | ||
55 | } | ||
56 | } | ||
57 | </style> |
src/layout1/mixin/ResizeHandler.js
0 → 100644
1 | import store from '@/store' | ||
2 | |||
3 | const { body } = document | ||
4 | const WIDTH = 992 // refer to Bootstrap's responsive design | ||
5 | |||
6 | export default { | ||
7 | watch: { | ||
8 | $route(route) { | ||
9 | if (this.device === 'mobile' && this.sidebar.opened) { | ||
10 | store.dispatch('app/closeSideBar', { withoutAnimation: false }) | ||
11 | } | ||
12 | } | ||
13 | }, | ||
14 | beforeMount() { | ||
15 | window.addEventListener('resize', this.$_resizeHandler) | ||
16 | }, | ||
17 | beforeDestroy() { | ||
18 | window.removeEventListener('resize', this.$_resizeHandler) | ||
19 | }, | ||
20 | mounted() { | ||
21 | const isMobile = this.$_isMobile() | ||
22 | if (isMobile) { | ||
23 | store.dispatch('app/toggleDevice', 'mobile') | ||
24 | store.dispatch('app/closeSideBar', { withoutAnimation: true }) | ||
25 | } | ||
26 | }, | ||
27 | methods: { | ||
28 | // use $_ for mixins properties | ||
29 | // https://vuejs.org/v2/style-guide/index.html#Private-property-names-essential | ||
30 | $_isMobile() { | ||
31 | const rect = body.getBoundingClientRect() | ||
32 | return rect.width - 1 < WIDTH | ||
33 | }, | ||
34 | $_resizeHandler() { | ||
35 | if (!document.hidden) { | ||
36 | const isMobile = this.$_isMobile() | ||
37 | store.dispatch('app/toggleDevice', isMobile ? 'mobile' : 'desktop') | ||
38 | |||
39 | if (isMobile) { | ||
40 | store.dispatch('app/closeSideBar', { withoutAnimation: true }) | ||
41 | } | ||
42 | } | ||
43 | } | ||
44 | } | ||
45 | } |
... | @@ -3,6 +3,7 @@ import Router from 'vue-router' | ... | @@ -3,6 +3,7 @@ import Router from 'vue-router' |
3 | Vue.use(Router) | 3 | Vue.use(Router) |
4 | /* Layout */ | 4 | /* Layout */ |
5 | import Layout from '@/layout' | 5 | import Layout from '@/layout' |
6 | import Layout1 from '@/layout1' | ||
6 | 7 | ||
7 | /* Router Modules */ | 8 | /* Router Modules */ |
8 | // import componentsRouter from './modules/components' | 9 | // import componentsRouter from './modules/components' |
... | @@ -47,21 +48,6 @@ export const asyncRoutes = [ | ... | @@ -47,21 +48,6 @@ export const asyncRoutes = [ |
47 | } | 48 | } |
48 | ] | 49 | ] |
49 | }, | 50 | }, |
50 | // 监管首页 | ||
51 | { | ||
52 | path: '/jgHome', | ||
53 | component: Layout, | ||
54 | redirect: '/jgHome', | ||
55 | meta: { title: '首页' }, | ||
56 | children: [ | ||
57 | { | ||
58 | path: 'jgHome', | ||
59 | component: () => import('@/views/jgHome/index'), | ||
60 | name: 'jgHome', | ||
61 | meta: { title: '首页', icon: 'workbench', affix: true } | ||
62 | } | ||
63 | ] | ||
64 | }, | ||
65 | // 接收报文查询 | 51 | // 接收报文查询 |
66 | { | 52 | { |
67 | path: '/jsbwcx', | 53 | path: '/jsbwcx', |
... | @@ -311,10 +297,96 @@ export const asyncRoutes = [ | ... | @@ -311,10 +297,96 @@ export const asyncRoutes = [ |
311 | } | 297 | } |
312 | ] | 298 | ] |
313 | 299 | ||
300 | export const asyncRoutes1 = [ | ||
301 | // 监管首页 | ||
302 | { | ||
303 | path: '/', | ||
304 | component: Layout1, | ||
305 | redirect: '/jgHome', | ||
306 | meta: { title: '首页' }, | ||
307 | children: [ | ||
308 | { | ||
309 | path: 'jgHome', | ||
310 | component: () => import('@/views/jgHome/index'), | ||
311 | name: 'jgHome', | ||
312 | meta: { title: '首页', icon: 'workbench', affix: true } | ||
313 | } | ||
314 | ] | ||
315 | }, | ||
316 | // 接收报文查询 | ||
317 | { | ||
318 | path: '/jsbwcx1', | ||
319 | component: Layout1, | ||
320 | children: [ | ||
321 | { | ||
322 | path: 'index', | ||
323 | component: () => import('@/views/jsbwcx/index'), | ||
324 | name: 'jsbwcx', | ||
325 | meta: { title: '接收报文查询1', icon: 'zsgl' } | ||
326 | } | ||
327 | ] | ||
328 | }, | ||
329 | // 上报报文查询 | ||
330 | { | ||
331 | path: '/sbbwcx1', | ||
332 | component: Layout1, | ||
333 | children: [ | ||
334 | { | ||
335 | path: 'index', | ||
336 | component: () => import('@/views/sbbwcx/index'), | ||
337 | name: 'sbbwcx', | ||
338 | meta: { title: '上报报文查询', icon: 'zsgl' } | ||
339 | } | ||
340 | ] | ||
341 | }, | ||
342 | // 登簿日志 | ||
343 | { | ||
344 | path: '/dbrzcx1', | ||
345 | component: Layout1, | ||
346 | children: [ | ||
347 | { | ||
348 | path: 'index', | ||
349 | component: () => import('@/views/dbrzcx/index'), | ||
350 | name: 'dbrzcx', | ||
351 | meta: { title: '登簿日志查询', icon: 'zhcx' } | ||
352 | } | ||
353 | ] | ||
354 | }, | ||
355 | // 系统管理 | ||
356 | { | ||
357 | path: '/system1', | ||
358 | component: Layout1, | ||
359 | meta: { title: '系统管理', icon: 'sqcx', breadcrumb: false }, | ||
360 | redirect: '/system/dictionaries', | ||
361 | alwaysShow: true, | ||
362 | name: 'system', | ||
363 | children: [ | ||
364 | { | ||
365 | path: 'dictionaries', | ||
366 | component: () => import('@/views/system/dictionaries/dictionaries.vue'), | ||
367 | name: 'dictionaries', | ||
368 | meta: { title: '字典管理' } | ||
369 | }, | ||
370 | { | ||
371 | path: 'validationRule', | ||
372 | component: () => import('@/views/system/validationRule'), | ||
373 | name: 'validationRule', | ||
374 | meta: { title: '上报效验规则配置' } | ||
375 | }, | ||
376 | { | ||
377 | path: 'timedTask', | ||
378 | component: () => import('@/views/system/timedTask'), | ||
379 | name: 'timedTask', | ||
380 | meta: { title: '定时任务' } | ||
381 | } | ||
382 | ] | ||
383 | } | ||
384 | ] | ||
385 | |||
314 | const createRouter = () => | 386 | const createRouter = () => |
315 | new Router({ | 387 | new Router({ |
316 | scrollBehavior: () => ({ y: 0 }), | 388 | scrollBehavior: () => ({ y: 0 }), |
317 | routes: [...constantRoutes, ...asyncRoutes] | 389 | routes: [...constantRoutes, ...asyncRoutes, ...asyncRoutes1] |
318 | }) | 390 | }) |
319 | 391 | ||
320 | const router = createRouter() | 392 | const router = createRouter() | ... | ... |
1 | .jgHome { | 1 | .jgHome { |
2 | display: flex; | 2 | display: flex; |
3 | justify-content: space-between; | 3 | justify-content: space-between; |
4 | height: 100%; | ||
4 | 5 | ||
5 | .bottom10 { | 6 | .bottom10 { |
6 | margin-bottom: 10px; | 7 | margin-bottom: 10px; |
... | @@ -51,7 +52,7 @@ | ... | @@ -51,7 +52,7 @@ |
51 | .barChart { | 52 | .barChart { |
52 | flex: 1; | 53 | flex: 1; |
53 | margin-top: 10px; | 54 | margin-top: 10px; |
54 | height: calc(100vh - 355px); | 55 | height: calc(100vh - 310px); |
55 | } | 56 | } |
56 | 57 | ||
57 | &-center { | 58 | &-center { | ... | ... |
... | @@ -42,7 +42,7 @@ | ... | @@ -42,7 +42,7 @@ |
42 | <span>违规总计</span> | 42 | <span>违规总计</span> |
43 | <el-button style="float: right;" type="text">更多</el-button> | 43 | <el-button style="float: right;" type="text">更多</el-button> |
44 | </div> | 44 | </div> |
45 | <lb-table ref="table" :pagination="false" :heightNum="595" :column="tableData.columns" :data="tableData.data"> | 45 | <lb-table ref="table" :pagination="false" :heightNum="546" :column="tableData.columns" :data="tableData.data"> |
46 | </lb-table> | 46 | </lb-table> |
47 | </el-card> | 47 | </el-card> |
48 | </div> | 48 | </div> | ... | ... |
-
Please register or sign in to post a comment