小蓝开发了一个登录功能,但是在登录界面中输入用户名后点击“确认”按钮并没有如预期般成功进入欢迎界面。但是从出现欢迎语来看,数据已经发生了改变,到底是怎么回事呢?请帮助小蓝排查代码,让登录功能回归正常吧!
目录结构如下:
├── components
│ ├── login.js
│ └── panel.js
├── css
│ └── style.css
├── index.html
├── lib
│ ├── vue.min.js
│ └── vuex.min.js
└── store
├── BaseModule.js
├── UserModule.js
└── index.js
其中:
index.html
是主页面。components
是为示例组件文件夹。lib
是存放项目相关依赖的文件夹。store
是 Vuex 状态管理文件夹。css
是存放项目样式的文件夹。注意:打开环境后发现缺少项目代码,请复制下述命令至命令行进行下载。
cd /home/project
wget https://labfile.oss.aliyuncs.com/courses/18164/dist_03.zip
unzip dist_03.zip
mv dist/* ./
rm -rf dist*
在浏览器中预览 index.html
页面效果如下:
此时输入用户名后回车/点击确定,数据发生改变,但还是停留在登录页,无法正确显示登录成功界面。
找到
index.html
中的TODO
部分,仔细阅读store
文件夹下的相关代码并结合Vuex
相关知识,排查代码中存在的问题,修改后使得登录界面输入 admin 时,点击确认按钮/回车可以正确显示如下界面:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="./lib/vue.min.js"></script>
<script src="./lib/vuex.min.js"></script>
<script src="./store/BaseModule.js"></script>
<script src="./store/UserModule.js"></script>
<script src="./store/index.js"></script>
<link rel="stylesheet" href="./css/style.css">
</head>
<body>
<div id="app">
<div class="wrapper" style="width: 900px;">
<!-- 2. 登录成功后的欢迎界面 -->
<Panel v-if="token" :username="username">
{{welcome}}
</Panel>
<!-- 1. 登录界面 -->
<Login v-else @confirm="login">
{{welcome}}
</Login>
</div>
</div>
<script src="./components/login.js"></script>
<script src="./components/panel.js"></script>
<script>
// 修改下面错误代码
var app = new Vue({
el: '#app',
data() { },
computed: {
welcome() {
// 使用 window.$store 访问 getters
return window.$store.getters['base/welcome'];
},
username() {
// 加上命名空间前缀访问 user 模块的 getters
return window.$store.getters['user/username'];
},
token() {
// 加上命名空间前缀访问 user 模块的 getters
return window.$store.getters['user/token'];
}
},
methods: {
// 回车/点击确认的回调事件
login(username) {
if (username) {
// 加上命名空间前缀提交 user 模块的 mutations
window.$store.commit('user/login', { username, token: 'sxgWKnLADfS8hUxbiMWyb' });
// 提交 base 模块的 mutations
window.$store.commit('base/say', '登录成功,欢迎你回来!');
}
}
}
})
</script>
</body>
</html>
1. HTML 头部
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="./lib/vue.min.js"></script>
<script src="./lib/vuex.min.js"></script>
<script src="./store/BaseModule.js"></script>
<script src="./store/UserModule.js"></script>
<script src="./store/index.js"></script>
<link rel="stylesheet" href="./css/style.css">
</head>
<meta charset="UTF-8">
:设置页面的字符编码为 UTF - 8,确保页面能正确显示各种字符。<meta http-equiv="X-UA-Compatible" content="IE=edge">
:指定页面在 Internet Explorer 中以最新的渲染模式显示。<meta name="viewport" content="width=device-width, initial-scale=1.0">
:设置页面的视口,使页面在不同设备上能自适应显示。./lib/vue.min.js
:引入 Vue.js 库,用于构建响应式的用户界面。./lib/vuex.min.js
:引入 Vuex 库,用于管理应用的状态。./store/BaseModule.js
、./store/UserModule.js
和 ./store/index.js
:引入 Vuex 模块和存储配置文件。./css/style.css
:引入外部样式文件,用于美化页面。2. HTML 主体
<body>
<div id="app">
<div class="wrapper" style="width: 900px;">
<!-- 2. 登录成功后的欢迎界面 -->
<Panel v-if="token" :username="username">
{{welcome}}
</Panel>
<!-- 1. 登录界面 -->
<Login v-else @confirm="login">
{{welcome}}
</Login>
</div>
</div>
<script src="./components/login.js"></script>
<script src="./components/panel.js"></script>
<!-- 定义 Vue 实例的脚本代码 -->
</body>
<div id="app">
:是 Vue 实例的挂载点,Vue 会接管该元素及其子元素的渲染和交互。<Panel>
组件:使用 v-if="token"
指令,当 token
存在时渲染该组件,用于显示登录成功后的欢迎界面,并通过 :username="username"
传递 username
数据。<Login>
组件:使用 v-else
指令,当 token
不存在时渲染该组件,用于显示登录界面,并通过 @confirm="login"
监听 confirm
事件,触发 login
方法。./components/login.js
和 ./components/panel.js
:引入登录和欢迎界面的组件脚本。3. Vue 实例
var app = new Vue({
el: '#app',
data() { },
computed: {
welcome() {
return window.$store.getters['base/welcome'];
},
username() {
return window.$store.getters['user/username'];
},
token() {
return window.$store.getters['user/token'];
}
},
methods: {
// 回车/点击确认的回调事件
login(username) {
if (username) {
window.$store.commit('user/login', { username, token: 'sxgWKnLADfS8hUxbiMWyb' });
window.$store.commit('base/say', '登录成功,欢迎你回来!');
}
}
}
})
el
属性:指定 Vue 实例的挂载点为 #app
。data
选项:这里为空,因为数据主要通过 Vuex 进行管理。methods
选项: login
方法:当用户在登录界面输入用户名并触发 confirm
事件时调用。如果用户名存在,则通过 window.$store.commit
提交两个 mutations
: user/login
:更新 UserModule
中的 username
和 token
状态。base/say
:更新 BaseModule
中的 welcome
状态,显示登录成功的欢迎信息。4. 关键知识点
Vuex 模块和命名空间
BaseModule
和 UserModule
,便于管理不同功能的状态。UserModule
开启了命名空间(namespaced: true
),在访问其 getters
和提交 mutations
时需要加上命名空间前缀,如 user/
。Vue 指令
v-if
和 v-else
:用于条件渲染,根据 token
的存在与否决定显示登录界面还是欢迎界面。@
语法:用于监听组件的自定义事件,如 @confirm="login"
监听 Login
组件的 confirm
事件。Vuex 的 getters
和 mutations
getters
:用于获取 Vuex 状态,类似于计算属性,可避免直接访问状态。mutations
:用于修改 Vuex 状态,是唯一可以修改状态的方式,通过 commit
方法触发。Login
组件
const LoginTemplate = `
<div class="wrapper login-wrapper" style="width: 539px;">
<div class="newsletter">
<div>
<h2> <slot /> </h2>
<p>(例如: admin)</p>
</div>
<form>
<div class="c-input-group">
<label for="newsletter" class="c-label sr-only"></label>
<input v-model="name" type="text" class="c-input" id="newsletter" placeholder="请输入用户名">
</div>
<button @click="login" class="c-button">确认</button>
</form>
</div>
</div>
`;
Vue.component('Login', {
name: 'Login',
template: LoginTemplate,
props: {},
data() {
return {
name: '',
};
},
watch: {},
mounted() {},
methods: {
login(e) {
this.$emit('confirm', this.name);
e.preventDefault();
},
},
});
LoginTemplate
):定义了登录界面的 HTML 结构,包含一个输入框用于输入用户名和一个确认按钮。data
:定义了一个局部变量 name
,用于存储用户输入的用户名。methods
:定义了 login
方法,当用户点击确认按钮时触发。该方法通过 $emit
触发一个自定义事件 confirm
,并将用户输入的用户名作为参数传递出去,同时调用 e.preventDefault()
阻止表单的默认提交行为。Panel
组件
const PanelTemplate = `
<blockquote v-show="username" class="c-quote">
<div class="c-quote__content">
<p> <slot /> </p>
</div>
<cite class="c-quote__cite">
<img src="data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wCEAAoKCgoKCgsMDAsPEA4QDxYUExMUFiIYGhgaGCIzICUgICUgMy03LCksNy1RQDg4QFFeT0pPXnFlZXGPiI+7u/sBCgoKCgoKCwwMCw8QDhAPFhQTExQWIhgaGBoYIjMgJSAgJSAzLTcsKSw3LVFAODhAUV5PSk9ecWVlcY+Ij7u7+//CABEIACgAKAMBIgACEQEDEQH/xAAxAAACAgIDAAAAAAAAAAAAAAAABQIEAwYBBwgBAAMBAAAAAAAAAAAAAAAAAAECAwD/2gAMAwEAAhADEAAAAOubFVlZafO2E2sG4gPnWbh1IamzcVxlhnDpMQnskgQUwHP/xAArEAACAQMCAwYHAAAAAAAAAAABAgMABBEFIQYSMRMUMkFRkRAiQkNxgdH/2gAIAQEAAT8AWKkgJ8qu4rqCJ5kfCggYxnb1rR7p7m5W2l3Z/AcVHpzDqtJqcAXJiOfTNQappxKh2ZMjzX+VE9jLGw7WF1Zd1LDcHau52sWu2iGSPs7e07eWRsDLydPYEYqG0UgHb4KjP4aW2lZlT5AW82dVH7JO1a/drf6jJNEwaLlVU/CirbXNas41jgv5VRei5zVvo5m6yhai4Xt2XLXlPw9Zq/IJ5ifXkwvvT8Px/ROak0SRPuire7K0l8ceKjdsRXenCk8+1TXRK75Ff//EAB0RAAICAQUAAAAAAAAAAAAAAAABAgMTERIhQVH/2gAIAQIBAT8AyIdvJlHbFdmWJvrfhKKNEJI//8QAGBEAAwEBAAAAAAAAAAAAAAAAABESECD/2gAIAQMBAT8ARJOvj//Z" alt="">
<div>
<p><span>用户名:</span><strong id="username"> {{username}} </strong></p>
</div>
</cite>
</blockquote>
`;
Vue.component('Panel', {
name: 'Panel',
template: PanelTemplate,
props: ['username'],
data: {},
watch: {},
mounted() {},
methods: {},
});
PanelTemplate
):定义了登录成功界面的 HTML 结构,包含一个引用块和用户头像、用户名信息。props
:接收一个 username
属性,用于显示登录用户的用户名。v-show
指令:根据 username
是否存在来决定是否显示该组件。BaseModule
const BaseModule = {
state: () => ({
welcome: '请输入用户名登录系统',
}),
getters: {
welcome(state) {
return state.welcome;
},
},
mutations: {
say(state, content) {
state.welcome = content;
},
},
actions: {},
};
state
:定义了一个状态 welcome
,初始值为 '请输入用户名登录系统'
。getters
:定义了一个 welcome
getter,用于获取 welcome
状态的值。mutations
:定义了一个 say
mutation,用于修改 welcome
状态的值。UserModule
const UserModule = {
namespaced: true,
state: () => ({
username: '',
token: null,
}),
getters: {
username(state) {
return state.username;
},
token(state) {
return state.token;
},
},
mutations: {
login(state, { username, token }) {
state.username = username;
state.token = token;
},
},
};
namespaced
:设置为 true
,表示该模块使用命名空间,避免命名冲突。state
:定义了两个状态 username
和 token
,初始值分别为 ''
和 null
。getters
:定义了两个 getter,分别用于获取 username
和 token
状态的值。mutations
:定义了一个 login
mutation,用于更新 username
和 token
状态的值。存储实例创建
const store = new Vuex.Store({
modules: {
base: BaseModule,
user: UserModule,
},
});
window.$store = store;
store
,并将 BaseModule
和 UserModule
作为模块注册到存储中。window.$store
,以便在全局范围内访问。BaseModule
和 UserModule
两个 Vuex 模块,并创建了一个 Vuex 存储实例 store
。BaseModule
存储了欢迎信息,初始状态下 welcome
的值为 '请输入用户名登录系统'
。UserModule
存储了用户相关信息,包括 username
(初始为空字符串)和 token
(初始为 null
),并且该模块开启了命名空间(namespaced: true
)。window.$store
,以便在全局范围内可以访问和操作这个存储实例。const BaseModule = {
state: () => ({
welcome: '请输入用户名登录系统'
}),
// ...
};
const UserModule = {
namespaced: true,
state: () => ({
username: '',
token: null
}),
// ...
};
const store = new Vuex.Store({
modules: {
base: BaseModule,
user: UserModule
}
});
window.$store = store;
Login
和 Panel
两个 Vue 组件。Login
组件用于显示登录界面,包含一个输入框让用户输入用户名和一个确认按钮。Panel
组件用于显示登录成功后的欢迎界面,接收 username
作为属性来显示登录用户的用户名。Vue.component('Login', {
// ...
});
Vue.component('Panel', {
// ...
});
computed
属性获取 token
的值,根据 token
是否存在来决定显示哪个组件。token
为 null
,所以会显示 Login
组件,也就是登录界面。<div id="app">
<div class="wrapper" style="width: 900px;">
<!-- 登录成功后的欢迎界面 -->
<Panel v-if="token" :username="username">
{{welcome}}
</Panel>
<!-- 登录界面 -->
<Login v-else @confirm="login">
{{welcome}}
</Login>
</div>
</div>
Login
组件中,用户在输入框输入用户名,输入框使用 v-model
指令绑定到组件的 name
数据属性上,实现双向数据绑定。login
方法。const LoginTemplate = `
<!-- ... -->
<input v-model="name" type="text" class="c-input" id="newsletter" placeholder="请输入用户名">
<!-- ... -->
<button @click="login" class="c-button">确认</button>
<!-- ... -->
`;
Vue.component('Login', {
// ...
data() {
return {
name: ''
};
},
methods: {
login(e) {
this.$emit('confirm', this.name);
e.preventDefault();
}
}
});
login
方法通过 $emit
触发一个自定义事件 confirm
,并将用户输入的用户名作为参数传递出去,同时调用 e.preventDefault()
阻止表单的默认提交行为。Login
组件的 confirm
事件,当事件触发时调用 login
方法。login
方法中,首先检查用户名是否存在,如果存在则进行以下操作: window.$store.commit
提交 user/login
mutation 到 UserModule
,更新 username
和 token
状态。这里模拟生成了一个固定的 token
值 'sxgWKnLADfS8hUxbiMWyb'
。base/say
mutation 到 BaseModule
,更新欢迎信息为 '登录成功,欢迎你回来!'
。var app = new Vue({
// ...
methods: {
login(username) {
if (username) {
window.$store.commit('user/login', { username, token: 'sxgWKnLADfS8hUxbiMWyb' });
window.$store.commit('base/say', '登录成功,欢迎你回来!');
}
}
}
});
token
已经被更新为一个有效的值,根据 HTML 中 v-if
和 v-else
指令的判断,会隐藏 Login
组件,显示 Panel
组件。Panel
组件接收 username
属性,显示登录用户的用户名,同时显示更新后的欢迎信息。<Panel v-if="token" :username="username">
{{welcome}}
</Panel>
整个登录功能的工作流程是从页面初始化开始,用户在登录界面输入用户名并提交,触发登录事件,更新 Vuex 状态,最后根据状态的变化显示相应的界面。