教程目录
一:《【vue create】一.使用vue creat搭建项目》
根据前文《【vue create】一.使用vue creat搭建项目》搭建好后,进行下面的操作
1.配置Esline
2.安装less
3.安装ant-design-vue(设置公共样式)
4.安装nprogress
5.配置环境变量env文件
6.配置axios
7.配置vuex状态管理器的store
8.配置Router、路由守卫以及登录页
9.配置路由守卫
10.实现登录页
11.注意点
在.eslintrc.js文件里代码替换如下
module.exports = {
root: true,
env: {
node: true,
},
extends: ['plugin:vue/strongly-recommended', '@vue/prettier'],
rules: {
'no-console': 'off',
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off',
//在rules中添加自定义规则
//关闭组件命名规则
'vue/multi-word-component-names': 'off',
'generator-star-spacing': 'off',
'no-mixed-operators': 0,
'vue/max-attributes-per-line': [
2,
{
singleline: 5,
multiline: {
max: 1,
allowFirstLine: false,
},
},
],
'vue/attribute-hyphenation': 0,
'vue/html-self-closing': 0,
'vue/component-name-in-template-casing': 0,
'vue/html-closing-bracket-spacing': 0,
'vue/singleline-html-element-content-newline': 0,
'vue/no-unused-components': 0,
'vue/multiline-html-element-content-newline': 0,
'vue/no-use-v-if-with-v-for': 0,
'vue/html-closing-bracket-newline': 0,
'vue/no-parsing-error': 0,
'prettier/prettier': 0,
'vue/no-template-shadow': 0,
'prefer-const': 0,
'no-tabs': 0,
'vue/max-attributes-per-line': 0,
quotes: [2, 'single', { avoidEscape: true, allowTemplateLiterals: true }], // avoidEscape: true 允许字符串使用单引号或双引号,只要字符串包含必须以其他方式转义的引号 ;allowTemplateLiterals: true 允许字符串使用反引号,
semi: [
2,
'never',
{
beforeStatementContinuationChars: 'never',
},
], //“beforeStatementContinuationChars”: “never” 如果该语句不会因为ASI而带来风险,那么即使它的下一行语句以 [,(,/,+,或 - 开头,也不允许在语句末尾添加分号
'no-delete-var': 2,
'prefer-const': [
2,
{
ignoreReadBeforeAssign: false,
},
],
'template-curly-spacing': 'off',
indent: 'off',
},
parserOptions: {
parser: 'babel-eslint',
},
overrides: [
{
files: ['**/__tests__/*.{j,t}s?(x)', '**/tests/unit/**/*.spec.{j,t}s?(x)'],
env: {
jest: true,
},
},
],
}
然后在终端运行如下代码安装插件
yarn add @vue/eslint-config-prettier@6.0.0 -D
yarn add eslint-plugin-prettier@3.3.1 -D
yarn add babel-eslint@10.1.0 -D
yarn add prettier@2.2.1 -D
在根目录新建.prettierrc文件,代码如下
{
"printWidth": 120,
"semi": false,
"singleQuote": true,
"prettier.spaceBeforeFunctionParen": true
}
vscode编辑器扩展里需要安装prettier - Code formatter插件
然后重新运行项目yarn serve后会有提示报错(只需点进报错的文件里,按Ctrl+S按键再次保存则错误解决)
1.在终端输入命令
//默认情况下安装less-loader不指定版本号,会导致版本过高跟less不匹配会出问题
yarn add [email protected] --save
yarn add [email protected] --save
在终端输入命令
//带@是指定版本号
yarn add [email protected]
在src\core文件夹下新建ant-design.js文件,文件代码如下(设置为按需引入组件)
// 按需加载ant-design
import Vue from 'vue'
// base library
import {
message,
notification,
Affix,
Anchor,
AutoComplete,
Alert,
Avatar,
BackTop,
Badge,
Breadcrumb,
Button,
Calendar,
Card,
Collapse,
Carousel,
Cascader,
Checkbox,
Col,
DatePicker,
Divider,
Dropdown,
Form,
FormModel,
Icon,
Input,
InputNumber,
Layout,
List,
LocaleProvider,
Menu,
Mentions,
Modal,
Pagination,
Popconfirm,
Popover,
Progress,
Radio,
Rate,
Row,
Select,
Slider,
Spin,
Statistic,
Steps,
Switch,
Table,
Transfer,
Tree,
TreeSelect,
Tabs,
Tag,
TimePicker,
Timeline,
Tooltip,
Upload,
Drawer,
Skeleton,
Comment,
// ColorPicker,
ConfigProvider,
Empty,
Result,
Descriptions,
PageHeader,
Space,
} from 'ant-design-vue'
import 'ant-design-vue/dist/antd.css'
// 全局 message 配置
message.config({
top: `240px`,
duration: 2,
maxCount: 1,
})
Vue.use(Affix)
Vue.use(Anchor)
Vue.use(AutoComplete)
Vue.use(Calendar)
Vue.use(LocaleProvider)
Vue.use(Mentions)
Vue.use(Slider)
Vue.use(Statistic)
Vue.use(Transfer)
Vue.use(TreeSelect)
Vue.use(Timeline)
Vue.use(Skeleton)
Vue.use(Comment)
Vue.use(Descriptions)
Vue.use(PageHeader)
Vue.use(Cascader)
Vue.use(ConfigProvider)
Vue.use(Layout)
Vue.use(Input)
Vue.use(InputNumber)
Vue.use(Button)
Vue.use(Switch)
Vue.use(Radio)
Vue.use(Checkbox)
Vue.use(Select)
Vue.use(Card)
Vue.use(Form)
Vue.use(FormModel)
Vue.use(Row)
Vue.use(Col)
Vue.use(Modal)
Vue.use(Table)
Vue.use(Tabs)
Vue.use(Icon)
Vue.use(Badge)
Vue.use(Popover)
Vue.use(Dropdown)
Vue.use(List)
Vue.use(Avatar)
Vue.use(Breadcrumb)
Vue.use(Steps)
Vue.use(Spin)
Vue.use(Menu)
Vue.use(Drawer)
Vue.use(Tooltip)
Vue.use(Alert)
Vue.use(Tag)
Vue.use(Divider)
Vue.use(DatePicker)
Vue.use(TimePicker)
Vue.use(Upload)
Vue.use(Progress)
Vue.use(Result)
Vue.use(Space)
Vue.use(Carousel)
Vue.use(BackTop)
Vue.use(Tree)
Vue.use(Pagination)
Vue.use(Popconfirm)
Vue.use(Steps)
Vue.use(Rate)
Vue.use(Empty)
Vue.use(Collapse)
Vue.prototype.$confirm = Modal.confirm
Vue.prototype.$message = message
Vue.prototype.$notification = notification
Vue.prototype.$info = Modal.info
Vue.prototype.$success = Modal.success
Vue.prototype.$error = Modal.error
Vue.prototype.$warning = Modal.warning
process.env.NODE_ENV !== 'production' && console.warn('[antd-pro] NOTICE: Antd use lazy-load.')
在babel.config.js文件里加入如下代码
plugins: [
[
'import',
{
libraryName: 'ant-design-vue',
libraryDirectory: 'es',
style: 'css',
},
],
],
在终端执行命令,安装babel-plugin-import组件(按需加载组件)
yarn add [email protected]
在src\styles文件夹下新建index.less文件,代码设置如下
// 覆盖 ant-design 样式
@import './ant-design-vue.less';
// 进度条样式
@import './nprogress.less';
// 公共样式
@import './common.less';
在src\styles文件夹下新建ant-design-vue.less文件,代码设置如下
// 列表
.table-wrapper {
height: 100%;
.ant-table {
.ant-table-body {
overflow: auto !important;
}
}
}
.ant-table-wrapper {
.ant-table-pagination {
&.ant-pagination {
float: none;
text-align: center;
}
}
.ant-table-header {
overflow: auto !important;
}
}
// form 布局
.c-form {
.ant-form-item {
display: flex;
.ant-form-item-label {
min-width: 80px;
}
.ant-form-item-control-wrapper {
flex: 1;
}
&.item-block {
display: block;
}
}
}
.ant-table {
.ant-table-tbody {
.ant-table-row-cell-break-word {
padding-left: 0;
padding-right: 0;
}
}
}
在src\styles文件夹下新建common.less文件,代码设置如下
// common
.flex {
display: flex;
}
.flex-1 {
flex: 1;
}
.block{
display: block;
}
.between-center{
justify-content: space-between;
align-items: center;
}
.wrap{
flex-wrap: wrap;
}
.hidden {
overflow: hidden;
}
.no-wrap {
overflow: hidden;
text-overflow:ellipsis;
white-space: nowrap;
}
.ellipsis-multiple {
word-break: break-all;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-box-orient: vertical;
// -webkit-line-clamp: 2;
overflow: hidden;
}
.text-center {
text-align: center;
}
.around-start {
justify-content: space-around;
align-items: flex-start;
}
.between-start {
justify-content: space-between;
align-items: flex-start;
}
.center-center {
justify-content: center;
align-items: center;
}
.around-center {
justify-content: space-around;
align-items: center;
}
.start-center {
justify-content: flex-start;
align-items: center;
}
.end-center {
justify-content: flex-end;
align-items: center;
}
.start-end {
justify-content: flex-start;
align-items: flex-end;
}
.evenly-center {
justify-content: space-between;
align-items: center;
&::after,
&::before {
content:"";
display: block;
}
}
.cursor-pointer {
cursor: pointer;
}
.not-select{
user-select:none;
}
.fl {
float: left;
}
.fr {
float: right;
}
.mt-20 {
margin-top: 20px;
}
.mt-5 {
margin-top: 5px;
}
.mt-30 {
margin-top: 30px;
}
.mr-1 {
margin-right: 1px;
}
.mr-2 {
margin-right: 2px;
}
.mr-3 {
margin-right: 3px;
}
.mr-4 {
margin-right: 4px;
}
.mr-5 {
margin-right: 5px;
}
.mr-6 {
margin-right: 6px;
}
.mr-7 {
margin-right: 7px;
}
.mr-8 {
margin-right: 8px;
}
.mr-9 {
margin-right: 9px;
}
.mr-10 {
margin-right: 10px;
}
.mr-11 {
margin-right: 11px;
}
.mr-12 {
margin-right: 12px;
}
.mr-13 {
margin-right: 13px;
}
.mr-14 {
margin-right: 14px;
}
.mr-15 {
margin-right: 15px;
}
.mr-16 {
margin-right: 16px;
}
.mr-17 {
margin-right: 17px;
}
.mr-18 {
margin-right: 18px;
}
.mr-19 {
margin-right: 19px;
}
.mr-20 {
margin-right: 20px;
}
.mb-1 {
margin-bottom: 1px;
}
.mb-2 {
margin-bottom: 2px;
}
.mb-3 {
margin-bottom: 3px;
}
.mb-4 {
margin-bottom: 4px;
}
.mb-5 {
margin-bottom: 5px;
}
.mb-10 {
margin-bottom: 10px;
}
.my-5 {
margin-top: 5px;
margin-bottom: 5px;
}
.ml-1 {
margin-left: 1px;
}
.ml-2 {
margin-left: 2px;
}
.ml-3 {
margin-left: 3px;
}
.ml-4 {
margin-left: 4px;
}
.ml-5 {
margin-left: 5px;
}
.ml-6 {
margin-left: 6px;
}
.ml-7 {
margin-left: 7px;
}
.ml-8 {
margin-left: 8px;
}
.ml-9 {
margin-left: 9px;
}
.ml-10 {
margin-left: 10px;
}
.ml-11 {
margin-left: 11px;
}
.ml-12 {
margin-left: 12px;
}
.ml-13 {
margin-left: 13px;
}
.ml-14 {
margin-left: 14px;
}
.ml-15 {
margin-left: 15px;
}
.ml-16 {
margin-left: 16px;
}
.ml-17 {
margin-left: 17px;
}
.ml-18 {
margin-left: 18px;
}
.fz-14 {
font-size: 14px;
}
.pd-10 {
padding: 10px;
}
.ml-53{
margin-left:53px;
}
在src\styles文件夹下新建nprogress.less文件,代码设置如下
#nprogress .bar {
height: 10rpx !important; //高度
background: #d64141 !important; //颜色
}
然后在src\main.js文件加入如下代码
import './core/ant-design'
import './styles/index.less'
nprogress一个第三方的进度条库,在后面router跳转的时会使用到
在控制台终端输入如下代码回车,安装nprogress插件
yarn add nprogress@0.2.0
我这里只使用开发环境和生产环境,根据实际业务可以新建多个env文件
在根目录下新建.env文件,代码如下(全局默认配置文件,不论什么环境都会加载并合并)
# 后端 api 代理路径
VUE_APP_API_BASE_URL = '/api'
VUE_APP_MINIO_PROXY_KEY=/micro-dev
在根目录下新建.env.dev文件,代码如下(开发环境下的配置文件,仅在开发环境加载)
# 环境变量
NODE_ENV=development
VUE_APP_ENV = 'dev'
# 项目目录路径
VUE_APP_PUBLIC_PATH=/
# 开发环境
VUE_APP_PROXY_API_URL = 'http://10.88.96.35:31325'
在根目录下新建.env.prod文件,代码如下(生产环境下的配置文件,仅在生产环境加载)
# 环境变量
NODE_ENV=production
VUE_APP_ENV = 'prod'
# 项目目录路径
VUE_APP_PUBLIC_PATH=/
# 生产环境
VUE_APP_PROXY_API_URL = 'http://120.46.172.49:31400'
"scripts": {
"serve": "vue-cli-service serve --mode dev --open",
"build": "vue-cli-service build --mode dev",
"lint": "vue-cli-service lint",
"build:prod": "vue-cli-service build --mode prod",
"build-prod": "vue-cli-service build --mode prod",
"build-dev": "vue-cli-service build --mode dev"
},
const vueConfig = {
transpileDependencies: true,
//开启esline校验
lintOnSave: true,
//配置api拦截规则
devServer: {
// development server port 8000
port: 8000,
https: false,
// If you want to turn on the proxy, please remove the mockjs /src/main.jsL11
allowedHosts: 'all', // vue项目提示Invalid Host header 配置是否关闭用于 DNS 重绑定的 HTTP 请求的 HOST 检查
proxy: {
'/api': {
target: process.env.VUE_APP_PROXY_API_URL,
ws: true, // 支持websocket
changeOrigin: true, // 是否跨域
secure: false, // 如果是https接口,需要配置这个参数
pathRewrite: {
'^/api': '',
},
},
},
},
}
module.exports = vueConfig
在终端执行命令
yarn add axios@0.21.1
在src\utils文件夹下新建request.js文件
import axios from 'axios'
import store from '@/store'
import notification from 'ant-design-vue/es/notification'
import { getToken, setToken } from './auth'
import { checkLogin } from '@/utils/authCenter'
// 创建axios实例
const request = axios.create({
// baseURL: process.env.VUE_APP_API_BASE_URL,
timeout: 30000, // 请求超时时间
})
// 异常处理
const errorHanlder = (error) => {
if (error.response) {
const data = error.response.data
// 获取tooken
const token = getToken()
if (error.response.status === 403) {
notification.error({
message: 'Forbidden',
description: data.message,
})
}
if (error.response.status === 401) {
//注销登录
checkLogin()
}
}
return Promise.reject(error)
}
// requets 拦截
request.interceptors.request.use((config) => {
const token = getToken()
// 请求头携带token
if (token) {
config.headers['Authorization'] = token
}
return config
}, errorHanlder)
// response拦截
request.interceptors.response.use((response) => {
// 状态码
const { status, message } = response.data // 根据系统指定的status值
const oldToken = store.state.user.token
const newToken = response.config.headers['Authorization']
if (!oldToken && newToken) {
setToken(newToken)
store.commit('user/SET_TOKEN', newToken)
}
//blob文件类型的
if (response.request.responseType == 'blob') {
return response
}
//放开status指定的状态的返回值
if (status === 0) {
return response.data
} else if (status === 101) {
return response.data
} else {
return response.data
}
// 其他状态码处理
}, errorHanlder)
export default request
安装js-cookie和jwt-decode(用于解析token以及存储数据到cookie)
yarn add js-cookie@2.2.1
yarn add jwt-decode@3.1.2
在src\utils文件夹下新建auth.js文件(用于操作token)
import Cookies from 'js-cookie'
import jwtDecode from 'jwt-decode'
let TokenKey = ''
if (process.env.VUE_APP_ENV === 'prod') {
// 生产
TokenKey = 'user-token'
} else {
// 开发
TokenKey = 'dev-user-token'
}
export function getToken() {
return Cookies.get(TokenKey) || sessionStorage.getItem('token')
}
export function setToken(token) {
return Cookies.set(TokenKey, token)
}
export function removeToken() {
return Cookies.remove(TokenKey)
}
export function getCurrentUser() {
const access_token = Cookies.get(TokenKey)
if (access_token != null) {
const decode = jwtDecode(access_token)
return decode.username
}
}
/**
* 从token获取用户信息
* @returns
*/
export function getUserInfo() {
const access_token = Cookies.get(TokenKey)
if (access_token != null) {
const decode = jwtDecode(access_token)
return decode
}
}
在src\utils文件夹下新建authCenter.js文件,代码如下(用于解析token,跳转登录页)
import store from '@/store'
import { removeToken, setToken, getToken } from '@/utils/auth'
//跳转登录
export const checkLogin = () => {
this.$router.push('/login')
}
//注销登录
export const logout = (appType) => {
store.commit('user/SET_TOKEN', '') // 清除token
store.commit('user/SET_INFO', {}) // 清除用户信息
var token = escape(getToken())
token = token.replace('%20', ' ')
checkLogin()
removeToken()
}
//解析url链接中的token值
export function getArgumentsToken() {
if (getQueryVariable('token')) {
var token = decodeURIComponent(getQueryVariable('token'))
store.commit('user/SET_TOKEN', token)
setToken(token)
return token
} else {
return false
}
}
// 获取url中数组的参数1
export function getQueryVariable(variable) {
// 从?开始获取后面的所有数据`
var query = window.location.search.substring(1)
// 从字符串&开始分隔成数组split
var vars = query.split('&')
// 遍历该数组
for (var i = 0; i < vars.length; i++) {
// 从等号部分分割成字符
var pair = vars[i].split('=')
// 如果第一个元素等于 传进来的参的话 就输出第二个元素
if (pair[0] == variable) {
return pair[1]
}
}
return false
}
在src\api文件夹下新建base.js文件,代码如下
/**
* 接口域名的管理
*/
const base = {
api: '/api',
}
export default base
在src\api文件夹下新建user.js文件,代码如下
import request from '@/utils/request'
import base from '@/api/base'
const userApi = {
personalInfo: base.api + '/family/user/personal/info', // 查询个人信息
loginPwd: base.api + '/family/auth/api/login/pwd', // 账号密码登录
}
/**
* 获取用户信息
* @returns
*/
export const getUserInfo = () => {
return request({
url: userApi.personalInfo,
method: 'get',
})
}
/**
* 账号密码登录
* @param {*} data
* @returns
*/
export const login = (data) => {
return request({
url: userApi.loginPwd,
method: 'post',
data,
})
}
在src\store文件夹下的index.js,代码如下
import Vue from 'vue'
import Vuex from 'vuex'
import getters from './getters'
Vue.use(Vuex)
const modulesFiles = require.context(`./modules`, true, /\.js$/)
const modules = modulesFiles.keys().reduce((modules, modulePath) => {
const moduleName = modulePath.replace(/^\.\/(.*)\.\w+$/, '$1')
const value = modulesFiles(modulePath)
modules[moduleName] = value.default
return modules
}, {})
const store = new Vuex.Store({
modules,
getters,
})
export default store
在src\store文件夹下新建getters.js文件,代码如下(存储数据)
const getters = {
//
token: (state) => state.user.token,
userInfo: (state) => state.user.info,
}
export default getters
在src\store\modules文件夹下新建user.js文件(配置存储的数据),代码如下
// 用户相关
import { getUserInfo, login } from '@/api/user'
import { getToken, setToken, removeToken } from '@/utils/auth'
import router from '@/router'
const state = {
token: getToken(),
info: {}, // 个人信息
}
const getters = {}
const mutations = {
SET_TOKEN: (state, token) => {
state.token = token
},
SET_INFO: (state, info) => {
state.info = info
},
}
const actions = {
// 登录
login({ commit }, userInfo) {
const { username, password } = userInfo
return new Promise((resolve, reject) => {
login({ username: username.trim(), password: password })
.then((response) => {
const { data, status } = response
//status为0登录成功
if (status === 0) {
commit('SET_TOKEN', data)
setToken(data)
resolve()
} else {
reject(response)
}
})
.catch((error) => {
reject(error)
})
})
},
// 登出
logout({ commit, state }) {
return new Promise((resolve, reject) => {
// 调取登出接口
commit('SET_TOKEN', '') // 清除token
commit('SET_INFO', {}) // 清除用户信息
removeToken()
resolve()
})
},
// 获取用户信息
getInfo({ commit, dispatch }) {
return new Promise((resolve, reject) => {
// 调取接口获取用户信息
getUserInfo()
.then((response) => {
const { data } = response
const localUserList = localStorage.getItem('localUserList')
? JSON.parse(localStorage.getItem('localUserList'))
: []
const index = localUserList.findIndex((item) => item.userId === data.userId)
if (index < 0) {
localUserList.push(data)
localStorage.setItem('localUserList', JSON.stringify(localUserList))
if (localUserList.length >= 2) {
localStorage.removeItem('localUserList')
router.push({ path: '/home' })
}
}
commit('SET_INFO', data)
resolve(response)
})
.catch((error) => {
reject(error)
})
})
},
}
export default {
namespaced: true, // 开启命名空间模块
state,
getters,
mutations,
actions,
}
在src\router文件夹下index.js文件,配置代码如下
import Vue from 'vue'
import VueRouter from 'vue-router'
import { constantRouteMap } from '@/config/router.config'
// hack router push callback
const originalPush = VueRouter.prototype.push
VueRouter.prototype.push = function push(location, onResolve, onReject) {
if (onResolve || onReject) return originalPush.call(this, location, onResolve, onReject)
return originalPush.call(this, location).catch((err) => err)
}
Vue.use(VueRouter)
const router = new VueRouter({
mode: 'history',
base: process.env.BASE_URL,
routes: constantRouteMap,
})
export default router
然后新建src\config文件夹下router.config.js文件
const RouteView = {
name: 'RouteView',
render: (h) => h('router-view'),
}
export const asyncRouterMap = [
{
path: '/',
name: 'index',
component: () => import('@/pages/home/home.vue'),
meta: { title: '首页' },
redirect: '/home',
children: [
{
path: '/home',
name: 'home',
component: () => import(/* webpackChunkName: "home" */ '@/pages/home/home.vue'),
meta: {
title: '首页',
current: '/home',
},
},
],
},
]
export const constantRouteMap = [
{
path: '/login',
component: RouteView,
meta: { title: '登录' },
redirect: '/login',
children: [
{
path: '/login',
name: 'login',
component: () => import(/* webpackChunkName: "user" */ '@/views/Login'),
meta: { title: '登录' },
},
],
},
...asyncRouterMap,
]
在main.js文件里加入如下代码
//切换页面让滚动条回到顶部
router.afterEach((to, from, next) => {
window.scrollTo(0, 0)
})
在src文件夹下新建permission.js文件
import router from './router'
import { getToken } from '@/utils/auth'
import NProgress from 'nprogress'
import store from './store'
import notification from 'ant-design-vue/es/notification'
import { checkLogin, getArgumentsToken, logout, getQueryVariable } from './utils/authCenter'
NProgress.configure({ showSpinner: false })
// 路由白名单,不需要登陆
const allowRouteList = ['home', 'login']
const loginRoutePath = '/login'
const defaultRoutePath = '/home'
router.beforeEach((to, from, next) => {
NProgress.start()
function checkToken() {
if (to.path === loginRoutePath) {
next({ path: defaultRoutePath })
NProgress.done()
} else {
// user info is null
if (Object.values(store.getters.userInfo).length === 0) {
store
.dispatch('user/getInfo')
.then(() => {
const redirect = decodeURIComponent(from.query.redirect || to.path)
if (to.path === redirect) {
// set the replace: true so the navigation will not leave a history record
next({ ...to, replace: true })
} else {
// 跳转到目的路由
next({ path: redirect })
}
})
.catch(() => {
notification.error({
message: '错误',
description: '请求用户信息失败,请重试!',
})
// 失败时,获取用户信息失败时,调用登出,来清空历史保留信息
store.dispatch('user/logout').then(() => {
logout()
})
})
} else {
next()
}
}
}
var loginStatus = getArgumentsToken()
if (getToken()) {
checkToken()
} else {
// 免登录名单
if (allowRouteList.includes(to.name)) {
var isToken = getQueryVariable('isToken')
if (!loginStatus && isToken != 0) {
checkLogin()
} else {
next()
}
} else {
if (loginStatus) {
checkToken()
} else {
checkLogin()
}
NProgress.done()
}
}
})
router.afterEach(() => {
NProgress.done()
})
在main.js文件加入路由守卫配置,代码如下
import './permission'
删除项目自带的AboutView.vue和HomeView.vue文件
在src\App.vue文件里,代码设置如下(设置高度自适应)
<template>
<div id="app">
<router-view />
</div>
</template>
<script>
export default {
name: 'App',
data() {
return {}
},
}
</script>
<style lang="less" scoped>
html,
body,
#app {
height: 100%;
}
</style>
在src\pages\home文件夹下新建home.vue文件
home.vue文件代码如下
<template>
<div>
<h3>这里是首页</h3>
<a-button @click="toLogin">跳转登录页</a-button>
</div>
</template>
<script>
export default {
name: 'Home',
methods: {
toLogin() {
this.$router.push('/login')
},
},
}
</script>
在src\utils文件夹下新建公共封装方法util.js文件,代码如下
export const timeFix = () => {
const time = new Date()
const hour = time.getHours()
return hour < 9 ? '早上好' : hour <= 11 ? '上午好' : hour <= 13 ? '中午好' : hour < 20 ? '下午好' : '晚上好'
}
在src\views文件夹下新建Login.vue文件
<template>
<div class="login">
<div class="login-content">
<div class="login-title">账号登录</div>
<a-form class="login-form" ref="formLogin" :form="form" @submit="handleSubmit">
<a-form-item>
<a-input
size="large"
type="text"
placeholder="账号"
autocomplete
v-decorator="[
'username',
{
rules: [{ required: true, message: '请输入帐户名' }, { validator: handleUsernameOrEmail }],
validateTrigger: 'change',
},
]"
>
</a-input>
</a-form-item>
<a-form-item>
<a-input-password
size="large"
placeholder="密码"
v-decorator="[
'password',
{ rules: [{ required: true, message: '请输入密码!' }], validateTrigger: 'blur' },
]"
autocomplete
>
</a-input-password>
</a-form-item>
<a-form-item style="margin-top: 30px; margin-bottom: 16px">
<a-button
size="large"
type="primary"
htmlType="submit"
class="login-button"
:loading="state.loginBtn"
:disabled="state.loginBtn"
>
登录
</a-button>
<a-button size="large" type="" class="forget-button" @click="forgetPwd()"> 忘记密码?</a-button>
<a-button size="large" type="" class="reg-button" @click="register()"> 注册 </a-button>
</a-form-item>
</a-form>
</div>
</div>
</template>
<script>
import { timeFix } from '@/utils/util'
export default {
data() {
return {
form: this.$form.createForm(this),
state: {
time: 60,
loginBtn: false,
loginType: 0,
smsSendBtn: false,
},
loginFlg: 0, //登录状态
}
},
created() {
this.loginFlg = localStorage.getItem('loginFlg')
},
methods: {
// 注册
register() {
// 跳转 注册页面
},
// 忘记密码
forgetPwd() {
// 跳转 忘记密码
},
//账号正则校验
handleUsernameOrEmail(rule, value, callback) {
const { state } = this
const regex = /^([a-zA-Z0-9_-])+@([a-zA-Z0-9_-])+((\.[a-zA-Z0-9_-]{2,3}){1,2})$/
if (regex.test(value)) {
state.loginType = 0
} else {
state.loginType = 1
}
callback()
},
//登录按钮
handleSubmit(e) {
e.preventDefault()
const {
form: { validateFields },
state,
} = this
state.loginBtn = true
const validateFieldsKey = ['username', 'password']
validateFields(validateFieldsKey, { force: true }, (err, values) => {
if (!err) {
if (this.loginFlg == 1) {
state.loginBtn = true
} else {
const loginParams = {
username: values.username,
password: values.password,
}
this.$store
.dispatch('user/login', loginParams)
.then(() => {
this.loginFlg = 0
localStorage.setItem('loginFlg', 0)
this.loginSuccess()
state.loginBtn = false
})
.catch((error) => {
//错误提示
this.$notification.error({
top: '100px',
message: '提示',
description: error.message,
})
state.loginBtn = false
})
}
} else {
state.loginBtn = false
}
})
},
//提示
loginSuccess() {
this.$router.push({ path: '/' })
// 延迟 1 秒显示欢迎信息
setTimeout(() => {
this.$notification.success({
top: '100px',
message: '欢迎',
description: `${timeFix()},欢迎回来`,
})
}, 1000)
},
},
}
</script>
<style lang="less" scoped>
.login {
margin: 0;
padding: 0;
height: 100%;
width: 100%;
display: flex;
justify-content: center;
align-items: center;
.login-content {
border: 1px solid #d9d9d9;
border-radius: 12px;
width: 457px;
height: 420px;
transition: all 0.3s;
transform: translateX(0);
.login-title {
margin-bottom: 25px;
font-size: 36px;
text-align: center;
font-weight: bold;
color: #000;
}
.login-form {
/deep/.ant-form-item {
margin-bottom: 45px;
.ant-form-item-control {
width: 407px;
margin: 0 auto;
.ant-input {
height: 70px;
font-size: 26px;
color: #000;
background-color: transparent;
border: 2px solid #d9d9d9;
opacity: 1;
border-radius: 10px;
&:-internal-autofill-previewed,
&:-internal-autofill-selected {
-webkit-text-fill-color: #000 !important;
transition: background-color 5000s ease-in-out 0s !important;
}
}
}
}
}
.login-button {
margin-top: 14px;
width: 164px;
height: 60px;
background-color: #3a5dea;
border-color: #3a5dea;
border-radius: 30px;
font-size: 24px;
margin-right: 15px;
}
.forget-button,
.reg-button {
border: none;
background-color: rgba(0, 0, 0, 0);
color: #3a5dea;
height: 20px;
position: relative;
top: 10px;
}
}
}
</style>
1.src\api\user.js接口文件、src\store\modules\user.js数据管理文件、src\permission.js导航守卫文件、env三个文件、vue.config.js文件请根据实际的业务来更改
2.上面10步全部操作完成后,请重新yarn serve运行
3./family/auth/api/login/pwd接口返回值
失败:
{
"status": 1,
"message": "用户名或者密码不正确!",
"traceId": "0e87ddcd80d5497485ebba38244e55e5.81.16784273719495701",
"data": 1
}
成功:
{
"status": 0,
"message": "success",
"traceId": "0e87ddcd80d5497485ebba38244e55e5.81.16784273719495701",
"data": '这里是后端返回的token值'
}
4./family/user/personal/info接口入参是headers里带token,返回值
{
"status": 0,
"message": "success",
"traceId": "0e87ddcd80d5497485ebba38244e55e5.81.16784273719495701",
"data": {
userId:'用户的id',
userName:'用户的名称',
}
}