【vue-iHRM】07-02员工管理模块

员工详情【vue-iHRM】07-02员工管理模块

员工详情页

需求:重置密码;展示员工的个人信息和岗位信息(支持图片的上传----上传到第三方平台:腾讯云)

详情页的基本布局和路由

  • 建立详情页路由 router/modules/employees.js
    • 配置二级路由,并且控制左侧菜单不显示
    • 动态路由参数的获取方式:1、$route.params.id;2、利用路由映射的props:true 配置,组件中通过props获取即可
{
  path: 'detail/:id', // 动态路由参数
  component: () => import('@/views/employees/detail'),
  hidden: true,
  meta: {
    title: '员工详情'
  }
}
  • 建立基本架构
<template>
  <div class="employees-detail-container">
    <div class="app-container">
      <el-card>
        <el-tabs>
          <el-tab-pane label="登录账号设置">
            <!-- 放置表单 -->
            <el-form label-width="120px" style="margin-left: 120px; margin-top:30px">
              <el-form-item label="姓名:">
                <el-input style="width:300px" />
              </el-form-item>
              <el-form-item label="密码:">
                <el-input style="width:300px" type="password" />
              </el-form-item>
              <el-form-item>
                <el-button type="primary">更新</el-button>
              </el-form-item>
            </el-form>
          </el-tab-pane>
          <el-tab-pane label="个人详情">
            <!-- 内容 -->
          </el-tab-pane>
          <el-tab-pane label="岗位信息">
            <!-- 内容 -->
          </el-tab-pane>
        </el-tabs>
      </el-card>
    </div>
  </div>
</template>

<script>
export default {
  name: 'EmployeesDetail'
}
</script>
  • 列表跳转到详情页
<el-button type="text" size="small" @click="$router.push(`/employees/detail/${scope.row.id}`)">查看</el-button>

总结:配置路由;准备模板;控制跳转

注意:动态路由的参数传递方式:1、基于$route.parems.id ;2、基于路由映射的props进行控制

获取用户信息

  • 发送请求获取数据
import { reqGetUserDetailById } from '@/api/user.js'
export default {
  name: 'EmployeesDetail',
  data () {
    return {
     pwdInfo: {
        username: '',
        password: ''
      }
    }
  },
  created () {
    this.loadUserInfo()
  },
  methods: {
    // 加载用户名和密码
    async loadUserInfo () {
      const ret = await getDetailInfo(this.id)
      this.pwdInfo.username = ret.data.username
    }
  }
}

注意:这里接口中读取的是后端的密文,我们并不能解密,所以我们不回显原密码!

data() {
  return {
    pwdInfo: {
      username: '',
      newPassword: ''
    },
    rules: {
      username: [
        { required: true, message: '姓名不能为空', trigger: ['blur', 'change'] }
      ],
      password: [
        { required: true, message: '密码不可以为空', trigger: ['blur', 'change'] },
        { min: 6, max: 9, message: '密码必须是6-9位', trigger: ['blur', 'change'] }
      ]
    }
  }
},
<!-- 放置表单 -->
<el-form ref="pwdForm" :model="pwdInfo" :rules="rules" label-width="120px" style="margin-left: 120px; margin-top:30px">
  <el-form-item label="姓名:" prop="username">
    <el-input v-model="pwdInfo.username" style="width:300px" />
  </el-form-item>
  <el-form-item label="密码:" prop="password">
    <el-input v-model="pwdInfo.password" style="width:300px" type="password" />
  </el-form-item>
  <el-form-item>
    <el-button type="primary" @click="handleUpdate">更新</el-button>
  </el-form-item>
</el-form>

总结:调用接口,获取数据,填充表单

注意:密码不需要填充表单(加密密码无法反解)

更新用户名密码

  • 保存个人基本信息接口 src/api/employees.js
export function reqSaveUserDetailById(data) {
  return request({
    method: 'put',
    url: `/sys/user/${data.id}`,
    data
  })
}
  • 用户名和密码的修改 src/views/employees/detail.vue
<el-button type="primary" @click="handleSubmit">更新</el-button>
import { reqSaveUserDetailById } from '@/api/employees'

// 重置密码
handleUpdate () {
    this.$refs.pwdForm.validate(async valid => {
        if (!valid) return
        // 调用接口更新
        const ret = await reqSaveUserDetailById({
            ...this.pwdInfo,
            id: this.id
        })
        this.$message.success(ret.message)
    })
},

总结:

  1. 提交表单时,需要提交输入的明文密码
  2. 对象的解构赋值用法

个人组件和岗位组件封装

封装个人详情组件

我们将员工个人信息分为三部分,账户,个人, 岗位,这个小节我们对个人组件和岗位组件进行封装

封装个人组件 src/views/employees/components/UserInfo.vue

  • 基本模板
<template>
  <div class="user-info">
    <!-- 个人信息 -->
    <el-form label-width="220px">
      <!-- 工号 入职时间 -->
      <el-row class="inline-info">
        <el-col :span="12">
          <el-form-item label="工号">
            <el-input v-model="userInfo.workNumber" class="inputW" />
          </el-form-item>
        </el-col>
        <el-col :span="12">
          <el-form-item label="入职时间">
            <el-date-picker
              v-model="userInfo.timeOfEntry"
              type="date"
              class="inputW"
              value-format="YYYY-MM-DD"
            />
          </el-form-item>
        </el-col>
      </el-row>
      <!-- 姓名 部门 -->
      <el-row class="inline-info">
        <el-col :span="12">
          <el-form-item label="姓名">
            <el-input v-model="userInfo.username" class="inputW" />
          </el-form-item>
        </el-col>
        <el-col :span="12">
          <el-form-item label="部门">
            <el-input v-model="userInfo.departmentName" class="inputW" />
          </el-form-item>
        </el-col>
      </el-row>
      <!--手机 聘用形式  -->
      <el-row class="inline-info">
        <el-col :span="12">
          <el-form-item label="手机">
            <el-input v-model="userInfo.mobile" />
          </el-form-item>
        </el-col>
        <el-col :span="12">
          <el-form-item label="聘用形式">
            <el-select v-model="userInfo.formOfEmployment" class="inputW">
              <el-option
                v-for="item in EmployeeEnum.hireType"
                :key="item.id"
                :label="item.value"
                :value="item.id"
              />
            </el-select>
          </el-form-item>
        </el-col>
      </el-row>
      <!-- 员工照片 -->
      <el-row class="inline-info">
        <el-col :span="12">
          <el-form-item label="员工头像">
            <!-- 放置上传图片 -->
           
          </el-form-item>
        </el-col>
      </el-row>
      <!-- 保存个人信息 -->
      <el-row class="inline-info" type="flex" justify="center">
        <el-col :span="12">
          <el-button type="primary" @click="saveUser">保存更新</el-button>
          <el-button @click="$router.back()">返回</el-button>

        </el-col>
      </el-row>
    </el-form>
    <!-- 基础信息 -->
    <el-form label-width="220px">
      <div class="block">
        <div class="title">基础信息</div>
        <el-form-item label="最高学历">
          <el-select v-model="formData.theHighestDegreeOfEducation" class="inputW2">
            <el-option
              v-for="item in EmployeeEnum.highestDegree"
              :key="item.value"
              :label="item.label"
              :value="item.value"
            />
          </el-select>
        </el-form-item>
        <!-- 个人头像 -->
        <!-- 员工照片 -->

        <el-form-item label="员工照片">
          <!-- 放置上传图片 -->
        </el-form-item>
        <el-form-item label="国家/地区">
          <el-select v-model="formData.nationalArea" class="inputW2">
            <el-option
              v-for="item in EmployeeEnum.isOverseas"
              :key="item.value"
              :label="item.label"
              :value="item.value"
            />
          </el-select>
        </el-form-item>
        <el-form-item label="护照号">
          <el-input v-model="formData.passportNo" placeholder="正规护照格式" class="inputW" />
        </el-form-item>
        <el-form-item label="身份证号">
          <el-input v-model="formData.idNumber" placeholder="正规身份证格式" class="inputW" />
        </el-form-item>
        <el-form-item label="籍贯">
          <el-input v-model="formData.nativePlace" placeholder="籍贯地址" class="inputW5" />
        </el-form-item>
        <el-form-item label="民族">
          <el-input v-model="formData.nation" placeholder="请输入民族" class="inputW2" />
        </el-form-item>
        <el-form-item label="婚姻状况">
          <el-select v-model="formData.maritalStatus" class="inputW2">
            <el-option
              v-for="item in EmployeeEnum.maritaStatus"
              :key="item.value"
              :label="item.label"
              :value="item.value"
            />
          </el-select>
        </el-form-item>
        <el-form-item label="生日">
          <el-input v-model="formData.birthday" placeholder="示例 0323" class="inputW" />
        </el-form-item>
        <el-form-item label="年龄">
          <el-input v-model="formData.age" type="number" class="inputW2" />
        </el-form-item>
        <el-form-item label="星座">
          <el-select v-model="formData.constellation" class="inputW2">
            <el-option
              v-for="item in EmployeeEnum.constellation"
              :key="item.value"
              :label="item.label"
              :value="item.value"
            />
          </el-select>
        </el-form-item>
        <el-form-item label="血型">
          <el-select v-model="formData.bloodType" class="inputW2">
            <el-option
              v-for="item in EmployeeEnum.bloodType"
              :key="item.value"
              :label="item.label"
              :value="item.value"
            />
          </el-select>
        </el-form-item>
        <el-form-item label="户籍所在地">
          <el-input v-model="formData.domicile" class="inputW5" />
        </el-form-item>
        <el-form-item label="政治面貌">
          <el-input v-model="formData.politicalOutlook" class="inputW2" />
        </el-form-item>
        <el-form-item label="入党时间">
          <el-date-picker
            v-model="formData.timeToJoinTheParty"
            type="date"
            placeholder="选择日期"
            class="inputW"
            value-format="yyyy-MM-dd"
          />
        </el-form-item>
        <el-form-item label="存档机构">
          <el-input v-model="formData.archivingOrganization" placeholder="请输入" />
        </el-form-item>
        <el-form-item label="子女状态">
          <el-input v-model="formData.stateOfChildren" placeholder="请输入" />
        </el-form-item>
        <el-form-item label="子女有无商业险">
          <el-radio-group v-model="formData.doChildrenHaveCommercialInsurance">
            <el-radio label="1">有</el-radio>
            <el-radio label="2">无</el-radio>
          </el-radio-group>
        </el-form-item>
        <el-form-item label="有无违法违纪状态">
          <el-input v-model="formData.isThereAnyViolationOfLawOrDiscipline" placeholder="请输入" />
        </el-form-item>
        <el-form-item label="有无重大病史">
          <el-input v-model="formData.areThereAnyMajorMedicalHistories" placeholder="请输入" />
        </el-form-item>
      </div>
      <!-- 通讯信息 -->
      <div class="block">
        <div class="title">通讯信息</div>
        <el-form-item label="QQ">
          <el-input v-model="formData.qq" placeholder="请输入" class="inputW" />
        </el-form-item>
        <el-form-item label="微信">
          <el-input v-model="formData.wechat" placeholder="请输入" class="inputW" />
        </el-form-item>
        <el-form-item label="现居住地">
          <el-input v-model="formData.placeOfResidence" placeholder="请输入" />
        </el-form-item>
        <el-form-item label="通讯地址">
          <el-input v-model="formData.postalAddress" placeholder="请输入" />
        </el-form-item>
        <el-form-item label="联系手机">
          <el-input v-model="formData.contactTheMobilePhone" placeholder="11位字符" maxlength="11" class="inputW" @change.native="handlePhone(2)" />
        </el-form-item>
        <el-form-item label="个人邮箱">
          <el-input v-model="formData.personalMailbox" placeholder="请输入" type="mail" class="inputW" />
        </el-form-item>
        <el-form-item label="紧急联系人">
          <el-input v-model="formData.emergencyContact" placeholder="请输入" class="inputW" />
        </el-form-item>
        <el-form-item label="紧急联系电话">
          <el-input v-model="formData.emergencyContactNumber" placeholder="11位字符" class="inputW" />
        </el-form-item>
      </div>
      <!-- 账号信息 -->
      <div class="block">
        <div class="title">账号信息</div>
        <el-form-item label="社保电脑号">
          <el-input v-model="formData.socialSecurityComputerNumber" placeholder="请输入" class="inputW" />
        </el-form-item>
        <el-form-item label="公积金账号">
          <el-input v-model="formData.providentFundAccount" placeholder="请输入" class="inputW" />
        </el-form-item>
        <el-form-item label="银行卡号">
          <el-input v-model="formData.bankCardNumber" placeholder="请输入" class="inputW" />
        </el-form-item>
        <el-form-item label="开户行">
          <el-input v-model="formData.openingBank" placeholder="请输入" class="inputW" />
        </el-form-item>
      </div>
      <!-- 教育信息 -->
      <div class="block">
        <div class="title">教育信息</div>
        <el-form-item label="学历类型">
          <el-select v-model="formData.educationalType" placeholder="请选择">
            <el-option
              v-for="item in EmployeeEnum.educationType"
              :key="item.value"
              :label="item.label"
              :value="item.value"
            />
          </el-select>
        </el-form-item>
        <el-form-item label="毕业学校">
          <el-input v-model="formData.graduateSchool" placeholder="请输入" class="inputW2" />
        </el-form-item>
        <el-form-item label="入学时间">
          <el-date-picker v-model="formData.enrolmentTime" type="data" placeholder="请输入时间" class="inputW" value-format="yyyy-MM-dd" />
        </el-form-item>
        <el-form-item label="毕业时间">
          <el-date-picker v-model="formData.graduationTime" type="data" placeholder="请输入时间" class="inputW" value-format="yyyy-MM-dd" />
        </el-form-item>
        <el-form-item label="专业">
          <el-input v-model="formData.major" placeholder="请输入" class="inputW" />
        </el-form-item>
      </div>
      <!-- 从业信息 -->
      <div class="block">
        <div class="title">从业信息</div>
        <el-form-item label="上家公司">
          <el-input v-model="formData.homeCompany" placeholder="请输入" class="inputW" />
        </el-form-item>
        <el-form-item label="职称">
          <el-input v-model="formData.title" placeholder="请输入" class="inputW" />
        </el-form-item>
        <el-form-item label="有无竞业限制">
          <el-input v-model="formData.isThereAnyCompetitionRestriction" placeholder="请输入" style="width:80%" />
        </el-form-item>
        <el-form-item label="备注">
          <el-input v-model="formData.remarks" type="textarea" placeholder="请输入备注" style="width:80%" />
        </el-form-item>
        <!-- 保存员工信息 -->
        <el-row class="inline-info" type="flex" justify="center">
          <el-col :span="12">
            <el-button type="primary" @click="savePersonal">保存更新</el-button>
            <el-button @click="$router.back()">返回</el-button>
          </el-col>
        </el-row>
      </div>
    </el-form>

  </div>

</template>

本章节个人数据过于**繁杂,庞大**,同学们在开发期间,拷贝代码即可,我们只写关键部位的代码

  • 定义user-info的数据
<script>
import EmployeeEnum from '@/api/constant/employees'

export default {
  data() {
    return {
      EmployeeEnum, // 员工枚举数据
      userInfo: {
        workNumber: '', // 工号
        timeOfEntry: '', // 入职时间
        username: '', // 姓名
        departmentName: '', // 部门
        mobile: '', // 手机
        formOfEmployment: '' // 聘用形式
      },
      formData: {
        userId: '',
        username: '', // 用户名
        sex: '', // 性别
        mobile: '', // 手机
        companyId: '', // 公司id
        departmentName: '', // 部门名称
        //  onTheJobStatus: '', // 在职状态 no
        dateOfBirth: '', // 出生日期
        timeOfEntry: '', // 入职时间
        theHighestDegreeOfEducation: '', // 最高学历
        nationalArea: '', // 国家
        passportNo: '', // 护照号
        idNumber: '', // 身份证号
        idCardPhotoPositive: '', // 身份证照正
        idCardPhotoBack: '', // 身份证照正
        nativePlace: '', // 籍贯
        nation: '', // 民族
        englishName: '', // 英文名字
        maritalStatus: '', // 婚姻状况
        staffPhoto: '', // 员工照片
        birthday: '', // 生日
        zodiac: '', // 属相
        age: '', // 年龄
        constellation: '', // 星座
        bloodType: '', // 血型
        domicile: '', // 户籍所在地
        politicalOutlook: '', // 政治面貌
        timeToJoinTheParty: '', // 入党时间
        archivingOrganization: '', // 存档机构
        stateOfChildren: '', // 子女状态
        doChildrenHaveCommercialInsurance: '1', // 保险状态
        isThereAnyViolationOfLawOrDiscipline: '', // 违法违纪状态
        areThereAnyMajorMedicalHistories: '', // 重大病史
        qq: '', // QQ
        wechat: '', // 微信
        residenceCardCity: '', // 居住证城市
        dateOfResidencePermit: '', // 居住证办理日期
        residencePermitDeadline: '', // 居住证截止日期
        placeOfResidence: '', // 现居住地
        postalAddress: '', // 通讯地址
        contactTheMobilePhone: '', // 联系手机
        personalMailbox: '', // 个人邮箱
        emergencyContact: '', // 紧急联系人
        emergencyContactNumber: '', // 紧急联系电话
        socialSecurityComputerNumber: '', // 社保电脑号
        providentFundAccount: '', // 公积金账号
        bankCardNumber: '', // 银行卡号
        openingBank: '', // 开户行
        educationalType: '', // 学历类型
        graduateSchool: '', // 毕业学校
        enrolmentTime: '', // 入学时间
        graduationTime: '', // 毕业时间
        major: '', // 专业
        graduationCertificate: '', // 毕业证书
        certificateOfAcademicDegree: '', // 学位证书
        homeCompany: '', // 上家公司
        title: '', // 职称
        resume: '', // 简历
        isThereAnyCompetitionRestriction: '', // 有无竞业限制
        proofOfDepartureOfFormerCompany: '', // 前公司离职证明
        remarks: '' // 备注
      }
    }
  },
  computed: {
    userId() {
      return this.$route.params.id
    }
  },
  methods: {
    saveUser() {

    },
    savePersonal() {

    }
  }
}
</script>
  • 在detail.vue组件中,注册并使用
<el-tab-pane label="个人详情">
  <!-- 放置个人详情 -->
  <user-info />
</el-tab-pane>
import UserInfo from './components/user-info.vue'
components: {
  UserInfo
},

总结:准备模板和数据,然后导入组件并使用

封装岗位组件

封装岗位组件**src/views/employee/components/JobInfo.vue**

  • 基本模板
<template>
<div class="job-info">
    <!-- 基础信息 -->
    <el-form label-width="220px">
      <div class="block">
        <div class="title">基础信息</div>
        <el-form-item label="岗位">
          <el-input v-model="formData.post" placeholder="请输入" class="inputW" />
        </el-form-item>
        <!-- <el-form-item label="转正日期">
          <el-date-picker
            v-model="formData.dateOfCorrection"
            type="date"
            placeholder="选择日期"
            value-format="yyyy-MM-dd"
          />
        </el-form-item> -->
        <el-form-item label="转正状态">
          <el-select v-model="formData.stateOfCorrection" placeholder="请选择" disabled>
            <el-option
              v-for="item in EmployeeEnum.stateOfCorrection"
              :key="item.value"
              :value="item.value"
            />
          </el-select>
        </el-form-item>
        <el-form-item label="职级">
          <el-input v-model="formData.rank" class="inputW" />
        </el-form-item>
        <el-form-item label="转正评价">
          <el-input v-model="formData.correctionEvaluation" type="textarea" placeholder="1-300位字符" />
        </el-form-item>
        <el-form-item label="汇报对象">
          <el-select v-model="formData.reportId" filterable placeholder="请选择" class="inputW">
            <el-option v-for="item in list" :key="item.id" :label="item.username" :value="item.id" />
          </el-select>
        </el-form-item>
        <el-form-item label="HRBP">
          <el-select v-model="formData.hrbp" filterable placeholder="请选择" class="inputW">
            <el-option v-for="item in list" :key="item.id" :label="item.username" :value="item.id" class="inputW" />
          </el-select>
        </el-form-item>
        <el-form-item class="formInfo" label="调整司龄(天):">
          <el-input v-model="formData.adjustmentAgedays" type="number" placeholder="请输入" class="inputW" />
        </el-form-item>
        <el-form-item label="首次参加工作时间">
          <el-date-picker
            v-model="formData.workingTimeForTheFirstTime"
            type="date"
            placeholder="选择日期"
            value-format="yyyy-MM-dd"
          />
        </el-form-item>
        <el-form-item label="调整工龄">
          <el-input v-model="formData.adjustmentOfLengthOfService" placeholder="0.00年" class="inputW" disabled />
        </el-form-item>
      </div>
      <!-- 合同信息 -->
      <div class="block">
        <div class="title">合同信息</div>
        <el-form-item class="formInfo" label="首次合同开始时间:">
          <el-date-picker
            v-model="formData.initialContractStartTime"
            type="date"
            placeholder="选择日期"
            value-format="yyyy-MM-dd"
          />
        </el-form-item>
        <el-form-item label="首次合同结束时间">
          <el-date-picker
            v-model="formData.firstContractTerminationTime"
            type="date"
            placeholder="选择日期"
            value-format="yyyy-MM-dd"
          />
        </el-form-item>
        <el-form-item label="现合同开始时间">
          <el-date-picker
            v-model="formData.currentContractStartTime"
            type="date"
            placeholder="选择日期"
            value-format="yyyy-MM-dd"
          />
        </el-form-item>
        <el-form-item label="现合同结束时间">
          <el-date-picker
            v-model="formData.closingTimeOfCurrentContract	"
            type="date"
            placeholder="选择日期"
            value-format="yyyy-MM-dd"
          />
        </el-form-item>
        <el-form-item label="合同期限">
          <el-select v-model="formData.contractPeriod" class="filter-item">
            <el-option
              v-for="item in EmployeeEnum.contractPeriod"
              :key="item.value"
              :label="item.label"
              :value="item.value"
            />
          </el-select>
        </el-form-item>
        <el-form-item label="续签次数">
          <el-select v-model="formData.renewalNumber" class="filter-item">
            <el-option
              v-for="item in EmployeeEnum.renewalCount"
              :key="item.id"
              :label="item.value"
              :value="item.id"
            />
          </el-select>
        </el-form-item>
      </div>
      <!-- 招聘信息 -->
      <div class="block">
        <div class="title">招聘信息</div>
        <el-form-item label="其他招聘渠道">
          <el-select v-model="formData.otherRecruitmentChannels" placeholder="请选择">
            <el-option
              v-for="item in EmployeeEnum.resumeSource"
              :key="item.id"
              :label="item.value"
              :value="item.value"
            />
          </el-select>
        </el-form-item>
        <el-form-item label="招聘渠道">
          <el-select v-model="formData.recruitmentChannels" placeholder="请选择">
            <el-option
              v-for="item in EmployeeEnum.resumeSource"
              :key="item.value"
              :label="item.label"
              :value="item.value"
            />
          </el-select>
        </el-form-item>
        <el-form-item label="社招/校招">
          <el-select v-model="formData.socialRecruitment" placeholder="请选择">
            <el-option
              v-for="item in EmployeeEnum.hireSourceType"
              :key="item.value"
              :label="item.label"
              :value="item.value"
            />
          </el-select>
        </el-form-item>
        <el-form-item label="推荐企业/人">
          <el-input v-model="formData.recommenderBusinessPeople" placeholder="请输入" class="infoPosition inputW" />
        </el-form-item>
      </div>
      <!-- 从业信息 -->
      <el-form-item>
        <el-button type="primary" @click="saveJob">保存更新</el-button>
        <el-button @click="$router.back()">返回</el-button>
      </el-form-item>
    </el-form>
  </div>

</template>
  • 定义岗位数据
<script>
import EmployeeEnum from '@/api/constant/employees'

export default {
  data() {
    return {
      list: [],
      EmployeeEnum,
      formData: {
        adjustmentAgedays: '', // 调整司龄天
        adjustmentOfLengthOfService: '', // 调整工龄天
        closingTimeOfCurrentContract: '', // 现合同结束时间
        companyId: '', // 公司ID
        contractDocuments: '', // 合同文件
        contractPeriod: '', // 合同期限
        correctionEvaluation: '', //  转正评价
        currentContractStartTime: '', // 现合同开始时间
        firstContractTerminationTime: '', // 首次合同结束时间
        hrbp: '', // HRBP
        initialContractStartTime: '', // 首次合同开始时间
        otherRecruitmentChannels: '', // 其他招聘渠道
        post: '', // 岗位
        rank: null, // 职级
        recommenderBusinessPeople: '', // 推荐企业人
        recruitmentChannels: '', // 招聘渠道
        renewalNumber: '', // 续签次数
        reportId: '', // 汇报对象
        reportName: null, // 汇报对象
        socialRecruitment: '', // 社招校招
        stateOfCorrection: '', // 转正状态
        taxableCity: '', // 纳税城市
        userId: '', // 员工ID
        workMailbox: '', // 工作邮箱
        workingCity: '', // 工作城市
        workingTimeForTheFirstTime: '' // 首次参加工作时间
      }
    }
  },
  computed: {
    userId() {
      return this.$route.params.id
    }
  },
  methods: {
    saveJob() {

    }
  }
}
</script>
  • 在detail.vue组件中,注册并使用
<el-tab-pane label="岗位详情">
  <job-info />
</el-tab-pane>
import JobInfo from './components/job-info.vue'
components: {
  JobInfo
},

总结:准备模板和数据并使用组件

员工个人信息和岗位信息

读取保存个人信息

这个环节里面大部分都是繁杂的属性和重复的过程,所以该环节直接将过程代码拷贝到项目中即可

  • 封装 读取个人信息 保存个人信息 读取岗位信息 保存岗位信息
/** *
 *  读取用户详情的基础信息 (个人详情-下面的接口)
 * **/
export function reqGetPersonalDetail(id) {
  return request({
    method: 'get',
    url: `/employees/${id}/personalInfo`
  })
}

/** *
 *  更新用户详情的基础信息 (个人详情-下面的接口)
 * **/
export function reqUpdatePersonal(data) {
  return request({
    method: 'put',
    url: `/employees/${data.userId}/personalInfo`,
    data
  })
}


/** **
 * 获取用户的岗位信息  (岗位信息)
 * ****/
export function reqGetJobDetail(id) {
  return request({
    method: 'get',
    url: `/employees/${id}/jobs`
  })
}


/**
 * 保存岗位信息  (岗位信息)
 * ****/
export function reqUpdateJob(data) {
  return request({
    method: 'put',
    url: `/employees/${data.userId}/jobs`,
    data
  })
}
  • 读取,保存个人信息 user-info 需要注意:这里的保存实际上分成了两个接口,这是接口的设计,我们只能遵守
import { reqGetPersonalDetail, reqUpdatePersonal, reqSaveUserDetailById } from '@/api/employees'
import { getDetailInfo } from '@/api/user'

created() {
  this.getUserBaseInfo()
  this.getUserDetailInfo()
},
methods: {
    // 获取员工基本信息(上面的表单)
    async getUserBaseInfo () {
      try {
        const ret = await getDetailInfo(this.userId)
        if (!ret.success) {
          this.$message.error(ret.message)
        } else {
          this.userInfo = ret.data
        }
      } catch {
        this.$message.error('获取用户基本信息失败')
      }
    },
    // 获取详细信息(下面的表单)
    async getUserDetailInfo () {
      try {
        const ret = await reqGetPersonalDetail(this.userId)
        if (!ret.success) {
          this.$message.error(ret.message)
        } else {
          this.formData = ret.data
        }
      } catch {
        this.$message.error('获取用户详细信息失败')
      }
    },
    async saveUser() {
      try {
        const ret = await reqSaveUserDetailById(this.userInfo)
        if (!ret.success) {
          this.$message.error(ret.message)
        } else {
          this.$message.success(ret.message)
        }
      } catch {
        this.$message.error('更新用户基本信息失败')
      }
    },
    async savePersonal() {
      try {
        console.log(this.formData, this.userId)
        const ret = await reqUpdatePersonal({
          ...this.formData,
          // 当前修改的用户的id
          id: this.userId
        })
        if (!ret.success) {
          this.$message.error(ret.message)
        } else {
          this.$message.success(ret.message)
        }
      } catch {
        this.$message.error('更新用户基本信息失败')
      }
    }
}

总结:

  1. 获取个人信息
  2. 保存个人信息

读取保存岗位信息

  • 读取,保存岗位信息 job-info
// 获取员工的简单列表
export function reqGetEmployeeSimple () {
  return request({
    url: '/sys/user/simple'
  })
}

  • 功能实现
import { reqGetEmployeeSimple, reqGetJobDetail, reqUpdateJob } from '@/api/employees'

computed: {
  userId() {
    return this.$route.params.id
  }
},
created() {
    // 获取岗位信息
    this.getJobInfo()
    // 获取下拉列表数据
    this.getList()
},
methods: {
    // 获取岗位信息
    async getJobInfo () {
      try {
        const ret = await reqGetJobDetail(this.userId)
        if (!ret.success) {
          this.$message.error(ret.message)
        } else {
          this.formData = ret.data
        }
      } catch {
        this.$message.error('获取岗位信息失败')
      }
    },
    // 获取下拉列表数据
    async getList () {
      try {
        const ret = await reqGetEmployeeSimple()
        if (!ret.success) {
          this.$message.error(ret.message)
        } else {
          this.list = ret.data
        }
      } catch {
        this.$message.error('获取岗位信息失败')
      }
    },
    // 报错岗位信息
    async saveJob() {
      try {
        const ret = await reqUpdateJob({
          ...this.formData,
          userId: this.userId
        })
        if (!ret.success) {
          this.$message.error(ret.message)
        } else {
          this.$message.success('更新岗位信息成功')
        }
      } catch {
        this.$message.error('更新岗位信息失败')
      }
    }
}

总结:

  1. 获取岗位信息
  2. 保存岗位信息

配置腾讯云Cos

【vue-iHRM】07-02员工管理模块

账号注册 (实名认证)

目标: 配置一个腾讯云cos

由于上课的开发的特殊性,我们不希望把所有的图片都上传到我们自己的官方服务器上,

这里我们可以采用一个腾讯云的图片方案

【vue-iHRM】07-02员工管理模块

上边图的意思就是说,我们找一个可以免费上传图片的服务器,帮我们**代管图片,我们在自己的数据库里只保存一个地址就行, 这其实也是很多项目的处理方案,会有一个公共的文件服务器**

第一步,我们必须先拥有一个腾迅云的开发者账号(有时会有腾讯云的广告电话)

请按照腾讯云的注册方式,注册自己的账号

第二步,实名认证

选择个人账户
【vue-iHRM】07-02员工管理模块

填写个人身份信息
【vue-iHRM】07-02员工管理模块

下一步,扫描二维码授权
【vue-iHRM】07-02员工管理模块

手机端授权

【vue-iHRM】07-02员工管理模块

总结:注册腾讯云账号;登录系统;进行实名认证(填充个人相关信息)

开通服务

  • 点击云产品 - 对象存储

【vue-iHRM】07-02员工管理模块

  • 点击开通服务
    【vue-iHRM】07-02员工管理模块

创建存储桶

到这一步,账号的部分就操作完毕,接下来,我们需要来创建一个存储图片的存储桶

登录 对象存储控制台创建存储桶。设置存储桶的权限为 公有读,私有写

【vue-iHRM】07-02员工管理模块
【vue-iHRM】07-02员工管理模块

总结:存储桶可以理解为存储图片的一个位置(分组)

注意:选择所属地域;添加自定义存储桶名称;访问权限选择共有读私有写

设置cors规则

【vue-iHRM】07-02员工管理模块

AllowHeader 需配成*,如下图所示
【vue-iHRM】07-02员工管理模块

注意:因为我们本身没有域名,所以这里设置成*****,仅限于测试,正式环境的话,这里需要配置真实的域名地址

封装上传图片组件分析

目标 梳理整个的上传过程
【vue-iHRM】07-02员工管理模块

初始化 cos 对象参数

名称 描述
SecretId 开发者拥有的项目身份识别 ID,用以身份认证,可在 API 密钥管理 页面获取
SecretKey 开发者拥有的项目身份密钥,可在 API 密钥管理 页面获取

注意,上述的参数我们在本次开发过程中,直接将参数放置在前端代码中存储,

但是腾讯云本身是不建议这么做的,因为**敏感信息**放在前端很容易被捕获,一般放在后台, 前端准备参数, 交给后台和腾讯云交互, 交互时, 需要秘钥, 秘钥在后台存着的

由于我们本次是测试研发,所以这个过程可以忽略

正确的做法应该是,通过网站调用接口换取敏感信息

相关文档

实例化 上传sdk (这边还没用到, 先没配)

var cos = new COS({
    SecretId: 'AKIDc3E9nAG5dtulOlsiHdljSt3wYtwkIafT', // 身份识别 ID
    SecretKey: 'Zrsc8QRZS4PTFkyuZx4uUnqc7PQAcokP', // 身份密钥
});

到目前为止,我们上传图片准备的内容就已经OK,接下来,我们在**src/componets** 新建一个**ImageUpload** 组件

该上传组件需要满足什么要求呢?

  1. 可以显示传入的图片 (本地预览)
  2. 可以删除传入的图片
  3. 可以上传图片到云服务器
  4. 上传到腾讯云之后,可以返回图片地址,覆盖显示
  5. 上传成功之后,可以回调成功函数

封装上传组件代码实现

**目标**实现上传组件的代码部分

JavaScript SDK 需浏览器支持基本的 HTML5 特性(支持 IE10 以上浏览器),

以便支持 ajax 上传文件和计算文件 MD5 值。

新建文件上传组件

  • 安装JavaScript SDK
npm i cos-js-sdk-v5
  • 新建上传图片组件 src/components/ImageUpload/index.vue

上传组件,我们可以沿用element的el-upload组件,并且采用照片墙的模式 list-type="picture-card"

<template>
  <el-upload list-type="picture-card" action="">
     <i class="el-icon-plus" />
  </el-upload>
</template>
  • 全局注册组件
import PageTools from './PageTools'
import UploadExcel from './UploadExcel'
import ImageUpload from './ImageUpload'
export default {
  install(Vue) {
    Vue.component('PageTools', PageTools) // 注册工具栏组件
    Vue.component('UploadExcel', UploadExcel) // 注册导入excel组件
    Vue.component('ImageUpload', ImageUpload) // 注册导入上传组件
  }
}

总结:新建一个通用上传文件的组件,通过插件进行全局注册,然后在用户详细信息页面使用组件即可。

点击图片进行预览

  • 限定上传的图片数量和action
<template>
  <div class="upload-box">
    <el-upload
      :on-preview="preview"
      :file-list="fileList"
      list-type="picture-card"
      :limit="1"
      action="#"
    >
      <i class="el-icon-plus" />
    </el-upload>
  </div>
</template>

action为什么给#, 因为我们要上传到腾讯云,需要自定义的上传方式,action给个#防止报错

data() {
  return {
    fileList: [
      { url: 'https://fuss10.elemecdn.com/3/63/4e7f3a15429bfda99bce42a18cdd1jpeg.jpeg?imageMogr2/thumbnail/360x360/format/webp/quality/100' }
    ],
    showDialog: false, // 控制显示弹层
    currentImg: ''
  }
},
preview(file) {
  // 这里应该弹出一个层 层里是点击的图片地址
  this.currentImg = file.url
  this.showDialog = true
},
  • 预览弹层
<el-dialog width="600px" top="8vh" title="图片预览" :visible.sync="showDialog">
  <img width="100%" :src="currentImg" alt="">
</el-dialog>

总结:

  1. 显示选择的图片列表 file-list
  2. 预览图片 on-preview
  3. 弹窗的使用

回顾

  • 导入
    • 理解文件上传或者导入的基本流程
      • 前端直接上传文件给后端:基于xhr2.0新特性FormData
      • 前端需要处理选择的文件数据,然后调用接口提交数据
        • 文件在前端转换为base64,然后提交该数据
        • Excel文件在前端转换为json数据,然后提交该数据
      • 请求参数数据格式的转换:逻辑思维(算法)
      • 日期的转换
  • 导出
    • 导出的本质:下载文件
      • 直接由后端提供一个下载链接即可,前端只要点击该链接,文件就会下载
      • 前端调用接口获取数据,然后把数据写入Excel:基于第三方包实现
      • 获取的原始的后端数据需要转换为导出的数据格式:逻辑思维(算法)
      • 日期和聘用形式处理
  • 头像上传
    • 个人详情页面结构处理:路由;布局;表单回填;表单提交
    • 上传文件的两种方案:
      • 上传到自己的服务器上
      • 上传到第三方云平台中(腾讯云)
    • 腾讯云用法
      • 注册账号
      • 实名认证
      • 创建存储桶
      • 设置跨域访问的规则CORS
      • 如何做到私钥写:生成自己账号的秘钥
    • 封装上传头像的组件
      • 基本结构封装:基于ElementUI的el-upload组件实现
      • 基本展示和图片预览

根据上传数量控制上传按钮

需求:当选中的图片大于等于一张时隐藏加号效果

  • 控制上传显示
computed: {
  // 设定一个计算属性 判断是否已经上传完了一张
  imgLen () {
    return this.fileList.length >= 1
  }
},
  • 模板布局
<el-upload
  list-type="picture-card"
  :file-list="fileList"
  :on-preview="preview"
  :limit="1"
  action=""
  :class='{hideplus: imgLen}'
>
  <i class="el-icon-plus" />
</el-upload>

<style scoped lang="scss">
.my-img-upload {
  width: 500px;
  .hideplus {
    ::v-deep .el-upload--picture-card {
      display: none;
    }
  }
}
</style>

小拓展: 如果希望能够定制, 图片的限制数量, 而不是永远只是一张, 这里的数字 1, 是可以通过父传子传过来的

注意:scss中的深度选择器需要使用 ::v-deep

删除图片

点击删除按钮, 看似删除成功了, 但是 fileList 数据没有删除, 需要处理

<el-upload
  :on-preview="preview"
  :on-remove="handleRemove"
  :file-list="fileList"
  :limit="1"
  list-type="picture-card"
  action="#"
>
  <i class="el-icon-plus" />
</el-upload>
// 控制删除动作
handleRemove (file, fileList) {
  // 参数一file表示要删除的文件信息
  // 参数二fileList表示删除之后剩余的文件信息
  // console.log(fileList)
  // file表示当前处理的文件信息
  // this.fileList = this.fileList.filter(item => {
  //   return item.uid !== file.uid
  // })
  this.fileList = fileList
},

总结:

  1. 监听删除的动作 on-remove(el-upload组件的选项)
  2. 删除事件的回调参数一表示要删除的文件信息,参数二表示删除后剩余的图片信息

添加操作

  • 默认选中图片时,会自动上传文件到action指向的地址,但是我们希望手动上传

  • 点击 + 号, 进行上传动作, 会触发 el-upload的http-request属性配的函数

    • 注意:auto-upload=“true”,这样才会触发http-request指向的函数
:http-request="handleUpload"

// 自定义上传动作 有个参数 有个file对象
handleUpload(params) {
  // 进行上传操作
  console.log(params.file)
}
  • 添加文件时, 触发on-change事件函数,函数中将页面的 fileList 同步到 this.fileList (数据中),从而方便进行预览操作
// 添加文件, 用户选了就应该新增文件预览
handleChange(file, fileList) {
  // console.log(fileList)
  // console.log(fileList.length)
  this.fileList = fileList
},

总结

  1. 监听选中文件的事件 on-change
  2. 如果想手动上传文件,而不是默认上传给action,那么需要配置 http-request,该配置函数中可以进行手动上传操作(必须保证 auto-upload 值为 true)
  • 基于el-upload组件的submit方法触发上传的动作
this.$refs.upload.submit()
<!-- 自动上传:选中图片后,自动把图片文件传递给action指向的url地址 -->
<!-- 手动上传:选中图片后,仅仅触发一个函数,该函数中需要自己写代码上传文件 -->

上传之前校验检查

控制上传图片的类型和上传大小, 如果不满足条件 返回false上传就会停止

:before-upload="beforeUpload"

// 配置上传前的校验, 只要通过校验, 才能进行上传
// 选择文件之前进行验证
beforeUpload (file) {
  // 检测文件的类型
  const types = ['image/png', 'image/jpeg', 'image/gif']
  if (!types.includes(file.type)) {
    // 不支持的格式
    this.$message.error('必须上传png,jpeg,gif三种类型之一')
    return false
  }
  // 检测文件的大小(限制1M以内)
  if (file.size / 1024 / 1024 > 1) {
    this.$message.error('图片不可以超过1M')
    return false
  }
  return true
},

总结:选择文件之前进行相关类型检测(文件类型和文件大小)

上传到腾讯云

  • 上传动作为el-upload的http-request属性
:http-request="handleUpload"

// 自定义上传动作 有个参数 有个file对象,是我们需要上传到腾讯云服务器的内容
handleUpload(params) {
  console.log(params.file)
}

我们需要在该方法中,调用腾讯云的上传方法

登录 访问管理控制台 ,获取您的项目 SecretId 和 SecretKey。

【vue-iHRM】07-02员工管理模块

  • 安装腾讯云上传文件sdk包
npm i cos-js-sdk-v5
  • 配置上传秘钥并上传
import COS from 'cos-js-sdk-v5' // 导入腾讯云的包(sdk)
const cos = new COS({
  SecretId: 'AKIDc3E9nAG5dtulOlsiHdljSt3wYtwkIafT', // 身份识别 ID
  SecretKey: 'Zrsc8QRZS4PTFkyuZx4uUnqc7PQAcokP' // 身份密钥
})

// 进行上传操作
handleUpload(params) {
  // console.log(params.file)
  if (params.file) {
    // 执行上传操作
    cos.putObject({
      Bucket: 'jepson-75-1256203106', /* 存储桶名称 */
      Region: 'ap-shanghai', /* 存储桶所在地域,必须字段 */
      Key: params.file.name, /* 文件名 */
      StorageClass: 'STANDARD', // 上传模式, 标准模式(默认即可)
      Body: params.file, // 上传文件对象
      onProgress: (progressData) => {
        console.log(JSON.stringify(progressData))
      }
    }, (err, data) => {
      console.log(err || data)
    })
  }
}

上传腾讯云基本流程

  • 配置上传的权限
import COS from 'cos-js-sdk-v5' // 导入腾讯云的包(sdk)
const cos = new COS({
  SecretId: 'AKIDc3E9nAG5dtulOlsiHdljSt3wYtwkIafT', // 身份识别 ID
  SecretKey: 'Zrsc8QRZS4PTFkyuZx4uUnqc7PQAcokP' // 身份密钥
})
  • 配置相关的请求参数
handleUpload (params) {
  cos.putObject({
    // 存储桶名称
    Bucket: 'abc-1252553968',
    // 存储桶所在地址
    Region: 'ap-beijing',
    // 上传的文件名称
    Key: params.file.name,
    // 上传模式
    StorageClass: 'STANDARD',
    // 上传的文件对象
    Body: params.file,
    // 监控上传的进度
    onProgress: function(progressData) {
      console.log(JSON.stringify(progressData))
    }
  }, function(err, data) {
    // 上传完成的回调
    console.log(err || data)
  })
},

总结

  1. 这种上传方式仅仅用于测试,不太安全(秘钥容易泄露)
  2. 正规的流程应该把秘钥存储在后端服务器中

上传成功之后处理返回数据

如何处理返回成功的数据, 应该在上传完图片, 得到图片地址,

  • 需要做的事情:
  1. 更新当前上传的对应图片的 status => 更新成 success
  2. 更新当前上传的对应图片的 url => 更新成腾讯云拿到的 Location (拼上前缀)

问题: this.fileList 是一个数组, 我怎么知道更新谁呢 ? params.file.uid 就是唯一标识

解决: 通过 params.file.uid 决定修改 this.fileList 中的哪个对象

  • 处理返回数据
upload(params) {
  if (!params.file) return
  // 将文件对象, 上传到腾讯云
  cos.putObject({
    Bucket: 'jepsonpp-75-1256203106', // 存储桶的名字
    Region: 'ap-shanghai', // 存储桶地域
    Key: params.file.name, // 上传到存储桶的文件名, 如果希望不重名, 可以对文件名进行处理
    StorageClass: 'STANDARD', // 上传模式, 标准模式
    Body: params.file, // 上传的文件对象
    // 上传的进度, 上传的过程中实时触发onProgress, 可以做进度条的展示
    onProgress: progressData => {
      // console.log(progressData)
    }
  }, (err, data) => {
    // 上传完成的回调
    if (err) {
      this.$message.error('上传图片失败')
      return
    }
    // data表示上传成功后后端返回的数据
    // 选中图片后,this.fileList中本来已经有了选中的这张图片的信息
    // 上传成功后需要把对应的图片的地址修改为腾讯云上传成功的地址,并且修改完成状态
    if (data.statusCode === 200) {
      const imgInfo = this.fileList.find(item => {
        return item.uid === params.file.uid
      })
      imgInfo.status = 'success'
      imgInfo.url = 'https://' + data.Location
    } else {
      this.$message.error('上传图片失败')
    }
  })
}

总结

  1. 腾讯云返回上传的结果后需要更新图片的地址和状态

上传的进度条显示

为了再上传图片过程中显示进度条,我们可以使用element-ui的进度条显示当前的上传进度

  • 放置进度条
<el-upload :before-upload="checkUpload" :on-change="onChange" :http-request="handleUpload" :on-remove="onRemove" :class="{hideplus: imgLen}" :on-preview="preview" :file-list="fileList" list-type="picture-card" action="http://baidu.com">
  <i class="el-icon-plus" />
</el-upload>
<el-progress v-if="isUploading" class="progress-tip" type="circle" :percentage="percent" />
  • 通过腾讯云sdk监听上传进度
// 手动实现文件上传操作
handleUpload (params) {
  // console.log(params.file)
  // 手动上传文件到腾讯云
  if (params.file) {
    // 显示进度条
    this.isUploading = true
    // 执行上传的动作
    cos.putObject({
      // 存储桶名称
      Bucket: 'byte-1252553968',
      // 存储桶所在地域,必须字段
      Region: 'ap-beijing',
      // 文件名称
      Key: params.file.name,
      // 上传模式, 标准模式(默认即可)
      StorageClass: 'STANDARD',
      // 上传文件对象
      Body: params.file,
      // 监听上传的进度
      onProgress: (progressData) => {
        // 更新进度
        this.percent = progressData.percent * 100
      }
    }, (err, data) => {
      // 上传的结果:如果err有值,证明上传失败了,如果err是null表示上传成功
      // data表示上传成功后,腾讯云返回的图片地址
      // console.log(err || data)
      if (err) {
        this.isUploading = false
        return this.$message.error('上传文件失败')
      }
      // 上传成功
      if (data.statusCode === 200) {
        // 获取腾讯云上的图片地址
        const fileInfo = this.fileList.find(item => item.uid === params.file.uid)
        if (fileInfo) {
          // 图片右上角会出现对勾
          fileInfo.status = 'success'
          // 更新图片地址
          fileInfo.url = 'https://' + data.Location
        }
      } else {
        this.$message.error('上传图片失败')
      }
      setTimeout(() => {
        this.isUploading = false
        this.percent = 0
      }, 500)
    })
  }
},

总结:

  1. 基于腾讯云API的回调监控上传的进度 onProgress
  2. 基于el-progress组件实现进度提示

在员工详情中应用上传组件

配置上传图片组件的数量

  • 组件接收参数
  props: {
    limit: {
      type: Number,
      default: 1
    }
  },
  computed: {
    // 判断是否选中了多张图片
    isMultiple () {
      return this.fileList.length >= this.limit
    }
  },
<el-upload
  list-type="picture-card"
  :file-list="fileList"
  :on-preview="preview"
  :on-remove="handleRemove"
  :on-change="handleChange"
  :before-upload="beforeUpload"
  :http-request="handleUpload"
  :limit="limit"
  action=""
  :class="{hideplus: isMultiple}"
>
  <i class="el-icon-plus" />
</el-upload>
  • 父组件传入数量
<image-upload :limit="1"/>

总结:如果有相关信息需要定制,需要抽取组件的属性

  • 支持默认图片的显示
  1. 父组件注入数据
<ImageUpload :default-img="userInfo.staffPhoto" :limit="1" />
  1. 子组件处理默认图片
export default {
  name: 'ImageUpload',
  props: {
    // 控制上传图片的数量
    limit: {
      type: Number,
      default: 1
    },
    // 获取默认的图片地址
    defaultImg: {
      type: String,
      default: ''
    }
  },
},
created () {
  this.fileList.push({
    url: this.defaultImg
  })
},"

总结:支持默认显示已有的头像

员工的头像

在**user-info.vue**中放置上传组件

  • 模板布局
<!-- 员工照片 -->
<el-row class="inline-info">
  <el-col :span="12">
    <el-form-item label="员工头像">
      <image-upload ref="avatarRef" :default-img='userInfo.staffPhoto' :limit="1" />
    </el-form-item>
  </el-col>
</el-row>
  • 封装的头像上传的组件默认图片的处理不可以使用created,而是要使用侦听器
  watch: {
    defaultImg (url) {
      this.fileList = [{ url }]
    }
  },
  • 当点击保存更新时,获取图片的内容, 进行提交
async saveUser() {
  // 去读取 员工上传的头像
  const fileList = this.$refs.avatrRef.fileList // 读取上传组件的数据
  // 进行提交
  ...
},
  • 判断图片是否上传成功
// 判断图片是否全部上传成功了
const fileList = this.$refs.myAvatar.fileList
const isSuccess = fileList.every(item => {
  return item.status === 'success'
})
if (!isSuccess) {
  this.$message.error('请等待上传图片成功后再提交表单')
  return
}
  • 调用接口更新头像employees/components/user-info.vue
async saveUser() {
  try {
    // 判断图片是否全部上传成功了
    const fileList = this.$refs.myAvatar.fileList
    const isSuccess = fileList.every(item => {
      return item.status === 'success'
    })
    if (!isSuccess) {
      this.$message.error('请等待上传图片成功后再提交表单')
      return
    }
    const ret = await reqSaveUserDetailById({
      ...this.userInfo,
      // 获取子组件中上传成功的图片的地址
      staffPhoto: this.$refs.myAvatar.fileList[0].url
    })
    if (!ret.success) {
      this.$message.error(ret.message)
    } else {
      this.$message.success(ret.message)
    }
  } catch {
    this.$message.error('更新用户基本信息失败')
  }
},

总结:

  1. 把上传成功的头像更新到自己的服务器中
  2. 需要初始化原有的默认头像

注意:默认头像的处理需要使用侦听器(默认是没有头像的,调用接口获取到头像后进行更新操作)

员工的照片

  • 员工证件照
<el-form-item label="员工照片">
  <ImageUpload :default-img="urlList" :limit="3" />
</el-form-item>
  • 读取时赋值照片
// 获取详细信息(下面的表单)
async getUserDetailInfo () {
  try {
    const ret = await reqGetPersonalDetail(this.userId)
    if (!ret.success) {
      this.$message.error(ret.message)
    } else {
      this.formData = ret.data
      // 获取照片数据进行初始化
      if (this.formData.staffPhoto) {
        // 获取员工的照片(如果是多张图片,会把多个url地址用;隔开)
        if (ret.data.staffPhoto) {
            // 获取多张员工照片
            this.urlList = ret.data.staffPhoto.split(';')
        }
      }
    }
  } catch {
    this.$message.error('获取用户详细信息失败')
  }
},
  • 封装上传成功的判断状态
  computed: {
    // 判断是否选中了多张图片
    isMultiple () {
      return this.fileList.length >= this.limit
    },
    // 判断是否所有的图片都上传成功了
    isAllSuccess () {
      return this.fileList.every(item => {
        return item.status === 'success'
      })
    }
  },
  • 保存时读取头像
async savePersonal () {
  try {
    // 获取最新的员工照片信息
    // photos = ['http://abc.com/a.png', 'http://abc.com/b.png']
    // photos = 'http://abc.com/a.png;http://abc.com/b.png'
    const fileList = this.$refs.myPhoto.fileList
    const successList = fileList.filter(item => item.status === 'success')
    if (successList.length === 0) {
      return this.$message.error('请上传所有照片')
    }
    // 上传成功
    const photos = successList.map(item => item.url).join(';')
    const ret = await reqUpdatePersonal({
      ...this.formData,
      // 腾讯云多张图片地址
      staffPhoto: photos,
      // 当前修改的用户的id
      id: this.userId
    })
    if (!ret.success) {
      this.$message.error(ret.message)
    } else {
      this.$message.success(ret.message)
    }
  } catch {
    this.$message.error('更新用户基本信息失败')
  }
}

总结:上传员工的照片,支持多张照片上传

注意:默认照片数据类型是数组

员工列表显示图片

目标:在员工列表中心显示图片

员工的头像可以在列表项中添加一列来进行显示, 处理下图片异常

  • 基于ElementUI提供的图片组件定制列表的员工头像
<el-table-column label="头像" prop="staffPhoto" sortable="">
  <template v-slot='scope'>
    <el-image class='staff' :src='scope.row.staffPhoto'>
      <div slot="error">
        <img class="staff" :src="defaultImage">
      </div>
    </el-image>
  </template>
</el-table-column>
  • 基于自定义指令封装
<el-table-column label="头像" prop="staffPhoto">
  <template #default="scope">
    <img v-imgerror="defaultImg" class="staff" :src="scope.row.staffPhoto" alt="">
  </template>
</el-table-column>

data() {
  return {
    ...
    defaultImg: 'https://dss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=2146034403,1504718527&fm=26&gp=0.jpg'
  }
},

<style lang="scss" scoped>
.employees-container {
  .staff {
    width: 70px;
    height: 70px;
    border-radius: 50%;
  }
}
</style>

我们尝试用之前的指令来处理图片的异常问题,但是发现只有两三张图片能显示

这是因为有的员工的头像的地址为空(null),给img赋值空的src不能触发错误事件,针对这一特点,我们需要对指令进行升级

插入节点的钩子里面判断空, 然后在组件更新之后的钩子中同样判断空 (发送请求回来, 会赋值给img src, 需要重新错误检测)

https://cn.vuejs.org/v2/guide/custom-directive.html

export const imgerror = {
  // 指的是指令所在元素被插入到页面中时执行的一个钩子
  // el 指令所在的元素 img标签
  // binding 指令相关的参数对象, 指令名, 指令值binding.value
  inserted(el, binding) {
    // 如果src没有赋值, 给默认src
    el.src = el.src || binding.value
    // console.log('指令所在元素, 被插入到页面中了')
    el.onerror = function() {
      // console.log('图片加载失败了, 重新指定一个有效的src')
      el.src = binding.value
    }
  },
  // 如果手动把src的值置空(删除头像),显示默认头像
  componentUpdated(el, binding) {
    // 如果src没有赋值, 给默认src
    el.src = el.src || binding.value
  }
}

总结:

  1. 作用域插槽定制列的模板
  2. 基于ElementUI提供的组件可以处理图片加载失败的情况
  3. 基于自定义指令方式也可以处理图片加载失败的情况

二维码生成

目标 基于 (图片地址 / 网页地址) 生成二维码

二维码功能将来工作中也很常见, 我们需要根据信息 或者 链接地址, 生成一个二维码! 比如: 做地址分享, 做手机图片预览等
【vue-iHRM】07-02员工管理模块

  • 首先,需要安装生成二维码的插件
npm i qrcode
  • qrcode的用法是(把info字符串转换为二维码图片,然后绘制到dom元素中:canvas/svg)
QrCode.toCanvas(dom, info)

dom为一个canvas的dom对象, info为转化二维码的信息

需求:点击头像,打开弹窗,显示二维码,扫码打开图片的链接。

点击图片-显示弹层

  • 准备弹层
<!-- 分享展示, 预览的二维码的弹层 -->
<el-dialog title="二维码" :visible="showCodeDialog" @close="showCodeDialog = false">
  二维码
</el-dialog>
  • 注册点击事件
<el-table-column label="头像" prop="staffPhoto" sortable="">
  <template v-slot='scope'>
    <el-image @click='showCodeDialog=true' class='staff' :src='scope.row.staffPhoto'>
      <div slot="error">
        <img class="staff" :src="defaultImage">
      </div>
    </el-image>
  </template>
</el-table-column>

总结:控制二维码弹窗的显示和隐藏。

二维码生成演示

  • 我们尝试将canvas标签放到dialog的弹层中
<el-dialog width="300px" title="二维码" :visible="showCodeDialog" @close="showCodeDialog = false">
  <el-row type="flex" justify="center">
    <canvas ref="myCanvas" />
  </el-row>
</el-dialog>
  • 在点击员工的图片时,显示弹层,并将图片地址转化成二维码
import QrCode from 'qrcode'

clickShowCodeDialog(url) {
  if (!url) return
  this.showCodeDialog = true
  this.$nextTick(() => {
    // 如果这里 url 写的是网址, 就会跳转到对应网址 (二维码分享效果)
    QrCode.toCanvas(this.$refs.myCanvas, url)
  })
}

总结:

  1. 二维码的绘制方式(绘制图片到canvas画布上)
  2. 关于$nextTick用法的理解

打印功能

目标 完成个人信息和工作信息的打印功能
【vue-iHRM】07-02员工管理模块

将来能够打印
【vue-iHRM】07-02员工管理模块

新建打印页面 - 配置路由

  • 配置路由
// 员工打印页
{
  path: 'print/:id',
  component: () => import('@/views/employees/print'),
  hidden: true,
  meta: {
    title: '员工打印'
  }
}
  • 获取用户id
  computed: {
    userId () {
      return this.$route.params.id
    }
  }
  • 准备图标按钮
<el-tab-pane label="个人详情">
  <el-tooltip class="tooltip-box" content="打印基本个人信息">
    <router-link :to="`/employees/print/${userId}?type=personal`">
      <i class="el-icon-printer" />
    </router-link>
  </el-tooltip>
  
  <user-info />
</el-tab-pane>

<el-tab-pane label="岗位信息">
  <el-tooltip class="tooltip-box" content="打印基本岗位信息">
    <router-link :to="`/employees/print/${userId}?type=job`">
      <i class="el-icon-printer" />
    </router-link>
  </el-tooltip>
  
  <job-info />
</el-tab-pane>
  • 样式
<style lang="scss" scoped>
.employees-detail-container {
  .el-tab-pane {
    padding-top: 10px;
  }
  .tooltip-box {
    position: absolute;
    right: 30px;
    top: 10px;
    z-index: 999;
  }
}
</style>
  • 创建页面组件 (已准备好)
<template>
  <div id="myPrint" class="dashboard-container">
    <div class="app-container">
      <el-card>
        <el-breadcrumb separator="/" class="titInfo ">
          <el-breadcrumb-item :to="{ path: '/' }">首页</el-breadcrumb-item>
          <el-breadcrumb-item>
            <router-link :to="{'path':'/employees'}">员工管理</router-link>
          </el-breadcrumb-item>
          <el-breadcrumb-item>打印</el-breadcrumb-item>
        </el-breadcrumb>
        <div v-if="type === 'personal'">
          <h2 class="centInfo">员工信息表</h2>
          <table cellspacing="0" width="100%" class="tableList">
            <tr class="title">
              <td colspan="8" class="centInfo">基本信息</td>
            </tr>
            <tr>
              <th style="width:10%">姓名</th>
              <td colspan="6" style="width:80%">{{ formData.username }}</td>
              <td rowspan="5" style="width:10%"><img style="width:155px;height:218px" :src="formData.staffPhoto"></td>

            </tr>
            <tr>
              <th>性别</th>
              <td colspan="6">{{ formData.sex }}</td>
            </tr>
            <tr>
              <th>手机</th>
              <td colspan="6">{{ formData.mobile }}</td>
            </tr>
            <tr>
              <th>出生日期</th>
              <td colspan="6">{{ formData.dateOfBirth | formatDate }}</td>
            </tr>
            <tr>
              <th>最高学历</th>
              <td colspan="6">{{ formData.theHighestDegreeOfEducation }}</td>
            </tr>
            <tr>
              <th style="width:10%">是否可编辑</th>
              <td style="width:35%">{{ formData.isItEditable }}</td>
              <th style="width:10%">是否隐藏号码</th>
              <td colspan="5" style="width:45%">{{ formData.doYouHideNumbers }}</td>
            </tr>
            <tr>
              <th>国家地区</th>
              <td>{{ formData.nationalArea }}</td>
              <th>护照号</th>
              <td colspan="5">{{ formData.passportNo }}</td>
            </tr>
            <tr>
              <th>身份证号</th>
              <td>{{ formData.idNumber }}</td>
              <th>身份证照片</th>
              <td colspan="5">{{ formData.iDCardPhoto }}</td>
            </tr>
            <tr>
              <th>籍贯</th>
              <td>{{ formData.nativePlace }}</td>
              <th>民族</th>
              <td colspan="5">{{ formData.nation }}</td>
            </tr>
            <tr>
              <th>英文名</th>
              <td>{{ formData.englishName }}</td>
              <th>婚姻状况</th>
              <td colspan="5">{{ formData.maritalStatus }}</td>
            </tr>
            <tr>
              <th>员工照片</th>
              <td>{{ formData.staffPhoto }}</td>
              <th>生日</th>
              <td colspan="5">{{ formData.birthday }}</td>
            </tr>
            <tr>
              <th>属相</th>
              <td>{{ formData.zodiac }}</td>
              <th>年龄</th>
              <td colspan="5">{{ formData.age }}</td>
            </tr>
            <tr>
              <th>星座</th>
              <td>{{ formData.constellation }}</td>
              <th>血型</th>
              <td colspan="5">{{ formData.bloodType }}</td>
            </tr>
            <tr>
              <th>户籍所在地</th>
              <td>{{ formData.domicile }}</td>
              <th>政治面貌</th>
              <td colspan="5">{{ formData.politicalOutlook }}</td>
            </tr>
            <tr>
              <th>入党时间</th>
              <td>{{ formData.timeToJoinTheParty }}</td>
              <th>存档机构</th>
              <td colspan="5">{{ formData.archivingOrganization }}</td>
            </tr>
            <tr>
              <th>子女状态</th>
              <td>{{ formData.stateOfChildren }}</td>
              <th>子女有无商业保险</th>
              <td colspan="5">{{ formData.doChildrenHaveCommercialInsurance }}</td>
            </tr>
            <tr>
              <th>有无违法违纪行为</th>
              <td>{{ formData.isThereAnyViolationOfLawOrDiscipline }}</td>
              <th>有无重大病史</th>
              <td colspan="5">{{ formData.areThereAnyMajorMedicalHistories }}</td>
            </tr>
            <tr class="title">
              <td colspan="8" class="centInfo">通讯信息</td>
            </tr>
            <tr>
              <th>QQ</th>
              <td>{{ formData.qQ }}</td>
              <th>微信</th>
              <td colspan="5">{{ formData.weChat }}</td>
            </tr>
            <tr>
              <th>居住证城市</th>
              <td>{{ formData.residenceCardCity }}</td>
              <th>居住证办理日期</th>
              <td colspan="5">{{ formData.dateOfResidencePermit }}</td>
            </tr>
            <tr>
              <th>居住证截止日期</th>
              <td>{{ formData.residencePermitDeadline }}</td>
              <th>现居住地</th>
              <td colspan="5">{{ formData.placeOfResidence }}</td>
            </tr>
            <tr>
              <th>通讯地址</th>
              <td>{{ formData.postalAddress }}</td>
              <th>联系手机</th>
              <td colspan="5">{{ formData.contactTheMobilePhone }}</td>
            </tr>
            <tr>
              <th>个人邮箱</th>
              <td>{{ formData.personalMailbox }}</td>
              <th>紧急联系人</th>
              <td colspan="5">{{ formData.emergencyContact }}</td>
            </tr>
            <tr>
              <th>紧急联系电话</th>
              <td colspan="7">{{ formData.emergencyContactNumber }}</td>
            </tr>
            <tr class="title">
              <td colspan="8" class="centInfo">账号信息</td>
            </tr>
            <tr>
              <th>社保电脑号</th>
              <td>{{ formData.socialSecurityComputerNumber }}</td>
              <th>公积金账号</th>
              <td colspan="5">{{ formData.providentFundAccount }}</td>
            </tr>
            <tr>
              <th>银行卡号</th>
              <td>{{ formData.bankCardNumber }}</td>
              <th>开户行</th>
              <td colspan="5">{{ formData.openingBank }}</td>
            </tr>
            <tr class="title">
              <td colspan="8" class="centInfo">教育信息</td>
            </tr>
            <tr>
              <th>学历类型</th>
              <td>{{ formData.educationalType }}</td>
              <th>毕业学校</th>
              <td colspan="5">{{ formData.graduateSchool }}</td>
            </tr>
            <tr>
              <th>入学时间</th>
              <td>{{ formData.enrolmentTime }}</td>
              <th>毕业时间</th>
              <td colspan="5">{{ formData.graduationTime }}</td>
            </tr>
            <tr>
              <th>专业</th>
              <td>{{ formData.major }}</td>
              <th>毕业证书</th>
              <td colspan="5">{{ formData.graduationCertificate }}</td>
            </tr>
            <tr>
              <th>学位证书</th>
              <td colspan="7">{{ formData.certificateOfAcademicDegree }}</td>
            </tr>
            <tr class="title">
              <td colspan="8" class="centInfo">从业信息</td>
            </tr>
            <tr>
              <th>上家公司</th>
              <td>{{ formData.homeCompany }}</td>
              <th>职称</th>
              <td colspan="5">{{ formData.title }}</td>
            </tr>
            <tr>
              <th>简历</th>
              <td>{{ formData.resume }}</td>
              <th>有无竞业限制</th>
              <td colspan="5">{{ formData.isThereAnyCompetitionRestriction }}</td>
            </tr>
            <tr>
              <th>前公司离职证明</th>
              <td>{{ formData.proofOfDepartureOfFormerCompany }}</td>
              <th>备注</th>
              <td colspan="5">{{ formData.remarks }}</td>
            </tr>
          </table>
          <div class="foot">签字:___________日期:___________</div>
        </div>
        <div v-else>
          <h2 class="centInfo">岗位信息表</h2>
          <table cellspacing="0" width="100%" class="tableList">
            <tr class="title">
              <td colspan="4" class="centInfo">基本信息</td>
            </tr>
            <tr>
              <th style="width:10%">姓名</th>
              <td style="width:40%">{{ formData.username }}</td>
              <th style="width:10%">入职日期</th>
              <td style="width:40%">{{ formData.dateOfEntry }}</td>
            </tr>
            <tr>
              <th>部门</th>
              <td>{{ formData.departmentName }}</td>
              <th>岗位</th>
              <td>{{ formData.post }}</td>
            </tr>
            <tr>
              <th>工作邮箱</th>
              <td>{{ formData.workMailbox }}</td>
              <th>工号</th>
              <td>{{ formData.workNumber }}</td>
            </tr>
            <tr>
              <th>转正日期</th>
              <td>{{ formData.dateOfCorrection }}</td>
              <th>转正状态</th>
              <td>{{ formData.stateOfCorrection }}</td>
            </tr>
            <tr>
              <th>职级</th>
              <td>{{ formData.rank }}</td>
              <th>汇报对象</th>
              <td>{{ formData.reportName }}</td>
            </tr>
            <tr>
              <th>HRBP</th>
              <td>{{ formData.hRBP }}</td>
              <th>聘用形式</th>
              <td>{{ formData.formOfEmployment }}</td>
            </tr>

            <tr>
              <th>管理形式</th>
              <td>{{ formData.formOfManagement }}</td>
              <th>调整司龄</th>
              <td>{{ formData.adjustmentAgedays }}</td>
            </tr>
            <tr>
              <th>司龄</th>
              <td>{{ formData.ageOfDivision }}</td>
              <th>首次参加工作时间</th>
              <td>{{ formData.workingTimeForTheFirstTime }}</td>
            </tr>

            <tr>
              <th>调整工龄天</th>
              <td>{{ formData.adjustmentOfLengthOfService }}</td>
              <th>工龄</th>
              <td>{{ formData.workingYears }}</td>
            </tr>
            <tr>
              <th>纳税城市</th>
              <td>{{ formData.taxableCity }}</td>
              <th>转正评价</th>
              <td>{{ formData.correctionEvaluation }}</td>
            </tr>
            <tr class="title">
              <td colspan="4" class="centInfo">合同信息</td>
            </tr>
            <tr>
              <th>首次合同开始时间</th>
              <td>{{ formData.initialContractStartTime }}</td>
              <th>首次合同结束时间</th>
              <td>{{ formData.firstContractTerminationTime }}</td>
            </tr>
            <tr>
              <th>现合同开始时间</th>
              <td>{{ formData.currentContractStartTime }}</td>
              <th>现合同结束时间</th>
              <td>{{ formData.closingTimeOfCurrentContract }}</td>
            </tr>

            <tr>
              <th>合同期限</th>
              <td>{{ formData.contractPeriod }}</td>
              <th>合同文件</th>
              <td>{{ formData.contractDocuments }}</td>
            </tr>
            <tr>
              <th>续签次数</th>
              <td colspan="3">{{ formData.renewalNumber }}</td>
            </tr>
            <tr class="title">
              <td colspan="4" class="centInfo">招聘信息</td>
            </tr>
            <tr>
              <th>其他招聘渠道</th>
              <td>{{ formData.otherRecruitmentChannels }}</td>
              <th>招聘渠道</th>
              <td>{{ formData.recruitmentChannels }}</td>
            </tr>
            <tr>
              <th>社招校招</th>
              <td>{{ formData.socialRecruitment }}</td>
              <th>推荐企业人</th>
              <td>{{ formData.recommenderBusinessPeople }}</td>
            </tr>
          </table>
          <div class="foot">签字:___________日期:___________</div>
        </div>
      </el-card>
    </div>
  </div>
</template>

<script>
import { reqGetPersonalDetail, reqGetJobDetail } from '@/api/employees'
import { getDetailInfo as reqGetUserDetailById } from '@/api/user'
export default {
  data() {
    return {
      formData: {}
    }
  },
  computed: {
    userId() {
      return this.$route.params.id
    },
    type() {
      return this.$route.query.type
    }
  },
  // 创建完毕状态
  created() {
    if (this.type === 'personal') {
      this.getPersonalDetail()
    } else {
      this.getJobDetail()
    }
  },
  // 组件更新
  methods: {
    async getPersonalDetail() {
      const { data: userInfo } = await reqGetUserDetailById(this.userId) // 获取个人基本信息(顶部)
      const { data: detail } = await reqGetPersonalDetail(this.userId) // 获取个人基本信息(底部)
      this.formData = { ...detail, ...userInfo }
    },
    async getJobDetail() {
      const { data: userInfo } = await reqGetUserDetailById(this.userId)
      const { data: jobInfo } = await reqGetJobDetail(this.userId) // 获取个人基本信息
      this.formData = { ...jobInfo, ...userInfo }
    }
  }
}
</script>

<style lang="scss">
.foot {
  padding: 30px 0;
  text-align: right;
}
</style>

总结:

  • 该页面内容实际上就是读取个人和详情的接口数据,根据传入的type类型决定显示个人还是岗位
  • type为**personal时显示个人,为job**时显示岗位

注意:路由的参数传递方式

  1. Restful风格 this.$route.params.id
  2. 查询字符串风格 this.$route.query.type

利用vue-print-nb打印

  • 首先,打印功能我们借助一个比较流行的插件 vue-print-nb
$ npm i vue-print-nb

它的用法是

  • 首先注册该插件
import Print from 'vue-print-nb'
Vue.use(Print)
  • 给要打印的盒子指定 id
<div id="printbox">
  • 使用v-print指令的方式进行打印(指定id是printbox的容器被打印)
<div style="text-align: right; margin-top: 10px;">
  <el-button v-print="{ id: 'printbox' }" type="primary" size="small">打印</el-button>
</div>

总结:基于第三方包实现打印功能

注意:第三方包扩展了一个指令 v-print

上一篇:.NET Framework 3.5 SP1离线安装卸载方法


下一篇:iptables 运行逻辑及-I -A 参数解析