1 组件层级,A跳转B,B嵌套C,C嵌套D,C嵌套E!
2 话不多说,直接上代码!
1 A组件history.push传递
// 编辑
const handleEdit = async (record: Task) => {
setTaskObj(record)
const res = await TaskController.getDetailUsingGET({ id: record.id as number })
history.push({
pathname: '/warfare/taskList/createTask',
state: res.content,
});
};
2 B组件 location进行接受, 表单里基本信息的数据formContentRef?.current?.basicInfoForm,目标,兵力,航线数据formContentRef?.current?.taskData
const CreateTask: React.FC<RouteChildrenProps> = ({ location }) => {
const locationState = location.state as any
const formContentRef = useRef() as any;
const [planeTypeList, setPlaneTypeList] = useState([]);
const [modelList, setModelList] = useState([]);
const [newLocationState, setNewLocationState] = useState();
const { dicts } = useSelector((state: any) => {
return state.warfare;
});
useEffect(() => {
if (locationState) {
const list = { ...locationState, taskTargetNos: locationState.taskTargetDetails }
setNewLocationState(list)
}
}, [locationState]);
useEffect(() => {
if (dicts && dicts.length) {
setPlaneTypeList(getFindList2(dicts, 'plane_type'));
setModelList(getFindList2(dicts, 'model_type'));
}
}, [dicts]);
// 保存
const onFinish = async () => {
console.log(formContentRef?.current)
formContentRef?.current?.basicInfoForm?.validateFields().then(async () => {
const taskData = formContentRef?.current?.taskData
if (taskData?.taskTargetNos.length <= 0) {
return message.error('请添加目标');
}
if (taskData?.taskTroopConfigs.length <= 0) {
return message.error('请添加兵力');
}
if (taskData?.taskTroopConfigs.length <= 0) {
return message.error('请添加兵力');
}
if (!taskData?.routeConfig) {
return message.error('请添加航线');
}
// 将目标[数组格式]转化字符串[逗号分隔]
const params = getTaskData(taskData)
try {
if (locationState) {
const res = await TaskController.updateUsingPUT(params);
if (res.code === 200) {
message.success('修改成功');
history.goBack()
} else {
message.error(res.msg);
}
} else {
const res = await TaskController.addUsingPOST(params);
if (res.code === 200) {
message.success('新增成功');
history.goBack()
} else {
message.error(res.msg);
}
}
} catch (error: any) {
errorNotice(error);
}
})
};
return (
<div className={styles.warfareCreateTask}>
<LjPageHeader title={locationState ? '编辑任务' : '新增任务'} extra={<><div className={styles.pageNavActions}>
<Button style={{
backgroundColor: 'rgba(47, 85, 151, 1)',
border: 'none',
color: '#fff'
}} type="primary" loading={false} onClick={onFinish}>保存</Button>
<Button style={{
backgroundColor: '#fff',
borderColor: 'rgba(47, 85, 151, 1)',
color: 'rgba(47, 85, 151, 1)'
}} type="primary" onClick={() => { history.goBack() }} >取消</Button>
</div></>} />
<div className={styles.pageContainer}>
<div className={styles.formContainer}>
{modelList.length > 0 && <FormContent ref={(ref) => {
if (ref) {
formContentRef.current = ref;
}
}} modelList={modelList} planeTypeList={planeTypeList} newLocationState={newLocationState} />}
</div>
<div className={styles.cesiumContainer}>
<Cesium />
</div>
</div>
</div>
)
}
export default CreateTask
3 C组件 forwardRef声明组件的格式,useImperativeHandle暴露组件方法和数据给父组件,AHooks.useSetState数据为响应式,AHooks.useUpdateEffect数据被更新
export type Props = {
ref: any;
modelList: any[];
planeTypeList: any[];
newLocationState: any,
};
const demoTask: Task = {
no: '',
taskName: '',
startTime: '',
endTime: '',
taskDesc: '',
taskTargetNos: [], // 目标
taskTroopConfigs: [] //
}
export const FormContent = forwardRef((props: Props, ref) => {
const { modelList, planeTypeList, newLocationState } = props
// const { state, sender } = useWarfareEmitPickPoint(CesiumEmitSubscriber.PAGE)
const [visibleTarget, setVisibleTarget] = useState(false);
const [visibleAircraft, setVisibleAircraft] = useState(false);
const [dataListTarget, setDataListTarget] = useState<any[]>([]);
const [dataListAircraft, setDataListAircraft] = useState<any[]>([]);
// 响应式的任务数据,[如果新增就是demoTask,编辑就是newLocationState]
const [taskData, setTaskData] = AHooks.useSetState<Task>(newLocationState ?
{ ...newLocationState, startTime: moment(newLocationState.startTime), endTime: moment(newLocationState.endTime) } as Task
: { ...demoTask } as Task)
/**
* 记录所有已被选择的目标ID
* 注意:因为后端Task.targetNos,新增修改接收是一个字符串(逗号分割),详情查询返回而是目标数组
*/
const targetsHandler = AHooks.useCreation(() => {
return {
taskTargetNos: taskData.taskTargetNos || [],
onOkTarget: (selectedRow: any[]) => {
// 根据目标号去重
const targets = getReductList(targetsHandler.taskTargetNos, selectedRow, 'targetNo')
setTaskData({
taskTargetNos: targets
})
setVisibleTarget(false)
},
removeTarget: (targetNo: string) => {
const targets = targetsHandler.taskTargetNos.filter((item) => {
return item.targetNo !== targetNo
})
setTaskData({
taskTargetNos: targets
})
},
}
}, [taskData, setTaskData])
/**
* 配置
*/
const TroopConfigsHandler = AHooks.useCreation(() => {
return {
taskTroopConfigs: taskData.taskTroopConfigs || [],
onOkAircraft: (selectedRow: any[]) => {
// 根据目标号去重
const troop = getReductList(TroopConfigsHandler.taskTroopConfigs, selectedRow, 'planeNo')
// 兵配置的字段自定义
const list = getAircraftList(troop, modelList, planeTypeList)
setTaskData({
taskTroopConfigs: list
})
setVisibleAircraft(false)
},
removeAircraft: (planeNo: string) => {
const troop = TroopConfigsHandler.taskTroopConfigs.filter((item) => {
return item.planeNo !== planeNo
})
setTaskData({
taskTroopConfigs: troop
})
},
}
}, [taskData, setTaskData])
/**
* 航线更新
*/
const CourseHandler = AHooks.useCreation(() => {
return {
routeConfig: taskData.routeConfig || {},
onAddCourse: () => {
const list = {
ranging: 10, //
routeInfo: JSON.stringify([{ longitude: '121', latitude: '81' }, { longitude: '122', latitude: '82' }, { longitude: '123', latitude: '83' }])
}
setTaskData({
routeConfig: list
})
},
removeRouteConfig: (index: number) => {
const routeInfo = CourseHandler.routeConfig?.routeInfo as any
const routeInfoList = getKey(JSON.parse(routeInfo)) || []
const course = routeInfoList.filter((item) => {
return item.key !== index
})
const list = {
ranging: 10, //
routeInfo: JSON.stringify(course)
}
setTaskData({
routeConfig: list
})
},
}
}, [taskData, setTaskData])
// 任务数据被更新
AHooks.useUpdateEffect(() => {
// console.group('FormContent')
// console.log('taskData', taskData)
// console.groupEnd()
}, [taskData])
// 基本信息表格
const [basicInfoForm] = ProForm.useForm()
useImperativeHandle(ref, () => {
return {
taskData,
basicInfoForm,
};
});
// 弹窗目标列表
const onAdd = async () => {
const res = await TargetController.searchUsingPOST({ attr: '1' })
setDataListTarget(res.content as any[])
setVisibleTarget(true)
}
const onCancelTarget = () => {
setVisibleTarget(false)
}
// 弹窗列表
const onAddAircraft = async () => {
const res = await planeInfoSearch({})
setDataListAircraft(res.content as any[])
setVisibleAircraft(true)
}
const onCancelAircraft = () => {
setVisibleAircraft(false)
}
return <div className={styles.warfareCreateTaskFormContent}>
<div className={styles.basicInfo}>
<LjFormCard title={<div>基本信息</div>} extra={<></>}>
<BasicInfo formRef={basicInfoForm} data={taskData} onChange={setTaskData} />
</LjFormCard>
</div>
<div className={styles.targetSelect}>
<LjFormCard title={<div>目标选择</div>} extra={<><a onClick={onAdd}>添加目标</a></>}>
{
taskData?.taskTargetNos && taskData?.taskTargetNos?.length > 0 ? <TargetSelect taskTargetNos={taskData.taskTargetNos as Target[]} removeTarget={targetsHandler.removeTarget} /> : <Empty />
}
</LjFormCard>
</div>
<div className={styles.troopsDeploy}>
<LjFormCard title={<div>兵配置</div>} extra={<><a onClick={onAddAircraft}>添加</a></>}>
{
taskData?.taskTroopConfigs && taskData?.taskTroopConfigs?.length > 0 ? <TroopsDeploy taskTroopConfigs={taskData.taskTroopConfigs ?? []} removeAircraft={TroopConfigsHandler.removeAircraft} /> : <Empty />
}
</LjFormCard>
</div>
<div className={styles.routeConfig}>
<LjFormCard title={<div>航线更新</div>} extra={<><a onClick={CourseHandler.onAddCourse}>添加航线</a></>}>
{
taskData?.routeConfig ? <RouteConfig routeConfig={taskData?.routeConfig} removeRouteConfig={CourseHandler.removeRouteConfig} /> : <Empty />
}
</LjFormCard>
</div>
<div className={styles.taskDistribution}>
<LjFormCard title={<div>任务分配</div>} extra={<></>}>
<TaskDistribution taskTroopConfigs={taskData.taskTroopConfigs ?? []} />
</LjFormCard>
</div>
{visibleTarget && <TargetTableModal taskData={taskData} onCancel={onCancelTarget} onOk={targetsHandler.onOkTarget} dataList={dataListTarget} />}
{visibleAircraft && <AircraftTableModel taskData={taskData} onCancel={onCancelAircraft} onOk={TroopConfigsHandler.onOkAircraft} dataList={dataListAircraft} />}
</div>
})
D 组件
import { ProForm, ProFormText, ProFormSelect, ProFormField, ProFormTextArea } from '@ant-design/pro-components';
import { useSelector } from 'umi';
import moment from 'moment';
import { errorNotice } from '@/utils/tip';
import { useState, useEffect } from 'react';
import type { FormInstance } from 'antd';
import { message } from 'antd';
import { DatePicker } from 'antd';
import * as AHooks from 'ahooks'
import React from 'react'
import styles from './style.module.less'
import type { Task } from '@/services/WarfareSystem/TaskController';
import type { SetState } from 'ahooks/lib/useSetState';
import { getFindList2 } from '@/utils/simulator';
import { TaskController } from '@/services/WarfareSystem/TaskController';
import type { RangePickerProps } from 'antd/es/date-picker';
type Props = {
data?: Task,
formRef: FormInstance<any>,
onChange: SetState<Task>
}
const BasicInfo: React.FC<Props> = (props) => {
const formRef = AHooks.useCreation<FormInstance<any>>(() => props.formRef, [props.formRef])
const [taskTypeList, setTaskTypeList] = useState([]);
const { dicts } = useSelector((state: any) => {
return state.warfare;
});
useEffect(() => {
if (dicts && dicts.length) {
setTaskTypeList(getFindList2(dicts, 'task_type'));
}
}, [dicts]);
/**
* 数据动态改变
*/
const onChange = AHooks.useMemoizedFn(props.onChange)
/**
* 默认数据
*/
const initFormData = props.data
// 校验任务编号
const getValidNO = async (e: any) => {
try {
const res = await TaskController.validNOUsingGET({ no: e?.target?.value });
if (!res.content) {
message.error('任务编号重复,请重新输入')
}
} catch (error: any) {
errorNotice(error);
}
}
const range = (start: number, end: number) => {
const result = [];
for (let i = start; i < end; i++) {
result.push(i);
}
return result;
};
// 限制年月日
const disabledDate: RangePickerProps['disabledDate'] = (current: any) => {
return current && current < moment(initFormData?.startTime);
};
// 限制时分秒
const disabledDateTime = () => {
if (initFormData?.startTime) {
const time = moment(initFormData?.startTime).format('YYYY-MM-DD HH:mm:ss')
console.log(time)
const newTime = time?.split(' ')[1].split(':') as any
console.log(newTime)
return {
disabledHours: () => range(0, 24).splice(0, newTime[0]),
disabledMinutes: () => range(0, 60).splice(0, newTime[1]),
disabledSeconds: () => range(0, 60).splice(0, newTime[2] * 1 + 1),
}
}
return {}
}
return (
<div className={styles.basicInfoContent}>
<ProForm<{
name: string;
company?: string;
useMode?: string;
formRef?: FormInstance<any>
}>
form={formRef}
submitter={
{
render: () => <></>
}
}
layout={'horizontal'}
grid={true}
rowProps={{
gutter: [24, 0],
}}
initialValues={{
...initFormData
}}
onValuesChange={(changedValues) => {
/**
* 更新数据
*/
onChange(changedValues)
}}
>
<ProFormText colProps={{ span: 12 }} name="no" label="任务编号" fieldProps={{ onChange: getValidNO }} rules={[{ required: true, message: '请输入' }]} />
<ProFormText colProps={{ span: 12 }} name="taskName" label="任务名称" rules={[{ required: true, message: '请输入' }]} />
<ProFormSelect
colProps={{ span: 12 }}
label="任务类型"
name="taskType"
placeholder='请选择'
options={taskTypeList}
rules={[{ required: true, message: '请选择' }]}
/>
<ProFormText colProps={{ span: 12 }} name="taskArea" label="任务区域" rules={[{ required: true, message: '请输入' }]} />
<ProFormField colProps={{ span: 12 }}
label="开始时间"
name="startTime"
rules={[{ required: true, message: '请选择' }]}>
<DatePicker showTime style={{ width: '100%' }} allowClear={false} />
</ProFormField>
<ProFormField colProps={{ span: 12 }}
label="结束时间"
name="endTime"
rules={[{ required: true, message: '请选择' }]}>
<DatePicker showTime style={{ width: '100%' }} disabledDate={disabledDate} disabledTime={disabledDateTime} allowClear={false} disabled={!initFormData?.startTime} />
</ProFormField>
<ProFormTextArea
colProps={{ span: 24 }}
name="taskDesc"
label="任务描述"
/>
</ProForm>
</div>
)
}
export default BasicInfo
E组件
import type { AmmunitionConfig, TaskTroopConfigDTO } from '@/services/WarfareSystem/TaskController'
import { useSelector } from 'umi';
import React, { useState, useEffect } from 'react'
import type { Column } from '@ljmp/ljmp-antd/es';
import { LjOverviewTable } from '@ljmp/ljmp-antd';
import styles from './style.module.less'
import * as AHooks from 'ahooks'
import imageDeleteBtn from './deleteBtn.png'
import planIcon from './planIcon.svg'
import { Input, InputNumber } from 'antd';
import _ from 'lodash';
import ImageAddBtn from './addBtn.png'
import TableModal from '@/pages/Warfare/AnalysisList/TableModal';
import { getFindList2, getLabelValue, getValue, getReductList } from '@/utils/simulator';
import { cabinSearch, weaponSearch } from '@/services/dataManage/api';
type Props = {
taskTroopConfigs: TaskTroopConfigDTO[],
removeAircraft: (targetNo: string) => void;
}
const TroopsDeploy: React.FC<Props> = (props) => {
const { taskTroopConfigs = [], removeAircraft } = props
// 操作的目标targetId
const [targetTroopId, setTargetTroopId] = React.useState<number>()
const [visible, setVisible] = useState(false);
const [cabinList, setCabinList] = useState([]);
const [weaponTypeList, setWeaponTypeList] = useState([]);
const [materialTypeList, setMaterialTypeList] = useState([]);
const [dataList, setDataList] = useState<any>([]);
const [dutyStatusList, setDutyStatusList] = useState([]);
const [ammunitionList, setModelAmmunitionList] = useState<any>();
const { dicts } = useSelector((state: any) => {
return state.warfare;
});
useEffect(() => {
if (dicts && dicts.length) {
setWeaponTypeList(getFindList2(dicts, 'weapon_type'));
setMaterialTypeList(getFindList2(dicts, 'material_type'));
setDutyStatusList(getFindList2(dicts, 'duty_status'));
}
}, [dicts]);
useEffect(() => { // 室
const getData = async () => {
const resCabin = await cabinSearch({});
setCabinList(getLabelValue(resCabin.content));
}
getData();
}, []);
React.useEffect(() => {
// console.log(taskTroopConfigs)
}, [taskTroopConfigs])
/**
* 强制刷新组件
*/
const update = AHooks.useUpdate()
// 表格的删除函数
const deleteRender = AHooks.useMemoizedFn((value, record, index) => {
if (targetTroopId == undefined) return
setTimeout(() => {
const targetTask = taskTroopConfigs.find(item => item.planeId === targetTroopId)!
if (!targetTask) return
targetTask.ammunitionConfig = _.cloneDeep(targetTask.ammunitionConfig)?.filter((item) => item.weaponId !== record.weaponId) || []
console.log(taskTroopConfigs)
update()
});
})
/**
* 点击加号向表格中插入一条数据
*/
const addAmmunition = AHooks.useMemoizedFn((targetId: number) => {
const targetTask = taskTroopConfigs.find(item => item.planeId === targetId)
// return
if (!targetTask) return
const newAmmunitionConfig = _.cloneDeep(targetTask.ammunitionConfig || [])
targetTask.ammunitionConfig = [...newAmmunitionConfig, {
amount: 0,
name: '',
weaponId: new Date().valueOf()
}]
update()
})
/**
*
*/
const add = AHooks.useMemoizedFn(async (targetId: number) => {
console.log(targetTroopId, taskTroopConfigs)
const targetTask = taskTroopConfigs.find(item => item.planeId === targetTroopId)
console.log(targetTask)
if (!targetTask) return
setModelAmmunitionList(targetTask?.ammunitionConfig ?? [])
const res = await weaponSearch({ resourceType: 'platform_weapon' });
setDataList(res.content)
setVisible(true)
})
// 弹窗选择后-列表
const onOk = AHooks.useMemoizedFn(async (selectedRow: any[]) => {
const targetTask = taskTroopConfigs.find(item => item.planeId === targetTroopId)
if (!targetTask) return
// 新添加的数据加weaponId
const list = selectedRow.map((item) => {
const obj = {
...item,
weaponId: item.id
}
return obj
})
// 新数据和旧数据取并集过滤
const arr = getReductList(list, targetTask?.ammunitionConfig ?? [], 'weaponId')
const newAmmunitionConfig = _.cloneDeep([...arr] || [])
targetTask.ammunitionConfig = [...newAmmunitionConfig]
setVisible(false)
})
const onCancel = () => {
setVisible(false)
}
const del = (row: TaskTroopConfigDTO) => {
removeAircraft(row.planeNo as string)
}
const columns: Column<AmmunitionConfig>[] = AHooks.useCreation(() => {
return [
{
title: '名称',
dataIndex: 'name',
render(value, record, index) {
return <Input type={'string'} disabled defaultValue={value ?? '-'} onChange={(e) => {
record.name = e.target.value ?? ''
}} />
}
},
{
title: '数量',
dataIndex: 'amount',
render(value, record, index) {
return <InputNumber min={0} defaultValue={value ?? 0} onChange={(newValue) => {
record.amount = newValue
}} />
}
},
{
title: '操作',
dataIndex: 'operation',
render(value, record, index) {
return <a onClick={() => deleteRender(value, record, index)}>删除</a>;
},
},
];
}, [deleteRender])
return (
<div className={styles.troopsDeployContent}>
{taskTroopConfigs.map((item) => {
return <div className={styles.troopsItem} key={item.id} onMouseEnter={() => { setTargetTroopId(item.planeId) }}>
<div className={styles.planeHeader}>
<img src={planIcon} alt="机图标" />
<span className={styles.planeNo}>{item.planeNo || '-'}</span>
<span className={styles.planeName}>{item.planeName || '-'}</span>
<img className={styles.imageDeleteBtn} src={imageDeleteBtn} onClick={() => { del(item) }} />
</div>
<div className={styles.planeContent}>
<div className={styles.planeInfoItem}>
<span>状态:</span>
<span>{getValue(item.dutyStatus as string, dutyStatusList) ?? '-'}</span>
</div>
<div className={styles.planeInfoItem}>
<span>库:</span>
<img className={styles.addBtn} onClick={() => {
// addAmmunition(item.id!)
add(item.id!)
}} src={ImageAddBtn} />
</div>
<div className={styles.ammunitionConfig}>
<LjOverviewTable<AmmunitionConfig> columns={columns} dataSource={item.ammunitionConfig ?? []} />
</div>
</div>
</div>
})}
{visible && <TableModal onCancel={onCancel} onOk={onOk} ammunitionList={ammunitionList} dataList={dataList} weaponTypeList={weaponTypeList} materialTypeList={materialTypeList} cabinList={cabinList} analysisType={'药'} />}
</div>
)
}
export default TroopsDeploy
TableModal弹窗
import React, { useState, useEffect } from 'react';
import { Table } from 'antd';
import { LjModal } from '@ljmp/ljmp-antd';
import type { Props } from './type';
import type { ColumnsType } from 'antd/es/table';
import { getValue } from '@/utils/simulator';
import type { WeaponList } from '@/services/dataManage/model';
import type { AmmunitionConfig } from '@/services/WarfareSystem/TaskController'
const AddModal = ({ onCancel, onOk, ammunitionList, dataList, weaponTypeList, materialTypeList, cabinList, analysisType }: Props) => {
const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([]);
const [selectedRow, setSelectedRow] = useState<React.Key[]>([]);
console.log(ammunitionList)
useEffect(() => {
if (ammunitionList.length > 0) {
const newArr = ammunitionList.map((item: AmmunitionConfig) => { return item.weaponId as number })
setSelectedRowKeys(newArr || [])
}
}, [ammunitionList]);
const onSelectChange = (newSelectedRowKeys: React.Key[], selectedRows: any[]) => {
setSelectedRowKeys(newSelectedRowKeys);
setSelectedRow(selectedRows)
};
const rowSelection = {
selectedRowKeys,
onChange: onSelectChange,
};
const columns: ColumnsType<WeaponList> = [
{
title: '名称',
dataIndex: 'name',
},
{
title: '类型',
dataIndex: 'weaponType',
render: (_, record) => {
if (analysisType === '药') {
return <span>{getValue(record.weaponType, weaponTypeList)}</span>
}
return <span>{getValue(record.weaponType, materialTypeList)}</span>
}
},
{
title: '编号',
dataIndex: 'cabinNo',
render: (_, record) => (
<span>{getValue(record.cabinNo, cabinList)}</span>
),
},
{
title: '总数量',
dataIndex: 'weaponTotal',
},
{
title: '可用数量',
dataIndex: 'canUse',
},
{
title: '最低储备数量',
dataIndex: 'minimum',
},
];
return (
<LjModal title={analysisType === '药' ? '药' : '物资'} visible onCancel={onCancel} width={800} onOk={() => onOk(selectedRow)}>
<Table rowKey='id' rowSelection={rowSelection} dataSource={dataList} columns={columns} />
</LjModal>
);
};
export default AddModal;