浏览代码

Merge remote-tracking branch 'origin/master'

yzx 7 月之前
父节点
当前提交
f8608eaf25
共有 60 个文件被更改,包括 8217 次插入36 次删除
  1. 3 0
      src/api/system/dept/index.ts
  2. 51 0
      src/api/system/studentAttendance/index.ts
  3. 53 0
      src/api/system/user/index.ts
  4. 6 0
      src/api/system/user/profile.ts
  5. 59 0
      src/api/system/userAchievement/index.ts
  6. 1 1
      src/components/FormCreate/src/useFormCreateDesigner.ts
  7. 2 2
      src/components/bpmnProcessDesigner/package/designer/ProcessViewer.vue
  8. 1 1
      src/components/bpmnProcessDesigner/package/penal/task/task-components/UserTask.vue
  9. 1 0
      src/layout/components/UserInfo/src/UserInfo.vue
  10. 7 2
      src/locales/zh-CN.ts
  11. 32 0
      src/views/Profile/components/ProfileUser.vue
  12. 2 2
      src/views/bpm/processInstance/detail/ProcessInstanceTimeline.vue
  13. 1 1
      src/views/bpm/processInstance/manager/index.vue
  14. 3 3
      src/views/crm/business/status/BusinessStatusForm.vue
  15. 2 2
      src/views/crm/statistics/customer/index.vue
  16. 2 2
      src/views/crm/statistics/funnel/index.vue
  17. 2 2
      src/views/crm/statistics/performance/index.vue
  18. 2 2
      src/views/crm/statistics/portrait/index.vue
  19. 2 2
      src/views/crm/statistics/rank/index.vue
  20. 3 3
      src/views/system/dept/DeptForm.vue
  21. 5 5
      src/views/system/dept/index.vue
  22. 96 0
      src/views/system/graduateStudent/UserAssignRoleForm.vue
  23. 305 0
      src/views/system/graduateStudent/UserForm.vue
  24. 138 0
      src/views/system/graduateStudent/UserImportForm.vue
  25. 332 0
      src/views/system/graduateStudent/index.vue
  26. 244 0
      src/views/system/graduateStudentT/UserForm.vue
  27. 138 0
      src/views/system/graduateStudentT/UserImportForm.vue
  28. 332 0
      src/views/system/graduateStudentT/index.vue
  29. 127 0
      src/views/system/selfAchievement/UserAchievementForm.vue
  30. 217 0
      src/views/system/selfAchievement/index.vue
  31. 131 0
      src/views/system/studentAttendanceManage/studentAttendance/StudentAttendanceForm.vue
  32. 284 0
      src/views/system/studentAttendanceManage/studentAttendance/index.vue
  33. 131 0
      src/views/system/studentAttendanceManage/studentAttendanceError/StudentAttendanceForm.vue
  34. 276 0
      src/views/system/studentAttendanceManage/studentAttendanceError/index.vue
  35. 1 1
      src/views/system/user/DeptTree.vue
  36. 30 4
      src/views/system/user/UserForm.vue
  37. 1 1
      src/views/system/user/index.vue
  38. 360 0
      src/views/system/user/student.vue
  39. 360 0
      src/views/system/user/teacher.vue
  40. 174 0
      src/views/system/workroomCollege/dept/DeptForm.vue
  41. 164 0
      src/views/system/workroomCollege/dept/index.vue
  42. 63 0
      src/views/system/workroomCollege/user/DeptTree.vue
  43. 96 0
      src/views/system/workroomCollege/user/UserAssignRoleForm.vue
  44. 243 0
      src/views/system/workroomCollege/user/UserForm.vue
  45. 138 0
      src/views/system/workroomCollege/user/UserImportForm.vue
  46. 362 0
      src/views/system/workroomCollege/user/index.vue
  47. 360 0
      src/views/system/workroomCollege/user/student.vue
  48. 360 0
      src/views/system/workroomCollege/user/teacher.vue
  49. 160 0
      src/views/system/workroomCollege/userAchievement/UserAchievementForm.vue
  50. 243 0
      src/views/system/workroomCollege/userAchievement/index.vue
  51. 112 0
      src/views/system/workroomTeacher/dept/index.vue
  52. 63 0
      src/views/system/workroomTeacher/user/DeptTree.vue
  53. 96 0
      src/views/system/workroomTeacher/user/UserAssignRoleForm.vue
  54. 244 0
      src/views/system/workroomTeacher/user/UserForm.vue
  55. 138 0
      src/views/system/workroomTeacher/user/UserImportForm.vue
  56. 362 0
      src/views/system/workroomTeacher/user/index.vue
  57. 355 0
      src/views/system/workroomTeacher/user/student.vue
  58. 349 0
      src/views/system/workroomTeacher/user/teacher.vue
  59. 149 0
      src/views/system/workroomTeacher/userAchievement/UserAchievementForm.vue
  60. 243 0
      src/views/system/workroomTeacher/userAchievement/index.vue

+ 3 - 0
src/api/system/dept/index.ts

@@ -27,6 +27,9 @@ export const getDept = async (id: number) => {
   return await request.get({ url: '/system/dept/get?id=' + id })
 }
 
+// 查询部门详情
+
+
 // 新增部门
 export const createDept = async (data: DeptVO) => {
   return await request.post({ url: '/system/dept/create', data: data })

+ 51 - 0
src/api/system/studentAttendance/index.ts

@@ -0,0 +1,51 @@
+import request from '@/config/axios'
+
+// 学生考勤记录 VO
+export interface StudentAttendanceVO {
+  id: number // 自增id
+  studentId: number // 学生id
+  deptId: string // 工作间id
+  date: Date // 日期
+  clockInTime: Date // 打卡时间
+  clockInStatus: string // 打卡状态 0正常,1迟到、2早退、3缺勤、4请假
+  remark: string // 备注原因
+  studentName: string // 学生名称
+}
+
+// 学生考勤记录 API
+export const StudentAttendanceApi = {
+  // 查询学生考勤记录分页
+  getStudentAttendancePage: async (params: any) => {
+    return await request.get({ url: `/system/student-attendance/page`, params })
+  },
+  // 查询学生考勤记录分页
+  getStudentAttendanceErrorPage: async (params: any) => {
+    return await request.get({ url: `/system/student-attendance/errorPage`, params })
+  },
+
+
+  // 查询学生考勤记录详情
+  getStudentAttendance: async (id: number) => {
+    return await request.get({ url: `/system/student-attendance/get?id=` + id })
+  },
+
+  // 新增学生考勤记录
+  createStudentAttendance: async (data: StudentAttendanceVO) => {
+    return await request.post({ url: `/system/student-attendance/create`, data })
+  },
+
+  // 修改学生考勤记录
+  updateStudentAttendance: async (data: StudentAttendanceVO) => {
+    return await request.put({ url: `/system/student-attendance/update`, data })
+  },
+
+  // 删除学生考勤记录
+  deleteStudentAttendance: async (id: number) => {
+    return await request.delete({ url: `/system/student-attendance/delete?id=` + id })
+  },
+
+  // 导出学生考勤记录 Excel
+  exportStudentAttendance: async (params) => {
+    return await request.download({ url: `/system/student-attendance/export-excel`, params })
+  },
+}

+ 53 - 0
src/api/system/user/index.ts

@@ -15,6 +15,7 @@ export interface UserVO {
   remark: string
   loginDate: Date
   createTime: Date
+  userType: string
 }
 
 // 查询用户管理列表
@@ -22,6 +23,48 @@ export const getUserPage = (params: PageParam) => {
   return request.get({ url: '/system/user/page', params })
 }
 
+// 查询老师管理列表
+export const getTeacherPage = (params: PageParam) => {
+  return request.get({ url: '/system/user/page0', params })
+}
+// 查询学生管理列表
+export const getStudentPage = (params: PageParam) => {
+  return request.get({ url: '/system/user/page1', params })
+}
+
+
+// 查询当前工作间下老师管理列表
+export const getDeptTeacherPage = (params: PageParam) => {
+  return request.get({ url: '/system/user/DeptTeacherPage', params })
+}
+
+// 查询当前工作间下学生管理列表
+export const getDeptStudentPage = (params: PageParam) => {
+  return request.get({ url: '/system/user/DeptStudentPage', params })
+}
+// 查询当前工作间下用户
+export const getDeptUser = () => {
+  return request.get({ url: '/system/user/DeptUser'})
+}
+
+// 查询毕业生管理列表(学院)
+export const getGraduateStudentPage = (params: PageParam) => {
+  return request.get({ url: '/system/user/graduateStudentPage',params })
+}
+
+// 查询毕业生管理列表(导师)
+export const getGraduateStudentTPage = (params: PageParam) => {
+  return request.get({ url: '/system/user/graduateStudentTPage',params })
+}
+
+// 查询所有导师列表
+export const getSupervisor = () => {
+  return request.get({ url: '/system/user/supervisor'})
+}
+
+
+
+
 // 查询所有用户列表
 export const getAllUser = () => {
   return request.get({ url: '/system/user/all' })
@@ -37,6 +80,11 @@ export const createUser = (data: UserVO) => {
   return request.post({ url: '/system/user/create', data })
 }
 
+// 新增教师
+export const createTeacher = (data: UserVO) => {
+  return request.post({ url: '/system/user/create_teacher', data })
+}
+
 // 修改用户
 export const updateUser = (data: UserVO) => {
   return request.put({ url: '/system/user/update', data })
@@ -57,6 +105,11 @@ export const importUserTemplate = () => {
   return request.download({ url: '/system/user/get-import-template' })
 }
 
+// 下载毕业生导入模板
+export const importGraduateStudentTemplate = () => {
+  return request.download({ url: '/system/user/get-import-GraduateStudentTemplate' })
+}
+
 // 用户密码重置
 export const resetUserPwd = (id: number, password: string) => {
   const data = {

+ 6 - 0
src/api/system/user/profile.ts

@@ -29,6 +29,12 @@ export interface ProfileVO {
   loginIp: string
   loginDate: Date
   createTime: Date
+  userNumber: string
+  grade: string
+  major: string
+  supervisor: string
+  masterType: string
+  userType: string
 }
 
 export interface UserProfileUpdateReqVO {

+ 59 - 0
src/api/system/userAchievement/index.ts

@@ -0,0 +1,59 @@
+import request from '@/config/axios'
+
+// 成果 VO
+export interface UserAchievementVO {
+  id: number // 自增id
+  userId: number // 用户id
+  achievementName: string // 成果名称
+  achievementType: string // 1论文,2专利,3著作
+  userName: string // 用户名称
+  detail: string // 照片详情url
+}
+
+// 成果 API
+export const UserAchievementApi = {
+  // 查询工作间下成员的成果分页
+  getUserAchievementPage: async (params: any) => {
+    return await request.get({ url: `/system/user-achievement/page`, params })
+  },
+
+    // 查询自己的成果分页
+    getSelfAchievementPage: async (params: any) => {
+      return await request.get({ url: `/system/user-achievement/selfPage`, params })
+    },
+  
+
+  // 查询成果详情
+  getUserAchievement: async (id: number) => {
+    return await request.get({ url: `/system/user-achievement/get?id=` + id })
+  },
+
+  // 新增成果
+  createUserAchievement: async (data: UserAchievementVO) => {
+    return await request.post({ url: `/system/user-achievement/create`, data })
+  },
+
+   // 新增自己的成果
+   createSelfAchievement: async (data: UserAchievementVO) => {
+    return await request.post({ url: `/system/user-achievement/selfCreate`, data })
+  },
+
+  // 修改成果
+  updateUserAchievement: async (data: UserAchievementVO) => {
+    return await request.put({ url: `/system/user-achievement/update`, data })
+  },
+  // 修改自己的成果
+  updateSelfAchievement: async (data: UserAchievementVO) => {
+    return await request.put({ url: `/system/user-achievement/selfUpdate`, data })
+  },
+
+  // 删除成果
+  deleteUserAchievement: async (id: number) => {
+    return await request.delete({ url: `/system/user-achievement/delete?id=` + id })
+  },
+
+  // 导出成果 Excel
+  exportUserAchievement: async (params) => {
+    return await request.download({ url: `/system/user-achievement/export-excel`, params })
+  },
+}

+ 1 - 1
src/components/FormCreate/src/useFormCreateDesigner.ts

@@ -55,7 +55,7 @@ export const useFormCreateDesigner = async (designer: Ref) => {
   })
   const deptSelectRule = useSelectRule({
     name: 'DeptSelect',
-    label: '部门选择器',
+    label: '工作间选择器',
     icon: 'icon-address-card-o'
   })
   const dictSelectRule = useDictSelectRule()

+ 2 - 2
src/components/bpmnProcessDesigner/package/designer/ProcessViewer.vue

@@ -321,7 +321,7 @@ const elementHover = (element) => {
           </div>` // 默认值
     if (element.value.type === 'bpmn:StartEvent' && processInstance.value) {
       html = `<p>发起人:${processInstance.value.startUser.nickname}</p>
-                  <p>部门:${processInstance.value.startUser.deptName}</p>
+                  <p>工作间:${processInstance.value.startUser.deptName}</p>
                   <p>创建时间:${formatDate(processInstance.value.createTime)}`
     } else if (element.value.type === 'bpmn:UserTask') {
       let task = taskList.value.find((m) => m.id === activity.taskId) // 找到活动对应的 taskId
@@ -336,7 +336,7 @@ const elementHover = (element) => {
         }
       })
       html = `<p>审批人:${task.assigneeUser.nickname}</p>
-                  <p>部门:${task.assigneeUser.deptName}</p>
+                  <p>工作间:${task.assigneeUser.deptName}</p>
                   <p>结果:${dataResult}</p>
                   <p>创建时间:${formatDate(task.createTime)}</p>`
       // html = `<p>审批人:${task.assigneeUser.nickname}</p>

+ 1 - 1
src/components/bpmnProcessDesigner/package/penal/task/task-components/UserTask.vue

@@ -32,7 +32,7 @@
     </el-form-item>
     <el-form-item
       v-if="userTaskForm.candidateStrategy == 20 || userTaskForm.candidateStrategy == 21"
-      label="指定部门"
+      label="指定工作间"
       prop="candidateParam"
       span="24"
     >

+ 1 - 0
src/layout/components/UserInfo/src/UserInfo.vue

@@ -46,6 +46,7 @@ const loginOut = async () => {
     replace('/login?redirect=/index')
   } catch {}
 }
+//获取个人信息
 const toProfile = async () => {
   push('/user/profile')
 }

+ 7 - 2
src/locales/zh-CN.ts

@@ -403,13 +403,18 @@ export default {
       nickname: '用户昵称',
       mobile: '手机号码',
       email: '用户邮箱',
-      dept: '所属部门',
+      dept: '所属工作间',
       posts: '所属岗位',
       roles: '所属角色',
       sex: '性别',
       man: '男',
       woman: '女',
-      createTime: '创建日期'
+      createTime: '创建日期',
+      userNumber:'学号',
+      grade: '年级',
+      major: '专业',
+      supervisor: '导师',
+      masterType: '学硕名称',
     },
     info: {
       title: '基本信息',

+ 32 - 0
src/views/Profile/components/ProfileUser.vue

@@ -43,6 +43,37 @@
         {{ t('profile.user.createTime') }}
         <div class="pull-right">{{ formatDate(userInfo.createTime) }}</div>
       </li>
+      <!-- 新加的属性 1本科生,2是毕业生    图标还没改-->
+    <li class="list-group-item" v-if="userInfo?.userType === '1' || userInfo?.userType === '2'">
+      <Icon class="mr-5px" icon="ep:calendar" />
+      {{ t('profile.user.userNumber') }}
+      <div class="pull-right">{{ userInfo?.userNumber}}</div>
+    </li>
+
+    <li class="list-group-item" v-if="userInfo?.userType === '1' || userInfo?.userType === '2'">
+      <Icon class="mr-5px" icon="ep:calendar" />
+      {{ t('profile.user.grade') }}
+      <div class="pull-right">{{ userInfo?.grade}}</div>
+    </li>
+
+    <li class="list-group-item" v-if="userInfo?.userType === '1' || userInfo?.userType === '2'">
+      <Icon class="mr-5px" icon="ep:calendar" />
+      {{ t('profile.user.major') }}
+      <div class="pull-right">{{ userInfo?.major}}</div>
+    </li>
+
+    <li class="list-group-item" v-if="userInfo?.userType === '1' || userInfo?.userType === '2'">
+      <Icon class="mr-5px" icon="ep:calendar" />
+      {{ t('profile.user.supervisor') }}
+      <div class="pull-right">{{ userInfo?.supervisor}}</div>
+    </li>
+
+    <li class="list-group-item" v-if="userInfo?.userType === '1' || userInfo?.userType === '2'">
+      <Icon class="mr-5px" icon="ep:calendar" />
+      {{ t('profile.user.masterType') }}
+      <div class="pull-right">{{ userInfo?.masterType}}</div>
+    </li>
+
     </ul>
   </div>
 </template>
@@ -58,6 +89,7 @@ const { t } = useI18n()
 const userInfo = ref({} as ProfileVO)
 const getUserInfo = async () => {
   const users = await getUserProfile()
+  console.log(users)
   userInfo.value = users
 }
 onMounted(async () => {

+ 2 - 2
src/views/bpm/processInstance/detail/ProcessInstanceTimeline.vue

@@ -74,7 +74,7 @@ const mockData: any = [
       id: 104,
       nickname: '测试号',
       deptId: 107,
-      deptName: '运维部门'
+      deptName: '运维工作间'
     },
     taskDefinitionKey: 'task-01',
     processInstanceId: 'fe0c60c6-68c3-11ef-9c7d-00a6181404fd',
@@ -138,7 +138,7 @@ const mockData: any = [
       id: 104,
       nickname: '财务总监',
       deptId: 107,
-      deptName: '运维部门'
+      deptName: '运维工作间'
     },
     taskDefinitionKey: 'task-01',
     processInstanceId: 'fe0c60c6-68c3-11ef-9c7d-00a6181404fd',

+ 1 - 1
src/views/bpm/processInstance/manager/index.vue

@@ -94,7 +94,7 @@
         fixed="left"
       />
       <el-table-column label="流程发起人" align="center" prop="startUser.nickname" width="120" />
-      <el-table-column label="发起部门" align="center" prop="startUser.deptName" width="120" />
+      <el-table-column label="发起工作间" align="center" prop="startUser.deptName" width="120" />
       <el-table-column label="流程状态" prop="status" width="120">
         <template #default="scope">
           <dict-tag :type="DICT_TYPE.BPM_PROCESS_INSTANCE_STATUS" :value="scope.row.status" />

+ 3 - 3
src/views/crm/business/status/BusinessStatusForm.vue

@@ -10,9 +10,9 @@
       <el-form-item label="状态组名" prop="name">
         <el-input v-model="formData.name" placeholder="请输入状态组名" />
       </el-form-item>
-      <el-form-item label="应用部门" prop="deptIds">
+      <el-form-item label="应用工作间" prop="deptIds">
         <template #label>
-          <Tooltip message="不选择部门时,默认全公司生效" title="应用部门" />
+          <Tooltip message="不选择工作间时,默认全公司生效" title="应用工作间" />
         </template>
         <el-tree
           ref="treeRef"
@@ -20,7 +20,7 @@
           :props="defaultProps"
           :check-strictly="!checkStrictly"
           node-key="id"
-          placeholder="请选择归属部门"
+          placeholder="请选择归属工作间"
           show-checkbox
         />
       </el-form-item>

+ 2 - 2
src/views/crm/statistics/customer/index.vue

@@ -37,7 +37,7 @@
           />
         </el-select>
       </el-form-item>
-      <el-form-item label="归属部门" prop="deptId">
+      <el-form-item label="工作间" prop="deptId">
         <el-tree-select
           v-model="queryParams.deptId"
           :data="deptList"
@@ -45,7 +45,7 @@
           check-strictly
           class="!w-240px"
           node-key="id"
-          placeholder="请选择归属部门"
+          placeholder="请选择工作间"
           @change="(queryParams.userId = undefined), handleQuery()"
         />
       </el-form-item>

+ 2 - 2
src/views/crm/statistics/funnel/index.vue

@@ -37,7 +37,7 @@
           />
         </el-select>
       </el-form-item>
-      <el-form-item label="归属部门" prop="deptId">
+      <el-form-item label="工作间" prop="deptId">
         <el-tree-select
           v-model="queryParams.deptId"
           :data="deptList"
@@ -45,7 +45,7 @@
           check-strictly
           class="!w-240px"
           node-key="id"
-          placeholder="请选择归属部门"
+          placeholder="请选择归属工作间"
           @change="(queryParams.userId = undefined), handleQuery()"
         />
       </el-form-item>

+ 2 - 2
src/views/crm/statistics/performance/index.vue

@@ -18,7 +18,7 @@
           :default-time="[new Date().getFullYear()]"
         />
       </el-form-item>
-      <el-form-item label="归属部门" prop="deptId">
+      <el-form-item label="工作间" prop="deptId">
         <el-tree-select
           v-model="queryParams.deptId"
           class="!w-240px"
@@ -26,7 +26,7 @@
           :props="defaultProps"
           check-strictly
           node-key="id"
-          placeholder="请选择归属部门"
+          placeholder="请选择归属工作间"
           @change="queryParams.userId = undefined"
         />
       </el-form-item>

+ 2 - 2
src/views/crm/statistics/portrait/index.vue

@@ -21,7 +21,7 @@
           value-format="YYYY-MM-DD HH:mm:ss"
         />
       </el-form-item>
-      <el-form-item label="归属部门" prop="deptId">
+      <el-form-item label="工作间" prop="deptId">
         <el-tree-select
           v-model="queryParams.deptId"
           :data="deptList"
@@ -29,7 +29,7 @@
           check-strictly
           class="!w-240px"
           node-key="id"
-          placeholder="请选择归属部门"
+          placeholder="请选择归属工作间"
           @change="queryParams.userId = undefined"
         />
       </el-form-item>

+ 2 - 2
src/views/crm/statistics/rank/index.vue

@@ -21,14 +21,14 @@
           :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
         />
       </el-form-item>
-      <el-form-item label="归属部门" prop="deptId">
+      <el-form-item label="工作间" prop="deptId">
         <el-tree-select
           v-model="queryParams.deptId"
           :data="deptList"
           :props="defaultProps"
           check-strictly
           node-key="id"
-          placeholder="请选择归属部门"
+          placeholder="请选择归属工作间"
           class="!w-240px"
         />
       </el-form-item>

+ 3 - 3
src/views/system/dept/DeptForm.vue

@@ -18,8 +18,8 @@
           value-key="deptId"
         />
       </el-form-item>
-      <el-form-item label="部门名称" prop="name">
-        <el-input v-model="formData.name" placeholder="请输入部门名称" />
+      <el-form-item label="工作间名称" prop="name">
+        <el-input v-model="formData.name" placeholder="请输入工作间名称" />
       </el-form-item>
       <el-form-item label="显示排序" prop="sort">
         <el-input-number v-model="formData.sort" :min="0" controls-position="right" />
@@ -87,7 +87,7 @@ const formData = ref({
 })
 const formRules = reactive<FormRules>({
   parentId: [{ required: true, message: '上级部门不能为空', trigger: 'blur' }],
-  name: [{ required: true, message: '部门名称不能为空', trigger: 'blur' }],
+  name: [{ required: true, message: '工作间名称不能为空', trigger: 'blur' }],
   sort: [{ required: true, message: '显示排序不能为空', trigger: 'blur' }],
   email: [{ type: 'email', message: '请输入正确的邮箱地址', trigger: ['blur', 'change'] }],
   phone: [

+ 5 - 5
src/views/system/dept/index.vue

@@ -8,19 +8,19 @@
       :inline="true"
       label-width="68px"
     >
-      <el-form-item label="部门名称" prop="name">
+      <el-form-item label="工作间" prop="name">
         <el-input
           v-model="queryParams.name"
-          placeholder="请输入部门名称"
+          placeholder="请输入工作间名称"
           clearable
           @keyup.enter="handleQuery"
           class="!w-240px"
         />
       </el-form-item>
-      <el-form-item label="部门状态" prop="status">
+      <el-form-item label="状态" prop="status">
         <el-select
           v-model="queryParams.status"
-          placeholder="请选择部门状态"
+          placeholder="请选择工作间状态"
           clearable
           class="!w-240px"
         >
@@ -59,7 +59,7 @@
       :default-expand-all="isExpandAll"
       v-if="refreshTable"
     >
-      <el-table-column prop="name" label="部门名称" />
+      <el-table-column prop="name" label="工作间名称" />
       <el-table-column prop="leader" label="负责人">
         <template #default="scope">
           {{ userList.find((user) => user.id === scope.row.leaderUserId)?.nickname }}

+ 96 - 0
src/views/system/graduateStudent/UserAssignRoleForm.vue

@@ -0,0 +1,96 @@
+<template>
+  <Dialog v-model="dialogVisible" title="分配角色">
+    <el-form ref="formRef" v-loading="formLoading" :model="formData" label-width="80px">
+      <el-form-item label="用户名称">
+        <el-input v-model="formData.username" :disabled="true" />
+      </el-form-item>
+      <el-form-item label="用户昵称">
+        <el-input v-model="formData.nickname" :disabled="true" />
+      </el-form-item>
+      <el-form-item label="角色">
+        <el-select v-model="formData.roleIds" multiple placeholder="请选择角色">
+          <el-option v-for="item in roleList" :key="item.id" :label="item.name" :value="item.id" />
+        </el-select>
+      </el-form-item>
+    </el-form>
+    <template #footer>
+      <el-button :disabled="formLoading" type="primary" @click="submitForm">确 定</el-button>
+      <el-button @click="dialogVisible = false">取 消</el-button>
+    </template>
+  </Dialog>
+</template>
+<script lang="ts" setup>
+import * as PermissionApi from '@/api/system/permission'
+import * as UserApi from '@/api/system/user'
+import * as RoleApi from '@/api/system/role'
+
+defineOptions({ name: 'SystemUserAssignRoleForm' })
+
+const { t } = useI18n() // 国际化
+const message = useMessage() // 消息弹窗
+
+const dialogVisible = ref(false) // 弹窗的是否展示
+const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
+const formData = ref({
+  id: -1,
+  nickname: '',
+  username: '',
+  roleIds: []
+})
+const formRef = ref() // 表单 Ref
+const roleList = ref([] as RoleApi.RoleVO[]) // 角色的列表
+
+/** 打开弹窗 */
+const open = async (row: UserApi.UserVO) => {
+  dialogVisible.value = true
+  resetForm()
+  // 设置数据
+  formData.value.id = row.id
+  formData.value.username = row.username
+  formData.value.nickname = row.nickname
+  // 获得角色拥有的菜单集合
+  formLoading.value = true
+  try {
+    formData.value.roleIds = await PermissionApi.getUserRoleList(row.id)
+  } finally {
+    formLoading.value = false
+  }
+  // 获得角色列表
+  roleList.value = await RoleApi.getSimpleRoleList()
+}
+defineExpose({ open }) // 提供 open 方法,用于打开弹窗
+
+/** 提交表单 */
+const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
+const submitForm = async () => {
+  // 校验表单
+  if (!formRef) return
+  const valid = await formRef.value.validate()
+  if (!valid) return
+  // 提交请求
+  formLoading.value = true
+  try {
+    await PermissionApi.assignUserRole({
+      userId: formData.value.id,
+      roleIds: formData.value.roleIds
+    })
+    message.success(t('common.updateSuccess'))
+    dialogVisible.value = false
+    // 发送操作成功的事件
+    emit('success', true)
+  } finally {
+    formLoading.value = false
+  }
+}
+
+/** 重置表单 */
+const resetForm = () => {
+  formData.value = {
+    id: -1,
+    nickname: '',
+    username: '',
+    roleIds: []
+  }
+  formRef.value?.resetFields()
+}
+</script>

+ 305 - 0
src/views/system/graduateStudent/UserForm.vue

@@ -0,0 +1,305 @@
+<template>
+  <Dialog v-model="dialogVisible" :title="dialogTitle">
+    <el-form
+      ref="formRef"
+      v-loading="formLoading"
+      :model="formData"
+      :rules="formRules"
+      label-width="80px"
+    >
+      <el-row>
+        <el-col :span="12">
+          <el-form-item label="用户昵称" prop="nickname">
+            <el-input v-model="formData.nickname" placeholder="请输入用户昵称" />
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item label="工作间" prop="deptId">
+            <el-tree-select
+              v-model="formData.deptId"
+              :data="deptList"
+              :props="defaultProps"
+              check-strictly
+              node-key="id"
+              placeholder="请选择归属工作间"
+            />
+          </el-form-item>
+        </el-col>
+      </el-row>
+      <el-row>
+        <el-col :span="12">
+          <el-form-item label="手机号码" prop="mobile">
+            <el-input v-model="formData.mobile" maxlength="11" placeholder="请输入手机号码" />
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item label="邮箱" prop="email">
+            <el-input v-model="formData.email" maxlength="50" placeholder="请输入邮箱" />
+          </el-form-item>
+        </el-col>
+      </el-row>
+      <el-row>
+        <el-col :span="12">
+          <el-form-item v-if="formData.id === undefined" label="用户名称" prop="username">
+            <el-input v-model="formData.username" placeholder="请输入用户名称" />
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item v-if="formData.id === undefined" label="用户密码" prop="password">
+            <el-input
+              v-model="formData.password"
+              placeholder="请输入用户密码"
+              show-password
+              type="password"
+            />
+          </el-form-item>
+        </el-col>
+      </el-row>
+      <el-row>
+        <el-col :span="12">
+          <el-form-item label="用户性别">
+            <el-select v-model="formData.sex" placeholder="请选择">
+              <el-option
+                v-for="dict in getIntDictOptions(DICT_TYPE.SYSTEM_USER_SEX)"
+                :key="dict.value"
+                :label="dict.label"
+                :value="dict.value"
+              />
+            </el-select>
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item label="岗位">
+            <el-select v-model="formData.postIds" multiple placeholder="请选择">
+              <el-option
+                v-for="item in postList"
+                :key="item.id"
+                :label="item.name"
+                :value="item.id!"
+              />
+            </el-select>
+          </el-form-item>
+        </el-col>
+      </el-row>
+
+      <el-row>
+        <el-col :span="12">
+          <el-form-item label="用户类型" prop="userType">
+            <el-select v-model="formData.userType" placeholder="请选择用户类型">
+              <el-option
+                v-for="option in userTypes"
+                :key="option.value"
+                :label="option.label"
+                :value="option.value"
+                />
+            </el-select>
+          </el-form-item>
+        </el-col>
+
+        <el-col :span="12">
+              <el-form-item label="导师名称" prop="supervisorId">
+                  <el-select
+                    v-model="formData.supervisor"
+                    @change="handleSupervisorChange"
+                    placeholder="请选择导师名称"
+                    clearable
+                    filterable
+                    class="!w-240px"
+                  >
+                    <el-option
+                      v-for="user in users"
+                      :key="user.id"
+                      :label="user.nickname"
+                      :value="user.id"
+                    />
+                  </el-select>
+          </el-form-item>
+        </el-col>
+        
+      </el-row>
+
+      <el-row>
+        
+        <el-col :span="12">
+          <el-form-item label="工作地点" prop="workPlace">
+            <el-input v-model="formData.workPlace" maxlength="11" placeholder="请输入工作地点" />
+          </el-form-item>
+        </el-col>
+        
+      </el-row>
+      
+      <el-row>
+        <el-col :span="24">
+          <el-form-item label="备注">
+            <el-input v-model="formData.remark" placeholder="请输入内容" type="textarea" />
+          </el-form-item>
+        </el-col>
+      </el-row>
+    </el-form>
+    <template #footer>
+      <el-button :disabled="formLoading" type="primary" @click="submitForm">确 定</el-button>
+      <el-button @click="dialogVisible = false">取 消</el-button>
+    </template>
+  </Dialog>
+</template>
+<script lang="ts" setup>
+import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
+import { CommonStatusEnum } from '@/utils/constants'
+import { defaultProps, handleTree } from '@/utils/tree'
+import * as PostApi from '@/api/system/post'
+import * as DeptApi from '@/api/system/dept'
+import * as UserApi from '@/api/system/user'
+import { FormRules } from 'element-plus'
+
+defineOptions({ name: 'SystemUserForm' })
+
+const { t } = useI18n() // 国际化
+const message = useMessage() // 消息弹窗
+
+const dialogVisible = ref(false) // 弹窗的是否展示
+const dialogTitle = ref('') // 弹窗的标题
+const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
+const formType = ref('') // 表单的类型:create - 新增;update - 修改
+const formData = ref({
+  nickname: '',
+  deptId: '',
+  mobile: '',
+  email: '',
+  id: undefined,
+  username: '',
+  password: '',
+  sex: undefined,
+  postIds: [],
+  remark: '',
+  status: CommonStatusEnum.ENABLE,
+  roleIds: [],
+  userType: '',
+  supervisor:'',
+  supervisorId:'',
+  workPlace:""
+})
+const formRules = reactive<FormRules>({
+  username: [{ required: true, message: '用户名称不能为空', trigger: 'blur' }],
+  nickname: [{ required: true, message: '用户昵称不能为空', trigger: 'blur' }],
+  password: [{ required: true, message: '用户密码不能为空', trigger: 'blur' }],
+  email: [
+    {
+      type: 'email',
+      message: '请输入正确的邮箱地址',
+      trigger: ['blur', 'change']
+    }
+  ],
+  mobile: [
+    {
+      pattern: /^(?:(?:\+|00)86)?1(?:3[\d]|4[5-79]|5[0-35-9]|6[5-7]|7[0-8]|8[\d]|9[189])\d{8}$/,
+      message: '请输入正确的手机号码',
+      trigger: 'blur'
+    }
+  ]
+})
+const formRef = ref() // 表单 Ref
+const deptList = ref<Tree[]>([]) // 树形结构
+const postList = ref([] as PostApi.PostVO[]) // 岗位列表
+
+
+//用户类型
+const userTypes = ref([
+  { label: '本校生', value: "1" },
+  { label: '毕业生', value: "2" },
+  { label: '导师', value: "3" },
+]);
+
+//获取所有导师
+const users = ref()
+const getSupervisor= async () => {
+  try {
+    const response = await UserApi.getSupervisor()
+    users.value = response
+  } catch (error) {
+    console.error('Error fetching user data:', error)
+  }
+}
+//传supervisorId给formData.supervisorId
+const handleSupervisorChange = (value: number) => {
+  const selectedUser = users.value.find(user => user.id === value);
+  if (selectedUser) {
+    formData.value.supervisorId = selectedUser.id;
+    formData.value.supervisor = selectedUser.nickname;
+  }
+}
+
+
+/** 打开弹窗 */
+const open = async (type: string, id?: number) => {
+  dialogVisible.value = true
+  dialogTitle.value = t('action.' + type)
+  formType.value = type
+  resetForm()
+  // 修改时,设置数据
+  if (id) {
+    formLoading.value = true
+    try {
+      formData.value = await UserApi.getUser(id)
+    } finally {
+      formLoading.value = false
+    }
+  }
+  // 加载部门树
+  deptList.value = handleTree(await DeptApi.getSimpleDeptList())
+  // 加载岗位列表
+  postList.value = await PostApi.getSimplePostList()
+}
+defineExpose({ open }) // 提供 open 方法,用于打开弹窗
+
+/** 提交表单 */
+const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
+const submitForm = async () => {
+  // 校验表单
+  if (!formRef) return
+  const valid = await formRef.value.validate()
+  if (!valid) return
+  // 提交请求
+  formLoading.value = true
+  try {
+    const data = formData.value as unknown as UserApi.UserVO
+    if (formType.value === 'create') {
+      await UserApi.createUser(data)
+      message.success(t('common.createSuccess'))
+    } else {
+      await UserApi.updateUser(data)
+      message.success(t('common.updateSuccess'))
+    }
+    dialogVisible.value = false
+    // 发送操作成功的事件
+    emit('success')
+  } finally {
+    formLoading.value = false
+  }
+}
+
+/** 重置表单 */
+const resetForm = () => {
+  formData.value = {
+    nickname: '',
+    deptId: '',
+    mobile: '',
+    email: '',
+    id: undefined,
+    username: '',
+    password: '',
+    sex: undefined,
+    postIds: [],
+    remark: '',
+    status: CommonStatusEnum.ENABLE,
+    roleIds: [],
+    userType: '',
+    supervisor:'',
+    supervisorId:'',
+    workPlace:""
+  }
+  formRef.value?.resetFields()
+}
+onMounted(() => {
+  getSupervisor()
+})
+</script>

+ 138 - 0
src/views/system/graduateStudent/UserImportForm.vue

@@ -0,0 +1,138 @@
+<template>
+  <Dialog v-model="dialogVisible" title="用户导入" width="400">
+    <el-upload
+      ref="uploadRef"
+      v-model:file-list="fileList"
+      :action="importUrl + '?updateSupport=' + updateSupport"
+      :auto-upload="false"
+      :disabled="formLoading"
+      :headers="uploadHeaders"
+      :limit="1"
+      :on-error="submitFormError"
+      :on-exceed="handleExceed"
+      :on-success="submitFormSuccess"
+      accept=".xlsx, .xls"
+      drag
+    >
+      <Icon icon="ep:upload" />
+      <div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
+      <template #tip>
+        <div class="el-upload__tip text-center">
+          <div class="el-upload__tip">
+            <el-checkbox v-model="updateSupport" />
+            是否更新已经存在的用户数据
+          </div>
+          <span>仅允许导入 xls、xlsx 格式文件。</span>
+          <el-link
+            :underline="false"
+            style="font-size: 12px; vertical-align: baseline"
+            type="primary"
+            @click="importTemplate"
+          >
+            下载模板
+          </el-link>
+        </div>
+      </template>
+    </el-upload>
+    <template #footer>
+      <el-button :disabled="formLoading" type="primary" @click="submitForm">确 定</el-button>
+      <el-button @click="dialogVisible = false">取 消</el-button>
+    </template>
+  </Dialog>
+</template>
+<script lang="ts" setup>
+import * as UserApi from '@/api/system/user'
+import { getAccessToken, getTenantId } from '@/utils/auth'
+import download from '@/utils/download'
+
+defineOptions({ name: 'SystemUserImportForm' })
+
+const message = useMessage() // 消息弹窗
+
+const dialogVisible = ref(false) // 弹窗的是否展示
+const formLoading = ref(false) // 表单的加载中
+const uploadRef = ref()
+const importUrl =
+  import.meta.env.VITE_BASE_URL + import.meta.env.VITE_API_URL + '/system/user/import'
+const uploadHeaders = ref() // 上传 Header 头
+const fileList = ref([]) // 文件列表
+const updateSupport = ref(0) // 是否更新已经存在的用户数据
+
+/** 打开弹窗 */
+const open = () => {
+  dialogVisible.value = true
+  updateSupport.value = 0
+  fileList.value = []
+  resetForm()
+}
+defineExpose({ open }) // 提供 open 方法,用于打开弹窗
+
+/** 提交表单 */
+const submitForm = async () => {
+  if (fileList.value.length == 0) {
+    message.error('请上传文件')
+    return
+  }
+  // 提交请求
+  uploadHeaders.value = {
+    Authorization: 'Bearer ' + getAccessToken(),
+    'tenant-id': getTenantId()
+  }
+  formLoading.value = true
+  uploadRef.value!.submit()
+}
+
+/** 文件上传成功 */
+const emits = defineEmits(['success'])
+const submitFormSuccess = (response: any) => {
+  if (response.code !== 0) {
+    message.error(response.msg)
+    formLoading.value = false
+    return
+  }
+  // 拼接提示语
+  const data = response.data
+  let text = '上传成功数量:' + data.createUsernames.length + ';'
+  for (let username of data.createUsernames) {
+    text += '< ' + username + ' >'
+  }
+  text += '更新成功数量:' + data.updateUsernames.length + ';'
+  for (const username of data.updateUsernames) {
+    text += '< ' + username + ' >'
+  }
+  text += '更新失败数量:' + Object.keys(data.failureUsernames).length + ';'
+  for (const username in data.failureUsernames) {
+    text += '< ' + username + ': ' + data.failureUsernames[username] + ' >'
+  }
+  message.alert(text)
+  formLoading.value = false
+  dialogVisible.value = false
+  // 发送操作成功的事件
+  emits('success')
+}
+
+/** 上传错误提示 */
+const submitFormError = (): void => {
+  message.error('上传失败,请您重新上传!')
+  formLoading.value = false
+}
+
+/** 重置表单 */
+const resetForm = async (): Promise<void> => {
+  // 重置上传状态和文件
+  formLoading.value = false
+  await nextTick()
+  uploadRef.value?.clearFiles()
+}
+
+/** 文件数超出提示 */
+const handleExceed = (): void => {
+  message.error('最多只能上传一个文件!')
+}
+
+/** 下载模板操作 */
+const importTemplate = async () => {
+  const res = await UserApi.importGraduateStudentTemplate()
+  download.excel(res, '用户导入模版.xls')
+}
+</script>

+ 332 - 0
src/views/system/graduateStudent/index.vue

@@ -0,0 +1,332 @@
+<template>
+
+  <el-row :gutter="20">
+   
+    <el-col :span="24" :xs="24">
+      <!-- 搜索 -->
+      <ContentWrap>
+        <el-form
+          class="-mb-15px"
+          :model="queryParams"
+          ref="queryFormRef"
+          :inline="true"
+          label-width="68px"
+        >
+          <el-form-item label="用户昵称" prop="nickname">
+            <el-input
+              v-model="queryParams.nickname"
+              placeholder="请输入用户昵称"
+              clearable
+              @keyup.enter="handleQuery"
+              class="!w-240px"
+            />
+          </el-form-item>
+          
+          <el-form-item label="年级" prop="grade">
+              <el-select
+                v-model="queryParams.grade"
+                placeholder="请选择年级"
+                clearable
+                class="!w-240px"
+              >
+                <el-option
+                  v-for="year in grades"
+                  :key="year"
+                  :label="year.toString()"
+                  :value="year"
+                />
+              </el-select>
+        </el-form-item>
+
+          <el-form-item label="手机号码" prop="mobile">
+            <el-input
+              v-model="queryParams.mobile"
+              placeholder="请输入手机号码"
+              clearable
+              @keyup.enter="handleQuery"
+              class="!w-240px"
+            />
+          </el-form-item>
+          <el-form-item label="状态" prop="status">
+            <el-select
+              v-model="queryParams.status"
+              placeholder="用户状态"
+              clearable
+              class="!w-240px"
+            >
+              <el-option
+                v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
+                :key="dict.value"
+                :label="dict.label"
+                :value="dict.value"
+              />
+            </el-select>
+          </el-form-item>
+          <el-form-item label="创建时间" prop="createTime">
+            <el-date-picker
+              v-model="queryParams.createTime"
+              value-format="YYYY-MM-DD HH:mm:ss"
+              type="datetimerange"
+              start-placeholder="开始日期"
+              end-placeholder="结束日期"
+              class="!w-240px"
+            />
+          </el-form-item>
+          <el-form-item>
+            <el-button @click="handleQuery"><Icon icon="ep:search" />搜索</el-button>
+            <el-button @click="resetQuery"><Icon icon="ep:refresh" />重置</el-button>
+        
+            <el-button
+              type="warning"
+              plain
+              @click="handleImport"
+              v-hasPermi="['system:user:import']"
+            >
+              <Icon icon="ep:upload" /> 导入
+            </el-button>
+            <el-button
+              type="success"
+              plain
+              @click="handleExport"
+              :loading="exportLoading"
+              v-hasPermi="['system:user:export']"
+            >
+              <Icon icon="ep:download" />导出
+            </el-button>
+          </el-form-item>
+        </el-form>
+      </ContentWrap>
+      <ContentWrap>
+        <el-table v-loading="loading" :data="list">
+          <el-table-column label="用户编号" align="center" key="id" prop="id" />
+          <!-- <el-table-column
+            label="用户名称"
+            align="center"
+            prop="username"
+            :show-overflow-tooltip="true"
+          /> -->
+          <el-table-column
+            label="用户昵称"
+            align="center"
+            prop="nickname"
+            :show-overflow-tooltip="true"
+          />
+          <el-table-column label="年级" align="center" prop="grade" width="120" />
+
+          <el-table-column
+            label="工作地点"
+            align="center"
+            key="workPlace"
+            prop="workPlace"
+            :show-overflow-tooltip="true"
+          />
+          <el-table-column label="手机号码" align="center" prop="mobile" width="120" />
+          <el-table-column label="导师" align="center" prop="supervisor" width="120" />
+          <el-table-column label="导师电话" align="center" prop="supervisorMobile" width="120" />
+          <el-table-column label="状态" key="status">
+            <template #default="scope">
+              <el-switch
+                v-model="scope.row.status"
+                :active-value="0"
+                :inactive-value="1"
+                @change="handleStatusChange(scope.row)"
+              />
+            </template>
+          </el-table-column>
+          <el-table-column
+            label="创建时间"
+            align="center"
+            prop="createTime"
+            :formatter="dateFormatter"
+            width="180"
+          />
+          <el-table-column label="操作" align="center" width="160">
+            <template #default="scope">
+              <div class="flex items-center justify-center">
+                <el-button
+                  type="primary"
+                  link
+                  @click="openForm('update', scope.row.id)"
+                  v-hasPermi="['system:user:update']"
+                >
+                  <Icon icon="ep:edit" />修改
+                </el-button>
+
+                <el-dropdown
+                  @command="(command) => handleCommand(command, scope.row)"
+                  v-hasPermi="[
+                    'system:user:delete',
+                  ]"
+                >
+                  <el-button type="primary" link><Icon icon="ep:d-arrow-right" /> 更多</el-button>
+                  <template #dropdown>
+                    <el-dropdown-menu>
+                      <el-dropdown-item
+                        command="handleDelete"
+                        v-if="checkPermi(['system:user:delete'])"
+                      >
+                        <Icon icon="ep:delete" />删除
+                      </el-dropdown-item>                      
+                    </el-dropdown-menu>
+                  </template>
+                </el-dropdown>
+
+              </div>
+            </template>
+          </el-table-column>
+        </el-table>
+        <Pagination
+          :total="total"
+          v-model:page="queryParams.pageNo"
+          v-model:limit="queryParams.pageSize"
+          @pagination="getList"
+        />
+      </ContentWrap>
+    </el-col>
+  </el-row>
+
+  <!-- 添加或修改用户对话框 -->
+  <UserForm ref="formRef" @success="getList" />
+  <!-- 用户导入对话框 -->
+  <UserImportForm ref="importFormRef" @success="getList" />
+ 
+</template>
+<script lang="ts" setup>
+import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
+import { checkPermi } from '@/utils/permission'
+import { dateFormatter } from '@/utils/formatTime'
+import download from '@/utils/download'
+import { CommonStatusEnum } from '@/utils/constants'
+import * as UserApi from '@/api/system/user'
+import UserForm from './UserForm.vue'
+import UserImportForm from './UserImportForm.vue'
+
+
+defineOptions({ name: 'SystemUser' })
+
+const message = useMessage() // 消息弹窗
+const { t } = useI18n() // 国际化
+
+const loading = ref(true) // 列表的加载中
+const total = ref(0) // 列表的总页数
+const list = ref([]) // 列表的数
+const queryParams = reactive({
+  pageNo: 1,
+  pageSize: 10,
+  username: undefined,
+  nickname: undefined,
+  mobile: undefined,
+  status: undefined,
+  deptId: undefined,
+  createTime: [],
+  supervisor: undefined,
+  supervisorMobile: undefined,
+  workPlace: undefined,
+  grade: undefined,
+})
+const queryFormRef = ref() // 搜索的表单
+
+// 生成年级选项数据
+const grades = ref(Array.from({ length: 2035 - 2010 + 1 }, (_, i) => 2010 + i));
+
+
+/** 查询列表 */
+const getList = async () => {
+  loading.value = true
+  try {
+    const data = await UserApi.getGraduateStudentPage(queryParams)
+    list.value = data.list
+    total.value = data.total
+  } finally {
+    loading.value = false
+  }
+}
+
+/** 搜索按钮操作 */
+const handleQuery = () => {
+  queryParams.pageNo = 1
+  getList()
+}
+
+/** 重置按钮操作 */
+const resetQuery = () => {
+  queryFormRef.value?.resetFields()
+  handleQuery()
+}
+
+
+/**修改操作 */
+const formRef = ref()
+const openForm = (type: string, id?: number) => {
+  formRef.value.open(type, id)
+}
+
+/** 用户导入 */
+const importFormRef = ref()
+const handleImport = () => {
+  importFormRef.value.open()
+}
+
+/** 修改用户状态 */
+const handleStatusChange = async (row: UserApi.UserVO) => {
+  try {
+    // 修改状态的二次确认
+    const text = row.status === CommonStatusEnum.ENABLE ? '启用' : '停用'
+    await message.confirm('确认要"' + text + '""' + row.username + '"用户吗?')
+    // 发起修改状态
+    await UserApi.updateUserStatus(row.id, row.status)
+    // 刷新列表
+    await getList()
+  } catch {
+    // 取消后,进行恢复按钮
+    row.status =
+      row.status === CommonStatusEnum.ENABLE ? CommonStatusEnum.DISABLE : CommonStatusEnum.ENABLE
+  }
+}
+
+/** 导出按钮操作 */
+const exportLoading = ref(false)
+const handleExport = async () => {
+  try {
+    // 导出的二次确认
+    await message.exportConfirm()
+    // 发起导出
+    exportLoading.value = true
+    const data = await UserApi.exportUser(queryParams)
+    download.excel(data, '用户数据.xls')
+  } catch {
+  } finally {
+    exportLoading.value = false
+  }
+}
+
+/** 操作分发 */
+const handleCommand = (command: string, row: UserApi.UserVO) => {
+  switch (command) {
+    case 'handleDelete':
+      handleDelete(row.id)
+      break
+    default:
+      break
+  }
+}
+
+/** 删除按钮操作 */
+const handleDelete = async (id: number) => {
+  try {
+    // 删除的二次确认
+    await message.delConfirm()
+    // 发起删除
+    await UserApi.deleteUser(id)
+    message.success(t('common.delSuccess'))
+    // 刷新列表
+    await getList()
+  } catch {}
+}
+
+
+/** 初始化 */
+onMounted(() => {
+  getList()
+})
+</script>

+ 244 - 0
src/views/system/graduateStudentT/UserForm.vue

@@ -0,0 +1,244 @@
+<template>
+  <Dialog v-model="dialogVisible" :title="dialogTitle">
+    <el-form
+      ref="formRef"
+      v-loading="formLoading"
+      :model="formData"
+      :rules="formRules"
+      label-width="80px"
+    >
+      <el-row>
+        <el-col :span="12">
+          <el-form-item label="用户昵称" prop="nickname">
+            <el-input v-model="formData.nickname" placeholder="请输入用户昵称" />
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item label="工作间" prop="deptId">
+            <el-tree-select
+              v-model="formData.deptId"
+              :data="deptList"
+              :props="defaultProps"
+              check-strictly
+              node-key="id"
+              placeholder="请选择归属工作间"
+            />
+          </el-form-item>
+        </el-col>
+      </el-row>
+      <el-row>
+        <el-col :span="12">
+          <el-form-item label="手机号码" prop="mobile">
+            <el-input v-model="formData.mobile" maxlength="11" placeholder="请输入手机号码" />
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item label="邮箱" prop="email">
+            <el-input v-model="formData.email" maxlength="50" placeholder="请输入邮箱" />
+          </el-form-item>
+        </el-col>
+      </el-row>
+      <el-row>
+        <el-col :span="12">
+          <el-form-item v-if="formData.id === undefined" label="用户名称" prop="username">
+            <el-input v-model="formData.username" placeholder="请输入用户名称" />
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item v-if="formData.id === undefined" label="用户密码" prop="password">
+            <el-input
+              v-model="formData.password"
+              placeholder="请输入用户密码"
+              show-password
+              type="password"
+            />
+          </el-form-item>
+        </el-col>
+      </el-row>
+      <el-row>
+        <el-col :span="12">
+          <el-form-item label="用户性别">
+            <el-select v-model="formData.sex" placeholder="请选择">
+              <el-option
+                v-for="dict in getIntDictOptions(DICT_TYPE.SYSTEM_USER_SEX)"
+                :key="dict.value"
+                :label="dict.label"
+                :value="dict.value"
+              />
+            </el-select>
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item label="岗位">
+            <el-select v-model="formData.postIds" multiple placeholder="请选择">
+              <el-option
+                v-for="item in postList"
+                :key="item.id"
+                :label="item.name"
+                :value="item.id!"
+              />
+            </el-select>
+          </el-form-item>
+        </el-col>
+      </el-row>
+
+      <el-row>
+        <el-col :span="12">
+          <el-form-item label="用户类型" prop="userType">
+            <el-select v-model="formData.userType" placeholder="请选择用户类型">
+              <el-option
+                v-for="option in userTypes"
+                :key="option.value"
+                :label="option.label"
+                :value="option.value"
+                />
+            </el-select>
+          </el-form-item>
+        </el-col>
+      </el-row>
+      
+      <el-row>
+        <el-col :span="24">
+          <el-form-item label="备注">
+            <el-input v-model="formData.remark" placeholder="请输入内容" type="textarea" />
+          </el-form-item>
+        </el-col>
+      </el-row>
+    </el-form>
+    <template #footer>
+      <el-button :disabled="formLoading" type="primary" @click="submitForm">确 定</el-button>
+      <el-button @click="dialogVisible = false">取 消</el-button>
+    </template>
+  </Dialog>
+</template>
+<script lang="ts" setup>
+import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
+import { CommonStatusEnum } from '@/utils/constants'
+import { defaultProps, handleTree } from '@/utils/tree'
+import * as PostApi from '@/api/system/post'
+import * as DeptApi from '@/api/system/dept'
+import * as UserApi from '@/api/system/user'
+import { FormRules } from 'element-plus'
+
+defineOptions({ name: 'SystemUserForm' })
+
+const { t } = useI18n() // 国际化
+const message = useMessage() // 消息弹窗
+
+const dialogVisible = ref(false) // 弹窗的是否展示
+const dialogTitle = ref('') // 弹窗的标题
+const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
+const formType = ref('') // 表单的类型:create - 新增;update - 修改
+const formData = ref({
+  nickname: '',
+  deptId: '',
+  mobile: '',
+  email: '',
+  id: undefined,
+  username: '',
+  password: '',
+  sex: undefined,
+  postIds: [],
+  remark: '',
+  status: CommonStatusEnum.ENABLE,
+  roleIds: [],
+  userType: ''
+})
+const formRules = reactive<FormRules>({
+  username: [{ required: true, message: '用户名称不能为空', trigger: 'blur' }],
+  nickname: [{ required: true, message: '用户昵称不能为空', trigger: 'blur' }],
+  password: [{ required: true, message: '用户密码不能为空', trigger: 'blur' }],
+  email: [
+    {
+      type: 'email',
+      message: '请输入正确的邮箱地址',
+      trigger: ['blur', 'change']
+    }
+  ],
+  mobile: [
+    {
+      pattern: /^(?:(?:\+|00)86)?1(?:3[\d]|4[5-79]|5[0-35-9]|6[5-7]|7[0-8]|8[\d]|9[189])\d{8}$/,
+      message: '请输入正确的手机号码',
+      trigger: 'blur'
+    }
+  ]
+})
+const formRef = ref() // 表单 Ref
+const deptList = ref<Tree[]>([]) // 树形结构
+const postList = ref([] as PostApi.PostVO[]) // 岗位列表
+
+
+//用户类型
+const userTypes = ref([
+  { label: '本校生', value: "1" },
+  { label: '毕业生', value: "2" },
+  { label: '导师', value: "3" },
+]);
+/** 打开弹窗 */
+const open = async (type: string, id?: number) => {
+  dialogVisible.value = true
+  dialogTitle.value = t('action.' + type)
+  formType.value = type
+  resetForm()
+  // 修改时,设置数据
+  if (id) {
+    formLoading.value = true
+    try {
+      formData.value = await UserApi.getUser(id)
+    } finally {
+      formLoading.value = false
+    }
+  }
+  // 加载部门树
+  deptList.value = handleTree(await DeptApi.getSimpleDeptList())
+  // 加载岗位列表
+  postList.value = await PostApi.getSimplePostList()
+}
+defineExpose({ open }) // 提供 open 方法,用于打开弹窗
+
+/** 提交表单 */
+const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
+const submitForm = async () => {
+  // 校验表单
+  if (!formRef) return
+  const valid = await formRef.value.validate()
+  if (!valid) return
+  // 提交请求
+  formLoading.value = true
+  try {
+    const data = formData.value as unknown as UserApi.UserVO
+    if (formType.value === 'create') {
+      await UserApi.createUser(data)
+      message.success(t('common.createSuccess'))
+    } else {
+      await UserApi.updateUser(data)
+      message.success(t('common.updateSuccess'))
+    }
+    dialogVisible.value = false
+    // 发送操作成功的事件
+    emit('success')
+  } finally {
+    formLoading.value = false
+  }
+}
+
+/** 重置表单 */
+const resetForm = () => {
+  formData.value = {
+    nickname: '',
+    deptId: '',
+    mobile: '',
+    email: '',
+    id: undefined,
+    username: '',
+    password: '',
+    sex: undefined,
+    postIds: [],
+    remark: '',
+    status: CommonStatusEnum.ENABLE,
+    roleIds: [],
+    userType: ''
+  }
+  formRef.value?.resetFields()
+}
+</script>

+ 138 - 0
src/views/system/graduateStudentT/UserImportForm.vue

@@ -0,0 +1,138 @@
+<template>
+  <Dialog v-model="dialogVisible" title="用户导入" width="400">
+    <el-upload
+      ref="uploadRef"
+      v-model:file-list="fileList"
+      :action="importUrl + '?updateSupport=' + updateSupport"
+      :auto-upload="false"
+      :disabled="formLoading"
+      :headers="uploadHeaders"
+      :limit="1"
+      :on-error="submitFormError"
+      :on-exceed="handleExceed"
+      :on-success="submitFormSuccess"
+      accept=".xlsx, .xls"
+      drag
+    >
+      <Icon icon="ep:upload" />
+      <div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
+      <template #tip>
+        <div class="el-upload__tip text-center">
+          <div class="el-upload__tip">
+            <el-checkbox v-model="updateSupport" />
+            是否更新已经存在的用户数据
+          </div>
+          <span>仅允许导入 xls、xlsx 格式文件。</span>
+          <el-link
+            :underline="false"
+            style="font-size: 12px; vertical-align: baseline"
+            type="primary"
+            @click="importTemplate"
+          >
+            下载模板
+          </el-link>
+        </div>
+      </template>
+    </el-upload>
+    <template #footer>
+      <el-button :disabled="formLoading" type="primary" @click="submitForm">确 定</el-button>
+      <el-button @click="dialogVisible = false">取 消</el-button>
+    </template>
+  </Dialog>
+</template>
+<script lang="ts" setup>
+import * as UserApi from '@/api/system/user'
+import { getAccessToken, getTenantId } from '@/utils/auth'
+import download from '@/utils/download'
+
+defineOptions({ name: 'SystemUserImportForm' })
+
+const message = useMessage() // 消息弹窗
+
+const dialogVisible = ref(false) // 弹窗的是否展示
+const formLoading = ref(false) // 表单的加载中
+const uploadRef = ref()
+const importUrl =
+  import.meta.env.VITE_BASE_URL + import.meta.env.VITE_API_URL + '/system/user/import'
+const uploadHeaders = ref() // 上传 Header 头
+const fileList = ref([]) // 文件列表
+const updateSupport = ref(0) // 是否更新已经存在的用户数据
+
+/** 打开弹窗 */
+const open = () => {
+  dialogVisible.value = true
+  updateSupport.value = 0
+  fileList.value = []
+  resetForm()
+}
+defineExpose({ open }) // 提供 open 方法,用于打开弹窗
+
+/** 提交表单 */
+const submitForm = async () => {
+  if (fileList.value.length == 0) {
+    message.error('请上传文件')
+    return
+  }
+  // 提交请求
+  uploadHeaders.value = {
+    Authorization: 'Bearer ' + getAccessToken(),
+    'tenant-id': getTenantId()
+  }
+  formLoading.value = true
+  uploadRef.value!.submit()
+}
+
+/** 文件上传成功 */
+const emits = defineEmits(['success'])
+const submitFormSuccess = (response: any) => {
+  if (response.code !== 0) {
+    message.error(response.msg)
+    formLoading.value = false
+    return
+  }
+  // 拼接提示语
+  const data = response.data
+  let text = '上传成功数量:' + data.createUsernames.length + ';'
+  for (let username of data.createUsernames) {
+    text += '< ' + username + ' >'
+  }
+  text += '更新成功数量:' + data.updateUsernames.length + ';'
+  for (const username of data.updateUsernames) {
+    text += '< ' + username + ' >'
+  }
+  text += '更新失败数量:' + Object.keys(data.failureUsernames).length + ';'
+  for (const username in data.failureUsernames) {
+    text += '< ' + username + ': ' + data.failureUsernames[username] + ' >'
+  }
+  message.alert(text)
+  formLoading.value = false
+  dialogVisible.value = false
+  // 发送操作成功的事件
+  emits('success')
+}
+
+/** 上传错误提示 */
+const submitFormError = (): void => {
+  message.error('上传失败,请您重新上传!')
+  formLoading.value = false
+}
+
+/** 重置表单 */
+const resetForm = async (): Promise<void> => {
+  // 重置上传状态和文件
+  formLoading.value = false
+  await nextTick()
+  uploadRef.value?.clearFiles()
+}
+
+/** 文件数超出提示 */
+const handleExceed = (): void => {
+  message.error('最多只能上传一个文件!')
+}
+
+/** 下载模板操作 */
+const importTemplate = async () => {
+  const res = await UserApi.importGraduateStudentTemplate()
+  download.excel(res, '用户导入模版.xls')
+}
+</script>

+ 332 - 0
src/views/system/graduateStudentT/index.vue

@@ -0,0 +1,332 @@
+<template>
+
+  <el-row :gutter="20">
+   
+    <el-col :span="24" :xs="24">
+      <!-- 搜索 -->
+      <ContentWrap>
+        <el-form
+          class="-mb-15px"
+          :model="queryParams"
+          ref="queryFormRef"
+          :inline="true"
+          label-width="68px"
+        >
+          <el-form-item label="用户昵称" prop="nickname">
+            <el-input
+              v-model="queryParams.nickname"
+              placeholder="请输入用户昵称"
+              clearable
+              @keyup.enter="handleQuery"
+              class="!w-240px"
+            />
+          </el-form-item>
+          
+          <el-form-item label="年级" prop="grade">
+              <el-select
+                v-model="queryParams.grade"
+                placeholder="请选择年级"
+                clearable
+                class="!w-240px"
+              >
+                <el-option
+                  v-for="year in grades"
+                  :key="year"
+                  :label="year.toString()"
+                  :value="year"
+                />
+              </el-select>
+        </el-form-item>
+
+          <el-form-item label="手机号码" prop="mobile">
+            <el-input
+              v-model="queryParams.mobile"
+              placeholder="请输入手机号码"
+              clearable
+              @keyup.enter="handleQuery"
+              class="!w-240px"
+            />
+          </el-form-item>
+          <el-form-item label="状态" prop="status">
+            <el-select
+              v-model="queryParams.status"
+              placeholder="用户状态"
+              clearable
+              class="!w-240px"
+            >
+              <el-option
+                v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
+                :key="dict.value"
+                :label="dict.label"
+                :value="dict.value"
+              />
+            </el-select>
+          </el-form-item>
+          <el-form-item label="创建时间" prop="createTime">
+            <el-date-picker
+              v-model="queryParams.createTime"
+              value-format="YYYY-MM-DD HH:mm:ss"
+              type="datetimerange"
+              start-placeholder="开始日期"
+              end-placeholder="结束日期"
+              class="!w-240px"
+            />
+          </el-form-item>
+          <el-form-item>
+            <el-button @click="handleQuery"><Icon icon="ep:search" />搜索</el-button>
+            <el-button @click="resetQuery"><Icon icon="ep:refresh" />重置</el-button>
+        
+            <el-button
+              type="warning"
+              plain
+              @click="handleImport"
+              v-hasPermi="['system:user:import']"
+            >
+              <Icon icon="ep:upload" /> 导入
+            </el-button>
+            <el-button
+              type="success"
+              plain
+              @click="handleExport"
+              :loading="exportLoading"
+              v-hasPermi="['system:user:export']"
+            >
+              <Icon icon="ep:download" />导出
+            </el-button>
+          </el-form-item>
+        </el-form>
+      </ContentWrap>
+      <ContentWrap>
+        <el-table v-loading="loading" :data="list">
+          <el-table-column label="用户编号" align="center" key="id" prop="id" />
+          <!-- <el-table-column
+            label="用户名称"
+            align="center"
+            prop="username"
+            :show-overflow-tooltip="true"
+          /> -->
+          <el-table-column
+            label="用户昵称"
+            align="center"
+            prop="nickname"
+            :show-overflow-tooltip="true"
+          />
+          <el-table-column label="年级" align="center" prop="grade" width="120" />
+
+          <el-table-column
+            label="工作地点"
+            align="center"
+            key="workPlace"
+            prop="workPlace"
+            :show-overflow-tooltip="true"
+          />
+          <el-table-column label="手机号码" align="center" prop="mobile" width="120" />
+          <el-table-column label="导师" align="center" prop="supervisor" width="120" />
+          <el-table-column label="导师电话" align="center" prop="supervisorMobile" width="120" />
+          <el-table-column label="状态" key="status">
+            <template #default="scope">
+              <el-switch
+                v-model="scope.row.status"
+                :active-value="0"
+                :inactive-value="1"
+                @change="handleStatusChange(scope.row)"
+              />
+            </template>
+          </el-table-column>
+          <el-table-column
+            label="创建时间"
+            align="center"
+            prop="createTime"
+            :formatter="dateFormatter"
+            width="180"
+          />
+          <el-table-column label="操作" align="center" width="160">
+            <template #default="scope">
+              <div class="flex items-center justify-center">
+                <el-button
+                  type="primary"
+                  link
+                  @click="openForm('update', scope.row.id)"
+                  v-hasPermi="['system:user:update']"
+                >
+                  <Icon icon="ep:edit" />修改
+                </el-button>
+
+                <el-dropdown
+                  @command="(command) => handleCommand(command, scope.row)"
+                  v-hasPermi="[
+                    'system:user:delete',
+                  ]"
+                >
+                  <el-button type="primary" link><Icon icon="ep:d-arrow-right" /> 更多</el-button>
+                  <template #dropdown>
+                    <el-dropdown-menu>
+                      <el-dropdown-item
+                        command="handleDelete"
+                        v-if="checkPermi(['system:user:delete'])"
+                      >
+                        <Icon icon="ep:delete" />删除
+                      </el-dropdown-item>                      
+                    </el-dropdown-menu>
+                  </template>
+                </el-dropdown>
+
+              </div>
+            </template>
+          </el-table-column>
+        </el-table>
+        <Pagination
+          :total="total"
+          v-model:page="queryParams.pageNo"
+          v-model:limit="queryParams.pageSize"
+          @pagination="getList"
+        />
+      </ContentWrap>
+    </el-col>
+  </el-row>
+
+  <!-- 添加或修改用户对话框 -->
+  <UserForm ref="formRef" @success="getList" />
+  <!-- 用户导入对话框 -->
+  <UserImportForm ref="importFormRef" @success="getList" />
+ 
+</template>
+<script lang="ts" setup>
+import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
+import { checkPermi } from '@/utils/permission'
+import { dateFormatter } from '@/utils/formatTime'
+import download from '@/utils/download'
+import { CommonStatusEnum } from '@/utils/constants'
+import * as UserApi from '@/api/system/user'
+import UserForm from './UserForm.vue'
+import UserImportForm from './UserImportForm.vue'
+
+
+defineOptions({ name: 'SystemUser' })
+
+const message = useMessage() // 消息弹窗
+const { t } = useI18n() // 国际化
+
+const loading = ref(true) // 列表的加载中
+const total = ref(0) // 列表的总页数
+const list = ref([]) // 列表的数
+const queryParams = reactive({
+  pageNo: 1,
+  pageSize: 10,
+  username: undefined,
+  nickname: undefined,
+  mobile: undefined,
+  status: undefined,
+  deptId: undefined,
+  createTime: [],
+  supervisor: undefined,
+  supervisorMobile: undefined,
+  workPlace: undefined,
+  grade: undefined,
+})
+const queryFormRef = ref() // 搜索的表单
+
+// 生成年级选项数据
+const grades = ref(Array.from({ length: 2035 - 2010 + 1 }, (_, i) => 2010 + i));
+
+
+/** 查询列表 */
+const getList = async () => {
+  loading.value = true
+  try {
+    const data = await UserApi.getGraduateStudentTPage(queryParams)
+    list.value = data.list
+    total.value = data.total
+  } finally {
+    loading.value = false
+  }
+}
+
+/** 搜索按钮操作 */
+const handleQuery = () => {
+  queryParams.pageNo = 1
+  getList()
+}
+
+/** 重置按钮操作 */
+const resetQuery = () => {
+  queryFormRef.value?.resetFields()
+  handleQuery()
+}
+
+
+/**修改操作 */
+const formRef = ref()
+const openForm = (type: string, id?: number) => {
+  formRef.value.open(type, id)
+}
+
+/** 用户导入 */
+const importFormRef = ref()
+const handleImport = () => {
+  importFormRef.value.open()
+}
+
+/** 修改用户状态 */
+const handleStatusChange = async (row: UserApi.UserVO) => {
+  try {
+    // 修改状态的二次确认
+    const text = row.status === CommonStatusEnum.ENABLE ? '启用' : '停用'
+    await message.confirm('确认要"' + text + '""' + row.username + '"用户吗?')
+    // 发起修改状态
+    await UserApi.updateUserStatus(row.id, row.status)
+    // 刷新列表
+    await getList()
+  } catch {
+    // 取消后,进行恢复按钮
+    row.status =
+      row.status === CommonStatusEnum.ENABLE ? CommonStatusEnum.DISABLE : CommonStatusEnum.ENABLE
+  }
+}
+
+/** 导出按钮操作 */
+const exportLoading = ref(false)
+const handleExport = async () => {
+  try {
+    // 导出的二次确认
+    await message.exportConfirm()
+    // 发起导出
+    exportLoading.value = true
+    const data = await UserApi.exportUser(queryParams)
+    download.excel(data, '用户数据.xls')
+  } catch {
+  } finally {
+    exportLoading.value = false
+  }
+}
+
+/** 操作分发 */
+const handleCommand = (command: string, row: UserApi.UserVO) => {
+  switch (command) {
+    case 'handleDelete':
+      handleDelete(row.id)
+      break
+    default:
+      break
+  }
+}
+
+/** 删除按钮操作 */
+const handleDelete = async (id: number) => {
+  try {
+    // 删除的二次确认
+    await message.delConfirm()
+    // 发起删除
+    await UserApi.deleteUser(id)
+    message.success(t('common.delSuccess'))
+    // 刷新列表
+    await getList()
+  } catch {}
+}
+
+
+/** 初始化 */
+onMounted(() => {
+  getList()
+})
+</script>

+ 127 - 0
src/views/system/selfAchievement/UserAchievementForm.vue

@@ -0,0 +1,127 @@
+<template>
+  <Dialog :title="dialogTitle" v-model="dialogVisible">
+    <el-form
+      ref="formRef"
+      :model="formData"
+      :rules="formRules"
+      label-width="100px"
+      v-loading="formLoading"
+    >
+
+      <el-form-item label="成果名称" prop="achievementName">
+        <el-input v-model="formData.achievementName" placeholder="请输入成果名称" />
+      </el-form-item>
+
+      <el-form-item label="成果类型" prop="achievementType">
+        <el-select v-model="formData.achievementType" placeholder="请选择成果类型">
+          <el-option
+            v-for="option in achievementTypes"
+            :key="option.value"
+            :label="option.label"
+            :value="option.value"
+          />
+        </el-select>
+      </el-form-item>
+
+      <el-form-item label="照片详情url" prop="detail">
+        <UploadImg v-model="formData.detail" />
+      </el-form-item>
+    </el-form>
+    <template #footer>
+      <el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button>
+      <el-button @click="dialogVisible = false">取 消</el-button>
+    </template>
+  </Dialog>
+</template>
+<script setup lang="ts">
+import { UserAchievementApi, UserAchievementVO } from '@/api/system/userAchievement'
+/** 成果 表单 */
+defineOptions({ name: 'UserAchievementForm' })
+
+const { t } = useI18n() // 国际化
+const message = useMessage() // 消息弹窗
+
+const dialogVisible = ref(false) // 弹窗的是否展示
+const dialogTitle = ref('') // 弹窗的标题
+const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
+const formType = ref('') // 表单的类型:create - 新增;update - 修改
+const formData = ref({
+  id: undefined,
+  userId: undefined,
+  achievementName: undefined,
+  achievementType: undefined,
+  userName: undefined,
+  detail: undefined,
+})
+const formRules = reactive({
+  userId: [{ required: true, message: '用户id不能为空', trigger: 'blur' }],
+  achievementName: [{ required: true, message: '成果名称不能为空', trigger: 'blur' }],
+  achievementType: [{ required: true, message: '成果类型不能为空', trigger: 'change' }],
+  userName: [{ required: true, message: '用户名称不能为空', trigger: 'blur' }],
+})
+const formRef = ref() // 表单 Ref
+
+// 成果类型选项
+const achievementTypes = ref([
+  { label: '论文', value: "1" },
+  { label: '专利', value: "2" },
+  { label: '著作', value: "3" },
+]);
+
+/** 打开弹窗 */
+const open = async (type: string, id?: number) => {
+  dialogVisible.value = true
+  dialogTitle.value = t('action.' + type)
+  formType.value = type
+  resetForm()
+  // 修改时,设置数据
+  if (id) {
+    formLoading.value = true
+    try {
+      formData.value = await UserAchievementApi.getUserAchievement(id)
+    } finally {
+      formLoading.value = false
+    }
+  }
+}
+defineExpose({ open }) // 提供 open 方法,用于打开弹窗
+
+
+/** 提交表单 */
+const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
+const submitForm = async () => {
+  // 校验表单
+  await formRef.value.validate()
+  // 提交请求
+  formLoading.value = true
+  try {
+    const data = formData.value as unknown as UserAchievementVO
+    if (formType.value === 'create') {
+      await UserAchievementApi.createSelfAchievement(data)
+      message.success(t('common.createSuccess'))
+    } else {
+      await UserAchievementApi.updateSelfAchievement(data)
+      message.success(t('common.updateSuccess'))
+    }
+    dialogVisible.value = false
+    // 发送操作成功的事件
+    emit('success')
+  } finally {
+    formLoading.value = false
+  }
+}
+
+/** 重置表单 */
+const resetForm = () => {
+  formData.value = {
+    id: undefined,
+    userId: undefined,
+    achievementName: undefined,
+    achievementType: undefined,
+    userName: undefined,
+    detail: undefined,
+  }
+  formRef.value?.resetFields()
+}
+
+</script>

+ 217 - 0
src/views/system/selfAchievement/index.vue

@@ -0,0 +1,217 @@
+<template>
+  <ContentWrap>
+    <!-- 搜索工作栏 -->
+    <el-form
+      class="-mb-15px"
+      :model="queryParams"
+      ref="queryFormRef"
+      :inline="true"
+      label-width="68px"
+    >
+
+      <el-form-item label="成果名称" prop="achievementName">
+        <el-input
+          v-model="queryParams.achievementName"
+          placeholder="请输入成果名称"
+          clearable
+          @keyup.enter="handleQuery"
+          class="!w-240px"
+        />
+      </el-form-item>
+      <el-form-item label="成果类型" prop="achievementType">
+        <el-select
+          v-model="queryParams.achievementType"
+          placeholder="请选择成果类型"
+          clearable
+          class="!w-240px"
+        >
+          <el-option :label="'论文'" :value="1" />
+          <el-option :label="'专利'" :value="2" />
+          <el-option :label="'著作'" :value="3" />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="创建时间" prop="createTime">
+        <el-date-picker
+          v-model="queryParams.createTime"
+          value-format="YYYY-MM-DD HH:mm:ss"
+          type="daterange"
+          start-placeholder="开始日期"
+          end-placeholder="结束日期"
+          :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
+          class="!w-220px"
+        />
+      </el-form-item>
+      <el-form-item>
+        <el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
+        <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
+        <el-button
+          type="primary"
+          plain
+          @click="openForm('create')"
+          v-hasPermi="['system:user-achievement:selfCreate']"
+        >
+          <Icon icon="ep:plus" class="mr-5px" /> 新增
+        </el-button>
+        <el-button
+          type="success"
+          plain
+          @click="handleExport"
+          :loading="exportLoading"
+          v-hasPermi="['system:user-achievement:export']"
+        >
+          <Icon icon="ep:download" class="mr-5px" /> 导出
+        </el-button>
+      </el-form-item>
+    </el-form>
+  </ContentWrap>
+
+  <!-- 列表 -->
+  <ContentWrap>
+    <el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
+      <el-table-column label="Id" align="center" prop="id" />
+      <!-- <el-table-column label="用户 Id" align="center" prop="userId" v-if="false" /> -->
+      <!-- <el-table-column label="用户名称" align="center" prop="userName" /> -->
+      <el-table-column label="成果名称" align="center" prop="achievementName" />
+      <el-table-column label="成果类型" align="center" prop="achievementType">
+        <template #default="rowData">
+          <span>
+            {{
+              rowData.row.achievementType == 1
+               ? '论文'
+                : rowData.row.achievementType ==2
+               ? '专利'
+                : rowData.row.achievementType == 3
+               ? '著作'
+                : '未知类型'
+            }}
+          </span>
+        </template>
+      </el-table-column>
+      <el-table-column
+        label="创建时间"
+        align="center"
+        prop="createTime"
+        :formatter="dateFormatter"
+        width="180px"
+      />
+      <el-table-column label="照片详情 url" align="center">
+        <!-- #default获取当前·行数据 -->
+        <template #default="{ row }">
+          <a :href="row.detail" target="_blank" v-if="row.detail">{{ row.detail }}</a>
+        </template>
+      </el-table-column>
+      <el-table-column label="操作" align="center" min-width="120px">
+        <template #default="rowData">
+          <el-button
+            link
+            type="primary"
+            @click="openForm('update', rowData.row.id)"
+            v-hasPermi="['system:user-achievement:selfUpdate']"
+          >
+            编辑
+          </el-button>
+          <el-button
+            link
+            type="danger"
+            @click="handleDelete(rowData.row.id)"
+            v-hasPermi="['system:user-achievement:delete']"
+          >
+            删除
+          </el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+    <!-- 分页 -->
+    <Pagination
+      :total="total"
+      v-model:page="queryParams.pageNo"
+      v-model:limit="queryParams.pageSize"
+      @pagination="getList"
+    />
+  </ContentWrap>
+
+  <!-- 表单弹窗:添加/修改 -->
+  <UserAchievementForm ref="formRef" @success="getList" />
+</template>
+
+<script setup lang="ts">
+import { dateFormatter } from '@/utils/formatTime'
+import download from '@/utils/download'
+import { UserAchievementApi, UserAchievementVO } from '@/api/system/userAchievement'
+import UserAchievementForm from './UserAchievementForm.vue'
+
+/** 成果 列表 */
+defineOptions({ name: 'UserAchievement' })
+
+const message = useMessage() // 消息弹窗
+const { t } = useI18n() // 国际化
+
+const loading = ref(true) // 列表的加载中
+const list = ref<UserAchievementVO[]>([]) // 列表的数据
+const total = ref(0) // 列表的总页数
+const queryParams = reactive({
+  pageNo: 1,
+  pageSize: 10,
+  userId: undefined,
+  achievementName: undefined,
+  achievementType: undefined,
+  userName: undefined,
+  createTime: [],
+  detail: undefined,
+})
+const queryFormRef = ref() // 搜索的表单
+const exportLoading = ref(false) // 导出的加载中
+
+const users = ref()
+
+const getList = async () => {
+  loading.value = true
+  try {
+    const data = await UserAchievementApi.getSelfAchievementPage(queryParams)
+    list.value = data.list
+    total.value = data.total
+  } finally {
+    loading.value = false
+  }
+}
+
+const handleQuery = () => {
+  queryParams.pageNo = 1
+  getList()
+}
+
+const resetQuery = () => {
+  queryFormRef.value.resetFields()
+  handleQuery()
+}
+
+const formRef = ref()
+const openForm = (type: string, id?: number) => {
+  formRef.value.open(type, id)
+}
+
+const handleDelete = async (id: number) => {
+  try {
+    await message.delConfirm()
+    await UserAchievementApi.deleteUserAchievement(id)
+    message.success(t('common.delSuccess'))
+    await getList()
+  } catch {}
+}
+
+const handleExport = async () => {
+  try {
+    await message.exportConfirm()
+    exportLoading.value = true
+    const data = await UserAchievementApi.exportUserAchievement(queryParams)
+    download.excel(data, '成果.xls')
+  } catch {
+  } finally {
+    exportLoading.value = false
+  }
+}
+
+onMounted(() => {
+  getList()
+})
+</script>

+ 131 - 0
src/views/system/studentAttendanceManage/studentAttendance/StudentAttendanceForm.vue

@@ -0,0 +1,131 @@
+<template>
+  <Dialog :title="dialogTitle" v-model="dialogVisible">
+    <el-form
+      ref="formRef"
+      :model="formData"
+      :rules="formRules"
+      label-width="100px"
+      v-loading="formLoading"
+    >
+      <el-form-item label="学生id" prop="studentId">
+        <el-input v-model="formData.studentId" placeholder="请输入学生id" />
+      </el-form-item>
+      <el-form-item label="工作间id" prop="deptId">
+        <el-input v-model="formData.deptId" placeholder="请输入工作间id" />
+      </el-form-item>
+      <el-form-item label="日期" prop="date">
+        <el-date-picker
+          v-model="formData.date"
+          type="date"
+          value-format="x"
+          placeholder="选择日期"
+        />
+      </el-form-item>
+      <el-form-item label="打卡时间" prop="clockInTime">
+        <el-date-picker
+          v-model="formData.clockInTime"
+          type="date"
+          value-format="x"
+          placeholder="选择打卡时间"
+        />
+      </el-form-item>
+      <el-form-item label="打卡状态 0正常,1迟到、2早退、3缺勤、4请假" prop="clockInStatus">
+        <el-radio-group v-model="formData.clockInStatus">
+          <el-radio label="1">请选择字典生成</el-radio>
+        </el-radio-group>
+      </el-form-item>
+      <el-form-item label="备注原因" prop="remark">
+        <el-input v-model="formData.remark" placeholder="请输入备注原因" />
+      </el-form-item>
+    </el-form>
+    <template #footer>
+      <el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button>
+      <el-button @click="dialogVisible = false">取 消</el-button>
+    </template>
+  </Dialog>
+</template>
+<script setup lang="ts">
+import { StudentAttendanceApi, StudentAttendanceVO } from '@/api/system/studentAttendance'
+
+/** 学生考勤记录 表单 */
+defineOptions({ name: 'StudentAttendanceForm' })
+
+const { t } = useI18n() // 国际化
+const message = useMessage() // 消息弹窗
+
+const dialogVisible = ref(false) // 弹窗的是否展示
+const dialogTitle = ref('') // 弹窗的标题
+const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
+const formType = ref('') // 表单的类型:create - 新增;update - 修改
+const formData = ref({
+  id: undefined,
+  studentId: undefined,
+  deptId: undefined,
+  date: undefined,
+  clockInTime: undefined,
+  clockInStatus: undefined,
+  remark: undefined,
+})
+const formRules = reactive({
+  studentId: [{ required: true, message: '学生id不能为空', trigger: 'blur' }],
+  deptId: [{ required: true, message: '工作间id不能为空', trigger: 'blur' }],
+  date: [{ required: true, message: '日期不能为空', trigger: 'blur' }],
+})
+const formRef = ref() // 表单 Ref
+
+/** 打开弹窗 */
+const open = async (type: string, id?: number) => {
+  dialogVisible.value = true
+  dialogTitle.value = t('action.' + type)
+  formType.value = type
+  resetForm()
+  // 修改时,设置数据
+  if (id) {
+    formLoading.value = true
+    try {
+      formData.value = await StudentAttendanceApi.getStudentAttendance(id)
+    } finally {
+      formLoading.value = false
+    }
+  }
+}
+defineExpose({ open }) // 提供 open 方法,用于打开弹窗
+
+/** 提交表单 */
+const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
+const submitForm = async () => {
+  // 校验表单
+  await formRef.value.validate()
+  // 提交请求
+  formLoading.value = true
+  try {
+    const data = formData.value as unknown as StudentAttendanceVO
+    if (formType.value === 'create') {
+      await StudentAttendanceApi.createStudentAttendance(data)
+      message.success(t('common.createSuccess'))
+    } else {
+      await StudentAttendanceApi.updateStudentAttendance(data)
+      message.success(t('common.updateSuccess'))
+    }
+    dialogVisible.value = false
+    // 发送操作成功的事件
+    emit('success')
+  } finally {
+    formLoading.value = false
+  }
+}
+
+/** 重置表单 */
+const resetForm = () => {
+  formData.value = {
+    id: undefined,
+    studentId: undefined,
+    deptId: undefined,
+    date: undefined,
+    clockInTime: undefined,
+    clockInStatus: undefined,
+    remark: undefined,
+  }
+  formRef.value?.resetFields()
+}
+</script>

+ 284 - 0
src/views/system/studentAttendanceManage/studentAttendance/index.vue

@@ -0,0 +1,284 @@
+<template>
+  <ContentWrap>
+    <!-- 搜索工作栏 -->
+    <el-form
+      class="-mb-15px"
+      :model="queryParams"
+      ref="queryFormRef"
+      :inline="true"
+      label-width="68px"
+    >
+      <el-form-item label="用户昵称" prop="studentName">
+            <el-input
+              placeholder="请输入用户昵称 "
+              v-model="queryParams.studentName"
+              clearable
+               @keyup.enter="handleQuery"
+              class="!w-240px"
+            />
+          </el-form-item>
+
+      <el-form-item label="创建时间" prop="createTime">
+            <el-date-picker
+              v-model="queryParams.createTime"
+              value-format="YYYY-MM-DD HH:mm:ss"
+              type="datetimerange"
+              start-placeholder="开始日期"
+              end-placeholder="结束日期"
+              class="!w-240px"
+            />
+          </el-form-item>
+
+      <el-form-item label="打卡状态 " prop="clockInStatus">
+        <el-select
+          v-model="queryParams.clockInStatus"
+          placeholder="请选择打卡状态 "
+          clearable
+          class="!w-240px"
+        >
+          <el-option :label="'正常'" :value="0" />
+          <el-option :label="'迟到'" :value="1" />
+          <el-option :label="'早退'" :value="2" />
+          <el-option :label="'缺勤'" :value="3" />
+          <el-option :label="'请假'" :value="4" />
+        </el-select>
+      </el-form-item>
+
+      <el-form-item>
+        <el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
+        <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
+        <el-button
+          type="primary"
+          plain
+          @click="openForm('create')"
+          v-hasPermi="['system:student-attendance:create']"
+        >
+          <Icon icon="ep:plus" class="mr-5px" /> 新增
+        </el-button>
+        <el-button
+          type="success"
+          plain
+          @click="handleExport"
+          :loading="exportLoading"
+          v-hasPermi="['system:student-attendance:export']"
+        >
+          <Icon icon="ep:download" class="mr-5px" /> 导出
+        </el-button>
+      </el-form-item>
+    </el-form>
+  </ContentWrap>
+
+  <!-- 列表 -->
+  <ContentWrap>
+    <el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
+      <el-table-column label="Id" align="center" prop="id" />
+      <!-- <el-table-column label="学生id" align="center" prop="studentId" /> -->
+      <el-table-column label="学生名称" align="center" prop="studentName" />
+      <!-- <el-table-column label="工作间id" align="center" prop="deptId" /> -->
+      <!-- <el-table-column label="日期" align="center" prop="date" /> -->
+      <el-table-column
+        label="打卡时间"
+        align="center"
+        prop="clockInTime"
+        :formatter="dateFormatter"
+        width="180px"
+      />
+      <el-table-column label="打卡状态 " align="center" prop="clockInStatus" >
+        <template #default="rowData">
+          <span :class="getStatusClass(rowData.row.clockInStatus)">
+            {{
+              rowData.row.clockInStatus == 0
+               ? '正常'
+                : rowData.row.clockInStatus ==1
+               ? '迟到'
+                : rowData.row.clockInStatus == 2
+               ? '早退'
+                : rowData.row.clockInStatus == 3
+               ? '缺勤'
+                : rowData.row.clockInStatus == 4
+               ? '请假'
+                : '未知类型'
+            }}
+          </span>
+        </template>
+      </el-table-column>
+      <!-- <el-table-column
+        label="创建时间"
+        align="center"
+        prop="createTime"
+        :formatter="dateFormatter"
+        width="180px"
+      /> -->
+      <el-table-column label="备注原因" align="center" prop="remark" />
+      <el-table-column label="操作" align="center" min-width="120px">
+        <template #default="scope">
+          <el-button
+            link
+            type="primary"
+            @click="openForm('update', scope.row.id)"
+            v-hasPermi="['system:student-attendance:update']"
+          >
+            编辑
+          </el-button>
+          <el-button
+            link
+            type="danger"
+            @click="handleDelete(scope.row.id)"
+            v-hasPermi="['system:student-attendance:delete']"
+          >
+            删除
+          </el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+    <!-- 分页 -->
+    <Pagination
+      :total="total"
+      v-model:page="queryParams.pageNo"
+      v-model:limit="queryParams.pageSize"
+      @pagination="getList"
+    />
+  </ContentWrap>
+
+  <!-- 表单弹窗:添加/修改 -->
+  <StudentAttendanceForm ref="formRef" @success="getList" />
+</template>
+
+<script setup lang="ts">
+import { dateFormatter } from '@/utils/formatTime'
+import download from '@/utils/download'
+import { StudentAttendanceApi, StudentAttendanceVO } from '@/api/system/studentAttendance'
+import StudentAttendanceForm from './StudentAttendanceForm.vue'
+
+/** 学生考勤记录 列表 */
+defineOptions({ name: 'StudentAttendance' })
+
+const message = useMessage() // 消息弹窗
+const { t } = useI18n() // 国际化
+
+const loading = ref(true) // 列表的加载中
+const list = ref<StudentAttendanceVO[]>([]) // 列表的数据
+const total = ref(0) // 列表的总页数
+const queryParams = reactive({
+  pageNo: 1,
+  pageSize: 10,
+  studentId: undefined,
+  studentName: undefined,
+  deptId: undefined,
+  date: [],
+  clockInTime: [],
+  clockInTimeRange: [],
+  clockInStatus: undefined,
+  createTime: [],
+  remark: undefined,
+})
+const queryFormRef = ref() // 搜索的表单
+const exportLoading = ref(false) // 导出的加载中
+
+/** 查询列表 */
+const getList = async () => {
+  loading.value = true
+  try {
+    const data = await StudentAttendanceApi.getStudentAttendancePage(queryParams)
+    list.value = data.list
+    total.value = data.total
+  } finally {
+    loading.value = false
+  }
+}
+
+/** 搜索按钮操作 */
+const handleQuery = () => {
+  queryParams.pageNo = 1
+  getList()
+}
+
+/** 重置按钮操作 */
+const resetQuery = () => {
+  queryFormRef.value.resetFields()
+  handleQuery()
+}
+
+/** 添加/修改操作 */
+const formRef = ref()
+const openForm = (type: string, id?: number) => {
+  formRef.value.open(type, id)
+}
+
+/** 删除按钮操作 */
+const handleDelete = async (id: number) => {
+  try {
+    // 删除的二次确认
+    await message.delConfirm()
+    // 发起删除
+    await StudentAttendanceApi.deleteStudentAttendance(id)
+    message.success(t('common.delSuccess'))
+    // 刷新列表
+    await getList()
+  } catch {}
+}
+
+/** 导出按钮操作 */
+const handleExport = async () => {
+  try {
+    // 导出的二次确认
+    await message.exportConfirm()
+    // 发起导出
+    exportLoading.value = true
+    const data = await StudentAttendanceApi.exportStudentAttendance(queryParams)
+    download.excel(data, '学生考勤记录.xls')
+  } catch {
+  } finally {
+    exportLoading.value = false
+  }
+}
+
+// 状态颜色显示
+const getStatusClass = (status) => {
+  switch (status) {
+    case "0":
+      return 'status-normal';
+    case "1":
+      return 'status-late';
+    case "2":
+      return 'status-early';
+    case "3":
+      return 'status-absent';
+    case "4":
+      return 'status-leave';
+    default:
+      return 'status-unknown';
+  }
+};
+
+/** 初始化 **/
+onMounted(() => {
+  getList()
+})
+</script>
+
+<style scoped>
+.status-normal {
+  color: green;
+}
+
+.status-late {
+  color: orange;
+}
+
+.status-early {
+  color: blue;
+}
+
+.status-absent {
+  color: red;
+}
+
+.status-leave {
+  color: purple;
+}
+
+.status-unknown {
+  color: gray;
+}
+</style>

+ 131 - 0
src/views/system/studentAttendanceManage/studentAttendanceError/StudentAttendanceForm.vue

@@ -0,0 +1,131 @@
+<template>
+  <Dialog :title="dialogTitle" v-model="dialogVisible">
+    <el-form
+      ref="formRef"
+      :model="formData"
+      :rules="formRules"
+      label-width="100px"
+      v-loading="formLoading"
+    >
+      <el-form-item label="学生id" prop="studentId">
+        <el-input v-model="formData.studentId" placeholder="请输入学生id" />
+      </el-form-item>
+      <el-form-item label="工作间id" prop="deptId">
+        <el-input v-model="formData.deptId" placeholder="请输入工作间id" />
+      </el-form-item>
+      <el-form-item label="日期" prop="date">
+        <el-date-picker
+          v-model="formData.date"
+          type="date"
+          value-format="x"
+          placeholder="选择日期"
+        />
+      </el-form-item>
+      <el-form-item label="打卡时间" prop="clockInTime">
+        <el-date-picker
+          v-model="formData.clockInTime"
+          type="date"
+          value-format="x"
+          placeholder="选择打卡时间"
+        />
+      </el-form-item>
+      <el-form-item label="打卡状态 0正常,1迟到、2早退、3缺勤、4请假" prop="clockInStatus">
+        <el-radio-group v-model="formData.clockInStatus">
+          <el-radio label="1">请选择字典生成</el-radio>
+        </el-radio-group>
+      </el-form-item>
+      <el-form-item label="备注原因" prop="remark">
+        <el-input v-model="formData.remark" placeholder="请输入备注原因" />
+      </el-form-item>
+    </el-form>
+    <template #footer>
+      <el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button>
+      <el-button @click="dialogVisible = false">取 消</el-button>
+    </template>
+  </Dialog>
+</template>
+<script setup lang="ts">
+import { StudentAttendanceApi, StudentAttendanceVO } from '@/api/system/studentAttendance'
+
+/** 学生考勤记录 表单 */
+defineOptions({ name: 'StudentAttendanceForm' })
+
+const { t } = useI18n() // 国际化
+const message = useMessage() // 消息弹窗
+
+const dialogVisible = ref(false) // 弹窗的是否展示
+const dialogTitle = ref('') // 弹窗的标题
+const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
+const formType = ref('') // 表单的类型:create - 新增;update - 修改
+const formData = ref({
+  id: undefined,
+  studentId: undefined,
+  deptId: undefined,
+  date: undefined,
+  clockInTime: undefined,
+  clockInStatus: undefined,
+  remark: undefined,
+})
+const formRules = reactive({
+  studentId: [{ required: true, message: '学生id不能为空', trigger: 'blur' }],
+  deptId: [{ required: true, message: '工作间id不能为空', trigger: 'blur' }],
+  date: [{ required: true, message: '日期不能为空', trigger: 'blur' }],
+})
+const formRef = ref() // 表单 Ref
+
+/** 打开弹窗 */
+const open = async (type: string, id?: number) => {
+  dialogVisible.value = true
+  dialogTitle.value = t('action.' + type)
+  formType.value = type
+  resetForm()
+  // 修改时,设置数据
+  if (id) {
+    formLoading.value = true
+    try {
+      formData.value = await StudentAttendanceApi.getStudentAttendance(id)
+    } finally {
+      formLoading.value = false
+    }
+  }
+}
+defineExpose({ open }) // 提供 open 方法,用于打开弹窗
+
+/** 提交表单 */
+const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
+const submitForm = async () => {
+  // 校验表单
+  await formRef.value.validate()
+  // 提交请求
+  formLoading.value = true
+  try {
+    const data = formData.value as unknown as StudentAttendanceVO
+    if (formType.value === 'create') {
+      await StudentAttendanceApi.createStudentAttendance(data)
+      message.success(t('common.createSuccess'))
+    } else {
+      await StudentAttendanceApi.updateStudentAttendance(data)
+      message.success(t('common.updateSuccess'))
+    }
+    dialogVisible.value = false
+    // 发送操作成功的事件
+    emit('success')
+  } finally {
+    formLoading.value = false
+  }
+}
+
+/** 重置表单 */
+const resetForm = () => {
+  formData.value = {
+    id: undefined,
+    studentId: undefined,
+    deptId: undefined,
+    date: undefined,
+    clockInTime: undefined,
+    clockInStatus: undefined,
+    remark: undefined,
+  }
+  formRef.value?.resetFields()
+}
+</script>

+ 276 - 0
src/views/system/studentAttendanceManage/studentAttendanceError/index.vue

@@ -0,0 +1,276 @@
+<template>
+  <ContentWrap>
+    <!-- 搜索工作栏 -->
+    <el-form
+      class="-mb-15px"
+      :model="queryParams"
+      ref="queryFormRef"
+      :inline="true"
+      label-width="68px"
+    >
+        <el-form-item label="用户昵称" prop="studentName">
+            <el-input
+              placeholder="请输入用户昵称 "
+              v-model="queryParams.studentName"
+              clearable
+               @keyup.enter="handleQuery"
+              class="!w-240px"
+            />
+          </el-form-item>
+
+      <el-form-item label="创建时间" prop="createTime">
+            <el-date-picker
+              v-model="queryParams.createTime"
+              value-format="YYYY-MM-DD HH:mm:ss"
+              type="datetimerange"
+              start-placeholder="开始日期"
+              end-placeholder="结束日期"
+              class="!w-240px"
+            />
+          </el-form-item>
+
+      <el-form-item label="打卡状态 " prop="clockInStatus">
+        <el-select
+          v-model="queryParams.clockInStatus"
+          placeholder="请选择打卡状态 "
+          clearable
+          class="!w-240px"
+        >
+          <el-option :label="'正常'" :value="0" />
+          <el-option :label="'迟到'" :value="1" />
+          <el-option :label="'早退'" :value="2" />
+          <el-option :label="'缺勤'" :value="3" />
+          <el-option :label="'请假'" :value="4" />
+        </el-select>
+      </el-form-item>
+      
+      <el-form-item>
+        <el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
+        <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
+        <el-button
+          type="primary"
+          plain
+          @click="openForm('create')"
+          v-hasPermi="['system:student-attendance:create']"
+        >
+          <Icon icon="ep:plus" class="mr-5px" /> 新增
+        </el-button>
+        <el-button
+          type="success"
+          plain
+          @click="handleExport"
+          :loading="exportLoading"
+          v-hasPermi="['system:student-attendance:export']"
+        >
+          <Icon icon="ep:download" class="mr-5px" /> 导出
+        </el-button>
+      </el-form-item>
+    </el-form>
+  </ContentWrap>
+
+  <!-- 列表 -->
+  <ContentWrap>
+    <el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
+      <el-table-column label="Id" align="center" prop="id" />
+
+      <el-table-column label="学生名称" align="center" prop="studentName" />
+      <el-table-column
+        label="打卡时间"
+        align="center"
+        prop="clockInTime"
+        :formatter="dateFormatter"
+        width="180px"
+      />
+      <el-table-column label="打卡状态 " align="center" prop="clockInStatus" >
+        <template #default="rowData">
+          <span :class="getStatusClass(rowData.row.clockInStatus)">
+            {{
+              rowData.row.clockInStatus == 0
+               ? '正常'
+                : rowData.row.clockInStatus ==1
+               ? '迟到'
+                : rowData.row.clockInStatus == 2
+               ? '早退'
+                : rowData.row.clockInStatus == 3
+               ? '缺勤'
+                : rowData.row.clockInStatus == 4
+               ? '请假'
+                : '未知类型'
+            }}
+          </span>
+        </template>
+      </el-table-column>
+     
+      <el-table-column label="备注原因" align="center" prop="remark" />
+      <el-table-column label="操作" align="center" min-width="120px">
+        <template #default="scope">
+          <el-button
+            link
+            type="primary"
+            @click="openForm('update', scope.row.id)"
+            v-hasPermi="['system:student-attendance:update']"
+          >
+            编辑
+          </el-button>
+          <el-button
+            link
+            type="danger"
+            @click="handleDelete(scope.row.id)"
+            v-hasPermi="['system:student-attendance:delete']"
+          >
+            删除
+          </el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+    <!-- 分页 -->
+    <Pagination
+      :total="total"
+      v-model:page="queryParams.pageNo"
+      v-model:limit="queryParams.pageSize"
+      @pagination="getList"
+    />
+  </ContentWrap>
+
+  <!-- 表单弹窗:添加/修改 -->
+  <StudentAttendanceForm ref="formRef" @success="getList" />
+</template>
+
+<script setup lang="ts">
+import { dateFormatter } from '@/utils/formatTime'
+import download from '@/utils/download'
+import { StudentAttendanceApi, StudentAttendanceVO } from '@/api/system/studentAttendance'
+import StudentAttendanceForm from './StudentAttendanceForm.vue'
+
+/** 学生考勤记录 列表 */
+defineOptions({ name: 'StudentAttendance' })
+
+const message = useMessage() // 消息弹窗
+const { t } = useI18n() // 国际化
+
+const loading = ref(true) // 列表的加载中
+const list = ref<StudentAttendanceVO[]>([]) // 列表的数据
+const total = ref(0) // 列表的总页数
+const queryParams = reactive({
+  pageNo: 1,
+  pageSize: 10,
+  studentId: undefined,
+  studentName: undefined,
+  deptId: undefined,
+  date: [],
+  clockInTime: [],
+  clockInTimeRange: [],
+  clockInStatus: undefined,
+  createTime: [],
+  remark: undefined,
+})
+const queryFormRef = ref() // 搜索的表单
+const exportLoading = ref(false) // 导出的加载中
+
+/** 查询列表 */
+const getList = async () => {
+  loading.value = true
+  try {
+    const data = await StudentAttendanceApi.getStudentAttendanceErrorPage(queryParams)
+    list.value = data.list
+    total.value = data.total
+  } finally {
+    loading.value = false
+  }
+}
+
+/** 搜索按钮操作 */
+const handleQuery = () => {
+  queryParams.pageNo = 1
+  getList()
+}
+
+/** 重置按钮操作 */
+const resetQuery = () => {
+  queryFormRef.value.resetFields()
+  handleQuery()
+}
+
+/** 添加/修改操作 */
+const formRef = ref()
+const openForm = (type: string, id?: number) => {
+  formRef.value.open(type, id)
+}
+
+/** 删除按钮操作 */
+const handleDelete = async (id: number) => {
+  try {
+    // 删除的二次确认
+    await message.delConfirm()
+    // 发起删除
+    await StudentAttendanceApi.deleteStudentAttendance(id)
+    message.success(t('common.delSuccess'))
+    // 刷新列表
+    await getList()
+  } catch {}
+}
+
+/** 导出按钮操作 */
+const handleExport = async () => {
+  try {
+    // 导出的二次确认
+    await message.exportConfirm()
+    // 发起导出
+    exportLoading.value = true
+    const data = await StudentAttendanceApi.exportStudentAttendance(queryParams)
+    download.excel(data, '学生考勤记录.xls')
+  } catch {
+  } finally {
+    exportLoading.value = false
+  }
+}
+
+// 状态颜色显示
+const getStatusClass = (status) => {
+  switch (status) {
+    case "0":
+      return 'status-normal';
+    case "1":
+      return 'status-late';
+    case "2":
+      return 'status-early';
+    case "3":
+      return 'status-absent';
+    case "4":
+      return 'status-leave';
+    default:
+      return 'status-unknown';
+  }
+};
+
+/** 初始化 **/
+onMounted(() => {
+  getList()
+})
+</script>
+
+<style scoped>
+.status-normal {
+  color: green;
+}
+
+.status-late {
+  color: orange;
+}
+
+.status-early {
+  color: blue;
+}
+
+.status-absent {
+  color: red;
+}
+
+.status-leave {
+  color: purple;
+}
+
+.status-unknown {
+  color: gray;
+}
+</style>

+ 1 - 1
src/views/system/user/DeptTree.vue

@@ -1,6 +1,6 @@
 <template>
   <div class="head-container">
-    <el-input v-model="deptName" class="mb-20px" clearable placeholder="请输入部门名称">
+    <el-input v-model="deptName" class="mb-20px" clearable placeholder="请输入工作间名称">
       <template #prefix>
         <Icon icon="ep:search" />
       </template>

+ 30 - 4
src/views/system/user/UserForm.vue

@@ -14,14 +14,14 @@
           </el-form-item>
         </el-col>
         <el-col :span="12">
-          <el-form-item label="归属部门" prop="deptId">
+          <el-form-item label="工作间" prop="deptId">
             <el-tree-select
               v-model="formData.deptId"
               :data="deptList"
               :props="defaultProps"
               check-strictly
               node-key="id"
-              placeholder="请选择归属部门"
+              placeholder="请选择归属工作间"
             />
           </el-form-item>
         </el-col>
@@ -81,6 +81,22 @@
           </el-form-item>
         </el-col>
       </el-row>
+
+      <el-row v-if="formType === 'update'">
+        <el-col :span="12">
+          <el-form-item label="用户类型" prop="userType">
+            <el-select v-model="formData.userType" placeholder="请选择用户类型">
+              <el-option
+                v-for="option in userTypes"
+                :key="option.value"
+                :label="option.label"
+                :value="option.value"
+                />
+            </el-select>
+          </el-form-item>
+        </el-col>
+      </el-row>
+      
       <el-row>
         <el-col :span="24">
           <el-form-item label="备注">
@@ -125,7 +141,8 @@ const formData = ref({
   postIds: [],
   remark: '',
   status: CommonStatusEnum.ENABLE,
-  roleIds: []
+  roleIds: [],
+  userType: ''
 })
 const formRules = reactive<FormRules>({
   username: [{ required: true, message: '用户名称不能为空', trigger: 'blur' }],
@@ -150,6 +167,14 @@ const formRef = ref() // 表单 Ref
 const deptList = ref<Tree[]>([]) // 树形结构
 const postList = ref([] as PostApi.PostVO[]) // 岗位列表
 
+
+//用户类型
+const userTypes = ref([
+  { label: '在校生', value: "1" },
+  { label: '毕业生', value: "2" },
+  { label: '导师', value: "3" },
+  { label: '学院', value: "4" },
+]);
 /** 打开弹窗 */
 const open = async (type: string, id?: number) => {
   dialogVisible.value = true
@@ -212,7 +237,8 @@ const resetForm = () => {
     postIds: [],
     remark: '',
     status: CommonStatusEnum.ENABLE,
-    roleIds: []
+    roleIds: [],
+    userType: ''
   }
   formRef.value?.resetFields()
 }

+ 1 - 1
src/views/system/user/index.vue

@@ -110,7 +110,7 @@
             :show-overflow-tooltip="true"
           />
           <el-table-column
-            label="部门"
+            label="工作间"
             align="center"
             key="deptName"
             prop="deptName"

+ 360 - 0
src/views/system/user/student.vue

@@ -0,0 +1,360 @@
+<template>
+
+  <el-row :gutter="20">
+    <!-- 左侧部门树 -->
+    <el-col :span="4" :xs="24">
+      <ContentWrap class="h-1/1">
+        <DeptTree @node-click="handleDeptNodeClick" />
+      </ContentWrap>
+    </el-col>
+    <el-col :span="20" :xs="24">
+      <!-- 搜索 -->
+      <ContentWrap>
+        <el-form
+          class="-mb-15px"
+          :model="queryParams"
+          ref="queryFormRef"
+          :inline="true"
+          label-width="68px"
+        >
+          <el-form-item label="用户名称" prop="username">
+            <el-input
+              v-model="queryParams.username"
+              placeholder="请输入用户名称"
+              clearable
+              @keyup.enter="handleQuery"
+              class="!w-240px"
+            />
+          </el-form-item>
+          <el-form-item label="手机号码" prop="mobile">
+            <el-input
+              v-model="queryParams.mobile"
+              placeholder="请输入手机号码"
+              clearable
+              @keyup.enter="handleQuery"
+              class="!w-240px"
+            />
+          </el-form-item>
+          <el-form-item label="状态" prop="status">
+            <el-select
+              v-model="queryParams.status"
+              placeholder="用户状态"
+              clearable
+              class="!w-240px"
+            >
+              <el-option
+                v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
+                :key="dict.value"
+                :label="dict.label"
+                :value="dict.value"
+              />
+            </el-select>
+          </el-form-item>
+          <el-form-item label="创建时间" prop="createTime">
+            <el-date-picker
+              v-model="queryParams.createTime"
+              value-format="YYYY-MM-DD HH:mm:ss"
+              type="datetimerange"
+              start-placeholder="开始日期"
+              end-placeholder="结束日期"
+              class="!w-240px"
+            />
+          </el-form-item>
+          <el-form-item>
+            <el-button @click="handleQuery"><Icon icon="ep:search" />搜索</el-button>
+            <el-button @click="resetQuery"><Icon icon="ep:refresh" />重置</el-button>
+             <!-- 暂时不在这新增 -->
+            <!-- <el-button
+              type="primary"
+              plain
+              @click="openForm('create')"
+              v-hasPermi="['system:user:create']"
+            >
+              <Icon icon="ep:plus" /> 新增
+            </el-button> -->
+            <el-button
+              type="warning"
+              plain
+              @click="handleImport"
+              v-hasPermi="['system:user:import']"
+            >
+              <Icon icon="ep:upload" /> 导入
+            </el-button>
+            <el-button
+              type="success"
+              plain
+              @click="handleExport"
+              :loading="exportLoading"
+              v-hasPermi="['system:user:export']"
+            >
+              <Icon icon="ep:download" />导出
+            </el-button>
+          </el-form-item>
+        </el-form>
+      </ContentWrap>
+      <ContentWrap>
+        <el-table v-loading="loading" :data="list">
+          <el-table-column label="用户编号" align="center" key="id" prop="id" />
+          <el-table-column
+            label="用户名称"
+            align="center"
+            prop="username"
+            :show-overflow-tooltip="true"
+          />
+          <el-table-column
+            label="用户昵称"
+            align="center"
+            prop="nickname"
+            :show-overflow-tooltip="true"
+          />
+          <el-table-column
+            label="工作间"
+            align="center"
+            key="deptName"
+            prop="deptName"
+            :show-overflow-tooltip="true"
+          />
+          <el-table-column label="手机号码" align="center" prop="mobile" width="120" />
+          <el-table-column label="状态" key="status">
+            <template #default="scope">
+              <el-switch
+                v-model="scope.row.status"
+                :active-value="0"
+                :inactive-value="1"
+                @change="handleStatusChange(scope.row)"
+              />
+            </template>
+          </el-table-column>
+          <el-table-column
+            label="创建时间"
+            align="center"
+            prop="createTime"
+            :formatter="dateFormatter"
+            width="180"
+          />
+          <el-table-column label="操作" align="center" width="160">
+            <template #default="scope">
+              <div class="flex items-center justify-center">
+                <el-button
+                  type="primary"
+                  link
+                  @click="openForm('update', scope.row.id)"
+                  v-hasPermi="['system:user:update']"
+                >
+                  <Icon icon="ep:edit" />修改
+                </el-button>
+                <el-dropdown
+                  @command="(command) => handleCommand(command, scope.row)"
+                  v-hasPermi="[
+                    'system:user:delete',
+                    'system:user:update-password',
+                    'system:permission:assign-user-role'
+                  ]"
+                >
+                  <el-button type="primary" link><Icon icon="ep:d-arrow-right" /> 更多</el-button>
+                  <template #dropdown>
+                    <el-dropdown-menu>
+                      <el-dropdown-item
+                        command="handleDelete"
+                        v-if="checkPermi(['system:user:delete'])"
+                      >
+                        <Icon icon="ep:delete" />删除
+                      </el-dropdown-item>
+                      <el-dropdown-item
+                        command="handleResetPwd"
+                        v-if="checkPermi(['system:user:update-password'])"
+                      >
+                        <Icon icon="ep:key" />重置密码
+                      </el-dropdown-item>
+                      <el-dropdown-item
+                        command="handleRole"
+                        v-if="checkPermi(['system:permission:assign-user-role'])"
+                      >
+                        <Icon icon="ep:circle-check" />分配角色
+                      </el-dropdown-item>
+                    </el-dropdown-menu>
+                  </template>
+                </el-dropdown>
+              </div>
+            </template>
+          </el-table-column>
+        </el-table>
+        <Pagination
+          :total="total"
+          v-model:page="queryParams.pageNo"
+          v-model:limit="queryParams.pageSize"
+          @pagination="getList"
+        />
+      </ContentWrap>
+    </el-col>
+  </el-row>
+
+  <!-- 添加或修改用户对话框 -->
+  <UserForm ref="formRef" @success="getList" />
+  <!-- 用户导入对话框 -->
+  <UserImportForm ref="importFormRef" @success="getList" />
+  <!-- 分配角色 -->
+  <UserAssignRoleForm ref="assignRoleFormRef" @success="getList" />
+</template>
+<script lang="ts" setup>
+import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
+import { checkPermi } from '@/utils/permission'
+import { dateFormatter } from '@/utils/formatTime'
+import download from '@/utils/download'
+import { CommonStatusEnum } from '@/utils/constants'
+import * as UserApi from '@/api/system/user'
+import UserForm from './UserForm.vue'
+import UserImportForm from './UserImportForm.vue'
+import UserAssignRoleForm from './UserAssignRoleForm.vue'
+import DeptTree from './DeptTree.vue'
+
+defineOptions({ name: 'SystemUser' })
+
+const message = useMessage() // 消息弹窗
+const { t } = useI18n() // 国际化
+
+const loading = ref(true) // 列表的加载中
+const total = ref(0) // 列表的总页数
+const list = ref([]) // 列表的数
+const queryParams = reactive({
+  pageNo: 1,
+  pageSize: 10,
+  username: undefined,
+  mobile: undefined,
+  status: undefined,
+  deptId: undefined,
+  createTime: []
+})
+const queryFormRef = ref() // 搜索的表单
+
+/** 查询列表 */
+const getList = async () => {
+  loading.value = true
+  try {
+    const data = await UserApi.getStudentPage(queryParams)
+    list.value = data.list
+    total.value = data.total
+  } finally {
+    loading.value = false
+  }
+}
+
+/** 搜索按钮操作 */
+const handleQuery = () => {
+  queryParams.pageNo = 1
+  getList()
+}
+
+/** 重置按钮操作 */
+const resetQuery = () => {
+  queryFormRef.value?.resetFields()
+  handleQuery()
+}
+
+/** 处理部门被点击 */
+const handleDeptNodeClick = async (row) => {
+  queryParams.deptId = row.id
+  await getList()
+}
+
+/** 添加/修改操作 */
+const formRef = ref()
+const openForm = (type: string, id?: number) => {
+  formRef.value.open(type, id)
+}
+
+/** 用户导入 */
+const importFormRef = ref()
+const handleImport = () => {
+  importFormRef.value.open()
+}
+
+/** 修改用户状态 */
+const handleStatusChange = async (row: UserApi.UserVO) => {
+  try {
+    // 修改状态的二次确认
+    const text = row.status === CommonStatusEnum.ENABLE ? '启用' : '停用'
+    await message.confirm('确认要"' + text + '""' + row.username + '"用户吗?')
+    // 发起修改状态
+    await UserApi.updateUserStatus(row.id, row.status)
+    // 刷新列表
+    await getList()
+  } catch {
+    // 取消后,进行恢复按钮
+    row.status =
+      row.status === CommonStatusEnum.ENABLE ? CommonStatusEnum.DISABLE : CommonStatusEnum.ENABLE
+  }
+}
+
+/** 导出按钮操作 */
+const exportLoading = ref(false)
+const handleExport = async () => {
+  try {
+    // 导出的二次确认
+    await message.exportConfirm()
+    // 发起导出
+    exportLoading.value = true
+    const data = await UserApi.exportUser(queryParams)
+    download.excel(data, '用户数据.xls')
+  } catch {
+  } finally {
+    exportLoading.value = false
+  }
+}
+
+/** 操作分发 */
+const handleCommand = (command: string, row: UserApi.UserVO) => {
+  switch (command) {
+    case 'handleDelete':
+      handleDelete(row.id)
+      break
+    case 'handleResetPwd':
+      handleResetPwd(row)
+      break
+    case 'handleRole':
+      handleRole(row)
+      break
+    default:
+      break
+  }
+}
+
+/** 删除按钮操作 */
+const handleDelete = async (id: number) => {
+  try {
+    // 删除的二次确认
+    await message.delConfirm()
+    // 发起删除
+    await UserApi.deleteUser(id)
+    message.success(t('common.delSuccess'))
+    // 刷新列表
+    await getList()
+  } catch {}
+}
+
+/** 重置密码 */
+const handleResetPwd = async (row: UserApi.UserVO) => {
+  try {
+    // 重置的二次确认
+    const result = await message.prompt(
+      '请输入"' + row.username + '"的新密码',
+      t('common.reminder')
+    )
+    const password = result.value
+    // 发起重置
+    await UserApi.resetUserPwd(row.id, password)
+    message.success('修改成功,新密码是:' + password)
+  } catch {}
+}
+
+/** 分配角色 */
+const assignRoleFormRef = ref()
+const handleRole = (row: UserApi.UserVO) => {
+  assignRoleFormRef.value.open(row)
+}
+
+/** 初始化 */
+onMounted(() => {
+  getList()
+})
+</script>

+ 360 - 0
src/views/system/user/teacher.vue

@@ -0,0 +1,360 @@
+<template>
+
+  <el-row :gutter="20">
+    <!-- 左侧部门树 -->
+    <el-col :span="4" :xs="24">
+      <ContentWrap class="h-1/1">
+        <DeptTree @node-click="handleDeptNodeClick" />
+      </ContentWrap>
+    </el-col>
+    <el-col :span="20" :xs="24">
+      <!-- 搜索 -->
+      <ContentWrap>
+        <el-form
+          class="-mb-15px"
+          :model="queryParams"
+          ref="queryFormRef"
+          :inline="true"
+          label-width="68px"
+        >
+          <el-form-item label="用户名称" prop="username">
+            <el-input
+              v-model="queryParams.username"
+              placeholder="请输入用户名称"
+              clearable
+              @keyup.enter="handleQuery"
+              class="!w-240px"
+            />
+          </el-form-item>
+          <el-form-item label="手机号码" prop="mobile">
+            <el-input
+              v-model="queryParams.mobile"
+              placeholder="请输入手机号码"
+              clearable
+              @keyup.enter="handleQuery"
+              class="!w-240px"
+            />
+          </el-form-item>
+          <el-form-item label="状态" prop="status">
+            <el-select
+              v-model="queryParams.status"
+              placeholder="用户状态"
+              clearable
+              class="!w-240px"
+            >
+              <el-option
+                v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
+                :key="dict.value"
+                :label="dict.label"
+                :value="dict.value"
+              />
+            </el-select>
+          </el-form-item>
+          <el-form-item label="创建时间" prop="createTime">
+            <el-date-picker
+              v-model="queryParams.createTime"
+              value-format="YYYY-MM-DD HH:mm:ss"
+              type="datetimerange"
+              start-placeholder="开始日期"
+              end-placeholder="结束日期"
+              class="!w-240px"
+            />
+          </el-form-item>
+          <el-form-item>
+            <el-button @click="handleQuery"><Icon icon="ep:search" />搜索</el-button>
+            <el-button @click="resetQuery"><Icon icon="ep:refresh" />重置</el-button>
+            <!-- 暂时不在这新增 -->
+            <!-- <el-button
+              type="primary"
+              plain
+              @click="openForm('create')"
+              v-hasPermi="['system:user:create']"
+            >
+              <Icon icon="ep:plus" /> 新增
+            </el-button> -->
+            <el-button
+              type="warning"
+              plain
+              @click="handleImport"
+              v-hasPermi="['system:user:import']"
+            >
+              <Icon icon="ep:upload" /> 导入
+            </el-button>
+            <el-button
+              type="success"
+              plain
+              @click="handleExport"
+              :loading="exportLoading"
+              v-hasPermi="['system:user:export']"
+            >
+              <Icon icon="ep:download" />导出
+            </el-button>
+          </el-form-item>
+        </el-form>
+      </ContentWrap>
+      <ContentWrap>
+        <el-table v-loading="loading" :data="list">
+          <el-table-column label="用户编号" align="center" key="id" prop="id" />
+          <el-table-column
+            label="用户名称"
+            align="center"
+            prop="username"
+            :show-overflow-tooltip="true"
+          />
+          <el-table-column
+            label="用户昵称"
+            align="center"
+            prop="nickname"
+            :show-overflow-tooltip="true"
+          />
+          <el-table-column
+            label="工作间"
+            align="center"
+            key="deptName"
+            prop="deptName"
+            :show-overflow-tooltip="true"
+          />
+          <el-table-column label="手机号码" align="center" prop="mobile" width="120" />
+          <el-table-column label="状态" key="status">
+            <template #default="scope">
+              <el-switch
+                v-model="scope.row.status"
+                :active-value="0"
+                :inactive-value="1"
+                @change="handleStatusChange(scope.row)"
+              />
+            </template>
+          </el-table-column>
+          <el-table-column
+            label="创建时间"
+            align="center"
+            prop="createTime"
+            :formatter="dateFormatter"
+            width="180"
+          />
+          <el-table-column label="操作" align="center" width="160">
+            <template #default="scope">
+              <div class="flex items-center justify-center">
+                <el-button
+                  type="primary"
+                  link
+                  @click="openForm('update', scope.row.id)"
+                  v-hasPermi="['system:user:update']"
+                >
+                  <Icon icon="ep:edit" />修改
+                </el-button>
+                <el-dropdown
+                  @command="(command) => handleCommand(command, scope.row)"
+                  v-hasPermi="[
+                    'system:user:delete',
+                    'system:user:update-password',
+                    'system:permission:assign-user-role'
+                  ]"
+                >
+                  <el-button type="primary" link><Icon icon="ep:d-arrow-right" /> 更多</el-button>
+                  <template #dropdown>
+                    <el-dropdown-menu>
+                      <el-dropdown-item
+                        command="handleDelete"
+                        v-if="checkPermi(['system:user:delete'])"
+                      >
+                        <Icon icon="ep:delete" />删除
+                      </el-dropdown-item>
+                      <el-dropdown-item
+                        command="handleResetPwd"
+                        v-if="checkPermi(['system:user:update-password'])"
+                      >
+                        <Icon icon="ep:key" />重置密码
+                      </el-dropdown-item>
+                      <el-dropdown-item
+                        command="handleRole"
+                        v-if="checkPermi(['system:permission:assign-user-role'])"
+                      >
+                        <Icon icon="ep:circle-check" />分配角色
+                      </el-dropdown-item>
+                    </el-dropdown-menu>
+                  </template>
+                </el-dropdown>
+              </div>
+            </template>
+          </el-table-column>
+        </el-table>
+        <Pagination
+          :total="total"
+          v-model:page="queryParams.pageNo"
+          v-model:limit="queryParams.pageSize"
+          @pagination="getList"
+        />
+      </ContentWrap>
+    </el-col>
+  </el-row>
+
+  <!-- 添加或修改用户对话框 -->
+  <UserForm ref="formRef" @success="getList" />
+  <!-- 用户导入对话框 -->
+  <UserImportForm ref="importFormRef" @success="getList" />
+  <!-- 分配角色 -->
+  <UserAssignRoleForm ref="assignRoleFormRef" @success="getList" />
+</template>
+<script lang="ts" setup>
+import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
+import { checkPermi } from '@/utils/permission'
+import { dateFormatter } from '@/utils/formatTime'
+import download from '@/utils/download'
+import { CommonStatusEnum } from '@/utils/constants'
+import * as UserApi from '@/api/system/user'
+import UserForm from './UserForm.vue'
+import UserImportForm from './UserImportForm.vue'
+import UserAssignRoleForm from './UserAssignRoleForm.vue'
+import DeptTree from './DeptTree.vue'
+
+defineOptions({ name: 'SystemUser' })
+
+const message = useMessage() // 消息弹窗
+const { t } = useI18n() // 国际化
+
+const loading = ref(true) // 列表的加载中
+const total = ref(0) // 列表的总页数
+const list = ref([]) // 列表的数
+const queryParams = reactive({
+  pageNo: 1,
+  pageSize: 10,
+  username: undefined,
+  mobile: undefined,
+  status: undefined,
+  deptId: undefined,
+  createTime: []
+})
+const queryFormRef = ref() // 搜索的表单
+
+/** 查询列表 */
+const getList = async () => {
+  loading.value = true
+  try {
+    const data = await UserApi.getTeacherPage(queryParams)
+    list.value = data.list
+    total.value = data.total
+  } finally {
+    loading.value = false
+  }
+}
+
+/** 搜索按钮操作 */
+const handleQuery = () => {
+  queryParams.pageNo = 1
+  getList()
+}
+
+/** 重置按钮操作 */
+const resetQuery = () => {
+  queryFormRef.value?.resetFields()
+  handleQuery()
+}
+
+/** 处理部门被点击 */
+const handleDeptNodeClick = async (row) => {
+  queryParams.deptId = row.id
+  await getList()
+}
+
+/** 添加/修改操作 */
+const formRef = ref()
+const openForm = (type: string, id?: number) => {
+  formRef.value.open(type, id)
+}
+
+/** 用户导入 */
+const importFormRef = ref()
+const handleImport = () => {
+  importFormRef.value.open()
+}
+
+/** 修改用户状态 */
+const handleStatusChange = async (row: UserApi.UserVO) => {
+  try {
+    // 修改状态的二次确认
+    const text = row.status === CommonStatusEnum.ENABLE ? '启用' : '停用'
+    await message.confirm('确认要"' + text + '""' + row.username + '"用户吗?')
+    // 发起修改状态
+    await UserApi.updateUserStatus(row.id, row.status)
+    // 刷新列表
+    await getList()
+  } catch {
+    // 取消后,进行恢复按钮
+    row.status =
+      row.status === CommonStatusEnum.ENABLE ? CommonStatusEnum.DISABLE : CommonStatusEnum.ENABLE
+  }
+}
+
+/** 导出按钮操作 */
+const exportLoading = ref(false)
+const handleExport = async () => {
+  try {
+    // 导出的二次确认
+    await message.exportConfirm()
+    // 发起导出
+    exportLoading.value = true
+    const data = await UserApi.exportUser(queryParams)
+    download.excel(data, '用户数据.xls')
+  } catch {
+  } finally {
+    exportLoading.value = false
+  }
+}
+
+/** 操作分发 */
+const handleCommand = (command: string, row: UserApi.UserVO) => {
+  switch (command) {
+    case 'handleDelete':
+      handleDelete(row.id)
+      break
+    case 'handleResetPwd':
+      handleResetPwd(row)
+      break
+    case 'handleRole':
+      handleRole(row)
+      break
+    default:
+      break
+  }
+}
+
+/** 删除按钮操作 */
+const handleDelete = async (id: number) => {
+  try {
+    // 删除的二次确认
+    await message.delConfirm()
+    // 发起删除
+    await UserApi.deleteUser(id)
+    message.success(t('common.delSuccess'))
+    // 刷新列表
+    await getList()
+  } catch {}
+}
+
+/** 重置密码 */
+const handleResetPwd = async (row: UserApi.UserVO) => {
+  try {
+    // 重置的二次确认
+    const result = await message.prompt(
+      '请输入"' + row.username + '"的新密码',
+      t('common.reminder')
+    )
+    const password = result.value
+    // 发起重置
+    await UserApi.resetUserPwd(row.id, password)
+    message.success('修改成功,新密码是:' + password)
+  } catch {}
+}
+
+/** 分配角色 */
+const assignRoleFormRef = ref()
+const handleRole = (row: UserApi.UserVO) => {
+  assignRoleFormRef.value.open(row)
+}
+
+/** 初始化 */
+onMounted(() => {
+  getList()
+})
+</script>

+ 174 - 0
src/views/system/workroomCollege/dept/DeptForm.vue

@@ -0,0 +1,174 @@
+<template>
+  <Dialog v-model="dialogVisible" :title="dialogTitle">
+    <el-form
+      ref="formRef"
+      v-loading="formLoading"
+      :model="formData"
+      :rules="formRules"
+      label-width="80px"
+    >
+      <el-form-item label="上级部门" prop="parentId">
+        <el-tree-select
+          v-model="formData.parentId"
+          :data="deptTree"
+          :props="defaultProps"
+          check-strictly
+          default-expand-all
+          placeholder="请选择上级部门"
+          value-key="deptId"
+        />
+      </el-form-item>
+      <el-form-item label="工作间名称" prop="name">
+        <el-input v-model="formData.name" placeholder="请输入工作间名称" />
+      </el-form-item>
+      <el-form-item label="显示排序" prop="sort">
+        <el-input-number v-model="formData.sort" :min="0" controls-position="right" />
+      </el-form-item>
+      <el-form-item label="负责人" prop="leaderUserId">
+        <el-select v-model="formData.leaderUserId" clearable placeholder="请输入负责人">
+          <el-option
+            v-for="item in userList"
+            :key="item.id"
+            :label="item.nickname"
+            :value="item.id"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="联系电话" prop="phone">
+        <el-input v-model="formData.phone" maxlength="11" placeholder="请输入联系电话" />
+      </el-form-item>
+      <el-form-item label="邮箱" prop="email">
+        <el-input v-model="formData.email" maxlength="50" placeholder="请输入邮箱" />
+      </el-form-item>
+      <el-form-item label="状态" prop="status">
+        <el-select v-model="formData.status" clearable placeholder="请选择状态">
+          <el-option
+            v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
+            :key="dict.value"
+            :label="dict.label"
+            :value="dict.value"
+          />
+        </el-select>
+      </el-form-item>
+    </el-form>
+    <template #footer>
+      <el-button type="primary" @click="submitForm">确 定</el-button>
+      <el-button @click="dialogVisible = false">取 消</el-button>
+    </template>
+  </Dialog>
+</template>
+<script lang="ts" setup>
+import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
+import { defaultProps, handleTree } from '@/utils/tree'
+import * as DeptApi from '@/api/system/dept'
+import * as UserApi from '@/api/system/user'
+import { CommonStatusEnum } from '@/utils/constants'
+import { FormRules } from 'element-plus'
+
+defineOptions({ name: 'SystemDeptForm' })
+
+const { t } = useI18n() // 国际化
+const message = useMessage() // 消息弹窗
+
+const dialogVisible = ref(false) // 弹窗的是否展示
+const dialogTitle = ref('') // 弹窗的标题
+const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
+const formType = ref('') // 表单的类型:create - 新增;update - 修改
+const formData = ref({
+  id: undefined,
+  title: '',
+  parentId: undefined,
+  name: undefined,
+  sort: undefined,
+  leaderUserId: undefined,
+  phone: undefined,
+  email: undefined,
+  status: CommonStatusEnum.ENABLE
+})
+const formRules = reactive<FormRules>({
+  parentId: [{ required: true, message: '上级部门不能为空', trigger: 'blur' }],
+  name: [{ required: true, message: '工作间名称不能为空', trigger: 'blur' }],
+  sort: [{ required: true, message: '显示排序不能为空', trigger: 'blur' }],
+  email: [{ type: 'email', message: '请输入正确的邮箱地址', trigger: ['blur', 'change'] }],
+  phone: [
+    { pattern: /^1[3|4|5|6|7|8|9][0-9]\d{8}$/, message: '请输入正确的手机号码', trigger: 'blur' }
+  ],
+  status: [{ required: true, message: '状态不能为空', trigger: 'blur' }]
+})
+const formRef = ref() // 表单 Ref
+const deptTree = ref() // 树形结构
+const userList = ref<UserApi.UserVO[]>([]) // 用户列表
+
+/** 打开弹窗 */
+const open = async (type: string, id?: number) => {
+  dialogVisible.value = true
+  dialogTitle.value = t('action.' + type)
+  formType.value = type
+  resetForm()
+  // 修改时,设置数据
+  if (id) {
+    formLoading.value = true
+    try {
+      formData.value = await DeptApi.getDept(id)
+    } finally {
+      formLoading.value = false
+    }
+  }
+  // 获得用户列表
+  userList.value = await UserApi.getSimpleUserList()
+  // 获得部门树
+  await getTree()
+}
+defineExpose({ open }) // 提供 open 方法,用于打开弹窗
+
+/** 提交表单 */
+const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
+const submitForm = async () => {
+  // 校验表单
+  if (!formRef) return
+  const valid = await formRef.value.validate()
+  if (!valid) return
+  // 提交请求
+  formLoading.value = true
+  try {
+    const data = formData.value as unknown as DeptApi.DeptVO
+    if (formType.value === 'create') {
+      await DeptApi.createDept(data)
+      message.success(t('common.createSuccess'))
+    } else {
+      await DeptApi.updateDept(data)
+      message.success(t('common.updateSuccess'))
+    }
+    dialogVisible.value = false
+    // 发送操作成功的事件
+    emit('success')
+  } finally {
+    formLoading.value = false
+  }
+}
+
+/** 重置表单 */
+const resetForm = () => {
+  formData.value = {
+    id: undefined,
+    title: '',
+    parentId: undefined,
+    name: undefined,
+    sort: undefined,
+    leaderUserId: undefined,
+    phone: undefined,
+    email: undefined,
+    status: CommonStatusEnum.ENABLE
+  }
+  formRef.value?.resetFields()
+}
+
+/** 获得部门树 */
+const getTree = async () => {
+  deptTree.value = []
+  const data = await DeptApi.getSimpleDeptList()
+  let dept: Tree = { id: 0, name: '顶级部门', children: [] }
+  dept.children = handleTree(data)
+  deptTree.value.push(dept)
+}
+</script>

+ 164 - 0
src/views/system/workroomCollege/dept/index.vue

@@ -0,0 +1,164 @@
+<template>
+  <!-- 搜索工作栏 -->
+  <ContentWrap>
+    <el-form
+      class="-mb-15px"
+      :model="queryParams"
+      ref="queryFormRef"
+      :inline="true"
+      label-width="68px"
+    >
+      <el-form-item label="工作间" prop="name">
+        <el-input
+          v-model="queryParams.name"
+          placeholder="请输入工作间名称"
+          clearable
+          @keyup.enter="handleQuery"
+          class="!w-240px"
+        />
+      </el-form-item>
+      <el-form-item label="状态" prop="status">
+        <el-select
+          v-model="queryParams.status"
+          placeholder="请选择工作间状态"
+          clearable
+          class="!w-240px"
+        >
+          <el-option
+            v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
+            :key="dict.value"
+            :label="dict.label"
+            :value="dict.value"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item>
+        <el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
+        <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
+       
+        <el-button type="danger" plain @click="toggleExpandAll">
+          <Icon icon="ep:sort" class="mr-5px" /> 展开/折叠
+        </el-button>
+      </el-form-item>
+    </el-form>
+  </ContentWrap>
+
+  <!-- 列表 -->
+  <ContentWrap>
+    <el-table
+      v-loading="loading"
+      :data="list"
+      row-key="id"
+      :default-expand-all="isExpandAll"
+      v-if="refreshTable"
+    >
+      <el-table-column prop="name" label="工作间名称" />
+      <el-table-column prop="leader" label="负责人">
+        <template #default="scope">
+          {{ userList.find((user) => user.id === scope.row.leaderUserId)?.nickname }}
+        </template>
+      </el-table-column>
+      <el-table-column prop="sort" label="排序" />
+      <el-table-column prop="status" label="状态">
+        <template #default="scope">
+          <dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="scope.row.status" />
+        </template>
+      </el-table-column>
+      <el-table-column
+        label="创建时间"
+        align="center"
+        prop="createTime"
+        width="180"
+        :formatter="dateFormatter"
+      />
+      <el-table-column label="操作" align="center">
+        <template #default="scope">
+          <el-button
+            link
+            type="primary"
+            @click="openForm('update', scope.row.id)"
+            v-hasPermi="['system:dept:update']"
+          >
+            修改
+          </el-button>
+        
+        </template>
+      </el-table-column>
+    </el-table>
+  </ContentWrap>
+
+  <!-- 表单弹窗:添加/修改 -->
+  <DeptForm ref="formRef" @success="getList" />
+</template>
+<script lang="ts" setup>
+import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
+import { dateFormatter } from '@/utils/formatTime'
+import { handleTree } from '@/utils/tree'
+import * as DeptApi from '@/api/system/dept'
+import DeptForm from './DeptForm.vue'
+import * as UserApi from '@/api/system/user'
+
+defineOptions({ name: 'SystemDept' })
+
+const message = useMessage() // 消息弹窗
+const { t } = useI18n() // 国际化
+
+const loading = ref(true) // 列表的加载中
+const list = ref() // 列表的数据
+const queryParams = reactive({
+  pageNo: 1,
+  pageSize: 100,
+  name: undefined,
+  status: undefined
+})
+const queryFormRef = ref() // 搜索的表单
+const isExpandAll = ref(true) // 是否展开,默认全部展开
+const refreshTable = ref(true) // 重新渲染表格状态
+const userList = ref<UserApi.UserVO[]>([]) // 用户列表
+
+/** 查询部门列表 */
+const getList = async () => {
+  loading.value = true
+  try {
+    const data = await DeptApi.getDeptPage(queryParams)
+    list.value = handleTree(data)
+  } finally {
+    loading.value = false
+  }
+}
+
+/** 展开/折叠操作 */
+const toggleExpandAll = () => {
+  refreshTable.value = false
+  isExpandAll.value = !isExpandAll.value
+  nextTick(() => {
+    refreshTable.value = true
+  })
+}
+
+/** 搜索按钮操作 */
+const handleQuery = () => {
+  getList()
+}
+
+/** 重置按钮操作 */
+const resetQuery = () => {
+  queryParams.pageNo = 1
+  queryFormRef.value.resetFields()
+  handleQuery()
+}
+
+/** 添加/修改操作 */
+const formRef = ref()
+const openForm = (type: string, id?: number) => {
+  formRef.value.open(type, id)
+}
+
+
+/** 初始化 **/
+onMounted(async () => {
+  await getList()
+  // 获取用户列表
+  userList.value = await UserApi.getSimpleUserList()
+})
+</script>

+ 63 - 0
src/views/system/workroomCollege/user/DeptTree.vue

@@ -0,0 +1,63 @@
+<template>
+  <div class="head-container">
+    <el-input v-model="deptName" class="mb-20px" clearable placeholder="请输入工作间名称">
+      <template #prefix>
+        <Icon icon="ep:search" />
+      </template>
+    </el-input>
+  </div>
+  <div class="head-container">
+    <el-tree
+      ref="treeRef"
+      :data="deptList"
+      :expand-on-click-node="false"
+      :filter-node-method="filterNode"
+      :props="defaultProps"
+      default-expand-all
+      highlight-current
+      node-key="id"
+      @node-click="handleNodeClick"
+    />
+  </div>
+</template>
+
+<script lang="ts" setup>
+import { ElTree } from 'element-plus'
+import * as DeptApi from '@/api/system/dept'
+import { defaultProps, handleTree } from '@/utils/tree'
+
+defineOptions({ name: 'SystemUserDeptTree' })
+
+const deptName = ref('')
+const deptList = ref<Tree[]>([]) // 树形结构
+const treeRef = ref<InstanceType<typeof ElTree>>()
+
+/** 获得部门树 */
+const getTree = async () => {
+  const res = await DeptApi.getSimpleDeptList()
+  deptList.value = []
+  deptList.value.push(...handleTree(res))
+}
+
+/** 基于名字过滤 */
+const filterNode = (name: string, data: Tree) => {
+  if (!name) return true
+  return data.name.includes(name)
+}
+
+/** 处理部门被点击 */
+const handleNodeClick = async (row: { [key: string]: any }) => {
+  emits('node-click', row)
+}
+const emits = defineEmits(['node-click'])
+
+/** 监听deptName */
+watch(deptName, (val) => {
+  treeRef.value!.filter(val)
+})
+
+/** 初始化 */
+onMounted(async () => {
+  await getTree()
+})
+</script>

+ 96 - 0
src/views/system/workroomCollege/user/UserAssignRoleForm.vue

@@ -0,0 +1,96 @@
+<template>
+  <Dialog v-model="dialogVisible" title="分配角色">
+    <el-form ref="formRef" v-loading="formLoading" :model="formData" label-width="80px">
+      <el-form-item label="用户名称">
+        <el-input v-model="formData.username" :disabled="true" />
+      </el-form-item>
+      <el-form-item label="用户昵称">
+        <el-input v-model="formData.nickname" :disabled="true" />
+      </el-form-item>
+      <el-form-item label="角色">
+        <el-select v-model="formData.roleIds" multiple placeholder="请选择角色">
+          <el-option v-for="item in roleList" :key="item.id" :label="item.name" :value="item.id" />
+        </el-select>
+      </el-form-item>
+    </el-form>
+    <template #footer>
+      <el-button :disabled="formLoading" type="primary" @click="submitForm">确 定</el-button>
+      <el-button @click="dialogVisible = false">取 消</el-button>
+    </template>
+  </Dialog>
+</template>
+<script lang="ts" setup>
+import * as PermissionApi from '@/api/system/permission'
+import * as UserApi from '@/api/system/user'
+import * as RoleApi from '@/api/system/role'
+
+defineOptions({ name: 'SystemUserAssignRoleForm' })
+
+const { t } = useI18n() // 国际化
+const message = useMessage() // 消息弹窗
+
+const dialogVisible = ref(false) // 弹窗的是否展示
+const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
+const formData = ref({
+  id: -1,
+  nickname: '',
+  username: '',
+  roleIds: []
+})
+const formRef = ref() // 表单 Ref
+const roleList = ref([] as RoleApi.RoleVO[]) // 角色的列表
+
+/** 打开弹窗 */
+const open = async (row: UserApi.UserVO) => {
+  dialogVisible.value = true
+  resetForm()
+  // 设置数据
+  formData.value.id = row.id
+  formData.value.username = row.username
+  formData.value.nickname = row.nickname
+  // 获得角色拥有的菜单集合
+  formLoading.value = true
+  try {
+    formData.value.roleIds = await PermissionApi.getUserRoleList(row.id)
+  } finally {
+    formLoading.value = false
+  }
+  // 获得角色列表
+  roleList.value = await RoleApi.getSimpleRoleList()
+}
+defineExpose({ open }) // 提供 open 方法,用于打开弹窗
+
+/** 提交表单 */
+const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
+const submitForm = async () => {
+  // 校验表单
+  if (!formRef) return
+  const valid = await formRef.value.validate()
+  if (!valid) return
+  // 提交请求
+  formLoading.value = true
+  try {
+    await PermissionApi.assignUserRole({
+      userId: formData.value.id,
+      roleIds: formData.value.roleIds
+    })
+    message.success(t('common.updateSuccess'))
+    dialogVisible.value = false
+    // 发送操作成功的事件
+    emit('success', true)
+  } finally {
+    formLoading.value = false
+  }
+}
+
+/** 重置表单 */
+const resetForm = () => {
+  formData.value = {
+    id: -1,
+    nickname: '',
+    username: '',
+    roleIds: []
+  }
+  formRef.value?.resetFields()
+}
+</script>

+ 243 - 0
src/views/system/workroomCollege/user/UserForm.vue

@@ -0,0 +1,243 @@
+<template>
+  <Dialog v-model="dialogVisible" :title="dialogTitle">
+    <el-form
+      ref="formRef"
+      v-loading="formLoading"
+      :model="formData"
+      :rules="formRules"
+      label-width="80px"
+    >
+      <el-row>
+        <el-col :span="12">
+          <el-form-item label="用户昵称" prop="nickname">
+            <el-input v-model="formData.nickname" placeholder="请输入用户昵称" />
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item label="工作间" prop="deptId">
+            <el-tree-select
+              v-model="formData.deptId"
+              :data="deptList"
+              :props="defaultProps"
+              check-strictly
+              node-key="id"
+              placeholder="请选择归属工作间"
+            />
+          </el-form-item>
+        </el-col>
+      </el-row>
+      <el-row>
+        <el-col :span="12">
+          <el-form-item label="手机号码" prop="mobile">
+            <el-input v-model="formData.mobile" maxlength="11" placeholder="请输入手机号码" />
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item label="邮箱" prop="email">
+            <el-input v-model="formData.email" maxlength="50" placeholder="请输入邮箱" />
+          </el-form-item>
+        </el-col>
+      </el-row>
+      <el-row>
+        <el-col :span="12">
+          <el-form-item v-if="formData.id === undefined" label="用户名称" prop="username">
+            <el-input v-model="formData.username" placeholder="请输入用户名称" />
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item v-if="formData.id === undefined" label="用户密码" prop="password">
+            <el-input
+              v-model="formData.password"
+              placeholder="请输入用户密码"
+              show-password
+              type="password"
+            />
+          </el-form-item>
+        </el-col>
+      </el-row>
+      <el-row>
+        <el-col :span="12">
+          <el-form-item label="用户性别">
+            <el-select v-model="formData.sex" placeholder="请选择">
+              <el-option
+                v-for="dict in getIntDictOptions(DICT_TYPE.SYSTEM_USER_SEX)"
+                :key="dict.value"
+                :label="dict.label"
+                :value="dict.value"
+              />
+            </el-select>
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item label="岗位">
+            <el-select v-model="formData.postIds" multiple placeholder="请选择">
+              <el-option
+                v-for="item in postList"
+                :key="item.id"
+                :label="item.name"
+                :value="item.id!"
+              />
+            </el-select>
+          </el-form-item>
+        </el-col>
+      </el-row>
+
+      <el-row v-if="formType === 'update'">
+        <el-col :span="12">
+          <el-form-item label="学生类型" prop="userType">
+            <el-select v-model="formData.userType" placeholder="请选择用户类型">
+              <el-option
+                v-for="option in userTypes"
+                :key="option.value"
+                :label="option.label"
+                :value="option.value"
+                />
+            </el-select>
+          </el-form-item>
+        </el-col>
+      </el-row>
+
+      <el-row>
+        <el-col :span="24">
+          <el-form-item label="备注">
+            <el-input v-model="formData.remark" placeholder="请输入内容" type="textarea" />
+          </el-form-item>
+        </el-col>
+      </el-row>
+    </el-form>
+    <template #footer>
+      <el-button :disabled="formLoading" type="primary" @click="submitForm">确 定</el-button>
+      <el-button @click="dialogVisible = false">取 消</el-button>
+    </template>
+  </Dialog>
+</template>
+<script lang="ts" setup>
+import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
+import { CommonStatusEnum } from '@/utils/constants'
+import { defaultProps, handleTree } from '@/utils/tree'
+import * as PostApi from '@/api/system/post'
+import * as DeptApi from '@/api/system/dept'
+import * as UserApi from '@/api/system/user'
+import { FormRules } from 'element-plus'
+
+defineOptions({ name: 'SystemUserForm' })
+
+const { t } = useI18n() // 国际化
+const message = useMessage() // 消息弹窗
+
+const dialogVisible = ref(false) // 弹窗的是否展示
+const dialogTitle = ref('') // 弹窗的标题
+const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
+const formType = ref('') // 表单的类型:create - 新增;update - 修改
+const formData = ref({
+  nickname: '',
+  deptId: '',
+  mobile: '',
+  email: '',
+  id: undefined,
+  username: '',
+  password: '',
+  sex: undefined,
+  postIds: [],
+  remark: '',
+  status: CommonStatusEnum.ENABLE,
+  roleIds: [],
+  userType: undefined
+})
+const formRules = reactive<FormRules>({
+  username: [{ required: true, message: '用户名称不能为空', trigger: 'blur' }],
+  nickname: [{ required: true, message: '用户昵称不能为空', trigger: 'blur' }],
+  password: [{ required: true, message: '用户密码不能为空', trigger: 'blur' }],
+  email: [
+    {
+      type: 'email',
+      message: '请输入正确的邮箱地址',
+      trigger: ['blur', 'change']
+    }
+  ],
+  mobile: [
+    {
+      pattern: /^(?:(?:\+|00)86)?1(?:3[\d]|4[5-79]|5[0-35-9]|6[5-7]|7[0-8]|8[\d]|9[189])\d{8}$/,
+      message: '请输入正确的手机号码',
+      trigger: 'blur'
+    }
+  ]
+})
+const formRef = ref() // 表单 Ref
+const deptList = ref<Tree[]>([]) // 树形结构
+const postList = ref([] as PostApi.PostVO[]) // 岗位列表
+
+//用户类型
+const userTypes = ref([
+  { label: '在校生', value: "1" },
+  { label: '毕业生', value: "2" },
+  { label: '导师', value: "3" },
+]);
+/** 打开弹窗 */
+const open = async (type: string, id?: number) => {
+  dialogVisible.value = true
+  dialogTitle.value = t('action.' + type)
+  formType.value = type
+  resetForm()
+  // 修改时,设置数据
+  if (id) {
+    formLoading.value = true
+    try {
+      formData.value = await UserApi.getUser(id)
+    } finally {
+      formLoading.value = false
+    }
+  }
+  // 加载部门树
+  deptList.value = handleTree(await DeptApi.getSimpleDeptList())
+  // 加载岗位列表
+  postList.value = await PostApi.getSimplePostList()
+}
+defineExpose({ open }) // 提供 open 方法,用于打开弹窗
+
+/** 提交表单 */
+const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
+const submitForm = async () => {
+  // 校验表单
+  if (!formRef) return
+  const valid = await formRef.value.validate()
+  if (!valid) return
+  // 提交请求
+  formLoading.value = true
+  try {
+    const data = formData.value as unknown as UserApi.UserVO
+    if (formType.value === 'create') {
+      await UserApi.createUser(data)
+      message.success(t('common.createSuccess'))
+    } else {
+      await UserApi.updateUser(data)
+      message.success(t('common.updateSuccess'))
+    }
+    dialogVisible.value = false
+    // 发送操作成功的事件
+    emit('success')
+  } finally {
+    formLoading.value = false
+  }
+}
+
+/** 重置表单 */
+const resetForm = () => {
+  formData.value = {
+    nickname: '',
+    deptId: '',
+    mobile: '',
+    email: '',
+    id: undefined,
+    username: '',
+    password: '',
+    sex: undefined,
+    postIds: [],
+    remark: '',
+    status: CommonStatusEnum.ENABLE,
+    roleIds: [],
+    userType: undefined
+  }
+  formRef.value?.resetFields()
+}
+</script>

+ 138 - 0
src/views/system/workroomCollege/user/UserImportForm.vue

@@ -0,0 +1,138 @@
+<template>
+  <Dialog v-model="dialogVisible" title="用户导入" width="400">
+    <el-upload
+      ref="uploadRef"
+      v-model:file-list="fileList"
+      :action="importUrl + '?updateSupport=' + updateSupport"
+      :auto-upload="false"
+      :disabled="formLoading"
+      :headers="uploadHeaders"
+      :limit="1"
+      :on-error="submitFormError"
+      :on-exceed="handleExceed"
+      :on-success="submitFormSuccess"
+      accept=".xlsx, .xls"
+      drag
+    >
+      <Icon icon="ep:upload" />
+      <div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
+      <template #tip>
+        <div class="el-upload__tip text-center">
+          <div class="el-upload__tip">
+            <el-checkbox v-model="updateSupport" />
+            是否更新已经存在的用户数据
+          </div>
+          <span>仅允许导入 xls、xlsx 格式文件。</span>
+          <el-link
+            :underline="false"
+            style="font-size: 12px; vertical-align: baseline"
+            type="primary"
+            @click="importTemplate"
+          >
+            下载模板
+          </el-link>
+        </div>
+      </template>
+    </el-upload>
+    <template #footer>
+      <el-button :disabled="formLoading" type="primary" @click="submitForm">确 定</el-button>
+      <el-button @click="dialogVisible = false">取 消</el-button>
+    </template>
+  </Dialog>
+</template>
+<script lang="ts" setup>
+import * as UserApi from '@/api/system/user'
+import { getAccessToken, getTenantId } from '@/utils/auth'
+import download from '@/utils/download'
+
+defineOptions({ name: 'SystemUserImportForm' })
+
+const message = useMessage() // 消息弹窗
+
+const dialogVisible = ref(false) // 弹窗的是否展示
+const formLoading = ref(false) // 表单的加载中
+const uploadRef = ref()
+const importUrl =
+  import.meta.env.VITE_BASE_URL + import.meta.env.VITE_API_URL + '/system/user/import'
+const uploadHeaders = ref() // 上传 Header 头
+const fileList = ref([]) // 文件列表
+const updateSupport = ref(0) // 是否更新已经存在的用户数据
+
+/** 打开弹窗 */
+const open = () => {
+  dialogVisible.value = true
+  updateSupport.value = 0
+  fileList.value = []
+  resetForm()
+}
+defineExpose({ open }) // 提供 open 方法,用于打开弹窗
+
+/** 提交表单 */
+const submitForm = async () => {
+  if (fileList.value.length == 0) {
+    message.error('请上传文件')
+    return
+  }
+  // 提交请求
+  uploadHeaders.value = {
+    Authorization: 'Bearer ' + getAccessToken(),
+    'tenant-id': getTenantId()
+  }
+  formLoading.value = true
+  uploadRef.value!.submit()
+}
+
+/** 文件上传成功 */
+const emits = defineEmits(['success'])
+const submitFormSuccess = (response: any) => {
+  if (response.code !== 0) {
+    message.error(response.msg)
+    formLoading.value = false
+    return
+  }
+  // 拼接提示语
+  const data = response.data
+  let text = '上传成功数量:' + data.createUsernames.length + ';'
+  for (let username of data.createUsernames) {
+    text += '< ' + username + ' >'
+  }
+  text += '更新成功数量:' + data.updateUsernames.length + ';'
+  for (const username of data.updateUsernames) {
+    text += '< ' + username + ' >'
+  }
+  text += '更新失败数量:' + Object.keys(data.failureUsernames).length + ';'
+  for (const username in data.failureUsernames) {
+    text += '< ' + username + ': ' + data.failureUsernames[username] + ' >'
+  }
+  message.alert(text)
+  formLoading.value = false
+  dialogVisible.value = false
+  // 发送操作成功的事件
+  emits('success')
+}
+
+/** 上传错误提示 */
+const submitFormError = (): void => {
+  message.error('上传失败,请您重新上传!')
+  formLoading.value = false
+}
+
+/** 重置表单 */
+const resetForm = async (): Promise<void> => {
+  // 重置上传状态和文件
+  formLoading.value = false
+  await nextTick()
+  uploadRef.value?.clearFiles()
+}
+
+/** 文件数超出提示 */
+const handleExceed = (): void => {
+  message.error('最多只能上传一个文件!')
+}
+
+/** 下载模板操作 */
+const importTemplate = async () => {
+  const res = await UserApi.importUserTemplate()
+  download.excel(res, '用户导入模版.xls')
+}
+</script>

+ 362 - 0
src/views/system/workroomCollege/user/index.vue

@@ -0,0 +1,362 @@
+<template>
+  <doc-alert title="用户体系" url="https://doc.iocoder.cn/user-center/" />
+  <doc-alert title="三方登陆" url="https://doc.iocoder.cn/social-user/" />
+  <doc-alert title="Excel 导入导出" url="https://doc.iocoder.cn/excel-import-and-export/" />
+
+  <el-row :gutter="20">
+    <!-- 左侧部门树 -->
+    <el-col :span="4" :xs="24">
+      <ContentWrap class="h-1/1">
+        <DeptTree @node-click="handleDeptNodeClick" />
+      </ContentWrap>
+    </el-col>
+    <el-col :span="20" :xs="24">
+      <!-- 搜索 -->
+      <ContentWrap>
+        <el-form
+          class="-mb-15px"
+          :model="queryParams"
+          ref="queryFormRef"
+          :inline="true"
+          label-width="68px"
+        >
+          <el-form-item label="用户名称" prop="username">
+            <el-input
+              v-model="queryParams.username"
+              placeholder="请输入用户名称"
+              clearable
+              @keyup.enter="handleQuery"
+              class="!w-240px"
+            />
+          </el-form-item>
+          <el-form-item label="手机号码" prop="mobile">
+            <el-input
+              v-model="queryParams.mobile"
+              placeholder="请输入手机号码"
+              clearable
+              @keyup.enter="handleQuery"
+              class="!w-240px"
+            />
+          </el-form-item>
+          <el-form-item label="状态" prop="status">
+            <el-select
+              v-model="queryParams.status"
+              placeholder="用户状态"
+              clearable
+              class="!w-240px"
+            >
+              <el-option
+                v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
+                :key="dict.value"
+                :label="dict.label"
+                :value="dict.value"
+              />
+            </el-select>
+          </el-form-item>
+          <el-form-item label="创建时间" prop="createTime">
+            <el-date-picker
+              v-model="queryParams.createTime"
+              value-format="YYYY-MM-DD HH:mm:ss"
+              type="datetimerange"
+              start-placeholder="开始日期"
+              end-placeholder="结束日期"
+              class="!w-240px"
+            />
+          </el-form-item>
+          <el-form-item>
+            <el-button @click="handleQuery"><Icon icon="ep:search" />搜索</el-button>
+            <el-button @click="resetQuery"><Icon icon="ep:refresh" />重置</el-button>
+            <el-button
+              type="primary"
+              plain
+              @click="openForm('create')"
+              v-hasPermi="['system:user:create']"
+            >
+              <Icon icon="ep:plus" /> 新增
+            </el-button>
+            <el-button
+              type="warning"
+              plain
+              @click="handleImport"
+              v-hasPermi="['system:user:import']"
+            >
+              <Icon icon="ep:upload" /> 导入
+            </el-button>
+            <el-button
+              type="success"
+              plain
+              @click="handleExport"
+              :loading="exportLoading"
+              v-hasPermi="['system:user:export']"
+            >
+              <Icon icon="ep:download" />导出
+            </el-button>
+          </el-form-item>
+        </el-form>
+      </ContentWrap>
+      <ContentWrap>
+        <el-table v-loading="loading" :data="list">
+          <el-table-column label="用户编号" align="center" key="id" prop="id" />
+          <el-table-column
+            label="用户名称"
+            align="center"
+            prop="username"
+            :show-overflow-tooltip="true"
+          />
+          <el-table-column
+            label="用户昵称"
+            align="center"
+            prop="nickname"
+            :show-overflow-tooltip="true"
+          />
+          <el-table-column
+            label="部门"
+            align="center"
+            key="deptName"
+            prop="deptName"
+            :show-overflow-tooltip="true"
+          />
+          <el-table-column label="手机号码" align="center" prop="mobile" width="120" />
+          <el-table-column label="状态" key="status">
+            <template #default="scope">
+              <el-switch
+                v-model="scope.row.status"
+                :active-value="0"
+                :inactive-value="1"
+                @change="handleStatusChange(scope.row)"
+              />
+            </template>
+          </el-table-column>
+          <el-table-column
+            label="创建时间"
+            align="center"
+            prop="createTime"
+            :formatter="dateFormatter"
+            width="180"
+          />
+          <el-table-column label="操作" align="center" width="160">
+            <template #default="scope">
+              <div class="flex items-center justify-center">
+                <el-button
+                  type="primary"
+                  link
+                  @click="openForm('update', scope.row.id)"
+                  v-hasPermi="['system:user:update']"
+                >
+                  <Icon icon="ep:edit" />修改
+                </el-button>
+                <el-dropdown
+                  @command="(command) => handleCommand(command, scope.row)"
+                  v-hasPermi="[
+                    'system:user:delete',
+                    'system:user:update-password',
+                    'system:permission:assign-user-role'
+                  ]"
+                >
+                  <el-button type="primary" link><Icon icon="ep:d-arrow-right" /> 更多</el-button>
+                  <template #dropdown>
+                    <el-dropdown-menu>
+                      <el-dropdown-item
+                        command="handleDelete"
+                        v-if="checkPermi(['system:user:delete'])"
+                      >
+                        <Icon icon="ep:delete" />删除
+                      </el-dropdown-item>
+                      <el-dropdown-item
+                        command="handleResetPwd"
+                        v-if="checkPermi(['system:user:update-password'])"
+                      >
+                        <Icon icon="ep:key" />重置密码
+                      </el-dropdown-item>
+                      <el-dropdown-item
+                        command="handleRole"
+                        v-if="checkPermi(['system:permission:assign-user-role'])"
+                      >
+                        <Icon icon="ep:circle-check" />分配角色
+                      </el-dropdown-item>
+                    </el-dropdown-menu>
+                  </template>
+                </el-dropdown>
+              </div>
+            </template>
+          </el-table-column>
+        </el-table>
+        <Pagination
+          :total="total"
+          v-model:page="queryParams.pageNo"
+          v-model:limit="queryParams.pageSize"
+          @pagination="getList"
+        />
+      </ContentWrap>
+    </el-col>
+  </el-row>
+
+  <!-- 添加或修改用户对话框 -->
+  <UserForm ref="formRef" @success="getList" />
+  <!-- 用户导入对话框 -->
+  <UserImportForm ref="importFormRef" @success="getList" />
+  <!-- 分配角色 -->
+  <UserAssignRoleForm ref="assignRoleFormRef" @success="getList" />
+</template>
+<script lang="ts" setup>
+import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
+import { checkPermi } from '@/utils/permission'
+import { dateFormatter } from '@/utils/formatTime'
+import download from '@/utils/download'
+import { CommonStatusEnum } from '@/utils/constants'
+import * as UserApi from '@/api/system/user'
+import UserForm from './UserForm.vue'
+import UserImportForm from './UserImportForm.vue'
+import UserAssignRoleForm from './UserAssignRoleForm.vue'
+import DeptTree from './DeptTree.vue'
+
+defineOptions({ name: 'SystemUser' })
+
+const message = useMessage() // 消息弹窗
+const { t } = useI18n() // 国际化
+
+const loading = ref(true) // 列表的加载中
+const total = ref(0) // 列表的总页数
+const list = ref([]) // 列表的数
+const queryParams = reactive({
+  pageNo: 1,
+  pageSize: 10,
+  username: undefined,
+  mobile: undefined,
+  status: undefined,
+  deptId: undefined,
+  createTime: []
+})
+const queryFormRef = ref() // 搜索的表单
+
+/** 查询列表 */
+const getList = async () => {
+  loading.value = true
+  try {
+    const data = await UserApi.getUserPage(queryParams)
+    list.value = data.list
+    total.value = data.total
+  } finally {
+    loading.value = false
+  }
+}
+
+/** 搜索按钮操作 */
+const handleQuery = () => {
+  queryParams.pageNo = 1
+  getList()
+}
+
+/** 重置按钮操作 */
+const resetQuery = () => {
+  queryFormRef.value?.resetFields()
+  handleQuery()
+}
+
+/** 处理部门被点击 */
+const handleDeptNodeClick = async (row) => {
+  queryParams.deptId = row.id
+  await getList()
+}
+
+/** 添加/修改操作 */
+const formRef = ref()
+const openForm = (type: string, id?: number) => {
+  formRef.value.open(type, id)
+}
+
+/** 用户导入 */
+const importFormRef = ref()
+const handleImport = () => {
+  importFormRef.value.open()
+}
+
+/** 修改用户状态 */
+const handleStatusChange = async (row: UserApi.UserVO) => {
+  try {
+    // 修改状态的二次确认
+    const text = row.status === CommonStatusEnum.ENABLE ? '启用' : '停用'
+    await message.confirm('确认要"' + text + '""' + row.username + '"用户吗?')
+    // 发起修改状态
+    await UserApi.updateUserStatus(row.id, row.status)
+    // 刷新列表
+    await getList()
+  } catch {
+    // 取消后,进行恢复按钮
+    row.status =
+      row.status === CommonStatusEnum.ENABLE ? CommonStatusEnum.DISABLE : CommonStatusEnum.ENABLE
+  }
+}
+
+/** 导出按钮操作 */
+const exportLoading = ref(false)
+const handleExport = async () => {
+  try {
+    // 导出的二次确认
+    await message.exportConfirm()
+    // 发起导出
+    exportLoading.value = true
+    const data = await UserApi.exportUser(queryParams)
+    download.excel(data, '用户数据.xls')
+  } catch {
+  } finally {
+    exportLoading.value = false
+  }
+}
+
+/** 操作分发 */
+const handleCommand = (command: string, row: UserApi.UserVO) => {
+  switch (command) {
+    case 'handleDelete':
+      handleDelete(row.id)
+      break
+    case 'handleResetPwd':
+      handleResetPwd(row)
+      break
+    case 'handleRole':
+      handleRole(row)
+      break
+    default:
+      break
+  }
+}
+
+/** 删除按钮操作 */
+const handleDelete = async (id: number) => {
+  try {
+    // 删除的二次确认
+    await message.delConfirm()
+    // 发起删除
+    await UserApi.deleteUser(id)
+    message.success(t('common.delSuccess'))
+    // 刷新列表
+    await getList()
+  } catch {}
+}
+
+/** 重置密码 */
+const handleResetPwd = async (row: UserApi.UserVO) => {
+  try {
+    // 重置的二次确认
+    const result = await message.prompt(
+      '请输入"' + row.username + '"的新密码',
+      t('common.reminder')
+    )
+    const password = result.value
+    // 发起重置
+    await UserApi.resetUserPwd(row.id, password)
+    message.success('修改成功,新密码是:' + password)
+  } catch {}
+}
+
+/** 分配角色 */
+const assignRoleFormRef = ref()
+const handleRole = (row: UserApi.UserVO) => {
+  assignRoleFormRef.value.open(row)
+}
+
+/** 初始化 */
+onMounted(() => {
+  getList()
+})
+</script>

+ 360 - 0
src/views/system/workroomCollege/user/student.vue

@@ -0,0 +1,360 @@
+<template>
+
+  <el-row :gutter="20">
+    <!-- 左侧部门树 -->
+    <el-col :span="4" :xs="24">
+      <ContentWrap class="h-1/1">
+        <DeptTree @node-click="handleDeptNodeClick" />
+      </ContentWrap>
+    </el-col>
+    <el-col :span="20" :xs="24">
+      <!-- 搜索 -->
+      <ContentWrap>
+        <el-form
+          class="-mb-15px"
+          :model="queryParams"
+          ref="queryFormRef"
+          :inline="true"
+          label-width="68px"
+        >
+          <el-form-item label="用户名称" prop="username">
+            <el-input
+              v-model="queryParams.username"
+              placeholder="请输入用户名称"
+              clearable
+              @keyup.enter="handleQuery"
+              class="!w-240px"
+            />
+          </el-form-item>
+          <el-form-item label="手机号码" prop="mobile">
+            <el-input
+              v-model="queryParams.mobile"
+              placeholder="请输入手机号码"
+              clearable
+              @keyup.enter="handleQuery"
+              class="!w-240px"
+            />
+          </el-form-item>
+          <el-form-item label="状态" prop="status">
+            <el-select
+              v-model="queryParams.status"
+              placeholder="用户状态"
+              clearable
+              class="!w-240px"
+            >
+              <el-option
+                v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
+                :key="dict.value"
+                :label="dict.label"
+                :value="dict.value"
+              />
+            </el-select>
+          </el-form-item>
+          <el-form-item label="创建时间" prop="createTime">
+            <el-date-picker
+              v-model="queryParams.createTime"
+              value-format="YYYY-MM-DD HH:mm:ss"
+              type="datetimerange"
+              start-placeholder="开始日期"
+              end-placeholder="结束日期"
+              class="!w-240px"
+            />
+          </el-form-item>
+          <el-form-item>
+            <el-button @click="handleQuery"><Icon icon="ep:search" />搜索</el-button>
+            <el-button @click="resetQuery"><Icon icon="ep:refresh" />重置</el-button>
+             <!-- 暂时不在这新增 -->
+            <!-- <el-button
+              type="primary"
+              plain
+              @click="openForm('create')"
+              v-hasPermi="['system:user:create']"
+            >
+              <Icon icon="ep:plus" /> 新增
+            </el-button> -->
+            <el-button
+              type="warning"
+              plain
+              @click="handleImport"
+              v-hasPermi="['system:user:import']"
+            >
+              <Icon icon="ep:upload" /> 导入
+            </el-button>
+            <el-button
+              type="success"
+              plain
+              @click="handleExport"
+              :loading="exportLoading"
+              v-hasPermi="['system:user:export']"
+            >
+              <Icon icon="ep:download" />导出
+            </el-button>
+          </el-form-item>
+        </el-form>
+      </ContentWrap>
+      <ContentWrap>
+        <el-table v-loading="loading" :data="list">
+          <el-table-column label="用户编号" align="center" key="id" prop="id" />
+          <el-table-column
+            label="用户名称"
+            align="center"
+            prop="username"
+            :show-overflow-tooltip="true"
+          />
+          <el-table-column
+            label="用户昵称"
+            align="center"
+            prop="nickname"
+            :show-overflow-tooltip="true"
+          />
+          <el-table-column
+            label="工作间"
+            align="center"
+            key="deptName"
+            prop="deptName"
+            :show-overflow-tooltip="true"
+          />
+          <el-table-column label="手机号码" align="center" prop="mobile" width="120" />
+          <el-table-column label="状态" key="status">
+            <template #default="scope">
+              <el-switch
+                v-model="scope.row.status"
+                :active-value="0"
+                :inactive-value="1"
+                @change="handleStatusChange(scope.row)"
+              />
+            </template>
+          </el-table-column>
+          <el-table-column
+            label="创建时间"
+            align="center"
+            prop="createTime"
+            :formatter="dateFormatter"
+            width="180"
+          />
+          <el-table-column label="操作" align="center" width="160">
+            <template #default="scope">
+              <div class="flex items-center justify-center">
+                <el-button
+                  type="primary"
+                  link
+                  @click="openForm('update', scope.row.id)"
+                  v-hasPermi="['system:user:update']"
+                >
+                  <Icon icon="ep:edit" />修改
+                </el-button>
+                <el-dropdown
+                  @command="(command) => handleCommand(command, scope.row)"
+                  v-hasPermi="[
+                    'system:user:delete',
+                    'system:user:update-password',
+                    'system:permission:assign-user-role'
+                  ]"
+                >
+                  <el-button type="primary" link><Icon icon="ep:d-arrow-right" /> 更多</el-button>
+                  <template #dropdown>
+                    <el-dropdown-menu>
+                      <el-dropdown-item
+                        command="handleDelete"
+                        v-if="checkPermi(['system:user:delete'])"
+                      >
+                        <Icon icon="ep:delete" />删除
+                      </el-dropdown-item>
+                      <el-dropdown-item
+                        command="handleResetPwd"
+                        v-if="checkPermi(['system:user:update-password'])"
+                      >
+                        <Icon icon="ep:key" />重置密码
+                      </el-dropdown-item>
+                      <el-dropdown-item
+                        command="handleRole"
+                        v-if="checkPermi(['system:permission:assign-user-role'])"
+                      >
+                        <Icon icon="ep:circle-check" />分配角色
+                      </el-dropdown-item>
+                    </el-dropdown-menu>
+                  </template>
+                </el-dropdown>
+              </div>
+            </template>
+          </el-table-column>
+        </el-table>
+        <Pagination
+          :total="total"
+          v-model:page="queryParams.pageNo"
+          v-model:limit="queryParams.pageSize"
+          @pagination="getList"
+        />
+      </ContentWrap>
+    </el-col>
+  </el-row>
+
+  <!-- 添加或修改用户对话框 -->
+  <UserForm ref="formRef" @success="getList" />
+  <!-- 用户导入对话框 -->
+  <UserImportForm ref="importFormRef" @success="getList" />
+  <!-- 分配角色 -->
+  <UserAssignRoleForm ref="assignRoleFormRef" @success="getList" />
+</template>
+<script lang="ts" setup>
+import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
+import { checkPermi } from '@/utils/permission'
+import { dateFormatter } from '@/utils/formatTime'
+import download from '@/utils/download'
+import { CommonStatusEnum } from '@/utils/constants'
+import * as UserApi from '@/api/system/user'
+import UserForm from './UserForm.vue'
+import UserImportForm from './UserImportForm.vue'
+import UserAssignRoleForm from './UserAssignRoleForm.vue'
+import DeptTree from './DeptTree.vue'
+
+defineOptions({ name: 'SystemUser' })
+
+const message = useMessage() // 消息弹窗
+const { t } = useI18n() // 国际化
+
+const loading = ref(true) // 列表的加载中
+const total = ref(0) // 列表的总页数
+const list = ref([]) // 列表的数
+const queryParams = reactive({
+  pageNo: 1,
+  pageSize: 10,
+  username: undefined,
+  mobile: undefined,
+  status: undefined,
+  deptId: undefined,
+  createTime: []
+})
+const queryFormRef = ref() // 搜索的表单
+
+/** 查询列表 */
+const getList = async () => {
+  loading.value = true
+  try {
+    const data = await UserApi.getStudentPage(queryParams)
+    list.value = data.list
+    total.value = data.total
+  } finally {
+    loading.value = false
+  }
+}
+
+/** 搜索按钮操作 */
+const handleQuery = () => {
+  queryParams.pageNo = 1
+  getList()
+}
+
+/** 重置按钮操作 */
+const resetQuery = () => {
+  queryFormRef.value?.resetFields()
+  handleQuery()
+}
+
+/** 处理部门被点击 */
+const handleDeptNodeClick = async (row) => {
+  queryParams.deptId = row.id
+  await getList()
+}
+
+/** 添加/修改操作 */
+const formRef = ref()
+const openForm = (type: string, id?: number) => {
+  formRef.value.open(type, id)
+}
+
+/** 用户导入 */
+const importFormRef = ref()
+const handleImport = () => {
+  importFormRef.value.open()
+}
+
+/** 修改用户状态 */
+const handleStatusChange = async (row: UserApi.UserVO) => {
+  try {
+    // 修改状态的二次确认
+    const text = row.status === CommonStatusEnum.ENABLE ? '启用' : '停用'
+    await message.confirm('确认要"' + text + '""' + row.username + '"用户吗?')
+    // 发起修改状态
+    await UserApi.updateUserStatus(row.id, row.status)
+    // 刷新列表
+    await getList()
+  } catch {
+    // 取消后,进行恢复按钮
+    row.status =
+      row.status === CommonStatusEnum.ENABLE ? CommonStatusEnum.DISABLE : CommonStatusEnum.ENABLE
+  }
+}
+
+/** 导出按钮操作 */
+const exportLoading = ref(false)
+const handleExport = async () => {
+  try {
+    // 导出的二次确认
+    await message.exportConfirm()
+    // 发起导出
+    exportLoading.value = true
+    const data = await UserApi.exportUser(queryParams)
+    download.excel(data, '用户数据.xls')
+  } catch {
+  } finally {
+    exportLoading.value = false
+  }
+}
+
+/** 操作分发 */
+const handleCommand = (command: string, row: UserApi.UserVO) => {
+  switch (command) {
+    case 'handleDelete':
+      handleDelete(row.id)
+      break
+    case 'handleResetPwd':
+      handleResetPwd(row)
+      break
+    case 'handleRole':
+      handleRole(row)
+      break
+    default:
+      break
+  }
+}
+
+/** 删除按钮操作 */
+const handleDelete = async (id: number) => {
+  try {
+    // 删除的二次确认
+    await message.delConfirm()
+    // 发起删除
+    await UserApi.deleteUser(id)
+    message.success(t('common.delSuccess'))
+    // 刷新列表
+    await getList()
+  } catch {}
+}
+
+/** 重置密码 */
+const handleResetPwd = async (row: UserApi.UserVO) => {
+  try {
+    // 重置的二次确认
+    const result = await message.prompt(
+      '请输入"' + row.username + '"的新密码',
+      t('common.reminder')
+    )
+    const password = result.value
+    // 发起重置
+    await UserApi.resetUserPwd(row.id, password)
+    message.success('修改成功,新密码是:' + password)
+  } catch {}
+}
+
+/** 分配角色 */
+const assignRoleFormRef = ref()
+const handleRole = (row: UserApi.UserVO) => {
+  assignRoleFormRef.value.open(row)
+}
+
+/** 初始化 */
+onMounted(() => {
+  getList()
+})
+</script>

+ 360 - 0
src/views/system/workroomCollege/user/teacher.vue

@@ -0,0 +1,360 @@
+<template>
+
+  <el-row :gutter="20">
+    <!-- 左侧部门树 -->
+    <el-col :span="4" :xs="24">
+      <ContentWrap class="h-1/1">
+        <DeptTree @node-click="handleDeptNodeClick" />
+      </ContentWrap>
+    </el-col>
+    <el-col :span="20" :xs="24">
+      <!-- 搜索 -->
+      <ContentWrap>
+        <el-form
+          class="-mb-15px"
+          :model="queryParams"
+          ref="queryFormRef"
+          :inline="true"
+          label-width="68px"
+        >
+          <el-form-item label="用户名称" prop="username">
+            <el-input
+              v-model="queryParams.username"
+              placeholder="请输入用户名称"
+              clearable
+              @keyup.enter="handleQuery"
+              class="!w-240px"
+            />
+          </el-form-item>
+          <el-form-item label="手机号码" prop="mobile">
+            <el-input
+              v-model="queryParams.mobile"
+              placeholder="请输入手机号码"
+              clearable
+              @keyup.enter="handleQuery"
+              class="!w-240px"
+            />
+          </el-form-item>
+          <el-form-item label="状态" prop="status">
+            <el-select
+              v-model="queryParams.status"
+              placeholder="用户状态"
+              clearable
+              class="!w-240px"
+            >
+              <el-option
+                v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
+                :key="dict.value"
+                :label="dict.label"
+                :value="dict.value"
+              />
+            </el-select>
+          </el-form-item>
+          <el-form-item label="创建时间" prop="createTime">
+            <el-date-picker
+              v-model="queryParams.createTime"
+              value-format="YYYY-MM-DD HH:mm:ss"
+              type="datetimerange"
+              start-placeholder="开始日期"
+              end-placeholder="结束日期"
+              class="!w-240px"
+            />
+          </el-form-item>
+          <el-form-item>
+            <el-button @click="handleQuery"><Icon icon="ep:search" />搜索</el-button>
+            <el-button @click="resetQuery"><Icon icon="ep:refresh" />重置</el-button>
+            <!-- 暂时不在这新增 -->
+            <!-- <el-button
+              type="primary"
+              plain
+              @click="openForm('create')"
+              v-hasPermi="['system:user:create']"
+            >
+              <Icon icon="ep:plus" /> 新增
+            </el-button> -->
+            <el-button
+              type="warning"
+              plain
+              @click="handleImport"
+              v-hasPermi="['system:user:import']"
+            >
+              <Icon icon="ep:upload" /> 导入
+            </el-button>
+            <el-button
+              type="success"
+              plain
+              @click="handleExport"
+              :loading="exportLoading"
+              v-hasPermi="['system:user:export']"
+            >
+              <Icon icon="ep:download" />导出
+            </el-button>
+          </el-form-item>
+        </el-form>
+      </ContentWrap>
+      <ContentWrap>
+        <el-table v-loading="loading" :data="list">
+          <el-table-column label="用户编号" align="center" key="id" prop="id" />
+          <el-table-column
+            label="用户名称"
+            align="center"
+            prop="username"
+            :show-overflow-tooltip="true"
+          />
+          <el-table-column
+            label="用户昵称"
+            align="center"
+            prop="nickname"
+            :show-overflow-tooltip="true"
+          />
+          <el-table-column
+            label="工作间"
+            align="center"
+            key="deptName"
+            prop="deptName"
+            :show-overflow-tooltip="true"
+          />
+          <el-table-column label="手机号码" align="center" prop="mobile" width="120" />
+          <el-table-column label="状态" key="status">
+            <template #default="scope">
+              <el-switch
+                v-model="scope.row.status"
+                :active-value="0"
+                :inactive-value="1"
+                @change="handleStatusChange(scope.row)"
+              />
+            </template>
+          </el-table-column>
+          <el-table-column
+            label="创建时间"
+            align="center"
+            prop="createTime"
+            :formatter="dateFormatter"
+            width="180"
+          />
+          <el-table-column label="操作" align="center" width="160">
+            <template #default="scope">
+              <div class="flex items-center justify-center">
+                <el-button
+                  type="primary"
+                  link
+                  @click="openForm('update', scope.row.id)"
+                  v-hasPermi="['system:user:update']"
+                >
+                  <Icon icon="ep:edit" />修改
+                </el-button>
+                <el-dropdown
+                  @command="(command) => handleCommand(command, scope.row)"
+                  v-hasPermi="[
+                    'system:user:delete',
+                    'system:user:update-password',
+                    'system:permission:assign-user-role'
+                  ]"
+                >
+                  <el-button type="primary" link><Icon icon="ep:d-arrow-right" /> 更多</el-button>
+                  <template #dropdown>
+                    <el-dropdown-menu>
+                      <el-dropdown-item
+                        command="handleDelete"
+                        v-if="checkPermi(['system:user:delete'])"
+                      >
+                        <Icon icon="ep:delete" />删除
+                      </el-dropdown-item>
+                      <el-dropdown-item
+                        command="handleResetPwd"
+                        v-if="checkPermi(['system:user:update-password'])"
+                      >
+                        <Icon icon="ep:key" />重置密码
+                      </el-dropdown-item>
+                      <el-dropdown-item
+                        command="handleRole"
+                        v-if="checkPermi(['system:permission:assign-user-role'])"
+                      >
+                        <Icon icon="ep:circle-check" />分配角色
+                      </el-dropdown-item>
+                    </el-dropdown-menu>
+                  </template>
+                </el-dropdown>
+              </div>
+            </template>
+          </el-table-column>
+        </el-table>
+        <Pagination
+          :total="total"
+          v-model:page="queryParams.pageNo"
+          v-model:limit="queryParams.pageSize"
+          @pagination="getList"
+        />
+      </ContentWrap>
+    </el-col>
+  </el-row>
+
+  <!-- 添加或修改用户对话框 -->
+  <UserForm ref="formRef" @success="getList" />
+  <!-- 用户导入对话框 -->
+  <UserImportForm ref="importFormRef" @success="getList" />
+  <!-- 分配角色 -->
+  <UserAssignRoleForm ref="assignRoleFormRef" @success="getList" />
+</template>
+<script lang="ts" setup>
+import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
+import { checkPermi } from '@/utils/permission'
+import { dateFormatter } from '@/utils/formatTime'
+import download from '@/utils/download'
+import { CommonStatusEnum } from '@/utils/constants'
+import * as UserApi from '@/api/system/user'
+import UserForm from './UserForm.vue'
+import UserImportForm from './UserImportForm.vue'
+import UserAssignRoleForm from './UserAssignRoleForm.vue'
+import DeptTree from './DeptTree.vue'
+
+defineOptions({ name: 'SystemUser' })
+
+const message = useMessage() // 消息弹窗
+const { t } = useI18n() // 国际化
+
+const loading = ref(true) // 列表的加载中
+const total = ref(0) // 列表的总页数
+const list = ref([]) // 列表的数
+const queryParams = reactive({
+  pageNo: 1,
+  pageSize: 10,
+  username: undefined,
+  mobile: undefined,
+  status: undefined,
+  deptId: undefined,
+  createTime: []
+})
+const queryFormRef = ref() // 搜索的表单
+
+/** 查询列表 */
+const getList = async () => {
+  loading.value = true
+  try {
+    const data = await UserApi.getTeacherPage(queryParams)
+    list.value = data.list
+    total.value = data.total
+  } finally {
+    loading.value = false
+  }
+}
+
+/** 搜索按钮操作 */
+const handleQuery = () => {
+  queryParams.pageNo = 1
+  getList()
+}
+
+/** 重置按钮操作 */
+const resetQuery = () => {
+  queryFormRef.value?.resetFields()
+  handleQuery()
+}
+
+/** 处理部门被点击 */
+const handleDeptNodeClick = async (row) => {
+  queryParams.deptId = row.id
+  await getList()
+}
+
+/** 添加/修改操作 */
+const formRef = ref()
+const openForm = (type: string, id?: number) => {
+  formRef.value.open(type, id)
+}
+
+/** 用户导入 */
+const importFormRef = ref()
+const handleImport = () => {
+  importFormRef.value.open()
+}
+
+/** 修改用户状态 */
+const handleStatusChange = async (row: UserApi.UserVO) => {
+  try {
+    // 修改状态的二次确认
+    const text = row.status === CommonStatusEnum.ENABLE ? '启用' : '停用'
+    await message.confirm('确认要"' + text + '""' + row.username + '"用户吗?')
+    // 发起修改状态
+    await UserApi.updateUserStatus(row.id, row.status)
+    // 刷新列表
+    await getList()
+  } catch {
+    // 取消后,进行恢复按钮
+    row.status =
+      row.status === CommonStatusEnum.ENABLE ? CommonStatusEnum.DISABLE : CommonStatusEnum.ENABLE
+  }
+}
+
+/** 导出按钮操作 */
+const exportLoading = ref(false)
+const handleExport = async () => {
+  try {
+    // 导出的二次确认
+    await message.exportConfirm()
+    // 发起导出
+    exportLoading.value = true
+    const data = await UserApi.exportUser(queryParams)
+    download.excel(data, '用户数据.xls')
+  } catch {
+  } finally {
+    exportLoading.value = false
+  }
+}
+
+/** 操作分发 */
+const handleCommand = (command: string, row: UserApi.UserVO) => {
+  switch (command) {
+    case 'handleDelete':
+      handleDelete(row.id)
+      break
+    case 'handleResetPwd':
+      handleResetPwd(row)
+      break
+    case 'handleRole':
+      handleRole(row)
+      break
+    default:
+      break
+  }
+}
+
+/** 删除按钮操作 */
+const handleDelete = async (id: number) => {
+  try {
+    // 删除的二次确认
+    await message.delConfirm()
+    // 发起删除
+    await UserApi.deleteUser(id)
+    message.success(t('common.delSuccess'))
+    // 刷新列表
+    await getList()
+  } catch {}
+}
+
+/** 重置密码 */
+const handleResetPwd = async (row: UserApi.UserVO) => {
+  try {
+    // 重置的二次确认
+    const result = await message.prompt(
+      '请输入"' + row.username + '"的新密码',
+      t('common.reminder')
+    )
+    const password = result.value
+    // 发起重置
+    await UserApi.resetUserPwd(row.id, password)
+    message.success('修改成功,新密码是:' + password)
+  } catch {}
+}
+
+/** 分配角色 */
+const assignRoleFormRef = ref()
+const handleRole = (row: UserApi.UserVO) => {
+  assignRoleFormRef.value.open(row)
+}
+
+/** 初始化 */
+onMounted(() => {
+  getList()
+})
+</script>

+ 160 - 0
src/views/system/workroomCollege/userAchievement/UserAchievementForm.vue

@@ -0,0 +1,160 @@
+<template>
+  <Dialog :title="dialogTitle" v-model="dialogVisible">
+    <el-form
+      ref="formRef"
+      :model="formData"
+      :rules="formRules"
+      label-width="100px"
+      v-loading="formLoading"
+    >
+      <el-form-item label="用户名称" prop="userName">
+        <el-select v-model="formData.userName" placeholder="请输入用户名称" @change="handleUserChange" filterable>
+          <el-option
+            v-for="user in users"
+            :key="user.id"
+            :label="user.nickname"
+            :value="user.nickname"
+          />
+           <!-- 隐藏的输入框用于存储 userId -->
+          <el-input type="hidden" v-model="formData.userId" />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="成果名称" prop="achievementName">
+        <el-input v-model="formData.achievementName" placeholder="请输入成果名称" />
+      </el-form-item>
+      <el-form-item label="成果类型" prop="achievementType">
+        <el-select v-model="formData.achievementType" placeholder="请选择成果类型">
+          <el-option
+            v-for="option in achievementTypes"
+            :key="option.value"
+            :label="option.label"
+            :value="option.value"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="照片详情url" prop="detail">
+        <UploadImg v-model="formData.detail" />
+      </el-form-item>
+    </el-form>
+    <template #footer>
+      <el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button>
+      <el-button @click="dialogVisible = false">取 消</el-button>
+    </template>
+  </Dialog>
+</template>
+<script setup lang="ts">
+import { UserAchievementApi, UserAchievementVO } from '@/api/system/userAchievement'
+import * as UserApi from '@/api/system/user'
+
+
+/** 成果 表单 */
+defineOptions({ name: 'UserAchievementForm' })
+
+const { t } = useI18n() // 国际化
+const message = useMessage() // 消息弹窗
+
+const dialogVisible = ref(false) // 弹窗的是否展示
+const dialogTitle = ref('') // 弹窗的标题
+const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
+const formType = ref('') // 表单的类型:create - 新增;update - 修改
+const formData = ref({
+  id: undefined,
+  userId: undefined,
+  achievementName: undefined,
+  achievementType: undefined,
+  userName: undefined,
+  detail: undefined,
+})
+const formRules = reactive({
+  userId: [{ required: true, message: '用户id不能为空', trigger: 'blur' }],
+  achievementName: [{ required: true, message: '成果名称不能为空', trigger: 'blur' }],
+  achievementType: [{ required: true, message: '成果类型不能为空', trigger: 'change' }],
+  userName: [{ required: true, message: '用户名称不能为空', trigger: 'blur' }],
+})
+const formRef = ref() // 表单 Ref
+
+// 成果类型选项
+const achievementTypes = ref([
+  { label: '论文', value: "1" },
+  { label: '专利', value: "2" },
+  { label: '著作', value: "3" },
+]);
+
+
+/** 获取用户列表 */
+const users = ref();
+const getDeptUser = async () => {
+    try {
+        const response = await UserApi.getDeptUser();
+        console.log(response);
+        users.value = response;
+    } catch (error) {
+        console.error("Error fetching user data:", error);
+    }
+};
+
+/** 打开弹窗 */
+const open = async (type: string, id?: number) => {
+  dialogVisible.value = true
+  dialogTitle.value = t('action.' + type)
+  formType.value = type
+  resetForm()
+  // 修改时,设置数据
+  if (id) {
+    formLoading.value = true
+    try {
+      formData.value = await UserAchievementApi.getUserAchievement(id)
+    } finally {
+      formLoading.value = false
+    }
+  }
+}
+defineExpose({ open }) // 提供 open 方法,用于打开弹窗
+
+/** 用户id更新 */
+const handleUserChange = (selectedNickname: string) => {
+  const selectedUser = users.value.find((user: any) => user.nickname === selectedNickname);
+  formData.value.userId = selectedUser? selectedUser.id : undefined;
+};
+
+/** 提交表单 */
+const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
+const submitForm = async () => {
+  // 校验表单
+  await formRef.value.validate()
+  // 提交请求
+  formLoading.value = true
+  try {
+    const data = formData.value as unknown as UserAchievementVO
+    if (formType.value === 'create') {
+      await UserAchievementApi.createUserAchievement(data)
+      message.success(t('common.createSuccess'))
+    } else {
+      await UserAchievementApi.updateUserAchievement(data)
+      message.success(t('common.updateSuccess'))
+    }
+    dialogVisible.value = false
+    // 发送操作成功的事件
+    emit('success')
+  } finally {
+    formLoading.value = false
+  }
+}
+
+/** 重置表单 */
+const resetForm = () => {
+  formData.value = {
+    id: undefined,
+    userId: undefined,
+    achievementName: undefined,
+    achievementType: undefined,
+    userName: undefined,
+    detail: undefined,
+  }
+  formRef.value?.resetFields()
+}
+
+onMounted(() => {
+  getDeptUser();
+})
+</script>

+ 243 - 0
src/views/system/workroomCollege/userAchievement/index.vue

@@ -0,0 +1,243 @@
+<template>
+  <ContentWrap>
+    <!-- 搜索工作栏 -->
+    <el-form
+      class="-mb-15px"
+      :model="queryParams"
+      ref="queryFormRef"
+      :inline="true"
+      label-width="68px"
+    >
+          <el-form-item label="用户名称" prop="userName">
+        <el-select
+          v-model="queryParams.userName"
+          placeholder="请选择用户名称"
+          clearable
+          filterable
+          class="!w-240px"
+        >
+          <el-option
+            v-for="user in users"
+            :key="user.id"
+            :label="user.nickname"
+            :value="user.nickname"
+          />
+        </el-select>
+      </el-form-item>
+
+      <el-form-item label="成果名称" prop="achievementName">
+        <el-input
+          v-model="queryParams.achievementName"
+          placeholder="请输入成果名称"
+          clearable
+          @keyup.enter="handleQuery"
+          class="!w-240px"
+        />
+      </el-form-item>
+      <el-form-item label="成果类型" prop="achievementType">
+        <el-select
+          v-model="queryParams.achievementType"
+          placeholder="请选择成果类型"
+          clearable
+          class="!w-240px"
+        >
+          <el-option :label="'论文'" :value="1" />
+          <el-option :label="'专利'" :value="2" />
+          <el-option :label="'著作'" :value="3" />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="创建时间" prop="createTime">
+        <el-date-picker
+          v-model="queryParams.createTime"
+          value-format="YYYY-MM-DD HH:mm:ss"
+          type="daterange"
+          start-placeholder="开始日期"
+          end-placeholder="结束日期"
+          :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
+          class="!w-220px"
+        />
+      </el-form-item>
+      <el-form-item>
+        <el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
+        <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
+        <el-button
+          type="primary"
+          plain
+          @click="openForm('create')"
+          v-hasPermi="['system:user-achievement:create']"
+        >
+          <Icon icon="ep:plus" class="mr-5px" /> 新增
+        </el-button>
+        <el-button
+          type="success"
+          plain
+          @click="handleExport"
+          :loading="exportLoading"
+          v-hasPermi="['system:user-achievement:export']"
+        >
+          <Icon icon="ep:download" class="mr-5px" /> 导出
+        </el-button>
+      </el-form-item>
+    </el-form>
+  </ContentWrap>
+
+  <!-- 列表 -->
+  <ContentWrap>
+    <el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
+      <el-table-column label="Id" align="center" prop="id" />
+      <el-table-column label="用户 Id" align="center" prop="userId" v-if="false" />
+      <el-table-column label="用户名称" align="center" prop="userName" />
+      <el-table-column label="成果名称" align="center" prop="achievementName" />
+      <el-table-column label="成果类型" align="center" prop="achievementType">
+        <template #default="rowData">
+          <span>
+            {{
+              rowData.row.achievementType == 1
+               ? '论文'
+                : rowData.row.achievementType ==2
+               ? '专利'
+                : rowData.row.achievementType == 3
+               ? '著作'
+                : '未知类型'
+            }}
+          </span>
+        </template>
+      </el-table-column>
+      <el-table-column
+        label="创建时间"
+        align="center"
+        prop="createTime"
+        :formatter="dateFormatter"
+        width="180px"
+      />
+      <el-table-column label="照片详情 url" align="center">
+        <!-- #default获取当前·行数据 -->
+        <template #default="{ row }">
+          <a :href="row.detail" target="_blank" v-if="row.detail">{{ row.detail }}</a>
+        </template>
+      </el-table-column>
+      <el-table-column label="操作" align="center" min-width="120px">
+        <template #default="rowData">
+          <el-button
+            link
+            type="primary"
+            @click="openForm('update', rowData.row.id)"
+            v-hasPermi="['system:user-achievement:update']"
+          >
+            编辑
+          </el-button>
+          <el-button
+            link
+            type="danger"
+            @click="handleDelete(rowData.row.id)"
+            v-hasPermi="['system:user-achievement:delete']"
+          >
+            删除
+          </el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+    <!-- 分页 -->
+    <Pagination
+      :total="total"
+      v-model:page="queryParams.pageNo"
+      v-model:limit="queryParams.pageSize"
+      @pagination="getList"
+    />
+  </ContentWrap>
+
+  <!-- 表单弹窗:添加/修改 -->
+  <UserAchievementForm ref="formRef" @success="getList" />
+</template>
+
+<script setup lang="ts">
+import { dateFormatter } from '@/utils/formatTime'
+import download from '@/utils/download'
+import { UserAchievementApi, UserAchievementVO } from '@/api/system/userAchievement'
+import UserAchievementForm from './UserAchievementForm.vue'
+import * as UserApi from '@/api/system/user'
+
+/** 成果 列表 */
+defineOptions({ name: 'UserAchievement' })
+
+const message = useMessage() // 消息弹窗
+const { t } = useI18n() // 国际化
+
+const loading = ref(true) // 列表的加载中
+const list = ref<UserAchievementVO[]>([]) // 列表的数据
+const total = ref(0) // 列表的总页数
+const queryParams = reactive({
+  pageNo: 1,
+  pageSize: 10,
+  userId: undefined,
+  achievementName: undefined,
+  achievementType: undefined,
+  userName: undefined,
+  createTime: [],
+  detail: undefined,
+})
+const queryFormRef = ref() // 搜索的表单
+const exportLoading = ref(false) // 导出的加载中
+
+const users = ref()
+const getDeptUser = async () => {
+  try {
+    const response = await UserApi.getDeptUser()
+    users.value = response
+  } catch (error) {
+    console.error('Error fetching user data:', error)
+  }
+}
+
+const getList = async () => {
+  loading.value = true
+  try {
+    const data = await UserAchievementApi.getUserAchievementPage(queryParams)
+    list.value = data.list
+    total.value = data.total
+  } finally {
+    loading.value = false
+  }
+}
+
+const handleQuery = () => {
+  queryParams.pageNo = 1
+  getList()
+}
+
+const resetQuery = () => {
+  queryFormRef.value.resetFields()
+  handleQuery()
+}
+
+const formRef = ref()
+const openForm = (type: string, id?: number) => {
+  formRef.value.open(type, id)
+}
+
+const handleDelete = async (id: number) => {
+  try {
+    await message.delConfirm()
+    await UserAchievementApi.deleteUserAchievement(id)
+    message.success(t('common.delSuccess'))
+    await getList()
+  } catch {}
+}
+
+const handleExport = async () => {
+  try {
+    await message.exportConfirm()
+    exportLoading.value = true
+    const data = await UserAchievementApi.exportUserAchievement(queryParams)
+    download.excel(data, '成果.xls')
+  } catch {
+  } finally {
+    exportLoading.value = false
+  }
+}
+
+onMounted(() => {
+  getList()
+  getDeptUser()
+})
+</script>

+ 112 - 0
src/views/system/workroomTeacher/dept/index.vue

@@ -0,0 +1,112 @@
+<template>
+  <el-card class="workspace-info">
+    <div class="user-info">
+      <div class="info-item">
+        <span class="info-label">负责人邮箱:</span>
+        <span>{{ userInfo.email }}</span>
+      </div>
+      <div class="info-item">
+        <span class="info-label">所属角色:</span>
+        <span>{{ userInfo.roles.join(', ') }}</span>
+      </div>
+      <div class="info-item">
+        <span class="info-label">所属部门:</span>
+        <span>{{ userInfo.department }}</span>
+      </div>
+      <div class="info-item">
+        <span class="info-label">手机号码:</span>
+        <span>{{ userInfo.phone }}</span>
+      </div>
+      <div class="info-item">
+        <span class="info-label">用户名称:</span>
+        <span>{{ userInfo.name }}</span>
+      </div>
+      <div class="info-item">
+        <span class="info-label">所属岗位:</span>
+        <span>{{ userInfo.position }}</span>
+      </div>
+      <div class="info-item">
+        <span class="info-label">创建日期:</span>
+        <span>{{ userInfo.creationDate }}</span>
+      </div>
+    </div>
+    <el-divider>负责人信息</el-divider>
+    <div class="personal-info">
+      <el-form ref="form" :model="form" label-width="80px">
+        <el-form-item label="手机号码">
+          <el-input v-model="form.phone"/>
+        </el-form-item>
+        <el-form-item label="用户昵称">
+          <el-input v-model="form.nickname"/>
+        </el-form-item>
+        <el-form-item label="用户邮箱">
+          <el-input v-model="form.email"/>
+        </el-form-item>
+        <el-form-item label="性别">
+          <el-radio-group v-model="form.gender">
+            <el-radio label="male">男</el-radio>
+            <el-radio label="female">女</el-radio>
+          </el-radio-group>
+        </el-form-item>
+        <el-form-item>
+          <el-button type="primary" @click="saveInfo">保存</el-button>
+        </el-form-item>
+      </el-form>
+    </div>
+  </el-card>
+</template>
+
+<script lang="ts">
+import { defineComponent, reactive, ref } from 'vue';
+
+export default defineComponent({
+  setup() {
+    const form = reactive({
+      phone: '18818260277',
+      nickname: 'admin',
+      email: 'aoteman@126.com',
+      gender: 'male'
+    });
+
+    const userInfo = reactive({
+      email: 'aoteman@126.com',
+      roles: ['超级管理员', '普通角色'],
+      department: '董事长,项目经理',
+      phone: '18818260277',
+      name: 'admin',
+      position: '基本资料',
+      creationDate: '2020-01-05 17:03:47'
+    });
+
+    const saveInfo = () => {
+      // 保存信息的逻辑
+      console.log('保存信息', form);
+    };
+
+    return {
+      form,
+      userInfo,
+      saveInfo
+    };
+  }
+});
+</script>
+
+<style scoped>
+.workspace-info {
+  max-width: 800px;
+  margin: 20px auto;
+}
+
+.info-item {
+  margin-bottom: 10px;
+}
+
+.info-label {
+  font-weight: bold;
+}
+
+.personal-info {
+  margin-top: 20px;
+}
+</style>

+ 63 - 0
src/views/system/workroomTeacher/user/DeptTree.vue

@@ -0,0 +1,63 @@
+<template>
+  <div class="head-container">
+    <el-input v-model="deptName" class="mb-20px" clearable placeholder="请输入工作间名称">
+      <template #prefix>
+        <Icon icon="ep:search" />
+      </template>
+    </el-input>
+  </div>
+  <div class="head-container">
+    <el-tree
+      ref="treeRef"
+      :data="deptList"
+      :expand-on-click-node="false"
+      :filter-node-method="filterNode"
+      :props="defaultProps"
+      default-expand-all
+      highlight-current
+      node-key="id"
+      @node-click="handleNodeClick"
+    />
+  </div>
+</template>
+
+<script lang="ts" setup>
+import { ElTree } from 'element-plus'
+import * as DeptApi from '@/api/system/dept'
+import { defaultProps, handleTree } from '@/utils/tree'
+
+defineOptions({ name: 'SystemUserDeptTree' })
+
+const deptName = ref('')
+const deptList = ref<Tree[]>([]) // 树形结构
+const treeRef = ref<InstanceType<typeof ElTree>>()
+
+/** 获得部门树 */
+const getTree = async () => {
+  const res = await DeptApi.getSimpleDeptList()
+  deptList.value = []
+  deptList.value.push(...handleTree(res))
+}
+
+/** 基于名字过滤 */
+const filterNode = (name: string, data: Tree) => {
+  if (!name) return true
+  return data.name.includes(name)
+}
+
+/** 处理部门被点击 */
+const handleNodeClick = async (row: { [key: string]: any }) => {
+  emits('node-click', row)
+}
+const emits = defineEmits(['node-click'])
+
+/** 监听deptName */
+watch(deptName, (val) => {
+  treeRef.value!.filter(val)
+})
+
+/** 初始化 */
+onMounted(async () => {
+  await getTree()
+})
+</script>

+ 96 - 0
src/views/system/workroomTeacher/user/UserAssignRoleForm.vue

@@ -0,0 +1,96 @@
+<template>
+  <Dialog v-model="dialogVisible" title="分配角色">
+    <el-form ref="formRef" v-loading="formLoading" :model="formData" label-width="80px">
+      <el-form-item label="用户名称">
+        <el-input v-model="formData.username" :disabled="true" />
+      </el-form-item>
+      <el-form-item label="用户昵称">
+        <el-input v-model="formData.nickname" :disabled="true" />
+      </el-form-item>
+      <el-form-item label="角色">
+        <el-select v-model="formData.roleIds" multiple placeholder="请选择角色">
+          <el-option v-for="item in roleList" :key="item.id" :label="item.name" :value="item.id" />
+        </el-select>
+      </el-form-item>
+    </el-form>
+    <template #footer>
+      <el-button :disabled="formLoading" type="primary" @click="submitForm">确 定</el-button>
+      <el-button @click="dialogVisible = false">取 消</el-button>
+    </template>
+  </Dialog>
+</template>
+<script lang="ts" setup>
+import * as PermissionApi from '@/api/system/permission'
+import * as UserApi from '@/api/system/user'
+import * as RoleApi from '@/api/system/role'
+
+defineOptions({ name: 'SystemUserAssignRoleForm' })
+
+const { t } = useI18n() // 国际化
+const message = useMessage() // 消息弹窗
+
+const dialogVisible = ref(false) // 弹窗的是否展示
+const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
+const formData = ref({
+  id: -1,
+  nickname: '',
+  username: '',
+  roleIds: []
+})
+const formRef = ref() // 表单 Ref
+const roleList = ref([] as RoleApi.RoleVO[]) // 角色的列表
+
+/** 打开弹窗 */
+const open = async (row: UserApi.UserVO) => {
+  dialogVisible.value = true
+  resetForm()
+  // 设置数据
+  formData.value.id = row.id
+  formData.value.username = row.username
+  formData.value.nickname = row.nickname
+  // 获得角色拥有的菜单集合
+  formLoading.value = true
+  try {
+    formData.value.roleIds = await PermissionApi.getUserRoleList(row.id)
+  } finally {
+    formLoading.value = false
+  }
+  // 获得角色列表
+  roleList.value = await RoleApi.getSimpleRoleList()
+}
+defineExpose({ open }) // 提供 open 方法,用于打开弹窗
+
+/** 提交表单 */
+const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
+const submitForm = async () => {
+  // 校验表单
+  if (!formRef) return
+  const valid = await formRef.value.validate()
+  if (!valid) return
+  // 提交请求
+  formLoading.value = true
+  try {
+    await PermissionApi.assignUserRole({
+      userId: formData.value.id,
+      roleIds: formData.value.roleIds
+    })
+    message.success(t('common.updateSuccess'))
+    dialogVisible.value = false
+    // 发送操作成功的事件
+    emit('success', true)
+  } finally {
+    formLoading.value = false
+  }
+}
+
+/** 重置表单 */
+const resetForm = () => {
+  formData.value = {
+    id: -1,
+    nickname: '',
+    username: '',
+    roleIds: []
+  }
+  formRef.value?.resetFields()
+}
+</script>

+ 244 - 0
src/views/system/workroomTeacher/user/UserForm.vue

@@ -0,0 +1,244 @@
+<template>
+  <Dialog v-model="dialogVisible" :title="dialogTitle">
+    <el-form
+      ref="formRef"
+      v-loading="formLoading"
+      :model="formData"
+      :rules="formRules"
+      label-width="80px"
+    >
+      <el-row>
+        <el-col :span="12">
+          <el-form-item label="用户昵称" prop="nickname">
+            <el-input v-model="formData.nickname" placeholder="请输入用户昵称" />
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item label="工作间" prop="deptId">
+            <el-tree-select
+              v-model="formData.deptId"
+              :data="deptList"
+              :props="defaultProps"
+              check-strictly
+              node-key="id"
+              placeholder="请选择归属工作间"
+            />
+          </el-form-item>
+        </el-col>
+      </el-row>
+      <el-row>
+        <el-col :span="12">
+          <el-form-item label="手机号码" prop="mobile">
+            <el-input v-model="formData.mobile" maxlength="11" placeholder="请输入手机号码" />
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item label="邮箱" prop="email">
+            <el-input v-model="formData.email" maxlength="50" placeholder="请输入邮箱" />
+          </el-form-item>
+        </el-col>
+      </el-row>
+      <el-row>
+        <el-col :span="12">
+          <el-form-item v-if="formData.id === undefined" label="用户名称" prop="username">
+            <el-input v-model="formData.username" placeholder="请输入用户名称" />
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item v-if="formData.id === undefined" label="用户密码" prop="password">
+            <el-input
+              v-model="formData.password"
+              placeholder="请输入用户密码"
+              show-password
+              type="password"
+            />
+          </el-form-item>
+        </el-col>
+      </el-row>
+      <el-row>
+        <el-col :span="12">
+          <el-form-item label="用户性别">
+            <el-select v-model="formData.sex" placeholder="请选择">
+              <el-option
+                v-for="dict in getIntDictOptions(DICT_TYPE.SYSTEM_USER_SEX)"
+                :key="dict.value"
+                :label="dict.label"
+                :value="dict.value"
+              />
+            </el-select>
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item label="岗位">
+            <el-select v-model="formData.postIds" multiple placeholder="请选择">
+              <el-option
+                v-for="item in postList"
+                :key="item.id"
+                :label="item.name"
+                :value="item.id!"
+              />
+            </el-select>
+          </el-form-item>
+        </el-col>
+      </el-row>
+
+      <el-row v-if="formType === 'update'">
+        <el-col :span="12">
+          <el-form-item label="学生类型" prop="userType">
+            <el-select v-model="formData.userType" placeholder="请选择用户类型">
+              <el-option
+                v-for="option in userTypes"
+                :key="option.value"
+                :label="option.label"
+                :value="option.value"
+                />
+            </el-select>
+          </el-form-item>
+        </el-col>
+      </el-row>
+      
+
+
+      <el-row>
+        <el-col :span="24">
+          <el-form-item label="备注">
+            <el-input v-model="formData.remark" placeholder="请输入内容" type="textarea" />
+          </el-form-item>
+        </el-col>
+      </el-row>
+    </el-form>
+    <template #footer>
+      <el-button :disabled="formLoading" type="primary" @click="submitForm">确 定</el-button>
+      <el-button @click="dialogVisible = false">取 消</el-button>
+    </template>
+  </Dialog>
+</template>
+<script lang="ts" setup>
+import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
+import { CommonStatusEnum } from '@/utils/constants'
+import { defaultProps, handleTree } from '@/utils/tree'
+import * as PostApi from '@/api/system/post'
+import * as DeptApi from '@/api/system/dept'
+import * as UserApi from '@/api/system/user'
+import { FormRules } from 'element-plus'
+
+defineOptions({ name: 'SystemUserForm' })
+
+const { t } = useI18n() // 国际化
+const message = useMessage() // 消息弹窗
+
+const dialogVisible = ref(false) // 弹窗的是否展示
+const dialogTitle = ref('') // 弹窗的标题
+const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
+const formType = ref('') // 表单的类型:create - 新增;update - 修改
+const formData = ref({
+  nickname: '',
+  deptId: '',
+  mobile: '',
+  email: '',
+  id: undefined,
+  username: '',
+  password: '',
+  sex: undefined,
+  postIds: [],
+  remark: '',
+  status: CommonStatusEnum.ENABLE,
+  roleIds: [],
+  userType: undefined
+})
+const formRules = reactive<FormRules>({
+  username: [{ required: true, message: '用户名称不能为空', trigger: 'blur' }],
+  nickname: [{ required: true, message: '用户昵称不能为空', trigger: 'blur' }],
+  password: [{ required: true, message: '用户密码不能为空', trigger: 'blur' }],
+  email: [
+    {
+      type: 'email',
+      message: '请输入正确的邮箱地址',
+      trigger: ['blur', 'change']
+    }
+  ],
+  mobile: [
+    {
+      pattern: /^(?:(?:\+|00)86)?1(?:3[\d]|4[5-79]|5[0-35-9]|6[5-7]|7[0-8]|8[\d]|9[189])\d{8}$/,
+      message: '请输入正确的手机号码',
+      trigger: 'blur'
+    }
+  ]
+})
+const formRef = ref() // 表单 Ref
+const deptList = ref<Tree[]>([]) // 树形结构
+const postList = ref([] as PostApi.PostVO[]) // 岗位列表
+
+const userTypes = ref([
+  { label: '在校生', value: "1" },
+  { label: '毕业生', value: "2" },
+  { label: '导师', value: "3" },
+]);
+/** 打开弹窗 */
+const open = async (type: string, id?: number) => {
+  dialogVisible.value = true
+  dialogTitle.value = t('action.' + type)
+  formType.value = type
+  resetForm()
+  // 修改时,设置数据
+  if (id) {
+    formLoading.value = true
+    try {
+      formData.value = await UserApi.getUser(id)
+    } finally {
+      formLoading.value = false
+    }
+  }
+  // 加载部门树
+  deptList.value = handleTree(await DeptApi.getSimpleDeptList())
+  // 加载岗位列表
+  postList.value = await PostApi.getSimplePostList()
+}
+defineExpose({ open }) // 提供 open 方法,用于打开弹窗
+
+/** 提交表单 */
+const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
+const submitForm = async () => {
+  // 校验表单
+  if (!formRef) return
+  const valid = await formRef.value.validate()
+  if (!valid) return
+  // 提交请求
+  formLoading.value = true
+  try {
+    const data = formData.value as unknown as UserApi.UserVO
+    if (formType.value === 'create') {
+      await UserApi.createUser(data)
+      message.success(t('common.createSuccess'))
+    } else {
+      await UserApi.updateUser(data)
+      message.success(t('common.updateSuccess'))
+    }
+    dialogVisible.value = false
+    // 发送操作成功的事件
+    emit('success')
+  } finally {
+    formLoading.value = false
+  }
+}
+
+/** 重置表单 */
+const resetForm = () => {
+  formData.value = {
+    nickname: '',
+    deptId: '',
+    mobile: '',
+    email: '',
+    id: undefined,
+    username: '',
+    password: '',
+    sex: undefined,
+    postIds: [],
+    remark: '',
+    status: CommonStatusEnum.ENABLE,
+    roleIds: [],
+    userType: undefined
+  }
+  formRef.value?.resetFields()
+}
+</script>

+ 138 - 0
src/views/system/workroomTeacher/user/UserImportForm.vue

@@ -0,0 +1,138 @@
+<template>
+  <Dialog v-model="dialogVisible" title="用户导入" width="400">
+    <el-upload
+      ref="uploadRef"
+      v-model:file-list="fileList"
+      :action="importUrl + '?updateSupport=' + updateSupport"
+      :auto-upload="false"
+      :disabled="formLoading"
+      :headers="uploadHeaders"
+      :limit="1"
+      :on-error="submitFormError"
+      :on-exceed="handleExceed"
+      :on-success="submitFormSuccess"
+      accept=".xlsx, .xls"
+      drag
+    >
+      <Icon icon="ep:upload" />
+      <div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
+      <template #tip>
+        <div class="el-upload__tip text-center">
+          <div class="el-upload__tip">
+            <el-checkbox v-model="updateSupport" />
+            是否更新已经存在的用户数据
+          </div>
+          <span>仅允许导入 xls、xlsx 格式文件。</span>
+          <el-link
+            :underline="false"
+            style="font-size: 12px; vertical-align: baseline"
+            type="primary"
+            @click="importTemplate"
+          >
+            下载模板
+          </el-link>
+        </div>
+      </template>
+    </el-upload>
+    <template #footer>
+      <el-button :disabled="formLoading" type="primary" @click="submitForm">确 定</el-button>
+      <el-button @click="dialogVisible = false">取 消</el-button>
+    </template>
+  </Dialog>
+</template>
+<script lang="ts" setup>
+import * as UserApi from '@/api/system/user'
+import { getAccessToken, getTenantId } from '@/utils/auth'
+import download from '@/utils/download'
+
+defineOptions({ name: 'SystemUserImportForm' })
+
+const message = useMessage() // 消息弹窗
+
+const dialogVisible = ref(false) // 弹窗的是否展示
+const formLoading = ref(false) // 表单的加载中
+const uploadRef = ref()
+const importUrl =
+  import.meta.env.VITE_BASE_URL + import.meta.env.VITE_API_URL + '/system/user/import'
+const uploadHeaders = ref() // 上传 Header 头
+const fileList = ref([]) // 文件列表
+const updateSupport = ref(0) // 是否更新已经存在的用户数据
+
+/** 打开弹窗 */
+const open = () => {
+  dialogVisible.value = true
+  updateSupport.value = 0
+  fileList.value = []
+  resetForm()
+}
+defineExpose({ open }) // 提供 open 方法,用于打开弹窗
+
+/** 提交表单 */
+const submitForm = async () => {
+  if (fileList.value.length == 0) {
+    message.error('请上传文件')
+    return
+  }
+  // 提交请求
+  uploadHeaders.value = {
+    Authorization: 'Bearer ' + getAccessToken(),
+    'tenant-id': getTenantId()
+  }
+  formLoading.value = true
+  uploadRef.value!.submit()
+}
+
+/** 文件上传成功 */
+const emits = defineEmits(['success'])
+const submitFormSuccess = (response: any) => {
+  if (response.code !== 0) {
+    message.error(response.msg)
+    formLoading.value = false
+    return
+  }
+  // 拼接提示语
+  const data = response.data
+  let text = '上传成功数量:' + data.createUsernames.length + ';'
+  for (let username of data.createUsernames) {
+    text += '< ' + username + ' >'
+  }
+  text += '更新成功数量:' + data.updateUsernames.length + ';'
+  for (const username of data.updateUsernames) {
+    text += '< ' + username + ' >'
+  }
+  text += '更新失败数量:' + Object.keys(data.failureUsernames).length + ';'
+  for (const username in data.failureUsernames) {
+    text += '< ' + username + ': ' + data.failureUsernames[username] + ' >'
+  }
+  message.alert(text)
+  formLoading.value = false
+  dialogVisible.value = false
+  // 发送操作成功的事件
+  emits('success')
+}
+
+/** 上传错误提示 */
+const submitFormError = (): void => {
+  message.error('上传失败,请您重新上传!')
+  formLoading.value = false
+}
+
+/** 重置表单 */
+const resetForm = async (): Promise<void> => {
+  // 重置上传状态和文件
+  formLoading.value = false
+  await nextTick()
+  uploadRef.value?.clearFiles()
+}
+
+/** 文件数超出提示 */
+const handleExceed = (): void => {
+  message.error('最多只能上传一个文件!')
+}
+
+/** 下载模板操作 */
+const importTemplate = async () => {
+  const res = await UserApi.importUserTemplate()
+  download.excel(res, '用户导入模版.xls')
+}
+</script>

+ 362 - 0
src/views/system/workroomTeacher/user/index.vue

@@ -0,0 +1,362 @@
+<template>
+  <doc-alert title="用户体系" url="https://doc.iocoder.cn/user-center/" />
+  <doc-alert title="三方登陆" url="https://doc.iocoder.cn/social-user/" />
+  <doc-alert title="Excel 导入导出" url="https://doc.iocoder.cn/excel-import-and-export/" />
+
+  <el-row :gutter="20">
+    <!-- 左侧部门树 -->
+    <el-col :span="4" :xs="24">
+      <ContentWrap class="h-1/1">
+        <DeptTree @node-click="handleDeptNodeClick" />
+      </ContentWrap>
+    </el-col>
+    <el-col :span="20" :xs="24">
+      <!-- 搜索 -->
+      <ContentWrap>
+        <el-form
+          class="-mb-15px"
+          :model="queryParams"
+          ref="queryFormRef"
+          :inline="true"
+          label-width="68px"
+        >
+          <el-form-item label="用户名称" prop="username">
+            <el-input
+              v-model="queryParams.username"
+              placeholder="请输入用户名称"
+              clearable
+              @keyup.enter="handleQuery"
+              class="!w-240px"
+            />
+          </el-form-item>
+          <el-form-item label="手机号码" prop="mobile">
+            <el-input
+              v-model="queryParams.mobile"
+              placeholder="请输入手机号码"
+              clearable
+              @keyup.enter="handleQuery"
+              class="!w-240px"
+            />
+          </el-form-item>
+          <el-form-item label="状态" prop="status">
+            <el-select
+              v-model="queryParams.status"
+              placeholder="用户状态"
+              clearable
+              class="!w-240px"
+            >
+              <el-option
+                v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
+                :key="dict.value"
+                :label="dict.label"
+                :value="dict.value"
+              />
+            </el-select>
+          </el-form-item>
+          <el-form-item label="创建时间" prop="createTime">
+            <el-date-picker
+              v-model="queryParams.createTime"
+              value-format="YYYY-MM-DD HH:mm:ss"
+              type="datetimerange"
+              start-placeholder="开始日期"
+              end-placeholder="结束日期"
+              class="!w-240px"
+            />
+          </el-form-item>
+          <el-form-item>
+            <el-button @click="handleQuery"><Icon icon="ep:search" />搜索</el-button>
+            <el-button @click="resetQuery"><Icon icon="ep:refresh" />重置</el-button>
+            <el-button
+              type="primary"
+              plain
+              @click="openForm('create')"
+              v-hasPermi="['system:user:create']"
+            >
+              <Icon icon="ep:plus" /> 新增
+            </el-button>
+            <el-button
+              type="warning"
+              plain
+              @click="handleImport"
+              v-hasPermi="['system:user:import']"
+            >
+              <Icon icon="ep:upload" /> 导入
+            </el-button>
+            <el-button
+              type="success"
+              plain
+              @click="handleExport"
+              :loading="exportLoading"
+              v-hasPermi="['system:user:export']"
+            >
+              <Icon icon="ep:download" />导出
+            </el-button>
+          </el-form-item>
+        </el-form>
+      </ContentWrap>
+      <ContentWrap>
+        <el-table v-loading="loading" :data="list">
+          <el-table-column label="用户编号" align="center" key="id" prop="id" />
+          <el-table-column
+            label="用户名称"
+            align="center"
+            prop="username"
+            :show-overflow-tooltip="true"
+          />
+          <el-table-column
+            label="用户昵称"
+            align="center"
+            prop="nickname"
+            :show-overflow-tooltip="true"
+          />
+          <el-table-column
+            label="部门"
+            align="center"
+            key="deptName"
+            prop="deptName"
+            :show-overflow-tooltip="true"
+          />
+          <el-table-column label="手机号码" align="center" prop="mobile" width="120" />
+          <el-table-column label="状态" key="status">
+            <template #default="scope">
+              <el-switch
+                v-model="scope.row.status"
+                :active-value="0"
+                :inactive-value="1"
+                @change="handleStatusChange(scope.row)"
+              />
+            </template>
+          </el-table-column>
+          <el-table-column
+            label="创建时间"
+            align="center"
+            prop="createTime"
+            :formatter="dateFormatter"
+            width="180"
+          />
+          <el-table-column label="操作" align="center" width="160">
+            <template #default="scope">
+              <div class="flex items-center justify-center">
+                <el-button
+                  type="primary"
+                  link
+                  @click="openForm('update', scope.row.id)"
+                  v-hasPermi="['system:user:update']"
+                >
+                  <Icon icon="ep:edit" />修改
+                </el-button>
+                <el-dropdown
+                  @command="(command) => handleCommand(command, scope.row)"
+                  v-hasPermi="[
+                    'system:user:delete',
+                    'system:user:update-password',
+                    'system:permission:assign-user-role'
+                  ]"
+                >
+                  <el-button type="primary" link><Icon icon="ep:d-arrow-right" /> 更多</el-button>
+                  <template #dropdown>
+                    <el-dropdown-menu>
+                      <el-dropdown-item
+                        command="handleDelete"
+                        v-if="checkPermi(['system:user:delete'])"
+                      >
+                        <Icon icon="ep:delete" />删除
+                      </el-dropdown-item>
+                      <el-dropdown-item
+                        command="handleResetPwd"
+                        v-if="checkPermi(['system:user:update-password'])"
+                      >
+                        <Icon icon="ep:key" />重置密码
+                      </el-dropdown-item>
+                      <el-dropdown-item
+                        command="handleRole"
+                        v-if="checkPermi(['system:permission:assign-user-role'])"
+                      >
+                        <Icon icon="ep:circle-check" />分配角色
+                      </el-dropdown-item>
+                    </el-dropdown-menu>
+                  </template>
+                </el-dropdown>
+              </div>
+            </template>
+          </el-table-column>
+        </el-table>
+        <Pagination
+          :total="total"
+          v-model:page="queryParams.pageNo"
+          v-model:limit="queryParams.pageSize"
+          @pagination="getList"
+        />
+      </ContentWrap>
+    </el-col>
+  </el-row>
+
+  <!-- 添加或修改用户对话框 -->
+  <UserForm ref="formRef" @success="getList" />
+  <!-- 用户导入对话框 -->
+  <UserImportForm ref="importFormRef" @success="getList" />
+  <!-- 分配角色 -->
+  <UserAssignRoleForm ref="assignRoleFormRef" @success="getList" />
+</template>
+<script lang="ts" setup>
+import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
+import { checkPermi } from '@/utils/permission'
+import { dateFormatter } from '@/utils/formatTime'
+import download from '@/utils/download'
+import { CommonStatusEnum } from '@/utils/constants'
+import * as UserApi from '@/api/system/user'
+import UserForm from './UserForm.vue'
+import UserImportForm from './UserImportForm.vue'
+import UserAssignRoleForm from './UserAssignRoleForm.vue'
+import DeptTree from './DeptTree.vue'
+
+defineOptions({ name: 'SystemUser' })
+
+const message = useMessage() // 消息弹窗
+const { t } = useI18n() // 国际化
+
+const loading = ref(true) // 列表的加载中
+const total = ref(0) // 列表的总页数
+const list = ref([]) // 列表的数
+const queryParams = reactive({
+  pageNo: 1,
+  pageSize: 10,
+  username: undefined,
+  mobile: undefined,
+  status: undefined,
+  deptId: undefined,
+  createTime: []
+})
+const queryFormRef = ref() // 搜索的表单
+
+/** 查询列表 */
+const getList = async () => {
+  loading.value = true
+  try {
+    const data = await UserApi.getUserPage(queryParams)
+    list.value = data.list
+    total.value = data.total
+  } finally {
+    loading.value = false
+  }
+}
+
+/** 搜索按钮操作 */
+const handleQuery = () => {
+  queryParams.pageNo = 1
+  getList()
+}
+
+/** 重置按钮操作 */
+const resetQuery = () => {
+  queryFormRef.value?.resetFields()
+  handleQuery()
+}
+
+/** 处理部门被点击 */
+const handleDeptNodeClick = async (row) => {
+  queryParams.deptId = row.id
+  await getList()
+}
+
+/** 添加/修改操作 */
+const formRef = ref()
+const openForm = (type: string, id?: number) => {
+  formRef.value.open(type, id)
+}
+
+/** 用户导入 */
+const importFormRef = ref()
+const handleImport = () => {
+  importFormRef.value.open()
+}
+
+/** 修改用户状态 */
+const handleStatusChange = async (row: UserApi.UserVO) => {
+  try {
+    // 修改状态的二次确认
+    const text = row.status === CommonStatusEnum.ENABLE ? '启用' : '停用'
+    await message.confirm('确认要"' + text + '""' + row.username + '"用户吗?')
+    // 发起修改状态
+    await UserApi.updateUserStatus(row.id, row.status)
+    // 刷新列表
+    await getList()
+  } catch {
+    // 取消后,进行恢复按钮
+    row.status =
+      row.status === CommonStatusEnum.ENABLE ? CommonStatusEnum.DISABLE : CommonStatusEnum.ENABLE
+  }
+}
+
+/** 导出按钮操作 */
+const exportLoading = ref(false)
+const handleExport = async () => {
+  try {
+    // 导出的二次确认
+    await message.exportConfirm()
+    // 发起导出
+    exportLoading.value = true
+    const data = await UserApi.exportUser(queryParams)
+    download.excel(data, '用户数据.xls')
+  } catch {
+  } finally {
+    exportLoading.value = false
+  }
+}
+
+/** 操作分发 */
+const handleCommand = (command: string, row: UserApi.UserVO) => {
+  switch (command) {
+    case 'handleDelete':
+      handleDelete(row.id)
+      break
+    case 'handleResetPwd':
+      handleResetPwd(row)
+      break
+    case 'handleRole':
+      handleRole(row)
+      break
+    default:
+      break
+  }
+}
+
+/** 删除按钮操作 */
+const handleDelete = async (id: number) => {
+  try {
+    // 删除的二次确认
+    await message.delConfirm()
+    // 发起删除
+    await UserApi.deleteUser(id)
+    message.success(t('common.delSuccess'))
+    // 刷新列表
+    await getList()
+  } catch {}
+}
+
+/** 重置密码 */
+const handleResetPwd = async (row: UserApi.UserVO) => {
+  try {
+    // 重置的二次确认
+    const result = await message.prompt(
+      '请输入"' + row.username + '"的新密码',
+      t('common.reminder')
+    )
+    const password = result.value
+    // 发起重置
+    await UserApi.resetUserPwd(row.id, password)
+    message.success('修改成功,新密码是:' + password)
+  } catch {}
+}
+
+/** 分配角色 */
+const assignRoleFormRef = ref()
+const handleRole = (row: UserApi.UserVO) => {
+  assignRoleFormRef.value.open(row)
+}
+
+/** 初始化 */
+onMounted(() => {
+  getList()
+})
+</script>

+ 355 - 0
src/views/system/workroomTeacher/user/student.vue

@@ -0,0 +1,355 @@
+<template>
+
+  <el-row :gutter="20">
+  
+    <el-col :span="24" :xs="24">
+      <!-- 搜索 -->
+      <ContentWrap>
+        <el-form
+          class="-mb-15px"
+          :model="queryParams"
+          ref="queryFormRef"
+          :inline="true"
+          label-width="68px"
+        >
+          <el-form-item label="用户名称" prop="username">
+            <el-input
+              v-model="queryParams.username"
+              placeholder="请输入用户名称"
+              clearable
+              @keyup.enter="handleQuery"
+              class="!w-240px"
+            />
+          </el-form-item>
+          <el-form-item label="手机号码" prop="mobile">
+            <el-input
+              v-model="queryParams.mobile"
+              placeholder="请输入手机号码"
+              clearable
+              @keyup.enter="handleQuery"
+              class="!w-240px"
+            />
+          </el-form-item>
+          <el-form-item label="状态" prop="status">
+            <el-select
+              v-model="queryParams.status"
+              placeholder="用户状态"
+              clearable
+              class="!w-240px"
+            >
+              <el-option
+                v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
+                :key="dict.value"
+                :label="dict.label"
+                :value="dict.value"
+              />
+            </el-select>
+          </el-form-item>
+          <el-form-item label="创建时间" prop="createTime">
+            <el-date-picker
+              v-model="queryParams.createTime"
+              value-format="YYYY-MM-DD HH:mm:ss"
+              type="datetimerange"
+              start-placeholder="开始日期"
+              end-placeholder="结束日期"
+              class="!w-240px"
+            />
+          </el-form-item>
+          <el-form-item>
+            <el-button @click="handleQuery"><Icon icon="ep:search" />搜索</el-button>
+            <el-button @click="resetQuery"><Icon icon="ep:refresh" />重置</el-button>
+             <!-- 暂时不在这新增 -->
+            <!-- <el-button
+              type="primary"
+              plain
+              @click="openForm('create')"
+              v-hasPermi="['system:user:create']"
+            >
+              <Icon icon="ep:plus" /> 新增
+            </el-button> -->
+            <el-button
+              type="warning"
+              plain
+              @click="handleImport"
+              v-hasPermi="['system:user:import']"
+            >
+              <Icon icon="ep:upload" /> 导入
+            </el-button>
+            <el-button
+              type="success"
+              plain
+              @click="handleExport"
+              :loading="exportLoading"
+              v-hasPermi="['system:user:export']"
+            >
+              <Icon icon="ep:download" />导出
+            </el-button>
+          </el-form-item>
+        </el-form>
+      </ContentWrap>
+      <ContentWrap>
+        <el-table v-loading="loading" :data="list">
+          <el-table-column label="用户编号" align="center" key="id" prop="id" />
+          <el-table-column
+            label="用户名称"
+            align="center"
+            prop="username"
+            :show-overflow-tooltip="true"
+          />
+          <el-table-column
+            label="用户昵称"
+            align="center"
+            prop="nickname"
+            :show-overflow-tooltip="true"
+          />
+          <el-table-column
+            label="工作间"
+            align="center"
+            key="deptName"
+            prop="deptName"
+            :show-overflow-tooltip="true"
+          />
+          <el-table-column label="手机号码" align="center" prop="mobile" width="120" />
+          <el-table-column label="状态" key="status">
+            <template #default="scope">
+              <el-switch
+                v-model="scope.row.status"
+                :active-value="0"
+                :inactive-value="1"
+                @change="handleStatusChange(scope.row)"
+              />
+            </template>
+          </el-table-column>
+          <el-table-column
+            label="创建时间"
+            align="center"
+            prop="createTime"
+            :formatter="dateFormatter"
+            width="180"
+          />
+          <el-table-column label="操作" align="center" width="160">
+            <template #default="scope">
+              <div class="flex items-center justify-center">
+                <el-button
+                  type="primary"
+                  link
+                  @click="openForm('update', scope.row.id)"
+                  v-hasPermi="['system:user:update']"
+                >
+                  <Icon icon="ep:edit" />修改
+                </el-button>
+                <el-dropdown
+                  @command="(command) => handleCommand(command, scope.row)"
+                  v-hasPermi="[
+                    'system:user:delete',
+                    'system:user:update-password',
+                    'system:permission:assign-user-role'
+                  ]"
+                >
+                  <el-button type="primary" link><Icon icon="ep:d-arrow-right" /> 更多</el-button>
+                  <template #dropdown>
+                    <el-dropdown-menu>
+                      <el-dropdown-item
+                        command="handleDelete"
+                        v-if="checkPermi(['system:user:delete'])"
+                      >
+                        <Icon icon="ep:delete" />删除
+                      </el-dropdown-item>
+                      <el-dropdown-item
+                        command="handleResetPwd"
+                        v-if="checkPermi(['system:user:update-password'])"
+                      >
+                        <Icon icon="ep:key" />重置密码
+                      </el-dropdown-item>
+                      <el-dropdown-item
+                        command="handleRole"
+                        v-if="checkPermi(['system:permission:assign-user-role'])"
+                      >
+                        <Icon icon="ep:circle-check" />分配角色
+                      </el-dropdown-item>
+                    </el-dropdown-menu>
+                  </template>
+                </el-dropdown>
+              </div>
+            </template>
+          </el-table-column>
+        </el-table>
+        <Pagination
+          :total="total"
+          v-model:page="queryParams.pageNo"
+          v-model:limit="queryParams.pageSize"
+          @pagination="getList"
+        />
+      </ContentWrap>
+    </el-col>
+  </el-row>
+
+  <!-- 添加或修改用户对话框 -->
+  <UserForm ref="formRef" @success="getList" />
+  <!-- 用户导入对话框 -->
+  <UserImportForm ref="importFormRef" @success="getList" />
+  <!-- 分配角色 -->
+  <UserAssignRoleForm ref="assignRoleFormRef" @success="getList" />
+</template>
+<script lang="ts" setup>
+import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
+import { checkPermi } from '@/utils/permission'
+import { dateFormatter } from '@/utils/formatTime'
+import download from '@/utils/download'
+import { CommonStatusEnum } from '@/utils/constants'
+import * as UserApi from '@/api/system/user'
+import UserForm from './UserForm.vue'
+import UserImportForm from './UserImportForm.vue'
+import UserAssignRoleForm from './UserAssignRoleForm.vue'
+import DeptTree from './DeptTree.vue'
+
+defineOptions({ name: 'SystemUser' })
+
+const message = useMessage() // 消息弹窗
+const { t } = useI18n() // 国际化
+
+const loading = ref(true) // 列表的加载中
+const total = ref(0) // 列表的总页数
+const list = ref([]) // 列表的数
+const queryParams = reactive({
+  pageNo: 1,
+  pageSize: 10,
+  username: undefined,
+  mobile: undefined,
+  status: undefined,
+  deptId: undefined,
+  createTime: []
+})
+const queryFormRef = ref() // 搜索的表单
+
+/** 查询列表 */
+const getList = async () => {
+  loading.value = true
+  try {
+    const data = await UserApi.getDeptStudentPage(queryParams)
+    list.value = data.list
+    total.value = data.total
+  } finally {
+    loading.value = false
+  }
+}
+
+/** 搜索按钮操作 */
+const handleQuery = () => {
+  queryParams.pageNo = 1
+  getList()
+}
+
+/** 重置按钮操作 */
+const resetQuery = () => {
+  queryFormRef.value?.resetFields()
+  handleQuery()
+}
+
+/** 处理部门被点击 */
+const handleDeptNodeClick = async (row) => {
+  queryParams.deptId = row.id
+  await getList()
+}
+
+/** 添加/修改操作 */
+const formRef = ref()
+const openForm = (type: string, id?: number) => {
+  formRef.value.open(type, id)
+}
+
+/** 用户导入 */
+const importFormRef = ref()
+const handleImport = () => {
+  importFormRef.value.open()
+}
+
+/** 修改用户状态 */
+const handleStatusChange = async (row: UserApi.UserVO) => {
+  try {
+    // 修改状态的二次确认
+    const text = row.status === CommonStatusEnum.ENABLE ? '启用' : '停用'
+    await message.confirm('确认要"' + text + '""' + row.username + '"用户吗?')
+    // 发起修改状态
+    await UserApi.updateUserStatus(row.id, row.status)
+    // 刷新列表
+    await getList()
+  } catch {
+    // 取消后,进行恢复按钮
+    row.status =
+      row.status === CommonStatusEnum.ENABLE ? CommonStatusEnum.DISABLE : CommonStatusEnum.ENABLE
+  }
+}
+
+/** 导出按钮操作 */
+const exportLoading = ref(false)
+const handleExport = async () => {
+  try {
+    // 导出的二次确认
+    await message.exportConfirm()
+    // 发起导出
+    exportLoading.value = true
+    const data = await UserApi.exportUser(queryParams)
+    download.excel(data, '用户数据.xls')
+  } catch {
+  } finally {
+    exportLoading.value = false
+  }
+}
+
+/** 操作分发 */
+const handleCommand = (command: string, row: UserApi.UserVO) => {
+  switch (command) {
+    case 'handleDelete':
+      handleDelete(row.id)
+      break
+    case 'handleResetPwd':
+      handleResetPwd(row)
+      break
+    case 'handleRole':
+      handleRole(row)
+      break
+    default:
+      break
+  }
+}
+
+/** 删除按钮操作 */
+const handleDelete = async (id: number) => {
+  try {
+    // 删除的二次确认
+    await message.delConfirm()
+    // 发起删除
+    await UserApi.deleteUser(id)
+    message.success(t('common.delSuccess'))
+    // 刷新列表
+    await getList()
+  } catch {}
+}
+
+/** 重置密码 */
+const handleResetPwd = async (row: UserApi.UserVO) => {
+  try {
+    // 重置的二次确认
+    const result = await message.prompt(
+      '请输入"' + row.username + '"的新密码',
+      t('common.reminder')
+    )
+    const password = result.value
+    // 发起重置
+    await UserApi.resetUserPwd(row.id, password)
+    message.success('修改成功,新密码是:' + password)
+  } catch {}
+}
+
+/** 分配角色 */
+const assignRoleFormRef = ref()
+const handleRole = (row: UserApi.UserVO) => {
+  assignRoleFormRef.value.open(row)
+}
+
+/** 初始化 */
+onMounted(() => {
+  getList()
+})
+</script>

+ 349 - 0
src/views/system/workroomTeacher/user/teacher.vue

@@ -0,0 +1,349 @@
+<template>
+
+  <el-row :gutter="20">
+    <el-col :span="24" :xs="24">
+      <!-- 搜索 -->
+      <ContentWrap>
+        <el-form
+          class="-mb-15px"
+          :model="queryParams"
+          ref="queryFormRef"
+          :inline="true"
+          label-width="68px"
+        >
+          <el-form-item label="用户名称" prop="username">
+            <el-input
+              v-model="queryParams.username"
+              placeholder="请输入用户名称"
+              clearable
+              @keyup.enter="handleQuery"
+              class="!w-240px"
+            />
+          </el-form-item>
+          <el-form-item label="手机号码" prop="mobile">
+            <el-input
+              v-model="queryParams.mobile"
+              placeholder="请输入手机号码"
+              clearable
+              @keyup.enter="handleQuery"
+              class="!w-240px"
+            />
+          </el-form-item>
+          <el-form-item label="状态" prop="status">
+            <el-select
+              v-model="queryParams.status"
+              placeholder="用户状态"
+              clearable
+              class="!w-240px"
+            >
+              <el-option
+                v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
+                :key="dict.value"
+                :label="dict.label"
+                :value="dict.value"
+              />
+            </el-select>
+          </el-form-item>
+          <el-form-item label="创建时间" prop="createTime">
+            <el-date-picker
+              v-model="queryParams.createTime"
+              value-format="YYYY-MM-DD HH:mm:ss"
+              type="datetimerange"
+              start-placeholder="开始日期"
+              end-placeholder="结束日期"
+              class="!w-240px"
+            />
+          </el-form-item>
+          <el-form-item>
+            <el-button @click="handleQuery"><Icon icon="ep:search" />搜索</el-button>
+            <el-button @click="resetQuery"><Icon icon="ep:refresh" />重置</el-button>
+            <!-- 暂时不在这新增 -->
+            <!-- <el-button
+              type="primary"
+              plain
+              @click="openForm('create')"
+              v-hasPermi="['system:teacher:create']"
+            >
+              <Icon icon="ep:plus" /> 新增
+            </el-button> -->
+            <el-button
+              type="warning"
+              plain
+              @click="handleImport"
+              v-hasPermi="['system:user:import']"
+            >
+              <Icon icon="ep:upload" /> 导入
+            </el-button>
+            <el-button
+              type="success"
+              plain
+              @click="handleExport"
+              :loading="exportLoading"
+              v-hasPermi="['system:user:export']"
+            >
+              <Icon icon="ep:download" />导出
+            </el-button>
+          </el-form-item>
+        </el-form>
+      </ContentWrap>
+      <ContentWrap>
+        <el-table v-loading="loading" :data="list">
+          <el-table-column label="用户编号" align="center" key="id" prop="id" />
+          <el-table-column
+            label="用户名称"
+            align="center"
+            prop="username"
+            :show-overflow-tooltip="true"
+          />
+          <el-table-column
+            label="用户昵称"
+            align="center"
+            prop="nickname"
+            :show-overflow-tooltip="true"
+          />
+          <el-table-column
+            label="工作间"
+            align="center"
+            key="deptName"
+            prop="deptName"
+            :show-overflow-tooltip="true"
+          />
+          <el-table-column label="手机号码" align="center" prop="mobile" width="120" />
+          <el-table-column label="状态" key="status">
+            <template #default="scope">
+              <el-switch
+                v-model="scope.row.status"
+                :active-value="0"
+                :inactive-value="1"
+                @change="handleStatusChange(scope.row)"
+              />
+            </template>
+          </el-table-column>
+          <el-table-column
+            label="创建时间"
+            align="center"
+            prop="createTime"
+            :formatter="dateFormatter"
+            width="180"
+          />
+          <el-table-column label="操作" align="center" width="160">
+            <template #default="scope">
+              <div class="flex items-center justify-center">
+                <el-button
+                  type="primary"
+                  link
+                  @click="openForm('update', scope.row.id)"
+                  v-hasPermi="['system:user:update']"
+                >
+                  <Icon icon="ep:edit" />修改
+                </el-button>
+                <el-dropdown
+                  @command="(command) => handleCommand(command, scope.row)"
+                  v-hasPermi="[
+                    'system:user:delete',
+                    'system:user:update-password',
+                    'system:permission:assign-user-role'
+                  ]"
+                >
+                  <el-button type="primary" link><Icon icon="ep:d-arrow-right" /> 更多</el-button>
+                  <template #dropdown>
+                    <el-dropdown-menu>
+                      <el-dropdown-item
+                        command="handleDelete"
+                        v-if="checkPermi(['system:user:delete'])"
+                      >
+                        <Icon icon="ep:delete" />删除
+                      </el-dropdown-item>
+                      <el-dropdown-item
+                        command="handleResetPwd"
+                        v-if="checkPermi(['system:user:update-password'])"
+                      >
+                        <Icon icon="ep:key" />重置密码
+                      </el-dropdown-item>
+                      <el-dropdown-item
+                        command="handleRole"
+                        v-if="checkPermi(['system:permission:assign-user-role'])"
+                      >
+                        <Icon icon="ep:circle-check" />分配角色
+                      </el-dropdown-item>
+                    </el-dropdown-menu>
+                  </template>
+                </el-dropdown>
+              </div>
+            </template>
+          </el-table-column>
+        </el-table>
+        <Pagination
+          :total="total"
+          v-model:page="queryParams.pageNo"
+          v-model:limit="queryParams.pageSize"
+          @pagination="getList"
+        />
+      </ContentWrap>
+    </el-col>
+  </el-row>
+
+  <!-- 添加或修改用户对话框 -->
+  <UserForm ref="formRef" @success="getList" />
+  <!-- 用户导入对话框 -->
+  <UserImportForm ref="importFormRef" @success="getList" />
+  <!-- 分配角色 -->
+  <UserAssignRoleForm ref="assignRoleFormRef" @success="getList" />
+</template>
+<script lang="ts" setup>
+import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
+import { checkPermi } from '@/utils/permission'
+import { dateFormatter } from '@/utils/formatTime'
+import download from '@/utils/download'
+import { CommonStatusEnum } from '@/utils/constants'
+import * as UserApi from '@/api/system/user'
+import UserForm from './UserForm.vue'
+import UserImportForm from './UserImportForm.vue'
+import UserAssignRoleForm from './UserAssignRoleForm.vue'
+import DeptTree from './DeptTree.vue'
+
+defineOptions({ name: 'SystemUser' })
+
+const message = useMessage() // 消息弹窗
+const { t } = useI18n() // 国际化
+
+const loading = ref(true) // 列表的加载中
+const total = ref(0) // 列表的总页数
+const list = ref([]) // 列表的数
+const queryParams = reactive({
+  pageNo: 1,
+  pageSize: 10,
+  username: undefined,
+  mobile: undefined,
+  status: undefined,
+  deptId: undefined,
+  createTime: []
+})
+const queryFormRef = ref() // 搜索的表单
+
+/** 查询列表 */
+const getList = async () => {
+  loading.value = true
+  try {
+    const data = await UserApi.getDeptTeacherPage(queryParams)
+    list.value = data.list
+    total.value = data.total
+  } finally {
+    loading.value = false
+  }
+}
+
+/** 搜索按钮操作 */
+const handleQuery = () => {
+  queryParams.pageNo = 1
+  getList()
+}
+
+/** 重置按钮操作 */
+const resetQuery = () => {
+  queryFormRef.value?.resetFields()
+  handleQuery()
+}
+
+
+/** 添加/修改操作 */
+const formRef = ref()
+const openForm = (type: string, id?: number) => {
+  formRef.value.open(type, id)
+}
+
+/** 用户导入 */
+const importFormRef = ref()
+const handleImport = () => {
+  importFormRef.value.open()
+}
+
+/** 修改用户状态 */
+const handleStatusChange = async (row: UserApi.UserVO) => {
+  try {
+    // 修改状态的二次确认
+    const text = row.status === CommonStatusEnum.ENABLE ? '启用' : '停用'
+    await message.confirm('确认要"' + text + '""' + row.username + '"用户吗?')
+    // 发起修改状态
+    await UserApi.updateUserStatus(row.id, row.status)
+    // 刷新列表
+    await getList()
+  } catch {
+    // 取消后,进行恢复按钮
+    row.status =
+      row.status === CommonStatusEnum.ENABLE ? CommonStatusEnum.DISABLE : CommonStatusEnum.ENABLE
+  }
+}
+
+/** 导出按钮操作 */
+const exportLoading = ref(false)
+const handleExport = async () => {
+  try {
+    // 导出的二次确认
+    await message.exportConfirm()
+    // 发起导出
+    exportLoading.value = true
+    const data = await UserApi.exportUser(queryParams)
+    download.excel(data, '用户数据.xls')
+  } catch {
+  } finally {
+    exportLoading.value = false
+  }
+}
+
+/** 操作分发 */
+const handleCommand = (command: string, row: UserApi.UserVO) => {
+  switch (command) {
+    case 'handleDelete':
+      handleDelete(row.id)
+      break
+    case 'handleResetPwd':
+      handleResetPwd(row)
+      break
+    case 'handleRole':
+      handleRole(row)
+      break
+    default:
+      break
+  }
+}
+
+/** 删除按钮操作 */
+const handleDelete = async (id: number) => {
+  try {
+    // 删除的二次确认
+    await message.delConfirm()
+    // 发起删除
+    await UserApi.deleteUser(id)
+    message.success(t('common.delSuccess'))
+    // 刷新列表
+    await getList()
+  } catch {}
+}
+
+/** 重置密码 */
+const handleResetPwd = async (row: UserApi.UserVO) => {
+  try {
+    // 重置的二次确认
+    const result = await message.prompt(
+      '请输入"' + row.username + '"的新密码',
+      t('common.reminder')
+    )
+    const password = result.value
+    // 发起重置
+    await UserApi.resetUserPwd(row.id, password)
+    message.success('修改成功,新密码是:' + password)
+  } catch {}
+}
+
+/** 分配角色 */
+const assignRoleFormRef = ref()
+const handleRole = (row: UserApi.UserVO) => {
+  assignRoleFormRef.value.open(row)
+}
+
+/** 初始化 */
+onMounted(() => {
+  getList()
+})
+</script>

+ 149 - 0
src/views/system/workroomTeacher/userAchievement/UserAchievementForm.vue

@@ -0,0 +1,149 @@
+<template>
+  <Dialog :title="dialogTitle" v-model="dialogVisible">
+    <el-form
+      ref="formRef"
+      :model="formData"
+      :rules="formRules"
+      label-width="100px"
+      v-loading="formLoading"
+    >
+      <el-form-item label="用户名称" prop="userName">
+        <el-select v-model="formData.userName" placeholder="请输入用户名称" @change="handleUserChange" filterable>
+          <el-option
+            v-for="user in users"
+            :key="user.id"
+            :label="user.nickname"
+            :value="user.nickname"
+          />
+           <!-- 隐藏的输入框用于存储 userId -->
+          <el-input type="hidden" v-model="formData.userId" />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="成果名称" prop="achievementName">
+        <el-input v-model="formData.achievementName" placeholder="请输入成果名称" />
+      </el-form-item>
+      <el-form-item label="成果类型" prop="achievementType">
+        <el-select v-model="formData.achievementType" placeholder="请选择成果类型">
+          <el-option :label="'论文'" :value="1" />
+          <el-option :label="'专利'" :value="2" />
+          <el-option :label="'著作'" :value="3" />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="照片详情url" prop="detail">
+        <UploadImg v-model="formData.detail" />
+      </el-form-item>
+    </el-form>
+    <template #footer>
+      <el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button>
+      <el-button @click="dialogVisible = false">取 消</el-button>
+    </template>
+  </Dialog>
+</template>
+<script setup lang="ts">
+import { UserAchievementApi, UserAchievementVO } from '@/api/system/userAchievement'
+import * as UserApi from '@/api/system/user'
+
+
+/** 成果 表单 */
+defineOptions({ name: 'UserAchievementForm' })
+
+const { t } = useI18n() // 国际化
+const message = useMessage() // 消息弹窗
+
+const dialogVisible = ref(false) // 弹窗的是否展示
+const dialogTitle = ref('') // 弹窗的标题
+const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
+const formType = ref('') // 表单的类型:create - 新增;update - 修改
+const formData = ref({
+  id: undefined,
+  userId: undefined,
+  achievementName: undefined,
+  achievementType: undefined,
+  userName: undefined,
+  detail: undefined,
+})
+const formRules = reactive({
+  userId: [{ required: true, message: '用户id不能为空', trigger: 'blur' }],
+  achievementName: [{ required: true, message: '成果名称不能为空', trigger: 'blur' }],
+  achievementType: [{ required: true, message: '成果类型不能为空', trigger: 'change' }],
+  userName: [{ required: true, message: '用户名称不能为空', trigger: 'blur' }],
+})
+const formRef = ref() // 表单 Ref
+
+/** 获取用户列表 */
+const users = ref();
+const getDeptUser = async () => {
+    try {
+        const response = await UserApi.getDeptUser();
+        console.log(response);
+        users.value = response;
+    } catch (error) {
+        console.error("Error fetching user data:", error);
+    }
+};
+
+/** 打开弹窗 */
+const open = async (type: string, id?: number) => {
+  dialogVisible.value = true
+  dialogTitle.value = t('action.' + type)
+  formType.value = type
+  resetForm()
+  // 修改时,设置数据
+  if (id) {
+    formLoading.value = true
+    try {
+      formData.value = await UserAchievementApi.getUserAchievement(id)
+    } finally {
+      formLoading.value = false
+    }
+  }
+}
+defineExpose({ open }) // 提供 open 方法,用于打开弹窗
+
+/** 用户id更新 */
+const handleUserChange = (selectedNickname: string) => {
+  const selectedUser = users.value.find((user: any) => user.nickname === selectedNickname);
+  formData.value.userId = selectedUser? selectedUser.id : undefined;
+};
+
+/** 提交表单 */
+const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
+const submitForm = async () => {
+  // 校验表单
+  await formRef.value.validate()
+  // 提交请求
+  formLoading.value = true
+  try {
+    const data = formData.value as unknown as UserAchievementVO
+    if (formType.value === 'create') {
+      await UserAchievementApi.createUserAchievement(data)
+      message.success(t('common.createSuccess'))
+    } else {
+      await UserAchievementApi.updateUserAchievement(data)
+      message.success(t('common.updateSuccess'))
+    }
+    dialogVisible.value = false
+    // 发送操作成功的事件
+    emit('success')
+  } finally {
+    formLoading.value = false
+  }
+}
+
+/** 重置表单 */
+const resetForm = () => {
+  formData.value = {
+    id: undefined,
+    userId: undefined,
+    achievementName: undefined,
+    achievementType: undefined,
+    userName: undefined,
+    detail: undefined,
+  }
+  formRef.value?.resetFields()
+}
+
+onMounted(() => {
+  getDeptUser();
+})
+</script>

+ 243 - 0
src/views/system/workroomTeacher/userAchievement/index.vue

@@ -0,0 +1,243 @@
+<template>
+  <ContentWrap>
+    <!-- 搜索工作栏 -->
+    <el-form
+      class="-mb-15px"
+      :model="queryParams"
+      ref="queryFormRef"
+      :inline="true"
+      label-width="68px"
+    >
+          <el-form-item label="用户名称" prop="userName">
+        <el-select
+          v-model="queryParams.userName"
+          placeholder="请选择用户名称"
+          clearable
+          filterable
+          class="!w-240px"
+        >
+          <el-option
+            v-for="user in users"
+            :key="user.id"
+            :label="user.nickname"
+            :value="user.nickname"
+          />
+        </el-select>
+      </el-form-item>
+
+      <el-form-item label="成果名称" prop="achievementName">
+        <el-input
+          v-model="queryParams.achievementName"
+          placeholder="请输入成果名称"
+          clearable
+          @keyup.enter="handleQuery"
+          class="!w-240px"
+        />
+      </el-form-item>
+      <el-form-item label="成果类型" prop="achievementType">
+        <el-select
+          v-model="queryParams.achievementType"
+          placeholder="请选择成果类型"
+          clearable
+          class="!w-240px"
+        >
+          <el-option :label="'论文'" :value="1" />
+          <el-option :label="'专利'" :value="2" />
+          <el-option :label="'著作'" :value="3" />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="创建时间" prop="createTime">
+        <el-date-picker
+          v-model="queryParams.createTime"
+          value-format="YYYY-MM-DD HH:mm:ss"
+          type="daterange"
+          start-placeholder="开始日期"
+          end-placeholder="结束日期"
+          :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
+          class="!w-220px"
+        />
+      </el-form-item>
+      <el-form-item>
+        <el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
+        <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
+        <el-button
+          type="primary"
+          plain
+          @click="openForm('create')"
+          v-hasPermi="['system:user-achievement:create']"
+        >
+          <Icon icon="ep:plus" class="mr-5px" /> 新增
+        </el-button>
+        <el-button
+          type="success"
+          plain
+          @click="handleExport"
+          :loading="exportLoading"
+          v-hasPermi="['system:user-achievement:export']"
+        >
+          <Icon icon="ep:download" class="mr-5px" /> 导出
+        </el-button>
+      </el-form-item>
+    </el-form>
+  </ContentWrap>
+
+  <!-- 列表 -->
+  <ContentWrap>
+    <el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
+      <el-table-column label="Id" align="center" prop="id" />
+      <el-table-column label="用户 Id" align="center" prop="userId" v-if="false" />
+      <el-table-column label="用户名称" align="center" prop="userName" />
+      <el-table-column label="成果名称" align="center" prop="achievementName" />
+      <el-table-column label="成果类型" align="center" prop="achievementType">
+        <template #default="rowData">
+          <span>
+            {{
+              rowData.row.achievementType == 1
+               ? '论文'
+                : rowData.row.achievementType ==2
+               ? '专利'
+                : rowData.row.achievementType == 3
+               ? '著作'
+                : '未知类型'
+            }}
+          </span>
+        </template>
+      </el-table-column>
+      <el-table-column
+        label="创建时间"
+        align="center"
+        prop="createTime"
+        :formatter="dateFormatter"
+        width="180px"
+      />
+      <el-table-column label="照片详情 url" align="center">
+        <!-- #default获取当前·行数据 -->
+        <template #default="{ row }">
+          <a :href="row.detail" target="_blank" v-if="row.detail">{{ row.detail }}</a>
+        </template>
+      </el-table-column>
+      <el-table-column label="操作" align="center" min-width="120px">
+        <template #default="rowData">
+          <el-button
+            link
+            type="primary"
+            @click="openForm('update', rowData.row.id)"
+            v-hasPermi="['system:user-achievement:update']"
+          >
+            编辑
+          </el-button>
+          <el-button
+            link
+            type="danger"
+            @click="handleDelete(rowData.row.id)"
+            v-hasPermi="['system:user-achievement:delete']"
+          >
+            删除
+          </el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+    <!-- 分页 -->
+    <Pagination
+      :total="total"
+      v-model:page="queryParams.pageNo"
+      v-model:limit="queryParams.pageSize"
+      @pagination="getList"
+    />
+  </ContentWrap>
+
+  <!-- 表单弹窗:添加/修改 -->
+  <UserAchievementForm ref="formRef" @success="getList" />
+</template>
+
+<script setup lang="ts">
+import { dateFormatter } from '@/utils/formatTime'
+import download from '@/utils/download'
+import { UserAchievementApi, UserAchievementVO } from '@/api/system/userAchievement'
+import UserAchievementForm from './UserAchievementForm.vue'
+import * as UserApi from '@/api/system/user'
+
+/** 成果 列表 */
+defineOptions({ name: 'UserAchievement' })
+
+const message = useMessage() // 消息弹窗
+const { t } = useI18n() // 国际化
+
+const loading = ref(true) // 列表的加载中
+const list = ref<UserAchievementVO[]>([]) // 列表的数据
+const total = ref(0) // 列表的总页数
+const queryParams = reactive({
+  pageNo: 1,
+  pageSize: 10,
+  userId: undefined,
+  achievementName: undefined,
+  achievementType: undefined,
+  userName: undefined,
+  createTime: [],
+  detail: undefined,
+})
+const queryFormRef = ref() // 搜索的表单
+const exportLoading = ref(false) // 导出的加载中
+
+const users = ref()
+const getDeptUser = async () => {
+  try {
+    const response = await UserApi.getDeptUser()
+    users.value = response
+  } catch (error) {
+    console.error('Error fetching user data:', error)
+  }
+}
+
+const getList = async () => {
+  loading.value = true
+  try {
+    const data = await UserAchievementApi.getUserAchievementPage(queryParams)
+    list.value = data.list
+    total.value = data.total
+  } finally {
+    loading.value = false
+  }
+}
+
+const handleQuery = () => {
+  queryParams.pageNo = 1
+  getList()
+}
+
+const resetQuery = () => {
+  queryFormRef.value.resetFields()
+  handleQuery()
+}
+
+const formRef = ref()
+const openForm = (type: string, id?: number) => {
+  formRef.value.open(type, id)
+}
+
+const handleDelete = async (id: number) => {
+  try {
+    await message.delConfirm()
+    await UserAchievementApi.deleteUserAchievement(id)
+    message.success(t('common.delSuccess'))
+    await getList()
+  } catch {}
+}
+
+const handleExport = async () => {
+  try {
+    await message.exportConfirm()
+    exportLoading.value = true
+    const data = await UserAchievementApi.exportUserAchievement(queryParams)
+    download.excel(data, '成果.xls')
+  } catch {
+  } finally {
+    exportLoading.value = false
+  }
+}
+
+onMounted(() => {
+  getList()
+  getDeptUser()
+})
+</script>