feat: 空状态引导全面应用与考勤批量标记

admin:
- 17 个列表页空态统一为 QueryEmpty 引导:学生/账单/入住/费用/押金/
  教室/房间/班级/考试/排课/机构/考勤机/钱包/租赁/通知/角色等,
  有创建权限的页面附带主操作按钮,无权限时纯展示
- 教师端课堂点名新增「全部已打卡/全部未打卡」批量按钮:
  仅作用于状态不一致的记录,确认后调用批量接口,展示成功/失败数量

server:
- 新增 PUT /attendance-records/batch-status 批量改状态接口
  (ids ≤200,逐条权限校验与会话锁,部分失败返回 failedIds,
  审计日志记录批量结果;路由声明在 :id 之前避免被捕获)

aislop scan: 5 引擎 0 issues
This commit is contained in:
2026-08-07 18:00:29 +08:00
parent c10476d203
commit 1b4ba893fd
23 changed files with 350 additions and 77 deletions

View File

@@ -1,8 +1,9 @@
import React from 'react';
import { Button, Empty, Popconfirm, Space, Table, Tag } from 'antd';
import { Button, Popconfirm, Space, Table, Tag } from 'antd';
import { DeleteOutlined, InboxOutlined } from '@ant-design/icons';
import dayjs from 'dayjs';
import PermissionButton from '../../components/PermissionButton';
import { QueryEmpty } from '../../components/QueryState';
import { message } from '../../ui/app-message';
import { statusMap } from './DepositModals';
import type { DepositRecord } from './DepositModals';
@@ -11,6 +12,8 @@ export interface DepositTableProps {
data: any[];
loading: boolean;
canPurgeDeposit: boolean;
canCreateDeposit?: boolean;
onCreateDeposit?: () => void;
refundForm: ReturnType<typeof import('antd').Form.useForm>[0];
onDetail: (record: DepositRecord) => void;
onRefund: (record: DepositRecord) => void;
@@ -22,6 +25,8 @@ export const DepositTable: React.FC<DepositTableProps> = ({
data,
loading,
canPurgeDeposit,
canCreateDeposit,
onCreateDeposit,
refundForm,
onDetail,
onRefund,
@@ -142,7 +147,18 @@ export const DepositTable: React.FC<DepositTableProps> = ({
pageSizeOptions: [15, 30, 50, 100],
showTotal: (total) => `${total}`,
}}
locale={{ emptyText: <Empty description="暂无数据" /> }}
locale={{
emptyText: (
<QueryEmpty
description="暂无数据"
action={
canCreateDeposit && onCreateDeposit
? { label: '收取押金', onClick: onCreateDeposit }
: undefined
}
/>
),
}}
/>
);
};

View File

@@ -225,6 +225,12 @@ const DepositsPage: React.FC = () => {
setBatchModal(true);
};
const openCreateDeposit = () => {
createForm.resetFields();
createForm.setFieldsValue({ amount: 500, paidDate: dayjs() });
setCreateModal(true);
};
const handleBatchRoomTypeChange = (roomType: string) => {
setBatchRoomType(roomType);
// 切换房型后候选学生列表会变化,重置勾选状态,避免把上一房型的选择提交到新房型
@@ -431,11 +437,7 @@ const DepositsPage: React.FC = () => {
permission="deposit:create"
type="primary"
icon={<PlusOutlined />}
onClick={() => {
createForm.resetFields();
createForm.setFieldsValue({ amount: 500, paidDate: dayjs() });
setCreateModal(true);
}}
onClick={openCreateDeposit}
>
</PermissionButton>
@@ -458,6 +460,8 @@ const DepositsPage: React.FC = () => {
data={filteredData}
loading={loading || (!!filterRoomType && eligibleLoading)}
canPurgeDeposit={canPurgeDeposit}
canCreateDeposit={hasPermission('deposit:create')}
onCreateDeposit={openCreateDeposit}
refundForm={refundForm}
onDetail={(record) => setDetailModal(record)}
onRefund={(record) => setRefundModal(record)}