再梳理一遍
一、商品后臺管理系統
1. 功能
1.1 服務端情況
- 開啟了CORS跨域支持
- 需要授權的 API ,必須在請求頭中使用
Authorization
字段提供token
令牌(axios攔截器) baseUrl
,接口地址:http://localhost:8888/api/private/v1/
token令牌在服務端生成,當登錄成功時,post請求返回的用戶信息里包含了這一元素:
1.1.1 axios發ajax請求
Axios:通過promise實現對ajax技術的一種封裝,ajax只能訪問同源的請求。axios并沒有install 方法,所以是不能使用vue.use()方法的。為了不在每個文件都引用一次axios,將它改造成vue插件。
- npm安裝
- axios發請求
1)使用
axios.get('/user?ID=12345').then(function (response) {console.log(response);}).catch(function (error) {console.log(error);});
2) 改造為vue插件
plugins/http.js
import axios from 'axios'
?
const httpHelper = {}
// 配置Vue插件
httpHelper.install = function fn (Vue) {axios.defaults.baseURL = 'http://127.0.0.1:8888/api/private/v1'Vue.prototype.$http = axios
}
?
export default httpHelper
main.js里安裝插件
import http from '@/plugins/http.js'
Vue.use(http)
1.1.2 攔截器 – 向服務端發請求需要token
httpAxios.install = function (Vue) {const instance = axios.create({baseURL: 'http://127.0.0.1:8888/api/private/v1'})
?instance.interceptors.request.use(function (config) {if (config.url.toLowerCase() !== 'login') {const token = sessionStorage.getItem('token')config.headers.Authorization = token}return config}, function (error) {return Promise.reject(error)})Vue.prototype.$http = instance
}
1.1.3 路由導航守衛 – 權限控制
路由的前置守衛 router.js
router.beforeEach((to, from, next) => {console.log(to, from)if (to.name === 'login') {next()} else {const token = sessionStorage.getItem('token')if (!token) {router.push({ 'name': 'login' })Message.warning('請先登錄')return}next()}
})
1.1.4 使用async和await
handleLogin () {this.$http.post('login', this.formData).then((res) => {const data = res.dataconst {meta: {status, msg}} = dataif (status === 200) {const token = data.data.tokensessionStorage.setItem('token', token)this.$message.success(msg)} else {this.$message.error(msg)}})
}
async handleLogin () {const res = await this.$http.post('login', this.formData)const data = res.dataconst { meta: { status, msg } } = dataif (status === 200) {const token = data.data.tokensessionStorage.setItem('token', token)this.$message.success(msg)} else {this.$message.error(msg)}
}
外層方法使用async
關鍵字(await最近的外層函數要加上async),發請求代碼前加 await
,省略.then
1.2 自定義組件
以自定義面包屑組件(子組件)為例,父組件(使用到面包屑的組件)向子組件傳值。
子組件:
<template><!-- 面包屑 --><el-breadcrumb separator="/"><el-breadcrumb-item :to="{ path: '/' }">首頁</el-breadcrumb-item><el-breadcrumb-item>{{this.level1}}</el-breadcrumb-item><el-breadcrumb-item>{{this.level2}}</el-breadcrumb-item></el-breadcrumb>
</template>
?
<script>
export default {name: 'my-bread',props: ['level1', 'level2']
}
</script>
父組件:
<my-bread level1='權限管理' level2='角色列表'></my-bread>
1.3 登錄
- 組件el-form 表單
- :rules=“rules” 表單驗證,
show-password
- 登錄按鈕的點擊事件:
1)表單驗證是否通過
2)通過設置token,跳轉到主頁 - 頭像:css設置水平居中
- 全局監聽enter鍵(把監聽事件綁定到document上),當(that.$route.path === ‘/login’) && (e.keyCode === 13),觸發登錄方法
1.4 主頁
- 布局
- 頭部+左側導航菜單+主體
1.4.1 頭部,退出
- 刪除token
sessionStorage.clear()
- 跳轉到登錄頁
this.$router.push({name: 'login'})
1.4.2 左側菜單 – 權限控制
- el-aside -> el-menu
- :router 開啟路由模式,設置el-menu-item的index值 為路由標識
- 動態渲染:不同身份登錄,權限控制
一級菜單和子菜單之間是并列關系,在知道有幾級菜單的情況下,思考:如果不知道有幾級菜單?
<el-menu :unique-opened=true :router=true><el-submenu :index="item1.order.toString()" v-for="(item1,i) in menus" :key="item1.id"><template slot="title"><i :class="iconlist[i]"></i><span>{{item1.authName}}</span></template><el-menu-item :index="item2.path" v-for="item2 in item1.children" :key="item2.id"><i class="el-icon-menu"></i><span>{{item2.authName}}</span></el-menu-item></el-submenu>
</el-menu>
1.5 系統功能
1.5.1 用戶管理 - 用戶列表 - 增刪改查
- 展示:現有用戶數量
GET
- 增加:用戶名、密碼、郵箱、手機,新增后重新加載頁面
POST
- 刪除:刪除后重新加載頁面
PUT
- 修改:
DELETE
1)改基本信息:手機、郵箱
2)改角色:關系到權限 - 組件
面包屑、搜索框、表格、分頁組件、switch狀態轉換
1.5.1.1 顯示、搜索的處理
顯示:
在created的時候,調用this.getUserList()
,拿到res的數據,賦值給data里的userlist
const res = await this.$http.get(`users?query=${this.query}&pagenum=${this.pagenum}&pagesize=${this.pagesize}`)
搜索:
<el-input @clear="getUserList" v-model="query" clearable @keyup.enter.native="getUserList">
鍵盤抬起時/輸入框清空時,依然是觸發getUserList
,該方法內部通過雙向數據綁定的query
進行傳參查詢,接口文檔中,query
參數可以為空,為空時顯示全量信息。
1.5.1.2 slot-scope=“scope” 參數傳遞
<el-table-column label="用戶狀態"><template slot-scope="scope"><!-- scope.row就是當前綁定的數據對象 --><el-switch @change="handleSwitchChange(scope.row)" v-model="scope.row.mg_state" active-color="#13ce66"inactive-color="#ff4949"></el-switch></template>
</el-table-column>
1.5.1.3 添加完成,清空文本框
for (const key in this.formData) {this.formData[key] = ''
}
1.5.1.4 編輯用戶,顯示已有信息
編輯按鈕所在行,通過slot-scope="scope"
將被編輯用戶信息傳給方法editUser
,通過初始化form(顯示用戶信息的el-form所綁定的內容)展示
editUser(user) {this.dialogFormVisibleEdit = truethis.form = user
}
<el-dialog title="編輯用戶" :visible.sync="dialogFormVisibleEdit"><el-form :model="form"><el-form-item label="用戶名" ><el-input v-model="form.username" disabled></el-input></el-form-item></el-form>
</el-dialog>
1.5.1.5 下拉框-顯示當前用戶角色
- select標簽有多個option,有
selected
屬性的option,會默認選中 - 被選中的option的value值就是select的value值,當選擇了其他角色時,select的value值也會隨之改變 => 獲取當前編輯用戶的角色id,雙向綁定給select的value,既處理了顯示,又處理了角色分配。
1.5.2 權限管理 - 權限列表、角色列表
1.5.2.1 權限列表
一到三級權限的名詞展示,可以作為后臺的文檔數據,不必作為一項功能
1.5.2.2 角色列表
- 添加角色(簡單提交)
- 編輯、刪除(簡單slot-scope傳參、delete請求)
- 分配角色(tag)
- 權限展示(表格展開行)
【難點1】 三級權限展示,樹形結構,row、col的結構
請求角色列表的產物,因此數據包含在getRoleList
的返回值里。
理解el-row和el-col的關系,兩者包含成為一維。
<el-row :gutter="20"><el-col :span="6"><div class="grid-content bg-purple"></div></el-col><el-col :span="6"><div class="grid-content bg-purple"></div></el-col><el-col :span="6"><div class="grid-content bg-purple"></div></el-col><el-col :span="6"><div class="grid-content bg-purple"></div></el-col>
</el-row>
{"data": [{"id": 30,"roleName": "主管","roleDesc": "技術負責人","children": [{"id": 101,"authName": "商品管理","path": null,"children": [{"id": 104,"authName": "商品列表","path": null,"children": [{"id": 105,"authName": "添加商品","path": null}]}]}]}],
}
代碼怎么寫:
- 先縱向看,有幾個一級權限就應當v-for循環出幾行
- 再橫向看,每個el-row里,拿一個el-col現實出當級權限,另一個el-col再作為一個整體來看(再看成一行兩列)
<el-row v-for="(item1,i) in scope.row.children" :key="i" class="expand"><!-- 第一列中放一級標簽 --><el-col :span="4"><!-- 可移除標簽,這里展示一級標簽,綁定取消權限方法 --><el-tag class="expand" closable @close="delRight(item1.id,scope.row)">{{item1.authName}}</el-tag></el-col><el-col :span="20"><!-- 第二列也是一行兩列 --><el-row v-for="(item2,index) in item1.children" :key="index"><!-- 第二行第一列中放二級標簽 --><el-col :span="4"><el-tag class="expand" type="success" closable @close="delRight(item2.id,scope.row)">{{item2.authName}}</el-tag></el-col><el-col :span="20"><el-tag class="expand" @close="delRight(item3.id,scope.row)" closable type="warning"v-for="(item3,indexInner) in item2.children" :key="indexInner">{{item3.authName}}</el-tag></el-col></el-row></el-col>
</el-row>
【難點2】 分配權限,樹形結構el-tree 全選、半選(如何展示)
- 展開所有的權限(樹形結構自帶的屬性:
default-expand-all
) - 顯示:先勾選上擁有的三級權限(三層forEach,獲得所有三級權限的集合,賦值給
checklistArr
)
:default-checked-keys="checklistArr"
- 提交:需要獲取所有的一、二、三級權限的集合(全選+半選),傳參修改
顯示:
editRight(role) {// checklist權限的集合,是個樹形結構this.checklist = role.childrenthis.currentRoleId = role.idvar tmpArr = []this.checklist.forEach(item1 => {var item2 = item1.childrenitem2.forEach(item2 => {var item3 = item2.childrenitem3.forEach(item3 => {tmpArr.push(item3.id)})})})this.checklistArr = tmpArr
}
提交:
async confirmRole() {let arr1 = this.$refs.mytree.getCheckedKeys()let arr2 = this.$refs.mytree.getHalfCheckedKeys()let arr = [...arr1, ...arr2]const res = await this.$http.post(`roles/${this.currentRoleId}/rights`, { rids: arr.join(',') })
}
1.5.3 商品管理 - 列表、分類參數、商品分類
1.5.3.1 商品分類
分類接口設計:
重要參數type
,type=2(返回一層、二層分類),type=3(返回前三層分類)
1.【難點3】樹形組件element-tree-grid在表格中的運用(組件選擇、配置)
- 表格樣式:樹形結構 +【是否有效】【級別】【操作】字段;
- 組件選擇:由于第一列的el-table-column要展示為樹形結構,因此使用組件element-tree-grid,all props of el-table-column are supported;
- 在獲取到三級商品分類后,element-tree-grid只需要綁定好對應的屬性,即可顯示。
<el-tree-grid treeKey="cat_id" parentKey="cat_pid" levelKey="cat_level" childKey="children" prop="cat_name"label="分類名稱">
</el-tree-grid>
2.【難點4】級聯選擇器-添加分類(理解接口type參數)
- 由于最細添加的是三級分類(或者添加一級、二級分類),因此級聯選擇器只用展示一、二級的父類,請求分類接口,type=2
- value / v-model: 選中項綁定值,是一個數組
- 級聯選擇器的
handleChange
方法,能獲取到最接近一層的父類參數分類id,配置級別和分類名稱,組成一個obj作為參數發POST請求
handleChange(value) {console.log(value);}
1.5.3.2 【難點5】分類參數 – 只允許為三級分類設置參數
- 級聯選擇器 type=3 展示出三級分類
- 動態(涉及列表展開)、靜態參數
如何限定只讓第三級被選中:
handleChange () {if (this.selectedOptions.length !== 3) {this.$message.warning('商品只能添加到三級分類')this.selectedOptions.length = 0}}
1.5.3.3 【難點6】什么時候渲染出參數,tabs標簽頁改造
在el-tab-pane
里含了el-table
–綁定dynamicAttrs el-button
在點擊了級聯選擇器器之后,渲染出動、靜態參數
async handleChange() {if (this.value.length === 3) {if (this.active === '1') {// 動態參數const res = await this.$http.get(`categories/${this.value[2]}/attributes?sel=many`)this.dynamicAttrs = res.data.datathis.dynamicAttrs.forEach((item) => {// 把attr_vals轉為數組類型item.attr_vals = item.attr_vals.split(',')})} else if (this.active === '2') {const res1 = await this.$http.get(`categories/${this.value[2]}/attributes?sel=only`)this.staticAttrs = res1.data.data}}
}
1.5.3.4 【體現功能】商品列表 - 1000條數據
商品參數數據格式:
參數名–參數值(可能有多個)
"data": [{"attr_id": 1,"attr_name": "cpu","cat_id": 22,"attr_sel": "only","attr_write": "manual","attr_vals": "2G","4G","8G"}],
- 查詢名稱、價格、重量、參數管理
- 添加商品詳細描述(名稱、價格、重點、數量、分類、參數、屬性、圖片)
- 步驟條組件+縱向tabs組件(需要包裹在el-form里)(并列關系)
- 上傳圖片(餓了么Upload組件、百度Web Uploader)
- 富文本編輯器
- 功能上的bug,商品列表不能顯示圖片(接口沒返回這個字段)
- bug2,數據庫表設計時,時間字段長度太短,導致時間不正確,令人汗顏
改了表字段的數據類型,可能不是最合適,但是時間顯示正常了。創建時間的時候,還是以1970位默認值。
1. 上傳圖片 – 拓展,檢測文件格式;性能
請求上傳接口,需要設置token(這里不是發axios請求)
<el-upload
action="http://47.xxx.1xx.7:8888/api/private/v1/upload"
:headers="header"
:on-remove="handleRemove"
:on-success="handleSuccess"
:file-list="fileList"
list-type="picture"><el-button size="small" type="primary">點擊上傳</el-button><div slot="tip" class="el-upload__tip">只能上傳jpg/png文件,且不超過500kb</div>
</el-upload>header: {Authorization: sessionStorage.getItem('token')
}
2. 富文本編輯器組件 vue-quill-editor
<quill-editor v-model="form.goods_introduce"></quill-editor>
1.5.4 訂單管理
1.5.5 數據統計
2. 框架選擇 – vue
- 容易上手:react(
JSX
,學習成本),vue(模板):任何合乎規范的 HTML 都是合法的 Vue 模板。可以在單文件組件里完全控制 CSS,將其作為組件代碼的一部分。 - vue-cli(核心插件)供了各種用途的模板。
- 體積:一個包含了 Vuex + Vue Router 的 Vue 項目 (gzip 之后 30kB) 相比使用了這些優化的 angular-cli 生成的默認項目尺寸 (~65KB) 還是要小得多。
- 當某個組件的狀態發生變化時,系統能精確知曉哪個組件確實需要被重渲染。(組件的依賴是在渲染過程中自動追蹤的)
3. 組件庫
3.1 elementUI
3.1.1 vue-cli安裝插件
vue create mypro
cd mypro
vue add element
這里選擇的是按需引入:
觀察項目文件夾中多了plugins-element.js
默認按需引入只引入了{Button組件} (可以減小體積),修改代碼
3.1.2 echarts
4. 項目結構
一般腳手架都應當有以下的幾個功能:
- 自動化構建代碼,比如打包、壓縮、上傳等功能
- 本地開發與調試,并有熱替換與熱加載等功能
- 本地接口數據模擬
- css 模塊化:拓展語言、預處理器saas、less(css 不是編程語言,所以不能聲明變量、函數,不能做判斷、循環和計算,也不能嵌套)
- 檢查并自動矯正不符合規范的代碼,并優化代碼格式
定義好項目的目錄結構
- 解耦:代碼盡量去耦合,這樣代碼邏輯清晰,也容易擴展
- 分塊:按照功能對代碼進行分塊、分組,并能快捷的添加分塊、分組
- 編輯器友好:需要更新功能時,可以很快的定位到相關文件,并且這些文件應該是很靠近的,而不至于到處找文件
5. 亮點
- 權限、商品分類涉及到多層級的樹形結構
- 權限控制