add
This commit is contained in:
parent
e223a02f95
commit
1fb33c2408
|
@ -1,3 +1,6 @@
|
|||
{
|
||||
"marscode.chatLanguage": "cn"
|
||||
"marscode.chatLanguage": "cn",
|
||||
"marscode.codeCompletionPro": {
|
||||
"enableCodeCompletionPro": true
|
||||
}
|
||||
}
|
|
@ -14,6 +14,7 @@ import {
|
|||
UnauthorizedException,
|
||||
Logger,
|
||||
} from '@nestjs/common';
|
||||
import { Request } from '@nestjs/common';
|
||||
import { AuthService } from './auth.service';
|
||||
import { AuthSchema, JwtPayload } from '@nice/common';
|
||||
import { AuthGuard } from './auth.guard';
|
||||
|
|
|
@ -27,11 +27,14 @@ export class StaffController {
|
|||
}
|
||||
@Get('find-by-dept')
|
||||
async findByDept(
|
||||
@Query('dept-id') deptId: string,
|
||||
@Query('dept-id') deptId: string | null,
|
||||
@Query('domain-id') domainId: string,
|
||||
) {
|
||||
try {
|
||||
const result = await this.staffService.findByDept({ deptId, domainId });
|
||||
const result = await this.staffService.findByDept({
|
||||
deptId: deptId || null,
|
||||
domainId: domainId,
|
||||
});
|
||||
return {
|
||||
data: result,
|
||||
errmsg: 'success',
|
||||
|
|
|
@ -72,6 +72,8 @@ const StaffSelect = {
|
|||
|
||||
@Injectable()
|
||||
export class StaffService extends BaseService<Prisma.StaffDelegate> {
|
||||
//索引
|
||||
[x: string]: any;
|
||||
constructor(private readonly departmentService: DepartmentService) {
|
||||
super(db, ObjectType.STAFF, true);
|
||||
}
|
||||
|
@ -83,7 +85,7 @@ export class StaffService extends BaseService<Prisma.StaffDelegate> {
|
|||
async findByDept(data: z.infer<typeof StaffMethodSchema.findByDept>) {
|
||||
const { deptId, domainId } = data;
|
||||
const childDepts = await this.departmentService.getDescendantIds(
|
||||
deptId,
|
||||
deptId || null,
|
||||
true,
|
||||
);
|
||||
const result = await db.staff.findMany({
|
||||
|
|
|
@ -39,6 +39,7 @@ export class TrainSituationRouter {
|
|||
deptId: z.string().optional(),
|
||||
domainId: z.string().optional(),
|
||||
trainContentId: z.string().optional(),
|
||||
date: z.string().optional(),
|
||||
}),
|
||||
)
|
||||
.query(async ({ input }) => {
|
||||
|
|
|
@ -18,13 +18,13 @@ export class TrainSituationService extends BaseService<Prisma.TrainSituationDele
|
|||
// 创建培训情况
|
||||
async create(args: Prisma.TrainSituationCreateArgs) {
|
||||
console.log(args);
|
||||
const result = await super.create(args);
|
||||
const result = await db.trainSituation.create(args);
|
||||
this.emitDataChanged(CrudOperation.CREATED, result);
|
||||
return result;
|
||||
}
|
||||
// 更新培训情况
|
||||
async update(args: Prisma.TrainSituationUpdateArgs) {
|
||||
const result = await super.update(args);
|
||||
const result = await db.trainSituation.update(args);
|
||||
this.emitDataChanged(CrudOperation.UPDATED, result);
|
||||
return result;
|
||||
}
|
||||
|
@ -35,10 +35,11 @@ export class TrainSituationService extends BaseService<Prisma.TrainSituationDele
|
|||
return result;
|
||||
}
|
||||
// 查找某一单位所有人员的培训情况
|
||||
async findManyByDeptId(args: {
|
||||
async findManyByDeptId(params: {
|
||||
deptId?: string;
|
||||
domainId?: string;
|
||||
trainContentId?: string;
|
||||
date?: string;
|
||||
}): Promise<
|
||||
{
|
||||
id: string;
|
||||
|
@ -49,16 +50,56 @@ export class TrainSituationService extends BaseService<Prisma.TrainSituationDele
|
|||
score: number;
|
||||
}[]
|
||||
> {
|
||||
const staffs = await this.staffService.findByDept({
|
||||
deptId: args.deptId,
|
||||
domainId: args.domainId,
|
||||
});
|
||||
const { deptId, domainId, trainContentId, date } = params;
|
||||
|
||||
// 构建查询条件
|
||||
const where: Prisma.TrainSituationWhereInput = {};
|
||||
|
||||
if (deptId) {
|
||||
const staffs = await this.staffService.findManyByDeptId(deptId);
|
||||
where.staffId = { in: staffs.map((s) => s.id) };
|
||||
}
|
||||
|
||||
if (trainContentId) {
|
||||
where.trainContentId = trainContentId;
|
||||
}
|
||||
|
||||
// 添加日期过滤条件
|
||||
if (date) {
|
||||
// 创建日期的开始时间(当天的00:00:00)
|
||||
const startDate = new Date(date);
|
||||
startDate.setHours(0, 0, 0, 0);
|
||||
|
||||
// 创建日期的结束时间(当天的23:59:59.999)
|
||||
const endDate = new Date(date);
|
||||
endDate.setHours(23, 59, 59, 999);
|
||||
|
||||
// 设置createdAt字段在指定的日期范围内
|
||||
where.createdAt = {
|
||||
gte: startDate,
|
||||
lte: endDate,
|
||||
};
|
||||
|
||||
// 日志输出,方便调试
|
||||
console.log(
|
||||
`Filtering train situations between ${startDate.toISOString()} and ${endDate.toISOString()}`,
|
||||
);
|
||||
}
|
||||
|
||||
// 查询结果并包含关联数据
|
||||
const result = await super.findMany({
|
||||
where: {
|
||||
staffId: {
|
||||
in: staffs.map((staff) => staff.id),
|
||||
where,
|
||||
include: {
|
||||
staff: {
|
||||
include: {
|
||||
department: true,
|
||||
position: true,
|
||||
},
|
||||
},
|
||||
...(args.trainContentId ? { trainContentId: args.trainContentId } : {}),
|
||||
trainContent: true,
|
||||
},
|
||||
orderBy: {
|
||||
createdAt: 'desc', // 按创建时间倒序排列,最新的记录在前
|
||||
},
|
||||
});
|
||||
return result;
|
||||
|
|
|
@ -1,42 +1,77 @@
|
|||
.ag-theme-alpine {
|
||||
--ag-primary-color: var(--color-primary);
|
||||
--ag-alpine-active-color: var(--color-primary);
|
||||
--ag-background-color: var(--color-bg-container);
|
||||
--ag-foreground-color: var(--colorText);
|
||||
--ag-borders-critical: solid 1px;
|
||||
--ag-critical-border-color: var(--color-border-secondary);
|
||||
--ag-borders: 1px solid;
|
||||
--ag-borders-input: solid 1px;
|
||||
--ag-border-color: var(--color-border-secondary);
|
||||
--ag-secondary-border-color: var(--color-border-secondary);
|
||||
--ag-secondary-foreground-color: var(--color-text-tertiary);
|
||||
/* --ag-border-radius: 2px; */
|
||||
--ag-header-column-separator-display: block;
|
||||
--ag-header-column-separator-height: 30%;
|
||||
--ag-header-column-separator-width: 2px;
|
||||
--ag-header-column-separator-color: var(--color-fill-secondary);
|
||||
--ag-font-size: var(--fontSize);
|
||||
--ag-header-background-color: white;
|
||||
--ag-selected-row-background-color: var(--color-border-primary);
|
||||
--ag-range-selection-border-color: var(--color-border-primary);
|
||||
--ag-header-font-size: var(--fontSize);
|
||||
--ag-header-font-weight: 600;
|
||||
--ag-header-foreground-color: var(--color-primary);
|
||||
--ag-row-border-style: solid;
|
||||
--ag-row-border-width: 1px;
|
||||
--ag-row-border-color: var(--color-border-secondary);
|
||||
--ag-row-hover-color: var(--color-bg-text-hover);
|
||||
--ag-padding-horizontal: 0.7rem;
|
||||
--ag-padding-vertical: 0.9rem;
|
||||
--ag-side-panel-border-width: 1px;
|
||||
--ag-side-panel-border-color: var(--color-border-secondary);
|
||||
--ag-spacing: 6px;
|
||||
--ag-odd-row-background-color: var(--color-fill-quaternary);
|
||||
--ag-wrapper-border-width: 0px;
|
||||
/* --ag-wrapper-border-color: var(--color-border-secondary); */
|
||||
/* --ag-wrapper-border-radius: 10px; */
|
||||
--ag-primary-color: var(--color-primary);
|
||||
--ag-alpine-active-color: var(--color-primary);
|
||||
--ag-background-color: var(--color-bg-container);
|
||||
--ag-foreground-color: var(--colorText);
|
||||
--ag-borders-critical: solid 1px;
|
||||
--ag-critical-border-color: var(--color-border-secondary);
|
||||
--ag-borders: 1px solid;
|
||||
--ag-borders-input: solid 1px;
|
||||
--ag-border-color: var(--color-border-secondary);
|
||||
--ag-secondary-border-color: var(--color-border-secondary);
|
||||
--ag-secondary-foreground-color: var(--color-text-tertiary);
|
||||
/* --ag-border-radius: 2px; */
|
||||
--ag-header-column-separator-display: block;
|
||||
--ag-header-column-separator-height: 30%;
|
||||
--ag-header-column-separator-width: 2px;
|
||||
--ag-header-column-separator-color: var(--color-fill-secondary);
|
||||
--ag-font-size: var(--fontSize);
|
||||
--ag-header-background-color: white;
|
||||
--ag-selected-row-background-color: var(--color-border-primary);
|
||||
--ag-range-selection-border-color: var(--color-border-primary);
|
||||
--ag-header-font-size: var(--fontSize);
|
||||
--ag-header-font-weight: 600;
|
||||
--ag-header-foreground-color: var(--color-primary);
|
||||
--ag-row-border-style: solid;
|
||||
--ag-row-border-width: 1px;
|
||||
--ag-row-border-color: var(--color-border-secondary);
|
||||
--ag-row-hover-color: var(--color-bg-text-hover);
|
||||
--ag-padding-horizontal: 0.7rem;
|
||||
--ag-padding-vertical: 0.9rem;
|
||||
--ag-side-panel-border-width: 1px;
|
||||
--ag-side-panel-border-color: var(--color-border-secondary);
|
||||
--ag-spacing: 6px;
|
||||
--ag-odd-row-background-color: var(--color-fill-quaternary);
|
||||
--ag-wrapper-border-width: 0px;
|
||||
/* --ag-wrapper-border-color: var(--color-border-secondary); */
|
||||
/* --ag-wrapper-border-radius: 10px; */
|
||||
}
|
||||
|
||||
.ag-root-wrapper {
|
||||
border: 0px;
|
||||
}
|
||||
border: 0px;
|
||||
}
|
||||
.assessment-standard-table .ant-table {
|
||||
border: 1px solid #e5e7eb;
|
||||
}
|
||||
|
||||
.assessment-standard-table .ant-table-thead > tr > th {
|
||||
background-color: #f9fafb;
|
||||
font-weight: 500;
|
||||
color: #374151;
|
||||
text-align: center;
|
||||
padding: 12px 8px;
|
||||
border: 1px solid #e5e7eb;
|
||||
}
|
||||
|
||||
.assessment-standard-table .ant-table-tbody > tr > td {
|
||||
text-align: center;
|
||||
padding: 12px 8px;
|
||||
border: 1px solid #e5e7eb;
|
||||
}
|
||||
|
||||
.assessment-standard-table .ant-table-tbody > tr:hover > td {
|
||||
background-color: #f9fafb;
|
||||
}
|
||||
|
||||
.assessment-standard-table .ant-input {
|
||||
text-align: center;
|
||||
border: 1px solid #d1d5db;
|
||||
border-radius: 4px;
|
||||
padding: 4px 8px;
|
||||
width: 80px;
|
||||
}
|
||||
|
||||
.assessment-standard-table .ant-input:focus {
|
||||
border-color: #9ca3af;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@ export default function AssessmentModal() {
|
|||
<div>
|
||||
<Modal
|
||||
title="添加年龄范围"
|
||||
visible={isAgeModalVisible}
|
||||
open={isAgeModalVisible}
|
||||
onOk={ageForm.submit}
|
||||
onCancel={handleAgeCancel}
|
||||
>
|
||||
|
@ -19,10 +19,9 @@ export default function AssessmentModal() {
|
|||
</Form.Item>
|
||||
</Form>
|
||||
</Modal>
|
||||
|
||||
<Modal
|
||||
title="添加分数与对应标准"
|
||||
visible={isScoreModalVisible}
|
||||
open={isScoreModalVisible}
|
||||
onOk={scoreForm.submit}
|
||||
onCancel={handleScoreCancel}
|
||||
>
|
||||
|
|
|
@ -57,7 +57,6 @@ export function AssessmentStandardProvider({ children }: AssessmentStandardProvi
|
|||
const showAgeModal = () => {
|
||||
setIsAgeModalVisible(true);
|
||||
};
|
||||
|
||||
// 处理年龄范围模态框确定
|
||||
const handleAgeOk = (values: any) => {
|
||||
console.log('values',values)
|
||||
|
@ -68,10 +67,8 @@ export function AssessmentStandardProvider({ children }: AssessmentStandardProvi
|
|||
label: end ? `${start}-${end}岁` : `${start}岁以上`,
|
||||
};
|
||||
setAgeRanges([...ageRanges, newRange]);
|
||||
|
||||
setIsAgeModalVisible(false);
|
||||
};
|
||||
|
||||
// 处理年龄范围模态框取消
|
||||
const handleAgeCancel = () => {
|
||||
setIsAgeModalVisible(false);
|
||||
|
@ -81,7 +78,6 @@ export function AssessmentStandardProvider({ children }: AssessmentStandardProvi
|
|||
const showScoreModal = () => {
|
||||
setIsScoreModalVisible(true);
|
||||
};
|
||||
|
||||
// 处理分数标准模态框确定
|
||||
const handleScoreOk = async (values: any) => {
|
||||
const { score, standards } = values;
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import { Button, Form, Input, Select, Skeleton } from "antd";
|
||||
import { Button, Form, Input, Select, Skeleton, Pagination } from "antd";
|
||||
import { useAssessmentStandardContext } from "./assessment-standard-provider";
|
||||
import { api, useSport } from "@nice/client";
|
||||
import toast from "react-hot-toast";
|
||||
import create from "@ant-design/icons/lib/components/IconFont";
|
||||
|
||||
import { useState } from 'react';
|
||||
const { Search } = Input;
|
||||
export default function SportCreateContent() {
|
||||
const { form, sportProjectList, sportProjectLoading } = useAssessmentStandardContext();
|
||||
const { createSportProject, softDeleteByIds } = useSport();
|
||||
|
@ -13,6 +13,9 @@ export default function SportCreateContent() {
|
|||
title: "体能考核"
|
||||
}
|
||||
})
|
||||
const [searchValue, setSearchValue] = useState('');
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const [pageSize, setPageSize] = useState(10);
|
||||
const handleCreateProject = async () => {
|
||||
if (form.getFieldsValue().createProjectName && form.getFieldsValue().unit) {
|
||||
await createSportProject.mutateAsync({
|
||||
|
@ -37,16 +40,20 @@ export default function SportCreateContent() {
|
|||
}
|
||||
}
|
||||
const handleDeleteProject = async (id: string) => {
|
||||
console.log(id)
|
||||
// console.log(id)
|
||||
await softDeleteByIds.mutateAsync({
|
||||
ids: [id]
|
||||
} as any)
|
||||
toast.success("删除项目成功")
|
||||
}
|
||||
const handleSearch = (value) => {
|
||||
setSearchValue(value);
|
||||
setCurrentPage(1);
|
||||
};
|
||||
return (
|
||||
<>
|
||||
<Form form={form} layout="vertical">
|
||||
<div className="flex items-center space-x-4 w-1/2">
|
||||
<div className="flex items-center space-x-4 w-1/3">
|
||||
<Form.Item label="创建项目" name="createProjectName">
|
||||
<Input placeholder="请输入创建的项目名称" className="mr-2" />
|
||||
</Form.Item>
|
||||
|
@ -64,16 +71,48 @@ export default function SportCreateContent() {
|
|||
</div>
|
||||
{sportProjectLoading ?
|
||||
<Skeleton></Skeleton> :
|
||||
<div className='w-1/3 my-3 max-h-48 overflow-y-auto'>
|
||||
<div className='flex my-3 max-h-48 overflow-y-auto'>
|
||||
{sportProjectList?.filter(item=>item.deletedAt === null)?.map((item) => (
|
||||
<div key={item.id} className='w-full flex justify-between p-4 mt-2 bg-white rounded-md'>
|
||||
<div className='font-bold'>{item.name}({item.unit})</div>
|
||||
<span className='text-red-500 cursor-pointer' onClick={() => handleDeleteProject(item.id)}>删除</span>
|
||||
<div
|
||||
key={item.id}
|
||||
className='w-full flex justify-between items-center p-4 my-3 bg-white rounded-lg shadow-sm border border-gray-100 hover:shadow-md transition-shadow duration-200'
|
||||
>
|
||||
<div className='flex items-center'>
|
||||
<span className='h-4 w-1 bg-blue-500 rounded-full mr-3'></span>
|
||||
<div className='font-medium text-gray-800'>
|
||||
{item.name}
|
||||
<span className='text-gray-500 text-sm ml-2'>({item.unit})</span>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
className='text-gray-400 hover:text-red-500 transition-colors duration-200 flex items-center gap-1 px-2 py-1 rounded-md hover:bg-red-50'
|
||||
onClick={() => handleDeleteProject(item.id)}
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" className="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
|
||||
</svg>
|
||||
<span>删除</span>
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
}
|
||||
</Form>
|
||||
{/* <Search
|
||||
placeholder="搜索..."
|
||||
allowClear
|
||||
value={searchValue}
|
||||
onChange={(e) => {
|
||||
if (!e.target.value) {
|
||||
setSearchValue('');
|
||||
setCurrentPage(1);
|
||||
} else {
|
||||
setSearchValue(e.target.value);
|
||||
}
|
||||
}}
|
||||
onSearch={handleSearch}
|
||||
style={{ width: 200 }}
|
||||
/> */}
|
||||
</>
|
||||
)
|
||||
}
|
|
@ -3,6 +3,7 @@ import { useAssessmentStandardContext } from "./assessment-standard-provider";
|
|||
import toast from "react-hot-toast";
|
||||
import { api, useSport } from "@nice/client";
|
||||
import { useEffect, useMemo } from "react";
|
||||
import '../../../App.css'
|
||||
export default function StandardCreateContent() {
|
||||
const {form,sportProjectList,ageRanges,records,showAgeModal,showScoreModal,setRecords,setAgeRanges,isStandardCreate,setIsStandardCreate} = useAssessmentStandardContext();
|
||||
const { createSportStandard,updateSportStandard } = useSport();
|
||||
|
@ -28,6 +29,9 @@ export default function StandardCreateContent() {
|
|||
dataIndex: 'score',
|
||||
key: 'score',
|
||||
width: 100,
|
||||
render: (score: number) => (
|
||||
<span className="font-medium text-gray-700">{score}</span>
|
||||
)
|
||||
},
|
||||
...ageRanges.map((range, index) => ({
|
||||
title: range.label,
|
||||
|
@ -35,7 +39,6 @@ export default function StandardCreateContent() {
|
|||
key: `standard[${index}]`,
|
||||
render: (_: any, record: any) => (
|
||||
<Input
|
||||
style={{ width: '80px' }}
|
||||
value={record.standards[index]}
|
||||
onChange={(e) => {
|
||||
const inputValue = e.target.value;
|
||||
|
@ -113,10 +116,8 @@ export default function StandardCreateContent() {
|
|||
setRecords(records)
|
||||
}
|
||||
}, [data])
|
||||
|
||||
return (
|
||||
<Form form={form} layout="vertical">
|
||||
|
||||
<Space size="large" className="my-6">
|
||||
<Form.Item label="项目" name="projectId">
|
||||
<Select
|
||||
|
@ -125,7 +126,6 @@ export default function StandardCreateContent() {
|
|||
options={sportProjectList?.filter(item=>item.deletedAt === null)?.map((item) => ({ value: item.id, label: `${item.name}(${item.unit})` })) || []}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item label="性别" name="gender">
|
||||
<Select
|
||||
style={{ width: 120 }}
|
||||
|
@ -136,7 +136,6 @@ export default function StandardCreateContent() {
|
|||
]}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item label="人员类型" name="personType">
|
||||
<Select
|
||||
style={{ width: 160 }}
|
||||
|
@ -151,14 +150,19 @@ export default function StandardCreateContent() {
|
|||
<Button onClick={showScoreModal} className="mt-9 ml-2">添加分数与对应标准</Button>
|
||||
<Button type="primary" onClick={handleSave} className='mt-9 ml-2'>保存标准</Button>
|
||||
</Space>
|
||||
|
||||
<Table
|
||||
columns={columns}
|
||||
dataSource={records}
|
||||
bordered
|
||||
pagination={false}
|
||||
scroll={{ x: 'max-content' }}
|
||||
/>
|
||||
<div className="mt-4">
|
||||
<Table
|
||||
columns={columns}
|
||||
dataSource={records}
|
||||
pagination={false}
|
||||
bordered
|
||||
rowKey="score"
|
||||
className="assessment-standard-table"
|
||||
size="middle"
|
||||
scroll={{ x: 'max-content' }}
|
||||
rowClassName={() => "bg-white"}
|
||||
/>
|
||||
</div>
|
||||
</Form>
|
||||
)
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
export default function CommonAssessmentPage() {
|
||||
return (
|
||||
<div>
|
||||
<h1>CommonAssessmentPage</h1>
|
||||
</div>
|
||||
)
|
||||
}
|
|
@ -0,0 +1,141 @@
|
|||
import { api } from "@nice/client";
|
||||
import { useMainContext } from "../layout/MainProvider";
|
||||
import toast from "react-hot-toast";
|
||||
import { Form, Modal, Select, Radio } from "antd";
|
||||
import { useState } from "react";
|
||||
|
||||
export default function DailyModal() {
|
||||
const { form, visible, setVisible, editingRecord, setEditingRecord } = useMainContext();
|
||||
const [submitting, setSubmitting] = useState(false);
|
||||
// 获取更新和创建的mutation
|
||||
const updateMutation = api.trainSituation.update.useMutation();
|
||||
const createMutation = api.trainSituation.create.useMutation();
|
||||
// 获取员工列表
|
||||
const { data: staffs } = api.staff.findMany.useQuery({
|
||||
where: {
|
||||
deletedAt: null
|
||||
},
|
||||
include: {
|
||||
position: true,
|
||||
},
|
||||
});
|
||||
// console.log('wd', staffs);
|
||||
|
||||
|
||||
// 获取训练内容列表
|
||||
const { data: trainContents } = api.trainContent.findMany.useQuery({});
|
||||
|
||||
const handleOk = async () => {
|
||||
try {
|
||||
console.log('handleOk 开始执行');
|
||||
const values = await form.validateFields();
|
||||
console.log('表单验证通过,值:', values);
|
||||
setSubmitting(true);
|
||||
// 根据 TrainSituation 模型构建数据
|
||||
const trainData = {
|
||||
staffId: values.staffId,
|
||||
trainContentId: values.trainContentId,
|
||||
mustTrainTime: 1, // 预期训练时间
|
||||
alreadyTrainTime: values.isPresent ? 1 : 0, // 实际训练时间
|
||||
value: values.content, // 训练内容描述
|
||||
score: values.isPresent ? 1 : 0,
|
||||
};
|
||||
if (editingRecord?.id) {
|
||||
await updateMutation.mutateAsync({
|
||||
where: { id: editingRecord.id },
|
||||
data: trainData
|
||||
});
|
||||
} else {
|
||||
await createMutation.mutateAsync({
|
||||
data: trainData
|
||||
});
|
||||
}
|
||||
console.log('操作成功');
|
||||
toast.success("保存成功");
|
||||
setVisible(false);
|
||||
setEditingRecord(null);
|
||||
form.resetFields();
|
||||
} catch (error) {
|
||||
console.error("保存失败:", error);
|
||||
toast.error("保存失败");
|
||||
} finally {
|
||||
setSubmitting(false);
|
||||
}
|
||||
};
|
||||
const handleCancel = () => {
|
||||
setVisible(false);
|
||||
setEditingRecord(null);
|
||||
form.resetFields();
|
||||
};
|
||||
// useEffect(() => {
|
||||
// if (visible && editingRecord) {
|
||||
// form.setFieldsValue(editingRecord);
|
||||
// }
|
||||
// }, [visible, editingRecord]);
|
||||
return (
|
||||
<Modal
|
||||
title="每日训练填报"
|
||||
open={visible}
|
||||
onOk={handleOk}
|
||||
onCancel={handleCancel}
|
||||
confirmLoading={submitting}
|
||||
>
|
||||
<Form
|
||||
form={form}
|
||||
layout="vertical"
|
||||
>
|
||||
<Form.Item
|
||||
name="staffId"
|
||||
label="人员"
|
||||
rules={[{ required: true, message: '请选择人员' }]}
|
||||
>
|
||||
<Select
|
||||
placeholder="请选择人员"
|
||||
options={staffs?.map(staff => ({
|
||||
label: `${staff.showname || staff.username || '未知'} ${staff.positionId || '无职务'})}`,
|
||||
value: staff.id
|
||||
}))}
|
||||
/>
|
||||
</Form.Item>
|
||||
{/* <Form.Item
|
||||
name="position"
|
||||
label="职务"
|
||||
>
|
||||
<Input disabled />
|
||||
</Form.Item> */}
|
||||
|
||||
<Form.Item
|
||||
name="trainContentId"
|
||||
label="训练科目"
|
||||
rules={[{ required: true, message: '请选择训练科目' }]}
|
||||
>
|
||||
<Select
|
||||
placeholder="请选择训练科目"
|
||||
options={trainContents?.map(item => ({
|
||||
label: item.title,
|
||||
value: item.id
|
||||
}))}
|
||||
/>
|
||||
</Form.Item>
|
||||
{/* <Form.Item
|
||||
name="content"
|
||||
label="训练内容"
|
||||
rules={[{ required: true, message: '请填写训练内容' }]}
|
||||
>
|
||||
<Input.TextArea rows={4} placeholder="请输入今日训练内容" />
|
||||
</Form.Item> */}
|
||||
<Form.Item
|
||||
name="isPresent"
|
||||
label="在位情况"
|
||||
rules={[{ required: true, message: '请选择在位情况' }]}
|
||||
initialValue={true}
|
||||
>
|
||||
<Radio.Group>
|
||||
<Radio value={true}>在位</Radio>
|
||||
<Radio value={false}>不在位</Radio>
|
||||
</Radio.Group>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Modal>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,76 @@
|
|||
import { MagnifyingGlassIcon } from "@heroicons/react/24/outline";
|
||||
import { Button, Form, Input, DatePicker } from "antd";
|
||||
import { useCallback, useEffect } from "react";
|
||||
import _ from "lodash";
|
||||
import { useMainContext } from "../layout/MainProvider";
|
||||
import DailyTable from "./DailyTable";
|
||||
import DailyModal from "./DailyModal";
|
||||
import dayjs from "dayjs";
|
||||
|
||||
export default function DailyReport() {
|
||||
const { form, formValue, setFormValue, setVisible, setSearchValue, editingRecord } = useMainContext();
|
||||
useEffect(() => {
|
||||
setFormValue({
|
||||
staffId: "",
|
||||
trainContentId: "",
|
||||
content: "",
|
||||
isPresent: true,
|
||||
date: dayjs().format('YYYY-MM-DD')
|
||||
});
|
||||
},);
|
||||
const handleNew = () => {
|
||||
form.setFieldsValue(formValue);
|
||||
setVisible(true);
|
||||
};
|
||||
|
||||
const handleSearch = useCallback(
|
||||
_.debounce((value: string) => {
|
||||
setSearchValue(value);
|
||||
}, 500),
|
||||
[]
|
||||
);
|
||||
|
||||
const handleDateChange = (date) => {
|
||||
if (!date) return;
|
||||
// 更新当前选择的日期
|
||||
const newFormValue = { ...formValue, date: date.format('YYYY-MM-DD') };
|
||||
setFormValue(newFormValue);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="p-2 min-h-screen bg-gradient-to-br">
|
||||
<Form>
|
||||
<div className="p-4 h-full flex flex-col">
|
||||
<div className="max-w-full mx-auto flex-1 flex flex-col">
|
||||
<div className="flex justify-between mb-4 space-x-4 items-center">
|
||||
<div className="text-2xl">每日训练填报</div>
|
||||
<DatePicker
|
||||
defaultValue={dayjs()}
|
||||
onChange={handleDateChange}
|
||||
allowClear={false}
|
||||
className="w-40"
|
||||
/>
|
||||
<div className="relative w-1/3">
|
||||
<Input
|
||||
placeholder="输入姓名搜索"
|
||||
onChange={(e) => handleSearch(e.target.value)}
|
||||
className="pl-10 w-full border"
|
||||
/>
|
||||
<MagnifyingGlassIcon className="absolute left-3 top-1/2 transform -translate-y-1/2 h-5 w-5" />
|
||||
</div>
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={handleNew}
|
||||
className="font-bold py-2 px-4 rounded"
|
||||
>
|
||||
新建填报
|
||||
</Button>
|
||||
</div>
|
||||
<DailyTable></DailyTable>
|
||||
<DailyModal></DailyModal>
|
||||
</div>
|
||||
</div>
|
||||
</Form>
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,143 @@
|
|||
import { Button, Table, Modal } from "antd";
|
||||
import { api } from "@nice/client";
|
||||
import { useMainContext } from "../layout/MainProvider";
|
||||
import dayjs from "dayjs";
|
||||
import { useState } from "react";
|
||||
|
||||
|
||||
// 定义 editRecord 的类型接口
|
||||
interface EditRecord {
|
||||
id: string;
|
||||
staffId?: string;
|
||||
staffName?: string;
|
||||
position?: string;
|
||||
trainContentId?: string;
|
||||
content?: string;
|
||||
isPresent: boolean;
|
||||
}
|
||||
|
||||
export default function DailyTable() {
|
||||
const { form, setVisible, searchValue, formValue } = useMainContext();
|
||||
const [editingRecord, setEditingRecord] = useState<EditRecord | null>(null);
|
||||
const { data: trainSituations, isLoading, refetch } =
|
||||
api.trainSituation.findMany.useQuery({
|
||||
where: {
|
||||
...(searchValue ? {
|
||||
staff: {
|
||||
showname: {
|
||||
contains: searchValue
|
||||
}
|
||||
}
|
||||
} : {})
|
||||
},
|
||||
include: {
|
||||
staff: {
|
||||
include: {
|
||||
position: true,
|
||||
department: true
|
||||
}
|
||||
},
|
||||
trainContent: true,
|
||||
|
||||
}
|
||||
});
|
||||
// console.log('data', trainSituations);
|
||||
|
||||
// 基于日期过滤数据(如果后端不支持日期过滤)
|
||||
const filteredTrainSituations = trainSituations?.filter(situation => {
|
||||
// 检查是否有日期条件
|
||||
if (!formValue.date) return true;
|
||||
|
||||
// 将记录日期转换为YYYY-MM-DD格式
|
||||
const situationDate = dayjs(situation.createdAt).format('YYYY-MM-DD');
|
||||
return situationDate === formValue.date;
|
||||
}) || [];
|
||||
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: "人员姓名",
|
||||
dataIndex: ["staff", "showname"],
|
||||
key: "showname",
|
||||
render: (name, record) => name || record.staff?.showname || "未知"
|
||||
},
|
||||
{
|
||||
title: "单位",
|
||||
dataIndex: ["staff", "department", "name"],
|
||||
key: "dept",
|
||||
render: (name) => name || "未知单位"
|
||||
},
|
||||
{
|
||||
title: "职务",
|
||||
dataIndex: ["staff", "position", "type"],
|
||||
key: "position",
|
||||
render: (type, record) => type || "无职务"
|
||||
},
|
||||
{
|
||||
title: "训练内容",
|
||||
dataIndex: ["trainContent", "title"],
|
||||
key: "trainContent",
|
||||
render: (title, record) => title || record.value || "未知内容"
|
||||
},
|
||||
{
|
||||
title: "在位情况",
|
||||
dataIndex: "alreadyTrainTime",
|
||||
key: "isPresent",
|
||||
render: (alreadyTrainTime) => alreadyTrainTime > 0 ? "在位" : "不在位"
|
||||
},
|
||||
{
|
||||
title: "操作",
|
||||
key: "action",
|
||||
render: (_, record) => (
|
||||
<div className="flex space-x-2">
|
||||
<Button
|
||||
type="primary"
|
||||
key={record.id}
|
||||
onClick={() => handleEdit(record)}
|
||||
>
|
||||
编辑
|
||||
</Button>
|
||||
</div>
|
||||
),
|
||||
}
|
||||
];
|
||||
|
||||
const handleEdit = (record: any) => {
|
||||
console.log(record);
|
||||
|
||||
const editRecord: EditRecord = {
|
||||
id: record.id,
|
||||
staffId: record.staff?.id,
|
||||
staffName: record.staff?.showname,
|
||||
position: record.staff?.position?.type,
|
||||
trainContentId: record.trainContent?.id,
|
||||
content: record.value,
|
||||
isPresent: record.alreadyTrainTime > 0,
|
||||
};
|
||||
|
||||
setEditingRecord(editRecord);
|
||||
form.setFieldsValue(editRecord);
|
||||
setVisible(true);
|
||||
refetch();
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{isLoading ? (
|
||||
<div>加载中...</div>
|
||||
) : (
|
||||
<Table
|
||||
columns={columns}
|
||||
dataSource={filteredTrainSituations}
|
||||
rowKey="id"
|
||||
tableLayout="fixed"
|
||||
pagination={{
|
||||
position: ["bottomCenter"],
|
||||
className: "flex justify-center mt-4",
|
||||
pageSize: 10,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
import React from 'react';
|
||||
import { Button, Table, Modal } from "antd";
|
||||
import { api } from "@nice/client";
|
||||
import { useEffect, useState } from "react";
|
||||
import toast from "react-hot-toast";
|
||||
import dayjs from "dayjs";
|
|
@ -1,10 +1,78 @@
|
|||
import DailyContext from "@web/src/components/models/trainPlan/TrainPlanContext";
|
||||
import DailyLayout from "@web/src/components/models/trainPlan/TrainPlanLayout";
|
||||
import { MagnifyingGlassIcon } from "@heroicons/react/24/outline";
|
||||
import { Button, Form, Input, DatePicker } from "antd";
|
||||
import { useCallback, useEffect } from "react";
|
||||
import _ from "lodash";
|
||||
import { useMainContext } from "../layout/MainProvider";
|
||||
import DailyTable from "./DailyTable";
|
||||
import DailyModal from "./DailyModal";
|
||||
import dayjs from "dayjs";
|
||||
|
||||
export default function DailyPage(){
|
||||
return <>
|
||||
<DailyContext>
|
||||
<DailyLayout></DailyLayout>
|
||||
</DailyContext>
|
||||
</>
|
||||
export default function DailyReport() {
|
||||
const { form, formValue, setFormValue, setVisible, setSearchValue, editingRecord } = useMainContext();
|
||||
|
||||
useEffect(() => {
|
||||
setFormValue({
|
||||
staffId: "",
|
||||
trainContentId: "",
|
||||
content: "",
|
||||
isPresent: true,
|
||||
date: dayjs().format('YYYY-MM-DD')
|
||||
});
|
||||
}, []);
|
||||
|
||||
const handleNew = () => {
|
||||
form.setFieldsValue(formValue);
|
||||
setVisible(true);
|
||||
};
|
||||
|
||||
const handleSearch = useCallback(
|
||||
_.debounce((value: string) => {
|
||||
setSearchValue(value);
|
||||
}, 500),
|
||||
[]
|
||||
);
|
||||
|
||||
const handleDateChange = (date) => {
|
||||
if (!date) return;
|
||||
// 更新当前选择的日期
|
||||
const newFormValue = { ...formValue, date: date.format('YYYY-MM-DD') };
|
||||
setFormValue(newFormValue);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="p-2 min-h-screen bg-gradient-to-br">
|
||||
<Form>
|
||||
<div className="p-4 h-full flex flex-col">
|
||||
<div className="max-w-full mx-auto flex-1 flex flex-col">
|
||||
<div className="flex justify-between mb-4 space-x-4 items-center">
|
||||
<div className="text-2xl">每日训练填报</div>
|
||||
<DatePicker
|
||||
defaultValue={dayjs()}
|
||||
onChange={handleDateChange}
|
||||
allowClear={false}
|
||||
className="w-40"
|
||||
/>
|
||||
<div className="relative w-1/3">
|
||||
<Input
|
||||
placeholder="输入姓名搜索"
|
||||
onChange={(e) => handleSearch(e.target.value)}
|
||||
className="pl-10 w-full border"
|
||||
/>
|
||||
<MagnifyingGlassIcon className="absolute left-3 top-1/2 transform -translate-y-1/2 h-5 w-5" />
|
||||
</div>
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={handleNew}
|
||||
className="font-bold py-2 px-4 rounded"
|
||||
>
|
||||
新建填报
|
||||
</Button>
|
||||
</div>
|
||||
<DailyTable></DailyTable>
|
||||
<DailyModal></DailyModal>
|
||||
</div>
|
||||
</div>
|
||||
</Form>
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -1,34 +1,642 @@
|
|||
import { Button } from "antd"
|
||||
import { api } from "@nice/client"
|
||||
import React from "react"
|
||||
import { useSport } from "@nice/client"
|
||||
export default function Dashboard() {
|
||||
const { createSportStandard } = useSport()
|
||||
const handleCreateSportStandard = async () => {
|
||||
const res = await createSportStandard.mutateAsync({
|
||||
data: {
|
||||
projectId: "cm8o6jzp908bp846bv513aqvo",
|
||||
gender: true,
|
||||
personType: "STAFF",
|
||||
ageRanges: [
|
||||
{ start: null, end: 24, label: "24岁以下" },
|
||||
{ start: 24, end: 27, label: "25-27岁" },
|
||||
{ start: 27, end: 30, label: "28-30岁" },
|
||||
{ start: 30, end: null, label: "30岁以上" }
|
||||
],
|
||||
scoreTable: {
|
||||
"100": [85, 81, 79, 77],
|
||||
"95": [74, 70, 68, 66],
|
||||
"90": [65, 61, 59, 57]
|
||||
import React, { useState } from 'react';
|
||||
import { Card, Row, Col, Divider } from 'antd';
|
||||
import ReactEcharts from 'echarts-for-react';
|
||||
|
||||
const Dashboard = () => {
|
||||
// 在这里获取你的项目数据
|
||||
const [data, setData] = useState({
|
||||
projectInfo: {
|
||||
name: '训练管理系统',
|
||||
type: '软件开发',
|
||||
status: '进行中',
|
||||
department: '技术研发中心',
|
||||
client: '软件小组',
|
||||
supervisor: '项目监理公司',
|
||||
contractor: '开发团队',
|
||||
planStartDate: '2023-10-01',
|
||||
planEndDate: '2024-03-31',
|
||||
actualStartDate: '2023-10-05',
|
||||
actualEndDate: '待定'
|
||||
},
|
||||
attendance: {
|
||||
personnel: { present: 15, absent: 5, leave: 2 }
|
||||
},
|
||||
training: {
|
||||
// 参训内容数据
|
||||
contents: [
|
||||
{ value: 8, name: '岗位训练' },
|
||||
{ value: 6, name: '共同科目' },
|
||||
{ value: 4, name: '通用四项' },
|
||||
{ value: 2, name: '其他' }
|
||||
],
|
||||
// 参训时长数据
|
||||
hours: [
|
||||
{ value: 25, name: '已完成' },
|
||||
{ value: 10, name: '进行中' },
|
||||
{ value: 5, name: '未开始' }
|
||||
]
|
||||
},
|
||||
materials: {
|
||||
planned: [10, 15, 20],
|
||||
actual: [5, 8, 12]
|
||||
}
|
||||
});
|
||||
|
||||
// 人员出勤率图表配置 - 优化样式
|
||||
const personnelAttendanceOption = {
|
||||
tooltip: {
|
||||
trigger: 'item',
|
||||
backgroundColor: 'rgba(0,21,41,0.8)',
|
||||
borderColor: 'rgba(32,120,160,0.8)',
|
||||
textStyle: { color: '#fff' },
|
||||
formatter: '{a} <br/>{b}: {c}人 ({d}%)'
|
||||
},
|
||||
legend: {
|
||||
orient: 'vertical',
|
||||
right: 10,
|
||||
top: 'center',
|
||||
icon: 'circle',
|
||||
itemWidth: 8,
|
||||
itemHeight: 8,
|
||||
textStyle: { color: '#fff', fontSize: 14 }
|
||||
},
|
||||
series: [{
|
||||
name: '人员出勤率',
|
||||
type: 'pie',
|
||||
radius: ['50%', '70%'],
|
||||
center: ['35%', '50%'],
|
||||
avoidLabelOverlap: true,
|
||||
itemStyle: {
|
||||
borderRadius: 0,
|
||||
borderWidth: 1,
|
||||
borderColor: 'rgba(32,120,160,0.8)',
|
||||
},
|
||||
label: {
|
||||
show: true,
|
||||
position: 'inside',
|
||||
formatter: '{d}%',
|
||||
fontSize: 10,
|
||||
color: '#fff',
|
||||
},
|
||||
labelLine: {
|
||||
show: false
|
||||
},
|
||||
emphasis: {
|
||||
label: {
|
||||
show: true,
|
||||
fontSize: 12,
|
||||
fontWeight: 'bold'
|
||||
}
|
||||
},
|
||||
data: [
|
||||
{
|
||||
value: data.attendance.personnel.present,
|
||||
name: '在位',
|
||||
itemStyle: { color: '#3DD598' }
|
||||
},
|
||||
{
|
||||
value: data.attendance.personnel.absent,
|
||||
name: '不在位',
|
||||
itemStyle: { color: '#FFB076' }
|
||||
},
|
||||
{
|
||||
value: data.attendance.personnel.leave,
|
||||
name: '请假',
|
||||
itemStyle: { color: '#FC5A5A' }
|
||||
}
|
||||
]
|
||||
}]
|
||||
};
|
||||
// 参训内容饼图配置
|
||||
const trainingContentsOption = {
|
||||
tooltip: {
|
||||
trigger: 'item',
|
||||
backgroundColor: 'rgba(0,21,41,0.8)',
|
||||
borderColor: 'rgba(32,120,160,0.8)',
|
||||
textStyle: { color: '#fff' },
|
||||
formatter: '{a} <br/>{b}: {c}项 ({d}%)'
|
||||
},
|
||||
legend: {
|
||||
orient: 'vertical',
|
||||
right: 10,
|
||||
top: 'center',
|
||||
itemWidth: 8,
|
||||
itemHeight: 8,
|
||||
icon: 'circle',
|
||||
textStyle: { color: '#fff', fontSize: 14 }
|
||||
},
|
||||
series: [{
|
||||
name: '参训内容',
|
||||
type: 'pie',
|
||||
radius: ['50%', '70%'],
|
||||
center: ['35%', '50%'],
|
||||
avoidLabelOverlap: true,
|
||||
itemStyle: {
|
||||
borderRadius: 0,
|
||||
borderWidth: 1,
|
||||
borderColor: 'rgba(32,120,160,0.8)',
|
||||
},
|
||||
label: {
|
||||
show: true,
|
||||
position: 'inside',
|
||||
formatter: '{d}%',
|
||||
fontSize: 10,
|
||||
color: '#fff'
|
||||
},
|
||||
labelLine: {
|
||||
show: false
|
||||
},
|
||||
emphasis: {
|
||||
label: {
|
||||
show: true,
|
||||
fontSize: 12,
|
||||
fontWeight: 'bold'
|
||||
}
|
||||
},
|
||||
data: [
|
||||
{
|
||||
value: data.training.contents[0].value,
|
||||
name: data.training.contents[0].name,
|
||||
itemStyle: { color: '#4FDFFF' }
|
||||
},
|
||||
{
|
||||
value: data.training.contents[1].value,
|
||||
name: data.training.contents[1].name,
|
||||
itemStyle: { color: '#FFCA28' }
|
||||
},
|
||||
{
|
||||
value: data.training.contents[2].value,
|
||||
name: data.training.contents[2].name,
|
||||
itemStyle: { color: '#3DD598' }
|
||||
},
|
||||
{
|
||||
value: data.training.contents[3].value,
|
||||
name: data.training.contents[3].name,
|
||||
itemStyle: { color: '#FC5A5A' }
|
||||
}
|
||||
]
|
||||
}]
|
||||
};
|
||||
// 参训时长饼图配置
|
||||
const trainingHoursOption = {
|
||||
tooltip: {
|
||||
trigger: 'item',
|
||||
backgroundColor: 'rgba(0,21,41,0.8)',
|
||||
borderColor: 'rgba(32,120,160,0.8)',
|
||||
textStyle: { color: '#fff' },
|
||||
formatter: '{a} <br/>{b}: {c}小时 ({d}%)'
|
||||
},
|
||||
legend: {
|
||||
orient: 'vertical',
|
||||
right: 10,
|
||||
top: 'center',
|
||||
itemWidth: 8,
|
||||
itemHeight: 8,
|
||||
icon: 'circle',
|
||||
textStyle: { color: '#fff', fontSize: 14 }
|
||||
},
|
||||
series: [{
|
||||
name: '参训时长',
|
||||
type: 'pie',
|
||||
radius: ['50%', '70%'],
|
||||
center: ['35%', '50%'],
|
||||
avoidLabelOverlap: true,
|
||||
itemStyle: {
|
||||
borderRadius: 0,
|
||||
borderWidth: 1,
|
||||
borderColor: 'rgba(32,120,160,0.8)',
|
||||
},
|
||||
label: {
|
||||
show: true,
|
||||
position: 'inside',
|
||||
formatter: '{d}%',
|
||||
fontSize: 10,
|
||||
color: '#fff'
|
||||
},
|
||||
labelLine: {
|
||||
show: false
|
||||
},
|
||||
emphasis: {
|
||||
label: {
|
||||
show: true,
|
||||
fontSize: 12,
|
||||
fontWeight: 'bold'
|
||||
}
|
||||
},
|
||||
data: [
|
||||
{
|
||||
value: data.training.hours[0].value,
|
||||
name: data.training.hours[0].name,
|
||||
itemStyle: { color: '#4FDFFF' }
|
||||
},
|
||||
{
|
||||
value: data.training.hours[1].value,
|
||||
name: data.training.hours[1].name,
|
||||
itemStyle: { color: '#FFB076' }
|
||||
},
|
||||
{
|
||||
value: data.training.hours[2].value,
|
||||
name: data.training.hours[2].name,
|
||||
itemStyle: { color: '#FC5A5A' }
|
||||
}
|
||||
]
|
||||
}]
|
||||
};
|
||||
// 任务完成情况柱状图配置
|
||||
const materialsOption = {
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
backgroundColor: 'rgba(0,21,41,0.8)',
|
||||
borderColor: 'rgba(32,120,160,0.8)',
|
||||
textStyle: { color: '#fff' },
|
||||
axisPointer: {
|
||||
type: 'shadow'
|
||||
}
|
||||
},
|
||||
legend: {
|
||||
data: ['上半年', '下半年'],
|
||||
right: 10,
|
||||
top: 10,
|
||||
textStyle: {
|
||||
color: '#fff',
|
||||
fontSize: 10
|
||||
}
|
||||
},
|
||||
grid: {
|
||||
left: '3%',
|
||||
right: '4%',
|
||||
bottom: '3%',
|
||||
top: '22%',
|
||||
containLabel: true
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: ['任务A', '任务B', '任务C'],
|
||||
axisLine: {
|
||||
lineStyle: {
|
||||
color: 'rgba(255,255,255,0.3)'
|
||||
}
|
||||
},
|
||||
axisLabel: {
|
||||
color: '#fff',
|
||||
fontSize: 10
|
||||
}
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
axisLine: {
|
||||
show: true,
|
||||
lineStyle: {
|
||||
color: 'rgba(255,255,255,0.3)'
|
||||
}
|
||||
},
|
||||
splitLine: {
|
||||
lineStyle: {
|
||||
color: 'rgba(255,255,255,0.1)',
|
||||
type: 'dashed'
|
||||
}
|
||||
},
|
||||
axisLabel: {
|
||||
color: '#fff',
|
||||
fontSize: 10
|
||||
}
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '上半年',
|
||||
type: 'bar',
|
||||
barWidth: '25%',
|
||||
itemStyle: {
|
||||
color: '#4FDFFF'
|
||||
},
|
||||
emphasis: {
|
||||
itemStyle: {
|
||||
color: '#6ab0f0'
|
||||
}
|
||||
},
|
||||
data: data.materials.planned
|
||||
},
|
||||
{
|
||||
name: '下半年',
|
||||
type: 'bar',
|
||||
barWidth: '25%',
|
||||
itemStyle: {
|
||||
color: '#FFCA28'
|
||||
},
|
||||
emphasis: {
|
||||
itemStyle: {
|
||||
color: '#FFB75C'
|
||||
}
|
||||
},
|
||||
data: data.materials.actual
|
||||
}
|
||||
]
|
||||
};
|
||||
// 项目进度甘特图配置
|
||||
const ganttChartOption = {
|
||||
backgroundColor: 'transparent',
|
||||
tooltip: {
|
||||
trigger: 'item',
|
||||
formatter: function (params) {
|
||||
if (params.seriesName === '实际完成' || params.seriesName === '计划完成') {
|
||||
return `${params.name}<br/>${params.seriesName}: ${params.value[0]} ~ ${params.value[1]}`;
|
||||
}
|
||||
return params.name;
|
||||
},
|
||||
backgroundColor: 'rgba(0,21,41,0.8)',
|
||||
borderColor: 'rgba(32,120,160,0.8)',
|
||||
textStyle: { color: '#fff'}
|
||||
},
|
||||
legend: {
|
||||
data: ['计划完成', '实际完成', '超时完成'],
|
||||
top: 0,
|
||||
right: 10,
|
||||
textStyle: { color: '#fff', fontSize: 12 },
|
||||
itemWidth: 15,
|
||||
itemHeight: 10
|
||||
},
|
||||
grid: {
|
||||
top: 30,
|
||||
bottom: 30,
|
||||
left: 100,
|
||||
right: 20
|
||||
},
|
||||
xAxis: {
|
||||
type: 'time',
|
||||
axisLine: { lineStyle: { color: 'rgba(255,255,255,0.3)' } },
|
||||
axisLabel: {
|
||||
color: '#fff',
|
||||
fontSize: 10,
|
||||
formatter: '{yyyy}.{MM}.{dd}',
|
||||
rotate: 45,
|
||||
margin: 8,
|
||||
align: 'center',
|
||||
interval: 'auto',
|
||||
hideOverlap: true
|
||||
},
|
||||
splitLine: {
|
||||
show: true,
|
||||
lineStyle: { color: 'rgba(255,255,255,0.1)', type: 'dashed' }
|
||||
},
|
||||
min: '2019-02-01',
|
||||
max: '2019-04-30'
|
||||
},
|
||||
yAxis: {
|
||||
type: 'category',
|
||||
data: ['岗位训练', '共同科目', '通用四项'],
|
||||
axisLine: { lineStyle: { color: 'rgba(255,255,255,0.3)' } },
|
||||
axisLabel: {
|
||||
color: '#fff',
|
||||
fontSize: 12,
|
||||
margin: 20
|
||||
},
|
||||
inverse: true
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '计划完成',
|
||||
type: 'bar',
|
||||
stack: 'same',
|
||||
itemStyle: { color: '#FFCA28' },
|
||||
barWidth: 20,
|
||||
data: [
|
||||
{
|
||||
name: '岗位训练',
|
||||
value: ['2019-03-08', '2019-04-17']
|
||||
},
|
||||
{
|
||||
name: '共同科目',
|
||||
value: ['2019-02-20', '2019-03-07']
|
||||
},
|
||||
{
|
||||
name: '通用四项',
|
||||
value: ['2019-02-05', '2019-02-26']
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: '实际完成',
|
||||
type: 'bar',
|
||||
stack: 'same',
|
||||
itemStyle: { color: '#4FDFFF' },
|
||||
barWidth: 20,
|
||||
data: [
|
||||
{
|
||||
},
|
||||
{
|
||||
name: '共同科目',
|
||||
value: ['2019-02-20', '2019-03-05']
|
||||
},
|
||||
{}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: '超时完成',
|
||||
type: 'bar',
|
||||
stack: 'same',
|
||||
itemStyle: { color: '#FC5A5A' },
|
||||
barWidth: 20,
|
||||
data: [
|
||||
// 空数据
|
||||
{},
|
||||
{},
|
||||
{
|
||||
name: '通用四项',
|
||||
value: ['2019-02-26', '2019-02-27']
|
||||
}
|
||||
]
|
||||
},
|
||||
// 添加当前日期标记
|
||||
{
|
||||
name: '当前日期',
|
||||
type: 'line',
|
||||
markLine: {
|
||||
symbol: ['none', 'none'],
|
||||
label: {
|
||||
show: false
|
||||
},
|
||||
lineStyle: {
|
||||
color: '#fff',
|
||||
type: 'dashed',
|
||||
width: 1
|
||||
},
|
||||
data: [
|
||||
{
|
||||
xAxis: '2019-02-27'
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
} as any)
|
||||
console.log(res)
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
// 六边形背景SVG
|
||||
const hexagonBg = `
|
||||
<svg width="100%" height="100%" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<pattern id="hexagons" width="50" height="43.4" patternUnits="userSpaceOnUse" patternTransform="scale(5)">
|
||||
<path d="M 0,25 12.5,0 37.5,0 50,25 37.5,50 12.5,50 Z"
|
||||
stroke="rgba(32,120,160,0.2)" stroke-width="0.5" fill="none"/>
|
||||
</pattern>
|
||||
</defs>
|
||||
<rect width="100%" height="100%" fill="url(#hexagons)" />
|
||||
</svg>
|
||||
`;
|
||||
|
||||
// 卡片通用样式
|
||||
const cardStyle = {
|
||||
backgroundColor: 'rgba(0,21,41,0.7)',
|
||||
color: 'white',
|
||||
border: '1px solid rgba(32,120,160,0.8)',
|
||||
boxShadow: '0 0 15px rgba(0,100,200,0.3)'
|
||||
};
|
||||
|
||||
const headerStyle = {
|
||||
color: '#4FDFFF',
|
||||
borderBottom: '1px solid rgba(32,120,160,0.8)',
|
||||
fontSize: '16px',
|
||||
fontWeight: 'bold'
|
||||
};
|
||||
|
||||
return (
|
||||
<div >
|
||||
数据看板(待开发)
|
||||
<Button type="primary" onClick={() => handleCreateSportStandard()}>创建体育项目</Button>
|
||||
<div style={{
|
||||
padding: '20px',
|
||||
minHeight: '100vh',
|
||||
background: 'linear-gradient(135deg, #001529 0%, #003366 100%)',
|
||||
position: 'relative',
|
||||
overflow: 'hidden'
|
||||
}}>
|
||||
{/* 六边形背景 */}
|
||||
<div dangerouslySetInnerHTML={{ __html: hexagonBg }} style={{
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
zIndex: 0
|
||||
}} />
|
||||
|
||||
<div style={{ position: 'relative', zIndex: 1 }}>
|
||||
<h1 style={{
|
||||
textAlign: 'center',
|
||||
color: '#4FDFFF',
|
||||
marginBottom: '20px',
|
||||
textShadow: '0 0 10px rgba(79,223,255,0.5)'
|
||||
}}>训练管理系统数据看板</h1>
|
||||
|
||||
<div style={{
|
||||
position: 'absolute',
|
||||
top: '20px',
|
||||
left: '20px',
|
||||
fontSize: '14px',
|
||||
color: '#fff'
|
||||
}}>
|
||||
{new Date().toLocaleString()} 星期{['日', '一', '二', '三', '四', '五', '六'][new Date().getDay()]}
|
||||
</div>
|
||||
|
||||
<Row gutter={[16, 16]}>
|
||||
<Col span={8}>
|
||||
<Card
|
||||
title="项目概况"
|
||||
bordered={false}
|
||||
style={{...cardStyle,height:'100%'}}
|
||||
headStyle={headerStyle}
|
||||
>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
|
||||
{/* <div style={{ display: 'flex' }}>
|
||||
<span style={{ width: '100px' }}></span>
|
||||
</div>
|
||||
<div style={{ display: 'flex' }}>
|
||||
<span style={{ width: '100px' }}></span>
|
||||
</div>
|
||||
<div style={{ display: 'flex' }}>
|
||||
<span style={{ width: '100px' }}></span>
|
||||
</div> */}
|
||||
<div style={{ display: 'flex' }}>
|
||||
<span style={{ width: '100px' }}>工程名称: </span>
|
||||
<span>{data.projectInfo.name}</span>
|
||||
</div>
|
||||
<div style={{ display: 'flex' }}>
|
||||
<span style={{ width: '100px' }}>开发单位: </span>
|
||||
<span>{data.projectInfo.client}</span>
|
||||
</div>
|
||||
<div style={{ display: 'flex' }}>
|
||||
<span style={{ width: '100px' }}>计划开始:</span>
|
||||
<span>{data.projectInfo.planStartDate}</span>
|
||||
</div>
|
||||
<div style={{ display: 'flex' }}>
|
||||
<span style={{ width: '100px' }}>计划结束:</span>
|
||||
<span>{data.projectInfo.planEndDate}</span>
|
||||
</div>
|
||||
<div style={{ display: 'flex' }}>
|
||||
<span style={{ width: '100px' }}></span>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</Col>
|
||||
|
||||
<Col span={16}>
|
||||
<Card
|
||||
title="项目进度甘特图"
|
||||
bordered={false}
|
||||
style={{...cardStyle,height:'100%'}}
|
||||
headStyle={headerStyle}
|
||||
>
|
||||
<ReactEcharts option={ganttChartOption} style={{ height: '220px' }} />
|
||||
</Card>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
<Row gutter={[16, 16]} style={{ marginTop: '16px' }}>
|
||||
<Col span={8}>
|
||||
<Card
|
||||
title="人员出勤率"
|
||||
bordered={false}
|
||||
style={cardStyle}
|
||||
headStyle={headerStyle}
|
||||
>
|
||||
<ReactEcharts option={personnelAttendanceOption} style={{ height: '220px' }} />
|
||||
</Card>
|
||||
</Col>
|
||||
|
||||
<Col span={8}>
|
||||
<Card
|
||||
title="参训内容"
|
||||
bordered={false}
|
||||
style={cardStyle}
|
||||
headStyle={headerStyle}
|
||||
>
|
||||
<ReactEcharts option={trainingContentsOption} style={{ height: '220px' }} />
|
||||
</Card>
|
||||
</Col>
|
||||
|
||||
<Col span={8}>
|
||||
<Card
|
||||
title="参训时长"
|
||||
bordered={false}
|
||||
style={cardStyle}
|
||||
headStyle={headerStyle}
|
||||
>
|
||||
<ReactEcharts option={trainingHoursOption} style={{ height: '220px' }} />
|
||||
</Card>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
<Row gutter={[16, 16]} style={{ marginTop: '16px' }}>
|
||||
<Col span={24}>
|
||||
<Card
|
||||
title="任务完成情况"
|
||||
bordered={false}
|
||||
style={{ height: '300px', backgroundColor: '#0c2135', color: 'white' }}
|
||||
headStyle={{ color: 'white', borderBottom: '1px solid #1890ff' }}
|
||||
>
|
||||
<ReactEcharts option={materialsOption} style={{ height: '220px' }} />
|
||||
</Card>
|
||||
</Col>
|
||||
</Row>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
export default Dashboard;
|
|
@ -39,7 +39,7 @@ const items = [
|
|||
),
|
||||
getItem(
|
||||
"训练计划",
|
||||
"/plan",
|
||||
"/plan",
|
||||
<i className="iconfont icon-icon-user" />,
|
||||
[
|
||||
getItem("周训练计划", "/plan/weekplan", null, null, null),
|
||||
|
@ -88,7 +88,7 @@ const NavigationMenu: React.FC = () => {
|
|||
"^/plan/monthplan": ["/plan"],
|
||||
// 添加考核成绩子路径的匹配规则
|
||||
"^/assessment/positionassessment": ["/assessment"],
|
||||
"^/assessment/commonassessment": ["/assessment"],
|
||||
"^/assessment/commonassessment": ["/assessment"],
|
||||
"^/assessment/sportsassessment": ["/assessment"],
|
||||
"^/admin/base-setting": ["/admin"],
|
||||
"^/admin/department": ["/admin"],
|
||||
|
@ -107,8 +107,7 @@ const NavigationMenu: React.FC = () => {
|
|||
location.pathname.startsWith("/assessment/") ||
|
||||
location.pathname === "/plan/weekplan" ||
|
||||
location.pathname === "/plan/monthplan"
|
||||
)
|
||||
{
|
||||
) {
|
||||
setSelectedKeys([location.pathname]);
|
||||
setOpenKeys([location.pathname.split('/').slice(0, 2).join('/')]);
|
||||
} else if (
|
||||
|
@ -121,9 +120,9 @@ const NavigationMenu: React.FC = () => {
|
|||
setOpenKeys(openKeyMerge(location.pathname));
|
||||
}
|
||||
}, [location.pathname]);
|
||||
|
||||
|
||||
const hit = (pathname: string): string[] => {
|
||||
for (let p in children2Parent) {
|
||||
for (const p in children2Parent) {
|
||||
if (pathname.search(p) >= 0) {
|
||||
return children2Parent[p];
|
||||
}
|
||||
|
@ -132,7 +131,7 @@ const NavigationMenu: React.FC = () => {
|
|||
};
|
||||
|
||||
const openKeyMerge = (pathname: string): string[] => {
|
||||
let newOpenKeys = hit(pathname);
|
||||
const newOpenKeys = hit(pathname);
|
||||
for (let i = 0; i < openKeys.length; i++) {
|
||||
let isIn = false;
|
||||
for (let j = 0; j < newOpenKeys.length; j++) {
|
||||
|
@ -167,20 +166,20 @@ const NavigationMenu: React.FC = () => {
|
|||
useEffect(() => {
|
||||
setActiveMenus(items);
|
||||
// console.log(activeMenus)
|
||||
},[items])
|
||||
}, [items])
|
||||
// useEffect(() => {
|
||||
// checkMenuPermissions(items, permissions);
|
||||
// }, [items, permissions]);
|
||||
|
||||
// const checkMenuPermissions = (items: any, permissions: any) => {
|
||||
// let menus: any = [];
|
||||
// const menus: any = [];
|
||||
// if (permissions.length === 0) {
|
||||
// setActiveMenus(menus);
|
||||
// return;
|
||||
// }
|
||||
|
||||
// for (let i in items) {
|
||||
// let menuItem = items[i];
|
||||
// for (const i in items) {
|
||||
// const menuItem = items[i];
|
||||
// // 一级菜单=>没有子菜单&配置了权限
|
||||
// if (menuItem.children === null) {
|
||||
// if (
|
||||
|
@ -192,10 +191,10 @@ const NavigationMenu: React.FC = () => {
|
|||
// menus.push(menuItem);
|
||||
// continue;
|
||||
// }
|
||||
// let children = [];
|
||||
// const children = [];
|
||||
|
||||
// for (let j in menuItem.children) {
|
||||
// let childrenItem = menuItem.children[j];
|
||||
// for (const j in menuItem.children) {
|
||||
// const childrenItem = menuItem.children[j];
|
||||
|
||||
// if (
|
||||
// typeof permissions[childrenItem.permission] !== "undefined" ||
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
export default function MonthPlanPage() {
|
||||
return (
|
||||
<div>
|
||||
<h1>MonthPlanPage</h1>
|
||||
</div>
|
||||
)
|
||||
}
|
|
@ -3,7 +3,7 @@ import * as XLSX from 'xlsx'
|
|||
import { Table, Select, Pagination } from 'antd'
|
||||
import type { ColumnsType, ColumnType } from 'antd/es/table'
|
||||
import { UploadOutlined } from '@ant-design/icons'
|
||||
|
||||
import React from 'react';
|
||||
interface TableData {
|
||||
key: string
|
||||
[key: string]: any
|
||||
|
@ -25,7 +25,7 @@ export default function WeekPlanPage() {
|
|||
const data = new Uint8Array(e.target?.result as ArrayBuffer)
|
||||
const workbook = XLSX.read(data, { type: 'array' })
|
||||
const firstSheet = workbook.Sheets[workbook.SheetNames[0]]
|
||||
|
||||
|
||||
// 处理合并单元格
|
||||
if (firstSheet['!merges']) {
|
||||
firstSheet['!merges'].forEach(merge => {
|
||||
|
@ -45,7 +45,7 @@ export default function WeekPlanPage() {
|
|||
})
|
||||
}
|
||||
|
||||
const jsonData: any[] = XLSX.utils.sheet_to_json(firstSheet, {
|
||||
const jsonData: any[] = XLSX.utils.sheet_to_json(firstSheet, {
|
||||
header: 1,
|
||||
defval: '',
|
||||
blankrows: false,
|
||||
|
@ -79,7 +79,7 @@ export default function WeekPlanPage() {
|
|||
children: text,
|
||||
props: {
|
||||
rowSpan: calculateRowSpan(tableData, index, header),
|
||||
style: {
|
||||
style: {
|
||||
border: '1px solid #f0f0f0',
|
||||
borderBottom: '1px solid #f0f0f0'
|
||||
}
|
||||
|
@ -158,7 +158,7 @@ export default function WeekPlanPage() {
|
|||
|
||||
// 获取当前页应该显示的值
|
||||
const currentValue = uniqueValues[(currentPage - 1)]
|
||||
|
||||
|
||||
// 返回第一列等于当前值的所有行
|
||||
return data.filter(item => item[firstColumnName] === currentValue)
|
||||
}
|
||||
|
@ -188,7 +188,7 @@ export default function WeekPlanPage() {
|
|||
<p className="text-sm text-gray-500">支持 .xlsx, .xls 格式的Excel文件</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
{data.length > 0 && (
|
||||
<>
|
||||
<Table
|
||||
|
@ -212,7 +212,7 @@ export default function WeekPlanPage() {
|
|||
<Pagination
|
||||
className="mt-4"
|
||||
current={currentPage}
|
||||
total={Array.from(new Set(data.map(item =>
|
||||
total={Array.from(new Set(data.map(item =>
|
||||
item[(columns[0] as ColumnType<TableData>)?.dataIndex as string]
|
||||
))).filter(Boolean).length}
|
||||
pageSize={1}
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
export default function PositionAssessmentPage() {
|
||||
return (
|
||||
<div>
|
||||
<h1>PositionAssessmentPage</h1>
|
||||
</div>
|
||||
)
|
||||
}
|
|
@ -1,7 +1,6 @@
|
|||
import { Button, Table, Modal, Form, Upload, Skeleton } from "antd";
|
||||
import { useEffect, useState,} from "react";
|
||||
import { useEffect, useState, } from "react";
|
||||
import toast from "react-hot-toast";
|
||||
import _ from "lodash";
|
||||
import * as XLSX from 'xlsx';
|
||||
import { UploadOutlined, DownloadOutlined } from '@ant-design/icons';
|
||||
import { useSportPageContext } from "./sportPageProvider";
|
||||
|
@ -11,14 +10,14 @@ import { api, useTrainSituation } from "@nice/client";
|
|||
export default function SportPage() {
|
||||
const [sportsData, setSportsData] = useState([]);
|
||||
const { sportProjectList, handlNewScoreOpen, staffsWithScore, staffsWithScoreLoading } = useSportPageContext()
|
||||
const {deleteSameGroupTrainSituation} = useTrainSituation()
|
||||
const { deleteSameGroupTrainSituation } = useTrainSituation()
|
||||
useEffect(() => {
|
||||
if (!staffsWithScore) return;
|
||||
const newSportsData = []
|
||||
staffsWithScore.forEach(item => {
|
||||
const groupedTrainSituations = {};
|
||||
item.trainSituations.forEach(train => {
|
||||
if(train.groupId === null) return
|
||||
if (train.groupId === null) return
|
||||
const groupId = train.groupId;
|
||||
if (!groupedTrainSituations[groupId]) {
|
||||
groupedTrainSituations[groupId] = {
|
||||
|
@ -39,12 +38,12 @@ export default function SportPage() {
|
|||
age: item.age,
|
||||
unit: item.department?.name,
|
||||
situationIds: groupedTrainSituations[groupId].situations.map(train => train.id),
|
||||
totalScore:groupedTrainSituations[groupId].totalScore,
|
||||
totalScore: groupedTrainSituations[groupId].totalScore,
|
||||
...groupedTrainSituations[groupId].trainScore,
|
||||
groupId
|
||||
})
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
console.log(newSportsData)
|
||||
setSportsData(newSportsData);
|
||||
|
@ -70,12 +69,6 @@ export default function SportPage() {
|
|||
dataIndex: "unit",
|
||||
key: "unit",
|
||||
},
|
||||
...sportProjectList?.filter(item=>item.deletedAt === null).map((item) => ({
|
||||
title: item.name,
|
||||
dataIndex: item.name,
|
||||
key: item.name,
|
||||
editable: true,
|
||||
})),
|
||||
{
|
||||
title: "BMI",
|
||||
dataIndex: "bodyType",
|
||||
|
@ -115,7 +108,7 @@ export default function SportPage() {
|
|||
console.log(record)
|
||||
await deleteSameGroupTrainSituation.mutateAsync(
|
||||
{
|
||||
groupId:record.groupId
|
||||
groupId: record.groupId
|
||||
}
|
||||
)
|
||||
toast.success("删除成功");
|
||||
|
@ -148,37 +141,35 @@ export default function SportPage() {
|
|||
|
||||
// 导入成绩
|
||||
const handleImport = (file) => {
|
||||
const reader = new FileReader();
|
||||
// const reader = new FileReader();
|
||||
// reader.onload = (e) => {
|
||||
// try {
|
||||
// const data = new Uint8Array(e.target.result);
|
||||
// const workbook = XLSX.read(data, { type: 'array' });
|
||||
// // 获取第一个工作表
|
||||
// const worksheet = workbook.Sheets[workbook.SheetNames[0]];
|
||||
// // 转换为JSON
|
||||
// const jsonData = XLSX.utils.sheet_to_json(worksheet);
|
||||
// // 添加id字段
|
||||
// const importedData = jsonData.map((item, index) => ({
|
||||
// id: sportsData.length + index + 1,
|
||||
// ...item,
|
||||
// // 确保totalScore是计算所有项目成绩的和
|
||||
// totalScore: calculateTotalScore(item)
|
||||
// }));
|
||||
// // 更新数据
|
||||
// setSportsData([...sportsData, ...importedData]);
|
||||
// toast.success(`成功导入${importedData.length}条记录`);
|
||||
// } catch (error) {
|
||||
// console.error('导入失败:', error);
|
||||
// toast.error("导入失败,请检查文件格式");
|
||||
// }
|
||||
// };
|
||||
|
||||
reader.onload = (e) => {
|
||||
try {
|
||||
const data = new Uint8Array(e.target.result);
|
||||
const workbook = XLSX.read(data, { type: 'array' });
|
||||
// 获取第一个工作表
|
||||
const worksheet = workbook.Sheets[workbook.SheetNames[0]];
|
||||
// 转换为JSON
|
||||
const jsonData = XLSX.utils.sheet_to_json(worksheet);
|
||||
// 添加id字段
|
||||
const importedData = jsonData.map((item, index) => ({
|
||||
id: sportsData.length + index + 1,
|
||||
...item,
|
||||
// 确保totalScore是计算所有项目成绩的和
|
||||
totalScore: calculateTotalScore(item)
|
||||
}));
|
||||
// reader.readAsArrayBuffer(file);
|
||||
|
||||
// 更新数据
|
||||
setSportsData([...sportsData, ...importedData]);
|
||||
toast.success(`成功导入${importedData.length}条记录`);
|
||||
} catch (error) {
|
||||
console.error('导入失败:', error);
|
||||
toast.error("导入失败,请检查文件格式");
|
||||
}
|
||||
};
|
||||
|
||||
reader.readAsArrayBuffer(file);
|
||||
|
||||
// 防止自动上传
|
||||
return false;
|
||||
// // 防止自动上传
|
||||
// return false;
|
||||
};
|
||||
|
||||
return (
|
||||
|
|
|
@ -51,7 +51,7 @@ export default function SportPageModal() {
|
|||
<Modal
|
||||
title="添加考核成绩"
|
||||
onOk={newScoreForm.submit}
|
||||
visible={isNewScoreModalVisible}
|
||||
open={isNewScoreModalVisible}
|
||||
onCancel={handleNewScoreCancel}
|
||||
>
|
||||
<Form
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
import { MagnifyingGlassIcon } from "@heroicons/react/24/outline";
|
||||
import { Button, Form, Input } from "antd";
|
||||
import { useCallback, useEffect} from "react";
|
||||
import { useCallback, useEffect } from "react";
|
||||
import _ from "lodash";
|
||||
import { useMainContext } from "../layout/MainProvider";
|
||||
import StaffTable from "./stafftable/page";
|
||||
import StaffModal from "./staffmodal/page";
|
||||
export default function StaffMessage() {
|
||||
const {form, formValue, setFormValue, setVisible, setSearchValue, editingRecord} = useMainContext();
|
||||
useEffect(()=>{
|
||||
const { form, formValue, setFormValue, setVisible, setSearchValue, editingRecord } = useMainContext();
|
||||
useEffect(() => {
|
||||
setFormValue(
|
||||
{
|
||||
username: "",
|
||||
|
@ -15,9 +15,9 @@ export default function StaffMessage() {
|
|||
absent: false,
|
||||
positionId: "",
|
||||
trainSituations: "",
|
||||
}
|
||||
}
|
||||
)
|
||||
},[])
|
||||
}, [])
|
||||
const handleNew = () => {
|
||||
form.setFieldsValue(formValue);
|
||||
console.log(editingRecord);
|
||||
|
@ -54,7 +54,7 @@ export default function StaffMessage() {
|
|||
</Button>
|
||||
</div>
|
||||
<StaffTable></StaffTable>
|
||||
<StaffModal></StaffModal>
|
||||
<StaffModal></StaffModal>
|
||||
</div>
|
||||
</div>
|
||||
</Form>
|
||||
|
|
|
@ -7,18 +7,17 @@ import TrainContentTreeSelect from "@web/src/components/models/trainContent/trai
|
|||
import DepartmentChildrenSelect from "@web/src/components/models/department/department-children-select";
|
||||
|
||||
export default function StaffModal() {
|
||||
const { data: traincontents} = api.trainSituation.findMany.useQuery({
|
||||
select:{
|
||||
const { data: traincontents } = api.trainSituation.findMany.useQuery({
|
||||
select: {
|
||||
id: true,
|
||||
trainContent:{
|
||||
select:{
|
||||
trainContent: {
|
||||
select: {
|
||||
id: true,
|
||||
title: true,
|
||||
type: true,
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
});
|
||||
// useEffect(() => {
|
||||
// traincontents?.forEach((situation)=>{
|
||||
|
@ -76,7 +75,7 @@ export default function StaffModal() {
|
|||
data: staffData
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
toast.success("保存成功");
|
||||
setVisible(false);
|
||||
setEditingRecord(null);
|
||||
|
@ -94,15 +93,15 @@ export default function StaffModal() {
|
|||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (visible&&editingRecord) {
|
||||
if (visible && editingRecord) {
|
||||
form.setFieldsValue(editingRecord);
|
||||
}
|
||||
}, [visible,editingRecord]);
|
||||
}, [visible, editingRecord]);
|
||||
return (
|
||||
<>
|
||||
<Modal
|
||||
title="编辑员工信息"
|
||||
visible={visible}
|
||||
open={visible}
|
||||
onOk={handleOk}
|
||||
onCancel={handleCancel}
|
||||
>
|
||||
|
@ -137,7 +136,7 @@ export default function StaffModal() {
|
|||
<Select.Option value={false}>是</Select.Option>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
|
||||
|
||||
<Form.List name="trainSituations">
|
||||
{(fields, { add, remove }) => (
|
||||
<>
|
||||
|
@ -149,7 +148,7 @@ export default function StaffModal() {
|
|||
label="培训内容"
|
||||
className="flex-1"
|
||||
>
|
||||
<TrainContentTreeSelect/>
|
||||
<TrainContentTreeSelect />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
{...restField}
|
||||
|
|
|
@ -9,15 +9,15 @@ import { render } from "react-dom";
|
|||
|
||||
// 提取处理嵌套字段的函数
|
||||
const getNestedValue = (record: any, dataIndex: string | string[]) => {
|
||||
if (Array.isArray(dataIndex)) {
|
||||
return dataIndex.reduce((obj, key) => obj?.[key], record);
|
||||
}
|
||||
return record[dataIndex];
|
||||
if (Array.isArray(dataIndex)) {
|
||||
return dataIndex.reduce((obj, key) => obj?.[key], record);
|
||||
}
|
||||
return record[dataIndex];
|
||||
};
|
||||
|
||||
export default function StaffTable() {
|
||||
const{form, setVisible,searchValue} = useMainContext()
|
||||
|
||||
const { form, setVisible, searchValue } = useMainContext()
|
||||
|
||||
const { data: staffs, isLoading } = api.staff.findMany.useQuery({
|
||||
where: {
|
||||
deletedAt: null,
|
||||
|
@ -44,7 +44,7 @@ export default function StaffTable() {
|
|||
console.log(staffs);
|
||||
}, [staffs]);
|
||||
const { softDeleteByIds } = useStaff();
|
||||
const {editingRecord, setEditingRecord} = useMainContext();
|
||||
const { editingRecord, setEditingRecord } = useMainContext();
|
||||
const [isTrainingModalVisible, setIsTrainingModalVisible] = useState(false);
|
||||
const [selectedTrainings, setSelectedTrainings] = useState([]);
|
||||
|
||||
|
@ -70,7 +70,7 @@ export default function StaffTable() {
|
|||
title: "职务",
|
||||
dataIndex: ["position", "type"],
|
||||
key: "position",
|
||||
render: (_, record) => record.position?.type || "无职务"
|
||||
render: (_, record) => record.position?.type || "无职务"
|
||||
},
|
||||
{
|
||||
title: "在位",
|
||||
|
@ -84,7 +84,7 @@ export default function StaffTable() {
|
|||
key: "trainSituations",
|
||||
render: (situations) => {
|
||||
if (!situations?.length) return "无培训记录";
|
||||
|
||||
|
||||
return (
|
||||
<div>
|
||||
{situations.slice(0, 2).map((s) => (
|
||||
|
@ -94,7 +94,7 @@ export default function StaffTable() {
|
|||
</div>
|
||||
))}
|
||||
{situations.length > 2 && (
|
||||
<span
|
||||
<span
|
||||
className="text-blue-500 cursor-pointer hover:underline"
|
||||
onClick={() => showTrainingDetails(situations)}
|
||||
>
|
||||
|
@ -142,15 +142,15 @@ export default function StaffTable() {
|
|||
];
|
||||
useEffect(() => {
|
||||
if (editingRecord) {
|
||||
form.setFieldsValue(editingRecord);
|
||||
console.log(editingRecord);
|
||||
form.setFieldsValue(editingRecord);
|
||||
console.log(editingRecord);
|
||||
}
|
||||
}, [editingRecord]);
|
||||
}, [editingRecord]);
|
||||
const handleEdit = (record) => {
|
||||
setEditingRecord(record);
|
||||
form.setFieldsValue(record); // 修正为设置当前记录的值
|
||||
setVisible(true);
|
||||
};
|
||||
};
|
||||
return (
|
||||
<>
|
||||
{
|
||||
|
@ -188,7 +188,7 @@ export default function StaffTable() {
|
|||
// 使用提取的函数处理嵌套字段
|
||||
<td key={column.key}>
|
||||
{column.render?.(
|
||||
getNestedValue(record, column.dataIndex),
|
||||
getNestedValue(record, column.dataIndex),
|
||||
record
|
||||
) || getNestedValue(record, column.dataIndex)}
|
||||
</td>
|
||||
|
@ -199,7 +199,7 @@ export default function StaffTable() {
|
|||
</Table>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
<Modal
|
||||
title="培训详情"
|
||||
open={isTrainingModalVisible}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
|
||||
import TrainPlanWrite from "./TrainPlanWrite";
|
||||
|
||||
|
||||
export default function DailyLayout(){
|
||||
return (
|
||||
<div className="w-full h-[calc(100vh-100px)]">
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
export default function TrainPlanWrite(){
|
||||
return <div>TrainPlanWrite</div>
|
||||
return <div>test</div>
|
||||
}
|
|
@ -17,6 +17,9 @@ import AdminLayout from "../components/layout/admin/AdminLayout";
|
|||
import { adminRoute } from "./admin-route";
|
||||
import SportPage from "../app/main/sport/page";
|
||||
import { SportPageProvider } from "../app/main/sport/sportPageProvider";
|
||||
import PositionAssessmentPage from "../app/main/positionassessment/page";
|
||||
import CommonAssessmentPage from "../app/main/commonassessment/page";
|
||||
import MonthPlanPage from "../app/main/plan/monthplan/page";
|
||||
interface CustomIndexRouteObject extends IndexRouteObject {
|
||||
name?: string;
|
||||
breadcrumb?: string;
|
||||
|
@ -25,7 +28,6 @@ interface CustomIndexRouteObject extends IndexRouteObject {
|
|||
name?: string;
|
||||
breadcrumb?: string;
|
||||
}
|
||||
|
||||
export interface CustomNonIndexRouteObject extends NonIndexRouteObject {
|
||||
name?: string;
|
||||
children?: CustomRouteObject[];
|
||||
|
@ -53,45 +55,45 @@ export const routes: CustomRouteObject[] = [
|
|||
children: [
|
||||
{
|
||||
index: true,
|
||||
element:<Dashboard></Dashboard>,
|
||||
element: <Dashboard></Dashboard>,
|
||||
},
|
||||
{
|
||||
path: "/staff",
|
||||
element: <StaffMessage></StaffMessage>,
|
||||
},
|
||||
{
|
||||
path:"/plan",
|
||||
children:[
|
||||
path: "/plan",
|
||||
children: [
|
||||
{
|
||||
path:"weekplan",
|
||||
element:<WeekPlanPage></WeekPlanPage>
|
||||
path: "weekplan",
|
||||
element: <WeekPlanPage></WeekPlanPage>
|
||||
},
|
||||
{
|
||||
path:"monthplan",
|
||||
element:<DailyPage></DailyPage>
|
||||
path: "monthplan",
|
||||
element: <MonthPlanPage></MonthPlanPage>
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path:"/daily",
|
||||
element:<DailyPage></DailyPage>
|
||||
path: "/daily",
|
||||
element: <DailyPage></DailyPage>
|
||||
},
|
||||
{
|
||||
path:"/assessment",
|
||||
children:[
|
||||
path: "/assessment",
|
||||
children: [
|
||||
{
|
||||
path:"positionassessment",
|
||||
element:<DailyPage></DailyPage>
|
||||
path: "positionassessment",
|
||||
element: <PositionAssessmentPage></PositionAssessmentPage>
|
||||
},
|
||||
{
|
||||
path:"commonassessment",
|
||||
element:<DailyPage></DailyPage>
|
||||
path: "commonassessment",
|
||||
element: <CommonAssessmentPage></CommonAssessmentPage>
|
||||
},
|
||||
{
|
||||
path:"sportsassessment",
|
||||
element:<SportPageProvider>
|
||||
<SportPage></SportPage>
|
||||
</SportPageProvider>
|
||||
path: "sportsassessment",
|
||||
element: <SportPageProvider>
|
||||
<SportPage></SportPage>
|
||||
</SportPageProvider>
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -100,13 +102,11 @@ export const routes: CustomRouteObject[] = [
|
|||
element: <AdminLayout></AdminLayout>,
|
||||
children: adminRoute.children,
|
||||
}
|
||||
|
||||
],
|
||||
},
|
||||
|
||||
],
|
||||
},
|
||||
|
||||
|
||||
{
|
||||
path: "/login",
|
||||
breadcrumb: "登录",
|
||||
|
|
|
@ -100,7 +100,7 @@ server {
|
|||
# 仅供内部使用
|
||||
internal;
|
||||
# 代理到认证服务
|
||||
proxy_pass http://192.168.252.77:3000/auth/file;
|
||||
proxy_pass http://192.168.252.77:3001/auth/file;
|
||||
|
||||
# 请求优化:不传递请求体
|
||||
proxy_pass_request_body off;
|
||||
|
|
|
@ -11,5 +11,11 @@
|
|||
},
|
||||
"keywords": [],
|
||||
"author": "insiinc",
|
||||
"license": "ISC"
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@antv/g6": "^5.0.44",
|
||||
"antd": "^5.23.0",
|
||||
"echarts": "^5.6.0",
|
||||
"echarts-for-react": "^3.0.2"
|
||||
}
|
||||
}
|
|
@ -437,7 +437,6 @@ model Staff {
|
|||
age Int? @default(22) @map("age")
|
||||
sex Boolean? @default(true) @map("sex")
|
||||
absent Boolean? @default(false) @map("absent")
|
||||
|
||||
trainSituations TrainSituation[]
|
||||
position Position? @relation("StaffPosition", fields: [positionId], references: [id])
|
||||
positionId String? @map("position_id")
|
||||
|
|
729
pnpm-lock.yaml
729
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue