From 25ab0c2c41862bc4926814e6905e07cf4b76d40a Mon Sep 17 00:00:00 2001
From: yubo <autumnal_wind@yeah.net>
Date: 星期一, 06 四月 2026 22:54:50 +0800
Subject: [PATCH] refactor(user): 规范字典选项计算属性格式

---
 src/views/user/archivesChange.vue |  567 ++++++++++++++++++++++++++++++++++++++++++++------------
 1 files changed, 442 insertions(+), 125 deletions(-)

diff --git a/src/views/user/archivesChange.vue b/src/views/user/archivesChange.vue
index 8517a3b..0109919 100644
--- a/src/views/user/archivesChange.vue
+++ b/src/views/user/archivesChange.vue
@@ -14,17 +14,19 @@
         <div class="menu dadetails">
           <div style="height: 22vh;">
 
-            <el-upload
-              class="avatar-uploader"
-              action="#"
-              :show-file-list="false"
-              :on-change="handlePictureCardPreview"
-              :auto-upload="false"
-            >
-              <div slot="tip" class="el-upload__tip">只能上传jpg/png文件,且不超过4MB</div>
-              <img v-if="empBaseInfoImageUrl" :src="empBaseInfoImageUrl" class="avatar">
-              <i v-else class="el-icon-plus avatar-uploader-icon" />
-            </el-upload>
+            <!-- 头像显示区域 -->
+            <div class="avatar-wrapper">
+              <img
+                v-if="empBaseInfoImageUrl"
+                :src="empBaseInfoImageUrl"
+                class="avatar"
+                @click="openUploadChoice"
+              >
+              <div v-else class="avatar-uploader-placeholder" @click="openUploadChoice">
+                <i class="el-icon-plus avatar-uploader-icon" />
+                <div class="upload-tip">点击上传照片</div>
+              </div>
+            </div>
             <!-- <img src="https://ss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=1333074204,3035391839&fm=26&gp=0.jpg" class="jbxxImg">-->
             <div class="title-da">
               档案号:{{ empBaseInfoForm.archivesNumb }}
@@ -516,6 +518,11 @@
                   <el-col :span="8">
                     <el-form-item label="相关证件" prop="certificateListName">
                       <el-input v-model="empBaseInfoForm.certificateListName" :disabled="readon ? false : true" />
+                    </el-form-item>
+                  </el-col>
+                  <el-col :span="8">
+                    <el-form-item label="年假天数" prop="annualLeave">
+                      <el-input v-model="empBaseInfoForm.annualLeave" disabled />
                     </el-form-item>
                   </el-col>
                 </el-row>
@@ -2589,6 +2596,78 @@
         </div>
       </el-dialog>
     </el-dialog>
+
+    <!-- 上传方式选择弹窗 -->
+    <el-dialog
+      title="选择上传方式"
+      :visible.sync="uploadChoiceDialogVisible"
+      width="400px"
+      :close-on-click-modal="false"
+    >
+      <div class="upload-choice-container">
+        <div class="upload-choice-item" @click="choiceCamera">
+          <i class="el-icon-camera" />
+          <span>拍照上传</span>
+        </div>
+        <div class="upload-choice-item" @click="choiceFile">
+          <i class="el-icon-folder-opened" />
+          <span>文件上传</span>
+        </div>
+      </div>
+    </el-dialog>
+
+    <!-- 摄像头拍照弹窗 -->
+    <el-dialog
+      title="拍照上传"
+      :visible.sync="cameraDialogVisible"
+      width="640px"
+      :close-on-click-modal="false"
+      @close="closeCamera"
+    >
+      <div class="camera-container">
+        <!-- 视频预览 -->
+        <video
+          v-show="!capturedImage"
+          ref="video"
+          class="camera-video"
+          autoplay
+          playsinline
+        />
+        <!-- 画布(用于拍照) -->
+        <canvas ref="canvas" style="display: none;" />
+
+        <!-- 拍照结果预览 -->
+        <img
+          v-if="capturedImage"
+          :src="capturedImage"
+          class="captured-image"
+        >
+      </div>
+
+      <div slot="footer" class="dialog-footer">
+        <el-button @click="closeCamera">取消</el-button>
+        <el-button
+          v-if="!capturedImage"
+          type="primary"
+          @click="takePhoto"
+        >
+          拍照
+        </el-button>
+        <el-button
+          v-if="capturedImage"
+          @click="retakePhoto"
+        >
+          重拍
+        </el-button>
+        <el-button
+          v-if="capturedImage"
+          type="primary"
+          @click="confirmPhoto"
+        >
+          确认使用
+        </el-button>
+      </div>
+    </el-dialog>
   </div>
 </template>
 <script>
@@ -2598,10 +2677,12 @@
 import Pagination from '@/components/Pagination'
 import { getToken } from '@/utils/auth'
 import { pages } from '@/settings'
+import dictMixin from '../../utils/dictMixin'
 
 export default {
   name: 'ArchivesEdit',
   components: { Treeselect, Pagination },
+  mixins: [dictMixin],
   props: {
     dialogVisible: {
       type: Boolean,
@@ -2697,6 +2778,12 @@
         }
       ],
       empBaseInfoImageUrl: '',
+      // 上传方式选择弹窗
+      uploadChoiceDialogVisible: false,
+      // 摄像头相关
+      cameraDialogVisible: false,
+      capturedImage: '',
+      stream: null,
       rules: {
         archivesNumb: [{ required: true, message: '请输入档案号', trigger: 'blur' }, {
           max: 20,
@@ -3429,7 +3516,8 @@
         modifyTime: '',
         modifier: '',
         empStatus: 0,
-        version: ''
+        version: '',
+        annualLeave: ''
       },
       fsnumShow: false,
       badRecordForm: {
@@ -3472,34 +3560,35 @@
         { type: 'dic_credentials' },
         { type: 'password' },
         { type: 'implicit' }
-      ],
-      statusOptions: [],
-      empTypeOptions: [],
-      nationOptions: [],
-      marriageOptions: [],
-      educationOptions: [],
-      nativePlaceOptions: [],
-      archivesStatusOptions: [],
-      insuranceTypeOptions: [],
-      empCardStatusOptions: [],
-      handbookStatusOptions: [],
-      ecgOptions: [],
-      certificateListOptions: [],
-      physicalExamTypeOptions: [],
-      contractStatusOptions: [],
-      leaveTypeOptions: [],
-      insuranceGaersOptions: [],
-      applayStatusOptions: [],
-      reportStatusOptions: [],
-      hospitalizatioFlagOptions: [],
-      settleStatusOptions: [],
-      arbitrationTypeOptions: [],
-      changeTypeOptions: [],
-      dimissionTypeOptions: [],
-      sexOptions: []
+      ]
     }
   },
   computed: {
+    // 字典选项计算属性
+    statusOptions() { return this.getDictOptions('PLITICAL') },
+    empTypeOptions() { return this.getDictOptions('EMPTYPE') },
+    nationOptions() { return this.getDictOptions('NATION') },
+    marriageOptions() { return this.getDictOptions('MARRIAGE') },
+    educationOptions() { return this.getDictOptions('EDUCATION') },
+    nativePlaceOptions() { return this.getDictOptions('NATIVEPLACE') },
+    archivesStatusOptions() { return this.getDictOptions('archivesStatus') },
+    insuranceTypeOptions() { return this.getDictOptions('INSURANCETYPE') },
+    empCardStatusOptions() { return this.getDictOptions('empCardStatus') },
+    handbookStatusOptions() { return this.getDictOptions('handbookStatus') },
+    certificateListOptions() { return this.getDictOptions('certificateList') },
+    physicalExamTypeOptions() { return this.getDictOptions('PHYSICALEXAMTYPE') },
+    ecgOptions() { return this.getDictOptions('ECG') },
+    contractStatusOptions() { return this.getDictOptions('CONTRACTSTATUS') },
+    leaveTypeOptions() { return this.getDictOptions('LEAVETYPE') },
+    insuranceGaersOptions() { return this.getDictOptions('INSURANCETYPE') },
+    applayStatusOptions() { return this.getDictOptions('applayStatus') },
+    reportStatusOptions() { return this.getDictOptions('reportStatus') },
+    hospitalizatioFlagOptions() { return this.getDictOptions('hospitalizatioFlag') },
+    settleStatusOptions() { return this.getDictOptions('settleStatus') },
+    arbitrationTypeOptions() { return this.getDictOptions('ZCTYPE') },
+    changeTypeOptions() { return this.getDictOptions('changeType') },
+    dimissionTypeOptions() { return this.getDictOptions('LZTYPE') },
+    sexOptions() { return this.getDictOptions('sex') },
     isVisible: {
       get() {
         return this.dialogVisible
@@ -3512,79 +3601,8 @@
   },
   mounted() {
     this.initDept()
-    /* 政治面貌*/
-    this.getDicts('PLITICAL').then(response => {
-      this.statusOptions = response.data
-    })
-    this.getDicts('sex').then(response => {
-      this.sexOptions = response.data
-    })
-    this.getDicts('empType').then(response => {
-      this.empTypeOptions = response.data
-    })
-    this.getDicts('NATION').then(response => {
-      this.nationOptions = response.data
-    })
-    this.getDicts('MARRIAGE').then(response => {
-      this.marriageOptions = response.data
-    })
-    this.getDicts('EDUCATION').then(response => {
-      this.educationOptions = response.data
-    })
-    this.getDicts('NATIVEPLACE').then(response => {
-      this.nativePlaceOptions = response.data
-    })
-    this.getDicts('archivesStatus').then(response => {
-      this.archivesStatusOptions = response.data
-    })
-    this.getDicts('INSURANCETYPE').then(response => {
-      this.insuranceTypeOptions = response.data
-    })
-    this.getDicts('empCardStatus').then(response => {
-      this.empCardStatusOptions = response.data
-    })
-    this.getDicts('handbookStatus').then(response => {
-      this.handbookStatusOptions = response.data
-    })
-    this.getDicts('certificateList').then(response => {
-      this.certificateListOptions = response.data
-    })
-    this.getDicts('PHYSICALEXAMTYPE').then(response => {
-      this.physicalExamTypeOptions = response.data
-    })
-    this.getDicts('ECG').then(response => {
-      this.ecgOptions = response.data
-    })
-    this.getDicts('CONTRACTSTATUS').then(response => {
-      this.contractStatusOptions = response.data
-    })
-    this.getDicts('LEAVETYPE').then(response => {
-      this.leaveTypeOptions = response.data
-    })
-    this.getDicts('INSURANCETYPE').then(response => {
-      this.insuranceGaersOptions = response.data
-    })
-    this.getDicts('applayStatus').then(response => {
-      this.applayStatusOptions = response.data
-    })
-    this.getDicts('reportStatus').then(response => {
-      this.reportStatusOptions = response.data
-    })
-    this.getDicts('hospitalizatioFlag').then(response => {
-      this.hospitalizatioFlagOptions = response.data
-    })
-    this.getDicts('settleStatus').then(response => {
-      this.settleStatusOptions = response.data
-    })
-    this.getDicts('ZCTYPE').then(response => {
-      this.arbitrationTypeOptions = response.data
-    })
-    this.getDicts('changeType').then(response => {
-      this.changeTypeOptions = response.data
-    })
-    this.getDicts('LZTYPE').then(response => {
-      this.dimissionTypeOptions = response.data
-    })
+    // 字典数据已在登录时预加载,直接从 Vuex 获取
+    this.initDictTypes(['PLITICAL', 'sex', 'EMPTYPE', 'NATION', 'MARRIAGE', 'EDUCATION', 'NATIVEPLACE', 'archivesStatus', 'INSURANCETYPE', 'empCardStatus', 'handbookStatus', 'certificateList', 'PHYSICALEXAMTYPE', 'ECG', 'CONTRACTSTATUS', 'LEAVETYPE', 'applayStatus', 'reportStatus', 'hospitalizatioFlag', 'settleStatus', 'ZCTYPE', 'changeType', 'LZTYPE'])
   },
   methods: {
     typeFormat(row, column) {
@@ -4014,22 +4032,8 @@
       this.physicalExamForm = { ...val }
       this.workExperienceForm = { ...val }
       this.badRecordForm = { ...val }
+      // 只加载附件数据(基本信息已在val中),其他模块懒加载
       this.initlabel()
-      this.initphysicalExamData()
-      this.initList() // 工作经历
-      this.initjobChangeData() // 调岗记录
-      this.initdimissionLogData() // 入离职记录
-      this.initcontractInfoData() // 合同信息
-      this.initleaveInfoData() // 请假记录
-      this.initresignData() // 辞职申请
-      this.initunemploymentData() // 失业金领取
-      this.initinsuranceData() // 社保申请
-      this.initremarkInfoData() // 备注
-      this.initlaborTroubleData() // 仲裁案件
-      this.initoccupationalData() // 工伤案件
-      this.initbadRecordData() // 不良记录
-      this.initaccidentCasesData() // 意外险案件
-      this.initdimissionAttendData() // 考勤情况
     },
     initphysicalExamData(params = {}) {
       params.pageSize = this.pagination.size
@@ -4695,6 +4699,149 @@
     },
     getIndex($index) {
       return (this.pagination.num - 1) * this.pagination.size + $index + 1
+    },
+    // 打开上传方式选择弹窗
+    openUploadChoice() {
+      this.uploadChoiceDialogVisible = true
+    },
+    // 选择拍照上传
+    choiceCamera() {
+      this.uploadChoiceDialogVisible = false
+      this.cameraDialogVisible = true
+      this.$nextTick(() => {
+        this.initCamera()
+      })
+    },
+    // 选择文件上传
+    choiceFile() {
+      this.uploadChoiceDialogVisible = false
+      // 创建隐藏的文件输入框
+      const input = document.createElement('input')
+      input.type = 'file'
+      input.accept = 'image/*'
+      input.onchange = (e) => {
+        const file = e.target.files[0]
+        if (file) {
+          this.handleFileUpload(file)
+        }
+      }
+      input.click()
+    },
+    // 处理文件上传
+    handleFileUpload(file) {
+      // 验证文件类型
+      const isImage = file.type.startsWith('image/')
+      if (!isImage) {
+        this.$message.error('请上传图片文件')
+        return
+      }
+      // 验证文件大小(限制10MB)
+      const isLt10M = file.size / 1024 / 1024 < 10
+      if (!isLt10M) {
+        this.$message.error('图片大小不能超过10MB')
+        return
+      }
+      // 生成预览URL
+      const imageUrl = URL.createObjectURL(file)
+      this.empBaseInfoImageUrl = imageUrl
+      // 读取文件为Base64
+      const reader = new FileReader()
+      reader.onload = (e) => {
+        this.empBaseInfoForm.imagePath = e.target.result
+        this.$message.success('照片上传成功')
+      }
+      reader.readAsDataURL(file)
+    },
+    // 打开摄像头
+    openCamera() {
+      this.cameraDialogVisible = true
+      this.$nextTick(() => {
+        this.initCamera()
+      })
+    },
+    // 初始化摄像头
+    async initCamera() {
+      try {
+        // 请求摄像头权限
+        this.stream = await navigator.mediaDevices.getUserMedia({
+          video: {
+            width: { ideal: 640 },
+            height: { ideal: 480 },
+            facingMode: 'user' // 前置摄像头
+          },
+          audio: false
+        })
+
+        // 将视频流绑定到 video 元素
+        const video = this.$refs.video
+        if (video) {
+          video.srcObject = this.stream
+        }
+      } catch (error) {
+        this.$message.error('无法访问摄像头,请检查摄像头权限设置')
+        console.error('摄像头初始化失败:', error)
+      }
+    },
+    // 拍照
+    takePhoto() {
+      const video = this.$refs.video
+      const canvas = this.$refs.canvas
+
+      if (!video || !canvas) return
+
+      // 设置画布尺寸
+      canvas.width = video.videoWidth || 640
+      canvas.height = video.videoHeight || 480
+
+      // 绘制视频帧到画布
+      const ctx = canvas.getContext('2d')
+      ctx.drawImage(video, 0, 0, canvas.width, canvas.height)
+
+      // 转换为图片数据
+      this.capturedImage = canvas.toDataURL('image/jpeg', 0.9)
+
+      // 停止摄像头
+      this.stopCamera()
+    },
+    // 重拍
+    retakePhoto() {
+      this.capturedImage = ''
+      this.initCamera()
+    },
+    // 确认使用照片
+    confirmPhoto() {
+      if (this.capturedImage) {
+        // 设置图片预览
+        this.empBaseInfoImageUrl = this.capturedImage
+
+        // 设置表单数据(Base64格式)
+        this.empBaseInfoForm.imagePath = this.capturedImage
+
+        // 关闭弹窗
+        this.closeCamera()
+
+        this.$message.success('照片已保存')
+      }
+    },
+    // 关闭摄像头
+    closeCamera() {
+      this.stopCamera()
+      this.cameraDialogVisible = false
+      this.capturedImage = ''
+    },
+    // 停止摄像头流
+    stopCamera() {
+      if (this.stream) {
+        this.stream.getTracks().forEach(track => {
+          track.stop()
+        })
+        this.stream = null
+      }
+
+      const video = this.$refs.video
+      if (video) {
+        video.srcObject = null
+      }
     },
     cleanOccupational() {
       this.occupationalForm.occupationalId = ''
@@ -5500,57 +5647,120 @@
     goAnchor: function(type) {
       let item = 1
       this.item = type
+      // 根据点击的菜单懒加载对应数据
       switch (type) {
         case 'jbxx':
           item = 0
+          // 基本信息 - 已加载
           break
         case 'gzjl':
           item = 1
+          // 工作经历 - 懒加载
+          if (!this.workExperienceData || this.workExperienceData.length === 0) {
+            this.initList()
+          }
           break
         case 'tjxx':
           item = 2
+          // 体检信息 - 懒加载
+          if (!this.physicalExamData || this.physicalExamData.length === 0) {
+            this.initphysicalExamData()
+          }
           break
         case 'htxx':
           item = 3
+          // 合同信息 - 懒加载
+          if (!this.contractInfoData || this.contractInfoData.length === 0) {
+            this.initcontractInfoData()
+          }
           break
         case 'tgjl':
           item = 4
+          // 调岗记录 - 懒加载
+          if (!this.jobChangeData || this.jobChangeData.length === 0) {
+            this.initjobChangeData()
+          }
           break
         case 'qjjl':
           item = 5
+          // 请假记录 - 懒加载
+          if (!this.leaveInfoData || this.leaveInfoData.length === 0) {
+            this.initleaveInfoData()
+          }
           break
         case 'czsq':
           item = 6
+          // 辞职申请 - 懒加载
+          if (!this.resignData || this.resignData.length === 0) {
+            this.initresignData()
+          }
           break
         case 'lzdykq':
           item = 7
+          // 离职当月考勤 - 懒加载
+          if (!this.dimissionAttendData || this.dimissionAttendData.length === 0) {
+            this.initdimissionAttendData()
+          }
           break
         case 'rlzjl':
           item = 8
+          // 入离职记录 - 懒加载
+          if (!this.dimissionLogData || this.dimissionLogData.length === 0) {
+            this.initdimissionLogData()
+          }
           break
         case 'syjlq':
           item = 9
+          // 失业金领取 - 懒加载
+          if (!this.unemploymentData || this.unemploymentData.length === 0) {
+            this.initunemploymentData()
+          }
           break
         case 'sbsq':
           item = 10
+          // 社保申请 - 懒加载
+          if (!this.insuranceData || this.insuranceData.length === 0) {
+            this.initinsuranceData()
+          }
           break
         case 'ywxaj':
           item = 11
+          // 意外险案件 - 懒加载
+          if (!this.accidentCasesData || this.accidentCasesData.length === 0) {
+            this.initaccidentCasesData()
+          }
           break
         case 'gsaj':
           item = 12
+          // 工伤案件 - 懒加载
+          if (!this.occupationalData || this.occupationalData.length === 0) {
+            this.initoccupationalData()
+          }
           break
         case 'lzaj':
           item = 13
+          // 仲裁案件 - 懒加载
+          if (!this.laborTroubleData || this.laborTroubleData.length === 0) {
+            this.initlaborTroubleData()
+          }
           break
         case 'bljl':
           item = 14
+          // 不良记录 - 懒加载
+          if (!this.badRecordData || this.badRecordData.length === 0) {
+            this.initbadRecordData()
+          }
           break
         case 'bz':
           item = 15
+          // 备注 - 懒加载
+          if (!this.remarkInfoData || this.remarkInfoData.length === 0) {
+            this.initremarkInfoData()
+          }
           break
         case 'jljt':
           item = 16
+          // 记录截图及相关附件 - 已在initlabel中加载
           break
       }
       this.$nextTick(() => {
@@ -5925,6 +6135,113 @@
     height: 100px;
   }
 
+  // 头像包装器
+  .avatar-wrapper {
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    margin-bottom: 10px;
+
+    .avatar {
+      width: 150px;
+      height: 150px;
+      border-radius: 4px;
+      cursor: pointer;
+      object-fit: cover;
+      border: 1px dashed #d9d9d9;
+
+      &:hover {
+        border-color: #409eff;
+      }
+    }
+
+    .avatar-uploader-placeholder {
+      width: 150px;
+      height: 150px;
+      border: 1px dashed #d9d9d9;
+      border-radius: 4px;
+      display: flex;
+      flex-direction: column;
+      justify-content: center;
+      align-items: center;
+      cursor: pointer;
+      background-color: #fafafa;
+
+      &:hover {
+        border-color: #409eff;
+        background-color: #f0f9ff;
+      }
+
+      .avatar-uploader-icon {
+        font-size: 28px;
+        color: #8c939d;
+        line-height: 1;
+        margin-bottom: 8px;
+      }
+
+      .upload-tip {
+        font-size: 12px;
+        color: #8c939d;
+      }
+    }
+  }
+
+  // 上传方式选择容器
+  .upload-choice-container {
+    display: flex;
+    justify-content: space-around;
+    padding: 20px 0;
+
+    .upload-choice-item {
+      display: flex;
+      flex-direction: column;
+      align-items: center;
+      justify-content: center;
+      width: 120px;
+      height: 120px;
+      border: 2px dashed #d9d9d9;
+      border-radius: 8px;
+      cursor: pointer;
+      transition: all 0.3s;
+
+      &:hover {
+        border-color: #409eff;
+        background-color: #f5f7fa;
+      }
+
+      i {
+        font-size: 40px;
+        color: #409eff;
+        margin-bottom: 10px;
+      }
+
+      span {
+        font-size: 14px;
+        color: #606266;
+      }
+    }
+  }
+
+  // 摄像头容器
+  .camera-container {
+    text-align: center;
+
+    .camera-video {
+      width: 100%;
+      max-width: 600px;
+      height: auto;
+      border-radius: 4px;
+      background: #000;
+    }
+
+    .captured-image {
+      width: 100%;
+      max-width: 600px;
+      height: auto;
+      border-radius: 4px;
+    }
+  }
+
   .table-button {
     color: #a00515;
     display: inline-block;

--
Gitblit v1.8.0