- 新增ISV用户平台
- 新增门户网站(portal) - 新增`C++`,`Rust`语言SDK
14
sop-website/sop-website-vue/.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
|
14
sop-website/sop-website-vue/.env.development
Normal file
@@ -0,0 +1,14 @@
|
||||
# just a flag
|
||||
ENV = 'development'
|
||||
|
||||
# base api
|
||||
VUE_APP_BASE_API = 'http://localhost:8083'
|
||||
|
||||
# vue-cli uses the VUE_CLI_BABEL_TRANSPILE_MODULES environment variable,
|
||||
# to control whether the babel-plugin-dynamic-import-node plugin is enabled.
|
||||
# It only does one thing by converting all import() to require().
|
||||
# This configuration can significantly increase the speed of hot updates,
|
||||
# when you have a large number of pages.
|
||||
# Detail: https://github.com/vuejs/vue-cli/blob/dev/packages/@vue/babel-preset-app/index.js
|
||||
|
||||
VUE_CLI_BABEL_TRANSPILE_MODULES = true
|
6
sop-website/sop-website-vue/.env.production
Normal file
@@ -0,0 +1,6 @@
|
||||
# just a flag
|
||||
ENV = 'production'
|
||||
|
||||
# base api
|
||||
VUE_APP_BASE_API = 'http://localhost:8083'
|
||||
|
8
sop-website/sop-website-vue/.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
sop-website/sop-website-vue/.eslintignore
Normal file
@@ -0,0 +1,4 @@
|
||||
build/*.js
|
||||
src/assets
|
||||
public
|
||||
dist
|
198
sop-website/sop-website-vue/.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
sop-website/sop-website-vue/.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
|
8
sop-website/sop-website-vue/.postcssrc.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': {}
|
||||
}
|
||||
}
|
5
sop-website/sop-website-vue/.travis.yml
Normal file
@@ -0,0 +1,5 @@
|
||||
language: node_js
|
||||
node_js: 10
|
||||
script: npm run test
|
||||
notifications:
|
||||
email: false
|
21
sop-website/sop-website-vue/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.
|
15
sop-website/sop-website-vue/README.md
Normal file
@@ -0,0 +1,15 @@
|
||||
# ISV管理后台
|
||||
|
||||
前提:先安装好npm,[npm安装教程](https://blog.csdn.net/zhangwenwu2/article/details/52778521)
|
||||
|
||||
- 启动服务端程序,运行`WebsiteServerApplication.java`
|
||||
- `cd sop-website-vue`
|
||||
- 执行`npm install --registry=https://registry.npm.taobao.org`
|
||||
- 执行`npm run dev`,访问`http://localhost:9529/`
|
||||
|
||||
|
||||
- 修改端口号:打开`vue.config.js`,找到`port`属性
|
||||
|
||||
## 构建
|
||||
|
||||
开发完毕后,运行`build.sh`脚本
|
5
sop-website/sop-website-vue/babel.config.js
Normal file
@@ -0,0 +1,5 @@
|
||||
module.exports = {
|
||||
presets: [
|
||||
'@vue/app'
|
||||
]
|
||||
}
|
13
sop-website/sop-website-vue/build.sh
Normal file
@@ -0,0 +1,13 @@
|
||||
#!/bin/sh
|
||||
|
||||
echo "开始构建..."
|
||||
|
||||
rm -rf dist
|
||||
npm run build:prod
|
||||
|
||||
public_path="sop-website-server/src/main/resources/public"
|
||||
|
||||
rm -rf ../$public_path/static/*
|
||||
cp -r dist/* ../$public_path
|
||||
|
||||
echo "构建完毕"
|
35
sop-website/sop-website-vue/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 = 9526
|
||||
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
sop-website/sop-website-vue/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/'
|
||||
}
|
66
sop-website/sop-website-vue/mock/index.js
Normal file
@@ -0,0 +1,66 @@
|
||||
import Mock from 'mockjs'
|
||||
import { param2Obj } from '../src/utils'
|
||||
|
||||
import user from './user'
|
||||
import table from './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).
|
||||
export 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))
|
||||
}
|
||||
}
|
||||
|
||||
// for mock server
|
||||
const responseFake = (url, type, respond) => {
|
||||
return {
|
||||
url: new RegExp(`/mock${url}`),
|
||||
type: type || 'get',
|
||||
response(req, res) {
|
||||
res.json(Mock.mock(respond instanceof Function ? respond(req, res) : respond))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default mocks.map(route => {
|
||||
return responseFake(route.url, route.type, route.response)
|
||||
})
|
68
sop-website/sop-website-vue/mock/mock-server.js
Normal file
@@ -0,0 +1,68 @@
|
||||
const chokidar = require('chokidar')
|
||||
const bodyParser = require('body-parser')
|
||||
const chalk = require('chalk')
|
||||
const path = require('path')
|
||||
|
||||
const mockDir = path.join(process.cwd(), 'mock')
|
||||
|
||||
function registerRoutes(app) {
|
||||
let mockLastIndex
|
||||
const { default: mocks } = require('./index.js')
|
||||
for (const mock of mocks) {
|
||||
app[mock.type](mock.url, mock.response)
|
||||
mockLastIndex = app._router.stack.length
|
||||
}
|
||||
const mockRoutesLength = Object.keys(mocks).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)]
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = app => {
|
||||
// es6 polyfill
|
||||
require('@babel/register')
|
||||
|
||||
// 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
sop-website/sop-website-vue/mock/table.js
Normal file
@@ -0,0 +1,29 @@
|
||||
import Mock from '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)'
|
||||
}]
|
||||
})
|
||||
|
||||
export default [
|
||||
{
|
||||
url: '/table/list',
|
||||
type: 'get',
|
||||
response: config => {
|
||||
const items = data.items
|
||||
return {
|
||||
code: 20000,
|
||||
data: {
|
||||
total: items.length,
|
||||
items: items
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
84
sop-website/sop-website-vue/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'
|
||||
}
|
||||
}
|
||||
|
||||
export default [
|
||||
// user login
|
||||
{
|
||||
url: '/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: '/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: '/user/logout',
|
||||
type: 'post',
|
||||
response: _ => {
|
||||
return {
|
||||
code: 20000,
|
||||
data: 'success'
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
69
sop-website/sop-website-vue/package.json
Normal file
@@ -0,0 +1,69 @@
|
||||
{
|
||||
"name": "opc-website",
|
||||
"version": "4.1.0",
|
||||
"description": "开放平台",
|
||||
"author": "tanghc",
|
||||
"license": "MIT",
|
||||
"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",
|
||||
"lint": "eslint --ext .js,.vue src",
|
||||
"test:unit": "jest --clearCache && vue-cli-service test:unit",
|
||||
"test:ci": "npm run lint && npm run test:unit",
|
||||
"svgo": "svgo -f src/icons/svg --config=src/icons/svgo.yml"
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "^0.18.0",
|
||||
"crypto-js": "^4.0.0",
|
||||
"element-ui": "2.13.0",
|
||||
"fast-text-encoding": "^1.0.3",
|
||||
"js-cookie": "2.2.0",
|
||||
"js-md5": "^0.7.3",
|
||||
"mavon-editor": "2.7.7",
|
||||
"needle": "^2.5.2",
|
||||
"normalize.css": "7.0.0",
|
||||
"nprogress": "0.2.0",
|
||||
"path-to-regexp": "2.4.0",
|
||||
"vue": "2.6.10",
|
||||
"vue-router": "3.0.6",
|
||||
"vuex": "3.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "7.0.0",
|
||||
"@babel/register": "7.0.0",
|
||||
"@vue/cli-plugin-babel": "3.6.0",
|
||||
"@vue/cli-plugin-eslint": "3.6.0",
|
||||
"@vue/cli-plugin-unit-jest": "3.6.3",
|
||||
"@vue/cli-service": "3.6.0",
|
||||
"@vue/test-utils": "1.0.0-beta.29",
|
||||
"babel-core": "7.0.0-bridge.0",
|
||||
"babel-eslint": "10.0.1",
|
||||
"babel-jest": "23.6.0",
|
||||
"chalk": "2.4.2",
|
||||
"connect": "3.6.6",
|
||||
"eslint": "5.15.3",
|
||||
"eslint-plugin-vue": "5.2.2",
|
||||
"html-webpack-plugin": "3.2.0",
|
||||
"mockjs": "1.0.1-beta3",
|
||||
"node-sass": "^4.9.0",
|
||||
"runjs": "^4.3.2",
|
||||
"sass-loader": "^7.1.0",
|
||||
"script-ext-html-webpack-plugin": "2.1.3",
|
||||
"script-loader": "0.7.2",
|
||||
"serve-static": "^1.13.2",
|
||||
"svg-sprite-loader": "4.1.3",
|
||||
"svgo": "1.2.2",
|
||||
"vue-template-compiler": "2.6.10"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.9",
|
||||
"npm": ">= 3.0.0"
|
||||
},
|
||||
"browserslist": [
|
||||
"> 1%",
|
||||
"last 2 versions",
|
||||
"not ie <= 8"
|
||||
]
|
||||
}
|
BIN
sop-website/sop-website-vue/public/favicon.ico
Normal file
After Width: | Height: | Size: 17 KiB |
17
sop-website/sop-website-vue/public/index.html
Normal file
@@ -0,0 +1,17 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
|
||||
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
|
||||
<title><%= webpackConfig.name %></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>
|
172
sop-website/sop-website-vue/public/static/code.json
Normal file
@@ -0,0 +1,172 @@
|
||||
[
|
||||
{
|
||||
"id": 1100,
|
||||
"code": "10000",
|
||||
"msg": "成功"
|
||||
},
|
||||
{
|
||||
"id": 1200,
|
||||
"code": "20000",
|
||||
"msg": "服务不可用",
|
||||
"children": [
|
||||
{
|
||||
"id": 1201,
|
||||
"sub_code": "isp.unknown-error",
|
||||
"sub_msg": "服务暂不可用",
|
||||
"solution": "联系平台客服排查日志"
|
||||
},
|
||||
{
|
||||
"id": 1202,
|
||||
"sub_code": "isp.no-service-support",
|
||||
"sub_msg": "业务系统不可用",
|
||||
"solution": "联系接口提供方"
|
||||
},
|
||||
{
|
||||
"id": 1203,
|
||||
"sub_code": "isp.invalid-content-type",
|
||||
"sub_msg": "业务系统返回错误的contentType",
|
||||
"solution": "联系接口提供方,返回的contentType是否是application/json或application/json;charset=UTF-8"
|
||||
},
|
||||
{
|
||||
"id": 1204,
|
||||
"sub_code": "isp.host-connection-refused",
|
||||
"sub_msg": "服务器拒绝访问",
|
||||
"solution": "联系接口提供方,检查配置的服务器域名是否正确"
|
||||
},
|
||||
{
|
||||
"id": 1205,
|
||||
"sub_code": "isp.business-timeout",
|
||||
"sub_msg": "业务处理超时",
|
||||
"solution": "联系接口提供方,业务逻辑处理时间太长"
|
||||
},
|
||||
{
|
||||
"id": 1206,
|
||||
"sub_code": "isp.invalid-result",
|
||||
"sub_msg": "无效的业务返回结果",
|
||||
"solution": "联系接口提供方,检查是否返回了正确的json"
|
||||
},
|
||||
{
|
||||
"id": 1207,
|
||||
"sub_code": "isp.exec-gzip-error",
|
||||
"sub_msg": "处理gzip异常",
|
||||
"solution": "联系客服排查日志"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 1300,
|
||||
"code": "40001",
|
||||
"msg": "缺少参数",
|
||||
"children": [
|
||||
{
|
||||
"id": 1301,
|
||||
"sub_code": "isv.missing-app-id",
|
||||
"sub_msg": "缺少appId参数",
|
||||
"solution": "检查参数"
|
||||
},
|
||||
{
|
||||
"id": 1302,
|
||||
"sub_code": "isv.missing-method",
|
||||
"sub_msg": "缺少method参数",
|
||||
"solution": "检查参数"
|
||||
},
|
||||
{
|
||||
"id": 1303,
|
||||
"sub_code": "isv.missing-signature",
|
||||
"sub_msg": "缺少签名参数",
|
||||
"solution": "检查参数"
|
||||
},
|
||||
{
|
||||
"id": 1304,
|
||||
"sub_code": "isv.missing-version",
|
||||
"sub_msg": "缺少版本号参数",
|
||||
"solution": "检查参数"
|
||||
},
|
||||
{
|
||||
"id": 1305,
|
||||
"sub_code": "isv.missing-signature-config",
|
||||
"sub_msg": "缺少秘钥配置",
|
||||
"solution": "联系接口提供方,配置秘钥"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 1400,
|
||||
"code": "40002",
|
||||
"msg": "非法参数",
|
||||
"children": [
|
||||
{
|
||||
"id": 1401,
|
||||
"sub_code": "isv.invalid-app-key",
|
||||
"sub_msg": "无效appId",
|
||||
"solution": "检查appId是否正确"
|
||||
},
|
||||
{
|
||||
"id": 1402,
|
||||
"sub_code": "isv.invalid-method",
|
||||
"sub_msg": "不存在的方法名",
|
||||
"solution": "检查方法名是否正确"
|
||||
},
|
||||
{
|
||||
"id": 1403,
|
||||
"sub_code": "isv.invalid-signature",
|
||||
"sub_msg": "无效签名",
|
||||
"solution": "检查签名算法是否正确"
|
||||
},
|
||||
{
|
||||
"id": 1404,
|
||||
"sub_code": "isv.invalid-timestamp",
|
||||
"sub_msg": "无效的时间戳",
|
||||
"solution": "检查时间戳参数是否正确"
|
||||
},
|
||||
{
|
||||
"id": 1405,
|
||||
"sub_code": "isv.invalid-environment",
|
||||
"sub_msg": "环境不一致",
|
||||
"solution": "检查测试环境正式环境是否混淆"
|
||||
},
|
||||
{
|
||||
"id": 1406,
|
||||
"sub_code": "isv.invalid-file-size",
|
||||
"sub_msg": "文件大小无效",
|
||||
"solution": "检查文件大小是否正确"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 1500,
|
||||
"code": "40004",
|
||||
"msg": "业务处理失败"
|
||||
},
|
||||
{
|
||||
"id": 1600,
|
||||
"code": "40006",
|
||||
"msg": "业务处理失败",
|
||||
"children": [
|
||||
{
|
||||
"id": 1601,
|
||||
"sub_code": "isv.api-no-permissions",
|
||||
"sub_msg": "没有当前接口权限",
|
||||
"solution": "联系接口提供方,检查是否分配接口权限"
|
||||
},
|
||||
{
|
||||
"id": 1602,
|
||||
"sub_code": "isv.access-forbidden",
|
||||
"sub_msg": "接入方禁止访问",
|
||||
"solution": "联系接口提供方,账号是否被禁用"
|
||||
},
|
||||
{
|
||||
"id": 1603,
|
||||
"sub_code": "isv.ip-forbidden",
|
||||
"sub_msg": "IP禁止访问",
|
||||
"solution": "联系接口提供方,IP加入了黑名单"
|
||||
},
|
||||
{
|
||||
"id": 1604,
|
||||
"sub_code": "isv.invalid-url",
|
||||
"sub_msg": "请求url不合法",
|
||||
"solution": "检查请求url是否正确"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
32
sop-website/sop-website-vue/public/static/doc/common/keys.md
Normal file
@@ -0,0 +1,32 @@
|
||||
# 公私钥介绍
|
||||
|
||||
- 公私钥用途
|
||||
|
||||
简单来说就是`私钥加签,公钥验证`
|
||||
|
||||
接入方和开放平台各自拥有一对公私钥,并且把自己的公钥给对方。
|
||||
|
||||
接入方登录后,首先生成一对公私钥,接入方的公钥被称为`应用公钥`,然后把公钥上传给开放平台,同时,开放平台会把平台公钥返回给接入方。
|
||||
这样实现了一个交换公钥的步骤,私钥自己保管,不能暴露。
|
||||
|
||||
- 接入方如何使用私钥
|
||||
|
||||
在进行接口调用时需要对请求参数进行加签,使用各自语言对应的`SHA256WithRSA`签名函数,并使用`应用私钥`对待签名字符串进行签名。
|
||||
|
||||
- 开放平台私钥如何使用
|
||||
|
||||
在进行业务回调的时候使用,如支付完成后开放平台回调接入方提供的回调接口,此时就是开放平台调用接入方的接口。
|
||||
|
||||
开放平台用平台私钥进行加签,然后接入方收到请求后,需要使用`平台公钥`进行验签,验证通过后再执行后续逻辑。
|
||||
|
||||
## 公私钥生成
|
||||
|
||||
在线生成,[点击前往](http://web.chacuo.net/netrsakeypair)
|
||||
|
||||
|
||||
`生成密钥位数`选择`2048位`,秘钥格式Java语言选择`PKCS#8`,非Java选择`PKCS#1`,证书密码不填
|
||||
|
||||
> 注:去掉首尾`-----BEGIN PUBLIC KEY-----`,`-----END PUBLIC KEY-----`
|
||||
|
||||
将生成的公钥上传,私钥保存。
|
||||
|
73
sop-website/sop-website-vue/public/static/doc/common/sign.md
Normal file
@@ -0,0 +1,73 @@
|
||||
# 签名算法
|
||||
|
||||
签名步骤如下:
|
||||
|
||||
1.筛选并排序
|
||||
|
||||
获取所有请求参数,不包括字节类型参数,如文件、字节流,剔除sign字段,剔除值为空的参数,并按照参数名ASCII码递增排序(字母升序排序),如果遇到相同字符则按照第二个字符的键值ASCII码递增排序,以此类推。
|
||||
|
||||
2.拼接
|
||||
|
||||
将排序后的参数与其对应值,组合成“参数=参数值”的格式,并且把这些参数用&字符连接起来,此时生成的字符串为待签名字符串。
|
||||
|
||||
|
||||
3.调用签名函数
|
||||
|
||||
使用各自语言对应的`SHA256WithRSA`签名函数并使用应用私钥对待签名字符串进行签名,对结果Base64编码。
|
||||
|
||||
4.把生成的签名赋值给`sign`参数,拼接到请求参数中。
|
||||
|
||||
|
||||
- 示例
|
||||
|
||||
假设目前已经存在一个接口:获取会员信息
|
||||
|
||||
- 接口名:member.info.get
|
||||
- 版本号:1.0
|
||||
- 请求方式:GET
|
||||
- 请求参数:name=jim&age=123&address=xxx
|
||||
|
||||
它的业务参数为:
|
||||
|
||||
```
|
||||
name=jim
|
||||
age=22
|
||||
address=xx
|
||||
```
|
||||
|
||||
加上公共请求参数:
|
||||
|
||||
```
|
||||
app_id=test_2020050924567817013559296
|
||||
method=member.info.get
|
||||
version=1.0
|
||||
charset=UTF-8
|
||||
timestamp=2019-06-03 15:18:29
|
||||
sign=(待生成)
|
||||
```
|
||||
|
||||
把业务请求参数和公共请求参数加起来,然后按照参数名ASCII码递增排序
|
||||
|
||||
则待签名字符串为:
|
||||
|
||||
```
|
||||
address=xx&age=22&app_id=test_2020050924567817013559296&charset=UTF-8&method=member.info.get&name=jim×tamp=2020-06-03 15:23:30&version=1.0
|
||||
```
|
||||
|
||||
使用各自语言对应的`SHA256WithRSA`签名函数并使用`应用私钥`对待签名字符串进行签名,对结果Base64编码,得到字符串:`adfdxadsf3asdfa`
|
||||
|
||||
把该字符串给`sign`参数,拼接到请求参数中,得到最终请求参数为:
|
||||
|
||||
```
|
||||
name=jim
|
||||
age=22
|
||||
address=xx
|
||||
app_id=test_2020050924567817013559296
|
||||
method=member.info.get
|
||||
version=1.0
|
||||
charset=UTF-8
|
||||
timestamp=2019-06-03 15:18:29
|
||||
sign=adfdxadsf3asdfa
|
||||
```
|
||||
|
||||
如果开放平台已经提供SDK,那么SDK中已经封装好签名步骤,直接调用SDK中的方法即可完成接口请求。
|
16
sop-website/sop-website-vue/public/static/doc/isv/menu.json
Normal file
@@ -0,0 +1,16 @@
|
||||
[
|
||||
{
|
||||
"title" : "签名专区",
|
||||
"icon": "el-icon-edit-outline",
|
||||
"children": [
|
||||
{
|
||||
"title": "签名算法",
|
||||
"path": "static/doc/common/sign.md"
|
||||
},
|
||||
{
|
||||
"title": "公私钥介绍",
|
||||
"path": "static/doc/common/keys.md"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
20
sop-website/sop-website-vue/public/static/params.json
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"commonParams": [
|
||||
{ "name": "app_id", "type": "string", "must": 1, "description": "接入方appId", "example": "20200317689494536224768000" },
|
||||
{ "name": "method", "type": "string", "must": 1, "description": "接口名", "example": "user.userinfo.get" },
|
||||
{ "name": "version", "type": "string", "must": 1, "description": "版本号", "example": "1.0" },
|
||||
{ "name": "charset", "type": "string", "must": 1, "description": "字符编码", "example": "UTF-8" },
|
||||
{ "name": "timestamp", "type": "string", "must": 1,
|
||||
"description": "时间戳,格式为yyyy-MM-dd HH:mm:ss,时区为GMT+8。服务端允许客户端请求最大时间误差为10分钟",
|
||||
"example": "2020-11-01 13:44:11" },
|
||||
{ "name": "app_auth_token", "type": "string", "must": 0, "description": "token", "example": "01c9a4191bfd4609d26" },
|
||||
{ "name": "sign", "type": "string", "must": 1, "description": "请求参数的签名串", "example": "xxxx" }
|
||||
],
|
||||
"commonResult": [
|
||||
{ "name": "request_id", "type": "string", "must": 1, "description": "每次请求唯一id", "example": "4e770f101c9a4191bfd4609d26c6e0bd" },
|
||||
{ "name": "code", "type": "string", "must": 1, "description": "返回码", "example": "40002" },
|
||||
{ "name": "msg", "type": "string", "must": 1, "description": "返回信息", "example": "非法的参数" },
|
||||
{ "name": "sub_code", "type": "string", "must": 1, "description": "请求失败返回的子错误码", "example": "isv.invalid-app-key" },
|
||||
{ "name": "sub_msg", "type": "string", "must": 1, "description": "请求失败返回的子错误信息", "example": "无效的app_id参数" }
|
||||
]
|
||||
}
|
11
sop-website/sop-website-vue/src/App.vue
Normal file
@@ -0,0 +1,11 @@
|
||||
<template>
|
||||
<div id="app">
|
||||
<el-backtop />
|
||||
<router-view />
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
name: 'App'
|
||||
}
|
||||
</script>
|
9
sop-website/sop-website-vue/src/api/table.js
Normal file
@@ -0,0 +1,9 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
export function getList(params) {
|
||||
return request({
|
||||
url: '/table/list',
|
||||
method: 'get',
|
||||
params
|
||||
})
|
||||
}
|
24
sop-website/sop-website-vue/src/api/user.js
Normal file
@@ -0,0 +1,24 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
export function login(data) {
|
||||
return request({
|
||||
url: '/user/login',
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
export function getInfo(token) {
|
||||
return request({
|
||||
url: '/user/info',
|
||||
method: 'get',
|
||||
params: { token }
|
||||
})
|
||||
}
|
||||
|
||||
export function logout() {
|
||||
return request({
|
||||
url: '/user/logout',
|
||||
method: 'post'
|
||||
})
|
||||
}
|
@@ -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>
|
273
sop-website/sop-website-vue/src/components/DocView/index.vue
Normal file
@@ -0,0 +1,273 @@
|
||||
<template>
|
||||
<div>
|
||||
<h2>{{ docInfo.summary }}</h2>
|
||||
<el-table
|
||||
:data="[{ methodLabel: '接口名(method)', methodValue: docInfo.name, versionLabel: '版本号(version)', versionValue: docInfo.version }]"
|
||||
border
|
||||
:cell-style="baseInfoCellStyle"
|
||||
:show-header="false"
|
||||
>
|
||||
<el-table-column prop="methodLabel" align="center" width="130">
|
||||
<template slot-scope="scope"><span class="api-info">{{ scope.row.methodLabel }}</span></template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="methodValue" />
|
||||
<el-table-column prop="versionLabel" align="center" width="130">
|
||||
<template slot-scope="scope"><span class="api-info">{{ scope.row.versionLabel }}</span></template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="versionValue" width="120" />
|
||||
</el-table>
|
||||
<h3>接口描述</h3>
|
||||
<div class="doc-overview">{{ docInfo.description || docInfo.title }}</div>
|
||||
<h3>请求地址</h3>
|
||||
<el-table
|
||||
:data="[{ envLabel: '环境', envValue: '正式环境', urlLabel: '请求地址', urlValue: urlProd }]"
|
||||
border
|
||||
:cell-style="baseInfoCellStyle"
|
||||
:show-header="false"
|
||||
>
|
||||
<el-table-column align="center" width="100">
|
||||
<template slot-scope="scope"><span class="api-info">{{ scope.row.envLabel }}</span></template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="envValue" width="140" />
|
||||
<el-table-column align="center" width="100">
|
||||
<template slot-scope="scope"><span class="api-info">{{ scope.row.urlLabel }}</span></template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="urlValue" />
|
||||
</el-table>
|
||||
<h3>请求方法</h3>
|
||||
<div class="doc-request-method">
|
||||
{{ docInfo.httpMethodList && docInfo.httpMethodList.join(' / ').toUpperCase() }}
|
||||
</div>
|
||||
<h2>请求参数</h2>
|
||||
<h3>公共请求参数</h3>
|
||||
<el-table
|
||||
:data="commonParams"
|
||||
:cell-style="cellStyleSmall()"
|
||||
:header-cell-style="headCellStyleSmall()"
|
||||
border
|
||||
>
|
||||
<el-table-column
|
||||
prop="name"
|
||||
label="名称"
|
||||
width="200"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="type"
|
||||
label="类型"
|
||||
width="100"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="must"
|
||||
label="必须"
|
||||
width="60"
|
||||
>
|
||||
<template slot-scope="scope">
|
||||
<span :class="scope.row.must ? 'danger' : ''">{{ scope.row.must ? '是' : '否' }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="description"
|
||||
label="描述"
|
||||
>
|
||||
<template slot-scope="scope">
|
||||
{{ scope.row.description }}
|
||||
<span v-if="scope.row.name === 'sign'">,
|
||||
<router-link target="_blank" to="/help?id=sign">
|
||||
<el-button type="text">签名算法介绍</el-button>
|
||||
</router-link>
|
||||
</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="example"
|
||||
label="示例值"
|
||||
/>
|
||||
</el-table>
|
||||
<h3>业务请求参数</h3>
|
||||
<parameter-table :data="docInfo.requestParameters" />
|
||||
<h2>响应参数</h2>
|
||||
<h3>公共响应参数</h3>
|
||||
<el-table
|
||||
:data="commonResult"
|
||||
:cell-style="cellStyleSmall()"
|
||||
:header-cell-style="headCellStyleSmall()"
|
||||
border
|
||||
>
|
||||
<el-table-column
|
||||
prop="name"
|
||||
label="名称"
|
||||
width="200"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="type"
|
||||
label="类型"
|
||||
width="100"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="description"
|
||||
label="描述"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="example"
|
||||
label="示例值"
|
||||
/>
|
||||
</el-table>
|
||||
<h3>业务响应参数</h3>
|
||||
<parameter-table :data="docInfo.responseParameters" />
|
||||
<h3>响应示例</h3>
|
||||
<pre class="normal-text">{{ JSON.stringify(responseSuccessExample, null, 4) }}</pre>
|
||||
<h3>错误示例</h3>
|
||||
<pre class="normal-text">{{ JSON.stringify(responseErrorExample, null, 4) }}</pre>
|
||||
<h2>业务错误码</h2>
|
||||
<router-link target="_blank" to="/code">
|
||||
<el-button type="text">公共错误码</el-button>
|
||||
</router-link>
|
||||
<el-table
|
||||
:data="docInfo.bizCodeList"
|
||||
border
|
||||
:cell-style="cellStyleSmall()"
|
||||
:header-cell-style="headCellStyleSmall()"
|
||||
>
|
||||
<el-table-column
|
||||
prop="code"
|
||||
label="sub_code(错误码)"
|
||||
width="300"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="msg"
|
||||
label="sub_msg(错误描述)"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="solution"
|
||||
label="解决方案"
|
||||
/>
|
||||
</el-table>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ParameterTable from '@/components/ParameterTable'
|
||||
export default {
|
||||
name: 'DocView',
|
||||
components: { ParameterTable },
|
||||
props: {
|
||||
item: {
|
||||
type: Object,
|
||||
default: () => {}
|
||||
},
|
||||
uri: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
urlProd: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
active: 'info',
|
||||
baseInfoCellStyle: (row) => {
|
||||
if (row.columnIndex === 0 || row.columnIndex === 2) {
|
||||
return { padding: '5px 0', background: '#f5f7fa' }
|
||||
} else {
|
||||
return { padding: '5px 0' }
|
||||
}
|
||||
},
|
||||
commonParams: [],
|
||||
commonResult: [],
|
||||
docBaseInfoData: [],
|
||||
docInfo: {
|
||||
summary: '',
|
||||
httpMethodList: [],
|
||||
requestParameters: [],
|
||||
responseParameters: [],
|
||||
bizCodes: []
|
||||
},
|
||||
responseSuccessExample: {},
|
||||
responseErrorExample: {
|
||||
error_response: {
|
||||
request_id: '0d27836fcac345729176359388aeeb74',
|
||||
code: '40004',
|
||||
msg: '业务处理失败',
|
||||
sub_code: 'isv.name-error',
|
||||
sub_msg: '姓名错误'
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
item(newVal) {
|
||||
this.initItem(newVal)
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.getFile('static/params.json', (json) => {
|
||||
this.commonParams = json.commonParams
|
||||
this.commonResult = json.commonResult
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
initItem(item) {
|
||||
this.setData(item)
|
||||
},
|
||||
setData: function(data) {
|
||||
this.docInfo = data
|
||||
this.createResponseExample(data)
|
||||
},
|
||||
createResponseExample: function(data) {
|
||||
const ret = {}
|
||||
const responseData = {
|
||||
request_id: '4b8e7ca9cbcb448491df2f0120e49b9d',
|
||||
code: '10000',
|
||||
msg: 'success'
|
||||
}
|
||||
ret[this.getResponseNodeName()] = responseData
|
||||
const bizRet = this.createExample(data.responseParameters)
|
||||
for (const key in bizRet) {
|
||||
responseData[key] = bizRet[key]
|
||||
}
|
||||
this.responseSuccessExample = ret
|
||||
},
|
||||
createExample: function(params) {
|
||||
const responseJson = {}
|
||||
for (let i = 0; i < params.length; i++) {
|
||||
const row = params[i]
|
||||
if (row.in === 'header') {
|
||||
continue
|
||||
}
|
||||
let val
|
||||
// 如果有子节点
|
||||
if (row.refs && row.refs.length > 0) {
|
||||
const childrenValue = this.createExample(row.refs)
|
||||
// 如果是数组
|
||||
if (row.type === 'array') {
|
||||
val = [childrenValue]
|
||||
} else {
|
||||
val = childrenValue
|
||||
}
|
||||
} else {
|
||||
// 单值
|
||||
val = row.paramExample
|
||||
}
|
||||
responseJson[row.name] = val
|
||||
}
|
||||
const isOneArray = Object.keys(responseJson).length === 1 && this.isArray(Object.values(responseJson)[0])
|
||||
if (isOneArray) {
|
||||
return Object.values(responseJson)[0]
|
||||
}
|
||||
return responseJson
|
||||
},
|
||||
getResponseNodeName: function() {
|
||||
const name = this.docInfo.name
|
||||
return name.replace(/\./g, '_') + '_response'
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.api-info {font-weight: bold;}
|
||||
.doc-overview {margin-top: 20px;margin-bottom: 30px;color: #666;font-size: 14px;}
|
||||
.doc-request-method {margin-bottom: 20px;color: #666;font-size: 14px;}
|
||||
</style>
|
123
sop-website/sop-website-vue/src/components/Docdebug/index.vue
Normal file
@@ -0,0 +1,123 @@
|
||||
<template>
|
||||
<div class="doc-debug">
|
||||
<h2>{{ docInfo.summary }}</h2>
|
||||
<el-table
|
||||
:data="[{ methodLabel: '接口名(method)', methodValue: docInfo.name, versionLabel: '版本号(version)', versionValue: docInfo.version }]"
|
||||
border
|
||||
:cell-style="baseInfoCellStyle"
|
||||
:show-header="false"
|
||||
>
|
||||
<el-table-column prop="methodLabel" align="center" width="130">
|
||||
<template slot-scope="scope"><span class="api-info">{{ scope.row.methodLabel }}</span></template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="methodValue" />
|
||||
<el-table-column prop="versionLabel" align="center" width="130">
|
||||
<template slot-scope="scope"><span class="api-info">{{ scope.row.versionLabel }}</span></template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="versionValue" width="120" />
|
||||
</el-table>
|
||||
<h3>接口描述</h3>
|
||||
<div class="doc-overview">{{ docInfo.description || docInfo.title }}</div>
|
||||
<h3>请求方法</h3>
|
||||
<div class="doc-request-method">
|
||||
{{ docInfo.httpMethodList && docInfo.httpMethodList.join(' / ').toUpperCase() }}
|
||||
</div>
|
||||
<h2>请求参数</h2>
|
||||
<parameter-table :data="docInfo.requestParameters" :editable="true" />
|
||||
<br/>
|
||||
<el-button type="primary" @click="send">发送请求</el-button>
|
||||
</div>
|
||||
</template>
|
||||
<style>
|
||||
.api-info {font-weight: bold;}
|
||||
.doc-overview {margin-top: 20px;margin-bottom: 30px;color: #666;font-size: 14px;}
|
||||
.doc-request-method {margin-bottom: 20px;color: #666;font-size: 14px;}
|
||||
.cell .choose-file {padding: 5px;}
|
||||
.doc-debug .cell .el-form-item {margin-bottom: 0;}
|
||||
</style>
|
||||
<script>
|
||||
import ParameterTable from '@/components/ParameterTable'
|
||||
export default {
|
||||
name: 'Docdebug',
|
||||
components: { ParameterTable },
|
||||
props: {
|
||||
item: {
|
||||
type: Object,
|
||||
default: () => {}
|
||||
},
|
||||
uri: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
urlProd: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
baseInfoCellStyle: (row) => {
|
||||
if (row.columnIndex === 0 || row.columnIndex === 2) {
|
||||
return { padding: '5px 0', background: '#f5f7fa' }
|
||||
} else {
|
||||
return { padding: '5px 0' }
|
||||
}
|
||||
},
|
||||
docInfo: {
|
||||
summary: '',
|
||||
httpMethodList: [],
|
||||
requestParameters: [],
|
||||
responseParameters: [],
|
||||
bizCodes: []
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
item(newVal) {
|
||||
this.initItem(newVal)
|
||||
}
|
||||
},
|
||||
created() {
|
||||
},
|
||||
methods: {
|
||||
send() {
|
||||
|
||||
},
|
||||
initItem(item) {
|
||||
this.setData(item)
|
||||
},
|
||||
setData: function(data) {
|
||||
this.docInfo = data
|
||||
},
|
||||
buildParamData: function(params) {
|
||||
const responseJson = {}
|
||||
for (let i = 0; i < params.length; i++) {
|
||||
const row = params[i]
|
||||
if (row.in === 'header') {
|
||||
continue
|
||||
}
|
||||
let val
|
||||
// 如果有子节点
|
||||
if (row.refs && row.refs.length > 0) {
|
||||
const childrenValue = this.buildParamData(row.refs)
|
||||
// 如果是数组
|
||||
if (row.type === 'array') {
|
||||
val = [childrenValue]
|
||||
} else {
|
||||
val = childrenValue
|
||||
}
|
||||
} else {
|
||||
// 单值
|
||||
val = row.paramExample
|
||||
}
|
||||
responseJson[row.name] = val
|
||||
}
|
||||
const isOneArray = Object.keys(responseJson).length === 1 && this.isArray(Object.values(responseJson)[0])
|
||||
if (isOneArray) {
|
||||
return Object.values(responseJson)[0]
|
||||
}
|
||||
return responseJson
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
@@ -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>
|
@@ -0,0 +1,87 @@
|
||||
<template>
|
||||
<el-table
|
||||
:data="data"
|
||||
border
|
||||
row-key="id"
|
||||
default-expand-all
|
||||
:tree-props="{ children: 'refs', hasChildren: 'hasChildren' }"
|
||||
:cell-style="cellStyleSmall()"
|
||||
:header-cell-style="headCellStyleSmall()"
|
||||
empty-text="无参数"
|
||||
>
|
||||
<el-table-column
|
||||
prop="name"
|
||||
label="名称"
|
||||
width="250"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="type"
|
||||
label="类型"
|
||||
width="100"
|
||||
>
|
||||
<template slot-scope="scope">
|
||||
<span>{{ scope.row.type }}</span>
|
||||
<span v-show="scope.row.type === 'array' && scope.row.elementType">
|
||||
<el-tooltip effect="dark" :content="`元素类型:${scope.row.elementType}`" placement="top">
|
||||
<i class="el-icon-info"></i>
|
||||
</el-tooltip>
|
||||
</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="required"
|
||||
label="必须"
|
||||
width="60"
|
||||
>
|
||||
<template slot-scope="scope">
|
||||
<span :class="scope.row.required ? 'danger' : ''">{{ scope.row.required ? '是' : '否' }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="maxLength"
|
||||
label="最大长度"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="description"
|
||||
label="描述"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="paramExample"
|
||||
label="示例值"
|
||||
>
|
||||
<template slot-scope="scope">
|
||||
<div v-if="editable">
|
||||
|
||||
</div>
|
||||
<div v-else>
|
||||
<div v-if="scope.row.type === 'enum'">
|
||||
{{ (scope.row.enums || []).join('、') }}
|
||||
</div>
|
||||
<div v-else>
|
||||
{{ scope.row.paramExample }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'ParameterTable',
|
||||
props: {
|
||||
data: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
editable: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
tree: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
43
sop-website/sop-website-vue/src/components/SvgIcon/index.vue
Normal file
@@ -0,0 +1,43 @@
|
||||
<template>
|
||||
<svg :class="svgClass" aria-hidden="true" v-on="$listeners">
|
||||
<use :xlink:href="iconName" />
|
||||
</svg>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'SvgIcon',
|
||||
props: {
|
||||
iconClass: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
className: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
iconName() {
|
||||
return `#icon-${this.iconClass}`
|
||||
},
|
||||
svgClass() {
|
||||
if (this.className) {
|
||||
return 'svg-icon ' + this.className
|
||||
} else {
|
||||
return 'svg-icon'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.svg-icon {
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
vertical-align: -0.15em;
|
||||
fill: currentColor;
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
465
sop-website/sop-website-vue/src/components/verifition/Verify.vue
Normal file
@@ -0,0 +1,247 @@
|
||||
<template>
|
||||
<div style="position: relative"
|
||||
>
|
||||
<div class="verify-img-out"
|
||||
v-show="showImage"
|
||||
>
|
||||
<div class="verify-img-panel" :style="{'width': setSize.imgWidth,
|
||||
'height': setSize.imgHeight,
|
||||
'background-size' : setSize.imgWidth + ' '+ setSize.imgHeight,
|
||||
'margin-bottom': vSpace + 'px'}"
|
||||
>
|
||||
<div class="verify-refresh" style="z-index:3" @click="refresh" v-show="showRefresh">
|
||||
<i class="iconfont icon-refresh"></i>
|
||||
</div>
|
||||
<img :src="'data:image/png;base64,'+pointBackImgBase"
|
||||
ref="canvas"
|
||||
alt="" style="width:100%;height:100%;display:block"
|
||||
@click="bindingClick?canvasClick($event):undefined">
|
||||
|
||||
<div v-for="(tempPoint, index) in tempPoints" :key="index" class="point-area"
|
||||
:style="{
|
||||
'background-color':'#1abd6c',
|
||||
color:'#fff',
|
||||
'z-index':9999,
|
||||
width:'20px',
|
||||
height:'20px',
|
||||
'text-align':'center',
|
||||
'line-height':'20px',
|
||||
'border-radius': '50%',
|
||||
position:'absolute',
|
||||
top:parseInt(tempPoint.y-10) + 'px',
|
||||
left:parseInt(tempPoint.x-10) + 'px'
|
||||
}">
|
||||
{{index + 1}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 'height': this.barSize.height, -->
|
||||
<div class="verify-bar-area"
|
||||
:style="{'width': setSize.imgWidth,
|
||||
'color': this.barAreaColor,
|
||||
'border-color': this.barAreaBorderColor,
|
||||
'line-height':this.barSize.height}">
|
||||
<span class="verify-msg">{{text}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script type="text/babel">
|
||||
/**
|
||||
* VerifyPoints
|
||||
* @description 点选
|
||||
* */
|
||||
import {resetSize, _code_chars, _code_color1, _code_color2} from './../utils/util'
|
||||
import {aesEncrypt} from "./../utils/ase"
|
||||
import {reqGet,reqCheck} from "./../api/index"
|
||||
|
||||
export default {
|
||||
name: 'VerifyPoints',
|
||||
props: {
|
||||
//弹出式pop,固定fixed
|
||||
mode: {
|
||||
type: String,
|
||||
default: 'fixed'
|
||||
},
|
||||
captchaType:{
|
||||
type:String,
|
||||
},
|
||||
//间隔
|
||||
vSpace: {
|
||||
type: Number,
|
||||
default: 5
|
||||
},
|
||||
imgSize: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {
|
||||
width: '310px',
|
||||
height: '155px'
|
||||
}
|
||||
}
|
||||
},
|
||||
barSize: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {
|
||||
width: '310px',
|
||||
height: '40px'
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
checkNum:3, //默认需要点击的字数
|
||||
fontPos: [], //选中的坐标信息
|
||||
checkPosArr: [], //用户点击的坐标
|
||||
num: 1, //点击的记数
|
||||
pointBackImgBase:'', //后端获取到的背景图片
|
||||
poinTextList:[], //后端返回的点击字体顺序
|
||||
backToken:'', //后端返回的token值
|
||||
imgRand: 0, // //随机的背景图片
|
||||
setSize: {
|
||||
imgHeight: 0,
|
||||
imgWidth: 0,
|
||||
barHeight: 0,
|
||||
barWidth: 0
|
||||
},
|
||||
showImage: true,
|
||||
tempPoints: [],
|
||||
text: '',
|
||||
barAreaColor: undefined,
|
||||
barAreaBorderColor: undefined,
|
||||
showRefresh: true,
|
||||
bindingClick: true
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
resetSize() {
|
||||
return resetSize
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
init() {
|
||||
//加载页面
|
||||
this.fontPos.splice(0, this.fontPos.length)
|
||||
this.checkPosArr.splice(0, this.checkPosArr.length)
|
||||
this.num = 1
|
||||
this.getPictrue();
|
||||
this.$nextTick(() => {
|
||||
this.setSize = this.resetSize(this) //重新设置宽度高度
|
||||
this.$parent.$emit('ready', this)
|
||||
})
|
||||
},
|
||||
canvasClick(e) {
|
||||
this.checkPosArr.push(this.getMousePos(this.$refs.canvas, e));
|
||||
if (this.num == this.checkNum) {
|
||||
this.num = this.createPoint(this.getMousePos(this.$refs.canvas, e));
|
||||
//按比例转换坐标值
|
||||
this.checkPosArr = this.pointTransfrom(this.checkPosArr,this.setSize);
|
||||
//等创建坐标执行完
|
||||
setTimeout(() => {
|
||||
// var flag = this.comparePos(this.fontPos, this.checkPosArr);
|
||||
//发送后端请求
|
||||
var captchaVerification = aesEncrypt(this.backToken+'---'+JSON.stringify(this.checkPosArr))
|
||||
let data = {
|
||||
captchaType:this.captchaType,
|
||||
"pointJson":aesEncrypt(JSON.stringify(this.checkPosArr)),
|
||||
"token":this.backToken
|
||||
}
|
||||
reqCheck(data).then(res=>{
|
||||
if (res.repCode == "0000") {
|
||||
this.barAreaColor = '#4cae4c'
|
||||
this.barAreaBorderColor = '#5cb85c'
|
||||
this.text = '验证成功'
|
||||
this.bindingClick = false
|
||||
if (this.mode=='pop') {
|
||||
setTimeout(()=>{
|
||||
this.$parent.clickShow = false;
|
||||
this.refresh();
|
||||
},1500)
|
||||
}
|
||||
this.$parent.$emit('success', {captchaVerification})
|
||||
}else{
|
||||
this.$parent.$emit('error', this)
|
||||
this.barAreaColor = '#d9534f'
|
||||
this.barAreaBorderColor = '#d9534f'
|
||||
this.text = '验证失败'
|
||||
setTimeout(() => {
|
||||
this.refresh();
|
||||
}, 700);
|
||||
}
|
||||
})
|
||||
}, 400);
|
||||
}
|
||||
if (this.num < this.checkNum) {
|
||||
this.num = this.createPoint(this.getMousePos(this.$refs.canvas, e));
|
||||
}
|
||||
},
|
||||
|
||||
//获取坐标
|
||||
getMousePos: function (obj, e) {
|
||||
var x = e.offsetX
|
||||
var y = e.offsetY
|
||||
return {x, y}
|
||||
},
|
||||
//创建坐标点
|
||||
createPoint: function (pos) {
|
||||
this.tempPoints.push(Object.assign({}, pos))
|
||||
return ++this.num;
|
||||
},
|
||||
refresh: function () {
|
||||
this.tempPoints.splice(0, this.tempPoints.length)
|
||||
this.barAreaColor = '#000'
|
||||
this.barAreaBorderColor = '#ddd'
|
||||
this.bindingClick = true
|
||||
this.fontPos.splice(0, this.fontPos.length)
|
||||
this.checkPosArr.splice(0, this.checkPosArr.length)
|
||||
this.num = 1
|
||||
this.getPictrue();
|
||||
this.text = '验证失败'
|
||||
this.showRefresh = true
|
||||
},
|
||||
|
||||
// 请求背景图片和验证图片
|
||||
getPictrue(){
|
||||
let data = {
|
||||
captchaType:this.captchaType
|
||||
}
|
||||
reqGet(data).then(res=>{
|
||||
if (res.repCode == "0000") {
|
||||
this.pointBackImgBase = res.repData.originalImageBase64
|
||||
this.backToken = res.repData.token
|
||||
this.poinTextList = res.repData.wordList
|
||||
this.text = '请依次点击【' + this.poinTextList.join(",") + '】'
|
||||
}else{
|
||||
this.text = res.repMsg;
|
||||
}
|
||||
})
|
||||
},
|
||||
//坐标转换函数
|
||||
pointTransfrom(pointArr,imgSize){
|
||||
var newPointArr = pointArr.map(p=>{
|
||||
let x = Math.round(310 * p.x/parseInt(imgSize.imgWidth))
|
||||
let y =Math.round(155 * p.y/parseInt(imgSize.imgHeight))
|
||||
return {x,y}
|
||||
})
|
||||
// console.log(newPointArr,"newPointArr");
|
||||
return newPointArr
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
// type变化则全面刷新
|
||||
type: {
|
||||
immediate: true,
|
||||
handler() {
|
||||
this.init()
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
// 禁止拖拽
|
||||
this.$el.onselectstart = function () {
|
||||
return false
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
@@ -0,0 +1,346 @@
|
||||
<template>
|
||||
<div style="position: relative;">
|
||||
<div v-if="type === '2'" class="verify-img-out"
|
||||
:style="{height: (parseInt(setSize.imgHeight) + vSpace) + 'px'}"
|
||||
>
|
||||
<div class="verify-img-panel" :style="{width: setSize.imgWidth,
|
||||
height: setSize.imgHeight,}">
|
||||
<img :src="'data:image/png;base64,'+backImgBase" alt="" style="width:100%;height:100%;display:block">
|
||||
<div class="verify-refresh" @click="refresh" v-show="showRefresh"><i class="iconfont icon-refresh"></i>
|
||||
</div>
|
||||
<transition name="tips">
|
||||
<span class="verify-tips" v-if="tipWords" :class="passFlag ?'suc-bg':'err-bg'">{{tipWords}}</span>
|
||||
</transition>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 公共部分 -->
|
||||
<div class="verify-bar-area" :style="{width: setSize.imgWidth,
|
||||
height: barSize.height,
|
||||
'line-height':barSize.height}">
|
||||
<span class="verify-msg" v-text="text"></span>
|
||||
<div class="verify-left-bar"
|
||||
:style="{width: (leftBarWidth!==undefined)?leftBarWidth: barSize.height, height: barSize.height, 'border-color': leftBarBorderColor, transaction: transitionWidth}">
|
||||
<span class="verify-msg" v-text="finishText"></span>
|
||||
<div class="verify-move-block"
|
||||
@touchstart="start"
|
||||
@mousedown="start"
|
||||
:style="{width: barSize.height, height: barSize.height, 'background-color': moveBlockBackgroundColor, left: moveBlockLeft, transition: transitionLeft}">
|
||||
<i :class="['verify-icon iconfont', iconClass]"
|
||||
:style="{color: iconColor}"></i>
|
||||
<div v-if="type === '2'"
|
||||
class="verify-sub-block"
|
||||
:style="{'width':Math.floor(parseInt(setSize.imgWidth)*47/310)+ 'px',
|
||||
'height': setSize.imgHeight,
|
||||
'top':'-' + (parseInt(setSize.imgHeight) + vSpace) + 'px',
|
||||
'background-size': setSize.imgWidth + ' ' + setSize.imgHeight,
|
||||
}">
|
||||
<img :src="'data:image/png;base64,'+blockBackImgBase" alt="" style="width:100%;height:100%;display:block">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script type="text/babel">
|
||||
/**
|
||||
* VerifySlide
|
||||
* @description 滑块
|
||||
* */
|
||||
import {aesEncrypt} from "./../utils/ase"
|
||||
import {resetSize} from './../utils/util'
|
||||
import {reqGet,reqCheck} from "./../api/index"
|
||||
|
||||
// "captchaType":"blockPuzzle",
|
||||
export default {
|
||||
name: 'VerifySlide',
|
||||
props: {
|
||||
captchaType:{
|
||||
type:String,
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
default: '1'
|
||||
},
|
||||
//弹出式pop,固定fixed
|
||||
mode: {
|
||||
type: String,
|
||||
default: 'fixed'
|
||||
},
|
||||
vSpace: {
|
||||
type: Number,
|
||||
default: 5
|
||||
},
|
||||
explain: {
|
||||
type: String,
|
||||
default: '向右滑动完成验证'
|
||||
},
|
||||
imgSize: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {
|
||||
width: '310px',
|
||||
height: '155px'
|
||||
}
|
||||
}
|
||||
},
|
||||
blockSize: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {
|
||||
width: '50px',
|
||||
height: '50px'
|
||||
}
|
||||
}
|
||||
},
|
||||
barSize: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {
|
||||
width: '310px',
|
||||
height: '40px'
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
passFlag:'', //是否通过的标识
|
||||
backImgBase:'', //验证码背景图片
|
||||
blockBackImgBase:'', //验证滑块的背景图片
|
||||
backToken:"", //后端返回的唯一token值
|
||||
startMoveTime:"", //移动开始的时间
|
||||
endMovetime:'', //移动结束的时间
|
||||
tipsBackColor:'', //提示词的背景颜色
|
||||
tipWords:'',
|
||||
text: '',
|
||||
finishText:'',
|
||||
setSize: {
|
||||
imgHeight: 0,
|
||||
imgWidth: 0,
|
||||
barHeight: 0,
|
||||
barWidth: 0
|
||||
},
|
||||
top: 0,
|
||||
left: 0,
|
||||
moveBlockLeft: undefined,
|
||||
leftBarWidth: undefined,
|
||||
// 移动中样式
|
||||
moveBlockBackgroundColor: undefined,
|
||||
leftBarBorderColor: '#ddd',
|
||||
iconColor: undefined,
|
||||
iconClass: 'icon-right',
|
||||
status: false, //鼠标状态
|
||||
isEnd: false, //是够验证完成
|
||||
showRefresh: true,
|
||||
transitionLeft: '',
|
||||
transitionWidth: ''
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
barArea() {
|
||||
return this.$el.querySelector('.verify-bar-area')
|
||||
},
|
||||
resetSize() {
|
||||
return resetSize
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
init() {
|
||||
this.text = this.explain
|
||||
this.getPictrue();
|
||||
this.$nextTick(() => {
|
||||
let setSize = this.resetSize(this) //重新设置宽度高度
|
||||
for (let key in setSize) {
|
||||
this.$set(this.setSize, key, setSize[key])
|
||||
}
|
||||
this.$parent.$emit('ready', this)
|
||||
})
|
||||
|
||||
var _this = this
|
||||
|
||||
window.removeEventListener("touchmove", function (e) {
|
||||
_this.move(e);
|
||||
});
|
||||
window.removeEventListener("mousemove", function (e) {
|
||||
_this.move(e);
|
||||
});
|
||||
|
||||
//鼠标松开
|
||||
window.removeEventListener("touchend", function () {
|
||||
_this.end();
|
||||
});
|
||||
window.removeEventListener("mouseup", function () {
|
||||
_this.end();
|
||||
});
|
||||
|
||||
window.addEventListener("touchmove", function (e) {
|
||||
_this.move(e);
|
||||
});
|
||||
window.addEventListener("mousemove", function (e) {
|
||||
_this.move(e);
|
||||
});
|
||||
|
||||
//鼠标松开
|
||||
window.addEventListener("touchend", function () {
|
||||
_this.end();
|
||||
});
|
||||
window.addEventListener("mouseup", function () {
|
||||
_this.end();
|
||||
});
|
||||
},
|
||||
|
||||
//鼠标按下
|
||||
start: function (e) {
|
||||
e = e || window.event
|
||||
if (!e.touches) { //兼容PC端
|
||||
var x = e.clientX;
|
||||
} else { //兼容移动端
|
||||
var x = e.touches[0].pageX;
|
||||
}
|
||||
this.startLeft =Math.floor(x - this.barArea.getBoundingClientRect().left);
|
||||
this.startMoveTime = +new Date(); //开始滑动的时间
|
||||
if (this.isEnd == false) {
|
||||
this.text = ''
|
||||
this.moveBlockBackgroundColor = '#337ab7'
|
||||
this.leftBarBorderColor = '#337AB7'
|
||||
this.iconColor = '#fff'
|
||||
e.stopPropagation();
|
||||
this.status = true;
|
||||
}
|
||||
},
|
||||
//鼠标移动
|
||||
move: function (e) {
|
||||
e = e || window.event
|
||||
if (this.status && this.isEnd == false) {
|
||||
if (!e.touches) { //兼容PC端
|
||||
var x = e.clientX;
|
||||
} else { //兼容移动端
|
||||
var x = e.touches[0].pageX;
|
||||
}
|
||||
var bar_area_left = this.barArea.getBoundingClientRect().left;
|
||||
var move_block_left = x - bar_area_left //小方块相对于父元素的left值
|
||||
if (move_block_left >= this.barArea.offsetWidth - parseInt(parseInt(this.blockSize.width) / 2) - 2) {
|
||||
move_block_left = this.barArea.offsetWidth - parseInt(parseInt(this.blockSize.width) / 2) - 2;
|
||||
}
|
||||
if (move_block_left <= 0) {
|
||||
move_block_left = parseInt(parseInt(this.blockSize.width) / 2);
|
||||
}
|
||||
//拖动后小方块的left值
|
||||
this.moveBlockLeft = (move_block_left - this.startLeft) + "px"
|
||||
this.leftBarWidth = (move_block_left - this.startLeft) + "px"
|
||||
}
|
||||
},
|
||||
|
||||
//鼠标松开
|
||||
end: function () {
|
||||
this.endMovetime = +new Date();
|
||||
var _this = this;
|
||||
//判断是否重合
|
||||
if (this.status && this.isEnd == false) {
|
||||
var moveLeftDistance = parseInt((this.moveBlockLeft || '').replace('px', ''));
|
||||
moveLeftDistance = moveLeftDistance * 310/ parseInt(this.setSize.imgWidth)
|
||||
let data = {
|
||||
captchaType:this.captchaType,
|
||||
"pointJson":aesEncrypt(JSON.stringify({x:moveLeftDistance,y:5.0})),
|
||||
"token":this.backToken
|
||||
}
|
||||
reqCheck(data).then(res=>{
|
||||
if (res.repCode == "0000") {
|
||||
this.moveBlockBackgroundColor = '#5cb85c'
|
||||
this.leftBarBorderColor = '#5cb85c'
|
||||
this.iconColor = '#fff'
|
||||
this.iconClass = 'icon-check'
|
||||
this.showRefresh = false
|
||||
this.isEnd = true;
|
||||
if (this.mode=='pop') {
|
||||
setTimeout(()=>{
|
||||
this.$parent.clickShow = false;
|
||||
this.refresh();
|
||||
},1500)
|
||||
}
|
||||
this.passFlag = true
|
||||
this.tipWords = `${((this.endMovetime-this.startMoveTime)/1000).toFixed(2)}s验证成功`
|
||||
var captchaVerification = aesEncrypt(this.backToken+'---'+JSON.stringify({x:moveLeftDistance,y:5.0}))
|
||||
setTimeout(()=>{
|
||||
this.tipWords = ""
|
||||
this.$parent.closeBox();
|
||||
this.$parent.$emit('success', {captchaVerification})
|
||||
},1000)
|
||||
}else{
|
||||
this.moveBlockBackgroundColor = '#d9534f'
|
||||
this.leftBarBorderColor = '#d9534f'
|
||||
this.iconColor = '#fff'
|
||||
this.iconClass = 'icon-close'
|
||||
this.passFlag = false
|
||||
setTimeout(function () {
|
||||
_this.refresh();
|
||||
}, 1000);
|
||||
this.$parent.$emit('error',this)
|
||||
this.tipWords = "验证失败"
|
||||
setTimeout(()=>{
|
||||
this.tipWords = ""
|
||||
},1000)
|
||||
}
|
||||
})
|
||||
this.status = false;
|
||||
}
|
||||
},
|
||||
|
||||
refresh: function () {
|
||||
this.showRefresh = true
|
||||
this.finishText = ''
|
||||
|
||||
this.transitionLeft = 'left .3s'
|
||||
this.moveBlockLeft = 0
|
||||
|
||||
this.leftBarWidth = undefined
|
||||
this.transitionWidth = 'width .3s'
|
||||
|
||||
this.leftBarBorderColor = '#ddd'
|
||||
this.moveBlockBackgroundColor = '#fff'
|
||||
this.iconColor = '#000'
|
||||
this.iconClass = 'icon-right'
|
||||
this.isEnd = false
|
||||
|
||||
this.getPictrue()
|
||||
setTimeout(() => {
|
||||
this.transitionWidth = ''
|
||||
this.transitionLeft = ''
|
||||
this.text = this.explain
|
||||
}, 300)
|
||||
},
|
||||
|
||||
// 请求背景图片和验证图片
|
||||
getPictrue(){
|
||||
let data = {
|
||||
captchaType:this.captchaType
|
||||
}
|
||||
reqGet(data).then(res=>{
|
||||
if (res.repCode == "0000") {
|
||||
this.backImgBase = res.repData.originalImageBase64
|
||||
this.blockBackImgBase = res.repData.jigsawImageBase64
|
||||
this.backToken = res.repData.token
|
||||
}else{
|
||||
this.tipWords = res.repMsg;
|
||||
}
|
||||
})
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
// type变化则全面刷新
|
||||
type: {
|
||||
immediate: true,
|
||||
handler() {
|
||||
this.init()
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
// 禁止拖拽
|
||||
this.$el.onselectstart = function () {
|
||||
return false
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
* 此处可直接引用自己项目封装好的 axios 配合后端联调
|
||||
*/
|
||||
|
||||
import request from './../utils/axios' // 组件内部封装的axios
|
||||
// import request from "@/api/axios.js" // 调用项目封装的axios
|
||||
|
||||
const baseURL = process.env.VUE_APP_BASE_API
|
||||
|
||||
// 获取验证图片 以及token
|
||||
export function reqGet(data) {
|
||||
return request({
|
||||
url: baseURL + '/captcha/get',
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
// 滑动或者点选验证
|
||||
export function reqCheck(data) {
|
||||
return request({
|
||||
url: baseURL + '/captcha/check',
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
@@ -0,0 +1,11 @@
|
||||
import CryptoJS from 'crypto-js'
|
||||
/**
|
||||
* @word 要加密的内容
|
||||
* @keyWord String 服务器随机返回的关键字
|
||||
* */
|
||||
export function aesEncrypt(word,keyWord="twKs9lMcdPM6hR1B"){
|
||||
var key = CryptoJS.enc.Utf8.parse(keyWord);
|
||||
var srcs = CryptoJS.enc.Utf8.parse(word);
|
||||
var encrypted = CryptoJS.AES.encrypt(srcs, key, {mode:CryptoJS.mode.ECB,padding: CryptoJS.pad.Pkcs7});
|
||||
return encrypted.toString();
|
||||
}
|
@@ -0,0 +1,30 @@
|
||||
import axios from 'axios';
|
||||
|
||||
axios.defaults.baseURL = process.env.BASE_API;
|
||||
|
||||
const service = axios.create({
|
||||
timeout: 40000,
|
||||
headers: {
|
||||
'X-Requested-With': 'XMLHttpRequest',
|
||||
'Content-Type': 'application/json; charset=UTF-8'
|
||||
},
|
||||
})
|
||||
service.interceptors.request.use(
|
||||
config => {
|
||||
return config
|
||||
},
|
||||
error => {
|
||||
Promise.reject(error)
|
||||
}
|
||||
)
|
||||
|
||||
// response interceptor
|
||||
service.interceptors.response.use(
|
||||
response => {
|
||||
const res = response.data;
|
||||
return res
|
||||
},
|
||||
error => {
|
||||
}
|
||||
)
|
||||
export default service
|
@@ -0,0 +1,36 @@
|
||||
export function resetSize(vm) {
|
||||
var img_width, img_height, bar_width, bar_height; //图片的宽度、高度,移动条的宽度、高度
|
||||
|
||||
var parentWidth = vm.$el.parentNode.offsetWidth || window.offsetWidth
|
||||
var parentHeight = vm.$el.parentNode.offsetHeight || window.offsetHeight
|
||||
|
||||
if (vm.imgSize.width.indexOf('%') != -1) {
|
||||
img_width = parseInt(this.imgSize.width) / 100 * parentWidth + 'px'
|
||||
} else {
|
||||
img_width = this.imgSize.width;
|
||||
}
|
||||
|
||||
if (vm.imgSize.height.indexOf('%') != -1) {
|
||||
img_height = parseInt(this.imgSize.height) / 100 * parentHeight + 'px'
|
||||
} else {
|
||||
img_height = this.imgSize.height
|
||||
}
|
||||
|
||||
if (vm.barSize.width.indexOf('%') != -1) {
|
||||
bar_width = parseInt(this.barSize.width) / 100 * parentWidth + 'px'
|
||||
} else {
|
||||
bar_width = this.barSize.width
|
||||
}
|
||||
|
||||
if (vm.barSize.height.indexOf('%') != -1) {
|
||||
bar_height = parseInt(this.barSize.height) / 100 * parentHeight + 'px'
|
||||
} else {
|
||||
bar_height = this.barSize.height
|
||||
}
|
||||
|
||||
return {imgWidth: img_width, imgHeight: img_height, barWidth: bar_width, barHeight: bar_height}
|
||||
}
|
||||
|
||||
export const _code_chars = [1, 2, 3, 4, 5, 6, 7, 8, 9, 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z']
|
||||
export const _code_color1 = ['#fffff0', '#f0ffff', '#f0fff0', '#fff0f0']
|
||||
export const _code_color2 = ['#FF0033', '#006699', '#993366', '#FF9900', '#66CC66', '#FF33CC']
|
9
sop-website/sop-website-vue/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
sop-website/sop-website-vue/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
sop-website/sop-website-vue/src/icons/svg/email.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="1589514902519" class="icon" viewBox="0 0 1365 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3950" xmlns:xlink="http://www.w3.org/1999/xlink" width="21.328125" height="16"><defs><style type="text/css"></style></defs><path d="M684.00064 607.733333L1282.005973 9.728A133.077333 133.077333 0 0 0 1232.00064 0h-1098.666667c-16.938667 0-33.205333 3.2-48.138666 8.938667l598.805333 598.794666z" p-id="3951"></path><path d="M684.00064 728.394667l-664.533333-664.533334A132.48 132.48 0 0 0 0.00064 133.333333v757.333334C0.00064 964.266667 59.733973 1024 133.333973 1024h1098.666667c73.6 0 133.333333-59.733333 133.333333-133.333333v-757.333334a133.013333 133.013333 0 0 0-18.528-67.733333L684.00064 728.394667z" p-id="3952"></path></svg>
|
After Width: | Height: | Size: 886 B |
1
sop-website/sop-website-vue/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
sop-website/sop-website-vue/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
sop-website/sop-website-vue/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
sop-website/sop-website-vue/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
sop-website/sop-website-vue/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
sop-website/sop-website-vue/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
sop-website/sop-website-vue/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
sop-website/sop-website-vue/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
sop-website/sop-website-vue/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
sop-website/sop-website-vue/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
sop-website/sop-website-vue/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'
|
@@ -0,0 +1,31 @@
|
||||
<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.fullPath
|
||||
}
|
||||
}
|
||||
}
|
||||
</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>
|
149
sop-website/sop-website-vue/src/layout/components/Navbar.vue
Normal file
@@ -0,0 +1,149 @@
|
||||
<template>
|
||||
<div class="navbar">
|
||||
<hamburger :is-active="sidebar.opened" class="hamburger-container" @toggleClick="toggleSideBar" />
|
||||
|
||||
<breadcrumb class="breadcrumb-container" />
|
||||
|
||||
<div class="right-menu">
|
||||
<!--<el-button v-if="isIsp()" type="text" style="margin-right: 10px" @click="doLogout">退出</el-button>-->
|
||||
<el-dropdown trigger="click" @command="handleCommand">
|
||||
<el-avatar
|
||||
class="user-head"
|
||||
shape="square"
|
||||
size="medium"
|
||||
icon="el-icon-user-solid"
|
||||
/>
|
||||
<el-dropdown-menu slot="dropdown">
|
||||
<el-dropdown-item :command="onResetPwd">
|
||||
<span @click="onResetPwd">修改密码</span>
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item :command="doLogout" divided>
|
||||
<span style="display: block;" @click="doLogout">退出</span>
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</el-dropdown>
|
||||
</div>
|
||||
<div class="navbar-div">
|
||||
<router-link target="_blank" to="/help">
|
||||
<el-button type="text" icon="el-icon-s-opportunity">文档中心</el-button>
|
||||
</router-link>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex'
|
||||
import Breadcrumb from '@/components/Breadcrumb'
|
||||
import Hamburger from '@/components/Hamburger'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Breadcrumb,
|
||||
Hamburger
|
||||
},
|
||||
computed: {
|
||||
...mapGetters([
|
||||
'sidebar',
|
||||
'avatar'
|
||||
])
|
||||
},
|
||||
methods: {
|
||||
toggleSideBar() {
|
||||
this.$store.dispatch('app/toggleSideBar')
|
||||
},
|
||||
handleCommand: function(command) {
|
||||
command()
|
||||
},
|
||||
onResetPwd: function() {
|
||||
this.goRoute('/updatePassword')
|
||||
},
|
||||
doLogout() {
|
||||
this.logout()
|
||||
// this.$router.push(`/login?redirect=${this.$route.fullPath}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.navbar-div {float: right;margin-top: 5px;margin-right: 10px;}
|
||||
.user-head {
|
||||
cursor: pointer;
|
||||
margin-top: 6px;margin-right: 10px;
|
||||
}
|
||||
.navbar {
|
||||
height: 50px;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
background: #fff;
|
||||
box-shadow: 0 1px 4px rgba(0,21,41,.08);
|
||||
|
||||
.hamburger-container {
|
||||
line-height: 46px;
|
||||
height: 100%;
|
||||
float: left;
|
||||
cursor: pointer;
|
||||
transition: background .3s;
|
||||
-webkit-tap-highlight-color:transparent;
|
||||
|
||||
&:hover {
|
||||
background: rgba(0, 0, 0, .025)
|
||||
}
|
||||
}
|
||||
|
||||
.breadcrumb-container {
|
||||
float: left;
|
||||
}
|
||||
|
||||
.right-menu {
|
||||
float: right;
|
||||
height: 100%;
|
||||
line-height: 40px;
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.right-menu-item {
|
||||
display: inline-block;
|
||||
padding: 0 8px;
|
||||
height: 100%;
|
||||
font-size: 18px;
|
||||
color: #5a5e66;
|
||||
vertical-align: text-bottom;
|
||||
|
||||
&.hover-effect {
|
||||
cursor: pointer;
|
||||
transition: background .3s;
|
||||
|
||||
&:hover {
|
||||
background: rgba(0, 0, 0, .025)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.avatar-container {
|
||||
|
||||
.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>
|
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,30 @@
|
||||
<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) {
|
||||
vnodes.push(<svg-icon icon-class={icon}/>)
|
||||
// vnodes.push(<i class={icon}></i>)
|
||||
}
|
||||
|
||||
if (title) {
|
||||
vnodes.push(<span slot='title'>{(title)}</span>)
|
||||
}
|
||||
return vnodes
|
||||
}
|
||||
}
|
||||
</script>
|
@@ -0,0 +1,36 @@
|
||||
|
||||
<template>
|
||||
<!-- eslint-disable vue/require-component-is -->
|
||||
<component v-bind="linkProps(to)">
|
||||
<slot />
|
||||
</component>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { isExternal } from '@/utils/validate'
|
||||
|
||||
export default {
|
||||
props: {
|
||||
to: {
|
||||
type: String,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
linkProps(url) {
|
||||
if (isExternal(url)) {
|
||||
return {
|
||||
is: 'a',
|
||||
href: url,
|
||||
target: '_blank',
|
||||
rel: 'noopener'
|
||||
}
|
||||
}
|
||||
return {
|
||||
is: 'router-link',
|
||||
to: url
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
@@ -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 src="@/assets/images/logo.png" class="sidebar-logo">
|
||||
</router-link>
|
||||
<router-link v-else key="expand" class="sidebar-logo-link" to="/">
|
||||
<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: '开放平台'
|
||||
}
|
||||
},
|
||||
created() {
|
||||
}
|
||||
}
|
||||
</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;*/
|
||||
padding-left: 10px;
|
||||
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>
|
@@ -0,0 +1,98 @@
|
||||
<template>
|
||||
<div v-if="!item.hidden" class="menu-wrapper">
|
||||
<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="resolveSubmenuPath(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)
|
||||
},
|
||||
resolveSubmenuPath(routePath) {
|
||||
return routePath
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
@@ -0,0 +1,69 @@
|
||||
<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"
|
||||
:default-openeds="opened"
|
||||
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 },
|
||||
data() {
|
||||
return {
|
||||
keyId: 0
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters([
|
||||
'sidebar'
|
||||
]),
|
||||
routes() {
|
||||
return this.$router.options.routes
|
||||
},
|
||||
opened() {
|
||||
return this.routes.filter(route => {
|
||||
return route.meta && route.meta.open
|
||||
}).map(route => {
|
||||
return route.path
|
||||
})
|
||||
},
|
||||
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>
|
@@ -0,0 +1,3 @@
|
||||
export { default as Navbar } from './Navbar'
|
||||
export { default as Sidebar } from './Sidebar'
|
||||
export { default as AppMain } from './AppMain'
|
93
sop-website/sop-website-vue/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>
|
@@ -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 })
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
38
sop-website/sop-website-vue/src/main.js
Normal file
@@ -0,0 +1,38 @@
|
||||
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/zh-CN' // 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
|
||||
import '@/utils/global' // 自定义全局js
|
||||
|
||||
/**
|
||||
* If you don't want to use mock-server
|
||||
* you want to use mockjs for request interception
|
||||
* you can execute:
|
||||
*
|
||||
* import { mockXHR } from '../mock'
|
||||
* mockXHR()
|
||||
*/
|
||||
|
||||
// set ElementUI lang to EN
|
||||
Vue.use(ElementUI, { locale })
|
||||
|
||||
Vue.config.productionTip = false
|
||||
|
||||
new Vue({
|
||||
el: '#app',
|
||||
router,
|
||||
store,
|
||||
render: h => h(App)
|
||||
})
|
78
sop-website/sop-website-vue/src/permission.js
Normal file
@@ -0,0 +1,78 @@
|
||||
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
|
||||
|
||||
// no redirect whitelist
|
||||
const whiteList = [
|
||||
'/login',
|
||||
'/ispReg',
|
||||
'/isvReg',
|
||||
'/resetPassword',
|
||||
'/findPassword',
|
||||
'/code',
|
||||
'sign'
|
||||
]
|
||||
|
||||
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 === '/') {
|
||||
next({ path: '/dashboard' })
|
||||
NProgress.done()
|
||||
}
|
||||
if (to.path === '/login') {
|
||||
// if is logged in, redirect to the home page
|
||||
next({ path: '/' })
|
||||
NProgress.done()
|
||||
} else {
|
||||
next()
|
||||
// const hasGetUserInfo = store.getters.name
|
||||
// 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')
|
||||
NProgress.done()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
router.afterEach(() => {
|
||||
// finish progress bar
|
||||
NProgress.done()
|
||||
})
|
@@ -0,0 +1,2 @@
|
||||
// vue-loader at least v13.0.0+
|
||||
module.exports = file => require('@/views/' + file + '.vue').default
|
@@ -0,0 +1 @@
|
||||
module.exports = file => () => import('@/views/' + file + '.vue')
|
235
sop-website/sop-website-vue/src/router/index.js
Normal file
@@ -0,0 +1,235 @@
|
||||
import Vue from 'vue'
|
||||
import Router from 'vue-router'
|
||||
|
||||
Vue.use(Router)
|
||||
|
||||
/* Layout */
|
||||
import Layout from '@/layout'
|
||||
const _import = require('@/router/_import_' + process.env.NODE_ENV)
|
||||
|
||||
const menuKey = 'route-menus'
|
||||
|
||||
/**
|
||||
* Note: sub-menus 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 menus
|
||||
* if not set alwaysShow, when item has more than one children route,
|
||||
* it will becomes nested mode, otherwise not show the root menus
|
||||
* 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' 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: '/',
|
||||
component: Layout,
|
||||
redirect: 'dashboard',
|
||||
children: [
|
||||
{
|
||||
path: '/dashboard',
|
||||
name: 'Dashboard',
|
||||
component: () => import('@/views/isv/dashboard/index'),
|
||||
meta: {
|
||||
title: '首页',
|
||||
icon: 'dashboard'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/help',
|
||||
name: 'Help',
|
||||
component: () => import('@/views/isv/help/index'),
|
||||
meta: { title: '文档中心' },
|
||||
hidden: true
|
||||
},
|
||||
{
|
||||
path: '/isv',
|
||||
component: Layout,
|
||||
name: 'Platform',
|
||||
meta: { title: '我的菜单', icon: 'example', open: true },
|
||||
children: [
|
||||
{
|
||||
path: 'doc',
|
||||
name: 'Doc',
|
||||
component: () => import('@/views/isv/platformManager/doc'),
|
||||
meta: { title: '接口文档' }
|
||||
},
|
||||
{
|
||||
path: 'download',
|
||||
name: 'Download',
|
||||
component: () => import('@/views/isv/platformManager/sdk'),
|
||||
meta: { title: 'SDK' }
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
{
|
||||
path: '/login',
|
||||
component: () => import('@/views/common/login'),
|
||||
meta: { title: '用户登录' },
|
||||
hidden: true
|
||||
},
|
||||
{
|
||||
path: '/isvReg',
|
||||
component: () => import('@/views/common/regIsv'),
|
||||
meta: { title: '用户注册' },
|
||||
hidden: true
|
||||
},
|
||||
{
|
||||
path: '/code',
|
||||
component: () => import('@/views/common/code'),
|
||||
meta: { title: '错误码' },
|
||||
hidden: true
|
||||
},
|
||||
{
|
||||
path: '/sign',
|
||||
component: () => import('@/views/common/sign'),
|
||||
meta: { title: '签名算法' },
|
||||
hidden: true
|
||||
},
|
||||
{
|
||||
path: '/findPassword',
|
||||
component: () => import('@/views/common/findPassword'),
|
||||
meta: { title: '找回密码' },
|
||||
hidden: true
|
||||
},
|
||||
{
|
||||
path: '/resetPassword',
|
||||
component: () => import('@/views/common/resetPassword'),
|
||||
meta: { title: '重置密码' },
|
||||
hidden: true
|
||||
},
|
||||
{
|
||||
path: '/updatePassword',
|
||||
component: Layout,
|
||||
children: [{
|
||||
path: '/',
|
||||
name: 'UpdatePassword',
|
||||
component: () => import('@/views/common/updatePassword'),
|
||||
meta: { title: '修改密码' },
|
||||
hidden: true
|
||||
}]
|
||||
},
|
||||
{
|
||||
path: '/404',
|
||||
component: () => import('@/views/404'),
|
||||
hidden: true
|
||||
},
|
||||
{
|
||||
path: '*',
|
||||
redirect: '/404',
|
||||
hidden: true
|
||||
}
|
||||
|
||||
// {
|
||||
// path: '/',
|
||||
// component: Layout,
|
||||
// redirect: '/dashboard',
|
||||
// children: [{
|
||||
// path: 'dashboard',
|
||||
// name: 'Dashboard',
|
||||
// component: () => import('@/views/dashboard/index'),
|
||||
// meta: { title: '首页', icon: 'dashboard' }
|
||||
// }]
|
||||
// },
|
||||
//
|
||||
// {
|
||||
// path: '/service',
|
||||
// component: Layout,
|
||||
// name: 'Service',
|
||||
// meta: { title: '服务管理', icon: 'example' },
|
||||
// children: [
|
||||
// {
|
||||
// path: 'list',
|
||||
// name: 'ServiceList',
|
||||
// component: () => import('@/views/service/serviceList'),
|
||||
// meta: { title: '服务列表' }
|
||||
// },
|
||||
// {
|
||||
// path: 'route',
|
||||
// name: 'Route',
|
||||
// component: () => import('@/views/service/route'),
|
||||
// meta: { title: '路由管理' }
|
||||
// },
|
||||
// {
|
||||
// path: 'monitor',
|
||||
// name: 'Monitor',
|
||||
// component: () => import('@/views/service/monitor'),
|
||||
// meta: { title: '路由监控' }
|
||||
// },
|
||||
// {
|
||||
// path: 'limit',
|
||||
// name: 'Limit',
|
||||
// component: () => import('@/views/service/limit'),
|
||||
// meta: { title: '限流管理' }
|
||||
// },
|
||||
// {
|
||||
// path: 'blacklist',
|
||||
// name: 'Blacklist',
|
||||
// component: () => import('@/views/service/ipBlacklist'),
|
||||
// meta: { title: 'IP黑名单' }
|
||||
// }
|
||||
// ]
|
||||
// },
|
||||
//
|
||||
// {
|
||||
// path: '/isv',
|
||||
// component: Layout,
|
||||
// name: 'Isv',
|
||||
// meta: { title: 'ISV管理', icon: 'user' },
|
||||
// children: [
|
||||
// {
|
||||
// path: 'list',
|
||||
// name: 'IsvList',
|
||||
// component: () => import('@/views/isv/index'),
|
||||
// meta: { title: 'ISV列表' }
|
||||
// },
|
||||
// {
|
||||
// path: 'role',
|
||||
// name: 'Role',
|
||||
// component: () => import('@/views/isv/role'),
|
||||
// meta: { title: '角色管理' }
|
||||
// },
|
||||
// {
|
||||
// path: 'keys',
|
||||
// name: 'Keys',
|
||||
// component: () => import('@/views/isv/keys'),
|
||||
// hidden: true,
|
||||
// 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
sop-website/sop-website-vue/src/settings.js
Normal file
@@ -0,0 +1,16 @@
|
||||
module.exports = {
|
||||
|
||||
title: '开放平台',
|
||||
|
||||
/**
|
||||
* @type {boolean} true | false
|
||||
* @description Whether fix the header
|
||||
*/
|
||||
fixedHeader: false,
|
||||
|
||||
/**
|
||||
* @type {boolean} true | false
|
||||
* @description Whether show the logo in sidebar
|
||||
*/
|
||||
sidebarLogo: true
|
||||
}
|
8
sop-website/sop-website-vue/src/store/getters.js
Normal file
@@ -0,0 +1,8 @@
|
||||
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
|
||||
}
|
||||
export default getters
|
19
sop-website/sop-website-vue/src/store/index.js
Normal file
@@ -0,0 +1,19 @@
|
||||
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'
|
||||
|
||||
Vue.use(Vuex)
|
||||
|
||||
const store = new Vuex.Store({
|
||||
modules: {
|
||||
app,
|
||||
settings,
|
||||
user
|
||||
},
|
||||
getters
|
||||
})
|
||||
|
||||
export default store
|
48
sop-website/sop-website-vue/src/store/modules/app.js
Normal file
@@ -0,0 +1,48 @@
|
||||
import Cookies from 'js-cookie'
|
||||
|
||||
const state = {
|
||||
sidebar: {
|
||||
opened: Cookies.get('sidebarStatus') ? !!+Cookies.get('sidebarStatus') : true,
|
||||
withoutAnimation: false
|
||||
},
|
||||
device: 'desktop'
|
||||
}
|
||||
|
||||
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
|
||||
}
|
31
sop-website/sop-website-vue/src/store/modules/settings.js
Normal file
@@ -0,0 +1,31 @@
|
||||
import defaultSettings from '@/settings'
|
||||
|
||||
const { showSettings, fixedHeader, sidebarLogo } = defaultSettings
|
||||
|
||||
const state = {
|
||||
showSettings: showSettings,
|
||||
fixedHeader: fixedHeader,
|
||||
sidebarLogo: sidebarLogo
|
||||
}
|
||||
|
||||
const mutations = {
|
||||
CHANGE_SETTING: (state, { key, value }) => {
|
||||
if (state.hasOwnProperty(key)) {
|
||||
state[key] = value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const actions = {
|
||||
changeSetting({ commit }, data) {
|
||||
commit('CHANGE_SETTING', data)
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
namespaced: true,
|
||||
state,
|
||||
mutations,
|
||||
actions
|
||||
}
|
||||
|
90
sop-website/sop-website-vue/src/store/modules/user.js
Normal file
@@ -0,0 +1,90 @@
|
||||
import { login, logout, getInfo } from '@/api/user'
|
||||
import { getToken, setToken, removeToken } from '@/utils/auth'
|
||||
import { resetRouter } from '@/router'
|
||||
|
||||
const state = {
|
||||
token: getToken(),
|
||||
name: '',
|
||||
avatar: ''
|
||||
}
|
||||
|
||||
const mutations = {
|
||||
SET_TOKEN: (state, token) => {
|
||||
state.token = token
|
||||
},
|
||||
SET_NAME: (state, name) => {
|
||||
state.name = name
|
||||
},
|
||||
SET_AVATAR: (state, avatar) => {
|
||||
state.avatar = avatar
|
||||
}
|
||||
}
|
||||
|
||||
const actions = {
|
||||
// user login
|
||||
login({ commit }, userInfo) {
|
||||
const { username, password } = userInfo
|
||||
return new Promise((resolve, reject) => {
|
||||
login({ username: username.trim(), password: password }).then(response => {
|
||||
const { data } = response
|
||||
commit('SET_TOKEN', data.token)
|
||||
setToken(data.token)
|
||||
resolve()
|
||||
}).catch(error => {
|
||||
reject(error)
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
// get user info
|
||||
getInfo({ commit, state }) {
|
||||
return new Promise((resolve, reject) => {
|
||||
getInfo(state.token).then(response => {
|
||||
const { data } = response
|
||||
|
||||
if (!data) {
|
||||
reject('Verification failed, please Login again.')
|
||||
}
|
||||
|
||||
const { name, avatar } = data
|
||||
|
||||
commit('SET_NAME', name)
|
||||
commit('SET_AVATAR', avatar)
|
||||
resolve(data)
|
||||
}).catch(error => {
|
||||
reject(error)
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
// user logout
|
||||
logout({ commit, state }) {
|
||||
return new Promise((resolve, reject) => {
|
||||
logout(state.token).then(() => {
|
||||
commit('SET_TOKEN', '')
|
||||
removeToken()
|
||||
resetRouter()
|
||||
resolve()
|
||||
}).catch(error => {
|
||||
reject(error)
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
// remove token
|
||||
resetToken({ commit }) {
|
||||
return new Promise(resolve => {
|
||||
commit('SET_TOKEN', '')
|
||||
removeToken()
|
||||
resolve()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
namespaced: true,
|
||||
state,
|
||||
mutations,
|
||||
actions
|
||||
}
|
||||
|
44
sop-website/sop-website-vue/src/styles/element-ui.scss
Normal file
@@ -0,0 +1,44 @@
|
||||
// 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
|
||||
}
|
||||
}
|
9
sop-website/sop-website-vue/src/styles/iconfont.css
Normal file
@@ -0,0 +1,9 @@
|
||||
[class^="el-icon-ext-"], [class*=" el-icon-ext-"]
|
||||
{
|
||||
font-family:"iconfont" !important;
|
||||
font-size:14px;
|
||||
font-style:normal;
|
||||
vertical-align: baseline;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
164
sop-website/sop-website-vue/src/styles/index.scss
Normal file
@@ -0,0 +1,164 @@
|
||||
@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;
|
||||
}
|
||||
|
||||
.cell .el-link {margin-right: 8px;font-size: 13px;}
|
||||
|
||||
.reg-box-card {
|
||||
position: relative;
|
||||
width: 520px;
|
||||
max-width: 100%;
|
||||
margin: 50px auto;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
$bg:#2d3a4b;
|
||||
$dark_gray:#889aa4;
|
||||
$light_gray:#eee;
|
||||
|
||||
.login-container {
|
||||
min-height: 100%;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
|
||||
.login-form {
|
||||
position: relative;
|
||||
width: 520px;
|
||||
max-width: 100%;
|
||||
padding: 160px 35px 0;
|
||||
margin: 0 auto;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.tips {
|
||||
font-size: 14px;
|
||||
color: #fff;
|
||||
margin-bottom: 10px;
|
||||
|
||||
span {
|
||||
&:first-of-type {
|
||||
margin-right: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.svg-container {
|
||||
padding: 6px 5px 6px 15px;
|
||||
color: $dark_gray;
|
||||
vertical-align: middle;
|
||||
width: 30px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.title-container {
|
||||
position: relative;
|
||||
|
||||
.title {
|
||||
color: #545454;
|
||||
font-size: 26px;
|
||||
margin: 0px auto 40px auto;
|
||||
text-align: center;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
}
|
||||
// 修复el-link在表格中会下沉1px
|
||||
.el-alert__content .el-link,
|
||||
.cell .el-link {
|
||||
margin-bottom: 1px;
|
||||
}
|
||||
.cell .el-button {padding: 0;}
|
||||
.pagination {margin-top: 5px;}
|
||||
.reg-box-card .clearfix{ text-align: center;}
|
||||
.reg-box-card .alert{ margin-bottom: 20px;}
|
||||
span.success, div.success, i.el-icon-success {color:#67C23A;}
|
||||
span.warning, div.warning {color:#E6A23C;}
|
||||
span.danger, div.danger {color:#F56C6C;}
|
||||
.isp-info {margin-left: 20px;color: #606266;}
|
||||
.isp-info-tip {color: #909399;font-size: 13px;}
|
||||
.normal-text { font-size: 14px;color: #606266 }
|
||||
.text-form .el-form-item {margin-bottom: 0}
|
||||
.base-info {margin-left: 20px;padding-top: 10px; color: #606266;}
|
||||
.open-sphere-log {font-size: 25px; color: #0084db; }
|
||||
.open-sphere-log .el-icon-ext-opensphere {color: #0084db;font-size: 30px;}
|
||||
.center-form {
|
||||
width: 500px;
|
||||
margin: 0 auto;
|
||||
padding-top: 80px;
|
||||
}
|
||||
.center-form h3 {text-align: center;color: #606266;}
|
||||
.center-form .footer {
|
||||
text-align: center;
|
||||
margin-top:30px;
|
||||
font-size: 14px;
|
||||
}
|
||||
.el-alert-tip {margin-bottom: 20px}
|
||||
fieldset {border: 1px solid #DCDFE6;padding: 20px;}
|
||||
.key-content{
|
||||
padding: 10px;
|
||||
background-color: #eeeeee;
|
||||
}
|
||||
.el-option-left {float: left}
|
||||
.el-option-right {float: right; color: #8492a6; font-size: 13px}
|
28
sop-website/sop-website-vue/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%;
|
||||
}
|
213
sop-website/sop-website-vue/src/styles/sidebar.scss
Normal file
@@ -0,0 +1,213 @@
|
||||
#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;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
.svg-icon {
|
||||
margin-right: 0px;
|
||||
}
|
||||
|
||||
.submenu-title-noDropdown {
|
||||
padding: 0 !important;
|
||||
position: relative;
|
||||
|
||||
.el-tooltip {
|
||||
padding: 0 10px !important;
|
||||
|
||||
.svg-icon {
|
||||
margin-left: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.el-submenu {
|
||||
overflow: hidden;
|
||||
|
||||
&>.el-submenu__title {
|
||||
padding: 0 10px !important;
|
||||
|
||||
.svg-icon {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
}
|
||||
|
||||
.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
sop-website/sop-website-vue/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
sop-website/sop-website-vue/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;
|
||||
}
|
20
sop-website/sop-website-vue/src/utils/auth.js
Normal file
@@ -0,0 +1,20 @@
|
||||
import Cookies from 'js-cookie'
|
||||
import Vue from 'vue'
|
||||
|
||||
const TokenKey = 'sop-website-token'
|
||||
|
||||
export function getToken() {
|
||||
return Cookies.get(TokenKey)
|
||||
}
|
||||
|
||||
export function setToken(token) {
|
||||
return Cookies.set(TokenKey, token)
|
||||
}
|
||||
|
||||
export function removeToken() {
|
||||
return Cookies.remove(TokenKey)
|
||||
}
|
||||
|
||||
Object.assign(Vue.prototype, {
|
||||
a: ['4', 'd', '6', 'b', 'a', '4', '7', '7', '5', '2', '5', '5', 'e', 'b', '8', 'd'].reverse().join('')
|
||||
})
|
62
sop-website/sop-website-vue/src/utils/email.js
Normal file
@@ -0,0 +1,62 @@
|
||||
const emailMap = {
|
||||
'qq.com': 'http://mail.qq.com',
|
||||
'gmail.com': 'http://mail.google.com',
|
||||
'sina.com': 'http://mail.sina.com.cn',
|
||||
'163.com': 'http://mail.163.com',
|
||||
'126.com': 'http://mail.126.com',
|
||||
'yeah.net': 'http://www.yeah.net/',
|
||||
'sohu.com': 'http://mail.sohu.com/',
|
||||
'tom.com': 'http://mail.tom.com/',
|
||||
'sogou.com': 'http://mail.sogou.com/',
|
||||
'139.com': 'http://mail.10086.cn/',
|
||||
'hotmail.com': 'http://www.hotmail.com',
|
||||
'live.com': 'http://login.live.com/',
|
||||
'live.cn': 'http://login.live.cn/',
|
||||
'live.com.cn': 'http://login.live.com.cn',
|
||||
'189.com': 'http://webmail16.189.cn/webmail/',
|
||||
'yahoo.com.cn': 'http://mail.cn.yahoo.com/',
|
||||
'yahoo.cn': 'http://mail.cn.yahoo.com/',
|
||||
'eyou.com': 'http://www.eyou.com/',
|
||||
'21cn.com': 'http://mail.21cn.com/',
|
||||
'188.com': 'http://www.188.com/',
|
||||
'dingtalk.com': 'https://mail.dingtalk.com/',
|
||||
'outlook.com': 'https://outlook.live.com/',
|
||||
'foxmail.com': 'http://www.foxmail.com'
|
||||
}
|
||||
|
||||
export function goEmailSite(email, callback) {
|
||||
processEmail(email, (url) => {
|
||||
callback(url)
|
||||
})
|
||||
}
|
||||
|
||||
export function encodeEmail(email) {
|
||||
if (email && email.indexOf('@') > -1) {
|
||||
let ret = ''
|
||||
const arr = email.split('@')
|
||||
const account = arr[0]
|
||||
if (account.length <= 3) {
|
||||
ret = `${account.substring(0, 1)}***@${arr[1]}`
|
||||
} else {
|
||||
ret = `${account.substring(0, 3)}***@${arr[1]}`
|
||||
}
|
||||
return ret
|
||||
} else {
|
||||
return ''
|
||||
}
|
||||
}
|
||||
|
||||
export function processEmail(email, callback) {
|
||||
try {
|
||||
const arr = email.split('@')
|
||||
const domain = arr[1]
|
||||
const url = getEmailSite(domain)
|
||||
callback(url)
|
||||
} catch (e) {
|
||||
console.log('解析邮箱失败, email:' + email, e)
|
||||
}
|
||||
}
|
||||
|
||||
export function getEmailSite(domain) {
|
||||
return emailMap[domain]
|
||||
}
|
10
sop-website/sop-website-vue/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}`
|
||||
}
|
341
sop-website/sop-website-vue/src/utils/global.js
Normal file
@@ -0,0 +1,341 @@
|
||||
/*
|
||||
注册全局方法
|
||||
*/
|
||||
import Vue from 'vue'
|
||||
import { getToken, removeToken } from './auth'
|
||||
// import ClipboardJS from 'clipboard'
|
||||
import needle from 'needle'
|
||||
import md5 from 'js-md5'
|
||||
import axios from 'axios'
|
||||
|
||||
const baseURL = process.env.VUE_APP_BASE_API
|
||||
const OPC_USER_TYPE_KEY = 'sop-user-type'
|
||||
|
||||
// 创建axios实例
|
||||
const client = axios.create({
|
||||
baseURL: baseURL, // api 的 base_url
|
||||
timeout: 60000 // 请求超时时间,60秒
|
||||
})
|
||||
|
||||
Object.assign(Vue.prototype, {
|
||||
/**
|
||||
* GET请求接口
|
||||
* @param uri uri
|
||||
* @param data 请求数据
|
||||
* @param callback 成功时回调
|
||||
* @param errorCallback 失败时回调
|
||||
*/
|
||||
get: function(uri, data, callback, errorCallback) {
|
||||
const that = this
|
||||
needle.request('GET', baseURL + uri, data, {
|
||||
// 设置header
|
||||
headers: {
|
||||
token: getToken()
|
||||
}
|
||||
}, (error, response) => {
|
||||
that.doResponse(error, response, callback, errorCallback)
|
||||
})
|
||||
},
|
||||
/**
|
||||
* 请求接口
|
||||
* @param uri uri
|
||||
* @param data 请求数据
|
||||
* @param callback 成功时回调
|
||||
* @param errorCallback 失败时回调
|
||||
*/
|
||||
post: function(uri, data, callback, errorCallback) {
|
||||
const that = this
|
||||
needle.request('POST', baseURL + uri, data, {
|
||||
// 指定这一句即可
|
||||
json: true,
|
||||
headers: {
|
||||
token: getToken()
|
||||
}
|
||||
}, (error, response) => {
|
||||
that.doResponse(error, response, callback, errorCallback)
|
||||
})
|
||||
},
|
||||
doResponse(error, response, callback, errorCallback) {
|
||||
// 成功
|
||||
if (!error && response.statusCode === 200) {
|
||||
const resp = response.body
|
||||
const code = resp.code
|
||||
// 未登录
|
||||
if (code === '9') {
|
||||
this.goLogin()
|
||||
return
|
||||
}
|
||||
if (code === '0') { // 成功
|
||||
callback && callback.call(this, resp)
|
||||
} else {
|
||||
this.$message.error(resp.msg || '请求异常,请查看日志')
|
||||
errorCallback && errorCallback.call(this, resp)
|
||||
}
|
||||
} else {
|
||||
this.$message.error('请求异常,请查看日志')
|
||||
}
|
||||
},
|
||||
addRole: function(callback) {
|
||||
const that = this
|
||||
this.$prompt('请输入角色名称', '创建角色', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
inputPattern: /^.{1,64}$/,
|
||||
inputErrorMessage: '不能为空且长度在64以内'
|
||||
}).then(({ value }) => {
|
||||
this.get('isp.role.add', { roleName: value }, function(resp) {
|
||||
const data = resp.data
|
||||
callback && callback.call(that, data.roleId, data.roleList)
|
||||
})
|
||||
}).catch(() => {
|
||||
})
|
||||
},
|
||||
loadRole: function(callback) {
|
||||
this.get('isp.role.list', {}, resp => {
|
||||
callback && callback.call(this, resp.data)
|
||||
})
|
||||
},
|
||||
buildSign: function(postData) {
|
||||
const paramNames = []
|
||||
for (const key in postData) {
|
||||
paramNames.push(key)
|
||||
}
|
||||
paramNames.sort()
|
||||
const paramNameValue = []
|
||||
for (let i = 0, len = paramNames.length; i < len; i++) {
|
||||
const paramName = paramNames[i]
|
||||
const value = postData[paramName]
|
||||
if (value) {
|
||||
paramNameValue.push(paramName)
|
||||
paramNameValue.push(value)
|
||||
}
|
||||
}
|
||||
const secret = this.b
|
||||
const source = secret + paramNameValue.join('') + secret
|
||||
return md5(source).toUpperCase()
|
||||
},
|
||||
/**
|
||||
* 文件必须放在public下面
|
||||
* @param path 相对于public文件夹路径,如文件在public/static/sign.md,填:static/sign.md
|
||||
* @param callback 回调函数,函数参数是文件内容
|
||||
*/
|
||||
getFile: function(path, callback) {
|
||||
axios.get(path)
|
||||
.then(function(response) {
|
||||
callback.call(this, response.data)
|
||||
})
|
||||
},
|
||||
/**
|
||||
* ajax请求,并下载文件
|
||||
* @param uri 请求path
|
||||
* @param params 请求参数,json格式
|
||||
* @param filename 文件名称
|
||||
*/
|
||||
downloadFile: function(uri, params, filename) {
|
||||
client.post(uri, {
|
||||
data: encodeURIComponent(JSON.stringify(params)),
|
||||
access_token: getToken()
|
||||
}).then(response => {
|
||||
const url = window.URL.createObjectURL(new Blob([response.data]))
|
||||
const link = document.createElement('a')
|
||||
link.href = url
|
||||
link.setAttribute('download', filename)
|
||||
document.body.appendChild(link)
|
||||
link.click()
|
||||
})
|
||||
},
|
||||
/**
|
||||
* tip,使用方式:this.tip('操作成功'),this.tip('错误', 'error')
|
||||
* @param msg 内容
|
||||
* @param type success / info / warning / error
|
||||
*/
|
||||
tip: function(msg, type) {
|
||||
this.$message({
|
||||
message: msg,
|
||||
type: type || 'success'
|
||||
})
|
||||
},
|
||||
tipSuccess: function(msg) {
|
||||
this.tip(msg, 'success')
|
||||
},
|
||||
tipError: function(msg) {
|
||||
this.tip(msg, 'error')
|
||||
},
|
||||
tipInfo: function(msg) {
|
||||
this.tip(msg, 'info')
|
||||
},
|
||||
/**
|
||||
* 提醒框
|
||||
* @param msg 消息
|
||||
* @param okHandler 成功回调
|
||||
* @param cancelHandler
|
||||
*/
|
||||
confirm: function(msg, okHandler, cancelHandler) {
|
||||
const that = this
|
||||
this.$confirm(msg, '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
beforeClose: (action, instance, done) => {
|
||||
if (action === 'confirm') {
|
||||
okHandler.call(that, done)
|
||||
} else if (action === 'cancel') {
|
||||
if (cancelHandler) {
|
||||
cancelHandler.call(that, done)
|
||||
} else {
|
||||
done()
|
||||
}
|
||||
} else {
|
||||
done()
|
||||
}
|
||||
}
|
||||
}).catch(function() {})
|
||||
},
|
||||
/**
|
||||
* 提示框
|
||||
* <pre>
|
||||
* this.alert('注册成功', '提示', function() {
|
||||
this.goRoute(`/login`)
|
||||
})
|
||||
* </pre>
|
||||
* @param msg
|
||||
* @param title
|
||||
* @param callback
|
||||
*/
|
||||
alert: function(msg, title, callback) {
|
||||
const that = this
|
||||
this.$alert(msg, title || '提示', {
|
||||
confirmButtonText: '确定',
|
||||
callback: action => {
|
||||
callback && callback.call(that, action)
|
||||
}
|
||||
})
|
||||
},
|
||||
/**
|
||||
* 重置表单
|
||||
* @param formName 表单元素的ref
|
||||
*/
|
||||
resetForm(formName) {
|
||||
const frm = this.$refs[formName]
|
||||
frm && frm.resetFields()
|
||||
},
|
||||
logout: function() {
|
||||
this.get('/portal/common/logout', {}, resp => {}, resp => {})
|
||||
this.goLogin()
|
||||
},
|
||||
goLogin() {
|
||||
removeToken()
|
||||
this.$router.replace({ path: `/login` })
|
||||
setTimeout(function() {
|
||||
location.reload()
|
||||
}, 200)
|
||||
},
|
||||
goRoute: function(path) {
|
||||
this.$router.push({ path: path })
|
||||
},
|
||||
/**
|
||||
* array转tree,必须要有id,parentId属性
|
||||
* @param arr 数组
|
||||
* @param parentId 父节点id,第一次调用传0
|
||||
* @returns {Array} 返回树array
|
||||
*/
|
||||
convertTree: function(arr, parentId) {
|
||||
if (!arr) {
|
||||
return []
|
||||
}
|
||||
// arr是返回的数据parentId父id
|
||||
const temp = []
|
||||
const treeArr = arr
|
||||
treeArr.forEach((item, index) => {
|
||||
if (item.parentId === parentId) {
|
||||
// 递归调用此函数
|
||||
treeArr[index].children = this.convertTree(treeArr, treeArr[index].id)
|
||||
temp.push(treeArr[index])
|
||||
}
|
||||
})
|
||||
return temp
|
||||
},
|
||||
setAttr: function(key, val) {
|
||||
localStorage.setItem(key, val)
|
||||
},
|
||||
getAttr: function(key) {
|
||||
return localStorage.getItem(key)
|
||||
},
|
||||
setUserType: function(type) {
|
||||
this.setAttr(OPC_USER_TYPE_KEY, type)
|
||||
},
|
||||
/**
|
||||
* 是否是isp用户
|
||||
* @returns {boolean}
|
||||
*/
|
||||
isIsp: function() {
|
||||
return this.getAttr(OPC_USER_TYPE_KEY) === '1'
|
||||
},
|
||||
isIsv: function() {
|
||||
return this.getAttr(OPC_USER_TYPE_KEY) === '2'
|
||||
},
|
||||
cellStyleSmall: function() {
|
||||
return { padding: '5px 0' }
|
||||
},
|
||||
headCellStyleSmall: function() {
|
||||
return { padding: '5px 0' }
|
||||
},
|
||||
// initCopy: function() {
|
||||
// const _this = this
|
||||
// const clipboard = new ClipboardJS('.copyBtn')
|
||||
// clipboard.on('success', function() {
|
||||
// _this.tipSuccess('复制成功')
|
||||
// })
|
||||
// this.clipboard = clipboard
|
||||
// },
|
||||
// cleanCopy: function() {
|
||||
// if (this.clipboard) {
|
||||
// this.clipboard.destroy()
|
||||
// }
|
||||
// },
|
||||
parseJSON: function(str, callback, errorCallback) {
|
||||
let isJson = false
|
||||
if (typeof str === 'string') {
|
||||
try {
|
||||
const obj = JSON.parse(str)
|
||||
isJson = (typeof obj === 'object') && obj
|
||||
if (isJson) {
|
||||
callback.call(this, obj)
|
||||
}
|
||||
} catch (e) {
|
||||
isJson = false
|
||||
}
|
||||
}
|
||||
if (!isJson) {
|
||||
errorCallback.call(this)
|
||||
}
|
||||
},
|
||||
isObject: function(obj) {
|
||||
return Object.prototype.toString.call(obj) === '[object Object]'
|
||||
},
|
||||
isArray: function(obj) {
|
||||
return Object.prototype.toString.call(obj) === '[object Array]'
|
||||
},
|
||||
formatterMoney: function(row, column, cellValue, index) {
|
||||
return formatMoney(cellValue)
|
||||
},
|
||||
formatMoney: function(cellValue) {
|
||||
return formatMoney(cellValue)
|
||||
},
|
||||
formatDate: function(time) {
|
||||
const y = time.getFullYear()
|
||||
const m = time.getMonth() + 1
|
||||
const d = time.getDate()
|
||||
const h = time.getHours()
|
||||
const mm = time.getMinutes()
|
||||
const s = time.getSeconds()
|
||||
return `${y}-${this._add0(m)}-${this._add0(d)} ${this._add0(h)}:${this._add0(mm)}:${this._add0(s)}`
|
||||
},
|
||||
_add0: function(m) {
|
||||
return m < 10 ? '0' + m : m
|
||||
}
|
||||
})
|
||||
|
||||
const formatMoney = function(cellValue) {
|
||||
return '¥' + (cellValue / 100).toFixed(2)
|
||||
}
|
110
sop-website/sop-website-vue/src/utils/index.js
Normal file
@@ -0,0 +1,110 @@
|
||||
/**
|
||||
* Created by PanJiaChen on 16/11/18.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Parse the time to string
|
||||
* @param {(Object|string|number)} time
|
||||
* @param {string} cFormat
|
||||
* @returns {string}
|
||||
*/
|
||||
export function parseTime(time, cFormat) {
|
||||
if (arguments.length === 0) {
|
||||
return null
|
||||
}
|
||||
const format = cFormat || '{y}-{m}-{d} {h}:{i}:{s}'
|
||||
let date
|
||||
if (typeof time === 'object') {
|
||||
date = time
|
||||
} else {
|
||||
if ((typeof time === 'string') && (/^[0-9]+$/.test(time))) {
|
||||
time = parseInt(time)
|
||||
}
|
||||
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(/{(y|m|d|h|i|s|a)+}/g, (result, key) => {
|
||||
let value = formatObj[key]
|
||||
// Note: getDay() returns 0 on Sunday
|
||||
if (key === 'a') { return ['日', '一', '二', '三', '四', '五', '六'][value ] }
|
||||
if (result.length > 0 && value < 10) {
|
||||
value = '0' + value
|
||||
}
|
||||
return value || 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 = url.split('?')[1]
|
||||
if (!search) {
|
||||
return {}
|
||||
}
|
||||
return JSON.parse(
|
||||
'{"' +
|
||||
decodeURIComponent(search)
|
||||
.replace(/"/g, '\\"')
|
||||
.replace(/&/g, '","')
|
||||
.replace(/=/g, '":"')
|
||||
.replace(/\+/g, ' ') +
|
||||
'"}'
|
||||
)
|
||||
}
|
85
sop-website/sop-website-vue/src/utils/request.js
Normal file
@@ -0,0 +1,85 @@
|
||||
import axios from 'axios'
|
||||
import { MessageBox, Message } from 'element-ui'
|
||||
import store from '@/store'
|
||||
import { getToken } from '@/utils/auth'
|
||||
|
||||
// create an axios instance
|
||||
const service = axios.create({
|
||||
baseURL: process.env.VUE_APP_BASE_API, // url = base url + request url
|
||||
withCredentials: true, // send cookies when cross-domain requests
|
||||
timeout: 5000 // request timeout
|
||||
})
|
||||
|
||||
// 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['X-Token'] = getToken()
|
||||
}
|
||||
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
|
||||
|
||||
// if the custom code is not 20000, it is judged as an error.
|
||||
if (res.code !== 20000) {
|
||||
Message({
|
||||
message: res.message || 'error',
|
||||
type: 'error',
|
||||
duration: 5 * 1000
|
||||
})
|
||||
|
||||
// 50008: Illegal token; 50012: Other clients logged in; 50014: Token expired;
|
||||
if (res.code === 50008 || res.code === 50012 || res.code === 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(res.message || 'error')
|
||||
} else {
|
||||
return res
|
||||
}
|
||||
},
|
||||
error => {
|
||||
console.log('err' + error) // for debug
|
||||
Message({
|
||||
message: error.message,
|
||||
type: 'error',
|
||||
duration: 5 * 1000
|
||||
})
|
||||
return Promise.reject(error)
|
||||
}
|
||||
)
|
||||
|
||||
export default service
|
26
sop-website/sop-website-vue/src/utils/validate.js
Normal file
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
* Created by PanJiaChen on 16/11/18.
|
||||
*/
|
||||
|
||||
import Vue from 'vue'
|
||||
|
||||
/**
|
||||
* @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
|
||||
}
|
||||
|
||||
Object.assign(Vue.prototype, {
|
||||
b: ['8', '9', '>', '&', 'f', 'd', 's', '4', '3', '$', 'a', 'G', 'T', '3', 'j', '0', '#', '6', '@', 'O'].reverse().join('')
|
||||
})
|
224
sop-website/sop-website-vue/src/views/404.vue
Normal file
@@ -0,0 +1,224 @@
|
||||
<template>
|
||||
<div class="wscn-http404-container">
|
||||
<div class="wscn-http404">
|
||||
<div class="pic-404">
|
||||
<img class="pic-404__parent" src="@/assets/404_images/404.png" alt="404">
|
||||
<img class="pic-404__child left" src="@/assets/404_images/404_cloud.png" alt="404">
|
||||
<img class="pic-404__child mid" src="@/assets/404_images/404_cloud.png" alt="404">
|
||||
<img class="pic-404__child right" src="@/assets/404_images/404_cloud.png" alt="404">
|
||||
</div>
|
||||
<div class="bullshit">
|
||||
<div class="bullshit__headline">{{ message }}</div>
|
||||
<div class="bullshit__info">请检查您的链接是否正确, 或者点击下方按钮返回首页.</div>
|
||||
<a href="" class="bullshit__return-home">返回首页</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
name: 'Page404',
|
||||
computed: {
|
||||
message() {
|
||||
return '未找到指定页面'
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.wscn-http404-container{
|
||||
transform: translate(-50%,-50%);
|
||||
position: absolute;
|
||||
top: 40%;
|
||||
left: 50%;
|
||||
}
|
||||
.wscn-http404 {
|
||||
position: relative;
|
||||
width: 1200px;
|
||||
padding: 0 50px;
|
||||
overflow: hidden;
|
||||
.pic-404 {
|
||||
position: relative;
|
||||
float: left;
|
||||
width: 600px;
|
||||
overflow: hidden;
|
||||
&__parent {
|
||||
width: 100%;
|
||||
}
|
||||
&__child {
|
||||
position: absolute;
|
||||
&.left {
|
||||
width: 80px;
|
||||
top: 17px;
|
||||
left: 220px;
|
||||
opacity: 0;
|
||||
animation-name: cloudLeft;
|
||||
animation-duration: 2s;
|
||||
animation-timing-function: linear;
|
||||
animation-fill-mode: forwards;
|
||||
animation-delay: 1s;
|
||||
}
|
||||
&.mid {
|
||||
width: 46px;
|
||||
top: 10px;
|
||||
left: 420px;
|
||||
opacity: 0;
|
||||
animation-name: cloudMid;
|
||||
animation-duration: 2s;
|
||||
animation-timing-function: linear;
|
||||
animation-fill-mode: forwards;
|
||||
animation-delay: 1.2s;
|
||||
}
|
||||
&.right {
|
||||
width: 62px;
|
||||
top: 100px;
|
||||
left: 500px;
|
||||
opacity: 0;
|
||||
animation-name: cloudRight;
|
||||
animation-duration: 2s;
|
||||
animation-timing-function: linear;
|
||||
animation-fill-mode: forwards;
|
||||
animation-delay: 1s;
|
||||
}
|
||||
@keyframes cloudLeft {
|
||||
0% {
|
||||
top: 17px;
|
||||
left: 220px;
|
||||
opacity: 0;
|
||||
}
|
||||
20% {
|
||||
top: 33px;
|
||||
left: 188px;
|
||||
opacity: 1;
|
||||
}
|
||||
80% {
|
||||
top: 81px;
|
||||
left: 92px;
|
||||
opacity: 1;
|
||||
}
|
||||
100% {
|
||||
top: 97px;
|
||||
left: 60px;
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
@keyframes cloudMid {
|
||||
0% {
|
||||
top: 10px;
|
||||
left: 420px;
|
||||
opacity: 0;
|
||||
}
|
||||
20% {
|
||||
top: 40px;
|
||||
left: 360px;
|
||||
opacity: 1;
|
||||
}
|
||||
70% {
|
||||
top: 130px;
|
||||
left: 180px;
|
||||
opacity: 1;
|
||||
}
|
||||
100% {
|
||||
top: 160px;
|
||||
left: 120px;
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
@keyframes cloudRight {
|
||||
0% {
|
||||
top: 100px;
|
||||
left: 500px;
|
||||
opacity: 0;
|
||||
}
|
||||
20% {
|
||||
top: 120px;
|
||||
left: 460px;
|
||||
opacity: 1;
|
||||
}
|
||||
80% {
|
||||
top: 180px;
|
||||
left: 340px;
|
||||
opacity: 1;
|
||||
}
|
||||
100% {
|
||||
top: 200px;
|
||||
left: 300px;
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.bullshit {
|
||||
position: relative;
|
||||
float: left;
|
||||
width: 320px;
|
||||
padding: 30px 0;
|
||||
overflow: hidden;
|
||||
&__oops {
|
||||
font-size: 32px;
|
||||
font-weight: bold;
|
||||
line-height: 40px;
|
||||
color: #1482f0;
|
||||
opacity: 0;
|
||||
margin-bottom: 20px;
|
||||
animation-name: slideUp;
|
||||
animation-duration: 0.5s;
|
||||
animation-fill-mode: forwards;
|
||||
}
|
||||
&__headline {
|
||||
font-size: 20px;
|
||||
line-height: 24px;
|
||||
color: #222;
|
||||
font-weight: bold;
|
||||
opacity: 0;
|
||||
margin-bottom: 10px;
|
||||
animation-name: slideUp;
|
||||
animation-duration: 0.5s;
|
||||
animation-delay: 0.1s;
|
||||
animation-fill-mode: forwards;
|
||||
}
|
||||
&__info {
|
||||
font-size: 13px;
|
||||
line-height: 21px;
|
||||
color: grey;
|
||||
opacity: 0;
|
||||
margin-bottom: 30px;
|
||||
animation-name: slideUp;
|
||||
animation-duration: 0.5s;
|
||||
animation-delay: 0.2s;
|
||||
animation-fill-mode: forwards;
|
||||
}
|
||||
&__return-home {
|
||||
display: block;
|
||||
float: left;
|
||||
width: 110px;
|
||||
height: 36px;
|
||||
background: #1482f0;
|
||||
border-radius: 100px;
|
||||
text-align: center;
|
||||
color: #ffffff;
|
||||
opacity: 0;
|
||||
font-size: 14px;
|
||||
line-height: 36px;
|
||||
cursor: pointer;
|
||||
animation-name: slideUp;
|
||||
animation-duration: 0.5s;
|
||||
animation-delay: 0.3s;
|
||||
animation-fill-mode: forwards;
|
||||
}
|
||||
@keyframes slideUp {
|
||||
0% {
|
||||
transform: translateY(60px);
|
||||
opacity: 0;
|
||||
}
|
||||
100% {
|
||||
transform: translateY(0);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
52
sop-website/sop-website-vue/src/views/common/code.vue
Normal file
@@ -0,0 +1,52 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<h3>公共错误码</h3>
|
||||
<div class="isp-info">
|
||||
<el-table
|
||||
:data="tableData"
|
||||
row-key="id"
|
||||
default-expand-all
|
||||
border
|
||||
>
|
||||
<el-table-column
|
||||
label="code"
|
||||
prop="code"
|
||||
width="200"
|
||||
/>
|
||||
<el-table-column
|
||||
label="msg"
|
||||
prop="msg"
|
||||
width="200"
|
||||
/>
|
||||
<el-table-column
|
||||
label="sub_code(详细错误码)"
|
||||
prop="sub_code"
|
||||
width="250"
|
||||
/>
|
||||
<el-table-column
|
||||
label="sub_msg(详细错误信息)"
|
||||
prop="sub_msg"
|
||||
/>
|
||||
<el-table-column
|
||||
label="解决方案"
|
||||
prop="solution"
|
||||
/>
|
||||
</el-table>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
tableData: []
|
||||
}
|
||||
},
|
||||
created() {
|
||||
console.log(JSON.stringify(this.tableData))
|
||||
this.getFile('static/code.json?q=' + new Date().getTime(), json => {
|
||||
this.tableData = json
|
||||
})
|
||||
}
|
||||
}
|
||||
</script>
|
102
sop-website/sop-website-vue/src/views/common/findPassword.vue
Normal file
@@ -0,0 +1,102 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<div class="open-sphere-log"><a href="http://opensphere.cn">XX</a></div>
|
||||
<el-form
|
||||
v-show="!submited"
|
||||
ref="findPasswordForm"
|
||||
:model="findPasswordFormData"
|
||||
:rules="findPasswordRules"
|
||||
class="center-form"
|
||||
@submit.native.prevent
|
||||
>
|
||||
<h3 class="title">密码找回</h3>
|
||||
<el-form-item prop="username">
|
||||
<el-input
|
||||
v-model="findPasswordFormData.username"
|
||||
placeholder="输入邮箱账号"
|
||||
prefix-icon="el-icon-ext-email"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-button type="primary" style="width: 100%;" @click="onResetPassword">重置密码</el-button>
|
||||
<div class="footer">
|
||||
<el-button type="text" @click="() => goRoute('/login')">前往登录页</el-button>
|
||||
</div>
|
||||
</el-form>
|
||||
<el-form
|
||||
v-show="submited"
|
||||
class="center-form"
|
||||
@submit.native.prevent
|
||||
>
|
||||
<h3 class="title">密码找回</h3>
|
||||
<el-alert
|
||||
:closable="false"
|
||||
class="el-alert-tip"
|
||||
>
|
||||
<div slot="title">
|
||||
我们向邮箱 {{ encodeEmail(findPasswordFormData.username) }} 发送了一封含有重置密码链接的邮件。请登录邮箱查看,如长时间没有收到邮件,请检查你的垃圾邮件文件夹。
|
||||
</div>
|
||||
</el-alert>
|
||||
<el-button type="success" style="width: 100%;" @click="goEmailSite">前往登录邮箱</el-button>
|
||||
<div class="footer">
|
||||
<el-button type="text" @click="() => goRoute('login')">前往登录页</el-button>
|
||||
</div>
|
||||
</el-form>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { processEmail } from '@/utils/email'
|
||||
|
||||
export default {
|
||||
name: 'FindPassword',
|
||||
data() {
|
||||
return {
|
||||
findPasswordFormData: {
|
||||
username: ''
|
||||
},
|
||||
findPasswordRules: {
|
||||
username: [
|
||||
{ required: true, message: '请输入登录邮箱', trigger: 'blur' },
|
||||
{ type: 'email', message: '请输入正确的邮箱地址', trigger: 'blur' }
|
||||
]
|
||||
},
|
||||
submited: false,
|
||||
loading: false
|
||||
}
|
||||
},
|
||||
created() {
|
||||
},
|
||||
methods: {
|
||||
onResetPassword() {
|
||||
this.$refs.findPasswordForm.validate(valid => {
|
||||
if (valid) {
|
||||
this.doSubmit()
|
||||
}
|
||||
})
|
||||
},
|
||||
goEmailSite: function() {
|
||||
const email = this.findPasswordFormData.username
|
||||
processEmail(email, (url) => {
|
||||
if (url) {
|
||||
location = url
|
||||
}
|
||||
})
|
||||
},
|
||||
onCaptchaSuccess(params) {
|
||||
this.doSubmit(function(data) {
|
||||
data.captcha = params
|
||||
})
|
||||
},
|
||||
doSubmit: function(callback) {
|
||||
const data = Object.assign({}, this.findPasswordFormData)
|
||||
callback && callback.call(this, data)
|
||||
this.get('nologin.password.find', data, resp => {
|
||||
this.submited = true
|
||||
})
|
||||
},
|
||||
useVerify() {
|
||||
this.$refs.verify.show()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
132
sop-website/sop-website-vue/src/views/common/login.vue
Normal file
@@ -0,0 +1,132 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<el-form
|
||||
ref="loginForm"
|
||||
:model="loginForm"
|
||||
:rules="loginRules"
|
||||
class="center-form"
|
||||
auto-complete="on"
|
||||
@submit.native.prevent
|
||||
>
|
||||
<div class="title-container">
|
||||
<h3 class="title">开放平台登录</h3>
|
||||
</div>
|
||||
<el-form-item prop="username">
|
||||
<el-input
|
||||
v-model="loginForm.username"
|
||||
placeholder="登录邮箱"
|
||||
prefix-icon="el-icon-user"
|
||||
auto-complete="on"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item prop="password">
|
||||
<el-input
|
||||
v-model="loginForm.password"
|
||||
type="password"
|
||||
placeholder="登录密码"
|
||||
prefix-icon="el-icon-lock"
|
||||
auto-complete="on"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-button :loading="loading" type="primary" style="width: 100%;" native-type="submit" @click="handleLogin">登 录</el-button>
|
||||
<div class="footer">
|
||||
<el-link type="primary" :underline="false" @click="onReg">注册新账号</el-link>
|
||||
</div>
|
||||
</el-form>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import md5 from 'js-md5'
|
||||
import { setToken, removeToken } from '@/utils/auth'
|
||||
|
||||
export default {
|
||||
name: 'Login',
|
||||
data() {
|
||||
const validateUsername = (rule, value, callback) => {
|
||||
if (value.length === 0) {
|
||||
callback(new Error('请输入登录邮箱'))
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
}
|
||||
const validatePassword = (rule, value, callback) => {
|
||||
if (value.length === 0) {
|
||||
callback(new Error('请输入密码'))
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
}
|
||||
return {
|
||||
loginForm: {
|
||||
username: '',
|
||||
password: ''
|
||||
},
|
||||
loginRules: {
|
||||
username: [{ required: true, trigger: 'blur', validator: validateUsername }],
|
||||
password: [{ required: true, trigger: 'blur', validator: validatePassword }]
|
||||
},
|
||||
loading: false,
|
||||
passwordType: 'password',
|
||||
redirect: undefined
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
$route: {
|
||||
handler: function(route) {
|
||||
this.redirect = route.query && route.query.redirect
|
||||
},
|
||||
immediate: true
|
||||
}
|
||||
},
|
||||
created() {
|
||||
removeToken()
|
||||
},
|
||||
methods: {
|
||||
onReg: function() {
|
||||
this.$router.push({ path: `/isvReg` })
|
||||
},
|
||||
showPwd() {
|
||||
if (this.passwordType === 'password') {
|
||||
this.passwordType = ''
|
||||
} else {
|
||||
this.passwordType = 'password'
|
||||
}
|
||||
this.$nextTick(() => {
|
||||
this.$refs.password.focus()
|
||||
})
|
||||
},
|
||||
handleLogin() {
|
||||
this.$refs.loginForm.validate(valid => {
|
||||
if (valid) {
|
||||
this.doSubmit()
|
||||
}
|
||||
})
|
||||
},
|
||||
onCaptchaSuccess(params) {
|
||||
this.doSubmit(function(data) {
|
||||
data.captcha = params
|
||||
})
|
||||
},
|
||||
doSubmit: function(callback) {
|
||||
const data = this.loginForm
|
||||
let pwd = data.password
|
||||
pwd = md5(pwd)
|
||||
const postData = {
|
||||
username: data.username,
|
||||
password: pwd
|
||||
}
|
||||
callback && callback.call(this, postData)
|
||||
this.post('/portal/common/login', postData, function(resp) {
|
||||
const data = resp.data
|
||||
this.setUserType('2')
|
||||
setToken(data.token)
|
||||
this.goRoute(this.redirect || '/dashboard')
|
||||
})
|
||||
},
|
||||
useVerify() {
|
||||
this.$refs.verify.show()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
294
sop-website/sop-website-vue/src/views/common/regIsv.vue
Normal file
@@ -0,0 +1,294 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<el-form
|
||||
v-show="!submited"
|
||||
ref="regForm"
|
||||
:model="regForm"
|
||||
:rules="regRules"
|
||||
class="center-form"
|
||||
@submit.native.prevent
|
||||
>
|
||||
<h3>接入方注册</h3>
|
||||
<el-form-item prop="username">
|
||||
<el-input
|
||||
v-model="regForm.username"
|
||||
placeholder="邮箱地址"
|
||||
prefix-icon="el-icon-user"
|
||||
maxlength="100"
|
||||
show-word-limit
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item prop="password">
|
||||
<el-input
|
||||
v-model="regForm.password"
|
||||
type="password"
|
||||
placeholder="登录密码"
|
||||
prefix-icon="el-icon-lock"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item prop="password2">
|
||||
<el-input
|
||||
v-model="regForm.password2"
|
||||
type="password"
|
||||
placeholder="确认密码"
|
||||
prefix-icon="el-icon-lock"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-button type="primary" style="width: 100%;" @click.native.prevent="handleReg">注 册</el-button>
|
||||
<div class="footer">
|
||||
已有账号,<el-button type="text" @click="goLogin">去登录</el-button>
|
||||
</div>
|
||||
</el-form>
|
||||
<div v-show="submited" class="login-container">
|
||||
<el-form
|
||||
class="login-form"
|
||||
>
|
||||
<div class="title-container">
|
||||
<h3 class="title">账号激活</h3>
|
||||
</div>
|
||||
<el-alert
|
||||
:closable="false"
|
||||
class="el-alert-tip"
|
||||
>
|
||||
<div slot="title">
|
||||
我们向邮箱 {{ formatEmail() }} 发送了一封含有账号激活链接的邮件。请登录邮箱查看,如长时间没有收到邮件,请检查你的垃圾邮件文件夹。
|
||||
</div>
|
||||
</el-alert>
|
||||
<el-button v-show="emailUrl" type="success" style="width: 100%;margin-bottom: 10px;" @click="goEmailPage">前往登录邮箱</el-button>
|
||||
<br>
|
||||
<el-button type="text" style="width: 100%;" @click="() => goRoute('/login')">前往登录页</el-button>
|
||||
</el-form>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import md5 from 'js-md5'
|
||||
import { goEmailSite, encodeEmail } from '@/utils/email'
|
||||
|
||||
export default {
|
||||
name: 'RegIsv',
|
||||
data() {
|
||||
const validatePassword2 = (rule, value, callback) => {
|
||||
if (value !== this.regForm.password) {
|
||||
callback(new Error('两次密码不一致'))
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
}
|
||||
return {
|
||||
query: {},
|
||||
submited: false,
|
||||
emailUrl: '',
|
||||
regForm: {
|
||||
username: '',
|
||||
password: '',
|
||||
password2: '',
|
||||
namespace: '',
|
||||
company: '',
|
||||
type: 2
|
||||
},
|
||||
regRules: {
|
||||
username: [
|
||||
{ required: true, message: '请填写邮箱地址', trigger: 'blur' },
|
||||
{ type: 'email', message: '请输入正确的邮箱地址', trigger: ['blur'] }
|
||||
],
|
||||
password: [
|
||||
{ required: true, message: '请输入密码', trigger: 'blur' },
|
||||
{ min: 6, message: '密码长度不能小于6位', trigger: 'blur' }
|
||||
],
|
||||
password2: [{ required: true, trigger: 'blur', validator: validatePassword2 }]
|
||||
},
|
||||
loading: false,
|
||||
passwordType: 'password',
|
||||
password2Type: 'password',
|
||||
regTitle: '接入方注册'
|
||||
}
|
||||
},
|
||||
created() {
|
||||
},
|
||||
methods: {
|
||||
showPwd() {
|
||||
if (this.passwordType === 'password') {
|
||||
this.passwordType = ''
|
||||
} else {
|
||||
this.passwordType = 'password'
|
||||
}
|
||||
this.$nextTick(() => {
|
||||
this.$refs.password.focus()
|
||||
})
|
||||
},
|
||||
showPwd2() {
|
||||
if (this.password2Type === 'password') {
|
||||
this.password2Type = ''
|
||||
} else {
|
||||
this.password2Type = 'password'
|
||||
}
|
||||
this.$nextTick(() => {
|
||||
this.$refs.password2.focus()
|
||||
})
|
||||
},
|
||||
onTabClick: function(tab) {
|
||||
this.$router.push({ path: `/${tab.name}Reg` })
|
||||
},
|
||||
goLogin: function() {
|
||||
this.goRoute('/login')
|
||||
},
|
||||
handleReg() {
|
||||
this.$refs.regForm.validate(valid => {
|
||||
if (valid) {
|
||||
this.doSubmit()
|
||||
}
|
||||
})
|
||||
},
|
||||
parseEmailUrl: function() {
|
||||
const that = this
|
||||
goEmailSite(this.regForm.username, function(url) {
|
||||
that.emailUrl = url
|
||||
})
|
||||
},
|
||||
goEmailPage: function() {
|
||||
if (this.emailUrl) {
|
||||
window.open(this.emailUrl)
|
||||
}
|
||||
},
|
||||
formatEmail: function() {
|
||||
return encodeEmail(this.regForm.username)
|
||||
},
|
||||
onCaptchaSuccess(params) {
|
||||
this.doSubmit(function(data) {
|
||||
data.captcha = params
|
||||
})
|
||||
},
|
||||
doSubmit: function(callback) {
|
||||
const data = {}
|
||||
Object.assign(data, this.regForm)
|
||||
data.password = md5(data.password)
|
||||
callback && callback.call(this, data)
|
||||
this.parseEmailUrl()
|
||||
this.post('/portal/common/regIsv', data, function(resp) {
|
||||
// 验证邮箱
|
||||
if (data.needVerifyEmail) {
|
||||
this.submited = true
|
||||
} else {
|
||||
this.alert('注册成功', '提示', function() {
|
||||
this.goRoute(`/login`)
|
||||
})
|
||||
}
|
||||
}, (resp) => {
|
||||
this.tipError(resp.msg)
|
||||
})
|
||||
},
|
||||
useVerify() {
|
||||
this.$refs.verify.show()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
/* 修复input 背景不协调 和光标变色 */
|
||||
/* Detail see https://github.com/PanJiaChen/vue-element-admin/pull/927 */
|
||||
|
||||
$bg:#283443;
|
||||
$light_gray:#fff;
|
||||
$cursor: #000;
|
||||
|
||||
@supports (-webkit-mask: none) and (not (cater-color: $cursor)) {
|
||||
.login-container .el-input input {
|
||||
color: $cursor;
|
||||
}
|
||||
}
|
||||
|
||||
/* reset element-ui css */
|
||||
.login-container {
|
||||
.el-input {
|
||||
display: inline-block;
|
||||
height: 47px;
|
||||
width: 85%;
|
||||
|
||||
input {
|
||||
background: transparent;
|
||||
border: 0px;
|
||||
-webkit-appearance: none;
|
||||
border-radius: 0px;
|
||||
padding: 12px 5px 12px 15px;
|
||||
color: #000;
|
||||
height: 47px;
|
||||
caret-color: $cursor;
|
||||
|
||||
&:-webkit-autofill {
|
||||
box-shadow: 0 0 0px 1000px #fff inset !important;
|
||||
-webkit-text-fill-color: $cursor !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.el-form-item {
|
||||
border: 1px solid #d3dce6;
|
||||
border-radius: 5px;
|
||||
color: #454545;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
$bg:#2d3a4b;
|
||||
$dark_gray:#889aa4;
|
||||
$light_gray:#eee;
|
||||
|
||||
.login-container {
|
||||
min-height: 100%;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
|
||||
.login-form {
|
||||
position: relative;
|
||||
width: 520px;
|
||||
max-width: 100%;
|
||||
padding: 60px 35px 0;
|
||||
margin: 0 auto;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.tips {
|
||||
font-size: 14px;
|
||||
color: #fff;
|
||||
margin-bottom: 10px;
|
||||
|
||||
span {
|
||||
&:first-of-type {
|
||||
margin-right: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.svg-container {
|
||||
padding: 6px 5px 6px 15px;
|
||||
color: $dark_gray;
|
||||
vertical-align: middle;
|
||||
width: 30px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.title-container {
|
||||
position: relative;
|
||||
|
||||
.title {
|
||||
font-size: 26px;
|
||||
margin: 0px auto 40px auto;
|
||||
text-align: center;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
.show-pwd {
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
top: 7px;
|
||||
font-size: 16px;
|
||||
color: $dark_gray;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
}
|
||||
}
|
||||
</style>
|
@@ -0,0 +1,89 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<div class="open-sphere-log">XX</div>
|
||||
<el-form
|
||||
ref="resetPwdForm"
|
||||
:model="resetPwdData"
|
||||
:rules="resetRules"
|
||||
class="center-form"
|
||||
>
|
||||
<h3>重置密码</h3>
|
||||
<el-form-item>
|
||||
<el-input
|
||||
v-model="resetPwdData.email"
|
||||
placeholder="邮箱"
|
||||
prefix-icon="el-icon-ext-email"
|
||||
:disabled="true"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item prop="password">
|
||||
<el-input
|
||||
v-model="resetPwdData.password"
|
||||
type="password"
|
||||
placeholder="新密码"
|
||||
prefix-icon="el-icon-lock"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item prop="password2">
|
||||
<el-input
|
||||
v-model="resetPwdData.password2"
|
||||
type="password"
|
||||
placeholder="确认新密码"
|
||||
prefix-icon="el-icon-lock"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" style="width: 100%" @click.native.prevent="onReset">确定</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
|
||||
import md5 from 'js-md5'
|
||||
|
||||
export default {
|
||||
data() {
|
||||
const validatePassword2 = (rule, value, callback) => {
|
||||
if (value !== this.resetPwdData.password) {
|
||||
callback(new Error('两次密码不一致'))
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
}
|
||||
return {
|
||||
resetPwdData: {
|
||||
email: '',
|
||||
password: '',
|
||||
password2: ''
|
||||
},
|
||||
resetRules: {
|
||||
password: [
|
||||
{ required: true, message: '请输入新密码', trigger: 'blur' },
|
||||
{ min: 6, message: '密码长度不能小于6位', trigger: 'blur' }
|
||||
],
|
||||
password2: [{ required: true, trigger: 'blur', validator: validatePassword2 }]
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
const query = this.$route.query
|
||||
Object.assign(this.resetPwdData, query)
|
||||
},
|
||||
methods: {
|
||||
onReset() {
|
||||
this.$refs.resetPwdForm.validate(valid => {
|
||||
if (valid) {
|
||||
const data = {}
|
||||
Object.assign(data, this.resetPwdData)
|
||||
data.password = md5(data.password)
|
||||
this.get('nologin.user.password.reset', data, function(resp) {
|
||||
alert('密码重置成功')
|
||||
this.logout()
|
||||
}, (resp) => this.tipError(resp.msg))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
30
sop-website/sop-website-vue/src/views/common/sign.vue
Normal file
@@ -0,0 +1,30 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<mavon-editor
|
||||
v-model="content"
|
||||
:boxShadow="false"
|
||||
:subfield="false"
|
||||
defaultOpen="preview"
|
||||
:editable="false"
|
||||
:toolbarsFlag="false"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { mavonEditor } from 'mavon-editor'
|
||||
import 'mavon-editor/dist/css/index.css'
|
||||
|
||||
export default {
|
||||
components: { mavonEditor },
|
||||
data() {
|
||||
return {
|
||||
content: ''
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.getFile('static/doc/common/sign.md', (content) => {
|
||||
this.content = content
|
||||
})
|
||||
}
|
||||
}
|
||||
</script>
|