辦公系統前端開發
本系統采用vue框架進行搭建,主要ui是Elementui,主要功能實現了一個公司的員工資料、人事管理、薪資管理、統計管理、系統管理等功能。
1登陸頁面設計
如圖所示,使用Elementui進行簡單搭建,驗證碼由后端提供,主要代碼如下,
本代碼主要運用了elemenui的Loading加載、form驗證表單
以及附屬的相關事件,樣式由后期調整得。
事件的主要功能:將axios從后端請求得到的token數據存入window.
SessionStorage.setItem()中,為了使axios做下一次請求時獲取token認證,
登陸即完成了

<template> <div> <el-form v-loading="loading" element-loading-text="正在登錄..." element-loading-spinner="el-icon-loading" element-loading-background="rgba(0, 0, 0, 0.8)" :rules="rules" ref="loginForm" :model="loginForm" class="loginContainer"> <h3 class="loginTitle">系統登陸</h3> <el-form-item prop="username"> <el-input type="text" v-model="loginForm.username" auto-complete="false" placeholder="請輸入用戶名"></el-input> </el-form-item> <el-form-item prop="password"> <el-input type="password" v-model="loginForm.password" auto-complete="false" placeholder="請輸入密碼"></el-input> </el-form-item> <el-form-item prop="code"> <el-input style="width:200px ; margin-right: 5px" type="text" v-model="loginForm.code" auto-complete="false" placeholder="點擊更換圖片"> </el-input> <img :src="captchaUrl" @click="updateCaptcha" class="el-form-item__content"> </el-form-item> <el-checkbox v-model="checked" class="loginRemember">記住我</el-checkbox> <el-button type="primary" style="width: 100%" @click="submitLogin">登錄</el-button> </el-form> </div> </template> <script> export default { name: "Login", data(){ return{ captchaUrl:'/captcha?time='+new Date(), loginForm:{ username:'admin', password:'123', code:'' }, checked:true, loading:false, rules:{ username: [{required:true,message:'請輸入用戶名',trigger:'blur'}], password: [{required:true,message: '請輸入密碼',trigger:'blur'}], code: [{required:true,message:'請輸入驗證碼',trigger:'blur'}] } } }, methods: { updateCaptcha(){ this.captchaUrl='/captcha?time='+new Date(); }, submitLogin() { this.$refs.loginForm.validate((valid) =>{ if (valid) { this.loading=true; this.postRequest('/login',this.loginForm).then(resp=>{ if (resp){ this.loading=false; const tokenStr=resp.obj.tokenHead+resp.obj.token; //存進去 跳轉頁面后獲取 window.sessionStorage.setItem('tokenStr',tokenStr); //跳轉 /* this.$router.replace('/home');*/ /*此處解決自己輸入屬地址的行為*/ let path = this.$route.query.redirect; this.$router.replace((path=='/'||path==undefined)?'/home' : path); } this.loading=false; }); } else { this.$message.error('請填寫完整!!!'); return false; } }); } } } </script> <style> .el-form-item__content{ display: flex; align-items: center } .loginContainer{ border-radius: 15px; background-clip: padding-box;/*背景裁剪到內邊框*/ margin: 180px auto; width: 350px; padding: 15px 35px 15px 35px;/*拉大間隔*/ background: #fff; border: 1px solid #eaeaea; } .loginTitle{ margin: 0px auto 40px auto; text-align: center; } .loginRemember{ text-align: left; margin: 0px 0px 15px 0px; } </style>
2主頁設計
如圖所示,主要將上面所屬的系統功能一一展示在左上角,主要運用的是
Elementui的container容器布局第四個,展示的數據菜單由路由模式從
后端獲取,實現自動展示相應的菜單,代碼如下。

<template> <div> <el-container> <el-header class="homeHeader"> <div class="title"> 雲e辦</div> <!--下拉菜單 指令事件--> <el-dropdown class="userInfo" @command="commandHandler"> <span class="el-dropdown-link"> {{user.name}}<i><img :src="user.userFace"/></i> </span> <el-dropdown-menu slot="dropdown"> <el-dropdown-item command="userinf">個人中心</el-dropdown-item> <el-dropdown-item command="setting">設置</el-dropdown-item> <el-dropdown-item command="logout">注銷登錄</el-dropdown-item> </el-dropdown-menu> </el-dropdown> </el-header> <el-container> <el-aside width="200px"> <el-menu router unique-opened> <el-submenu :index="index+''" v-for="(item,index) in routes" :key="index" v-if="!item.hidden"> <template slot="title"> <i :class="item.iconCls" style="color: darkcyan;margin-right: 5px"></i> <span>{{item.name}}</span> </template> <el-menu-item v-for="(children,index) in item.children" :index="children.path"> {{children.name}} </el-menu-item> </el-submenu> </el-menu> </el-aside> <el-main> <el-breadcrumb v-if="this.$router.currentRoute.path !='/home'" separator-class="el-icon-arrow-right"> <el-breadcrumb-item :to="{ path: '/home' }">首頁</el-breadcrumb-item> <el-breadcrumb-item>{{this.$router.currentRoute.name}}</el-breadcrumb-item> </el-breadcrumb> <div class="homeWelcome" v-if="this.$router.currentRoute.path=='/home'"> 歡迎來到雲e辦系統 </div> <router-view class="homeRouterView" /> </el-main> </el-container> </el-container> </div> </template> <script> export default { name: "Home", computed:{ routes(){ return this.$store.state.routes; } }, data(){ return{ user:JSON.parse(window.sessionStorage.getItem('user')) } }, methods:{ commandHandler(command){ if (command=='logout'){ { this.$confirm('此操作將注銷登錄, 是否繼續?', '提示', { confirmButtonText: '確定', cancelButtonText: '取消', type: 'warning' }).then(() => { this.postRequest('/logout'); window.sessionStorage.removeItem('tokenStr'); window.sessionStorage.removeItem('user'); //清空菜單 this.$store.commit('initRoutes',[]); this.$router.replace('/'); }).catch(() => { this.$message({ type: 'info', message: '已取消注銷' }); }); } } } } } </script> <style> .homeHeader{ background: #409eff; display: flex;/*文本生效*/ align-items: center; justify-content: space-between;/*將最后元素末尾對齊*/ padding: 0 15px; box-sizing: border-box; } .homeHeader .title{ font-size: 30px; font-family: 華文楷體; color: white; } .homeHeader .userInfo{ cursor:pointer; } .el-dropdown-link img{ width: 48px; height: 48px; border-radius: 24px; } .homeWelcome{ text-align: center; font-size: 30px; font-family: 華文楷體; color: #409eff; padding-top: 50px; } .homeRouterView{ margin-top: 10px; } </style>
3Vuex及路由存儲對象
安裝vuex后,使用vuex構建存儲對象,代碼如下
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex);
export default new Vuex.Store({
state:{
routes:[]
},
//同步
mutations:{
initRoutes(state,data){
state.routes=data;
}
},
//異步
actions:{
}
})
4菜單初始化工具類
因為我們剛剛使用vuex構建了路由儲存對象,需要使用菜單初始化工具將菜單存入routes[],
具體工具類代碼如下

import {getRequest} from './api' export const initMenu = (router,store)=>{ if(store.state.routes.length>0){ return; } getRequest('/system/cfg/menu').then(data=>{ if (data){ let fmtRoutes = formatRoutes(data); router.addRoutes(fmtRoutes); store.commit('initRoutes',fmtRoutes);/*核心*/ } }) } export const formatRoutes = (routes)=>{ let fmtRoutes=[]; routes.forEach(router=>{ let{ path, component, name, iconCls, children, }= router; if (children&&children instanceof Array){ //遞歸 children=formatRoutes(children); } let fmRouter = { path:path, name, iconCls, children, component(resolve){ if (component.startsWith('Home')){ require(['../views/'+component+'.vue'],resolve); }else if (component.startsWith('Emp')){ require(['../views/emp/'+component+'.vue'],resolve); }else if(component.startsWith('Per')){ require(['../views/per/'+component+'.vue'],resolve); }else if(component.startsWith('Sal')){ require(['../views/sal/'+component+'.vue'],resolve); }else if(component.startsWith('Sta')){ require(['../views/sta/'+component+'.vue'],resolve); }else if(component.startsWith('Sys')){ require(['../views/sys/'+component+'.vue'],resolve); } } } fmtRoutes.push(fmRouter); }); return fmtRoutes; }
5請求響應處理工具類
此工具類主要是過濾非法請求以及處理響應時發生的錯誤,將他們的信息用彈窗顯示出來如圖所示,具體代碼如下

import axios from 'axios' import {Message} from "element-ui"; //請求攔截器 axios.interceptors.request.use(config=>{ if (window.sessionStorage.getItem('tokenStr')){ //請求時自動帶入token config.headers['Authorization'] = window.sessionStorage.getItem('tokenStr') } return config; },error => { console.log(error); }) axios.interceptors.response.use(success=>{ //業務邏輯錯誤 if (success.status||success.data.code==200){ if (success.data.code==500||success.data.code==401||success.data.code==403){ Message.error({message:success.data.message}); return; } if (success.data.message){ Message.success({message:success.data.message}) } } return success.data; },error=>{ //未能調取接口錯誤 if (error.response.data.code==504||error.response.data.code==404){ Message.error({message:'服務器被吃了'}) }else if(error.response.data.code==403){ Message.error({message:'權限不足'}) }else if(error.response.data.code==401){ Message.error({message:'尚未登陸'}) router.replace('/') } else { if (error.response.data.message) { Message.error({message: error.response.data.message}); } else { Message.error({message: '未知錯誤'}) } } return; }); let base=''; //傳送json請求格式的post請求 export const postRequest=(url,params)=>{ return axios({ method:'post', url:`${base}${url}`, data:params }) } export const putRequest=(url,params)=>{ return axios({ method:'put', url:`${base}${url}`, data:params }) } export const getRequest=(url,params)=>{ return axios({ method:'get', url:`${base}${url}`, data:params }) } export const deleteRequest=(url,params)=>{ return axios({ method:'delete', url:`${base}${url}`, data:params }) }
6暴露路由
使用路由模式菜單,需要將路由包裝暴露出來,以便公共使用
import Vue from 'vue' import VueRouter from 'vue-router' import Login from '../views/Login.vue' Vue.use(VueRouter) const routes = [ { path: '/', name: 'Login', component: Login, hidden:true }, ] const router = new VueRouter({ routes }) export default router
7創建菜單組件
我們需要將菜單組件分組創建好,然后通過工具類初始化存入路由數組,以便實現點擊跳轉,代碼后面發布

8創建系統管理SysBasic的子組件及基礎信息設置代碼
基礎信息設置 代碼,采用了elementui的Tabs標簽效果,內容名字是它的子組件


<template> <div> <el-tabs v-model="activeName" type="card"> <el-tab-pane label="部門管理" name="DepMana"><DepMana></DepMana></el-tab-pane> <el-tab-pane label="職位管理" name="PosMana"><PosMana/></el-tab-pane> <el-tab-pane label="職稱管理" name="JoblevelMana"><JoblevelMana/></el-tab-pane> <el-tab-pane label="獎懲規則" name="EcMana"><EcMana/></el-tab-pane> <el-tab-pane label="權限組" name="PermissMana"><PermissMana/></el-tab-pane> </el-tabs> </div> </template> <script> import DepMana from '../../components/sys/basic/DepMana' import EcMana from "../../components/sys/basic/EcMana"; import JoblevelMana from "../../components/sys/basic/JoblevelMana"; import PermissMana from "../../components/sys/basic/PermissMana"; import PosMana from "../../components/sys/basic/PosMana"; export default { name: "SysBasic", data(){ return{ activeName:'JoblevelMana' /*默認開啟PosMana*/ } }, components:{ DepMana, EcMana, JoblevelMana, PermissMana, PosMana } } </script> <style scoped> </style>
9基礎信息設置之職位管理
采用Elementui的input輸入框、Table表格多選,含有添加、編輯、刪除、批量刪除事件

<template> <div> <div> <el-input suffix-icon="el-icon-plus" class="addPosInput" size="small" placeholder="添加職位" v-model="pos.name" @keydown.enter.native="addPosition" > </el-input> <el-button type="primary" icon="el-icon-plus" size="small" @click="addPosition">添加</el-button> </div> <div> <el-table border size="small" stripe :data="positions" @selection-change="handleSelectionChange" style="width: 70%"> <el-table-column type="selection" width="55"> </el-table-column> <el-table-column prop="id" label="編號" width="55"> </el-table-column> <el-table-column prop="name" label="職位" width="120"> </el-table-column> <el-table-column prop="createDate" label="創建時間" width="200"> </el-table-column> <el-table-column prop="enabled" label="是否啟用" width="120"> </el-table-column> <el-table-column label="操作"> <template scope="scope"> <el-button size="small" @click="showEditView(scope.$index, scope.row)">編輯</el-button> <el-button size="small" type="danger" @click="handleDelete(scope.$index, scope.row)">刪除</el-button> </template> </el-table-column> </el-table> </div> <el-button @click="deleteMany" type="danger" size="small" style="margin-top: 8px" :disabled="this.multipleSelection.length==8">批量刪除</el-button> <el-dialog title="編輯職位" :visible.sync="dialogVisible" size="tiny" > <div> <el-tag>職位名稱</el-tag> <el-input v-model="updatePos.name" class="updatePosInput"></el-input> </div> <span slot="footer" class="dialog-footer"> <el-button @click="dialogVisible = false" size="small">取 消</el-button> <el-button type="primary" @click="doUpdate" size="small">確 定</el-button> </span> </el-dialog> </div> </template> <script> export default { name: "PosMana", data(){ return{ pos:{ name:'' }, positions:[], dialogVisible:false, updatePos:{ name:"" }, multipleSelection:[] } }, mounted() { this.initPositions(); }, methods:{ initPositions(){ this.getRequest('/system/basic/pos/').then(resp=>{ if (resp){ this.positions = resp; } }) }, doUpdate(){ this.putRequest('/system/basic/pos/',this.updatePos).then(resp=>{ if (resp){ this.initPositions(); this.dialogVisible=false; } }) }, addPosition(){ if (this.pos.name){ this.postRequest('/system/basic/pos/',this.pos).then(resp=>{ if (resp){ this.initPositions(); } }) }else{ this.$message.error('職位名稱不能為空!'); } }, showEditView(index, date) { Object.assign(this.updatePos,date);/*date拷貝給updatePos*/ this.updatePos.createDate=""; this.dialogVisible=true; }, handleDelete(index, data) { this.$confirm('此操作將永久刪除['+data.name+'], 是否繼續?', '提示', { confirmButtonText: '確定', cancelButtonText: '取消', type: 'warning' }).then(() => { this.deleteRequest('/system/basic/pos/'+data.id).then(resp=>{ if (resp){ this.initPositions(); } }); }).catch(() => { this.$message({ type: 'info', message: '已取消刪除' }); }); }, handleSelectionChange(val){ this.multipleSelection=val; }, deleteMany(){ this.$confirm('此操作將永久刪除['+this.multipleSelection.length+']條職位, 是否繼續?', '提示', { confirmButtonText: '確定', cancelButtonText: '取消', type: 'warning' }).then(() => { let ids ='?'; this.multipleSelection.forEach(item=>{ ids += 'ids=' + item.id+'&'; }); this.deleteRequest('/system/basic/pos/'+ids).then(resp=>{ if (resp){ this.initPositions(); } }); }).catch(() => { this.$message({ type: 'info', message: '已取消刪除' }); }); } } } </script> <style> .addPosInput{ width:300px; margin-right: 8px; } /*.posMainMain{ margin-top: 10px; }*/ .updatePosInput{ width: 200px; margin-left: 8px; } </style>
10基礎信息設置之權限組
權限組 代碼,采用了elementui的折疊面板手風琴模式,Tree樹形控件

<template> <div> <div class="PermissManaTool"> <el-input size="small" placeholder="請輸入角色英文名" v-model="role.name"> <template slot="prepend">ROLE_</template> </el-input> <el-input size="small" placeholder="請輸入角色中文名" v-model="role.nameZh"> </el-input> <el-button size="small" type="primary" icon="el-icon-plus" @click="doAddRole">添加角色</el-button> </div> <div class="permissManaMain"> <!--折疊面板 手風琴模式 change自帶事件會傳:name的值 通過 accordion 屬性來設置是否以手風琴模式顯示。--> <el-collapse v-model="activeName" accordion @change="change"> <el-collapse-item :title="r.nameZh" :name='r.id' v-for="(r,index) in roles" :key="index"> <!--卡片--> <el-card class="box-card"> <div slot="header" class="clearfix"> <span>可訪問資源</span> <!--刪除按鈕--> <el-button style="float: right; padding: 3px 0;color: red" type="text" icon="el-icon-delete" @click="doDeleteRole(r)" /> </div> <div> <!--樹形控件 defaultProps展示子類和名字 node-key="id"事件通過id獲取數據--> <el-tree :data="allMenus" ref="tree" :key="index" :props="defaultProps" show-checkbox :default-checked-keys="selectedMenus" node-key="id" ></el-tree> <!-- 樣式 容器布局 向右對齊--> <div style="display: flex; justify-content: flex-end"> <el-button size="mini">取消修改</el-button> <el-button size="mini" type="primary" @click="doUpdate(r.id,index)">確認修改</el-button> <!--primary為主要按鈕樣式--> </div> </div> </el-card> </el-collapse-item> </el-collapse> </div> </div> </template> <script> export default { name: "PermissMana", data(){ return{ role:{ name:'', nameZh:'' }, roles:[], allMenus:[], defaultProps: { children: 'children', label: 'name' }, selectedMenus:[], activeName:-1 } }, mounted(){ this.initRoles(); }, methods:{ doAddRole(){ if (this.role.name && this.role.nameZh){ this.postRequest('/system/basic/permiss/role',this.role).then(resp=>{ if (resp){ this.initRoles(); this.role.name=''; this.role.nameZh=''; } }); }else{ this.$message.error('所有字段不能為空!'); } }, doUpdate(rid,index){ let tree = this.$refs.tree[index]; let selectedKeys = tree.getCheckedKeys(true);/*傳true只打印葉子節點*/ let url='/system/basic/permiss/role?rid='+rid; let mids =''; selectedKeys.forEach(key=>{ mids += '&mids='+key; }); url+=mids; this.putRequest(url).then(resp=>{ if (resp){ this.initRoles(); this.activeName=-1; } }); }, doDeleteRole(role){ this.$confirm('此操作將永久刪除['+role.nameZh+'], 是否繼續?', '提示', { confirmButtonText: '確定', cancelButtonText: '取消', type: 'warning' }).then(() => { this.deleteRequest('/system/basic/permiss/role/'+role.id).then(resp=>{ if (resp){ this.initRoles(); } }); }).catch(() => { this.$message({ type: 'info', message: '已取消刪除' }); }); }, initSelectedMenus(rid){ this.getRequest('/system/basic/permiss/mid/'+rid).then(resp=>{ if (resp){ this.selectedMenus=resp; } }); }, change(rid){ if (rid){ this.initAllMenus(); this.initSelectedMenus(rid); this.shuaxin(rid); } }, shuaxin(rid){ this.initAllMenus(); this.initSelectedMenus(rid); }, initAllMenus(){ this.getRequest('/system/basic/permiss/menus').then(resp=>{ if (resp){ this.allMenus=resp; } }); }, initRoles(){ this.getRequest('/system/basic/permiss/').then(resp=>{ if (resp){ this.roles=resp; } }); } } } </script> <style > .PermissManaTool{ display: flex;/*使用容器布局 下面左對齊生效*/ justify-content: flex-start;/*左對齊*/ } .PermissManaTool .el-input{ width: 300px; margin-right: 6px; } .permissManaMain{ margin-top: 10px; width: 700px; } </style>
