feat: 培训/绩效/违纪员工端签收+管理端签字状态只读化
- 后端: portal新增培训/绩效/违纪列表查看+签收接口,签收时创建证据链 - 管理端: 弹窗去掉HR手动签字勾选,改为只读显示签字状态 - 绩效列表新增签字状态列 - 员工端: 新增MyRecords页面,支持培训签收/拒绝、绩效签字、违纪签字 - 员工端导航新增'我的记录'入口 - EvidenceCategory新增TRAINING/PERFORMANCE类型
This commit is contained in:
@@ -193,7 +193,6 @@ function DisciplinaryForm({ employees, record, onSubmit, onClose }: {
|
||||
severity: record?.severity || 'WARNING',
|
||||
action: record?.action || 'ORAL_WARNING',
|
||||
actionDetail: record?.actionDetail || '',
|
||||
employeeAck: record?.employeeAck || false,
|
||||
witness: record?.witness || '',
|
||||
})
|
||||
|
||||
@@ -263,10 +262,18 @@ function DisciplinaryForm({ employees, record, onSubmit, onClose }: {
|
||||
<Label>见证人</Label>
|
||||
<Input value={form.witness} onChange={(e) => setForm({ ...form, witness: e.target.value })} placeholder="见证人(选填)" />
|
||||
</div>
|
||||
<label className="flex items-center gap-2">
|
||||
<input type="checkbox" checked={form.employeeAck} onChange={(e) => setForm({ ...form, employeeAck: e.target.checked })} />
|
||||
<span className="text-sm">员工已签字确认</span>
|
||||
</label>
|
||||
{record && (
|
||||
<div>
|
||||
<Label>签字状态</Label>
|
||||
<div className="text-sm text-gray-600">
|
||||
{record.employeeAck ? (
|
||||
<span className="text-green-600">已签字({record.ackDate ? new Date(record.ackDate).toLocaleDateString('zh-CN') : ''})</span>
|
||||
) : (
|
||||
<span className="text-amber-600">待签字 <span className="text-xs text-gray-400 ml-1">由员工在员工端签字确认</span></span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex justify-end gap-2 pt-2">
|
||||
<Button variant="secondary" size="sm" onClick={onClose}>取消</Button>
|
||||
<Button size="sm" onClick={() => onSubmit(form)} disabled={!form.employeeId || !form.description}>保存</Button>
|
||||
|
||||
@@ -99,14 +99,15 @@ export default function PerformanceRecords() {
|
||||
<th className="pb-2 pr-4 font-medium">等级</th>
|
||||
<th className="pb-2 pr-4 font-medium">结果</th>
|
||||
<th className="pb-2 pr-4 font-medium">考评人</th>
|
||||
<th className="pb-2 pr-4 font-medium">签字</th>
|
||||
<th className="pb-2 pr-4 font-medium">操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{isLoading ? (
|
||||
<tr><td colSpan={8} className="py-8 text-center text-gray-400">加载中...</td></tr>
|
||||
<tr><td colSpan={9} className="py-8 text-center text-gray-400">加载中...</td></tr>
|
||||
) : records.length === 0 ? (
|
||||
<tr><td colSpan={8} className="py-8 text-center text-gray-400">暂无绩效记录</td></tr>
|
||||
<tr><td colSpan={9} className="py-8 text-center text-gray-400">暂无绩效记录</td></tr>
|
||||
) : records.map((r: any) => (
|
||||
<tr key={r.id} className="border-b hover:bg-gray-50">
|
||||
<td className="py-2 pr-4">
|
||||
@@ -122,6 +123,13 @@ export default function PerformanceRecords() {
|
||||
</span>
|
||||
</td>
|
||||
<td className="py-2 pr-4 text-gray-600">{r.reviewer || '-'}</td>
|
||||
<td className="py-2 pr-4">
|
||||
{r.employeeAck ? (
|
||||
<span className="text-xs text-green-600">已签字</span>
|
||||
) : (
|
||||
<span className="text-xs text-amber-600">待签字</span>
|
||||
)}
|
||||
</td>
|
||||
<td className="py-2 pr-4">
|
||||
<div className="flex gap-1">
|
||||
<button onClick={() => setEditRecord(r)} className="p-1 hover:bg-gray-100 rounded">
|
||||
@@ -179,7 +187,6 @@ function PerformanceForm({ employees, record, onSubmit, onClose }: {
|
||||
summary: record?.summary || '',
|
||||
improvementPlan: record?.improvementPlan || '',
|
||||
reviewer: record?.reviewer || '',
|
||||
employeeAck: record?.employeeAck || false,
|
||||
})
|
||||
|
||||
return (
|
||||
@@ -241,6 +248,18 @@ function PerformanceForm({ employees, record, onSubmit, onClose }: {
|
||||
<Label>改进计划</Label>
|
||||
<Input value={form.improvementPlan} onChange={(e) => setForm({ ...form, improvementPlan: e.target.value })} placeholder="改进计划(选填)" />
|
||||
</div>
|
||||
{record && (
|
||||
<div>
|
||||
<Label>签字状态</Label>
|
||||
<div className="text-sm text-gray-600">
|
||||
{record.employeeAck ? (
|
||||
<span className="text-green-600">已签字({record.ackDate ? new Date(record.ackDate).toLocaleDateString('zh-CN') : ''})</span>
|
||||
) : (
|
||||
<span className="text-amber-600">待签字 <span className="text-xs text-gray-400 ml-1">由员工在员工端签字确认</span></span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex justify-end gap-2 pt-2">
|
||||
<Button variant="secondary" size="sm" onClick={onClose}>取消</Button>
|
||||
<Button size="sm" onClick={() => onSubmit(form)} disabled={!form.employeeId || !form.period}>保存</Button>
|
||||
|
||||
@@ -197,7 +197,6 @@ function TrainingForm({ employees, record, onSubmit, onClose }: {
|
||||
content: record?.content || '',
|
||||
trainer: record?.trainer || '',
|
||||
duration: record?.duration || 0,
|
||||
ackStatus: record?.ackStatus || 'PENDING',
|
||||
remark: record?.remark || '',
|
||||
})
|
||||
|
||||
@@ -242,14 +241,17 @@ function TrainingForm({ employees, record, onSubmit, onClose }: {
|
||||
<Input type="number" min={0} step={0.5} value={form.duration} onChange={(e) => setForm({ ...form, duration: Number(e.target.value) })} />
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<Label>签收状态</Label>
|
||||
<Select value={form.ackStatus} onChange={(e) => setForm({ ...form, ackStatus: e.target.value })}>
|
||||
<option value="PENDING">待签收</option>
|
||||
<option value="SIGNED">已签收</option>
|
||||
<option value="REFUSED">拒绝签收</option>
|
||||
</Select>
|
||||
</div>
|
||||
{record && (
|
||||
<div>
|
||||
<Label>签收状态</Label>
|
||||
<div className="text-sm text-gray-600">
|
||||
<span className={`inline-block px-2 py-0.5 rounded text-xs ${ACK_COLORS[record.ackStatus] || 'bg-gray-50 text-gray-600'}`}>
|
||||
{ACK_LABELS[record.ackStatus] || record.ackStatus}
|
||||
</span>
|
||||
<span className="ml-2 text-xs text-gray-400">由员工在员工端签收</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div>
|
||||
<Label>备注</Label>
|
||||
<Input value={form.remark} onChange={(e) => setForm({ ...form, remark: e.target.value })} placeholder="备注" />
|
||||
|
||||
Reference in New Issue
Block a user