feat: 统一分页组件并添加批量删除功能

为多个页面组件添加统一的分页统计显示和批量删除功能
在日志管理页面添加批量删除接口和前端实现
优化表格分页配置,统一显示总条目数和分页选项
This commit is contained in:
2025-09-25 23:52:01 +08:00
parent a71bbb0961
commit 214605d912
14 changed files with 303 additions and 48 deletions

View File

@ -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 }))))
} }

View File

@ -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 }))))
} }

View File

@ -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)
} }

View File

@ -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)
} }

View File

@ -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}>

View File

@ -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)
}}
/> />
{/* 编辑基础信息弹窗 */} {/* 编辑基础信息弹窗 */}

View File

@ -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">

View File

@ -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">

View File

@ -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}>

View File

@ -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}>

View File

@ -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}>

View File

@ -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}>

View File

@ -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)
}} }}
/> />

View File

@ -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 }}>
提示:此页面已支持分页、创建、编辑与重置密码。 提示:此页面已支持分页、创建、编辑与重置密码。