Skip to content

Commit 2de7ece

Browse files
feat: User Management
1 parent 0179b3d commit 2de7ece

File tree

12 files changed

+530
-18
lines changed

12 files changed

+530
-18
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,27 @@
11
from fastapi import APIRouter, Depends, Request
2-
from common.core.deps import CurrentUser
2+
from apps.system.models.user import user_grid
3+
from common.core.deps import CurrentUser, SessionDep
4+
from common.core.pagination import Paginator
5+
from common.core.schemas import PaginatedResponse, PaginationParams
36

47
router = APIRouter(tags=["user"], prefix="/user")
58

69

710
@router.get("/info")
811
async def user_info(current_user: CurrentUser):
9-
return current_user.to_dict()
12+
return current_user.to_dict()
13+
14+
15+
@router.get("/pager/{pageNum}/{pageSize}", response_model=PaginatedResponse[user_grid])
16+
async def pager(
17+
session: SessionDep,
18+
pageNum: int,
19+
pageSize: int
20+
):
21+
pagination = PaginationParams(page=pageNum, size=pageSize)
22+
paginator = Paginator(session)
23+
filters = {}
24+
return await paginator.get_paginated_response(
25+
model=user_grid,
26+
pagination=pagination,
27+
**filters)

backend/apps/system/crud/user.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11

22
from sqlmodel import Session, select
3-
from ..models.user import sys_user
3+
from ..models.user import sys_user, user_grid
44
from common.core.security import verify_md5pwd
55

66
def get_user_by_account(*, session: Session, account: str) -> sys_user | None:
7-
statement = select(sys_user).where(sys_user.account == account)
7+
#statement = select(sys_user).where(sys_user.account == account)
8+
statement = select(user_grid.id, user_grid.account, user_grid.oid, user_grid.password).where(user_grid.account == account)
89
session_user = session.exec(statement).first()
9-
return session_user
10+
result_user = sys_user.model_validate(session_user)
11+
return result_user
1012

1113
def authenticate(*, session: Session, account: str, password: str) -> sys_user | None:
1214
db_user = get_user_by_account(session=session, account=account)

backend/apps/system/models/user.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11

2+
from typing import Optional
23
from sqlmodel import SQLModel, Field
34

4-
class sys_user(SQLModel, table=True):
5+
class sys_user(SQLModel):
56
id: int = Field(primary_key=True, index=True)
67
account: str = Field(max_length=255, unique=True)
78
password: str = Field(max_length=255)
@@ -12,4 +13,12 @@ def to_dict(self):
1213
"id": self.id,
1314
"account": self.account,
1415
"oid": self.oid
15-
}
16+
}
17+
18+
class user_grid(sys_user, table=True):
19+
__tablename__ = "sys_user"
20+
name: str
21+
email: str
22+
status: int
23+
create_time: int
24+

backend/common/core/deps.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,12 @@
55
# from fastapi.security import OAuth2PasswordBearer
66
from jwt.exceptions import InvalidTokenError
77
from pydantic import BaseModel, ValidationError
8-
from sqlmodel import Session
8+
from sqlmodel import Session, select
99
from common.core.schemas import TokenPayload, XOAuth2PasswordBearer
1010
from common.core import security
1111
from common.core.config import settings
1212
from common.core.db import get_session
13-
from apps.system.models.user import sys_user
13+
from apps.system.models.user import sys_user, user_grid
1414
reusable_oauth2 = XOAuth2PasswordBearer(
1515
tokenUrl=f"{settings.API_V1_STR}/login/access-token"
1616
)
@@ -32,7 +32,11 @@ async def get_current_user(session: SessionDep, token: TokenDep) -> sys_user:
3232
status_code=status.HTTP_403_FORBIDDEN,
3333
detail="Could not validate credentials",
3434
)
35-
user = session.get(sys_user, token_data.id)
35+
statement = select(user_grid.id, user_grid.account, user_grid.oid, user_grid.password).where(user_grid.id == token_data.id)
36+
session_user = session.exec(statement).first()
37+
if not session_user:
38+
raise HTTPException(status_code=404, detail="User not found")
39+
user = sys_user.model_validate(session_user)
3640
if not user:
3741
raise HTTPException(status_code=404, detail="User not found")
3842
""" if not user.is_active:

frontend/src/api/auth.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { request } from '@/utils/request'
2+
3+
export const userApi = {
4+
pager: (pageNumber: number, pageSize: number) => request.get(`/user/pager/${pageNumber}/${pageSize}`),
5+
add: (data: any) => request.post('/settings/terminology', data),
6+
edit: (data: any) => request.put('/settings/terminology', data),
7+
delete: (id: number) => request.delete(`/settings/terminology/${id}`),
8+
query: (id: number) => request.get(`/settings/terminology/${id}`)
9+
}

frontend/src/components/layout/index.vue

Lines changed: 56 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@
4545
<h1>{{ currentPageTitle }}</h1>
4646
<div class="header-actions">
4747
<el-tooltip content="System manage" placement="bottom">
48-
<div class="header-icon-btn">
48+
<div class="header-icon-btn" @click="toSystem">
4949
<el-icon><iconsystem /></el-icon>
5050
<span>System manage</span>
5151
</div>
@@ -77,8 +77,27 @@
7777
</div>
7878
</div>
7979

80+
<div v-if="sysRouterList.length && showHead" class="sys-setting-container">
81+
<el-menu
82+
:default-active="activeMenu"
83+
class="el-menu-demo"
84+
mode="horizontal"
85+
>
86+
<el-menu-item v-for="item in sysRouterList" :key="item.path" :index="item.path" @click="menuSelect">
87+
<el-icon v-if="item.meta.icon">
88+
<component :is="resolveIcon(item.meta.icon)" />
89+
</el-icon>
90+
<span>{{ item.meta.title }}</span>
91+
</el-menu-item>
92+
</el-menu>
93+
</div>
8094

81-
<div class="page-content">
95+
<div v-if="sysRouterList.length && showHead" class="sys-page-content">
96+
<div class="sys-inner-container">
97+
<router-view />
98+
</div>
99+
</div>
100+
<div v-else class="page-content">
82101
<router-view />
83102
</div>
84103
</div>
@@ -105,19 +124,31 @@ const route = useRoute()
105124
const userStore = useUserStore()
106125
const name = ref('admin')
107126
const activeMenu = computed(() => route.path)
108-
109127
const routerList = computed(() => {
110128
return router.getRoutes().filter(route => {
111-
return route.path !== '/login' && !route.redirect && route.path !== '/:pathMatch(.*)*'
129+
return route.path !== '/login' && !route.path.includes('/system') && !route.redirect && route.path !== '/:pathMatch(.*)*'
112130
})
113131
})
132+
133+
const sysRouterList = computed(() => {
134+
return router.getRoutes().filter(route => route.path.includes('/system'))
135+
})
136+
137+
const showHead = computed(() => {
138+
return route.path.includes('/system')
139+
})
114140
const workspace = ref('1')
115141
const options = [
116142
{ value: '1', label: 'Default workspace' },
117143
{ value: '2', label: 'Workspace 2' },
118144
{ value: '3', label: 'Workspace 3' }
119145
]
120-
const currentPageTitle = computed(() => route.meta.title || 'Dashboard')
146+
const currentPageTitle = computed(() => {
147+
if (route.path.includes('/system')) {
148+
return 'System Settings'
149+
}
150+
return route.meta.title || 'Dashboard'
151+
})
121152
const resolveIcon = (iconName: any) => {
122153
const icons: Record<string, any> = {
123154
'ds': ds,
@@ -129,13 +160,15 @@ const resolveIcon = (iconName: any) => {
129160
}
130161
131162
const menuSelect = (e: any) => {
132-
console.log(routerList.value)
133163
router.push(e.index)
134164
}
135165
const logout = () => {
136166
userStore.logout()
137167
router.push('/login')
138168
}
169+
const toSystem = () => {
170+
router.push('/system')
171+
}
139172
</script>
140173

141174
<style lang="less" scoped>
@@ -260,6 +293,23 @@ const logout = () => {
260293
flex: 1;
261294
overflow-y: auto;
262295
}
296+
.sys-page-content {
297+
background-color: var(--white);
298+
border-radius: var(--border-radius);
299+
padding: 24px;
300+
box-shadow: var(--shadow);
301+
margin-top: 24px;
302+
.sys-inner-container {
303+
background: #fff;
304+
border-radius: 8px;
305+
padding: 20px;
306+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
307+
}
308+
}
309+
.sys-setting-container {
310+
overflow: hidden;
311+
border-radius: 8px;
312+
}
263313
}
264314
}
265315
</style>

frontend/src/router/index.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,25 @@ const router = createRouter({
6666
meta: { title: 'Setting', icon: 'setting' }
6767
}
6868
]
69+
},
70+
{
71+
path: '/system',
72+
component: Layout,
73+
redirect: '/system/user',
74+
children: [
75+
{
76+
path: 'user',
77+
name: 'user',
78+
component: () => import('@/views/system/user/index.vue'),
79+
meta: { title: 'User Management', icon: 'setting' }
80+
},
81+
{
82+
path: 'model',
83+
name: 'model',
84+
component: () => import('@/views/system/model/index.vue'),
85+
meta: { title: 'AI Model Configuration', icon: 'setting' }
86+
}
87+
]
6988
}
7089
]
7190
})

frontend/src/utils/date.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
export function formatTimestamp(timestamp: number, format: string = 'YYYY-MM-DD'): string {
2+
if (!timestamp) {
3+
return '-'
4+
}
5+
6+
const date = new Date(timestamp)
7+
const year = date.getFullYear()
8+
const month = String(date.getMonth() + 1).padStart(2, '0')
9+
const day = String(date.getDate()).padStart(2, '0')
10+
const hours = String(date.getHours()).padStart(2, '0')
11+
const minutes = String(date.getMinutes()).padStart(2, '0')
12+
const seconds = String(date.getSeconds()).padStart(2, '0')
13+
14+
return format.replace(/YYYY|MM|DD|HH|mm|ss/g, (match) => {
15+
switch (match) {
16+
case 'YYYY':
17+
return String(year)
18+
case 'MM':
19+
return month
20+
case 'DD':
21+
return day
22+
case 'HH':
23+
return hours
24+
case 'mm':
25+
return minutes
26+
case 'ss':
27+
return seconds
28+
default:
29+
return match
30+
}
31+
})
32+
}

frontend/src/utils/request.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ class HttpService {
7171
}
7272

7373
// Request logging
74-
console.log(`[Request] ${config.method?.toUpperCase()} ${config.url}`)
74+
// console.log(`[Request] ${config.method?.toUpperCase()} ${config.url}`)
7575

7676
return config
7777
},

frontend/src/views/login/index.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
<div class="login-form">
1212
<el-card class="login-card">
1313
<h2 class="login-title">Login</h2>
14-
<el-form :model="loginForm" :rules="rules" ref="loginFormRef">
14+
<el-form :model="loginForm" :rules="rules" ref="loginFormRef" @keyup.enter="submitForm">
1515
<el-form-item prop="username">
1616
<el-input v-model="loginForm.username" placeholder="username" prefix-icon="user"></el-input>
1717
</el-form-item>

0 commit comments

Comments
 (0)