角色管理

This commit is contained in:
zjl
2021-10-03 21:14:13 +08:00
parent 94d899dfc4
commit 58892dea40
28 changed files with 514 additions and 210 deletions
+7 -6
View File
@@ -51,6 +51,7 @@
<script lang="ts">
import { Options, Vue } from 'vue-class-component'
import { ElContainer, ElHeader, ElMain, ElAside, ElDropdown, ElDropdownMenu, ElDropdownItem, ElButton, ElMenu, ElSubMenu, ElMenuItem, ElBreadcrumb, ElBreadcrumbItem } from 'element-plus'
import { AxiosResponse } from 'axios'
import menus from '../config/menu'
@Options({
@@ -58,12 +59,12 @@ import menus from '../config/menu'
components: { ElContainer, ElHeader, ElMain, ElAside, ElDropdown, ElDropdownMenu, ElDropdownItem, ElButton, ElMenu, ElSubMenu, ElMenuItem, ElBreadcrumb, ElBreadcrumbItem }
})
export default class Home extends Vue{
private currentYear = new Date().getFullYear()
currentYear = new Date().getFullYear()
// 菜单项
private menus = menus
private activeMenuItem: string | null = null
private openMenuNames: string[] = []
get realname(): string { // 当前用户的显示名称
menus = menus
activeMenuItem: string | null = null
openMenuNames: string[] = []
get realname(): null | string { // 当前用户的显示名称
return this.$store.state.loginInfo.userInfo
? this.$store.state.loginInfo.userInfo.realname : null
}
@@ -79,7 +80,7 @@ export default class Home extends Vue{
this.$router.push('/login')
return
}
const { data } = await this.$http.post('/api/common/verifyToken', {token: localStorage.getItem('login_token')})
const { data } = await this.$http.post<{token: null | string}, AxiosResponse<any>>('/api/common/verifyToken', {token: localStorage.getItem('login_token')})
if(data.status) {
// 如果是已过期的token 服务端会签发新的token
this.$store.commit('login', {token: data.newToken || localStorage.getItem('login_token'), userInfo: data.userInfo})
+3 -4
View File
@@ -20,7 +20,6 @@
<script lang="ts">
import { Options, Vue } from 'vue-class-component'
import { ref } from 'vue'
import { VForm } from "../types"
import { ElMessage, ElButton, ElForm, ElFormItem, ElInput, ElTooltip } from 'element-plus'
import { AxiosResponse } from 'axios'
@@ -29,12 +28,12 @@ import { AxiosResponse } from 'axios'
components: { ElButton, ElForm, ElFormItem, ElInput, ElTooltip }
})
export default class Login extends Vue {
private readonly loginForm: VForm = ref('loginForm')
private userInfo: UserInfo = {
private readonly loginForm: any = ref('loginForm')
userInfo: UserInfo = {
username: null,
password: null
}
private ruleValidate = {
ruleValidate = {
username: [
{ required: true, message: '请输入用户名', trigger: 'blur' }
],
+17 -105
View File
@@ -1,121 +1,31 @@
<template>
<div>
<div class="clock-circle">
<div class="clock-face">
<div class="clock-hour" :style="{transform: `rotate(${clock.hourRotate}deg)`}" ></div>
<div class="clock-minute" :style="{transform: `rotate(${clock.minuteRotate}deg)`}"></div>
<div class="clock-second" :style="{transform: `rotate(${clock.secondRotate}deg)`}"></div>
</div>
</div>
<h2 class="time-text">{{timeText}}</h2>
<div class="nav-list">
<Row v-for="menu in menus" :key="menu.name">
<Col :span="3" class="nav-title">{{ menu.title }}</Col>
<Col :span="21">
<router-link :to="submenu.path" v-for="submenu in menu.child" :key="submenu.path" class="nav-item">
<Icon :type="submenu.icon" /> {{submenu.title}}
</router-link>
</Col>
</Row>
<el-row v-for="menu in menus" :key="menu.name">
<el-col :span="3" class="nav-title">{{ menu.title }}</el-col>
<el-col :span="21">
<router-link :to="submenu.path" v-for="submenu in menu.child" :key="submenu.path" class="nav-item">
{{submenu.title}}
</router-link>
</el-col>
</el-row>
</div>
</div>
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator'
import moment from 'moment'
import { Options, Vue } from 'vue-class-component'
import { ElRow, ElCol } from 'element-plus'
import menus from '../config/menu'
@Component({})
@Options({
name: 'Welcome',
components: { ElRow, ElCol }
})
export default class Welcome extends Vue {
private menus = menus
private timeText!: string
private clock = {
hourRotate: 0,
minuteRotate: 0,
secondRotate: 0
}
private clockTimer!: number | null
created(): void {
const timedUpdate = (): void => {
this.updateClock()
this.clockTimer = setTimeout(timedUpdate, 1000)
}
timedUpdate()
}
beforeDestroy(): void {
if(this.clockTimer) {
clearTimeout(this.clockTimer)
this.clockTimer = null
}
}
updateClock(): void {
const now = moment()
this.timeText = now.format('YYYY年M月D日 HH:mm:ss')
this.clock.secondRotate = now.seconds() * 6
this.clock.minuteRotate = now.minutes() * 6 + this.clock.secondRotate / 60
this.clock.hourRotate = ((now.hours() % 12) / 12) * 360 + 90 + this.clock.minuteRotate / 12
}
menus = menus
}
</script>
<style lang="less" scoped>
.clock-circle {
width: 180px;
height: 180px;
margin: 0 auto;
position: relative;
border: 8px solid #000;
border-radius: 50%;
box-shadow: 0 1px 8px rgba(34,34,34,0.3),inset 0 1px 8px rgba(34,34,34,0.3);
}
.clock-face {
width: 100%;
height: 100%;
&::after {
position: absolute;
top: 50%;
left: 50%;
width: 12px;
height: 12px;
margin: -6px 0 0 -6px;
background: #000;
border-radius: 6px;
content: "";
display: block
}
}
.clock-hour,.clock-minute,.clock-second {
width: 0;
height: 0;
position: absolute;
top: 50%;
left: 50%;
background: #000;
}
.clock-hour {
margin: -4px 0 -4px -25%;
padding: 4px 0 4px 25%;
transform-origin: 100% 50%;
border-radius: 4px 0 0 4px
}
.clock-minute {
margin: -40% -3px 0;
padding: 40% 3px 0;
transform-origin: 50% 100%;
border-radius: 3px 3px 0 0
}
.clock-second {
margin: -40% -1px 0 0;
padding: 40% 1px 0;
transform-origin: 50% 100%
}
.time-text {
text-align: center;
}
.nav-list {
.nav-title {
font-size: 16px;
@@ -130,6 +40,8 @@ export default class Welcome extends Vue {
margin: 10px;
border: 1px solid #ccc;
padding: 10px;
text-decoration: none;
color: var(--el-color-primary);
}
}
</style>
+212
View File
@@ -0,0 +1,212 @@
<template>
<div>
<el-form inline :model="search" @submit.prevent>
<el-form-item label="角色名称/描述:">
<el-input size="small" v-model="search.name" />
</el-form-item>
</el-form>
<div class="btn-container">
<el-button size="small" type="primary" @click="add">添加</el-button>
<div class="search-btn">
<el-button type="primary" @click="loadDataBase(true)" size="small" icon="el-icon-search">搜索</el-button>
<el-button @click="reset" size="small" icon="el-icon-refresh-right">重置</el-button>
</div>
</div>
<div class="table-container">
<el-table :data="systemRoleData" v-loading="loading" stripe height="520">
<el-table-column prop="name" label="角色名称" />
<el-table-column prop="methods" label="允许的请求类型" >
<template #default="scope">
<el-tag type="info" size="small" v-for="method in scope.row.methods" :key="method">{{method}}</el-tag>
</template>
</el-table-column>
<el-table-column prop="created_at" label="创建时间" >
<template #default="scope">
{{ datetimeFormat(scope.row.created_at) }}
</template>
</el-table-column>
<el-table-column prop="updated_at" label="更新时间" >
<template #default="scope">
{{ datetimeFormat(scope.row.updated_at) }}
</template>
</el-table-column>
<el-table-column label="操作" >
<template #default="scope">
<el-button size="mini" plain @click="update(scope.row)">修改</el-button>
<el-button type="danger" size="mini" plain @click="remove(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
</div>
<div class="page-container">
<el-pagination background
:page-sizes="$store.state.pageSizeOpts"
:total="total"
@size-change="pageSizeChange"
@current-change="pageChange">
</el-pagination>
</div>
<el-dialog v-model="addModal" :title="modalTitle" >
<el-form ref="roleForm" :model="formData" :rules="ruleValidate" :label-width="120">
<el-form-item label="角色名称" prop="name">
<el-input v-model="formData.name" />
</el-form-item>
<el-form-item label="描述">
<el-input v-model="formData.description" />
</el-form-item>
<el-form-item label="允许的请求类型">
<el-select v-model="formData.methods" multiple >
<el-option value="GET">GET</el-option>
<el-option value="POST">POST</el-option>
<el-option value="PUT">PUT</el-option>
<el-option value="DELETE">DELETE</el-option>
</el-select>
</el-form-item>
<el-form-item label="允许的URI">
<el-input v-model="uri.include">
<template #append>
<el-button icon="el-icon-plus" @click="addUri('include_uri', uri.include)"></el-button>
</template>
</el-input>
<el-tag v-for="uri in formData.include_uri" :key="uri" closable @close="removeUri('include_uri', uri)">{{uri}}</el-tag>
</el-form-item>
<el-form-item label="禁止的URI">
<el-input v-model="uri.exclude">
<template #append>
<el-button icon="el-icon-plus" @click="addUri('exclude_uri', uri.exclude)"></el-button>
</template>
</el-input>
<el-tag v-for="uri in formData.exclude_uri" :key="uri" closable @close="removeUri('exclude_uri', uri)">{{uri}}</el-tag>
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="addModal = false">取消</el-button>
<el-button type="primary" @click="save" :loading="modalLoading">确定</el-button>
</span>
</template>
</el-dialog>
</div>
</template>
<script lang="ts">
import { Options } from 'vue-class-component'
import BaseList from '../../model/baselist'
import { Page } from '../../model/common.dto'
import { SystemRoleModel } from '../../model/system/system-role'
import { AxiosResponse } from 'axios'
import { ElButton, ElForm, ElFormItem, ElInput, ElTable, ElTableColumn, ElTag, ElPagination, ElDialog, ElSelect, ElOption, ElMessage, ElMessageBox } from 'element-plus'
import { ref } from 'vue'
@Options({
name: 'SystemRole',
components: { ElButton, ElForm, ElFormItem, ElInput, ElTable, ElTableColumn, ElTag, ElPagination, ElDialog, ElSelect, ElOption }
})
export default class SystemRole extends BaseList<SystemRolePage> {
private readonly roleForm: any = ref('roleForm')
ruleValidate = {
name: [
{ required: true, message: '请输入角色名称', trigger: 'blur' }
],
}
systemRoleData: SystemRoleModel[] = []
protected search = new SystemRolePage()
addModal: boolean = false
modalTitle: string | null = null
uri: {
include: string | null,
exclude: string | null
} = {
include: null,
exclude: null
}
formData: SystemRoleModel = {
_id: null,
name: null,
description: null,
methods: [],
include_uri: [],
exclude_uri: []
}
async loadData() {
this.loading = true
const { data } = await this.$http.get<{params: SystemRolePage}, AxiosResponse<any>>('/api/system/role/list', {params:this.search})
this.loading = false
this.total = data.total
this.systemRoleData = data.data
}
created() {
this.loadData()
}
add() {
// 清空表单
this.uri.include = null
this.uri.exclude = null
this.formData = {
_id: null,
name: null,
description: null,
methods: [],
include_uri: [],
exclude_uri: []
}
this.modalTitle = '新增角色'
this.addModal = true
}
addUri(fieldName: 'include_uri' | 'exclude_uri', uri: string) {
if(!uri) return
if(this.formData[fieldName].indexOf(uri) === -1) {
this.formData[fieldName].push(uri)
}
}
removeUri(fieldName: 'include_uri' | 'exclude_uri', uri: string) {
let index = this.formData[fieldName].indexOf(uri)
if(index !== -1) {
this.formData[fieldName].splice(index, 1)
}
}
update(row: SystemRoleModel) {
this.uri.include = null
this.uri.exclude = null
this.formData._id = row._id
this.formData.name = row.name
this.formData.description = row.description
this.formData.methods = row.methods
this.formData.include_uri = row.include_uri
this.formData.exclude_uri = row.exclude_uri
this.modalTitle = '修改角色'
this.addModal = true
}
remove(row: SystemRoleModel) {
ElMessageBox.confirm(`是否确认删除 ${row.name} 角色?`, '确认删除', {type: 'warning'}).then(async () => {
const { data } = await this.$http.delete<{params: {id: string}}, AxiosResponse<any>>('/api/system/role/delete', {params: {id: row._id}})
if(data.status) {
ElMessage.success(data.message)
this.loadData()
} else {
ElMessage.warning(data.message)
}
}).catch(() => {})
}
async save() {
this.roleForm.validate(async (valid: boolean) => {
if(!valid) {
this.modalLoading = false
return
}
const { data } = await this.$http.post<SystemRoleModel, AxiosResponse<any>>('/api/system/role/save', this.formData)
this.addModal = false
ElMessage.success(data.message)
this.loadData()
})
}
}
class SystemRolePage extends Page {
name?: string
reset() {
super.reset()
this.name = undefined
}
}
</script>
View File