Compare commits

..

25 Commits

Author SHA1 Message Date
花裤衩 33a93a12b4 feat: add new sponsored 2020-12-04 13:50:23 +08:00
花裤衩 1bc2e5c198 docs: add coupon 2020-09-08 10:01:50 +08:00
花裤衩 9cc3fa732a perf: refine documentation button css 2020-09-07 21:45:28 +08:00
花裤衩 44e23d00ca docs: add vue-java-admin-dashboard-spring 2020-09-07 21:37:44 +08:00
花裤衩 8fd08c9167 docs: remove gitads 2020-08-30 22:57:38 +08:00
花裤衩 5e7113935c docs: add GitAds 2020-07-27 10:08:38 +08:00
Kerollos Magdy 46e11caafc
typo: replace Css with CSS (#3323) 2020-07-20 18:13:01 +08:00
dyz ea60478304
perf[permission.js]: remove useless code (#3301) 2020-07-05 13:12:46 +08:00
crayymumu fe22b3cd94
perf[Tinymce]: remove useless code (#3295) 2020-07-01 13:50:18 +08:00
花裤衩 7c06214190
perf[chore]: remove preserveWhitespace config (#3280) 2020-06-24 10:17:57 +08:00
花裤衩 73a9ab0379
fix[chore]: do not preload runtime.js (#3279) 2020-06-23 21:12:44 +08:00
花裤衩 585391228e [release] 4.4.0 2020-06-21 21:39:37 +08:00
Dihak ae2aa61880
bump: update to vue-cli@4 (#3028) 2020-06-21 21:25:55 +08:00
花裤衩 f6d8204b0b [release] 4.3.1 2020-06-18 21:01:02 +08:00
花裤衩 435db380d3 fix some typos 2020-06-15 17:18:55 +08:00
qige2016 6d88db5c73
fix[plop]: set trim => trim() (#3254)
notEmpty
2020-06-15 12:00:17 +08:00
花裤衩 1c943509f9 chore: update element-ui to 2.13.2 2020-06-12 10:18:05 +08:00
花裤衩 4e7665c072
fix[v-permission]: support dynamic set roles (#3251) 2020-06-11 21:02:18 +08:00
花裤衩 a87218e266 perf: lint code 2020-06-11 20:51:10 +08:00
Silentdoer 7702b3d809
perf: remove unused showdown
Co-authored-by: 花裤衩 <panfree23@gmail.com>
2020-06-11 20:24:17 +08:00
MaYuanhai a50180f653
fix[utils]: param2Obj bug when url params includes ==(#3100) 2020-06-11 20:18:23 +08:00
Cat73 fc68f56d13
feat[Menu]: menu icon support el-icon(#3048) 2020-06-11 13:57:41 +08:00
花裤衩 d3bd933a8e
refactor: change mock files to commonjs (#3246) 2020-06-11 11:40:53 +08:00
花裤衩 0bf61aac53 chore: turn on the preload 2020-06-10 21:47:50 +08:00
花裤衩 2ef49493c3 docs: fix wrong link 2020-06-09 10:51:27 +08:00
35 changed files with 365 additions and 552 deletions

View File

@ -3,12 +3,3 @@ ENV = 'development'
# base api
VUE_APP_BASE_API = '/dev-api'
# vue-cli uses the VUE_CLI_BABEL_TRANSPILE_MODULES environment variable,
# to control whether the babel-plugin-dynamic-import-node plugin is enabled.
# It only does one thing by converting all import() to require().
# This configuration can significantly increase the speed of hot updates,
# when you have a large number of pages.
# Detail: https://github.com/vuejs/vue-cli/blob/dev/packages/@vue/babel-preset-app/index.js
VUE_CLI_BABEL_TRANSPILE_MODULES = true

View File

@ -72,7 +72,13 @@ Entendiendo y aprendiendo esto pudiera ayudarle con su proyecto.
Sea un patrocinante y coloque su logo en nuestro LEEME en GitHub con un enlace directo a su sitio web. [[Se un Patrocinante]](https://www.patreon.com/panjiachen)
<a href="https://flatlogic.com/admin-dashboards?from=vue-element-admin"><img width="150px" src="https://wpimg.wallstcn.com/9c0b719b-5551-4c1e-b776-63994632d94a.png" /></a><p>Plantilla de Dashboard de administración hecha con Vue, React y Angular.</p>
### Akveo
<a href="https://store.akveo.com/products/vue-java-admin-dashboard-spring?utm_campaign=akveo_store-Vue-Vue_demo%2Fgithub&utm_source=vue_admin&utm_medium=referral&utm_content=github_banner"><img width="500px" src="https://raw.githubusercontent.com/PanJiaChen/vue-element-admin-site/master/docs/.vuepress/public/images/vue-java-banner.png" /></a><p>Get Java backend for Vue admin with 20% discount for 39$ use coupon code SWB0RAZPZR1M
</p>
### Flatlogic
<a href="https://flatlogic.com/admin-dashboards?from=vue-element-admin"><img width="150px" src="https://wpimg.wallstcn.com/9c0b719b-5551-4c1e-b776-63994632d94a.png" /></a><p>Admin Dashboard Templates made with Vue, React and Angular.</p>
## Características

View File

@ -69,6 +69,12 @@
Become a sponsor and get your logo on our README on GitHub with a link to your site. [[Become a sponsor]](https://www.patreon.com/panjiachen)
### Akveo
<a href="https://store.akveo.com/products/vue-java-admin-dashboard-spring?utm_campaign=akveo_store-Vue-Vue_demo%2Fgithub&utm_source=vue_admin&utm_medium=referral&utm_content=github_banner"><img width="500px" src="https://raw.githubusercontent.com/PanJiaChen/vue-element-admin-site/master/docs/.vuepress/public/images/vue-java-banner.png" /></a><p>Get Java backend for Vue admin with 20% discount for 39$ use coupon code SWB0RAZPZR1M
</p>
### Flatlogic
<a href="https://flatlogic.com/admin-dashboards?from=vue-element-admin"><img width="150px" src="https://wpimg.wallstcn.com/9c0b719b-5551-4c1e-b776-63994632d94a.png" /></a><p>Admin Dashboard Templates made with Vue, React and Angular.</p>
## 機能一覧

View File

@ -28,6 +28,28 @@
English | [简体中文](./README.zh-CN.md) | [日本語](./README.ja.md) | [Spanish](./README.es.md)
<p align="center">
<b>SPONSORED BY</b>
</p>
<table align="center" cellspacing="0" cellpadding="0">
<tbody>
<tr>
<td align="center" valign="middle" width="250">
<a href="https://www.duohui.cn/?utm_source=vue-element-admin&utm_medium=web&utm_campaign=vue-element-admin_github" title="多会" target="_blank">
<img height="60px" src="https://qiniu.cdn.duohui.co/brand/duohui.png" title="多会 - 活动服务销售平台">
<p>活动服务销售平台</p>
</a>
</td>
<td align="center" valign="middle" width="250">
<a href="https://youke.co/?utm_source=vue-element-admin&utm_medium=web&utm_campaign=vue-element-admin_github" title="有客" target="_blank">
<img height="60px" src="https://qiniu.cdn.duohui.co/brand/youke.png" title="有客 - 客户消息直达工作群">
<p>客户消息直达工作群</p>
</a>
</td>
</tr>
</tbody>
</table>
## Introduction
[vue-element-admin](https://panjiachen.github.io/vue-element-admin) is a production-ready front-end solution for admin interfaces. It is based on [vue](https://github.com/vuejs/vue) and uses the UI Toolkit [element-ui](https://github.com/ElemeFE/element).
@ -72,6 +94,12 @@ Understanding and learning this knowledge in advance will greatly help the use o
Become a sponsor and get your logo on our README on GitHub with a link to your site. [[Become a sponsor]](https://www.patreon.com/panjiachen)
### Akveo
<a href="https://store.akveo.com/products/vue-java-admin-dashboard-spring?utm_campaign=akveo_store-Vue-Vue_demo%2Fgithub&utm_source=vue_admin&utm_medium=referral&utm_content=github_banner"><img width="500px" src="https://raw.githubusercontent.com/PanJiaChen/vue-element-admin-site/master/docs/.vuepress/public/images/vue-java-banner.png" /></a><p>Get Java backend for Vue admin with 20% discount for 39$ use coupon code SWB0RAZPZR1M
</p>
### Flatlogic
<a href="https://flatlogic.com/admin-dashboards?from=vue-element-admin"><img width="150px" src="https://wpimg.wallstcn.com/9c0b719b-5551-4c1e-b776-63994632d94a.png" /></a><p>Admin Dashboard Templates made with Vue, React and Angular.</p>
## Features

View File

@ -28,6 +28,28 @@
简体中文 | [English](./README.md) | [日本語](./README.ja.md) | [Spanish](./README.es.md)
<p align="center">
<b>SPONSORED BY</b>
</p>
<table align="center" cellspacing="0" cellpadding="0">
<tbody>
<tr>
<td align="center" valign="middle" width="250">
<a href="https://www.duohui.cn/?utm_source=vue-element-admin&utm_medium=web&utm_campaign=vue-element-admin_github" title="多会" target="_blank">
<img height="60px" src="https://qiniu.cdn.duohui.co/brand/duohui.png" title="多会 - 活动服务销售平台">
<p>活动服务销售平台</p>
</a>
</td>
<td align="center" valign="middle" width="250">
<a href="https://youke.co/?utm_source=vue-element-admin&utm_medium=web&utm_campaign=vue-element-admin_github" title="有客" target="_blank">
<img height="60px" src="https://qiniu.cdn.duohui.co/brand/youke.png" title="有客 - 客户消息直达工作群">
<p>客户消息直达工作群</p>
</a>
</td>
</tr>
</tbody>
</table>
## 简介
[vue-element-admin](https://panjiachen.github.io/vue-element-admin) 是一个后台前端解决方案,它基于 [vue](https://github.com/vuejs/vue) 和 [element-ui](https://github.com/ElemeFE/element)实现。它使用了最新的前端技术栈,内置了 i18n 国际化解决方案,动态路由,权限验证,提炼了典型的业务模型,提供了丰富的功能组件,它可以帮助你快速搭建企业级中后台产品原型。相信不管你的需求是什么,本项目都能帮助到你。
@ -87,8 +109,14 @@
Become a sponsor and get your logo on our README on GitHub with a link to your site. [[Become a sponsor]](https://www.patreon.com/panjiachen)
### Akveo
<a href="https://store.akveo.com/products/vue-java-admin-dashboard-spring?utm_campaign=akveo_store-Vue-Vue_demo%2Fgithub&utm_source=vue_admin&utm_medium=referral&utm_content=github_banner"><img width="500px" src="https://raw.githubusercontent.com/PanJiaChen/vue-element-admin-site/master/docs/.vuepress/public/images/vue-java-banner.png" /></a><p>Java 后端整合可以使用优惠码SWB0RAZPZR1M获得20%的价格优化</p>
### Flatlogic
<a href="https://flatlogic.com/admin-dashboards?from=vue-element-admin"><img width="150px" src="https://wpimg.wallstcn.com/9c0b719b-5551-4c1e-b776-63994632d94a.png" /></a><p>Admin Dashboard Templates made with Vue, React and Angular.</p>
## 功能
```

View File

@ -1,5 +1,14 @@
module.exports = {
presets: [
'@vue/app'
]
// https://github.com/vuejs/vue-cli/tree/master/packages/@vue/babel-preset-app
'@vue/cli-plugin-babel/preset'
],
'env': {
'development': {
// babel-plugin-dynamic-import-node plugin only does one thing by converting all import() to require().
// This plugin can significantly increase the speed of hot updates, when you have a large number of pages.
// https://panjiachen.github.io/vue-element-admin-site/guide/advanced/lazy-loading.html
'plugins': ['dynamic-import-node']
}
}
}

View File

@ -1,4 +1,4 @@
import Mock from 'mockjs'
const Mock = require('mockjs')
const List = []
const count = 100
@ -27,7 +27,7 @@ for (let i = 0; i < count; i++) {
}))
}
export default [
module.exports = [
{
url: '/vue-element-admin/article/list',
type: 'get',

View File

@ -1,10 +1,10 @@
import Mock from 'mockjs'
import { param2Obj } from '../src/utils'
const Mock = require('mockjs')
const { param2Obj } = require('./utils')
import user from './user'
import role from './role'
import article from './article'
import search from './remote-search'
const user = require('./user')
const role = require('./role')
const article = require('./article')
const search = require('./remote-search')
const mocks = [
...user,
@ -16,7 +16,7 @@ const mocks = [
// for front mock
// please use it cautiously, it will redefine XMLHttpRequest,
// which will cause many of your third-party libraries to be invalidated(like progress event).
export function mockXHR() {
function mockXHR() {
// mock patch
// https://github.com/nuysoft/Mock/issues/300
Mock.XHR.prototype.proxy_send = Mock.XHR.prototype.send
@ -54,4 +54,7 @@ export function mockXHR() {
}
}
export default mocks
module.exports = {
mocks,
mockXHR
}

View File

@ -8,7 +8,7 @@ const mockDir = path.join(process.cwd(), 'mock')
function registerRoutes(app) {
let mockLastIndex
const { default: mocks } = require('./index.js')
const { mocks } = require('./index.js')
const mocksForServer = mocks.map(route => {
return responseFake(route.url, route.type, route.response)
})
@ -44,9 +44,6 @@ const responseFake = (url, type, respond) => {
}
module.exports = app => {
// es6 polyfill
require('@babel/register')
// parse app.body
// https://expressjs.com/en/4x/api.html#req.body
app.use(bodyParser.json())

View File

@ -1,4 +1,4 @@
import Mock from 'mockjs'
const Mock = require('mockjs')
const NameList = []
const count = 100
@ -10,7 +10,7 @@ for (let i = 0; i < count; i++) {
}
NameList.push({ name: 'mock-Pan' })
export default [
module.exports = [
// username search
{
url: '/vue-element-admin/search/user',

View File

@ -1,6 +1,6 @@
import Mock from 'mockjs'
import { deepClone } from '../../src/utils/index.js'
import { asyncRoutes, constantRoutes } from './routes.js'
const Mock = require('mockjs')
const { deepClone } = require('../utils')
const { asyncRoutes, constantRoutes } = require('./routes.js')
const routes = deepClone([...constantRoutes, ...asyncRoutes])
@ -35,7 +35,7 @@ const roles = [
}
]
export default [
module.exports = [
// mock get all routes form server
{
url: '/vue-element-admin/routes',

View File

@ -1,6 +1,6 @@
// Just a mock data
export const constantRoutes = [
const constantRoutes = [
{
path: '/redirect',
component: 'layout/Layout',
@ -72,7 +72,7 @@ export const constantRoutes = [
}
]
export const asyncRoutes = [
const asyncRoutes = [
{
path: '/permission',
component: 'layout/Layout',
@ -523,3 +523,8 @@ export const asyncRoutes = [
{ path: '*', redirect: '/404', hidden: true }
]
module.exports = {
constantRoutes,
asyncRoutes
}

View File

@ -23,7 +23,7 @@ const users = {
}
}
export default [
module.exports = [
// user login
{
url: '/vue-element-admin/user/login',

48
mock/utils.js Normal file
View File

@ -0,0 +1,48 @@
/**
* @param {string} url
* @returns {Object}
*/
function param2Obj(url) {
const search = decodeURIComponent(url.split('?')[1]).replace(/\+/g, ' ')
if (!search) {
return {}
}
const obj = {}
const searchArr = search.split('&')
searchArr.forEach(v => {
const index = v.indexOf('=')
if (index !== -1) {
const name = v.substring(0, index)
const val = v.substring(index + 1, v.length)
obj[name] = val
}
})
return obj
}
/**
* This is just a simple version of deep copy
* Has a lot of edge cases bug
* If you want to use a perfect deep copy, use lodash's _.cloneDeep
* @param {Object} source
* @returns {Object}
*/
function deepClone(source) {
if (!source && typeof source !== 'object') {
throw new Error('error arguments', 'deepClone')
}
const targetObj = source.constructor === Array ? [] : {}
Object.keys(source).forEach(keys => {
if (source[keys] && typeof source[keys] === 'object') {
targetObj[keys] = deepClone(source[keys])
} else {
targetObj[keys] = source[keys]
}
})
return targetObj
}
module.exports = {
param2Obj,
deepClone
}

View File

@ -1,55 +1,28 @@
{
"name": "vue-element-admin",
"version": "4.3.0",
"version": "4.4.0",
"description": "A magical vue admin. An out-of-box UI solution for enterprise applications. Newest development stack of vue. Lots of awesome features",
"author": "Pan <panfree23@gmail.com>",
"license": "MIT",
"scripts": {
"dev": "vue-cli-service serve",
"lint": "eslint --ext .js,.vue src",
"build:prod": "vue-cli-service build",
"build:stage": "vue-cli-service build --mode staging",
"preview": "node build/index.js --preview",
"lint": "eslint --ext .js,.vue src",
"test:unit": "jest --clearCache && vue-cli-service test:unit",
"test:ci": "npm run lint && npm run test:unit",
"new": "plop",
"svgo": "svgo -f src/icons/svg --config=src/icons/svgo.yml",
"new": "plop"
},
"husky": {
"hooks": {
"pre-commit": "lint-staged"
}
},
"lint-staged": {
"src/**/*.{js,vue}": [
"eslint --fix",
"git add"
]
},
"keywords": [
"vue",
"admin",
"dashboard",
"element-ui",
"boilerplate",
"admin-template",
"management-system"
],
"repository": {
"type": "git",
"url": "git+https://github.com/PanJiaChen/vue-element-admin.git"
},
"bugs": {
"url": "https://github.com/PanJiaChen/vue-element-admin/issues"
"test:unit": "jest --clearCache && vue-cli-service test:unit",
"test:ci": "npm run lint && npm run test:unit"
},
"dependencies": {
"axios": "0.18.1",
"clipboard": "2.0.4",
"codemirror": "5.45.0",
"core-js": "3.6.5",
"driver.js": "0.9.5",
"dropzone": "5.5.1",
"echarts": "4.2.1",
"element-ui": "2.13.0",
"element-ui": "2.13.2",
"file-saver": "2.0.1",
"fuse.js": "3.4.4",
"js-cookie": "2.2.0",
@ -60,7 +33,6 @@
"path-to-regexp": "2.4.0",
"screenfull": "4.2.0",
"script-loader": "0.7.2",
"showdown": "1.9.0",
"sortablejs": "1.8.4",
"tui-editor": "1.3.3",
"vue": "2.6.10",
@ -72,43 +44,68 @@
"xlsx": "0.14.1"
},
"devDependencies": {
"@babel/core": "7.0.0",
"@babel/register": "7.0.0",
"@vue/cli-plugin-babel": "3.5.3",
"@vue/cli-plugin-eslint": "^3.9.1",
"@vue/cli-plugin-unit-jest": "3.5.3",
"@vue/cli-service": "3.5.3",
"@vue/cli-plugin-babel": "4.4.4",
"@vue/cli-plugin-eslint": "4.4.4",
"@vue/cli-plugin-unit-jest": "4.4.4",
"@vue/cli-service": "4.4.4",
"@vue/test-utils": "1.0.0-beta.29",
"autoprefixer": "^9.5.1",
"babel-core": "7.0.0-bridge.0",
"babel-eslint": "10.0.1",
"autoprefixer": "9.5.1",
"babel-eslint": "10.1.0",
"babel-jest": "23.6.0",
"babel-plugin-dynamic-import-node": "2.3.3",
"chalk": "2.4.2",
"chokidar": "2.1.5",
"connect": "3.6.6",
"eslint": "5.15.3",
"eslint-plugin-vue": "5.2.2",
"eslint": "6.7.2",
"eslint-plugin-vue": "6.2.2",
"html-webpack-plugin": "3.2.0",
"html2canvas": "^1.0.0-rc.5",
"husky": "1.3.1",
"lint-staged": "8.1.5",
"mockjs": "1.0.1-beta3",
"plop": "2.3.0",
"runjs": "^4.3.2",
"sass": "^1.26.2",
"sass-loader": "^7.1.0",
"runjs": "4.3.2",
"sass": "1.26.2",
"sass-loader": "8.0.2",
"script-ext-html-webpack-plugin": "2.1.3",
"serve-static": "^1.13.2",
"serve-static": "1.13.2",
"svg-sprite-loader": "4.1.3",
"svgo": "1.2.0",
"vue-template-compiler": "2.6.10"
},
"browserslist": [
"> 1%",
"last 2 versions"
],
"bugs": {
"url": "https://github.com/PanJiaChen/vue-element-admin/issues"
},
"engines": {
"node": ">=8.9",
"npm": ">= 3.0.0"
},
"browserslist": [
"> 1%",
"last 2 versions"
]
"keywords": [
"vue",
"admin",
"dashboard",
"element-ui",
"boilerplate",
"admin-template",
"management-system"
],
"license": "MIT",
"lint-staged": {
"src/**/*.{js,vue}": [
"eslint --fix",
"git add"
]
},
"husky": {
"hooks": {
"pre-commit": "lint-staged"
}
},
"repository": {
"type": "git",
"url": "git+https://github.com/PanJiaChen/vue-element-admin.git"
}
}

View File

@ -1,9 +1,2 @@
exports.notEmpty = name => {
return v => {
if (!v || v.trim === '') {
return `${name} is required`
} else {
return true
}
}
}
exports.notEmpty = name => v =>
!v || v.trim() === '' ? `${name} is required` : true

View File

@ -248,6 +248,7 @@ export default {
//
isSupported,
//
// eslint-disable-next-line no-prototype-builtins
isSupportTouch: document.hasOwnProperty('ontouchstart'),
//
step: 1, // 1 2 3

View File

@ -1,12 +1,6 @@
<template>
<div class="upload-container">
<el-button
:style="{background:color,borderColor:color}"
icon="el-icon-upload"
size="mini"
type="primary"
@click=" dialogVisible=true"
>
<el-button :style="{background:color,borderColor:color}" icon="el-icon-upload" size="mini" type="primary" @click=" dialogVisible=true">
upload
</el-button>
<el-dialog :visible.sync="dialogVisible">
@ -21,20 +15,14 @@
action="https://httpbin.org/post"
list-type="picture-card"
>
<el-button
size="small"
type="primary"
>
<el-button size="small" type="primary">
Click upload
</el-button>
</el-upload>
<el-button @click="dialogVisible = false">
Cancel
</el-button>
<el-button
type="primary"
@click="handleSubmit"
>
<el-button type="primary" @click="handleSubmit">
Confirm
</el-button>
</el-dialog>
@ -61,16 +49,12 @@ export default {
},
methods: {
checkAllSuccess() {
return Object.keys(this.listObj).every(
item => this.listObj[item].hasSuccess
)
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(
'Please wait for all images to be uploaded successfully. If there is a network problem, please refresh the page and upload again!'
)
this.$message('Please wait for all images to be uploaded successfully. If there is a network problem, please refresh the page and upload again!')
return
}
this.$emit('successCBK', arr)
@ -108,12 +92,7 @@ export default {
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
}
_self.listObj[fileName] = { hasSuccess: false, uid: file.uid, width: this.width, height: this.height }
}
resolve(true)
})

View File

@ -1,234 +0,0 @@
<template>
<div class="layout-container">
<div ref="list" class="layout-body">
<tinymce
v-model="content"
:menu="menu"
:height="height"
:new-event-list="eventList"
/>
</div>
<div class="util-component" style="display:none;">
<el-upload
:multiple="true"
:file-list="dataList.list"
:on-progress="uploadProgress"
:on-success="handleSuccess"
class="editor-slide-upload"
action="https://httpbin.org/post"
list-type="picture-card"
>
<el-button id="editorUploader" size="small" type="primary">
Click upload
</el-button>
</el-upload>
</div>
<div ref="imageWrapper" class="show-html" v-html="content" />
</div>
</template>
<script>
import Tinymce from './index'
import html2canvas from 'html2canvas'
import { Loading } from 'element-ui'
export default {
name: 'TinymceDemo',
components: { Tinymce },
data() {
return {
showPreviewDialog: false,
dataurl: '',
eventList: [
{
toolbarName: 'uploadImg',
tooltip: 'uploadImg',
text: 'uploadImg',
fn: (e, callback) => {
this.handleAdd('uploadImg', data => {
if (callback && typeof callback === 'function') {
callback(data)
}
})
}
},
// {
// toolbarName: 'uploadVideo',
// tooltip: 'uploadVideo',
// text: 'uploadVideo',
// fn: (e, callback) => {
// this.handleAdd('uploadVideo', data => {
// if (callback && typeof callback === 'function') {
// callback(data)
// }
// })
// }
// },
{
toolbarName: 'spreview',
tooltip: 'spreview',
text: 'spreview',
fn: (e, callback) => {
this.toImage()
}
}
],
isUpload: false,
dataList: {
list: [
{
url: ''
}
],
type: 'image',
setDom: v => `<a>${v.dom}</a>`
},
height: 700,
menu: {
file: { title: '文件', items: 'newdocument' },
edit: {
title: '编辑',
items: 'undo redo | cut copy paste pastetext | selectall'
},
insert: { title: '插入', items: 'link media | image hr' },
view: { title: '查看', items: 'visualaid' },
format: {
title: '格式',
items:
'bold italic underline strikethrough superscript subscript | formats | removeformat'
},
table: {
title: '表格',
items: 'inserttable tableprops deletetable | cell row column'
},
tools: { title: '工具', items: 'spellchecker code' }
},
loadingInstance: null,
content: `<h1 style="text-align: center;">测试demo!</h1>
<p></p>
<img class="img" src="https://ali-image-test.dabanjia.com/image/20200508/1588911589606_5140%24ban.jpg" width="200"/>
<p>枕着幸福在梦中微笑清零生活的压力烦恼阳光透过窗与你拥抱伸伸懒腰迎接周末来到问候源于无法按捺的心跳愿你周末乐逍遥生活更美妙
周末来到让烦恼跑光让忧愁找不到跟寂寞说再见跟压力说拜拜跟快乐说你好牵着幸福的手携上平安开开心心的过周末
每个人都有一个世界每首歌都有一个故事每一周都有一个周末每个人都要一个愿望我的愿望很现实周末清晨搅黄你的美梦周末快乐
</p>
`
}
},
computed: {
fileType() {
//
if (this.dataList.type === 'image') {
return 'png|jpe?g|gif|svg'
}
if (this.dataList.type === 'video') {
// mp4
return 'mp4'
// return "mp4|webm|ogg|mp3|wav|flac|aac";
}
return ''
}
},
mounted() {
// padding
this.height = this.$refs.list.clientHeight - 32
},
methods: {
toImage() {
var width = 330 // dom
var height = this.$refs.imageWrapper.scrollHeight // dom
var canvas = document.createElement('canvas') // canvas
var scale = 2 // 2
canvas.width = width * scale * scale // canvas *
canvas.height = height * scale * scale // canvas *
canvas.getContext('2d').scale(scale, scale) // context,scale
var opts = {
tainttest: true, //
scale: scale, // scale
useCORS: true,
canvas: canvas, // canvas
logging: true, //
width: width, // dom
height: height // dom
}
html2canvas(this.$refs.imageWrapper, opts).then(canvas => {
const dataurl = canvas.toDataURL('image/png')
this.dataurl = dataurl
if (this.dataurl !== '') {
console.log(this.dataurl, '生成base64')
}
})
},
error(e) {
console.log(e)
},
handleAdd(key, callback) {
this.dataList.list[0].url = ''
this.isUpload = false
switch (key) {
case 'uploadImg':
this.dataList.type = 'image'
document.getElementById('editorUploader').click()
break
case 'uploadVideo':
this.dataList.type = 'video'
document.getElementById('editorUploader').click()
break
// ,
case 'selectImg':
break
case 'selectVideo':
break
}
const unWatch = this.$watch('isUpload', val => {
if (val && callback && typeof callback === 'function') {
callback(this.dataList)
//
unWatch()
}
})
},
uploadProgress() {
this.loadingInstance = Loading.service({
lock: true,
text: '数据加载中,请稍后...',
background: 'rgba(0, 0, 0, 0.1)'
})
},
//
handleSuccess(data, file) {
this.loadingInstance.close()
this.dataList.list[0].url = file.url
const { type } = this.dataList
if (type === 'image') {
this.dataList.setDom = v =>
`<img class="img-scale" src="${v.url}" width="200">`
}
if (type === 'video') {
//
this.dataList.setDom = v =>
`<p >
<video controls="controls" width="200" height="350" >
<source src="https://dev-saas.oss-cn-beijing.aliyuncs.com/056eb234fa9844b0b7ec3e13bd39faf7.mp4" type="video/mp4" />
<source src="${v.url}" type="video/mp4" />
</video>
</p>
`
}
this.isUpload = true
}
}
}
</script>
<style scoped>
.show-html {
position: fixed;
right: 40px;
top: 80px;
width: 330px;
padding: 16px;
background: #fff;
border: 1px solid goldenrod;
}
</style>

View File

@ -1,16 +1,8 @@
<template>
<div
:class="{ fullscreen: fullscreen }"
class="tinymce-container"
:style="{ width: containerWidth }"
>
<div :class="{fullscreen:fullscreen}" class="tinymce-container" :style="{width:containerWidth}">
<textarea :id="tinymceId" class="tinymce-textarea" />
<div class="editor-custom-btn-container">
<editorImage
color="#1890ff"
class="editor-upload-btn"
@successCBK="imageSuccessCBK"
/>
<editorImage color="#1890ff" class="editor-upload-btn" @successCBK="imageSuccessCBK" />
</div>
</div>
</template>
@ -26,8 +18,7 @@ import toolbar from './toolbar'
import load from './dynamicLoadScript'
// why use this cdn, detail see https://github.com/PanJiaChen/tinymce-all-in-one
const tinymceCDN =
'https://cdn.jsdelivr.net/npm/tinymce-all-in-one@4.9.3/tinymce.min.js'
const tinymceCDN = 'https://cdn.jsdelivr.net/npm/tinymce-all-in-one@4.9.3/tinymce.min.js'
export default {
name: 'Tinymce',
@ -36,11 +27,7 @@ export default {
id: {
type: String,
default: function() {
return (
'vue-tinymce-' +
+new Date() +
((Math.random() * 1000).toFixed(0) + '')
)
return 'vue-tinymce-' + +new Date() + ((Math.random() * 1000).toFixed(0) + '')
}
},
value: {
@ -58,13 +45,6 @@ export default {
type: String,
default: 'file edit insert view format table'
},
newEventList: {
type: Array,
required: false,
default() {
return []
}
},
height: {
type: [Number, String],
required: false,
@ -83,18 +63,17 @@ export default {
tinymceId: this.id,
fullscreen: false,
languageTypeList: {
en: 'en',
zh: 'zh_CN',
es: 'es_MX',
ja: 'ja'
'en': 'en',
'zh': 'zh_CN',
'es': 'es_MX',
'ja': 'ja'
}
}
},
computed: {
containerWidth() {
const width = this.width
if (/^[\d]+(\.[\d]+)?$/.test(width)) {
// matches `100`, `'100'`
if (/^[\d]+(\.[\d]+)?$/.test(width)) { // matches `100`, `'100'`
return `${width}px`
}
return width
@ -104,8 +83,7 @@ export default {
value(val) {
if (!this.hasChange && this.hasInit) {
this.$nextTick(() =>
window.tinymce.get(this.tinymceId).setContent(val || '')
)
window.tinymce.get(this.tinymceId).setContent(val || ''))
}
}
},
@ -126,7 +104,7 @@ export default {
methods: {
init() {
// dynamic load tinymce from cdn
load(tinymceCDN, err => {
load(tinymceCDN, (err) => {
if (err) {
this.$message.error(err.message)
return
@ -136,39 +114,13 @@ export default {
},
initTinymce() {
const _this = this
//
let newToolbar = []
let barStr = ''
const eventData = []
this.newEventList.map(itemData => {
const { toolbarName, tooltip, text, fn } = itemData
eventData.push({
data: {
tooltip,
text,
onclick: e => {
fn(e, data => {
//
_this.dataSuccessCBK(data)
})
}
},
name: toolbarName
})
barStr += toolbarName + ' '
})
if (this.toolbar.length > 0) {
newToolbar = this.toolbar.concat(barStr)
} else {
newToolbar = toolbar.concat(barStr)
}
window.tinymce.init({
selector: `#${this.tinymceId}`,
language: this.languageTypeList['en'],
height: this.height,
body_class: 'panel-body ',
object_resizing: false,
toolbar: newToolbar,
toolbar: this.toolbar.length > 0 ? this.toolbar : toolbar,
menubar: this.menubar,
plugins: plugins,
end_container_on_empty_block: true,
@ -192,9 +144,8 @@ export default {
})
},
setup(editor) {
eventData.map(item => {
console.log('-----', item)
editor.addButton(item.name, item.data)
editor.on('FullscreenStateChanged', (e) => {
_this.fullscreen = e.state
})
},
// it will try to keep these URLs intact
@ -253,29 +204,7 @@ export default {
window.tinymce.get(this.tinymceId).getContent()
},
imageSuccessCBK(arr) {
const _this = this
arr.forEach(v => {
window.tinymce
.get(_this.tinymceId)
.insertContent(`<img class="wscnph" src="${v.url}" >`)
})
},
dataSuccessCBK(dataList) {
const { type, list, setDom } = dataList
let newDom = ''
console.log(dataList)
const self = this
if (type === 'image') {
list.forEach(v => {
newDom = setDom(v)
window.tinymce.get(self.tinymceId).insertContent(newDom)
})
} else if (type === 'video') {
list.forEach(v => {
newDom = setDom(v)
window.tinymce.get(self.tinymceId).insertContent(newDom)
})
}
arr.forEach(v => window.tinymce.get(this.tinymceId).insertContent(`<img class="wscnph" src="${v.url}" >`))
}
}
}

View File

@ -1,11 +1,11 @@
import store from '@/store'
export default {
inserted(el, binding, vnode) {
const { value } = binding
const roles = store.getters && store.getters.roles
function checkPermission(el, binding) {
const { value } = binding
const roles = store.getters && store.getters.roles
if (value && value instanceof Array && value.length > 0) {
if (value && value instanceof Array) {
if (value.length > 0) {
const permissionRoles = value
const hasPermission = roles.some(role => {
@ -15,8 +15,17 @@ export default {
if (!hasPermission) {
el.parentNode && el.parentNode.removeChild(el)
}
} else {
throw new Error(`need roles! Like v-permission="['admin','editor']"`)
}
} else {
throw new Error(`need roles! Like v-permission="['admin','editor']"`)
}
}
export default {
inserted(el, binding) {
checkPermission(el, binding)
},
update(el, binding) {
checkPermission(el, binding)
}
}

View File

@ -17,7 +17,11 @@ export default {
const vnodes = []
if (icon) {
vnodes.push(<svg-icon icon-class={icon}/>)
if (icon.includes('el-icon')) {
vnodes.push(<i class={[icon, 'sub-el-icon']} />)
} else {
vnodes.push(<svg-icon icon-class={icon}/>)
}
}
if (title) {
@ -27,3 +31,11 @@ export default {
}
}
</script>
<style scoped>
.sub-el-icon {
color: currentColor;
width: 1em;
height: 1em;
}
</style>

View File

@ -25,7 +25,7 @@ import nestedRouter from './modules/nested'
* meta : {
roles: ['admin','editor'] control the page roles (you can set multiple roles)
title: 'title' the name show in sidebar and breadcrumb (recommend set)
icon: 'svg-name' the icon show in the sidebar
icon: 'svg-name'/'el-icon-x' the icon show in the sidebar
noCache: true if set true, the page will no be cached(default is false)
affix: true if set true, the tag will affix in the tags-view
breadcrumb: false if set false, the item will hidden in breadcrumb(default is true)
@ -197,7 +197,7 @@ export const asyncRoutes = [
name: 'Example',
meta: {
title: 'Example',
icon: 'example'
icon: 'el-icon-s-help'
},
children: [
{

View File

@ -13,6 +13,7 @@ const state = {
const mutations = {
CHANGE_SETTING: (state, { key, value }) => {
// eslint-disable-next-line no-prototype-builtins
if (state.hasOwnProperty(key)) {
state[key] = value
}

View File

@ -103,28 +103,23 @@ const actions = {
},
// dynamically modify permissions
changeRoles({ commit, dispatch }, role) {
return new Promise(async resolve => {
const token = role + '-token'
async changeRoles({ commit, dispatch }, role) {
const token = role + '-token'
commit('SET_TOKEN', token)
setToken(token)
commit('SET_TOKEN', token)
setToken(token)
const { roles } = await dispatch('getInfo')
const { roles } = await dispatch('getInfo')
resetRouter()
resetRouter()
// generate accessible routes map based on roles
const accessRoutes = await dispatch('permission/generateRoutes', roles, { root: true })
// generate accessible routes map based on roles
const accessRoutes = await dispatch('permission/generateRoutes', roles, { root: true })
// dynamically add accessible routes
router.addRoutes(accessRoutes)
// dynamically add accessible routes
router.addRoutes(accessRoutes)
// reset visited views and cached views
dispatch('tagsView/delAllViews', null, { root: true })
resolve()
})
// reset visited views and cached views
dispatch('tagsView/delAllViews', null, { root: true })
}
}

View File

@ -57,6 +57,11 @@
margin-right: 16px;
}
.sub-el-icon {
margin-right: 12px;
margin-left: -2px;
}
.el-menu {
border: none;
height: 100%;
@ -105,6 +110,10 @@
.svg-icon {
margin-left: 20px;
}
.sub-el-icon {
margin-left: 19px;
}
}
}
@ -118,6 +127,10 @@
margin-left: 20px;
}
.sub-el-icon {
margin-left: 19px;
}
.el-submenu__icon-arrow {
display: none;
}
@ -178,6 +191,10 @@
.svg-icon {
margin-right: 16px;
}
.sub-el-icon {
margin-right: 12px;
margin-left: -2px;
}
}
.nest-menu .el-submenu>.el-submenu__title,

View File

@ -162,19 +162,21 @@ export function param(json) {
* @returns {Object}
*/
export function param2Obj(url) {
const search = url.split('?')[1]
const search = decodeURIComponent(url.split('?')[1]).replace(/\+/g, ' ')
if (!search) {
return {}
}
return JSON.parse(
'{"' +
decodeURIComponent(search)
.replace(/"/g, '\\"')
.replace(/&/g, '","')
.replace(/=/g, '":"')
.replace(/\+/g, ' ') +
'"}'
)
const obj = {}
const searchArr = search.split('&')
searchArr.forEach(v => {
const index = v.indexOf('=')
if (index !== -1) {
const name = v.substring(0, index)
const val = v.substring(index + 1, v.length)
obj[name] = val
}
})
return obj
}
/**

View File

@ -13,11 +13,7 @@ export default function checkPermission(value) {
const hasPermission = roles.some(role => {
return permissionRoles.includes(role)
})
if (!hasPermission) {
return false
}
return true
return hasPermission
} else {
console.error(`need roles! Like v-permission="['admin','editor']"`)
return false

View File

@ -1,12 +1,8 @@
import axios from 'axios'
import {
Message,
MessageBox,
Loading
} from 'element-ui'
import { MessageBox, Message } from 'element-ui'
import store from '@/store'
import { getToken } from '@/utils/auth'
var loadingInstance = null
// create an axios instance
const service = axios.create({
baseURL: process.env.VUE_APP_BASE_API, // url = base url + request url
@ -25,12 +21,6 @@ service.interceptors.request.use(
// please modify it according to the actual situation
config.headers['X-Token'] = getToken()
}
console.log('------')
loadingInstance = Loading.service({
lock: true,
text: '数据加载中,请稍后...',
background: 'rgba(0, 0, 0, 0.1)'
})
return config
},
error => {
@ -53,7 +43,6 @@ service.interceptors.response.use(
* You can also judge the status by HTTP Status Code
*/
response => {
loadingInstance.close()
const res = response.data
// if the custom code is not 20000, it is judged as an error.
@ -83,7 +72,6 @@ service.interceptors.response.use(
}
},
error => {
loadingInstance.close()
console.log('err' + error) // for debug
Message({
message: error.message,

View File

@ -2,44 +2,34 @@
<div class="components-container">
<aside>
Rich text is a core feature of the management backend, but at the same time it is a place with lots of pits. In the process of selecting rich texts, I also took a lot of detours. The common rich texts on the market have been basically used, and I finally chose Tinymce. See the more detailed rich text comparison and introduction.
<a
target="_blank"
class="link-type"
href="https://panjiachen.github.io/vue-element-admin-site/component/rich-editor.html"
>Documentation</a>
<a target="_blank" class="link-type" href="https://panjiachen.github.io/vue-element-admin-site/feature/component/rich-editor.html">Documentation</a>
</aside>
<div>
<tinymce
v-model="content"
:height="300"
/>
<tinymce v-model="content" :height="300" />
</div>
<div class="editor-content" v-html="content" />
</div>
</template>
<script>
import Tinymce from '@/components/Tinymce/example'
import Tinymce from '@/components/Tinymce'
export default {
name: 'TinymceDemo',
components: { Tinymce },
data() {
return {
content: `<h1 style="text-align: center;">测试demo!</h1>
<p></p>
<img class="img" src="https://ali-image-test.dabanjia.com/image/20200508/1588911589606_5140%24ban.jpg" width="200"/>
<p>枕着幸福在梦中微笑清零生活的压力烦恼阳光透过窗与你拥抱伸伸懒腰迎接周末来到问候源于无法按捺的心跳愿你周末乐逍遥生活更美妙
周末来到让烦恼跑光让忧愁找不到跟寂寞说再见跟压力说拜拜跟快乐说你好牵着幸福的手携上平安开开心心的过周末
每个人都有一个世界每首歌都有一个故事每一周都有一个周末每个人都要一个愿望我的愿望很现实周末清晨搅黄你的美梦周末快乐
</p>
`
content:
`<h1 style="text-align: center;">Welcome to the TinyMCE demo!</h1><p style="text-align: center; font-size: 15px;"><img title="TinyMCE Logo" src="//www.tinymce.com/images/glyph-tinymce@2x.png" alt="TinyMCE Logo" width="110" height="97" /><ul>
<li>Our <a href="//www.tinymce.com/docs/">documentation</a> is a great resource for learning how to configure TinyMCE.</li><li>Have a specific question? Visit the <a href="https://community.tinymce.com/forum/">Community Forum</a>.</li><li>We also offer enterprise grade support as part of <a href="https://tinymce.com/pricing">TinyMCE premium subscriptions</a>.</li>
</ul>`
}
}
}
</script>
<style scoped>
.editor-content {
.editor-content{
margin-top: 20px;
}
</style>

View File

@ -15,7 +15,7 @@
<el-progress :percentage="18" />
</div>
<div class="progress-item">
<span>Css</span>
<span>CSS</span>
<el-progress :percentage="12" />
</div>
<div class="progress-item">

View File

@ -1,5 +1,6 @@
<template>
<div class="app-container documentation-container">
<a class="document-btn" target="_blank" href="https://store.akveo.com/products/vue-java-admin-dashboard-spring?utm_campaign=akveo_store-Vue-Vue_demo%2Fgithub&utm_source=vue_admin&utm_medium=referral&utm_content=demo_English_button">Java backend integration</a>
<a class="document-btn" target="_blank" href="https://panjiachen.github.io/vue-element-admin-site/">Documentation</a>
<a class="document-btn" target="_blank" href="https://github.com/PanJiaChen/vue-element-admin/">Github Repository</a>
<a class="document-btn" target="_blank" href="https://panjiachen.gitee.io/vue-element-admin-site/zh/">国内文档</a>
@ -37,7 +38,7 @@ export default {
margin: 50px;
display: flex;
flex-wrap: wrap;
justify-content: space-evenly;
justify-content: flex-start;
.document-btn {
flex-shrink: 0;
@ -46,8 +47,8 @@ export default {
background: black;
color: white;
height: 60px;
width: 200px;
margin-bottom: 16px;
padding: 0 16px;
margin: 16px;
line-height: 60px;
font-size: 20px;
text-align: center;

View File

@ -71,21 +71,21 @@ export default {
},
handleDownload() {
this.downloadLoading = true
import('@/vendor/Export2Excel').then(excel => {
const multiHeader = [['Id', 'Main Information', '', '', 'Date']]
const header = ['', 'Title', 'Author', 'Readings', '']
const filterVal = ['id', 'title', 'author', 'pageviews', 'display_time']
const list = this.list
const data = this.formatJson(filterVal, list)
const merges = ['A1:A2', 'B1:D1', 'E1:E2']
excel.export_json_to_excel({
multiHeader,
header,
merges,
data
})
this.downloadLoading = false
import('@/vendor/Export2Excel').then(excel => {
const multiHeader = [['Id', 'Main Information', '', '', 'Date']]
const header = ['', 'Title', 'Author', 'Readings', '']
const filterVal = ['id', 'title', 'author', 'pageviews', 'display_time']
const list = this.list
const data = this.formatJson(filterVal, list)
const merges = ['A1:A2', 'B1:D1', 'E1:E2']
excel.export_json_to_excel({
multiHeader,
header,
merges,
data
})
this.downloadLoading = false
})
},
formatJson(filterVal, jsonData) {
return jsonData.map(v => filterVal.map(j => {

View File

@ -0,0 +1,14 @@
import { param2Obj } from '@/utils/index.js'
describe('Utils:param2Obj', () => {
const url = 'https://github.com/PanJiaChen/vue-element-admin?name=bill&age=29&sex=1&field=dGVzdA==&key=%E6%B5%8B%E8%AF%95'
it('param2Obj test', () => {
expect(param2Obj(url)).toEqual({
name: 'bill',
age: '29',
sex: '1',
field: window.btoa('test'),
key: '测试'
})
})
})

View File

@ -49,8 +49,20 @@ module.exports = {
}
},
chainWebpack(config) {
config.plugins.delete('preload') // TODO: need test
config.plugins.delete('prefetch') // TODO: need test
// it can improve the speed of the first screen, it is recommended to turn on preload
// it can improve the speed of the first screen, it is recommended to turn on preload
config.plugin('preload').tap(() => [
{
rel: 'preload',
// to ignore runtime.js
// https://github.com/vuejs/vue-cli/blob/dev/packages/@vue/cli-service/lib/config/app.js#L171
fileBlacklist: [/\.map$/, /hot-update\.js$/, /runtime\..*\.js$/],
include: 'initial'
}
])
// when there are many pages, it will cause too many meaningless requests
config.plugins.delete('prefetch')
// set svg-sprite-loader
config.module
@ -69,17 +81,6 @@ module.exports = {
})
.end()
// set preserveWhitespace
config.module
.rule('vue')
.use('vue-loader')
.loader('vue-loader')
.tap(options => {
options.compilerOptions.preserveWhitespace = true
return options
})
.end()
config
.when(process.env.NODE_ENV !== 'development',
config => {
@ -115,6 +116,7 @@ module.exports = {
}
}
})
// https:// webpack.js.org/configuration/optimization/#optimizationruntimechunk
config.optimization.runtimeChunk('single')
}
)