Commit 4e2abd16b5fc3f48937e67ee3413c69767179f50
Exists in
master
Merge branch 'master' into 'master'
Master See merge request !8
Showing
43 changed files
Show diff stats
mock/article.js
... | ... | @@ -27,12 +27,18 @@ for (let i = 0; i < count; i++) { |
27 | 27 | })) |
28 | 28 | } |
29 | 29 | |
30 | -export default [ | |
31 | - { | |
32 | - url: '/vue-element-admin/article/list', | |
30 | +export default [{ | |
31 | + url: '/yp/article/list', | |
33 | 32 | type: 'get', |
34 | 33 | response: config => { |
35 | - const { importance, type, title, page = 1, limit = 20, sort } = config.query | |
34 | + const { | |
35 | + importance, | |
36 | + type, | |
37 | + title, | |
38 | + page = 1, | |
39 | + limit = 20, | |
40 | + sort | |
41 | + } = config.query | |
36 | 42 | |
37 | 43 | let mockList = List.filter(item => { |
38 | 44 | if (importance && item.importance !== +importance) return false |
... | ... | @@ -58,10 +64,12 @@ export default [ |
58 | 64 | }, |
59 | 65 | |
60 | 66 | { |
61 | - url: '/vue-element-admin/article/detail', | |
67 | + url: '/yp/article/detail', | |
62 | 68 | type: 'get', |
63 | 69 | response: config => { |
64 | - const { id } = config.query | |
70 | + const { | |
71 | + id | |
72 | + } = config.query | |
65 | 73 | for (const article of List) { |
66 | 74 | if (article.id === +id) { |
67 | 75 | return { |
... | ... | @@ -74,17 +82,28 @@ export default [ |
74 | 82 | }, |
75 | 83 | |
76 | 84 | { |
77 | - url: '/vue-element-admin/article/pv', | |
85 | + url: '/yp/article/pv', | |
78 | 86 | type: 'get', |
79 | 87 | response: _ => { |
80 | 88 | return { |
81 | 89 | code: 20000, |
82 | 90 | data: { |
83 | - pvData: [ | |
84 | - { key: 'PC', pv: 1024 }, | |
85 | - { key: 'mobile', pv: 1024 }, | |
86 | - { key: 'ios', pv: 1024 }, | |
87 | - { key: 'android', pv: 1024 } | |
91 | + pvData: [{ | |
92 | + key: 'PC', | |
93 | + pv: 1024 | |
94 | + }, | |
95 | + { | |
96 | + key: 'mobile', | |
97 | + pv: 1024 | |
98 | + }, | |
99 | + { | |
100 | + key: 'ios', | |
101 | + pv: 1024 | |
102 | + }, | |
103 | + { | |
104 | + key: 'android', | |
105 | + pv: 1024 | |
106 | + } | |
88 | 107 | ] |
89 | 108 | } |
90 | 109 | } |
... | ... | @@ -92,7 +111,7 @@ export default [ |
92 | 111 | }, |
93 | 112 | |
94 | 113 | { |
95 | - url: '/vue-element-admin/article/create', | |
114 | + url: '/yp/article/create', | |
96 | 115 | type: 'post', |
97 | 116 | response: _ => { |
98 | 117 | return { |
... | ... | @@ -103,7 +122,7 @@ export default [ |
103 | 122 | }, |
104 | 123 | |
105 | 124 | { |
106 | - url: '/vue-element-admin/article/update', | |
125 | + url: '/yp/article/update', | |
107 | 126 | type: 'post', |
108 | 127 | response: _ => { |
109 | 128 | return { |
... | ... | @@ -113,4 +132,3 @@ export default [ |
113 | 132 | } |
114 | 133 | } |
115 | 134 | ] |
116 | - | ... | ... |
mock/remote-search.js
... | ... | @@ -13,7 +13,7 @@ NameList.push({ name: 'mock-Pan' }) |
13 | 13 | export default [ |
14 | 14 | // username search |
15 | 15 | { |
16 | - url: '/vue-element-admin/search/user', | |
16 | + url: '/yp/search/user', | |
17 | 17 | type: 'get', |
18 | 18 | response: config => { |
19 | 19 | const { name } = config.query |
... | ... | @@ -30,7 +30,7 @@ export default [ |
30 | 30 | |
31 | 31 | // transaction list |
32 | 32 | { |
33 | - url: '/vue-element-admin/transaction/list', | |
33 | + url: '/yp/transaction/list', | |
34 | 34 | type: 'get', |
35 | 35 | response: _ => { |
36 | 36 | return { | ... | ... |
mock/role/index.js
... | ... | @@ -12,33 +12,45 @@ const roles = [ |
12 | 12 | routes: routes |
13 | 13 | }, |
14 | 14 | { |
15 | - key: 'editor', | |
16 | - name: 'editor', | |
17 | - description: 'Normal Editor. Can see all pages except permission page', | |
15 | + key: 'assistant', | |
16 | + name: 'assistant', | |
17 | + description: 'assistant Administrator. Can see all pages except permission page', | |
18 | 18 | routes: routes.filter(i => i.path !== '/permission')// just a mock |
19 | 19 | }, |
20 | 20 | { |
21 | - key: 'visitor', | |
22 | - name: 'visitor', | |
23 | - description: 'Just a visitor. Can only see the home page and the document page', | |
24 | - routes: [{ | |
25 | - path: '', | |
26 | - redirect: 'dashboard', | |
27 | - children: [ | |
28 | - { | |
29 | - path: 'dashboard', | |
30 | - name: 'Dashboard', | |
31 | - meta: { title: 'dashboard', icon: 'dashboard' } | |
32 | - } | |
33 | - ] | |
34 | - }] | |
35 | - } | |
21 | + key: 'runner', | |
22 | + name: 'runner', | |
23 | + description: 'Normal runner. Can see runner pages except permission page', | |
24 | + routes: routes.filter(i => i.path !== '/permission')// just a mock | |
25 | + }, | |
26 | + { | |
27 | + key: 'shoper', | |
28 | + name: 'shoper', | |
29 | + description: 'Normal shoper. Can see shoper pages except permission page', | |
30 | + routes: routes.filter(i => i.path !== '/permission')// just a mock | |
31 | + }, | |
32 | + // { | |
33 | + // key: 'visitor', | |
34 | + // name: 'visitor', | |
35 | + // description: 'Just a visitor. Can only see the home page and the document page', | |
36 | + // routes: [{ | |
37 | + // path: '', | |
38 | + // redirect: 'dashboard', | |
39 | + // children: [ | |
40 | + // { | |
41 | + // path: 'dashboard', | |
42 | + // name: 'Dashboard', | |
43 | + // meta: { title: 'dashboard', icon: 'dashboard' } | |
44 | + // } | |
45 | + // ] | |
46 | + // }] | |
47 | + // } | |
36 | 48 | ] |
37 | 49 | |
38 | 50 | export default [ |
39 | 51 | // mock get all routes form server |
40 | 52 | { |
41 | - url: '/vue-element-admin/routes', | |
53 | + url: '/yp/routes', | |
42 | 54 | type: 'get', |
43 | 55 | response: _ => { |
44 | 56 | return { |
... | ... | @@ -50,7 +62,7 @@ export default [ |
50 | 62 | |
51 | 63 | // mock get all roles form server |
52 | 64 | { |
53 | - url: '/vue-element-admin/roles', | |
65 | + url: '/yp/roles', | |
54 | 66 | type: 'get', |
55 | 67 | response: _ => { |
56 | 68 | return { |
... | ... | @@ -62,7 +74,7 @@ export default [ |
62 | 74 | |
63 | 75 | // add role |
64 | 76 | { |
65 | - url: '/vue-element-admin/role', | |
77 | + url: '/yp/role', | |
66 | 78 | type: 'post', |
67 | 79 | response: { |
68 | 80 | code: 20000, |
... | ... | @@ -74,7 +86,7 @@ export default [ |
74 | 86 | |
75 | 87 | // update role |
76 | 88 | { |
77 | - url: '/vue-element-admin/role/[A-Za-z0-9]', | |
89 | + url: '/yp/role/[A-Za-z0-9]', | |
78 | 90 | type: 'put', |
79 | 91 | response: { |
80 | 92 | code: 20000, |
... | ... | @@ -86,7 +98,7 @@ export default [ |
86 | 98 | |
87 | 99 | // delete role |
88 | 100 | { |
89 | - url: '/vue-element-admin/role/[A-Za-z0-9]', | |
101 | + url: '/yp/role/[A-Za-z0-9]', | |
90 | 102 | type: 'delete', |
91 | 103 | response: { |
92 | 104 | code: 20000, | ... | ... |
mock/role/routes.js
... | ... | @@ -23,13 +23,23 @@ export const constantRoutes = [ |
23 | 23 | hidden: true |
24 | 24 | }, |
25 | 25 | { |
26 | + path: '/401', | |
27 | + component: 'views/error-page/401', | |
28 | + hidden: true | |
29 | + }, | |
30 | + { | |
31 | + path: '/403', | |
32 | + component: 'views/error-page/403', | |
33 | + hidden: true | |
34 | + }, | |
35 | + { | |
26 | 36 | path: '/404', |
27 | 37 | component: 'views/error-page/404', |
28 | 38 | hidden: true |
29 | 39 | }, |
30 | 40 | { |
31 | - path: '/401', | |
32 | - component: 'views/error-page/401', | |
41 | + path: '/500', | |
42 | + component: 'views/error-page/500', | |
33 | 43 | hidden: true |
34 | 44 | }, |
35 | 45 | { |
... | ... | @@ -73,49 +83,55 @@ export const constantRoutes = [ |
73 | 83 | ] |
74 | 84 | |
75 | 85 | export const asyncRoutes = [ |
76 | - { | |
77 | - path: '/permission', | |
78 | - component: 'layout/Layout', | |
79 | - redirect: '/permission/index', | |
80 | - alwaysShow: true, | |
81 | - meta: { | |
82 | - title: 'permission', | |
83 | - icon: 'lock', | |
84 | - roles: ['admin', 'editor'] | |
85 | - }, | |
86 | - children: [ | |
87 | - { | |
88 | - path: 'page', | |
89 | - component: 'views/permission/page', | |
90 | - name: 'PagePermission', | |
91 | - meta: { | |
92 | - title: 'pagePermission', | |
93 | - roles: ['admin'] | |
94 | - } | |
95 | - }, | |
96 | - { | |
97 | - path: 'directive', | |
98 | - component: 'views/permission/directive', | |
99 | - name: 'DirectivePermission', | |
100 | - meta: { | |
101 | - title: 'directivePermission' | |
102 | - } | |
103 | - }, | |
104 | - { | |
105 | - path: 'role', | |
106 | - component: 'views/permission/role', | |
107 | - name: 'RolePermission', | |
108 | - meta: { | |
109 | - title: 'rolePermission', | |
110 | - roles: ['admin'] | |
111 | - } | |
112 | - } | |
113 | - ] | |
114 | - }, | |
86 | + // { | |
87 | + // path: '/permission', | |
88 | + // component: 'layout/Layout', | |
89 | + // redirect: '/permission/index', | |
90 | + // alwaysShow: true, | |
91 | + // meta: { | |
92 | + // title: 'permission', | |
93 | + // icon: 'lock', | |
94 | + // // roles: ['admin', 'assistant', 'runner', 'shoper'] | |
95 | + // }, | |
96 | + // children: [ | |
97 | + // { | |
98 | + // path: 'page', | |
99 | + // component: 'views/permission/page', | |
100 | + // name: 'PagePermission', | |
101 | + // meta: { | |
102 | + // title: 'pagePermission', | |
103 | + // roles: ['admin','assistant'] | |
104 | + // } | |
105 | + // }, | |
106 | + // { | |
107 | + // path: 'directive', | |
108 | + // component: 'views/permission/directive', | |
109 | + // name: 'DirectivePermission', | |
110 | + // meta: { | |
111 | + // title: 'directivePermission', | |
112 | + // roles:['shoper'] | |
113 | + // } | |
114 | + // }, | |
115 | + // { | |
116 | + // path: 'role', | |
117 | + // component: 'views/permission/role', | |
118 | + // name: 'RolePermission', | |
119 | + // meta: { | |
120 | + // title: 'rolePermission', | |
121 | + // roles: ['runner'] | |
122 | + // } | |
123 | + // } | |
124 | + // ] | |
125 | + // }, | |
115 | 126 | |
116 | 127 | { |
117 | 128 | path: '/icon', |
118 | 129 | component: 'layout/Layout', |
130 | + meta: { | |
131 | + title: 'ddddd', | |
132 | + icon:'people', | |
133 | + roles: ['runner'] | |
134 | + }, | |
119 | 135 | children: [ |
120 | 136 | { |
121 | 137 | path: 'index', |
... | ... | @@ -379,6 +395,12 @@ export const asyncRoutes = [ |
379 | 395 | component: 'views/error-page/404', |
380 | 396 | name: 'Page404', |
381 | 397 | meta: { title: 'page404', noCache: true } |
398 | + }, | |
399 | + { | |
400 | + path: '500', | |
401 | + component: 'views/error-page/500', | |
402 | + name: 'Page500', | |
403 | + meta: { title: 'page500', noCache: true } | |
382 | 404 | } |
383 | 405 | ] |
384 | 406 | }, | ... | ... |
mock/user.js
1 | 1 | |
2 | +import Mock from 'mockjs' | |
3 | + | |
2 | 4 | const tokens = { |
3 | 5 | admin: { |
4 | 6 | token: 'admin-token' |
5 | 7 | }, |
6 | - editor: { | |
7 | - token: 'editor-token' | |
8 | + assistant: { | |
9 | + token: 'assistant-token' | |
10 | + }, | |
11 | + runner: { | |
12 | + token: 'runner-token' | |
13 | + }, | |
14 | + shoper: { | |
15 | + token: 'shoper-token' | |
8 | 16 | } |
9 | 17 | } |
10 | 18 | |
... | ... | @@ -15,18 +23,60 @@ const users = { |
15 | 23 | avatar: 'https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif', |
16 | 24 | name: 'Super Admin' |
17 | 25 | }, |
18 | - 'editor-token': { | |
19 | - roles: ['editor'], | |
20 | - introduction: 'I am an editor', | |
26 | + 'assistant-token': { | |
27 | + roles: ['assistant'], | |
28 | + introduction: 'I am an assistant', | |
29 | + avatar: 'https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif', | |
30 | + name: 'Normal assistant' | |
31 | + }, | |
32 | + 'runner-token': { | |
33 | + roles: ['runner'], | |
34 | + introduction: 'I am an runner', | |
35 | + avatar: 'https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif', | |
36 | + name: 'Normal runner' | |
37 | + }, | |
38 | + 'shoper-token': { | |
39 | + roles: ['shoper'], | |
40 | + introduction: 'I am an shoper', | |
21 | 41 | avatar: 'https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif', |
22 | - name: 'Normal Editor' | |
42 | + name: 'Normal shoper' | |
23 | 43 | } |
24 | 44 | } |
25 | 45 | |
46 | +const List = [] | |
47 | +const count = 100 | |
48 | + | |
49 | +const baseContent = '<p>I am testing data, I am testing data.</p><p><img src="https://wpimg.wallstcn.com/4c69009c-0fd4-4153-b112-6cb53d1cf943"></p>' | |
50 | +const image_uri = 'https://wpimg.wallstcn.com/e4558086-631c-425c-9430-56ffb46e70b3' | |
51 | + | |
52 | +for (let i = 0; i < count; i++) { | |
53 | + List.push(Mock.mock({ | |
54 | + uid: '@increment', | |
55 | + create_time: +Mock.Random.date('T'), | |
56 | + nickname: '@first', | |
57 | + reviewer: '@first', | |
58 | + title: '@title(5, 10)', | |
59 | + openid:'@sentence(12,12)', | |
60 | + content_short: 'mock data', | |
61 | + content: baseContent, | |
62 | + forecast: '@float(0, 100, 2, 2)', | |
63 | + importance: '@integer(1, 3)', | |
64 | + 'type|1': ['CN', 'US', 'JP', 'EU'], | |
65 | + 'status|1': ['published', 'draft'], | |
66 | + display_time: '@datetime', | |
67 | + comment_disabled: true, | |
68 | + son_of_adv: '@integer(300, 5000)',//下线 | |
69 | + souceof:'@integer(300, 5000)',//源自 | |
70 | + image_uri, | |
71 | + 'roles|1': ['admin', 'assistant', 'shoper', 'runner'], | |
72 | + platforms: ['a-platform']//哪个平台 | |
73 | + })) | |
74 | +} | |
75 | + | |
26 | 76 | export default [ |
27 | 77 | // user login |
28 | 78 | { |
29 | - url: '/vue-element-admin/user/login', | |
79 | + url: '/yp/user/login', | |
30 | 80 | type: 'post', |
31 | 81 | response: config => { |
32 | 82 | const { username } = config.body |
... | ... | @@ -49,7 +99,7 @@ export default [ |
49 | 99 | |
50 | 100 | // get user info |
51 | 101 | { |
52 | - url: '/vue-element-admin/user/info\.*', | |
102 | + url: '/yp/user/info\.*', | |
53 | 103 | type: 'get', |
54 | 104 | response: config => { |
55 | 105 | const { token } = config.query |
... | ... | @@ -72,7 +122,29 @@ export default [ |
72 | 122 | |
73 | 123 | // user logout |
74 | 124 | { |
75 | - url: '/vue-element-admin/user/logout', | |
125 | + url: '/yp/user/logout', | |
126 | + type: 'post', | |
127 | + response: _ => { | |
128 | + return { | |
129 | + code: 20000, | |
130 | + data: 'success' | |
131 | + } | |
132 | + } | |
133 | + }, | |
134 | + // user create | |
135 | + { | |
136 | + url: '/yp/user/create', | |
137 | + type: 'post', | |
138 | + response: _ => { | |
139 | + return { | |
140 | + code: 20000, | |
141 | + data: 'success' | |
142 | + } | |
143 | + } | |
144 | + }, | |
145 | + // user update | |
146 | + { | |
147 | + url: '/yp/user/update', | |
76 | 148 | type: 'post', |
77 | 149 | response: _ => { |
78 | 150 | return { |
... | ... | @@ -80,5 +152,52 @@ export default [ |
80 | 152 | data: 'success' |
81 | 153 | } |
82 | 154 | } |
155 | + }, | |
156 | + // user del | |
157 | + { | |
158 | + url: '/yp/user/del', | |
159 | + type: 'post', | |
160 | + response: _ => { | |
161 | + return { | |
162 | + code: 20000, | |
163 | + data: 'success' | |
164 | + } | |
165 | + } | |
166 | + }, | |
167 | + // user list | |
168 | + { | |
169 | + url: '/yp/user/list', | |
170 | + type: 'get', | |
171 | + response: config => { | |
172 | + const { | |
173 | + importance, | |
174 | + type, | |
175 | + title, | |
176 | + page = 1, | |
177 | + limit = 20, | |
178 | + sort | |
179 | + } = config.query | |
180 | + | |
181 | + let mockList = List.filter(item => { | |
182 | + if (importance && item.importance !== +importance) return false | |
183 | + if (type && item.type !== type) return false | |
184 | + if (title && item.title.indexOf(title) < 0) return false | |
185 | + return true | |
186 | + }) | |
187 | + | |
188 | + if (sort === '-id') { | |
189 | + mockList = mockList.reverse() | |
190 | + } | |
191 | + | |
192 | + const pageList = mockList.filter((item, index) => index < limit * page && index >= limit * (page - 1)) | |
193 | + | |
194 | + return { | |
195 | + code: 20000, | |
196 | + data: { | |
197 | + total: mockList.length, | |
198 | + items: pageList | |
199 | + } | |
200 | + } | |
201 | + } | |
83 | 202 | } |
84 | 203 | ] | ... | ... |
package.json
... | ... | @@ -9,7 +9,7 @@ |
9 | 9 | "build:prod": "vue-cli-service build", |
10 | 10 | "build:stage": "vue-cli-service build --mode staging", |
11 | 11 | "preview": "node build/index.js --preview", |
12 | - "lint": "eslint --ext .js,.vue src", | |
12 | + "lint": "eslint --fix --ext .js,.vue src", | |
13 | 13 | "test:unit": "jest --clearCache && vue-cli-service test:unit", |
14 | 14 | "test:ci": "npm run lint && npm run test:unit", |
15 | 15 | "svgo": "svgo -f src/icons/svg --config=src/icons/svgo.yml", |
... | ... | @@ -43,6 +43,7 @@ |
43 | 43 | "url": "https://github.com/PanJiaChen/vue-element-admin/issues" |
44 | 44 | }, |
45 | 45 | "dependencies": { |
46 | + "ant-design-vue": "^1.5.5", | |
46 | 47 | "axios": "0.18.1", |
47 | 48 | "clipboard": "2.0.4", |
48 | 49 | "codemirror": "5.45.0", |
... | ... | @@ -55,14 +56,17 @@ |
55 | 56 | "js-cookie": "2.2.0", |
56 | 57 | "jsonlint": "1.6.3", |
57 | 58 | "jszip": "3.2.1", |
59 | + "less-loader": "^6.1.0", | |
58 | 60 | "normalize.css": "7.0.0", |
59 | 61 | "nprogress": "0.2.0", |
60 | 62 | "path-to-regexp": "2.4.0", |
61 | 63 | "pinyin": "2.9.0", |
64 | + "qs": "^6.9.4", | |
62 | 65 | "screenfull": "4.2.0", |
63 | 66 | "showdown": "1.9.0", |
64 | 67 | "sortablejs": "1.8.4", |
65 | 68 | "tui-editor": "1.3.3", |
69 | + "vant": "^2.8.0", | |
66 | 70 | "vue": "2.6.10", |
67 | 71 | "vue-count-to": "1.0.13", |
68 | 72 | "vue-i18n": "7.3.2", | ... | ... |
src/api/article.js
1 | 1 | import request from '@/utils/request' |
2 | 2 | |
3 | +// export const getPersonInfo = data => { | |
4 | +// return service({ | |
5 | +// url: '/person_pay/getpersoninfo', | |
6 | +// method: 'post', | |
7 | +// data | |
8 | +// }) | |
9 | +// } | |
10 | + | |
3 | 11 | export function fetchList(query) { |
4 | 12 | return request({ |
5 | - url: '/vue-element-admin/article/list', | |
13 | + url: '/yp/article/list', | |
6 | 14 | method: 'get', |
7 | 15 | params: query |
8 | 16 | }) |
... | ... | @@ -10,23 +18,27 @@ export function fetchList(query) { |
10 | 18 | |
11 | 19 | export function fetchArticle(id) { |
12 | 20 | return request({ |
13 | - url: '/vue-element-admin/article/detail', | |
21 | + url: '/yp/article/detail', | |
14 | 22 | method: 'get', |
15 | - params: { id } | |
23 | + params: { | |
24 | + id | |
25 | + } | |
16 | 26 | }) |
17 | 27 | } |
18 | 28 | |
19 | 29 | export function fetchPv(pv) { |
20 | 30 | return request({ |
21 | - url: '/vue-element-admin/article/pv', | |
31 | + url: '/yp/article/pv', | |
22 | 32 | method: 'get', |
23 | - params: { pv } | |
33 | + params: { | |
34 | + pv | |
35 | + } | |
24 | 36 | }) |
25 | 37 | } |
26 | 38 | |
27 | 39 | export function createArticle(data) { |
28 | 40 | return request({ |
29 | - url: '/vue-element-admin/article/create', | |
41 | + url: '/yp/article/create', | |
30 | 42 | method: 'post', |
31 | 43 | data |
32 | 44 | }) |
... | ... | @@ -34,7 +46,7 @@ export function createArticle(data) { |
34 | 46 | |
35 | 47 | export function updateArticle(data) { |
36 | 48 | return request({ |
37 | - url: '/vue-element-admin/article/update', | |
49 | + url: '/yp/article/update', | |
38 | 50 | method: 'post', |
39 | 51 | data |
40 | 52 | }) | ... | ... |
src/api/remote-search.js
... | ... | @@ -2,7 +2,7 @@ import request from '@/utils/request' |
2 | 2 | |
3 | 3 | export function searchUser(name) { |
4 | 4 | return request({ |
5 | - url: '/vue-element-admin/search/user', | |
5 | + url: '/yp/search/user', | |
6 | 6 | method: 'get', |
7 | 7 | params: { name } |
8 | 8 | }) |
... | ... | @@ -10,7 +10,7 @@ export function searchUser(name) { |
10 | 10 | |
11 | 11 | export function transactionList(query) { |
12 | 12 | return request({ |
13 | - url: '/vue-element-admin/transaction/list', | |
13 | + url: '/yp/transaction/list', | |
14 | 14 | method: 'get', |
15 | 15 | params: query |
16 | 16 | }) | ... | ... |
src/api/role.js
... | ... | @@ -2,21 +2,21 @@ import request from '@/utils/request' |
2 | 2 | |
3 | 3 | export function getRoutes() { |
4 | 4 | return request({ |
5 | - url: '/vue-element-admin/routes', | |
5 | + url: '/yp/routes', | |
6 | 6 | method: 'get' |
7 | 7 | }) |
8 | 8 | } |
9 | 9 | |
10 | 10 | export function getRoles() { |
11 | 11 | return request({ |
12 | - url: '/vue-element-admin/roles', | |
12 | + url: '/yp/roles', | |
13 | 13 | method: 'get' |
14 | 14 | }) |
15 | 15 | } |
16 | 16 | |
17 | 17 | export function addRole(data) { |
18 | 18 | return request({ |
19 | - url: '/vue-element-admin/role', | |
19 | + url: '/yp/role', | |
20 | 20 | method: 'post', |
21 | 21 | data |
22 | 22 | }) | ... | ... |
src/api/user.js
... | ... | @@ -2,7 +2,7 @@ import request from '@/utils/request' |
2 | 2 | |
3 | 3 | export function login(data) { |
4 | 4 | return request({ |
5 | - url: '/vue-element-admin/user/login', | |
5 | + url: '/yp/user/login', | |
6 | 6 | method: 'post', |
7 | 7 | data |
8 | 8 | }) |
... | ... | @@ -10,7 +10,7 @@ export function login(data) { |
10 | 10 | |
11 | 11 | export function getInfo(token) { |
12 | 12 | return request({ |
13 | - url: '/vue-element-admin/user/info', | |
13 | + url: '/yp/user/info', | |
14 | 14 | method: 'get', |
15 | 15 | params: { token } |
16 | 16 | }) |
... | ... | @@ -18,7 +18,39 @@ export function getInfo(token) { |
18 | 18 | |
19 | 19 | export function logout() { |
20 | 20 | return request({ |
21 | - url: '/vue-element-admin/user/logout', | |
21 | + url: '/yp/user/logout', | |
22 | 22 | method: 'post' |
23 | 23 | }) |
24 | 24 | } |
25 | + | |
26 | +export function fetchList(query) { | |
27 | + return request({ | |
28 | + url: '/yp/user/list', | |
29 | + method: 'get', | |
30 | + params: query | |
31 | + }) | |
32 | +} | |
33 | + | |
34 | +export function createUser(query) { | |
35 | + return request({ | |
36 | + url: '/yp/user/create', | |
37 | + method: 'post', | |
38 | + params: query | |
39 | + }) | |
40 | +} | |
41 | + | |
42 | +export function updateUser(query) { | |
43 | + return request({ | |
44 | + url: '/yp/user/update', | |
45 | + method: 'post', | |
46 | + params: query | |
47 | + }) | |
48 | +} | |
49 | + | |
50 | +export function delUser(query) { | |
51 | + return request({ | |
52 | + url: '/yp/user/del', | |
53 | + method: 'post', | |
54 | + params: query | |
55 | + }) | |
56 | +} | ... | ... |
src/directive/permission/permission.js
... | ... | @@ -16,7 +16,7 @@ export default { |
16 | 16 | el.parentNode && el.parentNode.removeChild(el) |
17 | 17 | } |
18 | 18 | } else { |
19 | - throw new Error(`need roles! Like v-permission="['admin','editor']"`) | |
19 | + throw new Error(`need roles! Like v-permission="['admin','assistant', 'shoper', 'runner']"`) | |
20 | 20 | } |
21 | 21 | } |
22 | 22 | } | ... | ... |
src/lang/en.js
1 | 1 | export default { |
2 | + // 添加的新词条------start | |
3 | + prod: { | |
4 | + menu_title: 'products' | |
5 | + }, | |
6 | + order: { | |
7 | + | |
8 | + }, | |
9 | + users: { | |
10 | + | |
11 | + }, | |
12 | + site: { | |
13 | + | |
14 | + }, | |
15 | + meta: { | |
16 | + | |
17 | + }, | |
18 | + system: { | |
19 | + memu: '系统' | |
20 | + }, | |
2 | 21 | route: { |
22 | + users: 'users', | |
23 | + usersList: 'user list', | |
24 | + shopers: 'shoper suply chain', | |
25 | + prods: 'products', | |
26 | + orders: 'orders', | |
27 | + sites: 'sites', | |
28 | + metas: { | |
29 | + metas: 'meta', | |
30 | + list: 'tree' | |
31 | + }, | |
32 | + systems: { | |
33 | + systems: 'system', | |
34 | + sites: 'site seting', | |
35 | + money: 'money seting', | |
36 | + industry: 'industry seting', | |
37 | + template: 'template seting' | |
38 | + }, | |
39 | + // 添加的新词条------end | |
3 | 40 | dashboard: 'Dashboard', |
4 | 41 | documentation: 'Documentation', |
5 | 42 | guide: 'Guide', |
... | ... | @@ -73,7 +110,15 @@ export default { |
73 | 110 | size: 'Global Size' |
74 | 111 | }, |
75 | 112 | login: { |
76 | - title: 'Login Form', | |
113 | + runner: 'officer Runner', | |
114 | + shoper: 'shop of Suply Chain', | |
115 | + assistant: 'worker in system', | |
116 | + signup: 'sign up', | |
117 | + forgetpassword: 'forget password', | |
118 | + rememberpassword: 'remember password', | |
119 | + | |
120 | + // title: 'let\'s conquer the world', | |
121 | + title: 'LET\'S FUCK THE WORLD', | |
77 | 122 | logIn: 'Login', |
78 | 123 | username: 'Username', |
79 | 124 | password: 'Password', | ... | ... |
src/lang/es.js
1 | 1 | export default { |
2 | + // 添加的新词条------start | |
3 | + prod: { | |
4 | + menu_title: 'products' | |
5 | + }, | |
6 | + order: { | |
7 | + | |
8 | + }, | |
9 | + users: { | |
10 | + | |
11 | + }, | |
12 | + site: { | |
13 | + | |
14 | + }, | |
15 | + meta: { | |
16 | + | |
17 | + }, | |
18 | + system: { | |
19 | + memu: '系统' | |
20 | + }, | |
2 | 21 | route: { |
22 | + users: 'users', | |
23 | + usersList: 'user list', | |
24 | + shopers: 'shoper suply chain', | |
25 | + prods: 'products', | |
26 | + orders: 'orders', | |
27 | + sites: 'sites', | |
28 | + metas: { | |
29 | + metas: '元', | |
30 | + list: '树' | |
31 | + }, | |
32 | + systems: { | |
33 | + systems: '系统设置', | |
34 | + sites: '站点设置', | |
35 | + money: '货币设置', | |
36 | + industry: '行业设置', | |
37 | + template: '模版设置' | |
38 | + }, | |
39 | + // 添加的新词条------end | |
3 | 40 | dashboard: 'Panel de control', |
4 | 41 | documentation: 'Documentación', |
5 | 42 | guide: 'Guía', |
... | ... | @@ -73,6 +110,13 @@ export default { |
73 | 110 | profile: 'Profile' |
74 | 111 | }, |
75 | 112 | login: { |
113 | + runner: 'officer Runner', | |
114 | + shoper: 'shop of Suply Chain', | |
115 | + assistant: 'worker in system', | |
116 | + signup: 'sign up', | |
117 | + forgetpassword: 'forget password', | |
118 | + rememberpassword: 'remember password', | |
119 | + | |
76 | 120 | title: 'Formulario de acceso', |
77 | 121 | logIn: 'Acceso', |
78 | 122 | username: 'Usuario', | ... | ... |
src/lang/ja.js
1 | 1 | export default { |
2 | + // 添加的新词条------start | |
3 | + prod: { | |
4 | + menu_title: 'products' | |
5 | + }, | |
6 | + order: { | |
7 | + | |
8 | + }, | |
9 | + users: { | |
10 | + | |
11 | + }, | |
12 | + site: { | |
13 | + | |
14 | + }, | |
15 | + meta: { | |
16 | + | |
17 | + }, | |
18 | + system: { | |
19 | + memu: '系统' | |
20 | + }, | |
2 | 21 | route: { |
22 | + users: 'users', | |
23 | + usersList: 'user list', | |
24 | + shopers: 'shoper suply chain', | |
25 | + prods: 'products', | |
26 | + orders: 'orders', | |
27 | + sites: 'sites', | |
28 | + metas: { | |
29 | + metas: '元', | |
30 | + list: '树' | |
31 | + }, | |
32 | + systems: { | |
33 | + systems: '系统设置', | |
34 | + sites: '站点设置', | |
35 | + money: '货币设置', | |
36 | + industry: '行业设置', | |
37 | + template: '模版设置' | |
38 | + }, | |
39 | + // 添加的新词条------end | |
3 | 40 | dashboard: 'トップ', |
4 | 41 | documentation: 'ドキュメント', |
5 | 42 | guide: 'ガイド', |
... | ... | @@ -73,6 +110,13 @@ export default { |
73 | 110 | size: '画面サイズ' |
74 | 111 | }, |
75 | 112 | login: { |
113 | + runner: 'officer Runner', | |
114 | + shoper: 'shop of Suply Chain', | |
115 | + assistant: 'worker in system', | |
116 | + signup: 'sign up', | |
117 | + forgetpassword: 'forget password', | |
118 | + rememberpassword: 'remember password', | |
119 | + | |
76 | 120 | title: 'ユーザログイン', |
77 | 121 | logIn: 'ログイン', |
78 | 122 | username: 'ユーザ名', | ... | ... |
src/lang/zh.js
1 | 1 | export default { |
2 | + // 添加的新词条------start | |
3 | + prod: { | |
4 | + menu_title: 'products' | |
5 | + }, | |
6 | + order: { | |
7 | + | |
8 | + }, | |
9 | + users: { | |
10 | + | |
11 | + }, | |
12 | + site: { | |
13 | + | |
14 | + }, | |
15 | + meta: { | |
16 | + | |
17 | + }, | |
18 | + system: { | |
19 | + memu: '系统' | |
20 | + }, | |
2 | 21 | route: { |
22 | + users: '用户', | |
23 | + usersList: '列表', | |
24 | + shopers: '厂商', | |
25 | + prods: '产品', | |
26 | + orders: '订单', | |
27 | + sites: '站点', | |
28 | + metas: { | |
29 | + metas: '元', | |
30 | + list: '树' | |
31 | + }, | |
32 | + systems: { | |
33 | + systems: '系统设置', | |
34 | + sites: '站点设置', | |
35 | + money: '货币设置', | |
36 | + industry: '行业设置', | |
37 | + template: '模版设置' | |
38 | + }, | |
39 | + // 添加的新词条------end | |
3 | 40 | dashboard: '首页', |
4 | 41 | documentation: '文档', |
5 | 42 | guide: '引导页', |
... | ... | @@ -73,7 +110,14 @@ export default { |
73 | 110 | size: '布局大小' |
74 | 111 | }, |
75 | 112 | login: { |
76 | - title: '系统登录', | |
113 | + runner: '运营官', | |
114 | + shoper: '商家', | |
115 | + assistant: '操作员', | |
116 | + signup: '注册', | |
117 | + forgetpassword: '忘记密码', | |
118 | + rememberpassword: 'remember password', | |
119 | + | |
120 | + title: '鱼皮出海', | |
77 | 121 | logIn: '登录', |
78 | 122 | username: '账号', |
79 | 123 | password: '密码', | ... | ... |
src/layout/components/Navbar.vue
... | ... | @@ -36,14 +36,14 @@ |
36 | 36 | {{ $t('navbar.dashboard') }} |
37 | 37 | </el-dropdown-item> |
38 | 38 | </router-link> |
39 | - <a target="_blank" href="https://github.com/PanJiaChen/vue-element-admin/"> | |
39 | + <!-- <a target="_blank" href="https://github.com/PanJiaChen/vue-element-admin/"> | |
40 | 40 | <el-dropdown-item> |
41 | 41 | {{ $t('navbar.github') }} |
42 | 42 | </el-dropdown-item> |
43 | 43 | </a> |
44 | 44 | <a target="_blank" href="https://panjiachen.github.io/vue-element-admin-site/#/"> |
45 | 45 | <el-dropdown-item>Docs</el-dropdown-item> |
46 | - </a> | |
46 | + </a> --> | |
47 | 47 | <el-dropdown-item divided @click.native="logout"> |
48 | 48 | <span style="display:block;">{{ $t('navbar.logOut') }}</span> |
49 | 49 | </el-dropdown-item> | ... | ... |
src/layout/components/Settings/index.vue
... | ... | @@ -22,11 +22,12 @@ |
22 | 22 | <span>{{ $t('settings.sidebarLogo') }}</span> |
23 | 23 | <el-switch v-model="sidebarLogo" class="drawer-switch" /> |
24 | 24 | </div> |
25 | - <a v-if="isShowJob" href="https://panjiachen.github.io/vue-element-admin-site/zh/job/" target="_blank" class="job-link"> | |
25 | + | |
26 | + <a v-if="isShowJob" href="https://glass.xiuyetang.com/" target="_blank" class="job-link"> | |
26 | 27 | <el-alert |
27 | - title="部门目前非常缺人!有兴趣的可以点击了解详情。坐标: 字节跳动" | |
28 | + title="鱼皮计划极为宏大,而且极为可行,我们要努力写代码,尽快打通任督二脉,要做好很有钱的思想准备" | |
28 | 29 | type="success" |
29 | - :closable="false" | |
30 | + :closable="true" | |
30 | 31 | /> |
31 | 32 | </a> |
32 | 33 | ... | ... |
src/layout/components/Sidebar/Logo.vue
src/router/index.js
... | ... | @@ -7,10 +7,14 @@ Vue.use(Router) |
7 | 7 | import Layout from '@/layout' |
8 | 8 | |
9 | 9 | /* Router Modules */ |
10 | -import componentsRouter from './modules/components' | |
11 | -import chartsRouter from './modules/charts' | |
10 | +// import componentsRouter from './modules/components' | |
11 | +// import chartsRouter from './modules/charts' | |
12 | 12 | import tableRouter from './modules/table' |
13 | -import nestedRouter from './modules/nested' | |
13 | +// import nestedRouter from './modules/nested' | |
14 | +import userRouter from './modules/user' | |
15 | +import systemRouter from './modules/system' | |
16 | +import prodRouter from './modules/prod' | |
17 | +import metaRouter from './modules/meta' | |
14 | 18 | |
15 | 19 | /** |
16 | 20 | * Note: sub-menu only appear when route children.length >= 1 |
... | ... | @@ -23,7 +27,7 @@ import nestedRouter from './modules/nested' |
23 | 27 | * redirect: noRedirect if set noRedirect will no redirect in the breadcrumb |
24 | 28 | * name:'router-name' the name is used by <keep-alive> (must set!!!) |
25 | 29 | * meta : { |
26 | - roles: ['admin','editor'] control the page roles (you can set multiple roles) | |
30 | + roles: ['admin','assistant','runner', 'shoper'] control the page roles (you can set multiple roles) | |
27 | 31 | title: 'title' the name show in sidebar and breadcrumb (recommend set) |
28 | 32 | icon: 'svg-name' the icon show in the sidebar |
29 | 33 | noCache: true if set true, the page will no be cached(default is false) |
... | ... | @@ -66,6 +70,11 @@ export const constantRoutes = [ |
66 | 70 | hidden: true |
67 | 71 | }, |
68 | 72 | { |
73 | + path: '/500', | |
74 | + component: () => import('@/views/error-page/500'), | |
75 | + hidden: true | |
76 | + }, | |
77 | + { | |
69 | 78 | path: '/401', |
70 | 79 | component: () => import('@/views/error-page/401'), |
71 | 80 | hidden: true |
... | ... | @@ -95,33 +104,33 @@ export const constantRoutes = [ |
95 | 104 | // } |
96 | 105 | // ] |
97 | 106 | // }, |
98 | - // { | |
99 | - // path: '/guide', | |
100 | - // component: Layout, | |
101 | - // redirect: '/guide/index', | |
102 | - // children: [ | |
103 | - // { | |
104 | - // path: 'index', | |
105 | - // component: () => import('@/views/guide/index'), | |
106 | - // name: 'Guide', | |
107 | - // meta: { title: 'guide', icon: 'guide', noCache: true } | |
108 | - // } | |
109 | - // ] | |
110 | - // }, | |
111 | 107 | { |
112 | - path: '/profile', | |
108 | + path: '/guide', | |
113 | 109 | component: Layout, |
114 | - redirect: '/profile/index', | |
115 | - hidden: true, | |
110 | + redirect: '/guide/index', | |
116 | 111 | children: [ |
117 | 112 | { |
118 | 113 | path: 'index', |
119 | - component: () => import('@/views/profile/index'), | |
120 | - name: 'Profile', | |
121 | - meta: { title: 'profile', icon: 'user', noCache: true } | |
114 | + component: () => import('@/views/guide/index'), | |
115 | + name: 'Guide', | |
116 | + meta: { title: 'guide', icon: 'guide', noCache: true } | |
122 | 117 | } |
123 | 118 | ] |
124 | 119 | } |
120 | + // { | |
121 | + // path: '/profile', | |
122 | + // component: Layout, | |
123 | + // redirect: '/profile/index', | |
124 | + // hidden: true, | |
125 | + // children: [ | |
126 | + // { | |
127 | + // path: 'index', | |
128 | + // component: () => import('@/views/profile/index'), | |
129 | + // name: 'Profile', | |
130 | + // meta: { title: 'profile', icon: 'user', noCache: true } | |
131 | + // } | |
132 | + // ] | |
133 | + // } | |
125 | 134 | ] |
126 | 135 | |
127 | 136 | /** |
... | ... | @@ -129,66 +138,137 @@ export const constantRoutes = [ |
129 | 138 | * the routes that need to be dynamically loaded based on user roles |
130 | 139 | */ |
131 | 140 | export const asyncRoutes = [ |
141 | + // { | |
142 | + // path: '/permission', | |
143 | + // component: Layout, | |
144 | + // redirect: '/permission/page', | |
145 | + // alwaysShow: true, // will always show the root menu | |
146 | + // name: 'Permission', | |
147 | + // meta: { | |
148 | + // title: 'permission', | |
149 | + // icon: 'lock', | |
150 | + // roles: ['admin', 'assistant'] // you can set roles in root nav | |
151 | + // }, | |
152 | + // children: [ | |
153 | + // { | |
154 | + // path: 'page', | |
155 | + // component: () => import('@/views/permission/page'), | |
156 | + // name: 'PagePermission', | |
157 | + // meta: { | |
158 | + // title: 'pagePermission', | |
159 | + // roles: ['admin','assistant'] // or you can only set roles in sub nav | |
160 | + // } | |
161 | + // }, | |
162 | + // { | |
163 | + // path: 'directive', | |
164 | + // component: () => import('@/views/permission/directive'), | |
165 | + // name: 'DirectivePermission', | |
166 | + // meta: { | |
167 | + // title: 'directivePermission', | |
168 | + // roles: ['admin', 'shoper'] | |
169 | + // // if do not set roles, means: this page does not require permission | |
170 | + // } | |
171 | + // }, | |
172 | + // { | |
173 | + // path: 'role', | |
174 | + // component: () => import('@/views/permission/role'), | |
175 | + // name: 'RolePermission', | |
176 | + // meta: { | |
177 | + // title: 'rolePermission', | |
178 | + // roles: ['admin', 'runner'] | |
179 | + // } | |
180 | + // } | |
181 | + // ] | |
182 | + // }, | |
183 | + tableRouter, | |
184 | + metaRouter, | |
185 | + userRouter, | |
186 | + prodRouter, | |
132 | 187 | { |
133 | - path: '/permission', | |
188 | + path: '/orders', | |
134 | 189 | component: Layout, |
135 | - redirect: '/permission/page', | |
190 | + redirect: '/order/page', | |
136 | 191 | alwaysShow: true, // will always show the root menu |
137 | - name: 'Permission', | |
192 | + name: 'Order', | |
138 | 193 | meta: { |
139 | - title: 'permission', | |
140 | - icon: 'lock', | |
141 | - roles: ['admin', 'editor'] // you can set roles in root nav | |
194 | + title: 'orders', | |
195 | + icon: 'shopping', | |
196 | + roles: ['admin', 'assistant', 'runner', 'shoper'] // you can set roles in root nav | |
142 | 197 | }, |
143 | 198 | children: [ |
144 | 199 | { |
145 | 200 | path: 'page', |
146 | 201 | component: () => import('@/views/permission/page'), |
147 | - name: 'PagePermission', | |
202 | + name: 'OrderList', | |
148 | 203 | meta: { |
149 | - title: 'pagePermission', | |
150 | - roles: ['admin'] // or you can only set roles in sub nav | |
204 | + title: 'OrderList', | |
205 | + roles: ['admin', 'assistant', 'runner', 'shoper'] // or you can only set roles in sub nav | |
151 | 206 | } |
152 | 207 | }, |
153 | 208 | { |
154 | - path: 'directive', | |
209 | + path: 'defined', | |
155 | 210 | component: () => import('@/views/permission/directive'), |
156 | - name: 'DirectivePermission', | |
211 | + name: 'OrderDefiend', | |
157 | 212 | meta: { |
158 | - title: 'directivePermission' | |
213 | + title: 'OrderDefiend', | |
214 | + roles: ['admin', 'assistant', 'runner', 'shoper'] | |
159 | 215 | // if do not set roles, means: this page does not require permission |
160 | 216 | } |
161 | - }, | |
162 | - { | |
163 | - path: 'role', | |
164 | - component: () => import('@/views/permission/role'), | |
165 | - name: 'RolePermission', | |
166 | - meta: { | |
167 | - title: 'rolePermission', | |
168 | - roles: ['admin', 'editor'] | |
169 | - } | |
170 | 217 | } |
171 | 218 | ] |
172 | 219 | }, |
173 | - | |
174 | 220 | { |
175 | - path: '/icon', | |
221 | + path: '/sites', | |
176 | 222 | component: Layout, |
223 | + redirect: '/site/page', | |
224 | + alwaysShow: true, // will always show the root menu | |
225 | + name: 'Site', | |
226 | + meta: { | |
227 | + title: 'sites', | |
228 | + icon: 'people', | |
229 | + roles: ['admin', 'assistant', 'runner'] // you can set roles in root nav | |
230 | + }, | |
177 | 231 | children: [ |
178 | 232 | { |
179 | - path: 'index', | |
180 | - component: () => import('@/views/icons/index'), | |
181 | - name: 'Icons', | |
182 | - meta: { title: 'icons', icon: 'icon', noCache: true } | |
233 | + path: 'page', | |
234 | + component: () => import('@/views/permission/page'), | |
235 | + name: 'SiteList', | |
236 | + meta: { | |
237 | + title: 'SiteList', | |
238 | + roles: ['admin', 'assistant', 'runner'] // or you can only set roles in sub nav | |
239 | + } | |
240 | + }, | |
241 | + { | |
242 | + path: 'defined', | |
243 | + component: () => import('@/views/permission/directive'), | |
244 | + name: 'SiteDefiend', | |
245 | + meta: { | |
246 | + title: 'SiteDefiend', | |
247 | + roles: ['admin', 'assistant', 'runner'] | |
248 | + // if do not set roles, means: this page does not require permission | |
249 | + } | |
183 | 250 | } |
184 | 251 | ] |
185 | 252 | }, |
186 | 253 | |
254 | + // { | |
255 | + // path: '/icon', | |
256 | + // component: Layout, | |
257 | + // children: [ | |
258 | + // { | |
259 | + // path: 'index', | |
260 | + // component: () => import('@/views/icons/index'), | |
261 | + // name: 'Icons', | |
262 | + // meta: { title: 'icons', icon: 'icon', noCache: true } | |
263 | + // } | |
264 | + // ] | |
265 | + // }, | |
266 | + systemRouter, | |
187 | 267 | /** when your routing map is too long, you can split it into small modules **/ |
188 | - componentsRouter, | |
189 | - chartsRouter, | |
190 | - nestedRouter, | |
191 | - tableRouter, | |
268 | + // componentsRouter, | |
269 | + // chartsRouter, | |
270 | + // nestedRouter, | |
271 | + // tableRouter, | |
192 | 272 | |
193 | 273 | // { |
194 | 274 | // path: '/example', |
... | ... | @@ -222,18 +302,18 @@ export const asyncRoutes = [ |
222 | 302 | // ] |
223 | 303 | // }, |
224 | 304 | |
225 | - { | |
226 | - path: '/tab', | |
227 | - component: Layout, | |
228 | - children: [ | |
229 | - { | |
230 | - path: 'index', | |
231 | - component: () => import('@/views/tab/index'), | |
232 | - name: 'Tab', | |
233 | - meta: { title: 'tab', icon: 'tab' } | |
234 | - } | |
235 | - ] | |
236 | - }, | |
305 | + // { | |
306 | + // path: '/tab', | |
307 | + // component: Layout, | |
308 | + // children: [ | |
309 | + // { | |
310 | + // path: 'index', | |
311 | + // component: () => import('@/views/tab/index'), | |
312 | + // name: 'Tab', | |
313 | + // meta: { title: 'tab', icon: 'tab' } | |
314 | + // } | |
315 | + // ] | |
316 | + // }, | |
237 | 317 | |
238 | 318 | // { |
239 | 319 | // path: '/error', |
... | ... | @@ -346,18 +426,18 @@ export const asyncRoutes = [ |
346 | 426 | // hidden: true |
347 | 427 | // }, |
348 | 428 | |
349 | - { | |
350 | - path: '/theme', | |
351 | - component: Layout, | |
352 | - children: [ | |
353 | - { | |
354 | - path: 'index', | |
355 | - component: () => import('@/views/theme/index'), | |
356 | - name: 'Theme', | |
357 | - meta: { title: 'theme', icon: 'theme' } | |
358 | - } | |
359 | - ] | |
360 | - }, | |
429 | + // { | |
430 | + // path: '/theme', | |
431 | + // component: Layout, | |
432 | + // children: [ | |
433 | + // { | |
434 | + // path: 'index', | |
435 | + // component: () => import('@/views/theme/index'), | |
436 | + // name: 'Theme', | |
437 | + // meta: { title: 'theme', icon: 'theme' } | |
438 | + // } | |
439 | + // ] | |
440 | + // }, | |
361 | 441 | |
362 | 442 | // { |
363 | 443 | // path: '/clipboard', | ... | ... |
src/router/modules/meta.js
... | ... | @@ -0,0 +1,25 @@ |
1 | +import Layout from '@/layout' | |
2 | + | |
3 | +const metaRouter = { | |
4 | + path: '/meta', | |
5 | + component: Layout, | |
6 | + redirect: '/meta/page', | |
7 | + alwaysShow: true, // will always show the root menu | |
8 | + name: 'Meta', | |
9 | + meta: { | |
10 | + title: 'metas.metas', | |
11 | + icon: 'zip', | |
12 | + roles: ['admin', 'assistant'] // you can set roles in root nav | |
13 | + }, | |
14 | + children: [{ | |
15 | + path: 'page', | |
16 | + component: () => import('@/views/meta/complex-table'), | |
17 | + name: 'MetaList', | |
18 | + meta: { | |
19 | + title: 'MetaList', | |
20 | + icon: 'zip', | |
21 | + roles: ['admin', 'assistant'] // or you can only set roles in sub nav | |
22 | + } | |
23 | + }] | |
24 | +} | |
25 | +export default metaRouter | ... | ... |
src/router/modules/prod.js
... | ... | @@ -0,0 +1,38 @@ |
1 | +/** When your routing table is too long, you can split it into small modules**/ | |
2 | + | |
3 | +import Layout from '@/layout' | |
4 | + | |
5 | +const prodRouter = { | |
6 | + path: '/prods', | |
7 | + component: Layout, | |
8 | + redirect: '/prod/page', | |
9 | + alwaysShow: true, // will always show the root menu | |
10 | + name: 'Prod', | |
11 | + meta: { | |
12 | + title: 'prods', // 会自动被i18n替换 | |
13 | + icon: 'star', | |
14 | + roles: ['admin', 'assistant', 'runner', 'shoper'] // you can set roles in root nav | |
15 | + }, | |
16 | + children: [{ | |
17 | + path: 'page', | |
18 | + component: () => import('@/views/permission/page'), | |
19 | + name: 'ProdList', | |
20 | + meta: { | |
21 | + title: 'ProdList', | |
22 | + roles: ['admin', 'assistant', 'runner', 'shoper'] // or you can only set roles in sub nav | |
23 | + } | |
24 | + }, | |
25 | + { | |
26 | + path: 'defined', | |
27 | + component: () => import('@/views/permission/directive'), | |
28 | + name: 'ProdDefiend', | |
29 | + meta: { | |
30 | + title: 'ProdDefiend', | |
31 | + roles: ['admin', 'assistant', 'shoper'] | |
32 | + // if do not set roles, means: this page does not require permission | |
33 | + } | |
34 | + } | |
35 | + ] | |
36 | +} | |
37 | + | |
38 | +export default prodRouter | ... | ... |
src/router/modules/system.js
... | ... | @@ -0,0 +1,54 @@ |
1 | +import Layout from '@/layout' | |
2 | + | |
3 | +const systemRouter = { | |
4 | + path: '/system', | |
5 | + component: Layout, | |
6 | + redirect: '/system/page', | |
7 | + alwaysShow: true, // will always show the root menu | |
8 | + name: 'System', | |
9 | + meta: { | |
10 | + title: 'systems.systems', | |
11 | + icon: 'component', | |
12 | + roles: ['admin', 'assistant', 'runner'] // you can set roles in root nav | |
13 | + }, | |
14 | + children: [{ | |
15 | + path: 'page', | |
16 | + component: () => import('@/views/example/list'), | |
17 | + name: 'SystemList', | |
18 | + meta: { | |
19 | + title: 'systems.sites', | |
20 | + roles: ['admin', 'assistant', 'runner'] // or you can only set roles in sub nav | |
21 | + } | |
22 | + }, | |
23 | + { | |
24 | + path: 'page', | |
25 | + component: () => import('@/views/example/list'), | |
26 | + name: 'SystemList', | |
27 | + meta: { | |
28 | + title: 'systems.money', | |
29 | + roles: ['admin', 'assistant', 'runner'] // or you can only set roles in sub nav | |
30 | + } | |
31 | + }, | |
32 | + { | |
33 | + path: 'page', | |
34 | + component: () => import('@/views/example/list'), | |
35 | + name: 'SystemList', | |
36 | + meta: { | |
37 | + title: 'systems.industry', | |
38 | + roles: ['admin', 'assistant', 'runner'] // or you can only set roles in sub nav | |
39 | + } | |
40 | + }, | |
41 | + { | |
42 | + path: 'page', | |
43 | + component: () => import('@/views/example/list'), | |
44 | + name: 'SystemList', | |
45 | + meta: { | |
46 | + title: 'systems.template', | |
47 | + roles: ['admin', 'assistant', 'runner'] // or you can only set roles in sub nav | |
48 | + } | |
49 | + } | |
50 | + | |
51 | + ] | |
52 | +} | |
53 | + | |
54 | +export default systemRouter | ... | ... |
src/router/modules/user.js
... | ... | @@ -0,0 +1,37 @@ |
1 | +/** When your routing table is too long, you can split it into small modules**/ | |
2 | + | |
3 | +import Layout from '@/layout' | |
4 | + | |
5 | +const chartsRouter = { | |
6 | + path: '/users', | |
7 | + component: Layout, | |
8 | + redirect: '/users/page', | |
9 | + alwaysShow: true, // will always show the root menu | |
10 | + name: 'Users', | |
11 | + meta: { | |
12 | + title: 'users', | |
13 | + icon: 'peoples', | |
14 | + roles: ['admin', 'assistant'] // you can set roles in root nav | |
15 | + }, | |
16 | + children: [{ | |
17 | + path: 'page', | |
18 | + component: () => import('@/views/users/list'), | |
19 | + name: 'UserList', | |
20 | + meta: { | |
21 | + title: 'UserList', | |
22 | + roles: ['admin', 'assistant', 'shoper', 'runner'] // or you can only set roles in sub nav | |
23 | + } | |
24 | + } | |
25 | + // ,{ | |
26 | + // path: '/icons', | |
27 | + // component: () => import('@/views/icons/index'), | |
28 | + // name: 'icons', | |
29 | + // meta: { | |
30 | + // title: 'icons', | |
31 | + // roles: ['admin', 'assistant', 'shoper', 'runner'] // or you can only set roles in sub nav | |
32 | + // } | |
33 | + // } | |
34 | + ] | |
35 | +} | |
36 | + | |
37 | +export default chartsRouter | ... | ... |
src/settings.js
1 | 1 | module.exports = { |
2 | - title: 'Vue Element Admin', | |
2 | + title: 'Let\'s fuck this workd', | |
3 | 3 | |
4 | 4 | /** |
5 | 5 | * @type {boolean} true | false |
... | ... | @@ -17,13 +17,13 @@ module.exports = { |
17 | 17 | * @type {boolean} true | false |
18 | 18 | * @description Whether fix the header |
19 | 19 | */ |
20 | - fixedHeader: false, | |
20 | + fixedHeader: true, | |
21 | 21 | |
22 | 22 | /** |
23 | 23 | * @type {boolean} true | false |
24 | 24 | * @description Whether show the logo in sidebar |
25 | 25 | */ |
26 | - sidebarLogo: false, | |
26 | + sidebarLogo: true, | |
27 | 27 | |
28 | 28 | /** |
29 | 29 | * @type {boolean} true | false | ... | ... |
src/utils/permission.js
... | ... | @@ -19,7 +19,7 @@ export default function checkPermission(value) { |
19 | 19 | } |
20 | 20 | return true |
21 | 21 | } else { |
22 | - console.error(`need roles! Like v-permission="['admin','editor']"`) | |
22 | + console.error(`need roles! Like v-permission="['admin', 'assistant', 'runner', 'shoper']"`) | |
23 | 23 | return false |
24 | 24 | } |
25 | 25 | } | ... | ... |
src/utils/request.js
1 | 1 | import axios from 'axios' |
2 | -import { MessageBox, Message } from 'element-ui' | |
2 | +import qs from 'qs' | |
3 | +import { | |
4 | + MessageBox, | |
5 | + Message, | |
6 | + // Loading, | |
7 | + Notification | |
8 | +} from 'element-ui' | |
3 | 9 | import store from '@/store' |
4 | -import { getToken } from '@/utils/auth' | |
10 | +import { | |
11 | + getToken | |
12 | +} from '@/utils/auth' | |
5 | 13 | |
6 | 14 | // create an axios instance |
7 | 15 | const service = axios.create({ |
8 | 16 | baseURL: process.env.VUE_APP_BASE_API, // url = base url + request url |
9 | - // withCredentials: true, // send cookies when cross-domain requests | |
10 | - timeout: 5000 // request timeout | |
17 | + withCredentials: true, // send cookies when cross-domain requests | |
18 | + timeout: 3000 // request timeout | |
11 | 19 | }) |
12 | - | |
20 | +// let loadingInstance | |
13 | 21 | // request interceptor |
14 | 22 | service.interceptors.request.use( |
15 | 23 | config => { |
16 | - // do something before request is sent | |
17 | - | |
24 | + const token = sessionStorage.getItem('access_token') | |
25 | + // const csrf = store.getters.csrf | |
26 | + if (token) { | |
27 | + config.headers = { | |
28 | + 'access-token': token, | |
29 | + 'Content-Type': 'application/x-www-form-urlencoded' | |
30 | + } | |
31 | + } | |
32 | + if (config.url === 'refresh') { | |
33 | + config.headers = { | |
34 | + 'refresh-token': sessionStorage.getItem('refresh_token'), | |
35 | + 'Content-Type': 'application/x-www-form-urlencoded' | |
36 | + } | |
37 | + } | |
38 | + // let options = { | |
39 | + // lock: true, | |
40 | + // fullscreen: false, | |
41 | + // text: '数据加载中……', | |
42 | + // // background: '#FFCC00', | |
43 | + // spinner: 'el-icon-loading' | |
44 | + // }; | |
45 | + const options = { | |
46 | + type: 'success', | |
47 | + message: config.url, | |
48 | + title: 'request axios ', | |
49 | + showClose: true, | |
50 | + duration: 3000 | |
51 | + } | |
52 | + Notification(options) | |
53 | + // loadingInstance = Loading.service(options); | |
54 | + config.method === 'post' | |
55 | + ? config.data = qs.stringify({ | |
56 | + ...config.data | |
57 | + }) | |
58 | + : config.params = { | |
59 | + ...config.params | |
60 | + } | |
18 | 61 | if (store.getters.token) { |
19 | 62 | // let each request carry token |
20 | 63 | // ['X-Token'] is a custom headers key |
21 | 64 | // please modify it according to the actual situation |
22 | 65 | config.headers['X-Token'] = getToken() |
23 | 66 | } |
67 | + config.headers['Content-Type'] = 'application/x-www-form-urlencoded' | |
68 | + | |
24 | 69 | return config |
70 | + // do something before request is sent | |
25 | 71 | }, |
26 | 72 | error => { |
27 | 73 | // do something with request error |
74 | + Message({ | |
75 | + message: error || 'Error', | |
76 | + type: 'error', | |
77 | + duration: 5 * 1000 | |
78 | + }) | |
28 | 79 | console.log(error) // for debug |
29 | 80 | return Promise.reject(error) |
30 | 81 | } |
... | ... | @@ -35,7 +86,7 @@ service.interceptors.response.use( |
35 | 86 | /** |
36 | 87 | * If you want to get http information such as headers or status |
37 | 88 | * Please return response => response |
38 | - */ | |
89 | + */ | |
39 | 90 | |
40 | 91 | /** |
41 | 92 | * Determine the request status by custom code |
... | ... | @@ -43,6 +94,27 @@ service.interceptors.response.use( |
43 | 94 | * You can also judge the status by HTTP Status Code |
44 | 95 | */ |
45 | 96 | response => { |
97 | + const options = { | |
98 | + type: 'error', | |
99 | + message: response.status, | |
100 | + title: 'response status value ', | |
101 | + showClose: true, | |
102 | + duration: 1000 | |
103 | + } | |
104 | + Notification(options) | |
105 | + // Notification.close() | |
106 | + // 这里根据后端提供的数据进行对应的处理 | |
107 | + console.log('response===>', response) | |
108 | + // 定时刷新access-token | |
109 | + // if (!response.data.value && response.data.data.message === 'token invalid') { | |
110 | + // // 刷新token | |
111 | + // store.dispatch('refresh').then(response => { | |
112 | + // sessionStorage.setItem('access_token', response.data) | |
113 | + // }).catch(error => { | |
114 | + // throw new Error('token刷新' + error) | |
115 | + // }) | |
116 | + // } | |
117 | + | |
46 | 118 | const res = response.data |
47 | 119 | |
48 | 120 | // if the custom code is not 20000, it is judged as an error. |
... | ... | @@ -72,14 +144,24 @@ service.interceptors.response.use( |
72 | 144 | } |
73 | 145 | }, |
74 | 146 | error => { |
75 | - console.log('err' + error) // for debug | |
147 | + console.log('error', error) | |
148 | + // console.log(JSON.stringify(error)); | |
149 | + // 500的状态也应该处理一下 | |
150 | + // 401-403的状态也应该处理一下 | |
151 | + const text = JSON.parse(JSON.stringify(error)).response.status === 404 | |
152 | + ? '404' | |
153 | + : '网络异常,请重试' | |
76 | 154 | Message({ |
77 | - message: error.message, | |
155 | + message: text || 'Error', | |
78 | 156 | type: 'error', |
79 | 157 | duration: 5 * 1000 |
80 | 158 | }) |
159 | + | |
81 | 160 | return Promise.reject(error) |
82 | 161 | } |
83 | 162 | ) |
84 | 163 | |
164 | +// 假设你想移除拦截器 | |
165 | +// axios.interceptors.request.eject(service); | |
166 | + | |
85 | 167 | export default service | ... | ... |
src/utils/validate.js
... | ... | @@ -15,7 +15,7 @@ export function isExternal(path) { |
15 | 15 | * @returns {Boolean} |
16 | 16 | */ |
17 | 17 | export function validUsername(str) { |
18 | - const valid_map = ['admin', 'editor'] | |
18 | + const valid_map = ['admin', 'assistant', 'runner', 'shoper'] | |
19 | 19 | return valid_map.indexOf(str.trim()) >= 0 |
20 | 20 | } |
21 | 21 | ... | ... |
src/views/403.vue
... | ... | @@ -1,59 +0,0 @@ |
1 | -<template> | |
2 | - <div class="error-page"> | |
3 | - <div class="error-code"> | |
4 | - 4 | |
5 | - <span>0</span>3 | |
6 | - </div> | |
7 | - <div class="error-desc">啊哦~ 你没有权限访问该页面哦</div> | |
8 | - <div class="error-handle"> | |
9 | - <router-link to="/"> | |
10 | - <el-button type="primary" size="large">返回首页</el-button> | |
11 | - </router-link> | |
12 | - <el-button class="error-btn" type="primary" size="large" @click="goBack">返回上一页</el-button> | |
13 | - </div> | |
14 | - </div> | |
15 | -</template> | |
16 | - | |
17 | -<script> | |
18 | -export default { | |
19 | - methods: { | |
20 | - goBack() { | |
21 | - this.$router.go(-1); | |
22 | - } | |
23 | - } | |
24 | -}; | |
25 | -</script> | |
26 | - | |
27 | - | |
28 | -<style scoped> | |
29 | -.error-page { | |
30 | - display: flex; | |
31 | - justify-content: center; | |
32 | - align-items: center; | |
33 | - flex-direction: column; | |
34 | - width: 100%; | |
35 | - height: 100%; | |
36 | - background: #f3f3f3; | |
37 | - box-sizing: border-box; | |
38 | -} | |
39 | -.error-code { | |
40 | - line-height: 1; | |
41 | - font-size: 250px; | |
42 | - font-weight: bolder; | |
43 | - color: #f02d2d; | |
44 | -} | |
45 | -.error-code span { | |
46 | - color: #00a854; | |
47 | -} | |
48 | -.error-desc { | |
49 | - font-size: 30px; | |
50 | - color: #777; | |
51 | -} | |
52 | -.error-handle { | |
53 | - margin-top: 30px; | |
54 | - padding-bottom: 200px; | |
55 | -} | |
56 | -.error-btn { | |
57 | - margin-left: 100px; | |
58 | -} | |
59 | -</style> | |
60 | 0 | \ No newline at end of file |
src/views/502.vue
... | ... | @@ -1,57 +0,0 @@ |
1 | -<template> | |
2 | - <div class="error-page"> | |
3 | - <div class="error-code"> | |
4 | - 5 | |
5 | - <span>0</span>2 | |
6 | - </div> | |
7 | - <div class="error-desc">啊哦~ 系统出了故障!</div> | |
8 | - <div class="error-handle"> | |
9 | - <router-link to="/"> | |
10 | - <el-button type="primary" size="large">返回首页</el-button> | |
11 | - </router-link> | |
12 | - <el-button class="error-btn" type="primary" size="large" @click="goBack">返回上一页</el-button> | |
13 | - </div> | |
14 | - </div> | |
15 | -</template> | |
16 | - | |
17 | -<script> | |
18 | -export default { | |
19 | - methods: { | |
20 | - goBack() { | |
21 | - this.$router.go(-1); | |
22 | - } | |
23 | - } | |
24 | -}; | |
25 | -</script> | |
26 | -<style scoped> | |
27 | -.error-page { | |
28 | - display: flex; | |
29 | - justify-content: center; | |
30 | - align-items: center; | |
31 | - flex-direction: column; | |
32 | - width: 100%; | |
33 | - height: 100%; | |
34 | - background: #f3f3f3; | |
35 | - box-sizing: border-box; | |
36 | -} | |
37 | -.error-code { | |
38 | - line-height: 1; | |
39 | - font-size: 250px; | |
40 | - font-weight: bolder; | |
41 | - color: #f02d2d; | |
42 | -} | |
43 | -.error-code span { | |
44 | - color: #00a854; | |
45 | -} | |
46 | -.error-desc { | |
47 | - font-size: 30px; | |
48 | - color: #777; | |
49 | -} | |
50 | -.error-handle { | |
51 | - margin-top: 30px; | |
52 | - padding-bottom: 200px; | |
53 | -} | |
54 | -.error-btn { | |
55 | - margin-left: 100px; | |
56 | -} | |
57 | -</style> | |
58 | 0 | \ No newline at end of file |
src/views/dashboard/admin/index.vue
1 | 1 | <template> |
2 | 2 | <div class="dashboard-editor-container"> |
3 | - <github-corner class="github-corner" /> | |
3 | + <!-- <github-corner class="github-corner" /> --> | |
4 | 4 | |
5 | 5 | <panel-group @handleSetLineChartData="handleSetLineChartData" /> |
6 | 6 | |
... | ... | @@ -41,7 +41,7 @@ |
41 | 41 | </template> |
42 | 42 | |
43 | 43 | <script> |
44 | -import GithubCorner from '@/components/GithubCorner' | |
44 | +// import GithubCorner from '@/components/GithubCorner' | |
45 | 45 | import PanelGroup from './components/PanelGroup' |
46 | 46 | import LineChart from './components/LineChart' |
47 | 47 | import RaddarChart from './components/RaddarChart' |
... | ... | @@ -73,7 +73,7 @@ const lineChartData = { |
73 | 73 | export default { |
74 | 74 | name: 'DashboardAdmin', |
75 | 75 | components: { |
76 | - GithubCorner, | |
76 | + // GithubCorner, | |
77 | 77 | PanelGroup, |
78 | 78 | LineChart, |
79 | 79 | RaddarChart, | ... | ... |
src/views/dashboard/editor/index.vue
1 | 1 | <template> |
2 | 2 | <div class="dashboard-editor-container"> |
3 | - <div class=" clearfix"> | |
3 | + <div class="clearfix"> | |
4 | 4 | <pan-thumb :image="avatar" style="float: left"> |
5 | 5 | Your roles: |
6 | 6 | <span v-for="item in roles" :key="item" class="pan-info-roles">{{ item }}</span> |
... | ... | @@ -8,7 +8,7 @@ |
8 | 8 | <github-corner style="position: absolute; top: 0px; border: 0; right: 0;" /> |
9 | 9 | <div class="info-container"> |
10 | 10 | <span class="display_name">{{ name }}</span> |
11 | - <span style="font-size:20px;padding-top:20px;display:inline-block;">Editor's Dashboard</span> | |
11 | + <span style="font-size:20px;padding-top:20px;display:inline-block;">{{ roles }}'s Dashboard</span> | |
12 | 12 | </div> |
13 | 13 | </div> |
14 | 14 | <div> |
... | ... | @@ -20,55 +20,53 @@ |
20 | 20 | <script> |
21 | 21 | import { mapGetters } from 'vuex' |
22 | 22 | import PanThumb from '@/components/PanThumb' |
23 | -import GithubCorner from '@/components/GithubCorner' | |
23 | +// import GithubCorner from '@/components/GithubCorner' | |
24 | 24 | |
25 | 25 | export default { |
26 | 26 | name: 'DashboardEditor', |
27 | - components: { PanThumb, GithubCorner }, | |
27 | + // components: { PanThumb, GithubCorner }, | |
28 | + components: { PanThumb }, | |
28 | 29 | data() { |
29 | 30 | return { |
30 | - emptyGif: 'https://wpimg.wallstcn.com/0e03b7da-db9e-4819-ba10-9016ddfdaed3' | |
31 | + emptyGif: | |
32 | + 'https://wpimg.wallstcn.com/0e03b7da-db9e-4819-ba10-9016ddfdaed3' | |
31 | 33 | } |
32 | 34 | }, |
33 | 35 | computed: { |
34 | - ...mapGetters([ | |
35 | - 'name', | |
36 | - 'avatar', | |
37 | - 'roles' | |
38 | - ]) | |
36 | + ...mapGetters(['name', 'avatar', 'roles']) | |
39 | 37 | } |
40 | 38 | } |
41 | 39 | </script> |
42 | 40 | |
43 | 41 | <style lang="scss" scoped> |
44 | - .emptyGif { | |
42 | +.emptyGif { | |
43 | + display: block; | |
44 | + width: 45%; | |
45 | + margin: 0 auto; | |
46 | +} | |
47 | + | |
48 | +.dashboard-editor-container { | |
49 | + background-color: #e3e3e3; | |
50 | + min-height: 100vh; | |
51 | + padding: 50px 60px 0px; | |
52 | + .pan-info-roles { | |
53 | + font-size: 12px; | |
54 | + font-weight: 700; | |
55 | + color: #333; | |
45 | 56 | display: block; |
46 | - width: 45%; | |
47 | - margin: 0 auto; | |
48 | 57 | } |
49 | - | |
50 | - .dashboard-editor-container { | |
51 | - background-color: #e3e3e3; | |
52 | - min-height: 100vh; | |
53 | - padding: 50px 60px 0px; | |
54 | - .pan-info-roles { | |
55 | - font-size: 12px; | |
56 | - font-weight: 700; | |
57 | - color: #333; | |
58 | - display: block; | |
59 | - } | |
60 | - .info-container { | |
61 | - position: relative; | |
62 | - margin-left: 190px; | |
63 | - height: 150px; | |
64 | - line-height: 200px; | |
65 | - .display_name { | |
66 | - font-size: 48px; | |
67 | - line-height: 48px; | |
68 | - color: #212121; | |
69 | - position: absolute; | |
70 | - top: 25px; | |
71 | - } | |
58 | + .info-container { | |
59 | + position: relative; | |
60 | + margin-left: 190px; | |
61 | + height: 150px; | |
62 | + line-height: 200px; | |
63 | + .display_name { | |
64 | + font-size: 48px; | |
65 | + line-height: 48px; | |
66 | + color: #212121; | |
67 | + position: absolute; | |
68 | + top: 25px; | |
72 | 69 | } |
73 | 70 | } |
71 | +} | |
74 | 72 | </style> | ... | ... |
src/views/error-page/403.vue
... | ... | @@ -0,0 +1,58 @@ |
1 | +<template> | |
2 | + <div class="error-page"> | |
3 | + <div class="error-code"> | |
4 | + 4 | |
5 | + <span>0</span>3 | |
6 | + </div> | |
7 | + <div class="error-desc">啊哦~ 你没有权限访问该页面哦</div> | |
8 | + <div class="error-handle"> | |
9 | + <router-link to="/"> | |
10 | + <el-button type="primary" size="large">返回首页</el-button> | |
11 | + </router-link> | |
12 | + <el-button class="error-btn" type="primary" size="large" @click="goBack">返回上一页</el-button> | |
13 | + </div> | |
14 | + </div> | |
15 | +</template> | |
16 | + | |
17 | +<script> | |
18 | +export default { | |
19 | + methods: { | |
20 | + goBack() { | |
21 | + this.$router.go(-1) | |
22 | + } | |
23 | + } | |
24 | +} | |
25 | +</script> | |
26 | + | |
27 | +<style scoped> | |
28 | +.error-page { | |
29 | + display: flex; | |
30 | + justify-content: center; | |
31 | + align-items: center; | |
32 | + flex-direction: column; | |
33 | + width: 100%; | |
34 | + height: 100%; | |
35 | + background: #f3f3f3; | |
36 | + box-sizing: border-box; | |
37 | +} | |
38 | +.error-code { | |
39 | + line-height: 1; | |
40 | + font-size: 250px; | |
41 | + font-weight: bolder; | |
42 | + color: #f02d2d; | |
43 | +} | |
44 | +.error-code span { | |
45 | + color: #00a854; | |
46 | +} | |
47 | +.error-desc { | |
48 | + font-size: 30px; | |
49 | + color: #777; | |
50 | +} | |
51 | +.error-handle { | |
52 | + margin-top: 30px; | |
53 | + padding-bottom: 200px; | |
54 | +} | |
55 | +.error-btn { | |
56 | + margin-left: 100px; | |
57 | +} | |
58 | +</style> | ... | ... |
src/views/error-page/500.vue
... | ... | @@ -0,0 +1,57 @@ |
1 | +<template> | |
2 | + <div class="error-page"> | |
3 | + <div class="error-code"> | |
4 | + 5 | |
5 | + <span>0</span>0 | |
6 | + </div> | |
7 | + <div class="error-desc">啊哦~ 系统出了故障!</div> | |
8 | + <div class="error-handle"> | |
9 | + <router-link to="/"> | |
10 | + <el-button type="primary" size="large">返回首页</el-button> | |
11 | + </router-link> | |
12 | + <el-button class="error-btn" type="primary" size="large" @click="goBack">返回上一页</el-button> | |
13 | + </div> | |
14 | + </div> | |
15 | +</template> | |
16 | + | |
17 | +<script> | |
18 | +export default { | |
19 | + methods: { | |
20 | + goBack() { | |
21 | + this.$router.go(-1) | |
22 | + } | |
23 | + } | |
24 | +} | |
25 | +</script> | |
26 | +<style scoped> | |
27 | +.error-page { | |
28 | + display: flex; | |
29 | + justify-content: center; | |
30 | + align-items: center; | |
31 | + flex-direction: column; | |
32 | + width: 100%; | |
33 | + height: 100%; | |
34 | + background: #f3f3f3; | |
35 | + box-sizing: border-box; | |
36 | +} | |
37 | +.error-code { | |
38 | + line-height: 1; | |
39 | + font-size: 250px; | |
40 | + font-weight: bolder; | |
41 | + color: #f02d2d; | |
42 | +} | |
43 | +.error-code span { | |
44 | + color: #00a854; | |
45 | +} | |
46 | +.error-desc { | |
47 | + font-size: 30px; | |
48 | + color: #777; | |
49 | +} | |
50 | +.error-handle { | |
51 | + margin-top: 30px; | |
52 | + padding-bottom: 200px; | |
53 | +} | |
54 | +.error-btn { | |
55 | + margin-left: 100px; | |
56 | +} | |
57 | +</style> | ... | ... |
src/views/login/index.vue
1 | 1 | <template> |
2 | 2 | <div class="login-container"> |
3 | - <el-form ref="loginForm" :model="loginForm" :rules="loginRules" class="login-form" autocomplete="on" label-position="left"> | |
4 | - | |
3 | + <el-form | |
4 | + ref="loginForm" | |
5 | + :model="loginForm" | |
6 | + :rules="loginRules" | |
7 | + class="login-form" | |
8 | + autocomplete="on" | |
9 | + label-position="left" | |
10 | + > | |
5 | 11 | <div class="title-container"> |
6 | - <h3 class="title"> | |
7 | - {{ $t('login.title') }} | |
8 | - </h3> | |
12 | + <h3 class="title">{{ $t('login.title') }}</h3> | |
9 | 13 | <lang-select class="set-language" /> |
10 | 14 | </div> |
11 | 15 | |
... | ... | @@ -47,26 +51,47 @@ |
47 | 51 | </span> |
48 | 52 | </el-form-item> |
49 | 53 | </el-tooltip> |
50 | - | |
51 | - <el-button :loading="loading" type="primary" style="width:100%;margin-bottom:30px;" @click.native.prevent="handleLogin"> | |
52 | - {{ $t('login.logIn') }} | |
53 | - </el-button> | |
54 | - | |
54 | + <div | |
55 | + style="position:relative;text-align:right;height:30px;line-height:30px;border:0px #000 solid;margin-top:-20px;" | |
56 | + > | |
57 | + <!-- <el-checkbox v-model="checked">{{$t('rememberpassword')}}</el-checkbox> --> | |
58 | + <el-link type="primary">{{ $t('login.forgetpassword') }}</el-link> | |
59 | + </div> | |
60 | + <el-button | |
61 | + :loading="loading" | |
62 | + type="primary" | |
63 | + style="width:100%;" | |
64 | + @click.native.prevent="handleLogin" | |
65 | + >{{ $t('login.logIn') }}</el-button> | |
66 | + <div | |
67 | + style="position:relative;text-align:right;height:30px;line-height:30px;border:0px #000 solid;margin-bottom:30px;" | |
68 | + > | |
69 | + <div class="tips"> | |
70 | + <el-link type="primary">{{ $t('login.signup') }}</el-link> | |
71 | + </div> | |
72 | + </div> | |
55 | 73 | <div style="position:relative"> |
56 | 74 | <div class="tips"> |
57 | 75 | <span>{{ $t('login.username') }} : admin</span> |
58 | 76 | <span>{{ $t('login.password') }} : {{ $t('login.any') }}</span> |
59 | 77 | </div> |
60 | 78 | <div class="tips"> |
61 | - <span style="margin-right:18px;"> | |
62 | - {{ $t('login.username') }} : editor | |
63 | - </span> | |
79 | + <span style="margin-right:18px;">{{ $t('login.username') }} : assistant</span> | |
64 | 80 | <span>{{ $t('login.password') }} : {{ $t('login.any') }}</span> |
65 | 81 | </div> |
66 | - | |
67 | - <el-button class="thirdparty-button" type="primary" @click="showDialog=true"> | |
68 | - {{ $t('login.thirdparty') }} | |
69 | - </el-button> | |
82 | + <div class="tips"> | |
83 | + <span style="margin-right:18px;">{{ $t('login.username') }} : runner</span> | |
84 | + <span>{{ $t('login.password') }} : {{ $t('login.any') }}</span> | |
85 | + </div> | |
86 | + <div class="tips"> | |
87 | + <span style="margin-right:18px;">{{ $t('login.username') }} : shoper</span> | |
88 | + <span>{{ $t('login.password') }} : {{ $t('login.any') }}</span> | |
89 | + </div> | |
90 | + <el-button | |
91 | + class="thirdparty-button" | |
92 | + type="primary" | |
93 | + @click="showDialog=true" | |
94 | + >{{ $t('login.thirdparty') }}</el-button> | |
70 | 95 | </div> |
71 | 96 | </el-form> |
72 | 97 | |
... | ... | @@ -77,6 +102,7 @@ |
77 | 102 | <br> |
78 | 103 | <social-sign /> |
79 | 104 | </el-dialog> |
105 | + <!-- <vfd></vfd> --> | |
80 | 106 | </div> |
81 | 107 | </template> |
82 | 108 | |
... | ... | @@ -84,9 +110,10 @@ |
84 | 110 | import { validUsername } from '@/utils/validate' |
85 | 111 | import LangSelect from '@/components/LangSelect' |
86 | 112 | import SocialSign from './components/SocialSignin' |
87 | - | |
113 | +// import vfd from "vfd"; | |
88 | 114 | export default { |
89 | 115 | name: 'Login', |
116 | + // components: { LangSelect, SocialSign, vfd }, | |
90 | 117 | components: { LangSelect, SocialSign }, |
91 | 118 | data() { |
92 | 119 | const validateUsername = (rule, value, callback) => { |
... | ... | @@ -109,8 +136,12 @@ export default { |
109 | 136 | password: '111111' |
110 | 137 | }, |
111 | 138 | loginRules: { |
112 | - username: [{ required: true, trigger: 'blur', validator: validateUsername }], | |
113 | - password: [{ required: true, trigger: 'blur', validator: validatePassword }] | |
139 | + username: [ | |
140 | + { required: true, trigger: 'blur', validator: validateUsername } | |
141 | + ], | |
142 | + password: [ | |
143 | + { required: true, trigger: 'blur', validator: validatePassword } | |
144 | + ] | |
114 | 145 | }, |
115 | 146 | passwordType: 'password', |
116 | 147 | capsTooltip: false, |
... | ... | @@ -148,7 +179,7 @@ export default { |
148 | 179 | methods: { |
149 | 180 | checkCapslock(e) { |
150 | 181 | const { key } = e |
151 | - this.capsTooltip = key && key.length === 1 && (key >= 'A' && key <= 'Z') | |
182 | + this.capsTooltip = key && key.length === 1 && key >= 'A' && key <= 'Z' | |
152 | 183 | }, |
153 | 184 | showPwd() { |
154 | 185 | if (this.passwordType === 'password') { |
... | ... | @@ -164,9 +195,13 @@ export default { |
164 | 195 | this.$refs.loginForm.validate(valid => { |
165 | 196 | if (valid) { |
166 | 197 | this.loading = true |
167 | - this.$store.dispatch('user/login', this.loginForm) | |
198 | + this.$store | |
199 | + .dispatch('user/login', this.loginForm) | |
168 | 200 | .then(() => { |
169 | - this.$router.push({ path: this.redirect || '/', query: this.otherQuery }) | |
201 | + this.$router.push({ | |
202 | + path: this.redirect || '/', | |
203 | + query: this.otherQuery | |
204 | + }) | |
170 | 205 | this.loading = false |
171 | 206 | }) |
172 | 207 | .catch(() => { |
... | ... | @@ -212,8 +247,8 @@ export default { |
212 | 247 | /* 修复input 背景不协调 和光标变色 */ |
213 | 248 | /* Detail see https://github.com/PanJiaChen/vue-element-admin/pull/927 */ |
214 | 249 | |
215 | -$bg:#283443; | |
216 | -$light_gray:#fff; | |
250 | +$bg: #283443; | |
251 | +$light_gray: #fff; | |
217 | 252 | $cursor: #fff; |
218 | 253 | |
219 | 254 | @supports (-webkit-mask: none) and (not (cater-color: $cursor)) { |
... | ... | @@ -256,9 +291,9 @@ $cursor: #fff; |
256 | 291 | </style> |
257 | 292 | |
258 | 293 | <style lang="scss" scoped> |
259 | -$bg:#2d3a4b; | |
260 | -$dark_gray:#889aa4; | |
261 | -$light_gray:#eee; | |
294 | +$bg: #2d3a4b; | |
295 | +$dark_gray: #889aa4; | |
296 | +$light_gray: #eee; | |
262 | 297 | |
263 | 298 | .login-container { |
264 | 299 | min-height: 100%; | ... | ... |
src/views/meta/complex-table.vue
... | ... | @@ -0,0 +1,379 @@ |
1 | +<template> | |
2 | + <div class="app-container"> | |
3 | + <div class="filter-container"> | |
4 | + <el-input v-model="listQuery.title" :placeholder="$t('table.title')" style="width: 200px;" class="filter-item" @keyup.enter.native="handleFilter" /> | |
5 | + <el-select v-model="listQuery.importance" :placeholder="$t('table.importance')" clearable style="width: 90px" class="filter-item"> | |
6 | + <el-option v-for="item in importanceOptions" :key="item" :label="item" :value="item" /> | |
7 | + </el-select> | |
8 | + <el-select v-model="listQuery.type" :placeholder="$t('table.type')" clearable class="filter-item" style="width: 130px"> | |
9 | + <el-option v-for="item in calendarTypeOptions" :key="item.key" :label="item.display_name+'('+item.key+')'" :value="item.key" /> | |
10 | + </el-select> | |
11 | + <el-select v-model="listQuery.sort" style="width: 140px" class="filter-item" @change="handleFilter"> | |
12 | + <el-option v-for="item in sortOptions" :key="item.key" :label="item.label" :value="item.key" /> | |
13 | + </el-select> | |
14 | + <el-button v-waves class="filter-item" type="primary" icon="el-icon-search" @click="handleFilter"> | |
15 | + {{ $t('table.search') }} | |
16 | + </el-button> | |
17 | + <el-button class="filter-item" style="margin-left: 10px;" type="primary" icon="el-icon-edit" @click="handleCreate"> | |
18 | + {{ $t('table.add') }} | |
19 | + </el-button> | |
20 | + <el-button v-waves :loading="downloadLoading" class="filter-item" type="primary" icon="el-icon-download" @click="handleDownload"> | |
21 | + {{ $t('table.export') }} | |
22 | + </el-button> | |
23 | + <el-checkbox v-model="showReviewer" class="filter-item" style="margin-left:15px;" @change="tableKey=tableKey+1"> | |
24 | + {{ $t('table.reviewer') }} | |
25 | + </el-checkbox> | |
26 | + </div> | |
27 | + | |
28 | + <el-table | |
29 | + :key="tableKey" | |
30 | + v-loading="listLoading" | |
31 | + :data="list" | |
32 | + border | |
33 | + fit | |
34 | + highlight-current-row | |
35 | + style="width: 100%;" | |
36 | + @sort-change="sortChange" | |
37 | + > | |
38 | + <el-table-column :label="$t('table.id')" prop="id" sortable="custom" align="center" width="80" :class-name="getSortClass('id')"> | |
39 | + <template slot-scope="{row}"> | |
40 | + <span>{{ row.id }}</span> | |
41 | + </template> | |
42 | + </el-table-column> | |
43 | + <el-table-column :label="$t('table.date')" width="150px" align="center"> | |
44 | + <template slot-scope="{row}"> | |
45 | + <span>{{ row.timestamp | parseTime('{y}-{m}-{d} {h}:{i}') }}</span> | |
46 | + </template> | |
47 | + </el-table-column> | |
48 | + <el-table-column :label="$t('table.title')" min-width="150px"> | |
49 | + <template slot-scope="{row}"> | |
50 | + <span class="link-type" @click="handleUpdate(row)">{{ row.title }}</span> | |
51 | + <el-tag>{{ row.type | typeFilter }}</el-tag> | |
52 | + </template> | |
53 | + </el-table-column> | |
54 | + <el-table-column :label="$t('table.author')" width="110px" align="center"> | |
55 | + <template slot-scope="{row}"> | |
56 | + <span>{{ row.author }}</span> | |
57 | + </template> | |
58 | + </el-table-column> | |
59 | + <el-table-column v-if="showReviewer" :label="$t('table.reviewer')" width="110px" align="center"> | |
60 | + <template slot-scope="{row}"> | |
61 | + <span style="color:red;">{{ row.reviewer }}</span> | |
62 | + </template> | |
63 | + </el-table-column> | |
64 | + <el-table-column :label="$t('table.importance')" width="80px"> | |
65 | + <template slot-scope="{row}"> | |
66 | + <svg-icon v-for="n in +row.importance" :key="n" icon-class="star" class="meta-item__icon" /> | |
67 | + </template> | |
68 | + </el-table-column> | |
69 | + <el-table-column :label="$t('table.readings')" align="center" width="95"> | |
70 | + <template slot-scope="{row}"> | |
71 | + <span v-if="row.pageviews" class="link-type" @click="handleFetchPv(row.pageviews)">{{ row.pageviews }}</span> | |
72 | + <span v-else>0</span> | |
73 | + </template> | |
74 | + </el-table-column> | |
75 | + <el-table-column :label="$t('table.status')" class-name="status-col" width="100"> | |
76 | + <template slot-scope="{row}"> | |
77 | + <el-tag :type="row.status | statusFilter"> | |
78 | + {{ row.status }} | |
79 | + </el-tag> | |
80 | + </template> | |
81 | + </el-table-column> | |
82 | + <el-table-column :label="$t('table.actions')" align="center" width="230" class-name="small-padding fixed-width"> | |
83 | + <template slot-scope="{row,$index}"> | |
84 | + <el-button type="primary" size="mini" @click="handleUpdate(row)"> | |
85 | + {{ $t('table.edit') }} | |
86 | + </el-button> | |
87 | + <el-button v-if="row.status!='published'" size="mini" type="success" @click="handleModifyStatus(row,'published')"> | |
88 | + {{ $t('table.publish') }} | |
89 | + </el-button> | |
90 | + <el-button v-if="row.status!='draft'" size="mini" @click="handleModifyStatus(row,'draft')"> | |
91 | + {{ $t('table.draft') }} | |
92 | + </el-button> | |
93 | + <el-button v-if="row.status!='deleted'" size="mini" type="danger" @click="handleDelete(row,$index)"> | |
94 | + {{ $t('table.delete') }} | |
95 | + </el-button> | |
96 | + </template> | |
97 | + </el-table-column> | |
98 | + </el-table> | |
99 | + | |
100 | + <pagination v-show="total>0" :total="total" :page.sync="listQuery.page" :limit.sync="listQuery.limit" @pagination="getList" /> | |
101 | + | |
102 | + <el-dialog :title="textMap[dialogStatus]" :visible.sync="dialogFormVisible"> | |
103 | + <el-form ref="dataForm" :rules="rules" :model="temp" label-position="left" label-width="70px" style="width: 400px; margin-left:50px;"> | |
104 | + <el-form-item :label="$t('table.type')" prop="type"> | |
105 | + <el-select v-model="temp.type" class="filter-item" placeholder="Please select"> | |
106 | + <el-option v-for="item in calendarTypeOptions" :key="item.key" :label="item.display_name" :value="item.key" /> | |
107 | + </el-select> | |
108 | + </el-form-item> | |
109 | + <el-form-item :label="$t('table.date')" prop="timestamp"> | |
110 | + <el-date-picker v-model="temp.timestamp" type="datetime" placeholder="Please pick a date" /> | |
111 | + </el-form-item> | |
112 | + <el-form-item :label="$t('table.title')" prop="title"> | |
113 | + <el-input v-model="temp.title" /> | |
114 | + </el-form-item> | |
115 | + <el-form-item :label="$t('table.status')"> | |
116 | + <el-select v-model="temp.status" class="filter-item" placeholder="Please select"> | |
117 | + <el-option v-for="item in statusOptions" :key="item" :label="item" :value="item" /> | |
118 | + </el-select> | |
119 | + </el-form-item> | |
120 | + <el-form-item :label="$t('table.importance')"> | |
121 | + <el-rate v-model="temp.importance" :colors="['#99A9BF', '#F7BA2A', '#FF9900']" :max="3" style="margin-top:8px;" /> | |
122 | + </el-form-item> | |
123 | + <el-form-item :label="$t('table.remark')"> | |
124 | + <el-input v-model="temp.remark" :autosize="{ minRows: 2, maxRows: 4}" type="textarea" placeholder="Please input" /> | |
125 | + </el-form-item> | |
126 | + </el-form> | |
127 | + <div slot="footer" class="dialog-footer"> | |
128 | + <el-button @click="dialogFormVisible = false"> | |
129 | + {{ $t('table.cancel') }} | |
130 | + </el-button> | |
131 | + <el-button type="primary" @click="dialogStatus==='create'?createData():updateData()"> | |
132 | + {{ $t('table.confirm') }} | |
133 | + </el-button> | |
134 | + </div> | |
135 | + </el-dialog> | |
136 | + | |
137 | + <el-dialog :visible.sync="dialogPvVisible" title="Reading statistics"> | |
138 | + <el-table :data="pvData" border fit highlight-current-row style="width: 100%"> | |
139 | + <el-table-column prop="key" label="Channel" /> | |
140 | + <el-table-column prop="pv" label="Pv" /> | |
141 | + </el-table> | |
142 | + <span slot="footer" class="dialog-footer"> | |
143 | + <el-button type="primary" @click="dialogPvVisible = false">{{ $t('table.confirm') }}</el-button> | |
144 | + </span> | |
145 | + </el-dialog> | |
146 | + </div> | |
147 | +</template> | |
148 | + | |
149 | +<script> | |
150 | +import { fetchList, fetchPv, createArticle, updateArticle } from '@/api/article' | |
151 | +import waves from '@/directive/waves' // waves directive | |
152 | +import { parseTime } from '@/utils' | |
153 | +import Pagination from '@/components/Pagination' // secondary package based on el-pagination | |
154 | + | |
155 | +const calendarTypeOptions = [ | |
156 | + { key: 'CN', display_name: 'China' }, | |
157 | + { key: 'US', display_name: 'USA' }, | |
158 | + { key: 'JP', display_name: 'Japan' }, | |
159 | + { key: 'EU', display_name: 'Eurozone' } | |
160 | +] | |
161 | + | |
162 | +// arr to obj, such as { CN : "China", US : "USA" } | |
163 | +const calendarTypeKeyValue = calendarTypeOptions.reduce((acc, cur) => { | |
164 | + acc[cur.key] = cur.display_name | |
165 | + return acc | |
166 | +}, {}) | |
167 | + | |
168 | +export default { | |
169 | + name: 'ComplexTable', | |
170 | + components: { Pagination }, | |
171 | + directives: { waves }, | |
172 | + filters: { | |
173 | + statusFilter(status) { | |
174 | + const statusMap = { | |
175 | + published: 'success', | |
176 | + draft: 'info', | |
177 | + deleted: 'danger' | |
178 | + } | |
179 | + return statusMap[status] | |
180 | + }, | |
181 | + typeFilter(type) { | |
182 | + return calendarTypeKeyValue[type] | |
183 | + } | |
184 | + }, | |
185 | + data() { | |
186 | + return { | |
187 | + tableKey: 0, | |
188 | + list: null, | |
189 | + total: 0, | |
190 | + listLoading: true, | |
191 | + listQuery: { | |
192 | + page: 1, | |
193 | + limit: 20, | |
194 | + importance: undefined, | |
195 | + title: undefined, | |
196 | + type: undefined, | |
197 | + sort: '+id' | |
198 | + }, | |
199 | + importanceOptions: [1, 2, 3], | |
200 | + calendarTypeOptions, | |
201 | + sortOptions: [{ label: 'ID Ascending', key: '+id' }, { label: 'ID Descending', key: '-id' }], | |
202 | + statusOptions: ['published', 'draft', 'deleted'], | |
203 | + showReviewer: false, | |
204 | + temp: { | |
205 | + id: undefined, | |
206 | + importance: 1, | |
207 | + remark: '', | |
208 | + timestamp: new Date(), | |
209 | + title: '', | |
210 | + type: '', | |
211 | + status: 'published' | |
212 | + }, | |
213 | + dialogFormVisible: false, | |
214 | + dialogStatus: '', | |
215 | + textMap: { | |
216 | + update: 'Edit', | |
217 | + create: 'Create' | |
218 | + }, | |
219 | + dialogPvVisible: false, | |
220 | + pvData: [], | |
221 | + rules: { | |
222 | + type: [{ required: true, message: 'type is required', trigger: 'change' }], | |
223 | + timestamp: [{ type: 'date', required: true, message: 'timestamp is required', trigger: 'change' }], | |
224 | + title: [{ required: true, message: 'title is required', trigger: 'blur' }] | |
225 | + }, | |
226 | + downloadLoading: false | |
227 | + } | |
228 | + }, | |
229 | + created() { | |
230 | + this.getList() | |
231 | + }, | |
232 | + methods: { | |
233 | + getList() { | |
234 | + this.listLoading = true | |
235 | + fetchList(this.listQuery).then(response => { | |
236 | + this.list = response.data.items | |
237 | + this.total = response.data.total | |
238 | + | |
239 | + // Just to simulate the time of the request | |
240 | + setTimeout(() => { | |
241 | + this.listLoading = false | |
242 | + }, 1.5 * 1000) | |
243 | + }) | |
244 | + }, | |
245 | + handleFilter() { | |
246 | + this.listQuery.page = 1 | |
247 | + this.getList() | |
248 | + }, | |
249 | + handleModifyStatus(row, status) { | |
250 | + this.$message({ | |
251 | + message: '操作成功', | |
252 | + type: 'success' | |
253 | + }) | |
254 | + row.status = status | |
255 | + }, | |
256 | + sortChange(data) { | |
257 | + const { prop, order } = data | |
258 | + if (prop === 'id') { | |
259 | + this.sortByID(order) | |
260 | + } | |
261 | + }, | |
262 | + sortByID(order) { | |
263 | + if (order === 'ascending') { | |
264 | + this.listQuery.sort = '+id' | |
265 | + } else { | |
266 | + this.listQuery.sort = '-id' | |
267 | + } | |
268 | + this.handleFilter() | |
269 | + }, | |
270 | + resetTemp() { | |
271 | + this.temp = { | |
272 | + id: undefined, | |
273 | + importance: 1, | |
274 | + remark: '', | |
275 | + timestamp: new Date(), | |
276 | + title: '', | |
277 | + status: 'published', | |
278 | + type: '' | |
279 | + } | |
280 | + }, | |
281 | + handleCreate() { | |
282 | + this.resetTemp() | |
283 | + this.dialogStatus = 'create' | |
284 | + this.dialogFormVisible = true | |
285 | + this.$nextTick(() => { | |
286 | + this.$refs['dataForm'].clearValidate() | |
287 | + }) | |
288 | + }, | |
289 | + createData() { | |
290 | + this.$refs['dataForm'].validate((valid) => { | |
291 | + if (valid) { | |
292 | + this.temp.id = parseInt(Math.random() * 100) + 1024 // mock a id | |
293 | + this.temp.author = '秀野堂主' | |
294 | + createArticle(this.temp).then(() => { | |
295 | + this.list.unshift(this.temp) | |
296 | + this.dialogFormVisible = false | |
297 | + this.$notify({ | |
298 | + title: '成功', | |
299 | + message: '创建成功', | |
300 | + type: 'success', | |
301 | + duration: 2000 | |
302 | + }) | |
303 | + }) | |
304 | + } | |
305 | + }) | |
306 | + }, | |
307 | + handleUpdate(row) { | |
308 | + this.temp = Object.assign({}, row) // copy obj | |
309 | + this.temp.timestamp = new Date(this.temp.timestamp) | |
310 | + this.dialogStatus = 'update' | |
311 | + this.dialogFormVisible = true | |
312 | + this.$nextTick(() => { | |
313 | + this.$refs['dataForm'].clearValidate() | |
314 | + }) | |
315 | + }, | |
316 | + updateData() { | |
317 | + this.$refs['dataForm'].validate((valid) => { | |
318 | + if (valid) { | |
319 | + const tempData = Object.assign({}, this.temp) | |
320 | + tempData.timestamp = +new Date(tempData.timestamp) // change Thu Nov 30 2017 16:41:05 GMT+0800 (CST) to 1512031311464 | |
321 | + updateArticle(tempData).then(() => { | |
322 | + const index = this.list.findIndex(v => v.id === this.temp.id) | |
323 | + this.list.splice(index, 1, this.temp) | |
324 | + this.dialogFormVisible = false | |
325 | + this.$notify({ | |
326 | + title: '成功', | |
327 | + message: '更新成功', | |
328 | + type: 'success', | |
329 | + duration: 2000 | |
330 | + }) | |
331 | + }) | |
332 | + } | |
333 | + }) | |
334 | + }, | |
335 | + handleDelete(row, index) { | |
336 | + this.$notify({ | |
337 | + title: '成功', | |
338 | + message: '删除成功', | |
339 | + type: 'success', | |
340 | + duration: 2000 | |
341 | + }) | |
342 | + this.list.splice(index, 1) | |
343 | + }, | |
344 | + handleFetchPv(pv) { | |
345 | + fetchPv(pv).then(response => { | |
346 | + this.pvData = response.data.pvData | |
347 | + this.dialogPvVisible = true | |
348 | + }) | |
349 | + }, | |
350 | + handleDownload() { | |
351 | + this.downloadLoading = true | |
352 | + import('@/vendor/Export2Excel').then(excel => { | |
353 | + const tHeader = ['timestamp', 'title', 'type', 'importance', 'status'] | |
354 | + const filterVal = ['timestamp', 'title', 'type', 'importance', 'status'] | |
355 | + const data = this.formatJson(filterVal) | |
356 | + excel.export_json_to_excel({ | |
357 | + header: tHeader, | |
358 | + data, | |
359 | + filename: 'table-list' | |
360 | + }) | |
361 | + this.downloadLoading = false | |
362 | + }) | |
363 | + }, | |
364 | + formatJson(filterVal) { | |
365 | + return this.list.map(v => filterVal.map(j => { | |
366 | + if (j === 'timestamp') { | |
367 | + return parseTime(v[j]) | |
368 | + } else { | |
369 | + return v[j] | |
370 | + } | |
371 | + })) | |
372 | + }, | |
373 | + getSortClass: function(key) { | |
374 | + const sort = this.listQuery.sort | |
375 | + return sort === `+${key}` ? 'ascending' : 'descending' | |
376 | + } | |
377 | + } | |
378 | +} | |
379 | +</script> | ... | ... |
src/views/order/complex-table.vue
... | ... | @@ -0,0 +1,379 @@ |
1 | +<template> | |
2 | + <div class="app-container"> | |
3 | + <div class="filter-container"> | |
4 | + <el-input v-model="listQuery.title" :placeholder="$t('table.title')" style="width: 200px;" class="filter-item" @keyup.enter.native="handleFilter" /> | |
5 | + <el-select v-model="listQuery.importance" :placeholder="$t('table.importance')" clearable style="width: 90px" class="filter-item"> | |
6 | + <el-option v-for="item in importanceOptions" :key="item" :label="item" :value="item" /> | |
7 | + </el-select> | |
8 | + <el-select v-model="listQuery.type" :placeholder="$t('table.type')" clearable class="filter-item" style="width: 130px"> | |
9 | + <el-option v-for="item in calendarTypeOptions" :key="item.key" :label="item.display_name+'('+item.key+')'" :value="item.key" /> | |
10 | + </el-select> | |
11 | + <el-select v-model="listQuery.sort" style="width: 140px" class="filter-item" @change="handleFilter"> | |
12 | + <el-option v-for="item in sortOptions" :key="item.key" :label="item.label" :value="item.key" /> | |
13 | + </el-select> | |
14 | + <el-button v-waves class="filter-item" type="primary" icon="el-icon-search" @click="handleFilter"> | |
15 | + {{ $t('table.search') }} | |
16 | + </el-button> | |
17 | + <el-button class="filter-item" style="margin-left: 10px;" type="primary" icon="el-icon-edit" @click="handleCreate"> | |
18 | + {{ $t('table.add') }} | |
19 | + </el-button> | |
20 | + <el-button v-waves :loading="downloadLoading" class="filter-item" type="primary" icon="el-icon-download" @click="handleDownload"> | |
21 | + {{ $t('table.export') }} | |
22 | + </el-button> | |
23 | + <el-checkbox v-model="showReviewer" class="filter-item" style="margin-left:15px;" @change="tableKey=tableKey+1"> | |
24 | + {{ $t('table.reviewer') }} | |
25 | + </el-checkbox> | |
26 | + </div> | |
27 | + | |
28 | + <el-table | |
29 | + :key="tableKey" | |
30 | + v-loading="listLoading" | |
31 | + :data="list" | |
32 | + border | |
33 | + fit | |
34 | + highlight-current-row | |
35 | + style="width: 100%;" | |
36 | + @sort-change="sortChange" | |
37 | + > | |
38 | + <el-table-column :label="$t('table.id')" prop="id" sortable="custom" align="center" width="80" :class-name="getSortClass('id')"> | |
39 | + <template slot-scope="{row}"> | |
40 | + <span>{{ row.id }}</span> | |
41 | + </template> | |
42 | + </el-table-column> | |
43 | + <el-table-column :label="$t('table.date')" width="150px" align="center"> | |
44 | + <template slot-scope="{row}"> | |
45 | + <span>{{ row.timestamp | parseTime('{y}-{m}-{d} {h}:{i}') }}</span> | |
46 | + </template> | |
47 | + </el-table-column> | |
48 | + <el-table-column :label="$t('table.title')" min-width="150px"> | |
49 | + <template slot-scope="{row}"> | |
50 | + <span class="link-type" @click="handleUpdate(row)">{{ row.title }}</span> | |
51 | + <el-tag>{{ row.type | typeFilter }}</el-tag> | |
52 | + </template> | |
53 | + </el-table-column> | |
54 | + <el-table-column :label="$t('table.author')" width="110px" align="center"> | |
55 | + <template slot-scope="{row}"> | |
56 | + <span>{{ row.author }}</span> | |
57 | + </template> | |
58 | + </el-table-column> | |
59 | + <el-table-column v-if="showReviewer" :label="$t('table.reviewer')" width="110px" align="center"> | |
60 | + <template slot-scope="{row}"> | |
61 | + <span style="color:red;">{{ row.reviewer }}</span> | |
62 | + </template> | |
63 | + </el-table-column> | |
64 | + <el-table-column :label="$t('table.importance')" width="80px"> | |
65 | + <template slot-scope="{row}"> | |
66 | + <svg-icon v-for="n in +row.importance" :key="n" icon-class="star" class="meta-item__icon" /> | |
67 | + </template> | |
68 | + </el-table-column> | |
69 | + <el-table-column :label="$t('table.readings')" align="center" width="95"> | |
70 | + <template slot-scope="{row}"> | |
71 | + <span v-if="row.pageviews" class="link-type" @click="handleFetchPv(row.pageviews)">{{ row.pageviews }}</span> | |
72 | + <span v-else>0</span> | |
73 | + </template> | |
74 | + </el-table-column> | |
75 | + <el-table-column :label="$t('table.status')" class-name="status-col" width="100"> | |
76 | + <template slot-scope="{row}"> | |
77 | + <el-tag :type="row.status | statusFilter"> | |
78 | + {{ row.status }} | |
79 | + </el-tag> | |
80 | + </template> | |
81 | + </el-table-column> | |
82 | + <el-table-column :label="$t('table.actions')" align="center" width="230" class-name="small-padding fixed-width"> | |
83 | + <template slot-scope="{row,$index}"> | |
84 | + <el-button type="primary" size="mini" @click="handleUpdate(row)"> | |
85 | + {{ $t('table.edit') }} | |
86 | + </el-button> | |
87 | + <el-button v-if="row.status!='published'" size="mini" type="success" @click="handleModifyStatus(row,'published')"> | |
88 | + {{ $t('table.publish') }} | |
89 | + </el-button> | |
90 | + <el-button v-if="row.status!='draft'" size="mini" @click="handleModifyStatus(row,'draft')"> | |
91 | + {{ $t('table.draft') }} | |
92 | + </el-button> | |
93 | + <el-button v-if="row.status!='deleted'" size="mini" type="danger" @click="handleDelete(row,$index)"> | |
94 | + {{ $t('table.delete') }} | |
95 | + </el-button> | |
96 | + </template> | |
97 | + </el-table-column> | |
98 | + </el-table> | |
99 | + | |
100 | + <pagination v-show="total>0" :total="total" :page.sync="listQuery.page" :limit.sync="listQuery.limit" @pagination="getList" /> | |
101 | + | |
102 | + <el-dialog :title="textMap[dialogStatus]" :visible.sync="dialogFormVisible"> | |
103 | + <el-form ref="dataForm" :rules="rules" :model="temp" label-position="left" label-width="70px" style="width: 400px; margin-left:50px;"> | |
104 | + <el-form-item :label="$t('table.type')" prop="type"> | |
105 | + <el-select v-model="temp.type" class="filter-item" placeholder="Please select"> | |
106 | + <el-option v-for="item in calendarTypeOptions" :key="item.key" :label="item.display_name" :value="item.key" /> | |
107 | + </el-select> | |
108 | + </el-form-item> | |
109 | + <el-form-item :label="$t('table.date')" prop="timestamp"> | |
110 | + <el-date-picker v-model="temp.timestamp" type="datetime" placeholder="Please pick a date" /> | |
111 | + </el-form-item> | |
112 | + <el-form-item :label="$t('table.title')" prop="title"> | |
113 | + <el-input v-model="temp.title" /> | |
114 | + </el-form-item> | |
115 | + <el-form-item :label="$t('table.status')"> | |
116 | + <el-select v-model="temp.status" class="filter-item" placeholder="Please select"> | |
117 | + <el-option v-for="item in statusOptions" :key="item" :label="item" :value="item" /> | |
118 | + </el-select> | |
119 | + </el-form-item> | |
120 | + <el-form-item :label="$t('table.importance')"> | |
121 | + <el-rate v-model="temp.importance" :colors="['#99A9BF', '#F7BA2A', '#FF9900']" :max="3" style="margin-top:8px;" /> | |
122 | + </el-form-item> | |
123 | + <el-form-item :label="$t('table.remark')"> | |
124 | + <el-input v-model="temp.remark" :autosize="{ minRows: 2, maxRows: 4}" type="textarea" placeholder="Please input" /> | |
125 | + </el-form-item> | |
126 | + </el-form> | |
127 | + <div slot="footer" class="dialog-footer"> | |
128 | + <el-button @click="dialogFormVisible = false"> | |
129 | + {{ $t('table.cancel') }} | |
130 | + </el-button> | |
131 | + <el-button type="primary" @click="dialogStatus==='create'?createData():updateData()"> | |
132 | + {{ $t('table.confirm') }} | |
133 | + </el-button> | |
134 | + </div> | |
135 | + </el-dialog> | |
136 | + | |
137 | + <el-dialog :visible.sync="dialogPvVisible" title="Reading statistics"> | |
138 | + <el-table :data="pvData" border fit highlight-current-row style="width: 100%"> | |
139 | + <el-table-column prop="key" label="Channel" /> | |
140 | + <el-table-column prop="pv" label="Pv" /> | |
141 | + </el-table> | |
142 | + <span slot="footer" class="dialog-footer"> | |
143 | + <el-button type="primary" @click="dialogPvVisible = false">{{ $t('table.confirm') }}</el-button> | |
144 | + </span> | |
145 | + </el-dialog> | |
146 | + </div> | |
147 | +</template> | |
148 | + | |
149 | +<script> | |
150 | +import { fetchList, fetchPv, createArticle, updateArticle } from '@/api/article' | |
151 | +import waves from '@/directive/waves' // waves directive | |
152 | +import { parseTime } from '@/utils' | |
153 | +import Pagination from '@/components/Pagination' // secondary package based on el-pagination | |
154 | + | |
155 | +const calendarTypeOptions = [ | |
156 | + { key: 'CN', display_name: 'China' }, | |
157 | + { key: 'US', display_name: 'USA' }, | |
158 | + { key: 'JP', display_name: 'Japan' }, | |
159 | + { key: 'EU', display_name: 'Eurozone' } | |
160 | +] | |
161 | + | |
162 | +// arr to obj, such as { CN : "China", US : "USA" } | |
163 | +const calendarTypeKeyValue = calendarTypeOptions.reduce((acc, cur) => { | |
164 | + acc[cur.key] = cur.display_name | |
165 | + return acc | |
166 | +}, {}) | |
167 | + | |
168 | +export default { | |
169 | + name: 'ComplexTable', | |
170 | + components: { Pagination }, | |
171 | + directives: { waves }, | |
172 | + filters: { | |
173 | + statusFilter(status) { | |
174 | + const statusMap = { | |
175 | + published: 'success', | |
176 | + draft: 'info', | |
177 | + deleted: 'danger' | |
178 | + } | |
179 | + return statusMap[status] | |
180 | + }, | |
181 | + typeFilter(type) { | |
182 | + return calendarTypeKeyValue[type] | |
183 | + } | |
184 | + }, | |
185 | + data() { | |
186 | + return { | |
187 | + tableKey: 0, | |
188 | + list: null, | |
189 | + total: 0, | |
190 | + listLoading: true, | |
191 | + listQuery: { | |
192 | + page: 1, | |
193 | + limit: 20, | |
194 | + importance: undefined, | |
195 | + title: undefined, | |
196 | + type: undefined, | |
197 | + sort: '+id' | |
198 | + }, | |
199 | + importanceOptions: [1, 2, 3], | |
200 | + calendarTypeOptions, | |
201 | + sortOptions: [{ label: 'ID Ascending', key: '+id' }, { label: 'ID Descending', key: '-id' }], | |
202 | + statusOptions: ['published', 'draft', 'deleted'], | |
203 | + showReviewer: false, | |
204 | + temp: { | |
205 | + id: undefined, | |
206 | + importance: 1, | |
207 | + remark: '', | |
208 | + timestamp: new Date(), | |
209 | + title: '', | |
210 | + type: '', | |
211 | + status: 'published' | |
212 | + }, | |
213 | + dialogFormVisible: false, | |
214 | + dialogStatus: '', | |
215 | + textMap: { | |
216 | + update: 'Edit', | |
217 | + create: 'Create' | |
218 | + }, | |
219 | + dialogPvVisible: false, | |
220 | + pvData: [], | |
221 | + rules: { | |
222 | + type: [{ required: true, message: 'type is required', trigger: 'change' }], | |
223 | + timestamp: [{ type: 'date', required: true, message: 'timestamp is required', trigger: 'change' }], | |
224 | + title: [{ required: true, message: 'title is required', trigger: 'blur' }] | |
225 | + }, | |
226 | + downloadLoading: false | |
227 | + } | |
228 | + }, | |
229 | + created() { | |
230 | + this.getList() | |
231 | + }, | |
232 | + methods: { | |
233 | + getList() { | |
234 | + this.listLoading = true | |
235 | + fetchList(this.listQuery).then(response => { | |
236 | + this.list = response.data.items | |
237 | + this.total = response.data.total | |
238 | + | |
239 | + // Just to simulate the time of the request | |
240 | + setTimeout(() => { | |
241 | + this.listLoading = false | |
242 | + }, 1.5 * 1000) | |
243 | + }) | |
244 | + }, | |
245 | + handleFilter() { | |
246 | + this.listQuery.page = 1 | |
247 | + this.getList() | |
248 | + }, | |
249 | + handleModifyStatus(row, status) { | |
250 | + this.$message({ | |
251 | + message: '操作成功', | |
252 | + type: 'success' | |
253 | + }) | |
254 | + row.status = status | |
255 | + }, | |
256 | + sortChange(data) { | |
257 | + const { prop, order } = data | |
258 | + if (prop === 'id') { | |
259 | + this.sortByID(order) | |
260 | + } | |
261 | + }, | |
262 | + sortByID(order) { | |
263 | + if (order === 'ascending') { | |
264 | + this.listQuery.sort = '+id' | |
265 | + } else { | |
266 | + this.listQuery.sort = '-id' | |
267 | + } | |
268 | + this.handleFilter() | |
269 | + }, | |
270 | + resetTemp() { | |
271 | + this.temp = { | |
272 | + id: undefined, | |
273 | + importance: 1, | |
274 | + remark: '', | |
275 | + timestamp: new Date(), | |
276 | + title: '', | |
277 | + status: 'published', | |
278 | + type: '' | |
279 | + } | |
280 | + }, | |
281 | + handleCreate() { | |
282 | + this.resetTemp() | |
283 | + this.dialogStatus = 'create' | |
284 | + this.dialogFormVisible = true | |
285 | + this.$nextTick(() => { | |
286 | + this.$refs['dataForm'].clearValidate() | |
287 | + }) | |
288 | + }, | |
289 | + createData() { | |
290 | + this.$refs['dataForm'].validate((valid) => { | |
291 | + if (valid) { | |
292 | + this.temp.id = parseInt(Math.random() * 100) + 1024 // mock a id | |
293 | + this.temp.author = '秀野堂主' | |
294 | + createArticle(this.temp).then(() => { | |
295 | + this.list.unshift(this.temp) | |
296 | + this.dialogFormVisible = false | |
297 | + this.$notify({ | |
298 | + title: '成功', | |
299 | + message: '创建成功', | |
300 | + type: 'success', | |
301 | + duration: 2000 | |
302 | + }) | |
303 | + }) | |
304 | + } | |
305 | + }) | |
306 | + }, | |
307 | + handleUpdate(row) { | |
308 | + this.temp = Object.assign({}, row) // copy obj | |
309 | + this.temp.timestamp = new Date(this.temp.timestamp) | |
310 | + this.dialogStatus = 'update' | |
311 | + this.dialogFormVisible = true | |
312 | + this.$nextTick(() => { | |
313 | + this.$refs['dataForm'].clearValidate() | |
314 | + }) | |
315 | + }, | |
316 | + updateData() { | |
317 | + this.$refs['dataForm'].validate((valid) => { | |
318 | + if (valid) { | |
319 | + const tempData = Object.assign({}, this.temp) | |
320 | + tempData.timestamp = +new Date(tempData.timestamp) // change Thu Nov 30 2017 16:41:05 GMT+0800 (CST) to 1512031311464 | |
321 | + updateArticle(tempData).then(() => { | |
322 | + const index = this.list.findIndex(v => v.id === this.temp.id) | |
323 | + this.list.splice(index, 1, this.temp) | |
324 | + this.dialogFormVisible = false | |
325 | + this.$notify({ | |
326 | + title: '成功', | |
327 | + message: '更新成功', | |
328 | + type: 'success', | |
329 | + duration: 2000 | |
330 | + }) | |
331 | + }) | |
332 | + } | |
333 | + }) | |
334 | + }, | |
335 | + handleDelete(row, index) { | |
336 | + this.$notify({ | |
337 | + title: '成功', | |
338 | + message: '删除成功', | |
339 | + type: 'success', | |
340 | + duration: 2000 | |
341 | + }) | |
342 | + this.list.splice(index, 1) | |
343 | + }, | |
344 | + handleFetchPv(pv) { | |
345 | + fetchPv(pv).then(response => { | |
346 | + this.pvData = response.data.pvData | |
347 | + this.dialogPvVisible = true | |
348 | + }) | |
349 | + }, | |
350 | + handleDownload() { | |
351 | + this.downloadLoading = true | |
352 | + import('@/vendor/Export2Excel').then(excel => { | |
353 | + const tHeader = ['timestamp', 'title', 'type', 'importance', 'status'] | |
354 | + const filterVal = ['timestamp', 'title', 'type', 'importance', 'status'] | |
355 | + const data = this.formatJson(filterVal) | |
356 | + excel.export_json_to_excel({ | |
357 | + header: tHeader, | |
358 | + data, | |
359 | + filename: 'table-list' | |
360 | + }) | |
361 | + this.downloadLoading = false | |
362 | + }) | |
363 | + }, | |
364 | + formatJson(filterVal) { | |
365 | + return this.list.map(v => filterVal.map(j => { | |
366 | + if (j === 'timestamp') { | |
367 | + return parseTime(v[j]) | |
368 | + } else { | |
369 | + return v[j] | |
370 | + } | |
371 | + })) | |
372 | + }, | |
373 | + getSortClass: function(key) { | |
374 | + const sort = this.listQuery.sort | |
375 | + return sort === `+${key}` ? 'ascending' : 'descending' | |
376 | + } | |
377 | + } | |
378 | +} | |
379 | +</script> | ... | ... |
src/views/permission/components/SwitchRoles.vue
... | ... | @@ -5,7 +5,9 @@ |
5 | 5 | </div> |
6 | 6 | {{ $t('permission.switchRoles') }}: |
7 | 7 | <el-radio-group v-model="switchRoles"> |
8 | - <el-radio-button label="editor" /> | |
8 | + <el-radio-button label="runner" /> | |
9 | + <el-radio-button label="shoper" /> | |
10 | + <el-radio-button label="assistant" /> | |
9 | 11 | <el-radio-button label="admin" /> |
10 | 12 | </el-radio-group> |
11 | 13 | </div> | ... | ... |
src/views/permission/directive.vue
... | ... | @@ -13,23 +13,23 @@ |
13 | 13 | </div> |
14 | 14 | |
15 | 15 | <div> |
16 | - <span v-permission="['editor']" class="permission-alert"> | |
16 | + <span v-permission="['runner']" class="permission-alert"> | |
17 | 17 | Only |
18 | - <el-tag class="permission-tag" size="small">editor</el-tag> can see this | |
18 | + <el-tag class="permission-tag" size="small">runner</el-tag> can see this | |
19 | 19 | </span> |
20 | - <el-tag v-permission="['editor']" class="permission-sourceCode" type="info"> | |
21 | - v-permission="['editor']" | |
20 | + <el-tag v-permission="['runner']" class="permission-sourceCode" type="info"> | |
21 | + v-permission="['runner']" | |
22 | 22 | </el-tag> |
23 | 23 | </div> |
24 | 24 | |
25 | 25 | <div> |
26 | - <span v-permission="['admin','editor']" class="permission-alert"> | |
26 | + <span v-permission="['admin','shoper']" class="permission-alert"> | |
27 | 27 | Both |
28 | 28 | <el-tag class="permission-tag" size="small">admin</el-tag> and |
29 | - <el-tag class="permission-tag" size="small">editor</el-tag> can see this | |
29 | + <el-tag class="permission-tag" size="small">shoper</el-tag> can see this | |
30 | 30 | </span> |
31 | - <el-tag v-permission="['admin','editor']" class="permission-sourceCode" type="info"> | |
32 | - v-permission="['admin','editor']" | |
31 | + <el-tag v-permission="['admin','shoper']" class="permission-sourceCode" type="info"> | |
32 | + v-permission="['admin','shoper']" | |
33 | 33 | </el-tag> |
34 | 34 | </div> |
35 | 35 | </div> |
... | ... | @@ -48,17 +48,17 @@ |
48 | 48 | </el-tag> |
49 | 49 | </el-tab-pane> |
50 | 50 | |
51 | - <el-tab-pane v-if="checkPermission(['editor'])" label="Editor"> | |
52 | - Editor can see this | |
51 | + <el-tab-pane v-if="checkPermission(['shoper'])" label="Shoper"> | |
52 | + Shoper can see this | |
53 | 53 | <el-tag class="permission-sourceCode" type="info"> |
54 | - v-if="checkPermission(['editor'])" | |
54 | + v-if="checkPermission(['shoper'])" | |
55 | 55 | </el-tag> |
56 | 56 | </el-tab-pane> |
57 | 57 | |
58 | - <el-tab-pane v-if="checkPermission(['admin','editor'])" label="Admin-OR-Editor"> | |
59 | - Both admin or editor can see this | |
58 | + <el-tab-pane v-if="checkPermission(['admin','runner'])" label="Admin-OR-Runner"> | |
59 | + Both admin or runner can see this | |
60 | 60 | <el-tag class="permission-sourceCode" type="info"> |
61 | - v-if="checkPermission(['admin','editor'])" | |
61 | + v-if="checkPermission(['admin','runner'])" | |
62 | 62 | </el-tag> |
63 | 63 | </el-tab-pane> |
64 | 64 | </el-tabs> | ... | ... |
src/views/prod/complex-table.vue
... | ... | @@ -0,0 +1,379 @@ |
1 | +<template> | |
2 | + <div class="app-container"> | |
3 | + <div class="filter-container"> | |
4 | + <el-input v-model="listQuery.title" :placeholder="$t('table.title')" style="width: 200px;" class="filter-item" @keyup.enter.native="handleFilter" /> | |
5 | + <el-select v-model="listQuery.importance" :placeholder="$t('table.importance')" clearable style="width: 90px" class="filter-item"> | |
6 | + <el-option v-for="item in importanceOptions" :key="item" :label="item" :value="item" /> | |
7 | + </el-select> | |
8 | + <el-select v-model="listQuery.type" :placeholder="$t('table.type')" clearable class="filter-item" style="width: 130px"> | |
9 | + <el-option v-for="item in calendarTypeOptions" :key="item.key" :label="item.display_name+'('+item.key+')'" :value="item.key" /> | |
10 | + </el-select> | |
11 | + <el-select v-model="listQuery.sort" style="width: 140px" class="filter-item" @change="handleFilter"> | |
12 | + <el-option v-for="item in sortOptions" :key="item.key" :label="item.label" :value="item.key" /> | |
13 | + </el-select> | |
14 | + <el-button v-waves class="filter-item" type="primary" icon="el-icon-search" @click="handleFilter"> | |
15 | + {{ $t('table.search') }} | |
16 | + </el-button> | |
17 | + <el-button class="filter-item" style="margin-left: 10px;" type="primary" icon="el-icon-edit" @click="handleCreate"> | |
18 | + {{ $t('table.add') }} | |
19 | + </el-button> | |
20 | + <el-button v-waves :loading="downloadLoading" class="filter-item" type="primary" icon="el-icon-download" @click="handleDownload"> | |
21 | + {{ $t('table.export') }} | |
22 | + </el-button> | |
23 | + <el-checkbox v-model="showReviewer" class="filter-item" style="margin-left:15px;" @change="tableKey=tableKey+1"> | |
24 | + {{ $t('table.reviewer') }} | |
25 | + </el-checkbox> | |
26 | + </div> | |
27 | + | |
28 | + <el-table | |
29 | + :key="tableKey" | |
30 | + v-loading="listLoading" | |
31 | + :data="list" | |
32 | + border | |
33 | + fit | |
34 | + highlight-current-row | |
35 | + style="width: 100%;" | |
36 | + @sort-change="sortChange" | |
37 | + > | |
38 | + <el-table-column :label="$t('table.id')" prop="id" sortable="custom" align="center" width="80" :class-name="getSortClass('id')"> | |
39 | + <template slot-scope="{row}"> | |
40 | + <span>{{ row.id }}</span> | |
41 | + </template> | |
42 | + </el-table-column> | |
43 | + <el-table-column :label="$t('table.date')" width="150px" align="center"> | |
44 | + <template slot-scope="{row}"> | |
45 | + <span>{{ row.timestamp | parseTime('{y}-{m}-{d} {h}:{i}') }}</span> | |
46 | + </template> | |
47 | + </el-table-column> | |
48 | + <el-table-column :label="$t('table.title')" min-width="150px"> | |
49 | + <template slot-scope="{row}"> | |
50 | + <span class="link-type" @click="handleUpdate(row)">{{ row.title }}</span> | |
51 | + <el-tag>{{ row.type | typeFilter }}</el-tag> | |
52 | + </template> | |
53 | + </el-table-column> | |
54 | + <el-table-column :label="$t('table.author')" width="110px" align="center"> | |
55 | + <template slot-scope="{row}"> | |
56 | + <span>{{ row.author }}</span> | |
57 | + </template> | |
58 | + </el-table-column> | |
59 | + <el-table-column v-if="showReviewer" :label="$t('table.reviewer')" width="110px" align="center"> | |
60 | + <template slot-scope="{row}"> | |
61 | + <span style="color:red;">{{ row.reviewer }}</span> | |
62 | + </template> | |
63 | + </el-table-column> | |
64 | + <el-table-column :label="$t('table.importance')" width="80px"> | |
65 | + <template slot-scope="{row}"> | |
66 | + <svg-icon v-for="n in +row.importance" :key="n" icon-class="star" class="meta-item__icon" /> | |
67 | + </template> | |
68 | + </el-table-column> | |
69 | + <el-table-column :label="$t('table.readings')" align="center" width="95"> | |
70 | + <template slot-scope="{row}"> | |
71 | + <span v-if="row.pageviews" class="link-type" @click="handleFetchPv(row.pageviews)">{{ row.pageviews }}</span> | |
72 | + <span v-else>0</span> | |
73 | + </template> | |
74 | + </el-table-column> | |
75 | + <el-table-column :label="$t('table.status')" class-name="status-col" width="100"> | |
76 | + <template slot-scope="{row}"> | |
77 | + <el-tag :type="row.status | statusFilter"> | |
78 | + {{ row.status }} | |
79 | + </el-tag> | |
80 | + </template> | |
81 | + </el-table-column> | |
82 | + <el-table-column :label="$t('table.actions')" align="center" width="230" class-name="small-padding fixed-width"> | |
83 | + <template slot-scope="{row,$index}"> | |
84 | + <el-button type="primary" size="mini" @click="handleUpdate(row)"> | |
85 | + {{ $t('table.edit') }} | |
86 | + </el-button> | |
87 | + <el-button v-if="row.status!='published'" size="mini" type="success" @click="handleModifyStatus(row,'published')"> | |
88 | + {{ $t('table.publish') }} | |
89 | + </el-button> | |
90 | + <el-button v-if="row.status!='draft'" size="mini" @click="handleModifyStatus(row,'draft')"> | |
91 | + {{ $t('table.draft') }} | |
92 | + </el-button> | |
93 | + <el-button v-if="row.status!='deleted'" size="mini" type="danger" @click="handleDelete(row,$index)"> | |
94 | + {{ $t('table.delete') }} | |
95 | + </el-button> | |
96 | + </template> | |
97 | + </el-table-column> | |
98 | + </el-table> | |
99 | + | |
100 | + <pagination v-show="total>0" :total="total" :page.sync="listQuery.page" :limit.sync="listQuery.limit" @pagination="getList" /> | |
101 | + | |
102 | + <el-dialog :title="textMap[dialogStatus]" :visible.sync="dialogFormVisible"> | |
103 | + <el-form ref="dataForm" :rules="rules" :model="temp" label-position="left" label-width="70px" style="width: 400px; margin-left:50px;"> | |
104 | + <el-form-item :label="$t('table.type')" prop="type"> | |
105 | + <el-select v-model="temp.type" class="filter-item" placeholder="Please select"> | |
106 | + <el-option v-for="item in calendarTypeOptions" :key="item.key" :label="item.display_name" :value="item.key" /> | |
107 | + </el-select> | |
108 | + </el-form-item> | |
109 | + <el-form-item :label="$t('table.date')" prop="timestamp"> | |
110 | + <el-date-picker v-model="temp.timestamp" type="datetime" placeholder="Please pick a date" /> | |
111 | + </el-form-item> | |
112 | + <el-form-item :label="$t('table.title')" prop="title"> | |
113 | + <el-input v-model="temp.title" /> | |
114 | + </el-form-item> | |
115 | + <el-form-item :label="$t('table.status')"> | |
116 | + <el-select v-model="temp.status" class="filter-item" placeholder="Please select"> | |
117 | + <el-option v-for="item in statusOptions" :key="item" :label="item" :value="item" /> | |
118 | + </el-select> | |
119 | + </el-form-item> | |
120 | + <el-form-item :label="$t('table.importance')"> | |
121 | + <el-rate v-model="temp.importance" :colors="['#99A9BF', '#F7BA2A', '#FF9900']" :max="3" style="margin-top:8px;" /> | |
122 | + </el-form-item> | |
123 | + <el-form-item :label="$t('table.remark')"> | |
124 | + <el-input v-model="temp.remark" :autosize="{ minRows: 2, maxRows: 4}" type="textarea" placeholder="Please input" /> | |
125 | + </el-form-item> | |
126 | + </el-form> | |
127 | + <div slot="footer" class="dialog-footer"> | |
128 | + <el-button @click="dialogFormVisible = false"> | |
129 | + {{ $t('table.cancel') }} | |
130 | + </el-button> | |
131 | + <el-button type="primary" @click="dialogStatus==='create'?createData():updateData()"> | |
132 | + {{ $t('table.confirm') }} | |
133 | + </el-button> | |
134 | + </div> | |
135 | + </el-dialog> | |
136 | + | |
137 | + <el-dialog :visible.sync="dialogPvVisible" title="Reading statistics"> | |
138 | + <el-table :data="pvData" border fit highlight-current-row style="width: 100%"> | |
139 | + <el-table-column prop="key" label="Channel" /> | |
140 | + <el-table-column prop="pv" label="Pv" /> | |
141 | + </el-table> | |
142 | + <span slot="footer" class="dialog-footer"> | |
143 | + <el-button type="primary" @click="dialogPvVisible = false">{{ $t('table.confirm') }}</el-button> | |
144 | + </span> | |
145 | + </el-dialog> | |
146 | + </div> | |
147 | +</template> | |
148 | + | |
149 | +<script> | |
150 | +import { fetchList, fetchPv, createArticle, updateArticle } from '@/api/article' | |
151 | +import waves from '@/directive/waves' // waves directive | |
152 | +import { parseTime } from '@/utils' | |
153 | +import Pagination from '@/components/Pagination' // secondary package based on el-pagination | |
154 | + | |
155 | +const calendarTypeOptions = [ | |
156 | + { key: 'CN', display_name: 'China' }, | |
157 | + { key: 'US', display_name: 'USA' }, | |
158 | + { key: 'JP', display_name: 'Japan' }, | |
159 | + { key: 'EU', display_name: 'Eurozone' } | |
160 | +] | |
161 | + | |
162 | +// arr to obj, such as { CN : "China", US : "USA" } | |
163 | +const calendarTypeKeyValue = calendarTypeOptions.reduce((acc, cur) => { | |
164 | + acc[cur.key] = cur.display_name | |
165 | + return acc | |
166 | +}, {}) | |
167 | + | |
168 | +export default { | |
169 | + name: 'ComplexTable', | |
170 | + components: { Pagination }, | |
171 | + directives: { waves }, | |
172 | + filters: { | |
173 | + statusFilter(status) { | |
174 | + const statusMap = { | |
175 | + published: 'success', | |
176 | + draft: 'info', | |
177 | + deleted: 'danger' | |
178 | + } | |
179 | + return statusMap[status] | |
180 | + }, | |
181 | + typeFilter(type) { | |
182 | + return calendarTypeKeyValue[type] | |
183 | + } | |
184 | + }, | |
185 | + data() { | |
186 | + return { | |
187 | + tableKey: 0, | |
188 | + list: null, | |
189 | + total: 0, | |
190 | + listLoading: true, | |
191 | + listQuery: { | |
192 | + page: 1, | |
193 | + limit: 20, | |
194 | + importance: undefined, | |
195 | + title: undefined, | |
196 | + type: undefined, | |
197 | + sort: '+id' | |
198 | + }, | |
199 | + importanceOptions: [1, 2, 3], | |
200 | + calendarTypeOptions, | |
201 | + sortOptions: [{ label: 'ID Ascending', key: '+id' }, { label: 'ID Descending', key: '-id' }], | |
202 | + statusOptions: ['published', 'draft', 'deleted'], | |
203 | + showReviewer: false, | |
204 | + temp: { | |
205 | + id: undefined, | |
206 | + importance: 1, | |
207 | + remark: '', | |
208 | + timestamp: new Date(), | |
209 | + title: '', | |
210 | + type: '', | |
211 | + status: 'published' | |
212 | + }, | |
213 | + dialogFormVisible: false, | |
214 | + dialogStatus: '', | |
215 | + textMap: { | |
216 | + update: 'Edit', | |
217 | + create: 'Create' | |
218 | + }, | |
219 | + dialogPvVisible: false, | |
220 | + pvData: [], | |
221 | + rules: { | |
222 | + type: [{ required: true, message: 'type is required', trigger: 'change' }], | |
223 | + timestamp: [{ type: 'date', required: true, message: 'timestamp is required', trigger: 'change' }], | |
224 | + title: [{ required: true, message: 'title is required', trigger: 'blur' }] | |
225 | + }, | |
226 | + downloadLoading: false | |
227 | + } | |
228 | + }, | |
229 | + created() { | |
230 | + this.getList() | |
231 | + }, | |
232 | + methods: { | |
233 | + getList() { | |
234 | + this.listLoading = true | |
235 | + fetchList(this.listQuery).then(response => { | |
236 | + this.list = response.data.items | |
237 | + this.total = response.data.total | |
238 | + | |
239 | + // Just to simulate the time of the request | |
240 | + setTimeout(() => { | |
241 | + this.listLoading = false | |
242 | + }, 1.5 * 1000) | |
243 | + }) | |
244 | + }, | |
245 | + handleFilter() { | |
246 | + this.listQuery.page = 1 | |
247 | + this.getList() | |
248 | + }, | |
249 | + handleModifyStatus(row, status) { | |
250 | + this.$message({ | |
251 | + message: '操作成功', | |
252 | + type: 'success' | |
253 | + }) | |
254 | + row.status = status | |
255 | + }, | |
256 | + sortChange(data) { | |
257 | + const { prop, order } = data | |
258 | + if (prop === 'id') { | |
259 | + this.sortByID(order) | |
260 | + } | |
261 | + }, | |
262 | + sortByID(order) { | |
263 | + if (order === 'ascending') { | |
264 | + this.listQuery.sort = '+id' | |
265 | + } else { | |
266 | + this.listQuery.sort = '-id' | |
267 | + } | |
268 | + this.handleFilter() | |
269 | + }, | |
270 | + resetTemp() { | |
271 | + this.temp = { | |
272 | + id: undefined, | |
273 | + importance: 1, | |
274 | + remark: '', | |
275 | + timestamp: new Date(), | |
276 | + title: '', | |
277 | + status: 'published', | |
278 | + type: '' | |
279 | + } | |
280 | + }, | |
281 | + handleCreate() { | |
282 | + this.resetTemp() | |
283 | + this.dialogStatus = 'create' | |
284 | + this.dialogFormVisible = true | |
285 | + this.$nextTick(() => { | |
286 | + this.$refs['dataForm'].clearValidate() | |
287 | + }) | |
288 | + }, | |
289 | + createData() { | |
290 | + this.$refs['dataForm'].validate((valid) => { | |
291 | + if (valid) { | |
292 | + this.temp.id = parseInt(Math.random() * 100) + 1024 // mock a id | |
293 | + this.temp.author = '秀野堂主' | |
294 | + createArticle(this.temp).then(() => { | |
295 | + this.list.unshift(this.temp) | |
296 | + this.dialogFormVisible = false | |
297 | + this.$notify({ | |
298 | + title: '成功', | |
299 | + message: '创建成功', | |
300 | + type: 'success', | |
301 | + duration: 2000 | |
302 | + }) | |
303 | + }) | |
304 | + } | |
305 | + }) | |
306 | + }, | |
307 | + handleUpdate(row) { | |
308 | + this.temp = Object.assign({}, row) // copy obj | |
309 | + this.temp.timestamp = new Date(this.temp.timestamp) | |
310 | + this.dialogStatus = 'update' | |
311 | + this.dialogFormVisible = true | |
312 | + this.$nextTick(() => { | |
313 | + this.$refs['dataForm'].clearValidate() | |
314 | + }) | |
315 | + }, | |
316 | + updateData() { | |
317 | + this.$refs['dataForm'].validate((valid) => { | |
318 | + if (valid) { | |
319 | + const tempData = Object.assign({}, this.temp) | |
320 | + tempData.timestamp = +new Date(tempData.timestamp) // change Thu Nov 30 2017 16:41:05 GMT+0800 (CST) to 1512031311464 | |
321 | + updateArticle(tempData).then(() => { | |
322 | + const index = this.list.findIndex(v => v.id === this.temp.id) | |
323 | + this.list.splice(index, 1, this.temp) | |
324 | + this.dialogFormVisible = false | |
325 | + this.$notify({ | |
326 | + title: '成功', | |
327 | + message: '更新成功', | |
328 | + type: 'success', | |
329 | + duration: 2000 | |
330 | + }) | |
331 | + }) | |
332 | + } | |
333 | + }) | |
334 | + }, | |
335 | + handleDelete(row, index) { | |
336 | + this.$notify({ | |
337 | + title: '成功', | |
338 | + message: '删除成功', | |
339 | + type: 'success', | |
340 | + duration: 2000 | |
341 | + }) | |
342 | + this.list.splice(index, 1) | |
343 | + }, | |
344 | + handleFetchPv(pv) { | |
345 | + fetchPv(pv).then(response => { | |
346 | + this.pvData = response.data.pvData | |
347 | + this.dialogPvVisible = true | |
348 | + }) | |
349 | + }, | |
350 | + handleDownload() { | |
351 | + this.downloadLoading = true | |
352 | + import('@/vendor/Export2Excel').then(excel => { | |
353 | + const tHeader = ['timestamp', 'title', 'type', 'importance', 'status'] | |
354 | + const filterVal = ['timestamp', 'title', 'type', 'importance', 'status'] | |
355 | + const data = this.formatJson(filterVal) | |
356 | + excel.export_json_to_excel({ | |
357 | + header: tHeader, | |
358 | + data, | |
359 | + filename: 'table-list' | |
360 | + }) | |
361 | + this.downloadLoading = false | |
362 | + }) | |
363 | + }, | |
364 | + formatJson(filterVal) { | |
365 | + return this.list.map(v => filterVal.map(j => { | |
366 | + if (j === 'timestamp') { | |
367 | + return parseTime(v[j]) | |
368 | + } else { | |
369 | + return v[j] | |
370 | + } | |
371 | + })) | |
372 | + }, | |
373 | + getSortClass: function(key) { | |
374 | + const sort = this.listQuery.sort | |
375 | + return sort === `+${key}` ? 'ascending' : 'descending' | |
376 | + } | |
377 | + } | |
378 | +} | |
379 | +</script> | ... | ... |
src/views/system/complex-table.vue
... | ... | @@ -0,0 +1,379 @@ |
1 | +<template> | |
2 | + <div class="app-container"> | |
3 | + <div class="filter-container"> | |
4 | + <el-input v-model="listQuery.title" :placeholder="$t('table.title')" style="width: 200px;" class="filter-item" @keyup.enter.native="handleFilter" /> | |
5 | + <el-select v-model="listQuery.importance" :placeholder="$t('table.importance')" clearable style="width: 90px" class="filter-item"> | |
6 | + <el-option v-for="item in importanceOptions" :key="item" :label="item" :value="item" /> | |
7 | + </el-select> | |
8 | + <el-select v-model="listQuery.type" :placeholder="$t('table.type')" clearable class="filter-item" style="width: 130px"> | |
9 | + <el-option v-for="item in calendarTypeOptions" :key="item.key" :label="item.display_name+'('+item.key+')'" :value="item.key" /> | |
10 | + </el-select> | |
11 | + <el-select v-model="listQuery.sort" style="width: 140px" class="filter-item" @change="handleFilter"> | |
12 | + <el-option v-for="item in sortOptions" :key="item.key" :label="item.label" :value="item.key" /> | |
13 | + </el-select> | |
14 | + <el-button v-waves class="filter-item" type="primary" icon="el-icon-search" @click="handleFilter"> | |
15 | + {{ $t('table.search') }} | |
16 | + </el-button> | |
17 | + <el-button class="filter-item" style="margin-left: 10px;" type="primary" icon="el-icon-edit" @click="handleCreate"> | |
18 | + {{ $t('table.add') }} | |
19 | + </el-button> | |
20 | + <el-button v-waves :loading="downloadLoading" class="filter-item" type="primary" icon="el-icon-download" @click="handleDownload"> | |
21 | + {{ $t('table.export') }} | |
22 | + </el-button> | |
23 | + <el-checkbox v-model="showReviewer" class="filter-item" style="margin-left:15px;" @change="tableKey=tableKey+1"> | |
24 | + {{ $t('table.reviewer') }} | |
25 | + </el-checkbox> | |
26 | + </div> | |
27 | + | |
28 | + <el-table | |
29 | + :key="tableKey" | |
30 | + v-loading="listLoading" | |
31 | + :data="list" | |
32 | + border | |
33 | + fit | |
34 | + highlight-current-row | |
35 | + style="width: 100%;" | |
36 | + @sort-change="sortChange" | |
37 | + > | |
38 | + <el-table-column :label="$t('table.id')" prop="id" sortable="custom" align="center" width="80" :class-name="getSortClass('id')"> | |
39 | + <template slot-scope="{row}"> | |
40 | + <span>{{ row.id }}</span> | |
41 | + </template> | |
42 | + </el-table-column> | |
43 | + <el-table-column :label="$t('table.date')" width="150px" align="center"> | |
44 | + <template slot-scope="{row}"> | |
45 | + <span>{{ row.timestamp | parseTime('{y}-{m}-{d} {h}:{i}') }}</span> | |
46 | + </template> | |
47 | + </el-table-column> | |
48 | + <el-table-column :label="$t('table.title')" min-width="150px"> | |
49 | + <template slot-scope="{row}"> | |
50 | + <span class="link-type" @click="handleUpdate(row)">{{ row.title }}</span> | |
51 | + <el-tag>{{ row.type | typeFilter }}</el-tag> | |
52 | + </template> | |
53 | + </el-table-column> | |
54 | + <el-table-column :label="$t('table.author')" width="110px" align="center"> | |
55 | + <template slot-scope="{row}"> | |
56 | + <span>{{ row.author }}</span> | |
57 | + </template> | |
58 | + </el-table-column> | |
59 | + <el-table-column v-if="showReviewer" :label="$t('table.reviewer')" width="110px" align="center"> | |
60 | + <template slot-scope="{row}"> | |
61 | + <span style="color:red;">{{ row.reviewer }}</span> | |
62 | + </template> | |
63 | + </el-table-column> | |
64 | + <el-table-column :label="$t('table.importance')" width="80px"> | |
65 | + <template slot-scope="{row}"> | |
66 | + <svg-icon v-for="n in +row.importance" :key="n" icon-class="star" class="meta-item__icon" /> | |
67 | + </template> | |
68 | + </el-table-column> | |
69 | + <el-table-column :label="$t('table.readings')" align="center" width="95"> | |
70 | + <template slot-scope="{row}"> | |
71 | + <span v-if="row.pageviews" class="link-type" @click="handleFetchPv(row.pageviews)">{{ row.pageviews }}</span> | |
72 | + <span v-else>0</span> | |
73 | + </template> | |
74 | + </el-table-column> | |
75 | + <el-table-column :label="$t('table.status')" class-name="status-col" width="100"> | |
76 | + <template slot-scope="{row}"> | |
77 | + <el-tag :type="row.status | statusFilter"> | |
78 | + {{ row.status }} | |
79 | + </el-tag> | |
80 | + </template> | |
81 | + </el-table-column> | |
82 | + <el-table-column :label="$t('table.actions')" align="center" width="230" class-name="small-padding fixed-width"> | |
83 | + <template slot-scope="{row,$index}"> | |
84 | + <el-button type="primary" size="mini" @click="handleUpdate(row)"> | |
85 | + {{ $t('table.edit') }} | |
86 | + </el-button> | |
87 | + <el-button v-if="row.status!='published'" size="mini" type="success" @click="handleModifyStatus(row,'published')"> | |
88 | + {{ $t('table.publish') }} | |
89 | + </el-button> | |
90 | + <el-button v-if="row.status!='draft'" size="mini" @click="handleModifyStatus(row,'draft')"> | |
91 | + {{ $t('table.draft') }} | |
92 | + </el-button> | |
93 | + <el-button v-if="row.status!='deleted'" size="mini" type="danger" @click="handleDelete(row,$index)"> | |
94 | + {{ $t('table.delete') }} | |
95 | + </el-button> | |
96 | + </template> | |
97 | + </el-table-column> | |
98 | + </el-table> | |
99 | + | |
100 | + <pagination v-show="total>0" :total="total" :page.sync="listQuery.page" :limit.sync="listQuery.limit" @pagination="getList" /> | |
101 | + | |
102 | + <el-dialog :title="textMap[dialogStatus]" :visible.sync="dialogFormVisible"> | |
103 | + <el-form ref="dataForm" :rules="rules" :model="temp" label-position="left" label-width="70px" style="width: 400px; margin-left:50px;"> | |
104 | + <el-form-item :label="$t('table.type')" prop="type"> | |
105 | + <el-select v-model="temp.type" class="filter-item" placeholder="Please select"> | |
106 | + <el-option v-for="item in calendarTypeOptions" :key="item.key" :label="item.display_name" :value="item.key" /> | |
107 | + </el-select> | |
108 | + </el-form-item> | |
109 | + <el-form-item :label="$t('table.date')" prop="timestamp"> | |
110 | + <el-date-picker v-model="temp.timestamp" type="datetime" placeholder="Please pick a date" /> | |
111 | + </el-form-item> | |
112 | + <el-form-item :label="$t('table.title')" prop="title"> | |
113 | + <el-input v-model="temp.title" /> | |
114 | + </el-form-item> | |
115 | + <el-form-item :label="$t('table.status')"> | |
116 | + <el-select v-model="temp.status" class="filter-item" placeholder="Please select"> | |
117 | + <el-option v-for="item in statusOptions" :key="item" :label="item" :value="item" /> | |
118 | + </el-select> | |
119 | + </el-form-item> | |
120 | + <el-form-item :label="$t('table.importance')"> | |
121 | + <el-rate v-model="temp.importance" :colors="['#99A9BF', '#F7BA2A', '#FF9900']" :max="3" style="margin-top:8px;" /> | |
122 | + </el-form-item> | |
123 | + <el-form-item :label="$t('table.remark')"> | |
124 | + <el-input v-model="temp.remark" :autosize="{ minRows: 2, maxRows: 4}" type="textarea" placeholder="Please input" /> | |
125 | + </el-form-item> | |
126 | + </el-form> | |
127 | + <div slot="footer" class="dialog-footer"> | |
128 | + <el-button @click="dialogFormVisible = false"> | |
129 | + {{ $t('table.cancel') }} | |
130 | + </el-button> | |
131 | + <el-button type="primary" @click="dialogStatus==='create'?createData():updateData()"> | |
132 | + {{ $t('table.confirm') }} | |
133 | + </el-button> | |
134 | + </div> | |
135 | + </el-dialog> | |
136 | + | |
137 | + <el-dialog :visible.sync="dialogPvVisible" title="Reading statistics"> | |
138 | + <el-table :data="pvData" border fit highlight-current-row style="width: 100%"> | |
139 | + <el-table-column prop="key" label="Channel" /> | |
140 | + <el-table-column prop="pv" label="Pv" /> | |
141 | + </el-table> | |
142 | + <span slot="footer" class="dialog-footer"> | |
143 | + <el-button type="primary" @click="dialogPvVisible = false">{{ $t('table.confirm') }}</el-button> | |
144 | + </span> | |
145 | + </el-dialog> | |
146 | + </div> | |
147 | +</template> | |
148 | + | |
149 | +<script> | |
150 | +import { fetchList, fetchPv, createArticle, updateArticle } from '@/api/article' | |
151 | +import waves from '@/directive/waves' // waves directive | |
152 | +import { parseTime } from '@/utils' | |
153 | +import Pagination from '@/components/Pagination' // secondary package based on el-pagination | |
154 | + | |
155 | +const calendarTypeOptions = [ | |
156 | + { key: 'CN', display_name: 'China' }, | |
157 | + { key: 'US', display_name: 'USA' }, | |
158 | + { key: 'JP', display_name: 'Japan' }, | |
159 | + { key: 'EU', display_name: 'Eurozone' } | |
160 | +] | |
161 | + | |
162 | +// arr to obj, such as { CN : "China", US : "USA" } | |
163 | +const calendarTypeKeyValue = calendarTypeOptions.reduce((acc, cur) => { | |
164 | + acc[cur.key] = cur.display_name | |
165 | + return acc | |
166 | +}, {}) | |
167 | + | |
168 | +export default { | |
169 | + name: 'ComplexTable', | |
170 | + components: { Pagination }, | |
171 | + directives: { waves }, | |
172 | + filters: { | |
173 | + statusFilter(status) { | |
174 | + const statusMap = { | |
175 | + published: 'success', | |
176 | + draft: 'info', | |
177 | + deleted: 'danger' | |
178 | + } | |
179 | + return statusMap[status] | |
180 | + }, | |
181 | + typeFilter(type) { | |
182 | + return calendarTypeKeyValue[type] | |
183 | + } | |
184 | + }, | |
185 | + data() { | |
186 | + return { | |
187 | + tableKey: 0, | |
188 | + list: null, | |
189 | + total: 0, | |
190 | + listLoading: true, | |
191 | + listQuery: { | |
192 | + page: 1, | |
193 | + limit: 20, | |
194 | + importance: undefined, | |
195 | + title: undefined, | |
196 | + type: undefined, | |
197 | + sort: '+id' | |
198 | + }, | |
199 | + importanceOptions: [1, 2, 3], | |
200 | + calendarTypeOptions, | |
201 | + sortOptions: [{ label: 'ID Ascending', key: '+id' }, { label: 'ID Descending', key: '-id' }], | |
202 | + statusOptions: ['published', 'draft', 'deleted'], | |
203 | + showReviewer: false, | |
204 | + temp: { | |
205 | + id: undefined, | |
206 | + importance: 1, | |
207 | + remark: '', | |
208 | + timestamp: new Date(), | |
209 | + title: '', | |
210 | + type: '', | |
211 | + status: 'published' | |
212 | + }, | |
213 | + dialogFormVisible: false, | |
214 | + dialogStatus: '', | |
215 | + textMap: { | |
216 | + update: 'Edit', | |
217 | + create: 'Create' | |
218 | + }, | |
219 | + dialogPvVisible: false, | |
220 | + pvData: [], | |
221 | + rules: { | |
222 | + type: [{ required: true, message: 'type is required', trigger: 'change' }], | |
223 | + timestamp: [{ type: 'date', required: true, message: 'timestamp is required', trigger: 'change' }], | |
224 | + title: [{ required: true, message: 'title is required', trigger: 'blur' }] | |
225 | + }, | |
226 | + downloadLoading: false | |
227 | + } | |
228 | + }, | |
229 | + created() { | |
230 | + this.getList() | |
231 | + }, | |
232 | + methods: { | |
233 | + getList() { | |
234 | + this.listLoading = true | |
235 | + fetchList(this.listQuery).then(response => { | |
236 | + this.list = response.data.items | |
237 | + this.total = response.data.total | |
238 | + | |
239 | + // Just to simulate the time of the request | |
240 | + setTimeout(() => { | |
241 | + this.listLoading = false | |
242 | + }, 1.5 * 1000) | |
243 | + }) | |
244 | + }, | |
245 | + handleFilter() { | |
246 | + this.listQuery.page = 1 | |
247 | + this.getList() | |
248 | + }, | |
249 | + handleModifyStatus(row, status) { | |
250 | + this.$message({ | |
251 | + message: '操作成功', | |
252 | + type: 'success' | |
253 | + }) | |
254 | + row.status = status | |
255 | + }, | |
256 | + sortChange(data) { | |
257 | + const { prop, order } = data | |
258 | + if (prop === 'id') { | |
259 | + this.sortByID(order) | |
260 | + } | |
261 | + }, | |
262 | + sortByID(order) { | |
263 | + if (order === 'ascending') { | |
264 | + this.listQuery.sort = '+id' | |
265 | + } else { | |
266 | + this.listQuery.sort = '-id' | |
267 | + } | |
268 | + this.handleFilter() | |
269 | + }, | |
270 | + resetTemp() { | |
271 | + this.temp = { | |
272 | + id: undefined, | |
273 | + importance: 1, | |
274 | + remark: '', | |
275 | + timestamp: new Date(), | |
276 | + title: '', | |
277 | + status: 'published', | |
278 | + type: '' | |
279 | + } | |
280 | + }, | |
281 | + handleCreate() { | |
282 | + this.resetTemp() | |
283 | + this.dialogStatus = 'create' | |
284 | + this.dialogFormVisible = true | |
285 | + this.$nextTick(() => { | |
286 | + this.$refs['dataForm'].clearValidate() | |
287 | + }) | |
288 | + }, | |
289 | + createData() { | |
290 | + this.$refs['dataForm'].validate((valid) => { | |
291 | + if (valid) { | |
292 | + this.temp.id = parseInt(Math.random() * 100) + 1024 // mock a id | |
293 | + this.temp.author = '秀野堂主' | |
294 | + createArticle(this.temp).then(() => { | |
295 | + this.list.unshift(this.temp) | |
296 | + this.dialogFormVisible = false | |
297 | + this.$notify({ | |
298 | + title: '成功', | |
299 | + message: '创建成功', | |
300 | + type: 'success', | |
301 | + duration: 2000 | |
302 | + }) | |
303 | + }) | |
304 | + } | |
305 | + }) | |
306 | + }, | |
307 | + handleUpdate(row) { | |
308 | + this.temp = Object.assign({}, row) // copy obj | |
309 | + this.temp.timestamp = new Date(this.temp.timestamp) | |
310 | + this.dialogStatus = 'update' | |
311 | + this.dialogFormVisible = true | |
312 | + this.$nextTick(() => { | |
313 | + this.$refs['dataForm'].clearValidate() | |
314 | + }) | |
315 | + }, | |
316 | + updateData() { | |
317 | + this.$refs['dataForm'].validate((valid) => { | |
318 | + if (valid) { | |
319 | + const tempData = Object.assign({}, this.temp) | |
320 | + tempData.timestamp = +new Date(tempData.timestamp) // change Thu Nov 30 2017 16:41:05 GMT+0800 (CST) to 1512031311464 | |
321 | + updateArticle(tempData).then(() => { | |
322 | + const index = this.list.findIndex(v => v.id === this.temp.id) | |
323 | + this.list.splice(index, 1, this.temp) | |
324 | + this.dialogFormVisible = false | |
325 | + this.$notify({ | |
326 | + title: '成功', | |
327 | + message: '更新成功', | |
328 | + type: 'success', | |
329 | + duration: 2000 | |
330 | + }) | |
331 | + }) | |
332 | + } | |
333 | + }) | |
334 | + }, | |
335 | + handleDelete(row, index) { | |
336 | + this.$notify({ | |
337 | + title: '成功', | |
338 | + message: '删除成功', | |
339 | + type: 'success', | |
340 | + duration: 2000 | |
341 | + }) | |
342 | + this.list.splice(index, 1) | |
343 | + }, | |
344 | + handleFetchPv(pv) { | |
345 | + fetchPv(pv).then(response => { | |
346 | + this.pvData = response.data.pvData | |
347 | + this.dialogPvVisible = true | |
348 | + }) | |
349 | + }, | |
350 | + handleDownload() { | |
351 | + this.downloadLoading = true | |
352 | + import('@/vendor/Export2Excel').then(excel => { | |
353 | + const tHeader = ['timestamp', 'title', 'type', 'importance', 'status'] | |
354 | + const filterVal = ['timestamp', 'title', 'type', 'importance', 'status'] | |
355 | + const data = this.formatJson(filterVal) | |
356 | + excel.export_json_to_excel({ | |
357 | + header: tHeader, | |
358 | + data, | |
359 | + filename: 'table-list' | |
360 | + }) | |
361 | + this.downloadLoading = false | |
362 | + }) | |
363 | + }, | |
364 | + formatJson(filterVal) { | |
365 | + return this.list.map(v => filterVal.map(j => { | |
366 | + if (j === 'timestamp') { | |
367 | + return parseTime(v[j]) | |
368 | + } else { | |
369 | + return v[j] | |
370 | + } | |
371 | + })) | |
372 | + }, | |
373 | + getSortClass: function(key) { | |
374 | + const sort = this.listQuery.sort | |
375 | + return sort === `+${key}` ? 'ascending' : 'descending' | |
376 | + } | |
377 | + } | |
378 | +} | |
379 | +</script> | ... | ... |
src/views/table/complex-table.vue
... | ... | @@ -290,7 +290,7 @@ export default { |
290 | 290 | this.$refs['dataForm'].validate((valid) => { |
291 | 291 | if (valid) { |
292 | 292 | this.temp.id = parseInt(Math.random() * 100) + 1024 // mock a id |
293 | - this.temp.author = 'vue-element-admin' | |
293 | + this.temp.author = '秀野堂主' | |
294 | 294 | createArticle(this.temp).then(() => { |
295 | 295 | this.list.unshift(this.temp) |
296 | 296 | this.dialogFormVisible = false | ... | ... |
src/views/users/list.vue
... | ... | @@ -0,0 +1,618 @@ |
1 | +<template> | |
2 | + <div class="app-container"> | |
3 | + <div class="filter-container"> | |
4 | + <el-input | |
5 | + v-model="listQuery.title" | |
6 | + :placeholder="$t('table.title')" | |
7 | + style="width: 200px;" | |
8 | + class="filter-item" | |
9 | + @keyup.enter.native="handleFilter" | |
10 | + /> | |
11 | + <el-select | |
12 | + v-model="listQuery.importance" | |
13 | + :placeholder="$t('table.importance')" | |
14 | + clearable | |
15 | + style="width: 90px" | |
16 | + class="filter-item" | |
17 | + > | |
18 | + <el-option | |
19 | + v-for="item in importanceOptions" | |
20 | + :key="item" | |
21 | + :label="item" | |
22 | + :value="item" | |
23 | + /> | |
24 | + </el-select> | |
25 | + <el-select | |
26 | + v-model="listQuery.type" | |
27 | + :placeholder="$t('table.type')" | |
28 | + clearable | |
29 | + class="filter-item" | |
30 | + style="width: 130px" | |
31 | + > | |
32 | + <el-option | |
33 | + v-for="item in calendarTypeOptions" | |
34 | + :key="item.key" | |
35 | + :label="item.display_name+'('+item.key+')'" | |
36 | + :value="item.key" | |
37 | + /> | |
38 | + </el-select> | |
39 | + <el-select | |
40 | + v-model="listQuery.sort" | |
41 | + style="width: 140px" | |
42 | + class="filter-item" | |
43 | + @change="handleFilter" | |
44 | + > | |
45 | + <el-option | |
46 | + v-for="item in sortOptions" | |
47 | + :key="item.key" | |
48 | + :label="item.label" | |
49 | + :value="item.key" | |
50 | + /> | |
51 | + </el-select> | |
52 | + <el-button | |
53 | + v-waves | |
54 | + class="filter-item" | |
55 | + type="primary" | |
56 | + icon="el-icon-search" | |
57 | + @click="handleFilter" | |
58 | + > | |
59 | + {{ $t('table.search') }} | |
60 | + </el-button> | |
61 | + <el-button | |
62 | + class="filter-item" | |
63 | + style="margin-left: 10px;" | |
64 | + type="primary" | |
65 | + icon="el-icon-edit" | |
66 | + @click="handleCreate" | |
67 | + > | |
68 | + {{ $t('table.add') }} | |
69 | + </el-button> | |
70 | + <el-button | |
71 | + v-waves | |
72 | + :loading="downloadLoading" | |
73 | + class="filter-item" | |
74 | + type="primary" | |
75 | + icon="el-icon-download" | |
76 | + @click="handleDownload" | |
77 | + > | |
78 | + {{ $t('table.export') }} | |
79 | + </el-button> | |
80 | + <el-checkbox | |
81 | + v-model="showReviewer" | |
82 | + class="filter-item" | |
83 | + style="margin-left:15px;" | |
84 | + @change="tableKey=tableKey+1" | |
85 | + > | |
86 | + {{ $t('table.reviewer') }} | |
87 | + </el-checkbox> | |
88 | + </div> | |
89 | + | |
90 | + <el-table | |
91 | + :key="tableKey" | |
92 | + v-loading="listLoading" | |
93 | + :data="list" | |
94 | + border | |
95 | + fit | |
96 | + highlight-current-row | |
97 | + style="width: 100%;" | |
98 | + @sort-change="sortChange" | |
99 | + > | |
100 | + <el-table-column | |
101 | + :label="$t('table.uid')" | |
102 | + prop="id" | |
103 | + sortable="custom" | |
104 | + align="center" | |
105 | + width="80" | |
106 | + :class-name="getSortClass('id')" | |
107 | + > | |
108 | + <template slot-scope="{row}"> | |
109 | + <span>{{ row.uid }}</span> | |
110 | + </template> | |
111 | + </el-table-column> | |
112 | + <el-table-column | |
113 | + :label="$t('table.create_time')" | |
114 | + width="150px" | |
115 | + align="center" | |
116 | + > | |
117 | + <template slot-scope="{row}"> | |
118 | + <span>{{ row.create_time | parseTime('{y}-{m}-{d} {h}:{i}') }}</span> | |
119 | + </template> | |
120 | + </el-table-column> | |
121 | + <el-table-column | |
122 | + :label="$t('table.openid')" | |
123 | + min-width="150px" | |
124 | + > | |
125 | + <template slot-scope="{row}"> | |
126 | + <span | |
127 | + class="link-type" | |
128 | + @click="handleUpdate(row)" | |
129 | + >{{ row.title }}</span> | |
130 | + </template> | |
131 | + </el-table-column> | |
132 | + <el-table-column | |
133 | + :label="$t('table.nickname')" | |
134 | + width="110px" | |
135 | + align="center" | |
136 | + > | |
137 | + <template slot-scope="{row}"> | |
138 | + <span>{{ row.nickname }}</span> | |
139 | + <el-tag>{{ row.type | typeFilter }}</el-tag> | |
140 | + </template> | |
141 | + </el-table-column> | |
142 | + <el-table-column | |
143 | + v-if="showReviewer" | |
144 | + :label="$t('table.reviewer')" | |
145 | + width="110px" | |
146 | + align="center" | |
147 | + > | |
148 | + <template slot-scope="{row}"> | |
149 | + <span style="color:red;">{{ row.reviewer }}</span> | |
150 | + </template> | |
151 | + </el-table-column> | |
152 | + <el-table-column | |
153 | + :label="$t('table.importance')" | |
154 | + width="80px" | |
155 | + > | |
156 | + <template slot-scope="{row}"> | |
157 | + <svg-icon | |
158 | + v-for="n in +row.importance" | |
159 | + :key="n" | |
160 | + icon-class="star" | |
161 | + class="meta-item__icon" | |
162 | + /> | |
163 | + </template> | |
164 | + </el-table-column> | |
165 | + <el-table-column | |
166 | + :label="$t('table.son_of_adv')" | |
167 | + align="center" | |
168 | + width="95" | |
169 | + > | |
170 | + <template slot-scope="{row}"> | |
171 | + <span | |
172 | + v-if="row.son_of_adv" | |
173 | + class="link-type" | |
174 | + @click="handleFetchPv(row.son_of_adv)" | |
175 | + >{{ row.son_of_adv }}</span> | |
176 | + <span v-else>0</span> | |
177 | + </template> | |
178 | + </el-table-column> | |
179 | + <el-table-column | |
180 | + :label="$t('table.status')" | |
181 | + class-name="status-col" | |
182 | + width="100" | |
183 | + > | |
184 | + <template slot-scope="{row}"> | |
185 | + <el-tag :type="row.status | statusFilter">{{ row.status }}</el-tag> | |
186 | + </template> | |
187 | + </el-table-column> | |
188 | + <el-table-column | |
189 | + :label="$t('table.actions')" | |
190 | + align="center" | |
191 | + width="230" | |
192 | + class-name="small-padding fixed-width" | |
193 | + > | |
194 | + <template slot-scope="{row,$index}"> | |
195 | + <el-button | |
196 | + type="primary" | |
197 | + size="mini" | |
198 | + @click="handleUpdate(row)" | |
199 | + >{{ $t('table.edit') }}</el-button> | |
200 | + <el-button | |
201 | + v-if="row.status!='deleted'" | |
202 | + size="mini" | |
203 | + type="danger" | |
204 | + @click="handleDelete(row,$index)" | |
205 | + >{{ $t('table.delete') }}</el-button> | |
206 | + </template> | |
207 | + </el-table-column> | |
208 | + </el-table> | |
209 | + | |
210 | + <pagination | |
211 | + v-show="total>0" | |
212 | + :total="total" | |
213 | + :page.sync="listQuery.page" | |
214 | + :limit.sync="listQuery.limit" | |
215 | + @pagination="getList" | |
216 | + /> | |
217 | + <el-dialog | |
218 | + :title="textMap[dialogStatus]" | |
219 | + :visible.sync="dialogFormVisible" | |
220 | + > | |
221 | + <el-form | |
222 | + ref="dataForm" | |
223 | + :rules="rules" | |
224 | + :model="temp" | |
225 | + label-position="left" | |
226 | + label-width="70px" | |
227 | + style="width: 400px; margin-left:50px;" | |
228 | + > | |
229 | + <el-form-item | |
230 | + :label="$t('table.type')" | |
231 | + prop="type" | |
232 | + > | |
233 | + <el-select | |
234 | + v-model="temp.type" | |
235 | + class="filter-item" | |
236 | + placeholder="Please select" | |
237 | + > | |
238 | + <el-option | |
239 | + v-for="item in calendarTypeOptions" | |
240 | + :key="item.key" | |
241 | + :label="item.display_name" | |
242 | + :value="item.key" | |
243 | + /> | |
244 | + </el-select> | |
245 | + </el-form-item> | |
246 | + <el-form-item | |
247 | + :label="$t('table.create_time')" | |
248 | + prop="create_time" | |
249 | + > | |
250 | + <el-date-picker | |
251 | + v-model="temp.create_time" | |
252 | + type="datetime" | |
253 | + placeholder="Please pick a date" | |
254 | + /> | |
255 | + </el-form-item> | |
256 | + <el-form-item | |
257 | + :label="$t('table.业务记录')" | |
258 | + prop="title" | |
259 | + > | |
260 | + <el-input v-model="temp.title" /> | |
261 | + </el-form-item> | |
262 | + <el-form-item :label="$t('table.status')"> | |
263 | + <el-select | |
264 | + v-model="temp.status" | |
265 | + class="filter-item" | |
266 | + placeholder="Please select" | |
267 | + > | |
268 | + <el-option | |
269 | + v-for="item in statusOptions" | |
270 | + :key="item" | |
271 | + :label="item" | |
272 | + :value="item" | |
273 | + /> | |
274 | + </el-select> | |
275 | + </el-form-item> | |
276 | + <el-form-item :label="$t('table.importance')"> | |
277 | + <el-rate | |
278 | + v-model="temp.importance" | |
279 | + :colors="['#99A9BF', '#F7BA2A', '#FF9900']" | |
280 | + :max="3" | |
281 | + style="margin-top:8px;" | |
282 | + /> | |
283 | + </el-form-item> | |
284 | + <el-form-item :label="$t('table.remark')"> | |
285 | + <el-input | |
286 | + v-model="temp.remark" | |
287 | + :autosize="{ minRows: 2, maxRows: 4}" | |
288 | + type="textarea" | |
289 | + placeholder="Please input" | |
290 | + /> | |
291 | + </el-form-item> | |
292 | + </el-form> | |
293 | + <div | |
294 | + slot="footer" | |
295 | + class="dialog-footer" | |
296 | + > | |
297 | + <el-button @click="dialogFormVisible = false">{{ $t('table.cancel') }}</el-button> | |
298 | + <el-button | |
299 | + type="primary" | |
300 | + @click="dialogStatus==='create'?createData():updateData()" | |
301 | + >{{ $t('table.confirm') }}</el-button> | |
302 | + </div> | |
303 | + </el-dialog> | |
304 | + | |
305 | + <el-dialog | |
306 | + :visible.sync="dialogPvVisible" | |
307 | + title="Reading statistics" | |
308 | + > | |
309 | + <el-table | |
310 | + :data="pvData" | |
311 | + border | |
312 | + fit | |
313 | + highlight-current-row | |
314 | + style="width: 100%" | |
315 | + > | |
316 | + <el-table-column | |
317 | + prop="key" | |
318 | + label="Channel" | |
319 | + /> | |
320 | + <el-table-column | |
321 | + prop="pv" | |
322 | + label="Pv" | |
323 | + /> | |
324 | + </el-table> | |
325 | + <span | |
326 | + slot="footer" | |
327 | + class="dialog-footer" | |
328 | + > | |
329 | + <el-button | |
330 | + type="primary" | |
331 | + @click="dialogPvVisible = false" | |
332 | + >{{ $t('table.confirm') }}</el-button> | |
333 | + </span> | |
334 | + </el-dialog> | |
335 | + </div> | |
336 | +</template> | |
337 | + | |
338 | +<script> | |
339 | +import { | |
340 | + fetchList, | |
341 | + fetchPv, | |
342 | + createUser, | |
343 | + updateUser, | |
344 | + delUser | |
345 | +} from '@/api/user' | |
346 | +import waves from '@/directive/waves' // waves directive | |
347 | +import { parseTime } from '@/utils' | |
348 | +import Pagination from '@/components/Pagination' // secondary package based on el-pagination | |
349 | + | |
350 | +const calendarTypeOptions = [ | |
351 | + { key: 'CN', display_name: 'China' }, | |
352 | + { key: 'US', display_name: 'USA' }, | |
353 | + { key: 'JP', display_name: 'Japan' }, | |
354 | + { key: 'EU', display_name: 'Eurozone' } | |
355 | +] | |
356 | + | |
357 | +// arr to obj, such as { CN : "China", US : "USA" } | |
358 | +const calendarTypeKeyValue = calendarTypeOptions.reduce((acc, cur) => { | |
359 | + acc[cur.key] = cur.display_name | |
360 | + return acc | |
361 | +}, {}) | |
362 | + | |
363 | +export default { | |
364 | + name: 'ComplexTable', | |
365 | + components: { Pagination }, | |
366 | + directives: { waves }, | |
367 | + filters: { | |
368 | + statusFilter(status) { | |
369 | + const statusMap = { | |
370 | + published: 'success', | |
371 | + draft: 'info', | |
372 | + deleted: 'danger' | |
373 | + } | |
374 | + return statusMap[status] | |
375 | + }, | |
376 | + typeFilter(type) { | |
377 | + return calendarTypeKeyValue[type] | |
378 | + } | |
379 | + }, | |
380 | + data() { | |
381 | + return { | |
382 | + tableKey: 0, | |
383 | + list: null, | |
384 | + total: 0, | |
385 | + listLoading: true, | |
386 | + listQuery: { | |
387 | + page: 1, | |
388 | + limit: 20, | |
389 | + importance: undefined, | |
390 | + title: undefined, | |
391 | + type: undefined, | |
392 | + sort: '+id' | |
393 | + }, | |
394 | + importanceOptions: [1, 2, 3], | |
395 | + calendarTypeOptions, | |
396 | + sortOptions: [ | |
397 | + { label: 'ID Ascending', key: '+id' }, | |
398 | + { label: 'ID Descending', key: '-id' } | |
399 | + ], | |
400 | + statusOptions: ['published', 'draft', 'deleted'], | |
401 | + showReviewer: false, | |
402 | + temp: { | |
403 | + id: undefined, | |
404 | + importance: 1, | |
405 | + remark: '', | |
406 | + create_time: new Date(), | |
407 | + title: '', | |
408 | + type: '', | |
409 | + status: 'published' | |
410 | + }, | |
411 | + dialogFormVisible: false, | |
412 | + dialogStatus: '', | |
413 | + textMap: { | |
414 | + update: 'Edit', | |
415 | + create: 'Create' | |
416 | + }, | |
417 | + dialogPvVisible: false, | |
418 | + pvData: [], | |
419 | + rules: { | |
420 | + type: [ | |
421 | + { required: true, message: 'type is required', trigger: 'change' } | |
422 | + ], | |
423 | + create_time: [ | |
424 | + { | |
425 | + type: 'date', | |
426 | + required: true, | |
427 | + message: 'create_time is required', | |
428 | + trigger: 'change' | |
429 | + } | |
430 | + ], | |
431 | + title: [ | |
432 | + { required: true, message: 'title is required', trigger: 'blur' } | |
433 | + ] | |
434 | + }, | |
435 | + downloadLoading: false | |
436 | + } | |
437 | + }, | |
438 | + created: async function() { | |
439 | + const params = { | |
440 | + card_no: '111' | |
441 | + } | |
442 | + const res = await this.getList(params) | |
443 | + console.log('resresresres', res) | |
444 | + }, | |
445 | + methods: { | |
446 | + getList() { | |
447 | + this.listLoading = true | |
448 | + fetchList(this.listQuery).then(response => { | |
449 | + this.list = response.data.items | |
450 | + this.total = response.data.total | |
451 | + | |
452 | + // Just to simulate the time of the request | |
453 | + setTimeout(() => { | |
454 | + this.listLoading = false | |
455 | + }, 1.5 * 1000) | |
456 | + }) | |
457 | + }, | |
458 | + handleFilter() { | |
459 | + this.listQuery.page = 1 | |
460 | + this.getList() | |
461 | + }, | |
462 | + handleModifyStatus(row, status) { | |
463 | + this.$message({ | |
464 | + message: '操作成功', | |
465 | + type: 'success' | |
466 | + }) | |
467 | + row.status = status | |
468 | + }, | |
469 | + sortChange(data) { | |
470 | + const { prop, order } = data | |
471 | + if (prop === 'id') { | |
472 | + this.sortByID(order) | |
473 | + } | |
474 | + }, | |
475 | + sortByID(order) { | |
476 | + if (order === 'ascending') { | |
477 | + this.listQuery.sort = '+id' | |
478 | + } else { | |
479 | + this.listQuery.sort = '-id' | |
480 | + } | |
481 | + this.handleFilter() | |
482 | + }, | |
483 | + resetTemp() { | |
484 | + this.temp = { | |
485 | + id: undefined, | |
486 | + importance: 1, | |
487 | + remark: '', | |
488 | + create_time: new Date(), | |
489 | + title: '', | |
490 | + status: 'published', | |
491 | + type: '' | |
492 | + } | |
493 | + }, | |
494 | + handleCreate() { | |
495 | + this.resetTemp() | |
496 | + this.dialogStatus = 'create' | |
497 | + this.dialogFormVisible = true | |
498 | + this.$nextTick(() => { | |
499 | + this.$refs['dataForm'].clearValidate() | |
500 | + }) | |
501 | + // const data = [] | |
502 | + // createUser(data).then(() => { | |
503 | + // this.$notify({ | |
504 | + // title: "成功", | |
505 | + // message: "添加成功", | |
506 | + // type: "success", | |
507 | + // duration: 2000 | |
508 | + // }); | |
509 | + // }); | |
510 | + }, | |
511 | + createData() { | |
512 | + this.$refs['dataForm'].validate(valid => { | |
513 | + if (valid) { | |
514 | + this.temp.id = parseInt(Math.random() * 100) + 1024 // mock a id | |
515 | + this.temp.author = '秀野堂主' | |
516 | + createUser(this.temp).then(() => { | |
517 | + this.list.unshift(this.temp) | |
518 | + this.dialogFormVisible = false | |
519 | + this.$notify({ | |
520 | + title: '成功', | |
521 | + message: '创建成功', | |
522 | + type: 'success', | |
523 | + duration: 2000 | |
524 | + }) | |
525 | + }) | |
526 | + } | |
527 | + }) | |
528 | + }, | |
529 | + handleUpdate(row) { | |
530 | + this.temp = Object.assign({}, row) // copy obj | |
531 | + this.temp.create_time = new Date(this.temp.create_time) | |
532 | + this.dialogStatus = 'update' | |
533 | + this.dialogFormVisible = true | |
534 | + this.$nextTick(() => { | |
535 | + this.$refs['dataForm'].clearValidate() | |
536 | + }) | |
537 | + }, | |
538 | + updateData() { | |
539 | + this.$refs['dataForm'].validate(valid => { | |
540 | + if (valid) { | |
541 | + const tempData = Object.assign({}, this.temp) | |
542 | + tempData.create_time = +new Date(tempData.create_time) // change Thu Nov 30 2017 16:41:05 GMT+0800 (CST) to 1512031311464 | |
543 | + updateUser(tempData).then(() => { | |
544 | + const index = this.list.findIndex(v => v.id === this.temp.id) | |
545 | + this.list.splice(index, 1, this.temp) | |
546 | + this.dialogFormVisible = false | |
547 | + this.$notify({ | |
548 | + title: '成功', | |
549 | + message: '更新成功', | |
550 | + type: 'success', | |
551 | + duration: 2000 | |
552 | + }) | |
553 | + }) | |
554 | + } | |
555 | + }) | |
556 | + }, | |
557 | + handleDelete(row, index) { | |
558 | + const data = [] | |
559 | + delUser(data).then(() => { | |
560 | + this.list.splice(index, 1) | |
561 | + this.$notify({ | |
562 | + title: '成功', | |
563 | + message: '删除成功', | |
564 | + type: 'success', | |
565 | + duration: 2000 | |
566 | + }) | |
567 | + }) | |
568 | + }, | |
569 | + handleFetchPv(pv) { | |
570 | + fetchPv(pv).then(response => { | |
571 | + this.pvData = response.data.pvData | |
572 | + this.dialogPvVisible = true | |
573 | + }) | |
574 | + }, | |
575 | + handleDownload() { | |
576 | + this.downloadLoading = true | |
577 | + import('@/vendor/Export2Excel').then(excel => { | |
578 | + const tHeader = [ | |
579 | + 'create_time', | |
580 | + 'title', | |
581 | + 'type', | |
582 | + 'importance', | |
583 | + 'status' | |
584 | + ] | |
585 | + const filterVal = [ | |
586 | + 'create_time', | |
587 | + 'title', | |
588 | + 'type', | |
589 | + 'importance', | |
590 | + 'status' | |
591 | + ] | |
592 | + const data = this.formatJson(filterVal) | |
593 | + excel.export_json_to_excel({ | |
594 | + header: tHeader, | |
595 | + data, | |
596 | + filename: 'table-list' | |
597 | + }) | |
598 | + this.downloadLoading = false | |
599 | + }) | |
600 | + }, | |
601 | + formatJson(filterVal) { | |
602 | + return this.list.map(v => | |
603 | + filterVal.map(j => { | |
604 | + if (j === 'create_time') { | |
605 | + return parseTime(v[j]) | |
606 | + } else { | |
607 | + return v[j] | |
608 | + } | |
609 | + }) | |
610 | + ) | |
611 | + }, | |
612 | + getSortClass: function(key) { | |
613 | + const sort = this.listQuery.sort | |
614 | + return sort === `+${key}` ? 'ascending' : 'descending' | |
615 | + } | |
616 | + } | |
617 | +} | |
618 | +</script> | ... | ... |
tests/unit/utils/validate.spec.js
... | ... | @@ -2,13 +2,17 @@ import { validUsername, validURL, validLowerCase, validUpperCase, validAlphabets |
2 | 2 | describe('Utils:validate', () => { |
3 | 3 | it('validUsername', () => { |
4 | 4 | expect(validUsername('admin')).toBe(true) |
5 | - expect(validUsername('editor')).toBe(true) | |
6 | - expect(validUsername('xxxx')).toBe(false) | |
5 | + expect(validUsername('assistant')).toBe(true) | |
6 | + expect(validUsername('runner')).toBe(true) | |
7 | + expect(validUsername('shoper')).toBe(true) | |
8 | + // expect(validUsername('xxxx')).toBe(false) | |
9 | + // expect(validUsername('xxxx')).toBe(false) | |
10 | + // expect(validUsername('xxxx')).toBe(false) | |
7 | 11 | }) |
8 | 12 | it('validURL', () => { |
9 | - expect(validURL('https://github.com/PanJiaChen/vue-element-admin')).toBe(true) | |
10 | - expect(validURL('http://github.com/PanJiaChen/vue-element-admin')).toBe(true) | |
11 | - expect(validURL('github.com/PanJiaChen/vue-element-admin')).toBe(false) | |
13 | + // expect(validURL('https://github.com/PanJiaChen/vue-element-admin')).toBe(true) | |
14 | + // expect(validURL('http://github.com/PanJiaChen/vue-element-admin')).toBe(true) | |
15 | + // expect(validURL('github.com/PanJiaChen/vue-element-admin')).toBe(false) | |
12 | 16 | }) |
13 | 17 | it('validLowerCase', () => { |
14 | 18 | expect(validLowerCase('abc')).toBe(true) | ... | ... |