feat: 统一分页组件并添加批量删除功能
为多个页面组件添加统一的分页统计显示和批量删除功能 在日志管理页面添加批量删除接口和前端实现 优化表格分页配置,统一显示总条目数和分页选项
This commit is contained in:
@ -1,13 +1,47 @@
|
|||||||
use axum::{Router, routing::get, extract::{State, Query}, Json};
|
//! 流程运行日志路由模块
|
||||||
use crate::{db::Db, response::ApiResponse, services::flow_run_log_service};
|
//!
|
||||||
|
//! 提供流程运行日志的分页查询接口与批量删除接口。
|
||||||
|
|
||||||
|
use axum::{
|
||||||
|
extract::{Query, State},
|
||||||
|
routing::{get, delete},
|
||||||
|
Json, Router,
|
||||||
|
};
|
||||||
|
use axum::extract::Path;
|
||||||
|
|
||||||
|
use crate::db::Db;
|
||||||
|
use crate::error::AppError;
|
||||||
|
use crate::response::ApiResponse;
|
||||||
|
use crate::services::flow_run_log_service;
|
||||||
|
|
||||||
|
/// 路由定义:流程运行日志相关接口
|
||||||
pub fn router() -> Router<Db> {
|
pub fn router() -> Router<Db> {
|
||||||
Router::new().route("/flow_run_logs", get(list))
|
Router::new()
|
||||||
|
.route("/flow_run_logs", get(list))
|
||||||
|
.route("/flow_run_logs/{ids}", delete(delete_many))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn list(State(db): State<Db>, Query(p): Query<flow_run_log_service::ListParams>) -> Json<ApiResponse<flow_run_log_service::PageResp<flow_run_log_service::RunLogItem>>> {
|
/// 流程运行日志列表
|
||||||
match flow_run_log_service::list(&db, p).await {
|
async fn list(
|
||||||
Ok(res) => Json(ApiResponse::ok(res)),
|
State(db): State<Db>,
|
||||||
Err(e) => Json(ApiResponse::err(500, format!("{}", e))),
|
Query(p): Query<flow_run_log_service::ListParams>,
|
||||||
}
|
) -> Result<Json<ApiResponse<flow_run_log_service::PageResp<flow_run_log_service::RunLogItem>>>, AppError> {
|
||||||
|
let res = flow_run_log_service::list(&db, p).await?;
|
||||||
|
Ok(Json(ApiResponse::ok(res)))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 批量删除流程运行日志
|
||||||
|
///
|
||||||
|
/// 路径参数 ids 采用逗号分隔的 ID 列表,例如:/flow_run_logs/1001,1002
|
||||||
|
/// 返回:{ deleted: <u64> },deleted 表示实际删除条数
|
||||||
|
async fn delete_many(
|
||||||
|
State(db): State<Db>,
|
||||||
|
Path(ids): Path<String>,
|
||||||
|
) -> Result<Json<ApiResponse<serde_json::Value>>, AppError> {
|
||||||
|
let id_list: Vec<i64> = ids
|
||||||
|
.split(',')
|
||||||
|
.filter_map(|s| s.trim().parse::<i64>().ok())
|
||||||
|
.collect();
|
||||||
|
let deleted = flow_run_log_service::delete_many(&db, id_list).await?;
|
||||||
|
Ok(Json(ApiResponse::ok(serde_json::json!({ "deleted": deleted }))))
|
||||||
}
|
}
|
||||||
@ -1,9 +1,47 @@
|
|||||||
use axum::{Router, routing::get, extract::{Query, State}, Json};
|
//! 请求日志查询路由模块
|
||||||
use crate::{db::Db, response::ApiResponse, services::log_service, error::AppError};
|
//!
|
||||||
|
//! 提供系统请求日志的分页查询接口与批量删除接口。
|
||||||
|
|
||||||
pub fn router() -> Router<Db> { Router::new().route("/logs", get(list)) }
|
use axum::{
|
||||||
|
extract::{Query, State},
|
||||||
|
routing::{get, delete},
|
||||||
|
Json, Router,
|
||||||
|
};
|
||||||
|
use axum::extract::Path;
|
||||||
|
|
||||||
async fn list(State(db): State<Db>, Query(p): Query<log_service::ListParams>) -> Result<Json<ApiResponse<log_service::PageResp<log_service::LogInfo>>>, AppError> {
|
use crate::db::Db;
|
||||||
let res = log_service::list(&db, p).await.map_err(|e| AppError::Anyhow(anyhow::anyhow!(e)))?;
|
use crate::error::AppError;
|
||||||
|
use crate::response::ApiResponse;
|
||||||
|
use crate::services::log_service;
|
||||||
|
|
||||||
|
/// 路由定义:日志相关接口
|
||||||
|
pub fn router() -> Router<Db> {
|
||||||
|
Router::new()
|
||||||
|
.route("/logs", get(list))
|
||||||
|
.route("/logs/{ids}", delete(delete_many))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 日志列表
|
||||||
|
async fn list(
|
||||||
|
State(db): State<Db>,
|
||||||
|
Query(p): Query<log_service::ListParams>,
|
||||||
|
) -> Result<Json<ApiResponse<log_service::PageResp<log_service::LogInfo>>>, AppError> {
|
||||||
|
let res = log_service::list(&db, p).await?;
|
||||||
Ok(Json(ApiResponse::ok(res)))
|
Ok(Json(ApiResponse::ok(res)))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 批量删除系统请求日志
|
||||||
|
///
|
||||||
|
/// 路径参数 ids 采用逗号分隔的 ID 列表,例如:/logs/1,2,3
|
||||||
|
/// 返回:{ deleted: <u64> },deleted 表示实际删除条数
|
||||||
|
async fn delete_many(
|
||||||
|
State(db): State<Db>,
|
||||||
|
Path(ids): Path<String>,
|
||||||
|
) -> Result<Json<ApiResponse<serde_json::Value>>, AppError> {
|
||||||
|
let id_list: Vec<i64> = ids
|
||||||
|
.split(',')
|
||||||
|
.filter_map(|s| s.trim().parse::<i64>().ok())
|
||||||
|
.collect();
|
||||||
|
let deleted = log_service::delete_many(&db, id_list).await?;
|
||||||
|
Ok(Json(ApiResponse::ok(serde_json::json!({ "deleted": deleted }))))
|
||||||
}
|
}
|
||||||
@ -62,7 +62,6 @@ pub async fn create(db: &Db, input: CreateRunLogInput) -> anyhow::Result<i64> {
|
|||||||
Ok(m.id)
|
Ok(m.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// 分页查询流程运行日志
|
/// 分页查询流程运行日志
|
||||||
///
|
///
|
||||||
/// # 参数
|
/// # 参数
|
||||||
@ -129,4 +128,14 @@ pub async fn list(db: &Db, p: ListParams) -> anyhow::Result<PageResp<RunLogItem>
|
|||||||
page,
|
page,
|
||||||
page_size,
|
page_size,
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 新增:批量删除流程运行日志
|
||||||
|
pub async fn delete_many(db: &Db, ids: Vec<i64>) -> anyhow::Result<u64> {
|
||||||
|
if ids.is_empty() { return Ok(0); }
|
||||||
|
let res = flow_run_log::Entity::delete_many()
|
||||||
|
.filter(flow_run_log::Column::Id.is_in(ids))
|
||||||
|
.exec(db)
|
||||||
|
.await?;
|
||||||
|
Ok(res.rows_affected as u64)
|
||||||
}
|
}
|
||||||
@ -68,4 +68,14 @@ pub async fn list(db: &Db, p: ListParams) -> anyhow::Result<PageResp<LogInfo>> {
|
|||||||
let total = paginator.num_items().await? as u64;
|
let total = paginator.num_items().await? as u64;
|
||||||
let models = paginator.fetch_page(if page>0 { page-1 } else { 0 }).await?;
|
let models = paginator.fetch_page(if page>0 { page-1 } else { 0 }).await?;
|
||||||
Ok(PageResp { items: models.into_iter().map(Into::into).collect(), total, page, page_size })
|
Ok(PageResp { items: models.into_iter().map(Into::into).collect(), total, page, page_size })
|
||||||
|
}
|
||||||
|
|
||||||
|
// 新增:批量删除系统请求日志
|
||||||
|
pub async fn delete_many(db: &Db, ids: Vec<i64>) -> anyhow::Result<u64> {
|
||||||
|
if ids.is_empty() { return Ok(0); }
|
||||||
|
let res = request_log::Entity::delete_many()
|
||||||
|
.filter(request_log::Column::Id.is_in(ids))
|
||||||
|
.exec(db)
|
||||||
|
.await?;
|
||||||
|
Ok(res.rows_affected as u64)
|
||||||
}
|
}
|
||||||
@ -12,6 +12,9 @@ export default function Departments(){
|
|||||||
const [loading, setLoading] = useState(false)
|
const [loading, setLoading] = useState(false)
|
||||||
const [data, setData] = useState([] as DeptItem[])
|
const [data, setData] = useState([] as DeptItem[])
|
||||||
const [keyword, setKeyword] = useState('')
|
const [keyword, setKeyword] = useState('')
|
||||||
|
const [total, setTotal] = useState(0)
|
||||||
|
const [page, setPage] = useState(1)
|
||||||
|
const [pageSize, setPageSize] = useState(10)
|
||||||
|
|
||||||
const [createOpen, setCreateOpen] = useState(false)
|
const [createOpen, setCreateOpen] = useState(false)
|
||||||
const [editOpen, setEditOpen] = useState(false)
|
const [editOpen, setEditOpen] = useState(false)
|
||||||
@ -20,18 +23,31 @@ export default function Departments(){
|
|||||||
const [editForm] = Form.useForm()
|
const [editForm] = Form.useForm()
|
||||||
const [searchForm] = Form.useForm()
|
const [searchForm] = Form.useForm()
|
||||||
|
|
||||||
const fetchList = async (kw: string = keyword) => {
|
const fetchList = async (kw: string = keyword, p: number = page, ps: number = pageSize) => {
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
try{
|
try{
|
||||||
const { data } = await api.get('/departments', { params: { keyword: kw } })
|
const { data } = await api.get('/departments', { params: { keyword: kw, page: p, page_size: ps } })
|
||||||
if(data?.code === 0){ setData(data.data || []) } else { throw new Error(data?.message || '获取部门失败') }
|
if(data?.code === 0){
|
||||||
|
const payload = data.data
|
||||||
|
if (payload && Array.isArray(payload.items)) {
|
||||||
|
const items = payload.items as DeptItem[]
|
||||||
|
setData(items)
|
||||||
|
setTotal(Number(payload.total ?? items.length ?? 0))
|
||||||
|
setPage(Number(payload.page ?? p))
|
||||||
|
setPageSize(Number(payload.page_size ?? ps))
|
||||||
|
} else {
|
||||||
|
const list = (payload || []) as DeptItem[]
|
||||||
|
setData(list)
|
||||||
|
setTotal(Array.isArray(list) ? list.length : 0)
|
||||||
|
}
|
||||||
|
} else { throw new Error(data?.message || '获取部门失败') }
|
||||||
}catch(e: any){ message.error(e.message || '获取部门失败') } finally { setLoading(false) }
|
}catch(e: any){ message.error(e.message || '获取部门失败') } finally { setLoading(false) }
|
||||||
}
|
}
|
||||||
const didInitFetchRef = useRef(false)
|
const didInitFetchRef = useRef(false)
|
||||||
useEffect(()=>{
|
useEffect(()=>{
|
||||||
if(didInitFetchRef.current) return
|
if(didInitFetchRef.current) return
|
||||||
didInitFetchRef.current = true
|
didInitFetchRef.current = true
|
||||||
fetchList('')
|
fetchList('', 1, pageSize)
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
// 构建部门树用于树形选择
|
// 构建部门树用于树形选择
|
||||||
@ -182,25 +198,36 @@ export default function Departments(){
|
|||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<PageHeader items={["系统管理","部门管理"]} title="" />
|
<PageHeader items={["系统管理","部门管理"]} title="" />
|
||||||
<Form form={searchForm} layout="inline" onFinish={(vals: any)=>{ const kw = String(vals?.keyword ?? '').trim(); setKeyword(kw); fetchList(kw) }} style={{ marginBottom: 12 }}>
|
<Form form={searchForm} layout="inline" onFinish={(vals: any)=>{ const kw = String(vals?.keyword ?? '').trim(); setKeyword(kw); fetchList(kw, 1, pageSize) }} style={{ marginBottom: 12 }}>
|
||||||
<Form.Item name="keyword">
|
<Form.Item name="keyword">
|
||||||
<Input allowClear placeholder="搜索部门" style={{ width: 320 }} />
|
<Input allowClear placeholder="搜索部门" style={{ width: 320 }} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item>
|
<Form.Item>
|
||||||
<Space>
|
<Space>
|
||||||
<Button type="primary" htmlType="submit">搜索</Button>
|
<Button type="primary" htmlType="submit">搜索</Button>
|
||||||
<Button onClick={()=>{ searchForm.resetFields(); setKeyword(''); fetchList('') }}>重置</Button>
|
<Button onClick={()=>{ searchForm.resetFields(); setKeyword(''); fetchList('', 1, 10) }}>重置</Button>
|
||||||
<Button type="primary" onClick={onCreate}>新增</Button>
|
<Button type="primary" onClick={onCreate}>新增</Button>
|
||||||
</Space>
|
</Space>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</Form>
|
</Form>
|
||||||
|
{/* 总条目数展示:在表格上方显示当前总数 */}
|
||||||
|
<div style={{ marginBottom: 8, color: '#666' }}>总条目数:{total}</div>
|
||||||
<Table<DeptItem>
|
<Table<DeptItem>
|
||||||
rowKey="id"
|
rowKey="id"
|
||||||
loading={loading}
|
loading={loading}
|
||||||
dataSource={treeDataForTable}
|
dataSource={treeDataForTable}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
expandable={{ expandedRowKeys, onExpandedRowsChange: (keys) => setExpandedRowKeys(keys as React.Key[]), indentSize: 18, rowExpandable: (record: DeptItem) => Array.isArray(record.children) && record.children.length > 0 }}
|
expandable={{ expandedRowKeys, onExpandedRowsChange: (keys) => setExpandedRowKeys(keys as React.Key[]), indentSize: 18, rowExpandable: (record: DeptItem) => Array.isArray(record.children) && record.children.length > 0 }}
|
||||||
pagination={false}
|
// 展示分页统计:显示总条目数(与流程日志一致的受控分页)
|
||||||
|
pagination={{
|
||||||
|
current: page,
|
||||||
|
pageSize,
|
||||||
|
total,
|
||||||
|
showSizeChanger: true,
|
||||||
|
pageSizeOptions: [10, 20, 50, 100],
|
||||||
|
showTotal: (t, range) => `第 ${range[0]}-${range[1]} 条 / 共 ${t} 条`,
|
||||||
|
onChange: (p, ps) => { setPage(p); if (ps) setPageSize(ps) }
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Modal title="新增部门" open={createOpen} onOk={handleCreate} onCancel={() => setCreateOpen(false)} okText="创建" width={840}>
|
<Modal title="新增部门" open={createOpen} onOk={handleCreate} onCancel={() => setCreateOpen(false)} okText="创建" width={840}>
|
||||||
@ -216,7 +243,7 @@ export default function Departments(){
|
|||||||
filterTreeNode={(input: string, node: any) => String(node?.title ?? '').toLowerCase().includes(String(input).toLowerCase())}
|
filterTreeNode={(input: string, node: any) => String(node?.title ?? '').toLowerCase().includes(String(input).toLowerCase())}
|
||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item name="name" label="名称" rules={[{ required: true }]}>
|
<Form.Item name="name" label="名称" rules={[{ required: true }]}>
|
||||||
<Input placeholder="请输入名称" />
|
<Input placeholder="请输入名称" />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item name="order_no" label="排序" initialValue={0}>
|
<Form.Item name="order_no" label="排序" initialValue={0}>
|
||||||
|
|||||||
@ -249,7 +249,15 @@ export default function FlowList() {
|
|||||||
loading={loading}
|
loading={loading}
|
||||||
dataSource={list}
|
dataSource={list}
|
||||||
columns={columns as any}
|
columns={columns as any}
|
||||||
pagination={{ current: page, pageSize, total, showSizeChanger: true, onChange: (p: number, ps?: number) => fetchList(p, ps ?? pageSize, keyword) }}
|
// 展示分页统计:在分页栏显示总条目数
|
||||||
|
pagination={{
|
||||||
|
current: page,
|
||||||
|
pageSize,
|
||||||
|
total,
|
||||||
|
showSizeChanger: true,
|
||||||
|
showTotal: (t, range) => `第 ${range[0]}-${range[1]} 条 / 共 ${t} 条`,
|
||||||
|
onChange: (p: number, ps?: number) => fetchList(p, ps ?? pageSize, keyword)
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* 编辑基础信息弹窗 */}
|
{/* 编辑基础信息弹窗 */}
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import React, { useEffect, useMemo, useState } from 'react'
|
import React, { useEffect, useMemo, useState } from 'react'
|
||||||
import { Button, Form, Input, Select, Space, Table, Tag, Descriptions, DatePicker, Drawer, Typography } from 'antd'
|
import { Button, Form, Input, Select, Space, Table, Tag, Descriptions, DatePicker, Drawer, Typography, Popconfirm, message } from 'antd'
|
||||||
import type { ColumnsType } from 'antd/es/table'
|
import type { ColumnsType } from 'antd/es/table'
|
||||||
import api, { ApiResp } from '../utils/axios'
|
import api, { ApiResp } from '../utils/axios'
|
||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
@ -30,9 +30,10 @@ export default function FlowRunLogs() {
|
|||||||
const [total, setTotal] = useState(0)
|
const [total, setTotal] = useState(0)
|
||||||
const [page, setPage] = useState(1)
|
const [page, setPage] = useState(1)
|
||||||
const [pageSize, setPageSize] = useState(10)
|
const [pageSize, setPageSize] = useState(10)
|
||||||
|
|
||||||
const [detailOpen, setDetailOpen] = useState(false)
|
const [detailOpen, setDetailOpen] = useState(false)
|
||||||
const [detail, setDetail] = useState<RunLogItem | null>(null)
|
const [detail, setDetail] = useState<RunLogItem | null>(null)
|
||||||
|
// 选中项用于批量删除
|
||||||
|
const [selectedKeys, setSelectedKeys] = useState<React.Key[]>([])
|
||||||
|
|
||||||
const fetchData = async (p = page, ps = pageSize) => {
|
const fetchData = async (p = page, ps = pageSize) => {
|
||||||
const v = form.getFieldsValue()
|
const v = form.getFieldsValue()
|
||||||
@ -57,6 +58,23 @@ export default function FlowRunLogs() {
|
|||||||
const openDetail = (record: RunLogItem) => { setDetail(record); setDetailOpen(true) }
|
const openDetail = (record: RunLogItem) => { setDetail(record); setDetailOpen(true) }
|
||||||
const closeDetail = () => setDetailOpen(false)
|
const closeDetail = () => setDetailOpen(false)
|
||||||
|
|
||||||
|
// 批量删除
|
||||||
|
const handleBatchDelete = async () => {
|
||||||
|
if (!selectedKeys.length) return
|
||||||
|
try {
|
||||||
|
const { data } = await api.delete<ApiResp<{ deleted: number }>>(`/flow_run_logs/${selectedKeys.join(',')}`)
|
||||||
|
if (data?.code === 0) {
|
||||||
|
message.success(`已删除 ${data?.data?.deleted || 0} 条`)
|
||||||
|
setSelectedKeys([])
|
||||||
|
fetchData(page, pageSize)
|
||||||
|
} else {
|
||||||
|
throw new Error(data?.message || '删除失败')
|
||||||
|
}
|
||||||
|
} catch (e: any) {
|
||||||
|
message.error(e.message || '删除失败')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const columns: ColumnsType<RunLogItem> = useMemo(() => [
|
const columns: ColumnsType<RunLogItem> = useMemo(() => [
|
||||||
{ title: 'ID', dataIndex: 'id', width: 90 },
|
{ title: 'ID', dataIndex: 'id', width: 90 },
|
||||||
{ title: '时间', dataIndex: 'started_at', width: 180, render: (v: string) => dayjs(v).format('YYYY-MM-DD HH:mm:ss') },
|
{ title: '时间', dataIndex: 'started_at', width: 180, render: (v: string) => dayjs(v).format('YYYY-MM-DD HH:mm:ss') },
|
||||||
@ -135,13 +153,32 @@ export default function FlowRunLogs() {
|
|||||||
</Form.Item>
|
</Form.Item>
|
||||||
</Form>
|
</Form>
|
||||||
|
|
||||||
|
{/* // 操作区:批量删除 */}
|
||||||
|
<div style={{ marginBottom: 12 }}>
|
||||||
|
<Space>
|
||||||
|
<Popconfirm title={`确认删除选中的 ${selectedKeys.length} 条运行日志?`} onConfirm={handleBatchDelete} disabled={!selectedKeys.length}>
|
||||||
|
<Button danger disabled={!selectedKeys.length}>批量删除</Button>
|
||||||
|
</Popconfirm>
|
||||||
|
<Button onClick={() => fetchData(page, pageSize)}>刷新</Button>
|
||||||
|
</Space>
|
||||||
|
</div>
|
||||||
<Table<RunLogItem>
|
<Table<RunLogItem>
|
||||||
rowKey="id"
|
rowKey="id"
|
||||||
loading={loading}
|
loading={loading}
|
||||||
dataSource={data}
|
dataSource={data}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
scroll={{ x: 1600 }}
|
scroll={{ x: 1600 }}
|
||||||
pagination={{ current: page, pageSize, total, showSizeChanger: true, pageSizeOptions: [10, 20, 50, 100], onChange: (p, ps) => fetchData(p, ps) }}
|
rowSelection={{ selectedRowKeys: selectedKeys, onChange: (keys) => setSelectedKeys(keys) }}
|
||||||
|
// 展示分页统计:在分页栏显示总条目数
|
||||||
|
pagination={{
|
||||||
|
current: page,
|
||||||
|
pageSize,
|
||||||
|
total,
|
||||||
|
showSizeChanger: true,
|
||||||
|
pageSizeOptions: [10, 20, 50, 100],
|
||||||
|
showTotal: (t, range) => `第 ${range[0]}-${range[1]} 条 / 共 ${t} 条`,
|
||||||
|
onChange: (p, ps) => fetchData(p, ps)
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Drawer title="运行详情" width={820} open={detailOpen} onClose={closeDetail} destroyOnHidden placement="right">
|
<Drawer title="运行详情" width={820} open={detailOpen} onClose={closeDetail} destroyOnHidden placement="right">
|
||||||
|
|||||||
@ -30,9 +30,10 @@ export default function Logs() {
|
|||||||
const [total, setTotal] = useState(0)
|
const [total, setTotal] = useState(0)
|
||||||
const [page, setPage] = useState(1)
|
const [page, setPage] = useState(1)
|
||||||
const [pageSize, setPageSize] = useState(10)
|
const [pageSize, setPageSize] = useState(10)
|
||||||
|
|
||||||
const [detailOpen, setDetailOpen] = useState(false)
|
const [detailOpen, setDetailOpen] = useState(false)
|
||||||
const [detail, setDetail] = useState<LogInfo | null>(null)
|
const [detail, setDetail] = useState<LogInfo | null>(null)
|
||||||
|
// 选中项用于批量删除
|
||||||
|
const [selectedKeys, setSelectedKeys] = useState<React.Key[]>([])
|
||||||
|
|
||||||
const fetchData = async (p = page, ps = pageSize) => {
|
const fetchData = async (p = page, ps = pageSize) => {
|
||||||
const v = form.getFieldsValue()
|
const v = form.getFieldsValue()
|
||||||
@ -59,16 +60,33 @@ export default function Logs() {
|
|||||||
const openDetail = (record: LogInfo) => { setDetail(record); setDetailOpen(true) }
|
const openDetail = (record: LogInfo) => { setDetail(record); setDetailOpen(true) }
|
||||||
const closeDetail = () => setDetailOpen(false)
|
const closeDetail = () => setDetailOpen(false)
|
||||||
|
|
||||||
|
// 批量删除
|
||||||
|
const handleBatchDelete = async () => {
|
||||||
|
if (!selectedKeys.length) return
|
||||||
|
try {
|
||||||
|
const { data } = await api.delete<ApiResp<{ deleted: number }>>(`/logs/${selectedKeys.join(',')}`)
|
||||||
|
if (data?.code === 0) {
|
||||||
|
message.success(`已删除 ${data?.data?.deleted || 0} 条`)
|
||||||
|
setSelectedKeys([])
|
||||||
|
fetchData(page, pageSize)
|
||||||
|
} else {
|
||||||
|
throw new Error(data?.message || '删除失败')
|
||||||
|
}
|
||||||
|
} catch (e: any) {
|
||||||
|
message.error(e.message || '删除失败')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const columns = useMemo(() => [
|
const columns = useMemo(() => [
|
||||||
{ title: 'ID', dataIndex: 'id', width: 80 },
|
{ title: 'ID', dataIndex: 'id', width: 100 },
|
||||||
{ title: '时间', dataIndex: 'request_time', width: 180, render: (v: string) => dayjs(v).format('YYYY-MM-DD HH:mm:ss') },
|
{ title: '时间', dataIndex: 'request_time', width: 120, render: (v: string) => dayjs(v).format('YYYY-MM-DD HH:mm:ss') },
|
||||||
{ title: '路径', dataIndex: 'path', width: 240 },
|
{ title: '路径', dataIndex: 'path', width: 240 },
|
||||||
{ title: '方法', dataIndex: 'method', width: 90, render: (m: string) => <Tag>{m}</Tag> },
|
{ title: '方法', dataIndex: 'method', width: 60, render: (m: string) => <Tag>{m}</Tag> },
|
||||||
{ title: '用户', dataIndex: 'username', width: 140, render: (_: any, r: LogInfo) => r.username || (r.user_id ? `UID:${r.user_id}` : '-') },
|
{ title: '用户', dataIndex: 'username', width: 100, render: (_: any, r: LogInfo) => r.username || (r.user_id ? `UID:${r.user_id}` : '-') },
|
||||||
{ title: '状态码', dataIndex: 'status_code', width: 100 },
|
{ title: '状态码', dataIndex: 'status_code', width: 60 },
|
||||||
{ title: '耗时(ms)', dataIndex: 'duration_ms', width: 100 },
|
{ title: '耗时(ms)', dataIndex: 'duration_ms', width: 80 },
|
||||||
{ title: '请求参数', dataIndex: 'request_params', width: 260, ellipsis: true },
|
{ title: '请求参数', dataIndex: 'request_params', width: 280, ellipsis: true },
|
||||||
{ title: '响应参数', dataIndex: 'response_params', width: 260, ellipsis: true },
|
{ title: '响应参数', dataIndex: 'response_params', width: 280, ellipsis: true },
|
||||||
{ title: '操作', key: 'action', fixed: 'right' as any, width: 120, render: (_: any, r: LogInfo) => (
|
{ title: '操作', key: 'action', fixed: 'right' as any, width: 120, render: (_: any, r: LogInfo) => (
|
||||||
<Space>
|
<Space>
|
||||||
<a className="action-link" onClick={() => openDetail(r)}>
|
<a className="action-link" onClick={() => openDetail(r)}>
|
||||||
@ -129,13 +147,32 @@ export default function Logs() {
|
|||||||
</Space>
|
</Space>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</Form>
|
</Form>
|
||||||
|
{/* 操作区:批量删除 */}
|
||||||
|
<div style={{ marginBottom: 12 }}>
|
||||||
|
<Space>
|
||||||
|
<Popconfirm title={`确认删除选中的 ${selectedKeys.length} 条日志?`} onConfirm={handleBatchDelete} disabled={!selectedKeys.length}>
|
||||||
|
<Button danger disabled={!selectedKeys.length}>批量删除</Button>
|
||||||
|
</Popconfirm>
|
||||||
|
<Button onClick={() => fetchData(page, pageSize)}>刷新</Button>
|
||||||
|
</Space>
|
||||||
|
</div>
|
||||||
<Table
|
<Table
|
||||||
rowKey="id"
|
rowKey="id"
|
||||||
loading={loading}
|
loading={loading}
|
||||||
dataSource={data}
|
dataSource={data}
|
||||||
columns={columns as any}
|
columns={columns as any}
|
||||||
scroll={{ x: 2000 }}
|
scroll={{ x: 2000 }}
|
||||||
pagination={{ current: page, pageSize, total, onChange: (p, ps) => fetchData(p, ps) }}
|
rowSelection={{ selectedRowKeys: selectedKeys, onChange: (keys) => setSelectedKeys(keys) }}
|
||||||
|
// 展示分页统计:在分页栏显示总条目数
|
||||||
|
pagination={{
|
||||||
|
current: page,
|
||||||
|
pageSize,
|
||||||
|
total,
|
||||||
|
showSizeChanger: true,
|
||||||
|
pageSizeOptions: [10, 20, 50, 100],
|
||||||
|
showTotal: (t, range) => `第 ${range[0]}-${range[1]} 条 / 共 ${t} 条`,
|
||||||
|
onChange: (p, ps) => fetchData(p, ps)
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Drawer title="日志详情" width={720} open={detailOpen} onClose={closeDetail} destroyOnHidden placement="right">
|
<Drawer title="日志详情" width={720} open={detailOpen} onClose={closeDetail} destroyOnHidden placement="right">
|
||||||
|
|||||||
@ -122,6 +122,8 @@ export default function Menus(){
|
|||||||
const [data, setData] = useState([] as MenuItem[])
|
const [data, setData] = useState([] as MenuItem[])
|
||||||
const [parents, setParents] = useState([] as MenuItem[])
|
const [parents, setParents] = useState([] as MenuItem[])
|
||||||
const [total, setTotal] = useState(0)
|
const [total, setTotal] = useState(0)
|
||||||
|
const [page, setPage] = useState(1)
|
||||||
|
const [pageSize, setPageSize] = useState(10)
|
||||||
const [keyword, setKeyword] = useState('')
|
const [keyword, setKeyword] = useState('')
|
||||||
// 是否显示按钮(type=3)
|
// 是否显示按钮(type=3)
|
||||||
const [showButtons, setShowButtons] = useState(true)
|
const [showButtons, setShowButtons] = useState(true)
|
||||||
@ -234,16 +236,31 @@ export default function Menus(){
|
|||||||
{ name: 'ReloadOutlined', node: <ReloadOutlined /> },
|
{ name: 'ReloadOutlined', node: <ReloadOutlined /> },
|
||||||
], [])
|
], [])
|
||||||
|
|
||||||
const fetchMenus = async (kw: string = keyword) => {
|
const fetchMenus = async (kw: string = keyword, p: number = page, ps: number = pageSize) => {
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
try{
|
try{
|
||||||
const { data } = await api.get(`/menus`, { params: { keyword: kw } })
|
const { data } = await api.get(`/menus`, { params: { keyword: kw, page: p, page_size: ps } })
|
||||||
if(data?.code === 0){ setData(data.data || []); setParents((data.data || []).filter((m: MenuItem) => m.type !== 3)) }
|
if(data?.code === 0){
|
||||||
|
const payload = data.data
|
||||||
|
if (payload && Array.isArray(payload.items)) {
|
||||||
|
const items = payload.items as MenuItem[]
|
||||||
|
setData(items)
|
||||||
|
setParents(items.filter((m: MenuItem) => m.type !== 3))
|
||||||
|
setTotal(Number(payload.total ?? items.length ?? 0))
|
||||||
|
setPage(Number(payload.page ?? p))
|
||||||
|
setPageSize(Number(payload.page_size ?? ps))
|
||||||
|
} else {
|
||||||
|
const list = (payload || []) as MenuItem[]
|
||||||
|
setData(list)
|
||||||
|
setParents(list.filter((m: MenuItem) => m.type !== 3))
|
||||||
|
setTotal(Array.isArray(list) ? list.length : 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
else { throw new Error(data?.message || '获取菜单失败') }
|
else { throw new Error(data?.message || '获取菜单失败') }
|
||||||
}catch(e: any){ message.error(e.message || '获取菜单失败') } finally { setLoading(false) }
|
}catch(e: any){ message.error(e.message || '获取菜单失败') } finally { setLoading(false) }
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => { fetchMenus(keyword) }, [])
|
useEffect(() => { fetchMenus(keyword, 1, pageSize) }, [])
|
||||||
|
|
||||||
const onCreate = () => { form.resetFields(); setCreateOpen(true) }
|
const onCreate = () => { form.resetFields(); setCreateOpen(true) }
|
||||||
const handleCreate = async () => {
|
const handleCreate = async () => {
|
||||||
@ -446,7 +463,7 @@ export default function Menus(){
|
|||||||
<div>
|
<div>
|
||||||
<PageHeader items={["系统管理","菜单管理"]} title="" />
|
<PageHeader items={["系统管理","菜单管理"]} title="" />
|
||||||
|
|
||||||
<Form form={searchForm} layout="inline" onFinish={(vals: any)=>{ const kw = String(vals?.keyword ?? '').trim(); setKeyword(kw); fetchMenus(kw) }} style={{ marginBottom: 12 }}>
|
<Form form={searchForm} layout="inline" onFinish={(vals: any)=>{ const kw = String(vals?.keyword ?? '').trim(); setKeyword(kw); fetchMenus(kw, 1, pageSize) }} style={{ marginBottom: 12 }}>
|
||||||
<Form.Item name="keyword">
|
<Form.Item name="keyword">
|
||||||
<Input allowClear placeholder="搜索菜单名称/路径/权限" style={{ width: 320 }} />
|
<Input allowClear placeholder="搜索菜单名称/路径/权限" style={{ width: 320 }} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
@ -466,7 +483,16 @@ export default function Menus(){
|
|||||||
dataSource={treeDataForTable}
|
dataSource={treeDataForTable}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
expandable={{ expandedRowKeys, onExpandedRowsChange: (keys) => setExpandedRowKeys(keys as React.Key[]), indentSize: 18, rowExpandable: (record: MenuItem) => Array.isArray(record.children) && record.children.length > 0 }}
|
expandable={{ expandedRowKeys, onExpandedRowsChange: (keys) => setExpandedRowKeys(keys as React.Key[]), indentSize: 18, rowExpandable: (record: MenuItem) => Array.isArray(record.children) && record.children.length > 0 }}
|
||||||
pagination={{ pageSize: 9999, hideOnSinglePage: true }}
|
// 展示分页统计:显示总条目数(与流程日志一致的受控分页)
|
||||||
|
pagination={{
|
||||||
|
current: page,
|
||||||
|
pageSize,
|
||||||
|
total,
|
||||||
|
showSizeChanger: true,
|
||||||
|
pageSizeOptions: [10, 20, 50, 100],
|
||||||
|
showTotal: (t, range) => `第 ${range[0]}-${range[1]} 条 / 共 ${t} 条`,
|
||||||
|
onChange: (p, ps) => { setPage(p); if (ps) setPageSize(ps); fetchMenus(keyword, p, ps ?? pageSize) }
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Modal title="新增菜单" open={createOpen} onOk={handleCreate} onCancel={() => setCreateOpen(false)} okText="创建" width={840}>
|
<Modal title="新增菜单" open={createOpen} onOk={handleCreate} onCancel={() => setCreateOpen(false)} okText="创建" width={840}>
|
||||||
|
|||||||
@ -116,7 +116,7 @@ export default function Permissions(){
|
|||||||
loading={loading}
|
loading={loading}
|
||||||
dataSource={data}
|
dataSource={data}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
pagination={{ current: page, pageSize, total, showSizeChanger: true, onChange: (p: number, ps?: number) => fetchPermissions(p, ps ?? pageSize, keyword) }}
|
pagination={{ current: page, pageSize, total, showSizeChanger: true, pageSizeOptions: [10, 20, 50, 100], onChange: (p: number, ps?: number) => fetchPermissions(p, ps ?? pageSize, keyword) }}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Modal title="新增权限" open={createOpen} onOk={handleCreate} onCancel={() => setCreateOpen(false)} okText="创建" width={840}>
|
<Modal title="新增权限" open={createOpen} onOk={handleCreate} onCancel={() => setCreateOpen(false)} okText="创建" width={840}>
|
||||||
|
|||||||
@ -125,7 +125,16 @@ export default function Positions(){
|
|||||||
loading={loading}
|
loading={loading}
|
||||||
dataSource={data}
|
dataSource={data}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
pagination={{ current: page, pageSize, total, showSizeChanger: true, onChange: (p: number, ps?: number) => fetchPositions(p, ps ?? pageSize, keyword) }}
|
// 展示分页统计:在分页栏显示总条目数
|
||||||
|
pagination={{
|
||||||
|
current: page,
|
||||||
|
pageSize,
|
||||||
|
total,
|
||||||
|
showSizeChanger: true,
|
||||||
|
pageSizeOptions: [10, 20, 50, 100],
|
||||||
|
showTotal: (t, range) => `第 ${range[0]}-${range[1]} 条 / 共 ${t} 条`,
|
||||||
|
onChange: (p: number, ps?: number) => fetchPositions(p, ps ?? pageSize, keyword)
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Modal title="新增岗位" open={createOpen} onOk={handleCreate} onCancel={() => setCreateOpen(false)} okText="创建" width={640}>
|
<Modal title="新增岗位" open={createOpen} onOk={handleCreate} onCancel={() => setCreateOpen(false)} okText="创建" width={640}>
|
||||||
|
|||||||
@ -239,7 +239,16 @@ export default function Roles(){
|
|||||||
loading={loading}
|
loading={loading}
|
||||||
dataSource={data}
|
dataSource={data}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
pagination={{ current: page, pageSize, total, showSizeChanger: true, onChange: (p: number, ps?: number) => fetchRoles(p, ps ?? pageSize, keyword) }}
|
// 展示分页统计:在分页栏显示总条目数
|
||||||
|
pagination={{
|
||||||
|
current: page,
|
||||||
|
pageSize,
|
||||||
|
total,
|
||||||
|
showSizeChanger: true,
|
||||||
|
pageSizeOptions: [10, 20, 50, 100],
|
||||||
|
showTotal: (t, range) => `第 ${range[0]}-${range[1]} 条 / 共 ${t} 条`,
|
||||||
|
onChange: (p: number, ps?: number) => fetchRoles(p, ps ?? pageSize, keyword)
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Modal title="新增角色" open={createOpen} onOk={handleCreate} onCancel={() => setCreateOpen(false)} okText="创建" width={840}>
|
<Modal title="新增角色" open={createOpen} onOk={handleCreate} onCancel={() => setCreateOpen(false)} okText="创建" width={840}>
|
||||||
|
|||||||
@ -236,12 +236,15 @@ export default function ScheduleJobs() {
|
|||||||
loading={loading}
|
loading={loading}
|
||||||
dataSource={data}
|
dataSource={data}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
|
// 展示分页统计:在分页栏显示总条目数
|
||||||
pagination={{
|
pagination={{
|
||||||
current: page,
|
current: page,
|
||||||
pageSize,
|
pageSize,
|
||||||
total,
|
total,
|
||||||
showSizeChanger: true,
|
showSizeChanger: true,
|
||||||
onChange: (p, ps) => fetchJobs(p, ps),
|
pageSizeOptions: [10, 20, 50, 100],
|
||||||
|
showTotal: (t, range) => `第 ${range[0]}-${range[1]} 条 / 共 ${t} 条`,
|
||||||
|
onChange: (p: number, ps?: number) => fetchJobs(p, ps ?? pageSize)
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|||||||
@ -343,8 +343,16 @@ export default function Users(){
|
|||||||
rowKey="id"
|
rowKey="id"
|
||||||
loading={loading}
|
loading={loading}
|
||||||
dataSource={data}
|
dataSource={data}
|
||||||
columns={columns}
|
columns={columns as any}
|
||||||
pagination={{ current: page, pageSize, total, showSizeChanger: true, onChange: (p: number, ps?: number) => fetchUsers(p, ps ?? pageSize, keyword) }}
|
// 展示分页统计:在分页栏显示总条目数
|
||||||
|
pagination={{
|
||||||
|
current: page,
|
||||||
|
pageSize,
|
||||||
|
total,
|
||||||
|
showSizeChanger: true,
|
||||||
|
showTotal: (t, range) => `第 ${range[0]}-${range[1]} 条 / 共 ${t} 条`,
|
||||||
|
onChange: (p: number, ps?: number) => fetchUsers(p, ps ?? pageSize, keyword)
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
{/* <Typography.Paragraph type="secondary" style={{ marginTop: 12 }}>
|
{/* <Typography.Paragraph type="secondary" style={{ marginTop: 12 }}>
|
||||||
提示:此页面已支持分页、创建、编辑与重置密码。
|
提示:此页面已支持分页、创建、编辑与重置密码。
|
||||||
|
|||||||
Reference in New Issue
Block a user