Skip to content

Commit b148bc0

Browse files
feat: User Batch Import
1 parent 761c3c8 commit b148bc0

File tree

8 files changed

+131
-15
lines changed

8 files changed

+131
-15
lines changed

backend/apps/system/api/user.py

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
from collections import defaultdict
22
from typing import Optional
3-
from fastapi import APIRouter, Path, Query
3+
from fastapi import APIRouter, File, Path, Query, UploadFile
44
from pydantic import Field
55
from sqlmodel import SQLModel, or_, select, delete as sqlmodel_delete
66
from apps.system.crud.user import check_account_exists, check_email_exists, check_email_format, check_pwd_format, get_db_user, single_delete, user_ws_options
7+
from apps.system.crud.user_excel import batchUpload, downTemplate
78
from apps.system.models.system_model import UserWsModel, WorkspaceModel
89
from apps.system.models.user import UserModel
910
from apps.system.schemas.auth import CacheName, CacheNamespace
@@ -19,6 +20,15 @@
1920

2021
router = APIRouter(tags=["system_user"], prefix="/user")
2122

23+
24+
@router.get("/template")
25+
async def templateExcel(trans: Trans):
26+
return await downTemplate(trans)
27+
28+
@router.post("/upload")
29+
async def upload_excel(trans: Trans, current_user: CurrentUser, file: UploadFile = File(...)):
30+
batchUpload(trans, file)
31+
2232
@router.get("/info", summary=f"{PLACEHOLDER_PREFIX}system_user_current_user", description=f"{PLACEHOLDER_PREFIX}system_user_current_user_desc")
2333
async def user_info(current_user: CurrentUser) -> UserInfoDTO:
2434
return current_user
@@ -266,4 +276,12 @@ async def statusChange(session: SessionDep, current_user: CurrentUser, trans: Tr
266276
db_user: UserModel = get_db_user(session=session, user_id=statusDto.id)
267277
db_user.status = status
268278
session.add(db_user)
269-
session.commit()
279+
session.commit()
280+
281+
282+
283+
""" async def batchUpload():
284+
pass
285+
286+
async def errorData():
287+
pass """
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
2+
3+
import asyncio
4+
from http.client import HTTPException
5+
import io
6+
from fastapi.responses import StreamingResponse
7+
import pandas as pd
8+
9+
10+
async def downTemplate(trans):
11+
def inner():
12+
data = {
13+
trans('i18n_user.account'): ['sqlbot1', 'sqlbot2'],
14+
trans('i18n_user.name'): ['sqlbot_employee1', 'sqlbot_employee2'],
15+
trans('i18n_user.email'): ['employee1@sqlbot.com', 'employee2@sqlbot.com'],
16+
trans('i18n_user.workspace'): [trans('i18n_default_workspace'), trans('i18n_default_workspace')],
17+
trans('i18n_user.role'): [trans('i18n_user.administrator'), trans('i18n_user.ordinary_member')],
18+
trans('i18n_user.status'): [trans('i18n_user.status_enabled'), trans('i18n_user.status_disabled')],
19+
trans('i18n_user.origin'): [trans('i18n_user.local_creation'), trans('i18n_user.local_creation')],
20+
trans('i18n_user.platform_user_id'): [None, None],
21+
}
22+
df = pd.DataFrame(data)
23+
buffer = io.BytesIO()
24+
with pd.ExcelWriter(buffer, engine='xlsxwriter', engine_kwargs={'options': {'strings_to_numbers': False}}) as writer:
25+
df.to_excel(writer, sheet_name='Sheet1', index=False)
26+
27+
workbook = writer.book
28+
worksheet = writer.sheets['Sheet1']
29+
30+
header_format = workbook.add_format({
31+
'bold': True,
32+
'font_size': 12,
33+
'font_name': '微软雅黑',
34+
'align': 'center',
35+
'valign': 'vcenter',
36+
'border': 0,
37+
'text_wrap': False,
38+
})
39+
40+
for i, col in enumerate(df.columns):
41+
max_length = max(
42+
len(str(col).encode('utf-8')) * 1.1,
43+
(df[col].astype(str)).apply(len).max()
44+
)
45+
worksheet.set_column(i, i, max_length + 12)
46+
47+
worksheet.write(0, i, col, header_format)
48+
49+
50+
worksheet.set_row(0, 30)
51+
for row in range(1, len(df) + 1):
52+
worksheet.set_row(row, 25)
53+
54+
buffer.seek(0)
55+
return io.BytesIO(buffer.getvalue())
56+
57+
result = await asyncio.to_thread(inner)
58+
return StreamingResponse(result, media_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
59+
60+
async def batchUpload(trans, file):
61+
ALLOWED_EXTENSIONS = {"xlsx", "xls"}
62+
if not file.filename.lower().endswith(tuple(ALLOWED_EXTENSIONS)):
63+
raise HTTPException(400, "Only support .xlsx/.xls")
64+
pass

backend/locales/en.json

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,18 @@
2020
"email": "Email",
2121
"password": "Password",
2222
"language_not_support": "The system does not support [{key}] language!",
23-
"ws_miss": "Current user is not in the workspace [{ws}]!"
23+
"ws_miss": "Current user is not in the workspace [{ws}]!",
24+
"name": "Name",
25+
"status": "User Status",
26+
"origin": "User Source",
27+
"workspace": "Workspace",
28+
"role": "Role",
29+
"platform_user_id": "External User Unique Identifier",
30+
"administrator": "Administrator",
31+
"ordinary_member": "Member",
32+
"status_enabled": "Enabled",
33+
"status_disabled": "Disabled",
34+
"local_creation": "Local"
2435
},
2536
"i18n_ws": {
2637
"title": "Workspace"

backend/locales/ko-KR.json

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,18 @@
2020
"email": "이메일",
2121
"password": "비밀번호",
2222
"language_not_support": "시스템이 [{key}] 언어를 지원하지 않습니다!",
23-
"ws_miss": "현재 사용자가 [{ws}] 작업 공간에 속해 있지 않습니다!"
23+
"ws_miss": "현재 사용자가 [{ws}] 작업 공간에 속해 있지 않습니다!",
24+
"name": "성명",
25+
"status": "사용자 상태",
26+
"origin": "사용자 출처",
27+
"workspace": "작업 공간",
28+
"role": "역할",
29+
"platform_user_id": "외부 사용자 고유 식별자",
30+
"administrator": "관리자",
31+
"ordinary_member": "일반 멤버",
32+
"status_enabled": "활성화됨",
33+
"status_disabled": "비활성화됨",
34+
"local_creation": "로컬 생성"
2435
},
2536
"i18n_ws": {
2637
"title": "작업 공간"

backend/locales/zh-CN.json

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,18 @@
2020
"email": "邮箱",
2121
"password": "密码",
2222
"language_not_support": "系统不支持[{key}]语言!",
23-
"ws_miss": "当前用户不在工作空间[{ws}]中!"
23+
"ws_miss": "当前用户不在工作空间[{ws}]中!",
24+
"name": "姓名",
25+
"status": "用户状态",
26+
"origin": "用户来源",
27+
"workspace": "工作空间",
28+
"role": "角色",
29+
"platform_user_id": "外部用户唯一标识",
30+
"administrator": "管理员",
31+
"ordinary_member": "普通成员",
32+
"status_enabled": "已启用",
33+
"status_disabled": "已禁用",
34+
"local_creation": "本地创建"
2435
},
2536
"i18n_ws": {
2637
"title": "工作空间"

frontend/src/api/user.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { request } from '@/utils/request'
22

33
export const userImportApi = {
4-
downExcelTemplateApi: () => request.post('/user/excelTemplate', {}, { responseType: 'blob' }),
4+
downExcelTemplateApi: () => request.get('/user/template', { responseType: 'blob' }),
55
importUserApi: (data: any) =>
66
request.post('/user/batchImport', data, {
77
headers: {

frontend/src/views/system/user/User.vue

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,12 @@
2323
</template>
2424
{{ $t('user.filter') }}
2525
</el-button>
26-
<!-- <el-button secondary @click="handleUserImport">
26+
<el-button secondary @click="handleUserImport">
2727
<template #icon>
2828
<ccmUpload></ccmUpload>
2929
</template>
3030
{{ $t('user.batch_import') }}
31-
</el-button> -->
31+
</el-button>
3232
<el-button type="primary" @click="editHandler(null)">
3333
<template #icon>
3434
<icon_add_outlined></icon_add_outlined>
@@ -393,7 +393,7 @@ import IconLock from '@/assets/svg/icon-key_outlined.svg'
393393
import IconOpeEdit from '@/assets/svg/icon_edit_outlined.svg'
394394
import IconOpeDelete from '@/assets/svg/icon_delete.svg'
395395
import iconFilter from '@/assets/svg/icon-filter_outlined.svg'
396-
// import ccmUpload from '@/assets/svg/icon_ccm-upload_outlined.svg'
396+
import ccmUpload from '@/assets/svg/icon_ccm-upload_outlined.svg'
397397
import icon_add_outlined from '@/assets/svg/icon_add_outlined.svg'
398398
import { userApi } from '@/api/user'
399399
import { workspaceList } from '@/api/workspace'
@@ -579,9 +579,9 @@ const handleEditPassword = (id: any) => {
579579
})
580580
}
581581
582-
// const handleUserImport = () => {
583-
// userImportRef.value.showDialog()
584-
// }
582+
const handleUserImport = () => {
583+
userImportRef.value.showDialog()
584+
}
585585
586586
const handleConfirmPassword = () => {
587587
passwordRef.value.validate((val: any) => {

frontend/src/views/system/user/UserImport.vue

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -91,8 +91,9 @@ const downExcel = () => {
9191
userImportApi
9292
.downExcelTemplateApi()
9393
.then((res) => {
94-
const blobData = res.data
95-
const blob = new Blob([blobData], { type: 'application/vnd.ms-excel' })
94+
const blob = new Blob([res], {
95+
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
96+
})
9697
const link = document.createElement('a')
9798
link.style.display = 'none'
9899
link.href = URL.createObjectURL(blob)
@@ -258,7 +259,7 @@ defineExpose({
258259
<el-dialog
259260
v-model="dialogShow"
260261
:title="t('user.batch_import')"
261-
width="400px"
262+
width="600px"
262263
modal-class="user-import-class"
263264
@before-close="closeDialog"
264265
>

0 commit comments

Comments
 (0)