feat(前端): 优化页面布局和查询表单样式

- 在FlowList和FlowRunLogs页面添加PageHeader组件
- 统一ScheduleJobs页面的查询表单样式为内联布局
- 为表格分页添加showSizeChanger选项
- 新增Rust代码规范文档
This commit is contained in:
2025-09-25 21:51:45 +08:00
parent 3f5b52ec2a
commit dfa1cbdd2f
4 changed files with 111 additions and 52 deletions

60
.trae/rules/code_rules.md Normal file
View File

@ -0,0 +1,60 @@
# Rust 复杂文件代码规范顺序(紧凑版)
1. **文件说明**
- 使用 `//!` 模块文档注释,描述文件或模块作用。
- 必须位于文件最顶部。
2. **版权声明(可选)**
- 如果项目需要版权信息,放在文档注释之后。
3. **外部依赖和模块引入use**
- 所有 `use` 必须放在文件顶部,不能出现在中间或底部。
- 引入顺序:标准库 -> 当前 crate 模块 -> 父模块 -> 第三方库
- 每一类之间空 1 行,同一类内部按字母序排列
- 所有 `use` **不允许**添加注释说明分组
- 紧凑要求:单行导入尽量合并,不要强行拆成多行
4. **子模块声明mod**
- 写在所有 `use` 之后
- `pub mod``mod` 都在这里集中声明
5. **常量定义const / static**
- 必须在类型定义之前
- 命名使用 `SCREAMING_SNAKE_CASE`
- 紧凑要求:值较短时保持单行,不要拆行
6. **类型别名type**
- 放在常量之后、结构体之前
7. **结构体定义struct**
- 放在文件的类型区域开头
- 所有 `struct` 必须集中写在这里,不允许与 `impl` 穿插
- 紧凑要求:字段数量不多时保持单行;字段多时每行一个
8. **枚举定义enum**
- 紧随结构体之后,集中定义所有枚举类型
- 紧凑要求:简单枚举保持单行,复杂枚举多行
9. **联合体定义union**
- 如果需要,放在枚举之后
10. **Trait 定义**
- 紧跟在所有数据类型struct/enum/union之后
11. **实现块impl Struct**
- 必须放在类型定义和 Trait 定义之后
- 内部方法顺序:构造函数 -> 公共方法 -> 私有方法
- 紧凑要求函数体内调用链、元组、match 分支若能在一行内写清楚,则保持单行
12. **Trait 实现impl Trait for Struct**
- 紧跟对应的 `impl Struct` 之后
- 不要与其他类型的实现混在一起
13. **自由函数fn不属于任何 impl**
- 文件中独立函数放在最后
- 按功能分组,可以用注释分隔
- 紧凑要求:参数少的函数调用保持单行
14. **测试模块(#[cfg(test)] mod tests**
- 永远放在文件最末尾
- 仅用于单元测试代码

View File

@ -4,6 +4,7 @@ import { PlusOutlined, ReloadOutlined, DeleteOutlined, EditOutlined, EyeOutlined
import { useNavigate } from 'react-router-dom' import { useNavigate } from 'react-router-dom'
import api, { type ApiResp } from '../utils/axios' import api, { type ApiResp } from '../utils/axios'
import dayjs from 'dayjs' import dayjs from 'dayjs'
import PageHeader from '../components/PageHeader'
interface FlowSummary { id: string; name: string; code?: string; remark?: string; created_at: string; updated_at: string; last_modified_by?: string } interface FlowSummary { id: string; name: string; code?: string; remark?: string; created_at: string; updated_at: string; last_modified_by?: string }
// 新增:扩展 Doc 以便复用 // 新增:扩展 Doc 以便复用
@ -206,7 +207,7 @@ export default function FlowList() {
width: 720, width: 720,
content: ( content: (
<pre style={{ maxHeight: 400, overflow: 'auto' }}> <pre style={{ maxHeight: 400, overflow: 'auto' }}>
{data.data?.yaml} {data.data?.yaml}
</pre> </pre>
) )
}) })
@ -219,6 +220,7 @@ export default function FlowList() {
return ( return (
<div> <div>
<div style={{ marginBottom: 12 }}> <div style={{ marginBottom: 12 }}>
<PageHeader items={["","流程管理"]} title="" />
<Form <Form
form={searchForm} form={searchForm}
layout="inline" layout="inline"

View File

@ -116,7 +116,7 @@ export default function FlowRunLogs() {
return ( return (
<div> <div>
<PageHeader items={["流程管理","运行日志"]} title="" /> <PageHeader items={["","运行日志"]} title="" />
<Form form={form} layout="inline" onFinish={() => fetchData(1, pageSize)} style={{ marginBottom: 12 }}> <Form form={form} layout="inline" onFinish={() => fetchData(1, pageSize)} style={{ marginBottom: 12 }}>
<Form.Item label="流程编码" name="flow_code"> <Form.Item label="流程编码" name="flow_code">
<Input placeholder="flow code" allowClear style={{ width: 280 }} /> <Input placeholder="flow code" allowClear style={{ width: 280 }} />
@ -141,7 +141,7 @@ export default function FlowRunLogs() {
dataSource={data} dataSource={data}
columns={columns} columns={columns}
scroll={{ x: 1600 }} scroll={{ x: 1600 }}
pagination={{ current: page, pageSize, total, onChange: (p, ps) => fetchData(p, ps) }} pagination={{ current: page, pageSize, total, showSizeChanger: true, pageSizeOptions: [10, 20, 50, 100], 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

@ -27,19 +27,22 @@ export default function ScheduleJobs() {
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 [keyword, setKeyword] = useState('') // 使用与系统日志一致的查询表单样式:内联 Form + 查询/重置
const [enabledFilter, setEnabledFilter] = useState<'all' | 'true' | 'false'>('all') const [searchForm] = Form.useForm()
const [modalOpen, setModalOpen] = useState(false) const [modalOpen, setModalOpen] = useState(false)
const [editing, setEditing] = useState<ScheduleJobItem | null>(null) const [editing, setEditing] = useState<ScheduleJobItem | null>(null)
const [form] = Form.useForm() const [form] = Form.useForm()
const [flowOptions, setFlowOptions] = useState<FlowOption[]>([]) const [flowOptions, setFlowOptions] = useState<FlowOption[]>([])
const fetchJobs = async (p: number = page, ps: number = pageSize, kw: string = keyword, ef: 'all' | 'true' | 'false' = enabledFilter) => { const fetchJobs = async (p: number = page, ps: number = pageSize) => {
const v = searchForm.getFieldsValue()
setLoading(true) setLoading(true)
try { try {
const params: any = { page: p, page_size: ps, keyword: kw } const params: any = { page: p, page_size: ps }
if (ef !== 'all') params.enabled = ef === 'true' if (v.keyword) params.keyword = String(v.keyword).trim()
// 按系统日志风格:下拉框 allowClear不选择即为“全部”选择 true/false 才加入过滤
if (typeof v.enabled === 'boolean') params.enabled = v.enabled
const { data } = await api.get('/schedule_jobs', { params }) const { data } = await api.get('/schedule_jobs', { params })
if (data?.code === 0) { if (data?.code === 0) {
const resp = data.data as PageResp<ScheduleJobItem> const resp = data.data as PageResp<ScheduleJobItem>
@ -67,7 +70,7 @@ export default function ScheduleJobs() {
} }
} }
useEffect(() => { fetchJobs(1, pageSize, keyword, enabledFilter) }, []) useEffect(() => { fetchJobs(1, pageSize) }, [])
const columns: ColumnsType<ScheduleJobItem> = useMemo(() => [ const columns: ColumnsType<ScheduleJobItem> = useMemo(() => [
{ title: '名称', dataIndex: 'name', key: 'name' }, { title: '名称', dataIndex: 'name', key: 'name' },
@ -144,7 +147,7 @@ export default function ScheduleJobs() {
if (data?.code === 0) { if (data?.code === 0) {
message.success('创建成功') message.success('创建成功')
setModalOpen(false) setModalOpen(false)
fetchJobs(1, pageSize, keyword, enabledFilter) fetchJobs(1, pageSize)
} else { } else {
throw new Error(data?.message || '创建失败') throw new Error(data?.message || '创建失败')
} }
@ -161,7 +164,7 @@ export default function ScheduleJobs() {
if (data?.code === 0) { if (data?.code === 0) {
message.success('删除成功') message.success('删除成功')
const nextPage = data?.data?.deleted ? (data?.data?.remaining === 0 && page > 1 ? page - 1 : page) : page const nextPage = data?.data?.deleted ? (data?.data?.remaining === 0 && page > 1 ? page - 1 : page) : page
fetchJobs(nextPage, pageSize, keyword, enabledFilter) fetchJobs(nextPage, pageSize)
} else { } else {
throw new Error(data?.message || '删除失败') throw new Error(data?.message || '删除失败')
} }
@ -201,38 +204,33 @@ export default function ScheduleJobs() {
} }
} }
const handleSearch = () => {
fetchJobs(1, pageSize, keyword, enabledFilter)
}
return ( return (
<div> <div>
<PageHeader items={["系统管理", "调度任务管理"]} title="" /> <PageHeader items={["系统管理", "定时任务"]} title="" />
<div style={{ background: '#fff', padding: 16, marginBottom: 12 }}> {/* 与系统日志一致的查询区:内联表单 */}
<Space wrap> <Form form={searchForm} layout="inline" onFinish={() => fetchJobs(1, pageSize)} style={{ marginBottom: 12 }}>
<Input.Search allowClear placeholder="关键字" value={keyword} onChange={e => setKeyword(e.target.value)} onSearch={handleSearch} style={{ width: 280 }} /> <Form.Item label="关键字" name="keyword">
<Select <Input allowClear placeholder="关键字" style={{ width: 320 }} />
value={enabledFilter} </Form.Item>
onChange={(v) => setEnabledFilter(v)} <Form.Item label="状态" name="enabled">
style={{ width: 160 }} <Select allowClear placeholder="全部" style={{ width: 140 }} options={[{ label: '启用', value: true }, { label: '禁用', value: false }]} />
options={[ </Form.Item>
{ label: '全部状态', value: 'all' }, <Form.Item>
{ label: '仅启用', value: 'true' }, <Space>
{ label: '仅禁用', value: 'false' }, <Button type="primary" htmlType="submit"></Button>
]} <Button onClick={() => { searchForm.resetFields(); fetchJobs(1, 10) }}></Button>
/>
<Button type="primary" onClick={() => fetchJobs(1, pageSize, keyword, enabledFilter)}></Button>
<Button onClick={() => { setKeyword(''); setEnabledFilter('all'); fetchJobs(1, pageSize, '', 'all') }}></Button>
</Space> </Space>
</div> </Form.Item>
</Form>
<div style={{ background: '#fff', padding: 16 }}> {/* 操作区保持与其他页面一致 */}
<div style={{ marginBottom: 12 }}> <div style={{ marginBottom: 12 }}>
<Space> <Space>
<Button type="primary" icon={<PlusOutlined />} onClick={openCreate}></Button> <Button type="primary" icon={<PlusOutlined />} onClick={openCreate}></Button>
<Button icon={<ReloadOutlined />} onClick={() => fetchJobs(page, pageSize, keyword, enabledFilter)}></Button> <Button icon={<ReloadOutlined />} onClick={() => fetchJobs(page, pageSize)}></Button>
</Space> </Space>
</div> </div>
<Table <Table
rowKey="id" rowKey="id"
loading={loading} loading={loading}
@ -243,10 +241,9 @@ export default function ScheduleJobs() {
pageSize, pageSize,
total, total,
showSizeChanger: true, showSizeChanger: true,
onChange: (p, ps) => fetchJobs(p, ps, keyword, enabledFilter), onChange: (p, ps) => fetchJobs(p, ps),
}} }}
/> />
</div>
<Modal <Modal
open={modalOpen} open={modalOpen}