
小蓝开发了一个登录功能,但是在登录界面中输入用户名后点击“确认”按钮并没有如预期般成功进入欢迎界面。但是从出现欢迎语来看,数据已经发生了改变,到底是怎么回事呢?请帮助小蓝排查代码,让登录功能回归正常吧!
目录结构如下:
├── 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 状态,最后根据状态的变化显示相应的界面。
