Commit 5be6edd7 authored by Gakki's avatar Gakki

init

parents
{
"presets": [
["env", {
"modules": false,
"targets": {
"browsers": ["> 1%", "last 2 versions", "not ie <= 8"]
}
}],
"stage-2"
],
"plugins": [
"transform-vue-jsx",
"transform-runtime",
["import", {
"libraryName": "ant-design-vue",
"libraryDirectory": "es",
"style": "css"
}],
]
}
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
/build/
/config/
/dist/
/*.js
// https://eslint.org/docs/user-guide/configuring
module.exports = {
root: true,
parserOptions: {
parser: 'babel-eslint'
},
env: {
browser: true,
es6: true,
},
// 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.
extends: ['plugin:vue/essential'],
// required to lint *.vue files
plugins: [
'vue'
],
// add your custom rules here
rules: {
// allow debugger during development
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off',
"indent": [
"error",
2
],
"linebreak-style": [
"error",
"unix",
],
"quotes": [
"error",
"single",
],
"semi": [
"error",
"never",
],
"no-console": 0,
'linebreak-style': [0, 'error', 'windows']
}
}
.DS_Store
node_modules/
/dist/
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.zip
// 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": {}
}
}
#!/usr/bin/groovy
@Library('pipeline-library')
def utils = new io.fabric8.Utils()
clientsNode{
def envTest = 'housemanage-test'
def envPrd = 'housemanage'
def projectName = "${JOB_BASE_NAME}"
def newVersion = ''
def deployType = ""
try {
deployType = DEPLOY_TYPE
} catch (Throwable e) {
deployType = "master"
}
checkout scm
if(deployType == 'dev'){
stage '切换构建分支'
sh "git checkout -b pro origin/zongbang-pro"
}
stage '构建版本'
echo 'NOTE: running pipelines for the first time will take longer as build and base docker images are pulled onto the node'
if (!fileExists ('Dockerfile')) {
writeFile file: 'Dockerfile', text: '''FROM 10.0.6.228:5000/node-nginx:8.12.0-slim
WORKDIR /app
# 将当前目录下的所有文件拷贝到工作目录下
COPY . /app/
# 1.安装依赖
# 2.运行 npm run build
# 3.将 dist 目录的所有文件拷贝到 nginx 的目录下
# 4.删除工作目录的文件,尤其是 node_modules 以减小镜像体积
# 由于镜像构建的每一步都会产生新层
# 为了减小镜像体积,尽可能将一些同类操作,集成到一个步骤中,如下、
RUN npm config set proxy=http://10.0.6.228:3128 \\
&& npm set registry https://registry.npm.taobao.org \\
&& npm install phantomjs-prebuilt@2.1.16 --ignore-scripts \\
&& npm install \\
&& npm run build \\
&& cp -r dist/* /var/www/html \\
&& cp nginx.conf /etc/nginx/conf.d \\
&& rm -rf /etc/nginx/sites-enabled \\
&& rm -rf /app'''
}
newVersion = performCanaryRelease {}
def rc = getKubernetesJson {
port = 80
label = 'nodejs'
version = newVersion
name = projectName
imageName = clusterImageName
}
if(deployType == 'master'){
stage '部署master'
kubernetesApply(file: rc, environment: envTest)
}
if(deployType == 'dev'){
stage '部署dev'
kubernetesApply(file: rc, environment: envPrd)
}
}
'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'
))
})
})
'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:'))
for (let i = 0; i < warnings.length; i++) {
const warning = warnings[i]
console.log(' ' + warning)
}
process.exit(1)
}
}
'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')
})
}
}
'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'
}
}
'use strict'
const path = require('path')
const utils = require('./utils')
const config = require('../config')
const vueLoaderConfig = require('./vue-loader.conf')
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: './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'),
}
},
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'
}
}
'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)
}
})
})
'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 = require('../config/prod.env')
if (process.env.NODE_ENV === 'sit') {
env = require('../config/sit.env')
} else if (process.env.NODE_ENV === 'uat') {
env = require('../config/uat.env')
}else if (process.env.NODE_ENV === 'devol') {
env = require('../config/devOnline.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: {
publicPath: '/',
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,
drop_debugger: true,
drop_console: true,
}
},
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: 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(
'\\.(' +
config.build.productionGzipExtensions.join('|') +
')$'
),
threshold: 10240,
minRatio: 0.8
})
)
}
if (config.build.bundleAnalyzerReport) {
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
webpackConfig.plugins.push(new BundleAnalyzerPlugin())
}
module.exports = webpackConfig
'use strict'
const merge = require('webpack-merge')
const prodEnv = require('./prod.env')
module.exports = merge(prodEnv, {
NODE_ENV: '"development"'
})
'use strict'
module.exports = {
NODE_ENV: '"devol"'
}
'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: {},
// Various Dev Server settings
host: 'localhost', // can be overwritten by process.env.HOST
port: 8080, // 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: false,
// 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: process.env.npm_config_report
}
}
'use strict'
module.exports = {
NODE_ENV: '"production"'
}
'use strict'
module.exports = {
NODE_ENV: '"sit"'
}
'use strict'
module.exports = {
NODE_ENV: '"uat"'
}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title></title>
</head>
<body>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>
server {
listen 80;
server_name localhost;
#vue项目的打包后的dist
root /var/www/html;
location / {
try_files $uri $uri/ @router;#需要指向下面的@router否则会出现vue的路由在nginx中刷新出现404
index index.html index.htm;
}
#对应上面的@router,主要原因是路由的路径资源并不是一个真实的路径,所以无法找到具体的文件
#因此需要rewrite到index.html中,然后交给路由在处理请求资源
location @router {
rewrite ^.*$ /index.html last;
}
}
This diff is collapsed.
{
"name": "demo",
"version": "1.0.0",
"description": "A Vue.js project",
"author": "other",
"private": true,
"scripts": {
"dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js",
"start": "npm run dev",
"lint": "eslint --fix --ext .js,.vue src",
"build": "NODE_ENV='production' node build/build.js",
"build:devol": "NODE_ENV='devol' node build/build.js",
"build:sit": "NODE_ENV='sit' node build/build.js",
"build:uat": "NODE_ENV='uat' node build/build.js"
},
"dependencies": {
"ant-design-vue": "^1.4.10",
"axios": "^0.19.0",
"crypto-js": "^3.1.9-1",
"js-cookie": "^2.2.1",
"js-md5": "^0.7.3",
"vue": "^2.5.2",
"vue-router": "^3.0.1",
"vuex": "^3.1.2"
},
"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-loader": "^7.1.1",
"babel-plugin-import": "^1.13.0",
"babel-plugin-syntax-jsx": "^6.18.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",
"chalk": "^2.0.1",
"compression-webpack-plugin": "^1.1.11",
"copy-webpack-plugin": "^4.0.1",
"cross-env": "^5.2.0",
"css-loader": "^0.28.0",
"eslint": "^4.15.0",
"eslint-friendly-formatter": "^3.0.0",
"eslint-loader": "^1.7.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",
"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",
"semver": "^5.3.0",
"shelljs": "^0.7.6",
"uglifyjs-webpack-plugin": "^1.1.1",
"url-loader": "^0.5.8",
"vue-loader": "^13.3.0",
"vue-style-loader": "^3.0.1",
"vue-template-compiler": "^2.5.2",
"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"
]
}
<template>
<div id="App">
<router-view/>
</div>
</template>
<script>
export default {
name: 'App',
// endChange
created() {
// this.getInfo()
// let token = this.$cookie.get('token')
// if (token !=undefined && token !=null && 'String'==typeof token) {
// this.$ajax.post({
// url:this.$api.CHECKTOKEN_POST
// })
// }
}
}
</script>
<style>
#App {
font-family: 'Avenir', Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
width: 100%;
height: 100%;
overflow: hidden;
}
</style>
.layoutMargin {
background-color: #fff;
margin: 16px 16px 0 16px;
}
.layoutPadding {
padding: 20px;
}
.routerWapper { height: 100%;}
.contentPadding { padding: 20px 10px }
/* 全局table设置 */
/* .routerWapper{ */
/* overflow: hidden; */
/* overflow-y:scroll; */
/* height: 100%; */
/* } */
.portalTableOperates {
margin-bottom: 10px;
}
.portalTable {
margin-bottom: 16px;
padding: 10px;
}
.portalTable .actionBtn {
color: #1890ff;
cursor: pointer;
}
/* 全局基础样式设置 */
.algin-right {
text-align: right;
}
.gayLine {
display: block;
clear: both;
width: 100%;
border-bottom: 1px dashed #e0e0e0;
margin: 20px 0
}
.gayLine.noline {
border-color: transparent
}
/* 全局表单样式 */
.protalForm .formItem {
width: 100%
}
.protalForm .formItemLine {
margin-bottom: 10px;
}
/* 详情页 */
.portalDetailWapper {
height: 100%;
}
.portalDetailTitle {
position: relative;
z-index: 2;
background-color: #fff;
text-align: left;
box-shadow: 0 2px 5px #e0e0e0
}
.portalDetailTitle .title {
font-size: 16px;
font-weight: bold;
color: #232323;
height: 60px;
line-height: 60px;
padding-left: 1rem
}
.portalDetailTitle .detailOperations {
float: right;
margin: 14px 1rem 0 0
}
.portalDetailContentWapper {
position: relative;
z-index: 1;
height: calc(100% - 60px)
}
.portalDetailContentBody {
position: relative;
z-index: 1;
overflow: hidden;
overflow-y: scroll;
padding-bottom: 20px;
height: 100%;
}
.portalDetailContentWapper .detailsPartSection {
margin-top: 20px;
padding-bottom: 20px;
}
.portalDetailContentWapper .detailsPartTitle {
text-align: left;
font-weight: bold;
color: #232323;
margin: 0 16px 16px 16px;
font-size: 16px;
text-indent: 0.5rem;
line-height: 60px;
border-bottom: 1px solid #e0e0e0;
}
.portalDetailContentWapper .detailsPartLine { margin-bottom: 16px }
/* 权限树详情页 */
.portalRoleTree { display: flex; flex-wrap: wrap; }
.portalRoleTree>li { flex: 0 0 25%; }
.portalRoleConfigTree .ant-tree-child-tree{ display: flex; flex-wrap: wrap; }
.portalRoleConfigTree .ant-tree-child-tree{ display: flex; flex-wrap: wrap; }
/* .portalRoleTree.ant-tree>li>.ant-tree-node-content-wrapper, */
.portalRoleConfigTree.ant-tree>li>.ant-tree-node-content-wrapper{ font-weight: bold }
.ant-input,
.ant-select,
.ant-select-selection,
.ant-input-number,
.ant-table {
border-radius: 2px;
}
.ant-btn {border-radius:2px; cursor: pointer;}
.ant-btn.ant-btn-default { border-color:#1890ff; color: #1890ff }
.has-error .ant-form-explain, .has-error .ant-form-split {font-size: 12px;}
.ant-form-item-control .ant-form-explain, .ant-form-item-control .ant-form-split { margin-top: 0px}
.ant-layout { height:100%}
/* 去除表单验证提示下方外边距 */
.protalForm .ant-form-item { margin-bottom: 0}
.protalForm.ant-form-inline .ant-form-item-with-help { margin-bottom: 0px}
/* 去掉进度条圆角 */
.ant-progress-inner,
.ant-progress-bg {
border-radius: 0 !important;
}
.ant-progress-inner {
background: #fff;
border: 1px solid #1890ff;
}
.ant-layout-sider-zero-width-trigger{
height: 0;
}
.portalDetailContentWapper .detailsPartLine{
margin-right: 1px;
margin-bottom: 1px;
}
/* 表格的操作那一列居中 */
.ant-table-tbody > tr > td:nth-last-child(1) {
text-align: center;
}
.ant-table-small > .ant-table-content > .ant-table-header > table > .ant-table-thead > tr > th, .ant-table-small > .ant-table-content > .ant-table-body > table > .ant-table-thead > tr > th, .ant-table-small > .ant-table-content > .ant-table-scroll > .ant-table-header > table > .ant-table-thead > tr > th, .ant-table-small > .ant-table-content > .ant-table-scroll > .ant-table-body > table > .ant-table-thead > tr > th, .ant-table-small > .ant-table-content > .ant-table-fixed-left > .ant-table-header > table > .ant-table-thead > tr > th, .ant-table-small > .ant-table-content > .ant-table-fixed-right > .ant-table-header > table > .ant-table-thead > tr > th, .ant-table-small > .ant-table-content > .ant-table-fixed-left > .ant-table-body-outer > .ant-table-body-inner > table > .ant-table-thead > tr > th, .ant-table-small > .ant-table-content > .ant-table-fixed-right > .ant-table-body-outer > .ant-table-body-inner > table > .ant-table-thead > tr > th{
text-align: center;
}
.ant-calendar-picker{
width: 100%;
}
/* 各个表单的搜索与重置. */
.algin-right {
margin-right: 10px;
margin-top: 14px;
}
This diff is collapsed.
<template>
<a-form :form="form">
<slot name="title"/>
<a-row v-for="(row, rowIndex) in layout" :key="rowIndex">
<a-col v-for="(item, key) in row" :key="key" :span="item.width" :offset="item.offset || 0" v-show="!item.hidden">
<!-- 不想包裹在a-form-item中可以设置custom为true, 否则render的组件在ActiveFormItem中定义 -->
<template v-if="item.custom && item.render">
<component :is="custom(key, item.render, item.props)"/>
</template>
<ActiveFormItem
v-else
:entry="key"
:model="model"
:item="item"
:labelWidth="labelWidth"
/>
</a-col>
</a-row>
<slot />
</a-form>
</template>
<script>
import Vue from 'vue'
import moment from 'moment'
import ActiveFormItem from './ActiveFormItem'
export default {
name: 'ActiveForm',
components: {
ActiveFormItem
},
props: {
layout: {
type: Array,
required: true
},
model: {
type: Object,
default () {
return {}
}
},
labelWidth: {
type: Number,
default () {
return 5
}
}
},
created () {
this.filterDateItem()
},
data () {
return {
form: this.$form.createForm(this, {
// 当表单值变化时同时赋给model
onValuesChange: (e, val) => {
this.model = Object.assign(this.model, this.operateDateItem(val, true))
}
}),
dateItems: {}
}
},
mounted () {
const model = this.filterDataModel(this.model)
// 根据model初始化表单值
this.form.setFieldsValue(this.operateDateItem(model, false))
},
methods: {
custom(entry, render, props) {
return Vue.component(entry, {
render: render,
props: props,
})
},
// 表单验证,上级组件可以通过this.$refs来调用此函数
validate (callback) {
this.form.validateFields((err, values) => {
if (err) {
this.$com.getFormValidErrTips(this, err) // 这边是后来加的需求,说要全局弹窗
}
callback(err, values)
})
},
// 筛选layout中的日期组件
filterDateItem () {
const result = {}
this.layout.forEach(item => {
for (const key in item) {
if (item[key].type && (item[key].type == 'date' || item[key].type == 'daterange')) {
result[key] = item[key]
}
}
})
this.dateItems = result
},
// 由于antd的日期组件都是moment格式,这里进行了转化成字符串
operateDateItem (obj, isMomentToString) {
const model = { ...obj }
for (const key in model) {
const dateItem = this.dateItems[key]
if (dateItem) {
if (dateItem.type == 'date') {
if (isMomentToString) {
if (!moment.isMoment(model[key])) return model
model[key] = moment(model[key]).format(dateItem.format || 'YYYY-MM-DD')
} else {
model[key] = moment(model[key], dateItem.format || 'YYYY-MM-DD')
}
} else if (dateItem.type == 'daterange') {
if (isMomentToString) {
if (!moment.isMoment(model[key][0]) || !moment.isMoment(model[key][1])) return model
model[key] = [moment(model[key][0]).format(dateItem.format || 'YYYY-MM-DD'), moment(model[key][1]).format(dateItem.format || 'YYYY-MM-DD')]
} else {
model[key] = [moment(model[key][0], dateItem.format || 'YYYY-MM-DD'), moment(model[key][1], dateItem.format || 'YYYY-MM-DD')]
}
}
}
}
return model
},
// 过滤掉layout中不存在的字段,防止You cannot set a form field before rendering a field associated with the value.的错误
filterDataModel (model) {
const keys = Object.keys(model)
if (keys.length <= 0) return {}
const list = []
if (this.layout.length > 0) {
this.layout.forEach(item => {
list.push(...Object.keys(item))
})
}
const result = {}
keys.forEach(key => {
if (list.indexOf(key) >= 0) {
result[key] = model[key]
}
})
return result
}
},
watch: {
model (cur, past) {
// 通过监听model来判断reset表单或给表单重新设置值
const keys = Object.keys(cur)
const result = this.filterDataModel(cur)
if (keys.length <= 0) {
this.form.resetFields()
} else {
this.form.resetFields()
this.form.setFieldsValue(this.operateDateItem(result, false))
}
}
}
}
</script>
<template>
<a-form-item class="activeform-item" :label-col="labelCol" :wrapper-col="wrapperCol" :label="item.label">
<a-input
v-if="item.type == 'input'"
v-decorator="validate"
:placeholder="placeholder"
:disabled="item.disabled" />
<a-textarea
v-if="item.type == 'textarea'"
v-decorator="validate"
:placeholder="placeholder"
:disabled="item.disabled" />
<a-checkbox-group
v-if="item.type == 'checkbox'"
v-decorator="validate"
:options="item.options"
:disabled="item.disabled" />
<a-radio-group
v-if="item.type == 'radio'"
v-decorator="validate"
:options="item.options"
:disabled="item.disabled" />
<a-select
v-if="item.type == 'select'"
v-decorator="validate"
allowClear
:placeholder="placeholder"
:disabled="item.disabled">
<a-select-option
v-for="option in item.options"
:key="option.value"
:value="option.value">
{{option.label}}
</a-select-option>
</a-select>
<a-cascader
v-if="item.type == 'cascader'"
v-decorator="validate"
allowClear
:options="item.options"
:placeholder="placeholder"
:disabled="item.disabled" />
<a-date-picker
v-if="item.type == 'date'"
style="width: 100%"
v-decorator="validate"
:format="item.format"
allowClear
:disabledDate="item.disabledDate || null"
:placeholder="placeholder"
:disabled="item.disabled" />
<a-range-picker
v-if="item.type == 'daterange'"
style="width: 100%"
v-decorator="validate"
:format="item.format"
allowClear
:disabledDate="item.disabledDate || null"
:placeholder="placeholder"
:disabled="item.disabled" />
<a-upload
v-if="item.type == 'upload'"
multiple
v-decorator="validate"
:customRequest="handleRequest"
@change="handleChange"
:remove="item.remove"
accept='.jpg,.jpeg,.png,.gif,.doc,.docx,.xlsx,.xls,.xlsm,.txt,.pdf'
:beforeUpload='item.beforeUpload'>
<a-button>
<a-icon type="upload" /> {{item.txt || '上传'}}
</a-button>
</a-upload>
<a-button class="ActiveForm-view-btn" v-if="item.type == 'view'" v-decorator="validate" @click="handleClick">
<a v-if="model[entry] && model[entry].fileType == 'file'" :href='model[entry].url' target="_blank">{{model[entry] && model[entry].name}}</a>
<span v-else-if="model[entry] && model[entry].fileType == 'pic'">{{model[entry] && model[entry].name}}</span>
<span v-else>{{model[entry] && model[entry].name}}</span>
</a-button>
<span v-if="item.type == 'text'" v-decorator="validate">
{{item.formatter ? item.formatter(model[entry]) : model[entry]}}
</span>
<template v-if="item.render">
<component :is="component" v-decorator="validate"/>
</template>
</a-form-item>
</template>
<script>
import Vue from 'vue'
export default {
name: 'ActiveFormItem',
props: {
entry: {
type: String,
required: true,
},
item: {
type: Object,
required: true,
},
model: {
type: Object,
default() {
return {}
}
},
labelWidth: {
type: Number,
default() {
return 5
}
},
},
data() {
return {
curData: null,
component: null,
}
},
created() {
if (this.item.render) {
this.component = Vue.component(this.entry, {
render: this.item.render,
props: this.item.props,
})
}
},
methods: {
handleRequest(obj) {
// 这里的obj是用来onProgress、onSuccess、onError的
this.$nextTick(() => {
this.item.customRequest(this.curData.file, obj)
})
},
handleChange(data, list) {
// 这里的data是用来响应改变model数据的
this.curData = data
},
// 点击查看文件按钮的回调
handleClick() {
this.item.onClick(this.model[this.entry].url)
},
// 上传文件的过滤,防止类型错误的报错
normFile(e) {
if (Array.isArray(e)) return e
return e && e.fileList
},
},
computed: {
// 默认表单验证
validate() {
if (this.item.type == 'checkbox') {
// 如果是CheckBox的话初始化要是个数组
return [this.entry, Object.assign(this.item.validate || {}, {initialValue: []})]
}
if (this.item.type == 'upload') {
return [this.entry, Object.assign(this.item.validate || {}, {valuePropName: 'fileList' , getValueFromEvent: this.normFile})]
}
return [this.entry, this.item.validate || {}]
},
// 默认placeholder
placeholder() {
const item = this.item
if (item.placeholder) {
return item.placeholder
}
if (item.type == 'input' || item.type == 'textarea') {
return '请输入'
}
if (item.type == 'daterange') {
return ['开始日期', '结束日期']
}
return '请选择'
},
labelCol() {
return {
style: {
width: `${this.labelWidth}px`
},
}
},
wrapperCol() {
return {
style: {
display: 'inline-block',
width: `calc(90% - ${this.labelWidth}px)`
}
}
}
}
}
</script>
<style scoped>
.ActiveForm-view-btn {
max-width: 100%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.activeform-item.ant-row.ant-form-item {
display: flex;
}
</style>
/** ActiveForm 使用示例 */
// 首先在入口文件引入并use组件
// import ActiveForm from '@/components/ActiveForm'
// Vue.use(ActiveForm)
<template>
<a-card>
<ActiveForm :layout="layout" :label-width="7" ref="exampleForm" :model="model">
<div slot="title">
<h2>ActiveForm Title(可选, 样式自己定)</h2>
</div>
<div style="text-align:right">
<a-button @click="handleSearch" type="primary">查询</a-button>
<a-button @click="handleReset">重置</a-button>
</div>
</ActiveForm>
</a-card>
</template>
<script>
export default {
name: 'ExampleComponent',
data() {
return {
layout: [
{
example1: {
label: '输入',
type: 'input',
width: 8,
validate: {
rules: [{required: true, message: '请输入'}]
}
},
example2: {
label: '单选',
type: 'radio',
width: 8,
options: [
{label: '男', value: '1'},
{label: '女', value: '0'},
]
},
example3: {
label: '选择',
type: 'select',
width: 8,
options: [
{label: '苹果', value: '苹果'},
{label: '香蕉', value: '香蕉'},
],
disabled: true
},
example4: {
label: '日期',
type: 'date',
width: 8,
validate: {
rules: [{required: true, message: '请选择日期'}]
}
},
example5: {
label: '日期区间',
type: 'daterange',
width: 8,
validate: {
rules: [{required: true, message: '请选择'}]
}
},
example6: {
label: '级联',
type: 'cascader',
width: 8,
options: [{
value: 'zhejiang',
label: 'Zhejiang',
children: [{
value: 'hangzhou',
label: 'Hangzhou',
children: [{
value: 'xihu',
label: 'West Lake',
}],
}],
}, {
value: 'jiangsu',
label: 'Jiangsu',
children: [{
value: 'nanjing',
label: 'Nanjing',
children: [{
value: 'zhonghuamen',
label: 'Zhong Hua Men',
}],
}],
}]
},
example7: {
label: '多选框',
type: 'checkbox',
width: 8,
options: [
{label: '龙', value: '龙'},
{label: '蛇', value: '蛇'},
],
validate: {
rules: [{required: true, message: '请选择'}]
}
},
example8: {
label: '查看文件',
type: 'view',
width: 8,
onClick: this.handleClick, // 点击后触发的回调,返回对应的url
},
},
{
example9: {
label: '上传',
type: 'upload',
txt: '上传文件', // 上传按钮的名字,不传的话默认是‘上传’
width: 8,
remove: this.remove, // remove动作会自动完成,没有其他特殊要求的操作可以不用调用了
customRequest: this.customRequest, // 上传请求
beforeUpload: this.beforeUpload, // 上传前的校验,没有其他特殊要求的操作可以不用调用了
validate: {
rules: [{required: true, message: '请上传'}]
}
},
example10: {
label: '文字',
type: 'text',
width: 8,
formatter: (val) => val += '(进行格式化处理)'
},
example11: {
label: 'test',
width: 8,
render:(h) => {
// render自定义组件,会比较麻烦
return h('div', [
h('p', '测试render2个'),
h('a-select', {
props: {
placeholder: 'placeholder',
allowClear: true,
options: [{value: 'test', label: '测试render select'}],
},
on: {
select(val) {}
},
style: 'color: red',
})
])
},
}
}
],
model: {
example4: '2019/08/23',
example5: ['2019/08/23', '2019/08/24'],
example7: ['龙'],
example8: {
name: 'accounts.xlsx',
fileType: 'file', // file为新开页下载,pic根据你的event回调来定
url: 'http://iftp.omniview.pro/temp/1567069629359-634052a98b43d554976a440bf07a35af.xlsx',
},
example9: [
{
name: 'accounts.xlsx',
objId: null,
response: null,
status: null,
type: null,
uid: 'f58ba55c2ce847a0bc7ea0ca74fadcb9',
url: 'http://iftp.omniview.pro/temp/1567069629359-634052a98b43d554976a440bf07a35af.xlsx',
}
],
example10: '纯展示数据用',
},
}
},
methods: {
handleSearch() {
this.$refs.exampleForm.validate(err => {
if (err) return
// TODO 这里做接下来的其他操作
this.model.example10 = JSON.stringify(this.model)
})
},
handleReset() {
this.model = {
example8: {
name: 'accounts.xlsx',
fileType: 'file',
url: 'http://iftp.omniview.pro/temp/1567069629359-634052a98b43d554976a440bf07a35af.xlsx',
},
}
},
// 上传的请求
customRequest(data, fn) { // data是数据,可修改data来同步更新model;fn用来调用onProgress、onSuccess、onError这些函数
setTimeout(() => { // 模拟请求完成后的操作
// 这里可以修改data的一些所需的字段值,比如插入url什么的
data.url = 'http://www.baidu.com'
// !!有一点不同的是没必要再手动插入model中了,model会自动更新
fn.onSuccess()
}, 500)
},
// remove动作会自动完成,如果没有其他特殊要求的操作可以去掉了
remove(data) {
},
// 上传前的校验,没有其他特殊要求的操作可以去掉了
beforeUpload(data) {
},
handleClick(data) {
},
}
}
</script>
import ActiveFormComponent from './ActiveForm'
export default (Vue) => {
Vue.component(ActiveFormComponent.name, ActiveFormComponent)
}
<template>
<a-table
:size="size"
:rowKey="rowKey"
:columns="layout"
:dataSource="data"
:bordered="bordered"
:rowClassName="setClassName"
:rowSelection="rowSelections"
:customRow="customRow"
:pagination="pagination"
@change="handleTableChange"
>
<template v-for="item in renderItems" :slot="item.dataIndex" slot-scope="text, record">
<a v-if="item.type === 'link'" :key="item.dataIndex" @click="item.onClick(record)">{{ text || item.linkText }}</a>
<span v-else-if="item.type === 'date'" :key="item.dataIndex">{{ $com.formatDate(text) }}</span>
<span v-else-if="item.type === 'money'" :key="item.dataIndex">{{ $com.toMoney(text) }}</span>
<a-input
v-else-if="item.type === 'input'"
v-model="record[item.dataIndex]"
:disabled="item.disabled"
:placeholder="item.placeholder"
style="width: 100%;"
:key="item.dataIndex"
/>
<a-select
v-else-if="item.type === 'select'"
v-model="record[item.dataIndex]"
:disabled="item.disabled"
:placeholder="item.placeholder"
allowClear
style="width: 100%;"
:key="item.dataIndex"
>
<a-select-option
v-for="option in item.options"
:key="option.value"
:value="option.value"
>{{ option.label }}</a-select-option>
</a-select>
<a-radio-group
v-if="item.type === 'radio'"
v-model="record[item.dataIndex]"
:options="item.options"
:disabled="item.disabled"
:key="item.dataIndex"
/>
</template>
<template slot="footer">
<slot name="footer" />
</template>
<template slot="title">
<slot name="title" />
</template>
<template v-for="item in slotItems" :slot="item.scopedSlots.customRender" slot-scope="text, record, index">
<slot :name="item.scopedSlots.customRender" :text="text" :record="record" :index="index" />
</template>
</a-table>
</template>
<script>
export default {
name: 'ActiveTable',
props: {
columns: {
type: Array,
default() {
return []
}
},
rowKey: {
type: String,
default: ''
},
data: {
type: Array,
default() {
return []
}
},
size: {
type: String,
default: 'small'
},
bordered: {
type: Boolean,
default: false,
},
stripe: {
// 是否显示斑马纹
type: Boolean,
default: false,
},
rowSelect: {
// 开启单行选择
type: Boolean,
default: false,
},
multiSelect: {
// 开启多选
type: Boolean,
default: false
},
multiSelectDisabled: {
// 开启多选后, 是否disable多选的checkbox
type: Boolean,
default: false
},
multiSelectDefaultChecked: {
// 开启多选后,根据此key来判断每行默认选中状态
type: [Boolean, String, Function],
default: ''
},
rowSelection: {
type: Object,
default: () => {
return {}
}
},
showPager: {
type: Boolean,
default: false,
},
total: {
type: Number,
},
currentPage: {
type: Number,
default: 1,
},
pageSize: {
type: Number,
default: 10,
}
},
data() {
return {
selectedRowKeys: [],
selectedRows: {},
renderItems: [],
slotItems: [],
}
},
methods: {
setClassName(record, index) {
if (this.selectedRowKeys.indexOf(record[this.rowKey]) >= 0 && this.rowSelect) {
return 'selected-row'
} else {
if (!this.stripe) {
return ''
}
return index % 2 === 1 ? 'odd' : 'even'
}
},
handleTableChange(pagination, filters, sorter) {
this.$emit('on-filter-change', filters)
this.$emit('on-page-change', pagination)
},
customRow(record) {
if (!this.rowSelect) return {}
return {
props: {},
on: {
click: () => {
// 选中项
this.selectedRows = record
// 选中项主键
this.selectedRowKeys = [record[this.rowKey]]
this.$emit('on-select-change', [record[this.rowKey]], record)
}
}
}
},
onSelectChange(selectedRowKeys, selectedRows) {
this.$emit('on-select-change', selectedRowKeys, selectedRows)
},
// 开启多选后 - 选择框的默认属性配置
getCheckboxProps(record) {
return {
props: {
defaultChecked: record[this.multiSelectDefaultChecked],
disabled: this.multiSelectDisabled
}
}
}
},
computed: {
rowSelections() {
if (!this.multiSelect) {
if (this.rowSelection.hasOwnProperty('type')) {
return { getCheckboxProps: this.getCheckboxProps, type: this.rowSelection.type }
} else {
return
}
} else {
return { onChange: this.onSelectChange, getCheckboxProps: this.getCheckboxProps }
}
},
pagination () {
if (!this.showPager) return false
return { showQuickJumper: true, total: this.total, current: this.currentPage, pageSize:this.pageSize }
},
layout() {
/* eslint-disable */
this.selectedRowKeys = this.data.length > 0 ? [this.data[0][this.rowKey]] : []
this.selectedRows = this.data.length > 0 ? this.data[0] : {}
const columns = [...this.columns]
columns.forEach(item => {
if (item.type) {
item.scopedSlots = { customRender: item.dataIndex }
this.renderItems.push(item)
} else {
if (item.scopedSlots) {
this.slotItems.push(item)
}
}
})
return columns
}
}
}
</script>
<template>
<ActiveTable
rowKey="id"
:columns="columns"
:data="list"
@on-page-change="test"
@on-select-change="select"
:currentPage="currentPage"
:total="total"
showPager
>
<div slot="test" slot-scope="{ text, record }">
<span>{{ text }}</span>
<a-input :value="record.gender"></a-input>
</div>
</ActiveTable>
</template>
<script>
export default {
name: 'TableExample',
data () {
return {
total: 30,
currentPage: 1,
columns: [{
title: 'Test',
dataIndex: 'test',
scopedSlots: { customRender: 'test' }
}, {
title: 'Name',
dataIndex: 'name',
type: 'link',
align: 'center',
onClick: this.testClick
}, {
title: 'Gender',
dataIndex: 'gender',
align: 'center',
type: 'input',
placeholder: '请输入',
filterMultiple: false,
filters: [
{ text: 'Male', value: 'male' },
{ text: 'Female', value: 'female' }
]
}, {
title: 'Email',
dataIndex: 'email',
align: 'center',
type: 'select',
placeholder: '请选择',
options: [
{ label: 'qq', value: '@qq.com' },
{ label: 'gmail', value: '@gmail.com' }
]
}],
list: [
{ test: 'aa', id: '1', name: 'test', gender: 'male', email: '@qq.com' },
{ id: '2', name: 'test', gender: 'male', email: '@qq.com' },
{ id: '3', name: 'test', gender: 'female', email: '@qq.com' },
{ id: '4', name: 'test', gender: 'male', email: '@qq.com' }
]
}
},
methods: {
log (a, b) {
},
test ({ current }) {
this.currentPage = current
},
select (a, b) {
},
testClick (data) {
}
}
}
</script>
import ActiveTableComponent from './ActiveTable'
export default (Vue) => {
Vue.component(ActiveTableComponent.name, ActiveTableComponent)
}
<template>
<router-view></router-view>
</template>
<script>
export default {
name: 'Wrapper',
}
</script>
<style>
</style>
This diff is collapsed.
<template>
<div class="header">
<a-breadcrumb class="portalNavBar">
<template v-for="(item,index) in list">
<a-breadcrumb-item :key="index">
<template v-if="!item.hideBread">
<template v-if="item.openMode">
<span v-if="item.showBreadPath" style="color:#1890ff" class="navlink" @click="$router.push(item.path)">{{item.title}}</span>
<span v-else>{{item.title}}</span>
</template>
<template v-else>
<router-link v-if="item.showBreadPath" style="color:#1890ff" class="navlink" :to="item.path">{{item.title}}</router-link>
<span v-else>{{item.title}}</span>
</template>
</template>
<span v-else>{{item.title}}</span>
</a-breadcrumb-item>
</template>
</a-breadcrumb>
</div>
</template>
<style scoped>
.navlink{ cursor: pointer;color:#1890ff}
/* .portalNavBar.ant-breadcrumb .navlink { color: #1890ff} */
</style>
<script>
import {checkHideInBread} from '@/util/mixins'
import {routes} from '@/router/routes'
export default {
name: 'NavBar',
data() {
return {
list: [],
}
},
mounted() {
const list = this.$cookie.get('NavbarList')
if(list && list.length>0){
this.list = JSON.parse(list)
}else{
this.list = [{title: '首页',routerName:'home', path: '/home'}]
}
this.list.forEach((element)=>{
this.checkPath(element)
})
this.getRoutes()
},
methods: {
/**
* @param {Object} navItem 当前展示路由的完整父子级中的一个路由节点
* @returns {Object} hideBread 是否隐藏在面包屑中,true为隐藏
* showBreadPath 是否在面包屑展中可点击跳转,true为可点击
*/
checkPath(navItem) {
let hideBread=false, showBreadPath = true
if(this.$route.path === navItem.path){
// 面包屑中的路径与当前路由一致时,无法点击,但要显示在面包屑中
navItem.hideBread = false
navItem.showBreadPath = false
}else{
if('/home' === navItem.path || '/' === navItem.path){
// 默认面包屑中的“首页”都是可以点击的
navItem.hideBread = false
navItem.showBreadPath = true
}else{
navItem.hideBread = checkHideInBread(navItem.routerName)
navItem.showBreadPath = hideBread ? false:true
}
}
},
getRoutes() {
const layoutRoutes = routes.find(route => route.name == 'Layout')
let list = []
layoutRoutes.children.forEach(parent => {
if (parent.children && parent.children.length > 0) {
parent.children.forEach(child => {
list.push(child)
})
}
})
this.routeList = list
}
},
watch: {
$route(to, from) {
let navList = []
if(to && to.matched && Array.isArray(to.matched)){
if (!to.name) {
const parentRoute = this.routeList.find(item => to.path.startsWith(item.path))
navList.push({ title: '首页', routerName:'home', path: '/home' })
if(parentRoute){
navList.push({ title: parentRoute.meta.title, routerName: parentRoute.name, path: parentRoute.path, openMode:!parentRoute.meta.openMode?'normal':parentRoute.meta.openMode})
}
}
to.matched.forEach((element,index) => {
if(0===index){
if (element.path.indexOf('/')>=0) {
if('/'===element.path || '/home'===element.path){
navList.push({
title: element.meta.title,
routerName:element.name,
path: element.path,
openMode:!element.meta.openMode?'normal':element.meta.openMode
})
}else{
navList.push({ title: '首页', routerName:'home', path: '/home' })
navList.push({
title: element.meta.title,
routerName:element.name,
path: element.path,
openMode:!element.meta.openMode?'normal':element.meta.openMode
})
}
}else{
navList.push({ title: '首页', routerName:'home', path: '/home', openMode:'normal' })
}
}else {
if('/'!=element.path && ''!=element.path && '/home'!=element.path){
navList.push({
title: element.meta.title,
routerName:element.name,
path: element.path,
openMode: !element.meta.openMode?'normal':element.meta.openMode
})
}
}
})
} else {
navList = [{title: '首页', path: '/'}]
}
navList.forEach((element)=>{
this.checkPath(element)
})
this.list = navList
this.$cookie.set('NavbarList', JSON.stringify(navList))
}
},
}
</script>
<template>
<div style="width: 200px">
<a-menu
mode="inline"
theme="dark"
v-model="current"
@openChange="onOpenChange"
:inlineCollapsed="collapsed"
:openKeys="openKeys"
@click="handleClick"
>
<template v-for="menu in list">
<template v-if="menu.children && menu.children.length > 0">
<a-sub-menu :key="menu.name" class="mus">
<span slot="title"><a-icon :type="menu.meta.menuIcon" /><span>{{menu.meta.title}}</span></span>
<a-menu-item class="mus" v-for="(item) in menu.children" :key="item.name">
<a-icon type="search" />
<span>{{item.meta.title}}</span>
</a-menu-item>
</a-sub-menu>
</template >
<template v-else>
<a-menu-item class="mus" :key="menu.name">
<a-icon :type="menu.meta.menuIcon" />
<span>{{menu.meta.title}}</span>
</a-menu-item>
</template >
</template >
</a-menu>
</div>
</template>
<script>
import common from '@/util/common'
import {menus} from '@/router/routes'
export default {
name: 'SideMenu',
props: {
menuMode:{
type: String,
required: true,
validator (value) {
return common.oneOf(value, ['vertical','inline' ])
}
}
},
data() {
return {
collapsed: false,
list:[],
light:[],
current:[],
rootSubmenuKeys: [],
openKeys: [],
}
},
methods: {
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] : []
}
},
toggleCollapsed() {
this.collapsed = !this.collapsed
},
jump(name){
if (this.$route.name == name) {
return
} else {
this.$router.push({name})
}
},
handleClick(e) {
this.current = [e.key]
this.jump(e.key)
},
},
mounted(){
this.list = menus
menus.forEach(col=>{
this.rootSubmenuKeys.push(col.name)
})
},
watch:{
$route(to){
menus.forEach(ele => {
if (ele.name == to.name) {
this.current = [to.name]
}
})
}
}
}
</script>
<style scoped>
.mus{
text-align: left;
}
</style>
<template>
<div class="loader" v-show="$store.state.showLoading">
<a-spin :tip="msg" :spinning="$store.state.showLoading" size="large"/>
</div>
</template>
<script>
export default {
name: 'Loader',
props: {
msg: {
type: String,
default: '加载中...',
}
}
}
</script>
<style scoped>
.loader {
position: fixed;
top: 0;
left: 0;
bottom: 0;
right: 0;
background-color: rgba(0,0,0,0.1);
display: flex;
align-items: center;
justify-content: center;
z-index: 999;
}
</style>
<template>
<div>
<a-row type="flex" justify="center" align="top" class="detailContent">
<a-col v-if="label" :span="labelSpan" class="label">{{label}}</a-col>
<a-col :span="textSpan" class="text">
<template v-for="(item,index) in fileList">
<template v-if="'pic'==item.fileType">
<p :key='index' @click="showOrgPic(item.url)" class="canload fileItem">{{item.name}}</p>
</template>
<template v-else-if="'file'==item.fileType">
<p :key='index' class="canload fileItem"><a :href='item.url' target="_blank">{{item.name}}</a></p>
</template>
<template v-else="">
<p :key='index' class="fileItem"><a :href='item.url' target="_blank">{{item.name}}</a></p>
</template>
</template>
</a-col>
</a-row>
<a-modal
:title='orgPic.name'
v-model="visibleOrgPic"
@ok="handleOk"
:footer="null"
>
<p class="orgPicContent"><img :src='orgPic.url' /></p>
</a-modal>
</div>
</template>
<script>
export default {
name: 'DetailFiles',
props: {
labelSpan:{
type: Number,
default:10
},
textSpan:{
type: Number,
default:14
},
label:{
type: String,
default: null,
},
/**
* 需要展示的文件列表
* [{name:文件名, url:文件链接}]
*/
files:{
type: Array,
default() {
return []
}
},
/**
* 需要展示的网络视频列表
*/
file:{
type: Array,
default() {
return []
}
}
},
data() {
return {
fileList:[],
netList:[],
visibleOrgPic:false,
orgPic:{
'url':'',
'name':''
},
orgPicUrl:''
}
},
computed: {
},
created(){
if(Array.isArray(this.files)){
for(let i=0;i<this.files.length;i++){
this.fileList.push({
'name':this.files[i].name,
'fileType':this.getFileType(this.files[i].name),
// 'uid':this.files[i].uid,
'url':this.files[i].url,
})
}
}
if(Array.isArray(this.file)){
for(let i=0;i<this.file.length;i++){
this.netList.push({
'name':this.file[i],
'fileType':'',
'url':this.file[i],
})
}
}
this.fileList = this.netList.concat(this.fileList)
},
methods: {
showOrgPic(url,name){
if(!!url){
this.orgPic.url = url
this.orgPic.name = !name?'查看图片':name
this.visibleOrgPic = true
}
},
handleOk(){
this.visibleOrgPic = false
this.orgPic = {
'url':'',
'name':''
}
},
/**
* 获取文件后缀名,判别文件为图片(pic)或文件(file)
* @param {String} name
* @returns 图片(pic)或文件(file);否则为空
*/
getFileType(name){
name = !name?'':name
let arr = name.split('.')
if(arr.length>0){
let len = arr.length
let suffix = arr[len-1].toLowerCase()
if(this.$com.oneOf(suffix,['jpeg','jpg','gif','png'])){
return 'pic'
}else if(this.$com.oneOf(suffix,['pdf','txt','doc','docx','xlsx','xls','xlsm','ppt','pptx'])){
return 'file'
}else{
return ''
}
}else{
return ''
}
}
}
}
</script>
<style>
.detailContent { font-size: 14px; }
.detailContent .label { text-align: right; color:rgba(0, 0, 0, 0.45)}
.detailContent .text { text-align: left; word-break:break-all;}
.detailContent .fileItem { border-radius: 2px; margin-left: 5px; border: 1px solid #ccc; display:inline-block; padding:2px 5px; font-size: 12px; line-height: 20px}
.detailContent .fileItem.canload { border-color: #1890ff; color: #1890ff; cursor: pointer;}
.orgPicContent { text-align: center}
.orgPicContent img{ width: 100%}
</style>
<template>
<a-row type="flex" justify="center" align="top" class="detailContent">
<a-col v-if="label !== ''" :title="label" :span="labelSpan" class="label">{{label}}</a-col>
<a-col v-else :title="label" :span="labelSpan" class="label" style="color:#fafafa">.</a-col>
<a-col :span="textSpan" class="text">
<a-tooltip>
<template slot="title">
{{text}}
</template>
<template v-if="text && text.length>0">{{text}}</template>
</a-tooltip>
<slot name='detailContent'></slot>
</a-col>
</a-row>
</template>
<script>
export default {
name: 'DetailItem',
props: {
labelSpan:{
type: Number,
default:10
},
textSpan:{
type: Number,
default:14
},
label:{
type: String,
required: true
},
text:{
// type: String,
default() {
return ''
}
}
},
data() {
return {
pointColor:''
}
},
computed: {
},
created(){
},
methods: {
}
}
</script>
<style>
.detailContent { margin: -1px; font-size: 14px; line-height: 40px;border: 1px solid #ccc}
.detailContent .label { white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;border-right:1px solid #ccc; background-color: #fafafa; text-align: right; color:rgba(0, 0, 0, 0.65)}
.detailContent .text {white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;padding-left: 8px; text-align: left; word-break:break-all;}
</style>
<template>
<div style="margin-top:-24px">
<div class='input_span' >
<span id="one" :style="returnStyle"></span>
<span id="two" :style="returnStyle"></span>
<span id="three" :style="returnStyle"></span>
</div>
</div>
</template>
<script>
export function checkStrong(sValue) {
var modes = 0
//正则表达式验证符合要求的
if (sValue.length < 1) return modes
if (/\d/.test(sValue)) modes++ //数字
if (/[a-z]/.test(sValue)) modes++ //小写
if (/[A-Z]/.test(sValue)) modes++ //大写
if (/\W/.test(sValue)) modes++ //特殊字符
//逻辑处理
switch (modes) {
case 1:
return 1
break
case 2:
return 2
break
case 3:
case 4:
return sValue.length < 10 ? 3 : 4
break
}
return modes
}
export default {
props: {
pwd:{
type:String,
default(){
return ''
}
},
width:{
type:[String,Number]
}
},
data() {
return {
msgText: '',
}
},
computed: {
returnStyle(){
if(this.width){
return{
width:this.width+'px'
}
}
}
},
watch: {
pwd(newValue, oldValue) {
this.msgText = checkStrong(newValue)
if (this.msgText > 1 || this.msgText == 1) {
document.getElementById('one').style.backgroundColor = 'red'
} else {
document.getElementById('one').style.backgroundColor = '#eee'
}
if (this.msgText > 2 || this.msgText == 2) {
document.getElementById('two').style.backgroundColor = 'orange'
} else {
document.getElementById('two').style.backgroundColor = '#eee'
}
if (this.msgText == 4) {
document.getElementById('three').style.backgroundColor = '#00D1B2'
} else {
document.getElementById('three').style.backgroundColor = '#eee'
}
}
},
}
</script>
<style scoped>
#inputValue {
width: 240px;
margin-left: 20px;
padding-left: 10px;
border-radius: 3px;
}
.input_span {
height: 24px;
}
.input_span span {
display: inline-block;
width: 84px;
height: 6px;
background: #eee;
line-height: 20px;
}
#one {
border-top-left-radius: 5px;
border-bottom-left-radius: 5px;
border-right: 0px solid;
margin-right: 5px;
}
#two {
border-left: 0px solid;
border-right: 0px solid;
margin-left: -5px;
margin-right: 5px;
}
#three {
border-top-right-radius: 5px;
border-bottom-right-radius: 5px;
border-left: 0px solid;
margin-left: -5px;
}
#font {
font-size: 12px;
}
#font span:nth-child(1) {
color: red;
margin-left: 35px;
}
#font span:nth-child(2) {
color: orange;
margin: 0 60px;
}
#font span:nth-child(3) {
color: #00D1B2;
}
</style>
// The Vue build version to load with the `import` command
// (runtime-only or standalone) has been set in webpack.base.conf with an alias.
import Vue from 'vue'
// toChange : 引入SPA-VUE组件。同时注意需在package.json中引入 single-spa、single-spa-vue两个包
// import singleSpaVue from 'single-spa-vue'
import App from './App.vue'
import router from './router'
import store from './store'
import ajax from './server/ajax'
import api from './server/api'
import cookie from './util/local-cookie'
import common from './util/common'
// import PermissionControl from './util/permission-control.js' // 权限自定义指令 v-permission="code"
import {PermissionFilter} from './util/permission-filter.js' // 权限全局方法 v-if="$permission('code')"
import {
Button,
message,
Spin,
Layout,
Menu,
Icon,
Breadcrumb,
Form,
Input,
InputNumber,
Card,
Dropdown,
Row,
Col,
Checkbox,
Select,
Alert,
Table,
Divider,
Upload,
Modal,
Tree,
Tabs,
DatePicker,
// skeleton,
Pagination,
Tag,
// Badge,
TreeSelect,
Radio,
Cascader,
LocaleProvider,
Steps,
Anchor,
Collapse,
Popconfirm,
Progress,
Switch,
Calendar,
BackTop,
Carousel,
Tooltip,
// CollapsePanel
} from 'ant-design-vue'
import DetailsItem from '@/components/detail/detailItem'
import DetailsFile from '@/components/detail/detailFile'
import './assets/base.css' // 引入全局样式
import './assets/reset-ant.css' // 重置ant-design样式
import ActiveForm from '@/components/ActiveForm'
import ActiveTable from '@/components/ActiveTable'
import RouterWapper from '@/components/Layout/content-wrapper'
import ZdyModal from '@/views/components/zdyModal' // 自定义字段修改弹框
import MutSelect from '@/views/components/mutSelect' // 多选框(控制表格展示列)
// import Demo from '@/views/components/demo' // 多选框(控制表格展示列)
import tableAndMut from '@/views/components/tableAndMut' // 多选框(控制表格展示列)
import BatchOperation from '@/views/components/BatchOperation' // 多选框(控制表格展示列)
// 由于日期组件默认是英文的,需要本地化
import moment from 'moment'
import 'moment/locale/zh-cn'
moment.locale('zh-cn')
Vue.use(ActiveForm)
Vue.use(ActiveTable)
// Vue.component('Demo', Demo)
Vue.component('tableAndMut', tableAndMut)
Vue.component('ZdyModal', ZdyModal)
Vue.component('MutSelect', MutSelect)
Vue.component('BatchOperation', BatchOperation)
Vue.component('DetailsItem', DetailsItem)
Vue.component('DetailsFile', DetailsFile)
Vue.component('RouterWapper', RouterWapper)
// Vue.component('comTable',comTable)
Vue.use(router)
Vue.use(Button)
Vue.use(Spin)
Vue.use(Layout)
Vue.use(Menu)
Vue.use(Icon)
Vue.use(Breadcrumb)
Vue.use(Form)
Vue.use(Input)
Vue.use(InputNumber)
Vue.use(Card)
Vue.use(Dropdown)
Vue.use(Row)
Vue.use(Col)
Vue.use(Checkbox)
Vue.use(Select)
Vue.use(Alert)
Vue.use(Table)
Vue.use(Divider)
Vue.use(Upload)
Vue.use(Modal)
// Vue.use(badge)
// Vue.use(skeleton)
Vue.use(Tree)
Vue.use(Tabs)
Vue.use(DatePicker)
Vue.use(Pagination)
Vue.use(Tag)
// Vue.use(Badge)
Vue.use(TreeSelect)
Vue.use(Radio)
Vue.use(Cascader)
Vue.use(LocaleProvider)
Vue.use(Steps)
Vue.use(Anchor)
Vue.use(Collapse)
Vue.use(Popconfirm)
Vue.use(Progress)
Vue.use(Switch)
Vue.use(Calendar)
Vue.use(BackTop)
Vue.use(Carousel)
Vue.use(Tooltip)
Vue.config.productionTip = false
Vue.prototype.$ajax = ajax
Vue.prototype.$api = api
Vue.prototype.$cookie = cookie
Vue.prototype.$com = common
Vue.prototype.$permission = PermissionFilter
Vue.prototype.$message = message
Vue.prototype.$modal = Modal
Vue.prototype.$store = store
Vue.prototype.$moment = moment
new Vue({
el: '#app',
router,
store,
render: h => h(App),
template: '<App/>',
})
Vue.directive('title', {
inserted: function (el, binding) {
document.title = el.dataset.title
}
})
// endChange
import Vue from 'vue'
import Router from 'vue-router'
import {routes} from './routes'
import Cookie from '@/util/local-cookie'
import Common from '@/util/common'
import store from '@/store'
Vue.use(Router)
const config = {
mode: 'history',
routes,
}
const router = new Router(config)
/**
* todo
*/
router.beforeEach((to, from, next) => {
const token = Cookie.get('token')
// 当前无token且不在login页面则推到登录页面
if (to.path != '/login' && !token) {
next('/login')
} else {
next()
}
})
export default router
// toChange : 需严格按下述要求完成router配置。且不允许使用懒加载模式的components引入方式
import Layout from '@/components/Layout/main'
import TipsNetworkErr from '@/views/errorTips/network'
import TipsNoAuth from '@/views/errorTips/auth'
import ContentWrapper from '@/components/Layout/content-wrapper'
import HomePage from '@/views/home'
import LoginPage from '@/views/login'
import PersonCenter from '@/views/person-center'
import basicInfo from '@/views/houseData/basicInfo'
import PropertyInfo from '@/views/houseData/propertyInfo'
import CmDivision from '@/views/houseData/cmDivision'
import Managers from '@/views/houseData/managers'
import IndCous from '@/views/houseData/indCous'
import NonResidents from '@/views/houseData/nonResidents'
import Buildings from '@/views/houseData/buildings'
import Members from '@/views/houseData/members'
import BasicView from '@/views/houseData/basicView'
import Shebei from '@/views/houseData/shebei'
import PropertyInfoView from '@/views/houseData/propertyInfoView'
import Tbls from '@/views/houseData/tbls'
import Demo from '@/views/components/demo'
// import TipsUpperLimitErr from '@/views/tips/upperlimit'
// import TipsNetworkErr from '@/views/tips/network'
// import TipsInnerNetworkErr from '@/views/tips/innerNetwork'
// import TipsNoAuth from '@/views/tips/auth'
// import TipsOutsite from '@/views/tips/outsite'
/**
* 要求:
* 1、配置Router时,需将此router的权限编码信息、打开方式信息、是否在面包屑隐藏信息、是否为左侧菜单、是否有菜单图标配置在内。
* 2、如此路由节点是要在面包屑中展示的,则需严格按照路由父子级,将子路由定义在其直属父级路由的children中
* 3、为页面渲layout,所有除’/‘外的路由,都需定在’/‘路由的children下
* 作用:除去正常路由展示,系统会从本数组内抓取权限菜单、判断是否能在面包屑中显示
* 特殊配置字段说明:
* meta.menuPath 标记是否为菜单。不是可不设置此字段
* meta.authCode 当此路由受权限控制,需设置此字段,并填写与服务端一直的权限编码。不受权限控制可不设置
* meta.hideInBread 标记是否需在面包屑中展示。不是可不设置此字段
* meta.menuIcon 标记此路由在展示时需显示的ant design的图标。只能填写ant design中ICON组件内允许的字符串
* meta.openMode 标记此路由点击后展示打开的方式。若值为normal,可不设置此字段
* spa 注册子前端项目的路由。注,此时设置的router.name为子项目展现路由名称的name,且需带上子项目名称前缀。如:/{micname}/{子项目router.name},且无需设定router.component
* normal 本项目中自有路由
* outsite 新开标签页打开,此打开方式将不嵌套layout
* meta.outsiteLink 当meta.openMode 为outsite时,必须配置此字段,并设置完整跳转页面的href
*
*/
export const menus = [
{
path: '/house', name: 'house', component:ContentWrapper,
meta: { title: '住宅信息', menuPath: true, menuIcon: 'profile', hideInBread: true },
children: [
{ // 小区列表
path: '/houseData/basicInfo', name: 'basicInfo',component: basicInfo,
meta: { title: '小区列表', menuPath:true, hideInBread:false, },
children:[
{
path: '/houseData/basicInfo/:id', name: 'basicView',component: BasicView,
meta: { title: '小区详细情况', hideInBread:false, },
},
{
path: '/houseData/buildings', name: 'buildings',component: Buildings,
meta: { title: '门牌栋', hideInBread:false, },
children:[
{
path: '/houseData/buildings/:id', name: 'buildingView',component: PropertyInfoView,
meta: { title: '门牌栋详情', hideInBread:false, }
}
]
},
{
path: '/houseData/shebei/:id', name: 'shebei',component: Shebei,
meta: { title: '设施设备', menuPath:true, hideInBread:false,},
children:[
]
},
]
},
{ // 业委会列表
path: '/houseData/indCous', name: 'indCous',component: IndCous,
meta: { title: '业委会信息列表', menuPath:true, hideInBread:false,},
children:[
{
path: '/houseData/indCou/:id', name: 'indCouView',component: PropertyInfoView,
meta: { title: '业委会详细情况', menuPath:false, hideInBread:false,},
},
{
path: '/houseData/members', name: 'members',component: Members,
meta: { title: '业委会成员列表', hideInBread:false, },
children:[
{
path: '/houseData/member/:id', name: 'memberView',component: PropertyInfoView,
meta: { title: '业委会成员详情', hideInBread:false,}
}
]
},
]
},
{ // 物业列表
path: '/houseData/propertyInfo', name: 'propertyInfo',component: PropertyInfo,
meta: { title: '物业企业列表', menuPath:true, hideInBread:false,},
children:[
{
path: '/houseData/propertyInfo/:id', name: 'propertyInfoView',component: PropertyInfoView,
meta: { title: '物业服务企业详细情况', hideInBread:false, }
}
]
},
{ // 小区管理处
path: '/houseData/cmDivision', name: 'cmDivision',component: CmDivision,
meta: { title: '小区管理处列表', menuPath:true,hideInBread:false, },
children:[
{
path: '/houseData/cmDivision/:id', name: 'cmDivisionView',component: PropertyInfoView,
meta: { title: '小区管理处详细情况', menuPath:true, hideInBread:false, }
}
]
},
{ // 小区经理
path: '/houseData/managers', name: 'managers',component: Managers,
meta: { title: '小区经理列表', menuPath:true, hideInBread:false,},
children:[
{
path: '/houseData/managers/:id', name: 'managerView',component: PropertyInfoView,
meta: { title: '小区经理详细情况', menuPath:false, hideInBread:false, }
},
]
},
]
},
{
path: '/noN', name: 'feiju',component:ContentWrapper,redirect:{name:'home'},
meta: { title: '非居信息', menuPath: true, menuIcon: 'profile', hideInBread: true },
children: [
{ // 非居列表
path: '/houseData/nonResidents', name: 'nonResidents',component: NonResidents,
meta: { title: '非居项目信息列表', menuPath:true, hideInBread:false,},
children:[
{
path: '/houseData/nonResident/:id', name: 'nonResidentsView',component: PropertyInfoView,
meta: { title: '非居详细情况', menuPath:false, hideInBread:false, }
},
]
},
]
},
{ // 拓展信息管理
path: '/tbls', name: 'tbls',component: Tbls,
meta: { title: '拓展信息管理', menuPath:true, menuIcon: 'setting',hideInBread:false,},
},
]
const appRoutes = [
{
path: '/', name: 'Layout', redirect:{name:'home'},
// toChange :需定义两个layout文件,分别用于子项目独立运行使用,和嵌入portal中使用
component: Layout,
// endChange
children: [
{ path: '/home', name: 'home', component: HomePage,
meta: { title: '首页', },
},
{ path: '/person', name: 'person',component: PersonCenter,
meta: { title: '个人中心' },
},
{ path: '/noauth', name: 'noauth', component: TipsNoAuth,
meta: { title: '无权访问', },
},
...menus
],
},
{
path: '/login',
name: 'login',
meta: { title: '登录'},
component: LoginPage,
},
{ path: '/networkerr', name: 'networkErr', component: TipsNetworkErr,
meta: { title: '网络异常', },
},
{ path: '/demo', name: 'demo', component: Demo,
meta: { title: 'demo', },
},
]
export const routes = [
...appRoutes,
]
import axios from 'axios'
import qs from 'qs'
import api from './api'
import Store from '@/store'
import Cookie from '@/util/local-cookie'
import router from '@/router'
import Common from '@/util/common'
import { Modal } from 'ant-design-vue'
// 配置请求的根域名和超时时间
const Axios = axios.create({
baseURL: api.BASE_URL,
timeout: 15000,
})
const CancelToken = axios.CancelToken
let cancelRequest = null
let currentRouterName ='',currentApi='',currentMethod=''
// 处理请求状态码
const reponseCodeHandler = (res) => {
const code = res.data && res.data.code
if ('string' == typeof code) {
if (code == '200') {
} else if (code == '911') {
// const params = {
// grant_type: 'refresh_token',
// client_id: 'house',
// client_secret: 'house',
// refreshToken: Cookie.get('refresh_token'),
// }
// request({
// method: 'POST',
// url: api.REFRESH_TOKEN_POST,
// params,
// contentType: 'application/x-www-form-urlencoded;charset=UTF-8',
// }).then(res => {
// if (res.code === '200') {
// Common.setToken(res.data.content.access_token, res.data.content.refresh_token)
// router.go(0)
// } else if (res.code === '912') {
// Common.handleLogOut()
// } else {
// Common.handleLogOut()
// }
// })
router.push({name:'login'})
} else if (code == '900') {
router.push({ name: 'noauth' })
} else if (code == '429') {//同一对外IP,2s内请求超过100次
router.push({ name: 'upperLimitErr' })
}else if (code == '710' || code == '720') {
if(Common.oneOf(currentMethod.toLocaleLowerCase(),['post','put','delete'])){
Modal.error({
title: '提交错误',
content: !res.data.msg?'':res.data.msg,
okText: '确认',
cancelText: '取消',
})
}
}else if (code == '912') {
//在refresh token 里的返回里已做处理,这里不做额外提示
}else if (code == '500') {
router.push({ name: 'networkErr' })
}
else{
if(Common.oneOf(currentMethod.toLocaleLowerCase(),['post','put','delete'])){
Modal.error({
title: '提交错误',
content: '系统异常',
okText: '确认',
cancelText: '取消',
})
}
}
}
}
const showErrPage= (api,routername,code)=>{
if(Common.oneOf(routername,['login','register','bindPhone'])){
if(!Common.oneOf(api,['/service-release/release/public/news'])){
router.push({
name: 'outerNetworkerr'
})
}
}else if(!!routername && routername.length>0){
router.push({
name: 'innerNetworkerr'
})
}else{
router.push({
name: 'outerNetworkerr'
})
}
}
// 根据报错的状态码进行错误处理
const errorHandler = (err) => {
const errStatus = (err.response && err.response.status) || (err.data && err.data.errcode)
if (errStatus) {
switch (errStatus) {
case 404: // 网络请求不存在,跳转统一报错页面
showErrPage(currentApi,currentRouterName)
break
/** 强说home页的请求报500就跳过去了,无法进行下一步操作,故此先注释掉 */
// case 500:
// const code = err.response.data && err.response.data.code
// showErrPage(currentApi,currentRouterName)
// break
default: // 其他错误,统一到网络异常页面
showErrPage(currentApi,currentRouterName)
break
}
/**超时了就跳转了且无法进行下一步操作,故此先注释掉 */
// } else if (err.toString().indexOf('timeout') != -1) { // 统一到网络异常页面
// showErrPage(currentApi,currentRouterName)
} else if (err.toString().indexOf('Network Error') != -1) { // 统一到网络异常页面
showErrPage(currentApi,currentRouterName)
}
}
Axios.interceptors.request.use(config => {
const token = Cookie.get('token') || Store.state.token
if (token) {
config.headers.Authorization = token
}
return config
}, error => {
return Promise.reject(error)
})
Axios.interceptors.response.use(response => {
reponseCodeHandler(response)
return response.data
}, error => {
errorHandler(error)
return error.response
})
/**
* 请求
* @param {String} method [请求方法]
* @param {String} url [请求地址]
* @param {Object} params [请求参数]
* @param {String} contentType [请求头,默认为'application/json;charset=UTF-8']
* @param {Boolean} hideLoading [隐藏请求时的loading图,默认为false]
*/
const request = ({ method, url, params, contentType = 'application/json;charset=UTF-8', hideLoading = false, routername }) => {
if (!url || typeof(url) != 'string') {
throw new Error('接口URL不正确')
}
// 存储当前调用接口所在的路由和API地址
currentApi = url
currentRouterName = !routername?'':routername
currentMethod = method
// transformResponse()执行完再执行then()。transformResponse函数用于提前处理返回的数据。返回的result对象比transformResponse函数的data对象包含的数据多。
// if (method == 'get') {
// let timestamp = Date.now()
// url = (url.indexOf('?') != -1) ? (url + '&timestamp=' + timestamp) : (url + '?timestamp=' + timestamp)
// url = encodeURI(url) //针对IE下地址传值带中文,对其转义
// }
if (!params || typeof(params) == 'string' || typeof(params) == 'number') {
params = {}
}
let config = {
method,
url,
headers: {
'X-Requested-With': 'XMLHttpRequest',
'Content-Type': contentType,
},
cancelToken: new CancelToken((c) => {
cancelRequest = c
}),
}
if (method === 'GET') {
config = Object.assign(config, { params })
} else {
if (contentType.toLowerCase().indexOf('x-www-form-urlencoded') >= 0) {
config = Object.assign(config, { data: qs.stringify(params) })
} else {
config = Object.assign(config, { data: params })
}
}
if (!hideLoading) {
Store.commit('SET_LOADING', true)
}
return new Promise((resolve, reject) => {
Axios(config)
.then(res => {
resolve(res)
Store.commit('SET_LOADING', false)
}).catch(err => {
reject(err)
Store.commit('SET_LOADING', false)
})
})
}
export default {
/**
* 取消请求
* @param {String} txt [取消请求时需要显示在控制台的提示信息]
*/
cancel(txt = '取消请求') {
Store.commit('SET_LOADING', false)
if (typeof(cancelRequest) === 'function') {
return cancelRequest(txt)
}
},
get(args) {
return request({ method: 'GET', ...args })
},
post(args) {
return request({ method: 'POST', ...args })
},
put(args) {
return request({ method: 'PUT', ...args })
},
delete(args) {
return request({ method: 'DELETE', ...args })
},
all(...ajaxs) {
return Promise.all(ajaxs)
},
}
let BASE_URL = '', MOCK_URL=''
/**
* todo
* 1、此处配置当前项目在不同环境下的API请求前缀
* 2、前端服务间、后端服务间需做跨域处理
*/
switch (process.env.NODE_ENV) {
case 'devol': // 本地线上部署环境下
// BASE_URL = 'http://search.omniview.pro/api'
BASE_URL = 'http://211.136.105.193/apiv2'
break
case 'sit': // sit环境下
// BASE_URL = 'http://search.omniview.pro/api'
BASE_URL = 'http://211.136.105.193/apiv2'
break
case 'uat': // uat环境下
// BASE_URL = 'http://search.omniview.pro/api'
BASE_URL = 'http://211.136.105.193/apiv2'
break
case 'production': // 生产环境下
// BASE_URL = 'http://search.omniview.pro/api'
BASE_URL = 'http://211.136.105.193/apiv2' //浦东
// BASE_URL = 'http://31.0.161.39/apiv2' //徐汇
// BASE_URL = 'http://hm.omniview.pro/api'
break
default: // 默认环境下(开发环境)
// BASE_URL = 'http://211.136.105.193/apiv2'
// BASE_URL = 'http://31.0.161.39/apiv2'
BASE_URL = 'http://hm.omniview.pro/api'
MOCK_URL = 'https://yapi.omniview.pro/mock/278'
break
};
/**
* 此处配置本项目涉及到的所有
* 要求
* 1、前端服务间、后端服务间需做跨域处理
* 2、所有的API都需要注释说明,标记业务动作是什么
* 3、在API的名称定义上,需体现本接口的请求方式。如USERINFO_POST、USERINFO_GET
* 4、所有的API按功能模块要求,分段并注释说明
*/
export default {
/**
* 各类请求的BASE_URL
*/
BASE_URL,
GET_USER_INFO: '/service-user/login/user/info', // 获取用户信息
PUT_PERSONAL_RESET_PWD:'service-user/pwd', // 修改密码
GET_KEYS:'service-customkey/show/{table}/keys', // 某表下所有可显示的字段
POST_KEYS:'service-customkey/show/keys',// 保存已选字段
GET_CAN_TBLS:'service-customkey/tbls',// 可进行数据拓展的表
GET_CONFIG:'config',//判断自定义字段功能是否开启
GET_PROJECT:'/service-search/public/projectConfig',//查看项目信息
POST_PROJECT:'/service-search/projectConfig',//配置项目信息
UPLOAD_TEMP:'/service-search/img/temp',//项目图片上传
GET_TBLS_LIST:'service-customkey/tbls', // 可进行数据拓展的表
POST_TBLS_KEYS:'service-customkey/key', // 新增自定义字段
PUT_TBLS_KEYS:'service-customkey/key/{id}', // 修改自定义字段
DELETE_TBLS_KEYS:'service-customkey/key/{id}', // 删除自定义字段
GET_TBLS_KEYS:'service-customkey/keys', // 某表下有哪些自定义字段
GET_EXT:'/service-search/entity/{id}/ext', // 查看某数据的自定义信息
POST_EXT:'/service-search/entity/ext', // 保存某数据的自定义信息
GET_SAFE_EXT:'/service-search/{table}/entity/{id}/ext', // 维护自定义信息的借口
POST_EXT_BATCH:'/service-search/entity/ext/batch', // 批量操作
GET_EXPORT:'/service-search/export/{table}',//数据导出
GET_STREET_LIST:'/service-search/streets', //街道下拉列表
// 小区列表
GET_BASIC_LIST: '/service-search/communities',
// 小区详情
GET_BASIC_ID: '/service-search/community/{id}',
// 物业服务企业列表
GET_PROPCOMPANIES_LIST:'/service-search/propCompanies',
// 物业服务企业详情
GET_PROPCOMPANY_ID: '/service-search/propCompany/{id}',
// 小区管理处列表
GET_CSS_LIST: '/service-search/css',
// 小区管理处详情
GET_CS_ID: '/service-search/css/{id}',
// 小区经理列表
GET_MANAGERS_LIST: '/service-search/managers',
// 小区经理详情
GET_MANAGER_ID: '/service-search/manager/{id}',
// 业委会列表
GET_INDCOUS_LIST: '/service-search/indCous',
// 业委会详情
GET_INDCOU_ID:'/service-search/indCou/{id}',
// 非居列表
GET_NONRESIDENTS_LIST: '/service-search/nonResidents',
// 非居详情
GET_NONRESIDENT_ID: '/service-search/nonResident/{id}',
// 小区楼栋列表
GET_BUILDINGS_LIST: '/service-search/community/buildings',
// 小去楼栋详情
GET_BUILDING_ID:'/service-search/community/building/{id}',
// 业委会成员列表
GET_MEMBERS_LIST: '/service-search/indcou/members',
// 业委会成员详情
GET_MEMBER_ID: '/service-search/indcou/member/{id}',
// 设施设备列表
GET_FACEQUS_LIST: '/service-search/community/facEqus',
// 房办列表
GET_HOS_LIST: '/service-search/hos',
// 首页统计列表
GET_RESOURE_LIST: '/service-search/resource/count',
// 首页统计列表
POST_LOGIN: '/service-user/public/login',
/**
* SSO
*/
GET_TOKEN: BASE_URL+'/uaa/oauth/token', // 获取token
/**
* XXX审查
*/
SHENGCHA_POST:'', // 提交需要XX审查的数据信息
}
export default {
}
import Vue from 'vue'
import Vuex from 'vuex'
import state from './state'
import actions from './actions'
import mutations from './mutations'
Vue.use(Vuex)
const isDev = process.env.NODE_ENV === 'development'
export default new Vuex.Store({
strict: isDev,
state,
actions,
mutations,
})
export default {
SET_LOADING(state, data) {
state.showLoading = data
},
SET_MENU(state, {authMenuAll, authCodeList, isAllPerm}) {
state.menuList = JSON.parse(JSON.stringify(authMenuAll)) // 保存菜单
state.permissionCodeList = authCodeList // 保存权限code数组
state.hasAllRight = isAllPerm // 保存是否拥有全部权限
},
SET_SEARCHPARAMS(state, searchParams) {
state.listSearchParams[searchParams.routeName] = JSON.parse(JSON.stringify(searchParams))
},
SET_CHECKEDLIST(state, list) { // 列表自定义字段的选中列表
state.checkedList = list
},
SET_DEFAULTMENU_STATUS(state, data) { // 菜单的默认展开、选中状态
state.defaultMenuStatus = data
},
SET_TABNAME(state, data) {
state.tabName = data
},
SET_USERNAME(state, name) {
state.userName = name
},
SET_USERINFO(state, userInfo) {
state.userInfos = JSON.parse(JSON.stringify(userInfo))
},
SET_CLEAR(state) {
state.token = null
state.showLoading = false
state.menuList = []
state.permissionCodeList = []
state.hasAllRight = false
state.userName = ''
state.userInfos = null
},
SET_TABNAME(state, tabName) {
state.tabName = tabName
},
}
export default {
token: null,
showLoading: false,
menuList: [],
permissionCodeList: [],
hasAllRight: false,
userName: '',
userInfos: null,
defaultMenuStatus: {
defaultSelectedKeys: [],
defaultOpenKeys: [],
},
tabName: '',
listSearchParams: {},
checkedList:[],
tabName:'',
}
<template>
<div style="text-align:center;padding-top:1px">
<div class="errTips">
<img class="img" src="@/assets/images/noauth.png" alt="noauth">
<p class="tips">无权访问</p>
<p class="subtips">请联系管理员处理</p>
</div>
<div>
<a-button class="moreOperations" type="primary" @click="toHome">返回工作台</a-button>
<a-button class="moreOperations" type="primary" @click="toLogin">重新登录</a-button>
</div>
</div>
</template>
<script>
export default {
name: 'NoAuth',
created() {
},
methods: {
toHome(){
this.$router.push({name:'home'})
},
toLogin(){
this.$com.handleLogOut()
}
}
}
</script>
<style scoped>
.errTips .tips {margin-bottom: 0 !important}
</style>
<style>
.errTips { width: 400px; margin: 150px auto 0 auto; text-align: center; line-height: 30px;}
.errTips .img { margin:0 auto 30px auto; width: 140px; }
.errTips .tips { font-size: 20px; line-height: 30px}
.errTips .subtips {color:#999}
.moreOperations { margin:0 auto; width:100px; }
</style>
<template>
<div style="text-align:center;padding-top:1px">
<div class="errTips">
<img class="img" src="@/assets/images/noauth.png" alt="noauth">
<p class="tips">404</p>
<p class="subtips">页面不存在</p>
</div>
<div>
<a-button class="moreOperations" type="primary" @click="toHome">返回工作台</a-button>
<a-button class="moreOperations" type="primary" @click="toLogin">重新登录</a-button>
</div>
</div>
</template>
<script>
export default {
name: 'ErrorPage',
created() {
},
methods: {
toHome(){
this.$router.push({name:'home'})
},
toLogin(){
this.$com.handleLogOut()
}
}
}
</script>
<style scoped>
.errTips .tips {margin-bottom: 0 !important}
</style>
<style>
.errTips { width: 400px; margin: 150px auto 0 auto; text-align: center; line-height: 30px;}
.errTips .img { margin:0 auto 30px auto; width: 140px; }
.errTips .tips { font-size: 20px; line-height: 30px}
.errTips .subtips {color:#999}
.moreOperations { margin:0 auto; width:100px; }
</style>
<template>
<div style="text-align:center;padding-top:1px">
<div class="errTips">
<img class="img" src="@/assets/images/networkerr.png" alt="networkerr">
<p class="tips">系统异常</p>
<p class="subtips">程序员正在处理中</p>
</div>
<div>
<a-button class="moreOperations" type="primary" @click="toLogin">重新登录</a-button>
</div>
</div>
</template>
<script>
export default {
name: 'NetworkError',
created() {
},
methods: {
toHome(){
this.$router.push({name:'home'})
},
toLogin(){
this.$com.handleLogOut()
}
}
}
</script>
<style scoped>
.errTips .tips {margin-bottom: 0 !important}
</style>
<style>
.errTips { width: 400px; margin: 150px auto 0 auto; text-align: center; line-height: 30px;}
.errTips .img { margin:0 auto 30px auto; width: 140px; }
.errTips .tips { font-size: 20px; line-height: 30px}
.errTips .subtips {color:#999}
.moreOperations { margin:0 auto; width:100px; }
</style>
<template>
<div style="text-align:center;padding-top:1px">
<div class="errTips">
<img class="img" src="@/assets/images/networkerr.png" alt="networkerr">
<p class="tips">网络错误</p>
<p class="subtips">程序员正在处理中</p>
</div>
<div>
<a-button class="moreOperations" type="primary" @click="toHome">返回工作台</a-button>
<a-button class="moreOperations" type="primary" @click="toLogin">重新登录</a-button>
</div>
</div>
</template>
<script>
export default {
name: 'NetworkError',
created() {
},
methods: {
toHome(){
this.$router.push({name:'home'})
},
toLogin(){
this.$com.handleLogOut()
}
}
}
</script>
<style scoped>
.errTips .tips {margin-bottom: 0 !important}
</style>
<style>
.errTips { width: 400px; margin: 150px auto 0 auto; text-align: center; line-height: 30px;}
.errTips .img { margin:0 auto 30px auto; width: 140px; }
.errTips .tips { font-size: 20px; line-height: 30px}
.errTips .subtips {color:#999}
.moreOperations { margin:0 auto; width:100px; }
</style>
<template>
<div style="text-align:center;padding-top:1px">
<div class="errTips">
<img class="img" src="@/assets/images/noauth.png" alt="noauth">
<p class="tips">已通过新窗口打开</p>
<p class="tips">{{$route.params.sysname}}</p>
<p class="subtips">请选择一下提示或操作</p>
</div>
<a-button class="moreOperations" type="primary" @click="toHome">返回工作台</a-button>
</div>
</template>
<script>
export default {
name: 'TipsOutsite',
created() {
},
methods: {
toHome(){
this.$router.push({name:'home'})
}
}
}
</script>
<style scoped>
.errTips .tips {margin-bottom: 0 !important}
</style>
<style>
.errTips { width: 400px; margin: 150px auto 0 auto; text-align: center; line-height: 30px;}
.errTips .img { margin:0 auto 30px auto; width: 140px; }
.errTips .tips { font-size: 20px; line-height: 30px}
.errTips .subtips {color:#999}
.moreOperations { margin:0 auto; width:100px; }
</style>
<template>
<div style="text-align:center;padding-top:1px">
<div class="errTips">
<img class="img" src="@/assets/images/upperlimit.png" alt="upperlimit">
<p class="tips">您访问过于太频繁了</p>
<p class="subtips"><span class="waitText">{{this.waitText}}</span>请稍后再试</p>
</div>
<div>
<a-button :disabled='!allowUse' class="moreOperations" type="primary" @click="toHome">返回工作台</a-button>
<a-button :disabled='!allowUse' class="moreOperations" type="primary" @click="toLogin">重新登录</a-button>
</div>
</div>
</template>
<script>
export default {
name: 'UpperLimitErr',
data() {
return {
waitSeconds:30,
allowUse:false,
allowUseTimer:null,
waitText:''
}
},
created() {
const interval = () => {
this.allowUse = false
this.allowUseTimer = setInterval(() => {
if (this.waitSeconds <= 0) {
this.clearTimer()
return
}
this.waitText = (this.waitSeconds -= 1) + 's'
}, 1000)
}
interval()
},
methods: {
clearTimer() {
clearInterval(this.allowUseTimer)
this.waitText = ''
this.allowUse = true
},
toHome(){
this.$router.push({name:'home'})
},
toLogin(){
this.$com.handleLogOut()
}
}
}
</script>
<style scoped>
.errTips .tips {margin-bottom: 0 !important}
</style>
<style>
.errTips { width: 400px; margin: 150px auto 0 auto; text-align: center; line-height: 30px;}
.errTips .img { margin:0 auto 30px auto; width: 140px; }
.errTips .tips { font-size: 20px; line-height: 30px}
.errTips .subtips {color:#999}
.moreOperations { margin:0 auto; width:100px; }
</style>
import Router from '@/router'
import Store from '@/store'
import { List } from 'ant-design-vue'
import Vue from 'vue'
/** 公共方法 */
export default {
/**
* 在深层数据结构中取值(为了替代类似 res && res.data && res.data.content这种写法)
* @param {Object} obj [必填-需要取值的目标对象(例:res)]
* @param {String} path [必填-数据结构路径(例:'data.content')]
* @param {Any} defaultValue [可选-如果取不到值则默认返回该值]
*/
confirm(obj, path, defaultValue = null) {
if (!obj || typeof(obj) != 'object' || !path || typeof(path) != 'string') return
const reducer = (accumulator, currentValue) =>
(accumulator && accumulator[currentValue]) ?
accumulator[currentValue] :
defaultValue
path = path.split('.')
return path.reduce(reducer, obj)
},
/**
* 判断一维数组中是否存在某个值
* @param {String} value 需要校验的字符串
* @param {Array} validList 被查找的一维数组
* @return {Boolean} 是否存在的结果
*/
oneOf (value, validList) {
for (let i = 0; i < validList.length; i++) {
if (value === validList[i]) {
return true
}
}
return false
},
/**
* 密码校验(6位以上数字字母的组合)
* @param {String} txt [需校验的文本]
*/
checkPassword(num) {
if (!num && num != 0) return false
const filter = /^(?![0-9]+$)(?![a-zA-Z]+$)[0-9A-Za-z]{6,}$/
return filter.test(num)
},
/**
* ----- 柯里化版本 (为了不再重复输入obj这个参数) -----
* 在深层数据结构中取值(为了替代类似 res && res.data && res.data.content这种写法)
* @param {Object} obj [必填-需要取值的目标对象(例:res)]
*/
confirm_currying(obj) {
if (!obj || typeof(obj) != 'object') return
return (path, defaultValue = null) => {
if (!path || typeof(path) != 'string') return
const reducer = (accumulator, currentValue) =>
(accumulator && accumulator[currentValue]) ?
accumulator[currentValue] :
defaultValue
path = path.split('.')
return path.reduce(reducer, obj)
}
},
/**
* 转换为金钱格式(千分位且保留两位小数)
* @param {Number | String} num [需转换的数字或字符串]
*/
toMoney(num) {
if (!num) {
return 0.00
}
num = this.toFloat(num).toFixed(2)
const arr = num.toString().split('.')
let int = (arr[0] || 0).toString(),
result = ''
while (int.length > 3) {
result = ',' + int.slice(-3) + result
int = int.slice(0, int.length - 3)
}
if (int) {
result = int + result
}
return `${result}.${arr[1]}`
},
/**
* 手机号码校验
* @param {String} num [需校验的手机号码]
*/
checkPhone(num) {
if (!num) return false
const filter = /^1[3-9][0-9]{9}$/
return filter.test(num)
},
/**
* 固定电话号码校验
* @param {String} num [需校验的固话]
*/
checkTel(num) {
if (!num) return false
const filter = /^(?:0[1-9][0-9]{1,2}-)?[2-8][0-9]{6,7}$/
return filter.test(num)
},
/**
* 身份证号码校验
* @param {String} num [需校验的身份证号码]
*/
checkID(num) {
if (!num) return false
const filter = /(^\d{15}$)|(^\d{17}([0-9]|X)$)/
return filter.test(num)
},
/**
* 数字校验(整数或者小数)
* @param {String} num [需校验的数字]
*/
checkNumber(num) {
if (!num && num != 0) return false
const filter = /^[0-9]+\.{0,1}[0-9]{0,2}$/
return filter.test(num)
},
/**
* 文本校验(只能为中文、英文、数字组合,不允许其他特殊符号)
* @param {String} txt [需校验的文本]
*/
checkContent(txt) {
const filter = /^[\u4E00-\u9FA5A-Za-z0-9]+$/
return filter.test(txt)
},
/**
* 将当前列表搜索条件,以及分页存储vuex
* @param {String} routeName 要存储搜索条件的列表页面路由名称
* @param {Object} page 分页信息
* @param {Object} param 搜索参数
*/
storeSearchParams(routeName, page, param) {
if (!!routeName && !!page && !!param) {
let searchParams = {
'routeName': routeName,
'params': param,
'pagination': page
}
Store.commit('SET_SEARCHPARAMS', searchParams)
}
},
/**
* 将当前的列表自定义字段 checked 的列表存储
*
*/
storeCheckedList(list) {
if (!!list) {
// Store.commit('SET_CHECKEDLIST', [])
Store.commit('SET_CHECKEDLIST', list)
}
},
// 顿号隔开各个选项的正则校验
validateCode(rule, value, callback) {
if (!value || value == undefined || value.split(',').join('').length === 0) {
callback('选项不能为空!')
} else {
if (/^\、|(\、{2,})|\、$/.test(value)) {
callback('请输入正确的选项并用顿号 "、" 隔开!不能用顿号开头和结尾')
} else{
callback()
}
}
},
//多选,单选,下拉 为已定义的选项
validateDef(rule, value, callback) {
if (!value || value == undefined) {
callback()
} else {
if ( /^\、|(\、{2,})|\、$/.test(value)) {
callback('请输入正确的选项并用顿号 "、" 隔开!不能用顿号开头和结尾')
} else{
callback()
}
}
},
//保留两位小数正则
validateNum2(rule, value, callback) {
if (!value || value == undefined || value.split(',').join('').length === 0) {
callback()
} else {
if ( /^-?(([1-9]+)|([0-9]+\.[0-9]{1,2}))$/.test(value)) {
callback()
} else{
callback('保留两位小数')
}
}
},
// 保留一位小数正则
validateNum1(rule, value, callback) {
if (!value || value == undefined || value.split(',').join('').length === 0) {
callback()
} else {
if ( /^-?(([1-9]+)|([0-9]+\.[0-9]{1}))$/.test(value)) {
callback()
} else{
callback('保留一位小数')
}
}
},
// 纯数字正则
validateNum(rule, value, callback) {
if (!value || value == undefined) {
callback()
} else {
if ( /^-?[1-9]\d*$/.test(value)) {
callback()
} else{
callback('只能输入整数')
}
}
},
// 将各个table中选中列的展示项储存cookie
// tbl 为表名, selKeysList为选中的项 ,allKeysList 为全部选项
saveColumnsCookie(tbl,selKeysList,allKeysList){
let arr = []
selKeysList.forEach(col=>{
allKeysList.forEach(item=>{
if (col == item.key) {
arr.push(
{
'key':col,
'keyName':item.keyName,
}
)
}
})
})
Vue.prototype.$cookie.set(tbl,arr)
},
}
import cryptoJs from 'crypto-js'
import Md5 from 'js-md5'
const CBCKEY = Md5('AcquisitHouseInformation')
const CBCIV = CBCKEY.slice(0,8)
// DES加密 -- CBC模式
export const encryptDes = (message) => {
var keyHex = cryptoJs.enc.Utf8.parse(CBCKEY)
var ivHex = cryptoJs.enc.Utf8.parse(CBCIV)
var option = { iv: ivHex, mode: cryptoJs.mode.CBC, padding: cryptoJs.pad.Pkcs7 }
var encrypted = cryptoJs.DES.encrypt(message, keyHex, option)
return encrypted.ciphertext.toString()
}
// DES解密 -- CBC模式
export const decryptDes = (message) => {
var keyHex = cryptoJs.enc.Utf8.parse(CBCKEY)
var ivHex = cryptoJs.enc.Utf8.parse(CBCIV)
var decrypted = cryptoJs.DES.decrypt(
{
ciphertext: cryptoJs.enc.Hex.parse(message)
},
keyHex,
{
iv: ivHex,
mode: cryptoJs.mode.CBC,
padding: cryptoJs.pad.Pkcs7
}
)
return decrypted.toString(cryptoJs.enc.Utf8)
}
/* 用于判断electron状态下使用localstorage替换js-cookie */
import jscookie from 'js-cookie'
import localcookie from './local-cookie'
const isElectronApp = window.navigator.userAgent.indexOf('Electron') !== -1
export default isElectronApp ? localcookie : jscookie
export default {
get(key) {
if (!key) {
return null
}
return localStorage.getItem(key)
},
set(key, val) {
if (!key) {
return null
}
localStorage.setItem(key, val)
},
remove(key) {
if (!key) {
return null
}
localStorage.removeItem(key)
},
}
This diff is collapsed.
import Vue from 'vue'
import {PermissionFilter} from './permission-filter'
const PermissionControl = Vue.directive('permission', {
bind: function (el, binding, vnode) { // 对于没有权限的按钮在虚拟DOM阶段将其注释掉,不渲染出来
if (!PermissionFilter(binding.value, vnode)) {
const comment = document.createComment(' ')
Object.defineProperty(comment, 'setAttribute', {
value: () => undefined,
})
vnode.elm = comment
vnode.text = ' '
vnode.isComment = true
vnode.context = undefined
vnode.tag = undefined
vnode.data.directives = undefined
if (vnode.componentInstance) {
vnode.componentInstance.$el = comment
}
if (el.parentNode) {
el.parentNode.replaceChild(comment, el)
}
}
}
})
export default PermissionControl
import store from '@/store'
import api from '@/server/api'
export const PermissionFilter = (value, vnode) => {
value = value + ''
if (store.state.hasAllRight) { // 如果有全部权限则一律通过
return true
}
let isExist = false
// 获取用户按钮权限
// const btnPermissionsArr = vnode.context.$route.meta.btnPermission;
const btnPermissionsArr = store.state.permissionCodeList
if (!btnPermissionsArr || btnPermissionsArr.length<=0) {
return false
}
if (btnPermissionsArr.indexOf(value) >= 0) {
isExist = true
}
return isExist
}
// 批量操作组件
<template>
<div class="batchOperation">
批量操作:
<a-button type="primary" @click="$emit('choose')">已选</a-button>
<a-button type="primary" @click="$emit('chooseAll')">全部</a-button>
</div>
</template>
<script>
export default {
name: 'BatchOperation'
}
</script>
<style scoped>
.batchOperation{
margin: 10px;
overflow: hidden;
width:200px;
position:relative;
top:-70px;
float: left;
/* margin-bottom: 10px; */
}
</style>
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
<template>
<div class="errTips">
<img class="img" src="@/assets/images/noauth.png" alt="noauth">
<h1>无权访问</h1>
<h2>请联系管理员处理</h2>
</div>
</template>
<script>
export default {
name: 'NoAuth',
}
</script>
<style>
.errTips { width: 300px; height:300px; margin: 150px 0 0 40%; text-align: center; line-height: 30px;}
.errTips .img { margin-bottom: 30px }
</style>
<template>
<div style="text-align:center;padding-top:1px">
<div class="errTips">
<img class="img" src="@/assets/images/networkerr.png" alt="networkerr">
<p class="tips">网络错误</p>
<p class="subtips">程序员正在处理中</p>
</div>
<div>
<a-button class="moreOperations" type="primary" @click="toHome">返回工作台</a-button>
<a-button class="moreOperations" type="primary" @click="toLogin">重新登录</a-button>
</div>
</div>
</template>
<script>
export default {
name: 'NetworkError',
created() {
},
methods: {
toHome(){
this.$router.push({name:'home'})
},
toLogin(){
// this.$com.handleLogOut()
this.$router.push({name:'login'})
}
}
}
</script>
<style scoped>
.errTips .tips {margin-bottom: 0 !important}
</style>
<style>
.errTips { width: 400px; margin: 150px auto 0 auto; text-align: center; line-height: 30px;}
.errTips .img { margin:0 auto 30px auto; width: 140px; }
.errTips .tips { font-size: 20px; line-height: 30px}
.errTips .subtips {color:#999}
.moreOperations { margin:0 auto; width:100px; }
</style>
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
<template>
<div class="footer">
<p>主办单位:全国水产技术推广总站、中国水产学会&nbsp;&nbsp;&nbsp;&nbsp; 技术支持:博彦科技股份有限公司</p>
<p>COPYRIGHT&copy;-{{$com.getCurrentYear()}} ALL RIGHTS RESERVED</p>
</div>
</template>
<script>
export default {
}
</script>
<style scoped>
.footer { height: 50px; width: 100%; text-align: center; position: absolute; bottom: 0px; left: 0px; color: #cfd7f3; font-size: 14px;}
.footer p { margin: 0; }
.footer p:last-of-type { color: #829bdc; }
</style>
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment