Browse Source

同步注册功能

xukai 4 months ago
parent
commit
c96ff7d388
2 changed files with 348 additions and 42 deletions
  1. 4 1
      src/api/login/index.ts
  2. 344 41
      src/view/register/index.vue

+ 4 - 1
src/api/login/index.ts

@@ -34,5 +34,8 @@ export function useLoginApi() {
     register: (query?: object) => {
       return request.postRequest(basePath, 'Personnel', 'RegisterPersonnel', query)
     },
-  };
+    checkUserNamePhoneExists: (query?: object) => {
+      return request.postRequest(basePath, 'Personnel', 'CheckUserNamePhoneExists', query)
+    },
+  }
 }

+ 344 - 41
src/view/register/index.vue

@@ -39,9 +39,10 @@
         <van-cell-group inset>
           <van-field
             v-model="state.form.userName"
-            label="用户名"
-            placeholder="用户名"
-            :rules="[{ required: true, message: '请填写用户名' }]"
+            label="登录账号"
+            placeholder="登录账号"
+            @blur="checkUserNamePhoneExists('userName')"
+            :rules="[{ required: true, message: '请填写登录账号' }]"
           />
           <van-field
             v-model="state.form.password"
@@ -116,20 +117,22 @@
             readonly
             label="组织部门"
             placeholder="组织部门"
-            @click="showDeptPicker = true"
+            @click="showDeptPickerHandler"
             :rules="[{ required: true, message: '请选择组织部门' }]"
           />
           <van-field
-            v-model="state.form.unitName"
-            label="单位名称"
-            placeholder="单位名称"
-            :rules="[{ required: true, message: '请填写单位名称' }]"
+            v-if="needInputDeptInfo"
+            v-model="state.form.deptDescribe"
+            label="部门描述"
+            placeholder="部门描述"
+            :rules="[{ required: true, message: '请填写部门描述' }]"
           />
           <van-field
             v-model="state.form.phone"
             type="tel"
             label="手机号"
             placeholder="手机号"
+            @blur="checkUserNamePhoneExists('phone')"
             :rules="[{ required: true, message: '请填写手机号' }]"
           />
           <van-field
@@ -144,6 +147,7 @@
           >
             <template #input>
               <van-radio-group
+                disabled
                 v-model="state.form.idType"
                 label="证件类型"
                 placeholder="证件类型"
@@ -174,6 +178,7 @@
           />
           <template v-else>
             <van-field
+              :disabled="!state.form.deptId"
               v-model="state.form.projectGroupName"
               is-link
               readonly
@@ -182,29 +187,54 @@
               :rules="[{ required: true, message: '请填写课题组' }]"
               @click="showPjtPicker = true"
             />
-            <van-field
-              v-model="state.form.projectDate"
-              is-link
-              readonly
-              label="所在时间"
-              placeholder="所在时间"
-              :rules="[{ required: true, validator: checkDate, message: '请选择所在时间' }]"
-              @click="showPjtDatePicker = true"
-            />
           </template>
         </van-cell-group>
       </van-form>
-      <van-form ref="projectInfoRef" v-show="state.active == 2" required="auto">
+      <van-form
+        ref="projectInfoRef"
+        v-show="state.active == 2"
+        required="auto"
+      >
         <van-cell-group inset>
           <van-field>
             <template #input>
               <div class="w100">
-                <van-button type="primary" block @click="addProject">新增</van-button>
-                <template v-for="(item, index) in state.form.projectList" :key="index">
-                  <van-field v-model="item.projectName" placeholder="项目名称" class="mt10"></van-field>
-                  <van-field v-model="item.projectTypeName" is-link readonly label="项目类型" placeholder="项目类型" @click="openPjtType(item, index)" />
-                  <van-field v-model="item.projectSource" placeholder="项目来源"></van-field>
-                  <van-button style="height: 30px;" type="danger" icon="el-icon-delete" @click="delProjectItem(index)">删除</van-button>
+                <van-button
+                  type="primary"
+                  block
+                  @click="addProject"
+                >
+                  新增
+                </van-button>
+                <template
+                  v-for="(item, index) in state.form.projectList"
+                  :key="index"
+                >
+                  <van-field
+                    v-model="item.projectName"
+                    placeholder="项目名称"
+                    class="mt10"
+                  ></van-field>
+                  <van-field
+                    v-model="item.projectTypeName"
+                    is-link
+                    readonly
+                    label="项目类型"
+                    placeholder="项目类型"
+                    @click="openPjtType(item, index)"
+                  />
+                  <van-field
+                    v-model="item.projectSource"
+                    placeholder="项目来源"
+                  ></van-field>
+                  <van-button
+                    style="height: 30px"
+                    type="danger"
+                    icon="el-icon-delete"
+                    @click="delProjectItem(index)"
+                  >
+                    删除
+                  </van-button>
                 </template>
               </div>
             </template>
@@ -244,12 +274,79 @@
       v-model:show="showDeptPicker"
       position="bottom"
     >
-      <van-picker
-        :columns="deptData"
-        :columns-field-names="{ text: 'deptName', value: 'id' }"
-        @confirm="onDeptConfirm"
-        @cancel="showDeptPicker = false"
-      />
+      <div class="cascade-picker">
+        <div class="cascade-header">
+          <span>请选择部门</span>
+          <van-icon
+            name="cross"
+            @click="showDeptPicker = false"
+          />
+        </div>
+        <div class="cascade-content">
+          <!-- 第一级 -->
+          <div class="cascade-column">
+            <div
+              v-for="item in cascadeData.level1"
+              :key="item.id"
+              class="cascade-item"
+              :class="{ active: selectedLevel1?.id === item.id }"
+              @click="selectLevel1(item)"
+            >
+              <span>{{ item.deptName }}{{ item.children?.length ? `(${item.children.length})` : '' }}</span>
+              <van-icon
+                v-if="item.children?.length"
+                name="arrow"
+              />
+            </div>
+          </div>
+
+          <!-- 第二级 -->
+          <div
+            v-if="cascadeData.level2.length"
+            class="cascade-column"
+          >
+            <div
+              v-for="item in cascadeData.level2"
+              :key="item.id"
+              class="cascade-item"
+              :class="{ active: selectedLevel2?.id === item.id }"
+              @click="selectLevel2(item)"
+            >
+              <span>{{ item.deptName }}{{ item.children?.length ? `(${item.children.length})` : '' }}</span>
+              <van-icon
+                v-if="item.children?.length"
+                name="arrow"
+              />
+            </div>
+          </div>
+
+          <!-- 第三级 -->
+          <div
+            v-if="cascadeData.level3.length"
+            class="cascade-column"
+          >
+            <div
+              v-for="item in cascadeData.level3"
+              :key="item.id"
+              class="cascade-item"
+              :class="{ active: selectedLevel3?.id === item.id }"
+              @click="selectLevel3(item)"
+            >
+              <span>{{ item.deptName }}</span>
+            </div>
+          </div>
+        </div>
+
+        <div class="cascade-footer">
+          <van-button @click="showDeptPicker = false">取消</van-button>
+          <van-button
+            type="primary"
+            @click="confirmCascadeSelection"
+          >
+            确定
+          </van-button>
+        </div>
+      </div>
     </van-popup>
     <!-- 课题组选择器 -->
     <van-popup
@@ -300,7 +397,7 @@
 </template>
 
 <script name="register" lang="ts" setup>
-  import { onMounted, reactive, ref } from 'vue'
+  import { onMounted, reactive, ref, watch } from 'vue'
   import to from 'await-to-js'
   import { useLoginApi } from '/@/api/login/index'
   import crypto from 'sm-crypto'
@@ -324,6 +421,17 @@
   const showPjtTypePicker = ref(false)
   const show = ref(false)
   const pjtTypeIndex = ref(-1)
+  const needInputDeptInfo = ref(false)
+  // 级联选择器相关数据
+  const cascadeData = ref({
+    level1: <any[]>[],
+    level2: <any[]>[],
+    level3: <any[]>[],
+  })
+  const selectedLevel1 = ref<any>(null)
+  const selectedLevel2 = ref<any>(null)
+  const selectedLevel3 = ref<any>(null)
+
   const userTypeList = ref(<RowDicDataType[]>[])
   const userSexList = ref(<RowDicDataType[]>[])
   const userCertList = ref(<RowDicDataType[]>[])
@@ -364,8 +472,11 @@
       projectGroupId: null,
       projectList: <any[]>[],
       unitName: '',
+      deptDescribe: '',
     },
   })
+  const deptDataBackup = ref(<any[]>[])
+
   const getDicts = () => {
     Promise.all([
       dictApi.getDictDataByType('sys_user_type'),
@@ -378,11 +489,58 @@
       userTypeList.value = type.data.values || []
       userSexList.value = sex.data.values || []
       userCertList.value = cert.data.values || []
+      deptDataBackup.value = dept.data || []
       deptData.value = dept.data || []
       pjtList.value = pjt.data.list || []
       pjtTypeList.value = pjtType.data.values || []
+
+      // 初始化级联数据
+      initCascadeData()
     })
   }
+
+  const checkUserNamePhoneExists = async (type: 'userName' | 'phone') => {
+    let resquest = loginApi.checkUserNamePhoneExists({ userName: state.form.userName, phone: '' })
+
+    if (type === 'phone') {
+      resquest = loginApi.checkUserNamePhoneExists({ userName: '', phone: state.form.phone })
+    }
+
+    await to(resquest)
+  }
+
+  const renderDeptData = () => {
+    let daptTree = deptDataBackup.value
+
+    if (state.form.userType === '10') {
+      daptTree = daptTree.filter((item) => item.id === 100001)
+    }
+
+    if (state.form.userType === '15') {
+      daptTree = daptTree.filter((item) => item.id !== 1000220)
+    }
+
+    if (state.form.userType === '20') {
+      daptTree = daptTree.filter((item) => item.id === 1000220)
+    }
+
+    return daptTree
+  }
+
+  const deptIncludesProjectGroup = async (deptId: number) => {
+    const [err, res]: ToResponse = await to(
+      proApi.getProjectGroupList({
+        pgOrg: deptId,
+        pgName: '',
+        pageNum: 1,
+        pageSize: 1000,
+      }),
+    )
+    if (err) return
+
+    pjtList.value = res?.data.list || []
+  }
+
   const preStep = () => {
     state.active--
   }
@@ -408,22 +566,33 @@
       return true
     }
   }
-  const checkDate = (value: any) => {
-    if (state.form.registerType === '10' && !value.length) {
-      return false
-    }
-    return true
-  }
+
+  watch(
+    () => state.form.userType,
+    (newVal) => {
+      if (newVal === '20') {
+        state.form.idType = '30'
+        needInputDeptInfo.value = true
+      } else {
+        state.form.idType = '20'
+        state.form.deptId = null
+        state.form.deptName = ''
+        needInputDeptInfo.value = false
+      }
+    },
+    { immediate: true },
+  )
+
   const changeType = (val: string) => {
     state.form.registerType = val
     if (val === '10' && state.active == 2) {
       state.active = 1
     }
   }
-  const onDeptConfirm = ({ selectedOptions }: { selectedOptions: any[] }) => {
-    showDeptPicker.value = false
-    state.form.deptName = selectedOptions[selectedOptions.length - 1].deptName
-    state.form.deptId = selectedOptions[selectedOptions.length - 1].id
+
+  const showDeptPickerHandler = () => {
+    initCascadeData()
+    showDeptPicker.value = true
   }
   const onPjtPicker = ({ selectedOptions }: { selectedOptions: any[] }) => {
     showPjtPicker.value = false
@@ -454,6 +623,64 @@
     state.form.projectList[pjtTypeIndex.value].projectType = selectedOptions[0].dictValue
     state.form.projectList[pjtTypeIndex.value].projectTypeName = selectedOptions[0].dictLabel
   }
+
+  watch(
+    () => state.form.deptId,
+    (newVal) => {
+      if (newVal) {
+        deptIncludesProjectGroup(newVal)
+      }
+    },
+  )
+
+  // 级联选择器方法
+  const initCascadeData = () => {
+    cascadeData.value.level1 = renderDeptData()
+    cascadeData.value.level2 = []
+    cascadeData.value.level3 = []
+    selectedLevel1.value = null
+    selectedLevel2.value = null
+    selectedLevel3.value = null
+  }
+
+  const selectLevel1 = (item: any) => {
+    selectedLevel1.value = item
+    selectedLevel2.value = null
+    selectedLevel3.value = null
+
+    if (item.children && item.children.length > 0) {
+      cascadeData.value.level2 = item.children
+      cascadeData.value.level3 = []
+    } else {
+      cascadeData.value.level2 = []
+      cascadeData.value.level3 = []
+    }
+  }
+
+  const selectLevel2 = (item: any) => {
+    selectedLevel2.value = item
+    selectedLevel3.value = null
+
+    if (item.children && item.children.length > 0) {
+      cascadeData.value.level3 = item.children
+    } else {
+      cascadeData.value.level3 = []
+    }
+  }
+
+  const selectLevel3 = (item: any) => {
+    selectedLevel3.value = item
+  }
+
+  const confirmCascadeSelection = () => {
+    const selectedItem = selectedLevel3.value || selectedLevel2.value || selectedLevel1.value
+    if (selectedItem) {
+      state.form.deptName = selectedItem.deptName
+      state.form.deptId = selectedItem.id
+      showDeptPicker.value = false
+    }
+  }
+
   const onRegister = async () => {
     const form = state.form.registerType == '10' ? personInfoRef.value : personInfoRef.value
     const [error] = await to(form.validate())
@@ -508,4 +735,80 @@
       justify-content: flex-end;
     }
   }
+
+  .cascade-picker {
+    background: #fff;
+    border-radius: 8px 8px 0 0;
+    overflow: hidden;
+
+    .cascade-header {
+      display: flex;
+      justify-content: space-between;
+      align-items: center;
+      padding: 16px;
+      border-bottom: 1px solid #f5f5f5;
+      font-size: 16px;
+      font-weight: 500;
+
+      .van-icon {
+        font-size: 18px;
+        color: #969799;
+        cursor: pointer;
+      }
+    }
+
+    .cascade-content {
+      display: flex;
+      height: 300px;
+
+      .cascade-column {
+        flex: 1;
+        border-right: 1px solid #f5f5f5;
+        overflow-y: auto;
+
+        &:last-child {
+          border-right: none;
+        }
+
+        .cascade-item {
+          display: flex;
+          justify-content: space-between;
+          align-items: center;
+          padding: 12px 16px;
+          cursor: pointer;
+          transition: background-color 0.2s;
+
+          &:hover {
+            background-color: #f7f8fa;
+          }
+
+          &.active {
+            background-color: #e8f3ff;
+            color: #1989fa;
+          }
+
+          span {
+            flex: 1;
+            font-size: 14px;
+          }
+
+          .van-icon {
+            font-size: 12px;
+            color: #c8c9cc;
+          }
+        }
+      }
+    }
+
+    .cascade-footer {
+      display: flex;
+      padding: 12px 16px;
+      border-top: 1px solid #f5f5f5;
+      gap: 12px;
+
+      .van-button {
+        flex: 1;
+      }
+    }
+  }
 </style>