Compare commits

..

18 Commits

Author SHA1 Message Date
Pan
b1311322ad [release] 2.1.0 2017-09-11 14:45:55 +08:00
Pan
ffec6b6df7 add:upload excel 2017-09-11 14:43:12 +08:00
Pan
f0afbf7ea5 refine:set page=1 when table filter 2017-09-11 11:17:29 +08:00
Pan
a4f8e0b805 Update README.md 2017-09-11 10:56:28 +08:00
花裤衩
2d72df3605 Update README.md 2017-09-08 20:10:54 +08:00
花裤衩
e9f92a7d3d Update README.md 2017-09-08 18:32:48 +08:00
花裤衩
ee362f22b2 Update README.md 2017-09-08 18:32:15 +08:00
花裤衩
11426c8494 add donate 2017-09-08 18:24:43 +08:00
Hex
fa5f5e9d26 与 vue-cli webpack 模板不一致 2017-09-08 15:53:09 +08:00
dongsuo
cda292dec1 fix blocked by AdBlock 2017-09-07 10:35:35 +08:00
Svend
877b73cd67 📝 typing fix 2017-09-06 10:20:31 +08:00
maemo
53d7243316 fix a typo 2017-09-05 13:53:24 +08:00
Pan
5379510013 refine tabsview 2017-09-01 10:59:45 +08:00
Pan
f712d4682e add:when active tabs closed will go to last path 2017-09-01 10:46:20 +08:00
Pan
cb0e889829 fix tinymce bug 2017-09-01 10:12:50 +08:00
Pan
6a8a02f839 refine tinymce 2017-09-01 10:04:08 +08:00
Pan
d56cd59474 refine code 2017-08-30 18:22:32 +08:00
Pan
a10cfcc837 add tinymce upload demo 2017-08-30 14:41:10 +08:00
17 changed files with 313 additions and 58 deletions

View File

@@ -1,29 +1,19 @@
[![vue](https://img.shields.io/badge/vue-2.4.2-brightgreen.svg)](https://github.com/vuejs/vue)
[![element-ui](https://img.shields.io/badge/element--ui-1.4.2-brightgreen.svg)](https://github.com/ElemeFE/element)
[![license](https://img.shields.io/github/license/mashape/apistatus.svg)](https://github.com/PanJiaChen/vue-element-admin/blob/master/LICENSE)
[![GitHub release](https://img.shields.io/github/release/PanJiaChen/vue-element-admin.svg)]()
## Intro
> In the past half year, I have been building a backend for management dashboard using Vue. Though the backend has contained greater than 70 pages and over 10 permissions, it still takes insignificant effort to maintain the project. So I decide to make it open source so as to share my development experience and progress on backend. The tech stack is mainly [Vue.js](https://github.com/vuejs/vue)+[Element](https://github.com/ElemeFE/element)+[axios](https://github.com/mzabriskie/axios). Since it's a personal project, all data requests are simulated with [Mock.js](https://github.com/nuysoft/Mock). **Note:** if anyone wants to modify or develop based on this project, please remove the mock files.
**Live demo:** http://panjiachen.github.io/vue-element-admin
**Note: element-ui@1.3.3 is used in the project, so vue 2.3.0+ is required.**
**Note: element-ui@1.4.2 is used in the project, so vue 2.3.0+ is required.**
More tutorials incoming. Including articles on:
- How to build structure of a backend dashboard project from scratch
- How to make a complete user system (e.g. permission authentication, two-factor authentication)
- How to package components (e.g. rich text)
- How to integrate with [Qiniu](https://www.qiniu.com/)
- Other development experience on backend
Join the group on QQ 591724180.
**Tutorials:**
- [Wiki](https://github.com/PanJiaChen/vue-element-admin/wiki)
- [Step by step instructions on playing with backend using Vue Part 1 - Fundamentals](https://juejin.im/post/59097cd7a22b9d0065fb61d2)
- [Step by step instructions on playing with backend using Vue Part 2 - Login permission](https://juejin.im/post/591aa14f570c35006961acac)
- [Step by step instructions on packaging a Vue component](https://segmentfault.com/a/1190000009090836)
**Please read the Wiki and articles above before creating any issue. Feel free to contribute by making a pull request.**
- vueAdmin-template: [vueAdmin-template](https://github.com/PanJiaChen/vueAdmin-template)  
- electron-vue-admin: [electron-vue-admin](https://github.com/PanJiaChen/electron-vue-admin)
- Donate:[donate](https://github.com/PanJiaChen/vue-element-admin/blob/master/README-en.md#donate)
## Features
@@ -43,6 +33,7 @@ Join the group on QQ 591724180.
- 401, 404 error page
- Error log
- Exporting to Excel
- Upload Excel
- Table example
- Interactive table example
- Drag & drop table example
@@ -117,6 +108,10 @@ npm run build:prod
## Changelog
Detailed changes for each release are documented in the [release notes](https://github.com/PanJiaChen/vue-element-admin/releases).
## Donate
If you find this project useful, you can buy me a cup of coffee
![donate](https://panjiachen.github.io/donate/donation.png)
## State Management
Only status of user and app configuration is managed by Vuex. Other data are managed by their own business pages.
@@ -174,3 +169,7 @@ Only status of user and app configuration is managed by Vuex. Other data are man
#### More
http://panjiachen.github.io/vue-element-admin
## License
MIT

View File

@@ -1,7 +1,7 @@
# vue-element-admin #
[![vue](https://img.shields.io/badge/vue-2.4.2-brightgreen.svg)](https://github.com/vuejs/vue)
[![element-ui](https://img.shields.io/badge/element--ui-1.4.1-brightgreen.svg)](https://github.com/ElemeFE/element)
[![element-ui](https://img.shields.io/badge/element--ui-1.4.2-brightgreen.svg)](https://github.com/ElemeFE/element)
[![license](https://img.shields.io/github/license/mashape/apistatus.svg)](https://github.com/PanJiaChen/vue-element-admin/blob/master/LICENSE)
[![GitHub release](https://img.shields.io/github/release/PanJiaChen/vue-element-admin.svg)]()
@@ -12,11 +12,15 @@
[wiki](https://github.com/PanJiaChen/vue-element-admin/wiki)
**本项目的定位是后台集成方案,不适合当基础模板来开发,模板建议使用 [vueAdmin-template](https://github.com/PanJiaChen/vueAdmin-template) 桌面端 [electron-vue-admin](https://github.com/PanJiaChen/electron-vue-admin)**
[donate](https://github.com/PanJiaChen/vue-element-admin#donate)
**本项目的定位是后台集成方案,不适合当基础模板来开发。**
- 模板建议使用: [vueAdmin-template](https://github.com/PanJiaChen/vueAdmin-template)  
- 桌面端: [electron-vue-admin](https://github.com/PanJiaChen/electron-vue-admin)
**注意该项目目前使用element-ui@1.4.1版本,所以最低兼容 Vue 2.3.0**
**注意该项目目前使用element-ui@1.4.2版本,所以最低兼容 Vue 2.3.0**
## 前言
> 这半年来一直在用vue写管理后台目前后台已经有百来个页面十几种权限但维护成本依然很低所以准备开源分享一下后台开发的经验和成果。目前的技术栈主要的采用vue+element+axios由webpack2打包。由于是个人项目所以数据请求都是用了mockjs模拟。注意在此项目基础上改造开发时请移除mock文件。
@@ -33,11 +37,13 @@
相应需求开了一个qq群 `591724180` 方便大家交流
**如有问题请先看上述文章和Wiki若不能满足欢迎 issue 和 pr ~**
或者可以加入该 **[圈子](https://jianshiapp.com/circles/1209)** 讨论问题
**如有问题请先看上述文章和Wiki若不能满足欢迎 issue 和 pr**
**该项目并不是一个脚手架,更倾向于是一个集成解决方案**
**该项目不支持低版本游览器有需求请自行添加polyfill [详情](https://github.com/PanJiaChen/vue-element-admin/wiki#babel-polyfill)**
**该项目不支持低版本游览器(如ie)有需求请自行添加polyfill [详情](https://github.com/PanJiaChen/vue-element-admin/wiki#babel-polyfill)**
## 功能
@@ -57,6 +63,7 @@
- 401404错误页面
- 错误日志
- 导出excel
- 前端可视化excel
- table example
- 动态table example
- 拖拽table example
@@ -129,6 +136,10 @@
## Changelog
Detailed changes for each release are documented in the [release notes](https://github.com/PanJiaChen/vue-element-admin/releases).
## Donate
If you find this project useful, you can buy me a cup of coffee
![donate](https://panjiachen.github.io/donate/donation.png)
## 状态管理
后台只有user和app配置相关状态使用vuex存在全局其它数据都由每个业务页面自己管理。

View File

@@ -68,7 +68,7 @@ module.exports = {
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
loader: 'url-loader',
exclude: [resolve('src/icons')],
query: {
options: {
limit: 10000,
name: utils.assetsPath('img/[name].[hash:7].[ext]')
}
@@ -76,7 +76,7 @@ module.exports = {
{
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
loader: 'url-loader',
query: {
options: {
limit: 10000,
name: utils.assetsPath('fonts/[name].[hash:7].[ext]')
}

View File

@@ -1,6 +1,6 @@
{
"name": "juicy",
"version": "2.0.0",
"version": "2.1.0",
"description": "A Vue.js admin",
"author": "Pan <panfree23@gmail.com>",
"license": "MIT",

View File

@@ -1,6 +1,6 @@
<template>
<transition :name="transitionName">
<div class="back-to-top" @click="backToTop" v-show="visible" :style="customStyle">
<div class="back-to-ceiling" @click="backToTop" v-show="visible" :style="customStyle">
<svg width="16" height="16" viewBox="0 0 17 17" xmlns="http://www.w3.org/2000/svg" class="Icon Icon--backToTopArrow" aria-hidden="true" style="height: 16px; width: 16px;">
<title>回到顶部</title>
<g>
@@ -82,14 +82,14 @@ export default {
</script>
<style scoped>
.back-to-top {
.back-to-ceiling {
position: fixed;
display: inline-block;
text-align: center;
cursor: pointer;
}
.back-to-top:hover {
.back-to-ceiling:hover {
background: #d5dbe7;
}
@@ -103,7 +103,7 @@ export default {
opacity: 0
}
.back-to-top .Icon {
.back-to-ceiling .Icon {
fill: #9aaabf;
background: none;
}

View File

@@ -0,0 +1,102 @@
<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"
:multiple="true"
:file-list="fileList"
:show-file-list="true"
list-type="picture-card"
:on-remove="handleRemove"
:on-success="handleSuccess"
: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,
listObj: {},
fileList: []
}
},
methods: {
checkAllSuccess() {
return Object.keys(this.listObj).every(item => this.listObj[item].hasSuccess)
},
handleSubmit() {
const arr = Object.keys(this.listObj).map(v => this.listObj[v])
if (!this.checkAllSuccess()) {
this.$message('请等待所有图片上传成功 或 出现了网络问题,请刷新页面重新上传!')
return
}
console.log(arr)
this.$emit('successCBK', arr)
this.listObj = {}
this.fileList = []
this.dialogVisible = false
},
handleSuccess(response, file) {
const uid = file.uid
const objKeyArr = Object.keys(this.listObj)
for (let i = 0, len = objKeyArr.length; i < len; i++) {
if (this.listObj[objKeyArr[i]].uid === uid) {
this.listObj[objKeyArr[i]].url = response.files.file
this.listObj[objKeyArr[i]].hasSuccess = true
return
}
}
},
handleRemove(file) {
const uid = file.uid
const objKeyArr = Object.keys(this.listObj)
for (let i = 0, len = objKeyArr.length; i < len; i++) {
if (this.listObj[objKeyArr[i]].uid === uid) {
delete this.listObj[objKeyArr[i]]
return
}
}
},
beforeUpload(file) {
const _self = this
const _URL = window.URL || window.webkitURL
const fileName = file.uid
this.listObj[fileName] = {}
return new Promise((resolve, reject) => {
const img = new Image()
img.src = _URL.createObjectURL(file)
img.onload = function() {
_self.listObj[fileName] = { hasSuccess: false, uid: file.uid, width: this.width, height: this.height }
}
resolve(true)
})
}
}
}
</script>
<style rel="stylesheet/scss" lang="scss" scoped>
.upload-container {
.editor-slide-upload {
margin-bottom: 20px;
}
}
</style>

View File

@@ -1,16 +1,21 @@
<template>
<div class='tinymce-container editor-container'>
<textarea class='tinymce-textarea' :id="id"></textarea>
<textarea class='tinymce-textarea' :id="tinymceId"></textarea>
<div class="editor-custom-btn-container">
<editorImage color="#20a0ff" class="editor-upload-btn" @successCBK="imageSuccessCBK"></editorImage>
</div>
</div>
</template>
<script>
import editorImage from './components/editorImage'
export default {
name: 'tinymce',
components: { editorImage },
props: {
id: {
type: String,
default: 'tinymceEditor'
type: String
},
value: {
type: String,
@@ -23,12 +28,6 @@ export default {
return ['removeformat undo redo | bullist numlist | outdent indent | forecolor | fullscreen code', 'bold italic blockquote | h2 p media link | alignleft aligncenter alignright']
}
},
data() {
return {
hasChange: false,
hasInit: false
}
},
menubar: {
default: ''
},
@@ -38,17 +37,24 @@ export default {
default: 360
}
},
data() {
return {
hasChange: false,
hasInit: false,
tinymceId: this.id || 'vue-tinymce-' + +new Date()
}
},
watch: {
value(val) {
if (!this.hasChange && this.hasInit) {
this.$nextTick(() => window.tinymce.get(this.id).setContent(val))
this.$nextTick(() => window.tinymce.get(this.tinymceId).setContent(val))
}
}
},
mounted() {
const _this = this
window.tinymce.init({
selector: `#${this.id}`,
selector: `#${this.tinymceId}`,
height: this.height,
body_class: 'panel-body ',
object_resizing: false,
@@ -143,8 +149,16 @@ export default {
}
})
},
methods: {
imageSuccessCBK(arr) {
const _this = this
arr.forEach(v => {
window.tinymce.get(_this.tinymceId).insertContent(`<img class="wscnph" src="${v.url}" >`)
})
}
},
destroyed() {
window.tinymce.get(this.id).destroy()
window.tinymce.get(this.tinymceId).destroy()
}
}
</script>
@@ -153,9 +167,17 @@ export default {
.tinymce-container {
position: relative
}
.tinymce-textarea {
visibility: hidden;
z-index: -1;
}
.editor-custom-btn-container {
position: absolute;
right: 15px;
/*z-index: 2005;*/
top: 18px;
}
.editor-upload-btn {
display: inline-block;
}
</style>

View File

@@ -0,0 +1,78 @@
<template>
<div>
<el-button :loading="loading" type="primary" @click="handleUpload">select excel file</el-button>
<input id="excel-upload-input" type="file" accept=".xlsx, .xls" class="c-hide" @change="handkeFileChange">
</div>
</template>
<script>
import XLSX from 'xlsx'
export default {
data() {
return {
loading: false,
excelData: {
header: null,
results: null
}
}
},
methods: {
generateDate({ header, results }) {
this.excelData.header = header
this.excelData.results = results
this.loading = false
this.$emit('on-selected-file', this.excelData)
},
handleUpload() {
document.getElementById('excel-upload-input').click()
},
handkeFileChange(e) {
this.loading = true
const files = e.target.files
const itemFile = files[0] // only use files[0]
const reader = new FileReader()
reader.onload = e => {
const data = e.target.result
const fixedData = this.fixdata(data)
const workbook = XLSX.read(btoa(fixedData), { type: 'base64' })
const firstSheetName = workbook.SheetNames[0]
const worksheet = workbook.Sheets[firstSheetName]
const header = this.get_header_row(worksheet)
const results = XLSX.utils.sheet_to_json(worksheet)
this.generateDate({ header, results })
}
reader.readAsArrayBuffer(itemFile)
},
fixdata(data) {
let o = ''
let l = 0
const w = 10240
for (; l < data.byteLength / w; ++l) o += String.fromCharCode.apply(null, new Uint8Array(data.slice(l * w, l * w + w)))
o += String.fromCharCode.apply(null, new Uint8Array(data.slice(l * w)))
return o
},
get_header_row(sheet) {
const headers = []
const range = XLSX.utils.decode_range(sheet['!ref'])
let C
const R = range.s.r /* start in the first row */
for (C = range.s.c; C <= range.e.c; ++C) { /* walk every column in the range */
var cell = sheet[XLSX.utils.encode_cell({ c: C, r: R })] /* find the cell in the first row */
var hdr = 'UNKNOWN ' + C // <-- replace with your desired default
if (cell && cell.t) hdr = XLSX.utils.format_cell(cell)
headers.push(hdr)
}
return headers
}
}
}
</script>
<style scoped>
#excel-upload-input{
display: none;
z-index: -9999;
}
</style>

View File

@@ -4,11 +4,11 @@ import 'element-ui/lib/theme-default/index.css'
import App from './App'
import router from './router'
import store from './store'
import * as filters from '@/filters' // 全局filter
import '@/icons' // icon
import '@/errorLog'// error log
import '@/permission' // 权限
import '@/mock' // 该项目所有请求使用mockjs模拟
import * as filters from './filters' // 全局filter
import './icons' // icon
import './errorLog'// error log
import './permission' // 权限
import './mock' // 该项目所有请求使用mockjs模拟
Vue.use(ElementUI)

View File

@@ -150,7 +150,8 @@ export const asyncRouterMap = [
icon: 'EXCEL',
children: [
{ path: 'download', component: _import('excel/index'), name: '导出excel' },
{ path: 'download2', component: _import('excel/selectExcel'), name: '导出已选择项' }
{ path: 'download2', component: _import('excel/selectExcel'), name: '导出已选择项' },
{ path: 'upload', component: _import('excel/uploadExcel'), name: 'upload excel' }
]
},
{

View File

@@ -32,14 +32,17 @@ const app = {
}
},
actions: {
ToggleSideBar: ({ commit }) => {
ToggleSideBar({ commit }) {
commit('TOGGLE_SIDEBAR')
},
addVisitedViews: ({ commit }, view) => {
addVisitedViews({ commit }, view) {
commit('ADD_VISITED_VIEWS', view)
},
delVisitedViews: ({ commit }, view) => {
commit('DEL_VISITED_VIEWS', view)
delVisitedViews({ commit, state }, view) {
return new Promise((resolve) => {
commit('DEL_VISITED_VIEWS', view)
resolve([...state.visitedViews])
})
}
}
}

View File

@@ -2,7 +2,7 @@
<div class="components-container">
<code>公司做的后台主要是一个cms系统公司也是以自媒体为核心的所以富文本是后台很核心的功能在选择富文本的过程中也走了不少的弯路市面上常见的富文本都基本用过了最终选择了Tinymce<a target='_blank' href='https://segmentfault.com/a/1190000009762198#articleHeader13'> 相关文章 </a> <a target='_blank' href='https://www.tinymce.com/'> 官网 </a></code>
<div>
<tinymce :height=200 ref="editor" v-model="content"></tinymce>
<tinymce :height='200' v-model="content"></tinymce>
</div>
<div class='editor-content' v-html='content'></div>
</div>

View File

@@ -4,7 +4,7 @@
<h3>请点击右上角bug小图表</h3>
<code>
现在的管理后台基本都是spa的形式了它增强了用户体验但同时也会增加页面出问题的可能性可能一个小小的疏忽就导致整个页面的死锁好在Vue官网提供了一个方法来捕获处理异常
现在的管理后台基本都是spa的形式了它增强了用户体验但同时也会增加页面出问题的可能性可能一个小小的疏忽就导致整个页面的死锁好在Vue官网提供了一个方法来捕获处理异常
</code>
<a href="#"><img src='http://panjiachen.github.io/images/errHandler.png'></a>
</div>

View File

@@ -236,6 +236,7 @@ export default {
})
},
handleFilter() {
this.listQuery.page = 1
this.getList()
},
handleSizeChange(val) {

View File

@@ -0,0 +1,29 @@
<template>
<div class="app-container">
<upload-excel @on-selected-file='selected'></upload-excel>
<el-table :data="tableData" border highlight-current-row style="width: 100%;margin-top:20px;">
<el-table-column v-for='item of tableHeader' :prop="item" :label="item" :key='item'>
</el-table-column>
</el-table>
</div>
</template>
<script>
import uploadExcel from 'components/UploadExcel/index.vue'
export default {
components: { uploadExcel },
data() {
return {
tableData: [],
tableHeader: []
}
},
methods: {
selected(data) {
this.tableData = data.results
this.tableHeader = data.header
}
}
}
</script>

View File

@@ -17,7 +17,16 @@ export default {
},
methods: {
closeViewTabs(view, $event) {
this.$store.dispatch('delVisitedViews', view)
this.$store.dispatch('delVisitedViews', view).then((views) => {
if (this.isActive(view.path)) {
const latestView = views.slice(-1)[0]
if (latestView) {
this.$router.push(latestView.path)
} else {
this.$router.push('/')
}
}
})
$event.preventDefault()
},
generateRoute() {

View File

@@ -3,7 +3,7 @@
<el-card class="box-card">
<div slot="header">
<span style="line-height: 36px;">偏好设置</span>
<a class='link-type link-title' target="_blank" href='https://segmentfault.com/a/1190000009762198#articleHeader2'>态换肤的教程</a>
<a class='link-type link-title' target="_blank" href='https://segmentfault.com/a/1190000009762198#articleHeader2'>态换肤的教程</a>
</div>
<div class="box-item">
<span class="field-label">换肤:</span>