完成五人团队正式内用最小收口(07-18-small-team-internal-readiness)

这个提交包含在:
2026-07-18 15:46:14 +08:00
父节点 58c8decef9
当前提交 5d0a5f9a38
共修改 134 个文件,包含 10790 行新增和 1413 行删除
+23 -18
查看文件
@@ -37,6 +37,7 @@ import { useAuth } from "../auth/AuthContext";
import { hasPermission } from "../auth/permissions";
import { EmployeeAvatar } from "../components/common/EmployeeAvatar";
import { ActionConfirmDialog } from "../components/common/ActionConfirmDialog";
import { AttachmentDeleteConfirmDialog } from "../components/common/AttachmentDeleteConfirmDialog";
import { PageHeader } from "../components/common/PageHeader";
import { EmployeePerformancePanel } from "../components/people/EmployeePerformancePanel";
import { EmployeeSalaryPanel } from "../components/people/EmployeeSalaryPanel";
@@ -184,6 +185,8 @@ export function EmployeeProfilePage() {
});
const canManageEmployeeProfile = hasPermission(permissions, "system.users");
const canManageSensitiveHr = hasPermission(permissions, "system.settings");
const canManageEmployeeContracts = hasPermission(permissions, "employee.contract.manage");
const canManagePayroll = hasPermission(permissions, "payroll.manage");
const canEditSelfProfile = me?.id === employeeId;
const canEditBaseFields = canManageEmployeeProfile || canEditSelfProfile;
const canEditExtendedFields = canManageEmployeeProfile;
@@ -215,7 +218,7 @@ export function EmployeeProfilePage() {
});
const contractsQuery = useQuery({
enabled: Boolean(employeeId) && canManageEmployeeProfile,
enabled: Boolean(employeeId) && canManageEmployeeContracts,
queryKey: ["employee-contracts", employeeId],
queryFn: () => fetchEmployeeContracts(employeeId),
});
@@ -267,7 +270,7 @@ export function EmployeeProfilePage() {
}, [employeeId, requestedTab]);
useEffect(() => {
if (activeTab === "salary" && !canManageSensitiveHr && !canEditSelfProfile) {
if (activeTab === "salary" && !canManagePayroll && !canEditSelfProfile) {
updateActiveTab("profile");
return;
}
@@ -275,11 +278,11 @@ export function EmployeeProfilePage() {
updateActiveTab("profile");
return;
}
if (canManageEmployeeProfile || canEditSelfProfile || activeTab === "profile") {
if (canManageEmployeeProfile || canManageEmployeeContracts || canEditSelfProfile || activeTab === "profile") {
return;
}
updateActiveTab("profile");
}, [activeTab, canEditSelfProfile, canManageEmployeeProfile, canManageSensitiveHr]);
}, [activeTab, canEditSelfProfile, canManageEmployeeContracts, canManageEmployeeProfile, canManagePayroll, canManageSensitiveHr]);
useEffect(() => {
setAvatarFile(null);
@@ -464,10 +467,11 @@ export function EmployeeProfilePage() {
});
const deleteContractMutation = useMutation({
mutationFn: (contractId: string) => deleteEmployeeContract(employeeId, contractId),
onSuccess: async (_result, contractId) => {
mutationFn: ({ contractId, reason }: { contractId: string; reason: string }) => deleteEmployeeContract(employeeId, contractId, reason),
onSuccess: async (_result, variables) => {
await queryClient.invalidateQueries({ queryKey: ["employee-contracts", employeeId] });
if (editingContractId === contractId) {
setDeleteContractTarget(null);
if (editingContractId === variables.contractId) {
setEditingContractId(null);
setContractForm(buildEmptyContractForm());
setContractAttachmentFile(null);
@@ -538,7 +542,7 @@ export function EmployeeProfilePage() {
const performanceOnlyMode = activeTab === "performance" && profilePermissionDenied && Boolean(performanceQuery.data);
const showPerformanceTab =
canManageSensitiveHr || canEditSelfProfile || performanceOnlyMode || performanceQuery.data?.viewerRole === "manager";
const showSalaryTab = canManageSensitiveHr || canEditSelfProfile;
const showSalaryTab = canManagePayroll || canEditSelfProfile;
const performanceContent =
performanceQuery.isLoading ? (
@@ -555,7 +559,7 @@ export function EmployeeProfilePage() {
]}
/>
);
const salaryContent = <EmployeeSalaryPanel employeeId={employeeId} canManage={canManageSensitiveHr} isSelf={canEditSelfProfile} />;
const salaryContent = <EmployeeSalaryPanel employeeId={employeeId} canManage={canManagePayroll} isSelf={canEditSelfProfile} />;
if (profileQuery.isLoading || (activeTab === "performance" && profilePermissionDenied && performanceQuery.isLoading)) {
return <div className="card-std p-6 text-sm text-zinc-500">加载员工档案中...</div>;
@@ -737,7 +741,7 @@ export function EmployeeProfilePage() {
<Tabs value={activeTab} onValueChange={(value) => updateActiveTab(value as ProfileTab)}>
<TabsList className="flex w-full flex-wrap justify-start gap-1">
<TabsTrigger value="profile">档案</TabsTrigger>
{canManageEmployeeProfile ? <TabsTrigger value="contracts">合同</TabsTrigger> : null}
{canManageEmployeeContracts ? <TabsTrigger value="contracts">合同</TabsTrigger> : null}
{canManageEmployeeProfile ? <TabsTrigger value="trainingCertificates">培训与证书</TabsTrigger> : null}
{showPerformanceTab ? <TabsTrigger value="performance">绩效</TabsTrigger> : null}
{showSalaryTab ? <TabsTrigger value="salary">薪资</TabsTrigger> : null}
@@ -1064,7 +1068,7 @@ export function EmployeeProfilePage() {
</div>
</TabsContent>
{canManageEmployeeProfile ? (
{canManageEmployeeContracts ? (
<TabsContent value="contracts" className="space-y-6">
{contractsQuery.isError ? (
<div className="rounded-lg border border-red-200 bg-red-50 px-3 py-2 text-sm text-red-600">
@@ -1115,7 +1119,7 @@ export function EmployeeProfilePage() {
<TableCell className="max-w-[220px]">
{contract.attachmentUrl ? (
<div className="space-y-2">
<div className="truncate text-xs text-zinc-500">{contract.attachmentPath ?? "-"}</div>
<div className="text-xs text-zinc-500">{contract.hasAttachment ? "已上传附件" : "未上传附件"}</div>
<Button
type="button"
size="sm"
@@ -1203,7 +1207,7 @@ export function EmployeeProfilePage() {
<div className="rounded-2xl border border-zinc-200 bg-zinc-50 p-4">
<div className="text-sm font-medium text-zinc-900">附件管理</div>
<div className="mt-1 text-xs text-zinc-500">
当前附件:{contracts.find((item) => item.id === editingContractId)?.attachmentPath ?? "未上传"}
当前附件:{contracts.find((item) => item.id === editingContractId)?.hasAttachment ? "已上传" : "未上传"}
</div>
<div className="mt-3 grid gap-3">
<Input
@@ -1520,17 +1524,18 @@ export function EmployeeProfilePage() {
deleteAvatarMutation.mutate();
}}
/>
<ActionConfirmDialog
<AttachmentDeleteConfirmDialog
open={Boolean(deleteContractTarget)}
onOpenChange={(open) => {
if (!open) setDeleteContractTarget(null);
}}
kind="delete"
title="确认删除员工合同?"
targetName={deleteContractTarget ? `合同 ${deleteContractTarget.contractNo}` : undefined}
description="删除后该合同记录和附件引用将从员工档案中移除,生效中的合同不能删除。"
onConfirm={async () => {
description={deleteContractTarget ? `对象:合同 ${deleteContractTarget.contractNo}。删除后合同记录与附件引用将从员工档案移除,且无法在 OA 内恢复。` : undefined}
pending={deleteContractMutation.isPending}
onConfirm={async (reason) => {
if (deleteContractTarget) {
deleteContractMutation.mutate(deleteContractTarget.id);
deleteContractMutation.mutate({ contractId: deleteContractTarget.id, reason });
}
}}
/>