一、项目简介
项目背景:定制app开发受疫情的影响,定制app开发许多企业由线上办公转定制app开发为线下办公。定制app开发随着线上办公的人数的增多,定制app开发线上办公的优点逐步凸显:定制app开发通过实现工作流程的自动化、定制app开发节省企业办公费用、定制app开发实现绿色办公,定制app开发同时提升办公效率。
项目介绍:定制app开发本项目实现了一个在线办公系统,定制app开发用来管理日常办公事物的:定制app开发日常流程审批,新闻,通知,公告,文件信息,财务,人事,费用,资产,行政,项目,定制app开发移动办公等。定制app开发通过软件的方式让办公定制app开发系统根据方便管理,定制app开发提高整体的管理运营水平。
实现方式:定制app开发本项目基于Vue+Spring Boot构架一个。定制app开发前端使用社区非常活跃定制app开发的开源框架vue进行构建。简单地说,定制app开发前后端分离 定制app开发的核心思想是前端页面通过 ajax 定制app开发调用后端的 restuful api 定制app开发进行数据交互,而 定制app开发单页面应用(single page web application,),定制app开发就是只有一张页面,定制app开发并在用户与应用程序交定制app开发互时动态更新该页面的 Web 应用程序。
1.1 技术架构
1.2 定制app开发前端技术架构
定制app开发本项目采用前后端分离开发模式,使用Spring Boot构建后端。
定制app开发前端使用的技术有:
Vue、Vue-cli、Vuex、VueRouter、ElementUI、Axios、ES6、Webpack、WebSocket、font-awesome、js-file-download、vue-chat
项目搭建:Vue-cli ;状态管理:Vuex ;路由管理:VueRouter;UI界面:ElementUI;通讯框架:Axios ;
前端语法:ES6;打包工具:Webpack;在线聊天:WebSocket;字体:font-awesome;定制app开发文件上传下载:js-file-download;定制app开发在线聊天开源项目:vue-chat
定制app开发前端模块分为:登录、职位管理、职称管理、部门管理、操作员历、员工管理、定制app开发工资账套管理、个人中心、在线聊天
1.3 云E办(前端)
定制app开发前后端之间通过 RESTful API 传递 JSON 定制app开发数据进行交流。不同于 JSP 之类,定制app开发后端是不涉及页面本身的内容的。定制app开发在开发的时候,定制app开发前端用前端的服务器(Nginx),定制app开发后端用后端的服务器(Tomcat),定制app开发当我开发前端内容的时候,定制app开发可以把前端的请求通过定制app开发前端服务器转发给后端(称为反向代理),定制app开发这样就能实时观察结果,定制app开发并且不需要知道后端怎么实现,定制app开发而只需要知道接口提供的功能。
目录
二、Vue.js框架
Web定制app开发前端开发从开始到兴起再到如今,定制app开发已经发展了很多年,定制app开发同时累积了非常多的开定制app开发发经验和开发工具。定制app开发过去的开发者们的一些定制app开发经验也都经过了技术发展,定制app开发环境变化等重重考验,定制app开发他们所创造出来的思想、定制app开发技术和工具,定制app开发也非常值得我们后来开定制app开发发者的借鉴和学习,定制app开发甚至直接拿来使用。定制app开发因为开发工具和开发语定制app开发言不管怎么发展,定制app开发不管差异有多大,定制app开发但是他们所解决的问题定制app开发都是相似而统一的,定制app开发可以归纳为:
(1)扩充JavaScript、html和CSS定制app开发三种编程语言本身的语言能力;
(2)定制app开发解决开发过程中重复的工作;
(3)定制app开发将项目进行模块化;
(4)定制app开发解决功能复用和变更问题;
(5)定制app开发解决开发和产品环境差异问题;
(6)定制app开发解决发布流程问题。
定制app开发为了解决上述问题便产定制app开发生了工程化的思想,定制app开发工程化就是这种避免重定制app开发复造轮子的最好实践。Vue.js定制app开发是一套构建用户界面的定制app开发渐进式框架,定制app开发它是由中国程序开发者尤雨溪在2013年开发。因为Vue.js定制app开发简洁的语法设计、定制app开发轻量快速的特点在技术定制app开发社区中深受开发者欢迎,定制app开发因而也促进了Vue.js定制app开发的推广和流行。Vue.js定制app开发在相关的工具和支持库定制app开发配合使用下,定制app开发也能完美地驱动复杂的单页应用,定制app开发开发一个大型的Web应用。Vue.js在权威的JavaScript定制app开发趋势榜上已经蹿升到了总榜的前30定制app开发位且能持续的排在榜单的前列,定制app开发显然已成为一个全球顶尖的JavaScript框架。Vue.js定制app开发的生态不仅体现在趋势榜上,定制app开发其配套的数据管理库vuex、定制app开发路由管理库Vue-router、打包工具、定制app开发开发者调试插件和项目定制app开发脚手架等工具和库也都定制app开发逐渐开发成型,定制app开发同样也拥有非常活跃的技术社区。
Vue.js定制app开发是一个轻量级的MVVM前端框架,定制app开发可以用于构建渐进式用户界面。开发者在Vue.js定制app开发中构建前端页面时,定制app开发只用关心页面逻辑的实现。Vue.js定制app开发最大的特点是由底层逐定制app开发层向上应用,定制app开发不仅易上手还能兼容大量的第三方库。
2.1Vue特性
虚拟DOM
vue区别于传统框架的特点一是虚拟DOM。浏览器进行DOM操作会带来较大的开销,因此在Vue中通过diff算法构建了Virtual DOM,数据每次更新时比对最小变化,重新构建Virtual DOM。
响应式
Vue.js的核心被设计为一个响应的数据绑定系统,因此可以非常方便的将数据与DOM保持同步。在使用jQuery手工操作DOM时往往容易编写命令式的、重复的并且易错的代码。而Vue.js拥抱数据驱动的视图概念意味着在普通HTML模板中使用特殊的语法将DOM“绑定”到底层数据。这种绑定一旦创建,DOM便与数据实现了保持同步。每当对数据进行了修改则会相应的更新DOM。通过这种方式,在开发应用中,所有的业务逻辑就几乎只用直接修改数据,而不必与对DOM进行单独的更新操作,使得数据和DOM更新不会搅合在一起。这也让应用的代码更容易撰写、理解与维护。
组件化
在大型的应用开发中,往往会将应用抽象为多个相对独立的模块,目的是为了代码块的可复用性和维护性。然而只有当考虑复用性的时候才会将某一模块做成单独的一个组件,实际上,Web的视图界面也完全可以分为一个组件树。组件化是Vue.js最强大的功能之一。组件可以将视图页面的标签元素进行扩展然后进行封装,最终变成可重复使用的代码。从高层面上理解,组件也可以是一个自定义的元素,然后通过Vue.js的编译器的编译,可以为这个元素添加某种特殊的功能。同时,组件也可以是原生的HTML元素,通过is特性扩展。Vue.js和同样强调组件化思想的前端框架React.js有些类似,但是要比其更加轻量,简洁和先进。
组件化通常是指Vue.js能够将JavaScript代码、超文本标记语言(hypertext markup language, HTML)代码和层叠样式表 (cascadingstyle sheets, CSS)代码写在同一个文件里。开发者在实际开发中常常会遇到页面的功能需要多次使用的情况,这时可以在components目录下,构建可复用的组件。如果其他页面需要使用该组件,那么可以通过import方法进行引入。由于页面由多个组件构成,组件与组件之间耦合度较低,可大量减少重复性代码。
局部刷新
vue是一个,单页面应用的主要特性就是网页的局部刷新,网页应用通过控制控制路由调用AJAX,后台只需要提供接口即可实现。这样的应用优势明显,首先在用户体验上会更人性化,不需要刷新整个页面,因此加载速度快速,体验更好。
2.2 MVVM设计模式
基于B/S架构的Java Web应用系统在被开发时,前端页面的绘制与美化是系统开发的重要工作。页面的绘制与交互一般是基于对文档对象模型(document object model, DOM)元素节点和数据的操控来完成的,但直接操作DOM节点极易产生错误。近年来,随着前端技术的发展,涌现了各种各样的前端框架,这些框架基于MVVM (Model-View-ViewModel)设计模式,为前端工程的开发与维护带来了许多便利 。MVVM设计模式基于传统的MVC设计模式衍生而来,全称为Model-View-ViewModel。Model层负责持有用户数据,View层负责在屏幕上显示视觉元素和控件,ViewModel层负责将模型转换为可在视图上直接显示的值。
2.3 vue相关组件
脚手架vue-cli
快速开发工具vue-cli。它可以帮助开发者基于vue.js框架进行快速开发。vue-cli将各种工具标准化,确保各种构建工具能够基于智能的默认配置平稳衔接,使开发者在撰写前端应用时更加专注,不必花费很长时间去调整项目配置。
路由Vue-router
Vue.js构建的单页面的Web应用需要基于路由和组件。其中,路由的主要作用是用来设定访问路径,并将访问路径和视图组件相映射起来。在单页面的Web应用里,路径之间的跳转和切换其实是对应组件之间的切换。和React.js一样,Vue.js自身是也是不具有路由功能的。因此,在Vue.js框架使用的时候需要与一个路由工具库相互协作,即Vue-router。Vue-router能够将不同层级并且嵌套的路由关系映射到相应的嵌套的组件,并且提供了一个细致的控制路径跳转的解决方式。
状态管理vuex
Vue.js的视图变化都基于组件的状态,所以当构建大型的Web应用的时候会产生大量的组件状态,从而需要对这些状态进行管理。由于vue的“单向数据流”无法解决多个组件共享状态的问题,所以才有了vuex。Vuex便是一个集中式存储和管理应用的所有组件的状态的管理架构,专门为配合使用了Vue.js框架的应用而设计。它借鉴了React.js的状态管理工具Flux和Redux的设计理念,并对一些概念进行了简化,从而能更好的发挥Vue.js的数据响应机制。
通信框架axios
通信框架。因为vue的边界很明确,就是为了处理DOM,所以不具备通信能力,此时就需要额外使用一个通信框架与服务器交互;当然也可以直接使用jQuery提供的AJAX通信功能。经典的Ajax技术实现了网页的局部数据刷新,而Axios又对Ajax进行再次封装,它具备如下特征:
(1)从浏览器中创建XMLHttpRequest
(2)从node.js中发出http请求
(3)支持PromiseApi
(4)拦截请求和响应
(5)转换请求和相应数据
(6)取消请求
(7)自动转换JSON数据
(8)客户端支持防止CSRF/XSRF
Axios插件很好的封装了Ajax技术,在项目开发中写法简洁明了,因此不容易出错,即使出错也易于排查。
打包工具webpack
前端开发和其他开发工作的主要区别,首先是前端基于多语言、多层次的编码和组织工作,其次前端产品的交付是基于浏览器的,这些资源是通过增量加载的方式运行到浏览器端,如何在开发环境组织好这些碎片化的代码和中资源,并且保证他们在浏览器快速、优雅的加载和更新,就需要一个模块化系统。
webpack是一个大型js应用程序的模块化工具,会自动处理命名空间等一系列js编程遇到的问题。当webpack处理js的应用程序时,它会构建一个复杂的依赖关系图,这个关系图中包含了应用程序依赖的模块,甚至包含了很多静态资源,然后webpack会将这些模块打包成一个或多个大的模块,在应用程序中引用。
ES6模块
vue通常用es6来写,用export default导出,其下面可以包含数据data,生命周期(mounted等),方法(methods)等,具体语法请看vue.js文档。ES6标准增加了javascript语言层面的模块体系定义。ES6模块的设计思想,是尽量静态化,使编译时就能确定模块的依赖关系,以及输入和输出的变量。CommonJS和AMD模块,都只能在运行时确定这些东西。
UI框架
ElementUI,饿了么出
三、搭建vue.js项目
3.1 搭建vue.js项目
环境准备
安装Node.js(>=6.x,首选8.x)本项目是v14.18.0版本
安装 Vue CLI
因为需要使用 npm 安装 Vue CLI,而 npm 是集成在 Node.js 中的,所以第一步我们需要安装 Node.js,访问官网 ,首页即可下载。
下载完成后运行安装包,一路下一步就行。
然后在 cmd 中输入 node -v,检查node是否安装成功。
输入 npm -v 查看npm版本号
输入 npm -g install npm ,将 npm 更新至最新版本。
之后,使用 npm install -g vue-cli 安装脚手架。(本项目使用版本2.9.6)
注意此种方式安装的是 2.x 版本的 Vue CLI,最新版本需要通过 npm install -g @vue/cli 安装。新版本可以使用图形化界面初始化项目,并加入了项目健康监控的内容,但使用新版本创建的项目依赖与这个教程不太相符,折腾起来比较麻烦。
安装Node.js的淘宝镜像加速器cnpm
大部分情况使用npm,遇到安装不了的使用cnpm
npm install cnpm -g
或npm install --registry=https://registry.npm.taobao.org
3.2 构建前端项目
通用方法
直接使用命令行构建项目。
然后执行命令 vue init webpack yeb,这里 webpack 是以 webpack 为模板指生成项目,还可以替换为 pwa、simple 等参数,这里不再赘述。
在程序执行的过程中会有一些提示,可以按照默认的设定一路回车下去,也可以按需修改,比如下图问我项目名称是不是 wj-vue,直接回车确认就行。
这里还会问是否安装 vue-router,一定要选是,也就是回车或按 Y,vue-router 是我们构建单页面应用的关键。
还有是否使用 es-lint,选N。
接下来等待项目构建完成就 OK 了。
可以看到 workspace 目录下生成了项目文件夹 需要在该文件夹执行 npm install ,npm run build 再执行 npm run dev
访问 ,查看网页 demo,大工告成!
注:在vue项目中,有的时候需要执行npm run serve启动项目,有的时候需要用npm run dev,具体有什么不一样呢?
区别
dev默认是-cli@2.x默认支持的命令;
serve默认是vue-cli@3.x及以上版本默认支持的命令。
3.3 vue项目结构分析
- ├── build --------------------------------- 项目构建(webpack)相关配置文件,配置参数什么的,一般不用动
-
- │ ├── build.js --------------------------webpack打包配置文件
-
- │ ├── check-versions.js ------------------------------ 检查npm,nodejs版本
-
- │ ├── dev-client.js ---------------------------------- 设置环境
-
- │ ├── dev-server.js ---------------------------------- 创建express服务器,配置中间件,启动可热重载的服务器,用于开发项目
-
- │ ├── utils.js --------------------------------------- 配置资源路径,配置css加载器
-
- │ ├── vue-loader.conf.js ----------------------------- 配置css加载器等
-
- │ ├── webpack.base.conf.js --------------------------- webpack基本配置
-
- │ ├── webpack.dev.conf.js ---------------------------- 用于开发的webpack设置
-
- │ ├── webpack.prod.conf.js --------------------------- 用于打包的webpack设置
-
- ├── config ---------------------------------- 配置目录,包括端口号等。我们初学可以使用默认的。
-
- │ ├── dev.env.js -------------------------- 开发环境变量
-
- │ ├── index.js ---------------------------- 项目配置文件
-
- │ ├── prod.env.js ------------------------- 生产环境变量
-
- │ ├── test.env.js ------------------------- 测试环境变量
-
- ├── node_modules ---------------------------- npm 加载的项目依赖模块
-
- ├── src ------------------------------------- 我们要开发的目录,基本上要做的事情都在这个目录里。
-
- │ ├── assets ------------------------------ 静态文件,放置一些图片,如logo等
-
- │ ├── components -------------------------- 组件目录,存放组件文件,可以不用。
-
- │ ├── main.js ----------------------------- 主js
-
- │ ├── App.vue ----------------------------- 项目入口组件,我们也可以直接将组件写这里,而不使用 components 目录。
-
- │ ├── router ------------------------------ 路由
-
- ├── static ---------------------------- 静态资源目录,如图片、字体等。
-
- ├── .babelrc--------------------------------- babel配置文件
-
- ├── .editorconfig---------------------------- 编辑器配置
-
- ├── .gitignore------------------------------- 配置git可忽略的文件
-
- ├── index.html ------------------------------ 首页入口文件,你可以添加一些 meta 信息或统计代码啥的。
-
- ├── package.json ---------------------------- node配置文件,记载着一些命令和依赖还有简要的项目描述信息
-
- ├── .README.md------------------------------- 项目的说明文档,markdown 格式。想怎么写怎么写,不会写就参照github上star多的项目,看人家怎么写的
主要文件详解
src——[项目核心文件]
在vue-cli的项目中,其中src文件夹是必须要掌握的,因为基本上要做的事情都在这个目录里。
index.html——[主页]
index.html如其他html一样,但一般只定义一个空的根节点,在main.js里面定义的实例将挂载在根节点下,内容都通过vue组件来填充,构建的文件将会被自动注入,也就是说我们编写的其它的内容都将在这个 div 中展示。整个项目只有这一个 html 文件,所以这是一个 单页面应用,当我们打开这个应用,表面上可以有很多页面,实际上它们都只不过在一个 div 中。
- <!DOCTYPE html>
-
- <html>
-
- <head>
-
- <meta charset="utf-8">
-
- <title>vuedemo</title>
-
- </head>
-
- <body>
-
- <!-- 定义的vue实例将挂载在#app节点下 -->
-
- <div id="app"></div>
-
- </body>
-
- </html>
App.vue——[根组件]
这个文件称为“根组件”,因为其它的组件又都包含在这个组件中。.vue 文件是一种自定义文件类型,在结构上类似 html,一个 .vue 文件即是一个 vue 组件。
一个vue页面通常由三部分组成:模板(template)、js(script)、样式(style)
<!-- 模板 -->
- <template><template>
-
- <div id="app">
-
- <img src="./assets/logo.png">
-
- <router-view></router-view>
-
- </div>
-
- </template>
-
- <!-- script -->
-
- <script>
-
- export default {
-
- name: 'app'
-
- }
-
- </script>
-
- <div id="app">
-
- <img src="./assets/logo.png">
-
- <router-view></router-view>
-
- </div>
-
- </template>
-
- <!-- script -->
-
- <script>
-
- export default {
-
- name: 'app'
-
- }
-
- </script>
<!-- 样式 -->
- <style>
-
- #app {
-
- font-family: 'Avenir', Helvetica, Arial, sans-serif;
-
- -webkit-font-smoothing: antialiased;
-
- -moz-osx-font-smoothing: grayscale;
-
- text-align: center;
-
- color: #2c3e50;
-
- margin-top: 60px;
-
- }
-
- </style>
【template】
其中模板只能包含一个父节点,也就是说顶层的div只能有一个(例如上面代码,父节点为#app的div,其没有兄弟节点)。这里也有一句 <div id="app">,但跟 index.html 里的那个是没有关系的。这个id=app 只是跟下面的 css 对应。
<router-view></router-view>是子路由视图,后面的路由页面都显示在此处。打一个比喻吧,<router-view>类似于一个插槽,跳转某个路由时,该路由下的页面就插在这个插槽中渲染显示
【script】
<script>标签里的内容即该组件的脚本,也就是 js 代码,export default 是 ES6 的语法,意思是将这个组件整体导出,之后就可以使用 import 导入组件了。大括号里的内容是这个组件的相关属性。
vue通常用es6来写,用export default导出,其下面可以包含数据data,生命周期(mounted等),方法(methods)等,具体语法请看vue.js文档。
【style】
样式通过style标签包裹,默认是影响全局的,如需定义作用域只在该组件下起作用,需在标签上加scoped.
如要引入外部css文件,首先需给项目安装css-loader依赖包,打开cmd,进入项目目录,输入npm install css-loader,回车。
安装完成后,就可以在style标签下import所需的css文件,例如:
- <style>
-
- import './assets/css/public.css'
-
- </style>
main.js——[入口文件]
main.js主要是引入vue框架,根组件及路由设置,并且定义vue实例,下面的 components:{App}就是引入的根组件App.vue。后期还可以引入插件,当然首先得安装插件。
前面我们说 App.vue 里的<div id="app"> 和 index.html 里的<div id="app"> 没有关系,那么这两个文件是怎么建立联系的呢?让我们来看入口文件 main.js 的代码
- /*引入vue框架*/
-
- import Vue from 'vue'
-
- /*引入根组件*/
-
- import App from './App'
-
- /*引入路由设置*/
-
- import router from './router'
-
- /*关闭生产模式下给出的提示*/
-
- Vue.config.productionTip = false
-
- /*定义实例*/
-
- new Vue({
-
- el: '#app',
-
- router,
-
- template: '<App/>',
-
- components: { App }
-
- })
最上面 import 了几个模块,其中 vue 模块在 node_modules 中,App 即 App.vue 里定义的组件,router 即 router 文件夹里定义的路由。
Vue.config.productionTip = false ,作用是阻止vue 在启动时生成生产提示。
在这个 js 文件中,我们创建了一个 Vue 对象(实例),el 属性提供一个在页面上已存在的 DOM 元素作为 Vue 对象的挂载目标,这里就通过index.html中的<div id="app"><div>中的id=“app”和这里的“#app”进行挂载。
router 代表该对象包含 Vue Router,并使用项目中定义的路由。components 表示该对象包含的 Vue 组件,template 是用一个字符串模板作为 Vue 实例的标识使用,类似于定义一个 html 标签。
3.4 安装 Element-UI
Element 的官方地址为
1.安装 Element
根据官方文档的描述,在项目文件夹下,执行 npm i element-ui -S 即可
在这里插入图片描述
2.引入 Element
引入分为完整引入和按需引入两种模式,按需引入可以缩小项目的体积,这里我们选择完整引入。
根据文档,我们需要修改 main.js 为如下内容
import ElementUI from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'
3.5 安装axios
进入到项目文件夹中,执行
npm install --save axios,以安装这个模块。
3.6 安装Vuex
Vuex,它是专门为 Vue 开发的状态管理方案,我们可以把需要在各个组件中传递使用的变量、方法定义在这里。之前我一直没有使用它,所以在不同组件传值的问题上十分头疼,要写很多多余的代码来调用不同组件的值,所以推荐大家从一开始就去熟悉这种管理方式。
运行 npm install vuex --save
之后,在 src 目录下新建一个文件夹 store,并在该目录下新建 index.js 文件,在该文件中引入 vue 和 vuex,代码如下:
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
安装vuex 启动 报错 “export ‘watch‘ was not found in ‘vue‘
如果你的vue版本是 2.X ,将vuex升到 3.X.X 就能够解决
npm install --save vuex@3.6.2
如果你的vue版本是 3.X ,将vuex升到 4.X.X 就能够解决
npm install --save vue@3.0.2
npm install --save vuex@4.0.0
解决版本冲突
可能是npm版本问题报错
解决方法:在命令后面加上
--legacy-peer-deps
3.7 安装VueRouter
npm install vue-router --save-dev
vue-router是Vue.js官方的路由插件,它和vue.js是深度集成的,适合用于构建单页面应用。vue的单页面应用是基于路由和组件的,路由用于设定访问路径,并将路径和组件映射起来。
router文件夹下,有一个index.js,即为路由配置文件。可以设置多个路由,‘/index’,’/list’之类的,当然首先得引入该组件,再为该组件设置路由。
3.8 安装font-awesome
npm install font-awesome
四、前端拦截器
4.1 配置登陆拦截器
顾名思义就是对请求的拦截,请求接口之前或之后的预处理工作。分别为请求拦截器和响应拦截器, 执行顺序: 请求拦截器 -> api请求 -> 响应拦截器。 拦截器的作用:a. 统计api从发起请求到返回数据需要的时间;b. 配置公共的请求头,加载弹窗等;c. 对响应状态码做拦截,比入后端返回400或500的状态码, 返回对应错误信息。
4.2 axios请求拦截器request
在vue项目中,我们通常使用与后台进行数据交互,axios是一款基于promise封装的库,可以运行在浏览器端和node环境中。请求拦截器request作用:在请求发送前统一执行某些操作,常用在请求头中处理token等
添加请求拦截器的方法
- axios.interceptors.request.use(function (config) {
-
- // 在发送请求之前做些什么
-
- return config;
-
- }, function (error) {
-
- // 对请求错误做些什么
-
- return Promise.reject(error);
-
- })
4.3 axios响应拦截器response
返回对象response中有response.status:Http响应码;response.data:后端返回的Json对象,包括response.data.code业务逻辑响应码,response.data.message:后端返回的响应提示信息;
添加响应拦截器方法
- axios.interceptors.response.use(function (response) {
-
- // 对响应数据做点什么
-
- return response;
-
- }, function (error) {
-
- // 对响应错误做点什么
-
- return Promise.reject(error);
-
- });
-
- }
4.4 封装请求
在项目中,我们并不会直接使用 axios,而是会对它进行一层封装。 通过export导出封装的请求,如定义一个postRequest方法接收url和params,然后axios对象。在axios里进行实际接口调用操作。
- export const postRequest = (url, params) => {
-
- return axios({
-
- method: 'post',
-
- url: `${base}${url}`,
-
- data: params
-
- })
-
- }
4.5 代码实现src/utils/api.js
- import axios from "axios";
-
- import {Message} from "element-ui";
-
- import router from "@/router";
-
- // 请求拦截器
-
- axios.interceptors.request.use(config => {
-
- // 如果存在 token,请求携带这个 token( 登录的时候 把 token 存入了 sessionStorage )
-
- if (window.sessionStorage.getItem("tokenStr")) {
-
- // token 的key : Authorization ; value: tokenStr
-
- config.headers['Authorization'] = window.sessionStorage.getItem('tokenStr')
-
- }
-
- return config;
-
- },error => {
-
- console.log(error)
-
- })
-
- // 响应拦截器 - 统一处理消息提示
-
- axios.interceptors.response.use(success => {
-
- // 业务逻辑错误
-
- if (success.status && success.status === 200) { // 调到接口
-
- // 后端:500 业务逻辑错误,401 未登录,403 无权访问;
-
- 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.code === 504 || error.response.code === 404) {
-
- Message.error({message: '服务器不存在'})
-
- } else if (error.response.code === 403) {
-
- Message.error({message: '权限不足,请联系管理员!'})
-
- } else if (error.response.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
-
- })
-
- }
-
- // 传送 json 格式的 get 请求
-
- export const getRequest = (url, params) => {
-
- return axios({
-
- method: 'get',
-
- url: `${base}${url}`,
-
- data: params
-
- })
-
- }
-
- // 传送 json 格式的 put 请求
-
- export const putRequest = (url, params) => {
-
- return axios({
-
- method: 'put',
-
- url: `${base}${url}`,
-
- data: params
-
- })
-
- }
-
- // 传送 json 格式的 delete 请求
-
- export const deleteRequest = (url, params) => {
-
- return axios({
-
- method: 'delete',
-
- url: `${base}${url}`,
-
- data: params
-
- })
-
- }
顾名思义就是对请求的拦截,分别为请求拦截器和响应拦截器, 执行顺序: 请求拦截器 -> api请求 -> 响应拦截器。 拦截器的作用:a. 统计api从发起请求到返回数据需要的时间;b. 配置公共的请求头,加载弹窗等;c. 对响应状态码做拦截,比入后端返回400或500的状态码, 返回对应错误信息。
4.6 main.js全局引入封装请求
通过main.js全局引入然后通过插件的方式使用方法。在具体调用时使用this.putRequest(url,params)形式使用
- import {postRequest} from "@/utils/api";
-
- import {putRequest} from "@/utils/api";
-
- import {getRequest} from "@/utils/api";
-
- import {deleteRequest} from "@/utils/api";
- Vue.prototype.postRequest = postRequest
-
- Vue.prototype.putRequest = putRequest
-
- Vue.prototype.getRequest = getRequest
-
- Vue.prototype.deleteRequest = deleteRequest
五、登陆页面
5.1 样式设计
为了设计界面,我们需要关注的地方是 <template> 标签内的 html 和 <style> 标签内的 css。登录框我们一般会用 Form 来做,打开 Element 的组件文档(),发现它为我们提供了丰富的 Form 组件,我们可以点击“显示代码”,复制我们需要的部分。
不过这里好像并没有特别符合我们应用场景的表单,或者说这些都是比较复杂的,我们只需要其中的一小部分。把页面再往下拉,可以看到关于这个组件的属性、事件、方法等的文档,根据这个文档,我们可以自己去构建需要的表单。
5.2 登陆页功能设计
5.3 Login.vue登录页
验证码通过后端返回图片。表单通过rules绑定规则,通过prop为元素添加属性,在rules里写规则。验证方式:this.$refs.loginForm.validate。
/captcha返回信息
/login登陆返回报文
登陆成功后会返回一个token。此token会作为后面前后端数据交互的一个凭证。为了保证系统安全性后端会定期更新该token,超过token的失效之后用户需要重新登录。前端将获取来的token存入sessionStorage中作为之后调用接口的钥匙,此后通过Axios进行get或者post请求时都需要带上此token。
在请求拦截器里判断toke是否存在,之后每次请求都会校验token,如果存在则请求携带token,放入Authorization参数中;后端校验token。
前端登陆成功后 通过this.$router.replace('/home') 跳转到home首页。replace方法替换后点击浏览器回退按钮不会跳转到登陆页面。登陆失败后端返回失败原因。
在用户未登陆的情况下,如果用户不是以访问登陆页,而是访问某个登陆后才能访问的路由,如。需要分情况讨论:1.用户可能输入首页地址或错误地址,登陆成功后让他跳到首页;2.否则成功跳转到他输入的地址。
this.$router.replace((path === '/' || path === undefined) ? '/home' : path)
- <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)"
-
- ref="loginForm" :model="loginForm" :rules="rules" class="loginContainer">
-
- <h3 class="loginTitle">系统登录</h3>
-
- <el-form-item prop="username">
-
- <el-input type="text" v-model="loginForm.username" placeholder="请输入用户名"></el-input>
-
- </el-form-item>
-
- <el-form-item prop="password">
-
- <el-input type="password" v-model="loginForm.password" placeholder="请输入密码"></el-input>
-
- </el-form-item>
-
- <el-form-item prop="code">
-
- <el-input type="text" v-model="loginForm.code" placeholder="点击图片更换验证码"
-
- style="width: 250px;margin-right: 5px;"></el-input>
-
- <img :src="captchaUrl" @click="updateCaptcha">
-
- </el-form-item>
-
- <el-button type="primary" style="width: 100%" @click="submitLogin">登录</el-button>
-
- </el-form>
-
- </div>
-
- </template>
-
- <script>
-
- export default {
-
- name: 'Login',
-
- components: {},
-
- props: [],
-
- data() {
-
- return {
-
- // 验证码
-
- captchaUrl:'/captcha?time=' + new Date(),//获取响应码后端接口
-
- loginForm: {
-
- username: 'admin',
-
- password: '123',
-
- code: '',
-
- },
-
- loading: false, // 加载中
-
- //校验规则,与表单绑定
-
- rules: {
-
- username: [{required: true, message: '请输入用户名', trigger: 'blur'}],
-
- password: [{required: true, message: '请输入密码', trigger: 'blur'}],
-
- code: [{required: true, message: '请输入验证码', trigger: 'blur'}]
-
- }
-
- }
-
- },
-
- mounted(){
-
-
-
- },
-
- 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 => {
-
- this.loading = false;//登录成功后关闭
-
- if (resp) {
-
- // 存储用户 token 到 sessionStorage
-
- const tokenStr = resp.obj.tokenHead + resp.obj.token;
-
- window.sessionStorage.setItem('tokenStr', tokenStr);
-
- // 跳转到首页
-
- // this.$router.push('/home') // 路由跳转,可以回退到上一页
-
- this.$router.replace('/home') // 路径替换,无法回退到上一页
-
-
-
- // 页面跳转
-
- // 拿到用户要跳转的路径
-
- let path = this.$route.query.redirect;
-
- // 用户可能输入首页地址或错误地址,让他跳到首页,否则跳转到他输入的地址
-
- this.$router.replace((path === '/' || path === undefined) ? '/home' : path)
-
- }
-
-
-
- })
-
- } else {
-
- this.$message.error('请输入所有字段!');
-
- return false;
-
- }
-
- })
-
- }
-
- }
-
- }
-
- </script>
-
- <style>
-
- .loginContainer {
-
- border-radius: 15px;
-
- background-clip: padding-box;
-
- /*属性规定背景的绘制区域 背景被裁剪到内边距框。 margin: 180 px auto;*/
-
- margin: 180px auto;
-
- width: 350px;
-
- padding: 15px 35px;
-
- background: #fff;
-
- border: 1px solid #eaeaea;
-
- box-shadow: 0 0 25px #cac6c6;
-
- /* X轴偏移量 Y轴偏移量 [阴影模糊半径] [阴影扩展] [阴影颜色] [投影方式]; */
-
- }
-
-
-
- .loginTitle {
-
- margin: 0 auto 40px auto;
-
- text-align: center;
-
- }
-
-
-
- .loginRemember {
-
- text-align: left;
-
- margin: 0 0 15px 0;
-
- }
-
-
-
- /*验证码*/
-
- .el-form-item__content {
-
- display: flex;
-
- align-items: center;
-
- }
-
- </style>
SessionStorage.setItem()中,为了使axios做下一次请求时获取token认证,登录之后拿到token放到sessionStrorage里
// 存储用户 token 到 sessionStorage
const tokenStr = resp.obj.tokenHead + resp.obj.token;
window.sessionStorage.setItem('tokenStr', tokenStr);
5.4 配置页面路由——router/index.js
- import Vue from 'vue'
-
- import Router from 'vue-router'
-
- import Login from "@/views/Login";
-
- Vue.use(Router)
-
- export default new Router({
-
- routes: [
-
- {
-
- path: '/',
-
- name: 'Login',
-
- component: Login,
-
- hidden: true // 不会被循环遍历出来
-
- },
-
- ]
-
- })
5.5 前端路由导航守卫
登录页面的开发似乎已经较为完善了,但其实还没有完,因为这个登录页面其实没有用,别人直接输入首页的网址,就可以绕过登录页面。为了让它发挥作用,我们还需要开发一个拦截器。使用钩子函数判断是否拦截函数及在某些时机会被调用的函数。这里我们使用 router.beforeEach(),意思是在访问每一个路由前调用。to 要去的路由; from 来自哪里的路由 ; next() 放行。
通过sessionStorage.getItem('user')获取用户的token,如果token不存在则需要登陆。
在判断是否为if (to.path == '/')登陆页,是的话放行,否则按用户指定的路由登陆;
main.js
- // 使用 router.beforeEach 注册一个全局前置守卫
-
- router.beforeEach((to, from, next) => {
-
- // to 要去的路由; from 来自哪里的路由 ; next() 放行
-
- // 用户登录成功时,把 token 存入 sessionStorage,如果携带 token,初始化菜单,放行
-
- if (window.sessionStorage.getItem('tokenStr')) {
-
- // 如果用户不存在
-
- //待首页功能部分完善后补充
-
- } else {
-
- if (to.path === '/') {
-
- next()
-
- } else {
-
- next('/?redirect=' + to.path)
-
- }
-
- }
-
- })
5.6 解决前后端跨域
前端端口默认8080,假设后端端口是8081,那8080如何访问到8081的数据,我们通过Node.js实现端口自动转发。浏览器的同源策略:两个页面必须具有相同的协议(protocol)主机(host)端口号(port)。同源策略是浏览器的一种安全机制,它是指浏览器会阻止对非同源页面的DOM操作以及XMLHttpRequest对象向非同源服务器发起http请求。请求一个接口时,出现Access-Control-Allow-Origin等,说明出现请求跨域了。vue中解决跨域的方法:配置vue.config.js文件,如果没有就自行新建一个。
原理:
1.将域名发送给本地的服务器(localhost:8080)
2.再由本地的服务器去请求真正的服务器
3.因为请求是从服务端发出的,所以不存在跨域的问题了。
在vue中是由node.js自动进行的
前端反向代理
vue.config.js
修改proxyTable 请求地址经过node.js后代理到后端地址8081
- proxyTable: {
-
- '/': {
-
- changeOrigin: true, //跨域
-
- target: 'http://localhost:8081',
-
- pathRewrite: {
-
- // '^/api': ''
-
- }
-
- },
-
-
-
- },
5.7 运行项目
六、首页页面
我们的项目虽然本质上是单页面应用,但表面上有多个功能页面。为了方便用户在这各个页面之间切换,我们需要添加一个导航栏。这个导航栏的要求很简单:
能够在每个页面显示
美观
为了实现第一个要求,我们需要把导航栏放在其它页面的父页面中(对 Vue 来说就是父组件),之前我们讲过,App.vue 是所有组件的父组件,但把导航栏放进去不合适,因为我们的登录页面中不应该显示导航栏。为了解决这个问题,我们在views目录下直接新建一个组件,命名为 Home.vue。和 App.vue 一样,写入了一个
<router-view/>,也就是子页面(组件)显示的地方。
Home.vue整体上实现了首页左侧菜单的获取和展示,右上角的个人中心的设置。从store.state中获取当前菜单信息、当前用户的登陆信息.。
6.1 菜单功能设计与实现
需要文件目录如下:views/emp基本资料 新建 EmpBasic.vue EmpAdv.vue
views/per 员工资料新建 PerEmp.vu PerEc.vue PerTrain.vue PerSalary.vue PerMv.vue
views/sal 工资账套 SalSob.vue SalSobcfg.vue SalTable.vue SalMonth.vue SalSearch.vue
views/sta 综合信息统计 新增StaAll.vue StaScore.vue StaPers.vue StaRecord.vue
views/sys 系统管理 新增 SysBasic.vue SysConfig.vue SysLog.vue SysAdmin.vue SysData.vue SysInit.vue
配置store/index.js
通过vuex进行路由状态管理
- import Vue from 'vue'
-
- import Vuex from 'vuex'
-
- Vue.use(Vuex)
-
- // 导入 Vuex
-
- const store = new Vuex.Store({
-
- state: {
-
- routes: []
-
- },
-
- mutations: { // 与 state 同步执行;可以改变 state 对应的值的方法
-
- // 初始化路由 菜单
-
- initRoutes(state, data) {
-
- state.routes = data
-
- },
-
- },
-
- // 异步执行
-
- actions: {
-
- }
-
- })
-
- export default store;
在Main.js中引入store
- import store from './store'
-
- new Vue({
-
- router,
-
- store,
-
- render: h => h(App)
-
- }).$mount('#app')
6.2 封装菜单请求工具
后端请求菜单接口返回信息
我们设计的菜单是根据用户信息加载的路由信息,即不同用户可能有不同的菜单权限。接口返回的菜单信息如下。通过children表示子菜单,子菜单中的parentId与父菜单的id相等时表示一个确定的父子菜单关系。如下的关系表示有一个层级菜单“员工资料/基本资料”。
如果store.state.routes有数据,初始化路由菜单。通过getRequest('/system/config/menu')方法从后端获取路由数据,按照层次关系拆分。
如何根据接口中的component字段找到对应的代码路径呢?
通过对接口对象中的component字段分类查找,例如component以Home开头,源代码在src/views/Home.vue中。
- if (component.startsWith('Home')) {
-
- require(['@/views/' + component + '.vue'], resolve);
-
- }
initMenu方法将路由数据存于store中,如果store中有数据则无需初始化,否则,初始化。
什么时候调用?每一个页面都需要调用初始化菜单方法。放在路由拦截器里,每次访问路由都要执行一次。
menus.js
- import {getRequest} from "@/utils/api";
-
- // 菜单请求工具类
-
- // router 路由; store Vuex
-
- export const initMenu = (router, store) => {
-
- // 如果有数据,初始化路由菜单
-
- if (store.state.routes.length > 0) {
-
- return;
-
- }
-
- getRequest('/system/config/menu').then(data => {
-
- // 如果数据存在 格式化路由
-
- if (data) {
-
- // 格式化好路由
-
- let fmtRoutes = formatRoutes(data)
-
- // 添加到 router
-
- router.addRoutes(fmtRoutes)
-
- // 将数据存入 Vuex
-
- store.commit('initRoutes',fmtRoutes)
-
- // 连接 WebSocket
-
- store.dispatch('connect')
-
- }
-
- })
-
- }
-
- export const formatRoutes = (routes) => {
-
- let fmtRoutes = []
-
- routes.forEach(router => {
-
- let {
-
- path,
-
- component,
-
- name,
-
- iconCls,
-
- children
-
- } = router;
-
- // 如果有 children 并且类型是数组
-
- if (children && children instanceof Array) {
-
- // 递归
-
- children = formatRoutes(children)
-
- }
-
- // 单独对某一个路由格式化 component
-
- let fmRouter = {
-
- path: path,
-
- name: name,
-
- iconCls: iconCls,
-
- children: 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
-
- }
更新main.js
获取当前用户登陆信息
将当前用户信息保存在sessionStorage的user中,每次路由切换时获取用户的登陆信息。
- // 使用 router.beforeEach 注册一个全局前置守卫
-
- router.beforeEach((to, from, next) => {
-
- // to 要去的路由; from 来自哪里的路由 ; next() 放行
-
- // 用户登录成功时,把 token 存入 sessionStorage,如果携带 token,初始化菜单,放行
-
- if (window.sessionStorage.getItem('tokenStr')) {
-
- initMenu(router, store)
-
- // 如果用户不存在
-
- if (!window.sessionStorage.getItem('user')
-
- ) {
-
- // 判断用户信息是否存在
-
- return getRequest('/admin/info').then(resp => {
-
- if (resp) {
-
- // 存入用户信息,转字符串,存入 sessionStorage
-
- window.sessionStorage.setItem('user', JSON.stringify(resp))
-
- // 同步用户信息 编辑用户
-
- store.commit('INIT_ADMIN',resp)
-
- next();
-
- }
-
- })
-
- }
-
- next();
-
- } else {
-
- if (to.path === '/') {
-
- next()
-
- } else {
-
- next('/?redirect=' + to.path)
-
- }
-
- }
-
- })
6.3 样式设计
登陆后的前端页面被分解为上方导航栏,左侧菜单栏和中间的主要功能区域,对于不同页面的切换,仅需要变化中间功能区域内容,提高了代码重用性。首先自定义页面各区域组件并将各组件文件保存到Views文件夹中,每个.vue文件都是单独的组件,路由中指定的组件通过导入语句<router-view></router-view>在页面中渲染。
布局使用了element-ui的container布局容器:el-container 外层容器;el-header 顶栏容器;el-aside 侧边栏容器 ;el-menu导航区域;el-main 主要区域容器;el-footer底栏容器
样式设计如下:
在el-menu导航里添加router属性实现菜单路由的动态渲染;首页导航菜单使用element-ui的NavMenu导航菜单控件。使用属性unique-opened:保证每次点击菜单只有一个菜单的展开。使用router属性,在激活导航时以 index 作为 path 进行路由跳转。
通过el-dropdown的@command点击菜单项触发的事件回调方法绑定el-dropdown-item中的command,实现注销登陆和进入个人中心功能。
elemet的MessageBox弹框实现注销登陆提示弹框。退出登陆后清除vuex中的菜单信息。
使用el-breadcrumb面包屑控件实现显示当前页面的路径,快速返回之前的任意页面功能。对于非首页的页面v-if="this.$router.currentRoute.path!=='/home'"显示层级:首先/当前页。
对于首页v-if="this.$router.currentRoute.path==='/home'",显示欢迎字体。
6.4 Home.vue代码
- <template>
-
- <div>
-
- <el-container>
-
- <el-header class="homeHeader">
-
- <div class="title">云办公</div>
-
- <!-- 1-1 添加在线聊天入口 -->
-
- <div>
-
- <el-button type="text" icon="el-icon-bell" size="normal"
-
- style="margin-right: 8px;color: black;" @click="goChar"></el-button>
-
- <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="userinfo">个人中心</el-dropdown-item>
-
- <el-dropdown-item command="setting">设置</el-dropdown-item>
-
- <el-dropdown-item command="logout">注销登录</el-dropdown-item>
-
- </el-dropdown-menu>
-
- </el-dropdown>
-
- </div>
-
- </el-header>
-
- <el-container>
-
- <el-aside width="200px">
-
- <!-- 1、添加 router -->
-
- <el-menu router unique-opened>
-
- <!-- 2、循环整个路由组件,不展示 hidden: true 的路由组件 -->
-
- <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: black;margin-right: 5px"></i>
-
- <span>{{ item.name }}</span>
-
- </template>
-
- <!-- 3、循环遍历子路由 -->
-
- <el-menu-item :index="children.path"
-
- v-for="(children,index) in item.children" :key="index">{{ children.name }}
-
- </el-menu-item>
-
- </el-submenu>
-
- </el-menu>
-
- </el-aside>
-
- <el-main>
-
- <!-- 面包屑导航区域 -->
-
- <el-breadcrumb separator-class="el-icon-arrow-right"
-
- v-if="this.$router.currentRoute.path!=='/home'">
-
- <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'">
-
- 欢迎来到云办公系统!
-
- </div>
-
- <!-- 路由点位符 -->
-
- <router-view class="homeRouterView"/>
-
- </el-main>
-
- </el-container>
-
- </el-container>
-
- </div>
-
- </template>
-
- <script>
-
- export default {
-
- name: 'Home',
-
- data() {
-
- return {
-
- // 获取用户信息,将字符串转对象
-
- // user: JSON.parse(window.sessionStorage.getItem('user'))
-
- }
-
- },
-
- computed: {
-
- // 从 vuex 获取 routes
-
- routes() {
-
- return this.$store.state.routes
-
- },
-
- user() {
-
- return this.$store.state.currentAdmin
-
- }
-
- },
-
- methods: {
-
- // 1-2 进入在线聊天页面
-
- goChar() {
-
- this.$router.push('/chat')
-
- },
-
- // 注销登录
-
- commandHandler(command) {
-
- if (command === 'logout') {
-
- // 弹框提示用户是否要删除
-
- this.$confirm('此操作将注销登录, 是否继续?', '提示', {
-
- confirmButtonText: '确定',
-
- cancelButtonText: '取消',
-
- type: 'warning'
-
- }).then(() => {
-
- // 注销登录
-
- this.postRequest('/logout')
-
- // 清空用户信息
-
- window.sessionStorage.removeItem('tokenStr')
-
- window.sessionStorage.removeItem('user')
-
- // 路由替换到登录页面
-
- // this.$router.replace('/')
-
- // 清空菜单信息;在src/utils/menus.js 中初始化菜单信息
-
- this.$store.commit('initRoutes', [])
-
- this.$router.replace('/')
-
- }).catch(() => {
-
- this.$message({
-
- type: 'info',
-
- message: '已取消注销登录'
-
- });
-
- });
-
- }
-
- if (command === 'userinfo') {
-
- this.$router.push('/userinfo')
-
- }
-
- }
-
- }
-
- }
-
- </script>
-
- <style scoped>
-
- .homeHeader {
-
- background: #3e9ef5;
-
- display: flex;
-
- align-items: center;
-
- justify-content: space-between;
-
- padding: 0 15px;
-
- box-sizing: border-box;
-
- }
-
- .homeHeader .title {
-
- font-size: 30px;
-
- /*font-family: 微软雅黑;*/
-
- font-family: 华文楷体;
-
- color: white;
-
- }
-
- .homeHeader .userInfo {
-
- cursor: pointer;
-
- }
-
- .el-dropdown-link img {
-
- width: 48px;
-
- height: 48px;
-
- border-radius: 50%;
-
- margin-left: 8px;
-
- }
-
- .homeWelcome {
-
- text-align: center;
-
- font-size: 30px;
-
- font-family: 华文楷体;
-
- color: #409ef4;
-
- padding-top: 50px;
-
- }
-
- .homeRouterView {
-
- margin-top: 10px;
-
- }
-
- </style>
忽略hidden属性
<!-- 2、循环整个路由组件,不展示 hidden: true 的路由组件 -->
<el-submenu :index="index +''" v-for="(item,index) in routes"
:key="index" v-if="!item.hidden">
6.5 更新路由router/index.js
忽略router/index.js的hidden:true的
/home路由从首页获取
- import Vue from 'vue'
-
- import VueRouter from 'vue-router'
-
- import Login from "@/views/Login";
-
- Vue.use(VueRouter)
-
- const routes = [
-
- {
-
- path: '/',
-
- name: 'Login',
-
- component: Login,
-
- hidden: true // 不会被循环遍历出来
-
- }
-
- ]
-
- const router = new VueRouter({
-
- routes
-
- })
-
- export default router
6.6 index.html消除边距
添加样式
- <!DOCTYPE html>
-
- <html>
-
- <head>
-
- <meta charset="utf-8">
-
- <meta name="viewport" content="width=device-width,initial-scale=1.0">
-
- <title>yeb-front</title>
-
- </head>
-
- <body style="margin:0px;padding:0px">
-
- <div id="app"></div>
-
- <!-- built files will be auto injected -->
-
- </body>
-
- </html>
七、基础信息设置
7.1 样式设计
系统管理/基础信息设置设计如下几个模块:部门管理、职位管理、职称管理、奖惩规则、权限组
Tabs 标签页
使用element的Tabs标签页完成不同业务功能的切换;分隔内容上有关联但属于不同类别的数据集合。
abs 组件提供了选项卡功能,默认选中第一个标签页,你也可以通过 value 属性来指定当前选中的标签页。
- <template>
-
- <el-tabs v-model="activeName" @tab-click="handleClick">
-
- <el-tab-pane label="用户管理" name="first">用户管理</el-tab-pane>
-
- <el-tab-pane label="配置管理" name="second">配置管理</el-tab-pane>
-
- <el-tab-pane label="角色管理" name="third">角色管理</el-tab-pane>
-
- <el-tab-pane label="定时任务补偿" name="fourth">定时任务补偿</el-tab-pane>
-
- </el-tabs>
-
- </template>
-
- <script>
-
- export default {
-
- data() {
-
- return {
-
- activeName: 'second'
-
- };
-
- },
-
- methods: {
-
- handleClick(tab, event) {
-
- console.log(tab, event);
-
- }
-
- }
-
- };
-
- </script>
7.2 组件化开发
在我们开发的过程中会遇到很多可以重复使用的代码块,而Vue则提供了这样的封装方式也就是Vue.。利用组件化开发,将部门管理、职位管理、职称管理、奖惩规则、权限组等使用组件方式引入。组件也是.vue文件。组件导入方式 import 组件名 from “组件路径”,示例:
import DepMana from "@/components/sys/basic/DepMana";导入后并不能直接使用,需要在components对象中注册组件。之后是组件的应用:<DepMana/>或<DepMana></DepMana/>
组件目录
7.3 SysBasic.vue
- <template>
-
- <div>
-
- <el-tabs v-model="activeName" type="card">
-
- <el-tab-pane label="部门管理" name="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="PositionMana"><PositionMana/></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 PositionMana from "@/components/sys/basic/PositionMana"; // 权限组
-
- import PosMana from "@/components/sys/basic/PosMana"; // 职位管理
-
- export default {
-
- name: "SysBasic",
-
- components:{
-
- JobLevelMana,
-
- DepMana,
-
- EcMana,
-
- PositionMana,
-
- PosMana
-
- },
-
- data() {
-
- return {
-
- activeName: 'DepMana' // 激活项
-
- }
-
- },
-
- methods: {}
-
- }
-
- </script>
-
- <style scoped>
-
- </style>
7.4 部门管理DepMana.vue组件
为了使用方便,一次性加载所有的部门。
样式设计:flex布局,space-between:两端对齐,与父元素对齐宽度。
搜索框过滤节点的方法,通过filterNode方法传入两个参数,value-搜索输入的值,data树形标签绑定的数据。value为空时展示所有标签值,否则,判断输入的value能否在data.name(展示在树形标签上的值)找到,这里用了js查找字符串方法indexOf,返回查找元素的下标,能找到返回下标(>=0)
expand-on-click-node仅当鼠标点击展开按钮时展开,方便添加和删除功能的使用。
点击按钮时获取当前节点的数据即要添加子部门的“上级部门”id(parentId)
- <template>
-
- <div style="width: 500px">
-
- <!-- 1 -->
-
- <el-input
-
- placeholder="请输入部门名称进行搜索..."
-
- prefix-icon="el-icon-search"
-
- v-model="filterText">
-
- </el-input>
-
- <!-- 9、:expand-on-click-node="false" 点击小三角箭头才会展开
- :default-expand-all="false" 设置默认不展开所有节点 -->
-
- <el-tree
-
- :data="deps"
-
- :props="defaultProps"
-
- default-expand-all
-
- :filter-node-method="filterNode"
-
- :expand-on-click-node="false"
-
- ref="tree">
-
- <!-- 7、label: 'name' -->
-
- <!-- 8、style="display: flex;justify-content: space-between;width: 100% 父容器宽度" 让添加和删除按键居右 -->
-
- <span class="custom-tree-node" slot-scope="{ node, data }"
-
- style="display: flex;justify-content: space-between;width: 100%">
-
- <span>{{ data.name }}</span>
-
- <span>
-
- <el-button
-
- plain
-
- type="primary"
-
- size="mini"
-
- class="depBtn"
-
- @click="() => showAddDep(data)">
-
- 添加部门
-
- </el-button>
-
- <!-- 10、showAddDep(data) deleteDep(data) data 后端传过来的完整的 json 对象 -->
-
- <el-button
-
- plain
-
- type="danger"
-
- size="mini"
-
- class="depBtn"
-
- @click="() => deleteDep(data)">
-
- 删除部门
-
- </el-button>
-
- </span>
-
- </span>
-
- </el-tree>
-
- <!-- 13、对话弹框 -->
-
- <el-dialog
-
- title="添加部门"
-
- :visible.sync="dialogVisible"
-
- width="30%">
-
- <!-- 16 -->
-
- <div>
-
- <table>
-
- <tr>
-
- <td>
-
- <el-tag>上级部门</el-tag>
-
- </td>
-
- <td>{{ pname }}</td>
-
- </tr>
-
- <tr>
-
- <td>
-
- <el-tag>部门名称</el-tag>
-
- </td>
-
- <td>
-
- <el-input v-model="dep.name" placeholder="请输入部门名称..." size="small"></el-input>
-
- </td>
-
- </tr>
-
- </table>
-
- </div>
-
- <span slot="footer" class="dialog-footer">
-
- <el-button @click="dialogVisible = false">取 消</el-button>
-
- <!-- 18、确定添加按钮绑定事件 @click="doAddDep" -->
-
- <el-button type="primary" @click="doAddDep">确 定</el-button>
-
- </span>
-
- </el-dialog>
-
- </div>
-
- </template>
-
- <script>
-
- export default {
-
- name: "DepMana",
-
- data() {
-
- return { // 2
-
- filterText: '',
-
- deps: [], // 所有部门整个数组
-
- defaultProps: { // 2 关联子部门
-
- children: 'children',
-
- label: 'name'
-
- },
-
- dialogVisible: false, // 14
-
- dep: { // 15、添加部门数据象
-
- name: '',
-
- parentId: -1,
-
- isParent: ''
-
- },
-
- pname: '' // 15、上级部门名称
-
- }
-
- },
-
- watch: {
-
- // 4、观察者事件,监控输入框的值(框架方法)
-
- filterText(val) {
-
- this.$refs.tree.filter(val);
-
- }
-
- },
-
- mounted() {
-
- this.initDeps() // 6、调用获取所有部门方法
-
- },
-
- methods: {
-
- // 删除部门调用的方法
-
- removeDepFromDeps(p, deps, id) {
-
- for (let i = 0; i < deps.length; i++) {
-
- let d = deps[i]
-
- if (d.id === id) {
-
- deps.splice(i, 1)
-
- if (deps.length === 0) {
-
- p.isParent = false
-
- }
-
- return;
-
- } else {
-
- this.removeDepFromDeps(d, d.children, id)
-
- }
-
- }
-
- },
-
- // 12、删除部门
-
- deleteDep(data) {
-
- // console.log(data)
-
- if (data.isParent) {
-
- this.$message.error('父部门删除失败!')
-
- } else {
-
- this.$confirm('此操作将永久删除该[' + data.name + ']部门, 是否继续?', '提示', {
-
- confirmButtonText: '确定',
-
- cancelButtonText: '取消',
-
- type: 'warning'
-
- }).then(() => {
-
- this.deleteRequest('/system/basic/department/' + data.id).then(resp => {
-
- if (resp) {
-
- this.removeDepFromDeps(null, this.deps, data.id)
-
- }
-
- })
-
- }).catch(() => {
-
- this.$message({
-
- type: 'info',
-
- message: '已取消删除'
-
- });
-
- });
-
- }
-
- },
-
- // 20、添加完部门 初始化 清空数据
-
- initDep() {
-
- this.dep = {
-
- name: '',
-
- parentId: -1
-
- }
-
- this.pname = ''
-
- },
-
- // 22、 递归查询所有部门信息,deps 查询到的整个数组,dep 添加的部门
-
- addDep2Deps(deps, dep) {
-
- for (let i = 0; i < deps.length; i++) {
-
- let d = deps[i] // 父部门
-
- if (d.id === dep.parentId) {
-
- d.children = d.children.concat(dep) // 把 dep 加为 d 的子部门
-
- if (d.children.length > 0) {
-
- d.isParent = true
-
- }
-
- return;
-
- } else {
-
- this.addDep2Deps(d.children, dep) // 递归调用此方法 以查询结果为条件 继续查询子部门
-
- }
-
- }
-
- },
-
- // 19、确认添加部门
-
- doAddDep() {
-
- this.postRequest('/system/basic/department/', this.dep).then(resp => {
-
- if (resp) {
-
- // console.log(resp)
-
- this.dialogVisible = false // 关闭对话框
-
- this.addDep2Deps(this.deps, resp.data) // 23、【无效】手动插入部门 显示添加后的数据
-
- this.initDep() // 21、调用初始化方法 清空数据
-
- }
-
- })
-
- },
-
- // 11、17、添加部门弹框
-
- showAddDep(data) {
-
- // console.log(data)
-
- this.dep.parentId = data.id
-
- this.pname = data.name
-
- this.dialogVisible = true
-
- },
-
- // 5、获取所有部门
-
- initDeps() {
-
- this.getRequest('/system/basic/department/').then(resp => {
-
- if (resp) {
-
- this.deps = resp
-
- }
-
- })
-
- },
-
- // 3、事件(框架方法)
-
- filterNode(value, data) { // data 整行数据
-
- if (!value) return true; // true 节点可以展示,false 节点隐藏
-
- return data.name.indexOf(value) !== -1; // label: 'name'
-
- }
-
- }
-
- }
-
- </script>
-
- <style scoped>
-
- /* 8 */
-
- .depBtn {
-
- padding: 2px;
-
- }
-
- </style>
7.5 职位管理PosMana.vue组件
element的表格控件,第一列做多选框,将其type="selection"。表格绑定多选事件@selection-change="handleSelectionChange"
通过el-dialog编辑按钮修改职位名称。绑定弹出框显示方法:visible.sync="dialogVisible",取消时dialogVisible=false,点击“编辑”时dialogVisible=true。
批量删除。通过multipleSelection数组获取多选数据,multipleSelection为空时,批量删除按钮禁用。提示框内通过multipleSelection展示多选信息
接口数据返回信息
PosMana.vue
键盘事件@keydown.enter.native="addPosition"输入后自动调用”添加“按钮绑定的方法。
编辑职位信息后:刷新列表数据,关闭弹框。
使用数据的拷贝Object.assign(this.updatePos, data),将data中数据赋值给updatePos,避免浅拷贝引发的updatePos对data的数据修改。
- <template>
-
- <div>
-
- <div>
-
- <el-input
-
- size="small"
-
- class="addPosInput"
-
- placeholder="请选择日期"
-
- suffix-icon="el-icon-plus"
-
- @keydown.enter.native="addPosition"
-
- v-model="pos.name">
-
- </el-input>
-
- <el-button size="small" icon="el-icon-plus" type="primary" @click="addPosition">添加</el-button>
-
- </div>
-
- <div class="posManaMain">
-
- <el-table
-
- border
-
- stripe
-
- size="small"
-
- :data="positions"
-
- style="width: 70%"
-
- @selection-change="handleSelectionChange">
-
- <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 label="操作">
-
- <template slot-scope="scope">
-
- <el-button
-
- size="mini"
-
- @click="showEditView(scope.$index, scope.row)">编辑
-
- </el-button>
-
- <el-button
-
- size="mini"
-
- type="danger"
-
- @click="handleDelete(scope.$index, scope.row)">删除
-
- </el-button>
-
- </template>
-
- </el-table-column>
-
- </el-table>
-
- </div>
-
- <!-- :disabled 不禁用条件为勾选中,没勾选中为禁用。 -->
-
- <el-button size="small" style="margin-top: 8px" type="danger"
-
- :disabled="this.multipleSelection.length===0" @click="deleteMany">批量删除
-
- </el-button>
-
- <el-dialog
-
- title="提示"
-
- :visible.sync="dialogVisible"
-
- width="30%">
-
- <div>
-
- <el-tag>职位名称</el-tag>
-
- <el-input v-model="updatePos.name" size="small" class="updatePosInput"></el-input>
-
- </div>
-
- <span slot="footer" class="dialog-footer">
-
- <el-button size="small" @click="dialogVisible = false">取 消</el-button>
-
- <el-button size="small" type="primary" @click="doUpdate">确 定</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: {
-
- // 批量删除请求
-
- 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: '已取消删除'
-
- });
-
- });
-
- },
-
- // 批量删除(取值)
-
- handleSelectionChange(val) {
-
- this.multipleSelection = val
-
- // console.log(val)
-
- },
-
- // 编辑职位
-
- doUpdate() {
-
- this.putRequest('/system/basic/pos/', this.updatePos).then(resp => {
-
- if (resp) {
-
- this.initPositions() // 刷新数据列表
-
- this.dialogVisible = false // 关闭对话框
-
- }
-
- })
-
- },
-
- // 编辑职位对话框
-
- showEditView(index, data) {
-
- Object.assign(this.updatePos, data) // 回显数据,数据拷贝
-
- // this.updatePos = data // 回显数据 有bug
-
- this.updatePos.createDate = ''
-
- this.dialogVisible = true // 显示编辑框
-
- },
-
- // 添加职位
-
- addPosition() {
-
- if (this.pos.name) {
-
- this.postRequest('/system/basic/pos/', this.pos).then(resp => {
-
- if (resp) {
-
- this.initPositions()
-
- this.pos.name = ''
-
- }
-
- })
-
- } else {
-
- this.$message.error('职位名称不能为空!')
-
- }
-
- },
-
- // 删除职位
-
- 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: '已取消删除'
-
- });
-
- });
-
- },
-
- // 获取后端接口所有职位数据
-
- initPositions() {
-
- this.getRequest('/system/basic/pos/').then(resp => {
-
- if (resp) {
-
- this.positions = resp
-
- }
-
- })
-
- }
-
- }
-
- }
-
- </script>
-
- <style scoped>
-
- /*添加职位输入框*/
-
- .addPosInput {
-
- width: 300px;
-
- margin-right: 8px;
-
- }
-
- /*所有数据表格*/
-
- .posManaMain {
-
- margin-top: 10px;
-
- }
-
- /*编号职位输入框*/
-
- .updatePosInput {
-
- width: 200px;
-
- margin-left: 8px;
-
- }
-
- </style>
7.6 职称管理JobLevelMana.vue组件
职称管理实现了职称的添加、更新、单条删除、批量删除功能。
单条更新里是否启用按钮使用了element的开关控件
JobLevelMana.vue
- <template>
-
- <div>
-
- <div>
-
- <el-input size="small" v-model="jl.name" placeholder="添加职称名称..."
-
- prefix-icon="el-icon-plus" style="width: 300px"></el-input>
-
- <el-select size="small" v-model="jl.titleLevel" placeholder="职称等级" style="margin-left: 6px;margin-right: 6px">
-
- <el-option
-
- v-for="item in titleLevels"
-
- :key="item"
-
- :label="item"
-
- :value="item">
-
- </el-option>
-
- </el-select>
-
- <el-button type="primary" icon="el-icon-plus" size="small" @click="addJobLevel">添加</el-button>
-
- </div>
-
- <div style="margin-top: 10px;">
-
- <el-table
-
- :data="jls"
-
- stripe
-
- border
-
- size="small"
-
- style="width: 70%;"
-
- @selection-change="handleSelectionChange">
-
- <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="150">
-
- </el-table-column>
-
- <el-table-column
-
- prop="titleLevel"
-
- label="职称等级"
-
- width="150">
-
- </el-table-column>
-
- <el-table-column
-
- prop="createDate"
-
- label="创建日期"
-
- width="150">
-
- </el-table-column>
-
- <el-table-column
-
- prop="enabled"
-
- label="是否启用"
-
- width="100">
-
- <template slot-scope="scope">
-
- <el-tag type="success" v-if="scope.row.enabled">已启用</el-tag>
-
- <el-tag type="danger" v-else>未启用</el-tag>
-
- </template>
-
- </el-table-column>
-
- <el-table-column label="操作" width="350">
-
- <template slot-scope="scope">
-
- <el-button
-
- size="small"
-
- @click="showEditView(scope.row)">编辑
-
- </el-button>
-
- <el-button
-
- size="small"
-
- type="danger"
-
- @click="deleteHandle(scope.row)">删除
-
- </el-button>
-
- </template>
-
- </el-table-column>
-
- </el-table>
-
- <el-button size="small" style="margin-top: 8px" type="danger"
-
- :disabled="this.multipleSelection.length===0" @click="deleteMany">批量删除
-
- </el-button>
-
- </div>
-
- <!-- 编辑弹框 -->
-
- <el-dialog
-
- title="编辑职称"
-
- :visible.sync="dialogVisible"
-
- width="30%">
-
- <table>
-
- <tr>
-
- <td>
-
- <el-tag>职称名称</el-tag>
-
- </td>
-
- <td>
-
- <el-input v-model="updateJl.name" size="small" style="margin-left: 6px"></el-input>
-
- </td>
-
- </tr>
-
- <tr>
-
- <td>
-
- <el-tag>职称等级</el-tag>
-
- </td>
-
- <td>
-
- <el-select size="small" v-model="updateJl.titleLevel" placeholder="职称等级"
-
- style="margin-left: 6px;margin-right: 6px">
-
- <el-option
-
- v-for="item in titleLevels"
-
- :key="item"
-
- :label="item"
-
- :value="item">
-
- </el-option>
-
- </el-select>
-
- </td>
-
- </tr>
-
- <tr>
-
- <td>
-
- <el-tag>是否启用</el-tag>
-
- </td>
-
- <td>
-
- <el-switch
-
- style="margin-left: 6px"
-
- v-model="updateJl.enabled"
-
- active-color="#13ce66"
-
- inactive-color="#ff4949"
-
- active-text="启用"
-
- inactive-text="未启用">
-
- </el-switch>
-
- </td>
-
- </tr>
-
- </table>
-
- <span slot="footer" class="dialog-footer">
-
- <el-button @click="dialogVisible = false">取 消</el-button>
-
- <el-button type="primary" @click="doUpdate">确 定</el-button>
-
- </span>
-
- </el-dialog>
-
- </div>
-
- </template>
-
- <script>
-
- export default {
-
- name: "JobLevelMana",
-
- data() {
-
- return {
-
- // 查询 添加 数据对象
-
- jl: {
-
- name: '',
-
- titleLevel: ''
-
- },
-
- // 更新 数据对象
-
- updateJl: {
-
- name: '',
-
- titleLevel: '',
-
- enabled: false
-
- },
-
- titleLevels: [
-
- '正高级',
-
- '副高级',
-
- '中级',
-
- '初级',
-
- '员级'
-
- ],
-
- jls: [], // 删除单条
-
- dialogVisible: false,
-
- multipleSelection: [] // 批量删除勾选中的值
-
- }
-
- },
-
- mounted() {
-
- this.initJls()
-
- },
-
- methods: {
-
- // 执行批量删除
-
- 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/joblevel/' + ids).then(resp => {
-
- if (resp) {
-
- this.initJls()
-
- }
-
- })
-
- }).catch(() => {
-
- this.$message({
-
- type: 'info',
-
- message: '已取消删除'
-
- });
-
- });
-
- },
-
- // 批量删除勾选中的值
-
- handleSelectionChange(val){
-
- this.multipleSelection = val
-
- },
-
- // 更新职称信息
-
- doUpdate() {
-
- this.putRequest('/system/basic/joblevel/', this.updateJl).then(resp => {
-
- if (resp) {
-
- this.initJls()
-
- this.dialogVisible = false
-
- }
-
- })
-
- },
-
- // 显示编辑弹框
-
- showEditView(data) {
-
- Object.assign(this.updateJl, data) // 复制数据,注意这里是 , 号隔开
-
- this.updateJl.createDate = '' // 更新日期由后端处理,这里不用传
-
- this.dialogVisible = true // 显示编辑弹框
-
- },
-
- // 删除职称
-
- deleteHandle(data) {
-
- this.$confirm('此操作将永久删除[' + data.name + ']职称, 是否继续?', '提示', {
-
- confirmButtonText: '确定',
-
- cancelButtonText: '取消',
-
- type: 'warning'
-
- }).then(() => {
-
- this.deleteRequest(' /system/basic/joblevel/' + data.id).then(resp => {
-
- if (resp) {
-
- this.initJls()
-
- }
-
- })
-
- }).catch(() => {
-
- this.$message({
-
- type: 'info',
-
- message: '已取消删除'
-
- });
-
- });
-
- },
-
- // 添加职称
-
- addJobLevel() {
-
- if (this.jl.name && this.jl.titleLevel) {
-
- this.postRequest('/system/basic/joblevel/', this.jl).then(resp => {
-
- if (resp) {
-
- this.initJls()
-
- }
-
- })
-
- } else {
-
- this.$message.error('字段不能为空!')
-
- }
-
- },
-
- // 获取职称列表数据
-
- initJls() {
-
- this.getRequest('/system/basic/joblevel/').then(resp => {
-
- if (resp) {
-
- this.jls = resp
-
- this.jl.name = ''
-
- this.jl.titleLevel = ''
-
- }
-
- })
-
- }
-
- }
-
- }
-
- </script>
-
- <style scoped>
-
- </style>
7.7 权限组PositionMana.vue组件
样式设计:由外到内依次使用了el-collapse折叠面板-》el-card卡片-》el-tree树形控件
<template slot="prepend">ROLE_</template>
使用element折叠面板展示角色,使用的是折叠面板的手风琴模式accordion,每次只能展开一个面板。
后端获取所有用户角色接口
与接口返回的nameZh角色字段绑定,展示在折叠面板上
折叠面板每个角色的菜单访问权限,采用树形控件设置角色权限。el-tree添加show-checkbox属性展示可选框。
默认选中的角色菜单列表根据角色rid获取后端接口返回的菜单selectedMenus。通过:default-checked-keys方式绑定。
使用getCheckedKeys获取选中节点key组成的数组。let selectedKeys = tree.getCheckedKeys(true) // 获取选中的节点数组,true 仅返回被选中的叶子节点的 keys,如三级分类
- <template>
-
- <div>
-
- <div class="positionManaTool">
-
- <el-input v-model="role.name" placeholder="请输入角色英文名" size="small">
-
- <template slot="prepend">ROLE_</template>
-
- </el-input>
-
- <el-input v-model="role.nameZh" placeholder="请输入角色中文名" size="small" @keydown.enter.native="doAddRole"></el-input>
-
- <el-button type="primary" icon="el-icon-plus" size="mini" @click="doAddRole">添加角色</el-button>
-
- </div>
-
- <!-- 手风琴 -->
-
- <div class="positionManaMain">
-
- <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 type="text" icon="el-icon-delete" style="float: right;padding: 3px 0;color: #f41f0a" @click="doDeleteRole(r)">
-
- </el-button>
-
- </div>
-
- <div>
-
- <el-tree ref="treeRef" show-checkbox :data="allMenus" :props="defaultProps"
-
- :default-checked-keys="selectedMenus"
-
- node-key="id" :key="index"></el-tree>
-
- <div style="display: flex;justify-content: flex-end">
-
- <el-button size="mini" @click="cancelUpdate">取消修改</el-button>
-
- <el-button size="mini" type="primary" @click="doUpdate(r.id,index)">确认修改</el-button>
-
- </div>
-
- </div>
-
- </el-card>
-
- </el-collapse-item>
-
- </el-collapse>
-
- </div>
-
- </div>
-
- </template>
-
- <script>
-
- export default {
-
- name: "PositionMana",
-
- data() {
-
- return {
-
- role: {
-
- name: '',
-
- nameZh: ''
-
- },
-
- roles: [],
-
- allMenus: [],
-
- defaultProps: { // 树形控件
-
- children: 'children',
-
- label: 'name' // 绑定数据 :name="r.id"
-
- },
-
- selectedMenus: [],
-
- activeName: -1 // 折叠面板 默认关闭
-
- }
-
- },
-
- mounted() {
-
- this.initRoles()
-
- this.initAllMenus()
-
- },
-
- methods: {
-
- // 删除角色
-
- doDeleteRole(role){
-
- this.$confirm('此操作将永久删除[' + role.nameZh + ']角色, 是否继续?', '提示', {
-
- confirmButtonText: '确定',
-
- cancelButtonText: '取消',
-
- type: 'warning'
-
- }).then(() => {
-
- this.deleteRequest('/system/basic/permission/role/' + role.id).then(resp => {
-
- if (resp) {
-
- this.initRoles()
-
- }
-
- })
-
- }).catch(() => {
-
- this.$message({
-
- type: 'info',
-
- message: '已取消删除'
-
- });
-
- });
-
- },
-
- // 添加角色
-
- doAddRole(){
-
- if (this.role.name && this.role.nameZh) {
-
- this.postRequest('/system/basic/permission/role',this.role).then(resp=>{
-
- if (resp) {
-
- this.initRoles()
-
- this.role.name = ''
-
- this.role.nameZh = ''
-
- }
-
- })
-
- }else {
-
- this.$message.error('所有字段不能为空!')
-
- }
-
- },
-
- // 取消修改
-
- cancelUpdate() {
-
- this.activeName = -1 // 关闭折叠面板
-
- },
-
- // 确认修改
-
- doUpdate(rid, index) {
-
- let tree = this.$refs.treeRef[index] // 获取引用对象和索引
-
- let selectedKeys = tree.getCheckedKeys(true) // 获取选中的节点数组,true 仅返回被选中的叶子节点的 keys,如三级分类
-
- // console.log(selectedKeys)
-
- let url = '/system/basic/permission/?rid=' + rid
-
- selectedKeys.forEach(key => {
-
- // 循环遍历出数组 id ,拼接在一起
-
- url += '&mids=' + key
-
- })
-
- this.putRequest(url).then(resp => {
-
- if (resp) {
-
- this.activeName = -1 // 关闭折叠面板
-
- }
-
- })
-
- },
-
- // 手风琴点击事件
-
- change(rid) {
-
- if (rid) {
-
- this.initAllMenus() // 调用获取所有菜单
-
- this.initSelectedMenus(rid) // 调用获取所有选中的菜单
-
- // alert(rid) // :name="r.id" label: 'name'
-
- }
-
- },
-
- // 获取所有选中的菜单
-
- initSelectedMenus(rid) { // :name="r.id" change(rid)
-
- this.getRequest('/system/basic/permission/mid/' + rid).then(resp => {
-
- if (resp) {
-
- this.selectedMenus = resp
-
- }
-
- })
-
- },
-
- // 获取所有菜单
-
- initAllMenus() {
-
- this.getRequest('/system/basic/permission/menus').then(resp => {
-
- if (resp) {
-
- this.allMenus = resp
-
- }
-
- })
-
- },
-
- // 获取所有角色
-
- initRoles() {
-
- this.getRequest('/system/basic/permission/').then(resp => {
-
- if (resp) {
-
- this.roles = resp
-
- }
-
- })
-
- }
-
- }
-
- }
-
- </script>
-
- <style scoped>
-
- .positionManaTool {
-
- display: flex;
-
- justify-content: flex-start;
-
- }
-
- .positionManaTool .el-input {
-
- width: 300px;
-
- margin-right: 6px;
-
- }
-
- .positionManaMain {
-
- margin-top: 10px;
-
- width: 700px;
-
- }
-
- </style>
八、操作员管理
可以通过搜索操作员的名字,来单独显示操作员的信息。展示所有操作员的时候,不会把自己当前登录的操作员显示出来。
操作员涉及了权限:操作员拥有哪些角色,在根据角色再去拥有哪些菜单的权限。
获取操作员管理后端信息返回
SysAdmin.vue
- <template>
-
- <div>
-
- <!-- 1、 -->
-
- <div style="display: flex;justify-content: center;margin-top: 10px;">
-
- <!-- 9、v-model="keywords" \ @click="doSearch">搜索 -->
-
- <el-input v-model="keywords" placeholder="通过用户名搜索用户..." prefix-icon="el-icon-search"
-
- style="width: 400px;margin-right: 10px;"></el-input>
-
- <el-button type="primary" icon="el-icon-search" @click="doSearch">搜索</el-button>
-
- </div>
-
- <!-- 2、6、 -->
-
- <div class="admin-container">
-
- <el-card class="admin-card" v-for="(admin,index) in admins" :key="index">
-
- <div slot="header" class="clearfix">
-
- <div class="userInfoTab">
-
- <div style="margin-top:5px;margin-right: 8px;">{{ admin.name}}</div>
-
- <div><img :src="admin.userFace" :alt="admin.name" :title="admin.name" class="userFace-img"></div>
-
- </div>
-
- <!-- 12、 @click="deleteAdmin(admin)" -->
-
- <el-button style="color:red;" type="text" icon="el-icon-delete"
-
- @click="deleteAdmin(admin)"></el-button>
-
- </div>
-
- <div class="userinfo-container">
-
- <div>用户名:{{ admin.name }}</div>
-
- <div>手机号码:{{ admin.phone }}</div>
-
- <div>电话号码:{{ admin.telephone }}</div>
-
- <div>地址:{{ admin.address }}</div>
-
- <div>用户状态:
-
- <!-- 14、更新操作员 @change="enabledChange(admin)" -->
-
- <el-switch
-
- v-model="admin.enabled"
-
- active-color="#13ce66"
-
- inactive-color="#ff4949"
-
- @change="enabledChange(admin)"
-
- active-text="启用"
-
- inactive-text="禁用">
-
- </el-switch>
-
- </div>
-
- <div>
-
- 用户角色:
-
- <el-tag style="margin-right: 4px;" type="success" v-for="(role,index) in admin.roles" :key="index">
-
- {{ role.nameZh }}
-
- </el-tag>
-
- <!-- 16、更新操作员角色 弹出框、选择器、 -->
-
- <!-- 20、@show="showPop(admin)" -->
-
- <!-- 24、@hide="hidePop(admin)" hide 隐藏时触发-->
-
- <el-popover
-
- placement="right"
-
- title="角色列表"
-
- width="200"
-
- @show="showPop(admin)"
-
- @hide="hidePop(admin)"
-
- trigger="click">
-
- <!-- 17、更新操作员角色 下拉框 -->
-
- <!-- 22、v-model="selectedRoles" 存的是1个角色id,multiple 多选,显示已有角色 -->
-
- <el-select v-model="selectedRoles" multiple placeholder="请选择">
-
- <el-option
-
- v-for="(r,index) in allRoles"
-
- :key="index"
-
- :label="r.nameZh"
-
- :value="r.id">
-
- </el-option>
-
- </el-select>
-
- <!-- 3个点按钮 ... -->
-
- <el-button slot="reference" type="text" icon="el-icon-more"></el-button>
-
- </el-popover>
-
- </div>
-
- <div>备注:{{ admin.remark }}</div>
-
- </div>
-
- </el-card>
-
- </div>
-
- </div>
-
- </template>
-
- <script>
-
- export default {
-
- name: "SysAdmin",
-
- data() {
-
- return {
-
- admins: [], // 3
-
- keywords: '', // 8、搜索关键字
-
- allRoles: [], // 18、更新操作员角色
-
- selectedRoles: [] // 23
-
- }
-
- },
-
- mounted() {
-
- this.initAdmins() // 5
-
- },
-
- methods: {
-
- // 25、更新操作员角色
-
- hidePop(admin) {
-
- let roles = []
-
- Object.assign(roles, admin.roles) // 拷贝对象
-
- let flag = false
-
- // 如果选中的角色 id 的长度和原来的不一样
-
- if (roles.length != this.selectedRoles.length) { // 用户对应角色id
-
- flag = true
-
- } else {
-
- // 角色 id 长度和原来的一样,但可能角色不一样
-
- // 先循环 admin.roles
-
- for (let i = 0; i < roles.length; i++) {
-
- let role = roles[i] // 用户对应的角色对象
-
- for (let j = 0; j < this.selectedRoles.length; j++) {
-
- let sr = this.selectedRoles[j] // 拿到用户对应的角色对象的id
-
- if (role.id == sr) { // 角色一样
-
- roles.splice(i, 1) // 删除
-
- i--
-
- break
-
- }
-
- }
-
- }
-
- if (roles.length != 0) {
-
- flag = true
-
- }
-
- }
-
- if (flag) {
-
- // 拼接 url(参数为 adminId、角色 rids )
-
- let url = '/system/admin/role?adminId=' + admin.id;
-
- this.selectedRoles.forEach(sr => {
-
- url += '&rids=' + sr
-
- });
-
- this.putRequest(url).then(resp => {
-
- if (resp) {
-
- this.initAdmins()
-
- }
-
- });
-
- }
-
- },
-
- // 21、下拉框获取所有用户角色
-
- showPop(admin) {
-
- this.initAllRoles()
-
- let roles = admin.roles // 拿到整个数组
-
- this.selectedRoles = []
-
- roles.forEach(r => {
-
- this.selectedRoles.push(r.id) // r.id 相同的角色放进数组
-
- })
-
- },
-
- // 19、获取所有操作员
-
- initAllRoles() {
-
- this.getRequest(' /system/admin/roles').then(resp => {
-
- if (resp) {
-
- this.allRoles = resp
-
- }
-
- })
-
- },
-
- // 15、更新操作员
-
- enabledChange(admin) {
-
- this.putRequest('/system/admin/', admin).then(resp => {
-
- if (resp) {
-
- this.initAdmins()
-
- }
-
- })
-
- },
-
- // 13、删除操作员
-
- deleteAdmin(admin) {
-
- this.$confirm('此操作将永久删除该[' + admin.name + '], 是否继续?', '提示', {
-
- confirmButtonText: '确定',
-
- cancelButtonText: '取消',
-
- type: 'warning'
-
- }).then(() => {
-
- this.deleteRequest('/system/admin/' + admin.id).then(resp => {
-
- if (resp) {
-
- this.initAdmins()
-
- }
-
- })
-
- }).catch(() => {
-
- this.$message({
-
- type: 'info',
-
- message: '已取消删除'
-
- });
-
- });
-
- },
-
- // 10 搜索
-
- doSearch() {
-
- this.initAdmins()
-
- },
-
- // 4、获取所有操作员;11、加参数关键字
-
- initAdmins() {
-
- this.getRequest('/system/admin/?keywords=' + this.keywords).then(resp => {
-
- if (resp) {
-
- this.admins = resp
-
- }
-
- })
-
- }
-
- }
-
- }
-
- </script>
-
- <style >
-
- /* 7 */
-
- .admin-container {
-
- margin-top: 10px;
-
- display: flex;
-
- justify-content: space-between;
-
- flex-wrap: wrap; /* 自动换行 */
-
- }
-
- .admin-card {
-
- width: 280px;
-
- margin-bottom: 20px;
-
- }
-
- .userInfoTab{
-
- /* background-color: black; */
-
- display: flex;
-
- justify-content: center;
-
- }
-
- /* 卡片顶部 */
-
- .clearfix {
-
- display: flex;
-
- justify-content: space-between;
-
- }
-
- .userFace-img {
-
- width: 36px;
-
- height: 36px;
-
- border-radius: 36px;
-
- }
-
- /* 头像居中 */
-
- /* .img-container {
- /* width: 100%;
- display: flex;
- justify-content: center;
- } */
-
- .userinfo-container {
-
- font-size: 12px;
-
- color: #3e9ef5;
-
- }
-
- </style>
九、员工资料
一、展示所有员工
二、分页展示
三、员工搜索
四、员工添加
五、更新和删除
六、导入导出数据
axios本身不提供下载功能,需要安装js-file-download
npm install js-file-download
以流的形式输出,流的格式是二进制数组。与axios的接口请求类似,js-file-download也需要封装请求拦截器和响应拦截器,因为js-file-download不共用axios封装的拦截器功能。请求拦截器需要重新设置对请求头Authorization的设置。axios的响应拦截器是对响应码进行判断,而js-file-download需要判断返回的是否是json字符串。通过判断返回头中的content-type,如果content-type是application/json格式,则是普通的json返回,需要将二进制编码转为普通的string形式。非json字符串才是流的形式返回,需要获取fileName,contentType。为了防止可能因为文件名是中文而造成乱码,需要将fileName进行格式转换。
- let fileDownload = require('js-file-download') // 插件
-
- let fileName = headers['content-disposition'].split(';')[1].split('filename=')[1]//文件名
-
- let contentType = headers['content-type'] // 响应类型
-
- fileName = decodeURIComponent(fileName) // 格式转换 防止乱码
-
- fileDownload(resp.data, fileName, contentType) // 通过插件下载文件
EmpBasic.vue
- <template>
-
- <div>
-
- <div>
-
- <div style="display: flex;justify-content: space-between;">
-
- <!-- 1、 -->
-
- <!-- 20、搜索 v-model="empName" <el-input @keydown.enter.native="initEmps" 回车键调用初始化会员方法
- 21、@click="initEmps">搜索</el-button>
- 22、清空 clearable @clear="initEmps" -->
-
- <!-- 28-8 :disabled="showAdvanceSearchVisible" -->
-
- <div style="margin-top: 10px;">
-
- <el-input style="width: 300px;margin-right: 10px;"
-
- prefix-icon="el-icon-search"
-
- v-model="empName"
-
- placeholder="请输入员工名进行搜索..."
-
- @keydown.enter.native="initEmps"
-
- clearable
-
- @clear="initEmps"
-
- :disabled="showAdvanceSearchVisible"
-
- ></el-input>
-
- <el-button type="primary" icon="el-icon-search" @click="initEmps"
-
- :disabled="showAdvanceSearchVisible">搜索
-
- </el-button>
-
- <!-- 28-3 @click="showAdvanceSearchVisible = !showAdvanceSearchVisible" -->
-
- <!-- 28-5 判断图标样式 :class="showAdvanceSearchVisible?'fa fa-angle-double-up':'fa fa-angle-double-down'"-->
-
- <el-button type="primary" @click="showAdvanceSearchVisible = !showAdvanceSearchVisible">
-
- <i :class="showAdvanceSearchVisible?'fa fa-angle-double-up':'fa fa-angle-double-down'"
-
- aria-hidden="true"></i>高级搜索
-
- </el-button>
-
- </div>
-
- <div>
-
- <!-- 27-1、3 导入数据 上传组件 用自己的按钮 -->
-
- <!-- 27-5 on-success 文件上传成功时的钩子; on-error 文件上传失败时的钩子; -->
-
- <!-- 27-8 导入的时候禁用导入按钮 :disabled="importDataDisabled" -->
-
- <!-- 27-11 :headers="headers" 设置上传的请求头部 -->
-
- <el-upload style="display: inline-flex;margin-right: 8px;" :show-file-list="false"
-
- :headers="headers"
-
- :before-upload="beforeUpload"
-
- :on-success="onSuccess"
-
- :on-error="onError"
-
- :disabled="importDataDisabled"
-
- action="/employee/basic/import"
-
- >
-
- <el-button type="success" :icon="importDataBtnIcon" :disabled="importDataDisabled">{{
-
- importDataBtnText
-
- }}
-
- </el-button>
-
- </el-upload>
-
- <!-- 26-1、导出数据 @click="exportData" -->
-
- <el-button type="success" @click="exportData"><i class="el-icon-download" aria-hidden="true"></i> 导出数据
-
- </el-button>
-
- <!-- 23-3、 @click="showAddEmpView" -->
-
- <el-button type="primary" icon="el-icon-plus" @click="showAddEmpView">添加员工</el-button>
-
- </div>
-
- </div>
-
- <!-- 28-1 高级搜索条件框 -->
-
- <!-- 28-4 高级搜索条件框 v-show="showAdvanceSearchVisible" -->
-
- <!-- 28-6 添加展开动画效果 <transition name="fade"> 包含整个搜索条件框 </transition> -->
-
- <!-- 30-2 绑定搜索条件数据 v-model="searchValue.xxxxx" -->
-
- <transition name="slide-fade">
-
- <div v-show="showAdvanceSearchVisible"
-
- style="border: 1px solid #379ff5;border-radius: 5px;box-sizing: border-box;padding: 5px;margin: 10px 0;">
-
- <el-row>
-
- <el-col :span="5">
-
- 政治面貌:
-
- <el-select v-model="searchValue.politicId" placeholder="请选择政治面貌" size="mini" style="width: 130px;">
-
- <el-option
-
- v-for="item in politicsstatus"
-
- :key="item.id"
-
- :label="item.name"
-
- :value="item.id">
-
- </el-option>
-
- </el-select>
-
- </el-col>
-
- <el-col :span="4">
-
- 民族:
-
- <el-select v-model="searchValue.nationId" placeholder="民族" size="mini" style="width: 130px;">
-
- <el-option
-
- v-for="item in nations"
-
- :key="item.id"
-
- :label="item.name"
-
- :value="item.id">
-
- </el-option>
-
- </el-select>
-
- </el-col>
-
- <el-col :span="4">
-
- 职位:
-
- <el-select v-model="searchValue.posId" placeholder="职位" size="mini" style="width: 130px;">
-
- <el-option
-
- v-for="item in positions"
-
- :key="item.id"
-
- :label="item.name"
-
- :value="item.id">
-
- </el-option>
-
- </el-select>
-
- </el-col>
-
- <el-col :span="4">
-
- 职称:
-
- <el-select v-model="searchValue.jobLevelId" placeholder="职称" size="mini" style="width: 130px;">
-
- <el-option
-
- v-for="item in joblevels"
-
- :key="item.id"
-
- :label="item.name"
-
- :value="item.id">
-
- </el-option>
-
- </el-select>
-
- </el-col>
-
- <el-col :span="6">
-
- 聘用形式:
-
- <el-radio-group v-model="searchValue.engageForm">
-
- <el-radio label="劳动合同">劳动合同</el-radio>
-
- <el-radio label="劳务合同">劳务合同</el-radio>
-
- </el-radio-group>
-
- </el-col>
-
- </el-row>
-
- <el-row style="margin-top: 10px;">
-
- <!-- 30-4 处理部门 v-model="visible2" -->
-
- <el-col :span="5">
-
- 所属部门:
-
- <el-popover
-
- placement="bottom"
-
- title="请选择部门"
-
- width="220"
-
- trigger="manual"
-
- v-model="visible2">
-
- <!-- 23-20 添加树形控件 default-expand-all 是否默认展开所有节点 ,节点点击事件 @node-click="handleNodeClick" -->
-
- <el-tree :data="allDeps"
-
- :props="defaultProps"
-
- default-expand-all
-
- @node-click="searchHandleNodeClick"></el-tree>
-
- <!-- 30-6 @node-click="searchHandleNodeClick" -->
-
- <!-- node-click 节点被点击时的回调 共三个参数,依次为:传递给 data 属性的数组中该节点所对应的对象、节点对应的 Node、节点组件本身。 -->
-
- <!-- 自定义点击事件 -->
-
- <!-- 30-7 @click="showDepView2" -->
-
- <div slot="reference"
-
- style="width:130px;display: inline-flex;
- border-radius: 5px;border: 1px solid #dedede;height: 28px;cursor: pointer;align-items: center;
- font-size: 12px;padding-left: 8px;box-sizing: border-box;"
-
- @click="showDepView2">{{ inputDepName }}
-
- </div><!-- 23-25 回显数据 {{inputDepName}} -->
-
- </el-popover>
-
- </el-col>
-
- <!-- 30-3 处理日期:v-model="searchValue.beginDateScope" value-format="yyyy-MM-dd" ;
- 两个面板各自独立切换当前年份 使用unlink-panels -->
-
- <el-col :span="10">
-
- 入职日期:
-
- <el-date-picker
-
- unlink-panels
-
- size="mini"
-
- v-model="searchValue.beginDateScope"
-
- type="datetimerange"
-
- range-separator="至"
-
- value-format="yyyy-MM-dd"
-
- start-placeholder="开始日期"
-
- end-placeholder="结束日期">
-
- </el-date-picker>
-
- </el-col>
-
- <el-col :span="5" :offset="4">
-
- <el-button size="mini">取消</el-button>
-
- <!-- 30-10 @click="initEmps('advanced')" -->
-
- <el-button type="primary" icon="el-icon-search" size="mini" @click="initEmps('advanced')">搜索</el-button>
-
- </el-col>
-
- </el-row>
-
- </div>
-
- </transition>
-
- </div>
-
- <div style="margin-top: 10px;">
-
- <!-- 2、表格;6、添加 loading -->
-
- <el-table
-
- :data="emps"
-
- v-loading="loading"
-
- element-loading-text="拼命加载中"
-
- element-loading-spinner="el-icon-loading"
-
- element-loading-background="rgba(0, 0, 0, 0.8)"
-
- style="width: 100%" stripe border>
-
- <el-table-column
-
- type="selection"
-
- width="55">
-
- </el-table-column>
-
- <el-table-column
-
- prop="name"
-
- label="姓名"
-
- align="left"
-
- fixed
-
- width="90">
-
- </el-table-column>
-
- <el-table-column
-
- prop="gender"
-
- label="性别"
-
- align="left"
-
- width="40">
-
- </el-table-column>
-
- <el-table-column
-
- prop="workId"
-
- label="工号"
-
- align="left"
-
- width="85">
-
- </el-table-column>
-
- <el-table-column
-
- prop="birthday"
-
- label="出生日期"
-
- align="left"
-
- width="95">
-
- </el-table-column>
-
- <el-table-column
-
- prop="idCard"
-
- label="身份证号"
-
- width="150">
-
- </el-table-column>
-
- <el-table-column
-
- prop="wedlock"
-
- label="婚姻状态"
-
- align="center"
-
- width="70">
-
- </el-table-column>
-
- <el-table-column
-
- prop="nation.name"
-
- label="民族"
-
- align="left"
-
- width="50">
-
- </el-table-column>
-
- <el-table-column
-
- prop="nativePlace"
-
- label="籍贯"
-
- align="center"
-
- width="80">
-
- </el-table-column>
-
- <el-table-column
-
- prop="politicsStatus.name"
-
- label="政治面貌"
-
- width="100">
-
- </el-table-column>
-
- <el-table-column
-
- prop="email"
-
- label="电子邮件"
-
- align="left"
-
- width="150">
-
- </el-table-column>
-
- <el-table-column
-
- prop="phone"
-
- label="电话号码"
-
- align="left"
-
- width="100">
-
- </el-table-column>
-
- <el-table-column
-
- prop="address"
-
- label="联系地址"
-
- align="center"
-
- width="220">
-
- </el-table-column>
-
- <el-table-column
-
- prop="department.name"
-
- label="所属部门"
-
- align="left"
-
- width="100">
-
- </el-table-column>
-
- <el-table-column
-
- prop="position.name"
-
- label="职位"
-
- width="100">
-
- </el-table-column>
-
- <el-table-column
-
- prop="joblevel.name"
-
- label="级别"
-
- width="100">
-
- </el-table-column>
-
- <el-table-column
-
- prop="engageForm"
-
- label="聘用形式"
-
- align="left"
-
- width="100">
-
- </el-table-column>
-
- <el-table-column
-
- prop="tiptopDegree"
-
- label="最高学历"
-
- align="center"
-
- width="80">
-
- </el-table-column>
-
- <el-table-column
-
- prop="school"
-
- label="毕业学校"
-
- align="left"
-
- width="150">
-
- </el-table-column>
-
- <el-table-column
-
- prop="specialty"
-
- label="所属专业"
-
- align="left"
-
- width="150">
-
- </el-table-column>
-
- <el-table-column
-
- prop="workState"
-
- label="在职状态"
-
- align="center"
-
- width="70">
-
- </el-table-column>
-
- <el-table-column
-
- prop="beginDate"
-
- label="入职日期"
-
- align="left"
-
- width="95">
-
- </el-table-column>
-
- <el-table-column
-
- prop="conversionTime"
-
- label="转正日期"
-
- align="left"
-
- width="95">
-
- </el-table-column>
-
- <el-table-column
-
- prop="beginContract"
-
- label="合同起始日期"
-
- align="left"
-
- width="95">
-
- </el-table-column>
-
- <el-table-column
-
- prop="endContract"
-
- label="合同截止日期"
-
- align="left"
-
- width="95">
-
- </el-table-column>
-
- <el-table-column
-
- label="合同期限"
-
- align="left"
-
- width="100">
-
- <template slot-scope="scope">
-
- <el-tag>{{ scope.row.contractTerm }}</el-tag>
-
- 年
-
- </template>
-
- </el-table-column>
-
- <el-table-column
-
- label="操作"
-
- fixed="right"
-
- width="200">
-
- <template slot-scope="scope">
-
- <!-- 25-4 给编辑按钮绑定点击事件 @click="showEmpView(scope.row)" -->
-
- <el-button style="padding: 3px;" size="mini" @click="showEmpView(scope.row)">编辑</el-button>
-
- <!-- <el-button style="padding: 3px;" size="mini" type="primary" plain>查看高级资料</el-button> -->
-
- <!-- 24-1 删除员工 @click="deleteEmp(scope.row)" -->
-
- <el-button style="padding: 3px;" size="mini" type="danger" @click="deleteEmp(scope.row)">删除</el-button>
-
- </template>
-
- </el-table-column>
-
- </el-table>
-
- <!-- 10、分页 -->
-
- <div style="display: flex;justify-content: flex-end;margin-top: 10px;">
-
- <!-- 13、@current-change="currentChange" 当前页
- 14、@size-change="sizeChange" 每页显示多少条 -->
-
- <el-pagination
-
- prev-text="上一页"
-
- next-text="下一页"
-
- @current-change="currentChange"
-
- @size-change="sizeChange"
-
- :page-sizes="[10,20,30,50,100]"
-
- layout="total, sizes, prev, pager, next, jumper"
-
- :total="total" background>
-
- </el-pagination>
-
- </div>
-
- </div>
-
- <!-- 23-1、开始- 添加员工弹框 -->
-
- <!-- 25-1 编辑员工 将添加员工弹框标题改为变量 根据条件显示是添加还是编辑 :title="title" -->
-
- <el-dialog
-
- :title="title"
-
- :visible.sync="dialogVisible"
-
- width="80%">
-
- <div>
-
- <!-- 23-6、<el-row <el-form -->
-
- <!-- 23-28 数据校验对象 :rules="empRules" ,每项属性对应 prop="posId" -->
-
- <el-form ref="empRef" :model="emp" :rules="empRules">
-
- <el-row>
-
- <el-col :span="6">
-
- <el-form-item label="姓名:" prop="name">
-
- <el-input v-model="emp.name" prefix-icon="el-icon-edit" placeholder="请输入员工姓名" size="mini"
-
- style="width: 150px;"></el-input>
-
- </el-form-item>
-
- </el-col>
-
- <el-col :span="5">
-
- <el-form-item label="性别:" prop="gender">
-
- <el-radio-group v-model="emp.gender" style="margin-top: 8px;">
-
- <el-radio label="男">男</el-radio>
-
- <el-radio label="女">女</el-radio>
-
- </el-radio-group>
-
- </el-form-item>
-
- </el-col>
-
- <el-col :span="6">
-
- <el-form-item label="出生日期:" prop="birthday">
-
- <el-date-picker
-
- v-model="emp.birthday"
-
- type="date"
-
- value-format="yyyy-MM-dd"
-
- size="mini"
-
- style="width: 150px;"
-
- placeholder="出生日期">
-
- </el-date-picker>
-
- </el-form-item>
-
- </el-col>
-
- <el-col :span="7">
-
- <!-- 23-10、 添加员工 给每项赋值 -->
-
- <el-form-item label="政治面貌:" prop="politicId">
-
- <el-select v-model="emp.politicId" placeholder="请选择政治面貌" size="mini" style="width: 200px;">
-
- <el-option
-
- v-for="item in politicsstatus"
-
- :key="item.id"
-
- :label="item.name"
-
- :value="item.id">
-
- </el-option>
-
- </el-select>
-
- </el-form-item>
-
- </el-col>
-
- </el-row>
-
- <el-row>
-
- <el-col :span="6">
-
- <el-form-item label="民族:" prop="nationId">
-
- <el-select v-model="emp.nationId" placeholder="民族" size="mini" style="width: 150px;">
-
- <el-option
-
- v-for="item in nations"
-
- :key="item.id"
-
- :label="item.name"
-
- :value="item.id">
-
- </el-option>
-
- </el-select>
-
- </el-form-item>
-
- </el-col>
-
- <el-col :span="5">
-
- <el-form-item label="籍贯:" prop="nativePlace">
-
- <el-input v-model="emp.nativePlace" placeholder="籍贯" prefix-icon="el-icon-edit" size="small"
-
- style="width: 120px;"></el-input>
-
- </el-form-item>
-
- </el-col>
-
- <el-col :span="6">
-
- <el-form-item label="电子邮箱:" prop="email">
-
- <el-input v-model="emp.email" placeholder="请输入电子邮箱" prefix-icon="el-icon-message" size="mini"
-
- style="width: 150px;"></el-input>
-
- </el-form-item>
-
- </el-col>
-
- <el-col :span="7">
-
- <el-form-item label="联系地址:" prop="address">
-
- <el-input v-model="emp.address" placeholder="请输入联系地址" prefix-icon="el-icon-edit" size="mini"
-
- style="width: 200px;"></el-input>
-
- </el-form-item>
-
- </el-col>
-
- </el-row>
-
- <el-row>
-
- <el-col :span="6">
-
- <el-form-item label="职位:" prop="posId">
-
- <el-select v-model="emp.posId" placeholder="职位" size="mini" style="width: 150px;">
-
- <el-option
-
- v-for="item in positions"
-
- :key="item.id"
-
- :label="item.name"
-
- :value="item.id">
-
- </el-option>
-
- </el-select>
-
- </el-form-item>
-
- </el-col>
-
- <el-col :span="5">
-
- <el-form-item label="职称:" prop="jobLevelId">
-
- <el-select v-model="emp.jobLevelId" placeholder="职称" size="mini" style="width: 150px;">
-
- <el-option
-
- v-for="item in joblevels"
-
- :key="item.id"
-
- :label="item.name"
-
- :value="item.id">
-
- </el-option>
-
- </el-select>
-
- </el-form-item>
-
- </el-col>
-
- <el-col :span="6">
-
- <!-- 23-15 -->
-
- <el-form-item label="所属部门:" prop="departmentId">
-
- <!-- 23-17 manual 手动弹出框 -->
-
- <el-popover
-
- placement="bottom"
-
- title="请选择部门"
-
- width="200"
-
- trigger="manual"
-
- v-model="visible">
-
- <!-- 23-20 添加树形控件 default-expand-all 是否默认展开所有节点 ,节点点击事件 @node-click="handleNodeClick" -->
-
- <el-tree :data="allDeps"
-
- :props="defaultProps"
-
- default-expand-all
-
- @node-click="handleNodeClick"></el-tree>
-
- <!-- node-click 节点被点击时的回调 共三个参数,依次为:传递给 data 属性的数组中该节点所对应的对象、节点对应的 Node、节点组件本身。 -->
-
- <!-- 自定义点击事件 -->
-
- <div slot="reference"
-
- style="width:150px;display: inline-flex;
- border-radius: 5px;border: 1px solid #dedede;height: 28px;cursor: pointer;align-items: center;
- font-size: 12px;padding-left: 8px;box-sizing: border-box;"
-
- @click="showDepView">{{ inputDepName }}
-
- </div><!-- 23-25 回显数据 {{inputDepName}} -->
-
- </el-popover>
-
- </el-form-item>
-
- </el-col>
-
- <el-col :span="7">
-
- <el-form-item label="电话号码:" prop="phone">
-
- <el-input v-model="emp.phone" placeholder="请输入电话号码" size="mini" style="width: 200px;"
-
- prefix-icon="el-icon-phone"></el-input>
-
- </el-form-item>
-
- </el-col>
-
- </el-row>
-
- <el-row>
-
- <el-col :span="6">
-
- <el-form-item label="工号:" prop="workId">
-
- <el-input v-model="emp.workId" placeholder="请输入工号" size="mini" style="width: 150px;"
-
- prefix-icon="el-icon-edit" disabled></el-input>
-
- </el-form-item>
-
- </el-col>
-
- <el-col :span="5">
-
- <!-- 23-14 数据在 data 中写死的 -->
-
- <el-form-item label="学历:" prop="tiptopDegree">
-
- <el-select v-model="emp.tiptopDegree" placeholder="职称" size="mini" style="width: 150px;">
-
- <el-option
-
- v-for="item in tiptopDegrees"
-
- :key="item"
-
- :label="item"
-
- :value="item">
-
- </el-option>
-
- </el-select>
-
- </el-form-item>
-
- </el-col>
-
- <el-col :span="6">
-
- <el-form-item label="毕业院校:" prop="school">
-
- <el-input v-model="emp.school" placeholder="请输入学校" size="mini" style="width: 150px;"
-
- prefix-icon="el-icon-edit"></el-input>
-
- </el-form-item>
-
- </el-col>
-
- <el-col :span="7">
-
- <el-form-item label="专业名称:" prop="specialty">
-
- <el-input v-model="emp.specialty" placeholder="请输入专业名称" size="mini" style="width: 200px;"
-
- prefix-icon="el-icon-edit"></el-input>
-
- </el-form-item>
-
- </el-col>
-
- </el-row>
-
- <el-row>
-
- <el-col :span="6">
-
- <el-form-item label="入职日期:" prop="beginDate">
-
- <el-date-picker
-
- v-model="emp.beginDate"
-
- type="date"
-
- value-format="yyyy-MM-dd"
-
- size="mini"
-
- style="width: 120px;"
-
- placeholder="入职日期">
-
- </el-date-picker>
-
- </el-form-item>
-
- </el-col>
-
- <el-col :span="5">
-
- <el-form-item label="转正日期:" prop="conversionTime">
-
- <el-date-picker
-
- v-model="emp.conversionTime"
-
- type="date"
-
- value-format="yyyy-MM-dd"
-
- size="mini"
-
- style="width: 120px;"
-
- placeholder="转正日期">
-
- </el-date-picker>
-
- </el-form-item>
-
- </el-col>
-
- <el-col :span="6">
-
- <el-form-item label="合同起始日期:" prop="beginContract">
-
- <el-date-picker
-
- v-model="emp.beginContract"
-
- type="date"
-
- value-format="yyyy-MM-dd"
-
- size="mini"
-
- style="width: 135px;"
-
- placeholder="合同起始日期">
-
- </el-date-picker>
-
- </el-form-item>
-
- </el-col>
-
- <el-col :span="7">
-
- <el-form-item label="合同截止日期:" prop="endContract">
-
- <el-date-picker
-
- v-model="emp.endContract"
-
- type="date"
-
- value-format="yyyy-MM-dd"
-
- size="mini"
-
- style="width: 170px;"
-
- placeholder="合同截止日期">
-
- </el-date-picker>
-
- </el-form-item>
-
- </el-col>
-
- </el-row>
-
- <el-row>
-
- <el-col :span="8">
-
- <el-form-item label="身份证号码:" prop="idCard">
-
- <el-input v-model="emp.idCard" placeholder="请输入身份证号码"
-
- size="mini" prefix-icon="el-icon-edit" style="width: 180px;"></el-input>
-
- </el-form-item>
-
- </el-col>
-
- <el-col :span="8">
-
- <el-form-item label="聘用形式:" prop="engageForm">
-
- <el-radio-group v-model="emp.engageForm" style="margin-top: 8px;">
-
- <el-radio label="劳动合同">劳动合同</el-radio>
-
- <el-radio label="劳务合同">劳务合同</el-radio>
-
- </el-radio-group>
-
- </el-form-item>
-
- </el-col>
-
- <el-col :span="8">
-
- <el-form-item label="婚姻状况:" prop="wedlock">
-
- <el-radio-group v-model="emp.wedlock" style="margin-top: 8px;">
-
- <el-radio label="未婚">未婚</el-radio>
-
- <el-radio label="已婚">已婚</el-radio>
-
- <el-radio label="离异">离异</el-radio>
-
- </el-radio-group>
-
- </el-form-item>
-
- </el-col>
-
- </el-row>
-
- </el-form>
-
- </div>
-
- <span slot="footer" class="dialog-footer">
-
- <el-button @click="dialogVisible = false">取 消</el-button>
-
- <!-- 23-26 @click="doAddEmp"-->
-
- <el-button type="primary" @click="doAddEmp">确 定</el-button>
-
- </span>
-
- </el-dialog>
-
- </div>
-
- </template>
-
- <script>
-
- export default {
-
- name: "EmpBasic",
-
- data() {
-
- return {
-
- searchValue: { // 30-1 高级搜索 条件对象
-
- politicId: null, // 政治面貌
-
- nationId: null, // 民族
-
- posId: null, // 职位
-
- jobLevelId: null, // 职称
-
- engageForm: '', // 聘用形式
-
- departmentId: null, // 部门 id
-
- beginDateScope: null // 入职日期范围
-
- },
-
- showAdvanceSearchVisible: false, // 28-2 高级搜索框 动态效果
-
- headers: { // 27-12 定义请求头
-
- Authorization: window.sessionStorage.getItem('tokenStr')
-
- },
-
- importDataDisabled: false, // 27-9 导入按钮 默认不禁用
-
- importDataBtnText: '导入数据', // 27-2 导入数据
-
- importDataBtnIcon: 'el-icon-upload2', // 27-2 导入数据
-
- title: '', // 25-2 添加编辑员工弹框动态标题
-
- emps: [], // 3、获取所有员工(分页)
-
- loading: false, // 7、添加 loading
-
- total: 0, // 11 分页总条数
-
- currentPage: 1, // 14、默认显示第1页(currentPage 后端字段)
-
- size: 10, // 15、默认每页显示 10 条
-
- empName: '', // 18、搜索
-
- dialogVisible: false, // 23-2、添加员工弹框
-
- nations: [], // 23-7 添加员工 民族
-
- joblevels: [], // 23-7 职称
-
- politicsstatus: [], // 23-7 政治面貌
-
- positions: [], // 23-7 职位
-
- department: [], // 部门
-
- // 23-13、学历
-
- tiptopDegrees: ['博士', '硕士', '本科', '大专', '高中', '初中', '小学', '其它'],
-
- // 23-5、添加员工
-
- emp: {
-
- id: null,
-
- name: '',
-
- gender: '',
-
- birthday: '',
-
- idCard: '',
-
- wedlock: '',
-
- nationId: null,
-
- nativePlace: '',
-
- politicId: null,
-
- email: '',
-
- phone: '',
-
- address: '',
-
- departmentId: null,
-
- jobLevelId: null,
-
- posId: null,
-
- engageForm: '',
-
- tiptopDegree: '',
-
- specialty: '',
-
- school: '',
-
- beginDate: '',
-
- workState: '在职',
-
- workId: '',
-
- contractTerm: null,
-
- conversionTime: '',
-
- notworkDate: null,
-
- beginContract: '',
-
- endContract: '',
-
- workAge: null,
-
- salaryId: null
-
- },
-
- visible: false, // 23-18 弹出框
-
- visible2: false, // 30-5 高级搜索 部门
-
- // 23-21 树形控件
-
- defaultProps: {
-
- children: 'children',
-
- label: 'name'
-
- },
-
- allDeps: [], // 23-21 树形控件 绑定 所属部门 数据对象
-
- inputDepName: '',// 23-23 回显部门数据
-
- // 23-30 表单数据校验
-
- empRules: {
-
- name: [{required: true, message: '请输入员工名', trigger: 'blur'}],
-
- gender: [{required: true, message: '请输入员工性别', trigger: 'blur'}],
-
- birthday: [{required: true, message: '请输入出生日期', trigger: 'blur'}],
-
- idCard: [{required: true, message: '请输入身份证号码', trigger: 'blur'},
-
- {
-
- pattern: /(^[1-9]\d{5}(18|19|([23]\d))\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$)|(^[1-9]\d{5}\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{2}$)/,
-
- message: '身份证号码不正确', trigger: 'blur'
-
- }],
-
- wedlock: [{required: true, message: '请输入婚姻状况', trigger: 'blur'}],
-
- nationId: [{required: true, message: '请输入民族', trigger: 'blur'}],
-
- nativePlace: [{required: true, message: '请输入籍贯', trigger: 'blur'}],
-
- politicId: [{required: true, message: '请输入政治面貌', trigger: 'blur'}],
-
- email: [{required: true, message: '请输入邮箱地址', trigger: 'blur'},
-
- {type: 'email', message: '邮箱地址格式不正确', trigger: 'blur'}],
-
- phone: [{required: true, message: '请输入电话号码', trigger: 'blur'},
-
- {
-
- pattern: /^(0|86|17951)?(13[0-9]|15[012356789]|17[678]|18[0-9]|14[57])[0-9]{8}$/,
-
- message: '请输入合法手机号码', trigger: 'blur'
-
- }],
-
- address: [{required: true, message: '请输入地址', trigger: 'blur'}],
-
- departmentId: [{required: true, message: '请输入部门名称', trigger: 'blur'}],
-
- jobLevelId: [{required: true, message: '请输入职称', trigger: 'blur'}],
-
- posId: [{required: true, message: '请输入职位', trigger: 'blur'}],
-
- engageForm: [{required: true, message: '请输入聘用形式', trigger: 'blur'}],
-
- tiptopDegree: [{required: true, message: '请输入学历', trigger: 'blur'}],
-
- specialty: [{required: true, message: '请输入专业', trigger: 'blur'}],
-
- school: [{required: true, message: '请输入毕业院校', trigger: 'blur'}],
-
- beginDate: [{required: true, message: '请输入入职日期', trigger: 'blur'}],
-
- workState: [{required: true, message: '请输入工作状态', trigger: 'blur'}],
-
- workId: [{required: true, message: '请输入工号', trigger: 'blur'}],
-
- contractTerm: [{required: true, message: '请输入合同期限', trigger: 'blur'}],
-
- conversionTime: [{required: true, message: '请输入转正日期', trigger: 'blur'}],
-
- notworkDate: [{required: true, message: '请输入离职日期', trigger: 'blur'}],
-
- beginContract: [{required: true, message: '请输入合同起始日期', trigger: 'blur'}],
-
- endContract: [{required: true, message: '请输入合同结束日期', trigger: 'blur'}],
-
- workAge: [{required: true, message: '请输入工龄', trigger: 'blur'}]
-
- }
-
- }
-
- },
-
- mounted() {
-
- this.initEmps() // 5、获取所有员工(分页)
-
- this.initData() // 23-9 添加员工
-
- this.initPositions() // 23-12 获取职位
-
- },
-
- methods: {
-
- // 27-6 数据导入成功 恢复原来的图标和状态
-
- onSuccess() {
-
- this.importDataBtnIcon = 'el-icon-upload2'
-
- this.importDataBtnText = '导入数据'
-
- this.importDataDisabled = false // 29-10 不禁用导入按钮
-
- this.initEmps()
-
- },
-
- // 27-7 数据导入失败 恢复原来的图标和状态
-
- onError() {
-
- this.importDataBtnIcon = 'el-icon-upload2'
-
- this.importDataBtnText = '导入数据'
-
- this.importDataDisabled = false // 29-10 不禁用导入按钮
-
- },
-
- // 27-4、导入数据 改变图标和添加 loading 状态
-
- beforeUpload() {
-
- this.importDataBtnIcon = 'el-icon-loading'
-
- this.importDataBtnText = '正在导入'
-
- this.importDataDisabled = true // 29-10 禁用导入按钮
-
- },
-
- // 26-2 下载请求
-
- exportData() {
-
- this.downloadRequest('/employee/basic/export')
-
- },
-
- // 25-5 编辑员工按钮 点击事件
-
- showEmpView(data) {
-
- this.title = '编辑员工信息'
-
- this.emp = data // 回显数据
-
- this.inputDepName = data.department.name // 25-7 回显部门信息
-
- this.initPositions() // 25-9 初始化职位信息
-
- this.dialogVisible = true
-
- },
-
- // 24-2 删除员工
-
- deleteEmp(data) {
-
- this.$confirm('此操作将永久删除该员工' + data.name + ', 是否继续?', '提示', {
-
- confirmButtonText: '确定',
-
- cancelButtonText: '取消',
-
- type: 'warning'
-
- }).then(() => {
-
- this.deleteRequest('/employee/basic/' + data.id).then(resp => {
-
- if (resp) {
-
- this.initEmps()
-
- }
-
- })
-
- }).catch(() => {
-
- this.$message({
-
- type: 'info',
-
- message: '已取消删除'
-
- });
-
- });
-
- },
-
- // 23-27 确定添加员工
-
- // 25-10 添加或编辑员工 有id编辑员工 没有id添加员工
-
- // 添加和编辑这里就请求方式不一样 putRequest postRequest ,其它的都一样
-
- doAddEmp() {
-
- if (this.emp.id) {
-
- // 有 id 编辑员工
-
- this.$refs['empRef'].validate(valid => {
-
- if (valid) {
-
- this.putRequest('/employee/basic/', this.emp).then(resp => {
-
- if (resp) {
-
- this.dialogVisible = false
-
- this.initEmps()
-
- }
-
- })
-
- }
-
- })
-
- } else {
-
- // 没有id 添加员工
-
- // empRef 表单中定义的引用对象 ref="empRef"
-
- this.$refs['empRef'].validate(valid => {
-
- if (valid) {
-
- this.postRequest('/employee/basic/', this.emp).then(resp => {
-
- if (resp) {
-
- this.dialogVisible = false
-
- this.initEmps()
-
- }
-
- })
-
- }
-
- })
-
- }
-
- },
-
- // 30-7 高级搜索 部门点击事件
-
- searchHandleNodeClick(data) {
-
- this.inputDepName = data.name
-
- this.searchValue.departmentId = data.id
-
- this.visible2 = !this.visible2 // 弹框
-
- },
-
- // 23-22、24 树控件节点点击事件
-
- handleNodeClick(data) {
-
- this.inputDepName = data.name
-
- this.emp.departmentId = data.id
-
- this.visible = !this.visible // 弹框
-
- },
-
- // 30-9 高级搜索 部门弹框
-
- showDepView2() {
-
- this.visible2 = !this.visible2
-
- },
-
- // 23-16 添加员工 所属部门
-
- showDepView() {
-
- this.visible = !this.visible // 23-19 弹出框
-
- },
-
- // 23-13 添加员工 获取最大号
-
- getMaxworkId() {
-
- this.getRequest('/employee/basic/maxWorkID').then(resp => {
-
- if (resp) {
-
- this.emp.workId = resp.obj
-
- }
-
- })
-
- },
-
- // 23-11、 添加员工 获取职位 有可能变动 打开对话框的时候调用此方法
-
- initPositions() {
-
- this.getRequest('/employee/basic/Positions').then(resp => {
-
- if (resp) {
-
- this.positions = resp
-
- }
-
- })
-
- },
-
- // 23-8、添加员工 不怎么变动的数据。放 sessionStorage ,就不用怎么去查
-
- initData() {
-
- // 获取民族数据:先从 sessionStorage 里取,取不到再调用接口获取数据
-
- if (!window.sessionStorage.getItem("nations")) {
-
- this.getRequest('/employee/basic/nations').then(resp => {
-
- this.nations = resp
-
- // 存到 sessionStorage 里,把对象转字符串
-
- window.sessionStorage.setItem('nations', JSON.stringify(resp))
-
- })
-
- } else {
-
- // 从 sessionStorage 获取,字符串转对象
-
- this.nations = JSON.parse(window.sessionStorage.getItem('nations'))
-
- }
-
- // 获取职称
-
- if (!window.sessionStorage.getItem('joblevels')) {
-
- this.getRequest('/employee/basic/joblevels').then(resp => {
-
- if (resp) {
-
- this.joblevels = resp
-
- window.sessionStorage.setItem('joblevels', JSON.stringify(resp))
-
- }
-
- })
-
- } else {
-
- // 从 sessionStorage 获取,字符串转对象
-
- this.joblevels = JSON.parse(window.sessionStorage.getItem('joblevels'))
-
- }
-
- // 获取政治面貌
-
- if (!window.sessionStorage.getItem('politicsstatus')) {
-
- this.getRequest('/employee/basic/politicsStatus').then(resp => {
-
- if (resp) {
-
- this.politicsstatus = resp
-
- window.sessionStorage.setItem('politicsstatus', JSON.stringify(resp))
-
- }
-
- })
-
- } else {
-
- // 从 sessionStorage 获取,字符串转对象
-
- this.politicsstatus = JSON.parse(window.sessionStorage.getItem('politicsstatus'))
-
- }
-
- // 23-22 树形控件 绑定 所属部门 数据对象
-
- if (!window.sessionStorage.getItem('allDeps')) {
-
- this.getRequest('/employee/basic/deps').then(resp => {
-
- if (resp) {
-
- this.allDeps = resp
-
- window.sessionStorage.setItem('allDeps', JSON.parse(resp))
-
- }
-
- })
-
- } else {
-
- this.allDeps = window.sessionStorage.getItem('allDeps')
-
- }
-
- },
-
- // 23-4、添加员点击事件
-
- showAddEmpView() {
-
- // 25-6 清空表单
-
- this.emp = {
-
- id: null,
-
- name: '',
-
- gender: '',
-
- birthday: '',
-
- idCard: '',
-
- wedlock: '',
-
- nationId: null,
-
- nativePlace: '',
-
- politicId: null,
-
- email: '',
-
- phone: '',
-
- address: '',
-
- departmentId: null,
-
- jobLevelId: null,
-
- posId: null,
-
- engageForm: '',
-
- tiptopDegree: '',
-
- specialty: '',
-
- school: '',
-
- beginDate: '',
-
- workState: '在职',
-
- workId: '',
-
- contractTerm: null,
-
- conversionTime: '',
-
- notworkDate: null,
-
- beginContract: '',
-
- endContract: '',
-
- workAge: null,
-
- salaryId: null
-
- }
-
- this.inputDepName = '' // 25-8 清空部门信息
-
- this.title = '添加员工' // 25-3 点击添加员工按钮时,弹出框标题为 添加员工
-
- this.getMaxworkId() // 23-14 获取最大工号
-
- this.initPositions() // 23-12 获取职位
-
- this.dialogVisible = true
-
- },
-
- // 15、分页 每页显示多少条 默认会把 size 传进来
-
- sizeChange(size) {
-
- this.size = size
-
- this.initEmps()
-
- },
-
- // 13、分页-当前页-currentPage 点击的时候自己会带过来
-
- currentChange(currentPage) {
-
- this.currentPage = currentPage // 16
-
- this.initEmps() // 18、调用方法
-
- },
-
- // 4、获取所有员工(分页)
-
- initEmps(type) {
-
- this.loading = true // 8、添加 loading
-
- // 30-11 定义高级搜索 url
-
- let url = '/employee/basic/?currentPage=' + this.currentPage + '&size=' + this.size
-
- if (type && type === 'advanced') { // 说明是高级搜索
-
- if (this.searchValue.politicId) {
-
- url += '&politicId=' + this.searchValue.politicId
-
- }
-
- if (this.searchValue.nationId) {
-
- url += '&nationId=' + this.searchValue.nationId
-
- }
-
- if (this.searchValue.posId) {
-
- url += '&posId=' + this.searchValue.posId
-
- }
-
- if (this.searchValue.jobLevelId) {
-
- url += '&jobLevelId=' + this.searchValue.jobLevelId
-
- }
-
- if (this.searchValue.engageForm) {
-
- url += '&engageForm=' + this.searchValue.engageForm
-
- }
-
- if (this.searchValue.departmentId) {
-
- url += '&departmentId=' + this.searchValue.departmentId
-
- }
-
- if (this.searchValue.beginDateScope) {
-
- url += '&beginDateScope=' + this.searchValue.beginDateScope
-
- }
-
- } else {
-
- url += '&name=' + this.empName
-
- }
-
- // 17、添加分页参数 ?currentPage='+this.currentPage+'&size='+this.size
-
- // 19、添加用户名搜索参数 +'&name='+this.empName,传参 根据条件搜索,不传参查询所有
-
- this.getRequest(url).then(resp => {
-
- // this.getRequest('/employee/basic/').then(resp => {
-
- this.loading = false // 9、关闭 loading
-
- if (resp) {
-
- this.emps = resp.data
-
- this.total = resp.total // 12、分页
-
- }
-
- });
-
- }
-
- }
-
- }
-
- </script>
-
- <style>
-
- /*28-7 展开收起条件搜索框动画样式 */
-
- /* 可以设置不同的进入和离开动画 */
-
- /* 设置持续时间和动画函数 */
-
- .slide-fade-enter-active {
-
- transition: all .8s ease;
-
- }
-
- .slide-fade-leave-active {
-
- transition: all .8s cubic-bezier(1.0, 0.5, 0.8, 1.0);
-
- }
-
- .slide-fade-enter, .slide-fade-leave-to
-
- /* .slide-fade-leave-active for below version 2.1.8 */
-
- {
-
- transform: translateX(10px);
-
- opacity: 0;
-
- }
-
- </style>
十、工资账套管理
树形结构
SalSob.vue
- <template>
-
- <div>
-
- <!-- 1-1 绘制表格 -->
-
- <div style="display: flex;justify-content: space-between;">
-
- <!-- 2-3 @click="showAddSalaryView" 点击打开 添加工资账套对话框 -->
-
- <el-button type="primary" icon="el-icon-plus" size="mini" @click="showAddSalaryView">添加工资账套</el-button>
-
- <!-- 5-3 刷新功能 直接绑定点击事件 调用获取所有数据方法 -->
-
- <el-button type="success" icon="el-icon-refresh" size="mini" @click="initSalaries"></el-button>
-
- </div>
-
- <div style="margin-top: 10px;">
-
- <el-table
-
- :data="salaries"
-
- stripe
-
- border>
-
- <!-- 多选框 type="selection" -->
-
- <el-table-column
-
- type="selection"
-
- width="40">
-
- </el-table-column>
-
- <el-table-column
-
- prop="name"
-
- label="账套名称"
-
- width="120">
-
- </el-table-column>
-
- <el-table-column
-
- prop="basicSalary"
-
- label="基本工资"
-
- width="70">
-
- </el-table-column>
-
- <el-table-column
-
- prop="trafficSalary"
-
- label="交通补助"
-
- width="70">
-
- </el-table-column>
-
- <el-table-column
-
- prop="lunchSalary"
-
- label="午餐补助"
-
- width="70">
-
- </el-table-column>
-
- <el-table-column
-
- prop="bonus"
-
- label="奖金"
-
- width="70">
-
- </el-table-column>
-
- <el-table-column
-
- prop="createDate"
-
- label="启用时间"
-
- width="100">
-
- </el-table-column>
-
- <!-- 多级表头:el-table-column 里面嵌套 el-table-column,就可以实现多级表头 -->
-
- <el-table-column
-
- label="养老金"
-
- align="center">
-
- <el-table-column
-
- prop="pensionPer"
-
- label="比率"
-
- width="70">
-
- </el-table-column>
-
- <el-table-column
-
- prop="pensionBase"
-
- label="基数"
-
- width="70">
-
- </el-table-column>
-
- </el-table-column>
-
- <!-- 多级表头 -->
-
- <el-table-column
-
- label="医疗保险"
-
- align="center">
-
- <el-table-column
-
- prop="medicalPer"
-
- label="比率"
-
- width="70">
-
- </el-table-column>
-
- <el-table-column
-
- prop="medicalBase"
-
- label="基数"
-
- width="70">
-
- </el-table-column>
-
- </el-table-column>
-
- <!-- 多级表头 -->
-
- <el-table-column
-
- label="公积金"
-
- align="center">
-
- <el-table-column
-
- prop="accumulationFundPer"
-
- label="比率"
-
- width="70">
-
- </el-table-column>
-
- <el-table-column
-
- prop="accumulationFundBase"
-
- label="基数"
-
- width="70">
-
- </el-table-column>
-
- </el-table-column>
-
- <el-table-column
-
- label="操作">
-
- <!-- 5-1 删除工资账套 拿到当前行数据 绑定点击事件 传行数据-->
-
- <template slot-scope="scope">
-
- <!-- 6-4 @click="showEditSalaryView(scope.row)">编辑 -->
-
- <el-button type="primary" size="mini" @click="showEditSalaryView(scope.row)">编辑</el-button>
-
- <el-button type="danger" size="mini" @click="deleteSalary(scope.row)">删除</el-button>
-
- </template>
-
- </el-table-column>
-
- </el-table>
-
- </div>
-
- <!-- 2-1 添加工资账套对话框 -->
-
- <!-- 6-2 把标题变成属性 -->
-
- <el-dialog
-
- :title="dialogTitle"
-
- :visible.sync="dialogVisible"
-
- width="50%">
-
- <!-- 3-8 调整样式 -->
-
- <div style="display: flex;justify-content: space-around;align-items: center;">
-
- <!-- 3-1 添加步骤条 -->
-
- <!-- 3-5 :active="activeItemIndex" -->
-
- <el-steps direction="vertical" :active="activeItemIndex">
-
- <!-- 3-3 循环遍历数据 -->
-
- <el-step :title="itemName" v-for="(itemName,index) in salaryItemName" :key="index"></el-step>
-
- </el-steps>
-
- <!-- 3-4 循环遍历数据 -->
-
- <!-- 3-7 v-show="activeItemIndex = index" 与下标相等才展示 -->
-
- <!-- 4-2 修改各项的值 绑定和遍历-->
-
- <el-input v-model="salary[title]" :placeholder="'请输入'+salaryItemName[index]+'...'"
-
- v-for="(value,title,index) in salary"
-
- :key="index" v-show="activeItemIndex === index" style="width: 200px;"></el-input>
-
- </div>
-
- <span slot="footer" class="dialog-footer">
-
- <!-- 3-10 按钮判断根据索引显示 文字提示 -->
-
- <el-button @click="preStep">{{ activeItemIndex === 10 ? '取消' : '上一步' }}</el-button>
-
- <el-button type="primary" @click="nextStep">{{ activeItemIndex === 10 ? '完成' : '下一步' }}</el-button>
-
- </span>
-
- </el-dialog>
-
- </div>
-
- </template>
-
- <script>
-
- export default {
-
- name: "SalSob",
-
- data() {
-
- return {
-
- dialogTitle: '添加工资账套', // 6-1 标题
-
- dialogVisible: false, // 2-2 添加工资账套对话框
-
- salaries: [], // 1-2 定义数组
-
- activeItemIndex: 0, // 3-6 步骤条激活索引
-
- salaryItemName: [ // 3-2 步骤条数据对象
-
- '账套名称',
-
- '基本工资',
-
- '交通补助',
-
- '午餐补助',
-
- '奖金',
-
- '养老金比率',
-
- '养老金基数',
-
- '医疗保险比率',
-
- '医疗保险基数',
-
- '公积金比率',
-
- '公积金基数'
-
- ],
-
- // 4-1 定义工资账套数据
-
- salary: {
-
- name: '',
-
- basicSalary: 0,
-
- trafficSalary: 0,
-
- lunchSalary: 0,
-
- bonus: 0,
-
- pensionPer: 0.0,
-
- pensionBase: 0,
-
- medicalPer: 0.0,
-
- medicalBase: 0,
-
- accumulationFundPer: 0.0,
-
- accumulationFundBase: 0
-
- }
-
- }
-
- },
-
- mounted() {
-
- this.initSalaries()
-
- },
-
- methods: {
-
- // 6-5 点击编辑显示对话框
-
- showEditSalaryView(data) {
-
- this.dialogTitle = '编辑工资账套' // 设置标题
-
- this.activeItemIndex = 0 // 默认激活的索引
-
- this.salary.id = data.id
-
- this.salary.name = data.name
-
- this.salary.basicSalary = data.basicSalary
-
- this.salary.trafficSalary = data.trafficSalary
-
- this.salary.lunchSalary = data.lunchSalary
-
- this.salary.bonus = data.bonus
-
- this.salary.pensionPer = data.pensionPer
-
- this.salary.pensionBase = data.pensionBase
-
- this.salary.medicalPer = data.medicalPer
-
- this.salary.medicalBase = data.medicalBase
-
- this.salary.accumulationFundPer = data.accumulationFundPer
-
- this.salary.accumulationFundBase = data.accumulationFundBase
-
- this.dialogVisible = true // 打开对话框
-
- },
-
- // 5-2 删除工资账套
-
- deleteSalary(data) {
-
- this.$confirm('此操作将永久删除该[' + data.name + ']工资账套, 是否继续?', '提示', {
-
- confirmButtonText: '确定',
-
- cancelButtonText: '取消',
-
- type: 'warning'
-
- }).then(() => {
-
- this.deleteRequest('/salary/sob/' + data.id).then(resp => {
-
- if (resp) {
-
- this.initSalaries()
-
- }
-
- })
-
- }).catch(() => {
-
- this.$message({
-
- type: 'info',
-
- message: '已取消删除'
-
- });
-
- });
-
- },
-
- preStep() { // 3-13 上一步 取消
-
- if (this.activeItemIndex === 0) {
-
- return
-
- } else if (this.activeItemIndex === 10) {
-
- this.dialogVisible = false;
-
- return;
-
- }
-
- this.activeItemIndex--
-
- },
-
- nextStep() { // 3-12 下一步 完成
-
- if (this.activeItemIndex === 10) {
-
- // alert("ok")
-
- // console.log(this.salary)
-
- // 4-4 添加工资账套
-
- if (this.salary.id) { // 6-6 有 id 调用编辑接口,没有 id 执行添加
-
- this.putRequest('/salary/sob/', this.salary).then(resp => {
-
- if (resp) {
-
- this.initSalaries()
-
- this.dialogVisible = false // 关闭弹框
-
- }
-
- })
-
- } else {
-
- this.postRequest('/salary/sob/', this.salary).then(resp => {
-
- if (resp) {
-
- this.initSalaries()
-
- this.dialogVisible = false
-
- }
-
- })
-
- }
-
- return
-
- }
-
- this.activeItemIndex++
-
- },
-
- // 2-4 点击打开添加工资账套对话框
-
- showAddSalaryView() {
-
- this.dialogTitle = '添加工资账套' // 6-3 添加的时候显示此标题
-
- this.salary = { // 4-3 清空表单
-
- name: '',
-
- basicSalary: 0,
-
- trafficSalary: 0,
-
- lunchSalary: 0,
-
- bonus: 0,
-
- pensionPer: 0.0,
-
- pensionBase: 0,
-
- medicalPer: 0.0,
-
- medicalBase: 0,
-
- accumulationFundPer: 0.0,
-
- accumulationFundBase: 0
-
- }
-
- this.activeItemIndex = 0 // 3-14 步骤条索引从0开始
-
- this.dialogVisible = true;
-
- },
-
- // 1-3 初始化数据
-
- initSalaries() {
-
- this.getRequest('/salary/sob/').then(resp => {
-
- if (resp) {
-
- this.salaries = resp
-
- }
-
- })
-
- }
-
- }
-
- }
-
- </script>
-
- <style scoped>
-
- </style>
十一、员工账套设置
SalSobCfg.vue
- <template>
-
- <div>
-
- <el-table
-
- size="mini"
-
- :data="emps"
-
- stripe
-
- border>
-
- <el-table-column
-
- align="left"
-
- type="selection"
-
- width="55">
-
- </el-table-column>
-
- <el-table-column
-
- prop="name"
-
- label="姓名"
-
- align="left"
-
- fixed
-
- width="120">
-
- </el-table-column>
-
- <el-table-column
-
- prop="workId"
-
- label="工号"
-
- align="left"
-
- width="120">
-
- </el-table-column>
-
- <el-table-column
-
- prop="email"
-
- label="邮箱地址"
-
- align="left"
-
- width="200">
-
- </el-table-column>
-
- <el-table-column
-
- prop="phone"
-
- label="电话号码"
-
- align="left"
-
- width="120">
-
- </el-table-column>
-
- <el-table-column
-
- prop="department.name"
-
- label="所属部门"
-
- align="left"
-
- width="120">
-
- </el-table-column>
-
- <el-table-column
-
- label="工资账套"
-
- align="center">
-
- <template slot-scope="scope">
-
- <el-tooltip placement="right" v-if="scope.row.salary">
-
- <div slot="content">
-
- <table>
-
- <tr>
-
- <td>基本工资</td>
-
- <td>{{ scope.row.salary.basicSalary }}</td>
-
- </tr>
-
- <tr>
-
- <td>交通补助</td>
-
- <td>{{ scope.row.salary.trafficSalary }}</td>
-
- </tr>
-
- <tr>
-
- <td>午餐补助</td>
-
- <td>{{ scope.row.salary.lunchSalary }}</td>
-
- </tr>
-
- <tr>
-
- <td>奖金</td>
-
- <td>{{ scope.row.salary.bonus }}</td>
-
- </tr>
-
- <tr>
-
- <td>养老金比率</td>
-
- <td>{{ scope.row.salary.pensionPer }}</td>
-
- </tr>
-
- <tr>
-
- <td>养老金基数</td>
-
- <td>{{ scope.row.salary.pensionBase }}</td>
-
- </tr>
-
- <tr>
-
- <td>医疗保险比率</td>
-
- <td>{{ scope.row.salary.medicalPer }}</td>
-
- </tr>
-
- <tr>
-
- <td>医疗保险基数</td>
-
- <td>{{ scope.row.salary.medicalBase }}</td>
-
- </tr>
-
- <tr>
-
- <td>公积金比率</td>
-
- <td>{{ scope.row.salary.accumulationFundPer }}</td>
-
- </tr>
-
- <tr>
-
- <td>公积金基数</td>
-
- <td>{{ scope.row.salary.accumulationFundBase }}</td>
-
- </tr>
-
- </table>
-
- </div>
-
- <el-tag>{{ scope.row.salary.name }}</el-tag>
-
- </el-tooltip>
-
- <el-tag v-else>暂未设置</el-tag>
-
- </template>
-
- </el-table-column>
-
- <!-- 2-1 编辑工资账套 -->
-
- <el-table-column
-
- label="操作"
-
- align="center">
-
- <template slot-scope="scope">
-
- <!-- 2-5 当前员工的工资账套 @show="showPop(scope.row.salary)" show 显示时触发 -->
-
- <!-- 2-9 @hide="hidePop(scope.row)" hide 隐藏时触发 -->
-
- <el-popover
-
- size="mini"
-
- @show="showPop(scope.row.salary)"
-
- @hide="hidePop(scope.row)"
-
- placement="right"
-
- title="编辑工资账套"
-
- width="200"
-
- trigger="click">
-
- <div>
-
- <!-- 2-6 v-model="currentSalary" -->
-
- <el-select v-model="currentSalary" placeholder="请选择">
-
- <el-option
-
- size="mini"
-
- v-for="item in salaries"
-
- :key="item.id"
-
- :label="item.name"
-
- :value="item.id">
-
- </el-option>
-
- </el-select>
-
- </div>
-
- <el-button slot="reference" type="danger">修改工资账套</el-button>
-
- </el-popover>
-
- </template>
-
- </el-table-column>
-
- </el-table>
-
- <!-- 1-1 分页组件 -->
-
- <div style="display: flex;justify-content: flex-end;margin-top: 5px;">
-
- <el-pagination
-
- @size-change="sizeChange"
-
- @current-change="currentChange"
-
- layout="total, sizes, prev, pager, next, jumper"
-
- :total="total" background>
-
- </el-pagination>
-
- </div>
-
- </div>
-
- </template>
-
- <script>
-
- export default {
-
- name: "SalSobCfg",
-
- data() {
-
- return {
-
- emps: [],
-
- salaries: [], // 2-2 工资账套数组
-
- currentPage: 1, // 1-2 当前页
-
- size: 10, // 1-2 每页显示条数
-
- total: 0, // 1-2 分页
-
- currentSalary: null // 2-7 当前员工工资账套
-
- }
-
- },
-
- mounted() {
-
- this.initEmps()
-
- this.initSalaries() // 2-4 初始化 获取所有工资账套
-
- },
-
- methods: {
-
- // 2-10
-
- hidePop(data) { // 隐藏时触发
-
- // 当前员工工资账套存在 并且不等于当前的 才更新
-
- if (this.currentSalary && this.currentSalary!==data.salary.id) {
-
- this.putRequest('/salary/sobcfg/?eid=' + data.id + '&sid=' + this.currentSalary).then(resp => {
-
- if (resp) {
-
- this.initEmps()
-
- }
-
- });
-
- }
-
- },
-
- // 2-8 员工工资账套
-
- showPop(data) { // 显示时触发
-
- if (data) {
-
- this.currentSalary = data.id;
-
- } else {
-
- this.currentSalary = null
-
- }
-
- },
-
- // 2-3 获取所有工资账套
-
- initSalaries() {
-
- this.getRequest('/salary/sobcfg/salaries').then(resp => {
-
- if (resp) {
-
- this.salaries = resp
-
- }
-
- })
-
- },
-
- // 1-3 分页-当前页
-
- currentChange(page) {
-
- this.currentPage = page
-
- this.initEmps()
-
- },
-
- // 1-4 分页-每页显示数量
-
- sizeChange(size) {
-
- this.size = size
-
- this.initEmps()
-
- },
-
- // 获取所有数据
-
- initEmps() {
-
- this.getRequest('/salary/sobcfg/?currentPage=' + this.currentPage + '&size=' + this.size).then(resp => {
-
- if (resp) {
-
- this.emps = resp.data
-
- this.total = resp.total
-
- }
-
- })
-
- }
-
- }
-
- }
-
- </script>
-
- <style scoped>
-
- </style>
十二、聊天功能
安装npm install --save stompjs
将GitHub上的开源项目与自身项目进行整合来实现即时聊天功能
简介
一个基于Vue + Webpack构建的简单chat示例,聊天记录保存在localStorge。简单演示了Vue的 component、filter、directive、computed以及组件间的事件通讯。 原项目目前存在一个Bug:打开项目关闭浏览器再次打开会报错。这里使用在此项目基础上重构的项目 来与我们项目进行整合.
下载(用chrom打开下载)
地址:
整合项目
将下载下来的项目所在的文件,加入到本项目中。
在这里插入图片描述
assets:是网页所需要的图片,这个不需要,因为后端提供了图片,更改代码直接从后端获取。
componts:在自己的目录下创建该目录下的几个文件,复制过去。
vuex:是stroy,将其中的的代码,加入到本项目的story/index.js
main.js:就是一个普通的入口,不需要加入项目当汇总。
components
card.vue
- <template>
-
- <div id="card">
-
- <header>
-
- <img class="avatar" v-bind:src="user.userFace" v-bind:alt="user.name">
-
- <p class="name">{{user.name}}</p>
-
- </header>
-
- <footer>
-
- <input class="search" type="text" v-model="$store.state.filterKey" placeholder="search user...">
-
- </footer>
-
- </div>
-
- </template>
-
- <script>
-
- export default {
-
- name: 'card',
-
- data () {
-
- return {
-
- user:JSON.parse(window.sessionStorage.getItem("user"))
-
- }
-
- }
-
- }
-
- </script>
-
- <style lang="scss" scoped>
-
- #card {
-
- padding: 12px;
-
- .avatar{
-
- width: 40px;
-
- height: 40px;
-
- vertical-align: middle;/*这个是图片和文字居中对齐*/
-
- }
-
- .name {
-
- display: inline-block;
-
- padding: 10px;
-
- margin-bottom: 15px;
-
- font-size: 16px;
-
- }
-
- .search {
-
- background: #26292E;
-
- height: 30px;
-
- line-height: 30px;
-
- padding: 0 10px;
-
- border: 1px solid #3a3a3a;
-
- border-radius: 4px;
-
- outline: none;/*鼠标点击后不会出现蓝色边框*/
-
- color: #FFF;
-
- }
-
- }
-
- </style>
list.vue
- <template>
-
- <div id="list">
-
- <ul style="padding-left: 0;">
-
- <li v-for="item in admins" :class="{ active: currentSession?item.username === currentSession.username:false }"
-
- v-on:click="changecurrentSession(item)"><!-- :class="[item.id === currentSession ? 'active':'']" -->
-
- <!-- 未读消息提示 小红点 <el-badge is-dot> </el-badge> -->
-
- <el-badge is-dot :is-dot="idDot[user.username+'#'+item.username]"><img class="avatar" :src="item.userFace" :alt="item.name"></el-badge>
-
- <p class="name">{{ item.name }}</p>
-
- </li>
-
- </ul>
-
- </div>
-
- </template>
-
- <script>
-
- import {mapState} from 'vuex'
-
- export default {
-
- name: 'list',
-
- data() {
-
- return {
-
- user:JSON.parse(window.sessionStorage.getItem('user'))
-
- }
-
- },
-
- computed: mapState([
-
- 'idDot',
-
- 'admins',
-
- 'currentSession'
-
- ]),
-
- methods: {
-
- changecurrentSession: function (currentSession) {
-
- this.$store.commit('changecurrentSession', currentSession)
-
- }
-
- }
-
- }
-
- </script>
-
- <style lang="scss" scoped>
-
- #list {
-
- li {
-
- padding: 0 15px;
-
- border-bottom: 1px solid #292C33;
-
- cursor: pointer;
-
- &:hover {
-
- background-color: rgba(255, 255, 255, 0.03);
-
- }
-
- }
-
- li.active { /*注意这个是.不是冒号:*/
-
- background-color: rgba(255, 255, 255, 0.1);
-
- }
-
- .avatar {
-
- border-radius: 2px;
-
- width: 30px;
-
- height: 30px;
-
- vertical-align: middle;
-
- }
-
- .name {
-
- display: inline-block;
-
- margin-left: 15px;
-
- }
-
- }
-
- </style>
message.vue
- <template>
-
- <div id="message" v-scroll-bottom="sessions">
-
- <ul v-if="currentSession">
-
- <li v-for="entry in sessions[user.username+'#'+currentSession.username]">
-
- <p class="time">
-
- <span>{{ entry.date | time }}</span>
-
- </p>
-
- <div class="main" :class="{self:entry.self}">
-
- <img class="avatar" :src="entry.self ? user.userFace:currentSession.userFace" alt="">
-
- <p class="text">{{ entry.content }}</p>
-
- </div>
-
- </li>
-
- </ul>
-
- </div>
-
- </template>
-
- <script>
-
- import {mapState} from 'vuex'
-
- export default {
-
- name: 'message',
-
- data() {
-
- return {
-
- user: JSON.parse(window.sessionStorage.getItem('user')), // 当前用户
-
- }
-
- },
-
- computed: mapState([
-
- 'sessions',
-
- 'currentSession'
-
- ]),
-
- filters: {
-
- time(date) {
-
- if (date) {
-
- date = new Date(date);
-
- }
-
- return `${date.getHours()}:${date.getMinutes()}`;
-
- }
-
- },
-
- directives: {/*这个是vue的自定义指令,官方文档有详细说明*/
-
- // 发送消息后滚动到底部,这里无法使用原作者的方法,也未找到合理的方法解决,暂用setTimeout的方法模拟
-
- 'scroll-bottom'(el) {
-
- //console.log(el.scrollTop);
-
- setTimeout(function () {
-
- el.scrollTop += 9999;
-
- }, 1)
-
- }
-
- }
-
- }
-
- </script>
-
- <style lang="scss" scoped>
-
- #message {
-
- padding: 15px;
-
- max-height: 68%;
-
- overflow-y: scroll;
-
- ul {
-
- list-style-type: none;
-
- padding-left: 0;
-
- li {
-
- margin-bottom: 15px;
-
- }
-
- }
-
- .time {
-
- text-align: center;
-
- margin: 7px 0;
-
- > span {
-
- display: inline-block;
-
- padding: 0 18px;
-
- font-size: 12px;
-
- color: #FFF;
-
- background-color: #dcdcdc;
-
- border-radius: 2px;
-
- }
-
- }
-
- .main {
-
- .avatar {
-
- float: left;
-
- margin: 0 10px 0 0;
-
- border-radius: 3px;
-
- width: 30px;
-
- height: 30px;
-
- }
-
- .text {
-
- display: inline-block;
-
- padding: 0 10px;
-
- max-width: 80%;
-
- background-color: #fafafa;
-
- border-radius: 4px;
-
- line-height: 30px;
-
- }
-
- }
-
- .self {
-
- text-align: right;
-
- .avatar {
-
- float: right;
-
- margin: 0 0 0 10px;
-
- border-radius: 3px;
-
- width: 30px;
-
- height: 30px;
-
- }
-
- .text {
-
- display: inline-block;
-
- padding: 0 10px;
-
- max-width: 80%;
-
- background-color: #b2e281;
-
- border-radius: 4px;
-
- line-height: 30px;
-
- }
-
- }
-
- }
-
- </style>
usertext.vue
- <template>
-
- <div id="uesrtext">
-
- <textarea placeholder="按 Ctrl + Enter 发送" v-model="content" v-on:keyup="addMessage"></textarea>
-
- </div>
-
- </template>
-
- <script>
-
- import {mapState} from 'vuex'
-
- export default {
-
- name: 'uesrtext',
-
- data() {
-
- return {
-
- content: ''
-
- }
-
- },
-
- computed: mapState([
-
- 'currentSession'
-
- ]),
-
- methods: {
-
- addMessage(e) {
-
- if (e.ctrlKey && e.keyCode === 13 && this.content.length) {
-
- // 自定义发送消息
-
- let msgObj = {}
-
- // let msgObj = new Object()
-
- msgObj.to = this.currentSession.username
-
- msgObj.content = this.content
-
- this.$store.state.stomp.send('/ws/chat', {}, JSON.stringify(msgObj))
-
- this.$store.commit('addMessage', msgObj);
-
- this.content = '';
-
- }
-
- }
-
- }
-
- }
-
- </script>
-
- <style lang="scss" scoped>
-
- #uesrtext {
-
- position: absolute;
-
- bottom: 0;
-
- right: 0;
-
- width: 100%;
-
- height: 30%;
-
- border-top: solid 1px #DDD;
-
- > textarea {
-
- padding: 10px;
-
- width: 100%;
-
- height: 100%;
-
- border: none;
-
- outline: none;
-
- }
-
- }
-
- </style>
-
- chat/FriendChat.vue
-
- <template>
-
- <div id="app">
-
- <div class="sidebar">
-
- <card></card>
-
- <list></list>
-
- </div>
-
- <div class="main">
-
- <message></message>
-
- <userText></userText>
-
- </div>
-
- </div>
-
- </template>
-
- <script>
-
- import card from '@/components/chat/card.vue'
-
- import list from '@/components/chat/list.vue'
-
- import message from '@/components/chat/message.vue'
-
- import userText from '@/components/chat/usertext.vue'
-
- export default {
-
- name: 'FriendChat',
-
- data () {
-
- return {
-
- }
-
- },
-
- mounted:function() {
-
- this.$store.dispatch('initData');
-
- },
-
- components:{
-
- card,
-
- list,
-
- message,
-
- userText
-
- }
-
- }
-
- </script>
-
- <style lang="scss" scoped>
-
- #app {
-
- margin: 20px 100px;
-
- //margin: 20px auto;
-
- width: 800px;
-
- height: 600px;
-
- overflow: hidden;
-
- border-radius: 10px;
-
- border: 1px solid #c8c9c9;
-
- .sidebar, .main {
-
- height: 100%;
-
- }
-
- .sidebar {
-
- float: left;
-
- color: #f4f4f4;
-
- background-color: #2e3238;
-
- width: 200px;
-
- }
-
- .main {
-
- position: relative;
-
- overflow: hidden;
-
- background-color: #eee;
-
- }
-
- }
-
- </style>
十三、个人中心
AdminInfo.vue
- <template>
-
- <div>
-
- <el-card class="box-card" style="width: 400px;">
-
- <div slot="header" class="clearfix">
-
- <span>{{ admin.name }}</span>
-
- </div>
-
- <div>
-
- <div>
-
- <div style="display: flex;justify-content: center;">
-
- <img title="点击修改用户头像" :src="admin.userFace" style="height: 100px;width: 100px;border-radius: 50px;" alt="">
-
- </div>
-
- <div>电话号码:
-
- <el-tag>{{ admin.telephone }}</el-tag>
-
- </div>
-
- <div>手机号码:
-
- <el-tag>{{ admin.phone }}</el-tag>
-
- </div>
-
- <div>居住地址:
-
- <el-tag>{{ admin.address }}</el-tag>
-
- </div>
-
- <div>用户标签:
-
- <el-tag type="success" v-for="(r,index) in admin.roles" :key="index">{{ r.nameZh }}</el-tag>
-
- </div>
-
- </div>
-
- <div style="display: flex;justify-content: space-around;margin-top: 10px;">
-
- <!-- 1-3 @click="showUpdateAdminInfoView" -->
-
- <el-button type="primary" size="mini" @click="showUpdateAdminInfoView">修改信息</el-button>
-
- <!-- 2-1 用户修改密码 @click="showUpdatePasswordView" -->
-
- <el-button type="danger" size="mini" @click="showUpdatePasswordView">修改密码</el-button>
-
- </div>
-
- </div>
-
- </el-card>
-
- <!-- 1-1 编辑用户信息 -->
-
- <el-dialog
-
- title="编辑用户信息"
-
- :visible.sync="dialogVisible"
-
- width="30%">
-
- <div>
-
- <table>
-
- <tr>
-
- <td>用户昵称:</td>
-
- <td>
-
- <!-- 1-5 重新给每项赋值 admin2 -->
-
- <el-input v-model="admin2.name"></el-input>
-
- </td>
-
- </tr>
-
- <tr>
-
- <td>电话号码:</td>
-
- <td>
-
- <el-input v-model="admin2.telephone"></el-input>
-
- </td>
-
- </tr>
-
- <tr>
-
- <td>手机号码:</td>
-
- <td>
-
- <el-input v-model="admin2.phone"></el-input>
-
- </td>
-
- </tr>
-
- <tr>
-
- <td>用户地址:</td>
-
- <td>
-
- <el-input v-model="admin2.address"></el-input>
-
- </td>
-
- </tr>
-
- </table>
-
- </div>
-
- <span slot="footer" class="dialog-footer">
-
- <el-button @click="dialogVisible = false">取 消</el-button>
-
- <!-- 1-8 @click="updateAdminInfo" -->
-
- <el-button type="primary" @click="updateAdminInfo">确 定</el-button>
-
- </span>
-
- </el-dialog>
-
- <!-- 2-2 修改密码 -->
-
- <el-dialog
-
- title="更新密码"
-
- :visible.sync="passwordDialogVisible"
-
- width="30%">
-
- <div>
-
- <!-- 2-8 调整修改密码表单 -->
-
- <el-form :model="ruleForm" status-icon :rules="rules" ref="ruleForm" label-width="100px" class="demo-ruleForm">
-
- <el-form-item label="请输入旧密码" prop="oldPass">
-
- <el-input type="password" v-model="ruleForm.oldPass" autocomplete="off"></el-input>
-
- </el-form-item>
-
- <el-form-item label="请输入新密码" prop="pass">
-
- <el-input type="password" v-model="ruleForm.pass" autocomplete="off"></el-input>
-
- </el-form-item>
-
- <el-form-item label="确认新密码" prop="checkPass">
-
- <el-input type="password" v-model="ruleForm.checkPass" autocomplete="off"></el-input>
-
- </el-form-item>
-
- <el-form-item>
-
- <el-button type="primary" @click="submitForm('ruleForm')">提交</el-button>
-
- <el-button @click="resetForm('ruleForm')">重置</el-button>
-
- </el-form-item>
-
- </el-form>
-
- </div>
-
- </el-dialog>
-
- </div>
-
- </template>
-
- <script>
-
- export default {
-
- name: "AdminInfo",
-
- data() {
-
- // 2-5 修改密码校验规则 一定要放最前面
-
- var validatePass = (rule, value, callback) => {
-
- if (value === '') {
-
- callback(new Error('请输入密码'));
-
- } else {
-
- if (this.ruleForm.checkPass !== '') {
-
- this.$refs.ruleForm.validateField('checkPass');
-
- }
-
- callback();
-
- }
-
- }
-
- var validatePass2 = (rule, value, callback) => {
-
- if (value === '') {
-
- callback(new Error('请再次输入密码'));
-
- } else if (value !== this.ruleForm.pass) {
-
- callback(new Error('两次输入密码不一致!'));
-
- } else {
-
- callback();
-
- }
-
- }
-
- return {
-
- admin: null,
-
- admin2: null, // 1-5 编辑的对象
-
- dialogVisible: false, // 1-2 编辑用户信息
-
- passwordDialogVisible: false, // 2-3 修改密码
-
- ruleForm: { // 2-6 校验对象 规则
-
- pass: '',
-
- checkPass: '',
-
- oldPass: '', // 2-9
-
- },
-
- rules: {
-
- pass: [
-
- {validator: validatePass, trigger: 'blur'}
-
- ],
-
- checkPass: [
-
- {validator: validatePass2, trigger: 'blur'}
-
- ],
-
- oldPass: [
-
- {validator: validatePass, trigger: 'blur'}
-
- ]
-
- }
-
- }
-
- },
-
- mounted() {
-
- this.initAdmin()
-
- },
-
- methods: {
-
- // 2-7 预校验 提交表单
-
- submitForm(formName) {
-
- this.$refs[formName].validate((valid) => {
-
- if (valid) {
-
- // alert('submit!');
-
- this.ruleForm.adminId = this.admin.id
-
- this.putRequest('/admin/pass', this.ruleForm).then(resp => {
-
- if (resp) {
-
- // 更新密码成功后 退出登录
-
- this.postRequest('/logout') // 退出登录
-
- window.sessionStorage.removeItem('user')
-
- window.sessionStorage.removeItem('tokenStr')
-
- this.$store.commit('initRoutes', []) // 初始化路由 菜单 置空
-
- this.$router.replace('/') // 跳到登录页面
-
- }
-
- })
-
- } else {
-
- console.log('error submit!!');
-
- return false;
-
- }
-
- });
-
- },
-
- // 2-7 重围修改密码表单
-
- resetForm(formName) {
-
- this.$refs[formName].resetFields();
-
- },
-
- // 2-4 修改密码
-
- showUpdatePasswordView() {
-
- this.passwordDialogVisible = true
-
- },
-
- // 1-9 更新用户
-
- updateAdminInfo() {
-
- this.putRequest('/admin/info', this.admin2).then(resp => {
-
- if (resp) {
-
- this.dialogVisible = false
-
- this.initAdmin()
-
- }
-
- })
-
- },
-
- // 1-4 编辑用户信息弹框
-
- showUpdateAdminInfoView() {
-
- this.dialogVisible = true
-
- },
-
- initAdmin() {
-
- this.getRequest('/admin/info').then(resp => {
-
- if (resp) {
-
- this.admin = resp
-
- this.admin2 = Object.assign({}, this.admin) // 1-6 对象拷贝给 admin2
-
- window.sessionStorage.setItem('user', JSON.stringify(resp))
-
- this.$store.commit('INIT_ADMIN', resp)
-
- }
-
- })
-
- }
-
- }
-
- }
-
- </script>
-
- <style scoped>
-
- </style>
十四、源代码
前端地址:
后端地址: