R40: 完成档案编辑扩展与员工导出

这个提交包含在:
2026-03-28 17:42:09 +08:00
父节点 1cac1447b1
当前提交 2eff8e01a2
共修改 10 个文件,包含 654 行新增和 23 行删除
+54
查看文件
@@ -7,21 +7,29 @@ import {
startEmployeeOffboarding,
startEmployeeOnboarding,
} from "../api/employees";
import { exportEmployees } from "../api/exports";
import { fetchDepartments } from "../api/departments";
import { isApiError } from "../api/types";
import { mapApiErrorMessage } from "../api/errorMessages";
import { useAuth } from "../auth/AuthContext";
import { hasPermission } from "../auth/permissions";
import { downloadBlob } from "../lib/download";
import { PageHeader } from "../components/common/PageHeader";
import { Button } from "../components/ui/button";
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "../components/ui/dropdown-menu";
import { Input } from "../components/ui/input";
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "../components/ui/table";
import { StatusBadge } from "../components/common/StatusBadge";
import { toast } from "sonner";
export function PeoplePage() {
const { permissions } = useAuth();
const queryClient = useQueryClient();
const [q, setQ] = useState("");
const [status, setStatus] = useState<"" | "active" | "archived">("");
const [departmentId, setDepartmentId] = useState("");
const [exportError, setExportError] = useState("");
const canExportEmployees = hasPermission(permissions, "system.users");
const departmentsQuery = useQuery({
queryKey: ["departments", "people-page", "active"],
@@ -88,6 +96,30 @@ export function PeoplePage() {
},
});
const exportMutation = useMutation({
mutationFn: (format: "csv" | "xlsx") =>
exportEmployees({
format,
q: q || undefined,
status: status || undefined,
departmentId: departmentId || undefined,
}),
onSuccess: (response, format) => {
downloadBlob(response.blob, response.filename ?? `employees.${format}`);
setExportError("");
toast.success("导出完成");
},
onError: (error) => {
const message = isApiError(error)
? `${error.code}: ${mapApiErrorMessage(error.code, error.details, error.message)}`
: error instanceof Error
? error.message
: "导出失败";
setExportError(message);
toast.error(message);
},
});
const items = employeesQuery.data?.items ?? [];
const departments = departmentsQuery.data?.items ?? [];
@@ -98,6 +130,23 @@ export function PeoplePage() {
subtitle="小团队用这里管入职、转正、离职与档案入口。"
actions={
<div className="flex gap-2">
{canExportEmployees ? (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="secondary" type="button" disabled={exportMutation.isPending}>
{exportMutation.isPending ? "导出中..." : "导出"}
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem onSelect={() => exportMutation.mutate("csv")}>
导出 CSV
</DropdownMenuItem>
<DropdownMenuItem onSelect={() => exportMutation.mutate("xlsx")}>
导出 Excel
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
) : null}
<Button asChild variant="secondary" type="button">
<Link to="/system/users">先新建账号</Link>
</Button>
@@ -137,6 +186,11 @@ export function PeoplePage() {
</div>
<div className="card-std p-6">
{exportError ? (
<p className="mb-4 rounded-lg border border-red-200 bg-red-50 px-3 py-2 text-sm text-red-600">
{exportError}
</p>
) : null}
{employeesQuery.isError ? (
<p className="mb-4 rounded-lg border border-red-200 bg-red-50 px-3 py-2 text-sm text-red-600">
{isApiError(employeesQuery.error)