目錄
- [d2-admin基本使用]
- [1 安裝]
- [1.1 全局安裝 d2-admin]
- [1.2 創(chuàng)建項(xiàng)目]
- [1.3 啟動項(xiàng)目]
- [1.4 注意事項(xiàng)]
- [2 修改框架 title 和 logo]
- [2.1 修改 title]
- [2.2 修改 logo]
- [3 圖表庫]
- [3.1 常用的圖表庫]
- [3.2 安裝v-charts]
- [3.3 導(dǎo)入項(xiàng)目]
- [3.4 簡單舉例]
- [3.5 注意事項(xiàng)]
- [4 CURD插件(表格)]
- [4.1 安裝]
- [4.2 導(dǎo)入項(xiàng)目]
- [4.3 圖表使用]
- [4.4 注意事項(xiàng)]
- [5 定義數(shù)據(jù)API]
- [5.1 第一步洞翩,使用mockjs創(chuàng)建數(shù)據(jù)]
- [5.2 第二步役纹,創(chuàng)建API接口葵袭,共VUE項(xiàng)目調(diào)用]
- [5.3 第三步,在 VUEX 中創(chuàng)建API进统,調(diào)用第二步中創(chuàng)建API接口,對數(shù)據(jù)進(jìn)行操作]
- [5.4 第四步浪听,在 view 中調(diào)用 VUEX 中的模塊中的方法實(shí)現(xiàn)具體功能螟碎。]
- [6 權(quán)限控制]
- [6.1 第一步,在 mock 中添加用戶信息迹栓,以及不同的用戶對應(yīng)的不同的權(quán)限掉分,并保存至本地的sessionStorage中。]
- [6.2 第二步克伊,在 src/API 中定義調(diào)用 mock 中 API 的接口酥郭,取到用戶信息]
- [6.3 第三步,在 router/index.js 中添加鉤子函數(shù)愿吹。]
- [6.4 第四步不从,在 store 中根據(jù)權(quán)限處理數(shù)據(jù)]
- [6.5 第五步,渲染頁面后犁跪,即可看到當(dāng)前用戶具體有哪些操作權(quán)限]
- [1 安裝]
d2-admin基本使用
官方演示:https://d2.pub/d2-admin/preview/#/login?redirect=%2Findex
官方文檔:[https://d2.pub/zh/doc/d2-admin/
1 安裝
1.1 全局安裝 d2-admin
npm install -g @d2-admin/d2-admin-cli
1.2 創(chuàng)建項(xiàng)目
d2 create 項(xiàng)目名稱
或者
d2 c 項(xiàng)目名稱
之后選擇 簡化版
1.3 啟動項(xiàng)目
進(jìn)入項(xiàng)目文件夾
npm i
npm run serve
1.4 注意事項(xiàng)
-
d2-container
是 D2Admin 構(gòu)建頁面最重要的組件,在模板頁面中記得要用該標(biāo)簽包裹椿息,該標(biāo)簽針對不樣式的頁面內(nèi)置不同的類型歹袁,詳見官方文檔
2 修改框架 title 和 logo
2.1 修改 title
// .env.development
# 開發(fā)環(huán)境
# 頁面 title 前綴
VUE_APP_TITLE=ZAdmin
修改完成后重啟項(xiàng)目即生效
2.2 修改 logo
首頁LOGL:
替換 .publicimagethemed2logoall.png
網(wǎng)頁 ico 小圖標(biāo):
替換 .publicicon.ico
3 圖表庫
3.1 常用的圖表庫
- 百度圖表庫:echarts (此框架不方便)
- 餓了么圖表庫:v-charts (用這個)
v-charts 官方文檔:https://d2.pub/zh/doc/d2-admin/component/charts.html
3.2 安裝v-charts
npm i v-charts echarts -S
3.3 導(dǎo)入項(xiàng)目
// main.js
import Vue from 'vue'
import VCharts from 'v-charts'
import App from './App.vue'
Vue.use(VCharts)
new Vue({
el: '#app',
render: h => h(App)
})
3.4 簡單舉例
以折線圖為例,其他類型詳見官方文檔寝优。
為了美觀条舔,將其包含在elementUI的Card組件中。
<template>
<el-card>
<ve-line :data="chartData"></ve-line>
</el-card>
</template>
<script>
export default {
data: function () {
return {
chartData: {
columns: ['日期', '訪問用戶', '下單用戶', '下單率'],
rows: [
{ '日期': '1/1', '訪問用戶': 1393, '下單用戶': 1093, '下單率': 0.32 },
{ '日期': '1/2', '訪問用戶': 3530, '下單用戶': 3230, '下單率': 0.26 },
{ '日期': '1/3', '訪問用戶': 2923, '下單用戶': 2623, '下單率': 0.76 },
{ '日期': '1/4', '訪問用戶': 1723, '下單用戶': 1423, '下單率': 0.49 },
{ '日期': '1/5', '訪問用戶': 3792, '下單用戶': 3492, '下單率': 0.323 },
{ '日期': '1/6', '訪問用戶': 4593, '下單用戶': 4293, '下單率': 0.78 }
]
}
}
}
}
</script>
3.5 注意事項(xiàng)
- 將多個圖表分別放置在tab標(biāo)簽頁時倡勇,切換標(biāo)簽頁后下個圖表可能會等待很久才會出現(xiàn)逞刷,是因?yàn)槭盏?elementUI 中Tabs標(biāo)簽頁的
lazy
屬性的影響(初始化時有個渲染的過程),如果沒有開啟延遲渲染妻熊,會只渲染第一個標(biāo)簽頁內(nèi)容夸浅,因此需要將lazy
設(shè)置為true
從第二 tbgs 標(biāo)簽頁起,將lazy
屬性設(shè)置為 true
<el-tabs v-model="activeName" @tab-click="handleClick">
<el-tab-pane label="用戶管理" name="first">
<ve-line :data="chartData"></ve-line>
</el-tab-pane>
<el-tab-pane :lazy="true" label="配置管理" name="second">
<ve-histogram :data="chartDataHis"></ve-histogram>
</el-tab-pane>
<el-tab-pane :lazy="true" label="角色管理" name="third">角色管理</el-tab-pane>
<el-tab-pane :lazy="true" label="定時任務(wù)補(bǔ)償" name="fourth">定時任務(wù)補(bǔ)償</el-tab-pane>
</el-tabs>
4 CURD插件(表格)
D2 CURD是一個基于Vue.js 和 Element UI的表格組件扔役,封裝了常用的表格操作帆喇,使用該組件可以快速在頁面上處理表格數(shù)據(jù)。
詳見官方文檔
4.1 安裝
npm i element-ui @d2-projects/d2-crud -S
4.2 導(dǎo)入項(xiàng)目
import Vue from 'vue'
import ElementUI from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'
import D2Crud from '@d2-projects/d2-crud'
Vue.use(ElementUI)
Vue.use(D2Crud)
new Vue({
el: '#app',
render: h => h(App)
})
4.3 圖表使用
此處為帶有新增和分頁功能的表格亿胸,但CURD2.x中分頁功能的數(shù)據(jù)需要從后臺獲取坯钦,因此這里只簡單演示了表格的樣式。
columns
: 表格的列屬性
data
: 表格的數(shù)據(jù)
add-title
: 彈出新增模態(tài)框的標(biāo)題
pagination
: 開啟分頁功能
loading
: 加載數(shù)據(jù)時會有加載中的樣式
slot="header"
: 表格的頭部信息侈玄,自定義樣式(如:標(biāo)題婉刀,按鈕,輸入框等)可以顯示在表格的頂部(2.x新增序仙,更好的替代了1.x中的 title
屬性)
BusinessTable1List
: 后臺數(shù)據(jù)API突颊,獲取后臺數(shù)據(jù),這里只是頁面展示潘悼,采用固定的數(shù)據(jù)律秃,未使用API接口。
<template>
<d2-container>
<el-card>
<d2-crud
ref="d2Crud"
:columns="columns"
:data="data"
add-title="添加數(shù)據(jù)"
:add-template="addTemplate"
:form-options="formOptions"
:pagination="pagination"
:loading="loading"
@pagination-current-change="paginationCurrentChange"
@dialog-open="handleDialogOpen"
@row-add="handleRowAdd"
@dialog-cancel="handleDialogCancel">
<el-button slot="header" style="margin-bottom: 5px" @click="addRow"><i class="fa fa-plus" aria-hidden="true"></i> 新增</el-button>
<el-button slot="header" style="margin-bottom: 5px" @click="addRowWithNewTemplate">使用自定義模板新增</el-button>
<el-input slot="header" style="margin-bottom: 5px" placeholder="商品名稱" suffix-icon="el-icon-search"> </el-input>
<el-input slot="header" style="margin-bottom: 5px" placeholder="最低價格" suffix-icon="el-icon-caret-bottom"> </el-input>
<el-input slot="header" style="margin-bottom: 5px" placeholder="最高價格" suffix-icon="el-icon-caret-top"> </el-input>
<el-button slot="header" style="margin-bottom: 5px"><i class="el-icon-search"></i> 搜索</el-button>
</d2-crud>
</el-card>
</d2-container>
</template>
<script>
// import { BusinessTable1List } from '@api/demo.business.table.1'
export default {
data () {
return {
columns: [
{
title: '日期',
key: 'date'
},
{
title: '姓名',
key: 'name'
},
{
title: '地址',
key: 'address'
}
],
data: [
{
date: '2016-05-02',
name: '王小虎',
address: '上海市普陀區(qū)金沙江路 1518 弄'
},
{
date: '2016-05-04',
name: '王小虎',
address: '上海市普陀區(qū)金沙江路 1517 弄'
},
{
date: '2016-05-01',
name: '王小虎',
address: '上海市普陀區(qū)金沙江路 1519 弄'
},
{
date: '2016-05-03',
name: '王小虎',
address: '上海市普陀區(qū)金沙江路 1516 弄'
}
],
addTemplate: {
date: {
title: '日期',
value: '2016-05-05'
},
name: {
title: '姓名',
value: '王小虎'
},
address: {
title: '地址',
value: '上海市普陀區(qū)金沙江路 1520 弄'
}
},
formOptions: {
labelWidth: '80px',
labelPosition: 'left',
saveLoading: false
},
loading: false,
pagination: {
currentPage: 1,
pageSize: 5,
total: 100
}
}
},
mounted () {
this.fetchData()
},
methods: {
handleDialogOpen ({ mode }) {
this.$message({
message: '打開模態(tài)框治唤,模式為:' + mode,
type: 'success'
})
},
// 普通的新增
addRow () {
this.$refs.d2Crud.showDialog({
mode: 'add'
})
},
// 傳入自定義模板的新增
addRowWithNewTemplate () {
this.$refs.d2Crud.showDialog({
mode: 'add',
template: {
name: {
title: '姓名',
value: ''
},
value1: {
title: '新屬性1',
value: ''
},
value2: {
title: '新屬性2',
value: ''
}
}
})
},
handleRowAdd (row, done) {
this.formOptions.saveLoading = true
setTimeout(() => {
console.log(row)
this.$message({
message: '保存成功',
type: 'success'
});
// done可以傳入一個對象來修改提交的某個字段
done({
address: '我是通過done事件傳入的數(shù)據(jù)棒动!'
})
this.formOptions.saveLoading = false
}, 300)
},
handleDialogCancel (done) {
this.$message({
message: '取消保存',
type: 'warning'
});
done()
},
paginationCurrentChange (currentPage) {
this.pagination.currentPage = currentPage
this.fetchData()
},
fetchData () {
this.loading = true
BusinessTable1List({
...this.pagination
}).then(res => {
this.data = res.list
this.pagination.total = res.page.total
this.loading = false
}).catch(err => {
console.log('err', err)
this.loading = false
})
}
}
}
</script>
<style scoped>
.el-input {
width: 200px;
margin-right: 10px;
}
</style>
4.4 注意事項(xiàng)
- 課程中使用CURD組件為1.x,最新版本為2.x宾添,變化較大船惨,尤其表現(xiàn)在分頁和添加功能上,如在1.x中分頁是完全基于前端辞槐,而2.x版本的CURD是基于后端的掷漱,其需要后端提供數(shù)據(jù)接口,對返回的數(shù)據(jù)進(jìn)行分頁展現(xiàn)榄檬。詳見官方文檔卜范。關(guān)于如何從定義后臺API并使用mockjs模擬生成后臺數(shù)據(jù),請看下一章
5 定義數(shù)據(jù)API
- mock (模擬后端鹿榜,設(shè)計API)
- api (封裝axios調(diào)用api海雪,對接后端和vuex)
- view (使用vuex處理數(shù)據(jù))
兩個概念:
- 模塊化
- 命名空間
5.1 第一步锦爵,使用mockjs創(chuàng)建數(shù)據(jù)
注意:創(chuàng)建調(diào)用API的URL時,需帶上前綴
/api/xxx
// /mock/api/product.js
const Mock = require('mockjs')
const Qs = require('qs')
const Random = Mock.Random
const titleList = ['男士上衣', 'T恤', '襯衫', '牛仔褲', '皮衣', '短裙', '女士襯衫', '長裙', '羽絨服', '秋褲', '軍大衣'];
const getProductList = function (params) {
// 獲取全部商品列表或者分頁商品列表
let products = []
for (let i = 0; i < 100; i++) {
let product = {
id: i + 1,
title: titleList[Math.floor(Math.random()*titleList.length)],
price: Random.float(10, 100).toFixed(2),
stock: Random.integer(10, 100),
saleCount: Random.integer(0, 90),
isSale: Random.integer(0, 1),
createTime: Random.datetime(),
imgUrl: Random.dataImage('60x60', 'ZAdmin-' + (i + 1)),
showEditButton: true,
showRemoveButton: true
}
products.push(product)
}
let total = products.length;
if (params.body && products.length) {
let pageInfo = JSON.parse(Qs.parse(params).body);
let start = (pageInfo.currentPage - 1) * pageInfo.pageSize;
let end = pageInfo.currentPage * pageInfo.pageSize;
products = products.slice(start, end);
console.log(`start: ${start} end: ${end} products: ${products} total: ${total}`);
}
return { products, total }
}
// 通過post請求奥裸,使用參數(shù)的token判斷是否登錄险掀,并通過參數(shù)判斷獲取全部評論列表或者獲取分頁評論列表
const getProductComments = function (params) {
let data = JSON.parse(Qs.parse(params).body);
console.log('api-comments-params: ', data);
if (!data.token) {
return {
status: 401,
msg: 'token錯誤,需要登錄',
data: {}
}
}
let comments = []
for (let i = 0; i < 120; i++) {
let comment = {
id: i + 1,
name: Random.cname(),
itemScore: Random.integer(1, 5),
serviceScore: Random.integer(1, 5),
content: Random.ctitle(10, 50),
createTime: Random.datetime(),
showEditButton: true,
showRemoveButton: true
}
comments.push(comment)
}
let total = comments.length;
if (data.page) {
let pageInfo = data.page;
let start = (pageInfo.currentPage - 1) * pageInfo.pageSize;
let end = pageInfo.currentPage * pageInfo.pageSize;
comments = comments.slice(start, end);
console.log(`currentPage: ${pageInfo.currentPage} start: ${start} end: ${end} comments: ${comments} total: ${total}`);
}
return {
status: 0,
msg: '成功',
data: comments,
total: total
}
}
// 創(chuàng)建API的URL湾宙,讓vue通過URL獲取數(shù)據(jù)
Mock.mock('/api/products', 'get', getProductList); // 獲取所有商品
Mock.mock('/api/products', 'post', getProductList); // 獲取分頁商品數(shù)據(jù)
Mock.mock('/api/productComments', 'post', getProductComments)
5.2 第二步樟氢,創(chuàng)建API接口,共VUE項(xiàng)目調(diào)用
注意:
引用 plugin/axios 中的request
方法發(fā)起請求
發(fā)起請求的URL侠鳄,不需要帶/api/
前綴
// /src/api/pruduct.js
import request from '@/plugin/axios'
export function getProducts (data) {
return request({
url: '/products',
method: 'get',
data
})
}
export function getPageProducts (data) {
return request({
url: '/products',
method: 'post',
data
})
}
export function getProductComments (data) {
return request({
url: '/productComments',
method: 'post',
data
})
}
5.3 第三步埠啃,在 VUEX 中創(chuàng)建API,調(diào)用第二步中創(chuàng)建API接口伟恶,對數(shù)據(jù)進(jìn)行操作
注意:
- 在 /src/store/modules/ 下創(chuàng)建操作對象的模塊碴开,如 product,其包括了 modules 文件夾和 index.js 文件博秫,index.js 文件復(fù)制 srcstoremodulesd2adminindex.js即可
- 在modules文件夾下創(chuàng)建 product.js 文件用于調(diào)用第二步中創(chuàng)建的 API 接口潦牛,并對收到的數(shù)據(jù)進(jìn)行操作,文件結(jié)構(gòu)與 VUE 項(xiàng)目中store.js相同挡育。
- 在 product.js 文件中聲明
namespaced: true
巴碗,加了命名空間操作,將該文件直接加在了product文件夾下良价,被調(diào)用時,中間省去了一層 modules蒿叠,為 product/product - 調(diào)用 API 時抵蚊,需要先導(dǎo)入提前定義好的 API
- 修改 srcstoreindex.js 文件俱尼,將該模塊導(dǎo)出货矮,使 view 能夠調(diào)用
// srcstoremodulesproductmodulesproduct.js
import { getProducts, getPageProducts, getProductComments } from '@/api/product'
export default {
namespaced: true,
actions: {
getProducts ({ commit }, payload) {
commit('getProducts', payload)
},
getPageProducts ({ commit }, payload) {
commit('getPageProducts', payload)
},
getComments ({ commit }, payload) {
commit('getComments', payload)
}
},
mutations: {
getProducts (state, payload) {
state.loading = true;
getProducts(payload).then(res => {
console.log('getProducts: ', res);
state.loading = false;
state.products = res.products;
}).catch( err => {
console.log('err', err)
state.loading = false
})
},
getComments (state, payload) {
// 獲取全部評價信息或者分頁評價信息
state.loading = true;
console.log(payload);
getProductComments(payload).then(res => {
console.log('getComments: ', res);
if (res.status == 0) {
state.loading = false;
state.total = res.total;
if (payload.page) {
state.pageComments = res.data;
} else {
state.comments = res.data;
}
}
}).catch( err => {
console.log('err', err)
state.loading = false
})
},
getPageProducts (state, payload) {
console.log('page_params: ', payload);
state.loading = true;
getPageProducts(payload).then(res => {
console.log('res: ', res);
state.loading = false;
state.total = res.total;
state.pageProducts = res.products;
}).catch( err => {
console.log('err', err)
state.loading = false
})
}
},
state: {
products: [],
pageProducts: [],
comments: [],
pageComments: [],
loading: false,
total: 0
},
getters: {
products: (state) => { return state.products },
pageProducts: (state) => { return state.pageProducts },
comments: (state) => { return state.comments },
pageComments: (state) => { return state.pageComments },
loading: (state) => { return state.loading },
total: (state) => { return state.total }
}
}
// srcstoreindex.js
import Vue from 'vue'
import Vuex from 'vuex'
import d2admin from './modules/d2admin'
import product from './modules/product'
Vue.use(Vuex)
export default new Vuex.Store({
modules: {
d2admin,
product
}
})
5.4 第四步抓督,在 view 中調(diào)用 VUEX 中的模塊中的方法實(shí)現(xiàn)具體功能宿稀。
注意:
- 調(diào)用vuex中的方法以及從vuex中取數(shù)據(jù)時越庇,都需加上命名空間
product/product
- 使用mockjs 生成的圖片為base64 格式,需要新建組件轉(zhuǎn)換圖片進(jìn)行顯示痊硕,view使用時需導(dǎo)入該組件
- D2-admin框架自帶很多工具集挡鞍,其中 src/libs/util.js 中
cookies.get('token')
可以獲取當(dāng)前用戶的token - CURD2.x 分頁功能放棄純前端分頁方法锈麸,采用后端傳入分頁好的數(shù)據(jù)(切片處理)進(jìn)行分頁
自定義組件氓奈,用于顯示圖片:
// srcviewsproductallMyImage.vue
<template>
<div>
<img :src="value" alt="商品圖片">
</div>
</template>
<script>
export default {
props: {
value: '' // 必須叫value
}
}
</script>
基于 CURD2.x 的本地增刪改查 以及 分頁功能實(shí)現(xiàn)
<template>
<d2-container>
<template slot="header">
<div class="flex-header">
<div class="header-title">
商品列表
</div>
<div>
<el-input v-model="searchWords" placeholder="商品名稱" suffix-icon="el-icon-search"></el-input>
<el-input v-model="searchMinPrice" placeholder="最低價格" suffix-icon="el-icon-caret-bottom"> </el-input>
<el-input v-model="searchMaxPrice" placeholder="最高價格" suffix-icon="el-icon-caret-top"> </el-input>
<el-button @click="onSearch"><i class="el-icon-search"></i> 搜索</el-button>
<el-button type="primary" @click="addRow"><i class="fa fa-plus" aria-hidden="true"></i> 添加商品</el-button>
</div>
</div>
</template>
<el-card>
<d2-crud
ref="d2Crud"
:columns="columns"
:data="filterProducts.length ? filterProducts : products"
add-title="添加商品"
:add-template="addTemplate"
:rowHandle="rowHandle"
edit-title="編輯商品信息"
:edit-template="editTemplate"
:form-options="formOptions"
:loading="loading"
:pagination="pagination"
@pagination-current-change="paginationCurrentChange"
@row-add="handleRowAdd"
@row-edit="handleRowEdit"
@row-remove="handleRowRemove"
@dialog-cancel="handleDialogCancel">
</d2-crud>
</el-card>
<template slot="footer">ZAdmin Created By <a href="">@ZBW</a> for D2-admin</template>
</d2-container>
</template>
<script>
// 導(dǎo)入自定義用于顯示圖片的組件
import MyImage from "./MyImage"
export default {
data () {
return {
searchWords: '',
searchMinPrice: '',
searchMaxPrice: '',
filterProducts: [],
columns: [
{
title: 'ID',
key: 'id',
width: '40'
},
{
title: '名稱',
key: 'title'
},
{
title: '價格',
key: 'price',
width: '80'
},
{
title: '庫存',
key: 'stock',
width: '80'
},
{
title: '銷量',
key: 'saleCount',
width: '80'
},
{
title: '是否上架',
key: 'isSale',
component: {
name: 'el-select',
options: [
{
value: 0,
label: '否'
},
{
value: 1,
label: '是'
}
]
}
},
{
title: '圖片',
key: 'imgUrl',
width: '120',
component: {
name: MyImage
}
},
{
title: '創(chuàng)建時間',
key: 'createTime'
}
],
addTemplate: {
createTime: {
title: '創(chuàng)建日期',
value: '2019-06-01',
component: {
name: 'el-date-picker',
span: 12
}
},
isSale: {
title: '是否上架',
value: 0,
component: {
name: 'el-select',
options: [
{
value: 0,
label: '否'
},
{
value: 1,
label: '是'
}
],
span: 12
}
},
title: {
title: '名稱',
value: '',
span: 24
},
price: {
title: '價格',
value: '',
span: 24
}
},
rowHandle: {
columnHeader: '操作',
edit: {
icon: 'el-icon-edit',
text: '編輯',
size: 'mini',
show (index, row) {
if (row.showEditButton) {
return true
}
return false
},
disabled (index, row) {
if (row.forbidEdit) {
return true
}
return false
}
},
remove: {
icon: 'el-icon-delete',
size: 'mini',
text: '刪除',
fixed: 'right',
confirm: true,
show (index, row) {
if (row.showRemoveButton) {
return true
}
return false
}
}
},
editTemplate: {
createTime: {
title: '創(chuàng)建日期',
component: {
name: 'el-date-picker',
span: 12
}
},
isSale: {
title: '是否上架',
component: {
name: 'el-select',
options: [
{
value: 0,
label: '否'
},
{
value: 1,
label: '是'
}
],
span: 12
}
},
title: {
title: '名稱',
span: 24
},
price: {
title: '價格',
span: 24
},
forbidEdit: {
title: '禁用按鈕',
value: false,
component: {
show: false
}
},
showEditButton: {
title: '顯示按鈕',
value: true,
component: {
show: false
}
}
},
formOptions: {
labelWidth: '80px',
labelPosition: 'left',
saveLoading: false
},
}
},
created() {
// 調(diào)用vuex中方法時,需要加上命名空間 product/product
this.fetchData();
this.$store.commit('product/product/getProducts'); // 請求全部商品列表
},
computed: {
all_products() {
// 全部商品列表,用于搜索
return this.$store.getters['product/product/products'];
},
products() {
// 當(dāng)前分頁的商品列表
// 取 vuex 中數(shù)據(jù)時套硼,需要加上命名空間 product/product
return this.$store.getters['product/product/pageProducts']
},
loading() {
return this.$store.getters['product/product/loading']
},
pagination() {
return {
currentPage: 1,
pageSize: 5,
background: true,
total: this.$store.getters['product/product/total']
}
}
},
methods: {
onSearch() {
this.filterProducts = this.all_products.filter(p => {
if (this.searchWords){
return p.price >= parseFloat(this.searchMinPrice) && p.price <= parseFloat(this.searchMaxPrice) && p.title.includes(this.searchWords);
} else {
return p.price >= parseFloat(this.searchMinPrice) && p.price <= parseFloat(this.searchMaxPrice);
}
})
console.log('filterProducts: ', this.filterProducts);
},
addRow () {
// 點(diǎn)擊新增后耸携,以“添加”模式打開模態(tài)框
this.$refs.d2Crud.showDialog({
mode: 'add'
})
},
handleRowAdd (row, done) {
// 點(diǎn)擊確認(rèn)添加后觸發(fā)的事件,可以將數(shù)據(jù)傳遞到后臺,保存至數(shù)據(jù)庫中
this.formOptions.saveLoading = true
setTimeout(() => {
// row 是表單提交的內(nèi)容
console.log(row)
this.$message({
message: '保存成功',
type: 'success'
});
// done可以傳入一個對象來修改提交的某個字段
done({
price: '你雖然提交了 但是我能在這修改你顯示在頁面的內(nèi)容!'
})
this.formOptions.saveLoading = false
}, 300)
},
handleRowEdit ({ index, row }, done) {
// 點(diǎn)擊確認(rèn)修改后觸發(fā)的事件白粉,可以將數(shù)據(jù)傳遞到后臺传泊,保存至數(shù)據(jù)庫中
// index 是當(dāng)前編輯行的索引, row 是當(dāng)前編輯行的數(shù)據(jù)鸭巴, done 用于控制編輯成功眷细,可以在 done() 之前加入自己的邏輯代碼
this.formOptions.saveLoading = true
setTimeout(() => {
console.log(index)
console.log(row)
this.$message({
message: '編輯成功',
type: 'success'
})
// done可以傳入一個對象來修改提交的某個字段
// done()可以傳入包含表單字段的對象來覆蓋提交的數(shù)據(jù),done(false) 可以取消編輯
done({
price: '你雖然在后臺修改了價格鹃祖,但是我能在這控制你在前臺顯示的內(nèi)容'
})
this.formOptions.saveLoading = false
}, 300)
},
handleRowRemove ({ index, row }, done) {
// 與編輯類似
setTimeout(() => {
console.log(index)
console.log(row)
this.$message({
message: '刪除成功',
type: 'success'
})
done()
}, 300)
},
handleDialogCancel (done) {
// 關(guān)閉模態(tài)框執(zhí)行的事件溪椎,并可以自定義執(zhí)行done函數(shù)
this.$message({
message: '取消保存',
type: 'warning'
});
done()
},
paginationCurrentChange (currentPage) {
// 分頁頁碼發(fā)生改變觸發(fā)的事件
this.pagination.currentPage = currentPage
this.fetchData()
},
fetchData () {
// 點(diǎn)擊分頁按鈕后,動態(tài)請求該頁所需的數(shù)據(jù)
this.$store.commit('product/product/getPageProducts', {pageSize: this.pagination.pageSize, currentPage: this.pagination.currentPage})
}
}
}
</script>
<style scoped>
.flex-header {
display: flex;
justify-content: space-between;
align-items:center
}
.header-title {
min-width: 4rem;
}
.flex-header .el-input {
width: 200px;
margin-right: 10px;
}
</style>
經(jīng)測試恬口,各功能全部正常校读。
6 權(quán)限控制
用戶權(quán)限:
- 使用token進(jìn)行驗(yàn)證
- 使用sessionStorage保存用戶信息(包含權(quán)限列表)
- 登錄時保存
- 退出時刪除
- 結(jié)合路由meta信息進(jìn)行判斷
- meta:注意使用...meta,原來有bug祖能,需要解析賦值(最新版中地熄,該BUG已修復(fù))
控制權(quán)限的幾種方式:
- 控制查看表格,以及表格的操作按鈕
- 無權(quán)限訪問某頁面或者頁面部分內(nèi)容
6.1 第一步芯杀,在 mock 中添加用戶信息,以及不同的用戶對應(yīng)的不同的權(quán)限,并保存至本地的sessionStorage中揭厚。
- 在 srcmockapisys.login.js 中定義用戶信息却特,以及不同用戶角色(role)對應(yīng)不同頁面(路由)的權(quán)限信息
- 定義獲取用戶信息接口,其中包括該用戶對用角色的權(quán)限信息
// srcmockapisys.login.js
import Mock from 'mockjs'
const userDB = [
{ username: 'admin', password: 'admin', uuid: 'admin-uuid', name: '管理員', role: 'admin' },
{ username: 'editor', password: 'editor', uuid: 'editor-uuid', name: '編輯', role: 'editor' },
{ username: 'user1', password: 'user1', uuid: 'user1-uuid', name: '用戶1', role: 'user' }
]
// 為不同的用戶角色(role)劃分不同的權(quán)限
const permissions = {
admin: [
{
path: '/index',
operations: ['modify', 'delete', 'add']
},
{
path: '/product',
operations: ['modify', 'delete', 'add']
},
{
path: '/order',
operations: ['modify', 'delete', 'add']
},
{
path: '/permission',
operations: ['modify', 'delete', 'add']
},
{
path: '/users',
operations: ['modify', 'delete', 'add']
},
{
path: '/menu',
operations: ['modify', 'delete', 'add']
},
{
path: '/stuff',
operations: ['modify', 'delete', 'add']
},
{
path: '/data-charts',
operations: ['modify', 'delete', 'add']
},
{
path: '/log'
}
],
editor: [{
path: '/index',
operations: ['modify', 'add']
},
{
path: '/product',
operations: ['modify', 'add']
},
{
path: '/permission',
operations: ['modify']
},
{
path: '/order',
operations: ['modify', 'add']
}
],
user: [{
path: '/index',
operations: ['add']
},
{
path: '/product',
operations: ['add']
},
{
path: '/order',
operations: ['add']
}
]
}
Mock.mock('/api/userInfo', 'post', ({ body }) => {
const bodyObj = JSON.parse(body)
if (!bodyObj.token) {
return {
status: 401,
msg: 'token錯誤筛圆,需要登錄',
data: {}
}
}
const user = userDB.find(e => e.uuid === bodyObj.uuid)
if (user) {
return {
status: 0,
msg: '成功',
data: {
username: user.username,
name: user.name,
uuid: user.uuid,
role: user.role,
permissions: permissions[user.role]
}
}
} else {
return {
status: 401,
msg: '用戶名或密碼錯誤',
data: {}
}
}
})
// 判斷是否登錄
export default [
{
path: '/api/login',
method: 'post',
handle ({ body }) {
const user = userDB.find(e => e.username === body.username && e.password === body.password)
if (user) {
return {
code: 0,
msg: '登錄成功',
data: {
...user,
token: '8dfhassad0asdjwoeiruty'
}
}
} else {
return {
code: 401,
msg: '用戶名或密碼錯誤',
data: {}
}
}
}
}
]
6.2 第二步裂明,在 src/API 中定義調(diào)用 mock 中 API 的接口,取到用戶信息
// srcapisys.login.js
import request from '@/plugin/axios'
export function AccountLogin (data) {
return request({
url: '/login',
method: 'post',
data
})
}
export function getUserInfo (data) {
return request({
url: '/userinfo',
method: 'post',
data
})
}
6.3 第三步太援,在 router/index.js 中添加鉤子函數(shù)闽晦。
- 獲取跳轉(zhuǎn)路由的 meta 信息,判斷該路由是否需要驗(yàn)證提岔。
- 若需驗(yàn)證仙蛉,則從sessionStroage中獲取用戶的權(quán)限列表,如果該用戶的權(quán)限列表中沒有此路由信息碱蒙,則跳轉(zhuǎn)至401頁面提示無權(quán)訪問此頁面荠瘪,如果權(quán)限列表中有此路由信息,則將該用戶對此路由的權(quán)限信息(如:增刪改查)添加至路由的 meta 信息中赛惩,并跳轉(zhuǎn)頁面哀墓。
- 若不需要驗(yàn)證,直接跳轉(zhuǎn)喷兼。
// srcrouterindex.js
import Vue from 'vue'
import VueRouter from 'vue-router'
// 進(jìn)度條
import NProgress from 'nprogress'
import 'nprogress/nprogress.css'
import store from '@/store/index'
import util from '@/libs/util.js'
// 路由數(shù)據(jù)
import routes from './routes'
Vue.use(VueRouter)
// 導(dǎo)出路由 在 main.js 里使用
const router = new VueRouter({
routes
})
/**
* 路由攔截
* 權(quán)限驗(yàn)證
*/
router.beforeEach((to, from, next) => {
// 進(jìn)度條
NProgress.start()
// 關(guān)閉搜索面板
store.commit('d2admin/search/set', false)
// 獲取路由的meta信息篮绰,判斷當(dāng)前頁面是否需要驗(yàn)證(登錄驗(yàn)證,權(quán)限驗(yàn)證等等)
// 驗(yàn)證當(dāng)前路由所有的匹配中是否需要有登錄驗(yàn)證的
if (to.matched.some(r => r.meta.auth)) {
// 這里暫時將cookie里是否存有token作為驗(yàn)證是否登錄的條件
// 請根據(jù)自身業(yè)務(wù)需要修改
const token = util.cookies.get('token') // 獲取cookie中的token
if (token && token !== 'undefined') {
// 用戶已登錄季惯,從sessionStorage中獲取用戶權(quán)限信息
let userInfo = JSON.parse(sessionStorage.getItem('userInfo'));
console.log(token, userInfo);
if (userInfo) {
let permissions = userInfo.permissions;
console.log('router permissions: ', permissions);
let allow = false;
permissions.forEach(p => {
let item = router.match(p.path) // 找到權(quán)限列表中匹配改條路由對應(yīng)的權(quán)限信息
if (item) {
// 匹配到路由后吠各,將權(quán)限信息添加在路由的meta中
item.meta.operations = p.operations
}
// 完全匹配或者前綴匹配
console.log('path: ', to.path, p.path);
if (p.path === to.path || to.path.startsWith(p.path)) {
allow = true;
}
})
// 根據(jù)allow判斷是否可以跳轉(zhuǎn)到下一個頁面
if (allow) {
next()
} else {
next({ name: '401' })
}
} else {
next()
}
} else {
// 沒有登錄的時候跳轉(zhuǎn)到登錄界面
// 攜帶上登陸成功之后需要跳轉(zhuǎn)的頁面完整路徑
next({
name: 'login',
query: {
redirect: to.fullPath
}
})
// https://github.com/d2-projects/d2-admin/issues/138
NProgress.done()
}
} else {
// 不需要身份校驗(yàn) 直接通過
next()
}
})
router.afterEach(to => {
// 進(jìn)度條
NProgress.done()
// 多頁控制 打開新的頁面
store.dispatch('d2admin/page/open', to)
// 更改標(biāo)題
util.title(to.meta.title)
})
export default router
6.4 第四步,在 store 中根據(jù)權(quán)限處理數(shù)據(jù)
- (用戶登錄的API)登錄成功后把 userInfo 存至本地的 sessionStorage 中星瘾,注銷登錄后將用戶信息從sessionStorage 中刪除
- 通過取到 router 對象(
this.$route.meta.[operations]
)的 meta 中的權(quán)限信息(如:增刪改查)走孽,得出具體某個頁面的操作權(quán)限信息,針對不同用戶權(quán)限做出不同的操作琳状。
注意:
- $router: 表示全局的 router 對象
- $route: 表示當(dāng)前頁面的路由對象
// srcstoremodulesd2adminmodulesaccount.js
import { Message, MessageBox } from 'element-ui'
import util from '@/libs/util.js'
import router from '@/router'
import { AccountLogin, getUserInfo } from '@api/sys.login'
export default {
namespaced: true,
actions: {
/**
* @description 登錄
* @param {Object} param context
* @param {Object} param username {String} 用戶賬號
* @param {Object} param password {String} 密碼
* @param {Object} param route {Object} 登錄成功后定向的路由對象 任何 vue-router 支持的格式
*/
login ({ dispatch }, {
username = '',
password = ''
} = {}) {
return new Promise((resolve, reject) => {
// 開始請求登錄接口
AccountLogin({
username,
password
})
.then(async res => {
// 設(shè)置 cookie 一定要存 uuid 和 token 兩個 cookie
// 整個系統(tǒng)依賴這兩個數(shù)據(jù)進(jìn)行校驗(yàn)和存儲
// uuid 是用戶身份唯一標(biāo)識 用戶注冊的時候確定 并且不可改變 不可重復(fù)
// token 代表用戶當(dāng)前登錄狀態(tài) 建議在網(wǎng)絡(luò)請求中攜帶 token
// 如有必要 token 需要定時更新磕瓷,默認(rèn)保存一天
util.cookies.set('uuid', res.uuid)
util.cookies.set('token', res.token)
// 登錄成功后,加載用戶信息和權(quán)限信息
getUserInfo({ uuid: res.uuid, token: res.token }).then(res => {
console.log('get user info: ', res);
if (res.status == 401) {
return;
} else {
let userInfo = res.data;
sessionStorage.setItem('userInfo', JSON.stringify(userInfo));
}
})
// 設(shè)置 vuex 用戶信息
await dispatch('d2admin/user/set', {
name: res.name
}, { root: true })
// 用戶登錄后從持久化數(shù)據(jù)加載一系列的設(shè)置
await dispatch('load')
// 結(jié)束
resolve()
})
.catch(err => {
console.log('err: ', err)
reject(err)
})
})
},
/**
* @description 注銷用戶并返回登錄頁面
* @param {Object} param context
* @param {Object} param confirm {Boolean} 是否需要確認(rèn)
*/
logout ({ commit, dispatch }, { confirm = false } = {}) {
/**
* @description 注銷
*/
async function logout () {
// 刪除cookie
util.cookies.remove('token')
util.cookies.remove('uuid')
// 退出后將sessionStorage中的用戶信息刪除
sessionStorage.removeItem('userInfo')
// 清空 vuex 用戶信息
await dispatch('d2admin/user/set', {}, { root: true })
// 跳轉(zhuǎn)路由
router.push({
name: 'login'
})
}
// 判斷是否需要確認(rèn)
if (confirm) {
commit('d2admin/gray/set', true, { root: true })
MessageBox.confirm('注銷當(dāng)前賬戶嗎? 打開的標(biāo)簽頁和用戶設(shè)置將會被保存念逞。', '確認(rèn)操作', {
confirmButtonText: '確定注銷',
cancelButtonText: '放棄',
type: 'warning'
})
.then(() => {
commit('d2admin/gray/set', false, { root: true })
logout()
})
.catch(() => {
commit('d2admin/gray/set', false, { root: true })
Message({
message: '放棄注銷用戶'
})
})
} else {
logout()
}
},
/**
* @description 用戶登錄后從持久化數(shù)據(jù)加載一系列的設(shè)置
* @param {Object} state vuex state
*/
load ({ dispatch }) {
return new Promise(async resolve => {
// DB -> store 加載用戶名
await dispatch('d2admin/user/load', null, { root: true })
// DB -> store 加載主題
await dispatch('d2admin/theme/load', null, { root: true })
// DB -> store 加載頁面過渡效果設(shè)置
await dispatch('d2admin/transition/load', null, { root: true })
// DB -> store 持久化數(shù)據(jù)加載上次退出時的多頁列表
await dispatch('d2admin/page/openedLoad', null, { root: true })
// DB -> store 持久化數(shù)據(jù)加載側(cè)邊欄折疊狀態(tài)
await dispatch('d2admin/menu/asideCollapseLoad', null, { root: true })
// DB -> store 持久化數(shù)據(jù)加載全局尺寸
await dispatch('d2admin/size/load', null, { root: true })
// end
resolve()
})
}
}
}
6.5 第五步困食,渲染頁面后,即可看到當(dāng)前用戶具體有哪些操作權(quán)限
- 針對表格操作翎承,可以根據(jù) meta 信息中存儲的操作權(quán)限來判斷當(dāng)前用戶所具有的權(quán)限硕盹,最后對其權(quán)限之外的按鈕等進(jìn)行隱藏或者disable。
// srcviewspermissionindex.vue
<template>
<d2-container>
<template slot="header">頁面權(quán)限驗(yàn)證</template>
<el-card>
<d2-crud
ref="d2Crud"
:columns="columns"
:data="data"
add-title="我的新增"
edit-title="我的修改"
:add-template="addTemplate"
:edit-template="editTemplate"
:rowHandle="rowHandle"
:form-options="formOptions"
@row-add="handleRowAdd"
@row-edit="handleRowEdit"
@row-remove="handleRowRemove"
@dialog-cancel="handleDialogCancel">
<el-button slot="header" type="primary" style="margin-bottom: 5px;" @click="addRow">新增</el-button>
</d2-crud>
</el-card>
<template slot="footer">ZAdmin Created By <a href="">@ZBW</a> for D2-admin</template>
</d2-container>
</template>
<script>
export default {
data () {
return {
columns: [
{
title: 'ID',
key: 'id',
width: 50
},
{
title: '日期',
key: 'date',
width: 150
},
{
title: '姓名',
key: 'name'
},
{
title: '地址',
key: 'address'
},
{
title: '會員',
key: 'role',
width: 100
}
],
data: [
{
id: 1,
date: '2016-05-02',
name: '王小虎',
address: '上海市普陀區(qū)金沙江路 1518 弄',
role: '普通會員',
showEditButton: this.$route.meta.operations.includes('modify'),
showRemoveButton: this.$route.meta.operations.includes('delete')
},
{
id: 2,
date: '2016-05-04',
name: '王小虎',
address: '上海市普陀區(qū)金沙江路 1517 弄',
role: '普通會員',
showEditButton: this.$route.meta.operations.includes('modify'),
showRemoveButton: this.$route.meta.operations.includes('delete')
},
{
id: 3,
date: '2016-05-01',
name: '王小虎',
address: '上海市普陀區(qū)金沙江路 1519 弄',
role: '普通會員',
showEditButton: this.$route.meta.operations.includes('modify'),
showRemoveButton: this.$route.meta.operations.includes('delete')
},
{
id: 4,
date: '2016-05-03',
name: '王小虎',
address: '上海市普陀區(qū)金沙江路 1516 弄',
role: '普通會員',
showEditButton: this.$route.meta.operations.includes('modify'),
showRemoveButton: this.$route.meta.operations.includes('delete')
},
{
id: 5,
date: '2016-05-02',
name: '王小虎',
address: '上海市普陀區(qū)金沙江路 1518 弄',
role: '普通會員',
showEditButton: this.$route.meta.operations.includes('modify'),
showRemoveButton: this.$route.meta.operations.includes('delete')
}
],
rowHandle: {
columnHeader: '編輯表格',
edit: {
icon: 'el-icon-edit',
text: '編輯',
size: 'small',
show (index, row) {
if (row.showEditButton) {
return true
}
return false
},
disabled (index, row) {
if (row.forbidEdit) {
return true
}
return false
}
},
remove: {
icon: 'el-icon-delete',
size: 'small',
fixed: 'right',
confirm: true,
show (index, row) {
if (row.showRemoveButton) {
return true
}
return false
},
disabled (index, row) {
if (row.forbidRemove) {
return true
}
return false
}
}
},
addTemplate: {
date: {
title: '日期',
value: '2018-11-11',
component: {
name: 'el-date-picker',
span: 12
}
},
role: {
title: '會員',
value: '普通會員',
component: {
name: 'el-select',
options: [
{
value: '普通會員',
label: '普通會員'
},
{
value: '金卡會員',
label: '金卡會員'
}
],
span: 12
}
},
name: {
title: '姓名',
value: ''
},
address: {
title: '地址',
value: ''
}
},
editTemplate: {
date: {
title: '日期',
value: '2018-11-11',
component: {
name: 'el-date-picker',
span: 12
}
},
role: {
title: '會員',
value: '普通會員',
component: {
name: 'el-select',
options: [
{
value: '普通會員',
label: '普通會員'
},
{
value: '金卡會員',
label: '金卡會員'
}
],
span: 12
}
},
name: {
title: '姓名',
value: ''
},
address: {
title: '地址',
value: ''
},
forbidEdit: {
title: '禁用按鈕',
value: false,
component: {
show: false
}
},
showEditButton: {
title: '顯示按鈕',
value: true,
component: {
show: false
}
}
},
formOptions: {
labelWidth: '80px',
labelPosition: 'left',
saveLoading: false
}
}
},
methods: {
addRow () {
this.$refs.d2Crud.showDialog({
mode: 'add'
})
},
handleRowAdd (row, done) {
this.formOptions.saveLoading = true
setTimeout(() => {
console.log(row)
this.$message({
message: '添加成功',
type: 'success'
});
done({
showEditButton: this.$route.meta.operations.includes('modify'),
showRemoveButton: this.$route.meta.operations.includes('delete')
})
this.formOptions.saveLoading = false
}, 300);
},
handleRowEdit ({index, row}, done) {
this.formOptions.saveLoading = true
setTimeout(() => {
console.log(index)
console.log(row)
this.$message({
message: '編輯成功',
type: 'success'
})
done()
this.formOptions.saveLoading = false
}, 300)
},
handleDialogCancel (done) {
this.$message({
message: '操作已取消',
type: 'warning'
});
done()
},
handleRowRemove ({index, row}, done) {
setTimeout(() => {
console.log(index)
console.log(row)
this.$message({
message: '刪除成功',
type: 'success'
})
done()
}, 300)
}
}
}
</script>
<style scoped>
</style>
admin:
editer:
user:
內(nèi)容來源于網(wǎng)絡(luò)叨咖,如有侵權(quán)請聯(lián)系客服刪除