Commit 632adc4e authored by 智勇's avatar 智勇

状态码拦截器

parent 725147ee
...@@ -21,7 +21,7 @@ export function logout() { ...@@ -21,7 +21,7 @@ export function logout() {
export function getUserInfo(token) { export function getUserInfo(token) {
return request({ return request({
url: '/user/check_token', url: '/user/check_token_new',
method: 'get', method: 'get',
params: { token } params: { token }
}) })
......
...@@ -7,7 +7,7 @@ const dockersRouter = { ...@@ -7,7 +7,7 @@ const dockersRouter = {
redirect: 'noredirect', redirect: 'noredirect',
name: 'Dockers', name: 'Dockers',
meta: { meta: {
title: 'dockers', title: 'Dockers',
icon: 'chart' icon: 'chart'
}, },
children: [ children: [
......
import { loginByUsername, getUserInfo } from '@/api/login' import { loginByUsername, logout, getUserInfo } from '@/api/login'
// import { loginByUsername, logout, getUserInfo } from '@/api/login'
import { getToken, setToken, removeToken } from '@/utils/auth' import { getToken, setToken, removeToken } from '@/utils/auth'
const user = { const user = {
...@@ -67,7 +66,7 @@ const user = { ...@@ -67,7 +66,7 @@ const user = {
// if (response.data.code !== '0000') { // if (response.data.code !== '0000') {
// reject('Verification failed, please login again.') // reject('Verification failed, please login again.')
// } // }
const data = response const data = response.data
data.roles = ['admin'] data.roles = ['admin']
if (data.roles && data.roles.length > 0) { // 验证返回的roles是否是一个非空数组 if (data.roles && data.roles.length > 0) { // 验证返回的roles是否是一个非空数组
...@@ -101,19 +100,16 @@ const user = { ...@@ -101,19 +100,16 @@ const user = {
// 登出 // 登出
LogOut({ commit, state }) { LogOut({ commit, state }) {
commit('SET_TOKEN', '') return new Promise((resolve, reject) => {
commit('SET_ROLES', []) logout(state.token).then(() => {
removeToken() commit('SET_TOKEN', '')
// return new Promise((resolve, reject) => { commit('SET_ROLES', [])
// logout(state.token).then(() => { removeToken()
// commit('SET_TOKEN', '') resolve()
// commit('SET_ROLES', []) }).catch(error => {
// removeToken() reject(error)
// resolve() })
// }).catch(error => { })
// reject(error)
// })
// })
}, },
// 前端 登出 // 前端 登出
......
...@@ -39,15 +39,7 @@ service.interceptors.response.use( ...@@ -39,15 +39,7 @@ service.interceptors.response.use(
response => { response => {
const res = response.data const res = response.data
if (res.code !== '0000') { if (res.code !== '0000') {
Message({ if (res.status === 401) {
message: res.msg,
type: 'error',
duration: 5 * 1000
})
// 50008:非法的token; 50012:其他客户端登录了; 50014:Token 过期了;
if (res.code === 50008 || res.code === 50012 || res.code === 50014) {
// 请自行在引入 MessageBox
// import { Message, MessageBox } from 'element-ui'
MessageBox.confirm('你已被登出,可以取消继续留在该页面,或者重新登录', '确定登出', { MessageBox.confirm('你已被登出,可以取消继续留在该页面,或者重新登录', '确定登出', {
confirmButtonText: '重新登录', confirmButtonText: '重新登录',
cancelButtonText: '取消', cancelButtonText: '取消',
...@@ -57,11 +49,16 @@ service.interceptors.response.use( ...@@ -57,11 +49,16 @@ service.interceptors.response.use(
location.reload() // 为了重新实例化vue-router对象 避免bug location.reload() // 为了重新实例化vue-router对象 避免bug
}) })
}) })
} else {
Message({
message: res.msg,
type: 'error',
duration: 5 * 1000
})
} }
return Promise.reject('error') return Promise.reject('error')
} else {
return response.data
} }
return response.data
}, },
error => { error => {
console.log('err' + error) // for debug console.log('err' + error) // for debug
......
<template>
<div class="chart-container">
<chart height="100%" width="100%"/>
</div>
</template>
<script>
import Chart from '@/components/Charts/keyboard'
export default {
name: 'KeyboardChart',
components: { Chart }
}
</script>
<style scoped>
.chart-container{
position: relative;
width: 100%;
height: calc(100vh - 84px);
}
</style>
<template>
<div class="chart-container">
<chart height="100%" width="100%"/>
</div>
</template>
<script>
import Chart from '@/components/Charts/lineMarker'
export default {
name: 'LineChart',
components: { Chart }
}
</script>
<style scoped>
.chart-container{
position: relative;
width: 100%;
height: calc(100vh - 84px);
}
</style>
<template>
<div class="chart-container">
<chart height="100%" width="100%"/>
</div>
</template>
<script>
import Chart from '@/components/Charts/mixChart'
export default {
name: 'MixChart',
components: { Chart }
}
</script>
<style scoped>
.chart-container{
position: relative;
width: 100%;
height: calc(100vh - 84px);
}
</style>
<template>
<div class="app-container">
<el-tabs v-model="activeName">
<el-tab-pane label="use clipboard directly" name="directly">
<el-input v-model="inputData" placeholder="Please input" style="width:400px;max-width:100%;"/>
<el-button type="primary" icon="document" @click="handleCopy(inputData,$event)">copy</el-button>
</el-tab-pane>
<el-tab-pane label="use clipboard by v-directive" name="v-directive">
<el-input v-model="inputData" placeholder="Please input" style="width:400px;max-width:100%;"/>
<el-button v-clipboard:copy="inputData" v-clipboard:success="clipboardSuccess" type="primary" icon="document">copy</el-button>
</el-tab-pane>
</el-tabs>
</div>
</template>
<script>
import clip from '@/utils/clipboard' // use clipboard directly
import clipboard from '@/directive/clipboard/index.js' // use clipboard by v-directive
export default {
name: 'ClipboardDemo',
directives: {
clipboard
},
data() {
return {
activeName: 'directly',
inputData: 'https://github.com/PanJiaChen/vue-element-admin'
}
},
methods: {
handleCopy(text, event) {
clip(text, event)
},
clipboardSuccess() {
this.$message({
message: 'Copy successfully',
type: 'success',
duration: 1500
})
}
}
}
</script>
<template>
<div :class="className" :style="{height:height,width:width}"/>
</template>
<script>
import echarts from 'echarts'
require('echarts/theme/macarons') // echarts theme
import { debounce } from '@/utils'
const animationDuration = 6000
export default {
props: {
className: {
type: String,
default: 'chart'
},
width: {
type: String,
default: '100%'
},
height: {
type: String,
default: '300px'
}
},
data() {
return {
chart: null
}
},
mounted() {
this.initChart()
this.__resizeHandler = debounce(() => {
if (this.chart) {
this.chart.resize()
}
}, 100)
window.addEventListener('resize', this.__resizeHandler)
},
beforeDestroy() {
if (!this.chart) {
return
}
window.removeEventListener('resize', this.__resizeHandler)
this.chart.dispose()
this.chart = null
},
methods: {
initChart() {
this.chart = echarts.init(this.$el, 'macarons')
this.chart.setOption({
tooltip: {
trigger: 'axis',
axisPointer: { // 坐标轴指示器,坐标轴触发有效
type: 'shadow' // 默认为直线,可选为:'line' | 'shadow'
}
},
grid: {
top: 10,
left: '2%',
right: '2%',
bottom: '3%',
containLabel: true
},
xAxis: [{
type: 'category',
data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],
axisTick: {
alignWithLabel: true
}
}],
yAxis: [{
type: 'value',
axisTick: {
show: false
}
}],
series: [{
name: 'pageA',
type: 'bar',
stack: 'vistors',
barWidth: '60%',
data: [79, 52, 200, 334, 390, 330, 220],
animationDuration
}, {
name: 'pageB',
type: 'bar',
stack: 'vistors',
barWidth: '60%',
data: [80, 52, 200, 334, 390, 330, 220],
animationDuration
}, {
name: 'pageC',
type: 'bar',
stack: 'vistors',
barWidth: '60%',
data: [30, 52, 200, 334, 390, 330, 220],
animationDuration
}]
})
}
}
}
</script>
<template>
<el-card class="box-card-component" style="margin-left:8px;">
<div slot="header" class="box-card-header">
<img src="https://wpimg.wallstcn.com/e7d23d71-cf19-4b90-a1cc-f56af8c0903d.png">
</div>
<div style="position:relative;">
<pan-thumb :image="avatar" class="panThumb"/>
<mallki class-name="mallki-text" text="vue-element-admin"/>
<div style="padding-top:35px;" class="progress-item">
<span>Vue</span>
<el-progress :percentage="70"/>
</div>
<div class="progress-item">
<span>JavaScript</span>
<el-progress :percentage="18"/>
</div>
<div class="progress-item">
<span>Css</span>
<el-progress :percentage="12"/>
</div>
<div class="progress-item">
<span>ESLint</span>
<el-progress :percentage="100" status="success"/>
</div>
</div>
</el-card>
</template>
<script>
import { mapGetters } from 'vuex'
import PanThumb from '@/components/PanThumb'
import Mallki from '@/components/TextHoverEffect/Mallki'
export default {
components: { PanThumb, Mallki },
filters: {
statusFilter(status) {
const statusMap = {
success: 'success',
pending: 'danger'
}
return statusMap[status]
}
},
data() {
return {
statisticsData: {
article_count: 1024,
pageviews_count: 1024
}
}
},
computed: {
...mapGetters([
'name',
'avatar',
'roles'
])
}
}
</script>
<style rel="stylesheet/scss" lang="scss" >
.box-card-component{
.el-card__header {
padding: 0px!important;
}
}
</style>
<style rel="stylesheet/scss" lang="scss" scoped>
.box-card-component {
.box-card-header {
position: relative;
height: 220px;
img {
width: 100%;
height: 100%;
transition: all 0.2s linear;
&:hover {
transform: scale(1.1, 1.1);
filter: contrast(130%);
}
}
}
.mallki-text {
position: absolute;
top: 0px;
right: 0px;
font-size: 20px;
font-weight: bold;
}
.panThumb {
z-index: 100;
height: 70px!important;
width: 70px!important;
position: absolute!important;
top: -45px;
left: 0px;
border: 5px solid #ffffff;
background-color: #fff;
margin: auto;
box-shadow: none!important;
/deep/ .pan-info {
box-shadow: none!important;
}
}
.progress-item {
margin-bottom: 10px;
font-size: 14px;
}
@media only screen and (max-width: 1510px){
.mallki-text{
display: none;
}
}
}
</style>
<template>
<div :class="className" :style="{height:height,width:width}"/>
</template>
<script>
import echarts from 'echarts'
require('echarts/theme/macarons') // echarts theme
import { debounce } from '@/utils'
export default {
props: {
className: {
type: String,
default: 'chart'
},
width: {
type: String,
default: '100%'
},
height: {
type: String,
default: '350px'
},
autoResize: {
type: Boolean,
default: true
},
chartData: {
type: Object,
required: true
}
},
data() {
return {
chart: null,
sidebarElm: null
}
},
watch: {
chartData: {
deep: true,
handler(val) {
this.setOptions(val)
}
}
},
mounted() {
this.initChart()
if (this.autoResize) {
this.__resizeHandler = debounce(() => {
if (this.chart) {
this.chart.resize()
}
}, 100)
window.addEventListener('resize', this.__resizeHandler)
}
// 监听侧边栏的变化
this.sidebarElm = document.getElementsByClassName('sidebar-container')[0]
this.sidebarElm && this.sidebarElm.addEventListener('transitionend', this.sidebarResizeHandler)
},
beforeDestroy() {
if (!this.chart) {
return
}
if (this.autoResize) {
window.removeEventListener('resize', this.__resizeHandler)
}
this.sidebarElm && this.sidebarElm.removeEventListener('transitionend', this.sidebarResizeHandler)
this.chart.dispose()
this.chart = null
},
methods: {
sidebarResizeHandler(e) {
if (e.propertyName === 'width') {
this.__resizeHandler()
}
},
setOptions({ expectedData, actualData } = {}) {
this.chart.setOption({
xAxis: {
data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],
boundaryGap: false,
axisTick: {
show: false
}
},
grid: {
left: 10,
right: 10,
bottom: 20,
top: 30,
containLabel: true
},
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'cross'
},
padding: [5, 10]
},
yAxis: {
axisTick: {
show: false
}
},
legend: {
data: ['expected', 'actual']
},
series: [{
name: 'expected', itemStyle: {
normal: {
color: '#FF005A',
lineStyle: {
color: '#FF005A',
width: 2
}
}
},
smooth: true,
type: 'line',
data: expectedData,
animationDuration: 2800,
animationEasing: 'cubicInOut'
},
{
name: 'actual',
smooth: true,
type: 'line',
itemStyle: {
normal: {
color: '#3888fa',
lineStyle: {
color: '#3888fa',
width: 2
},
areaStyle: {
color: '#f3f8ff'
}
}
},
data: actualData,
animationDuration: 2800,
animationEasing: 'quadraticOut'
}]
})
},
initChart() {
this.chart = echarts.init(this.$el, 'macarons')
this.setOptions(this.chartData)
}
}
}
</script>
<template>
<el-row :gutter="40" class="panel-group">
<el-col :xs="12" :sm="12" :lg="6" class="card-panel-col">
<div class="card-panel" @click="handleSetLineChartData('newVisitis')">
<div class="card-panel-icon-wrapper icon-people">
<svg-icon icon-class="peoples" class-name="card-panel-icon" />
</div>
<div class="card-panel-description">
<div class="card-panel-text">New Visits</div>
<count-to :start-val="0" :end-val="102400" :duration="2600" class="card-panel-num"/>
</div>
</div>
</el-col>
<el-col :xs="12" :sm="12" :lg="6" class="card-panel-col">
<div class="card-panel" @click="handleSetLineChartData('messages')">
<div class="card-panel-icon-wrapper icon-message">
<svg-icon icon-class="message" class-name="card-panel-icon" />
</div>
<div class="card-panel-description">
<div class="card-panel-text">Messages</div>
<count-to :start-val="0" :end-val="81212" :duration="3000" class="card-panel-num"/>
</div>
</div>
</el-col>
<el-col :xs="12" :sm="12" :lg="6" class="card-panel-col">
<div class="card-panel" @click="handleSetLineChartData('purchases')">
<div class="card-panel-icon-wrapper icon-money">
<svg-icon icon-class="money" class-name="card-panel-icon" />
</div>
<div class="card-panel-description">
<div class="card-panel-text">Purchases</div>
<count-to :start-val="0" :end-val="9280" :duration="3200" class="card-panel-num"/>
</div>
</div>
</el-col>
<el-col :xs="12" :sm="12" :lg="6" class="card-panel-col">
<div class="card-panel" @click="handleSetLineChartData('shoppings')">
<div class="card-panel-icon-wrapper icon-shopping">
<svg-icon icon-class="shopping" class-name="card-panel-icon" />
</div>
<div class="card-panel-description">
<div class="card-panel-text">Shoppings</div>
<count-to :start-val="0" :end-val="13600" :duration="3600" class="card-panel-num"/>
</div>
</div>
</el-col>
</el-row>
</template>
<script>
import CountTo from 'vue-count-to'
export default {
components: {
CountTo
},
methods: {
handleSetLineChartData(type) {
this.$emit('handleSetLineChartData', type)
}
}
}
</script>
<style rel="stylesheet/scss" lang="scss" scoped>
.panel-group {
margin-top: 18px;
.card-panel-col{
margin-bottom: 32px;
}
.card-panel {
height: 108px;
cursor: pointer;
font-size: 12px;
position: relative;
overflow: hidden;
color: #666;
background: #fff;
box-shadow: 4px 4px 40px rgba(0, 0, 0, .05);
border-color: rgba(0, 0, 0, .05);
&:hover {
.card-panel-icon-wrapper {
color: #fff;
}
.icon-people {
background: #40c9c6;
}
.icon-message {
background: #36a3f7;
}
.icon-money {
background: #f4516c;
}
.icon-shopping {
background: #34bfa3
}
}
.icon-people {
color: #40c9c6;
}
.icon-message {
color: #36a3f7;
}
.icon-money {
color: #f4516c;
}
.icon-shopping {
color: #34bfa3
}
.card-panel-icon-wrapper {
float: left;
margin: 14px 0 0 14px;
padding: 16px;
transition: all 0.38s ease-out;
border-radius: 6px;
}
.card-panel-icon {
float: left;
font-size: 48px;
}
.card-panel-description {
float: right;
font-weight: bold;
margin: 26px;
margin-left: 0px;
.card-panel-text {
line-height: 18px;
color: rgba(0, 0, 0, 0.45);
font-size: 16px;
margin-bottom: 12px;
}
.card-panel-num {
font-size: 20px;
}
}
}
}
</style>
<template>
<div :class="className" :style="{height:height,width:width}"/>
</template>
<script>
import echarts from 'echarts'
require('echarts/theme/macarons') // echarts theme
import { debounce } from '@/utils'
export default {
props: {
className: {
type: String,
default: 'chart'
},
width: {
type: String,
default: '100%'
},
height: {
type: String,
default: '300px'
}
},
data() {
return {
chart: null
}
},
mounted() {
this.initChart()
this.__resizeHandler = debounce(() => {
if (this.chart) {
this.chart.resize()
}
}, 100)
window.addEventListener('resize', this.__resizeHandler)
},
beforeDestroy() {
if (!this.chart) {
return
}
window.removeEventListener('resize', this.__resizeHandler)
this.chart.dispose()
this.chart = null
},
methods: {
initChart() {
this.chart = echarts.init(this.$el, 'macarons')
this.chart.setOption({
tooltip: {
trigger: 'item',
formatter: '{a} <br/>{b} : {c} ({d}%)'
},
legend: {
left: 'center',
bottom: '10',
data: ['Industries', 'Technology', 'Forex', 'Gold', 'Forecasts']
},
calculable: true,
series: [
{
name: 'WEEKLY WRITE ARTICLES',
type: 'pie',
roseType: 'radius',
radius: [15, 95],
center: ['50%', '38%'],
data: [
{ value: 320, name: 'Industries' },
{ value: 240, name: 'Technology' },
{ value: 149, name: 'Forex' },
{ value: 100, name: 'Gold' },
{ value: 59, name: 'Forecasts' }
],
animationEasing: 'cubicInOut',
animationDuration: 2600
}
]
})
}
}
}
</script>
<template>
<div :class="className" :style="{height:height,width:width}"/>
</template>
<script>
import echarts from 'echarts'
require('echarts/theme/macarons') // echarts theme
import { debounce } from '@/utils'
const animationDuration = 3000
export default {
props: {
className: {
type: String,
default: 'chart'
},
width: {
type: String,
default: '100%'
},
height: {
type: String,
default: '300px'
}
},
data() {
return {
chart: null
}
},
mounted() {
this.initChart()
this.__resizeHandler = debounce(() => {
if (this.chart) {
this.chart.resize()
}
}, 100)
window.addEventListener('resize', this.__resizeHandler)
},
beforeDestroy() {
if (!this.chart) {
return
}
window.removeEventListener('resize', this.__resizeHandler)
this.chart.dispose()
this.chart = null
},
methods: {
initChart() {
this.chart = echarts.init(this.$el, 'macarons')
this.chart.setOption({
tooltip: {
trigger: 'axis',
axisPointer: { // 坐标轴指示器,坐标轴触发有效
type: 'shadow' // 默认为直线,可选为:'line' | 'shadow'
}
},
radar: {
radius: '66%',
center: ['50%', '42%'],
splitNumber: 8,
splitArea: {
areaStyle: {
color: 'rgba(127,95,132,.3)',
opacity: 1,
shadowBlur: 45,
shadowColor: 'rgba(0,0,0,.5)',
shadowOffsetX: 0,
shadowOffsetY: 15
}
},
indicator: [
{ name: 'Sales', max: 10000 },
{ name: 'Administration', max: 20000 },
{ name: 'Information Techology', max: 20000 },
{ name: 'Customer Support', max: 20000 },
{ name: 'Development', max: 20000 },
{ name: 'Marketing', max: 20000 }
]
},
legend: {
left: 'center',
bottom: '10',
data: ['Allocated Budget', 'Expected Spending', 'Actual Spending']
},
series: [{
type: 'radar',
symbolSize: 0,
areaStyle: {
normal: {
shadowBlur: 13,
shadowColor: 'rgba(0,0,0,.2)',
shadowOffsetX: 0,
shadowOffsetY: 10,
opacity: 1
}
},
data: [
{
value: [5000, 7000, 12000, 11000, 15000, 14000],
name: 'Allocated Budget'
},
{
value: [4000, 9000, 15000, 15000, 13000, 11000],
name: 'Expected Spending'
},
{
value: [5500, 11000, 12000, 15000, 12000, 12000],
name: 'Actual Spending'
}
],
animationDuration: animationDuration
}]
})
}
}
}
</script>
<template>
<li :class="{ completed: todo.done, editing: editing }" class="todo">
<div class="view">
<input
:checked="todo.done"
class="toggle"
type="checkbox"
@change="toggleTodo( todo)">
<label @dblclick="editing = true" v-text="todo.text"/>
<button class="destroy" @click="deleteTodo( todo )"/>
</div>
<input
v-focus="editing"
v-show="editing"
:value="todo.text"
class="edit"
@keyup.enter="doneEdit"
@keyup.esc="cancelEdit"
@blur="doneEdit">
</li>
</template>
<script>
export default {
name: 'Todo',
directives: {
focus(el, { value }, { context }) {
if (value) {
context.$nextTick(() => {
el.focus()
})
}
}
},
props: {
todo: {
type: Object,
default: function() {
return {}
}
}
},
data() {
return {
editing: false
}
},
methods: {
deleteTodo(todo) {
this.$emit('deleteTodo', todo)
},
editTodo({ todo, value }) {
this.$emit('editTodo', { todo, value })
},
toggleTodo(todo) {
this.$emit('toggleTodo', todo)
},
doneEdit(e) {
const value = e.target.value.trim()
const { todo } = this
if (!value) {
this.deleteTodo({
todo
})
} else if (this.editing) {
this.editTodo({
todo,
value
})
this.editing = false
}
},
cancelEdit(e) {
e.target.value = this.todo.text
this.editing = false
}
}
}
</script>
.todoapp {
font: 14px 'Helvetica Neue', Helvetica, Arial, sans-serif;
line-height: 1.4em;
color: #4d4d4d;
min-width: 230px;
max-width: 550px;
margin: 0 auto ;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
font-weight: 300;
background: #fff;
z-index: 1;
position: relative;
button {
margin: 0;
padding: 0;
border: 0;
background: none;
font-size: 100%;
vertical-align: baseline;
font-family: inherit;
font-weight: inherit;
color: inherit;
-webkit-appearance: none;
appearance: none;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
:focus {
outline: 0;
}
.hidden {
display: none;
}
.todoapp {
background: #fff;
margin: 130px 0 40px 0;
position: relative;
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2), 0 25px 50px 0 rgba(0, 0, 0, 0.1);
}
.todoapp input::-webkit-input-placeholder {
font-style: italic;
font-weight: 300;
color: #e6e6e6;
}
.todoapp input::-moz-placeholder {
font-style: italic;
font-weight: 300;
color: #e6e6e6;
}
.todoapp input::input-placeholder {
font-style: italic;
font-weight: 300;
color: #e6e6e6;
}
.todoapp h1 {
position: absolute;
top: -155px;
width: 100%;
font-size: 100px;
font-weight: 100;
text-align: center;
color: rgba(175, 47, 47, 0.15);
-webkit-text-rendering: optimizeLegibility;
-moz-text-rendering: optimizeLegibility;
text-rendering: optimizeLegibility;
}
.new-todo,
.edit {
position: relative;
margin: 0;
width: 100%;
font-size: 18px;
font-family: inherit;
font-weight: inherit;
line-height: 1.4em;
border: 0;
color: inherit;
padding: 6px;
border: 1px solid #999;
box-shadow: inset 0 -1px 5px 0 rgba(0, 0, 0, 0.2);
box-sizing: border-box;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.new-todo {
padding: 10px 16px 16px 60px;
border: none;
background: rgba(0, 0, 0, 0.003);
box-shadow: inset 0 -2px 1px rgba(0, 0, 0, 0.03);
}
.main {
position: relative;
z-index: 2;
border-top: 1px solid #e6e6e6;
}
.toggle-all {
text-align: center;
border: none;
/* Mobile Safari */
opacity: 0;
position: absolute;
}
.toggle-all+label {
width: 60px;
height: 34px;
font-size: 0;
position: absolute;
top: -52px;
left: -13px;
-webkit-transform: rotate(90deg);
transform: rotate(90deg);
}
.toggle-all+label:before {
content: '❯';
font-size: 22px;
color: #e6e6e6;
padding: 10px 27px 10px 27px;
}
.toggle-all:checked+label:before {
color: #737373;
}
.todo-list {
margin: 0;
padding: 0;
list-style: none;
}
.todo-list li {
position: relative;
font-size: 24px;
border-bottom: 1px solid #ededed;
}
.todo-list li:last-child {
border-bottom: none;
}
.todo-list li.editing {
border-bottom: none;
padding: 0;
}
.todo-list li.editing .edit {
display: block;
width: 506px;
padding: 12px 16px;
margin: 0 0 0 43px;
}
.todo-list li.editing .view {
display: none;
}
.todo-list li .toggle {
text-align: center;
width: 40px;
/* auto, since non-WebKit browsers doesn't support input styling */
height: auto;
position: absolute;
top: 0;
bottom: 0;
margin: auto 0;
border: none;
/* Mobile Safari */
-webkit-appearance: none;
appearance: none;
}
.todo-list li .toggle {
opacity: 0;
}
.todo-list li .toggle+label {
/*
Firefox requires `#` to be escaped - https://bugzilla.mozilla.org/show_bug.cgi?id=922433
IE and Edge requires *everything* to be escaped to render, so we do that instead of just the `#` - https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/7157459/
*/
background-image: url('data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20width%3D%2240%22%20height%3D%2240%22%20viewBox%3D%22-10%20-18%20100%20135%22%3E%3Ccircle%20cx%3D%2250%22%20cy%3D%2250%22%20r%3D%2250%22%20fill%3D%22none%22%20stroke%3D%22%23ededed%22%20stroke-width%3D%223%22/%3E%3C/svg%3E');
background-repeat: no-repeat;
background-position: center left;
background-size: 36px;
}
.todo-list li .toggle:checked+label {
background-size: 36px;
background-image: url('data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20width%3D%2240%22%20height%3D%2240%22%20viewBox%3D%22-10%20-18%20100%20135%22%3E%3Ccircle%20cx%3D%2250%22%20cy%3D%2250%22%20r%3D%2250%22%20fill%3D%22none%22%20stroke%3D%22%23bddad5%22%20stroke-width%3D%223%22/%3E%3Cpath%20fill%3D%22%235dc2af%22%20d%3D%22M72%2025L42%2071%2027%2056l-4%204%2020%2020%2034-52z%22/%3E%3C/svg%3E');
}
.todo-list li label {
word-break: break-all;
padding: 15px 15px 15px 50px;
display: block;
line-height: 1.0;
font-size: 14px;
transition: color 0.4s;
}
.todo-list li.completed label {
color: #d9d9d9;
text-decoration: line-through;
}
.todo-list li .destroy {
display: none;
position: absolute;
top: 0;
right: 10px;
bottom: 0;
width: 40px;
height: 40px;
margin: auto 0;
font-size: 30px;
color: #cc9a9a;
transition: color 0.2s ease-out;
cursor: pointer;
}
.todo-list li .destroy:hover {
color: #af5b5e;
}
.todo-list li .destroy:after {
content: '×';
}
.todo-list li:hover .destroy {
display: block;
}
.todo-list li .edit {
display: none;
}
.todo-list li.editing:last-child {
margin-bottom: -1px;
}
.footer {
color: #777;
position: relative;
padding: 10px 15px;
height: 40px;
text-align: center;
border-top: 1px solid #e6e6e6;
}
.footer:before {
content: '';
position: absolute;
right: 0;
bottom: 0;
left: 0;
height: 40px;
overflow: hidden;
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2), 0 8px 0 -3px #f6f6f6, 0 9px 1px -3px rgba(0, 0, 0, 0.2), 0 16px 0 -6px #f6f6f6, 0 17px 2px -6px rgba(0, 0, 0, 0.2);
}
.todo-count {
float: left;
text-align: left;
}
.todo-count strong {
font-weight: 300;
}
.filters {
margin: 0;
padding: 0;
position: relative;
z-index: 1;
list-style: none;
}
.filters li {
display: inline;
}
.filters li a {
color: inherit;
font-size: 12px;
padding: 3px 7px;
text-decoration: none;
border: 1px solid transparent;
border-radius: 3px;
}
.filters li a:hover {
border-color: rgba(175, 47, 47, 0.1);
}
.filters li a.selected {
border-color: rgba(175, 47, 47, 0.2);
}
.clear-completed,
html .clear-completed:active {
float: right;
position: relative;
line-height: 20px;
text-decoration: none;
cursor: pointer;
}
.clear-completed:hover {
text-decoration: underline;
}
.info {
margin: 65px auto 0;
color: #bfbfbf;
font-size: 10px;
text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5);
text-align: center;
}
.info p {
line-height: 1;
}
.info a {
color: inherit;
text-decoration: none;
font-weight: 400;
}
.info a:hover {
text-decoration: underline;
}
/*
Hack to remove background from Mobile Safari.
Can't use it globally since it destroys checkboxes in Firefox
*/
@media screen and (-webkit-min-device-pixel-ratio:0) {
.toggle-all,
.todo-list li .toggle {
background: none;
}
.todo-list li .toggle {
height: 40px;
}
}
@media (max-width: 430px) {
.footer {
height: 50px;
}
.filters {
bottom: 10px;
}
}
}
<template>
<section class="todoapp">
<!-- header -->
<header class="header">
<input class="new-todo" autocomplete="off" placeholder="Todo List" @keyup.enter="addTodo">
</header>
<!-- main section -->
<section v-show="todos.length" class="main">
<input id="toggle-all" :checked="allChecked" class="toggle-all" type="checkbox" @change="toggleAll({ done: !allChecked })">
<label for="toggle-all"/>
<ul class="todo-list">
<todo
v-for="(todo, index) in filteredTodos"
:key="index"
:todo="todo"
@toggleTodo="toggleTodo"
@editTodo="editTodo"
@deleteTodo="deleteTodo"/>
</ul>
</section>
<!-- footer -->
<footer v-show="todos.length" class="footer">
<span class="todo-count">
<strong>{{ remaining }}</strong>
{{ remaining | pluralize('item') }} left
</span>
<ul class="filters">
<li v-for="(val, key) in filters" :key="key">
<a :class="{ selected: visibility === key }" @click.prevent="visibility = key">{{ key | capitalize }}</a>
</li>
</ul>
<!-- <button class="clear-completed" v-show="todos.length > remaining" @click="clearCompleted">
Clear completed
</button> -->
</footer>
</section>
</template>
<script>
import Todo from './Todo.vue'
const STORAGE_KEY = 'todos'
const filters = {
all: todos => todos,
active: todos => todos.filter(todo => !todo.done),
completed: todos => todos.filter(todo => todo.done)
}
const defalutList = [
{ text: 'star this repository', done: false },
{ text: 'fork this repository', done: false },
{ text: 'follow author', done: false },
{ text: 'vue-element-admin', done: true },
{ text: 'vue', done: true },
{ text: 'element-ui', done: true },
{ text: 'axios', done: true },
{ text: 'webpack', done: true }
]
export default {
components: { Todo },
filters: {
pluralize: (n, w) => n === 1 ? w : w + 's',
capitalize: s => s.charAt(0).toUpperCase() + s.slice(1)
},
data() {
return {
visibility: 'all',
filters,
// todos: JSON.parse(window.localStorage.getItem(STORAGE_KEY)) || defalutList
todos: defalutList
}
},
computed: {
allChecked() {
return this.todos.every(todo => todo.done)
},
filteredTodos() {
return filters[this.visibility](this.todos)
},
remaining() {
return this.todos.filter(todo => !todo.done).length
}
},
methods: {
setLocalStorage() {
window.localStorage.setItem(STORAGE_KEY, JSON.stringify(this.todos))
},
addTodo(e) {
const text = e.target.value
if (text.trim()) {
this.todos.push({
text,
done: false
})
this.setLocalStorage()
}
e.target.value = ''
},
toggleTodo(val) {
val.done = !val.done
this.setLocalStorage()
},
deleteTodo(todo) {
this.todos.splice(this.todos.indexOf(todo), 1)
this.setLocalStorage()
},
editTodo({ todo, value }) {
todo.text = value
this.setLocalStorage()
},
clearCompleted() {
this.todos = this.todos.filter(todo => !todo.done)
this.setLocalStorage()
},
toggleAll({ done }) {
this.todos.forEach(todo => {
todo.done = done
this.setLocalStorage()
})
}
}
}
</script>
<style lang="scss">
@import './index.scss';
</style>
<template>
<el-table :data="list" style="width: 100%;padding-top: 15px;">
<el-table-column label="Order_No" min-width="200">
<template slot-scope="scope">
{{ scope.row.order_no | orderNoFilter }}
</template>
</el-table-column>
<el-table-column label="Price" width="195" align="center">
<template slot-scope="scope">
¥{{ scope.row.price | toThousandFilter }}
</template>
</el-table-column>
<el-table-column label="Status" width="100" align="center">
<template slot-scope="scope">
<el-tag :type="scope.row.status | statusFilter"> {{ scope.row.status }}</el-tag>
</template>
</el-table-column>
</el-table>
</template>
<script>
import { fetchList } from '@/api/transaction'
export default {
filters: {
statusFilter(status) {
const statusMap = {
success: 'success',
pending: 'danger'
}
return statusMap[status]
},
orderNoFilter(str) {
return str.substring(0, 30)
}
},
data() {
return {
list: null
}
},
created() {
this.fetchData()
},
methods: {
fetchData() {
fetchList().then(response => {
this.list = response.data.items.slice(0, 8)
})
}
}
}
</script>
<template>
<div class="dashboard-editor-container">
<github-corner style="position: absolute; top: 0px; border: 0; right: 0;"/>
<panel-group @handleSetLineChartData="handleSetLineChartData"/>
<el-row style="background:#fff;padding:16px 16px 0;margin-bottom:32px;">
<line-chart :chart-data="lineChartData"/>
</el-row>
<el-row :gutter="32">
<el-col :xs="24" :sm="24" :lg="8">
<div class="chart-wrapper">
<raddar-chart/>
</div>
</el-col>
<el-col :xs="24" :sm="24" :lg="8">
<div class="chart-wrapper">
<pie-chart/>
</div>
</el-col>
<el-col :xs="24" :sm="24" :lg="8">
<div class="chart-wrapper">
<bar-chart/>
</div>
</el-col>
</el-row>
<el-row :gutter="8">
<el-col :xs="{span: 24}" :sm="{span: 24}" :md="{span: 24}" :lg="{span: 12}" :xl="{span: 12}" style="padding-right:8px;margin-bottom:30px;">
<transaction-table/>
</el-col>
<el-col :xs="{span: 24}" :sm="{span: 12}" :md="{span: 12}" :lg="{span: 6}" :xl="{span: 6}" style="margin-bottom:30px;">
<todo-list/>
</el-col>
<el-col :xs="{span: 24}" :sm="{span: 12}" :md="{span: 12}" :lg="{span: 6}" :xl="{span: 6}" style="margin-bottom:30px;">
<box-card/>
</el-col>
</el-row>
</div>
</template>
<script>
import GithubCorner from '@/components/GithubCorner'
import PanelGroup from './components/PanelGroup'
import LineChart from './components/LineChart'
import RaddarChart from './components/RaddarChart'
import PieChart from './components/PieChart'
import BarChart from './components/BarChart'
import TransactionTable from './components/TransactionTable'
import TodoList from './components/TodoList'
import BoxCard from './components/BoxCard'
const lineChartData = {
newVisitis: {
expectedData: [100, 120, 161, 134, 105, 160, 165],
actualData: [120, 82, 91, 154, 162, 140, 145]
},
messages: {
expectedData: [200, 192, 120, 144, 160, 130, 140],
actualData: [180, 160, 151, 106, 145, 150, 130]
},
purchases: {
expectedData: [80, 100, 121, 104, 105, 90, 100],
actualData: [120, 90, 100, 138, 142, 130, 130]
},
shoppings: {
expectedData: [130, 140, 141, 142, 145, 150, 160],
actualData: [120, 82, 91, 154, 162, 140, 130]
}
}
export default {
name: 'DashboardAdmin',
components: {
GithubCorner,
PanelGroup,
LineChart,
RaddarChart,
PieChart,
BarChart,
TransactionTable,
TodoList,
BoxCard
},
data() {
return {
lineChartData: lineChartData.newVisitis
}
},
methods: {
handleSetLineChartData(type) {
this.lineChartData = lineChartData[type]
}
}
}
</script>
<style rel="stylesheet/scss" lang="scss" scoped>
.dashboard-editor-container {
padding: 32px;
background-color: rgb(240, 242, 245);
.chart-wrapper {
background: #fff;
padding: 16px 16px 0;
margin-bottom: 32px;
}
}
</style>
<template>
<div class="dashboard-editor-container">
<div class=" clearfix">
<pan-thumb :image="avatar" style="float: left"> Your roles:
<span v-for="item in roles" :key="item" class="pan-info-roles">{{ item }}</span>
</pan-thumb>
<github-corner style="position: absolute; top: 0px; border: 0; right: 0;"/>
<div class="info-container">
<span class="display_name">{{ name }}</span>
<span style="font-size:20px;padding-top:20px;display:inline-block;">Editor's Dashboard</span>
</div>
</div>
<div>
<img :src="emptyGif" class="emptyGif">
</div>
</div>
</template>
<script>
import { mapGetters } from 'vuex'
import PanThumb from '@/components/PanThumb'
import GithubCorner from '@/components/GithubCorner'
export default {
name: 'DashboardEditor',
components: { PanThumb, GithubCorner },
data() {
return {
emptyGif: 'https://wpimg.wallstcn.com/0e03b7da-db9e-4819-ba10-9016ddfdaed3'
}
},
computed: {
...mapGetters([
'name',
'avatar',
'roles'
])
}
}
</script>
<style rel="stylesheet/scss" lang="scss" scoped>
.emptyGif {
display: block;
width: 45%;
margin: 0 auto;
}
.dashboard-editor-container {
background-color: #e3e3e3;
min-height: 100vh;
padding: 50px 60px 0px;
.pan-info-roles {
font-size: 12px;
font-weight: 700;
color: #333;
display: block;
}
.info-container {
position: relative;
margin-left: 190px;
height: 150px;
line-height: 200px;
.display_name {
font-size: 48px;
line-height: 48px;
color: #212121;
position: absolute;
top: 25px;
}
}
}
</style>
<template>
<div class="dashboard-container">
<component :is="currentRole"/>
</div>
</template>
<script>
import { mapGetters } from 'vuex'
import adminDashboard from './admin'
import editorDashboard from './editor'
export default {
name: 'Dashboard',
components: { adminDashboard, editorDashboard },
data() {
return {
currentRole: 'adminDashboard'
}
},
computed: {
...mapGetters([
'roles'
])
},
created() {
if (!this.roles.includes('admin')) {
this.currentRole = 'editorDashboard'
}
}
}
</script>
...@@ -113,7 +113,7 @@ export default { ...@@ -113,7 +113,7 @@ export default {
methods: { methods: {
getConfig() { getConfig() {
getConfig(this.listQuery).then(res => { getConfig(this.listQuery).then(res => {
this.configs = res.data.data this.configs = res.data
this.listLoading = false this.listLoading = false
}) })
}, },
...@@ -139,22 +139,14 @@ export default { ...@@ -139,22 +139,14 @@ export default {
this.$refs['dataForm'].validate((valid) => { this.$refs['dataForm'].validate((valid) => {
if (valid) { if (valid) {
saveConfig(this.temp).then((res) => { saveConfig(this.temp).then((res) => {
if (res.data.code !== '0000') { this.dialogFormVisible = false
this.$notify({ this.$notify({
message: `${res.data.msg}`, title: '成功',
type: 'error', message: '保存成功',
duration: 2000 type: 'success',
}) duration: 2000
} else { })
this.dialogFormVisible = false this.getConfig()
this.$notify({
title: '成功',
message: '保存成功',
type: 'success',
duration: 2000
})
this.getConfig()
}
}) })
} }
}) })
......
...@@ -84,13 +84,13 @@ export default { ...@@ -84,13 +84,13 @@ export default {
methods: { methods: {
getNamespace() { getNamespace() {
getNamespace().then(res => { getNamespace().then(res => {
this.namespaces = res.data.data.map(item => item.name) this.namespaces = res.data.map(item => item.name)
}) })
}, },
getDBName() { getDBName() {
getDBName().then(res => { getDBName().then(res => {
this.dbnames = res.data.data.map(item => item.dbname) this.dbnames = res.data.map(item => item.dbname)
this.dbnames.unshift('all(no_mall)') this.dbnames.unshift('all(no_mall)')
}) })
}, },
...@@ -106,7 +106,7 @@ export default { ...@@ -106,7 +106,7 @@ export default {
syncQuery(location) { syncQuery(location) {
dbSyncQuery({ location }).then(res => { dbSyncQuery({ location }).then(res => {
res = res.data.data res = res.data
if (this.timer > 300) { if (this.timer > 300) {
this.$notify({ this.$notify({
title: '超时', title: '超时',
...@@ -141,7 +141,7 @@ export default { ...@@ -141,7 +141,7 @@ export default {
if (valid) { if (valid) {
dbSync(this.temp).then((res) => { dbSync(this.temp).then((res) => {
this.dialogFormVisible = false this.dialogFormVisible = false
this.location = res.data.data.location this.location = res.data.location
this.$notify({ this.$notify({
message: '正在同步', message: '正在同步',
type: 'success', type: 'success',
......
...@@ -64,7 +64,7 @@ export default { ...@@ -64,7 +64,7 @@ export default {
getAllEnvTemplate() { getAllEnvTemplate() {
getAllEnvTemplate().then(res => { getAllEnvTemplate().then(res => {
this.templates = res.data.data this.templates = res.data
}) })
}, },
......
...@@ -302,7 +302,7 @@ export default { ...@@ -302,7 +302,7 @@ export default {
}, },
created() { created() {
getType().then(res => { getType().then(res => {
this.typeList = res.data.data this.typeList = res.data
}) })
this.namespace = this.$route.params.name this.namespace = this.$route.params.name
this.getServicelist() this.getServicelist()
...@@ -310,7 +310,7 @@ export default { ...@@ -310,7 +310,7 @@ export default {
methods: { methods: {
getServicelist() { getServicelist() {
fetchK8sdetail({ 'namespace': this.namespace }).then(res => { fetchK8sdetail({ 'namespace': this.namespace }).then(res => {
const servicelist = res.data.data.services const servicelist = res.data.services
this.array2Object(servicelist) this.array2Object(servicelist)
}) })
}, },
...@@ -327,9 +327,9 @@ export default { ...@@ -327,9 +327,9 @@ export default {
this.tagName = '' this.tagName = ''
this.options = [] this.options = []
this.tagOptions = [] this.tagOptions = []
fetchUserrepository({ 'namespace': 'qa-' + label }).then(res => { fetchUserrepository({ 'namespace': 'qa-' + type }).then(res => {
this.options = res.data.data.repoInfo this.options = res.data.repoInfo
this.server = res.data.data.server this.server = res.data.server
}) })
this.dialogVisible = true this.dialogVisible = true
}, },
...@@ -340,8 +340,8 @@ export default { ...@@ -340,8 +340,8 @@ export default {
} }
}) })
fetchTaglist({ reponame: this.reponame }).then(res => { fetchTaglist({ reponame: this.reponame }).then(res => {
if (res.data.data.tagCount) { if (res.data.tagCount) {
this.tagOptions = res.data.data.tagInfo this.tagOptions = res.data.tagInfo
} }
}) })
}, },
...@@ -359,7 +359,7 @@ export default { ...@@ -359,7 +359,7 @@ export default {
}).then(res => { }).then(res => {
if (res.data.code === '0000') { if (res.data.code === '0000') {
this.$message({ this.$message({
message: res.data.data, message: res.data,
type: 'success', type: 'success',
duration: 1000, duration: 1000,
onClose() { onClose() {
...@@ -377,14 +377,14 @@ export default { ...@@ -377,14 +377,14 @@ export default {
}, },
handleDetail(data) { handleDetail(data) {
ingressHost({ namespace: data.namespace, serviceName: data.serviceName }).then(res => { ingressHost({ namespace: data.namespace, serviceName: data.serviceName }).then(res => {
const url = res.data.data.body.spec.rules[0].host const url = res.data.body.spec.rules[0].host
window.open(`http://${url}/`, '_blank') window.open(`http://${url}/`, '_blank')
}) })
}, },
getServiceDetails(value) { getServiceDetails(value) {
fetchServiceDetails({ serviceName: value.serviceName, namespace: value.namespace }).then(res => { fetchServiceDetails({ serviceName: value.serviceName, namespace: value.namespace }).then(res => {
this.centerDialogVisible = true this.centerDialogVisible = true
this.value = res.data.data.service this.value = res.data.service
}) })
}, },
submitHost(formName) { submitHost(formName) {
...@@ -417,9 +417,9 @@ export default { ...@@ -417,9 +417,9 @@ export default {
this.rename = value.serviceName this.rename = value.serviceName
this.tagOptions = [] this.tagOptions = []
fetchTaglist({ reponame: 'qa-' + value.userLabels.type + '/' + this.rename }).then(res => { fetchTaglist({ reponame: 'qa-' + value.userLabels.type + '/' + this.rename }).then(res => {
if (res.data.data.tagCount) { if (res.data.tagCount) {
this.tagOptions = res.data.data.tagInfo this.tagOptions = res.data.tagInfo
this.server = res.data.data.server this.server = res.data.server
} }
}) })
}, },
...@@ -482,7 +482,7 @@ export default { ...@@ -482,7 +482,7 @@ export default {
this.form.host = '' this.form.host = ''
this.ingressName = data.serviceName this.ingressName = data.serviceName
ingressHost({ namespace: data.namespace, serviceName: data.serviceName }).then(res => { ingressHost({ namespace: data.namespace, serviceName: data.serviceName }).then(res => {
this.form.host = res.data.data.body.spec.rules[0].host this.form.host = res.data.body.spec.rules[0].host
}) })
}, },
deleteService(data) { deleteService(data) {
...@@ -514,7 +514,7 @@ export default { ...@@ -514,7 +514,7 @@ export default {
}, },
linkShell(scopeData) { linkShell(scopeData) {
fetchServiceInstance({ serviceName: scopeData.serviceName, namespace: scopeData.namespace }).then(res => { fetchServiceInstance({ serviceName: scopeData.serviceName, namespace: scopeData.namespace }).then(res => {
const podName = res.data.data.instances[0].name const podName = res.data.instances[0].name
window.open(`http://172.30.220.24:9600/static/terminal.html?namespace=${scopeData.namespace}&pod=${podName}`, '_blank') window.open(`http://172.30.220.24:9600/static/terminal.html?namespace=${scopeData.namespace}&pod=${podName}`, '_blank')
}) })
}, },
...@@ -526,7 +526,7 @@ export default { ...@@ -526,7 +526,7 @@ export default {
path = `/home/quant_group/logs/${scopeData.serviceName}.log` path = `/home/quant_group/logs/${scopeData.serviceName}.log`
} }
fetchServiceInstance({ serviceName: scopeData.serviceName, namespace: scopeData.namespace }).then(res => { fetchServiceInstance({ serviceName: scopeData.serviceName, namespace: scopeData.namespace }).then(res => {
const podName = res.data.data.instances[0].name const podName = res.data.instances[0].name
window.open(`http://172.30.220.24:9600/static/logs.html?namespace=${scopeData.namespace}&pod=${podName}&paths=${path}`, '_blank') window.open(`http://172.30.220.24:9600/static/logs.html?namespace=${scopeData.namespace}&pod=${podName}&paths=${path}`, '_blank')
}) })
}, },
......
...@@ -64,7 +64,7 @@ export default { ...@@ -64,7 +64,7 @@ export default {
getAllEnvTemplate() { getAllEnvTemplate() {
getAllEnvTemplate().then(res => { getAllEnvTemplate().then(res => {
// todo: http服务统一返回res.data // todo: http服务统一返回res.data
this.templates = res.data.data this.templates = res.data
}) })
}, },
......
...@@ -62,7 +62,7 @@ export default { ...@@ -62,7 +62,7 @@ export default {
fetchK8sList() { fetchK8sList() {
fetchKubernetesList().then(res => { fetchKubernetesList().then(res => {
const exculde = ['default', 'kube-system'] const exculde = ['default', 'kube-system']
this.tableData = res.data.data.namespaces.filter((item) => { this.tableData = res.data.namespaces.filter((item) => {
if (!exculde.includes(item.name)) { if (!exculde.includes(item.name)) {
return item return item
} }
......
<template>
<div class="app-container documentation-container">
<a class="document-btn" target="_blank" href="https://panjiachen.github.io/vue-element-admin-site/">{{ $t('documentation.documentation') }}</a>
<a class="document-btn" target="_blank" href="https://github.com/PanJiaChen/vue-element-admin/">{{ $t('documentation.github') }}</a>
<a class="document-btn" target="_blank" href="https://panjiachen.gitee.io/vue-element-admin-site/zh/">国内文档</a>
<dropdown-menu :items="articleList" style="float:left;margin-left:50px;" title="系列文章"/>
</div>
</template>
<script>
import DropdownMenu from '@/components/Share/dropdownMenu'
export default {
name: 'Documentation',
components: { DropdownMenu },
data() {
return {
articleList: [
{ title: '基础篇', href: 'https://juejin.im/post/59097cd7a22b9d0065fb61d2' },
{ title: '登录权限篇', href: 'https://juejin.im/post/591aa14f570c35006961acac' },
{ title: '实战篇', href: 'https://juejin.im/post/593121aa0ce4630057f70d35' },
{ title: 'vue-admin-template 篇', href: 'https://juejin.im/post/595b4d776fb9a06bbe7dba56' },
{ title: '自行封装 component', href: 'https://segmentfault.com/a/1190000009090836' },
{ title: '优雅的使用 icon', href: 'https://juejin.im/post/59bb864b5188257e7a427c09' },
{ title: 'webpack4(上)', href: 'https://juejin.im/post/59bb864b5188257e7a427c09' },
{ title: 'webpack4(下)', href: 'https://juejin.im/post/5b5d6d6f6fb9a04fea58aabc' }
]
}
}
}
</script>
<style rel="stylesheet/scss" lang="scss" scoped>
.documentation-container {
margin: 50px;
.document-btn {
float: left;
margin-left: 50px;
display: block;
cursor: pointer;
background: black;
color: white;
height: 60px;
width: 200px;
line-height: 60px;
font-size: 20px;
text-align: center;
}
}
</style>
<template>
<div>
<!--error code-->
{{ a.a }}
<!--error code-->
</div>
</template>
<script>
export default {
name: 'ErrorTestA'
}
</script>
<template>
<div/>
</template>
<script>
export default {
created() {
this.b = b // eslint-disable-line
}
}
</script>
<template>
<div class="errPage-container">
<errorA/>
<errorB/>
<!-- $t is vue-i18n global function to translate lang -->
<h3>{{ $t('errorLog.tips') }}</h3>
<code>
{{ $t('errorLog.description') }}
<a target="_blank" class="link-type" href="https://panjiachen.github.io/vue-element-admin-site/guide/advanced/error.html">
{{ $t('errorLog.documentation') }}
</a>
</code>
<a href="#">
<img src="https://wpimg.wallstcn.com/360e4842-4db5-42d0-b078-f9a84a825546.gif">
</a>
</div>
</template>
<script>
import errorA from './errorTestA'
import errorB from './errorTestB'
export default {
name: 'ErrorLog',
components: { errorA, errorB }
}
</script>
<style scoped>
.errPage-container {
padding: 30px;
}
</style>
<template>
<div class="createPost-container">
<el-form ref="postForm" :model="postForm" :rules="rules" class="form-container">
<sticky :class-name="'sub-navbar '+postForm.status">
<CommentDropdown v-model="postForm.comment_disabled" />
<PlatformDropdown v-model="postForm.platforms" />
<SourceUrlDropdown v-model="postForm.source_uri" />
<el-button v-loading="loading" style="margin-left: 10px;" type="success" @click="submitForm">发布
</el-button>
<el-button v-loading="loading" type="warning" @click="draftForm">草稿</el-button>
</sticky>
<div class="createPost-main-container">
<el-row>
<Warning />
<el-col :span="24">
<el-form-item style="margin-bottom: 40px;" prop="title">
<MDinput v-model="postForm.title" :maxlength="100" name="name" required>
标题
</MDinput>
</el-form-item>
<div class="postInfo-container">
<el-row>
<el-col :span="8">
<el-form-item label-width="45px" label="作者:" class="postInfo-container-item">
<el-select v-model="postForm.author" :remote-method="getRemoteUserList" filterable remote placeholder="搜索用户">
<el-option v-for="(item,index) in userListOptions" :key="item+index" :label="item" :value="item"/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="10">
<el-form-item label-width="80px" label="发布时间:" class="postInfo-container-item">
<el-date-picker v-model="postForm.display_time" type="datetime" format="yyyy-MM-dd HH:mm:ss" placeholder="选择日期时间"/>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label-width="60px" label="重要性:" class="postInfo-container-item">
<el-rate
v-model="postForm.importance"
:max="3"
:colors="['#99A9BF', '#F7BA2A', '#FF9900']"
:low-threshold="1"
:high-threshold="3"
style="margin-top:8px;"/>
</el-form-item>
</el-col>
</el-row>
</div>
</el-col>
</el-row>
<el-form-item style="margin-bottom: 40px;" label-width="45px" label="摘要:">
<el-input :rows="1" v-model="postForm.content_short" type="textarea" class="article-textarea" autosize placeholder="请输入内容"/>
<span v-show="contentShortLength" class="word-counter">{{ contentShortLength }}</span>
</el-form-item>
<el-form-item prop="content" style="margin-bottom: 30px;">
<Tinymce ref="editor" :height="400" v-model="postForm.content" />
</el-form-item>
<el-form-item prop="image_uri" style="margin-bottom: 30px;">
<Upload v-model="postForm.image_uri" />
</el-form-item>
</div>
</el-form>
</div>
</template>
<script>
import Tinymce from '@/components/Tinymce'
import Upload from '@/components/Upload/singleImage3'
import MDinput from '@/components/MDinput'
import Sticky from '@/components/Sticky' // 粘性header组件
import { validateURL } from '@/utils/validate'
import { fetchArticle } from '@/api/article'
import { userSearch } from '@/api/remoteSearch'
import Warning from './Warning'
import { CommentDropdown, PlatformDropdown, SourceUrlDropdown } from './Dropdown'
const defaultForm = {
status: 'draft',
title: '', // 文章题目
content: '', // 文章内容
content_short: '', // 文章摘要
source_uri: '', // 文章外链
image_uri: '', // 文章图片
display_time: undefined, // 前台展示时间
id: undefined,
platforms: ['a-platform'],
comment_disabled: false,
importance: 0
}
export default {
name: 'ArticleDetail',
components: { Tinymce, MDinput, Upload, Sticky, Warning, CommentDropdown, PlatformDropdown, SourceUrlDropdown },
props: {
isEdit: {
type: Boolean,
default: false
}
},
data() {
const validateRequire = (rule, value, callback) => {
if (value === '') {
this.$message({
message: rule.field + '为必传项',
type: 'error'
})
callback(new Error(rule.field + '为必传项'))
} else {
callback()
}
}
const validateSourceUri = (rule, value, callback) => {
if (value) {
if (validateURL(value)) {
callback()
} else {
this.$message({
message: '外链url填写不正确',
type: 'error'
})
callback(new Error('外链url填写不正确'))
}
} else {
callback()
}
}
return {
postForm: Object.assign({}, defaultForm),
loading: false,
userListOptions: [],
rules: {
image_uri: [{ validator: validateRequire }],
title: [{ validator: validateRequire }],
content: [{ validator: validateRequire }],
source_uri: [{ validator: validateSourceUri, trigger: 'blur' }]
},
tempRoute: {}
}
},
computed: {
contentShortLength() {
return this.postForm.content_short.length
},
lang() {
return this.$store.getters.language
}
},
created() {
if (this.isEdit) {
const id = this.$route.params && this.$route.params.id
this.fetchData(id)
} else {
this.postForm = Object.assign({}, defaultForm)
}
// Why need to make a copy of this.$route here?
// Because if you enter this page and quickly switch tag, may be in the execution of the setTagsViewTitle function, this.$route is no longer pointing to the current page
// https://github.com/PanJiaChen/vue-element-admin/issues/1221
this.tempRoute = Object.assign({}, this.$route)
},
methods: {
fetchData(id) {
fetchArticle(id).then(response => {
this.postForm = response.data
// Just for test
this.postForm.title += ` Article Id:${this.postForm.id}`
this.postForm.content_short += ` Article Id:${this.postForm.id}`
// Set tagsview title
this.setTagsViewTitle()
}).catch(err => {
console.log(err)
})
},
setTagsViewTitle() {
const title = this.lang === 'zh' ? '编辑文章' : 'Edit Article'
const route = Object.assign({}, this.tempRoute, { title: `${title}-${this.postForm.id}` })
this.$store.dispatch('updateVisitedView', route)
},
submitForm() {
this.postForm.display_time = parseInt(this.display_time / 1000)
console.log(this.postForm)
this.$refs.postForm.validate(valid => {
if (valid) {
this.loading = true
this.$notify({
title: '成功',
message: '发布文章成功',
type: 'success',
duration: 2000
})
this.postForm.status = 'published'
this.loading = false
} else {
console.log('error submit!!')
return false
}
})
},
draftForm() {
if (this.postForm.content.length === 0 || this.postForm.title.length === 0) {
this.$message({
message: '请填写必要的标题和内容',
type: 'warning'
})
return
}
this.$message({
message: '保存成功',
type: 'success',
showClose: true,
duration: 1000
})
this.postForm.status = 'draft'
},
getRemoteUserList(query) {
userSearch(query).then(response => {
if (!response.data.items) return
this.userListOptions = response.data.items.map(v => v.name)
})
}
}
}
</script>
<style rel="stylesheet/scss" lang="scss" scoped>
@import "~@/styles/mixin.scss";
.createPost-container {
position: relative;
.createPost-main-container {
padding: 40px 45px 20px 50px;
.postInfo-container {
position: relative;
@include clearfix;
margin-bottom: 10px;
.postInfo-container-item {
float: left;
}
}
}
.word-counter {
width: 40px;
position: absolute;
right: -10px;
top: 0px;
}
}
</style>
<template>
<el-dropdown :show-timeout="100" trigger="click">
<el-button plain>{{ !comment_disabled?'评论已打开':'评论已关闭' }}
<i class="el-icon-caret-bottom el-icon--right"/>
</el-button>
<el-dropdown-menu slot="dropdown" class="no-padding">
<el-dropdown-item>
<el-radio-group v-model="comment_disabled" style="padding: 10px;">
<el-radio :label="true">关闭评论</el-radio>
<el-radio :label="false">打开评论</el-radio>
</el-radio-group>
</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</template>
<script>
export default {
props: {
value: {
type: Boolean,
default: false
}
},
computed: {
comment_disabled: {
get() {
return this.value
},
set(val) {
this.$emit('input', val)
}
}
}
}
</script>
<template>
<el-dropdown :hide-on-click="false" :show-timeout="100" trigger="click">
<el-button plain>
平台({{ platforms.length }})
<i class="el-icon-caret-bottom el-icon--right"/>
</el-button>
<el-dropdown-menu slot="dropdown" class="no-border">
<el-checkbox-group v-model="platforms" style="padding: 5px 15px;">
<el-checkbox v-for="item in platformsOptions" :label="item.key" :key="item.key">
{{ item.name }}
</el-checkbox>
</el-checkbox-group>
</el-dropdown-menu>
</el-dropdown>
</template>
<script>
export default {
props: {
value: {
required: true,
default: () => [],
type: Array
}
},
data() {
return {
platformsOptions: [
{ key: 'a-platform', name: 'a-platform' },
{ key: 'b-platform', name: 'b-platform' },
{ key: 'c-platform', name: 'c-platform' }
]
}
},
computed: {
platforms: {
get() {
return this.value
},
set(val) {
this.$emit('input', val)
}
}
}
}
</script>
<template>
<el-dropdown :show-timeout="100" trigger="click">
<el-button plain>
外链
<i class="el-icon-caret-bottom el-icon--right"/>
</el-button>
<el-dropdown-menu slot="dropdown" class="no-padding no-border" style="width:400px">
<el-form-item label-width="0px" style="margin-bottom: 0px" prop="source_uri">
<el-input v-model="source_uri" placeholder="请输入内容">
<template slot="prepend">填写url</template>
</el-input>
</el-form-item>
</el-dropdown-menu>
</el-dropdown>
</template>
<script>
export default {
props: {
value: {
type: String,
default: ''
}
},
computed: {
source_uri: {
get() {
return this.value
},
set(val) {
this.$emit('input', val)
}
}
}
}
</script>
export { default as CommentDropdown } from './Comment'
export { default as PlatformDropdown } from './Platform'
export { default as SourceUrlDropdown } from './SourceUrl'
<template>
<p class="warn-content">
创建和编辑页面是不能被keep-alive 缓存的,因为keep-alive 的include 目前不支持根据路由来缓存,所以目前都是基于component name 来缓存的,如果你想要实现缓存的效果,可以使用localstorage 等浏览器缓存方案。或者不要使用keep-alive
的include,直接缓存所有页面。详情见
<a
href="https://panjiachen.github.io/vue-element-admin-site/guide/essentials/tags-view.html"
target="_blank">文档</a>
</p>
</template>
<template>
<article-detail :is-edit="false"/>
</template>
<script>
import ArticleDetail from './components/ArticleDetail'
export default {
name: 'CreateForm',
components: { ArticleDetail }
}
</script>
<template>
<article-detail :is-edit="true"/>
</template>
<script>
import ArticleDetail from './components/ArticleDetail'
export default {
name: 'EditForm',
components: { ArticleDetail }
}
</script>
<template>
<div class="app-container">
<el-table v-loading="listLoading" :data="list" border fit highlight-current-row style="width: 100%">
<el-table-column align="center" label="ID" width="80">
<template slot-scope="scope">
<span>{{ scope.row.id }}</span>
</template>
</el-table-column>
<el-table-column width="180px" align="center" label="Date">
<template slot-scope="scope">
<span>{{ scope.row.timestamp | parseTime('{y}-{m}-{d} {h}:{i}') }}</span>
</template>
</el-table-column>
<el-table-column width="120px" align="center" label="Author">
<template slot-scope="scope">
<span>{{ scope.row.author }}</span>
</template>
</el-table-column>
<el-table-column width="100px" label="Importance">
<template slot-scope="scope">
<svg-icon v-for="n in +scope.row.importance" :key="n" icon-class="star" class="meta-item__icon"/>
</template>
</el-table-column>
<el-table-column class-name="status-col" label="Status" width="110">
<template slot-scope="scope">
<el-tag :type="scope.row.status | statusFilter">{{ scope.row.status }}</el-tag>
</template>
</el-table-column>
<el-table-column min-width="300px" label="Title">
<template slot-scope="scope">
<router-link :to="'/example/edit/'+scope.row.id" class="link-type">
<span>{{ scope.row.title }}</span>
</router-link>
</template>
</el-table-column>
<el-table-column align="center" label="Actions" width="120">
<template slot-scope="scope">
<router-link :to="'/example/edit/'+scope.row.id">
<el-button type="primary" size="small" icon="el-icon-edit">Edit</el-button>
</router-link>
</template>
</el-table-column>
</el-table>
<pagination v-show="total>0" :total="total" :page.sync="listQuery.page" :limit.sync="listQuery.limit" @pagination="getList" />
</div>
</template>
<script>
import { fetchList } from '@/api/article'
import Pagination from '@/components/Pagination' // Secondary package based on el-pagination
export default {
name: 'ArticleList',
components: { Pagination },
filters: {
statusFilter(status) {
const statusMap = {
published: 'success',
draft: 'info',
deleted: 'danger'
}
return statusMap[status]
}
},
data() {
return {
list: null,
total: 0,
listLoading: true,
listQuery: {
page: 1,
limit: 20
}
}
},
created() {
this.getList()
},
methods: {
getList() {
this.listLoading = true
fetchList(this.listQuery).then(response => {
this.list = response.data.items
this.total = response.data.total
this.listLoading = false
})
},
handleSizeChange(val) {
this.listQuery.limit = val
this.getList()
},
handleCurrentChange(val) {
this.listQuery.page = val
this.getList()
}
}
}
</script>
<style scoped>
.edit-input {
padding-right: 100px;
}
.cancel-btn {
position: absolute;
right: 15px;
top: 10px;
}
</style>
<template>
<div style="display:inline-block;">
<label class="radio-label">Cell Auto-Width: </label>
<el-radio-group v-model="autoWidth">
<el-radio :label="true" border>True</el-radio>
<el-radio :label="false" border>False</el-radio>
</el-radio-group>
</div>
</template>
<script>
export default {
props: {
value: {
type: Boolean,
default: true
}
},
computed: {
autoWidth: {
get() {
return this.value
},
set(val) {
this.$emit('input', val)
}
}
}
}
</script>
<template>
<div style="display:inline-block;">
<label class="radio-label">Book Type: </label>
<el-select v-model="bookType" style="width:120px;" >
<el-option
v-for="item in options"
:key="item"
:label="item"
:value="item"/>
</el-select>
</div>
</template>
<script>
export default {
props: {
value: {
type: String,
default: 'xlsx'
}
},
data() {
return {
options: ['xlsx', 'csv', 'txt']
}
},
computed: {
bookType: {
get() {
return this.value
},
set(val) {
this.$emit('input', val)
}
}
}
}
</script>
<template>
<div style="display:inline-block;">
<!-- $t is vue-i18n global function to translate lang -->
<label class="radio-label" style="padding-left:0;">Filename: </label>
<el-input :placeholder="$t('excel.placeholder')" v-model="filename" style="width:340px;" prefix-icon="el-icon-document"/>
</div>
</template>
<script>
export default {
props: {
value: {
type: String,
default: ''
}
},
computed: {
filename: {
get() {
return this.value
},
set(val) {
this.$emit('input', val)
}
}
}
}
</script>
<template>
<!-- $t is vue-i18n global function to translate lang -->
<div class="app-container">
<div>
<FilenameOption v-model="filename" />
<AutoWidthOption v-model="autoWidth" />
<BookTypeOption v-model="bookType" />
<el-button :loading="downloadLoading" style="margin:0 0 20px 20px;" type="primary" icon="document" @click="handleDownload">{{ $t('excel.export') }} Excel</el-button>
<a href="https://panjiachen.github.io/vue-element-admin-site/feature/component/excel.html" target="_blank" style="margin-left:15px;">
<el-tag type="info">Documentation</el-tag>
</a>
</div>
<el-table v-loading="listLoading" :data="list" element-loading-text="拼命加载中" border fit highlight-current-row>
<el-table-column align="center" label="Id" width="95">
<template slot-scope="scope">
{{ scope.$index }}
</template>
</el-table-column>
<el-table-column label="Title">
<template slot-scope="scope">
{{ scope.row.title }}
</template>
</el-table-column>
<el-table-column label="Author" width="110" align="center">
<template slot-scope="scope">
<el-tag>{{ scope.row.author }}</el-tag>
</template>
</el-table-column>
<el-table-column label="Readings" width="115" align="center">
<template slot-scope="scope">
{{ scope.row.pageviews }}
</template>
</el-table-column>
<el-table-column align="center" label="Date" width="220">
<template slot-scope="scope">
<i class="el-icon-time"/>
<span>{{ scope.row.timestamp | parseTime('{y}-{m}-{d} {h}:{i}') }}</span>
</template>
</el-table-column>
</el-table>
</div>
</template>
<script>
import { fetchList } from '@/api/article'
import { parseTime } from '@/utils'
// options components
import FilenameOption from './components/FilenameOption'
import AutoWidthOption from './components/AutoWidthOption'
import BookTypeOption from './components/BookTypeOption'
export default {
name: 'ExportExcel',
components: { FilenameOption, AutoWidthOption, BookTypeOption },
data() {
return {
list: null,
listLoading: true,
downloadLoading: false,
filename: '',
autoWidth: true,
bookType: 'xlsx'
}
},
created() {
this.fetchData()
},
methods: {
fetchData() {
this.listLoading = true
fetchList().then(response => {
this.list = response.data.items
this.listLoading = false
})
},
handleDownload() {
this.downloadLoading = true
import('@/vendor/Export2Excel').then(excel => {
const tHeader = ['Id', 'Title', 'Author', 'Readings', 'Date']
const filterVal = ['id', 'title', 'author', 'pageviews', 'display_time']
const list = this.list
const data = this.formatJson(filterVal, list)
excel.export_json_to_excel({
header: tHeader,
data,
filename: this.filename,
autoWidth: this.autoWidth,
bookType: this.bookType
})
this.downloadLoading = false
})
},
formatJson(filterVal, jsonData) {
return jsonData.map(v => filterVal.map(j => {
if (j === 'timestamp') {
return parseTime(v[j])
} else {
return v[j]
}
}))
}
}
}
</script>
<style>
.radio-label {
font-size: 14px;
color: #606266;
line-height: 40px;
padding: 0 12px 0 30px;
}
</style>
<template>
<div class="app-container">
<!-- $t is vue-i18n global function to translate lang -->
<el-input :placeholder="$t('excel.placeholder')" v-model="filename" style="width:340px;" prefix-icon="el-icon-document"/>
<el-button :loading="downloadLoading" style="margin-bottom:20px" type="primary" icon="document" @click="handleDownload">{{ $t('excel.selectedExport') }}</el-button>
<a href="https://panjiachen.github.io/vue-element-admin-site/feature/component/excel.html" target="_blank" style="margin-left:15px;">
<el-tag type="info">Documentation</el-tag>
</a>
<el-table
v-loading="listLoading"
ref="multipleTable"
:data="list"
element-loading-text="拼命加载中"
border
fit
highlight-current-row
@selection-change="handleSelectionChange">
<el-table-column type="selection" align="center"/>
<el-table-column align="center" label="Id" width="95">
<template slot-scope="scope">
{{ scope.$index }}
</template>
</el-table-column>
<el-table-column label="Title">
<template slot-scope="scope">
{{ scope.row.title }}
</template>
</el-table-column>
<el-table-column label="Author" width="110" align="center">
<template slot-scope="scope">
<el-tag>{{ scope.row.author }}</el-tag>
</template>
</el-table-column>
<el-table-column label="Readings" width="115" align="center">
<template slot-scope="scope">
{{ scope.row.pageviews }}
</template>
</el-table-column>
<el-table-column align="center" label="PDate" width="220">
<template slot-scope="scope">
<i class="el-icon-time"/>
<span>{{ scope.row.display_time }}</span>
</template>
</el-table-column>
</el-table>
</div>
</template>
<script>
import { fetchList } from '@/api/article'
export default {
name: 'SelectExcel',
data() {
return {
list: null,
listLoading: true,
multipleSelection: [],
downloadLoading: false,
filename: ''
}
},
created() {
this.fetchData()
},
methods: {
fetchData() {
this.listLoading = true
fetchList(this.listQuery).then(response => {
this.list = response.data.items
this.listLoading = false
})
},
handleSelectionChange(val) {
this.multipleSelection = val
},
handleDownload() {
if (this.multipleSelection.length) {
this.downloadLoading = true
import('@/vendor/Export2Excel').then(excel => {
const tHeader = ['Id', 'Title', 'Author', 'Readings', 'Date']
const filterVal = ['id', 'title', 'author', 'pageviews', 'display_time']
const list = this.multipleSelection
const data = this.formatJson(filterVal, list)
excel.export_json_to_excel({
header: tHeader,
data,
filename: this.filename
})
this.$refs.multipleTable.clearSelection()
this.downloadLoading = false
})
} else {
this.$message({
message: 'Please select at least one item',
type: 'warning'
})
}
},
formatJson(filterVal, jsonData) {
return jsonData.map(v => filterVal.map(j => v[j]))
}
}
}
</script>
<template>
<div class="app-container">
<upload-excel-component :on-success="handleSuccess" :before-upload="beforeUpload"/>
<el-table :data="tableData" border highlight-current-row style="width: 100%;margin-top:20px;">
<el-table-column v-for="item of tableHeader" :prop="item" :label="item" :key="item"/>
</el-table>
</div>
</template>
<script>
import UploadExcelComponent from '@/components/UploadExcel/index.vue'
export default {
name: 'UploadExcel',
components: { UploadExcelComponent },
data() {
return {
tableData: [],
tableHeader: []
}
},
methods: {
beforeUpload(file) {
const isLt1M = file.size / 1024 / 1024 < 1
if (isLt1M) {
return true
}
this.$message({
message: 'Please do not upload files larger than 1m in size.',
type: 'warning'
})
return false
},
handleSuccess({ results, header }) {
this.tableData = results
this.tableHeader = header
}
}
}
</script>
const steps = [
{
element: '.hamburger-container',
popover: {
title: 'Hamburger',
description: 'Open && Close sidebar',
position: 'bottom'
}
},
{
element: '.breadcrumb-container',
popover: {
title: 'Breadcrumb',
description: 'Indicate the current page location',
position: 'bottom'
}
},
{
element: '.screenfull',
popover: {
title: 'Screenfull',
description: 'Bring the page into fullscreen',
position: 'left'
}
},
{
element: '.international-icon',
popover: {
title: 'Switch language',
description: 'Switch the system language',
position: 'left'
}
},
{
element: '.theme-switch',
popover: {
title: 'Theme Switch',
description: 'Custom switch system theme',
position: 'left'
}
},
{
element: '.tags-view-container',
popover: {
title: 'Tags view',
description: 'The history of the page you visited',
position: 'bottom'
},
padding: 0
}
]
export default steps
<template>
<div class="app-container">
<p class="warn-content">
{{ $t('guide.description') }}
<a href="https://github.com/kamranahmedse/driver.js" target="_blank">driver.js.
</a>
</p>
<el-button icon="el-icon-question" type="primary" @click.prevent.stop="guide">{{ $t('guide.button') }}</el-button>
</div>
</template>
<script>
import Driver from 'driver.js' // import driver.js
import 'driver.js/dist/driver.min.css' // import driver.js css
import steps from './defineSteps'
export default {
name: 'Guide',
data() {
return {
driver: null
}
},
mounted() {
this.driver = new Driver()
},
methods: {
guide() {
this.driver.defineSteps(steps)
this.driver.start()
}
}
}
</script>
<template>
<div>
<el-card class="box-card" style="margin-top:40px;">
<div slot="header" class="clearfix">
<svg-icon icon-class="international" />
<span style="margin-left:10px;">{{ $t('i18nView.title') }}</span>
</div>
<div>
<el-radio-group v-model="lang" size="small">
<el-radio label="zh" border>简体中文</el-radio>
<el-radio label="en" border>English</el-radio>
<el-radio label="es" border>Español</el-radio>
</el-radio-group>
<el-tag style="margin-top:15px;display:block;" type="info">{{ $t('i18nView.note') }}</el-tag>
</div>
</el-card>
<el-row :gutter="20" style="margin:100px 15px 50px;">
<el-col :span="12" :xs="24">
<div class="block">
<el-date-picker v-model="date" :placeholder="$t('i18nView.datePlaceholder')" type="date"/>
</div>
<div class="block">
<el-select v-model="value" :placeholder="$t('i18nView.selectPlaceholder')">
<el-option
v-for="item in options"
:key="item.value"
:label="item.label"
:value="item.value"/>
</el-select>
</div>
<div class="block">
<el-button class="item-btn" size="small">{{ $t('i18nView.default') }}</el-button>
<el-button class="item-btn" size="small" type="primary">{{ $t('i18nView.primary') }}</el-button>
<el-button class="item-btn" size="small" type="success">{{ $t('i18nView.success') }}</el-button>
<el-button class="item-btn" size="small" type="info">{{ $t('i18nView.info') }}</el-button>
<el-button class="item-btn" size="small" type="warning">{{ $t('i18nView.warning') }}</el-button>
<el-button class="item-btn" size="small" type="danger">{{ $t('i18nView.danger') }}</el-button>
</div>
</el-col>
<el-col :span="12" :xs="24">
<el-table :data="tableData" fit highlight-current-row border style="width: 100%">
<el-table-column :label="$t('i18nView.tableName')" prop="name" width="100" align="center"/>
<el-table-column :label="$t('i18nView.tableDate')" prop="date" width="120" align="center"/>
<el-table-column :label="$t('i18nView.tableAddress')" prop="address"/>
</el-table>
</el-col>
</el-row>
</div>
</template>
<script>
import local from './local'
const viewName = 'i18nView'
export default {
name: 'I18n',
data() {
return {
date: '',
tableData: [{
date: '2016-05-03',
name: 'Tom',
address: 'No. 189, Grove St, Los Angeles'
},
{
date: '2016-05-02',
name: 'Tom',
address: 'No. 189, Grove St, Los Angeles'
},
{
date: '2016-05-04',
name: 'Tom',
address: 'No. 189, Grove St, Los Angeles'
},
{
date: '2016-05-01',
name: 'Tom',
address: 'No. 189, Grove St, Los Angeles'
}],
options: [],
value: ''
}
},
computed: {
lang: {
get() {
return this.$store.state.app.language
},
set(lang) {
this.$i18n.locale = lang
this.$store.dispatch('setLanguage', lang)
}
}
},
watch: {
lang() {
this.setOptions()
}
},
created() {
if (!this.$i18n.getLocaleMessage('en')[viewName]) {
this.$i18n.mergeLocaleMessage('en', local.en)
this.$i18n.mergeLocaleMessage('zh', local.zh)
this.$i18n.mergeLocaleMessage('es', local.es)
}
this.setOptions() // set default select options
},
methods: {
setOptions() {
this.options = [
{
value: '1',
label: this.$t('i18nView.one')
},
{
value: '2',
label: this.$t('i18nView.two')
},
{
value: '3',
label: this.$t('i18nView.three')
}
]
}
}
}
</script>
<style scoped>
.box-card {
width: 600px;
max-width: 100%;
margin: 20px auto;
}
.item-btn{
margin-bottom: 15px;
margin-left: 0px;
}
.block {
padding: 25px;
}
</style>
export default {
zh: {
i18nView: {
title: '切换语言',
note: '本项目国际化基于 vue-i18n',
datePlaceholder: '请选择日期',
selectPlaceholder: '请选择',
tableDate: '日期',
tableName: '姓名',
tableAddress: '地址',
default: '默认按钮',
primary: '主要按钮',
success: '成功按钮',
info: '信息按钮',
warning: '警告按钮',
danger: '危险按钮',
one: '',
two: '',
three: ''
}
},
en: {
i18nView: {
title: 'Switch Language',
note: 'The internationalization of this project is based on vue-i18n',
datePlaceholder: 'Pick a day',
selectPlaceholder: 'Select',
tableDate: 'tableDate',
tableName: 'tableName',
tableAddress: 'tableAddress',
default: 'default:',
primary: 'primary',
success: 'success',
info: 'info',
warning: 'warning',
danger: 'danger',
one: 'One',
two: 'Two',
three: 'Three'
}
},
es: {
i18nView: {
title: 'Switch Language',
note: 'The internationalization of this project is based on vue-i18n',
datePlaceholder: 'Pick a day',
selectPlaceholder: 'Select',
tableDate: 'tableDate',
tableName: 'tableName',
tableAddress: 'tableAddress',
default: 'default:',
primary: 'primary',
success: 'success',
info: 'info',
warning: 'warning',
danger: 'danger',
one: 'One',
two: 'Two',
three: 'Three'
}
}
}
...@@ -89,7 +89,7 @@ export default { ...@@ -89,7 +89,7 @@ export default {
this.$store.dispatch('toggleSideBar') this.$store.dispatch('toggleSideBar')
}, },
logout() { logout() {
this.$store.dispatch('LogOut').then(() => { this.$store.dispatch('FedLogOut').then(() => {
location.reload()// In order to re-instantiate the vue-router object to avoid bugs location.reload()// In order to re-instantiate the vue-router object to avoid bugs
}) })
} }
......
<template >
<div style="padding:30px;">
<el-alert :closable="false" title="menu 1">
<router-view />
</el-alert>
</div>
</template>
<template >
<div style="padding:30px;">
<el-alert :closable="false" title="menu 1-1" type="success">
<router-view />
</el-alert>
</div>
</template>
<template>
<div style="padding:30px;">
<el-alert :closable="false" title="menu 1-2" type="success">
<router-view />
</el-alert>
</div>
</template>
<template functional>
<div style="padding:30px;">
<el-alert :closable="false" title="menu 1-2-1" type="warning" />
</div>
</template>
<template functional>
<div style="padding:30px;">
<el-alert :closable="false" title="menu 1-2-2" type="warning" />
</div>
</template>
<template functional>
<div style="padding:30px;">
<el-alert :closable="false" title="menu 1-3" type="success" />
</div>
</template>
<template>
<div style="padding:30px;">
<el-alert :closable="false" title="menu 2" />
</div>
</template>
const title = 'Plans for the Next Iteration of Vue.js'
const content = `<p>Last week at<a href="https://vuejs.london/summary" rel="nofollow">Vue.js London</a>I gave a brief sneak peek of what’s coming in the next major version of Vue. This post provides an in-depth overview of the plan.</p>
<p><img class=" wscnph" src="https://wpimg.wallstcn.com/b8a1d7be-0b73-41b8-be8c-7c01c93cab66.png" data-wscntype="image" data-wscnh="742" data-wscnw="692" /></p>
<h3>Why a new majorversion?</h3>
<p>Vue 2.0 was released<a href="https://medium.com/the-vue-point/vue-2-0-is-here-ef1f26acf4b8" rel="nofollow">exactly two years ago</a>(how time flies!). During this period, the core has remained backwards compatible with five minor releases. We’ve accumulated a number of ideas that would bring improvements, but they were held off because they would result in breaking changes. At the same time, the JavaScript ecosystem and the language itself has been evolving rapidly. There are greatly improved tools that could enhance our workflow, and many new language features that could unlock simpler, more complete, and more efficient solutions to the problems Vue is trying to solve. What’s more exciting is that we are seeing ES2015 support becoming a baseline for all major evergreen browsers. Vue 3.0 aims to leverage these new language features to make Vue core smaller, faster, and more powerful.</p>
<p>Vue 3.0 is currently in prototyping phase, and we have already implemented a runtime close to feature-parity with 2.x.<strong>Many of the items listed below are either already implemented, or confirmed to be feasible. Ones that are not yet implemented or still in exploration phase are marked with a *.</strong></p>
<h3>The Details</h3>
<h4>High-Level APIChanges</h4>
<blockquote>TL;DR: Everything except render function API and scoped-slots syntax will either remain the same or can be made 2.x compatible via a compatibility build.</blockquote>
<p>Since it’s a new major, there is going to be some breaking changes. However, we take backwards compatibility seriously, so we want to start communicating these changes as soon as possible. Here’s the currently planned public API changes:</p>
<ul><li>Template syntax will remain 99% the same. There may be small tweaks in scoped slots syntax, but other than that we have no plans to change anything else for templates.</li><li>3.0 will support class-based components natively, with the aim to provide an API that is pleasant to use in native ES2015 without requiring any transpilation or stage-x features. Most current options will have a reasonable mapping in the class-based API. Stage-x features such as class fields and decorators can still be used optionally to enhance the authoring experience. In addition, the API is designed with TypeScript type inference in mind. The 3.x codebase will itself be written in TypeScript, and providing improved TypeScript support. (That said, usage of TypeScript in an application is still entirely optional.)</li><li>The 2.x object-based component format will still be supported by internally transforming the object to a corresponding class.</li><li>Mixins will still be supported.*</li><li>Top level APIs will likely receive an overhaul to avoid globally mutating the Vue runtime when installing plugins. Instead, plugins will be applied and scoped to a component tree. This will make it easier to test components that rely on specific plugins, and also make it possible to mount multiple Vue applications on the same page with different plugins, but using the same Vue runtime.*</li><li>Functional components can finally be plain functions —however, async components will now need to be explicitly created via a helper function.</li><li>The part that will receive the most changes is the Virtual DOM format used in render functions. We are currently collecting feedback from major library authors and will be sharing more details as we are more confident of the changes, but as long as you don’t heavily rely on hand-written (non-JSX) render functions in your app, upgrading should be a reasonably straightforward process.</li></ul>
<h4>Source Code Architecture</h4>
<blockquote>TL;DR: better decoupled internal modules, TypeScript, and a codebase that is easier to contribute to.</blockquote>
<p>We are re-writing 3.0 from the ground up for a cleaner and more maintainable architecture, in particular trying to make it easier to contribute to. We are breaking some internal functionalities into individual packages in order to isolate the scope of complexity. For example, the observer module will become its own package, with its own public API and tests. Note this does not affect framework-level API— you will not have to manually import individual bits from multiple packages in order to use Vue. Instead, the final Vue package is assembled using these internal packages.</p>
<p>The codebase is also now written in TypeScript. Although this will make proficiency in TypeScript a pre-requisite for contributing to the new codebase, we believe the type information and IDE support will actually make it easier for a new contributor to make meaningful contributions.</p>
<p>Decoupling the observer and scheduler into separate packages also allows us to easily experiment with alternative implementations of these parts. For example, we can implement an IE11 compatible observer implementation with the same API, or an alternative scheduler that leverages<code>requestIdleCallback</code>to yield to the browser during long updates.*</p>
<p><img class=" wscnph" src="https://wpimg.wallstcn.com/4d0b5fb2-d7f9-48fd-8f1b-03362b534dd9.png" data-wscntype="image" data-wscnh="716" data-wscnw="460" /></p>
<h4>Observation Mechanism</h4>
<blockquote>TL;DR: more complete, precise, efficient and debuggable reactivity tracking &amp; API for creating observables.</blockquote>
<p>3.0 will ship with a Proxy-based observer implementation that provides reactivity tracking with full language coverage. This eliminates a number of limitations of Vue 2’s current implementation based on<code>Object.defineProperty</code>:</p>
<p>The new observer also features the following:</p>
<p>Easily understand why a component is re-rendering</p>
<p><img class=" wscnph" src="https://wpimg.wallstcn.com/a0c9d811-1ef9-4628-8976-f7c1aaa66da0.png" data-wscntype="image" data-wscnh="540" data-wscnw="789" /></p>
<h4>Other Runtime Improvements</h4>
<blockquote>TL;DR: smaller, faster, tree-shakable features, fragments &amp; portals, custom renderer API.</blockquote>
<h4>Compiler Improvements*</h4>
<blockquote>TL;DR: tree-shaking friendly output, more AOT optimizations, parser with better error info and source map support.</blockquote>
<h4>IE11 Support*</h4>
<blockquote>TL;DR: it will be supported, but in a separate build with the same reactivity limitations of Vue 2.x.</blockquote>
<p>The new codebase currently targets evergreen browsers only and assumes baseline native ES2015 support. But alas, we know a lot of our users still need to support IE11 for the foreseeable future. Most of the ES2015 features used can be transpiled / polyfilled for IE11, with the exception for Proxies. Our plan is to implement an alternative observer with the same API, but using the good old ES5<code>Object.defineProperty</code>API. A separate build of Vue 3.x will be distributed using this observer implementation. However, this build will be subject to the same change detection caveats of Vue 2.x and thus not fully compatible with the “modern” build of 3.x. We are aware that this imposes some inconvenience for library authors as they will need to be aware of compatibility for two different builds, but we will make sure to provide clear guidelines on this when we reach that stage.</p>
<h3>How Do We GetThere</h3>
<p>First of all, although we are announcing it today, we do not have a definitive timeline yet. What we do know at the moment is the steps we will be taking to get there:</p>
<h4>1. Internal Feedback for the Runtime Prototype</h4>
<p>This is the phase we are in right now. Currently, we already have a working runtime prototype that includes the new observer, Virtual DOM and component implementation. We have invited a group of authors of influential community projects to provide feedback for the internal changes, and would like to make sure they are comfortable with the changes before moving forward. We want to ensure that important libraries in the ecosystem will be ready at the same time when we release 3.0, so that users relying on those projects can upgrade easily.</p>
<h4>2. Public Feedback viaRFCs</h4>
<p>Once we gain a certain level of confidence in the new design, for each breaking change we will be opening a dedicated RFC issue which includes:</p>
<p>We will anticipate public feedback from the wider community to help us consolidate these ideas.</p>
<h4>3. Introduce Compatible Features in 2.x &amp;2.x-next</h4>
<p>We are not forgetting about 2.x! In fact, we plan to use 2.x to progressively accustom users to the new changes. We will be gradually introducing confirmed API changes into 2.x via opt-in adaptors, and 2.x-next will allow users to try out the new Proxy-based observer.</p>
<p>The last minor release in 2.x will become LTS and continue to receive bug and security fixes for 18 months when 3.0 is released.</p>
<h4>4. AlphaPhase</h4>
<p>Next, we will finish up the compiler and server-side rendering parts of 3.0 and start making alpha releases. These will mostly be for stability testing purposes in small greenfield apps.</p>
<h4>5. BetaPhase</h4>
<p>During beta phase, our main goal is updating support libraries and tools like Vue Router, Vuex, Vue CLI, Vue DevTools and make sure they work smoothly with the new core. We will also be working with major library authors from the community to help them get ready for 3.0.</p>
<h4>6. RCPhase</h4>
<p>Once we consider the API and codebase stable, we will enter RC phase with API freeze. During this phase we will also work on a “compat build”: a build of 3.0 that includes compatibility layers for 2.x API. This build will also ship with a flag you can turn on to emit deprecation warnings for 2.x API usage in your app. The compat build can be used as a guide to upgrade your app to 3.0.</p>
<h4>7. IE11build</h4>
<p>The last task before the final release will be the IE11 compatibility build as mentioned above.</p>
<h4>8. FinalRelease</h4>
<p>In all honesty, we don’t know when this will happen yet, but likely in 2019. Again, we care more about shipping something that is solid and stable rather than hitting specific dates. There is a lot of work to be done, but we are excited for what’s coming next!</p>`
const data = {
title,
content
}
export default data
<template>
<div v-loading.fullscreen.lock="fullscreenLoading" class="main-article" element-loading-text="Efforts to generate PDF">
<div class="article__heading">
<div class="article__heading__title"> {{ article.title }}</div>
</div>
<div style="color: #ccc;">
This article is from Evan You on <a target="_blank" href="https://medium.com/the-vue-point/plans-for-the-next-iteration-of-vue-js-777ffea6fabf">medium</a>
</div>
<div ref="content" class="node-article-content" v-html="article.content"/>
</div>
</template>
<script>
export default {
data() {
return {
article: '',
fullscreenLoading: true
}
},
mounted() {
this.fetchData()
},
methods: {
fetchData() {
import('./content.js').then(data => {
const { title } = data.default
document.title = title
this.article = data.default
setTimeout(() => {
this.fullscreenLoading = false
this.$nextTick(() => {
window.print()
})
}, 3000)
})
}
}
}
</script>
<style rel="stylesheet/scss" lang="scss">
@mixin clearfix {
&:before {
display: table;
content: '';
clear: both;
}
&:after {
display: table;
content: '';
clear: both;
}
}
.main-article {
padding: 20px;
margin: 0 auto;
display: block;
width: 740px;
background: #fff;
}
.article__heading {
position: relative;
padding: 0 0 20px;
overflow: hidden;
}
.article__heading__title {
display: block;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
line-clamp: 2;
word-wrap: break-word;
overflow-wrap: break-word;
font-size: 32px;
line-height: 48px;
font-weight: 600;
color: #333;
overflow: hidden;
}
.node-article-content {
margin: 20px 0 0;
@include clearfix;
font-size: 16px;
color: #333;
letter-spacing: 0.5px;
line-height: 28px;
margin-bottom: 30px;
font-family: medium-content-serif-font, Georgia, Cambria, "Times New Roman", Times, serif;
&> :last-child {
margin-bottom: 0;
}
b,
strong {
font-weight: inherit;
font-weight: bolder;
}
img {
max-width: 100%;
display: block;
margin: 0 auto;
}
p {
font-weight: 400;
font-style: normal;
font-size: 21px;
line-height: 1.58;
letter-spacing: -.003em;
}
ul {
margin-bottom: 30px;
}
li {
--x-height-multiplier: 0.375;
--baseline-multiplier: 0.17;
letter-spacing: .01rem;
font-weight: 400;
font-style: normal;
font-size: 21px;
line-height: 1.58;
letter-spacing: -.003em;
margin-left: 30px;
margin-bottom: 14px;
}
a {
text-decoration: none;
background-repeat: repeat-x;
background-image: linear-gradient(to right, rgba(0, 0, 0, .84) 100%, rgba(0, 0, 0, 0) 0);
background-size: 1px 1px;
background-position: 0 calc(1em + 1px);
padding: 0 6px;
}
code {
background: rgba(0, 0, 0, .05);
padding: 3px 4px;
margin: 0 2px;
font-size: 16px;
display: inline-block;
}
img {
border: 0;
}
/* 解决 IE6-7 图片缩放锯齿问题 */
img {
-ms-interpolation-mode: bicubic;
}
blockquote {
--x-height-multiplier: 0.375;
--baseline-multiplier: 0.17;
font-family: medium-content-serif-font, Georgia, Cambria, "Times New Roman", Times, serif;
letter-spacing: .01rem;
font-weight: 400;
font-style: italic;
font-size: 21px;
line-height: 1.58;
letter-spacing: -.003em;
border-left: 3px solid rgba(0, 0, 0, .84);
padding-left: 20px;
margin-left: -23px;
padding-bottom: 2px;
}
a {
text-decoration: none;
}
h2,
h3,
h4 {
font-size: 34px;
line-height: 1.15;
letter-spacing: -.015em;
margin: 53px 0 0;
}
h4 {
font-size: 26px;
}
}
</style>
<template>
<div class="app-container">
<code style="margin-top:15px;">{{ $t('pdf.tips') }}</code>
<router-link target="_blank" to="/pdf/download">
<el-button type="primary">Click to download PDF</el-button>
</router-link>
</div>
</template>
<template>
<div>
<div style="margin-bottom:15px;">{{ $t('permission.roles') }}{{ roles }}</div>
{{ $t('permission.switchRoles') }}
<el-radio-group v-model="switchRoles">
<el-radio-button label="editor"/>
<el-radio-button label="admin"/>
</el-radio-group>
</div>
</template>
<script>
export default {
computed: {
roles() {
return this.$store.getters.roles
},
switchRoles: {
get() {
return this.roles[0]
},
set(val) {
this.$store.dispatch('ChangeRoles', val).then(() => {
this.$emit('change')
})
}
}
}
}
</script>
<template>
<div class="app-container">
<switch-roles @change="handleRolesChange" />
<div :key="key" style="margin-top:30px;">
<div>
<span v-permission="['admin']" class="permission-alert">
Only
<el-tag class="permission-tag" size="small">admin</el-tag> can see this
</span>
<el-tag v-permission="['admin']" class="permission-sourceCode" type="info">v-permission="['admin']"</el-tag>
</div>
<div>
<span v-permission="['editor']" class="permission-alert">
Only
<el-tag class="permission-tag" size="small">editor</el-tag> can see this
</span>
<el-tag v-permission="['editor']" class="permission-sourceCode" type="info">v-permission="['editor']"</el-tag>
</div>
<div>
<span v-permission="['admin','editor']" class="permission-alert">
Both
<el-tag class="permission-tag" size="small">admin</el-tag> and
<el-tag class="permission-tag" size="small">editor</el-tag> can see this
</span>
<el-tag v-permission="['admin','editor']" class="permission-sourceCode" type="info">v-permission="['admin','editor']"</el-tag>
</div>
</div>
<div :key="'checkPermission'+key" style="margin-top:60px;">
<code>
{{ $t('permission.tips') }}
<br> e.g.
</code>
<el-tabs type="border-card" style="width:550px;">
<el-tab-pane v-if="checkPermission(['admin'])" label="Admin">
Admin can see this
<el-tag class="permission-sourceCode" type="info">v-if="checkPermission(['admin'])"</el-tag>
</el-tab-pane>
<el-tab-pane v-if="checkPermission(['editor'])" label="Editor">
Editor can see this
<el-tag class="permission-sourceCode" type="info">v-if="checkPermission(['editor'])"</el-tag>
</el-tab-pane>
<el-tab-pane v-if="checkPermission(['admin','editor'])" label="Admin-OR-Editor">
Both admin or editor can see this
<el-tag class="permission-sourceCode" type="info">v-if="checkPermission(['admin','editor'])"</el-tag>
</el-tab-pane>
</el-tabs>
</div>
</div>
</template>
<script>
import permission from '@/directive/permission/index.js' // 权限判断指令
import checkPermission from '@/utils/permission' // 权限判断函数
import SwitchRoles from './components/SwitchRoles'
export default{
name: 'DirectivePermission',
components: { SwitchRoles },
directives: { permission },
data() {
return {
key: 1 // 为了能每次切换权限的时候重新初始化指令
}
},
methods: {
checkPermission,
handleRolesChange() {
this.key++
}
}
}
</script>
<style rel="stylesheet/scss" lang="scss" scoped>
.app-container {
/deep/ .permission-alert {
width: 320px;
margin-top: 15px;
background-color: #f0f9eb;
color: #67c23a;
padding: 8px 16px;
border-radius: 4px;
display: inline-block;
}
/deep/ .permission-sourceCode {
margin-left: 15px;
}
/deep/ .permission-tag {
background-color: #ecf5ff;
}
}
</style>
<template>
<div class="app-container">
<switch-roles @change="handleRolesChange" />
</div>
</template>
<script>
import SwitchRoles from './components/SwitchRoles'
export default{
name: 'PagePermission',
components: { SwitchRoles },
methods: {
handleRolesChange() {
this.$router.push({ path: '/permission/index?' + +new Date() })
}
}
}
</script>
...@@ -216,7 +216,7 @@ export default { ...@@ -216,7 +216,7 @@ export default {
} }
} }
getProjects(this.listQuery).then(res => { getProjects(this.listQuery).then(res => {
this.proconfigs = res.data.data this.proconfigs = res.data
this.listLoading = false this.listLoading = false
}) })
}, },
......
<template>
<el-upload :data="dataObj" :multiple="true" :before-upload="beforeUpload" action="https://upload.qbox.me" drag>
<i class="el-icon-upload"/>
<div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
</el-upload>
</template>
<script>
import { getToken } from '@/api/qiniu'
// 获取七牛token 后端通过Access Key,Secret Key,bucket等生成token
// 七牛官方sdk https://developer.qiniu.com/sdk#official-sdk
export default{
data() {
return {
dataObj: { token: '', key: '' },
image_uri: [],
fileList: []
}
},
methods: {
beforeUpload() {
const _self = this
return new Promise((resolve, reject) => {
getToken().then(response => {
const key = response.data.qiniu_key
const token = response.data.qiniu_token
_self._data.dataObj.token = token
_self._data.dataObj.key = key
resolve(true)
}).catch(err => {
console.log(err)
reject(false)
})
})
}
}
}
</script>
<script>
export default {
beforeCreate() {
const { params, query } = this.$route
const { path } = params
this.$router.replace({ path: '/' + path, query })
},
render: function(h) {
return h() // avoid warning message
}
}
</script>
...@@ -117,14 +117,14 @@ export default { ...@@ -117,14 +117,14 @@ export default {
} }
} }
getLog(this.listQuery).then(res => { getLog(this.listQuery).then(res => {
this.logs = res.data.data this.logs = res.data
this.listLoading = false this.listLoading = false
}) })
}, },
getLogCount() { getLogCount() {
getLogCount(this.listQuery).then(res => { getLogCount(this.listQuery).then(res => {
this.count = res.data.data this.count = res.data
}) })
}, },
......
<template>
<div class="icons-container">
<p class="warn-content">
<a href="https://panjiachen.github.io/vue-element-admin-site/guide/advanced/icon.html" target="_blank">Add and use
</a>
</p>
<div class="icons-wrapper">
<div v-for="item of iconsMap" :key="item" @click="handleClipboard(generateIconCode(item),$event)">
<el-tooltip placement="top">
<div slot="content">
{{ generateIconCode(item) }}
</div>
<div class="icon-item">
<svg-icon :icon-class="item" class-name="disabled" />
<span>{{ item }}</span>
</div>
</el-tooltip>
</div>
</div>
</div>
</template>
<script>
import icons from './requireIcons'
import clipboard from '@/utils/clipboard'
export default {
name: 'Icons',
data() {
return {
iconsMap: icons
}
},
methods: {
generateIconCode(symbol) {
return `<svg-icon icon-class="${symbol}" />`
},
handleClipboard(text, event) {
clipboard(text, event)
}
}
}
</script>
<style rel="stylesheet/scss" lang="scss" scoped>
.icons-container {
margin: 10px 20px 0;
overflow: hidden;
.icons-wrapper {
margin: 0 auto;
}
.icon-item {
margin: 20px;
height: 110px;
text-align: center;
width: 110px;
float: left;
font-size: 30px;
color: #24292e;
cursor: pointer;
}
span {
display: block;
font-size: 24px;
margin-top: 10px;
}
.disabled{
pointer-events: none;
}
}
</style>
const req = require.context('../../icons/svg', false, /\.svg$/)
const requireAll = requireContext => requireContext.keys()
const re = /\.\/(.*)\.svg/
const icons = requireAll(req).map(i => {
return i.match(re)[1]
})
export default icons
<template>
<el-table :data="list" border fit highlight-current-row style="width: 100%">
<el-table-column
v-loading="loading"
align="center"
label="ID"
width="65"
element-loading-text="请给我点时间!">
<template slot-scope="scope">
<span>{{ scope.row.id }}</span>
</template>
</el-table-column>
<el-table-column width="180px" align="center" label="Date">
<template slot-scope="scope">
<span>{{ scope.row.timestamp | parseTime('{y}-{m}-{d} {h}:{i}') }}</span>
</template>
</el-table-column>
<el-table-column min-width="300px" label="Title">
<template slot-scope="scope">
<span>{{ scope.row.title }}</span>
<el-tag>{{ scope.row.type }}</el-tag>
</template>
</el-table-column>
<el-table-column width="110px" align="center" label="Author">
<template slot-scope="scope">
<span>{{ scope.row.author }}</span>
</template>
</el-table-column>
<el-table-column width="120px" label="Importance">
<template slot-scope="scope">
<svg-icon v-for="n in +scope.row.importance" :key="n" icon-class="star"/>
</template>
</el-table-column>
<el-table-column align="center" label="Readings" width="95">
<template slot-scope="scope">
<span>{{ scope.row.pageviews }}</span>
</template>
</el-table-column>
<el-table-column class-name="status-col" label="Status" width="110">
<template slot-scope="scope">
<el-tag :type="scope.row.status | statusFilter">{{ scope.row.status }}</el-tag>
</template>
</el-table-column>
</el-table>
</template>
<script>
import { fetchList } from '@/api/article'
export default {
filters: {
statusFilter(status) {
const statusMap = {
published: 'success',
draft: 'info',
deleted: 'danger'
}
return statusMap[status]
}
},
props: {
type: {
type: String,
default: 'CN'
}
},
data() {
return {
list: null,
listQuery: {
page: 1,
limit: 5,
type: this.type,
sort: '+id'
},
loading: false
}
},
created() {
this.getList()
},
methods: {
getList() {
this.loading = true
this.$emit('create') // for test
fetchList(this.listQuery).then(response => {
this.list = response.data.items
this.loading = false
})
}
}
}
</script>
<template>
<div class="tab-container">
<el-tag>mounted times :{{ createdTimes }}</el-tag>
<el-alert :closable="false" style="width:200px;display:inline-block;vertical-align: middle;margin-left:30px;" title="Tab with keep-alive" type="success"/>
<el-tabs v-model="activeName" style="margin-top:15px;" type="border-card">
<el-tab-pane v-for="item in tabMapOptions" :label="item.label" :key="item.key" :name="item.key">
<keep-alive>
<tab-pane v-if="activeName==item.key" :type="item.key" @create="showCreatedTimes"/>
</keep-alive>
</el-tab-pane>
</el-tabs>
</div>
</template>
<script>
import tabPane from './components/tabPane'
export default {
name: 'Tab',
components: { tabPane },
data() {
return {
tabMapOptions: [
{ label: 'China', key: 'CN' },
{ label: 'USA', key: 'US' },
{ label: 'Japan', key: 'JP' },
{ label: 'Eurozone', key: 'EU' }
],
activeName: 'CN',
createdTimes: 0
}
},
methods: {
showCreatedTimes() {
this.createdTimes = this.createdTimes + 1
}
}
}
</script>
<style scoped>
.tab-container{
margin: 30px;
}
</style>
<template>
<div class="app-container">
<div class="filter-container">
<el-input :placeholder="$t('table.title')" v-model="listQuery.title" style="width: 200px;" class="filter-item" @keyup.enter.native="handleFilter"/>
<el-select v-model="listQuery.importance" :placeholder="$t('table.importance')" clearable style="width: 90px" class="filter-item">
<el-option v-for="item in importanceOptions" :key="item" :label="item" :value="item"/>
</el-select>
<el-select v-model="listQuery.type" :placeholder="$t('table.type')" clearable class="filter-item" style="width: 130px">
<el-option v-for="item in calendarTypeOptions" :key="item.key" :label="item.display_name+'('+item.key+')'" :value="item.key"/>
</el-select>
<el-select v-model="listQuery.sort" style="width: 140px" class="filter-item" @change="handleFilter">
<el-option v-for="item in sortOptions" :key="item.key" :label="item.label" :value="item.key"/>
</el-select>
<el-button v-waves class="filter-item" type="primary" icon="el-icon-search" @click="handleFilter">{{ $t('table.search') }}</el-button>
<el-button class="filter-item" style="margin-left: 10px;" type="primary" icon="el-icon-edit" @click="handleCreate">{{ $t('table.add') }}</el-button>
<el-button v-waves :loading="downloadLoading" class="filter-item" type="primary" icon="el-icon-download" @click="handleDownload">{{ $t('table.export') }}</el-button>
<el-checkbox v-model="showReviewer" class="filter-item" style="margin-left:15px;" @change="tableKey=tableKey+1">{{ $t('table.reviewer') }}</el-checkbox>
</div>
<el-table
v-loading="listLoading"
:key="tableKey"
:data="list"
border
fit
highlight-current-row
style="width: 100%;"
@sort-change="sortChange">
<el-table-column :label="$t('table.id')" prop="id" sortable="custom" align="center" width="65">
<template slot-scope="scope">
<span>{{ scope.row.id }}</span>
</template>
</el-table-column>
<el-table-column :label="$t('table.date')" width="150px" align="center">
<template slot-scope="scope">
<span>{{ scope.row.timestamp | parseTime('{y}-{m}-{d} {h}:{i}') }}</span>
</template>
</el-table-column>
<el-table-column :label="$t('table.title')" min-width="150px">
<template slot-scope="scope">
<span class="link-type" @click="handleUpdate(scope.row)">{{ scope.row.title }}</span>
<el-tag>{{ scope.row.type | typeFilter }}</el-tag>
</template>
</el-table-column>
<el-table-column :label="$t('table.author')" width="110px" align="center">
<template slot-scope="scope">
<span>{{ scope.row.author }}</span>
</template>
</el-table-column>
<el-table-column v-if="showReviewer" :label="$t('table.reviewer')" width="110px" align="center">
<template slot-scope="scope">
<span style="color:red;">{{ scope.row.reviewer }}</span>
</template>
</el-table-column>
<el-table-column :label="$t('table.importance')" width="80px">
<template slot-scope="scope">
<svg-icon v-for="n in +scope.row.importance" :key="n" icon-class="star" class="meta-item__icon"/>
</template>
</el-table-column>
<el-table-column :label="$t('table.readings')" align="center" width="95">
<template slot-scope="scope">
<span v-if="scope.row.pageviews" class="link-type" @click="handleFetchPv(scope.row.pageviews)">{{ scope.row.pageviews }}</span>
<span v-else>0</span>
</template>
</el-table-column>
<el-table-column :label="$t('table.status')" class-name="status-col" width="100">
<template slot-scope="scope">
<el-tag :type="scope.row.status | statusFilter">{{ scope.row.status }}</el-tag>
</template>
</el-table-column>
<el-table-column :label="$t('table.actions')" align="center" width="230" class-name="small-padding fixed-width">
<template slot-scope="scope">
<el-button type="primary" size="mini" @click="handleUpdate(scope.row)">{{ $t('table.edit') }}</el-button>
<el-button v-if="scope.row.status!='published'" size="mini" type="success" @click="handleModifyStatus(scope.row,'published')">{{ $t('table.publish') }}
</el-button>
<el-button v-if="scope.row.status!='draft'" size="mini" @click="handleModifyStatus(scope.row,'draft')">{{ $t('table.draft') }}
</el-button>
<el-button v-if="scope.row.status!='deleted'" size="mini" type="danger" @click="handleModifyStatus(scope.row,'deleted')">{{ $t('table.delete') }}
</el-button>
</template>
</el-table-column>
</el-table>
<pagination v-show="total>0" :total="total" :page.sync="listQuery.page" :limit.sync="listQuery.limit" @pagination="getList" />
<el-dialog :title="textMap[dialogStatus]" :visible.sync="dialogFormVisible">
<el-form ref="dataForm" :rules="rules" :model="temp" label-position="left" label-width="70px" style="width: 400px; margin-left:50px;">
<el-form-item :label="$t('table.type')" prop="type">
<el-select v-model="temp.type" class="filter-item" placeholder="Please select">
<el-option v-for="item in calendarTypeOptions" :key="item.key" :label="item.display_name" :value="item.key"/>
</el-select>
</el-form-item>
<el-form-item :label="$t('table.date')" prop="timestamp">
<el-date-picker v-model="temp.timestamp" type="datetime" placeholder="Please pick a date"/>
</el-form-item>
<el-form-item :label="$t('table.title')" prop="title">
<el-input v-model="temp.title"/>
</el-form-item>
<el-form-item :label="$t('table.status')">
<el-select v-model="temp.status" class="filter-item" placeholder="Please select">
<el-option v-for="item in statusOptions" :key="item" :label="item" :value="item"/>
</el-select>
</el-form-item>
<el-form-item :label="$t('table.importance')">
<el-rate v-model="temp.importance" :colors="['#99A9BF', '#F7BA2A', '#FF9900']" :max="3" style="margin-top:8px;"/>
</el-form-item>
<el-form-item :label="$t('table.remark')">
<el-input :autosize="{ minRows: 2, maxRows: 4}" v-model="temp.remark" type="textarea" placeholder="Please input"/>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="dialogFormVisible = false">{{ $t('table.cancel') }}</el-button>
<el-button type="primary" @click="dialogStatus==='create'?createData():updateData()">{{ $t('table.confirm') }}</el-button>
</div>
</el-dialog>
<el-dialog :visible.sync="dialogPvVisible" title="Reading statistics">
<el-table :data="pvData" border fit highlight-current-row style="width: 100%">
<el-table-column prop="key" label="Channel"/>
<el-table-column prop="pv" label="Pv"/>
</el-table>
<span slot="footer" class="dialog-footer">
<el-button type="primary" @click="dialogPvVisible = false">{{ $t('table.confirm') }}</el-button>
</span>
</el-dialog>
</div>
</template>
<script>
import { fetchList, fetchPv, createArticle, updateArticle } from '@/api/article'
import waves from '@/directive/waves' // Waves directive
import { parseTime } from '@/utils'
import Pagination from '@/components/Pagination' // Secondary package based on el-pagination
const calendarTypeOptions = [
{ key: 'CN', display_name: 'China' },
{ key: 'US', display_name: 'USA' },
{ key: 'JP', display_name: 'Japan' },
{ key: 'EU', display_name: 'Eurozone' }
]
// arr to obj ,such as { CN : "China", US : "USA" }
const calendarTypeKeyValue = calendarTypeOptions.reduce((acc, cur) => {
acc[cur.key] = cur.display_name
return acc
}, {})
export default {
name: 'ComplexTable',
components: { Pagination },
directives: { waves },
filters: {
statusFilter(status) {
const statusMap = {
published: 'success',
draft: 'info',
deleted: 'danger'
}
return statusMap[status]
},
typeFilter(type) {
return calendarTypeKeyValue[type]
}
},
data() {
return {
tableKey: 0,
list: null,
total: 0,
listLoading: true,
listQuery: {
page: 1,
limit: 20,
importance: undefined,
title: undefined,
type: undefined,
sort: '+id'
},
importanceOptions: [1, 2, 3],
calendarTypeOptions,
sortOptions: [{ label: 'ID Ascending', key: '+id' }, { label: 'ID Descending', key: '-id' }],
statusOptions: ['published', 'draft', 'deleted'],
showReviewer: false,
temp: {
id: undefined,
importance: 1,
remark: '',
timestamp: new Date(),
title: '',
type: '',
status: 'published'
},
dialogFormVisible: false,
dialogStatus: '',
textMap: {
update: 'Edit',
create: 'Create'
},
dialogPvVisible: false,
pvData: [],
rules: {
type: [{ required: true, message: 'type is required', trigger: 'change' }],
timestamp: [{ type: 'date', required: true, message: 'timestamp is required', trigger: 'change' }],
title: [{ required: true, message: 'title is required', trigger: 'blur' }]
},
downloadLoading: false
}
},
created() {
this.getList()
},
methods: {
getList() {
this.listLoading = true
fetchList(this.listQuery).then(response => {
this.list = response.data.items
this.total = response.data.total
// Just to simulate the time of the request
setTimeout(() => {
this.listLoading = false
}, 1.5 * 1000)
})
},
handleFilter() {
this.listQuery.page = 1
this.getList()
},
handleModifyStatus(row, status) {
this.$message({
message: '操作成功',
type: 'success'
})
row.status = status
},
sortChange(data) {
const { prop, order } = data
if (prop === 'id') {
this.sortByID(order)
}
},
sortByID(order) {
if (order === 'ascending') {
this.listQuery.sort = '+id'
} else {
this.listQuery.sort = '-id'
}
this.handleFilter()
},
resetTemp() {
this.temp = {
id: undefined,
importance: 1,
remark: '',
timestamp: new Date(),
title: '',
status: 'published',
type: ''
}
},
handleCreate() {
this.resetTemp()
this.dialogStatus = 'create'
this.dialogFormVisible = true
this.$nextTick(() => {
this.$refs['dataForm'].clearValidate()
})
},
createData() {
this.$refs['dataForm'].validate((valid) => {
if (valid) {
this.temp.id = parseInt(Math.random() * 100) + 1024 // mock a id
this.temp.author = 'vue-element-admin'
createArticle(this.temp).then(() => {
this.list.unshift(this.temp)
this.dialogFormVisible = false
this.$notify({
title: '成功',
message: '创建成功',
type: 'success',
duration: 2000
})
})
}
})
},
handleUpdate(row) {
this.temp = Object.assign({}, row) // copy obj
this.temp.timestamp = new Date(this.temp.timestamp)
this.dialogStatus = 'update'
this.dialogFormVisible = true
this.$nextTick(() => {
this.$refs['dataForm'].clearValidate()
})
},
updateData() {
this.$refs['dataForm'].validate((valid) => {
if (valid) {
const tempData = Object.assign({}, this.temp)
tempData.timestamp = +new Date(tempData.timestamp) // change Thu Nov 30 2017 16:41:05 GMT+0800 (CST) to 1512031311464
updateArticle(tempData).then(() => {
for (const v of this.list) {
if (v.id === this.temp.id) {
const index = this.list.indexOf(v)
this.list.splice(index, 1, this.temp)
break
}
}
this.dialogFormVisible = false
this.$notify({
title: '成功',
message: '更新成功',
type: 'success',
duration: 2000
})
})
}
})
},
handleDelete(row) {
this.$notify({
title: '成功',
message: '删除成功',
type: 'success',
duration: 2000
})
const index = this.list.indexOf(row)
this.list.splice(index, 1)
},
handleFetchPv(pv) {
fetchPv(pv).then(response => {
this.pvData = response.data.pvData
this.dialogPvVisible = true
})
},
handleDownload() {
this.downloadLoading = true
import('@/vendor/Export2Excel').then(excel => {
const tHeader = ['timestamp', 'title', 'type', 'importance', 'status']
const filterVal = ['timestamp', 'title', 'type', 'importance', 'status']
const data = this.formatJson(filterVal, this.list)
excel.export_json_to_excel({
header: tHeader,
data,
filename: 'table-list'
})
this.downloadLoading = false
})
},
formatJson(filterVal, jsonData) {
return jsonData.map(v => filterVal.map(j => {
if (j === 'timestamp') {
return parseTime(v[j])
} else {
return v[j]
}
}))
}
}
}
</script>
<template>
<div class="app-container">
<!-- Note that row-key is necessary to get a correct row order. -->
<el-table v-loading="listLoading" :data="list" row-key="id" border fit highlight-current-row style="width: 100%">
<el-table-column align="center" label="ID" width="65">
<template slot-scope="scope">
<span>{{ scope.row.id }}</span>
</template>
</el-table-column>
<el-table-column width="180px" align="center" label="Date">
<template slot-scope="scope">
<span>{{ scope.row.timestamp | parseTime('{y}-{m}-{d} {h}:{i}') }}</span>
</template>
</el-table-column>
<el-table-column min-width="300px" label="Title">
<template slot-scope="scope">
<span>{{ scope.row.title }}</span>
</template>
</el-table-column>
<el-table-column width="110px" align="center" label="Author">
<template slot-scope="scope">
<span>{{ scope.row.author }}</span>
</template>
</el-table-column>
<el-table-column width="100px" label="Importance">
<template slot-scope="scope">
<svg-icon v-for="n in +scope.row.importance" :key="n" icon-class="star" class="icon-star"/>
</template>
</el-table-column>
<el-table-column align="center" label="Readings" width="95">
<template slot-scope="scope">
<span>{{ scope.row.pageviews }}</span>
</template>
</el-table-column>
<el-table-column class-name="status-col" label="Status" width="110">
<template slot-scope="scope">
<el-tag :type="scope.row.status | statusFilter">{{ scope.row.status }}</el-tag>
</template>
</el-table-column>
<el-table-column align="center" label="Drag" width="80">
<template slot-scope="{}">
<svg-icon class="drag-handler" icon-class="drag"/>
</template>
</el-table-column>
</el-table>
<!-- $t is vue-i18n global function to translate lang (lang in @/lang) -->
<div class="show-d">{{ $t('table.dragTips1') }} : &nbsp; {{ oldList }}</div>
<div class="show-d">{{ $t('table.dragTips2') }} : {{ newList }}</div>
</div>
</template>
<script>
import { fetchList } from '@/api/article'
import Sortable from 'sortablejs'
export default {
name: 'DragTable',
filters: {
statusFilter(status) {
const statusMap = {
published: 'success',
draft: 'info',
deleted: 'danger'
}
return statusMap[status]
}
},
data() {
return {
list: null,
total: null,
listLoading: true,
listQuery: {
page: 1,
limit: 10
},
sortable: null,
oldList: [],
newList: []
}
},
created() {
this.getList()
},
methods: {
getList() {
this.listLoading = true
fetchList(this.listQuery).then(response => {
this.list = response.data.items
this.total = response.data.total
this.listLoading = false
this.oldList = this.list.map(v => v.id)
this.newList = this.oldList.slice()
this.$nextTick(() => {
this.setSort()
})
})
},
setSort() {
const el = document.querySelectorAll('.el-table__body-wrapper > table > tbody')[0]
this.sortable = Sortable.create(el, {
ghostClass: 'sortable-ghost', // Class name for the drop placeholder,
setData: function(dataTransfer) {
dataTransfer.setData('Text', '')
// to avoid Firefox bug
// Detail see : https://github.com/RubaXa/Sortable/issues/1012
},
onEnd: evt => {
const targetRow = this.list.splice(evt.oldIndex, 1)[0]
this.list.splice(evt.newIndex, 0, targetRow)
// for show the changes, you can delete in you code
const tempIndex = this.newList.splice(evt.oldIndex, 1)[0]
this.newList.splice(evt.newIndex, 0, tempIndex)
}
})
}
}
}
</script>
<style>
.sortable-ghost{
opacity: .8;
color: #fff!important;
background: #42b983!important;
}
</style>
<style scoped>
.icon-star{
margin-right:2px;
}
.drag-handler{
width: 20px;
height: 20px;
cursor: pointer;
}
.show-d{
margin-top: 15px;
}
</style>
<template>
<div class="app-container">
<div class="filter-container">
<el-checkbox-group v-model="checkboxVal">
<el-checkbox label="apple">apple</el-checkbox>
<el-checkbox label="banana">banana</el-checkbox>
<el-checkbox label="orange">orange</el-checkbox>
</el-checkbox-group>
</div>
<el-table :data="tableData" :key="key" border fit highlight-current-row style="width: 100%">
<el-table-column prop="name" label="fruitName" width="180"/>
<el-table-column v-for="fruit in formThead" :key="fruit" :label="fruit">
<template slot-scope="scope">
{{ scope.row[fruit] }}
</template>
</el-table-column>
</el-table>
</div>
</template>
<script>
const defaultFormThead = ['apple', 'banana']
export default {
data() {
return {
tableData: [
{
name: 'fruit-1',
apple: 'apple-10',
banana: 'banana-10',
orange: 'orange-10'
},
{
name: 'fruit-2',
apple: 'apple-20',
banana: 'banana-20',
orange: 'orange-20'
}
],
key: 1, // table key
formTheadOptions: ['apple', 'banana', 'orange'],
checkboxVal: defaultFormThead, // checkboxVal
formThead: defaultFormThead // 默认表头 Default header
}
},
watch: {
checkboxVal(valArr) {
this.formThead = this.formTheadOptions.filter(i => valArr.indexOf(i) >= 0)
this.key = this.key + 1// 为了保证table 每次都会重渲 In order to ensure the table will be re-rendered each time
}
}
}
</script>
<template>
<div class="app-container">
<div style="margin:0 0 5px 20px">{{ $t('table.dynamicTips1') }}</div>
<fixed-thead/>
<div style="margin:30px 0 5px 20px">{{ $t('table.dynamicTips2') }}</div>
<unfixed-thead/>
</div>
</template>
<script>
import fixedThead from './fixedThead'
import unfixedThead from './unfixedThead'
export default {
name: 'DynamicTable',
components: { fixedThead, unfixedThead }
}
</script>
<template>
<div class="app-container">
<div class="filter-container">
<el-checkbox-group v-model="formThead">
<el-checkbox label="apple">apple</el-checkbox>
<el-checkbox label="banana">banana</el-checkbox>
<el-checkbox label="orange">orange</el-checkbox>
</el-checkbox-group>
</div>
<el-table :data="tableData" border fit highlight-current-row style="width: 100%">
<el-table-column prop="name" label="fruitName" width="180"/>
<el-table-column v-for="fruit in formThead" :key="fruit" :label="fruit">
<template slot-scope="scope">
{{ scope.row[fruit] }}
</template>
</el-table-column>
</el-table>
</div>
</template>
<script>
export default {
data() {
return {
tableData: [
{
name: 'fruit-1',
apple: 'apple-10',
banana: 'banana-10',
orange: 'orange-10'
},
{
name: 'fruit-2',
apple: 'apple-20',
banana: 'banana-20',
orange: 'orange-20'
}
],
formThead: ['apple', 'banana']
}
}
}
</script>
<template>
<div class="app-container">
<el-table v-loading="listLoading" :data="list" border fit highlight-current-row style="width: 100%">
<el-table-column align="center" label="ID" width="80">
<template slot-scope="scope">
<span>{{ scope.row.id }}</span>
</template>
</el-table-column>
<el-table-column width="180px" align="center" label="Date">
<template slot-scope="scope">
<span>{{ scope.row.timestamp | parseTime('{y}-{m}-{d} {h}:{i}') }}</span>
</template>
</el-table-column>
<el-table-column width="120px" align="center" label="Author">
<template slot-scope="scope">
<span>{{ scope.row.author }}</span>
</template>
</el-table-column>
<el-table-column width="100px" label="Importance">
<template slot-scope="scope">
<svg-icon v-for="n in +scope.row.importance" :key="n" icon-class="star" class="meta-item__icon"/>
</template>
</el-table-column>
<el-table-column class-name="status-col" label="Status" width="110">
<template slot-scope="scope">
<el-tag :type="scope.row.status | statusFilter">{{ scope.row.status }}</el-tag>
</template>
</el-table-column>
<el-table-column min-width="300px" label="Title">
<template slot-scope="scope">
<template v-if="scope.row.edit">
<el-input v-model="scope.row.title" class="edit-input" size="small"/>
<el-button class="cancel-btn" size="small" icon="el-icon-refresh" type="warning" @click="cancelEdit(scope.row)">cancel</el-button>
</template>
<span v-else>{{ scope.row.title }}</span>
</template>
</el-table-column>
<el-table-column align="center" label="Actions" width="120">
<template slot-scope="scope">
<el-button v-if="scope.row.edit" type="success" size="small" icon="el-icon-circle-check-outline" @click="confirmEdit(scope.row)">Ok</el-button>
<el-button v-else type="primary" size="small" icon="el-icon-edit" @click="scope.row.edit=!scope.row.edit">Edit</el-button>
</template>
</el-table-column>
</el-table>
</div>
</template>
<script>
import { fetchList } from '@/api/article'
export default {
name: 'InlineEditTable',
filters: {
statusFilter(status) {
const statusMap = {
published: 'success',
draft: 'info',
deleted: 'danger'
}
return statusMap[status]
}
},
data() {
return {
list: null,
listLoading: true,
listQuery: {
page: 1,
limit: 10
}
}
},
created() {
this.getList()
},
methods: {
getList() {
this.listLoading = true
fetchList(this.listQuery).then(response => {
const items = response.data.items
this.list = items.map(v => {
this.$set(v, 'edit', false) // https://vuejs.org/v2/guide/reactivity.html
v.originalTitle = v.title // will be used when user click the cancel botton
return v
})
this.listLoading = false
})
},
cancelEdit(row) {
row.title = row.originalTitle
row.edit = false
this.$message({
message: 'The title has been restored to the original value',
type: 'warning'
})
},
confirmEdit(row) {
row.edit = false
row.originalTitle = row.title
this.$message({
message: 'The title has been edited',
type: 'success'
})
}
}
}
</script>
<style scoped>
.edit-input {
padding-right: 100px;
}
.cancel-btn {
position: absolute;
right: 15px;
top: 10px;
}
</style>
/**
* @Author: jianglei
* @Date: 2017-10-12 12:06:49
*/
'use strict'
import Vue from 'vue'
export default function treeToArray(data, expandAll, parent, level, item) {
const marLTemp = []
let tmp = []
Array.from(data).forEach(function(record) {
if (record._expanded === undefined) {
Vue.set(record, '_expanded', expandAll)
}
let _level = 1
if (level !== undefined && level !== null) {
_level = level + 1
}
Vue.set(record, '_level', _level)
// 如果有父元素
if (parent) {
Vue.set(record, 'parent', parent)
// 如果父元素有偏移量,需要计算在this的偏移量中
// 偏移量还与前面同级元素有关,需要加上前面所有元素的长度和
if (!marLTemp[_level]) {
marLTemp[_level] = 0
}
Vue.set(record, '_marginLeft', marLTemp[_level] + parent._marginLeft)
Vue.set(record, '_width', record[item] / parent[item] * parent._width)
// 在本次计算过偏移量后加上自己长度,以供下一个元素使用
marLTemp[_level] += record._width
} else {
// 如果为根
// 初始化偏移量存储map
marLTemp[record.id] = []
// map中是一个数组,存储的是每级的长度和
// 初始情况下为0
marLTemp[record.id][_level] = 0
Vue.set(record, '_marginLeft', 0)
Vue.set(record, '_width', 1)
}
tmp.push(record)
if (record.children && record.children.length > 0) {
const children = treeToArray(record.children, expandAll, record, _level, item)
tmp = tmp.concat(children)
}
})
return tmp
}
<template>
<div class="app-container">
<el-tag style="margin-bottom:20px;">
<a href="https://github.com/PanJiaChen/vue-element-admin/tree/master/src/components/TreeTable" target="_blank">Documentation</a>
</el-tag>
<tree-table :data="data" :eval-func="func" :eval-args="args" :expand-all="expandAll" border>
<el-table-column label="事件">
<template slot-scope="scope">
<span style="color:sandybrown">{{ scope.row.event }}</span>
<el-tag>{{ scope.row.timeLine+'ms' }}</el-tag>
</template>
</el-table-column>
<el-table-column label="时间线">
<template slot-scope="scope">
<el-tooltip :content="scope.row.timeLine+'ms'" effect="dark" placement="left">
<div class="processContainer">
<div
:style="{ width:scope.row._width * 500+'px',
background:scope.row._width>0.5?'rgba(233,0,0,.5)':'rgba(0,0,233,0.5)',
marginLeft:scope.row._marginLeft * 500+'px' }"
class="process">
<span style="display:inline-block"/>
</div>
</div>
</el-tooltip>
</template>
</el-table-column>
<el-table-column label="操作" width="200">
<template slot-scope="scope">
<el-button type="text" @click="message(scope.row)">点击</el-button>
</template>
</el-table-column>
</tree-table>
</div>
</template>
<script>
/**
Auth: Lei.j1ang
Created: 2018/1/19-14:54
*/
import treeTable from '@/components/TreeTable'
import treeToArray from './customEval'
export default {
name: 'CustomTreeTableDemo',
components: { treeTable },
data() {
return {
func: treeToArray,
expandAll: false,
data:
{
id: 1,
event: '事件1',
timeLine: 100,
comment: '',
children: [
{
id: 2,
event: '事件2',
timeLine: 10,
comment: ''
},
{
id: 3,
event: '事件3',
timeLine: 90,
comment: '',
children: [
{
id: 4,
event: '事件4',
timeLine: 5,
comment: ''
},
{
id: 5,
event: '事件5',
timeLine: 10,
comment: ''
},
{
id: 6,
event: '事件6',
timeLine: 75,
comment: '',
children: [
{
id: 7,
event: '事件7',
timeLine: 50,
comment: '',
children: [
{
id: 71,
event: '事件71',
timeLine: 25,
comment: 'xx'
},
{
id: 72,
event: '事件72',
timeLine: 5,
comment: 'xx'
},
{
id: 73,
event: '事件73',
timeLine: 20,
comment: 'xx'
}
]
},
{
id: 8,
event: '事件8',
timeLine: 25,
comment: ''
}
]
}
]
}
]
},
args: [null, null, 'timeLine']
}
},
methods: {
message(row) {
this.$message.info(row.event)
}
}
}
</script>
<template>
<div class="app-container">
<el-tag style="margin-bottom:20px;">
<a href="https://github.com/PanJiaChen/vue-element-admin/tree/master/src/components/TreeTable" target="_blank">Documentation</a>
</el-tag>
<tree-table :data="data" :columns="columns" border/>
</div>
</template>
<script>
/**
Auth: Lei.j1ang
Created: 2018/1/19-14:54
*/
import treeTable from '@/components/TreeTable'
export default {
name: 'TreeTableDemo',
components: { treeTable },
data() {
return {
columns: [
{
text: '事件',
value: 'event',
width: 200
},
{
text: 'ID',
value: 'id'
},
{
text: '时间线',
value: 'timeLine'
},
{
text: '备注',
value: 'comment'
}
],
data: [
{
id: 0,
event: '事件1',
timeLine: 50,
comment: ''
},
{
id: 1,
event: '事件1',
timeLine: 100,
comment: '',
children: [
{
id: 2,
event: '事件2',
timeLine: 10,
comment: ''
},
{
id: 3,
event: '事件3',
timeLine: 90,
comment: '',
children: [
{
id: 4,
event: '事件4',
timeLine: 5,
comment: ''
},
{
id: 5,
event: '事件5',
timeLine: 10,
comment: ''
},
{
id: 6,
event: '事件6',
timeLine: 75,
comment: '',
children: [
{
id: 7,
event: '事件7',
timeLine: 50,
comment: '',
children: [
{
id: 71,
event: '事件71',
timeLine: 25,
comment: 'xx'
},
{
id: 72,
event: '事件72',
timeLine: 5,
comment: 'xx'
},
{
id: 73,
event: '事件73',
timeLine: 20,
comment: 'xx'
}
]
},
{
id: 8,
event: '事件8',
timeLine: 25,
comment: ''
}
]
}
]
}
]
}
]
}
}
}
</script>
<template>
<div class="app-container">
<el-card class="box-card">
<div slot="header">
<a class="link-type link-title" target="_blank" href="https://panjiachen.github.io/vue-element-admin-site/guide/advanced/theme.html">
{{ $t('theme.documentation') }}
</a>
</div>
<div class="box-item">
<span class="field-label">{{ $t('theme.change') }} : </span>
<el-switch v-model="theme"/>
<code style="margin-top:15px;">{{ $t('theme.tips') }}</code>
</div>
</el-card>
<div class="block">
<el-button type="primary">Primary</el-button>
<el-button type="success">Success</el-button>
<el-button type="info">Info</el-button>
<el-button type="warning">Warning</el-button>
<el-button type="danger">Danger</el-button>
</div>
<div class="block">
<el-button type="primary" icon="el-icon-edit"/>
<el-button type="primary" icon="el-icon-share"/>
<el-button type="primary" icon="el-icon-delete"/>
<el-button type="primary" icon="el-icon-search">Search</el-button>
<el-button type="primary">
Upload
<i class="el-icon-upload el-icon-right"/>
</el-button>
</div>
<div class="block">
<el-tag v-for="tag in tags" :type="tag.type" :key="tag.type" class="tag-item">
{{ tag.name }}
</el-tag>
</div>
<div class="block">
<el-radio-group v-model="radio">
<el-radio :label="3">Option A</el-radio>
<el-radio :label="6">Option B</el-radio>
<el-radio :label="9">Option C</el-radio>
</el-radio-group>
</div>
<div class="block">
<el-slider v-model="slideValue"/>
</div>
</div>
</template>
<script>
import { toggleClass } from '@/utils'
import '@/assets/custom-theme/index.css' // the theme changed version element-ui css
export default {
name: 'Theme',
data() {
return {
theme: false,
tags: [
{ name: 'Tag One', type: '' },
{ name: 'Tag Two', type: 'info' },
{ name: 'Tag Three', type: 'success' },
{ name: 'Tag Four', type: 'warning' },
{ name: 'Tag Five', type: 'danger' }
],
slideValue: 50,
radio: 3
}
},
watch: {
theme() {
toggleClass(document.body, 'custom-theme')
}
}
}
</script>
<style scoped>
.field-label{
vertical-align: middle;
}
.box-card {
width: 400px;
max-width: 100%;
margin: 20px auto;
}
.block {
padding: 30px 24px;
}
.tag-item {
margin-right: 15px;
}
</style>
<template>
<div class="app-container">
<!-- $t is vue-i18n global function to translate lang -->
<el-input :placeholder="$t('zip.placeholder')" v-model="filename" style="width:300px;" prefix-icon="el-icon-document"/>
<el-button :loading="downloadLoading" style="margin-bottom:20px;" type="primary" icon="document" @click="handleDownload">{{ $t('zip.export') }} zip</el-button>
<el-table v-loading="listLoading" :data="list" element-loading-text="拼命加载中" border fit highlight-current-row>
<el-table-column align="center" label="ID" width="95">
<template slot-scope="scope">
{{ scope.$index }}
</template>
</el-table-column>
<el-table-column label="Title">
<template slot-scope="scope">
{{ scope.row.title }}
</template>
</el-table-column>
<el-table-column label="Author" width="95" align="center">
<template slot-scope="scope">
<el-tag>{{ scope.row.author }}</el-tag>
</template>
</el-table-column>
<el-table-column label="Readings" width="115" align="center">
<template slot-scope="scope">
{{ scope.row.pageviews }}
</template>
</el-table-column>
<el-table-column align="center" label="Date" width="220">
<template slot-scope="scope">
<i class="el-icon-time"/>
<span>{{ scope.row.display_time }}</span>
</template>
</el-table-column>
</el-table>
</div>
</template>
<script>
import { fetchList } from '@/api/article'
export default {
name: 'ExportZip',
data() {
return {
list: null,
listLoading: true,
downloadLoading: false,
filename: ''
}
},
created() {
this.fetchData()
},
methods: {
fetchData() {
this.listLoading = true
fetchList().then(response => {
this.list = response.data.items
this.listLoading = false
})
},
handleDownload() {
this.downloadLoading = true
import('@/vendor/Export2Zip').then(zip => {
const tHeader = ['Id', 'Title', 'Author', 'Readings', 'Date']
const filterVal = ['id', 'title', 'author', 'pageviews', 'display_time']
const list = this.list
const data = this.formatJson(filterVal, list)
zip.export_txt_to_zip(tHeader, data, this.filename, this.filename)
this.downloadLoading = false
})
},
formatJson(filterVal, jsonData) {
return jsonData.map(v => filterVal.map(j => v[j]))
}
}
}
</script>
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment