Merge pull request #1 from PanJiaChen/master

同步更新
This commit is contained in:
AlecZhang 2017-12-01 15:21:39 +08:00 committed by GitHub
commit f1afeb0699
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
259 changed files with 10744 additions and 8062 deletions

View File

@ -1,5 +1,10 @@
{ {
"presets": ["es2015", "stage-2"], "presets": [
["env", {
"modules": false
}],
"stage-2"
],
"plugins": ["transform-runtime"], "plugins": ["transform-runtime"],
"comments": false "comments": false
} }

14
.editorconfig Normal file
View File

@ -0,0 +1,14 @@
# http://editorconfig.org
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
[*.md]
insert_final_newline = false
trim_trailing_whitespace = false

View File

@ -6,7 +6,8 @@ module.exports = {
}, },
env: { env: {
browser: true, browser: true,
node: true node: true,
es6: true,
}, },
extends: 'eslint:recommended', extends: 'eslint:recommended',
// required to lint *.vue files // required to lint *.vue files
@ -22,297 +23,122 @@ module.exports = {
} }
}, },
// add your custom rules here // add your custom rules here
//it is base on https://github.com/vuejs/eslint-config-vue
'rules': { 'rules': {
// don't require .vue extension when importing 'accessor-pairs': 2,
// 'import/extensions': ['error', 'always', { 'arrow-spacing': [2, { 'before': true, 'after': true }],
// 'js': 'never', 'block-spacing': [2, 'always'],
// 'vue': 'never' 'brace-style': [2, '1tbs', { 'allowSingleLine': true }],
// }], 'camelcase': [0, { 'properties': 'always' }],
// allow debugger during development 'comma-dangle': [2, 'never'],
'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0, 'comma-spacing': [2, { 'before': false, 'after': true }],
/* 'comma-style': [2, 'last'],
* Possible Errors 'constructor-super': 2,
*/ 'curly': [2, 'multi-line'],
'dot-location': [2, 'property'],
// disallow unnecessary parentheses 'eol-last': 2,
'no-extra-parens': ['error', 'all', {'nestedBinaryExpressions': false}], 'eqeqeq': [2, 'allow-null'],
'generator-star-spacing': [2, { 'before': true, 'after': true }],
// disallow negating the left operand of relational operators 'handle-callback-err': [2, '^(err|error)$' ],
'no-unsafe-negation': 'error', 'indent': [2, 2, { 'SwitchCase': 1 }],
'jsx-quotes': [2, 'prefer-single'],
// enforce valid JSDoc comments 'key-spacing': [2, { 'beforeColon': false, 'afterColon': true }],
'valid-jsdoc': 'off', 'keyword-spacing': [2, { 'before': true, 'after': true }],
'new-cap': [2, { 'newIsCap': true, 'capIsNew': false }],
/* 'new-parens': 2,
* Best Practices 'no-array-constructor': 2,
*/ 'no-caller': 2,
// 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', 'no-console': 'off',
'no-class-assign': 2,
// enforce consistent spacing before function definition opening parenthesis 'no-cond-assign': 2,
'space-before-function-paren': ['error', 'never'], 'no-const-assign': 2,
'no-control-regex': 0,
// enforce consistent spacing inside parentheses 'no-delete-var': 2,
'space-in-parens': 'error', 'no-dupe-args': 2,
'no-dupe-class-members': 2,
// require spacing around infix operators 'no-dupe-keys': 2,
'space-infix-ops': 'error', 'no-duplicate-case': 2,
'no-empty-character-class': 2,
// enforce consistent spacing before or after unary operators 'no-empty-pattern': 2,
'space-unary-ops': 'error', 'no-eval': 2,
'no-ex-assign': 2,
// enforce consistent spacing after the // or /* in a comment 'no-extend-native': 2,
'spaced-comment': 'error', 'no-extra-bind': 2,
'no-extra-boolean-cast': 2,
// require or disallow Unicode byte order mark (BOM) 'no-extra-parens': [2, 'functions'],
'unicode-bom': 'error', 'no-fallthrough': 2,
'no-floating-decimal': 2,
'no-func-assign': 2,
/* 'no-implied-eval': 2,
* ECMAScript 6 'no-inner-declarations': [2, 'functions'],
*/ 'no-invalid-regexp': 2,
'no-irregular-whitespace': 2,
// require braces around arrow function bodies 'no-iterator': 2,
'arrow-body-style': 'error', 'no-label-var': 2,
'no-labels': [2, { 'allowLoop': false, 'allowSwitch': false }],
// require parentheses around arrow function arguments 'no-lone-blocks': 2,
'arrow-parens': ['error', 'as-needed'], 'no-mixed-spaces-and-tabs': 2,
'no-multi-spaces': 2,
// enforce consistent spacing before and after the arrow in arrow functions 'no-multi-str': 2,
'arrow-spacing': 'error', 'no-multiple-empty-lines': [2, { 'max': 1 }],
'no-native-reassign': 2,
// enforce consistent spacing around * operators in generator functions 'no-negated-in-lhs': 2,
'generator-star-spacing': ['error', 'after'], 'no-new-object': 2,
'no-new-require': 2,
// disallow duplicate module imports 'no-new-symbol': 2,
'no-duplicate-imports': 'error', 'no-new-wrappers': 2,
'no-obj-calls': 2,
// disallow unnecessary computed property keys in object literals 'no-octal': 2,
'no-useless-computed-key': 'error', 'no-octal-escape': 2,
'no-path-concat': 2,
// disallow unnecessary constructors 'no-proto': 2,
'no-useless-constructor': 'error', 'no-redeclare': 2,
'no-regex-spaces': 2,
// disallow renaming import, export, and destructured assignments to the same name 'no-return-assign': [2, 'except-parens'],
'no-useless-rename': 'error', 'no-self-assign': 2,
'no-self-compare': 2,
// require let or const instead of var 'no-sequences': 2,
'no-var': 'error', 'no-shadow-restricted-names': 2,
'no-spaced-func': 2,
// require or disallow method and property shorthand syntax for object literals 'no-sparse-arrays': 2,
'object-shorthand': 'error', 'no-this-before-super': 2,
'no-throw-literal': 2,
// require arrow functions as callbacks 'no-trailing-spaces': 2,
'prefer-arrow-callback': 'error', 'no-undef': 2,
'no-undef-init': 2,
// require const declarations for variables that are never reassigned after declared 'no-unexpected-multiline': 2,
'prefer-const': 'error', 'no-unmodified-loop-condition': 2,
'no-unneeded-ternary': [2, { 'defaultAssignment': false }],
// disallow parseInt() in favor of binary, octal, and hexadecimal literals 'no-unreachable': 2,
'prefer-numeric-literals': 'error', 'no-unsafe-finally': 2,
'no-unused-vars': [2, { 'vars': 'all', 'args': 'none' }],
// require rest parameters instead of arguments 'no-useless-call': 2,
'prefer-rest-params': 'error', 'no-useless-computed-key': 2,
'no-useless-constructor': 2,
// require spread operators instead of .apply() 'no-useless-escape': 0,
'prefer-spread': 'error', 'no-whitespace-before-property': 2,
'no-with': 2,
// enforce spacing between rest and spread operators and their expressions 'one-var': [2, { 'initialized': 'never' }],
'rest-spread-spacing': 'error', 'operator-linebreak': [2, 'after', { 'overrides': { '?': 'before', ':': 'before' } }],
'padded-blocks': [2, 'never'],
// require or disallow spacing around embedded expressions of template strings 'quotes': [2, 'single', { 'avoidEscape': true, 'allowTemplateLiterals': true }],
'template-curly-spacing': 'error', 'semi': [2, 'never'],
'semi-spacing': [2, { 'before': false, 'after': true }],
// require or disallow spacing around the * in yield* expressions 'space-before-blocks': [2, 'always'],
'yield-star-spacing': 'error' 'space-before-function-paren': [2, 'never'],
'space-in-parens': [2, 'never'],
'space-infix-ops': 2,
'space-unary-ops': [2, { 'words': true, 'nonwords': false }],
'spaced-comment': [2, 'always', { 'markers': ['global', 'globals', 'eslint', 'eslint-disable', '*package', '!', ','] }],
'template-curly-spacing': [2, 'never'],
'use-isnan': 2,
'valid-typeof': 2,
'wrap-iife': [2, 'any'],
'yield-star-spacing': [2, 'both'],
'yoda': [2, 'never'],
'prefer-const': 2,
'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0,
'object-curly-spacing': [2, 'always', { objectsInObjects: false }],
'array-bracket-spacing': [2, 'never']
} }
} }

4
.gitignore vendored
View File

@ -1,10 +1,10 @@
.DS_Store .DS_Store
node_modules/ node_modules/
dist/ dist/
static/ckeditor gifs/
npm-debug.log npm-debug.log
test/unit/coverage test/unit/coverage
test/e2e/reports test/e2e/reports
selenium-debug.log selenium-debug.log
.idea .idea
package-lock.json

8
.postcssrc.js Normal file
View File

@ -0,0 +1,8 @@
// https://github.com/michael-ciniawsky/postcss-load-config
module.exports = {
"plugins": {
// to edit target browsers: use "browserslist" field in package.json
"autoprefixer": {}
}
}

View File

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

173
README.md
View File

@ -1,59 +1,92 @@
# vue-element-admin # <p align="center">
[线上地址](http://panjiachen.github.io/vue-element-admin) <img width="320" src="https://wpimg.wallstcn.com/ecc53a42-d79b-42e2-8852-5126b810a4c8.svg">
</p>
[English Document](https://github.com/PanJiaChen/vue-element-admin/blob/master/README-en.md) # vue-element-admin
[wiki](https://github.com/PanJiaChen/vue-element-admin/wiki) [![vue](https://img.shields.io/badge/vue-2.5.9-brightgreen.svg)](https://github.com/vuejs/vue)
[![element-ui](https://img.shields.io/badge/element--ui-2.0.7-brightgreen.svg)](https://github.com/ElemeFE/element)
[![license](https://img.shields.io/github/license/mashape/apistatus.svg)](https://github.com/PanJiaChen/vue-element-admin/blob/master/LICENSE)
[![GitHub release](https://img.shields.io/github/release/PanJiaChen/vue-element-admin.svg)]()
**注意该项目目前使用element-ui@1.3.3版本,所以最低兼容 Vue 2.3.0** **A magical vue admin.**
## 前言 - [线上地址](http://panjiachen.github.io/vue-element-admin)
> 这半年来一直在用vue写管理后台目前后台已经有百来个个页面十几种权限但维护成本依然很低所以准备开源分享一下后台开发的经验和成果。目前的技术栈主要的采用vue+element+axios由webpack2打包.由于是个人项目所以数据请求都是用了mockjs模拟。注意在次项目基础上改造开发时请移除mock文件。
写了一个系列的教程配套文章,如何从零构建后一个完整的后台项目: - [使用文档](https://panjiachen.github.io/vue-element-admin-site/#/)
- [wiki](https://github.com/PanJiaChen/vue-element-admin/wiki) - [English Document](https://github.com/PanJiaChen/vue-element-admin/blob/master/README-en.md)
- [wiki](https://github.com/PanJiaChen/vue-element-admin/wiki)
- [donate](https://panjiachen.github.io/vue-element-admin-site/#/donate)
**本项目的定位是后台集成方案,不适合当基础模板来开发。**
- 模板建议使用: [vueAdmin-template](https://github.com/PanJiaChen/vueAdmin-template)  
- 桌面端: [electron-vue-admin](https://github.com/PanJiaChen/electron-vue-admin)
**注意该项目目前使用element-ui@2.0.5版本,所以最低兼容 Vue 2.5.0**
楼主这里有一份调查[问卷](https://www.wjx.cn/m/16866569.aspx) 有空请填写一下,以表对本项目的支持~ps:不是给这个调查问卷网站做广告,所以填完问卷不用点上面抽奖有的没的那些东西
## 前序准备
你的本地环境需要安装 [node](http://nodejs.org/) 和 [git](https://git-scm.com/)。我们的技术栈基于 [ES2015+](http://es6.ruanyifeng.com/)、[vue](https://cn.vuejs.org/index.html)、[vuex](https://vuex.vuejs.org/zh-cn/)、[vue-router](https://router.vuejs.org/zh-cn/) 和 [element-ui](https://github.com/ElemeFE/element),提前了解和学习这些知识会对使用本项目有很大的帮助。
同时配套一个系列的教程文章,如何从零构建后一个完整的后台项目,建议大家先看完这些文章再来实践本项目
- [手摸手,带你用 vue 撸后台 系列一(基础篇)](https://juejin.im/post/59097cd7a22b9d0065fb61d2) - [手摸手,带你用 vue 撸后台 系列一(基础篇)](https://juejin.im/post/59097cd7a22b9d0065fb61d2)
- [手摸手,带你用 vue 撸后台 系列二(登录权限篇)](https://juejin.im/post/591aa14f570c35006961acac) - [手摸手,带你用 vue 撸后台 系列二(登录权限篇)](https://juejin.im/post/591aa14f570c35006961acac)
- [手摸手,带你用 vue 撸后台 系列三 (实战篇)](https://juejin.im/post/593121aa0ce4630057f70d35) - [手摸手,带你用 vue 撸后台 系列三 (实战篇)](https://juejin.im/post/593121aa0ce4630057f70d35)
- [手摸手带你用vue撸后台 系列四(vueAdmin 一个极简的后台基础模板)](https://juejin.im/post/595b4d776fb9a06bbe7dba56)
- [手摸手带你封装一个vue component](https://segmentfault.com/a/1190000009090836) - [手摸手带你封装一个vue component](https://segmentfault.com/a/1190000009090836)
相应需求开了一个qq群 591724180 方便大家交流 相应需求开了一个qq群 `591724180` 方便大家交流
**如有问题请先看上述文章和Wiki,若不能满足欢迎issue和pr~** 或者可以加入该 **[圈子](https://jianshiapp.com/circles/1209)** 讨论问题
**该项目并不是一个脚手架,更倾向于是一个集成解决方案方案** **如有问题请先看上述使用文档和文章,若不能满足,欢迎 issue 和 pr**
**该项目不支持低版本游览器有需求请自行添加polyfill[详情](https://github.com/PanJiaChen/vue-element-admin/wiki#babel-polyfill)** **本项目并不是一个脚手架,更倾向于是一个集成解决方案**
**该项目不支持低版本游览器(如ie)有需求请自行添加polyfill [详情](https://github.com/PanJiaChen/vue-element-admin/wiki#babel-polyfill)**
## 功能 ## 功能
- 登录/注销 - 登录/注销
- 权限验证 - 权限验证
- 侧边栏 - 多环境发布
- 面包屑 - 动态侧边栏(支持多级路由)
- 动态面包屑
- 国际化多语言
- 多种动态换肤
- 快捷导航(标签页)
- 富文本编辑器 - 富文本编辑器
- Markdown编辑器 - Markdown编辑器
- JSON编辑器 - JSON编辑器
- Screenfull全屏
- 列表拖拽 - 列表拖拽
- plitPane - Svg Sprite 图标
- Dropzone - Dashboard
- Sticky - 本地mock数据
- CountTo - Echarts 图表
- echarts图表 - Clipboard(剪贴复制)
- 401401错误页面 - 401/404错误页面
- 错误日志 - 错误日志
- 导出excel - 导出excel
- table example - 导出zip
- 前端可视化excel
- Table example
- 动态table example - 动态table example
- 拖拽table example - 拖拽table example
- 内联编辑table example - 内联编辑table example
- form example - Form example
- 多环境发布 - 二步登录
- dashboard - SplitPane
- 二次登录 - Dropzone
- 动态侧边栏(支持多级路由) - Sticky
- mock数据 - CountTo
- Markdown2html
## 开发 ## 开发
@ -63,7 +96,8 @@
# 安装依赖 # 安装依赖
npm install npm install
   //or # 建议不要用cnpm  安装有各种诡异的bug 可以通过如下操作解决npm速度慢的问题    
//or # 建议不要用cnpm  安装有各种诡异的bug 可以通过如下操作解决npm速度慢的问题
npm install --registry=https://registry.npm.taobao.org npm install --registry=https://registry.npm.taobao.org
# 本地开发 开启服务 # 本地开发 开启服务
@ -80,6 +114,8 @@
npm run build:prod npm run build:prod
``` ```
更多信息请参考 [使用文档](https://panjiachen.github.io/vue-element-admin-site/#/)
## 目录结构 ## 目录结构
```shell ```shell
├── build // 构建相关   ├── build // 构建相关  
@ -89,17 +125,20 @@
│   ├── assets // 主题 字体等静态资源 │   ├── assets // 主题 字体等静态资源
│   ├── components // 全局公用组件 │   ├── components // 全局公用组件
│   ├── directive // 全局指令 │   ├── directive // 全局指令
│   ├── filtres // 全局filter │   ├── filtres // 全局 filter
│   ├── mock // mock数据 │   ├── icons // 项目所有 svg icons
│   ├── lang // 国际化 language
│   ├── mock // 项目mock 模拟数据
│   ├── router // 路由 │   ├── router // 路由
│   ├── store // 全局store管理 │   ├── store // 全局 store管理
│   ├── styles // 全局样式 │   ├── styles // 全局样式
│   ├── utils // 全局公用方法 │   ├── utils // 全局公用方法
│   ├── view // view │   ├── vendor // 公用vendor
│   ├── views // view
│   ├── App.vue // 入口页面 │   ├── App.vue // 入口页面
│   └── main.js // 入口 加载组件 初始化等 │   ├── main.js // 入口 加载组件 初始化等
│ └── permission.js // 权限管理
├── static // 第三方不打包资源 ├── static // 第三方不打包资源
│   ├── jquery
│   └── Tinymce // 富文本 │   └── Tinymce // 富文本
├── .babelrc // babel-loader 配置 ├── .babelrc // babel-loader 配置
├── eslintrc.js // eslint 配置项 ├── eslintrc.js // eslint 配置项
@ -113,63 +152,13 @@
## Changelog ## Changelog
Detailed changes for each release are documented in the [release notes](https://github.com/PanJiaChen/vue-element-admin/releases). Detailed changes for each release are documented in the [release notes](https://github.com/PanJiaChen/vue-element-admin/releases).
## 状态管理 ## [查看更多demo](http://panjiachen.github.io/vue-element-admin)
后台只有user和app配置相关状态使用vuex存在全局其它数据都由每个业务页面自己管理。 ![](https://wpimg.wallstcn.com/1bc334a6-32a8-4f29-a037-ac3f5ce32588.png)
## Donate
If you find this project useful, you can buy me a cup of coffee
![donate](https://panjiachen.github.io/donate/donation.png)
## 效果图 ## License
#### 两步验证登录 支持微信和qq
![两步验证 here](https://github.com/PanJiaChen/vue-element-admin/blob/master/gifs/2login.gif)
#### 真正的动态换肤
![真正的动态换肤](https://github.com/PanJiaChen/vue-element-admin/blob/master/gifs/theme.gif)<br />
#### 可收起侧边栏
![enter image description here](https://github.com/PanJiaChen/vue-element-admin/blob/master/gifs/leftmenu.gif)
#### table拖拽排序
![enter image description here](https://github.com/PanJiaChen/vue-element-admin/blob/master/gifs/order.gif)
#### 动态table
![enter image description here](https://github.com/PanJiaChen/vue-element-admin/blob/master/gifs/dynamictable.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)
#### 封装table组件
![enter image description here](https://github.com/PanJiaChen/vue-element-admin/blob/master/gifs/table.gif)
#### 图表
![enter image description here](https://github.com/PanJiaChen/vue-element-admin/blob/master/gifs/echarts.gif)
#### 导出excel
![enter image description here](https://github.com/PanJiaChen/vue-element-admin/blob/master/gifs/excel.png)
## [更多demo](http://panjiachen.github.io/vue-element-admin)
MIT

View File

@ -9,9 +9,7 @@ var webpack = require('webpack');
var config = require('../config'); var config = require('../config');
var webpackConfig = require('./webpack.prod.conf'); var webpackConfig = require('./webpack.prod.conf');
console.log(process.env.NODE_ENV) var spinner = ora('building for ' + process.env.NODE_ENV + ' of ' + process.env.env_config+ ' mode...' )
var spinner = ora('building for ' + process.env.NODE_ENV + '...')
spinner.start() spinner.start()

View File

@ -1,4 +1,5 @@
require('./check-versions')(); // 检查 Node 和 npm 版本 require('./check-versions')(); // 检查 Node 和 npm 版本
var config = require('../config'); var config = require('../config');
if (!process.env.NODE_ENV) { if (!process.env.NODE_ENV) {
process.env.NODE_ENV = JSON.parse(config.dev.env.NODE_ENV) process.env.NODE_ENV = JSON.parse(config.dev.env.NODE_ENV)
@ -28,19 +29,19 @@ var devMiddleware = require('webpack-dev-middleware')(compiler, {
}); });
var hotMiddleware = require('webpack-hot-middleware')(compiler, { var hotMiddleware = require('webpack-hot-middleware')(compiler, {
log: () => { log: false,
} heartbeat: 2000
}); });
// force page reload when html-webpack-plugin template changes // force page reload when html-webpack-plugin template changes
compiler.plugin('compilation', function (compilation) { // currently disabled until this is resolved:
compilation.plugin('html-webpack-plugin-after-emit', function (data, cb) { // https://github.com/jantimon/html-webpack-plugin/issues/680
hotMiddleware.publish({action: 'reload'}); // compiler.plugin('compilation', function (compilation) {
cb() // compilation.plugin('html-webpack-plugin-after-emit', function (data, cb) {
}) // hotMiddleware.publish({ action: 'reload' })
}); // cb()
// })
// compiler.apply(new DashboardPlugin()); // })
// proxy api requests // proxy api requests
Object.keys(proxyTable).forEach(function (context) { Object.keys(proxyTable).forEach(function (context) {
@ -67,18 +68,26 @@ app.use(staticPath, express.static('./static'));
var uri = 'http://localhost:' + port var uri = 'http://localhost:' + port
devMiddleware.waitUntilValid(function () { var _resolve
console.log('> Listening at ' + uri + '\n') var readyPromise = new Promise(resolve => {
}); _resolve = resolve
})
module.exports = app.listen(port, function (err) { console.log('> Starting dev server...')
if (err) { devMiddleware.waitUntilValid(() => {
console.log(err); console.log('> Listening at ' + uri + '\n')
return // when env is testing, don't need open it
} if (autoOpenBrowser && process.env.NODE_ENV !== 'testing') {
opn(uri)
}
_resolve()
})
// when env is testing, don't need open it var server = app.listen(port)
if (autoOpenBrowser && process.env.NODE_ENV !== 'testing') {
opn(uri) module.exports = {
} ready: readyPromise,
}); close: () => {
server.close()
}
}

View File

@ -3,69 +3,76 @@ var config = require('../config')
var ExtractTextPlugin = require('extract-text-webpack-plugin') var ExtractTextPlugin = require('extract-text-webpack-plugin')
exports.assetsPath = function (_path) { exports.assetsPath = function (_path) {
var assetsSubDirectory = process.env.NODE_ENV === 'production' var assetsSubDirectory = process.env.NODE_ENV === 'production'
? config.build.assetsSubDirectory ? config.build.assetsSubDirectory
: config.dev.assetsSubDirectory : config.dev.assetsSubDirectory
return path.posix.join(assetsSubDirectory, _path) return path.posix.join(assetsSubDirectory, _path)
} }
exports.cssLoaders = function (options) { exports.cssLoaders = function (options) {
options = options || {} options = options || {}
var cssLoader = { var cssLoader = {
loader: 'css-loader', loader: 'css-loader',
options: { options: {
minimize: process.env.NODE_ENV === 'production', minimize: process.env.NODE_ENV === 'production',
sourceMap: options.sourceMap sourceMap: options.sourceMap
} }
}
var postcssLoader = {
loader: 'postcss-loader',
options: {
sourceMap: true
}
}
// generate loader string to be used with extract text plugin
function generateLoaders (loader, loaderOptions) {
var loaders = options.usePostCSS !== false ? [cssLoader, postcssLoader] : [cssLoader]
if (loader) {
loaders.push({
loader: loader + '-loader',
options: Object.assign({}, loaderOptions, {
sourceMap: options.sourceMap
})
})
} }
// generate loader string to be used with extract text plugin // Extract CSS when that option is specified
function generateLoaders(loader, loaderOptions) { // (which is the case during production build)
var loaders = [cssLoader] if (options.extract) {
if (loader) { return ExtractTextPlugin.extract({
loaders.push({ use: loaders,
loader: loader + '-loader', fallback: 'vue-style-loader'
options: Object.assign({}, loaderOptions, { })
sourceMap: options.sourceMap } else {
}) return ['vue-style-loader'].concat(loaders)
})
}
// 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 // https://vue-loader.vuejs.org/en/configurations/extract-css.html
return { return {
css: generateLoaders(), css: generateLoaders(),
postcss: generateLoaders(), postcss: generateLoaders(),
less: generateLoaders('less'), less: generateLoaders('less'),
sass: generateLoaders('sass', {indentedSyntax: true}), sass: generateLoaders('sass', { indentedSyntax: true }),
scss: generateLoaders('sass'), scss: generateLoaders('sass'),
stylus: generateLoaders('stylus'), stylus: generateLoaders('stylus'),
styl: generateLoaders('stylus') styl: generateLoaders('stylus')
} }
} }
// Generate loaders for standalone style files (outside of .vue) // Generate loaders for standalone style files (outside of .vue)
exports.styleLoaders = function (options) { exports.styleLoaders = function (options) {
var output = [] var output = []
var loaders = exports.cssLoaders(options) var loaders = exports.cssLoaders(options)
for (var extension in loaders) { for (var extension in loaders) {
var loader = loaders[extension] var loader = loaders[extension]
output.push({ output.push({
test: new RegExp('\\.' + extension + '$'), test: new RegExp('\\.' + extension + '$'),
use: loader use: loader
}) })
} }
return output return output
} }

View File

@ -1,85 +1,92 @@
var path = require('path'); var path = require('path')
var utils = require('./utils'); var utils = require('./utils')
var config = require('../config'); var config = require('../config')
var vueLoaderConfig = require('./vue-loader.conf'); var vueLoaderConfig = require('./vue-loader.conf')
function resolve(dir) { function resolve(dir) {
return path.join(__dirname, '..', dir) return path.join(__dirname, '..', dir)
} }
var src = path.resolve(__dirname, '../src');
module.exports = { module.exports = {
entry: { entry: {
app: './src/main.js' app: './src/main.js'
}, },
output: { output: {
path: config.build.assetsRoot, path: config.build.assetsRoot,
filename: '[name].js', filename: '[name].js',
publicPath: process.env.NODE_ENV !== 'development' ? config.build.assetsPublicPath: config.dev.assetsPublicPath publicPath: process.env.NODE_ENV === 'production' ? config.build.assetsPublicPath : config.dev.assetsPublicPath
}, },
resolve: { resolve: {
extensions: ['.js', '.vue', '.json'], extensions: ['.js', '.vue', '.json'],
alias: { alias: {
'vue$': 'vue/dist/vue.esm.js', 'vue$': 'vue/dist/vue.esm.js',
'@': resolve('src'), '@': resolve('src'),
'src': path.resolve(__dirname, '../src'), 'src': path.resolve(__dirname, '../src'),
'assets': path.resolve(__dirname, '../src/assets'), 'assets': path.resolve(__dirname, '../src/assets'),
'components': path.resolve(__dirname, '../src/components'), 'components': path.resolve(__dirname, '../src/components'),
'views': path.resolve(__dirname, '../src/views'), 'views': path.resolve(__dirname, '../src/views'),
'styles': path.resolve(__dirname, '../src/styles'), 'styles': path.resolve(__dirname, '../src/styles'),
'api': path.resolve(__dirname, '../src/api'), 'api': path.resolve(__dirname, '../src/api'),
'utils': path.resolve(__dirname, '../src/utils'), 'utils': path.resolve(__dirname, '../src/utils'),
'store': path.resolve(__dirname, '../src/store'), 'store': path.resolve(__dirname, '../src/store'),
'router': path.resolve(__dirname, '../src/router'), 'router': path.resolve(__dirname, '../src/router'),
'mock': path.resolve(__dirname, '../src/mock'), 'mock': path.resolve(__dirname, '../src/mock'),
'vendor': path.resolve(__dirname, '../src/vendor'), 'vendor': path.resolve(__dirname, '../src/vendor'),
'static': path.resolve(__dirname, '../static') 'static': path.resolve(__dirname, '../static')
}
},
module: {
rules: [
{
test: /\.(js|vue)$/,
loader: 'eslint-loader',
enforce: "pre",
include: [resolve('src'), resolve('test')],
options: {
formatter: require('eslint-friendly-formatter')
} }
}, },
externals: { {
jquery: 'jQuery' test: /\.vue$/,
}, loader: 'vue-loader',
module: { options: vueLoaderConfig
rules: [ },
// { {
// test: /\.(js|vue)$/, test: /\.js$/,
// loader: 'eslint-loader', loader: 'babel-loader?cacheDirectory',
// enforce: "pre", include: [resolve('src'), resolve('test')]
// include: [resolve('src'), resolve('test')], },
// options: { {
// formatter: require('eslint-friendly-formatter') test: /\.svg$/,
// } loader: 'svg-sprite-loader',
// }, include: [resolve('src/icons')],
{ test: /\.vue$/, options: {
loader: 'vue-loader', symbolId: 'icon-[name]'
options: vueLoaderConfig }
}, },
{ {
test: /\.js$/, test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
loader: 'babel-loader?cacheDirectory', loader: 'url-loader',
include: [resolve('src'), resolve('test')] exclude: [resolve('src/icons')],
}, options: {
{ limit: 10000,
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/, name: utils.assetsPath('img/[name].[hash:7].[ext]')
loader: 'url-loader', }
query: { },
limit: 10000, {
name: utils.assetsPath('img/[name].[hash:7].[ext]') test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
} loader: 'url-loader',
}, options: {
{ limit: 10000,
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/, name: utils.assetsPath('fonts/[name].[hash:7].[ext]')
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')
//注入全局mixin // },
// sassResources: path.join(__dirname, '../src/styles/mixin.scss'),
// sassLoader: {
// data: path.join(__dirname, '../src/styles/index.scss')
// },
} }

View File

@ -9,39 +9,36 @@ var FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin')
// add hot-reload related code to entry chunks // add hot-reload related code to entry chunks
Object.keys(baseWebpackConfig.entry).forEach(function (name) { Object.keys(baseWebpackConfig.entry).forEach(function (name) {
baseWebpackConfig.entry[name] = ['./build/dev-client'].concat(baseWebpackConfig.entry[name]) baseWebpackConfig.entry[name] = ['./build/dev-client'].concat(baseWebpackConfig.entry[name])
}) })
function resolveApp(relativePath) { function resolveApp(relativePath) {
return path.resolve(relativePath); return path.resolve(relativePath);
} }
module.exports = merge(baseWebpackConfig, { module.exports = merge(baseWebpackConfig, {
module: { module: {
rules: utils.styleLoaders({sourceMap: config.dev.cssSourceMap}) rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap, usePostCSS: true })
}, },
// cheap-source-map is faster for development // cheap-source-map is faster for development
devtool: '#cheap-source-map', devtool: '#cheap-source-map',
cache: true, cache: true,
plugins: [ plugins: [
new webpack.DefinePlugin({ new webpack.DefinePlugin({
'process.env': config.dev.env 'process.env': config.dev.env
}), }),
new webpack.ProvidePlugin({ // https://github.com/glenjamin/webpack-hot-middleware#installation--usage
$: 'jquery', new webpack.HotModuleReplacementPlugin(),
'jQuery': 'jquery' new webpack.NoEmitOnErrorsPlugin(),
}), // https://github.com/ampedandwired/html-webpack-plugin
// https://github.com/glenjamin/webpack-hot-middleware#installation--usage new HtmlWebpackPlugin({
new webpack.HotModuleReplacementPlugin(), filename: 'index.html',
new webpack.NoEmitOnErrorsPlugin(), template: 'index.html',
// https://github.com/ampedandwired/html-webpack-plugin favicon: resolveApp('favicon.ico'),
new HtmlWebpackPlugin({ inject: true,
filename: 'index.html', path: config.dev.assetsPublicPath + config.dev.assetsSubDirectory
template: 'index.html', }),
favicon: resolveApp('favicon.ico'), new FriendlyErrorsPlugin()
inject: true, ]
path:config.dev.staticPath
}),
new FriendlyErrorsPlugin()
]
}) })

View File

@ -9,105 +9,133 @@ var HtmlWebpackPlugin = require('html-webpack-plugin')
var ExtractTextPlugin = require('extract-text-webpack-plugin') var ExtractTextPlugin = require('extract-text-webpack-plugin')
var OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin') var OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin')
var env = process.env.NODE_ENV === 'production' ? config.build.prodEnv : config.build.sitEnv var env = config.build[process.env.env_config+'Env']
function resolveApp(relativePath) { function resolveApp(relativePath) {
return path.resolve(relativePath); return path.resolve(relativePath);
} }
var webpackConfig = merge(baseWebpackConfig, { var webpackConfig = merge(baseWebpackConfig, {
module: { module: {
rules: utils.styleLoaders({ rules: utils.styleLoaders({
sourceMap: config.build.productionSourceMap, sourceMap: config.build.productionSourceMap,
extract: true extract: true,
}) usePostCSS: true
}, })
devtool: config.build.productionSourceMap ? '#source-map' : false, },
output: { devtool: config.build.productionSourceMap ? '#source-map' : false,
path: config.build.assetsRoot, output: {
filename: utils.assetsPath('js/[name].[chunkhash].js'), path: config.build.assetsRoot,
chunkFilename: utils.assetsPath('js/[id].[chunkhash].js') filename: utils.assetsPath('js/[name].[chunkhash].js'),
}, chunkFilename: utils.assetsPath('js/[id].[chunkhash].js'),
plugins: [ publicPath: config.build.assetsPublicPath
// http://vuejs.github.io/vue-loader/en/workflow/production.html },
new webpack.DefinePlugin({ plugins: [
'process.env': env // http://vuejs.github.io/vue-loader/en/workflow/production.html
}), new webpack.DefinePlugin({
new webpack.optimize.UglifyJsPlugin({ 'process.env': env
compress: { }),
warnings: false new webpack.optimize.UglifyJsPlugin({
}, compress: {
sourceMap: true warnings: false
}), },
// extract css into its own file sourceMap: true,
new ExtractTextPlugin({ parallel: true
filename: utils.assetsPath('css/[name].[contenthash].css') }),
}), // extract css into its own file
// Compress extracted CSS. We are using this plugin so that possible new ExtractTextPlugin({
// duplicated CSS from different components can be deduped. filename: utils.assetsPath('css/[name].[contenthash].css')
new OptimizeCSSPlugin(), }),
// generate dist index.html with correct asset hash for caching. // Compress extracted CSS. We are using this plugin so that possible
// you can customize output by editing /index.html // duplicated CSS from different components can be deduped.
// see https://github.com/ampedandwired/html-webpack-plugin new OptimizeCSSPlugin(),
new HtmlWebpackPlugin({ // generate dist index.html with correct asset hash for caching.
filename: process.env.NODE_ENV === 'testing' // you can customize output by editing /index.html
? 'index.html' // see https://github.com/ampedandwired/html-webpack-plugin
: config.build.index, new HtmlWebpackPlugin({
template: 'index.html', filename: 'index.html',
inject: true, template: 'index.html',
favicon: resolveApp('favicon.ico'), inject: true,
minify: { favicon: resolveApp('favicon.ico'),
removeComments: true, minify: {
collapseWhitespace: true, removeComments: true,
removeRedundantAttributes: true, collapseWhitespace: true,
useShortDoctype: true, removeRedundantAttributes: true,
removeEmptyAttributes: true, useShortDoctype: true,
removeStyleLinkTypeAttributes: true, removeEmptyAttributes: true,
keepClosingSlash: true, removeStyleLinkTypeAttributes: true,
minifyJS: true, keepClosingSlash: true,
minifyCSS: true, minifyJS: true,
minifyURLs: true minifyCSS: true,
}, minifyURLs: true
path:config.build.staticPath, },
// necessary to consistently work with multiple chunks via CommonsChunkPlugin path: config.build.assetsPublicPath + config.build.assetsSubDirectory,
chunksSortMode: 'dependency' // necessary to consistently work with multiple chunks via CommonsChunkPlugin
}), chunksSortMode: 'dependency'
// split vendor js into its own file }),
new webpack.optimize.CommonsChunkPlugin({ // cache Module Identifiers
name: 'vendor', new webpack.HashedModuleIdsPlugin(),
minChunks: function (module, count) { // enable scope hoisting
// any required modules inside node_modules are extracted to vendor new webpack.optimize.ModuleConcatenationPlugin(),
return ( // split vendor js into its own file
module.resource && new webpack.optimize.CommonsChunkPlugin({
/\.js$/.test(module.resource) && name: 'vendor',
module.resource.indexOf( minChunks: function (module) {
path.join(__dirname, '../node_modules') // any required modules inside node_modules are extracted to vendor
) === 0 return (
) module.resource &&
} /\.js$/.test(module.resource) &&
}), module.resource.indexOf(
// extract webpack runtime and module manifest to its own file in order to path.join(__dirname, '../node_modules')
// prevent vendor hash from being updated whenever app bundle is updated ) === 0
new webpack.optimize.CommonsChunkPlugin({ )
name: 'manifest', }
chunks: ['vendor'] }),
}), // extract webpack runtime and module manifest to its own file in order to
// copy custom static assets // prevent vendor hash from being updated whenever app bundle is updated
new CopyWebpackPlugin([ new webpack.optimize.CommonsChunkPlugin({
{ name: 'manifest',
from: path.resolve(__dirname, '../static'), minChunks: Infinity
to: config.build.assetsSubDirectory, }),
ignore: ['.*'] // split echarts into its own file
} new webpack.optimize.CommonsChunkPlugin({
]), async: 'echarts',
new webpack.ProvidePlugin({ minChunks(module) {
$: 'jquery', var context = module.context;
'jQuery': 'jquery' return context && (context.indexOf('echarts') >= 0 || context.indexOf('zrender') >= 0);
}) }
] }),
// split xlsx into its own file
new webpack.optimize.CommonsChunkPlugin({
async: 'xlsx',
minChunks(module) {
var context = module.context;
return context && (context.indexOf('xlsx') >= 0);
}
}),
// This instance extracts shared chunks from code splitted chunks and bundles them
// in a separate chunk, similar to the vendor chunk
// see: https://webpack.js.org/plugins/commons-chunk-plugin/#extra-async-commons-chunk
new webpack.optimize.CommonsChunkPlugin({
name: 'app',
async: 'vendor-async',
children: true,
minChunks: 3
}),
// copy custom static assets
new CopyWebpackPlugin([{
from: path.resolve(__dirname, '../static'),
to: config.build.assetsSubDirectory,
ignore: ['.*']
}])
]
}) })
if (config.build.bundleAnalyzerReport) { if (config.build.bundleAnalyzerReport) {
var BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin var BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
webpackConfig.plugins.push(new BundleAnalyzerPlugin()) webpackConfig.plugins.push(new BundleAnalyzerPlugin())
} }
module.exports = webpackConfig module.exports = webpackConfig

View File

@ -1,5 +1,6 @@
module.exports = { module.exports = {
NODE_ENV: '"development"', NODE_ENV: '"development"',
ENV_CONFIG: '"dev"',
BASE_API: '"https://api-dev"', BASE_API: '"https://api-dev"',
APP_ORIGIN: '"https://wallstreetcn.com"' APP_ORIGIN: '"https://wallstreetcn.com"'
} }

View File

@ -3,39 +3,37 @@ var path = require('path')
module.exports = { module.exports = {
build: { build: {
sitEnv: require('./sit.env'), sitEnv: require('./sit.env'),
prodEnv: require('./prod.env'), prodEnv: require('./prod.env'),
index: path.resolve(__dirname, '../dist/index.html'), index: path.resolve(__dirname, '../dist/index.html'),
assetsRoot: path.resolve(__dirname, '../dist'), assetsRoot: path.resolve(__dirname, '../dist'),
assetsSubDirectory: '', assetsSubDirectory: 'static',
assetsPublicPath: './', //生产环境assetsPublicPath: '/' assetsPublicPath: './', //请根据自己路径配置更改
staticPath:'./', //生产环境 staticPath:'' productionSourceMap: false,
productionSourceMap: true, // Gzip off by default as many popular static hosts such as
// Gzip off by default as many popular static hosts such as // Surge or Netlify already gzip all static assets for you.
// Surge or Netlify already gzip all static assets for you. // Before setting to `true`, make sure to:
// Before setting to `true`, make sure to: // npm install --save-dev compression-webpack-plugin
// npm install --save-dev compression-webpack-plugin productionGzip: false,
productionGzip: false, productionGzipExtensions: ['js', 'css'],
productionGzipExtensions: ['js', 'css'], // Run the build command with an extra argument to
// Run the build command with an extra argument to // View the bundle analyzer report after build finishes:
// View the bundle analyzer report after build finishes: // `npm run build --report`
// `npm run build --report` // Set to `true` or `false` to always turn it on or off
// Set to `true` or `false` to always turn it on or off bundleAnalyzerReport: process.env.npm_config_report
bundleAnalyzerReport: process.env.npm_config_report
}, },
dev: { dev: {
env: require('./dev.env'), env: require('./dev.env'),
port: 9527, port: 9527,
autoOpenBrowser: true, autoOpenBrowser: true,
assetsSubDirectory: 'static', assetsSubDirectory: 'static',
assetsPublicPath: '/', assetsPublicPath: '/',
staticPath:'/static/', proxyTable: {},
proxyTable: {}, // CSS Sourcemaps off by default because relative paths are "buggy"
// CSS Sourcemaps off by default because relative paths are "buggy" // with this option, according to the CSS-Loader README
// with this option, according to the CSS-Loader README // (https://github.com/webpack/css-loader#sourcemaps)
// (https://github.com/webpack/css-loader#sourcemaps) // In our experience, they generally work as expected,
// In our experience, they generally work as expected, // just be aware of this issue when enabling this option.
// just be aware of this issue when enabling this option. cssSourceMap: false
cssSourceMap: false
} }
} }

View File

@ -1,5 +1,6 @@
module.exports = { module.exports = {
NODE_ENV: '"production"', NODE_ENV: '"production"',
ENV_CONFIG: '"prod"',
BASE_API: '"https://api-prod"', BASE_API: '"https://api-prod"',
APP_ORIGIN: '"https://wallstreetcn.com"' APP_ORIGIN: '"https://wallstreetcn.com"'
}; };

View File

@ -1,5 +1,6 @@
module.exports = { module.exports = {
NODE_ENV: '"production"', NODE_ENV: '"production"',
ENV_CONFIG: '"sit"',
BASE_API: '"https://api-sit"', BASE_API: '"https://api-sit"',
APP_ORIGIN: '"https://wallstreetcn.com"' APP_ORIGIN: '"https://wallstreetcn.com"'
}; };

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 117 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

After

Width:  |  Height:  |  Size: 66 KiB

BIN
gifs/tabs.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

View File

@ -1,17 +1,15 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"> <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="renderer" content="webkit"> <meta name="renderer" content="webkit">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"> <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
<title>Juicy</title> <title>vue-element-admin</title>
</head> </head>
<body> <script src=<%= htmlWebpackPlugin.options.path %>/tinymce/tinymce.min.js></script>
<script src=<%= htmlWebpackPlugin.options.path %>jquery.min.js></script> <body>
<script src=<%= htmlWebpackPlugin.options.path %>tinymce/tinymce.min.js></script> <div id="app"></div>
<div id="app"></div> <!-- built files will be auto injected -->
<!-- built files will be auto injected --> </body>
</body>
</html> </html>

View File

@ -1,95 +1,101 @@
{ {
"name": "juicy", "name": "vue-element-admin",
"version": "1.0.1", "version": "3.0.0",
"description": "A Vue.js admin", "description": "A Vue.js admin",
"author": "Pan <panfree23@gmail.com>", "author": "Pan <panfree23@gmail.com>",
"license": "MIT", "license": "MIT",
"private": true, "private": true,
"scripts": { "scripts": {
"dev": "node build/dev-server.js", "dev": "node build/dev-server.js",
"build:prod": "cross-env NODE_ENV=production node build/build.js", "build:prod": "cross-env NODE_ENV=production env_config=prod node build/build.js",
"build:sit": "cross-env NODE_ENV=sit node build/build.js", "build:sit": "cross-env NODE_ENV=production env_config=sit node build/build.js",
"build:sit-preview": "cross-env NODE_ENV=sit npm_config_preview=true npm_config_report=true node build/build.js", "build:sit-preview": "cross-env NODE_ENV=production env_config=sit npm_config_preview=true npm_config_report=true node build/build.js",
"lint": "eslint --ext .js,.vue src" "lint": "eslint --ext .js,.vue src"
}, },
"dependencies": { "dependencies": {
"axios": "0.15.3", "axios": "0.17.1",
"codemirror": "5.25.2", "clipboard": "1.7.1",
"dropzone": "4.3.0", "codemirror": "5.31.0",
"echarts": "3.4.0", "dropzone": "5.2.0",
"element-ui": "1.3.3", "echarts": "3.8.5",
"element-ui": "2.0.7",
"file-saver": "1.3.3", "file-saver": "1.3.3",
"jquery": "3.1.1", "font-awesome": "4.7.0",
"js-cookie": "2.1.3", "js-cookie": "2.2.0",
"jsonlint": "1.6.2", "jsonlint": "1.6.2",
"jszip": "3.1.4",
"mockjs": "1.0.1-beta3", "mockjs": "1.0.1-beta3",
"normalize.css": "3.0.2", "normalize.css": "7.0.0",
"nprogress": "0.2.0", "nprogress": "0.2.0",
"showdown": "1.7.1", "screenfull": "3.3.2",
"showdown": "1.8.2",
"simplemde": "1.11.2", "simplemde": "1.11.2",
"sortablejs": "1.5.1", "sortablejs": "1.6.1",
"vue": "2.3.3", "vue": "2.5.9",
"vue-count-to": "1.0.5", "vue-count-to": "1.0.10",
"vue-multiselect": "2.0.0-beta.15", "vue-i18n": "7.3.2",
"vue-router": "2.5.3", "vue-multiselect": "2.0.6",
"vuedraggable": "2.8.4", "vue-router": "3.0.1",
"vuex": "2.3.1", "vue-splitpane": "1.0.0",
"xlsx": "0.8.1" "vuedraggable": "2.15.0",
"vuex": "3.0.1",
"xlsx": "^0.11.7"
}, },
"devDependencies": { "devDependencies": {
"autoprefixer": "6.7.2", "autoprefixer": "7.1.6",
"babel-core": "6.22.1", "babel-core": "6.26.0",
"babel-eslint": "7.1.1", "babel-eslint": "8.0.2",
"babel-loader": "6.2.10", "babel-loader": "7.1.2",
"babel-plugin-transform-runtime": "6.22.0", "babel-plugin-transform-runtime": "6.23.0",
"babel-preset-es2015": "6.22.0", "babel-preset-env": "1.6.1",
"babel-preset-stage-2": "6.22.0", "babel-preset-stage-2": "6.24.1",
"babel-register": "6.22.0", "babel-register": "6.26.0",
"chalk": "1.1.3", "chalk": "2.3.0",
"connect-history-api-fallback": "1.3.0", "connect-history-api-fallback": "1.4.0",
"copy-webpack-plugin": "4.0.1", "copy-webpack-plugin": "4.2.0",
"cross-env": "4.0.0", "cross-env": "5.1.1",
"css-loader": "0.28.0", "css-loader": "0.28.7",
"eslint": "3.19.0", "eslint": "4.11.0",
"eslint-friendly-formatter": "2.0.7", "eslint-friendly-formatter": "3.0.0",
"eslint-import-resolver-webpack": "0.8.1", "eslint-import-resolver-webpack": "0.8.3",
"eslint-loader": "1.7.1", "eslint-loader": "1.9.0",
"eslint-plugin-html": "2.0.1", "eslint-plugin-html": "3.2.2",
"eslint-plugin-import": "2.2.0", "eslint-plugin-import": "2.8.0",
"eventsource-polyfill": "0.9.6", "eventsource-polyfill": "0.9.6",
"express": "4.14.1", "express": "4.16.2",
"extract-text-webpack-plugin": "2.0.0", "extract-text-webpack-plugin": "3.0.2",
"file-loader": "0.10.0", "file-loader": "0.11.2",
"friendly-errors-webpack-plugin": "1.1.3", "friendly-errors-webpack-plugin": "1.6.1",
"function-bind": "1.1.0", "function-bind": "1.1.0",
"html-webpack-plugin": "2.28.0", "html-webpack-plugin": "2.30.0",
"http-proxy-middleware": "0.17.3", "http-proxy-middleware": "0.17.4",
"node-sass": "^4.5.0", "node-sass": "^4.5.0",
"opn": "4.0.2", "opn": "4.0.2",
"optimize-css-assets-webpack-plugin": "1.3.0", "optimize-css-assets-webpack-plugin": "3.2.0",
"ora": "1.1.0", "ora": "1.1.0",
"postcss-loader": "^2.0.8",
"pushstate-server": "2.1.0", "pushstate-server": "2.1.0",
"rimraf": "2.6.0", "rimraf": "2.6.0",
"sass-loader": "6.0.5", "sass-loader": "6.0.6",
"script-loader": "0.7.0", "script-loader": "0.7.2",
"semver": "5.3.0", "semver": "5.3.0",
"style-loader": "0.17.0", "style-loader": "0.19.0",
"url-loader": "0.5.8", "svg-sprite-loader": "3.4.1",
"vue-loader": "12.0.4", "url-loader": "0.6.2",
"vue-style-loader": "2.0.5", "vue-loader": "13.5.0",
"vue-template-compiler": "2.3.3", "vue-style-loader": "3.0.3",
"webpack": "2.5.1", "vue-template-compiler": "2.5.9",
"webpack-bundle-analyzer": "2.2.1", "webpack": "3.8.1",
"webpack-dashboard": "0.2.1", "webpack-bundle-analyzer": "2.9.0",
"webpack-dev-middleware": "1.10.0", "webpack-dev-middleware": "1.12.0",
"webpack-hot-middleware": "2.18.0", "webpack-hot-middleware": "2.20.0",
"webpack-merge": "4.1.0" "webpack-merge": "4.1.0"
}, },
"engines": { "engines": {
"node": ">= 4.0.0", "node": ">= 4.0.0",
"npm": ">= 3.0.0" "npm": ">= 3.0.0"
}, },
"browserlist": [ "browserslist": [
"> 1%", "> 1%",
"last 2 versions", "last 2 versions",
"not ie <= 8" "not ie <= 8"

View File

@ -1,11 +1,16 @@
<template> <template>
<div id="app"> <div id="app">
<router-view></router-view> <router-view></router-view>
</div> </div>
</template> </template>
<script> <script>
export default{ export default{
name: 'APP' name: 'APP'
} }
</script> </script>
<style lang="scss">
@import '~normalize.css/normalize.css'; // normalize.css
@import './styles/index.scss'; //
</style>

View File

@ -1,16 +1,40 @@
import fetch from 'utils/fetch'; import request from '@/utils/request'
export function getList() { export function fetchList(query) {
return fetch({ return request({
url: '/article/list', url: '/article/list',
method: 'get' method: 'get',
}); params: query
})
} }
export function getArticle() { export function fetchArticle() {
return fetch({ return request({
url: '/article/detail', url: '/article/detail',
method: 'get' method: 'get'
}); })
} }
export function fetchPv(pv) {
return request({
url: '/article/pv',
method: 'get',
params: { pv }
})
}
export function createArticle(data) {
return request({
url: '/article/create',
method: 'post',
data
})
}
export function updateArticle(data) {
return request({
url: '/article/update',
method: 'post',
data
})
}

View File

@ -1,17 +0,0 @@
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 }
});
}

View File

@ -1,29 +1,29 @@
import fetch from 'utils/fetch'; import request from '@/utils/request'
export function loginByEmail(email, password) { export function loginByUsername(username, password) {
const data = { const data = {
email, username,
password password
}; }
return fetch({ return request({
url: '/login/loginbyemail', url: '/login/login',
method: 'post', method: 'post',
data data
}); })
} }
export function logout() { export function logout() {
return fetch({ return request({
url: '/login/logout', url: '/login/logout',
method: 'post' method: 'post'
}); })
} }
export function getInfo(token) { export function getUserInfo(token) {
return fetch({ return request({
url: '/user/info', url: '/user/info',
method: 'get', method: 'get',
params: { token } params: { token }
}); })
} }

View File

@ -1,8 +1,8 @@
import fetch from 'utils/fetch'; import request from '@/utils/request'
export function getToken() { export function getToken() {
return fetch({ return request({
url: '/qiniu/upload/token', // 假地址 自行替换 url: '/qiniu/upload/token', // 假地址 自行替换
method: 'get' method: 'get'
}); })
} }

View File

@ -1,9 +1,9 @@
import fetch from 'utils/fetch'; import request from '@/utils/request'
export function userSearch(name) { export function userSearch(name) {
return fetch({ return request({
url: '/search/user', url: '/search/user',
method: 'get', method: 'get',
params: { name } params: { name }
}); })
} }

9
src/api/transaction.js Normal file
View File

@ -0,0 +1,9 @@
import request from '@/utils/request'
export function fetchList(query) {
return request({
url: '/transaction/list',
method: 'get',
params: query
})
}

View File

Before

Width:  |  Height:  |  Size: 160 KiB

After

Width:  |  Height:  |  Size: 160 KiB

View File

Before

Width:  |  Height:  |  Size: 96 KiB

After

Width:  |  Height:  |  Size: 96 KiB

View File

Before

Width:  |  Height:  |  Size: 4.7 KiB

After

Width:  |  Height:  |  Size: 4.7 KiB

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,199 @@
/* eslint-disable */
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module.
define(['exports', 'echarts'], factory);
} else if (typeof exports === 'object' && typeof exports.nodeName !== 'string') {
// CommonJS
factory(exports, require('echarts'));
} else {
// Browser globals
factory({}, root.echarts);
}
}(this, function (exports, echarts) {
var log = function (msg) {
if (typeof console !== 'undefined') {
console && console.error && console.error(msg);
}
};
if (!echarts) {
log('ECharts is not Loaded');
return;
}
var colorPalette = [
'#2ec7c9','#b6a2de','#5ab1ef','#ffb980','#d87a80',
'#8d98b3','#e5cf0d','#97b552','#95706d','#dc69aa',
'#07a2a4','#9a7fd1','#588dd5','#f5994e','#c05050',
'#59678c','#c9ab00','#7eb00a','#6f5553','#c14089'
];
var theme = {
color: colorPalette,
title: {
textStyle: {
fontWeight: 'normal',
color: '#008acd'
}
},
visualMap: {
itemWidth: 15,
color: ['#5ab1ef','#e0ffff']
},
toolbox: {
iconStyle: {
normal: {
borderColor: colorPalette[0]
}
}
},
tooltip: {
backgroundColor: 'rgba(50,50,50,0.5)',
axisPointer : {
type : 'line',
lineStyle : {
color: '#008acd'
},
crossStyle: {
color: '#008acd'
},
shadowStyle : {
color: 'rgba(200,200,200,0.2)'
}
}
},
dataZoom: {
dataBackgroundColor: '#efefff',
fillerColor: 'rgba(182,162,222,0.2)',
handleColor: '#008acd'
},
grid: {
borderColor: '#eee'
},
categoryAxis: {
axisLine: {
lineStyle: {
color: '#008acd'
}
},
splitLine: {
lineStyle: {
color: ['#eee']
}
}
},
valueAxis: {
axisLine: {
lineStyle: {
color: '#008acd'
}
},
splitArea : {
show : true,
areaStyle : {
color: ['rgba(250,250,250,0.1)','rgba(200,200,200,0.1)']
}
},
splitLine: {
lineStyle: {
color: ['#eee']
}
}
},
timeline : {
lineStyle : {
color : '#008acd'
},
controlStyle : {
normal : { color : '#008acd'},
emphasis : { color : '#008acd'}
},
symbol : 'emptyCircle',
symbolSize : 3
},
line: {
smooth : true,
symbol: 'emptyCircle',
symbolSize: 3
},
candlestick: {
itemStyle: {
normal: {
color: '#d87a80',
color0: '#2ec7c9',
lineStyle: {
color: '#d87a80',
color0: '#2ec7c9'
}
}
}
},
scatter: {
symbol: 'circle',
symbolSize: 4
},
map: {
label: {
normal: {
textStyle: {
color: '#d87a80'
}
}
},
itemStyle: {
normal: {
borderColor: '#eee',
areaColor: '#ddd'
},
emphasis: {
areaColor: '#fe994e'
}
}
},
graph: {
color: colorPalette
},
gauge : {
axisLine: {
lineStyle: {
color: [[0.2, '#2ec7c9'],[0.8, '#5ab1ef'],[1, '#d87a80']],
width: 10
}
},
axisTick: {
splitNumber: 10,
length :15,
lineStyle: {
color: 'auto'
}
},
splitLine: {
length :22,
lineStyle: {
color: 'auto'
}
},
pointer : {
width : 5
}
}
};
echarts.registerTheme('macarons', theme);
}));

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,110 @@
<template>
<transition :name="transitionName">
<div class="back-to-ceiling" @click="backToTop" v-show="visible" :style="customStyle">
<svg width="16" height="16" viewBox="0 0 17 17" xmlns="http://www.w3.org/2000/svg" class="Icon Icon--backToTopArrow" aria-hidden="true" style="height: 16px; width: 16px;">
<title>回到顶部</title>
<g>
<path d="M12.036 15.59c0 .55-.453.995-.997.995H5.032c-.55 0-.997-.445-.997-.996V8.584H1.03c-1.1 0-1.36-.633-.578-1.416L7.33.29c.39-.39 1.026-.385 1.412 0l6.878 6.88c.782.78.523 1.415-.58 1.415h-3.004v7.004z" fill-rule="evenodd"></path>
</g>
</svg>
</div>
</transition>
</template>
<script>
export default {
name: 'BackToTop',
props: {
visibilityHeight: {
type: Number,
default: 400
},
backPosition: {
type: Number,
default: 0
},
customStyle: {
type: Object,
default: {
right: '50px',
bottom: '50px',
width: '40px',
height: '40px',
'border-radius': '4px',
'line-height': '45px',
background: '#e7eaf1'
}
},
transitionName: {
type: String,
default: 'fade'
}
},
data() {
return {
visible: false,
interval: null
}
},
mounted() {
window.addEventListener('scroll', this.handleScroll)
},
beforeDestroy() {
window.removeEventListener('scroll', this.handleScroll)
if (this.interval) {
clearInterval(this.interval)
}
},
methods: {
handleScroll() {
this.visible = window.pageYOffset > this.visibilityHeight
},
backToTop() {
const start = window.pageYOffset
let i = 0
this.interval = setInterval(() => {
const next = Math.floor(this.easeInOutQuad(10 * i, start, -start, 500))
if (next <= this.backPosition) {
window.scrollTo(0, this.backPosition)
clearInterval(this.interval)
} else {
window.scrollTo(0, next)
}
i++
}, 16.7)
},
easeInOutQuad(t, b, c, d) {
if ((t /= d / 2) < 1) return c / 2 * t * t + b
return -c / 2 * (--t * (t - 2) - 1) + b
}
}
}
</script>
<style scoped>
.back-to-ceiling {
position: fixed;
display: inline-block;
text-align: center;
cursor: pointer;
}
.back-to-ceiling:hover {
background: #d5dbe7;
}
.fade-enter-active,
.fade-leave-active {
transition: opacity .5s;
}
.fade-enter,
.fade-leave-to {
opacity: 0
}
.back-to-ceiling .Icon {
fill: #9aaabf;
background: none;
}
</style>

View File

@ -0,0 +1,54 @@
<template>
<el-breadcrumb class="app-breadcrumb" separator="/">
<transition-group name="breadcrumb">
<el-breadcrumb-item v-for="(item,index) in levelList" :key="item.path" v-if='item.meta.title'>
<span v-if='item.redirect==="noredirect"||index==levelList.length-1' class="no-redirect">{{generateTitle(item.meta.title)}}</span>
<router-link v-else :to="item.redirect||item.path">{{generateTitle(item.meta.title)}}</router-link>
</el-breadcrumb-item>
</transition-group>
</el-breadcrumb>
</template>
<script>
export default {
created() {
this.getBreadcrumb()
},
data() {
return {
levelList: null
}
},
watch: {
$route() {
this.getBreadcrumb()
}
},
methods: {
getBreadcrumb() {
let matched = this.$route.matched.filter(item => item.name)
const first = matched[0]
if (first && first.name !== 'dashboard') {
matched = [{ path: '/dashboard', meta: { title: 'dashboard' }}].concat(matched)
}
this.levelList = matched
},
generateTitle(title) {
return this.$t('route.' + title)
}
}
}
</script>
<style rel="stylesheet/scss" lang="scss" scoped>
.app-breadcrumb.el-breadcrumb {
display: inline-block;
font-size: 14px;
line-height: 50px;
margin-left: 10px;
.no-redirect {
color: #97a8be;
cursor: text;
}
}
</style>

View File

@ -1,104 +0,0 @@
<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>

View File

@ -1,112 +1,150 @@
<template> <template>
<div :class="className" :id="id" :style="{height:height,width:width}"></div> <div :class="className" :id="id" :style="{height:height,width:width}"></div>
</template> </template>
<script> <script>
// ECharts import echarts from '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 {
export default { props: {
name: 'barPercent', className: {
props: { type: String,
className: { default: 'chart'
type: String, },
default: 'chart' id: {
}, type: String,
id: { default: 'chart'
type: String, },
default: 'chart' width: {
}, type: String,
width: { default: '200px'
type: String, },
default: '200px' height: {
}, type: String,
height: { default: '200px'
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;
}
}]
})
}
}
} }
},
data() {
return {
chart: null
}
},
mounted() {
this.initChart()
},
beforeDestroy() {
if (!this.chart) {
return
}
this.chart.dispose()
this.chart = null
},
methods: {
initChart() {
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> </script>

View File

@ -1,149 +0,0 @@
<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>

View File

@ -1,146 +0,0 @@
<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>

View File

@ -1,219 +1,222 @@
<template> <template>
<div :class="className" :id="id" :style="{height:height,width:width}"></div> <div :class="className" :id="id" :style="{height:height,width:width}"></div>
</template> </template>
<script> <script>
// ECharts import echarts from 'echarts'
const echarts = require('echarts/lib/echarts');
require('echarts/lib/chart/line'); export default {
// props: {
require('echarts/lib/component/tooltip'); className: {
require('echarts/lib/component/title'); type: String,
require('echarts/lib/component/legend'); default: 'chart'
export default { },
name: 'barPercent', id: {
props: { type: String,
className: { default: 'chart'
type: String, },
default: 'chart' width: {
}, type: String,
id: { default: '200px'
type: String, },
default: 'chart' height: {
}, type: String,
width: { default: '200px'
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]
}]
})
}
}
} }
},
data() {
return {
chart: null
}
},
mounted() {
this.initChart()
},
beforeDestroy() {
if (!this.chart) {
return
}
this.chart.dispose()
this.chart = null
},
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> </script>

View File

@ -0,0 +1,267 @@
<template>
<div :class="className" :id="id" :style="{height:height,width:width}"></div>
</template>
<script>
import echarts from 'echarts'
export default {
props: {
className: {
type: String,
default: 'chart'
},
id: {
type: String,
default: 'chart'
},
width: {
type: String,
default: '200px'
},
height: {
type: String,
default: '200px'
}
},
data() {
return {
chart: null
}
},
mounted() {
this.initChart()
this.chart = null
},
beforeDestroy() {
if (!this.chart) {
return
}
this.chart.dispose()
this.chart = null
},
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: '统计',
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>

View File

@ -1,265 +0,0 @@
<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: '统计',
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>

View File

@ -0,0 +1,158 @@
<template>
<div class="dndList">
<div class="dndList-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" :key='element.id'>
<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="dndList-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" :key='element.id'>
<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: 'DndList',
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>
.dndList {
background: #fff;
padding-bottom: 40px;
&:after {
content: "";
display: table;
clear: both;
}
.dndList-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>

View File

@ -1,206 +1,205 @@
<template> <template>
<div :ref="id" :action="url" class="dropzone" :id="id"> <div :ref="id" :action="url" class="dropzone" :id="id">
<input type="file" name="file"> <input type="file" name="file">
</div> </div>
</template> </template>
<script> <script>
import Dropzone from 'dropzone'; import Dropzone from 'dropzone'
import 'dropzone/dist/dropzone.css'; import 'dropzone/dist/dropzone.css'
// import { getToken } from 'api/qiniu'; // import { getToken } from 'api/qiniu';
Dropzone.autoDiscover = false; Dropzone.autoDiscover = false
export default { export default {
data() { data() {
return { return {
dropzone: '', dropzone: '',
initOnce: true 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
} }
}, },
mounted() { accept: (file, done) => {
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; // const token = this.$store.getters.token;
// getToken(token).then(response => { // getToken(token).then(response => {
// file.token = response.data.qiniu_token; // file.token = response.data.qiniu_token;
// file.key = response.data.qiniu_key; // file.key = response.data.qiniu_key;
// file.url = response.data.qiniu_url; // file.url = response.data.qiniu_url;
// done(); // done();
// }) // })
done(); done()
}, },
sending: (file, xhr, formData) => { sending: (file, xhr, formData) => {
/* 七牛*/ // formData.append('token', file.token);
console.log(file, xhr, formData) // formData.append('key', file.key);
// formData.append('token', file.token); vm.initOnce = false
// formData.append('key', file.key); }
vm.initOnce = false; })
}
});
if (this.couldPaste) { if (this.couldPaste) {
document.addEventListener('paste', this.pasteImg) document.addEventListener('paste', this.pasteImg)
} }
this.dropzone.on('success', file => { this.dropzone.on('success', file => {
vm.$emit('dropzone-success', file, vm.dropzone.element) vm.$emit('dropzone-success', file, vm.dropzone.element)
}); })
this.dropzone.on('addedfile', file => { this.dropzone.on('addedfile', file => {
vm.$emit('dropzone-fileAdded', file) vm.$emit('dropzone-fileAdded', file)
}); })
this.dropzone.on('removedfile', file => { this.dropzone.on('removedfile', file => {
vm.$emit('dropzone-removedFile', file) vm.$emit('dropzone-removedFile', file)
}); })
this.dropzone.on('error', (file, error, xhr) => { this.dropzone.on('error', (file, error, xhr) => {
vm.$emit('dropzone-error', file, error, xhr) vm.$emit('dropzone-error', file, error, xhr)
}); })
this.dropzone.on('successmultiple', (file, error, xhr) => { this.dropzone.on('successmultiple', (file, error, xhr) => {
vm.$emit('dropzone-successmultiple', file, error, xhr) vm.$emit('dropzone-successmultiple', file, error, xhr)
}); })
}, },
methods: { methods: {
removeAllFiles() { removeAllFiles() {
this.dropzone.removeAllFiles(true) this.dropzone.removeAllFiles(true)
}, },
processQueue() { processQueue() {
this.dropzone.processQueue() this.dropzone.processQueue()
}, },
pasteImg(event) { pasteImg(event) {
const items = (event.clipboardData || event.originalEvent.clipboardData).items; const items = (event.clipboardData || event.originalEvent.clipboardData).items
if (items[0].kind === 'file') { if (items[0].kind === 'file') {
this.dropzone.addFile(items[0].getAsFile()) this.dropzone.addFile(items[0].getAsFile())
} }
}, },
initImages(val) { initImages(val) {
if (!val) return; if (!val) return
if (Array.isArray(val)) { if (Array.isArray(val)) {
val.map((v, i) => { val.map((v, i) => {
const mockFile = { name: 'name' + i, size: 12345, url: v }; const mockFile = { name: 'name' + i, size: 12345, url: v }
this.dropzone.options.addedfile.call(this.dropzone, mockFile); this.dropzone.options.addedfile.call(this.dropzone, mockFile)
this.dropzone.options.thumbnail.call(this.dropzone, mockFile, v); this.dropzone.options.thumbnail.call(this.dropzone, mockFile, v)
mockFile.previewElement.classList.add('dz-success'); mockFile.previewElement.classList.add('dz-success')
mockFile.previewElement.classList.add('dz-complete'); mockFile.previewElement.classList.add('dz-complete')
return true return true
}) })
} else { } else {
const mockFile = { name: 'name', size: 12345, url: val }; const mockFile = { name: 'name', size: 12345, url: val }
this.dropzone.options.addedfile.call(this.dropzone, mockFile); this.dropzone.options.addedfile.call(this.dropzone, mockFile)
this.dropzone.options.thumbnail.call(this.dropzone, mockFile, val); this.dropzone.options.thumbnail.call(this.dropzone, mockFile, val)
mockFile.previewElement.classList.add('dz-success'); mockFile.previewElement.classList.add('dz-success')
mockFile.previewElement.classList.add('dz-complete'); 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
}
} }
} }
},
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> </script>
<style scoped> <style scoped>

View File

@ -1,51 +1,56 @@
<template> <template>
<div> <div>
<el-badge :is-dot="true" style="line-height: 30px;" @click.native="dialogTableVisible=true"> <el-badge :is-dot="true" style="line-height: 30px;" @click.native="dialogTableVisible=true">
<el-button size="small" type="primary"> <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> <svg t="1492682037685" class="bug-svg" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1863"
</el-button> xmlns:xlink="http://www.w3.org/1999/xlink" width="64" height="64">
</el-badge> <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"
<el-dialog title="bug日志" :visible.sync="dialogTableVisible"> p-id="1864"></path>
<el-table :data="logsList"> </svg>
<el-table-column label="message"> </el-button>
<template scope="scope"> </el-badge>
<div>msg:{{ scope.row.err.message }}</div> <el-dialog title="bug日志" :visible.sync="dialogTableVisible">
<br/> <el-table :data="logsList">
<div>url: {{scope.row.url}}</div> <el-table-column label="message">
</template> <template slot-scope="scope">
</el-table-column> <div>msg:{{ scope.row.err.message }}</div>
<el-table-column label="stack"> <br/>
<template scope="scope"> <div>url: {{scope.row.url}}</div>
{{ scope.row.err.stack}} </template>
</template> </el-table-column>
</el-table-column> <el-table-column label="stack">
<template slot-scope="scope">
{{ scope.row.err.stack}}
</template>
</el-table-column>
</el-table> </el-table>
</el-dialog> </el-dialog>
</div> </div>
</template> </template>
<script> <script>
export default { export default {
name: 'errLog', name: 'errLog',
props: { props: {
logsList: { logsList: {
type: Array type: Array
}
},
data() {
return {
dialogTableVisible: false
}
}
} }
},
data() {
return {
dialogTableVisible: false
}
}
}
</script> </script>
<style scoped> <style scoped>
.bug-svg { .bug-svg {
width: 1em; width: 1em;
height: 1em; height: 1em;
vertical-align: -0.15em; vertical-align: -0.15em;
fill: currentColor; fill: currentColor;
overflow: hidden; overflow: hidden;
} }
</style> </style>

View File

@ -0,0 +1,42 @@
<template>
<a href="https://github.com/PanJiaChen/vue-element-admin" target="_blank" class="github-corner" aria-label="View source on Github">
<svg width="80" height="80" viewBox="0 0 250 250" style="fill:#40c9c6; color:#fff; position: absolute; top: 84px; border: 0; right: 0;"
aria-hidden="true">
<path d="M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z"></path>
<path d="M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2"
fill="currentColor" style="transform-origin: 130px 106px;" class="octo-arm"></path>
<path d="M115.0,115.0 C114.9,115.1 118.7,116.5 119.8,115.4 L133.7,101.6 C136.9,99.2 139.9,98.4 142.2,98.6 C133.8,88.0 127.5,74.4 143.8,58.0 C148.5,53.4 154.0,51.2 159.7,51.0 C160.3,49.4 163.2,43.6 171.4,40.1 C171.4,40.1 176.1,42.5 178.8,56.2 C183.1,58.6 187.2,61.8 190.9,65.4 C194.5,69.0 197.7,73.2 200.1,77.6 C213.8,80.2 216.3,84.9 216.3,84.9 C212.7,93.1 206.9,96.0 205.4,96.6 C205.1,102.4 203.0,107.8 198.3,112.5 C181.9,128.9 168.3,122.5 157.7,114.1 C157.9,116.9 156.7,120.9 152.7,124.9 L141.0,136.5 C139.8,137.7 141.6,141.9 141.8,141.8 Z"
fill="currentColor" class="octo-body"></path>
</svg>
</a>
</template>
<style scoped>
.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
}
}
</style>

View File

@ -1,36 +1,45 @@
<template> <template>
<div> <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> <svg t="1492500959545" @click="toggleClick" class="wscn-icon hamburger" :class="{'is-active':isActive}" style="" viewBox="0 0 1024 1024"
</div> 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> </template>
<script> <script>
export default { export default {
name: 'hamburger', name: 'hamburger',
props: { props: {
isActive: { isActive: {
type: Boolean, type: Boolean,
default: false default: false
}, },
toggleClick: { toggleClick: {
type: Function, type: Function,
default: null default: null
}
}
} }
}
}
</script> </script>
<style scoped> <style scoped>
.hamburger { .hamburger {
display: inline-block; display: inline-block;
cursor: pointer; cursor: pointer;
width: 20px; width: 20px;
height: 20px; height: 20px;
transform: rotate(0deg); transform: rotate(90deg);
transition: .38s; transition: .38s;
transform-origin: 50% 50%; transform-origin: 50% 50%;
} }
.hamburger.is-active {
transform: rotate(90deg); .hamburger.is-active {
} transform: rotate(0deg);
}
</style> </style>

View File

@ -1,11 +0,0 @@
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$/))

View File

@ -1,52 +0,0 @@
<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>

View File

@ -1,26 +0,0 @@
<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>

View File

@ -105,7 +105,7 @@
<script> <script>
/* eslint-disable */ /* eslint-disable */
import {effectRipple, data2blob} from './utils'; import {effectRipple, data2blob} from './utils';
import fetch from 'utils/fetch'; import request from 'utils/request';
import langBag from './lang'; import langBag from './lang';
const mimes = { const mimes = {
'jpg': 'image/jpeg', 'jpg': 'image/jpeg',
@ -672,7 +672,7 @@
that.loading = 1; that.loading = 1;
that.setStep(3); that.setStep(3);
that.$emit('crop-success', createImgUrl, field, ki); that.$emit('crop-success', createImgUrl, field, ki);
fetch({ request({
url, url,
method: 'post', method: 'post',
data: fmData data: fmData

View File

@ -37,5 +37,5 @@ const langBag = {
lowestPx: 'The lowest pixel in the image: ' lowestPx: 'The lowest pixel in the image: '
} }
} }
}; }
export default langBag; export default langBag

View File

@ -0,0 +1,63 @@
<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>

View File

@ -1,297 +1,278 @@
<template> <template>
<div class="material-input__component" :class="computedClasses"> <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" <div :class="{iconClass:icon}">
:readonly="readonly" :disabled="disabled" :autocomplete="autocomplete" :required="required" @focus="handleFocus(true)" <i class="el-input__icon material-input__icon" :class="['el-icon-' + icon]" v-if="icon"></i>
@blur="handleFocus(false)" @input="handleModelInput"> <input v-if="type === 'email'" type="email" class="material-input" :name="name" :placeholder="fillPlaceHolder" v-model="currentValue"
<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="handleMdFocus"
:readonly="readonly" :disabled="disabled" :autocomplete="autocomplete" :required="required" @focus="handleFocus(true)" @blur="handleMdBlur" @input="handleModelInput">
@blur="handleFocus(false)" @input="handleModelInput"> <input v-if="type === 'url'" type="url" class="material-input" :name="name" :placeholder="fillPlaceHolder" v-model="currentValue"
<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" :required="required" @focus="handleMdFocus"
:readonly="readonly" :disabled="disabled" :autocomplete="autocomplete" :max="max" :min="min" :minlength="minlength" @blur="handleMdBlur" @input="handleModelInput">
:maxlength="maxlength" :required="required" @focus="handleFocus(true)" @blur="handleFocus(false)" @input="handleModelInput"> <input v-if="type === 'number'" type="number" class="material-input" :name="name" :placeholder="fillPlaceHolder" v-model="currentValue"
<input v-if="type === 'password'" type="password" class="material-input" :name="name" :id="id" :placeholder="placeholder" :step="step" :readonly="readonly" :disabled="disabled" :autoComplete="autoComplete" :max="max" :min="min" :minlength="minlength"
v-model="valueCopy" :readonly="readonly" :disabled="disabled" :autocomplete="autocomplete" :max="max" :min="min" :maxlength="maxlength" :required="required" @focus="handleMdFocus" @blur="handleMdBlur" @input="handleModelInput">
:required="required" @focus="handleFocus(true)" @blur="handleFocus(false)" @input="handleModelInput"> <input v-if="type === 'password'" type="password" class="material-input" :name="name" :placeholder="fillPlaceHolder" v-model="currentValue"
<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" :max="max" :min="min" :required="required" @focus="handleMdFocus"
:readonly="readonly" :disabled="disabled" :autocomplete="autocomplete" :required="required" @focus="handleFocus(true)" @blur="handleMdBlur" @input="handleModelInput">
@blur="handleFocus(false)" @input="handleModelInput"> <input v-if="type === 'tel'" type="tel" class="material-input" :name="name" :placeholder="fillPlaceHolder" v-model="currentValue"
<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" :required="required" @focus="handleMdFocus"
:readonly="readonly" :disabled="disabled" :autocomplete="autocomplete" :minlength="minlength" :maxlength="maxlength" @blur="handleMdBlur" @input="handleModelInput">
:required="required" @focus="handleFocus(true)" @blur="handleFocus(false)" @input="handleModelInput"> <input v-if="type === 'text'" type="text" class="material-input" :name="name" :placeholder="fillPlaceHolder" v-model="currentValue"
:readonly="readonly" :disabled="disabled" :autoComplete="autoComplete" :minlength="minlength" :maxlength="maxlength"
<span class="material-input-bar"></span> :required="required" @focus="handleMdFocus" @blur="handleMdBlur" @input="handleModelInput">
<span class="material-input-bar"></span>
<label class="material-label"> <label class="material-label">
<slot></slot> <slot></slot>
</label> </label>
<div v-if="errorMessages" class="material-errors">
<div v-for="error in computedErrors" class="material-error">
{{ error }}
</div>
</div>
</div> </div>
</div>
</template> </template>
<script> <script>
// source:https://github.com/wemake-services/vue-material-input/blob/master/src/components/MaterialInput.vue // source:https://github.com/wemake-services/vue-material-input/blob/master/src/components/MaterialInput.vue
export default {
name: 'material-input', export default {
computed: { name: 'md-input',
computedErrors() { props: {
return typeof this.errorMessages === 'string' icon: String,
? [this.errorMessages] : this.errorMessages name: String,
}, type: {
computedClasses() { type: String,
return { default: 'text'
'material--active': this.focus, },
'material--disabled': this.disabled, value: [String, Number],
'material--has-errors': Boolean(!this.valid || (this.errorMessages && this.errorMessages.length)), placeholder: String,
'material--raised': Boolean(this.focus || this.valueCopy || // has value readonly: Boolean,
(this.placeholder && !this.valueCopy)) // has placeholder disabled: Boolean,
} min: String,
max: String,
step: String,
minlength: Number,
maxlength: Number,
required: {
type: Boolean,
default: true
},
autoComplete: {
type: String,
default: 'off'
},
validateEvent: {
type: Boolean,
default: true
}
},
computed: {
computedClasses() {
return {
'material--active': this.focus,
'material--disabled': this.disabled,
'material--raised': Boolean(this.focus || this.currentValue) // has value
}
}
},
watch: {
value(newValue) {
this.currentValue = newValue
}
},
data() {
return {
currentValue: this.value,
focus: false,
fillPlaceHolder: null
}
},
methods: {
handleModelInput(event) {
const value = event.target.value
this.$emit('input', value)
if (this.$parent.$options.componentName === 'ElFormItem') {
if (this.validateEvent) {
this.$parent.$emit('el.form.change', [value])
} }
}, }
data() { this.$emit('change', value)
return { },
valueCopy: null, handleMdFocus(event) {
focus: false, this.focus = true
valid: true this.$emit('focus', event)
} if (this.placeholder && this.placeholder !== '') {
}, this.fillPlaceHolder = this.placeholder
beforeMount() { }
// Here we are following the Vue2 convention on custom v-model: },
// https://github.com/vuejs/vue/issues/2873#issuecomment-223759341 handleMdBlur(event) {
this.copyValue(this.value) this.focus = false
}, this.$emit('blur', event)
methods: { this.fillPlaceHolder = null
handleModelInput(event) { if (this.$parent.$options.componentName === 'ElFormItem') {
this.$emit('input', event.target.value, event) if (this.validateEvent) {
this.handleValidation() this.$parent.$emit('el.form.blur', [this.currentValue])
},
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> </script>
<style rel="stylesheet/scss" lang="scss" scoped> <style rel="stylesheet/scss" lang="scss" scoped>
// Fonts: // Fonts:
$font-size-base: 16px; $font-size-base: 16px;
$font-size-small: 18px; $font-size-small: 18px;
$font-size-smallest: 12px; $font-size-smallest: 12px;
$font-weight-normal: normal; $font-weight-normal: normal;
// Utils $font-weight-bold: bold;
$spacer: 12px; $apixel: 1px;
$transition: 0.2s ease all; // Utils
// Base clases: $spacer: 12px;
%base-bar-pseudo { $transition: 0.2s ease all;
content: ''; $index: 0px;
height: 1px; $index-has-icon: 30px;
width: 0; // Theme:
bottom: 0; $color-white: white;
$color-grey: #9E9E9E;
$color-grey-light: #E0E0E0;
$color-blue: #2196F3;
$color-red: #F44336;
$color-black: black;
// Base clases:
%base-bar-pseudo {
content: '';
height: 1px;
width: 0;
bottom: 0;
position: absolute;
transition: $transition;
}
// Mixins:
@mixin slided-top() {
top: - ($font-size-base + $spacer);
left: 0;
font-size: $font-size-base;
font-weight: $font-weight-bold;
}
// Component:
.material-input__component {
margin-top: 36px;
position: relative;
* {
box-sizing: border-box;
}
.iconClass {
.material-input__icon {
position: absolute; position: absolute;
transition: $transition; left: 0;
line-height: $font-size-base;
color: $color-blue;
top: $spacer;
width: $index-has-icon;
height: $font-size-base;
font-size: $font-size-base;
font-weight: $font-weight-normal;
pointer-events: none;
}
.material-label {
left: $index-has-icon;
}
.material-input {
text-indent: $index-has-icon;
}
} }
.material-input {
font-size: $font-size-base;
padding: $spacer $spacer $spacer - $apixel * 10 $spacer / 2;
display: block;
width: 100%;
border: none;
line-height: 1;
border-radius: 0;
&:focus {
outline: none;
border: none;
border-bottom: 1px solid transparent; // fixes the height issue
}
}
.material-label {
font-weight: $font-weight-normal;
position: absolute;
pointer-events: none;
left: $index;
top: 0;
transition: $transition;
font-size: $font-size-small;
}
.material-input-bar {
position: relative;
display: block;
width: 100%;
&:before {
@extend %base-bar-pseudo;
left: 50%;
}
&:after {
@extend %base-bar-pseudo;
right: 50%;
}
}
// Disabled state:
&.material--disabled {
.material-input {
border-bottom-style: dashed;
}
}
// Raised state:
&.material--raised {
.material-label {
@include slided-top();
}
}
// Active state:
&.material--active {
.material-input-bar {
&:before,
&:after {
width: 50%;
}
}
}
}
// Mixins: .material-input__component {
@mixin slided-top() { background: $color-white;
top: -2 * $spacer; .material-input {
font-size: $font-size-small; background: none;
color: $color-black;
text-indent: $index;
border-bottom: 1px solid $color-grey-light;
} }
.material-label {
// Component: color: $color-grey;
.material-input__component {
/*margin-top: 30px;*/
position: relative;
* {
box-sizing: border-box;
}
.material-input {
font-size: $font-size-base;
padding: $spacer $spacer $spacer $spacer / 2;
display: block;
width: 100%;
border: none;
border-radius: 0;
&:focus {
outline: none;
border: none;
border-bottom: 1px solid transparent; // fixes the height issue
}
}
.material-label {
font-size: $font-size-base;
font-weight: $font-weight-normal;
position: absolute;
pointer-events: none;
left: 0;
top: $spacer;
transition: $transition;
}
.material-input-bar {
position: relative;
display: block;
width: 100%;
&:before {
@extend %base-bar-pseudo;
left: 50%;
}
&:after {
@extend %base-bar-pseudo;
right: 50%;
}
}
// Disabled state:
&.material--disabled {
.material-input {
border-bottom-style: dashed;
}
}
// Raised state:
&.material--raised {
.material-label {
@include slided-top();
}
}
// Active state:
&.material--active {
.material-input-bar {
&:before,
&:after {
width: 50%;
}
}
}
// Errors:
.material-errors {
position: relative;
overflow: hidden;
.material-error {
font-size: $font-size-smallest;
line-height: $font-size-smallest + 2px;
overflow: hidden;
margin-top: 0;
padding-top: $spacer / 2;
padding-right: $spacer / 2;
padding-left: 0;
}
}
} }
.material-input-bar {
// Theme: &:before,
$color-white: white; &:after {
$color-grey: #9E9E9E; background: $color-blue;
$color-grey-light: #E0E0E0; }
$color-blue: #2196F3;
$color-red: #F44336;
$color-black: black;
.material-input__component {
background: $color-white;
.material-input {
background: none;
color: $color-black;
text-indent: 30px;
border-bottom: 1px solid $color-grey-light;
}
.material-label {
color: $color-grey;
}
.material-input-bar {
&:before,
&:after {
background: $color-blue;
}
}
// Active state:
&.material--active {
.material-label {
color: $color-blue;
}
}
// Errors:
&.material--has-errors {
&.material--active .material-label {
color: $color-red;
}
.material-input-bar {
&:before,
&:after {
background: $color-red;
}
}
.material-errors {
color: $color-red;
}
}
} }
// Active state:
&.material--active {
.material-label {
color: $color-blue;
}
}
// Errors:
&.material--has-errors {
&.material--active .material-label {
color: $color-red;
}
.material-input-bar {
&:before,
&:after {
background: transparent;
}
}
}
}
</style> </style>

View File

@ -1,20 +1,21 @@
<template> <template>
<div class='simplemde-container' :style="{height:height+'px',zIndex:zIndex}"> <div class="simplemde-container" :style="{height:height+'px',zIndex:zIndex}">
<textarea :id='id'> <textarea :id="id">
</textarea> </textarea>
</div> </div>
</template> </template>
<script> <script>
import 'font-awesome/css/font-awesome.min.css'
import 'simplemde/dist/simplemde.min.css' import 'simplemde/dist/simplemde.min.css'
import SimpleMDE from 'simplemde' import SimpleMDE from 'simplemde'
export default { export default {
name: 'Sticky', name: 'simplemde-md',
props: { props: {
value: String, value: String,
id: { id: {
type: String, type: String
default: 'markdown-editor'
}, },
autofocus: { autofocus: {
type: Boolean, type: Boolean,
@ -40,17 +41,18 @@ export default {
return { return {
simplemde: null, simplemde: null,
hasChange: false hasChange: false
}; }
}, },
watch: { watch: {
value(val) { value(val) {
if (val === this.simplemde.value() && !this.hasChange) return; if (val === this.simplemde.value() && !this.hasChange) return
this.simplemde.value(val); this.simplemde.value(val)
} }
}, },
mounted() { mounted() {
this.simplemde = new SimpleMDE({ this.simplemde = new SimpleMDE({
element: document.getElementById(this.id), element: document.getElementById(this.id || 'markdown-editor-' + +new Date()),
autoDownloadFontAwesome: false,
autofocus: this.autofocus, autofocus: this.autofocus,
toolbar: this.toolbar, toolbar: this.toolbar,
spellChecker: false, spellChecker: false,
@ -59,53 +61,59 @@ export default {
}, },
// hideIcons: ['guide', 'heading', 'quote', 'image', 'preview', 'side-by-side', 'fullscreen'], // hideIcons: ['guide', 'heading', 'quote', 'image', 'preview', 'side-by-side', 'fullscreen'],
placeholder: this.placeholder placeholder: this.placeholder
}); })
if (this.value) { if (this.value) {
this.simplemde.value(this.value); this.simplemde.value(this.value)
} }
this.simplemde.codemirror.on('change', () => { this.simplemde.codemirror.on('change', () => {
if (this.hasChange) { if (this.hasChange) {
this.hasChange = true this.hasChange = true
} }
this.$emit('input', this.simplemde.value()); this.$emit('input', this.simplemde.value())
}); })
}, },
destroyed() { destroyed() {
this.simplemde = null; this.simplemde = null
} }
}; }
</script> </script>
<style> <style>
.simplemde-container .CodeMirror { .simplemde-container .CodeMirror {
/*height: 150px;*/ min-height: 150px;
min-height: 150px; }
}
.simplemde-container .CodeMirror-scroll{
min-height: 150px;
}
.simplemde-container .CodeMirror-code{ .simplemde-container .CodeMirror-scroll {
padding-bottom: 40px; min-height: 150px;
} }
.simplemde-container .editor-statusbar {
display: none;
}
.simplemde-container .CodeMirror .CodeMirror-code .cm-link { .simplemde-container .CodeMirror-code {
color: #1482F0; padding-bottom: 40px;
} }
.simplemde-container .CodeMirror .CodeMirror-code .cm-string.cm-url { .simplemde-container .editor-statusbar {
color: #2d3b4d; display: none;
font-weight: bold; }
}
.simplemde-container .CodeMirror .CodeMirror-code .cm-formatting-link-string.cm-url { .simplemde-container .CodeMirror .CodeMirror-code .cm-link {
padding: 0 2px; color: #1482F0;
font-weight: bold; }
color: #E61E1E;
} .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;
}
.simplemde-container .editor-toolbar.fullscreen,
.simplemde-container .CodeMirror-fullscreen {
z-index: 1003;
}
</style> </style>

View File

@ -1,145 +1,140 @@
<template> <template>
<div class="pan-item" :style="{zIndex:zIndex,height:height,width:width}"> <div class="pan-item" :style="{zIndex:zIndex,height:height,width:width}">
<div class="pan-info"> <div class="pan-info">
<div class="pan-info-roles-container"> <div class="pan-info-roles-container">
<slot></slot> <slot></slot>
</div> </div>
</div> </div>
<img class="pan-thumb" :src="image"> <img class="pan-thumb" :src="image">
</div> </div>
</template> </template>
<script> <script>
export default { export default {
name: 'PanThumb', name: 'PanThumb',
props: { props: {
image: { image: {
type: String, type: String,
required: true required: true
}, },
zIndex: { zIndex: {
type: Number, type: Number,
default: 100 default: 100
}, },
width: { width: {
type: String, type: String,
default: '150px' default: '150px'
}, },
height: { height: {
type: String, type: String,
default: '150px' default: '150px'
} }
}, }
data() { }
return {};
}
};
</script> </script>
<style scoped> <style scoped>
.pan-item { .pan-item {
width: 200px; width: 200px;
height: 200px; height: 200px;
border-radius: 50%; border-radius: 50%;
display: inline-block; display: inline-block;
position: relative; position: relative;
cursor: default; cursor: default;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2); box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
} }
.pan-info-roles-container { .pan-info-roles-container {
padding: 20px; padding: 20px;
text-align: center; text-align: center;
} }
.pan-thumb { .pan-thumb {
width: 100%; width: 100%;
height: 100%; height: 100%;
background-size: 100%; background-size: 100%;
border-radius: 50%; border-radius: 50%;
overflow: hidden; overflow: hidden;
position: absolute; position: absolute;
transform-origin: 95% 40%; transform-origin: 95% 40%;
transition: all 0.3s ease-in-out; transition: all 0.3s ease-in-out;
} }
.pan-thumb:after { .pan-thumb:after {
content: ''; content: '';
width: 8px; width: 8px;
height: 8px; height: 8px;
position: absolute; position: absolute;
border-radius: 50%; border-radius: 50%;
top: 40%; top: 40%;
left: 95%; left: 95%;
margin: -4px 0 0 -4px; margin: -4px 0 0 -4px;
background: radial-gradient(ellipse at center, rgba(14, 14, 14, 1) 0%, rgba(125, 126, 125, 1) 100%); 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); box-shadow: 0 0 1px rgba(255, 255, 255, 0.9);
} }
.pan-info { .pan-info {
position: absolute; position: absolute;
width: inherit; width: inherit;
height: inherit; height: inherit;
border-radius: 50%; border-radius: 50%;
overflow: hidden; overflow: hidden;
box-shadow: inset 0 0 0 5px rgba(0, 0, 0, 0.05); box-shadow: inset 0 0 0 5px rgba(0, 0, 0, 0.05);
} }
.pan-info h3 { .pan-info h3 {
color: #fff; color: #fff;
text-transform: uppercase; text-transform: uppercase;
position: relative; position: relative;
letter-spacing: 2px; letter-spacing: 2px;
font-size: 18px; font-size: 18px;
margin: 0 60px; margin: 0 60px;
padding: 22px 0 0 0; padding: 22px 0 0 0;
height: 85px; height: 85px;
font-family: 'Open Sans', Arial, sans-serif; font-family: 'Open Sans', Arial, sans-serif;
text-shadow: 0 0 1px #fff, text-shadow: 0 0 1px #fff, 0 1px 2px rgba(0, 0, 0, 0.3);
0 1px 2px rgba(0, 0, 0, 0.3); }
}
.pan-info p { .pan-info p {
color: #fff; color: #fff;
padding: 10px 5px; padding: 10px 5px;
font-style: italic; font-style: italic;
margin: 0 30px; margin: 0 30px;
font-size: 12px; font-size: 12px;
border-top: 1px solid rgba(255, 255, 255, 0.5); border-top: 1px solid rgba(255, 255, 255, 0.5);
} }
.pan-info p a { .pan-info p a {
display: block; display: block;
color: #333; color: #333;
width: 80px; width: 80px;
height: 80px; height: 80px;
background: rgba(255, 255, 255, 0.3); background: rgba(255, 255, 255, 0.3);
border-radius: 50%; border-radius: 50%;
color: #fff; color: #fff;
font-style: normal; font-style: normal;
font-weight: 700; font-weight: 700;
text-transform: uppercase; text-transform: uppercase;
font-size: 9px; font-size: 9px;
letter-spacing: 1px; letter-spacing: 1px;
padding-top: 24px; padding-top: 24px;
margin: 7px auto 0; margin: 7px auto 0;
font-family: 'Open Sans', Arial, sans-serif; font-family: 'Open Sans', Arial, sans-serif;
opacity: 0; opacity: 0;
transition: transform 0.3s ease-in-out 0.2s, transition: transform 0.3s ease-in-out 0.2s, opacity 0.3s ease-in-out 0.2s, background 0.2s linear 0s;
opacity 0.3s ease-in-out 0.2s, transform: translateX(60px) rotate(90deg);
background 0.2s linear 0s; }
transform: translateX(60px) rotate(90deg);
}
.pan-info p a:hover { .pan-info p a:hover {
background: rgba(255, 255, 255, 0.5); background: rgba(255, 255, 255, 0.5);
} }
.pan-item:hover .pan-thumb { .pan-item:hover .pan-thumb {
transform: rotate(-110deg); transform: rotate(-110deg);
} }
.pan-item:hover .pan-info p a { .pan-item:hover .pan-info p a {
opacity: 1; opacity: 1;
transform: translateX(0px) rotate(0deg); transform: translateX(0px) rotate(0deg);
} }
</style> </style>

View File

@ -0,0 +1,65 @@
<template>
<div>
<svg t="1508738709248" @click='click' class="screenfull-svg" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"
p-id="2069" xmlns:xlink="http://www.w3.org/1999/xlink" width="32" height="32">
<path d="M333.493443 428.647617 428.322206 333.832158 262.572184 168.045297 366.707916 64.444754 64.09683 64.444754 63.853283 366.570793 167.283957 262.460644Z"
p-id="2070"></path>
<path d="M854.845439 760.133334 688.61037 593.95864 593.805144 688.764889 759.554142 854.56096 655.44604 958.161503 958.055079 958.161503 958.274066 656.035464Z"
p-id="2071"></path>
<path d="M688.535669 428.550403 854.31025 262.801405 957.935352 366.921787 957.935352 64.34754 655.809313 64.081481 759.919463 167.535691 593.70793 333.731874Z"
p-id="2072"></path>
<path d="M333.590658 594.033341 167.8171 759.804852 64.218604 655.67219 64.218604 958.270996 366.342596 958.502263 262.234493 855.071589 428.421466 688.86108Z"
p-id="2073"></path>
</svg>
</div>
</template>
<script>
import screenfull from 'screenfull'
export default {
name: 'screenfull',
props: {
width: {
type: Number,
default: 22
},
height: {
type: Number,
default: 22
},
fill: {
type: String,
default: '#48576a'
}
},
data() {
return {
isFullscreen: false
}
},
methods: {
click() {
if (!screenfull.enabled) {
this.$message({
message: 'you browser can not work',
type: 'warning'
})
return false
}
screenfull.toggle()
}
}
}
</script>
<style scoped>
.screenfull-svg {
display: inline-block;
cursor: pointer;
fill: #5a5e66;;
width: 20px;
height: 20px;
vertical-align: 10px;
}
</style>

View File

@ -0,0 +1,56 @@
<template>
<div class="scroll-container" ref="scrollContainer" @mousewheel="handleScroll">
<div class="scroll-wrapper" ref="scrollWrapper" :style="{top: top + 'px'}">
<slot></slot>
</div>
</div>
</template>
<script>
const delta = 15
export default {
name: 'scrollBar',
data() {
return {
top: 0
}
},
methods: {
handleScroll(e) {
e.preventDefault()
const $container = this.$refs.scrollContainer
const $containerHeight = $container.offsetHeight
const $wrapper = this.$refs.scrollWrapper
const $wrapperHeight = $wrapper.offsetHeight
if (e.wheelDelta > 0) {
this.top = Math.min(0, this.top + e.wheelDelta)
} else {
if ($containerHeight - delta < $wrapperHeight) {
if (this.top < -($wrapperHeight - $containerHeight + delta)) {
this.top = this.top
} else {
this.top = Math.max(this.top + e.wheelDelta, $containerHeight - $wrapperHeight - delta)
}
} else {
this.top = 0
}
}
}
}
}
</script>
<style rel="stylesheet/scss" lang="scss" scoped>
@import '../../styles/variables.scss';
.scroll-container {
position: relative;
width: 100%;
height: 100%;
background-color: $menuBg;
.scroll-wrapper {
position: absolute;
width: 100%!important;
}
}
</style>

View File

@ -0,0 +1,51 @@
<template>
<div class="scroll-container" ref="scrollContainer" @mousewheel="handleScroll">
<div class="scroll-wrapper" ref="scrollWrapper" :style="{left: left + 'px'}">
<slot></slot>
</div>
</div>
</template>
<script>
export default {
name: 'scrollPane',
data() {
return {
left: 0
}
},
methods: {
handleScroll(e) {
e.preventDefault()
const $container = this.$refs.scrollContainer
const $containerWidth = $container.offsetWidth
const $wrapper = this.$refs.scrollWrapper
const $wrapperWidth = $wrapper.offsetWidth
if (e.wheelDelta > 0) {
this.left = Math.min(0, this.left + e.wheelDelta)
} else {
if ($containerWidth - 100 < $wrapperWidth) {
if (this.left < -($wrapperWidth - $containerWidth + 100)) {
this.left = this.left
} else {
this.left = Math.max(this.left + e.wheelDelta, $containerWidth - $wrapperWidth - 100)
}
} else {
this.left = 0
}
}
}
}
}
</script>
<style rel="stylesheet/scss" lang="scss" scoped>
.scroll-container {
white-space: nowrap;
position: relative;
overflow: hidden;
.scroll-wrapper {
position: absolute;
}
}
</style>

View File

@ -0,0 +1,97 @@
<template>
<div class="share-dropdown-menu" :class="{active:isActive}">
<div class="share-dropdown-menu-wrapper">
<span class="share-dropdown-menu-title" @click.self="clickTitle">{{title}}</span>
<div class="share-dropdown-menu-item" v-for="(item,index) of items" :key='index'>
<a v-if="item.href" :href="item.href" target="_blank">{{item.title}}</a>
<span v-else>{{item.title}}</span>
</div>
</div>
</div>
</template>
<script>
export default {
props: {
items: {
type: Array
},
title: {
type: String,
default: 'vue'
}
},
data() {
return {
isActive: false
}
},
methods: {
clickTitle() {
this.isActive = !this.isActive
}
}
}
</script>
<style rel="stylesheet/scss" lang="scss" >
$n: 5; //items.length
$t: .1s;
.share-dropdown-menu {
width: 250px;
position: relative;
z-index: 1;
&-title {
width: 100%;
display: block;
cursor: pointer;
background: black;
color: white;
height: 60px;
line-height: 60px;
font-size: 20px;
text-align: center;
z-index: 2;
transform: translate3d(0,0,0);
}
&-wrapper {
position: relative;
}
&-item {
text-align: center;
position: absolute;
width: 100%;
background: #e0e0e0;
line-height: 60px;
height: 60px;
cursor: pointer;
font-size: 20px;
opacity: 1;
transition: transform 0.28s ease;
&:hover {
background: black;
color: white;
}
@for $i from 1 through $n {
&:nth-of-type(#{$i}) {
z-index: -1;
transition-delay: $i*$t;
transform: translate3d(0, -60px, 0);
}
}
}
&.active {
.share-dropdown-menu-wrapper {
z-index: 1;
}
.share-dropdown-menu-item {
@for $i from 1 through $n {
&:nth-of-type(#{$i}) {
transition-delay: ($n - $i)*$t;
transform: translate3d(0, ($i - 1)*60px, 0);
}
}
}
}
}
</style>

View File

@ -1,41 +0,0 @@
<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>

View File

@ -1,71 +0,0 @@
<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>

View File

@ -1,111 +0,0 @@
<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' : ''
}
},
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>

View File

@ -1,73 +1,76 @@
<template> <template>
<div :style="{height:height+'px',zIndex:zIndex}"> <div :style="{height:height+'px',zIndex:zIndex}">
<div :class="className" :style="{top:stickyTop+'px',zIndex:zIndex,position:position,width:width,height:height+'px'}"> <div :class="className" :style="{top:stickyTop+'px',zIndex:zIndex,position:position,width:width,height:height+'px'}">
<slot> <slot>
<div>sticky</div> <div>sticky</div>
</slot> </slot>
</div>
</div> </div>
</div>
</template> </template>
<script>
export default {
name: 'Sticky',
props: {
stickyTop: {
type: Number,
default: 0
},
zIndex: {
type: Number,
default: 1
},
className: {
type: String
}
},
data() {
return {
active: false,
position: '',
currentTop: '',
width: undefined,
height: undefined,
child: null,
stickyHeight: 0
}; <script>
}, export default {
methods: { name: 'Sticky',
sticky() { props: {
if (this.active) { stickyTop: {
return type: Number,
} default: 0
this.position = 'fixed'; },
this.active = true; zIndex: {
this.width = this.width + 'px'; type: Number,
}, default: 1
reset() { },
if (!this.active) { className: {
return type: String
} }
this.position = ''; },
this.width = 'auto' data() {
this.active = false return {
}, active: false,
handleScroll() { position: '',
this.width = this.$el.getBoundingClientRect().width; currentTop: '',
const offsetTop = this.$el.getBoundingClientRect().top; width: undefined,
if (offsetTop <= this.stickyTop) { height: undefined,
this.sticky(); child: null,
return stickyHeight: 0
} }
this.reset() },
} mounted() {
}, this.height = this.$el.getBoundingClientRect().height
mounted() { window.addEventListener('scroll', this.handleScroll)
this.height = this.$el.getBoundingClientRect().height; },
window.addEventListener('scroll', this.handleScroll); activated() {
}, this.handleScroll()
destroyed() { },
window.removeEventListener('scroll', this.handleScroll); destroyed() {
window.removeEventListener('scroll', this.handleScroll)
},
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()
}
}
}
</script> </script>

View File

@ -0,0 +1,42 @@
<template>
<svg :class="svgClass" aria-hidden="true">
<use :xlink:href="iconName"></use>
</svg>
</template>
<script>
export default {
name: 'svg-icon',
props: {
iconClass: {
type: String,
required: true
},
className: {
type: String
}
},
computed: {
iconName() {
return `#icon-${this.iconClass}`
},
svgClass() {
if (this.className) {
return 'svg-icon ' + this.className
} else {
return 'svg-icon'
}
}
}
}
</script>
<style scoped>
.svg-icon {
width: 1em;
height: 1em;
vertical-align: -0.15em;
fill: currentColor;
overflow: hidden;
}
</style>

View File

@ -0,0 +1,113 @@
<template>
<a class="link--mallki" :class="className" href="#">
{{text}}
<span :data-letters="text"></span>
<span :data-letters="text"></span>
</a>
</template>
<script>
export default {
props: {
className: {
type: String
},
text: {
type: String,
default: 'vue-element-admin'
}
}
}
</script>
<style>
/* Mallki */
.link--mallki {
font-weight: 800;
color: #4dd9d5;
font-family: 'Dosis', sans-serif;
-webkit-transition: color 0.5s 0.25s;
transition: color 0.5s 0.25s;
overflow: hidden;
position: relative;
display: inline-block;
line-height: 1;
outline: none;
text-decoration: none;
}
.link--mallki:hover {
-webkit-transition: none;
transition: none;
color: transparent;
}
.link--mallki::before {
content: '';
width: 100%;
height: 6px;
margin: -3px 0 0 0;
background: #3888fa;
position: absolute;
left: 0;
top: 50%;
-webkit-transform: translate3d(-100%, 0, 0);
transform: translate3d(-100%, 0, 0);
-webkit-transition: -webkit-transform 0.4s;
transition: transform 0.4s;
-webkit-transition-timing-function: cubic-bezier(0.7, 0, 0.3, 1);
transition-timing-function: cubic-bezier(0.7, 0, 0.3, 1);
}
.link--mallki:hover::before {
-webkit-transform: translate3d(100%, 0, 0);
transform: translate3d(100%, 0, 0);
}
.link--mallki span {
position: absolute;
height: 50%;
width: 100%;
left: 0;
top: 0;
overflow: hidden;
}
.link--mallki span::before {
content: attr(data-letters);
color: red;
position: absolute;
left: 0;
width: 100%;
color: #3888fa;
-webkit-transition: -webkit-transform 0.5s;
transition: transform 0.5s;
}
.link--mallki span:nth-child(2) {
top: 50%;
}
.link--mallki span:first-child::before {
top: 0;
-webkit-transform: translate3d(0, 100%, 0);
transform: translate3d(0, 100%, 0);
}
.link--mallki span:nth-child(2)::before {
bottom: 0;
-webkit-transform: translate3d(0, -100%, 0);
transform: translate3d(0, -100%, 0);
}
.link--mallki:hover span::before {
-webkit-transition-delay: 0.3s;
transition-delay: 0.3s;
-webkit-transform: translate3d(0, 0, 0);
transform: translate3d(0, 0, 0);
-webkit-transition-timing-function: cubic-bezier(0.2, 1, 0.3, 1);
transition-timing-function: cubic-bezier(0.2, 1, 0.3, 1);
}
</style>

View File

@ -0,0 +1,145 @@
<template>
<el-color-picker
class="theme-picker"
popper-class="theme-picker-dropdown"
v-model="theme"></el-color-picker>
</template>
<script>
import { getVersion } from '@/utils/index.js'
const version = getVersion('element-ui') // element-ui version from package.json
const ORIGINAL_THEME = '#409EFF' // default color
export default {
data() {
return {
chalk: '', // content of theme-chalk css
theme: ORIGINAL_THEME
}
},
watch: {
theme(val, oldVal) {
if (typeof val !== 'string') return
const themeCluster = this.getThemeCluster(val.replace('#', ''))
const originalCluster = this.getThemeCluster(oldVal.replace('#', ''))
console.log(themeCluster, originalCluster)
const getHandler = (variable, id) => {
return () => {
const originalCluster = this.getThemeCluster(ORIGINAL_THEME.replace('#', ''))
const newStyle = this.updateStyle(this[variable], originalCluster, themeCluster)
let styleTag = document.getElementById(id)
if (!styleTag) {
styleTag = document.createElement('style')
styleTag.setAttribute('id', id)
document.head.appendChild(styleTag)
}
styleTag.innerText = newStyle
}
}
const chalkHandler = getHandler('chalk', 'chalk-style')
if (!this.chalk) {
const url = `https://unpkg.com/element-ui@${version}/lib/theme-chalk/index.css`
this.getCSSString(url, chalkHandler, 'chalk')
} else {
chalkHandler()
}
const styles = [].slice.call(document.querySelectorAll('style'))
.filter(style => {
const text = style.innerText
return new RegExp(oldVal, 'i').test(text) && !/Chalk Variables/.test(text)
})
styles.forEach(style => {
const { innerText } = style
if (typeof innerText !== 'string') return
style.innerText = this.updateStyle(innerText, originalCluster, themeCluster)
})
this.$message({
message: '换肤成功',
type: 'success'
})
}
},
methods: {
updateStyle(style, oldCluster, newCluster) {
let newStyle = style
oldCluster.forEach((color, index) => {
newStyle = newStyle.replace(new RegExp(color, 'ig'), newCluster[index])
})
return newStyle
},
getCSSString(url, callback, variable) {
const xhr = new XMLHttpRequest()
xhr.onreadystatechange = () => {
if (xhr.readyState === 4 && xhr.status === 200) {
this[variable] = xhr.responseText.replace(/@font-face{[^}]+}/, '')
callback()
}
}
xhr.open('GET', url)
xhr.send()
},
getThemeCluster(theme) {
const tintColor = (color, tint) => {
let red = parseInt(color.slice(0, 2), 16)
let green = parseInt(color.slice(2, 4), 16)
let blue = parseInt(color.slice(4, 6), 16)
if (tint === 0) { // when primary color is in its rgb space
return [red, green, blue].join(',')
} else {
red += Math.round(tint * (255 - red))
green += Math.round(tint * (255 - green))
blue += Math.round(tint * (255 - blue))
red = red.toString(16)
green = green.toString(16)
blue = blue.toString(16)
return `#${red}${green}${blue}`
}
}
const shadeColor = (color, shade) => {
let red = parseInt(color.slice(0, 2), 16)
let green = parseInt(color.slice(2, 4), 16)
let blue = parseInt(color.slice(4, 6), 16)
red = Math.round((1 - shade) * red)
green = Math.round((1 - shade) * green)
blue = Math.round((1 - shade) * blue)
red = red.toString(16)
green = green.toString(16)
blue = blue.toString(16)
return `#${red}${green}${blue}`
}
const clusters = [theme]
for (let i = 0; i <= 9; i++) {
clusters.push(tintColor(theme, Number((i / 10).toFixed(2))))
}
clusters.push(shadeColor(theme, 0.1))
return clusters
}
}
}
</script>
<style>
.theme-picker .el-color-picker__trigger {
vertical-align: middle;
}
.theme-picker-dropdown .el-color-dropdown__link-btn {
display: none;
}
</style>

View File

@ -0,0 +1,95 @@
<template>
<div class="upload-container">
<el-button icon='upload' :style="{background:color,borderColor:color}" @click=" dialogVisible=true" type="primary">上传图片
</el-button>
<el-dialog :visible.sync="dialogVisible">
<el-upload class="editor-slide-upload" action="https://httpbin.org/post" :multiple="true" :file-list="fileList" :show-file-list="true"
list-type="picture-card" :on-remove="handleRemove" :on-success="handleSuccess" :before-upload="beforeUpload">
<el-button size="small" type="primary">点击上传</el-button>
</el-upload>
<el-button @click="dialogVisible = false"> </el-button>
<el-button type="primary" @click="handleSubmit"> </el-button>
</el-dialog>
</div>
</template>
<script>
// import { getToken } from 'api/qiniu'
export default {
name: 'editorSlideUpload',
props: {
color: {
type: String,
default: '#20a0ff'
}
},
data() {
return {
dialogVisible: false,
listObj: {},
fileList: []
}
},
methods: {
checkAllSuccess() {
return Object.keys(this.listObj).every(item => this.listObj[item].hasSuccess)
},
handleSubmit() {
const arr = Object.keys(this.listObj).map(v => this.listObj[v])
if (!this.checkAllSuccess()) {
this.$message('请等待所有图片上传成功 或 出现了网络问题,请刷新页面重新上传!')
return
}
console.log(arr)
this.$emit('successCBK', arr)
this.listObj = {}
this.fileList = []
this.dialogVisible = false
},
handleSuccess(response, file) {
const uid = file.uid
const objKeyArr = Object.keys(this.listObj)
for (let i = 0, len = objKeyArr.length; i < len; i++) {
if (this.listObj[objKeyArr[i]].uid === uid) {
this.listObj[objKeyArr[i]].url = response.files.file
this.listObj[objKeyArr[i]].hasSuccess = true
return
}
}
},
handleRemove(file) {
const uid = file.uid
const objKeyArr = Object.keys(this.listObj)
for (let i = 0, len = objKeyArr.length; i < len; i++) {
if (this.listObj[objKeyArr[i]].uid === uid) {
delete this.listObj[objKeyArr[i]]
return
}
}
},
beforeUpload(file) {
const _self = this
const _URL = window.URL || window.webkitURL
const fileName = file.uid
this.listObj[fileName] = {}
return new Promise((resolve, reject) => {
const img = new Image()
img.src = _URL.createObjectURL(file)
img.onload = function() {
_self.listObj[fileName] = { hasSuccess: false, uid: file.uid, width: this.width, height: this.height }
}
resolve(true)
})
}
}
}
</script>
<style rel="stylesheet/scss" lang="scss" scoped>
.upload-container {
.editor-slide-upload {
margin-bottom: 20px;
}
}
</style>

View File

@ -1,154 +1,152 @@
<template> <template>
<div class='tinymce-container editor-container'> <div class="tinymce-container editor-container">
<textarea class='tinymce-textarea' :id="id"></textarea> <textarea class="tinymce-textarea" :id="tinymceId"></textarea>
<div class="editor-custom-btn-container">
<editorImage color="#20a0ff" class="editor-upload-btn" @successCBK="imageSuccessCBK"></editorImage>
</div>
</div> </div>
</template> </template>
<script> <script>
// import { getToken, upload } from 'api/qiniu'; // import editorImage from './components/editorImage'
export default {
name: 'tinymce', export default {
props: { name: 'tinymce',
id: { components: { editorImage },
type: String, props: {
default: 'tinymceEditor' id: {
}, type: String
value: { },
type: String, value: {
default: '' type: String,
}, default: ''
toolbar: { },
type: Array, toolbar: {
required: false, type: Array,
default() { required: false,
return ['removeformat undo redo | bullist numlist | outdent indent | forecolor | fullscreen code', 'bold italic blockquote | h2 p media link | alignleft aligncenter alignright'] 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,
init_instance_callback: editor => {
if (_this.value) {
editor.setContent(_this.value)
}
_this.hasInit = true;
editor.on('NodeChange Change KeyUp', () => {
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: '正文',
text: '正文',
onclick() {
editor.execCommand('mceToggleFormat', false, 'p');
},
onPostRender() {
const btn = this;
editor.on('init', () => {
editor.formatter.formatChanged('p', state => {
btn.active(state);
});
});
}
});
}
});
},
destroyed() {
tinymce.get(this.id).destroy();
} }
},
menubar: {
default: ''
},
height: {
type: Number,
required: false,
default: 360
}
},
data() {
return {
hasChange: false,
hasInit: false,
tinymceId: this.id || 'vue-tinymce-' + +new Date()
}
},
watch: {
value(val) {
if (!this.hasChange && this.hasInit) {
this.$nextTick(() => window.tinymce.get(this.tinymceId).setContent(val))
}
}
},
mounted() {
this.initTinymce()
},
activated() {
this.initTinymce()
},
deactivated() {
this.destroyTinymce()
},
methods: {
initTinymce() {
const _this = this
window.tinymce.init({
selector: `#${this.tinymceId}`,
height: this.height,
body_class: 'panel-body ',
object_resizing: false,
toolbar: this.toolbar,
menubar: this.menubar,
plugins: 'advlist,autolink,code,paste,textcolor, colorpicker,fullscreen,link,lists,media,wordcount, imagetools',
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',
imagetools_cors_hosts: ['wpimg.wallstcn.com', 'wallstreetcn.com'],
imagetools_toolbar: 'watermark',
default_link_target: '_blank',
link_title: false,
init_instance_callback: editor => {
if (_this.value) {
editor.setContent(_this.value)
}
_this.hasInit = true
editor.on('NodeChange Change KeyUp', () => {
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);
// });
// },
})
},
destroyTinymce() {
if (window.tinymce.get(this.tinymceId)) {
window.tinymce.get(this.tinymceId).destroy()
}
},
setContent(value) {
window.tinymce.get(this.tinymceId).setContent(value)
},
getContent() {
window.tinymce.get(this.tinymceId).getContent()
},
imageSuccessCBK(arr) {
const _this = this
arr.forEach(v => {
window.tinymce.get(_this.tinymceId).insertContent(`<img class="wscnph" src="${v.url}" >`)
})
}
},
destroyed() {
this.destroyTinymce()
}
} }
</script> </script>
@ -156,18 +154,16 @@
.tinymce-container { .tinymce-container {
position: relative position: relative
} }
.tinymce-textarea { .tinymce-textarea {
visibility: hidden; visibility: hidden;
z-index: -1; z-index: -1;
} }
.editor-custom-btn-container { .editor-custom-btn-container {
position: absolute; position: absolute;
right: 15px; right: 15px;
/*z-index: 2005;*/
top: 18px; top: 18px;
} }
.editor-upload-btn { .editor-upload-btn {
display: inline-block; display: inline-block;
} }

View File

@ -1,14 +1,7 @@
<template> <template>
<div class="upload-container"> <div class="upload-container">
<el-upload <el-upload class="image-uploader" :data="dataObj" drag :multiple="false" :show-file-list="false" action="https://httpbin.org/post"
class="image-uploader" :on-success="handleImageScucess">
:data="dataObj"
drag
:multiple="false"
:show-file-list="false"
action="https://httpbin.org/post"
:on-success="handleImageScucess">
<i class="el-icon-upload"></i> <i class="el-icon-upload"></i>
<div class="el-upload__text">将文件拖到此处<em>点击上传</em></div> <div class="el-upload__text">将文件拖到此处<em>点击上传</em></div>
</el-upload> </el-upload>
@ -22,53 +15,55 @@
</div> </div>
</div> </div>
</template> </template>
<script> <script>
// //
import { getToken } from 'api/qiniu'; import { getToken } from '@/api/qiniu'
export default {
name: 'singleImageUpload', export default {
props: { name: 'singleImageUpload',
value: String props: {
}, value: String
computed: { },
imageUrl() { computed: {
return this.value imageUrl() {
} return this.value
}, }
data() { },
return { data() {
tempUrl: '', return {
dataObj: { token: '', key: '' } tempUrl: '',
}; dataObj: { token: '', key: '' }
}, }
methods: { },
rmImage() { methods: {
this.emitInput(''); rmImage() {
}, this.emitInput('')
emitInput(val) { },
this.$emit('input', val); emitInput(val) {
}, this.$emit('input', val)
handleImageScucess() { },
this.emitInput(this.tempUrl) handleImageScucess() {
}, this.emitInput(this.tempUrl)
beforeUpload() { },
const _self = this; beforeUpload() {
return new Promise((resolve, reject) => { const _self = this
getToken().then(response => { return new Promise((resolve, reject) => {
const key = response.data.qiniu_key; getToken().then(response => {
const token = response.data.qiniu_token; const key = response.data.qiniu_key
_self._data.dataObj.token = token; const token = response.data.qiniu_token
_self._data.dataObj.key = key; _self._data.dataObj.token = token
this.tempUrl = response.data.qiniu_url; _self._data.dataObj.key = key
resolve(true); this.tempUrl = response.data.qiniu_url
}).catch(err => { resolve(true)
console.log(err); }).catch(err => {
reject(false) console.log(err)
}); reject(false)
}); })
} })
} }
}; }
}
</script> </script>
<style rel="stylesheet/scss" lang="scss" scoped> <style rel="stylesheet/scss" lang="scss" scoped>

View File

@ -1,126 +1,118 @@
<template> <template>
<div class="singleImageUpload2 upload-container"> <div class="singleImageUpload2 upload-container">
<el-upload <el-upload class="image-uploader" :data="dataObj" drag :multiple="false" :show-file-list="false" action="https://httpbin.org/post"
class="image-uploader" :on-success="handleImageScucess">
:data="dataObj" <i class="el-icon-upload"></i>
drag <div class="el-upload__text">Drag或<em>点击上传</em></div>
:multiple="false" </el-upload>
:show-file-list="false" <div v-show="imageUrl.length>0" class="image-preview">
action="https://httpbin.org/post" <div class="image-preview-wrapper" v-show="imageUrl.length>1">
<img :src="imageUrl">
:on-success="handleImageScucess"> <div class="image-preview-action">
<i class="el-icon-upload"></i> <i @click="rmImage" class="el-icon-delete"></i>
<div class="el-upload__text">Drag或<em>点击上传</em></div> </div>
</el-upload> </div>
<div v-show="imageUrl.length>0" class="image-preview"> </div>
<div class="image-preview-wrapper" v-show="imageUrl.length>1"> </div>
<img :src="imageUrl">
<div class="image-preview-action">
<i @click="rmImage" class="el-icon-delete"></i>
</div>
</div>
</div>
</div>
</template> </template>
<script> <script>
// import { getToken } from '@/api/qiniu'
import { getToken } from 'api/qiniu';
export default { export default {
name: 'singleImageUpload2', name: 'singleImageUpload2',
props: { props: {
value: String value: String
}, },
computed: { computed: {
imageUrl() { imageUrl() {
return this.value return this.value
} }
}, },
data() { data() {
return { return {
tempUrl: '', tempUrl: '',
dataObj: { token: '', key: '' } dataObj: { token: '', key: '' }
}; }
}, },
methods: { methods: {
rmImage() { rmImage() {
this.emitInput(''); this.emitInput('')
}, },
emitInput(val) { emitInput(val) {
this.$emit('input', val); this.$emit('input', val)
}, },
handleImageScucess() { handleImageScucess() {
this.emitInput(this.tempUrl) this.emitInput(this.tempUrl)
}, },
beforeUpload() { beforeUpload() {
const _self = this; const _self = this
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
getToken().then(response => { getToken().then(response => {
const key = response.data.qiniu_key; const key = response.data.qiniu_key
const token = response.data.qiniu_token; const token = response.data.qiniu_token
_self._data.dataObj.token = token; _self._data.dataObj.token = token
_self._data.dataObj.key = key; _self._data.dataObj.key = key
this.tempUrl = response.data.qiniu_url; this.tempUrl = response.data.qiniu_url
resolve(true); resolve(true)
}).catch(err => { }).catch(() => {
console.log(err); reject(false)
reject(false) })
}); })
}); }
} }
} }
};
</script> </script>
<style rel="stylesheet/scss" lang="scss" scoped> <style rel="stylesheet/scss" lang="scss" scoped>
.upload-container { .upload-container {
width: 100%; width: 100%;
height: 100%; height: 100%;
position: relative; position: relative;
.image-uploader{ .image-uploader {
height: 100%; height: 100%;
} }
.image-preview { .image-preview {
width: 100%; width: 100%;
height: 100%; height: 100%;
position: absolute; position: absolute;
left: 0px; left: 0px;
top: 0px; top: 0px;
border: 1px dashed #d9d9d9; border: 1px dashed #d9d9d9;
.image-preview-wrapper { .image-preview-wrapper {
position: relative; position: relative;
width: 100%; width: 100%;
height: 100%; height: 100%;
img { img {
width: 100%; width: 100%;
height: 100%; height: 100%;
} }
} }
.image-preview-action { .image-preview-action {
position: absolute; position: absolute;
width: 100%; width: 100%;
height: 100%; height: 100%;
left: 0; left: 0;
top: 0; top: 0;
cursor: default; cursor: default;
text-align: center; text-align: center;
color: #fff; color: #fff;
opacity: 0; opacity: 0;
font-size: 20px; font-size: 20px;
background-color: rgba(0, 0, 0, .5); background-color: rgba(0, 0, 0, .5);
transition: opacity .3s; transition: opacity .3s;
cursor: pointer; cursor: pointer;
text-align: center; text-align: center;
line-height: 200px; line-height: 200px;
.el-icon-delete { .el-icon-delete {
font-size: 36px; font-size: 36px;
} }
} }
&:hover { &:hover {
.image-preview-action { .image-preview-action {
opacity: 1; opacity: 1;
} }
} }
} }
} }
</style> </style>

View File

@ -1,154 +1,146 @@
<template> <template>
<div class="upload-container"> <div class="upload-container">
<el-upload <el-upload class="image-uploader" :data="dataObj" drag :multiple="false" :show-file-list="false" action="https://httpbin.org/post"
class="image-uploader" :on-success="handleImageScucess">
:data="dataObj" <i class="el-icon-upload"></i>
drag <div class="el-upload__text">将文件拖到此处<em>点击上传</em></div>
:multiple="false" </el-upload>
:show-file-list="false" <div class="image-preview image-app-preview">
action="https://httpbin.org/post" <div class="image-preview-wrapper" v-show="imageUrl.length>1">
<div class='app-fake-conver'>&nbsp&nbsp全球 付费节目单 最热 经济</div>
:on-success="handleImageScucess"> <img :src="imageUrl">
<i class="el-icon-upload"></i> <div class="image-preview-action">
<div class="el-upload__text">将文件拖到此处<em>点击上传</em></div> <i @click="rmImage" class="el-icon-delete"></i>
</el-upload> </div>
<div class="image-preview image-app-preview"> </div>
<div class="image-preview-wrapper" v-show="imageUrl.length>1"> </div>
<div class='app-fake-conver'>&nbsp&nbsp全球 付费节目单 最热 经济</div> <div class="image-preview">
<img :src="imageUrl"> <div class="image-preview-wrapper" v-show="imageUrl.length>1">
<div class="image-preview-action"> <img :src="imageUrl">
<i @click="rmImage" class="el-icon-delete"></i> <div class="image-preview-action">
</div> <i @click="rmImage" class="el-icon-delete"></i>
</div> </div>
</div> </div>
<div class="image-preview"> </div>
<div class="image-preview-wrapper" v-show="imageUrl.length>1"> </div>
<img :src="imageUrl">
<div class="image-preview-action">
<i @click="rmImage" class="el-icon-delete"></i>
</div>
</div>
</div>
</div>
</template> </template>
<script> <script>
// import { getToken } from '@/api/qiniu'
import { getToken } from 'api/qiniu';
export default { export default {
name: 'singleImageUpload', name: 'singleImageUpload',
props: { props: {
value: String value: String
}, },
computed: { computed: {
imageUrl() { imageUrl() {
return this.value return this.value
} }
}, },
data() { data() {
return { return {
tempUrl: '', tempUrl: '',
dataObj: { token: '', key: '' } dataObj: { token: '', key: '' }
}; }
}, },
methods: { methods: {
rmImage() { rmImage() {
this.emitInput(''); this.emitInput('')
}, },
emitInput(val) { emitInput(val) {
this.$emit('input', val); this.$emit('input', val)
}, },
handleImageScucess(file) { handleImageScucess(file) {
this.emitInput(file.files.file) this.emitInput(file.files.file)
}, },
beforeUpload() { beforeUpload() {
const _self = this; const _self = this
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
getToken().then(response => { getToken().then(response => {
const key = response.data.qiniu_key; const key = response.data.qiniu_key
const token = response.data.qiniu_token; const token = response.data.qiniu_token
_self._data.dataObj.token = token; _self._data.dataObj.token = token
_self._data.dataObj.key = key; _self._data.dataObj.key = key
this.tempUrl = response.data.qiniu_url; this.tempUrl = response.data.qiniu_url
resolve(true); resolve(true)
}).catch(err => { }).catch(err => {
console.log(err); console.log(err)
reject(false) reject(false)
}); })
}); })
} }
} }
}; }
</script> </script>
<style rel="stylesheet/scss" lang="scss" scoped> <style rel="stylesheet/scss" lang="scss" scoped>
@import "src/styles/mixin.scss"; @import "src/styles/mixin.scss";
.upload-container { .upload-container {
width: 100%; width: 100%;
position: relative; position: relative;
@include clearfix; @include clearfix;
.image-uploader { .image-uploader {
width: 35%; width: 35%;
float: left; float: left;
} }
.image-preview { .image-preview {
width: 200px; width: 200px;
height: 200px; height: 200px;
position: relative; position: relative;
border: 1px dashed #d9d9d9; border: 1px dashed #d9d9d9;
float: left; float: left;
margin-left: 50px; margin-left: 50px;
.image-preview-wrapper { .image-preview-wrapper {
position: relative; position: relative;
width: 100%; width: 100%;
height: 100%; height: 100%;
img { img {
width: 100%; width: 100%;
height: 100%; height: 100%;
} }
} }
.image-preview-action { .image-preview-action {
position: absolute; position: absolute;
width: 100%; width: 100%;
height: 100%; height: 100%;
left: 0; left: 0;
top: 0; top: 0;
cursor: default; cursor: default;
text-align: center; text-align: center;
color: #fff; color: #fff;
opacity: 0; opacity: 0;
font-size: 20px; font-size: 20px;
background-color: rgba(0, 0, 0, .5); background-color: rgba(0, 0, 0, .5);
transition: opacity .3s; transition: opacity .3s;
cursor: pointer; cursor: pointer;
text-align: center; text-align: center;
line-height: 200px; line-height: 200px;
.el-icon-delete { .el-icon-delete {
font-size: 36px; font-size: 36px;
} }
} }
&:hover { &:hover {
.image-preview-action { .image-preview-action {
opacity: 1; opacity: 1;
} }
} }
} }
.image-app-preview{ .image-app-preview {
width: 320px; width: 320px;
height: 180px; height: 180px;
position: relative; position: relative;
border: 1px dashed #d9d9d9; border: 1px dashed #d9d9d9;
float: left; float: left;
margin-left: 50px; margin-left: 50px;
.app-fake-conver{ .app-fake-conver {
height: 44px; height: 44px;
position: absolute; position: absolute;
width: 100%; width: 100%; // background: rgba(0, 0, 0, .1);
// background: rgba(0, 0, 0, .1); text-align: center;
text-align: center; line-height: 64px;
line-height: 64px; color: #fff;
color: #fff; }
}
} }
}
}
</style> </style>

View File

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

View File

@ -1,64 +0,0 @@
<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>

View File

@ -1,156 +0,0 @@
<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>

View File

@ -0,0 +1,49 @@
// Inspired by https://github.com/Inndy/vue-clipboard2
const Clipboard = require('clipboard')
if (!Clipboard) {
throw new Error('you shold npm install `clipboard` --save at first ')
}
export default {
bind(el, binding) {
if (binding.arg === 'success') {
el._v_clipboard_success = binding.value
} else if (binding.arg === 'error') {
el._v_clipboard_error = binding.value
} else {
const clipboard = new Clipboard(el, {
text() { return binding.value },
action() { return binding.arg === 'cut' ? 'cut' : 'copy' }
})
clipboard.on('success', e => {
const callback = el._v_clipboard_success
callback && callback(e) // eslint-disable-line
})
clipboard.on('error', e => {
const callback = el._v_clipboard_error
callback && callback(e) // eslint-disable-line
})
el._v_clipboard = clipboard
}
},
update(el, binding) {
if (binding.arg === 'success') {
el._v_clipboard_success = binding.value
} else if (binding.arg === 'error') {
el._v_clipboard_error = binding.value
} else {
el._v_clipboard.text = function() { return binding.value }
el._v_clipboard.action = function() { return binding.arg === 'cut' ? 'cut' : 'copy' }
}
},
unbind(el, binding) {
if (binding.arg === 'success') {
delete el._v_clipboard_success
} else if (binding.arg === 'error') {
delete el._v_clipboard_error
} else {
el._v_clipboard.destroy()
delete el._v_clipboard
}
}
}

View File

@ -0,0 +1,13 @@
import Clipboard from './clipboard'
const install = function(Vue) {
Vue.directive('Clipboard', Clipboard)
}
if (window.Vue) {
window.clipboard = Clipboard
Vue.use(install); // eslint-disable-line
}
Clipboard.install = install
export default Clipboard

View File

@ -1,99 +1,91 @@
(function() { const vueSticky = {}
const vueSticky = {}; let listenAction
let listenAction; vueSticky.install = Vue => {
vueSticky.install = Vue => { Vue.directive('sticky', {
Vue.directive('sticky', { inserted(el, binding) {
inserted(el, binding) { const params = binding.value || {}
const params = binding.value || {}, const stickyTop = params.stickyTop || 0
stickyTop = params.stickyTop || 0, const zIndex = params.zIndex || 1000
zIndex = params.zIndex || 1000, const elStyle = el.style
elStyle = el.style;
elStyle.position = '-webkit-sticky'; elStyle.position = '-webkit-sticky'
elStyle.position = 'sticky'; elStyle.position = 'sticky'
// if the browser support css stickyCurrently Safari, Firefox and Chrome Canary // if the browser support css stickyCurrently Safari, Firefox and Chrome Canary
// if (~elStyle.position.indexOf('sticky')) { // if (~elStyle.position.indexOf('sticky')) {
// elStyle.top = `${stickyTop}px`; // elStyle.top = `${stickyTop}px`;
// elStyle.zIndex = zIndex; // elStyle.zIndex = zIndex;
// return // return
// } // }
const elHeight = el.getBoundingClientRect().height; const elHeight = el.getBoundingClientRect().height
const elWidth = el.getBoundingClientRect().width; const elWidth = el.getBoundingClientRect().width
elStyle.cssText = `top: ${stickyTop}px; z-index: ${zIndex}`; elStyle.cssText = `top: ${stickyTop}px; z-index: ${zIndex}`
const parentElm = el.parentNode || document.documentElement; const parentElm = el.parentNode || document.documentElement
const placeholder = document.createElement('div'); const placeholder = document.createElement('div')
placeholder.style.display = 'none'; placeholder.style.display = 'none'
placeholder.style.width = `${elWidth}px`; placeholder.style.width = `${elWidth}px`
placeholder.style.height = `${elHeight}px`; placeholder.style.height = `${elHeight}px`
parentElm.insertBefore(placeholder, el) parentElm.insertBefore(placeholder, el)
let active = false; let active = false
const getScroll = (target, top) => { const getScroll = (target, top) => {
const prop = top ? 'pageYOffset' : 'pageXOffset'; const prop = top ? 'pageYOffset' : 'pageXOffset'
const method = top ? 'scrollTop' : 'scrollLeft'; const method = top ? 'scrollTop' : 'scrollLeft'
let ret = target[prop]; let ret = target[prop]
if (typeof ret !== 'number') { if (typeof ret !== 'number') {
ret = window.document.documentElement[method]; ret = window.document.documentElement[method]
} }
return ret; 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)
} }
})
}; const sticky = () => {
if (typeof exports == 'object') { if (active) {
module.exports = vueSticky return
} else if (typeof define == 'function' && define.amd) { }
define([], () => vueSticky) if (!elStyle.height) {
} else if (window.Vue) { elStyle.height = `${el.offsetHeight}px`
window.vueSticky = vueSticky; }
Vue.use(vueSticky)
} 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)
}
})
}
export default vueSticky

View File

@ -1,54 +0,0 @@
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)
}
}());

View File

@ -0,0 +1,13 @@
import waves from './waves'
const install = function(Vue) {
Vue.directive('waves', waves)
}
if (window.Vue) {
window.waves = waves
Vue.use(install); // eslint-disable-line
}
waves.install = install
export default waves

View File

@ -0,0 +1,42 @@
import './waves.css'
export default{
bind(el, binding) {
el.addEventListener('click', e => {
const customOpts = Object.assign({}, binding.value)
const opts = Object.assign({
ele: el, // 波纹作用元素
type: 'hit', // hit点击位置扩散center中心点扩展
color: 'rgba(0, 0, 0, 0.15)' // 波纹颜色
}, customOpts)
const 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)
}
}

14
src/errorLog.js Normal file
View File

@ -0,0 +1,14 @@
import Vue from 'vue'
import errLog from '@/store/errLog'
// 生产环境错误日志
if (process.env.NODE_ENV === 'production') {
Vue.config.errorHandler = function(err, vm) {
console.log(err, window.location.href)
errLog.pushLog({
err,
url: window.location.href,
vm
})
}
}

View File

@ -4,8 +4,9 @@ function pluralize(time, label) {
} }
return time + label + 's' return time + label + 's'
} }
export function timeAgo(time) { export function timeAgo(time) {
const between = Date.now() / 1000 - Number(time); const between = Date.now() / 1000 - Number(time)
if (between < 3600) { if (between < 3600) {
return pluralize(~~(between / 60), ' minute') return pluralize(~~(between / 60), ' minute')
} else if (between < 86400) { } else if (between < 86400) {
@ -17,20 +18,19 @@ export function timeAgo(time) {
export function parseTime(time, cFormat) { export function parseTime(time, cFormat) {
if (arguments.length === 0) { if (arguments.length === 0) {
return null; return null
} }
if ((time + '').length === 10) { if ((time + '').length === 10) {
time = +time * 1000 time = +time * 1000
} }
const format = cFormat || '{y}-{m}-{d} {h}:{i}:{s}'
const format = cFormat || '{y}-{m}-{d} {h}:{i}:{s}'; let date
let date; if (typeof time === 'object') {
if (typeof time == 'object') { date = time
date = time;
} else { } else {
date = new Date(parseInt(time)); date = new Date(parseInt(time))
} }
const formatObj = { const formatObj = {
y: date.getFullYear(), y: date.getFullYear(),
@ -40,24 +40,24 @@ export function parseTime(time, cFormat) {
i: date.getMinutes(), i: date.getMinutes(),
s: date.getSeconds(), s: date.getSeconds(),
a: date.getDay() a: date.getDay()
}; }
const time_str = format.replace(/{(y|m|d|h|i|s|a)+}/g, (result, key) => { const time_str = format.replace(/{(y|m|d|h|i|s|a)+}/g, (result, key) => {
let value = formatObj[key]; let value = formatObj[key]
if (key === 'a') return ['一', '二', '三', '四', '五', '六', '日'][value - 1]; if (key === 'a') return ['一', '二', '三', '四', '五', '六', '日'][value - 1]
if (result.length > 0 && value < 10) { if (result.length > 0 && value < 10) {
value = '0' + value; value = '0' + value
} }
return value || 0; return value || 0
}); })
return time_str; return time_str
} }
export function formatTime(time, option) { export function formatTime(time, option) {
time = +time * 1000; time = +time * 1000
const d = new Date(time); const d = new Date(time)
const now = Date.now(); const now = Date.now()
const diff = (now - d) / 1000; const diff = (now - d) / 1000
if (diff < 30) { if (diff < 30) {
return '刚刚' return '刚刚'
@ -75,34 +75,30 @@ export function formatTime(time, option) {
} }
} }
/* 数字 格式化*/ /* 数字 格式化*/
export function nFormatter(num, digits) { export function nFormatter(num, digits) {
const si = [ const si = [
{ value: 1E18, symbol: 'E' }, { value: 1E18, symbol: 'E' },
{ value: 1E15, symbol: 'P' }, { value: 1E15, symbol: 'P' },
{ value: 1E12, symbol: 'T' }, { value: 1E12, symbol: 'T' },
{ value: 1E9, symbol: 'G' }, { value: 1E9, symbol: 'G' },
{ value: 1E6, symbol: 'M' }, { value: 1E6, symbol: 'M' },
{ value: 1E3, symbol: 'k' } { value: 1E3, symbol: 'k' }
]; ]
for (let i = 0; i < si.length; i++) { for (let i = 0; i < si.length; i++) {
if (num >= si[i].value) { 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 / si[i].value + 0.1).toFixed(digits).replace(/\.0+$|(\.[0-9]*[1-9])0+$/, '$1') + si[i].symbol
} }
} }
return num.toString(); return num.toString()
} }
export function html2Text(val) { export function html2Text(val) {
const div = document.createElement('div'); const div = document.createElement('div')
div.innerHTML = val; div.innerHTML = val
return div.textContent || div.innerText; return div.textContent || div.innerText
} }
export function toThousandslsFilter(num) { export function toThousandslsFilter(num) {
return (+num || 0).toString().replace(/(\d)(?=(?:\d{3})+$)/g, '$1,'); return (+num || 0).toString().replace(/^-?\d+/g, m => m.replace(/(?=(?!\b)(\d{3})+$)/g, ','))
} }

12
src/icons/index.js Normal file
View File

@ -0,0 +1,12 @@
import Vue from 'vue'
import SvgIcon from '@/components/SvgIcon'// svg组件
import generateIconsView from '@/views/svg-icons/generateIconsView.js'// just for @/views/icons , you can delete it
// register globally
Vue.component('svg-icon', SvgIcon)
const requireAll = requireContext => requireContext.keys().map(requireContext)
const req = require.context('./svg', false, /\.svg$/)
const iconMap = requireAll(req)
generateIconsView.generate(iconMap) // just for @/views/icons , you can delete it

1
src/icons/svg/404.svg Normal file
View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1503994850540" class="icon" style="" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="10206" xmlns:xlink="http://www.w3.org/1999/xlink" width="64" height="64"><defs><style type="text/css"></style></defs><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" p-id="10207"></path></svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

1
src/icons/svg/bug.svg Normal file
View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1503994864347" class="icon" style="" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="10314" xmlns:xlink="http://www.w3.org/1999/xlink" width="64" height="64"><defs><style type="text/css"></style></defs><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="10315"></path></svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

1
src/icons/svg/chart.svg Normal file
View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1503994873331" class="icon" style="" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="10422" xmlns:xlink="http://www.w3.org/1999/xlink" width="64" height="64"><defs><style type="text/css"></style></defs><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" p-id="10423"></path></svg>

After

Width:  |  Height:  |  Size: 552 B

View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1506419860538" class="icon" style="" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4662" xmlns:xlink="http://www.w3.org/1999/xlink" width="64" height="64"><defs><style type="text/css"></style></defs><path d="M438.857143 950.857143l512 0 0-365.714286-237.714286 0q-22.820571 0-38.838857-16.018286t-16.018286-38.838857l0-237.714286-219.428571 0 0 658.285714zM585.142857 128l0-36.571429q0-7.460571-5.412571-12.873143t-12.873143-5.412571l-402.285714 0q-7.460571 0-12.873143 5.412571t-5.412571 12.873143l0 36.571429q0 7.460571 5.412571 12.873143t12.873143 5.412571l402.285714 0q7.460571 0 12.873143-5.412571t5.412571-12.873143zM731.428571 512l170.861714 0-170.861714-170.861714 0 170.861714zM1024 585.142857l0 384q0 22.820571-16.018286 38.838857t-38.838857 16.018286l-548.571429 0q-22.820571 0-38.838857-16.018286t-16.018286-38.838857l0-91.428571-310.857143 0q-22.820571 0-38.838857-16.018286t-16.018286-38.838857l0-768q0-22.820571 16.018286-38.838857t38.838857-16.018286l621.714286 0q22.820571 0 38.838857 16.018286t16.018286 38.838857l0 187.465143q11.995429 7.460571 20.553143 16.018286l233.179429 233.179429q16.018286 16.018286 27.428571 43.446857t11.410286 50.322286z" p-id="4663"></path></svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1506329916765" class="icon" style="" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1661" xmlns:xlink="http://www.w3.org/1999/xlink" width="64" height="64"><defs><style type="text/css"></style></defs><path d="M64 64 448 64 448 448 64 448 64 64ZM64 576 448 576 448 960 64 960 64 576ZM576 576 960 576 960 960 576 960 576 576ZM768 448C874.038669 448 960 362.038672 960 256 960 149.961328 874.038669 64 768 64 661.961328 64 576 149.961328 576 256 576 362.038672 661.961328 448 768 448Z" p-id="1662"></path></svg>

After

Width:  |  Height:  |  Size: 683 B

View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1509611822979" class="icon" style="" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="10379" xmlns:xlink="http://www.w3.org/1999/xlink" width="32" height="32"><defs><style type="text/css"></style></defs><path d="M219.428571 658.285714q0-30.285714-21.428571-51.714285T146.285714 585.142857t-51.714285 21.428572T73.142857 658.285714t21.428572 51.714286T146.285714 731.428571t51.714286-21.428571T219.428571 658.285714z m109.714286-256q0-30.285714-21.428571-51.714285T256 329.142857t-51.714286 21.428572T182.857143 402.285714t21.428571 51.714286T256 475.428571t51.714286-21.428571T329.142857 402.285714z m244.571429 274.857143l57.714285-218.285714q3.428571-14.857143-4.285714-27.714286T605.142857 414.285714t-27.428571 3.714286-17.142857 22.571429l-57.714286 218.285714q-34.285714 2.857143-61.142857 24.857143t-36 56.285714q-11.428571 44 11.428571 83.428571t66.857143 50.857143 83.428571-11.428571 50.857143-66.857143q9.142857-34.285714-3.428571-66.857143t-41.142857-52z m377.142857-18.857143q0-30.285714-21.428572-51.714285T877.714286 585.142857t-51.714286 21.428572-21.428571 51.714285 21.428571 51.714286 51.714286 21.428571 51.714285-21.428571 21.428572-51.714286z m-365.714286-365.714285q0-30.285714-21.428571-51.714286T512 219.428571t-51.714286 21.428572T438.857143 292.571429t21.428571 51.714285T512 365.714286t51.714286-21.428572T585.142857 292.571429z m256 109.714285q0-30.285714-21.428571-51.714285T768 329.142857t-51.714286 21.428572T694.857143 402.285714t21.428571 51.714286T768 475.428571t51.714286-21.428571T841.142857 402.285714z m182.857143 256q0 149.142857-80.571429 276-10.857143 16.571429-30.857142 16.571429H111.428571q-20 0-30.857142-16.571429Q0 808 0 658.285714q0-104 40.571429-198.857143t109.142857-163.428571 163.428571-109.142857 198.857143-40.571429 198.857143 40.571429 163.428571 109.142857 109.142857 163.428571 40.571429 198.857143z" p-id="10380"></path></svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1510826638494" class="icon" style="" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1669" xmlns:xlink="http://www.w3.org/1999/xlink" width="64" height="64"><defs><style type="text/css"></style></defs><path d="M743.253333 144.184889H374.499556v734.378667H743.253333a92.017778 92.017778 0 0 0 92.16-91.818667V235.975111a91.989333 91.989333 0 0 0-92.16-91.790222z m-0.398222 293.888c0.398222 20.48-1.507556 26.794667-9.756444 26.794667-3.612444 0.597333-9.415111-1.621333-17.863111-8.874667-12.657778-8.931556-21.504-16.753778-29.155556-21.617778-6.798222-5.888-17.550222-5.205333-24.291556 0-8.874667 4.949333-21.532444 15.872-28.814222 21.617778-8.988444 7.907556-15.018667 8.874667-17.180444 8.874667-8.618667 0-10.837333-7.424-10.496-26.794667l-0.312889-223.601778c0-21.162667 8.561778-24.376889 17.265778-24.376889h103.708444c10.552889 0 17.294222 4.835556 17.294222 24.376889l-0.398222 223.601778zM190.122667 235.975111V786.773333a92.046222 92.046222 0 0 0 92.188444 91.818667h46.08V144.184889h-46.08a92.017778 92.017778 0 0 0-92.188444 91.790222z" fill="" p-id="1670"></path></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

1
src/icons/svg/drag.svg Normal file
View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1503994190757" class="icon" style="" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="9007" xmlns:xlink="http://www.w3.org/1999/xlink" width="64" height="64"><defs><style type="text/css"></style></defs><path d="M574.957891 267.016403 511.503696 267.016403l204.64896 0L511.212054 63.654762l-203.361641 203.361641L449.041086 267.016403l0 189.662641L258.687714 456.679044l0 125.916804L449.041086 582.595848l0 190.354396 125.916804 0L574.957891 582.595848l188.874695 0L763.832586 456.679044 574.957891 456.679044 574.957891 267.016403zM511.25401 960.345238l189.620685-187.394994L323.125305 772.950244 511.25401 960.345238zM71.291696 518.891967l187.394994 189.620685L258.68669 330.762239 71.291696 518.891967zM763.832586 330.762239l0 377.74939 188.874695-189.620685L763.832586 330.762239z" p-id="9008"></path></svg>

After

Width:  |  Height:  |  Size: 983 B

1
src/icons/svg/email.svg Normal file
View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1503994210967" class="icon" style="" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="9120" xmlns:xlink="http://www.w3.org/1999/xlink" width="64" height="64"><defs><style type="text/css"></style></defs><path d="M513 583.8l448.5-448.5c-11.6-4.7-24.3-7.3-37.5-7.3L100 128c-12.7 0-24.9 2.4-36.1 6.7L513 583.8z" p-id="9121"></path><path d="M513 674.3 14.6 175.9C5.3 191.1 0 208.9 0 228l0 568c0 55.2 44.8 100 100 100l824 0c55.2 0 100-44.8 100-100l0-568c0-18.5-5.1-35.9-13.9-50.8L513 674.3z" p-id="9122"></path></svg>

After

Width:  |  Height:  |  Size: 684 B

View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1503994811583" class="icon" style="" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="9878" xmlns:xlink="http://www.w3.org/1999/xlink" width="64" height="64"><defs><style type="text/css"></style></defs><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" p-id="9879"></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" p-id="9880"></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" p-id="9881"></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" p-id="9882"></path></svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

Some files were not shown because too many files have changed in this diff Show More