Merge pull request #1 from PanJiaChen/master

同步
This commit is contained in:
yluoyu 2017-11-16 01:07:57 -06:00 committed by GitHub
commit b23a6449b9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
64 changed files with 718 additions and 378 deletions

1
.gitignore vendored
View File

@ -8,3 +8,4 @@ test/unit/coverage
test/e2e/reports test/e2e/reports
selenium-debug.log selenium-debug.log
.idea .idea
package-lock.json

View File

@ -2,7 +2,7 @@
module.exports = { module.exports = {
"plugins": { "plugins": {
// to edit target browsers: use "browserlist" field in package.json // to edit target browsers: use "browserslist" field in package.json
"autoprefixer": {} "autoprefixer": {}
} }
} }

View File

@ -32,8 +32,9 @@
- ECharts - ECharts
- 401, 404 error page - 401, 404 error page
- Error log - Error log
- Exporting to Excel - Export Excel
- Upload Excel - Upload Excel
- Export Zip
- Table example - Table example
- Interactive table example - Interactive table example
- Drag & drop table example - Drag & drop table example
@ -47,6 +48,7 @@
- screenfull - screenfull
- markdown2html - markdown2html
- views-tab - views-tab
- clipboard
## Development ## Development

View File

@ -22,6 +22,8 @@
**注意该项目目前使用element-ui@1.4.2版本,所以最低兼容 Vue 2.3.0** **注意该项目目前使用element-ui@1.4.2版本,所以最低兼容 Vue 2.3.0**
楼主这里有一份调查[问卷](https://www.wjx.cn/m/16866569.aspx) 有空请填写一下,以表对本项目的支持~ps:不是给这个调查问卷网站做广告,所以填完问卷不用点上面抽奖有的没的那些东西
## 前言 ## 前言
> 这半年来一直在用vue写管理后台目前后台已经有百来个页面十几种权限但维护成本依然很低所以准备开源分享一下后台开发的经验和成果。目前的技术栈主要的采用vue+element+axios由webpack2打包。由于是个人项目所以数据请求都是用了mockjs模拟。注意在此项目基础上改造开发时请移除mock文件。 > 这半年来一直在用vue写管理后台目前后台已经有百来个页面十几种权限但维护成本依然很低所以准备开源分享一下后台开发的经验和成果。目前的技术栈主要的采用vue+element+axios由webpack2打包。由于是个人项目所以数据请求都是用了mockjs模拟。注意在此项目基础上改造开发时请移除mock文件。
@ -63,6 +65,7 @@
- 401404错误页面 - 401404错误页面
- 错误日志 - 错误日志
- 导出excel - 导出excel
- zip
- 前端可视化excel - 前端可视化excel
- table example - table example
- 动态table example - 动态table example
@ -78,6 +81,7 @@
- screenfull - screenfull
- markdown2html - markdown2html
- views-tab - views-tab
- clipboard
## 开发 ## 开发

View File

@ -9,9 +9,7 @@ var webpack = require('webpack');
var config = require('../config'); var config = require('../config');
var webpackConfig = require('./webpack.prod.conf'); var webpackConfig = require('./webpack.prod.conf');
console.log(process.env.NODE_ENV) var spinner = ora('building for ' + process.env.NODE_ENV + ' of ' + process.env.env_config+ ' mode...' )
var spinner = ora('building for ' + process.env.NODE_ENV + '...')
spinner.start() spinner.start()

View File

@ -1,4 +1,5 @@
require('./check-versions')(); // 检查 Node 和 npm 版本 require('./check-versions')(); // 检查 Node 和 npm 版本
var config = require('../config'); var config = require('../config');
if (!process.env.NODE_ENV) { if (!process.env.NODE_ENV) {
process.env.NODE_ENV = JSON.parse(config.dev.env.NODE_ENV) process.env.NODE_ENV = JSON.parse(config.dev.env.NODE_ENV)
@ -28,8 +29,8 @@ var devMiddleware = require('webpack-dev-middleware')(compiler, {
}); });
var hotMiddleware = require('webpack-hot-middleware')(compiler, { var hotMiddleware = require('webpack-hot-middleware')(compiler, {
log: () => { log: false,
} heartbeat: 2000
}); });
// force page reload when html-webpack-plugin template changes // force page reload when html-webpack-plugin template changes
@ -40,8 +41,6 @@ compiler.plugin('compilation', function (compilation) {
}) })
}); });
// compiler.apply(new DashboardPlugin());
// proxy api requests // proxy api requests
Object.keys(proxyTable).forEach(function (context) { Object.keys(proxyTable).forEach(function (context) {
var options = proxyTable[context] var options = proxyTable[context]
@ -67,18 +66,26 @@ app.use(staticPath, express.static('./static'));
var uri = 'http://localhost:' + port var uri = 'http://localhost:' + port
devMiddleware.waitUntilValid(function () { var _resolve
var readyPromise = new Promise(resolve => {
_resolve = resolve
})
console.log('> Starting dev server...')
devMiddleware.waitUntilValid(() => {
console.log('> Listening at ' + uri + '\n') console.log('> Listening at ' + uri + '\n')
});
module.exports = app.listen(port, function (err) {
if (err) {
console.log(err);
return
}
// when env is testing, don't need open it // when env is testing, don't need open it
if (autoOpenBrowser && process.env.NODE_ENV !== 'testing') { if (autoOpenBrowser && process.env.NODE_ENV !== 'testing') {
opn(uri) opn(uri)
} }
}); _resolve()
})
var server = app.listen(port)
module.exports = {
ready: readyPromise,
close: () => {
server.close()
}
}

View File

@ -14,7 +14,7 @@ module.exports = {
output: { output: {
path: config.build.assetsRoot, path: config.build.assetsRoot,
filename: '[name].js', filename: '[name].js',
publicPath: process.env.NODE_ENV !== 'development' ? config.build.assetsPublicPath : config.dev.assetsPublicPath publicPath: process.env.NODE_ENV === 'production' ? config.build.assetsPublicPath : config.dev.assetsPublicPath
}, },
resolve: { resolve: {
extensions: ['.js', '.vue', '.json'], extensions: ['.js', '.vue', '.json'],

View File

@ -9,7 +9,7 @@ var HtmlWebpackPlugin = require('html-webpack-plugin')
var ExtractTextPlugin = require('extract-text-webpack-plugin') var ExtractTextPlugin = require('extract-text-webpack-plugin')
var OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin') var OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin')
var env = process.env.NODE_ENV === 'production' ? config.build.prodEnv : config.build.sitEnv var env = config.build[process.env.env_config+'Env']
function resolveApp(relativePath) { function resolveApp(relativePath) {
return path.resolve(relativePath); return path.resolve(relativePath);
@ -95,6 +95,14 @@ var webpackConfig = merge(baseWebpackConfig, {
return context && (context.indexOf('echarts') >= 0 || context.indexOf('zrender') >= 0); return context && (context.indexOf('echarts') >= 0 || context.indexOf('zrender') >= 0);
} }
}), }),
// split xlsx into its own file
new webpack.optimize.CommonsChunkPlugin({
async: 'xlsx',
minChunks(module) {
var context = module.context;
return context && (context.indexOf('xlsx') >= 0);
}
}),
// extract webpack runtime and module manifest to its own file in order to // extract webpack runtime and module manifest to its own file in order to
// prevent vendor hash from being updated whenever app bundle is updated // prevent vendor hash from being updated whenever app bundle is updated
new webpack.optimize.CommonsChunkPlugin({ new webpack.optimize.CommonsChunkPlugin({
@ -109,9 +117,11 @@ var webpackConfig = merge(baseWebpackConfig, {
}]) }])
] ]
}) })
if (config.build.bundleAnalyzerReport) { if (config.build.bundleAnalyzerReport) {
var BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin var BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
webpackConfig.plugins.push(new BundleAnalyzerPlugin()) webpackConfig.plugins.push(new BundleAnalyzerPlugin())
} }
module.exports = webpackConfig module.exports = webpackConfig

View File

@ -1,5 +1,6 @@
module.exports = { module.exports = {
NODE_ENV: '"development"', NODE_ENV: '"development"',
ENV_CONFIG: '"dev"',
BASE_API: '"https://api-dev"', BASE_API: '"https://api-dev"',
APP_ORIGIN: '"https://wallstreetcn.com"' APP_ORIGIN: '"https://wallstreetcn.com"'
} }

View File

@ -1,5 +1,6 @@
module.exports = { module.exports = {
NODE_ENV: '"production"', NODE_ENV: '"production"',
ENV_CONFIG: '"prod"',
BASE_API: '"https://api-prod"', BASE_API: '"https://api-prod"',
APP_ORIGIN: '"https://wallstreetcn.com"' APP_ORIGIN: '"https://wallstreetcn.com"'
}; };

View File

@ -1,5 +1,6 @@
module.exports = { module.exports = {
NODE_ENV: '"production"', NODE_ENV: '"production"',
ENV_CONFIG: '"sit"',
BASE_API: '"https://api-sit"', BASE_API: '"https://api-sit"',
APP_ORIGIN: '"https://wallstreetcn.com"' APP_ORIGIN: '"https://wallstreetcn.com"'
}; };

View File

@ -1,22 +1,23 @@
{ {
"name": "juicy", "name": "juicy",
"version": "2.1.0", "version": "2.2.1",
"description": "A Vue.js admin", "description": "A Vue.js admin",
"author": "Pan <panfree23@gmail.com>", "author": "Pan <panfree23@gmail.com>",
"license": "MIT", "license": "MIT",
"private": true, "private": true,
"scripts": { "scripts": {
"dev": "node build/dev-server.js", "dev": "node build/dev-server.js",
"build:prod": "cross-env NODE_ENV=production node build/build.js", "build:prod": "cross-env NODE_ENV=production env_config=prod node build/build.js",
"build:sit": "cross-env NODE_ENV=sit node build/build.js", "build:sit": "cross-env NODE_ENV=production env_config=sit node build/build.js",
"build:sit-preview": "cross-env NODE_ENV=sit npm_config_preview=true npm_config_report=true node build/build.js", "build:sit-preview": "cross-env NODE_ENV=production env_config=sit npm_config_preview=true npm_config_report=true node build/build.js",
"lint": "eslint --ext .js,.vue src" "lint": "eslint --ext .js,.vue src"
}, },
"dependencies": { "dependencies": {
"axios": "0.16.2", "axios": "0.16.2",
"clipboard": "1.7.1",
"codemirror": "5.26.0", "codemirror": "5.26.0",
"dropzone": "5.1.0", "dropzone": "5.1.0",
"echarts": "3.6.2", "echarts": "3.8.2",
"element-ui": "1.4.2", "element-ui": "1.4.2",
"file-saver": "1.3.3", "file-saver": "1.3.3",
"js-cookie": "2.1.4", "js-cookie": "2.1.4",
@ -35,7 +36,8 @@
"vue-splitpane": "^1.0.0", "vue-splitpane": "^1.0.0",
"vuedraggable": "2.14.1", "vuedraggable": "2.14.1",
"vuex": "2.3.1", "vuex": "2.3.1",
"xlsx": "^0.10.8" "xlsx": "^0.10.8",
"jszip": "^3.1.4"
}, },
"devDependencies": { "devDependencies": {
"autoprefixer": "7.1.1", "autoprefixer": "7.1.1",
@ -90,7 +92,7 @@
"node": ">= 4.0.0", "node": ">= 4.0.0",
"npm": ">= 3.0.0" "npm": ">= 3.0.0"
}, },
"browserlist": [ "browserslist": [
"> 1%", "> 1%",
"last 2 versions", "last 2 versions",
"not ie <= 8" "not ie <= 8"

View File

@ -1,144 +1,110 @@
<template> <template>
<div class="material-input__component" :class="computedClasses"> <div class="material-input__component" :class="computedClasses">
<input v-if="type === 'email'" type="email" class="material-input" :name="name" :id="id" :placeholder="placeholder" v-model="valueCopy" <div :class="{iconClass:icon}">
:readonly="readonly" :disabled="disabled" :autocomplete="autocomplete" :required="required" @focus="handleFocus(true)" <i class="el-input__icon material-input__icon" :class="['el-icon-' + icon]" v-if="icon"></i>
@blur="handleFocus(false)" @input="handleModelInput"> <input v-if="type === 'email'" type="email" class="material-input" :name="name" :placeholder="fillPlaceHolder" v-model="currentValue"
<input v-if="type === 'url'" type="url" class="material-input" :name="name" :id="id" :placeholder="placeholder" v-model="valueCopy" :readonly="readonly" :disabled="disabled" :autoComplete="autoComplete" :required="required" @focus="handleMdFocus"
:readonly="readonly" :disabled="disabled" :autocomplete="autocomplete" :required="required" @focus="handleFocus(true)" @blur="handleMdBlur" @input="handleModelInput">
@blur="handleFocus(false)" @input="handleModelInput"> <input v-if="type === 'url'" type="url" class="material-input" :name="name" :placeholder="fillPlaceHolder" v-model="currentValue"
<input v-if="type === 'number'" type="number" class="material-input" :name="name" :id="id" :placeholder="placeholder" v-model="valueCopy" :readonly="readonly" :disabled="disabled" :autoComplete="autoComplete" :required="required" @focus="handleMdFocus"
:readonly="readonly" :disabled="disabled" :autocomplete="autocomplete" :max="max" :min="min" :minlength="minlength" :maxlength="maxlength" @blur="handleMdBlur" @input="handleModelInput">
:required="required" @focus="handleFocus(true)" @blur="handleFocus(false)" @input="handleModelInput"> <input v-if="type === 'number'" type="number" class="material-input" :name="name" :placeholder="fillPlaceHolder" v-model="currentValue"
<input v-if="type === 'password'" type="password" class="material-input" :name="name" :id="id" :placeholder="placeholder" :step="step" :readonly="readonly" :disabled="disabled" :autoComplete="autoComplete" :max="max" :min="min" :minlength="minlength"
v-model="valueCopy" :readonly="readonly" :disabled="disabled" :autocomplete="autocomplete" :max="max" :min="min" :required="required" :maxlength="maxlength" :required="required" @focus="handleMdFocus" @blur="handleMdBlur" @input="handleModelInput">
@focus="handleFocus(true)" @blur="handleFocus(false)" @input="handleModelInput"> <input v-if="type === 'password'" type="password" class="material-input" :name="name" :placeholder="fillPlaceHolder" v-model="currentValue"
<input v-if="type === 'tel'" type="tel" class="material-input" :name="name" :id="id" :placeholder="placeholder" v-model="valueCopy" :readonly="readonly" :disabled="disabled" :autoComplete="autoComplete" :max="max" :min="min" :required="required" @focus="handleMdFocus"
:readonly="readonly" :disabled="disabled" :autocomplete="autocomplete" :required="required" @focus="handleFocus(true)" @blur="handleMdBlur" @input="handleModelInput">
@blur="handleFocus(false)" @input="handleModelInput"> <input v-if="type === 'tel'" type="tel" class="material-input" :name="name" :placeholder="fillPlaceHolder" v-model="currentValue"
<input v-if="type === 'text'" type="text" class="material-input" :name="name" :id="id" :placeholder="placeholder" v-model="valueCopy" :readonly="readonly" :disabled="disabled" :autoComplete="autoComplete" :required="required" @focus="handleMdFocus"
:readonly="readonly" :disabled="disabled" :autocomplete="autocomplete" :minlength="minlength" :maxlength="maxlength" @blur="handleMdBlur" @input="handleModelInput">
:required="required" @focus="handleFocus(true)" @blur="handleFocus(false)" @input="handleModelInput"> <input v-if="type === 'text'" type="text" class="material-input" :name="name" :placeholder="fillPlaceHolder" v-model="currentValue"
:readonly="readonly" :disabled="disabled" :autoComplete="autoComplete" :minlength="minlength" :maxlength="maxlength"
:required="required" @focus="handleMdFocus" @blur="handleMdBlur" @input="handleModelInput">
<span class="material-input-bar"></span> <span class="material-input-bar"></span>
<label class="material-label"> <label class="material-label">
<slot></slot> <slot></slot>
</label> </label>
<div v-if="errorMessages" class="material-errors">
<div v-for="error in computedErrors" class="material-error" :key='error'>
{{ error }}
</div>
</div> </div>
</div> </div>
</template> </template>
<script> <script>
// source:https://github.com/wemake-services/vue-material-input/blob/master/src/components/MaterialInput.vue // source:https://github.com/wemake-services/vue-material-input/blob/master/src/components/MaterialInput.vue
export default { export default {
name: 'material-input', name: 'md-input',
computed: { props: {
computedErrors() { icon: String,
return typeof this.errorMessages === 'string' name: String,
? [this.errorMessages] : this.errorMessages type: {
type: String,
default: 'text'
}, },
value: [String, Number],
placeholder: String,
readonly: Boolean,
disabled: Boolean,
min: String,
max: String,
step: String,
minlength: Number,
maxlength: Number,
required: {
type: Boolean,
default: true
},
autoComplete: {
type: String,
default: 'off'
},
validateEvent: {
type: Boolean,
default: true
}
},
computed: {
computedClasses() { computedClasses() {
return { return {
'material--active': this.focus, 'material--active': this.focus,
'material--disabled': this.disabled, 'material--disabled': this.disabled,
'material--has-errors': Boolean(!this.valid || (this.errorMessages && this.errorMessages.length)), 'material--raised': Boolean(this.focus || this.currentValue) // has value
'material--raised': Boolean(this.focus || this.valueCopy || // has value
(this.placeholder && !this.valueCopy)) // has placeholder
} }
} }
}, },
data() { data() {
return { return {
valueCopy: null, currentValue: this.value,
focus: false, focus: false,
valid: true fillPlaceHolder: null
} }
}, },
beforeMount() {
// Here we are following the Vue2 convention on custom v-model:
// https://github.com/vuejs/vue/issues/2873#issuecomment-223759341
this.copyValue(this.value)
},
methods: { methods: {
handleModelInput(event) { handleModelInput(event) {
this.$emit('input', event.target.value, event) const value = event.target.value
this.handleValidation() this.$emit('input', value)
if (this.$parent.$options.componentName === 'ElFormItem') {
if (this.validateEvent) {
this.$parent.$emit('el.form.change', [value])
}
}
this.$emit('change', value)
}, },
handleFocus(focused) { handleMdFocus(event) {
this.focus = focused this.focus = true
}, this.$emit('focus', event)
handleValidation() { if (this.placeholder && this.placeholder !== '') {
this.valid = this.$el ? this.$el.querySelector('.material-input').validity.valid : this.valid this.fillPlaceHolder = this.placeholder
},
copyValue(value) {
this.valueCopy = value
this.handleValidation()
} }
}, },
watch: { handleMdBlur(event) {
value(newValue) { this.focus = false
this.copyValue(newValue) this.$emit('blur', event)
this.fillPlaceHolder = null
if (this.$parent.$options.componentName === 'ElFormItem') {
if (this.validateEvent) {
this.$parent.$emit('el.form.blur', [this.currentValue])
}
} }
},
props: {
id: {
type: String,
default: null
},
name: {
type: String,
default: null
},
type: {
type: String,
default: 'text'
},
value: {
default: null
},
placeholder: {
type: String,
default: null
},
readonly: {
type: Boolean,
default: false
},
disabled: {
type: Boolean,
default: false
},
min: {
type: String,
default: null
},
max: {
type: String,
default: null
},
minlength: {
type: Number,
default: null
},
maxlength: {
type: Number,
default: null
},
required: {
type: Boolean,
default: true
},
autocomplete: {
type: String,
default: 'off'
},
errorMessages: {
type: [Array, String],
default: null
} }
} }
} }
@ -150,9 +116,20 @@ export default {
$font-size-small: 18px; $font-size-small: 18px;
$font-size-smallest: 12px; $font-size-smallest: 12px;
$font-weight-normal: normal; $font-weight-normal: normal;
$font-weight-bold: bold;
$apixel: 1px;
// Utils // Utils
$spacer: 12px; $spacer: 12px;
$transition: 0.2s ease all; $transition: 0.2s ease all;
$index: 0px;
$index-has-icon: 30px;
// Theme:
$color-white: white;
$color-grey: #9E9E9E;
$color-grey-light: #E0E0E0;
$color-blue: #2196F3;
$color-red: #F44336;
$color-black: black;
// Base clases: // Base clases:
%base-bar-pseudo { %base-bar-pseudo {
content: ''; content: '';
@ -165,23 +142,45 @@ export default {
// Mixins: // Mixins:
@mixin slided-top() { @mixin slided-top() {
top: -2 * $spacer; top: - ($font-size-base + $spacer);
font-size: $font-size-small; left: 0;
font-size: $font-size-base;
font-weight: $font-weight-bold;
} }
// Component: // Component:
.material-input__component { .material-input__component {
/*margin-top: 30px;*/ margin-top: 36px;
position: relative; position: relative;
* { * {
box-sizing: border-box; box-sizing: border-box;
} }
.iconClass {
.material-input__icon {
position: absolute;
left: 0;
color: $color-blue;
top: $spacer;
width: $index-has-icon;
height: $font-size-base;
font-size: $font-size-base;
font-weight: $font-weight-normal;
pointer-events: none;
}
.material-label {
left: $index-has-icon;
}
.material-input {
text-indent: $index-has-icon;
}
}
.material-input { .material-input {
font-size: $font-size-base; font-size: $font-size-base;
padding: $spacer $spacer $spacer $spacer / 2; padding: $spacer $spacer $spacer - $apixel * 10 $spacer / 2;
display: block; display: block;
width: 100%; width: 100%;
border: none; border: none;
line-height: 1;
border-radius: 0; border-radius: 0;
&:focus { &:focus {
outline: none; outline: none;
@ -190,13 +189,13 @@ export default {
} }
} }
.material-label { .material-label {
font-size: $font-size-base;
font-weight: $font-weight-normal; font-weight: $font-weight-normal;
position: absolute; position: absolute;
pointer-events: none; pointer-events: none;
left: 0; left: $index;
top: $spacer; top: 0;
transition: $transition; transition: $transition;
font-size: $font-size-small;
} }
.material-input-bar { .material-input-bar {
position: relative; position: relative;
@ -232,35 +231,14 @@ export default {
} }
} }
} }
// Errors:
.material-errors {
position: relative;
overflow: hidden;
.material-error {
font-size: $font-size-smallest;
line-height: $font-size-smallest + 2px;
overflow: hidden;
margin-top: 0;
padding-top: $spacer / 2;
padding-right: $spacer / 2;
padding-left: 0;
}
}
} }
// Theme:
$color-white: white;
$color-grey: #9E9E9E;
$color-grey-light: #E0E0E0;
$color-blue: #2196F3;
$color-red: #F44336;
$color-black: black;
.material-input__component { .material-input__component {
background: $color-white; background: $color-white;
.material-input { .material-input {
background: none; background: none;
color: $color-black; color: $color-black;
text-indent: 30px; text-indent: $index;
border-bottom: 1px solid $color-grey-light; border-bottom: 1px solid $color-grey-light;
} }
.material-label { .material-label {
@ -286,12 +264,9 @@ export default {
.material-input-bar { .material-input-bar {
&:before, &:before,
&:after { &:after {
background: $color-red; background: transparent;
} }
} }
.material-errors {
color: $color-red;
}
} }
} }
</style> </style>

View File

@ -150,6 +150,12 @@ export default {
}) })
}, },
methods: { methods: {
setContent(value) {
window.tinymce.get(this.tinymceId).setContent(value)
},
getContent() {
window.tinymce.get(this.tinymceId).getContent()
},
imageSuccessCBK(arr) { imageSuccessCBK(arr) {
const _this = this const _this = this
arr.forEach(v => { arr.forEach(v => {

View File

@ -0,0 +1,49 @@
// Inspired by https://github.com/Inndy/vue-clipboard2
const Clipboard = require('clipboard')
if (!Clipboard) {
throw new Error('you shold npm install `clipboard` --save at first ')
}
export default {
bind(el, binding) {
if (binding.arg === 'success') {
el._v_clipboard_success = binding.value
} else if (binding.arg === 'error') {
el._v_clipboard_error = binding.value
} else {
const clipboard = new Clipboard(el, {
text() { return binding.value },
action() { return binding.arg === 'cut' ? 'cut' : 'copy' }
})
clipboard.on('success', e => {
const callback = el._v_clipboard_success
callback && callback(e) // eslint-disable-line
})
clipboard.on('error', e => {
const callback = el._v_clipboard_error
callback && callback(e) // eslint-disable-line
})
el._v_clipboard = clipboard
}
},
update(el, binding) {
if (binding.arg === 'success') {
el._v_clipboard_success = binding.value
} else if (binding.arg === 'error') {
el._v_clipboard_error = binding.value
} else {
el._v_clipboard.text = function() { return binding.value }
el._v_clipboard.action = function() { return binding.arg === 'cut' ? 'cut' : 'copy' }
}
},
unbind(el, binding) {
if (binding.arg === 'success') {
delete el._v_clipboard_success
} else if (binding.arg === 'error') {
delete el._v_clipboard_error
} else {
el._v_clipboard.destroy()
delete el._v_clipboard
}
}
}

View File

@ -0,0 +1,13 @@
import Clipboard from './clipboard'
const install = function(Vue) {
Vue.directive('Clipboard', Clipboard)
}
if (window.Vue) {
window.clipboard = Clipboard
Vue.use(install); // eslint-disable-line
}
Clipboard.install = install
export default Clipboard

View File

@ -0,0 +1,13 @@
import waves from './waves'
const install = function(Vue) {
Vue.directive('waves', waves)
}
if (window.Vue) {
window.waves = waves
Vue.use(install); // eslint-disable-line
}
waves.install = install
export default waves

View File

Before

Width:  |  Height:  |  Size: 552 B

After

Width:  |  Height:  |  Size: 552 B

View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1506419860538" class="icon" style="" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4662" xmlns:xlink="http://www.w3.org/1999/xlink" width="64" height="64"><defs><style type="text/css"></style></defs><path d="M438.857143 950.857143l512 0 0-365.714286-237.714286 0q-22.820571 0-38.838857-16.018286t-16.018286-38.838857l0-237.714286-219.428571 0 0 658.285714zM585.142857 128l0-36.571429q0-7.460571-5.412571-12.873143t-12.873143-5.412571l-402.285714 0q-7.460571 0-12.873143 5.412571t-5.412571 12.873143l0 36.571429q0 7.460571 5.412571 12.873143t12.873143 5.412571l402.285714 0q7.460571 0 12.873143-5.412571t5.412571-12.873143zM731.428571 512l170.861714 0-170.861714-170.861714 0 170.861714zM1024 585.142857l0 384q0 22.820571-16.018286 38.838857t-38.838857 16.018286l-548.571429 0q-22.820571 0-38.838857-16.018286t-16.018286-38.838857l0-91.428571-310.857143 0q-22.820571 0-38.838857-16.018286t-16.018286-38.838857l0-768q0-22.820571 16.018286-38.838857t38.838857-16.018286l621.714286 0q22.820571 0 38.838857 16.018286t16.018286 38.838857l0 187.465143q11.995429 7.460571 20.553143 16.018286l233.179429 233.179429q16.018286 16.018286 27.428571 43.446857t11.410286 50.322286z" p-id="4663"></path></svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1506329916765" class="icon" style="" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1661" xmlns:xlink="http://www.w3.org/1999/xlink" width="64" height="64"><defs><style type="text/css"></style></defs><path d="M64 64 448 64 448 448 64 448 64 64ZM64 576 448 576 448 960 64 960 64 576ZM576 576 960 576 960 960 576 960 576 576ZM768 448C874.038669 448 960 362.038672 960 256 960 149.961328 874.038669 64 768 64 661.961328 64 576 149.961328 576 256 576 362.038672 661.961328 448 768 448Z" p-id="1662"></path></svg>

After

Width:  |  Height:  |  Size: 683 B

View File

Before

Width:  |  Height:  |  Size: 983 B

After

Width:  |  Height:  |  Size: 983 B

View File

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

Before

Width:  |  Height:  |  Size: 866 B

After

Width:  |  Height:  |  Size: 866 B

View File

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

1
src/icons/svg/form.svg Normal file
View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1506330387278" class="icon" style="" viewBox="0 0 1069 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1447" xmlns:xlink="http://www.w3.org/1999/xlink" width="66.8125" height="64"><defs><style type="text/css"></style></defs><path d="M746.027944 190.083832q-11.241517 0-18.906188-7.664671t-12.774451-17.884232-7.664671-20.9501-2.55489-17.884232l0-125.700599 2.043912 0q9.197605 0 17.373253 2.043912t19.928144 9.708583 28.61477 21.461078 42.411178 36.279441q27.592814 24.526946 43.944112 41.389222t25.037924 28.61477 10.730539 19.928144 2.043912 14.307385l0 16.351297-150.227545 0zM1063.856287 671.42515q3.065868 8.175649 4.087824 20.439122t-10.219561 23.50499q-5.10978 5.10978-9.197605 9.708583t-7.153693 7.664671q-4.087824 4.087824-7.153693 6.131737l-86.866267-85.844311q6.131737-5.10978 13.796407-12.263473t12.774451-11.241517q12.263473-11.241517 26.570858-9.708583t23.50499 6.642715q10.219561 5.10978 21.972056 17.884232t17.884232 27.081836zM703.105788 766.467066q22.483034 0 37.812375-12.263473l-198.259481 206.43513-282.05988 0q-19.417166 0-42.411178-11.241517t-42.922156-29.636727-33.213573-42.411178-13.285429-49.56487l0-695.952096q0-21.461078 9.708583-44.966068t26.570858-42.411178 38.323353-31.680639 44.966068-12.774451l391.409182 0 0 127.744511q0 19.417166 6.131737 41.9002t18.906188 41.389222 33.213573 31.680639 49.053892 12.774451l149.205589 0 0 338.267465-140.007984 145.117764q11.241517-16.351297 11.241517-35.768463 0-26.570858-18.906188-45.477046t-45.477046-18.906188l-383.233533 0q-26.570858 0-44.966068 18.906188t-18.39521 45.477046 18.39521 44.966068 44.966068 18.39521l383.233533 0zM319.872255 383.233533q-26.570858 0-44.966068 18.906188t-18.39521 45.477046 18.39521 44.966068 44.966068 18.39521l383.233533 0q26.570858 0 45.477046-18.39521t18.906188-44.966068-18.906188-45.477046-45.477046-18.906188l-383.233533 0zM705.149701 895.233533l13.285429-13.285429 25.548902-25.548902q15.329341-15.329341 33.724551-34.235529t36.790419-37.301397q43.944112-43.944112 99.129741-98.107784l85.844311 85.844311-99.129741 99.129741-36.790419 36.790419-33.724551 33.724551q-14.307385 14.307385-24.015968 24.526946t-10.730539 11.241517q-5.10978 4.087824-11.241517 8.686627t-12.263473 7.664671-18.906188 7.664671-26.05988 8.686627-25.548902 7.153693-18.39521 4.087824q-12.263473 2.043912-16.351297-3.065868t-2.043912-17.373253q1.021956-6.131737 4.087824-18.39521t7.153693-25.037924 7.664671-24.015968 5.620758-15.329341q6.131737-13.285429 16.351297-23.50499z" p-id="1448"></path></svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@ -1 +0,0 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1503993937334" class="icon" style="" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="8099" xmlns:xlink="http://www.w3.org/1999/xlink" width="64" height="64"><defs><style type="text/css"></style></defs><path d="M960 256v64H64v-64c0-35.2 28.8-64 64-64h768c35.2 0 64 28.8 64 64z m0 128v384c0 35.2-28.8 64-64 64H128c-35.2 0-64-28.8-64-64V384h896zM256 640H128v32h128v-32z m128-64H128v32h256v-32z" p-id="8100"></path></svg>

Before

Width:  |  Height:  |  Size: 591 B

View File

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

Before

Width:  |  Height:  |  Size: 8.0 KiB

After

Width:  |  Height:  |  Size: 8.0 KiB

View File

@ -1 +0,0 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1503994075075" class="icon" style="" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="8325" xmlns:xlink="http://www.w3.org/1999/xlink" width="64" height="64"><defs><style type="text/css"></style></defs><path d="M896 192v704H256.8c-71.2 0-128.8-57.6-128.8-128.8V256.8C128 185.6 185.6 128 256.8 128H768v512H257.6c-36 0-65.6 29.6-65.6 65.6v60.8c0 36 29.6 65.6 65.6 65.6H832V192h64zM768 704H256v64h512v-64z" p-id="8326"></path></svg>

Before

Width:  |  Height:  |  Size: 602 B

View File

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -1 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1503994012480" class="icon" style="" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="8212" xmlns:xlink="http://www.w3.org/1999/xlink" width="64" height="64"><defs><style type="text/css"></style></defs><path d="M896 320H128V160c0-17.6 14.4-32 32-32h704c17.6 0 32 14.4 32 32v160zM320 896H160c-17.6 0-32-14.4-32-32V384h192v512zM864 896H384V384h512v480c0 17.6-14.4 32-32 32z" p-id="8213"></path></svg> <?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1506329761546" class="icon" style="" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1384" xmlns:xlink="http://www.w3.org/1999/xlink" width="64" height="64"><defs><style type="text/css"></style></defs><path d="M622.276923 39.384615H401.723077c-13.784615 0-23.630769 11.815385-23.630769 25.6v49.23077c0 13.784615 11.815385 25.6 23.630769 25.6h220.553846c13.784615 0 23.630769-11.815385 23.630769-25.6V64.984615c1.969231-13.784615-9.846154-25.6-23.630769-25.6z m336.738462 0H738.461538c-13.784615 0-25.6 11.815385-25.6 25.6v49.23077c0 13.784615 11.815385 25.6 25.6 25.6h220.553847c13.784615-1.969231 25.6-11.815385 25.6-25.6V64.984615c0-13.784615-11.815385-25.6-25.6-25.6z m0 165.415385H334.769231c-13.784615 0-25.6-11.815385-25.6-25.6V64.984615c0-13.784615-11.815385-25.6-25.6-25.6H64.984615C51.2 39.384615 39.384615 51.2 39.384615 64.984615v896c0 11.815385 11.815385 23.630769 25.6 23.63077h894.03077c13.784615 0 25.6-11.815385 25.6-25.6v-728.615385c0-13.784615-11.815385-25.6-25.6-25.6z" p-id="1385"></path></svg>

Before

Width:  |  Height:  |  Size: 571 B

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

Before

Width:  |  Height:  |  Size: 678 B

After

Width:  |  Height:  |  Size: 678 B

View File

Before

Width:  |  Height:  |  Size: 678 B

After

Width:  |  Height:  |  Size: 678 B

View File

Before

Width:  |  Height:  |  Size: 673 B

After

Width:  |  Height:  |  Size: 673 B

View File

Before

Width:  |  Height:  |  Size: 777 B

After

Width:  |  Height:  |  Size: 777 B

View File

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

1
src/icons/svg/zip.svg Normal file
View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1506326020470" class="icon" style="" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2561" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M619.364365 933.396352c1.372783 0.06385 2.681715 0.191551 4.054497 0.191551h309.291099a65.670086 65.670086 0 0 0 65.606235-65.606235V150.974154a65.670086 65.670086 0 0 0-65.606235-65.606235H623.418862c-1.372783 0-2.71364 0.127701-4.054497 0.191551V-0.031925L15.691224 80.547217v858.404116l603.673141 82.654279v-88.20926z m0-810.101325c1.340857-0.191551 2.681715-0.415027 4.054497-0.415027h309.291099c15.515635 0 28.12608 12.610444 28.12608 28.12608v717.007513a28.158005 28.158005 0 0 1-28.12608 28.12608H623.418862c-1.372783 0-2.71364-0.223476-4.054497-0.415028V123.326952zM248.329977 605.429026l-143.918691-3.671395v-23.401154l86.868402-133.255682v-1.181231l-78.919033 1.308932v-36.043523l134.564614-3.51177v26.082869l-87.506906 133.734559v1.149307l88.911614 1.404707v37.352456z m72.406297 1.85166l-44.759096-1.149306v-201.192456l44.759096-1.149306v203.491068z m171.087015-92.966111c-16.664942 15.356009-41.151551 22.060296-69.341481 21.868745a113.81325 113.81325 0 0 1-16.122213-1.05353v74.353733l-46.099954-1.181231v-202.788714c14.238628-2.809415 34.383414-5.171878 63.179923-5.938083 29.498862-0.766204 50.792954 4.309899 65.191208 15.292159 13.887451 10.439532 23.305378 27.966454 23.305378 48.845518s-7.119314 38.629462-20.080936 50.601403z m-65.925487-79.174435a80.13219 80.13219 0 0 0-19.538207 2.202837v61.392113c4.022572 0.92583 8.970974 1.213157 15.834887 1.213156 25.380514-0.031925 41.215401-12.897771 41.215401-34.479189 0-19.378581-13.63205-30.712019-37.480156-30.296992z m306.322058-296.233702h73.523679v30.328917h-73.523679v-30.328917z m-73.555604 45.397599h73.523679v30.360842h-73.523679v-30.360842z m73.555604 49.675573h73.523679v30.360842h-73.523679v-30.360842z m0 95.903227h73.523679v30.328917h-73.523679v-30.328917z m-73.555604-48.717818h73.523679v30.328917h-73.523679v-30.328917z m72.821325 376.142417a72.7894 72.7894 0 0 0 72.7894-72.821325l-13.440499-121.986095c0-40.225721-19.155105-72.821325-59.380827-72.821325s-59.348901 32.595604-59.348901 72.821325l-13.472424 121.986095a72.7894 72.7894 0 0 0 72.821325 72.821325z m-24.103508-133.862261h48.207015v101.84131h-48.207015v-101.84131z" p-id="2562"></path></svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@ -1 +0,0 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1503994912370" class="icon" style="" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="10530" xmlns:xlink="http://www.w3.org/1999/xlink" width="64" height="64"><defs><style type="text/css"></style></defs><path d="M568.6 0h454.9v454.9H568.6V0z m0 568.6h454.9v454.9H568.6V568.6zM0 568.6h454.9v454.9H0V568.6zM0 0h454.9v454.9H0V0z" fill="" p-id="10531"></path></svg>

Before

Width:  |  Height:  |  Size: 534 B

View File

@ -34,7 +34,7 @@ export default {
if (userMap[token]) { if (userMap[token]) {
return userMap[token] return userMap[token]
} else { } else {
return Promise.reject('error') return false
} }
}, },
logout: () => 'success' logout: () => 'success'

View File

@ -3,6 +3,7 @@ import store from './store'
import NProgress from 'nprogress' // Progress 进度条 import NProgress from 'nprogress' // Progress 进度条
import 'nprogress/nprogress.css'// Progress 进度条样式 import 'nprogress/nprogress.css'// Progress 进度条样式
import { getToken } from '@/utils/auth' // 验权 import { getToken } from '@/utils/auth' // 验权
import { Message } from 'element-ui'
// permissiom judge // permissiom judge
function hasPermission(roles, permissionRoles) { function hasPermission(roles, permissionRoles) {
@ -18,6 +19,7 @@ router.beforeEach((to, from, next) => {
if (getToken()) { // 判断是否有token if (getToken()) { // 判断是否有token
if (to.path === '/login') { if (to.path === '/login') {
next({ path: '/' }) next({ path: '/' })
NProgress.done() // router在hash模式下 手动改变hash 重定向回来 不会触发afterEach 暂时hack方案 pshistory模式下无问题可删除该行
} else { } else {
if (store.getters.roles.length === 0) { // 判断当前用户是否已拉取完user_info信息 if (store.getters.roles.length === 0) { // 判断当前用户是否已拉取完user_info信息
store.dispatch('GetUserInfo').then(res => { // 拉取user_info store.dispatch('GetUserInfo').then(res => { // 拉取user_info
@ -28,6 +30,7 @@ router.beforeEach((to, from, next) => {
}) })
}).catch(() => { }).catch(() => {
store.dispatch('FedLogOut').then(() => { store.dispatch('FedLogOut').then(() => {
Message.error('验证失败,请重新登录')
next({ path: '/login' }) next({ path: '/login' })
}) })
}) })
@ -37,6 +40,7 @@ router.beforeEach((to, from, next) => {
next()// next()//
} else { } else {
next({ path: '/401', query: { noGoBack: true }}) next({ path: '/401', query: { noGoBack: true }})
NProgress.done() // router在hash模式下 手动改变hash 重定向回来 不会触发afterEach 暂时hack方案 pshistory模式下无问题可删除该行
} }
// 可删 ↑ // 可删 ↑
} }
@ -46,7 +50,7 @@ router.beforeEach((to, from, next) => {
next() next()
} else { } else {
next('/login') // 否则全部重定向到登录页 next('/login') // 否则全部重定向到登录页
NProgress.done() // 在hash模式下 改变手动改变hash 重定向回来 不会触发afterEach 暂时hack方案 pshistory模式下无问题可删除该行 NProgress.done() // router在hash模式下 手动改变hash 重定向回来 不会触发afterEach 暂时hack方案 pshistory模式下无问题可删除该行
} }
} }
}) })

View File

@ -32,7 +32,7 @@ export const constantRouterMap = [
path: '/introduction', path: '/introduction',
component: Layout, component: Layout,
redirect: '/introduction/index', redirect: '/introduction/index',
icon: 'xinrenzhinan', icon: 'people',
noDropdown: true, noDropdown: true,
children: [{ path: 'index', component: _import('introduction/index'), name: '简述' }] children: [{ path: 'index', component: _import('introduction/index'), name: '简述' }]
} }
@ -50,7 +50,7 @@ export const asyncRouterMap = [
component: Layout, component: Layout,
redirect: '/permission/index', redirect: '/permission/index',
name: '权限测试', name: '权限测试',
icon: 'quanxian', icon: 'lock',
meta: { role: ['admin'] }, meta: { role: ['admin'] },
noDropdown: true, noDropdown: true,
children: [{ path: 'index', component: _import('permission/index'), name: '权限测试页', meta: { role: ['admin'] }}] children: [{ path: 'index', component: _import('permission/index'), name: '权限测试页', meta: { role: ['admin'] }}]
@ -58,7 +58,7 @@ export const asyncRouterMap = [
{ {
path: '/icon', path: '/icon',
component: Layout, component: Layout,
icon: 'icons', icon: 'icon',
noDropdown: true, noDropdown: true,
children: [{ path: 'index', component: _import('svg-icons/index'), name: 'icons' }] children: [{ path: 'index', component: _import('svg-icons/index'), name: 'icons' }]
}, },
@ -67,7 +67,7 @@ export const asyncRouterMap = [
component: Layout, component: Layout,
redirect: '/components/index', redirect: '/components/index',
name: '组件', name: '组件',
icon: 'zujian', icon: 'component',
children: [ children: [
{ path: 'index', component: _import('components/index'), name: '介绍 ' }, { path: 'index', component: _import('components/index'), name: '介绍 ' },
{ path: 'tinymce', component: _import('components/tinymce'), name: '富文本编辑器' }, { path: 'tinymce', component: _import('components/tinymce'), name: '富文本编辑器' },
@ -88,7 +88,7 @@ export const asyncRouterMap = [
component: Layout, component: Layout,
redirect: '/charts/index', redirect: '/charts/index',
name: '图表', name: '图表',
icon: 'tubiao', icon: 'chart',
children: [ children: [
{ path: 'index', component: _import('charts/index'), name: '介绍' }, { path: 'index', component: _import('charts/index'), name: '介绍' },
{ path: 'keyboard', component: _import('charts/keyboard'), name: '键盘图表' }, { path: 'keyboard', component: _import('charts/keyboard'), name: '键盘图表' },
@ -102,7 +102,7 @@ export const asyncRouterMap = [
component: Layout, component: Layout,
redirect: 'noredirect', redirect: 'noredirect',
name: '综合实例', name: '综合实例',
icon: 'zonghe', icon: 'example',
children: [ children: [
{ {
path: '/example/table', path: '/example/table',
@ -117,8 +117,8 @@ export const asyncRouterMap = [
{ path: 'table', component: _import('example/table/table'), name: '综合table' } { path: 'table', component: _import('example/table/table'), name: '综合table' }
] ]
}, },
{ path: 'form/edit', icon: 'shouce', component: _import('example/form'), name: '编辑Form', meta: { isEdit: true }}, { path: 'form/edit', icon: 'form', component: _import('example/form'), name: '编辑Form', meta: { isEdit: true }},
{ path: 'form/create', icon: 'from', component: _import('example/form'), name: '创建Form' }, { path: 'form/create', icon: 'form', component: _import('example/form'), name: '创建Form' },
{ path: 'tab/index', icon: 'tab', component: _import('example/tab/index'), name: 'Tab' } { path: 'tab/index', icon: 'tab', component: _import('example/tab/index'), name: 'Tab' }
] ]
}, },
@ -147,13 +147,23 @@ export const asyncRouterMap = [
component: Layout, component: Layout,
redirect: '/excel/download', redirect: '/excel/download',
name: 'excel', name: 'excel',
icon: 'EXCEL', icon: 'excel',
children: [ children: [
{ path: 'download', component: _import('excel/index'), name: '导出excel' }, { path: 'download', component: _import('excel/index'), name: 'export excel' },
{ path: 'download2', component: _import('excel/selectExcel'), name: '导出已选择项' }, { path: 'download2', component: _import('excel/selectExcel'), name: 'export selected' },
{ path: 'upload', component: _import('excel/uploadExcel'), name: 'upload excel' } { path: 'upload', component: _import('excel/uploadExcel'), name: 'upload excel' }
] ]
}, },
{
path: '/zip',
component: Layout,
redirect: '/zip/download',
name: 'zip',
icon: 'zip',
children: [
{ path: 'download', component: _import('zip/index'), name: 'export zip' }
]
},
{ {
path: '/theme', path: '/theme',
component: Layout, component: Layout,
@ -163,6 +173,14 @@ export const asyncRouterMap = [
noDropdown: true, noDropdown: true,
children: [{ path: 'index', component: _import('theme/index'), name: '换肤' }] children: [{ path: 'index', component: _import('theme/index'), name: '换肤' }]
}, },
{
path: '/clipboard',
component: Layout,
redirect: 'noredirect',
icon: 'clipboard',
noDropdown: true,
children: [{ path: 'index', component: _import('clipboard/index'), name: 'clipboard' }]
},
{ path: '*', redirect: '/404', hidden: true } { path: '*', redirect: '/404', hidden: true }
] ]

View File

@ -63,6 +63,9 @@ const user = {
GetUserInfo({ commit, state }) { GetUserInfo({ commit, state }) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
getUserInfo(state.token).then(response => { getUserInfo(state.token).then(response => {
if (!response.data) { // 由于mockjs 不支持自定义状态码只能这样hack
reject('error')
}
const data = response.data const data = response.data
commit('SET_ROLES', data.role) commit('SET_ROLES', data.role)
commit('SET_NAME', data.name) commit('SET_NAME', data.name)

36
src/utils/clipboard.js Normal file
View File

@ -0,0 +1,36 @@
import Clipboard from 'clipboard'
import Vue from 'vue'
function clipboardSuccess() {
Vue.prototype.$message({
message: '复制成功',
type: 'success',
duration: 1500
})
}
function clipboardError() {
Vue.prototype.$message({
message: '复制失败',
type: 'error'
})
}
export default function handleClipboard(text, event) {
const clipboard = new Clipboard(event.target, {
text: () => text
})
clipboard.on('success', () => {
clipboardSuccess()
clipboard.off('error')
clipboard.off('success')
clipboard.destroy()
})
clipboard.on('error', () => {
clipboardError()
clipboard.off('error')
clipboard.off('success')
clipboard.destroy()
})
clipboard.onClick(event)
}

View File

@ -1,7 +1,8 @@
/* eslint-disable */ /* eslint-disable */
require('script-loader!file-saver'); require('script-loader!file-saver');
require('script-loader!vendor/Blob'); require('script-loader!vendor/Blob');
require('script-loader!xlsx/dist/xlsx.core.min'); import XLSX from 'xlsx'
function generateArray(table) { function generateArray(table) {
var out = []; var out = [];
var rows = table.querySelectorAll('tr'); var rows = table.querySelectorAll('tr');
@ -93,14 +94,12 @@ function s2ab(s) {
export function export_table_to_excel(id) { export function export_table_to_excel(id) {
var theTable = document.getElementById(id); var theTable = document.getElementById(id);
console.log('a')
var oo = generateArray(theTable); var oo = generateArray(theTable);
var ranges = oo[1]; var ranges = oo[1];
/* original data */ /* original data */
var data = oo[0]; var data = oo[0];
var ws_name = "SheetJS"; var ws_name = "SheetJS";
console.log(data);
var wb = new Workbook(), ws = sheet_from_array_of_arrays(data); var wb = new Workbook(), ws = sheet_from_array_of_arrays(data);
@ -117,9 +116,6 @@ export function export_table_to_excel(id) {
saveAs(new Blob([s2ab(wbout)], {type: "application/octet-stream"}), "test.xlsx") saveAs(new Blob([s2ab(wbout)], {type: "application/octet-stream"}), "test.xlsx")
} }
function formatJson(jsonData) {
console.log(jsonData)
}
export function export_json_to_excel(th, jsonData, defaultTitle) { export function export_json_to_excel(th, jsonData, defaultTitle) {
/* original data */ /* original data */
@ -130,6 +126,29 @@ export function export_json_to_excel(th, jsonData, defaultTitle) {
var wb = new Workbook(), ws = sheet_from_array_of_arrays(data); var wb = new Workbook(), ws = sheet_from_array_of_arrays(data);
/*设置worksheet每列的最大宽度*/
const colWidth = data.map(row => row.map(val => {
/*先判断是否为null/undefined*/
if (val == null) {
return {'wch': 10};
}
/*再判断是否为中文*/
else if (val.toString().charCodeAt(0) > 255) {
return {'wch': val.toString().length * 2};
} else {
return {'wch': val.toString().length};
}
}))
/*以第一行为初始值*/
let result = colWidth[0];
for (let i = 1; i < colWidth.length; i++) {
for (let j = 0; j < colWidth[i].length; j++) {
if (result[j]['wch'] < colWidth[i][j]['wch']) {
result[j]['wch'] = colWidth[i][j]['wch'];
}
}
}
ws['!cols'] = result;
/* add worksheet to workbook */ /* add worksheet to workbook */
wb.SheetNames.push(ws_name); wb.SheetNames.push(ws_name);

22
src/vendor/Export2Zip.js vendored Normal file
View File

@ -0,0 +1,22 @@
/* eslint-disable */
require('script-loader!file-saver');
import JSZip from 'jszip'
export function export_txt_to_zip(th, jsonData, txtName, zipName) {
const zip = new JSZip()
const txt_name = txtName || '文本'
const zip_name = zipName || '压缩包'
const data = jsonData
let txtData = `${th}\r\n`
data.forEach((row) => {
let tempStr = ''
tempStr = row.toString()
txtData += `${tempStr}\r\n`
})
zip.file(`${txt_name}.txt`, txtData)
zip.generateAsync({type:"blob"}).then((blob) => {
saveAs(blob, `${zip_name}.zip`)
}, (err) => {
alert('导出失败')
})
}

View File

@ -0,0 +1,44 @@
<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="请输入内容" style='width:400px;'></el-input>
<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="请输入内容" style='width:400px;'></el-input>
<el-button type="primary" icon="document" v-clipboard:copy='inputData' v-clipboard:success='clipboardSuccess'>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 {
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: '复制成功',
type: 'success',
duration: 1500
})
}
}
}
</script>

View File

@ -1,9 +1,11 @@
<template> <template>
<div class="components-container"> <div class="components-container">
<div class='component-item'> <div class='component-item'>
<md-input name="name" v-model="title" required :maxlength="100"> <el-form :model="demo" :rules="demoRules">
标题 <el-form-item prop="title">
</md-input> <md-input icon="search" name="title" placeholder="输入标题" v-model="demo.title">标题</md-input>
</el-form-item>
</el-form>
<code class='code-part'>Material Design 的input</code> <code class='code-part'>Material Design 的input</code>
</div> </div>
@ -22,18 +24,33 @@
</template> </template>
<script> <script>
import MdInput from '@/components/MDinput'
import PanThumb from '@/components/PanThumb' import PanThumb from '@/components/PanThumb'
import waves from '@/directive/waves.js' // import MdInput from '@/components/MDinput'
import waves from '@/directive/waves/index.js' //
export default { export default {
components: { MdInput, PanThumb }, components: {
PanThumb,
MdInput
},
directives: { directives: {
waves waves
}, },
data() { data() {
const validate = (rule, value, callback) => {
if (value.length !== 6) {
callback(new Error('请输入六个字符'))
} else {
callback()
}
}
return { return {
demo: {
title: '' title: ''
},
demoRules: {
title: [{ required: true, trigger: 'change', validator: validate }]
}
} }
} }
} }

View File

@ -34,12 +34,12 @@
<div class="info-item"> <div class="info-item">
<count-to class="info-item-num" :startVal='0' :endVal='statisticsData.article_count' :duration='3400'></count-to> <count-to class="info-item-num" :startVal='0' :endVal='statisticsData.article_count' :duration='3400'></count-to>
<span class="info-item-text">文章</span> <span class="info-item-text">文章</span>
<icon-svg icon-class="a" class="dashboard-editor-icon"></icon-svg> <icon-svg icon-class="trendChart1" class="dashboard-editor-icon"></icon-svg>
</div> </div>
<div class="info-item"> <div class="info-item">
<count-to class="info-item-num" :startVal='0' :endVal='statisticsData.pageviews_count' :duration='3600'></count-to> <count-to class="info-item-num" :startVal='0' :endVal='statisticsData.pageviews_count' :duration='3600'></count-to>
<span class="info-item-text">浏览量</span> <span class="info-item-text">浏览量</span>
<icon-svg icon-class="b" class="dashboard-editor-icon"></icon-svg> <icon-svg icon-class="trendChart2" class="dashboard-editor-icon"></icon-svg>
</div> </div>
</el-card> </el-card>
</el-col> </el-col>

View File

@ -29,7 +29,7 @@
<el-table-column width="80px" label="重要性"> <el-table-column width="80px" label="重要性">
<template scope="scope"> <template scope="scope">
<icon-svg v-for="n in +scope.row.importance" icon-class="wujiaoxing" :key="n"></icon-svg> <icon-svg v-for="n in +scope.row.importance" icon-class="star" :key="n"></icon-svg>
</template> </template>
</el-table-column> </el-table-column>

View File

@ -29,7 +29,7 @@
<el-table-column width="80px" label="重要性"> <el-table-column width="80px" label="重要性">
<template scope="scope"> <template scope="scope">
<icon-svg v-for="n in +scope.row.importance" icon-class="wujiaoxing" class="meta-item__icon" :key="n"></icon-svg> <icon-svg v-for="n in +scope.row.importance" icon-class="star" class="meta-item__icon" :key="n"></icon-svg>
</template> </template>
</el-table-column> </el-table-column>
@ -47,7 +47,7 @@
<el-table-column align="center" label="拖拽" width="95"> <el-table-column align="center" label="拖拽" width="95">
<template scope="scope"> <template scope="scope">
<icon-svg class='drag-handler' icon-class="tuozhuai"></icon-svg> <icon-svg class='drag-handler' icon-class="drag"></icon-svg>
</template> </template>
</el-table-column> </el-table-column>

View File

@ -23,7 +23,7 @@
<el-table-column width="100px" label="重要性"> <el-table-column width="100px" label="重要性">
<template scope="scope"> <template scope="scope">
<icon-svg v-for="n in +scope.row.importance" icon-class="wujiaoxing" class="meta-item__icon" :key="n"></icon-svg> <icon-svg v-for="n in +scope.row.importance" icon-class="star" class="meta-item__icon" :key="n"></icon-svg>
</template> </template>
</el-table-column> </el-table-column>

View File

@ -60,7 +60,7 @@
<el-table-column width="80px" label="重要性"> <el-table-column width="80px" label="重要性">
<template scope="scope"> <template scope="scope">
<icon-svg v-for="n in +scope.row.importance" icon-class="wujiaoxing" class="meta-item__icon" :key="n"></icon-svg> <icon-svg v-for="n in +scope.row.importance" icon-class="star" class="meta-item__icon" :key="n"></icon-svg>
</template> </template>
</el-table-column> </el-table-column>
@ -151,7 +151,7 @@
<script> <script>
import { fetchList, fetchPv } from '@/api/article' import { fetchList, fetchPv } from '@/api/article'
import waves from '@/directive/waves.js'// import waves from '@/directive/waves/index.js' //
import { parseTime } from '@/utils' import { parseTime } from '@/utils'
const calendarTypeOptions = [ const calendarTypeOptions = [

View File

@ -25,7 +25,7 @@
<el-table-column align="center" prop="created_at" label="发布时间" width="220"> <el-table-column align="center" prop="created_at" label="发布时间" width="220">
<template scope="scope"> <template scope="scope">
<i class="el-icon-time"></i> <i class="el-icon-time"></i>
<span>{{scope.row.display_time}}</span> <span>{{scope.row.timestamp | parseTime('{y}-{m}-{d} {h}:{i}')}}</span>
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
@ -34,6 +34,7 @@
<script> <script>
import { fetchList } from '@/api/article' import { fetchList } from '@/api/article'
import { parseTime } from 'utils'
export default { export default {
data() { data() {
@ -67,7 +68,13 @@ export default {
}) })
}, },
formatJson(filterVal, jsonData) { formatJson(filterVal, jsonData) {
return jsonData.map(v => filterVal.map(j => v[j])) return jsonData.map(v => filterVal.map(j => {
if (j === 'timestamp') {
return parseTime(v[j])
} else {
return v[j]
}
}))
} }
} }
} }

View File

@ -30,13 +30,16 @@ export default {
$event.preventDefault() $event.preventDefault()
}, },
generateRoute() { generateRoute() {
if (this.$route.matched[this.$route.matched.length - 1].name) { if (this.$route.name) {
return this.$route.matched[this.$route.matched.length - 1] return this.$route
} }
this.$route.matched[0].path = '/' return false
return this.$route.matched[0]
}, },
addViewTabs() { addViewTabs() {
const route = this.generateRoute()
if (!route) {
return false
}
this.$store.dispatch('addVisitedViews', this.generateRoute()) this.$store.dispatch('addVisitedViews', this.generateRoute())
}, },
isActive(path) { isActive(path) {

View File

@ -5,18 +5,18 @@
<el-form-item prop="username"> <el-form-item prop="username">
<span class="svg-container svg-container_login"> <span class="svg-container svg-container_login">
<icon-svg icon-class="yonghuming" /> <icon-svg icon-class="user" />
</span> </span>
<el-input name="username" type="text" v-model="loginForm.username" autoComplete="on" placeholder="邮箱" /> <el-input name="username" type="text" v-model="loginForm.username" autoComplete="on" placeholder="邮箱" />
</el-form-item> </el-form-item>
<el-form-item prop="password"> <el-form-item prop="password">
<span class="svg-container"> <span class="svg-container">
<icon-svg icon-class="mima" /> <icon-svg icon-class="password" />
</span> </span>
<el-input name="password" :type="pwdType" @keyup.enter.native="handleLogin" v-model="loginForm.password" autoComplete="on" <el-input name="password" :type="pwdType" @keyup.enter.native="handleLogin" v-model="loginForm.password" autoComplete="on"
placeholder="密码" /> placeholder="密码" />
<span class='show-pwd' @click='showPwd'><icon-svg icon-class="yanjing" /></span> <span class='show-pwd' @click='showPwd'><icon-svg icon-class="eye" /></span>
</el-form-item> </el-form-item>
<el-button type="primary" style="width:100%;margin-bottom:30px;" :loading="loading" @click.native.prevent="handleLogin">登录</el-button> <el-button type="primary" style="width:100%;margin-bottom:30px;" :loading="loading" @click.native.prevent="handleLogin">登录</el-button>

View File

@ -1,10 +1,10 @@
<template> <template>
<div class="social-signup-container"> <div class="social-signup-container">
<div class="sign-btn" @click="wechatHandleClick('wechat')"> <div class="sign-btn" @click="wechatHandleClick('wechat')">
<span class="wx-svg-container"><icon-svg icon-class="weixin" class="icon"></icon-svg></span> <span class="wx-svg-container"><icon-svg icon-class="wechat" class="icon"></icon-svg></span>
</div> </div>
<div class="sign-btn" @click="tencentHandleClick('tencent')"> <div class="sign-btn" @click="tencentHandleClick('tencent')">
<span class="qq-svg-container"><icon-svg icon-class="QQ" class="icon"></icon-svg></span> QQ <span class="qq-svg-container"><icon-svg icon-class="qq" class="icon"></icon-svg></span> QQ
</div> </div>
</div> </div>
</template> </template>

View File

@ -1,10 +1,10 @@
<template> <template>
<div class="icons-container"> <div class="icons-container">
<div class="icons-wrapper"> <div class="icons-wrapper">
<div v-for='item of iconsMap' :key='item' class='icon-item'> <div v-for='item of iconsMap' :key='item' class='icon-item' @click='handleClipboard(generateIconCode(item),$event)'>
<el-tooltip placement="top" effect="light"> <el-tooltip placement="top" effect="light">
<div slot="content"> <div slot="content">
{{`&lt;icon-svg :icon-class=&quot;${item}&quot; /&gt;`}} {{generateIconCode(item)}}
</div> </div>
<icon-svg :icon-class="item" /> <icon-svg :icon-class="item" />
</el-tooltip> </el-tooltip>
@ -16,6 +16,7 @@
<script> <script>
import icons from './generateIconsView' import icons from './generateIconsView'
import clipboard from '@/utils/clipboard' // use clipboard directly
export default { export default {
data() { data() {
@ -28,6 +29,14 @@ export default {
return i.default.id.split('-')[1] return i.default.id.split('-')[1]
}) })
this.iconsMap = iconsMap this.iconsMap = iconsMap
},
methods: {
generateIconCode(symbol) {
return `<icon-svg :icon-class="${symbol}" />`
},
handleClipboard(text, event) {
clipboard(text, event)
}
} }
} }
</script> </script>

74
src/views/zip/index.vue Normal file
View File

@ -0,0 +1,74 @@
<template>
<div class="app-container">
<el-button style='margin-bottom:20px;' type="primary" icon="document" @click="handleDownload" :loading="downloadLoading">导出zip</el-button>
<el-table :data="list" v-loading.body="listLoading" element-loading-text="拼命加载中" border fit highlight-current-row>
<el-table-column align="center" label='ID' width="95">
<template scope="scope">
{{scope.$index}}
</template>
</el-table-column>
<el-table-column label="文章标题">
<template scope="scope">
{{scope.row.title}}
</template>
</el-table-column>
<el-table-column label="作者" width="95" align="center">
<template scope="scope">
<el-tag>{{scope.row.author}}</el-tag>
</template>
</el-table-column>
<el-table-column label="阅读数" width="115" align="center">
<template scope="scope">
{{scope.row.pageviews}}
</template>
</el-table-column>
<el-table-column align="center" prop="created_at" label="发布时间" width="220">
<template scope="scope">
<i class="el-icon-time"></i>
<span>{{scope.row.display_time}}</span>
</template>
</el-table-column>
</el-table>
</div>
</template>
<script>
import { fetchList } from '@/api/article'
export default {
data() {
return {
list: null,
listLoading: true,
downloadLoading: false
}
},
created() {
this.fetchData()
},
methods: {
fetchData() {
this.listLoading = true
fetchList().then(response => {
this.list = response.data.items
this.listLoading = false
})
},
handleDownload() {
this.downloadLoading = true
require.ensure([], () => {
const { export_txt_to_zip } = require('vendor/Export2Zip')
const tHeader = ['序号', '文章标题', '作者', '阅读数', '发布时间']
const filterVal = ['id', 'title', 'author', 'pageviews', 'display_time']
const list = this.list
const data = this.formatJson(filterVal, list)
export_txt_to_zip(tHeader, data, '列表文本', '压缩文本')
this.downloadLoading = false
})
},
formatJson(filterVal, jsonData) {
return jsonData.map(v => filterVal.map(j => v[j]))
}
}
}
</script>