assistant-todo/src/ui/task/project/DetailModelForm.tsx

779 lines
36 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import {PlusOutlined, QuestionCircleOutlined} from '@ant-design/icons';
import {
ModalForm,
ProForm,
ProFormDateTimeRangePicker,
ProFormSelect,
ProFormText, ProFormTextArea, ProFormTreeSelect,
} from '@ant-design/pro-components';
import {Button, Cascader, CascaderProps, Form, message, Popconfirm, Select, Space, Spin} from 'antd';
import React, {Fragment, useEffect, useState} from "react";
import {
addTask, deleteTask, getTask,
getTaskTreeResult,
OPERATION_BUTTON_TYPE, TASK_TYPE,
taskPriorityList,
taskStateList, updateTask
} from "@/lib/task/project/data";
import {CascaderOption, DataType} from "@/lib/definitions";
import dayjs, {Dayjs} from "dayjs";
import DiaryOption from "@/components/DiaryOption";
import ShareOption from "@/components/ShareOption";
import StepSort from "@/components/StepSort";
import SettingCron from "@/components/SettingCron";
import ClickRecord from "@/components/ClickRecord";
import {onceConsumerRead} from "@/utils/codeToReadName";
import style from "@/ui/task/project/DetailModelForm.module.css"
import TeamMember from "@/components/TeamMember";
import {useSearchParams} from "next/dist/client/components/navigation";
import TaskRemindComponent from "@/components/TaskRemindComponent";
export type DetailModelFormProps = {
// 当前内容id
itemId?: string,
pid?: string,
taskType?: string,
// 祖宗任务id
pPid?: string,
// 操作id
operationId: OPERATION_BUTTON_TYPE,
// 标题描述
description: string,
// 任务内容描述
taskContent?: string,
// 是否打开界面,用于非按钮操作
open: boolean,
// 使用按钮操作
haveButton: boolean,
closeOpen?: () => void,
expectedStartTime?: Dayjs,
expectedEndTime?: Dayjs,
// 重新加载数据
reloadData?: () => void
}
export type PidSelectTree = {
label: string;
value: string;
pid: string;
fId: string | undefined,
fName: string | undefined,
children?: PidSelectTree[]
}
export type ParentTaskVO = {
pid: string,
pName: string | undefined,
fId: string | undefined,
fName: string | undefined,
}
export const DetailModelForm: React.FC<DetailModelFormProps> = (props) => {
const [form] = Form.useForm<DataType>();
const searchParams = useSearchParams();
const [requestTask, setRequestTask] = useState<DataType>()
console.log("DetailModelForm:props:", props, requestTask)
const [editFormDisable, setEditFormDisable] = useState(props.operationId === OPERATION_BUTTON_TYPE.DETAIL)
// 团队第一层 pid必须为0
const [taskType, setTaskType] = useState('0')
const [spinning, setSpinning] = useState(true)
const [operationRequest, setOperationRequest] = useState(false)
const [onceConsumeChange, setOnceConsumeChange] = useState<string[]>(["1", "h"])
const [defaultPTask, setDefaultPTask] = useState<ParentTaskVO>()
const [selectPid, setSelectPid] = useState<string>();
// key为当前任务idvalue为当前任务作为父任务信息
const [pTaskMap, setPTaskMap] = useState<Record<string, ParentTaskVO>>()
const [remindTypeList, setRemindTypeList] = useState<string[]>([])
function initData() {
form.resetFields()
setTaskType('0')
setOnceConsumeChange(["1", "h"])
setRequestTask(undefined);
setDefaultPTask({
pid: '0',
pName: undefined,
fId: undefined,
fName: undefined,
})
}
const [options, setOptions] = useState<CascaderOption[]>(Array.from({length: 60}, (_, i) => ({
// label: i.toString().padStart(2, '0'), // 显示为 "00", "01", ..., "59"
label: i,
value: i,
isLeaf: false,
})));
const cascaderLoadData = (selectedOptions: CascaderOption[]) => {
const targetOption = selectedOptions[selectedOptions.length - 1];
targetOption.children = [
{
label: "分钟",
value: "m",
isLeaf: true,
},
{
label: "小时",
value: "h",
isLeaf: true,
},
{
label: "天",
value: "d",
isLeaf: true,
}
]
setOptions([...options])
}
const cascaderOnChange: CascaderProps<CascaderOption>['onChange'] =
(value: (string | number)[], selectedOptions: CascaderOption[]) => {
if (value.length > 0) {
console.log({value})
setOnceConsumeChange(value.map(valueMap => valueMap.toString()))
} else {
setOnceConsumeChange([])
}
};
const changeValueToLabel = () => {
const onceConsumeShow = ["1", "小時"];
if (onceConsumeChange && onceConsumeChange.length == 2) {
if (onceConsumeChange[1] == 'm') {
onceConsumeShow[1] = '分钟'
} else if (onceConsumeChange[1] == 'h') {
onceConsumeShow[1] = '小时'
} else if (onceConsumeChange[1] == 'd') {
onceConsumeShow[1] = '天'
}
return onceConsumeShow;
} else {
return []
}
}
useEffect(() => {
if (searchParams && searchParams.get("pid")) {
setDefaultPTask({
pid: searchParams.get("pid")!,
pName: searchParams.get("pName")!,
fId: searchParams.get("fId")!,
fName: searchParams.get("fName")!,
})
setSelectPid(searchParams.get("pid")!)
} else {
setDefaultPTask({
pid: '0',
pName: undefined,
fId: undefined,
fName: undefined,
})
setSelectPid('0')
}
}, [searchParams]);
useEffect(() => {
if (props.itemId != undefined && (
props.operationId === OPERATION_BUTTON_TYPE.DETAIL || props.operationId === OPERATION_BUTTON_TYPE.UPDATE)) {
getTask(props.itemId).then(task => {
console.log('DetailModelForm:getTask(props.itemId)', props.itemId, task);
if (task.status.success) {
// setTaskMessage(task.data)
task.data.state = taskStateList.find(taskState => taskState.code === task.data.state?.toString())!.name;
task.data.priority = taskPriorityList.find(taskPriority => taskPriority.code === task.data.priority?.toString())!.name;
task.data.actualTimeRange = [task.data.actualStartTime ? dayjs(task.data.actualStartTime) : undefined,
task.data.actualEndTime ? dayjs(task.data.actualEndTime) : undefined];
task.data.expectedTimeRange = [task.data.expectedStartTime ? dayjs(task.data.expectedStartTime) : undefined,
task.data.expectedEndTime ? dayjs(task.data.expectedEndTime) : undefined];
form.setFieldsValue(task.data)
if (task.data.pid == "0") {
form.setFieldValue("pid", undefined)
} else if (searchParams) {
form.setFieldValue("pid", task.data.pName)
}
if (task.data.taskType) {
setTaskType(task.data.taskType)
}
setRequestTask(task.data)
setDefaultPTask({
pid: task.data.pid,
pName: task.data.pName,
fId: task.data.fId,
fName: task.data.fName,
})
if (task.data.onceConsume) {
setOnceConsumeChange(task.data.onceConsume.split(","))
}
console.log("form.setFieldsValue(task.data)" + JSON.stringify(task.data))
} else {
message.error(task.status.message);
initData()
props.reloadData?.()
}
}).finally(() => {
setSpinning(false)
}
)
} else if (props.operationId === OPERATION_BUTTON_TYPE.ADD || props.operationId === OPERATION_BUTTON_TYPE.ADD_CHILD) {
let data = {
'expectedTimeRange': [props.expectedStartTime ? props.expectedStartTime : dayjs(), props.expectedEndTime],
'pid': props.pid,
'description': props.taskContent,
'name': props.taskContent && props.taskContent?.length > 10 ? props.taskContent.substring(0, 10) : props.taskContent,
};
form.setFieldsValue(data)
setSpinning(false)
}
}, [props])
function childReduce(child: DataType[]): PidSelectTree[] {
// 使用示例
const {trees, taskMap} = childReduceInner(child);
setPTaskMap(taskMap); // 一次性设置
return trees;
}
function childReduceInner(child: DataType[]): { trees: PidSelectTree[]; taskMap: Record<string, ParentTaskVO> } {
const result: PidSelectTree[] = [];
const parentTaskMap: Record<string, ParentTaskVO> = {};
child.forEach(data => {
const resultData: PidSelectTree = {
label: data.name,
value: data.id,
pid: data.pid,
fId: data.fId,
fName: data.fName
};
if (data.children) {
const childResult = childReduceInner(data.children);
resultData.children = childResult.trees;
Object.assign(parentTaskMap, childResult.taskMap);
}
parentTaskMap[data.id] = {
pid: data.id,
pName: data.name,
fId: data.fId,
fName: data.fName
};
result.push(resultData);
});
return {trees: result, taskMap: parentTaskMap};
}
// 错误闭包使用
// function childReduce(child: DataType[]): PidSelectTree[] {
// const result: PidSelectTree[] = [];
// const parentTaskMap: Record<string, ParentTaskVO> = {};
// child.map(data => {
// const resultData: PidSelectTree = {
// label: data.name, value: data.id, pid: data.pid,
// fId: data.fId, fName: data.fName
// };
// if (data.children) {
// resultData.children = childReduce(data.children);
// }
// parentTaskMap[data.id] = {pid: data.id, pName: data.name, fId: data.fId, fName: data.fName};
// result.push(resultData);
// })
// setPTaskMap({...pTaskMap, ...parentTaskMap})
// return result;
// }
// function childReduce(child: DataType[]): PidSelectTree[] {
// const result: PidSelectTree[] = [];
// const parentTaskMap: Record<string, ParentTaskVO> = {};
//
// child.forEach(data => {
// const resultData: PidSelectTree = {
// label: data.name,
// value: data.id,
// pid: data.pid,
// fId: data.fId,
// fName: data.fName
// };
// if (data.children) {
// resultData.children = childReduce(data.children);
// }
// parentTaskMap[data.id] = {
// pid: data.id,
// pName: data.name,
// fId: data.fId,
// fName: data.fName
// };
// result.push(resultData);
// });
//
// setPTaskMap(prev => ({ ...prev, ...parentTaskMap }));
// return result;
// }
// 如果不是添加任务需要回显
// Form 当中的 initialValues
// ProComponents 底层也是封装的 antd ,所以用法也和 antd 相同。注意 initialValues 不能被 setState 动态更新,
// 所以你需要用 setFieldsValue 来更新。 initialValues 只在 form 初始化时生效且只生效一次,如果你需要异步加载,
// 推荐使用 request或者 initialValues ? <Form/> : null
return (
<Fragment>
<Spin spinning={spinning} fullscreen/>
<ModalForm<DataType>
title={props.description}
open={!spinning && props.open && !props.haveButton}
trigger={props.haveButton ?
<Button type="primary">
<PlusOutlined/>
{props.description}
</Button> : undefined
}
form={form}
loading={operationRequest}
autoFocusFirstInput
modalProps={{
destroyOnClose: true,
maskClosable: false,
onCancel: () => {
initData()
// props.reloadData?.();
props.closeOpen?.();
},
}}
readonly={editFormDisable}
submitter={props.itemId !== undefined && props.itemId !== '-1' ? {
render: (prop, defaultDoms) => {
console.log("submitter render: ", {prop})
let result = [
editFormDisable ? <Button
key="edit"
onClick={() => {
// props.submit();
setEditFormDisable(false)
}}
></Button> : undefined,
props.operationId === OPERATION_BUTTON_TYPE.DETAIL || props.operationId === OPERATION_BUTTON_TYPE.UPDATE ?
<Popconfirm
key='delete'
title="删除任务"
description="确认要删除任务?"
icon={<QuestionCircleOutlined style={{color: 'red'}}/>}
okText="确认"
cancelText="取消"
onConfirm={() => {
if (props.itemId !== undefined) {
deleteTask(props.itemId).then((response => {
console.log('response', response)
if (response.status.success) {
message.success("删除任务成功:" + response.data)
initData()
props.reloadData?.()
}
}));
}
}}
>
<Button type="primary" key="delete" danger>
</Button>
</Popconfirm> : undefined,
requestTask && requestTask.id ?
<DiaryOption key="diary" taskId={requestTask.id}
taskName={requestTask.name}/> : undefined,
]
// 非新增或者编辑状态不展示
if (!editFormDisable) {
result.push(...defaultDoms)
} else {
if (editFormDisable && taskType == '1') {
result.push(<ShareOption taskId={props.itemId!}/>)
result.push(<TeamMember taskId={props.itemId!} closeOpen={props.closeOpen}
reloadData={props.reloadData}/>)
}
result.push(<Button type="primary" key="close"
onClick={() => props.closeOpen?.()}></Button>)
}
if (taskType == '2') {
result.push(<StepSort taskId={props.itemId!} stepList={requestTask?.stepList || []}/>)
}
if (taskType == '3' && requestTask) {
result.push(<ClickRecord taskId={props.itemId!} taskName={requestTask!.name}
reloadData={props.reloadData}
onceConsume={requestTask!.onceConsume}/>)
}
return result;
},
} : {
render: (prop, defaultDoms) => {
console.log("submitter render: ", {prop}, {props}, {taskType}, {defaultDoms})
// if ( prop.searchConfig) {
// if (taskType == '1') {
// prop.searchConfig.submitText = "创建团队"
// }else {
// prop.searchConfig.submitText = "确认"
// }
// }
// return defaultDoms;
const result = defaultDoms.filter(defaultButton => defaultButton.key == 'rest');
// if (taskType == '2') {
// result.push(<StepSort taskId={props.itemId!} stepList={requestTask?.stepList||[]}/>)
// }
result.push(<Button type="primary" key="create-team" loading={operationRequest}
onClick={() => form.submit()}>{taskType == '1' ? "创建团队" : "确认"}
</Button>)
return result;
}
}
}
onFinish={async (values) => {
console.log('Received values of form: ', values, defaultPTask, {...requestTask, ...values});
setOperationRequest(true)
if (requestTask) {
const {sortNo} = requestTask;
values.sortNo = sortNo;
}
values.pid = defaultPTask!.pid;
values.fId = defaultPTask!.fId;
values.fName = defaultPTask!.fName;
if (values.pid === undefined) {
values.pid = '0'
}
if (values.expectedTimeRange?.[0] != undefined) {
values.expectedStartTime = dayjs(values.expectedTimeRange[0]).format()
}
if (values.expectedTimeRange?.[1] != undefined) {
values.expectedEndTime = dayjs(values.expectedTimeRange[1]).format()
}
if (values.actualTimeRange?.[0] != undefined) {
values.actualStartTime = dayjs(values.actualTimeRange[0]).toDate()
}
if (values.actualTimeRange?.[1] != undefined) {
values.actualEndTime = dayjs(values.actualTimeRange[1]).toDate()
}
var result: boolean = false;
values.remindType = remindTypeList;
values.onceConsume = onceConsumeChange.join(',')
let state = taskStateList.find(taskState => taskState.name === values.state?.toString());
if (state) {
values.state = state.code
}
let priority = taskPriorityList.find(taskPriority => taskPriority.name === values.priority?.toString())
if (priority) {
values.priority = priority.code
}
console.log('update:values:', {values})
try {
// todo 修改
if (props.operationId === OPERATION_BUTTON_TYPE.UPDATE || (props.operationId === OPERATION_BUTTON_TYPE.DETAIL && !editFormDisable)) {
await updateTask(values).then(response => {
console.log('response', response)
if (response.data.status.success) {
message.success("修改任务成功:" + response.data.data.name)
// 树任务重新刷新
// 四象限任务重新刷新
// 如果可以直接更新列表而不请求。。。。。。
initData()
props.reloadData?.()
result = true
} else {
result = false
}
}
);
} else {
await addTask(values).then(response => {
console.log('response', response)
if (response.data.status.success) {
setRequestTask(response.data.data)
message.success(`添加计划${response.data.data.name}成功`)
// 树任务重新刷新
// 四象限任务重新刷新
// 如果可以直接更新列表而不请求。。。。。。
console.log('props.reloadData?.()', props.reloadData)
result = (taskType != '1')
if (result) {
initData()
}
props.reloadData?.()
} else {
result = false
}
}
);
}
} catch (e) {
setOperationRequest(false)
return false;
}
setDefaultPTask({
pid: '0',
pName: undefined,
fId: undefined,
fName: undefined,
})
setOperationRequest(false)
return result;
}}
>
<ProFormText width="sm" name="id" hidden={true} label="主键"/>
<ProFormText width="sm" name="code" initialValue={props.itemId} hidden={true} label="任务编码"/>
<ProFormText width="sm" name="pPid" initialValue={props.pPid} hidden={true} label="祖宗id"/>
<ProForm.Group>
<ProFormSelect
required={true}
options={TASK_TYPE.map(task =>
(task.value == '1' && (requestTask?.id)) ? {...task, disabled: true} : task)}
// (task.value == '1' ) ? {...task, disabled: true} : task)}
width="sm"
name="taskType"
label="任务类型"
initialValue={taskType}
// 不建议类型随意改变
disabled={requestTask && requestTask.id !== undefined}
// disabled={requestTask && requestTask.id !== undefined && requestTask.taskType == '1'}
onChange={(value: string, option) => {
setTaskType(value)
}}
rules={[
{
required: true,
message: "请输入计划类型"
}
]}
/>
<ProFormText
required={true}
hidden={taskType != '1'}
width="sm"
name="fName"
label="团队名称"
tooltip="最长为 10 位"
placeholder="请输入团队名称"
disabled={editFormDisable}
/>
<ProFormText
hidden={true}
name="cron"
/>
<ProFormTreeSelect
params={{pid: selectPid}}
hidden={taskType == '1'}
width="sm"
initialValue={defaultPTask?.pName}
request={(params, props) => {
console.log("request", {params}, {props})
return getTaskTreeResult(JSON.stringify(
{
pageSize: 1000,
pageNumber: 1,
data: [{code: 'pid', value: params.pid, operateType: '='},
// 如果父任务完成会导致父任务不展示
// {
// code: 'state',
// value: '8,9',
// operateType: 'IN'
// },
{code: '', value: true, operateType: "TREE"}]
}
)).then(result => childReduce(result.data.content))
}}
name="pid"
label="父级任务"
fieldProps={{
showSearch: true,
treeNodeFilterProp: 'label',
// filterTreeNode: (inputValue, treeNode) => {
// // 根据 label 进行过滤
// return treeNode.label!.toString().toLowerCase().includes(inputValue.toLowerCase());
// },
onSelect: (e, node) => {
console.log('onSelect', e, node, pTaskMap);
setDefaultPTask(pTaskMap?.[e] || {
pid: '0',
pName: undefined,
fId: undefined,
fName: undefined,
})
},
onClear: () => {
setDefaultPTask({
pid: '0',
pName: undefined,
fId: undefined,
fName: undefined,
})
if (selectPid != '0') {
setSelectPid("0")
}
}
}}
disabled={editFormDisable}
/>
<ProFormText
required={true}
width="sm"
name="name"
label="任务名称"
tooltip="最长为 10 位"
placeholder="请输入任务名称"
disabled={editFormDisable}
rules={[
{
required: true,
message: "请输入计划名称"
}, {
max: 10,
message: "名称长度不易超过10个字"
}
]}
/>
</ProForm.Group>
<ProFormTextArea
width="xl"
name="description"
label="任务描述"
tooltip="最长255个字"
placeholder="请输入任务描述"
disabled={editFormDisable}
/>
{taskType == '0' &&
<TaskRemindComponent remindTypeList={remindTypeList}
setRemindTypeList={(a: string[]) => setRemindTypeList(a)}
readonly={editFormDisable}/>}
<ProForm.Group>
<ProFormSelect
request={async () =>
taskPriorityList.map
(
taskState => {
return {'label': taskState.name, 'value': taskState.code}
}
)
}
width="sm"
name="priority"
label="任务优先级"
initialValue='3'
disabled={editFormDisable}
rules={[{required: true, message: "请选择计划优先级"}]}
/>
<ProFormSelect
width="sm"
options={taskStateList.map(taskState => {
return {'label': taskState.name, 'value': taskState.code}
})}
name="state"
label="任务状态"
initialValue='8'
disabled={editFormDisable}
rules={
[
{
required: true,
message: "请选择计划状态"
}
]
}
/>
</ProForm.Group>
<ProForm.Group>
{taskType != '3' && <ProFormDateTimeRangePicker
initialValue={[dayjs(), undefined]}
name="expectedTimeRange"
label="期望时间"
fieldProps={{
showTime: {
format: 'HH:mm',
},
format: "YYYY-MM-DD HH:mm", allowEmpty: [true, true], needConfirm: true
}}
placeholder={['开始时间', '结束时间']}
disabled={editFormDisable}
/>}
{taskType == '3' && <ProFormDateTimeRangePicker
initialValue={[dayjs(), undefined]}
name="expectedTimeRange"
label="下次触发时间"
fieldProps={{
showTime: {
format: 'HH:mm',
},
format: "YYYY-MM-DD HH:mm", allowEmpty: [true, true], needConfirm: true
}}
placeholder={['开始时间', '结束时间']}
disabled={editFormDisable}
/>}
{taskType != '3' && <ProFormDateTimeRangePicker
name="actualTimeRange"
label="实际时间"
fieldProps={{
showTime: {
format: 'HH:mm',
},
format: "YYYY-MM-DD HH:mm", allowEmpty: [true, true], needConfirm: true
}}
placeholder={['开始时间', '结束时间']}
disabled={editFormDisable}
/>}
</ProForm.Group>
{taskType == "3" &&
<Fragment>
{editFormDisable ?
<div className={style.localDiv}>
<text>:{onceConsumerRead(requestTask?.onceConsume)}</text>
</div> :
<div className={style.localDiv}>
<text style={{paddingRight: '8px'}}>:</text>
<Cascader options={options} loadData={cascaderLoadData}
onChange={cascaderOnChange}
defaultValue={changeValueToLabel()}
clearIcon={true}
// value={remindType.split(",")}
/>
{/*<Space wrap>*/}
{/* <text>单次耗时:</text>*/}
{/* <Select*/}
{/* style={{width: 120}}*/}
{/* allowClear*/}
{/* defaultValue={onceConsumeChange[0]}*/}
{/* onChange={(value) => {*/}
{/* onceConsumeChange[0] = value*/}
{/* setOnceConsumeChange(onceConsumeChange)*/}
{/* }}*/}
{/* options={onceConsumeList}*/}
{/* />*/}
{/* <Select*/}
{/* style={{width: 120}}*/}
{/* allowClear*/}
{/* defaultValue={onceConsumeChange[1]}*/}
{/* onChange={(value) => {*/}
{/* onceConsumeChange[1] = value*/}
{/* setOnceConsumeChange(onceConsumeChange)*/}
{/* }}*/}
{/* options={[{label: "分钟", value: "m"}, {label: "小时", value: "h"}, {*/}
{/* label: "天",*/}
{/* value: "d"*/}
{/* }]}*/}
{/* />*/}
{/*</Space>*/}
</div>}
<SettingCron canSetting={!editFormDisable} cron={form.getFieldValue("cron")}
setCronFunction={(cron: string) => {
form.setFieldValue("cron", cron)
}}
onceConsumer={onceConsumeChange}
setExpectedTimeRange={(expect: (Dayjs | undefined)[]) => {
form.setFieldValue("expectedTimeRange", expect)
}}
/></Fragment>}
</ModalForm>
</Fragment>
);
};