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

View File

@ -21,12 +21,20 @@
<div class="c-title">用户列表</div> <div class="c-title">用户列表</div>
<el-form> <el-form>
<div class="c-item"> <div class="c-item">
<label class="c-label">用户昵称</label> <label class="c-label">用户</label>
<el-input v-model="p.username" placeholder="模糊查询"></el-input> <el-input v-model="p.username" placeholder="模糊查询"></el-input>
</div> </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"> <div class="c-item">
<label class="c-label">注册日期:</label> <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> <el-date-picker v-model="p.end_time" type="date" value-format="yyyy-MM-dd" placeholder="结束日期"></el-date-picker>
</div> </div>
<div class="c-item" style="min-width: 0px;"> <div class="c-item" style="min-width: 0px;">
@ -49,7 +57,7 @@
<div class="fast-btn"> <div class="fast-btn">
<el-button type="primary" icon="el-icon-plus" @click="add()">新增</el-button> <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="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="warning" icon="el-icon-download" @click="sa.exportExcel()">导出</el-button>
<el-button type="info" icon="el-icon-refresh" @click="sa.f5()">重置</el-button> <el-button type="info" icon="el-icon-refresh" @click="sa.f5()">重置</el-button>
</div> </div>
@ -57,36 +65,31 @@
<el-table class="data-table" ref="data-table" :data="dataList" size="small"> <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 type="selection" width="45px"></el-table-column>
<el-table-column label="编号" prop="id" width="80px" > </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"> <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;" > style="width: 3em; height: 3em; float: left; margin-right: 1em; border-radius: 50%; cursor: pointer;" >
<div style="float: left; width: 130px; line-height: 20px;"> <div style="float: left; width: 130px; line-height: 20px;">
<b>{{s.row.username}}</b> <b>{{s.row.username}} / {{s.row.nickname}}</b>
<p>{{s.row.tell}}</p> <p>{{s.row.student_id}} / {{s.row.phone}}</p>
</div> </div>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="个人相册"> <el-table-column label="真实姓名" prop="real_name"> </el-table-column>
<template slot-scope="s"> <el-table-column label="邮箱" prop="email"> </el-table-column>
<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="sex"> </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="注册于" prop="create_time"></el-table-column>
<el-table-column label="状态"> <el-table-column label="状态">
<template slot-scope="s"> <template slot-scope="s">
<el-switch v-model="s.row.status" :active-value="1" :inactive-value="2" inactive-color="#ff4949"></el-switch> <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: green;" v-if="s.row.status === 1">正常</b>
<b style="color: red;" v-if="s.row.status == 2">禁用</b> <b style="color: red;" v-if="s.row.status === 0">禁用</b>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="address" label="操作"> <el-table-column prop="address" label="操作">
<template slot-scope="s"> <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="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> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
@ -117,33 +120,42 @@
data: { data: {
p: { // 查询参数 p: { // 查询参数
username: '', username: '',
nickname: '',
real_name: '',
create_type: 0, create_type: 0,
sortType: 1, sortType: 1,
start_time: new Date().getFullYear() + '-' + (new Date().getMonth() + 1) + '-1', // 本月一号 start_time: new Date().getFullYear() + '-' + (new Date().getMonth() + 1) + '-1', // 本月一号
end_time: new Date().getFullYear() + '-' + (new Date().getMonth() + 1) + '-' + new Date().getDate(), // 本月当日 end_time: new Date().getFullYear() + '-' + (new Date().getMonth() + 1) + '-' + new Date().getDate(), // 本月当日
pageNo: 1, pageNo: 1,
pageSize: 10, pageSize: 10,
}, },
dataCount: 1422, dataCount: 0,
dataList: [] dataList: []
}, },
methods: { methods: {
// 数据刷新 // 数据刷新
f5: function() { f5: function() {
sa.ajax2('/user/getList', this.p, function(res){ sa.ajax('/user/admin/list', this.p, function(res){
this.dataList = res.data; // 数据 this.dataList = res.data.data; // 数据
this.dataCount = res.dataCount; // 分页 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(); // 刷新表格高度 sa.f5TableHeight(); // 刷新表格高度
}.bind(this), {res: mockData}); }.bind(this), {});
}, },
// 查看 // 查看
get: function(data) { get: function(data) {
var str = '<div>'; var str = '<div>';
str += '<p>编号:' + data.id + '</p>'; 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>性别:' + data.sex + '</p>';
str += '<p>当前状态:<b>' + (data.status == 1 ? '正常' : '禁用') + '</b></p>'; str += '<p>当前状态:<b>' + (data.status == 1 ? '正常' : '禁用') + '</b></p>';
str += '<p>注册方式:' + data.create_type + '</p>';
str += '<p>注册时间:' + data.create_time + '</p>'; str += '<p>注册时间:' + data.create_time + '</p>';
str += '</div>'; str += '</div>';
sa.alert(str); 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 import Auth
from fastapi_user_auth.auth.backends.redis import RedisTokenStore from fastapi_user_auth.auth.backends.redis import RedisTokenStore
from fastapi_user_auth.auth.models import CasbinRule, LoginHistory from fastapi_user_auth.auth.models import CasbinRule, LoginHistory
from persica.factory.component import AsyncInitializingComponent from persica.factory.component import AsyncInitializingComponent
from pydantic import SecretStr 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 sqlmodel.ext.asyncio.session import AsyncSession
from src.core.database import Database from src.core.database import Database
@ -133,3 +135,49 @@ class UserRepo(AsyncInitializingComponent):
await session.commit() await session.commit()
await session.refresh(user) await session.refresh(user)
return 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 enum import Enum
from typing import Optional from typing import Optional
@ -107,3 +108,27 @@ class UserRoleEnum(str, Enum):
ADMIN = "admin" # 管理员 ADMIN = "admin" # 管理员
STUDENT = "student" # 教职工 STUDENT = "student" # 教职工
OUT = "out" # 校外人员 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.auth.models import CasbinRule, LoginHistory
from fastapi_user_auth.utils.casbin import update_subject_roles from fastapi_user_auth.utils.casbin import update_subject_roles
@ -96,6 +97,20 @@ class UserServices(AsyncInitializingComponent):
user.avatar = avatar user.avatar = avatar
return await self.repo.update_user(user) 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): class UserRoleServices(AsyncInitializingComponent):
__order__ = 1 __order__ = 1