feat: user list

This commit is contained in:
xtaodada 2024-11-11 15:14:13 +08:00
parent 65b3a7074b
commit 918ab92940
Signed by: xtaodada
GPG Key ID: 4CBB3F4FA8C85659
6 changed files with 205 additions and 43 deletions

View File

@ -21,30 +21,42 @@
<div class="c-title">用户添加</div>
<el-form v-if="m">
<div class="c-item br">
<label class="c-label">昵称</label>
<label class="c-label">*用户名</label>
<el-input v-model="m.username"></el-input>
</div>
<div class="c-item br">
<label class="c-label">密码:</label>
<label class="c-label">昵称:</label>
<el-input v-model="m.nickname"></el-input>
</div>
<div class="c-item br">
<label class="c-label">真实姓名:</label>
<el-input v-model="m.real_name"></el-input>
</div>
<div class="c-item br">
<label class="c-label">*密码:</label>
<el-input v-model="m.password"></el-input>
</div>
<div class="c-item br">
<label class="c-label">手机:</label>
<label class="c-label">*学号:</label>
<el-input v-model="m.student_id"></el-input>
</div>
<div class="c-item br">
<label class="c-label">*手机:</label>
<el-input v-model="m.phone"></el-input>
</div>
<div class="c-item br">
<label class="c-label">角色:</label>
<label class="c-label">*角色:</label>
<el-select v-model="m.role_id">
<el-option label="管理员" :value="1"></el-option>
<el-option label="公告管理员" :value="2"></el-option>
<el-option label="普通用户" :value="3"></el-option>
<el-option label="管理员" :value="'admin'"></el-option>
<el-option label="普通用户" :value="'student'"></el-option>
<el-option label="校外人员" :value="'out'"></el-option>
</el-select>
</div>
<div class="c-item br">
<label class="c-label">性别:</label>
<el-radio-group v-model="m.sex">
<el-radio :label="1"></el-radio>
<el-radio :label="2"></el-radio>
<el-radio :label="'男'"></el-radio>
<el-radio :label="'女'"></el-radio>
</el-radio-group>
</div>
<div class="c-item br">
@ -58,11 +70,15 @@
new Vue({
el: '.vue-box',
data: {
m: { // 查询参数
username: '',
password: '',
m: { // 查询参数
student_id: '',
phone: '',
sex: 1,
username: '',
nickname: '',
real_name: '',
password: '',
sex: '男',
role_id: 'admin',
}
},
methods: {

View File

@ -21,12 +21,20 @@
<div class="c-title">用户列表</div>
<el-form>
<div class="c-item">
<label class="c-label">用户昵称</label>
<label class="c-label">用户</label>
<el-input v-model="p.username" placeholder="模糊查询"></el-input>
</div>
<div class="c-item">
<label class="c-label">用户昵称:</label>
<el-input v-model="p.nickname" placeholder="模糊查询"></el-input>
</div>
<div class="c-item">
<label class="c-label">真实姓名:</label>
<el-input v-model="p.real_name" placeholder="模糊查询"></el-input>
</div>
<div class="c-item">
<label class="c-label">注册日期:</label>
<el-date-picker v-model="p.start_time" type="date" value-format="yyyy-MM-dd" placeholder="开始日期"></el-date-picker> -
<el-date-picker v-model="p.start_time" type="date" value-format="yyyy-MM-dd" placeholder="开始日期"></el-date-picker> -
<el-date-picker v-model="p.end_time" type="date" value-format="yyyy-MM-dd" placeholder="结束日期"></el-date-picker>
</div>
<div class="c-item" style="min-width: 0px;">
@ -49,7 +57,7 @@
<div class="fast-btn">
<el-button type="primary" icon="el-icon-plus" @click="add()">新增</el-button>
<el-button type="success" icon="el-icon-view" @click="getBySelect()">查看</el-button>
<el-button type="danger" icon="el-icon-delete" @click="deleteByIds()">删除</el-button>
<el-button type="danger" icon="el-icon-delete" @click="deleteByIds()">禁用</el-button>
<el-button type="warning" icon="el-icon-download" @click="sa.exportExcel()">导出</el-button>
<el-button type="info" icon="el-icon-refresh" @click="sa.f5()">重置</el-button>
</div>
@ -57,36 +65,31 @@
<el-table class="data-table" ref="data-table" :data="dataList" size="small">
<el-table-column type="selection" width="45px"></el-table-column>
<el-table-column label="编号" prop="id" width="80px" > </el-table-column>
<el-table-column label="昵称" prop="username" width="220px">
<el-table-column label="用户名" prop="username" width="220px">
<template slot-scope="s">
<img :src="s.row.avatar" @click="sa.showImage(s.row.avatar, '400px', '400px')"
<img :src="sa.cfg.api_url + s.row.avatar" @click="sa.showImage(sa.cfg.api_url + s.row.avatar, '400px', '400px')"
style="width: 3em; height: 3em; float: left; margin-right: 1em; border-radius: 50%; cursor: pointer;" >
<div style="float: left; width: 130px; line-height: 20px;">
<b>{{s.row.username}}</b>
<p>{{s.row.tell}}</p>
<b>{{s.row.username}} / {{s.row.nickname}}</b>
<p>{{s.row.student_id}} / {{s.row.phone}}</p>
</div>
</template>
</el-table-column>
<el-table-column label="个人相册">
<template slot-scope="s">
<img :src="s.row.photo_list[0]" style="width: 40px; height: 40px; cursor: pointer;" @click="sa.showImageList(s.row.photo_list)" >
共{{s.row.photo_list.length}}张, 点击预览
</template>
</el-table-column>
<el-table-column label="真实姓名" prop="real_name"> </el-table-column>
<el-table-column label="邮箱" prop="email"> </el-table-column>
<el-table-column label="性别" prop="sex"> </el-table-column>
<el-table-column label="注册方式" prop="create_type"></el-table-column>
<el-table-column label="注册于" prop="create_time"></el-table-column>
<el-table-column label="状态">
<template slot-scope="s">
<el-switch v-model="s.row.status" :active-value="1" :inactive-value="2" inactive-color="#ff4949"></el-switch>
<b style="color: green;" v-if="s.row.status == 1">正常</b>
<b style="color: red;" v-if="s.row.status == 2">禁用</b>
<el-switch v-model="s.row.status" :active-value="1" :inactive-value="0" inactive-color="#ff4949"></el-switch>
<b style="color: green;" v-if="s.row.status === 1">正常</b>
<b style="color: red;" v-if="s.row.status === 0">禁用</b>
</template>
</el-table-column>
<el-table-column prop="address" label="操作">
<template slot-scope="s">
<el-button class="c-btn" type="success" icon="el-icon-view" @click="get(s.row)">详情</el-button>
<el-button class="c-btn" type="danger" icon="el-icon-delete" @click="del(s.row)">删除</el-button>
<el-button class="c-btn" type="danger" icon="el-icon-delete" @click="del(s.row)">禁用</el-button>
</template>
</el-table-column>
</el-table>
@ -117,33 +120,42 @@
data: {
p: { // 查询参数
username: '',
nickname: '',
real_name: '',
create_type: 0,
sortType: 1,
start_time: new Date().getFullYear() + '-' + (new Date().getMonth() + 1) + '-1', // 本月一号
end_time: new Date().getFullYear() + '-' + (new Date().getMonth() + 1) + '-' + new Date().getDate(), // 本月当日
start_time: new Date().getFullYear() + '-' + (new Date().getMonth() + 1) + '-1', // 本月一号
end_time: new Date().getFullYear() + '-' + (new Date().getMonth() + 1) + '-' + new Date().getDate(), // 本月当日
pageNo: 1,
pageSize: 10,
},
dataCount: 1422,
dataCount: 0,
dataList: []
},
methods: {
// 数据刷新
f5: function() {
sa.ajax2('/user/getList', this.p, function(res){
this.dataList = res.data; // 数据
this.dataCount = res.dataCount; // 分页
sa.ajax('/user/admin/list', this.p, function(res){
this.dataList = res.data.data; // 数据
for (let i = 0; i < this.dataList.length; i++) {
this.dataList[i].status = this.dataList[i].is_active ? 1 : 0;
}
this.dataCount = res.data.count; // 分页
sa.f5TableHeight(); // 刷新表格高度
}.bind(this), {res: mockData});
}.bind(this), {});
},
// 查看
get: function(data) {
var str = '<div>';
str += '<p>编号:' + data.id + '</p>';
str += '<p>昵称:' + data.username + '</p>';
str += '<p>用户名:' + data.username + '</p>';
str += '<p>昵称:' + data.nickname + '</p>';
str += '<p>真实姓名:' + data.real_name + '</p>';
str += '<p>学号:' + data.student_id + '</p>';
str += '<p>电话:' + data.phone + '</p>';
str += '<p>邮箱:' + data.email + '</p>';
str += '<p>性别:' + data.sex + '</p>';
str += '<p>当前状态:<b>' + (data.status == 1 ? '正常' : '禁用') + '</b></p>';
str += '<p>注册方式:' + data.create_type + '</p>';
str += '<p>注册时间:' + data.create_time + '</p>';
str += '</div>';
sa.alert(str);

46
src/route/users_admin.py Normal file
View File

@ -0,0 +1,46 @@
from fastapi import HTTPException
from fastapi_amis_admin.crud import BaseApiOut
from starlette import status
from src.plugin import handler
from src.plugin.plugin import Plugin
from src.services.users.schemas import UserList
from src.services.users.services import UserServices
class UserRoutes(Plugin):
_prefix = "/user/admin"
def __init__(
self,
user_services: UserServices,
):
self.user_services = user_services
@handler.post("/list", student=True)
async def get_user_list(self, data: UserList):
username, nickname, real_name = data.username, data.nickname, data.real_name
start_time, end_time = data.start, data.end
page_no, page_size = data.pageNo, data.pageSize
if page_no < 1:
page_no = 1
if page_size < 0 or page_size > 100:
page_size = 10
try:
data, count = await self.user_services.get_user_list(
username,
nickname,
real_name,
start_time,
end_time,
page_no,
page_size,
)
return BaseApiOut(
code=0, msg="请求成功", data={"data": data, "count": count}
)
except Exception as e:
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"Error Execute SQL{e}",
) from e

View File

@ -1,11 +1,13 @@
from typing import Optional, Sequence
from datetime import datetime
from typing import Optional, Sequence, Tuple
from fastapi_user_auth.auth import Auth
from fastapi_user_auth.auth.backends.redis import RedisTokenStore
from fastapi_user_auth.auth.models import CasbinRule, LoginHistory
from persica.factory.component import AsyncInitializingComponent
from pydantic import SecretStr
from sqlmodel import select
from sqlalchemy import func
from sqlmodel import select, col
from sqlmodel.ext.asyncio.session import AsyncSession
from src.core.database import Database
@ -133,3 +135,49 @@ class UserRepo(AsyncInitializingComponent):
await session.commit()
await session.refresh(user)
return user
@staticmethod
async def get_count(session, q) -> int:
count_q = (
q.with_only_columns(func.count())
.order_by(None)
.select_from(q.get_final_froms()[0])
)
iterator = await session.exec(count_q)
for count in iterator:
return count
return 0
async def get_user_list(
self,
username: str,
nickname: str,
real_name: str,
start_time: datetime,
end_time: datetime,
page_no: int,
page_size: int,
) -> Tuple[Sequence[UserModel], int]:
async with AsyncSession(self.engine) as session:
statement = select(self.user_model)
if username:
statement = statement.where(
col(self.user_model.username).like(f"%{username}%")
)
if nickname:
statement = statement.where(
col(self.user_model.nickname).like(f"%{nickname}%")
)
if real_name:
statement = statement.where(
col(self.user_model.real_name).like(f"%{real_name}%")
)
if start_time:
statement = statement.where(self.user_model.create_time >= start_time)
if end_time:
statement = statement.where(self.user_model.create_time <= end_time)
all_count = await self.get_count(session, statement)
offset = (page_no - 1) * page_size
statement = statement.offset(offset).limit(page_size)
r = await session.exec(statement)
return r.all(), all_count

View File

@ -1,3 +1,4 @@
from datetime import date, datetime
from enum import Enum
from typing import Optional
@ -107,3 +108,27 @@ class UserRoleEnum(str, Enum):
ADMIN = "admin" # 管理员
STUDENT = "student" # 教职工
OUT = "out" # 校外人员
class UserList(BaseModel):
username: str = ""
nickname: str = ""
real_name: str = ""
sortType: int = 1
start_time: str
end_time: str
pageNo: int = 1
pageSize: int = 10
@property
def start(self):
y, m, d = self.start_time.split("-")
start_time = date(int(y), int(m), int(d))
return datetime.combine(start_time, datetime.min.time())
@property
def end(self):
y, m, d = self.end_time.split("-")
end_time = date(int(y), int(m), int(d))
return datetime.combine(end_time, datetime.max.time())

View File

@ -1,4 +1,5 @@
from typing import Optional, List, Union, Sequence
from datetime import datetime
from typing import Optional, List, Union, Sequence, Tuple
from fastapi_user_auth.auth.models import CasbinRule, LoginHistory
from fastapi_user_auth.utils.casbin import update_subject_roles
@ -96,6 +97,20 @@ class UserServices(AsyncInitializingComponent):
user.avatar = avatar
return await self.repo.update_user(user)
async def get_user_list(
self,
username: str,
nickname: str,
real_name: str,
start_time: datetime,
end_time: datetime,
page_no: int,
page_size: int,
) -> Tuple[Sequence[UserModel], int]:
return await self.repo.get_user_list(
username, nickname, real_name, start_time, end_time, page_no, page_size
)
class UserRoleServices(AsyncInitializingComponent):
__order__ = 1