Compare commits
25 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
db61251d89 | ||
|
582c6f4ae4 | ||
|
9c2a7e9485 | ||
|
fde12e8ef9 | ||
|
8945476c22 | ||
|
14ff09a414 | ||
|
a14547aaf9 | ||
|
b1311322ad | ||
|
ffec6b6df7 | ||
|
f0afbf7ea5 | ||
|
a4f8e0b805 | ||
|
2d72df3605 | ||
|
e9f92a7d3d | ||
|
ee362f22b2 | ||
|
11426c8494 | ||
|
fa5f5e9d26 | ||
|
cda292dec1 | ||
|
877b73cd67 | ||
|
53d7243316 | ||
|
5379510013 | ||
|
f712d4682e | ||
|
cb0e889829 | ||
|
6a8a02f839 | ||
|
d56cd59474 | ||
|
a10cfcc837 |
37
README-en.md
@@ -1,29 +1,19 @@
|
||||
[](https://github.com/vuejs/vue)
|
||||
[](https://github.com/ElemeFE/element)
|
||||
[](https://github.com/PanJiaChen/vue-element-admin/blob/master/LICENSE)
|
||||
[]()
|
||||
|
||||
## 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
|
||||

|
||||
|
||||
## 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
|
||||
|
21
README.md
@@ -1,7 +1,7 @@
|
||||
# vue-element-admin #
|
||||
|
||||
[](https://github.com/vuejs/vue)
|
||||
[](https://github.com/ElemeFE/element)
|
||||
[](https://github.com/ElemeFE/element)
|
||||
[](https://github.com/PanJiaChen/vue-element-admin/blob/master/LICENSE)
|
||||
[]()
|
||||
|
||||
@@ -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 @@
|
||||
- 401,404错误页面
|
||||
- 错误日志
|
||||
- 导出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
|
||||

|
||||
|
||||
## 状态管理
|
||||
后台只有user和app配置相关状态使用vuex存在全局,其它数据都由每个业务页面自己管理。
|
||||
|
||||
|
@@ -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]')
|
||||
}
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "juicy",
|
||||
"version": "2.0.0",
|
||||
"version": "2.1.1",
|
||||
"description": "A Vue.js admin",
|
||||
"author": "Pan <panfree23@gmail.com>",
|
||||
"license": "MIT",
|
||||
|
@@ -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;
|
||||
}
|
||||
|
@@ -1,297 +1,272 @@
|
||||
<template>
|
||||
<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"
|
||||
:readonly="readonly" :disabled="disabled" :autocomplete="autocomplete" :required="required" @focus="handleFocus(true)"
|
||||
@blur="handleFocus(false)" @input="handleModelInput">
|
||||
<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="handleFocus(true)"
|
||||
@blur="handleFocus(false)" @input="handleModelInput">
|
||||
<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" :max="max" :min="min" :minlength="minlength" :maxlength="maxlength"
|
||||
:required="required" @focus="handleFocus(true)" @blur="handleFocus(false)" @input="handleModelInput">
|
||||
<input v-if="type === 'password'" type="password" 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="handleFocus(true)" @blur="handleFocus(false)" @input="handleModelInput">
|
||||
<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" :required="required" @focus="handleFocus(true)"
|
||||
@blur="handleFocus(false)" @input="handleModelInput">
|
||||
<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" :minlength="minlength" :maxlength="maxlength"
|
||||
:required="required" @focus="handleFocus(true)" @blur="handleFocus(false)" @input="handleModelInput">
|
||||
|
||||
<span class="material-input-bar"></span>
|
||||
|
||||
<label class="material-label">
|
||||
<slot></slot>
|
||||
</label>
|
||||
<div v-if="errorMessages" class="material-errors">
|
||||
<div v-for="error in computedErrors" class="material-error" :key='error'>
|
||||
{{ error }}
|
||||
</div>
|
||||
<div :class="{iconClass:icon}">
|
||||
<i class="el-input__icon material-input__icon" :class="['el-icon-' + icon]" v-if="icon"></i>
|
||||
<input v-if="type === 'email'" type="email" class="material-input" :name="name" :placeholder="fillPlaceHolder" v-model="currentValue"
|
||||
:readonly="readonly" :disabled="disabled" :autoComplete="autoComplete" :required="required" @focus="handleMdFocus"
|
||||
@blur="handleMdBlur" @input="handleModelInput">
|
||||
<input v-if="type === 'url'" type="url" class="material-input" :name="name" :placeholder="fillPlaceHolder" v-model="currentValue"
|
||||
:readonly="readonly" :disabled="disabled" :autoComplete="autoComplete" :required="required" @focus="handleMdFocus"
|
||||
@blur="handleMdBlur" @input="handleModelInput">
|
||||
<input v-if="type === 'number'" type="number" class="material-input" :name="name" :placeholder="fillPlaceHolder" v-model="currentValue"
|
||||
:step="step" :readonly="readonly" :disabled="disabled" :autoComplete="autoComplete" :max="max" :min="min" :minlength="minlength"
|
||||
:maxlength="maxlength" :required="required" @focus="handleMdFocus" @blur="handleMdBlur" @input="handleModelInput">
|
||||
<input v-if="type === 'password'" type="password" class="material-input" :name="name" :placeholder="fillPlaceHolder" v-model="currentValue"
|
||||
:readonly="readonly" :disabled="disabled" :autoComplete="autoComplete" :max="max" :min="min" :required="required" @focus="handleMdFocus"
|
||||
@blur="handleMdBlur" @input="handleModelInput">
|
||||
<input v-if="type === 'tel'" type="tel" class="material-input" :name="name" :placeholder="fillPlaceHolder" v-model="currentValue"
|
||||
:readonly="readonly" :disabled="disabled" :autoComplete="autoComplete" :required="required" @focus="handleMdFocus"
|
||||
@blur="handleMdBlur" @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>
|
||||
<label class="material-label">
|
||||
<slot></slot>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<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 {
|
||||
name: 'material-input',
|
||||
computed: {
|
||||
computedErrors() {
|
||||
return typeof this.errorMessages === 'string'
|
||||
? [this.errorMessages] : this.errorMessages
|
||||
name: 'md-input',
|
||||
props: {
|
||||
icon: String,
|
||||
name: String,
|
||||
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() {
|
||||
return {
|
||||
'material--active': this.focus,
|
||||
'material--disabled': this.disabled,
|
||||
'material--has-errors': Boolean(!this.valid || (this.errorMessages && this.errorMessages.length)),
|
||||
'material--raised': Boolean(this.focus || this.valueCopy || // has value
|
||||
(this.placeholder && !this.valueCopy)) // has placeholder
|
||||
'material--raised': Boolean(this.focus || this.currentValue) // has value
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
valueCopy: null,
|
||||
currentValue: this.value,
|
||||
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: {
|
||||
handleModelInput(event) {
|
||||
this.$emit('input', event.target.value, event)
|
||||
this.handleValidation()
|
||||
const value = event.target.value
|
||||
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) {
|
||||
this.focus = focused
|
||||
handleMdFocus(event) {
|
||||
this.focus = true
|
||||
this.$emit('focus', event)
|
||||
if (this.placeholder && this.placeholder !== '') {
|
||||
this.fillPlaceHolder = this.placeholder
|
||||
}
|
||||
},
|
||||
handleValidation() {
|
||||
this.valid = this.$el ? this.$el.querySelector('.material-input').validity.valid : this.valid
|
||||
},
|
||||
copyValue(value) {
|
||||
this.valueCopy = value
|
||||
this.handleValidation()
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value(newValue) {
|
||||
this.copyValue(newValue)
|
||||
}
|
||||
},
|
||||
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
|
||||
handleMdBlur(event) {
|
||||
this.focus = false
|
||||
this.$emit('blur', event)
|
||||
this.fillPlaceHolder = null
|
||||
if (this.$parent.$options.componentName === 'ElFormItem') {
|
||||
if (this.validateEvent) {
|
||||
this.$parent.$emit('el.form.blur', [this.currentValue])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style rel="stylesheet/scss" lang="scss" scoped>
|
||||
// Fonts:
|
||||
$font-size-base: 16px;
|
||||
$font-size-small: 18px;
|
||||
$font-size-smallest: 12px;
|
||||
$font-weight-normal: normal;
|
||||
// Utils
|
||||
$spacer: 12px;
|
||||
$transition: 0.2s ease all;
|
||||
// Base clases:
|
||||
%base-bar-pseudo {
|
||||
content: '';
|
||||
height: 1px;
|
||||
width: 0;
|
||||
bottom: 0;
|
||||
// Fonts:
|
||||
$font-size-base: 16px;
|
||||
$font-size-small: 18px;
|
||||
$font-size-smallest: 12px;
|
||||
$font-weight-normal: normal;
|
||||
$font-weight-bold: bold;
|
||||
$apixel: 1px;
|
||||
// Utils
|
||||
$spacer: 12px;
|
||||
$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-bar-pseudo {
|
||||
content: '';
|
||||
height: 1px;
|
||||
width: 0;
|
||||
bottom: 0;
|
||||
position: absolute;
|
||||
transition: $transition;
|
||||
}
|
||||
|
||||
// Mixins:
|
||||
@mixin slided-top() {
|
||||
top: - ($font-size-base + $spacer);
|
||||
left: 0;
|
||||
font-size: $font-size-base;
|
||||
font-weight: $font-weight-bold;
|
||||
}
|
||||
|
||||
// Component:
|
||||
.material-input__component {
|
||||
margin-top: 36px;
|
||||
position: relative;
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.iconClass {
|
||||
.material-input__icon {
|
||||
position: absolute;
|
||||
transition: $transition;
|
||||
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 {
|
||||
font-size: $font-size-base;
|
||||
padding: $spacer $spacer $spacer - $apixel * 10 $spacer / 2;
|
||||
display: block;
|
||||
width: 100%;
|
||||
border: none;
|
||||
line-height: 1;
|
||||
border-radius: 0;
|
||||
&:focus {
|
||||
outline: none;
|
||||
border: none;
|
||||
border-bottom: 1px solid transparent; // fixes the height issue
|
||||
}
|
||||
}
|
||||
.material-label {
|
||||
font-weight: $font-weight-normal;
|
||||
position: absolute;
|
||||
pointer-events: none;
|
||||
left: $index;
|
||||
top: 0;
|
||||
transition: $transition;
|
||||
font-size: $font-size-small;
|
||||
}
|
||||
.material-input-bar {
|
||||
position: relative;
|
||||
display: block;
|
||||
width: 100%;
|
||||
&:before {
|
||||
@extend %base-bar-pseudo;
|
||||
left: 50%;
|
||||
}
|
||||
&:after {
|
||||
@extend %base-bar-pseudo;
|
||||
right: 50%;
|
||||
}
|
||||
}
|
||||
// Disabled state:
|
||||
&.material--disabled {
|
||||
.material-input {
|
||||
border-bottom-style: dashed;
|
||||
}
|
||||
}
|
||||
// Raised state:
|
||||
&.material--raised {
|
||||
.material-label {
|
||||
@include slided-top();
|
||||
}
|
||||
}
|
||||
// Active state:
|
||||
&.material--active {
|
||||
.material-input-bar {
|
||||
&:before,
|
||||
&:after {
|
||||
width: 50%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Mixins:
|
||||
@mixin slided-top() {
|
||||
top: -2 * $spacer;
|
||||
font-size: $font-size-small;
|
||||
.material-input__component {
|
||||
background: $color-white;
|
||||
.material-input {
|
||||
background: none;
|
||||
color: $color-black;
|
||||
text-indent: $index;
|
||||
border-bottom: 1px solid $color-grey-light;
|
||||
}
|
||||
|
||||
// Component:
|
||||
.material-input__component {
|
||||
/*margin-top: 30px;*/
|
||||
position: relative;
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.material-input {
|
||||
font-size: $font-size-base;
|
||||
padding: $spacer $spacer $spacer $spacer / 2;
|
||||
display: block;
|
||||
width: 100%;
|
||||
border: none;
|
||||
border-radius: 0;
|
||||
&:focus {
|
||||
outline: none;
|
||||
border: none;
|
||||
border-bottom: 1px solid transparent; // fixes the height issue
|
||||
}
|
||||
}
|
||||
.material-label {
|
||||
font-size: $font-size-base;
|
||||
font-weight: $font-weight-normal;
|
||||
position: absolute;
|
||||
pointer-events: none;
|
||||
left: 0;
|
||||
top: $spacer;
|
||||
transition: $transition;
|
||||
}
|
||||
.material-input-bar {
|
||||
position: relative;
|
||||
display: block;
|
||||
width: 100%;
|
||||
&:before {
|
||||
@extend %base-bar-pseudo;
|
||||
left: 50%;
|
||||
}
|
||||
&:after {
|
||||
@extend %base-bar-pseudo;
|
||||
right: 50%;
|
||||
}
|
||||
}
|
||||
// Disabled state:
|
||||
&.material--disabled {
|
||||
.material-input {
|
||||
border-bottom-style: dashed;
|
||||
}
|
||||
}
|
||||
// Raised state:
|
||||
&.material--raised {
|
||||
.material-label {
|
||||
@include slided-top();
|
||||
}
|
||||
}
|
||||
// Active state:
|
||||
&.material--active {
|
||||
.material-input-bar {
|
||||
&:before,
|
||||
&:after {
|
||||
width: 50%;
|
||||
}
|
||||
}
|
||||
}
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
.material-label {
|
||||
color: $color-grey;
|
||||
}
|
||||
|
||||
// Theme:
|
||||
$color-white: white;
|
||||
$color-grey: #9E9E9E;
|
||||
$color-grey-light: #E0E0E0;
|
||||
$color-blue: #2196F3;
|
||||
$color-red: #F44336;
|
||||
$color-black: black;
|
||||
.material-input__component {
|
||||
background: $color-white;
|
||||
.material-input {
|
||||
background: none;
|
||||
color: $color-black;
|
||||
text-indent: 30px;
|
||||
border-bottom: 1px solid $color-grey-light;
|
||||
}
|
||||
.material-label {
|
||||
color: $color-grey;
|
||||
}
|
||||
.material-input-bar {
|
||||
&:before,
|
||||
&:after {
|
||||
background: $color-blue;
|
||||
}
|
||||
}
|
||||
// Active state:
|
||||
&.material--active {
|
||||
.material-label {
|
||||
color: $color-blue;
|
||||
}
|
||||
}
|
||||
// Errors:
|
||||
&.material--has-errors {
|
||||
&.material--active .material-label {
|
||||
color: $color-red;
|
||||
}
|
||||
.material-input-bar {
|
||||
&:before,
|
||||
&:after {
|
||||
background: $color-red;
|
||||
}
|
||||
}
|
||||
.material-errors {
|
||||
color: $color-red;
|
||||
}
|
||||
}
|
||||
.material-input-bar {
|
||||
&:before,
|
||||
&:after {
|
||||
background: $color-blue;
|
||||
}
|
||||
}
|
||||
// Active state:
|
||||
&.material--active {
|
||||
.material-label {
|
||||
color: $color-blue;
|
||||
}
|
||||
}
|
||||
// Errors:
|
||||
&.material--has-errors {
|
||||
&.material--active .material-label {
|
||||
color: $color-red;
|
||||
}
|
||||
.material-input-bar {
|
||||
&:before,
|
||||
&:after {
|
||||
background: transparent;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
102
src/components/Tinymce/components/editorImage.vue
Normal 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>
|
@@ -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>
|
||||
|
78
src/components/UploadExcel/index.vue
Normal 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>
|
Before Width: | Height: | Size: 552 B After Width: | Height: | Size: 552 B |
1
src/icons/svg/component.svg
Normal 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 |
Before Width: | Height: | Size: 983 B After Width: | Height: | Size: 983 B |
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 1.0 KiB |
Before Width: | Height: | Size: 866 B After Width: | Height: | Size: 866 B |
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.8 KiB |
1
src/icons/svg/form.svg
Normal 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 |
@@ -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 |
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 1.0 KiB |
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.7 KiB |
Before Width: | Height: | Size: 8.0 KiB After Width: | Height: | Size: 8.0 KiB |
@@ -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 |
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
@@ -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 |
Before Width: | Height: | Size: 678 B After Width: | Height: | Size: 678 B |
Before Width: | Height: | Size: 678 B After Width: | Height: | Size: 678 B |
Before Width: | Height: | Size: 673 B After Width: | Height: | Size: 673 B |
Before Width: | Height: | Size: 777 B After Width: | Height: | Size: 777 B |
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 2.2 KiB |
@@ -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 |
10
src/main.js
@@ -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)
|
||||
|
||||
|
@@ -1 +1 @@
|
||||
module.exports = file => require('@/views/' + file + '.vue').default
|
||||
module.exports = file => require('@/views/' + file + '.vue').default // vue-loader at least v13.0.0+
|
||||
|
@@ -32,7 +32,7 @@ export const constantRouterMap = [
|
||||
path: '/introduction',
|
||||
component: Layout,
|
||||
redirect: '/introduction/index',
|
||||
icon: 'xinrenzhinan',
|
||||
icon: 'people',
|
||||
noDropdown: true,
|
||||
children: [{ path: 'index', component: _import('introduction/index'), name: '简述' }]
|
||||
}
|
||||
@@ -50,7 +50,7 @@ export const asyncRouterMap = [
|
||||
component: Layout,
|
||||
redirect: '/permission/index',
|
||||
name: '权限测试',
|
||||
icon: 'quanxian',
|
||||
icon: 'lock',
|
||||
meta: { role: ['admin'] },
|
||||
noDropdown: true,
|
||||
children: [{ path: 'index', component: _import('permission/index'), name: '权限测试页', meta: { role: ['admin'] }}]
|
||||
@@ -58,7 +58,7 @@ export const asyncRouterMap = [
|
||||
{
|
||||
path: '/icon',
|
||||
component: Layout,
|
||||
icon: 'icons',
|
||||
icon: 'icon',
|
||||
noDropdown: true,
|
||||
children: [{ path: 'index', component: _import('svg-icons/index'), name: 'icons' }]
|
||||
},
|
||||
@@ -67,7 +67,7 @@ export const asyncRouterMap = [
|
||||
component: Layout,
|
||||
redirect: '/components/index',
|
||||
name: '组件',
|
||||
icon: 'zujian',
|
||||
icon: 'component',
|
||||
children: [
|
||||
{ path: 'index', component: _import('components/index'), name: '介绍 ' },
|
||||
{ path: 'tinymce', component: _import('components/tinymce'), name: '富文本编辑器' },
|
||||
@@ -88,7 +88,7 @@ export const asyncRouterMap = [
|
||||
component: Layout,
|
||||
redirect: '/charts/index',
|
||||
name: '图表',
|
||||
icon: 'tubiao',
|
||||
icon: 'chart',
|
||||
children: [
|
||||
{ path: 'index', component: _import('charts/index'), name: '介绍' },
|
||||
{ path: 'keyboard', component: _import('charts/keyboard'), name: '键盘图表' },
|
||||
@@ -102,7 +102,7 @@ export const asyncRouterMap = [
|
||||
component: Layout,
|
||||
redirect: 'noredirect',
|
||||
name: '综合实例',
|
||||
icon: 'zonghe',
|
||||
icon: 'example',
|
||||
children: [
|
||||
{
|
||||
path: '/example/table',
|
||||
@@ -117,8 +117,8 @@ export const asyncRouterMap = [
|
||||
{ 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/create', icon: 'from', component: _import('example/form'), name: '创建Form' },
|
||||
{ path: 'form/edit', icon: 'form', component: _import('example/form'), name: '编辑Form', meta: { isEdit: true }},
|
||||
{ path: 'form/create', icon: 'form', component: _import('example/form'), name: '创建Form' },
|
||||
{ path: 'tab/index', icon: 'tab', component: _import('example/tab/index'), name: 'Tab' }
|
||||
]
|
||||
},
|
||||
@@ -147,10 +147,11 @@ export const asyncRouterMap = [
|
||||
component: Layout,
|
||||
redirect: '/excel/download',
|
||||
name: 'excel',
|
||||
icon: 'EXCEL',
|
||||
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' }
|
||||
]
|
||||
},
|
||||
{
|
||||
|
@@ -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])
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,9 +1,11 @@
|
||||
<template>
|
||||
<div class="components-container">
|
||||
<div class='component-item'>
|
||||
<md-input name="name" v-model="title" required :maxlength="100">
|
||||
标题
|
||||
</md-input>
|
||||
<el-form :model="demo" :rules="demoRules">
|
||||
<el-form-item prop="title">
|
||||
<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>
|
||||
</div>
|
||||
|
||||
@@ -22,18 +24,33 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import MdInput from '@/components/MDinput'
|
||||
import PanThumb from '@/components/PanThumb'
|
||||
import MdInput from '@/components/MDinput'
|
||||
import waves from '@/directive/waves.js' // 水波纹指令
|
||||
|
||||
export default {
|
||||
components: { MdInput, PanThumb },
|
||||
components: {
|
||||
PanThumb,
|
||||
MdInput
|
||||
},
|
||||
directives: {
|
||||
waves
|
||||
},
|
||||
data() {
|
||||
const validate = (rule, value, callback) => {
|
||||
if (value.length !== 6) {
|
||||
callback(new Error('请输入六个字符'))
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
}
|
||||
return {
|
||||
title: ''
|
||||
demo: {
|
||||
title: ''
|
||||
},
|
||||
demoRules: {
|
||||
title: [{ required: true, trigger: 'change', validator: validate }]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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>
|
||||
|
@@ -34,12 +34,12 @@
|
||||
<div class="info-item">
|
||||
<count-to class="info-item-num" :startVal='0' :endVal='statisticsData.article_count' :duration='3400'></count-to>
|
||||
<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 class="info-item">
|
||||
<count-to class="info-item-num" :startVal='0' :endVal='statisticsData.pageviews_count' :duration='3600'></count-to>
|
||||
<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>
|
||||
</el-card>
|
||||
</el-col>
|
||||
|
@@ -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>
|
||||
|
@@ -29,7 +29,7 @@
|
||||
|
||||
<el-table-column width="80px" label="重要性">
|
||||
<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>
|
||||
</el-table-column>
|
||||
|
||||
|
@@ -29,7 +29,7 @@
|
||||
|
||||
<el-table-column width="80px" label="重要性">
|
||||
<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>
|
||||
</el-table-column>
|
||||
|
||||
@@ -47,7 +47,7 @@
|
||||
|
||||
<el-table-column align="center" label="拖拽" width="95">
|
||||
<template scope="scope">
|
||||
<icon-svg class='drag-handler' icon-class="tuozhuai"></icon-svg>
|
||||
<icon-svg class='drag-handler' icon-class="drag"></icon-svg>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
|
@@ -23,7 +23,7 @@
|
||||
|
||||
<el-table-column width="100px" label="重要性">
|
||||
<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>
|
||||
</el-table-column>
|
||||
|
||||
|
@@ -60,7 +60,7 @@
|
||||
|
||||
<el-table-column width="80px" label="重要性">
|
||||
<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>
|
||||
</el-table-column>
|
||||
|
||||
@@ -236,6 +236,7 @@ export default {
|
||||
})
|
||||
},
|
||||
handleFilter() {
|
||||
this.listQuery.page = 1
|
||||
this.getList()
|
||||
},
|
||||
handleSizeChange(val) {
|
||||
|
29
src/views/excel/uploadExcel.vue
Normal 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>
|
@@ -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() {
|
||||
|
@@ -5,18 +5,18 @@
|
||||
|
||||
<el-form-item prop="username">
|
||||
<span class="svg-container svg-container_login">
|
||||
<icon-svg icon-class="yonghuming" />
|
||||
<icon-svg icon-class="user" />
|
||||
</span>
|
||||
<el-input name="username" type="text" v-model="loginForm.username" autoComplete="on" placeholder="邮箱" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item prop="password">
|
||||
<span class="svg-container">
|
||||
<icon-svg icon-class="mima" />
|
||||
<icon-svg icon-class="password" />
|
||||
</span>
|
||||
<el-input name="password" :type="pwdType" @keyup.enter.native="handleLogin" v-model="loginForm.password" autoComplete="on"
|
||||
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-button type="primary" style="width:100%;margin-bottom:30px;" :loading="loading" @click.native.prevent="handleLogin">登录</el-button>
|
||||
|
@@ -1,10 +1,10 @@
|
||||
<template>
|
||||
<div class="social-signup-container">
|
||||
<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 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>
|
||||
</template>
|
||||
|
@@ -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>
|
||||
|