From bd4acd33583d6016b8931bead5843597c1292c57 Mon Sep 17 00:00:00 2001 From: nightcp Date: Tue, 2 Sep 2025 17:35:02 +0800 Subject: [PATCH 1/2] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E5=B7=B2=E7=9F=A5?= =?UTF-8?q?=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 校验创建考核唯一性 - 统一绩效列表和详情总分显示 - 优化弹窗提示API返回消息 --- app/departments/page.tsx | 10 ++++++++-- app/employees/page.tsx | 10 ++++++++-- app/evaluations/page.tsx | 19 ++++++++++++++++--- server/go.sum | 2 -- server/handlers/export.go | 2 +- server/handlers/kpi.go | 24 ++++++++++++++++++++++-- 6 files changed, 55 insertions(+), 12 deletions(-) diff --git a/app/departments/page.tsx b/app/departments/page.tsx index ed38aff..2f5ee38 100644 --- a/app/departments/page.tsx +++ b/app/departments/page.tsx @@ -13,9 +13,10 @@ import { departmentApi, type Department, type PaginatedResponse, type Pagination import { useAppContext } from "@/lib/app-context" import { Pagination, usePagination } from "@/components/pagination" import { LoadingInline } from "@/components/loading" +import { AxiosError } from "axios" export default function DepartmentsPage() { - const { Confirm } = useAppContext() + const { Confirm, Alert } = useAppContext() const [departments, setDepartments] = useState([]) const [loading, setLoading] = useState(true) const [dialogOpen, setDialogOpen] = useState(false) @@ -94,8 +95,13 @@ export default function DepartmentsPage() { try { await departmentApi.delete(id) fetchDepartments() - } catch (error) { + } catch (error: unknown) { console.error("删除部门失败:", error) + if (error instanceof AxiosError) { + Alert("删除失败", error.response?.data?.error || "删除部门失败,请重试") + } else { + Alert("删除失败", "删除部门失败,请重试") + } } } } diff --git a/app/employees/page.tsx b/app/employees/page.tsx index e22bbaa..85cd6d7 100644 --- a/app/employees/page.tsx +++ b/app/employees/page.tsx @@ -14,9 +14,10 @@ import { employeeApi, departmentApi, type Employee, type Department, type Pagina import { useAppContext } from "@/lib/app-context" import { Pagination, usePagination } from "@/components/pagination" import { LoadingInline } from "@/components/loading" +import { AxiosError } from "axios" export default function EmployeesPage() { - const { Confirm } = useAppContext() + const { Confirm, Alert } = useAppContext() const [employees, setEmployees] = useState([]) const [departments, setDepartments] = useState([]) const [managers, setManagers] = useState([]) @@ -147,8 +148,13 @@ export default function EmployeesPage() { try { await employeeApi.delete(id) fetchEmployees() - } catch (error) { + } catch (error: unknown) { console.error("删除员工失败:", error) + if (error instanceof AxiosError) { + Alert("删除失败", error.response?.data?.error || "删除员工失败,请重试") + } else { + Alert("删除失败", "删除员工失败,请重试") + } } } } diff --git a/app/evaluations/page.tsx b/app/evaluations/page.tsx index 9a82e2e..d4d5f8a 100644 --- a/app/evaluations/page.tsx +++ b/app/evaluations/page.tsx @@ -54,6 +54,7 @@ import { Pagination, usePagination } from "@/components/pagination" import { LoadingInline } from "@/components/loading" import { toast } from "sonner" import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip" +import { AxiosError } from "axios" export default function EvaluationsPage() { const { Alert, Confirm, getStatusBadge, isTouch } = useAppContext() @@ -451,9 +452,13 @@ export default function EvaluationsPage() { // 成功提示 Alert("创建成功", `已为 ${formData.employee_ids.length} 个员工创建考核`) - } catch (error) { + } catch (error: unknown) { console.error("创建评估失败:", error) - Alert("创建失败", "创建考核失败,请重试") + if (error instanceof AxiosError) { + Alert("创建失败", error.response?.data?.error || "创建考核失败,请重试") + } else { + Alert("创建失败", "创建考核失败,请重试") + } } } @@ -1313,7 +1318,15 @@ export default function EvaluationsPage() { {evaluation.template?.name} {getPeriodValue(evaluation)} -
{formatScore(evaluation.total_score)}
+
+ {formatScore( + evaluation.scores?.reduce( + (acc, score) => + acc + + (score.final_score ?? score.hr_score ?? score.manager_score ?? score.self_score ?? 0), 0 + ) + )} +
{getStatusBadge(evaluation.status)} diff --git a/server/go.sum b/server/go.sum index e021015..c7e3151 100644 --- a/server/go.sum +++ b/server/go.sum @@ -8,8 +8,6 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/dootask/tools/server/go v0.0.0-20250716160050-ecb1e6de2b73 h1:h77oVnQjMiuTTjnLCiJQrUUl9GfiTgK1hP9E2vzClQg= -github.com/dootask/tools/server/go v0.0.0-20250716160050-ecb1e6de2b73/go.mod h1:QTdgJGF8hr4pPTl7200zQbYJ2K0ag7UX5l2Tlm6LhgU= github.com/dootask/tools/server/go v0.0.0-20250717040353-7e9cc89be08c h1:TaUYF5MtdSoBg0tzNEqq5Xf2XZzhyw2zZSPrBgmC/UA= github.com/dootask/tools/server/go v0.0.0-20250717040353-7e9cc89be08c/go.mod h1:QTdgJGF8hr4pPTl7200zQbYJ2K0ag7UX5l2Tlm6LhgU= github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU= diff --git a/server/handlers/export.go b/server/handlers/export.go index 4d564e8..9ebb7f9 100644 --- a/server/handlers/export.go +++ b/server/handlers/export.go @@ -521,7 +521,7 @@ func ExportPeriodToExcel(c *gin.Context) { } } - sheetName := fmt.Sprintf("%s", evaluation.Employee.Name) + sheetName := evaluation.Employee.Name // Excel工作表名称有字符限制,如果名称过长或有特殊字符,使用简化名称 if len(sheetName) > 31 { sheetName = fmt.Sprintf("员工%d", idx+1) diff --git a/server/handlers/kpi.go b/server/handlers/kpi.go index 0127f5f..215310f 100644 --- a/server/handlers/kpi.go +++ b/server/handlers/kpi.go @@ -167,7 +167,7 @@ func GetEvaluations(c *gin.Context) { } // 构建查询 - query := models.DB.Preload("Employee.Department").Preload("Template").Preload("Scores") + query := models.DB.Preload("Employee.Department").Preload("Template").Preload("Scores").Preload("Scores.Item") // 添加筛选条件 if status != "" { @@ -240,8 +240,28 @@ func CreateEvaluation(c *gin.Context) { // 开始数据库事务 tx := models.DB.Begin() + row := models.KPIEvaluation{} + result := tx.Preload("Employee"). + Where("employee_id = ? AND template_id = ? AND period = ? AND year = ? AND month = ?", + evaluation.EmployeeID, evaluation.TemplateID, evaluation.Period, evaluation.Year, evaluation.Month). + Find(&row).Limit(1) + if result.Error != nil { + tx.Rollback() + c.JSON(http.StatusInternalServerError, gin.H{ + "error": "创建评估失败", + }) + return + } + if row.ID > 0 { + tx.Rollback() + c.JSON(http.StatusBadRequest, gin.H{ + "error": fmt.Sprintf("员工【%s】评估记录已存在", row.Employee.Name), + }) + return + } + // 创建评估记录 - result := tx.Create(&evaluation) + result = tx.Create(&evaluation) if result.Error != nil { tx.Rollback() c.JSON(http.StatusInternalServerError, gin.H{ From d87c49f272551dc37d7a1d990fb4ba695a09f16c Mon Sep 17 00:00:00 2001 From: nightcp Date: Tue, 9 Sep 2025 10:59:08 +0800 Subject: [PATCH 2/2] =?UTF-8?q?feat:=20=E5=9C=A8=E8=80=83=E6=A0=B8?= =?UTF-8?q?=E7=AE=A1=E7=90=86=E4=B8=AD=E5=BC=95=E5=85=A5=E5=91=A8=E6=9C=9F?= =?UTF-8?q?=E3=80=81=E5=B9=B4=E4=BB=BD=E3=80=81=E6=9C=88=E4=BB=BD=E5=92=8C?= =?UTF-8?q?=E5=AD=A3=E5=BA=A6=E7=9A=84=E7=AD=9B=E9=80=89=E7=8A=B6=E6=80=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/evaluations/page.tsx | 125 ++++++++++++++++++++++++++++++++++++++- lib/api.ts | 6 ++ server/handlers/kpi.go | 39 ++++++++++++ 3 files changed, 169 insertions(+), 1 deletion(-) diff --git a/app/evaluations/page.tsx b/app/evaluations/page.tsx index d4d5f8a..bb1004d 100644 --- a/app/evaluations/page.tsx +++ b/app/evaluations/page.tsx @@ -79,6 +79,12 @@ export default function EvaluationsPage() { const [statusFilter, setStatusFilter] = useState("all") const [employeeFilter, setEmployeeFilter] = useState("all") + // 周期筛选相关状态 + const [periodFilter, setPeriodFilter] = useState("") + const [yearFilter, setYearFilter] = useState("") + const [monthFilter, setMonthFilter] = useState("") + const [quarterFilter, setQuarterFilter] = useState("") + // 添加绩效视图Tab相关状态 const [viewTab, setViewTab] = useState<"my" | "team">("my") // 默认显示我的绩效 @@ -143,6 +149,20 @@ export default function EvaluationsPage() { params.status = statusFilter } + // 周期筛选参数 + if (periodFilter) { + params.period = periodFilter + } + if (yearFilter) { + params.year = yearFilter + } + if (monthFilter) { + params.month = monthFilter + } + if (quarterFilter) { + params.quarter = quarterFilter + } + // 根据Tab和角色设置员工筛选 if (viewTab === "my") { // 我的绩效:只显示自己的 @@ -178,7 +198,7 @@ export default function EvaluationsPage() { } finally { setLoading(false) } - }, [currentUser, currentPage, pageSize, statusFilter, employeeFilter, viewTab, isHR, isManager, refreshUnreadEvaluations]) + }, [currentUser, currentPage, pageSize, statusFilter, employeeFilter, periodFilter, yearFilter, monthFilter, quarterFilter, viewTab, isHR, isManager, refreshUnreadEvaluations]) // 获取模板列表 const fetchTemplates = async () => { @@ -402,6 +422,10 @@ export default function EvaluationsPage() { setViewTab(tab) setStatusFilter("all") setEmployeeFilter("all") + setPeriodFilter("") + setYearFilter("") + setMonthFilter("") + setQuarterFilter("") resetPagination() } @@ -1217,6 +1241,7 @@ export default function EvaluationsPage() { {loading && }
+ {/* 状态筛选 */} + + {/* 员工筛选 */} {viewTab === "team" && ( )} + + {/* 周期筛选 */} + + + {/* 年份筛选 */} + + + {/* 月份筛选 - 只在月度周期时显示 */} + {periodFilter === "monthly" && ( + + )} + + {/* 季度筛选 - 只在季度周期时显示 */} + {periodFilter === "quarterly" && ( + + )} + + {/* 重置筛选 */}