Merge branch 'master' of git.komect.net:ISG/secogateway
This commit is contained in:
commit
f056710c6b
|
@ -21,3 +21,4 @@ Product/build/debug/
|
|||
.build/
|
||||
_install/
|
||||
.idea/
|
||||
/ControlPlatform/web/node_modules
|
||||
|
|
|
@ -84,6 +84,7 @@ enum commcfgmsgtype{
|
|||
//COMMMSGNL_BASE = NLMSG_MIN_TYPE,/*netlink 保留控制消息*/
|
||||
COMMMSGNL_BASE = 0x10,/*netlink 保留控制消息*/
|
||||
COMMNMSG_CFG_DEBUGFS = 0x11,/*keep the same with NLMSG_PDELIV_DEBUGFS */
|
||||
FREEAUTH_CFG = 0x13, /*用户态发送给内核态的免认证规则消息*/
|
||||
COMMNMSG_POLICYCONF,
|
||||
|
||||
NK_DEBUGFS_PRK_ONOFF_CFG = 0X16,/*keep the same with DEBUGFS PRINTK ON OR OFF */
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
/* This file is auto generated,for sGATE version info */
|
||||
/* Used readelf to get this information form driver of application */
|
||||
/* "readelf --debug-dump=macro <filename>" */
|
||||
#define sGATE_COMPILE_DATE "2019-06-19"
|
||||
#define sGATE_COMPILE_TIME "14:18:13"
|
||||
#define sGATE_COMPILE_MAJOR "20190619"
|
||||
#define sGATE_COMPILE_SUB "141813"
|
||||
#define sGATE_COMPILE_BY "hx"
|
||||
#define sGATE_COMPILE_DATE "2019-07-01"
|
||||
#define sGATE_COMPILE_TIME "17:53:10"
|
||||
#define sGATE_COMPILE_MAJOR "20190701"
|
||||
#define sGATE_COMPILE_SUB "175310"
|
||||
#define sGATE_COMPILE_BY "cl"
|
||||
#define sGATE_COMPILE_HOST "esgwdev01"
|
||||
#define sGATE_GIT_TAGS "c0ad51e6f-dev"
|
||||
#define sGATE_GIT_VERS "c0ad51e6f27589e51268ec92a14ee1cb701a2d5f"
|
||||
#define sGATE_GIT_TAGS "aaa812c65-dev"
|
||||
#define sGATE_GIT_VERS "aaa812c654225f595f12a32bc7d56bcc225f3ee4"
|
||||
|
|
|
@ -81,8 +81,30 @@ extern "C" {
|
|||
S2J_STRUCT_GET_STRUCT_ELEMENT(child_struct, to_struct, child_json, from_json, type, element)
|
||||
|
||||
/* s2j.c */
|
||||
extern S2jHook s2jHook;
|
||||
void s2j_init(S2jHook *hook);
|
||||
//extern S2jHook s2jHook;
|
||||
S2jHook s2jHook = {
|
||||
.malloc_fn = malloc,
|
||||
.free_fn = free,
|
||||
};
|
||||
|
||||
static void s2j_init(S2jHook *hook)
|
||||
{
|
||||
/* initialize cJSON library */
|
||||
if(hook == NULL)
|
||||
{
|
||||
hook = &s2jHook;
|
||||
}
|
||||
|
||||
cJSON_InitHooks((cJSON_Hooks *)hook);
|
||||
/* initialize hooks */
|
||||
if (hook) {
|
||||
s2jHook.malloc_fn = (hook->malloc_fn) ? hook->malloc_fn : malloc;
|
||||
s2jHook.free_fn = (hook->free_fn) ? hook->free_fn : free;
|
||||
} else {
|
||||
s2jHook.malloc_fn = malloc;
|
||||
s2jHook.free_fn = free;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"presets": [
|
||||
["env", {
|
||||
"modules": false,
|
||||
"targets": {
|
||||
"browsers": ["> 1%", "last 2 versions", "not ie <= 8"]
|
||||
}
|
||||
}],
|
||||
"stage-2"
|
||||
],
|
||||
"plugins": ["transform-vue-jsx", "transform-runtime"],
|
||||
"env": {
|
||||
"test": {
|
||||
"presets": ["env", "stage-2"],
|
||||
"plugins": ["transform-vue-jsx", "transform-es2015-modules-commonjs", "dynamic-import-node"]
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
|
@ -0,0 +1,5 @@
|
|||
/build/
|
||||
/config/
|
||||
/dist/
|
||||
/*.js
|
||||
/test/unit/coverage/
|
|
@ -0,0 +1,29 @@
|
|||
// https://eslint.org/docs/user-guide/configuring
|
||||
|
||||
module.exports = {
|
||||
root: true,
|
||||
parserOptions: {
|
||||
parser: 'babel-eslint'
|
||||
},
|
||||
env: {
|
||||
browser: true,
|
||||
},
|
||||
extends: [
|
||||
// https://github.com/vuejs/eslint-plugin-vue#priority-a-essential-error-prevention
|
||||
// consider switching to `plugin:vue/strongly-recommended` or `plugin:vue/recommended` for stricter rules.
|
||||
'plugin:vue/essential',
|
||||
// https://github.com/standard/standard/blob/master/docs/RULES-en.md
|
||||
'standard'
|
||||
],
|
||||
// required to lint *.vue files
|
||||
plugins: [
|
||||
'vue'
|
||||
],
|
||||
// add your custom rules here
|
||||
rules: {
|
||||
// allow async-await
|
||||
'generator-star-spacing': 'off',
|
||||
// allow debugger during development
|
||||
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off'
|
||||
}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
*.js linguist-language=vue
|
||||
|
||||
*.css linguist-language=vue
|
||||
|
||||
*.html linguist-language=vue
|
|
@ -0,0 +1,10 @@
|
|||
// https://github.com/michael-ciniawsky/postcss-load-config
|
||||
|
||||
module.exports = {
|
||||
"plugins": {
|
||||
"postcss-import": {},
|
||||
"postcss-url": {},
|
||||
// to edit target browsers: use "browserslist" field in package.json
|
||||
"autoprefixer": {}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
'use strict'
|
||||
require('./check-versions')()
|
||||
|
||||
process.env.NODE_ENV = 'production'
|
||||
|
||||
const ora = require('ora')
|
||||
const rm = require('rimraf')
|
||||
const path = require('path')
|
||||
const chalk = require('chalk')
|
||||
const webpack = require('webpack')
|
||||
const config = require('../config')
|
||||
const webpackConfig = require('./webpack.prod.conf')
|
||||
|
||||
const spinner = ora('building for production...')
|
||||
spinner.start()
|
||||
|
||||
rm(path.join(config.build.assetsRoot, config.build.assetsSubDirectory), err => {
|
||||
if (err) throw err
|
||||
webpack(webpackConfig, (err, stats) => {
|
||||
spinner.stop()
|
||||
if (err) throw err
|
||||
process.stdout.write(stats.toString({
|
||||
colors: true,
|
||||
modules: false,
|
||||
children: false, // If you are using ts-loader, setting this to true will make TypeScript errors show up during build.
|
||||
chunks: false,
|
||||
chunkModules: false
|
||||
}) + '\n\n')
|
||||
|
||||
if (stats.hasErrors()) {
|
||||
console.log(chalk.red(' Build failed with errors.\n'))
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
console.log(chalk.cyan(' Build complete.\n'))
|
||||
console.log(chalk.yellow(
|
||||
' Tip: built files are meant to be served over an HTTP server.\n' +
|
||||
' Opening index.html over file:// won\'t work.\n'
|
||||
))
|
||||
})
|
||||
})
|
|
@ -0,0 +1,54 @@
|
|||
'use strict'
|
||||
const chalk = require('chalk')
|
||||
const semver = require('semver')
|
||||
const packageConfig = require('../package.json')
|
||||
const shell = require('shelljs')
|
||||
|
||||
function exec (cmd) {
|
||||
return require('child_process').execSync(cmd).toString().trim()
|
||||
}
|
||||
|
||||
const versionRequirements = [
|
||||
{
|
||||
name: 'node',
|
||||
currentVersion: semver.clean(process.version),
|
||||
versionRequirement: packageConfig.engines.node
|
||||
}
|
||||
]
|
||||
|
||||
if (shell.which('npm')) {
|
||||
versionRequirements.push({
|
||||
name: 'npm',
|
||||
currentVersion: exec('npm --version'),
|
||||
versionRequirement: packageConfig.engines.npm
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = function () {
|
||||
const warnings = []
|
||||
|
||||
for (let i = 0; i < versionRequirements.length; i++) {
|
||||
const 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 (let i = 0; i < warnings.length; i++) {
|
||||
const warning = warnings[i]
|
||||
console.log(' ' + warning)
|
||||
}
|
||||
|
||||
console.log()
|
||||
process.exit(1)
|
||||
}
|
||||
}
|
Binary file not shown.
After Width: | Height: | Size: 6.7 KiB |
|
@ -0,0 +1,101 @@
|
|||
'use strict'
|
||||
const path = require('path')
|
||||
const config = require('../config')
|
||||
const ExtractTextPlugin = require('extract-text-webpack-plugin')
|
||||
const packageConfig = require('../package.json')
|
||||
|
||||
exports.assetsPath = function (_path) {
|
||||
const assetsSubDirectory = process.env.NODE_ENV === 'production'
|
||||
? config.build.assetsSubDirectory
|
||||
: config.dev.assetsSubDirectory
|
||||
|
||||
return path.posix.join(assetsSubDirectory, _path)
|
||||
}
|
||||
|
||||
exports.cssLoaders = function (options) {
|
||||
options = options || {}
|
||||
|
||||
const cssLoader = {
|
||||
loader: 'css-loader',
|
||||
options: {
|
||||
sourceMap: options.sourceMap
|
||||
}
|
||||
}
|
||||
|
||||
const postcssLoader = {
|
||||
loader: 'postcss-loader',
|
||||
options: {
|
||||
sourceMap: options.sourceMap
|
||||
}
|
||||
}
|
||||
|
||||
// generate loader string to be used with extract text plugin
|
||||
function generateLoaders (loader, loaderOptions) {
|
||||
const loaders = options.usePostCSS ? [cssLoader, postcssLoader] : [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)
|
||||
}
|
||||
}
|
||||
|
||||
// https://vue-loader.vuejs.org/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) {
|
||||
const output = []
|
||||
const loaders = exports.cssLoaders(options)
|
||||
|
||||
for (const extension in loaders) {
|
||||
const loader = loaders[extension]
|
||||
output.push({
|
||||
test: new RegExp('\\.' + extension + '$'),
|
||||
use: loader
|
||||
})
|
||||
}
|
||||
|
||||
return output
|
||||
}
|
||||
|
||||
exports.createNotifierCallback = () => {
|
||||
const notifier = require('node-notifier')
|
||||
|
||||
return (severity, errors) => {
|
||||
if (severity !== 'error') return
|
||||
|
||||
const error = errors[0]
|
||||
const filename = error.file && error.file.split('!').pop()
|
||||
|
||||
notifier.notify({
|
||||
title: packageConfig.name,
|
||||
message: severity + ': ' + error.name,
|
||||
subtitle: filename || '',
|
||||
icon: path.join(__dirname, 'logo.png')
|
||||
})
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
'use strict'
|
||||
const utils = require('./utils')
|
||||
const config = require('../config')
|
||||
const isProduction = process.env.NODE_ENV === 'production'
|
||||
const sourceMapEnabled = isProduction
|
||||
? config.build.productionSourceMap
|
||||
: config.dev.cssSourceMap
|
||||
|
||||
module.exports = {
|
||||
loaders: utils.cssLoaders({
|
||||
sourceMap: sourceMapEnabled,
|
||||
extract: isProduction
|
||||
}),
|
||||
cssSourceMap: sourceMapEnabled,
|
||||
cacheBusting: config.dev.cacheBusting,
|
||||
transformToRequire: {
|
||||
video: ['src', 'poster'],
|
||||
source: 'src',
|
||||
img: 'src',
|
||||
image: 'xlink:href'
|
||||
}
|
||||
}
|
|
@ -0,0 +1,110 @@
|
|||
'use strict'
|
||||
const path = require('path')
|
||||
const utils = require('./utils')
|
||||
const config = require('../config')
|
||||
const vueLoaderConfig = require('./vue-loader.conf')
|
||||
const webpack = require('webpack');
|
||||
|
||||
function resolve (dir) {
|
||||
return path.join(__dirname, '..', dir)
|
||||
}
|
||||
|
||||
const createLintingRule = () => ({
|
||||
test: /\.(js|vue)$/,
|
||||
loader: 'eslint-loader',
|
||||
enforce: 'pre',
|
||||
include: [resolve('src'), resolve('test')],
|
||||
options: {
|
||||
formatter: require('eslint-friendly-formatter'),
|
||||
emitWarning: !config.dev.showEslintErrorsInOverlay
|
||||
}
|
||||
})
|
||||
|
||||
module.exports = {
|
||||
context: path.resolve(__dirname, '../'),
|
||||
entry: {
|
||||
app: ['babel-polyfill', './src/main.js']
|
||||
},
|
||||
output: {
|
||||
path: config.build.assetsRoot,
|
||||
filename: '[name].js',
|
||||
publicPath: process.env.NODE_ENV === 'production'
|
||||
? config.build.assetsPublicPath
|
||||
: config.dev.assetsPublicPath
|
||||
},
|
||||
resolve: {
|
||||
extensions: ['.js', '.vue', '.json'],
|
||||
alias: {
|
||||
'vue$': 'vue/dist/vue.esm.js',
|
||||
'@': resolve('src'),
|
||||
'~': resolve('src/components'),
|
||||
'utils': resolve('src/utils')
|
||||
|
||||
}
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
...(config.dev.useEslint ? [createLintingRule()] : []),
|
||||
{
|
||||
test: /\.vue$/,
|
||||
loader: 'vue-loader',
|
||||
options: vueLoaderConfig
|
||||
},
|
||||
{
|
||||
test: /\.js$/,
|
||||
loader: 'babel-loader',
|
||||
include: [resolve('src'), resolve('test'), resolve('node_modules/webpack-dev-server/client')]
|
||||
},
|
||||
{
|
||||
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
|
||||
loader: 'url-loader',
|
||||
options: {
|
||||
limit: 10000,
|
||||
name: utils.assetsPath('img/[name].[hash:7].[ext]')
|
||||
}
|
||||
},
|
||||
{
|
||||
test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
|
||||
loader: 'url-loader',
|
||||
options: {
|
||||
limit: 10000,
|
||||
name: utils.assetsPath('media/[name].[hash:7].[ext]')
|
||||
}
|
||||
},
|
||||
{
|
||||
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
|
||||
loader: 'url-loader',
|
||||
options: {
|
||||
limit: 10000,
|
||||
name: utils.assetsPath('fonts/[name].[hash:7].[ext]')
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
node: {
|
||||
// prevent webpack from injecting useless setImmediate polyfill because Vue
|
||||
// source contains it (although only uses it if it's native).
|
||||
setImmediate: false,
|
||||
// prevent webpack from injecting mocks to Node native modules
|
||||
// that does not make sense for the client
|
||||
dgram: 'empty',
|
||||
fs: 'empty',
|
||||
net: 'empty',
|
||||
tls: 'empty',
|
||||
child_process: 'empty'
|
||||
},
|
||||
plugins: [
|
||||
// Ignore all locale files of moment.js
|
||||
new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/),
|
||||
new webpack.optimize.UglifyJsPlugin({
|
||||
beautify: false,
|
||||
comments: false,
|
||||
compress: {
|
||||
warnings: false,
|
||||
drop_console: true,
|
||||
collapse_vars: true,
|
||||
reduce_vars: true,
|
||||
}
|
||||
}),
|
||||
]
|
||||
}
|
|
@ -0,0 +1,95 @@
|
|||
'use strict'
|
||||
const utils = require('./utils')
|
||||
const webpack = require('webpack')
|
||||
const config = require('../config')
|
||||
const merge = require('webpack-merge')
|
||||
const path = require('path')
|
||||
const baseWebpackConfig = require('./webpack.base.conf')
|
||||
const CopyWebpackPlugin = require('copy-webpack-plugin')
|
||||
const HtmlWebpackPlugin = require('html-webpack-plugin')
|
||||
const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin')
|
||||
const portfinder = require('portfinder')
|
||||
|
||||
const HOST = process.env.HOST
|
||||
const PORT = process.env.PORT && Number(process.env.PORT)
|
||||
|
||||
const devWebpackConfig = merge(baseWebpackConfig, {
|
||||
module: {
|
||||
rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap, usePostCSS: true })
|
||||
},
|
||||
// cheap-module-eval-source-map is faster for development
|
||||
devtool: config.dev.devtool,
|
||||
|
||||
// these devServer options should be customized in /config/index.js
|
||||
devServer: {
|
||||
clientLogLevel: 'warning',
|
||||
historyApiFallback: {
|
||||
rewrites: [
|
||||
{ from: /.*/, to: path.posix.join(config.dev.assetsPublicPath, 'index.html') },
|
||||
],
|
||||
},
|
||||
hot: true,
|
||||
contentBase: false, // since we use CopyWebpackPlugin.
|
||||
compress: true,
|
||||
host: HOST || config.dev.host,
|
||||
port: PORT || config.dev.port,
|
||||
open: config.dev.autoOpenBrowser,
|
||||
overlay: config.dev.errorOverlay
|
||||
? { warnings: false, errors: true }
|
||||
: false,
|
||||
publicPath: config.dev.assetsPublicPath,
|
||||
proxy: config.dev.proxyTable,
|
||||
quiet: true, // necessary for FriendlyErrorsPlugin
|
||||
watchOptions: {
|
||||
poll: config.dev.poll,
|
||||
}
|
||||
},
|
||||
plugins: [
|
||||
new webpack.DefinePlugin({
|
||||
'process.env': require('../config/dev.env')
|
||||
}),
|
||||
new webpack.HotModuleReplacementPlugin(),
|
||||
new webpack.NamedModulesPlugin(), // HMR shows correct file names in console on update.
|
||||
new webpack.NoEmitOnErrorsPlugin(),
|
||||
// https://github.com/ampedandwired/html-webpack-plugin
|
||||
new HtmlWebpackPlugin({
|
||||
filename: 'index.html',
|
||||
template: 'index.html',
|
||||
inject: true
|
||||
}),
|
||||
// copy custom static assets
|
||||
new CopyWebpackPlugin([
|
||||
{
|
||||
from: path.resolve(__dirname, '../static'),
|
||||
to: config.dev.assetsSubDirectory,
|
||||
ignore: ['.*']
|
||||
}
|
||||
])
|
||||
]
|
||||
})
|
||||
|
||||
module.exports = new Promise((resolve, reject) => {
|
||||
portfinder.basePort = process.env.PORT || config.dev.port
|
||||
portfinder.getPort((err, port) => {
|
||||
if (err) {
|
||||
reject(err)
|
||||
} else {
|
||||
// publish the new Port, necessary for e2e tests
|
||||
process.env.PORT = port
|
||||
// add port to devServer config
|
||||
devWebpackConfig.devServer.port = port
|
||||
|
||||
// Add FriendlyErrorsPlugin
|
||||
devWebpackConfig.plugins.push(new FriendlyErrorsPlugin({
|
||||
compilationSuccessInfo: {
|
||||
messages: [`Your application is running here: http://${devWebpackConfig.devServer.host}:${port}`],
|
||||
},
|
||||
onErrors: config.dev.notifyOnErrors
|
||||
? utils.createNotifierCallback()
|
||||
: undefined
|
||||
}))
|
||||
|
||||
resolve(devWebpackConfig)
|
||||
}
|
||||
})
|
||||
})
|
|
@ -0,0 +1,147 @@
|
|||
'use strict'
|
||||
const path = require('path')
|
||||
const utils = require('./utils')
|
||||
const webpack = require('webpack')
|
||||
const config = require('../config')
|
||||
const merge = require('webpack-merge')
|
||||
const baseWebpackConfig = require('./webpack.base.conf')
|
||||
const CopyWebpackPlugin = require('copy-webpack-plugin')
|
||||
const HtmlWebpackPlugin = require('html-webpack-plugin')
|
||||
const ExtractTextPlugin = require('extract-text-webpack-plugin')
|
||||
const OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin')
|
||||
const UglifyJsPlugin = require('uglifyjs-webpack-plugin')
|
||||
|
||||
const env = process.env.NODE_ENV === 'testing'
|
||||
? require('../config/test.env')
|
||||
: require('../config/prod.env')
|
||||
|
||||
const webpackConfig = merge(baseWebpackConfig, {
|
||||
module: {
|
||||
rules: utils.styleLoaders({
|
||||
sourceMap: config.build.productionSourceMap,
|
||||
extract: true,
|
||||
usePostCSS: true
|
||||
})
|
||||
},
|
||||
devtool: config.build.productionSourceMap ? config.build.devtool : 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 UglifyJsPlugin({
|
||||
uglifyOptions: {
|
||||
compress: {
|
||||
warnings: false
|
||||
}
|
||||
},
|
||||
sourceMap: config.build.productionSourceMap,
|
||||
parallel: true
|
||||
}),
|
||||
// extract css into its own file
|
||||
new ExtractTextPlugin({
|
||||
filename: utils.assetsPath('css/[name].[contenthash].css'),
|
||||
// Setting the following option to `false` will not extract CSS from codesplit chunks.
|
||||
// Their CSS will instead be inserted dynamically with style-loader when the codesplit chunk has been loaded by webpack.
|
||||
// It's currently set to `true` because we are seeing that sourcemaps are included in the codesplit bundle as well when it's `false`,
|
||||
// increasing file size: https://github.com/vuejs-templates/webpack/issues/1110
|
||||
allChunks: true,
|
||||
}),
|
||||
// Compress extracted CSS. We are using this plugin so that possible
|
||||
// duplicated CSS from different components can be deduped.
|
||||
new OptimizeCSSPlugin({
|
||||
cssProcessorOptions: config.build.productionSourceMap
|
||||
? { safe: true, map: { inline: false } }
|
||||
: { safe: true }
|
||||
}),
|
||||
// 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,
|
||||
minify: {
|
||||
removeComments: true,
|
||||
collapseWhitespace: true,
|
||||
removeAttributeQuotes: true
|
||||
// more options:
|
||||
// https://github.com/kangax/html-minifier#options-quick-reference
|
||||
},
|
||||
// necessary to consistently work with multiple chunks via CommonsChunkPlugin
|
||||
chunksSortMode: 'dependency'
|
||||
}),
|
||||
// keep module.id stable when vendor modules does not change
|
||||
new webpack.HashedModuleIdsPlugin(),
|
||||
// enable scope hoisting
|
||||
new webpack.optimize.ModuleConcatenationPlugin(),
|
||||
// split vendor js into its own file
|
||||
new webpack.optimize.CommonsChunkPlugin({
|
||||
name: 'vendor',
|
||||
minChunks (module) {
|
||||
// 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',
|
||||
minChunks: Infinity
|
||||
}),
|
||||
// This instance extracts shared chunks from code splitted chunks and bundles them
|
||||
// in a separate chunk, similar to the vendor chunk
|
||||
// see: https://webpack.js.org/plugins/commons-chunk-plugin/#extra-async-commons-chunk
|
||||
new webpack.optimize.CommonsChunkPlugin({
|
||||
name: 'app',
|
||||
async: 'vendor-async',
|
||||
children: true,
|
||||
minChunks: 3
|
||||
}),
|
||||
|
||||
// copy custom static assets
|
||||
new CopyWebpackPlugin([
|
||||
{
|
||||
from: path.resolve(__dirname, '../static'),
|
||||
to: config.build.assetsSubDirectory,
|
||||
ignore: ['.*']
|
||||
}
|
||||
])
|
||||
]
|
||||
})
|
||||
|
||||
if (config.build.productionGzip) {
|
||||
const CompressionWebpackPlugin = require('compression-webpack-plugin')
|
||||
|
||||
webpackConfig.plugins.push(
|
||||
new CompressionWebpackPlugin({
|
||||
asset: '[path].gz[query]',
|
||||
algorithm: 'gzip',
|
||||
test: new RegExp(
|
||||
'\\.(js|css)$'
|
||||
),
|
||||
threshold: 10240,
|
||||
minRatio: 0.8
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
if (config.build.bundleAnalyzerReport) {
|
||||
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
|
||||
webpackConfig.plugins.push(new BundleAnalyzerPlugin())
|
||||
}
|
||||
|
||||
module.exports = webpackConfig
|
|
@ -0,0 +1,7 @@
|
|||
'use strict'
|
||||
const merge = require('webpack-merge')
|
||||
const prodEnv = require('./prod.env')
|
||||
|
||||
module.exports = merge(prodEnv, {
|
||||
NODE_ENV: '"development"'
|
||||
})
|
|
@ -0,0 +1,84 @@
|
|||
'use strict'
|
||||
// Template version: 1.3.1
|
||||
// see http://vuejs-templates.github.io/webpack for documentation.
|
||||
|
||||
const path = require('path')
|
||||
|
||||
module.exports = {
|
||||
dev: {
|
||||
|
||||
// Paths
|
||||
assetsSubDirectory: 'static',
|
||||
assetsPublicPath: '/',
|
||||
proxyTable: {
|
||||
'api': {
|
||||
target: 'http://192.168.100.98:8181',
|
||||
changeorigin: true,
|
||||
pathrewrite: {
|
||||
'^/api': '/'
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// Various Dev Server settings
|
||||
host: 'localhost', // can be overwritten by process.env.HOST
|
||||
port: 8081, // can be overwritten by process.env.PORT, if port is in use, a free one will be determined
|
||||
autoOpenBrowser: false,
|
||||
errorOverlay: true,
|
||||
notifyOnErrors: true,
|
||||
poll: false, // https://webpack.js.org/configuration/dev-server/#devserver-watchoptions-
|
||||
|
||||
// Use Eslint Loader?
|
||||
// If true, your code will be linted during bundling and
|
||||
// linting errors and warnings will be shown in the console.
|
||||
useEslint: true,
|
||||
// If true, eslint errors and warnings will also be shown in the error overlay
|
||||
// in the browser.
|
||||
showEslintErrorsInOverlay: false,
|
||||
|
||||
/**
|
||||
* Source Maps
|
||||
*/
|
||||
|
||||
// https://webpack.js.org/configuration/devtool/#development
|
||||
devtool: 'cheap-module-eval-source-map',
|
||||
|
||||
// If you have problems debugging vue-files in devtools,
|
||||
// set this to false - it *may* help
|
||||
// https://vue-loader.vuejs.org/en/options.html#cachebusting
|
||||
cacheBusting: true,
|
||||
|
||||
cssSourceMap: true
|
||||
},
|
||||
|
||||
build: {
|
||||
// Template for index.html
|
||||
index: path.resolve(__dirname, '../dist/index.html'),
|
||||
|
||||
// Paths
|
||||
assetsRoot: path.resolve(__dirname, '../dist'),
|
||||
assetsSubDirectory: 'static',
|
||||
assetsPublicPath: './',
|
||||
|
||||
/**
|
||||
* Source Maps
|
||||
*/
|
||||
|
||||
productionSourceMap: true,
|
||||
// https://webpack.js.org/configuration/devtool/#production
|
||||
devtool: '#source-map',
|
||||
|
||||
// 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: true,
|
||||
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: true
|
||||
}
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
'use strict'
|
||||
module.exports = {
|
||||
NODE_ENV: '"production"'
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
'use strict'
|
||||
const merge = require('webpack-merge')
|
||||
const devEnv = require('./dev.env')
|
||||
|
||||
module.exports = merge(devEnv, {
|
||||
NODE_ENV: '"testing"'
|
||||
})
|
|
@ -0,0 +1,25 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<link rel="shortcut icon" href="static/img/favicon.ico" type="image/icon">
|
||||
<title>SDN 控制平台</title>
|
||||
<style>.project-loading{background:#fff;width:100%;height:100%;position:fixed;left:0;top:0;z-index:100000}.project-loading .loader{position:fixed;top:50%;left:50%}@-webkit-keyframes line-scale-pulse-out{0%{-webkit-transform:scaley(1);transform:scaley(1)}50%{-webkit-transform:scaley(.4);transform:scaley(.4)}100%{-webkit-transform:scaley(1);transform:scaley(1)}}@keyframes line-scale-pulse-out{0%{-webkit-transform:scaley(1);transform:scaley(1)}50%{-webkit-transform:scaley(.4);transform:scaley(.4)}100%{-webkit-transform:scaley(1);transform:scaley(1)}}.line-scale-pulse-out>div{width:3px;height:33px;border-radius:2px;margin:1px;-webkit-animation-fill-mode:both;animation-fill-mode:both;display:inline-block;-webkit-animation:line-scale-pulse-out .9s -.6s infinite cubic-bezier(.85,.25,.37,.85);animation:line-scale-pulse-out .9s -.6s infinite cubic-bezier(.85,.25,.37,.85)}.line-scale-pulse-out>div:nth-child(2),.line-scale-pulse-out>div:nth-child(4){-webkit-animation-delay:-.4s!important;animation-delay:-.4s!important}.line-scale-pulse-out>div:nth-child(1),.line-scale-pulse-out>div:nth-child(5){-webkit-animation-delay:-.2s!important;animation-delay:-.2s!important}</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="cmcc">
|
||||
<div class="project-loading">
|
||||
<div class="loader">
|
||||
<div class="loader-inner line-scale-pulse-out">
|
||||
<div style="background:#f95476"></div>
|
||||
<div style="background:#ffb74e"></div>
|
||||
<div style="background:#4886ff"></div>
|
||||
<div style="background:#ffb74e"></div>
|
||||
<div style="background:#f95476"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,95 @@
|
|||
{
|
||||
"name": "cmcc-front",
|
||||
"version": "1.0.0",
|
||||
"description": "CMCC Front By Vue",
|
||||
"author": "CMHI <http://hy.10086.cn/>",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js",
|
||||
"start": "npm run dev",
|
||||
"build": "node build/build.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"ant-design-vue": "^1.3.7",
|
||||
"apexcharts": "^2.6.0",
|
||||
"axios": "^0.18.0",
|
||||
"babel-polyfill": "^6.26.0",
|
||||
"date-fns": "^1.29.0",
|
||||
"enquire.js": "^2.1.6",
|
||||
"vue": "^2.5.17",
|
||||
"vue-apexcharts": "^1.2.7",
|
||||
"vue-router": "^3.0.1",
|
||||
"vuedraggable": "^2.16.0",
|
||||
"vuex": "^3.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"autoprefixer": "^7.1.2",
|
||||
"babel-core": "^6.22.1",
|
||||
"babel-eslint": "^8.2.1",
|
||||
"babel-helper-vue-jsx-merge-props": "^2.0.3",
|
||||
"babel-jest": "^21.0.2",
|
||||
"babel-loader": "^7.1.1",
|
||||
"babel-plugin-dynamic-import-node": "^1.2.0",
|
||||
"babel-plugin-syntax-jsx": "^6.18.0",
|
||||
"babel-plugin-transform-es2015-modules-commonjs": "^6.26.0",
|
||||
"babel-plugin-transform-runtime": "^6.22.0",
|
||||
"babel-plugin-transform-vue-jsx": "^3.5.0",
|
||||
"babel-preset-env": "^1.3.2",
|
||||
"babel-preset-stage-2": "^6.22.0",
|
||||
"babel-register": "^6.22.0",
|
||||
"chalk": "^2.0.1",
|
||||
"chromedriver": "^2.27.2",
|
||||
"compression-webpack-plugin": "^1.1.12",
|
||||
"copy-webpack-plugin": "^4.0.1",
|
||||
"cross-spawn": "^5.0.1",
|
||||
"css-loader": "^0.28.0",
|
||||
"eslint": "^4.15.0",
|
||||
"eslint-config-standard": "^10.2.1",
|
||||
"eslint-friendly-formatter": "^3.0.0",
|
||||
"eslint-loader": "^1.7.1",
|
||||
"eslint-plugin-import": "^2.7.0",
|
||||
"eslint-plugin-node": "^5.2.0",
|
||||
"eslint-plugin-promise": "^3.4.0",
|
||||
"eslint-plugin-standard": "^3.0.1",
|
||||
"eslint-plugin-vue": "^4.0.0",
|
||||
"extract-text-webpack-plugin": "^3.0.0",
|
||||
"file-loader": "^1.1.4",
|
||||
"friendly-errors-webpack-plugin": "^1.6.1",
|
||||
"html-webpack-plugin": "^2.30.1",
|
||||
"jest": "^22.0.4",
|
||||
"jest-serializer-vue": "^0.3.0",
|
||||
"less": "^3.7.1",
|
||||
"less-loader": "^4.1.0",
|
||||
"nightwatch": "^0.9.12",
|
||||
"node-notifier": "^5.1.2",
|
||||
"optimize-css-assets-webpack-plugin": "^3.2.0",
|
||||
"ora": "^1.2.0",
|
||||
"portfinder": "^1.0.13",
|
||||
"postcss-import": "^11.0.0",
|
||||
"postcss-loader": "^2.0.8",
|
||||
"postcss-url": "^7.2.1",
|
||||
"rimraf": "^2.6.0",
|
||||
"selenium-server": "^3.0.1",
|
||||
"semver": "^5.3.0",
|
||||
"shelljs": "^0.7.6",
|
||||
"uglifyjs-webpack-plugin": "^1.1.1",
|
||||
"url-loader": "^0.5.8",
|
||||
"vue-jest": "^1.0.2",
|
||||
"vue-loader": "^13.3.0",
|
||||
"vue-style-loader": "^3.0.1",
|
||||
"vue-template-compiler": "2.5.17",
|
||||
"webpack": "^3.6.0",
|
||||
"webpack-bundle-analyzer": "^2.9.0",
|
||||
"webpack-dev-server": "^2.9.1",
|
||||
"webpack-merge": "^4.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6.0.0",
|
||||
"npm": ">= 3.0.0"
|
||||
},
|
||||
"browserslist": [
|
||||
"> 1%",
|
||||
"last 2 versions",
|
||||
"not ie <= 8"
|
||||
]
|
||||
}
|
|
@ -0,0 +1,191 @@
|
|||
<template>
|
||||
<a-locale-provider :locale="chinese">
|
||||
<div id="cmcc">
|
||||
<router-view/>
|
||||
</div>
|
||||
</a-locale-provider>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import enquireScreen from './utils/device'
|
||||
import chinese from 'ant-design-vue/lib/locale-provider/zh_CN'
|
||||
import 'moment/locale/zh-cn'
|
||||
|
||||
export default {
|
||||
name: 'cmcc',
|
||||
data () {
|
||||
return {
|
||||
chinese
|
||||
}
|
||||
},
|
||||
created () {
|
||||
let _this = this
|
||||
enquireScreen(isMobile => {
|
||||
_this.$store.commit('setting/setDevice', isMobile)
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
</script>
|
||||
<style lang="less">
|
||||
:global {
|
||||
.dragable-ghost {
|
||||
border: 1px dashed #aaaaaa;
|
||||
opacity: 0.65;
|
||||
}
|
||||
.dragable-chose {
|
||||
border: 1px dashed #aaaaaa;
|
||||
opacity: 0.65;
|
||||
}
|
||||
.dragable-drag {
|
||||
border: 1px dashed #aaaaaa;
|
||||
opacity: 0.65;
|
||||
}
|
||||
}
|
||||
|
||||
::-webkit-scrollbar {
|
||||
width: .5rem;
|
||||
height: .5rem;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
border-radius: 1px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
border-radius: 1px;
|
||||
background: rgba(0, 0, 0, .2);
|
||||
}
|
||||
|
||||
.multi-page {
|
||||
margin: -24px 0 0
|
||||
}
|
||||
|
||||
.single-page {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.card-area {
|
||||
width: 100%
|
||||
}
|
||||
|
||||
.not-menu-page {
|
||||
background: #fff;
|
||||
padding: 16px 32px;
|
||||
border-left: 1px solid #e8e8e8;
|
||||
border-right: 1px solid #e8e8e8;
|
||||
border-bottom: 1px solid #e8e8e8;
|
||||
}
|
||||
|
||||
.ant-btn, .ant-input, .input, .ant-pagination-item, .ant-pagination-prev .ant-pagination-item-link,
|
||||
.ant-pagination-next .ant-pagination-item-link, .ant-tag, .ant-modal-content, .ant-select-selection,
|
||||
.ant-select-dropdown, .ant-input-group-addon, .ant-input-number-input, .ant-input-number,
|
||||
.ant-pagination-options-quick-jumper input, .ant-alert {
|
||||
border-radius: 2px !important;
|
||||
}
|
||||
|
||||
.ant-tabs.ant-tabs-card .ant-tabs-card-bar .ant-tabs-tab, .ant-tabs.ant-tabs-card > .ant-tabs-bar .ant-tabs-tab {
|
||||
border-radius: 3px 3px 0 0 !important;
|
||||
}
|
||||
|
||||
.ant-card-wider-padding .ant-card-body {
|
||||
padding: 5px 10px !important;
|
||||
}
|
||||
|
||||
.ant-modal-mask {
|
||||
background-color: rgba(0, 0, 0, 0.6) !important;
|
||||
}
|
||||
|
||||
.ant-modal-header {
|
||||
border-bottom-color: #fff !important;
|
||||
}
|
||||
|
||||
.ant-menu-dark .ant-menu-inline.ant-menu-sub {
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2) inset !important;
|
||||
}
|
||||
|
||||
.ant-menu-inline .ant-menu-item, .ant-menu-vertical .ant-menu-item {
|
||||
margin-top: 0 !important;
|
||||
}
|
||||
.ant-menu-dark, .ant-menu-dark .ant-menu-sub {
|
||||
background: #393e46 !important;
|
||||
}
|
||||
|
||||
.ant-table-row-expand-icon {
|
||||
margin-right: 1rem !important;
|
||||
}
|
||||
|
||||
:root .ant-tabs-tab-prev {
|
||||
border: 1px solid #e8e8e8;
|
||||
border-radius: 3px 0 0 0;
|
||||
background: #fafafa;
|
||||
}
|
||||
.ant-card-loading{
|
||||
&:after{
|
||||
width: 0 !important;
|
||||
}
|
||||
}
|
||||
.ant-tabs-tab-next.ant-tabs-tab-arrow-show {
|
||||
border: 1px solid #e8e8e8;
|
||||
border-radius: 0 3px 0 0;
|
||||
background: #fafafa;
|
||||
}
|
||||
.ant-layout-header, .system-top-menu {
|
||||
height: 59px !important;
|
||||
line-height: 59px !important;
|
||||
}
|
||||
.ant-form-item {
|
||||
margin-bottom: 1rem !important;
|
||||
}
|
||||
.ant-menu-inline, .ant-menu-vertical, .ant-menu-vertical-left {
|
||||
border-right: 0 solid #ccc !important;
|
||||
}
|
||||
.ant-drawer-body {
|
||||
padding-bottom: 3rem !important;
|
||||
}
|
||||
.page-tabs .ant-tabs-close-x {
|
||||
color:#fff !important;
|
||||
margin-left: 0.3rem! important;
|
||||
}
|
||||
.page-tabs:hover .ant-tabs-close-x {
|
||||
color: #f95476 !important;
|
||||
}
|
||||
.drawer-bootom-button {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
border-top: 1px solid #e8e8e8;
|
||||
padding: 10px 16px;
|
||||
text-align: right;
|
||||
left: 0;
|
||||
background: #fff;
|
||||
border-radius: 0 0 2px 2px;
|
||||
}
|
||||
.search {
|
||||
margin-bottom: .5rem !important;
|
||||
}
|
||||
i {
|
||||
font-size: .97rem;
|
||||
}
|
||||
p {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
word-break: break-all;
|
||||
white-space: nowrap;
|
||||
}
|
||||
@media screen and (min-width: 1400px) {
|
||||
}
|
||||
|
||||
@media screen and (max-width: 1399px) {
|
||||
p, .ant-pagination, .ant-form, .ant-dropdown, .ant-form-item, .ant-select, .ant-breadcrumb, .ant-form label, .ant-btn, .ant-table,
|
||||
.ant-menu-vertical .ant-menu-item, .ant-menu-vertical-left .ant-menu-item, .ant-menu-vertical-right .ant-menu-item, .ant-menu-inline .ant-menu-item, .ant-menu-vertical .ant-menu-submenu-title, .ant-menu-vertical-left .ant-menu-submenu-title, .ant-menu-vertical-right .ant-menu-submenu-title, .ant-menu-inline .ant-menu-submenu-title {
|
||||
font-size: 13px !important;
|
||||
}
|
||||
.ant-card-head {
|
||||
font-size: 14px !important;
|
||||
}
|
||||
.page-tabs .ant-tabs-nav-container {
|
||||
font-size: 13px !important;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,140 @@
|
|||
<template>
|
||||
<div class="theme-color" :style="{backgroundColor: color}" @click="toggle">
|
||||
<a-icon v-if="sChecked" type="check" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
const Group = {
|
||||
name: 'ColorCheckboxGroup',
|
||||
props: {
|
||||
defaultValues: {
|
||||
required: false
|
||||
},
|
||||
multiple: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
values: [],
|
||||
options: []
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
colors () {
|
||||
let colors = []
|
||||
this.options.forEach(item => {
|
||||
if (item.sChecked) {
|
||||
colors.push(item.color)
|
||||
}
|
||||
})
|
||||
return colors
|
||||
}
|
||||
},
|
||||
provide () {
|
||||
return {
|
||||
groupContext: this
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
values: function (newVal, oldVal) {
|
||||
if (!(newVal.length === 1 && oldVal.length === 1 && newVal[0] === oldVal[0]) || this.multiple) {
|
||||
this.$emit('change', this.values, this.colors)
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleChange (option) {
|
||||
if (!option.checked) {
|
||||
this.values = this.values.filter(item => item !== option.value)
|
||||
} else {
|
||||
if (!this.multiple) {
|
||||
this.values = [option.value]
|
||||
this.options.forEach(item => {
|
||||
if (item.value !== option.value) {
|
||||
item.sChecked = false
|
||||
}
|
||||
})
|
||||
} else {
|
||||
this.values.push(option.value)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
render (h) {
|
||||
const clear = h('div', {attrs: {style: 'clear: both'}})
|
||||
return h(
|
||||
'div',
|
||||
{},
|
||||
[this.$slots.default, clear]
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
name: 'ColorCheckbox',
|
||||
Group: Group,
|
||||
props: {
|
||||
color: {
|
||||
required: true
|
||||
},
|
||||
value: {
|
||||
required: true
|
||||
},
|
||||
checked: {
|
||||
required: false,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
sChecked: this.checked
|
||||
}
|
||||
},
|
||||
inject: ['groupContext'],
|
||||
watch: {
|
||||
'sChecked': function (val) {
|
||||
const value = {
|
||||
value: this.value,
|
||||
color: this.color,
|
||||
checked: this.sChecked
|
||||
}
|
||||
this.$emit('change', value)
|
||||
const groupContext = this.groupContext
|
||||
if (groupContext) {
|
||||
groupContext.handleChange(value)
|
||||
}
|
||||
}
|
||||
},
|
||||
created () {
|
||||
const groupContext = this.groupContext
|
||||
if (groupContext) {
|
||||
this.sChecked = groupContext.defaultValues.indexOf(this.value) >= 0
|
||||
groupContext.options.push(this)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
toggle () {
|
||||
this.sChecked = !this.sChecked
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.theme-color{
|
||||
float: left;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-radius: 2px;
|
||||
cursor: pointer;
|
||||
margin-right: 8px;
|
||||
text-align: center;
|
||||
color: #fff;
|
||||
font-weight: bold;
|
||||
margin-top: .5rem;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,146 @@
|
|||
<template>
|
||||
<div class="img-check-box" @click="toggle">
|
||||
<img :src="img" />
|
||||
<div v-if="sChecked" class="check-item">
|
||||
<a-icon type="check" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
const Group = {
|
||||
name: 'ImgCheckboxGroup',
|
||||
props: {
|
||||
multiple: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
defaultValues: {
|
||||
type: Array,
|
||||
required: false,
|
||||
default: () => []
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
values: [],
|
||||
options: []
|
||||
}
|
||||
},
|
||||
provide () {
|
||||
return {
|
||||
groupContext: this
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
'values': function (newVal, oldVal) {
|
||||
// 此条件是为解决单选时,触发两次chang事件问题
|
||||
if (!(newVal.length === 1 && oldVal.length === 1 && newVal[0] === oldVal[0])) {
|
||||
this.$emit('change', this.values)
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleChange (option) {
|
||||
if (!option.checked) {
|
||||
this.values = this.values.filter(item => item !== option.value)
|
||||
} else {
|
||||
if (!this.multiple) {
|
||||
this.values = [option.value]
|
||||
this.options.forEach(item => {
|
||||
if (item.value !== option.value) {
|
||||
item.sChecked = false
|
||||
}
|
||||
})
|
||||
} else {
|
||||
this.values.push(option.value)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
render (h) {
|
||||
return h(
|
||||
'div',
|
||||
{
|
||||
attrs: {style: 'display: flex'}
|
||||
},
|
||||
[this.$slots.default]
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
name: 'ImgCheckbox',
|
||||
Group,
|
||||
props: {
|
||||
checked: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
img: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
value: {
|
||||
required: true
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
sChecked: this.checked
|
||||
}
|
||||
},
|
||||
inject: ['groupContext'],
|
||||
watch: {
|
||||
'sChecked': function (val) {
|
||||
const option = {
|
||||
value: this.value,
|
||||
checked: this.sChecked
|
||||
}
|
||||
this.$emit('change', option)
|
||||
const groupContext = this.groupContext
|
||||
if (groupContext) {
|
||||
groupContext.handleChange(option)
|
||||
}
|
||||
}
|
||||
},
|
||||
created () {
|
||||
const groupContext = this.groupContext
|
||||
if (groupContext) {
|
||||
this.sChecked = groupContext.defaultValues.length > 0 ? groupContext.defaultValues.indexOf(this.value) >= 0 : this.sChecked
|
||||
groupContext.options.push(this)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
toggle () {
|
||||
if (this.sChecked) {
|
||||
return
|
||||
}
|
||||
this.sChecked = !this.sChecked
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.img-check-box{
|
||||
margin-right: 16px;
|
||||
position: relative;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
.check-item{
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
width: 100%;
|
||||
padding-top: 15px;
|
||||
padding-left: 24px;
|
||||
height: 100%;
|
||||
color: #1890ff;
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,25 @@
|
|||
<template>
|
||||
<a-range-picker
|
||||
:key="id"
|
||||
ref="rangeDate"
|
||||
@change="onChange" style="width: 100%"></a-range-picker>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'RangeDate',
|
||||
data () {
|
||||
return {
|
||||
id: +new Date()
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onChange (date, dateString) {
|
||||
this.$emit('change', dateString)
|
||||
},
|
||||
reset () {
|
||||
this.id = +new Date()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,71 @@
|
|||
<template>
|
||||
<div class="exception">
|
||||
<div class="img">
|
||||
<img :src="config[type].img" />
|
||||
<!--<div class="ele" :style="{backgroundImage: `url(${config[type].img})`}"/>-->
|
||||
</div>
|
||||
<div class="content">
|
||||
<h1>{{config[type].title}}</h1>
|
||||
<div class="desc">{{config[type].desc}}</div>
|
||||
<div class="action">
|
||||
<a-button type="primary" @click="returnHome">带我回首页</a-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Config from './typeConfig'
|
||||
|
||||
export default {
|
||||
name: 'ExceptionPage',
|
||||
props: ['type'],
|
||||
data () {
|
||||
return {
|
||||
config: Config
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
returnHome () {
|
||||
this.$router.push('/')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.exception{
|
||||
min-height: 500px;
|
||||
height: 80%;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
margin-top: 150px;
|
||||
.img{
|
||||
display: inline-block;
|
||||
padding-right: 52px;
|
||||
zoom: 1;
|
||||
img{
|
||||
height: 360px;
|
||||
max-width: 430px;
|
||||
}
|
||||
}
|
||||
.content{
|
||||
display: inline-block;
|
||||
flex: auto;
|
||||
h1{
|
||||
color: #434e59;
|
||||
font-size: 72px;
|
||||
font-weight: 600;
|
||||
line-height: 72px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
.desc{
|
||||
color: rgba(0,0,0,.45);
|
||||
font-size: 20px;
|
||||
line-height: 28px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
|
@ -0,0 +1,19 @@
|
|||
const config = {
|
||||
403: {
|
||||
img: 'https://gw.alipayobjects.com/zos/rmsportal/wZcnGqRDyhPOEYFcZDnb.svg',
|
||||
title: '403',
|
||||
desc: '抱歉,你无权访问该页面'
|
||||
},
|
||||
404: {
|
||||
img: 'https://gw.alipayobjects.com/zos/rmsportal/KpnpchXsobRgLElEozzI.svg',
|
||||
title: '404',
|
||||
desc: '抱歉,你访问的页面不存在或仍在开发中'
|
||||
},
|
||||
500: {
|
||||
img: 'https://gw.alipayobjects.com/zos/rmsportal/RVRUAYdCGeYNBWoKiIwB.svg',
|
||||
title: '500',
|
||||
desc: '抱歉,服务器出错了'
|
||||
}
|
||||
}
|
||||
|
||||
export default config
|
|
@ -0,0 +1,71 @@
|
|||
<template>
|
||||
<a-menu :style="style" class="contextmenu" v-show="visible" @click="handleClick" :selectedKeys="selectedKeys">
|
||||
<a-menu-item :key="item.key" v-for="item in itemList">
|
||||
<a-icon role="menuitemicon" v-if="item.icon" :type="item.icon" />{{item.text}}
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'Contextmenu',
|
||||
props: {
|
||||
visible: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
itemList: {
|
||||
type: Array,
|
||||
required: true,
|
||||
default: () => []
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
left: 0,
|
||||
top: 0,
|
||||
target: null,
|
||||
selectedKeys: []
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
style () {
|
||||
return {
|
||||
left: this.left + 'px',
|
||||
top: this.top + 'px'
|
||||
}
|
||||
}
|
||||
},
|
||||
created () {
|
||||
window.addEventListener('mousedown', e => this.closeMenu(e))
|
||||
window.addEventListener('contextmenu', e => this.setPosition(e))
|
||||
},
|
||||
methods: {
|
||||
closeMenu (e) {
|
||||
if (['menuitemicon', 'menuitem'].indexOf(e.target.getAttribute('role')) < 0) {
|
||||
this.$emit('update:visible', false)
|
||||
}
|
||||
},
|
||||
setPosition (e) {
|
||||
this.left = e.clientX
|
||||
this.top = e.clientY
|
||||
this.target = e.target
|
||||
},
|
||||
handleClick ({key}) {
|
||||
this.$emit('select', key, this.target)
|
||||
this.$emit('update:visible', false)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.contextmenu{
|
||||
position: fixed;
|
||||
z-index: 2;
|
||||
border: 1px solid #eee;
|
||||
border-radius: 4px;
|
||||
box-shadow: 2px 2px 5px #e8e8e8 !important;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,116 @@
|
|||
<template>
|
||||
<a-layout-sider
|
||||
:class="[theme, 'sider', isMobile ? null : 'shadow', fixSiderbar? 'ant-fixed-sidemenu' : null]"
|
||||
width="256px"
|
||||
:collapsible="collapsible"
|
||||
v-model="collapsed"
|
||||
:trigger="null">
|
||||
<div :class="['logo', theme]">
|
||||
<router-link to="/">
|
||||
<img src="static/img/logo.png" alt="">
|
||||
<h1 class="animated fadeIn">{{systemName}}</h1>
|
||||
</router-link>
|
||||
</div>
|
||||
<i-menu :theme="theme" :collapsed="collapsed" :menuData="menuData" @select="onSelect"/>
|
||||
</a-layout-sider>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import IMenu from './menu'
|
||||
import { mapState } from 'vuex'
|
||||
|
||||
export default {
|
||||
name: 'SiderMenu',
|
||||
components: {IMenu},
|
||||
props: {
|
||||
collapsible: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
collapsed: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
menuData: {
|
||||
type: Array,
|
||||
required: true
|
||||
},
|
||||
theme: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: 'dark'
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState({
|
||||
isMobile: state => state.setting.isMobile,
|
||||
systemName: state => state.setting.systemName,
|
||||
fixSiderbar: state => state.setting.fixSiderbar
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
onSelect (obj) {
|
||||
this.$emit('menuSelect', obj)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.shadow {
|
||||
box-shadow: 1px 0 6px rgba(0, 21, 41, .35);
|
||||
}
|
||||
.sider {
|
||||
z-index: 16;
|
||||
position: relative;
|
||||
overflow-x: hidden;
|
||||
&.light {
|
||||
background-color: #fff;
|
||||
}
|
||||
&.dark {
|
||||
background-color: #393e46;
|
||||
}
|
||||
&.ant-fixed-sidemenu {
|
||||
position: fixed;
|
||||
overflow-y: auto;
|
||||
height: 100%;
|
||||
}
|
||||
.logo {
|
||||
height: 59px;
|
||||
position: relative;
|
||||
line-height: 59px;
|
||||
padding-left: 24px;
|
||||
-webkit-transition: all .3s;
|
||||
transition: all .3s;
|
||||
overflow: hidden;
|
||||
&.light {
|
||||
background-color: #fff;
|
||||
border-bottom: 1px solid #f8f8f8;
|
||||
}
|
||||
&.dark {
|
||||
background-color: #393e46;
|
||||
h1 {
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
h1 {
|
||||
color: #fff;
|
||||
font-size: 20px;
|
||||
margin: 0 0 0 12px;
|
||||
font-family: Chinese Quote,-apple-system,BlinkMacSystemFont,Segoe UI,PingFang SC,Hiragino Sans GB,Microsoft YaHei,Helvetica Neue,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol;
|
||||
font-weight: 600;
|
||||
display: inline-block;
|
||||
height: 32px;
|
||||
line-height: 32px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
img {
|
||||
width: 32px;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,170 @@
|
|||
import Menu from 'ant-design-vue/es/menu'
|
||||
import Icon from 'ant-design-vue/es/icon'
|
||||
|
||||
const {Item, SubMenu} = Menu
|
||||
|
||||
export default {
|
||||
name: 'IMenu',
|
||||
props: {
|
||||
menuData: {
|
||||
type: Array,
|
||||
required: true
|
||||
},
|
||||
theme: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: 'dark'
|
||||
},
|
||||
mode: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: 'inline'
|
||||
},
|
||||
collapsed: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
openKeys: [],
|
||||
selectedKeys: [],
|
||||
cachedOpenKeys: []
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
rootSubmenuKeys: (vm) => {
|
||||
let keys = []
|
||||
vm.menuData.forEach(item => {
|
||||
keys.push(item.path)
|
||||
})
|
||||
return keys
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.updateMenu()
|
||||
},
|
||||
watch: {
|
||||
collapsed (val) {
|
||||
if (val) {
|
||||
this.cachedOpenKeys = this.openKeys
|
||||
this.openKeys = []
|
||||
} else {
|
||||
this.openKeys = this.cachedOpenKeys
|
||||
}
|
||||
},
|
||||
'$route': function () {
|
||||
this.updateMenu()
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
renderIcon: function (h, icon) {
|
||||
return icon === 'none' ? null
|
||||
: h(
|
||||
Icon,
|
||||
{
|
||||
props: {type: icon}
|
||||
})
|
||||
},
|
||||
renderMenuItem: function (h, menu, pIndex, index) {
|
||||
return h(
|
||||
Item,
|
||||
{
|
||||
key: menu.path ? menu.path : 'item_' + pIndex + '_' + index
|
||||
},
|
||||
[
|
||||
h(
|
||||
'a',
|
||||
{attrs: {href: '#' + menu.path}},
|
||||
[
|
||||
this.renderIcon(h, menu.icon),
|
||||
h('span', [menu.name])
|
||||
]
|
||||
)
|
||||
]
|
||||
)
|
||||
},
|
||||
renderSubMenu: function (h, menu, pIndex, index) {
|
||||
const this2_ = this
|
||||
let subItem = [h('span',
|
||||
{slot: 'title'},
|
||||
[
|
||||
this.renderIcon(h, menu.icon),
|
||||
h('span', [menu.name])
|
||||
]
|
||||
)]
|
||||
let itemArr = []
|
||||
let pIndex_ = pIndex + '_' + index
|
||||
menu.children.forEach(function (item, i) {
|
||||
itemArr.push(this2_.renderItem(h, item, pIndex_, i))
|
||||
})
|
||||
return h(
|
||||
SubMenu,
|
||||
{key: menu.path ? menu.path : 'submenu_' + pIndex + '_' + index},
|
||||
subItem.concat(itemArr)
|
||||
)
|
||||
},
|
||||
renderItem: function (h, menu, pIndex, index) {
|
||||
if (!menu.hidden) {
|
||||
return menu.children ? this.renderSubMenu(h, menu, pIndex, index) : this.renderMenuItem(h, menu, pIndex, index)
|
||||
}
|
||||
},
|
||||
renderMenu: function (h, menuTree) {
|
||||
const this2_ = this
|
||||
let menuArr = []
|
||||
menuTree.forEach(function (menu, i) {
|
||||
if (!menu.hidden) {
|
||||
menuArr.push(this2_.renderItem(h, menu, '0', i))
|
||||
}
|
||||
})
|
||||
return menuArr
|
||||
},
|
||||
onOpenChange (openKeys) {
|
||||
const latestOpenKey = openKeys.find(key => this.openKeys.indexOf(key) === -1)
|
||||
if (this.rootSubmenuKeys.indexOf(latestOpenKey) === -1) {
|
||||
this.openKeys = openKeys
|
||||
} else {
|
||||
this.openKeys = latestOpenKey ? [latestOpenKey] : []
|
||||
}
|
||||
},
|
||||
updateMenu () {
|
||||
let routes = this.$route.matched.concat()
|
||||
if (routes.length >= 4 && this.$route.meta.hidden) {
|
||||
routes.pop()
|
||||
this.selectedKeys = [routes[2].path]
|
||||
} else {
|
||||
this.selectedKeys = [routes.pop().path]
|
||||
}
|
||||
|
||||
let openKeys = []
|
||||
if (this.mode === 'inline') {
|
||||
routes.forEach((item) => {
|
||||
openKeys.push(item.path)
|
||||
})
|
||||
}
|
||||
|
||||
this.collapsed ? this.cachedOpenKeys = openKeys : this.openKeys = openKeys
|
||||
}
|
||||
},
|
||||
render (h) {
|
||||
return h(
|
||||
Menu,
|
||||
{
|
||||
props: {
|
||||
theme: this.$props.theme,
|
||||
mode: this.$props.mode,
|
||||
openKeys: this.openKeys,
|
||||
selectedKeys: this.selectedKeys
|
||||
},
|
||||
on: {
|
||||
openChange: this.onOpenChange,
|
||||
select: (obj) => {
|
||||
this.selectedKeys = obj.selectedKeys
|
||||
this.$emit('select', obj)
|
||||
}
|
||||
}
|
||||
}, this.renderMenu(h, this.menuData)
|
||||
)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,139 @@
|
|||
<template>
|
||||
<a-layout-sider class="sider" width="273">
|
||||
<setting-item title="导航栏颜色">
|
||||
<img-checkbox-group @change="setTheme">
|
||||
<img-checkbox img="static/img/side-bar-dark.svg" :checked="dark"
|
||||
value="dark"/>
|
||||
<img-checkbox img="static/img/side-bar-light.svg" :checked="!dark"
|
||||
value="light"/>
|
||||
</img-checkbox-group>
|
||||
</setting-item>
|
||||
<setting-item title="主题颜色">
|
||||
<color-checkbox-group @change="onColorChange" :defaultValues="defaultValues" :multiple="false">
|
||||
<template v-for="(color, index) in colorList">
|
||||
<color-checkbox :color="color" :value="index + 1" :key="index"/>
|
||||
</template>
|
||||
</color-checkbox-group>
|
||||
</setting-item>
|
||||
<a-divider/>
|
||||
<setting-item title="导航栏位置">
|
||||
<img-checkbox-group @change="setLayout">
|
||||
<img-checkbox img="static/img/side-bar-left.svg" :checked="side" value="side"/>
|
||||
<img-checkbox img="static/img/side-bar-top.svg" :checked="!side" value="head"/>
|
||||
</img-checkbox-group>
|
||||
</setting-item>
|
||||
<setting-item>
|
||||
<a-list :split="false">
|
||||
<a-list-item>
|
||||
固定顶栏
|
||||
<a-switch :checked="fixedHeader" slot="actions" size="small" @change="fixHeader"/>
|
||||
</a-list-item>
|
||||
<a-list-item>
|
||||
固定侧边栏
|
||||
<a-switch :checked="fixedSiderbar" slot="actions" size="small" @change="fixSiderbar"/>
|
||||
</a-list-item>
|
||||
<a-list-item>
|
||||
多页签模式
|
||||
<a-switch :checked="multipage" slot="actions" size="small" @change="setMultipage"/>
|
||||
</a-list-item>
|
||||
</a-list>
|
||||
</setting-item>
|
||||
<a-button style="width: 100%" icon="save" @click="updateUserConfig">保存设置</a-button>
|
||||
</a-layout-sider>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import SettingItem from './SettingItem'
|
||||
import StyleItem from './StyleItem'
|
||||
import ColorCheckbox from '../checkbox/ColorCheckbox'
|
||||
import ImgCheckbox from '../checkbox/ImgCheckbox'
|
||||
import { updateTheme } from 'utils/color'
|
||||
import {mapState, mapMutations} from 'vuex'
|
||||
|
||||
const ColorCheckboxGroup = ColorCheckbox.Group
|
||||
const ImgCheckboxGroup = ImgCheckbox.Group
|
||||
|
||||
export default {
|
||||
name: 'Setting',
|
||||
components: {ImgCheckboxGroup, ImgCheckbox, ColorCheckboxGroup, ColorCheckbox, StyleItem, SettingItem},
|
||||
computed: {
|
||||
...mapState({
|
||||
multipage: state => state.setting.multipage,
|
||||
theme: state => state.setting.theme,
|
||||
colorList: state => state.setting.colorList,
|
||||
fixedSiderbar: state => state.setting.fixSiderbar,
|
||||
fixedHeader: state => state.setting.fixHeader,
|
||||
layout: state => state.setting.layout,
|
||||
color: state => state.setting.color,
|
||||
user: state => state.account.user
|
||||
}),
|
||||
dark () {
|
||||
return this.theme === 'dark'
|
||||
},
|
||||
side () {
|
||||
return this.layout === 'side'
|
||||
},
|
||||
defaultValues () {
|
||||
let currentColor = this.$store.state.setting.color
|
||||
if (Array.isArray(currentColor)) {
|
||||
currentColor = currentColor[0]
|
||||
}
|
||||
let index = this.colorList.indexOf(currentColor) + 1
|
||||
return `[${index}]`
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
...mapMutations({setSettingBar: 'setting/setSettingBar'}),
|
||||
onColorChange (values, colors) {
|
||||
if (colors.length > 0) {
|
||||
updateTheme(colors)
|
||||
this.$store.commit('setting/setColor', colors)
|
||||
}
|
||||
},
|
||||
setTheme (values) {
|
||||
this.$store.commit('setting/setTheme', values[0])
|
||||
},
|
||||
setLayout (values) {
|
||||
this.$store.commit('setting/setLayout', values[0])
|
||||
},
|
||||
setMultipage (checked) {
|
||||
this.$store.commit('setting/setMultipage', checked)
|
||||
},
|
||||
fixSiderbar (checked) {
|
||||
this.$store.commit('setting/fixSiderbar', checked)
|
||||
},
|
||||
fixHeader (checked) {
|
||||
this.$store.commit('setting/fixHeader', checked)
|
||||
},
|
||||
updateUserConfig () {
|
||||
this.$put('user/userconfig', {
|
||||
multiPage: this.multipage ? '1' : '0',
|
||||
theme: this.theme,
|
||||
fixedSiderbar: this.fixedSiderbar ? '1' : '0',
|
||||
fixedHeader: this.fixedHeader ? '1' : '0',
|
||||
layout: this.layout,
|
||||
color: this.color,
|
||||
userId: this.user.userId
|
||||
}).then(() => {
|
||||
this.$message.success('保存成功')
|
||||
this.setSettingBar(false)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.sider {
|
||||
background-color: #fff;
|
||||
height: 100%;
|
||||
padding: 24px;
|
||||
font-size: 14px;
|
||||
line-height: 1.5;
|
||||
word-wrap: break-word;
|
||||
position: relative;
|
||||
.flex {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,25 @@
|
|||
<template>
|
||||
<div class="setting-item">
|
||||
<h3 class="title">{{title}}</h3>
|
||||
<slot></slot>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'SettingItem',
|
||||
props: ['title']
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.setting-item{
|
||||
margin-bottom: 24px;
|
||||
.title{
|
||||
font-size: 14px;
|
||||
color: rgba(0,0,0,.85);
|
||||
line-height: 22px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,36 @@
|
|||
<template>
|
||||
<div class="style">
|
||||
<img :src="img" />
|
||||
<div v-if="selected" class="select-item">
|
||||
<a-icon type="check" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'StyleItem',
|
||||
props: ['selected', 'img']
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.style{
|
||||
margin-right: 16px;
|
||||
position: relative;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
.select-item{
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
width: 100%;
|
||||
padding-top: 15px;
|
||||
padding-left: 24px;
|
||||
height: 100%;
|
||||
color: #1890ff;
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,133 @@
|
|||
<template>
|
||||
<div >
|
||||
<div :class="['mask', openDrawer ? 'open' : 'close']" @click="close"></div>
|
||||
<div :class="['drawer', placement, openDrawer ? 'open' : 'close']">
|
||||
<div ref="drawer" style="position: relative; height: 100%;">
|
||||
<slot></slot>
|
||||
</div>
|
||||
<div v-if="showHandler" :class="['handler-container', placement]" ref="handler">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapMutations } from 'vuex'
|
||||
|
||||
export default {
|
||||
name: 'Drawer',
|
||||
data () {
|
||||
return {
|
||||
drawerWidth: 0
|
||||
}
|
||||
},
|
||||
props: {
|
||||
openDrawer: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
placement: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: 'left'
|
||||
},
|
||||
showHandler: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
this.drawerWidth = this.getDrawerWidth()
|
||||
},
|
||||
watch: {
|
||||
'drawerWidth': function (val) {
|
||||
if (this.placement === 'left') {
|
||||
this.$refs.handler.style.left = val + 'px'
|
||||
} else {
|
||||
this.$refs.handler.style.right = val + 'px'
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
close () {
|
||||
this.setSettingBar(false)
|
||||
},
|
||||
getDrawerWidth () {
|
||||
return this.$refs.drawer.clientWidth
|
||||
},
|
||||
...mapMutations({setSettingBar: 'setting/setSettingBar'})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.mask{
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(0, 0, 0, 0.2);
|
||||
transition: all 0.5s;
|
||||
z-index: 100;
|
||||
&.open{
|
||||
display: inline-block;
|
||||
}
|
||||
&.close{
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
.drawer{
|
||||
position: fixed;
|
||||
height: 100%;
|
||||
transition: all 0.5s;
|
||||
z-index: 100;
|
||||
&.left{
|
||||
left: 0px;
|
||||
&.open{
|
||||
box-shadow: 2px 0 8px rgba(0,0,0,.15);
|
||||
}
|
||||
&.close{
|
||||
transform: translateX(-100%);
|
||||
}
|
||||
}
|
||||
&.right{
|
||||
right: 0px;
|
||||
&.open{
|
||||
box-shadow: -2px 0 8px rgba(0,0,0,.15);
|
||||
}
|
||||
&.close{
|
||||
transform: translateX(100%);
|
||||
}
|
||||
}
|
||||
.sider{
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
.handler-container{
|
||||
position: fixed;
|
||||
top: 200px;
|
||||
text-align: center;
|
||||
transition: all 0.5s;
|
||||
cursor: pointer;
|
||||
.handler {
|
||||
height: 40px;
|
||||
width: 40px;
|
||||
background-color: #fff;
|
||||
z-index: 100;
|
||||
font-size: 26px;
|
||||
box-shadow: 2px 0 8px rgba(0, 0, 0, 0.15);
|
||||
line-height: 40px;
|
||||
}
|
||||
&.left{
|
||||
.handler{
|
||||
border-radius: 0 5px 5px 0;
|
||||
}
|
||||
}
|
||||
&.right{
|
||||
.handler{
|
||||
border-radius: 5px 0 0 5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,40 @@
|
|||
import Vue from 'vue'
|
||||
import CMCC from './CMCC'
|
||||
import router from './router'
|
||||
import Antd from 'ant-design-vue'
|
||||
import store from './store'
|
||||
import request from 'utils/request'
|
||||
import db from 'utils/localstorage'
|
||||
import VueApexCharts from 'vue-apexcharts'
|
||||
|
||||
import 'ant-design-vue/dist/antd.css'
|
||||
|
||||
import 'utils/install'
|
||||
|
||||
Vue.config.productionTip = false
|
||||
Vue.use(Antd)
|
||||
Vue.use(db)
|
||||
Vue.use(VueApexCharts)
|
||||
|
||||
Vue.component('apexchart', VueApexCharts)
|
||||
|
||||
Vue.use({
|
||||
install (Vue) {
|
||||
Vue.prototype.$db = db
|
||||
}
|
||||
})
|
||||
|
||||
Vue.prototype.$post = request.post
|
||||
Vue.prototype.$get = request.get
|
||||
Vue.prototype.$put = request.put
|
||||
Vue.prototype.$delete = request.delete
|
||||
Vue.prototype.$export = request.export
|
||||
Vue.prototype.$download = request.download
|
||||
Vue.prototype.$upload = request.upload
|
||||
|
||||
/* eslint-disable no-new */
|
||||
new Vue({
|
||||
router,
|
||||
store,
|
||||
render: h => h(CMCC)
|
||||
}).$mount('#cmcc')
|
|
@ -0,0 +1,112 @@
|
|||
import Vue from 'vue'
|
||||
import Router from 'vue-router'
|
||||
import MenuView from '@/views/common/MenuView'
|
||||
import PageView from '@/views/common/PageView'
|
||||
import LoginView from '@/views/login/Common'
|
||||
import EmptyPageView from '@/views/common/EmptyPageView'
|
||||
import HomePageView from '@/views/HomePage'
|
||||
import db from 'utils/localstorage'
|
||||
import request from 'utils/request'
|
||||
|
||||
Vue.use(Router)
|
||||
|
||||
let constRouter = [
|
||||
{
|
||||
path: '/login',
|
||||
name: '登录页',
|
||||
component: LoginView
|
||||
},
|
||||
{
|
||||
path: '/index',
|
||||
name: '首页',
|
||||
redirect: '/home'
|
||||
}
|
||||
]
|
||||
|
||||
let router = new Router({
|
||||
routes: constRouter
|
||||
})
|
||||
|
||||
const whiteList = ['/login']
|
||||
|
||||
let asyncRouter
|
||||
|
||||
// 导航守卫,渲染动态路由
|
||||
router.beforeEach((to, from, next) => {
|
||||
if (whiteList.indexOf(to.path) !== -1) {
|
||||
next()
|
||||
}
|
||||
let token = db.get('USER_TOKEN')
|
||||
let user = db.get('USER')
|
||||
let userRouter = get('USER_ROUTER')
|
||||
if (token.length && user) {
|
||||
if (!asyncRouter) {
|
||||
if (!userRouter) {
|
||||
request.get(`menu/${user.username}`).then((res) => {
|
||||
asyncRouter = res.data
|
||||
save('USER_ROUTER', asyncRouter)
|
||||
go(to, next)
|
||||
})
|
||||
} else {
|
||||
asyncRouter = userRouter
|
||||
go(to, next)
|
||||
}
|
||||
} else {
|
||||
next()
|
||||
}
|
||||
} else {
|
||||
next('/login')
|
||||
}
|
||||
})
|
||||
|
||||
function go (to, next) {
|
||||
asyncRouter = filterAsyncRouter(asyncRouter)
|
||||
router.addRoutes(asyncRouter)
|
||||
next({...to, replace: true})
|
||||
}
|
||||
|
||||
function save (name, data) {
|
||||
localStorage.setItem(name, JSON.stringify(data))
|
||||
}
|
||||
|
||||
function get (name) {
|
||||
return JSON.parse(localStorage.getItem(name))
|
||||
}
|
||||
|
||||
function filterAsyncRouter (routes) {
|
||||
return routes.filter((route) => {
|
||||
let component = route.component
|
||||
if (component) {
|
||||
switch (route.component) {
|
||||
case 'MenuView':
|
||||
route.component = MenuView
|
||||
break
|
||||
case 'PageView':
|
||||
route.component = PageView
|
||||
break
|
||||
case 'EmptyPageView':
|
||||
route.component = EmptyPageView
|
||||
break
|
||||
case 'HomePageView':
|
||||
route.component = HomePageView
|
||||
break
|
||||
default:
|
||||
route.component = view(component)
|
||||
}
|
||||
if (route.children && route.children.length) {
|
||||
route.children = filterAsyncRouter(route.children)
|
||||
}
|
||||
return true
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function view (path) {
|
||||
return function (resolve) {
|
||||
import(`@/views/${path}.vue`).then(mod => {
|
||||
resolve(mod)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export default router
|
|
@ -0,0 +1,13 @@
|
|||
import Vue from 'vue'
|
||||
import Vuex from 'vuex'
|
||||
import account from './modules/account'
|
||||
import setting from './modules/setting'
|
||||
|
||||
Vue.use(Vuex)
|
||||
|
||||
export default new Vuex.Store({
|
||||
modules: {
|
||||
account,
|
||||
setting
|
||||
}
|
||||
})
|
|
@ -0,0 +1,34 @@
|
|||
import db from 'utils/localstorage'
|
||||
|
||||
export default {
|
||||
namespaced: true,
|
||||
state: {
|
||||
token: db.get('USER_TOKEN'),
|
||||
expireTime: db.get('EXPIRE_TIME'),
|
||||
user: db.get('USER'),
|
||||
permissions: db.get('PERMISSIONS'),
|
||||
roles: db.get('ROLES')
|
||||
},
|
||||
mutations: {
|
||||
setToken (state, val) {
|
||||
db.save('USER_TOKEN', val)
|
||||
state.token = val
|
||||
},
|
||||
setExpireTime (state, val) {
|
||||
db.save('EXPIRE_TIME', val)
|
||||
state.expireTime = val
|
||||
},
|
||||
setUser (state, val) {
|
||||
db.save('USER', val)
|
||||
state.user = val
|
||||
},
|
||||
setPermissions (state, val) {
|
||||
db.save('PERMISSIONS', val)
|
||||
state.permissions = val
|
||||
},
|
||||
setRoles (state, val) {
|
||||
db.save('ROLES', val)
|
||||
state.roles = val
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,78 @@
|
|||
import db from 'utils/localstorage'
|
||||
|
||||
export default {
|
||||
namespaced: true,
|
||||
state: {
|
||||
sidebar: {
|
||||
opened: true
|
||||
},
|
||||
settingBar: {
|
||||
opened: false
|
||||
},
|
||||
isMobile: false,
|
||||
theme: db.get('THEME', 'light'),
|
||||
layout: db.get('LAYOUT', 'side'),
|
||||
systemName: 'SDN 控制平台',
|
||||
copyright: `${new Date().getFullYear()} <a href="http://hy.10086.cn/" target="_blank">中移(杭州)信息技术有限公司</a>`,
|
||||
multipage: getBooleanValue(db.get('MULTIPAGE'), true),
|
||||
fixSiderbar: getBooleanValue(db.get('FIX_SIDERBAR'), true),
|
||||
fixHeader: getBooleanValue(db.get('FIX_HEADER'), true),
|
||||
colorList: [
|
||||
'rgb(245, 34, 45)',
|
||||
'rgb(250, 84, 28)',
|
||||
'rgb(250, 173, 20)',
|
||||
'rgb(66, 185, 131)',
|
||||
'rgb(82, 196, 26)',
|
||||
'rgb(24, 144, 255)',
|
||||
'rgb(47, 84, 235)',
|
||||
'rgb(114, 46, 209)'
|
||||
],
|
||||
color: db.get('COLOR', 'rgb(24, 144, 255)')
|
||||
},
|
||||
mutations: {
|
||||
setDevice (state, isMobile) {
|
||||
state.isMobile = isMobile
|
||||
},
|
||||
setTheme (state, theme) {
|
||||
db.save('THEME', theme)
|
||||
state.theme = theme
|
||||
},
|
||||
setLayout (state, layout) {
|
||||
db.save('LAYOUT', layout)
|
||||
state.layout = layout
|
||||
},
|
||||
setMultipage (state, multipage) {
|
||||
db.save('MULTIPAGE', multipage)
|
||||
state.multipage = multipage
|
||||
},
|
||||
setSidebar (state, type) {
|
||||
state.sidebar.opened = type
|
||||
},
|
||||
fixSiderbar (state, flag) {
|
||||
db.save('FIX_SIDERBAR', flag)
|
||||
state.fixSiderbar = flag
|
||||
},
|
||||
fixHeader (state, flag) {
|
||||
db.save('FIX_HEADER', flag)
|
||||
state.fixHeader = flag
|
||||
},
|
||||
setSettingBar (state, flag) {
|
||||
state.settingBar.opened = flag
|
||||
},
|
||||
setColor (state, color) {
|
||||
db.save('COLOR', color)
|
||||
state.color = color
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getBooleanValue (value, defaultValue) {
|
||||
if (Object.is(value, null)) {
|
||||
return defaultValue
|
||||
}
|
||||
if (JSON.stringify(value) !== '{}') {
|
||||
return value
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
import { message } from 'ant-design-vue/es'
|
||||
|
||||
let lessNodesAppended
|
||||
|
||||
const updateTheme = primaryColor => {
|
||||
if (!primaryColor) {
|
||||
return
|
||||
}
|
||||
const hideMessage = message.loading('加载主题...', 0)
|
||||
function buildIt () {
|
||||
if (!window.less) {
|
||||
return
|
||||
}
|
||||
setTimeout(() => {
|
||||
window.less
|
||||
.modifyVars({
|
||||
'@primary-color': primaryColor
|
||||
})
|
||||
.then(() => {
|
||||
hideMessage()
|
||||
})
|
||||
.catch((e) => {
|
||||
console.log(e)
|
||||
message.error('Failed to update theme')
|
||||
hideMessage()
|
||||
})
|
||||
}, 200)
|
||||
}
|
||||
if (!lessNodesAppended) {
|
||||
// insert less.js and color.less
|
||||
const lessStyleNode = document.createElement('link')
|
||||
const lessConfigNode = document.createElement('script')
|
||||
const lessScriptNode = document.createElement('script')
|
||||
lessStyleNode.setAttribute('rel', 'stylesheet/less')
|
||||
lessStyleNode.setAttribute('href', '/static/less/Color.less')
|
||||
lessConfigNode.innerHTML = `
|
||||
window.less = {
|
||||
async: true,
|
||||
env: 'production',
|
||||
javascriptEnabled: true
|
||||
}
|
||||
`
|
||||
lessScriptNode.src = 'https://cdn.bootcss.com/less.js/3.9.0/less.min.js'
|
||||
lessScriptNode.async = true
|
||||
lessScriptNode.onload = () => {
|
||||
buildIt()
|
||||
lessScriptNode.onload = null
|
||||
}
|
||||
document.body.appendChild(lessStyleNode)
|
||||
document.body.appendChild(lessConfigNode)
|
||||
document.body.appendChild(lessScriptNode)
|
||||
lessNodesAppended = true
|
||||
} else {
|
||||
buildIt()
|
||||
}
|
||||
}
|
||||
export { updateTheme }
|
|
@ -0,0 +1,6 @@
|
|||
export function triggerWindowResizeEvent () {
|
||||
let event = document.createEvent('HTMLEvents')
|
||||
event.initEvent('resize', true, true)
|
||||
event.eventType = 'message'
|
||||
window.dispatchEvent(event)
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
import enquireJs from 'enquire.js'
|
||||
|
||||
const enquireScreen = function (call) {
|
||||
const hanlder = {
|
||||
match: function () {
|
||||
call && call(true)
|
||||
},
|
||||
unmatch: function () {
|
||||
call && call(false)
|
||||
}
|
||||
}
|
||||
enquireJs.register('only screen and (max-width: 767.99px)', hanlder)
|
||||
}
|
||||
|
||||
export default enquireScreen
|
|
@ -0,0 +1,17 @@
|
|||
import Vue from 'vue'
|
||||
|
||||
import {hasPermission, hasNoPermission, hasAnyPermission, hasRole, hasAnyRole} from 'utils/permissionDirect'
|
||||
|
||||
const Plugins = [
|
||||
hasPermission,
|
||||
hasNoPermission,
|
||||
hasAnyPermission,
|
||||
hasRole,
|
||||
hasAnyRole
|
||||
]
|
||||
|
||||
Plugins.map((plugin) => {
|
||||
Vue.use(plugin)
|
||||
})
|
||||
|
||||
export default Vue
|
|
@ -0,0 +1,16 @@
|
|||
let db = {
|
||||
save (key, value) {
|
||||
localStorage.setItem(key, JSON.stringify(value))
|
||||
},
|
||||
get (key, defaultValue = {}) {
|
||||
return JSON.parse(localStorage.getItem(key)) || defaultValue
|
||||
},
|
||||
remove (key) {
|
||||
localStorage.removeItem(key)
|
||||
},
|
||||
clear () {
|
||||
localStorage.clear()
|
||||
}
|
||||
}
|
||||
|
||||
export default db
|
|
@ -0,0 +1,126 @@
|
|||
// 定义一些和权限有关的 Vue指令
|
||||
|
||||
// 必须包含列出的所有权限,元素才显示
|
||||
export const hasPermission = {
|
||||
install (Vue) {
|
||||
Vue.directive('hasPermission', {
|
||||
bind (el, binding, vnode) {
|
||||
let permissions = vnode.context.$store.state.account.permissions
|
||||
let value = binding.value.split(',')
|
||||
let flag = true
|
||||
for (let v of value) {
|
||||
if (!permissions.includes(v)) {
|
||||
flag = false
|
||||
}
|
||||
}
|
||||
if (!flag) {
|
||||
if (!el.parentNode) {
|
||||
el.style.display = 'none'
|
||||
} else {
|
||||
el.parentNode.removeChild(el)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 当不包含列出的权限时,渲染该元素
|
||||
export const hasNoPermission = {
|
||||
install (Vue) {
|
||||
Vue.directive('hasNoPermission', {
|
||||
bind (el, binding, vnode) {
|
||||
let permissions = vnode.context.$store.state.account.permissions
|
||||
let value = binding.value.split(',')
|
||||
let flag = true
|
||||
for (let v of value) {
|
||||
if (permissions.includes(v)) {
|
||||
flag = false
|
||||
}
|
||||
}
|
||||
if (!flag) {
|
||||
if (!el.parentNode) {
|
||||
el.style.display = 'none'
|
||||
} else {
|
||||
el.parentNode.removeChild(el)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 只要包含列出的任意一个权限,元素就会显示
|
||||
export const hasAnyPermission = {
|
||||
install (Vue) {
|
||||
Vue.directive('hasAnyPermission', {
|
||||
bind (el, binding, vnode) {
|
||||
let permissions = vnode.context.$store.state.account.permissions
|
||||
let value = binding.value.split(',')
|
||||
let flag = false
|
||||
for (let v of value) {
|
||||
if (permissions.includes(v)) {
|
||||
flag = true
|
||||
}
|
||||
}
|
||||
if (!flag) {
|
||||
if (!el.parentNode) {
|
||||
el.style.display = 'none'
|
||||
} else {
|
||||
el.parentNode.removeChild(el)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 必须包含列出的所有角色,元素才显示
|
||||
export const hasRole = {
|
||||
install (Vue) {
|
||||
Vue.directive('hasRole', {
|
||||
bind (el, binding, vnode) {
|
||||
let permissions = vnode.context.$store.state.account.roles
|
||||
let value = binding.value.split(',')
|
||||
let flag = true
|
||||
for (let v of value) {
|
||||
if (!permissions.includes(v)) {
|
||||
flag = false
|
||||
}
|
||||
}
|
||||
if (!flag) {
|
||||
if (!el.parentNode) {
|
||||
el.style.display = 'none'
|
||||
} else {
|
||||
el.parentNode.removeChild(el)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 只要包含列出的任意一个角色,元素就会显示
|
||||
export const hasAnyRole = {
|
||||
install (Vue) {
|
||||
Vue.directive('hasAnyRole', {
|
||||
bind (el, binding, vnode) {
|
||||
let permissions = vnode.context.$store.state.account.roles
|
||||
let value = binding.value.split(',')
|
||||
let flag = false
|
||||
for (let v of value) {
|
||||
if (permissions.includes(v)) {
|
||||
flag = true
|
||||
}
|
||||
}
|
||||
if (!flag) {
|
||||
if (!el.parentNode) {
|
||||
el.style.display = 'none'
|
||||
} else {
|
||||
el.parentNode.removeChild(el)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -0,0 +1,219 @@
|
|||
import axios from 'axios'
|
||||
import {message, Modal, notification} from 'ant-design-vue'
|
||||
import moment from 'moment'
|
||||
import store from '../store'
|
||||
import db from 'utils/localstorage'
|
||||
moment.locale('zh-cn')
|
||||
|
||||
// 统一配置
|
||||
let SDN_REQUEST = axios.create({
|
||||
// baseURL: 'http://127.0.0.1:9527/',
|
||||
baseURL: 'http://192.168.100.98:8181/',
|
||||
responseType: 'json',
|
||||
withCredentials: true,
|
||||
validateStatus (status) {
|
||||
// 200 外的状态码都认定为失败
|
||||
return status === 200
|
||||
}
|
||||
})
|
||||
|
||||
// 拦截请求
|
||||
SDN_REQUEST.interceptors.request.use((config) => {
|
||||
let expireTime = store.state.account.expireTime
|
||||
let now = moment().format('YYYYMMDDHHmmss')
|
||||
// 让token早10秒种过期,提升“请重新登录”弹窗体验
|
||||
if (now - expireTime >= -10) {
|
||||
Modal.error({
|
||||
title: '登录已过期',
|
||||
content: '很抱歉,登录已过期,请重新登录',
|
||||
okText: '重新登录',
|
||||
mask: false,
|
||||
onOk: () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
db.clear()
|
||||
location.reload()
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
// 有 token就带上
|
||||
if (store.state.account.token) {
|
||||
config.headers.Authentication = store.state.account.token
|
||||
}
|
||||
return config
|
||||
}, (error) => {
|
||||
return Promise.reject(error)
|
||||
})
|
||||
|
||||
// 拦截响应
|
||||
SDN_REQUEST.interceptors.response.use((config) => {
|
||||
return config
|
||||
}, (error) => {
|
||||
if (error.response) {
|
||||
let errorMessage = error.response.data === null ? '系统内部异常,请联系网站管理员' : error.response.data.message
|
||||
switch (error.response.status) {
|
||||
case 404:
|
||||
notification.error({
|
||||
message: '系统提示',
|
||||
description: '很抱歉,资源未找到',
|
||||
duration: 4
|
||||
})
|
||||
break
|
||||
case 403:
|
||||
case 401:
|
||||
notification.warn({
|
||||
message: '系统提示',
|
||||
description: '很抱歉,您无法访问该资源,可能是因为没有相应权限或者登录已失效',
|
||||
duration: 4
|
||||
})
|
||||
break
|
||||
default:
|
||||
notification.error({
|
||||
message: '系统提示',
|
||||
description: errorMessage,
|
||||
duration: 4
|
||||
})
|
||||
break
|
||||
}
|
||||
}
|
||||
return Promise.reject(error)
|
||||
})
|
||||
|
||||
const request = {
|
||||
post (url, params) {
|
||||
return SDN_REQUEST.post(url, params, {
|
||||
transformRequest: [(params) => {
|
||||
let result = ''
|
||||
Object.keys(params).forEach((key) => {
|
||||
if (!Object.is(params[key], undefined) && !Object.is(params[key], null)) {
|
||||
result += encodeURIComponent(key) + '=' + encodeURIComponent(params[key]) + '&'
|
||||
}
|
||||
})
|
||||
return result
|
||||
}],
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded'
|
||||
}
|
||||
})
|
||||
},
|
||||
put (url, params) {
|
||||
return SDN_REQUEST.put(url, params, {
|
||||
transformRequest: [(params) => {
|
||||
let result = ''
|
||||
Object.keys(params).forEach((key) => {
|
||||
if (!Object.is(params[key], undefined) && !Object.is(params[key], null)) {
|
||||
result += encodeURIComponent(key) + '=' + encodeURIComponent(params[key]) + '&'
|
||||
}
|
||||
})
|
||||
return result
|
||||
}],
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded'
|
||||
}
|
||||
})
|
||||
},
|
||||
get (url, params) {
|
||||
let _params
|
||||
if (Object.is(params, undefined)) {
|
||||
_params = ''
|
||||
} else {
|
||||
_params = '?'
|
||||
for (let key in params) {
|
||||
if (params.hasOwnProperty(key) && params[key] !== null) {
|
||||
_params += `${key}=${params[key]}&`
|
||||
}
|
||||
}
|
||||
}
|
||||
return SDN_REQUEST.get(`${url}${_params}`)
|
||||
},
|
||||
delete (url, params) {
|
||||
let _params
|
||||
if (Object.is(params, undefined)) {
|
||||
_params = ''
|
||||
} else {
|
||||
_params = '?'
|
||||
for (let key in params) {
|
||||
if (params.hasOwnProperty(key) && params[key] !== null) {
|
||||
_params += `${key}=${params[key]}&`
|
||||
}
|
||||
}
|
||||
}
|
||||
return SDN_REQUEST.delete(`${url}${_params}`)
|
||||
},
|
||||
export (url, params = {}) {
|
||||
message.loading('导出数据中')
|
||||
return SDN_REQUEST.post(url, params, {
|
||||
transformRequest: [(params) => {
|
||||
let result = ''
|
||||
Object.keys(params).forEach((key) => {
|
||||
if (!Object.is(params[key], undefined) && !Object.is(params[key], null)) {
|
||||
result += encodeURIComponent(key) + '=' + encodeURIComponent(params[key]) + '&'
|
||||
}
|
||||
})
|
||||
return result
|
||||
}],
|
||||
responseType: 'blob'
|
||||
}).then((r) => {
|
||||
const content = r.data
|
||||
const blob = new Blob([content])
|
||||
const fileName = `${new Date().getTime()}_导出结果.xlsx`
|
||||
if ('download' in document.createElement('a')) {
|
||||
const elink = document.createElement('a')
|
||||
elink.download = fileName
|
||||
elink.style.display = 'none'
|
||||
elink.href = URL.createObjectURL(blob)
|
||||
document.body.appendChild(elink)
|
||||
elink.click()
|
||||
URL.revokeObjectURL(elink.href)
|
||||
document.body.removeChild(elink)
|
||||
} else {
|
||||
navigator.msSaveBlob(blob, fileName)
|
||||
}
|
||||
}).catch((r) => {
|
||||
console.error(r)
|
||||
message.error('导出失败')
|
||||
})
|
||||
},
|
||||
download (url, params, filename) {
|
||||
message.loading('文件传输中')
|
||||
return SDN_REQUEST.post(url, params, {
|
||||
transformRequest: [(params) => {
|
||||
let result = ''
|
||||
Object.keys(params).forEach((key) => {
|
||||
if (!Object.is(params[key], undefined) && !Object.is(params[key], null)) {
|
||||
result += encodeURIComponent(key) + '=' + encodeURIComponent(params[key]) + '&'
|
||||
}
|
||||
})
|
||||
return result
|
||||
}],
|
||||
responseType: 'blob'
|
||||
}).then((r) => {
|
||||
const content = r.data
|
||||
const blob = new Blob([content])
|
||||
if ('download' in document.createElement('a')) {
|
||||
const elink = document.createElement('a')
|
||||
elink.download = filename
|
||||
elink.style.display = 'none'
|
||||
elink.href = URL.createObjectURL(blob)
|
||||
document.body.appendChild(elink)
|
||||
elink.click()
|
||||
URL.revokeObjectURL(elink.href)
|
||||
document.body.removeChild(elink)
|
||||
} else {
|
||||
navigator.msSaveBlob(blob, filename)
|
||||
}
|
||||
}).catch((r) => {
|
||||
console.error(r)
|
||||
message.error('下载失败')
|
||||
})
|
||||
},
|
||||
upload (url, params) {
|
||||
return SDN_REQUEST.post(url, params, {
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data'
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export default request
|
|
@ -0,0 +1,48 @@
|
|||
.textOverflow() {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
word-break: break-all;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.textOverflowMulti(@line: 3, @bg: #fff) {
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
line-height: 1.5em;
|
||||
max-height: @line * 1.5em;
|
||||
text-align: justify;
|
||||
margin-right: -1em;
|
||||
padding-right: 1em;
|
||||
&:before {
|
||||
background: @bg;
|
||||
content: '...';
|
||||
padding: 0 1px;
|
||||
position: absolute;
|
||||
right: 14px;
|
||||
bottom: 0;
|
||||
}
|
||||
&:after {
|
||||
background: white;
|
||||
content: '';
|
||||
margin-top: 0.2em;
|
||||
position: absolute;
|
||||
right: 14px;
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
}
|
||||
}
|
||||
|
||||
.clearfix() {
|
||||
zoom: 1;
|
||||
&:before,
|
||||
&:after {
|
||||
content: ' ';
|
||||
display: table;
|
||||
}
|
||||
&:after {
|
||||
clear: both;
|
||||
visibility: hidden;
|
||||
font-size: 0;
|
||||
height: 0;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,291 @@
|
|||
<template>
|
||||
<div :class="[multipage === true ? 'multi-page':'single-page', 'not-menu-page', 'home-page']">
|
||||
<a-row :gutter="8" class="head-info">
|
||||
<a-card class="head-info-card">
|
||||
<a-col :span="12">
|
||||
<div class="head-info-avatar">
|
||||
<img alt="头像" :src="avatar">
|
||||
</div>
|
||||
<div class="head-info-count">
|
||||
<div class="head-info-welcome">
|
||||
{{welcomeMessage}}
|
||||
</div>
|
||||
<div class="head-info-desc">
|
||||
<p>{{user.deptName ? user.deptName : '暂无部门'}} | {{user.roleName ? user.roleName : '暂无角色'}}</p>
|
||||
</div>
|
||||
<div class="head-info-time">上次登录时间:{{user.lastLoginTime ? user.lastLoginTime : '第一次访问系统'}}</div>
|
||||
</div>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<div>
|
||||
<a-row class="more-info">
|
||||
<a-col :span="4"></a-col>
|
||||
<a-col :span="4"></a-col>
|
||||
<a-col :span="4"></a-col>
|
||||
<a-col :span="4">
|
||||
<head-info title="今日IP" :content="todayIp" :center="false" :bordered="false"/>
|
||||
</a-col>
|
||||
<a-col :span="4">
|
||||
<head-info title="今日访问" :content="todayVisitCount" :center="false" :bordered="false"/>
|
||||
</a-col>
|
||||
<a-col :span="4">
|
||||
<head-info title="总访问量" :content="totalVisitCount" :center="false" />
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
</a-col>
|
||||
</a-card>
|
||||
</a-row>
|
||||
<a-row :gutter="8" class="count-info">
|
||||
<a-col :span="12" class="visit-count-wrapper">
|
||||
<a-card class="visit-count">
|
||||
<apexchart ref="count" type=bar height=300 :options="chartOptions" :series="series" />
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import HeadInfo from '@/views/common/HeadInfo'
|
||||
import {mapState} from 'vuex'
|
||||
import moment from 'moment'
|
||||
moment.locale('zh-cn')
|
||||
|
||||
export default {
|
||||
name: 'HomePage',
|
||||
components: {HeadInfo},
|
||||
data () {
|
||||
return {
|
||||
series: [],
|
||||
chartOptions: {
|
||||
chart: {
|
||||
toolbar: {
|
||||
show: false
|
||||
}
|
||||
},
|
||||
plotOptions: {
|
||||
bar: {
|
||||
horizontal: false,
|
||||
columnWidth: '35%'
|
||||
}
|
||||
},
|
||||
dataLabels: {
|
||||
enabled: false
|
||||
},
|
||||
stroke: {
|
||||
show: true,
|
||||
width: 2,
|
||||
colors: ['transparent']
|
||||
},
|
||||
xaxis: {
|
||||
categories: []
|
||||
},
|
||||
fill: {
|
||||
opacity: 1
|
||||
|
||||
}
|
||||
},
|
||||
todayIp: '',
|
||||
todayVisitCount: '',
|
||||
totalVisitCount: '',
|
||||
userRole: '',
|
||||
userDept: '',
|
||||
lastLoginTime: '',
|
||||
welcomeMessage: ''
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState({
|
||||
multipage: state => state.setting.multipage,
|
||||
user: state => state.account.user
|
||||
}),
|
||||
avatar () {
|
||||
return `static/avatar/${this.user.avatar}`
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
welcome () {
|
||||
const date = new Date()
|
||||
const hour = date.getHours()
|
||||
let time = hour < 6 ? '早上好' : (hour <= 11 ? '上午好' : (hour <= 13 ? '中午好' : (hour <= 18 ? '下午好' : '晚上好')))
|
||||
let welcomeArr = [
|
||||
'喝杯咖啡休息下吧☕',
|
||||
'要不要和朋友打局LOL',
|
||||
'要不要和朋友打局王者荣耀',
|
||||
'几天没见又更好看了呢😍',
|
||||
'今天吃了什么好吃的呢',
|
||||
'今天您微笑了吗😊',
|
||||
'今天帮助别人解决问题了吗',
|
||||
'准备吃些什么呢',
|
||||
'周末要不要去看电影?'
|
||||
]
|
||||
let index = Math.floor((Math.random() * welcomeArr.length))
|
||||
return `${time},${this.user.username},${welcomeArr[index]}`
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
this.welcomeMessage = this.welcome()
|
||||
this.$get(`index/${this.user.username}`).then((r) => {
|
||||
let data = r.data.data
|
||||
this.todayIp = data.todayIp
|
||||
this.todayVisitCount = data.todayVisitCount
|
||||
this.totalVisitCount = data.totalVisitCount
|
||||
let sevenVisitCount = []
|
||||
let dateArr = []
|
||||
for (let i = 6; i >= 0; i--) {
|
||||
let time = moment().subtract(i, 'days').format('MM-DD')
|
||||
let contain = false
|
||||
for (let o of data.lastSevenVisitCount) {
|
||||
if (o.days === time) {
|
||||
contain = true
|
||||
sevenVisitCount.push(o.count)
|
||||
}
|
||||
}
|
||||
if (!contain) {
|
||||
sevenVisitCount.push(0)
|
||||
}
|
||||
dateArr.push(time)
|
||||
}
|
||||
let sevenUserVistCount = []
|
||||
for (let i = 6; i >= 0; i--) {
|
||||
let time = moment().subtract(i, 'days').format('MM-DD')
|
||||
let contain = false
|
||||
for (let o of data.lastSevenUserVisitCount) {
|
||||
if (o.days === time) {
|
||||
contain = true
|
||||
sevenUserVistCount.push(o.count)
|
||||
}
|
||||
}
|
||||
if (!contain) {
|
||||
sevenUserVistCount.push(0)
|
||||
}
|
||||
}
|
||||
this.$refs.count.updateSeries([
|
||||
{
|
||||
name: '您',
|
||||
data: sevenUserVistCount
|
||||
},
|
||||
{
|
||||
name: '总数',
|
||||
data: sevenVisitCount
|
||||
}
|
||||
], true)
|
||||
this.$refs.count.updateOptions({
|
||||
xaxis: {
|
||||
categories: dateArr
|
||||
},
|
||||
title: {
|
||||
text: '近七日系统访问记录',
|
||||
align: 'left'
|
||||
}
|
||||
}, true, true)
|
||||
}).catch((r) => {
|
||||
console.error(r)
|
||||
this.$message.error('获取首页信息失败')
|
||||
})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="less">
|
||||
.home-page {
|
||||
.head-info {
|
||||
margin-bottom: .5rem;
|
||||
.head-info-card {
|
||||
padding: .5rem;
|
||||
border-color: #f1f1f1;
|
||||
.head-info-avatar {
|
||||
display: inline-block;
|
||||
float: left;
|
||||
margin-right: 1rem;
|
||||
img {
|
||||
width: 5rem;
|
||||
border-radius: 2px;
|
||||
}
|
||||
}
|
||||
.head-info-count {
|
||||
display: inline-block;
|
||||
float: left;
|
||||
.head-info-welcome {
|
||||
font-size: 1.05rem;
|
||||
margin-bottom: .1rem;
|
||||
}
|
||||
.head-info-desc {
|
||||
color: rgba(0, 0, 0, 0.45);
|
||||
font-size: .8rem;
|
||||
padding: .2rem 0;
|
||||
p {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
.head-info-time {
|
||||
color: rgba(0, 0, 0, 0.45);
|
||||
font-size: .8rem;
|
||||
padding: .2rem 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.count-info {
|
||||
.visit-count-wrapper {
|
||||
padding-left: 0 !important;
|
||||
.visit-count {
|
||||
padding: .5rem;
|
||||
border-color: #f1f1f1;
|
||||
.ant-card-body {
|
||||
padding: .5rem 1rem !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
.project-wrapper {
|
||||
padding-right: 0 !important;
|
||||
.project-card {
|
||||
border: none !important;
|
||||
.ant-card-head {
|
||||
border-left: 1px solid #f1f1f1 !important;
|
||||
border-top: 1px solid #f1f1f1 !important;
|
||||
border-right: 1px solid #f1f1f1 !important;
|
||||
}
|
||||
.ant-card-body {
|
||||
padding: 0 !important;
|
||||
table {
|
||||
width: 100%;
|
||||
td {
|
||||
width: 50%;
|
||||
border: 1px solid #f1f1f1;
|
||||
padding: .6rem;
|
||||
.project-avatar-wrapper {
|
||||
display:inline-block;
|
||||
float:left;
|
||||
margin-right:.7rem;
|
||||
.project-avatar {
|
||||
color: #42b983;
|
||||
background-color: #d6f8b8;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.project-detail {
|
||||
display:inline-block;
|
||||
float:left;
|
||||
text-align:left;
|
||||
width: 78%;
|
||||
.project-name {
|
||||
font-size:.9rem;
|
||||
margin-top:-2px;
|
||||
font-weight:600;
|
||||
}
|
||||
.project-desc {
|
||||
color:rgba(0, 0, 0, 0.45);
|
||||
p {
|
||||
margin-bottom:0;
|
||||
font-size:.6rem;
|
||||
white-space:normal;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,8 @@
|
|||
<template>
|
||||
<router-view></router-view>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
name: 'EmptyPageView'
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,36 @@
|
|||
<template>
|
||||
<div class="footer">
|
||||
<div class="copyright">
|
||||
Copyright
|
||||
<a-icon type="copyright"/>
|
||||
<span v-html="copyright"></span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'GlobalFooter',
|
||||
props: ['copyright']
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.footer {
|
||||
padding: 0 16px;
|
||||
margin: 24px 0;
|
||||
text-align: center;
|
||||
|
||||
.copyright {
|
||||
color: rgba(0, 0, 0, .45);
|
||||
font-size: 14px;
|
||||
i {
|
||||
font-size: .8rem !important;
|
||||
}
|
||||
}
|
||||
|
||||
&a {
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,143 @@
|
|||
<template>
|
||||
<a-layout-header :class="[fixHeader && 'ant-header-fixedHeader', layout === 'side' ? (sidebarOpened ? 'ant-header-side-opened' : 'ant-header-side-closed') : null, theme, 'global-header' ]">
|
||||
<div :class="['global-header-wide', layout]">
|
||||
<router-link v-if="isMobile || layout === 'head'" to="/" :class="['logo', isMobile ? null : 'pc', theme]">
|
||||
<img width="32" src="static/img/logo.png" alt=""/>
|
||||
<h1 v-if="!isMobile">{{systemName}}</h1>
|
||||
</router-link>
|
||||
<a-divider v-if="isMobile" type="vertical" />
|
||||
<a-icon v-if="layout === 'side'" class="trigger" :type="collapsed ? 'menu-unfold' : 'menu-fold'" @click="toggleCollapse"/>
|
||||
<div v-if="layout === 'head'" class="global-header-menu">
|
||||
<i-menu style="height: 64px; line-height: 64px;" class="system-top-menu" :theme="theme" mode="horizontal" :menuData="menuData" @select="onSelect"/>
|
||||
</div>
|
||||
<div :class="['global-header-right', theme]">
|
||||
<header-avatar class="header-item"/>
|
||||
</div>
|
||||
</div>
|
||||
</a-layout-header>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import HeaderAvatar from './HeaderAvatar'
|
||||
import IMenu from '@/components/menu/menu'
|
||||
import { mapState } from 'vuex'
|
||||
|
||||
export default {
|
||||
name: 'GlobalHeader',
|
||||
components: {IMenu, HeaderAvatar},
|
||||
props: ['collapsed', 'menuData'],
|
||||
computed: {
|
||||
...mapState({
|
||||
isMobile: state => state.setting.isMobile,
|
||||
layout: state => state.setting.layout,
|
||||
systemName: state => state.setting.systemName,
|
||||
sidebarOpened: state => state.setting.sidebar.opened,
|
||||
fixHeader: state => state.setting.fixHeader
|
||||
}),
|
||||
theme () {
|
||||
return this.layout === 'side' ? 'light' : this.$store.state.setting.theme
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
toggleCollapse () {
|
||||
this.$emit('toggleCollapse')
|
||||
},
|
||||
onSelect (obj) {
|
||||
this.$emit('menuSelect', obj)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.trigger {
|
||||
font-size: 20px;
|
||||
line-height: 64px;
|
||||
padding: 0 24px;
|
||||
cursor: pointer;
|
||||
transition: color .3s;
|
||||
}
|
||||
.header-item{
|
||||
padding: 0 19px;
|
||||
display: inline-block;
|
||||
height: 100%;
|
||||
cursor: pointer;
|
||||
vertical-align: middle;
|
||||
i{
|
||||
font-size: 16px;
|
||||
color: rgba(0,0,0,.65);
|
||||
}
|
||||
}
|
||||
.global-header{
|
||||
padding: 0 12px 0 0;
|
||||
-webkit-box-shadow: 0 1px 4px rgba(0,21,41,.08);
|
||||
box-shadow: 0 1px 4px rgba(0,21,41,.08);
|
||||
position: relative;
|
||||
&.light{
|
||||
background: #fff;
|
||||
}
|
||||
&.dark{
|
||||
background: #393e46;
|
||||
}
|
||||
.global-header-wide{
|
||||
&.head{
|
||||
padding: 0 24px;
|
||||
}
|
||||
&.side{
|
||||
}
|
||||
.logo {
|
||||
height: 64px;
|
||||
line-height: 58px;
|
||||
vertical-align: top;
|
||||
display: inline-block;
|
||||
padding: 0 12px 0 24px;
|
||||
cursor: pointer;
|
||||
font-size: 20px;
|
||||
&.pc{
|
||||
padding: 0 12px 0 0;
|
||||
}
|
||||
img {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
}
|
||||
h1{
|
||||
display: inline-block;
|
||||
font-size: 16px;
|
||||
}
|
||||
&.dark h1{
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
.global-header-menu{
|
||||
display: inline-block;
|
||||
}
|
||||
.global-header-right{
|
||||
float: right;
|
||||
&.dark{
|
||||
color: #fff;
|
||||
i{
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.ant-header-fixedHeader {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 0;
|
||||
z-index: 15;
|
||||
width: 100%;
|
||||
transition: width .2s;
|
||||
|
||||
&.ant-header-side-opened {
|
||||
width: 100%;
|
||||
padding-left: 254px;
|
||||
}
|
||||
|
||||
&.ant-header-side-closed {
|
||||
width: 100%;
|
||||
padding-left: 80px;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,100 @@
|
|||
<template>
|
||||
<a-layout>
|
||||
<drawer v-if="isMobile" :openDrawer="collapsed" @change="onDrawerChange">
|
||||
<sider-menu :theme="theme" :menuData="menuData" :collapsed="false" :collapsible="false" @menuSelect="onMenuSelect"/>
|
||||
</drawer>
|
||||
<sider-menu :theme="theme" v-else-if="layout === 'side'" :menuData="menuData" :collapsed="collapsed" :collapsible="true" />
|
||||
<drawer :open-drawer="settingBar" placement="right">
|
||||
<setting />
|
||||
</drawer>
|
||||
<a-layout :style="{ paddingLeft: paddingLeft }">
|
||||
<global-header :menuData="menuData" :collapsed="collapsed" @toggleCollapse="toggleCollapse"/>
|
||||
<a-layout-content :style="{minHeight: minHeight, margin: '20px 14px 0'}" :class="fixHeader ? 'fixed-header-content' : null">
|
||||
<slot></slot>
|
||||
</a-layout-content>
|
||||
<a-layout-footer style="padding: .29rem 0" class="copyright">
|
||||
<global-footer :copyright="copyright"/>
|
||||
</a-layout-footer>
|
||||
</a-layout>
|
||||
</a-layout>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import GlobalHeader from './GlobalHeader'
|
||||
import GlobalFooter from './GlobalFooter'
|
||||
import Drawer from '~/tool/Drawer'
|
||||
import SiderMenu from '~/menu/SiderMenu'
|
||||
import Setting from '~/setting/Setting'
|
||||
import { mapState, mapMutations } from 'vuex'
|
||||
import { triggerWindowResizeEvent } from 'utils/common'
|
||||
|
||||
const minHeight = window.innerHeight - 64 - 24 - 66
|
||||
|
||||
let menuData = []
|
||||
|
||||
export default {
|
||||
name: 'GlobalLayout',
|
||||
components: {Setting, SiderMenu, Drawer, GlobalFooter, GlobalHeader},
|
||||
data () {
|
||||
return {
|
||||
minHeight: minHeight + 'px',
|
||||
collapsed: false,
|
||||
menuData: menuData
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
paddingLeft () {
|
||||
return this.fixSiderbar && this.layout === 'side' && !this.isMobile ? `${this.sidebarOpened ? 256 : 80}px` : '0'
|
||||
},
|
||||
...mapState({
|
||||
sidebarOpened: state => state.setting.sidebar.opened,
|
||||
isMobile: state => state.setting.isMobile,
|
||||
theme: state => state.setting.theme,
|
||||
layout: state => state.setting.layout,
|
||||
copyright: state => state.setting.copyright,
|
||||
fixSiderbar: state => state.setting.fixSiderbar,
|
||||
fixHeader: state => state.setting.fixHeader,
|
||||
settingBar: state => state.setting.settingBar.opened
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
...mapMutations({setSidebar: 'setting/setSidebar'}),
|
||||
toggleCollapse () {
|
||||
this.collapsed = !this.collapsed
|
||||
this.setSidebar(!this.collapsed)
|
||||
triggerWindowResizeEvent()
|
||||
},
|
||||
onDrawerChange (show) {
|
||||
this.collapsed = show
|
||||
},
|
||||
onMenuSelect () {
|
||||
this.toggleCollapse()
|
||||
}
|
||||
},
|
||||
beforeCreate () {
|
||||
let routers = this.$db.get('USER_ROUTER')
|
||||
menuData = routers.find((item) => item.path === '/').children.filter((menu) => {
|
||||
let meta = menu.meta
|
||||
if (typeof meta.isShow === 'undefined') {
|
||||
return true
|
||||
} else return meta.isShow
|
||||
})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.setting{
|
||||
background-color: #1890ff;
|
||||
color: #fff;
|
||||
border-radius: 5px 0 0 5px;
|
||||
line-height: 40px;
|
||||
font-size: 22px;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
box-shadow: -2px 0 8px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
.fixed-header-content {
|
||||
margin: 76px 12px 0 !important;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,56 @@
|
|||
<template>
|
||||
<div class="head-info" :class="center && 'center'">
|
||||
<span>{{ title }}</span>
|
||||
<p><a>{{ content }}</a></p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'HeadInfo',
|
||||
props: {
|
||||
title: {
|
||||
default: ''
|
||||
},
|
||||
content: {
|
||||
default: ''
|
||||
},
|
||||
bordered: {
|
||||
default: false
|
||||
},
|
||||
center: {
|
||||
default: true
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.head-info {
|
||||
position: relative;
|
||||
text-align: left;
|
||||
padding: 0 32px 0 0;
|
||||
min-width: 125px;
|
||||
|
||||
&.center {
|
||||
text-align: center;
|
||||
padding: 0 32px;
|
||||
}
|
||||
|
||||
span {
|
||||
color: rgba(0, 0, 0, .45);
|
||||
display: inline-block;
|
||||
font-size: .95rem;
|
||||
line-height: 32px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
p {
|
||||
line-height: 32px;
|
||||
margin: 0;
|
||||
a {
|
||||
font-weight: 600;
|
||||
font-size: 1rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,110 @@
|
|||
<template>
|
||||
<div>
|
||||
<a-dropdown style="display: inline-block; height: 100%; vertical-align: initial">
|
||||
<span style="cursor: pointer">
|
||||
<a-avatar class="avatar" size="small" shape="circle"
|
||||
:src="avatar"/>
|
||||
<span class="curr-user">{{user.username}}</span>
|
||||
</span>
|
||||
<a-menu style="width: 150px" slot="overlay">
|
||||
<a-menu-item @click="openProfile">
|
||||
<a-icon type="user"/>
|
||||
<span>个人中心</span>
|
||||
</a-menu-item>
|
||||
<a-menu-item @click="updatePassword">
|
||||
<a-icon type="key"/>
|
||||
<span>密码修改</span>
|
||||
</a-menu-item>
|
||||
<a-menu-divider></a-menu-divider>
|
||||
<a-menu-item @click="handleSettingClick">
|
||||
<a-icon type="setting"/>
|
||||
<span>系统定制</span>
|
||||
</a-menu-item>
|
||||
<a-menu-divider></a-menu-divider>
|
||||
<a-menu-item @click="logout">
|
||||
<a-icon type="logout"/>
|
||||
<span>退出登录</span>
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</a-dropdown>
|
||||
<update-password
|
||||
@success="handleUpdate"
|
||||
@cancel="handleCancelUpdate"
|
||||
:user="user"
|
||||
:updatePasswordModelVisible="updatePasswordModelVisible">
|
||||
</update-password>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapMutations, mapState } from 'vuex'
|
||||
import UpdatePassword from '../personal/UpdatePassword'
|
||||
|
||||
export default {
|
||||
name: 'HeaderAvatar',
|
||||
components: {UpdatePassword},
|
||||
data () {
|
||||
return {
|
||||
updatePasswordModelVisible: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState({
|
||||
settingBar: state => state.setting.settingBar.opened,
|
||||
user: state => state.account.user
|
||||
}),
|
||||
avatar () {
|
||||
return `static/avatar/${this.user.avatar}`
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleSettingClick () {
|
||||
this.setSettingBar(!this.settingBar)
|
||||
},
|
||||
openProfile () {
|
||||
this.$router.push('/profile')
|
||||
},
|
||||
updatePassword () {
|
||||
this.updatePasswordModelVisible = true
|
||||
},
|
||||
handleCancelUpdate () {
|
||||
this.updatePasswordModelVisible = false
|
||||
},
|
||||
handleUpdate () {
|
||||
this.updatePasswordModelVisible = false
|
||||
this.$message.success('更新密码成功,请重新登录系统')
|
||||
setTimeout(() => {
|
||||
this.logout()
|
||||
}, 1500)
|
||||
},
|
||||
logout () {
|
||||
this.$get(`logout/${this.user.id}`).then(() => {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.$db.clear()
|
||||
location.reload()
|
||||
})
|
||||
}).catch(() => {
|
||||
this.$message.error('退出系统失败')
|
||||
})
|
||||
},
|
||||
...mapMutations({setSettingBar: 'setting/setSettingBar'})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.ant-avatar-sm {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
}
|
||||
.avatar {
|
||||
margin: 20px 4px 20px 0;
|
||||
color: #1890ff;
|
||||
background: hsla(0, 0%, 100%, .85);
|
||||
vertical-align: middle;
|
||||
}
|
||||
.curr-user {
|
||||
font-weight: 600;
|
||||
margin-left: 6px
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,161 @@
|
|||
<template>
|
||||
<global-layout>
|
||||
<contextmenu :itemList="menuItemList" :visible.sync="menuVisible" @select="onMenuSelect"/>
|
||||
<a-tabs
|
||||
class="page-tabs"
|
||||
@contextmenu.native="e => onContextmenu(e)"
|
||||
v-if="multipage"
|
||||
:active-key="activePage"
|
||||
style="margin-top: -8px; margin-bottom: 8px"
|
||||
:hide-add="true"
|
||||
type="editable-card"
|
||||
@change="changePage"
|
||||
@edit="editPage">
|
||||
<a-tab-pane :id="page.fullPath" :key="page.fullPath" v-for="page in pageList" forceRender>
|
||||
<span slot="tab" :pagekey="page.fullPath">{{page.name}}</span>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
<keep-alive v-if="multipage">
|
||||
<router-view/>
|
||||
</keep-alive>
|
||||
<router-view v-else/>
|
||||
</global-layout>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import GlobalLayout from './GlobalLayout'
|
||||
import Contextmenu from '~/menu/Contextmenu'
|
||||
|
||||
export default {
|
||||
name: 'MenuView',
|
||||
components: {Contextmenu, GlobalLayout},
|
||||
data () {
|
||||
return {
|
||||
pageList: [],
|
||||
linkList: [],
|
||||
activePage: '',
|
||||
menuVisible: false,
|
||||
menuItemList: [
|
||||
{key: '1', icon: 'arrow-left', text: '关闭左侧'},
|
||||
{key: '2', icon: 'arrow-right', text: '关闭右侧'},
|
||||
{key: '3', icon: 'close', text: '关闭其它'}
|
||||
]
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
multipage () {
|
||||
return this.$store.state.setting.multipage
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.pageList.push(this.$route)
|
||||
this.linkList.push(this.$route.fullPath)
|
||||
this.activePage = this.$route.fullPath
|
||||
},
|
||||
watch: {
|
||||
'$route': function (newRoute, oldRoute) {
|
||||
this.activePage = newRoute.fullPath
|
||||
if (!this.multipage) {
|
||||
this.linkList = [newRoute.fullPath]
|
||||
this.pageList = [newRoute]
|
||||
} else if (this.linkList.indexOf(newRoute.fullPath) < 0) {
|
||||
this.linkList.push(newRoute.fullPath)
|
||||
this.pageList.push(newRoute)
|
||||
}
|
||||
},
|
||||
'activePage': function (key) {
|
||||
this.$router.push(key)
|
||||
},
|
||||
'multipage': function (newVal, oldVal) {
|
||||
if (!newVal) {
|
||||
this.linkList = [this.$route.fullPath]
|
||||
this.pageList = [this.$route]
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
changePage (key) {
|
||||
this.activePage = key
|
||||
},
|
||||
editPage (key, action) {
|
||||
this[action](key)
|
||||
},
|
||||
remove (key) {
|
||||
if (this.pageList.length === 1) {
|
||||
this.$router.push('/')
|
||||
if (!this.pageList[0].meta.closeable) {
|
||||
return
|
||||
}
|
||||
}
|
||||
this.pageList = this.pageList.filter(item => item.fullPath !== key)
|
||||
let index = this.linkList.indexOf(key)
|
||||
this.linkList = this.linkList.filter(item => item !== key)
|
||||
index = index >= this.linkList.length ? this.linkList.length - 1 : index
|
||||
this.activePage = this.linkList[index]
|
||||
},
|
||||
onContextmenu (e) {
|
||||
const pagekey = this.getPageKey(e.target)
|
||||
if (pagekey !== null) {
|
||||
e.preventDefault()
|
||||
this.menuVisible = true
|
||||
}
|
||||
},
|
||||
getPageKey (target, depth) {
|
||||
depth = depth || 0
|
||||
if (depth > 2) {
|
||||
return null
|
||||
}
|
||||
let pageKey = target.getAttribute('pagekey')
|
||||
pageKey = pageKey || (target.previousElementSibling ? target.previousElementSibling.getAttribute('pagekey') : null)
|
||||
return pageKey || (target.firstElementChild ? this.getPageKey(target.firstElementChild, ++depth) : null)
|
||||
},
|
||||
onMenuSelect (key, target) {
|
||||
let pageKey = this.getPageKey(target)
|
||||
switch (key) {
|
||||
case '1':
|
||||
this.closeLeft(pageKey)
|
||||
break
|
||||
case '2':
|
||||
this.closeRight(pageKey)
|
||||
break
|
||||
case '3':
|
||||
this.closeOthers(pageKey)
|
||||
break
|
||||
case '4':
|
||||
this.closeAll(pageKey)
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
},
|
||||
closeOthers (pageKey) {
|
||||
let index = this.linkList.indexOf(pageKey)
|
||||
this.linkList = this.linkList.slice(index, index + 1)
|
||||
this.pageList = this.pageList.slice(index, index + 1)
|
||||
this.activePage = this.linkList[0]
|
||||
},
|
||||
closeLeft (pageKey) {
|
||||
let index = this.linkList.indexOf(pageKey)
|
||||
this.linkList = this.linkList.slice(index)
|
||||
this.pageList = this.pageList.slice(index)
|
||||
if (this.linkList.indexOf(this.activePage) < 0) {
|
||||
this.activePage = this.linkList[0]
|
||||
}
|
||||
},
|
||||
closeRight (pageKey) {
|
||||
let index = this.linkList.indexOf(pageKey)
|
||||
this.linkList = this.linkList.slice(0, index + 1)
|
||||
this.pageList = this.pageList.slice(0, index + 1)
|
||||
if (this.linkList.indexOf(this.activePage < 0)) {
|
||||
this.activePage = this.linkList[this.linkList.length - 1]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
>>>.ant-tabs-tab{
|
||||
margin-right: 1px !important;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,108 @@
|
|||
<template>
|
||||
<div class="page-content">
|
||||
<div :class="['page-header-wide', layout]">
|
||||
<div class="breadcrumb">
|
||||
<a-breadcrumb>
|
||||
<a-breadcrumb-item :key="item.path" v-for="(item, index) in breadcrumb">
|
||||
<span v-if="index === 0"><router-link to="/">{{item.name}}</router-link></span>
|
||||
<span v-else>{{item.name}}</span>
|
||||
</a-breadcrumb-item>
|
||||
</a-breadcrumb>
|
||||
</div>
|
||||
<div class="detail">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'PageContent',
|
||||
props: {
|
||||
title: {
|
||||
type: String,
|
||||
required: false
|
||||
},
|
||||
breadcrumb: {
|
||||
type: Array,
|
||||
required: false
|
||||
},
|
||||
logo: {
|
||||
type: String,
|
||||
required: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
layout () {
|
||||
return this.$store.state.setting.layout
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.page-content{
|
||||
background: #fff;
|
||||
padding: 14px 22px;
|
||||
border-left: 1px solid #e8e8e8;
|
||||
border-right: 1px solid #e8e8e8;
|
||||
border-bottom: 1px solid #e8e8e8;
|
||||
.page-header-wide{
|
||||
.breadcrumb{
|
||||
margin-bottom: .6rem;
|
||||
}
|
||||
.detail{
|
||||
display: flex;
|
||||
padding: 0 0 1rem 0;
|
||||
.row {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
}
|
||||
.avatar {
|
||||
flex: 0 1 72px;
|
||||
margin:0 24px 8px 0;
|
||||
& > span {
|
||||
border-radius: 72px;
|
||||
display: block;
|
||||
width: 72px;
|
||||
height: 72px;
|
||||
}
|
||||
}
|
||||
.main{
|
||||
width: 100%;
|
||||
flex: 0 1 auto;
|
||||
.title{
|
||||
flex: auto;
|
||||
font-size: 20px;
|
||||
font-weight: 500;
|
||||
color: rgba(0,0,0,.85);
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.logo{
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
border-radius: 4px;
|
||||
margin-right: 16px;
|
||||
}
|
||||
.content{
|
||||
margin-bottom: 16px;
|
||||
flex: auto;
|
||||
}
|
||||
.extra{
|
||||
flex: 0 1 auto;
|
||||
margin-left: 88px;
|
||||
min-width: 242px;
|
||||
text-align: right;
|
||||
}
|
||||
.action{
|
||||
margin-left: 56px;
|
||||
min-width: 266px;
|
||||
flex: 0 1 auto;
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,61 @@
|
|||
<template>
|
||||
<div :class="multipage === true ? 'multi-page':'single-page'">
|
||||
<page-content :breadcrumb="breadcrumb" :title="title" :logo="logo">
|
||||
<slot></slot>
|
||||
</page-content>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import PageContent from './PageContent'
|
||||
export default {
|
||||
name: 'PageLayout',
|
||||
components: {PageContent},
|
||||
props: ['logo', 'title'],
|
||||
data () {
|
||||
return {
|
||||
breadcrumb: []
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
multipage () {
|
||||
return this.$store.state.setting.multipage
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
this.getBreadcrumb()
|
||||
},
|
||||
updated () {
|
||||
this.getBreadcrumb()
|
||||
},
|
||||
methods: {
|
||||
getBreadcrumb () {
|
||||
this.breadcrumb = this.$route.matched
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.link{
|
||||
margin-top: 16px;
|
||||
line-height: 24px;
|
||||
a{
|
||||
font-size: 14px;
|
||||
margin-right: 32px;
|
||||
i{
|
||||
font-size: 22px;
|
||||
margin-right: 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.page-content{
|
||||
&.side{
|
||||
margin: 24px 24px 0;
|
||||
}
|
||||
&.head{
|
||||
margin: 24px auto 0;
|
||||
max-width: 1400px;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,48 @@
|
|||
<template>
|
||||
<page-layout :title="title">
|
||||
<keep-alive v-if="multipage">
|
||||
<router-view ref="page"/>
|
||||
</keep-alive>
|
||||
<router-view ref="page" v-else/>
|
||||
</page-layout>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import PageLayout from './PageLayout'
|
||||
export default {
|
||||
name: 'PageView',
|
||||
components: {PageLayout},
|
||||
data () {
|
||||
return {
|
||||
title: ''
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
multipage () {
|
||||
return this.$store.state.setting.multipage
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
this.getPageHeaderInfo()
|
||||
},
|
||||
updated () {
|
||||
this.getPageHeaderInfo()
|
||||
},
|
||||
methods: {
|
||||
getPageHeaderInfo () {
|
||||
this.title = this.$route.name
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.extraImg{
|
||||
margin-top: -60px;
|
||||
text-align: center;
|
||||
width: 195px;
|
||||
img{
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,14 @@
|
|||
<template>
|
||||
<exception-page type="403" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ExceptionPage from '~/exception/ExceptionPage'
|
||||
export default {
|
||||
components: {ExceptionPage}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
|
@ -0,0 +1,14 @@
|
|||
<template>
|
||||
<exception-page type="404" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ExceptionPage from '~/exception/ExceptionPage'
|
||||
export default {
|
||||
components: {ExceptionPage}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
|
@ -0,0 +1,14 @@
|
|||
<template>
|
||||
<exception-page type="500" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ExceptionPage from '~/exception/ExceptionPage'
|
||||
export default {
|
||||
components: {ExceptionPage}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
|
@ -0,0 +1,103 @@
|
|||
<template>
|
||||
<div class="container">
|
||||
<div class="content">
|
||||
<div class="top">
|
||||
<div class="header">
|
||||
<img alt="logo" class="logo" src="static/img/logo.png" />
|
||||
<span class="title">{{systemName}}</span>
|
||||
</div>
|
||||
<div class="desc"></div>
|
||||
</div>
|
||||
<component :is="componentName" @regist="handleRegist" class="main-content"></component>
|
||||
</div>
|
||||
<global-footer :copyright="copyright" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import GlobalFooter from '../common/GlobalFooter'
|
||||
import Login from './Login'
|
||||
|
||||
export default {
|
||||
name: 'Common',
|
||||
components: {GlobalFooter, Login},
|
||||
data () {
|
||||
return {
|
||||
componentName: 'Login'
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
systemName () {
|
||||
return this.$store.state.setting.systemName
|
||||
},
|
||||
copyright () {
|
||||
return this.$store.state.setting.copyright
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleRegist (val) {
|
||||
this.componentName = val
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100vh;
|
||||
overflow: auto;
|
||||
background: #f0f2f5 url('https://gw.alipayobjects.com/zos/rmsportal/TVYTbAXWheQpRcWDaDMu.svg') no-repeat center 110px;
|
||||
background-size: 100%;
|
||||
.content {
|
||||
padding: 32px 0;
|
||||
flex: 1;
|
||||
@media (min-width: 768px){
|
||||
padding: 116px 0 10px;
|
||||
}
|
||||
.top {
|
||||
text-align: center;
|
||||
.header {
|
||||
height: 50px;
|
||||
line-height: 50px;
|
||||
a {
|
||||
text-decoration: none;
|
||||
}
|
||||
.logo {
|
||||
width: 40px;
|
||||
height: 19px;
|
||||
vertical-align: center;
|
||||
margin-right: 16px;
|
||||
}
|
||||
.title {
|
||||
font-size: 28px;
|
||||
color: rgba(0,0,0,.85);
|
||||
font-family: 'Myriad Pro', 'Helvetica Neue', Arial, Helvetica, sans-serif;
|
||||
font-weight: 600;
|
||||
position: relative;
|
||||
top: 6px;
|
||||
}
|
||||
}
|
||||
.desc {
|
||||
font-size: 14px;
|
||||
color: rgba(0,0,0,.45);
|
||||
margin-top: 12px;
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
}
|
||||
.main-content {
|
||||
width: 368px;
|
||||
margin: 0 auto;
|
||||
@media screen and (max-width: 576px) {
|
||||
width: 95%;
|
||||
}
|
||||
@media screen and (max-width: 320px) {
|
||||
.captcha-button{
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,139 @@
|
|||
<template>
|
||||
<div class="login">
|
||||
<a-form @submit.prevent="doLogin" :autoFormCreate="(form) => this.form = form">
|
||||
<a-tabs size="large" :tabBarStyle="{textAlign: 'center'}" style="padding: 0 2px;" :activeKey="activeKey"
|
||||
@change="handleTabsChange">
|
||||
<a-tab-pane tab="账户密码登录" key="1">
|
||||
<a-alert type="error" :closable="true" v-show="error" :message="error" showIcon
|
||||
style="margin-bottom: 24px;"></a-alert>
|
||||
<a-form-item
|
||||
fieldDecoratorId="name"
|
||||
:fieldDecoratorOptions="{rules: [{ required: true, message: '请输入账户名', whitespace: true}]}">
|
||||
<a-input size="large">
|
||||
<a-icon slot="prefix" type="user"></a-icon>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
fieldDecoratorId="password"
|
||||
:fieldDecoratorOptions="{rules: [{ required: true, message: '请输入密码', whitespace: true}]}">
|
||||
<a-input size="large" type="password">
|
||||
<a-icon slot="prefix" type="lock"></a-icon>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
<a-form-item>
|
||||
<a-button :loading="loading" style="width: 100%; margin-top: 4px" size="large" htmlType="submit" type="primary">
|
||||
登录
|
||||
</a-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {mapMutations} from 'vuex'
|
||||
|
||||
export default {
|
||||
name: 'Login',
|
||||
data () {
|
||||
return {
|
||||
loading: false,
|
||||
error: '',
|
||||
activeKey: '1'
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
systemName () {
|
||||
return this.$store.state.setting.systemName
|
||||
},
|
||||
copyright () {
|
||||
return this.$store.state.setting.copyright
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.$db.clear()
|
||||
this.$router.options.routes = []
|
||||
},
|
||||
methods: {
|
||||
doLogin () {
|
||||
if (this.activeKey === '1') {
|
||||
// 用户名密码登录
|
||||
this.form.validateFields(['name', 'password'], (errors, values) => {
|
||||
if (!errors) {
|
||||
this.loading = true
|
||||
let name = this.form.getFieldValue('name')
|
||||
let password = this.form.getFieldValue('password')
|
||||
this.$post('oauth2/token', {
|
||||
username: name,
|
||||
password: password,
|
||||
scope: 'sdn',
|
||||
grant_type: 'password'
|
||||
}).then((r) => {
|
||||
let data = r.data.data
|
||||
data.user = name
|
||||
data.password = password
|
||||
this.saveLoginData(data)
|
||||
setTimeout(() => {
|
||||
this.loading = false
|
||||
}, 500)
|
||||
this.$router.push('/')
|
||||
}).catch(() => {
|
||||
setTimeout(() => {
|
||||
this.loading = false
|
||||
}, 500)
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
handleTabsChange (val) {
|
||||
this.activeKey = val
|
||||
},
|
||||
...mapMutations({
|
||||
setToken: 'account/setToken',
|
||||
setExpireTime: 'account/setExpireTime',
|
||||
setPermissions: 'account/setPermissions',
|
||||
setRoles: 'account/setRoles',
|
||||
setUser: 'account/setUser',
|
||||
setTheme: 'setting/setTheme',
|
||||
setLayout: 'setting/setLayout',
|
||||
setMultipage: 'setting/setMultipage',
|
||||
fixSiderbar: 'setting/fixSiderbar',
|
||||
fixHeader: 'setting/fixHeader',
|
||||
setColor: 'setting/setColor'
|
||||
}),
|
||||
saveLoginData (data) {
|
||||
this.setToken(data.access_token)
|
||||
this.setExpireTime(data.expires_in)
|
||||
this.setUser(data.user)
|
||||
this.setPermissions(data.permissions)
|
||||
this.setRoles(data.roles)
|
||||
this.setTheme(data.config.theme)
|
||||
this.setLayout(data.config.layout)
|
||||
this.setMultipage(data.config.multiPage === '1')
|
||||
this.fixSiderbar(data.config.fixSiderbar === '1')
|
||||
this.fixHeader(data.config.fixHeader === '1')
|
||||
this.setColor(data.config.color)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.login {
|
||||
.icon {
|
||||
font-size: 24px;
|
||||
color: rgba(0, 0, 0, 0.2);
|
||||
margin-left: 16px;
|
||||
vertical-align: middle;
|
||||
cursor: pointer;
|
||||
transition: color 0.3s;
|
||||
|
||||
&:hover {
|
||||
color: #1890ff;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,293 @@
|
|||
<template>
|
||||
<div class="user-layout-register">
|
||||
<a-form ref="formRegister" :autoFormCreate="(form)=>{this.form = form}" id="formRegister">
|
||||
<a-form-item
|
||||
fieldDecoratorId="email"
|
||||
:fieldDecoratorOptions="{rules: [{ required: true, message: '请输入注册账号' }, { validator: this.handleUsernameCheck }], validateTrigger: ['change', 'blur']}">
|
||||
<a-input size="large" type="text" v-model="username" placeholder="账号"></a-input>
|
||||
</a-form-item>
|
||||
<a-popover placement="rightTop" trigger="click" :visible="state.passwordLevelChecked">
|
||||
<template slot="content">
|
||||
<div :style="{ width: '240px' }">
|
||||
<div :class="['user-register', passwordLevelClass]">强度:<span>{{ passwordLevelName }}</span></div>
|
||||
<a-progress :percent="state.percent" :showInfo="false" :strokeColor=" passwordLevelColor "/>
|
||||
<div style="margin-top: 10px;">
|
||||
<span>请至少输入 6 个字符。请不要使用容易被猜到的密码。</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<a-form-item
|
||||
fieldDecoratorId="password"
|
||||
:fieldDecoratorOptions="{rules: [{ required: true, message: '至少6位密码'}, { validator: this.handlePasswordLevel }], validateTrigger: ['change', 'blur']}">
|
||||
<a-input size="large" v-model="password" type="password" @click="handlePasswordInputClick" autocomplete="false"
|
||||
placeholder="至少6位密码"></a-input>
|
||||
</a-form-item>
|
||||
</a-popover>
|
||||
|
||||
<a-form-item
|
||||
fieldDecoratorId="password2"
|
||||
:fieldDecoratorOptions="{rules: [{ required: true, message: '至少6位密码' }, { validator: this.handlePasswordCheck }], validateTrigger: ['change', 'blur']}">
|
||||
|
||||
<a-input size="large" type="password" autocomplete="false" placeholder="确认密码"></a-input>
|
||||
</a-form-item>
|
||||
<!--
|
||||
<a-form-item
|
||||
fieldDecoratorId="mobile"
|
||||
:fieldDecoratorOptions="{rules: [{ required: true, message: '请输入正确的手机号', pattern: /^1[3456789]\d{9}$/ }, { validator: this.handlePhoneCheck } ], validateTrigger: ['change', 'blur'] }">
|
||||
<a-input size="large" placeholder="11 位手机号">
|
||||
<a-select slot="addonBefore" size="large" defaultValue="+86">
|
||||
<a-select-option value="+86">+86</a-select-option>
|
||||
<a-select-option value="+87">+87</a-select-option>
|
||||
</a-select>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
<a-input-group size="large" compact>
|
||||
<a-select style="width: 20%" size="large" defaultValue="+86">
|
||||
<a-select-option value="+86">+86</a-select-option>
|
||||
<a-select-option value="+87">+87</a-select-option>
|
||||
</a-select>
|
||||
<a-input style="width: 80%" size="large" placeholder="11 位手机号"></a-input>
|
||||
</a-input-group>
|
||||
|
||||
<a-row :gutter="16">
|
||||
<a-col class="gutter-row" :span="16">
|
||||
<a-form-item
|
||||
fieldDecoratorId="captcha"
|
||||
:fieldDecoratorOptions="{rules: [{ required: true, message: '请输入验证码' }], validateTrigger: 'blur'}">
|
||||
<a-input size="large" type="text" placeholder="验证码">
|
||||
<a-icon slot="prefix" type='mail' :style="{ color: 'rgba(0,0,0,.25)' }"/>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col class="gutter-row" :span="8">
|
||||
<a-button
|
||||
class="getCaptcha"
|
||||
size="large"
|
||||
:disabled="state.smsSendBtn"
|
||||
@click.stop.prevent="getCaptcha"
|
||||
v-text="!state.smsSendBtn && '获取验证码'||(state.time+' s')"></a-button>
|
||||
</a-col>
|
||||
</a-row>
|
||||
-->
|
||||
<a-form-item>
|
||||
<a-button
|
||||
size="large"
|
||||
type="primary"
|
||||
htmlType="submit"
|
||||
class="register-button"
|
||||
:loading="registerBtn"
|
||||
@click.stop.prevent="handleSubmit"
|
||||
:disabled="registerBtn">立即注册
|
||||
</a-button>
|
||||
<a class="login" @click="returnLogin">使用已有账户登录</a>
|
||||
</a-form-item>
|
||||
|
||||
</a-form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
const levelNames = {
|
||||
0: '低',
|
||||
1: '低',
|
||||
2: '中',
|
||||
3: '强'
|
||||
}
|
||||
const levelClass = {
|
||||
0: 'error',
|
||||
1: 'error',
|
||||
2: 'warning',
|
||||
3: 'success'
|
||||
}
|
||||
const levelColor = {
|
||||
0: '#ff0000',
|
||||
1: '#ff0000',
|
||||
2: '#ff7e05',
|
||||
3: '#52c41a'
|
||||
}
|
||||
export default {
|
||||
name: 'Regist',
|
||||
components: {},
|
||||
data () {
|
||||
return {
|
||||
form: null,
|
||||
username: '',
|
||||
password: '',
|
||||
state: {
|
||||
time: 60,
|
||||
smsSendBtn: false,
|
||||
passwordLevel: 0,
|
||||
passwordLevelChecked: false,
|
||||
percent: 10,
|
||||
progressColor: '#FF0000'
|
||||
},
|
||||
registerBtn: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
passwordLevelClass () {
|
||||
return levelClass[this.state.passwordLevel]
|
||||
},
|
||||
passwordLevelName () {
|
||||
return levelNames[this.state.passwordLevel]
|
||||
},
|
||||
passwordLevelColor () {
|
||||
return levelColor[this.state.passwordLevel]
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
isMobile () {
|
||||
return this.$store.state.setting.isMobile
|
||||
},
|
||||
handlePasswordLevel (rule, value, callback) {
|
||||
let level = 0
|
||||
|
||||
// 判断这个字符串中有没有数字
|
||||
if (/[0-9]/.test(value)) {
|
||||
level++
|
||||
}
|
||||
// 判断字符串中有没有字母
|
||||
if (/[a-zA-Z]/.test(value)) {
|
||||
level++
|
||||
}
|
||||
// 判断字符串中有没有特殊符号
|
||||
if (/[^0-9a-zA-Z_]/.test(value)) {
|
||||
level++
|
||||
}
|
||||
this.state.passwordLevel = level
|
||||
this.state.percent = level * 30
|
||||
if (level >= 2) {
|
||||
if (level >= 3) {
|
||||
this.state.percent = 100
|
||||
}
|
||||
callback()
|
||||
} else {
|
||||
if (level === 0) {
|
||||
this.state.percent = 10
|
||||
}
|
||||
callback(new Error('密码强度不够'))
|
||||
}
|
||||
},
|
||||
|
||||
handlePasswordCheck (rule, value, callback) {
|
||||
let password = this.form.getFieldValue('password')
|
||||
if (value === undefined) {
|
||||
callback(new Error('请输入密码'))
|
||||
}
|
||||
if (value && password && value.trim() !== password.trim()) {
|
||||
callback(new Error('两次密码不一致'))
|
||||
}
|
||||
callback()
|
||||
},
|
||||
|
||||
handleUsernameCheck (rule, value, callback) {
|
||||
let username = this.username.trim()
|
||||
if (username.length) {
|
||||
if (username.length > 10) {
|
||||
callback(new Error('用户名不能超过10个字符'))
|
||||
} else if (username.length < 4) {
|
||||
callback(new Error('用户名不能少于4个字符'))
|
||||
} else {
|
||||
this.$get(`user/check/${username}`).then((r) => {
|
||||
if (r.data) {
|
||||
callback()
|
||||
} else {
|
||||
this.validateStatus = 'error'
|
||||
callback(new Error('抱歉,该用户名已存在'))
|
||||
}
|
||||
})
|
||||
}
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
},
|
||||
|
||||
// handlePhoneCheck (rule, value, callback) {
|
||||
// callback()
|
||||
// },
|
||||
|
||||
handlePasswordInputClick () {
|
||||
if (!this.isMobile()) {
|
||||
this.state.passwordLevelChecked = true
|
||||
return
|
||||
}
|
||||
this.state.passwordLevelChecked = false
|
||||
},
|
||||
|
||||
handleSubmit () {
|
||||
this.form.validateFields((err, values) => {
|
||||
if (!err) {
|
||||
this.$post('regist', {
|
||||
username: this.username,
|
||||
password: this.password
|
||||
}).then(() => {
|
||||
this.$message.success('注册成功')
|
||||
this.returnLogin()
|
||||
}).catch(() => {
|
||||
this.$message.error('抱歉,注册账号失败')
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
// getCaptcha (e) {
|
||||
// e.preventDefault()
|
||||
// let that = this
|
||||
//
|
||||
// this.form.validateFields(['mobile'], {force: true},
|
||||
// (err, values) => {
|
||||
// if (!err) {
|
||||
// this.state.smsSendBtn = true
|
||||
//
|
||||
// let interval = window.setInterval(() => {
|
||||
// if (that.state.time-- <= 0) {
|
||||
// that.state.time = 60
|
||||
// that.state.smsSendBtn = false
|
||||
// window.clearInterval(interval)
|
||||
// }
|
||||
// }, 1000)
|
||||
// }
|
||||
// }
|
||||
// )
|
||||
// },
|
||||
returnLogin () {
|
||||
this.$emit('regist', 'Login')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="less">
|
||||
.user-register {
|
||||
&.error {
|
||||
color: #ff0000;
|
||||
}
|
||||
&.warning {
|
||||
color: #ff7e05;
|
||||
}
|
||||
&.success {
|
||||
color: #52c41a;
|
||||
}
|
||||
}
|
||||
.user-layout-register {
|
||||
.ant-input-group-addon {
|
||||
&:first-child {
|
||||
background-color: #fff;
|
||||
}
|
||||
}
|
||||
& > h3 {
|
||||
font-size: 16px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.getCaptcha {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 40px;
|
||||
}
|
||||
.register-button {
|
||||
width: 50%;
|
||||
}
|
||||
.login {
|
||||
float: right;
|
||||
line-height: 40px;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,152 @@
|
|||
<template>
|
||||
<a-card :bordered="false" class="card-area">
|
||||
<div>
|
||||
<div class="alert">
|
||||
<a-alert type="success" :show-icon="true">
|
||||
<div slot="message">
|
||||
共追踪到 {{dataSource.length}} 条近期HTTP请求记录
|
||||
<a style="margin-left: 24px" @click="search">点击刷新</a>
|
||||
</div>
|
||||
</a-alert>
|
||||
</div>
|
||||
<!-- 表格区域 -->
|
||||
<a-table :columns="columns"
|
||||
:dataSource="dataSource"
|
||||
:pagination="pagination"
|
||||
:loading="loading"
|
||||
:scroll="{ x: 900 }"
|
||||
@change="handleTableChange">
|
||||
</a-table>
|
||||
</div>
|
||||
</a-card>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import moment from 'moment'
|
||||
moment.locale('zh-cn')
|
||||
|
||||
export default {
|
||||
data () {
|
||||
return {
|
||||
advanced: false,
|
||||
dataSource: [],
|
||||
pagination: {
|
||||
defaultPageSize: 10,
|
||||
defaultCurrent: 1,
|
||||
pageSizeOptions: ['10', '20', '30', '40', '100'],
|
||||
showQuickJumper: true,
|
||||
showSizeChanger: true,
|
||||
showTotal: (total, range) => `显示 ${range[0]} ~ ${range[1]} 条记录,共 ${total} 条记录`
|
||||
},
|
||||
loading: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
columns () {
|
||||
return [{
|
||||
title: '请求时间',
|
||||
dataIndex: 'timestamp',
|
||||
customRender: (text, row, index) => {
|
||||
return moment(text).format('YYYY-MM-DD HH:mm:ss')
|
||||
}
|
||||
}, {
|
||||
title: '请求方法',
|
||||
dataIndex: 'request.method',
|
||||
customRender: (text, row, index) => {
|
||||
switch (text) {
|
||||
case 'GET':
|
||||
return <a-tag color="#87d068">{text}</a-tag>
|
||||
case 'POST':
|
||||
return <a-tag color="#2db7f5">{text}</a-tag>
|
||||
case 'PUT':
|
||||
return <a-tag color="#ffba5a">{text}</a-tag>
|
||||
case 'DELETE':
|
||||
return <a-tag color="#f50">{text}</a-tag>
|
||||
default:
|
||||
return text
|
||||
}
|
||||
},
|
||||
filters: [
|
||||
{ text: 'GET', value: 'GET' },
|
||||
{ text: 'POST', value: 'POST' },
|
||||
{ text: 'PUT', value: 'PUT' },
|
||||
{ text: 'DELETE', value: 'DELETE' }
|
||||
],
|
||||
filterMultiple: true,
|
||||
onFilter: (value, record) => record.request.method.includes(value)
|
||||
}, {
|
||||
title: '请求URL',
|
||||
dataIndex: 'request.uri',
|
||||
customRender: (text, row, index) => {
|
||||
return text.split('?')[0]
|
||||
}
|
||||
}, {
|
||||
title: '响应状态',
|
||||
dataIndex: 'response.status',
|
||||
customRender: (text, row, index) => {
|
||||
if (text < 200) {
|
||||
return <a-tag color="pink">{text}</a-tag>
|
||||
} else if (text < 201) {
|
||||
return <a-tag color="green">{text}</a-tag>
|
||||
} else if (text < 399) {
|
||||
return <a-tag color="cyan">{text}</a-tag>
|
||||
} else if (text < 403) {
|
||||
return <a-tag color="orange">{text}</a-tag>
|
||||
} else if (text < 501) {
|
||||
return <a-tag color="red">{text}</a-tag>
|
||||
} else {
|
||||
return text
|
||||
}
|
||||
}
|
||||
}, {
|
||||
title: '请求耗时',
|
||||
dataIndex: 'timeTaken',
|
||||
customRender: (text, row, index) => {
|
||||
if (text < 500) {
|
||||
return <a-tag color="green">{text} ms</a-tag>
|
||||
} else if (text < 1000) {
|
||||
return <a-tag color="cyan">{text} ms</a-tag>
|
||||
} else if (text < 1500) {
|
||||
return <a-tag color="orange">{text} ms</a-tag>
|
||||
} else {
|
||||
return <a-tag color="red">{text} ms</a-tag>
|
||||
}
|
||||
}
|
||||
}]
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
this.fetch()
|
||||
},
|
||||
methods: {
|
||||
search () {
|
||||
this.fetch()
|
||||
},
|
||||
handleTableChange (pagination, filters, sorter) {
|
||||
this.fetch()
|
||||
},
|
||||
fetch () {
|
||||
this.loading = true
|
||||
this.$get('actuator/httptrace').then((r) => {
|
||||
let data = r.data
|
||||
this.loading = false
|
||||
let filterData = []
|
||||
for (let d of data.traces) {
|
||||
if (d.request.method !== 'OPTIONS' &&
|
||||
d.request.uri.indexOf('httptrace') === -1) {
|
||||
filterData.push(d)
|
||||
}
|
||||
}
|
||||
this.dataSource = filterData
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@import "../../../static/less/Common";
|
||||
.alert {
|
||||
margin-bottom: .5rem;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,223 @@
|
|||
<template>
|
||||
<a-skeleton active :loading="loading" :paragraph="{rows: 17}">
|
||||
<div class="jvm-info">
|
||||
<div class="alert">
|
||||
<a-alert type="success" :show-icon="true">
|
||||
<div slot="message">
|
||||
数据获取时间 {{this.time}}
|
||||
<a style="margin-left: 24px" @click="create">点击刷新</a>
|
||||
</div>
|
||||
</a-alert>
|
||||
</div>
|
||||
<table>
|
||||
<tr>
|
||||
<th>参数</th>
|
||||
<th>描述</th>
|
||||
<th>当前值</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a-tag color="purple">jvm.memory.max</a-tag></td>
|
||||
<td>JVM 最大内存</td>
|
||||
<td>{{jvm.memory.max}} MB</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a-tag color="purple">jvm.memory.committed</a-tag></td>
|
||||
<td>JVM 可用内存</td>
|
||||
<td>{{jvm.memory.committed}} MB</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a-tag color="purple">jvm.memory.used</a-tag></td>
|
||||
<td>JVM 已用内存</td>
|
||||
<td>{{jvm.memory.used}} MB</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a-tag color="cyan">jvm.buffer.memory.used</a-tag></td>
|
||||
<td>JVM 缓冲区已用内存</td>
|
||||
<td>{{jvm.buffer.memory.used}} MB</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a-tag color="cyan">jvm.buffer.count</a-tag></td>
|
||||
<td>当前缓冲区数量</td>
|
||||
<td>{{jvm.buffer.count}} 个</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a-tag color="green">jvm.threads.daemon</a-tag></td>
|
||||
<td>JVM 守护线程数量</td>
|
||||
<td>{{jvm.threads.daemon}} 个</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a-tag color="green">jvm.threads.live</a-tag></td>
|
||||
<td>JVM 当前活跃线程数量</td>
|
||||
<td>{{jvm.threads.live}} 个</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a-tag color="green">jvm.threads.peak</a-tag></td>
|
||||
<td>JVM 峰值线程数量</td>
|
||||
<td>{{jvm.threads.peak}} 个</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a-tag color="orange">jvm.classes.loaded</a-tag></td>
|
||||
<td>JVM 已加载 Class 数量</td>
|
||||
<td>{{jvm.classes.loaded}} 个</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a-tag color="orange">jvm.classes.unloaded</a-tag></td>
|
||||
<td>JVM 未加载 Class 数量</td>
|
||||
<td>{{jvm.classes.unloaded}} 个</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a-tag color="pink">jvm.gc.memory.allocated</a-tag></td>
|
||||
<td>GC 时, 年轻代分配的内存空间</td>
|
||||
<td>{{jvm.gc.memory.allocated}} MB</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a-tag color="pink">jvm.gc.memory.promoted</a-tag></td>
|
||||
<td>GC 时, 老年代分配的内存空间</td>
|
||||
<td>{{jvm.gc.memory.promoted}} MB</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a-tag color="pink">jvm.gc.max.data.size</a-tag></td>
|
||||
<td>GC 时, 老年代的最大内存空间</td>
|
||||
<td>{{jvm.gc.maxDataSize}} MB</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a-tag color="pink">jvm.gc.live.data.size</a-tag></td>
|
||||
<td>FullGC 时, 老年代的内存空间</td>
|
||||
<td>{{jvm.gc.liveDataSize}} MB</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a-tag color="blue">jvm.gc.pause.count</a-tag></td>
|
||||
<td>系统启动以来GC 次数</td>
|
||||
<td>{{jvm.gc.pause.count}} 次</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a-tag color="blue">jvm.gc.pause.totalTime</a-tag></td>
|
||||
<td>系统启动以来GC 总耗时</td>
|
||||
<td>{{jvm.gc.pause.totalTime}} 秒</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</a-skeleton>
|
||||
</template>
|
||||
<script>
|
||||
import axios from 'axios'
|
||||
import moment from 'moment'
|
||||
moment.locale('zh-cn')
|
||||
|
||||
export default {
|
||||
data () {
|
||||
return {
|
||||
time: '',
|
||||
loading: true,
|
||||
jvm: {
|
||||
memory: {
|
||||
max: 0,
|
||||
committed: 0,
|
||||
used: 0
|
||||
},
|
||||
buffer: {
|
||||
memory: {
|
||||
used: 0
|
||||
},
|
||||
count: 0
|
||||
},
|
||||
threads: {
|
||||
daemon: 0,
|
||||
live: 0,
|
||||
peak: 0
|
||||
},
|
||||
classes: {
|
||||
loaded: 0,
|
||||
unloaded: 0
|
||||
},
|
||||
gc: {
|
||||
memory: {
|
||||
allocated: 0,
|
||||
promoted: 0
|
||||
},
|
||||
maxDataSize: 0,
|
||||
liveDataSize: 0,
|
||||
pause: {
|
||||
totalTime: 0,
|
||||
count: 0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
this.create()
|
||||
},
|
||||
methods: {
|
||||
create () {
|
||||
this.time = moment().format('YYYY年MM月DD日 HH时mm分ss秒')
|
||||
axios.all([
|
||||
this.$get('actuator/metrics/jvm.memory.max'),
|
||||
this.$get('actuator/metrics/jvm.memory.committed'),
|
||||
this.$get('actuator/metrics/jvm.memory.used'),
|
||||
this.$get('actuator/metrics/jvm.buffer.memory.used'),
|
||||
this.$get('actuator/metrics/jvm.buffer.count'),
|
||||
this.$get('actuator/metrics/jvm.threads.daemon'),
|
||||
this.$get('actuator/metrics/jvm.threads.live'),
|
||||
this.$get('actuator/metrics/jvm.threads.peak'),
|
||||
this.$get('actuator/metrics/jvm.classes.loaded'),
|
||||
this.$get('actuator/metrics/jvm.classes.unloaded'),
|
||||
this.$get('actuator/metrics/jvm.gc.memory.allocated'),
|
||||
this.$get('actuator/metrics/jvm.gc.memory.promoted'),
|
||||
this.$get('actuator/metrics/jvm.gc.max.data.size'),
|
||||
this.$get('actuator/metrics/jvm.gc.live.data.size'),
|
||||
this.$get('actuator/metrics/jvm.gc.pause')
|
||||
]).then((r) => {
|
||||
this.jvm.memory.max = this.convert(r[0].data.measurements[0].value)
|
||||
this.jvm.memory.committed = this.convert(r[1].data.measurements[0].value)
|
||||
this.jvm.memory.used = this.convert(r[2].data.measurements[0].value)
|
||||
this.jvm.buffer.memory.used = this.convert(r[3].data.measurements[0].value)
|
||||
this.jvm.buffer.count = r[4].data.measurements[0].value
|
||||
this.jvm.threads.daemon = r[5].data.measurements[0].value
|
||||
this.jvm.threads.live = r[6].data.measurements[0].value
|
||||
this.jvm.threads.peak = r[7].data.measurements[0].value
|
||||
this.jvm.classes.loaded = r[8].data.measurements[0].value
|
||||
this.jvm.classes.unloaded = r[9].data.measurements[0].value
|
||||
this.jvm.gc.memory.allocated = this.convert(r[10].data.measurements[0].value)
|
||||
this.jvm.gc.memory.promoted = this.convert(r[11].data.measurements[0].value)
|
||||
this.jvm.gc.maxDataSize = this.convert(r[12].data.measurements[0].value)
|
||||
this.jvm.gc.liveDataSize = this.convert(r[13].data.measurements[0].value)
|
||||
this.jvm.gc.pause.count = r[14].data.measurements[0].value
|
||||
this.jvm.gc.pause.totalTime = r[14].data.measurements[1].value
|
||||
this.loading = false
|
||||
}).catch((r) => {
|
||||
console.error(r)
|
||||
this.$message.error('获取JVM信息失败')
|
||||
})
|
||||
},
|
||||
convert (value) {
|
||||
return Number(value / 1048576).toFixed(3)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="less">
|
||||
.jvm-info {
|
||||
width: 100%;
|
||||
table {
|
||||
width: 100%;
|
||||
tr {
|
||||
line-height: 1.5rem;
|
||||
border-bottom: 1px solid #f1f1f1;
|
||||
th {
|
||||
background: #fafafa;
|
||||
padding: .5rem;
|
||||
}
|
||||
td {
|
||||
padding: .5rem;
|
||||
.ant-tag {
|
||||
font-size: .8rem !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.alert {
|
||||
margin-bottom: .5rem;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,151 @@
|
|||
<template>
|
||||
<a-card :bordered="false" class="card-area">
|
||||
<div :class="advanced ? 'search' : null">
|
||||
<!-- 搜索区域 -->
|
||||
<a-form layout="horizontal">
|
||||
<div :class="advanced ? null: 'fold'">
|
||||
<a-row >
|
||||
<a-col :md="12" :sm="24" >
|
||||
<a-form-item
|
||||
label="用户名"
|
||||
:labelCol="{span: 5}"
|
||||
:wrapperCol="{span: 18, offset: 1}">
|
||||
<a-input v-model="queryParams.username"/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
<span style="float: right; margin-top: 3px;">
|
||||
<a-button type="primary" @click="search">查询</a-button>
|
||||
<a-button style="margin-left: 8px" @click="reset">重置</a-button>
|
||||
</span>
|
||||
</a-form>
|
||||
</div>
|
||||
<div>
|
||||
<!-- 表格区域 -->
|
||||
<a-table :columns="columns"
|
||||
:dataSource="dataSource"
|
||||
:pagination="pagination"
|
||||
:loading="loading"
|
||||
:scroll="{ x: 900 }"
|
||||
@change="handleTableChange">
|
||||
<template slot="username" slot-scope="text, record">
|
||||
<template v-if="record.id === user.id">
|
||||
{{record.username}} <a-tag color="pink">current</a-tag>
|
||||
</template>
|
||||
<template v-else>
|
||||
{{record.username}}
|
||||
</template>
|
||||
</template>
|
||||
<template slot="operation" slot-scope="text, record">
|
||||
<a-icon v-hasPermission="'user:kickout'" type="poweroff" style="color: #f95476" @click="kickout(record)" title="踢出"></a-icon>
|
||||
<a-badge v-hasNoPermission="'user:kickout'" status="warning" text="无权限"></a-badge>
|
||||
</template>
|
||||
</a-table>
|
||||
</div>
|
||||
</a-card>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {mapState} from 'vuex'
|
||||
|
||||
export default {
|
||||
name: 'Online',
|
||||
data () {
|
||||
return {
|
||||
advanced: false,
|
||||
dataSource: [],
|
||||
queryParams: {},
|
||||
pagination: {
|
||||
defaultPageSize: 10000000,
|
||||
hideOnSinglePage: true,
|
||||
indentSize: 100
|
||||
},
|
||||
loading: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
columns () {
|
||||
return [{
|
||||
title: '用户名',
|
||||
dataIndex: 'username',
|
||||
scopedSlots: { customRender: 'username' }
|
||||
}, {
|
||||
title: '登录时间',
|
||||
dataIndex: 'loginTime'
|
||||
}, {
|
||||
title: '登录IP',
|
||||
dataIndex: 'ip'
|
||||
}, {
|
||||
title: '登录地点',
|
||||
dataIndex: 'loginAddress'
|
||||
}, {
|
||||
title: '操作',
|
||||
dataIndex: 'operation',
|
||||
scopedSlots: { customRender: 'operation' },
|
||||
fixed: 'right',
|
||||
width: 120
|
||||
}]
|
||||
},
|
||||
...mapState({
|
||||
user: state => state.account.user
|
||||
})
|
||||
},
|
||||
mounted () {
|
||||
this.fetch()
|
||||
},
|
||||
methods: {
|
||||
search () {
|
||||
this.fetch({
|
||||
...this.queryParams
|
||||
})
|
||||
},
|
||||
kickout (record) {
|
||||
let that = this
|
||||
this.$confirm({
|
||||
title: '确定踢出该用户?',
|
||||
content: '当您点击确定按钮后,该用户的登录将会马上失效',
|
||||
centered: true,
|
||||
onOk () {
|
||||
that.$delete(`kickout/${record.id}`).then(() => {
|
||||
that.$message.success('踢出用户成功')
|
||||
if (that.user.id === record.id) {
|
||||
that.$db.clear()
|
||||
location.reload()
|
||||
} else {
|
||||
that.search()
|
||||
}
|
||||
}).catch((r) => {
|
||||
console.error(r)
|
||||
that.$message.error('踢出用户失败')
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
reset () {
|
||||
// 重置查询参数
|
||||
this.queryParams = {}
|
||||
this.fetch()
|
||||
},
|
||||
handleTableChange (pagination, filters, sorter) {
|
||||
this.fetch({
|
||||
...this.queryParams
|
||||
})
|
||||
},
|
||||
fetch (params = {}) {
|
||||
this.loading = true
|
||||
this.$get('online', {
|
||||
...params
|
||||
}).then((r) => {
|
||||
let data = r.data.data
|
||||
this.loading = false
|
||||
this.dataSource = data
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@import "../../../static/less/Common";
|
||||
</style>
|
|
@ -0,0 +1,212 @@
|
|||
<template>
|
||||
<div style="width: 100%;margin-top: 1rem">
|
||||
<a-row :gutter="8">
|
||||
<a-col :span="12">
|
||||
<apexchart ref="memoryInfo" type=area height=350 :options="memory.chartOptions" :series="memory.series" />
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<apexchart ref="keySize" type=area height=350 :options="key.chartOptions" :series="key.series" />
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-row :gutter="8">
|
||||
<a-divider orientation="left">Redis详细信息</a-divider>
|
||||
<table style="border-bottom: 1px solid #f1f1f1;">
|
||||
<tr v-for="(info, index) in redisInfo" :key="index" style="border-top: 1px solid #f1f1f1;">
|
||||
<td style="padding: .7rem 1rem">{{info.key}}</td>
|
||||
<td style="padding: .7rem 1rem">{{info.description}}</td>
|
||||
<td style="padding: .7rem 1rem">{{info.value}}</td>
|
||||
</tr>
|
||||
</table>
|
||||
</a-row>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import axios from 'axios'
|
||||
import moment from 'moment'
|
||||
|
||||
export default {
|
||||
name: 'RedisInfo',
|
||||
data () {
|
||||
return {
|
||||
loading: true,
|
||||
memory: {
|
||||
series: [],
|
||||
chartOptions: {
|
||||
chart: {
|
||||
animations: {
|
||||
enabled: true,
|
||||
easing: 'linear',
|
||||
dynamicAnimation: {
|
||||
speed: 3000
|
||||
}
|
||||
},
|
||||
toolbar: {
|
||||
show: false
|
||||
},
|
||||
zoom: {
|
||||
enabled: false
|
||||
}
|
||||
},
|
||||
dataLabels: {
|
||||
enabled: false
|
||||
},
|
||||
stroke: {
|
||||
curve: 'smooth'
|
||||
},
|
||||
title: {
|
||||
text: 'Redis内存实时占用情况(kb)',
|
||||
align: 'left'
|
||||
},
|
||||
markers: {
|
||||
size: 0
|
||||
},
|
||||
xaxis: {
|
||||
},
|
||||
yaxis: {},
|
||||
legend: {
|
||||
show: false
|
||||
}
|
||||
},
|
||||
data: [],
|
||||
xdata: []
|
||||
},
|
||||
key: {
|
||||
series: [],
|
||||
chartOptions: {
|
||||
chart: {
|
||||
animations: {
|
||||
enabled: true,
|
||||
easing: 'linear',
|
||||
dynamicAnimation: {
|
||||
speed: 3000
|
||||
}
|
||||
},
|
||||
toolbar: {
|
||||
show: false
|
||||
},
|
||||
zoom: {
|
||||
enabled: false
|
||||
}
|
||||
},
|
||||
dataLabels: {
|
||||
enabled: false
|
||||
},
|
||||
colors: ['#f5564e'],
|
||||
stroke: {
|
||||
curve: 'smooth'
|
||||
},
|
||||
title: {
|
||||
text: 'Redis key实时数量(个)',
|
||||
align: 'left'
|
||||
},
|
||||
markers: {
|
||||
size: 0
|
||||
},
|
||||
xaxis: {
|
||||
},
|
||||
yaxis: {},
|
||||
legend: {
|
||||
show: false
|
||||
}
|
||||
},
|
||||
data: [],
|
||||
xdata: []
|
||||
},
|
||||
redisInfo: [],
|
||||
timer: null
|
||||
}
|
||||
},
|
||||
beforeDestroy () {
|
||||
if (this.timer) {
|
||||
clearInterval(this.timer)
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
let minMemory = 1e10
|
||||
let minSize = 1e10
|
||||
let maxMemory = -1e10
|
||||
let maxSize = -1e10
|
||||
this.timer = setInterval(() => {
|
||||
if (this.$route.path.indexOf('redis') !== -1) {
|
||||
axios.all([
|
||||
this.$get('redis/keysSize'),
|
||||
this.$get('redis/memoryInfo')
|
||||
]).then((r) => {
|
||||
let currentMemory = r[1].data.used_memory / 1000
|
||||
let currentSize = r[0].data.dbSize
|
||||
if (currentMemory < minMemory) {
|
||||
minMemory = currentMemory
|
||||
}
|
||||
if (currentMemory > maxMemory) {
|
||||
maxMemory = currentMemory
|
||||
}
|
||||
if (currentSize < minSize) {
|
||||
minSize = currentSize
|
||||
}
|
||||
if (currentSize > maxSize) {
|
||||
maxSize = currentSize
|
||||
}
|
||||
let time = moment().format('hh:mm:ss')
|
||||
this.memory.data.push(currentMemory)
|
||||
this.memory.xdata.push(time)
|
||||
this.key.data.push(currentSize)
|
||||
this.key.xdata.push(time)
|
||||
if (this.memory.data.length >= 6) {
|
||||
this.memory.data.shift()
|
||||
this.memory.xdata.shift()
|
||||
}
|
||||
if (this.key.data.length >= 6) {
|
||||
this.key.data.shift()
|
||||
this.key.xdata.shift()
|
||||
}
|
||||
this.$refs.memoryInfo.updateSeries([
|
||||
{
|
||||
name: '内存(kb)',
|
||||
data: this.memory.data.slice()
|
||||
}
|
||||
])
|
||||
this.$refs.memoryInfo.updateOptions({
|
||||
xaxis: {
|
||||
categories: this.memory.xdata.slice()
|
||||
},
|
||||
yaxis: {
|
||||
min: minMemory,
|
||||
max: maxMemory
|
||||
}
|
||||
}, true, true)
|
||||
this.$refs.keySize.updateSeries([
|
||||
{
|
||||
name: 'key数量',
|
||||
data: this.key.data.slice()
|
||||
}
|
||||
])
|
||||
this.$refs.keySize.updateOptions({
|
||||
xaxis: {
|
||||
categories: this.key.xdata.slice()
|
||||
},
|
||||
yaxis: {
|
||||
min: minSize - 2,
|
||||
max: maxSize + 2
|
||||
}
|
||||
}, true, true)
|
||||
if (this.loading) {
|
||||
this.loading = false
|
||||
}
|
||||
}).catch((r) => {
|
||||
console.error(r)
|
||||
this.$message.error('获取Redis信息失败')
|
||||
if (this.timer) {
|
||||
clearInterval(this.timer)
|
||||
}
|
||||
})
|
||||
}
|
||||
}, 3000)
|
||||
this.$get('redis/info').then((r) => {
|
||||
this.redisInfo = r.data.data
|
||||
})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
|
||||
</style>
|
|
@ -0,0 +1,13 @@
|
|||
<template>
|
||||
<div>
|
||||
<div>Redis终端</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
name: 'RedisTerminal'
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
|
||||
</style>
|
|
@ -0,0 +1,124 @@
|
|||
<template>
|
||||
<a-skeleton active :loading="loading" :paragraph="{rows: 17}">
|
||||
<div class="jvm-info">
|
||||
<div class="alert">
|
||||
<a-alert type="success" :show-icon="true">
|
||||
<div slot="message">
|
||||
数据获取时间 {{this.time}}
|
||||
<a style="margin-left: 24px" @click="create">点击刷新</a>
|
||||
</div>
|
||||
</a-alert>
|
||||
</div>
|
||||
<table>
|
||||
<tr>
|
||||
<th>参数</th>
|
||||
<th>描述</th>
|
||||
<th>当前值</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a-tag color="green">system.cpu.count</a-tag></td>
|
||||
<td>CPU 数量</td>
|
||||
<td>{{system.cpu.count}} 核</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a-tag color="green">system.cpu.usage</a-tag></td>
|
||||
<td>系统 CPU 使用率</td>
|
||||
<td>{{system.cpu.usage}} %</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a-tag color="purple">process.start.time</a-tag></td>
|
||||
<td>应用启动时间点</td>
|
||||
<td>{{system.process.startTime}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a-tag color="purple">process.uptime</a-tag></td>
|
||||
<td>应用已运行时间</td>
|
||||
<td>{{system.process.uptime}} 秒</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a-tag color="purple">process.cpu.usage</a-tag></td>
|
||||
<td>当前应用 CPU 使用率</td>
|
||||
<td>{{system.process.cpuUsage}} %</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</a-skeleton>
|
||||
</template>
|
||||
<script>
|
||||
import axios from 'axios'
|
||||
import moment from 'moment'
|
||||
moment.locale('zh-cn')
|
||||
|
||||
export default {
|
||||
data () {
|
||||
return {
|
||||
time: '',
|
||||
loading: true,
|
||||
system: {
|
||||
cpu: {
|
||||
count: 0,
|
||||
usage: 0
|
||||
},
|
||||
process: {
|
||||
cpuUsage: 0,
|
||||
uptime: 0,
|
||||
startTime: 0
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
this.create()
|
||||
},
|
||||
methods: {
|
||||
create () {
|
||||
this.time = moment().format('YYYY年MM月DD日 HH时mm分ss秒')
|
||||
axios.all([
|
||||
this.$get('actuator/metrics/system.cpu.count'),
|
||||
this.$get('actuator/metrics/system.cpu.usage'),
|
||||
this.$get('actuator/metrics/process.uptime'),
|
||||
this.$get('actuator/metrics/process.start.time'),
|
||||
this.$get('actuator/metrics/process.cpu.usage')
|
||||
]).then((r) => {
|
||||
this.system.cpu.count = r[0].data.measurements[0].value
|
||||
this.system.cpu.usage = this.convert(r[1].data.measurements[0].value)
|
||||
this.system.process.uptime = r[2].data.measurements[0].value
|
||||
this.system.process.startTime = moment(r[3].data.measurements[0].value * 1000).format('YYYY-MM-DD HH:mm:ss')
|
||||
this.system.process.cpuUsage = this.convert(r[4].data.measurements[0].value)
|
||||
this.loading = false
|
||||
}).catch((r) => {
|
||||
console.error(r)
|
||||
this.$message.error('获取服务器信息失败')
|
||||
})
|
||||
},
|
||||
convert (value) {
|
||||
return Number(value * 100).toFixed(2)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="less">
|
||||
.jvm-info {
|
||||
width: 100%;
|
||||
table {
|
||||
width: 100%;
|
||||
tr {
|
||||
line-height: 1.5rem;
|
||||
border-bottom: 1px solid #f1f1f1;
|
||||
th {
|
||||
background: #fafafa;
|
||||
padding: .5rem;
|
||||
}
|
||||
td {
|
||||
padding: .5rem;
|
||||
.ant-tag {
|
||||
font-size: .8rem !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.alert {
|
||||
margin-bottom: .5rem;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,300 @@
|
|||
<template>
|
||||
<a-card :bordered="false" class="card-area">
|
||||
<a-form layout="horizontal">
|
||||
<div :class="advanced ? null: 'fold'">
|
||||
<a-row>
|
||||
<a-col :md="12" :sm="24" >
|
||||
<a-form-item
|
||||
label="操作人"
|
||||
:labelCol="{span: 5}"
|
||||
:wrapperCol="{span: 18, offset: 1}">
|
||||
<a-input v-model="queryParams.username"/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :md="12" :sm="24" >
|
||||
<a-form-item
|
||||
label="操作描述"
|
||||
:labelCol="{span: 5}"
|
||||
:wrapperCol="{span: 18, offset: 1}">
|
||||
<a-input v-model="queryParams.operation"/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-row v-if="advanced">
|
||||
<a-col :md="12" :sm="24" >
|
||||
<a-form-item
|
||||
label="操作地点"
|
||||
:labelCol="{span: 5}"
|
||||
:wrapperCol="{span: 18, offset: 1}">
|
||||
<a-input v-model="queryParams.location"/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :md="12" :sm="24" >
|
||||
<a-form-item
|
||||
label="操作时间"
|
||||
:labelCol="{span: 5}"
|
||||
:wrapperCol="{span: 18, offset: 1}">
|
||||
<range-date @change="handleDateChange" ref="createTime" style="width: 100%"></range-date>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
<span style="float: right; margin-top: 3px;">
|
||||
<a-button type="primary" @click="search">查询</a-button>
|
||||
<a-button style="margin-left: 8px" @click="reset">重置</a-button>
|
||||
<a @click="toggleAdvanced" style="margin-left: 8px">
|
||||
{{advanced ? '收起' : '展开'}}
|
||||
<a-icon :type="advanced ? 'up' : 'down'" />
|
||||
</a>
|
||||
</span>
|
||||
</a-form>
|
||||
<div>
|
||||
<div class="operator">
|
||||
<a-button v-hasPermission="'log:delete'" @click="batchDelete" type="primary" ghost>删除</a-button>
|
||||
<a-dropdown v-hasPermission="'log:export'">
|
||||
<a-menu slot="overlay">
|
||||
<a-menu-item key="export-data" @click="exprotExccel">导出Excel</a-menu-item>
|
||||
</a-menu>
|
||||
<a-button>
|
||||
更多操作 <a-icon type="down" />
|
||||
</a-button>
|
||||
</a-dropdown>
|
||||
</div>
|
||||
<!-- 表格区域 -->
|
||||
<a-table ref="TableInfo"
|
||||
:columns="columns"
|
||||
:dataSource="dataSource"
|
||||
:pagination="pagination"
|
||||
:loading="loading"
|
||||
:rowSelection="{selectedRowKeys: selectedRowKeys, onChange: onSelectChange}"
|
||||
@change="handleTableChange" :scroll="{ x: 1400 }">
|
||||
<template slot="method" slot-scope="text, record">
|
||||
<a-popover placement="topLeft">
|
||||
<template slot="content">
|
||||
<div>{{text}}</div>
|
||||
</template>
|
||||
<p style="width: 200px;margin-bottom: 0">{{text}}</p>
|
||||
</a-popover>
|
||||
</template>
|
||||
<template slot="params" slot-scope="text, record">
|
||||
<a-popover placement="topLeft">
|
||||
<template slot="content">
|
||||
<div style="max-width: 300px;">{{text}}</div>
|
||||
</template>
|
||||
<p style="width: 100px;margin-bottom: 0">{{text}}</p>
|
||||
</a-popover>
|
||||
</template>
|
||||
</a-table>
|
||||
</div>
|
||||
</a-card>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import RangeDate from '@/components/datetime/RangeDate'
|
||||
|
||||
export default {
|
||||
name: 'SystemLog',
|
||||
components: {RangeDate},
|
||||
data () {
|
||||
return {
|
||||
advanced: false,
|
||||
dataSource: [],
|
||||
sortedInfo: null,
|
||||
paginationInfo: null,
|
||||
selectedRowKeys: [],
|
||||
queryParams: {},
|
||||
pagination: {
|
||||
pageSizeOptions: ['10', '20', '30', '40', '100'],
|
||||
defaultCurrent: 1,
|
||||
defaultPageSize: 10,
|
||||
showQuickJumper: true,
|
||||
showSizeChanger: true,
|
||||
showTotal: (total, range) => `显示 ${range[0]} ~ ${range[1]} 条记录,共 ${total} 条记录`
|
||||
},
|
||||
loading: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
columns () {
|
||||
let { sortedInfo } = this
|
||||
sortedInfo = sortedInfo || {}
|
||||
return [{
|
||||
title: '操作人',
|
||||
dataIndex: 'username'
|
||||
}, {
|
||||
title: '操作描述',
|
||||
dataIndex: 'operation'
|
||||
}, {
|
||||
title: '耗时',
|
||||
dataIndex: 'time',
|
||||
customRender: (text, row, index) => {
|
||||
if (text < 500) {
|
||||
return <a-tag color="green">{text} ms</a-tag>
|
||||
} else if (text < 1000) {
|
||||
return <a-tag color="cyan">{text} ms</a-tag>
|
||||
} else if (text < 1500) {
|
||||
return <a-tag color="orange">{text} ms</a-tag>
|
||||
} else {
|
||||
return <a-tag color="red">{text} ms</a-tag>
|
||||
}
|
||||
},
|
||||
sorter: true,
|
||||
sortOrder: sortedInfo.columnKey === 'time' && sortedInfo.order
|
||||
}, {
|
||||
title: '执行方法',
|
||||
dataIndex: 'method',
|
||||
scopedSlots: { customRender: 'method' }
|
||||
}, {
|
||||
title: '方法参数',
|
||||
dataIndex: 'params',
|
||||
scopedSlots: { customRender: 'params' },
|
||||
width: 100
|
||||
}, {
|
||||
title: 'IP地址',
|
||||
dataIndex: 'ip'
|
||||
}, {
|
||||
title: '操作地点',
|
||||
dataIndex: 'location'
|
||||
}, {
|
||||
title: '操作时间',
|
||||
dataIndex: 'createTime',
|
||||
sorter: true,
|
||||
sortOrder: sortedInfo.columnKey === 'createTime' && sortedInfo.order
|
||||
}]
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
this.fetch()
|
||||
},
|
||||
methods: {
|
||||
onSelectChange (selectedRowKeys) {
|
||||
this.selectedRowKeys = selectedRowKeys
|
||||
},
|
||||
toggleAdvanced () {
|
||||
this.advanced = !this.advanced
|
||||
if (!this.advanced) {
|
||||
this.queryParams.createTimeFrom = ''
|
||||
this.queryParams.createTimeTo = ''
|
||||
this.queryParams.location = ''
|
||||
}
|
||||
},
|
||||
handleDateChange (value) {
|
||||
if (value) {
|
||||
this.queryParams.createTimeFrom = value[0]
|
||||
this.queryParams.createTimeTo = value[1]
|
||||
}
|
||||
},
|
||||
batchDelete () {
|
||||
if (!this.selectedRowKeys.length) {
|
||||
this.$message.warning('请选择需要删除的记录')
|
||||
return
|
||||
}
|
||||
let that = this
|
||||
this.$confirm({
|
||||
title: '确定删除所选中的记录?',
|
||||
content: '当您点击确定按钮后,这些记录将会被彻底删除',
|
||||
centered: true,
|
||||
onOk () {
|
||||
let ids = []
|
||||
for (let key of that.selectedRowKeys) {
|
||||
ids.push(that.dataSource[key].id)
|
||||
}
|
||||
that.$delete('log/' + ids.join(',')).then(() => {
|
||||
that.$message.success('删除成功')
|
||||
that.selectedRowKeys = []
|
||||
that.search()
|
||||
})
|
||||
},
|
||||
onCancel () {
|
||||
that.selectedRowKeys = []
|
||||
}
|
||||
})
|
||||
},
|
||||
exprotExccel () {
|
||||
let {sortedInfo} = this
|
||||
let sortField, sortOrder
|
||||
// 获取当前列的排序和列的过滤规则
|
||||
if (sortedInfo) {
|
||||
sortField = sortedInfo.field
|
||||
sortOrder = sortedInfo.order
|
||||
}
|
||||
this.$export('log/excel', {
|
||||
sortField: sortField,
|
||||
sortOrder: sortOrder,
|
||||
...this.queryParams
|
||||
})
|
||||
},
|
||||
search () {
|
||||
let {sortedInfo} = this
|
||||
let sortField, sortOrder
|
||||
// 获取当前列的排序和列的过滤规则
|
||||
if (sortedInfo) {
|
||||
sortField = sortedInfo.field
|
||||
sortOrder = sortedInfo.order
|
||||
}
|
||||
this.fetch({
|
||||
sortField: sortField,
|
||||
sortOrder: sortOrder,
|
||||
...this.queryParams
|
||||
})
|
||||
},
|
||||
reset () {
|
||||
// 取消选中
|
||||
this.selectedRowKeys = []
|
||||
// 重置分页
|
||||
this.$refs.TableInfo.pagination.current = this.pagination.defaultCurrent
|
||||
if (this.paginationInfo) {
|
||||
this.paginationInfo.current = this.pagination.defaultCurrent
|
||||
this.paginationInfo.pageSize = this.pagination.defaultPageSize
|
||||
}
|
||||
// 重置列排序规则
|
||||
this.sortedInfo = null
|
||||
// 重置查询参数
|
||||
this.queryParams = {}
|
||||
// 清空时间选择
|
||||
if (this.advanced) {
|
||||
this.$refs.createTime.reset()
|
||||
}
|
||||
this.fetch()
|
||||
},
|
||||
handleTableChange (pagination, filters, sorter) {
|
||||
// 将这两个参数赋值给Vue data,用于后续使用
|
||||
this.paginationInfo = pagination
|
||||
this.sortedInfo = sorter
|
||||
this.fetch({
|
||||
sortField: sorter.field,
|
||||
sortOrder: sorter.order,
|
||||
...this.queryParams,
|
||||
...filters
|
||||
})
|
||||
},
|
||||
fetch (params = {}) {
|
||||
this.loading = true
|
||||
if (this.paginationInfo) {
|
||||
// 如果分页信息不为空,则设置表格当前第几页,每页条数,并设置查询分页参数
|
||||
this.$refs.TableInfo.pagination.current = this.paginationInfo.current
|
||||
this.$refs.TableInfo.pagination.pageSize = this.paginationInfo.pageSize
|
||||
params.pageSize = this.paginationInfo.pageSize
|
||||
params.pageNum = this.paginationInfo.current
|
||||
} else {
|
||||
// 如果分页信息为空,则设置为默认值
|
||||
params.pageSize = this.pagination.defaultPageSize
|
||||
params.pageNum = this.pagination.defaultCurrent
|
||||
}
|
||||
this.$get('log', {
|
||||
...params
|
||||
}).then((r) => {
|
||||
let data = r.data
|
||||
const pagination = { ...this.pagination }
|
||||
pagination.total = data.total
|
||||
this.loading = false
|
||||
this.dataSource = data.rows
|
||||
this.pagination = pagination
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
@import "../../../static/less/Common";
|
||||
</style>
|
|
@ -0,0 +1,201 @@
|
|||
<template>
|
||||
<a-skeleton active :loading="loading" :paragraph="{rows: 17}">
|
||||
<div class="jvm-info">
|
||||
<div class="alert">
|
||||
<a-alert type="success" :show-icon="true">
|
||||
<div slot="message">
|
||||
数据获取时间 {{this.time}}
|
||||
<a style="margin-left: 24px" @click="create">点击刷新</a>
|
||||
</div>
|
||||
</a-alert>
|
||||
</div>
|
||||
<table>
|
||||
<tr>
|
||||
<th>参数</th>
|
||||
<th>描述</th>
|
||||
<th>当前值</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a-tag color="green">tomcat.sessions.created</a-tag></td>
|
||||
<td>tomcat 已创建 session 数</td>
|
||||
<td>{{tomcat.sessions.created}} 个</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a-tag color="green">tomcat.sessions.expired</a-tag></td>
|
||||
<td>tomcat 已过期 session 数</td>
|
||||
<td>{{tomcat.sessions.expired}} 个</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a-tag color="green">tomcat.sessions.active.current</a-tag></td>
|
||||
<td>tomcat 当前活跃 session 数</td>
|
||||
<td>{{tomcat.sessions.active.current}} 个</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a-tag color="green">tomcat.sessions.active.max</a-tag></td>
|
||||
<td>tomcat 活跃 session 数峰值</td>
|
||||
<td>{{tomcat.sessions.active.max}} 个</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a-tag color="green">tomcat.sessions.rejected</a-tag></td>
|
||||
<td>超过session 最大配置后,拒绝的 session 个数</td>
|
||||
<td>{{tomcat.sessions.rejected}} 个</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a-tag color="purple">tomcat.global.sent</a-tag></td>
|
||||
<td>发送的字节数</td>
|
||||
<td>{{tomcat.global.sent}} bytes</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a-tag color="purple">tomcat.global.request.max</a-tag></td>
|
||||
<td>request 请求最长耗时</td>
|
||||
<td>{{tomcat.global.request.max}} 秒</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a-tag color="purple">tomcat.global.request.count</a-tag></td>
|
||||
<td>全局 request 请求次数</td>
|
||||
<td>{{tomcat.global.request.count}} 次</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a-tag color="purple">tomcat.global.request.totalTime</a-tag></td>
|
||||
<td>全局 request 请求总耗时</td>
|
||||
<td>{{tomcat.global.request.totalTime}} 秒</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a-tag color="cyan">tomcat.servlet.request.max</a-tag></td>
|
||||
<td>servlet 请求最长耗时</td>
|
||||
<td>{{tomcat.servlet.request.max}} 秒</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a-tag color="cyan">tomcat.servlet.request.count</a-tag></td>
|
||||
<td>servlet 总请求次数</td>
|
||||
<td>{{tomcat.servlet.request.count}} 次</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a-tag color="cyan">tomcat.servlet.request.totalTime</a-tag></td>
|
||||
<td>servlet 请求总耗时</td>
|
||||
<td>{{tomcat.servlet.request.totalTime}} 秒</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a-tag color="pink">tomcat.threads.current</a-tag></td>
|
||||
<td>tomcat 当前线程数(包括守护线程)</td>
|
||||
<td>{{tomcat.threads.current}} 个</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a-tag color="pink">tomcat.threads.configMax</a-tag></td>
|
||||
<td>tomcat 配置的线程最大数</td>
|
||||
<td>{{tomcat.threads.configMax}} 个</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</a-skeleton>
|
||||
</template>
|
||||
<script>
|
||||
import axios from 'axios'
|
||||
import moment from 'moment'
|
||||
moment.locale('zh-cn')
|
||||
|
||||
export default {
|
||||
data () {
|
||||
return {
|
||||
time: '',
|
||||
loading: true,
|
||||
tomcat: {
|
||||
sessions: {
|
||||
created: 0,
|
||||
expired: 0,
|
||||
active: {
|
||||
current: 0,
|
||||
max: 0
|
||||
},
|
||||
rejected: 0
|
||||
},
|
||||
global: {
|
||||
sent: 0,
|
||||
request: {
|
||||
count: 0,
|
||||
max: 0,
|
||||
totalTime: 0
|
||||
}
|
||||
},
|
||||
servlet: {
|
||||
request: {
|
||||
count: 0,
|
||||
totalTime: 0,
|
||||
max: 0
|
||||
}
|
||||
},
|
||||
threads: {
|
||||
current: 0,
|
||||
configMax: 0
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
this.create()
|
||||
},
|
||||
methods: {
|
||||
create () {
|
||||
this.time = moment().format('YYYY年MM月DD日 HH时mm分ss秒')
|
||||
axios.all([
|
||||
this.$get('actuator/metrics/tomcat.sessions.created'),
|
||||
this.$get('actuator/metrics/tomcat.sessions.expired'),
|
||||
this.$get('actuator/metrics/tomcat.sessions.active.current'),
|
||||
this.$get('actuator/metrics/tomcat.sessions.active.max'),
|
||||
this.$get('actuator/metrics/tomcat.sessions.rejected'),
|
||||
this.$get('actuator/metrics/tomcat.global.sent'),
|
||||
this.$get('actuator/metrics/tomcat.global.request.max'),
|
||||
this.$get('actuator/metrics/tomcat.global.request'),
|
||||
this.$get('actuator/metrics/tomcat.servlet.request'),
|
||||
this.$get('actuator/metrics/tomcat.servlet.request.max'),
|
||||
this.$get('actuator/metrics/tomcat.threads.current'),
|
||||
this.$get('actuator/metrics/tomcat.threads.config.max')
|
||||
]).then((r) => {
|
||||
this.tomcat.sessions.created = r[0].data.measurements[0].value
|
||||
this.tomcat.sessions.expired = r[1].data.measurements[0].value
|
||||
this.tomcat.sessions.active.current = r[2].data.measurements[0].value
|
||||
this.tomcat.sessions.active.max = r[3].data.measurements[0].value
|
||||
this.tomcat.sessions.rejected = r[4].data.measurements[0].value
|
||||
this.tomcat.global.sent = r[5].data.measurements[0].value
|
||||
this.tomcat.global.request.max = r[6].data.measurements[0].value
|
||||
this.tomcat.global.request.count = r[7].data.measurements[0].value
|
||||
this.tomcat.global.request.totalTime = r[7].data.measurements[1].value
|
||||
this.tomcat.servlet.request.count = r[8].data.measurements[0].value
|
||||
this.tomcat.servlet.request.totalTime = r[8].data.measurements[1].value
|
||||
this.tomcat.servlet.request.max = r[9].data.measurements[0].value
|
||||
this.tomcat.threads.current = r[10].data.measurements[0].value
|
||||
this.tomcat.threads.configMax = r[11].data.measurements[0].value
|
||||
this.loading = false
|
||||
}).catch((r) => {
|
||||
console.error(r)
|
||||
this.$message.error('获取Tomcat信息失败')
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="less">
|
||||
.jvm-info {
|
||||
width: 100%;
|
||||
table {
|
||||
width: 100%;
|
||||
tr {
|
||||
line-height: 1.5rem;
|
||||
border-bottom: 1px solid #f1f1f1;
|
||||
th {
|
||||
background: #fafafa;
|
||||
padding: .5rem;
|
||||
}
|
||||
td {
|
||||
padding: .5rem;
|
||||
.ant-tag {
|
||||
font-size: .8rem !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.alert {
|
||||
margin-bottom: .5rem;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,190 @@
|
|||
<template>
|
||||
<div style="width: 100%">
|
||||
<div class="option-area">
|
||||
<a-upload
|
||||
class="upload-area"
|
||||
:fileList="fileList"
|
||||
:remove="handleRemove"
|
||||
:disabled="fileList.length === 1"
|
||||
:beforeUpload="beforeUpload">
|
||||
<a-button>
|
||||
<a-icon type="upload" /> 选择.xlsx文件
|
||||
</a-button>
|
||||
</a-upload>
|
||||
<div class="button-area">
|
||||
<a-button type="primary" @click="downloadTemplate" style="margin-right: .5rem">
|
||||
模板下载
|
||||
</a-button>
|
||||
<a-button @click="exportExcel" style="margin-right: .5rem">
|
||||
导出Excel
|
||||
</a-button>
|
||||
<a-button
|
||||
@click="handleUpload"
|
||||
:disabled="fileList.length === 0"
|
||||
:loading="uploading">
|
||||
{{uploading ? '导入中' : '导入Excel' }}
|
||||
</a-button>
|
||||
</div>
|
||||
</div>
|
||||
<a-card :bordered="false" class="card-area">
|
||||
<!-- 表格区域 -->
|
||||
<a-table ref="TableInfo"
|
||||
:columns="columns"
|
||||
:dataSource="dataSource"
|
||||
:pagination="pagination"
|
||||
:loading="loading"
|
||||
@change="handleTableChange"
|
||||
:scroll="{ x: 900 }">
|
||||
</a-table>
|
||||
</a-card>
|
||||
<import-result
|
||||
@close="handleClose"
|
||||
:importData="importData"
|
||||
:errors="errors"
|
||||
:times="times"
|
||||
:importResultVisible="importResultVisible">
|
||||
</import-result>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import ImportResult from './ImportResult'
|
||||
|
||||
export default {
|
||||
components: {ImportResult},
|
||||
data () {
|
||||
return {
|
||||
fileList: [],
|
||||
importData: [],
|
||||
times: '',
|
||||
errors: [],
|
||||
uploading: false,
|
||||
importResultVisible: false,
|
||||
dataSource: [],
|
||||
paginationInfo: null,
|
||||
pagination: {
|
||||
pageSizeOptions: ['10', '20', '30', '40', '100'],
|
||||
defaultCurrent: 1,
|
||||
defaultPageSize: 10,
|
||||
showQuickJumper: true,
|
||||
showSizeChanger: true,
|
||||
showTotal: (total, range) => `显示 ${range[0]} ~ ${range[1]} 条记录,共 ${total} 条记录`
|
||||
},
|
||||
loading: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
columns () {
|
||||
return [{
|
||||
title: '字段1',
|
||||
dataIndex: 'field1'
|
||||
}, {
|
||||
title: '字段2',
|
||||
dataIndex: 'field2'
|
||||
}, {
|
||||
title: '字段3',
|
||||
dataIndex: 'field3'
|
||||
}, {
|
||||
title: '导入时间',
|
||||
dataIndex: 'createTime'
|
||||
}]
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
this.fetch()
|
||||
},
|
||||
methods: {
|
||||
handleClose () {
|
||||
this.importResultVisible = false
|
||||
},
|
||||
downloadTemplate () {
|
||||
this.$download('test/template', {}, '导入模板.xlsx')
|
||||
},
|
||||
exportExcel () {
|
||||
this.$export('test/export')
|
||||
},
|
||||
handleRemove (file) {
|
||||
if (this.uploading) {
|
||||
this.$message.warning('文件导入中,请勿删除')
|
||||
return
|
||||
}
|
||||
const index = this.fileList.indexOf(file)
|
||||
const newFileList = this.fileList.slice()
|
||||
newFileList.splice(index, 1)
|
||||
this.fileList = newFileList
|
||||
},
|
||||
beforeUpload (file) {
|
||||
this.fileList = [...this.fileList, file]
|
||||
return false
|
||||
},
|
||||
handleUpload () {
|
||||
const { fileList } = this
|
||||
const formData = new FormData()
|
||||
formData.append('file', fileList[0])
|
||||
this.uploading = true
|
||||
this.$upload('test/import', formData).then((r) => {
|
||||
let data = r.data.data
|
||||
if (data.data.length) {
|
||||
this.fetch()
|
||||
}
|
||||
this.importData = data.data
|
||||
this.errors = data.error
|
||||
this.times = data.time / 1000
|
||||
this.uploading = false
|
||||
this.fileList = []
|
||||
this.importResultVisible = true
|
||||
}).catch((r) => {
|
||||
console.error(r)
|
||||
this.uploading = false
|
||||
this.fileList = []
|
||||
})
|
||||
},
|
||||
handleTableChange (pagination, filters, sorter) {
|
||||
this.paginationInfo = pagination
|
||||
this.fetch()
|
||||
},
|
||||
fetch (params = {}) {
|
||||
this.loading = true
|
||||
if (this.paginationInfo) {
|
||||
this.$refs.TableInfo.pagination.current = this.paginationInfo.current
|
||||
this.$refs.TableInfo.pagination.pageSize = this.paginationInfo.pageSize
|
||||
params.pageSize = this.paginationInfo.pageSize
|
||||
params.pageNum = this.paginationInfo.current
|
||||
} else {
|
||||
params.pageSize = this.pagination.defaultPageSize
|
||||
params.pageNum = this.pagination.defaultCurrent
|
||||
}
|
||||
this.$get('test', {
|
||||
...params
|
||||
}).then((r) => {
|
||||
let data = r.data
|
||||
const pagination = { ...this.pagination }
|
||||
pagination.total = data.total
|
||||
this.loading = false
|
||||
this.dataSource = data.rows
|
||||
this.pagination = pagination
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@import "../../../static/less/Common";
|
||||
.option-area {
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
padding: 0 .9rem;
|
||||
margin: .5rem 0;
|
||||
.upload-area {
|
||||
display: inline;
|
||||
float: left;
|
||||
width: 50%
|
||||
}
|
||||
.button-area {
|
||||
margin-left: 1rem;
|
||||
display: inline;
|
||||
float: right;
|
||||
width: 30%;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,169 @@
|
|||
<template>
|
||||
<a-modal
|
||||
class="import-result"
|
||||
title="导入结果"
|
||||
v-model="show"
|
||||
:centered="true"
|
||||
:footer="null"
|
||||
:maskClosable="false"
|
||||
:width=1000
|
||||
@cancel="handleCancel">
|
||||
<div class="import-desc">
|
||||
<span v-if="importData.length === 0 && errors.length === 0">
|
||||
<a-alert
|
||||
message="暂无导入记录"
|
||||
type="info">
|
||||
</a-alert>
|
||||
</span>
|
||||
<span v-if="importData.length !== 0 && errors.length !== 0">
|
||||
<a-alert
|
||||
message="部分导入成功"
|
||||
type="warning">
|
||||
<div slot="description">
|
||||
成功导入 <a>{{importData.length}}</a> 条记录,<a>{{errors.length}}</a> 条记录导入失败,共耗时 <a>{{times}}</a> 秒
|
||||
</div>
|
||||
</a-alert>
|
||||
</span>
|
||||
<span v-if="importData.length !== 0 && errors.length === 0">
|
||||
<a-alert
|
||||
message="全部导入成功"
|
||||
type="success">
|
||||
<div slot="description">
|
||||
成功导入 <a>{{importData.length}}</a> 条记录,共耗时 <a>{{times}}</a> 秒
|
||||
</div>
|
||||
</a-alert>
|
||||
</span>
|
||||
<span v-if="importData.length === 0 && errors.length !== 0">
|
||||
<a-alert
|
||||
message="全部导入失败"
|
||||
type="error">
|
||||
<div slot="description">
|
||||
<a>{{errors.length}}</a> 条记录导入失败,共耗时 <a>{{times}}</a> 秒
|
||||
</div>
|
||||
</a-alert>
|
||||
</span>
|
||||
</div>
|
||||
<a-tabs defaultActiveKey="1">
|
||||
<a-tab-pane tab="成功记录" key="1" v-if="importData.length">
|
||||
<a-table ref="successTable"
|
||||
:columns="successColumns"
|
||||
:dataSource="importData"
|
||||
:pagination="pagination"
|
||||
:scroll="{ x: 900 }">
|
||||
</a-table>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane tab="失败记录" key="2" v-if="errors.length">
|
||||
<a-table ref="errorTable"
|
||||
:columns="errorColumns"
|
||||
:dataSource="errorsData"
|
||||
:pagination="pagination"
|
||||
:scroll="{ x: 900 }">
|
||||
</a-table>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</a-modal>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
importResultVisible: {
|
||||
required: true,
|
||||
default: false
|
||||
},
|
||||
importData: {
|
||||
required: true
|
||||
},
|
||||
errors: {
|
||||
required: true
|
||||
},
|
||||
times: {
|
||||
required: true
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
pagination: {
|
||||
pageSizeOptions: ['5', '10'],
|
||||
defaultCurrent: 1,
|
||||
defaultPageSize: 5,
|
||||
showQuickJumper: true,
|
||||
showSizeChanger: true,
|
||||
showTotal: (total, range) => `显示 ${range[0]} ~ ${range[1]} 条记录,共 ${total} 条记录`
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
errorsData () {
|
||||
let arr = []
|
||||
for (let i = 0; i < this.errors.length; i++) {
|
||||
let error = this.errors[i]
|
||||
let e = {}
|
||||
for (let field of error.errorFields) {
|
||||
e = {...field}
|
||||
e.row = error.row
|
||||
arr.push(e)
|
||||
}
|
||||
}
|
||||
return arr
|
||||
},
|
||||
successColumns () {
|
||||
return [{
|
||||
title: '字段1',
|
||||
dataIndex: 'field1'
|
||||
}, {
|
||||
title: '字段2',
|
||||
dataIndex: 'field2'
|
||||
}, {
|
||||
title: '字段3',
|
||||
dataIndex: 'field3'
|
||||
}, {
|
||||
title: '导入时间',
|
||||
dataIndex: 'createTime'
|
||||
}]
|
||||
},
|
||||
errorColumns () {
|
||||
return [{
|
||||
title: '行',
|
||||
dataIndex: 'row',
|
||||
customRender: (text, row, index) => {
|
||||
return `第 ${text + 1} 行`
|
||||
}
|
||||
}, {
|
||||
title: '列',
|
||||
dataIndex: 'cellIndex',
|
||||
customRender: (text, row, index) => {
|
||||
return `第 ${text + 1} 列`
|
||||
}
|
||||
}, {
|
||||
title: '列名',
|
||||
dataIndex: 'column'
|
||||
}, {
|
||||
title: '错误信息',
|
||||
dataIndex: 'errorMessage'
|
||||
}]
|
||||
},
|
||||
show: {
|
||||
get: function () {
|
||||
return this.importResultVisible
|
||||
},
|
||||
set: function () {
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleCancel () {
|
||||
this.$emit('close')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="less">
|
||||
.import-result {
|
||||
.import-desc {
|
||||
margin-bottom: .5rem;
|
||||
a {
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,117 @@
|
|||
<template>
|
||||
<div :class="[multipage === true ? 'multi-page':'single-page', 'not-menu-page', 'user-profile']">
|
||||
<a-card title="">
|
||||
<a href="javascript:void(0)" slot="extra" @click="updateProfile">编辑资料</a>
|
||||
<a-row :gutter="8">
|
||||
<a-col :span="6">
|
||||
<a-row style="text-align: center">
|
||||
<img style="width: 10rem;border-radius: 2px" alt="头像" :src="avatar">
|
||||
</a-row>
|
||||
<a-row style="text-align: center">
|
||||
<a-button icon="edit" style="margin-top:1rem" @click="updateAvatar">修改头像</a-button>
|
||||
</a-row>
|
||||
</a-col>
|
||||
<a-col :span="12" style="font-size: 1rem">
|
||||
<p>账户:{{user.username}}</p>
|
||||
<p :title="user.roleName">角色:{{user.roleName? user.roleName: '暂无角色'}}</p>
|
||||
<p>性别:{{sex}}</p>
|
||||
<p>电话:{{user.mobile ? user.mobile : '暂未绑定电话'}}</p>
|
||||
<p>邮箱:{{user.email ? user.email : '暂未绑定邮箱'}}</p>
|
||||
<p>部门:{{user.deptName? user.deptName: '暂无部门'}}</p>
|
||||
<p>描述:{{user.description}}</p>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-card>
|
||||
<update-avatar
|
||||
@cancel="handleUpdateAvatarCancel"
|
||||
@success="handleUpdateAvatarSuccess"
|
||||
:user="user"
|
||||
:updateAvatarModelVisible="updateAvatarModelVisible">
|
||||
</update-avatar>
|
||||
<update-profile
|
||||
ref="updateProfile"
|
||||
@success="handleProfileEditSuccess"
|
||||
@close="handleProfileEditClose"
|
||||
:profileEditVisiable="profileEditVisiable">
|
||||
</update-profile>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import {mapState, mapMutations} from 'vuex'
|
||||
import UpdateAvatar from './UpdateAvatar'
|
||||
import UpdateProfile from './UpdateProfile'
|
||||
|
||||
export default {
|
||||
name: 'Profile',
|
||||
components: {UpdateAvatar, UpdateProfile},
|
||||
data () {
|
||||
return {
|
||||
updateAvatarModelVisible: false,
|
||||
profileEditVisiable: false,
|
||||
userId: ''
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState({
|
||||
multipage: state => state.setting.multipage,
|
||||
user: state => state.account.user
|
||||
}),
|
||||
avatar () {
|
||||
return `static/avatar/${this.user.avatar}`
|
||||
},
|
||||
sex () {
|
||||
switch (this.user.ssex) {
|
||||
case '0':
|
||||
return '男'
|
||||
case '1':
|
||||
return '女'
|
||||
case '2':
|
||||
return '保密'
|
||||
default:
|
||||
return this.user.ssex
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
...mapMutations({
|
||||
setUser: 'account/setUser'
|
||||
}),
|
||||
handleUpdateAvatarCancel () {
|
||||
this.updateAvatarModelVisible = false
|
||||
},
|
||||
handleUpdateAvatarSuccess (avatar) {
|
||||
this.updateAvatarModelVisible = false
|
||||
this.$message.success('更换头像成功')
|
||||
let user = this.user
|
||||
user.avatar = avatar
|
||||
this.setUser(user)
|
||||
},
|
||||
updateAvatar () {
|
||||
this.updateAvatarModelVisible = true
|
||||
this.userId = this.user.userId
|
||||
},
|
||||
updateProfile () {
|
||||
this.$refs.updateProfile.setFormValues(this.user)
|
||||
this.profileEditVisiable = true
|
||||
},
|
||||
handleProfileEditClose () {
|
||||
this.profileEditVisiable = false
|
||||
},
|
||||
handleProfileEditSuccess () {
|
||||
this.profileEditVisiable = false
|
||||
this.$message.success('修改成功')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="less">
|
||||
.user-profile {
|
||||
.ant-card-body {
|
||||
padding: 1rem 0 !important;
|
||||
p {
|
||||
font-size: .9rem !important;
|
||||
margin-bottom: .6rem !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,121 @@
|
|||
<template>
|
||||
<a-modal
|
||||
class="update-avatar"
|
||||
title="选择头像"
|
||||
@cancel="handleCancel"
|
||||
:width="710"
|
||||
:footer="null"
|
||||
v-model="show">
|
||||
<a-tabs defaultActiveKey="1" class="avatar-tabs">
|
||||
<a-tab-pane tab="后田花子" key="1">
|
||||
<template v-for="(avatar, index) in hthz">
|
||||
<div class="avatar-wrapper" :key="index">
|
||||
<img alt="点击选择" :src="'static/avatar/' + avatar" @click="change(avatar)">
|
||||
</div>
|
||||
</template>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane tab="阿里系" key="2" forceRender>
|
||||
<template v-for="(avatar, index) in al">
|
||||
<div class="avatar-wrapper" :key="index">
|
||||
<img alt="点击选择" :src="'static/avatar/' + avatar" @click="change(avatar)">
|
||||
</div>
|
||||
</template>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane tab="脸萌" key="3">
|
||||
<template v-for="(avatar, index) in lm">
|
||||
<div class="avatar-wrapper" :key="index">
|
||||
<img alt="点击选择" :src="'static/avatar/' + avatar" @click="change(avatar)">
|
||||
</div>
|
||||
</template>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</a-modal>
|
||||
</template>
|
||||
<script>
|
||||
const hthz = ['default.jpg', '1d22f3e41d284f50b2c8fc32e0788698.jpeg',
|
||||
'2dd7a2d09fa94bf8b5c52e5318868b4d9.jpg', '2dd7a2d09fa94bf8b5c52e5318868b4df.jpg',
|
||||
'8f5b60ef00714a399ee544d331231820.jpeg', '17e420c250804efe904a09a33796d5a10.jpg',
|
||||
'17e420c250804efe904a09a33796d5a16.jpg', '87d8194bc9834e9f8f0228e9e530beb1.jpeg',
|
||||
'496b3ace787342f7954b7045b8b06804.jpeg', '595ba7b05f2e485eb50565a50cb6cc3c.jpeg',
|
||||
'964e40b005724165b8cf772355796c8c.jpeg', '5997fedcc7bd4cffbd350b40d1b5b987.jpg',
|
||||
'5997fedcc7bd4cffbd350b40d1b5b9824.jpg', 'a3b10296862e40edb811418d64455d00.jpeg',
|
||||
'a43456282d684e0b9319cf332f8ac468.jpeg', 'bba284ac05b041a8b8b0d1927868d5c9x.jpg',
|
||||
'c7c4ee7be3eb4e73a19887dc713505145.jpg', 'ff698bb2d25c4d218b3256b46c706ece.jpeg']
|
||||
const al = ['cnrhVkzwxjPwAaCfPbdc.png', 'BiazfanxmamNRoxxVxka.png', 'gaOngJwsRYRaVAuXXcmB.png',
|
||||
'WhxKECPNujWoWEFNdnJE.png', 'ubnKSIfAJTxIgXOKlciN.png', 'jZUIxmJycoymBprLOUbT.png']
|
||||
const lm = ['19034103295190235.jpg', '20180414165920.jpg', '20180414170003.jpg',
|
||||
'20180414165927.jpg', '20180414165754.jpg', '20180414165815.jpg',
|
||||
'20180414165821.jpg', '20180414165827.jpg', '20180414165834.jpg',
|
||||
'20180414165840.jpg', '20180414165846.jpg', '20180414165855.jpg',
|
||||
'20180414165909.jpg', '20180414165914.jpg', '20180414165936.jpg',
|
||||
'20180414165942.jpg', '20180414165947.jpg', '20180414165955.jpg']
|
||||
export default {
|
||||
props: {
|
||||
updateAvatarModelVisible: {
|
||||
default: false
|
||||
},
|
||||
user: {
|
||||
required: true
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
hthz,
|
||||
al,
|
||||
lm,
|
||||
updating: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
show: {
|
||||
get: function () {
|
||||
return this.updateAvatarModelVisible
|
||||
},
|
||||
set: function () {
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleCancel () {
|
||||
this.$emit('cancel')
|
||||
},
|
||||
change (avatar) {
|
||||
if (this.updating) {
|
||||
this.$message.warning('更换头像中,请勿重复点击')
|
||||
return
|
||||
}
|
||||
this.updating = true
|
||||
this.$put('user/avatar', {
|
||||
username: this.user.username,
|
||||
avatar
|
||||
}).then(() => {
|
||||
this.$emit('success', avatar)
|
||||
this.updating = false
|
||||
}).catch((r) => {
|
||||
console.error(r)
|
||||
this.$message.error('更新头像失败')
|
||||
this.updating = false
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="less">
|
||||
.update-avatar {
|
||||
.ant-modal-body {
|
||||
padding: 0 1rem 1rem 1rem!important;
|
||||
.avatar-tabs {
|
||||
.avatar-wrapper {
|
||||
display: inline-block;
|
||||
img {
|
||||
width: 6rem;
|
||||
border-radius: 2px;
|
||||
display: inline-block;
|
||||
margin: .5rem;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,223 @@
|
|||
<template>
|
||||
<div>
|
||||
<!-- 密码修改 -->
|
||||
<a-modal
|
||||
title="密码修改"
|
||||
:keyboard="false"
|
||||
:maskClosable="false"
|
||||
:closable="false"
|
||||
v-model="show"
|
||||
@cancel="cancelUpdatePassword"
|
||||
@ok="handleUpdatePassword">
|
||||
<a-form :autoFormCreate="(form)=>{this.form = form}">
|
||||
<a-form-item
|
||||
label='旧密码'
|
||||
v-bind="formItemLayout"
|
||||
fieldDecoratorId="oldPassword"
|
||||
:fieldDecoratorOptions="{rules: [{ required: true, message: '请输入旧密码'}, { validator: this.handleOldPassowrd }], validateTrigger: ['blur']}">
|
||||
<a-input type="password"
|
||||
autocomplete="false"
|
||||
v-model="oldPassword"
|
||||
placeholder="请输入旧密码"></a-input>
|
||||
</a-form-item>
|
||||
<a-popover placement="rightTop" trigger="click" :visible="state.passwordLevelChecked">
|
||||
<template slot="content">
|
||||
<div :style="{ width: '240px' }">
|
||||
<div :class="['update-password', passwordLevelClass]">强度:<span>{{ passwordLevelName }}</span></div>
|
||||
<a-progress :percent="state.percent" :showInfo="false" :strokeColor=" passwordLevelColor "/>
|
||||
<div style="margin-top: 10px;">
|
||||
<span>请至少输入 6 个字符。请不要使用容易被猜到的密码。</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<a-form-item
|
||||
label='新密码'
|
||||
v-bind="formItemLayout"
|
||||
fieldDecoratorId="password"
|
||||
:fieldDecoratorOptions="{rules: [{ required: true, message: '至少6位密码,区分大小写'}, { validator: this.handlePasswordLevel }], validateTrigger: ['change', 'blur']}">
|
||||
<a-input type="password"
|
||||
@click="handlePasswordInputClick"
|
||||
v-model="newPassword"
|
||||
autocomplete="false"
|
||||
placeholder="至少6位密码,区分大小写"></a-input>
|
||||
</a-form-item>
|
||||
</a-popover>
|
||||
<a-form-item
|
||||
label='再次确认'
|
||||
v-bind="formItemLayout"
|
||||
fieldDecoratorId="password2"
|
||||
:fieldDecoratorOptions="{rules: [{ required: true, message: '至少6位密码,区分大小写' }, { validator: this.handlePasswordCheck }], validateTrigger: ['change', 'blur']}">
|
||||
<a-input type="password" autocomplete="false" placeholder="确认密码"></a-input>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
const formItemLayout = {
|
||||
labelCol: { span: 4 },
|
||||
wrapperCol: { span: 18 }
|
||||
}
|
||||
const levelNames = {
|
||||
0: '低',
|
||||
1: '低',
|
||||
2: '中',
|
||||
3: '强'
|
||||
}
|
||||
const levelClass = {
|
||||
0: 'error',
|
||||
1: 'error',
|
||||
2: 'warning',
|
||||
3: 'success'
|
||||
}
|
||||
const levelColor = {
|
||||
0: '#ff0000',
|
||||
1: '#ff0000',
|
||||
2: '#ff7e05',
|
||||
3: '#52c41a'
|
||||
}
|
||||
|
||||
export default {
|
||||
props: {
|
||||
updatePasswordModelVisible: {
|
||||
default: false
|
||||
},
|
||||
user: {
|
||||
required: true
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
form: null,
|
||||
formItemLayout,
|
||||
state: {
|
||||
passwordLevel: 0,
|
||||
passwordLevelChecked: false,
|
||||
percent: 10,
|
||||
progressColor: '#FF0000'
|
||||
},
|
||||
oldPassword: '',
|
||||
newPassword: '',
|
||||
validateStatus: '',
|
||||
help: ''
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
show: {
|
||||
get: function () {
|
||||
return this.updatePasswordModelVisible
|
||||
},
|
||||
set: function () {
|
||||
}
|
||||
},
|
||||
passwordLevelClass () {
|
||||
return levelClass[this.state.passwordLevel]
|
||||
},
|
||||
passwordLevelName () {
|
||||
return levelNames[this.state.passwordLevel]
|
||||
},
|
||||
passwordLevelColor () {
|
||||
return levelColor[this.state.passwordLevel]
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
isMobile () {
|
||||
return this.$store.state.setting.isMobile
|
||||
},
|
||||
cancelUpdatePassword () {
|
||||
this.state.passwordLevelChecked = false
|
||||
this.form.resetFields()
|
||||
this.$emit('cancel')
|
||||
},
|
||||
handleUpdatePassword () {
|
||||
this.form.validateFields((err, values) => {
|
||||
if (!err) {
|
||||
this.$put('user/password', {
|
||||
password: this.newPassword,
|
||||
username: this.user.username
|
||||
}).then(() => {
|
||||
this.state.passwordLevelChecked = false
|
||||
this.$emit('success')
|
||||
this.form.resetFields()
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
handlePasswordLevel (rule, value, callback) {
|
||||
let level = 0
|
||||
// 判断这个字符串中有没有数字
|
||||
if (/[0-9]/.test(value)) {
|
||||
level++
|
||||
}
|
||||
// 判断字符串中有没有字母
|
||||
if (/[a-zA-Z]/.test(value)) {
|
||||
level++
|
||||
}
|
||||
// 判断字符串中有没有特殊符号
|
||||
if (/[^0-9a-zA-Z_]/.test(value)) {
|
||||
level++
|
||||
}
|
||||
this.state.passwordLevel = level
|
||||
this.state.percent = level * 30
|
||||
if (level >= 2) {
|
||||
if (level >= 3) {
|
||||
this.state.percent = 100
|
||||
}
|
||||
callback()
|
||||
} else {
|
||||
if (level === 0) {
|
||||
this.state.percent = 10
|
||||
}
|
||||
callback(new Error('密码强度不够'))
|
||||
}
|
||||
},
|
||||
handlePasswordCheck (rule, value, callback) {
|
||||
let password = this.form.getFieldValue('password')
|
||||
if (value === undefined) {
|
||||
callback(new Error('请输入密码'))
|
||||
}
|
||||
if (value && password && value.trim() !== password.trim()) {
|
||||
callback(new Error('两次密码不一致'))
|
||||
}
|
||||
callback()
|
||||
},
|
||||
handlePasswordInputClick () {
|
||||
if (!this.isMobile()) {
|
||||
this.state.passwordLevelChecked = true
|
||||
return
|
||||
}
|
||||
this.state.passwordLevelChecked = false
|
||||
},
|
||||
handleOldPassowrd (rule, value, callback) {
|
||||
let password = this.oldPassword
|
||||
if (this.oldPassword.trim().length) {
|
||||
this.$get('user/password/check', {
|
||||
password: password,
|
||||
username: this.user.username
|
||||
}).then((r) => {
|
||||
if (r.data) {
|
||||
callback()
|
||||
} else {
|
||||
callback(new Error('旧密码不正确'))
|
||||
}
|
||||
})
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="less">
|
||||
.update-password {
|
||||
&.error {
|
||||
color: #ff0000;
|
||||
}
|
||||
&.warning {
|
||||
color: #ff7e05;
|
||||
}
|
||||
&.success {
|
||||
color: #52c41a;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,164 @@
|
|||
<template>
|
||||
<a-drawer
|
||||
title="编辑资料"
|
||||
:maskClosable="false"
|
||||
width=650
|
||||
placement="right"
|
||||
:closable="false"
|
||||
@close="onClose"
|
||||
:visible="profileEditVisiable"
|
||||
style="height: calc(100% - 55px);overflow: auto;padding-bottom: 53px;">
|
||||
<a-form :form="form">
|
||||
<a-form-item label='邮箱' v-bind="formItemLayout">
|
||||
<a-input
|
||||
v-decorator="[
|
||||
'email',
|
||||
{rules: [
|
||||
{ type: 'email', message: '请输入正确的邮箱' },
|
||||
{ max: 50, message: '长度不能超过50个字符'}
|
||||
]}
|
||||
]"/>
|
||||
</a-form-item>
|
||||
<a-form-item label="手机" v-bind="formItemLayout">
|
||||
<a-input
|
||||
v-decorator="['mobile', {rules: [
|
||||
{ pattern: '^0?(13[0-9]|15[012356789]|17[013678]|18[0-9]|14[57])[0-9]{8}$', message: '请输入正确的手机号'}
|
||||
]}]"/>
|
||||
</a-form-item>
|
||||
<a-form-item label='部门' v-bind="formItemLayout">
|
||||
<a-tree-select
|
||||
:allowClear="true"
|
||||
:dropdownStyle="{ maxHeight: '220px', overflow: 'auto' }"
|
||||
:treeData="deptTreeData"
|
||||
@change="onDeptChange"
|
||||
:value="userDept">
|
||||
</a-tree-select>
|
||||
</a-form-item>
|
||||
<a-form-item label='性别' v-bind="formItemLayout">
|
||||
<a-radio-group
|
||||
v-decorator="[
|
||||
'ssex',
|
||||
{rules: [{ required: true, message: '请选择性别' }]}
|
||||
]">
|
||||
<a-radio value="0">男</a-radio>
|
||||
<a-radio value="1">女</a-radio>
|
||||
<a-radio value="2">保密</a-radio>
|
||||
</a-radio-group>
|
||||
</a-form-item>
|
||||
<a-form-item label='描述' v-bind="formItemLayout">
|
||||
<a-textarea
|
||||
:rows="4"
|
||||
v-decorator="[
|
||||
'description',
|
||||
{rules: [
|
||||
{ max: 100, message: '长度不能超过100个字符'}
|
||||
]}]">
|
||||
</a-textarea>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
<div class="drawer-bootom-button">
|
||||
<a-popconfirm title="确定放弃编辑?" @confirm="onClose" okText="确定" cancelText="取消">
|
||||
<a-button style="margin-right: .8rem">取消</a-button>
|
||||
</a-popconfirm>
|
||||
<a-button @click="handleSubmit" type="primary" :loading="loading">提交</a-button>
|
||||
</div>
|
||||
</a-drawer>
|
||||
</template>
|
||||
<script>
|
||||
import {mapState, mapMutations} from 'vuex'
|
||||
|
||||
const formItemLayout = {
|
||||
labelCol: { span: 3 },
|
||||
wrapperCol: { span: 18 }
|
||||
}
|
||||
export default {
|
||||
props: {
|
||||
profileEditVisiable: {
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
formItemLayout,
|
||||
form: this.$form.createForm(this),
|
||||
deptTreeData: [],
|
||||
userDept: [],
|
||||
userId: '',
|
||||
roleId: '',
|
||||
status: '',
|
||||
username: '',
|
||||
loading: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState({
|
||||
currentUser: state => state.account.user
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
...mapMutations({
|
||||
setUser: 'account/setUser'
|
||||
}),
|
||||
onClose () {
|
||||
this.loading = false
|
||||
this.form.resetFields()
|
||||
this.$emit('close')
|
||||
},
|
||||
setFormValues ({...user}) {
|
||||
this.userId = user.userId
|
||||
let fields = ['email', 'ssex', 'description', 'mobile']
|
||||
Object.keys(user).forEach((key) => {
|
||||
if (fields.indexOf(key) !== -1) {
|
||||
this.form.getFieldDecorator(key)
|
||||
let obj = {}
|
||||
obj[key] = user[key]
|
||||
this.form.setFieldsValue(obj)
|
||||
}
|
||||
})
|
||||
if (user.deptId) {
|
||||
this.userDept = [user.deptId]
|
||||
}
|
||||
this.status = user.status
|
||||
this.roleId = user.roleId
|
||||
this.username = user.username
|
||||
},
|
||||
onDeptChange (value) {
|
||||
this.userDept = value
|
||||
},
|
||||
handleSubmit () {
|
||||
this.form.validateFields((err, values) => {
|
||||
if (!err) {
|
||||
this.loading = true
|
||||
let user = this.form.getFieldsValue()
|
||||
user.userId = this.userId
|
||||
user.deptId = this.userDept
|
||||
user.roleId = this.roleId
|
||||
user.status = this.status
|
||||
user.username = this.username
|
||||
this.$put('user/profile', {
|
||||
...user
|
||||
}).then((r) => {
|
||||
this.loading = false
|
||||
this.$emit('success')
|
||||
// 更新其state
|
||||
this.$get(`user/${user.username}`).then((r) => {
|
||||
this.setUser(r.data)
|
||||
})
|
||||
}).catch(() => {
|
||||
this.loading = false
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
profileEditVisiable () {
|
||||
if (this.profileEditVisiable) {
|
||||
this.$get('dept').then((r) => {
|
||||
this.deptTreeData = r.data.rows.children
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,251 @@
|
|||
<template>
|
||||
<a-card :bordered="false" class="card-area">
|
||||
<div :class="advanced ? 'search' : null">
|
||||
<!-- 搜索区域 -->
|
||||
<a-form layout="horizontal">
|
||||
<div :class="advanced ? null: 'fold'">
|
||||
<a-row >
|
||||
<a-col :md="12" :sm="24" >
|
||||
<a-form-item
|
||||
label="名称"
|
||||
:labelCol="{span: 5}"
|
||||
:wrapperCol="{span: 18, offset: 1}">
|
||||
<a-input v-model="queryParams.deptName"/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :md="12" :sm="24" >
|
||||
<a-form-item
|
||||
label="创建时间"
|
||||
:labelCol="{span: 5}"
|
||||
:wrapperCol="{span: 18, offset: 1}">
|
||||
<range-date @change="handleDateChange" ref="createTime"></range-date>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
<span style="float: right; margin-top: 3px;">
|
||||
<a-button type="primary" @click="search">查询</a-button>
|
||||
<a-button style="margin-left: 8px" @click="reset">重置</a-button>
|
||||
</span>
|
||||
</a-form>
|
||||
</div>
|
||||
<div>
|
||||
<div class="operator">
|
||||
<a-button v-hasPermission="'dept:add'" type="primary" ghost @click="add">新增</a-button>
|
||||
<a-button v-hasPermission="'dept:delete'" @click="batchDelete">删除</a-button>
|
||||
<a-dropdown v-hasPermission="'dept:export'">
|
||||
<a-menu slot="overlay">
|
||||
<a-menu-item key="export-data" @click="exportExcel">导出Excel</a-menu-item>
|
||||
</a-menu>
|
||||
<a-button>
|
||||
更多操作 <a-icon type="down" />
|
||||
</a-button>
|
||||
</a-dropdown>
|
||||
</div>
|
||||
<!-- 表格区域 -->
|
||||
<a-table :columns="columns"
|
||||
:dataSource="dataSource"
|
||||
:pagination="pagination"
|
||||
:loading="loading"
|
||||
:scroll="{ x: 900 }"
|
||||
:rowSelection="{selectedRowKeys: selectedRowKeys, onChange: onSelectChange}"
|
||||
@change="handleTableChange">
|
||||
<template slot="operation" slot-scope="text, record">
|
||||
<a-icon v-hasPermission="'dept:update'" type="setting" theme="twoTone" twoToneColor="#4a9ff5" @click="edit(record)" title="修改"></a-icon>
|
||||
<a-badge v-hasNoPermission="'dept:update'" status="warning" text="无权限"></a-badge>
|
||||
</template>
|
||||
</a-table>
|
||||
</div>
|
||||
<!-- 新增部门 -->
|
||||
<dept-add
|
||||
@success="handleDeptAddSuccess"
|
||||
@close="handleDeptAddClose"
|
||||
:deptAddVisiable="deptAddVisiable">
|
||||
</dept-add>
|
||||
<!-- 修改部门 -->
|
||||
<dept-edit
|
||||
ref="deptEdit"
|
||||
@success="handleDeptEditSuccess"
|
||||
@close="handleDeptEditClose"
|
||||
:deptEditVisiable="deptEditVisiable">
|
||||
</dept-edit>
|
||||
</a-card>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import RangeDate from '@/components/datetime/RangeDate'
|
||||
import DeptAdd from './DeptAdd'
|
||||
import DeptEdit from './DeptEdit'
|
||||
|
||||
export default {
|
||||
name: 'Dept',
|
||||
components: {DeptAdd, DeptEdit, RangeDate},
|
||||
data () {
|
||||
return {
|
||||
advanced: false,
|
||||
dataSource: [],
|
||||
selectedRowKeys: [],
|
||||
queryParams: {},
|
||||
sortedInfo: null,
|
||||
pagination: {
|
||||
defaultPageSize: 10000000,
|
||||
hideOnSinglePage: true,
|
||||
indentSize: 100
|
||||
},
|
||||
loading: false,
|
||||
deptAddVisiable: false,
|
||||
deptEditVisiable: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
columns () {
|
||||
let { sortedInfo } = this
|
||||
sortedInfo = sortedInfo || {}
|
||||
return [{
|
||||
title: '名称',
|
||||
dataIndex: 'text'
|
||||
}, {
|
||||
title: '排序',
|
||||
dataIndex: 'order'
|
||||
}, {
|
||||
title: '创建时间',
|
||||
dataIndex: 'createTime',
|
||||
sorter: true,
|
||||
sortOrder: sortedInfo.columnKey === 'createTime' && sortedInfo.order
|
||||
}, {
|
||||
title: '修改时间',
|
||||
dataIndex: 'modifyTime',
|
||||
sorter: true,
|
||||
sortOrder: sortedInfo.columnKey === 'modifyTime' && sortedInfo.order
|
||||
}, {
|
||||
title: '操作',
|
||||
dataIndex: 'operation',
|
||||
scopedSlots: { customRender: 'operation' },
|
||||
fixed: 'right',
|
||||
width: 120
|
||||
}]
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
this.fetch()
|
||||
},
|
||||
methods: {
|
||||
onSelectChange (selectedRowKeys) {
|
||||
this.selectedRowKeys = selectedRowKeys
|
||||
},
|
||||
handleDeptAddClose () {
|
||||
this.deptAddVisiable = false
|
||||
},
|
||||
handleDeptAddSuccess () {
|
||||
this.deptAddVisiable = false
|
||||
this.$message.success('新增部门成功')
|
||||
this.fetch()
|
||||
},
|
||||
add () {
|
||||
this.deptAddVisiable = true
|
||||
},
|
||||
handleDeptEditClose () {
|
||||
this.deptEditVisiable = false
|
||||
},
|
||||
handleDeptEditSuccess () {
|
||||
this.deptEditVisiable = false
|
||||
this.$message.success('修改部门成功')
|
||||
this.fetch()
|
||||
},
|
||||
edit (record) {
|
||||
this.deptEditVisiable = true
|
||||
this.$refs.deptEdit.setFormValues(record)
|
||||
},
|
||||
handleDateChange (value) {
|
||||
if (value) {
|
||||
this.queryParams.createTimeFrom = value[0]
|
||||
this.queryParams.createTimeTo = value[1]
|
||||
}
|
||||
},
|
||||
batchDelete () {
|
||||
if (!this.selectedRowKeys.length) {
|
||||
this.$message.warning('请选择需要删除的记录')
|
||||
return
|
||||
}
|
||||
let that = this
|
||||
this.$confirm({
|
||||
title: '确定删除所选中的记录?',
|
||||
content: '当您点击确定按钮后,这些记录将会被彻底删除,如果其包含子记录,也将一并删除!',
|
||||
centered: true,
|
||||
onOk () {
|
||||
that.$delete('dept/' + that.selectedRowKeys.join(',')).then(() => {
|
||||
that.$message.success('删除成功')
|
||||
that.selectedRowKeys = []
|
||||
that.fetch()
|
||||
})
|
||||
},
|
||||
onCancel () {
|
||||
that.selectedRowKeys = []
|
||||
}
|
||||
})
|
||||
},
|
||||
exportExcel () {
|
||||
let {sortedInfo} = this
|
||||
let sortField, sortOrder
|
||||
// 获取当前列的排序和列的过滤规则
|
||||
if (sortedInfo) {
|
||||
sortField = sortedInfo.field
|
||||
sortOrder = sortedInfo.order
|
||||
}
|
||||
this.$export('dept/excel', {
|
||||
sortField: sortField,
|
||||
sortOrder: sortOrder,
|
||||
...this.queryParams
|
||||
})
|
||||
},
|
||||
search () {
|
||||
let {sortedInfo} = this
|
||||
let sortField, sortOrder
|
||||
// 获取当前列的排序和列的过滤规则
|
||||
if (sortedInfo) {
|
||||
sortField = sortedInfo.field
|
||||
sortOrder = sortedInfo.order
|
||||
}
|
||||
this.fetch({
|
||||
sortField: sortField,
|
||||
sortOrder: sortOrder,
|
||||
...this.queryParams
|
||||
})
|
||||
},
|
||||
reset () {
|
||||
// 取消选中
|
||||
this.selectedRowKeys = []
|
||||
// 重置列排序规则
|
||||
this.sortedInfo = null
|
||||
// 重置查询参数
|
||||
this.queryParams = {}
|
||||
// 清空时间选择
|
||||
this.$refs.createTime.reset()
|
||||
this.fetch()
|
||||
},
|
||||
handleTableChange (pagination, filters, sorter) {
|
||||
this.sortedInfo = sorter
|
||||
this.fetch({
|
||||
sortField: sorter.field,
|
||||
sortOrder: sorter.order,
|
||||
...this.queryParams,
|
||||
...filters
|
||||
})
|
||||
},
|
||||
fetch (params = {}) {
|
||||
this.loading = true
|
||||
this.$get('dept', {
|
||||
...params
|
||||
}).then((r) => {
|
||||
let data = r.data
|
||||
this.loading = false
|
||||
this.dataSource = data.rows.children
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@import "../../../../static/less/Common";
|
||||
</style>
|
|
@ -0,0 +1,123 @@
|
|||
<template>
|
||||
<a-drawer
|
||||
title="新增部门"
|
||||
:maskClosable="false"
|
||||
width=650
|
||||
placement="right"
|
||||
:closable="false"
|
||||
@close="onClose"
|
||||
:visible="deptAddVisiable"
|
||||
style="height: calc(100% - 55px);overflow: auto;padding-bottom: 53px;">
|
||||
<a-form :form="form">
|
||||
<a-form-item label='部门名称' v-bind="formItemLayout">
|
||||
<a-input v-model="dept.deptName"
|
||||
v-decorator="['deptName',
|
||||
{rules: [
|
||||
{ required: true, message: '部门名称不能为空'},
|
||||
{ max: 20, message: '长度不能超过20个字符'}
|
||||
]}]"/>
|
||||
</a-form-item>
|
||||
<a-form-item label='部门排序' v-bind="formItemLayout">
|
||||
<a-input-number v-model="dept.orderNum" style="width: 100%"/>
|
||||
</a-form-item>
|
||||
<a-form-item label='上级部门'
|
||||
style="margin-bottom: 2rem"
|
||||
v-bind="formItemLayout">
|
||||
<a-tree
|
||||
:key="deptTreeKeys"
|
||||
:checkable="true"
|
||||
:checkStrictly="true"
|
||||
@check="handleCheck"
|
||||
@expand="handleExpand"
|
||||
:expandedKeys="expandedKeys"
|
||||
:treeData="deptTreeData">
|
||||
</a-tree>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
<div class="drawer-bootom-button">
|
||||
<a-popconfirm title="确定放弃编辑?" @confirm="onClose" okText="确定" cancelText="取消">
|
||||
<a-button style="margin-right: .8rem">取消</a-button>
|
||||
</a-popconfirm>
|
||||
<a-button @click="handleSubmit" type="primary" :loading="loading">提交</a-button>
|
||||
</div>
|
||||
</a-drawer>
|
||||
</template>
|
||||
<script>
|
||||
const formItemLayout = {
|
||||
labelCol: { span: 3 },
|
||||
wrapperCol: { span: 18 }
|
||||
}
|
||||
export default {
|
||||
name: 'DeptAdd',
|
||||
props: {
|
||||
deptAddVisiable: {
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
loading: false,
|
||||
formItemLayout,
|
||||
form: this.$form.createForm(this),
|
||||
dept: {},
|
||||
checkedKeys: [],
|
||||
expandedKeys: [],
|
||||
deptTreeData: [],
|
||||
deptTreeKeys: +new Date()
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
reset () {
|
||||
this.loading = false
|
||||
this.deptTreeKeys = +new Date()
|
||||
this.expandedKeys = this.checkedKeys = []
|
||||
this.dept = {}
|
||||
this.form.resetFields()
|
||||
},
|
||||
onClose () {
|
||||
this.reset()
|
||||
this.$emit('close')
|
||||
},
|
||||
handleCheck (checkedKeys) {
|
||||
this.checkedKeys = checkedKeys
|
||||
},
|
||||
handleExpand (expandedKeys) {
|
||||
this.expandedKeys = expandedKeys
|
||||
},
|
||||
handleSubmit () {
|
||||
let checkedArr = Object.is(this.checkedKeys.checked, undefined) ? this.checkedKeys : this.checkedKeys.checked
|
||||
if (checkedArr.length > 1) {
|
||||
this.$message.error('最多只能选择一个上级部门,请修改')
|
||||
return
|
||||
}
|
||||
this.form.validateFields((err, values) => {
|
||||
if (!err) {
|
||||
this.loading = true
|
||||
if (checkedArr.length) {
|
||||
this.dept.parentId = checkedArr[0]
|
||||
} else {
|
||||
this.dept.parentId = ''
|
||||
}
|
||||
this.$post('dept', {
|
||||
...this.dept
|
||||
}).then(() => {
|
||||
this.reset()
|
||||
this.$emit('success')
|
||||
}).catch(() => {
|
||||
this.loading = false
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
deptAddVisiable () {
|
||||
if (this.deptAddVisiable) {
|
||||
this.$get('dept').then((r) => {
|
||||
this.deptTreeData = r.data.rows.children
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,142 @@
|
|||
<template>
|
||||
<a-drawer
|
||||
title="修改按钮"
|
||||
:maskClosable="false"
|
||||
width=650
|
||||
placement="right"
|
||||
:closable="false"
|
||||
@close="onClose"
|
||||
:visible="deptEditVisiable"
|
||||
style="height: calc(100% - 55px);overflow: auto;padding-bottom: 53px;">
|
||||
<a-form :form="form">
|
||||
<a-form-item label='部门名称' v-bind="formItemLayout">
|
||||
<a-input v-decorator="['deptName',
|
||||
{rules: [
|
||||
{ required: true, message: '部门名称不能为空'},
|
||||
{ max: 20, message: '长度不能超过20个字符'}
|
||||
]}]"/>
|
||||
</a-form-item>
|
||||
<a-form-item label='部门排序' v-bind="formItemLayout">
|
||||
<a-input-number v-decorator="['orderNum']" style="width: 100%"/>
|
||||
</a-form-item>
|
||||
<a-form-item label='上级部门'
|
||||
style="margin-bottom: 2rem"
|
||||
v-bind="formItemLayout">
|
||||
<a-tree
|
||||
:key="deptTreeKey"
|
||||
:checkable="true"
|
||||
:checkStrictly="true"
|
||||
@check="handleCheck"
|
||||
@expand="handleExpand"
|
||||
:expandedKeys="expandedKeys"
|
||||
:defaultCheckedKeys="defaultCheckedKeys"
|
||||
:treeData="deptTreeData">
|
||||
</a-tree>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
<div class="drawer-bootom-button">
|
||||
<a-popconfirm title="确定放弃编辑?" @confirm="onClose" okText="确定" cancelText="取消">
|
||||
<a-button style="margin-right: .8rem">取消</a-button>
|
||||
</a-popconfirm>
|
||||
<a-button @click="handleSubmit" type="primary" :loading="loading">提交</a-button>
|
||||
</div>
|
||||
</a-drawer>
|
||||
</template>
|
||||
<script>
|
||||
const formItemLayout = {
|
||||
labelCol: { span: 3 },
|
||||
wrapperCol: { span: 18 }
|
||||
}
|
||||
export default {
|
||||
name: 'DeptEdit',
|
||||
props: {
|
||||
deptEditVisiable: {
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
loading: false,
|
||||
formItemLayout,
|
||||
form: this.$form.createForm(this),
|
||||
deptTreeKey: +new Date(),
|
||||
dept: {},
|
||||
checkedKeys: [],
|
||||
expandedKeys: [],
|
||||
defaultCheckedKeys: [],
|
||||
deptTreeData: []
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
reset () {
|
||||
this.loading = false
|
||||
this.deptTreeKey = +new Date()
|
||||
this.expandedKeys = this.checkedKeys = this.defaultCheckedKeys = []
|
||||
this.button = {}
|
||||
this.form.resetFields()
|
||||
},
|
||||
onClose () {
|
||||
this.reset()
|
||||
this.$emit('close')
|
||||
},
|
||||
handleCheck (checkedKeys) {
|
||||
this.checkedKeys = checkedKeys
|
||||
},
|
||||
handleExpand (expandedKeys) {
|
||||
this.expandedKeys = expandedKeys
|
||||
},
|
||||
setFormValues ({...dept}) {
|
||||
this.form.getFieldDecorator('deptName')
|
||||
this.form.setFieldsValue({'deptName': dept.text})
|
||||
this.form.getFieldDecorator('orderNum')
|
||||
this.form.setFieldsValue({'orderNum': dept.order})
|
||||
if (dept.parentId !== '0') {
|
||||
this.defaultCheckedKeys.push(dept.parentId)
|
||||
this.checkedKeys = this.defaultCheckedKeys
|
||||
this.expandedKeys = this.checkedKeys
|
||||
}
|
||||
this.dept.deptId = dept.id
|
||||
},
|
||||
handleSubmit () {
|
||||
let checkedArr = Object.is(this.checkedKeys.checked, undefined) ? this.checkedKeys : this.checkedKeys.checked
|
||||
if (checkedArr.length > 1) {
|
||||
this.$message.error('最多只能选择一个上级部门,请修改')
|
||||
return
|
||||
}
|
||||
if (checkedArr[0] === this.dept.deptId) {
|
||||
this.$message.error('不能选择自己作为上级部门,请修改')
|
||||
return
|
||||
}
|
||||
this.form.validateFields((err, values) => {
|
||||
if (!err) {
|
||||
this.loading = true
|
||||
let dept = this.form.getFieldsValue()
|
||||
dept.parentId = checkedArr[0]
|
||||
if (Object.is(dept.parentId, undefined)) {
|
||||
dept.parentId = 0
|
||||
}
|
||||
dept.deptId = this.dept.deptId
|
||||
this.$put('dept', {
|
||||
...dept
|
||||
}).then(() => {
|
||||
this.reset()
|
||||
this.$emit('success')
|
||||
}).catch(() => {
|
||||
this.loading = false
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
deptEditVisiable () {
|
||||
if (this.deptEditVisiable) {
|
||||
this.$get('dept').then((r) => {
|
||||
this.deptTreeData = r.data.rows.children
|
||||
this.deptTreeKey = +new Date()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,35 @@
|
|||
<template>
|
||||
<a-tree-select
|
||||
:allowClear="true"
|
||||
:dropdownStyle="{ maxHeight: '220px', overflow: 'auto' }"
|
||||
:treeData="deptTreeData"
|
||||
v-model="value">
|
||||
</a-tree-select>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'DetpInputTree',
|
||||
data () {
|
||||
return {
|
||||
deptTreeData: [],
|
||||
value: undefined
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
reset () {
|
||||
this.value = ''
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
this.$get('dept').then((r) => {
|
||||
this.deptTreeData = r.data.rows.children
|
||||
})
|
||||
},
|
||||
watch: {
|
||||
value (value) {
|
||||
this.$emit('change', value)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,272 @@
|
|||
<template>
|
||||
<a-card :bordered="false" class="card-area">
|
||||
<div :class="advanced ? 'search' : null">
|
||||
<!-- 搜索区域 -->
|
||||
<a-form layout="horizontal">
|
||||
<div :class="advanced ? null: 'fold'">
|
||||
<a-row >
|
||||
<a-col :md="12" :sm="24" >
|
||||
<a-form-item
|
||||
label="名称"
|
||||
:labelCol="{span: 5}"
|
||||
:wrapperCol="{span: 18, offset: 1}">
|
||||
<a-input v-model="queryParams.name"/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :md="12" :sm="24" >
|
||||
<a-form-item
|
||||
label="创建时间"
|
||||
:labelCol="{span: 5}"
|
||||
:wrapperCol="{span: 18, offset: 1}">
|
||||
<range-date @change="handleDateChange" ref="createTime"></range-date>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
<span style="float: right; margin-top: 3px;">
|
||||
<a-button type="primary" @click="search">查询</a-button>
|
||||
<a-button style="margin-left: 8px" @click="reset">重置</a-button>
|
||||
</span>
|
||||
</a-form>
|
||||
</div>
|
||||
<div>
|
||||
<div class="operator">
|
||||
<a-button v-hasPermission="'domain:add'" type="primary" ghost @click="add">新增</a-button>
|
||||
<a-button v-hasPermission="'domain:delete'" @click="batchDelete">删除</a-button>
|
||||
<a-dropdown v-hasPermission="'domain:export'">
|
||||
<a-menu slot="overlay">
|
||||
<a-menu-item key="export-data" @click="exportExcel">导出Excel</a-menu-item>
|
||||
</a-menu>
|
||||
<a-button>
|
||||
更多操作 <a-icon type="down" />
|
||||
</a-button>
|
||||
</a-dropdown>
|
||||
</div>
|
||||
<!-- 表格区域 -->
|
||||
<a-table :columns="columns"
|
||||
:dataSource="dataSource"
|
||||
:pagination="pagination"
|
||||
:loading="loading"
|
||||
:scroll="{ x: 900 }"
|
||||
:rowSelection="{selectedRowKeys: selectedRowKeys, onChange: onSelectChange}"
|
||||
@change="handleTableChange">
|
||||
<template slot="operation" slot-scope="text, record">
|
||||
<a-icon v-hasPermission="'domain:update'" type="setting" theme="twoTone" twoToneColor="#4a9ff5" @click="edit(record)" title="修改"></a-icon>
|
||||
<a-icon type="eye" theme="twoTone" twoToneColor="#42b983" @click="view(record)" title="查看"></a-icon>
|
||||
</template>
|
||||
</a-table>
|
||||
</div>
|
||||
<!-- 新增域 -->
|
||||
<domain-add
|
||||
@success="handleDomainAddSuccess"
|
||||
@close="handleDomainAddClose"
|
||||
:domainAddVisiable="domainAdd.visible">
|
||||
</domain-add>
|
||||
<!-- 修改域 -->
|
||||
<domain-edit
|
||||
ref="domainEdit"
|
||||
@success="handleDomainEditSuccess"
|
||||
@close="handleDomainEditClose"
|
||||
:domainEditVisible="domainEdit.visible"
|
||||
:domainEditData="domainEdit.data">
|
||||
</domain-edit>
|
||||
<domain-info
|
||||
ref="domainInfo"
|
||||
@close="handleDomainInfoClose"
|
||||
:domainInfoVisible="domainInfo.visible"
|
||||
:domainInfoData="domainInfo.data">
|
||||
</domain-info>
|
||||
</a-card>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import RangeDate from '@/components/datetime/RangeDate'
|
||||
import DomainAdd from './DomainAdd'
|
||||
import DomainEdit from './DomainEdit'
|
||||
import DomainInfo from './DomainInfo'
|
||||
|
||||
export default {
|
||||
name: 'Domain',
|
||||
components: {DomainAdd, DomainEdit, DomainInfo, RangeDate},
|
||||
data () {
|
||||
return {
|
||||
advanced: false,
|
||||
domainAdd: {
|
||||
visible: false
|
||||
},
|
||||
domainEdit: {
|
||||
visible: false,
|
||||
data: {}
|
||||
},
|
||||
domainInfo: {
|
||||
visible: false,
|
||||
data: {}
|
||||
},
|
||||
dataSource: [],
|
||||
selectedRowKeys: [],
|
||||
queryParams: {},
|
||||
sortedInfo: null,
|
||||
pagination: {
|
||||
defaultPageSize: 10000000,
|
||||
hideOnSinglePage: true,
|
||||
indentSize: 100
|
||||
},
|
||||
loading: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
columns () {
|
||||
let { sortedInfo } = this
|
||||
sortedInfo = sortedInfo || {}
|
||||
return [{
|
||||
title: '名称',
|
||||
dataIndex: 'name'
|
||||
}, {
|
||||
title: '编号',
|
||||
dataIndex: 'order'
|
||||
}, {
|
||||
title: '创建时间',
|
||||
dataIndex: 'createTime',
|
||||
sorter: true,
|
||||
sortOrder: sortedInfo.columnKey === 'createTime' && sortedInfo.order
|
||||
}, {
|
||||
title: '修改时间',
|
||||
dataIndex: 'modifyTime',
|
||||
sorter: true,
|
||||
sortOrder: sortedInfo.columnKey === 'modifyTime' && sortedInfo.order
|
||||
}, {
|
||||
title: '操作',
|
||||
dataIndex: 'operation',
|
||||
scopedSlots: { customRender: 'operation' },
|
||||
fixed: 'right',
|
||||
width: 120
|
||||
}]
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
this.fetch()
|
||||
},
|
||||
methods: {
|
||||
onSelectChange (selectedRowKeys) {
|
||||
this.selectedRowKeys = selectedRowKeys
|
||||
},
|
||||
handleDomainAddClose () {
|
||||
this.domainAdd.visible = false
|
||||
},
|
||||
handleDomainAddSuccess () {
|
||||
this.domainAdd.visible = false
|
||||
this.$message.success('新增域成功')
|
||||
this.fetch()
|
||||
},
|
||||
add () {
|
||||
this.domainAdd.visible = true
|
||||
},
|
||||
handleDomainEditClose () {
|
||||
this.domainEdit.visible = false
|
||||
},
|
||||
handleDomainEditSuccess () {
|
||||
this.domainEdit.visible = false
|
||||
this.$message.success('修改域成功')
|
||||
this.fetch()
|
||||
},
|
||||
handleDomainInfoClose () {
|
||||
this.domainInfo.visible = false
|
||||
},
|
||||
view (record) {
|
||||
this.domainInfo.data = record
|
||||
this.domainInfo.visible = true
|
||||
},
|
||||
edit (record) {
|
||||
this.domainEdit.visible = true
|
||||
this.$refs.domainEdit.setFormValues(record)
|
||||
},
|
||||
handleDateChange (value) {
|
||||
if (value) {
|
||||
this.queryParams.createTimeFrom = value[0]
|
||||
this.queryParams.createTimeTo = value[1]
|
||||
}
|
||||
},
|
||||
batchDelete () {
|
||||
if (!this.selectedRowKeys.length) {
|
||||
this.$message.warning('请选择需要删除的记录')
|
||||
return
|
||||
}
|
||||
let that = this
|
||||
this.$confirm({
|
||||
title: '确定删除所选中的记录?',
|
||||
content: '当您点击确定按钮后,这些记录将会被彻底删除,如果其包含子记录,也将一并删除!',
|
||||
centered: true,
|
||||
onOk () {
|
||||
that.$delete('domains/' + that.selectedRowKeys.join(',')).then(() => {
|
||||
that.$message.success('删除成功')
|
||||
that.selectedRowKeys = []
|
||||
that.fetch()
|
||||
})
|
||||
},
|
||||
onCancel () {
|
||||
that.selectedRowKeys = []
|
||||
}
|
||||
})
|
||||
},
|
||||
exportExcel () {
|
||||
let {sortedInfo} = this
|
||||
let sortField, sortOrder
|
||||
// 获取当前列的排序和列的过滤规则
|
||||
if (sortedInfo) {
|
||||
sortField = sortedInfo.field
|
||||
sortOrder = sortedInfo.order
|
||||
}
|
||||
this.$export('domains/excel', {
|
||||
sortField: sortField,
|
||||
sortOrder: sortOrder,
|
||||
...this.queryParams
|
||||
})
|
||||
},
|
||||
search () {
|
||||
let {sortedInfo} = this
|
||||
let sortField, sortOrder
|
||||
// 获取当前列的排序和列的过滤规则
|
||||
if (sortedInfo) {
|
||||
sortField = sortedInfo.field
|
||||
sortOrder = sortedInfo.order
|
||||
}
|
||||
this.fetch({
|
||||
sortField: sortField,
|
||||
sortOrder: sortOrder,
|
||||
...this.queryParams
|
||||
})
|
||||
},
|
||||
reset () {
|
||||
// 取消选中
|
||||
this.selectedRowKeys = []
|
||||
// 重置列排序规则
|
||||
this.sortedInfo = null
|
||||
// 重置查询参数
|
||||
this.queryParams = {}
|
||||
// 清空时间选择
|
||||
this.$refs.createTime.reset()
|
||||
this.fetch()
|
||||
},
|
||||
handleTableChange (pagination, filters, sorter) {
|
||||
this.sortedInfo = sorter
|
||||
this.fetch({
|
||||
sortField: sorter.field,
|
||||
sortOrder: sorter.order,
|
||||
...this.queryParams,
|
||||
...filters
|
||||
})
|
||||
},
|
||||
fetch (params = {}) {
|
||||
this.loading = true
|
||||
this.$get('domains', {
|
||||
...params
|
||||
}).then((r) => {
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@import "../../../../static/less/Common";
|
||||
</style>
|
|
@ -0,0 +1,99 @@
|
|||
<template>
|
||||
<a-drawer
|
||||
title="新增域"
|
||||
:maskClosable="false"
|
||||
width=650
|
||||
placement="right"
|
||||
:closable="false"
|
||||
@close="onClose"
|
||||
:visible="domainAddVisiable"
|
||||
style="height: calc(100% - 55px);overflow: auto;padding-bottom: 53px;">
|
||||
<a-form :form="form">
|
||||
<a-form-item label='域名称' v-bind="formItemLayout">
|
||||
<a-input v-model="domain.name"
|
||||
v-decorator="['name',
|
||||
{rules: [
|
||||
{ required: true, message: '部门名称不能为空'},
|
||||
{ max: 20, message: '长度不能超过20个字符'}
|
||||
]}]"/>
|
||||
</a-form-item>
|
||||
<a-form-item label='域编号' v-bind="formItemLayout">
|
||||
<a-input-number v-model="domain.domainid" style="width: 100%"/>
|
||||
</a-form-item>
|
||||
<a-form-item label='域描述' v-bind="formItemLayout">
|
||||
<a-input v-model="domain.description" style="width: 100%"/>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
<div class="drawer-bootom-button">
|
||||
<a-popconfirm title="确定放弃编辑?" @confirm="onClose" okText="确定" cancelText="取消">
|
||||
<a-button style="margin-right: .8rem">取消</a-button>
|
||||
</a-popconfirm>
|
||||
<a-button @click="handleSubmit" type="primary" :loading="loading">提交</a-button>
|
||||
</div>
|
||||
</a-drawer>
|
||||
</template>
|
||||
<script>
|
||||
const formItemLayout = {
|
||||
labelCol: { span: 3 },
|
||||
wrapperCol: { span: 18 }
|
||||
}
|
||||
export default {
|
||||
name: 'DomainAdd',
|
||||
props: {
|
||||
domainAddVisiable: {
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
loading: false,
|
||||
formItemLayout,
|
||||
form: this.$form.createForm(this),
|
||||
domain: {},
|
||||
enable: true
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
reset () {
|
||||
this.loading = false
|
||||
this.expandedKeys = this.checkedKeys = []
|
||||
this.domain = {}
|
||||
this.form.resetFields()
|
||||
},
|
||||
onClose () {
|
||||
this.reset()
|
||||
this.$emit('close')
|
||||
},
|
||||
|
||||
handleSubmit () {
|
||||
let checkedArr = Object.is(this.checkedKeys.checked, undefined) ? this.checkedKeys : this.checkedKeys.checked
|
||||
this.form.validateFields((err, values) => {
|
||||
if (!err) {
|
||||
this.loading = true
|
||||
if (checkedArr.length) {
|
||||
this.dept.parentId = checkedArr[0]
|
||||
} else {
|
||||
this.dept.parentId = ''
|
||||
}
|
||||
this.$post('auth/v1/domains', {
|
||||
...this.dept
|
||||
}).then(() => {
|
||||
this.reset()
|
||||
this.$emit('success')
|
||||
}).catch(() => {
|
||||
this.loading = false
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
domainAddVisiable () {
|
||||
if (this.domainAddVisiable) {
|
||||
this.$get('/auth/v1/domains').then((r) => {
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,112 @@
|
|||
<template>
|
||||
<a-drawer
|
||||
title="修改域信息"
|
||||
:maskClosable="false"
|
||||
width=650
|
||||
placement="right"
|
||||
:closable="false"
|
||||
@close="onClose"
|
||||
:visible="domainEditVisible"
|
||||
style="height: calc(100% - 55px);overflow: auto;padding-bottom: 53px;">
|
||||
<a-form :form="form">
|
||||
<a-form-item label='域名称' v-bind="formItemLayout">
|
||||
<a-input v-decorator="['domainName',
|
||||
{rules: [
|
||||
{ required: true, message: '域名称不能为空'},
|
||||
{ max: 20, message: '长度不能超过20个字符'}
|
||||
]}]"/>
|
||||
</a-form-item>
|
||||
<a-form-item label='域编号' v-bind="formItemLayout">
|
||||
<a-input-number v-decorator="['orderNum']" style="width: 100%"/>
|
||||
</a-form-item>
|
||||
<a-form-item label='域描述' v-bind="formItemLayout">
|
||||
<a-input v-decorator="['domainId']" style="width: 100%"/>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
<div class="drawer-bootom-button">
|
||||
<a-popconfirm title="确定放弃编辑?" @confirm="onClose" okText="确定" cancelText="取消">
|
||||
<a-button style="margin-right: .8rem">取消</a-button>
|
||||
</a-popconfirm>
|
||||
<a-button @click="handleSubmit" type="primary" :loading="loading">提交</a-button>
|
||||
</div>
|
||||
</a-drawer>
|
||||
</template>
|
||||
<script>
|
||||
const formItemLayout = {
|
||||
labelCol: { span: 3 },
|
||||
wrapperCol: { span: 18 }
|
||||
}
|
||||
export default {
|
||||
name: 'DomainEdit',
|
||||
props: {
|
||||
domainEditVisible: {
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
loading: false,
|
||||
formItemLayout,
|
||||
form: this.$form.createForm(this),
|
||||
domain: {}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
reset () {
|
||||
this.loading = false
|
||||
this.button = {}
|
||||
this.form.resetFields()
|
||||
},
|
||||
onClose () {
|
||||
this.reset()
|
||||
this.$emit('close')
|
||||
},
|
||||
handleCheck (checkedKeys) {
|
||||
this.checkedKeys = checkedKeys
|
||||
},
|
||||
handleExpand (expandedKeys) {
|
||||
this.expandedKeys = expandedKeys
|
||||
},
|
||||
setFormValues ({...domain}) {
|
||||
this.form.getFieldDecorator('domainName')
|
||||
this.form.setFieldsValue({'domainName': domain.name})
|
||||
this.form.getFieldDecorator('orderNum')
|
||||
this.form.setFieldsValue({'orderNum': domain.order})
|
||||
this.form.getFieldDecorator('domainId')
|
||||
this.form.setFieldsValue({'domainId': domain.domainId})
|
||||
this.domain.domainId = domain.domainId
|
||||
},
|
||||
handleSubmit () {
|
||||
this.form.validateFields((err, values) => {
|
||||
if (!err) {
|
||||
this.loading = true
|
||||
let domain = this.form.getFieldsValue()
|
||||
domain.domainId = this.domain.domainId
|
||||
this.$put('domains', {
|
||||
...domain
|
||||
}).then(() => {
|
||||
this.reset()
|
||||
this.$emit('success')
|
||||
}).catch(() => {
|
||||
this.loading = false
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
domainEditVisible () {
|
||||
if (this.domainEditVisible) {
|
||||
this.$get('menu').then((r) => {
|
||||
this.allTreeKeys = r.data.ids
|
||||
this.$get('role/menu/' + this.roleInfoData.roleId).then((r) => {
|
||||
this.defaultCheckedKeys.splice(0, this.defaultCheckedKeys.length, r.data)
|
||||
this.checkedKeys = r.data
|
||||
this.expandedKeys = r.data
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,44 @@
|
|||
<template>
|
||||
<a-drawer
|
||||
title="域信息"
|
||||
:maskClosable="false"
|
||||
width=650
|
||||
placement="right"
|
||||
:closable="true"
|
||||
@close="close"
|
||||
:visible="domainInfoVisible"
|
||||
style="height: calc(100% - 55px);overflow: auto;padding-bottom: 53px;">
|
||||
<p><a-icon type="crown" /> 域名称:{{domainInfoData.roleName}}</p>
|
||||
<p :title="domainInfoData.remark"><a-icon type="book" /> 域描述:{{domainInfoData.remark}}</p>
|
||||
<p><a-icon type="clock-circle" /> 创建时间:{{domainInfoData.createTime}}</p>
|
||||
<p><a-icon type="clock-circle" /> 修改时间:{{domainInfoData.modifyTime? domainInfoData.modifyTime : '暂未修改'}}</p>
|
||||
</a-drawer>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
name: 'DomainInfo',
|
||||
props: {
|
||||
domainInfoVisible: {
|
||||
require: true,
|
||||
default: false
|
||||
},
|
||||
domainInfoData: {
|
||||
require: true
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
key: +new Date(),
|
||||
loading: true,
|
||||
checkedKeys: [],
|
||||
menuTreeData: []
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
close () {
|
||||
this.$emit('close')
|
||||
this.checkedKeys = []
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,151 @@
|
|||
<template>
|
||||
<a-drawer
|
||||
title="新增按钮"
|
||||
:maskClosable="false"
|
||||
width=650
|
||||
placement="right"
|
||||
:closable="false"
|
||||
@close="onClose"
|
||||
:visible="buttonAddVisiable"
|
||||
style="height: calc(100% - 55px);overflow: auto;padding-bottom: 53px;">
|
||||
<a-form :form="form">
|
||||
<a-form-item label='按钮名称' v-bind="formItemLayout">
|
||||
<a-input v-model="button.menuName"
|
||||
v-decorator="['menuName',
|
||||
{rules: [
|
||||
{ required: true, message: '按钮名称不能为空'},
|
||||
{ max: 10, message: '长度不能超过10个字符'}
|
||||
]}]"/>
|
||||
</a-form-item>
|
||||
<a-form-item label='相关权限' v-bind="formItemLayout">
|
||||
<a-input v-model="button.perms"
|
||||
v-decorator="['perms',
|
||||
{rules: [
|
||||
{ max: 50, message: '长度不能超过50个字符'}
|
||||
]}]"/>
|
||||
</a-form-item>
|
||||
<a-form-item label='上级菜单'
|
||||
style="margin-bottom: 2rem"
|
||||
v-bind="formItemLayout">
|
||||
<a-tree
|
||||
:key="menuTreeKey"
|
||||
:checkable="true"
|
||||
:checkStrictly="true"
|
||||
@check="handleCheck"
|
||||
@expand="handleExpand"
|
||||
:expandedKeys="expandedKeys"
|
||||
:treeData="menuTreeData">
|
||||
</a-tree>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
<div class="drawer-bootom-button">
|
||||
<a-dropdown style="float: left" :trigger="['click']" placement="topCenter">
|
||||
<a-menu slot="overlay">
|
||||
<a-menu-item key="1" @click="expandAll">展开所有</a-menu-item>
|
||||
<a-menu-item key="2" @click="closeAll">合并所有</a-menu-item>
|
||||
</a-menu>
|
||||
<a-button>
|
||||
树操作 <a-icon type="up" />
|
||||
</a-button>
|
||||
</a-dropdown>
|
||||
<a-popconfirm title="确定放弃编辑?" @confirm="onClose" okText="确定" cancelText="取消">
|
||||
<a-button style="margin-right: .8rem">取消</a-button>
|
||||
</a-popconfirm>
|
||||
<a-button @click="handleSubmit" type="primary" :loading="loading">提交</a-button>
|
||||
</div>
|
||||
</a-drawer>
|
||||
</template>
|
||||
<script>
|
||||
const formItemLayout = {
|
||||
labelCol: { span: 3 },
|
||||
wrapperCol: { span: 18 }
|
||||
}
|
||||
export default {
|
||||
name: 'ButtonAdd',
|
||||
props: {
|
||||
buttonAddVisiable: {
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
loading: false,
|
||||
formItemLayout,
|
||||
form: this.$form.createForm(this),
|
||||
menuTreeKey: +new Date(),
|
||||
button: {},
|
||||
checkedKeys: [],
|
||||
expandedKeys: [],
|
||||
menuTreeData: []
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
reset () {
|
||||
this.loading = false
|
||||
this.menuTreeKey = +new Date()
|
||||
this.expandedKeys = this.checkedKeys = []
|
||||
this.button = {}
|
||||
this.form.resetFields()
|
||||
},
|
||||
onClose () {
|
||||
this.reset()
|
||||
this.$emit('close')
|
||||
},
|
||||
handleCheck (checkedKeys) {
|
||||
this.checkedKeys = checkedKeys
|
||||
},
|
||||
expandAll () {
|
||||
this.expandedKeys = this.allTreeKeys
|
||||
},
|
||||
closeAll () {
|
||||
this.expandedKeys = []
|
||||
},
|
||||
handleExpand (expandedKeys) {
|
||||
this.expandedKeys = expandedKeys
|
||||
},
|
||||
handleSubmit () {
|
||||
let checkedArr = Object.is(this.checkedKeys.checked, undefined) ? this.checkedKeys : this.checkedKeys.checked
|
||||
if (!checkedArr.length) {
|
||||
this.$message.error('请为按钮选择一个上级菜单')
|
||||
return
|
||||
}
|
||||
if (checkedArr.length > 1) {
|
||||
this.$message.error('最多只能选择一个上级菜单,请修改')
|
||||
return
|
||||
}
|
||||
this.form.validateFields((err, values) => {
|
||||
if (!err) {
|
||||
this.loading = true
|
||||
if (checkedArr.length) {
|
||||
this.button.parentId = checkedArr[0]
|
||||
} else {
|
||||
this.button.parentId = ''
|
||||
}
|
||||
// 0 表示菜单 1 表示按钮
|
||||
this.button.type = '1'
|
||||
this.$post('menu', {
|
||||
...this.button
|
||||
}).then(() => {
|
||||
this.reset()
|
||||
this.$emit('success')
|
||||
}).catch(() => {
|
||||
this.loading = false
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
buttonAddVisiable () {
|
||||
if (this.buttonAddVisiable) {
|
||||
this.$get('menu', {
|
||||
type: '0'
|
||||
}).then((r) => {
|
||||
this.menuTreeData = r.data.rows.children
|
||||
this.allTreeKeys = r.data.ids
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,161 @@
|
|||
<template>
|
||||
<a-drawer
|
||||
title="修改按钮"
|
||||
:maskClosable="false"
|
||||
width=650
|
||||
placement="right"
|
||||
:closable="false"
|
||||
@close="onClose"
|
||||
:visible="buttonEditVisiable"
|
||||
style="height: calc(100% - 55px);overflow: auto;padding-bottom: 53px;">
|
||||
<a-form :form="form">
|
||||
<a-form-item label='按钮名称' v-bind="formItemLayout">
|
||||
<a-input v-decorator="['menuName',
|
||||
{rules: [
|
||||
{ required: true, message: '按钮名称不能为空'},
|
||||
{ max: 10, message: '长度不能超过10个字符'}
|
||||
]}]"/>
|
||||
</a-form-item>
|
||||
<a-form-item label='相关权限' v-bind="formItemLayout">
|
||||
<a-input v-decorator="['perms',
|
||||
{rules: [
|
||||
{ max: 50, message: '长度不能超过50个字符'}
|
||||
]}]"/>
|
||||
</a-form-item>
|
||||
<a-form-item label='上级菜单'
|
||||
style="margin-bottom: 2rem"
|
||||
v-bind="formItemLayout">
|
||||
<a-tree
|
||||
:key="menuTreeKey"
|
||||
:checkable="true"
|
||||
:checkStrictly="true"
|
||||
@check="handleCheck"
|
||||
@expand="handleExpand"
|
||||
:expandedKeys="expandedKeys"
|
||||
:defaultCheckedKeys="defaultCheckedKeys"
|
||||
:treeData="menuTreeData">
|
||||
</a-tree>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
<div class="drawer-bootom-button">
|
||||
<a-dropdown style="float: left" :trigger="['click']" placement="topCenter">
|
||||
<a-menu slot="overlay">
|
||||
<a-menu-item key="1" @click="expandAll">展开所有</a-menu-item>
|
||||
<a-menu-item key="2" @click="closeAll">合并所有</a-menu-item>
|
||||
</a-menu>
|
||||
<a-button>
|
||||
树操作 <a-icon type="up" />
|
||||
</a-button>
|
||||
</a-dropdown>
|
||||
<a-popconfirm title="确定放弃编辑?" @confirm="onClose" okText="确定" cancelText="取消">
|
||||
<a-button style="margin-right: .8rem">取消</a-button>
|
||||
</a-popconfirm>
|
||||
<a-button @click="handleSubmit" type="primary" :loading="loading">提交</a-button>
|
||||
</div>
|
||||
</a-drawer>
|
||||
</template>
|
||||
<script>
|
||||
const formItemLayout = {
|
||||
labelCol: { span: 3 },
|
||||
wrapperCol: { span: 18 }
|
||||
}
|
||||
export default {
|
||||
name: 'ButtonEdit',
|
||||
props: {
|
||||
buttonEditVisiable: {
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
loading: false,
|
||||
formItemLayout,
|
||||
form: this.$form.createForm(this),
|
||||
menuTreeKey: +new Date(),
|
||||
button: {},
|
||||
checkedKeys: [],
|
||||
expandedKeys: [],
|
||||
defaultCheckedKeys: [],
|
||||
menuTreeData: []
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
reset () {
|
||||
this.loading = false
|
||||
this.menuTreeKey = +new Date()
|
||||
this.expandedKeys = this.checkedKeys = this.defaultCheckedKeys = []
|
||||
this.button = {}
|
||||
this.form.resetFields()
|
||||
},
|
||||
onClose () {
|
||||
this.reset()
|
||||
this.$emit('close')
|
||||
},
|
||||
handleCheck (checkedKeys) {
|
||||
this.checkedKeys = checkedKeys
|
||||
},
|
||||
expandAll () {
|
||||
this.expandedKeys = this.allTreeKeys
|
||||
},
|
||||
closeAll () {
|
||||
this.expandedKeys = []
|
||||
},
|
||||
handleExpand (expandedKeys) {
|
||||
this.expandedKeys = expandedKeys
|
||||
},
|
||||
setFormValues ({...menu}) {
|
||||
this.form.getFieldDecorator('menuName')
|
||||
this.form.setFieldsValue({'menuName': menu.text})
|
||||
this.form.getFieldDecorator('perms')
|
||||
this.form.setFieldsValue({'perms': menu.permission})
|
||||
|
||||
this.defaultCheckedKeys.push(menu.parentId)
|
||||
this.checkedKeys = this.defaultCheckedKeys
|
||||
this.expandedKeys = this.checkedKeys
|
||||
this.button.menuId = menu.id
|
||||
},
|
||||
handleSubmit () {
|
||||
let checkedArr = Object.is(this.checkedKeys.checked, undefined) ? this.checkedKeys : this.checkedKeys.checked
|
||||
if (!checkedArr.length) {
|
||||
this.$message.error('请为按钮选择一个上级菜单')
|
||||
return
|
||||
}
|
||||
if (checkedArr.length > 1) {
|
||||
this.$message.error('最多只能选择一个上级菜单,请修改')
|
||||
return
|
||||
}
|
||||
this.form.validateFields((err, values) => {
|
||||
if (!err) {
|
||||
this.loading = true
|
||||
let button = this.form.getFieldsValue()
|
||||
button.parentId = checkedArr[0]
|
||||
// 0 表示菜单 1 表示按钮
|
||||
button.type = '1'
|
||||
button.menuId = this.button.menuId
|
||||
this.$put('menu', {
|
||||
...button
|
||||
}).then(() => {
|
||||
this.reset()
|
||||
this.$emit('success')
|
||||
}).catch(() => {
|
||||
this.loading = false
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
buttonEditVisiable () {
|
||||
if (this.buttonEditVisiable) {
|
||||
this.$get('menu', {
|
||||
type: '0'
|
||||
}).then((r) => {
|
||||
this.menuTreeData = r.data.rows.children
|
||||
this.allTreeKeys = r.data.ids
|
||||
this.menuTreeKey = +new Date()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,35 @@
|
|||
@active-color: #4a4a48;
|
||||
ul {
|
||||
max-height: 700px;
|
||||
overflow-y: auto;
|
||||
padding-left: .5rem;
|
||||
i {
|
||||
font-size: 1.5rem;
|
||||
border: 1px solid #f1f1f1;
|
||||
padding: .2rem;
|
||||
margin: .3rem;
|
||||
cursor: pointer;
|
||||
&.active, &:hover {
|
||||
border-radius: 2px;
|
||||
border-color: @active-color;
|
||||
background-color: @active-color;
|
||||
color: #fff;
|
||||
transition: all .3s;
|
||||
}
|
||||
}
|
||||
li {
|
||||
list-style: none;
|
||||
float: left;
|
||||
width: 5%;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
color: #555;
|
||||
transition: color .3s ease-in-out,background-color .3s ease-in-out;
|
||||
position: relative;
|
||||
margin: 3px 0;
|
||||
border-radius: 4px;
|
||||
background-color: #fff;
|
||||
overflow: hidden;
|
||||
padding: 10px 0 0;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,123 @@
|
|||
<template>
|
||||
<a-modal
|
||||
v-model="show"
|
||||
:width="900"
|
||||
:keyboard="false"
|
||||
:closable="false"
|
||||
:centered="true"
|
||||
@ok="ok"
|
||||
@cancel="cancel"
|
||||
:maskClosable="false"
|
||||
:mask="false"
|
||||
okText="确认"
|
||||
cancelText="取消">
|
||||
<a-tabs>
|
||||
<a-tab-pane tab="方向性图标" key="1">
|
||||
<ul>
|
||||
<li v-for="icon in icons.directionIcons" :key="icon">
|
||||
<a-icon :type="icon" :title="icon" @click="chooseIcon(icon)" :class="{'active':activeIndex === icon}"/>
|
||||
</li>
|
||||
</ul>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane tab="指示性图标" key="2">
|
||||
<ul>
|
||||
<li v-for="icon in icons.suggestionIcons" :key="icon">
|
||||
<a-icon :type="icon" :title="icon" @click="chooseIcon(icon)" :class="{'active':activeIndex === icon}"/>
|
||||
</li>
|
||||
</ul>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane tab="编辑类图标" key="3">
|
||||
<ul>
|
||||
<li v-for="icon in icons.editIcons" :key="icon">
|
||||
<a-icon :type="icon" :title="icon" @click="chooseIcon(icon)" :class="{'active':activeIndex === icon}"/>
|
||||
</li>
|
||||
</ul>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane tab="数据类图标" key="4">
|
||||
<ul>
|
||||
<li v-for="icon in icons.dataIcons" :key="icon">
|
||||
<a-icon :type="icon" :title="icon" @click="chooseIcon(icon)" :class="{'active':activeIndex === icon}"/>
|
||||
</li>
|
||||
</ul>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane tab="网站通用图标" key="5">
|
||||
<ul>
|
||||
<li v-for="icon in icons.webIcons" :key="icon">
|
||||
<a-icon :type="icon" :title="icon" @click="chooseIcon(icon)" :class="{'active':activeIndex === icon}"/>
|
||||
</li>
|
||||
</ul>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane tab="品牌和标识" key="6">
|
||||
<ul>
|
||||
<li v-for="icon in icons.logoIcons" :key="icon">
|
||||
<a-icon :type="icon" :title="icon" @click="chooseIcon(icon)" :class="{'active':activeIndex === icon}"/>
|
||||
</li>
|
||||
</ul>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</a-modal>
|
||||
</template>
|
||||
<script>
|
||||
const directionIcons = ['step-backward', 'step-forward', 'fast-backward', 'fast-forward', 'shrink', 'arrows-alt', 'down', 'up', 'left', 'right', 'caret-up', 'caret-down', 'caret-left', 'caret-right', 'up-circle', 'down-circle', 'left-circle', 'right-circle', 'up-circle-o', 'down-circle-o', 'right-circle-o', 'left-circle-o', 'double-right', 'double-left', 'vertical-left', 'vertical-right', 'forward', 'backward', 'rollback', 'enter', 'retweet', 'swap', 'swap-left', 'swap-right', 'arrow-up', 'arrow-down', 'arrow-left', 'arrow-right', 'play-circle', 'play-circle-o', 'up-square', 'down-square', 'left-square', 'right-square', 'up-square-o', 'down-square-o', 'left-square-o', 'right-square-o', 'login', 'logout', 'menu-fold', 'menu-unfold', 'border-bottom', 'border-horizontal', 'border-inner', 'border-left', 'border-right', 'border-top', 'border-verticle', 'pic-center', 'pic-left', 'pic-right', 'radius-bottomleft', 'radius-bottomright', 'radius-upleft', 'radius-upright', 'fullscreen', 'fullscreen-exit']
|
||||
const suggestionIcons = ['question', 'question-circle', 'plus', 'plus-circle', 'pause', 'pause-circle', 'minus', 'minus-circle', 'plus-square', 'minus-square', 'info', 'info-circle', 'exclamation', 'exclamation-circle', 'close', 'close-circle', 'close-square', 'check', 'check-circle', 'check-square', 'clock-circle', 'warning', 'issues-close', 'stop']
|
||||
const editIcons = ['edit', 'form', 'copy', 'scissor', 'delete', 'snippets', 'diff', 'highlight', 'align-center', 'align-left', 'align-right', 'bg-colors', 'bold', 'italic', 'underline', 'strikethrough', 'redo', 'undo', 'zoom-in', 'zoom-out', 'font-colors', 'font-size', 'line-height', 'colum-height', 'dash', 'small-dash', 'sort-ascending', 'sort-descending', 'drag', 'ordered-list', 'radius-setting']
|
||||
const dataIcons = ['area-chart', 'pie-chart', 'bar-chart', 'dot-chart', 'line-chart', 'radar-chart', 'heat-map', 'fall', 'rise', 'stock', 'box-plot', 'fund', 'sliders']
|
||||
const webIcons = ['lock', 'unlock', 'bars', 'book', 'calendar', 'cloud', 'cloud-download', 'code', 'copy', 'credit-card', 'delete', 'desktop', 'download', 'ellipsis', 'file', 'file-text', 'file-unknown', 'file-pdf', 'file-word', 'file-excel', 'file-jpg', 'file-ppt', 'file-markdown', 'file-add', 'folder', 'folder-open', 'folder-add', 'hdd', 'frown', 'meh', 'smile', 'inbox', 'laptop', 'appstore', 'link', 'mail', 'mobile', 'notification', 'paper-clip', 'picture', 'poweroff', 'reload', 'search', 'setting', 'share-alt', 'shopping-cart', 'tablet', 'tag', 'tags', 'to-top', 'upload', 'user', 'video-camera', 'home', 'loading', 'loading-3-quarters', 'cloud-upload', 'star', 'heart', 'environment', 'eye', 'camera', 'save', 'team', 'solution', 'phone', 'filter', 'exception', 'export', 'customer-service', 'qrcode', 'scan', 'like', 'dislike', 'message', 'pay-circle', 'calculator', 'pushpin', 'bulb', 'select', 'switcher', 'rocket', 'bell', 'disconnect', 'database', 'compass', 'barcode', 'hourglass', 'key', 'flag', 'layout', 'printer', 'sound', 'usb', 'skin', 'tool', 'sync', 'wifi', 'car', 'schedule', 'user-add', 'user-delete', 'usergroup-add', 'usergroup-delete', 'man', 'woman', 'shop', 'gift', 'idcard', 'medicine-box', 'red-envelope', 'coffee', 'copyright', 'trademark', 'safety', 'wallet', 'bank', 'trophy', 'contacts', 'global', 'shake', 'api', 'fork', 'dashboard', 'table', 'profile', 'alert', 'audit', 'branches', 'build', 'border', 'crown', 'experiment', 'fire', 'money-collect', 'property-safety', 'read', 'reconciliation', 'rest', 'security-scan', 'insurance', 'interation', 'safety-certificate', 'project', 'thunderbolt', 'block', 'cluster', 'deployment-unit', 'dollar', 'euro', 'pound', 'file-done', 'file-exclamation', 'file-protect', 'file-search', 'file-sync', 'gateway', 'gold', 'robot', 'shopping']
|
||||
const logoIcons = ['android', 'apple', 'windows', 'ie', 'chrome', 'github', 'aliwangwang', 'dingding', 'weibo-square', 'weibo-circle', 'taobao-circle', 'html5', 'weibo', 'twitter', 'wechat', 'youtube', 'alipay-circle', 'taobao', 'skype', 'qq', 'medium-workmark', 'gitlab', 'medium', 'linkedin', 'google-plus', 'dropbox', 'facebook', 'codepen', 'amazon', 'google', 'codepen-circle', 'alipay', 'ant-design', 'aliyun', 'zhihu', 'slack', 'slack-square', 'behance', 'behance-square', 'dribbble', 'dribbble-square', 'instagram', 'yuque', 'alibaba', 'yahoo']
|
||||
export default {
|
||||
name: 'Icons',
|
||||
props: {
|
||||
iconChooseVisible: {
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
icons: {
|
||||
directionIcons,
|
||||
suggestionIcons,
|
||||
editIcons,
|
||||
dataIcons,
|
||||
webIcons,
|
||||
logoIcons
|
||||
},
|
||||
choosedIcon: '',
|
||||
activeIndex: ''
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
show: {
|
||||
get: function () {
|
||||
return this.iconChooseVisible
|
||||
},
|
||||
set: function () {
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
reset () {
|
||||
this.activeIndex = ''
|
||||
},
|
||||
chooseIcon (icon) {
|
||||
this.activeIndex = icon
|
||||
this.choosedIcon = icon
|
||||
this.$message.success(`选中 ${icon}`)
|
||||
},
|
||||
ok () {
|
||||
if (this.choosedIcon === '') {
|
||||
this.$message.warning('尚未选择任何图标')
|
||||
return
|
||||
}
|
||||
this.reset()
|
||||
this.$emit('choose', this.choosedIcon)
|
||||
},
|
||||
cancel () {
|
||||
this.reset()
|
||||
this.$emit('close')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
@import "Icon";
|
||||
</style>
|
|
@ -0,0 +1,325 @@
|
|||
<template>
|
||||
<a-card :bordered="false" class="card-area">
|
||||
<div :class="advanced ? 'search' : null">
|
||||
<!-- 搜索区域 -->
|
||||
<a-form layout="horizontal">
|
||||
<div :class="advanced ? null: 'fold'">
|
||||
<a-row >
|
||||
<a-col :md="12" :sm="24" >
|
||||
<a-form-item
|
||||
label="名称"
|
||||
:labelCol="{span: 5}"
|
||||
:wrapperCol="{span: 18, offset: 1}">
|
||||
<a-input v-model="queryParams.menuName"/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :md="12" :sm="24" >
|
||||
<a-form-item
|
||||
label="创建时间"
|
||||
:labelCol="{span: 5}"
|
||||
:wrapperCol="{span: 18, offset: 1}">
|
||||
<range-date @change="handleDateChange" ref="createTime"></range-date>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
<span style="float: right; margin-top: 3px;">
|
||||
<a-button type="primary" @click="search">查询</a-button>
|
||||
<a-button style="margin-left: 8px" @click="reset">重置</a-button>
|
||||
</span>
|
||||
</a-form>
|
||||
</div>
|
||||
<div>
|
||||
<div class="operator">
|
||||
<a-popconfirm
|
||||
title="请选择创建类型"
|
||||
okText="按钮"
|
||||
cancelText="菜单"
|
||||
@cancel="() => createMenu()"
|
||||
@confirm="() => createButton()">
|
||||
<a-icon slot="icon" type="question-circle-o" style="color: orangered" />
|
||||
<a-button type="primary" v-hasPermission="'menu:add'" ghost>新增</a-button>
|
||||
</a-popconfirm>
|
||||
<a-button v-hasPermission="'menu:delete'" @click="batchDelete">删除</a-button>
|
||||
<a-dropdown v-hasPermission="'menu:export'">
|
||||
<a-menu slot="overlay">
|
||||
<a-menu-item key="export-data" @click="exprotExccel">导出Excel</a-menu-item>
|
||||
</a-menu>
|
||||
<a-button>
|
||||
更多操作 <a-icon type="down" />
|
||||
</a-button>
|
||||
</a-dropdown>
|
||||
</div>
|
||||
<!-- 表格区域 -->
|
||||
<a-table :columns="columns"
|
||||
:key="key"
|
||||
:dataSource="dataSource"
|
||||
:pagination="pagination"
|
||||
:loading="loading"
|
||||
:rowSelection="{selectedRowKeys: selectedRowKeys, onChange: onSelectChange}"
|
||||
@change="handleTableChange" :scroll="{ x: 1500 }">
|
||||
<template slot="icon" slot-scope="text, record">
|
||||
<a-icon :type="text" />
|
||||
</template>
|
||||
<template slot="operation" slot-scope="text, record">
|
||||
<a-icon v-hasPermission="'menu:update'" type="setting" theme="twoTone" twoToneColor="#4a9ff5" @click="edit(record)" title="修改"></a-icon>
|
||||
<a-badge v-hasNoPermission="'menu:update'" status="warning" text="无权限"></a-badge>
|
||||
</template>
|
||||
</a-table>
|
||||
</div>
|
||||
<!-- 新增菜单 -->
|
||||
<menu-add
|
||||
@close="handleMenuAddClose"
|
||||
@success="handleMenuAddSuccess"
|
||||
:menuAddVisiable="menuAddVisiable">
|
||||
</menu-add>
|
||||
<!-- 修改菜单 -->
|
||||
<menu-edit
|
||||
ref="menuEdit"
|
||||
@close="handleMenuEditClose"
|
||||
@success="handleMenuEditSuccess"
|
||||
:menuEditVisiable="menuEditVisiable">
|
||||
</menu-edit>
|
||||
<!-- 新增按钮 -->
|
||||
<button-add
|
||||
@close="handleButtonAddClose"
|
||||
@success="handleButtonAddSuccess"
|
||||
:buttonAddVisiable="buttonAddVisiable">
|
||||
</button-add>
|
||||
<!-- 修改按钮 -->
|
||||
<button-edit
|
||||
ref="buttonEdit"
|
||||
@close="handleButtonEditClose"
|
||||
@success="handleButtonEditSuccess"
|
||||
:buttonEditVisiable="buttonEditVisiable">
|
||||
</button-edit>
|
||||
</a-card>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import RangeDate from '@/components/datetime/RangeDate'
|
||||
import MenuAdd from './MenuAdd'
|
||||
import MenuEdit from './MenuEdit'
|
||||
import ButtonAdd from './ButtonAdd'
|
||||
import ButtonEdit from './ButtonEdit'
|
||||
|
||||
export default {
|
||||
name: 'Menu',
|
||||
components: {ButtonAdd, ButtonEdit, RangeDate, MenuAdd, MenuEdit},
|
||||
data () {
|
||||
return {
|
||||
advanced: false,
|
||||
key: +new Date(),
|
||||
queryParams: {},
|
||||
filteredInfo: null,
|
||||
dataSource: [],
|
||||
selectedRowKeys: [],
|
||||
pagination: {
|
||||
defaultPageSize: 10000000,
|
||||
hideOnSinglePage: true,
|
||||
indentSize: 100
|
||||
},
|
||||
loading: false,
|
||||
menuAddVisiable: false,
|
||||
menuEditVisiable: false,
|
||||
buttonAddVisiable: false,
|
||||
buttonEditVisiable: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
columns () {
|
||||
let {filteredInfo} = this
|
||||
filteredInfo = filteredInfo || {}
|
||||
return [{
|
||||
title: '名称',
|
||||
dataIndex: 'text',
|
||||
width: 200,
|
||||
fixed: 'left'
|
||||
}, {
|
||||
title: '图标',
|
||||
dataIndex: 'icon',
|
||||
scopedSlots: { customRender: 'icon' }
|
||||
}, {
|
||||
title: '类型',
|
||||
dataIndex: 'type',
|
||||
customRender: (text, row, index) => {
|
||||
switch (text) {
|
||||
case '0':
|
||||
return <a-tag color="cyan"> 菜单 </a-tag>
|
||||
case '1':
|
||||
return <a-tag color="pink"> 按钮 </a-tag>
|
||||
default:
|
||||
return text
|
||||
}
|
||||
},
|
||||
filters: [
|
||||
{text: '按钮', value: '1'},
|
||||
{text: '菜单', value: '0'}
|
||||
],
|
||||
filterMultiple: false,
|
||||
filteredValue: filteredInfo.type || null,
|
||||
onFilter: (value, record) => record.type.includes(value)
|
||||
}, {
|
||||
title: '地址',
|
||||
dataIndex: 'path'
|
||||
}, {
|
||||
title: 'Vue组件',
|
||||
dataIndex: 'component'
|
||||
}, {
|
||||
title: '权限',
|
||||
dataIndex: 'permission'
|
||||
}, {
|
||||
title: '排序',
|
||||
dataIndex: 'order'
|
||||
}, {
|
||||
title: '创建时间',
|
||||
dataIndex: 'createTime'
|
||||
}, {
|
||||
title: '修改时间',
|
||||
dataIndex: 'modifyTime'
|
||||
}, {
|
||||
title: '操作',
|
||||
dataIndex: 'operation',
|
||||
width: 120,
|
||||
scopedSlots: {customRender: 'operation'},
|
||||
fixed: 'right'
|
||||
}]
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
this.fetch()
|
||||
},
|
||||
methods: {
|
||||
onSelectChange (selectedRowKeys) {
|
||||
this.selectedRowKeys = selectedRowKeys
|
||||
},
|
||||
handleMenuEditClose () {
|
||||
this.menuEditVisiable = false
|
||||
},
|
||||
handleMenuEditSuccess () {
|
||||
this.menuEditVisiable = false
|
||||
this.$message.success('修改菜单成功')
|
||||
this.fetch()
|
||||
},
|
||||
handleButtonEditClose () {
|
||||
this.buttonEditVisiable = false
|
||||
},
|
||||
handleButtonEditSuccess () {
|
||||
this.buttonEditVisiable = false
|
||||
this.$message.success('修改按钮成功')
|
||||
this.fetch()
|
||||
},
|
||||
edit (record) {
|
||||
if (record.type === '0') {
|
||||
this.$refs.menuEdit.setFormValues(record)
|
||||
this.menuEditVisiable = true
|
||||
} else {
|
||||
this.$refs.buttonEdit.setFormValues(record)
|
||||
this.buttonEditVisiable = true
|
||||
}
|
||||
},
|
||||
handleButtonAddClose () {
|
||||
this.buttonAddVisiable = false
|
||||
},
|
||||
handleButtonAddSuccess () {
|
||||
this.buttonAddVisiable = false
|
||||
this.$message.success('新增按钮成功')
|
||||
this.fetch()
|
||||
},
|
||||
createButton () {
|
||||
this.buttonAddVisiable = true
|
||||
},
|
||||
handleMenuAddClose () {
|
||||
this.menuAddVisiable = false
|
||||
},
|
||||
handleMenuAddSuccess () {
|
||||
this.menuAddVisiable = false
|
||||
this.$message.success('新增菜单成功')
|
||||
this.fetch()
|
||||
},
|
||||
createMenu () {
|
||||
this.menuAddVisiable = true
|
||||
},
|
||||
handleDateChange (value) {
|
||||
if (value) {
|
||||
this.queryParams.createTimeFrom = value[0]
|
||||
this.queryParams.createTimeTo = value[1]
|
||||
}
|
||||
},
|
||||
batchDelete () {
|
||||
if (!this.selectedRowKeys.length) {
|
||||
this.$message.warning('请选择需要删除的记录')
|
||||
return
|
||||
}
|
||||
let that = this
|
||||
this.$confirm({
|
||||
title: '确定删除所选中的记录?',
|
||||
content: '当您点击确定按钮后,这些记录将会被彻底删除,如果其包含子记录,也将一并删除!',
|
||||
centered: true,
|
||||
onOk () {
|
||||
that.$delete('menu/' + that.selectedRowKeys.join(',')).then(() => {
|
||||
that.$message.success('删除成功')
|
||||
that.selectedRowKeys = []
|
||||
that.fetch()
|
||||
})
|
||||
},
|
||||
onCancel () {
|
||||
that.selectedRowKeys = []
|
||||
}
|
||||
})
|
||||
},
|
||||
exprotExccel () {
|
||||
let {filteredInfo} = this
|
||||
this.$export('menu/excel', {
|
||||
...this.queryParams,
|
||||
...filteredInfo
|
||||
})
|
||||
},
|
||||
search () {
|
||||
let {filteredInfo} = this
|
||||
this.fetch({
|
||||
...this.queryParams,
|
||||
...filteredInfo
|
||||
})
|
||||
},
|
||||
reset () {
|
||||
// 取消选中
|
||||
this.selectedRowKeys = []
|
||||
// 重置列过滤器规则
|
||||
this.filteredInfo = null
|
||||
// 重置查询参数
|
||||
this.queryParams = {}
|
||||
// 清空时间选择
|
||||
this.$refs.createTime.reset()
|
||||
this.fetch()
|
||||
},
|
||||
handleTableChange (pagination, filters, sorter) {
|
||||
// 将这两个个参数赋值给Vue data,用于后续使用
|
||||
this.filteredInfo = filters
|
||||
this.fetch({
|
||||
sortField: sorter.field,
|
||||
sortOrder: sorter.order,
|
||||
...this.queryParams,
|
||||
...filters
|
||||
})
|
||||
},
|
||||
fetch (params = {}) {
|
||||
this.loading = true
|
||||
this.$get('menu', {
|
||||
...params
|
||||
}).then((r) => {
|
||||
let data = r.data
|
||||
this.loading = false
|
||||
if (Object.is(data.rows.children, undefined)) {
|
||||
this.dataSource = data.rows
|
||||
} else {
|
||||
this.dataSource = data.rows.children
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
@import "../../../../static/less/Common";
|
||||
</style>
|
|
@ -0,0 +1,198 @@
|
|||
<template>
|
||||
<a-drawer
|
||||
title="新增菜单"
|
||||
:maskClosable="false"
|
||||
width=650
|
||||
placement="right"
|
||||
:closable="false"
|
||||
@close="onClose"
|
||||
:visible="menuAddVisiable"
|
||||
style="height: calc(100% - 55px);overflow: auto;padding-bottom: 53px;">
|
||||
<a-form :form="form">
|
||||
<a-form-item label='菜单名称' v-bind="formItemLayout">
|
||||
<a-input v-model="menu.menuName"
|
||||
v-decorator="['menuName',
|
||||
{rules: [
|
||||
{ required: true, message: '菜单名称不能为空'},
|
||||
{ max: 10, message: '长度不能超过10个字符'}
|
||||
]}]"/>
|
||||
</a-form-item>
|
||||
<a-form-item label='菜单URL'
|
||||
v-bind="formItemLayout">
|
||||
<a-input v-model="menu.path"
|
||||
v-decorator="['path',
|
||||
{rules: [
|
||||
{ required: true, message: '菜单URL不能为空'},
|
||||
{ max: 50, message: '长度不能超过50个字符'}
|
||||
]}]"/>
|
||||
</a-form-item>
|
||||
<a-form-item label='组件地址'
|
||||
v-bind="formItemLayout">
|
||||
<a-input v-model="menu.component"
|
||||
v-decorator="['component',
|
||||
{rules: [
|
||||
{ required: true, message: '组件地址不能为空'},
|
||||
{ max: 100, message: '长度不能超过100个字符'}
|
||||
]}]"/>
|
||||
</a-form-item>
|
||||
<a-form-item label='相关权限' v-bind="formItemLayout">
|
||||
<a-input v-model="menu.perms"
|
||||
v-decorator="['perms',
|
||||
{rules: [
|
||||
{ max: 50, message: '长度不能超过50个字符'}
|
||||
]}]"/>
|
||||
</a-form-item>
|
||||
<a-form-item label='菜单图标'
|
||||
v-bind="formItemLayout">
|
||||
<a-input ref="icons" v-model="menu.icon" placeholder="点击右侧按钮选择图标">
|
||||
<a-icon v-if="menu.icon" slot="suffix" type="close-circle" @click="deleteIcons"/>
|
||||
<a-icon slot="addonAfter" type="setting" style="cursor: pointer" @click="chooseIcons"/>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label='菜单排序' v-bind="formItemLayout">
|
||||
<a-input-number v-model="menu.orderNum" style="width: 100%"/>
|
||||
</a-form-item>
|
||||
<a-form-item label='上级菜单'
|
||||
style="margin-bottom: 2rem"
|
||||
v-bind="formItemLayout">
|
||||
<a-tree
|
||||
:key="menuTreeKey"
|
||||
:checkable="true"
|
||||
:checkStrictly="true"
|
||||
@check="handleCheck"
|
||||
@expand="handleExpand"
|
||||
:expandedKeys="expandedKeys"
|
||||
:treeData="menuTreeData">
|
||||
</a-tree>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
<div class="drawer-bootom-button">
|
||||
<a-dropdown style="float: left" :trigger="['click']" placement="topCenter">
|
||||
<a-menu slot="overlay">
|
||||
<a-menu-item key="1" @click="expandAll">展开所有</a-menu-item>
|
||||
<a-menu-item key="2" @click="closeAll">合并所有</a-menu-item>
|
||||
</a-menu>
|
||||
<a-button>
|
||||
树操作 <a-icon type="up" />
|
||||
</a-button>
|
||||
</a-dropdown>
|
||||
<a-popconfirm title="确定放弃编辑?" @confirm="onClose" okText="确定" cancelText="取消">
|
||||
<a-button style="margin-right: .8rem">取消</a-button>
|
||||
</a-popconfirm>
|
||||
<a-button @click="handleSubmit" type="primary" :loading="loading">提交</a-button>
|
||||
</div>
|
||||
<icons
|
||||
@choose="handleIconChoose"
|
||||
@close="handleIconCancel"
|
||||
:iconChooseVisible="iconChooseVisible">
|
||||
</icons>
|
||||
</a-drawer>
|
||||
</template>
|
||||
<script>
|
||||
import Icons from './Icons'
|
||||
const formItemLayout = {
|
||||
labelCol: { span: 3 },
|
||||
wrapperCol: { span: 18 }
|
||||
}
|
||||
export default {
|
||||
name: 'MenuAdd',
|
||||
components: {Icons},
|
||||
props: {
|
||||
menuAddVisiable: {
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
loading: false,
|
||||
formItemLayout,
|
||||
form: this.$form.createForm(this),
|
||||
menuTreeKey: +new Date(),
|
||||
menu: {
|
||||
icon: ''
|
||||
},
|
||||
checkedKeys: [],
|
||||
expandedKeys: [],
|
||||
menuTreeData: [],
|
||||
iconChooseVisible: false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
reset () {
|
||||
this.loading = false
|
||||
this.menuTreeKey = +new Date()
|
||||
this.expandedKeys = this.checkedKeys = []
|
||||
this.menu = {}
|
||||
this.form.resetFields()
|
||||
},
|
||||
onClose () {
|
||||
this.reset()
|
||||
this.$emit('close')
|
||||
},
|
||||
handleCheck (checkedKeys) {
|
||||
this.checkedKeys = checkedKeys
|
||||
},
|
||||
expandAll () {
|
||||
this.expandedKeys = this.allTreeKeys
|
||||
},
|
||||
closeAll () {
|
||||
this.expandedKeys = []
|
||||
},
|
||||
handleExpand (expandedKeys) {
|
||||
this.expandedKeys = expandedKeys
|
||||
},
|
||||
chooseIcons () {
|
||||
this.iconChooseVisible = true
|
||||
},
|
||||
handleIconCancel () {
|
||||
this.iconChooseVisible = false
|
||||
},
|
||||
handleIconChoose (value) {
|
||||
this.menu.icon = value
|
||||
this.iconChooseVisible = false
|
||||
},
|
||||
deleteIcons () {
|
||||
this.menu.icon = ''
|
||||
},
|
||||
handleSubmit () {
|
||||
let checkedArr = Object.is(this.checkedKeys.checked, undefined) ? this.checkedKeys : this.checkedKeys.checked
|
||||
if (checkedArr.length > 1) {
|
||||
this.$message.error('最多只能选择一个上级菜单,请修改')
|
||||
return
|
||||
}
|
||||
this.form.validateFields((err, values) => {
|
||||
if (!err) {
|
||||
this.loading = true
|
||||
if (checkedArr.length) {
|
||||
this.menu.parentId = checkedArr[0]
|
||||
} else {
|
||||
this.menu.parentId = ''
|
||||
}
|
||||
// 0 表示菜单 1 表示按钮
|
||||
this.menu.type = '0'
|
||||
this.$post('menu', {
|
||||
...this.menu
|
||||
}).then(() => {
|
||||
this.reset()
|
||||
this.$emit('success')
|
||||
}).catch(() => {
|
||||
this.loading = false
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
menuAddVisiable () {
|
||||
if (this.menuAddVisiable) {
|
||||
this.$get('menu', {
|
||||
type: '0'
|
||||
}).then((r) => {
|
||||
this.menuTreeData = r.data.rows.children
|
||||
this.allTreeKeys = r.data.ids
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,232 @@
|
|||
<template>
|
||||
<a-drawer
|
||||
title="修改菜单"
|
||||
:maskClosable="false"
|
||||
width=650
|
||||
placement="right"
|
||||
:closable="false"
|
||||
@close="onClose"
|
||||
:visible="menuEditVisiable"
|
||||
style="height: calc(100% - 55px);overflow: auto;padding-bottom: 53px;">
|
||||
<a-form :form="form">
|
||||
<a-form-item label='菜单名称' v-bind="formItemLayout">
|
||||
<a-input v-decorator="['menuName',
|
||||
{rules: [
|
||||
{ required: true, message: '菜单名称不能为空'},
|
||||
{ max: 10, message: '长度不能超过10个字符'}
|
||||
]}]"/>
|
||||
</a-form-item>
|
||||
<a-form-item label='菜单URL'
|
||||
v-bind="formItemLayout">
|
||||
<a-input v-decorator="['path',
|
||||
{rules: [
|
||||
{ required: true, message: '菜单URL不能为空'},
|
||||
{ max: 50, message: '长度不能超过50个字符'}
|
||||
]}]"/>
|
||||
</a-form-item>
|
||||
<a-form-item label='组件地址'
|
||||
v-bind="formItemLayout">
|
||||
<a-input v-decorator="['component',
|
||||
{rules: [
|
||||
{ required: true, message: '组件地址不能为空'},
|
||||
{ max: 100, message: '长度不能超过100个字符'}
|
||||
]}]"/>
|
||||
</a-form-item>
|
||||
<a-form-item label='相关权限' v-bind="formItemLayout">
|
||||
<a-input v-decorator="['perms',
|
||||
{rules: [
|
||||
{ max: 50, message: '长度不能超过50个字符'}
|
||||
]}]"/>
|
||||
</a-form-item>
|
||||
<a-form-item label='菜单图标'
|
||||
v-decorator="['icon']"
|
||||
v-bind="formItemLayout">
|
||||
<a-input placeholder="点击右侧按钮选择图标" v-model="menu.icon">
|
||||
<a-icon v-if="menu.icon" slot="suffix" type="close-circle" @click="deleteIcons"/>
|
||||
<a-icon slot="addonAfter" type="setting" style="cursor: pointer" @click="chooseIcons"/>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label='菜单排序' v-bind="formItemLayout">
|
||||
<a-input-number v-decorator="['orderNum']" style="width: 100%"/>
|
||||
</a-form-item>
|
||||
<a-form-item label='上级菜单'
|
||||
style="margin-bottom: 2rem"
|
||||
v-bind="formItemLayout">
|
||||
<a-tree
|
||||
ref="menuTree"
|
||||
:key="menuTreeKey"
|
||||
:checkable="true"
|
||||
:checkStrictly="true"
|
||||
@check="handleCheck"
|
||||
@expand="handleExpand"
|
||||
:expandedKeys="expandedKeys"
|
||||
:defaultCheckedKeys="defaultCheckedKeys"
|
||||
:treeData="menuTreeData">
|
||||
</a-tree>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
<div class="drawer-bootom-button">
|
||||
<a-dropdown style="float: left" :trigger="['click']" placement="topCenter">
|
||||
<a-menu slot="overlay">
|
||||
<a-menu-item key="1" @click="expandAll">展开所有</a-menu-item>
|
||||
<a-menu-item key="2" @click="closeAll">合并所有</a-menu-item>
|
||||
</a-menu>
|
||||
<a-button>
|
||||
树操作 <a-icon type="up" />
|
||||
</a-button>
|
||||
</a-dropdown>
|
||||
<a-popconfirm title="确定放弃编辑?" @confirm="onClose" okText="确定" cancelText="取消">
|
||||
<a-button style="margin-right: .8rem">取消</a-button>
|
||||
</a-popconfirm>
|
||||
<a-button @click="handleSubmit" type="primary" :loading="loading">提交</a-button>
|
||||
</div>
|
||||
<icons
|
||||
@choose="handleIconChoose"
|
||||
@close="handleIconCancel"
|
||||
:iconChooseVisible="iconChooseVisible">
|
||||
</icons>
|
||||
</a-drawer>
|
||||
</template>
|
||||
<script>
|
||||
import Icons from './Icons'
|
||||
const formItemLayout = {
|
||||
labelCol: { span: 3 },
|
||||
wrapperCol: { span: 18 }
|
||||
}
|
||||
export default {
|
||||
name: 'MenuEdit',
|
||||
components: {Icons},
|
||||
props: {
|
||||
menuEditVisiable: {
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
loading: false,
|
||||
formItemLayout,
|
||||
form: this.$form.createForm(this),
|
||||
menuTreeKey: +new Date(),
|
||||
menu: {
|
||||
icon: ''
|
||||
},
|
||||
checkedKeys: [],
|
||||
expandedKeys: [],
|
||||
menuTreeData: [],
|
||||
defaultCheckedKeys: [],
|
||||
iconChooseVisible: false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
reset () {
|
||||
this.loading = false
|
||||
this.menuTreeKey = +new Date()
|
||||
this.expandedKeys = this.checkedKeys = this.defaultCheckedKeys = []
|
||||
this.menu = {}
|
||||
this.form.resetFields()
|
||||
},
|
||||
onClose () {
|
||||
this.reset()
|
||||
this.$emit('close')
|
||||
},
|
||||
handleCheck (checkedKeys) {
|
||||
this.checkedKeys = checkedKeys
|
||||
},
|
||||
expandAll () {
|
||||
this.expandedKeys = this.allTreeKeys
|
||||
},
|
||||
closeAll () {
|
||||
this.expandedKeys = []
|
||||
},
|
||||
handleExpand (expandedKeys) {
|
||||
this.expandedKeys = expandedKeys
|
||||
},
|
||||
chooseIcons () {
|
||||
this.iconChooseVisible = true
|
||||
},
|
||||
handleIconCancel () {
|
||||
this.iconChooseVisible = false
|
||||
},
|
||||
handleIconChoose (value) {
|
||||
this.menu.icon = value
|
||||
this.iconChooseVisible = false
|
||||
},
|
||||
deleteIcons () {
|
||||
this.menu.icon = ''
|
||||
},
|
||||
setFormValues ({...menu}) {
|
||||
let fields = ['path', 'component', 'icon']
|
||||
Object.keys(menu).forEach((key) => {
|
||||
if (fields.indexOf(key) !== -1) {
|
||||
this.form.getFieldDecorator(key)
|
||||
let obj = {}
|
||||
obj[key] = menu[key]
|
||||
this.form.setFieldsValue(obj)
|
||||
}
|
||||
})
|
||||
this.form.getFieldDecorator('menuName')
|
||||
this.form.setFieldsValue({'menuName': menu.text})
|
||||
this.form.getFieldDecorator('perms')
|
||||
this.form.setFieldsValue({'perms': menu.permission})
|
||||
this.form.getFieldDecorator('orderNum')
|
||||
this.form.setFieldsValue({'orderNum': menu.order})
|
||||
this.menu.icon = menu.icon
|
||||
if (menu.parentId !== '0') {
|
||||
this.defaultCheckedKeys.push(menu.parentId)
|
||||
this.checkedKeys = this.defaultCheckedKeys
|
||||
this.expandedKeys = this.checkedKeys
|
||||
}
|
||||
this.menu.menuId = menu.id
|
||||
this.menuTreeKey = +new Date()
|
||||
},
|
||||
handleSubmit () {
|
||||
let checkedArr = Object.is(this.checkedKeys.checked, undefined) ? this.checkedKeys : this.checkedKeys.checked
|
||||
if (checkedArr.length > 1) {
|
||||
this.$message.error('最多只能选择一个上级菜单,请修改')
|
||||
return
|
||||
}
|
||||
if (checkedArr[0] === this.menu.menuId) {
|
||||
this.$message.error('不能选择自己作为上级菜单,请修改')
|
||||
return
|
||||
}
|
||||
this.form.validateFields((err, values) => {
|
||||
if (!err) {
|
||||
this.loading = true
|
||||
let icon = this.menu.icon
|
||||
let menu = this.form.getFieldsValue()
|
||||
menu.icon = icon
|
||||
menu.menuId = this.menu.menuId
|
||||
if (checkedArr.length) {
|
||||
menu.parentId = checkedArr[0]
|
||||
} else {
|
||||
menu.parentId = ''
|
||||
}
|
||||
// 0 表示菜单 1 表示按钮
|
||||
menu.type = '0'
|
||||
this.$put('menu', {
|
||||
...menu
|
||||
}).then(() => {
|
||||
this.reset()
|
||||
this.$emit('success')
|
||||
}).catch(() => {
|
||||
this.loading = false
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
menuEditVisiable () {
|
||||
if (this.menuEditVisiable) {
|
||||
this.$get('menu', {
|
||||
type: '0'
|
||||
}).then((r) => {
|
||||
this.menuTreeData = r.data.rows.children
|
||||
this.allTreeKeys = r.data.ids
|
||||
this.menuTreeKey = +new Date()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,343 @@
|
|||
<template>
|
||||
<a-card :bordered="false" class="card-area">
|
||||
<div :class="advanced ? 'search' : null">
|
||||
<!-- 搜索区域 -->
|
||||
<a-form layout="horizontal">
|
||||
<div :class="advanced ? null: 'fold'">
|
||||
<a-row>
|
||||
<a-col :md="12" :sm="24">
|
||||
<a-form-item
|
||||
label="资源名称"
|
||||
:labelCol="{span: 5}"
|
||||
:wrapperCol="{span: 18, offset: 1}">
|
||||
<a-input v-model="queryParams.roleName"/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :md="12" :sm="24">
|
||||
<a-form-item
|
||||
label="创建时间"
|
||||
:labelCol="{span: 5}"
|
||||
:wrapperCol="{span: 18, offset: 1}">
|
||||
<range-date @change="handleDateChange" ref="createTime"></range-date>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
<span style="float: right; margin-top: 3px;">
|
||||
<a-button type="primary" @click="search">查询</a-button>
|
||||
<a-button style="margin-left: 8px" @click="reset">重置</a-button>
|
||||
</span>
|
||||
</a-form>
|
||||
</div>
|
||||
<div>
|
||||
<div class="operator">
|
||||
<a-button v-hasPermission="'resource:add'" ghost type="primary" @click="add">新增</a-button>
|
||||
<a-button v-hasPermission="'resource:delete'" @click="batchDelete">删除</a-button>
|
||||
<a-dropdown v-hasPermission="'resource:export'">
|
||||
<a-menu slot="overlay">
|
||||
<a-menu-item key="export-data" @click="exportExccel">导出Excel</a-menu-item>
|
||||
</a-menu>
|
||||
<a-button>
|
||||
更多操作
|
||||
<a-icon type="down"/>
|
||||
</a-button>
|
||||
</a-dropdown>
|
||||
</div>
|
||||
<!-- 表格区域 -->
|
||||
<a-table ref="TableInfo"
|
||||
:columns="columns"
|
||||
:dataSource="dataSource"
|
||||
:pagination="pagination"
|
||||
:loading="loading"
|
||||
:rowSelection="{selectedRowKeys: selectedRowKeys, onChange: onSelectChange}"
|
||||
:scroll="{ x: 900 }"
|
||||
@change="handleTableChange">
|
||||
<template slot="remark" slot-scope="text, record">
|
||||
<a-popover placement="topLeft">
|
||||
<template slot="content">
|
||||
<div style="max-width: 200px">{{text}}</div>
|
||||
</template>
|
||||
<p style="width: 200px;margin-bottom: 0">{{text}}</p>
|
||||
</a-popover>
|
||||
</template>
|
||||
<template slot="operation" slot-scope="text, record">
|
||||
<a-icon type="eye" theme="twoTone" twoToneColor="#42b983" @click="view(record)" title="查看"></a-icon>
|
||||
</template>
|
||||
</a-table>
|
||||
<!-- 新增资源 -->
|
||||
<resource-add
|
||||
@close="handleResourceAddClose"
|
||||
@success="handleResourceAddSuccess"
|
||||
:resourceAddVisiable="resourceAdd.visible">
|
||||
</resource-add>
|
||||
<resource-info
|
||||
@close="handleResourceInfoClose"
|
||||
:resourceInfoVisiable="resourceInfo.visible"
|
||||
:resourceInfoData="resourceInfo.data">
|
||||
</resource-info>
|
||||
</div>
|
||||
</a-card>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import RangeDate from '@/components/datetime/RangeDate'
|
||||
import ResourceAdd from './ResourceAdd'
|
||||
import ResourceInfo from './ResourceInfo'
|
||||
|
||||
export default {
|
||||
name: 'resource',
|
||||
components: {RangeDate, ResourceAdd, ResourceInfo},
|
||||
data () {
|
||||
return {
|
||||
advanced: false,
|
||||
resourceInfo: {
|
||||
visible: false,
|
||||
data: {}
|
||||
},
|
||||
resourceAdd: {
|
||||
visible: false
|
||||
},
|
||||
queryParams: {
|
||||
createTimeFrom: '',
|
||||
createTimeTo: ''
|
||||
},
|
||||
filteredInfo: null,
|
||||
dataSource: [],
|
||||
sortedInfo: null,
|
||||
selectedRowKeys: [],
|
||||
pagination: {
|
||||
pageSizeOptions: ['10', '20', '30', '40', '100'],
|
||||
defaultCurrent: 1,
|
||||
defaultPageSize: 10,
|
||||
showQuickJumper: true,
|
||||
showSizeChanger: true,
|
||||
showTotal: (total, range) => `显示 ${range[0]} ~ ${range[1]} 条记录,共 ${total} 条记录`
|
||||
},
|
||||
loading: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
columns () {
|
||||
let { sortedInfo, filteredInfo } = this
|
||||
sortedInfo = sortedInfo || {}
|
||||
filteredInfo = filteredInfo || {}
|
||||
return [{
|
||||
title: '资源名称',
|
||||
dataIndex: 'username',
|
||||
sorter: true,
|
||||
sortOrder: sortedInfo.columnKey === 'username' && sortedInfo.order
|
||||
}, {
|
||||
title: '性别',
|
||||
dataIndex: 'ssex',
|
||||
customRender: (text, row, index) => {
|
||||
switch (text) {
|
||||
case '0':
|
||||
return '男'
|
||||
case '1':
|
||||
return '女'
|
||||
case '2':
|
||||
return '保密'
|
||||
default:
|
||||
return text
|
||||
}
|
||||
},
|
||||
filters: [
|
||||
{ text: '男', value: '0' },
|
||||
{ text: '女', value: '1' },
|
||||
{ text: '保密', value: '2' }
|
||||
],
|
||||
filterMultiple: false,
|
||||
filteredValue: filteredInfo.ssex || null,
|
||||
onFilter: (value, record) => record.ssex.includes(value)
|
||||
}, {
|
||||
title: '邮箱',
|
||||
dataIndex: 'email',
|
||||
scopedSlots: { customRender: 'email' },
|
||||
width: 100
|
||||
}, {
|
||||
title: '部门',
|
||||
dataIndex: 'deptName'
|
||||
}, {
|
||||
title: '电话',
|
||||
dataIndex: 'mobile'
|
||||
}, {
|
||||
title: '状态',
|
||||
dataIndex: 'status',
|
||||
customRender: (text, row, index) => {
|
||||
switch (text) {
|
||||
case '0':
|
||||
return <a-tag color="red">锁定</a-tag>
|
||||
case '1':
|
||||
return <a-tag color="cyan">有效</a-tag>
|
||||
default:
|
||||
return text
|
||||
}
|
||||
},
|
||||
filters: [
|
||||
{ text: '有效', value: '1' },
|
||||
{ text: '锁定', value: '0' }
|
||||
],
|
||||
filterMultiple: false,
|
||||
filteredValue: filteredInfo.status || null,
|
||||
onFilter: (value, record) => record.status.includes(value)
|
||||
}, {
|
||||
title: '创建时间',
|
||||
dataIndex: 'createTime',
|
||||
sorter: true,
|
||||
sortOrder: sortedInfo.columnKey === 'createTime' && sortedInfo.order
|
||||
}, {
|
||||
title: '操作',
|
||||
dataIndex: 'operation',
|
||||
scopedSlots: { customRender: 'operation' }
|
||||
}]
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
this.fetch()
|
||||
},
|
||||
methods: {
|
||||
onSelectChange (selectedRowKeys) {
|
||||
this.selectedRowKeys = selectedRowKeys
|
||||
},
|
||||
toggleAdvanced () {
|
||||
this.advanced = !this.advanced
|
||||
if (!this.advanced) {
|
||||
this.queryParams.createTimeFrom = ''
|
||||
this.queryParams.createTimeTo = ''
|
||||
}
|
||||
},
|
||||
add () {
|
||||
this.resourceAdd.visible = true
|
||||
},
|
||||
view (record) {
|
||||
this.resourceInfo.data = record
|
||||
this.resourceInfo.visible = true
|
||||
},
|
||||
handleResourceInfoClose () {
|
||||
this.resourceInfo.visible = false
|
||||
},
|
||||
handleResourceAddClose () {
|
||||
this.resourceAdd.visible = false
|
||||
},
|
||||
handleResourceAddSuccess () {
|
||||
this.resourceAdd.visible = false
|
||||
this.$message.success('新增资源成功')
|
||||
this.fetch()
|
||||
},
|
||||
handleDateChange (value) {
|
||||
if (value) {
|
||||
this.queryParams.createTimeFrom = value[0]
|
||||
this.queryParams.createTimeTo = value[1]
|
||||
}
|
||||
},
|
||||
batchDelete () {
|
||||
if (!this.selectedRowKeys.length) {
|
||||
this.$message.warning('请选择需要删除的记录')
|
||||
return
|
||||
}
|
||||
let that = this
|
||||
this.$confirm({
|
||||
title: '确定删除所选中的记录?',
|
||||
content: '当您点击确定按钮后,这些记录将会被彻底删除,如果其包含子记录,也将一并删除!',
|
||||
centered: true,
|
||||
onOk () {
|
||||
that.$delete('resource/' + that.selectedRowKeys.join(',')).then(() => {
|
||||
that.$message.success('删除成功')
|
||||
that.selectedRowKeys = []
|
||||
that.fetch()
|
||||
})
|
||||
},
|
||||
onCancel () {
|
||||
that.selectedRowKeys = []
|
||||
}
|
||||
})
|
||||
},
|
||||
exportExccel () {
|
||||
let {sortedInfo} = this
|
||||
let sortField, sortOrder
|
||||
// 获取当前列的排序和列的过滤规则
|
||||
if (sortedInfo) {
|
||||
sortField = sortedInfo.field
|
||||
sortOrder = sortedInfo.order
|
||||
}
|
||||
this.$export('resource/excel', {
|
||||
sortField: sortField,
|
||||
sortOrder: sortOrder,
|
||||
...this.queryParams
|
||||
})
|
||||
},
|
||||
search () {
|
||||
let {sortedInfo} = this
|
||||
let sortField, sortOrder
|
||||
// 获取当前列的排序和列的过滤规则
|
||||
if (sortedInfo) {
|
||||
sortField = sortedInfo.field
|
||||
sortOrder = sortedInfo.order
|
||||
}
|
||||
this.fetch({
|
||||
sortField: sortField,
|
||||
sortOrder: sortOrder,
|
||||
...this.queryParams
|
||||
})
|
||||
},
|
||||
reset () {
|
||||
// 取消选中
|
||||
this.selectedRowKeys = []
|
||||
// 重置分页
|
||||
this.$refs.TableInfo.pagination.current = this.pagination.defaultCurrent
|
||||
if (this.paginationInfo) {
|
||||
this.paginationInfo.current = this.pagination.defaultCurrent
|
||||
this.paginationInfo.pageSize = this.pagination.defaultPageSize
|
||||
}
|
||||
// 重置列过滤器规则
|
||||
this.filteredInfo = null
|
||||
// 重置列排序规则
|
||||
this.sortedInfo = null
|
||||
// 重置查询参数
|
||||
this.queryParams = {}
|
||||
// 清空时间选择
|
||||
if (this.advanced) {
|
||||
this.$refs.createTime.reset()
|
||||
}
|
||||
this.fetch()
|
||||
},
|
||||
handleTableChange (pagination, filters, sorter) {
|
||||
// 将这两个个参数赋值给Vue data,用于后续使用
|
||||
this.filteredInfo = filters
|
||||
this.fetch({
|
||||
sortField: sorter.field,
|
||||
sortOrder: sorter.order,
|
||||
...this.queryParams,
|
||||
...filters
|
||||
})
|
||||
},
|
||||
fetch (params = {}) {
|
||||
this.loading = true
|
||||
if (this.paginationInfo) {
|
||||
// 如果分页信息不为空,则设置表格当前第几页,每页条数,并设置查询分页参数
|
||||
this.$refs.TableInfo.pagination.current = this.paginationInfo.current
|
||||
this.$refs.TableInfo.pagination.pageSize = this.paginationInfo.pageSize
|
||||
params.pageSize = this.paginationInfo.pageSize
|
||||
params.pageNum = this.paginationInfo.current
|
||||
} else {
|
||||
// 如果分页信息为空,则设置为默认值
|
||||
params.pageSize = this.pagination.defaultPageSize
|
||||
params.pageNum = this.pagination.defaultCurrent
|
||||
}
|
||||
this.$get('user', {
|
||||
...params
|
||||
}).then((r) => {
|
||||
let data = r.data
|
||||
const pagination = {...this.pagination}
|
||||
pagination.total = data.total
|
||||
this.dataSource = data.rows
|
||||
this.pagination = pagination
|
||||
this.loading = false
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
@import "../../../../static/less/Common";
|
||||
</style>
|
|
@ -0,0 +1,162 @@
|
|||
<template>
|
||||
<a-drawer
|
||||
title="新增资源"
|
||||
:maskClosable="false"
|
||||
width=650
|
||||
placement="right"
|
||||
:closable="false"
|
||||
@close="onClose"
|
||||
:visible="resourceAddVisiable"
|
||||
style="height: calc(100% - 55px);overflow: auto;padding-bottom: 53px;">
|
||||
<a-form :form="form">
|
||||
<a-form-item label='资源名称'
|
||||
v-bind="formItemLayout"
|
||||
:validateStatus="validateStatus"
|
||||
:help="help">
|
||||
<a-input @blur="handleResourceNameBlur" v-model="resource.userName" v-decorator="['roleName']"/>
|
||||
</a-form-item>
|
||||
<a-form-item label='资源描述' v-bind="formItemLayout">
|
||||
<a-textarea
|
||||
:rows="4"
|
||||
v-model="resource.remark"
|
||||
v-decorator="[
|
||||
'remark',
|
||||
{rules: [
|
||||
{ max: 100, message: '长度不能超过100个字符'}
|
||||
]}]">
|
||||
</a-textarea>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
<div class="drawer-bootom-button">
|
||||
<a-popconfirm title="确定放弃编辑?" @confirm="onClose" okText="确定" cancelText="取消">
|
||||
<a-button style="margin-right: .8rem">取消</a-button>
|
||||
</a-popconfirm>
|
||||
<a-button @click="handleSubmit" type="primary" :loading="loading">提交</a-button>
|
||||
</div>
|
||||
</a-drawer>
|
||||
</template>
|
||||
<script>
|
||||
const formItemLayout = {
|
||||
labelCol: { span: 3 },
|
||||
wrapperCol: { span: 18 }
|
||||
}
|
||||
export default {
|
||||
name: 'ResourceAdd',
|
||||
props: {
|
||||
resourceAddVisiable: {
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
loading: false,
|
||||
formItemLayout,
|
||||
form: this.$form.createForm(this),
|
||||
validateStatus: '',
|
||||
menuSelectStatus: '',
|
||||
help: '',
|
||||
menuSelectHelp: '',
|
||||
resource: {
|
||||
userName: '',
|
||||
remark: '',
|
||||
menuId: ''
|
||||
},
|
||||
checkedKeys: [],
|
||||
checkStrictly: true
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
reset () {
|
||||
this.validateStatus = this.help = ''
|
||||
this.loading = false
|
||||
this.form.resetFields()
|
||||
},
|
||||
onClose () {
|
||||
this.reset()
|
||||
this.$emit('close')
|
||||
},
|
||||
expandAll () {
|
||||
this.expandedKeys = this.allTreeKeys
|
||||
},
|
||||
closeAll () {
|
||||
this.expandedKeys = []
|
||||
},
|
||||
enableRelate () {
|
||||
this.checkStrictly = false
|
||||
},
|
||||
disableRelate () {
|
||||
this.checkStrictly = true
|
||||
},
|
||||
handleCheck (checkedKeys) {
|
||||
this.checkedKeys = checkedKeys
|
||||
let checkedArr = Object.is(checkedKeys.checked, undefined) ? checkedKeys : checkedKeys.checked
|
||||
if (checkedArr.length) {
|
||||
this.menuSelectStatus = ''
|
||||
this.menuSelectHelp = ''
|
||||
} else {
|
||||
this.menuSelectStatus = 'error'
|
||||
this.menuSelectHelp = '请选择相应的权限'
|
||||
}
|
||||
},
|
||||
handleExpand (expandedKeys) {
|
||||
this.expandedKeys = expandedKeys
|
||||
},
|
||||
handleSubmit () {
|
||||
let checkedArr = Object.is(this.checkedKeys.checked, undefined) ? this.checkedKeys : this.checkedKeys.checked
|
||||
if (this.validateStatus !== 'success') {
|
||||
this.handleResourceNameBlur()
|
||||
} else if (checkedArr.length === 0) {
|
||||
this.menuSelectStatus = 'error'
|
||||
this.menuSelectHelp = '请选择相应的权限'
|
||||
} else {
|
||||
this.form.validateFields((err, values) => {
|
||||
if (!err) {
|
||||
this.loading = true
|
||||
this.resource.menuId = checkedArr.join(',')
|
||||
this.$post('resource', {
|
||||
...this.resource
|
||||
}).then((r) => {
|
||||
this.reset()
|
||||
this.$emit('success')
|
||||
}).catch(() => {
|
||||
this.loading = false
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
handleResourceNameBlur () {
|
||||
let resourceName = this.resource.resourceName.trim()
|
||||
if (resourceName.length) {
|
||||
if (resourceName.length > 10) {
|
||||
this.validateStatus = 'error'
|
||||
this.help = '资源名称不能超过10个字符'
|
||||
} else {
|
||||
this.validateStatus = 'validating'
|
||||
this.$get(`role/check/${resourceName}`).then((r) => {
|
||||
if (r.data) {
|
||||
this.validateStatus = 'success'
|
||||
this.help = ''
|
||||
} else {
|
||||
this.validateStatus = 'error'
|
||||
this.help = '抱歉,该资源名称已存在'
|
||||
}
|
||||
})
|
||||
}
|
||||
} else {
|
||||
this.validateStatus = 'error'
|
||||
this.help = '资源名称不能为空'
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
resourceAddVisiable () {
|
||||
if (this.resourceAddVisiable) {
|
||||
this.$get('resourceName').then((r) => {
|
||||
this.allTreeKeys = r.data.ids
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,44 @@
|
|||
<template>
|
||||
<a-drawer
|
||||
title="角色信息"
|
||||
:maskClosable="false"
|
||||
width=650
|
||||
placement="right"
|
||||
:closable="true"
|
||||
@close="close"
|
||||
:visible="resourceInfoVisiable"
|
||||
style="height: calc(100% - 55px);overflow: auto;padding-bottom: 53px;">
|
||||
<p><a-icon type="crown" /> 资源名称:{{resourceInfoData.roleName}}</p>
|
||||
<p :title="resourceInfoData.remark"><a-icon type="book" /> 资源描述:{{resourceInfoData.remark}}</p>
|
||||
<p><a-icon type="clock-circle" /> 创建时间:{{resourceInfoData.createTime}}</p>
|
||||
<p><a-icon type="clock-circle" /> 修改时间:{{resourceInfoData.modifyTime? resourceInfoData.modifyTime : '暂未修改'}}</p>
|
||||
</a-drawer>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
name: 'ResourceInfo',
|
||||
props: {
|
||||
resourceInfoVisiable: {
|
||||
require: true,
|
||||
default: false
|
||||
},
|
||||
resourceInfoData: {
|
||||
require: true
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
key: +new Date(),
|
||||
loading: true,
|
||||
checkedKeys: [],
|
||||
menuTreeData: []
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
close () {
|
||||
this.$emit('close')
|
||||
this.checkedKeys = []
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue