目录javascript
官方演示:https://d2admin.fairyever.com/#/index
官方文档:https://doc.d2admin.fairyever.com/zh/css
npm install -g @d2-admin/d2-admin-cli
d2 create 项目名称
或者html
d2 c 项目名称
以后选择 简化版前端
进入项目文件夹vue
npm i
npm run serve
d2-container
是 D2Admin 构建页面最重要的组件,在模板页面中记得要用该标签包裹,该标签针对不样式的页面内置不一样的类型,详见官方文档// .env.development # 开发环境 # 页面 title 前缀 VUE_APP_TITLE=ZAdmin
修改完成后重启项目即生效java
首页LOGL:
替换 .\public\image\theme\d2\logo\all.pngios
网页 ico 小图标:
替换 .\public\icon.icogit
v-charts 官方文档:https://v-charts.js.org/#/github
npm i v-charts echarts -S
// 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) })
以折线图为例,其余类型详见官方文档。vue-router
为了美观,将其包含在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>
lazy
属性的影响(初始化时有个渲染的过程),若是没有开启延迟渲染,会只渲染第一个标签页内容,所以须要将 lazy
设置为 true
从第二 tbgs 标签页起,将lazy
属性设置为 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="定时任务补偿" name="fourth">定时任务补偿</el-tab-pane> </el-tabs>
D2 CURD是一个基于Vue.js 和 Element UI的表格组件,封装了经常使用的表格操做,使用该组件能够快速在页面上处理表格数据。
详见官方文档
npm i element-ui @d2-projects/d2-crud -S
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) })
此处为带有新增和分页功能的表格,但CURD2.x中分页功能的数据须要从后台获取,所以这里只简单演示了表格的样式。
columns
: 表格的列属性
data
: 表格的数据
add-title
: 弹出新增模态框的标题
pagination
: 开启分页功能
loading
: 加载数据时会有加载中的样式
slot="header"
: 表格的头部信息,自定义样式(如:标题,按钮,输入框等)能够显示在表格的顶部(2.x新增,更好的替代了1.x中的 title
属性)
BusinessTable1List
: 后台数据API,获取后台数据,这里只是页面展现,采用固定的数据,未使用API接口。
<template> <d2-container> <el-card> <d2-crud ref="d2Crud" :columns="columns" :data="data" add-title="添加数据" :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: '上海市普陀区金沙江路 1518 弄' }, { date: '2016-05-04', name: '王小虎', address: '上海市普陀区金沙江路 1517 弄' }, { date: '2016-05-01', name: '王小虎', address: '上海市普陀区金沙江路 1519 弄' }, { date: '2016-05-03', name: '王小虎', address: '上海市普陀区金沙江路 1516 弄' } ], addTemplate: { date: { title: '日期', value: '2016-05-05' }, name: { title: '姓名', value: '王小虎' }, address: { title: '地址', value: '上海市普陀区金沙江路 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: '打开模态框,模式为:' + 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事件传入的数据!' }) 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>
两个概念:
注意:建立调用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请求,使用参数的token判断是否登陆,并经过参数判断获取所有评论列表或者获取分页评论列表 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 } } // 建立API的URL,让vue经过URL获取数据 Mock.mock('/api/products', 'get', getProductList); // 获取全部商品 Mock.mock('/api/products', 'post', getProductList); // 获取分页商品数据 Mock.mock('/api/productComments', 'post', getProductComments)
注意:
引用 plugin/axios 中的request
方法发起请求
发起请求的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 }) }
注意:
namespaced: true
,加了命名空间操做,将该文件直接加在了product文件夹下,被调用时,中间省去了一层 modules,为 product/product// \src\store\modules\product\modules\product.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 } } }
// \src\store\index.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 } })
注意:
product/product
cookies.get('token')
能够获取当前用户的token自定义组件,用于显示图片:
// \src\views\product\all\MyImage.vue <template> <div> <img :src="value" alt="商品图片"> </div> </template> <script> export default { props: { value: '' // 必须叫value } } </script>
基于 CURD2.x 的本地增删改查 以及 分页功能实现
<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> // 导入自定义用于显示图片的组件 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: '建立时间', key: 'createTime' } ], addTemplate: { createTime: { title: '建立日期', 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: '建立日期', 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() { // 调用vuex中方法时,须要加上命名空间 product/product this.fetchData(); this.$store.commit('product/product/getProducts'); // 请求所有商品列表 }, computed: { all_products() { // 所有商品列表,用于搜索 return this.$store.getters['product/product/products']; }, products() { // 当前分页的商品列表 // 取 vuex 中数据时,须要加上命名空间 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 () { // 点击新增后,以“添加”模式打开模态框 this.$refs.d2Crud.showDialog({ mode: 'add' }) }, handleRowAdd (row, done) { // 点击确认添加后触发的事件,能够将数据传递到后台,保存至数据库中 this.formOptions.saveLoading = true setTimeout(() => { // row 是表单提交的内容 console.log(row) this.$message({ message: '保存成功', type: 'success' }); // done能够传入一个对象来修改提交的某个字段 done({ price: '你虽然提交了 可是我能在这修改你显示在页面的内容!' }) this.formOptions.saveLoading = false }, 300) }, handleRowEdit ({ index, row }, done) { // 点击确认修改后触发的事件,能够将数据传递到后台,保存至数据库中 // index 是当前编辑行的索引, row 是当前编辑行的数据, done 用于控制编辑成功,能够在 done() 以前加入本身的逻辑代码 this.formOptions.saveLoading = true setTimeout(() => { console.log(index) console.log(row) this.$message({ message: '编辑成功', type: 'success' }) // done能够传入一个对象来修改提交的某个字段 // done()能够传入包含表单字段的对象来覆盖提交的数据,done(false) 能够取消编辑 done({ price: '你虽然在后台修改了价格,可是我能在这控制你在前台显示的内容' }) 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) { // 关闭模态框执行的事件,并能够自定义执行done函数 this.$message({ message: '取消保存', type: 'warning' }); done() }, paginationCurrentChange (currentPage) { // 分页页码发生改变触发的事件 this.pagination.currentPage = currentPage this.fetchData() }, fetchData () { // 点击分页按钮后,动态请求该页所需的数据 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>
经测试,各功能所有正常。
用户权限:
控制权限的几种方式:
// src\mock\api\sys.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)划分不一样的权限 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: {} } } } } ]
// src\api\sys.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 }) }
// \src\router\index.js import Vue from 'vue' import VueRouter from 'vue-router' // 进度条 import NProgress from 'nprogress' import 'nprogress/nprogress.css' import store from '@/store/index' import util from '@/libs/util.js' // 路由数据 import routes from './routes' Vue.use(VueRouter) // 导出路由 在 main.js 里使用 const router = new VueRouter({ routes }) /** * 路由拦截 * 权限验证 */ router.beforeEach((to, from, next) => { // 进度条 NProgress.start() // 关闭搜索面板 store.commit('d2admin/search/set', false) // 获取路由的meta信息,判断当前页面是否须要验证(登陆验证,权限验证等等) // 验证当前路由全部的匹配中是否须要有登陆验证的 if (to.matched.some(r => r.meta.auth)) { // 这里暂时将cookie里是否存有token做为验证是否登陆的条件 // 请根据自身业务须要修改 const token = util.cookies.get('token') // 获取cookie中的token if (token && token !== 'undefined') { // 用户已登陆,从sessionStorage中获取用户权限信息 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) // 找到权限列表中匹配改条路由对应的权限信息 if (item) { // 匹配到路由后,将权限信息添加在路由的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; } }) // 根据allow判断是否能够跳转到下一个页面 if (allow) { next() } else { next({ name: '401' }) } } else { next() } } else { // 没有登陆的时候跳转到登陆界面 // 携带上登录成功以后须要跳转的页面完整路径 next({ name: 'login', query: { redirect: to.fullPath } }) // https://github.com/d2-projects/d2-admin/issues/138 NProgress.done() } } else { // 不须要身份校验 直接经过 next() } }) router.afterEach(to => { // 进度条 NProgress.done() // 多页控制 打开新的页面 store.dispatch('d2admin/page/open', to) // 更改标题 util.title(to.meta.title) }) export default router
this.$route.meta.[operations]
)的 meta 中的权限信息(如:增删改查),得出具体某个页面的操做权限信息,针对不一样用户权限作出不一样的操做。注意:
// src\store\modules\d2admin\modules\account.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 => { // 设置 cookie 必定要存 uuid 和 token 两个 cookie // 整个系统依赖这两个数据进行校验和存储 // uuid 是用户身份惟一标识 用户注册的时候肯定 而且不可改变 不可重复 // token 表明用户当前登陆状态 建议在网络请求中携带 token // 若有必要 token 须要定时更新,默认保存一天 util.cookies.set('uuid', res.uuid) util.cookies.set('token', res.token) // 登陆成功后,加载用户信息和权限信息 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)); } }) // 设置 vuex 用户信息 await dispatch('d2admin/user/set', { name: res.name }, { root: true }) // 用户登陆后从持久化数据加载一系列的设置 await dispatch('load') // 结束 resolve() }) .catch(err => { console.log('err: ', err) reject(err) }) }) }, /** * @description 注销用户并返回登陆页面 * @param {Object} param context * @param {Object} param confirm {Boolean} 是否须要确认 */ 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 }) // 跳转路由 router.push({ name: 'login' }) } // 判断是否须要确认 if (confirm) { commit('d2admin/gray/set', true, { root: true }) MessageBox.confirm('注销当前帐户吗? 打开的标签页和用户设置将会被保存。', '确认操做', { 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 用户登陆后从持久化数据加载一系列的设置 * @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 加载页面过渡效果设置 await dispatch('d2admin/transition/load', null, { root: true }) // DB -> store 持久化数据加载上次退出时的多页列表 await dispatch('d2admin/page/openedLoad', null, { root: true }) // DB -> store 持久化数据加载侧边栏折叠状态 await dispatch('d2admin/menu/asideCollapseLoad', null, { root: true }) // DB -> store 持久化数据加载全局尺寸 await dispatch('d2admin/size/load', null, { root: true }) // end resolve() }) } } }
// src\views\permission\index.vue <template> <d2-container> <template slot="header">页面权限验证</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: '上海市普陀区金沙江路 1518 弄', role: '普通会员', showEditButton: this.$route.meta.operations.includes('modify'), showRemoveButton: this.$route.meta.operations.includes('delete') }, { id: 2, date: '2016-05-04', name: '王小虎', address: '上海市普陀区金沙江路 1517 弄', role: '普通会员', showEditButton: this.$route.meta.operations.includes('modify'), showRemoveButton: this.$route.meta.operations.includes('delete') }, { id: 3, date: '2016-05-01', name: '王小虎', address: '上海市普陀区金沙江路 1519 弄', role: '普通会员', showEditButton: this.$route.meta.operations.includes('modify'), showRemoveButton: this.$route.meta.operations.includes('delete') }, { id: 4, date: '2016-05-03', name: '王小虎', address: '上海市普陀区金沙江路 1516 弄', role: '普通会员', showEditButton: this.$route.meta.operations.includes('modify'), showRemoveButton: this.$route.meta.operations.includes('delete') }, { id: 5, date: '2016-05-02', name: '王小虎', address: '上海市普陀区金沙江路 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: