init
This commit is contained in:
parent
304b17520c
commit
d10370086e
145 changed files with 61322 additions and 0 deletions
5
.babelrc
Normal file
5
.babelrc
Normal file
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"presets": ["es2015", "stage-2"],
|
||||
"plugins": ["transform-runtime"],
|
||||
"comments": false
|
||||
}
|
3
.eslintignore
Normal file
3
.eslintignore
Normal file
|
@ -0,0 +1,3 @@
|
|||
build/*.js
|
||||
config/*.js
|
||||
src/assets
|
318
.eslintrc.js
Normal file
318
.eslintrc.js
Normal file
|
@ -0,0 +1,318 @@
|
|||
module.exports = {
|
||||
root: true,
|
||||
parser: 'babel-eslint',
|
||||
parserOptions: {
|
||||
sourceType: 'module'
|
||||
},
|
||||
env: {
|
||||
browser: true,
|
||||
node: true
|
||||
},
|
||||
extends: 'eslint:recommended',
|
||||
// required to lint *.vue files
|
||||
plugins: [
|
||||
'html'
|
||||
],
|
||||
// check if imports actually resolve
|
||||
'settings': {
|
||||
'import/resolver': {
|
||||
'webpack': {
|
||||
'config': 'build/webpack.base.conf.js'
|
||||
}
|
||||
}
|
||||
},
|
||||
// add your custom rules here
|
||||
'rules': {
|
||||
// don't require .vue extension when importing
|
||||
// 'import/extensions': ['error', 'always', {
|
||||
// 'js': 'never',
|
||||
// 'vue': 'never'
|
||||
// }],
|
||||
// allow debugger during development
|
||||
'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0,
|
||||
/*
|
||||
* Possible Errors
|
||||
*/
|
||||
|
||||
// disallow unnecessary parentheses
|
||||
'no-extra-parens': ['error', 'all', {'nestedBinaryExpressions': false}],
|
||||
|
||||
// disallow negating the left operand of relational operators
|
||||
'no-unsafe-negation': 'error',
|
||||
|
||||
// enforce valid JSDoc comments
|
||||
'valid-jsdoc': 'off',
|
||||
|
||||
/*
|
||||
* Best Practices
|
||||
*/
|
||||
|
||||
// enforce return statements in callbacks of array methods
|
||||
'array-callback-return': 'error',
|
||||
|
||||
// enforce consistent brace style for all control statements
|
||||
curly: ['error', 'multi-line'],
|
||||
|
||||
// enforce consistent newlines before and after dots
|
||||
'dot-location': ['error', 'property'],
|
||||
|
||||
// enforce dot notation whenever possible
|
||||
'dot-notation': 'error',
|
||||
|
||||
// require the use of === and !==
|
||||
'eqeqeq': ['error', 'smart'],
|
||||
|
||||
// disallow the use of arguments.caller or arguments.callee
|
||||
'no-caller': 'error',
|
||||
|
||||
// disallow empty functions
|
||||
'no-empty-function': 'error',
|
||||
|
||||
// disallow unnecessary calls to .bind()
|
||||
'no-extra-bind': 'error',
|
||||
|
||||
// disallow unnecessary labels
|
||||
'no-extra-label': 'error',
|
||||
|
||||
// disallow leading or trailing decimal points in numeric literals
|
||||
'no-floating-decimal': 'error',
|
||||
|
||||
// disallow assignments to native objects or read-only global variables
|
||||
'no-global-assign': 'error',
|
||||
|
||||
// disallow the use of eval()-like methods
|
||||
'no-implied-eval': 'error',
|
||||
|
||||
// disallow the use of the __iterator__ property
|
||||
'no-iterator': 'error',
|
||||
|
||||
// disallow unnecessary nested blocks
|
||||
'no-lone-blocks': 'error',
|
||||
|
||||
// disallow multiple spaces
|
||||
'no-multi-spaces': 'error',
|
||||
|
||||
// disallow new operators with the String, Number, and Boolean objects
|
||||
'no-new-wrappers': 'error',
|
||||
|
||||
// disallow octal escape sequences in string literals
|
||||
'no-octal-escape': 'error',
|
||||
|
||||
// disallow the use of the __proto__ property
|
||||
'no-proto': 'error',
|
||||
|
||||
// disallow comparisons where both sides are exactly the same
|
||||
'no-self-compare': 'error',
|
||||
|
||||
// disallow throwing literals as exceptions
|
||||
'no-throw-literal': 'error',
|
||||
|
||||
// disallow unused expressions
|
||||
'no-unused-expressions': 'error',
|
||||
|
||||
// disallow unnecessary calls to .call() and .apply()
|
||||
'no-useless-call': 'error',
|
||||
|
||||
// disallow unnecessary concatenation of literals or template literals
|
||||
'no-useless-concat': 'error',
|
||||
|
||||
// disallow unnecessary escape characters
|
||||
'no-useless-escape': 'error',
|
||||
|
||||
// disallow void operators
|
||||
'no-void': 'error',
|
||||
|
||||
// require parentheses around immediate function invocations
|
||||
'wrap-iife': 'error',
|
||||
|
||||
// require or disallow “Yoda” conditions
|
||||
yoda: 'error',
|
||||
|
||||
/*
|
||||
* Variables
|
||||
*/
|
||||
|
||||
// disallow labels that share a name with a variable
|
||||
'no-label-var': 'error',
|
||||
|
||||
// disallow initializing variables to undefined
|
||||
'no-undef-init': 'error',
|
||||
'no-undef': 'off',
|
||||
// disallow the use of variables before they are defined
|
||||
'no-use-before-define': 'error',
|
||||
|
||||
/*
|
||||
* Node.js and CommonJS
|
||||
*/
|
||||
|
||||
// disallow new operators with calls to require
|
||||
'no-new-require': 'error',
|
||||
|
||||
/*
|
||||
* Stylistic Issues
|
||||
*/
|
||||
|
||||
// enforce consistent spacing inside array brackets
|
||||
'array-bracket-spacing': 'error',
|
||||
|
||||
// enforce consistent spacing inside single-line blocks
|
||||
'block-spacing': 'error',
|
||||
|
||||
// enforce consistent brace style for blocks
|
||||
'brace-style': ['error', '1tbs', {'allowSingleLine': true}],
|
||||
|
||||
// require or disallow trailing commas
|
||||
'comma-dangle': 'error',
|
||||
|
||||
// enforce consistent spacing before and after commas
|
||||
'comma-spacing': 'error',
|
||||
|
||||
// enforce consistent comma style
|
||||
'comma-style': 'error',
|
||||
|
||||
// enforce consistent spacing inside computed property brackets
|
||||
'computed-property-spacing': 'error',
|
||||
|
||||
// require or disallow spacing between function identifiers and their invocations
|
||||
'func-call-spacing': 'error',
|
||||
|
||||
// enforce consistent indentation
|
||||
indent: ['error', 2, {SwitchCase: 1}],
|
||||
|
||||
// enforce the consistent use of either double or single quotes in JSX attributes
|
||||
'jsx-quotes': 'error',
|
||||
|
||||
// enforce consistent spacing between keys and values in object literal properties
|
||||
'key-spacing': 'error',
|
||||
|
||||
// enforce consistent spacing before and after keywords
|
||||
'keyword-spacing': 'error',
|
||||
|
||||
// enforce consistent linebreak style
|
||||
'linebreak-style': 'error',
|
||||
|
||||
// require or disallow newlines around directives
|
||||
'lines-around-directive': 'error',
|
||||
|
||||
// require constructor names to begin with a capital letter
|
||||
'new-cap': 'off',
|
||||
|
||||
// require parentheses when invoking a constructor with no arguments
|
||||
'new-parens': 'error',
|
||||
|
||||
// disallow Array constructors
|
||||
'no-array-constructor': 'error',
|
||||
|
||||
// disallow Object constructors
|
||||
'no-new-object': 'error',
|
||||
|
||||
// disallow trailing whitespace at the end of lines
|
||||
'no-trailing-spaces': 'error',
|
||||
|
||||
// disallow ternary operators when simpler alternatives exist
|
||||
'no-unneeded-ternary': 'error',
|
||||
|
||||
// disallow whitespace before properties
|
||||
'no-whitespace-before-property': 'error',
|
||||
|
||||
// enforce consistent spacing inside braces
|
||||
'object-curly-spacing': ['error', 'always'],
|
||||
|
||||
// require or disallow padding within blocks
|
||||
'padded-blocks': ['error', 'never'],
|
||||
|
||||
// require quotes around object literal property names
|
||||
'quote-props': ['error', 'as-needed'],
|
||||
|
||||
// enforce the consistent use of either backticks, double, or single quotes
|
||||
quotes: ['error', 'single'],
|
||||
|
||||
// enforce consistent spacing before and after semicolons
|
||||
'semi-spacing': 'error',
|
||||
|
||||
// require or disallow semicolons instead of ASI
|
||||
// semi: ['error', 'never'],
|
||||
|
||||
// enforce consistent spacing before blocks
|
||||
'space-before-blocks': 'error',
|
||||
|
||||
'no-console': 'off',
|
||||
|
||||
// enforce consistent spacing before function definition opening parenthesis
|
||||
'space-before-function-paren': ['error', 'never'],
|
||||
|
||||
// enforce consistent spacing inside parentheses
|
||||
'space-in-parens': 'error',
|
||||
|
||||
// require spacing around infix operators
|
||||
'space-infix-ops': 'error',
|
||||
|
||||
// enforce consistent spacing before or after unary operators
|
||||
'space-unary-ops': 'error',
|
||||
|
||||
// enforce consistent spacing after the // or /* in a comment
|
||||
'spaced-comment': 'error',
|
||||
|
||||
// require or disallow Unicode byte order mark (BOM)
|
||||
'unicode-bom': 'error',
|
||||
|
||||
|
||||
/*
|
||||
* ECMAScript 6
|
||||
*/
|
||||
|
||||
// require braces around arrow function bodies
|
||||
'arrow-body-style': 'error',
|
||||
|
||||
// require parentheses around arrow function arguments
|
||||
'arrow-parens': ['error', 'as-needed'],
|
||||
|
||||
// enforce consistent spacing before and after the arrow in arrow functions
|
||||
'arrow-spacing': 'error',
|
||||
|
||||
// enforce consistent spacing around * operators in generator functions
|
||||
'generator-star-spacing': ['error', 'after'],
|
||||
|
||||
// disallow duplicate module imports
|
||||
'no-duplicate-imports': 'error',
|
||||
|
||||
// disallow unnecessary computed property keys in object literals
|
||||
'no-useless-computed-key': 'error',
|
||||
|
||||
// disallow unnecessary constructors
|
||||
'no-useless-constructor': 'error',
|
||||
|
||||
// disallow renaming import, export, and destructured assignments to the same name
|
||||
'no-useless-rename': 'error',
|
||||
|
||||
// require let or const instead of var
|
||||
'no-var': 'error',
|
||||
|
||||
// require or disallow method and property shorthand syntax for object literals
|
||||
'object-shorthand': 'error',
|
||||
|
||||
// require arrow functions as callbacks
|
||||
'prefer-arrow-callback': 'error',
|
||||
|
||||
// require const declarations for variables that are never reassigned after declared
|
||||
'prefer-const': 'error',
|
||||
|
||||
// disallow parseInt() in favor of binary, octal, and hexadecimal literals
|
||||
'prefer-numeric-literals': 'error',
|
||||
|
||||
// require rest parameters instead of arguments
|
||||
'prefer-rest-params': 'error',
|
||||
|
||||
// require spread operators instead of .apply()
|
||||
'prefer-spread': 'error',
|
||||
|
||||
// enforce spacing between rest and spread operators and their expressions
|
||||
'rest-spread-spacing': 'error',
|
||||
|
||||
// require or disallow spacing around embedded expressions of template strings
|
||||
'template-curly-spacing': 'error',
|
||||
|
||||
// require or disallow spacing around the * in yield* expressions
|
||||
'yield-star-spacing': 'error'
|
||||
}
|
||||
}
|
10
.gitignore
vendored
Normal file
10
.gitignore
vendored
Normal file
|
@ -0,0 +1,10 @@
|
|||
.DS_Store
|
||||
node_modules/
|
||||
dist/
|
||||
static/ckeditor
|
||||
|
||||
npm-debug.log
|
||||
test/unit/coverage
|
||||
test/e2e/reports
|
||||
selenium-debug.log
|
||||
.idea
|
42
build/build.js
Normal file
42
build/build.js
Normal file
|
@ -0,0 +1,42 @@
|
|||
require('./check-versions')();
|
||||
var server = require('pushstate-server');
|
||||
var opn = require('opn')
|
||||
var ora = require('ora')
|
||||
var rm = require('rimraf')
|
||||
var path = require('path')
|
||||
var chalk = require('chalk')
|
||||
var webpack = require('webpack');
|
||||
var config = require('../config');
|
||||
var webpackConfig = require('./webpack.prod.conf');
|
||||
|
||||
console.log(process.env.NODE_ENV)
|
||||
console.log(process.env.npm_config_preview)
|
||||
|
||||
var spinner = ora('building for ' + process.env.NODE_ENV + '...')
|
||||
spinner.start()
|
||||
|
||||
|
||||
rm(path.join(config.build.assetsRoot, config.build.assetsSubDirectory), err => {
|
||||
if (err) throw err
|
||||
webpack(webpackConfig, function (err, stats) {
|
||||
spinner.stop()
|
||||
if (err) throw err
|
||||
process.stdout.write(stats.toString({
|
||||
colors: true,
|
||||
modules: false,
|
||||
children: false,
|
||||
chunks: false,
|
||||
chunkModules: false
|
||||
}) + '\n\n')
|
||||
|
||||
console.log(chalk.cyan(' Build complete.\n'))
|
||||
if(process.env.npm_config_preview){
|
||||
server.start({
|
||||
port: 80,
|
||||
directory: './dist',
|
||||
file: '/index.html'
|
||||
});
|
||||
opn('http://kushnerpreview.wallstreetcn.com/')
|
||||
}
|
||||
})
|
||||
})
|
45
build/check-versions.js
Normal file
45
build/check-versions.js
Normal file
|
@ -0,0 +1,45 @@
|
|||
var chalk = require('chalk')
|
||||
var semver = require('semver')
|
||||
var packageConfig = require('../package.json')
|
||||
|
||||
function exec(cmd) {
|
||||
return require('child_process').execSync(cmd).toString().trim()
|
||||
}
|
||||
|
||||
var versionRequirements = [
|
||||
{
|
||||
name: 'node',
|
||||
currentVersion: semver.clean(process.version),
|
||||
versionRequirement: packageConfig.engines.node
|
||||
},
|
||||
{
|
||||
name: 'npm',
|
||||
currentVersion: exec('npm --version'),
|
||||
versionRequirement: packageConfig.engines.npm
|
||||
}
|
||||
]
|
||||
|
||||
module.exports = function () {
|
||||
var warnings = []
|
||||
for (var i = 0; i < versionRequirements.length; i++) {
|
||||
var mod = versionRequirements[i]
|
||||
if (!semver.satisfies(mod.currentVersion, mod.versionRequirement)) {
|
||||
warnings.push(mod.name + ': ' +
|
||||
chalk.red(mod.currentVersion) + ' should be ' +
|
||||
chalk.green(mod.versionRequirement)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (warnings.length) {
|
||||
console.log('')
|
||||
console.log(chalk.yellow('To use this template, you must update following to modules:'))
|
||||
console.log()
|
||||
for (var i = 0; i < warnings.length; i++) {
|
||||
var warning = warnings[i]
|
||||
console.log(' ' + warning)
|
||||
}
|
||||
console.log()
|
||||
process.exit(1)
|
||||
}
|
||||
}
|
9
build/dev-client.js
Normal file
9
build/dev-client.js
Normal file
|
@ -0,0 +1,9 @@
|
|||
/* eslint-disable */
|
||||
require('eventsource-polyfill')
|
||||
var hotClient = require('webpack-hot-middleware/client?noInfo=true&reload=true')
|
||||
|
||||
hotClient.subscribe(function (event) {
|
||||
if (event.action === 'reload') {
|
||||
window.location.reload()
|
||||
}
|
||||
})
|
85
build/dev-server.js
Normal file
85
build/dev-server.js
Normal file
|
@ -0,0 +1,85 @@
|
|||
require('./check-versions')(); // 检查 Node 和 npm 版本
|
||||
var config = require('../config');
|
||||
if (!process.env.NODE_ENV) {
|
||||
process.env.NODE_ENV = config.dev.env;
|
||||
// process.env.NODE_ENV = JSON.parse(config.dev.env.NODE_ENV)
|
||||
}
|
||||
|
||||
var opn = require('opn')
|
||||
var path = require('path');
|
||||
var express = require('express');
|
||||
var webpack = require('webpack');
|
||||
var proxyMiddleware = require('http-proxy-middleware');
|
||||
var webpackConfig = require('./webpack.dev.conf');
|
||||
|
||||
// default port where dev server listens for incoming traffic
|
||||
var port = process.env.PORT || config.dev.port;
|
||||
// automatically open browser, if not set will be false
|
||||
var autoOpenBrowser = !!config.dev.autoOpenBrowser;
|
||||
// Define HTTP proxies to your custom API backend
|
||||
// https://github.com/chimurai/http-proxy-middleware
|
||||
var proxyTable = config.dev.proxyTable;
|
||||
|
||||
var app = express();
|
||||
var compiler = webpack(webpackConfig);
|
||||
|
||||
var devMiddleware = require('webpack-dev-middleware')(compiler, {
|
||||
publicPath: webpackConfig.output.publicPath,
|
||||
quiet: true
|
||||
});
|
||||
|
||||
var hotMiddleware = require('webpack-hot-middleware')(compiler, {
|
||||
log: () => {
|
||||
}
|
||||
});
|
||||
|
||||
// force page reload when html-webpack-plugin template changes
|
||||
compiler.plugin('compilation', function (compilation) {
|
||||
compilation.plugin('html-webpack-plugin-after-emit', function (data, cb) {
|
||||
hotMiddleware.publish({action: 'reload'});
|
||||
cb()
|
||||
})
|
||||
});
|
||||
|
||||
// compiler.apply(new DashboardPlugin());
|
||||
|
||||
// proxy api requests
|
||||
Object.keys(proxyTable).forEach(function (context) {
|
||||
var options = proxyTable[context]
|
||||
if (typeof options === 'string') {
|
||||
options = {target: options}
|
||||
}
|
||||
app.use(proxyMiddleware(options.filter || context, options))
|
||||
});
|
||||
|
||||
// handle fallback for HTML5 history API
|
||||
app.use(require('connect-history-api-fallback')());
|
||||
|
||||
// serve webpack bundle output
|
||||
app.use(devMiddleware);
|
||||
|
||||
// enable hot-reload and state-preserving
|
||||
// compilation error display
|
||||
app.use(hotMiddleware);
|
||||
|
||||
// serve pure static assets
|
||||
var staticPath = path.posix.join(config.dev.assetsPublicPath, config.dev.assetsSubDirectory);
|
||||
app.use(staticPath, express.static('./static'));
|
||||
|
||||
var uri = 'http://localhost:' + port
|
||||
|
||||
devMiddleware.waitUntilValid(function () {
|
||||
console.log('> Listening at ' + uri + '\n')
|
||||
});
|
||||
|
||||
module.exports = app.listen(port, function (err) {
|
||||
if (err) {
|
||||
console.log(err);
|
||||
return
|
||||
}
|
||||
|
||||
// when env is testing, don't need open it
|
||||
if (autoOpenBrowser && process.env.NODE_ENV !== 'testing') {
|
||||
opn(uri)
|
||||
}
|
||||
});
|
71
build/utils.js
Normal file
71
build/utils.js
Normal file
|
@ -0,0 +1,71 @@
|
|||
var path = require('path')
|
||||
var config = require('../config')
|
||||
var ExtractTextPlugin = require('extract-text-webpack-plugin')
|
||||
|
||||
exports.assetsPath = function (_path) {
|
||||
var assetsSubDirectory = process.env.NODE_ENV === 'production'
|
||||
? config.build.assetsSubDirectory
|
||||
: config.dev.assetsSubDirectory
|
||||
return path.posix.join(assetsSubDirectory, _path)
|
||||
}
|
||||
|
||||
exports.cssLoaders = function (options) {
|
||||
options = options || {}
|
||||
|
||||
var cssLoader = {
|
||||
loader: 'css-loader',
|
||||
options: {
|
||||
minimize: process.env.NODE_ENV === 'production',
|
||||
sourceMap: options.sourceMap
|
||||
}
|
||||
}
|
||||
|
||||
// generate loader string to be used with extract text plugin
|
||||
function generateLoaders(loader, loaderOptions) {
|
||||
var loaders = [cssLoader]
|
||||
if (loader) {
|
||||
loaders.push({
|
||||
loader: loader + '-loader',
|
||||
options: Object.assign({}, loaderOptions, {
|
||||
sourceMap: options.sourceMap
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// Extract CSS when that option is specified
|
||||
// (which is the case during production build)
|
||||
if (options.extract) {
|
||||
return ExtractTextPlugin.extract({
|
||||
use: loaders,
|
||||
fallback: 'vue-style-loader'
|
||||
})
|
||||
} else {
|
||||
return ['vue-style-loader'].concat(loaders)
|
||||
}
|
||||
}
|
||||
|
||||
// http://vuejs.github.io/vue-loader/en/configurations/extract-css.html
|
||||
return {
|
||||
css: generateLoaders(),
|
||||
postcss: generateLoaders(),
|
||||
less: generateLoaders('less'),
|
||||
sass: generateLoaders('sass', {indentedSyntax: true}),
|
||||
scss: generateLoaders('sass'),
|
||||
stylus: generateLoaders('stylus'),
|
||||
styl: generateLoaders('stylus')
|
||||
}
|
||||
}
|
||||
|
||||
// Generate loaders for standalone style files (outside of .vue)
|
||||
exports.styleLoaders = function (options) {
|
||||
var output = []
|
||||
var loaders = exports.cssLoaders(options)
|
||||
for (var extension in loaders) {
|
||||
var loader = loaders[extension]
|
||||
output.push({
|
||||
test: new RegExp('\\.' + extension + '$'),
|
||||
use: loader
|
||||
})
|
||||
}
|
||||
return output
|
||||
}
|
12
build/vue-loader.conf.js
Normal file
12
build/vue-loader.conf.js
Normal file
|
@ -0,0 +1,12 @@
|
|||
var utils = require('./utils')
|
||||
var config = require('../config')
|
||||
var isProduction = process.env.NODE_ENV === 'production'
|
||||
|
||||
module.exports = {
|
||||
loaders: utils.cssLoaders({
|
||||
sourceMap: isProduction
|
||||
? config.build.productionSourceMap
|
||||
: config.dev.cssSourceMap,
|
||||
extract: isProduction
|
||||
})
|
||||
}
|
92
build/webpack.base.conf.js
Normal file
92
build/webpack.base.conf.js
Normal file
|
@ -0,0 +1,92 @@
|
|||
var path = require('path');
|
||||
var utils = require('./utils');
|
||||
var config = require('../config');
|
||||
var vueLoaderConfig = require('./vue-loader.conf');
|
||||
|
||||
function resolve(dir) {
|
||||
return path.join(__dirname, '..', dir)
|
||||
}
|
||||
|
||||
var src = path.resolve(__dirname, '../src');
|
||||
var env = process.env.NODE_ENV
|
||||
// check env & config/index.js to decide weither to enable CSS Sourcemaps for the
|
||||
// various preprocessor loaders added to vue-loader at the end of this file
|
||||
var cssSourceMapDev = (env === 'development' && config.dev.cssSourceMap)
|
||||
var cssSourceMapProd = (env === 'production||sit' && config.build.productionSourceMap)
|
||||
var useCssSourceMap = cssSourceMapDev || cssSourceMapProd
|
||||
|
||||
module.exports = {
|
||||
entry: {
|
||||
app: './src/main.js'
|
||||
},
|
||||
output: {
|
||||
path: config.build.assetsRoot,
|
||||
filename: '[name].js',
|
||||
publicPath: process.env.NODE_ENV === 'production||sit' ? config.build.assetsPublicPath : config.dev.assetsPublicPath
|
||||
},
|
||||
resolve: {
|
||||
extensions: ['.js', '.vue', '.json'],
|
||||
alias: {
|
||||
'vue$': 'vue/dist/vue.esm.js',
|
||||
'@': resolve('src'),
|
||||
'src': path.resolve(__dirname, '../src'),
|
||||
'assets': path.resolve(__dirname, '../src/assets'),
|
||||
'components': path.resolve(__dirname, '../src/components'),
|
||||
'views': path.resolve(__dirname, '../src/views'),
|
||||
'styles': path.resolve(__dirname, '../src/styles'),
|
||||
'api': path.resolve(__dirname, '../src/api'),
|
||||
'utils': path.resolve(__dirname, '../src/utils'),
|
||||
'store': path.resolve(__dirname, '../src/store'),
|
||||
'router': path.resolve(__dirname, '../src/router'),
|
||||
'mock': path.resolve(__dirname, '../src/mock'),
|
||||
'vendor': path.resolve(__dirname, '../src/vendor'),
|
||||
'static': path.resolve(__dirname, '../static')
|
||||
}
|
||||
},
|
||||
externals: {
|
||||
jquery: 'jQuery'
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
// {
|
||||
// test: /\.(js|vue)$/,
|
||||
// loader: 'eslint-loader',
|
||||
// enforce: "pre",
|
||||
// include: [resolve('src'), resolve('test')],
|
||||
// options: {
|
||||
// formatter: require('eslint-friendly-formatter')
|
||||
// }
|
||||
// },
|
||||
{ test: /\.vue$/,
|
||||
loader: 'vue-loader',
|
||||
options: vueLoaderConfig
|
||||
},
|
||||
{
|
||||
test: /\.js$/,
|
||||
loader: 'babel-loader?cacheDirectory',
|
||||
include: [resolve('src'), resolve('test')]
|
||||
},
|
||||
{
|
||||
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
|
||||
loader: 'url-loader',
|
||||
query: {
|
||||
limit: 10000,
|
||||
name: utils.assetsPath('img/[name].[hash:7].[ext]')
|
||||
}
|
||||
},
|
||||
{
|
||||
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
|
||||
loader: 'url-loader',
|
||||
query: {
|
||||
limit: 10000,
|
||||
name: utils.assetsPath('fonts/[name].[hash:7].[ext]')
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
//注入全局mixin
|
||||
// sassResources: path.join(__dirname, '../src/styles/mixin.scss'),
|
||||
// sassLoader: {
|
||||
// data: path.join(__dirname, '../src/styles/index.scss')
|
||||
// },
|
||||
}
|
47
build/webpack.dev.conf.js
Normal file
47
build/webpack.dev.conf.js
Normal file
|
@ -0,0 +1,47 @@
|
|||
var utils = require('./utils')
|
||||
var path = require('path')
|
||||
var webpack = require('webpack')
|
||||
var config = require('../config')
|
||||
var merge = require('webpack-merge')
|
||||
var baseWebpackConfig = require('./webpack.base.conf')
|
||||
var HtmlWebpackPlugin = require('html-webpack-plugin')
|
||||
var FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin')
|
||||
|
||||
// add hot-reload related code to entry chunks
|
||||
Object.keys(baseWebpackConfig.entry).forEach(function (name) {
|
||||
baseWebpackConfig.entry[name] = ['./build/dev-client'].concat(baseWebpackConfig.entry[name])
|
||||
})
|
||||
|
||||
function resolveApp(relativePath) {
|
||||
return path.resolve(relativePath);
|
||||
}
|
||||
|
||||
module.exports = merge(baseWebpackConfig, {
|
||||
module: {
|
||||
rules: utils.styleLoaders({sourceMap: config.dev.cssSourceMap})
|
||||
},
|
||||
// cheap-source-map is faster for development
|
||||
devtool: '#cheap-source-map',
|
||||
cache: true,
|
||||
plugins: [
|
||||
new webpack.DefinePlugin({
|
||||
'process.env': config.dev.env
|
||||
}),
|
||||
new webpack.ProvidePlugin({
|
||||
$: 'jquery',
|
||||
'jQuery': 'jquery'
|
||||
}),
|
||||
// https://github.com/glenjamin/webpack-hot-middleware#installation--usage
|
||||
new webpack.HotModuleReplacementPlugin(),
|
||||
new webpack.NoErrorsPlugin(),
|
||||
// https://github.com/ampedandwired/html-webpack-plugin
|
||||
new HtmlWebpackPlugin({
|
||||
filename: 'index.html',
|
||||
template: 'index.html',
|
||||
favicon: resolveApp('favicon.ico'),
|
||||
inject: true,
|
||||
path:config.dev.staticPath
|
||||
}),
|
||||
new FriendlyErrorsPlugin()
|
||||
]
|
||||
})
|
113
build/webpack.prod.conf.js
Normal file
113
build/webpack.prod.conf.js
Normal file
|
@ -0,0 +1,113 @@
|
|||
var path = require('path')
|
||||
var utils = require('./utils')
|
||||
var webpack = require('webpack')
|
||||
var config = require('../config')
|
||||
var merge = require('webpack-merge')
|
||||
var baseWebpackConfig = require('./webpack.base.conf')
|
||||
var CopyWebpackPlugin = require('copy-webpack-plugin')
|
||||
var HtmlWebpackPlugin = require('html-webpack-plugin')
|
||||
var ExtractTextPlugin = require('extract-text-webpack-plugin')
|
||||
var OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin')
|
||||
|
||||
var env = process.env.NODE_ENV === 'production' ? config.build.prodEnv : config.build.sitEnv
|
||||
|
||||
function resolveApp(relativePath) {
|
||||
return path.resolve(relativePath);
|
||||
}
|
||||
|
||||
var webpackConfig = merge(baseWebpackConfig, {
|
||||
module: {
|
||||
rules: utils.styleLoaders({
|
||||
sourceMap: config.build.productionSourceMap,
|
||||
extract: true
|
||||
})
|
||||
},
|
||||
devtool: config.build.productionSourceMap ? '#source-map' : false,
|
||||
output: {
|
||||
path: config.build.assetsRoot,
|
||||
filename: utils.assetsPath('js/[name].[chunkhash].js'),
|
||||
chunkFilename: utils.assetsPath('js/[id].[chunkhash].js')
|
||||
},
|
||||
plugins: [
|
||||
// http://vuejs.github.io/vue-loader/en/workflow/production.html
|
||||
new webpack.DefinePlugin({
|
||||
'process.env': env
|
||||
}),
|
||||
new webpack.optimize.UglifyJsPlugin({
|
||||
compress: {
|
||||
warnings: false
|
||||
},
|
||||
sourceMap: true
|
||||
}),
|
||||
// extract css into its own file
|
||||
new ExtractTextPlugin({
|
||||
filename: utils.assetsPath('css/[name].[contenthash].css')
|
||||
}),
|
||||
// Compress extracted CSS. We are using this plugin so that possible
|
||||
// duplicated CSS from different components can be deduped.
|
||||
new OptimizeCSSPlugin(),
|
||||
// generate dist index.html with correct asset hash for caching.
|
||||
// you can customize output by editing /index.html
|
||||
// see https://github.com/ampedandwired/html-webpack-plugin
|
||||
new HtmlWebpackPlugin({
|
||||
filename: process.env.NODE_ENV === 'testing'
|
||||
? 'index.html'
|
||||
: config.build.index,
|
||||
template: 'index.html',
|
||||
inject: true,
|
||||
favicon: resolveApp('favicon.ico'),
|
||||
minify: {
|
||||
removeComments: true,
|
||||
collapseWhitespace: true,
|
||||
removeRedundantAttributes: true,
|
||||
useShortDoctype: true,
|
||||
removeEmptyAttributes: true,
|
||||
removeStyleLinkTypeAttributes: true,
|
||||
keepClosingSlash: true,
|
||||
minifyJS: true,
|
||||
minifyCSS: true,
|
||||
minifyURLs: true
|
||||
},
|
||||
path:config.build.staticPath,
|
||||
// necessary to consistently work with multiple chunks via CommonsChunkPlugin
|
||||
chunksSortMode: 'dependency'
|
||||
}),
|
||||
// split vendor js into its own file
|
||||
new webpack.optimize.CommonsChunkPlugin({
|
||||
name: 'vendor',
|
||||
minChunks: function (module, count) {
|
||||
// any required modules inside node_modules are extracted to vendor
|
||||
return (
|
||||
module.resource &&
|
||||
/\.js$/.test(module.resource) &&
|
||||
module.resource.indexOf(
|
||||
path.join(__dirname, '../node_modules')
|
||||
) === 0
|
||||
)
|
||||
}
|
||||
}),
|
||||
// extract webpack runtime and module manifest to its own file in order to
|
||||
// prevent vendor hash from being updated whenever app bundle is updated
|
||||
new webpack.optimize.CommonsChunkPlugin({
|
||||
name: 'manifest',
|
||||
chunks: ['vendor']
|
||||
}),
|
||||
// copy custom static assets
|
||||
new CopyWebpackPlugin([
|
||||
{
|
||||
from: path.resolve(__dirname, '../static'),
|
||||
to: config.build.assetsSubDirectory,
|
||||
ignore: ['.*']
|
||||
}
|
||||
]),
|
||||
new webpack.ProvidePlugin({
|
||||
$: 'jquery',
|
||||
'jQuery': 'jquery'
|
||||
})
|
||||
]
|
||||
})
|
||||
if (config.build.bundleAnalyzerReport) {
|
||||
var BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
|
||||
webpackConfig.plugins.push(new BundleAnalyzerPlugin())
|
||||
}
|
||||
module.exports = webpackConfig
|
5
config/dev.env.js
Normal file
5
config/dev.env.js
Normal file
|
@ -0,0 +1,5 @@
|
|||
module.exports = {
|
||||
NODE_ENV: '"development"',
|
||||
BASE_API: '"https://api-dev"',
|
||||
APP_ORIGIN: '"https://wallstreetcn.com"'
|
||||
}
|
41
config/index.js
Normal file
41
config/index.js
Normal file
|
@ -0,0 +1,41 @@
|
|||
// see http://vuejs-templates.github.io/webpack for documentation.
|
||||
var path = require('path')
|
||||
|
||||
module.exports = {
|
||||
build: {
|
||||
sitEnv: require('./sit.env'),
|
||||
prodEnv: require('./prod.env'),
|
||||
index: path.resolve(__dirname, '../dist/index.html'),
|
||||
assetsRoot: path.resolve(__dirname, '../dist'),
|
||||
assetsSubDirectory: '',
|
||||
assetsPublicPath: '/',
|
||||
staticPath:'',
|
||||
productionSourceMap: true,
|
||||
// Gzip off by default as many popular static hosts such as
|
||||
// Surge or Netlify already gzip all static assets for you.
|
||||
// Before setting to `true`, make sure to:
|
||||
// npm install --save-dev compression-webpack-plugin
|
||||
productionGzip: false,
|
||||
productionGzipExtensions: ['js', 'css'],
|
||||
// Run the build command with an extra argument to
|
||||
// View the bundle analyzer report after build finishes:
|
||||
// `npm run build --report`
|
||||
// Set to `true` or `false` to always turn it on or off
|
||||
bundleAnalyzerReport: process.env.npm_config_report
|
||||
},
|
||||
dev: {
|
||||
env: require('./dev.env'),
|
||||
port: 9527,
|
||||
autoOpenBrowser: true,
|
||||
assetsSubDirectory: 'static',
|
||||
staticPath:'/static',
|
||||
assetsPublicPath: '/',
|
||||
proxyTable: {},
|
||||
// CSS Sourcemaps off by default because relative paths are "buggy"
|
||||
// with this option, according to the CSS-Loader README
|
||||
// (https://github.com/webpack/css-loader#sourcemaps)
|
||||
// In our experience, they generally work as expected,
|
||||
// just be aware of this issue when enabling this option.
|
||||
cssSourceMap: false
|
||||
}
|
||||
}
|
5
config/prod.env.js
Normal file
5
config/prod.env.js
Normal file
|
@ -0,0 +1,5 @@
|
|||
module.exports = {
|
||||
NODE_ENV: '"production"',
|
||||
BASE_API: '"https://api-prod',
|
||||
APP_ORIGIN: '"https://wallstreetcn.com"'
|
||||
};
|
5
config/sit.env.js
Normal file
5
config/sit.env.js
Normal file
|
@ -0,0 +1,5 @@
|
|||
module.exports = {
|
||||
NODE_ENV: '"production"',
|
||||
BASE_API: '"https://api-sit"',
|
||||
APP_ORIGIN: '"https://wallstreetcn.com"'
|
||||
};
|
BIN
favicon.ico
Normal file
BIN
favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 66 KiB |
17
index.html
Normal file
17
index.html
Normal file
|
@ -0,0 +1,17 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
|
||||
<meta name="renderer" content="webkit">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
|
||||
<title>Juicy</title>
|
||||
</head>
|
||||
<body>
|
||||
<script src=<%= htmlWebpackPlugin.options.path %>/jquery.min.js></script>
|
||||
<script src=<%= htmlWebpackPlugin.options.path %>/tinymce1.3/tinymce.min.js></script>
|
||||
<div id="app"></div>
|
||||
<!-- built files will be auto injected -->
|
||||
</body>
|
||||
|
||||
</html>
|
93
package.json
Normal file
93
package.json
Normal file
|
@ -0,0 +1,93 @@
|
|||
{
|
||||
"name": "juicy",
|
||||
"version": "1.0.0",
|
||||
"description": "A Vue.js admin",
|
||||
"author": "Pan <panfree23@gmail.com>",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "node build/dev-server.js",
|
||||
"build:prod": "NODE_ENV=production node build/build.js",
|
||||
"build:sit": "NODE_ENV=sit node build/build.js",
|
||||
"build:sit-preview": "NODE_ENV=sit npm_config_preview=true npm_config_report=true node build/build.js",
|
||||
"lint": "eslint --ext .js,.vue src"
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "0.15.3",
|
||||
"codemirror": "5.22.0",
|
||||
"dropzone": "4.3.0",
|
||||
"echarts": "3.4.0",
|
||||
"element-ui": "1.2.7",
|
||||
"file-saver": "1.3.3",
|
||||
"jquery": "3.1.1",
|
||||
"js-cookie": "2.1.3",
|
||||
"jsonlint": "1.6.2",
|
||||
"normalize.css": "3.0.2",
|
||||
"nprogress": "0.2.0",
|
||||
"showdown": "1.6.4",
|
||||
"simplemde": "1.11.2",
|
||||
"vue": "2.2.6",
|
||||
"vue-multiselect": "2.0.0-beta.14",
|
||||
"vue-router": "2.3.0",
|
||||
"vuedraggable": "2.8.4",
|
||||
"vuex": "2.2.1",
|
||||
"xlsx": "0.8.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"autoprefixer": "6.7.2",
|
||||
"babel-core": "6.22.1",
|
||||
"babel-eslint": "7.1.1",
|
||||
"babel-loader": "6.2.10",
|
||||
"babel-plugin-transform-runtime": "6.22.0",
|
||||
"babel-preset-es2015": "6.22.0",
|
||||
"babel-preset-stage-2": "6.22.0",
|
||||
"babel-register": "6.22.0",
|
||||
"chalk": "1.1.3",
|
||||
"connect-history-api-fallback": "1.3.0",
|
||||
"copy-webpack-plugin": "4.0.1",
|
||||
"css-loader": "0.26.1",
|
||||
"eslint": "3.14.1",
|
||||
"eslint-friendly-formatter": "2.0.7",
|
||||
"eslint-loader": "1.6.1",
|
||||
"eslint-plugin-html": "2.0.0",
|
||||
"eslint-config-airbnb-base": "11.0.1",
|
||||
"eslint-import-resolver-webpack": "0.8.1",
|
||||
"eslint-plugin-import": "2.2.0",
|
||||
"eventsource-polyfill": "0.9.6",
|
||||
"express": "4.14.1",
|
||||
"extract-text-webpack-plugin": "2.0.0",
|
||||
"file-loader": "0.10.0",
|
||||
"friendly-errors-webpack-plugin": "^1.1.3",
|
||||
"function-bind": "1.1.0",
|
||||
"html-webpack-plugin": "2.28.0",
|
||||
"http-proxy-middleware": "0.17.3",
|
||||
"webpack-bundle-analyzer": "2.2.1",
|
||||
"semver": "5.3.0",
|
||||
"opn": "4.0.2",
|
||||
"optimize-css-assets-webpack-plugin": "1.3.0",
|
||||
"ora": "1.1.0",
|
||||
"rimraf": "2.6.0",
|
||||
"url-loader": "0.5.7",
|
||||
"vue-loader": "11.3.4",
|
||||
"vue-style-loader": "2.0.0",
|
||||
"vue-template-compiler": "2.2.6",
|
||||
"webpack": "2.2.1",
|
||||
"webpack-dev-middleware": "1.10.0",
|
||||
"webpack-hot-middleware": "2.16.1",
|
||||
"webpack-merge": "2.6.1",
|
||||
"webpack-dashboard": "0.2.1",
|
||||
"node-sass": "3.7.0",
|
||||
"pushstate-server": "2.1.0",
|
||||
"sass-loader": "4.0.2",
|
||||
"script-loader": "0.7.0",
|
||||
"style-loader": "0.13.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 4.0.0",
|
||||
"npm": ">= 3.0.0"
|
||||
},
|
||||
"browserlist": [
|
||||
"> 1%",
|
||||
"last 2 versions",
|
||||
"not ie <= 8"
|
||||
]
|
||||
}
|
11
src/App.vue
Normal file
11
src/App.vue
Normal file
|
@ -0,0 +1,11 @@
|
|||
<template>
|
||||
<div id="app">
|
||||
<router-view></router-view>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default{
|
||||
name: 'APP'
|
||||
}
|
||||
</script>
|
28
src/api/qiniu.js
Normal file
28
src/api/qiniu.js
Normal file
|
@ -0,0 +1,28 @@
|
|||
import fetch, { tpFetch } from 'utils/fetch';
|
||||
|
||||
export function getToken() {
|
||||
return fetch({
|
||||
url: '/qiniu/upload/token',
|
||||
method: 'get'
|
||||
});
|
||||
}
|
||||
export function upload(data) {
|
||||
return tpFetch({
|
||||
url: 'https://upload.qbox.me',
|
||||
method: 'post',
|
||||
data
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/* 外部uri转七牛uri*/
|
||||
export function netUpload(token, net_url) {
|
||||
const imgData = {
|
||||
net_url
|
||||
};
|
||||
return fetch({
|
||||
url: '/qiniu/upload/net/async',
|
||||
method: 'post',
|
||||
data: imgData
|
||||
});
|
||||
}
|
BIN
src/assets/401.gif
Normal file
BIN
src/assets/401.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 160 KiB |
BIN
src/assets/compbig.gif
Normal file
BIN
src/assets/compbig.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 793 KiB |
BIN
src/assets/custom-theme/fonts/element-icons.ttf
Normal file
BIN
src/assets/custom-theme/fonts/element-icons.ttf
Normal file
Binary file not shown.
BIN
src/assets/custom-theme/fonts/element-icons.woff
Normal file
BIN
src/assets/custom-theme/fonts/element-icons.woff
Normal file
Binary file not shown.
23959
src/assets/custom-theme/index.css
Normal file
23959
src/assets/custom-theme/index.css
Normal file
File diff suppressed because it is too large
Load diff
126
src/assets/iconfont/iconfont.js
Normal file
126
src/assets/iconfont/iconfont.js
Normal file
|
@ -0,0 +1,126 @@
|
|||
;(function(window) {
|
||||
|
||||
var svgSprite = '<svg>' +
|
||||
'' +
|
||||
'<symbol id="icon-zujian" viewBox="0 0 1024 1024">' +
|
||||
'' +
|
||||
'<path d="M568.6 0h454.9v454.9H568.6V0z m0 568.6h454.9v454.9H568.6V568.6zM0 568.6h454.9v454.9H0V568.6zM0 0h454.9v454.9H0V0z" fill="" ></path>' +
|
||||
'' +
|
||||
'</symbol>' +
|
||||
'' +
|
||||
'</svg>'
|
||||
var script = function() {
|
||||
var scripts = document.getElementsByTagName('script')
|
||||
return scripts[scripts.length - 1]
|
||||
}()
|
||||
var shouldInjectCss = script.getAttribute("data-injectcss")
|
||||
|
||||
/**
|
||||
* document ready
|
||||
*/
|
||||
var ready = function(fn) {
|
||||
if (document.addEventListener) {
|
||||
if (~["complete", "loaded", "interactive"].indexOf(document.readyState)) {
|
||||
setTimeout(fn, 0)
|
||||
} else {
|
||||
var loadFn = function() {
|
||||
document.removeEventListener("DOMContentLoaded", loadFn, false)
|
||||
fn()
|
||||
}
|
||||
document.addEventListener("DOMContentLoaded", loadFn, false)
|
||||
}
|
||||
} else if (document.attachEvent) {
|
||||
IEContentLoaded(window, fn)
|
||||
}
|
||||
|
||||
function IEContentLoaded(w, fn) {
|
||||
var d = w.document,
|
||||
done = false,
|
||||
// only fire once
|
||||
init = function() {
|
||||
if (!done) {
|
||||
done = true
|
||||
fn()
|
||||
}
|
||||
}
|
||||
// polling for no errors
|
||||
var polling = function() {
|
||||
try {
|
||||
// throws errors until after ondocumentready
|
||||
d.documentElement.doScroll('left')
|
||||
} catch (e) {
|
||||
setTimeout(polling, 50)
|
||||
return
|
||||
}
|
||||
// no errors, fire
|
||||
|
||||
init()
|
||||
};
|
||||
|
||||
polling()
|
||||
// trying to always fire before onload
|
||||
d.onreadystatechange = function() {
|
||||
if (d.readyState == 'complete') {
|
||||
d.onreadystatechange = null
|
||||
init()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert el before target
|
||||
*
|
||||
* @param {Element} el
|
||||
* @param {Element} target
|
||||
*/
|
||||
|
||||
var before = function(el, target) {
|
||||
target.parentNode.insertBefore(el, target)
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepend el to target
|
||||
*
|
||||
* @param {Element} el
|
||||
* @param {Element} target
|
||||
*/
|
||||
|
||||
var prepend = function(el, target) {
|
||||
if (target.firstChild) {
|
||||
before(el, target.firstChild)
|
||||
} else {
|
||||
target.appendChild(el)
|
||||
}
|
||||
}
|
||||
|
||||
function appendSvg() {
|
||||
var div, svg
|
||||
|
||||
div = document.createElement('div')
|
||||
div.innerHTML = svgSprite
|
||||
svgSprite = null
|
||||
svg = div.getElementsByTagName('svg')[0]
|
||||
if (svg) {
|
||||
svg.setAttribute('aria-hidden', 'true')
|
||||
svg.style.position = 'absolute'
|
||||
svg.style.width = 0
|
||||
svg.style.height = 0
|
||||
svg.style.overflow = 'hidden'
|
||||
prepend(svg, document.body)
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldInjectCss && !window.__iconfont__svg__cssinject__) {
|
||||
window.__iconfont__svg__cssinject__ = true
|
||||
try {
|
||||
document.write("<style>.svgfont {display: inline-block;width: 1em;height: 1em;fill: currentColor;vertical-align: -0.1em;font-size:16px;}</style>");
|
||||
} catch (e) {
|
||||
console && console.log(e)
|
||||
}
|
||||
}
|
||||
|
||||
ready(appendSvg)
|
||||
|
||||
|
||||
})(window)
|
103
src/components/Charts/barPercent.vue
Normal file
103
src/components/Charts/barPercent.vue
Normal file
|
@ -0,0 +1,103 @@
|
|||
<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));
|
||||
},
|
||||
setOptions() {
|
||||
this.chart.setOption({
|
||||
tooltip: {
|
||||
show: true,
|
||||
formatter(params) {
|
||||
return '已完成' + params.value + '篇<br/>目标90篇<br/>完成进度' + Math.round((params.value / 90) * 100) + '%'
|
||||
}
|
||||
},
|
||||
grid: {
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
top: 0,
|
||||
containLabel: false
|
||||
},
|
||||
xAxis: [{
|
||||
type: 'category',
|
||||
data: ['文章完成比例']
|
||||
}],
|
||||
yAxis: [{
|
||||
type: 'value',
|
||||
data: [],
|
||||
show: false
|
||||
}],
|
||||
animationDelay: 1000,
|
||||
series: [{
|
||||
type: 'bar',
|
||||
name: '初诊',
|
||||
itemStyle: {
|
||||
normal: {
|
||||
color: '#e5e5e5'
|
||||
}
|
||||
},
|
||||
silent: true,
|
||||
barGap: '-100%', // Make series be overlap
|
||||
data: [150]
|
||||
}, {
|
||||
type: 'bar',
|
||||
name: 'app',
|
||||
itemStyle: {
|
||||
normal: {
|
||||
color: '#30b08f'
|
||||
}
|
||||
},
|
||||
z: 10,
|
||||
data: [this.dataNum]
|
||||
}]
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
113
src/components/Charts/keyboard.vue
Normal file
113
src/components/Charts/keyboard.vue
Normal file
|
@ -0,0 +1,113 @@
|
|||
<template>
|
||||
<div :class="className" :id="id" :style="{height:height,width:width}"></div>
|
||||
</template>
|
||||
<script>
|
||||
// 引入 ECharts 主模块
|
||||
const echarts = require('echarts/lib/echarts');
|
||||
// 引入柱状图
|
||||
require('echarts/lib/chart/bar');
|
||||
require('echarts/lib/chart/line');
|
||||
// 引入提示框和标题组件
|
||||
require('echarts/lib/component/tooltip');
|
||||
require('echarts/lib/component/title');
|
||||
|
||||
require('echarts/lib/component/visualMap');
|
||||
export default {
|
||||
name: 'barPercent',
|
||||
props: {
|
||||
className: {
|
||||
type: String,
|
||||
default: 'bar-percent-chart'
|
||||
},
|
||||
id: {
|
||||
type: String,
|
||||
default: 'bar-percent-chart'
|
||||
},
|
||||
width: {
|
||||
type: String,
|
||||
default: '200px'
|
||||
},
|
||||
height: {
|
||||
type: String,
|
||||
default: '200px'
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {};
|
||||
},
|
||||
mounted() {
|
||||
this.initBar();
|
||||
},
|
||||
methods: {
|
||||
initBar() {
|
||||
this.chart = echarts.init(document.getElementById(this.id));
|
||||
|
||||
const xAxisData = [];
|
||||
const data = [];
|
||||
for (let i = 0; i < 30; i++) {
|
||||
xAxisData.push(i + '号');
|
||||
data.push(Math.round(Math.random() * 2 + 3))
|
||||
}
|
||||
|
||||
this.chart.setOption(
|
||||
{
|
||||
backgroundColor: '#08263a',
|
||||
tooltip: {
|
||||
trigger: 'axis'
|
||||
},
|
||||
xAxis: {
|
||||
show: false,
|
||||
data: xAxisData
|
||||
},
|
||||
visualMap: {
|
||||
show: false,
|
||||
min: 0,
|
||||
max: 50,
|
||||
dimension: 0,
|
||||
inRange: {
|
||||
color: ['#4a657a', '#308e92', '#b1cfa5', '#f5d69f', '#f5898b', '#ef5055']
|
||||
}
|
||||
},
|
||||
yAxis: {
|
||||
axisLine: {
|
||||
show: false
|
||||
},
|
||||
axisLabel: {
|
||||
textStyle: {
|
||||
color: '#4a657a'
|
||||
}
|
||||
},
|
||||
splitLine: {
|
||||
show: true,
|
||||
lineStyle: {
|
||||
color: '#08263f'
|
||||
}
|
||||
},
|
||||
axisTick: {}
|
||||
},
|
||||
series: [{
|
||||
type: 'bar',
|
||||
data,
|
||||
name: '撸文数',
|
||||
itemStyle: {
|
||||
normal: {
|
||||
barBorderRadius: 5,
|
||||
shadowBlur: 10,
|
||||
shadowColor: '#111'
|
||||
}
|
||||
},
|
||||
animationEasing: 'elasticOut',
|
||||
animationEasingUpdate: 'elasticOut',
|
||||
animationDelay(idx) {
|
||||
return idx * 20;
|
||||
},
|
||||
animationDelayUpdate(idx) {
|
||||
return idx * 20;
|
||||
}
|
||||
}]
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
145
src/components/Charts/line.vue
Normal file
145
src/components/Charts/line.vue
Normal file
|
@ -0,0 +1,145 @@
|
|||
<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));
|
||||
},
|
||||
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>
|
291
src/components/Dropzone/index.vue
Normal file
291
src/components/Dropzone/index.vue
Normal file
|
@ -0,0 +1,291 @@
|
|||
<template>
|
||||
<div :ref="id" :action="url" class="dropzone" :id="id">
|
||||
<input type="file" name="file">
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import Dropzone from 'dropzone';
|
||||
import 'dropzone/dist/dropzone.css';
|
||||
import { getToken } from 'api/qiniu';
|
||||
|
||||
Dropzone.autoDiscover = false;
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
dropzone: '',
|
||||
initOnce: true
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
const element = document.getElementById(this.id);
|
||||
const vm = this;
|
||||
this.dropzone = new Dropzone(element, {
|
||||
clickable: this.clickable,
|
||||
thumbnailWidth: this.thumbnailWidth,
|
||||
thumbnailHeight: this.thumbnailHeight,
|
||||
maxFiles: this.maxFiles,
|
||||
maxFilesize: this.maxFilesize,
|
||||
dictRemoveFile: 'Remove',
|
||||
addRemoveLinks: this.showRemoveLink,
|
||||
acceptedFiles: this.acceptedFiles,
|
||||
autoProcessQueue: this.autoProcessQueue,
|
||||
dictDefaultMessage: '<i style="margin-top: 3em;display: inline-block" class="material-icons">' + this.defaultMsg + '</i><br>Drop files here to upload',
|
||||
dictMaxFilesExceeded: '只能一个图',
|
||||
previewTemplate: '<div class="dz-preview dz-file-preview"> <div class="dz-image" style="width:' + this.thumbnailWidth + 'px;height:' + this.thumbnailHeight + 'px" ><img style="width:' + this.thumbnailWidth + 'px;height:' + this.thumbnailHeight + 'px" data-dz-thumbnail /></div> <div class="dz-details"><div class="dz-size"><span data-dz-size></span></div> <div class="dz-progress"><span class="dz-upload" data-dz-uploadprogress></span></div> <div class="dz-error-message"><span data-dz-errormessage></span></div> <div class="dz-success-mark"> <i class="material-icons">done</i> </div> <div class="dz-error-mark"><i class="material-icons">error</i></div></div>',
|
||||
init() {
|
||||
const val = vm.defaultImg;
|
||||
if (!val) return;
|
||||
if (Array.isArray(val)) {
|
||||
if (val.length === 0) return;
|
||||
val.map((v, i) => {
|
||||
const mockFile = { name: 'name' + i, size: 12345, url: v };
|
||||
this.options.addedfile.call(this, mockFile);
|
||||
this.options.thumbnail.call(this, mockFile, v);
|
||||
mockFile.previewElement.classList.add('dz-success');
|
||||
mockFile.previewElement.classList.add('dz-complete');
|
||||
vm.initOnce = false;
|
||||
return true;
|
||||
})
|
||||
} else {
|
||||
const mockFile = { name: 'name', size: 12345, url: val };
|
||||
this.options.addedfile.call(this, mockFile);
|
||||
this.options.thumbnail.call(this, mockFile, val);
|
||||
mockFile.previewElement.classList.add('dz-success');
|
||||
mockFile.previewElement.classList.add('dz-complete');
|
||||
vm.initOnce = false;
|
||||
}
|
||||
},
|
||||
accept: (file, done) => {
|
||||
const token = this.$store.getters.token;
|
||||
getToken(token).then(response => {
|
||||
file.token = response.data.qiniu_token;
|
||||
file.key = response.data.qiniu_key;
|
||||
file.url = response.data.qiniu_url;
|
||||
done();
|
||||
})
|
||||
},
|
||||
sending: (file, xhr, formData) => {
|
||||
formData.append('token', file.token);
|
||||
formData.append('key', file.key);
|
||||
vm.initOnce = false;
|
||||
}
|
||||
});
|
||||
|
||||
if (this.couldPaste) {
|
||||
document.addEventListener('paste', this.pasteImg)
|
||||
}
|
||||
|
||||
this.dropzone.on('success', file => {
|
||||
vm.$emit('dropzone-success', file, vm.dropzone.element)
|
||||
});
|
||||
this.dropzone.on('addedfile', file => {
|
||||
vm.$emit('dropzone-fileAdded', file)
|
||||
});
|
||||
this.dropzone.on('removedfile', file => {
|
||||
vm.$emit('dropzone-removedFile', file)
|
||||
});
|
||||
this.dropzone.on('error', (file, error, xhr) => {
|
||||
vm.$emit('dropzone-error', file, error, xhr)
|
||||
});
|
||||
this.dropzone.on('successmultiple', (file, error, xhr) => {
|
||||
vm.$emit('dropzone-successmultiple', file, error, xhr)
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
removeAllFiles() {
|
||||
this.dropzone.removeAllFiles(true)
|
||||
},
|
||||
processQueue() {
|
||||
this.dropzone.processQueue()
|
||||
},
|
||||
pasteImg(event) {
|
||||
const items = (event.clipboardData || event.originalEvent.clipboardData).items;
|
||||
if (items[0].kind === 'file') {
|
||||
this.dropzone.addFile(items[0].getAsFile())
|
||||
}
|
||||
},
|
||||
initImages(val) {
|
||||
if (!val) return;
|
||||
if (Array.isArray(val)) {
|
||||
val.map((v, i) => {
|
||||
const mockFile = { name: 'name' + i, size: 12345, url: v };
|
||||
this.dropzone.options.addedfile.call(this.dropzone, mockFile);
|
||||
this.dropzone.options.thumbnail.call(this.dropzone, mockFile, v);
|
||||
mockFile.previewElement.classList.add('dz-success');
|
||||
mockFile.previewElement.classList.add('dz-complete');
|
||||
return true
|
||||
})
|
||||
} else {
|
||||
const mockFile = { name: 'name', size: 12345, url: val };
|
||||
this.dropzone.options.addedfile.call(this.dropzone, mockFile);
|
||||
this.dropzone.options.thumbnail.call(this.dropzone, mockFile, val);
|
||||
mockFile.previewElement.classList.add('dz-success');
|
||||
mockFile.previewElement.classList.add('dz-complete');
|
||||
}
|
||||
}
|
||||
|
||||
},
|
||||
destroyed() {
|
||||
document.removeEventListener('paste', this.pasteImg);
|
||||
this.dropzone.destroy();
|
||||
},
|
||||
watch: {
|
||||
defaultImg(val) {
|
||||
if (val.length === 0) {
|
||||
this.initOnce = false;
|
||||
return;
|
||||
}
|
||||
if (!this.initOnce) return;
|
||||
this.initImages(val);
|
||||
this.initOnce = false;
|
||||
}
|
||||
},
|
||||
props: {
|
||||
id: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
url: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
clickable: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
defaultMsg: {
|
||||
type: String,
|
||||
default: '上传图片'
|
||||
},
|
||||
acceptedFiles: {
|
||||
type: String
|
||||
},
|
||||
thumbnailHeight: {
|
||||
type: Number,
|
||||
default: 200
|
||||
},
|
||||
thumbnailWidth: {
|
||||
type: Number,
|
||||
default: 200
|
||||
},
|
||||
showRemoveLink: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
maxFilesize: {
|
||||
type: Number,
|
||||
default: 2
|
||||
},
|
||||
maxFiles: {
|
||||
type: Number,
|
||||
default: 3
|
||||
},
|
||||
autoProcessQueue: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
useCustomDropzoneOptions: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
defaultImg: {
|
||||
default: false
|
||||
},
|
||||
couldPaste: {
|
||||
default: false
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.dropzone {
|
||||
border: 2px solid #E5E5E5;
|
||||
font-family: 'Roboto', sans-serif;
|
||||
color: #777;
|
||||
transition: background-color .2s linear;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.dropzone:hover {
|
||||
background-color: #F6F6F6;
|
||||
}
|
||||
|
||||
i {
|
||||
color: #CCC;
|
||||
}
|
||||
|
||||
.dropzone .dz-image img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.dropzone input[name='file'] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.dropzone .dz-preview .dz-image {
|
||||
border-radius: 0px;
|
||||
}
|
||||
|
||||
.dropzone .dz-preview:hover .dz-image img {
|
||||
transform: none;
|
||||
-webkit-filter: none;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.dropzone .dz-preview .dz-details {
|
||||
bottom: 0px;
|
||||
top: 0px;
|
||||
color: white;
|
||||
background-color: rgba(33, 150, 243, 0.8);
|
||||
transition: opacity .2s linear;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.dropzone .dz-preview .dz-details .dz-filename span, .dropzone .dz-preview .dz-details .dz-size span {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.dropzone .dz-preview .dz-details .dz-filename:not(:hover) span {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.dropzone .dz-preview .dz-details .dz-filename:hover span {
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.dropzone .dz-preview .dz-remove {
|
||||
position: absolute;
|
||||
z-index: 30;
|
||||
color: white;
|
||||
margin-left: 15px;
|
||||
padding: 10px;
|
||||
top: inherit;
|
||||
bottom: 15px;
|
||||
border: 2px white solid;
|
||||
text-decoration: none;
|
||||
text-transform: uppercase;
|
||||
font-size: 0.8rem;
|
||||
font-weight: 800;
|
||||
letter-spacing: 1.1px;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.dropzone .dz-preview:hover .dz-remove {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.dropzone .dz-preview .dz-success-mark, .dropzone .dz-preview .dz-error-mark {
|
||||
margin-left: -40px;
|
||||
margin-top: -50px;
|
||||
}
|
||||
|
||||
.dropzone .dz-preview .dz-success-mark i, .dropzone .dz-preview .dz-error-mark i {
|
||||
color: white;
|
||||
font-size: 5rem;
|
||||
}
|
||||
</style>
|
43
src/components/ErrLog/index.vue
Normal file
43
src/components/ErrLog/index.vue
Normal file
|
@ -0,0 +1,43 @@
|
|||
<template>
|
||||
<div>
|
||||
<el-badge :is-dot="true" style="line-height: 30px;" @click.native="dialogTableVisible=true">
|
||||
<el-button size="small" type="primary">
|
||||
<wscn-icon-svg icon-class="bug" class="meta-item__icon"/>
|
||||
</el-button>
|
||||
</el-badge>
|
||||
<el-dialog title="bug日志" v-model="dialogTableVisible">
|
||||
<el-table :data="logsList">
|
||||
<el-table-column label="message">
|
||||
<template scope="scope">
|
||||
<div>msg:{{ scope.row.err.message }}</div>
|
||||
<br/>
|
||||
<div>url: {{scope.row.url}}</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="stack">
|
||||
<template scope="scope">
|
||||
{{ scope.row.err.stack}}
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
</el-table>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'errLog',
|
||||
props: {
|
||||
logsList: {
|
||||
type: Array
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
dialogTableVisible: false
|
||||
}
|
||||
},
|
||||
methods: {}
|
||||
}
|
||||
</script>
|
38
src/components/Hamburger/index.vue
Normal file
38
src/components/Hamburger/index.vue
Normal file
|
@ -0,0 +1,38 @@
|
|||
<template>
|
||||
<div>
|
||||
<svg @click="toggleClick" class="wscn-icon hamburger" :class="{'is-active':isActive}" aria-hidden="true">
|
||||
<use xlink:href="#icon-hamburger"></use>
|
||||
</svg>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'hamburger',
|
||||
props: {
|
||||
isActive: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
toggleClick: {
|
||||
type: Function,
|
||||
default: null
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.hamburger {
|
||||
display: inline-block;
|
||||
cursor: pointer;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
transform: rotate(0deg);
|
||||
transition: .38s;
|
||||
transform-origin: 50% 50%;
|
||||
}
|
||||
.hamburger.is-active {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
</style>
|
11
src/components/Icon-svg/index.js
Normal file
11
src/components/Icon-svg/index.js
Normal file
|
@ -0,0 +1,11 @@
|
|||
import Vue from 'vue'
|
||||
|
||||
function registerAllComponents(requireContext) {
|
||||
return requireContext.keys().forEach(comp => {
|
||||
const vueComp = requireContext(comp)
|
||||
const compName = vueComp.name ? vueComp.name.toLowerCase() : /\/([\w-]+)\.vue$/.exec(comp)[1]
|
||||
Vue.component(compName, vueComp)
|
||||
})
|
||||
}
|
||||
|
||||
registerAllComponents(require.context('./', false, /\.vue$/))
|
52
src/components/Icon-svg/wscn-icon-stack.vue
Normal file
52
src/components/Icon-svg/wscn-icon-stack.vue
Normal file
|
@ -0,0 +1,52 @@
|
|||
<template>
|
||||
<div class="icon-container" :style="containerStyle">
|
||||
<slot class="icon"></slot>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'wscn-icon-stack',
|
||||
props: {
|
||||
width: {
|
||||
type: Number,
|
||||
default: 20
|
||||
},
|
||||
shape: {
|
||||
type: String,
|
||||
default: 'circle',
|
||||
validator: val => {
|
||||
const validShapes = ['circle', 'square']
|
||||
return validShapes.indexOf(val) > -1
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
containerStyle() {
|
||||
return {
|
||||
width: `${this.width}px`,
|
||||
height: `${this.width}px`,
|
||||
fontSize: `${this.width * 0.6}px`,
|
||||
borderRadius: `${this.shape === 'circle' && '50%'}`
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.icon-container {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
background: #1482F0;
|
||||
|
||||
.icon {
|
||||
position: absolute;
|
||||
color: #ffffff;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
}
|
||||
</style>
|
26
src/components/Icon-svg/wscn-icon-svg.vue
Normal file
26
src/components/Icon-svg/wscn-icon-svg.vue
Normal file
|
@ -0,0 +1,26 @@
|
|||
<template>
|
||||
<svg class="wscn-icon" aria-hidden="true">
|
||||
<use :xlink:href="iconName"></use>
|
||||
</svg>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'wscn-icon-svg',
|
||||
props: {
|
||||
iconClass: {
|
||||
type: String,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
iconName() {
|
||||
return `#icon-${this.iconClass}`
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
</style>
|
676
src/components/ImageCropper/index.vue
Normal file
676
src/components/ImageCropper/index.vue
Normal file
|
@ -0,0 +1,676 @@
|
|||
<template>
|
||||
<div class="vue-image-crop-upload" v-show="show">
|
||||
<div class="vicp-wrap">
|
||||
<div class="vicp-close" @click="off">
|
||||
<i class="vicp-icon4"></i>
|
||||
</div>
|
||||
|
||||
<div class="vicp-step1" v-show="step == 1">
|
||||
<div class="vicp-drop-area"
|
||||
@dragleave="preventDefault"
|
||||
@dragover="preventDefault"
|
||||
@dragenter="preventDefault"
|
||||
@click="handleClick"
|
||||
@drop="handleChange">
|
||||
<i class="vicp-icon1" v-show="loading != 1">
|
||||
<i class="vicp-icon1-arrow"></i>
|
||||
<i class="vicp-icon1-body"></i>
|
||||
<i class="vicp-icon1-bottom"></i>
|
||||
</i>
|
||||
<span class="vicp-hint" v-show="loading !== 1">{{ lang.hint }}</span>
|
||||
<span class="vicp-no-supported-hint" v-show="!isSupported">{{ lang.noSupported }}</span>
|
||||
<input type="file" v-show="false" @change="handleChange" ref="fileinput">
|
||||
</div>
|
||||
<div class="vicp-error" v-show="hasError">
|
||||
<i class="vicp-icon2"></i> {{ errorMsg }}
|
||||
</div>
|
||||
<div class="vicp-operate">
|
||||
<a @click="off" @mousedown="ripple">{{ lang.btn.off }}</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="vicp-step2" v-if="step == 2">
|
||||
<div class="vicp-crop">
|
||||
<div class="vicp-crop-left" v-show="true">
|
||||
<div class="vicp-img-container">
|
||||
<img :src="sourceImgUrl"
|
||||
:style="sourceImgStyle"
|
||||
class="vicp-img"
|
||||
draggable="false"
|
||||
@drag="preventDefault"
|
||||
@dragstart="preventDefault"
|
||||
@dragend="preventDefault"
|
||||
@dragleave="preventDefault"
|
||||
@dragover="preventDefault"
|
||||
@dragenter="preventDefault"
|
||||
@drop="preventDefault"
|
||||
@mousedown="imgStartMove"
|
||||
@mousemove="imgMove"
|
||||
@mouseup="createImg"
|
||||
@mouseout="createImg"
|
||||
ref="img">
|
||||
<div class="vicp-img-shade vicp-img-shade-1" :style="sourceImgShadeStyle"></div>
|
||||
<div class="vicp-img-shade vicp-img-shade-2" :style="sourceImgShadeStyle"></div>
|
||||
</div>
|
||||
<div class="vicp-range">
|
||||
<input type="range" :value="scale.range" step="1" min="0" max="100" @change="zoomChange">
|
||||
<i @mousedown="startZoomSub" @mouseout="endZoomSub" @mouseup="endZoomSub"
|
||||
class="vicp-icon5"></i>
|
||||
<i @mousedown="startZoomAdd" @mouseout="endZoomAdd" @mouseup="endZoomAdd"
|
||||
class="vicp-icon6"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="vicp-crop-right" v-show="true">
|
||||
<div class="vicp-preview">
|
||||
<div class="vicp-preview-item">
|
||||
<img :src="createImgUrl" :style="previewStyle">
|
||||
<span>{{ lang.preview }}</span>
|
||||
</div>
|
||||
<div class="vicp-preview-item">
|
||||
<img :src="createImgUrl" :style="previewStyle" v-if="!noCircle">
|
||||
<span>{{ lang.preview }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="vicp-operate">
|
||||
<a @click="setStep(1)" @mousedown="ripple">{{ lang.btn.back }}</a>
|
||||
<a class="vicp-operate-btn" @click="upload" @mousedown="ripple">{{ lang.btn.save }}</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="vicp-step3" v-if="step == 3">
|
||||
<div class="vicp-upload">
|
||||
<span class="vicp-loading" v-show="loading === 1">{{ lang.loading }}</span>
|
||||
<div class="vicp-progress-wrap">
|
||||
<span class="vicp-progress" v-show="loading === 1" :style="progressStyle"></span>
|
||||
</div>
|
||||
<div class="vicp-error" v-show="hasError">
|
||||
<i class="vicp-icon2"></i> {{ errorMsg }}
|
||||
</div>
|
||||
<div class="vicp-success" v-show="loading === 2">
|
||||
<i class="vicp-icon3"></i> {{ lang.success }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="vicp-operate">
|
||||
<a @click="setStep(2)" @mousedown="ripple">{{ lang.btn.back }}</a>
|
||||
<a @click="off" @mousedown="ripple">{{ lang.btn.close }}</a>
|
||||
</div>
|
||||
</div>
|
||||
<canvas v-show="false" :width="width" :height="height" ref="canvas"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/* eslint-disable */
|
||||
import {getToken, upload} from 'api/qiniu';
|
||||
import {effectRipple, data2blob} from './utils';
|
||||
import langBag from './lang';
|
||||
const mimes = {
|
||||
'jpg': 'image/jpeg',
|
||||
'png': 'image/png',
|
||||
'gif': 'image/gif',
|
||||
'svg': 'image/svg+xml',
|
||||
'psd': 'image/photoshop'
|
||||
};
|
||||
|
||||
export default {
|
||||
props: {
|
||||
// 域,上传文件name,触发事件会带上(如果一个页面多个图片上传控件,可以做区分
|
||||
field: {
|
||||
type: String,
|
||||
default: 'avatar'
|
||||
},
|
||||
// 上传地址
|
||||
url: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 其他要上传文件附带的数据,对象格式
|
||||
params: {
|
||||
type: Object,
|
||||
default: null
|
||||
},
|
||||
// 剪裁图片的宽
|
||||
width: {
|
||||
type: Number,
|
||||
default: 200
|
||||
},
|
||||
// 剪裁图片的高
|
||||
height: {
|
||||
type: Number,
|
||||
default: 200
|
||||
},
|
||||
// 不预览圆形图片
|
||||
noCircle: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 单文件大小限制
|
||||
maxSize: {
|
||||
type: Number,
|
||||
default: 10240
|
||||
},
|
||||
// 语言类型
|
||||
langType: {
|
||||
type: String,
|
||||
'default': 'zh'
|
||||
},
|
||||
|
||||
},
|
||||
data() {
|
||||
let that = this,
|
||||
{
|
||||
langType,
|
||||
width,
|
||||
height
|
||||
} = that,
|
||||
isSupported = true,
|
||||
lang = langBag[langType] ? langBag[langType] : lang['zh'];
|
||||
|
||||
if (typeof FormData != 'function') {
|
||||
isSupported = false;
|
||||
}
|
||||
return {
|
||||
show: true,
|
||||
// 图片的mime
|
||||
mime:mimes['jpg'],
|
||||
// 语言包
|
||||
lang,
|
||||
// 浏览器是否支持该控件
|
||||
isSupported,
|
||||
// 步骤
|
||||
step: 1, //1选择文件 2剪裁 3上传
|
||||
// 上传状态及进度
|
||||
loading: 0, //0未开始 1正在 2成功 3错误
|
||||
progress: 0,
|
||||
// 是否有错误及错误信息
|
||||
hasError: false,
|
||||
errorMsg: '',
|
||||
// 需求图宽高比
|
||||
ratio: width / height,
|
||||
// 原图地址、生成图片地址
|
||||
sourceImg: null,
|
||||
sourceImgUrl: '',
|
||||
createImgUrl: '',
|
||||
// 原图片拖动事件初始值
|
||||
sourceImgMouseDown: {
|
||||
on: false,
|
||||
mX: 0, //鼠标按下的坐标
|
||||
mY: 0,
|
||||
x: 0, //scale原图坐标
|
||||
y: 0
|
||||
},
|
||||
// 生成图片预览的容器大小
|
||||
previewContainer: {
|
||||
width: 100,
|
||||
height: 100
|
||||
},
|
||||
// 原图容器宽高
|
||||
sourceImgContainer: { // sic
|
||||
width: 240,
|
||||
height: 180
|
||||
},
|
||||
// 原图展示属性
|
||||
scale: {
|
||||
zoomAddOn: false, //按钮缩放事件开启
|
||||
zoomSubOn: false, //按钮缩放事件开启
|
||||
range: 1, //最大100
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: 0,
|
||||
height: 0,
|
||||
maxWidth: 0,
|
||||
maxHeight: 0,
|
||||
minWidth: 0, //最宽
|
||||
minHeight: 0,
|
||||
naturalWidth: 0, //原宽
|
||||
naturalHeight: 0
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 进度条样式
|
||||
progressStyle() {
|
||||
let {
|
||||
progress
|
||||
} = this;
|
||||
return {
|
||||
width: progress + '%'
|
||||
}
|
||||
},
|
||||
// 原图样式
|
||||
sourceImgStyle() {
|
||||
let {
|
||||
scale,
|
||||
sourceImgMasking
|
||||
} = this;
|
||||
return {
|
||||
top: scale.y + sourceImgMasking.y + 'px',
|
||||
left: scale.x + sourceImgMasking.x + 'px',
|
||||
width: scale.width + 'px',
|
||||
height: scale.height + 'px'
|
||||
}
|
||||
},
|
||||
// 原图蒙版属性
|
||||
sourceImgMasking() {
|
||||
let {
|
||||
width,
|
||||
height,
|
||||
ratio,
|
||||
sourceImgContainer
|
||||
} = this,
|
||||
sic = sourceImgContainer,
|
||||
sicRatio = sic.width / sic.height, // 原图容器宽高比
|
||||
x = 0,
|
||||
y = 0,
|
||||
w = sic.width,
|
||||
h = sic.height,
|
||||
scale = 1;
|
||||
if (ratio < sicRatio) {
|
||||
scale = sic.height / height;
|
||||
w = sic.height * ratio;
|
||||
x = (sic.width - w) / 2;
|
||||
}
|
||||
if (ratio > sicRatio) {
|
||||
scale = sic.width / width;
|
||||
h = sic.width / ratio;
|
||||
y = (sic.height - h) / 2;
|
||||
}
|
||||
return {
|
||||
scale, // 蒙版相对需求宽高的缩放
|
||||
x,
|
||||
y,
|
||||
width: w,
|
||||
height: h
|
||||
};
|
||||
},
|
||||
// 原图遮罩样式
|
||||
sourceImgShadeStyle() {
|
||||
let sic = this.sourceImgContainer,
|
||||
sim = this.sourceImgMasking,
|
||||
w = sim.width == sic.width ? sim.width : (sic.width - sim.width) / 2,
|
||||
h = sim.height == sic.height ? sim.height : (sic.height - sim.height) / 2;
|
||||
return {
|
||||
width: w + 'px',
|
||||
height: h + 'px'
|
||||
};
|
||||
},
|
||||
previewStyle() {
|
||||
let {
|
||||
width,
|
||||
height,
|
||||
ratio,
|
||||
previewContainer
|
||||
} = this,
|
||||
pc = previewContainer,
|
||||
w = pc.width,
|
||||
h = pc.height,
|
||||
pcRatio = w / h;
|
||||
if (ratio < pcRatio) {
|
||||
w = pc.height * ratio;
|
||||
}
|
||||
if (ratio > pcRatio) {
|
||||
h = pc.width / ratio;
|
||||
}
|
||||
return {
|
||||
width: w + 'px',
|
||||
height: h + 'px'
|
||||
};
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 点击波纹效果
|
||||
ripple(e) {
|
||||
effectRipple(e);
|
||||
},
|
||||
// 关闭控件
|
||||
off() {
|
||||
this.show = false;
|
||||
},
|
||||
// 设置步骤
|
||||
setStep(step) {
|
||||
let that = this;
|
||||
setTimeout(function () {
|
||||
that.step = step;
|
||||
}, 200);
|
||||
},
|
||||
/* 图片选择区域函数绑定
|
||||
---------------------------------------------------------------*/
|
||||
preventDefault(e) {
|
||||
e.preventDefault();
|
||||
return false;
|
||||
},
|
||||
handleClick(e) {
|
||||
if (this.loading !== 1) {
|
||||
if (e.target !== this.$refs.fileinput) {
|
||||
e.preventDefault();
|
||||
if (document.activeElement !== this.$refs) {
|
||||
this.$refs.fileinput.click();
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
handleChange(e) {
|
||||
e.preventDefault();
|
||||
if (this.loading !== 1) {
|
||||
let files = e.target.files || e.dataTransfer.files;
|
||||
this.reset();
|
||||
if (this.checkFile(files[0])) {
|
||||
this.setSourceImg(files[0]);
|
||||
}
|
||||
}
|
||||
},
|
||||
/* ---------------------------------------------------------------*/
|
||||
// 检测选择的文件是否合适
|
||||
checkFile(file) {
|
||||
let that = this,
|
||||
{
|
||||
lang,
|
||||
maxSize
|
||||
} = that;
|
||||
// 仅限图片
|
||||
if (file.type.indexOf('image') === -1) {
|
||||
that.hasError = true;
|
||||
that.errorMsg = lang.error.onlyImg;
|
||||
return false;
|
||||
}
|
||||
this.mime=file.type;
|
||||
// 超出大小
|
||||
if (file.size / 1024 > maxSize) {
|
||||
that.hasError = true;
|
||||
that.errorMsg = lang.error.outOfSize + maxSize + 'kb';
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
// 重置控件
|
||||
reset() {
|
||||
let that = this;
|
||||
that.step = 1;
|
||||
that.loading = 0;
|
||||
that.hasError = false;
|
||||
that.errorMsg = '';
|
||||
that.progress = 0;
|
||||
},
|
||||
// 设置图片源
|
||||
setSourceImg(file) {
|
||||
let that = this,
|
||||
fr = new FileReader();
|
||||
fr.onload = function (e) {
|
||||
that.sourceImgUrl = fr.result;
|
||||
that.startCrop();
|
||||
};
|
||||
fr.readAsDataURL(file);
|
||||
},
|
||||
// 剪裁前准备工作
|
||||
startCrop() {
|
||||
let that = this,
|
||||
{
|
||||
width,
|
||||
height,
|
||||
ratio,
|
||||
scale,
|
||||
sourceImgUrl,
|
||||
sourceImgMasking,
|
||||
lang
|
||||
} = that,
|
||||
sim = sourceImgMasking,
|
||||
img = new Image();
|
||||
img.src = sourceImgUrl;
|
||||
img.onload = function () {
|
||||
let nWidth = img.naturalWidth,
|
||||
nHeight = img.naturalHeight,
|
||||
nRatio = nWidth / nHeight,
|
||||
w = sim.width,
|
||||
h = sim.height,
|
||||
x = 0,
|
||||
y = 0;
|
||||
// 图片像素不达标
|
||||
// if (nWidth < width || nHeight < height) {
|
||||
// that.hasError = true;
|
||||
// that.errorMsg = lang.error.lowestPx + width + '*' + height;
|
||||
// return false;
|
||||
// }
|
||||
if (ratio > nRatio) {
|
||||
h = w / nRatio;
|
||||
y = (sim.height - h) / 2;
|
||||
}
|
||||
if (ratio < nRatio) {
|
||||
w = h * nRatio;
|
||||
x = (sim.width - w) / 2;
|
||||
}
|
||||
scale.range = 0;
|
||||
scale.x = x;
|
||||
scale.y = y;
|
||||
scale.width = w;
|
||||
scale.height = h;
|
||||
scale.minWidth = w;
|
||||
scale.minHeight = h;
|
||||
scale.maxWidth = nWidth * sim.scale;
|
||||
scale.maxHeight = nHeight * sim.scale;
|
||||
scale.naturalWidth = nWidth;
|
||||
scale.naturalHeight = nHeight;
|
||||
that.sourceImg = img;
|
||||
that.createImg();
|
||||
that.setStep(2);
|
||||
};
|
||||
},
|
||||
// 鼠标按下图片准备移动
|
||||
imgStartMove(e) {
|
||||
let {
|
||||
sourceImgMouseDown,
|
||||
scale
|
||||
} = this,
|
||||
simd = sourceImgMouseDown;
|
||||
simd.mX = e.screenX;
|
||||
simd.mY = e.screenY;
|
||||
simd.x = scale.x;
|
||||
simd.y = scale.y;
|
||||
simd.on = true;
|
||||
},
|
||||
// 鼠标按下状态下移动,图片移动
|
||||
imgMove(e) {
|
||||
let {
|
||||
sourceImgMouseDown: {
|
||||
on,
|
||||
mX,
|
||||
mY,
|
||||
x,
|
||||
y
|
||||
},
|
||||
scale,
|
||||
sourceImgMasking
|
||||
} = this,
|
||||
sim = sourceImgMasking,
|
||||
nX = e.screenX,
|
||||
nY = e.screenY,
|
||||
dX = nX - mX,
|
||||
dY = nY - mY,
|
||||
rX = x + dX,
|
||||
rY = y + dY;
|
||||
if (!on) return;
|
||||
if (rX > 0) {
|
||||
rX = 0;
|
||||
}
|
||||
if (rY > 0) {
|
||||
rY = 0;
|
||||
}
|
||||
if (rX < sim.width - scale.width) {
|
||||
rX = sim.width - scale.width;
|
||||
}
|
||||
if (rY < sim.height - scale.height) {
|
||||
rY = sim.height - scale.height;
|
||||
}
|
||||
scale.x = rX;
|
||||
scale.y = rY;
|
||||
},
|
||||
// 按钮按下开始放大
|
||||
startZoomAdd(e) {
|
||||
let that = this,
|
||||
{
|
||||
scale
|
||||
} = that;
|
||||
scale.zoomAddOn = true;
|
||||
function zoom() {
|
||||
if (scale.zoomAddOn) {
|
||||
let range = scale.range >= 100 ? 100 : ++scale.range;
|
||||
that.zoomImg(range);
|
||||
setTimeout(function () {
|
||||
zoom();
|
||||
}, 60);
|
||||
}
|
||||
}
|
||||
|
||||
zoom();
|
||||
},
|
||||
// 按钮松开或移开取消放大
|
||||
endZoomAdd(e) {
|
||||
this.scale.zoomAddOn = false;
|
||||
},
|
||||
// 按钮按下开始缩小
|
||||
startZoomSub(e) {
|
||||
let that = this,
|
||||
{
|
||||
scale
|
||||
} = that;
|
||||
scale.zoomSubOn = true;
|
||||
function zoom() {
|
||||
if (scale.zoomSubOn) {
|
||||
let range = scale.range <= 0 ? 0 : --scale.range;
|
||||
that.zoomImg(range);
|
||||
setTimeout(function () {
|
||||
zoom();
|
||||
}, 60);
|
||||
}
|
||||
}
|
||||
|
||||
zoom();
|
||||
},
|
||||
// 按钮松开或移开取消缩小
|
||||
endZoomSub(e) {
|
||||
let {
|
||||
scale
|
||||
} = this;
|
||||
scale.zoomSubOn = false;
|
||||
},
|
||||
zoomChange(e) {
|
||||
this.zoomImg(e.target.value);
|
||||
},
|
||||
// 缩放原图
|
||||
zoomImg(newRange) {
|
||||
let that = this,
|
||||
{
|
||||
sourceImgMasking,
|
||||
sourceImgMouseDown,
|
||||
scale
|
||||
} = this,
|
||||
{
|
||||
maxWidth,
|
||||
maxHeight,
|
||||
minWidth,
|
||||
minHeight,
|
||||
width,
|
||||
height,
|
||||
x,
|
||||
y,
|
||||
range
|
||||
} = scale,
|
||||
sim = sourceImgMasking,
|
||||
// 蒙版宽高
|
||||
sWidth = sim.width,
|
||||
sHeight = sim.height,
|
||||
// 新宽高
|
||||
nWidth = minWidth + (maxWidth - minWidth) * newRange / 100,
|
||||
nHeight = minHeight + (maxHeight - minHeight) * newRange / 100,
|
||||
// 新坐标(根据蒙版中心点缩放)
|
||||
nX = sWidth / 2 - (nWidth / width) * (sWidth / 2 - x),
|
||||
nY = sHeight / 2 - (nHeight / height) * (sHeight / 2 - y);
|
||||
// 判断新坐标是否超过蒙版限制
|
||||
if (nX > 0) {
|
||||
nX = 0;
|
||||
}
|
||||
if (nY > 0) {
|
||||
nY = 0;
|
||||
}
|
||||
if (nX < sWidth - nWidth) {
|
||||
nX = sWidth - nWidth;
|
||||
}
|
||||
if (nY < sHeight - nHeight) {
|
||||
nY = sHeight - nHeight;
|
||||
}
|
||||
// 赋值处理
|
||||
scale.x = nX;
|
||||
scale.y = nY;
|
||||
scale.width = nWidth;
|
||||
scale.height = nHeight;
|
||||
scale.range = newRange;
|
||||
setTimeout(function () {
|
||||
if (scale.range == newRange) {
|
||||
that.createImg();
|
||||
}
|
||||
}, 300);
|
||||
},
|
||||
// 生成需求图片
|
||||
createImg(e) {
|
||||
let that = this,
|
||||
{
|
||||
mime,
|
||||
sourceImg,
|
||||
scale: {
|
||||
x,
|
||||
y,
|
||||
width,
|
||||
height
|
||||
},
|
||||
sourceImgMasking: {
|
||||
scale
|
||||
}
|
||||
} = that,
|
||||
canvas = that.$refs.canvas,
|
||||
ctx = canvas.getContext('2d');
|
||||
if (e) {
|
||||
// 取消鼠标按下移动状态
|
||||
that.sourceImgMouseDown.on = false;
|
||||
}
|
||||
ctx.drawImage(sourceImg, x / scale, y / scale, width / scale, height / scale);
|
||||
that.createImgUrl = canvas.toDataURL(mime);
|
||||
},
|
||||
// 上传图片
|
||||
upload() {
|
||||
let that = this,
|
||||
{
|
||||
lang,
|
||||
mime,
|
||||
createImgUrl
|
||||
} = this,
|
||||
formData = new FormData();
|
||||
// 上传文件
|
||||
that.loading = 1;
|
||||
that.progress = 0;
|
||||
that.setStep(3);
|
||||
formData.append('file', data2blob(createImgUrl, mime));
|
||||
const token = this.$store.getters.token;
|
||||
getToken(token).then((response)=> {
|
||||
const url = response.data.qiniu_url;
|
||||
formData.append('token', response.data.qiniu_token);
|
||||
formData.append('key', response.data.qiniu_key);
|
||||
upload(formData).then((response)=> {
|
||||
that.loading = 2;
|
||||
that.$emit('crop-upload-success', url);
|
||||
}).catch(err => {
|
||||
that.loading = 3;
|
||||
that.hasError = true;
|
||||
that.errorMsg = lang.fail;
|
||||
that.$emit('crop-upload-fail');
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@import "./upload.css";
|
||||
</style>
|
41
src/components/ImageCropper/lang.js
Normal file
41
src/components/ImageCropper/lang.js
Normal file
|
@ -0,0 +1,41 @@
|
|||
const langBag = {
|
||||
zh: {
|
||||
hint: '点击,或拖动图片至此处',
|
||||
loading: '正在上传……',
|
||||
noSupported: '浏览器不支持该功能,请使用IE10以上或其他现在浏览器!',
|
||||
success: '上传成功',
|
||||
fail: '图片上传失败',
|
||||
preview: '头像预览',
|
||||
btn: {
|
||||
off: '取消',
|
||||
close: '关闭',
|
||||
back: '上一步',
|
||||
save: '保存'
|
||||
},
|
||||
error: {
|
||||
onlyImg: '仅限图片格式',
|
||||
outOfSize: '单文件大小不能超过 ',
|
||||
lowestPx: '图片最低像素为(宽*高):'
|
||||
}
|
||||
},
|
||||
en: {
|
||||
hint: 'Click, or drag the file here',
|
||||
loading: 'Uploading……',
|
||||
noSupported: 'Browser does not support, please use IE10+ or other browsers',
|
||||
success: 'Upload success',
|
||||
fail: 'Upload failed',
|
||||
preview: 'Preview',
|
||||
btn: {
|
||||
off: 'Cancel',
|
||||
close: 'Close',
|
||||
back: 'Back',
|
||||
save: 'Save'
|
||||
},
|
||||
error: {
|
||||
onlyImg: 'Image only',
|
||||
outOfSize: 'Image exceeds size limit: ',
|
||||
lowestPx: 'The lowest pixel in the image: '
|
||||
}
|
||||
}
|
||||
};
|
||||
export default langBag;
|
691
src/components/ImageCropper/upload.css
Normal file
691
src/components/ImageCropper/upload.css
Normal file
|
@ -0,0 +1,691 @@
|
|||
@charset "UTF-8";
|
||||
@-webkit-keyframes vicp_progress {
|
||||
0% {
|
||||
background-position-y: 0;
|
||||
}
|
||||
100% {
|
||||
background-position-y: 40px;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes vicp_progress {
|
||||
0% {
|
||||
background-position-y: 0;
|
||||
}
|
||||
100% {
|
||||
background-position-y: 40px;
|
||||
}
|
||||
}
|
||||
|
||||
@-webkit-keyframes vicp {
|
||||
0% {
|
||||
opacity: 0;
|
||||
-webkit-transform: scale(0) translatey(-60px);
|
||||
transform: scale(0) translatey(-60px);
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
-webkit-transform: scale(1) translatey(0);
|
||||
transform: scale(1) translatey(0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes vicp {
|
||||
0% {
|
||||
opacity: 0;
|
||||
-webkit-transform: scale(0) translatey(-60px);
|
||||
transform: scale(0) translatey(-60px);
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
-webkit-transform: scale(1) translatey(0);
|
||||
transform: scale(1) translatey(0);
|
||||
}
|
||||
}
|
||||
|
||||
.vue-image-crop-upload {
|
||||
position: fixed;
|
||||
display: block;
|
||||
-webkit-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
z-index: 10000;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(0, 0, 0, 0.65);
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
-moz-tap-highlight-color: transparent;
|
||||
}
|
||||
|
||||
.vue-image-crop-upload .vicp-wrap {
|
||||
-webkit-box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.23);
|
||||
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.23);
|
||||
position: fixed;
|
||||
display: block;
|
||||
-webkit-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
z-index: 10000;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
margin: auto;
|
||||
width: 600px;
|
||||
height: 330px;
|
||||
padding: 25px;
|
||||
background-color: #fff;
|
||||
border-radius: 2px;
|
||||
-webkit-animation: vicp 0.12s ease-in;
|
||||
animation: vicp 0.12s ease-in;
|
||||
}
|
||||
|
||||
.vue-image-crop-upload .vicp-wrap .vicp-close {
|
||||
position: absolute;
|
||||
right: -30px;
|
||||
top: -30px;
|
||||
}
|
||||
|
||||
.vue-image-crop-upload .vicp-wrap .vicp-close .vicp-icon4 {
|
||||
position: relative;
|
||||
display: block;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
cursor: pointer;
|
||||
-webkit-transition: -webkit-transform 0.18s;
|
||||
transition: -webkit-transform 0.18s;
|
||||
transition: transform 0.18s;
|
||||
transition: transform 0.18s, -webkit-transform 0.18s;
|
||||
-webkit-transform: rotate(0);
|
||||
-ms-transform: rotate(0);
|
||||
transform: rotate(0);
|
||||
}
|
||||
|
||||
.vue-image-crop-upload .vicp-wrap .vicp-close .vicp-icon4::after, .vue-image-crop-upload .vicp-wrap .vicp-close .vicp-icon4::before {
|
||||
-webkit-box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.23);
|
||||
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.23);
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 12px;
|
||||
left: 4px;
|
||||
width: 20px;
|
||||
height: 3px;
|
||||
-webkit-transform: rotate(45deg);
|
||||
-ms-transform: rotate(45deg);
|
||||
transform: rotate(45deg);
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.vue-image-crop-upload .vicp-wrap .vicp-close .vicp-icon4::after {
|
||||
-webkit-transform: rotate(-45deg);
|
||||
-ms-transform: rotate(-45deg);
|
||||
transform: rotate(-45deg);
|
||||
}
|
||||
|
||||
.vue-image-crop-upload .vicp-wrap .vicp-close .vicp-icon4:hover {
|
||||
-webkit-transform: rotate(90deg);
|
||||
-ms-transform: rotate(90deg);
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
.vue-image-crop-upload .vicp-wrap .vicp-step1 .vicp-drop-area {
|
||||
position: relative;
|
||||
padding: 35px;
|
||||
height: 200px;
|
||||
background-color: rgba(0, 0, 0, 0.03);
|
||||
text-align: center;
|
||||
border: 1px dashed rgba(0, 0, 0, 0.08);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.vue-image-crop-upload .vicp-wrap .vicp-step1 .vicp-drop-area .vicp-icon1 {
|
||||
display: block;
|
||||
margin: 0 auto 6px;
|
||||
width: 42px;
|
||||
height: 42px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.vue-image-crop-upload .vicp-wrap .vicp-step1 .vicp-drop-area .vicp-icon1 .vicp-icon1-arrow {
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-bottom: 14.7px solid rgba(0, 0, 0, 0.3);
|
||||
border-left: 14.7px solid transparent;
|
||||
border-right: 14.7px solid transparent;
|
||||
}
|
||||
|
||||
.vue-image-crop-upload .vicp-wrap .vicp-step1 .vicp-drop-area .vicp-icon1 .vicp-icon1-body {
|
||||
display: block;
|
||||
width: 12.6px;
|
||||
height: 14.7px;
|
||||
margin: 0 auto;
|
||||
background-color: rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.vue-image-crop-upload .vicp-wrap .vicp-step1 .vicp-drop-area .vicp-icon1 .vicp-icon1-bottom {
|
||||
-webkit-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
display: block;
|
||||
height: 12.6px;
|
||||
border: 6px solid rgba(0, 0, 0, 0.3);
|
||||
border-top: none;
|
||||
}
|
||||
|
||||
.vue-image-crop-upload .vicp-wrap .vicp-step1 .vicp-drop-area .vicp-hint {
|
||||
display: block;
|
||||
padding: 15px;
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
line-height: 30px;
|
||||
}
|
||||
|
||||
.vue-image-crop-upload .vicp-wrap .vicp-step1 .vicp-drop-area .vicp-no-supported-hint {
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
padding: 30px;
|
||||
width: 100%;
|
||||
height: 60px;
|
||||
line-height: 30px;
|
||||
background-color: #eee;
|
||||
text-align: center;
|
||||
color: #666;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.vue-image-crop-upload .vicp-wrap .vicp-step1 .vicp-drop-area:hover {
|
||||
cursor: pointer;
|
||||
border-color: rgba(0, 0, 0, 0.1);
|
||||
background-color: rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-left {
|
||||
float: left;
|
||||
}
|
||||
|
||||
.vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-left .vicp-img-container {
|
||||
position: relative;
|
||||
display: block;
|
||||
width: 240px;
|
||||
height: 180px;
|
||||
background-color: #e5e5e0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-left .vicp-img-container .vicp-img {
|
||||
position: absolute;
|
||||
display: block;
|
||||
cursor: move;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-left .vicp-img-container .vicp-img-shade {
|
||||
-webkit-box-shadow: 0 2px 6px 0 rgba(0, 0, 0, 0.18);
|
||||
box-shadow: 0 2px 6px 0 rgba(0, 0, 0, 0.18);
|
||||
position: absolute;
|
||||
background-color: rgba(241, 242, 243, 0.8);
|
||||
}
|
||||
|
||||
.vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-left .vicp-img-container .vicp-img-shade.vicp-img-shade-1 {
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-left .vicp-img-container .vicp-img-shade.vicp-img-shade-2 {
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-left .vicp-range {
|
||||
position: relative;
|
||||
margin: 30px 0;
|
||||
width: 240px;
|
||||
height: 18px;
|
||||
}
|
||||
|
||||
.vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-left .vicp-range .vicp-icon5,
|
||||
.vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-left .vicp-range .vicp-icon6 {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
border-radius: 100%;
|
||||
background-color: rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-left .vicp-range .vicp-icon5:hover,
|
||||
.vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-left .vicp-range .vicp-icon6:hover {
|
||||
-webkit-box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.12);
|
||||
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.12);
|
||||
cursor: pointer;
|
||||
background-color: rgba(0, 0, 0, 0.14);
|
||||
}
|
||||
|
||||
.vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-left .vicp-range .vicp-icon5 {
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-left .vicp-range .vicp-icon5::before {
|
||||
position: absolute;
|
||||
content: '';
|
||||
display: block;
|
||||
left: 3px;
|
||||
top: 8px;
|
||||
width: 12px;
|
||||
height: 2px;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-left .vicp-range .vicp-icon6 {
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-left .vicp-range .vicp-icon6::before {
|
||||
position: absolute;
|
||||
content: '';
|
||||
display: block;
|
||||
left: 3px;
|
||||
top: 8px;
|
||||
width: 12px;
|
||||
height: 2px;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-left .vicp-range .vicp-icon6::after {
|
||||
position: absolute;
|
||||
content: '';
|
||||
display: block;
|
||||
top: 3px;
|
||||
left: 8px;
|
||||
width: 2px;
|
||||
height: 12px;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-left .vicp-range input[type=range] {
|
||||
display: block;
|
||||
padding-top: 5px;
|
||||
margin: 0 auto;
|
||||
width: 180px;
|
||||
height: 8px;
|
||||
vertical-align: top;
|
||||
background: transparent;
|
||||
-webkit-appearance: none;
|
||||
-moz-appearance: none;
|
||||
appearance: none;
|
||||
cursor: pointer;
|
||||
/* 滑块
|
||||
---------------------------------------------------------------*/
|
||||
/* 轨道
|
||||
---------------------------------------------------------------*/
|
||||
}
|
||||
|
||||
.vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-left .vicp-range input[type=range]:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-left .vicp-range input[type=range]::-webkit-slider-thumb {
|
||||
-webkit-box-shadow: 0 2px 6px 0 rgba(0, 0, 0, 0.18);
|
||||
box-shadow: 0 2px 6px 0 rgba(0, 0, 0, 0.18);
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
margin-top: -3px;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
background-color: #61c091;
|
||||
border-radius: 100%;
|
||||
border: none;
|
||||
-webkit-transition: 0.2s;
|
||||
transition: 0.2s;
|
||||
}
|
||||
|
||||
.vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-left .vicp-range input[type=range]::-moz-range-thumb {
|
||||
box-shadow: 0 2px 6px 0 rgba(0, 0, 0, 0.18);
|
||||
-moz-appearance: none;
|
||||
appearance: none;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
background-color: #61c091;
|
||||
border-radius: 100%;
|
||||
border: none;
|
||||
-webkit-transition: 0.2s;
|
||||
transition: 0.2s;
|
||||
}
|
||||
|
||||
.vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-left .vicp-range input[type=range]::-ms-thumb {
|
||||
box-shadow: 0 2px 6px 0 rgba(0, 0, 0, 0.18);
|
||||
appearance: none;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
background-color: #61c091;
|
||||
border: none;
|
||||
border-radius: 100%;
|
||||
-webkit-transition: 0.2s;
|
||||
transition: 0.2s;
|
||||
}
|
||||
|
||||
.vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-left .vicp-range input[type=range]:active::-moz-range-thumb {
|
||||
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.23);
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
}
|
||||
|
||||
.vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-left .vicp-range input[type=range]:active::-ms-thumb {
|
||||
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.23);
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
}
|
||||
|
||||
.vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-left .vicp-range input[type=range]:active::-webkit-slider-thumb {
|
||||
-webkit-box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.23);
|
||||
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.23);
|
||||
margin-top: -4px;
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
}
|
||||
|
||||
.vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-left .vicp-range input[type=range]::-webkit-slider-runnable-track {
|
||||
-webkit-box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.12);
|
||||
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.12);
|
||||
width: 100%;
|
||||
height: 6px;
|
||||
cursor: pointer;
|
||||
border-radius: 2px;
|
||||
border: none;
|
||||
background-color: rgba(68, 170, 119, 0.3);
|
||||
}
|
||||
|
||||
.vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-left .vicp-range input[type=range]::-moz-range-track {
|
||||
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.12);
|
||||
width: 100%;
|
||||
height: 6px;
|
||||
cursor: pointer;
|
||||
border-radius: 2px;
|
||||
border: none;
|
||||
background-color: rgba(68, 170, 119, 0.3);
|
||||
}
|
||||
|
||||
.vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-left .vicp-range input[type=range]::-ms-track {
|
||||
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.12);
|
||||
width: 100%;
|
||||
cursor: pointer;
|
||||
background: transparent;
|
||||
border-color: transparent;
|
||||
color: transparent;
|
||||
height: 6px;
|
||||
border-radius: 2px;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-left .vicp-range input[type=range]::-ms-fill-lower {
|
||||
background-color: rgba(68, 170, 119, 0.3);
|
||||
}
|
||||
|
||||
.vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-left .vicp-range input[type=range]::-ms-fill-upper {
|
||||
background-color: rgba(68, 170, 119, 0.15);
|
||||
}
|
||||
|
||||
.vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-left .vicp-range input[type=range]:focus::-webkit-slider-runnable-track {
|
||||
background-color: rgba(68, 170, 119, 0.5);
|
||||
}
|
||||
|
||||
.vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-left .vicp-range input[type=range]:focus::-moz-range-track {
|
||||
background-color: rgba(68, 170, 119, 0.5);
|
||||
}
|
||||
|
||||
.vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-left .vicp-range input[type=range]:focus::-ms-fill-lower {
|
||||
background-color: rgba(68, 170, 119, 0.45);
|
||||
}
|
||||
|
||||
.vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-left .vicp-range input[type=range]:focus::-ms-fill-upper {
|
||||
background-color: rgba(68, 170, 119, 0.25);
|
||||
}
|
||||
|
||||
.vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-right {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-right .vicp-preview {
|
||||
height: 150px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-right .vicp-preview .vicp-preview-item {
|
||||
position: relative;
|
||||
padding: 5px;
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
float: left;
|
||||
margin-right: 16px;
|
||||
}
|
||||
|
||||
.vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-right .vicp-preview .vicp-preview-item span {
|
||||
position: absolute;
|
||||
bottom: -30px;
|
||||
width: 100%;
|
||||
font-size: 14px;
|
||||
color: #bbb;
|
||||
display: block;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-right .vicp-preview .vicp-preview-item img {
|
||||
position: absolute;
|
||||
display: block;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
margin: auto;
|
||||
padding: 3px;
|
||||
background-color: #fff;
|
||||
border: 1px solid rgba(0, 0, 0, 0.15);
|
||||
overflow: hidden;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-right .vicp-preview .vicp-preview-item:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.vue-image-crop-upload .vicp-wrap .vicp-step2 .vicp-crop .vicp-crop-right .vicp-preview .vicp-preview-item:last-child img {
|
||||
border-radius: 100%;
|
||||
}
|
||||
|
||||
.vue-image-crop-upload .vicp-wrap .vicp-step3 .vicp-upload {
|
||||
position: relative;
|
||||
padding: 35px;
|
||||
height: 200px;
|
||||
background-color: rgba(0, 0, 0, 0.03);
|
||||
text-align: center;
|
||||
border: 1px dashed #ddd;
|
||||
}
|
||||
|
||||
.vue-image-crop-upload .vicp-wrap .vicp-step3 .vicp-upload .vicp-loading {
|
||||
display: block;
|
||||
padding: 15px;
|
||||
font-size: 16px;
|
||||
color: #999;
|
||||
line-height: 30px;
|
||||
}
|
||||
|
||||
.vue-image-crop-upload .vicp-wrap .vicp-step3 .vicp-upload .vicp-progress-wrap {
|
||||
margin-top: 12px;
|
||||
background-color: rgba(0, 0, 0, 0.08);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.vue-image-crop-upload .vicp-wrap .vicp-step3 .vicp-upload .vicp-progress-wrap .vicp-progress {
|
||||
position: relative;
|
||||
display: block;
|
||||
height: 5px;
|
||||
border-radius: 3px;
|
||||
background-color: #4a7;
|
||||
-webkit-box-shadow: 0 2px 6px 0 rgba(68, 170, 119, 0.3);
|
||||
box-shadow: 0 2px 6px 0 rgba(68, 170, 119, 0.3);
|
||||
-webkit-transition: width 0.15s linear;
|
||||
transition: width 0.15s linear;
|
||||
background-image: -webkit-linear-gradient(135deg, rgba(255, 255, 255, 0.2) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.2) 50%, rgba(255, 255, 255, 0.2) 75%, transparent 75%, transparent);
|
||||
background-image: linear-gradient(-45deg, rgba(255, 255, 255, 0.2) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.2) 50%, rgba(255, 255, 255, 0.2) 75%, transparent 75%, transparent);
|
||||
background-size: 40px 40px;
|
||||
-webkit-animation: vicp_progress 0.5s linear infinite;
|
||||
animation: vicp_progress 0.5s linear infinite;
|
||||
}
|
||||
|
||||
.vue-image-crop-upload .vicp-wrap .vicp-step3 .vicp-upload .vicp-progress-wrap .vicp-progress::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
display: block;
|
||||
top: -3px;
|
||||
right: -3px;
|
||||
width: 9px;
|
||||
height: 9px;
|
||||
border: 1px solid rgba(245, 246, 247, 0.7);
|
||||
-webkit-box-shadow: 0 1px 4px 0 rgba(68, 170, 119, 0.7);
|
||||
box-shadow: 0 1px 4px 0 rgba(68, 170, 119, 0.7);
|
||||
border-radius: 100%;
|
||||
background-color: #4a7;
|
||||
}
|
||||
|
||||
.vue-image-crop-upload .vicp-wrap .vicp-step3 .vicp-upload .vicp-error,
|
||||
.vue-image-crop-upload .vicp-wrap .vicp-step3 .vicp-upload .vicp-success {
|
||||
height: 100px;
|
||||
line-height: 100px;
|
||||
}
|
||||
|
||||
.vue-image-crop-upload .vicp-wrap .vicp-operate {
|
||||
position: absolute;
|
||||
right: 20px;
|
||||
bottom: 20px;
|
||||
}
|
||||
|
||||
.vue-image-crop-upload .vicp-wrap .vicp-operate a {
|
||||
position: relative;
|
||||
float: left;
|
||||
display: block;
|
||||
margin-left: 10px;
|
||||
width: 100px;
|
||||
height: 36px;
|
||||
line-height: 36px;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
color: #4a7;
|
||||
border-radius: 2px;
|
||||
overflow: hidden;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.vue-image-crop-upload .vicp-wrap .vicp-operate a:hover {
|
||||
background-color: rgba(0, 0, 0, 0.03);
|
||||
}
|
||||
|
||||
.vue-image-crop-upload .vicp-wrap .vicp-error,
|
||||
.vue-image-crop-upload .vicp-wrap .vicp-success {
|
||||
display: block;
|
||||
font-size: 14px;
|
||||
line-height: 24px;
|
||||
height: 24px;
|
||||
color: #d10;
|
||||
text-align: center;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.vue-image-crop-upload .vicp-wrap .vicp-success {
|
||||
color: #4a7;
|
||||
}
|
||||
|
||||
.vue-image-crop-upload .vicp-wrap .vicp-icon3 {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
top: 4px;
|
||||
}
|
||||
|
||||
.vue-image-crop-upload .vicp-wrap .vicp-icon3::after {
|
||||
position: absolute;
|
||||
top: 3px;
|
||||
left: 6px;
|
||||
width: 6px;
|
||||
height: 10px;
|
||||
border-width: 0 2px 2px 0;
|
||||
border-color: #4a7;
|
||||
border-style: solid;
|
||||
-webkit-transform: rotate(45deg);
|
||||
-ms-transform: rotate(45deg);
|
||||
transform: rotate(45deg);
|
||||
content: '';
|
||||
}
|
||||
|
||||
.vue-image-crop-upload .vicp-wrap .vicp-icon2 {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
top: 4px;
|
||||
}
|
||||
|
||||
.vue-image-crop-upload .vicp-wrap .vicp-icon2::after, .vue-image-crop-upload .vicp-wrap .vicp-icon2::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 9px;
|
||||
left: 4px;
|
||||
width: 13px;
|
||||
height: 2px;
|
||||
background-color: #d10;
|
||||
-webkit-transform: rotate(45deg);
|
||||
-ms-transform: rotate(45deg);
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
|
||||
.vue-image-crop-upload .vicp-wrap .vicp-icon2::after {
|
||||
-webkit-transform: rotate(-45deg);
|
||||
-ms-transform: rotate(-45deg);
|
||||
transform: rotate(-45deg);
|
||||
}
|
||||
|
||||
.e-ripple {
|
||||
position: absolute;
|
||||
border-radius: 100%;
|
||||
background-color: rgba(0, 0, 0, 0.15);
|
||||
background-clip: padding-box;
|
||||
pointer-events: none;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
-webkit-transform: scale(0);
|
||||
-ms-transform: scale(0);
|
||||
transform: scale(0);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.e-ripple.z-active {
|
||||
opacity: 0;
|
||||
-webkit-transform: scale(2);
|
||||
-ms-transform: scale(2);
|
||||
transform: scale(2);
|
||||
-webkit-transition: opacity 1.2s ease-out, -webkit-transform 0.6s ease-out;
|
||||
transition: opacity 1.2s ease-out, -webkit-transform 0.6s ease-out;
|
||||
transition: opacity 1.2s ease-out, transform 0.6s ease-out;
|
||||
transition: opacity 1.2s ease-out, transform 0.6s ease-out, -webkit-transform 0.6s ease-out;
|
||||
}
|
58
src/components/ImageCropper/utils.js
Normal file
58
src/components/ImageCropper/utils.js
Normal file
|
@ -0,0 +1,58 @@
|
|||
/* eslint-disable */
|
||||
|
||||
/**
|
||||
*
|
||||
* @param e
|
||||
* @param arg_opts
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function effectRipple(e, arg_opts) {
|
||||
let opts = Object.assign({
|
||||
ele: e.target, // 波纹作用元素
|
||||
type: 'hit', // hit点击位置扩散 center中心点扩展
|
||||
bgc: 'rgba(0, 0, 0, 0.15)' // 波纹颜色
|
||||
}, arg_opts),
|
||||
target = opts.ele;
|
||||
if (target) {
|
||||
let rect = target.getBoundingClientRect(),
|
||||
ripple = target.querySelector('.e-ripple');
|
||||
if (!ripple) {
|
||||
ripple = document.createElement('span');
|
||||
ripple.className = 'e-ripple';
|
||||
ripple.style.height = ripple.style.width = Math.max(rect.width, rect.height) + 'px';
|
||||
target.appendChild(ripple);
|
||||
} else {
|
||||
ripple.className = 'e-ripple';
|
||||
}
|
||||
switch (opts.type) {
|
||||
case 'center':
|
||||
ripple.style.top = (rect.height / 2 - ripple.offsetHeight / 2) + 'px';
|
||||
ripple.style.left = (rect.width / 2 - ripple.offsetWidth / 2) + 'px';
|
||||
break;
|
||||
default:
|
||||
ripple.style.top = (e.pageY - rect.top - ripple.offsetHeight / 2 - document.body.scrollTop) + 'px';
|
||||
ripple.style.left = (e.pageX - rect.left - ripple.offsetWidth / 2 - document.body.scrollLeft) + 'px';
|
||||
}
|
||||
ripple.style.backgroundColor = opts.bgc;
|
||||
ripple.className = 'e-ripple z-active';
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// database64文件格式转换为2进制
|
||||
/**
|
||||
*
|
||||
* @param data
|
||||
* @param mime
|
||||
* @returns {*}
|
||||
*/
|
||||
export function data2blob(data, mime) {
|
||||
// dataURL 的格式为 “data:image/png;base64,****”,逗号之前都是一些说明性的文字,我们只需要逗号之后的就行了
|
||||
data = data.split(',')[1];
|
||||
data = window.atob(data);
|
||||
var ia = new Uint8Array(data.length);
|
||||
for (var i = 0; i < data.length; i++) {
|
||||
ia[i] = data.charCodeAt(i);
|
||||
}
|
||||
// canvas.toDataURL 返回的默认格式就是 image/png
|
||||
return new Blob([ia], {type: mime});
|
||||
};
|
407
src/components/MDinput/index.vue
Normal file
407
src/components/MDinput/index.vue
Normal file
|
@ -0,0 +1,407 @@
|
|||
<template>
|
||||
<div class="material-input__component" :class="computedClasses">
|
||||
<input
|
||||
v-if="type === 'email'"
|
||||
type="email"
|
||||
class="material-input"
|
||||
:name="name"
|
||||
:id="id"
|
||||
:placeholder="placeholder"
|
||||
v-model="valueCopy"
|
||||
|
||||
:readonly="readonly"
|
||||
:disabled="disabled"
|
||||
:autocomplete="autocomplete"
|
||||
|
||||
:required="required"
|
||||
|
||||
@focus="handleFocus(true)"
|
||||
@blur="handleFocus(false)"
|
||||
@input="handleModelInput"
|
||||
>
|
||||
<input
|
||||
v-if="type === 'url'"
|
||||
type="url"
|
||||
class="material-input"
|
||||
:name="name"
|
||||
:id="id"
|
||||
:placeholder="placeholder"
|
||||
v-model="valueCopy"
|
||||
|
||||
:readonly="readonly"
|
||||
:disabled="disabled"
|
||||
:autocomplete="autocomplete"
|
||||
|
||||
:required="required"
|
||||
|
||||
@focus="handleFocus(true)"
|
||||
@blur="handleFocus(false)"
|
||||
@input="handleModelInput"
|
||||
>
|
||||
<input
|
||||
v-if="type === 'number'"
|
||||
type="number"
|
||||
class="material-input"
|
||||
:name="name"
|
||||
:id="id"
|
||||
:placeholder="placeholder"
|
||||
v-model="valueCopy"
|
||||
|
||||
:readonly="readonly"
|
||||
:disabled="disabled"
|
||||
:autocomplete="autocomplete"
|
||||
|
||||
:max="max"
|
||||
:min="min"
|
||||
:minlength="minlength"
|
||||
:maxlength="maxlength"
|
||||
:required="required"
|
||||
|
||||
@focus="handleFocus(true)"
|
||||
@blur="handleFocus(false)"
|
||||
@input="handleModelInput"
|
||||
>
|
||||
<input
|
||||
v-if="type === 'password'"
|
||||
type="password"
|
||||
class="material-input"
|
||||
:name="name"
|
||||
:id="id"
|
||||
:placeholder="placeholder"
|
||||
v-model="valueCopy"
|
||||
|
||||
:readonly="readonly"
|
||||
:disabled="disabled"
|
||||
:autocomplete="autocomplete"
|
||||
|
||||
:max="max"
|
||||
:min="min"
|
||||
:required="required"
|
||||
|
||||
@focus="handleFocus(true)"
|
||||
@blur="handleFocus(false)"
|
||||
@input="handleModelInput"
|
||||
>
|
||||
<input
|
||||
v-if="type === 'tel'"
|
||||
type="tel"
|
||||
class="material-input"
|
||||
:name="name"
|
||||
:id="id"
|
||||
:placeholder="placeholder"
|
||||
v-model="valueCopy"
|
||||
|
||||
:readonly="readonly"
|
||||
:disabled="disabled"
|
||||
:autocomplete="autocomplete"
|
||||
|
||||
:required="required"
|
||||
|
||||
@focus="handleFocus(true)"
|
||||
@blur="handleFocus(false)"
|
||||
@input="handleModelInput"
|
||||
>
|
||||
<input
|
||||
v-if="type === 'text'"
|
||||
type="text"
|
||||
class="material-input"
|
||||
:name="name"
|
||||
:id="id"
|
||||
:placeholder="placeholder"
|
||||
v-model="valueCopy"
|
||||
|
||||
:readonly="readonly"
|
||||
:disabled="disabled"
|
||||
:autocomplete="autocomplete"
|
||||
|
||||
:minlength="minlength"
|
||||
:maxlength="maxlength"
|
||||
:required="required"
|
||||
|
||||
@focus="handleFocus(true)"
|
||||
@blur="handleFocus(false)"
|
||||
@input="handleModelInput"
|
||||
>
|
||||
|
||||
<span class="material-input-bar"></span>
|
||||
|
||||
<label class="material-label">
|
||||
<slot></slot>
|
||||
</label>
|
||||
<div v-if="errorMessages" class="material-errors">
|
||||
<div v-for="error in computedErrors" class="material-error">
|
||||
{{ error }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'material-input',
|
||||
computed: {
|
||||
computedErrors() {
|
||||
return typeof this.errorMessages === 'string'
|
||||
? [this.errorMessages] : this.errorMessages
|
||||
},
|
||||
computedClasses() {
|
||||
return {
|
||||
'material--active': this.focus,
|
||||
'material--disabled': this.disabled,
|
||||
'material--has-errors': Boolean(
|
||||
!this.valid ||
|
||||
(this.errorMessages && this.errorMessages.length)),
|
||||
'material--raised': Boolean(
|
||||
this.focus ||
|
||||
this.valueCopy || // has value
|
||||
(this.placeholder && !this.valueCopy)) // has placeholder
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
valueCopy: null,
|
||||
focus: false,
|
||||
valid: true
|
||||
}
|
||||
},
|
||||
beforeMount() {
|
||||
// Here we are following the Vue2 convention on custom v-model:
|
||||
// https://github.com/vuejs/vue/issues/2873#issuecomment-223759341
|
||||
this.copyValue(this.value)
|
||||
},
|
||||
methods: {
|
||||
handleModelInput(event) {
|
||||
this.$emit('input', event.target.value, event)
|
||||
this.handleValidation()
|
||||
},
|
||||
handleFocus(focused) {
|
||||
this.focus = focused
|
||||
},
|
||||
handleValidation() {
|
||||
this.valid = this.$el ? this.$el.querySelector(
|
||||
'.material-input').validity.valid : this.valid
|
||||
},
|
||||
copyValue(value) {
|
||||
this.valueCopy = value
|
||||
this.handleValidation()
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value(newValue) {
|
||||
this.copyValue(newValue)
|
||||
}
|
||||
},
|
||||
props: {
|
||||
id: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
name: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
default: 'text'
|
||||
},
|
||||
value: {
|
||||
default: null
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
readonly: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
min: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
max: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
minlength: {
|
||||
type: Number,
|
||||
default: null
|
||||
},
|
||||
maxlength: {
|
||||
type: Number,
|
||||
default: null
|
||||
},
|
||||
required: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
autocomplete: {
|
||||
type: String,
|
||||
default: 'off'
|
||||
},
|
||||
errorMessages: {
|
||||
type: [Array, String],
|
||||
default: null
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style rel="stylesheet/scss" lang="scss" scoped>
|
||||
// Fonts:
|
||||
$font-size-base: 16px;
|
||||
$font-size-small: 18px;
|
||||
$font-size-smallest: 12px;
|
||||
$font-weight-normal: normal;
|
||||
// Utils
|
||||
$spacer: 12px;
|
||||
$transition: 0.2s ease all;
|
||||
// Base clases:
|
||||
%base-bar-pseudo {
|
||||
content: '';
|
||||
height: 1px;
|
||||
width: 0;
|
||||
bottom: 0;
|
||||
position: absolute;
|
||||
transition: $transition;
|
||||
}
|
||||
|
||||
// Mixins:
|
||||
@mixin slided-top() {
|
||||
top: -2 * $spacer;
|
||||
font-size: $font-size-small;
|
||||
}
|
||||
|
||||
// Component:
|
||||
.material-input__component {
|
||||
/*margin-top: 30px;*/
|
||||
position: relative;
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.material-input {
|
||||
font-size: $font-size-base;
|
||||
padding: $spacer $spacer $spacer $spacer / 2;
|
||||
display: block;
|
||||
width: 100%;
|
||||
border: none;
|
||||
border-radius: 0;
|
||||
&:focus {
|
||||
outline: none;
|
||||
border: none;
|
||||
border-bottom: 1px solid transparent; // fixes the height issue
|
||||
}
|
||||
}
|
||||
.material-label {
|
||||
font-size: $font-size-base;
|
||||
font-weight: $font-weight-normal;
|
||||
position: absolute;
|
||||
pointer-events: none;
|
||||
left: 0;
|
||||
top: $spacer;
|
||||
transition: $transition;
|
||||
}
|
||||
.material-input-bar {
|
||||
position: relative;
|
||||
display: block;
|
||||
width: 100%;
|
||||
&:before {
|
||||
@extend %base-bar-pseudo;
|
||||
left: 50%;
|
||||
}
|
||||
&:after {
|
||||
@extend %base-bar-pseudo;
|
||||
right: 50%;
|
||||
}
|
||||
}
|
||||
// Disabled state:
|
||||
&.material--disabled {
|
||||
.material-input {
|
||||
border-bottom-style: dashed;
|
||||
}
|
||||
}
|
||||
// Raised state:
|
||||
&.material--raised {
|
||||
.material-label {
|
||||
@include slided-top();
|
||||
}
|
||||
}
|
||||
// Active state:
|
||||
&.material--active {
|
||||
.material-input-bar {
|
||||
&:before,
|
||||
&:after {
|
||||
width: 50%;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Errors:
|
||||
.material-errors {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
.material-error {
|
||||
font-size: $font-size-smallest;
|
||||
line-height: $font-size-smallest + 2px;
|
||||
overflow: hidden;
|
||||
margin-top: 0;
|
||||
padding-top: $spacer / 2;
|
||||
padding-right: $spacer / 2;
|
||||
padding-left: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Theme:
|
||||
$color-white: white;
|
||||
$color-grey: #9E9E9E;
|
||||
$color-grey-light: #E0E0E0;
|
||||
$color-blue: #2196F3;
|
||||
$color-red: #F44336;
|
||||
$color-black: black;
|
||||
.material-input__component {
|
||||
background: $color-white;
|
||||
.material-input {
|
||||
background: none;
|
||||
color: $color-black;
|
||||
text-indent: 30px;
|
||||
border-bottom: 1px solid $color-grey-light;
|
||||
}
|
||||
.material-label {
|
||||
color: $color-grey;
|
||||
}
|
||||
.material-input-bar {
|
||||
&:before,
|
||||
&:after {
|
||||
background: $color-blue;
|
||||
}
|
||||
}
|
||||
// Active state:
|
||||
&.material--active {
|
||||
.material-label {
|
||||
color: $color-blue;
|
||||
}
|
||||
}
|
||||
// Errors:
|
||||
&.material--has-errors {
|
||||
// These styles are required
|
||||
// for custom validation:
|
||||
&.material--active .material-label {
|
||||
color: $color-red;
|
||||
}
|
||||
.material-input-bar {
|
||||
&:before,
|
||||
&:after {
|
||||
background: $color-red;
|
||||
}
|
||||
}
|
||||
.material-errors {
|
||||
color: $color-red;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
108
src/components/MdEditor/index.vue
Normal file
108
src/components/MdEditor/index.vue
Normal file
|
@ -0,0 +1,108 @@
|
|||
<template>
|
||||
<div class='simplemde-container'>
|
||||
<textarea :id='id'>
|
||||
</textarea>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import 'simplemde/dist/simplemde.min.css'
|
||||
import SimpleMDE from 'simplemde'
|
||||
export default {
|
||||
name: 'Sticky',
|
||||
props: {
|
||||
value: String,
|
||||
id: {
|
||||
type: String,
|
||||
default: 'markdown-editor'
|
||||
},
|
||||
autofocus: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
toolbar: {
|
||||
type: Array
|
||||
// default() {
|
||||
// return ['bold', '|', 'link']
|
||||
// }
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
simplemde: null,
|
||||
hasChange: false
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
value(val) {
|
||||
if (val === this.simplemde.value() && !this.hasChange) return;
|
||||
this.simplemde.value(val);
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.simplemde = new SimpleMDE({
|
||||
element: document.getElementById(this.id),
|
||||
autofocus: this.autofocus,
|
||||
toolbar: this.toolbar,
|
||||
spellChecker: false,
|
||||
insertTexts: {
|
||||
link: ['[', ']( )']
|
||||
},
|
||||
// hideIcons: ['guide', 'heading', 'quote', 'image', 'preview', 'side-by-side', 'fullscreen'],
|
||||
placeholder: this.placeholder
|
||||
});
|
||||
if (this.value) {
|
||||
this.simplemde.value(this.value);
|
||||
}
|
||||
|
||||
this.simplemde.codemirror.on('change', () => {
|
||||
if (this.hasChange) {
|
||||
this.hasChange = true
|
||||
}
|
||||
this.$emit('input', this.simplemde.value());
|
||||
});
|
||||
},
|
||||
destroyed() {
|
||||
this.simplemde = null;
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.simplemde-container .CodeMirror {
|
||||
height: 150px;
|
||||
min-height: 150px;
|
||||
}
|
||||
.simplemde-container .CodeMirror-scroll{
|
||||
min-height: 150px;
|
||||
}
|
||||
|
||||
.simplemde-container .CodeMirror-code{
|
||||
padding-bottom: 40px;
|
||||
}
|
||||
.simplemde-container .editor-statusbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.simplemde-container .CodeMirror .CodeMirror-code .cm-link {
|
||||
color: #1482F0;
|
||||
}
|
||||
|
||||
.simplemde-container .CodeMirror .CodeMirror-code .cm-string.cm-url {
|
||||
color: #2d3b4d;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.simplemde-container .CodeMirror .CodeMirror-code .cm-formatting-link-string.cm-url {
|
||||
padding: 0 2px;
|
||||
font-weight: bold;
|
||||
color: #E61E1E;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
|
145
src/components/PanThumb/index.vue
Normal file
145
src/components/PanThumb/index.vue
Normal file
|
@ -0,0 +1,145 @@
|
|||
<template>
|
||||
<div class="pan-item" :style="{zIndex:zIndex,height:height,width:width}">
|
||||
<div class="pan-info">
|
||||
<div class="pan-info-roles-container">
|
||||
<slot>pan</slot>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pan-thumb" :style="{ backgroundImage: 'url('+ image+')' }"></div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
name: 'PanThumb',
|
||||
props: {
|
||||
image: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
zIndex: {
|
||||
type: Number,
|
||||
default: 100
|
||||
},
|
||||
width: {
|
||||
type: String,
|
||||
default: '150px'
|
||||
},
|
||||
height: {
|
||||
type: String,
|
||||
default: '150px'
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {};
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.pan-item {
|
||||
width: 200px;
|
||||
height: 200px;
|
||||
border-radius: 50%;
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
cursor: default;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.pan-info-roles-container {
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.pan-thumb {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-size: 100%;
|
||||
border-radius: 50%;
|
||||
overflow: hidden;
|
||||
position: absolute;
|
||||
transform-origin: 95% 40%;
|
||||
transition: all 0.3s ease-in-out;
|
||||
}
|
||||
|
||||
.pan-thumb:after {
|
||||
content: '';
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
position: absolute;
|
||||
border-radius: 50%;
|
||||
top: 40%;
|
||||
left: 95%;
|
||||
margin: -4px 0 0 -4px;
|
||||
background: radial-gradient(ellipse at center, rgba(14, 14, 14, 1) 0%, rgba(125, 126, 125, 1) 100%);
|
||||
box-shadow: 0 0 1px rgba(255, 255, 255, 0.9);
|
||||
}
|
||||
|
||||
.pan-info {
|
||||
position: absolute;
|
||||
width: inherit;
|
||||
height: inherit;
|
||||
border-radius: 50%;
|
||||
overflow: hidden;
|
||||
box-shadow: inset 0 0 0 5px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.pan-info h3 {
|
||||
color: #fff;
|
||||
text-transform: uppercase;
|
||||
position: relative;
|
||||
letter-spacing: 2px;
|
||||
font-size: 18px;
|
||||
margin: 0 60px;
|
||||
padding: 22px 0 0 0;
|
||||
height: 85px;
|
||||
font-family: 'Open Sans', Arial, sans-serif;
|
||||
text-shadow: 0 0 1px #fff,
|
||||
0 1px 2px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.pan-info p {
|
||||
color: #fff;
|
||||
padding: 10px 5px;
|
||||
font-style: italic;
|
||||
margin: 0 30px;
|
||||
font-size: 12px;
|
||||
border-top: 1px solid rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
|
||||
.pan-info p a {
|
||||
display: block;
|
||||
color: #333;
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
background: rgba(255, 255, 255, 0.3);
|
||||
border-radius: 50%;
|
||||
color: #fff;
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
font-size: 9px;
|
||||
letter-spacing: 1px;
|
||||
padding-top: 24px;
|
||||
margin: 7px auto 0;
|
||||
font-family: 'Open Sans', Arial, sans-serif;
|
||||
opacity: 0;
|
||||
transition: transform 0.3s ease-in-out 0.2s,
|
||||
opacity 0.3s ease-in-out 0.2s,
|
||||
background 0.2s linear 0s;
|
||||
transform: translateX(60px) rotate(90deg);
|
||||
}
|
||||
|
||||
.pan-info p a:hover {
|
||||
background: rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
|
||||
.pan-item:hover .pan-thumb {
|
||||
transform: rotate(-110deg);
|
||||
}
|
||||
|
||||
.pan-item:hover .pan-info p a {
|
||||
opacity: 1;
|
||||
transform: translateX(0px) rotate(0deg);
|
||||
}
|
||||
</style>
|
61
src/components/SplitPane/Pane.vue
Normal file
61
src/components/SplitPane/Pane.vue
Normal file
|
@ -0,0 +1,61 @@
|
|||
<template>
|
||||
<div :class="classes">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'Pane',
|
||||
props: {
|
||||
// split: {
|
||||
// validator: function (value) {
|
||||
// return ['vertical', 'horizontal'].indexOf(value) >= 0
|
||||
// },
|
||||
// required: true
|
||||
// }
|
||||
},
|
||||
// computed:{
|
||||
// classes () {
|
||||
// return this.$parent.split
|
||||
// },
|
||||
// },
|
||||
data() {
|
||||
const classes = ['Pane', this.$parent.split, 'className'];
|
||||
return {
|
||||
classes: classes.join(' '),
|
||||
percent: 50
|
||||
}
|
||||
},
|
||||
created() {
|
||||
// console.log(this.$parent.split)
|
||||
},
|
||||
|
||||
methods: {
|
||||
|
||||
}
|
||||
}
|
||||
</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>
|
75
src/components/SplitPane/Resizer.vue
Normal file
75
src/components/SplitPane/Resizer.vue
Normal file
|
@ -0,0 +1,75 @@
|
|||
<template>
|
||||
<div :class="classes" @mousedown="onMouseDown"></div>
|
||||
</template>
|
||||
<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%;
|
||||
/*margin: 0 -5px;*/
|
||||
|
||||
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>
|
||||
<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(' ')
|
||||
}
|
||||
},
|
||||
|
||||
methods: {}
|
||||
}
|
||||
</script>
|
175
src/components/SplitPane/SplitPane-backup.vue
Normal file
175
src/components/SplitPane/SplitPane-backup.vue
Normal file
|
@ -0,0 +1,175 @@
|
|||
<!--<template>-->
|
||||
<!--<div :style="{ cursor, userSelect }" class="vue-splitter-container clearfix" @mouseup="onMouseUp"-->
|
||||
<!--@mousemove="onMouseMove">-->
|
||||
|
||||
<!--<Pane split="vertical" :style="{ width: percent+'%' }" class="left-container splitter-pane">-->
|
||||
<!--orange-->
|
||||
<!--</Pane>-->
|
||||
|
||||
<!--<Resizer split="vertical" :onMouseDown="onMouseDown" @click="onClick"></Resizer>-->
|
||||
<!--<div class="todel" :style="{ width: 100-percent+'%'}">-->
|
||||
<!--<Pane split="horizontal" class="top-container">-->
|
||||
<!--<div slot>apple banana</div>-->
|
||||
<!--</Pane>-->
|
||||
<!--<Resizer split="horizontal" :onMouseDown="onMouseDown" @click="onClick"></Resizer>-->
|
||||
<!--<Pane split="horizontal" class="bottom-container">-->
|
||||
<!--<div slot>apple banana</div>-->
|
||||
<!--</Pane>-->
|
||||
<!--</div>-->
|
||||
|
||||
<!--</div>-->
|
||||
|
||||
<!--</template>-->
|
||||
<style scoped>
|
||||
.clearfix:after {
|
||||
visibility: hidden;
|
||||
display: block;
|
||||
font-size: 0;
|
||||
content: " ";
|
||||
clear: both;
|
||||
height: 0;
|
||||
}
|
||||
.vue-splitter-container {
|
||||
height: inherit;
|
||||
display: flex;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
/* eslint-disable */
|
||||
import Resizer from './Resizer';
|
||||
import vue from 'vue'
|
||||
export default {
|
||||
name: 'splitPane',
|
||||
components: {Resizer},
|
||||
props: {
|
||||
margin: {
|
||||
type: Number,
|
||||
default: 10
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
active: false,
|
||||
percent: 50,
|
||||
hasMoved: false,
|
||||
panes: []
|
||||
}
|
||||
},
|
||||
props: {
|
||||
split: {
|
||||
validator: function (value) {
|
||||
return ['vertical', 'horizontal'].indexOf(value) >= 0
|
||||
},
|
||||
required: true
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
userSelect () {
|
||||
return this.active ? 'none' : ''
|
||||
},
|
||||
cursor () {
|
||||
return this.active ? 'col-resize' : ''
|
||||
},
|
||||
// $paneItems () {
|
||||
// return this.$children.filter(child => {
|
||||
// console.log(child)
|
||||
// })
|
||||
// }
|
||||
},
|
||||
render(h){
|
||||
const temp = [];
|
||||
this.$slots.default.map((item, i) => {
|
||||
if (item.tag && item.tag.toUpperCase().indexOf('PANE') >= 0) {
|
||||
temp.push(item)
|
||||
}
|
||||
});
|
||||
const newSlots = [];
|
||||
const length = temp.length;
|
||||
temp.map((item, index)=> {
|
||||
newSlots.push(item)
|
||||
if (index != length - 1) {
|
||||
newSlots.push(
|
||||
h('Resizer', {
|
||||
props: {
|
||||
split: this.split,
|
||||
onMouseDown: this.onMouseDown
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
})
|
||||
return h('div', {
|
||||
on: {
|
||||
mousemove: this.onMouseMove
|
||||
}
|
||||
}, [
|
||||
h('div', {
|
||||
'class': {
|
||||
'vue-splitter-container': true
|
||||
},
|
||||
}, newSlots)
|
||||
])
|
||||
},
|
||||
// beforeMount(){
|
||||
// this.$slots.default=this.$slots.default.map((item, i) => {
|
||||
// if (item.tag&&item.tag.toUpperCase().indexOf('PANE') >= 0) {
|
||||
// return item
|
||||
// }else{
|
||||
// return null
|
||||
// }
|
||||
// })
|
||||
//
|
||||
// },
|
||||
created(){
|
||||
|
||||
},
|
||||
mounted(){
|
||||
|
||||
},
|
||||
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;
|
||||
while (target) {
|
||||
offset += target.offsetLeft;
|
||||
target = target.offsetParent;
|
||||
}
|
||||
const percent = Math.floor(((e.pageX - offset) / e.currentTarget.offsetWidth) * 10000) / 100;
|
||||
if (percent > this.margin && percent < 100 - this.margin) {
|
||||
this.percent = percent;
|
||||
}
|
||||
console.log(percent)
|
||||
this.$children.map((v, i)=> {
|
||||
if (i == 0) {
|
||||
v.percent = percent
|
||||
} else {
|
||||
v.percent = 100 - percent
|
||||
}
|
||||
|
||||
})
|
||||
this.$emit('resize');
|
||||
this.hasMoved = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
117
src/components/SplitPane/SplitPane.vue
Normal file
117
src/components/SplitPane/SplitPane.vue
Normal file
|
@ -0,0 +1,117 @@
|
|||
<template>
|
||||
<div ref :style="{ cursor, userSelect}" class="vue-splitter-container clearfix" @mouseup="onMouseUp"
|
||||
@mousemove="onMouseMove">
|
||||
<Pane class="splitter-pane splitter-paneL" :split="split" :style="{ [type]: percent+'%'}">
|
||||
<slot name="paneL"></slot>
|
||||
</Pane>
|
||||
<Resizer :style="{ [resizeType]: percent+'%'}" :split="split" :onMouseDown="onMouseDown"
|
||||
@click="onClick"></Resizer>
|
||||
<Pane class="splitter-pane splitter-paneR" :split="split" :style="{ [type]: 100-percent+'%'}">
|
||||
<slot name="paneR"></slot>
|
||||
</Pane>
|
||||
</div>
|
||||
</template>
|
||||
<style scoped>
|
||||
.clearfix:after {
|
||||
visibility: hidden;
|
||||
display: block;
|
||||
font-size: 0;
|
||||
content: " ";
|
||||
clear: both;
|
||||
height: 0;
|
||||
}
|
||||
.vue-splitter-container {
|
||||
height: 100%;
|
||||
/*display: flex;*/
|
||||
position: relative;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import Resizer from './Resizer';
|
||||
import Pane from './Pane';
|
||||
export default {
|
||||
name: 'splitPane',
|
||||
components: { Resizer, Pane },
|
||||
props: {
|
||||
margin: {
|
||||
type: Number,
|
||||
default: 10
|
||||
},
|
||||
split: {
|
||||
validator(value) {
|
||||
return ['vertical', 'horizontal'].indexOf(value) >= 0
|
||||
},
|
||||
required: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
active: false,
|
||||
hasMoved: false,
|
||||
height: null,
|
||||
percent: 50,
|
||||
type: this.split === 'vertical' ? 'width' : 'height',
|
||||
resizeType: this.split === 'vertical' ? 'left' : 'top'
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
userSelect() {
|
||||
return this.active ? 'none' : ''
|
||||
},
|
||||
cursor() {
|
||||
return this.active ? 'col-resize' : ''
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
const element = this.$el;
|
||||
const elementOffset = element.getBoundingClientRect();
|
||||
console.log(elementOffset.height)
|
||||
// this.height = elementOffset.height+'px';
|
||||
},
|
||||
methods: {
|
||||
onClick() {
|
||||
if (!this.hasMoved) {
|
||||
this.percent = 50;
|
||||
this.$emit('resize');
|
||||
}
|
||||
},
|
||||
onMouseDown() {
|
||||
this.active = true;
|
||||
this.hasMoved = false;
|
||||
},
|
||||
onMouseUp() {
|
||||
this.active = false;
|
||||
},
|
||||
onMouseMove(e) {
|
||||
if (e.buttons === 0 || e.which === 0) {
|
||||
this.active = false;
|
||||
}
|
||||
if (this.active) {
|
||||
let offset = 0;
|
||||
let target = e.currentTarget;
|
||||
if (this.split === 'vertical') {
|
||||
while (target) {
|
||||
offset += target.offsetLeft;
|
||||
target = target.offsetParent;
|
||||
}
|
||||
} else {
|
||||
while (target) {
|
||||
offset += target.offsetTop;
|
||||
target = target.offsetParent;
|
||||
}
|
||||
}
|
||||
|
||||
const currentPage = this.split === 'vertical' ? e.pageX : e.pageY;
|
||||
const targetOffset = this.split === 'vertical' ? e.currentTarget.offsetWidth : e.currentTarget.offsetHeight;
|
||||
const percent = Math.floor(((currentPage - offset) / targetOffset) * 10000) / 100;
|
||||
if (percent > this.margin && percent < 100 - this.margin) {
|
||||
this.percent = percent;
|
||||
}
|
||||
this.$emit('resize');
|
||||
this.hasMoved = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
7
src/components/SplitPane/index.js
Normal file
7
src/components/SplitPane/index.js
Normal file
|
@ -0,0 +1,7 @@
|
|||
import SplitPane from './a.vue';
|
||||
import Pane from './Pane.vue';
|
||||
|
||||
export {
|
||||
SplitPane,
|
||||
Pane
|
||||
}
|
73
src/components/Sticky/index.vue
Normal file
73
src/components/Sticky/index.vue
Normal file
|
@ -0,0 +1,73 @@
|
|||
<template>
|
||||
<div :style="{height:height+'px',zIndex:zIndex}">
|
||||
<div :class="className" :style="{top:stickyTop+'px',zIndex:zIndex,position:position,width:width,height:height+'px'}">
|
||||
<slot>
|
||||
<div>sticky</div>
|
||||
</slot>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
name: 'Sticky',
|
||||
props: {
|
||||
stickyTop: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
zIndex: {
|
||||
type: Number,
|
||||
default: 1000
|
||||
},
|
||||
className: {
|
||||
type: String
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
active: false,
|
||||
position: '',
|
||||
currentTop: '',
|
||||
width: undefined,
|
||||
height: undefined,
|
||||
child: null,
|
||||
stickyHeight: 0
|
||||
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
sticky() {
|
||||
if (this.active) {
|
||||
return
|
||||
}
|
||||
this.position = 'fixed';
|
||||
this.active = true;
|
||||
this.width = this.width + 'px';
|
||||
},
|
||||
reset() {
|
||||
if (!this.active) {
|
||||
return
|
||||
}
|
||||
this.position = '';
|
||||
this.width = 'auto'
|
||||
this.active = false
|
||||
},
|
||||
handleScroll() {
|
||||
this.width = this.$el.getBoundingClientRect().width;
|
||||
const offsetTop = this.$el.getBoundingClientRect().top;
|
||||
if (offsetTop <= this.stickyTop) {
|
||||
this.sticky();
|
||||
return
|
||||
}
|
||||
this.reset()
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.height = this.$el.getBoundingClientRect().height;
|
||||
window.addEventListener('scroll', this.handleScroll);
|
||||
},
|
||||
destroyed() {
|
||||
window.removeEventListener('scroll', this.handleScroll);
|
||||
}
|
||||
};
|
||||
</script>
|
119
src/components/Tinymce/components/editorAudio.vue
Normal file
119
src/components/Tinymce/components/editorAudio.vue
Normal file
|
@ -0,0 +1,119 @@
|
|||
<template>
|
||||
<div class="upload-container">
|
||||
<el-button :style="{background:color,borderColor:color}" @click=" dialogVisible=true" type="primary">上传音频
|
||||
</el-button>
|
||||
<el-dialog v-model="dialogVisible">
|
||||
<el-form ref="form" :model="form" :rules="rules" label-width="100px" label-position="right">
|
||||
<el-upload
|
||||
class="editor-audio-upload"
|
||||
action="https://upload.qbox.me"
|
||||
:data="dataObj"
|
||||
:show-file-list="true"
|
||||
:file-list="audioList"
|
||||
:on-success="handleAudioScucess"
|
||||
:on-change="handleAudioChange"
|
||||
:before-upload="audioBeforeUpload">
|
||||
<el-button size="small" type="primary">上传音频</el-button>
|
||||
</el-upload>
|
||||
<el-form-item prop="url" label="音频URL">
|
||||
<el-input v-model="form.url"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="title" label="音频标题">
|
||||
<el-input v-model="form.title"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="音频文本">
|
||||
<el-input type="textarea" :autosize="{ minRows: 2}" v-model="form.text"></el-input>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||
<el-button type="primary" @click="handleSubmit">确 定</el-button>
|
||||
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { getToken } from 'api/qiniu';
|
||||
|
||||
export default {
|
||||
name: 'editorAudioUpload',
|
||||
props: {
|
||||
color: {
|
||||
type: String,
|
||||
default: '#20a0ff'
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
dialogVisible: false,
|
||||
dataObj: { token: '', key: '' },
|
||||
audioList: [],
|
||||
tempAudioUrl: '',
|
||||
form: {
|
||||
title: '',
|
||||
url: '',
|
||||
text: ''
|
||||
},
|
||||
rules: {
|
||||
title: [
|
||||
{ required: true, trigger: 'blur' }
|
||||
],
|
||||
url: [
|
||||
{ required: true, trigger: 'blur' }
|
||||
]
|
||||
}
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
handleSubmit() {
|
||||
this.$refs.form.validate(valid => {
|
||||
if (valid) {
|
||||
this.$emit('successCBK', this.form);
|
||||
this.dialogVisible = false;
|
||||
this.form = {
|
||||
title: '',
|
||||
url: '',
|
||||
text: ''
|
||||
}
|
||||
} else {
|
||||
this.$message('填写有误');
|
||||
return false;
|
||||
}
|
||||
});
|
||||
},
|
||||
handleAudioChange(file, fileList) {
|
||||
this.audioList = fileList.slice(-1);
|
||||
},
|
||||
handleAudioScucess() {
|
||||
this.form.url = this.tempAudioUrl
|
||||
},
|
||||
audioBeforeUpload() {
|
||||
const _self = this;
|
||||
return new Promise((resolve, reject) => {
|
||||
getToken().then(response => {
|
||||
const key = response.data.qiniu_key;
|
||||
const token = response.data.qiniu_token;
|
||||
_self._data.dataObj.token = token;
|
||||
_self._data.dataObj.key = key;
|
||||
this.tempAudioUrl = response.data.qiniu_url;
|
||||
resolve(true);
|
||||
}).catch(err => {
|
||||
console.log(err);
|
||||
reject(false)
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style rel="stylesheet/scss" lang="scss" scoped>
|
||||
.upload-container {
|
||||
.editor-audio-upload {
|
||||
button {
|
||||
float: left;
|
||||
margin-left: 30px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
85
src/components/Tinymce/components/editorImage.vue
Normal file
85
src/components/Tinymce/components/editorImage.vue
Normal file
|
@ -0,0 +1,85 @@
|
|||
<template>
|
||||
<div class="upload-container">
|
||||
<el-button icon='upload' :style="{background:color,borderColor:color}" @click=" dialogVisible=true" type="primary">上传图片
|
||||
</el-button>
|
||||
<el-dialog v-model="dialogVisible">
|
||||
<el-upload
|
||||
class="editor-slide-upload"
|
||||
action="https://upload.qbox.me"
|
||||
:data="dataObj"
|
||||
:multiple="true"
|
||||
:file-list="fileList"
|
||||
:show-file-list="true"
|
||||
list-type="picture-card"
|
||||
:on-remove="handleRemove"
|
||||
:before-upload="beforeUpload">
|
||||
<el-button size="small" type="primary">点击上传</el-button>
|
||||
</el-upload>
|
||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||
<el-button type="primary" @click="handleSubmit">确 定</el-button>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { getToken } from 'api/qiniu';
|
||||
|
||||
export default {
|
||||
name: 'editorSlideUpload',
|
||||
props: {
|
||||
color: {
|
||||
type: String,
|
||||
default: '#20a0ff'
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
dialogVisible: false,
|
||||
dataObj: { token: '', key: '' },
|
||||
list: [],
|
||||
fileList: []
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
handleSubmit() {
|
||||
const arr = this.list.map(v => v.url);
|
||||
this.$emit('successCBK', arr);
|
||||
this.list = [];
|
||||
this.fileList = [];
|
||||
this.dialogVisible = false;
|
||||
},
|
||||
handleRemove(file) {
|
||||
const key = file.response.key;
|
||||
for (let i = 0, len = this.list.length; i < len; i++) {
|
||||
if (this.list[i].key === key) {
|
||||
this.list.splice(i, 1);
|
||||
return
|
||||
}
|
||||
}
|
||||
},
|
||||
beforeUpload() {
|
||||
const _self = this;
|
||||
return new Promise((resolve, reject) => {
|
||||
getToken().then(response => {
|
||||
const key = response.data.qiniu_key;
|
||||
const token = response.data.qiniu_token;
|
||||
_self._data.dataObj.token = token;
|
||||
_self._data.dataObj.key = key;
|
||||
this.list.push({ key, url: response.data.qiniu_url });
|
||||
resolve(true);
|
||||
}).catch(err => {
|
||||
console.log(err);
|
||||
reject(false)
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style rel="stylesheet/scss" lang="scss" scoped>
|
||||
.upload-container {
|
||||
.editor-slide-upload {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
}
|
||||
</style>
|
82
src/components/Tinymce/components/editorSlide.vue
Normal file
82
src/components/Tinymce/components/editorSlide.vue
Normal file
|
@ -0,0 +1,82 @@
|
|||
<template>
|
||||
<div class="upload-container">
|
||||
<el-button :style="{background:color,borderColor:color}" @click=" dialogVisible=true" type="primary">上传轮播图
|
||||
</el-button>
|
||||
<el-dialog v-model="dialogVisible">
|
||||
<el-upload
|
||||
class="editor-slide-upload"
|
||||
action="https://upload.qbox.me"
|
||||
:data="dataObj"
|
||||
:multiple="true"
|
||||
:show-file-list="true"
|
||||
list-type="picture-card"
|
||||
:on-remove="handleRemove"
|
||||
:before-upload="beforeUpload">
|
||||
<el-button size="small" type="primary">点击上传</el-button>
|
||||
</el-upload>
|
||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||
<el-button type="primary" @click="handleSubmit">确 定</el-button>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { getToken } from 'api/qiniu';
|
||||
|
||||
export default {
|
||||
name: 'editorSlideUpload',
|
||||
props: {
|
||||
color: {
|
||||
type: String,
|
||||
default: '#20a0ff'
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
dialogVisible: false,
|
||||
dataObj: { token: '', key: '' },
|
||||
list: []
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
handleSubmit() {
|
||||
const arr = this.list.map(v => v.url);
|
||||
this.$emit('successCBK', arr);
|
||||
this.list = [];
|
||||
this.dialogVisible = false;
|
||||
},
|
||||
handleRemove(file) {
|
||||
const key = file.response.key;
|
||||
for (let i = 0, len = this.list.length; i < len; i++) {
|
||||
if (this.list[i].key === key) {
|
||||
this.list.splice(i, 1);
|
||||
return
|
||||
}
|
||||
}
|
||||
},
|
||||
beforeUpload() {
|
||||
const _self = this;
|
||||
return new Promise((resolve, reject) => {
|
||||
getToken().then(response => {
|
||||
const key = response.data.qiniu_key;
|
||||
const token = response.data.qiniu_token;
|
||||
_self._data.dataObj.token = token;
|
||||
_self._data.dataObj.key = key;
|
||||
this.list.push({ key, url: response.data.qiniu_url });
|
||||
resolve(true);
|
||||
}).catch(err => {
|
||||
console.log(err);
|
||||
reject(false)
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style rel="stylesheet/scss" lang="scss" scoped>
|
||||
.upload-container {
|
||||
.editor-slide-upload {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
}
|
||||
</style>
|
167
src/components/Tinymce/components/editorVideo.vue
Normal file
167
src/components/Tinymce/components/editorVideo.vue
Normal file
|
@ -0,0 +1,167 @@
|
|||
<template>
|
||||
<div class="upload-container">
|
||||
<el-button :style="{background:color,borderColor:color}" @click=" dialogVisible=true" type="primary">上传视频</el-button>
|
||||
<el-dialog v-model="dialogVisible">
|
||||
<el-form ref="form" :model="form" :rules="rules" label-width="140px" label-position="left">
|
||||
<el-upload
|
||||
class="editor-video-upload"
|
||||
action="https://upload.qbox.me"
|
||||
:data="dataObj"
|
||||
:show-file-list="true"
|
||||
:file-list="videoList"
|
||||
:on-success="handleVideoScucess"
|
||||
:on-change="handleVideoChange"
|
||||
:before-upload="videoBeforeUpload">
|
||||
<el-button size="small" type="primary">上传视频</el-button>
|
||||
</el-upload>
|
||||
<el-form-item prop="url" label="视频URL">
|
||||
<el-input v-model="form.url"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="title" label="视频标题">
|
||||
<el-input v-model="form.title"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="上传视频封面图">
|
||||
</el-form-item>
|
||||
<el-upload
|
||||
class="image-uploader"
|
||||
action="https://upload.qbox.me"
|
||||
:show-file-list="false"
|
||||
:data="dataObj"
|
||||
:on-success="handleImageScucess"
|
||||
:before-upload="beforeImageUpload">
|
||||
<img v-if="form.image" :src="form.image" class="image-uploader-image">
|
||||
<i v-else class="el-icon-plus avatar-uploader-icon"></i>
|
||||
</el-upload>
|
||||
</el-form>
|
||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||
<el-button type="primary" @click="handleSubmit">确 定</el-button>
|
||||
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { getToken } from 'api/qiniu';
|
||||
|
||||
export default {
|
||||
name: 'editorVideoUpload',
|
||||
props: {
|
||||
color: {
|
||||
type: String,
|
||||
default: '#20a0ff'
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
dialogVisible: false,
|
||||
dataObj: { token: '', key: '' },
|
||||
videoList: [],
|
||||
tempVideoUrl: '',
|
||||
tempImageUrl: '',
|
||||
form: {
|
||||
title: '',
|
||||
url: '',
|
||||
image: ''
|
||||
},
|
||||
rules: {
|
||||
url: [
|
||||
{ required: true, trigger: 'blur' }
|
||||
],
|
||||
title: [
|
||||
{ required: true, trigger: 'blur' }
|
||||
]
|
||||
}
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
handleSubmit() {
|
||||
this.$refs.form.validate(valid => {
|
||||
if (valid) {
|
||||
if (this.form.image.length > 0) {
|
||||
this.$emit('successCBK', this.form);
|
||||
this.dialogVisible = false;
|
||||
this.form = {
|
||||
title: '',
|
||||
url: '',
|
||||
image: ''
|
||||
}
|
||||
} else {
|
||||
this.$message('请上传图片');
|
||||
}
|
||||
} else {
|
||||
this.$message('填写有误');
|
||||
return false;
|
||||
}
|
||||
});
|
||||
},
|
||||
handleVideoChange(file, fileList) {
|
||||
this.videoList = fileList.slice(-1);
|
||||
},
|
||||
handleVideoScucess() {
|
||||
this.form.url = this.tempVideoUrl
|
||||
},
|
||||
videoBeforeUpload() {
|
||||
const _self = this;
|
||||
return new Promise((resolve, reject) => {
|
||||
getToken().then(response => {
|
||||
const key = response.data.qiniu_key;
|
||||
const token = response.data.qiniu_token;
|
||||
_self._data.dataObj.token = token;
|
||||
_self._data.dataObj.key = key;
|
||||
this.tempVideoUrl = response.data.qiniu_url;
|
||||
resolve(true);
|
||||
}).catch(err => {
|
||||
console.log(err);
|
||||
reject(false)
|
||||
});
|
||||
});
|
||||
},
|
||||
handleImageScucess() {
|
||||
this.form.image = this.tempImageUrl
|
||||
},
|
||||
beforeImageUpload() {
|
||||
const _self = this;
|
||||
return new Promise((resolve, reject) => {
|
||||
getToken().then(response => {
|
||||
const key = response.data.qiniu_key;
|
||||
const token = response.data.qiniu_token;
|
||||
_self._data.dataObj.token = token;
|
||||
_self._data.dataObj.key = key;
|
||||
this.tempImageUrl = response.data.qiniu_url;
|
||||
resolve(true);
|
||||
}).catch(err => {
|
||||
console.log(err);
|
||||
reject(false)
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style rel="stylesheet/scss" lang="scss" scoped>
|
||||
.upload-container {
|
||||
.editor-video-upload {
|
||||
button {
|
||||
float: left;
|
||||
}
|
||||
}
|
||||
.image-uploader {
|
||||
margin: 5px auto;
|
||||
width: 400px;
|
||||
height: 200px;
|
||||
border: 1px dashed #d9d9d9;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
line-height: 200px;
|
||||
i {
|
||||
font-size: 28px;
|
||||
color: #8c939d;
|
||||
}
|
||||
.image-uploader-image {
|
||||
height: 200px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
251
src/components/Tinymce/index.vue
Normal file
251
src/components/Tinymce/index.vue
Normal file
|
@ -0,0 +1,251 @@
|
|||
<template>
|
||||
<div class='tinymce-container editor-container'>
|
||||
<textarea class='tinymce-textarea' :id="id"></textarea>
|
||||
<div class="editor-custom-btn-container">
|
||||
<editorSlide v-if="customButton.indexOf('editorSlide')>=0" color="#3A71A8" class="editor-upload-btn" @successCBK="slideSuccessCBK"></editorSlide>
|
||||
<editorAudio v-if="customButton.indexOf('editorAudio')>=0" color="#30B08F" class="editor-upload-btn" @successCBK="aduioSuccessCBK"></editorAudio>
|
||||
<editorVideo v-if="customButton.indexOf('editorVideo')>=0" color="#E65D6E" class="editor-upload-btn" @successCBK="videoSuccessCBK"></editorVideo>
|
||||
<editorImage v-if="customButton.indexOf('editorImage')>=0" color="#20a0ff" class="editor-upload-btn" @successCBK="imageSuccessCBK"></editorImage>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import editorAudio from './components/editorAudio';
|
||||
import editorVideo from './components/editorVideo';
|
||||
import editorSlide from './components/editorSlide';
|
||||
import editorImage from './components/editorImage';
|
||||
import { getToken, upload } from 'api/qiniu';
|
||||
export default {
|
||||
name: 'tinymce',
|
||||
components: { editorImage, editorAudio, editorSlide, editorVideo },
|
||||
props: {
|
||||
id: {
|
||||
type: String,
|
||||
default: 'tinymceEditor'
|
||||
},
|
||||
value: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
customButton: {
|
||||
type: Array,
|
||||
required: false,
|
||||
default() {
|
||||
return ['editorAudio', 'editorImage']
|
||||
}
|
||||
},
|
||||
toolbar: {
|
||||
type: Array,
|
||||
required: false,
|
||||
default() {
|
||||
return ['removeformat undo redo | bullist numlist | outdent indent | forecolor | fullscreen code', 'bold italic blockquote | h2 p media link | alignleft aligncenter alignright']
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
hasChange: false,
|
||||
hasInit: false
|
||||
}
|
||||
},
|
||||
menubar: {
|
||||
default: ''
|
||||
},
|
||||
height: {
|
||||
type: Number,
|
||||
required: false,
|
||||
default: 360
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value(val) {
|
||||
if (!this.hasChange && this.hasInit) {
|
||||
this.$nextTick(() => tinymce.get(this.id).setContent(val))
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
const _this = this;
|
||||
tinymce.init({
|
||||
selector: `#${this.id}`,
|
||||
height: this.height,
|
||||
body_class: 'panel-body ',
|
||||
object_resizing: false,
|
||||
// language: 'zh_CN',
|
||||
// language_url: '/static/tinymce/langs/zh_CN.js',
|
||||
toolbar: this.toolbar,
|
||||
menubar: this.menubar,
|
||||
plugins: 'advlist,autolink,code,powerpaste,textcolor, colorpicker,fullscreen,link,lists,media,wordcount, imagetools,watermark',
|
||||
end_container_on_empty_block: true,
|
||||
powerpaste_word_import: 'clean',
|
||||
code_dialog_height: 450,
|
||||
code_dialog_width: 1000,
|
||||
advlist_bullet_styles: 'square',
|
||||
advlist_number_styles: 'default',
|
||||
block_formats: '普通标签=p;小标题=h2;',
|
||||
imagetools_cors_hosts: ['wpimg.wallstcn.com', 'wallstreetcn.com'],
|
||||
imagetools_toolbar: 'watermark',
|
||||
default_link_target: '_blank',
|
||||
link_title: false,
|
||||
textcolor_map: [
|
||||
'1482f0', '1482f0',
|
||||
'4595e6', '4595e6'],
|
||||
init_instance_callback: editor => {
|
||||
if (_this.value) {
|
||||
editor.setContent(_this.value)
|
||||
}
|
||||
_this.hasInit = true;
|
||||
editor.on('Change', () => {
|
||||
this.hasChange = true;
|
||||
this.$emit('input', editor.getContent({ format: 'raw' }));
|
||||
});
|
||||
},
|
||||
images_dataimg_filter(img) {
|
||||
setTimeout(() => {
|
||||
const $image = $(img);
|
||||
$image.removeAttr('width');
|
||||
$image.removeAttr('height');
|
||||
if ($image[0].height && $image[0].width) {
|
||||
$image.attr('data-wscntype', 'image');
|
||||
$image.attr('data-wscnh', $image[0].height);
|
||||
$image.attr('data-wscnw', $image[0].width);
|
||||
$image.addClass('wscnph');
|
||||
}
|
||||
}, 0);
|
||||
return img
|
||||
},
|
||||
images_upload_handler(blobInfo, success, failure, progress) {
|
||||
progress(0);
|
||||
const token = _this.$store.getters.token;
|
||||
getToken(token).then(response => {
|
||||
const url = response.data.qiniu_url;
|
||||
const formData = new FormData();
|
||||
formData.append('token', response.data.qiniu_token);
|
||||
formData.append('key', response.data.qiniu_key);
|
||||
formData.append('file', blobInfo.blob(), url);
|
||||
upload(formData).then(() => {
|
||||
success(url);
|
||||
progress(100);
|
||||
// setTimeout(() => {
|
||||
// const doc = tinymce.activeEditor.getDoc();
|
||||
// const $$ = tinymce.dom.DomQuery;
|
||||
// const $image = $$(doc).find('img[src="' + url + '"]')
|
||||
// $image.addClass('wscnph');
|
||||
// $image.attr('data-wscntype', 'image');
|
||||
// $image.attr('data-wscnh', $image[0].height || 640);
|
||||
// $image.attr('data-wscnw', $image[0].width || 640);
|
||||
// }, 0);
|
||||
})
|
||||
}).catch(err => {
|
||||
failure('出现未知问题,刷新页面,或者联系程序员')
|
||||
console.log(err);
|
||||
});
|
||||
},
|
||||
setup(editor) {
|
||||
editor.addButton('h2', {
|
||||
title: '小标题', // tooltip text seen on mouseover
|
||||
text: '小标题',
|
||||
onclick() {
|
||||
editor.execCommand('mceToggleFormat', false, 'h2');
|
||||
},
|
||||
onPostRender() {
|
||||
const btn = this;
|
||||
editor.on('init', () => {
|
||||
editor.formatter.formatChanged('h2', state => {
|
||||
btn.active(state);
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
editor.addButton('p', {
|
||||
title: '正文', // tooltip text seen on mouseover
|
||||
text: '正文',
|
||||
onclick() {
|
||||
editor.execCommand('mceToggleFormat', false, 'p');
|
||||
},
|
||||
onPostRender() {
|
||||
const btn = this;
|
||||
editor.on('init', () => {
|
||||
editor.formatter.formatChanged('p', state => {
|
||||
btn.active(state);
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
imageSuccessCBK(arr) {
|
||||
console.log(arr)
|
||||
const _this = this;
|
||||
arr.forEach(v => {
|
||||
const node = document.createElement('img');
|
||||
node.setAttribute('src', v);
|
||||
node.onload = function() {
|
||||
$(this).addClass('wscnph');
|
||||
$(this).attr('data-wscntype', 'image');
|
||||
$(this).attr('data-wscnh', this.height);
|
||||
$(this).attr('data-wscnw', this.width);
|
||||
tinymce.get(_this.id).insertContent(node.outerHTML)
|
||||
}
|
||||
})
|
||||
},
|
||||
slideSuccessCBK(arr) {
|
||||
const node = document.createElement('img');
|
||||
node.setAttribute('data-wscntype', 'slide');
|
||||
node.setAttribute('data-uri', arr.toString());
|
||||
node.setAttribute('data-wscnh', '190');
|
||||
node.setAttribute('data-wscnw', '200');
|
||||
node.setAttribute('src', ' https://wdl.wallstreetcn.com/6410b47d-a54c-4826-9bc1-c3e5df31280c');
|
||||
node.className = 'wscnph editor-placeholder';
|
||||
tinymce.get(this.id).insertContent(node.outerHTML)
|
||||
},
|
||||
videoSuccessCBK(form) {
|
||||
const node = document.createElement('img');
|
||||
node.setAttribute('data-wscntype', 'video');
|
||||
node.setAttribute('data-uri', form.url);
|
||||
node.setAttribute('data-cover-img-uri', form.image);
|
||||
node.setAttribute('data-title', form.title);
|
||||
node.setAttribute('src', 'https://wdl.wallstreetcn.com/07aeb3e7-f4ca-4207-befb-c987b3dc7011');
|
||||
node.className = 'wscnph editor-placeholder';
|
||||
tinymce.get(this.id).insertContent(node.outerHTML)
|
||||
},
|
||||
aduioSuccessCBK(form) {
|
||||
const node = document.createElement('img');
|
||||
node.setAttribute('data-wscntype', 'audio');
|
||||
node.setAttribute('data-uri', form.url);
|
||||
node.setAttribute('data-title', form.title);
|
||||
node.setAttribute('data-text', form.text);
|
||||
node.setAttribute('src', 'https://wdl.wallstreetcn.com/2ed0c8c8-fb82-499d-b81c-3fd1de114eae');
|
||||
node.className = 'wscnph editor-placeholder';
|
||||
tinymce.get(this.id).insertContent(node.outerHTML)
|
||||
}
|
||||
},
|
||||
destroyed() {
|
||||
tinymce.get(this.id).destroy();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.tinymce-container {
|
||||
position: relative
|
||||
}
|
||||
|
||||
.tinymce-textarea {
|
||||
visibility: hidden;
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
.editor-custom-btn-container {
|
||||
position: absolute;
|
||||
right: 15px;
|
||||
/*z-index: 2005;*/
|
||||
top: 18px;
|
||||
}
|
||||
|
||||
.editor-upload-btn {
|
||||
display: inline-block;
|
||||
}
|
||||
</style>
|
128
src/components/Upload/singleImage.vue
Normal file
128
src/components/Upload/singleImage.vue
Normal file
|
@ -0,0 +1,128 @@
|
|||
<template>
|
||||
<div class="upload-container">
|
||||
<el-upload
|
||||
class="image-uploader"
|
||||
:data="dataObj"
|
||||
drag
|
||||
:multiple="false"
|
||||
:show-file-list="false"
|
||||
action="https://upload.qbox.me"
|
||||
:before-upload="beforeUpload"
|
||||
:on-success="handleImageScucess">
|
||||
<i class="el-icon-upload"></i>
|
||||
<div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
|
||||
</el-upload>
|
||||
<div class="image-preview">
|
||||
<div class="image-preview-wrapper" v-show="imageUrl.length>1">
|
||||
<img :src="imageUrl+'?imageView2/1/w/200/h/200'">
|
||||
<div class="image-preview-action">
|
||||
<i @click="rmImage" class="el-icon-delete"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
// 预览效果见付费文章
|
||||
import { getToken } from 'api/qiniu';
|
||||
export default {
|
||||
name: 'singleImageUpload',
|
||||
props: {
|
||||
value: String
|
||||
},
|
||||
computed: {
|
||||
imageUrl() {
|
||||
return this.value
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
tempUrl: '',
|
||||
dataObj: { token: '', key: '' }
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
rmImage() {
|
||||
this.emitInput('');
|
||||
},
|
||||
emitInput(val) {
|
||||
this.$emit('input', val);
|
||||
},
|
||||
handleImageScucess() {
|
||||
this.emitInput(this.tempUrl)
|
||||
},
|
||||
beforeUpload() {
|
||||
const _self = this;
|
||||
return new Promise((resolve, reject) => {
|
||||
getToken().then(response => {
|
||||
const key = response.data.qiniu_key;
|
||||
const token = response.data.qiniu_token;
|
||||
_self._data.dataObj.token = token;
|
||||
_self._data.dataObj.key = key;
|
||||
this.tempUrl = response.data.qiniu_url;
|
||||
resolve(true);
|
||||
}).catch(err => {
|
||||
console.log(err);
|
||||
reject(false)
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style rel="stylesheet/scss" lang="scss" scoped>
|
||||
@import "src/styles/mixin.scss";
|
||||
.upload-container {
|
||||
width: 100%;
|
||||
position: relative;
|
||||
@include clearfix;
|
||||
.image-uploader {
|
||||
width: 60%;
|
||||
float: left;
|
||||
}
|
||||
.image-preview {
|
||||
width: 200px;
|
||||
height: 200px;
|
||||
position: relative;
|
||||
border: 1px dashed #d9d9d9;
|
||||
float: left;
|
||||
margin-left: 50px;
|
||||
.image-preview-wrapper {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
.image-preview-action {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
left: 0;
|
||||
top: 0;
|
||||
cursor: default;
|
||||
text-align: center;
|
||||
color: #fff;
|
||||
opacity: 0;
|
||||
font-size: 20px;
|
||||
background-color: rgba(0, 0, 0, .5);
|
||||
transition: opacity .3s;
|
||||
cursor: pointer;
|
||||
text-align: center;
|
||||
line-height: 200px;
|
||||
.el-icon-delete {
|
||||
font-size: 36px;
|
||||
}
|
||||
}
|
||||
&:hover {
|
||||
.image-preview-action {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
126
src/components/Upload/singleImage2.vue
Normal file
126
src/components/Upload/singleImage2.vue
Normal file
|
@ -0,0 +1,126 @@
|
|||
<template>
|
||||
<div class="singleImageUpload2 upload-container">
|
||||
<el-upload
|
||||
class="image-uploader"
|
||||
:data="dataObj"
|
||||
drag
|
||||
:multiple="false"
|
||||
:show-file-list="false"
|
||||
action="https://upload.qbox.me"
|
||||
:before-upload="beforeUpload"
|
||||
:on-success="handleImageScucess">
|
||||
<i class="el-icon-upload"></i>
|
||||
<div class="el-upload__text">Drag或<em>点击上传</em></div>
|
||||
</el-upload>
|
||||
<div v-show="imageUrl.length>0" class="image-preview">
|
||||
<div class="image-preview-wrapper" v-show="imageUrl.length>1">
|
||||
<img :src="imageUrl">
|
||||
<div class="image-preview-action">
|
||||
<i @click="rmImage" class="el-icon-delete"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
// 预览效果见专题
|
||||
import { getToken } from 'api/qiniu';
|
||||
export default {
|
||||
name: 'singleImageUpload2',
|
||||
props: {
|
||||
value: String
|
||||
},
|
||||
computed: {
|
||||
imageUrl() {
|
||||
return this.value
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
tempUrl: '',
|
||||
dataObj: { token: '', key: '' }
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
rmImage() {
|
||||
this.emitInput('');
|
||||
},
|
||||
emitInput(val) {
|
||||
this.$emit('input', val);
|
||||
},
|
||||
handleImageScucess() {
|
||||
this.emitInput(this.tempUrl)
|
||||
},
|
||||
beforeUpload() {
|
||||
const _self = this;
|
||||
return new Promise((resolve, reject) => {
|
||||
getToken().then(response => {
|
||||
const key = response.data.qiniu_key;
|
||||
const token = response.data.qiniu_token;
|
||||
_self._data.dataObj.token = token;
|
||||
_self._data.dataObj.key = key;
|
||||
this.tempUrl = response.data.qiniu_url;
|
||||
resolve(true);
|
||||
}).catch(err => {
|
||||
console.log(err);
|
||||
reject(false)
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style rel="stylesheet/scss" lang="scss" scoped>
|
||||
.upload-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
.image-uploader{
|
||||
height: 100%;
|
||||
}
|
||||
.image-preview {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
left: 0px;
|
||||
top: 0px;
|
||||
border: 1px dashed #d9d9d9;
|
||||
.image-preview-wrapper {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
.image-preview-action {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
left: 0;
|
||||
top: 0;
|
||||
cursor: default;
|
||||
text-align: center;
|
||||
color: #fff;
|
||||
opacity: 0;
|
||||
font-size: 20px;
|
||||
background-color: rgba(0, 0, 0, .5);
|
||||
transition: opacity .3s;
|
||||
cursor: pointer;
|
||||
text-align: center;
|
||||
line-height: 200px;
|
||||
.el-icon-delete {
|
||||
font-size: 36px;
|
||||
}
|
||||
}
|
||||
&:hover {
|
||||
.image-preview-action {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
154
src/components/Upload/singleImage3.vue
Normal file
154
src/components/Upload/singleImage3.vue
Normal file
|
@ -0,0 +1,154 @@
|
|||
<template>
|
||||
<div class="upload-container">
|
||||
<el-upload
|
||||
class="image-uploader"
|
||||
:data="dataObj"
|
||||
drag
|
||||
:multiple="false"
|
||||
:show-file-list="false"
|
||||
action="https://upload.qbox.me"
|
||||
:before-upload="beforeUpload"
|
||||
:on-success="handleImageScucess">
|
||||
<i class="el-icon-upload"></i>
|
||||
<div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
|
||||
</el-upload>
|
||||
<div class="image-preview image-app-preview">
|
||||
<div class="image-preview-wrapper" v-show="imageUrl.length>1">
|
||||
<div class='app-fake-conver'>  全球 付费节目单 最热 经济</div>
|
||||
<img :src="imageUrl+'?imageView2/1/h/180/w/320/q/100'">
|
||||
<div class="image-preview-action">
|
||||
<i @click="rmImage" class="el-icon-delete"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="image-preview">
|
||||
<div class="image-preview-wrapper" v-show="imageUrl.length>1">
|
||||
<img :src="imageUrl+'?imageView2/1/w/200/h/200'">
|
||||
<div class="image-preview-action">
|
||||
<i @click="rmImage" class="el-icon-delete"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
// 预览效果见文章
|
||||
import { getToken } from 'api/qiniu';
|
||||
export default {
|
||||
name: 'singleImageUpload',
|
||||
props: {
|
||||
value: String
|
||||
},
|
||||
computed: {
|
||||
imageUrl() {
|
||||
return this.value
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
tempUrl: '',
|
||||
dataObj: { token: '', key: '' }
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
rmImage() {
|
||||
this.emitInput('');
|
||||
},
|
||||
emitInput(val) {
|
||||
this.$emit('input', val);
|
||||
},
|
||||
handleImageScucess() {
|
||||
this.emitInput(this.tempUrl)
|
||||
},
|
||||
beforeUpload() {
|
||||
const _self = this;
|
||||
return new Promise((resolve, reject) => {
|
||||
getToken().then(response => {
|
||||
const key = response.data.qiniu_key;
|
||||
const token = response.data.qiniu_token;
|
||||
_self._data.dataObj.token = token;
|
||||
_self._data.dataObj.key = key;
|
||||
this.tempUrl = response.data.qiniu_url;
|
||||
resolve(true);
|
||||
}).catch(err => {
|
||||
console.log(err);
|
||||
reject(false)
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style rel="stylesheet/scss" lang="scss" scoped>
|
||||
@import "src/styles/mixin.scss";
|
||||
.upload-container {
|
||||
width: 100%;
|
||||
position: relative;
|
||||
@include clearfix;
|
||||
.image-uploader {
|
||||
width: 35%;
|
||||
float: left;
|
||||
}
|
||||
.image-preview {
|
||||
width: 200px;
|
||||
height: 200px;
|
||||
position: relative;
|
||||
border: 1px dashed #d9d9d9;
|
||||
float: left;
|
||||
margin-left: 50px;
|
||||
.image-preview-wrapper {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
.image-preview-action {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
left: 0;
|
||||
top: 0;
|
||||
cursor: default;
|
||||
text-align: center;
|
||||
color: #fff;
|
||||
opacity: 0;
|
||||
font-size: 20px;
|
||||
background-color: rgba(0, 0, 0, .5);
|
||||
transition: opacity .3s;
|
||||
cursor: pointer;
|
||||
text-align: center;
|
||||
line-height: 200px;
|
||||
.el-icon-delete {
|
||||
font-size: 36px;
|
||||
}
|
||||
}
|
||||
&:hover {
|
||||
.image-preview-action {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
.image-app-preview{
|
||||
width: 320px;
|
||||
height: 180px;
|
||||
position: relative;
|
||||
border: 1px dashed #d9d9d9;
|
||||
float: left;
|
||||
margin-left: 50px;
|
||||
.app-fake-conver{
|
||||
height: 44px;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
// background: rgba(0, 0, 0, .1);
|
||||
text-align: center;
|
||||
line-height: 64px;
|
||||
color: #fff;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
64
src/components/jsonEditor/index.vue
Normal file
64
src/components/jsonEditor/index.vue
Normal file
|
@ -0,0 +1,64 @@
|
|||
<template>
|
||||
<div class='json-editor'>
|
||||
<textarea ref='textarea'></textarea>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
<script>
|
||||
import CodeMirror from 'codemirror';
|
||||
import 'codemirror/addon/lint/lint.css';
|
||||
import 'codemirror/lib/codemirror.css';
|
||||
import 'codemirror/theme/rubyblue.css';
|
||||
require('script-loader!jsonlint');
|
||||
import 'codemirror/mode/javascript/javascript'
|
||||
import 'codemirror/addon/lint/lint'
|
||||
import 'codemirror/addon/lint/json-lint';
|
||||
|
||||
export default {
|
||||
name: 'jsonEditor',
|
||||
data() {
|
||||
return {
|
||||
jsonEditor: false
|
||||
}
|
||||
},
|
||||
props: ['value'],
|
||||
watch: {
|
||||
value(value) {
|
||||
const editor_value = this.jsonEditor.getValue();
|
||||
if (value !== editor_value) {
|
||||
this.jsonEditor.setValue(JSON.stringify(this.value, null, 2));
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.jsonEditor = CodeMirror.fromTextArea(this.$refs.textarea, {
|
||||
lineNumbers: true,
|
||||
mode: 'application/json',
|
||||
gutters: ['CodeMirror-lint-markers'],
|
||||
theme: 'rubyblue',
|
||||
lint: true
|
||||
});
|
||||
|
||||
this.jsonEditor.setValue(JSON.stringify(this.value, null, 2));
|
||||
this.jsonEditor.on('change', cm => {
|
||||
this.$emit('changed', cm.getValue())
|
||||
this.$emit('input', cm.getValue())
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
getValue() {
|
||||
return this.jsonEditor.getValue()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.CodeMirror {
|
||||
height: 100%;
|
||||
}
|
||||
.json-editor .cm-s-rubyblue span.cm-string{
|
||||
color: #F08047;
|
||||
}
|
||||
</style>
|
157
src/components/twoDndList/index.vue
Normal file
157
src/components/twoDndList/index.vue
Normal file
|
@ -0,0 +1,157 @@
|
|||
<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.id}}] {{element.title}}</div>
|
||||
<div style="position:absolute;right:0px;">
|
||||
<a style="float: left ;margin-top: -20px;margin-right:5px;" :href="'/article/edit/'+element.id" target="_blank"><i style="color:#20a0ff" class="el-icon-information"></i></a>
|
||||
<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.id}}] {{element.title}}</div>
|
||||
<a style="float: right ;margin-top: -20px;" :href="'/article/edit/'+element.id" target="_blank"><i style="color:#20a0ff" class="el-icon-information"></i></a>
|
||||
</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;
|
||||
padding: 5px 12px;
|
||||
margin-top: 4px;
|
||||
border: 1px solid #bfcbd9;
|
||||
transition: all 1s;
|
||||
}
|
||||
|
||||
.list-complete-item-handle {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
margin-right: 50px;
|
||||
}
|
||||
.list-complete-item-handle2{
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
.list-complete-item.sortable-chosen {
|
||||
background: #4AB7BD;
|
||||
}
|
||||
|
||||
.list-complete-item.sortable-ghost {
|
||||
background: #30B08F;
|
||||
}
|
||||
|
||||
.list-complete-enter, .list-complete-leave-active {
|
||||
opacity: 0;
|
||||
}
|
||||
</style>
|
101
src/directive/sticky.js
Normal file
101
src/directive/sticky.js
Normal file
|
@ -0,0 +1,101 @@
|
|||
(function() {
|
||||
const vueSticky = {};
|
||||
let listenAction;
|
||||
vueSticky.install = Vue => {
|
||||
Vue.directive('sticky', {
|
||||
inserted(el, binding) {
|
||||
const params = binding.value || {},
|
||||
stickyTop = params.stickyTop || 0,
|
||||
zIndex = params.zIndex || 1000,
|
||||
elStyle = el.style;
|
||||
|
||||
elStyle.position = '-webkit-sticky';
|
||||
elStyle.position = 'sticky';
|
||||
|
||||
// if the browser support css sticky(Currently Safari, Firefox and Chrome Canary)
|
||||
// if (~elStyle.position.indexOf('sticky')) {
|
||||
// elStyle.top = `${stickyTop}px`;
|
||||
// elStyle.zIndex = zIndex;
|
||||
// return
|
||||
// }
|
||||
|
||||
const elHeight = el.getBoundingClientRect().height;
|
||||
const elWidth = el.getBoundingClientRect().width;
|
||||
elStyle.cssText = `top: ${stickyTop}px; z-index: ${zIndex}`;
|
||||
|
||||
const parentElm = el.parentNode || document.documentElement;
|
||||
const placeholder = document.createElement('div');
|
||||
placeholder.style.display = 'none';
|
||||
placeholder.style.width = `${elWidth}px`;
|
||||
placeholder.style.height = `${elHeight}px`;
|
||||
parentElm.insertBefore(placeholder, el)
|
||||
|
||||
let active = false;
|
||||
|
||||
const getScroll = (target, top) => {
|
||||
const prop = top ? 'pageYOffset' : 'pageXOffset';
|
||||
const method = top ? 'scrollTop' : 'scrollLeft';
|
||||
let ret = target[prop];
|
||||
if (typeof ret !== 'number') {
|
||||
ret = window.document.documentElement[method];
|
||||
}
|
||||
return ret;
|
||||
};
|
||||
|
||||
const sticky = () => {
|
||||
if (active) {
|
||||
return
|
||||
}
|
||||
if (!elStyle.height) {
|
||||
elStyle.height = `${el.offsetHeight}px`
|
||||
}
|
||||
|
||||
elStyle.position = 'fixed';
|
||||
elStyle.width = `${elWidth}px`;
|
||||
placeholder.style.display = 'inline-block';
|
||||
active = true
|
||||
};
|
||||
|
||||
const reset = () => {
|
||||
if (!active) {
|
||||
return
|
||||
}
|
||||
|
||||
elStyle.position = '';
|
||||
placeholder.style.display = 'none';
|
||||
active = false;
|
||||
};
|
||||
|
||||
const check = () => {
|
||||
const scrollTop = getScroll(window, true);
|
||||
const offsetTop = el.getBoundingClientRect().top;
|
||||
if (offsetTop < stickyTop) {
|
||||
sticky();
|
||||
} else {
|
||||
if (scrollTop < elHeight + stickyTop) {
|
||||
reset()
|
||||
}
|
||||
}
|
||||
};
|
||||
listenAction = () => {
|
||||
check()
|
||||
};
|
||||
|
||||
window.addEventListener('scroll', listenAction)
|
||||
},
|
||||
|
||||
unbind() {
|
||||
window.removeEventListener('scroll', listenAction)
|
||||
}
|
||||
})
|
||||
};
|
||||
if (typeof exports == 'object') {
|
||||
module.exports = vueSticky
|
||||
} else if (typeof define == 'function' && define.amd) {
|
||||
define([], () => vueSticky)
|
||||
} else if (window.Vue) {
|
||||
window.vueSticky = vueSticky;
|
||||
Vue.use(vueSticky)
|
||||
}
|
||||
}());
|
||||
|
26
src/directive/waves.css
Normal file
26
src/directive/waves.css
Normal file
|
@ -0,0 +1,26 @@
|
|||
.waves-ripple {
|
||||
position: absolute;
|
||||
border-radius: 100%;
|
||||
background-color: rgba(0, 0, 0, 0.15);
|
||||
background-clip: padding-box;
|
||||
pointer-events: none;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
-webkit-transform: scale(0);
|
||||
-ms-transform: scale(0);
|
||||
transform: scale(0);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.waves-ripple.z-active {
|
||||
opacity: 0;
|
||||
-webkit-transform: scale(2);
|
||||
-ms-transform: scale(2);
|
||||
transform: scale(2);
|
||||
-webkit-transition: opacity 1.2s ease-out, -webkit-transform 0.6s ease-out;
|
||||
transition: opacity 1.2s ease-out, -webkit-transform 0.6s ease-out;
|
||||
transition: opacity 1.2s ease-out, transform 0.6s ease-out;
|
||||
transition: opacity 1.2s ease-out, transform 0.6s ease-out, -webkit-transform 0.6s ease-out;
|
||||
}
|
54
src/directive/waves.js
Normal file
54
src/directive/waves.js
Normal file
|
@ -0,0 +1,54 @@
|
|||
import './waves.css';
|
||||
(function() {
|
||||
const vueWaves = {};
|
||||
vueWaves.install = (Vue, options = {}) => {
|
||||
Vue.directive('waves', {
|
||||
bind(el, binding) {
|
||||
el.addEventListener('click', e => {
|
||||
const customOpts = Object.assign(options, binding.value);
|
||||
const opts = Object.assign({
|
||||
ele: el, // 波纹作用元素
|
||||
type: 'hit', // hit点击位置扩散center中心点扩展
|
||||
color: 'rgba(0, 0, 0, 0.15)' // 波纹颜色
|
||||
}, customOpts),
|
||||
target = opts.ele;
|
||||
if (target) {
|
||||
target.style.position = 'relative';
|
||||
target.style.overflow = 'hidden';
|
||||
const rect = target.getBoundingClientRect();
|
||||
let ripple = target.querySelector('.waves-ripple');
|
||||
if (!ripple) {
|
||||
ripple = document.createElement('span');
|
||||
ripple.className = 'waves-ripple';
|
||||
ripple.style.height = ripple.style.width = Math.max(rect.width, rect.height) + 'px';
|
||||
target.appendChild(ripple);
|
||||
} else {
|
||||
ripple.className = 'waves-ripple';
|
||||
}
|
||||
switch (opts.type) {
|
||||
case 'center':
|
||||
ripple.style.top = (rect.height / 2 - ripple.offsetHeight / 2) + 'px';
|
||||
ripple.style.left = (rect.width / 2 - ripple.offsetWidth / 2) + 'px';
|
||||
break;
|
||||
default:
|
||||
ripple.style.top = (e.pageY - rect.top - ripple.offsetHeight / 2 - document.body.scrollTop) + 'px';
|
||||
ripple.style.left = (e.pageX - rect.left - ripple.offsetWidth / 2 - document.body.scrollLeft) + 'px';
|
||||
}
|
||||
ripple.style.backgroundColor = opts.color;
|
||||
ripple.className = 'waves-ripple z-active';
|
||||
return false;
|
||||
}
|
||||
}, false);
|
||||
}
|
||||
})
|
||||
};
|
||||
if (typeof exports == 'object') {
|
||||
module.exports = vueWaves
|
||||
} else if (typeof define == 'function' && define.amd) {
|
||||
define([], () => vueWaves)
|
||||
} else if (window.Vue) {
|
||||
window.vueWaves = vueWaves;
|
||||
Vue.use(vueWaves)
|
||||
}
|
||||
}());
|
||||
|
108
src/filters/index.js
Normal file
108
src/filters/index.js
Normal file
|
@ -0,0 +1,108 @@
|
|||
function pluralize(time, label) {
|
||||
if (time === 1) {
|
||||
return time + label
|
||||
}
|
||||
return time + label + 's'
|
||||
}
|
||||
export function timeAgo(time) {
|
||||
const between = Date.now() / 1000 - Number(time);
|
||||
if (between < 3600) {
|
||||
return pluralize(~~(between / 60), ' minute')
|
||||
} else if (between < 86400) {
|
||||
return pluralize(~~(between / 3600), ' hour')
|
||||
} else {
|
||||
return pluralize(~~(between / 86400), ' day')
|
||||
}
|
||||
}
|
||||
|
||||
export function parseTime(time, cFormat) {
|
||||
if (arguments.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ((time + '').length === 10) {
|
||||
time = +time * 1000
|
||||
}
|
||||
|
||||
|
||||
const format = cFormat || '{y}-{m}-{d} {h}:{i}:{s}';
|
||||
let date;
|
||||
if (typeof time == 'object') {
|
||||
date = time;
|
||||
} else {
|
||||
date = new Date(parseInt(time));
|
||||
}
|
||||
const formatObj = {
|
||||
y: date.getFullYear(),
|
||||
m: date.getMonth() + 1,
|
||||
d: date.getDate(),
|
||||
h: date.getHours(),
|
||||
i: date.getMinutes(),
|
||||
s: date.getSeconds(),
|
||||
a: date.getDay()
|
||||
};
|
||||
const time_str = format.replace(/{(y|m|d|h|i|s|a)+}/g, (result, key) => {
|
||||
let value = formatObj[key];
|
||||
if (key === 'a') return ['一', '二', '三', '四', '五', '六', '日'][value - 1];
|
||||
if (result.length > 0 && value < 10) {
|
||||
value = '0' + value;
|
||||
}
|
||||
return value || 0;
|
||||
});
|
||||
return time_str;
|
||||
}
|
||||
|
||||
export function formatTime(time, option) {
|
||||
time = +time * 1000;
|
||||
const d = new Date(time);
|
||||
const now = Date.now();
|
||||
|
||||
const diff = (now - d) / 1000;
|
||||
|
||||
if (diff < 30) {
|
||||
return '刚刚'
|
||||
} else if (diff < 3600) { // less 1 hour
|
||||
return Math.ceil(diff / 60) + '分钟前'
|
||||
} else if (diff < 3600 * 24) {
|
||||
return Math.ceil(diff / 3600) + '小时前'
|
||||
} else if (diff < 3600 * 24 * 2) {
|
||||
return '1天前'
|
||||
}
|
||||
if (option) {
|
||||
return parseTime(time, option)
|
||||
} else {
|
||||
return d.getMonth() + 1 + '月' + d.getDate() + '日' + d.getHours() + '时' + d.getMinutes() + '分'
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* 数字 格式化*/
|
||||
export function nFormatter(num, digits) {
|
||||
const si = [
|
||||
{ value: 1E18, symbol: 'E' },
|
||||
{ value: 1E15, symbol: 'P' },
|
||||
{ value: 1E12, symbol: 'T' },
|
||||
{ value: 1E9, symbol: 'G' },
|
||||
{ value: 1E6, symbol: 'M' },
|
||||
{ value: 1E3, symbol: 'k' }
|
||||
];
|
||||
for (let i = 0; i < si.length; i++) {
|
||||
if (num >= si[i].value) {
|
||||
return (num / si[i].value + 0.1).toFixed(digits).replace(/\.0+$|(\.[0-9]*[1-9])0+$/, '$1') + si[i].symbol;
|
||||
}
|
||||
}
|
||||
return num.toString();
|
||||
}
|
||||
|
||||
|
||||
export function html2Text(val) {
|
||||
const div = document.createElement('div');
|
||||
div.innerHTML = val;
|
||||
return div.textContent || div.innerText;
|
||||
}
|
||||
|
||||
|
||||
export function toThousandslsFilter(num) {
|
||||
return (+num || 0).toString().replace(/(\d)(?=(?:\d{3})+$)/g, '$1,');
|
||||
}
|
116
src/main.js
Normal file
116
src/main.js
Normal file
|
@ -0,0 +1,116 @@
|
|||
// The Vue build version to load with the `import` command
|
||||
// (runtime-only or standalone) has been set in webpack.base.conf with an alias.
|
||||
import Vue from 'vue';
|
||||
import App from './App';
|
||||
import router from './router';
|
||||
import store from './store';
|
||||
import ElementUI from 'element-ui';
|
||||
import 'element-ui/lib/theme-default/index.css';
|
||||
import 'assets/custom-theme/index.css'; // https://github.com/PanJiaChen/custom-element-theme
|
||||
import NProgress from 'nprogress';
|
||||
import 'nprogress/nprogress.css';
|
||||
import 'normalize.css/normalize.css';
|
||||
import 'styles/index.scss';
|
||||
import 'components/Icon-svg/index';
|
||||
import 'assets/iconfont/iconfont';
|
||||
import * as filters from './filters';
|
||||
import Multiselect from 'vue-multiselect';
|
||||
import Sticky from 'components/Sticky';
|
||||
import 'vue-multiselect/dist/vue-multiselect.min.css';
|
||||
import vueWaves from './directive/waves';
|
||||
import vueSticky from './directive/sticky';
|
||||
import errLog from 'store/errLog';
|
||||
// import './styles/mixin.scss';
|
||||
|
||||
// register globally
|
||||
Vue.component('multiselect', Multiselect);
|
||||
Vue.component('Sticky', Sticky);
|
||||
Vue.use(ElementUI);
|
||||
Vue.use(vueWaves);
|
||||
Vue.use(vueSticky);
|
||||
|
||||
|
||||
// register global utility filters.
|
||||
Object.keys(filters).forEach(key => {
|
||||
Vue.filter(key, filters[key])
|
||||
});
|
||||
|
||||
|
||||
function hasPermission(roles, permissionRoles) {
|
||||
if (roles.indexOf('admin') >= 0) return true;
|
||||
return roles.some(role => permissionRoles.indexOf(role) >= 0)
|
||||
}
|
||||
// register global progress.
|
||||
const whiteList = ['/login', '/authredirect', '/reset', '/sendpwd'];// 不重定向白名单
|
||||
router.beforeEach((to, from, next) => {
|
||||
NProgress.start();
|
||||
if (store.getters.token) {
|
||||
if (to.path === '/login') {
|
||||
next({ path: '/' });
|
||||
} else {
|
||||
console.log('a')
|
||||
if (to.meta && to.meta.role) {
|
||||
if (hasPermission(store.getters.roles, to.meta.role)) {
|
||||
next();
|
||||
} else {
|
||||
next('/401');
|
||||
}
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (whiteList.indexOf(to.path) !== -1) {
|
||||
next()
|
||||
} else {
|
||||
next('/login')
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
router.afterEach(() => {
|
||||
NProgress.done();
|
||||
});
|
||||
|
||||
|
||||
// 异步组件
|
||||
// Vue.component('async-Editor', function (resolve) {
|
||||
// require(['components/Editor'], resolve)
|
||||
// });
|
||||
|
||||
// window.onunhandledrejection = e => {
|
||||
// console.log('unhandled', e.reason, e.promise);
|
||||
// e.preventDefault()
|
||||
// };
|
||||
|
||||
// 生产环境错误日志
|
||||
if (process.env === 'production') {
|
||||
Vue.config.errorHandler = function(err, vm) {
|
||||
console.log(err, window.location.href);
|
||||
errLog.pushLog({
|
||||
err,
|
||||
url: window.location.href,
|
||||
vm
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
// window.onerror = function (msg, url, lineNo, columnNo, error) {
|
||||
// console.log('window')
|
||||
// };
|
||||
//
|
||||
// console.error = (function (origin) {
|
||||
// return function (errorlog) {
|
||||
// // handler();//基于业务的日志记录及数据报错
|
||||
// console.log('console'+errorlog)
|
||||
// origin.call(console, errorlog);
|
||||
// }
|
||||
// })(console.error);
|
||||
|
||||
new Vue({
|
||||
router,
|
||||
store,
|
||||
render: h => h(App)
|
||||
}).$mount('#app');
|
||||
|
||||
|
25
src/mock/login.js
Normal file
25
src/mock/login.js
Normal file
|
@ -0,0 +1,25 @@
|
|||
const userMap = {
|
||||
admin: {
|
||||
role: ['admin'],
|
||||
token: 'admin',
|
||||
introduction: '我是超级管理员',
|
||||
avatar: 'https://wdl.wallstreetcn.com/48a3e1e0-ea2c-4a4e-9928-247645e3428b',
|
||||
name: '超级管理员小潘'
|
||||
},
|
||||
editor: {
|
||||
role: ['editor'],
|
||||
token: 'editor',
|
||||
introduction: '我是编辑',
|
||||
avatar: 'https://wdl.wallstreetcn.com/48a3e1e0-ea2c-4a4e-9928-247645e3428b',
|
||||
name: '普通编辑小张'
|
||||
|
||||
},
|
||||
developer: {
|
||||
role: ['develop'],
|
||||
token: 'develop',
|
||||
introduction: '我是开发',
|
||||
avatar: 'https://wdl.wallstreetcn.com/48a3e1e0-ea2c-4a4e-9928-247645e3428b',
|
||||
name: '工程师小王'
|
||||
}
|
||||
}
|
||||
export default userMap
|
81
src/router/index.js
Normal file
81
src/router/index.js
Normal file
|
@ -0,0 +1,81 @@
|
|||
import Vue from 'vue';
|
||||
import Router from 'vue-router';
|
||||
|
||||
/* layout*/
|
||||
import Layout from '../views/layout/Layout';
|
||||
|
||||
// dashboard
|
||||
// import dashboard from '../views/dashboard/index';
|
||||
const dashboard = resolve => require(['../views/dashboard/index'], resolve);
|
||||
/* error*/
|
||||
const Err404 = resolve => require(['../views/error/404'], resolve);
|
||||
const Err401 = resolve => require(['../views/error/401'], resolve);
|
||||
/* login*/
|
||||
import Login from '../views/login/';
|
||||
import authRedirect from '../views/login/authredirect';
|
||||
import sendPWD from '../views/login/sendpwd';
|
||||
import reset from '../views/login/reset';
|
||||
|
||||
/* components*/
|
||||
const Tinymce = resolve => require(['../views/components/tinymce'], resolve);
|
||||
const Markdown = resolve => require(['../views/components/markdown'], resolve);
|
||||
|
||||
/* admin*/
|
||||
// const AdminCreateUser = resolve => require(['../views/admin/createUser'], resolve);
|
||||
// const QuicklyCreateUser = resolve => require(['../views/admin/quicklycreate'], resolve);
|
||||
// const UserProfile = resolve => require(['../views/admin/profile'], resolve);
|
||||
// const UsersList = resolve => require(['../views/admin/usersList'], resolve);
|
||||
|
||||
|
||||
|
||||
|
||||
Vue.use(Router);
|
||||
|
||||
export default new Router({
|
||||
mode: 'history',
|
||||
scrollBehavior: () => ({ y: 0 }),
|
||||
routes: [
|
||||
{ path: '/login', component: Login, hidden: true },
|
||||
{ path: '/authredirect', component: authRedirect, hidden: true },
|
||||
{ path: '/sendpwd', component: sendPWD, hidden: true },
|
||||
{ path: '/reset', component: reset, hidden: true },
|
||||
{ path: '/404', component: Err404, hidden: true },
|
||||
{ path: '/401', component: Err401, hidden: true },
|
||||
{
|
||||
path: '/',
|
||||
component: Layout,
|
||||
redirect: '/dashboard',
|
||||
name: '首页',
|
||||
hidden: true,
|
||||
children: [
|
||||
{ path: 'dashboard', component: dashboard }
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/admin',
|
||||
component: Layout,
|
||||
redirect: 'noredirect',
|
||||
name: '组件',
|
||||
icon: 'zujian',
|
||||
children: [
|
||||
{ path: 'tinymce', component: Tinymce, name: '富文本编辑器' },
|
||||
{ path: 'markdown', component: Markdown, name: 'Markdown' }
|
||||
|
||||
]
|
||||
},
|
||||
// {
|
||||
// path: '/admin',
|
||||
// component: Layout,
|
||||
// redirect: 'noredirect',
|
||||
// name: '后台管理',
|
||||
// icon: 'geren1',
|
||||
// children: [
|
||||
// { path: 'createuser', component: AdminCreateUser, name: '管理员', meta: { role: ['admin'] } },
|
||||
// { path: 'list', component: UsersList, name: '后台用户列表', meta: { role: ['super_editor', 'product', 'author_assistant'] } },
|
||||
// { path: 'qicklyCreate', component: QuicklyCreateUser, name: '一键创建账户', meta: { role: ['super_editor', 'gold_editor', 'weex_editor', 'wscn_editor', 'author_assistant', 'product'] } },
|
||||
// { path: 'profile', component: UserProfile, name: '个人' }
|
||||
// ]
|
||||
// },
|
||||
{ path: '*', redirect: '/404', hidden: true }
|
||||
]
|
||||
});
|
13
src/store/errLog.js
Normal file
13
src/store/errLog.js
Normal file
|
@ -0,0 +1,13 @@
|
|||
const errLog = {
|
||||
state: {
|
||||
errLog: []
|
||||
},
|
||||
pushLog(log) {
|
||||
this.state.errLog.unshift(log)
|
||||
},
|
||||
clearLog() {
|
||||
this.state.errLog = [];
|
||||
}
|
||||
};
|
||||
|
||||
export default errLog;
|
15
src/store/getters.js
Normal file
15
src/store/getters.js
Normal file
|
@ -0,0 +1,15 @@
|
|||
const getters = {
|
||||
sidebar: state => state.app.sidebar,
|
||||
livenewsChannels: state => state.app.livenewsChannels,
|
||||
token: state => state.user.token,
|
||||
avatar: state => state.user.avatar,
|
||||
name: state => state.user.name,
|
||||
uid: state => state.user.uid,
|
||||
email: state => state.user.email,
|
||||
introduction: state => state.user.introduction,
|
||||
auth_type: state => state.user.auth_type,
|
||||
status: state => state.user.status,
|
||||
roles: state => state.user.roles,
|
||||
setting: state => state.user.setting
|
||||
};
|
||||
export default getters
|
17
src/store/index.js
Normal file
17
src/store/index.js
Normal file
|
@ -0,0 +1,17 @@
|
|||
import Vue from 'vue';
|
||||
import Vuex from 'vuex';
|
||||
import app from './modules/app';
|
||||
import user from './modules/user';
|
||||
import getters from './getters';
|
||||
|
||||
Vue.use(Vuex);
|
||||
|
||||
const store = new Vuex.Store({
|
||||
modules: {
|
||||
app,
|
||||
user
|
||||
},
|
||||
getters
|
||||
});
|
||||
|
||||
export default store
|
38
src/store/modules/app.js
Normal file
38
src/store/modules/app.js
Normal file
|
@ -0,0 +1,38 @@
|
|||
import Cookies from 'js-cookie';
|
||||
|
||||
const app = {
|
||||
state: {
|
||||
sidebar: {
|
||||
opened: !+Cookies.get('sidebarStatus')
|
||||
},
|
||||
theme: 'default',
|
||||
livenewsChannels: Cookies.get('livenewsChannels') || '[]'
|
||||
},
|
||||
mutations: {
|
||||
TOGGLE_SIDEBAR: state => {
|
||||
if (state.sidebar.opened) {
|
||||
Cookies.set('sidebarStatus', 1);
|
||||
} else {
|
||||
Cookies.set('sidebarStatus', 0);
|
||||
}
|
||||
state.sidebar.opened = !state.sidebar.opened;
|
||||
},
|
||||
SET_LIVENEWS_CHANNELS: (status, channels) => {
|
||||
status.livenewsChannels = JSON.stringify(channels);
|
||||
Cookies.set('livenewsChannels', JSON.stringify(channels));
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
ToggleSideBar: ({ commit }) => {
|
||||
commit('TOGGLE_SIDEBAR')
|
||||
},
|
||||
setTheme: ({ commit }, theme) => {
|
||||
commit('SET_THEME', theme)
|
||||
},
|
||||
setlivenewsChannels: ({ commit }, channels) => {
|
||||
commit('SET_LIVENEWS_CHANNELS', channels)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default app;
|
129
src/store/modules/user.js
Normal file
129
src/store/modules/user.js
Normal file
|
@ -0,0 +1,129 @@
|
|||
// import { loginByEmail, loginByThirdparty } from 'api/login';
|
||||
// import { userInfo, userLogout } from 'api/adminUser';
|
||||
import Cookies from 'js-cookie';
|
||||
import userMap from 'mock/login';
|
||||
|
||||
const user = {
|
||||
state: {
|
||||
user: '',
|
||||
status: '',
|
||||
email: '',
|
||||
code: '',
|
||||
uid: undefined,
|
||||
auth_type: '',
|
||||
token: Cookies.get('X-Ivanka-Token'),
|
||||
name: '',
|
||||
avatar: '',
|
||||
introduction: '',
|
||||
roles: [],
|
||||
setting: {
|
||||
articlePlatform: []
|
||||
}
|
||||
},
|
||||
|
||||
mutations: {
|
||||
SET_AUTH_TYPE: (state, type) => {
|
||||
state.auth_type = type;
|
||||
},
|
||||
SET_CODE: (state, code) => {
|
||||
state.code = code;
|
||||
},
|
||||
SET_TOKEN: (state, token) => {
|
||||
state.token = token;
|
||||
},
|
||||
SET_UID: (state, uid) => {
|
||||
state.uid = uid;
|
||||
},
|
||||
SET_EMAIL: (state, email) => {
|
||||
state.email = email;
|
||||
},
|
||||
SET_INTRODUCTION: (state, introduction) => {
|
||||
state.introduction = introduction;
|
||||
},
|
||||
SET_SETTING: (state, setting) => {
|
||||
state.setting = setting;
|
||||
},
|
||||
SET_STATUS: (state, status) => {
|
||||
state.status = status;
|
||||
},
|
||||
SET_NAME: (state, name) => {
|
||||
state.name = name;
|
||||
},
|
||||
SET_AVATAR: (state, avatar) => {
|
||||
state.avatar = avatar;
|
||||
},
|
||||
SET_ROLES: (state, roles) => {
|
||||
state.roles = roles;
|
||||
},
|
||||
LOGIN_SUCCESS: () => {
|
||||
console.log('login success')
|
||||
},
|
||||
LOGOUT_USER: state => {
|
||||
state.user = '';
|
||||
}
|
||||
},
|
||||
|
||||
actions: {
|
||||
// 邮箱登录
|
||||
LoginByEmail({ commit }, userInfo) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const email = userInfo.email.split('@')[0];
|
||||
if (userMap[email]) {
|
||||
commit('SET_ROLES', userMap[email].role);
|
||||
commit('SET_TOKEN', userMap[email].token);
|
||||
Cookies.set('X-Ivanka-Token', userMap[email].token);
|
||||
resolve();
|
||||
} else {
|
||||
reject('账号不正确');
|
||||
}
|
||||
});
|
||||
},
|
||||
// 第三方验证登录
|
||||
LoginByThirdparty({ commit, state }, code) {
|
||||
return new Promise((resolve, reject) => {
|
||||
commit('SET_CODE', code);
|
||||
loginByThirdparty(state.status, state.email, state.code, state.auth_type).then(response => {
|
||||
commit('SET_TOKEN', response.data.token);
|
||||
Cookies.set('X-Ivanka-Token', response.data.token);
|
||||
resolve();
|
||||
}).catch(error => {
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
},
|
||||
// 获取用户信息
|
||||
GetInfo({ commit, state }) {
|
||||
return new Promise(resolve => {
|
||||
const token = state.token;
|
||||
commit('SET_ROLES', userMap[token].role);
|
||||
commit('SET_NAME', userMap[token].name);
|
||||
commit('SET_AVATAR', userMap[token].avatar);
|
||||
commit('SET_INTRODUCTION', userMap[token].introduction);
|
||||
resolve();
|
||||
});
|
||||
},
|
||||
// 登出
|
||||
LogOut({ commit, state }) {
|
||||
return new Promise((resolve, reject) => {
|
||||
userLogout(state.token).then(() => {
|
||||
commit('SET_TOKEN', '');
|
||||
Cookies.remove('X-Ivanka-Token');
|
||||
resolve();
|
||||
}).catch(error => {
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
// 前端 登出
|
||||
FedLogOut({ commit }) {
|
||||
return new Promise(resolve => {
|
||||
commit('SET_TOKEN', '');
|
||||
Cookies.remove('X-Ivanka-Token');
|
||||
resolve();
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default user;
|
39
src/store/permission.js
Normal file
39
src/store/permission.js
Normal file
|
@ -0,0 +1,39 @@
|
|||
const permission = {
|
||||
state: {
|
||||
permissionRoutes: []
|
||||
},
|
||||
init(data) {
|
||||
const roles = data.roles;
|
||||
const router = data.router;
|
||||
const permissionRoutes = router.filter(v => {
|
||||
if (roles.indexOf('admin') >= 0) return true;
|
||||
if (this.hasPermission(roles, v)) {
|
||||
if (v.children && v.children.length > 0) {
|
||||
v.children = v.children.filter(child => {
|
||||
if (this.hasPermission(roles, child)) {
|
||||
return child
|
||||
}
|
||||
return false;
|
||||
});
|
||||
return v
|
||||
} else {
|
||||
return v
|
||||
}
|
||||
}
|
||||
return false;
|
||||
});
|
||||
this.permissionRoutes = permissionRoutes;
|
||||
},
|
||||
get() {
|
||||
return this.permissionRoutes
|
||||
},
|
||||
hasPermission(roles, route) {
|
||||
if (route.meta && route.meta.role) {
|
||||
return roles.some(role => route.meta.role.indexOf(role) >= 0)
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default permission;
|
103
src/styles/btn.scss
Normal file
103
src/styles/btn.scss
Normal file
|
@ -0,0 +1,103 @@
|
|||
$blue:#324157;
|
||||
$light-blue:#3A71A8;
|
||||
$red:#C03639;
|
||||
$pink: #E65D6E;
|
||||
$green: #30B08F;
|
||||
$tiffany: #4AB7BD;
|
||||
$yellow:#FEC171;
|
||||
|
||||
$panGreen: #30B08F;
|
||||
|
||||
@mixin colorBtn($color) {
|
||||
background: $color;
|
||||
&:hover {
|
||||
color: $color;
|
||||
&:before, &:after {
|
||||
background: $color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.blue-btn {
|
||||
@include colorBtn($blue)
|
||||
}
|
||||
|
||||
.light-blue-btn{
|
||||
@include colorBtn($light-blue)
|
||||
}
|
||||
|
||||
|
||||
.red-btn {
|
||||
@include colorBtn($red)
|
||||
}
|
||||
|
||||
.pink-btn {
|
||||
@include colorBtn($pink)
|
||||
}
|
||||
|
||||
.green-btn {
|
||||
@include colorBtn($green)
|
||||
}
|
||||
|
||||
|
||||
.tiffany-btn {
|
||||
@include colorBtn($tiffany)
|
||||
}
|
||||
|
||||
|
||||
.yellow-btn {
|
||||
@include colorBtn($yellow)
|
||||
}
|
||||
|
||||
.pan-btn {
|
||||
font-size: 14px;
|
||||
color: #fff;
|
||||
padding: 14px 36px;
|
||||
border-radius: 8px;
|
||||
border: none;
|
||||
outline: none;
|
||||
margin-right: 25px;
|
||||
transition: 600ms ease all;
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
&:hover {
|
||||
background: #fff;
|
||||
&:before, &:after {
|
||||
width: 100%;
|
||||
transition: 600ms ease all;
|
||||
}
|
||||
}
|
||||
&:before, &:after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
height: 2px;
|
||||
width: 0;
|
||||
transition: 400ms ease all;
|
||||
}
|
||||
&::after {
|
||||
right: inherit;
|
||||
top: inherit;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.custom-button{
|
||||
display: inline-block;
|
||||
line-height: 1;
|
||||
white-space: nowrap;
|
||||
cursor: pointer;
|
||||
background: #fff;
|
||||
color: #fff;
|
||||
-webkit-appearance: none;
|
||||
text-align: center;
|
||||
box-sizing: border-box;
|
||||
outline: 0;
|
||||
margin: 0;
|
||||
padding: 10px 15px;
|
||||
font-size: 14px;
|
||||
border-radius: 4px;
|
||||
}
|
348
src/styles/editor.scss
Normal file
348
src/styles/editor.scss
Normal file
|
@ -0,0 +1,348 @@
|
|||
//富文本
|
||||
//移除 至static/tinymce/skins/lightgray.content.min.css
|
||||
.small-size {
|
||||
width: 800px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
img{
|
||||
max-height: 300px;
|
||||
}
|
||||
.note-color .dropdown-menu li .btn-group{
|
||||
&:first-child{
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
//禁止图片缩放
|
||||
.note-control-sizing {
|
||||
display: none;
|
||||
}
|
||||
.panel-body {
|
||||
$blue: #1478F0;
|
||||
font-size: 16px;
|
||||
line-height: 1.4em;
|
||||
& > :last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
img {
|
||||
max-width: 100%;
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
}
|
||||
table {
|
||||
width: 100% !important;
|
||||
}
|
||||
embed {
|
||||
max-width: 100%;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
p {
|
||||
// margin-bottom: 1em;
|
||||
text-align: justify;
|
||||
word-break: break-all;
|
||||
}
|
||||
ul {
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
li {
|
||||
margin-left: 20px;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
a {
|
||||
color: $blue;
|
||||
}
|
||||
hr {
|
||||
margin: 1em auto;
|
||||
border: none;
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
height: 1px;
|
||||
background: #DCDCDC;
|
||||
}
|
||||
//add type.css start
|
||||
blockquote p {
|
||||
font-size: 16px;
|
||||
letter-spacing: 1px;
|
||||
line-height: 28px;
|
||||
color: #333;
|
||||
}
|
||||
blockquote p:last-of-type {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
/* HTML5 媒体文件跟 img 保持一致 */
|
||||
audio,
|
||||
canvas,
|
||||
video {
|
||||
display: inline-block;
|
||||
*display: inline;
|
||||
*zoom: 1;
|
||||
}
|
||||
/* 要注意表单元素并不继承父级 font 的问题 */
|
||||
button,
|
||||
input,
|
||||
select,
|
||||
textarea {
|
||||
font: 500 14px/1.8 'Hiragino Sans GB', Microsoft YaHei, sans-serif;
|
||||
}
|
||||
/* 去掉各Table cell 的边距并让其边重合 */
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
border-spacing: 0;
|
||||
}
|
||||
/* IE bug fixed: th 不继承 text-align*/
|
||||
th {
|
||||
text-align: inherit;
|
||||
}
|
||||
/* 去除默认边框 */
|
||||
fieldset,
|
||||
img {
|
||||
border: 0;
|
||||
}
|
||||
/* 解决 IE6-7 图片缩放锯齿问题 */
|
||||
img {
|
||||
-ms-interpolation-mode: bicubic;
|
||||
}
|
||||
/* ie6 7 8(q) bug 显示为行内表现 */
|
||||
iframe {
|
||||
display: block;
|
||||
}
|
||||
/* 块/段落引用 */
|
||||
blockquote {
|
||||
position: relative;
|
||||
font-size: 16px;
|
||||
letter-spacing: 1px;
|
||||
line-height: 28px;
|
||||
margin-bottom: 40px;
|
||||
padding: 20px;
|
||||
background: #f0f2f5;
|
||||
&:before{
|
||||
position: absolute;
|
||||
content: " \300D";
|
||||
top: 10px;
|
||||
left: 2px;
|
||||
-webkit-transform: rotate(180deg);
|
||||
transform: rotate(180deg);
|
||||
color: #333;
|
||||
}
|
||||
&:after{
|
||||
position: absolute;
|
||||
content: " \300D";
|
||||
right: 6px;
|
||||
bottom: 12px;
|
||||
color: #333;
|
||||
}
|
||||
}
|
||||
blockquote blockquote {
|
||||
padding: 0 0 0 1em;
|
||||
margin-left: 2em;
|
||||
border-left: 3px solid $blue;
|
||||
}
|
||||
/* Firefox 以外,元素没有下划线,需添加 */
|
||||
acronym,
|
||||
abbr {
|
||||
border-bottom: 1px dotted;
|
||||
font-variant: normal;
|
||||
}
|
||||
/* 添加鼠标问号,进一步确保应用的语义是正确的(要知道,交互他们也有洁癖,如果你不去掉,那得多花点口舌) */
|
||||
abbr {
|
||||
cursor: help;
|
||||
}
|
||||
/* 一致的 del 样式 */
|
||||
del {
|
||||
text-decoration: line-through;
|
||||
}
|
||||
address,
|
||||
caption,
|
||||
cite,
|
||||
code,
|
||||
del,
|
||||
em,
|
||||
th,
|
||||
var {
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
}
|
||||
em {
|
||||
font-style: normal;
|
||||
font-family: sans-serif;
|
||||
font-weight: bold;
|
||||
}
|
||||
/* 对齐是排版最重要的因素, 别让什么都居中 */
|
||||
caption,
|
||||
th {
|
||||
text-align: left;
|
||||
}
|
||||
q:before,
|
||||
q:after {
|
||||
content: '';
|
||||
}
|
||||
/* 统一上标和下标 */
|
||||
sub,
|
||||
sup {
|
||||
font-size: 75%;
|
||||
line-height: 0;
|
||||
position: relative;
|
||||
vertical-align: text-top;
|
||||
}
|
||||
:root sub,
|
||||
:root sup {
|
||||
vertical-align: baseline;
|
||||
/* for ie9 and other mordern browsers */
|
||||
}
|
||||
sup {
|
||||
top: -0.5em;
|
||||
}
|
||||
sub {
|
||||
bottom: -0.25em;
|
||||
}
|
||||
/* 让链接在 hover 状态下显示下划线 */
|
||||
a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
/* 默认不显示下划线,保持页面简洁 */
|
||||
ins,
|
||||
a {
|
||||
text-decoration: none;
|
||||
}
|
||||
u,
|
||||
.typo-u {
|
||||
text-decoration: underline;
|
||||
}
|
||||
/* 标记,类似于手写的荧光笔的作用 */
|
||||
mark {
|
||||
background: #fffdd1;
|
||||
}
|
||||
/* 代码片断 */
|
||||
pre,
|
||||
code {
|
||||
font-family: "Courier New", Courier, monospace;
|
||||
}
|
||||
pre {
|
||||
border: 1px solid #ddd;
|
||||
border-left-width: 0.4em;
|
||||
background: #fbfbfb;
|
||||
padding: 10px;
|
||||
}
|
||||
/* 底部印刷体、版本等标记 */
|
||||
small {
|
||||
font-size: 12px;
|
||||
color: #888;
|
||||
}
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6 {
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
color: #1478f0;
|
||||
border-left: 5px solid #1478f0;
|
||||
padding-left: 10px;
|
||||
margin: 30px 0;
|
||||
}
|
||||
h2 {
|
||||
font-size: 1.2em;
|
||||
}
|
||||
/* 保证块/段落之间的空白隔行 */
|
||||
.typo p,
|
||||
.typo pre,
|
||||
.typo ul,
|
||||
.typo ol,
|
||||
.typo dl,
|
||||
.typo form,
|
||||
.typo hr,
|
||||
.typo table,
|
||||
.typo-p,
|
||||
.typo-pre,
|
||||
.typo-ul,
|
||||
.typo-ol,
|
||||
.typo-dl,
|
||||
.typo-form,
|
||||
.typo-hr,
|
||||
.typo-table {
|
||||
margin-bottom: 15px;
|
||||
line-height: 25px;
|
||||
}
|
||||
/* 标题应该更贴紧内容,并与其他块区分,margin 值要相应做优化 */
|
||||
.typo h1,
|
||||
.typo h2,
|
||||
.typo h3,
|
||||
.typo h4,
|
||||
.typo h5,
|
||||
.typo h6,
|
||||
.typo-h1,
|
||||
.typo-h2,
|
||||
.typo-h3,
|
||||
.typo-h4,
|
||||
.typo-h5,
|
||||
.typo-h6 {
|
||||
margin-bottom: 0.4em;
|
||||
line-height: 1.5;
|
||||
}
|
||||
.typo h1,
|
||||
.typo-h1 {
|
||||
font-size: 1.8em;
|
||||
}
|
||||
.typo h2,
|
||||
.typo-h2 {
|
||||
font-size: 1.6em;
|
||||
}
|
||||
.typo h3,
|
||||
.typo-h3 {
|
||||
font-size: 1.4em;
|
||||
}
|
||||
.typo h4,
|
||||
.typo-h0 {
|
||||
font-size: 1.2em;
|
||||
}
|
||||
.typo h5,
|
||||
.typo h6,
|
||||
.typo-h5,
|
||||
.typo-h6 {
|
||||
font-size: 1em;
|
||||
}
|
||||
/* 在文章中,应该还原 ul 和 ol 的样式 */
|
||||
.typo ul,
|
||||
.typo-ul {
|
||||
margin-left: 1.3em;
|
||||
list-style: disc;
|
||||
}
|
||||
.typo ol,
|
||||
.typo-ol {
|
||||
list-style: decimal;
|
||||
margin-left: 1.9em;
|
||||
}
|
||||
.typo li ul,
|
||||
.typo li ol,
|
||||
.typo-ul ul,
|
||||
.typo-ul ol,
|
||||
.typo-ol ul,
|
||||
.typo-ol ol {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
margin-left: 2em;
|
||||
}
|
||||
.typo li ul,
|
||||
.typo-ul ul,
|
||||
.typo-ol ul {
|
||||
list-style: circle;
|
||||
}
|
||||
/* 同 ul/ol,在文章中应用 table 基本格式 */
|
||||
.typo table th,
|
||||
.typo table td,
|
||||
.typo-table th,
|
||||
.typo-table td {
|
||||
border: 1px solid #ddd;
|
||||
padding: 5px 10px;
|
||||
}
|
||||
.typo table th,
|
||||
.typo-table th {
|
||||
background: #fbfbfb;
|
||||
}
|
||||
.typo table thead th,
|
||||
.typo-table thead th {
|
||||
background: #f1f1f1;
|
||||
}
|
||||
}
|
||||
|
||||
|
392
src/styles/index.scss
Normal file
392
src/styles/index.scss
Normal file
|
@ -0,0 +1,392 @@
|
|||
@import './btn.scss';
|
||||
// @import './editor.scss';
|
||||
@import "./mixin.scss";
|
||||
|
||||
body {
|
||||
//height: 100%;
|
||||
//overflow-y: scroll;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
text-rendering: optimizeLegibility;
|
||||
font-family: Helvetica Neue,Helvetica,PingFang SC,Hiragino Sans GB,Microsoft YaHei,Arial,sans-serif;
|
||||
//@include scrollBar;
|
||||
}
|
||||
label{
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
html {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
*, *:before, *:after {
|
||||
box-sizing: inherit;
|
||||
}
|
||||
|
||||
.no-padding {
|
||||
padding: 0px !important;
|
||||
}
|
||||
|
||||
.padding-content {
|
||||
padding: 4px 0;
|
||||
}
|
||||
|
||||
a:focus,
|
||||
a:active {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
a,
|
||||
a:focus,
|
||||
a:hover {
|
||||
cursor: pointer;
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.fr {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.fl {
|
||||
float: left;
|
||||
}
|
||||
|
||||
.pr-5 {
|
||||
padding-right: 5px;
|
||||
}
|
||||
|
||||
.pl-5 {
|
||||
padding-left: 5px;
|
||||
}
|
||||
|
||||
.block {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.inlineBlock {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.components-container{
|
||||
margin: 30px 50px;
|
||||
}
|
||||
|
||||
code{
|
||||
background: #eef1f6;
|
||||
padding: 20px 10px;
|
||||
margin-bottom: 20px;
|
||||
display: block;
|
||||
}
|
||||
.fade-enter-active, .fade-leave-active {
|
||||
transition: all .2s ease
|
||||
}
|
||||
|
||||
.fade-enter, .fade-leave-active {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
//editor
|
||||
//.editor-placeholder {
|
||||
// margin: 0 auto;
|
||||
// display: block;
|
||||
// .editor-placeholder-title {
|
||||
// text-align: center;
|
||||
// font-size: 20px;
|
||||
// padding-bottom: 5px;
|
||||
// }
|
||||
// .editor-placeholder-image {
|
||||
// display: block;
|
||||
// max-height: 100px;
|
||||
// margin: 0 auto;
|
||||
// }
|
||||
//}
|
||||
|
||||
//main-container全局样式
|
||||
.app-container {
|
||||
padding: 20px;
|
||||
//min-height: 100%;
|
||||
}
|
||||
|
||||
//element ui upload
|
||||
.upload-container {
|
||||
.el-upload {
|
||||
width: 100%;
|
||||
.el-upload-dragger {
|
||||
width: 100%;
|
||||
height: 200px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.singleImageUpload2.upload-container {
|
||||
.el-upload {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
.el-upload-dragger {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
.el-icon-upload {
|
||||
margin: 30% 0 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.editor-video-upload {
|
||||
@include clearfix;
|
||||
margin-bottom: 10px;
|
||||
.el-upload {
|
||||
float: left;
|
||||
width: 100px;
|
||||
|
||||
}
|
||||
.el-upload-list {
|
||||
float: left;
|
||||
.el-upload-list__item:first-child {
|
||||
margin-top: 0px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.el-upload-list--picture-card {
|
||||
float: left;
|
||||
}
|
||||
|
||||
.pagination-container {
|
||||
margin-top: 30px;
|
||||
}
|
||||
|
||||
.pointer {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.wscn-icon {
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
vertical-align: -0.15em;
|
||||
fill: currentColor;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.sub-navbar {
|
||||
height: 50px;
|
||||
line-height: 50px;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
text-align: right;
|
||||
padding-right: 20px;
|
||||
transition: 600ms ease position;
|
||||
background: linear-gradient(90deg, rgba(32, 182, 249, 1) 0%, rgba(32, 182, 249, 1) 0%, rgba(33, 120, 241, 1) 100%, rgba(33, 120, 241, 1) 100%);
|
||||
.subtitle {
|
||||
font-size: 20px;
|
||||
color: #fff;
|
||||
}
|
||||
&.draft {
|
||||
background: #d0d0d0;
|
||||
}
|
||||
&.deleted {
|
||||
background: #d0d0d0;
|
||||
}
|
||||
}
|
||||
|
||||
.link-type,.link-type:focus {
|
||||
color: #337ab7;
|
||||
cursor: pointer;
|
||||
&:hover{
|
||||
color: rgb(32, 160, 255);
|
||||
}
|
||||
}
|
||||
|
||||
.publishedTag, .draftTag, .deletedTag {
|
||||
color: #fff;
|
||||
background-color: $panGreen;
|
||||
line-height: 1;
|
||||
text-align: center;
|
||||
margin: 0;
|
||||
padding: 8px 12px;
|
||||
font-size: 14px;
|
||||
border-radius: 4px;
|
||||
position: absolute;
|
||||
left: 20px;
|
||||
top: 10px;
|
||||
}
|
||||
|
||||
.draftTag {
|
||||
background-color: $yellow;
|
||||
}
|
||||
|
||||
.deletedTag {
|
||||
background-color: $red;
|
||||
}
|
||||
|
||||
.input-label {
|
||||
font-size: 14px;
|
||||
color: #48576a;
|
||||
line-height: 1;
|
||||
padding: 11px 5px 11px 0;
|
||||
}
|
||||
|
||||
.clearfix {
|
||||
&:after {
|
||||
visibility: hidden;
|
||||
display: block;
|
||||
font-size: 0;
|
||||
content: " ";
|
||||
clear: both;
|
||||
height: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.no-marginLeft {
|
||||
.el-checkbox {
|
||||
margin: 0 20px 15px 0;
|
||||
}
|
||||
.el-checkbox + .el-checkbox {
|
||||
margin-left: 0px;
|
||||
}
|
||||
}
|
||||
|
||||
.filter-container {
|
||||
padding-bottom: 10px;
|
||||
.filter-item {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
//文章页textarea修改样式
|
||||
.article-textarea {
|
||||
textarea {
|
||||
padding-right: 40px;
|
||||
resize: none;
|
||||
border: none;
|
||||
border-radius: 0px;
|
||||
border-bottom: 1px solid #bfcbd9;
|
||||
}
|
||||
}
|
||||
|
||||
//实时新闻创建页特殊处理
|
||||
.recentNews-container {
|
||||
p {
|
||||
display: inline-block;
|
||||
}
|
||||
.el-collapse-item__content{
|
||||
padding-right:0px;
|
||||
}
|
||||
}
|
||||
|
||||
//refine vue-multiselect plugin
|
||||
.multiselect {
|
||||
line-height: 16px;
|
||||
}
|
||||
|
||||
.multiselect--active {
|
||||
z-index: 1000 !important;
|
||||
}
|
||||
|
||||
//reset element ui
|
||||
.block-checkbox {
|
||||
display: block;
|
||||
}
|
||||
|
||||
//上传页面不显示删除icon
|
||||
.mediaUpload-container {
|
||||
.el-upload__btn-delete {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
.operation-container {
|
||||
.cell {
|
||||
padding: 10px !important;
|
||||
}
|
||||
.el-button {
|
||||
&:nth-child(3) {
|
||||
margin-top: 10px;
|
||||
margin-left: 0px;
|
||||
}
|
||||
&:nth-child(4) {
|
||||
margin-top: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.el-upload {
|
||||
input[type="file"] {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
.el-upload__input {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.cell {
|
||||
.el-tag {
|
||||
margin-right: 8px;
|
||||
}
|
||||
}
|
||||
.small-padding{
|
||||
.cell{
|
||||
padding-left: 8px;
|
||||
padding-right: 8px;
|
||||
}
|
||||
}
|
||||
.status-col {
|
||||
.cell {
|
||||
padding: 0 10px;
|
||||
text-align: center;
|
||||
.el-tag {
|
||||
margin-right: 0px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//.el-form-item__content{
|
||||
// margin-left: 0px!important;
|
||||
//}
|
||||
.no-border {
|
||||
.el-input-group__prepend, .el-input__inner, .el-date-editor__editor, .multiselect__tags {
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
|
||||
.el-select__tags {
|
||||
max-width: 100% !important;
|
||||
}
|
||||
|
||||
.small-space .el-form-item {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.no-padding {
|
||||
.el-dropdown-menu__item {
|
||||
padding: 0px;
|
||||
}
|
||||
.el-dropdown-menu {
|
||||
padding: 0px;
|
||||
}
|
||||
}
|
||||
|
||||
.no-hover {
|
||||
.el-dropdown-menu__item:not(.is-disabled):hover {
|
||||
background-color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
.el-tooltip-fullwidth {
|
||||
width: 100%;
|
||||
.el-tooltip__rel {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
//暂时性解决diolag 问题 https://github.com/ElemeFE/element/issues/2461
|
||||
.el-dialog{
|
||||
transform: none;
|
||||
left: 0;
|
||||
position: relative;
|
||||
margin: 0 auto;
|
||||
}
|
57
src/styles/mixin.scss
Normal file
57
src/styles/mixin.scss
Normal file
|
@ -0,0 +1,57 @@
|
|||
@mixin clearfix {
|
||||
&:after {
|
||||
content: "";
|
||||
display: table;
|
||||
clear: both;
|
||||
}
|
||||
}
|
||||
|
||||
@mixin scrollBar {
|
||||
&::-webkit-scrollbar-track-piece {
|
||||
background: #d3dce6;
|
||||
}
|
||||
&::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background: #99a9bf;
|
||||
border-radius: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
@mixin relative {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
@mixin pct($pct) {
|
||||
width: #{$pct};
|
||||
position: relative;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
@mixin triangle($width, $height, $color, $direction) {
|
||||
$width: $width/2;
|
||||
$color-border-style: $height solid $color;
|
||||
$transparent-border-style: $width solid transparent;
|
||||
height: 0;
|
||||
width: 0;
|
||||
@if $direction == up {
|
||||
border-bottom: $color-border-style;
|
||||
border-left: $transparent-border-style;
|
||||
border-right: $transparent-border-style;
|
||||
} @else if $direction == right {
|
||||
border-left: $color-border-style;
|
||||
border-top: $transparent-border-style;
|
||||
border-bottom: $transparent-border-style;
|
||||
} @else if $direction == down {
|
||||
border-top: $color-border-style;
|
||||
border-left: $transparent-border-style;
|
||||
border-right: $transparent-border-style;
|
||||
} @else if $direction == left {
|
||||
border-right: $color-border-style;
|
||||
border-top: $transparent-border-style;
|
||||
border-bottom: $transparent-border-style;
|
||||
}
|
||||
}
|
8
src/utils/createUniqueString.js
Normal file
8
src/utils/createUniqueString.js
Normal file
|
@ -0,0 +1,8 @@
|
|||
/**
|
||||
* Created by jiachenpan on 17/3/8.
|
||||
*/
|
||||
export default function createUniqueString() {
|
||||
const timestamp = +new Date() + '';
|
||||
const randomNum = parseInt((1 + Math.random()) * 65536) + '';
|
||||
return (+(randomNum + timestamp)).toString(32);
|
||||
}
|
72
src/utils/fetch.js
Normal file
72
src/utils/fetch.js
Normal file
|
@ -0,0 +1,72 @@
|
|||
import axios from 'axios';
|
||||
import { Message } from 'element-ui';
|
||||
import store from '../store';
|
||||
import router from '../router';
|
||||
|
||||
export default function fetch(options) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const instance = axios.create({
|
||||
baseURL: process.env.BASE_API,
|
||||
// timeout: 2000,
|
||||
headers: { 'X-Ivanka-Token': store.getters.token }
|
||||
});
|
||||
instance(options)
|
||||
.then(response => {
|
||||
const res = response.data;
|
||||
if (res.code !== 20000) {
|
||||
console.log(options); // for debug
|
||||
Message({
|
||||
message: res.message,
|
||||
type: 'error',
|
||||
duration: 5 * 1000
|
||||
});
|
||||
// 50014:Token 过期了 50012:其他客户端登录了 50008:非法的token
|
||||
if (res.code === 50008 || res.code === 50014 || res.code === 50012) {
|
||||
Message({
|
||||
message: res.message,
|
||||
type: 'error',
|
||||
duration: 5 * 1000
|
||||
});
|
||||
// router.push({path: '/'})
|
||||
// TODO
|
||||
store.dispatch('FedLogOut').then(() => {
|
||||
router.push({ path: '/login' })
|
||||
});
|
||||
}
|
||||
reject(res);
|
||||
}
|
||||
resolve(res);
|
||||
})
|
||||
.catch(error => {
|
||||
Message({
|
||||
message: '发生异常错误,请刷新页面重试,或联系程序员',
|
||||
type: 'error',
|
||||
duration: 5 * 1000
|
||||
});
|
||||
console.log(error); // for debug
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export function tpFetch(options) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const instance = axios.create({
|
||||
// timeout: 2000,
|
||||
});
|
||||
instance(options)
|
||||
.then(response => {
|
||||
const res = response.data;
|
||||
resolve(res);
|
||||
})
|
||||
.catch(error => {
|
||||
Message({
|
||||
message: '发生异常错误,请刷新页面重试,或联系程序员',
|
||||
type: 'error',
|
||||
duration: 5 * 1000
|
||||
});
|
||||
console.log(error); // for debug
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
}
|
221
src/utils/index.js
Normal file
221
src/utils/index.js
Normal file
|
@ -0,0 +1,221 @@
|
|||
/**
|
||||
* Created by jiachenpan on 16/11/18.
|
||||
*/
|
||||
|
||||
import showdown from 'showdown' // markdown转化
|
||||
const converter = new showdown.Converter();
|
||||
|
||||
export function parseTime(time, cFormat) {
|
||||
if (arguments.length === 0) {
|
||||
return null;
|
||||
}
|
||||
const format = cFormat || '{y}-{m}-{d} {h}:{i}:{s}';
|
||||
let date;
|
||||
if (typeof time == 'object') {
|
||||
date = time;
|
||||
} else {
|
||||
if (('' + time).length === 10) time = parseInt(time) * 1000;
|
||||
date = new Date(time);
|
||||
}
|
||||
const formatObj = {
|
||||
y: date.getFullYear(),
|
||||
m: date.getMonth() + 1,
|
||||
d: date.getDate(),
|
||||
h: date.getHours(),
|
||||
i: date.getMinutes(),
|
||||
s: date.getSeconds(),
|
||||
a: date.getDay()
|
||||
};
|
||||
const time_str = format.replace(/{(y|m|d|h|i|s|a)+}/g, (result, key) => {
|
||||
let value = formatObj[key];
|
||||
if (key === 'a') return ['一', '二', '三', '四', '五', '六', '日'][value - 1];
|
||||
if (result.length > 0 && value < 10) {
|
||||
value = '0' + value;
|
||||
}
|
||||
return value || 0;
|
||||
});
|
||||
return time_str;
|
||||
}
|
||||
|
||||
export function formatTime(time, option) {
|
||||
time = +time * 1000;
|
||||
const d = new Date(time);
|
||||
const now = Date.now();
|
||||
|
||||
const diff = (now - d) / 1000;
|
||||
|
||||
if (diff < 30) {
|
||||
return '刚刚'
|
||||
} else if (diff < 3600) { // less 1 hour
|
||||
return Math.ceil(diff / 60) + '分钟前'
|
||||
} else if (diff < 3600 * 24) {
|
||||
return Math.ceil(diff / 3600) + '小时前'
|
||||
} else if (diff < 3600 * 24 * 2) {
|
||||
return '1天前'
|
||||
}
|
||||
if (option) {
|
||||
return parseTime(time, option)
|
||||
} else {
|
||||
return d.getMonth() + 1 + '月' + d.getDate() + '日' + d.getHours() + '时' + d.getMinutes() + '分'
|
||||
}
|
||||
}
|
||||
|
||||
// 格式化时间
|
||||
|
||||
|
||||
export function getQueryObject(url) {
|
||||
url = url == null ? window.location.href : url;
|
||||
const search = url.substring(url.lastIndexOf('?') + 1);
|
||||
const obj = {};
|
||||
const reg = /([^?&=]+)=([^?&=]*)/g;
|
||||
search.replace(reg, (rs, $1, $2) => {
|
||||
const name = decodeURIComponent($1);
|
||||
let val = decodeURIComponent($2);
|
||||
val = String(val);
|
||||
obj[name] = val;
|
||||
return rs;
|
||||
});
|
||||
return obj;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
*get getByteLen
|
||||
* @param {Sting} val input value
|
||||
* @returns {number} output value
|
||||
*/
|
||||
export function getByteLen(val) {
|
||||
let len = 0;
|
||||
for (let i = 0; i < val.length; i++) {
|
||||
if (val[i].match(/[^\x00-\xff]/ig) != null) {
|
||||
len += 1;
|
||||
} else { len += 0.5; }
|
||||
}
|
||||
return Math.floor(len);
|
||||
}
|
||||
|
||||
export function cleanArray(actual) {
|
||||
const newArray = [];
|
||||
for (let i = 0; i < actual.length; i++) {
|
||||
if (actual[i]) {
|
||||
newArray.push(actual[i]);
|
||||
}
|
||||
}
|
||||
return newArray;
|
||||
}
|
||||
|
||||
export function param(json) {
|
||||
if (!json) return '';
|
||||
return cleanArray(Object.keys(json).map(key => {
|
||||
if (json[key] === undefined) return '';
|
||||
return encodeURIComponent(key) + '=' +
|
||||
encodeURIComponent(json[key]);
|
||||
})).join('&');
|
||||
}
|
||||
|
||||
export function html2Text(val) {
|
||||
const div = document.createElement('div');
|
||||
div.innerHTML = val;
|
||||
return div.textContent || div.innerText;
|
||||
}
|
||||
|
||||
export function objectMerge(target, source) {
|
||||
/* Merges two objects,
|
||||
giving the last one precedence */
|
||||
|
||||
if (typeof target !== 'object') {
|
||||
target = {};
|
||||
}
|
||||
if (Array.isArray(source)) {
|
||||
return source.slice();
|
||||
}
|
||||
for (const property in source) {
|
||||
if (source.hasOwnProperty(property)) {
|
||||
const sourceProperty = source[property];
|
||||
if (typeof sourceProperty === 'object') {
|
||||
target[property] = objectMerge(target[property], sourceProperty);
|
||||
continue;
|
||||
}
|
||||
target[property] = sourceProperty;
|
||||
}
|
||||
}
|
||||
return target;
|
||||
}
|
||||
|
||||
|
||||
export function scrollTo(element, to, duration) {
|
||||
if (duration <= 0) return;
|
||||
const difference = to - element.scrollTop;
|
||||
const perTick = difference / duration * 10;
|
||||
|
||||
setTimeout(() => {
|
||||
console.log(new Date())
|
||||
element.scrollTop = element.scrollTop + perTick;
|
||||
if (element.scrollTop === to) return;
|
||||
scrollTo(element, to, duration - 10);
|
||||
}, 10);
|
||||
}
|
||||
|
||||
export function toggleClass(element, className) {
|
||||
if (!element || !className) {
|
||||
return;
|
||||
}
|
||||
|
||||
let classString = element.className;
|
||||
const nameIndex = classString.indexOf(className);
|
||||
if (nameIndex === -1) {
|
||||
classString += '' + className;
|
||||
} else {
|
||||
classString = classString.substr(0, nameIndex) + classString.substr(nameIndex + className.length);
|
||||
}
|
||||
element.className = classString;
|
||||
}
|
||||
|
||||
export const pickerOptions = [
|
||||
{
|
||||
text: '今天',
|
||||
onClick(picker) {
|
||||
const end = new Date();
|
||||
const start = new Date(new Date().toDateString());
|
||||
end.setTime(start.getTime());
|
||||
picker.$emit('pick', [start, end]);
|
||||
}
|
||||
}, {
|
||||
text: '最近一周',
|
||||
onClick(picker) {
|
||||
const end = new Date(new Date().toDateString());
|
||||
const start = new Date();
|
||||
start.setTime(end.getTime() - 3600 * 1000 * 24 * 7);
|
||||
picker.$emit('pick', [start, end]);
|
||||
}
|
||||
}, {
|
||||
text: '最近一个月',
|
||||
onClick(picker) {
|
||||
const end = new Date(new Date().toDateString());
|
||||
const start = new Date();
|
||||
start.setTime(start.getTime() - 3600 * 1000 * 24 * 30);
|
||||
picker.$emit('pick', [start, end]);
|
||||
}
|
||||
}, {
|
||||
text: '最近三个月',
|
||||
onClick(picker) {
|
||||
const end = new Date(new Date().toDateString());
|
||||
const start = new Date();
|
||||
start.setTime(start.getTime() - 3600 * 1000 * 24 * 90);
|
||||
picker.$emit('pick', [start, end]);
|
||||
}
|
||||
}]
|
||||
|
||||
export function getTime(type) {
|
||||
if (type === 'start') {
|
||||
return new Date().getTime() - 3600 * 1000 * 24 * 90
|
||||
} else {
|
||||
return new Date(new Date().toDateString())
|
||||
}
|
||||
}
|
||||
|
||||
export function showdownMD(md) {
|
||||
return converter.makeHtml(md)
|
||||
}
|
27
src/utils/openWindow.js
Normal file
27
src/utils/openWindow.js
Normal file
|
@ -0,0 +1,27 @@
|
|||
/**
|
||||
*Created by jiachenpan on 16/11/29.
|
||||
* @param {Sting} url
|
||||
* @param {Sting} title
|
||||
* @param {Number} w
|
||||
* @param {Number} h
|
||||
*/
|
||||
|
||||
export default function openWindow(url, title, w, h) {
|
||||
// Fixes dual-screen position Most browsers Firefox
|
||||
const dualScreenLeft = window.screenLeft !== undefined ? window.screenLeft : screen.left;
|
||||
const dualScreenTop = window.screenTop !== undefined ? window.screenTop : screen.top;
|
||||
|
||||
const width = window.innerWidth ? window.innerWidth : document.documentElement.clientWidth ? document.documentElement.clientWidth : screen.width;
|
||||
const height = window.innerHeight ? window.innerHeight : document.documentElement.clientHeight ? document.documentElement.clientHeight : screen.height;
|
||||
|
||||
const left = ((width / 2) - (w / 2)) + dualScreenLeft;
|
||||
const top = ((height / 2) - (h / 2)) + dualScreenTop;
|
||||
const newWindow = window.open(url, title, 'toolbar=no, location=no, directories=no, status=no, menubar=no, scrollbars=no, resizable=yes, copyhistory=no, width=' + w + ', height=' + h + ', top=' + top + ', left=' + left);
|
||||
|
||||
// Puts focus on the newWindow
|
||||
if (window.focus) {
|
||||
newWindow.focus();
|
||||
}
|
||||
}
|
||||
|
||||
|
41
src/utils/validate.js
Normal file
41
src/utils/validate.js
Normal file
|
@ -0,0 +1,41 @@
|
|||
/**
|
||||
* Created by jiachenpan on 16/11/18.
|
||||
*/
|
||||
|
||||
/* 是否是公司邮箱*/
|
||||
export function isWscnEmail(str) {
|
||||
const reg = /^[a-z0-9](?:[-_.+]?[a-z0-9]+)*@wallstreetcn\.com$/i;
|
||||
return reg.test(str);
|
||||
}
|
||||
|
||||
/* 合法uri*/
|
||||
export function validateURL(textval) {
|
||||
const urlregex = /^(https?|ftp):\/\/([a-zA-Z0-9.-]+(:[a-zA-Z0-9.&%$-]+)*@)*((25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9][0-9]?)(\.(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])){3}|([a-zA-Z0-9-]+\.)*[a-zA-Z0-9-]+\.(com|edu|gov|int|mil|net|org|biz|arpa|info|name|pro|aero|coop|museum|[a-zA-Z]{2}))(:[0-9]+)*(\/($|[a-zA-Z0-9.,?'\\+&%$#=~_-]+))*$/;
|
||||
return urlregex.test(textval);
|
||||
}
|
||||
|
||||
/* 小写字母*/
|
||||
export function validateLowerCase(str) {
|
||||
const reg = /^[a-z]+$/;
|
||||
return reg.test(str);
|
||||
}
|
||||
|
||||
/* 验证key*/
|
||||
// export function validateKey(str) {
|
||||
// var reg = /^[a-z_\-:]+$/;
|
||||
// return reg.test(str);
|
||||
// }
|
||||
|
||||
/* 大写字母*/
|
||||
export function validateUpperCase(str) {
|
||||
const reg = /^[A-Z]+$/;
|
||||
return reg.test(str);
|
||||
}
|
||||
|
||||
/* 大小写字母*/
|
||||
export function validatAlphabets(str) {
|
||||
const reg = /^[A-Za-z]+$/;
|
||||
return reg.test(str);
|
||||
}
|
||||
|
||||
|
179
src/vendor/Blob.js
vendored
Normal file
179
src/vendor/Blob.js
vendored
Normal file
|
@ -0,0 +1,179 @@
|
|||
/* eslint-disable */
|
||||
/* Blob.js
|
||||
* A Blob implementation.
|
||||
* 2014-05-27
|
||||
*
|
||||
* By Eli Grey, http://eligrey.com
|
||||
* By Devin Samarin, https://github.com/eboyjr
|
||||
* License: X11/MIT
|
||||
* See LICENSE.md
|
||||
*/
|
||||
|
||||
/*global self, unescape */
|
||||
/*jslint bitwise: true, regexp: true, confusion: true, es5: true, vars: true, white: true,
|
||||
plusplus: true */
|
||||
|
||||
/*! @source http://purl.eligrey.com/github/Blob.js/blob/master/Blob.js */
|
||||
|
||||
(function (view) {
|
||||
"use strict";
|
||||
|
||||
view.URL = view.URL || view.webkitURL;
|
||||
|
||||
if (view.Blob && view.URL) {
|
||||
try {
|
||||
new Blob;
|
||||
return;
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
// Internally we use a BlobBuilder implementation to base Blob off of
|
||||
// in order to support older browsers that only have BlobBuilder
|
||||
var BlobBuilder = view.BlobBuilder || view.WebKitBlobBuilder || view.MozBlobBuilder || (function(view) {
|
||||
var
|
||||
get_class = function(object) {
|
||||
return Object.prototype.toString.call(object).match(/^\[object\s(.*)\]$/)[1];
|
||||
}
|
||||
, FakeBlobBuilder = function BlobBuilder() {
|
||||
this.data = [];
|
||||
}
|
||||
, FakeBlob = function Blob(data, type, encoding) {
|
||||
this.data = data;
|
||||
this.size = data.length;
|
||||
this.type = type;
|
||||
this.encoding = encoding;
|
||||
}
|
||||
, FBB_proto = FakeBlobBuilder.prototype
|
||||
, FB_proto = FakeBlob.prototype
|
||||
, FileReaderSync = view.FileReaderSync
|
||||
, FileException = function(type) {
|
||||
this.code = this[this.name = type];
|
||||
}
|
||||
, file_ex_codes = (
|
||||
"NOT_FOUND_ERR SECURITY_ERR ABORT_ERR NOT_READABLE_ERR ENCODING_ERR "
|
||||
+ "NO_MODIFICATION_ALLOWED_ERR INVALID_STATE_ERR SYNTAX_ERR"
|
||||
).split(" ")
|
||||
, file_ex_code = file_ex_codes.length
|
||||
, real_URL = view.URL || view.webkitURL || view
|
||||
, real_create_object_URL = real_URL.createObjectURL
|
||||
, real_revoke_object_URL = real_URL.revokeObjectURL
|
||||
, URL = real_URL
|
||||
, btoa = view.btoa
|
||||
, atob = view.atob
|
||||
|
||||
, ArrayBuffer = view.ArrayBuffer
|
||||
, Uint8Array = view.Uint8Array
|
||||
;
|
||||
FakeBlob.fake = FB_proto.fake = true;
|
||||
while (file_ex_code--) {
|
||||
FileException.prototype[file_ex_codes[file_ex_code]] = file_ex_code + 1;
|
||||
}
|
||||
if (!real_URL.createObjectURL) {
|
||||
URL = view.URL = {};
|
||||
}
|
||||
URL.createObjectURL = function(blob) {
|
||||
var
|
||||
type = blob.type
|
||||
, data_URI_header
|
||||
;
|
||||
if (type === null) {
|
||||
type = "application/octet-stream";
|
||||
}
|
||||
if (blob instanceof FakeBlob) {
|
||||
data_URI_header = "data:" + type;
|
||||
if (blob.encoding === "base64") {
|
||||
return data_URI_header + ";base64," + blob.data;
|
||||
} else if (blob.encoding === "URI") {
|
||||
return data_URI_header + "," + decodeURIComponent(blob.data);
|
||||
} if (btoa) {
|
||||
return data_URI_header + ";base64," + btoa(blob.data);
|
||||
} else {
|
||||
return data_URI_header + "," + encodeURIComponent(blob.data);
|
||||
}
|
||||
} else if (real_create_object_URL) {
|
||||
return real_create_object_URL.call(real_URL, blob);
|
||||
}
|
||||
};
|
||||
URL.revokeObjectURL = function(object_URL) {
|
||||
if (object_URL.substring(0, 5) !== "data:" && real_revoke_object_URL) {
|
||||
real_revoke_object_URL.call(real_URL, object_URL);
|
||||
}
|
||||
};
|
||||
FBB_proto.append = function(data/*, endings*/) {
|
||||
var bb = this.data;
|
||||
// decode data to a binary string
|
||||
if (Uint8Array && (data instanceof ArrayBuffer || data instanceof Uint8Array)) {
|
||||
var
|
||||
str = ""
|
||||
, buf = new Uint8Array(data)
|
||||
, i = 0
|
||||
, buf_len = buf.length
|
||||
;
|
||||
for (; i < buf_len; i++) {
|
||||
str += String.fromCharCode(buf[i]);
|
||||
}
|
||||
bb.push(str);
|
||||
} else if (get_class(data) === "Blob" || get_class(data) === "File") {
|
||||
if (FileReaderSync) {
|
||||
var fr = new FileReaderSync;
|
||||
bb.push(fr.readAsBinaryString(data));
|
||||
} else {
|
||||
// async FileReader won't work as BlobBuilder is sync
|
||||
throw new FileException("NOT_READABLE_ERR");
|
||||
}
|
||||
} else if (data instanceof FakeBlob) {
|
||||
if (data.encoding === "base64" && atob) {
|
||||
bb.push(atob(data.data));
|
||||
} else if (data.encoding === "URI") {
|
||||
bb.push(decodeURIComponent(data.data));
|
||||
} else if (data.encoding === "raw") {
|
||||
bb.push(data.data);
|
||||
}
|
||||
} else {
|
||||
if (typeof data !== "string") {
|
||||
data += ""; // convert unsupported types to strings
|
||||
}
|
||||
// decode UTF-16 to binary string
|
||||
bb.push(unescape(encodeURIComponent(data)));
|
||||
}
|
||||
};
|
||||
FBB_proto.getBlob = function(type) {
|
||||
if (!arguments.length) {
|
||||
type = null;
|
||||
}
|
||||
return new FakeBlob(this.data.join(""), type, "raw");
|
||||
};
|
||||
FBB_proto.toString = function() {
|
||||
return "[object BlobBuilder]";
|
||||
};
|
||||
FB_proto.slice = function(start, end, type) {
|
||||
var args = arguments.length;
|
||||
if (args < 3) {
|
||||
type = null;
|
||||
}
|
||||
return new FakeBlob(
|
||||
this.data.slice(start, args > 1 ? end : this.data.length)
|
||||
, type
|
||||
, this.encoding
|
||||
);
|
||||
};
|
||||
FB_proto.toString = function() {
|
||||
return "[object Blob]";
|
||||
};
|
||||
FB_proto.close = function() {
|
||||
this.size = this.data.length = 0;
|
||||
};
|
||||
return FakeBlobBuilder;
|
||||
}(view));
|
||||
|
||||
view.Blob = function Blob(blobParts, options) {
|
||||
var type = options ? (options.type || "") : "";
|
||||
var builder = new BlobBuilder();
|
||||
if (blobParts) {
|
||||
for (var i = 0, len = blobParts.length; i < len; i++) {
|
||||
builder.append(blobParts[i]);
|
||||
}
|
||||
}
|
||||
return builder.getBlob(type);
|
||||
};
|
||||
}(typeof self !== "undefined" && self || typeof window !== "undefined" && window || this.content || this));
|
141
src/vendor/Export2Excel.js
vendored
Normal file
141
src/vendor/Export2Excel.js
vendored
Normal file
|
@ -0,0 +1,141 @@
|
|||
/* eslint-disable */
|
||||
require('script-loader!file-saver');
|
||||
require('script-loader!vendor/Blob');
|
||||
require('script-loader!xlsx/dist/xlsx.core.min');
|
||||
function generateArray(table) {
|
||||
var out = [];
|
||||
var rows = table.querySelectorAll('tr');
|
||||
var ranges = [];
|
||||
for (var R = 0; R < rows.length; ++R) {
|
||||
var outRow = [];
|
||||
var row = rows[R];
|
||||
var columns = row.querySelectorAll('td');
|
||||
for (var C = 0; C < columns.length; ++C) {
|
||||
var cell = columns[C];
|
||||
var colspan = cell.getAttribute('colspan');
|
||||
var rowspan = cell.getAttribute('rowspan');
|
||||
var cellValue = cell.innerText;
|
||||
if (cellValue !== "" && cellValue == +cellValue) cellValue = +cellValue;
|
||||
|
||||
//Skip ranges
|
||||
ranges.forEach(function (range) {
|
||||
if (R >= range.s.r && R <= range.e.r && outRow.length >= range.s.c && outRow.length <= range.e.c) {
|
||||
for (var i = 0; i <= range.e.c - range.s.c; ++i) outRow.push(null);
|
||||
}
|
||||
});
|
||||
|
||||
//Handle Row Span
|
||||
if (rowspan || colspan) {
|
||||
rowspan = rowspan || 1;
|
||||
colspan = colspan || 1;
|
||||
ranges.push({s: {r: R, c: outRow.length}, e: {r: R + rowspan - 1, c: outRow.length + colspan - 1}});
|
||||
}
|
||||
;
|
||||
|
||||
//Handle Value
|
||||
outRow.push(cellValue !== "" ? cellValue : null);
|
||||
|
||||
//Handle Colspan
|
||||
if (colspan) for (var k = 0; k < colspan - 1; ++k) outRow.push(null);
|
||||
}
|
||||
out.push(outRow);
|
||||
}
|
||||
return [out, ranges];
|
||||
};
|
||||
|
||||
function datenum(v, date1904) {
|
||||
if (date1904) v += 1462;
|
||||
var epoch = Date.parse(v);
|
||||
return (epoch - new Date(Date.UTC(1899, 11, 30))) / (24 * 60 * 60 * 1000);
|
||||
}
|
||||
|
||||
function sheet_from_array_of_arrays(data, opts) {
|
||||
var ws = {};
|
||||
var range = {s: {c: 10000000, r: 10000000}, e: {c: 0, r: 0}};
|
||||
for (var R = 0; R != data.length; ++R) {
|
||||
for (var C = 0; C != data[R].length; ++C) {
|
||||
if (range.s.r > R) range.s.r = R;
|
||||
if (range.s.c > C) range.s.c = C;
|
||||
if (range.e.r < R) range.e.r = R;
|
||||
if (range.e.c < C) range.e.c = C;
|
||||
var cell = {v: data[R][C]};
|
||||
if (cell.v == null) continue;
|
||||
var cell_ref = XLSX.utils.encode_cell({c: C, r: R});
|
||||
|
||||
if (typeof cell.v === 'number') cell.t = 'n';
|
||||
else if (typeof cell.v === 'boolean') cell.t = 'b';
|
||||
else if (cell.v instanceof Date) {
|
||||
cell.t = 'n';
|
||||
cell.z = XLSX.SSF._table[14];
|
||||
cell.v = datenum(cell.v);
|
||||
}
|
||||
else cell.t = 's';
|
||||
|
||||
ws[cell_ref] = cell;
|
||||
}
|
||||
}
|
||||
if (range.s.c < 10000000) ws['!ref'] = XLSX.utils.encode_range(range);
|
||||
return ws;
|
||||
}
|
||||
|
||||
function Workbook() {
|
||||
if (!(this instanceof Workbook)) return new Workbook();
|
||||
this.SheetNames = [];
|
||||
this.Sheets = {};
|
||||
}
|
||||
|
||||
function s2ab(s) {
|
||||
var buf = new ArrayBuffer(s.length);
|
||||
var view = new Uint8Array(buf);
|
||||
for (var i = 0; i != s.length; ++i) view[i] = s.charCodeAt(i) & 0xFF;
|
||||
return buf;
|
||||
}
|
||||
|
||||
export function export_table_to_excel(id) {
|
||||
var theTable = document.getElementById(id);
|
||||
console.log('a')
|
||||
var oo = generateArray(theTable);
|
||||
var ranges = oo[1];
|
||||
|
||||
/* original data */
|
||||
var data = oo[0];
|
||||
var ws_name = "SheetJS";
|
||||
console.log(data);
|
||||
|
||||
var wb = new Workbook(), ws = sheet_from_array_of_arrays(data);
|
||||
|
||||
/* add ranges to worksheet */
|
||||
// ws['!cols'] = ['apple', 'banan'];
|
||||
ws['!merges'] = ranges;
|
||||
|
||||
/* add worksheet to workbook */
|
||||
wb.SheetNames.push(ws_name);
|
||||
wb.Sheets[ws_name] = ws;
|
||||
|
||||
var wbout = XLSX.write(wb, {bookType: 'xlsx', bookSST: false, type: 'binary'});
|
||||
|
||||
saveAs(new Blob([s2ab(wbout)], {type: "application/octet-stream"}), "test.xlsx")
|
||||
}
|
||||
|
||||
function formatJson(jsonData) {
|
||||
console.log(jsonData)
|
||||
}
|
||||
export function export_json_to_excel(th, jsonData, defaultTitle) {
|
||||
|
||||
/* original data */
|
||||
|
||||
var data = jsonData;
|
||||
data.unshift(th);
|
||||
var ws_name = "SheetJS";
|
||||
|
||||
var wb = new Workbook(), ws = sheet_from_array_of_arrays(data);
|
||||
|
||||
|
||||
/* add worksheet to workbook */
|
||||
wb.SheetNames.push(ws_name);
|
||||
wb.Sheets[ws_name] = ws;
|
||||
|
||||
var wbout = XLSX.write(wb, {bookType: 'xlsx', bookSST: false, type: 'binary'});
|
||||
var title = defaultTitle || '列表'
|
||||
saveAs(new Blob([s2ab(wbout)], {type: "application/octet-stream"}), title + ".xlsx")
|
||||
}
|
87
src/views/admin/createUser.vue
Normal file
87
src/views/admin/createUser.vue
Normal file
|
@ -0,0 +1,87 @@
|
|||
<template>
|
||||
<div class="app-container">
|
||||
<h1 class="page-heading">
|
||||
创建后台用户
|
||||
</h1>
|
||||
<el-form ref="createForm" :rules="createRules" label-position="left" style='width:80%' :model="form" label-width="100px">
|
||||
<el-form-item label="用户邮箱" prop="email">
|
||||
<el-input v-model="form.email" placeholder="公司邮箱"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="权限选择" >
|
||||
<el-select style="width: 100%" v-model="form.role" multiple placeholder="请选择">
|
||||
<el-option
|
||||
v-for="item in roleList"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value">
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" :loading="loading" @click.native.prevent="onSubmit">立即创建</el-button>
|
||||
<el-button>
|
||||
<router-link class="normal_link" to="/index">
|
||||
取消
|
||||
</router-link>
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { createNewUser, getRoleList } from 'api/adminUser';
|
||||
import { isWscnEmail } from 'utils/validate';
|
||||
|
||||
export default{
|
||||
name: 'createUser',
|
||||
data() {
|
||||
const validateEmail = (rule, value, callback) => {
|
||||
if (!isWscnEmail(value)) {
|
||||
callback(new Error('邮箱错误'));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
};
|
||||
return {
|
||||
roleList: [],
|
||||
loading: false,
|
||||
form: {
|
||||
email: '',
|
||||
role: ''
|
||||
},
|
||||
createRules: {
|
||||
email: [
|
||||
{ required: true, message: '请输入邮箱', trigger: 'blur' },
|
||||
{ validator: validateEmail }
|
||||
]
|
||||
}
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
onSubmit() {
|
||||
this.$refs.createForm.validate(valid => {
|
||||
if (valid) {
|
||||
this.loading = true;
|
||||
const data = {
|
||||
email: this.form.email,
|
||||
roles: this.form.role
|
||||
};
|
||||
createNewUser(data).then(() => {
|
||||
this.$message.success('创建成功');
|
||||
});
|
||||
} else {
|
||||
this.$message.error('error submit!!');
|
||||
}
|
||||
this.loading = false;
|
||||
});
|
||||
}
|
||||
},
|
||||
created() {
|
||||
getRoleList().then(response => {
|
||||
const roleMap = response.data.role_map;
|
||||
const keyArr = Object.keys(roleMap);
|
||||
this.roleList = keyArr.map(v => ({ value: v, label: roleMap[v] }));
|
||||
});
|
||||
}
|
||||
};
|
||||
</script>
|
404
src/views/admin/profile.vue
Normal file
404
src/views/admin/profile.vue
Normal file
|
@ -0,0 +1,404 @@
|
|||
<template>
|
||||
<div class="profile-container clearfix">
|
||||
<div style="position: relative;margin-left: 30px;">
|
||||
<PanThumb :image="avatar"> 你的权限:
|
||||
<span class="pan-info-roles" v-for="item in roles">{{item}}</span>
|
||||
</PanThumb>
|
||||
<el-button type="primary" icon="upload" style="position: absolute;bottom: 15px;margin-left: 40px;"
|
||||
@click="handleImagecropper">修改头像
|
||||
</el-button>
|
||||
</div>
|
||||
<!--popover-->
|
||||
<el-popover
|
||||
ref="popoverWX"
|
||||
placement="top"
|
||||
width="160"
|
||||
trigger="click"
|
||||
v-model="popoverVisibleWX">
|
||||
<p>你确定要解绑么?</p>
|
||||
<div style="text-align: right; margin: 0">
|
||||
<el-button size="mini" type="text" @click="popoverVisibleWX = false">取消</el-button>
|
||||
<el-button type="primary" size="mini" @click="handleUnbind('wechat')">确定</el-button>
|
||||
</div>
|
||||
</el-popover>
|
||||
<el-popover
|
||||
ref="popoverQQ"
|
||||
placement="top"
|
||||
width="160"
|
||||
trigger="click"
|
||||
v-model="popoverVisibleQQ">
|
||||
<p>你确定要解绑么?</p>
|
||||
<div style="text-align: right; margin: 0">
|
||||
<el-button size="mini" type="text" @click="popoverVisibleQQ = false">取消</el-button>
|
||||
<el-button type="primary" size="mini" @click="handleUnbind('tencent')">确定</el-button>
|
||||
</div>
|
||||
</el-popover>
|
||||
<!--popover End-->
|
||||
|
||||
<el-card class="box-card">
|
||||
<div slot="header" class="clearfix">
|
||||
<span style="line-height: 36px;">个人资料</span>
|
||||
</div>
|
||||
<div class="box-item">
|
||||
<span class="field-label">昵称</span>
|
||||
<div class="field-content">
|
||||
{{name}}
|
||||
<el-button class="edit-btn" @click="handleEditName" type="primary" icon="edit"
|
||||
size="small"></el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="box-item">
|
||||
<span class="field-label">简介</span>
|
||||
<div class="field-content">
|
||||
{{introduction.length==0?'未填写':introduction}}
|
||||
<el-button class="edit-btn" @click="handleIntroductionName" type="primary" icon="edit"
|
||||
size="small"></el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="box-item" style="margin-bottom: 10px;">
|
||||
<span class="field-label">密码</span>
|
||||
<div class="field-content">
|
||||
<el-button type="primary" @click="resetPSWdialogVisible=true">修改密码</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="box-item" style="margin-top: 5px;">
|
||||
<div class="field-content">
|
||||
<span class="wx-svg-container"><wscn-icon-svg icon-class="weixin" class="icon"/></span>
|
||||
<el-button class="unbind-btn" v-popover:popoverWX type="danger">解绑微信</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="box-item">
|
||||
<div class="field-content">
|
||||
<span class="qq-svg-container"><wscn-icon-svg icon-class="QQ" class="icon"/></span>
|
||||
<el-button class="unbind-btn" v-popover:popoverQQ style="padding: 10px 18px" type="danger">
|
||||
解绑QQ
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<el-card class="box-card">
|
||||
<div slot="header" class="clearfix">
|
||||
<span style="line-height: 36px;">偏好设置</span>
|
||||
<el-button @click="updateSetting" style="float: right;margin-top: 5px;" size="small" type="success">
|
||||
更新偏好
|
||||
</el-button>
|
||||
</div>
|
||||
<div>
|
||||
<div class="box-item">
|
||||
<span class="field-label">文章平台默认项选择:</span>
|
||||
<el-checkbox-group v-model="articlePlatform">
|
||||
<el-checkbox label="wscn-platform">见闻</el-checkbox>
|
||||
<el-checkbox label="gold-platform">黄金头条</el-checkbox>
|
||||
<el-checkbox label="weex-platform">WEEX</el-checkbox>
|
||||
</el-checkbox-group>
|
||||
<span class="field-label">使用自定义主题:</span>
|
||||
<el-switch
|
||||
v-model="theme"
|
||||
on-text=""
|
||||
off-text="">
|
||||
</el-switch>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
|
||||
<ImageCropper field="img"
|
||||
:width="300"
|
||||
:height="300"
|
||||
url="/upload"
|
||||
@crop-upload-success="cropSuccess"
|
||||
:key="imagecropperKey"
|
||||
v-show="imagecropperShow"></ImageCropper>
|
||||
|
||||
<el-dialog title="昵称" v-model="nameDialogFormVisible">
|
||||
<el-form label-position="left" label-width="50px">
|
||||
<el-form-item label="昵称" style="width: 300px;">
|
||||
<input class="input" ref="nameInput" :value="name" autocomplete="off" :maxlength=10>
|
||||
<span>(最多填写十个字符)</span>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button @click="nameDialogFormVisible = false">取 消</el-button>
|
||||
<el-button type="primary" @click="setName">确 定</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
|
||||
<el-dialog title="简介" v-model="introductionDialogFormVisible">
|
||||
<el-form label-position="left" label-width="50px">
|
||||
<el-form-item label="简介" style="width: 500px;">
|
||||
<textarea :row=3 class="textarea" ref="introductionInput" :value="introduction"></textarea>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button @click="introductionDialogFormVisible = false">取 消</el-button>
|
||||
<el-button type="primary" @click="setIntroduction">确 定</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
|
||||
<el-dialog title="提示" v-model="resetPSWdialogVisible" size="tiny">
|
||||
<span>你确定要重设密码么? <strong>     ( 注:重设密码将会登出,请注意!!! )</strong></span>
|
||||
<span slot="footer" class="dialog-footer">
|
||||
<el-button @click="resetPSWdialogVisible = false">取 消</el-button>
|
||||
<el-button type="primary" @click="resetPSW">确 定</el-button>
|
||||
</span>
|
||||
</el-dialog>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import { updateInfo, unbind, updateSetting } from 'api/adminUser';
|
||||
import ImageCropper from 'components/ImageCropper';
|
||||
import PanThumb from 'components/PanThumb';
|
||||
import { toggleClass } from 'utils'
|
||||
|
||||
export default{
|
||||
components: { ImageCropper, PanThumb },
|
||||
data() {
|
||||
return {
|
||||
nameDialogFormVisible: false,
|
||||
introductionDialogFormVisible: false,
|
||||
resetPSWdialogVisible: false,
|
||||
popoverVisibleQQ: false,
|
||||
popoverVisibleWX: false,
|
||||
imagecropperShow: false,
|
||||
imagecropperKey: 0,
|
||||
articlePlatform: [],
|
||||
theme: false
|
||||
}
|
||||
},
|
||||
created() {
|
||||
if (this.setting.articlePlatform) {
|
||||
this.articlePlatform = this.setting.articlePlatform
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters([
|
||||
'name',
|
||||
'avatar',
|
||||
'email',
|
||||
'introduction',
|
||||
'roles',
|
||||
'uid',
|
||||
'setting'
|
||||
])
|
||||
},
|
||||
watch: {
|
||||
theme() {
|
||||
toggleClass(document.body, 'custom-theme')
|
||||
// this.$store.dispatch('setTheme', value);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
resetPSW() {
|
||||
this.$store.dispatch('LogOut').then(() => {
|
||||
this.$router.push({ path: '/sendpwd' })
|
||||
});
|
||||
},
|
||||
toggleResetDialog(state) {
|
||||
this.resetDialogVisible = state;
|
||||
},
|
||||
handleEditName() {
|
||||
this.nameDialogFormVisible = true;
|
||||
},
|
||||
handleIntroductionName() {
|
||||
this.introductionDialogFormVisible = true;
|
||||
},
|
||||
setName() {
|
||||
const displayName = this.$refs.nameInput.value;
|
||||
const data = {
|
||||
display_name: displayName,
|
||||
uid: this.uid
|
||||
};
|
||||
updateInfo(data).then(() => {
|
||||
this.$store.commit('SET_NAME', displayName);
|
||||
this.$notify({
|
||||
title: '成功',
|
||||
message: '昵称修改成功',
|
||||
type: 'success'
|
||||
});
|
||||
});
|
||||
this.nameDialogFormVisible = false;
|
||||
},
|
||||
setIntroduction() {
|
||||
const introduction = this.$refs.introductionInput.value;
|
||||
const data = {
|
||||
introduction,
|
||||
uid: this.uid
|
||||
};
|
||||
updateInfo(data).then(() => {
|
||||
this.$store.commit('SET_INTRODUCTION', introduction);
|
||||
this.$notify({
|
||||
title: '成功',
|
||||
message: '简介修改成功',
|
||||
type: 'success'
|
||||
});
|
||||
});
|
||||
this.introductionDialogFormVisible = false;
|
||||
},
|
||||
handleUnbind(unbindType) {
|
||||
const data = {
|
||||
unbind_type: unbindType
|
||||
};
|
||||
unbind(data).then(() => {
|
||||
this.$notify({
|
||||
title: '成功',
|
||||
message: '解绑成功,即将登出',
|
||||
type: 'success'
|
||||
});
|
||||
setTimeout(() => {
|
||||
this.$store.dispatch('LogOut').then(() => {
|
||||
this.$router.push({ path: '/login' })
|
||||
});
|
||||
}, 3000)
|
||||
});
|
||||
|
||||
this.popoverVisibleQQ = false;
|
||||
this.popoverVisibleWX = false;
|
||||
},
|
||||
handleImagecropper() {
|
||||
this.imagecropperShow = true;
|
||||
this.imagecropperKey = this.imagecropperKey + 1;
|
||||
},
|
||||
cropSuccess(url) {
|
||||
this.imagecropperShow = false;
|
||||
const data = {
|
||||
image: url,
|
||||
uid: this.uid
|
||||
};
|
||||
updateInfo(data).then(() => {
|
||||
this.$store.commit('SET_AVATAR', url);
|
||||
this.$notify({
|
||||
title: '成功',
|
||||
message: '头像修改成功',
|
||||
type: 'success'
|
||||
});
|
||||
});
|
||||
},
|
||||
updateSetting() {
|
||||
const obj = Object.assign(this.setting, { articlePlatform: this.articlePlatform });
|
||||
updateSetting({ setting: JSON.stringify(obj) }).then(() => {
|
||||
this.$store.commit('SET_SETTING', this.setting);
|
||||
this.$notify({
|
||||
title: '成功',
|
||||
message: '更新偏好成功',
|
||||
type: 'success'
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style rel="stylesheet/scss" lang="scss" scoped>
|
||||
.input {
|
||||
-moz-appearance: none;
|
||||
appearance: none;
|
||||
background-color: #fff;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #bfcbd9;
|
||||
color: #1f2d3d;
|
||||
display: block;
|
||||
font-size: inherit;
|
||||
height: 36px;
|
||||
line-height: 1;
|
||||
padding: 3px 10px;
|
||||
transition: border-color .2s cubic-bezier(.645, .045, .355, 1);
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.textarea {
|
||||
height: 90px;
|
||||
display: block;
|
||||
resize: vertical;
|
||||
padding: 5px 7px;
|
||||
line-height: 1.5;
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
font-size: 14px;
|
||||
color: #1f2d3d;
|
||||
background-color: #fff;
|
||||
background-image: none;
|
||||
border: 1px solid #bfcbd9;
|
||||
border-radius: 4px;
|
||||
transition: border-color .2s cubic-bezier(.645, .045, .355, 1);
|
||||
}
|
||||
|
||||
.icon {
|
||||
color: #fff;
|
||||
font-size: 30px;
|
||||
margin-top: 6px;
|
||||
}
|
||||
|
||||
.wx-svg-container, .qq-svg-container {
|
||||
display: inline-block;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
line-height: 40px;
|
||||
text-align: center;
|
||||
padding-top: 1px;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 10px;
|
||||
margin-right: 55px;
|
||||
}
|
||||
|
||||
.wx-svg-container {
|
||||
background-color: #8dc349;
|
||||
}
|
||||
|
||||
.qq-svg-container {
|
||||
background-color: #6BA2D6;
|
||||
}
|
||||
|
||||
.unbind-btn {
|
||||
position: absolute;
|
||||
right: -60px;
|
||||
top: 2px;
|
||||
}
|
||||
|
||||
.profile-container {
|
||||
padding: 20px;
|
||||
.box-card {
|
||||
width: 400px;
|
||||
margin: 30px;
|
||||
float: left;
|
||||
.field-label {
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
line-height: 36px;
|
||||
color: #333;
|
||||
padding-right: 30px;
|
||||
}
|
||||
.box-item {
|
||||
.field-content {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
.edit-btn {
|
||||
display: none;
|
||||
position: absolute;
|
||||
right: -50px;
|
||||
top: -5px;
|
||||
}
|
||||
}
|
||||
&:hover {
|
||||
.edit-btn {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.pan-info-roles {
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
color: #333;
|
||||
display: block;
|
||||
}
|
||||
</style>
|
92
src/views/admin/quicklycreate.vue
Normal file
92
src/views/admin/quicklycreate.vue
Normal file
|
@ -0,0 +1,92 @@
|
|||
<template>
|
||||
<div class="app-container quicklyCreateUser-container">
|
||||
<el-form ref="form" :rules="rules" :model="form" label-position="left" label-width="60px">
|
||||
<el-card style=" margin-top: 50px;width: 600px;">
|
||||
<div slot="header" class="clearfix">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="20">
|
||||
<el-form-item label="昵称" prop="display_name">
|
||||
<el-input v-model="form.display_name"></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="4">
|
||||
<el-button type="success" @click="onSubmit">立即创建</el-button>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
<el-button style="height: 150px;width: 150px;" @click="handleImagecropper" type="primary">上传头像
|
||||
</el-button>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<img style=" float:right;width: 150px;height: 150px;border-radius: 50%;margin-left: 50px;"
|
||||
:src="form.image">
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-card>
|
||||
</el-form>
|
||||
|
||||
|
||||
<el-tooltip style="position: absolute;margin-left: 750px;top: 380px" placement="top">
|
||||
<el-button>Tooltip</el-button>
|
||||
<div slot="content">昵称为必填项<br/><br/>一键创建只能创建后台虚拟账号<br/><br/>没有任何实际操作能力</div>
|
||||
</el-tooltip>
|
||||
|
||||
<ImageCropper field="img"
|
||||
:width="300"
|
||||
:height="300"
|
||||
url="/upload"
|
||||
@crop-upload-success="cropSuccess"
|
||||
:key="imagecropperKey"
|
||||
v-show="imagecropperShow">
|
||||
|
||||
</ImageCropper>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { createNewUser } from 'api/adminUser';
|
||||
import ImageCropper from 'components/ImageCropper';
|
||||
|
||||
export default{
|
||||
name: 'quicklyCreateUser',
|
||||
components: { ImageCropper },
|
||||
data() {
|
||||
return {
|
||||
form: {
|
||||
display_name: '',
|
||||
image: '',
|
||||
role: ['virtual_editor']
|
||||
},
|
||||
imagecropperShow: false,
|
||||
imagecropperKey: 0,
|
||||
rules: {
|
||||
display_name: [{ required: true, message: '昵称必填', trigger: 'blur' }]
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onSubmit() {
|
||||
this.$refs.form.validate(valid => {
|
||||
if (valid) {
|
||||
createNewUser(this.form).then(() => {
|
||||
this.$message.success('创建成功')
|
||||
});
|
||||
} else {
|
||||
console.log('error submit!!');
|
||||
return false;
|
||||
}
|
||||
});
|
||||
},
|
||||
handleImagecropper() {
|
||||
this.imagecropperShow = true;
|
||||
this.imagecropperKey = this.imagecropperKey + 1;
|
||||
},
|
||||
cropSuccess(url) {
|
||||
this.imagecropperShow = false;
|
||||
this.form.image = url
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
241
src/views/admin/usersList.vue
Normal file
241
src/views/admin/usersList.vue
Normal file
|
@ -0,0 +1,241 @@
|
|||
<template>
|
||||
<div class="app-container adminUsers-list-container">
|
||||
<div class="filter-container">
|
||||
<el-input @keyup.enter.native="handleFilter" style="width:135px;" class="filter-item" placeholder="ID" type="number" v-model="listQuery.uid">
|
||||
</el-input>
|
||||
|
||||
<el-input style="width:135px;" class="filter-item" placeholder="Name" @keyup.enter.native="handleFilter" v-model="listQuery.display_name">
|
||||
</el-input>
|
||||
|
||||
<el-input class="filter-item" style="width:300px;display: inline-table" placeholder="email" @keyup.enter.native="handleFilter"
|
||||
v-model="listQuery.email">
|
||||
<template slot="append">@wallstreetcn.com</template>
|
||||
</el-input>
|
||||
</el-input>
|
||||
|
||||
<el-select style="width: 250px" class="filter-item" v-model="listQuery.role" clearable placeholder="权限">
|
||||
<el-option v-for="item in roleOptions" :key="item.value" :label="item.label" :value="item.value">
|
||||
</el-option>
|
||||
</el-select>
|
||||
|
||||
<el-button class="filter-item" type="primary" icon="search" @click="handleFilter">
|
||||
搜索
|
||||
</el-button>
|
||||
<el-button class="filter-item" type="danger" @click="resetFilter">重置筛选项</el-button>
|
||||
</div>
|
||||
|
||||
<el-table :data="list" v-loading.body="listLoading" border fit highlight-current-row>
|
||||
<el-table-column label="ID" width="130">
|
||||
<template scope="scope">
|
||||
<span>{{scope.row.uid}}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column label="Name">
|
||||
<template scope="scope">
|
||||
<span>{{scope.row.display_name}}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column label="Email">
|
||||
<template scope="scope">
|
||||
<span>{{scope.row.email}}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column label="Role">
|
||||
<template scope="scope">
|
||||
<el-tag style="margin-right: 5px;" v-for='item in scope.row.roles' :key='item+scope.row.uid' type="primary">
|
||||
{{item}}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column label="操作" width="170">
|
||||
<template scope="scope">
|
||||
<el-button size="small" type="success" @click="handleEdit(scope.row)">编辑权限</el-button>
|
||||
<el-button size="small" v-if='scope.row.roles.indexOf("virtual_editor")>=0||hasRoleEdit' type="primary" @click="handleInfo(scope.row)">修改</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<div v-show="!listLoading" class="pagination-container">
|
||||
<el-pagination @size-change="handleSizeChange" @current-change="handleCurrentChange" :current-page="listQuery.page" :page-sizes="[10,20,30, 50]"
|
||||
:page-size="listQuery.limit" layout="total, sizes, prev, pager, next, jumper" :total="total">
|
||||
</el-pagination>
|
||||
</div>
|
||||
|
||||
<el-dialog title="编辑" v-model="dialogFormVisible" size='small'>
|
||||
<el-form :model="tempForm" label-position="left" label-width="70px">
|
||||
<el-form-item label="权限">
|
||||
<el-select style="width: 100%" v-model="tempForm.roles" multiple placeholder="请选择">
|
||||
<el-option v-for="item in roleOptions" :key="item.value" :label="item.label" :value="item.value">
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button @click="dialogFormVisible = false">取 消</el-button>
|
||||
<el-button type="primary" @click="updateUser">确 定</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
|
||||
<el-dialog title="编辑简介" v-model="dialogInfoVisible">
|
||||
<el-form :model="infoForm">
|
||||
<el-form-item label="昵称">
|
||||
<el-input v-model="infoForm.display_name"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="简介">
|
||||
<el-input type="textarea" :autosize="{ minRows: 2}" v-model="infoForm.introduction"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="头像">
|
||||
|
||||
</el-form-item>
|
||||
<div style='width:200px;height:200px;'>
|
||||
<singleImageUpload2 v-model="infoForm.image"></singleImageUpload2>
|
||||
</div>
|
||||
</el-form>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button @click="dialogInfoVisible = false">取 消</el-button>
|
||||
<el-button type="primary" @click="submitInfo">确 定</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import { getRoleList, updateInfo } from 'api/adminUser';
|
||||
import { getUserList } from 'api/remoteSearch';
|
||||
import { objectMerge } from 'utils';
|
||||
import singleImageUpload2 from 'components/Upload/singleImage2';
|
||||
|
||||
export default {
|
||||
name: 'adminUsersList',
|
||||
components: { singleImageUpload2 },
|
||||
data() {
|
||||
return {
|
||||
list: null,
|
||||
total: null,
|
||||
listLoading: true,
|
||||
listQuery: {
|
||||
page: 1,
|
||||
limit: 20,
|
||||
role: '',
|
||||
uid: undefined,
|
||||
display_name: ''
|
||||
},
|
||||
roleOptions: [],
|
||||
dialogFormVisible: false,
|
||||
tempForm: {
|
||||
uid: null,
|
||||
roles: []
|
||||
},
|
||||
dialogInfoVisible: false,
|
||||
infoForm: {
|
||||
uid: null,
|
||||
image: '',
|
||||
display_name: '',
|
||||
introduction: ''
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters([
|
||||
'roles'
|
||||
]),
|
||||
hasRoleEdit() {
|
||||
if (this.roles.indexOf('admin') >= 0) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.getList();
|
||||
this.getAdminRoleList();
|
||||
},
|
||||
methods: {
|
||||
getList() {
|
||||
getUserList(this.listQuery).then(response => {
|
||||
const data = response.data;
|
||||
this.list = data.items;
|
||||
this.total = data.count;
|
||||
this.listLoading = false;
|
||||
})
|
||||
},
|
||||
resetFilter() {
|
||||
this.listQuery = {
|
||||
page: 1,
|
||||
limit: 20,
|
||||
role: '',
|
||||
uid: undefined,
|
||||
display_name: ''
|
||||
}
|
||||
this.getList();
|
||||
},
|
||||
handleSizeChange(val) {
|
||||
this.listQuery.limit = val;
|
||||
this.getList();
|
||||
},
|
||||
handleCurrentChange(val) {
|
||||
this.listQuery.page = val;
|
||||
this.getList();
|
||||
},
|
||||
handleFilter() {
|
||||
this.getList();
|
||||
},
|
||||
getAdminRoleList() {
|
||||
getRoleList().then(response => {
|
||||
const roleMap = response.data.role_map;
|
||||
const keyArr = Object.keys(roleMap);
|
||||
this.roleOptions = keyArr.map(v => ({ value: v, label: roleMap[v] }));
|
||||
})
|
||||
},
|
||||
handleEdit(row) {
|
||||
this.dialogFormVisible = true;
|
||||
objectMerge(this.tempForm, row);
|
||||
},
|
||||
updateUser() {
|
||||
updateInfo(this.tempForm).then(() => {
|
||||
this.$notify({
|
||||
title: '成功',
|
||||
message: '修改成功',
|
||||
type: 'success'
|
||||
});
|
||||
for (const item of this.list) {
|
||||
if (item.uid === this.tempForm.uid) {
|
||||
const index = this.list.indexOf(item);
|
||||
this.list[index] = objectMerge(this.list[index], this.tempForm);
|
||||
break
|
||||
}
|
||||
}
|
||||
this.dialogFormVisible = false;
|
||||
});
|
||||
},
|
||||
handleInfo(row) {
|
||||
this.dialogInfoVisible = true;
|
||||
objectMerge(this.infoForm, row);
|
||||
},
|
||||
submitInfo() {
|
||||
updateInfo(this.infoForm).then(() => {
|
||||
this.$notify({
|
||||
title: '成功',
|
||||
message: '修改成功',
|
||||
type: 'success'
|
||||
});
|
||||
for (const item of this.list) {
|
||||
if (item.uid === this.infoForm.uid) {
|
||||
const index = this.list.indexOf(item);
|
||||
this.list[index] = objectMerge(this.list[index], this.infoForm);
|
||||
break
|
||||
}
|
||||
}
|
||||
this.dialogInfoVisible = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
61
src/views/components/404.vue
Normal file
61
src/views/components/404.vue
Normal file
|
@ -0,0 +1,61 @@
|
|||
<template>
|
||||
<div class="errorpage-container"> 404
|
||||
<splitPane v-on:resize="resize" split="vertical">
|
||||
<template slot="paneL">
|
||||
<div class="left-container"></div>
|
||||
</template>
|
||||
<template slot="paneR">
|
||||
<splitPane split="horizontal">
|
||||
<template slot="paneL">
|
||||
<div class="top-container"></div>
|
||||
</template>
|
||||
<template slot="paneR">
|
||||
<div class="bottom-container">
|
||||
</div>
|
||||
</template>
|
||||
</splitPane>
|
||||
</template>
|
||||
</splitPane>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
<script>
|
||||
import splitPane from 'components/SplitPane/SplitPane'
|
||||
export default {
|
||||
components: { splitPane },
|
||||
methods: {
|
||||
resize() {
|
||||
console.log('resize')
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<style rel="stylesheet/scss" lang="scss" scoped>
|
||||
.errorpage-container{
|
||||
position: relative;
|
||||
height: 100vh;
|
||||
}
|
||||
.left-container {
|
||||
background-color: #F38181;
|
||||
height:100%;
|
||||
}
|
||||
|
||||
.right-container {
|
||||
background-color: #FCE38A;
|
||||
height: 200px;
|
||||
}
|
||||
|
||||
.top-container {
|
||||
background-color: #FCE38A;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.bottom-container {
|
||||
width: 100%;
|
||||
background-color: #95E1D3;
|
||||
height: 100%;
|
||||
|
||||
}
|
||||
</style>
|
22
src/views/components/markdown.vue
Normal file
22
src/views/components/markdown.vue
Normal file
|
@ -0,0 +1,22 @@
|
|||
<template>
|
||||
<div class="components-container">
|
||||
<code>公司做的后台主要是一个cms系统,公司也是已自媒体为核心的,所以富文本是后台很核心的功能。在选择富文本的过程中也走了不少的弯路,市面上常见的富文本都基本用过了,最终选择了tinymce</code>
|
||||
<div class="editor-container">
|
||||
<MdEditor id='contentEditor' ref="contentEditor" v-model='content' :height="150"></MdEditor>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import MdEditor from 'components/MdEditor';
|
||||
|
||||
export default {
|
||||
components: { MdEditor },
|
||||
data() {
|
||||
return {
|
||||
content: 'Simplemde'
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
|
28
src/views/components/tinymce.vue
Normal file
28
src/views/components/tinymce.vue
Normal file
|
@ -0,0 +1,28 @@
|
|||
<template>
|
||||
<div class="components-container">
|
||||
<code>公司做的后台主要是一个cms系统,公司也是已自媒体为核心的,所以富文本是后台很核心的功能。在选择富文本的过程中也走了不少的弯路,市面上常见的富文本都基本用过了,最终选择了tinymce</code>
|
||||
<div class="editor-container">
|
||||
<Tinymce :height=200 ref="editor" v-model="content"></Tinymce>
|
||||
</div>
|
||||
<!--<div class='editor-content'>
|
||||
{{content}}
|
||||
</div>-->
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import Tinymce from 'components/Tinymce';
|
||||
|
||||
export default {
|
||||
components: { Tinymce },
|
||||
data() {
|
||||
return {
|
||||
content: 'Tinymce'
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
|
75
src/views/dashboard/default/index.vue
Normal file
75
src/views/dashboard/default/index.vue
Normal file
|
@ -0,0 +1,75 @@
|
|||
<template>
|
||||
<div class="dashboard-editor-container">
|
||||
<div class=" clearfix">
|
||||
<PanThumb style="float: left" :image="avatar"> 你的权限:
|
||||
<span class="pan-info-roles" v-for="item in roles">{{item}}</span>
|
||||
</PanThumb>
|
||||
<div class="info-container">
|
||||
<span class="display_name">{{name}}</span>
|
||||
<span style='font-size:20px;padding-top:20px;display:inline-block;'>赶紧把你们想要的快捷键报给产品锦鲤!</span>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<img class='emptyGif' :src="emptyGif" >
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import PanThumb from 'components/PanThumb';
|
||||
import emptyGif from 'assets/gifs/business_fella.gif';
|
||||
export default {
|
||||
name: 'dashboard-default',
|
||||
components: { PanThumb },
|
||||
data() {
|
||||
return {
|
||||
emptyGif
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters([
|
||||
'name',
|
||||
'avatar',
|
||||
'email',
|
||||
'uid',
|
||||
'introduction',
|
||||
'roles'
|
||||
])
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style rel="stylesheet/scss" lang="scss" scoped>
|
||||
.emptyGif {
|
||||
display: block;
|
||||
width: 45%;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.dashboard-editor-container {
|
||||
background-color: #e3e3e3;
|
||||
min-height: 100vh;
|
||||
margin-top: -50px;
|
||||
padding: 100px 60px 0px;
|
||||
.pan-info-roles {
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
color: #333;
|
||||
display: block;
|
||||
}
|
||||
.info-container {
|
||||
position: relative;
|
||||
margin-left: 190px;
|
||||
height: 150px;
|
||||
line-height: 200px;
|
||||
.display_name {
|
||||
font-size: 48px;
|
||||
line-height: 48px;
|
||||
color: #212121;
|
||||
position: absolute;
|
||||
top: 25px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
34
src/views/dashboard/editor/articlesChart.vue
Normal file
34
src/views/dashboard/editor/articlesChart.vue
Normal file
|
@ -0,0 +1,34 @@
|
|||
<template>
|
||||
<div class="articlesChart-container">
|
||||
<span class="articlesChart-container-title">每天撸文</span>
|
||||
<line-chart :listData='listData' ></line-chart>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import LineChart from 'components/Charts/line';
|
||||
export default {
|
||||
name: 'articlesChart',
|
||||
components: { LineChart },
|
||||
props: {
|
||||
listData: {
|
||||
type: Array,
|
||||
default: [],
|
||||
require: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
.articlesChart-container {
|
||||
width: 100%;
|
||||
}
|
||||
.articlesChart-container-title {
|
||||
color: #7F8C8D;
|
||||
font-size: 16px;
|
||||
display: inline-block;
|
||||
margin-top: 10px;
|
||||
}
|
||||
</style>
|
284
src/views/dashboard/editor/index.vue
Normal file
284
src/views/dashboard/editor/index.vue
Normal file
|
@ -0,0 +1,284 @@
|
|||
<template>
|
||||
<div class="dashboard-editor-container">
|
||||
<div class=" clearfix">
|
||||
<PanThumb style="float: left" :image="avatar"> 你的权限:
|
||||
<span class="pan-info-roles" v-for="item in roles">{{item}}</span>
|
||||
</PanThumb>
|
||||
<div class="info-container">
|
||||
<span class="display_name">{{name}}</span>
|
||||
<div class="info-wrapper">
|
||||
<router-link class="info-item" :to="'/article/wscnlist?uid='+uid">
|
||||
<span class="info-item-num">{{statisticsData.article_count | toThousandslsFilter}}</span>
|
||||
<span class="info-item-text">文章</span>
|
||||
<wscn-icon-svg icon-class="a" class="dashboard-editor-icon"/>
|
||||
</router-link>
|
||||
<div class="info-item" style="cursor: auto">
|
||||
<span class="info-item-num"> {{statisticsData.pageviews_count | toThousandslsFilter}}</span>
|
||||
<span class="info-item-text">浏览量</span>
|
||||
<wscn-icon-svg icon-class="b" class="dashboard-editor-icon"/>
|
||||
</div>
|
||||
<router-link class="info-item" :to="'/comment/commentslist?res_author_id='+uid">
|
||||
<span class="info-item-num">{{statisticsData.comment_count | toThousandslsFilter}}</span>
|
||||
<span class="info-item-text">评论</span>
|
||||
<wscn-icon-svg icon-class="c" class="dashboard-editor-icon"/>
|
||||
</router-link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="btn-group">
|
||||
<router-link class="pan-btn blue-btn" to="/article/create">发表文章</router-link>
|
||||
<router-link class="pan-btn light-blue-btn" to="/livenews/create">发布快讯</router-link>
|
||||
<router-link class="pan-btn red-btn" to="/push/create">推送</router-link>
|
||||
<router-link class="pan-btn pink-btn" to="/comment/commentslist">评论管理</router-link>
|
||||
<router-link class="pan-btn green-btn" to="/article/wscnlist">文章列表</router-link>
|
||||
<router-link class="pan-btn tiffany-btn" to="/livenews/list">实时列表</router-link>
|
||||
</div>
|
||||
|
||||
<div class="clearfix main-dashboard-container">
|
||||
<div class="chart-container">
|
||||
<MonthKpi style="border-bottom: 1px solid #DEE1E2;"
|
||||
:articlesComplete='statisticsData.month_article_count'></MonthKpi>
|
||||
<ArticlesChart :listData='statisticsData.week_article'></ArticlesChart>
|
||||
</div>
|
||||
<div class="recent-articles-container">
|
||||
<div class="recent-articles-title">最近撸了</div>
|
||||
<div class="recent-articles-wrapper">
|
||||
<template v-if="recentArticles.length!=0">
|
||||
<div class="recent-articles-item" v-for="item in recentArticles">
|
||||
<span class="recent-articles-status">{{item.status | statusFilter}}</span>
|
||||
<router-link class="recent-articles-content" :to="'/article/edit/'+item.id">
|
||||
<span>{{item.title}}</span>
|
||||
</router-link>
|
||||
<span class="recent-articles-time"><i style="padding-right: 4px;" class="el-icon-time"></i>{{item.display_time | parseTime('{m}-{d} {h}:{i}')}}</span>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div class="recent-articles-emptyTitle">你太懒了最近都没有撸</div>
|
||||
<!--<img class="emptyGif" :src="emptyGif">-->
|
||||
</template>
|
||||
</div>
|
||||
<router-link class="recent-articles-more" :to="'/article/wscnlist?uid='+uid">
|
||||
Show more
|
||||
</router-link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import PanThumb from 'components/PanThumb';
|
||||
import MonthKpi from './monthKpi';
|
||||
import ArticlesChart from './articlesChart';
|
||||
// import { getStatistics } from 'api/article';
|
||||
|
||||
import emptyGif from 'assets/compbig.gif';
|
||||
export default {
|
||||
name: 'dashboard-editor',
|
||||
components: { PanThumb, MonthKpi, ArticlesChart },
|
||||
data() {
|
||||
return {
|
||||
chart: null,
|
||||
statisticsData: {
|
||||
article_count: undefined,
|
||||
comment_count: undefined,
|
||||
latest_article: [],
|
||||
month_article_count: undefined,
|
||||
pageviews_count: undefined,
|
||||
week_article: []
|
||||
},
|
||||
emptyGif
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.fetchData();
|
||||
},
|
||||
computed: {
|
||||
...mapGetters([
|
||||
'name',
|
||||
'avatar',
|
||||
'email',
|
||||
'uid',
|
||||
'introduction',
|
||||
'roles'
|
||||
]),
|
||||
recentArticles() {
|
||||
return this.statisticsData.latest_article.slice(0, 7)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
fetchData() {
|
||||
// getStatistics().then(response => {
|
||||
// this.statisticsData = response.data;
|
||||
// this.statisticsData.week_article = this.statisticsData.week_article.slice().reverse();
|
||||
// })
|
||||
}
|
||||
},
|
||||
filters: {
|
||||
statusFilter(status) {
|
||||
const statusMap = {
|
||||
published: '已发布',
|
||||
draft: '草稿中',
|
||||
deleted: '已删除'
|
||||
};
|
||||
return statusMap[status];
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style rel="stylesheet/scss" lang="scss" scoped>
|
||||
.emptyGif {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.recent-articles-emptyTitle {
|
||||
font-size: 16px;
|
||||
color: #95A5A6;
|
||||
padding-top: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.dashboard-editor-container {
|
||||
padding: 30px 50px;
|
||||
.pan-info-roles {
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
color: #333;
|
||||
display: block;
|
||||
}
|
||||
.info-container {
|
||||
position: relative;
|
||||
margin-left: 190px;
|
||||
height: 150px;
|
||||
line-height: 200px;
|
||||
.display_name {
|
||||
font-size: 48px;
|
||||
line-height: 48px;
|
||||
color: #212121;
|
||||
position: absolute;
|
||||
top: 25px;
|
||||
}
|
||||
.info-wrapper {
|
||||
line-height: 40px;
|
||||
position: absolute;
|
||||
bottom: 0px;
|
||||
.info-item {
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
margin-right: 95px;
|
||||
.info-item-num {
|
||||
color: #212121;
|
||||
font-size: 24px;
|
||||
display: inline-block;
|
||||
padding-right: 5px;
|
||||
}
|
||||
.info-item-text {
|
||||
color: #727272;
|
||||
font-size: 14px;
|
||||
padding-right: 5px;
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
}
|
||||
.dashboard-editor-icon {
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
}
|
||||
}
|
||||
|
||||
.btn-group {
|
||||
margin: 30px 36px 30px 0;
|
||||
}
|
||||
.main-dashboard-container {
|
||||
width: 100%;
|
||||
position: relative;
|
||||
border: 1px solid #DEE1E2;
|
||||
padding: 0 10px;
|
||||
}
|
||||
.chart-container {
|
||||
float: left;
|
||||
position: relative;
|
||||
padding-right: 10px;
|
||||
width: 40%;
|
||||
border-right: 1px solid #DEE1E2;
|
||||
}
|
||||
.recent-articles-container {
|
||||
padding: 12px 12px 0px;
|
||||
float: left;
|
||||
width: 60%;
|
||||
position: relative;
|
||||
.recent-articles- {
|
||||
&title {
|
||||
font-size: 16px;
|
||||
color: #95A5A6;
|
||||
letter-spacing: 1px;
|
||||
padding-left: 15px;
|
||||
padding-bottom: 10px;
|
||||
border-bottom: 1px solid #DEE1E2;
|
||||
}
|
||||
&more {
|
||||
color: #2C3E50;
|
||||
font-size: 12px;
|
||||
float: right;
|
||||
margin-right: 25px;
|
||||
line-height: 40px;
|
||||
&:hover {
|
||||
color: #3A71A8;
|
||||
}
|
||||
}
|
||||
&wrapper {
|
||||
padding: 0 20px 0 22px;
|
||||
.recent-articles- {
|
||||
&item {
|
||||
cursor: pointer;
|
||||
padding: 16px 100px 16px 16px;
|
||||
border-bottom: 1px solid #DEE1E2;
|
||||
position: relative;
|
||||
&:before {
|
||||
content: "";
|
||||
height: 104%;
|
||||
width: 0px;
|
||||
background: #30B08F;
|
||||
display: inline-block;
|
||||
position: absolute;
|
||||
opacity: 0;
|
||||
left: 0px;
|
||||
top: -2px;
|
||||
transition: 0.3s ease all;
|
||||
}
|
||||
&:hover {
|
||||
&:before {
|
||||
opacity: 1;
|
||||
width: 3px;
|
||||
}
|
||||
}
|
||||
}
|
||||
&status {
|
||||
font-size: 12px;
|
||||
display: inline-block;
|
||||
color: #9B9B9B;
|
||||
padding-right: 6px;
|
||||
}
|
||||
&content {
|
||||
font-size: 13px;
|
||||
color: #2C3E50;
|
||||
&:hover {
|
||||
color: #3A71A8;
|
||||
}
|
||||
}
|
||||
&time {
|
||||
position: absolute;
|
||||
right: 16px;
|
||||
top: 16px;
|
||||
color: #9B9B9B;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
</style>
|
61
src/views/dashboard/editor/monthKpi.vue
Normal file
61
src/views/dashboard/editor/monthKpi.vue
Normal file
|
@ -0,0 +1,61 @@
|
|||
<template>
|
||||
<div class="monthKpi-container">
|
||||
<span class="monthKpi-container-title">{{month}}月</span>
|
||||
<BarPercent class="monthKpi-container-chart" :dataNum='articlesComplete'></BarPercent>
|
||||
<span class="monthKpi-container-text">文章完成比例</span>
|
||||
<span class="monthKpi-container-count">{{articlesComplete}}<b>篇</b></span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import BarPercent from 'components/Charts/barPercent';
|
||||
export default {
|
||||
name: 'monthKpi',
|
||||
components: { BarPercent },
|
||||
props: {
|
||||
articlesComplete: {
|
||||
type: Number
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
month: new Date().getMonth() + 1
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style rel="stylesheet/scss" lang="scss" scoped>
|
||||
.monthKpi-container{
|
||||
width: 100%;
|
||||
}
|
||||
.monthKpi-container-title {
|
||||
color: #7F8C8D;
|
||||
font-size: 16px;
|
||||
display: inline-block;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.monthKpi-container-chart {
|
||||
margin-left: 100px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.monthKpi-container-text {
|
||||
margin-left: 112px;
|
||||
color: #9EA7B3;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.monthKpi-container-count {
|
||||
color: #30B08F;
|
||||
font-size: 34px;
|
||||
position: absolute;
|
||||
left: 260px;
|
||||
top: 60px;
|
||||
b {
|
||||
padding-left: 10px;
|
||||
color: #9EA7B3;
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
</style>
|
38
src/views/dashboard/index.vue
Normal file
38
src/views/dashboard/index.vue
Normal file
|
@ -0,0 +1,38 @@
|
|||
<template>
|
||||
<div class="dashboard-container">
|
||||
<component v-bind:is="currentRole"> </component>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import EditorDashboard from './editor/index';
|
||||
import DefaultDashboard from './default/index';
|
||||
export default {
|
||||
name: 'dashboard',
|
||||
components: { EditorDashboard, DefaultDashboard },
|
||||
data() {
|
||||
return {
|
||||
currentRole: 'EditorDashboard'
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters([
|
||||
'name',
|
||||
'avatar',
|
||||
'email',
|
||||
'introduction',
|
||||
'roles'
|
||||
])
|
||||
},
|
||||
created() {
|
||||
if (this.roles.indexOf('admin') >= 0) {
|
||||
return;
|
||||
}
|
||||
const isEditor = this.roles.some(v => v.indexOf('editor') >= 0)
|
||||
if (!isEditor) {
|
||||
this.currentRole = 'DefaultDashboard';
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
82
src/views/error/401.vue
Normal file
82
src/views/error/401.vue
Normal file
|
@ -0,0 +1,82 @@
|
|||
<template>
|
||||
<div class="errPage-container">
|
||||
<el-button @click="back" icon='arrow-left' class="pan-back-btn">返回</el-button>
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
<h1 class="text-jumbo text-ginormous">Oops!</h1>
|
||||
<h2>你没有权限去该页面</h2>
|
||||
<h6>如有不满请联系你领导</h6>
|
||||
<ul class="list-unstyled">
|
||||
<li>或者你可以去:</li>
|
||||
<li class="link-type">
|
||||
<router-link to="/dashboard">回首页</router-link>
|
||||
</li>
|
||||
<li class="link-type"><a href="https://www.taobao.com/">随便看看</a></li>
|
||||
<li><a @click="dialogVisible=true" href="#">点我看图</a></li>
|
||||
</ul>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<img :src="errGif" width="313" height="428" alt="Girl has dropped her ice cream.">
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-dialog title="随便看" v-model="dialogVisible" size="large">
|
||||
<img class="pan-img" :src="ewizardClap">
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import errGif from 'assets/401.gif';
|
||||
import ewizardClap from 'assets/gifs/wizard_clap.gif';
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
errGif: errGif + '?' + +new Date(),
|
||||
ewizardClap,
|
||||
dialogVisible: false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
back() {
|
||||
this.$router.go(-1)
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style rel="stylesheet/scss" lang="scss" scoped>
|
||||
.errPage-container {
|
||||
width: 800px;
|
||||
margin: 100px auto;
|
||||
.pan-back-btn {
|
||||
background: #008489;
|
||||
color: #fff;
|
||||
}
|
||||
.pan-gif {
|
||||
margin: 0 auto;
|
||||
display: block;
|
||||
}
|
||||
.pan-img{
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.text-jumbo {
|
||||
font-size: 60px;
|
||||
font-weight: 700;
|
||||
color: #484848;
|
||||
}
|
||||
.list-unstyled {
|
||||
font-size: 14px;
|
||||
li {
|
||||
padding-bottom: 5px;
|
||||
}
|
||||
a {
|
||||
color: #008489;
|
||||
text-decoration: none;
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
61
src/views/error/404.vue
Normal file
61
src/views/error/404.vue
Normal file
|
@ -0,0 +1,61 @@
|
|||
<template>
|
||||
<div class="errorpage-container"> 404
|
||||
<splitPane v-on:resize="resize" split="vertical">
|
||||
<template slot="paneL">
|
||||
<div class="left-container"></div>
|
||||
</template>
|
||||
<template slot="paneR">
|
||||
<splitPane split="horizontal">
|
||||
<template slot="paneL">
|
||||
<div class="top-container"></div>
|
||||
</template>
|
||||
<template slot="paneR">
|
||||
<div class="bottom-container">
|
||||
</div>
|
||||
</template>
|
||||
</splitPane>
|
||||
</template>
|
||||
</splitPane>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
<script>
|
||||
import splitPane from 'components/SplitPane/SplitPane'
|
||||
export default {
|
||||
components: { splitPane },
|
||||
methods: {
|
||||
resize() {
|
||||
console.log('resize')
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<style rel="stylesheet/scss" lang="scss" scoped>
|
||||
.errorpage-container{
|
||||
position: relative;
|
||||
height: 100vh;
|
||||
}
|
||||
.left-container {
|
||||
background-color: #F38181;
|
||||
height:100%;
|
||||
}
|
||||
|
||||
.right-container {
|
||||
background-color: #FCE38A;
|
||||
height: 200px;
|
||||
}
|
||||
|
||||
.top-container {
|
||||
background-color: #FCE38A;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.bottom-container {
|
||||
width: 100%;
|
||||
background-color: #95E1D3;
|
||||
height: 100%;
|
||||
|
||||
}
|
||||
</style>
|
20
src/views/layout/AppMain.vue
Normal file
20
src/views/layout/AppMain.vue
Normal file
|
@ -0,0 +1,20 @@
|
|||
<template>
|
||||
<section class="app-main" style="min-height: 100%">
|
||||
<transition name="fade" mode="out-in">
|
||||
<router-view :key="key"></router-view>
|
||||
</transition>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'AppMain',
|
||||
computed: {
|
||||
key() {
|
||||
return this.$route.name !== undefined
|
||||
? this.$route.name + +new Date()
|
||||
: this.$route + +new Date()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
98
src/views/layout/Layout.vue
Normal file
98
src/views/layout/Layout.vue
Normal file
|
@ -0,0 +1,98 @@
|
|||
<template>
|
||||
<div class="app-wrapper" :class="{hideSidebar:!sidebar.opened}">
|
||||
<div class="sidebar-wrapper">
|
||||
<Sidebar class="sidebar-container"/>
|
||||
</div>
|
||||
<div class="main-container">
|
||||
<Navbar/>
|
||||
<App-main/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { Navbar, Sidebar, AppMain } from 'views/layout';
|
||||
import store from 'store';
|
||||
import router from 'router';
|
||||
import permission from 'store/permission';
|
||||
// import { Loading } from 'element-ui';
|
||||
// let loadingInstance;
|
||||
export default {
|
||||
name: 'layout',
|
||||
components: {
|
||||
Navbar,
|
||||
Sidebar,
|
||||
AppMain
|
||||
},
|
||||
computed: {
|
||||
sidebar() {
|
||||
return this.$store.state.app.sidebar;
|
||||
}
|
||||
},
|
||||
beforeRouteEnter: (to, from, next) => {
|
||||
console.log('b')
|
||||
const roles = store.getters.roles;
|
||||
if (roles.length !== 0) {
|
||||
next();
|
||||
return
|
||||
}
|
||||
// loadingInstance = Loading.service({ fullscreen: true, text: '玩命加载中' });
|
||||
store.dispatch('GetInfo').then(() => {
|
||||
permission.init({
|
||||
roles: store.getters.roles,
|
||||
router: router.options.routes
|
||||
});
|
||||
// loadingInstance.close();
|
||||
next();
|
||||
}).catch(err => {
|
||||
// loadingInstance.close();
|
||||
console.log(err);
|
||||
});
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style rel="stylesheet/scss" lang="scss" scoped>
|
||||
@import "src/styles/mixin.scss";
|
||||
|
||||
.app-wrapper {
|
||||
@include clearfix;
|
||||
position: relative;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
padding-left: 180px;
|
||||
&.hideSidebar {
|
||||
padding-left: 40px;
|
||||
.sidebar-wrapper {
|
||||
transform: translate(-140px, 0);
|
||||
.sidebar-container {
|
||||
transform: translate(132px, 0);
|
||||
}
|
||||
&:hover {
|
||||
transform: translate(0, 0);
|
||||
.sidebar-container {
|
||||
transform: translate(0, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.sidebar-wrapper {
|
||||
width: 180px;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
z-index: 1001;
|
||||
overflow-x: hidden;
|
||||
transition: all .28s ease-out;
|
||||
@include scrollBar;
|
||||
}
|
||||
.sidebar-container {
|
||||
transition: all .28s ease-out;
|
||||
}
|
||||
.main-container {
|
||||
width: 100%;
|
||||
min-height: 100%;
|
||||
transition: all .28s ease-out;
|
||||
}
|
||||
}
|
||||
</style>
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue