浏览代码

新增批量注册家长课堂账号、角色、短信通知功能

liyanbo 1 月之前
父节点
当前提交
a07beb6e60

+ 24 - 0
src/api/system/parentClassRegister/index.ts

@@ -0,0 +1,24 @@
+import request from '@/config/axios'
+
+// 家长课堂注册 VO
+export interface ParentClassRegisterVO {
+  id?: number // 主键
+  type: string // 类型:single-单个,batch-批量
+  nickname?: string // 用户昵称
+  mobile: string // 手机号
+  batchMobiles?: string // 批量手机号(多行文本)
+  username?: string // 账号
+  password?: string // 密码
+  deptId?: number // 归属部门
+  roleIds?: string // 角色ID列表(逗号分隔)
+  remark?: string // 备注
+  sendSms: boolean // 是否发送短信
+}
+
+// 家长课堂注册 API
+export const ParentClassRegisterApi = {
+  // 注册家长课堂用户
+  registerParentClass: async (data: ParentClassRegisterVO) => {
+    return await request.post({ url: `/system/user/parent-class-register`, data })
+  }
+}

+ 387 - 0
src/views/system/user/ParentClassRegisterForm.vue

@@ -0,0 +1,387 @@
+<template>
+  <Dialog :title="dialogTitle" v-model="dialogVisible">
+    <el-form
+      ref="formRef"
+      :model="formData"
+      :rules="formRules"
+      label-width="80px"
+      v-loading="formLoading"
+    >
+      <el-form-item label="类型" prop="type">
+        <el-radio-group v-model="formData.type">
+          <el-radio label="single" border>单个</el-radio>
+          <el-radio label="batch" border>批量</el-radio>
+        </el-radio-group>
+      </el-form-item>
+
+      <!-- 单个注册字段 -->
+      <template v-if="formData.type === 'single'">
+        <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="username">
+              <el-input v-model="formData.username" placeholder="请输入账号" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="密码" prop="password">
+              <el-input v-model="formData.password" placeholder="请输入密码" type="password" />
+            </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" placeholder="请输入手机号" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="角色" prop="roleIds">
+              <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-col>
+        </el-row>
+      </template>
+
+      <!-- 批量注册字段 -->
+      <template v-else-if="formData.type === 'batch'">
+        <el-row>
+          <el-col :span="12">
+            <el-form-item label="用户昵称" prop="nickname">
+              <el-input v-model="formData.nickname" placeholder="默认采取手机号尾号4位数" :disabled="true" />
+            </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="username">
+              <el-input v-model="formData.username" placeholder="默认采取手机号" :disabled="true" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="密码" prop="password">
+              <el-input v-model="formData.password" placeholder="默认采取手机号" :disabled="true" />
+            </el-form-item>
+          </el-col>
+        </el-row>
+        <el-row>
+          <el-col :span="12">
+            <el-form-item label="角色" prop="roleIds">
+              <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-col>
+          <el-col :span="12">
+            <el-form-item label="短信通知" prop="sendSms">
+              <el-switch v-model="formData.sendSms" active-text="开启" inactive-text="关闭" />
+            </el-form-item>
+          </el-col>
+        </el-row>
+        <el-row>
+          <el-col :span="24">
+            <el-form-item label="手机号" prop="batchMobiles">
+              <el-input
+                v-model="batchMobilesText"
+                type="textarea"
+                placeholder="请输入多个手机号,每行一个"
+                rows="4"
+              />
+            </el-form-item>
+          </el-col>
+        </el-row>
+      </template>
+
+      <el-row>
+        <el-col :span="24">
+          <el-form-item label="备注" prop="remark">
+            <el-input v-model="formData.remark" type="textarea" placeholder="请输入备注" />
+          </el-form-item>
+        </el-col>
+      </el-row>
+    </el-form>
+
+    <template #footer>
+      <el-button @click="submitForm" type="primary" :loading="formLoading">确 定</el-button>
+      <el-button @click="dialogVisible = false">取 消</el-button>
+    </template>
+  </Dialog>
+</template>
+
+<script setup lang="ts">
+import { ParentClassRegisterApi, ParentClassRegisterVO } from '@/api/system/parentClassRegister'
+import * as DeptApi from '@/api/system/dept'
+import * as RoleApi from '@/api/system/role'
+import { defaultProps, handleTree } from '@/utils/tree'
+
+/** 家长课堂注册 表单 */
+defineOptions({ name: 'ParentClassRegisterForm' })
+
+const { t } = useI18n() // 国际化
+const message = useMessage() // 消息弹窗
+
+const dialogVisible = ref(false) // 弹窗的是否展示
+const dialogTitle = ref('') // 弹窗的标题
+const formLoading = ref(false) // 表单的加载中:提交的按钮禁用
+const formData = ref({
+  type: 'single', // 默认单个
+  nickname: '',
+  mobile: '',
+  batchMobiles: '', // 批量手机号字符串,用于textarea输入
+  username: '',
+  password: '',
+  deptId: '',
+  roleIds: [],
+  remark: '',
+  sendSms: true
+})
+
+const deptList = ref<Tree[]>([]) // 部门树形结构
+const roleList = ref<RoleApi.RoleVO[]>([]) // 角色列表
+
+// 用于批量手机号文本输入
+const batchMobilesText = computed({
+  get: () => formData.value.batchMobiles || '',
+  set: (val: string) => {
+    formData.value.batchMobiles = val
+  }
+})
+
+const formRules = computed(() => {
+  const rules: Record<string, any> = {
+    type: [
+      { required: true, message: '请选择类型', trigger: 'change' }
+    ],
+    mobile: [
+      {
+        required: formData.value.type === 'single',
+        message: '请输入手机号',
+        trigger: 'blur'
+      },
+      {
+        pattern: /^1[3-9]\d{9}$/,
+        message: '请输入正确的手机号码',
+        trigger: 'blur',
+        required: formData.value.type === 'single'
+      }
+    ],
+    nickname: [
+      {
+        required: formData.value.type === 'single',
+        message: '请输入用户昵称',
+        trigger: 'blur'
+      }
+    ],
+    username: [
+      {
+        required: formData.value.type === 'single',
+        message: '请输入账号',
+        trigger: 'blur'
+      }
+    ],
+    deptId: [
+      {
+        required: formData.value.type === 'single',
+        message: '请选择归属部门',
+        trigger: 'change'
+      }
+    ],
+    roleIds: [
+      {
+        required: formData.value.type === 'single' || formData.value.type === 'batch',
+        message: '请选择角色',
+        trigger: 'change'
+      }
+    ],
+    password: [
+      {
+        required: formData.value.type === 'single',
+        message: '请输入密码',
+        trigger: 'blur'
+      }
+    ],
+    batchMobiles: [
+      {
+        required: formData.value.type === 'batch',
+        message: '请输入手机号',
+        trigger: 'blur'
+      }
+    ]
+  }
+
+  return rules
+})
+
+const formRef = ref() // 表单 Ref
+
+/** 打开弹窗 */
+const open = (type: string) => {
+  dialogVisible.value = true
+  dialogTitle.value = '家长课堂注册'
+  resetForm()
+
+  // 设置默认值
+  formData.value.type = 'single'
+
+  // 加载部门树和角色列表
+  loadDeptList()
+  loadRoleList()
+}
+
+/** 加载部门列表 */
+const loadDeptList = async () => {
+  try {
+    deptList.value = handleTree(await DeptApi.getSimpleDeptList())
+  } catch (error) {
+    console.error('加载部门列表失败:', error)
+  }
+}
+
+/** 加载角色列表 */
+const loadRoleList = async () => {
+  try {
+    roleList.value = await RoleApi.getSimpleRoleList()
+  } catch (error) {
+    console.error('加载角色列表失败:', error)
+  }
+}
+
+// 监听类型变化更新默认值
+watch(() => formData.value.type, (newType) => {
+  if (newType === 'single') {
+    // 切换到单个模式时,清空批量数据
+    formData.value.batchMobiles = ''
+    formData.value.nickname = ''
+    formData.value.username = ''
+    formData.value.password = ''
+  } else {
+    // 切换到批量模式时,清空单个数据并设置批量默认值
+    formData.value.mobile = ''
+    formData.value.nickname = '默认采取手机号后4位'
+    formData.value.username = '默认采取手机号'
+    formData.value.password = '默认采取手机号'
+  }
+}, { immediate: true })
+
+/** 提交表单 */
+const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
+const submitForm = async () => {
+  // 校验表单
+  await formRef.value.validate()
+
+  // 准备提交数据
+  const submitData = { ...formData.value }
+
+  // 如果是单个模式,对手机号进行格式校验
+  if (submitData.type === 'single') {
+    // 校验单个手机号格式
+    if (submitData.mobile && !/^1[3-9]\d{9}$/.test(submitData.mobile)) {
+      throw new Error('手机号格式不正确')
+    }
+    delete submitData.batchMobiles
+  }
+  // 如果是批量模式,处理批量手机号
+  else if (submitData.type === 'batch') {
+    // 处理批量手机号,将textarea中的手机号按换行符分割为数组
+    if (submitData.batchMobiles) {
+      submitData.batchMobiles = submitData.batchMobiles.split('\n').filter(phone => phone.trim())
+    }
+
+    // 对每个手机号进行格式校验
+    if (submitData.batchMobiles && submitData.batchMobiles.length > 0) {
+      const invalidMobiles = submitData.batchMobiles.filter(mobile => !/^1[3-9]\d{9}$/.test(mobile))
+      if (invalidMobiles.length > 0) {
+        throw new Error(`以下手机号格式不正确: ${invalidMobiles.join(', ')}`)
+      }
+
+    } else {
+      throw new Error('请输入有效的手机号')
+    }
+
+    // 清理批量模式下的默认值,避免提交给后端
+    if (submitData.nickname === '默认采取手机号尾号4位数') {
+      submitData.nickname = ''
+    }
+    if (submitData.username === '默认采取手机号') {
+      submitData.username = ''
+    }
+    if (submitData.password === '默认采取手机号') {
+      submitData.password = ''
+    }
+  }
+
+  // 提交请求
+  formLoading.value = true
+  try {
+    const data = submitData as unknown as ParentClassRegisterVO
+    await ParentClassRegisterApi.registerParentClass(data)
+    message.success('家长课堂注册成功')
+    dialogVisible.value = false
+    // 发送操作成功的事件
+    emit('success')
+  } finally {
+    formLoading.value = false
+  }
+}
+
+/** 重置表单 */
+const resetForm = () => {
+  formData.value = {
+    type: 'single',
+    nickname: '',
+    mobile: '',
+    batchMobiles: '',
+    username: '',
+    password: '',
+    deptId: '',
+    roleIds: [],
+    remark: '',
+    sendSms: true
+  }
+  formRef.value?.resetFields()
+}
+
+defineExpose({ open }) // 提供 open 方法,用于打开弹窗
+</script>

+ 17 - 0
src/views/system/user/index.vue

@@ -71,6 +71,14 @@
             >
               <Icon icon="ep:plus" /> 新增
             </el-button>
+            <el-button
+              type="danger"
+              plain
+              @click="openParentClassRegister"
+              v-hasPermi="['system:user:create']"
+            >
+              <Icon icon="ep:plus" /> 家长课堂注册
+            </el-button>
             <el-button
               type="warning"
               plain
@@ -195,6 +203,8 @@
   <UserImportForm ref="importFormRef" @success="getList" />
   <!-- 分配角色 -->
   <UserAssignRoleForm ref="assignRoleFormRef" @success="getList" />
+  <!-- 家长课堂注册 -->
+  <ParentClassRegisterForm ref="parentClassRegisterRef" @success="getList" />
 </template>
 <script lang="ts" setup>
 import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
@@ -206,6 +216,7 @@ import * as UserApi from '@/api/system/user'
 import UserForm from './UserForm.vue'
 import UserImportForm from './UserImportForm.vue'
 import UserAssignRoleForm from './UserAssignRoleForm.vue'
+import ParentClassRegisterForm from './ParentClassRegisterForm.vue'
 import DeptTree from './DeptTree.vue'
 
 defineOptions({ name: 'SystemUser' })
@@ -353,6 +364,12 @@ const handleRole = (row: UserApi.UserVO) => {
   assignRoleFormRef.value.open(row)
 }
 
+/** 家长课堂注册 */
+const parentClassRegisterRef = ref()
+const openParentClassRegister = () => {
+  parentClassRegisterRef.value.open('create')
+}
+
 /** 初始化 */
 onMounted(() => {
   getList()