Compare commits

...

28 Commits

Author SHA1 Message Date
Pan
7287894cb8 Release 1.0.2 2017-06-15 14:44:26 +08:00
Pan
29402cc889 fix typo 2017-06-15 14:35:48 +08:00
Pan
a1a96c38d7 add tabs example 2017-06-15 14:33:02 +08:00
Pan
2927e08d06 change mock data 2017-06-15 13:44:48 +08:00
Pan
5a8755ec9f add screenfull 2017-06-15 13:23:49 +08:00
Pan
3a2bf13a1f refine code 2017-06-15 11:05:41 +08:00
Pan
4a6ef1af17 degrade eslint
eslint4.0 has some problem
2017-06-15 10:49:27 +08:00
Pan
42b5875d36 upgrade babel-core 2017-06-15 10:39:03 +08:00
Pan
4af4df702c upgrade package.json 2017-06-15 10:38:12 +08:00
Pan
72d9a406b4 refine introduction document 2017-06-14 18:02:35 +08:00
Pan
bd607b4728 refine code && document 2017-06-14 18:02:12 +08:00
Pan
f82ec2d5d7 rm redundant code 2017-06-14 17:37:21 +08:00
Pan
c412d17b70 refine tinymce && tinymce demo 2017-06-14 17:34:32 +08:00
Pan
09deec1e5e update iconfont 2017-06-14 16:50:11 +08:00
Pan
acaa6bb269 refine sidebar css 2017-06-13 16:48:47 +08:00
Pan
7d6917a5ef Create README.md 2017-06-13 13:44:01 +08:00
Pan
dd73827112 fix avatar component bug 2017-06-13 11:04:54 +08:00
Pan
4fc649e828 refine node-sass version 2017-06-12 18:01:29 +08:00
Pan
5165ec26ae Create README.md 2017-06-12 17:50:19 +08:00
Pan
d2827bf047 add edit and create demo 2017-06-12 16:05:40 +08:00
Pan
e9d37a94d3 add markdown to html 2017-06-12 15:25:56 +08:00
Pan
9607d07cc0 Create README.md 2017-06-12 09:48:38 +08:00
Pan
ecf7558e8e refine permission example 2017-06-06 10:46:20 +08:00
Pan
703c0c5cc5 fix lint 2017-06-02 18:14:39 +08:00
Pan
46b6a6e19f add qiniu upload example 2017-06-02 18:14:19 +08:00
Pan
e8fb41d0ff update read 2017-05-31 18:28:06 +08:00
Pan
f2847862e0 replace readme gifs 2017-05-31 17:04:26 +08:00
Pan
e6bae82fe5 replace img 2017-05-31 16:49:24 +08:00
52 changed files with 547 additions and 779 deletions

View File

@@ -52,6 +52,9 @@ Join the group on QQ 591724180.
- Two-factor authentication
- Collapsing sidebar (support nested routes)
- Mock data
- cache tabs example
- screenfull
- markdown2html
## Development
@@ -111,6 +114,9 @@ npm run build:prod
└── package.json // package.json
```
## Changelog
Detailed changes for each release are documented in the [release notes](https://github.com/PanJiaChen/vue-element-admin/releases).
## State Management
Only status of user and app configuration is managed by Vuex. Other data are managed by their own business pages.

View File

@@ -8,19 +8,24 @@
**注意该项目目前使用element-ui@1.3.3版本,所以最低兼容 Vue 2.3.0**
## 前言
> 这半年来一直在用vue写管理后台目前后台已经有七十多个页面十几种权限但维护成本依然很低所以准备开源分享一下后台开发的经验和成果。目前的技术栈主要的采用vue+element+axios.由于是个人项目所以数据请求都是用了mockjs模拟。注意在次项目基础上改造开发时请移除mock文件。
> 这半年来一直在用vue写管理后台目前后台已经有百来个个页面十几种权限但维护成本依然很低所以准备开源分享一下后台开发的经验和成果。目前的技术栈主要的采用vue+element+axios由webpack2打包.由于是个人项目所以数据请求都是用了mockjs模拟。注意在次项目基础上改造开发时请移除mock文件。
后续会出一系列的教程配套文章,如何从零构建后台项目框架,如何做完整的用户系统(如权限验证,二次登录等),如何二次开发组件(如富文本),如何整合七牛等等文章,各种后台开发经验等等。莫急~~
相应需求开了一个qq群 591724180 方便大家交流
写了一个系列的教程配套文章,如何从零构建后一个完整的后台项目:
- [wiki](https://github.com/PanJiaChen/vue-element-admin/wiki)
- [手摸手,带你用 vue 撸后台 系列一(基础篇)](https://juejin.im/post/59097cd7a22b9d0065fb61d2)
- [手摸手,带你用 vue 撸后台 系列二(登录权限篇)](https://juejin.im/post/591aa14f570c35006961acac)
- [ 手摸手,带你封装一个vue component](https://segmentfault.com/a/1190000009090836)
- [手摸手,带你用 vue 撸后台 系列三 (实战篇)](https://juejin.im/post/593121aa0ce4630057f70d35)
- [手摸手带你封装一个vue component](https://segmentfault.com/a/1190000009090836)
相应需求开了一个qq群 591724180 方便大家交流
**如有问题请先看上述文章和Wiki,若不能满足欢迎issue和pr~**
**该项目并不是一个脚手架,更倾向于是一个集成解决方案方案**
**该项目不支持低版本游览器有需求请自行添加polyfill[详情](https://github.com/PanJiaChen/vue-element-admin/wiki#babel-polyfill)**
**如有问题请先看上述问题和Wiki,不能满足欢迎issue和pr~**
## 功能
- 登录/注销
@@ -42,12 +47,16 @@
- table example
- 动态table example
- 拖拽table example
- 内联编辑table example
- form example
- 多环境发布
- dashboard
- 二次登录
- 动态侧边栏(支持多级路由)
- mock数据
- cache tabs example
- screenfull
- markdown2html
## 开发
@@ -104,6 +113,9 @@
```
## Changelog
Detailed changes for each release are documented in the [release notes](https://github.com/PanJiaChen/vue-element-admin/releases).
## 状态管理
后台只有user和app配置相关状态使用vuex存在全局其它数据都由每个业务页面自己管理。

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 486 KiB

After

Width:  |  Height:  |  Size: 532 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 605 KiB

After

Width:  |  Height:  |  Size: 601 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 968 KiB

After

Width:  |  Height:  |  Size: 1.1 MiB

View File

@@ -1,6 +1,6 @@
{
"name": "juicy",
"version": "1.0.1",
"version": "1.0.2",
"description": "A Vue.js admin",
"author": "Pan <panfree23@gmail.com>",
"license": "MIT",
@@ -13,57 +13,59 @@
"lint": "eslint --ext .js,.vue src"
},
"dependencies": {
"axios": "0.15.3",
"codemirror": "5.25.2",
"dropzone": "4.3.0",
"echarts": "3.4.0",
"element-ui": "1.3.3",
"axios": "0.16.2",
"codemirror": "5.26.0",
"dropzone": "5.1.0",
"echarts": "3.6.1",
"element-ui": "1.3.6",
"file-saver": "1.3.3",
"jquery": "3.1.1",
"js-cookie": "2.1.3",
"js-cookie": "2.1.4",
"jsonlint": "1.6.2",
"mockjs": "1.0.1-beta3",
"normalize.css": "3.0.2",
"nprogress": "0.2.0",
"screenfull": "3.2.2",
"showdown": "1.7.1",
"simplemde": "1.11.2",
"sortablejs": "1.5.1",
"vue": "2.3.3",
"vue-count-to": "1.0.5",
"vue-multiselect": "2.0.0-beta.15",
"vue-router": "2.5.3",
"vuedraggable": "2.8.4",
"vuedraggable": "2.13.1",
"vuex": "2.3.1",
"xlsx": "0.8.1"
},
"devDependencies": {
"autoprefixer": "6.7.2",
"babel-core": "6.22.1",
"babel-eslint": "7.1.1",
"babel-loader": "6.2.10",
"babel-plugin-transform-runtime": "6.22.0",
"babel-preset-es2015": "6.22.0",
"babel-preset-stage-2": "6.22.0",
"babel-register": "6.22.0",
"autoprefixer": "7.1.1",
"babel-core": "6.25.0",
"babel-eslint": "7.2.3",
"babel-loader": "7.0.0",
"babel-plugin-transform-runtime": "6.23.0",
"babel-preset-es2015": "6.24.1",
"babel-preset-stage-2": "6.24.1",
"babel-register": "6.24.1",
"chalk": "1.1.3",
"connect-history-api-fallback": "1.3.0",
"copy-webpack-plugin": "4.0.1",
"cross-env": "4.0.0",
"css-loader": "0.28.0",
"cross-env": "5.0.1",
"css-loader": "0.28.4",
"eslint": "3.19.0",
"eslint-friendly-formatter": "2.0.7",
"eslint-friendly-formatter": "3.0.0",
"eslint-import-resolver-webpack": "0.8.1",
"eslint-loader": "1.7.1",
"eslint-plugin-html": "2.0.1",
"eslint-plugin-import": "2.2.0",
"eslint-plugin-html": "3.0.0",
"eslint-plugin-import": "2.3.0",
"eventsource-polyfill": "0.9.6",
"express": "4.14.1",
"extract-text-webpack-plugin": "2.0.0",
"file-loader": "0.10.0",
"friendly-errors-webpack-plugin": "1.1.3",
"express": "4.15.3",
"extract-text-webpack-plugin": "2.1.2",
"file-loader": "0.11.2",
"friendly-errors-webpack-plugin": "1.6.1",
"function-bind": "1.1.0",
"html-webpack-plugin": "2.28.0",
"http-proxy-middleware": "0.17.3",
"node-sass": "4.5.2",
"http-proxy-middleware": "0.17.4",
"node-sass": "^4.5.0",
"opn": "4.0.2",
"optimize-css-assets-webpack-plugin": "1.3.0",
"ora": "1.1.0",
@@ -74,13 +76,12 @@
"semver": "5.3.0",
"style-loader": "0.17.0",
"url-loader": "0.5.8",
"vue-loader": "12.0.4",
"vue-style-loader": "2.0.5",
"vue-loader": "12.2.1",
"vue-style-loader": "3.0.1",
"vue-template-compiler": "2.3.3",
"webpack": "2.5.1",
"webpack-bundle-analyzer": "2.2.1",
"webpack-dashboard": "0.2.1",
"webpack-dev-middleware": "1.10.0",
"webpack": "2.6.1",
"webpack-bundle-analyzer": "2.8.2",
"webpack-dev-middleware": "1.10.2",
"webpack-hot-middleware": "2.18.0",
"webpack-merge": "4.1.0"
},

View File

@@ -1,28 +1,8 @@
// import fetch, { tpFetch } from 'utils/fetch';
import fetch from 'utils/fetch';
// export function getToken() {
// return fetch({
// url: '/qiniu/upload/token',
// method: 'get'
// });
// }
// export function upload(data) {
// return tpFetch({
// url: 'https://upload.qbox.me',
// method: 'post',
// data
// });
// }
// /* 外部uri转七牛uri*/
// export function netUpload(token, net_url) {
// const imgData = {
// net_url
// };
// return fetch({
// url: '/qiniu/upload/net/async',
// method: 'post',
// data: imgData
// });
// }
export function getToken() {
return fetch({
url: '/qiniu/upload/token', // 假地址 自行替换
method: 'get'
});
}

File diff suppressed because one or more lines are too long

View File

@@ -51,7 +51,6 @@
backgroundColor: '#344b58',
title: {
text: '统计',
subtext: 'from http://gallery.echartsjs.com',
x: '4%',
textStyle: {
color: '#fff',

View File

@@ -328,6 +328,7 @@
// 关闭控件
off() {
this.show = false;
this.$emit('close');
},
// 设置步骤
setStep(step) {

View File

@@ -0,0 +1,57 @@
<template>
<svg @click='click' class="icon screenfull" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" t="1497503607356" viewBox="0 0 1024 1024" version="1.1" p-id="4109" :fill='fill' :width="width" :height="height"><path d="M604.157933 512l204.484208 204.484208 82.942037-82.942037c10.364045-10.952446 26.498514-13.83817 40.309054-8.067746 13.249769 5.742794 22.465664 18.99154 22.465664 33.977859l0 258.042008c0 20.168342-16.695241 36.863582-36.863582 36.863582L659.452283 954.357873c-14.986319 0-28.236088-9.215896-33.977859-23.025413-5.770424-13.249769-2.885723-29.384237 8.067746-39.748283l82.942037-82.942037L512 604.157933 307.515792 808.642141l82.942037 82.942037c10.952446 10.364045 13.83817 26.498514 8.067746 39.748283-5.742794 13.809517-18.99154 23.025413-33.977859 23.025413L106.504686 954.357873c-20.168342 0-36.863582-16.695241-36.863582-36.863582L69.641103 659.452283c0-14.986319 9.215896-28.236088 23.025413-33.977859 13.249769-5.770424 29.384237-2.8847 39.748283 8.067746l82.942037 82.942037 204.484208-204.484208L215.357859 307.515792l-82.942037 82.942037c-6.890944 6.918573-16.10684 10.952446-25.911136 10.952446-4.593622 0-9.804297-1.14815-13.83817-2.8847-13.809517-5.742794-23.025413-18.99154-23.025413-33.977859L69.641103 106.504686c0-20.168342 16.695241-36.863582 36.863582-36.863582L364.546693 69.641103c14.986319 0 28.236088 9.215896 33.977859 23.025413 5.770424 13.249769 2.8847 29.384237-8.067746 39.748283l-82.942037 82.942037 204.484208 204.484208L716.484208 215.357859l-82.942037-82.942037c-10.952446-10.364045-13.83817-26.498514-8.067746-39.748283 5.742794-13.809517 18.99154-23.025413 33.977859-23.025413l258.042008 0c20.168342 0 36.863582 16.695241 36.863582 36.863582l0 258.042008c0 14.986319-9.215896 28.236088-22.465664 33.977859-4.593622 1.736551-9.804297 2.8847-14.397918 2.8847-9.804297 0-19.020192-4.033873-25.911136-10.952446l-82.942037-82.942037L604.157933 512z" p-id="4110"/></svg>
</template>
<script>
import screenfull from 'screenfull';
export default {
name: 'hamburger',
props: {
width: {
type: Number,
default: 22
},
height: {
type: Number,
default: 22
},
fill: {
type: String,
default: '#48576a'
}
},
data() {
return {
isFullscreen: false
}
},
methods: {
click() {
if (!screenfull.enabled) {
this.$message({
message: 'you browser can not work',
type: 'warning'
});
return false;
}
if (this.isFullscreen) {
screenfull.exit();
this.isFullscreen = false;
} else {
screenfull.request();
this.isFullscreen = true;
}
}
}
}
</script>
<style scoped>
.screenfull {
display: inline-block;
cursor: pointer;
vertical-align: -0.15em;
}
</style>

View File

@@ -1,119 +0,0 @@
<template>
<div class="upload-container">
<el-button :style="{background:color,borderColor:color}" @click=" dialogVisible=true" type="primary">上传音频
</el-button>
<el-dialog v-model="dialogVisible">
<el-form ref="form" :model="form" :rules="rules" label-width="100px" label-position="right">
<el-upload
class="editor-audio-upload"
action="https://upload.qbox.me"
:data="dataObj"
:show-file-list="true"
:file-list="audioList"
:on-success="handleAudioScucess"
:on-change="handleAudioChange"
:before-upload="audioBeforeUpload">
<el-button size="small" type="primary">上传音频</el-button>
</el-upload>
<el-form-item prop="url" label="音频URL">
<el-input v-model="form.url"></el-input>
</el-form-item>
<el-form-item prop="title" label="音频标题">
<el-input v-model="form.title"></el-input>
</el-form-item>
<el-form-item label="音频文本">
<el-input type="textarea" :autosize="{ minRows: 2}" v-model="form.text"></el-input>
</el-form-item>
</el-form>
<el-button @click="dialogVisible = false"> </el-button>
<el-button type="primary" @click="handleSubmit"> </el-button>
</el-dialog>
</div>
</template>
<script>
import { getToken } from 'api/qiniu';
export default {
name: 'editorAudioUpload',
props: {
color: {
type: String,
default: '#20a0ff'
}
},
data() {
return {
dialogVisible: false,
dataObj: { token: '', key: '' },
audioList: [],
tempAudioUrl: '',
form: {
title: '',
url: '',
text: ''
},
rules: {
title: [
{ required: true, trigger: 'blur' }
],
url: [
{ required: true, trigger: 'blur' }
]
}
};
},
methods: {
handleSubmit() {
this.$refs.form.validate(valid => {
if (valid) {
this.$emit('successCBK', this.form);
this.dialogVisible = false;
this.form = {
title: '',
url: '',
text: ''
}
} else {
this.$message('填写有误');
return false;
}
});
},
handleAudioChange(file, fileList) {
this.audioList = fileList.slice(-1);
},
handleAudioScucess() {
this.form.url = this.tempAudioUrl
},
audioBeforeUpload() {
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;
this.tempAudioUrl = response.data.qiniu_url;
resolve(true);
}).catch(err => {
console.log(err);
reject(false)
});
});
}
}
};
</script>
<style rel="stylesheet/scss" lang="scss" scoped>
.upload-container {
.editor-audio-upload {
button {
float: left;
margin-left: 30px;
margin-bottom: 20px;
}
}
}
</style>

View File

@@ -1,88 +0,0 @@
<template>
<div class="upload-container">
<el-button icon='upload' :style="{background:color,borderColor:color}" @click=" dialogVisible=true" type="primary">上传图片
</el-button>
<el-dialog v-model="dialogVisible">
<el-upload
class="editor-slide-upload"
action="https://httpbin.org/post"
:data="dataObj"
:multiple="true"
:file-list="fileList"
:show-file-list="true"
list-type="picture-card"
:on-remove="handleRemove"
:on-success="handleImageScucess">
<el-button size="small" type="primary">点击上传</el-button>
</el-upload>
<el-button @click="dialogVisible = false"> </el-button>
<el-button type="primary" @click="handleSubmit"> </el-button>
</el-dialog>
</div>
</template>
<script>
import { getToken } from 'api/qiniu';
export default {
name: 'editorSlideUpload',
props: {
color: {
type: String,
default: '#20a0ff'
}
},
data() {
return {
dialogVisible: false,
dataObj: { token: '', key: '' },
list: [],
fileList: []
};
},
methods: {
handleSubmit() {
const arr = this.list.map(v => v.url);
this.$emit('successCBK', arr);
this.list = [];
this.fileList = [];
this.dialogVisible = false;
},
handleRemove(file) {
const key = file.response.key;
for (let i = 0, len = this.list.length; i < len; i++) {
if (this.list[i].key === key) {
this.list.splice(i, 1);
return
}
}
},
handleImageScucess(file) {
this.list.push({ url: file.files.file });
},
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;
this.list.push({ key, url: response.data.qiniu_url });
resolve(true);
}).catch(err => {
console.log(err);
reject(false)
});
});
}
}
};
</script>
<style rel="stylesheet/scss" lang="scss" scoped>
.upload-container {
.editor-slide-upload {
margin-bottom: 20px;
}
}
</style>

View File

@@ -1,82 +0,0 @@
<template>
<div class="upload-container">
<el-button :style="{background:color,borderColor:color}" @click=" dialogVisible=true" type="primary">上传轮播图
</el-button>
<el-dialog v-model="dialogVisible">
<el-upload
class="editor-slide-upload"
action="https://upload.qbox.me"
:data="dataObj"
:multiple="true"
:show-file-list="true"
list-type="picture-card"
:on-remove="handleRemove"
:before-upload="beforeUpload">
<el-button size="small" type="primary">点击上传</el-button>
</el-upload>
<el-button @click="dialogVisible = false"> </el-button>
<el-button type="primary" @click="handleSubmit"> </el-button>
</el-dialog>
</div>
</template>
<script>
import { getToken } from 'api/qiniu';
export default {
name: 'editorSlideUpload',
props: {
color: {
type: String,
default: '#20a0ff'
}
},
data() {
return {
dialogVisible: false,
dataObj: { token: '', key: '' },
list: []
};
},
methods: {
handleSubmit() {
const arr = this.list.map(v => v.url);
this.$emit('successCBK', arr);
this.list = [];
this.dialogVisible = false;
},
handleRemove(file) {
const key = file.response.key;
for (let i = 0, len = this.list.length; i < len; i++) {
if (this.list[i].key === key) {
this.list.splice(i, 1);
return
}
}
},
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;
this.list.push({ key, url: response.data.qiniu_url });
resolve(true);
}).catch(err => {
console.log(err);
reject(false)
});
});
}
}
};
</script>
<style rel="stylesheet/scss" lang="scss" scoped>
.upload-container {
.editor-slide-upload {
margin-bottom: 20px;
}
}
</style>

View File

@@ -1,167 +0,0 @@
<template>
<div class="upload-container">
<el-button :style="{background:color,borderColor:color}" @click=" dialogVisible=true" type="primary">上传视频</el-button>
<el-dialog v-model="dialogVisible">
<el-form ref="form" :model="form" :rules="rules" label-width="140px" label-position="left">
<el-upload
class="editor-video-upload"
action="https://upload.qbox.me"
:data="dataObj"
:show-file-list="true"
:file-list="videoList"
:on-success="handleVideoScucess"
:on-change="handleVideoChange"
:before-upload="videoBeforeUpload">
<el-button size="small" type="primary">上传视频</el-button>
</el-upload>
<el-form-item prop="url" label="视频URL">
<el-input v-model="form.url"></el-input>
</el-form-item>
<el-form-item prop="title" label="视频标题">
<el-input v-model="form.title"></el-input>
</el-form-item>
<el-form-item label="上传视频封面图">
</el-form-item>
<el-upload
class="image-uploader"
action="https://upload.qbox.me"
:show-file-list="false"
:data="dataObj"
:on-success="handleImageScucess"
:before-upload="beforeImageUpload">
<img v-if="form.image" :src="form.image" class="image-uploader-image">
<i v-else class="el-icon-plus avatar-uploader-icon"></i>
</el-upload>
</el-form>
<el-button @click="dialogVisible = false"> </el-button>
<el-button type="primary" @click="handleSubmit"> </el-button>
</el-dialog>
</div>
</template>
<script>
import { getToken } from 'api/qiniu';
export default {
name: 'editorVideoUpload',
props: {
color: {
type: String,
default: '#20a0ff'
}
},
data() {
return {
dialogVisible: false,
dataObj: { token: '', key: '' },
videoList: [],
tempVideoUrl: '',
tempImageUrl: '',
form: {
title: '',
url: '',
image: ''
},
rules: {
url: [
{ required: true, trigger: 'blur' }
],
title: [
{ required: true, trigger: 'blur' }
]
}
};
},
methods: {
handleSubmit() {
this.$refs.form.validate(valid => {
if (valid) {
if (this.form.image.length > 0) {
this.$emit('successCBK', this.form);
this.dialogVisible = false;
this.form = {
title: '',
url: '',
image: ''
}
} else {
this.$message('请上传图片');
}
} else {
this.$message('填写有误');
return false;
}
});
},
handleVideoChange(file, fileList) {
this.videoList = fileList.slice(-1);
},
handleVideoScucess() {
this.form.url = this.tempVideoUrl
},
videoBeforeUpload() {
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;
this.tempVideoUrl = response.data.qiniu_url;
resolve(true);
}).catch(err => {
console.log(err);
reject(false)
});
});
},
handleImageScucess() {
this.form.image = this.tempImageUrl
},
beforeImageUpload() {
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;
this.tempImageUrl = response.data.qiniu_url;
resolve(true);
}).catch(err => {
console.log(err);
reject(false)
});
});
}
}
};
</script>
<style rel="stylesheet/scss" lang="scss" scoped>
.upload-container {
.editor-video-upload {
button {
float: left;
}
}
.image-uploader {
margin: 5px auto;
width: 400px;
height: 200px;
border: 1px dashed #d9d9d9;
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
line-height: 200px;
i {
font-size: 28px;
color: #8c939d;
}
.image-uploader-image {
height: 200px;
}
}
}
</style>

View File

@@ -1,29 +1,13 @@
<template>
<div class='tinymce-container editor-container'>
<textarea class='tinymce-textarea' :id="id"></textarea>
<!--业务需求可删除-->
<div class="editor-custom-btn-container">
<editorSlide v-if="customButton.indexOf('editorSlide')>=0" color="#3A71A8" class="editor-upload-btn" @successCBK="slideSuccessCBK"></editorSlide>
<editorAudio v-if="customButton.indexOf('editorAudio')>=0" color="#30B08F" class="editor-upload-btn" @successCBK="aduioSuccessCBK"></editorAudio>
<editorVideo v-if="customButton.indexOf('editorVideo')>=0" color="#E65D6E" class="editor-upload-btn" @successCBK="videoSuccessCBK"></editorVideo>
<editorImage v-if="customButton.indexOf('editorImage')>=0" color="#20a0ff" class="editor-upload-btn" @successCBK="imageSuccessCBK"></editorImage>
<!--业务需求可删除-->
</div>
</div>
</template>
<script>
// tinymce 在最外层目录static下 由index.html直接引入挂载在window下。不通过impot不打包
// 业务需求可删除
import editorAudio from './components/editorAudio';
import editorVideo from './components/editorVideo';
import editorSlide from './components/editorSlide';
import editorImage from './components/editorImage';
// import { getToken, upload } from 'api/qiniu'; // 七牛
// 业务需求可删除
export default {
name: 'tinymce',
components: { editorImage, editorAudio, editorSlide, editorVideo }, // 业务需求可删除
props: {
id: {
type: String,
@@ -33,13 +17,6 @@
type: String,
default: ''
},
customButton: {
type: Array,
required: false,
default() {
return ['editorAudio', 'editorImage']
}
},
toolbar: {
type: Array,
required: false,
@@ -92,15 +69,12 @@
imagetools_toolbar: 'watermark',
default_link_target: '_blank',
link_title: false,
textcolor_map: [
'1482f0', '1482f0',
'4595e6', '4595e6'],
init_instance_callback: editor => {
if (_this.value) {
editor.setContent(_this.value)
}
_this.hasInit = true;
editor.on('Change', () => {
editor.on('NodeChange Change KeyUp', () => {
this.hasChange = true;
this.$emit('input', editor.getContent({ format: 'raw' }));
});
@@ -172,55 +146,6 @@
}
});
},
methods: {
/* 业务代码可删除 start*/
imageSuccessCBK(arr) {
console.log(arr)
const _this = this;
arr.forEach(v => {
const node = document.createElement('img');
node.setAttribute('src', v);
node.onload = function() {
$(this).addClass('wscnph');
$(this).attr('data-wscntype', 'image');
$(this).attr('data-wscnh', this.height);
$(this).attr('data-wscnw', this.width);
tinymce.get(_this.id).insertContent(node.outerHTML)
}
})
},
slideSuccessCBK(arr) {
const node = document.createElement('img');
node.setAttribute('data-wscntype', 'slide');
node.setAttribute('data-uri', arr.toString());
node.setAttribute('data-wscnh', '190');
node.setAttribute('data-wscnw', '200');
node.setAttribute('src', ' https://wdl.wallstreetcn.com/6410b47d-a54c-4826-9bc1-c3e5df31280c');
node.className = 'wscnph editor-placeholder';
tinymce.get(this.id).insertContent(node.outerHTML)
},
videoSuccessCBK(form) {
const node = document.createElement('img');
node.setAttribute('data-wscntype', 'video');
node.setAttribute('data-uri', form.url);
node.setAttribute('data-cover-img-uri', form.image);
node.setAttribute('data-title', form.title);
node.setAttribute('src', 'https://wdl.wallstreetcn.com/07aeb3e7-f4ca-4207-befb-c987b3dc7011');
node.className = 'wscnph editor-placeholder';
tinymce.get(this.id).insertContent(node.outerHTML)
},
aduioSuccessCBK(form) {
const node = document.createElement('img');
node.setAttribute('data-wscntype', 'audio');
node.setAttribute('data-uri', form.url);
node.setAttribute('data-title', form.title);
node.setAttribute('data-text', form.text);
node.setAttribute('src', 'https://wdl.wallstreetcn.com/2ed0c8c8-fb82-499d-b81c-3fd1de114eae');
node.className = 'wscnph editor-placeholder';
tinymce.get(this.id).insertContent(node.outerHTML)
}
/* 业务代码可删除 end*/
},
destroyed() {
tinymce.get(this.id).destroy();
}
@@ -240,7 +165,6 @@
.editor-custom-btn-container {
position: absolute;
right: 15px;
/*z-index: 2005;*/
top: 18px;
}

View File

@@ -13,7 +13,7 @@ for (let i = 0; i < count; i++) {
title: '@ctitle(10, 20)',
forecast: '@float(0, 100, 2, 2)',
importance: '@integer(1, 3)',
'type|1': ['FD', 'FE', 'BI', 'VN'],
'type|1': ['CN', 'US', 'JP', 'EU'],
'status|1': ['published', 'draft', 'deleted'],
pageviews: '@integer(300, 5000)'
}));

View File

@@ -5,7 +5,7 @@ const userMap = {
role: ['admin'],
token: 'admin',
introduction: '我是超级管理员',
avatar: 'https://wdl.wallstreetcn.com/48a3e1e0-ea2c-4a4e-9928-247645e3428b',
avatar: 'https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif',
name: '超级管理员小潘',
uid: '001'
},
@@ -13,7 +13,7 @@ const userMap = {
role: ['editor'],
token: 'editor',
introduction: '我是编辑',
avatar: 'https://wdl.wallstreetcn.com/48a3e1e0-ea2c-4a4e-9928-247645e3428b',
avatar: 'https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif',
name: '普通编辑小张',
uid: '002'
@@ -22,7 +22,7 @@ const userMap = {
role: ['develop'],
token: 'develop',
introduction: '我是开发',
avatar: 'https://wdl.wallstreetcn.com/48a3e1e0-ea2c-4a4e-9928-247645e3428b',
avatar: 'https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif',
name: '工程师小王',
uid: '003'
}

View File

@@ -56,7 +56,9 @@ const DynamicTable = () => import('../views/example/table/dynamictable');
const Table = () => import('../views/example/table/table');
const DragTable = () => import('../views/example/table/dragTable');
const InlineEditTable = () => import('../views/example/table/inlineEditTable');
const Form1 = () => import('../views/example/form1');
const Form = () => import('../views/example/form');
const Tab = () => import('../views/example/tab/index');
/* permission */
const Permission = () => import('../views/permission/index');
@@ -197,7 +199,7 @@ export const asyncRouterMap = [
path: '/table',
component: TableLayout,
redirect: '/table/table',
name: 'table',
name: 'Table',
children: [
{ path: 'dynamictable', component: DynamicTable, name: '动态table' },
{ path: 'dragtable', component: DragTable, name: '拖拽table' },
@@ -205,7 +207,10 @@ export const asyncRouterMap = [
{ path: 'table', component: Table, name: '综合table' }
]
},
{ path: 'form1', component: Form1, name: '综合form1' }
{ path: 'form/edit', component: Form, name: '编辑Form', meta: { isEdit: true } },
{ path: 'form/create', component: Form, name: '创建Form' },
{ path: 'tab/index', component: Tab, name: 'Tab' }
]
},
{ path: '*', redirect: '/404', hidden: true }

View File

@@ -132,6 +132,16 @@ const user = {
Cookies.remove('Admin-Token');
resolve();
});
},
// 动态修改权限
ChangeRole({ commit }, role) {
return new Promise(resolve => {
commit('SET_ROLES', [role]);
commit('SET_TOKEN', role);
Cookies.set('Admin-Token', role);
resolve();
})
}
}
};

View File

@@ -1,7 +1,7 @@
<template>
<div class="components-container" >
<code>
这里的所有的图表都基于echarts,实例代码来源<a href='http://gallery.echartsjs.com/explore.html#sort=rank~timeframe=all~author=all' target='_blank'>gallery</a><br/>其实echarts封装的很好了用vue封装是很简单的事情建议大家自己来封装
这里的所有的图表都基于ECharts,实例代码来源<a href='http://gallery.echartsjs.com/explore.html#sort=rank~timeframe=all~author=all' target='_blank'>gallery</a><br/>其实ECharts封装的很好了用vue封装是很简单的事情建议大家自己来封装<a target='_blank' class='lin' href="https://segmentfault.com/a/1190000009762198#articleHeader16">相关教程</a>
</code>
</div>
</template>

View File

@@ -1,14 +1,13 @@
<template>
<div class="components-container" style='height:100vh'>
<div class='chart-container'>
<keyboardChart height='100%' width='100%' />
<keyboardChart height='100%' width='100%' />
</div>
</div>
</template>
<script>
import keyboardChart from 'components/Charts/keyboard';
export default {
components: { keyboardChart }
};
@@ -16,9 +15,9 @@
<style scoped>
.chart-container{
position: relative;
width: 100%;
height: 90%;
position: relative;
width: 100%;
height: 90%;
}
</style>

View File

@@ -1,14 +1,13 @@
<template>
<div class="components-container" style='height:100vh'>
<div class='chart-container'>
<keyboardChart2 id='apple' height='100%' width='100%' />
<keyboardChart2 id='apple' height='100%' width='100%' />
</div>
</div>
</template>
<script>
import keyboardChart2 from 'components/Charts/keyboard2';
export default {
components: { keyboardChart2 }
};
@@ -16,9 +15,9 @@
<style scoped>
.chart-container{
position: relative;
width: 100%;
height: 90%;
position: relative;
width: 100%;
height: 90%;
}
</style>

View File

@@ -1,16 +1,13 @@
<template>
<div class="components-container" style='height:100vh'>
https://github.com/ecomfe/echarts/blob/master/index.js
http://echarts.baidu.com/tutorial.html
<div class='chart-container'>
<lineMarker height='100%' width='100%' />
<lineMarker height='100%' width='100%' />
</div>
</div>
</template>
<script>
import lineMarker from 'components/Charts/lineMarker';
export default {
components: { lineMarker }
};
@@ -18,9 +15,9 @@
<style scoped>
.chart-container{
position: relative;
width: 100%;
height: 80%;
position: relative;
width: 100%;
height: 80%;
}
</style>

View File

@@ -1,14 +1,13 @@
<template>
<div class="components-container" style='height:100vh'>
<div class='chart-container'>
<mixchart id='apple' height='100%' width='100%' />
<mixchart id='apple' height='100%' width='100%' />
</div>
</div>
</template>
<script>
import mixchart from 'components/Charts/mixchart';
export default {
components: { mixchart }
};
@@ -16,10 +15,10 @@
<style scoped>
.chart-container{
position: relative;
width: 100%;
height: 90%;
padding-bottom: 40px;
position: relative;
width: 100%;
height: 90%;
padding-bottom: 40px;
}
</style>

View File

@@ -9,9 +9,11 @@
<el-button type="primary" icon="upload" style="position: absolute;bottom: 15px;margin-left: 40px;" @click="imagecropperShow=true">修改头像
</el-button>
<ImageCropper :width="300" :height="300" url="https://httpbin.org/post" @crop-upload-success="cropSuccess" :key="imagecropperKey" v-show="imagecropperShow" />
<ImageCropper :width="300" :height="300" url="https://httpbin.org/post" @close='close' @crop-upload-success="cropSuccess"
:key="imagecropperKey" v-show="imagecropperShow" />
</div>
</template>
<script>
import ImageCropper from 'components/ImageCropper';
import PanThumb from 'components/PanThumb';
@@ -29,6 +31,9 @@
this.imagecropperShow = false;
this.imagecropperKey = this.imagecropperKey + 1;
this.image = resData.files.avatar;
},
close() {
this.imagecropperShow = false;
}
}
};

View File

@@ -1,8 +1,8 @@
<template>
<div class="components-container">
<code>countTo 组件 <a href='https://github.com/PanJiaChen/vue-countTo' target='_blank'>线上地址</a></code>
<code> <a href='https://github.com/PanJiaChen/vue-countTo' target='_blank'>countTo component</a></code>
<count-to ref='example' class='example' :start-val='_startVal' :end-val='_endVal' :duration='_duration' :decimals='_decimals'
<count-to ref='example' class='example' :start-val='_startVal' :end-val='_endVal' :duration='_duration' :decimals='_decimals'
:separator='_separator' :prefix='_prefix' :suffix='_suffix' :autoplay='false' />
<div style='margin-left: 25%;margin-top: 40px;'>
<label class="label" for="startValInput">startVal: <input type="number" v-model.number='setStartVal' name='startValInput' /></label>
@@ -20,9 +20,9 @@
:separator=&#x27;{{_separator}}&#x27; :prefix=&#x27;{{_prefix}}&#x27; :suffix=&#x27;{{_suffix}}&#x27; :autoplay=false&gt;</code>
</div>
</template>
<script>
import countTo from 'vue-count-to';
export default {
components: { countTo },
data() {

View File

@@ -1,6 +1,6 @@
<template>
<div class="components-container">
<code>拖拽https://github.com/SortableJS/Vue.Draggable 项目Vue.Draggable</code>
<code>drag-list base on <a href="https://github.com/SortableJS/Vue.Draggable" target="_blank">Vue.Draggable</a></code>
<div class="editor-container">
<DndList :list1="list1" :list2="list2" list1Title="头条列表" list2Title="文章池" />
</div>

View File

@@ -1,7 +1,7 @@
<template>
<div class="components-container">
<code>这里暂时列出了自己在项目中自己封装和用到的组件如有补充可以提<a href='https://github.com/PanJiaChen/vue-element-admin/issues' target='_blank'>issue</a><br/>
我个人崇尚自己封装组件因为很多组件会和业务后高度的耦合很多时候第三方封装是满足不了需求的如有需要可以看楼主之前写过的一篇<a href='https://segmentfault.com/a/1190000009090836' target='_blank'>文章</a>
<code>这里暂时列出了自己在项目中用到的组件和一些自己封装的组件如有补充可以提<a target='_blank' href='https://github.com/PanJiaChen/vue-element-admin/issues'> issue </a><br/>
我个人崇尚自己封装组件因为很多组件会和业务后高度的耦合而且第三方封装的组件灵活性可控性都不高如有需要可以看楼主之前写过的一篇<a href='https://segmentfault.com/a/1190000009090836' target='_blank'>文章</a>
</code>
</div>
</template>

View File

@@ -1,11 +1,12 @@
<template>
<div class="components-container" style='height:100vh'>
<code>有校验</code>
<code>jsonEditor is base on <a href="https://github.com/codemirror/CodeMirror" target="_blank">CodeMirrorr</a>,lint base on json-lint </code>
<div class="editor-container">
<json-editor ref="jsonEditor" v-model="value"></json-editor>
</div>
</div>
</template>
<script>
import jsonEditor from 'components/jsonEditor';
const jsonData = '[{"items":[{"market_type":"forexdata","symbol":"XAUUSD"},{"market_type":"forexdata","symbol":"UKOIL"},{"market_type":"forexdata","symbol":"CORN"}],"name":""},{"items":[{"market_type":"forexdata","symbol":"XAUUSD"},{"market_type":"forexdata","symbol":"XAGUSD"},{"market_type":"forexdata","symbol":"AUTD"},{"market_type":"forexdata","symbol":"AGTD"}],"name":"贵金属"},{"items":[{"market_type":"forexdata","symbol":"CORN"},{"market_type":"forexdata","symbol":"WHEAT"},{"market_type":"forexdata","symbol":"SOYBEAN"},{"market_type":"forexdata","symbol":"SUGAR"}],"name":"农产品"},{"items":[{"market_type":"forexdata","symbol":"UKOIL"},{"market_type":"forexdata","symbol":"USOIL"},{"market_type":"forexdata","symbol":"NGAS"}],"name":"能源化工"}]';
@@ -21,8 +22,8 @@
<style scoped>
.editor-container{
position: relative;
height: 100%;
position: relative;
height: 100%;
}
</style>

View File

@@ -1,19 +1,30 @@
<template>
<div class="components-container">
<code>公司做的后台主要是一个cms系统公司也是以自媒体为核心的所以富文本是后台很核心的功能在选择富文本的过程中也走了不少的弯路市面上常见的富文本都基本用过了最终选择了tinymce</code>
<code>Markdown 我们这里选用了 <a href="https://github.com/sparksuite/simplemde-markdown-editor" target="_blank">simplemde-markdown-editor</a> 简单的用vue封装了一下<a target='_blank' href='https://segmentfault.com/a/1190000009762198#articleHeader14'> 相关文章 </a></code>
<div class="editor-container">
<MdEditor id='contentEditor' ref="contentEditor" v-model='content' :height="300" :zIndex='20'></MdEditor>
<MdEditor id='contentEditor' ref="contentEditor" v-model='content' :height="300" :zIndex='20'></MdEditor>
</div>
<el-button @click='markdown2Html' style="margin-top:80px;" type="primary">转为HTML<i class="el-icon-document el-icon--right"></i></el-button>
<div v-html="html"></div>
</div>
</template>
<script>
import MdEditor from 'components/MdEditor';
export default {
components: { MdEditor },
data() {
return {
content: 'Simplemde'
content: '## Simplemde',
html: ''
}
},
methods: {
markdown2Html() {
import('showdown').then(showdown => {
const converter = new showdown.Converter();
this.html = converter.makeHtml(this.content)
})
}
}
};

View File

@@ -1,14 +1,13 @@
<template>
<div class="components-container">
<code>公司做的后台主要是一个cms系统公司也是以自媒体为核心的所以富文本是后台很核心的功能在选择富文本的过程中也走了不少的弯路市面上常见的富文本都基本用过了最终选择了tinymce</code>
<div class="editor-container">
<code>公司做的后台主要是一个cms系统公司也是以自媒体为核心的所以富文本是后台很核心的功能在选择富文本的过程中也走了不少的弯路市面上常见的富文本都基本用过了最终选择了Tinymce<a target='_blank' href='https://segmentfault.com/a/1190000009762198#articleHeader13'> 相关文章 </a></code>
<div>
<Tinymce :height=200 ref="editor" v-model="content"></Tinymce>
</div>
<!--<div class='editor-content'>
{{content}}
</div>-->
<div class='editor-content' v-html='content'></div>
</div>
</template>
<script>
import Tinymce from 'components/Tinymce';
@@ -18,11 +17,14 @@
return {
content: 'Tinymce'
}
},
methods: {
}
};
</script>
<style scoped>
.editor-content{
margin-top: 20px;
}
</style>

View File

@@ -1,26 +1,19 @@
<template>
<div class="errPage-container">
<!--error code-->
<err-code/>
<!--error code-->
<err-code/>
<h3>请点击右上角bug小图表</h3>
<code>
<code>
现在的管理后台基本都是spa的形式了它增强了用户体验但同时也会怎增加页面出问题的可能性可能一个小小的疏忽就导致整个页面的死锁好在Vue官网提供了一个方法来捕获处理异常
</code>
<a href="#"><img src='../../../documentImg/code1.png'></a>
</div>
</template>
<script>
import errCode from './errcode';
// import code1Img from
export default {
components: { errCode },
data() {
return {
}
},
methods: {
back() {
this.$router.go(-1)
@@ -28,6 +21,7 @@
}
};
</script>
<style scoped>
.errPage-container{
padding: 30px;

View File

@@ -4,7 +4,7 @@
<el-row>
<el-col :span="12">
<h1 class="text-jumbo text-ginormous">Oops!</h1>
gif来源<a href='https://zh.airbnb.com/' target='_blank'>airbnb</a> 页面
gif来源<a href='https://zh.airbnb.com/' target='_blank'>airbnb</a> 页面
<h2>你没有权限去该页面</h2>
<h6>如有不满请联系你领导</h6>
<ul class="list-unstyled">
@@ -26,9 +26,9 @@
</el-dialog>
</div>
</template>
<script>
import errGif from 'assets/401.gif';
export default {
data() {
return {
@@ -50,38 +50,38 @@
</script>
<style rel="stylesheet/scss" lang="scss" scoped>
.errPage-container {
width: 800px;
margin: 100px auto;
.pan-back-btn {
background: #008489;
color: #fff;
}
.pan-gif {
margin: 0 auto;
display: block;
}
.pan-img{
display: block;
margin: 0 auto;
}
.text-jumbo {
font-size: 60px;
font-weight: 700;
color: #484848;
}
.list-unstyled {
font-size: 14px;
li {
padding-bottom: 5px;
}
a {
color: #008489;
text-decoration: none;
&:hover {
text-decoration: underline;
}
}
}
.errPage-container {
width: 800px;
margin: 100px auto;
.pan-back-btn {
background: #008489;
color: #fff;
}
.pan-gif {
margin: 0 auto;
display: block;
}
.pan-img {
display: block;
margin: 0 auto;
}
.text-jumbo {
font-size: 60px;
font-weight: 700;
color: #484848;
}
.list-unstyled {
font-size: 14px;
li {
padding-bottom: 5px;
}
a {
color: #008489;
text-decoration: none;
&:hover {
text-decoration: underline;
}
}
}
}
</style>

View File

@@ -9,7 +9,7 @@
</div>
<div class="bullshit">
<div class="bullshit__oops">OOPS!</div>
<div class="bullshit__info">版权所有<a class='link-type' href='https://wallstreetcn.com' target='_blank'>华尔街见闻</a></div>
<div class="bullshit__info">版权所有<a class='link-type' href='https://wallstreetcn.com' target='_blank'>华尔街见闻</a></div>
<div class="bullshit__headline">{{ message }}</div>
<div class="bullshit__info">请检查您输入的网址是否正确请点击以下按钮返回主页或者发送错误报告</div>
<a href="/" class="bullshit__return-home">返回首页</a>
@@ -17,6 +17,7 @@
</div>
</div>
</template>
<script>
export default {
computed: {
@@ -26,6 +27,7 @@
}
}
</script>
<style rel="stylesheet/scss" lang="scss" scoped>
.wscn-http404 {
position: relative;

View File

@@ -7,6 +7,9 @@
<div style="display:inline-block">
<el-dropdown trigger="click">
<router-link style="margin-right:15px;" v-show='isEdit' :to="{ path:'create'}">
<el-button type="info">创建form</el-button>
</router-link>
<el-button>{{!postForm.comment_disabled?'评论已打开':'评论已关闭'}}<i class="el-icon-caret-bottom el-icon--right"></i></el-button>
<el-dropdown-menu class="no-padding no-hover" slot="dropdown">
<el-dropdown-item>
@@ -185,10 +188,16 @@
computed: {
contentShortLength() {
return this.postForm.content_short.length
},
isEdit() {
return this.$route.meta.isEdit // meta
// return this.$route.path.indexOf('edit') !== -1 //
}
},
created() {
this.fetchData();
if (this.isEdit) {
this.fetchData();
}
},
methods: {
fetchData() {

View File

@@ -0,0 +1,98 @@
<template>
<el-table :data="list" border fit highlight-current-row style="width: 100%">
<el-table-column align="center" label="序号" width="65">
<template scope="scope">
<span>{{scope.row.id}}</span>
</template>
</el-table-column>
<el-table-column width="180px" align="center" label="时间">
<template scope="scope">
<span>{{scope.row.timestamp | parseTime('{y}-{m}-{d} {h}:{i}')}}</span>
</template>
</el-table-column>
<el-table-column min-width="300px" label="标题">
<template scope="scope">
<span class="link-type" @click="handleUpdate(scope.row)">{{scope.row.title}}</span>
<el-tag>{{scope.row.type}}</el-tag>
</template>
</el-table-column>
<el-table-column width="110px" align="center" label="作者">
<template scope="scope">
<span>{{scope.row.author}}</span>
</template>
</el-table-column>
<el-table-column width="80px" label="重要性">
<template scope="scope">
<wscn-icon-svg v-for="n in +scope.row.importance" icon-class="wujiaoxing" class="meta-item__icon" :key="n" />
</template>
</el-table-column>
<el-table-column align="center" label="阅读数" width="95">
<template scope="scope">
<span>{{scope.row.pageviews}}</span>
</template>
</el-table-column>
<el-table-column class-name="status-col" label="状态" width="90">
<template 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_table';
export default {
name: 'articleDetail',
props: {
type: {
type: String,
default: 'CN'
}
},
data() {
return {
list: null,
total: null,
listQuery: {
page: 1,
limit: 5,
type: this.type,
sort: '+id'
}
}
},
filters: {
statusFilter(status) {
const statusMap = {
published: 'success',
draft: 'gray',
deleted: 'danger'
};
return statusMap[status]
}
},
created() {
this.getList();
},
methods: {
getList() {
this.$emit('create'); // for test
fetchList(this.listQuery).then(response => {
this.list = response.data.items;
this.total = response.data.total;
})
}
}
}
</script>

View File

@@ -0,0 +1,44 @@
<template>
<div class="tab-container">
<el-tag type="primary">mounted times {{createdTimes}}</el-tag>
<el-tabs style='margin-top:15px;' v-model="activeName" 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'></tab-pane>
</keep-alive>
</el-tab-pane>
</el-tabs>
</div>
</template>
<script>
import tabPane from './components/tabPane'
export default {
name: 'tabDemo',
components: { tabPane },
data() {
return {
tabMapOptions: [
{ label: '中国', key: 'CN' },
{ label: '美国', key: 'US' },
{ label: '日本', key: 'JP' },
{ label: '欧元区', key: 'EU' }
],
activeName: 'CN',
createdTimes: 0
}
},
methods: {
showCreatedTimes() {
this.createdTimes = this.createdTimes + 1;
}
}
}
</script>
<style scoped>
.tab-container{
margin: 30px;
}
</style>

View File

@@ -1,7 +1,7 @@
<template>
<div class="app-container">
<div class="filter-container">
<el-checkbox-group v-model="formThead">
<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>
@@ -18,22 +18,21 @@
</el-table>
</div>
</template>
<script>
export default {
data() {
return {
tableData: [{
name: '水果',
list: [{ name: 'apple', value: 10 }, { name: 'banana', value: 20 }, { name: 'orange', value: 20 }]
}, {
name: '水果2',
list: [{ name: 'apple2', value: 12 }, { name: 'banana2', value: 22 }, { name: 'orange', value: 20 }]
}],
formThead: ['apple', 'banana']
export default {
data() {
return {
tableData: [{
name: '水果',
list: [{ name: 'apple', value: 10 }, { name: 'banana', value: 20 }, { name: 'orange', value: 20 }]
}, {
name: '水果2',
list: [{ name: 'apple2', value: 12 }, { name: 'banana2', value: 22 }, { name: 'orange', value: 20 }]
}],
formThead: ['apple', 'banana']
}
}
}
};
};
</script>

View File

@@ -22,10 +22,10 @@
<el-button class="filter-item" type="primary" v-waves icon="search" @click="handleFilter">搜索</el-button>
<el-button class="filter-item" style="margin-left: 10px;" @click="handleCreate" type="primary" icon="edit">添加</el-button>
<el-button class="filter-item" type="primary" icon="document" @click="handleDownload">导出</el-button>
<el-checkbox class="filter-item" @change='tableKey=tableKey+1' v-model="showAuditor">显示审核人</el-checkbox>
<el-checkbox class="filter-item" @change='tableKey=tableKey+1' v-model="showAuditor">显示审核人</el-checkbox>
</div>
<el-table :key='tableKey' :data="list" v-loading.body="listLoading" border fit highlight-current-row style="width: 100%">
<el-table :key='tableKey' :data="list" v-loading.body="listLoading" border fit highlight-current-row style="width: 100%">
<el-table-column align="center" label="序号" width="65">
<template scope="scope">
@@ -58,10 +58,9 @@
</template>
</el-table-column>
<el-table-column width="80px" label="重要性">
<template scope="scope">
<wscn-icon-svg v-for="n in +scope.row.importance" icon-class="wujiaoxing" class="meta-item__icon" :key="n" />
<wscn-icon-svg v-for="n in +scope.row.importance" icon-class="wujiaoxing" class="meta-item__icon" :key="n" />
</template>
</el-table-column>
@@ -71,28 +70,28 @@
</template>
</el-table-column>
<el-table-column class-name="status-col" label="状态" width="90">
<el-table-column class-name="status-col" label="状态" width="90">
<template scope="scope">
<el-tag :type="scope.row.status | statusFilter">{{scope.row.status}}</el-tag>
</template>
</el-table-column>
<el-table-column align="center" label="操作" width="150">
<el-table-column align="center" label="操作" width="150">
<template scope="scope">
<el-button v-if="scope.row.status!='published'" size="small" type="success" @click="handleModifyStatus(scope.row,'published')">发布
</el-button>
<el-button v-if="scope.row.status!='draft'" size="small" @click="handleModifyStatus(scope.row,'draft')">草稿
</el-button>
<el-button v-if="scope.row.status!='deleted'" size="small" type="danger" @click="handleModifyStatus(scope.row,'deleted')">删除
</el-button>
</el-button>
</template>
</el-table-column>
</el-table>
<div v-show="!listLoading" class="pagination-container">
<el-pagination @size-change="handleSizeChange" @current-change="handleCurrentChange" :current-page.sync="listQuery.page" :page-sizes="[10,20,30, 50]"
:page-size="listQuery.limit" layout="total, sizes, prev, pager, next, jumper" :total="total">
<el-pagination @size-change="handleSizeChange" @current-change="handleCurrentChange" :current-page.sync="listQuery.page"
:page-sizes="[10,20,30, 50]" :page-size="listQuery.limit" layout="total, sizes, prev, pager, next, jumper" :total="total">
</el-pagination>
</div>
@@ -138,14 +137,14 @@
</el-dialog>
<el-dialog title="阅读数统计" :visible.sync="dialogPvVisible" size="small">
<el-table :data="pvData" border fit highlight-current-row style="width: 100%">
<el-table-column prop="key" label="渠道"> </el-table-column>
<el-table-column prop="pv" label="pv"> </el-table-column>
<el-table :data="pvData" border fit highlight-current-row style="width: 100%">
<el-table-column prop="key" label="渠道"> </el-table-column>
<el-table-column prop="pv" label="pv"> </el-table-column>
</el-table>
<span slot="footer" class="dialog-footer">
<el-button type="primary" @click="dialogPvVisible = false"> </el-button>
</span>
</el-dialog >
</el-dialog>
</div>
</template>
@@ -155,10 +154,10 @@
import { parseTime } from 'utils';
const calendarTypeOptions = [
{ key: 'FD', display_name: '经济数据' },
{ key: 'FE', display_name: '财经大事' },
{ key: 'BI', display_name: '国债发行' },
{ key: 'VN', display_name: '假期报告' }
{ key: 'CN', display_name: '中国' },
{ key: 'US', display_name: '美国' },
{ key: 'JP', display_name: '日本' },
{ key: 'EU', display_name: '欧元区' }
];
// arr to obj

View File

@@ -32,6 +32,7 @@
</el-table>
</div>
</template>
<script>
import { getList } from 'api/article';
export default {

View File

@@ -1,15 +1,24 @@
<template>
<div class="app-container">
<div class="wrapper">
<code>
这半年来一直在用vue写管理后台目前后台已经有七十多个页面十几种权限但维护成本依然很低效率依然很高所以准备开源分享一下后台开发的经验和成果目前的技术栈主要的采用vue+element+axios.由于是个人项目所以数据请求都是用了mockjs代替会出一系列的教程配套文章如如何从零构建后台项目框架如何做完整的用户系统如权限验证二次登录等如何二次开发组件如富文本如何整合七牛等等文章各种后台开发经验等等莫急~~
<code>
这半年来一直在用vue写管理后台目前后台已经有百来个个页面十几种权限但维护成本依然很低所以准备开源分享一下后台开发的经验和成果目前的技术栈主要的采用vue+element+axios由webpack2打包.由于是个人项目所以数据请求都是用了mockjs模拟注意在次项目基础上改造开发时请移除mock文件
写了一个系列的教程配套文章如何从零构建后一个完整的后台项目:
<ul>
<li><a target='_blank' class='lin' href="https://github.com/PanJiaChen/vue-element-admin/wiki">wiki</a></li>
<li><a target='_blank' href="https://juejin.im/post/59097cd7a22b9d0065fb61d2">手摸手带你用 vue 撸后台 系列一(基础篇)</a></li>
<li><a target='_blank' href="https://juejin.im/post/591aa14f570c35006961acac">手摸手带你用 vue 撸后台 系列二(登录权限篇)</a></li>
<li><a target='_blank' href="https://juejin.im/post/593121aa0ce4630057f70d35">手摸手带你用 vue 撸后台 系列三 (实战篇)</a></li>
<li><a target='_blank' href="https://segmentfault.com/a/1190000009090836">手摸手带你封装一个vue component</a></li>
</ul>
</code>
</div>
</div>
</template>
<style scoped>
.wrapper{
width: 800px;
margin: 30px auto;
width: 800px;
margin: 30px auto;
}
</style>

View File

@@ -11,9 +11,7 @@
name: 'AppMain',
computed: {
key() {
return this.$route.name !== undefined
? this.$route.name + +new Date()
: this.$route + +new Date()
return this.$route.name !== undefined ? this.$route.name + +new Date() : this.$route + +new Date()
}
}
}

View File

@@ -1,7 +1,7 @@
<template>
<div class="app-wrapper" :class="{hideSidebar:!sidebar.opened}">
<div class="sidebar-wrapper">
<Sidebar class="sidebar-container"/>
<Sidebar class="sidebar-container" />
</div>
<div class="main-container">
<Navbar/>
@@ -27,9 +27,9 @@
}
}
</script>
<style rel="stylesheet/scss" lang="scss" scoped>
@import "src/styles/mixin.scss";
.app-wrapper {
@include clearfix;
position: relative;
@@ -59,12 +59,17 @@
bottom: 0;
left: 0;
z-index: 1001;
overflow-x: hidden;
overflow: hidden;
transition: all .28s ease-out;
@include scrollBar;
}
.sidebar-container {
transition: all .28s ease-out;
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: -17px;
overflow-y: scroll;
}
.main-container {
min-height: 100%;

View File

@@ -34,6 +34,7 @@
}
}
</script>
<style rel="stylesheet/scss" lang="scss" scoped>
.app-levelbar.el-breadcrumb {
display: inline-block;

View File

@@ -1,20 +1,21 @@
<template>
<el-menu class="navbar" mode="horizontal">
<Hamburger class="hamburger-container" :toggleClick="toggleSideBar" :isActive="sidebar.opened"></Hamburger>
<hamburger class="hamburger-container" :toggleClick="toggleSideBar" :isActive="sidebar.opened"></hamburger>
<levelbar></levelbar>
<ErrLog v-if="log.length>0" class="errLog-container" :logsList="log"></ErrLog>
<error-log v-if="log.length>0" class="errLog-container" :logsList="log"></error-log>
<screenfull class='screenfull'></screenfull>
<el-dropdown class="avatar-container" trigger="click">
<div class="avatar-wrapper">
<img class="user-avatar" :src="avatar+'?imageView2/1/w/80/h/80'">
<i class="el-icon-caret-bottom"/>
<i class="el-icon-caret-bottom" />
</div>
<el-dropdown-menu class="user-dropdown" slot="dropdown">
<router-link class='inlineBlock' to="/">
<router-link class='inlineBlock' to="/">
<el-dropdown-item>
首页
</el-dropdown-item>
</router-link>
<router-link class='inlineBlock' to="/admin/profile">
<router-link class='inlineBlock' to="/admin/profile">
<el-dropdown-item>
设置
</el-dropdown-item>
@@ -29,14 +30,16 @@
import { mapGetters } from 'vuex';
import Levelbar from './Levelbar';
import Hamburger from 'components/Hamburger';
import ErrLog from 'components/ErrLog';
import Screenfull from 'components/Screenfull';
import ErrorLog from 'components/ErrLog';
import errLogStore from 'store/errLog';
export default {
components: {
Levelbar,
Hamburger,
ErrLog
ErrorLog,
Screenfull
},
data() {
return {
@@ -62,6 +65,7 @@
}
}
</script>
<style rel="stylesheet/scss" lang="scss" scoped>
.navbar {
height: 50px;
@@ -78,6 +82,12 @@
position: absolute;
right: 150px;
}
.screenfull{
position: absolute;
right: 90px;
top: 16px;
color: red;
}
.avatar-container {
height: 50px;
display: inline-block;

View File

@@ -6,9 +6,9 @@
<script>
import { mapGetters } from 'vuex';
import SidebarItem from './SidebarItem';
import SidebarItem from './SidebarItem';
export default {
components:{SidebarItem},
components: { SidebarItem },
computed: {
...mapGetters([
'permission_routers'
@@ -16,6 +16,7 @@
}
}
</script>
<style rel="stylesheet/scss" lang="scss" scoped>
.el-menu {
min-height: 100%;

View File

@@ -34,6 +34,7 @@
}
}
</script>
<style rel="stylesheet/scss" lang="scss" scoped>
.wscn-icon {
margin-right: 10px;

View File

@@ -1,10 +1,10 @@
<template>
<div class="app-container">
<div style='margin-bottom:15px;'>你的权限 {{roles}}</div>
切换权限
<el-radio-group v-model="role">
<el-radio-button label="editor"></el-radio-button>
</el-radio-group>
<div style='margin-bottom:15px;'>你的权限 {{roles}}</div>
切换权限
<el-radio-group v-model="role">
<el-radio-button label="editor"></el-radio-button>
</el-radio-group>
</div>
</template>
@@ -23,8 +23,9 @@
},
watch: {
role(val) {
this.$store.commit('SET_ROLES', [val]);
this.$router.push({ path: '/permission/index?' + +new Date() });
this.$store.dispatch('ChangeRole', val).then(() => {
this.$router.push({ path: '/permission/index?' + +new Date() });
})
}
}
}

View File

@@ -0,0 +1,44 @@
<template>
<el-upload
action="https://upload.qbox.me"
:data="dataObj"
drag
:multiple="true"
:before-upload="beforeUpload">
<i class="el-icon-upload"></i>
<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>

View File

@@ -76,17 +76,17 @@ export default {
</script>
<style scoped>
.box-card{
width: 400px;
margin: 20px auto;
}
.block{
padding: 30px 24px;
}
.alert-item{
margin-bottom: 10px;
}
.tag-item{
margin-right: 15px;
}
.box-card{
width: 400px;
margin: 20px auto;
}
.block{
padding: 30px 24px;
}
.alert-item{
margin-bottom: 10px;
}
.tag-item{
margin-right: 15px;
}
</style>