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 |