目录

简介

运行项目

项目结构

开发记录

禁止点击事件的触发

v-for循环对象

使用vue-create-api快速创建全局公共组件

vue-create-api使用技巧(如何进行组件通信以及销毁的注意事项)

动态组件切换及过渡效果

vue项目引入Echarts

mixin混入与写utils方法导入的区别

axios前后端交互注意事项

express处理POST请求

数据库操作细节问题

利用数据库级联操作


简介

github地址:https://github.com/ChenMingK/staffSystem

项目体验地址:http://cmk1018.cn:8084/#/login

个人博客:http://www.cmk1018.cn

如果你觉得对你有帮助,就给个star呗~


上截图:

登录页

首页

信息管理界面:分为用户视图和管理员视图,剩下的几个界面都是相似的

啰嗦一句:为什么不用vue-element-ui?UI框架很适合做后台管理系统之类的,一是因为我还没看里面的API吧,二是想练一下CSS,如果你只会用UI那你写出来的东西又怎么能体现出与众不同呢?之后打算用vue+vue-elementUI和react+antd重写一下

运行项目

1.把node-server文件夹下的建表和插入数据的sql语句在自己的mysql中执行
2.修改node-server文件夹下的const.js中的相关信息:dbUser和dbPwd改为自己要连接的数据库名和密码
3.下载前端文件,修改.env.development(开发环境下的环境变量)中的VUE_APP_BASE_URL改为自己的node服务监听的IP和端口
4.npm run serve启动服务

简单来说只要改两个地方:一是前端的AJAX请求地址,这个我已经设计为一个环境变量了,这样只要改着一个变量就相当于改了所有AJAX请求的URL地址(IP+端口号);二是服务端的连接数据库用到的用户名和密码,这个我也提取出来放在const.js文件夹下了,如果这都不会改的话建议看下node.js如何连接mysql

项目结构

开发记录

禁止点击事件的触发

需求分析:以用户身份登录时不能允许添加、删除记录操作,需要将按钮置为灰色同时原先设置的点击事件不能触发,通过权限值(privilege)来动态绑定样式。

<div class="edit-btn"
     :class="{'btn-disabled': privilege === 0}"
     @click="showEditComponent(item)"> <!--根据权限来判断是否禁用按钮-使用动态样式-->
  <span>编辑</span>
</div>

方案1:纯CSS实现,给动态绑定的class添加一下样式

  • 鼠标悬浮到该元素上时显示禁止红圈圈cursor: not-allowed
  • 鼠标原有的事件不能实现pointer-events:none

这样做有一个问题,设置了pointer-events后发现cursor样式失效了,即鼠标悬浮在该按钮上不是禁止的样式而是默认的样式

方案2:JS在事件处理程序中做相应判断并返回

  • 绑定的事件回调中通过event.currentTarget获取触发点击事件的节点(绑定该事件的节点),event.target是你点击的节点,其会向上冒泡到event.currentTarget
  • 获取节点后判断其类名中是否存在我们动态添加的'btn-disabled'
showEditComponent() {
  let node = event.currentTarget // 获取触发点击事件的节点
  if (node.className.search('btn-disabled') !== -1) { // node.className返回一个字符串包含类名
    return // 不会创建表单
  }
}

v-for循环对象

需求分析:创建表单时,需要知道哪些信息需要编辑,对于添加一条记录来说只需要员工信息的key,对于编辑一条记录而言不仅需要key,还需要value(即已有的值);显然我们需要传一个对象给表单组件,一般用v-for来遍历数组,那么v-for能不能遍历对象呢?当然可以。

<!--对象遍历:注意书写顺序,value在key之前-->
<div v-for="(value, key, index) in object" :key="key">
  {{ index }}. {{ key }} - {{ value }}
</div>
<!--数组遍历-->
<div v-for="(value, index) in array" :key="index">
  {{ value }} - {{ index }}
</div>
<!--数组对象遍历-->
<div v-for="(value, key, index) in objectArray" :key="index">
  {{ value }} - {{ index }}
</div>

使用vue-create-api快速创建全局公共组件

Github: https://github.com/cube-ui/vue-create-api/blob/master/README_zh-CN.md

1.cnpm i -S vue-create-api
2.utils文件夹下新建create-api.js

// use vue-create-api
// 通过API的形式调用组件
// 会在body()最外层添加组件,一般弹窗之类的才会使用(全屏)
// 使用vue-create-api的组件必须增加一个name属性
import CreateAPI from 'vue-create-api'
import Vue from 'vue'
import Toast from '../components/common/Toast' // 需要使用的公共组件
import Popup from '../components/common/Popup'
import GroupDialog from '../components/shelf/ShelfGroupDialog.vue'

Vue.use(CreateAPI)
Vue.createAPI(Toast, true)
Vue.createAPI(Popup, true)
Vue.createAPI(GroupDialog, true)

3.组件中使用(JS创建组件)

main.js导入import './utils/create-api'

// Vue.createAPI(Toast, true) => this.$createToast
this.$createToast({
  $props: {

  }
}).show() // 前面完成DOM的创建, show()方法完成组件的展示

注意vue-create-api使用的组件必须添加一个name属性如name: 'toast',

可以看到这类组件应该为全屏的,因为vue-create-api是在与App组件同级创建新的组件的

 

vue-create-api使用技巧(如何进行组件通信以及销毁的注意事项)

this.formComponent = this.$createForm({
  $props: {
    inputList: GATE_FORM,
    tableName: 'atendence'
  },
  $events: {
    submit: this.flushData, // 使用flushData方法作为子组件submit事件的回调
    hide: this.hideForm // 点击取消时删除组件
  }
}).show()

这段代码利用vue-create-api创建了一个全局组件,$events选项可以设置响应子组件事件的回调,比如我创建一个全局的表单组件,用户往里面填写了数据后点击提交触发$emit('submit', args),那么这个父组件就能响应这个事件。这样就可以方便地进行组件通信了。

 

动态组件切换及过渡效果

左侧tab的实现本来应该用前端路由来实现的,即点击不同的tab切换不同的路由视图;这里使用的是动态组件的方式,即切换不同的tab就切换main部分(右侧)的组件,好处是我们如果想实现过渡效果,比如淡入淡出,只需要在外层添加transition即可。

<transition name="fade">
  <component :is="chooseComponent(currentTab)"></component>
</transition>

vue项目引入Echarts

  • npm install echarts
  • main.js添加如下代码
import echarts from 'echarts'
Vue.prototype.$echarts = echarts
  • 使用时注意将echarts.init()'之类的改为this.$echarts.init(dom) new this.$echarts.graphic.LinearGradient`
  • 也有说法可以按需导入什么的

mixin混入与写utils方法导入的区别

mixin混入相比(公共方法)写在utils的好处是可以不用手动绑定this直接通过this.xxx调用公有方法,因为其相当于把methods注入了methods选项中,书写更为简洁

比如分页操作

paging(data) {
  let arr = []
  let pages = Math.floor(data.length / 10) + 1
  for (let i = 0; i < pages; i++) {
    arr[i] = []
    for (let j = 0; j < 10; j++) {
      if ((i === pages - 1) && (i * 10 + j === data.length)) { // 25 -> max: [2][4]
        break
      } 
      arr[i][j] = data[i * 10 + j]
    }
  }
  this.pagingList = arr
  this.allPages = arr.length + 1
}

每个组件的data选项都有pagingList和allPages这两个属性,使用mixin则直接this.paging(data)即可,如果写成一个方法export导出import导入就需要paging.call(this, data)

axios前后端交互注意事项

params选项是携带在URL后面的参数,即URL&后面的,data选项对于GET请求无效,POST请求如果想传送一个对象给后端需要将这个对象使用JSON.stringify序列化,后端通过JSON.parse来获取这个对象(如果不这么做会无法传输?)

export function insertData(table, paramObj) {
  return axios({
    method: 'post', // POST请求data选项才有用?
    url: `${process.env.VUE_APP_BASE_URL}/insert`,
    params: {
      table // 需要插入的表
    },
    data: {
      obj: JSON.stringify(paramObj) // 必须为序列化的对象? plain object?
    }
  })
}

express处理POST请求

需要添加处理POST请求的中间件

  • npm install body-parser
  • cosnt bodyParser = require('body-parser)
  • app.use(bodyParser.json())
  • app.use(bodyParser.urlencoded({extended: false}))

前端axios的data选项可以通过req.body来接收,params选项可以通过req.query来接收

数据库操作细节问题

const sql = `SELECT * FROM user WHERE username = '${query.username}'` 
// 注意这里使用${}外面要加引号,sql语句本身视为字符串,'user1'是要加上字符串的`

利用数据库级联操作

员工信息表中的emp_number(员工编号)作为其他表的外键被引用,那么删除员工信息的时候就会有一个问题:由于员工信息中的员工编号是作为外键被其他表引用的,数据库会报错。 这就需要利用数据库的级联操作,我们在建表时添加foreign key(emp_number) references information(emp_number) on delete cascade 字样,这样我们后台连接数据库执行删除员工信息的时候只需要普通地执行删除操作数据库就会自动帮我们删除其他表中引用该外键的相关记录。如果不这么做的话,可能需要先查询其他表中对应的记录全部删除后再删除本表的记录。

create table leave_t(leave_number int primary key,
                    leave_date varchar(20),
                    emp_number int,
                    leave_reason varchar(50),
                    req_date varchar(20),
                    leave_approve varchar(5),
                    foreign key(emp_number) references information(emp_number)
on delete cascade);