Commit 5cb37594009b8d245707447f26ee774ce0991401
1 parent
f3ec4dd955
Exists in
master
auto commit the code by alias command
Showing
6 changed files
with
1140 additions
and
0 deletions
Show diff stats
src/router/modules/order.js
File was created | 1 | /** When your routing table is too long, you can split it into small modules**/ | |
2 | |||
3 | import Layout from '@/layout' | ||
4 | |||
5 | const orderRouter = { | ||
6 | path: '/orders', | ||
7 | component: Layout, | ||
8 | redirect: '/order/page', | ||
9 | alwaysShow: true, // will always show the root menu | ||
10 | name: 'Order', | ||
11 | meta: { | ||
12 | title: 'orders', | ||
13 | icon: 'shopping', | ||
14 | roles: ['admin', 'assistant', 'runner', 'shoper'] // you can set roles in root nav | ||
15 | }, | ||
16 | children: [{ | ||
17 | path: 'page', | ||
18 | component: () => import('@/views/order/list'), | ||
19 | name: 'OrderList', | ||
20 | meta: { | ||
21 | title: 'OrderList', | ||
22 | roles: ['admin', 'assistant', 'runner', 'shoper'] // or you can only set roles in sub nav | ||
23 | } | ||
24 | }] | ||
25 | } | ||
26 | |||
27 | export default orderRouter | ||
28 |
src/router/modules/sites.js
File was created | 1 | import Layout from '@/layout' | |
2 | |||
3 | const sitesRouter = { | ||
4 | path: '/sites', | ||
5 | component: Layout, | ||
6 | redirect: '/site/page', | ||
7 | alwaysShow: true, // will always show the root menu | ||
8 | name: 'Site', | ||
9 | meta: { | ||
10 | title: 'sites', | ||
11 | icon: 'people', | ||
12 | roles: ['admin', 'assistant', 'runner'] // you can set roles in root nav | ||
13 | }, | ||
14 | children: [{ | ||
15 | path: 'page', | ||
16 | component: () => import('@/views/site/list'), | ||
17 | name: 'SiteList', | ||
18 | meta: { | ||
19 | title: '站点列表', | ||
20 | roles: ['admin', 'runner'] | ||
21 | |||
22 | } | ||
23 | }] | ||
24 | } | ||
25 | |||
26 | export default sitesRouter | ||
27 |
src/views/dashboard/runner/index.vue
File was created | 1 | <template> | |
2 | <div class="dashboard-editor-container"> | ||
3 | <div class="clearfix"> | ||
4 | <pan-thumb :image="avatar" style="float: left"> | ||
5 | Your roles: | ||
6 | <span v-for="item in roles" :key="item" class="pan-info-roles">{{ item }}</span> | ||
7 | </pan-thumb> | ||
8 | <github-corner style="position: absolute; top: 0px; border: 0; right: 0;" /> | ||
9 | <div class="info-container"> | ||
10 | <span class="display_name">{{ name }}</span> | ||
11 | <span style="font-size:20px;padding-top:20px;display:inline-block;">{{ roles }}'s Dashboard</span> | ||
12 | </div> | ||
13 | </div> | ||
14 | <div> | ||
15 | <img :src="emptyGif" class="emptyGif"> | ||
16 | </div> | ||
17 | </div> | ||
18 | </template> | ||
19 | |||
20 | <script> | ||
21 | import { mapGetters } from 'vuex' | ||
22 | import PanThumb from '@/components/PanThumb' | ||
23 | // import GithubCorner from '@/components/GithubCorner' | ||
24 | |||
25 | export default { | ||
26 | name: 'DashboardEditor', | ||
27 | // components: { PanThumb, GithubCorner }, | ||
28 | components: { PanThumb }, | ||
29 | data() { | ||
30 | return { | ||
31 | emptyGif: | ||
32 | 'https://wpimg.wallstcn.com/0e03b7da-db9e-4819-ba10-9016ddfdaed3' | ||
33 | } | ||
34 | }, | ||
35 | computed: { | ||
36 | ...mapGetters(['name', 'avatar', 'roles']) | ||
37 | } | ||
38 | } | ||
39 | </script> | ||
40 | |||
41 | <style lang="scss" scoped> | ||
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; | ||
56 | display: block; | ||
57 | } | ||
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; | ||
69 | } | ||
70 | } | ||
71 | } | ||
72 | </style> | ||
73 |
src/views/order/list.vue
File was created | 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.id')" | ||
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.id }}</span> | ||
110 | </template> | ||
111 | </el-table-column> | ||
112 | <el-table-column | ||
113 | :label="$t('table.date')" | ||
114 | width="150px" | ||
115 | align="center" | ||
116 | > | ||
117 | <template slot-scope="{row}"> | ||
118 | <span>{{ row.timestamp | parseTime('{y}-{m}-{d} {h}:{i}') }}</span> | ||
119 | </template> | ||
120 | </el-table-column> | ||
121 | <el-table-column | ||
122 | :label="$t('table.title')" | ||
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 | <el-tag>{{ row.type | typeFilter }}</el-tag> | ||
131 | </template> | ||
132 | </el-table-column> | ||
133 | <el-table-column | ||
134 | :label="$t('table.author')" | ||
135 | width="110px" | ||
136 | align="center" | ||
137 | > | ||
138 | <template slot-scope="{row}"> | ||
139 | <span>{{ row.author }}</span> | ||
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.readings')" | ||
167 | align="center" | ||
168 | width="95" | ||
169 | > | ||
170 | <template slot-scope="{row}"> | ||
171 | <span | ||
172 | v-if="row.pageviews" | ||
173 | class="link-type" | ||
174 | @click="handleFetchPv(row.pageviews)" | ||
175 | >{{ row.pageviews }}</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"> | ||
186 | {{ row.status }} | ||
187 | </el-tag> | ||
188 | </template> | ||
189 | </el-table-column> | ||
190 | <el-table-column | ||
191 | :label="$t('table.actions')" | ||
192 | align="center" | ||
193 | width="230" | ||
194 | class-name="small-padding fixed-width" | ||
195 | > | ||
196 | <template slot-scope="{row,$index}"> | ||
197 | <el-button | ||
198 | type="primary" | ||
199 | size="mini" | ||
200 | @click="handleUpdate(row)" | ||
201 | > | ||
202 | {{ $t('table.edit') }} | ||
203 | </el-button> | ||
204 | <el-button | ||
205 | v-if="row.status!='published'" | ||
206 | size="mini" | ||
207 | type="success" | ||
208 | @click="handleModifyStatus(row,'published')" | ||
209 | > | ||
210 | {{ $t('table.publish') }} | ||
211 | </el-button> | ||
212 | <el-button | ||
213 | v-if="row.status!='draft'" | ||
214 | size="mini" | ||
215 | @click="handleModifyStatus(row,'draft')" | ||
216 | > | ||
217 | {{ $t('table.draft') }} | ||
218 | </el-button> | ||
219 | <el-button | ||
220 | v-if="row.status!='deleted'" | ||
221 | size="mini" | ||
222 | type="danger" | ||
223 | @click="handleDelete(row,$index)" | ||
224 | > | ||
225 | {{ $t('table.delete') }} | ||
226 | </el-button> | ||
227 | </template> | ||
228 | </el-table-column> | ||
229 | </el-table> | ||
230 | |||
231 | <pagination | ||
232 | v-show="total>0" | ||
233 | :total="total" | ||
234 | :page.sync="listQuery.page" | ||
235 | :limit.sync="listQuery.limit" | ||
236 | @pagination="getList" | ||
237 | /> | ||
238 | |||
239 | <el-dialog | ||
240 | :title="textMap[dialogStatus]" | ||
241 | :visible.sync="dialogFormVisible" | ||
242 | > | ||
243 | <el-form | ||
244 | ref="dataForm" | ||
245 | :rules="rules" | ||
246 | :model="temp" | ||
247 | label-position="left" | ||
248 | label-width="70px" | ||
249 | style="width: 400px; margin-left:50px;" | ||
250 | > | ||
251 | <el-form-item | ||
252 | :label="$t('table.type')" | ||
253 | prop="type" | ||
254 | > | ||
255 | <el-select | ||
256 | v-model="temp.type" | ||
257 | class="filter-item" | ||
258 | placeholder="Please select" | ||
259 | > | ||
260 | <el-option | ||
261 | v-for="item in calendarTypeOptions" | ||
262 | :key="item.key" | ||
263 | :label="item.display_name" | ||
264 | :value="item.key" | ||
265 | /> | ||
266 | </el-select> | ||
267 | </el-form-item> | ||
268 | <el-form-item | ||
269 | :label="$t('table.date')" | ||
270 | prop="timestamp" | ||
271 | > | ||
272 | <el-date-picker | ||
273 | v-model="temp.timestamp" | ||
274 | type="datetime" | ||
275 | placeholder="Please pick a date" | ||
276 | /> | ||
277 | </el-form-item> | ||
278 | <el-form-item | ||
279 | :label="$t('table.title')" | ||
280 | prop="title" | ||
281 | > | ||
282 | <el-input v-model="temp.title" /> | ||
283 | </el-form-item> | ||
284 | <el-form-item :label="$t('table.status')"> | ||
285 | <el-select | ||
286 | v-model="temp.status" | ||
287 | class="filter-item" | ||
288 | placeholder="Please select" | ||
289 | > | ||
290 | <el-option | ||
291 | v-for="item in statusOptions" | ||
292 | :key="item" | ||
293 | :label="item" | ||
294 | :value="item" | ||
295 | /> | ||
296 | </el-select> | ||
297 | </el-form-item> | ||
298 | <el-form-item :label="$t('table.importance')"> | ||
299 | <el-rate | ||
300 | v-model="temp.importance" | ||
301 | :colors="['#99A9BF', '#F7BA2A', '#FF9900']" | ||
302 | :max="3" | ||
303 | style="margin-top:8px;" | ||
304 | /> | ||
305 | </el-form-item> | ||
306 | <el-form-item :label="$t('table.remark')"> | ||
307 | <el-input | ||
308 | v-model="temp.remark" | ||
309 | :autosize="{ minRows: 2, maxRows: 4}" | ||
310 | type="textarea" | ||
311 | placeholder="Please input" | ||
312 | /> | ||
313 | </el-form-item> | ||
314 | </el-form> | ||
315 | <div | ||
316 | slot="footer" | ||
317 | class="dialog-footer" | ||
318 | > | ||
319 | <el-button @click="dialogFormVisible = false"> | ||
320 | {{ $t('table.cancel') }} | ||
321 | </el-button> | ||
322 | <el-button | ||
323 | type="primary" | ||
324 | @click="dialogStatus==='create'?createData():updateData()" | ||
325 | > | ||
326 | {{ $t('table.confirm') }} | ||
327 | </el-button> | ||
328 | </div> | ||
329 | </el-dialog> | ||
330 | |||
331 | <el-dialog | ||
332 | :visible.sync="dialogPvVisible" | ||
333 | title="Reading statistics" | ||
334 | > | ||
335 | <el-table | ||
336 | :data="pvData" | ||
337 | border | ||
338 | fit | ||
339 | highlight-current-row | ||
340 | style="width: 100%" | ||
341 | > | ||
342 | <el-table-column | ||
343 | prop="key" | ||
344 | label="Channel" | ||
345 | /> | ||
346 | <el-table-column | ||
347 | prop="pv" | ||
348 | label="Pv" | ||
349 | /> | ||
350 | </el-table> | ||
351 | <span | ||
352 | slot="footer" | ||
353 | class="dialog-footer" | ||
354 | > | ||
355 | <el-button | ||
356 | type="primary" | ||
357 | @click="dialogPvVisible = false" | ||
358 | >{{ $t('table.confirm') }}</el-button> | ||
359 | </span> | ||
360 | </el-dialog> | ||
361 | </div> | ||
362 | </template> | ||
363 | |||
364 | <script> | ||
365 | import { | ||
366 | fetchList, | ||
367 | fetchPv, | ||
368 | createArticle, | ||
369 | updateArticle | ||
370 | } from '@/api/article' | ||
371 | import waves from '@/directive/waves' // waves directive | ||
372 | import { parseTime } from '@/utils' | ||
373 | import Pagination from '@/components/Pagination' // secondary package based on el-pagination | ||
374 | |||
375 | const calendarTypeOptions = [ | ||
376 | { key: 'CN', display_name: 'China' }, | ||
377 | { key: 'US', display_name: 'USA' }, | ||
378 | { key: 'JP', display_name: 'Japan' }, | ||
379 | { key: 'EU', display_name: 'Eurozone' } | ||
380 | ] | ||
381 | |||
382 | // arr to obj, such as { CN : "China", US : "USA" } | ||
383 | const calendarTypeKeyValue = calendarTypeOptions.reduce((acc, cur) => { | ||
384 | acc[cur.key] = cur.display_name | ||
385 | return acc | ||
386 | }, {}) | ||
387 | |||
388 | export default { | ||
389 | name: 'ComplexTable', | ||
390 | components: { Pagination }, | ||
391 | directives: { waves }, | ||
392 | filters: { | ||
393 | statusFilter(status) { | ||
394 | const statusMap = { | ||
395 | published: 'success', | ||
396 | draft: 'info', | ||
397 | deleted: 'danger' | ||
398 | } | ||
399 | return statusMap[status] | ||
400 | }, | ||
401 | typeFilter(type) { | ||
402 | return calendarTypeKeyValue[type] | ||
403 | } | ||
404 | }, | ||
405 | data() { | ||
406 | return { | ||
407 | tableKey: 0, | ||
408 | list: null, | ||
409 | total: 0, | ||
410 | listLoading: true, | ||
411 | listQuery: { | ||
412 | page: 1, | ||
413 | limit: 20, | ||
414 | importance: undefined, | ||
415 | title: undefined, | ||
416 | type: undefined, | ||
417 | sort: '+id' | ||
418 | }, | ||
419 | importanceOptions: [1, 2, 3], | ||
420 | calendarTypeOptions, | ||
421 | sortOptions: [ | ||
422 | { label: 'ID Ascending', key: '+id' }, | ||
423 | { label: 'ID Descending', key: '-id' } | ||
424 | ], | ||
425 | statusOptions: ['published', 'draft', 'deleted'], | ||
426 | showReviewer: false, | ||
427 | temp: { | ||
428 | id: undefined, | ||
429 | importance: 1, | ||
430 | remark: '', | ||
431 | timestamp: new Date(), | ||
432 | title: '', | ||
433 | type: '', | ||
434 | status: 'published' | ||
435 | }, | ||
436 | dialogFormVisible: false, | ||
437 | dialogStatus: '', | ||
438 | textMap: { | ||
439 | update: 'Edit', | ||
440 | create: 'Create' | ||
441 | }, | ||
442 | dialogPvVisible: false, | ||
443 | pvData: [], | ||
444 | rules: { | ||
445 | type: [ | ||
446 | { required: true, message: 'type is required', trigger: 'change' } | ||
447 | ], | ||
448 | timestamp: [ | ||
449 | { | ||
450 | type: 'date', | ||
451 | required: true, | ||
452 | message: 'timestamp is required', | ||
453 | trigger: 'change' | ||
454 | } | ||
455 | ], | ||
456 | title: [ | ||
457 | { required: true, message: 'title is required', trigger: 'blur' } | ||
458 | ] | ||
459 | }, | ||
460 | downloadLoading: false | ||
461 | } | ||
462 | }, | ||
463 | created() { | ||
464 | this.getList() | ||
465 | }, | ||
466 | methods: { | ||
467 | getList() { | ||
468 | this.listLoading = true | ||
469 | fetchList(this.listQuery).then(response => { | ||
470 | this.list = response.data.items | ||
471 | this.total = response.data.total | ||
472 | |||
473 | // Just to simulate the time of the request | ||
474 | setTimeout(() => { | ||
475 | this.listLoading = false | ||
476 | }, 1.5 * 1000) | ||
477 | }) | ||
478 | }, | ||
479 | handleFilter() { | ||
480 | this.listQuery.page = 1 | ||
481 | this.getList() | ||
482 | }, | ||
483 | handleModifyStatus(row, status) { | ||
484 | this.$message({ | ||
485 | message: '操作成功', | ||
486 | type: 'success' | ||
487 | }) | ||
488 | row.status = status | ||
489 | }, | ||
490 | sortChange(data) { | ||
491 | const { prop, order } = data | ||
492 | if (prop === 'id') { | ||
493 | this.sortByID(order) | ||
494 | } | ||
495 | }, | ||
496 | sortByID(order) { | ||
497 | if (order === 'ascending') { | ||
498 | this.listQuery.sort = '+id' | ||
499 | } else { | ||
500 | this.listQuery.sort = '-id' | ||
501 | } | ||
502 | this.handleFilter() | ||
503 | }, | ||
504 | resetTemp() { | ||
505 | this.temp = { | ||
506 | id: undefined, | ||
507 | importance: 1, | ||
508 | remark: '', | ||
509 | timestamp: new Date(), | ||
510 | title: '', | ||
511 | status: 'published', | ||
512 | type: '' | ||
513 | } | ||
514 | }, | ||
515 | handleCreate() { | ||
516 | this.resetTemp() | ||
517 | this.dialogStatus = 'create' | ||
518 | this.dialogFormVisible = true | ||
519 | this.$nextTick(() => { | ||
520 | this.$refs['dataForm'].clearValidate() | ||
521 | }) | ||
522 | }, | ||
523 | createData() { | ||
524 | this.$refs['dataForm'].validate(valid => { | ||
525 | if (valid) { | ||
526 | this.temp.id = parseInt(Math.random() * 100) + 1024 // mock a id | ||
527 | this.temp.author = '秀野堂主' | ||
528 | createArticle(this.temp).then(() => { | ||
529 | this.list.unshift(this.temp) | ||
530 | this.dialogFormVisible = false | ||
531 | this.$notify({ | ||
532 | title: '成功', | ||
533 | message: '创建成功', | ||
534 | type: 'success', | ||
535 | duration: 2000 | ||
536 | }) | ||
537 | }) | ||
538 | } | ||
539 | }) | ||
540 | }, | ||
541 | handleUpdate(row) { | ||
542 | this.temp = Object.assign({}, row) // copy obj | ||
543 | this.temp.timestamp = new Date(this.temp.timestamp) | ||
544 | this.dialogStatus = 'update' | ||
545 | this.dialogFormVisible = true | ||
546 | this.$nextTick(() => { | ||
547 | this.$refs['dataForm'].clearValidate() | ||
548 | }) | ||
549 | }, | ||
550 | updateData() { | ||
551 | this.$refs['dataForm'].validate(valid => { | ||
552 | if (valid) { | ||
553 | const tempData = Object.assign({}, this.temp) | ||
554 | tempData.timestamp = +new Date(tempData.timestamp) // change Thu Nov 30 2017 16:41:05 GMT+0800 (CST) to 1512031311464 | ||
555 | updateArticle(tempData).then(() => { | ||
556 | const index = this.list.findIndex(v => v.id === this.temp.id) | ||
557 | this.list.splice(index, 1, this.temp) | ||
558 | this.dialogFormVisible = false | ||
559 | this.$notify({ | ||
560 | title: '成功', | ||
561 | message: '更新成功', | ||
562 | type: 'success', | ||
563 | duration: 2000 | ||
564 | }) | ||
565 | }) | ||
566 | } | ||
567 | }) | ||
568 | }, | ||
569 | handleDelete(row, index) { | ||
570 | this.$notify({ | ||
571 | title: '成功', | ||
572 | message: '删除成功', | ||
573 | type: 'success', | ||
574 | duration: 2000 | ||
575 | }) | ||
576 | this.list.splice(index, 1) | ||
577 | }, | ||
578 | handleFetchPv(pv) { | ||
579 | fetchPv(pv).then(response => { | ||
580 | this.pvData = response.data.pvData | ||
581 | this.dialogPvVisible = true | ||
582 | }) | ||
583 | }, | ||
584 | handleDownload() { | ||
585 | this.downloadLoading = true | ||
586 | import('@/vendor/Export2Excel').then(excel => { | ||
587 | const tHeader = ['timestamp', 'title', 'type', 'importance', 'status'] | ||
588 | const filterVal = [ | ||
589 | 'timestamp', | ||
590 | 'title', | ||
591 | 'type', | ||
592 | 'importance', | ||
593 | 'status' | ||
594 | ] | ||
595 | const data = this.formatJson(filterVal) | ||
596 | excel.export_json_to_excel({ | ||
597 | header: tHeader, | ||
598 | data, | ||
599 | filename: 'table-list' | ||
600 | }) | ||
601 | this.downloadLoading = false | ||
602 | }) | ||
603 | }, | ||
604 | formatJson(filterVal) { | ||
605 | return this.list.map(v => | ||
606 | filterVal.map(j => { | ||
607 | if (j === 'timestamp') { | ||
608 | return parseTime(v[j]) | ||
609 | } else { | ||
610 | return v[j] | ||
611 | } | ||
612 | }) | ||
613 | ) | ||
614 | }, | ||
615 | getSortClass: function(key) { | ||
616 | const sort = this.listQuery.sort | ||
617 | return sort === `+${key}` ? 'ascending' : 'descending' | ||
618 | } | ||
619 | } | ||
620 | } | ||
621 | </script> | ||
622 |
src/views/prod/list.vue
File was created | 1 | <template> | |
2 | <div class="app-container"> | ||
3 | <!-- Note that row-key is necessary to get a correct row order. --> | ||
4 | <el-table | ||
5 | ref="dragTable" | ||
6 | v-loading="listLoading" | ||
7 | :data="list" | ||
8 | row-key="id" | ||
9 | border | ||
10 | fit | ||
11 | highlight-current-row | ||
12 | style="width: 100%" | ||
13 | > | ||
14 | <el-table-column | ||
15 | align="center" | ||
16 | label="ID" | ||
17 | width="65" | ||
18 | > | ||
19 | <template slot-scope="{row}"> | ||
20 | <span>{{ row.id }}</span> | ||
21 | </template> | ||
22 | </el-table-column> | ||
23 | |||
24 | <el-table-column | ||
25 | width="180px" | ||
26 | align="center" | ||
27 | label="Date" | ||
28 | > | ||
29 | <template slot-scope="{row}"> | ||
30 | <span>{{ row.timestamp | parseTime('{y}-{m}-{d} {h}:{i}') }}</span> | ||
31 | </template> | ||
32 | </el-table-column> | ||
33 | |||
34 | <el-table-column | ||
35 | min-width="300px" | ||
36 | label="Title" | ||
37 | > | ||
38 | <template slot-scope="{row}"> | ||
39 | <span>{{ row.title }}</span> | ||
40 | </template> | ||
41 | </el-table-column> | ||
42 | |||
43 | <el-table-column | ||
44 | width="110px" | ||
45 | align="center" | ||
46 | label="Author" | ||
47 | > | ||
48 | <template slot-scope="{row}"> | ||
49 | <span>{{ row.author }}</span> | ||
50 | </template> | ||
51 | </el-table-column> | ||
52 | |||
53 | <el-table-column | ||
54 | width="100px" | ||
55 | label="Importance" | ||
56 | > | ||
57 | <template slot-scope="{row}"> | ||
58 | <svg-icon | ||
59 | v-for="n in + row.importance" | ||
60 | :key="n" | ||
61 | icon-class="star" | ||
62 | class="icon-star" | ||
63 | /> | ||
64 | </template> | ||
65 | </el-table-column> | ||
66 | |||
67 | <el-table-column | ||
68 | align="center" | ||
69 | label="Readings" | ||
70 | width="95" | ||
71 | > | ||
72 | <template slot-scope="{row}"> | ||
73 | <span>{{ row.pageviews }}</span> | ||
74 | </template> | ||
75 | </el-table-column> | ||
76 | |||
77 | <el-table-column | ||
78 | class-name="status-col" | ||
79 | label="Status" | ||
80 | width="110" | ||
81 | > | ||
82 | <template slot-scope="{row}"> | ||
83 | <el-tag :type="row.status | statusFilter"> | ||
84 | {{ row.status }} | ||
85 | </el-tag> | ||
86 | </template> | ||
87 | </el-table-column> | ||
88 | |||
89 | <el-table-column | ||
90 | align="center" | ||
91 | label="Drag" | ||
92 | width="80" | ||
93 | > | ||
94 | <template slot-scope="{}"> | ||
95 | <svg-icon | ||
96 | class="drag-handler" | ||
97 | icon-class="drag" | ||
98 | /> | ||
99 | </template> | ||
100 | </el-table-column> | ||
101 | </el-table> | ||
102 | <!-- $t is vue-i18n global function to translate lang (lang in @/lang) --> | ||
103 | <div class="show-d"> | ||
104 | <el-tag style="margin-right:12px;">{{ $t('table.dragTips1') }} :</el-tag> {{ oldList }} | ||
105 | </div> | ||
106 | <div class="show-d"> | ||
107 | <el-tag>{{ $t('table.dragTips2') }} :</el-tag> {{ newList }} | ||
108 | </div> | ||
109 | </div> | ||
110 | </template> | ||
111 | |||
112 | <script> | ||
113 | import { fetchList } from '@/api/article' | ||
114 | import Sortable from 'sortablejs' | ||
115 | |||
116 | export default { | ||
117 | name: 'DragTable', | ||
118 | filters: { | ||
119 | statusFilter(status) { | ||
120 | const statusMap = { | ||
121 | published: 'success', | ||
122 | draft: 'info', | ||
123 | deleted: 'danger' | ||
124 | } | ||
125 | return statusMap[status] | ||
126 | } | ||
127 | }, | ||
128 | data() { | ||
129 | return { | ||
130 | list: null, | ||
131 | total: null, | ||
132 | listLoading: true, | ||
133 | listQuery: { | ||
134 | page: 1, | ||
135 | limit: 10 | ||
136 | }, | ||
137 | sortable: null, | ||
138 | oldList: [], | ||
139 | newList: [] | ||
140 | } | ||
141 | }, | ||
142 | created() { | ||
143 | this.getList() | ||
144 | }, | ||
145 | methods: { | ||
146 | async getList() { | ||
147 | this.listLoading = true | ||
148 | const { data } = await fetchList(this.listQuery) | ||
149 | this.list = data.items | ||
150 | this.total = data.total | ||
151 | this.listLoading = false | ||
152 | this.oldList = this.list.map(v => v.id) | ||
153 | this.newList = this.oldList.slice() | ||
154 | this.$nextTick(() => { | ||
155 | this.setSort() | ||
156 | }) | ||
157 | }, | ||
158 | setSort() { | ||
159 | const el = this.$refs.dragTable.$el.querySelectorAll( | ||
160 | '.el-table__body-wrapper > table > tbody' | ||
161 | )[0] | ||
162 | this.sortable = Sortable.create(el, { | ||
163 | ghostClass: 'sortable-ghost', // Class name for the drop placeholder, | ||
164 | setData: function(dataTransfer) { | ||
165 | // to avoid Firefox bug | ||
166 | // Detail see : https://github.com/RubaXa/Sortable/issues/1012 | ||
167 | dataTransfer.setData('Text', '') | ||
168 | }, | ||
169 | onEnd: evt => { | ||
170 | const targetRow = this.list.splice(evt.oldIndex, 1)[0] | ||
171 | this.list.splice(evt.newIndex, 0, targetRow) | ||
172 | |||
173 | // for show the changes, you can delete in you code | ||
174 | const tempIndex = this.newList.splice(evt.oldIndex, 1)[0] | ||
175 | this.newList.splice(evt.newIndex, 0, tempIndex) | ||
176 | } | ||
177 | }) | ||
178 | } | ||
179 | } | ||
180 | } | ||
181 | </script> | ||
182 | |||
183 | <style> | ||
184 | .sortable-ghost { | ||
185 | opacity: 0.8; | ||
186 | color: #fff !important; | ||
187 | background: #42b983 !important; | ||
188 | } | ||
189 | </style> | ||
190 | |||
191 | <style scoped> | ||
192 | .icon-star { | ||
193 | margin-right: 2px; | ||
194 | } | ||
195 | .drag-handler { | ||
196 | width: 20px; | ||
197 | height: 20px; | ||
198 | cursor: pointer; | ||
199 | } | ||
200 | .show-d { | ||
201 | margin-top: 15px; | ||
202 | } | ||
203 | </style> | ||
204 |
src/views/site/list.vue
File was created | 1 | <template> | |
2 | <div class="app-container"> | ||
3 | <el-table | ||
4 | v-loading="listLoading" | ||
5 | :data="list" | ||
6 | border | ||
7 | fit | ||
8 | highlight-current-row | ||
9 | style="width: 100%" | ||
10 | > | ||
11 | <el-table-column | ||
12 | align="center" | ||
13 | label="ID" | ||
14 | width="80" | ||
15 | > | ||
16 | <template slot-scope="{row}"> | ||
17 | <span>{{ row.id }}</span> | ||
18 | </template> | ||
19 | </el-table-column> | ||
20 | |||
21 | <el-table-column | ||
22 | width="180px" | ||
23 | align="center" | ||
24 | label="Date" | ||
25 | > | ||
26 | <template slot-scope="{row}"> | ||
27 | <span>{{ row.timestamp | parseTime('{y}-{m}-{d} {h}:{i}') }}</span> | ||
28 | </template> | ||
29 | </el-table-column> | ||
30 | |||
31 | <el-table-column | ||
32 | width="120px" | ||
33 | align="center" | ||
34 | label="Author" | ||
35 | > | ||
36 | <template slot-scope="{row}"> | ||
37 | <span>{{ row.author }}</span> | ||
38 | </template> | ||
39 | </el-table-column> | ||
40 | |||
41 | <el-table-column | ||
42 | width="100px" | ||
43 | label="Importance" | ||
44 | > | ||
45 | <template slot-scope="{row}"> | ||
46 | <svg-icon | ||
47 | v-for="n in + row.importance" | ||
48 | :key="n" | ||
49 | icon-class="star" | ||
50 | class="meta-item__icon" | ||
51 | /> | ||
52 | </template> | ||
53 | </el-table-column> | ||
54 | |||
55 | <el-table-column | ||
56 | class-name="status-col" | ||
57 | label="Status" | ||
58 | width="110" | ||
59 | > | ||
60 | <template slot-scope="{row}"> | ||
61 | <el-tag :type="row.status | statusFilter"> | ||
62 | {{ row.status }} | ||
63 | </el-tag> | ||
64 | </template> | ||
65 | </el-table-column> | ||
66 | |||
67 | <el-table-column | ||
68 | min-width="300px" | ||
69 | label="Title" | ||
70 | > | ||
71 | <template slot-scope="{row}"> | ||
72 | <template v-if="row.edit"> | ||
73 | <el-input | ||
74 | v-model="row.title" | ||
75 | class="edit-input" | ||
76 | size="small" | ||
77 | /> | ||
78 | <el-button | ||
79 | class="cancel-btn" | ||
80 | size="small" | ||
81 | icon="el-icon-refresh" | ||
82 | type="warning" | ||
83 | @click="cancelEdit(row)" | ||
84 | > | ||
85 | cancel | ||
86 | </el-button> | ||
87 | </template> | ||
88 | <span v-else>{{ row.title }}</span> | ||
89 | </template> | ||
90 | </el-table-column> | ||
91 | |||
92 | <el-table-column | ||
93 | align="center" | ||
94 | label="Actions" | ||
95 | width="120" | ||
96 | > | ||
97 | <template slot-scope="{row}"> | ||
98 | <el-button | ||
99 | v-if="row.edit" | ||
100 | type="success" | ||
101 | size="small" | ||
102 | icon="el-icon-circle-check-outline" | ||
103 | @click="confirmEdit(row)" | ||
104 | > | ||
105 | Ok | ||
106 | </el-button> | ||
107 | <el-button | ||
108 | v-else | ||
109 | type="primary" | ||
110 | size="small" | ||
111 | icon="el-icon-edit" | ||
112 | @click="row.edit=!row.edit" | ||
113 | > | ||
114 | Edit | ||
115 | </el-button> | ||
116 | </template> | ||
117 | </el-table-column> | ||
118 | </el-table> | ||
119 | </div> | ||
120 | </template> | ||
121 | |||
122 | <script> | ||
123 | import { fetchList } from '@/api/article' | ||
124 | |||
125 | export default { | ||
126 | name: 'InlineEditTable', | ||
127 | filters: { | ||
128 | statusFilter(status) { | ||
129 | const statusMap = { | ||
130 | published: 'success', | ||
131 | draft: 'info', | ||
132 | deleted: 'danger' | ||
133 | } | ||
134 | return statusMap[status] | ||
135 | } | ||
136 | }, | ||
137 | data() { | ||
138 | return { | ||
139 | list: null, | ||
140 | listLoading: true, | ||
141 | listQuery: { | ||
142 | page: 1, | ||
143 | limit: 10 | ||
144 | } | ||
145 | } | ||
146 | }, | ||
147 | created() { | ||
148 | this.getList() | ||
149 | }, | ||
150 | methods: { | ||
151 | async getList() { | ||
152 | this.listLoading = true | ||
153 | const { data } = await fetchList(this.listQuery) | ||
154 | const items = data.items | ||
155 | this.list = items.map(v => { | ||
156 | this.$set(v, 'edit', false) // https://vuejs.org/v2/guide/reactivity.html | ||
157 | v.originalTitle = v.title // will be used when user click the cancel botton | ||
158 | return v | ||
159 | }) | ||
160 | this.listLoading = false | ||
161 | }, | ||
162 | cancelEdit(row) { | ||
163 | row.title = row.originalTitle | ||
164 | row.edit = false | ||
165 | this.$message({ | ||
166 | message: 'The title has been restored to the original value', | ||
167 | type: 'warning' | ||
168 | }) | ||
169 | }, | ||
170 | confirmEdit(row) { | ||
171 | row.edit = false | ||
172 | row.originalTitle = row.title | ||
173 | this.$message({ | ||
174 | message: 'The title has been edited', | ||
175 | type: 'success' | ||
176 | }) | ||
177 | } | ||
178 | } | ||
179 | } | ||
180 | </script> | ||
181 | |||
182 | <style scoped> | ||
183 | .edit-input { | ||
184 | padding-right: 100px; | ||
185 | } | ||
186 | .cancel-btn { | ||
187 | position: absolute; | ||
188 | right: 15px; | ||
189 | top: 10px; | ||
190 | } | ||
191 | </style> | ||
192 |