5
.babelrc
Normal file
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"presets": ["es2015", "stage-2"],
|
||||
"plugins": ["transform-runtime"],
|
||||
"comments": false
|
||||
}
|
3
.eslintignore
Normal file
|
@ -0,0 +1,3 @@
|
|||
build/*.js
|
||||
config/*.js
|
||||
src/assets
|
318
.eslintrc.js
Normal file
|
@ -0,0 +1,318 @@
|
|||
module.exports = {
|
||||
root: true,
|
||||
parser: 'babel-eslint',
|
||||
parserOptions: {
|
||||
sourceType: 'module'
|
||||
},
|
||||
env: {
|
||||
browser: true,
|
||||
node: true
|
||||
},
|
||||
extends: 'eslint:recommended',
|
||||
// required to lint *.vue files
|
||||
plugins: [
|
||||
'html'
|
||||
],
|
||||
// check if imports actually resolve
|
||||
'settings': {
|
||||
'import/resolver': {
|
||||
'webpack': {
|
||||
'config': 'build/webpack.base.conf.js'
|
||||
}
|
||||
}
|
||||
},
|
||||
// add your custom rules here
|
||||
'rules': {
|
||||
// don't require .vue extension when importing
|
||||
// 'import/extensions': ['error', 'always', {
|
||||
// 'js': 'never',
|
||||
// 'vue': 'never'
|
||||
// }],
|
||||
// allow debugger during development
|
||||
'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0,
|
||||
/*
|
||||
* Possible Errors
|
||||
*/
|
||||
|
||||
// disallow unnecessary parentheses
|
||||
'no-extra-parens': ['error', 'all', {'nestedBinaryExpressions': false}],
|
||||
|
||||
// disallow negating the left operand of relational operators
|
||||
'no-unsafe-negation': 'error',
|
||||
|
||||
// enforce valid JSDoc comments
|
||||
'valid-jsdoc': 'off',
|
||||
|
||||
/*
|
||||
* Best Practices
|
||||
*/
|
||||
|
||||
// enforce return statements in callbacks of array methods
|
||||
'array-callback-return': 'error',
|
||||
|
||||
// enforce consistent brace style for all control statements
|
||||
curly: ['error', 'multi-line'],
|
||||
|
||||
// enforce consistent newlines before and after dots
|
||||
'dot-location': ['error', 'property'],
|
||||
|
||||
// enforce dot notation whenever possible
|
||||
'dot-notation': 'error',
|
||||
|
||||
// require the use of === and !==
|
||||
'eqeqeq': ['error', 'smart'],
|
||||
|
||||
// disallow the use of arguments.caller or arguments.callee
|
||||
'no-caller': 'error',
|
||||
|
||||
// disallow empty functions
|
||||
'no-empty-function': 'error',
|
||||
|
||||
// disallow unnecessary calls to .bind()
|
||||
'no-extra-bind': 'error',
|
||||
|
||||
// disallow unnecessary labels
|
||||
'no-extra-label': 'error',
|
||||
|
||||
// disallow leading or trailing decimal points in numeric literals
|
||||
'no-floating-decimal': 'error',
|
||||
|
||||
// disallow assignments to native objects or read-only global variables
|
||||
'no-global-assign': 'error',
|
||||
|
||||
// disallow the use of eval()-like methods
|
||||
'no-implied-eval': 'error',
|
||||
|
||||
// disallow the use of the __iterator__ property
|
||||
'no-iterator': 'error',
|
||||
|
||||
// disallow unnecessary nested blocks
|
||||
'no-lone-blocks': 'error',
|
||||
|
||||
// disallow multiple spaces
|
||||
'no-multi-spaces': 'error',
|
||||
|
||||
// disallow new operators with the String, Number, and Boolean objects
|
||||
'no-new-wrappers': 'error',
|
||||
|
||||
// disallow octal escape sequences in string literals
|
||||
'no-octal-escape': 'error',
|
||||
|
||||
// disallow the use of the __proto__ property
|
||||
'no-proto': 'error',
|
||||
|
||||
// disallow comparisons where both sides are exactly the same
|
||||
'no-self-compare': 'error',
|
||||
|
||||
// disallow throwing literals as exceptions
|
||||
'no-throw-literal': 'error',
|
||||
|
||||
// disallow unused expressions
|
||||
'no-unused-expressions': 'error',
|
||||
|
||||
// disallow unnecessary calls to .call() and .apply()
|
||||
'no-useless-call': 'error',
|
||||
|
||||
// disallow unnecessary concatenation of literals or template literals
|
||||
'no-useless-concat': 'error',
|
||||
|
||||
// disallow unnecessary escape characters
|
||||
'no-useless-escape': 'error',
|
||||
|
||||
// disallow void operators
|
||||
'no-void': 'error',
|
||||
|
||||
// require parentheses around immediate function invocations
|
||||
'wrap-iife': 'error',
|
||||
|
||||
// require or disallow “Yoda” conditions
|
||||
yoda: 'error',
|
||||
|
||||
/*
|
||||
* Variables
|
||||
*/
|
||||
|
||||
// disallow labels that share a name with a variable
|
||||
'no-label-var': 'error',
|
||||
|
||||
// disallow initializing variables to undefined
|
||||
'no-undef-init': 'error',
|
||||
'no-undef': 'off',
|
||||
// disallow the use of variables before they are defined
|
||||
'no-use-before-define': 'error',
|
||||
|
||||
/*
|
||||
* Node.js and CommonJS
|
||||
*/
|
||||
|
||||
// disallow new operators with calls to require
|
||||
'no-new-require': 'error',
|
||||
|
||||
/*
|
||||
* Stylistic Issues
|
||||
*/
|
||||
|
||||
// enforce consistent spacing inside array brackets
|
||||
'array-bracket-spacing': 'error',
|
||||
|
||||
// enforce consistent spacing inside single-line blocks
|
||||
'block-spacing': 'error',
|
||||
|
||||
// enforce consistent brace style for blocks
|
||||
'brace-style': ['error', '1tbs', {'allowSingleLine': true}],
|
||||
|
||||
// require or disallow trailing commas
|
||||
'comma-dangle': 'error',
|
||||
|
||||
// enforce consistent spacing before and after commas
|
||||
'comma-spacing': 'error',
|
||||
|
||||
// enforce consistent comma style
|
||||
'comma-style': 'error',
|
||||
|
||||
// enforce consistent spacing inside computed property brackets
|
||||
'computed-property-spacing': 'error',
|
||||
|
||||
// require or disallow spacing between function identifiers and their invocations
|
||||
'func-call-spacing': 'error',
|
||||
|
||||
// enforce consistent indentation
|
||||
indent: ['error', 2, {SwitchCase: 1}],
|
||||
|
||||
// enforce the consistent use of either double or single quotes in JSX attributes
|
||||
'jsx-quotes': 'error',
|
||||
|
||||
// enforce consistent spacing between keys and values in object literal properties
|
||||
'key-spacing': 'error',
|
||||
|
||||
// enforce consistent spacing before and after keywords
|
||||
'keyword-spacing': 'error',
|
||||
|
||||
// enforce consistent linebreak style
|
||||
'linebreak-style': 'error',
|
||||
|
||||
// require or disallow newlines around directives
|
||||
'lines-around-directive': 'error',
|
||||
|
||||
// require constructor names to begin with a capital letter
|
||||
'new-cap': 'off',
|
||||
|
||||
// require parentheses when invoking a constructor with no arguments
|
||||
'new-parens': 'error',
|
||||
|
||||
// disallow Array constructors
|
||||
'no-array-constructor': 'error',
|
||||
|
||||
// disallow Object constructors
|
||||
'no-new-object': 'error',
|
||||
|
||||
// disallow trailing whitespace at the end of lines
|
||||
'no-trailing-spaces': 'error',
|
||||
|
||||
// disallow ternary operators when simpler alternatives exist
|
||||
'no-unneeded-ternary': 'error',
|
||||
|
||||
// disallow whitespace before properties
|
||||
'no-whitespace-before-property': 'error',
|
||||
|
||||
// enforce consistent spacing inside braces
|
||||
'object-curly-spacing': ['error', 'always'],
|
||||
|
||||
// require or disallow padding within blocks
|
||||
'padded-blocks': ['error', 'never'],
|
||||
|
||||
// require quotes around object literal property names
|
||||
'quote-props': ['error', 'as-needed'],
|
||||
|
||||
// enforce the consistent use of either backticks, double, or single quotes
|
||||
quotes: ['error', 'single'],
|
||||
|
||||
// enforce consistent spacing before and after semicolons
|
||||
'semi-spacing': 'error',
|
||||
|
||||
// require or disallow semicolons instead of ASI
|
||||
// semi: ['error', 'never'],
|
||||
|
||||
// enforce consistent spacing before blocks
|
||||
'space-before-blocks': 'error',
|
||||
|
||||
'no-console': 'off',
|
||||
|
||||
// enforce consistent spacing before function definition opening parenthesis
|
||||
'space-before-function-paren': ['error', 'never'],
|
||||
|
||||
// enforce consistent spacing inside parentheses
|
||||
'space-in-parens': 'error',
|
||||
|
||||
// require spacing around infix operators
|
||||
'space-infix-ops': 'error',
|
||||
|
||||
// enforce consistent spacing before or after unary operators
|
||||
'space-unary-ops': 'error',
|
||||
|
||||
// enforce consistent spacing after the // or /* in a comment
|
||||
'spaced-comment': 'error',
|
||||
|
||||
// require or disallow Unicode byte order mark (BOM)
|
||||
'unicode-bom': 'error',
|
||||
|
||||
|
||||
/*
|
||||
* ECMAScript 6
|
||||
*/
|
||||
|
||||
// require braces around arrow function bodies
|
||||
'arrow-body-style': 'error',
|
||||
|
||||
// require parentheses around arrow function arguments
|
||||
'arrow-parens': ['error', 'as-needed'],
|
||||
|
||||
// enforce consistent spacing before and after the arrow in arrow functions
|
||||
'arrow-spacing': 'error',
|
||||
|
||||
// enforce consistent spacing around * operators in generator functions
|
||||
'generator-star-spacing': ['error', 'after'],
|
||||
|
||||
// disallow duplicate module imports
|
||||
'no-duplicate-imports': 'error',
|
||||
|
||||
// disallow unnecessary computed property keys in object literals
|
||||
'no-useless-computed-key': 'error',
|
||||
|
||||
// disallow unnecessary constructors
|
||||
'no-useless-constructor': 'error',
|
||||
|
||||
// disallow renaming import, export, and destructured assignments to the same name
|
||||
'no-useless-rename': 'error',
|
||||
|
||||
// require let or const instead of var
|
||||
'no-var': 'error',
|
||||
|
||||
// require or disallow method and property shorthand syntax for object literals
|
||||
'object-shorthand': 'error',
|
||||
|
||||
// require arrow functions as callbacks
|
||||
'prefer-arrow-callback': 'error',
|
||||
|
||||
// require const declarations for variables that are never reassigned after declared
|
||||
'prefer-const': 'error',
|
||||
|
||||
// disallow parseInt() in favor of binary, octal, and hexadecimal literals
|
||||
'prefer-numeric-literals': 'error',
|
||||
|
||||
// require rest parameters instead of arguments
|
||||
'prefer-rest-params': 'error',
|
||||
|
||||
// require spread operators instead of .apply()
|
||||
'prefer-spread': 'error',
|
||||
|
||||
// enforce spacing between rest and spread operators and their expressions
|
||||
'rest-spread-spacing': 'error',
|
||||
|
||||
// require or disallow spacing around embedded expressions of template strings
|
||||
'template-curly-spacing': 'error',
|
||||
|
||||
// require or disallow spacing around the * in yield* expressions
|
||||
'yield-star-spacing': 'error'
|
||||
}
|
||||
}
|
10
.gitignore
vendored
Normal file
|
@ -0,0 +1,10 @@
|
|||
.DS_Store
|
||||
node_modules/
|
||||
dist/
|
||||
static/ckeditor
|
||||
|
||||
npm-debug.log
|
||||
test/unit/coverage
|
||||
test/e2e/reports
|
||||
selenium-debug.log
|
||||
.idea
|
101
README.md
|
@ -1,6 +1,91 @@
|
|||
这是一个vue+element+axios+webpack2的管理后台 先占个坑
|
||||
效果预览
|
||||
[线上地址](http://panjiachen.github.io/vue-element-admin)
|
||||
|
||||
## 前言
|
||||
> 这半年来一直在用vue写管理后台,目前后台已经有七十多个页面,十几种权限,但维护成本依然很低,效率依然很高,所以准备开源分享一下后台开发的经验和成果。目前的技术栈主要的采用vue+element+axios.由于是个人项目,所以数据请求都是用了mockjs代替。会出一系列的教程配套文章,如如何从零构建后台项目框架,如何做完整的用户系统(如权限验证,二次登录等),如何二次开发组件(如富文本),如何整合七牛等等文章,各种后台开发经验等等。莫急~~
|
||||
|
||||
## 功能
|
||||
- 登录/注销
|
||||
- 权限验证
|
||||
- 侧边栏
|
||||
- 面包屑
|
||||
- 富文本编辑器
|
||||
- Markdown编辑器
|
||||
- JSON编辑器
|
||||
- 列表拖拽
|
||||
- plitPane
|
||||
- Dropzone
|
||||
- Sticky
|
||||
- CountTo
|
||||
- echarts图表
|
||||
- 401,401错误页面
|
||||
- 错误日志
|
||||
- 导出excel
|
||||
- table example
|
||||
- form example
|
||||
- 多环境发布
|
||||
- dashboard
|
||||
- 二次登录
|
||||
- 动态侧边栏
|
||||
- mock数据
|
||||
|
||||
|
||||
## 开发
|
||||
```bash
|
||||
# 克隆项目
|
||||
git clone https://github.com/PanJiaChen/vue-element-admin.git
|
||||
|
||||
# 安装依赖
|
||||
npm install
|
||||
|
||||
# 本地开发 开启服务
|
||||
npm run build
|
||||
```
|
||||
浏览器访问 http://localhost:9527
|
||||
|
||||
## 发布
|
||||
```bash
|
||||
# 发布测试环境 带webpack ananalyzer
|
||||
npm run build:sit-preview
|
||||
|
||||
# 构建生成环境
|
||||
npm run build:prod
|
||||
```
|
||||
|
||||
## 目录结构
|
||||
```shell
|
||||
├── build // 构建相关
|
||||
├── config // 配置相关
|
||||
├── src // 源代码
|
||||
│ ├── api // 所以请求
|
||||
│ ├── assets // 主题 字体等静态资源
|
||||
│ ├── components // 全局公用组件
|
||||
│ ├── directive // 全局指令
|
||||
│ ├── filtres // 全局filter
|
||||
│ ├── mock // mock数据
|
||||
│ ├── router // 路由
|
||||
│ ├── store // 全局store管理
|
||||
│ ├── styles // 全局样式
|
||||
│ ├── utils // 全局公用方法
|
||||
│ ├── view // view
|
||||
│ ├── App.vue // 入口页面
|
||||
│ └── main.js // 入口 加载组件 初始化等
|
||||
├── static // 第三方不打包资源
|
||||
│ ├── jquery
|
||||
│ └── Tinymce // 富文本
|
||||
├── .babelrc // babel-loader 配置
|
||||
├── eslintrc.js // eslint 配置项
|
||||
├── .gitignore // git 忽略项
|
||||
├── favicon.ico // favicon图标
|
||||
├── index.html // html模板
|
||||
└── package.json // package.json
|
||||
|
||||
```
|
||||
|
||||
## 状态管理
|
||||
后台只有user和app配置相关状态使用vuex存在全局,其它数据都由每个业务页面自己管理。
|
||||
|
||||
|
||||
## 效果图
|
||||
|
||||
#### 两步验证登录 支持微信和qq
|
||||
|
||||
|
@ -8,7 +93,9 @@
|
|||
|
||||
#### 真正的动态换肤
|
||||
|
||||
![真正的动态换肤](https://github.com/PanJiaChen/vue-element-admin/blob/master/gifs/theme.gif)
|
||||
![真正的动态换肤](https://github.com/PanJiaChen/vue-element-admin/blob/master/gifs/theme.gif)<br />
|
||||
|
||||
|
||||
|
||||
#### 可收起侧边栏
|
||||
|
||||
|
@ -18,10 +105,18 @@
|
|||
|
||||
![enter image description here](https://github.com/PanJiaChen/vue-element-admin/blob/master/gifs/order.gif)
|
||||
|
||||
|
||||
|
||||
#### 上传裁剪头像
|
||||
|
||||
![enter image description here](https://github.com/PanJiaChen/vue-element-admin/blob/master/gifs/uploadAvatar.gif)
|
||||
|
||||
|
||||
|
||||
#### 错误统计
|
||||
|
||||
![enter image description here](https://github.com/PanJiaChen/vue-element-admin/blob/master/gifs/errorlog.gif)
|
||||
|
||||
#### 富文本(整合七牛 打水印等个性化功能)
|
||||
|
||||
![enter image description here](https://github.com/PanJiaChen/vue-element-admin/blob/master/gifs/editor.gif)
|
||||
|
|
42
build/build.js
Normal file
|
@ -0,0 +1,42 @@
|
|||
require('./check-versions')();
|
||||
var server = require('pushstate-server');
|
||||
var opn = require('opn')
|
||||
var ora = require('ora')
|
||||
var rm = require('rimraf')
|
||||
var path = require('path')
|
||||
var chalk = require('chalk')
|
||||
var webpack = require('webpack');
|
||||
var config = require('../config');
|
||||
var webpackConfig = require('./webpack.prod.conf');
|
||||
|
||||
console.log(process.env.NODE_ENV)
|
||||
console.log(process.env.npm_config_preview)
|
||||
|
||||
var spinner = ora('building for ' + process.env.NODE_ENV + '...')
|
||||
spinner.start()
|
||||
|
||||
|
||||
rm(path.join(config.build.assetsRoot, config.build.assetsSubDirectory), err => {
|
||||
if (err) throw err
|
||||
webpack(webpackConfig, function (err, stats) {
|
||||
spinner.stop()
|
||||
if (err) throw err
|
||||
process.stdout.write(stats.toString({
|
||||
colors: true,
|
||||
modules: false,
|
||||
children: false,
|
||||
chunks: false,
|
||||
chunkModules: false
|
||||
}) + '\n\n')
|
||||
|
||||
console.log(chalk.cyan(' Build complete.\n'))
|
||||
if(process.env.npm_config_preview){
|
||||
server.start({
|
||||
port: 80,
|
||||
directory: './dist',
|
||||
file: '/index.html'
|
||||
});
|
||||
opn('http://kushnerpreview.wallstreetcn.com/')
|
||||
}
|
||||
})
|
||||
})
|
45
build/check-versions.js
Normal file
|
@ -0,0 +1,45 @@
|
|||
var chalk = require('chalk')
|
||||
var semver = require('semver')
|
||||
var packageConfig = require('../package.json')
|
||||
|
||||
function exec(cmd) {
|
||||
return require('child_process').execSync(cmd).toString().trim()
|
||||
}
|
||||
|
||||
var versionRequirements = [
|
||||
{
|
||||
name: 'node',
|
||||
currentVersion: semver.clean(process.version),
|
||||
versionRequirement: packageConfig.engines.node
|
||||
},
|
||||
{
|
||||
name: 'npm',
|
||||
currentVersion: exec('npm --version'),
|
||||
versionRequirement: packageConfig.engines.npm
|
||||
}
|
||||
]
|
||||
|
||||
module.exports = function () {
|
||||
var warnings = []
|
||||
for (var i = 0; i < versionRequirements.length; i++) {
|
||||
var mod = versionRequirements[i]
|
||||
if (!semver.satisfies(mod.currentVersion, mod.versionRequirement)) {
|
||||
warnings.push(mod.name + ': ' +
|
||||
chalk.red(mod.currentVersion) + ' should be ' +
|
||||
chalk.green(mod.versionRequirement)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (warnings.length) {
|
||||
console.log('')
|
||||
console.log(chalk.yellow('To use this template, you must update following to modules:'))
|
||||
console.log()
|
||||
for (var i = 0; i < warnings.length; i++) {
|
||||
var warning = warnings[i]
|
||||
console.log(' ' + warning)
|
||||
}
|
||||
console.log()
|
||||
process.exit(1)
|
||||
}
|
||||
}
|
9
build/dev-client.js
Normal file
|
@ -0,0 +1,9 @@
|
|||
/* eslint-disable */
|
||||
require('eventsource-polyfill')
|
||||
var hotClient = require('webpack-hot-middleware/client?noInfo=true&reload=true')
|
||||
|
||||
hotClient.subscribe(function (event) {
|
||||
if (event.action === 'reload') {
|
||||
window.location.reload()
|
||||
}
|
||||
})
|
84
build/dev-server.js
Normal file
|
@ -0,0 +1,84 @@
|
|||
require('./check-versions')(); // 检查 Node 和 npm 版本
|
||||
var config = require('../config');
|
||||
if (!process.env.NODE_ENV) {
|
||||
process.env.NODE_ENV = JSON.parse(config.dev.env.NODE_ENV)
|
||||
}
|
||||
|
||||
var opn = require('opn')
|
||||
var path = require('path');
|
||||
var express = require('express');
|
||||
var webpack = require('webpack');
|
||||
var proxyMiddleware = require('http-proxy-middleware');
|
||||
var webpackConfig = require('./webpack.dev.conf');
|
||||
|
||||
// default port where dev server listens for incoming traffic
|
||||
var port = process.env.PORT || config.dev.port;
|
||||
// automatically open browser, if not set will be false
|
||||
var autoOpenBrowser = !!config.dev.autoOpenBrowser;
|
||||
// Define HTTP proxies to your custom API backend
|
||||
// https://github.com/chimurai/http-proxy-middleware
|
||||
var proxyTable = config.dev.proxyTable;
|
||||
|
||||
var app = express();
|
||||
var compiler = webpack(webpackConfig);
|
||||
|
||||
var devMiddleware = require('webpack-dev-middleware')(compiler, {
|
||||
publicPath: webpackConfig.output.publicPath,
|
||||
quiet: true
|
||||
});
|
||||
|
||||
var hotMiddleware = require('webpack-hot-middleware')(compiler, {
|
||||
log: () => {
|
||||
}
|
||||
});
|
||||
|
||||
// force page reload when html-webpack-plugin template changes
|
||||
compiler.plugin('compilation', function (compilation) {
|
||||
compilation.plugin('html-webpack-plugin-after-emit', function (data, cb) {
|
||||
hotMiddleware.publish({action: 'reload'});
|
||||
cb()
|
||||
})
|
||||
});
|
||||
|
||||
// compiler.apply(new DashboardPlugin());
|
||||
|
||||
// proxy api requests
|
||||
Object.keys(proxyTable).forEach(function (context) {
|
||||
var options = proxyTable[context]
|
||||
if (typeof options === 'string') {
|
||||
options = {target: options}
|
||||
}
|
||||
app.use(proxyMiddleware(options.filter || context, options))
|
||||
});
|
||||
|
||||
// handle fallback for HTML5 history API
|
||||
app.use(require('connect-history-api-fallback')());
|
||||
|
||||
// serve webpack bundle output
|
||||
app.use(devMiddleware);
|
||||
|
||||
// enable hot-reload and state-preserving
|
||||
// compilation error display
|
||||
app.use(hotMiddleware);
|
||||
|
||||
// serve pure static assets
|
||||
var staticPath = path.posix.join(config.dev.assetsPublicPath, config.dev.assetsSubDirectory);
|
||||
app.use(staticPath, express.static('./static'));
|
||||
|
||||
var uri = 'http://localhost:' + port
|
||||
|
||||
devMiddleware.waitUntilValid(function () {
|
||||
console.log('> Listening at ' + uri + '\n')
|
||||
});
|
||||
|
||||
module.exports = app.listen(port, function (err) {
|
||||
if (err) {
|
||||
console.log(err);
|
||||
return
|
||||
}
|
||||
|
||||
// when env is testing, don't need open it
|
||||
if (autoOpenBrowser && process.env.NODE_ENV !== 'testing') {
|
||||
opn(uri)
|
||||
}
|
||||
});
|
71
build/utils.js
Normal file
|
@ -0,0 +1,71 @@
|
|||
var path = require('path')
|
||||
var config = require('../config')
|
||||
var ExtractTextPlugin = require('extract-text-webpack-plugin')
|
||||
|
||||
exports.assetsPath = function (_path) {
|
||||
var assetsSubDirectory = process.env.NODE_ENV === 'production'
|
||||
? config.build.assetsSubDirectory
|
||||
: config.dev.assetsSubDirectory
|
||||
return path.posix.join(assetsSubDirectory, _path)
|
||||
}
|
||||
|
||||
exports.cssLoaders = function (options) {
|
||||
options = options || {}
|
||||
|
||||
var cssLoader = {
|
||||
loader: 'css-loader',
|
||||
options: {
|
||||
minimize: process.env.NODE_ENV === 'production',
|
||||
sourceMap: options.sourceMap
|
||||
}
|
||||
}
|
||||
|
||||
// generate loader string to be used with extract text plugin
|
||||
function generateLoaders(loader, loaderOptions) {
|
||||
var loaders = [cssLoader]
|
||||
if (loader) {
|
||||
loaders.push({
|
||||
loader: loader + '-loader',
|
||||
options: Object.assign({}, loaderOptions, {
|
||||
sourceMap: options.sourceMap
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// Extract CSS when that option is specified
|
||||
// (which is the case during production build)
|
||||
if (options.extract) {
|
||||
return ExtractTextPlugin.extract({
|
||||
use: loaders,
|
||||
fallback: 'vue-style-loader'
|
||||
})
|
||||
} else {
|
||||
return ['vue-style-loader'].concat(loaders)
|
||||
}
|
||||
}
|
||||
|
||||
// http://vuejs.github.io/vue-loader/en/configurations/extract-css.html
|
||||
return {
|
||||
css: generateLoaders(),
|
||||
postcss: generateLoaders(),
|
||||
less: generateLoaders('less'),
|
||||
sass: generateLoaders('sass', {indentedSyntax: true}),
|
||||
scss: generateLoaders('sass'),
|
||||
stylus: generateLoaders('stylus'),
|
||||
styl: generateLoaders('stylus')
|
||||
}
|
||||
}
|
||||
|
||||
// Generate loaders for standalone style files (outside of .vue)
|
||||
exports.styleLoaders = function (options) {
|
||||
var output = []
|
||||
var loaders = exports.cssLoaders(options)
|
||||
for (var extension in loaders) {
|
||||
var loader = loaders[extension]
|
||||
output.push({
|
||||
test: new RegExp('\\.' + extension + '$'),
|
||||
use: loader
|
||||
})
|
||||
}
|
||||
return output
|
||||
}
|
12
build/vue-loader.conf.js
Normal file
|
@ -0,0 +1,12 @@
|
|||
var utils = require('./utils')
|
||||
var config = require('../config')
|
||||
var isProduction = process.env.NODE_ENV === 'production'
|
||||
|
||||
module.exports = {
|
||||
loaders: utils.cssLoaders({
|
||||
sourceMap: isProduction
|
||||
? config.build.productionSourceMap
|
||||
: config.dev.cssSourceMap,
|
||||
extract: isProduction
|
||||
})
|
||||
}
|
85
build/webpack.base.conf.js
Normal file
|
@ -0,0 +1,85 @@
|
|||
var path = require('path');
|
||||
var utils = require('./utils');
|
||||
var config = require('../config');
|
||||
var vueLoaderConfig = require('./vue-loader.conf');
|
||||
|
||||
function resolve(dir) {
|
||||
return path.join(__dirname, '..', dir)
|
||||
}
|
||||
var src = path.resolve(__dirname, '../src');
|
||||
|
||||
module.exports = {
|
||||
entry: {
|
||||
app: './src/main.js'
|
||||
},
|
||||
output: {
|
||||
path: config.build.assetsRoot,
|
||||
filename: '[name].js',
|
||||
publicPath: process.env.NODE_ENV !== 'development' ? config.build.assetsPublicPath: config.dev.assetsPublicPath
|
||||
},
|
||||
resolve: {
|
||||
extensions: ['.js', '.vue', '.json'],
|
||||
alias: {
|
||||
'vue$': 'vue/dist/vue.esm.js',
|
||||
'@': resolve('src'),
|
||||
'src': path.resolve(__dirname, '../src'),
|
||||
'assets': path.resolve(__dirname, '../src/assets'),
|
||||
'components': path.resolve(__dirname, '../src/components'),
|
||||
'views': path.resolve(__dirname, '../src/views'),
|
||||
'styles': path.resolve(__dirname, '../src/styles'),
|
||||
'api': path.resolve(__dirname, '../src/api'),
|
||||
'utils': path.resolve(__dirname, '../src/utils'),
|
||||
'store': path.resolve(__dirname, '../src/store'),
|
||||
'router': path.resolve(__dirname, '../src/router'),
|
||||
'mock': path.resolve(__dirname, '../src/mock'),
|
||||
'vendor': path.resolve(__dirname, '../src/vendor'),
|
||||
'static': path.resolve(__dirname, '../static')
|
||||
}
|
||||
},
|
||||
externals: {
|
||||
jquery: 'jQuery'
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
// {
|
||||
// test: /\.(js|vue)$/,
|
||||
// loader: 'eslint-loader',
|
||||
// enforce: "pre",
|
||||
// include: [resolve('src'), resolve('test')],
|
||||
// options: {
|
||||
// formatter: require('eslint-friendly-formatter')
|
||||
// }
|
||||
// },
|
||||
{ test: /\.vue$/,
|
||||
loader: 'vue-loader',
|
||||
options: vueLoaderConfig
|
||||
},
|
||||
{
|
||||
test: /\.js$/,
|
||||
loader: 'babel-loader?cacheDirectory',
|
||||
include: [resolve('src'), resolve('test')]
|
||||
},
|
||||
{
|
||||
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
|
||||
loader: 'url-loader',
|
||||
query: {
|
||||
limit: 10000,
|
||||
name: utils.assetsPath('img/[name].[hash:7].[ext]')
|
||||
}
|
||||
},
|
||||
{
|
||||
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
|
||||
loader: 'url-loader',
|
||||
query: {
|
||||
limit: 10000,
|
||||
name: utils.assetsPath('fonts/[name].[hash:7].[ext]')
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
//注入全局mixin
|
||||
// sassResources: path.join(__dirname, '../src/styles/mixin.scss'),
|
||||
// sassLoader: {
|
||||
// data: path.join(__dirname, '../src/styles/index.scss')
|
||||
// },
|
||||
}
|
47
build/webpack.dev.conf.js
Normal file
|
@ -0,0 +1,47 @@
|
|||
var utils = require('./utils')
|
||||
var path = require('path')
|
||||
var webpack = require('webpack')
|
||||
var config = require('../config')
|
||||
var merge = require('webpack-merge')
|
||||
var baseWebpackConfig = require('./webpack.base.conf')
|
||||
var HtmlWebpackPlugin = require('html-webpack-plugin')
|
||||
var FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin')
|
||||
|
||||
// add hot-reload related code to entry chunks
|
||||
Object.keys(baseWebpackConfig.entry).forEach(function (name) {
|
||||
baseWebpackConfig.entry[name] = ['./build/dev-client'].concat(baseWebpackConfig.entry[name])
|
||||
})
|
||||
|
||||
function resolveApp(relativePath) {
|
||||
return path.resolve(relativePath);
|
||||
}
|
||||
|
||||
module.exports = merge(baseWebpackConfig, {
|
||||
module: {
|
||||
rules: utils.styleLoaders({sourceMap: config.dev.cssSourceMap})
|
||||
},
|
||||
// cheap-source-map is faster for development
|
||||
devtool: '#cheap-source-map',
|
||||
cache: true,
|
||||
plugins: [
|
||||
new webpack.DefinePlugin({
|
||||
'process.env': config.dev.env
|
||||
}),
|
||||
new webpack.ProvidePlugin({
|
||||
$: 'jquery',
|
||||
'jQuery': 'jquery'
|
||||
}),
|
||||
// https://github.com/glenjamin/webpack-hot-middleware#installation--usage
|
||||
new webpack.HotModuleReplacementPlugin(),
|
||||
new webpack.NoErrorsPlugin(),
|
||||
// https://github.com/ampedandwired/html-webpack-plugin
|
||||
new HtmlWebpackPlugin({
|
||||
filename: 'index.html',
|
||||
template: 'index.html',
|
||||
favicon: resolveApp('favicon.ico'),
|
||||
inject: true,
|
||||
path:config.dev.staticPath
|
||||
}),
|
||||
new FriendlyErrorsPlugin()
|
||||
]
|
||||
})
|
113
build/webpack.prod.conf.js
Normal file
|
@ -0,0 +1,113 @@
|
|||
var path = require('path')
|
||||
var utils = require('./utils')
|
||||
var webpack = require('webpack')
|
||||
var config = require('../config')
|
||||
var merge = require('webpack-merge')
|
||||
var baseWebpackConfig = require('./webpack.base.conf')
|
||||
var CopyWebpackPlugin = require('copy-webpack-plugin')
|
||||
var HtmlWebpackPlugin = require('html-webpack-plugin')
|
||||
var ExtractTextPlugin = require('extract-text-webpack-plugin')
|
||||
var OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin')
|
||||
|
||||
var env = process.env.NODE_ENV === 'production' ? config.build.prodEnv : config.build.sitEnv
|
||||
|
||||
function resolveApp(relativePath) {
|
||||
return path.resolve(relativePath);
|
||||
}
|
||||
|
||||
var webpackConfig = merge(baseWebpackConfig, {
|
||||
module: {
|
||||
rules: utils.styleLoaders({
|
||||
sourceMap: config.build.productionSourceMap,
|
||||
extract: true
|
||||
})
|
||||
},
|
||||
devtool: config.build.productionSourceMap ? '#source-map' : false,
|
||||
output: {
|
||||
path: config.build.assetsRoot,
|
||||
filename: utils.assetsPath('js/[name].[chunkhash].js'),
|
||||
chunkFilename: utils.assetsPath('js/[id].[chunkhash].js')
|
||||
},
|
||||
plugins: [
|
||||
// http://vuejs.github.io/vue-loader/en/workflow/production.html
|
||||
new webpack.DefinePlugin({
|
||||
'process.env': env
|
||||
}),
|
||||
new webpack.optimize.UglifyJsPlugin({
|
||||
compress: {
|
||||
warnings: false
|
||||
},
|
||||
sourceMap: true
|
||||
}),
|
||||
// extract css into its own file
|
||||
new ExtractTextPlugin({
|
||||
filename: utils.assetsPath('css/[name].[contenthash].css')
|
||||
}),
|
||||
// Compress extracted CSS. We are using this plugin so that possible
|
||||
// duplicated CSS from different components can be deduped.
|
||||
new OptimizeCSSPlugin(),
|
||||
// generate dist index.html with correct asset hash for caching.
|
||||
// you can customize output by editing /index.html
|
||||
// see https://github.com/ampedandwired/html-webpack-plugin
|
||||
new HtmlWebpackPlugin({
|
||||
filename: process.env.NODE_ENV === 'testing'
|
||||
? 'index.html'
|
||||
: config.build.index,
|
||||
template: 'index.html',
|
||||
inject: true,
|
||||
favicon: resolveApp('favicon.ico'),
|
||||
minify: {
|
||||
removeComments: true,
|
||||
collapseWhitespace: true,
|
||||
removeRedundantAttributes: true,
|
||||
useShortDoctype: true,
|
||||
removeEmptyAttributes: true,
|
||||
removeStyleLinkTypeAttributes: true,
|
||||
keepClosingSlash: true,
|
||||
minifyJS: true,
|
||||
minifyCSS: true,
|
||||
minifyURLs: true
|
||||
},
|
||||
path:config.build.staticPath,
|
||||
// necessary to consistently work with multiple chunks via CommonsChunkPlugin
|
||||
chunksSortMode: 'dependency'
|
||||
}),
|
||||
// split vendor js into its own file
|
||||
new webpack.optimize.CommonsChunkPlugin({
|
||||
name: 'vendor',
|
||||
minChunks: function (module, count) {
|
||||
// any required modules inside node_modules are extracted to vendor
|
||||
return (
|
||||
module.resource &&
|
||||
/\.js$/.test(module.resource) &&
|
||||
module.resource.indexOf(
|
||||
path.join(__dirname, '../node_modules')
|
||||
) === 0
|
||||
)
|
||||
}
|
||||
}),
|
||||
// extract webpack runtime and module manifest to its own file in order to
|
||||
// prevent vendor hash from being updated whenever app bundle is updated
|
||||
new webpack.optimize.CommonsChunkPlugin({
|
||||
name: 'manifest',
|
||||
chunks: ['vendor']
|
||||
}),
|
||||
// copy custom static assets
|
||||
new CopyWebpackPlugin([
|
||||
{
|
||||
from: path.resolve(__dirname, '../static'),
|
||||
to: config.build.assetsSubDirectory,
|
||||
ignore: ['.*']
|
||||
}
|
||||
]),
|
||||
new webpack.ProvidePlugin({
|
||||
$: 'jquery',
|
||||
'jQuery': 'jquery'
|
||||
})
|
||||
]
|
||||
})
|
||||
if (config.build.bundleAnalyzerReport) {
|
||||
var BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
|
||||
webpackConfig.plugins.push(new BundleAnalyzerPlugin())
|
||||
}
|
||||
module.exports = webpackConfig
|
5
config/dev.env.js
Normal file
|
@ -0,0 +1,5 @@
|
|||
module.exports = {
|
||||
NODE_ENV: '"development"',
|
||||
BASE_API: '"https://api-dev"',
|
||||
APP_ORIGIN: '"https://wallstreetcn.com"'
|
||||
}
|
41
config/index.js
Normal file
|
@ -0,0 +1,41 @@
|
|||
// see http://vuejs-templates.github.io/webpack for documentation.
|
||||
var path = require('path')
|
||||
|
||||
module.exports = {
|
||||
build: {
|
||||
sitEnv: require('./sit.env'),
|
||||
prodEnv: require('./prod.env'),
|
||||
index: path.resolve(__dirname, '../dist/index.html'),
|
||||
assetsRoot: path.resolve(__dirname, '../dist'),
|
||||
assetsSubDirectory: '',
|
||||
assetsPublicPath: './',
|
||||
staticPath:'./',
|
||||
productionSourceMap: true,
|
||||
// Gzip off by default as many popular static hosts such as
|
||||
// Surge or Netlify already gzip all static assets for you.
|
||||
// Before setting to `true`, make sure to:
|
||||
// npm install --save-dev compression-webpack-plugin
|
||||
productionGzip: false,
|
||||
productionGzipExtensions: ['js', 'css'],
|
||||
// Run the build command with an extra argument to
|
||||
// View the bundle analyzer report after build finishes:
|
||||
// `npm run build --report`
|
||||
// Set to `true` or `false` to always turn it on or off
|
||||
bundleAnalyzerReport: process.env.npm_config_report
|
||||
},
|
||||
dev: {
|
||||
env: require('./dev.env'),
|
||||
port: 9527,
|
||||
autoOpenBrowser: true,
|
||||
assetsSubDirectory: 'static',
|
||||
assetsPublicPath: '/',
|
||||
staticPath:'/static/',
|
||||
proxyTable: {},
|
||||
// CSS Sourcemaps off by default because relative paths are "buggy"
|
||||
// with this option, according to the CSS-Loader README
|
||||
// (https://github.com/webpack/css-loader#sourcemaps)
|
||||
// In our experience, they generally work as expected,
|
||||
// just be aware of this issue when enabling this option.
|
||||
cssSourceMap: false
|
||||
}
|
||||
}
|
5
config/prod.env.js
Normal file
|
@ -0,0 +1,5 @@
|
|||
module.exports = {
|
||||
NODE_ENV: '"production"',
|
||||
BASE_API: '"https://api-prod"',
|
||||
APP_ORIGIN: '"https://wallstreetcn.com"'
|
||||
};
|
5
config/sit.env.js
Normal file
|
@ -0,0 +1,5 @@
|
|||
module.exports = {
|
||||
NODE_ENV: '"production"',
|
||||
BASE_API: '"https://api-sit"',
|
||||
APP_ORIGIN: '"https://wallstreetcn.com"'
|
||||
};
|
BIN
documentImg/code1.png
Normal file
After Width: | Height: | Size: 22 KiB |
BIN
favicon.ico
Normal file
After Width: | Height: | Size: 66 KiB |
BIN
gifs/errorlog.gif
Normal file
After Width: | Height: | Size: 486 KiB |
17
index.html
Normal file
|
@ -0,0 +1,17 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
|
||||
<meta name="renderer" content="webkit">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
|
||||
<title>Juicy</title>
|
||||
</head>
|
||||
<body>
|
||||
<script src=<%= htmlWebpackPlugin.options.path %>jquery.min.js></script>
|
||||
<script src=<%= htmlWebpackPlugin.options.path %>tinymce1.3/tinymce.min.js></script>
|
||||
<div id="app"></div>
|
||||
<!-- built files will be auto injected -->
|
||||
</body>
|
||||
|
||||
</html>
|
96
package.json
Normal file
|
@ -0,0 +1,96 @@
|
|||
{
|
||||
"name": "juicy",
|
||||
"version": "1.0.0",
|
||||
"description": "A Vue.js admin",
|
||||
"author": "Pan <panfree23@gmail.com>",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "node build/dev-server.js",
|
||||
"build:prod": "NODE_ENV=production node build/build.js",
|
||||
"build:sit": "NODE_ENV=sit node build/build.js",
|
||||
"build:sit-preview": "NODE_ENV=sit npm_config_preview=true npm_config_report=true node build/build.js",
|
||||
"lint": "eslint --ext .js,.vue src"
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "0.15.3",
|
||||
"codemirror": "5.22.0",
|
||||
"dropzone": "4.3.0",
|
||||
"echarts": "3.4.0",
|
||||
"element-ui": "1.2.9",
|
||||
"file-saver": "1.3.3",
|
||||
"jquery": "3.1.1",
|
||||
"js-cookie": "2.1.3",
|
||||
"jsonlint": "1.6.2",
|
||||
"mockjs": "1.0.1-beta3",
|
||||
"normalize.css": "3.0.2",
|
||||
"nprogress": "0.2.0",
|
||||
"showdown": "1.6.4",
|
||||
"simplemde": "1.11.2",
|
||||
"vue": "2.2.6",
|
||||
"vue-multiselect": "2.0.0-beta.14",
|
||||
"vue-router": "2.3.0",
|
||||
"vuedraggable": "2.8.4",
|
||||
"vuex": "2.2.1",
|
||||
"xlsx": "0.8.1",
|
||||
"vue-count-to":"1.0.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"autoprefixer": "6.7.2",
|
||||
"axios-mock-adapter": "^1.8.1",
|
||||
"babel-core": "6.22.1",
|
||||
"babel-eslint": "7.1.1",
|
||||
"babel-loader": "6.2.10",
|
||||
"babel-plugin-transform-runtime": "6.22.0",
|
||||
"babel-preset-es2015": "6.22.0",
|
||||
"babel-preset-stage-2": "6.22.0",
|
||||
"babel-register": "6.22.0",
|
||||
"chalk": "1.1.3",
|
||||
"connect-history-api-fallback": "1.3.0",
|
||||
"copy-webpack-plugin": "4.0.1",
|
||||
"css-loader": "0.26.1",
|
||||
"eslint": "3.14.1",
|
||||
"eslint-config-airbnb-base": "11.0.1",
|
||||
"eslint-friendly-formatter": "2.0.7",
|
||||
"eslint-import-resolver-webpack": "0.8.1",
|
||||
"eslint-loader": "1.6.3",
|
||||
"eslint-plugin-html": "2.0.0",
|
||||
"eslint-plugin-import": "2.2.0",
|
||||
"eventsource-polyfill": "0.9.6",
|
||||
"express": "4.14.1",
|
||||
"extract-text-webpack-plugin": "2.0.0",
|
||||
"file-loader": "0.10.0",
|
||||
"friendly-errors-webpack-plugin": "1.1.3",
|
||||
"function-bind": "1.1.0",
|
||||
"html-webpack-plugin": "2.28.0",
|
||||
"http-proxy-middleware": "0.17.3",
|
||||
"node-sass": "3.7.0",
|
||||
"opn": "4.0.2",
|
||||
"optimize-css-assets-webpack-plugin": "1.3.0",
|
||||
"ora": "1.1.0",
|
||||
"pushstate-server": "2.1.0",
|
||||
"rimraf": "2.6.0",
|
||||
"sass-loader": "4.0.2",
|
||||
"script-loader": "0.7.0",
|
||||
"semver": "5.3.0",
|
||||
"style-loader": "0.13.1",
|
||||
"url-loader": "0.5.7",
|
||||
"vue-loader": "11.3.4",
|
||||
"vue-style-loader": "2.0.0",
|
||||
"vue-template-compiler": "2.2.6",
|
||||
"webpack": "2.2.1",
|
||||
"webpack-bundle-analyzer": "2.2.1",
|
||||
"webpack-dashboard": "0.2.1",
|
||||
"webpack-dev-middleware": "1.10.0",
|
||||
"webpack-hot-middleware": "2.16.1",
|
||||
"webpack-merge": "2.6.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 4.0.0",
|
||||
"npm": ">= 3.0.0"
|
||||
},
|
||||
"browserlist": [
|
||||
"> 1%",
|
||||
"last 2 versions",
|
||||
"not ie <= 8"
|
||||
]
|
||||
}
|
11
src/App.vue
Normal file
|
@ -0,0 +1,11 @@
|
|||
<template>
|
||||
<div id="app">
|
||||
<router-view></router-view>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default{
|
||||
name: 'APP'
|
||||
}
|
||||
</script>
|
16
src/api/article.js
Normal file
|
@ -0,0 +1,16 @@
|
|||
import { fetch } from 'utils/fetch';
|
||||
|
||||
export function getList() {
|
||||
return fetch({
|
||||
url: '/article/list',
|
||||
method: 'get'
|
||||
});
|
||||
}
|
||||
|
||||
export function getArticle() {
|
||||
return fetch({
|
||||
url: '/article/detail',
|
||||
method: 'get'
|
||||
});
|
||||
}
|
||||
|
17
src/api/article_table.js
Normal file
|
@ -0,0 +1,17 @@
|
|||
import { fetch } from 'utils/fetch';
|
||||
|
||||
export function fetchList(query) {
|
||||
return fetch({
|
||||
url: '/article_table/list',
|
||||
method: 'get',
|
||||
params: query
|
||||
});
|
||||
}
|
||||
|
||||
export function fetchPv(pv) {
|
||||
return fetch({
|
||||
url: '/article_table/pv',
|
||||
method: 'get',
|
||||
params: { pv }
|
||||
});
|
||||
}
|
30
src/api/login.js
Normal file
|
@ -0,0 +1,30 @@
|
|||
import { fetch } from 'utils/fetch';
|
||||
|
||||
export function loginByEmail(email, password) {
|
||||
const data = {
|
||||
email,
|
||||
password
|
||||
};
|
||||
return fetch({
|
||||
url: '/login/loginbyemail',
|
||||
method: 'post',
|
||||
params: data
|
||||
});
|
||||
}
|
||||
|
||||
export function logout() {
|
||||
return fetch({
|
||||
url: '/login/logout',
|
||||
method: 'post'
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
export function getInfo(token) {
|
||||
return fetch({
|
||||
url: '/user/info',
|
||||
method: 'get',
|
||||
params: { token }
|
||||
});
|
||||
}
|
||||
|
28
src/api/qiniu.js
Normal file
|
@ -0,0 +1,28 @@
|
|||
import fetch, { tpFetch } from 'utils/fetch';
|
||||
|
||||
export function getToken() {
|
||||
return fetch({
|
||||
url: '/qiniu/upload/token',
|
||||
method: 'get'
|
||||
});
|
||||
}
|
||||
export function upload(data) {
|
||||
return tpFetch({
|
||||
url: 'https://upload.qbox.me',
|
||||
method: 'post',
|
||||
data
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/* 外部uri转七牛uri*/
|
||||
export function netUpload(token, net_url) {
|
||||
const imgData = {
|
||||
net_url
|
||||
};
|
||||
return fetch({
|
||||
url: '/qiniu/upload/net/async',
|
||||
method: 'post',
|
||||
data: imgData
|
||||
});
|
||||
}
|
9
src/api/remoteSearch.js
Normal file
|
@ -0,0 +1,9 @@
|
|||
import { fetch } from 'utils/fetch';
|
||||
|
||||
export function userSearch(name) {
|
||||
return fetch({
|
||||
url: '/search/user',
|
||||
method: 'get',
|
||||
params: { name }
|
||||
});
|
||||
}
|
BIN
src/assets/401.gif
Normal file
After Width: | Height: | Size: 160 KiB |
BIN
src/assets/404.png
Normal file
After Width: | Height: | Size: 96 KiB |
BIN
src/assets/404_cloud.png
Normal file
After Width: | Height: | Size: 4.7 KiB |
BIN
src/assets/compbig.gif
Normal file
After Width: | Height: | Size: 793 KiB |
BIN
src/assets/custom-theme/fonts/element-icons.ttf
Normal file
BIN
src/assets/custom-theme/fonts/element-icons.woff
Normal file
23959
src/assets/custom-theme/index.css
Normal file
188
src/assets/iconfont/iconfont.js
Normal file
|
@ -0,0 +1,188 @@
|
|||
;(function(window) {
|
||||
|
||||
var svgSprite = '<svg>' +
|
||||
'' +
|
||||
'<symbol id="icon-bug" viewBox="0 0 1024 1024">' +
|
||||
'' +
|
||||
'<path d="M969.142857 548.571429q0 14.848-10.861714 25.709714t-25.709714 10.861714l-128 0q0 97.718857-38.290286 165.705143l118.857143 119.442286q10.861714 10.861714 10.861714 25.709714t-10.861714 25.709714q-10.276571 10.861714-25.709714 10.861714t-25.709714-10.861714l-113.152-112.566857q-2.852571 2.852571-8.557714 7.424t-23.990857 16.274286-37.156571 20.845714-46.848 16.566857-55.442286 7.424l0-512-73.142857 0 0 512q-29.147429 0-58.002286-7.716571t-49.700571-18.870857-37.705143-22.272-24.868571-18.578286l-8.557714-8.009143-104.557714 118.272q-11.446857 11.995429-27.428571 11.995429-13.714286 0-24.576-9.142857-10.861714-10.276571-11.702857-25.417143t8.850286-26.587429l115.419429-129.718857q-33.133714-65.133714-33.133714-156.562286l-128 0q-14.848 0-25.709714-10.861714t-10.861714-25.709714 10.861714-25.709714 25.709714-10.861714l128 0 0-168.009143-98.852571-98.852571q-10.861714-10.861714-10.861714-25.709714t10.861714-25.709714 25.709714-10.861714 25.709714 10.861714l98.852571 98.852571 482.304 0 98.852571-98.852571q10.861714-10.861714 25.709714-10.861714t25.709714 10.861714 10.861714 25.709714-10.861714 25.709714l-98.852571 98.852571 0 168.009143 128 0q14.848 0 25.709714 10.861714t10.861714 25.709714zM694.857143 219.428571l-365.714286 0q0-75.995429 53.430857-129.426286t129.426286-53.430857 129.426286 53.430857 53.430857 129.426286z" ></path>' +
|
||||
'' +
|
||||
'</symbol>' +
|
||||
'' +
|
||||
'<symbol id="icon-tubiaoleixingzhengchang" viewBox="0 0 1024 1024">' +
|
||||
'' +
|
||||
'<path d="M64 448 320 448 320 960 64 960 64 448 64 448ZM704 256 960 256 960 960 704 960 704 256 704 256ZM384 64 640 64 640 960 384 960 384 64 384 64Z" ></path>' +
|
||||
'' +
|
||||
'</symbol>' +
|
||||
'' +
|
||||
'<symbol id="icon-quanxian" viewBox="0 0 1024 1024">' +
|
||||
'' +
|
||||
'<path d="M818.246893 412.326906l-45.988404 0 0-70.991868c0-152.307871-123.463939-275.778974-275.778974-275.778974s-275.78102 123.471103-275.78102 275.778974l0 70.991868-45.987381 0c-25.379017 0-45.988404 20.566408-45.988404 45.987381l0 455.407074c0 25.428136 20.560268 45.988404 45.988404 45.988404l643.535779 0c25.37697 0 45.988404-20.560268 45.988404-45.988404L864.235296 458.314287C864.190271 432.893314 843.623863 412.326906 818.246893 412.326906L818.246893 412.326906zM680.331823 412.326906 312.62516 412.326906l0-70.991868c0-101.55393 82.344426-183.853331 183.854355-183.853331 101.509928 0 183.853331 82.343403 183.853331 183.853331L680.332846 412.326906 680.331823 412.326906zM680.331823 412.326906" ></path>' +
|
||||
'' +
|
||||
'</symbol>' +
|
||||
'' +
|
||||
'<symbol id="icon-zonghe" viewBox="0 0 1024 1024">' +
|
||||
'' +
|
||||
'<path d="M770.56 460.8l250.88 0C998.4 220.16 803.84 25.6 563.2 2.56l0 250.88C668.16 273.92 750.08 355.84 770.56 460.8L770.56 460.8zM770.56 460.8" ></path>' +
|
||||
'' +
|
||||
'<path d="M460.8 253.44 460.8 2.56C220.16 25.6 25.6 220.16 2.56 460.8l250.88 0C273.92 355.84 355.84 273.92 460.8 253.44L460.8 253.44zM460.8 253.44" ></path>' +
|
||||
'' +
|
||||
'<path d="M563.2 770.56l0 250.88c243.2-23.04 435.2-217.6 460.8-460.8l-250.88 0C750.08 668.16 668.16 750.08 563.2 770.56L563.2 770.56zM563.2 770.56" ></path>' +
|
||||
'' +
|
||||
'<path d="M253.44 563.2 2.56 563.2c23.04 243.2 217.6 435.2 460.8 460.8l0-250.88C355.84 750.08 273.92 668.16 253.44 563.2L253.44 563.2zM253.44 563.2" ></path>' +
|
||||
'' +
|
||||
'</symbol>' +
|
||||
'' +
|
||||
'<symbol id="icon-404" viewBox="0 0 1024 1024">' +
|
||||
'' +
|
||||
'<path d="M931.6 585.6l0 79c28.6-60.2 44.8-127.4 44.8-198.4C976.4 211 769.4 4 514.2 4S52 211 52 466.2c0 3.2 0.2 6.4 0.2 9.6l166-206 96.4 0L171.8 485.6l46.4 0 0-54.8 99.2-154.6 0 209.4 0 100 0 82.4-99.2 0 0-82.4L67.6 585.6c43 161 170.6 287.4 332.4 328.6-10.4 26.2-40.6 89.4-90.8 100.6-62.2 14 168.8 3.4 333.6-104.6 126.6-36.6 230.8-125.8 287.4-242.2l-97.6 0 0-82.4-166.2 0 0-87.2 0-12.8L666.4 476l166.2-206.2 94 0-140.4 215.8 46.4 0 0-59 99.2-154 0 213.2L931.8 585.6zM366.2 608c-4.8-11.2-7.2-23.2-7.2-36L359 357.6c0-12.8 2.4-24.8 7.2-36 4.8-11.2 11.4-21 19.6-29.2 8.2-8.2 18-14.8 29.2-19.6 11.2-4.8 23.2-7.2 36-7.2l81.6 0c12.8 0 24.8 2.4 36 7.2 11 4.8 20.6 11.2 28.8 19.2l-88.6 129.4 0-23c0-4.8-1.6-8.8-4.8-12-3.2-3.2-7.2-4.8-12-4.8-4.8 0-8.8 1.6-12 4.8-3.2 3.2-4.8 7.2-4.8 12l0 72L372.6 620C370.2 616.2 368 612.2 366.2 608zM624.4 572c0 12.8-2.4 24.8-7.2 36-4.8 11.2-11.4 21-19.6 29.2-8.2 8.2-18 14.8-29.2 19.6-11.2 4.8-23.2 7.2-36 7.2l-81.6 0c-12.8 0-24.8-2.4-36-7.2-11.2-4.8-21-11.4-29.2-19.6-3.6-3.6-7-7.8-10-12l99.2-144.6 0 50.6c0 4.8 1.6 8.8 4.8 12 3.2 3.2 7.2 4.8 12 4.8 4.8 0 8.8-1.6 12-4.8 3.2-3.2 4.8-7.2 4.8-12l0-99.6 92.6-135.2c6.6 7.4 12 15.8 16 25.2 4.8 11.2 7.2 23.2 7.2 36L624.2 572z" ></path>' +
|
||||
'' +
|
||||
'</symbol>' +
|
||||
'' +
|
||||
'<symbol id="icon-xinrenzhinan" viewBox="0 0 1024 1024">' +
|
||||
'' +
|
||||
'<path d="M780.108 761.059c54.451 60.351 87.706 138.983 87.706 225.358 0 12.015-0.659 23.882-1.902 35.581l-71.955 0c1.589-11.675 2.695-23.493 2.695-35.581 0-71.578-29.094-136.386-76.189-185.002C658.778 836.02 587.76 855.95 512 855.95c-75.689 0-146.65-19.888-208.294-54.432-47.129 48.604-76.358 113.305-76.358 184.9 0 12.088 1.105 23.906 2.695 35.581l-71.955 0c-1.243-11.699-1.902-23.567-1.902-35.581 0-86.366 33.19-165.055 87.587-225.446-96.765-78.277-158.75-197.84-158.75-331.998C85.023 193.163 276.188 2.001 512 2.001s426.977 191.162 426.977 426.972C938.977 563.184 876.94 682.785 780.108 761.059zM512 295.787c-196.511 0-355.814-80.302-355.814 122.251 0 202.551 159.303 366.749 355.814 366.749s355.814-164.199 355.814-366.749C867.814 215.485 708.511 295.787 512 295.787zM678.047 500.136c-26.2 0-47.442-21.24-47.442-47.442 0-26.197 21.242-47.442 47.442-47.442 26.202 0 47.442 21.244 47.442 47.442C725.488 478.896 704.249 500.136 678.047 500.136zM654.326 630.601c0 32.754-63.722 59.302-142.326 59.302s-142.326-26.549-142.326-59.302c0-8.445 4.376-16.446 12.017-23.719 21.98 20.927 71.979 35.579 130.309 35.579s108.329-14.652 130.309-35.579C649.949 614.155 654.326 622.156 654.326 630.601zM345.953 500.136c-26.202 0-47.442-21.24-47.442-47.442 0-26.197 21.24-47.442 47.442-47.442 26.2 0 47.442 21.244 47.442 47.442C393.395 478.896 372.153 500.136 345.953 500.136z" ></path>' +
|
||||
'' +
|
||||
'</symbol>' +
|
||||
'' +
|
||||
'<symbol id="icon-theme" viewBox="0 0 1024 1024">' +
|
||||
'' +
|
||||
'<path d="M788.00002 159.831491C756.00002 128 746 128 724.3801 128L642 128C642 128 576 188.923077 512 188.923077 448 188.923077 384 128 384 128L299.204802 128C276.629934 128 266 140.923077 245.847214 159.831491L81.582979 323.871735C70.243732 335.19552 52 371.692308 81.582979 408.655004 81.582979 408.655004 224.023667 540.29784 238.000003 541.53846L238.000003 835.076924C238.000003 868.452352 286.579 896 320 896L706 896C739.419808 896 788.00002 868.452352 788.00002 835.076924L788.00002 541.53846C802.145492 540.385864 942.448564 408.654992 942.448564 408.654992 974.00002 372 965.851264 334.883878 942.448584 311.513109L788.00002 159.831491Z" ></path>' +
|
||||
'' +
|
||||
'</symbol>' +
|
||||
'' +
|
||||
'<symbol id="icon-wujiaoxing" viewBox="0 0 1024 1024">' +
|
||||
'' +
|
||||
'<path d="M565.272827 34.627285l112.095872 237.542288c8.706637 18.321022 25.411424 31.051641 44.82285 33.996289l250.776598 38.081157c48.697387 7.411435 68.22505 70.046082 32.933559 105.979639l-181.494353 184.937155c-13.998147 14.230618-20.352386 34.815477-17.05903 54.93539l42.819161 261.127145c8.346858 50.695541-42.64204 89.451974-86.225039 65.51841l-224.307979-123.271141c-17.285968-9.525824-37.992596-9.525824-55.278564 0l-224.313514 123.271141c-43.582999 23.933565-94.571897-14.822869-86.219504-65.51841l42.813626-261.127145c3.321031-20.119914-3.088559-40.704772-17.086706-54.93539l-181.439002-184.937155c-35.285956-35.933557-15.819179-98.57374 32.933559-105.979639l250.748923-38.081157c19.350541-2.939112 36.083003-15.675267 44.75643-33.996289l112.123547-237.542288C480.497972-11.540583 543.509003-11.540583 565.272827 34.627285z" ></path>' +
|
||||
'' +
|
||||
'</symbol>' +
|
||||
'' +
|
||||
'<symbol id="icon-EXCEL" viewBox="0 0 1024 1024">' +
|
||||
'' +
|
||||
'<path d="M625.664 132.608V199.68h309.76v43.008h-309.76V312.32h309.76v43.008h-309.76v68.608h309.76v43.008h-309.76v68.608h309.76v43.008h-309.76v68.608h309.76v43.008h-309.76v68.096h309.76v43.008h-309.76v89.088H1024v-757.76h-398.336zM0 914.944L577.024 1024V0L0 109.056" ></path>' +
|
||||
'' +
|
||||
'<path d="M229.376 660.48H139.776l118.272-187.904-112.64-180.736h92.16l65.536 119.808L370.688 291.84h89.088l-112.64 177.664L466.944 660.48H373.248l-70.144-125.44L229.376 660.48z" ></path>' +
|
||||
'' +
|
||||
'</symbol>' +
|
||||
'' +
|
||||
'<symbol id="icon-zujian" viewBox="0 0 1024 1024">' +
|
||||
'' +
|
||||
'<path d="M568.6 0h454.9v454.9H568.6V0z m0 568.6h454.9v454.9H568.6V568.6zM0 568.6h454.9v454.9H0V568.6zM0 0h454.9v454.9H0V0z" fill="" ></path>' +
|
||||
'' +
|
||||
'</symbol>' +
|
||||
'' +
|
||||
'</svg>'
|
||||
var script = function() {
|
||||
var scripts = document.getElementsByTagName('script')
|
||||
return scripts[scripts.length - 1]
|
||||
}()
|
||||
var shouldInjectCss = script.getAttribute("data-injectcss")
|
||||
|
||||
/**
|
||||
* document ready
|
||||
*/
|
||||
var ready = function(fn) {
|
||||
if (document.addEventListener) {
|
||||
if (~["complete", "loaded", "interactive"].indexOf(document.readyState)) {
|
||||
setTimeout(fn, 0)
|
||||
} else {
|
||||
var loadFn = function() {
|
||||
document.removeEventListener("DOMContentLoaded", loadFn, false)
|
||||
fn()
|
||||
}
|
||||
document.addEventListener("DOMContentLoaded", loadFn, false)
|
||||
}
|
||||
} else if (document.attachEvent) {
|
||||
IEContentLoaded(window, fn)
|
||||
}
|
||||
|
||||
function IEContentLoaded(w, fn) {
|
||||
var d = w.document,
|
||||
done = false,
|
||||
// only fire once
|
||||
init = function() {
|
||||
if (!done) {
|
||||
done = true
|
||||
fn()
|
||||
}
|
||||
}
|
||||
// polling for no errors
|
||||
var polling = function() {
|
||||
try {
|
||||
// throws errors until after ondocumentready
|
||||
d.documentElement.doScroll('left')
|
||||
} catch (e) {
|
||||
setTimeout(polling, 50)
|
||||
return
|
||||
}
|
||||
// no errors, fire
|
||||
|
||||
init()
|
||||
};
|
||||
|
||||
polling()
|
||||
// trying to always fire before onload
|
||||
d.onreadystatechange = function() {
|
||||
if (d.readyState == 'complete') {
|
||||
d.onreadystatechange = null
|
||||
init()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert el before target
|
||||
*
|
||||
* @param {Element} el
|
||||
* @param {Element} target
|
||||
*/
|
||||
|
||||
var before = function(el, target) {
|
||||
target.parentNode.insertBefore(el, target)
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepend el to target
|
||||
*
|
||||
* @param {Element} el
|
||||
* @param {Element} target
|
||||
*/
|
||||
|
||||
var prepend = function(el, target) {
|
||||
if (target.firstChild) {
|
||||
before(el, target.firstChild)
|
||||
} else {
|
||||
target.appendChild(el)
|
||||
}
|
||||
}
|
||||
|
||||
function appendSvg() {
|
||||
var div, svg
|
||||
|
||||
div = document.createElement('div')
|
||||
div.innerHTML = svgSprite
|
||||
svgSprite = null
|
||||
svg = div.getElementsByTagName('svg')[0]
|
||||
if (svg) {
|
||||
svg.setAttribute('aria-hidden', 'true')
|
||||
svg.style.position = 'absolute'
|
||||
svg.style.width = 0
|
||||
svg.style.height = 0
|
||||
svg.style.overflow = 'hidden'
|
||||
prepend(svg, document.body)
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldInjectCss && !window.__iconfont__svg__cssinject__) {
|
||||
window.__iconfont__svg__cssinject__ = true
|
||||
try {
|
||||
document.write("<style>.svgfont {display: inline-block;width: 1em;height: 1em;fill: currentColor;vertical-align: -0.1em;font-size:16px;}</style>");
|
||||
} catch (e) {
|
||||
console && console.log(e)
|
||||
}
|
||||
}
|
||||
|
||||
ready(appendSvg)
|
||||
|
||||
|
||||
})(window)
|
104
src/components/Charts/barPercent.vue
Normal file
|
@ -0,0 +1,104 @@
|
|||
<template>
|
||||
<div :class="className" :id="id" :style="{height:height,width:width}"></div>
|
||||
</template>
|
||||
<script>
|
||||
// 引入 ECharts 主模块
|
||||
const echarts = require('echarts/lib/echarts');
|
||||
// 引入柱状图
|
||||
require('echarts/lib/chart/bar');
|
||||
// 引入提示框和标题组件
|
||||
require('echarts/lib/component/tooltip');
|
||||
export default {
|
||||
name: 'barPercent',
|
||||
props: {
|
||||
className: {
|
||||
type: String,
|
||||
default: 'bar-percent-chart'
|
||||
},
|
||||
id: {
|
||||
type: String,
|
||||
default: 'bar-percent-chart'
|
||||
},
|
||||
width: {
|
||||
type: String,
|
||||
default: '100px'
|
||||
},
|
||||
height: {
|
||||
type: String,
|
||||
default: '80px'
|
||||
},
|
||||
dataNum: {
|
||||
type: Number,
|
||||
default: 0
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
chart: null
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
dataNum() {
|
||||
this.setOptions()
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.initBar();
|
||||
},
|
||||
methods: {
|
||||
initBar() {
|
||||
this.chart = echarts.init(document.getElementById(this.id));
|
||||
this.setOptions();
|
||||
},
|
||||
setOptions() {
|
||||
this.chart.setOption({
|
||||
tooltip: {
|
||||
show: true,
|
||||
formatter(params) {
|
||||
return '已完成' + params.value + '篇<br/>目标90篇<br/>完成进度' + Math.round((params.value / 90) * 100) + '%'
|
||||
}
|
||||
},
|
||||
grid: {
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
top: 0,
|
||||
containLabel: false
|
||||
},
|
||||
xAxis: [{
|
||||
type: 'category',
|
||||
data: ['文章完成比例']
|
||||
}],
|
||||
yAxis: [{
|
||||
type: 'value',
|
||||
data: [],
|
||||
show: false
|
||||
}],
|
||||
animationDelay: 1000,
|
||||
series: [{
|
||||
type: 'bar',
|
||||
name: '初诊',
|
||||
itemStyle: {
|
||||
normal: {
|
||||
color: '#e5e5e5'
|
||||
}
|
||||
},
|
||||
silent: true,
|
||||
barGap: '-100%', // Make series be overlap
|
||||
data: [150]
|
||||
}, {
|
||||
type: 'bar',
|
||||
name: 'app',
|
||||
itemStyle: {
|
||||
normal: {
|
||||
color: '#30b08f'
|
||||
}
|
||||
},
|
||||
z: 10,
|
||||
data: [this.dataNum]
|
||||
}]
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
113
src/components/Charts/keyboard.vue
Normal file
|
@ -0,0 +1,113 @@
|
|||
<template>
|
||||
<div :class="className" :id="id" :style="{height:height,width:width}"></div>
|
||||
</template>
|
||||
<script>
|
||||
// 引入 ECharts 主模块
|
||||
const echarts = require('echarts/lib/echarts');
|
||||
// 引入柱状图
|
||||
require('echarts/lib/chart/bar');
|
||||
require('echarts/lib/chart/line');
|
||||
// 引入提示框和标题组件
|
||||
require('echarts/lib/component/tooltip');
|
||||
require('echarts/lib/component/title');
|
||||
|
||||
require('echarts/lib/component/visualMap');
|
||||
export default {
|
||||
name: 'barPercent',
|
||||
props: {
|
||||
className: {
|
||||
type: String,
|
||||
default: 'chart'
|
||||
},
|
||||
id: {
|
||||
type: String,
|
||||
default: 'chart'
|
||||
},
|
||||
width: {
|
||||
type: String,
|
||||
default: '200px'
|
||||
},
|
||||
height: {
|
||||
type: String,
|
||||
default: '200px'
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {};
|
||||
},
|
||||
mounted() {
|
||||
this.initBar();
|
||||
},
|
||||
methods: {
|
||||
initBar() {
|
||||
this.chart = echarts.init(document.getElementById(this.id));
|
||||
|
||||
const xAxisData = [];
|
||||
const data = [];
|
||||
for (let i = 0; i < 30; i++) {
|
||||
xAxisData.push(i + '号');
|
||||
data.push(Math.round(Math.random() * 2 + 3))
|
||||
}
|
||||
|
||||
this.chart.setOption(
|
||||
{
|
||||
backgroundColor: '#08263a',
|
||||
tooltip: {
|
||||
trigger: 'axis'
|
||||
},
|
||||
xAxis: {
|
||||
show: false,
|
||||
data: xAxisData
|
||||
},
|
||||
visualMap: {
|
||||
show: false,
|
||||
min: 0,
|
||||
max: 50,
|
||||
dimension: 0,
|
||||
inRange: {
|
||||
color: ['#4a657a', '#308e92', '#b1cfa5', '#f5d69f', '#f5898b', '#ef5055']
|
||||
}
|
||||
},
|
||||
yAxis: {
|
||||
axisLine: {
|
||||
show: false
|
||||
},
|
||||
axisLabel: {
|
||||
textStyle: {
|
||||
color: '#4a657a'
|
||||
}
|
||||
},
|
||||
splitLine: {
|
||||
show: true,
|
||||
lineStyle: {
|
||||
color: '#08263f'
|
||||
}
|
||||
},
|
||||
axisTick: {}
|
||||
},
|
||||
series: [{
|
||||
type: 'bar',
|
||||
data,
|
||||
name: '撸文数',
|
||||
itemStyle: {
|
||||
normal: {
|
||||
barBorderRadius: 5,
|
||||
shadowBlur: 10,
|
||||
shadowColor: '#111'
|
||||
}
|
||||
},
|
||||
animationEasing: 'elasticOut',
|
||||
animationEasingUpdate: 'elasticOut',
|
||||
animationDelay(idx) {
|
||||
return idx * 20;
|
||||
},
|
||||
animationDelayUpdate(idx) {
|
||||
return idx * 20;
|
||||
}
|
||||
}]
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
149
src/components/Charts/keyboard2.vue
Normal file
|
@ -0,0 +1,149 @@
|
|||
<template>
|
||||
<div :class="className" :id="id" :style="{height:height,width:width}"></div>
|
||||
</template>
|
||||
<script>
|
||||
// 引入 ECharts 主模块
|
||||
const echarts = require('echarts/lib/echarts');
|
||||
// 引入柱状图
|
||||
require('echarts/lib/chart/bar');
|
||||
require('echarts/lib/chart/line');
|
||||
// 引入提示框和标题组件
|
||||
require('echarts/lib/component/tooltip');
|
||||
require('echarts/lib/component/title');
|
||||
|
||||
require('echarts/lib/component/visualMap');
|
||||
export default {
|
||||
name: 'barPercent',
|
||||
props: {
|
||||
className: {
|
||||
type: String,
|
||||
default: 'chart'
|
||||
},
|
||||
id: {
|
||||
type: String,
|
||||
default: 'chart'
|
||||
},
|
||||
width: {
|
||||
type: String,
|
||||
default: '200px'
|
||||
},
|
||||
height: {
|
||||
type: String,
|
||||
default: '200px'
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {};
|
||||
},
|
||||
mounted() {
|
||||
this.initBar();
|
||||
},
|
||||
methods: {
|
||||
initBar() {
|
||||
this.chart = echarts.init(document.getElementById(this.id));
|
||||
|
||||
const xAxisData = [];
|
||||
const data = [];
|
||||
const data2 = [];
|
||||
for (let i = 0; i < 50; i++) {
|
||||
xAxisData.push(i);
|
||||
data.push((Math.sin(i / 5) * (i / 5 - 10) + i / 6) * 5);
|
||||
data2.push((Math.sin(i / 5) * (i / 5 + 10) + i / 6) * 3);
|
||||
}
|
||||
this.chart.setOption(
|
||||
{
|
||||
backgroundColor: '#08263a',
|
||||
xAxis: [{
|
||||
show: false,
|
||||
data: xAxisData
|
||||
}, {
|
||||
show: false,
|
||||
data: xAxisData
|
||||
}],
|
||||
visualMap: {
|
||||
show: false,
|
||||
min: 0,
|
||||
max: 50,
|
||||
dimension: 0,
|
||||
inRange: {
|
||||
color: ['#4a657a', '#308e92', '#b1cfa5', '#f5d69f', '#f5898b', '#ef5055']
|
||||
}
|
||||
},
|
||||
yAxis: {
|
||||
axisLine: {
|
||||
show: false
|
||||
},
|
||||
axisLabel: {
|
||||
textStyle: {
|
||||
color: '#4a657a'
|
||||
}
|
||||
},
|
||||
splitLine: {
|
||||
show: true,
|
||||
lineStyle: {
|
||||
color: '#08263f'
|
||||
}
|
||||
},
|
||||
axisTick: {
|
||||
show: false
|
||||
}
|
||||
},
|
||||
series: [{
|
||||
name: 'back',
|
||||
type: 'bar',
|
||||
data: data2,
|
||||
z: 1,
|
||||
itemStyle: {
|
||||
normal: {
|
||||
opacity: 0.4,
|
||||
barBorderRadius: 5,
|
||||
shadowBlur: 3,
|
||||
shadowColor: '#111'
|
||||
}
|
||||
}
|
||||
}, {
|
||||
name: 'Simulate Shadow',
|
||||
type: 'line',
|
||||
data,
|
||||
z: 2,
|
||||
showSymbol: false,
|
||||
animationDelay: 0,
|
||||
animationEasing: 'linear',
|
||||
animationDuration: 1200,
|
||||
lineStyle: {
|
||||
normal: {
|
||||
color: 'transparent'
|
||||
}
|
||||
},
|
||||
areaStyle: {
|
||||
normal: {
|
||||
color: '#08263a',
|
||||
shadowBlur: 50,
|
||||
shadowColor: '#000'
|
||||
}
|
||||
}
|
||||
}, {
|
||||
name: 'front',
|
||||
type: 'bar',
|
||||
data,
|
||||
xAxisIndex: 1,
|
||||
z: 3,
|
||||
itemStyle: {
|
||||
normal: {
|
||||
barBorderRadius: 5
|
||||
}
|
||||
}
|
||||
}],
|
||||
animationEasing: 'elasticOut',
|
||||
animationEasingUpdate: 'elasticOut',
|
||||
animationDelay(idx) {
|
||||
return idx * 20;
|
||||
},
|
||||
animationDelayUpdate(idx) {
|
||||
return idx * 20;
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
146
src/components/Charts/line.vue
Normal file
|
@ -0,0 +1,146 @@
|
|||
<template>
|
||||
<div :class="className" :id="id" :style="{height:height,width:width}"></div>
|
||||
</template>
|
||||
<script>
|
||||
// 引入 ECharts 主模块
|
||||
const echarts = require('echarts/lib/echarts');
|
||||
// 引入图
|
||||
require('echarts/lib/chart/line');
|
||||
// 引入提示框和标题组件
|
||||
require('echarts/lib/component/markLine');
|
||||
require('echarts/lib/component/markPoint');
|
||||
require('echarts/lib/component/tooltip');
|
||||
export default {
|
||||
name: 'lineChart',
|
||||
props: {
|
||||
className: {
|
||||
type: String,
|
||||
default: 'line-chart'
|
||||
},
|
||||
id: {
|
||||
type: String,
|
||||
default: 'line-chart'
|
||||
},
|
||||
width: {
|
||||
type: String,
|
||||
default: '100%'
|
||||
},
|
||||
height: {
|
||||
type: String,
|
||||
default: '280px'
|
||||
},
|
||||
listData: {
|
||||
type: Array,
|
||||
require: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
chart: null
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
listData(dataList) {
|
||||
this.setLine(dataList)
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.chart = echarts.init(document.getElementById(this.id));
|
||||
this.setLine(this.listData);
|
||||
},
|
||||
methods: {
|
||||
setLine(dataList) {
|
||||
const xAxisData = [];
|
||||
const data = [];
|
||||
for (let i = 0; i < dataList.length; i++) {
|
||||
const item = dataList[i]
|
||||
xAxisData.push(item.week.substring(item.week.length - 2) + '周');
|
||||
data.push(item.count)
|
||||
}
|
||||
const markLineData = [];
|
||||
for (let i = 1; i < data.length; i++) {
|
||||
markLineData.push([{
|
||||
xAxis: i - 1,
|
||||
yAxis: data[i - 1],
|
||||
value: data[i] - data[i - 1]
|
||||
}, {
|
||||
xAxis: i,
|
||||
yAxis: data[i]
|
||||
}]);
|
||||
}
|
||||
this.chart.setOption({
|
||||
title: {
|
||||
text: 'Awesome Chart'
|
||||
},
|
||||
grid: {
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 20,
|
||||
|
||||
containLabel: true
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'axis'
|
||||
},
|
||||
animationDelay: 1000,
|
||||
xAxis: {
|
||||
data: xAxisData,
|
||||
axisLine: {
|
||||
show: false
|
||||
},
|
||||
axisTick: {
|
||||
show: false
|
||||
}
|
||||
// axisLabel:{
|
||||
// show:false
|
||||
// },
|
||||
},
|
||||
|
||||
yAxis: {
|
||||
splitLine: {
|
||||
show: false
|
||||
},
|
||||
show: false
|
||||
// min: 90
|
||||
},
|
||||
series: [{
|
||||
name: '撸文数',
|
||||
type: 'line',
|
||||
data,
|
||||
markPoint: {
|
||||
data: [
|
||||
{ type: 'max', name: '最大值' },
|
||||
{ type: 'min', name: '最小值' }
|
||||
]
|
||||
},
|
||||
itemStyle: {
|
||||
normal: {
|
||||
color: '#30b08f'
|
||||
}
|
||||
},
|
||||
markLine: {
|
||||
silent: true,
|
||||
smooth: true,
|
||||
effect: {
|
||||
show: true
|
||||
},
|
||||
animationDuration(idx) {
|
||||
return idx * 100;
|
||||
},
|
||||
animationDelay: 1000,
|
||||
animationEasing: 'quadraticInOut',
|
||||
distance: 1,
|
||||
label: {
|
||||
normal: {
|
||||
position: 'middle'
|
||||
}
|
||||
},
|
||||
symbol: ['none', 'none'],
|
||||
data: markLineData
|
||||
}
|
||||
}]
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
219
src/components/Charts/linemarker.vue
Normal file
|
@ -0,0 +1,219 @@
|
|||
<template>
|
||||
<div :class="className" :id="id" :style="{height:height,width:width}"></div>
|
||||
</template>
|
||||
<script>
|
||||
// 引入 ECharts 主模块
|
||||
const echarts = require('echarts/lib/echarts');
|
||||
|
||||
require('echarts/lib/chart/line');
|
||||
// 引入提示框和标题组件
|
||||
require('echarts/lib/component/tooltip');
|
||||
require('echarts/lib/component/title');
|
||||
require('echarts/lib/component/legend');
|
||||
export default {
|
||||
name: 'barPercent',
|
||||
props: {
|
||||
className: {
|
||||
type: String,
|
||||
default: 'chart'
|
||||
},
|
||||
id: {
|
||||
type: String,
|
||||
default: 'chart'
|
||||
},
|
||||
width: {
|
||||
type: String,
|
||||
default: '200px'
|
||||
},
|
||||
height: {
|
||||
type: String,
|
||||
default: '200px'
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {};
|
||||
},
|
||||
mounted() {
|
||||
this.initChart();
|
||||
},
|
||||
methods: {
|
||||
initChart() {
|
||||
this.chart = echarts.init(document.getElementById(this.id));
|
||||
|
||||
this.chart.setOption({
|
||||
backgroundColor: '#394056',
|
||||
title: {
|
||||
text: '请求数',
|
||||
textStyle: {
|
||||
fontWeight: 'normal',
|
||||
fontSize: 16,
|
||||
color: '#F1F1F3'
|
||||
},
|
||||
left: '6%'
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
lineStyle: {
|
||||
color: '#57617B'
|
||||
}
|
||||
}
|
||||
},
|
||||
legend: {
|
||||
icon: 'rect',
|
||||
itemWidth: 14,
|
||||
itemHeight: 5,
|
||||
itemGap: 13,
|
||||
data: ['移动', '电信', '联通'],
|
||||
right: '4%',
|
||||
textStyle: {
|
||||
fontSize: 12,
|
||||
color: '#F1F1F3'
|
||||
}
|
||||
},
|
||||
grid: {
|
||||
left: '3%',
|
||||
right: '4%',
|
||||
bottom: '3%',
|
||||
containLabel: true
|
||||
},
|
||||
xAxis: [{
|
||||
type: 'category',
|
||||
boundaryGap: false,
|
||||
axisLine: {
|
||||
lineStyle: {
|
||||
color: '#57617B'
|
||||
}
|
||||
},
|
||||
data: ['13:00', '13:05', '13:10', '13:15', '13:20', '13:25', '13:30', '13:35', '13:40', '13:45', '13:50', '13:55']
|
||||
}],
|
||||
yAxis: [{
|
||||
type: 'value',
|
||||
name: '单位(%)',
|
||||
axisTick: {
|
||||
show: false
|
||||
},
|
||||
axisLine: {
|
||||
lineStyle: {
|
||||
color: '#57617B'
|
||||
}
|
||||
},
|
||||
axisLabel: {
|
||||
margin: 10,
|
||||
textStyle: {
|
||||
fontSize: 14
|
||||
}
|
||||
},
|
||||
splitLine: {
|
||||
lineStyle: {
|
||||
color: '#57617B'
|
||||
}
|
||||
}
|
||||
}],
|
||||
series: [{
|
||||
name: '移动',
|
||||
type: 'line',
|
||||
smooth: true,
|
||||
symbol: 'circle',
|
||||
symbolSize: 5,
|
||||
showSymbol: false,
|
||||
lineStyle: {
|
||||
normal: {
|
||||
width: 1
|
||||
}
|
||||
},
|
||||
areaStyle: {
|
||||
normal: {
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
|
||||
offset: 0,
|
||||
color: 'rgba(137, 189, 27, 0.3)'
|
||||
}, {
|
||||
offset: 0.8,
|
||||
color: 'rgba(137, 189, 27, 0)'
|
||||
}], false),
|
||||
shadowColor: 'rgba(0, 0, 0, 0.1)',
|
||||
shadowBlur: 10
|
||||
}
|
||||
},
|
||||
itemStyle: {
|
||||
normal: {
|
||||
color: 'rgb(137,189,27)',
|
||||
borderColor: 'rgba(137,189,2,0.27)',
|
||||
borderWidth: 12
|
||||
|
||||
}
|
||||
},
|
||||
data: [220, 182, 191, 134, 150, 120, 110, 125, 145, 122, 165, 122]
|
||||
}, {
|
||||
name: '电信',
|
||||
type: 'line',
|
||||
smooth: true,
|
||||
symbol: 'circle',
|
||||
symbolSize: 5,
|
||||
showSymbol: false,
|
||||
lineStyle: {
|
||||
normal: {
|
||||
width: 1
|
||||
}
|
||||
},
|
||||
areaStyle: {
|
||||
normal: {
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
|
||||
offset: 0,
|
||||
color: 'rgba(0, 136, 212, 0.3)'
|
||||
}, {
|
||||
offset: 0.8,
|
||||
color: 'rgba(0, 136, 212, 0)'
|
||||
}], false),
|
||||
shadowColor: 'rgba(0, 0, 0, 0.1)',
|
||||
shadowBlur: 10
|
||||
}
|
||||
},
|
||||
itemStyle: {
|
||||
normal: {
|
||||
color: 'rgb(0,136,212)',
|
||||
borderColor: 'rgba(0,136,212,0.2)',
|
||||
borderWidth: 12
|
||||
|
||||
}
|
||||
},
|
||||
data: [120, 110, 125, 145, 122, 165, 122, 220, 182, 191, 134, 150]
|
||||
}, {
|
||||
name: '联通',
|
||||
type: 'line',
|
||||
smooth: true,
|
||||
symbol: 'circle',
|
||||
symbolSize: 5,
|
||||
showSymbol: false,
|
||||
lineStyle: {
|
||||
normal: {
|
||||
width: 1
|
||||
}
|
||||
},
|
||||
areaStyle: {
|
||||
normal: {
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
|
||||
offset: 0,
|
||||
color: 'rgba(219, 50, 51, 0.3)'
|
||||
}, {
|
||||
offset: 0.8,
|
||||
color: 'rgba(219, 50, 51, 0)'
|
||||
}], false),
|
||||
shadowColor: 'rgba(0, 0, 0, 0.1)',
|
||||
shadowBlur: 10
|
||||
}
|
||||
},
|
||||
itemStyle: {
|
||||
normal: {
|
||||
color: 'rgb(219,50,51)',
|
||||
borderColor: 'rgba(219,50,51,0.2)',
|
||||
borderWidth: 12
|
||||
}
|
||||
},
|
||||
data: [220, 182, 125, 145, 122, 191, 134, 150, 120, 110, 165, 122]
|
||||
}]
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
266
src/components/Charts/mixchart.vue
Normal file
|
@ -0,0 +1,266 @@
|
|||
<template>
|
||||
<div :class="className" :id="id" :style="{height:height,width:width}"></div>
|
||||
</template>
|
||||
<script>
|
||||
// 引入 ECharts 主模块
|
||||
const echarts = require('echarts/lib/echarts');
|
||||
require('echarts/lib/chart/bar');
|
||||
require('echarts/lib/chart/line');
|
||||
// 引入提示框和标题组件
|
||||
require('echarts/lib/component/tooltip');
|
||||
require('echarts/lib/component/title');
|
||||
require('echarts/lib/component/legend');
|
||||
require('echarts/lib/component/dataZoom');
|
||||
export default {
|
||||
name: 'barPercent',
|
||||
props: {
|
||||
className: {
|
||||
type: String,
|
||||
default: 'chart'
|
||||
},
|
||||
id: {
|
||||
type: String,
|
||||
default: 'chart'
|
||||
},
|
||||
width: {
|
||||
type: String,
|
||||
default: '200px'
|
||||
},
|
||||
height: {
|
||||
type: String,
|
||||
default: '200px'
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {};
|
||||
},
|
||||
mounted() {
|
||||
this.initChart();
|
||||
},
|
||||
methods: {
|
||||
initChart() {
|
||||
this.chart = echarts.init(document.getElementById(this.id));
|
||||
const xData = (function() {
|
||||
const data = [];
|
||||
for (let i = 1; i < 13; i++) {
|
||||
data.push(i + '月份');
|
||||
}
|
||||
return data;
|
||||
}());
|
||||
this.chart.setOption({
|
||||
backgroundColor: '#344b58',
|
||||
title: {
|
||||
text: '统计',
|
||||
subtext: 'from http://gallery.echartsjs.com',
|
||||
x: '4%',
|
||||
textStyle: {
|
||||
color: '#fff',
|
||||
fontSize: '22'
|
||||
},
|
||||
subtextStyle: {
|
||||
color: '#90979c',
|
||||
fontSize: '16'
|
||||
}
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
textStyle: {
|
||||
color: '#fff'
|
||||
}
|
||||
}
|
||||
},
|
||||
grid: {
|
||||
borderWidth: 0,
|
||||
top: 110,
|
||||
bottom: 95,
|
||||
textStyle: {
|
||||
color: '#fff'
|
||||
}
|
||||
},
|
||||
legend: {
|
||||
x: '15%',
|
||||
top: '10%',
|
||||
textStyle: {
|
||||
color: '#90979c'
|
||||
},
|
||||
data: ['女', '男', '平均']
|
||||
},
|
||||
calculable: true,
|
||||
xAxis: [{
|
||||
type: 'category',
|
||||
axisLine: {
|
||||
lineStyle: {
|
||||
color: '#90979c'
|
||||
}
|
||||
},
|
||||
splitLine: {
|
||||
show: false
|
||||
},
|
||||
axisTick: {
|
||||
show: false
|
||||
},
|
||||
splitArea: {
|
||||
show: false
|
||||
},
|
||||
axisLabel: {
|
||||
interval: 0
|
||||
|
||||
},
|
||||
data: xData
|
||||
}],
|
||||
yAxis: [{
|
||||
type: 'value',
|
||||
splitLine: {
|
||||
show: false
|
||||
},
|
||||
axisLine: {
|
||||
lineStyle: {
|
||||
color: '#90979c'
|
||||
}
|
||||
},
|
||||
axisTick: {
|
||||
show: false
|
||||
},
|
||||
axisLabel: {
|
||||
interval: 0
|
||||
},
|
||||
splitArea: {
|
||||
show: false
|
||||
}
|
||||
}],
|
||||
dataZoom: [{
|
||||
show: true,
|
||||
height: 30,
|
||||
xAxisIndex: [
|
||||
0
|
||||
],
|
||||
bottom: 30,
|
||||
start: 10,
|
||||
end: 80,
|
||||
handleIcon: 'path://M306.1,413c0,2.2-1.8,4-4,4h-59.8c-2.2,0-4-1.8-4-4V200.8c0-2.2,1.8-4,4-4h59.8c2.2,0,4,1.8,4,4V413z',
|
||||
handleSize: '110%',
|
||||
handleStyle: {
|
||||
color: '#d3dee5'
|
||||
|
||||
},
|
||||
textStyle: {
|
||||
color: '#fff' },
|
||||
borderColor: '#90979c'
|
||||
|
||||
|
||||
}, {
|
||||
type: 'inside',
|
||||
show: true,
|
||||
height: 15,
|
||||
start: 1,
|
||||
end: 35
|
||||
}],
|
||||
series: [{
|
||||
name: '女',
|
||||
type: 'bar',
|
||||
stack: '总量',
|
||||
barMaxWidth: 35,
|
||||
barGap: '10%',
|
||||
itemStyle: {
|
||||
normal: {
|
||||
color: 'rgba(255,144,128,1)',
|
||||
label: {
|
||||
show: true,
|
||||
textStyle: {
|
||||
color: '#fff'
|
||||
},
|
||||
position: 'insideTop',
|
||||
formatter(p) {
|
||||
return p.value > 0 ? p.value : '';
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
data: [
|
||||
709,
|
||||
1917,
|
||||
2455,
|
||||
2610,
|
||||
1719,
|
||||
1433,
|
||||
1544,
|
||||
3285,
|
||||
5208,
|
||||
3372,
|
||||
2484,
|
||||
4078
|
||||
]
|
||||
},
|
||||
|
||||
{
|
||||
name: '男',
|
||||
type: 'bar',
|
||||
stack: '总量',
|
||||
itemStyle: {
|
||||
normal: {
|
||||
color: 'rgba(0,191,183,1)',
|
||||
barBorderRadius: 0,
|
||||
label: {
|
||||
show: true,
|
||||
position: 'top',
|
||||
formatter(p) {
|
||||
return p.value > 0 ? p.value : '';
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
data: [
|
||||
327,
|
||||
1776,
|
||||
507,
|
||||
1200,
|
||||
800,
|
||||
482,
|
||||
204,
|
||||
1390,
|
||||
1001,
|
||||
951,
|
||||
381,
|
||||
220
|
||||
]
|
||||
}, {
|
||||
name: '平均',
|
||||
type: 'line',
|
||||
stack: '总量',
|
||||
symbolSize: 10,
|
||||
symbol: 'circle',
|
||||
itemStyle: {
|
||||
normal: {
|
||||
color: 'rgba(252,230,48,1)',
|
||||
barBorderRadius: 0,
|
||||
label: {
|
||||
show: true,
|
||||
position: 'top',
|
||||
formatter(p) {
|
||||
return p.value > 0 ? p.value : '';
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
data: [
|
||||
1036,
|
||||
3693,
|
||||
2962,
|
||||
3810,
|
||||
2519,
|
||||
1915,
|
||||
1748,
|
||||
4675,
|
||||
6209,
|
||||
4323,
|
||||
2865,
|
||||
4298
|
||||
]
|
||||
}
|
||||
]
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
294
src/components/Dropzone/index.vue
Normal file
|
@ -0,0 +1,294 @@
|
|||
<template>
|
||||
<div :ref="id" :action="url" class="dropzone" :id="id">
|
||||
<input type="file" name="file">
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import Dropzone from 'dropzone';
|
||||
import 'dropzone/dist/dropzone.css';
|
||||
import { getToken } from 'api/qiniu';
|
||||
|
||||
Dropzone.autoDiscover = false;
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
dropzone: '',
|
||||
initOnce: true
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
const element = document.getElementById(this.id);
|
||||
const vm = this;
|
||||
this.dropzone = new Dropzone(element, {
|
||||
clickable: this.clickable,
|
||||
thumbnailWidth: this.thumbnailWidth,
|
||||
thumbnailHeight: this.thumbnailHeight,
|
||||
maxFiles: this.maxFiles,
|
||||
maxFilesize: this.maxFilesize,
|
||||
dictRemoveFile: 'Remove',
|
||||
addRemoveLinks: this.showRemoveLink,
|
||||
acceptedFiles: this.acceptedFiles,
|
||||
autoProcessQueue: this.autoProcessQueue,
|
||||
dictDefaultMessage: '<i style="margin-top: 3em;display: inline-block" class="material-icons">' + this.defaultMsg + '</i><br>Drop files here to upload',
|
||||
dictMaxFilesExceeded: '只能一个图',
|
||||
previewTemplate: '<div class="dz-preview dz-file-preview"> <div class="dz-image" style="width:' + this.thumbnailWidth + 'px;height:' + this.thumbnailHeight + 'px" ><img style="width:' + this.thumbnailWidth + 'px;height:' + this.thumbnailHeight + 'px" data-dz-thumbnail /></div> <div class="dz-details"><div class="dz-size"><span data-dz-size></span></div> <div class="dz-progress"><span class="dz-upload" data-dz-uploadprogress></span></div> <div class="dz-error-message"><span data-dz-errormessage></span></div> <div class="dz-success-mark"> <i class="material-icons">done</i> </div> <div class="dz-error-mark"><i class="material-icons">error</i></div></div>',
|
||||
init() {
|
||||
const val = vm.defaultImg;
|
||||
if (!val) return;
|
||||
if (Array.isArray(val)) {
|
||||
if (val.length === 0) return;
|
||||
val.map((v, i) => {
|
||||
const mockFile = { name: 'name' + i, size: 12345, url: v };
|
||||
this.options.addedfile.call(this, mockFile);
|
||||
this.options.thumbnail.call(this, mockFile, v);
|
||||
mockFile.previewElement.classList.add('dz-success');
|
||||
mockFile.previewElement.classList.add('dz-complete');
|
||||
vm.initOnce = false;
|
||||
return true;
|
||||
})
|
||||
} else {
|
||||
const mockFile = { name: 'name', size: 12345, url: val };
|
||||
this.options.addedfile.call(this, mockFile);
|
||||
this.options.thumbnail.call(this, mockFile, val);
|
||||
mockFile.previewElement.classList.add('dz-success');
|
||||
mockFile.previewElement.classList.add('dz-complete');
|
||||
vm.initOnce = false;
|
||||
}
|
||||
},
|
||||
accept: (file, done) => {
|
||||
/* 七牛*/
|
||||
// const token = this.$store.getters.token;
|
||||
// getToken(token).then(response => {
|
||||
// file.token = response.data.qiniu_token;
|
||||
// file.key = response.data.qiniu_key;
|
||||
// file.url = response.data.qiniu_url;
|
||||
// done();
|
||||
// })
|
||||
done();
|
||||
},
|
||||
sending: (file, xhr, formData) => {
|
||||
/* 七牛*/
|
||||
// formData.append('token', file.token);
|
||||
// formData.append('key', file.key);
|
||||
vm.initOnce = false;
|
||||
}
|
||||
});
|
||||
|
||||
if (this.couldPaste) {
|
||||
document.addEventListener('paste', this.pasteImg)
|
||||
}
|
||||
|
||||
this.dropzone.on('success', file => {
|
||||
vm.$emit('dropzone-success', file, vm.dropzone.element)
|
||||
});
|
||||
this.dropzone.on('addedfile', file => {
|
||||
vm.$emit('dropzone-fileAdded', file)
|
||||
});
|
||||
this.dropzone.on('removedfile', file => {
|
||||
vm.$emit('dropzone-removedFile', file)
|
||||
});
|
||||
this.dropzone.on('error', (file, error, xhr) => {
|
||||
vm.$emit('dropzone-error', file, error, xhr)
|
||||
});
|
||||
this.dropzone.on('successmultiple', (file, error, xhr) => {
|
||||
vm.$emit('dropzone-successmultiple', file, error, xhr)
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
removeAllFiles() {
|
||||
this.dropzone.removeAllFiles(true)
|
||||
},
|
||||
processQueue() {
|
||||
this.dropzone.processQueue()
|
||||
},
|
||||
pasteImg(event) {
|
||||
const items = (event.clipboardData || event.originalEvent.clipboardData).items;
|
||||
if (items[0].kind === 'file') {
|
||||
this.dropzone.addFile(items[0].getAsFile())
|
||||
}
|
||||
},
|
||||
initImages(val) {
|
||||
if (!val) return;
|
||||
if (Array.isArray(val)) {
|
||||
val.map((v, i) => {
|
||||
const mockFile = { name: 'name' + i, size: 12345, url: v };
|
||||
this.dropzone.options.addedfile.call(this.dropzone, mockFile);
|
||||
this.dropzone.options.thumbnail.call(this.dropzone, mockFile, v);
|
||||
mockFile.previewElement.classList.add('dz-success');
|
||||
mockFile.previewElement.classList.add('dz-complete');
|
||||
return true
|
||||
})
|
||||
} else {
|
||||
const mockFile = { name: 'name', size: 12345, url: val };
|
||||
this.dropzone.options.addedfile.call(this.dropzone, mockFile);
|
||||
this.dropzone.options.thumbnail.call(this.dropzone, mockFile, val);
|
||||
mockFile.previewElement.classList.add('dz-success');
|
||||
mockFile.previewElement.classList.add('dz-complete');
|
||||
}
|
||||
}
|
||||
|
||||
},
|
||||
destroyed() {
|
||||
document.removeEventListener('paste', this.pasteImg);
|
||||
this.dropzone.destroy();
|
||||
},
|
||||
watch: {
|
||||
defaultImg(val) {
|
||||
if (val.length === 0) {
|
||||
this.initOnce = false;
|
||||
return;
|
||||
}
|
||||
if (!this.initOnce) return;
|
||||
this.initImages(val);
|
||||
this.initOnce = false;
|
||||
}
|
||||
},
|
||||
props: {
|
||||
id: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
url: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
clickable: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
defaultMsg: {
|
||||
type: String,
|
||||
default: '上传图片'
|
||||
},
|
||||
acceptedFiles: {
|
||||
type: String
|
||||
},
|
||||
thumbnailHeight: {
|
||||
type: Number,
|
||||
default: 200
|
||||
},
|
||||
thumbnailWidth: {
|
||||
type: Number,
|
||||
default: 200
|
||||
},
|
||||
showRemoveLink: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
maxFilesize: {
|
||||
type: Number,
|
||||
default: 2
|
||||
},
|
||||
maxFiles: {
|
||||
type: Number,
|
||||
default: 3
|
||||
},
|
||||
autoProcessQueue: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
useCustomDropzoneOptions: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
defaultImg: {
|
||||
default: false
|
||||
},
|
||||
couldPaste: {
|
||||
default: false
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.dropzone {
|
||||
border: 2px solid #E5E5E5;
|
||||
font-family: 'Roboto', sans-serif;
|
||||
color: #777;
|
||||
transition: background-color .2s linear;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.dropzone:hover {
|
||||
background-color: #F6F6F6;
|
||||
}
|
||||
|
||||
i {
|
||||
color: #CCC;
|
||||
}
|
||||
|
||||
.dropzone .dz-image img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.dropzone input[name='file'] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.dropzone .dz-preview .dz-image {
|
||||
border-radius: 0px;
|
||||
}
|
||||
|
||||
.dropzone .dz-preview:hover .dz-image img {
|
||||
transform: none;
|
||||
-webkit-filter: none;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.dropzone .dz-preview .dz-details {
|
||||
bottom: 0px;
|
||||
top: 0px;
|
||||
color: white;
|
||||
background-color: rgba(33, 150, 243, 0.8);
|
||||
transition: opacity .2s linear;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.dropzone .dz-preview .dz-details .dz-filename span, .dropzone .dz-preview .dz-details .dz-size span {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.dropzone .dz-preview .dz-details .dz-filename:not(:hover) span {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.dropzone .dz-preview .dz-details .dz-filename:hover span {
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.dropzone .dz-preview .dz-remove {
|
||||
position: absolute;
|
||||
z-index: 30;
|
||||
color: white;
|
||||
margin-left: 15px;
|
||||
padding: 10px;
|
||||
top: inherit;
|
||||
bottom: 15px;
|
||||
border: 2px white solid;
|
||||
text-decoration: none;
|
||||
text-transform: uppercase;
|
||||
font-size: 0.8rem;
|
||||
font-weight: 800;
|
||||
letter-spacing: 1.1px;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.dropzone .dz-preview:hover .dz-remove {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.dropzone .dz-preview .dz-success-mark, .dropzone .dz-preview .dz-error-mark {
|
||||
margin-left: -40px;
|
||||
margin-top: -50px;
|
||||
}
|
||||
|
||||
.dropzone .dz-preview .dz-success-mark i, .dropzone .dz-preview .dz-error-mark i {
|
||||
color: white;
|
||||
font-size: 5rem;
|
||||
}
|
||||
</style>
|
51
src/components/ErrLog/index.vue
Normal file
|
@ -0,0 +1,51 @@
|
|||
<template>
|
||||
<div>
|
||||
<el-badge :is-dot="true" style="line-height: 30px;" @click.native="dialogTableVisible=true">
|
||||
<el-button size="small" type="primary">
|
||||
<svg t="1492682037685" class="bug-svg" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1863" xmlns:xlink="http://www.w3.org/1999/xlink" width="64" height="64"><path d="M969.142857 548.571429q0 14.848-10.861714 25.709714t-25.709714 10.861714l-128 0q0 97.718857-38.290286 165.705143l118.857143 119.442286q10.861714 10.861714 10.861714 25.709714t-10.861714 25.709714q-10.276571 10.861714-25.709714 10.861714t-25.709714-10.861714l-113.152-112.566857q-2.852571 2.852571-8.557714 7.424t-23.990857 16.274286-37.156571 20.845714-46.848 16.566857-55.442286 7.424l0-512-73.142857 0 0 512q-29.147429 0-58.002286-7.716571t-49.700571-18.870857-37.705143-22.272-24.868571-18.578286l-8.557714-8.009143-104.557714 118.272q-11.446857 11.995429-27.428571 11.995429-13.714286 0-24.576-9.142857-10.861714-10.276571-11.702857-25.417143t8.850286-26.587429l115.419429-129.718857q-33.133714-65.133714-33.133714-156.562286l-128 0q-14.848 0-25.709714-10.861714t-10.861714-25.709714 10.861714-25.709714 25.709714-10.861714l128 0 0-168.009143-98.852571-98.852571q-10.861714-10.861714-10.861714-25.709714t10.861714-25.709714 25.709714-10.861714 25.709714 10.861714l98.852571 98.852571 482.304 0 98.852571-98.852571q10.861714-10.861714 25.709714-10.861714t25.709714 10.861714 10.861714 25.709714-10.861714 25.709714l-98.852571 98.852571 0 168.009143 128 0q14.848 0 25.709714 10.861714t10.861714 25.709714zM694.857143 219.428571l-365.714286 0q0-75.995429 53.430857-129.426286t129.426286-53.430857 129.426286 53.430857 53.430857 129.426286z" p-id="1864"></path></svg>
|
||||
</el-button>
|
||||
</el-badge>
|
||||
<el-dialog title="bug日志" v-model="dialogTableVisible">
|
||||
<el-table :data="logsList">
|
||||
<el-table-column label="message">
|
||||
<template scope="scope">
|
||||
<div>msg:{{ scope.row.err.message }}</div>
|
||||
<br/>
|
||||
<div>url: {{scope.row.url}}</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="stack">
|
||||
<template scope="scope">
|
||||
{{ scope.row.err.stack}}
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
</el-table>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'errLog',
|
||||
props: {
|
||||
logsList: {
|
||||
type: Array
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
dialogTableVisible: false
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style scoped>
|
||||
.bug-svg {
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
vertical-align: -0.15em;
|
||||
fill: currentColor;
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
36
src/components/Hamburger/index.vue
Normal file
|
@ -0,0 +1,36 @@
|
|||
<template>
|
||||
<div>
|
||||
<svg t="1492500959545" @click="toggleClick" class="wscn-icon hamburger" :class="{'is-active':isActive}" style="" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1691" xmlns:xlink="http://www.w3.org/1999/xlink" width="64" height="64"><path d="M966.8023 568.849776 57.196677 568.849776c-31.397081 0-56.850799-25.452695-56.850799-56.850799l0 0c0-31.397081 25.452695-56.849776 56.850799-56.849776l909.605623 0c31.397081 0 56.849776 25.452695 56.849776 56.849776l0 0C1023.653099 543.397081 998.200404 568.849776 966.8023 568.849776z" p-id="1692"></path><path d="M966.8023 881.527125 57.196677 881.527125c-31.397081 0-56.850799-25.452695-56.850799-56.849776l0 0c0-31.397081 25.452695-56.849776 56.850799-56.849776l909.605623 0c31.397081 0 56.849776 25.452695 56.849776 56.849776l0 0C1023.653099 856.07443 998.200404 881.527125 966.8023 881.527125z" p-id="1693"></path><path d="M966.8023 256.17345 57.196677 256.17345c-31.397081 0-56.850799-25.452695-56.850799-56.849776l0 0c0-31.397081 25.452695-56.850799 56.850799-56.850799l909.605623 0c31.397081 0 56.849776 25.452695 56.849776 56.850799l0 0C1023.653099 230.720755 998.200404 256.17345 966.8023 256.17345z" p-id="1694"></path></svg>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'hamburger',
|
||||
props: {
|
||||
isActive: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
toggleClick: {
|
||||
type: Function,
|
||||
default: null
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.hamburger {
|
||||
display: inline-block;
|
||||
cursor: pointer;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
transform: rotate(0deg);
|
||||
transition: .38s;
|
||||
transform-origin: 50% 50%;
|
||||
}
|
||||
.hamburger.is-active {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
</style>
|
11
src/components/Icon-svg/index.js
Normal file
|
@ -0,0 +1,11 @@
|
|||
import Vue from 'vue'
|
||||
|
||||
function registerAllComponents(requireContext) {
|
||||
return requireContext.keys().forEach(comp => {
|
||||
const vueComp = requireContext(comp)
|
||||
const compName = vueComp.name ? vueComp.name.toLowerCase() : /\/([\w-]+)\.vue$/.exec(comp)[1]
|
||||
Vue.component(compName, vueComp)
|
||||
})
|
||||
}
|
||||
|
||||
registerAllComponents(require.context('./', false, /\.vue$/))
|
52
src/components/Icon-svg/wscn-icon-stack.vue
Normal file
|
@ -0,0 +1,52 @@
|
|||
<template>
|
||||
<div class="icon-container" :style="containerStyle">
|
||||
<slot class="icon"></slot>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'wscn-icon-stack',
|
||||
props: {
|
||||
width: {
|
||||
type: Number,
|
||||
default: 20
|
||||
},
|
||||
shape: {
|
||||
type: String,
|
||||
default: 'circle',
|
||||
validator: val => {
|
||||
const validShapes = ['circle', 'square']
|
||||
return validShapes.indexOf(val) > -1
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
containerStyle() {
|
||||
return {
|
||||
width: `${this.width}px`,
|
||||
height: `${this.width}px`,
|
||||
fontSize: `${this.width * 0.6}px`,
|
||||
borderRadius: `${this.shape === 'circle' && '50%'}`
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.icon-container {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
background: #1482F0;
|
||||
|
||||
.icon {
|
||||
position: absolute;
|
||||
color: #ffffff;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
}
|
||||
</style>
|
26
src/components/Icon-svg/wscn-icon-svg.vue
Normal file
|
@ -0,0 +1,26 @@
|
|||
<template>
|
||||
<svg class="wscn-icon" aria-hidden="true">
|
||||
<use :xlink:href="iconName"></use>
|
||||
</svg>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'wscn-icon-svg',
|
||||
props: {
|
||||
iconClass: {
|
||||
type: String,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
iconName() {
|
||||
return `#icon-${this.iconClass}`
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
</style>
|
716
src/components/ImageCropper/index.vue
Normal file
|
@ -0,0 +1,716 @@
|
|||
<template>
|
||||
<div class="vue-image-crop-upload" v-show="show">
|
||||
<div class="vicp-wrap">
|
||||
<div class="vicp-close" @click="off">
|
||||
<i class="vicp-icon4"></i>
|
||||
</div>
|
||||
|
||||
<div class="vicp-step1" v-show="step == 1">
|
||||
<div class="vicp-drop-area"
|
||||
@dragleave="preventDefault"
|
||||
@dragover="preventDefault"
|
||||
@dragenter="preventDefault"
|
||||
@click="handleClick"
|
||||
@drop="handleChange">
|
||||
<i class="vicp-icon1" v-show="loading != 1">
|
||||
<i class="vicp-icon1-arrow"></i>
|
||||
<i class="vicp-icon1-body"></i>
|
||||
<i class="vicp-icon1-bottom"></i>
|
||||
</i>
|
||||
<span class="vicp-hint" v-show="loading !== 1">{{ lang.hint }}</span>
|
||||
<span class="vicp-no-supported-hint" v-show="!isSupported">{{ lang.noSupported }}</span>
|
||||
<input type="file" v-show="false" @change="handleChange" ref="fileinput">
|
||||
</div>
|
||||
<div class="vicp-error" v-show="hasError">
|
||||
<i class="vicp-icon2"></i> {{ errorMsg }}
|
||||
</div>
|
||||
<div class="vicp-operate">
|
||||
<a @click="off" @mousedown="ripple">{{ lang.btn.off }}</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="vicp-step2" v-if="step == 2">
|
||||
<div class="vicp-crop">
|
||||
<div class="vicp-crop-left" v-show="true">
|
||||
<div class="vicp-img-container">
|
||||
<img :src="sourceImgUrl"
|
||||
:style="sourceImgStyle"
|
||||
class="vicp-img"
|
||||
draggable="false"
|
||||
@drag="preventDefault"
|
||||
@dragstart="preventDefault"
|
||||
@dragend="preventDefault"
|
||||
@dragleave="preventDefault"
|
||||
@dragover="preventDefault"
|
||||
@dragenter="preventDefault"
|
||||
@drop="preventDefault"
|
||||
@mousedown="imgStartMove"
|
||||
@mousemove="imgMove"
|
||||
@mouseup="createImg"
|
||||
@mouseout="createImg"
|
||||
ref="img">
|
||||
<div class="vicp-img-shade vicp-img-shade-1" :style="sourceImgShadeStyle"></div>
|
||||
<div class="vicp-img-shade vicp-img-shade-2" :style="sourceImgShadeStyle"></div>
|
||||
</div>
|
||||
<div class="vicp-range">
|
||||
<input type="range" :value="scale.range" step="1" min="0" max="100" @change="zoomChange">
|
||||
<i @mousedown="startZoomSub" @mouseout="endZoomSub" @mouseup="endZoomSub"
|
||||
class="vicp-icon5"></i>
|
||||
<i @mousedown="startZoomAdd" @mouseout="endZoomAdd" @mouseup="endZoomAdd"
|
||||
class="vicp-icon6"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="vicp-crop-right" v-show="true">
|
||||
<div class="vicp-preview">
|
||||
<div class="vicp-preview-item">
|
||||
<img :src="createImgUrl" :style="previewStyle">
|
||||
<span>{{ lang.preview }}</span>
|
||||
</div>
|
||||
<div class="vicp-preview-item">
|
||||
<img :src="createImgUrl" :style="previewStyle" v-if="!noCircle">
|
||||
<span>{{ lang.preview }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="vicp-operate">
|
||||
<a @click="setStep(1)" @mousedown="ripple">{{ lang.btn.back }}</a>
|
||||
<a class="vicp-operate-btn" @click="upload" @mousedown="ripple">{{ lang.btn.save }}</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="vicp-step3" v-if="step == 3">
|
||||
<div class="vicp-upload">
|
||||
<span class="vicp-loading" v-show="loading === 1">{{ lang.loading }}</span>
|
||||
<div class="vicp-progress-wrap">
|
||||
<span class="vicp-progress" v-show="loading === 1" :style="progressStyle"></span>
|
||||
</div>
|
||||
<div class="vicp-error" v-show="hasError">
|
||||
<i class="vicp-icon2"></i> {{ errorMsg }}
|
||||
</div>
|
||||
<div class="vicp-success" v-show="loading === 2">
|
||||
<i class="vicp-icon3"></i> {{ lang.success }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="vicp-operate">
|
||||
<a @click="setStep(2)" @mousedown="ripple">{{ lang.btn.back }}</a>
|
||||
<a @click="off" @mousedown="ripple">{{ lang.btn.close }}</a>
|
||||
</div>
|
||||
</div>
|
||||
<canvas v-show="false" :width="width" :height="height" ref="canvas"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/* eslint-disable */
|
||||
import {effectRipple, data2blob} from './utils';
|
||||
import langBag from './lang';
|
||||
const mimes = {
|
||||
'jpg': 'image/jpeg',
|
||||
'png': 'image/png',
|
||||
'gif': 'image/gif',
|
||||
'svg': 'image/svg+xml',
|
||||
'psd': 'image/photoshop'
|
||||
};
|
||||
|
||||
export default {
|
||||
props: {
|
||||
// 域,上传文件name,触发事件会带上(如果一个页面多个图片上传控件,可以做区分
|
||||
field: {
|
||||
type: String,
|
||||
default: 'avatar'
|
||||
},
|
||||
// 上传地址
|
||||
url: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 其他要上传文件附带的数据,对象格式
|
||||
params: {
|
||||
type: Object,
|
||||
default: null
|
||||
},
|
||||
// 剪裁图片的宽
|
||||
width: {
|
||||
type: Number,
|
||||
default: 200
|
||||
},
|
||||
// 剪裁图片的高
|
||||
height: {
|
||||
type: Number,
|
||||
default: 200
|
||||
},
|
||||
// 不预览圆形图片
|
||||
noCircle: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 单文件大小限制
|
||||
maxSize: {
|
||||
type: Number,
|
||||
default: 10240
|
||||
},
|
||||
// 语言类型
|
||||
langType: {
|
||||
type: String,
|
||||
'default': 'zh'
|
||||
},
|
||||
|
||||
},
|
||||
data() {
|
||||
let that = this,
|
||||
{
|
||||
langType,
|
||||
width,
|
||||
height
|
||||
} = that,
|
||||
isSupported = true,
|
||||
lang = langBag[langType] ? langBag[langType] : lang['zh'];
|
||||
|
||||
if (typeof FormData != 'function') {
|
||||
isSupported = false;
|
||||
}
|
||||
return {
|
||||
show: true,
|
||||
// 图片的mime
|
||||
mime:mimes['jpg'],
|
||||
// 语言包
|
||||
lang,
|
||||
// 浏览器是否支持该控件
|
||||
isSupported,
|
||||
// 步骤
|
||||
step: 1, //1选择文件 2剪裁 3上传
|
||||
// 上传状态及进度
|
||||
loading: 0, //0未开始 1正在 2成功 3错误
|
||||
progress: 0,
|
||||
// 是否有错误及错误信息
|
||||
hasError: false,
|
||||
errorMsg: '',
|
||||
// 需求图宽高比
|
||||
ratio: width / height,
|
||||
// 原图地址、生成图片地址
|
||||
sourceImg: null,
|
||||
sourceImgUrl: '',
|
||||
createImgUrl: '',
|
||||
// 原图片拖动事件初始值
|
||||
sourceImgMouseDown: {
|
||||
on: false,
|
||||
mX: 0, //鼠标按下的坐标
|
||||
mY: 0,
|
||||
x: 0, //scale原图坐标
|
||||
y: 0
|
||||
},
|
||||
// 生成图片预览的容器大小
|
||||
previewContainer: {
|
||||
width: 100,
|
||||
height: 100
|
||||
},
|
||||
// 原图容器宽高
|
||||
sourceImgContainer: { // sic
|
||||
width: 240,
|
||||
height: 180
|
||||
},
|
||||
// 原图展示属性
|
||||
scale: {
|
||||
zoomAddOn: false, //按钮缩放事件开启
|
||||
zoomSubOn: false, //按钮缩放事件开启
|
||||
range: 1, //最大100
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: 0,
|
||||
height: 0,
|
||||
maxWidth: 0,
|
||||
maxHeight: 0,
|
||||
minWidth: 0, //最宽
|
||||
minHeight: 0,
|
||||
naturalWidth: 0, //原宽
|
||||
naturalHeight: 0
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 进度条样式
|
||||
progressStyle() {
|
||||
let {
|
||||
progress
|
||||
} = this;
|
||||
return {
|
||||
width: progress + '%'
|
||||
}
|
||||
},
|
||||
// 原图样式
|
||||
sourceImgStyle() {
|
||||
let {
|
||||
scale,
|
||||
sourceImgMasking
|
||||
} = this;
|
||||
return {
|
||||
top: scale.y + sourceImgMasking.y + 'px',
|
||||
left: scale.x + sourceImgMasking.x + 'px',
|
||||
width: scale.width + 'px',
|
||||
height: scale.height + 'px'
|
||||
}
|
||||
},
|
||||
// 原图蒙版属性
|
||||
sourceImgMasking() {
|
||||
let {
|
||||
width,
|
||||
height,
|
||||
ratio,
|
||||
sourceImgContainer
|
||||
} = this,
|
||||
sic = sourceImgContainer,
|
||||
sicRatio = sic.width / sic.height, // 原图容器宽高比
|
||||
x = 0,
|
||||
y = 0,
|
||||
w = sic.width,
|
||||
h = sic.height,
|
||||
scale = 1;
|
||||
if (ratio < sicRatio) {
|
||||
scale = sic.height / height;
|
||||
w = sic.height * ratio;
|
||||
x = (sic.width - w) / 2;
|
||||
}
|
||||
if (ratio > sicRatio) {
|
||||
scale = sic.width / width;
|
||||
h = sic.width / ratio;
|
||||
y = (sic.height - h) / 2;
|
||||
}
|
||||
return {
|
||||
scale, // 蒙版相对需求宽高的缩放
|
||||
x,
|
||||
y,
|
||||
width: w,
|
||||
height: h
|
||||
};
|
||||
},
|
||||
// 原图遮罩样式
|
||||
sourceImgShadeStyle() {
|
||||
let sic = this.sourceImgContainer,
|
||||
sim = this.sourceImgMasking,
|
||||
w = sim.width == sic.width ? sim.width : (sic.width - sim.width) / 2,
|
||||
h = sim.height == sic.height ? sim.height : (sic.height - sim.height) / 2;
|
||||
return {
|
||||
width: w + 'px',
|
||||
height: h + 'px'
|
||||
};
|
||||
},
|
||||
previewStyle() {
|
||||
let {
|
||||
width,
|
||||
height,
|
||||
ratio,
|
||||
previewContainer
|
||||
} = this,
|
||||
pc = previewContainer,
|
||||
w = pc.width,
|
||||
h = pc.height,
|
||||
pcRatio = w / h;
|
||||
if (ratio < pcRatio) {
|
||||
w = pc.height * ratio;
|
||||
}
|
||||
if (ratio > pcRatio) {
|
||||
h = pc.width / ratio;
|
||||
}
|
||||
return {
|
||||
width: w + 'px',
|
||||
height: h + 'px'
|
||||
};
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 点击波纹效果
|
||||
ripple(e) {
|
||||
effectRipple(e);
|
||||
},
|
||||
// 关闭控件
|
||||
off() {
|
||||
this.show = false;
|
||||
},
|
||||
// 设置步骤
|
||||
setStep(step) {
|
||||
let that = this;
|
||||
setTimeout(function () {
|
||||
that.step = step;
|
||||
}, 200);
|
||||
},
|
||||
/* 图片选择区域函数绑定
|
||||
---------------------------------------------------------------*/
|
||||
preventDefault(e) {
|
||||
e.preventDefault();
|
||||
return false;
|
||||
},
|
||||
handleClick(e) {
|
||||
if (this.loading !== 1) {
|
||||
if (e.target !== this.$refs.fileinput) {
|
||||
e.preventDefault();
|
||||
if (document.activeElement !== this.$refs) {
|
||||
this.$refs.fileinput.click();
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
handleChange(e) {
|
||||
e.preventDefault();
|
||||
if (this.loading !== 1) {
|
||||
let files = e.target.files || e.dataTransfer.files;
|
||||
this.reset();
|
||||
if (this.checkFile(files[0])) {
|
||||
this.setSourceImg(files[0]);
|
||||
}
|
||||
}
|
||||
},
|
||||
/* ---------------------------------------------------------------*/
|
||||
// 检测选择的文件是否合适
|
||||
checkFile(file) {
|
||||
let that = this,
|
||||
{
|
||||
lang,
|
||||
maxSize
|
||||
} = that;
|
||||
// 仅限图片
|
||||
if (file.type.indexOf('image') === -1) {
|
||||
that.hasError = true;
|
||||
that.errorMsg = lang.error.onlyImg;
|
||||
return false;
|
||||
}
|
||||
this.mime=file.type;
|
||||
// 超出大小
|
||||
if (file.size / 1024 > maxSize) {
|
||||
that.hasError = true;
|
||||
that.errorMsg = lang.error.outOfSize + maxSize + 'kb';
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
// 重置控件
|
||||
reset() {
|
||||
let that = this;
|
||||
that.step = 1;
|
||||
that.loading = 0;
|
||||
that.hasError = false;
|
||||
that.errorMsg = '';
|
||||
that.progress = 0;
|
||||
},
|
||||
// 设置图片源
|
||||
setSourceImg(file) {
|
||||
let that = this,
|
||||
fr = new FileReader();
|
||||
fr.onload = function (e) {
|
||||
that.sourceImgUrl = fr.result;
|
||||
that.startCrop();
|
||||
};
|
||||
fr.readAsDataURL(file);
|
||||
},
|
||||
// 剪裁前准备工作
|
||||
startCrop() {
|
||||
let that = this,
|
||||
{
|
||||
width,
|
||||
height,
|
||||
ratio,
|
||||
scale,
|
||||
sourceImgUrl,
|
||||
sourceImgMasking,
|
||||
lang
|
||||
} = that,
|
||||
sim = sourceImgMasking,
|
||||
img = new Image();
|
||||
img.src = sourceImgUrl;
|
||||
img.onload = function () {
|
||||
let nWidth = img.naturalWidth,
|
||||
nHeight = img.naturalHeight,
|
||||
nRatio = nWidth / nHeight,
|
||||
w = sim.width,
|
||||
h = sim.height,
|
||||
x = 0,
|
||||
y = 0;
|
||||
// 图片像素不达标
|
||||
// if (nWidth < width || nHeight < height) {
|
||||
// that.hasError = true;
|
||||
// that.errorMsg = lang.error.lowestPx + width + '*' + height;
|
||||
// return false;
|
||||
// }
|
||||
if (ratio > nRatio) {
|
||||
h = w / nRatio;
|
||||
y = (sim.height - h) / 2;
|
||||
}
|
||||
if (ratio < nRatio) {
|
||||
w = h * nRatio;
|
||||
x = (sim.width - w) / 2;
|
||||
}
|
||||
scale.range = 0;
|
||||
scale.x = x;
|
||||
scale.y = y;
|
||||
scale.width = w;
|
||||
scale.height = h;
|
||||
scale.minWidth = w;
|
||||
scale.minHeight = h;
|
||||
scale.maxWidth = nWidth * sim.scale;
|
||||
scale.maxHeight = nHeight * sim.scale;
|
||||
scale.naturalWidth = nWidth;
|
||||
scale.naturalHeight = nHeight;
|
||||
that.sourceImg = img;
|
||||
that.createImg();
|
||||
that.setStep(2);
|
||||
};
|
||||
},
|
||||
// 鼠标按下图片准备移动
|
||||
imgStartMove(e) {
|
||||
let {
|
||||
sourceImgMouseDown,
|
||||
scale
|
||||
} = this,
|
||||
simd = sourceImgMouseDown;
|
||||
simd.mX = e.screenX;
|
||||
simd.mY = e.screenY;
|
||||
simd.x = scale.x;
|
||||
simd.y = scale.y;
|
||||
simd.on = true;
|
||||
},
|
||||
// 鼠标按下状态下移动,图片移动
|
||||
imgMove(e) {
|
||||
let {
|
||||
sourceImgMouseDown: {
|
||||
on,
|
||||
mX,
|
||||
mY,
|
||||
x,
|
||||
y
|
||||
},
|
||||
scale,
|
||||
sourceImgMasking
|
||||
} = this,
|
||||
sim = sourceImgMasking,
|
||||
nX = e.screenX,
|
||||
nY = e.screenY,
|
||||
dX = nX - mX,
|
||||
dY = nY - mY,
|
||||
rX = x + dX,
|
||||
rY = y + dY;
|
||||
if (!on) return;
|
||||
if (rX > 0) {
|
||||
rX = 0;
|
||||
}
|
||||
if (rY > 0) {
|
||||
rY = 0;
|
||||
}
|
||||
if (rX < sim.width - scale.width) {
|
||||
rX = sim.width - scale.width;
|
||||
}
|
||||
if (rY < sim.height - scale.height) {
|
||||
rY = sim.height - scale.height;
|
||||
}
|
||||
scale.x = rX;
|
||||
scale.y = rY;
|
||||
},
|
||||
// 按钮按下开始放大
|
||||
startZoomAdd(e) {
|
||||
let that = this,
|
||||
{
|
||||
scale
|
||||
} = that;
|
||||
scale.zoomAddOn = true;
|
||||
function zoom() {
|
||||
if (scale.zoomAddOn) {
|
||||
let range = scale.range >= 100 ? 100 : ++scale.range;
|
||||
that.zoomImg(range);
|
||||
setTimeout(function () {
|
||||
zoom();
|
||||
}, 60);
|
||||
}
|
||||
}
|
||||
|
||||
zoom();
|
||||
},
|
||||
// 按钮松开或移开取消放大
|
||||
endZoomAdd(e) {
|
||||
this.scale.zoomAddOn = false;
|
||||
},
|
||||
// 按钮按下开始缩小
|
||||
startZoomSub(e) {
|
||||
let that = this,
|
||||
{
|
||||
scale
|
||||
} = that;
|
||||
scale.zoomSubOn = true;
|
||||
function zoom() {
|
||||
if (scale.zoomSubOn) {
|
||||
let range = scale.range <= 0 ? 0 : --scale.range;
|
||||
that.zoomImg(range);
|
||||
setTimeout(function () {
|
||||
zoom();
|
||||
}, 60);
|
||||
}
|
||||
}
|
||||
|
||||
zoom();
|
||||
},
|
||||
// 按钮松开或移开取消缩小
|
||||
endZoomSub(e) {
|
||||
let {
|
||||
scale
|
||||
} = this;
|
||||
scale.zoomSubOn = false;
|
||||
},
|
||||
zoomChange(e) {
|
||||
this.zoomImg(e.target.value);
|
||||
},
|
||||
// 缩放原图
|
||||
zoomImg(newRange) {
|
||||
let that = this,
|
||||
{
|
||||
sourceImgMasking,
|
||||
sourceImgMouseDown,
|
||||
scale
|
||||
} = this,
|
||||
{
|
||||
maxWidth,
|
||||
maxHeight,
|
||||
minWidth,
|
||||
minHeight,
|
||||
width,
|
||||
height,
|
||||
x,
|
||||
y,
|
||||
range
|
||||
} = scale,
|
||||
sim = sourceImgMasking,
|
||||
// 蒙版宽高
|
||||
sWidth = sim.width,
|
||||
sHeight = sim.height,
|
||||
// 新宽高
|
||||
nWidth = minWidth + (maxWidth - minWidth) * newRange / 100,
|
||||
nHeight = minHeight + (maxHeight - minHeight) * newRange / 100,
|
||||
// 新坐标(根据蒙版中心点缩放)
|
||||
nX = sWidth / 2 - (nWidth / width) * (sWidth / 2 - x),
|
||||
nY = sHeight / 2 - (nHeight / height) * (sHeight / 2 - y);
|
||||
// 判断新坐标是否超过蒙版限制
|
||||
if (nX > 0) {
|
||||
nX = 0;
|
||||
}
|
||||
if (nY > 0) {
|
||||
nY = 0;
|
||||
}
|
||||
if (nX < sWidth - nWidth) {
|
||||
nX = sWidth - nWidth;
|
||||
}
|
||||
if (nY < sHeight - nHeight) {
|
||||
nY = sHeight - nHeight;
|
||||
}
|
||||
// 赋值处理
|
||||
scale.x = nX;
|
||||
scale.y = nY;
|
||||
scale.width = nWidth;
|
||||
scale.height = nHeight;
|
||||
scale.range = newRange;
|
||||
setTimeout(function () {
|
||||
if (scale.range == newRange) {
|
||||
that.createImg();
|
||||
}
|
||||
}, 300);
|
||||
},
|
||||
// 生成需求图片
|
||||
createImg(e) {
|
||||
let that = this,
|
||||
{
|
||||
mime,
|
||||
sourceImg,
|
||||
scale: {
|
||||
x,
|
||||
y,
|
||||
width,
|
||||
height
|
||||
},
|
||||
sourceImgMasking: {
|
||||
scale
|
||||
}
|
||||
} = that,
|
||||
canvas = that.$refs.canvas,
|
||||
ctx = canvas.getContext('2d');
|
||||
if (e) {
|
||||
// 取消鼠标按下移动状态
|
||||
that.sourceImgMouseDown.on = false;
|
||||
}
|
||||
ctx.drawImage(sourceImg, x / scale, y / scale, width / scale, height / scale);
|
||||
that.createImgUrl = canvas.toDataURL(mime);
|
||||
},
|
||||
// 上传图片
|
||||
upload() {
|
||||
let that = this,
|
||||
{
|
||||
lang,
|
||||
imgFormat,
|
||||
mime,
|
||||
url,
|
||||
params,
|
||||
headers,
|
||||
field,
|
||||
ki,
|
||||
createImgUrl
|
||||
} = this,
|
||||
fmData = new FormData();
|
||||
fmData.append(field, data2blob(createImgUrl, mime), field + '.' + imgFormat);
|
||||
// 添加其他参数
|
||||
if (typeof params == 'object' && params) {
|
||||
Object.keys(params).forEach((k) => {
|
||||
fmData.append(k, params[k]);
|
||||
})
|
||||
}
|
||||
// 监听进度回调
|
||||
const uploadProgress = function(event) {
|
||||
if (event.lengthComputable) {
|
||||
that.progress = 100 * Math.round(event.loaded) / event.total;
|
||||
}
|
||||
};
|
||||
// 上传文件
|
||||
that.reset();
|
||||
that.loading = 1;
|
||||
that.setStep(3);
|
||||
that.$emit('crop-success', createImgUrl, field, ki);
|
||||
new Promise(function(resolve, reject) {
|
||||
let client = new XMLHttpRequest();
|
||||
client.open('POST', url, true);
|
||||
client.onreadystatechange = function() {
|
||||
if (this.readyState !== 4) {
|
||||
return;
|
||||
}
|
||||
if (this.status === 200) {
|
||||
resolve(JSON.parse(this.responseText));
|
||||
} else {
|
||||
reject(this.status);
|
||||
}
|
||||
};
|
||||
client.upload.addEventListener("progress", uploadProgress, false); //监听进度
|
||||
// 设置header
|
||||
if (typeof headers == 'object' && headers) {
|
||||
Object.keys(headers).forEach((k) => {
|
||||
client.setRequestHeader(k, headers[k]);
|
||||
})
|
||||
}
|
||||
client.send(fmData);
|
||||
}).then(
|
||||
// 上传成功
|
||||
function(resData) {
|
||||
that.loading = 2;
|
||||
that.$emit('crop-upload-success', url);
|
||||
},
|
||||
// 上传失败
|
||||
function(sts) {
|
||||
if (that.value) {
|
||||
that.loading = 3;
|
||||
that.hasError = true;
|
||||
that.errorMsg = lang.fail;
|
||||
that.$emit('crop-upload-fail', sts, field, ki);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@import "./upload.css";
|
||||
</style>
|
41
src/components/ImageCropper/lang.js
Normal file
|
@ -0,0 +1,41 @@
|
|||
const langBag = {
|
||||
zh: {
|
||||
hint: '点击,或拖动图片至此处',
|
||||
loading: '正在上传……',
|
||||
noSupported: '浏览器不支持该功能,请使用IE10以上或其他现在浏览器!',
|
||||
success: '上传成功',
|
||||
fail: '图片上传失败',
|
||||
preview: '头像预览',
|
||||
btn: {
|
||||
off: '取消',
|
||||
close: '关闭',
|
||||
back: '上一步',
|
||||
save: '保存'
|
||||
},
|
||||
error: {
|
||||
onlyImg: '仅限图片格式',
|
||||
outOfSize: '单文件大小不能超过 ',
|
||||
lowestPx: '图片最低像素为(宽*高):'
|
||||
}
|
||||
},
|
||||
en: {
|
||||
hint: 'Click, or drag the file here',
|
||||
loading: 'Uploading……',
|
||||
noSupported: 'Browser does not support, please use IE10+ or other browsers',
|
||||
success: 'Upload success',
|
||||
fail: 'Upload failed',
|
||||
preview: 'Preview',
|
||||
btn: {
|
||||
off: 'Cancel',
|
||||
close: 'Close',
|
||||
back: 'Back',
|
||||
save: 'Save'
|
||||
},
|
||||
error: {
|
||||
onlyImg: 'Image only',
|
||||
outOfSize: 'Image exceeds size limit: ',
|
||||
lowestPx: 'The lowest pixel in the image: '
|
||||
}
|
||||
}
|
||||
};
|
||||
export default langBag;
|
691
src/components/ImageCropper/upload.css
Normal file
|
@ -0,0 +1,691 @@
|
|||
@charset "UTF-8";
|
||||
@-webkit-keyframes vicp_progress {
|
||||
0% {
|
||||
background-position-y: 0;
|
||||
}
|
||||
100% {
|
||||
background-position-y: 40px;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes vicp_progress {
|
||||
0% {
|
||||
background-position-y: 0;
|
||||
}
|
||||
100% {
|
||||
background-position-y: 40px;
|
||||
}
|
||||
}
|
||||
|
||||
@-webkit-keyframes vicp {
|
||||
0% {
|
||||
opacity: 0;
|
||||
-webkit-transform: scale(0) translatey(-60px);
|
||||
transform: scale(0) translatey(-60px);
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
-webkit-transform: scale(1) translatey(0);
|
||||
transform: scale(1) translatey(0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes vicp {
|
||||
0% {
|
||||
opacity: 0;
|
||||
-webkit-transform: scale(0) translatey(-60px);
|
||||
transform: scale(0) translatey(-60px);
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
-webkit-transform: scale(1) translatey(0);
|
||||
transform: scale(1) translatey(0);
|
||||
}
|
||||
}
|
||||
|
||||
.vue-image-crop-upload {
|
||||
position: fixed;
|
||||
display: block;
|
||||
-webkit-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
z-index: 10000;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(0, 0, 0, 0.65);
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
-moz-tap-highlight-color: transparent;
|
||||
}
|
||||
|
||||
.vue-image-crop-upload .vicp-wrap {
|
||||
-webkit-box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.23);
|
||||
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.23);
|
||||
position: fixed;
|
||||
display: block;
|
||||
-webkit-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
z-index: 10000;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
margin: auto;
|
||||
width: 600px;
|
||||
height: 330px;
|
||||
padding: 25px;
|
||||
background-color: #fff;
|
||||
border-radius: 2px;
|
||||
-webkit-animation: vicp 0.12s ease-in;
|
||||
animation: vicp 0.12s ease-in;
|
||||
}
|
||||
|
||||
.vue-image-crop-upload .vicp-wrap .vicp-close {
|
||||
position: absolute;
|
||||
right: -30px;
|
||||
top: -30px;
|
||||
}
|
||||
|
||||
.vue-image-crop-upload .vicp-wrap .vicp-close .vicp-icon4 {
|
||||
position: relative;
|
||||
display: block;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
cursor: pointer;
|
||||
-webkit-transition: -webkit-transform 0.18s;
|
||||
transition: -webkit-transform 0.18s;
|
||||
transition: transform 0.18s;
|
||||
transition: transform 0.18s, -webkit-transform 0.18s;
|
||||
-webkit-transform: rotate(0);
|
||||
-ms-transform: rotate(0);
|
||||
transform: rotate(0);
|
||||
}
|
||||
|
||||
.vue-image-crop-upload .vicp-wrap .vicp-close .vicp-icon4::after, .vue-image-crop-upload .vicp-wrap .vicp-close .vicp-icon4::before {
|
||||
-webkit-box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.23);
|
||||
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.23);
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 12px;
|
||||
left: 4px;
|
||||
width: 20px;
|
||||
height: 3px;
|
||||
-webkit-transform: rotate(45deg);
|
||||
-ms-transform: rotate(45deg);
|
||||
transform: rotate(45deg);
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.vue-image-crop-upload .vicp-wrap .vicp-close .vicp-icon4::after {
|
||||
-webkit-transform: rotate(-45deg);
|
||||
-ms-transform: rotate(-45deg);
|
||||
transform: rotate(-45deg);
|
||||
}
|
||||
|
||||
.vue-image-crop-upload .vicp-wrap .vicp-close .vicp-icon4:hover {
|
||||
-webkit-transform: rotate(90deg);
|
||||
-ms-transform: rotate(90deg);
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
.vue-image-crop-upload .vicp-wrap .vicp-step1 .vicp-drop-area {
|
||||
position: relative;
|
||||
padding: 35px;
|
||||
height: 200px;
|
||||
background-color: rgba(0, 0, 0, 0.03);
|
||||
text-align: center;
|
||||
border: 1px dashed rgba(0, 0, 0, 0.08);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.vue-image-crop-upload .vicp-wrap .vicp-step1 .vicp-drop-area .vicp-icon1 {
|
||||
display: block;
|
||||
margin: 0 auto 6px;
|
||||
width: 42px;
|
||||
height: 42px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.vue-image-crop-upload .vicp-wrap .vicp-step1 .vicp-drop-area .vicp-icon1 .vicp-icon1-arrow {
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-bottom: 14.7px solid rgba(0, 0, 0, 0.3);
|
||||
border-left: 14.7px solid transparent;
|
||||
border-right: 14.7px solid transparent;
|
||||
}
|
||||
|
||||
.vue-image-crop-upload .vicp-wrap .vicp-step1 .vicp-drop-area .vicp-icon1 .vicp-icon1-body {
|
||||
display: block;
|
||||
width: 12.6px;
|
||||
height: 14.7px;
|
||||
margin: 0 auto;
|
||||
background-color: rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.vue-image-crop-upload .vicp-wrap .vicp-step1 .vicp-drop-area .vicp-icon1 .vicp-icon1-bottom {
|
||||
-webkit-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
display: block;
|
||||
height: 12.6px;
|
||||
border: 6px solid rgba(0, 0, 0, 0.3);
|
||||
border-top: none;
|
||||
}
|
||||
|
||||
.vue-image-crop-upload .vicp-wrap .vicp-step1 .vicp-drop-area .vicp-hint {
|
||||
display: block;
|
||||
padding: 15px;
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
line-height: 30px;
|
||||
}
|
||||
|
||||
.vue-image-crop-upload .vicp-wrap .vicp-step1 .vicp-drop-area .vicp-no-supported-hint {
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
padding: 30px;
|
||||
width: 100%;
|
||||
height: 60px;
|
||||
line-height: 30px;
|
||||
background-color: #eee;
|
||||
text-align: center;
|
||||
color: #666;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.vue-image-crop-upload .vicp-wrap .vicp-step1 .vicp-drop-area:hover {
|
||||
cursor: pointer;
|
||||
border-color: rgba(0, 0, 0, 0.1);
|
||||
background-color: rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-left {
|
||||
float: left;
|
||||
}
|
||||
|
||||
.vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-left .vicp-img-container {
|
||||
position: relative;
|
||||
display: block;
|
||||
width: 240px;
|
||||
height: 180px;
|
||||
background-color: #e5e5e0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-left .vicp-img-container .vicp-img {
|
||||
position: absolute;
|
||||
display: block;
|
||||
cursor: move;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-left .vicp-img-container .vicp-img-shade {
|
||||
-webkit-box-shadow: 0 2px 6px 0 rgba(0, 0, 0, 0.18);
|
||||
box-shadow: 0 2px 6px 0 rgba(0, 0, 0, 0.18);
|
||||
position: absolute;
|
||||
background-color: rgba(241, 242, 243, 0.8);
|
||||
}
|
||||
|
||||
.vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-left .vicp-img-container .vicp-img-shade.vicp-img-shade-1 {
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-left .vicp-img-container .vicp-img-shade.vicp-img-shade-2 {
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-left .vicp-range {
|
||||
position: relative;
|
||||
margin: 30px 0;
|
||||
width: 240px;
|
||||
height: 18px;
|
||||
}
|
||||
|
||||
.vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-left .vicp-range .vicp-icon5,
|
||||
.vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-left .vicp-range .vicp-icon6 {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
border-radius: 100%;
|
||||
background-color: rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-left .vicp-range .vicp-icon5:hover,
|
||||
.vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-left .vicp-range .vicp-icon6:hover {
|
||||
-webkit-box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.12);
|
||||
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.12);
|
||||
cursor: pointer;
|
||||
background-color: rgba(0, 0, 0, 0.14);
|
||||
}
|
||||
|
||||
.vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-left .vicp-range .vicp-icon5 {
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-left .vicp-range .vicp-icon5::before {
|
||||
position: absolute;
|
||||
content: '';
|
||||
display: block;
|
||||
left: 3px;
|
||||
top: 8px;
|
||||
width: 12px;
|
||||
height: 2px;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-left .vicp-range .vicp-icon6 {
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-left .vicp-range .vicp-icon6::before {
|
||||
position: absolute;
|
||||
content: '';
|
||||
display: block;
|
||||
left: 3px;
|
||||
top: 8px;
|
||||
width: 12px;
|
||||
height: 2px;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-left .vicp-range .vicp-icon6::after {
|
||||
position: absolute;
|
||||
content: '';
|
||||
display: block;
|
||||
top: 3px;
|
||||
left: 8px;
|
||||
width: 2px;
|
||||
height: 12px;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-left .vicp-range input[type=range] {
|
||||
display: block;
|
||||
padding-top: 5px;
|
||||
margin: 0 auto;
|
||||
width: 180px;
|
||||
height: 8px;
|
||||
vertical-align: top;
|
||||
background: transparent;
|
||||
-webkit-appearance: none;
|
||||
-moz-appearance: none;
|
||||
appearance: none;
|
||||
cursor: pointer;
|
||||
/* 滑块
|
||||
---------------------------------------------------------------*/
|
||||
/* 轨道
|
||||
---------------------------------------------------------------*/
|
||||
}
|
||||
|
||||
.vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-left .vicp-range input[type=range]:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-left .vicp-range input[type=range]::-webkit-slider-thumb {
|
||||
-webkit-box-shadow: 0 2px 6px 0 rgba(0, 0, 0, 0.18);
|
||||
box-shadow: 0 2px 6px 0 rgba(0, 0, 0, 0.18);
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
margin-top: -3px;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
background-color: #61c091;
|
||||
border-radius: 100%;
|
||||
border: none;
|
||||
-webkit-transition: 0.2s;
|
||||
transition: 0.2s;
|
||||
}
|
||||
|
||||
.vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-left .vicp-range input[type=range]::-moz-range-thumb {
|
||||
box-shadow: 0 2px 6px 0 rgba(0, 0, 0, 0.18);
|
||||
-moz-appearance: none;
|
||||
appearance: none;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
background-color: #61c091;
|
||||
border-radius: 100%;
|
||||
border: none;
|
||||
-webkit-transition: 0.2s;
|
||||
transition: 0.2s;
|
||||
}
|
||||
|
||||
.vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-left .vicp-range input[type=range]::-ms-thumb {
|
||||
box-shadow: 0 2px 6px 0 rgba(0, 0, 0, 0.18);
|
||||
appearance: none;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
background-color: #61c091;
|
||||
border: none;
|
||||
border-radius: 100%;
|
||||
-webkit-transition: 0.2s;
|
||||
transition: 0.2s;
|
||||
}
|
||||
|
||||
.vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-left .vicp-range input[type=range]:active::-moz-range-thumb {
|
||||
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.23);
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
}
|
||||
|
||||
.vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-left .vicp-range input[type=range]:active::-ms-thumb {
|
||||
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.23);
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
}
|
||||
|
||||
.vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-left .vicp-range input[type=range]:active::-webkit-slider-thumb {
|
||||
-webkit-box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.23);
|
||||
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.23);
|
||||
margin-top: -4px;
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
}
|
||||
|
||||
.vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-left .vicp-range input[type=range]::-webkit-slider-runnable-track {
|
||||
-webkit-box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.12);
|
||||
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.12);
|
||||
width: 100%;
|
||||
height: 6px;
|
||||
cursor: pointer;
|
||||
border-radius: 2px;
|
||||
border: none;
|
||||
background-color: rgba(68, 170, 119, 0.3);
|
||||
}
|
||||
|
||||
.vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-left .vicp-range input[type=range]::-moz-range-track {
|
||||
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.12);
|
||||
width: 100%;
|
||||
height: 6px;
|
||||
cursor: pointer;
|
||||
border-radius: 2px;
|
||||
border: none;
|
||||
background-color: rgba(68, 170, 119, 0.3);
|
||||
}
|
||||
|
||||
.vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-left .vicp-range input[type=range]::-ms-track {
|
||||
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.12);
|
||||
width: 100%;
|
||||
cursor: pointer;
|
||||
background: transparent;
|
||||
border-color: transparent;
|
||||
color: transparent;
|
||||
height: 6px;
|
||||
border-radius: 2px;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-left .vicp-range input[type=range]::-ms-fill-lower {
|
||||
background-color: rgba(68, 170, 119, 0.3);
|
||||
}
|
||||
|
||||
.vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-left .vicp-range input[type=range]::-ms-fill-upper {
|
||||
background-color: rgba(68, 170, 119, 0.15);
|
||||
}
|
||||
|
||||
.vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-left .vicp-range input[type=range]:focus::-webkit-slider-runnable-track {
|
||||
background-color: rgba(68, 170, 119, 0.5);
|
||||
}
|
||||
|
||||
.vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-left .vicp-range input[type=range]:focus::-moz-range-track {
|
||||
background-color: rgba(68, 170, 119, 0.5);
|
||||
}
|
||||
|
||||
.vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-left .vicp-range input[type=range]:focus::-ms-fill-lower {
|
||||
background-color: rgba(68, 170, 119, 0.45);
|
||||
}
|
||||
|
||||
.vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-left .vicp-range input[type=range]:focus::-ms-fill-upper {
|
||||
background-color: rgba(68, 170, 119, 0.25);
|
||||
}
|
||||
|
||||
.vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-right {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-right .vicp-preview {
|
||||
height: 150px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-right .vicp-preview .vicp-preview-item {
|
||||
position: relative;
|
||||
padding: 5px;
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
float: left;
|
||||
margin-right: 16px;
|
||||
}
|
||||
|
||||
.vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-right .vicp-preview .vicp-preview-item span {
|
||||
position: absolute;
|
||||
bottom: -30px;
|
||||
width: 100%;
|
||||
font-size: 14px;
|
||||
color: #bbb;
|
||||
display: block;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-right .vicp-preview .vicp-preview-item img {
|
||||
position: absolute;
|
||||
display: block;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
margin: auto;
|
||||
padding: 3px;
|
||||
background-color: #fff;
|
||||
border: 1px solid rgba(0, 0, 0, 0.15);
|
||||
overflow: hidden;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-right .vicp-preview .vicp-preview-item:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-right .vicp-preview .vicp-preview-item:last-child img {
|
||||
border-radius: 100%;
|
||||
}
|
||||
|
||||
.vue-image-crop-upload .vicp-wrap .vicp-step3 .vicp-upload {
|
||||
position: relative;
|
||||
padding: 35px;
|
||||
height: 200px;
|
||||
background-color: rgba(0, 0, 0, 0.03);
|
||||
text-align: center;
|
||||
border: 1px dashed #ddd;
|
||||
}
|
||||
|
||||
.vue-image-crop-upload .vicp-wrap .vicp-step3 .vicp-upload .vicp-loading {
|
||||
display: block;
|
||||
padding: 15px;
|
||||
font-size: 16px;
|
||||
color: #999;
|
||||
line-height: 30px;
|
||||
}
|
||||
|
||||
.vue-image-crop-upload .vicp-wrap .vicp-step3 .vicp-upload .vicp-progress-wrap {
|
||||
margin-top: 12px;
|
||||
background-color: rgba(0, 0, 0, 0.08);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.vue-image-crop-upload .vicp-wrap .vicp-step3 .vicp-upload .vicp-progress-wrap .vicp-progress {
|
||||
position: relative;
|
||||
display: block;
|
||||
height: 5px;
|
||||
border-radius: 3px;
|
||||
background-color: #4a7;
|
||||
-webkit-box-shadow: 0 2px 6px 0 rgba(68, 170, 119, 0.3);
|
||||
box-shadow: 0 2px 6px 0 rgba(68, 170, 119, 0.3);
|
||||
-webkit-transition: width 0.15s linear;
|
||||
transition: width 0.15s linear;
|
||||
background-image: -webkit-linear-gradient(135deg, rgba(255, 255, 255, 0.2) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.2) 50%, rgba(255, 255, 255, 0.2) 75%, transparent 75%, transparent);
|
||||
background-image: linear-gradient(-45deg, rgba(255, 255, 255, 0.2) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.2) 50%, rgba(255, 255, 255, 0.2) 75%, transparent 75%, transparent);
|
||||
background-size: 40px 40px;
|
||||
-webkit-animation: vicp_progress 0.5s linear infinite;
|
||||
animation: vicp_progress 0.5s linear infinite;
|
||||
}
|
||||
|
||||
.vue-image-crop-upload .vicp-wrap .vicp-step3 .vicp-upload .vicp-progress-wrap .vicp-progress::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
display: block;
|
||||
top: -3px;
|
||||
right: -3px;
|
||||
width: 9px;
|
||||
height: 9px;
|
||||
border: 1px solid rgba(245, 246, 247, 0.7);
|
||||
-webkit-box-shadow: 0 1px 4px 0 rgba(68, 170, 119, 0.7);
|
||||
box-shadow: 0 1px 4px 0 rgba(68, 170, 119, 0.7);
|
||||
border-radius: 100%;
|
||||
background-color: #4a7;
|
||||
}
|
||||
|
||||
.vue-image-crop-upload .vicp-wrap .vicp-step3 .vicp-upload .vicp-error,
|
||||
.vue-image-crop-upload .vicp-wrap .vicp-step3 .vicp-upload .vicp-success {
|
||||
height: 100px;
|
||||
line-height: 100px;
|
||||
}
|
||||
|
||||
.vue-image-crop-upload .vicp-wrap .vicp-operate {
|
||||
position: absolute;
|
||||
right: 20px;
|
||||
bottom: 20px;
|
||||
}
|
||||
|
||||
.vue-image-crop-upload .vicp-wrap .vicp-operate a {
|
||||
position: relative;
|
||||
float: left;
|
||||
display: block;
|
||||
margin-left: 10px;
|
||||
width: 100px;
|
||||
height: 36px;
|
||||
line-height: 36px;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
color: #4a7;
|
||||
border-radius: 2px;
|
||||
overflow: hidden;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.vue-image-crop-upload .vicp-wrap .vicp-operate a:hover {
|
||||
background-color: rgba(0, 0, 0, 0.03);
|
||||
}
|
||||
|
||||
.vue-image-crop-upload .vicp-wrap .vicp-error,
|
||||
.vue-image-crop-upload .vicp-wrap .vicp-success {
|
||||
display: block;
|
||||
font-size: 14px;
|
||||
line-height: 24px;
|
||||
height: 24px;
|
||||
color: #d10;
|
||||
text-align: center;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.vue-image-crop-upload .vicp-wrap .vicp-success {
|
||||
color: #4a7;
|
||||
}
|
||||
|
||||
.vue-image-crop-upload .vicp-wrap .vicp-icon3 {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
top: 4px;
|
||||
}
|
||||
|
||||
.vue-image-crop-upload .vicp-wrap .vicp-icon3::after {
|
||||
position: absolute;
|
||||
top: 3px;
|
||||
left: 6px;
|
||||
width: 6px;
|
||||
height: 10px;
|
||||
border-width: 0 2px 2px 0;
|
||||
border-color: #4a7;
|
||||
border-style: solid;
|
||||
-webkit-transform: rotate(45deg);
|
||||
-ms-transform: rotate(45deg);
|
||||
transform: rotate(45deg);
|
||||
content: '';
|
||||
}
|
||||
|
||||
.vue-image-crop-upload .vicp-wrap .vicp-icon2 {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
top: 4px;
|
||||
}
|
||||
|
||||
.vue-image-crop-upload .vicp-wrap .vicp-icon2::after, .vue-image-crop-upload .vicp-wrap .vicp-icon2::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 9px;
|
||||
left: 4px;
|
||||
width: 13px;
|
||||
height: 2px;
|
||||
background-color: #d10;
|
||||
-webkit-transform: rotate(45deg);
|
||||
-ms-transform: rotate(45deg);
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
|
||||
.vue-image-crop-upload .vicp-wrap .vicp-icon2::after {
|
||||
-webkit-transform: rotate(-45deg);
|
||||
-ms-transform: rotate(-45deg);
|
||||
transform: rotate(-45deg);
|
||||
}
|
||||
|
||||
.e-ripple {
|
||||
position: absolute;
|
||||
border-radius: 100%;
|
||||
background-color: rgba(0, 0, 0, 0.15);
|
||||
background-clip: padding-box;
|
||||
pointer-events: none;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
-webkit-transform: scale(0);
|
||||
-ms-transform: scale(0);
|
||||
transform: scale(0);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.e-ripple.z-active {
|
||||
opacity: 0;
|
||||
-webkit-transform: scale(2);
|
||||
-ms-transform: scale(2);
|
||||
transform: scale(2);
|
||||
-webkit-transition: opacity 1.2s ease-out, -webkit-transform 0.6s ease-out;
|
||||
transition: opacity 1.2s ease-out, -webkit-transform 0.6s ease-out;
|
||||
transition: opacity 1.2s ease-out, transform 0.6s ease-out;
|
||||
transition: opacity 1.2s ease-out, transform 0.6s ease-out, -webkit-transform 0.6s ease-out;
|
||||
}
|
58
src/components/ImageCropper/utils.js
Normal file
|
@ -0,0 +1,58 @@
|
|||
/* eslint-disable */
|
||||
|
||||
/**
|
||||
*
|
||||
* @param e
|
||||
* @param arg_opts
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function effectRipple(e, arg_opts) {
|
||||
let opts = Object.assign({
|
||||
ele: e.target, // 波纹作用元素
|
||||
type: 'hit', // hit点击位置扩散 center中心点扩展
|
||||
bgc: 'rgba(0, 0, 0, 0.15)' // 波纹颜色
|
||||
}, arg_opts),
|
||||
target = opts.ele;
|
||||
if (target) {
|
||||
let rect = target.getBoundingClientRect(),
|
||||
ripple = target.querySelector('.e-ripple');
|
||||
if (!ripple) {
|
||||
ripple = document.createElement('span');
|
||||
ripple.className = 'e-ripple';
|
||||
ripple.style.height = ripple.style.width = Math.max(rect.width, rect.height) + 'px';
|
||||
target.appendChild(ripple);
|
||||
} else {
|
||||
ripple.className = 'e-ripple';
|
||||
}
|
||||
switch (opts.type) {
|
||||
case 'center':
|
||||
ripple.style.top = (rect.height / 2 - ripple.offsetHeight / 2) + 'px';
|
||||
ripple.style.left = (rect.width / 2 - ripple.offsetWidth / 2) + 'px';
|
||||
break;
|
||||
default:
|
||||
ripple.style.top = (e.pageY - rect.top - ripple.offsetHeight / 2 - document.body.scrollTop) + 'px';
|
||||
ripple.style.left = (e.pageX - rect.left - ripple.offsetWidth / 2 - document.body.scrollLeft) + 'px';
|
||||
}
|
||||
ripple.style.backgroundColor = opts.bgc;
|
||||
ripple.className = 'e-ripple z-active';
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// database64文件格式转换为2进制
|
||||
/**
|
||||
*
|
||||
* @param data
|
||||
* @param mime
|
||||
* @returns {*}
|
||||
*/
|
||||
export function data2blob(data, mime) {
|
||||
// dataURL 的格式为 “data:image/png;base64,****”,逗号之前都是一些说明性的文字,我们只需要逗号之后的就行了
|
||||
data = data.split(',')[1];
|
||||
data = window.atob(data);
|
||||
var ia = new Uint8Array(data.length);
|
||||
for (var i = 0; i < data.length; i++) {
|
||||
ia[i] = data.charCodeAt(i);
|
||||
}
|
||||
// canvas.toDataURL 返回的默认格式就是 image/png
|
||||
return new Blob([ia], {type: mime});
|
||||
};
|
407
src/components/MDinput/index.vue
Normal file
|
@ -0,0 +1,407 @@
|
|||
<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">
|
||||
{{ error }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'material-input',
|
||||
computed: {
|
||||
computedErrors() {
|
||||
return typeof this.errorMessages === 'string'
|
||||
? [this.errorMessages] : this.errorMessages
|
||||
},
|
||||
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
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
valueCopy: null,
|
||||
focus: false,
|
||||
valid: true
|
||||
}
|
||||
},
|
||||
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()
|
||||
},
|
||||
handleFocus(focused) {
|
||||
this.focus = focused
|
||||
},
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
</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;
|
||||
position: absolute;
|
||||
transition: $transition;
|
||||
}
|
||||
|
||||
// Mixins:
|
||||
@mixin slided-top() {
|
||||
top: -2 * $spacer;
|
||||
font-size: $font-size-small;
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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 {
|
||||
// These styles are required
|
||||
// for custom validation:
|
||||
&.material--active .material-label {
|
||||
color: $color-red;
|
||||
}
|
||||
.material-input-bar {
|
||||
&:before,
|
||||
&:after {
|
||||
background: $color-red;
|
||||
}
|
||||
}
|
||||
.material-errors {
|
||||
color: $color-red;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
108
src/components/MdEditor/index.vue
Normal file
|
@ -0,0 +1,108 @@
|
|||
<template>
|
||||
<div class='simplemde-container'>
|
||||
<textarea :id='id'>
|
||||
</textarea>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import 'simplemde/dist/simplemde.min.css'
|
||||
import SimpleMDE from 'simplemde'
|
||||
export default {
|
||||
name: 'Sticky',
|
||||
props: {
|
||||
value: String,
|
||||
id: {
|
||||
type: String,
|
||||
default: 'markdown-editor'
|
||||
},
|
||||
autofocus: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
toolbar: {
|
||||
type: Array
|
||||
// default() {
|
||||
// return ['bold', '|', 'link']
|
||||
// }
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
simplemde: null,
|
||||
hasChange: false
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
value(val) {
|
||||
if (val === this.simplemde.value() && !this.hasChange) return;
|
||||
this.simplemde.value(val);
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.simplemde = new SimpleMDE({
|
||||
element: document.getElementById(this.id),
|
||||
autofocus: this.autofocus,
|
||||
toolbar: this.toolbar,
|
||||
spellChecker: false,
|
||||
insertTexts: {
|
||||
link: ['[', ']( )']
|
||||
},
|
||||
// hideIcons: ['guide', 'heading', 'quote', 'image', 'preview', 'side-by-side', 'fullscreen'],
|
||||
placeholder: this.placeholder
|
||||
});
|
||||
if (this.value) {
|
||||
this.simplemde.value(this.value);
|
||||
}
|
||||
|
||||
this.simplemde.codemirror.on('change', () => {
|
||||
if (this.hasChange) {
|
||||
this.hasChange = true
|
||||
}
|
||||
this.$emit('input', this.simplemde.value());
|
||||
});
|
||||
},
|
||||
destroyed() {
|
||||
this.simplemde = null;
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.simplemde-container .CodeMirror {
|
||||
height: 150px;
|
||||
min-height: 150px;
|
||||
}
|
||||
.simplemde-container .CodeMirror-scroll{
|
||||
min-height: 150px;
|
||||
}
|
||||
|
||||
.simplemde-container .CodeMirror-code{
|
||||
padding-bottom: 40px;
|
||||
}
|
||||
.simplemde-container .editor-statusbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.simplemde-container .CodeMirror .CodeMirror-code .cm-link {
|
||||
color: #1482F0;
|
||||
}
|
||||
|
||||
.simplemde-container .CodeMirror .CodeMirror-code .cm-string.cm-url {
|
||||
color: #2d3b4d;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.simplemde-container .CodeMirror .CodeMirror-code .cm-formatting-link-string.cm-url {
|
||||
padding: 0 2px;
|
||||
font-weight: bold;
|
||||
color: #E61E1E;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
|
145
src/components/PanThumb/index.vue
Normal file
|
@ -0,0 +1,145 @@
|
|||
<template>
|
||||
<div class="pan-item" :style="{zIndex:zIndex,height:height,width:width}">
|
||||
<div class="pan-info">
|
||||
<div class="pan-info-roles-container">
|
||||
<slot>pan</slot>
|
||||
</div>
|
||||
</div>
|
||||
<img class="pan-thumb" :src="image">
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
name: 'PanThumb',
|
||||
props: {
|
||||
image: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
zIndex: {
|
||||
type: Number,
|
||||
default: 100
|
||||
},
|
||||
width: {
|
||||
type: String,
|
||||
default: '150px'
|
||||
},
|
||||
height: {
|
||||
type: String,
|
||||
default: '150px'
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {};
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.pan-item {
|
||||
width: 200px;
|
||||
height: 200px;
|
||||
border-radius: 50%;
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
cursor: default;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.pan-info-roles-container {
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.pan-thumb {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-size: 100%;
|
||||
border-radius: 50%;
|
||||
overflow: hidden;
|
||||
position: absolute;
|
||||
transform-origin: 95% 40%;
|
||||
transition: all 0.3s ease-in-out;
|
||||
}
|
||||
|
||||
.pan-thumb:after {
|
||||
content: '';
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
position: absolute;
|
||||
border-radius: 50%;
|
||||
top: 40%;
|
||||
left: 95%;
|
||||
margin: -4px 0 0 -4px;
|
||||
background: radial-gradient(ellipse at center, rgba(14, 14, 14, 1) 0%, rgba(125, 126, 125, 1) 100%);
|
||||
box-shadow: 0 0 1px rgba(255, 255, 255, 0.9);
|
||||
}
|
||||
|
||||
.pan-info {
|
||||
position: absolute;
|
||||
width: inherit;
|
||||
height: inherit;
|
||||
border-radius: 50%;
|
||||
overflow: hidden;
|
||||
box-shadow: inset 0 0 0 5px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.pan-info h3 {
|
||||
color: #fff;
|
||||
text-transform: uppercase;
|
||||
position: relative;
|
||||
letter-spacing: 2px;
|
||||
font-size: 18px;
|
||||
margin: 0 60px;
|
||||
padding: 22px 0 0 0;
|
||||
height: 85px;
|
||||
font-family: 'Open Sans', Arial, sans-serif;
|
||||
text-shadow: 0 0 1px #fff,
|
||||
0 1px 2px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.pan-info p {
|
||||
color: #fff;
|
||||
padding: 10px 5px;
|
||||
font-style: italic;
|
||||
margin: 0 30px;
|
||||
font-size: 12px;
|
||||
border-top: 1px solid rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
|
||||
.pan-info p a {
|
||||
display: block;
|
||||
color: #333;
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
background: rgba(255, 255, 255, 0.3);
|
||||
border-radius: 50%;
|
||||
color: #fff;
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
font-size: 9px;
|
||||
letter-spacing: 1px;
|
||||
padding-top: 24px;
|
||||
margin: 7px auto 0;
|
||||
font-family: 'Open Sans', Arial, sans-serif;
|
||||
opacity: 0;
|
||||
transition: transform 0.3s ease-in-out 0.2s,
|
||||
opacity 0.3s ease-in-out 0.2s,
|
||||
background 0.2s linear 0s;
|
||||
transform: translateX(60px) rotate(90deg);
|
||||
}
|
||||
|
||||
.pan-info p a:hover {
|
||||
background: rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
|
||||
.pan-item:hover .pan-thumb {
|
||||
transform: rotate(-110deg);
|
||||
}
|
||||
|
||||
.pan-item:hover .pan-info p a {
|
||||
opacity: 1;
|
||||
transform: translateX(0px) rotate(0deg);
|
||||
}
|
||||
</style>
|
41
src/components/SplitPane/Pane.vue
Normal file
|
@ -0,0 +1,41 @@
|
|||
<template>
|
||||
<div :class="classes">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'Pane',
|
||||
data() {
|
||||
const classes = ['Pane', this.$parent.split, 'className'];
|
||||
return {
|
||||
classes: classes.join(' '),
|
||||
percent: 50
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.splitter-pane.vertical.splitter-paneL{
|
||||
position: absolute;
|
||||
left: 0px;
|
||||
height: 100%;
|
||||
}
|
||||
.splitter-pane.vertical.splitter-paneR{
|
||||
position: absolute;
|
||||
right: 0px;
|
||||
height: 100%;
|
||||
}
|
||||
.splitter-pane.horizontal.splitter-paneL{
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
width: 100%;
|
||||
}
|
||||
.splitter-pane.horizontal.splitter-paneR{
|
||||
position: absolute;
|
||||
bottom: 0px;
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
71
src/components/SplitPane/Resizer.vue
Normal file
|
@ -0,0 +1,71 @@
|
|||
<template>
|
||||
<div :class="classes" @mousedown="onMouseDown"></div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
split: {
|
||||
validator(value) {
|
||||
return ['vertical', 'horizontal'].indexOf(value) >= 0
|
||||
},
|
||||
required: true
|
||||
},
|
||||
onMouseDown: {
|
||||
type: Function,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
const classes = ['Resizer', this.split, 'className'];
|
||||
return {
|
||||
classes: classes.join(' ')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style scoped>
|
||||
.Resizer {
|
||||
-moz-box-sizing: border-box;
|
||||
-webkit-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
background: #000;
|
||||
position: absolute;
|
||||
opacity: .2;
|
||||
z-index: 1;
|
||||
/*-moz-background-clip: padding;*/
|
||||
/*-webkit-background-clip: padding;*/
|
||||
/*background-clip: padding-box;*/
|
||||
}
|
||||
|
||||
/*.Resizer:hover {*/
|
||||
/*-webkit-transition: all 2s ease;*/
|
||||
/*transition: all 2s ease;*/
|
||||
/*}*/
|
||||
|
||||
.Resizer.horizontal {
|
||||
height: 11px;
|
||||
margin: -5px 0;
|
||||
border-top: 5px solid rgba(255, 255, 255, 0);
|
||||
border-bottom: 5px solid rgba(255, 255, 255, 0);
|
||||
cursor: row-resize;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.Resizer.horizontal:hover {
|
||||
border-top: 5px solid rgba(0, 0, 0, 0.5);
|
||||
border-bottom: 5px solid rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.Resizer.vertical {
|
||||
width: 11px;
|
||||
height: 100%;
|
||||
border-left: 5px solid rgba(255, 255, 255, 0);
|
||||
border-right: 5px solid rgba(255, 255, 255, 0);
|
||||
cursor: col-resize;
|
||||
}
|
||||
|
||||
.Resizer.vertical:hover {
|
||||
border-left: 5px solid rgba(0, 0, 0, 0.5);
|
||||
border-right: 5px solid rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
</style>
|
116
src/components/SplitPane/index.vue
Normal file
|
@ -0,0 +1,116 @@
|
|||
<template>
|
||||
<div ref :style="{ cursor, userSelect}" class="vue-splitter-container clearfix" @mouseup="onMouseUp"
|
||||
@mousemove="onMouseMove">
|
||||
<Pane class="splitter-pane splitter-paneL" :split="split" :style="{ [type]: percent+'%'}">
|
||||
<slot name="paneL"></slot>
|
||||
</Pane>
|
||||
<Resizer :style="{ [resizeType]: percent+'%'}" :split="split" :onMouseDown="onMouseDown"
|
||||
@click="onClick"></Resizer>
|
||||
<Pane class="splitter-pane splitter-paneR" :split="split" :style="{ [type]: 100-percent+'%'}">
|
||||
<slot name="paneR"></slot>
|
||||
</Pane>
|
||||
</div>
|
||||
</template>
|
||||
<style scoped>
|
||||
.clearfix:after {
|
||||
visibility: hidden;
|
||||
display: block;
|
||||
font-size: 0;
|
||||
content: " ";
|
||||
clear: both;
|
||||
height: 0;
|
||||
}
|
||||
.vue-splitter-container {
|
||||
height: 100%;
|
||||
/*display: flex;*/
|
||||
position: relative;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import Resizer from './Resizer';
|
||||
import Pane from './Pane';
|
||||
export default {
|
||||
name: 'splitPane',
|
||||
components: { Resizer, Pane },
|
||||
props: {
|
||||
margin: {
|
||||
type: Number,
|
||||
default: 10
|
||||
},
|
||||
split: {
|
||||
validator(value) {
|
||||
return ['vertical', 'horizontal'].indexOf(value) >= 0
|
||||
},
|
||||
required: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
active: false,
|
||||
hasMoved: false,
|
||||
height: null,
|
||||
percent: 50,
|
||||
type: this.split === 'vertical' ? 'width' : 'height',
|
||||
resizeType: this.split === 'vertical' ? 'left' : 'top'
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
userSelect() {
|
||||
return this.active ? 'none' : ''
|
||||
},
|
||||
cursor() {
|
||||
return this.active ? 'col-resize' : ''
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
const element = this.$el;
|
||||
const elementOffset = element.getBoundingClientRect();
|
||||
// this.height = elementOffset.height+'px';
|
||||
},
|
||||
methods: {
|
||||
onClick() {
|
||||
if (!this.hasMoved) {
|
||||
this.percent = 50;
|
||||
this.$emit('resize');
|
||||
}
|
||||
},
|
||||
onMouseDown() {
|
||||
this.active = true;
|
||||
this.hasMoved = false;
|
||||
},
|
||||
onMouseUp() {
|
||||
this.active = false;
|
||||
},
|
||||
onMouseMove(e) {
|
||||
if (e.buttons === 0 || e.which === 0) {
|
||||
this.active = false;
|
||||
}
|
||||
if (this.active) {
|
||||
let offset = 0;
|
||||
let target = e.currentTarget;
|
||||
if (this.split === 'vertical') {
|
||||
while (target) {
|
||||
offset += target.offsetLeft;
|
||||
target = target.offsetParent;
|
||||
}
|
||||
} else {
|
||||
while (target) {
|
||||
offset += target.offsetTop;
|
||||
target = target.offsetParent;
|
||||
}
|
||||
}
|
||||
|
||||
const currentPage = this.split === 'vertical' ? e.pageX : e.pageY;
|
||||
const targetOffset = this.split === 'vertical' ? e.currentTarget.offsetWidth : e.currentTarget.offsetHeight;
|
||||
const percent = Math.floor(((currentPage - offset) / targetOffset) * 10000) / 100;
|
||||
if (percent > this.margin && percent < 100 - this.margin) {
|
||||
this.percent = percent;
|
||||
}
|
||||
this.$emit('resize');
|
||||
this.hasMoved = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
73
src/components/Sticky/index.vue
Normal file
|
@ -0,0 +1,73 @@
|
|||
<template>
|
||||
<div :style="{height:height+'px',zIndex:zIndex}">
|
||||
<div :class="className" :style="{top:stickyTop+'px',zIndex:zIndex,position:position,width:width,height:height+'px'}">
|
||||
<slot>
|
||||
<div>sticky</div>
|
||||
</slot>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
name: 'Sticky',
|
||||
props: {
|
||||
stickyTop: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
zIndex: {
|
||||
type: Number,
|
||||
default: 1000
|
||||
},
|
||||
className: {
|
||||
type: String
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
active: false,
|
||||
position: '',
|
||||
currentTop: '',
|
||||
width: undefined,
|
||||
height: undefined,
|
||||
child: null,
|
||||
stickyHeight: 0
|
||||
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
sticky() {
|
||||
if (this.active) {
|
||||
return
|
||||
}
|
||||
this.position = 'fixed';
|
||||
this.active = true;
|
||||
this.width = this.width + 'px';
|
||||
},
|
||||
reset() {
|
||||
if (!this.active) {
|
||||
return
|
||||
}
|
||||
this.position = '';
|
||||
this.width = 'auto'
|
||||
this.active = false
|
||||
},
|
||||
handleScroll() {
|
||||
this.width = this.$el.getBoundingClientRect().width;
|
||||
const offsetTop = this.$el.getBoundingClientRect().top;
|
||||
if (offsetTop <= this.stickyTop) {
|
||||
this.sticky();
|
||||
return
|
||||
}
|
||||
this.reset()
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.height = this.$el.getBoundingClientRect().height;
|
||||
window.addEventListener('scroll', this.handleScroll);
|
||||
},
|
||||
destroyed() {
|
||||
window.removeEventListener('scroll', this.handleScroll);
|
||||
}
|
||||
};
|
||||
</script>
|
119
src/components/Tinymce/components/editorAudio.vue
Normal file
|
@ -0,0 +1,119 @@
|
|||
<template>
|
||||
<div class="upload-container">
|
||||
<el-button :style="{background:color,borderColor:color}" @click=" dialogVisible=true" type="primary">上传音频
|
||||
</el-button>
|
||||
<el-dialog v-model="dialogVisible">
|
||||
<el-form ref="form" :model="form" :rules="rules" label-width="100px" label-position="right">
|
||||
<el-upload
|
||||
class="editor-audio-upload"
|
||||
action="https://upload.qbox.me"
|
||||
:data="dataObj"
|
||||
:show-file-list="true"
|
||||
:file-list="audioList"
|
||||
:on-success="handleAudioScucess"
|
||||
:on-change="handleAudioChange"
|
||||
:before-upload="audioBeforeUpload">
|
||||
<el-button size="small" type="primary">上传音频</el-button>
|
||||
</el-upload>
|
||||
<el-form-item prop="url" label="音频URL">
|
||||
<el-input v-model="form.url"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="title" label="音频标题">
|
||||
<el-input v-model="form.title"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="音频文本">
|
||||
<el-input type="textarea" :autosize="{ minRows: 2}" v-model="form.text"></el-input>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||
<el-button type="primary" @click="handleSubmit">确 定</el-button>
|
||||
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { getToken } from 'api/qiniu';
|
||||
|
||||
export default {
|
||||
name: 'editorAudioUpload',
|
||||
props: {
|
||||
color: {
|
||||
type: String,
|
||||
default: '#20a0ff'
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
dialogVisible: false,
|
||||
dataObj: { token: '', key: '' },
|
||||
audioList: [],
|
||||
tempAudioUrl: '',
|
||||
form: {
|
||||
title: '',
|
||||
url: '',
|
||||
text: ''
|
||||
},
|
||||
rules: {
|
||||
title: [
|
||||
{ required: true, trigger: 'blur' }
|
||||
],
|
||||
url: [
|
||||
{ required: true, trigger: 'blur' }
|
||||
]
|
||||
}
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
handleSubmit() {
|
||||
this.$refs.form.validate(valid => {
|
||||
if (valid) {
|
||||
this.$emit('successCBK', this.form);
|
||||
this.dialogVisible = false;
|
||||
this.form = {
|
||||
title: '',
|
||||
url: '',
|
||||
text: ''
|
||||
}
|
||||
} else {
|
||||
this.$message('填写有误');
|
||||
return false;
|
||||
}
|
||||
});
|
||||
},
|
||||
handleAudioChange(file, fileList) {
|
||||
this.audioList = fileList.slice(-1);
|
||||
},
|
||||
handleAudioScucess() {
|
||||
this.form.url = this.tempAudioUrl
|
||||
},
|
||||
audioBeforeUpload() {
|
||||
const _self = this;
|
||||
return new Promise((resolve, reject) => {
|
||||
getToken().then(response => {
|
||||
const key = response.data.qiniu_key;
|
||||
const token = response.data.qiniu_token;
|
||||
_self._data.dataObj.token = token;
|
||||
_self._data.dataObj.key = key;
|
||||
this.tempAudioUrl = response.data.qiniu_url;
|
||||
resolve(true);
|
||||
}).catch(err => {
|
||||
console.log(err);
|
||||
reject(false)
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style rel="stylesheet/scss" lang="scss" scoped>
|
||||
.upload-container {
|
||||
.editor-audio-upload {
|
||||
button {
|
||||
float: left;
|
||||
margin-left: 30px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
88
src/components/Tinymce/components/editorImage.vue
Normal file
|
@ -0,0 +1,88 @@
|
|||
<template>
|
||||
<div class="upload-container">
|
||||
<el-button icon='upload' :style="{background:color,borderColor:color}" @click=" dialogVisible=true" type="primary">上传图片
|
||||
</el-button>
|
||||
<el-dialog v-model="dialogVisible">
|
||||
<el-upload
|
||||
class="editor-slide-upload"
|
||||
action="https://httpbin.org/post"
|
||||
:data="dataObj"
|
||||
:multiple="true"
|
||||
:file-list="fileList"
|
||||
:show-file-list="true"
|
||||
list-type="picture-card"
|
||||
:on-remove="handleRemove"
|
||||
:on-success="handleImageScucess">
|
||||
<el-button size="small" type="primary">点击上传</el-button>
|
||||
</el-upload>
|
||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||
<el-button type="primary" @click="handleSubmit">确 定</el-button>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { getToken } from 'api/qiniu';
|
||||
|
||||
export default {
|
||||
name: 'editorSlideUpload',
|
||||
props: {
|
||||
color: {
|
||||
type: String,
|
||||
default: '#20a0ff'
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
dialogVisible: false,
|
||||
dataObj: { token: '', key: '' },
|
||||
list: [],
|
||||
fileList: []
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
handleSubmit() {
|
||||
const arr = this.list.map(v => v.url);
|
||||
this.$emit('successCBK', arr);
|
||||
this.list = [];
|
||||
this.fileList = [];
|
||||
this.dialogVisible = false;
|
||||
},
|
||||
handleRemove(file) {
|
||||
const key = file.response.key;
|
||||
for (let i = 0, len = this.list.length; i < len; i++) {
|
||||
if (this.list[i].key === key) {
|
||||
this.list.splice(i, 1);
|
||||
return
|
||||
}
|
||||
}
|
||||
},
|
||||
handleImageScucess(file) {
|
||||
this.list.push({ url: file.files.file });
|
||||
},
|
||||
beforeUpload() {
|
||||
const _self = this;
|
||||
return new Promise((resolve, reject) => {
|
||||
getToken().then(response => {
|
||||
const key = response.data.qiniu_key;
|
||||
const token = response.data.qiniu_token;
|
||||
_self._data.dataObj.token = token;
|
||||
_self._data.dataObj.key = key;
|
||||
this.list.push({ key, url: response.data.qiniu_url });
|
||||
resolve(true);
|
||||
}).catch(err => {
|
||||
console.log(err);
|
||||
reject(false)
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style rel="stylesheet/scss" lang="scss" scoped>
|
||||
.upload-container {
|
||||
.editor-slide-upload {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
}
|
||||
</style>
|
82
src/components/Tinymce/components/editorSlide.vue
Normal file
|
@ -0,0 +1,82 @@
|
|||
<template>
|
||||
<div class="upload-container">
|
||||
<el-button :style="{background:color,borderColor:color}" @click=" dialogVisible=true" type="primary">上传轮播图
|
||||
</el-button>
|
||||
<el-dialog v-model="dialogVisible">
|
||||
<el-upload
|
||||
class="editor-slide-upload"
|
||||
action="https://upload.qbox.me"
|
||||
:data="dataObj"
|
||||
:multiple="true"
|
||||
:show-file-list="true"
|
||||
list-type="picture-card"
|
||||
:on-remove="handleRemove"
|
||||
:before-upload="beforeUpload">
|
||||
<el-button size="small" type="primary">点击上传</el-button>
|
||||
</el-upload>
|
||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||
<el-button type="primary" @click="handleSubmit">确 定</el-button>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { getToken } from 'api/qiniu';
|
||||
|
||||
export default {
|
||||
name: 'editorSlideUpload',
|
||||
props: {
|
||||
color: {
|
||||
type: String,
|
||||
default: '#20a0ff'
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
dialogVisible: false,
|
||||
dataObj: { token: '', key: '' },
|
||||
list: []
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
handleSubmit() {
|
||||
const arr = this.list.map(v => v.url);
|
||||
this.$emit('successCBK', arr);
|
||||
this.list = [];
|
||||
this.dialogVisible = false;
|
||||
},
|
||||
handleRemove(file) {
|
||||
const key = file.response.key;
|
||||
for (let i = 0, len = this.list.length; i < len; i++) {
|
||||
if (this.list[i].key === key) {
|
||||
this.list.splice(i, 1);
|
||||
return
|
||||
}
|
||||
}
|
||||
},
|
||||
beforeUpload() {
|
||||
const _self = this;
|
||||
return new Promise((resolve, reject) => {
|
||||
getToken().then(response => {
|
||||
const key = response.data.qiniu_key;
|
||||
const token = response.data.qiniu_token;
|
||||
_self._data.dataObj.token = token;
|
||||
_self._data.dataObj.key = key;
|
||||
this.list.push({ key, url: response.data.qiniu_url });
|
||||
resolve(true);
|
||||
}).catch(err => {
|
||||
console.log(err);
|
||||
reject(false)
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style rel="stylesheet/scss" lang="scss" scoped>
|
||||
.upload-container {
|
||||
.editor-slide-upload {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
}
|
||||
</style>
|
167
src/components/Tinymce/components/editorVideo.vue
Normal file
|
@ -0,0 +1,167 @@
|
|||
<template>
|
||||
<div class="upload-container">
|
||||
<el-button :style="{background:color,borderColor:color}" @click=" dialogVisible=true" type="primary">上传视频</el-button>
|
||||
<el-dialog v-model="dialogVisible">
|
||||
<el-form ref="form" :model="form" :rules="rules" label-width="140px" label-position="left">
|
||||
<el-upload
|
||||
class="editor-video-upload"
|
||||
action="https://upload.qbox.me"
|
||||
:data="dataObj"
|
||||
:show-file-list="true"
|
||||
:file-list="videoList"
|
||||
:on-success="handleVideoScucess"
|
||||
:on-change="handleVideoChange"
|
||||
:before-upload="videoBeforeUpload">
|
||||
<el-button size="small" type="primary">上传视频</el-button>
|
||||
</el-upload>
|
||||
<el-form-item prop="url" label="视频URL">
|
||||
<el-input v-model="form.url"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="title" label="视频标题">
|
||||
<el-input v-model="form.title"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="上传视频封面图">
|
||||
</el-form-item>
|
||||
<el-upload
|
||||
class="image-uploader"
|
||||
action="https://upload.qbox.me"
|
||||
:show-file-list="false"
|
||||
:data="dataObj"
|
||||
:on-success="handleImageScucess"
|
||||
:before-upload="beforeImageUpload">
|
||||
<img v-if="form.image" :src="form.image" class="image-uploader-image">
|
||||
<i v-else class="el-icon-plus avatar-uploader-icon"></i>
|
||||
</el-upload>
|
||||
</el-form>
|
||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||
<el-button type="primary" @click="handleSubmit">确 定</el-button>
|
||||
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { getToken } from 'api/qiniu';
|
||||
|
||||
export default {
|
||||
name: 'editorVideoUpload',
|
||||
props: {
|
||||
color: {
|
||||
type: String,
|
||||
default: '#20a0ff'
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
dialogVisible: false,
|
||||
dataObj: { token: '', key: '' },
|
||||
videoList: [],
|
||||
tempVideoUrl: '',
|
||||
tempImageUrl: '',
|
||||
form: {
|
||||
title: '',
|
||||
url: '',
|
||||
image: ''
|
||||
},
|
||||
rules: {
|
||||
url: [
|
||||
{ required: true, trigger: 'blur' }
|
||||
],
|
||||
title: [
|
||||
{ required: true, trigger: 'blur' }
|
||||
]
|
||||
}
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
handleSubmit() {
|
||||
this.$refs.form.validate(valid => {
|
||||
if (valid) {
|
||||
if (this.form.image.length > 0) {
|
||||
this.$emit('successCBK', this.form);
|
||||
this.dialogVisible = false;
|
||||
this.form = {
|
||||
title: '',
|
||||
url: '',
|
||||
image: ''
|
||||
}
|
||||
} else {
|
||||
this.$message('请上传图片');
|
||||
}
|
||||
} else {
|
||||
this.$message('填写有误');
|
||||
return false;
|
||||
}
|
||||
});
|
||||
},
|
||||
handleVideoChange(file, fileList) {
|
||||
this.videoList = fileList.slice(-1);
|
||||
},
|
||||
handleVideoScucess() {
|
||||
this.form.url = this.tempVideoUrl
|
||||
},
|
||||
videoBeforeUpload() {
|
||||
const _self = this;
|
||||
return new Promise((resolve, reject) => {
|
||||
getToken().then(response => {
|
||||
const key = response.data.qiniu_key;
|
||||
const token = response.data.qiniu_token;
|
||||
_self._data.dataObj.token = token;
|
||||
_self._data.dataObj.key = key;
|
||||
this.tempVideoUrl = response.data.qiniu_url;
|
||||
resolve(true);
|
||||
}).catch(err => {
|
||||
console.log(err);
|
||||
reject(false)
|
||||
});
|
||||
});
|
||||
},
|
||||
handleImageScucess() {
|
||||
this.form.image = this.tempImageUrl
|
||||
},
|
||||
beforeImageUpload() {
|
||||
const _self = this;
|
||||
return new Promise((resolve, reject) => {
|
||||
getToken().then(response => {
|
||||
const key = response.data.qiniu_key;
|
||||
const token = response.data.qiniu_token;
|
||||
_self._data.dataObj.token = token;
|
||||
_self._data.dataObj.key = key;
|
||||
this.tempImageUrl = response.data.qiniu_url;
|
||||
resolve(true);
|
||||
}).catch(err => {
|
||||
console.log(err);
|
||||
reject(false)
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style rel="stylesheet/scss" lang="scss" scoped>
|
||||
.upload-container {
|
||||
.editor-video-upload {
|
||||
button {
|
||||
float: left;
|
||||
}
|
||||
}
|
||||
.image-uploader {
|
||||
margin: 5px auto;
|
||||
width: 400px;
|
||||
height: 200px;
|
||||
border: 1px dashed #d9d9d9;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
line-height: 200px;
|
||||
i {
|
||||
font-size: 28px;
|
||||
color: #8c939d;
|
||||
}
|
||||
.image-uploader-image {
|
||||
height: 200px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
242
src/components/Tinymce/index.vue
Normal file
|
@ -0,0 +1,242 @@
|
|||
<template>
|
||||
<div class='tinymce-container editor-container'>
|
||||
<textarea class='tinymce-textarea' :id="id"></textarea>
|
||||
<div class="editor-custom-btn-container">
|
||||
<editorSlide v-if="customButton.indexOf('editorSlide')>=0" color="#3A71A8" class="editor-upload-btn" @successCBK="slideSuccessCBK"></editorSlide>
|
||||
<editorAudio v-if="customButton.indexOf('editorAudio')>=0" color="#30B08F" class="editor-upload-btn" @successCBK="aduioSuccessCBK"></editorAudio>
|
||||
<editorVideo v-if="customButton.indexOf('editorVideo')>=0" color="#E65D6E" class="editor-upload-btn" @successCBK="videoSuccessCBK"></editorVideo>
|
||||
<editorImage v-if="customButton.indexOf('editorImage')>=0" color="#20a0ff" class="editor-upload-btn" @successCBK="imageSuccessCBK"></editorImage>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import editorAudio from './components/editorAudio';
|
||||
import editorVideo from './components/editorVideo';
|
||||
import editorSlide from './components/editorSlide';
|
||||
import editorImage from './components/editorImage';
|
||||
import { getToken, upload } from 'api/qiniu';
|
||||
export default {
|
||||
name: 'tinymce',
|
||||
components: { editorImage, editorAudio, editorSlide, editorVideo },
|
||||
props: {
|
||||
id: {
|
||||
type: String,
|
||||
default: 'tinymceEditor'
|
||||
},
|
||||
value: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
customButton: {
|
||||
type: Array,
|
||||
required: false,
|
||||
default() {
|
||||
return ['editorAudio', 'editorImage']
|
||||
}
|
||||
},
|
||||
toolbar: {
|
||||
type: Array,
|
||||
required: false,
|
||||
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: ''
|
||||
},
|
||||
height: {
|
||||
type: Number,
|
||||
required: false,
|
||||
default: 360
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value(val) {
|
||||
if (!this.hasChange && this.hasInit) {
|
||||
this.$nextTick(() => tinymce.get(this.id).setContent(val))
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
const _this = this;
|
||||
tinymce.init({
|
||||
selector: `#${this.id}`,
|
||||
height: this.height,
|
||||
body_class: 'panel-body ',
|
||||
object_resizing: false,
|
||||
// language: 'zh_CN',
|
||||
// language_url: '/static/tinymce/langs/zh_CN.js',
|
||||
toolbar: this.toolbar,
|
||||
menubar: this.menubar,
|
||||
plugins: 'advlist,autolink,code,paste,textcolor, colorpicker,fullscreen,link,lists,media,wordcount, imagetools,watermark',
|
||||
end_container_on_empty_block: true,
|
||||
powerpaste_word_import: 'clean',
|
||||
code_dialog_height: 450,
|
||||
code_dialog_width: 1000,
|
||||
advlist_bullet_styles: 'square',
|
||||
advlist_number_styles: 'default',
|
||||
block_formats: '普通标签=p;小标题=h2;',
|
||||
imagetools_cors_hosts: ['wpimg.wallstcn.com', 'wallstreetcn.com'],
|
||||
imagetools_toolbar: 'watermark',
|
||||
default_link_target: '_blank',
|
||||
link_title: false,
|
||||
textcolor_map: [
|
||||
'1482f0', '1482f0',
|
||||
'4595e6', '4595e6'],
|
||||
init_instance_callback: editor => {
|
||||
if (_this.value) {
|
||||
editor.setContent(_this.value)
|
||||
}
|
||||
_this.hasInit = true;
|
||||
editor.on('Change', () => {
|
||||
this.hasChange = true;
|
||||
this.$emit('input', editor.getContent({ format: 'raw' }));
|
||||
});
|
||||
},
|
||||
// images_dataimg_filter(img) {
|
||||
// setTimeout(() => {
|
||||
// const $image = $(img);
|
||||
// $image.removeAttr('width');
|
||||
// $image.removeAttr('height');
|
||||
// if ($image[0].height && $image[0].width) {
|
||||
// $image.attr('data-wscntype', 'image');
|
||||
// $image.attr('data-wscnh', $image[0].height);
|
||||
// $image.attr('data-wscnw', $image[0].width);
|
||||
// $image.addClass('wscnph');
|
||||
// }
|
||||
// }, 0);
|
||||
// return img
|
||||
// },
|
||||
// images_upload_handler(blobInfo, success, failure, progress) {
|
||||
// progress(0);
|
||||
// const token = _this.$store.getters.token;
|
||||
// getToken(token).then(response => {
|
||||
// const url = response.data.qiniu_url;
|
||||
// const formData = new FormData();
|
||||
// formData.append('token', response.data.qiniu_token);
|
||||
// formData.append('key', response.data.qiniu_key);
|
||||
// formData.append('file', blobInfo.blob(), url);
|
||||
// upload(formData).then(() => {
|
||||
// success(url);
|
||||
// progress(100);
|
||||
// })
|
||||
// }).catch(err => {
|
||||
// failure('出现未知问题,刷新页面,或者联系程序员')
|
||||
// console.log(err);
|
||||
// });
|
||||
// },
|
||||
setup(editor) {
|
||||
editor.addButton('h2', {
|
||||
title: '小标题', // tooltip text seen on mouseover
|
||||
text: '小标题',
|
||||
onclick() {
|
||||
editor.execCommand('mceToggleFormat', false, 'h2');
|
||||
},
|
||||
onPostRender() {
|
||||
const btn = this;
|
||||
editor.on('init', () => {
|
||||
editor.formatter.formatChanged('h2', state => {
|
||||
btn.active(state);
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
editor.addButton('p', {
|
||||
title: '正文', // tooltip text seen on mouseover
|
||||
text: '正文',
|
||||
onclick() {
|
||||
editor.execCommand('mceToggleFormat', false, 'p');
|
||||
},
|
||||
onPostRender() {
|
||||
const btn = this;
|
||||
editor.on('init', () => {
|
||||
editor.formatter.formatChanged('p', state => {
|
||||
btn.active(state);
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
imageSuccessCBK(arr) {
|
||||
console.log(arr)
|
||||
const _this = this;
|
||||
arr.forEach(v => {
|
||||
const node = document.createElement('img');
|
||||
node.setAttribute('src', v);
|
||||
node.onload = function() {
|
||||
$(this).addClass('wscnph');
|
||||
$(this).attr('data-wscntype', 'image');
|
||||
$(this).attr('data-wscnh', this.height);
|
||||
$(this).attr('data-wscnw', this.width);
|
||||
tinymce.get(_this.id).insertContent(node.outerHTML)
|
||||
}
|
||||
})
|
||||
},
|
||||
slideSuccessCBK(arr) {
|
||||
const node = document.createElement('img');
|
||||
node.setAttribute('data-wscntype', 'slide');
|
||||
node.setAttribute('data-uri', arr.toString());
|
||||
node.setAttribute('data-wscnh', '190');
|
||||
node.setAttribute('data-wscnw', '200');
|
||||
node.setAttribute('src', ' https://wdl.wallstreetcn.com/6410b47d-a54c-4826-9bc1-c3e5df31280c');
|
||||
node.className = 'wscnph editor-placeholder';
|
||||
tinymce.get(this.id).insertContent(node.outerHTML)
|
||||
},
|
||||
videoSuccessCBK(form) {
|
||||
const node = document.createElement('img');
|
||||
node.setAttribute('data-wscntype', 'video');
|
||||
node.setAttribute('data-uri', form.url);
|
||||
node.setAttribute('data-cover-img-uri', form.image);
|
||||
node.setAttribute('data-title', form.title);
|
||||
node.setAttribute('src', 'https://wdl.wallstreetcn.com/07aeb3e7-f4ca-4207-befb-c987b3dc7011');
|
||||
node.className = 'wscnph editor-placeholder';
|
||||
tinymce.get(this.id).insertContent(node.outerHTML)
|
||||
},
|
||||
aduioSuccessCBK(form) {
|
||||
const node = document.createElement('img');
|
||||
node.setAttribute('data-wscntype', 'audio');
|
||||
node.setAttribute('data-uri', form.url);
|
||||
node.setAttribute('data-title', form.title);
|
||||
node.setAttribute('data-text', form.text);
|
||||
node.setAttribute('src', 'https://wdl.wallstreetcn.com/2ed0c8c8-fb82-499d-b81c-3fd1de114eae');
|
||||
node.className = 'wscnph editor-placeholder';
|
||||
tinymce.get(this.id).insertContent(node.outerHTML)
|
||||
}
|
||||
},
|
||||
destroyed() {
|
||||
tinymce.get(this.id).destroy();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.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>
|
128
src/components/Upload/singleImage.vue
Normal file
|
@ -0,0 +1,128 @@
|
|||
<template>
|
||||
<div class="upload-container">
|
||||
<el-upload
|
||||
class="image-uploader"
|
||||
:data="dataObj"
|
||||
drag
|
||||
:multiple="false"
|
||||
:show-file-list="false"
|
||||
action="https://httpbin.org/post"
|
||||
|
||||
:on-success="handleImageScucess">
|
||||
<i class="el-icon-upload"></i>
|
||||
<div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
|
||||
</el-upload>
|
||||
<div class="image-preview">
|
||||
<div class="image-preview-wrapper" v-show="imageUrl.length>1">
|
||||
<img :src="imageUrl+'?imageView2/1/w/200/h/200'">
|
||||
<div class="image-preview-action">
|
||||
<i @click="rmImage" class="el-icon-delete"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
// 预览效果见付费文章
|
||||
import { getToken } from 'api/qiniu';
|
||||
export default {
|
||||
name: 'singleImageUpload',
|
||||
props: {
|
||||
value: String
|
||||
},
|
||||
computed: {
|
||||
imageUrl() {
|
||||
return this.value
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
tempUrl: '',
|
||||
dataObj: { token: '', key: '' }
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
rmImage() {
|
||||
this.emitInput('');
|
||||
},
|
||||
emitInput(val) {
|
||||
this.$emit('input', val);
|
||||
},
|
||||
handleImageScucess() {
|
||||
this.emitInput(this.tempUrl)
|
||||
},
|
||||
beforeUpload() {
|
||||
const _self = this;
|
||||
return new Promise((resolve, reject) => {
|
||||
getToken().then(response => {
|
||||
const key = response.data.qiniu_key;
|
||||
const token = response.data.qiniu_token;
|
||||
_self._data.dataObj.token = token;
|
||||
_self._data.dataObj.key = key;
|
||||
this.tempUrl = response.data.qiniu_url;
|
||||
resolve(true);
|
||||
}).catch(err => {
|
||||
console.log(err);
|
||||
reject(false)
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style rel="stylesheet/scss" lang="scss" scoped>
|
||||
@import "src/styles/mixin.scss";
|
||||
.upload-container {
|
||||
width: 100%;
|
||||
position: relative;
|
||||
@include clearfix;
|
||||
.image-uploader {
|
||||
width: 60%;
|
||||
float: left;
|
||||
}
|
||||
.image-preview {
|
||||
width: 200px;
|
||||
height: 200px;
|
||||
position: relative;
|
||||
border: 1px dashed #d9d9d9;
|
||||
float: left;
|
||||
margin-left: 50px;
|
||||
.image-preview-wrapper {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
.image-preview-action {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
left: 0;
|
||||
top: 0;
|
||||
cursor: default;
|
||||
text-align: center;
|
||||
color: #fff;
|
||||
opacity: 0;
|
||||
font-size: 20px;
|
||||
background-color: rgba(0, 0, 0, .5);
|
||||
transition: opacity .3s;
|
||||
cursor: pointer;
|
||||
text-align: center;
|
||||
line-height: 200px;
|
||||
.el-icon-delete {
|
||||
font-size: 36px;
|
||||
}
|
||||
}
|
||||
&:hover {
|
||||
.image-preview-action {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
126
src/components/Upload/singleImage2.vue
Normal file
|
@ -0,0 +1,126 @@
|
|||
<template>
|
||||
<div class="singleImageUpload2 upload-container">
|
||||
<el-upload
|
||||
class="image-uploader"
|
||||
:data="dataObj"
|
||||
drag
|
||||
:multiple="false"
|
||||
:show-file-list="false"
|
||||
action="https://httpbin.org/post"
|
||||
|
||||
:on-success="handleImageScucess">
|
||||
<i class="el-icon-upload"></i>
|
||||
<div class="el-upload__text">Drag或<em>点击上传</em></div>
|
||||
</el-upload>
|
||||
<div v-show="imageUrl.length>0" class="image-preview">
|
||||
<div class="image-preview-wrapper" v-show="imageUrl.length>1">
|
||||
<img :src="imageUrl">
|
||||
<div class="image-preview-action">
|
||||
<i @click="rmImage" class="el-icon-delete"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
// 预览效果见专题
|
||||
import { getToken } from 'api/qiniu';
|
||||
export default {
|
||||
name: 'singleImageUpload2',
|
||||
props: {
|
||||
value: String
|
||||
},
|
||||
computed: {
|
||||
imageUrl() {
|
||||
return this.value
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
tempUrl: '',
|
||||
dataObj: { token: '', key: '' }
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
rmImage() {
|
||||
this.emitInput('');
|
||||
},
|
||||
emitInput(val) {
|
||||
this.$emit('input', val);
|
||||
},
|
||||
handleImageScucess() {
|
||||
this.emitInput(this.tempUrl)
|
||||
},
|
||||
beforeUpload() {
|
||||
const _self = this;
|
||||
return new Promise((resolve, reject) => {
|
||||
getToken().then(response => {
|
||||
const key = response.data.qiniu_key;
|
||||
const token = response.data.qiniu_token;
|
||||
_self._data.dataObj.token = token;
|
||||
_self._data.dataObj.key = key;
|
||||
this.tempUrl = response.data.qiniu_url;
|
||||
resolve(true);
|
||||
}).catch(err => {
|
||||
console.log(err);
|
||||
reject(false)
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style rel="stylesheet/scss" lang="scss" scoped>
|
||||
.upload-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
.image-uploader{
|
||||
height: 100%;
|
||||
}
|
||||
.image-preview {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
left: 0px;
|
||||
top: 0px;
|
||||
border: 1px dashed #d9d9d9;
|
||||
.image-preview-wrapper {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
.image-preview-action {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
left: 0;
|
||||
top: 0;
|
||||
cursor: default;
|
||||
text-align: center;
|
||||
color: #fff;
|
||||
opacity: 0;
|
||||
font-size: 20px;
|
||||
background-color: rgba(0, 0, 0, .5);
|
||||
transition: opacity .3s;
|
||||
cursor: pointer;
|
||||
text-align: center;
|
||||
line-height: 200px;
|
||||
.el-icon-delete {
|
||||
font-size: 36px;
|
||||
}
|
||||
}
|
||||
&:hover {
|
||||
.image-preview-action {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
154
src/components/Upload/singleImage3.vue
Normal file
|
@ -0,0 +1,154 @@
|
|||
<template>
|
||||
<div class="upload-container">
|
||||
<el-upload
|
||||
class="image-uploader"
|
||||
:data="dataObj"
|
||||
drag
|
||||
:multiple="false"
|
||||
:show-file-list="false"
|
||||
action="https://httpbin.org/post"
|
||||
|
||||
:on-success="handleImageScucess">
|
||||
<i class="el-icon-upload"></i>
|
||||
<div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
|
||||
</el-upload>
|
||||
<div class="image-preview image-app-preview">
|
||||
<div class="image-preview-wrapper" v-show="imageUrl.length>1">
|
||||
<div class='app-fake-conver'>  全球 付费节目单 最热 经济</div>
|
||||
<img :src="imageUrl">
|
||||
<div class="image-preview-action">
|
||||
<i @click="rmImage" class="el-icon-delete"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="image-preview">
|
||||
<div class="image-preview-wrapper" v-show="imageUrl.length>1">
|
||||
<img :src="imageUrl">
|
||||
<div class="image-preview-action">
|
||||
<i @click="rmImage" class="el-icon-delete"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
// 预览效果见文章
|
||||
import { getToken } from 'api/qiniu';
|
||||
export default {
|
||||
name: 'singleImageUpload',
|
||||
props: {
|
||||
value: String
|
||||
},
|
||||
computed: {
|
||||
imageUrl() {
|
||||
return this.value
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
tempUrl: '',
|
||||
dataObj: { token: '', key: '' }
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
rmImage() {
|
||||
this.emitInput('');
|
||||
},
|
||||
emitInput(val) {
|
||||
this.$emit('input', val);
|
||||
},
|
||||
handleImageScucess(file) {
|
||||
this.emitInput(file.files.file)
|
||||
},
|
||||
beforeUpload() {
|
||||
const _self = this;
|
||||
return new Promise((resolve, reject) => {
|
||||
getToken().then(response => {
|
||||
const key = response.data.qiniu_key;
|
||||
const token = response.data.qiniu_token;
|
||||
_self._data.dataObj.token = token;
|
||||
_self._data.dataObj.key = key;
|
||||
this.tempUrl = response.data.qiniu_url;
|
||||
resolve(true);
|
||||
}).catch(err => {
|
||||
console.log(err);
|
||||
reject(false)
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style rel="stylesheet/scss" lang="scss" scoped>
|
||||
@import "src/styles/mixin.scss";
|
||||
.upload-container {
|
||||
width: 100%;
|
||||
position: relative;
|
||||
@include clearfix;
|
||||
.image-uploader {
|
||||
width: 35%;
|
||||
float: left;
|
||||
}
|
||||
.image-preview {
|
||||
width: 200px;
|
||||
height: 200px;
|
||||
position: relative;
|
||||
border: 1px dashed #d9d9d9;
|
||||
float: left;
|
||||
margin-left: 50px;
|
||||
.image-preview-wrapper {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
.image-preview-action {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
left: 0;
|
||||
top: 0;
|
||||
cursor: default;
|
||||
text-align: center;
|
||||
color: #fff;
|
||||
opacity: 0;
|
||||
font-size: 20px;
|
||||
background-color: rgba(0, 0, 0, .5);
|
||||
transition: opacity .3s;
|
||||
cursor: pointer;
|
||||
text-align: center;
|
||||
line-height: 200px;
|
||||
.el-icon-delete {
|
||||
font-size: 36px;
|
||||
}
|
||||
}
|
||||
&:hover {
|
||||
.image-preview-action {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
.image-app-preview{
|
||||
width: 320px;
|
||||
height: 180px;
|
||||
position: relative;
|
||||
border: 1px dashed #d9d9d9;
|
||||
float: left;
|
||||
margin-left: 50px;
|
||||
.app-fake-conver{
|
||||
height: 44px;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
// background: rgba(0, 0, 0, .1);
|
||||
text-align: center;
|
||||
line-height: 64px;
|
||||
color: #fff;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
64
src/components/jsonEditor/index.vue
Normal file
|
@ -0,0 +1,64 @@
|
|||
<template>
|
||||
<div class='json-editor'>
|
||||
<textarea ref='textarea'></textarea>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
<script>
|
||||
import CodeMirror from 'codemirror';
|
||||
import 'codemirror/addon/lint/lint.css';
|
||||
import 'codemirror/lib/codemirror.css';
|
||||
import 'codemirror/theme/rubyblue.css';
|
||||
require('script-loader!jsonlint');
|
||||
import 'codemirror/mode/javascript/javascript'
|
||||
import 'codemirror/addon/lint/lint'
|
||||
import 'codemirror/addon/lint/json-lint';
|
||||
|
||||
export default {
|
||||
name: 'jsonEditor',
|
||||
data() {
|
||||
return {
|
||||
jsonEditor: false
|
||||
}
|
||||
},
|
||||
props: ['value'],
|
||||
watch: {
|
||||
value(value) {
|
||||
const editor_value = this.jsonEditor.getValue();
|
||||
if (value !== editor_value) {
|
||||
this.jsonEditor.setValue(JSON.stringify(this.value, null, 2));
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.jsonEditor = CodeMirror.fromTextArea(this.$refs.textarea, {
|
||||
lineNumbers: true,
|
||||
mode: 'application/json',
|
||||
gutters: ['CodeMirror-lint-markers'],
|
||||
theme: 'rubyblue',
|
||||
lint: true
|
||||
});
|
||||
|
||||
this.jsonEditor.setValue(JSON.stringify(this.value, null, 2));
|
||||
this.jsonEditor.on('change', cm => {
|
||||
this.$emit('changed', cm.getValue())
|
||||
this.$emit('input', cm.getValue())
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
getValue() {
|
||||
return this.jsonEditor.getValue()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.CodeMirror {
|
||||
height: 100%;
|
||||
}
|
||||
.json-editor .cm-s-rubyblue span.cm-string{
|
||||
color: #F08047;
|
||||
}
|
||||
</style>
|
156
src/components/twoDndList/index.vue
Normal file
|
@ -0,0 +1,156 @@
|
|||
<template>
|
||||
<div class="twoDndList">
|
||||
<div class="twoDndList-list" :style="{width:width1}">
|
||||
<h3>{{list1Title}}</h3>
|
||||
<draggable :list="list1" class="dragArea" :options="{group:'article'}">
|
||||
<div class="list-complete-item" v-for="element in list1">
|
||||
<div class="list-complete-item-handle">[{{element.author}}] {{element.title}}</div>
|
||||
<div style="position:absolute;right:0px;">
|
||||
<span style="float: right ;margin-top: -20px;margin-right:5px;" @click="deleteEle(element)">
|
||||
<i style="color:#ff4949" class="el-icon-delete"></i>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</draggable>
|
||||
</div>
|
||||
|
||||
<div class="twoDndList-list" :style="{width:width2}">
|
||||
<h3>{{list2Title}}</h3>
|
||||
<draggable :list="filterList2" class="dragArea" :options="{group:'article'}">
|
||||
<div class="list-complete-item" v-for="element in filterList2">
|
||||
<div class='list-complete-item-handle2' @click="pushEle(element)"> [{{element.author}}] {{element.title}}</div>
|
||||
</div>
|
||||
</draggable>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import draggable from 'vuedraggable'
|
||||
export default {
|
||||
name: 'twoDndList',
|
||||
components: { draggable },
|
||||
computed: {
|
||||
filterList2() {
|
||||
return this.list2.filter(v => {
|
||||
if (this.isNotInList1(v)) {
|
||||
return v
|
||||
}
|
||||
return false;
|
||||
})
|
||||
}
|
||||
},
|
||||
props: {
|
||||
list1: {
|
||||
type: Array,
|
||||
default() {
|
||||
return []
|
||||
}
|
||||
},
|
||||
list2: {
|
||||
type: Array,
|
||||
default() {
|
||||
return []
|
||||
}
|
||||
},
|
||||
list1Title: {
|
||||
type: String,
|
||||
default: 'list1'
|
||||
},
|
||||
list2Title: {
|
||||
type: String,
|
||||
default: 'list2'
|
||||
},
|
||||
width1: {
|
||||
type: String,
|
||||
default: '48%'
|
||||
},
|
||||
width2: {
|
||||
type: String,
|
||||
default: '48%'
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
isNotInList1(v) {
|
||||
return this.list1.every(k => v.id !== k.id)
|
||||
},
|
||||
isNotInList2(v) {
|
||||
return this.list2.every(k => v.id !== k.id)
|
||||
},
|
||||
deleteEle(ele) {
|
||||
for (const item of this.list1) {
|
||||
if (item.id === ele.id) {
|
||||
const index = this.list1.indexOf(item);
|
||||
this.list1.splice(index, 1)
|
||||
break
|
||||
}
|
||||
}
|
||||
if (this.isNotInList2(ele)) {
|
||||
this.list2.unshift(ele)
|
||||
}
|
||||
},
|
||||
pushEle(ele) {
|
||||
this.list1.push(ele)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style rel="stylesheet/scss" lang="scss" scoped>
|
||||
.twoDndList {
|
||||
background: #fff;
|
||||
padding-bottom: 40px;
|
||||
&:after {
|
||||
content: "";
|
||||
display: table;
|
||||
clear: both;
|
||||
}
|
||||
.twoDndList-list {
|
||||
float: left;
|
||||
padding-bottom: 30px;
|
||||
&:first-of-type {
|
||||
margin-right: 2%;
|
||||
}
|
||||
.dragArea {
|
||||
margin-top: 15px;
|
||||
min-height: 50px;
|
||||
padding-bottom: 30px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.list-complete-item {
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
font-size: 14px;
|
||||
padding: 5px 12px;
|
||||
margin-top: 4px;
|
||||
border: 1px solid #bfcbd9;
|
||||
transition: all 1s;
|
||||
}
|
||||
|
||||
.list-complete-item-handle {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
margin-right: 50px;
|
||||
}
|
||||
.list-complete-item-handle2{
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
.list-complete-item.sortable-chosen {
|
||||
background: #4AB7BD;
|
||||
}
|
||||
|
||||
.list-complete-item.sortable-ghost {
|
||||
background: #30B08F;
|
||||
}
|
||||
|
||||
.list-complete-enter, .list-complete-leave-active {
|
||||
opacity: 0;
|
||||
}
|
||||
</style>
|
101
src/directive/sticky.js
Normal file
|
@ -0,0 +1,101 @@
|
|||
(function() {
|
||||
const vueSticky = {};
|
||||
let listenAction;
|
||||
vueSticky.install = Vue => {
|
||||
Vue.directive('sticky', {
|
||||
inserted(el, binding) {
|
||||
const params = binding.value || {},
|
||||
stickyTop = params.stickyTop || 0,
|
||||
zIndex = params.zIndex || 1000,
|
||||
elStyle = el.style;
|
||||
|
||||
elStyle.position = '-webkit-sticky';
|
||||
elStyle.position = 'sticky';
|
||||
|
||||
// if the browser support css sticky(Currently Safari, Firefox and Chrome Canary)
|
||||
// if (~elStyle.position.indexOf('sticky')) {
|
||||
// elStyle.top = `${stickyTop}px`;
|
||||
// elStyle.zIndex = zIndex;
|
||||
// return
|
||||
// }
|
||||
|
||||
const elHeight = el.getBoundingClientRect().height;
|
||||
const elWidth = el.getBoundingClientRect().width;
|
||||
elStyle.cssText = `top: ${stickyTop}px; z-index: ${zIndex}`;
|
||||
|
||||
const parentElm = el.parentNode || document.documentElement;
|
||||
const placeholder = document.createElement('div');
|
||||
placeholder.style.display = 'none';
|
||||
placeholder.style.width = `${elWidth}px`;
|
||||
placeholder.style.height = `${elHeight}px`;
|
||||
parentElm.insertBefore(placeholder, el)
|
||||
|
||||
let active = false;
|
||||
|
||||
const getScroll = (target, top) => {
|
||||
const prop = top ? 'pageYOffset' : 'pageXOffset';
|
||||
const method = top ? 'scrollTop' : 'scrollLeft';
|
||||
let ret = target[prop];
|
||||
if (typeof ret !== 'number') {
|
||||
ret = window.document.documentElement[method];
|
||||
}
|
||||
return ret;
|
||||
};
|
||||
|
||||
const sticky = () => {
|
||||
if (active) {
|
||||
return
|
||||
}
|
||||
if (!elStyle.height) {
|
||||
elStyle.height = `${el.offsetHeight}px`
|
||||
}
|
||||
|
||||
elStyle.position = 'fixed';
|
||||
elStyle.width = `${elWidth}px`;
|
||||
placeholder.style.display = 'inline-block';
|
||||
active = true
|
||||
};
|
||||
|
||||
const reset = () => {
|
||||
if (!active) {
|
||||
return
|
||||
}
|
||||
|
||||
elStyle.position = '';
|
||||
placeholder.style.display = 'none';
|
||||
active = false;
|
||||
};
|
||||
|
||||
const check = () => {
|
||||
const scrollTop = getScroll(window, true);
|
||||
const offsetTop = el.getBoundingClientRect().top;
|
||||
if (offsetTop < stickyTop) {
|
||||
sticky();
|
||||
} else {
|
||||
if (scrollTop < elHeight + stickyTop) {
|
||||
reset()
|
||||
}
|
||||
}
|
||||
};
|
||||
listenAction = () => {
|
||||
check()
|
||||
};
|
||||
|
||||
window.addEventListener('scroll', listenAction)
|
||||
},
|
||||
|
||||
unbind() {
|
||||
window.removeEventListener('scroll', listenAction)
|
||||
}
|
||||
})
|
||||
};
|
||||
if (typeof exports == 'object') {
|
||||
module.exports = vueSticky
|
||||
} else if (typeof define == 'function' && define.amd) {
|
||||
define([], () => vueSticky)
|
||||
} else if (window.Vue) {
|
||||
window.vueSticky = vueSticky;
|
||||
Vue.use(vueSticky)
|
||||
}
|
||||
}());
|
||||
|
26
src/directive/waves.css
Normal file
|
@ -0,0 +1,26 @@
|
|||
.waves-ripple {
|
||||
position: absolute;
|
||||
border-radius: 100%;
|
||||
background-color: rgba(0, 0, 0, 0.15);
|
||||
background-clip: padding-box;
|
||||
pointer-events: none;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
-webkit-transform: scale(0);
|
||||
-ms-transform: scale(0);
|
||||
transform: scale(0);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.waves-ripple.z-active {
|
||||
opacity: 0;
|
||||
-webkit-transform: scale(2);
|
||||
-ms-transform: scale(2);
|
||||
transform: scale(2);
|
||||
-webkit-transition: opacity 1.2s ease-out, -webkit-transform 0.6s ease-out;
|
||||
transition: opacity 1.2s ease-out, -webkit-transform 0.6s ease-out;
|
||||
transition: opacity 1.2s ease-out, transform 0.6s ease-out;
|
||||
transition: opacity 1.2s ease-out, transform 0.6s ease-out, -webkit-transform 0.6s ease-out;
|
||||
}
|
54
src/directive/waves.js
Normal file
|
@ -0,0 +1,54 @@
|
|||
import './waves.css';
|
||||
(function() {
|
||||
const vueWaves = {};
|
||||
vueWaves.install = (Vue, options = {}) => {
|
||||
Vue.directive('waves', {
|
||||
bind(el, binding) {
|
||||
el.addEventListener('click', e => {
|
||||
const customOpts = Object.assign(options, binding.value);
|
||||
const opts = Object.assign({
|
||||
ele: el, // 波纹作用元素
|
||||
type: 'hit', // hit点击位置扩散center中心点扩展
|
||||
color: 'rgba(0, 0, 0, 0.15)' // 波纹颜色
|
||||
}, customOpts),
|
||||
target = opts.ele;
|
||||
if (target) {
|
||||
target.style.position = 'relative';
|
||||
target.style.overflow = 'hidden';
|
||||
const rect = target.getBoundingClientRect();
|
||||
let ripple = target.querySelector('.waves-ripple');
|
||||
if (!ripple) {
|
||||
ripple = document.createElement('span');
|
||||
ripple.className = 'waves-ripple';
|
||||
ripple.style.height = ripple.style.width = Math.max(rect.width, rect.height) + 'px';
|
||||
target.appendChild(ripple);
|
||||
} else {
|
||||
ripple.className = 'waves-ripple';
|
||||
}
|
||||
switch (opts.type) {
|
||||
case 'center':
|
||||
ripple.style.top = (rect.height / 2 - ripple.offsetHeight / 2) + 'px';
|
||||
ripple.style.left = (rect.width / 2 - ripple.offsetWidth / 2) + 'px';
|
||||
break;
|
||||
default:
|
||||
ripple.style.top = (e.pageY - rect.top - ripple.offsetHeight / 2 - document.body.scrollTop) + 'px';
|
||||
ripple.style.left = (e.pageX - rect.left - ripple.offsetWidth / 2 - document.body.scrollLeft) + 'px';
|
||||
}
|
||||
ripple.style.backgroundColor = opts.color;
|
||||
ripple.className = 'waves-ripple z-active';
|
||||
return false;
|
||||
}
|
||||
}, false);
|
||||
}
|
||||
})
|
||||
};
|
||||
if (typeof exports == 'object') {
|
||||
module.exports = vueWaves
|
||||
} else if (typeof define == 'function' && define.amd) {
|
||||
define([], () => vueWaves)
|
||||
} else if (window.Vue) {
|
||||
window.vueWaves = vueWaves;
|
||||
Vue.use(vueWaves)
|
||||
}
|
||||
}());
|
||||
|
108
src/filters/index.js
Normal file
|
@ -0,0 +1,108 @@
|
|||
function pluralize(time, label) {
|
||||
if (time === 1) {
|
||||
return time + label
|
||||
}
|
||||
return time + label + 's'
|
||||
}
|
||||
export function timeAgo(time) {
|
||||
const between = Date.now() / 1000 - Number(time);
|
||||
if (between < 3600) {
|
||||
return pluralize(~~(between / 60), ' minute')
|
||||
} else if (between < 86400) {
|
||||
return pluralize(~~(between / 3600), ' hour')
|
||||
} else {
|
||||
return pluralize(~~(between / 86400), ' day')
|
||||
}
|
||||
}
|
||||
|
||||
export function parseTime(time, cFormat) {
|
||||
if (arguments.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ((time + '').length === 10) {
|
||||
time = +time * 1000
|
||||
}
|
||||
|
||||
|
||||
const format = cFormat || '{y}-{m}-{d} {h}:{i}:{s}';
|
||||
let date;
|
||||
if (typeof time == 'object') {
|
||||
date = time;
|
||||
} else {
|
||||
date = new Date(parseInt(time));
|
||||
}
|
||||
const formatObj = {
|
||||
y: date.getFullYear(),
|
||||
m: date.getMonth() + 1,
|
||||
d: date.getDate(),
|
||||
h: date.getHours(),
|
||||
i: date.getMinutes(),
|
||||
s: date.getSeconds(),
|
||||
a: date.getDay()
|
||||
};
|
||||
const time_str = format.replace(/{(y|m|d|h|i|s|a)+}/g, (result, key) => {
|
||||
let value = formatObj[key];
|
||||
if (key === 'a') return ['一', '二', '三', '四', '五', '六', '日'][value - 1];
|
||||
if (result.length > 0 && value < 10) {
|
||||
value = '0' + value;
|
||||
}
|
||||
return value || 0;
|
||||
});
|
||||
return time_str;
|
||||
}
|
||||
|
||||
export function formatTime(time, option) {
|
||||
time = +time * 1000;
|
||||
const d = new Date(time);
|
||||
const now = Date.now();
|
||||
|
||||
const diff = (now - d) / 1000;
|
||||
|
||||
if (diff < 30) {
|
||||
return '刚刚'
|
||||
} else if (diff < 3600) { // less 1 hour
|
||||
return Math.ceil(diff / 60) + '分钟前'
|
||||
} else if (diff < 3600 * 24) {
|
||||
return Math.ceil(diff / 3600) + '小时前'
|
||||
} else if (diff < 3600 * 24 * 2) {
|
||||
return '1天前'
|
||||
}
|
||||
if (option) {
|
||||
return parseTime(time, option)
|
||||
} else {
|
||||
return d.getMonth() + 1 + '月' + d.getDate() + '日' + d.getHours() + '时' + d.getMinutes() + '分'
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* 数字 格式化*/
|
||||
export function nFormatter(num, digits) {
|
||||
const si = [
|
||||
{ value: 1E18, symbol: 'E' },
|
||||
{ value: 1E15, symbol: 'P' },
|
||||
{ value: 1E12, symbol: 'T' },
|
||||
{ value: 1E9, symbol: 'G' },
|
||||
{ value: 1E6, symbol: 'M' },
|
||||
{ value: 1E3, symbol: 'k' }
|
||||
];
|
||||
for (let i = 0; i < si.length; i++) {
|
||||
if (num >= si[i].value) {
|
||||
return (num / si[i].value + 0.1).toFixed(digits).replace(/\.0+$|(\.[0-9]*[1-9])0+$/, '$1') + si[i].symbol;
|
||||
}
|
||||
}
|
||||
return num.toString();
|
||||
}
|
||||
|
||||
|
||||
export function html2Text(val) {
|
||||
const div = document.createElement('div');
|
||||
div.innerHTML = val;
|
||||
return div.textContent || div.innerText;
|
||||
}
|
||||
|
||||
|
||||
export function toThousandslsFilter(num) {
|
||||
return (+num || 0).toString().replace(/(\d)(?=(?:\d{3})+$)/g, '$1,');
|
||||
}
|
109
src/main.js
Normal file
|
@ -0,0 +1,109 @@
|
|||
// The Vue build version to load with the `import` command
|
||||
// (runtime-only or standalone) has been set in webpack.base.conf with an alias.
|
||||
import Vue from 'vue';
|
||||
import App from './App';
|
||||
import router from './router';
|
||||
import store from './store';
|
||||
import ElementUI from 'element-ui';
|
||||
import 'element-ui/lib/theme-default/index.css';
|
||||
import 'assets/custom-theme/index.css'; // https://github.com/PanJiaChen/custom-element-theme
|
||||
import NProgress from 'nprogress';
|
||||
import 'nprogress/nprogress.css';
|
||||
import 'normalize.css/normalize.css';
|
||||
import 'styles/index.scss';
|
||||
import 'components/Icon-svg/index';
|
||||
import 'assets/iconfont/iconfont';
|
||||
import * as filters from './filters';
|
||||
import Multiselect from 'vue-multiselect';
|
||||
import Sticky from 'components/Sticky';
|
||||
import 'vue-multiselect/dist/vue-multiselect.min.css';
|
||||
import vueWaves from './directive/waves';
|
||||
import vueSticky from './directive/sticky';
|
||||
import errLog from 'store/errLog';
|
||||
import './mock/index.js';
|
||||
// import './styles/mixin.scss';
|
||||
|
||||
// register globally
|
||||
Vue.component('multiselect', Multiselect);
|
||||
Vue.component('Sticky', Sticky);
|
||||
Vue.use(ElementUI);
|
||||
Vue.use(vueWaves);
|
||||
Vue.use(vueSticky);
|
||||
|
||||
|
||||
// register global utility filters.
|
||||
Object.keys(filters).forEach(key => {
|
||||
Vue.filter(key, filters[key])
|
||||
});
|
||||
|
||||
function hasPermission(roles, permissionRoles) {
|
||||
if (roles.indexOf('admin') >= 0) return true;
|
||||
return roles.some(role => permissionRoles.indexOf(role) >= 0)
|
||||
}
|
||||
// register global progress.
|
||||
const whiteList = ['/login', '/authredirect', '/reset', '/sendpwd'];// 不重定向白名单
|
||||
router.beforeEach((to, from, next) => {
|
||||
NProgress.start();
|
||||
if (store.getters.token) {
|
||||
if (to.path === '/login') {
|
||||
next({ path: '/' });
|
||||
} else {
|
||||
if (to.meta && to.meta.role) {
|
||||
if (hasPermission(store.getters.roles, to.meta.role)) {
|
||||
next();
|
||||
} else {
|
||||
next('/401');
|
||||
}
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (whiteList.indexOf(to.path) !== -1) {
|
||||
next()
|
||||
} else {
|
||||
next('/login')
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
router.afterEach(() => {
|
||||
NProgress.done();
|
||||
});
|
||||
|
||||
// window.onunhandledrejection = e => {
|
||||
// console.log('unhandled', e.reason, e.promise);
|
||||
// e.preventDefault()
|
||||
// };
|
||||
|
||||
// 生产环境错误日志
|
||||
if (process.env === 'production') {
|
||||
Vue.config.errorHandler = function(err, vm) {
|
||||
console.log(err, window.location.href);
|
||||
errLog.pushLog({
|
||||
err,
|
||||
url: window.location.href,
|
||||
vm
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
// window.onerror = function (msg, url, lineNo, columnNo, error) {
|
||||
// console.log('window')
|
||||
// };
|
||||
//
|
||||
// console.error = (function (origin) {
|
||||
// return function (errorlog) {
|
||||
// // handler();//基于业务的日志记录及数据报错
|
||||
// console.log('console'+errorlog)
|
||||
// origin.call(console, errorlog);
|
||||
// }
|
||||
// })(console.error);
|
||||
|
||||
new Vue({
|
||||
router,
|
||||
store,
|
||||
render: h => h(App)
|
||||
}).$mount('#app');
|
||||
|
||||
|
49
src/mock/article.js
Normal file
|
@ -0,0 +1,49 @@
|
|||
import Mock from 'mockjs';
|
||||
|
||||
|
||||
const List = [];
|
||||
const count = 20;
|
||||
|
||||
|
||||
for (let i = 0; i < count; i++) {
|
||||
List.push(Mock.mock({
|
||||
id: '@id',
|
||||
title: '@ctitle(10, 20)',
|
||||
'status|1': ['published', 'draft'],
|
||||
author: '@cname',
|
||||
display_time: '@datetime',
|
||||
pageviews: '@integer(300, 5000)'
|
||||
}));
|
||||
}
|
||||
|
||||
export default {
|
||||
getList: () => new Promise(resolve => {
|
||||
setTimeout(() => {
|
||||
resolve([200, {
|
||||
data: List
|
||||
}]);
|
||||
}, 100);
|
||||
}),
|
||||
getArticle: () => new Promise(resolve => {
|
||||
setTimeout(() => {
|
||||
resolve([200, {
|
||||
data: {
|
||||
id: 120000000001,
|
||||
author: { key: 'mockPan' },
|
||||
source_name: '原创作者',
|
||||
category_item: [{ key: 'global', name: '全球' }],
|
||||
comment_disabled: false,
|
||||
content: '<p>我是测试数据我是测试数据</p><p><img class="wscnph" src="https://wpimg.wallstcn.com/4c69009c-0fd4-4153-b112-6cb53d1cf943" data-wscntype="image" data-wscnh="300" data-wscnw="400" data-mce-src="https://wpimg.wallstcn.com/4c69009c-0fd4-4153-b112-6cb53d1cf943"></p>"',
|
||||
content_short: '我是测试数据',
|
||||
display_time: +new Date(),
|
||||
image_uri: 'https://wpimg.wallstcn.com/e4558086-631c-425c-9430-56ffb46e70b3',
|
||||
platforms: ['a-platform'],
|
||||
source_uri: 'https://github.com/PanJiaChen/vue-element-admin',
|
||||
status: 'published',
|
||||
tags: [],
|
||||
title: ''
|
||||
}
|
||||
}]);
|
||||
}, 100);
|
||||
})
|
||||
};
|
47
src/mock/article_table.js
Normal file
|
@ -0,0 +1,47 @@
|
|||
import Mock from 'mockjs';
|
||||
|
||||
|
||||
const List = [];
|
||||
const count = 100;
|
||||
|
||||
for (let i = 0; i < count; i++) {
|
||||
List.push(Mock.mock({
|
||||
id: '@increment',
|
||||
timestamp: +Mock.Random.date('T'),
|
||||
author: '@cname',
|
||||
title: '@ctitle(10, 20)',
|
||||
forecast: '@float(0, 100, 2, 2)',
|
||||
importance: '@integer(1, 3)',
|
||||
'type|1': ['FD', 'FE', 'BI', 'VN'],
|
||||
'status|1': ['published', 'draft', 'deleted'],
|
||||
pageviews: '@integer(300, 5000)'
|
||||
}));
|
||||
}
|
||||
|
||||
export default {
|
||||
getList: config => {
|
||||
const { importance, type, title, page, limit } = config.params;
|
||||
const mockList = List.filter(item => {
|
||||
if (importance && item.importance !== importance) return false;
|
||||
if (type && item.type !== type) return false;
|
||||
if (title && item.title.indexOf(title) < 0) return false;
|
||||
return true;
|
||||
});
|
||||
const pageList = mockList.filter((item, index) => index < limit * page && index >= limit * (page - 1));
|
||||
return new Promise(resolve => {
|
||||
setTimeout(() => {
|
||||
resolve([200, {
|
||||
total: mockList.length,
|
||||
items: pageList
|
||||
}]);
|
||||
}, 100);
|
||||
})
|
||||
},
|
||||
getPv: () => new Promise(resolve => {
|
||||
setTimeout(() => {
|
||||
resolve([200, {
|
||||
pvData: [{ key: 'PC网站', pv: 1024 }, { key: 'mobile网站', pv: 1024 }, { key: 'ios', pv: 1024 }, { key: 'android', pv: 1024 }]
|
||||
}]);
|
||||
}, 100);
|
||||
})
|
||||
};
|
26
src/mock/index.js
Normal file
|
@ -0,0 +1,26 @@
|
|||
import axios from 'axios';
|
||||
import MockAdapter from 'axios-mock-adapter';
|
||||
import loginAPI from './login';
|
||||
import articleAPI from './article';
|
||||
import article_tableAPI from './article_table';
|
||||
import remoteSearchAPI from './remoteSearch';
|
||||
const mock = new MockAdapter(axios);
|
||||
|
||||
// 登录相关
|
||||
mock.onPost('/login/loginbyemail').reply(loginAPI.loginByEmail);
|
||||
mock.onPost('/login/logout').reply(loginAPI.logout);
|
||||
mock.onGet('/user/info').reply(loginAPI.getInfo);
|
||||
|
||||
// 文章相关
|
||||
mock.onGet('/article/list').reply(articleAPI.getList);
|
||||
mock.onGet('/article/detail').reply(articleAPI.getArticle);
|
||||
|
||||
// table example相关
|
||||
mock.onGet('/article_table/list').reply(article_tableAPI.getList);
|
||||
mock.onGet('/article_table/pv').reply(article_tableAPI.getPv);
|
||||
|
||||
// 搜索相关
|
||||
mock.onGet('/search/user').reply(remoteSearchAPI.searchUser);
|
||||
|
||||
|
||||
export default mock;
|
60
src/mock/login.js
Normal file
|
@ -0,0 +1,60 @@
|
|||
const userMap = {
|
||||
admin: {
|
||||
role: ['admin'],
|
||||
token: 'admin',
|
||||
introduction: '我是超级管理员',
|
||||
avatar: 'https://wdl.wallstreetcn.com/48a3e1e0-ea2c-4a4e-9928-247645e3428b',
|
||||
name: '超级管理员小潘'
|
||||
},
|
||||
editor: {
|
||||
role: ['editor'],
|
||||
token: 'editor',
|
||||
introduction: '我是编辑',
|
||||
avatar: 'https://wdl.wallstreetcn.com/48a3e1e0-ea2c-4a4e-9928-247645e3428b',
|
||||
name: '普通编辑小张'
|
||||
|
||||
},
|
||||
developer: {
|
||||
role: ['develop'],
|
||||
token: 'develop',
|
||||
introduction: '我是开发',
|
||||
avatar: 'https://wdl.wallstreetcn.com/48a3e1e0-ea2c-4a4e-9928-247645e3428b',
|
||||
name: '工程师小王'
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
loginByEmail: config => {
|
||||
const { email } = config.params;
|
||||
return new Promise((resolve, reject) => {
|
||||
if (userMap[email.split('@')[0]]) {
|
||||
setTimeout(() => {
|
||||
resolve([200, {
|
||||
data: userMap[email.split('@')[0]]
|
||||
}]);
|
||||
}, 500);
|
||||
} else {
|
||||
reject('账号不正确')
|
||||
}
|
||||
})
|
||||
},
|
||||
getInfo: config => {
|
||||
const { token } = config.params;
|
||||
return new Promise((resolve, reject) => {
|
||||
if (userMap[token]) {
|
||||
setTimeout(() => {
|
||||
resolve([200, {
|
||||
data: userMap[token]
|
||||
}]);
|
||||
}, 100);
|
||||
} else {
|
||||
reject('获取失败')
|
||||
}
|
||||
})
|
||||
},
|
||||
logout: () => new Promise(resolve => {
|
||||
setTimeout(() => {
|
||||
resolve([200, { data: 'success' }]);
|
||||
}, 100);
|
||||
})
|
||||
};
|
29
src/mock/remoteSearch.js
Normal file
|
@ -0,0 +1,29 @@
|
|||
import Mock from 'mockjs';
|
||||
|
||||
const NameList = [];
|
||||
const count = 100;
|
||||
|
||||
for (let i = 0; i < count; i++) {
|
||||
NameList.push(Mock.mock({
|
||||
name: '@first'
|
||||
}));
|
||||
}
|
||||
NameList.push({ name: 'mockPan' })
|
||||
|
||||
export default {
|
||||
searchUser: config => {
|
||||
const { name } = config.params;
|
||||
const mockNameList = NameList.filter(item => {
|
||||
const lowerCaseName = item.name.toLowerCase()
|
||||
if (name && lowerCaseName.indexOf(name.toLowerCase()) < 0) return false;
|
||||
return true;
|
||||
});
|
||||
return new Promise(resolve => {
|
||||
setTimeout(() => {
|
||||
resolve([200, {
|
||||
items: mockNameList
|
||||
}]);
|
||||
}, 100);
|
||||
})
|
||||
}
|
||||
};
|
221
src/router/index.js
Normal file
|
@ -0,0 +1,221 @@
|
|||
import Vue from 'vue';
|
||||
import Router from 'vue-router';
|
||||
|
||||
/* layout*/
|
||||
import Layout from '../views/layout/Layout';
|
||||
|
||||
// dashboard
|
||||
// import dashboard from '../views/dashboard/index';
|
||||
const dashboard = resolve => require(['../views/dashboard/index'], resolve);
|
||||
|
||||
/* error page*/
|
||||
const Err404 = resolve => require(['../views/error/404'], resolve);
|
||||
const Err401 = resolve => require(['../views/error/401'], resolve);
|
||||
|
||||
/* login*/
|
||||
import Login from '../views/login/';
|
||||
import authRedirect from '../views/login/authredirect';
|
||||
import sendPWD from '../views/login/sendpwd';
|
||||
import reset from '../views/login/reset';
|
||||
|
||||
/* Introduction*/
|
||||
const Introduction = resolve => require(['../views/introduction/index'], resolve);
|
||||
|
||||
/* components*/
|
||||
const componentsIndex = resolve => require(['../views/components/index'], resolve);
|
||||
const Tinymce = resolve => require(['../views/components/tinymce'], resolve);
|
||||
const Markdown = resolve => require(['../views/components/markdown'], resolve);
|
||||
const JsonEditor = resolve => require(['../views/components/jsoneditor'], resolve);
|
||||
const DndList = resolve => require(['../views/components/dndlist'], resolve);
|
||||
const AvatarUpload = resolve => require(['../views/components/avatarupload'], resolve);
|
||||
const Dropzone = resolve => require(['../views/components/dropzone'], resolve);
|
||||
const Sticky = resolve => require(['../views/components/sticky'], resolve);
|
||||
const SplitPane = resolve => require(['../views/components/splitpane'], resolve);
|
||||
const CountTo = resolve => require(['../views/components/countto'], resolve);
|
||||
const Mixin = resolve => require(['../views/components/mixin'], resolve);
|
||||
|
||||
|
||||
/* charts*/
|
||||
const chartIndex = resolve => require(['../views/charts/index'], resolve);
|
||||
const KeyboardChart = resolve => require(['../views/charts/keyboard'], resolve);
|
||||
const KeyboardChart2 = resolve => require(['../views/charts/keyboard2'], resolve);
|
||||
const LineMarker = resolve => require(['../views/charts/line'], resolve);
|
||||
const MixChart = resolve => require(['../views/charts/mixchart'], resolve);
|
||||
|
||||
/* error log*/
|
||||
const ErrorLog = resolve => require(['../views/errlog/index'], resolve);
|
||||
|
||||
/* excel*/
|
||||
const ExcelDownload = resolve => require(['../views/excel/index'], resolve);
|
||||
|
||||
/* theme*/
|
||||
const Theme = resolve => require(['../views/theme/index'], resolve);
|
||||
|
||||
/* example*/
|
||||
const DynamicTable = resolve => require(['../views/example/dynamictable'], resolve);
|
||||
const Table = resolve => require(['../views/example/table'], resolve);
|
||||
const Form1 = resolve => require(['../views/example/form1'], resolve);
|
||||
const Form2 = resolve => require(['../views/example/form2'], resolve);
|
||||
|
||||
/* permission */
|
||||
const Permission = resolve => require(['../views/permission/index'], resolve);
|
||||
|
||||
/* admin*/
|
||||
// const AdminCreateUser = resolve => require(['../views/admin/createUser'], resolve);
|
||||
// const QuicklyCreateUser = resolve => require(['../views/admin/quicklycreate'], resolve);
|
||||
// const UserProfile = resolve => require(['../views/admin/profile'], resolve);
|
||||
// const UsersList = resolve => require(['../views/admin/usersList'], resolve);
|
||||
|
||||
|
||||
|
||||
|
||||
Vue.use(Router);
|
||||
|
||||
export default new Router({
|
||||
// mode: 'history', //后端支持可开
|
||||
scrollBehavior: () => ({ y: 0 }),
|
||||
routes: [
|
||||
{ path: '/login', component: Login, hidden: true },
|
||||
{ path: '/authredirect', component: authRedirect, hidden: true },
|
||||
{ path: '/sendpwd', component: sendPWD, hidden: true },
|
||||
{ path: '/reset', component: reset, hidden: true },
|
||||
{ path: '/404', component: Err404, hidden: true },
|
||||
{ path: '/401', component: Err401, hidden: true },
|
||||
{
|
||||
path: '/',
|
||||
component: Layout,
|
||||
redirect: '/dashboard',
|
||||
name: '首页',
|
||||
hidden: true,
|
||||
children: [{ path: 'dashboard', component: dashboard }]
|
||||
},
|
||||
{
|
||||
path: '/introduction',
|
||||
component: Layout,
|
||||
redirect: '/introduction/index',
|
||||
name: '简述',
|
||||
icon: 'xinrenzhinan',
|
||||
noDropdown: true,
|
||||
children: [
|
||||
{ path: 'index', component: Introduction, name: '简述' }
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/permission',
|
||||
component: Layout,
|
||||
redirect: '/permission/index',
|
||||
name: '权限测试',
|
||||
icon: 'quanxian',
|
||||
meta: { role: ['admin'] },
|
||||
noDropdown: true,
|
||||
children: [
|
||||
{ path: 'index', component: Permission, name: '权限测试页', meta: { role: ['admin'] } }
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/components',
|
||||
component: Layout,
|
||||
redirect: '/components/index',
|
||||
name: '组件',
|
||||
icon: 'zujian',
|
||||
children: [
|
||||
{ path: 'index', component: componentsIndex, name: '介绍 ' },
|
||||
{ path: 'tinymce', component: Tinymce, name: '富文本编辑器' },
|
||||
{ path: 'markdown', component: Markdown, name: 'Markdown' },
|
||||
{ path: 'jsoneditor', component: JsonEditor, name: 'JSON编辑器' },
|
||||
{ path: 'dndlist', component: DndList, name: '列表拖拽' },
|
||||
{ path: 'splitpane', component: SplitPane, name: 'SplitPane' },
|
||||
{ path: 'avatarupload', component: AvatarUpload, name: '头像上传' },
|
||||
{ path: 'dropzone', component: Dropzone, name: 'Dropzone' },
|
||||
{ path: 'sticky', component: Sticky, name: 'Sticky' },
|
||||
{ path: 'countto', component: CountTo, name: 'CountTo' },
|
||||
{ path: 'mixin', component: Mixin, name: '小组件' }
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/charts',
|
||||
component: Layout,
|
||||
redirect: '/charts/index',
|
||||
name: '图表',
|
||||
icon: 'tubiaoleixingzhengchang',
|
||||
children: [
|
||||
{ path: 'index', component: chartIndex, name: '介绍' },
|
||||
{ path: 'keyboard', component: KeyboardChart, name: '键盘图表' },
|
||||
{ path: 'keyboard2', component: KeyboardChart2, name: '键盘图表2' },
|
||||
{ path: 'line', component: LineMarker, name: '折线图' },
|
||||
{ path: 'mixchart', component: MixChart, name: '混合图表' }
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/errorpage',
|
||||
component: Layout,
|
||||
redirect: 'noredirect',
|
||||
name: '错误页面',
|
||||
icon: '404',
|
||||
children: [
|
||||
{ path: '401', component: Err401, name: '401' },
|
||||
{ path: '404', component: Err404, name: '404' }
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/errlog',
|
||||
component: Layout,
|
||||
redirect: 'noredirect',
|
||||
name: 'errlog',
|
||||
icon: 'bug',
|
||||
noDropdown: true,
|
||||
children: [
|
||||
{ path: 'log', component: ErrorLog, name: '错误日志' }
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/excel',
|
||||
component: Layout,
|
||||
redirect: 'noredirect',
|
||||
name: 'excel',
|
||||
icon: 'EXCEL',
|
||||
noDropdown: true,
|
||||
children: [
|
||||
{ path: 'download', component: ExcelDownload, name: '导出excel' }
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/theme',
|
||||
component: Layout,
|
||||
redirect: 'noredirect',
|
||||
name: 'theme',
|
||||
icon: 'theme',
|
||||
noDropdown: true,
|
||||
children: [
|
||||
{ path: 'index', component: Theme, name: '换肤' }
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/example',
|
||||
component: Layout,
|
||||
redirect: 'noredirect',
|
||||
name: '综合实例',
|
||||
icon: 'zonghe',
|
||||
children: [
|
||||
{ path: 'dynamictable', component: DynamicTable, name: '动态table' },
|
||||
{ path: 'table', component: Table, name: '综合table' },
|
||||
{ path: 'form1', component: Form1, name: '综合form1' }
|
||||
// { path: 'form2', component: Form2, name: '综合form2' }
|
||||
]
|
||||
},
|
||||
// {
|
||||
// path: '/admin',
|
||||
// component: Layout,
|
||||
// redirect: 'noredirect',
|
||||
// name: '后台管理',
|
||||
// icon: 'geren1',
|
||||
// children: [
|
||||
// { path: 'createuser', component: AdminCreateUser, name: '管理员', meta: { role: ['admin'] } },
|
||||
// { path: 'list', component: UsersList, name: '后台用户列表', meta: { role: ['super_editor', 'product', 'author_assistant'] } },
|
||||
// { path: 'qicklyCreate', component: QuicklyCreateUser, name: '一键创建账户', meta: { role: ['super_editor', 'gold_editor', 'weex_editor', 'wscn_editor', 'author_assistant', 'product'] } },
|
||||
// { path: 'profile', component: UserProfile, name: '个人' }
|
||||
// ]
|
||||
// },
|
||||
{ path: '*', redirect: '/404', hidden: true }
|
||||
]
|
||||
});
|
13
src/store/errLog.js
Normal file
|
@ -0,0 +1,13 @@
|
|||
const errLog = {
|
||||
state: {
|
||||
errLog: []
|
||||
},
|
||||
pushLog(log) {
|
||||
this.state.errLog.unshift(log)
|
||||
},
|
||||
clearLog() {
|
||||
this.state.errLog = [];
|
||||
}
|
||||
};
|
||||
|
||||
export default errLog;
|
15
src/store/getters.js
Normal file
|
@ -0,0 +1,15 @@
|
|||
const getters = {
|
||||
sidebar: state => state.app.sidebar,
|
||||
livenewsChannels: state => state.app.livenewsChannels,
|
||||
token: state => state.user.token,
|
||||
avatar: state => state.user.avatar,
|
||||
name: state => state.user.name,
|
||||
uid: state => state.user.uid,
|
||||
email: state => state.user.email,
|
||||
introduction: state => state.user.introduction,
|
||||
auth_type: state => state.user.auth_type,
|
||||
status: state => state.user.status,
|
||||
roles: state => state.user.roles,
|
||||
setting: state => state.user.setting
|
||||
};
|
||||
export default getters
|
17
src/store/index.js
Normal file
|
@ -0,0 +1,17 @@
|
|||
import Vue from 'vue';
|
||||
import Vuex from 'vuex';
|
||||
import app from './modules/app';
|
||||
import user from './modules/user';
|
||||
import getters from './getters';
|
||||
|
||||
Vue.use(Vuex);
|
||||
|
||||
const store = new Vuex.Store({
|
||||
modules: {
|
||||
app,
|
||||
user
|
||||
},
|
||||
getters
|
||||
});
|
||||
|
||||
export default store
|
38
src/store/modules/app.js
Normal file
|
@ -0,0 +1,38 @@
|
|||
import Cookies from 'js-cookie';
|
||||
|
||||
const app = {
|
||||
state: {
|
||||
sidebar: {
|
||||
opened: !+Cookies.get('sidebarStatus')
|
||||
},
|
||||
theme: 'default',
|
||||
livenewsChannels: Cookies.get('livenewsChannels') || '[]'
|
||||
},
|
||||
mutations: {
|
||||
TOGGLE_SIDEBAR: state => {
|
||||
if (state.sidebar.opened) {
|
||||
Cookies.set('sidebarStatus', 1);
|
||||
} else {
|
||||
Cookies.set('sidebarStatus', 0);
|
||||
}
|
||||
state.sidebar.opened = !state.sidebar.opened;
|
||||
},
|
||||
SET_LIVENEWS_CHANNELS: (status, channels) => {
|
||||
status.livenewsChannels = JSON.stringify(channels);
|
||||
Cookies.set('livenewsChannels', JSON.stringify(channels));
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
ToggleSideBar: ({ commit }) => {
|
||||
commit('TOGGLE_SIDEBAR')
|
||||
},
|
||||
setTheme: ({ commit }, theme) => {
|
||||
commit('SET_THEME', theme)
|
||||
},
|
||||
setlivenewsChannels: ({ commit }, channels) => {
|
||||
commit('SET_LIVENEWS_CHANNELS', channels)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default app;
|
137
src/store/modules/user.js
Normal file
|
@ -0,0 +1,137 @@
|
|||
import { loginByEmail, logout, getInfo } from 'api/login';
|
||||
import Cookies from 'js-cookie';
|
||||
|
||||
const user = {
|
||||
state: {
|
||||
user: '',
|
||||
status: '',
|
||||
email: '',
|
||||
code: '',
|
||||
uid: undefined,
|
||||
auth_type: '',
|
||||
token: Cookies.get('X-Ivanka-Token'),
|
||||
name: '',
|
||||
avatar: '',
|
||||
introduction: '',
|
||||
roles: [],
|
||||
setting: {
|
||||
articlePlatform: []
|
||||
}
|
||||
},
|
||||
|
||||
mutations: {
|
||||
SET_AUTH_TYPE: (state, type) => {
|
||||
state.auth_type = type;
|
||||
},
|
||||
SET_CODE: (state, code) => {
|
||||
state.code = code;
|
||||
},
|
||||
SET_TOKEN: (state, token) => {
|
||||
state.token = token;
|
||||
},
|
||||
SET_UID: (state, uid) => {
|
||||
state.uid = uid;
|
||||
},
|
||||
SET_EMAIL: (state, email) => {
|
||||
state.email = email;
|
||||
},
|
||||
SET_INTRODUCTION: (state, introduction) => {
|
||||
state.introduction = introduction;
|
||||
},
|
||||
SET_SETTING: (state, setting) => {
|
||||
state.setting = setting;
|
||||
},
|
||||
SET_STATUS: (state, status) => {
|
||||
state.status = status;
|
||||
},
|
||||
SET_NAME: (state, name) => {
|
||||
state.name = name;
|
||||
},
|
||||
SET_AVATAR: (state, avatar) => {
|
||||
state.avatar = avatar;
|
||||
},
|
||||
SET_ROLES: (state, roles) => {
|
||||
state.roles = roles;
|
||||
},
|
||||
LOGIN_SUCCESS: () => {
|
||||
console.log('login success')
|
||||
},
|
||||
LOGOUT_USER: state => {
|
||||
state.user = '';
|
||||
}
|
||||
},
|
||||
|
||||
actions: {
|
||||
// 邮箱登录
|
||||
LoginByEmail({ commit }, userInfo) {
|
||||
return new Promise((resolve, reject) => {
|
||||
loginByEmail(userInfo.email, userInfo.password).then(response => {
|
||||
const data = response.data;
|
||||
Cookies.set('X-Ivanka-Token', response.data.token);
|
||||
commit('SET_TOKEN', data.token);
|
||||
commit('SET_EMAIL', userInfo.email);
|
||||
resolve();
|
||||
}).catch(error => {
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
// 获取用户信息
|
||||
GetInfo({ commit, state }) {
|
||||
return new Promise((resolve, reject) => {
|
||||
getInfo(state.token).then(response => {
|
||||
const data = response.data;
|
||||
commit('SET_ROLES', data.role);
|
||||
commit('SET_NAME', data.name);
|
||||
commit('SET_AVATAR', data.avatar);
|
||||
commit('SET_INTRODUCTION', data.introduction);
|
||||
resolve(response);
|
||||
}).catch(error => {
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
// 第三方验证登录
|
||||
LoginByThirdparty({ commit, state }, code) {
|
||||
return new Promise((resolve, reject) => {
|
||||
commit('SET_CODE', code);
|
||||
loginByThirdparty(state.status, state.email, state.code, state.auth_type).then(response => {
|
||||
commit('SET_TOKEN', response.data.token);
|
||||
Cookies.set('X-Ivanka-Token', response.data.token);
|
||||
resolve();
|
||||
}).catch(error => {
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
// 登出
|
||||
LogOut({ commit, state }) {
|
||||
return new Promise((resolve, reject) => {
|
||||
logout(state.token).then(() => {
|
||||
commit('SET_TOKEN', '');
|
||||
commit('SET_ROLES', []);
|
||||
Cookies.remove('X-Ivanka-Token');
|
||||
resolve();
|
||||
}).catch(error => {
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
// 前端 登出
|
||||
FedLogOut({ commit }) {
|
||||
return new Promise(resolve => {
|
||||
commit('SET_TOKEN', '');
|
||||
Cookies.remove('X-Ivanka-Token');
|
||||
resolve();
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default user;
|
39
src/store/permission.js
Normal file
|
@ -0,0 +1,39 @@
|
|||
const permission = {
|
||||
state: {
|
||||
permissionRoutes: []
|
||||
},
|
||||
init(data) {
|
||||
const roles = data.roles;
|
||||
const router = data.router;
|
||||
const permissionRoutes = router.filter(v => {
|
||||
if (roles.indexOf('admin') >= 0) return true;
|
||||
if (this.hasPermission(roles, v)) {
|
||||
if (v.children && v.children.length > 0) {
|
||||
v.children = v.children.filter(child => {
|
||||
if (this.hasPermission(roles, child)) {
|
||||
return child
|
||||
}
|
||||
return false;
|
||||
});
|
||||
return v
|
||||
} else {
|
||||
return v
|
||||
}
|
||||
}
|
||||
return false;
|
||||
});
|
||||
this.permissionRoutes = permissionRoutes;
|
||||
},
|
||||
get() {
|
||||
return this.permissionRoutes
|
||||
},
|
||||
hasPermission(roles, route) {
|
||||
if (route.meta && route.meta.role) {
|
||||
return roles.some(role => route.meta.role.indexOf(role) >= 0)
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default permission;
|
103
src/styles/btn.scss
Normal file
|
@ -0,0 +1,103 @@
|
|||
$blue:#324157;
|
||||
$light-blue:#3A71A8;
|
||||
$red:#C03639;
|
||||
$pink: #E65D6E;
|
||||
$green: #30B08F;
|
||||
$tiffany: #4AB7BD;
|
||||
$yellow:#FEC171;
|
||||
|
||||
$panGreen: #30B08F;
|
||||
|
||||
@mixin colorBtn($color) {
|
||||
background: $color;
|
||||
&:hover {
|
||||
color: $color;
|
||||
&:before, &:after {
|
||||
background: $color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.blue-btn {
|
||||
@include colorBtn($blue)
|
||||
}
|
||||
|
||||
.light-blue-btn{
|
||||
@include colorBtn($light-blue)
|
||||
}
|
||||
|
||||
|
||||
.red-btn {
|
||||
@include colorBtn($red)
|
||||
}
|
||||
|
||||
.pink-btn {
|
||||
@include colorBtn($pink)
|
||||
}
|
||||
|
||||
.green-btn {
|
||||
@include colorBtn($green)
|
||||
}
|
||||
|
||||
|
||||
.tiffany-btn {
|
||||
@include colorBtn($tiffany)
|
||||
}
|
||||
|
||||
|
||||
.yellow-btn {
|
||||
@include colorBtn($yellow)
|
||||
}
|
||||
|
||||
.pan-btn {
|
||||
font-size: 14px;
|
||||
color: #fff;
|
||||
padding: 14px 36px;
|
||||
border-radius: 8px;
|
||||
border: none;
|
||||
outline: none;
|
||||
margin-right: 25px;
|
||||
transition: 600ms ease all;
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
&:hover {
|
||||
background: #fff;
|
||||
&:before, &:after {
|
||||
width: 100%;
|
||||
transition: 600ms ease all;
|
||||
}
|
||||
}
|
||||
&:before, &:after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
height: 2px;
|
||||
width: 0;
|
||||
transition: 400ms ease all;
|
||||
}
|
||||
&::after {
|
||||
right: inherit;
|
||||
top: inherit;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.custom-button{
|
||||
display: inline-block;
|
||||
line-height: 1;
|
||||
white-space: nowrap;
|
||||
cursor: pointer;
|
||||
background: #fff;
|
||||
color: #fff;
|
||||
-webkit-appearance: none;
|
||||
text-align: center;
|
||||
box-sizing: border-box;
|
||||
outline: 0;
|
||||
margin: 0;
|
||||
padding: 10px 15px;
|
||||
font-size: 14px;
|
||||
border-radius: 4px;
|
||||
}
|
83
src/styles/element-ui.scss
Normal file
|
@ -0,0 +1,83 @@
|
|||
//覆盖一些element-ui样式
|
||||
.block-checkbox {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.operation-container {
|
||||
.cell {
|
||||
padding: 10px !important;
|
||||
}
|
||||
.el-button {
|
||||
&:nth-child(3) {
|
||||
margin-top: 10px;
|
||||
margin-left: 0px;
|
||||
}
|
||||
&:nth-child(4) {
|
||||
margin-top: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.el-upload {
|
||||
input[type="file"] {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
.el-upload__input {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.cell {
|
||||
.el-tag {
|
||||
margin-right: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.small-padding {
|
||||
.cell {
|
||||
padding-left: 8px;
|
||||
padding-right: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.status-col {
|
||||
.cell {
|
||||
padding: 0 10px;
|
||||
text-align: center;
|
||||
.el-tag {
|
||||
margin-right: 0px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//暂时性解决diolag 问题 https://github.com/ElemeFE/element/issues/2461
|
||||
.el-dialog {
|
||||
transform: none;
|
||||
left: 0;
|
||||
position: relative;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
|
||||
//文章页textarea修改样式
|
||||
.article-textarea {
|
||||
textarea {
|
||||
padding-right: 40px;
|
||||
resize: none;
|
||||
border: none;
|
||||
border-radius: 0px;
|
||||
border-bottom: 1px solid #bfcbd9;
|
||||
}
|
||||
}
|
||||
|
||||
//element ui upload
|
||||
.upload-container {
|
||||
.el-upload {
|
||||
width: 100%;
|
||||
.el-upload-dragger {
|
||||
width: 100%;
|
||||
height: 200px;
|
||||
}
|
||||
}
|
||||
}
|
259
src/styles/index.scss
Normal file
|
@ -0,0 +1,259 @@
|
|||
@import './btn.scss';
|
||||
@import './element-ui.scss';
|
||||
@import "./mixin.scss";
|
||||
body {
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
text-rendering: optimizeLegibility;
|
||||
font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB, Microsoft YaHei, Arial, sans-serif;
|
||||
}
|
||||
|
||||
label {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
html {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
*,
|
||||
*:before,
|
||||
*:after {
|
||||
box-sizing: inherit;
|
||||
}
|
||||
|
||||
.no-padding {
|
||||
padding: 0px !important;
|
||||
}
|
||||
|
||||
.padding-content {
|
||||
padding: 4px 0;
|
||||
}
|
||||
|
||||
a:focus,
|
||||
a:active {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
a,
|
||||
a:focus,
|
||||
a:hover {
|
||||
cursor: pointer;
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.fr {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.fl {
|
||||
float: left;
|
||||
}
|
||||
|
||||
.pr-5 {
|
||||
padding-right: 5px;
|
||||
}
|
||||
|
||||
.pl-5 {
|
||||
padding-left: 5px;
|
||||
}
|
||||
|
||||
.block {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.pointer {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.inlineBlock {
|
||||
display: block;
|
||||
}
|
||||
|
||||
code {
|
||||
background: #eef1f6;
|
||||
padding: 15px 10px;
|
||||
margin-bottom: 20px;
|
||||
display: block;
|
||||
line-height: 36px;
|
||||
a {
|
||||
color: #337ab7;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
color: rgb(32, 160, 255);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.fade-enter-active,
|
||||
.fade-leave-active {
|
||||
transition: all .2s ease
|
||||
}
|
||||
|
||||
.fade-enter,
|
||||
.fade-leave-active {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
//main-container全局样式
|
||||
.app-container {
|
||||
padding: 20px;
|
||||
}
|
||||
.components-container {
|
||||
margin: 30px 50px;
|
||||
position: relative;
|
||||
}
|
||||
.pagination-container {
|
||||
margin-top: 30px;
|
||||
}
|
||||
|
||||
|
||||
.editor-container .CodeMirror {
|
||||
height: 100%!important;
|
||||
}
|
||||
|
||||
.wscn-icon {
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
vertical-align: -0.15em;
|
||||
fill: currentColor;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.sub-navbar {
|
||||
height: 50px;
|
||||
line-height: 50px;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
text-align: right;
|
||||
padding-right: 20px;
|
||||
transition: 600ms ease position;
|
||||
background: linear-gradient(90deg, rgba(32, 182, 249, 1) 0%, rgba(32, 182, 249, 1) 0%, rgba(33, 120, 241, 1) 100%, rgba(33, 120, 241, 1) 100%);
|
||||
.subtitle {
|
||||
font-size: 20px;
|
||||
color: #fff;
|
||||
}
|
||||
&.draft {
|
||||
background: #d0d0d0;
|
||||
}
|
||||
&.deleted {
|
||||
background: #d0d0d0;
|
||||
}
|
||||
}
|
||||
|
||||
.link-type,
|
||||
.link-type:focus {
|
||||
color: #337ab7;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
color: rgb(32, 160, 255);
|
||||
}
|
||||
}
|
||||
|
||||
.publishedTag,
|
||||
.draftTag,
|
||||
.deletedTag {
|
||||
color: #fff;
|
||||
background-color: $panGreen;
|
||||
line-height: 1;
|
||||
text-align: center;
|
||||
margin: 0;
|
||||
padding: 8px 12px;
|
||||
font-size: 14px;
|
||||
border-radius: 4px;
|
||||
position: absolute;
|
||||
left: 20px;
|
||||
top: 10px;
|
||||
}
|
||||
|
||||
.draftTag {
|
||||
background-color: $yellow;
|
||||
}
|
||||
|
||||
.deletedTag {
|
||||
background-color: $red;
|
||||
}
|
||||
|
||||
.input-label {
|
||||
font-size: 14px;
|
||||
color: #48576a;
|
||||
line-height: 1;
|
||||
padding: 11px 5px 11px 0;
|
||||
}
|
||||
|
||||
.clearfix {
|
||||
&:after {
|
||||
visibility: hidden;
|
||||
display: block;
|
||||
font-size: 0;
|
||||
content: " ";
|
||||
clear: both;
|
||||
height: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.no-marginLeft {
|
||||
.el-checkbox {
|
||||
margin: 0 20px 15px 0;
|
||||
}
|
||||
.el-checkbox+.el-checkbox {
|
||||
margin-left: 0px;
|
||||
}
|
||||
}
|
||||
|
||||
.filter-container {
|
||||
padding-bottom: 10px;
|
||||
.filter-item {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//refine vue-multiselect plugin
|
||||
.multiselect {
|
||||
line-height: 16px;
|
||||
}
|
||||
|
||||
.multiselect--active {
|
||||
z-index: 1000 !important;
|
||||
}
|
||||
|
||||
//暂时性解决diolag 问题 https://github.com/ElemeFE/element/issues/2461
|
||||
.el-dialog {
|
||||
transform: none;
|
||||
left: 0;
|
||||
position: relative;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
//github-corner
|
||||
.github-corner:hover .octo-arm {
|
||||
animation: octocat-wave 560ms ease-in-out
|
||||
}
|
||||
|
||||
@keyframes octocat-wave {
|
||||
0%,
|
||||
100% {
|
||||
transform: rotate(0)
|
||||
}
|
||||
20%,
|
||||
60% {
|
||||
transform: rotate(-25deg)
|
||||
}
|
||||
40%,
|
||||
80% {
|
||||
transform: rotate(10deg)
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width:500px) {
|
||||
.github-corner:hover .octo-arm {
|
||||
animation: none
|
||||
}
|
||||
.github-corner .octo-arm {
|
||||
animation: octocat-wave 560ms ease-in-out
|
||||
}
|
||||
}
|
57
src/styles/mixin.scss
Normal file
|
@ -0,0 +1,57 @@
|
|||
@mixin clearfix {
|
||||
&:after {
|
||||
content: "";
|
||||
display: table;
|
||||
clear: both;
|
||||
}
|
||||
}
|
||||
|
||||
@mixin scrollBar {
|
||||
&::-webkit-scrollbar-track-piece {
|
||||
background: #d3dce6;
|
||||
}
|
||||
&::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background: #99a9bf;
|
||||
border-radius: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
@mixin relative {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
@mixin pct($pct) {
|
||||
width: #{$pct};
|
||||
position: relative;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
@mixin triangle($width, $height, $color, $direction) {
|
||||
$width: $width/2;
|
||||
$color-border-style: $height solid $color;
|
||||
$transparent-border-style: $width solid transparent;
|
||||
height: 0;
|
||||
width: 0;
|
||||
@if $direction == up {
|
||||
border-bottom: $color-border-style;
|
||||
border-left: $transparent-border-style;
|
||||
border-right: $transparent-border-style;
|
||||
} @else if $direction == right {
|
||||
border-left: $color-border-style;
|
||||
border-top: $transparent-border-style;
|
||||
border-bottom: $transparent-border-style;
|
||||
} @else if $direction == down {
|
||||
border-top: $color-border-style;
|
||||
border-left: $transparent-border-style;
|
||||
border-right: $transparent-border-style;
|
||||
} @else if $direction == left {
|
||||
border-right: $color-border-style;
|
||||
border-top: $transparent-border-style;
|
||||
border-bottom: $transparent-border-style;
|
||||
}
|
||||
}
|
8
src/utils/createUniqueString.js
Normal file
|
@ -0,0 +1,8 @@
|
|||
/**
|
||||
* Created by jiachenpan on 17/3/8.
|
||||
*/
|
||||
export default function createUniqueString() {
|
||||
const timestamp = +new Date() + '';
|
||||
const randomNum = parseInt((1 + Math.random()) * 65536) + '';
|
||||
return (+(randomNum + timestamp)).toString(32);
|
||||
}
|
72
src/utils/fetch.js
Normal file
|
@ -0,0 +1,72 @@
|
|||
import axios from 'axios';
|
||||
import { Message } from 'element-ui';
|
||||
import store from '../store';
|
||||
import router from '../router';
|
||||
|
||||
export default function _fetch(options) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const instance = axios.create({
|
||||
baseURL: process.env.BASE_API,
|
||||
// timeout: 2000,
|
||||
headers: { 'X-Ivanka-Token': store.getters.token }
|
||||
});
|
||||
instance(options)
|
||||
.then(response => {
|
||||
const res = response.data;
|
||||
if (res.code !== 20000) {
|
||||
console.log(options); // for debug
|
||||
Message({
|
||||
message: res.message,
|
||||
type: 'error',
|
||||
duration: 5 * 1000
|
||||
});
|
||||
// 50014:Token 过期了 50012:其他客户端登录了 50008:非法的token
|
||||
if (res.code === 50008 || res.code === 50014 || res.code === 50012) {
|
||||
Message({
|
||||
message: res.message,
|
||||
type: 'error',
|
||||
duration: 5 * 1000
|
||||
});
|
||||
// router.push({path: '/'})
|
||||
// TODO
|
||||
store.dispatch('FedLogOut').then(() => {
|
||||
router.push({ path: '/login' })
|
||||
});
|
||||
}
|
||||
reject(res);
|
||||
}
|
||||
resolve(res);
|
||||
})
|
||||
.catch(error => {
|
||||
Message({
|
||||
message: '发生异常错误,请刷新页面重试,或联系程序员',
|
||||
type: 'error',
|
||||
duration: 5 * 1000
|
||||
});
|
||||
console.log(error); // for debug
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export function fetch(options) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const instance = axios.create({
|
||||
timeout: 2000 // 超时
|
||||
});
|
||||
instance(options)
|
||||
.then(response => {
|
||||
const res = response.data;
|
||||
resolve(res);
|
||||
})
|
||||
.catch(error => {
|
||||
Message({
|
||||
message: error,
|
||||
type: 'error',
|
||||
duration: 5 * 1000
|
||||
});
|
||||
console.log(error); // for debug
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
}
|
221
src/utils/index.js
Normal file
|
@ -0,0 +1,221 @@
|
|||
/**
|
||||
* Created by jiachenpan on 16/11/18.
|
||||
*/
|
||||
|
||||
import showdown from 'showdown' // markdown转化
|
||||
const converter = new showdown.Converter();
|
||||
|
||||
export function parseTime(time, cFormat) {
|
||||
if (arguments.length === 0) {
|
||||
return null;
|
||||
}
|
||||
const format = cFormat || '{y}-{m}-{d} {h}:{i}:{s}';
|
||||
let date;
|
||||
if (typeof time == 'object') {
|
||||
date = time;
|
||||
} else {
|
||||
if (('' + time).length === 10) time = parseInt(time) * 1000;
|
||||
date = new Date(time);
|
||||
}
|
||||
const formatObj = {
|
||||
y: date.getFullYear(),
|
||||
m: date.getMonth() + 1,
|
||||
d: date.getDate(),
|
||||
h: date.getHours(),
|
||||
i: date.getMinutes(),
|
||||
s: date.getSeconds(),
|
||||
a: date.getDay()
|
||||
};
|
||||
const time_str = format.replace(/{(y|m|d|h|i|s|a)+}/g, (result, key) => {
|
||||
let value = formatObj[key];
|
||||
if (key === 'a') return ['一', '二', '三', '四', '五', '六', '日'][value - 1];
|
||||
if (result.length > 0 && value < 10) {
|
||||
value = '0' + value;
|
||||
}
|
||||
return value || 0;
|
||||
});
|
||||
return time_str;
|
||||
}
|
||||
|
||||
export function formatTime(time, option) {
|
||||
time = +time * 1000;
|
||||
const d = new Date(time);
|
||||
const now = Date.now();
|
||||
|
||||
const diff = (now - d) / 1000;
|
||||
|
||||
if (diff < 30) {
|
||||
return '刚刚'
|
||||
} else if (diff < 3600) { // less 1 hour
|
||||
return Math.ceil(diff / 60) + '分钟前'
|
||||
} else if (diff < 3600 * 24) {
|
||||
return Math.ceil(diff / 3600) + '小时前'
|
||||
} else if (diff < 3600 * 24 * 2) {
|
||||
return '1天前'
|
||||
}
|
||||
if (option) {
|
||||
return parseTime(time, option)
|
||||
} else {
|
||||
return d.getMonth() + 1 + '月' + d.getDate() + '日' + d.getHours() + '时' + d.getMinutes() + '分'
|
||||
}
|
||||
}
|
||||
|
||||
// 格式化时间
|
||||
|
||||
|
||||
export function getQueryObject(url) {
|
||||
url = url == null ? window.location.href : url;
|
||||
const search = url.substring(url.lastIndexOf('?') + 1);
|
||||
const obj = {};
|
||||
const reg = /([^?&=]+)=([^?&=]*)/g;
|
||||
search.replace(reg, (rs, $1, $2) => {
|
||||
const name = decodeURIComponent($1);
|
||||
let val = decodeURIComponent($2);
|
||||
val = String(val);
|
||||
obj[name] = val;
|
||||
return rs;
|
||||
});
|
||||
return obj;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
*get getByteLen
|
||||
* @param {Sting} val input value
|
||||
* @returns {number} output value
|
||||
*/
|
||||
export function getByteLen(val) {
|
||||
let len = 0;
|
||||
for (let i = 0; i < val.length; i++) {
|
||||
if (val[i].match(/[^\x00-\xff]/ig) != null) {
|
||||
len += 1;
|
||||
} else { len += 0.5; }
|
||||
}
|
||||
return Math.floor(len);
|
||||
}
|
||||
|
||||
export function cleanArray(actual) {
|
||||
const newArray = [];
|
||||
for (let i = 0; i < actual.length; i++) {
|
||||
if (actual[i]) {
|
||||
newArray.push(actual[i]);
|
||||
}
|
||||
}
|
||||
return newArray;
|
||||
}
|
||||
|
||||
export function param(json) {
|
||||
if (!json) return '';
|
||||
return cleanArray(Object.keys(json).map(key => {
|
||||
if (json[key] === undefined) return '';
|
||||
return encodeURIComponent(key) + '=' +
|
||||
encodeURIComponent(json[key]);
|
||||
})).join('&');
|
||||
}
|
||||
|
||||
export function html2Text(val) {
|
||||
const div = document.createElement('div');
|
||||
div.innerHTML = val;
|
||||
return div.textContent || div.innerText;
|
||||
}
|
||||
|
||||
export function objectMerge(target, source) {
|
||||
/* Merges two objects,
|
||||
giving the last one precedence */
|
||||
|
||||
if (typeof target !== 'object') {
|
||||
target = {};
|
||||
}
|
||||
if (Array.isArray(source)) {
|
||||
return source.slice();
|
||||
}
|
||||
for (const property in source) {
|
||||
if (source.hasOwnProperty(property)) {
|
||||
const sourceProperty = source[property];
|
||||
if (typeof sourceProperty === 'object') {
|
||||
target[property] = objectMerge(target[property], sourceProperty);
|
||||
continue;
|
||||
}
|
||||
target[property] = sourceProperty;
|
||||
}
|
||||
}
|
||||
return target;
|
||||
}
|
||||
|
||||
|
||||
export function scrollTo(element, to, duration) {
|
||||
if (duration <= 0) return;
|
||||
const difference = to - element.scrollTop;
|
||||
const perTick = difference / duration * 10;
|
||||
|
||||
setTimeout(() => {
|
||||
console.log(new Date())
|
||||
element.scrollTop = element.scrollTop + perTick;
|
||||
if (element.scrollTop === to) return;
|
||||
scrollTo(element, to, duration - 10);
|
||||
}, 10);
|
||||
}
|
||||
|
||||
export function toggleClass(element, className) {
|
||||
if (!element || !className) {
|
||||
return;
|
||||
}
|
||||
|
||||
let classString = element.className;
|
||||
const nameIndex = classString.indexOf(className);
|
||||
if (nameIndex === -1) {
|
||||
classString += '' + className;
|
||||
} else {
|
||||
classString = classString.substr(0, nameIndex) + classString.substr(nameIndex + className.length);
|
||||
}
|
||||
element.className = classString;
|
||||
}
|
||||
|
||||
export const pickerOptions = [
|
||||
{
|
||||
text: '今天',
|
||||
onClick(picker) {
|
||||
const end = new Date();
|
||||
const start = new Date(new Date().toDateString());
|
||||
end.setTime(start.getTime());
|
||||
picker.$emit('pick', [start, end]);
|
||||
}
|
||||
}, {
|
||||
text: '最近一周',
|
||||
onClick(picker) {
|
||||
const end = new Date(new Date().toDateString());
|
||||
const start = new Date();
|
||||
start.setTime(end.getTime() - 3600 * 1000 * 24 * 7);
|
||||
picker.$emit('pick', [start, end]);
|
||||
}
|
||||
}, {
|
||||
text: '最近一个月',
|
||||
onClick(picker) {
|
||||
const end = new Date(new Date().toDateString());
|
||||
const start = new Date();
|
||||
start.setTime(start.getTime() - 3600 * 1000 * 24 * 30);
|
||||
picker.$emit('pick', [start, end]);
|
||||
}
|
||||
}, {
|
||||
text: '最近三个月',
|
||||
onClick(picker) {
|
||||
const end = new Date(new Date().toDateString());
|
||||
const start = new Date();
|
||||
start.setTime(start.getTime() - 3600 * 1000 * 24 * 90);
|
||||
picker.$emit('pick', [start, end]);
|
||||
}
|
||||
}]
|
||||
|
||||
export function getTime(type) {
|
||||
if (type === 'start') {
|
||||
return new Date().getTime() - 3600 * 1000 * 24 * 90
|
||||
} else {
|
||||
return new Date(new Date().toDateString())
|
||||
}
|
||||
}
|
||||
|
||||
export function showdownMD(md) {
|
||||
return converter.makeHtml(md)
|
||||
}
|
27
src/utils/openWindow.js
Normal file
|
@ -0,0 +1,27 @@
|
|||
/**
|
||||
*Created by jiachenpan on 16/11/29.
|
||||
* @param {Sting} url
|
||||
* @param {Sting} title
|
||||
* @param {Number} w
|
||||
* @param {Number} h
|
||||
*/
|
||||
|
||||
export default function openWindow(url, title, w, h) {
|
||||
// Fixes dual-screen position Most browsers Firefox
|
||||
const dualScreenLeft = window.screenLeft !== undefined ? window.screenLeft : screen.left;
|
||||
const dualScreenTop = window.screenTop !== undefined ? window.screenTop : screen.top;
|
||||
|
||||
const width = window.innerWidth ? window.innerWidth : document.documentElement.clientWidth ? document.documentElement.clientWidth : screen.width;
|
||||
const height = window.innerHeight ? window.innerHeight : document.documentElement.clientHeight ? document.documentElement.clientHeight : screen.height;
|
||||
|
||||
const left = ((width / 2) - (w / 2)) + dualScreenLeft;
|
||||
const top = ((height / 2) - (h / 2)) + dualScreenTop;
|
||||
const newWindow = window.open(url, title, 'toolbar=no, location=no, directories=no, status=no, menubar=no, scrollbars=no, resizable=yes, copyhistory=no, width=' + w + ', height=' + h + ', top=' + top + ', left=' + left);
|
||||
|
||||
// Puts focus on the newWindow
|
||||
if (window.focus) {
|
||||
newWindow.focus();
|
||||
}
|
||||
}
|
||||
|
||||
|
41
src/utils/validate.js
Normal file
|
@ -0,0 +1,41 @@
|
|||
/**
|
||||
* Created by jiachenpan on 16/11/18.
|
||||
*/
|
||||
|
||||
/* 是否是公司邮箱*/
|
||||
export function isWscnEmail(str) {
|
||||
const reg = /^[a-z0-9](?:[-_.+]?[a-z0-9]+)*@wallstreetcn\.com$/i;
|
||||
return reg.test(str);
|
||||
}
|
||||
|
||||
/* 合法uri*/
|
||||
export function validateURL(textval) {
|
||||
const urlregex = /^(https?|ftp):\/\/([a-zA-Z0-9.-]+(:[a-zA-Z0-9.&%$-]+)*@)*((25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9][0-9]?)(\.(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])){3}|([a-zA-Z0-9-]+\.)*[a-zA-Z0-9-]+\.(com|edu|gov|int|mil|net|org|biz|arpa|info|name|pro|aero|coop|museum|[a-zA-Z]{2}))(:[0-9]+)*(\/($|[a-zA-Z0-9.,?'\\+&%$#=~_-]+))*$/;
|
||||
return urlregex.test(textval);
|
||||
}
|
||||
|
||||
/* 小写字母*/
|
||||
export function validateLowerCase(str) {
|
||||
const reg = /^[a-z]+$/;
|
||||
return reg.test(str);
|
||||
}
|
||||
|
||||
/* 验证key*/
|
||||
// export function validateKey(str) {
|
||||
// var reg = /^[a-z_\-:]+$/;
|
||||
// return reg.test(str);
|
||||
// }
|
||||
|
||||
/* 大写字母*/
|
||||
export function validateUpperCase(str) {
|
||||
const reg = /^[A-Z]+$/;
|
||||
return reg.test(str);
|
||||
}
|
||||
|
||||
/* 大小写字母*/
|
||||
export function validatAlphabets(str) {
|
||||
const reg = /^[A-Za-z]+$/;
|
||||
return reg.test(str);
|
||||
}
|
||||
|
||||
|
179
src/vendor/Blob.js
vendored
Normal file
|
@ -0,0 +1,179 @@
|
|||
/* eslint-disable */
|
||||
/* Blob.js
|
||||
* A Blob implementation.
|
||||
* 2014-05-27
|
||||
*
|
||||
* By Eli Grey, http://eligrey.com
|
||||
* By Devin Samarin, https://github.com/eboyjr
|
||||
* License: X11/MIT
|
||||
* See LICENSE.md
|
||||
*/
|
||||
|
||||
/*global self, unescape */
|
||||
/*jslint bitwise: true, regexp: true, confusion: true, es5: true, vars: true, white: true,
|
||||
plusplus: true */
|
||||
|
||||
/*! @source http://purl.eligrey.com/github/Blob.js/blob/master/Blob.js */
|
||||
|
||||
(function (view) {
|
||||
"use strict";
|
||||
|
||||
view.URL = view.URL || view.webkitURL;
|
||||
|
||||
if (view.Blob && view.URL) {
|
||||
try {
|
||||
new Blob;
|
||||
return;
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
// Internally we use a BlobBuilder implementation to base Blob off of
|
||||
// in order to support older browsers that only have BlobBuilder
|
||||
var BlobBuilder = view.BlobBuilder || view.WebKitBlobBuilder || view.MozBlobBuilder || (function(view) {
|
||||
var
|
||||
get_class = function(object) {
|
||||
return Object.prototype.toString.call(object).match(/^\[object\s(.*)\]$/)[1];
|
||||
}
|
||||
, FakeBlobBuilder = function BlobBuilder() {
|
||||
this.data = [];
|
||||
}
|
||||
, FakeBlob = function Blob(data, type, encoding) {
|
||||
this.data = data;
|
||||
this.size = data.length;
|
||||
this.type = type;
|
||||
this.encoding = encoding;
|
||||
}
|
||||
, FBB_proto = FakeBlobBuilder.prototype
|
||||
, FB_proto = FakeBlob.prototype
|
||||
, FileReaderSync = view.FileReaderSync
|
||||
, FileException = function(type) {
|
||||
this.code = this[this.name = type];
|
||||
}
|
||||
, file_ex_codes = (
|
||||
"NOT_FOUND_ERR SECURITY_ERR ABORT_ERR NOT_READABLE_ERR ENCODING_ERR "
|
||||
+ "NO_MODIFICATION_ALLOWED_ERR INVALID_STATE_ERR SYNTAX_ERR"
|
||||
).split(" ")
|
||||
, file_ex_code = file_ex_codes.length
|
||||
, real_URL = view.URL || view.webkitURL || view
|
||||
, real_create_object_URL = real_URL.createObjectURL
|
||||
, real_revoke_object_URL = real_URL.revokeObjectURL
|
||||
, URL = real_URL
|
||||
, btoa = view.btoa
|
||||
, atob = view.atob
|
||||
|
||||
, ArrayBuffer = view.ArrayBuffer
|
||||
, Uint8Array = view.Uint8Array
|
||||
;
|
||||
FakeBlob.fake = FB_proto.fake = true;
|
||||
while (file_ex_code--) {
|
||||
FileException.prototype[file_ex_codes[file_ex_code]] = file_ex_code + 1;
|
||||
}
|
||||
if (!real_URL.createObjectURL) {
|
||||
URL = view.URL = {};
|
||||
}
|
||||
URL.createObjectURL = function(blob) {
|
||||
var
|
||||
type = blob.type
|
||||
, data_URI_header
|
||||
;
|
||||
if (type === null) {
|
||||
type = "application/octet-stream";
|
||||
}
|
||||
if (blob instanceof FakeBlob) {
|
||||
data_URI_header = "data:" + type;
|
||||
if (blob.encoding === "base64") {
|
||||
return data_URI_header + ";base64," + blob.data;
|
||||
} else if (blob.encoding === "URI") {
|
||||
return data_URI_header + "," + decodeURIComponent(blob.data);
|
||||
} if (btoa) {
|
||||
return data_URI_header + ";base64," + btoa(blob.data);
|
||||
} else {
|
||||
return data_URI_header + "," + encodeURIComponent(blob.data);
|
||||
}
|
||||
} else if (real_create_object_URL) {
|
||||
return real_create_object_URL.call(real_URL, blob);
|
||||
}
|
||||
};
|
||||
URL.revokeObjectURL = function(object_URL) {
|
||||
if (object_URL.substring(0, 5) !== "data:" && real_revoke_object_URL) {
|
||||
real_revoke_object_URL.call(real_URL, object_URL);
|
||||
}
|
||||
};
|
||||
FBB_proto.append = function(data/*, endings*/) {
|
||||
var bb = this.data;
|
||||
// decode data to a binary string
|
||||
if (Uint8Array && (data instanceof ArrayBuffer || data instanceof Uint8Array)) {
|
||||
var
|
||||
str = ""
|
||||
, buf = new Uint8Array(data)
|
||||
, i = 0
|
||||
, buf_len = buf.length
|
||||
;
|
||||
for (; i < buf_len; i++) {
|
||||
str += String.fromCharCode(buf[i]);
|
||||
}
|
||||
bb.push(str);
|
||||
} else if (get_class(data) === "Blob" || get_class(data) === "File") {
|
||||
if (FileReaderSync) {
|
||||
var fr = new FileReaderSync;
|
||||
bb.push(fr.readAsBinaryString(data));
|
||||
} else {
|
||||
// async FileReader won't work as BlobBuilder is sync
|
||||
throw new FileException("NOT_READABLE_ERR");
|
||||
}
|
||||
} else if (data instanceof FakeBlob) {
|
||||
if (data.encoding === "base64" && atob) {
|
||||
bb.push(atob(data.data));
|
||||
} else if (data.encoding === "URI") {
|
||||
bb.push(decodeURIComponent(data.data));
|
||||
} else if (data.encoding === "raw") {
|
||||
bb.push(data.data);
|
||||
}
|
||||
} else {
|
||||
if (typeof data !== "string") {
|
||||
data += ""; // convert unsupported types to strings
|
||||
}
|
||||
// decode UTF-16 to binary string
|
||||
bb.push(unescape(encodeURIComponent(data)));
|
||||
}
|
||||
};
|
||||
FBB_proto.getBlob = function(type) {
|
||||
if (!arguments.length) {
|
||||
type = null;
|
||||
}
|
||||
return new FakeBlob(this.data.join(""), type, "raw");
|
||||
};
|
||||
FBB_proto.toString = function() {
|
||||
return "[object BlobBuilder]";
|
||||
};
|
||||
FB_proto.slice = function(start, end, type) {
|
||||
var args = arguments.length;
|
||||
if (args < 3) {
|
||||
type = null;
|
||||
}
|
||||
return new FakeBlob(
|
||||
this.data.slice(start, args > 1 ? end : this.data.length)
|
||||
, type
|
||||
, this.encoding
|
||||
);
|
||||
};
|
||||
FB_proto.toString = function() {
|
||||
return "[object Blob]";
|
||||
};
|
||||
FB_proto.close = function() {
|
||||
this.size = this.data.length = 0;
|
||||
};
|
||||
return FakeBlobBuilder;
|
||||
}(view));
|
||||
|
||||
view.Blob = function Blob(blobParts, options) {
|
||||
var type = options ? (options.type || "") : "";
|
||||
var builder = new BlobBuilder();
|
||||
if (blobParts) {
|
||||
for (var i = 0, len = blobParts.length; i < len; i++) {
|
||||
builder.append(blobParts[i]);
|
||||
}
|
||||
}
|
||||
return builder.getBlob(type);
|
||||
};
|
||||
}(typeof self !== "undefined" && self || typeof window !== "undefined" && window || this.content || this));
|
141
src/vendor/Export2Excel.js
vendored
Normal file
|
@ -0,0 +1,141 @@
|
|||
/* eslint-disable */
|
||||
require('script-loader!file-saver');
|
||||
require('script-loader!vendor/Blob');
|
||||
require('script-loader!xlsx/dist/xlsx.core.min');
|
||||
function generateArray(table) {
|
||||
var out = [];
|
||||
var rows = table.querySelectorAll('tr');
|
||||
var ranges = [];
|
||||
for (var R = 0; R < rows.length; ++R) {
|
||||
var outRow = [];
|
||||
var row = rows[R];
|
||||
var columns = row.querySelectorAll('td');
|
||||
for (var C = 0; C < columns.length; ++C) {
|
||||
var cell = columns[C];
|
||||
var colspan = cell.getAttribute('colspan');
|
||||
var rowspan = cell.getAttribute('rowspan');
|
||||
var cellValue = cell.innerText;
|
||||
if (cellValue !== "" && cellValue == +cellValue) cellValue = +cellValue;
|
||||
|
||||
//Skip ranges
|
||||
ranges.forEach(function (range) {
|
||||
if (R >= range.s.r && R <= range.e.r && outRow.length >= range.s.c && outRow.length <= range.e.c) {
|
||||
for (var i = 0; i <= range.e.c - range.s.c; ++i) outRow.push(null);
|
||||
}
|
||||
});
|
||||
|
||||
//Handle Row Span
|
||||
if (rowspan || colspan) {
|
||||
rowspan = rowspan || 1;
|
||||
colspan = colspan || 1;
|
||||
ranges.push({s: {r: R, c: outRow.length}, e: {r: R + rowspan - 1, c: outRow.length + colspan - 1}});
|
||||
}
|
||||
;
|
||||
|
||||
//Handle Value
|
||||
outRow.push(cellValue !== "" ? cellValue : null);
|
||||
|
||||
//Handle Colspan
|
||||
if (colspan) for (var k = 0; k < colspan - 1; ++k) outRow.push(null);
|
||||
}
|
||||
out.push(outRow);
|
||||
}
|
||||
return [out, ranges];
|
||||
};
|
||||
|
||||
function datenum(v, date1904) {
|
||||
if (date1904) v += 1462;
|
||||
var epoch = Date.parse(v);
|
||||
return (epoch - new Date(Date.UTC(1899, 11, 30))) / (24 * 60 * 60 * 1000);
|
||||
}
|
||||
|
||||
function sheet_from_array_of_arrays(data, opts) {
|
||||
var ws = {};
|
||||
var range = {s: {c: 10000000, r: 10000000}, e: {c: 0, r: 0}};
|
||||
for (var R = 0; R != data.length; ++R) {
|
||||
for (var C = 0; C != data[R].length; ++C) {
|
||||
if (range.s.r > R) range.s.r = R;
|
||||
if (range.s.c > C) range.s.c = C;
|
||||
if (range.e.r < R) range.e.r = R;
|
||||
if (range.e.c < C) range.e.c = C;
|
||||
var cell = {v: data[R][C]};
|
||||
if (cell.v == null) continue;
|
||||
var cell_ref = XLSX.utils.encode_cell({c: C, r: R});
|
||||
|
||||
if (typeof cell.v === 'number') cell.t = 'n';
|
||||
else if (typeof cell.v === 'boolean') cell.t = 'b';
|
||||
else if (cell.v instanceof Date) {
|
||||
cell.t = 'n';
|
||||
cell.z = XLSX.SSF._table[14];
|
||||
cell.v = datenum(cell.v);
|
||||
}
|
||||
else cell.t = 's';
|
||||
|
||||
ws[cell_ref] = cell;
|
||||
}
|
||||
}
|
||||
if (range.s.c < 10000000) ws['!ref'] = XLSX.utils.encode_range(range);
|
||||
return ws;
|
||||
}
|
||||
|
||||
function Workbook() {
|
||||
if (!(this instanceof Workbook)) return new Workbook();
|
||||
this.SheetNames = [];
|
||||
this.Sheets = {};
|
||||
}
|
||||
|
||||
function s2ab(s) {
|
||||
var buf = new ArrayBuffer(s.length);
|
||||
var view = new Uint8Array(buf);
|
||||
for (var i = 0; i != s.length; ++i) view[i] = s.charCodeAt(i) & 0xFF;
|
||||
return buf;
|
||||
}
|
||||
|
||||
export function export_table_to_excel(id) {
|
||||
var theTable = document.getElementById(id);
|
||||
console.log('a')
|
||||
var oo = generateArray(theTable);
|
||||
var ranges = oo[1];
|
||||
|
||||
/* original data */
|
||||
var data = oo[0];
|
||||
var ws_name = "SheetJS";
|
||||
console.log(data);
|
||||
|
||||
var wb = new Workbook(), ws = sheet_from_array_of_arrays(data);
|
||||
|
||||
/* add ranges to worksheet */
|
||||
// ws['!cols'] = ['apple', 'banan'];
|
||||
ws['!merges'] = ranges;
|
||||
|
||||
/* add worksheet to workbook */
|
||||
wb.SheetNames.push(ws_name);
|
||||
wb.Sheets[ws_name] = ws;
|
||||
|
||||
var wbout = XLSX.write(wb, {bookType: 'xlsx', bookSST: false, type: 'binary'});
|
||||
|
||||
saveAs(new Blob([s2ab(wbout)], {type: "application/octet-stream"}), "test.xlsx")
|
||||
}
|
||||
|
||||
function formatJson(jsonData) {
|
||||
console.log(jsonData)
|
||||
}
|
||||
export function export_json_to_excel(th, jsonData, defaultTitle) {
|
||||
|
||||
/* original data */
|
||||
|
||||
var data = jsonData;
|
||||
data.unshift(th);
|
||||
var ws_name = "SheetJS";
|
||||
|
||||
var wb = new Workbook(), ws = sheet_from_array_of_arrays(data);
|
||||
|
||||
|
||||
/* add worksheet to workbook */
|
||||
wb.SheetNames.push(ws_name);
|
||||
wb.Sheets[ws_name] = ws;
|
||||
|
||||
var wbout = XLSX.write(wb, {bookType: 'xlsx', bookSST: false, type: 'binary'});
|
||||
var title = defaultTitle || '列表'
|
||||
saveAs(new Blob([s2ab(wbout)], {type: "application/octet-stream"}), title + ".xlsx")
|
||||
}
|
87
src/views/admin/createUser.vue
Normal file
|
@ -0,0 +1,87 @@
|
|||
<template>
|
||||
<div class="app-container">
|
||||
<h1 class="page-heading">
|
||||
创建后台用户
|
||||
</h1>
|
||||
<el-form ref="createForm" :rules="createRules" label-position="left" style='width:80%' :model="form" label-width="100px">
|
||||
<el-form-item label="用户邮箱" prop="email">
|
||||
<el-input v-model="form.email" placeholder="公司邮箱"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="权限选择" >
|
||||
<el-select style="width: 100%" v-model="form.role" multiple placeholder="请选择">
|
||||
<el-option
|
||||
v-for="item in roleList"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value">
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" :loading="loading" @click.native.prevent="onSubmit">立即创建</el-button>
|
||||
<el-button>
|
||||
<router-link class="normal_link" to="/index">
|
||||
取消
|
||||
</router-link>
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { createNewUser, getRoleList } from 'api/adminUser';
|
||||
import { isWscnEmail } from 'utils/validate';
|
||||
|
||||
export default{
|
||||
name: 'createUser',
|
||||
data() {
|
||||
const validateEmail = (rule, value, callback) => {
|
||||
if (!isWscnEmail(value)) {
|
||||
callback(new Error('邮箱错误'));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
};
|
||||
return {
|
||||
roleList: [],
|
||||
loading: false,
|
||||
form: {
|
||||
email: '',
|
||||
role: ''
|
||||
},
|
||||
createRules: {
|
||||
email: [
|
||||
{ required: true, message: '请输入邮箱', trigger: 'blur' },
|
||||
{ validator: validateEmail }
|
||||
]
|
||||
}
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
onSubmit() {
|
||||
this.$refs.createForm.validate(valid => {
|
||||
if (valid) {
|
||||
this.loading = true;
|
||||
const data = {
|
||||
email: this.form.email,
|
||||
roles: this.form.role
|
||||
};
|
||||
createNewUser(data).then(() => {
|
||||
this.$message.success('创建成功');
|
||||
});
|
||||
} else {
|
||||
this.$message.error('error submit!!');
|
||||
}
|
||||
this.loading = false;
|
||||
});
|
||||
}
|
||||
},
|
||||
created() {
|
||||
getRoleList().then(response => {
|
||||
const roleMap = response.data.role_map;
|
||||
const keyArr = Object.keys(roleMap);
|
||||
this.roleList = keyArr.map(v => ({ value: v, label: roleMap[v] }));
|
||||
});
|
||||
}
|
||||
};
|
||||
</script>
|
404
src/views/admin/profile.vue
Normal file
|
@ -0,0 +1,404 @@
|
|||
<template>
|
||||
<div class="profile-container clearfix">
|
||||
<div style="position: relative;margin-left: 30px;">
|
||||
<PanThumb :image="avatar"> 你的权限:
|
||||
<span class="pan-info-roles" v-for="item in roles">{{item}}</span>
|
||||
</PanThumb>
|
||||
<el-button type="primary" icon="upload" style="position: absolute;bottom: 15px;margin-left: 40px;"
|
||||
@click="handleImagecropper">修改头像
|
||||
</el-button>
|
||||
</div>
|
||||
<!--popover-->
|
||||
<el-popover
|
||||
ref="popoverWX"
|
||||
placement="top"
|
||||
width="160"
|
||||
trigger="click"
|
||||
v-model="popoverVisibleWX">
|
||||
<p>你确定要解绑么?</p>
|
||||
<div style="text-align: right; margin: 0">
|
||||
<el-button size="mini" type="text" @click="popoverVisibleWX = false">取消</el-button>
|
||||
<el-button type="primary" size="mini" @click="handleUnbind('wechat')">确定</el-button>
|
||||
</div>
|
||||
</el-popover>
|
||||
<el-popover
|
||||
ref="popoverQQ"
|
||||
placement="top"
|
||||
width="160"
|
||||
trigger="click"
|
||||
v-model="popoverVisibleQQ">
|
||||
<p>你确定要解绑么?</p>
|
||||
<div style="text-align: right; margin: 0">
|
||||
<el-button size="mini" type="text" @click="popoverVisibleQQ = false">取消</el-button>
|
||||
<el-button type="primary" size="mini" @click="handleUnbind('tencent')">确定</el-button>
|
||||
</div>
|
||||
</el-popover>
|
||||
<!--popover End-->
|
||||
|
||||
<el-card class="box-card">
|
||||
<div slot="header" class="clearfix">
|
||||
<span style="line-height: 36px;">个人资料</span>
|
||||
</div>
|
||||
<div class="box-item">
|
||||
<span class="field-label">昵称</span>
|
||||
<div class="field-content">
|
||||
{{name}}
|
||||
<el-button class="edit-btn" @click="handleEditName" type="primary" icon="edit"
|
||||
size="small"></el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="box-item">
|
||||
<span class="field-label">简介</span>
|
||||
<div class="field-content">
|
||||
{{introduction.length==0?'未填写':introduction}}
|
||||
<el-button class="edit-btn" @click="handleIntroductionName" type="primary" icon="edit"
|
||||
size="small"></el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="box-item" style="margin-bottom: 10px;">
|
||||
<span class="field-label">密码</span>
|
||||
<div class="field-content">
|
||||
<el-button type="primary" @click="resetPSWdialogVisible=true">修改密码</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="box-item" style="margin-top: 5px;">
|
||||
<div class="field-content">
|
||||
<span class="wx-svg-container"><wscn-icon-svg icon-class="weixin" class="icon"/></span>
|
||||
<el-button class="unbind-btn" v-popover:popoverWX type="danger">解绑微信</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="box-item">
|
||||
<div class="field-content">
|
||||
<span class="qq-svg-container"><wscn-icon-svg icon-class="QQ" class="icon"/></span>
|
||||
<el-button class="unbind-btn" v-popover:popoverQQ style="padding: 10px 18px" type="danger">
|
||||
解绑QQ
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<el-card class="box-card">
|
||||
<div slot="header" class="clearfix">
|
||||
<span style="line-height: 36px;">偏好设置</span>
|
||||
<el-button @click="updateSetting" style="float: right;margin-top: 5px;" size="small" type="success">
|
||||
更新偏好
|
||||
</el-button>
|
||||
</div>
|
||||
<div>
|
||||
<div class="box-item">
|
||||
<span class="field-label">文章平台默认项选择:</span>
|
||||
<el-checkbox-group v-model="articlePlatform">
|
||||
<el-checkbox label="wscn-platform">见闻</el-checkbox>
|
||||
<el-checkbox label="gold-platform">黄金头条</el-checkbox>
|
||||
<el-checkbox label="weex-platform">WEEX</el-checkbox>
|
||||
</el-checkbox-group>
|
||||
<span class="field-label">使用自定义主题:</span>
|
||||
<el-switch
|
||||
v-model="theme"
|
||||
on-text=""
|
||||
off-text="">
|
||||
</el-switch>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
|
||||
<ImageCropper field="img"
|
||||
:width="300"
|
||||
:height="300"
|
||||
url="/upload"
|
||||
@crop-upload-success="cropSuccess"
|
||||
:key="imagecropperKey"
|
||||
v-show="imagecropperShow"></ImageCropper>
|
||||
|
||||
<el-dialog title="昵称" v-model="nameDialogFormVisible">
|
||||
<el-form label-position="left" label-width="50px">
|
||||
<el-form-item label="昵称" style="width: 300px;">
|
||||
<input class="input" ref="nameInput" :value="name" autocomplete="off" :maxlength=10>
|
||||
<span>(最多填写十个字符)</span>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button @click="nameDialogFormVisible = false">取 消</el-button>
|
||||
<el-button type="primary" @click="setName">确 定</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
|
||||
<el-dialog title="简介" v-model="introductionDialogFormVisible">
|
||||
<el-form label-position="left" label-width="50px">
|
||||
<el-form-item label="简介" style="width: 500px;">
|
||||
<textarea :row=3 class="textarea" ref="introductionInput" :value="introduction"></textarea>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button @click="introductionDialogFormVisible = false">取 消</el-button>
|
||||
<el-button type="primary" @click="setIntroduction">确 定</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
|
||||
<el-dialog title="提示" v-model="resetPSWdialogVisible" size="tiny">
|
||||
<span>你确定要重设密码么? <strong>     ( 注:重设密码将会登出,请注意!!! )</strong></span>
|
||||
<span slot="footer" class="dialog-footer">
|
||||
<el-button @click="resetPSWdialogVisible = false">取 消</el-button>
|
||||
<el-button type="primary" @click="resetPSW">确 定</el-button>
|
||||
</span>
|
||||
</el-dialog>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import { updateInfo, unbind, updateSetting } from 'api/adminUser';
|
||||
import ImageCropper from 'components/ImageCropper';
|
||||
import PanThumb from 'components/PanThumb';
|
||||
import { toggleClass } from 'utils'
|
||||
|
||||
export default{
|
||||
components: { ImageCropper, PanThumb },
|
||||
data() {
|
||||
return {
|
||||
nameDialogFormVisible: false,
|
||||
introductionDialogFormVisible: false,
|
||||
resetPSWdialogVisible: false,
|
||||
popoverVisibleQQ: false,
|
||||
popoverVisibleWX: false,
|
||||
imagecropperShow: false,
|
||||
imagecropperKey: 0,
|
||||
articlePlatform: [],
|
||||
theme: false
|
||||
}
|
||||
},
|
||||
created() {
|
||||
if (this.setting.articlePlatform) {
|
||||
this.articlePlatform = this.setting.articlePlatform
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters([
|
||||
'name',
|
||||
'avatar',
|
||||
'email',
|
||||
'introduction',
|
||||
'roles',
|
||||
'uid',
|
||||
'setting'
|
||||
])
|
||||
},
|
||||
watch: {
|
||||
theme() {
|
||||
toggleClass(document.body, 'custom-theme')
|
||||
// this.$store.dispatch('setTheme', value);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
resetPSW() {
|
||||
this.$store.dispatch('LogOut').then(() => {
|
||||
this.$router.push({ path: '/sendpwd' })
|
||||
});
|
||||
},
|
||||
toggleResetDialog(state) {
|
||||
this.resetDialogVisible = state;
|
||||
},
|
||||
handleEditName() {
|
||||
this.nameDialogFormVisible = true;
|
||||
},
|
||||
handleIntroductionName() {
|
||||
this.introductionDialogFormVisible = true;
|
||||
},
|
||||
setName() {
|
||||
const displayName = this.$refs.nameInput.value;
|
||||
const data = {
|
||||
display_name: displayName,
|
||||
uid: this.uid
|
||||
};
|
||||
updateInfo(data).then(() => {
|
||||
this.$store.commit('SET_NAME', displayName);
|
||||
this.$notify({
|
||||
title: '成功',
|
||||
message: '昵称修改成功',
|
||||
type: 'success'
|
||||
});
|
||||
});
|
||||
this.nameDialogFormVisible = false;
|
||||
},
|
||||
setIntroduction() {
|
||||
const introduction = this.$refs.introductionInput.value;
|
||||
const data = {
|
||||
introduction,
|
||||
uid: this.uid
|
||||
};
|
||||
updateInfo(data).then(() => {
|
||||
this.$store.commit('SET_INTRODUCTION', introduction);
|
||||
this.$notify({
|
||||
title: '成功',
|
||||
message: '简介修改成功',
|
||||
type: 'success'
|
||||
});
|
||||
});
|
||||
this.introductionDialogFormVisible = false;
|
||||
},
|
||||
handleUnbind(unbindType) {
|
||||
const data = {
|
||||
unbind_type: unbindType
|
||||
};
|
||||
unbind(data).then(() => {
|
||||
this.$notify({
|
||||
title: '成功',
|
||||
message: '解绑成功,即将登出',
|
||||
type: 'success'
|
||||
});
|
||||
setTimeout(() => {
|
||||
this.$store.dispatch('LogOut').then(() => {
|
||||
this.$router.push({ path: '/login' })
|
||||
});
|
||||
}, 3000)
|
||||
});
|
||||
|
||||
this.popoverVisibleQQ = false;
|
||||
this.popoverVisibleWX = false;
|
||||
},
|
||||
handleImagecropper() {
|
||||
this.imagecropperShow = true;
|
||||
this.imagecropperKey = this.imagecropperKey + 1;
|
||||
},
|
||||
cropSuccess(url) {
|
||||
this.imagecropperShow = false;
|
||||
const data = {
|
||||
image: url,
|
||||
uid: this.uid
|
||||
};
|
||||
updateInfo(data).then(() => {
|
||||
this.$store.commit('SET_AVATAR', url);
|
||||
this.$notify({
|
||||
title: '成功',
|
||||
message: '头像修改成功',
|
||||
type: 'success'
|
||||
});
|
||||
});
|
||||
},
|
||||
updateSetting() {
|
||||
const obj = Object.assign(this.setting, { articlePlatform: this.articlePlatform });
|
||||
updateSetting({ setting: JSON.stringify(obj) }).then(() => {
|
||||
this.$store.commit('SET_SETTING', this.setting);
|
||||
this.$notify({
|
||||
title: '成功',
|
||||
message: '更新偏好成功',
|
||||
type: 'success'
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style rel="stylesheet/scss" lang="scss" scoped>
|
||||
.input {
|
||||
-moz-appearance: none;
|
||||
appearance: none;
|
||||
background-color: #fff;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #bfcbd9;
|
||||
color: #1f2d3d;
|
||||
display: block;
|
||||
font-size: inherit;
|
||||
height: 36px;
|
||||
line-height: 1;
|
||||
padding: 3px 10px;
|
||||
transition: border-color .2s cubic-bezier(.645, .045, .355, 1);
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.textarea {
|
||||
height: 90px;
|
||||
display: block;
|
||||
resize: vertical;
|
||||
padding: 5px 7px;
|
||||
line-height: 1.5;
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
font-size: 14px;
|
||||
color: #1f2d3d;
|
||||
background-color: #fff;
|
||||
background-image: none;
|
||||
border: 1px solid #bfcbd9;
|
||||
border-radius: 4px;
|
||||
transition: border-color .2s cubic-bezier(.645, .045, .355, 1);
|
||||
}
|
||||
|
||||
.icon {
|
||||
color: #fff;
|
||||
font-size: 30px;
|
||||
margin-top: 6px;
|
||||
}
|
||||
|
||||
.wx-svg-container, .qq-svg-container {
|
||||
display: inline-block;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
line-height: 40px;
|
||||
text-align: center;
|
||||
padding-top: 1px;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 10px;
|
||||
margin-right: 55px;
|
||||
}
|
||||
|
||||
.wx-svg-container {
|
||||
background-color: #8dc349;
|
||||
}
|
||||
|
||||
.qq-svg-container {
|
||||
background-color: #6BA2D6;
|
||||
}
|
||||
|
||||
.unbind-btn {
|
||||
position: absolute;
|
||||
right: -60px;
|
||||
top: 2px;
|
||||
}
|
||||
|
||||
.profile-container {
|
||||
padding: 20px;
|
||||
.box-card {
|
||||
width: 400px;
|
||||
margin: 30px;
|
||||
float: left;
|
||||
.field-label {
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
line-height: 36px;
|
||||
color: #333;
|
||||
padding-right: 30px;
|
||||
}
|
||||
.box-item {
|
||||
.field-content {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
.edit-btn {
|
||||
display: none;
|
||||
position: absolute;
|
||||
right: -50px;
|
||||
top: -5px;
|
||||
}
|
||||
}
|
||||
&:hover {
|
||||
.edit-btn {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.pan-info-roles {
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
color: #333;
|
||||
display: block;
|
||||
}
|
||||
</style>
|