first commit
14
.editorconfig
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
# http://editorconfig.org
|
||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
charset = utf-8
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 2
|
||||||
|
end_of_line = lf
|
||||||
|
insert_final_newline = true
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
|
||||||
|
[*.md]
|
||||||
|
insert_final_newline = false
|
||||||
|
trim_trailing_whitespace = false
|
5
.env.development
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
# just a flag
|
||||||
|
ENV = 'development'
|
||||||
|
|
||||||
|
# base api
|
||||||
|
VUE_APP_BASE_API = '/dev-api'
|
7
.env.production
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
# just a flag
|
||||||
|
ENV = 'production'
|
||||||
|
|
||||||
|
# base api
|
||||||
|
# 配置api
|
||||||
|
VUE_APP_BASE_API = 'http://127.0.0.1:8898/api'
|
||||||
|
|
8
.env.staging
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
NODE_ENV = production
|
||||||
|
|
||||||
|
# just a flag
|
||||||
|
ENV = 'staging'
|
||||||
|
|
||||||
|
# base api
|
||||||
|
VUE_APP_BASE_API = '/stage-api'
|
||||||
|
|
4
.eslintignore
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
build/*.js
|
||||||
|
src/assets
|
||||||
|
public
|
||||||
|
dist
|
198
.eslintrc.js
Normal file
@ -0,0 +1,198 @@
|
|||||||
|
module.exports = {
|
||||||
|
root: true,
|
||||||
|
parserOptions: {
|
||||||
|
parser: 'babel-eslint',
|
||||||
|
sourceType: 'module'
|
||||||
|
},
|
||||||
|
env: {
|
||||||
|
browser: true,
|
||||||
|
node: true,
|
||||||
|
es6: true,
|
||||||
|
},
|
||||||
|
extends: ['plugin:vue/recommended', 'eslint:recommended'],
|
||||||
|
|
||||||
|
// add your custom rules here
|
||||||
|
//it is base on https://github.com/vuejs/eslint-config-vue
|
||||||
|
rules: {
|
||||||
|
"vue/max-attributes-per-line": [2, {
|
||||||
|
"singleline": 10,
|
||||||
|
"multiline": {
|
||||||
|
"max": 1,
|
||||||
|
"allowFirstLine": false
|
||||||
|
}
|
||||||
|
}],
|
||||||
|
"vue/singleline-html-element-content-newline": "off",
|
||||||
|
"vue/multiline-html-element-content-newline":"off",
|
||||||
|
"vue/name-property-casing": ["error", "PascalCase"],
|
||||||
|
"vue/no-v-html": "off",
|
||||||
|
'accessor-pairs': 2,
|
||||||
|
'arrow-spacing': [2, {
|
||||||
|
'before': true,
|
||||||
|
'after': true
|
||||||
|
}],
|
||||||
|
'block-spacing': [2, 'always'],
|
||||||
|
'brace-style': [2, '1tbs', {
|
||||||
|
'allowSingleLine': true
|
||||||
|
}],
|
||||||
|
'camelcase': [0, {
|
||||||
|
'properties': 'always'
|
||||||
|
}],
|
||||||
|
'comma-dangle': [2, 'never'],
|
||||||
|
'comma-spacing': [2, {
|
||||||
|
'before': false,
|
||||||
|
'after': true
|
||||||
|
}],
|
||||||
|
'comma-style': [2, 'last'],
|
||||||
|
'constructor-super': 2,
|
||||||
|
'curly': [2, 'multi-line'],
|
||||||
|
'dot-location': [2, 'property'],
|
||||||
|
'eol-last': 2,
|
||||||
|
'eqeqeq': ["error", "always", {"null": "ignore"}],
|
||||||
|
'generator-star-spacing': [2, {
|
||||||
|
'before': true,
|
||||||
|
'after': true
|
||||||
|
}],
|
||||||
|
'handle-callback-err': [2, '^(err|error)$'],
|
||||||
|
'indent': [2, 2, {
|
||||||
|
'SwitchCase': 1
|
||||||
|
}],
|
||||||
|
'jsx-quotes': [2, 'prefer-single'],
|
||||||
|
'key-spacing': [2, {
|
||||||
|
'beforeColon': false,
|
||||||
|
'afterColon': true
|
||||||
|
}],
|
||||||
|
'keyword-spacing': [2, {
|
||||||
|
'before': true,
|
||||||
|
'after': true
|
||||||
|
}],
|
||||||
|
'new-cap': [2, {
|
||||||
|
'newIsCap': true,
|
||||||
|
'capIsNew': false
|
||||||
|
}],
|
||||||
|
'new-parens': 2,
|
||||||
|
'no-array-constructor': 2,
|
||||||
|
'no-caller': 2,
|
||||||
|
'no-console': 'off',
|
||||||
|
'no-class-assign': 2,
|
||||||
|
'no-cond-assign': 2,
|
||||||
|
'no-const-assign': 2,
|
||||||
|
'no-control-regex': 0,
|
||||||
|
'no-delete-var': 2,
|
||||||
|
'no-dupe-args': 2,
|
||||||
|
'no-dupe-class-members': 2,
|
||||||
|
'no-dupe-keys': 2,
|
||||||
|
'no-duplicate-case': 2,
|
||||||
|
'no-empty-character-class': 2,
|
||||||
|
'no-empty-pattern': 2,
|
||||||
|
'no-eval': 2,
|
||||||
|
'no-ex-assign': 2,
|
||||||
|
'no-extend-native': 2,
|
||||||
|
'no-extra-bind': 2,
|
||||||
|
'no-extra-boolean-cast': 2,
|
||||||
|
'no-extra-parens': [2, 'functions'],
|
||||||
|
'no-fallthrough': 2,
|
||||||
|
'no-floating-decimal': 2,
|
||||||
|
'no-func-assign': 2,
|
||||||
|
'no-implied-eval': 2,
|
||||||
|
'no-inner-declarations': [2, 'functions'],
|
||||||
|
'no-invalid-regexp': 2,
|
||||||
|
'no-irregular-whitespace': 2,
|
||||||
|
'no-iterator': 2,
|
||||||
|
'no-label-var': 2,
|
||||||
|
'no-labels': [2, {
|
||||||
|
'allowLoop': false,
|
||||||
|
'allowSwitch': false
|
||||||
|
}],
|
||||||
|
'no-lone-blocks': 2,
|
||||||
|
'no-mixed-spaces-and-tabs': 2,
|
||||||
|
'no-multi-spaces': 2,
|
||||||
|
'no-multi-str': 2,
|
||||||
|
'no-multiple-empty-lines': [2, {
|
||||||
|
'max': 1
|
||||||
|
}],
|
||||||
|
'no-native-reassign': 2,
|
||||||
|
'no-negated-in-lhs': 2,
|
||||||
|
'no-new-object': 2,
|
||||||
|
'no-new-require': 2,
|
||||||
|
'no-new-symbol': 2,
|
||||||
|
'no-new-wrappers': 2,
|
||||||
|
'no-obj-calls': 2,
|
||||||
|
'no-octal': 2,
|
||||||
|
'no-octal-escape': 2,
|
||||||
|
'no-path-concat': 2,
|
||||||
|
'no-proto': 2,
|
||||||
|
'no-redeclare': 2,
|
||||||
|
'no-regex-spaces': 2,
|
||||||
|
'no-return-assign': [2, 'except-parens'],
|
||||||
|
'no-self-assign': 2,
|
||||||
|
'no-self-compare': 2,
|
||||||
|
'no-sequences': 2,
|
||||||
|
'no-shadow-restricted-names': 2,
|
||||||
|
'no-spaced-func': 2,
|
||||||
|
'no-sparse-arrays': 2,
|
||||||
|
'no-this-before-super': 2,
|
||||||
|
'no-throw-literal': 2,
|
||||||
|
'no-trailing-spaces': 2,
|
||||||
|
'no-undef': 2,
|
||||||
|
'no-undef-init': 2,
|
||||||
|
'no-unexpected-multiline': 2,
|
||||||
|
'no-unmodified-loop-condition': 2,
|
||||||
|
'no-unneeded-ternary': [2, {
|
||||||
|
'defaultAssignment': false
|
||||||
|
}],
|
||||||
|
'no-unreachable': 2,
|
||||||
|
'no-unsafe-finally': 2,
|
||||||
|
'no-unused-vars': [2, {
|
||||||
|
'vars': 'all',
|
||||||
|
'args': 'none'
|
||||||
|
}],
|
||||||
|
'no-useless-call': 2,
|
||||||
|
'no-useless-computed-key': 2,
|
||||||
|
'no-useless-constructor': 2,
|
||||||
|
'no-useless-escape': 0,
|
||||||
|
'no-whitespace-before-property': 2,
|
||||||
|
'no-with': 2,
|
||||||
|
'one-var': [2, {
|
||||||
|
'initialized': 'never'
|
||||||
|
}],
|
||||||
|
'operator-linebreak': [2, 'after', {
|
||||||
|
'overrides': {
|
||||||
|
'?': 'before',
|
||||||
|
':': 'before'
|
||||||
|
}
|
||||||
|
}],
|
||||||
|
'padded-blocks': [2, 'never'],
|
||||||
|
'quotes': [2, 'single', {
|
||||||
|
'avoidEscape': true,
|
||||||
|
'allowTemplateLiterals': true
|
||||||
|
}],
|
||||||
|
'semi': [2, 'never'],
|
||||||
|
'semi-spacing': [2, {
|
||||||
|
'before': false,
|
||||||
|
'after': true
|
||||||
|
}],
|
||||||
|
'space-before-blocks': [2, 'always'],
|
||||||
|
'space-before-function-paren': [2, 'never'],
|
||||||
|
'space-in-parens': [2, 'never'],
|
||||||
|
'space-infix-ops': 2,
|
||||||
|
'space-unary-ops': [2, {
|
||||||
|
'words': true,
|
||||||
|
'nonwords': false
|
||||||
|
}],
|
||||||
|
'spaced-comment': [2, 'always', {
|
||||||
|
'markers': ['global', 'globals', 'eslint', 'eslint-disable', '*package', '!', ',']
|
||||||
|
}],
|
||||||
|
'template-curly-spacing': [2, 'never'],
|
||||||
|
'use-isnan': 2,
|
||||||
|
'valid-typeof': 2,
|
||||||
|
'wrap-iife': [2, 'any'],
|
||||||
|
'yield-star-spacing': [2, 'both'],
|
||||||
|
'yoda': [2, 'never'],
|
||||||
|
'prefer-const': 2,
|
||||||
|
'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0,
|
||||||
|
'object-curly-spacing': [2, 'always', {
|
||||||
|
objectsInObjects: false
|
||||||
|
}],
|
||||||
|
'array-bracket-spacing': [2, 'never']
|
||||||
|
}
|
||||||
|
}
|
16
.gitignore
vendored
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
.DS_Store
|
||||||
|
node_modules/
|
||||||
|
dist/
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
package-lock.json
|
||||||
|
tests/**/coverage/
|
||||||
|
|
||||||
|
# Editor directories and files
|
||||||
|
.idea
|
||||||
|
.vscode
|
||||||
|
*.suo
|
||||||
|
*.ntvs*
|
||||||
|
*.njsproj
|
||||||
|
*.sln
|
5
.travis.yml
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
language: node_js
|
||||||
|
node_js: 10
|
||||||
|
script: npm run test
|
||||||
|
notifications:
|
||||||
|
email: false
|
21
LICENSE
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2017-present PanJiaChen
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
14
babel.config.js
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
module.exports = {
|
||||||
|
presets: [
|
||||||
|
// https://github.com/vuejs/vue-cli/tree/master/packages/@vue/babel-preset-app
|
||||||
|
'@vue/cli-plugin-babel/preset'
|
||||||
|
],
|
||||||
|
'env': {
|
||||||
|
'development': {
|
||||||
|
// babel-plugin-dynamic-import-node plugin only does one thing by converting all import() to require().
|
||||||
|
// This plugin can significantly increase the speed of hot updates, when you have a large number of pages.
|
||||||
|
// https://panjiachen.github.io/vue-element-admin-site/guide/advanced/lazy-loading.html
|
||||||
|
'plugins': ['dynamic-import-node']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
35
build/index.js
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
const { run } = require('runjs')
|
||||||
|
const chalk = require('chalk')
|
||||||
|
const config = require('../vue.config.js')
|
||||||
|
const rawArgv = process.argv.slice(2)
|
||||||
|
const args = rawArgv.join(' ')
|
||||||
|
|
||||||
|
if (process.env.npm_config_preview || rawArgv.includes('--preview')) {
|
||||||
|
const report = rawArgv.includes('--report')
|
||||||
|
|
||||||
|
run(`vue-cli-service build ${args}`)
|
||||||
|
|
||||||
|
const port = 9527
|
||||||
|
const publicPath = config.publicPath
|
||||||
|
|
||||||
|
var connect = require('connect')
|
||||||
|
var serveStatic = require('serve-static')
|
||||||
|
const app = connect()
|
||||||
|
|
||||||
|
app.use(
|
||||||
|
publicPath,
|
||||||
|
serveStatic('./dist', {
|
||||||
|
index: ['index.html', '/']
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
app.listen(port, function () {
|
||||||
|
console.log(chalk.green(`> Preview at http://localhost:${port}${publicPath}`))
|
||||||
|
if (report) {
|
||||||
|
console.log(chalk.green(`> Report at http://localhost:${port}${publicPath}report.html`))
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
run(`vue-cli-service build ${args}`)
|
||||||
|
}
|
24
jest.config.js
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
module.exports = {
|
||||||
|
moduleFileExtensions: ['js', 'jsx', 'json', 'vue'],
|
||||||
|
transform: {
|
||||||
|
'^.+\\.vue$': 'vue-jest',
|
||||||
|
'.+\\.(css|styl|less|sass|scss|svg|png|jpg|ttf|woff|woff2)$':
|
||||||
|
'jest-transform-stub',
|
||||||
|
'^.+\\.jsx?$': 'babel-jest'
|
||||||
|
},
|
||||||
|
moduleNameMapper: {
|
||||||
|
'^@/(.*)$': '<rootDir>/src/$1'
|
||||||
|
},
|
||||||
|
snapshotSerializers: ['jest-serializer-vue'],
|
||||||
|
testMatch: [
|
||||||
|
'**/tests/unit/**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)'
|
||||||
|
],
|
||||||
|
collectCoverageFrom: ['src/utils/**/*.{js,vue}', '!src/utils/auth.js', '!src/utils/request.js', 'src/components/**/*.{js,vue}'],
|
||||||
|
coverageDirectory: '<rootDir>/tests/unit/coverage',
|
||||||
|
// 'collectCoverage': true,
|
||||||
|
'coverageReporters': [
|
||||||
|
'lcov',
|
||||||
|
'text-summary'
|
||||||
|
],
|
||||||
|
testURL: 'http://localhost/'
|
||||||
|
}
|
9
jsconfig.json
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"baseUrl": "./",
|
||||||
|
"paths": {
|
||||||
|
"@/*": ["src/*"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"exclude": ["node_modules", "dist"]
|
||||||
|
}
|
57
mock/index.js
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
const Mock = require('mockjs')
|
||||||
|
const { param2Obj } = require('./utils')
|
||||||
|
|
||||||
|
const user = require('./user')
|
||||||
|
const table = require('./table')
|
||||||
|
|
||||||
|
const mocks = [
|
||||||
|
...user,
|
||||||
|
...table
|
||||||
|
]
|
||||||
|
|
||||||
|
// for front mock
|
||||||
|
// please use it cautiously, it will redefine XMLHttpRequest,
|
||||||
|
// which will cause many of your third-party libraries to be invalidated(like progress event).
|
||||||
|
function mockXHR() {
|
||||||
|
// mock patch
|
||||||
|
// https://github.com/nuysoft/Mock/issues/300
|
||||||
|
Mock.XHR.prototype.proxy_send = Mock.XHR.prototype.send
|
||||||
|
Mock.XHR.prototype.send = function() {
|
||||||
|
if (this.custom.xhr) {
|
||||||
|
this.custom.xhr.withCredentials = this.withCredentials || false
|
||||||
|
|
||||||
|
if (this.responseType) {
|
||||||
|
this.custom.xhr.responseType = this.responseType
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.proxy_send(...arguments)
|
||||||
|
}
|
||||||
|
|
||||||
|
function XHR2ExpressReqWrap(respond) {
|
||||||
|
return function(options) {
|
||||||
|
let result = null
|
||||||
|
if (respond instanceof Function) {
|
||||||
|
const { body, type, url } = options
|
||||||
|
// https://expressjs.com/en/4x/api.html#req
|
||||||
|
result = respond({
|
||||||
|
method: type,
|
||||||
|
body: JSON.parse(body),
|
||||||
|
query: param2Obj(url)
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
result = respond
|
||||||
|
}
|
||||||
|
return Mock.mock(result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const i of mocks) {
|
||||||
|
Mock.mock(new RegExp(i.url), i.type || 'get', XHR2ExpressReqWrap(i.response))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
mocks,
|
||||||
|
mockXHR
|
||||||
|
}
|
||||||
|
|
81
mock/mock-server.js
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
const chokidar = require('chokidar')
|
||||||
|
const bodyParser = require('body-parser')
|
||||||
|
const chalk = require('chalk')
|
||||||
|
const path = require('path')
|
||||||
|
const Mock = require('mockjs')
|
||||||
|
|
||||||
|
const mockDir = path.join(process.cwd(), 'mock')
|
||||||
|
|
||||||
|
function registerRoutes(app) {
|
||||||
|
let mockLastIndex
|
||||||
|
const { mocks } = require('./index.js')
|
||||||
|
const mocksForServer = mocks.map(route => {
|
||||||
|
return responseFake(route.url, route.type, route.response)
|
||||||
|
})
|
||||||
|
for (const mock of mocksForServer) {
|
||||||
|
app[mock.type](mock.url, mock.response)
|
||||||
|
mockLastIndex = app._router.stack.length
|
||||||
|
}
|
||||||
|
const mockRoutesLength = Object.keys(mocksForServer).length
|
||||||
|
return {
|
||||||
|
mockRoutesLength: mockRoutesLength,
|
||||||
|
mockStartIndex: mockLastIndex - mockRoutesLength
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function unregisterRoutes() {
|
||||||
|
Object.keys(require.cache).forEach(i => {
|
||||||
|
if (i.includes(mockDir)) {
|
||||||
|
delete require.cache[require.resolve(i)]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// for mock server
|
||||||
|
const responseFake = (url, type, respond) => {
|
||||||
|
return {
|
||||||
|
url: new RegExp(`${process.env.VUE_APP_BASE_API}${url}`),
|
||||||
|
type: type || 'get',
|
||||||
|
response(req, res) {
|
||||||
|
console.log('request invoke:' + req.path)
|
||||||
|
res.json(Mock.mock(respond instanceof Function ? respond(req, res) : respond))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = app => {
|
||||||
|
// parse app.body
|
||||||
|
// https://expressjs.com/en/4x/api.html#req.body
|
||||||
|
app.use(bodyParser.json())
|
||||||
|
app.use(bodyParser.urlencoded({
|
||||||
|
extended: true
|
||||||
|
}))
|
||||||
|
|
||||||
|
const mockRoutes = registerRoutes(app)
|
||||||
|
var mockRoutesLength = mockRoutes.mockRoutesLength
|
||||||
|
var mockStartIndex = mockRoutes.mockStartIndex
|
||||||
|
|
||||||
|
// watch files, hot reload mock server
|
||||||
|
chokidar.watch(mockDir, {
|
||||||
|
ignored: /mock-server/,
|
||||||
|
ignoreInitial: true
|
||||||
|
}).on('all', (event, path) => {
|
||||||
|
if (event === 'change' || event === 'add') {
|
||||||
|
try {
|
||||||
|
// remove mock routes stack
|
||||||
|
app._router.stack.splice(mockStartIndex, mockRoutesLength)
|
||||||
|
|
||||||
|
// clear routes cache
|
||||||
|
unregisterRoutes()
|
||||||
|
|
||||||
|
const mockRoutes = registerRoutes(app)
|
||||||
|
mockRoutesLength = mockRoutes.mockRoutesLength
|
||||||
|
mockStartIndex = mockRoutes.mockStartIndex
|
||||||
|
|
||||||
|
console.log(chalk.magentaBright(`\n > Mock Server hot reload success! changed ${path}`))
|
||||||
|
} catch (error) {
|
||||||
|
console.log(chalk.redBright(error))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
29
mock/table.js
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
const Mock = require('mockjs')
|
||||||
|
|
||||||
|
const data = Mock.mock({
|
||||||
|
'items|30': [{
|
||||||
|
id: '@id',
|
||||||
|
title: '@sentence(10, 20)',
|
||||||
|
'status|1': ['published', 'draft', 'deleted'],
|
||||||
|
author: 'name',
|
||||||
|
display_time: '@datetime',
|
||||||
|
pageviews: '@integer(300, 5000)'
|
||||||
|
}]
|
||||||
|
})
|
||||||
|
|
||||||
|
module.exports = [
|
||||||
|
{
|
||||||
|
url: '/vue-admin-template/table/list',
|
||||||
|
type: 'get',
|
||||||
|
response: config => {
|
||||||
|
const items = data.items
|
||||||
|
return {
|
||||||
|
code: 20000,
|
||||||
|
data: {
|
||||||
|
total: items.length,
|
||||||
|
items: items
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
84
mock/user.js
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
|
||||||
|
const tokens = {
|
||||||
|
admin: {
|
||||||
|
token: 'admin-token'
|
||||||
|
},
|
||||||
|
editor: {
|
||||||
|
token: 'editor-token'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const users = {
|
||||||
|
'admin-token': {
|
||||||
|
roles: ['admin'],
|
||||||
|
introduction: 'I am a super administrator',
|
||||||
|
avatar: 'https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif',
|
||||||
|
name: 'Super Admin'
|
||||||
|
},
|
||||||
|
'editor-token': {
|
||||||
|
roles: ['editor'],
|
||||||
|
introduction: 'I am an editor',
|
||||||
|
avatar: 'https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif',
|
||||||
|
name: 'Normal Editor'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = [
|
||||||
|
// user login
|
||||||
|
{
|
||||||
|
url: '/vue-admin-template/user/login',
|
||||||
|
type: 'post',
|
||||||
|
response: config => {
|
||||||
|
const { username } = config.body
|
||||||
|
const token = tokens[username]
|
||||||
|
|
||||||
|
// mock error
|
||||||
|
if (!token) {
|
||||||
|
return {
|
||||||
|
code: 60204,
|
||||||
|
message: 'Account and password are incorrect.'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
code: 20000,
|
||||||
|
data: token
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// get user info
|
||||||
|
{
|
||||||
|
url: '/vue-admin-template/user/info\.*',
|
||||||
|
type: 'get',
|
||||||
|
response: config => {
|
||||||
|
const { token } = config.query
|
||||||
|
const info = users[token]
|
||||||
|
|
||||||
|
// mock error
|
||||||
|
if (!info) {
|
||||||
|
return {
|
||||||
|
code: 50008,
|
||||||
|
message: 'Login failed, unable to get user details.'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
code: 20000,
|
||||||
|
data: info
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// user logout
|
||||||
|
{
|
||||||
|
url: '/vue-admin-template/user/logout',
|
||||||
|
type: 'post',
|
||||||
|
response: _ => {
|
||||||
|
return {
|
||||||
|
code: 20000,
|
||||||
|
data: 'success'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
25
mock/utils.js
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
/**
|
||||||
|
* @param {string} url
|
||||||
|
* @returns {Object}
|
||||||
|
*/
|
||||||
|
function param2Obj(url) {
|
||||||
|
const search = decodeURIComponent(url.split('?')[1]).replace(/\+/g, ' ')
|
||||||
|
if (!search) {
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
const obj = {}
|
||||||
|
const searchArr = search.split('&')
|
||||||
|
searchArr.forEach(v => {
|
||||||
|
const index = v.indexOf('=')
|
||||||
|
if (index !== -1) {
|
||||||
|
const name = v.substring(0, index)
|
||||||
|
const val = v.substring(index + 1, v.length)
|
||||||
|
obj[name] = val
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return obj
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
param2Obj
|
||||||
|
}
|
70
package.json
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
{
|
||||||
|
"name": "vue-admin-template",
|
||||||
|
"version": "4.4.0",
|
||||||
|
"description": "A vue admin template with Element UI & axios & iconfont & permission control & lint",
|
||||||
|
"author": "Pan <panfree23@gmail.com>",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vue-cli-service serve",
|
||||||
|
"build:prod": "vue-cli-service build",
|
||||||
|
"build:stage": "vue-cli-service build --mode staging",
|
||||||
|
"preview": "node build/index.js --preview",
|
||||||
|
"svgo": "svgo -f src/icons/svg --config=src/icons/svgo.yml",
|
||||||
|
"lint": "eslint --ext .js,.vue src",
|
||||||
|
"test:unit": "jest --clearCache && vue-cli-service test:unit",
|
||||||
|
"test:ci": "npm run lint && npm run test:unit"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@amap/amap-jsapi-loader": "^1.0.1",
|
||||||
|
"axios": "0.18.1",
|
||||||
|
"core-js": "3.6.5",
|
||||||
|
"echarts": "^5.4.1",
|
||||||
|
"element-china-area-data": "^6.1.0",
|
||||||
|
"element-ui": "2.13.2",
|
||||||
|
"js-cookie": "2.2.0",
|
||||||
|
"js-sha1": "^0.6.0",
|
||||||
|
"moment": "^2.29.4",
|
||||||
|
"normalize.css": "7.0.0",
|
||||||
|
"nprogress": "0.2.0",
|
||||||
|
"path-to-regexp": "2.4.0",
|
||||||
|
"qs": "^6.11.0",
|
||||||
|
"swiper": "^6.8.4",
|
||||||
|
"vue": "2.6.10",
|
||||||
|
"vue-router": "3.0.6",
|
||||||
|
"vuex": "3.6.2",
|
||||||
|
"wangeditor": "^4.7.15"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@vue/cli-plugin-babel": "4.4.4",
|
||||||
|
"@vue/cli-plugin-eslint": "4.4.4",
|
||||||
|
"@vue/cli-plugin-unit-jest": "4.4.4",
|
||||||
|
"@vue/cli-service": "4.4.4",
|
||||||
|
"@vue/test-utils": "1.0.0-beta.29",
|
||||||
|
"autoprefixer": "9.5.1",
|
||||||
|
"babel-eslint": "10.1.0",
|
||||||
|
"babel-jest": "23.6.0",
|
||||||
|
"babel-plugin-dynamic-import-node": "2.3.3",
|
||||||
|
"chalk": "2.4.2",
|
||||||
|
"connect": "3.6.6",
|
||||||
|
"eslint": "6.7.2",
|
||||||
|
"eslint-plugin-vue": "6.2.2",
|
||||||
|
"html-webpack-plugin": "3.2.0",
|
||||||
|
"mockjs": "1.0.1-beta3",
|
||||||
|
"runjs": "4.3.2",
|
||||||
|
"sass": "1.26.8",
|
||||||
|
"sass-loader": "^7.3.1",
|
||||||
|
"script-ext-html-webpack-plugin": "2.1.3",
|
||||||
|
"serve-static": "1.13.2",
|
||||||
|
"svg-sprite-loader": "4.1.3",
|
||||||
|
"svgo": "1.2.2",
|
||||||
|
"vue-template-compiler": "2.6.10"
|
||||||
|
},
|
||||||
|
"browserslist": [
|
||||||
|
"> 1%",
|
||||||
|
"last 2 versions"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8.9",
|
||||||
|
"npm": ">= 3.0.0"
|
||||||
|
},
|
||||||
|
"license": "MIT"
|
||||||
|
}
|
8
postcss.config.js
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
// https://github.com/michael-ciniawsky/postcss-load-config
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
'plugins': {
|
||||||
|
// to edit target browsers: use "browserslist" field in package.json
|
||||||
|
'autoprefixer': {}
|
||||||
|
}
|
||||||
|
}
|
15020
public/address.json
Normal file
BIN
public/favicon.ico
Normal file
After Width: | Height: | Size: 66 KiB |
20
public/index.html
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
|
||||||
|
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
|
||||||
|
<title>基于Spring Boot的停车场管理系统-门户端</title>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<noscript>
|
||||||
|
<strong>We're sorry but <%= webpackConfig.name %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
|
||||||
|
</noscript>
|
||||||
|
<div id="app"></div>
|
||||||
|
<!-- built files will be auto injected -->
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
11
src/App.vue
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
<template>
|
||||||
|
<div id="app">
|
||||||
|
<router-view />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'App'
|
||||||
|
}
|
||||||
|
</script>
|
56
src/api/brand.js
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
// 车辆品牌接口
|
||||||
|
|
||||||
|
import request from '@/utils/request'
|
||||||
|
|
||||||
|
import qs from 'qs'
|
||||||
|
|
||||||
|
// 获取车辆品牌列表
|
||||||
|
export const getBrandList = (data) => {
|
||||||
|
return request({
|
||||||
|
url: '/AllBrandpage',
|
||||||
|
method: 'POST',
|
||||||
|
data: qs.stringify(data)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 新增车辆品牌
|
||||||
|
export const addBrand = (data) => {
|
||||||
|
return request({
|
||||||
|
url: '/brand/add',
|
||||||
|
method: 'POST',
|
||||||
|
data: qs.stringify(data)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除车辆品牌
|
||||||
|
export const deleteBrand = (id) => {
|
||||||
|
return request({
|
||||||
|
url: `/brand/delete?id=${id}`,
|
||||||
|
method: 'GET'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 修改车辆品牌状态
|
||||||
|
export const changeStatus = (id, status) => {
|
||||||
|
return request({
|
||||||
|
url: `/brand/changeStatus?id=${id}&status=${status}`,
|
||||||
|
method: 'GET'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取车辆品牌详情
|
||||||
|
export const getBrandDetail = (id) => {
|
||||||
|
return request({
|
||||||
|
url: `/brand/detail?id=${id}`,
|
||||||
|
method: 'GET'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 修改车辆品牌
|
||||||
|
export const editBrand = (data) => {
|
||||||
|
return request({
|
||||||
|
url: '/brand/edit',
|
||||||
|
method: 'POST',
|
||||||
|
data: qs.stringify(data)
|
||||||
|
})
|
||||||
|
}
|
34
src/api/car.js
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
// 车辆管理接口
|
||||||
|
|
||||||
|
import request from '@/utils/request'
|
||||||
|
import qs from 'qs'
|
||||||
|
|
||||||
|
//租借车位
|
||||||
|
export const carRent = (data) => {
|
||||||
|
return request({
|
||||||
|
url: '/carRent',
|
||||||
|
method: 'POST',
|
||||||
|
data: qs.stringify(data)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// 还车
|
||||||
|
export const returnCar = (data) => {
|
||||||
|
return request({
|
||||||
|
url: '/returnCar',
|
||||||
|
method: 'POST',
|
||||||
|
data: qs.stringify(data)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// 获取停车场的车辆列表
|
||||||
|
export const getCars = (id) => {
|
||||||
|
return request({
|
||||||
|
url: `/getPid?parking_id=${id}`,
|
||||||
|
method: 'GET'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// 通过停车场ID查询车位列表
|
||||||
|
export const getCarList = (id) => {
|
||||||
|
return request({
|
||||||
|
url: `/getCarsInParking?parking_id=${id}`,
|
||||||
|
})
|
||||||
|
}
|
22
src/api/comment.js
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
// 评论接口
|
||||||
|
|
||||||
|
// 修改格式
|
||||||
|
import qs from 'qs'
|
||||||
|
|
||||||
|
import request from '@/utils/request'
|
||||||
|
// 发布评论
|
||||||
|
export const push = (data) => {
|
||||||
|
return request({
|
||||||
|
url: '/comment',
|
||||||
|
method: 'POST',
|
||||||
|
data: qs.stringify(data)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// 获取个人信息接口
|
||||||
|
export function getInfo(data) {
|
||||||
|
return request({
|
||||||
|
url: '/FindUserById',
|
||||||
|
method: 'post',
|
||||||
|
data: qs.stringify(data)
|
||||||
|
})
|
||||||
|
}
|
34
src/api/common.js
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
// 公共接口
|
||||||
|
|
||||||
|
// 修改格式
|
||||||
|
import qs from 'qs'
|
||||||
|
|
||||||
|
import request from '@/utils/request'
|
||||||
|
|
||||||
|
// 导入axios
|
||||||
|
import axios from 'axios';
|
||||||
|
|
||||||
|
// 获取城市列表
|
||||||
|
// 模拟异步获取城市数据的函数
|
||||||
|
//
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export const getCity = (data) => {
|
||||||
|
return request({
|
||||||
|
url: '/cityPicker',
|
||||||
|
method: 'POST',
|
||||||
|
data: qs.stringify(data)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 封装的table组件请求数据
|
||||||
|
*/
|
||||||
|
export const tableData = (params) => {
|
||||||
|
return request({
|
||||||
|
url: params.url,
|
||||||
|
method: params.method,
|
||||||
|
data: qs.stringify(params.data)
|
||||||
|
})
|
||||||
|
}
|
84
src/api/forum.js
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
// 公共接口
|
||||||
|
|
||||||
|
// 修改格式
|
||||||
|
import qs from 'qs'
|
||||||
|
|
||||||
|
import request from '@/utils/request'
|
||||||
|
|
||||||
|
// 导入axios
|
||||||
|
import axios from 'axios';
|
||||||
|
export function getCategories() {
|
||||||
|
return request({
|
||||||
|
url: '/forum/categories',
|
||||||
|
method: 'get'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getPosts(params) {
|
||||||
|
return request({
|
||||||
|
url: '/forum/posts',
|
||||||
|
method: 'get',
|
||||||
|
params
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getPostDetail(id) {
|
||||||
|
return request({
|
||||||
|
url: `/forum/posts/${id}`,
|
||||||
|
method: 'get'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createPost(data) {
|
||||||
|
// 使用 URLSearchParams 构建查询参数
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
params.append('title', data.title);
|
||||||
|
params.append('content', data.content);
|
||||||
|
params.append('categoryId', data.categoryId);
|
||||||
|
params.append('userId', data.userId);
|
||||||
|
|
||||||
|
return request({
|
||||||
|
url: '/forum/post', // 后端接口路径
|
||||||
|
method: 'get', // 使用 GET 请求
|
||||||
|
params: params // 通过 params 传递查询参数
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// 获取帖子的所有回复
|
||||||
|
export function getReplies(postId) {
|
||||||
|
return request({
|
||||||
|
url: `/forum/posts/${postId}/replies`,
|
||||||
|
method: 'GET'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建新的回复
|
||||||
|
export function createReply(postId, replyData,userId) {
|
||||||
|
// 创建FormData对象
|
||||||
|
const formData = new FormData();
|
||||||
|
// 添加回复内容
|
||||||
|
formData.append('content', replyData);
|
||||||
|
formData.append('userId', userId);
|
||||||
|
formData.append('postId', postId);
|
||||||
|
return request({
|
||||||
|
url: `/forum/posts/${postId}/repliesed`,
|
||||||
|
method: 'POST',
|
||||||
|
data: formData,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 修改回复内容
|
||||||
|
export function updateReply(id, replyData) {
|
||||||
|
return request({
|
||||||
|
url: `/forum/replies/${id}`,
|
||||||
|
method: 'put',
|
||||||
|
data: replyData // 更新的回复数据
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除回复
|
||||||
|
export function deleteReply(id) {
|
||||||
|
return request({
|
||||||
|
url: `/forum/replies/${id}`,
|
||||||
|
method: 'delete'
|
||||||
|
});
|
||||||
|
}
|
20
src/api/index.js
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
// 所有接口统一暴露
|
||||||
|
|
||||||
|
import * as common from './common'
|
||||||
|
import * as parking from './parking'
|
||||||
|
import * as brand from './brand'
|
||||||
|
import * as car from './car'
|
||||||
|
import * as member from './member'
|
||||||
|
import * as comment from './comment'
|
||||||
|
import * as pact from './pact'
|
||||||
|
import * as userinfo from './userinfo'
|
||||||
|
export default {
|
||||||
|
common,
|
||||||
|
parking,
|
||||||
|
brand,
|
||||||
|
car,
|
||||||
|
member,
|
||||||
|
comment,
|
||||||
|
pact,
|
||||||
|
userinfo
|
||||||
|
}
|
23
src/api/member.js
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
// 会员接口
|
||||||
|
|
||||||
|
// 修改格式
|
||||||
|
import qs from 'qs'
|
||||||
|
|
||||||
|
import request from '@/utils/request'
|
||||||
|
|
||||||
|
|
||||||
|
// 删除会员
|
||||||
|
export const memberDelete = (id) => {
|
||||||
|
return request({
|
||||||
|
url: `/member/delete?id=${id}`,
|
||||||
|
method: 'GET'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取会员详情
|
||||||
|
export const getMemberDetail = (id) => {
|
||||||
|
return request({
|
||||||
|
url: `/member/detail?id=${id}`,
|
||||||
|
method: 'GET',
|
||||||
|
})
|
||||||
|
}
|
14
src/api/pact.js
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
// 合同接口
|
||||||
|
|
||||||
|
// 修改格式
|
||||||
|
import qs from 'qs'
|
||||||
|
|
||||||
|
import request from '@/utils/request'
|
||||||
|
// 查询合同
|
||||||
|
export const FindPactByPactId = (data) => {
|
||||||
|
return request({
|
||||||
|
url: '/FindPactByPactId',
|
||||||
|
method: 'POST',
|
||||||
|
data: qs.stringify(data)
|
||||||
|
})
|
||||||
|
}
|
66
src/api/parking.js
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
// 停车场接口
|
||||||
|
|
||||||
|
// 修改格式
|
||||||
|
import qs from 'qs'
|
||||||
|
|
||||||
|
import request from '@/utils/request'
|
||||||
|
|
||||||
|
// 新增停车场
|
||||||
|
export const parkingAdd = (data) => {
|
||||||
|
return request({
|
||||||
|
url: '/parking/add',
|
||||||
|
method: 'POST',
|
||||||
|
data: qs.stringify(data)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取停车场列表
|
||||||
|
export const getParkingList = (data) => {
|
||||||
|
return request({
|
||||||
|
url: '/parking/list',
|
||||||
|
method: 'POST',
|
||||||
|
data: qs.stringify(data)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除停车场
|
||||||
|
export const deleteParking = (id) => {
|
||||||
|
return request({
|
||||||
|
url: `/parking/delete?id=${id}`,
|
||||||
|
method: 'GET'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取停车场详情
|
||||||
|
export const getParkingDetail = (id) => {
|
||||||
|
return request({
|
||||||
|
url: `/parking/detail?id=${id}`,
|
||||||
|
method: 'GET',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 编辑停车场
|
||||||
|
export const editParking = (data) => {
|
||||||
|
return request({
|
||||||
|
url: '/parking/edit',
|
||||||
|
method: 'POST',
|
||||||
|
data: qs.stringify(data)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 修改禁启用状态
|
||||||
|
export const changeEnable = (id, enable) => {
|
||||||
|
return request({
|
||||||
|
url: `/parking/changeEnable?id=${id}&enable=${enable}`,
|
||||||
|
method: 'GET'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// 获取停车场
|
||||||
|
export const getParking = () => {
|
||||||
|
return request({
|
||||||
|
url: '/webParking',
|
||||||
|
method: 'POST',
|
||||||
|
})
|
||||||
|
}
|
51
src/api/user.js
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
import request from '@/utils/request'
|
||||||
|
|
||||||
|
// 用户注册接口
|
||||||
|
export function register(data) {
|
||||||
|
return request({
|
||||||
|
// url: '/vue-admin-template/user/login',
|
||||||
|
// url: '/admin/acl/index/login',
|
||||||
|
url: '/user_register',
|
||||||
|
method: 'post',
|
||||||
|
data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// 发送手机验证码
|
||||||
|
export function sendcode(data) {
|
||||||
|
return request({
|
||||||
|
// url: '/vue-admin-template/user/login',
|
||||||
|
// url: '/admin/acl/index/login',
|
||||||
|
url: '/sendCode',
|
||||||
|
method: 'post',
|
||||||
|
data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// 用户登录接口
|
||||||
|
export function login(data) {
|
||||||
|
return request({
|
||||||
|
// url: '/vue-admin-template/user/login',
|
||||||
|
// url: '/admin/acl/index/login',
|
||||||
|
url: '/login',
|
||||||
|
method: 'post',
|
||||||
|
data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// 获取个人信息接口
|
||||||
|
export function getInfo(data) {
|
||||||
|
return request({
|
||||||
|
url: '/FindUserById',
|
||||||
|
method: 'post',
|
||||||
|
data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// 用户退出接口
|
||||||
|
export function logout(token) {
|
||||||
|
const data = new URLSearchParams();
|
||||||
|
data.append('token', token);
|
||||||
|
return request({
|
||||||
|
// url: '/admin/acl/index/logout',
|
||||||
|
url: '/logout/',
|
||||||
|
method: 'post',
|
||||||
|
data
|
||||||
|
})
|
||||||
|
}
|
23
src/api/userinfo.js
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
// 评论接口
|
||||||
|
|
||||||
|
// 修改格式
|
||||||
|
import qs from 'qs'
|
||||||
|
|
||||||
|
import request from '@/utils/request'
|
||||||
|
|
||||||
|
// 获取个人信息接口
|
||||||
|
export function getInfo(data) {
|
||||||
|
return request({
|
||||||
|
url: '/FindUserById',
|
||||||
|
method: 'post',
|
||||||
|
data: qs.stringify(data)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// 更新个人信息接口
|
||||||
|
export function RealUser(data) {
|
||||||
|
return request({
|
||||||
|
url: '/RealUser',
|
||||||
|
method: 'post',
|
||||||
|
data: qs.stringify(data)
|
||||||
|
})
|
||||||
|
}
|
BIN
src/assets/404_images/404.png
Normal file
After Width: | Height: | Size: 96 KiB |
BIN
src/assets/404_images/404_cloud.png
Normal file
After Width: | Height: | Size: 4.7 KiB |
BIN
src/assets/images/parking_location_img.png
Normal file
After Width: | Height: | Size: 3.0 KiB |
78
src/components/Breadcrumb/index.vue
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
<template>
|
||||||
|
<el-breadcrumb class="app-breadcrumb" separator="/">
|
||||||
|
<transition-group name="breadcrumb">
|
||||||
|
<el-breadcrumb-item v-for="(item,index) in levelList" :key="item.path">
|
||||||
|
<span v-if="item.redirect==='noRedirect'||index==levelList.length-1" class="no-redirect">{{ item.meta.title }}</span>
|
||||||
|
<a v-else @click.prevent="handleLink(item)">{{ item.meta.title }}</a>
|
||||||
|
</el-breadcrumb-item>
|
||||||
|
</transition-group>
|
||||||
|
</el-breadcrumb>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import pathToRegexp from 'path-to-regexp'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
levelList: null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
$route() {
|
||||||
|
this.getBreadcrumb()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.getBreadcrumb()
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
getBreadcrumb() {
|
||||||
|
// only show routes with meta.title
|
||||||
|
let matched = this.$route.matched.filter(item => item.meta && item.meta.title)
|
||||||
|
const first = matched[0]
|
||||||
|
|
||||||
|
if (!this.isDashboard(first)) {
|
||||||
|
matched = [{ path: '/dashboard', meta: { title: '控制台' }}].concat(matched)
|
||||||
|
}
|
||||||
|
|
||||||
|
this.levelList = matched.filter(item => item.meta && item.meta.title && item.meta.breadcrumb !== false)
|
||||||
|
},
|
||||||
|
isDashboard(route) {
|
||||||
|
const name = route && route.name
|
||||||
|
if (!name) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return name.trim().toLocaleLowerCase() === 'Dashboard'.toLocaleLowerCase()
|
||||||
|
},
|
||||||
|
pathCompile(path) {
|
||||||
|
// To solve this problem https://github.com/PanJiaChen/vue-element-admin/issues/561
|
||||||
|
const { params } = this.$route
|
||||||
|
var toPath = pathToRegexp.compile(path)
|
||||||
|
return toPath(params)
|
||||||
|
},
|
||||||
|
handleLink(item) {
|
||||||
|
const { redirect, path } = item
|
||||||
|
if (redirect) {
|
||||||
|
this.$router.push(redirect)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.$router.push(this.pathCompile(path))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.app-breadcrumb.el-breadcrumb {
|
||||||
|
display: inline-block;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 50px;
|
||||||
|
margin-left: 8px;
|
||||||
|
|
||||||
|
.no-redirect {
|
||||||
|
color: #97a8be;
|
||||||
|
cursor: text;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
172
src/components/Commons/cityArea.vue
Normal file
@ -0,0 +1,172 @@
|
|||||||
|
<template>
|
||||||
|
<el-cascader
|
||||||
|
v-model="regionValue"
|
||||||
|
:options="region_options"
|
||||||
|
:props="region_options_props"
|
||||||
|
:placeholder="initCascaderValue"
|
||||||
|
@change="changeCascader"
|
||||||
|
@clearCheckedNodes="clearCheckedNodes"
|
||||||
|
></el-cascader>
|
||||||
|
<!-- <div id="app">
|
||||||
|
<el-cascader
|
||||||
|
size="large"
|
||||||
|
:options="options"
|
||||||
|
v-model="selectedOptions"
|
||||||
|
@change="handleChange">
|
||||||
|
</el-cascader>
|
||||||
|
</div> -->
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
|
||||||
|
|
||||||
|
// import { regionData } from 'element-china-area-data';
|
||||||
|
// import { getCity } from "@/api/common";
|
||||||
|
|
||||||
|
// export default {
|
||||||
|
// data() {
|
||||||
|
// return {
|
||||||
|
// options: regionData,
|
||||||
|
// selectedOptions: [],
|
||||||
|
// address: [],
|
||||||
|
// addressData: {},
|
||||||
|
// initCascaderValue: '请选择省市区',
|
||||||
|
// };
|
||||||
|
// },
|
||||||
|
// methods: {
|
||||||
|
// handleChange(value) {
|
||||||
|
// console.log(value);
|
||||||
|
// },
|
||||||
|
// async lazyLoad(node, resolve) {
|
||||||
|
// const { level } = node;
|
||||||
|
// const requestData = {};
|
||||||
|
// let levelType = {
|
||||||
|
// 0: { type: "province" },
|
||||||
|
// 1: { type: "city", code: "province_code" },
|
||||||
|
// 2: { type: "area", code: "city_code" },
|
||||||
|
// };
|
||||||
|
// if (levelType[level].code) {
|
||||||
|
// requestData[levelType[level].code] = node.value;
|
||||||
|
// }
|
||||||
|
// requestData.type = levelType[level].type;
|
||||||
|
// let result = await getCity(requestData);
|
||||||
|
// if (result.code === 200) {
|
||||||
|
// const data = result.data;
|
||||||
|
// const type = levelType[level].type;
|
||||||
|
// data.forEach((item) => {
|
||||||
|
// item.value = item[type + '_code'];
|
||||||
|
// item.label = item[type + '_name'];
|
||||||
|
// item.leaf = level >= 2;
|
||||||
|
// });
|
||||||
|
// this.addressData[type] = data;
|
||||||
|
// resolve(data);
|
||||||
|
// }
|
||||||
|
// if (level !== 0) {
|
||||||
|
// this.getAddress(node);
|
||||||
|
// }
|
||||||
|
// },
|
||||||
|
// changeCascader(value) {
|
||||||
|
// this.$emit("update:region", value.join());
|
||||||
|
// const area_code = value[value.length - 1];
|
||||||
|
// const area_name = this.addressData.area.filter(item => item.value === area_code)[0].label;
|
||||||
|
// this.address[2] = area_name;
|
||||||
|
// this.$emit('setMapLocation', this.address.join(""));
|
||||||
|
// },
|
||||||
|
// getAddress(node) {
|
||||||
|
// const index = node.level - 1;
|
||||||
|
// this.address[index] = node.label;
|
||||||
|
// },
|
||||||
|
// clearCheckedNodes() {
|
||||||
|
// this.selectedOptions = [];
|
||||||
|
// },
|
||||||
|
// cascaderValue(value) {
|
||||||
|
// this.initCascaderValue = value;
|
||||||
|
// }
|
||||||
|
// },
|
||||||
|
// };
|
||||||
|
|
||||||
|
import { getCity } from "@/api/common";
|
||||||
|
export default {
|
||||||
|
name: "CityArea",
|
||||||
|
data() {
|
||||||
|
const _this = this
|
||||||
|
return {
|
||||||
|
initCascaderValue: '请选择省市区',
|
||||||
|
// 地址代码
|
||||||
|
address: [],
|
||||||
|
// 地址数据
|
||||||
|
addressData: {},
|
||||||
|
regionValue: "",
|
||||||
|
// 级联数据
|
||||||
|
region_options: [],
|
||||||
|
// 级联动态加载配置
|
||||||
|
region_options_props: {
|
||||||
|
lazy: true,
|
||||||
|
async lazyLoad(node, resolve) {
|
||||||
|
const { level } = node;
|
||||||
|
// 请求参数
|
||||||
|
const requestData = {};
|
||||||
|
let levelType = {
|
||||||
|
0: { type: "province" },
|
||||||
|
1: { type: "city", code: "province_code" },
|
||||||
|
2: { type: "area", code: "city_code" },
|
||||||
|
};
|
||||||
|
if (levelType[level].code) {
|
||||||
|
requestData[levelType[level].code] = node.value;
|
||||||
|
}
|
||||||
|
requestData.type = levelType[level].type;
|
||||||
|
let result = await getCity(requestData);
|
||||||
|
if (result.status === 200) {
|
||||||
|
const data = result.data;
|
||||||
|
console.log('Address data:', data);
|
||||||
|
// 类型
|
||||||
|
const type = levelType[level].type
|
||||||
|
// 对省市区进行展示处理
|
||||||
|
data.forEach((item) => {
|
||||||
|
item.value = item[type + '_code']
|
||||||
|
item.label = item[type + '_name']
|
||||||
|
item.leaf = level >= 2
|
||||||
|
});
|
||||||
|
// 存储省市区数据
|
||||||
|
_this.addressData[type] = data
|
||||||
|
resolve(data);
|
||||||
|
}
|
||||||
|
// 获取地址
|
||||||
|
if (level !== 0) {
|
||||||
|
_this.getAddress(node)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
props: ["region"],
|
||||||
|
methods: {
|
||||||
|
// 区域改变事件
|
||||||
|
changeCascader(value) {
|
||||||
|
// 将修改的值传递给父组件
|
||||||
|
this.$emit("update:region", value.join());
|
||||||
|
// 最后一级区县的数据
|
||||||
|
const area_code = value[value.length - 1]
|
||||||
|
const area_name = this.addressData.area.filter(item => item.value === area_code)[0].label
|
||||||
|
this.address[2] = area_name
|
||||||
|
this.$emit('setMapLocation', this.address.join(""))
|
||||||
|
},
|
||||||
|
// 将选择的地址放入数组中
|
||||||
|
getAddress(node) {
|
||||||
|
const index = node.level - 1
|
||||||
|
this.address[index] = node.label
|
||||||
|
},
|
||||||
|
// 清空级联数据
|
||||||
|
clearCheckedNodes() {
|
||||||
|
this.regionValue = ''
|
||||||
|
},
|
||||||
|
// 省市区的显示值
|
||||||
|
cascaderValue(value) {
|
||||||
|
this.initCascaderValue = value
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
</style>
|
244
src/components/Dialog/addCarBrand.vue
Normal file
@ -0,0 +1,244 @@
|
|||||||
|
<template>
|
||||||
|
<el-dialog
|
||||||
|
:title="title"
|
||||||
|
:visible.sync="dialogVisible"
|
||||||
|
@close="close"
|
||||||
|
:close-on-click-modal="false"
|
||||||
|
:destroy-on-close="true"
|
||||||
|
>
|
||||||
|
<el-form :model="form" ref="dialogBrand" :rules="rules">
|
||||||
|
<el-form-item label="品牌中文" label-width="120px" prop="brand_name_ch">
|
||||||
|
<el-input v-model="form.brand_name_ch" autocomplete="off"></el-input>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="品牌英文" label-width="120px" prop="brand_name_en">
|
||||||
|
<el-input v-model="form.brand_name_en" autocomplete="off"></el-input>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="LOGO" label-width="120px">
|
||||||
|
<el-upload
|
||||||
|
:action="uploadURL"
|
||||||
|
name="img"
|
||||||
|
:file-list="fileList"
|
||||||
|
:on-success="uploadSuccess"
|
||||||
|
:on-change="uploadChange"
|
||||||
|
list-type="picture-card"
|
||||||
|
ref="upload"
|
||||||
|
:auto-upload="false"
|
||||||
|
:limit="1"
|
||||||
|
>
|
||||||
|
<i slot="default" class="el-icon-plus"></i>
|
||||||
|
<div slot="file" slot-scope="{ file }">
|
||||||
|
<img class="el-upload-list__item-thumbnail" :src="file.url" />
|
||||||
|
<span class="el-upload-list__item-actions">
|
||||||
|
<span
|
||||||
|
class="el-upload-list__item-preview"
|
||||||
|
@click="handlePictureCardPreview(file)"
|
||||||
|
>
|
||||||
|
<i class="el-icon-zoom-in"></i>
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
v-if="!disabled"
|
||||||
|
class="el-upload-list__item-delete"
|
||||||
|
@click="handleRemove(file)"
|
||||||
|
>
|
||||||
|
<i class="el-icon-delete"></i>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div slot="tip" class="el-upload__tip">
|
||||||
|
只能上传jpg/png文件,且只能上传一张图片
|
||||||
|
</div>
|
||||||
|
</el-upload>
|
||||||
|
<el-dialog
|
||||||
|
width="40%"
|
||||||
|
top="8vh"
|
||||||
|
:visible.sync="dialogPreviewVisible"
|
||||||
|
:modal="false"
|
||||||
|
:close-on-click-modal="false"
|
||||||
|
>
|
||||||
|
<img width="100%" :src="dialogImageUrl" alt="" />
|
||||||
|
</el-dialog>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="类型" label-width="120px" prop="status">
|
||||||
|
<el-radio-group v-model="form.status">
|
||||||
|
<el-radio :label="0">禁用</el-radio>
|
||||||
|
<el-radio :label="1">启用</el-radio>
|
||||||
|
</el-radio-group>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item class="btn">
|
||||||
|
<el-button type="primary" @click="submitForm">确定</el-button>
|
||||||
|
<el-button @click="close">取消</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</el-dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: "AddCarBrand",
|
||||||
|
props: {
|
||||||
|
flagVisible: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
// 对话框的显示隐藏
|
||||||
|
dialogVisible: false,
|
||||||
|
// 表单的数据
|
||||||
|
form: {
|
||||||
|
brand_name_ch: "",
|
||||||
|
brand_name_en: "",
|
||||||
|
status: "",
|
||||||
|
},
|
||||||
|
id: "",
|
||||||
|
// 上传图片相关
|
||||||
|
disabled: false,
|
||||||
|
dialogPreviewVisible: false,
|
||||||
|
dialogImageUrl: "",
|
||||||
|
uploadURL: this.$store.state.app.uploadURL,
|
||||||
|
fileList: [],
|
||||||
|
isOldFile: true,
|
||||||
|
// 校验规则
|
||||||
|
rules: {
|
||||||
|
brand_name_ch: [
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: "请输入车辆品牌(中文)",
|
||||||
|
trigger: "blur",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
brand_name_en: [
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: "请输入车辆品牌(英文)",
|
||||||
|
trigger: "blur",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
// 父组件传递的品牌详细数据
|
||||||
|
this.$bus.$on("brandDetail", (data) => {
|
||||||
|
this.form.brand_name_ch = data.brand_name_ch;
|
||||||
|
this.form.brand_name_en = data.brand_name_en;
|
||||||
|
this.form.status = data.status;
|
||||||
|
this.id = data.id;
|
||||||
|
this.fileList.push({ name: data.brand_name_en, url: data.brand_logo });
|
||||||
|
});
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
// 关闭对话框方法
|
||||||
|
close() {
|
||||||
|
// 清空表单
|
||||||
|
this.$refs.dialogBrand.resetFields();
|
||||||
|
// 移除upload的图片
|
||||||
|
this.$refs.upload.clearFiles();
|
||||||
|
// 关闭对话框
|
||||||
|
this.$emit("update:flagVisible", false);
|
||||||
|
this.id = "";
|
||||||
|
this.dialogImageUrl = "";
|
||||||
|
this.form.brand_name_ch = "";
|
||||||
|
this.form.brand_name_en = "";
|
||||||
|
this.form.status = "";
|
||||||
|
this.fileList = [];
|
||||||
|
this.isOldFile = true
|
||||||
|
},
|
||||||
|
// 提交表单
|
||||||
|
submitForm() {
|
||||||
|
// 校验
|
||||||
|
this.$refs.dialogBrand.validate(async (valid) => {
|
||||||
|
if (valid) {
|
||||||
|
this.upload();
|
||||||
|
} else {
|
||||||
|
console.log("error submit!!");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
// 图片上传
|
||||||
|
upload() {
|
||||||
|
if (this.isOldFile && this.fileList.length !== 0 && this.id) {
|
||||||
|
this.editBrand(this.fileList[0].url)
|
||||||
|
} else {
|
||||||
|
this.fileList.length === 0
|
||||||
|
? this.message("warning", "请上传图片")
|
||||||
|
: this.$refs.upload.submit();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// 图片上传成功
|
||||||
|
uploadSuccess(response, file, fileList) {
|
||||||
|
if (response.code === 200) {
|
||||||
|
const imgUrl = response.imgUrl;
|
||||||
|
// 调用新增品牌接口
|
||||||
|
this.id ? this.editBrand(imgUrl) : this.addBrand(imgUrl);
|
||||||
|
} else {
|
||||||
|
this.$message.error(response.message);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// 新增车辆品牌
|
||||||
|
async addBrand(brand_logo) {
|
||||||
|
// 参数处理
|
||||||
|
let data = JSON.parse(JSON.stringify(this.form));
|
||||||
|
this.form.status === "" && delete data.status;
|
||||||
|
data.brand_logo = brand_logo;
|
||||||
|
let result = await this.$API.brand.addBrand(data);
|
||||||
|
if (result.code === 200) {
|
||||||
|
this.message("success", result.message);
|
||||||
|
this.close();
|
||||||
|
// 重新获取一次列表
|
||||||
|
this.$emit("getBrandList");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// 编辑车辆品牌
|
||||||
|
async editBrand(brand_logo) {
|
||||||
|
// 参数处理
|
||||||
|
let data = JSON.parse(JSON.stringify(this.form));
|
||||||
|
this.form.status === "" && delete data.status;
|
||||||
|
data.brand_logo = brand_logo;
|
||||||
|
data.id = this.id;
|
||||||
|
let result = await this.$API.brand.editBrand(data);
|
||||||
|
if (result.code === 200) {
|
||||||
|
this.message("success", result.message);
|
||||||
|
this.close();
|
||||||
|
// 重新获取一次列表
|
||||||
|
this.$emit("getBrandList");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// 图片移除
|
||||||
|
handleRemove(file) {
|
||||||
|
this.fileList = [];
|
||||||
|
this.isOldFile = false
|
||||||
|
},
|
||||||
|
// 图片改变
|
||||||
|
uploadChange(file, fileList) {
|
||||||
|
this.fileList = fileList;
|
||||||
|
},
|
||||||
|
// 图片预览
|
||||||
|
handlePictureCardPreview(file) {
|
||||||
|
this.dialogPreviewVisible = true;
|
||||||
|
this.dialogImageUrl = file.url;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
title() {
|
||||||
|
return this.id ? "编辑车辆品牌" : "新增车辆品牌";
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// 监听是否隐藏
|
||||||
|
watch: {
|
||||||
|
flagVisible: {
|
||||||
|
handler(newValue, oldValue) {
|
||||||
|
this.dialogVisible = newValue;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.btn {
|
||||||
|
padding-left: 120px;
|
||||||
|
}
|
||||||
|
</style>
|
37
src/components/Dialog/mapDialog.vue
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
<template>
|
||||||
|
<el-dialog
|
||||||
|
:title="parkingData.parkingName"
|
||||||
|
:visible="dialogVisibleMap"
|
||||||
|
width="70%"
|
||||||
|
:close-on-click-modal="false"
|
||||||
|
@close="close"
|
||||||
|
style="padding: 0"
|
||||||
|
>
|
||||||
|
<div class="mapDialog"><Map v-if="dialogVisibleMap" @mapComplete="addMarker" ref="map"></Map></div>
|
||||||
|
</el-dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import Map from '@/views/map/map'
|
||||||
|
export default {
|
||||||
|
name: 'mapDiglog',
|
||||||
|
components: { Map },
|
||||||
|
props: ['dialogVisibleMap', 'parkingData'],
|
||||||
|
methods: {
|
||||||
|
close() {
|
||||||
|
this.$emit('update:dialogVisibleMap', false)
|
||||||
|
},
|
||||||
|
// 添加点标记,在地图加载完成后调用
|
||||||
|
addMarker() {
|
||||||
|
this.$refs.map.addMarker(this.parkingData.coord)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.mapDialog {
|
||||||
|
width: 100%;
|
||||||
|
height: 650px;
|
||||||
|
}
|
||||||
|
</style>
|
110
src/components/Form/form.vue
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
|
||||||
|
<el-form ref="form" :model="formData" :label-width="config.form_config.label_width" :inline="config.form_config.inline">
|
||||||
|
|
||||||
|
<el-form-item v-for="item in config.form_item_config" :key="item.prop" :rules="item.rules" :label="item.label" :prop="item.prop">
|
||||||
|
<!-- 输入框类型 -->
|
||||||
|
<el-input v-if="item.type === 'input'" v-model.trim="formData[item.prop]" :placeholder="item.placeholder" :disabled="item.disabled" :style="{ width: item.width}"></el-input>
|
||||||
|
|
||||||
|
<!-- 插槽类型 -->
|
||||||
|
<slot v-if="item.type === 'slot'" :name="item.slotName"></slot>
|
||||||
|
|
||||||
|
<!-- 单选类型 -->
|
||||||
|
<el-radio-group v-if="item.type === 'radio'" v-model="formData[item.prop]">
|
||||||
|
<el-radio v-for="radio in item.options" :key="radio.value" :label="radio.value">{{ radio.label }}</el-radio>
|
||||||
|
</el-radio-group>
|
||||||
|
|
||||||
|
<!-- 开关类型 -->
|
||||||
|
<el-switch v-if="item.type === 'switch'" v-model="formData[item.prop]" active-color="#13ce66"></el-switch>
|
||||||
|
|
||||||
|
<!-- 下拉框类型 -->
|
||||||
|
<el-select v-if="item.type === 'select'" v-model="formData[item.prop]" :placeholder="item.placeholder">
|
||||||
|
<el-option v-for="option in item.options" :key="option.id || option.value" :label="option.label" :value="option.value">
|
||||||
|
</el-option>
|
||||||
|
</el-select>
|
||||||
|
|
||||||
|
<!-- 富文本编辑器 -->
|
||||||
|
<div v-if="item.type === 'richText'" ref="editorDom"></div>
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
|
|
||||||
|
<!-- 按钮 -->
|
||||||
|
<el-form-item>
|
||||||
|
<el-button v-for="item in config.form_handler" :key="item.key" :type="item.type" @click="item.handler && item.handler()">
|
||||||
|
{{item.label}}
|
||||||
|
</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
|
</el-form>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import E from "wangeditor";
|
||||||
|
export default {
|
||||||
|
name: 'Form',
|
||||||
|
props: ['config', 'formData'],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
required_msg: {
|
||||||
|
'input': '请输入',
|
||||||
|
'radio': '请选择',
|
||||||
|
'select': '请选择'
|
||||||
|
},
|
||||||
|
// 富文本
|
||||||
|
editor: null,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.config.form_item_config.forEach(item => {
|
||||||
|
if (item.type === 'richText') {
|
||||||
|
this.createEditor()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
// 初始化form数据
|
||||||
|
initForm() {
|
||||||
|
this.config.form_item_config.forEach(item => {
|
||||||
|
// rules规则
|
||||||
|
if (item.required) this.rules(item);
|
||||||
|
})
|
||||||
|
},
|
||||||
|
rules(item) {
|
||||||
|
const requiredRules = [{ required: true, message: item.required_msg || `${this.required_msg[item.type]}${item.label}`, trigger: "change" }]
|
||||||
|
// 其他规则
|
||||||
|
if (item.rules && item.rules.length > 0) {
|
||||||
|
item.rules = requiredRules.concat(item.rules)
|
||||||
|
} else {
|
||||||
|
item.rules = requiredRules
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
// 创建富文本
|
||||||
|
createEditor() {
|
||||||
|
this.editor = new E(this.$refs.editorDom);
|
||||||
|
this.editor.create();
|
||||||
|
},
|
||||||
|
// 清空富文本内容
|
||||||
|
clearText() {
|
||||||
|
this.editor.txt.clear()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
config: {
|
||||||
|
immediate: true,
|
||||||
|
handler(newValue) {
|
||||||
|
this.initForm()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
|
||||||
|
</style>
|
44
src/components/Hamburger/index.vue
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
<template>
|
||||||
|
<div style="padding: 0 15px;" @click="toggleClick">
|
||||||
|
<svg
|
||||||
|
:class="{'is-active':isActive}"
|
||||||
|
class="hamburger"
|
||||||
|
viewBox="0 0 1024 1024"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="64"
|
||||||
|
height="64"
|
||||||
|
>
|
||||||
|
<path d="M408 442h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8zm-8 204c0 4.4 3.6 8 8 8h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56zm504-486H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0 632H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zM142.4 642.1L298.7 519a8.84 8.84 0 0 0 0-13.9L142.4 381.9c-5.8-4.6-14.4-.5-14.4 6.9v246.3a8.9 8.9 0 0 0 14.4 7z" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'Hamburger',
|
||||||
|
props: {
|
||||||
|
isActive: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
toggleClick() {
|
||||||
|
this.$emit('toggleClick')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.hamburger {
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align: middle;
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hamburger.is-active {
|
||||||
|
transform: rotate(180deg);
|
||||||
|
}
|
||||||
|
</style>
|
91
src/components/Nav/Nav.vue
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
<template>
|
||||||
|
<ul :class="{ displayShow: displayShow, nav: true }">
|
||||||
|
<li>
|
||||||
|
<i class="el-icon-warning-outline"></i>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<i class="el-icon-search"></i>
|
||||||
|
</li>
|
||||||
|
<li class="choose-car" @click="chooseCar">选择车辆</li>
|
||||||
|
<li @click="moveTolocation">
|
||||||
|
<i class="el-icon-location-outline"></i>
|
||||||
|
</li>
|
||||||
|
<!-- <li @click="goToUser">
|
||||||
|
<i class="el-icon-user"></i>
|
||||||
|
</li> -->
|
||||||
|
</ul>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "Nav",
|
||||||
|
methods: {
|
||||||
|
// 去用户中心
|
||||||
|
goToUser() {
|
||||||
|
this.$router.push({
|
||||||
|
name: this.$store.state.userAbout.token ? "User" : "Login"
|
||||||
|
});
|
||||||
|
},
|
||||||
|
// 去选择车辆
|
||||||
|
chooseCar() {
|
||||||
|
localStorage.getItem('id') ? this.$router.push({ name: "Car" }) : this.$message({type: 'warning', message: '请先选择停车场'})
|
||||||
|
|
||||||
|
},
|
||||||
|
// 定位
|
||||||
|
moveTolocation() {
|
||||||
|
this.$bus.$emit("location");
|
||||||
|
},
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
displayShow() {
|
||||||
|
return this.$route.name === "Car" ? true : false;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.nav {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 86px;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%);
|
||||||
|
z-index: 1;
|
||||||
|
height: 86px;
|
||||||
|
line-height: 86px;
|
||||||
|
color: #fff;
|
||||||
|
li {
|
||||||
|
width: 44px;
|
||||||
|
height: 44px;
|
||||||
|
line-height: 44px;
|
||||||
|
text-align: center;
|
||||||
|
background-color: #3c4147;
|
||||||
|
border-radius: 50%;
|
||||||
|
display: inline-block;
|
||||||
|
margin-right: 34px;
|
||||||
|
&:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
color: #00a3ff;
|
||||||
|
}
|
||||||
|
i {
|
||||||
|
line-height: 44px;
|
||||||
|
font-size: 16px;
|
||||||
|
&:hover {
|
||||||
|
color: #00a3ff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.choose-car {
|
||||||
|
width: 240px;
|
||||||
|
height: 100%;
|
||||||
|
line-height: 86px;
|
||||||
|
margin-right: 34px;
|
||||||
|
font-size: 26px;
|
||||||
|
border-radius: 40px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.displayShow {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
</style>
|
36
src/components/Pagination/Pagination.vue
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
<template>
|
||||||
|
<el-pagination
|
||||||
|
:page-sizes="[10, 17, 26, 35]"
|
||||||
|
:page-size="pageSize"
|
||||||
|
:current-page="pageNumber"
|
||||||
|
layout="->, total, sizes, prev, pager, next, jumper"
|
||||||
|
background
|
||||||
|
class="pagination"
|
||||||
|
@size-change="sizeChange"
|
||||||
|
@current-change="currentChange"
|
||||||
|
:total="total">
|
||||||
|
</el-pagination>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'Pagination',
|
||||||
|
props: ['total', 'pageSize', 'pageNumber'],
|
||||||
|
methods: {
|
||||||
|
sizeChange(value) {
|
||||||
|
this.$emit('update:pageSize', value)
|
||||||
|
this.$emit('getDataList')
|
||||||
|
},
|
||||||
|
currentChange(value) {
|
||||||
|
this.$emit('update:pageNumber', value)
|
||||||
|
this.$emit('getDataList')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.pagination {
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
</style>
|
62
src/components/SvgIcon/index.vue
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
<template>
|
||||||
|
<div v-if="isExternal" :style="styleExternalIcon" class="svg-external-icon svg-icon" v-on="$listeners" />
|
||||||
|
<svg v-else :class="svgClass" aria-hidden="true" v-on="$listeners">
|
||||||
|
<use :xlink:href="iconName" />
|
||||||
|
</svg>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// doc: https://panjiachen.github.io/vue-element-admin-site/feature/component/svg-icon.html#usage
|
||||||
|
import { isExternal } from '@/utils/validate'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'SvgIcon',
|
||||||
|
props: {
|
||||||
|
iconClass: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
className: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
isExternal() {
|
||||||
|
return isExternal(this.iconClass)
|
||||||
|
},
|
||||||
|
iconName() {
|
||||||
|
return `#icon-${this.iconClass}`
|
||||||
|
},
|
||||||
|
svgClass() {
|
||||||
|
if (this.className) {
|
||||||
|
return 'svg-icon ' + this.className
|
||||||
|
} else {
|
||||||
|
return 'svg-icon'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
styleExternalIcon() {
|
||||||
|
return {
|
||||||
|
mask: `url(${this.iconClass}) no-repeat 50% 50%`,
|
||||||
|
'-webkit-mask': `url(${this.iconClass}) no-repeat 50% 50%`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.svg-icon {
|
||||||
|
width: 1em;
|
||||||
|
height: 1em;
|
||||||
|
vertical-align: -0.15em;
|
||||||
|
fill: currentColor;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.svg-external-icon {
|
||||||
|
background-color: currentColor;
|
||||||
|
mask-size: cover!important;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
</style>
|
181
src/components/TableData/table.vue
Normal file
@ -0,0 +1,181 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<el-table
|
||||||
|
:data="tableList"
|
||||||
|
tooltip-effect="dark"
|
||||||
|
style="width: 100%"
|
||||||
|
border
|
||||||
|
v-loading="table_loading"
|
||||||
|
element-loading-text="正在加载中..."
|
||||||
|
class="table"
|
||||||
|
>
|
||||||
|
<!-- 勾选列 -->
|
||||||
|
<el-table-column
|
||||||
|
v-if="config.checkedColumn"
|
||||||
|
type="selection"
|
||||||
|
width="40"
|
||||||
|
></el-table-column>
|
||||||
|
|
||||||
|
<template v-for="config_header in config.table_headers">
|
||||||
|
<!-- 需要回调处理值的列 -->
|
||||||
|
<el-table-column
|
||||||
|
v-if="config_header.type === 'function'"
|
||||||
|
:key="config_header.prop"
|
||||||
|
:prop="config_header.prop"
|
||||||
|
:label="config_header.label"
|
||||||
|
:width="config_header.width"
|
||||||
|
:align="config_header.align || 'left'"
|
||||||
|
>
|
||||||
|
<template slot-scope="scope">
|
||||||
|
<span>{{
|
||||||
|
config_header.callback && config_header.callback(scope.row)
|
||||||
|
}}</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
|
||||||
|
<!-- 需要放入其他html标签,例如:button、img,使用插槽处理-->
|
||||||
|
<el-table-column
|
||||||
|
v-else-if="config_header.type === 'slot'"
|
||||||
|
:key="config_header.prop"
|
||||||
|
:prop="config_header.prop"
|
||||||
|
:label="config_header.label"
|
||||||
|
:width="config_header.width"
|
||||||
|
:align="config_header.align"
|
||||||
|
>
|
||||||
|
<template slot-scope="scope">
|
||||||
|
<slot :name="config_header.slotName" :data="scope.row"></slot>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
|
||||||
|
<!-- 纯文本 -->
|
||||||
|
<el-table-column
|
||||||
|
v-else
|
||||||
|
:key="config_header.prop"
|
||||||
|
:prop="config_header.prop"
|
||||||
|
:label="config_header.label"
|
||||||
|
:width="config_header.width"
|
||||||
|
:align="config_header.align"
|
||||||
|
></el-table-column>
|
||||||
|
</template>
|
||||||
|
</el-table>
|
||||||
|
<!-- 分页器 -->
|
||||||
|
<el-pagination
|
||||||
|
v-if="config.pagination"
|
||||||
|
:page-sizes="[10, 17, 26, 35]"
|
||||||
|
:page-size="pageSize"
|
||||||
|
:current-page="pageNumber"
|
||||||
|
layout="->, total, sizes, prev, pager, next, jumper"
|
||||||
|
background
|
||||||
|
class="pagination"
|
||||||
|
@size-change="sizeChange"
|
||||||
|
@current-change="currentChange"
|
||||||
|
:total="total"
|
||||||
|
>
|
||||||
|
</el-pagination>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: "Table",
|
||||||
|
props: ["table_config"],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
// 表格配置
|
||||||
|
config: {
|
||||||
|
// 表头配置
|
||||||
|
table_headers: [],
|
||||||
|
// API请求配置
|
||||||
|
axios_config: {},
|
||||||
|
// 请求参数
|
||||||
|
requestData: {},
|
||||||
|
// 勾选列
|
||||||
|
checkedColumn: true,
|
||||||
|
// 分页
|
||||||
|
pagination: true,
|
||||||
|
},
|
||||||
|
// 表格数据
|
||||||
|
tableList: [],
|
||||||
|
pageSize: 10,
|
||||||
|
pageNumber: 1,
|
||||||
|
total: 0,
|
||||||
|
// 加载效果
|
||||||
|
table_loading: false,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.initConfig();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
// 初始化配置
|
||||||
|
initConfig() {
|
||||||
|
for (const key in this.table_config) {
|
||||||
|
if (Object.keys(this.config).includes(key)) {
|
||||||
|
this.config[key] = this.table_config[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 初始化完成后调用接口请求数据
|
||||||
|
this.getTableData();
|
||||||
|
},
|
||||||
|
// 发送请求获取数据
|
||||||
|
async getTableData() {
|
||||||
|
let { url, method } = this.config.axios_config;
|
||||||
|
let reqData = {
|
||||||
|
url,
|
||||||
|
method,
|
||||||
|
data: this.config.requestData,
|
||||||
|
};
|
||||||
|
this.table_loading = true;
|
||||||
|
let result = await this.$API.common.tableData(reqData);
|
||||||
|
if (result.status === 200) {
|
||||||
|
this.tableList = result.data;
|
||||||
|
// 考虑DOM元素渲染
|
||||||
|
this.$nextTick(() => {
|
||||||
|
// todo something...
|
||||||
|
});
|
||||||
|
this.total = result.total;
|
||||||
|
this.table_loading = false;
|
||||||
|
} else {
|
||||||
|
this.tableList = []
|
||||||
|
this.$message({
|
||||||
|
type: 'warning',
|
||||||
|
message: result.message
|
||||||
|
})
|
||||||
|
this.table_loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// 有其余参数处理获取数据
|
||||||
|
getTableDataWithParams(params = "") {
|
||||||
|
if (params) {
|
||||||
|
// 有参数更新请求参数
|
||||||
|
this.config.requestData = params;
|
||||||
|
}
|
||||||
|
this.getTableData();
|
||||||
|
},
|
||||||
|
// 页面大小改变
|
||||||
|
sizeChange(value) {
|
||||||
|
this.config.requestData.pageSize = value;
|
||||||
|
this.getTableData();
|
||||||
|
},
|
||||||
|
// 当前页改变
|
||||||
|
currentChange(value) {
|
||||||
|
this.config.requestData.pageNumber = value;
|
||||||
|
this.getTableData();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
table_config: {
|
||||||
|
immediate: true,
|
||||||
|
handler(newValue) {
|
||||||
|
// console.log(newValue);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.table {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
</style>
|
47
src/components/Upload/index.vue
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
<template>
|
||||||
|
<div style="display: flex; flex-direction: column; justify-content: center">
|
||||||
|
<input
|
||||||
|
type="file"
|
||||||
|
@change="uploadImage"
|
||||||
|
ref="fileInput"
|
||||||
|
style="display: none"
|
||||||
|
/>
|
||||||
|
<el-button type="success" @click="openFileInput">上传照片</el-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import axios from "axios";
|
||||||
|
export default {
|
||||||
|
name: "Upload",
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
photo: "",
|
||||||
|
};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
openFileInput() {
|
||||||
|
this.$refs.fileInput.click();
|
||||||
|
},
|
||||||
|
uploadImage(event) {
|
||||||
|
var file = event.target.files[0];
|
||||||
|
var formData = new FormData();
|
||||||
|
formData.append("image", file);
|
||||||
|
formData.append("token", "1c17b11693cb5ec63859b091c5b9c1b2");
|
||||||
|
axios
|
||||||
|
.post("http://8.141.3.231:40061/api/index.php", formData, {
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "multipart/form-data",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.then((response) => {
|
||||||
|
this.$emit("upload-success", response.data.url);
|
||||||
|
this.$message({ type: "success", message: "上传成功" });
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
this.$message.error("上传图片失败:" + error);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
9
src/icons/index.js
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import Vue from 'vue'
|
||||||
|
import SvgIcon from '@/components/SvgIcon'// svg component
|
||||||
|
|
||||||
|
// register globally
|
||||||
|
Vue.component('svg-icon', SvgIcon)
|
||||||
|
|
||||||
|
const req = require.context('./svg', false, /\.svg$/)
|
||||||
|
const requireAll = requireContext => requireContext.keys().map(requireContext)
|
||||||
|
requireAll(req)
|
1
src/icons/svg/cheliangguanli.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg class="icon" width="200px" height="200.00px" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"><path fill="#e6e6e6" d="M224.304762 497.371429h175.542857v78.019047H224.304762zM872.838095 258.438095l-97.523809-146.285714c-9.752381-14.628571-24.380952-24.380952-39.009524-24.380952H258.438095L146.285714 258.438095H4.87619v97.52381h78.019048L19.504762 448.609524c-4.87619 9.752381-4.87619 19.504762-4.876191 29.257143v385.219047H341.333333v-117.028571h204.8v-97.52381H243.809524v117.028572H107.27619v-273.066667l53.638096-78.019048h697.295238l39.009524 58.514286 82.895238-53.638095-39.009524-58.514286H1024v-97.523809h-151.161905zM229.180952 316.952381l82.895238-126.780952h399.84762l82.895238 126.780952H229.180952zM980.114286 551.009524l-63.390476 34.133333c-19.504762-24.380952-48.761905-39.009524-82.895239-48.761905V463.238095h-78.019047V536.380952c-29.257143 9.752381-58.514286 24.380952-82.895238 48.761905l-63.390476-39.009524v4.876191l-39.009524 68.266666 68.266666 39.009524c-4.87619 9.752381-4.87619 24.380952-4.87619 39.009524 0 14.628571 4.87619 29.257143 4.87619 43.885714l-68.266666 39.009524h-4.876191l39.009524 68.266667 68.266667-39.009524c19.504762 24.380952 48.761905 39.009524 78.019047 48.761905v78.019047H828.952381v-78.019047c29.257143-9.752381 58.514286-24.380952 78.019048-43.885714l68.266666 39.009523 39.009524-68.266666-68.266667-39.009524c4.87619-14.628571 9.752381-29.257143 9.752381-48.761905 0-14.628571-4.87619-29.257143-4.87619-43.885714l63.390476-39.009524-34.133333-63.390476z m-185.295238 234.057143c-48.761905 0-87.771429-39.009524-87.771429-87.771429s39.009524-87.771429 87.771429-87.771428 87.771429 39.009524 87.771428 87.771428-39.009524 87.771429-87.771428 87.771429z" /></svg>
|
After Width: | Height: | Size: 1.8 KiB |
1
src/icons/svg/dashboard.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg width="128" height="100" xmlns="http://www.w3.org/2000/svg"><path d="M27.429 63.638c0-2.508-.893-4.65-2.679-6.424-1.786-1.775-3.94-2.662-6.464-2.662-2.524 0-4.679.887-6.465 2.662-1.785 1.774-2.678 3.916-2.678 6.424 0 2.508.893 4.65 2.678 6.424 1.786 1.775 3.94 2.662 6.465 2.662 2.524 0 4.678-.887 6.464-2.662 1.786-1.775 2.679-3.916 2.679-6.424zm13.714-31.801c0-2.508-.893-4.65-2.679-6.424-1.785-1.775-3.94-2.662-6.464-2.662-2.524 0-4.679.887-6.464 2.662-1.786 1.774-2.679 3.916-2.679 6.424 0 2.508.893 4.65 2.679 6.424 1.785 1.774 3.94 2.662 6.464 2.662 2.524 0 4.679-.888 6.464-2.662 1.786-1.775 2.679-3.916 2.679-6.424zM71.714 65.98l7.215-27.116c.285-1.23.107-2.378-.536-3.443-.643-1.064-1.56-1.762-2.75-2.094-1.19-.33-2.333-.177-3.429.462-1.095.639-1.81 1.573-2.143 2.804l-7.214 27.116c-2.857.237-5.405 1.266-7.643 3.088-2.238 1.822-3.738 4.152-4.5 6.992-.952 3.644-.476 7.098 1.429 10.364 1.905 3.265 4.69 5.37 8.357 6.317 3.667.947 7.143.474 10.429-1.42 3.285-1.892 5.404-4.66 6.357-8.305.762-2.84.619-5.607-.429-8.305-1.047-2.697-2.762-4.85-5.143-6.46zm47.143-2.342c0-2.508-.893-4.65-2.678-6.424-1.786-1.775-3.94-2.662-6.465-2.662-2.524 0-4.678.887-6.464 2.662-1.786 1.774-2.679 3.916-2.679 6.424 0 2.508.893 4.65 2.679 6.424 1.786 1.775 3.94 2.662 6.464 2.662 2.524 0 4.679-.887 6.465-2.662 1.785-1.775 2.678-3.916 2.678-6.424zm-45.714-45.43c0-2.509-.893-4.65-2.679-6.425C68.68 10.01 66.524 9.122 64 9.122c-2.524 0-4.679.887-6.464 2.661-1.786 1.775-2.679 3.916-2.679 6.425 0 2.508.893 4.65 2.679 6.424 1.785 1.774 3.94 2.662 6.464 2.662 2.524 0 4.679-.888 6.464-2.662 1.786-1.775 2.679-3.916 2.679-6.424zm32 13.629c0-2.508-.893-4.65-2.679-6.424-1.785-1.775-3.94-2.662-6.464-2.662-2.524 0-4.679.887-6.464 2.662-1.786 1.774-2.679 3.916-2.679 6.424 0 2.508.893 4.65 2.679 6.424 1.785 1.774 3.94 2.662 6.464 2.662 2.524 0 4.679-.888 6.464-2.662 1.786-1.775 2.679-3.916 2.679-6.424zM128 63.638c0 12.351-3.357 23.78-10.071 34.286-.905 1.372-2.19 2.058-3.858 2.058H13.93c-1.667 0-2.953-.686-3.858-2.058C3.357 87.465 0 76.037 0 63.638c0-8.613 1.69-16.847 5.071-24.703C8.452 31.08 13 24.312 18.714 18.634c5.715-5.68 12.524-10.199 20.429-13.559C47.048 1.715 55.333.035 64 .035c8.667 0 16.952 1.68 24.857 5.04 7.905 3.36 14.714 7.88 20.429 13.559 5.714 5.678 10.262 12.446 13.643 20.301 3.38 7.856 5.071 16.09 5.071 24.703z"/></svg>
|
After Width: | Height: | Size: 2.3 KiB |
1
src/icons/svg/example.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M96.258 57.462h31.421C124.794 27.323 100.426 2.956 70.287.07v31.422a32.856 32.856 0 0 1 25.971 25.97zm-38.796-25.97V.07C27.323 2.956 2.956 27.323.07 57.462h31.422a32.856 32.856 0 0 1 25.97-25.97zm12.825 64.766v31.421c30.46-2.885 54.507-27.253 57.713-57.712H96.579c-2.886 13.466-13.146 23.726-26.292 26.291zM31.492 70.287H.07c2.886 30.46 27.253 54.507 57.713 57.713V96.579c-13.466-2.886-23.726-13.146-26.291-26.292z"/></svg>
|
After Width: | Height: | Size: 497 B |
1
src/icons/svg/eye-open.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg class="icon" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" width="128" height="128"><defs><style/></defs><path d="M512 128q69.675 0 135.51 21.163t115.498 54.997 93.483 74.837 73.685 82.006 51.67 74.837 32.17 54.827L1024 512q-2.347 4.992-6.315 13.483T998.87 560.17t-31.658 51.669-44.331 59.99-56.832 64.34-69.504 60.16-82.347 51.5-94.848 34.687T512 896q-69.675 0-135.51-21.163t-115.498-54.826-93.483-74.326-73.685-81.493-51.67-74.496-32.17-54.997L0 513.707q2.347-4.992 6.315-13.483t18.816-34.816 31.658-51.84 44.331-60.33 56.832-64.683 69.504-60.331 82.347-51.84 94.848-34.816T512 128.085zm0 85.333q-46.677 0-91.648 12.331t-81.152 31.83-70.656 47.146-59.648 54.485-48.853 57.686-37.675 52.821-26.325 43.99q12.33 21.674 26.325 43.52t37.675 52.351 48.853 57.003 59.648 53.845T339.2 767.02t81.152 31.488T512 810.667t91.648-12.331 81.152-31.659 70.656-46.848 59.648-54.186 48.853-57.344 37.675-52.651T927.957 512q-12.33-21.675-26.325-43.648t-37.675-52.65-48.853-57.345-59.648-54.186-70.656-46.848-81.152-31.659T512 213.334zm0 128q70.656 0 120.661 50.006T682.667 512 632.66 632.661 512 682.667 391.339 632.66 341.333 512t50.006-120.661T512 341.333zm0 85.334q-35.328 0-60.33 25.002T426.666 512t25.002 60.33T512 597.334t60.33-25.002T597.334 512t-25.002-60.33T512 426.666z"/></svg>
|
After Width: | Height: | Size: 1.3 KiB |
1
src/icons/svg/eye.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg width="128" height="64" xmlns="http://www.w3.org/2000/svg"><path d="M127.072 7.994c1.37-2.208.914-5.152-.914-6.87-2.056-1.717-4.797-1.226-6.396.982-.229.245-25.586 32.382-55.74 32.382-29.24 0-55.74-32.382-55.968-32.627-1.6-1.963-4.57-2.208-6.397-.49C-.17 3.086-.399 6.275 1.2 8.238c.457.736 5.94 7.36 14.62 14.72L4.17 35.96c-1.828 1.963-1.6 5.152.228 6.87.457.98 1.6 1.471 2.742 1.471s2.284-.49 3.198-1.472l12.564-13.983c5.94 4.416 13.021 8.587 20.788 11.53l-4.797 17.418c-.685 2.699.686 5.397 3.198 6.133h1.37c2.057 0 3.884-1.472 4.341-3.68L52.6 42.83c3.655.736 7.538 1.227 11.422 1.227 3.883 0 7.767-.49 11.422-1.227l4.797 17.173c.457 2.208 2.513 3.68 4.34 3.68.457 0 .914 0 1.143-.246 2.513-.736 3.883-3.434 3.198-6.133l-4.797-17.172c7.767-2.944 14.848-7.114 20.788-11.53l12.336 13.738c.913.981 2.056 1.472 3.198 1.472s2.284-.49 3.198-1.472c1.828-1.963 1.828-4.906.228-6.87l-11.65-13.001c9.366-7.36 14.849-14.474 14.849-14.474z"/></svg>
|
After Width: | Height: | Size: 944 B |
1
src/icons/svg/form.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M84.068 23.784c-1.02 0-1.877-.32-2.572-.96a8.588 8.588 0 0 1-1.738-2.237 11.524 11.524 0 0 1-1.042-2.621c-.232-.895-.348-1.641-.348-2.238V0h.278c.834 0 1.622.085 2.363.256.742.17 1.645.575 2.711 1.214 1.066.64 2.363 1.535 3.892 2.686 1.53 1.15 3.453 2.664 5.77 4.54 2.502 2.045 4.494 3.771 5.977 5.178 1.483 1.406 2.618 2.6 3.406 3.58.787.98 1.274 1.812 1.46 2.494.185.682.277 1.278.277 1.79v2.046H84.068zM127.3 84.01c.278.682.464 1.535.556 2.558.093 1.023-.37 2.003-1.39 2.94-.463.427-.88.832-1.25 1.215-.372.384-.696.704-.974.96a6.69 6.69 0 0 1-.973.767l-11.816-10.741a44.331 44.331 0 0 0 1.877-1.535 31.028 31.028 0 0 1 1.737-1.406c1.112-.938 2.317-1.343 3.615-1.215 1.297.128 2.363.405 3.197.83.927.427 1.923 1.173 2.989 2.239 1.065 1.065 1.876 2.195 2.432 3.388zM78.23 95.902c2.038 0 3.752-.511 5.143-1.534l-26.969 25.83H18.037c-1.761 0-3.684-.47-5.77-1.407a24.549 24.549 0 0 1-5.838-3.709 21.373 21.373 0 0 1-4.518-5.306c-1.204-2.003-1.807-4.07-1.807-6.202V16.495c0-1.79.44-3.665 1.32-5.626A18.41 18.41 0 0 1 5.04 5.562a21.798 21.798 0 0 1 5.213-3.964C12.198.533 14.237 0 16.37 0h53.24v15.984c0 1.62.278 3.367.834 5.242a16.704 16.704 0 0 0 2.572 5.179c1.159 1.577 2.665 2.898 4.518 3.964 1.853 1.066 4.078 1.598 6.673 1.598h20.295v42.325L85.458 92.45c1.02-1.364 1.529-2.856 1.529-4.476 0-2.216-.857-4.113-2.572-5.69-1.714-1.577-3.776-2.366-6.186-2.366H26.1c-2.409 0-4.448.789-6.116 2.366-1.668 1.577-2.502 3.474-2.502 5.69 0 2.217.834 4.092 2.502 5.626 1.668 1.535 3.707 2.302 6.117 2.302h52.13zM26.1 47.951c-2.41 0-4.449.789-6.117 2.366-1.668 1.577-2.502 3.473-2.502 5.69 0 2.216.834 4.092 2.502 5.626 1.668 1.534 3.707 2.302 6.117 2.302h52.13c2.409 0 4.47-.768 6.185-2.302 1.715-1.534 2.572-3.41 2.572-5.626 0-2.217-.857-4.113-2.572-5.69-1.714-1.577-3.776-2.366-6.186-2.366H26.1zm52.407 64.063l1.807-1.663 3.476-3.196a479.75 479.75 0 0 0 4.587-4.284 500.757 500.757 0 0 1 5.004-4.667c3.985-3.666 8.48-7.758 13.485-12.276l11.677 10.741-13.485 12.404-5.004 4.603-4.587 4.22a179.46 179.46 0 0 0-3.267 3.068c-.88.853-1.367 1.322-1.46 1.407-.463.341-.973.703-1.529 1.087-.556.383-1.112.703-1.668.959-.556.256-1.413.575-2.572.959a83.5 83.5 0 0 1-3.545 1.087 72.2 72.2 0 0 1-3.475.895c-1.112.256-1.946.426-2.502.511-1.112.17-1.854.043-2.224-.383-.371-.426-.464-1.151-.278-2.174.092-.511.278-1.279.556-2.302.278-1.023.602-2.067.973-3.132l1.042-3.005c.325-.938.58-1.577.765-1.918a10.157 10.157 0 0 1 2.224-2.941z"/></svg>
|
After Width: | Height: | Size: 2.4 KiB |
1
src/icons/svg/link.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M115.625 127.937H.063V12.375h57.781v12.374H12.438v90.813h90.813V70.156h12.374z"/><path d="M116.426 2.821l8.753 8.753-56.734 56.734-8.753-8.745z"/><path d="M127.893 37.982h-12.375V12.375H88.706V0h39.187z"/></svg>
|
After Width: | Height: | Size: 285 B |
1
src/icons/svg/member.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1675579475154" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2447" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M1007.4 597.1l-50 0c-4.6-16.5-11.1-32.2-19.3-46.8l35.3-35.3c6.5-6.5 6.5-17 0-23.5l-57-57c-3.2-3.2-7.5-4.8-11.7-4.8-4.3 0-8.5 1.6-11.7 4.8l-35.3 35.3c-14.6-8.2-30.3-14.7-46.8-19.3l0-50c0-9.1-7.5-16.6-16.6-16.6l-80.6 0c-9.1 0-16.6 7.5-16.6 16.6l0 50c-16.5 4.6-32.2 11.1-46.8 19.3L615 434.6c-3.2-3.2-7.5-4.8-11.7-4.8s-8.5 1.6-11.7 4.8l-57 57c-6.4 6.5-6.4 17 0 23.5l35.3 35.3c-8.2 14.6-14.7 30.3-19.3 46.8l-50 0c-9.1 0-16.6 7.5-16.6 16.6l0 80.6c0 9.1 7.5 16.6 16.6 16.6l50 0c4.6 16.5 11.1 32.2 19.3 46.8L534.5 793c-6.4 6.5-6.4 17 0 23.5l57 57c3.2 3.2 7.5 4.8 11.7 4.8s8.5-1.6 11.7-4.8l35.3-35.3c14.6 8.2 30.3 14.7 46.8 19.3l0 50c0 9.1 7.5 16.6 16.6 16.6l80.6 0c9.1 0 16.6-7.5 16.6-16.6l0-50c16.5-4.6 32.2-11.1 46.8-19.3l35.3 35.3c3.2 3.2 7.5 4.8 11.7 4.8 4.3 0 8.5-1.6 11.7-4.8l57-57c6.5-6.5 6.5-17 0-23.5l-35.3-35.3c8.2-14.6 14.7-30.3 19.3-46.8l50 0c9.1 0 16.6-7.5 16.6-16.6l0-80.6C1024 604.6 1016.5 597.1 1007.4 597.1zM895.8 693.8c-3.2 11.5-7.7 22.5-13.4 32.6l-20.3 35.9-35.9 20.3c-10.1 5.7-21 10.2-32.6 13.4L753.9 807l-39.7-11.1c-11.5-3.2-22.5-7.7-32.6-13.4l-35.9-20.3-20.3-35.9c-5.7-10.1-10.2-21-13.4-32.6L601 654.1l11.1-39.7c3.2-11.5 7.7-22.5 13.4-32.6l20.3-35.9 35.9-20.3c10.1-5.7 21-10.2 32.6-13.4l39.7-11.1 39.7 11.1c11.5 3.2 22.5 7.7 32.6 13.4l35.9 20.3 20.3 35.9c5.7 10.1 10.2 21 13.4 32.6l11.1 39.7L895.8 693.8z" p-id="2448"></path><path d="M753.9 552.8c-55.9 0-101.3 45.3-101.3 101.3 0 55.9 45.3 101.3 101.3 101.3 55.9 0 101.3-45.3 101.3-101.3C855.2 598.1 809.9 552.8 753.9 552.8zM753.9 691.3c-20.6 0-37.3-16.7-37.3-37.3s16.7-37.3 37.3-37.3 37.3 16.7 37.3 37.3S774.5 691.3 753.9 691.3z" p-id="2449"></path><path d="M672 923.5c-17.7 0-32 14.3-32 32l0 4.5L64 960 64 798.5c0-157.5 123.2-283.9 280.5-287.9 5-0.1 10-0.1 14.9 0 37.1 0.9 73 8.8 106.8 23.5 16.2 7 35.1-0.4 42.1-16.7 7-16.2-0.4-35.1-16.7-42.1-4.8-2.1-9.6-4-14.4-5.9 75.4-43.4 126.2-124.8 126.2-218C603.4 112.6 490.9 0 352 0S100.6 112.6 100.6 251.4c0 93.2 50.7 174.6 126.1 218-47.2 17.9-90.3 46.1-126.5 83.2C35.6 618.7 0 706.1 0 798.5l0 169.7C0 999 25 1024 55.7 1024l592.5 0c30.7 0 55.7-25 55.7-55.7l0-12.8C704 937.8 689.7 923.5 672 923.5zM164.6 251.4c0-50.1 19.5-97.1 54.9-132.5C254.9 83.5 301.9 64 352 64c50.1 0 97.1 19.5 132.5 54.9 35.4 35.4 54.9 82.5 54.9 132.5 0 50.1-19.5 97.1-54.9 132.5-35.4 35.4-82.5 54.9-132.5 54.9-50.1 0-97.1-19.5-132.5-54.9C184.1 348.6 164.6 301.5 164.6 251.4z" p-id="2450"></path></svg>
|
After Width: | Height: | Size: 2.7 KiB |
1
src/icons/svg/nested.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M.002 9.2c0 5.044 3.58 9.133 7.998 9.133 4.417 0 7.997-4.089 7.997-9.133 0-5.043-3.58-9.132-7.997-9.132S.002 4.157.002 9.2zM31.997.066h95.981V18.33H31.997V.066zm0 45.669c0 5.044 3.58 9.132 7.998 9.132 4.417 0 7.997-4.088 7.997-9.132 0-3.263-1.524-6.278-3.998-7.91-2.475-1.63-5.524-1.63-7.998 0-2.475 1.632-4 4.647-4 7.91zM63.992 36.6h63.986v18.265H63.992V36.6zm-31.995 82.2c0 5.043 3.58 9.132 7.998 9.132 4.417 0 7.997-4.089 7.997-9.132 0-5.044-3.58-9.133-7.997-9.133s-7.998 4.089-7.998 9.133zm31.995-9.131h63.986v18.265H63.992V109.67zm0-27.404c0 5.044 3.58 9.133 7.998 9.133 4.417 0 7.997-4.089 7.997-9.133 0-3.263-1.524-6.277-3.998-7.909-2.475-1.631-5.524-1.631-7.998 0-2.475 1.632-4 4.646-4 7.91zm31.995-9.13h31.991V91.4H95.987V73.135z"/></svg>
|
After Width: | Height: | Size: 821 B |
1
src/icons/svg/parking.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg class="icon" width="200px" height="198.26px" viewBox="0 0 1033 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"><path fill="#e6e6e6" d="M672 275.2C672 121.6 547.2 0 393.6 0H0v1008h140.8V550.4h252.8c153.6 0 278.4-124.8 278.4-275.2z m-300.8 131.2H140.8V140.8h230.4c67.2 3.2 128 64 128 131.2 3.2 67.2-60.8 131.2-128 134.4zM1017.6 761.6c-3.2-9.6-16-19.2-16-19.2L950.4 608c-9.6-25.6-41.6-38.4-54.4-38.4h-265.6c-12.8 0-44.8 12.8-54.4 38.4l-51.2 137.6s-12.8 9.6-16 19.2c-3.2 9.6-9.6 16-9.6 76.8 0 57.6 12.8 80 25.6 86.4l9.6 6.4v54.4c0 19.2 3.2 22.4 22.4 22.4h38.4c16 0 28.8 0 28.8-19.2v-54.4h284.8V992c0 19.2 12.8 19.2 28.8 19.2h38.4c16 0 22.4-6.4 22.4-22.4v-54.4l9.6-6.4c12.8-6.4 25.6-28.8 25.6-86.4-6.4-64-9.6-70.4-16-80z m-428.8-35.2c3.2-6.4 16-67.2 22.4-89.6 3.2-6.4 19.2-28.8 38.4-28.8h220.8c19.2 0 35.2 19.2 38.4 28.8 6.4 22.4 19.2 80 22.4 89.6 6.4 9.6-3.2 9.6-3.2 9.6H595.2s-6.4 0-6.4-9.6z m6.4 147.2c-22.4 0-41.6-19.2-41.6-41.6 0-22.4 19.2-41.6 41.6-41.6 22.4 0 41.6 19.2 41.6 41.6 0 25.6-19.2 41.6-41.6 41.6z m336 0c-22.4 0-41.6-19.2-41.6-41.6 0-22.4 19.2-41.6 41.6-41.6 22.4 0 41.6 19.2 41.6 41.6-3.2 25.6-19.2 41.6-41.6 41.6z" /></svg>
|
After Width: | Height: | Size: 1.3 KiB |
1
src/icons/svg/password.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M108.8 44.322H89.6v-5.36c0-9.04-3.308-24.163-25.6-24.163-23.145 0-25.6 16.881-25.6 24.162v5.361H19.2v-5.36C19.2 15.281 36.798 0 64 0c27.202 0 44.8 15.281 44.8 38.961v5.361zm-32 39.356c0-5.44-5.763-9.832-12.8-9.832-7.037 0-12.8 4.392-12.8 9.832 0 3.682 2.567 6.808 6.407 8.477v11.205c0 2.718 2.875 4.962 6.4 4.962 3.524 0 6.4-2.244 6.4-4.962V92.155c3.833-1.669 6.393-4.795 6.393-8.477zM128 64v49.201c0 8.158-8.645 14.799-19.2 14.799H19.2C8.651 128 0 121.359 0 113.201V64c0-8.153 8.645-14.799 19.2-14.799h89.6c10.555 0 19.2 6.646 19.2 14.799z"/></svg>
|
After Width: | Height: | Size: 623 B |
1
src/icons/svg/pinpaizhuanqu.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg class="icon" width="200px" height="200.00px" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"><path fill="#e6e6e6" d="M512 1011.3c-16.2 0-32.4-4.2-46.8-12.5L113.8 795.9C85 779.2 67 748.2 67 714.9V309.1c0-33.3 17.9-64.4 46.8-81L465.2 25.2c28.9-16.7 64.7-16.7 93.6 0l351.4 202.9c28.9 16.7 46.8 47.7 46.8 81V636c0 17.7-14.3 32-32 32s-32-14.3-32-32V309.1c0-10.5-5.7-20.3-14.8-25.6L526.8 80.6c-9.1-5.3-20.4-5.3-29.6 0L145.8 283.5c-9.1 5.3-14.8 15.1-14.8 25.6v405.7c0 10.5 5.7 20.3 14.8 25.6l351.4 202.9c9.1 5.3 20.4 5.3 29.6 0l320.7-185.2c15.3-8.8 34.9-3.6 43.7 11.7 8.8 15.3 3.6 34.9-11.7 43.7L558.8 998.8c-14.4 8.3-30.6 12.5-46.8 12.5z" /><path fill="#e6e6e6" d="M573.6 544.5c-5.8 0-11.6-1.4-17-4.3L511 515.8l-45.9 23.7c-12.3 6.3-26.8 5.2-37.9-3-11.1-8.2-16.5-21.7-14.1-35.3l9.1-50.9-36.7-36.4c-9.8-9.7-13.2-23.9-8.8-37s15.6-22.4 29.2-24.3l51.2-7 23.2-46.2c6.2-12.2 18.5-19.8 32.2-19.8h0.3c13.8 0.1 26.1 7.9 32.1 20.3l22.5 46.5 51.1 7.8c13.6 2.1 24.7 11.6 28.9 24.7 4.2 13.2 0.6 27.3-9.4 36.8l-37.3 35.8 8.4 51c2.2 13.6-3.4 27.1-14.6 35.1-6.2 4.6-13.5 6.9-20.9 6.9z m13.2-60.8z m-75.5-35.6c5.8 0 11.7 1.4 17 4.3l8.5 4.6-1.6-9.5c-1.9-11.7 2-23.6 10.6-31.8l7-6.7-9.6-1.5c-11.7-1.8-21.8-9.2-27-19.9l-4.2-8.7-4.3 8.6c-5.3 10.6-15.5 17.9-27.3 19.5l-9.6 1.3 6.9 6.8c8.4 8.3 12.2 20.3 10.1 31.9l-1.7 9.5 8.6-4.4c5.3-2.6 10.9-4 16.6-4z" /><path fill="#e6e6e6" d="M512 220c51.3 0 99.5 20 135.8 56.2S704 360.7 704 412c0 35.6-9.8 70.4-28.4 100.5l-20 32.5 19.1 33.1 54.3 94-12-0.6c-1.1-0.1-2.3-0.1-3.5-0.1-5.7 0-11.3 0.7-16.8 2.1-14.5 3.7-27.3 11.7-37 23.1l-4.8 5.6-3.4 6.6-5.7 11.2-49.5-85.7-20.9-36.3-41.6 4.7c-7.3 0.8-14.6 1.2-21.8 1.2s-14.5-0.4-21.8-1.2l-41.6-4.7-20.9 36.3-49.5 85.7-5.7-11.2-3.4-6.6-4.8-5.6c-9.7-11.4-22.5-19.4-37-23.1-5.4-1.4-11.1-2.1-16.8-2.1-1.1 0-2.3 0-3.5 0.1l-12 0.6 54.3-94 19.1-33.1-20-32.5C329.8 482.4 320 447.6 320 412c0-51.3 20-99.5 56.2-135.8S460.7 220 512 220m0-64c-141.4 0-256 114.6-256 256 0 49.2 13.9 95.1 37.9 134.1l-86 148.9c-10.4 18 0.2 40.8 20.6 44.5l81.7-4.1h0.3c0.3 0 0.6 0 0.9 0.1 1.6 0.4 3 1.3 4.1 2.6l37.6 73.3c6.1 7.1 14.5 10.6 22.8 10.6 10.2 0 20.3-5.1 26-15l81.2-140.6c9.5 1.1 19.1 1.6 28.9 1.6s19.4-0.6 28.9-1.6L622.1 807c5.7 9.9 15.8 15 26 15 8.4 0 16.8-3.5 22.8-10.6l37.6-73.3c1.1-1.3 2.5-2.2 4.1-2.6 0.3-0.1 0.6-0.1 0.9-0.1h0.3l81.7 4.1c20.4-3.7 31-26.5 20.6-44.5l-86-148.9c24-39 37.9-84.9 37.9-134.1 0-141.4-114.6-256-256-256z" /></svg>
|
After Width: | Height: | Size: 2.5 KiB |
1
src/icons/svg/table.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M.006.064h127.988v31.104H.006V.064zm0 38.016h38.396v41.472H.006V38.08zm0 48.384h38.396v41.472H.006V86.464zM44.802 38.08h38.396v41.472H44.802V38.08zm0 48.384h38.396v41.472H44.802V86.464zM89.598 38.08h38.396v41.472H89.598zm0 48.384h38.396v41.472H89.598z"/><path d="M.006.064h127.988v31.104H.006V.064zm0 38.016h38.396v41.472H.006V38.08zm0 48.384h38.396v41.472H.006V86.464zM44.802 38.08h38.396v41.472H44.802V38.08zm0 48.384h38.396v41.472H44.802V86.464zM89.598 38.08h38.396v41.472H89.598zm0 48.384h38.396v41.472H89.598z"/></svg>
|
After Width: | Height: | Size: 597 B |
1
src/icons/svg/tree.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M126.713 90.023c.858.985 1.287 2.134 1.287 3.447v29.553c0 1.423-.429 2.6-1.287 3.53-.858.93-1.907 1.395-3.146 1.395H97.824c-1.145 0-2.146-.465-3.004-1.395-.858-.93-1.287-2.107-1.287-3.53V93.47c0-.875.19-1.696.572-2.462.382-.766.906-1.368 1.573-1.806a3.84 3.84 0 0 1 2.146-.657h9.725V69.007a3.84 3.84 0 0 0-.43-1.806 3.569 3.569 0 0 0-1.143-1.313 2.714 2.714 0 0 0-1.573-.492h-36.47v23.149h9.725c1.144 0 2.145.492 3.004 1.478.858.985 1.287 2.134 1.287 3.447v29.553c0 .876-.191 1.696-.573 2.463-.38.766-.905 1.368-1.573 1.806a3.84 3.84 0 0 1-2.145.656H51.915a3.84 3.84 0 0 1-2.145-.656c-.668-.438-1.216-1.04-1.645-1.806a4.96 4.96 0 0 1-.644-2.463V93.47c0-1.313.43-2.462 1.288-3.447.858-.986 1.907-1.478 3.146-1.478h9.582v-23.15h-37.9c-.953 0-1.74.356-2.359 1.068-.62.711-.93 1.56-.93 2.544v19.538h9.726c1.239 0 2.264.492 3.074 1.478.81.985 1.216 2.134 1.216 3.447v29.553c0 1.423-.405 2.6-1.216 3.53-.81.93-1.835 1.395-3.074 1.395H4.29c-.476 0-.93-.082-1.358-.246a4.1 4.1 0 0 1-1.144-.657 4.658 4.658 0 0 1-.93-1.067 5.186 5.186 0 0 1-.643-1.395 5.566 5.566 0 0 1-.215-1.56V93.47c0-.437.048-.875.143-1.313a3.95 3.95 0 0 1 .429-1.15c.19-.328.429-.656.715-.984.286-.329.572-.602.858-.821.286-.22.62-.383 1.001-.493.382-.11.763-.164 1.144-.164h9.726V61.619c0-.985.31-1.833.93-2.544.619-.712 1.358-1.068 2.216-1.068h44.335V39.62h-9.582c-1.24 0-2.288-.492-3.146-1.477a5.09 5.09 0 0 1-1.287-3.448V5.14c0-1.423.429-2.627 1.287-3.612.858-.985 1.907-1.477 3.146-1.477h25.743c.763 0 1.478.246 2.145.739a5.17 5.17 0 0 1 1.573 1.888c.382.766.573 1.587.573 2.462v29.553c0 1.313-.43 2.463-1.287 3.448-.859.985-1.86 1.477-3.004 1.477h-9.725v18.389h42.762c.954 0 1.74.355 2.36 1.067.62.711.93 1.56.93 2.545v26.925h9.582c1.239 0 2.288.492 3.146 1.478z"/></svg>
|
After Width: | Height: | Size: 1.8 KiB |
1
src/icons/svg/user.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg width="130" height="130" xmlns="http://www.w3.org/2000/svg"><path d="M63.444 64.996c20.633 0 37.359-14.308 37.359-31.953 0-17.649-16.726-31.952-37.359-31.952-20.631 0-37.36 14.303-37.358 31.952 0 17.645 16.727 31.953 37.359 31.953zM80.57 75.65H49.434c-26.652 0-48.26 18.477-48.26 41.27v2.664c0 9.316 21.608 9.325 48.26 9.325H80.57c26.649 0 48.256-.344 48.256-9.325v-2.663c0-22.794-21.605-41.271-48.256-41.271z" stroke="#979797"/></svg>
|
After Width: | Height: | Size: 440 B |
22
src/icons/svgo.yml
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
# replace default config
|
||||||
|
|
||||||
|
# multipass: true
|
||||||
|
# full: true
|
||||||
|
|
||||||
|
plugins:
|
||||||
|
|
||||||
|
# - name
|
||||||
|
#
|
||||||
|
# or:
|
||||||
|
# - name: false
|
||||||
|
# - name: true
|
||||||
|
#
|
||||||
|
# or:
|
||||||
|
# - name:
|
||||||
|
# param1: 1
|
||||||
|
# param2: 2
|
||||||
|
|
||||||
|
- removeAttrs:
|
||||||
|
attrs:
|
||||||
|
- 'fill'
|
||||||
|
- 'fill-rule'
|
40
src/layout/components/AppMain.vue
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
<template>
|
||||||
|
<section class="app-main">
|
||||||
|
<transition name="fade-transform" mode="out-in">
|
||||||
|
<router-view :key="key" />
|
||||||
|
</transition>
|
||||||
|
</section>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'AppMain',
|
||||||
|
computed: {
|
||||||
|
key() {
|
||||||
|
return this.$route.path
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.app-main {
|
||||||
|
/*50 = navbar */
|
||||||
|
min-height: calc(100vh - 50px);
|
||||||
|
width: 100%;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.fixed-header+.app-main {
|
||||||
|
padding-top: 50px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
// fix css style bug in open el-dialog
|
||||||
|
.el-popup-parent--hidden {
|
||||||
|
.fixed-header {
|
||||||
|
padding-right: 15px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
167
src/layout/components/Navbar.vue
Normal file
@ -0,0 +1,167 @@
|
|||||||
|
<template>
|
||||||
|
<div class="navbar">
|
||||||
|
<hamburger
|
||||||
|
:is-active="sidebar.opened"
|
||||||
|
class="hamburger-container"
|
||||||
|
@toggleClick="toggleSideBar"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<breadcrumb class="breadcrumb-container" />
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="user_info"
|
||||||
|
style="
|
||||||
|
position: absolute;
|
||||||
|
height: 100%;
|
||||||
|
line-height: 50px;
|
||||||
|
right: 110px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
:src="avatar"
|
||||||
|
class="user-avatar"
|
||||||
|
style="width: 40px; height: 40px; border-radius: 50%"
|
||||||
|
@click="gotoUserCenter"
|
||||||
|
/>
|
||||||
|
<div style="margin-left: 15px; line-height: 50px">{{ userName }}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="right-menu">
|
||||||
|
<div class="logout" @click="logout">
|
||||||
|
<i class="el-icon-switch-button"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { mapGetters, mapState } from "vuex";
|
||||||
|
import Breadcrumb from "@/components/Breadcrumb";
|
||||||
|
import Hamburger from "@/components/Hamburger";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
Breadcrumb,
|
||||||
|
Hamburger,
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
avatar: "",
|
||||||
|
};
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.avatar =
|
||||||
|
this.$store.state.user.avatar != ""
|
||||||
|
? this.$store.state.user.avatar
|
||||||
|
: window.localStorage.getItem("avatar");
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
userName() {
|
||||||
|
return window.localStorage.getItem("userName");
|
||||||
|
},
|
||||||
|
...mapGetters(["sidebar", "avatar", "name"]),
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
gotoUserCenter() {
|
||||||
|
this.$router.push("/memberManage/memberList");
|
||||||
|
},
|
||||||
|
toggleSideBar() {
|
||||||
|
this.$store.dispatch("app/toggleSideBar");
|
||||||
|
},
|
||||||
|
logout() {
|
||||||
|
this.$confirm("是否退出登录?", "提示", {
|
||||||
|
confirmButtonText: "确定",
|
||||||
|
cancelButtonText: "取消",
|
||||||
|
type: "warning",
|
||||||
|
})
|
||||||
|
.then(async () => {
|
||||||
|
await this.$store.dispatch("user/logout");
|
||||||
|
this.$router.push(`/login?redirect=${this.$route.fullPath}`);
|
||||||
|
})
|
||||||
|
.catch(() => {});
|
||||||
|
return false;
|
||||||
|
// await this.$store.dispatch("user/logout");
|
||||||
|
// this.$router.push(`/login?redirect=${this.$route.fullPath}`);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.navbar {
|
||||||
|
height: 50px;
|
||||||
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
|
background: #fff;
|
||||||
|
box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08);
|
||||||
|
|
||||||
|
.hamburger-container {
|
||||||
|
line-height: 46px;
|
||||||
|
height: 100%;
|
||||||
|
float: left;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background 0.3s;
|
||||||
|
-webkit-tap-highlight-color: transparent;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: rgba(0, 0, 0, 0.025);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.breadcrumb-container {
|
||||||
|
float: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.right-menu {
|
||||||
|
float: right;
|
||||||
|
height: 100%;
|
||||||
|
line-height: 50px;
|
||||||
|
|
||||||
|
.logout {
|
||||||
|
width: 80px;
|
||||||
|
height: 50px;
|
||||||
|
border-left: 2px solid #eee;
|
||||||
|
box-sizing: border-box;
|
||||||
|
font-size: 25px;
|
||||||
|
text-align: center;
|
||||||
|
cursor: pointer;
|
||||||
|
&:hover {
|
||||||
|
color: #5cb6ff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.user_info {
|
||||||
|
.user-avatar {
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar-container {
|
||||||
|
margin-right: 30px;
|
||||||
|
|
||||||
|
.avatar-wrapper {
|
||||||
|
margin-top: 5px;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
.user-avatar {
|
||||||
|
cursor: pointer;
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-icon-caret-bottom {
|
||||||
|
cursor: pointer;
|
||||||
|
position: absolute;
|
||||||
|
right: -20px;
|
||||||
|
top: 25px;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
26
src/layout/components/Sidebar/FixiOSBug.js
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
export default {
|
||||||
|
computed: {
|
||||||
|
device() {
|
||||||
|
return this.$store.state.app.device
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
// In order to fix the click on menu on the ios device will trigger the mouseleave bug
|
||||||
|
// https://github.com/PanJiaChen/vue-element-admin/issues/1135
|
||||||
|
this.fixBugIniOS()
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
fixBugIniOS() {
|
||||||
|
const $subMenu = this.$refs.subMenu
|
||||||
|
if ($subMenu) {
|
||||||
|
const handleMouseleave = $subMenu.handleMouseleave
|
||||||
|
$subMenu.handleMouseleave = (e) => {
|
||||||
|
if (this.device === 'mobile') {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
handleMouseleave(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
41
src/layout/components/Sidebar/Item.vue
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'MenuItem',
|
||||||
|
functional: true,
|
||||||
|
props: {
|
||||||
|
icon: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
render(h, context) {
|
||||||
|
const { icon, title } = context.props
|
||||||
|
const vnodes = []
|
||||||
|
|
||||||
|
if (icon) {
|
||||||
|
if (icon.includes('el-icon')) {
|
||||||
|
vnodes.push(<i class={[icon, 'sub-el-icon']} />)
|
||||||
|
} else {
|
||||||
|
vnodes.push(<svg-icon icon-class={icon}/>)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (title) {
|
||||||
|
vnodes.push(<span slot='title'>{(title)}</span>)
|
||||||
|
}
|
||||||
|
return vnodes
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.sub-el-icon {
|
||||||
|
color: currentColor;
|
||||||
|
width: 1em;
|
||||||
|
height: 1em;
|
||||||
|
}
|
||||||
|
</style>
|
43
src/layout/components/Sidebar/Link.vue
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
<template>
|
||||||
|
<component :is="type" v-bind="linkProps(to)">
|
||||||
|
<slot />
|
||||||
|
</component>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { isExternal } from '@/utils/validate'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
to: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
isExternal() {
|
||||||
|
return isExternal(this.to)
|
||||||
|
},
|
||||||
|
type() {
|
||||||
|
if (this.isExternal) {
|
||||||
|
return 'a'
|
||||||
|
}
|
||||||
|
return 'router-link'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
linkProps(to) {
|
||||||
|
if (this.isExternal) {
|
||||||
|
return {
|
||||||
|
href: to,
|
||||||
|
target: '_blank',
|
||||||
|
rel: 'noopener'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
to: to
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
82
src/layout/components/Sidebar/Logo.vue
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
<template>
|
||||||
|
<div class="sidebar-logo-container" :class="{'collapse':collapse}">
|
||||||
|
<transition name="sidebarLogoFade">
|
||||||
|
<router-link v-if="collapse" key="collapse" class="sidebar-logo-link" to="/">
|
||||||
|
<img v-if="logo" :src="logo" class="sidebar-logo">
|
||||||
|
<h1 v-else class="sidebar-title">{{ title }} </h1>
|
||||||
|
</router-link>
|
||||||
|
<router-link v-else key="expand" class="sidebar-logo-link" to="/">
|
||||||
|
<img v-if="logo" :src="logo" class="sidebar-logo">
|
||||||
|
<h1 class="sidebar-title">{{ title }} </h1>
|
||||||
|
</router-link>
|
||||||
|
</transition>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'SidebarLogo',
|
||||||
|
props: {
|
||||||
|
collapse: {
|
||||||
|
type: Boolean,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
title: 'Vue Admin Template',
|
||||||
|
logo: 'https://wpimg.wallstcn.com/69a1c46c-eb1c-4b46-8bd4-e9e686ef5251.png'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.sidebarLogoFade-enter-active {
|
||||||
|
transition: opacity 1.5s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebarLogoFade-enter,
|
||||||
|
.sidebarLogoFade-leave-to {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-logo-container {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
height: 50px;
|
||||||
|
line-height: 50px;
|
||||||
|
background: #2b2f3a;
|
||||||
|
text-align: center;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
& .sidebar-logo-link {
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
& .sidebar-logo {
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
vertical-align: middle;
|
||||||
|
margin-right: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
& .sidebar-title {
|
||||||
|
display: inline-block;
|
||||||
|
margin: 0;
|
||||||
|
color: #fff;
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 50px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-family: Avenir, Helvetica Neue, Arial, Helvetica, sans-serif;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.collapse {
|
||||||
|
.sidebar-logo {
|
||||||
|
margin-right: 0px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
95
src/layout/components/Sidebar/SidebarItem.vue
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
<template>
|
||||||
|
<div v-if="!item.hidden">
|
||||||
|
<template v-if="hasOneShowingChild(item.children,item) && (!onlyOneChild.children||onlyOneChild.noShowingChildren)&&!item.alwaysShow">
|
||||||
|
<app-link v-if="onlyOneChild.meta" :to="resolvePath(onlyOneChild.path)">
|
||||||
|
<el-menu-item :index="resolvePath(onlyOneChild.path)" :class="{'submenu-title-noDropdown':!isNest}">
|
||||||
|
<item :icon="onlyOneChild.meta.icon||(item.meta&&item.meta.icon)" :title="onlyOneChild.meta.title" />
|
||||||
|
</el-menu-item>
|
||||||
|
</app-link>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<el-submenu v-else ref="subMenu" :index="resolvePath(item.path)" popper-append-to-body>
|
||||||
|
<template slot="title">
|
||||||
|
<item v-if="item.meta" :icon="item.meta && item.meta.icon" :title="item.meta.title" />
|
||||||
|
</template>
|
||||||
|
<sidebar-item
|
||||||
|
v-for="child in item.children"
|
||||||
|
:key="child.path"
|
||||||
|
:is-nest="true"
|
||||||
|
:item="child"
|
||||||
|
:base-path="resolvePath(child.path)"
|
||||||
|
class="nest-menu"
|
||||||
|
/>
|
||||||
|
</el-submenu>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import path from 'path'
|
||||||
|
import { isExternal } from '@/utils/validate'
|
||||||
|
import Item from './Item'
|
||||||
|
import AppLink from './Link'
|
||||||
|
import FixiOSBug from './FixiOSBug'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'SidebarItem',
|
||||||
|
components: { Item, AppLink },
|
||||||
|
mixins: [FixiOSBug],
|
||||||
|
props: {
|
||||||
|
// route object
|
||||||
|
item: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
isNest: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
basePath: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
// To fix https://github.com/PanJiaChen/vue-admin-template/issues/237
|
||||||
|
// TODO: refactor with render function
|
||||||
|
this.onlyOneChild = null
|
||||||
|
return {}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
hasOneShowingChild(children = [], parent) {
|
||||||
|
const showingChildren = children.filter(item => {
|
||||||
|
if (item.hidden) {
|
||||||
|
return false
|
||||||
|
} else {
|
||||||
|
// Temp set(will be used if only has one showing child)
|
||||||
|
this.onlyOneChild = item
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// When there is only one child router, the child router is displayed by default
|
||||||
|
if (showingChildren.length === 1) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show parent if there are no child router to display
|
||||||
|
if (showingChildren.length === 0) {
|
||||||
|
this.onlyOneChild = { ... parent, path: '', noShowingChildren: true }
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
},
|
||||||
|
resolvePath(routePath) {
|
||||||
|
if (isExternal(routePath)) {
|
||||||
|
return routePath
|
||||||
|
}
|
||||||
|
if (isExternal(this.basePath)) {
|
||||||
|
return this.basePath
|
||||||
|
}
|
||||||
|
return path.resolve(this.basePath, routePath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
56
src/layout/components/Sidebar/index.vue
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
<template>
|
||||||
|
<div :class="{'has-logo':showLogo}">
|
||||||
|
<logo v-if="showLogo" :collapse="isCollapse" />
|
||||||
|
<el-scrollbar wrap-class="scrollbar-wrapper">
|
||||||
|
<el-menu
|
||||||
|
:default-active="activeMenu"
|
||||||
|
:collapse="isCollapse"
|
||||||
|
:background-color="variables.menuBg"
|
||||||
|
:text-color="variables.menuText"
|
||||||
|
:unique-opened="false"
|
||||||
|
:active-text-color="variables.menuActiveText"
|
||||||
|
:collapse-transition="false"
|
||||||
|
mode="vertical"
|
||||||
|
>
|
||||||
|
<sidebar-item v-for="route in routes" :key="route.path" :item="route" :base-path="route.path" />
|
||||||
|
</el-menu>
|
||||||
|
</el-scrollbar>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { mapGetters } from 'vuex'
|
||||||
|
import Logo from './Logo'
|
||||||
|
import SidebarItem from './SidebarItem'
|
||||||
|
import variables from '@/styles/variables.scss'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: { SidebarItem, Logo },
|
||||||
|
computed: {
|
||||||
|
...mapGetters([
|
||||||
|
'sidebar'
|
||||||
|
]),
|
||||||
|
routes() {
|
||||||
|
return this.$router.options.routes
|
||||||
|
},
|
||||||
|
activeMenu() {
|
||||||
|
const route = this.$route
|
||||||
|
const { meta, path } = route
|
||||||
|
// if set path, the sidebar will highlight the path you set
|
||||||
|
if (meta.activeMenu) {
|
||||||
|
return meta.activeMenu
|
||||||
|
}
|
||||||
|
return path
|
||||||
|
},
|
||||||
|
showLogo() {
|
||||||
|
return this.$store.state.settings.sidebarLogo
|
||||||
|
},
|
||||||
|
variables() {
|
||||||
|
return variables
|
||||||
|
},
|
||||||
|
isCollapse() {
|
||||||
|
return !this.sidebar.opened
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
3
src/layout/components/index.js
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export { default as Navbar } from './Navbar'
|
||||||
|
export { default as Sidebar } from './Sidebar'
|
||||||
|
export { default as AppMain } from './AppMain'
|
93
src/layout/index.vue
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
<template>
|
||||||
|
<div :class="classObj" class="app-wrapper">
|
||||||
|
<div v-if="device==='mobile'&&sidebar.opened" class="drawer-bg" @click="handleClickOutside" />
|
||||||
|
<sidebar class="sidebar-container" />
|
||||||
|
<div class="main-container">
|
||||||
|
<div :class="{'fixed-header':fixedHeader}">
|
||||||
|
<navbar />
|
||||||
|
</div>
|
||||||
|
<app-main />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { Navbar, Sidebar, AppMain } from './components'
|
||||||
|
import ResizeMixin from './mixin/ResizeHandler'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'Layout',
|
||||||
|
components: {
|
||||||
|
Navbar,
|
||||||
|
Sidebar,
|
||||||
|
AppMain
|
||||||
|
},
|
||||||
|
mixins: [ResizeMixin],
|
||||||
|
computed: {
|
||||||
|
sidebar() {
|
||||||
|
return this.$store.state.app.sidebar
|
||||||
|
},
|
||||||
|
device() {
|
||||||
|
return this.$store.state.app.device
|
||||||
|
},
|
||||||
|
fixedHeader() {
|
||||||
|
return this.$store.state.settings.fixedHeader
|
||||||
|
},
|
||||||
|
classObj() {
|
||||||
|
return {
|
||||||
|
hideSidebar: !this.sidebar.opened,
|
||||||
|
openSidebar: this.sidebar.opened,
|
||||||
|
withoutAnimation: this.sidebar.withoutAnimation,
|
||||||
|
mobile: this.device === 'mobile'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
handleClickOutside() {
|
||||||
|
this.$store.dispatch('app/closeSideBar', { withoutAnimation: false })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@import "~@/styles/mixin.scss";
|
||||||
|
@import "~@/styles/variables.scss";
|
||||||
|
|
||||||
|
.app-wrapper {
|
||||||
|
@include clearfix;
|
||||||
|
position: relative;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
&.mobile.openSidebar{
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.drawer-bg {
|
||||||
|
background: #000;
|
||||||
|
opacity: 0.3;
|
||||||
|
width: 100%;
|
||||||
|
top: 0;
|
||||||
|
height: 100%;
|
||||||
|
position: absolute;
|
||||||
|
z-index: 999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fixed-header {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
z-index: 9;
|
||||||
|
width: calc(100% - #{$sideBarWidth});
|
||||||
|
transition: width 0.28s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hideSidebar .fixed-header {
|
||||||
|
width: calc(100% - 54px)
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile .fixed-header {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
45
src/layout/mixin/ResizeHandler.js
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
import store from '@/store'
|
||||||
|
|
||||||
|
const { body } = document
|
||||||
|
const WIDTH = 992 // refer to Bootstrap's responsive design
|
||||||
|
|
||||||
|
export default {
|
||||||
|
watch: {
|
||||||
|
$route(route) {
|
||||||
|
if (this.device === 'mobile' && this.sidebar.opened) {
|
||||||
|
store.dispatch('app/closeSideBar', { withoutAnimation: false })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
beforeMount() {
|
||||||
|
window.addEventListener('resize', this.$_resizeHandler)
|
||||||
|
},
|
||||||
|
beforeDestroy() {
|
||||||
|
window.removeEventListener('resize', this.$_resizeHandler)
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
const isMobile = this.$_isMobile()
|
||||||
|
if (isMobile) {
|
||||||
|
store.dispatch('app/toggleDevice', 'mobile')
|
||||||
|
store.dispatch('app/closeSideBar', { withoutAnimation: true })
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
// use $_ for mixins properties
|
||||||
|
// https://vuejs.org/v2/style-guide/index.html#Private-property-names-essential
|
||||||
|
$_isMobile() {
|
||||||
|
const rect = body.getBoundingClientRect()
|
||||||
|
return rect.width - 1 < WIDTH
|
||||||
|
},
|
||||||
|
$_resizeHandler() {
|
||||||
|
if (!document.hidden) {
|
||||||
|
const isMobile = this.$_isMobile()
|
||||||
|
store.dispatch('app/toggleDevice', isMobile ? 'mobile' : 'desktop')
|
||||||
|
|
||||||
|
if (isMobile) {
|
||||||
|
store.dispatch('app/closeSideBar', { withoutAnimation: true })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
79
src/main.js
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
import Vue from 'vue'
|
||||||
|
|
||||||
|
import 'normalize.css/normalize.css' // A modern alternative to CSS resets
|
||||||
|
|
||||||
|
import ElementUI from 'element-ui'
|
||||||
|
import 'element-ui/lib/theme-chalk/index.css'
|
||||||
|
import locale from 'element-ui/lib/locale/lang/en' // lang i18n
|
||||||
|
|
||||||
|
import '@/styles/index.scss' // global css
|
||||||
|
|
||||||
|
import App from './App'
|
||||||
|
import store from './store'
|
||||||
|
import router from './router'
|
||||||
|
|
||||||
|
import '@/icons' // icon
|
||||||
|
import '@/permission' // permission control
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If you don't want to use mock-server
|
||||||
|
* you want to use MockJs for mock api
|
||||||
|
* you can execute: mockXHR()
|
||||||
|
*
|
||||||
|
* Currently MockJs will be used in the production environment,
|
||||||
|
* please remove it before going online ! ! !
|
||||||
|
*/
|
||||||
|
if (process.env.NODE_ENV === 'production') {
|
||||||
|
const { mockXHR } = require('../mock')
|
||||||
|
mockXHR()
|
||||||
|
}
|
||||||
|
|
||||||
|
// set ElementUI lang to EN
|
||||||
|
// Vue.use(ElementUI, { locale })
|
||||||
|
// 如果想要中文版 element-ui,按如下方式声明
|
||||||
|
Vue.use(ElementUI)
|
||||||
|
|
||||||
|
import API from '@/api'
|
||||||
|
Vue.prototype.$API = API
|
||||||
|
|
||||||
|
// 全局组件 分页器
|
||||||
|
import Pagination from '@/components/Pagination/Pagination'
|
||||||
|
Vue.component('Pagination', Pagination)
|
||||||
|
// 封装一个message
|
||||||
|
Vue.prototype.message = function(type = 'warning', message) {
|
||||||
|
return this.$message({
|
||||||
|
message: message,
|
||||||
|
type
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// 在 main.js 中注册全局过滤器
|
||||||
|
Vue.filter('formatDate', function(value) {
|
||||||
|
if (!value) return '';
|
||||||
|
|
||||||
|
// 解析日期
|
||||||
|
const date = new Date(value);
|
||||||
|
|
||||||
|
// 检查日期是否有效
|
||||||
|
if (isNaN(date)) return '';
|
||||||
|
|
||||||
|
// 格式化日期为 yyyy-MM-dd HH:mm:ss
|
||||||
|
return date.getFullYear() + '-' +
|
||||||
|
(date.getMonth() + 1).toString().padStart(2, '0') + '-' +
|
||||||
|
date.getDate().toString().padStart(2, '0') + ' ' +
|
||||||
|
date.getHours().toString().padStart(2, '0') + ':' +
|
||||||
|
date.getMinutes().toString().padStart(2, '0') + ':' +
|
||||||
|
date.getSeconds().toString().padStart(2, '0');
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
Vue.config.productionTip = false
|
||||||
|
|
||||||
|
new Vue({
|
||||||
|
el: '#app',
|
||||||
|
router,
|
||||||
|
store,
|
||||||
|
beforeCreate() {
|
||||||
|
Vue.prototype.$bus = this
|
||||||
|
},
|
||||||
|
render: h => h(App)
|
||||||
|
})
|
63
src/permission.js
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
import router from './router'
|
||||||
|
import store from './store'
|
||||||
|
import { Message } from 'element-ui'
|
||||||
|
import NProgress from 'nprogress' // progress bar
|
||||||
|
import 'nprogress/nprogress.css' // progress bar style
|
||||||
|
import { getToken } from '@/utils/auth' // get token from cookie
|
||||||
|
import getPageTitle from '@/utils/get-page-title'
|
||||||
|
|
||||||
|
NProgress.configure({ showSpinner: false }) // NProgress Configuration
|
||||||
|
// 配置白名单
|
||||||
|
const whiteList = ['/login', '/register'] // no redirect whitelist
|
||||||
|
|
||||||
|
router.beforeEach(async(to, from, next) => {
|
||||||
|
// start progress bar
|
||||||
|
NProgress.start()
|
||||||
|
|
||||||
|
// set page title
|
||||||
|
document.title = getPageTitle(to.meta.title)
|
||||||
|
|
||||||
|
// determine whether the user has logged in
|
||||||
|
const hasToken = getToken()
|
||||||
|
|
||||||
|
if (hasToken) {
|
||||||
|
if (to.path === '/login') {
|
||||||
|
// if is logged in, redirect to the home page
|
||||||
|
next({ path: '/' })
|
||||||
|
NProgress.done()
|
||||||
|
} else {
|
||||||
|
const hasGetUserInfo = store.getters.userId
|
||||||
|
if (hasGetUserInfo) {
|
||||||
|
next()
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
// get user info
|
||||||
|
await store.dispatch('user/getInfo')
|
||||||
|
next()
|
||||||
|
} catch (error) {
|
||||||
|
// remove token and go to login page to re-login
|
||||||
|
await store.dispatch('user/resetToken')
|
||||||
|
Message.error(error || 'Has Error')
|
||||||
|
next(`/login?redirect=${to.path}`)
|
||||||
|
NProgress.done()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
/* has no token*/
|
||||||
|
|
||||||
|
if (whiteList.indexOf(to.path) !== -1) {
|
||||||
|
// in the free login whitelist, go directly
|
||||||
|
next()
|
||||||
|
} else {
|
||||||
|
// other pages that do not have permission to access are redirected to the login page.
|
||||||
|
next(`/login?redirect=${to.path}`)
|
||||||
|
NProgress.done()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
router.afterEach(() => {
|
||||||
|
// finish progress bar
|
||||||
|
NProgress.done()
|
||||||
|
})
|
183
src/router/index.js
Normal file
@ -0,0 +1,183 @@
|
|||||||
|
import Vue from 'vue'
|
||||||
|
import Router from 'vue-router'
|
||||||
|
|
||||||
|
Vue.use(Router)
|
||||||
|
|
||||||
|
/* Layout */
|
||||||
|
import Layout from '@/layout'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Note: sub-menu only appear when route children.length >= 1
|
||||||
|
* Detail see: https://panjiachen.github.io/vue-element-admin-site/guide/essentials/router-and-nav.html
|
||||||
|
*
|
||||||
|
* hidden: true if set true, item will not show in the sidebar(default is false)
|
||||||
|
* alwaysShow: true if set true, will always show the root menu
|
||||||
|
* if not set alwaysShow, when item has more than one children route,
|
||||||
|
* it will becomes nested mode, otherwise not show the root menu
|
||||||
|
* redirect: noRedirect if set noRedirect will no redirect in the breadcrumb
|
||||||
|
* name:'router-name' the name is used by <keep-alive> (must set!!!)
|
||||||
|
* meta : {
|
||||||
|
roles: ['admin','editor'] control the page roles (you can set multiple roles)
|
||||||
|
title: 'title' the name show in sidebar and breadcrumb (recommend set)
|
||||||
|
icon: 'svg-name'/'el-icon-x' the icon show in the sidebar
|
||||||
|
breadcrumb: false if set false, the item will hidden in breadcrumb(default is true)
|
||||||
|
activeMenu: '/example/list' if set path, the sidebar will highlight the path you set
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* constantRoutes
|
||||||
|
* a base page that does not have permission requirements
|
||||||
|
* all roles can be accessed
|
||||||
|
*/
|
||||||
|
export const constantRoutes = [{
|
||||||
|
// 用户登录路由
|
||||||
|
path: '/login',
|
||||||
|
component: () =>
|
||||||
|
import ('@/views/login/index.vue'),
|
||||||
|
hidden: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// 用户注册路由
|
||||||
|
path: '/register',
|
||||||
|
name: 'register',
|
||||||
|
component: () =>
|
||||||
|
import ('@/views/register/index.vue'),
|
||||||
|
hidden: true
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
// 404路由
|
||||||
|
path: '/404',
|
||||||
|
component: () =>
|
||||||
|
import ('@/views/404'),
|
||||||
|
hidden: true
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
path: '/',
|
||||||
|
component: Layout,
|
||||||
|
redirect: '/parking/parkingList',
|
||||||
|
meta: { title: '首页', icon: 'dashboard' },
|
||||||
|
children: [{
|
||||||
|
path: 'dashboard',
|
||||||
|
name: 'Dashboard',
|
||||||
|
component: () =>
|
||||||
|
import ('@/views/dashboard/index'),
|
||||||
|
meta: { title: '控制台', icon: 'dashboard' },
|
||||||
|
children: [{
|
||||||
|
path: 'cars',
|
||||||
|
name: 'Car',
|
||||||
|
component: () =>
|
||||||
|
import ('@/views/Car/Car.vue'),
|
||||||
|
meta: { title: '租车地图', icon: 'car' }
|
||||||
|
}]
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
// 停车场
|
||||||
|
{
|
||||||
|
path: '/parking',
|
||||||
|
component: Layout,
|
||||||
|
redirect: '/parking/parkingList',
|
||||||
|
name: 'Parking',
|
||||||
|
meta: { title: '营业网店', icon: 'parking' },
|
||||||
|
|
||||||
|
children: [{
|
||||||
|
path: 'parkingList',
|
||||||
|
name: 'ParkingList',
|
||||||
|
component: () =>
|
||||||
|
import ('@/views/parking/parkingList'),
|
||||||
|
meta: { title: '网点列表' }
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
path: '/forum',
|
||||||
|
component: Layout,
|
||||||
|
redirect: '/forum/forum',
|
||||||
|
name: 'forum',
|
||||||
|
meta: { title: '交流论坛', icon: 'pinpaizhuanqu' },
|
||||||
|
alwaysShow: true,
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: '/forum',
|
||||||
|
name: 'forum',
|
||||||
|
component: () => import('@/views/Forum/forum'),
|
||||||
|
meta: { title: '交流论坛' }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/post/:id', // 动态参数 :id
|
||||||
|
name: 'post',
|
||||||
|
component: () => import('@/views/Forum/post'), // 加载帖子详情页面
|
||||||
|
meta: { title: '帖子详细' },
|
||||||
|
hidden: true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
// 车辆管理
|
||||||
|
{
|
||||||
|
path: '/carManage',
|
||||||
|
component: Layout,
|
||||||
|
redirect: '/carManage/carList',
|
||||||
|
name: 'CarManage',
|
||||||
|
meta: { title: '订单管理', icon: 'cheliangguanli' },
|
||||||
|
alwaysShow: true,
|
||||||
|
children: [{
|
||||||
|
path: 'carList',
|
||||||
|
name: 'CarList',
|
||||||
|
component: () =>
|
||||||
|
import ('@/views/carManage/carList'),
|
||||||
|
meta: { title: '订单列表' }
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/ai',
|
||||||
|
component: Layout,
|
||||||
|
redirect: '/ai/chat',
|
||||||
|
name: 'MemberManage',
|
||||||
|
meta: { title: '智能推荐', icon: 'el-icon-sort' },
|
||||||
|
alwaysShow: true,
|
||||||
|
children: [{
|
||||||
|
path: 'chat',
|
||||||
|
name: 'chat',
|
||||||
|
component: () =>
|
||||||
|
import ('@/views/Chat/index'),
|
||||||
|
meta: { title: '智能推荐' }
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
// 会员管理
|
||||||
|
{
|
||||||
|
path: '/memberManage',
|
||||||
|
component: Layout,
|
||||||
|
redirect: '/memberManage/memberList',
|
||||||
|
name: 'MemberManage',
|
||||||
|
meta: { title: '个人中心', icon: 'member' },
|
||||||
|
alwaysShow: true,
|
||||||
|
children: [{
|
||||||
|
path: 'memberList',
|
||||||
|
name: 'MemberList',
|
||||||
|
component: () =>
|
||||||
|
import ('@/views/memberManage/memberList'),
|
||||||
|
meta: { title: '我的资料' }
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
|
||||||
|
// 404 page must be placed at the end !!!
|
||||||
|
{ path: '*', redirect: '/404', hidden: true }
|
||||||
|
]
|
||||||
|
|
||||||
|
const createRouter = () => new Router({
|
||||||
|
// mode: 'history', // require service support
|
||||||
|
scrollBehavior: () => ({ y: 0 }),
|
||||||
|
routes: constantRoutes
|
||||||
|
})
|
||||||
|
|
||||||
|
const router = createRouter()
|
||||||
|
|
||||||
|
// Detail see: https://github.com/vuejs/vue-router/issues/1234#issuecomment-357941465
|
||||||
|
export function resetRouter() {
|
||||||
|
const newRouter = createRouter()
|
||||||
|
router.matcher = newRouter.matcher // reset router
|
||||||
|
}
|
||||||
|
|
||||||
|
export default router
|
16
src/settings.js
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
module.exports = {
|
||||||
|
|
||||||
|
title: '基于Spring Boot的停车场管理系统-门户端',
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {boolean} true | false
|
||||||
|
* @description Whether fix the header
|
||||||
|
*/
|
||||||
|
fixedHeader: false,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {boolean} true | false
|
||||||
|
* @description Whether show the logo in sidebar
|
||||||
|
*/
|
||||||
|
sidebarLogo: false
|
||||||
|
}
|
33
src/store/car.js
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
import { getCars } from '@/api/car'
|
||||||
|
import { Message } from 'element-ui'
|
||||||
|
|
||||||
|
|
||||||
|
const carsOptions = {
|
||||||
|
namespaced: true,
|
||||||
|
actions: {
|
||||||
|
// 获取停车场车辆列表数据
|
||||||
|
async getCarList(context, id) {
|
||||||
|
let result = await getCars(id)
|
||||||
|
if (result.status === 200) {
|
||||||
|
if (result.data.length === 0) {
|
||||||
|
Message({ type: 'warning', message: result.message })
|
||||||
|
}
|
||||||
|
context.commit('GETCARLIST', result.data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mutations: {
|
||||||
|
GETCARLIST(state, data) {
|
||||||
|
state.carList = data
|
||||||
|
},
|
||||||
|
// 清空车辆列表
|
||||||
|
CLEARCARLIST(state) {
|
||||||
|
state.carList = []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
state: {
|
||||||
|
carList: []
|
||||||
|
},
|
||||||
|
getters: {}
|
||||||
|
}
|
||||||
|
export default carsOptions
|
9
src/store/getters.js
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
const getters = {
|
||||||
|
sidebar: state => state.app.sidebar,
|
||||||
|
device: state => state.app.device,
|
||||||
|
token: state => state.user.token,
|
||||||
|
avatar: state => state.user.avatar,
|
||||||
|
name: state => state.user.name,
|
||||||
|
userId: state => window.localStorage.getItem('userId')
|
||||||
|
}
|
||||||
|
export default getters
|
22
src/store/index.js
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import Vue from 'vue'
|
||||||
|
import Vuex from 'vuex'
|
||||||
|
import getters from './getters'
|
||||||
|
import app from './modules/app'
|
||||||
|
import settings from './modules/settings'
|
||||||
|
import user from './modules/user'
|
||||||
|
import parkingOptions from "./parking";
|
||||||
|
import carsOptions from "./car";
|
||||||
|
Vue.use(Vuex)
|
||||||
|
|
||||||
|
const store = new Vuex.Store({
|
||||||
|
modules: {
|
||||||
|
app,
|
||||||
|
settings,
|
||||||
|
user,
|
||||||
|
parkingAbout: parkingOptions,
|
||||||
|
carsAbout: carsOptions,
|
||||||
|
},
|
||||||
|
getters
|
||||||
|
})
|
||||||
|
|
||||||
|
export default store
|
50
src/store/modules/app.js
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
import Cookies from 'js-cookie'
|
||||||
|
|
||||||
|
const state = {
|
||||||
|
sidebar: {
|
||||||
|
opened: Cookies.get('sidebarStatus') ? !!+Cookies.get('sidebarStatus') : true,
|
||||||
|
withoutAnimation: false
|
||||||
|
},
|
||||||
|
device: 'desktop',
|
||||||
|
uploadURL: 'http://127.0.0.1:3007/car/upload/img'
|
||||||
|
// uploadURL: 'http://www.hualearn.site/car/upload/img'
|
||||||
|
}
|
||||||
|
|
||||||
|
const mutations = {
|
||||||
|
TOGGLE_SIDEBAR: state => {
|
||||||
|
state.sidebar.opened = !state.sidebar.opened
|
||||||
|
state.sidebar.withoutAnimation = false
|
||||||
|
if (state.sidebar.opened) {
|
||||||
|
Cookies.set('sidebarStatus', 1)
|
||||||
|
} else {
|
||||||
|
Cookies.set('sidebarStatus', 0)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
CLOSE_SIDEBAR: (state, withoutAnimation) => {
|
||||||
|
Cookies.set('sidebarStatus', 0)
|
||||||
|
state.sidebar.opened = false
|
||||||
|
state.sidebar.withoutAnimation = withoutAnimation
|
||||||
|
},
|
||||||
|
TOGGLE_DEVICE: (state, device) => {
|
||||||
|
state.device = device
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const actions = {
|
||||||
|
toggleSideBar({ commit }) {
|
||||||
|
commit('TOGGLE_SIDEBAR')
|
||||||
|
},
|
||||||
|
closeSideBar({ commit }, { withoutAnimation }) {
|
||||||
|
commit('CLOSE_SIDEBAR', withoutAnimation)
|
||||||
|
},
|
||||||
|
toggleDevice({ commit }, device) {
|
||||||
|
commit('TOGGLE_DEVICE', device)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
namespaced: true,
|
||||||
|
state,
|
||||||
|
mutations,
|
||||||
|
actions
|
||||||
|
}
|
32
src/store/modules/settings.js
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
import defaultSettings from '@/settings'
|
||||||
|
|
||||||
|
const { showSettings, fixedHeader, sidebarLogo } = defaultSettings
|
||||||
|
|
||||||
|
const state = {
|
||||||
|
showSettings: showSettings,
|
||||||
|
fixedHeader: fixedHeader,
|
||||||
|
sidebarLogo: sidebarLogo
|
||||||
|
}
|
||||||
|
|
||||||
|
const mutations = {
|
||||||
|
CHANGE_SETTING: (state, { key, value }) => {
|
||||||
|
// eslint-disable-next-line no-prototype-builtins
|
||||||
|
if (state.hasOwnProperty(key)) {
|
||||||
|
state[key] = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const actions = {
|
||||||
|
changeSetting({ commit }, data) {
|
||||||
|
commit('CHANGE_SETTING', data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
namespaced: true,
|
||||||
|
state,
|
||||||
|
mutations,
|
||||||
|
actions
|
||||||
|
}
|
||||||
|
|
140
src/store/modules/user.js
Normal file
@ -0,0 +1,140 @@
|
|||||||
|
import { login, logout, getInfo, sendcode, register } from '@/api/user'
|
||||||
|
import { getToken, setToken, removeToken, getUsername } from '@/utils/auth'
|
||||||
|
import { resetRouter } from '@/router'
|
||||||
|
|
||||||
|
|
||||||
|
const getDefaultState = () => {
|
||||||
|
return {
|
||||||
|
token: getToken(),
|
||||||
|
name: '',
|
||||||
|
avatar: '',
|
||||||
|
userId: ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const state = getDefaultState()
|
||||||
|
|
||||||
|
const mutations = {
|
||||||
|
RESET_STATE: (state) => {
|
||||||
|
Object.assign(state, getDefaultState())
|
||||||
|
},
|
||||||
|
SET_TOKEN: (state, token) => {
|
||||||
|
state.token = token
|
||||||
|
},
|
||||||
|
SET_USERID: (state, userId) => {
|
||||||
|
state.userId = userId
|
||||||
|
},
|
||||||
|
SET_NAME: (state, userName) => {
|
||||||
|
state.name = userName
|
||||||
|
},
|
||||||
|
SET_AVATAR: (state, avatar) => {
|
||||||
|
state.avatar = avatar
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const actions = {
|
||||||
|
// 用户注册
|
||||||
|
async register({ commit }, loginForm) {
|
||||||
|
let data = new URLSearchParams()
|
||||||
|
data.append("Phone", loginForm.Phone.trim())
|
||||||
|
data.append("UserName", loginForm.UserName.trim())
|
||||||
|
data.append('PassWord', loginForm.PassWord.trim())
|
||||||
|
data.append('code', loginForm.verifycode.trim())
|
||||||
|
let result = await register(data);
|
||||||
|
if (result.status === 200) {
|
||||||
|
return result;
|
||||||
|
} else {
|
||||||
|
return Promise.reject(new Error('fail'))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async sendcode({ commit }, loginForm) {
|
||||||
|
let data = new URLSearchParams()
|
||||||
|
data.append("Phone", loginForm.Phone.trim())
|
||||||
|
let result = await sendcode(data);
|
||||||
|
if (result.status === 200) {
|
||||||
|
return result;
|
||||||
|
} else {
|
||||||
|
return Promise.reject(new Error('fail'))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// 用户登录
|
||||||
|
// user login
|
||||||
|
async login({ commit }, loginForm) {
|
||||||
|
let data = new URLSearchParams()
|
||||||
|
data.append("Phone", loginForm.Phone.trim())
|
||||||
|
data.append('PassWord', loginForm.PassWord.trim())
|
||||||
|
let result = await login(data);
|
||||||
|
if (result.status === 200) {
|
||||||
|
commit
|
||||||
|
commit('SET_TOKEN', result.token)
|
||||||
|
commit('SET_USERID', result.data)
|
||||||
|
setToken(result.token);
|
||||||
|
// 将userId存储到localstorage中
|
||||||
|
window.localStorage.setItem('userId', result.data)
|
||||||
|
return 'ok';
|
||||||
|
} else {
|
||||||
|
return Promise.reject(new Error('fail'))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
//获取用户信息
|
||||||
|
getInfo({ commit, state }) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
let data = new URLSearchParams()
|
||||||
|
data.append("userId", localStorage.getItem("userId"))
|
||||||
|
getInfo(data).then(response => {
|
||||||
|
const { data } = response
|
||||||
|
if (!data) {
|
||||||
|
return reject('Verification failed, please Login again.')
|
||||||
|
}
|
||||||
|
const { userName, user_pic,avatar } = data[0]
|
||||||
|
console.log(userName)
|
||||||
|
console.log(user_pic)
|
||||||
|
console.log(data)
|
||||||
|
commit('SET_NAME', userName)
|
||||||
|
// 将userId存储到localstorage中
|
||||||
|
window.localStorage.setItem('userName', userName)
|
||||||
|
console.log(avatar)
|
||||||
|
commit('SET_AVATAR', avatar)
|
||||||
|
window.localStorage.setItem('avatar', avatar);
|
||||||
|
resolve(data)
|
||||||
|
}).catch(error => {
|
||||||
|
reject(error)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
},
|
||||||
|
logout({ commit }) {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
// 清空localstorage
|
||||||
|
window.localStorage.clear();
|
||||||
|
// 移除token
|
||||||
|
removeToken();
|
||||||
|
// 重置路由
|
||||||
|
resetRouter();
|
||||||
|
// 清空vuex
|
||||||
|
commit('RESET_STATE');
|
||||||
|
|
||||||
|
// 等待异步操作完成后再跳转到登录页面
|
||||||
|
setTimeout(() => {
|
||||||
|
resolve();
|
||||||
|
next(`/login`);
|
||||||
|
}, 1000);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
// remove token
|
||||||
|
resetToken({ commit }) {
|
||||||
|
return new Promise(resolve => {
|
||||||
|
removeToken() // must remove token first
|
||||||
|
commit('RESET_STATE')
|
||||||
|
resolve()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
namespaced: true,
|
||||||
|
state,
|
||||||
|
mutations,
|
||||||
|
actions
|
||||||
|
}
|
25
src/store/parking.js
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import { getParking } from '@/api/parking'
|
||||||
|
|
||||||
|
|
||||||
|
const parkingOptions = {
|
||||||
|
namespaced: true,
|
||||||
|
actions: {
|
||||||
|
// 获取停车场数据
|
||||||
|
async getParking(context, value) {
|
||||||
|
let result = await getParking()
|
||||||
|
if (result.status === 200) {
|
||||||
|
context.commit('GETPARKING', result.data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mutations: {
|
||||||
|
GETPARKING(state, parking) {
|
||||||
|
state.parkingList = parking
|
||||||
|
}
|
||||||
|
},
|
||||||
|
state: {
|
||||||
|
parkingList: []
|
||||||
|
},
|
||||||
|
getters: {}
|
||||||
|
}
|
||||||
|
export default parkingOptions
|
107
src/styles/element-ui.scss
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
// cover some element-ui styles
|
||||||
|
|
||||||
|
.el-breadcrumb__inner,
|
||||||
|
.el-breadcrumb__inner a {
|
||||||
|
font-weight: 400 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-upload {
|
||||||
|
input[type="file"] {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-upload__input {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// to fixed https://github.com/ElemeFE/element/issues/2461
|
||||||
|
.el-dialog {
|
||||||
|
transform: none;
|
||||||
|
left: 0;
|
||||||
|
position: relative;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
// refine element ui upload
|
||||||
|
.upload-container {
|
||||||
|
.el-upload {
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
.el-upload-dragger {
|
||||||
|
width: 100%;
|
||||||
|
height: 200px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// dropdown
|
||||||
|
.el-dropdown-menu {
|
||||||
|
a {
|
||||||
|
display: block
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// to fix el-date-picker css style
|
||||||
|
.el-range-separator {
|
||||||
|
box-sizing: content-box;
|
||||||
|
}
|
||||||
|
ul, li { list-style: none; }
|
||||||
|
// 上传图片
|
||||||
|
.upload-image-wrap {
|
||||||
|
display: flex;
|
||||||
|
.upload-img {
|
||||||
|
position: relative;
|
||||||
|
background-color: #fbfdff;
|
||||||
|
border: 1px dashed #c0ccda;
|
||||||
|
border-radius: 6px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
width: 148px;
|
||||||
|
height: 148px;
|
||||||
|
cursor: pointer;
|
||||||
|
line-height: 146px;
|
||||||
|
vertical-align: top;
|
||||||
|
text-align: center;
|
||||||
|
img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
}
|
||||||
|
&:before {
|
||||||
|
content: "+";
|
||||||
|
font-size: 50px;
|
||||||
|
font-weight: 300;
|
||||||
|
color: #ccc;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.upload-img-list {
|
||||||
|
flex: 1;
|
||||||
|
padding-left: 30px;
|
||||||
|
li {
|
||||||
|
float: left;
|
||||||
|
width: 60px;
|
||||||
|
height: 60px;
|
||||||
|
margin: 0 10px;
|
||||||
|
img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-form-item__content {
|
||||||
|
z-index: 200 ;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-dialog__body {
|
||||||
|
padding-left: 100px;
|
||||||
|
}
|
||||||
|
.text {
|
||||||
|
.el-row {
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
}
|
65
src/styles/index.scss
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
@import './variables.scss';
|
||||||
|
@import './mixin.scss';
|
||||||
|
@import './transition.scss';
|
||||||
|
@import './element-ui.scss';
|
||||||
|
@import './sidebar.scss';
|
||||||
|
|
||||||
|
body {
|
||||||
|
height: 100%;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
text-rendering: optimizeLegibility;
|
||||||
|
font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB, Microsoft YaHei, Arial, sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
html {
|
||||||
|
height: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
#app {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
*,
|
||||||
|
*:before,
|
||||||
|
*:after {
|
||||||
|
box-sizing: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:focus,
|
||||||
|
a:active {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
a,
|
||||||
|
a:focus,
|
||||||
|
a:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
color: inherit;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
div:focus {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.clearfix {
|
||||||
|
&:after {
|
||||||
|
visibility: hidden;
|
||||||
|
display: block;
|
||||||
|
font-size: 0;
|
||||||
|
content: " ";
|
||||||
|
clear: both;
|
||||||
|
height: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// main-container global css
|
||||||
|
.app-container {
|
||||||
|
padding: 20px;
|
||||||
|
}
|
28
src/styles/mixin.scss
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
@mixin clearfix {
|
||||||
|
&:after {
|
||||||
|
content: "";
|
||||||
|
display: table;
|
||||||
|
clear: both;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@mixin scrollBar {
|
||||||
|
&::-webkit-scrollbar-track-piece {
|
||||||
|
background: #d3dce6;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::-webkit-scrollbar {
|
||||||
|
width: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::-webkit-scrollbar-thumb {
|
||||||
|
background: #99a9bf;
|
||||||
|
border-radius: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@mixin relative {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
226
src/styles/sidebar.scss
Normal file
@ -0,0 +1,226 @@
|
|||||||
|
#app {
|
||||||
|
|
||||||
|
.main-container {
|
||||||
|
min-height: 100%;
|
||||||
|
transition: margin-left .28s;
|
||||||
|
margin-left: $sideBarWidth;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-container {
|
||||||
|
transition: width 0.28s;
|
||||||
|
width: $sideBarWidth !important;
|
||||||
|
background-color: $menuBg;
|
||||||
|
height: 100%;
|
||||||
|
position: fixed;
|
||||||
|
font-size: 0px;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
z-index: 1001;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
// reset element-ui css
|
||||||
|
.horizontal-collapse-transition {
|
||||||
|
transition: 0s width ease-in-out, 0s padding-left ease-in-out, 0s padding-right ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scrollbar-wrapper {
|
||||||
|
overflow-x: hidden !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-scrollbar__bar.is-vertical {
|
||||||
|
right: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-scrollbar {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.has-logo {
|
||||||
|
.el-scrollbar {
|
||||||
|
height: calc(100% - 50px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.is-horizontal {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
display: inline-block;
|
||||||
|
width: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.svg-icon {
|
||||||
|
margin-right: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sub-el-icon {
|
||||||
|
margin-right: 12px;
|
||||||
|
margin-left: -2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-menu {
|
||||||
|
border: none;
|
||||||
|
height: 100%;
|
||||||
|
width: 100% !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
// menu hover
|
||||||
|
.submenu-title-noDropdown,
|
||||||
|
.el-submenu__title {
|
||||||
|
&:hover {
|
||||||
|
background-color: $menuHover !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.is-active>.el-submenu__title {
|
||||||
|
color: $subMenuActiveText !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
& .nest-menu .el-submenu>.el-submenu__title,
|
||||||
|
& .el-submenu .el-menu-item {
|
||||||
|
min-width: $sideBarWidth !important;
|
||||||
|
background-color: $subMenuBg !important;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: $subMenuHover !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.hideSidebar {
|
||||||
|
.sidebar-container {
|
||||||
|
width: 54px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main-container {
|
||||||
|
margin-left: 54px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.submenu-title-noDropdown {
|
||||||
|
padding: 0 !important;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
.el-tooltip {
|
||||||
|
padding: 0 !important;
|
||||||
|
|
||||||
|
.svg-icon {
|
||||||
|
margin-left: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sub-el-icon {
|
||||||
|
margin-left: 19px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-submenu {
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
&>.el-submenu__title {
|
||||||
|
padding: 0 !important;
|
||||||
|
|
||||||
|
.svg-icon {
|
||||||
|
margin-left: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sub-el-icon {
|
||||||
|
margin-left: 19px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-submenu__icon-arrow {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-menu--collapse {
|
||||||
|
.el-submenu {
|
||||||
|
&>.el-submenu__title {
|
||||||
|
&>span {
|
||||||
|
height: 0;
|
||||||
|
width: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
visibility: hidden;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-menu--collapse .el-menu .el-submenu {
|
||||||
|
min-width: $sideBarWidth !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
// mobile responsive
|
||||||
|
.mobile {
|
||||||
|
.main-container {
|
||||||
|
margin-left: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-container {
|
||||||
|
transition: transform .28s;
|
||||||
|
width: $sideBarWidth !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.hideSidebar {
|
||||||
|
.sidebar-container {
|
||||||
|
pointer-events: none;
|
||||||
|
transition-duration: 0.3s;
|
||||||
|
transform: translate3d(-$sideBarWidth, 0, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.withoutAnimation {
|
||||||
|
|
||||||
|
.main-container,
|
||||||
|
.sidebar-container {
|
||||||
|
transition: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// when menu collapsed
|
||||||
|
.el-menu--vertical {
|
||||||
|
&>.el-menu {
|
||||||
|
.svg-icon {
|
||||||
|
margin-right: 16px;
|
||||||
|
}
|
||||||
|
.sub-el-icon {
|
||||||
|
margin-right: 12px;
|
||||||
|
margin-left: -2px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.nest-menu .el-submenu>.el-submenu__title,
|
||||||
|
.el-menu-item {
|
||||||
|
&:hover {
|
||||||
|
// you can use $subMenuHover
|
||||||
|
background-color: $menuHover !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// the scroll bar appears when the subMenu is too long
|
||||||
|
>.el-menu--popup {
|
||||||
|
max-height: 100vh;
|
||||||
|
overflow-y: auto;
|
||||||
|
|
||||||
|
&::-webkit-scrollbar-track-piece {
|
||||||
|
background: #d3dce6;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::-webkit-scrollbar {
|
||||||
|
width: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::-webkit-scrollbar-thumb {
|
||||||
|
background: #99a9bf;
|
||||||
|
border-radius: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
48
src/styles/transition.scss
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
// global transition css
|
||||||
|
|
||||||
|
/* fade */
|
||||||
|
.fade-enter-active,
|
||||||
|
.fade-leave-active {
|
||||||
|
transition: opacity 0.28s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fade-enter,
|
||||||
|
.fade-leave-active {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* fade-transform */
|
||||||
|
.fade-transform-leave-active,
|
||||||
|
.fade-transform-enter-active {
|
||||||
|
transition: all .5s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fade-transform-enter {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateX(-30px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.fade-transform-leave-to {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateX(30px);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* breadcrumb transition */
|
||||||
|
.breadcrumb-enter-active,
|
||||||
|
.breadcrumb-leave-active {
|
||||||
|
transition: all .5s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.breadcrumb-enter,
|
||||||
|
.breadcrumb-leave-active {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateX(20px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.breadcrumb-move {
|
||||||
|
transition: all .5s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.breadcrumb-leave-active {
|
||||||
|
position: absolute;
|
||||||
|
}
|
25
src/styles/variables.scss
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
// sidebar
|
||||||
|
$menuText:#bfcbd9;
|
||||||
|
$menuActiveText:#409EFF;
|
||||||
|
$subMenuActiveText:#f4f4f5; //https://github.com/ElemeFE/element/issues/12951
|
||||||
|
|
||||||
|
$menuBg:#304156;
|
||||||
|
$menuHover:#263445;
|
||||||
|
|
||||||
|
$subMenuBg:#1f2d3d;
|
||||||
|
$subMenuHover:#001528;
|
||||||
|
|
||||||
|
$sideBarWidth: 210px;
|
||||||
|
|
||||||
|
// the :export directive is the magic sauce for webpack
|
||||||
|
// https://www.bluematador.com/blog/how-to-share-variables-between-js-and-sass
|
||||||
|
:export {
|
||||||
|
menuText: $menuText;
|
||||||
|
menuActiveText: $menuActiveText;
|
||||||
|
subMenuActiveText: $subMenuActiveText;
|
||||||
|
menuBg: $menuBg;
|
||||||
|
menuHover: $menuHover;
|
||||||
|
subMenuBg: $subMenuBg;
|
||||||
|
subMenuHover: $subMenuHover;
|
||||||
|
sideBarWidth: $sideBarWidth;
|
||||||
|
}
|
23
src/utils/auth.js
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import Cookies from 'js-cookie'
|
||||||
|
|
||||||
|
const TokenKey = 'vue_admin_template_token'
|
||||||
|
const userNameKey = "username"
|
||||||
|
|
||||||
|
export function getToken() {
|
||||||
|
return Cookies.get(TokenKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setToken(token) {
|
||||||
|
return Cookies.set(TokenKey, token)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function removeToken() {
|
||||||
|
return Cookies.remove(TokenKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getUsername() {
|
||||||
|
return Cookies.get(userNameKey);
|
||||||
|
}
|
||||||
|
export function setUsername(value){
|
||||||
|
return Cookies.set(userNameKey, value);
|
||||||
|
}
|
10
src/utils/get-page-title.js
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import defaultSettings from '@/settings'
|
||||||
|
|
||||||
|
const title = defaultSettings.title || 'Vue Admin Template'
|
||||||
|
|
||||||
|
export default function getPageTitle(pageTitle) {
|
||||||
|
if (pageTitle) {
|
||||||
|
return `${pageTitle} - ${title}`
|
||||||
|
}
|
||||||
|
return `${title}`
|
||||||
|
}
|
118
src/utils/index.js
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
/**
|
||||||
|
* Created by PanJiaChen on 16/11/18.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse the time to string
|
||||||
|
* @param {(Object|string|number)} time
|
||||||
|
* @param {string} cFormat
|
||||||
|
* @returns {string | null}
|
||||||
|
*/
|
||||||
|
// decodeURI
|
||||||
|
export function parseTime(time, cFormat) {
|
||||||
|
if (arguments.length === 0 || !time) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
const format = cFormat || '{y}-{m}-{d} {h}:{i}:{s}'
|
||||||
|
let date
|
||||||
|
if (typeof time === 'object') {
|
||||||
|
date = time
|
||||||
|
} else {
|
||||||
|
if ((typeof time === 'string')) {
|
||||||
|
if ((/^[0-9]+$/.test(time))) {
|
||||||
|
// support "1548221490638"
|
||||||
|
time = parseInt(time)
|
||||||
|
} else {
|
||||||
|
// support safari
|
||||||
|
// https://stackoverflow.com/questions/4310953/invalid-date-in-safari
|
||||||
|
time = time.replace(new RegExp(/-/gm), '/')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((typeof time === 'number') && (time.toString().length === 10)) {
|
||||||
|
time = time * 1000
|
||||||
|
}
|
||||||
|
date = new Date(time)
|
||||||
|
}
|
||||||
|
const formatObj = {
|
||||||
|
y: date.getFullYear(),
|
||||||
|
m: date.getMonth() + 1,
|
||||||
|
d: date.getDate(),
|
||||||
|
h: date.getHours(),
|
||||||
|
i: date.getMinutes(),
|
||||||
|
s: date.getSeconds(),
|
||||||
|
a: date.getDay()
|
||||||
|
}
|
||||||
|
const time_str = format.replace(/{([ymdhisa])+}/g, (result, key) => {
|
||||||
|
const value = formatObj[key]
|
||||||
|
// Note: getDay() returns 0 on Sunday
|
||||||
|
if (key === 'a') { return ['日', '一', '二', '三', '四', '五', '六'][value ] }
|
||||||
|
return value.toString().padStart(2, '0')
|
||||||
|
})
|
||||||
|
return time_str
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {number} time
|
||||||
|
* @param {string} option
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
export function formatTime(time, option) {
|
||||||
|
if (('' + time).length === 10) {
|
||||||
|
time = parseInt(time) * 1000
|
||||||
|
} else {
|
||||||
|
time = +time
|
||||||
|
}
|
||||||
|
const d = new Date(time)
|
||||||
|
const now = Date.now()
|
||||||
|
|
||||||
|
const diff = (now - d) / 1000
|
||||||
|
|
||||||
|
if (diff < 30) {
|
||||||
|
return '刚刚'
|
||||||
|
} else if (diff < 3600) {
|
||||||
|
// less 1 hour
|
||||||
|
return Math.ceil(diff / 60) + '分钟前'
|
||||||
|
} else if (diff < 3600 * 24) {
|
||||||
|
return Math.ceil(diff / 3600) + '小时前'
|
||||||
|
} else if (diff < 3600 * 24 * 2) {
|
||||||
|
return '1天前'
|
||||||
|
}
|
||||||
|
if (option) {
|
||||||
|
return parseTime(time, option)
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
d.getMonth() +
|
||||||
|
1 +
|
||||||
|
'月' +
|
||||||
|
d.getDate() +
|
||||||
|
'日' +
|
||||||
|
d.getHours() +
|
||||||
|
'时' +
|
||||||
|
d.getMinutes() +
|
||||||
|
'分'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} url
|
||||||
|
* @returns {Object}
|
||||||
|
*/
|
||||||
|
export function param2Obj(url) {
|
||||||
|
const search = decodeURIComponent(url.split('?')[1]).replace(/\+/g, ' ')
|
||||||
|
if (!search) {
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
const obj = {}
|
||||||
|
const searchArr = search.split('&')
|
||||||
|
searchArr.forEach(v => {
|
||||||
|
const index = v.indexOf('=')
|
||||||
|
if (index !== -1) {
|
||||||
|
const name = v.substring(0, index)
|
||||||
|
const val = v.substring(index + 1, v.length)
|
||||||
|
obj[name] = val
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return obj
|
||||||
|
}
|
147
src/utils/request.js
Normal file
@ -0,0 +1,147 @@
|
|||||||
|
import axios from 'axios'
|
||||||
|
import { MessageBox, Message } from 'element-ui'
|
||||||
|
import store from '@/store'
|
||||||
|
import { getToken } from '@/utils/auth'
|
||||||
|
import router from "@/router"; //看具体项目封装的路由文件修改
|
||||||
|
|
||||||
|
// create an axios instance
|
||||||
|
const service = axios.create({
|
||||||
|
// baseURL: process.env.VUE_APP_BASE_API,
|
||||||
|
baseURL: 'http://127.0.0.1:8898/api',
|
||||||
|
timeout: 5000
|
||||||
|
})
|
||||||
|
|
||||||
|
//跳转登陆页面
|
||||||
|
const toLogin = () => {
|
||||||
|
// 执行退出操作
|
||||||
|
store.dispatch('user/logout').then(() => {
|
||||||
|
// 返回登录页面
|
||||||
|
router.push('/login')
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
//封装状态码错误处理函数
|
||||||
|
const errorHandle = status => {
|
||||||
|
switch (status) {
|
||||||
|
//登录不成功时跳转到登录页面
|
||||||
|
case 401:
|
||||||
|
console.log("认证失败,未登录或无权限");
|
||||||
|
toLogin();
|
||||||
|
break;
|
||||||
|
case 403:
|
||||||
|
//token过期了,清除token存储
|
||||||
|
localStorage.removeItem("token");
|
||||||
|
console.log("token校验失败");
|
||||||
|
toLogin();
|
||||||
|
break;
|
||||||
|
case 404:
|
||||||
|
console.log("请求的资源不存在");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
console.log("请求出错,状态码为:" + status);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// request interceptor
|
||||||
|
service.interceptors.request.use(
|
||||||
|
config => {
|
||||||
|
// do something before request is sent
|
||||||
|
|
||||||
|
if (store.getters.token) {
|
||||||
|
// let each request carry token
|
||||||
|
// ['X-Token'] is a custom headers key
|
||||||
|
// please modify it according to the actual situation
|
||||||
|
config.headers['Authorization'] = getToken(); // 携带token
|
||||||
|
config.headers['Content-Type'] = 'application/x-www-form-urlencoded'
|
||||||
|
// config.headers['Username'] = getUsername(); // 携带token
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
return config
|
||||||
|
},
|
||||||
|
error => {
|
||||||
|
// do something with request error
|
||||||
|
console.log(error) // for debug
|
||||||
|
return Promise.reject(error)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// response interceptor
|
||||||
|
service.interceptors.response.use(
|
||||||
|
/**
|
||||||
|
* If you want to get http information such as headers or status
|
||||||
|
* Please return response => response
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine the request status by custom code
|
||||||
|
* Here is just an example
|
||||||
|
* You can also judge the status by HTTP Status Code
|
||||||
|
*/
|
||||||
|
response => {
|
||||||
|
const res = response.data
|
||||||
|
console.log(res.status); // 添加此行
|
||||||
|
if (res.status == 401) {
|
||||||
|
toLogin()
|
||||||
|
}
|
||||||
|
// if the custom code is not 20000, it is judged as an error.
|
||||||
|
if (res.status !== 20000 && res.status !== 200 && res.status !== 201) {
|
||||||
|
Message({
|
||||||
|
message: res.message || 'Error',
|
||||||
|
type: 'error',
|
||||||
|
duration: 5 * 1000
|
||||||
|
})
|
||||||
|
|
||||||
|
// 50008: Illegal token; 50012: Other clients logged in; 50014: Token expired;
|
||||||
|
if (res.status === 50008 || res.status === 50012 || res.status === 50014) {
|
||||||
|
// to re-login
|
||||||
|
MessageBox.confirm('You have been logged out, you can cancel to stay on this page, or log in again', 'Confirm logout', {
|
||||||
|
confirmButtonText: 'Re-Login',
|
||||||
|
cancelButtonText: 'Cancel',
|
||||||
|
type: 'warning'
|
||||||
|
}).then(() => {
|
||||||
|
store.dispatch('user/resetToken').then(() => {
|
||||||
|
location.reload()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return Promise.reject(new Error(res.message || 'Error'))
|
||||||
|
} else {
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
// const data = response.data;
|
||||||
|
// // console.log(data);
|
||||||
|
// // 不为0,即接口异常时
|
||||||
|
// if (data.resCode !== 0) {
|
||||||
|
// Message.error(data.message);
|
||||||
|
// return Promise.reject(data);
|
||||||
|
// } else {
|
||||||
|
// return data; // return Promise.resolve(data);
|
||||||
|
// }
|
||||||
|
},
|
||||||
|
error => {
|
||||||
|
// let { response } = error;
|
||||||
|
//判断是否为重复请求而出错
|
||||||
|
// if (isCancel(error)) console.log("请求失败,原因是" + error.message);
|
||||||
|
if (error.response) {
|
||||||
|
errorHandle(error.response.status);
|
||||||
|
|
||||||
|
}
|
||||||
|
//判断客户端有无联网
|
||||||
|
if (!window.navigator.onLine) {
|
||||||
|
//断网处理:跳转断网页面/提示网络错误等等
|
||||||
|
alert("请检查网络是否连接");
|
||||||
|
}
|
||||||
|
//出错中断promise链
|
||||||
|
// return new Promise(() => {});
|
||||||
|
console.log('err' + error) // for debug
|
||||||
|
Message({
|
||||||
|
message: error.message,
|
||||||
|
type: 'error',
|
||||||
|
duration: 5 * 1000
|
||||||
|
})
|
||||||
|
return Promise.reject(error)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
export default service
|
20
src/utils/validate.js
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
/**
|
||||||
|
* Created by PanJiaChen on 16/11/18.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} path
|
||||||
|
* @returns {Boolean}
|
||||||
|
*/
|
||||||
|
export function isExternal(path) {
|
||||||
|
return /^(https?:|mailto:|tel:)/.test(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} str
|
||||||
|
* @returns {Boolean}
|
||||||
|
*/
|
||||||
|
export function validUsername(str) {
|
||||||
|
const valid_map = ['admin', 'editor']
|
||||||
|
return valid_map.indexOf(str.trim()) >= 0
|
||||||
|
}
|