1.起步
1.1.前言
這篇文檔是跟隨騰訊課堂課程:前端教程vuex實(shí)現(xiàn)管理系統(tǒng),給自己寫的課堂隨筆,僅作記錄作用。建議先去vuex官網(wǎng)了解一些基本知識(shí)再開始。
本文檔可能會(huì)出現(xiàn)數(shù)據(jù)對(duì)不上的問題,原因是視頻中有不斷更改數(shù)據(jù),所以后面會(huì)對(duì)之前的數(shù)據(jù)庫(kù)或者頁(yè)面元素作出調(diào)整。然后我自己嘗試解決了部分工具版本上的問題,很多地方記錄的不詳盡,推薦看視頻。
主要用到的工具/包有:vue, vuex, npm, nodemon, mysql, element-ui, axios
1.2.初始化項(xiàng)目
-
打開項(xiàng)目位置文件夾,在地址欄輸入cmd打開當(dāng)前目錄的控制臺(tái),輸入以下代碼:
vue init webpack vuexms控制臺(tái)顯示圖 -
繼續(xù)在控制臺(tái)輸入指令以安裝相關(guān)包:
cd vuexms npm i初始完成的文件目錄 -
試著運(yùn)行我們的初始項(xiàng)目:
npm run dev顯示
控制臺(tái)表明我們的項(xiàng)目運(yùn)行在了http://localhost:8080,打開就能看到默認(rèn)的初始界面。
1.3.個(gè)性化
- 打開
vuexms\src\App.vue,這是我們項(xiàng)目的路由出口,同時(shí)刪掉<template>中的img標(biāo)簽和<style>中的所有內(nèi)容:
<!-- 這是默認(rèn)的主頁(yè)面logo圖 -->
<img src="./assets/logo.png">
-
打開
vuexms\src\components\HelloWorld.vue,這是路由指示的主界面,刪除<template>中的標(biāo)簽。同時(shí),將文件名改為我們需要的login.vue,然后更改src\router\index.js中的相關(guān)代碼。import Vue from 'vue' import Router from 'vue-router' // 引入組件 import login from '@/components/login' // 注冊(cè)路由 Vue.use(Router) // 導(dǎo)出路由 export default new Router({ routes: [ { path: '/login', name: 'login', component: login } ] })
2.實(shí)現(xiàn)登錄頁(yè)
2.1.使用Element
官網(wǎng)地址:Element
-
切換回控制臺(tái),
Ctrl+C暫時(shí)停止服務(wù)器。npm i element-ui -S -
找到
src目錄 下的main.js,添加以下代碼// 引入 import ElementUI from 'element-ui'; import 'element-ui/lib/theme-chalk/index.css'; // 注冊(cè) Vue.use(ElementUI); -
測(cè)試是否注冊(cè)成功,在
login.vue中加入以下代碼,重啟服務(wù)器:npm run dev,如果頁(yè)面中正常加載出了按鈕表示注冊(cè)成功。<el-button>按鈕</el-button>
2.2.使用組件
在
element-ui官網(wǎng)找到合適的表單登錄組件,分別將里面的HTML代碼和Javascript代碼放到對(duì)應(yīng)頁(yè)面,我們這里是login.vue。-
我們會(huì)加入
css代碼讓頁(yè)面更加美觀,在這之前應(yīng)該重置樣式來避免一些錯(cuò)誤??梢栽?code>static中新建css文件夾,然后放入合適的reset.css,然后再在index.html中引入:<!-- reset.css --> <link rel="stylesheet" href="./static/css/reset.css"> -
自定義數(shù)據(jù)和樣式。
登陸窗口
2.3.axios
安裝:
npm i axios -S
在main.js中引入:
// 引入 axios
import axios from 'axios'
// 掛載到 Vue 的原型上
Vue.prototype.axios = axios
在login.vue中檢測(cè):
submitForm(formName) {
this.$refs[formName].validate((valid) => {
if (valid) {
// 確認(rèn)是否能打印出信息
console.log(this.axios)
} else {
console.log('error submit!!');
return false;
}
});
},
向后臺(tái)發(fā)送數(shù)據(jù)并得到響應(yīng):
this.axios.post('/api/checklogin', {
username: _this.loginForm.username,
password: _this.loginForm.password
})
.then(response => {
console.log('接收后端響應(yīng)的數(shù)據(jù)')
})
2.4.express搭建服務(wù)器
實(shí)現(xiàn)登錄,需要從服務(wù)器獲取數(shù)據(jù),所以我用 express 搭建了一個(gè)。
-
確保全局安裝了 express,4.X 后的版本需要安裝 express-generator。
npm install -g express npm install -g express-generator在環(huán)境變量中添加 path: <u>C:\Users\admin\AppData\Roaming\npm</u>。
-
項(xiàng)目同級(jí)目錄打開cmd工具:
express server -e cd server npm install -
打開 server 中的 app.js 添加:
// 監(jiān)聽端口 app.listen(888, () => { console.log('888端口的服務(wù)器已經(jīng)啟動(dòng)...') })在 server\routes\index.js 中設(shè)置:
// 接收請(qǐng)求 router.post('/checklogin', (req, res) => { // 接收用戶名和密碼 let { username, password} = req.body; console.log(username, password) // 發(fā)送響應(yīng) res.send('服務(wù)器數(shù)據(jù)') })因?yàn)槭强缬蛘?qǐng)求,在 vuexms\config\index.js 中設(shè)置:
proxyTable: { '/api': { target: 'http://localhost:888', // 接口的域名 changeOrigin: true, // 如果是跨域請(qǐng)求,需要配置此項(xiàng) pathRewrite: { '/api': '' } } }項(xiàng)目控制臺(tái)重啟項(xiàng)目
npm run dev服務(wù)器控制臺(tái)啟動(dòng)服務(wù)器
node app-
打開項(xiàng)目頁(yè)面,輸入帳號(hào)密碼提交后檢查服務(wù)器控制臺(tái)和網(wǎng)頁(yè)響應(yīng)數(shù)據(jù):
服務(wù)端收到數(shù)據(jù)服務(wù)器數(shù)據(jù)
至此說明項(xiàng)目和服務(wù)器已經(jīng)聯(lián)通了,接下來需要比對(duì)數(shù)據(jù)庫(kù)進(jìn)行登錄驗(yàn)證。
2.5.連接數(shù)據(jù)庫(kù)
首先我們需要?jiǎng)?chuàng)建一個(gè)數(shù)據(jù)庫(kù),在 計(jì)算機(jī) -> 管理 -> 服務(wù) 中啟動(dòng) mysql,新開一個(gè) cmd 控制臺(tái)窗口:
-
連接mysql
mysql -hlocalhost -uroot -proot -
新建數(shù)據(jù)庫(kù)
show databases; create database vuexms; use vuexms; -
新建表
create table users ( id int primary key auto_increment, username varchar(50), password varchar(50), realname varchar(50), age int, idType varchar(50) ); desc users; -
插入數(shù)據(jù)
insert into users(username, password, realname, age, idType) value('xiaoyao', '123456', '李逍遙', '20', '00001'); insert into users(username, password, realname, age, idType) value('linger', '123456', '趙靈兒', '18', '00002'); select * from users;
然后通過 node 連接數(shù)據(jù)庫(kù):
-
在服務(wù)器控制臺(tái)安裝mysql包
npm i mysql -S -
在 server\routes 路徑下新建 conn.js 文件:
// 引入mysql var mysql = require('mysql') // 創(chuàng)建連接 var connection = mysql.createConnection({ host : 'localhost', user : 'root', password : '123456', database : 'vuexms' }); // 暴露出去 module.exports = connection -
在同路徑的 index.js 中:
// 引入連接數(shù)據(jù)庫(kù)模塊 const connection = require('./conn') // 連接數(shù)據(jù) connection.connect(() => { console.log('數(shù)據(jù)庫(kù)連接成功!') }) router.post('/checklogin', (req, res) => { ... // 執(zhí)行sql查詢 const sqlStr = `select * from users where username='${username}' and password='${password}'` connection.query(sqlStr, (err, data) => { if(err) { console.log(err) }else { res.send(data) } }) }) -
重啟服務(wù)器,會(huì)顯示數(shù)據(jù)庫(kù)連接成功。提交表單,在 Response 中可以看到發(fā)送回來的 data:
數(shù)據(jù)庫(kù)連接成功得到數(shù)據(jù)
-
渲染頁(yè)面,在 login.vue 中
.then(response => { if(response.data.length) { console.log('接收后端響應(yīng)的數(shù)據(jù)', response.data[0].username) _this.$message({ message: '恭喜你,登錄成功!', type: 'success' }) }else { _this.$message.error('請(qǐng)檢查用戶名或密碼') } })
3.保存登錄數(shù)據(jù)
3.1.vue異步加載組件
繼續(xù)在 login.vue 中
.then(response => {
if(...){
...
// 跳轉(zhuǎn)到首頁(yè)
_this.$router.push('/index')
}
...
})
那么我們需要在 vuexms\src\components 增加一個(gè)頁(yè)面組件 index.vue
<template>
<h1>index</h1>
</template>
...
在路由中配置,并將引入的方式改為異步加載
// 引入組件
// import login from '@/components/login'
// import index from '@/components/index'
// 異步加載
const login = () => import('@/components/login')
const index = () => import('@/components/index')
export default new Router({
routes: [
...
{
path: '/',
name: 'index',
component: index
}
]
})
3.2存入登錄用戶數(shù)據(jù)
-
安裝 vuex 包,在項(xiàng)目控制臺(tái):
npm i vuex -S -
在 src 目錄下新建 vuex 文件夾然后新建 store.js 文件:
// 引入 vuex import Vue from 'vue' import Vuex from 'vuex' // 注冊(cè) vue Vue.use(Vuex) // 狀態(tài) const state = { userinfo: JSON.parse(localStorage.getItem('userinfo')) } // mutations 用于操作 state const mutations = { // 保存用戶數(shù)據(jù) SAVE_USERINFO (state, userinfo) { // 把用戶數(shù)據(jù)放到本地存儲(chǔ)實(shí)現(xiàn)持久化 localStorage.setItem('userinfo', JSON.stringify(userinfo)) state.userinfo = userinfo console.log('賦值后的用戶信息:' , state.userinfo) } } // 創(chuàng)建 store 倉(cāng)庫(kù)暴露出去 export default new Vuex.Store({ state, mutations }) -
在 src\main.js 中
... import store from './vuex/store' ... new Vue({ ... store, }) -
login.vue
.then(response => { if(...){ ... // 把當(dāng)前用戶數(shù)據(jù)存入 state _this.$store.commit('SAVE_USERINFO', response.data[0]) ... _this.$router.push('/') } ... }) -
index.vue 主頁(yè)調(diào)用
<template> ... <p>用戶信息:{{ $store.state.userinfo }}</p> ... </template>

4.主頁(yè)
4.1.樣式
在 element 中選個(gè)喜歡的 container 組件樣式粘貼到 components\index.vue 組件中,進(jìn)行修改調(diào)整

4.2.路由跳轉(zhuǎn)
簡(jiǎn)單添加幾個(gè)其余的頁(yè)面組件,例如:userlist.vue, useradd.vue, passwordedit.vue。重要的是實(shí)現(xiàn)路由跳轉(zhuǎn),而在 element-ui 中有 router 屬性可以幫助我們節(jié)省步驟。
首先,確定需要進(jìn)行跳轉(zhuǎn)的樣式模塊,也就是頁(yè)面左側(cè)的導(dǎo)航欄,在元素中加入 router 屬性:
<el-menu :default-openeds="['1']" router>
然后試著點(diǎn)擊導(dǎo)航欄目錄,網(wǎng)頁(yè)地址會(huì)加上 el-menu-item 元素的 index 屬性的值。
然后,在 router\index.js 中:
// 引入
const home = () => import('@/components/home')
const userlist = () => import('@/components/userlist')
const useradd = () => import('@/components/useradd')
const passwordedit = () => import('@/components/passwordedit')
...
routes: [
...
{
path: '/',
redirect: '/home',
name: 'index',
component: index,
children: [
{
path: '/home',
name: 'home',
component: home
},
{
path: '/userlist',
name: 'userlist',
component: userlist
},
{
path: '/useradd',
name: 'useradd',
component: useradd
},
{
path: '/passwordedit',
name: 'passwordedit',
component: passwordedit
},
]
4.3.獲取state數(shù)據(jù)
想要在默認(rèn)頁(yè)面得到展示當(dāng)前用戶的一些數(shù)據(jù),就要從 state 中獲取,vuex 提供了一種更加簡(jiǎn)單的方式來幫助我們獲取到數(shù)據(jù),在 components\home.vue 中:
// 引入 mapState
import {mapState} from 'vuex'
export default {
computed: {
// 輔助函數(shù) 獲取 state 數(shù)據(jù)
...mapState({
// userinfo: this.$store.state.userinfo
userinfo: state => state.userinfo
})
}
}
5.用戶列表頁(yè)
5.1.觸發(fā)actions異步獲取數(shù)據(jù)
在 vuexms\src\vuex\store.js 中發(fā)起請(qǐng)求:
// 引入 axios
import axios from 'axios'
...
const state = {
...
userList: []
}
const mutations = {
...
// 獲取全局的用戶數(shù)據(jù)
GET_USERLIST (state, userList) {
state.userList = userList
}
}
// 定義 actions 異步的主要是 commit mutations,由 mutations 來改變狀態(tài)
const actions = {
GET_USERLIST({ commit }) {
// 使用 Promise,其他頁(yè)面可以通過 .then 的方式來保證頁(yè)面渲染前得到數(shù)據(jù)
return new Promise((resolve, reject) => {
axios.get('/api/getuserlist').then(response => {
// console.log('獲取用戶數(shù)據(jù)列表', response.data)
commit('GET_USERLIST', response.data)
resolve()
})
})
}
}
export default new Vuex.Store({
state,
mutations,
actions
})
在服務(wù)端 server\routes\index.js 響應(yīng):
// 接收獲取用戶列表的請(qǐng)求
router.get('/getuserlist', (req, res) => {
// 查詢數(shù)據(jù)庫(kù) 把當(dāng)前素有用戶數(shù)據(jù)返回給前端
const sqlStr = 'select * from users'
connection.query(sqlStr, (err, data) => {
if(err) {
throw err
}else {
res.send(data)
}
})
})
檢查是否傳回?cái)?shù)據(jù),在 vuexms\src\components\userlist.vue 中:
created() {
this.$store.dispatch('GET_USERLIST').then(() => {
console.log(this.$store.state.userList)
})
}

5.2.渲染頁(yè)面
繼續(xù)操作 userlist.vue 文件:
// 引入輔助函數(shù)
import {mapState, mapActions} from 'vuex'
// 刪除之前的假數(shù)據(jù)
data() {
return {
tableData: []
}
},
created() {
// 以下方式冗長(zhǎng)不擅于管理
// this.$store.dispatch('GET_USERLIST').then(() => {
// // console.log(this.$store.state.userList)
// // 把全局的 userlist 賦值給 tableData
// // this.tableData = this.$store.state.userList
// })
this.getUserList().then(() => {
this.tableData = this.userList
})
},
methods: {
...mapActions({
getUserList: 'GET_USERLIST'
}),
},
computed: {
...mapState({
userList: state => state.userList
})
}

5.3.過濾
為了使用 getters 屬性,在復(fù)制本頁(yè)面用戶列表添加到下方,并改為目標(biāo)用戶表,我們做過濾出年齡大于20歲的用戶列表這個(gè)操作:
data() {
return {
tableData: [],
newtableData: []
};
},
created() {
this.getUserList().then(() => {
...
this.newtableData = this.userList.filter(v => v.age > 20)
});
}
把需要獲取的數(shù)據(jù)定義成全局的,但是效果不變,在 vuex\store.js 中:
// 定義全局共享屬性 getter
const getters = {
vipUsers: state => state.userList.filter(v => v.age > 20)
}
export default new Vuex.Store({
...
getters
})
回到 userlist.vue 文件:
created() {
this.getUserList().then(() => {
...
// this.newtableData = this.userList.filter(v => v.age > 20)
this.newtableData = this.$store.getters.vipUsers
});
}
使用 mapGetters 輔助函數(shù)使編寫更簡(jiǎn)潔:
import { mapState, mapActions, mapGetters} from "vuex";
...
created() {
this.getUserList().then(() => {
...
this.newtableData = this.vipUsers
});
}
...
computed: {
...mapGetters(['vipUsers']),
...
}

到此,對(duì) vuex 的重要屬性就基本都使用了,也搭好了一個(gè)簡(jiǎn)單的管理系統(tǒng)模板。
我有意將它具化成較詳盡的資產(chǎn)管理系統(tǒng),等完成以后發(fā)布出來。







