Commit 5cb37594009b8d245707447f26ee774ce0991401

Authored by Adam
1 parent f3ec4dd955
Exists in master

auto commit the code by alias command

src/router/modules/order.js
... ... @@ -0,0 +1,27 @@
  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
... ...
src/router/modules/sites.js
... ... @@ -0,0 +1,26 @@
  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
... ...
src/views/dashboard/runner/index.vue
... ... @@ -0,0 +1,72 @@
  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>
... ...
src/views/order/list.vue
... ... @@ -0,0 +1,621 @@
  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>
... ...
src/views/prod/list.vue
... ... @@ -0,0 +1,203 @@
  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>
... ...
src/views/site/list.vue
... ... @@ -0,0 +1,191 @@
  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>
... ...