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,
|
UnauthorizedException,
|
||||||
Logger,
|
Logger,
|
||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
|
import { Request } from '@nestjs/common';
|
||||||
import { AuthService } from './auth.service';
|
import { AuthService } from './auth.service';
|
||||||
import { AuthSchema, JwtPayload } from '@nice/common';
|
import { AuthSchema, JwtPayload } from '@nice/common';
|
||||||
import { AuthGuard } from './auth.guard';
|
import { AuthGuard } from './auth.guard';
|
||||||
|
|
|
@ -27,11 +27,14 @@ export class StaffController {
|
||||||
}
|
}
|
||||||
@Get('find-by-dept')
|
@Get('find-by-dept')
|
||||||
async findByDept(
|
async findByDept(
|
||||||
@Query('dept-id') deptId: string,
|
@Query('dept-id') deptId: string | null,
|
||||||
@Query('domain-id') domainId: string,
|
@Query('domain-id') domainId: string,
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
const result = await this.staffService.findByDept({ deptId, domainId });
|
const result = await this.staffService.findByDept({
|
||||||
|
deptId: deptId || null,
|
||||||
|
domainId: domainId,
|
||||||
|
});
|
||||||
return {
|
return {
|
||||||
data: result,
|
data: result,
|
||||||
errmsg: 'success',
|
errmsg: 'success',
|
||||||
|
|
|
@ -72,6 +72,8 @@ const StaffSelect = {
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class StaffService extends BaseService<Prisma.StaffDelegate> {
|
export class StaffService extends BaseService<Prisma.StaffDelegate> {
|
||||||
|
//索引
|
||||||
|
[x: string]: any;
|
||||||
constructor(private readonly departmentService: DepartmentService) {
|
constructor(private readonly departmentService: DepartmentService) {
|
||||||
super(db, ObjectType.STAFF, true);
|
super(db, ObjectType.STAFF, true);
|
||||||
}
|
}
|
||||||
|
@ -83,7 +85,7 @@ export class StaffService extends BaseService<Prisma.StaffDelegate> {
|
||||||
async findByDept(data: z.infer<typeof StaffMethodSchema.findByDept>) {
|
async findByDept(data: z.infer<typeof StaffMethodSchema.findByDept>) {
|
||||||
const { deptId, domainId } = data;
|
const { deptId, domainId } = data;
|
||||||
const childDepts = await this.departmentService.getDescendantIds(
|
const childDepts = await this.departmentService.getDescendantIds(
|
||||||
deptId,
|
deptId || null,
|
||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
const result = await db.staff.findMany({
|
const result = await db.staff.findMany({
|
||||||
|
|
|
@ -39,6 +39,7 @@ export class TrainSituationRouter {
|
||||||
deptId: z.string().optional(),
|
deptId: z.string().optional(),
|
||||||
domainId: z.string().optional(),
|
domainId: z.string().optional(),
|
||||||
trainContentId: z.string().optional(),
|
trainContentId: z.string().optional(),
|
||||||
|
date: z.string().optional(),
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.query(async ({ input }) => {
|
.query(async ({ input }) => {
|
||||||
|
|
|
@ -18,13 +18,13 @@ export class TrainSituationService extends BaseService<Prisma.TrainSituationDele
|
||||||
// 创建培训情况
|
// 创建培训情况
|
||||||
async create(args: Prisma.TrainSituationCreateArgs) {
|
async create(args: Prisma.TrainSituationCreateArgs) {
|
||||||
console.log(args);
|
console.log(args);
|
||||||
const result = await super.create(args);
|
const result = await db.trainSituation.create(args);
|
||||||
this.emitDataChanged(CrudOperation.CREATED, result);
|
this.emitDataChanged(CrudOperation.CREATED, result);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
// 更新培训情况
|
// 更新培训情况
|
||||||
async update(args: Prisma.TrainSituationUpdateArgs) {
|
async update(args: Prisma.TrainSituationUpdateArgs) {
|
||||||
const result = await super.update(args);
|
const result = await db.trainSituation.update(args);
|
||||||
this.emitDataChanged(CrudOperation.UPDATED, result);
|
this.emitDataChanged(CrudOperation.UPDATED, result);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
@ -35,10 +35,11 @@ export class TrainSituationService extends BaseService<Prisma.TrainSituationDele
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
// 查找某一单位所有人员的培训情况
|
// 查找某一单位所有人员的培训情况
|
||||||
async findManyByDeptId(args: {
|
async findManyByDeptId(params: {
|
||||||
deptId?: string;
|
deptId?: string;
|
||||||
domainId?: string;
|
domainId?: string;
|
||||||
trainContentId?: string;
|
trainContentId?: string;
|
||||||
|
date?: string;
|
||||||
}): Promise<
|
}): Promise<
|
||||||
{
|
{
|
||||||
id: string;
|
id: string;
|
||||||
|
@ -49,16 +50,56 @@ export class TrainSituationService extends BaseService<Prisma.TrainSituationDele
|
||||||
score: number;
|
score: number;
|
||||||
}[]
|
}[]
|
||||||
> {
|
> {
|
||||||
const staffs = await this.staffService.findByDept({
|
const { deptId, domainId, trainContentId, date } = params;
|
||||||
deptId: args.deptId,
|
|
||||||
domainId: args.domainId,
|
// 构建查询条件
|
||||||
});
|
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({
|
const result = await super.findMany({
|
||||||
where: {
|
where,
|
||||||
staffId: {
|
include: {
|
||||||
in: staffs.map((staff) => staff.id),
|
staff: {
|
||||||
|
include: {
|
||||||
|
department: true,
|
||||||
|
position: true,
|
||||||
},
|
},
|
||||||
...(args.trainContentId ? { trainContentId: args.trainContentId } : {}),
|
},
|
||||||
|
trainContent: true,
|
||||||
|
},
|
||||||
|
orderBy: {
|
||||||
|
createdAt: 'desc', // 按创建时间倒序排列,最新的记录在前
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
return result;
|
return result;
|
||||||
|
|
|
@ -40,3 +40,38 @@
|
||||||
.ag-root-wrapper {
|
.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>
|
<div>
|
||||||
<Modal
|
<Modal
|
||||||
title="添加年龄范围"
|
title="添加年龄范围"
|
||||||
visible={isAgeModalVisible}
|
open={isAgeModalVisible}
|
||||||
onOk={ageForm.submit}
|
onOk={ageForm.submit}
|
||||||
onCancel={handleAgeCancel}
|
onCancel={handleAgeCancel}
|
||||||
>
|
>
|
||||||
|
@ -19,10 +19,9 @@ export default function AssessmentModal() {
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</Form>
|
</Form>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
||||||
<Modal
|
<Modal
|
||||||
title="添加分数与对应标准"
|
title="添加分数与对应标准"
|
||||||
visible={isScoreModalVisible}
|
open={isScoreModalVisible}
|
||||||
onOk={scoreForm.submit}
|
onOk={scoreForm.submit}
|
||||||
onCancel={handleScoreCancel}
|
onCancel={handleScoreCancel}
|
||||||
>
|
>
|
||||||
|
|
|
@ -57,7 +57,6 @@ export function AssessmentStandardProvider({ children }: AssessmentStandardProvi
|
||||||
const showAgeModal = () => {
|
const showAgeModal = () => {
|
||||||
setIsAgeModalVisible(true);
|
setIsAgeModalVisible(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
// 处理年龄范围模态框确定
|
// 处理年龄范围模态框确定
|
||||||
const handleAgeOk = (values: any) => {
|
const handleAgeOk = (values: any) => {
|
||||||
console.log('values',values)
|
console.log('values',values)
|
||||||
|
@ -68,10 +67,8 @@ export function AssessmentStandardProvider({ children }: AssessmentStandardProvi
|
||||||
label: end ? `${start}-${end}岁` : `${start}岁以上`,
|
label: end ? `${start}-${end}岁` : `${start}岁以上`,
|
||||||
};
|
};
|
||||||
setAgeRanges([...ageRanges, newRange]);
|
setAgeRanges([...ageRanges, newRange]);
|
||||||
|
|
||||||
setIsAgeModalVisible(false);
|
setIsAgeModalVisible(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
// 处理年龄范围模态框取消
|
// 处理年龄范围模态框取消
|
||||||
const handleAgeCancel = () => {
|
const handleAgeCancel = () => {
|
||||||
setIsAgeModalVisible(false);
|
setIsAgeModalVisible(false);
|
||||||
|
@ -81,7 +78,6 @@ export function AssessmentStandardProvider({ children }: AssessmentStandardProvi
|
||||||
const showScoreModal = () => {
|
const showScoreModal = () => {
|
||||||
setIsScoreModalVisible(true);
|
setIsScoreModalVisible(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
// 处理分数标准模态框确定
|
// 处理分数标准模态框确定
|
||||||
const handleScoreOk = async (values: any) => {
|
const handleScoreOk = async (values: any) => {
|
||||||
const { score, standards } = values;
|
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 { useAssessmentStandardContext } from "./assessment-standard-provider";
|
||||||
import { api, useSport } from "@nice/client";
|
import { api, useSport } from "@nice/client";
|
||||||
import toast from "react-hot-toast";
|
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() {
|
export default function SportCreateContent() {
|
||||||
const { form, sportProjectList, sportProjectLoading } = useAssessmentStandardContext();
|
const { form, sportProjectList, sportProjectLoading } = useAssessmentStandardContext();
|
||||||
const { createSportProject, softDeleteByIds } = useSport();
|
const { createSportProject, softDeleteByIds } = useSport();
|
||||||
|
@ -13,6 +13,9 @@ export default function SportCreateContent() {
|
||||||
title: "体能考核"
|
title: "体能考核"
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
const [searchValue, setSearchValue] = useState('');
|
||||||
|
const [currentPage, setCurrentPage] = useState(1);
|
||||||
|
const [pageSize, setPageSize] = useState(10);
|
||||||
const handleCreateProject = async () => {
|
const handleCreateProject = async () => {
|
||||||
if (form.getFieldsValue().createProjectName && form.getFieldsValue().unit) {
|
if (form.getFieldsValue().createProjectName && form.getFieldsValue().unit) {
|
||||||
await createSportProject.mutateAsync({
|
await createSportProject.mutateAsync({
|
||||||
|
@ -37,16 +40,20 @@ export default function SportCreateContent() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const handleDeleteProject = async (id: string) => {
|
const handleDeleteProject = async (id: string) => {
|
||||||
console.log(id)
|
// console.log(id)
|
||||||
await softDeleteByIds.mutateAsync({
|
await softDeleteByIds.mutateAsync({
|
||||||
ids: [id]
|
ids: [id]
|
||||||
} as any)
|
} as any)
|
||||||
toast.success("删除项目成功")
|
toast.success("删除项目成功")
|
||||||
}
|
}
|
||||||
|
const handleSearch = (value) => {
|
||||||
|
setSearchValue(value);
|
||||||
|
setCurrentPage(1);
|
||||||
|
};
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Form form={form} layout="vertical">
|
<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">
|
<Form.Item label="创建项目" name="createProjectName">
|
||||||
<Input placeholder="请输入创建的项目名称" className="mr-2" />
|
<Input placeholder="请输入创建的项目名称" className="mr-2" />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
@ -64,16 +71,48 @@ export default function SportCreateContent() {
|
||||||
</div>
|
</div>
|
||||||
{sportProjectLoading ?
|
{sportProjectLoading ?
|
||||||
<Skeleton></Skeleton> :
|
<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) => (
|
{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
|
||||||
<div className='font-bold'>{item.name}({item.unit})</div>
|
key={item.id}
|
||||||
<span className='text-red-500 cursor-pointer' onClick={() => handleDeleteProject(item.id)}>删除</span>
|
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>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
</Form>
|
</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 toast from "react-hot-toast";
|
||||||
import { api, useSport } from "@nice/client";
|
import { api, useSport } from "@nice/client";
|
||||||
import { useEffect, useMemo } from "react";
|
import { useEffect, useMemo } from "react";
|
||||||
|
import '../../../App.css'
|
||||||
export default function StandardCreateContent() {
|
export default function StandardCreateContent() {
|
||||||
const {form,sportProjectList,ageRanges,records,showAgeModal,showScoreModal,setRecords,setAgeRanges,isStandardCreate,setIsStandardCreate} = useAssessmentStandardContext();
|
const {form,sportProjectList,ageRanges,records,showAgeModal,showScoreModal,setRecords,setAgeRanges,isStandardCreate,setIsStandardCreate} = useAssessmentStandardContext();
|
||||||
const { createSportStandard,updateSportStandard } = useSport();
|
const { createSportStandard,updateSportStandard } = useSport();
|
||||||
|
@ -28,6 +29,9 @@ export default function StandardCreateContent() {
|
||||||
dataIndex: 'score',
|
dataIndex: 'score',
|
||||||
key: 'score',
|
key: 'score',
|
||||||
width: 100,
|
width: 100,
|
||||||
|
render: (score: number) => (
|
||||||
|
<span className="font-medium text-gray-700">{score}</span>
|
||||||
|
)
|
||||||
},
|
},
|
||||||
...ageRanges.map((range, index) => ({
|
...ageRanges.map((range, index) => ({
|
||||||
title: range.label,
|
title: range.label,
|
||||||
|
@ -35,7 +39,6 @@ export default function StandardCreateContent() {
|
||||||
key: `standard[${index}]`,
|
key: `standard[${index}]`,
|
||||||
render: (_: any, record: any) => (
|
render: (_: any, record: any) => (
|
||||||
<Input
|
<Input
|
||||||
style={{ width: '80px' }}
|
|
||||||
value={record.standards[index]}
|
value={record.standards[index]}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
const inputValue = e.target.value;
|
const inputValue = e.target.value;
|
||||||
|
@ -113,10 +116,8 @@ export default function StandardCreateContent() {
|
||||||
setRecords(records)
|
setRecords(records)
|
||||||
}
|
}
|
||||||
}, [data])
|
}, [data])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Form form={form} layout="vertical">
|
<Form form={form} layout="vertical">
|
||||||
|
|
||||||
<Space size="large" className="my-6">
|
<Space size="large" className="my-6">
|
||||||
<Form.Item label="项目" name="projectId">
|
<Form.Item label="项目" name="projectId">
|
||||||
<Select
|
<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})` })) || []}
|
options={sportProjectList?.filter(item=>item.deletedAt === null)?.map((item) => ({ value: item.id, label: `${item.name}(${item.unit})` })) || []}
|
||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
<Form.Item label="性别" name="gender">
|
<Form.Item label="性别" name="gender">
|
||||||
<Select
|
<Select
|
||||||
style={{ width: 120 }}
|
style={{ width: 120 }}
|
||||||
|
@ -136,7 +136,6 @@ export default function StandardCreateContent() {
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
<Form.Item label="人员类型" name="personType">
|
<Form.Item label="人员类型" name="personType">
|
||||||
<Select
|
<Select
|
||||||
style={{ width: 160 }}
|
style={{ width: 160 }}
|
||||||
|
@ -151,14 +150,19 @@ export default function StandardCreateContent() {
|
||||||
<Button onClick={showScoreModal} className="mt-9 ml-2">添加分数与对应标准</Button>
|
<Button onClick={showScoreModal} className="mt-9 ml-2">添加分数与对应标准</Button>
|
||||||
<Button type="primary" onClick={handleSave} className='mt-9 ml-2'>保存标准</Button>
|
<Button type="primary" onClick={handleSave} className='mt-9 ml-2'>保存标准</Button>
|
||||||
</Space>
|
</Space>
|
||||||
|
<div className="mt-4">
|
||||||
<Table
|
<Table
|
||||||
columns={columns}
|
columns={columns}
|
||||||
dataSource={records}
|
dataSource={records}
|
||||||
bordered
|
|
||||||
pagination={false}
|
pagination={false}
|
||||||
|
bordered
|
||||||
|
rowKey="score"
|
||||||
|
className="assessment-standard-table"
|
||||||
|
size="middle"
|
||||||
scroll={{ x: 'max-content' }}
|
scroll={{ x: 'max-content' }}
|
||||||
|
rowClassName={() => "bg-white"}
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
</Form>
|
</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 { MagnifyingGlassIcon } from "@heroicons/react/24/outline";
|
||||||
import DailyLayout from "@web/src/components/models/trainPlan/TrainPlanLayout";
|
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(){
|
export default function DailyReport() {
|
||||||
return <>
|
const { form, formValue, setFormValue, setVisible, setSearchValue, editingRecord } = useMainContext();
|
||||||
<DailyContext>
|
|
||||||
<DailyLayout></DailyLayout>
|
useEffect(() => {
|
||||||
</DailyContext>
|
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 React, { useState } from 'react';
|
||||||
import { api } from "@nice/client"
|
import { Card, Row, Col, Divider } from 'antd';
|
||||||
import React from "react"
|
import ReactEcharts from 'echarts-for-react';
|
||||||
import { useSport } from "@nice/client"
|
|
||||||
export default function Dashboard() {
|
const Dashboard = () => {
|
||||||
const { createSportStandard } = useSport()
|
// 在这里获取你的项目数据
|
||||||
const handleCreateSportStandard = async () => {
|
const [data, setData] = useState({
|
||||||
const res = await createSportStandard.mutateAsync({
|
projectInfo: {
|
||||||
data: {
|
name: '训练管理系统',
|
||||||
projectId: "cm8o6jzp908bp846bv513aqvo",
|
type: '软件开发',
|
||||||
gender: true,
|
status: '进行中',
|
||||||
personType: "STAFF",
|
department: '技术研发中心',
|
||||||
ageRanges: [
|
client: '软件小组',
|
||||||
{ start: null, end: 24, label: "24岁以下" },
|
supervisor: '项目监理公司',
|
||||||
{ start: 24, end: 27, label: "25-27岁" },
|
contractor: '开发团队',
|
||||||
{ start: 27, end: 30, label: "28-30岁" },
|
planStartDate: '2023-10-01',
|
||||||
{ start: 30, end: null, label: "30岁以上" }
|
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: '其他' }
|
||||||
],
|
],
|
||||||
scoreTable: {
|
// 参训时长数据
|
||||||
"100": [85, 81, 79, 77],
|
hours: [
|
||||||
"95": [74, 70, 68, 66],
|
{ value: 25, name: '已完成' },
|
||||||
"90": [65, 61, 59, 57]
|
{ 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 (
|
return (
|
||||||
<div >
|
<div style={{
|
||||||
数据看板(待开发)
|
padding: '20px',
|
||||||
<Button type="primary" onClick={() => handleCreateSportStandard()}>创建体育项目</Button>
|
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>
|
</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;
|
|
@ -107,8 +107,7 @@ const NavigationMenu: React.FC = () => {
|
||||||
location.pathname.startsWith("/assessment/") ||
|
location.pathname.startsWith("/assessment/") ||
|
||||||
location.pathname === "/plan/weekplan" ||
|
location.pathname === "/plan/weekplan" ||
|
||||||
location.pathname === "/plan/monthplan"
|
location.pathname === "/plan/monthplan"
|
||||||
)
|
) {
|
||||||
{
|
|
||||||
setSelectedKeys([location.pathname]);
|
setSelectedKeys([location.pathname]);
|
||||||
setOpenKeys([location.pathname.split('/').slice(0, 2).join('/')]);
|
setOpenKeys([location.pathname.split('/').slice(0, 2).join('/')]);
|
||||||
} else if (
|
} else if (
|
||||||
|
@ -123,7 +122,7 @@ const NavigationMenu: React.FC = () => {
|
||||||
}, [location.pathname]);
|
}, [location.pathname]);
|
||||||
|
|
||||||
const hit = (pathname: string): string[] => {
|
const hit = (pathname: string): string[] => {
|
||||||
for (let p in children2Parent) {
|
for (const p in children2Parent) {
|
||||||
if (pathname.search(p) >= 0) {
|
if (pathname.search(p) >= 0) {
|
||||||
return children2Parent[p];
|
return children2Parent[p];
|
||||||
}
|
}
|
||||||
|
@ -132,7 +131,7 @@ const NavigationMenu: React.FC = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const openKeyMerge = (pathname: string): string[] => {
|
const openKeyMerge = (pathname: string): string[] => {
|
||||||
let newOpenKeys = hit(pathname);
|
const newOpenKeys = hit(pathname);
|
||||||
for (let i = 0; i < openKeys.length; i++) {
|
for (let i = 0; i < openKeys.length; i++) {
|
||||||
let isIn = false;
|
let isIn = false;
|
||||||
for (let j = 0; j < newOpenKeys.length; j++) {
|
for (let j = 0; j < newOpenKeys.length; j++) {
|
||||||
|
@ -167,20 +166,20 @@ const NavigationMenu: React.FC = () => {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setActiveMenus(items);
|
setActiveMenus(items);
|
||||||
// console.log(activeMenus)
|
// console.log(activeMenus)
|
||||||
},[items])
|
}, [items])
|
||||||
// useEffect(() => {
|
// useEffect(() => {
|
||||||
// checkMenuPermissions(items, permissions);
|
// checkMenuPermissions(items, permissions);
|
||||||
// }, [items, permissions]);
|
// }, [items, permissions]);
|
||||||
|
|
||||||
// const checkMenuPermissions = (items: any, permissions: any) => {
|
// const checkMenuPermissions = (items: any, permissions: any) => {
|
||||||
// let menus: any = [];
|
// const menus: any = [];
|
||||||
// if (permissions.length === 0) {
|
// if (permissions.length === 0) {
|
||||||
// setActiveMenus(menus);
|
// setActiveMenus(menus);
|
||||||
// return;
|
// return;
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// for (let i in items) {
|
// for (const i in items) {
|
||||||
// let menuItem = items[i];
|
// const menuItem = items[i];
|
||||||
// // 一级菜单=>没有子菜单&配置了权限
|
// // 一级菜单=>没有子菜单&配置了权限
|
||||||
// if (menuItem.children === null) {
|
// if (menuItem.children === null) {
|
||||||
// if (
|
// if (
|
||||||
|
@ -192,10 +191,10 @@ const NavigationMenu: React.FC = () => {
|
||||||
// menus.push(menuItem);
|
// menus.push(menuItem);
|
||||||
// continue;
|
// continue;
|
||||||
// }
|
// }
|
||||||
// let children = [];
|
// const children = [];
|
||||||
|
|
||||||
// for (let j in menuItem.children) {
|
// for (const j in menuItem.children) {
|
||||||
// let childrenItem = menuItem.children[j];
|
// const childrenItem = menuItem.children[j];
|
||||||
|
|
||||||
// if (
|
// if (
|
||||||
// typeof permissions[childrenItem.permission] !== "undefined" ||
|
// 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 { Table, Select, Pagination } from 'antd'
|
||||||
import type { ColumnsType, ColumnType } from 'antd/es/table'
|
import type { ColumnsType, ColumnType } from 'antd/es/table'
|
||||||
import { UploadOutlined } from '@ant-design/icons'
|
import { UploadOutlined } from '@ant-design/icons'
|
||||||
|
import React from 'react';
|
||||||
interface TableData {
|
interface TableData {
|
||||||
key: string
|
key: string
|
||||||
[key: string]: any
|
[key: string]: any
|
||||||
|
|
|
@ -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 { 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 toast from "react-hot-toast";
|
||||||
import _ from "lodash";
|
|
||||||
import * as XLSX from 'xlsx';
|
import * as XLSX from 'xlsx';
|
||||||
import { UploadOutlined, DownloadOutlined } from '@ant-design/icons';
|
import { UploadOutlined, DownloadOutlined } from '@ant-design/icons';
|
||||||
import { useSportPageContext } from "./sportPageProvider";
|
import { useSportPageContext } from "./sportPageProvider";
|
||||||
|
@ -11,14 +10,14 @@ import { api, useTrainSituation } from "@nice/client";
|
||||||
export default function SportPage() {
|
export default function SportPage() {
|
||||||
const [sportsData, setSportsData] = useState([]);
|
const [sportsData, setSportsData] = useState([]);
|
||||||
const { sportProjectList, handlNewScoreOpen, staffsWithScore, staffsWithScoreLoading } = useSportPageContext()
|
const { sportProjectList, handlNewScoreOpen, staffsWithScore, staffsWithScoreLoading } = useSportPageContext()
|
||||||
const {deleteSameGroupTrainSituation} = useTrainSituation()
|
const { deleteSameGroupTrainSituation } = useTrainSituation()
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!staffsWithScore) return;
|
if (!staffsWithScore) return;
|
||||||
const newSportsData = []
|
const newSportsData = []
|
||||||
staffsWithScore.forEach(item => {
|
staffsWithScore.forEach(item => {
|
||||||
const groupedTrainSituations = {};
|
const groupedTrainSituations = {};
|
||||||
item.trainSituations.forEach(train => {
|
item.trainSituations.forEach(train => {
|
||||||
if(train.groupId === null) return
|
if (train.groupId === null) return
|
||||||
const groupId = train.groupId;
|
const groupId = train.groupId;
|
||||||
if (!groupedTrainSituations[groupId]) {
|
if (!groupedTrainSituations[groupId]) {
|
||||||
groupedTrainSituations[groupId] = {
|
groupedTrainSituations[groupId] = {
|
||||||
|
@ -39,7 +38,7 @@ export default function SportPage() {
|
||||||
age: item.age,
|
age: item.age,
|
||||||
unit: item.department?.name,
|
unit: item.department?.name,
|
||||||
situationIds: groupedTrainSituations[groupId].situations.map(train => train.id),
|
situationIds: groupedTrainSituations[groupId].situations.map(train => train.id),
|
||||||
totalScore:groupedTrainSituations[groupId].totalScore,
|
totalScore: groupedTrainSituations[groupId].totalScore,
|
||||||
...groupedTrainSituations[groupId].trainScore,
|
...groupedTrainSituations[groupId].trainScore,
|
||||||
groupId
|
groupId
|
||||||
})
|
})
|
||||||
|
@ -70,12 +69,6 @@ export default function SportPage() {
|
||||||
dataIndex: "unit",
|
dataIndex: "unit",
|
||||||
key: "unit",
|
key: "unit",
|
||||||
},
|
},
|
||||||
...sportProjectList?.filter(item=>item.deletedAt === null).map((item) => ({
|
|
||||||
title: item.name,
|
|
||||||
dataIndex: item.name,
|
|
||||||
key: item.name,
|
|
||||||
editable: true,
|
|
||||||
})),
|
|
||||||
{
|
{
|
||||||
title: "BMI",
|
title: "BMI",
|
||||||
dataIndex: "bodyType",
|
dataIndex: "bodyType",
|
||||||
|
@ -115,7 +108,7 @@ export default function SportPage() {
|
||||||
console.log(record)
|
console.log(record)
|
||||||
await deleteSameGroupTrainSituation.mutateAsync(
|
await deleteSameGroupTrainSituation.mutateAsync(
|
||||||
{
|
{
|
||||||
groupId:record.groupId
|
groupId: record.groupId
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
toast.success("删除成功");
|
toast.success("删除成功");
|
||||||
|
@ -148,37 +141,35 @@ export default function SportPage() {
|
||||||
|
|
||||||
// 导入成绩
|
// 导入成绩
|
||||||
const handleImport = (file) => {
|
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) => {
|
// reader.readAsArrayBuffer(file);
|
||||||
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]);
|
// return false;
|
||||||
toast.success(`成功导入${importedData.length}条记录`);
|
|
||||||
} catch (error) {
|
|
||||||
console.error('导入失败:', error);
|
|
||||||
toast.error("导入失败,请检查文件格式");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
reader.readAsArrayBuffer(file);
|
|
||||||
|
|
||||||
// 防止自动上传
|
|
||||||
return false;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -51,7 +51,7 @@ export default function SportPageModal() {
|
||||||
<Modal
|
<Modal
|
||||||
title="添加考核成绩"
|
title="添加考核成绩"
|
||||||
onOk={newScoreForm.submit}
|
onOk={newScoreForm.submit}
|
||||||
visible={isNewScoreModalVisible}
|
open={isNewScoreModalVisible}
|
||||||
onCancel={handleNewScoreCancel}
|
onCancel={handleNewScoreCancel}
|
||||||
>
|
>
|
||||||
<Form
|
<Form
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
import { MagnifyingGlassIcon } from "@heroicons/react/24/outline";
|
import { MagnifyingGlassIcon } from "@heroicons/react/24/outline";
|
||||||
import { Button, Form, Input } from "antd";
|
import { Button, Form, Input } from "antd";
|
||||||
import { useCallback, useEffect} from "react";
|
import { useCallback, useEffect } from "react";
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
import { useMainContext } from "../layout/MainProvider";
|
import { useMainContext } from "../layout/MainProvider";
|
||||||
import StaffTable from "./stafftable/page";
|
import StaffTable from "./stafftable/page";
|
||||||
import StaffModal from "./staffmodal/page";
|
import StaffModal from "./staffmodal/page";
|
||||||
export default function StaffMessage() {
|
export default function StaffMessage() {
|
||||||
const {form, formValue, setFormValue, setVisible, setSearchValue, editingRecord} = useMainContext();
|
const { form, formValue, setFormValue, setVisible, setSearchValue, editingRecord } = useMainContext();
|
||||||
useEffect(()=>{
|
useEffect(() => {
|
||||||
setFormValue(
|
setFormValue(
|
||||||
{
|
{
|
||||||
username: "",
|
username: "",
|
||||||
|
@ -17,7 +17,7 @@ export default function StaffMessage() {
|
||||||
trainSituations: "",
|
trainSituations: "",
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
},[])
|
}, [])
|
||||||
const handleNew = () => {
|
const handleNew = () => {
|
||||||
form.setFieldsValue(formValue);
|
form.setFieldsValue(formValue);
|
||||||
console.log(editingRecord);
|
console.log(editingRecord);
|
||||||
|
|
|
@ -7,18 +7,17 @@ import TrainContentTreeSelect from "@web/src/components/models/trainContent/trai
|
||||||
import DepartmentChildrenSelect from "@web/src/components/models/department/department-children-select";
|
import DepartmentChildrenSelect from "@web/src/components/models/department/department-children-select";
|
||||||
|
|
||||||
export default function StaffModal() {
|
export default function StaffModal() {
|
||||||
const { data: traincontents} = api.trainSituation.findMany.useQuery({
|
const { data: traincontents } = api.trainSituation.findMany.useQuery({
|
||||||
select:{
|
select: {
|
||||||
id: true,
|
id: true,
|
||||||
trainContent:{
|
trainContent: {
|
||||||
select:{
|
select: {
|
||||||
id: true,
|
id: true,
|
||||||
title: true,
|
title: true,
|
||||||
type: true,
|
type: true,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
// useEffect(() => {
|
// useEffect(() => {
|
||||||
// traincontents?.forEach((situation)=>{
|
// traincontents?.forEach((situation)=>{
|
||||||
|
@ -94,15 +93,15 @@ export default function StaffModal() {
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (visible&&editingRecord) {
|
if (visible && editingRecord) {
|
||||||
form.setFieldsValue(editingRecord);
|
form.setFieldsValue(editingRecord);
|
||||||
}
|
}
|
||||||
}, [visible,editingRecord]);
|
}, [visible, editingRecord]);
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Modal
|
<Modal
|
||||||
title="编辑员工信息"
|
title="编辑员工信息"
|
||||||
visible={visible}
|
open={visible}
|
||||||
onOk={handleOk}
|
onOk={handleOk}
|
||||||
onCancel={handleCancel}
|
onCancel={handleCancel}
|
||||||
>
|
>
|
||||||
|
@ -149,7 +148,7 @@ export default function StaffModal() {
|
||||||
label="培训内容"
|
label="培训内容"
|
||||||
className="flex-1"
|
className="flex-1"
|
||||||
>
|
>
|
||||||
<TrainContentTreeSelect/>
|
<TrainContentTreeSelect />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
{...restField}
|
{...restField}
|
||||||
|
|
|
@ -16,7 +16,7 @@ const getNestedValue = (record: any, dataIndex: string | string[]) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function StaffTable() {
|
export default function StaffTable() {
|
||||||
const{form, setVisible,searchValue} = useMainContext()
|
const { form, setVisible, searchValue } = useMainContext()
|
||||||
|
|
||||||
const { data: staffs, isLoading } = api.staff.findMany.useQuery({
|
const { data: staffs, isLoading } = api.staff.findMany.useQuery({
|
||||||
where: {
|
where: {
|
||||||
|
@ -44,7 +44,7 @@ export default function StaffTable() {
|
||||||
console.log(staffs);
|
console.log(staffs);
|
||||||
}, [staffs]);
|
}, [staffs]);
|
||||||
const { softDeleteByIds } = useStaff();
|
const { softDeleteByIds } = useStaff();
|
||||||
const {editingRecord, setEditingRecord} = useMainContext();
|
const { editingRecord, setEditingRecord } = useMainContext();
|
||||||
const [isTrainingModalVisible, setIsTrainingModalVisible] = useState(false);
|
const [isTrainingModalVisible, setIsTrainingModalVisible] = useState(false);
|
||||||
const [selectedTrainings, setSelectedTrainings] = useState([]);
|
const [selectedTrainings, setSelectedTrainings] = useState([]);
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
|
|
||||||
import TrainPlanWrite from "./TrainPlanWrite";
|
import TrainPlanWrite from "./TrainPlanWrite";
|
||||||
|
|
||||||
|
|
||||||
export default function DailyLayout(){
|
export default function DailyLayout(){
|
||||||
return (
|
return (
|
||||||
<div className="w-full h-[calc(100vh-100px)]">
|
<div className="w-full h-[calc(100vh-100px)]">
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
export default function TrainPlanWrite(){
|
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 { adminRoute } from "./admin-route";
|
||||||
import SportPage from "../app/main/sport/page";
|
import SportPage from "../app/main/sport/page";
|
||||||
import { SportPageProvider } from "../app/main/sport/sportPageProvider";
|
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 {
|
interface CustomIndexRouteObject extends IndexRouteObject {
|
||||||
name?: string;
|
name?: string;
|
||||||
breadcrumb?: string;
|
breadcrumb?: string;
|
||||||
|
@ -25,7 +28,6 @@ interface CustomIndexRouteObject extends IndexRouteObject {
|
||||||
name?: string;
|
name?: string;
|
||||||
breadcrumb?: string;
|
breadcrumb?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CustomNonIndexRouteObject extends NonIndexRouteObject {
|
export interface CustomNonIndexRouteObject extends NonIndexRouteObject {
|
||||||
name?: string;
|
name?: string;
|
||||||
children?: CustomRouteObject[];
|
children?: CustomRouteObject[];
|
||||||
|
@ -53,43 +55,43 @@ export const routes: CustomRouteObject[] = [
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
index: true,
|
index: true,
|
||||||
element:<Dashboard></Dashboard>,
|
element: <Dashboard></Dashboard>,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/staff",
|
path: "/staff",
|
||||||
element: <StaffMessage></StaffMessage>,
|
element: <StaffMessage></StaffMessage>,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path:"/plan",
|
path: "/plan",
|
||||||
children:[
|
children: [
|
||||||
{
|
{
|
||||||
path:"weekplan",
|
path: "weekplan",
|
||||||
element:<WeekPlanPage></WeekPlanPage>
|
element: <WeekPlanPage></WeekPlanPage>
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path:"monthplan",
|
path: "monthplan",
|
||||||
element:<DailyPage></DailyPage>
|
element: <MonthPlanPage></MonthPlanPage>
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path:"/daily",
|
path: "/daily",
|
||||||
element:<DailyPage></DailyPage>
|
element: <DailyPage></DailyPage>
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path:"/assessment",
|
path: "/assessment",
|
||||||
children:[
|
children: [
|
||||||
{
|
{
|
||||||
path:"positionassessment",
|
path: "positionassessment",
|
||||||
element:<DailyPage></DailyPage>
|
element: <PositionAssessmentPage></PositionAssessmentPage>
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path:"commonassessment",
|
path: "commonassessment",
|
||||||
element:<DailyPage></DailyPage>
|
element: <CommonAssessmentPage></CommonAssessmentPage>
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path:"sportsassessment",
|
path: "sportsassessment",
|
||||||
element:<SportPageProvider>
|
element: <SportPageProvider>
|
||||||
<SportPage></SportPage>
|
<SportPage></SportPage>
|
||||||
</SportPageProvider>
|
</SportPageProvider>
|
||||||
}
|
}
|
||||||
|
@ -100,10 +102,8 @@ export const routes: CustomRouteObject[] = [
|
||||||
element: <AdminLayout></AdminLayout>,
|
element: <AdminLayout></AdminLayout>,
|
||||||
children: adminRoute.children,
|
children: adminRoute.children,
|
||||||
}
|
}
|
||||||
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -100,7 +100,7 @@ server {
|
||||||
# 仅供内部使用
|
# 仅供内部使用
|
||||||
internal;
|
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;
|
proxy_pass_request_body off;
|
||||||
|
|
|
@ -11,5 +11,11 @@
|
||||||
},
|
},
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
"author": "insiinc",
|
"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")
|
age Int? @default(22) @map("age")
|
||||||
sex Boolean? @default(true) @map("sex")
|
sex Boolean? @default(true) @map("sex")
|
||||||
absent Boolean? @default(false) @map("absent")
|
absent Boolean? @default(false) @map("absent")
|
||||||
|
|
||||||
trainSituations TrainSituation[]
|
trainSituations TrainSituation[]
|
||||||
position Position? @relation("StaffPosition", fields: [positionId], references: [id])
|
position Position? @relation("StaffPosition", fields: [positionId], references: [id])
|
||||||
positionId String? @map("position_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