feat(system): implement robust batch user creation integrating existing pinyin logic and backend duplication prevention
This commit is contained in:
@ -174,6 +174,49 @@ def create_user():
|
|||||||
return jsonify({'msg': str(e)}), 400
|
return jsonify({'msg': str(e)}), 400
|
||||||
|
|
||||||
|
|
||||||
|
# ==============================================================================
|
||||||
|
# 批量创建用户
|
||||||
|
# ==============================================================================
|
||||||
|
@auth_bp.route('/user/batch', methods=['POST'])
|
||||||
|
@jwt_required()
|
||||||
|
def batch_create_user():
|
||||||
|
try:
|
||||||
|
data_list = request.get_json()
|
||||||
|
if not data_list or not isinstance(data_list, list):
|
||||||
|
return jsonify({'msg': '请求数据必须是用户数组'}), 400
|
||||||
|
|
||||||
|
# 数据清洗:移除用户没有权限的字段
|
||||||
|
user_permissions = get_current_user_permissions()
|
||||||
|
for data in data_list:
|
||||||
|
if 'system_user:*' not in user_permissions:
|
||||||
|
field_to_perm = {
|
||||||
|
'cn_name': 'system_user:username',
|
||||||
|
'username': 'system_user:username',
|
||||||
|
'password': 'system_user:password',
|
||||||
|
'department': 'system_user:department',
|
||||||
|
'role': 'system_user:role',
|
||||||
|
'email': 'system_user:email',
|
||||||
|
}
|
||||||
|
for field in list(data.keys()):
|
||||||
|
perm_code = field_to_perm.get(field)
|
||||||
|
if field == 'password':
|
||||||
|
if 'system_user:operation' not in user_permissions:
|
||||||
|
data.pop(field, None)
|
||||||
|
continue
|
||||||
|
if perm_code and perm_code not in user_permissions:
|
||||||
|
data.pop(field, None)
|
||||||
|
|
||||||
|
claims = get_jwt()
|
||||||
|
operator_role = claims.get('role')
|
||||||
|
|
||||||
|
results = AuthService.batch_create_users(data_list, operator_role)
|
||||||
|
return jsonify({'msg': '批量处理完成', 'data': results}), 200
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
current_app.logger.error(f"Batch User Create Failed: {str(e)}")
|
||||||
|
return jsonify({'msg': str(e)}), 500
|
||||||
|
|
||||||
|
|
||||||
# ==============================================================================
|
# ==============================================================================
|
||||||
# 更新用户(管理员)
|
# 更新用户(管理员)
|
||||||
# ==============================================================================
|
# ==============================================================================
|
||||||
|
|||||||
@ -260,6 +260,29 @@ class AuthService:
|
|||||||
# 返回时,最好把生成的ID告诉前端
|
# 返回时,最好把生成的ID告诉前端
|
||||||
return new_user.to_dict()
|
return new_user.to_dict()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def batch_create_users(data_list, operator_role):
|
||||||
|
"""
|
||||||
|
批量创建新用户。复用 create_user 的核心防重逻辑。
|
||||||
|
"""
|
||||||
|
results = []
|
||||||
|
for data in data_list:
|
||||||
|
try:
|
||||||
|
# 复用单条创建逻辑,它自带张三/zhangsan1的防重机制
|
||||||
|
new_user_dict = AuthService.create_user(data, operator_role)
|
||||||
|
results.append({
|
||||||
|
"cn_name": data.get('cn_name'),
|
||||||
|
"account_id": new_user_dict.get('account_id'),
|
||||||
|
"status": "success"
|
||||||
|
})
|
||||||
|
except Exception as e:
|
||||||
|
results.append({
|
||||||
|
"cn_name": data.get('cn_name'),
|
||||||
|
"error": str(e),
|
||||||
|
"status": "fail"
|
||||||
|
})
|
||||||
|
return results
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def update_user(user_id, data, operator_role):
|
def update_user(user_id, data, operator_role):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@ -67,3 +67,12 @@ export function changeMyPassword(data: { new_password: string; confirm_password:
|
|||||||
data
|
data
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 【新增】批量创建用户
|
||||||
|
export function batchCreateUser(data: any[]) {
|
||||||
|
return request({
|
||||||
|
url: '/v1/auth/user/batch',
|
||||||
|
method: 'post',
|
||||||
|
data
|
||||||
|
})
|
||||||
|
}
|
||||||
@ -4,10 +4,15 @@
|
|||||||
<template #header>
|
<template #header>
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<span style="font-weight: bold;">员工账号管理</span>
|
<span style="font-weight: bold;">员工账号管理</span>
|
||||||
|
<div>
|
||||||
|
<el-button v-if="userStore.hasPermission('system_user:operation')" type="success" @click="batchDialogVisible = true">
|
||||||
|
批量新增
|
||||||
|
</el-button>
|
||||||
<el-button v-if="userStore.hasPermission('system_user:operation')" type="primary" @click="handleCreate">
|
<el-button v-if="userStore.hasPermission('system_user:operation')" type="primary" @click="handleCreate">
|
||||||
+ 新增员工
|
+ 新增员工
|
||||||
</el-button>
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<el-table
|
<el-table
|
||||||
@ -138,12 +143,52 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
|
|
||||||
|
<!-- 批量新增弹窗 -->
|
||||||
|
<el-dialog v-model="batchDialogVisible" title="批量新增员工" width="600px" destroy-on-close @close="batchForm.namesText = ''">
|
||||||
|
<el-form :model="batchForm" label-width="100px">
|
||||||
|
<el-form-item label="所属部门" required>
|
||||||
|
<el-select v-model="batchForm.department" style="width: 100%" placeholder="请选择部门">
|
||||||
|
<el-option v-for="d in departmentOptions" :key="d" :label="d" :value="d" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="系统角色" required>
|
||||||
|
<el-select v-model="batchForm.role" style="width: 100%" placeholder="请选择角色">
|
||||||
|
<el-option v-for="r in roleOptions" :key="r.value" :label="r.label" :value="r.value" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="员工名单" required>
|
||||||
|
<el-input type="textarea" v-model="batchForm.namesText" :rows="8" placeholder="请输入真实姓名,每行一个。密码默认统一为 123456" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<template #footer>
|
||||||
|
<el-button @click="batchDialogVisible = false">取消</el-button>
|
||||||
|
<el-button type="primary" :loading="batchSubmitting" @click="handleBatchSubmit">确认创建</el-button>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
|
||||||
|
<!-- 批量创建结果弹窗 -->
|
||||||
|
<el-dialog v-model="batchResultVisible" title="批量创建结果" width="600px">
|
||||||
|
<div style="margin-bottom: 10px; color: #67C23A; font-weight: bold;">请复制以下账号分发给员工:</div>
|
||||||
|
<el-table :data="batchResults" border height="400px">
|
||||||
|
<el-table-column prop="cn_name" label="姓名" width="150" />
|
||||||
|
<el-table-column label="生成账号">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<span v-if="row.status === 'success'" style="color: #409EFF; font-weight: bold;">{{ row.account_id }}</span>
|
||||||
|
<span v-else style="color: #F56C6C">{{ row.error }}</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
<template #footer>
|
||||||
|
<el-button @click="batchResultVisible = false">关闭</el-button>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { reactive, ref, onMounted, computed } from 'vue'
|
import { reactive, ref, onMounted, computed } from 'vue'
|
||||||
import { createUser, updateUser, getUserList, deleteUser } from '@/api/auth'
|
import { createUser, updateUser, getUserList, deleteUser, batchCreateUser } from '@/api/auth'
|
||||||
import { useUserStore } from '@/stores/user'
|
import { useUserStore } from '@/stores/user'
|
||||||
import { ElMessage } from 'element-plus'
|
import { ElMessage } from 'element-plus'
|
||||||
import { pinyin } from 'pinyin-pro' // ★ 务必安装: npm install pinyin-pro
|
import { pinyin } from 'pinyin-pro' // ★ 务必安装: npm install pinyin-pro
|
||||||
@ -199,6 +244,17 @@ const form = reactive({
|
|||||||
email: ''
|
email: ''
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 批量新增相关状态
|
||||||
|
const batchDialogVisible = ref(false)
|
||||||
|
const batchResultVisible = ref(false)
|
||||||
|
const batchSubmitting = ref(false)
|
||||||
|
const batchForm = reactive({
|
||||||
|
namesText: '',
|
||||||
|
department: '',
|
||||||
|
role: ''
|
||||||
|
})
|
||||||
|
const batchResults = ref<any[]>([])
|
||||||
|
|
||||||
// ★ 监听中文输入,自动转拼音
|
// ★ 监听中文输入,自动转拼音
|
||||||
const handleNameInput = (val: string) => {
|
const handleNameInput = (val: string) => {
|
||||||
if (isEdit.value) return // 编辑模式下不联动
|
if (isEdit.value) return // 编辑模式下不联动
|
||||||
@ -370,6 +426,43 @@ const onSubmit = async () => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 批量提交逻辑
|
||||||
|
const handleBatchSubmit = async () => {
|
||||||
|
if (!batchForm.department || !batchForm.role || !batchForm.namesText.trim()) {
|
||||||
|
return ElMessage.warning('请填写完整部门、角色及员工名单')
|
||||||
|
}
|
||||||
|
|
||||||
|
const names = batchForm.namesText.split('\n').map(n => n.trim()).filter(n => n)
|
||||||
|
if (names.length === 0) return ElMessage.warning('未能识别到有效姓名')
|
||||||
|
|
||||||
|
const payload = names.map(name => {
|
||||||
|
const pinyinStr = pinyin(name, { toneType: 'none', type: 'array' }).join('').toLowerCase()
|
||||||
|
return {
|
||||||
|
cn_name: name,
|
||||||
|
username: pinyinStr, // 拼音基础串,后端会自动防重
|
||||||
|
password: '123456',
|
||||||
|
department: batchForm.department,
|
||||||
|
role: batchForm.role,
|
||||||
|
email: ''
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
batchSubmitting.value = true
|
||||||
|
try {
|
||||||
|
const res: any = await batchCreateUser(payload)
|
||||||
|
if (res.code === 200 || res.msg === '批量处理完成') {
|
||||||
|
batchResults.value = res.data
|
||||||
|
batchDialogVisible.value = false
|
||||||
|
batchResultVisible.value = true
|
||||||
|
getList() // 刷新底层列表
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
ElMessage.error('批量创建遇到错误')
|
||||||
|
} finally {
|
||||||
|
batchSubmitting.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const resetForm = () => {
|
const resetForm = () => {
|
||||||
if (!formRef.value) return
|
if (!formRef.value) return
|
||||||
formRef.value.resetFields()
|
formRef.value.resetFields()
|
||||||
|
|||||||
Reference in New Issue
Block a user