用户登录问题修改

This commit is contained in:
jiang 2024-09-08 20:13:32 +08:00
parent 312c9ebaec
commit eb9e9db8b1
13 changed files with 452 additions and 166 deletions

View File

@ -25,6 +25,18 @@ export function isLogin(data) {
})
}
export function isAdmin(data) {
return request({
url: '/auth/isAdmin',
headers: {
isToken: false,
repeatSubmit: false
},
method: 'post',
data: data
})
}
export function getPhoneCode(payload) {
return request({
url: '/auth/getPhoneCode',

View File

@ -145,3 +145,12 @@ export function approvalStatus(data) {
data: data
})
}
//用户注册审批
export function checkPasswordStatus(data) {
return request({
url: '/system/user/checkPasswordStatus',
method: 'get'
})
}

View File

@ -12,6 +12,29 @@
<settings/>
</right-panel>
</div>
<el-dialog :title="title" :visible.sync="showChangePasswordDialog" width="30%" :close-on-click-modal="false"
:show-close="false"
>
<el-form ref="form" :model="user" :rules="rules" label-width="80px">
<el-form-item label="旧密码" prop="oldPassword">
<el-input v-model="user.oldPassword" placeholder="请输入旧密码" type="password" show-password/>
</el-form-item>
<el-form-item label="新密码" prop="newPassword">
<el-input v-model="user.newPassword" placeholder="请输入新密码" type="password" show-password/>
</el-form-item>
<el-form-item label="确认密码" prop="confirmPassword">
<el-input v-model="user.confirmPassword" placeholder="请确认新密码" type="password" show-password/>
</el-form-item>
<!-- <el-form-item>
<el-button type="primary" size="mini" @click="submit">保存</el-button>
<el-button type="danger" size="mini" @click="close">关闭</el-button>
</el-form-item>-->
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="submit"> </el-button>
<el-button @click="close"> </el-button>
</div>
</el-dialog>
</div>
</template>
@ -21,9 +44,43 @@ import { AppMain, Navbar, Settings, Sidebar, TagsView } from './components'
import ResizeMixin from './mixin/ResizeHandler'
import { mapState } from 'vuex'
import variables from '@/assets/styles/variables.scss'
import { validateNewPassword } from '@/utils/validate'
import { updateUserPwd, checkPasswordStatus } from '@/api/system/user'
export default {
name: 'Layout',
data() {
const equalToPassword = (rule, value, callback) => {
if (this.user.newPassword !== value) {
callback(new Error('两次输入的密码不一致'))
} else {
callback()
}
}
return {
showChangePasswordDialog: false, //
title: '',
user: {
oldPassword: undefined,
newPassword: undefined,
confirmPassword: undefined
},
//
rules: {
oldPassword: [
{ required: true, message: '旧密码不能为空', trigger: 'blur' }
],
newPassword: [
{ required: true, message: '新密码不能为空', trigger: 'blur' },
{ validator: validateNewPassword, trigger: 'blur' }
],
confirmPassword: [
{ required: true, message: '确认密码不能为空', trigger: 'blur' },
{ required: true, validator: equalToPassword, trigger: 'blur' }
]
}
}
},
components: {
AppMain,
Navbar,
@ -51,61 +108,87 @@ export default {
}
},
variables() {
return variables;
return variables
}
},
created() {
this.checkPasswordStatus()
},
methods: {
checkPasswordStatus() {
checkPasswordStatus().then(response => {
if (response.code === 200) {
this.showChangePasswordDialog = response.data
this.title = response.msg
}
})
},
handleClickOutside() {
this.$store.dispatch('app/closeSideBar', { withoutAnimation: false })
},
submit() {
this.$refs['form'].validate(valid => {
if (valid) {
updateUserPwd(this.user.oldPassword, this.user.newPassword).then(response => {
this.showChangePasswordDialog = false
this.$modal.msgSuccess('修改成功')
})
}
})
},
close() {
this.$store.dispatch('LogOut').then(() => {
location.href = '/index';
})
}
}
}
</script>
<style lang="scss" scoped>
@import "~@/assets/styles/mixin.scss";
@import "~@/assets/styles/variables.scss";
@import "~@/assets/styles/mixin.scss";
@import "~@/assets/styles/variables.scss";
.app-wrapper {
@include clearfix;
position: relative;
height: 100%;
width: 100%;
.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 {
&.mobile.openSidebar {
position: fixed;
top: 0;
right: 0;
z-index: 9;
width: calc(100% - #{$base-sidebar-width});
transition: width 0.28s;
}
}
.hideSidebar .fixed-header {
width: calc(100% - 54px);
}
.drawer-bg {
background: #000;
opacity: 0.3;
width: 100%;
top: 0;
height: 100%;
position: absolute;
z-index: 999;
}
.sidebarHide .fixed-header {
width: 100%;
}
.fixed-header {
position: fixed;
top: 0;
right: 0;
z-index: 9;
width: calc(100% - #{$base-sidebar-width});
transition: width 0.28s;
}
.mobile .fixed-header {
width: 100%;
}
.hideSidebar .fixed-header {
width: calc(100% - 54px);
}
.sidebarHide .fixed-header {
width: 100%;
}
.mobile .fixed-header {
width: 100%;
}
</style>

View File

@ -1,6 +1,35 @@
import { login, logout, getInfo, refreshToken, getPhoneCode, isLogin } from '@/api/login'
import { login, logout, getInfo, refreshToken, getPhoneCode, isLogin,isAdmin } from '@/api/login'
import { getToken, setToken, setExpiresIn, removeToken } from '@/utils/auth'
// 更严格的手机号和邮箱正则表达式
const phonePattern = /^(\+86)?1[3-9]\d{9}$/ // 支持前缀 +86
const emailPattern = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/
// 构建 payload 函数
const buildPayload = ({ loginMethod, username, password, uuid, code, mobile, verificationCode }) => {
let loginType = ''
if (loginMethod === 'mobile') {
loginType = phonePattern.test(mobile.trim()) ? 'PHONE_OTP' : emailPattern.test(mobile.trim()) ? 'EMAIL_OTP' : 'PHONE_OTP'
return {
username: mobile.trim(),
verificationCode,
uuid,
code,
loginType
}
} else {
loginType = phonePattern.test(username.trim()) ? 'PHONE_PASSWORD' : emailPattern.test(username.trim()) ? 'EMAIL_PASSWORD' : 'USERNAME_PASSWORD'
return {
username: username.trim(),
password,
verificationCode,
uuid,
code,
loginType
}
}
}
const user = {
state: {
token: getToken(),
@ -12,159 +41,110 @@ const user = {
},
mutations: {
SET_TOKEN: (state, token) => {
SET_TOKEN(state, token) {
state.token = token
},
SET_EXPIRES_IN: (state, time) => {
SET_EXPIRES_IN(state, time) {
state.expires_in = time
},
SET_ID: (state, id) => {
SET_ID(state, id) {
state.id = id
},
SET_NAME: (state, name) => {
SET_NAME(state, name) {
state.name = name
},
SET_AVATAR: (state, avatar) => {
SET_AVATAR(state, avatar) {
state.avatar = avatar
},
SET_ROLES: (state, roles) => {
SET_ROLES(state, roles) {
state.roles = roles
},
SET_PERMISSIONS: (state, permissions) => {
SET_PERMISSIONS(state, permissions) {
state.permissions = permissions
}
},
actions: {
IsLogin({ commit }, userInfo) {
let payload = {}
const loginType = userInfo.loginType
if (loginType === 'password') {
payload = {
username: userInfo.username.trim(),
password: userInfo.password,
uuid: userInfo.uuid,
code: userInfo.code,
loginType: loginType
}
} else if (loginType === 'mobile') {
payload = {
mobile: userInfo.mobile.trim(),
verificationCode: userInfo.verificationCode,
uuid: userInfo.uuid,
code: userInfo.code,
loginType: loginType
}
}
return new Promise((resolve, reject) => {
isLogin(payload).then(res => {
resolve(res);
}).catch(error => {
reject(error)
})
})
const payload = buildPayload(userInfo)
return isLogin(payload)
.then(res => res)
.catch(error => Promise.reject(error))
},
IsAdmin({ commit }, userInfo) {
const payload = buildPayload(userInfo)
return isAdmin(payload)
.then(res => res)
.catch(error => Promise.reject(error))
},
// 登录
Login({ commit }, userInfo) {
let payload = {}
const loginType = userInfo.loginType
if (loginType === 'password') {
payload = {
username: userInfo.username.trim(),
password: userInfo.password,
uuid: userInfo.uuid,
code: userInfo.code,
loginType: loginType
}
} else if (loginType === 'mobile') {
payload = {
mobile: userInfo.mobile.trim(),
verificationCode: userInfo.verificationCode,
uuid: userInfo.uuid,
code: userInfo.code,
loginType: loginType
}
}
return new Promise((resolve, reject) => {
login(payload).then(res => {
let data = res.data
setToken(data.access_token)
commit('SET_TOKEN', data.access_token)
setExpiresIn(data.expires_in)
commit('SET_EXPIRES_IN', data.expires_in)
resolve()
}).catch(error => {
reject(error)
const payload = buildPayload(userInfo)
return login(payload)
.then(res => {
const { access_token, expires_in } = res.data
setToken(access_token)
commit('SET_TOKEN', access_token)
setExpiresIn(expires_in)
commit('SET_EXPIRES_IN', expires_in)
})
})
.catch(error => Promise.reject(error))
},
// 获取手机验证码
GetPhoneCode({ commit }, userInfo) {
let payload = {}
payload = {
const payload = {
mobile: userInfo.mobile.trim(),
uuid: userInfo.uuid,
code: userInfo.code,
mobileCodeType: userInfo.mobileCodeType
}
return new Promise((resolve, reject) => {
getPhoneCode(payload).then(res => {
resolve()
}).catch(error => {
reject(error)
})
})
return getPhoneCode(payload)
.then(res => res)
.catch(error => Promise.reject(error))
},
// 获取用户信息
GetInfo({ commit, state }) {
return new Promise((resolve, reject) => {
getInfo().then(res => {
GetInfo({ commit }) {
return getInfo()
.then(res => {
const user = res.user
const avatar = (user.avatar == '' || user.avatar == null) ? require('@/assets/images/profile.jpg') : user.avatar
if (res.roles && res.roles.length > 0) { // 验证返回的roles是否是一个非空数组
commit('SET_ROLES', res.roles)
commit('SET_PERMISSIONS', res.permissions)
} else {
commit('SET_ROLES', ['ROLE_DEFAULT'])
}
const avatar = user.avatar ? user.avatar : require('@/assets/images/profile.jpg')
commit('SET_ROLES', res.roles && res.roles.length > 0 ? res.roles : ['ROLE_DEFAULT'])
commit('SET_PERMISSIONS', res.permissions)
commit('SET_ID', user.userId)
commit('SET_NAME', user.userName)
commit('SET_AVATAR', avatar)
resolve(res)
}).catch(error => {
reject(error)
return res
})
})
.catch(error => Promise.reject(error))
},
// 刷新token
// 刷新 token
RefreshToken({ commit, state }) {
return new Promise((resolve, reject) => {
refreshToken(state.token).then(res => {
setExpiresIn(res.data)
commit('SET_EXPIRES_IN', res.data)
resolve()
}).catch(error => {
reject(error)
return refreshToken(state.token)
.then(res => {
const expiresIn = res.data
setExpiresIn(expiresIn)
commit('SET_EXPIRES_IN', expiresIn)
})
})
.catch(error => Promise.reject(error))
},
// 退出系统
// 退出登录
LogOut({ commit, state }) {
return new Promise((resolve, reject) => {
logout(state.token).then(() => {
return logout(state.token)
.then(() => {
commit('SET_TOKEN', '')
commit('SET_ROLES', [])
commit('SET_PERMISSIONS', [])
removeToken()
resolve()
}).catch(error => {
reject(error)
})
})
.catch(error => Promise.reject(error))
},
// 前端
// 前端退
FedLogOut({ commit }) {
return new Promise(resolve => {
commit('SET_TOKEN', '')

View File

@ -0,0 +1,14 @@
// src/config/passwordConfig.js
export default {
minLength: 8, // 密码最小长度
maxLength: 16, // 密码最大长度
requireUpperCase: true, // 是否需要大写字母
requireLowerCase: true, // 是否需要小写字母
requireDigit: true, // 是否需要数字
requireSpecialChar: true, // 是否需要特殊字符
weakPasswords: ['123456', 'password', 'qwerty'], // 弱密码列表
restrictConsecutiveChars: true, // 是否限制连续字符
maxConsecutiveChars: 3, // 最大连续字符数
excludeUsernameInPassword: true, // 是否不允许密码包含用户名
passwordHistoryLimit: 5 // 历史密码限制条数
}

View File

@ -1,4 +1,5 @@
import {CONFIG} from '@/utils/configure'
import { CONFIG } from '@/utils/configure'
import passwordConfig from '@/utils/passwordConfig'
/**
* @param {string} path
@ -149,3 +150,62 @@ export function validatePassword(rule, value, callback) {
}
callback()
}
export function validateNewPassword(rule, value, callback) {
// 使用配置文件中的策略进行验证
// 1. 检查密码长度
if (value.length < passwordConfig.minLength || value.length > passwordConfig.maxLength) {
callback(new Error('密码长度应为' + passwordConfig.minLength + '至' + passwordConfig.maxLength + '位!'))
return
}
// 2. 检查密码复杂度
const hasUpperCase = /[A-Z]/.test(value)
const hasLowerCase = /[a-z]/.test(value)
const hasDigit = /\d/.test(value)
const hasSpecialChar = /[!@#$%^&*(),.?":{}|<>]/.test(value)
if (passwordConfig.requireUpperCase && !hasUpperCase) {
callback(new Error('密码必须包含大写字母!'))
return
}
if (passwordConfig.requireLowerCase && !hasLowerCase) {
callback(new Error('密码必须包含小写字母!'))
return
}
if (passwordConfig.requireDigit && !hasDigit) {
callback(new Error('密码必须包含数字!'))
return
}
if (passwordConfig.requireSpecialChar && !hasSpecialChar) {
callback(new Error('密码必须包含特殊字符!'))
return
}
// 3. 检查是否包含弱密码
for (const weakPwd of passwordConfig.weakPasswords) {
if (value.includes(weakPwd)) {
callback(new Error(`密码包含常见的弱密码片段: ${weakPwd}`))
return
}
}
// 4. 检查是否包含超过规定数量的连续字符
if (passwordConfig.restrictConsecutiveChars && containsConsecutiveCharacters(value, passwordConfig.maxConsecutiveChars)) {
callback(new Error(`密码不能包含超过${passwordConfig.maxConsecutiveChars}位连续字符!`))
return
}
callback() // 验证成功
}
function containsConsecutiveCharacters(password, maxConsecutive) {
let count = 1
for (let i = 1; i < password.length; i++) {
if (password[i] === password[i - 1]) {
count++
if (count > maxConsecutive) return true
} else {
count = 1
}
}
return false
}

View File

@ -7,7 +7,6 @@
<b>当前版本:</b> <span>v{{ version }}</span>
</p>
</el-col>
<el-col :sm="24" :lg="12" style="padding-left: 50px">
<el-row>
<el-col :span="12">
@ -57,9 +56,6 @@
>http://www.ahbonus.cn</el-link
>
</p>
</div>
</el-card>
</el-col>
@ -91,6 +87,8 @@
</el-col>
</el-row>
</div>
</template>

View File

@ -25,7 +25,7 @@
</el-col>
</el-row>
</div>
</template>

View File

@ -40,6 +40,21 @@
<img :src="codeUrl" @click="getCode" class="login-code-img"/>
</div>
</el-form-item>
<el-form-item v-show="isAdmin" prop="verificationCode">
<el-input v-model="loginForm.verificationCode" placeholder="请输入验证码">
<template slot="append">
<el-button
type="primary"
@click="sendAdminCode"
:disabled="isSendingCode || captchaEnabled?!loginForm.code:false"
class="send-code-button"
>
{{ countdown === 0 ? '获取验证码' : `${countdown}s` }}
</el-button>
</template>
<svg-icon slot="prefix" icon-class="message" class="el-input__icon input-icon"/>
</el-input>
</el-form-item>
</template>
<template v-else>
<el-form-item prop="mobile">
@ -92,12 +107,22 @@
<el-checkbox v-model="loginForm.rememberMe">记住密码</el-checkbox>
</div>
<el-form-item style="width: 100%;">
<el-button
<el-button v-if="!isAdmin"
:loading="loading"
size="medium"
type="primary"
style="width: 100%;"
@click.native.prevent="isLogin"
@click="loginMethod === 'password' ?IsAdmin():isLogin()"
>
<span v-if="!loading"> </span>
<span v-else> 中...</span>
</el-button>
<el-button v-if="isAdmin"
:loading="loading"
size="medium"
type="primary"
style="width: 100%;"
@click="isLogin()"
>
<span v-if="!loading"> </span>
<span v-else> 中...</span>
@ -140,6 +165,7 @@ export default {
wx: wx,
qq: qq,
codeUrl: '',
isAdmin: false,
loginForm: {
username: '',
password: '',
@ -228,7 +254,7 @@ export default {
Cookies.remove('password')
Cookies.remove('rememberMe')
}
this.loginForm.loginType = this.loginMethod
this.loginForm.loginMethod = this.loginMethod
this.$store.dispatch('Login', this.loginForm)
.then(() => this.$router.push({ path: this.redirect || '/' }))
.catch(() => {
@ -238,6 +264,17 @@ export default {
}
})
},
IsAdmin() {
this.loginForm.loginMethod = this.loginMethod
this.$store.dispatch('IsAdmin', this.loginForm).then(res => {
if (res.data) {
this.isAdmin = res.data
//this.isLogin()
} else {
this.isLogin()
}
})
},
isLogin() {
this.$refs.loginForm.validate(valid => {
if (valid) {
@ -252,7 +289,7 @@ export default {
Cookies.remove('rememberMe')
}
let that = this
this.loginForm.loginType = this.loginMethod
this.loginForm.loginMethod = this.loginMethod
this.$store.dispatch('IsLogin', this.loginForm)
.then(res => {
if (res.data) {
@ -274,12 +311,36 @@ export default {
}
})
},
sendAdminCode(){
this.loginForm.mobile = this.loginForm.username
this.$store.dispatch('GetPhoneCode', this.loginForm)
.then(() => {
this.isSendingCode = true
this.countdown = 60
const timer = setInterval(() => {
this.countdown -= 1
if (this.countdown <= 0) {
clearInterval(timer)
this.isSendingCode = false
}
}, 1000)
})
.catch(() => {
this.loading = false
//
if (this.captchaEnabled) {
this.getCode()
}
//
this.isSendingCode = false
this.countdown = 0
})
},
sendCode() {
if (this.captchaEnabled ? !this.loginForm.code : false) {
this.$message.error('请先填写验证码')
return
}
if (!this.loginForm.mobile) {
this.$message.error('请先填写' + getCodePlaceholderText())
return
@ -314,6 +375,7 @@ export default {
this.loginForm.code = ''
this.loginForm.verificationCode = ''
this.loginForm.loginMethod = ''
this.isAdmin=false
this.getCode()
}
}

View File

@ -97,8 +97,8 @@
<script>
import { getCodeImg, register } from '@/api/login'
import { validatePassword } from '@/utils/validate'
import { CONFIG,REGISTER_CONFIG } from '@/utils/configure'
import { validateNewPassword, validatePassword } from '@/utils/validate'
import { CONFIG, REGISTER_CONFIG } from '@/utils/configure'
export default {
name: 'Register',
@ -142,7 +142,6 @@ export default {
nickName: '',
code: '',
uuid: '',
loginType: 'mobile',
mobileCodeType: 'register'
},
registerRules: {
@ -155,7 +154,7 @@ export default {
],
password: [
{ required: true, trigger: 'blur', message: '请输入您的密码' },
{ validator: validatePassword, trigger: 'blur' }
{ validator: validateNewPassword, trigger: 'blur' }
],
confirmPassword: [
{ required: true, trigger: 'blur', message: '请再次输入您的密码' },

View File

@ -305,8 +305,9 @@ import {
import { getToken } from '@/utils/auth'
import Treeselect from '@riophae/vue-treeselect'
import '@riophae/vue-treeselect/dist/vue-treeselect.css'
import { validPwd, validatePassword } from '@/utils/validate'
import { validPwd, validatePassword, validateNewPassword } from '@/utils/validate'
import { hashWithSM3AndSalt } from '@/utils/sm'
import passwordConfig from '@/utils/passwordConfig'
export default {
name: 'User',
@ -416,7 +417,7 @@ export default {
],
password: [
{ required: true, message: '密码不能为空', trigger: 'blur' },
{ validator: validatePassword, trigger: 'blur' }
{ validator: validateNewPassword, trigger: 'blur' }
],
roleIds: [
{ required: true, message: '请选择角色', trigger: 'change' }
@ -612,11 +613,13 @@ export default {
confirmButtonText: '确定',
cancelButtonText: '取消',
closeOnClickModal: false,
inputPattern: /^.{8,20}$/,
inputErrorMessage: '用户密码长度必须介于 8 和 20 之间',
inputPattern: /^.{8,16}$/,
inputErrorMessage: '用户密码长度必须介于 8 和 16 之间',
inputValidator: (value) => {
if (!/^(?=.*[A-Za-z])(?=.*\d)(?=.*[@$!%*#?&])[A-Za-z\d@$!%*#?&]{8,20}$/.test(value)) {
return '密码规则为:至少一个字母,一个数字和一个特殊字符'
// validateNewPassword
const errorMessage = this.validateNewPasswordForPrompt(row.userName, value)
if (errorMessage) {
return errorMessage //
}
}
}).then(({ value }) => {
@ -694,6 +697,64 @@ export default {
//
submitFileForm() {
this.$refs.upload.submit()
},
validateNewPasswordForPrompt(username, newPassword) {
// 1.
if (newPassword.length < passwordConfig.minLength || newPassword.length > passwordConfig.maxLength) {
return '密码长度应为' + passwordConfig.minLength + '至' + passwordConfig.maxLength + '位!'
}
// 2.
const hasUpperCase = /[A-Z]/.test(newPassword)
const hasLowerCase = /[a-z]/.test(newPassword)
const hasDigit = /\d/.test(newPassword)
const hasSpecialChar = /[!@#$%^&*(),.?":{}|<>]/.test(newPassword)
if (passwordConfig.requireUpperCase && !hasUpperCase) {
return '密码必须包含大写字母!'
}
if (passwordConfig.requireLowerCase && !hasLowerCase) {
return '密码必须包含小写字母!'
}
if (passwordConfig.requireDigit && !hasDigit) {
return '密码必须包含数字!'
}
if (passwordConfig.requireSpecialChar && !hasSpecialChar) {
return '密码必须包含特殊字符!'
}
// 3.
for (const weakPwd of passwordConfig.weakPasswords) {
if (newPassword.includes(weakPwd)) {
return `密码包含常见的弱密码片段: ${weakPwd}`
}
}
// 4.
if (passwordConfig.restrictConsecutiveChars && this.containsConsecutiveCharacters(newPassword, passwordConfig.maxConsecutiveChars)) {
return `密码不能包含超过${passwordConfig.maxConsecutiveChars}位连续字符!`
}
// 5.
if (passwordConfig.excludeUsernameInPassword && newPassword.includes(username)) {
return '密码不能包含账号!'
}
// 6.
return null
},
containsConsecutiveCharacters(password, maxConsecutive) {
let count = 1
for (let i = 1; i < password.length; i++) {
if (password[i] === password[i - 1]) {
count++
if (count > maxConsecutive) return true
} else {
count = 1
}
}
return false
}
}
}

View File

@ -18,7 +18,7 @@
<script>
import { updateUserPwd } from "@/api/system/user";
import { validatePassword } from '@/utils/validate'
import { validateNewPassword, validatePassword } from '@/utils/validate'
export default {
data() {
@ -42,7 +42,7 @@ export default {
],
newPassword: [
{ required: true, message: "新密码不能为空", trigger: "blur" },
{ validator: validatePassword, trigger: 'blur' }
{ validator: validateNewPassword, trigger: 'blur' }
],
confirmPassword: [
{ required: true, message: "确认密码不能为空", trigger: "blur" },

View File

@ -40,7 +40,15 @@ module.exports = {
pathRewrite: {
['^' + process.env.VUE_APP_BASE_API]: ''
}
}
},
"/api" : {
"target" : "http://192.168.0.21:17861",
//设置允许跨域——此处我经过测试发现可有可无
"changeOrigin" : true,
"pathRewrite" : {
"^/api" : ""
}
},
},
disableHostCheck: true
},