feat:顺序计划

This commit is contained in:
1708-huayu 2025-08-04 18:54:30 +08:00
parent b90b63853f
commit 5083347a41
8 changed files with 565 additions and 226 deletions

View File

@ -100,7 +100,7 @@ const DiaryOption = (props: SelectDiary) => {
if (!open) {
return
}
setSendValueFlag(true)
setSelectLoading(true)
const fakeDataUrl = process.env.NEXT_PUBLIC_TODO_REQUEST_URL + `/task/message/diary/select`;
fetch(fakeDataUrl, {
method: 'POST', headers: {
@ -123,7 +123,7 @@ const DiaryOption = (props: SelectDiary) => {
listRef.current.scrollTo({top: 9999999});
}
}
setSendValueFlag(false)
setSelectLoading(false)
});
};
@ -190,7 +190,7 @@ const DiaryOption = (props: SelectDiary) => {
// 滚动处理 end
// 点击操作 start
const [clickTaskDiary, setClickTaskDiary] = useState<ListDiary>()
const onClickTAskDiary = (item: ListDiary, operate: string) => {
const onClickTaskDiary = (item: ListDiary, operate: string) => {
if (clickTaskDiary == item && operate == 'L') {
setClickTaskDiary(undefined)
} else {
@ -368,7 +368,7 @@ const DiaryOption = (props: SelectDiary) => {
<Button style={{flexGrow: 1}} onClick={() => setCurrentIndex(2)}
type={currentIndex == 2 ? "primary" : "default"}></Button>
</div>
<List loading={sendValueFlag}>
<List loading={selectLoading}>
<VirtualList
data={diaryReduceList}
height={containerHeight}
@ -400,8 +400,8 @@ const DiaryOption = (props: SelectDiary) => {
>
<div
className={`${style.detailLine} ${item.id === clickTaskDiary?.id ? style.detailLineClick : ''}`}
onClick={() => onClickTAskDiary(item, "L")}
onContextMenu={() => onClickTAskDiary(item, "R")}>
onClick={() => onClickTaskDiary(item, "L")}
onContextMenu={() => onClickTaskDiary(item, "R")}>
<text
style={{
textDecoration: item.enableFlag === '0' && currentIndex === 0 ? 'line-through' : '',

View File

@ -2,6 +2,7 @@ import React, {useState, useEffect, Fragment} from 'react';
import dayjs, {Dayjs} from 'dayjs';
import {Button, Input, Modal, Segmented} from 'antd';
import style from "@/components/SettingCton.module.css"
import {generateNextTimeAPI} from "@/components/service/ScheduleTask";
type PresetType = 'everyMinute' | 'everyHour' | 'daily' | 'weekly' | 'monthly';
@ -10,8 +11,144 @@ interface CronGeneratorProps {
cron?: string;
}
function cronToChinese(cronExpression: string) {
// 解析Cron表达式各部分
const parts = cronExpression.split(' ');
if (parts.length < 5) {
return '非法的Cron表达式';
}
const [minute, hour, dayOfMonth, month, dayOfWeek] = parts;
// 解析分钟
function parseMinute(m: string) {
if (m === '*') return '每分钟';
if (m === '0') return '';
if (m.includes('/')) {
const interval = m.split('/')[1];
return `${interval}分钟`;
}
if (m.includes(',')) {
const minutes = m.split(',').map(m => parseInt(m));
return `${minutes.join('、')}`;
}
return `${parseInt(m)}`;
}
// 解析小时
function parseHour(h: string) {
if (h === '*') return '每小时';
if (h.includes('/')) {
const interval = h.split('/')[1];
return `${interval}小时`;
}
if (h.includes(',')) {
const hours = h.split(',').map(h => parseInt(h));
return `${hours.join('、')}`;
}
return `${parseInt(h)}`;
}
// 解析日期
function parseDayOfMonth(d: string) {
if (d === '*') return '';
if (d === 'L') return '每月最后一天';
if (d.includes('W')) {
const day = d.replace('W', '');
return `在每月${day}日最近的工作日`;
}
if (d.includes('/')) {
const interval = d.split('/')[1];
return `${interval}`;
}
if (d.includes(',')) {
const days = d.split(',').map(d => parseInt(d));
return `在每月${days.join('、')}`;
}
return `在每月${parseInt(d)}`;
}
// 解析月份
function parseMonth(m: string) {
if (m === '*') return '';
const monthNames = ['一月', '二月', '三月', '四月', '五月', '六月',
'七月', '八月', '九月', '十月', '十一月', '十二月'];
if (m.includes('/')) {
const interval = m.split('/')[1];
return `${interval}个月`;
}
if (m.includes(',')) {
const months = m.split(',').map(m => monthNames[parseInt(m) - 1]);
return `${months.join('、')}`;
}
return `${monthNames[parseInt(m) - 1]}`;
}
// 解析星期
function parseDayOfWeek(d: string) {
if (d === '*' || d === '?') return '';
const dayNames = ['周日', '周一', '周二', '周三', '周四', '周五', '周六'];
if (d.includes('L')) {
const dayIndex = parseInt(d.replace('L', ''));
return `每月最后一个${dayNames[dayIndex]}`;
}
if (d.includes('#')) {
const [dayIndex, weekNum] = d.split('#').map(Number);
return `每月第${weekNum}${dayNames[dayIndex]}`;
}
if (d.includes('/')) {
const interval = d.split('/')[1];
return `${interval}天(周)`;
}
if (d.includes(',')) {
const days = d.split(',').map(d => dayNames[parseInt(d)]);
return `在每周${days.join('、')}`;
}
return `在每周${dayNames[parseInt(d)]}`;
}
// 构建描述
const minuteDesc = parseMinute(minute);
const hourDesc = parseHour(hour);
const dayOfMonthDesc = parseDayOfMonth(dayOfMonth);
const monthDesc = parseMonth(month);
const dayOfWeekDesc = parseDayOfWeek(dayOfWeek);
// 处理整点时间
if (minute === '0' && hour !== '*') {
// 每天固定时间
if (dayOfMonth === '*' && month === '*' && (dayOfWeek === '*' || dayOfWeek === '?')) {
return `每天${hourDesc}`;
}
return `${hourDesc} ${dayOfMonthDesc} ${monthDesc} ${dayOfWeekDesc}`.replace(/\s+/g, ' ').trim();
}
// 组合描述
let description = `${minuteDesc} ${hourDesc} ${dayOfMonthDesc} ${monthDesc} ${dayOfWeekDesc}`;
// 清理多余空格和空部分
description = description.replace(/\s+/g, ' ').trim();
description = description.replace(/\s\s+/g, ' ');
// 处理一些常见模式
if (description === '每分钟 每小时') return '每分钟';
if (description.startsWith('每分钟 在')) return description.replace('每分钟', '每分钟的');
// 处理空描述
if (!description) return '非法的Cron表达式';
// 优化描述
description = description
.replace(/在每月(\d+)日 在每周/g, '在每月$1日且')
.replace(/在每月(\d+)日 在/g, '在每月$1日')
.replace(/在每月/g, '每月')
.replace(/在每周/g, '每周')
.replace(/在/g, '');
return description;
}
const CronGenerator: React.FC<CronGeneratorProps> = ({setCronFunction, cron}) => {
const [showModal,setShowModal] = useState(false);
const [showModal, setShowModal] = useState(false);
const [current, setCurrent] = useState<number>(0);
const [cronSeconds, setCronSeconds] = useState<string>(cron ? cron.split(' ')[0] : '*');
const [cronMinutes, setCronMinutes] = useState<string>(cron ? cron.split(' ')[1] : '*');
@ -24,7 +161,9 @@ const CronGenerator: React.FC<CronGeneratorProps> = ({setCronFunction, cron}) =>
const [fullCronExpression, setFullCronExpression] = useState<string>(
`${cronMinutes} ${cronHours} ${cronDayOfMonth} ${cronMonth} ${cronDayOfWeek}`.trim()
);
const [showTimeListModal, setShowTimeListModal] = useState(false);
const [canReadCron,setCanReadCron]=useState<string>()
const titleItem = ['具体时间', '周期', '自定义cron'];
useEffect(() => {
@ -34,17 +173,20 @@ const CronGenerator: React.FC<CronGeneratorProps> = ({setCronFunction, cron}) =>
const onClickItem = (index: number) => {
setCurrent(index);
};
useEffect(() => {
setCanReadCron(cronToChinese(fullCronExpression))
}, [fullCronExpression]);
const onClickConfirmCron = () => {
if (fullCronExpression) {
try {
// Replace generateNextTimeAPI with your actual API call
// generateNextTimeAPI('0 ' + fullCronExpression).then(res => {
// setNextOccurrences(res.map((next: string) => dayjs(next)));
// setCronFunction(false);
// });
generateNextTimeAPI('0 ' + fullCronExpression).then(res => {
setNextOccurrences(res.data.data.map((next: string) => dayjs(next)));
setCronFunction(false);
});
console.log('Cron confirmed:', fullCronExpression);
setCronFunction(false);
setShowModal(false)
} catch (error) {
console.error('Cron expression is invalid', error);
}
@ -69,10 +211,10 @@ const CronGenerator: React.FC<CronGeneratorProps> = ({setCronFunction, cron}) =>
return;
}
console.log('cron' + fullCronExpression);
// Replace generateNextTimeAPI with your actual API call
// generateNextTimeAPI('0 ' + fullCronExpression).then(res => {
// setNextOccurrences(res.map((next: string) => dayjs(next)));
// });
generateNextTimeAPI('0 ' + fullCronExpression).then(res => {
setNextOccurrences(res.data.data.map((next: string) => dayjs(next)));
});
setShowTimeListModal(true)
} catch (error) {
console.error('Cron expression is invalid', error);
}
@ -125,142 +267,148 @@ const CronGenerator: React.FC<CronGeneratorProps> = ({setCronFunction, cron}) =>
return (
<Fragment>
<Button type="primary" onClick={()=>setShowModal(!showModal)}>
<text>{canReadCron}</text>
<Button type="primary" onClick={() => setShowModal(!showModal)}>
</Button>
<Modal
title="设置时间"
closable={{ 'aria-label': 'Custom Close Button' }}
closable={{'aria-label': 'Custom Close Button'}}
open={showModal}
// onOk={handleOk}
onCancel={()=>setShowModal(false)}
onOk={onClickConfirmCron}
onCancel={() => setShowModal(false)}
>
<div className={style.container}>
<div className={style.cronForm}>
<div>
<Segmented
value={titleItem[current]}
options={titleItem}
onChange={(value) => onClickItem(titleItem.indexOf(value as string))}
block
/>
</div>
<div className={style.content}>
{current === 0 && (
<>
<div className={style.formItem}>
<div className={style.label}>0-59</div>
<Input
className={style.input}
value={cronMinutes}
onChange={(e) =>
setCronMinutes(e.target.value)}
placeholder="*"
/>
</div>
<div className={style.formItem}>
<div className={style.label}>0-23</div>
<Input
className={style.input}
value={cronHours}
onChange={(e) =>
setCronHours(e.target.value)}
placeholder="*"
/>
</div>
<div className={style.formItem}>
<div className={style.label}>1-31</div>
<Input
className={style.input}
value={cronDayOfMonth}
onChange={(e) => setCronDayOfMonth(e.target.value)}
placeholder="*"
/>
</div>
<div className={style.formItem}>
<div className={style.label}>1-12</div>
<Input
className={style.input}
value={cronMonth}
onChange={(e) => setCronMonth(e.target.value)}
placeholder="*"
/>
</div>
<div className={style.formItem}>
<div className={style.label}>0-60=</div>
<Input
className={style.input}
value={cronDayOfWeek}
onChange={(e) => setCronDayOfWeek(e.target.value)}
placeholder="*"
/>
</div>
</>
)}
{current === 1 && (
<div className={style.presetButtons}>
<div>
<Input
type="number"
value={everyNumber}
onChange={(e) => setEveryNumber(parseInt(e.target.value) || 1)}
placeholder="1"
/>
</div>
<Button className={style.presetBtn} onClick={() => setPreset('everyMinute')}>
{everyNumber}
</Button>
<Button className={style.presetBtn} onClick={() => setPreset('everyHour')}>
{everyNumber}
</Button>
<Button className={style.presetBtn} onClick={() => setPreset('daily')}>
{everyNumber}
</Button>
<Button className={style.presetBtn} onClick={() => setPreset('weekly')}>
{everyNumber}
</Button>
<Button className={style.presetBtn} onClick={() => setPreset('monthly')}>
</Button>
</div>
)}
{current === 2 && (
<div className={style.presetButtons}>
<div style={{color: 'red'}}>!!!5</div>
<Input value={fullCronExpression}
onChange={(e) => setFullCronExpression(e.target.value)}/>
</div>
)}
</div>
<div className={style.buttonFlex}>
<Button size="small" danger onClick={resetOccurrences}>
</Button>
<Button size="small" type="primary" onClick={generateNextOccurrences}>
</Button>
</div>
{/*<div className={style.container}>*/}
<div className={style.cronForm}>
<div>
<Segmented
value={titleItem[current]}
options={titleItem}
onChange={(value) => onClickItem(titleItem.indexOf(value as string))}
block
/>
</div>
{nextOccurrences.length > 0 && (
<div className={style.results}>
<div className={style.resultsTitle}></div>
{nextOccurrences.map((item, index) => (
<div className={style.occurrenceItem} key={index}>
<div>{dayjs(item).format('YYYY-MM-DD HH:mm')}</div>
<div className={style.content}>
{current === 0 && (
<>
<div className={style.formItem}>
<div className={style.label}>0-59</div>
<Input
className={style.input}
value={cronMinutes}
onChange={(e) =>
setCronMinutes(e.target.value)}
placeholder="*"
/>
</div>
))}
</div>
)}
<Button type="primary" onClick={onClickConfirmCron}>
</Button>
<div className={style.formItem}>
<div className={style.label}>0-23</div>
<Input
className={style.input}
value={cronHours}
onChange={(e) =>
setCronHours(e.target.value)}
placeholder="*"
/>
</div>
<div className={style.formItem}>
<div className={style.label}>1-31</div>
<Input
className={style.input}
value={cronDayOfMonth}
onChange={(e) => setCronDayOfMonth(e.target.value)}
placeholder="*"
/>
</div>
<div className={style.formItem}>
<div className={style.label}>1-12</div>
<Input
className={style.input}
value={cronMonth}
onChange={(e) => setCronMonth(e.target.value)}
placeholder="*"
/>
</div>
<div className={style.formItem}>
<div className={style.label}>0-60=</div>
<Input
className={style.input}
value={cronDayOfWeek}
onChange={(e) => setCronDayOfWeek(e.target.value)}
placeholder="*"
/>
</div>
</>
)}
{current === 1 && (
<div className={style.presetButtons}>
<div>
<Input
type="number"
value={everyNumber}
onChange={(e) => setEveryNumber(parseInt(e.target.value) || 1)}
placeholder="1"
/>
</div>
<Button className={style.presetBtn} onClick={() => setPreset('everyMinute')}>
{everyNumber}
</Button>
<Button className={style.presetBtn} onClick={() => setPreset('everyHour')}>
{everyNumber}
</Button>
<Button className={style.presetBtn} onClick={() => setPreset('daily')}>
{everyNumber}
</Button>
<Button className={style.presetBtn} onClick={() => setPreset('weekly')}>
{everyNumber}
</Button>
<Button className={style.presetBtn} onClick={() => setPreset('monthly')}>
</Button>
</div>
)}
{current === 2 && (
<div className={style.presetButtons}>
<div style={{color: 'red'}}>!!!5</div>
<Input value={fullCronExpression}
onChange={(e) => setFullCronExpression(e.target.value)}/>
</div>
)}
</div>
<div className={style.buttonFlex}>
<Button size="small" danger onClick={resetOccurrences}>
</Button>
<Button size="small" type="primary" onClick={generateNextOccurrences}>
</Button>
</div>
</div>
{nextOccurrences.length > 0 && (
// <div className={style.results}>
// <div className={style.resultsTitle}>最近触发时间:</div>
<Modal
title="最近触发时间"
closable={{'aria-label': 'Custom Close Button'}}
open={showTimeListModal}
onOk={() => setShowTimeListModal(false)}
onCancel={() => setShowTimeListModal(false)}
>
{nextOccurrences.map((item, index) => (
<div className={style.occurrenceItem} key={index}>
<div>{dayjs(item).format('YYYY-MM-DD HH:mm')}</div>
</div>
))}
</Modal>
// </div>
)}
{/*</div>*/}
</Modal>
</Fragment>

View File

@ -1,27 +1,25 @@
import React, {CSSProperties, Fragment, useEffect, useState} from "react";
import {DragDropContext, Droppable, Draggable, DropResult, DraggingStyle, NotDraggingStyle} from "react-beautiful-dnd";
import {TaskStepSortVO} from "@/components/type/TaskSort.d";
import {Button, Drawer, message, Modal} from "antd";
import {TaskStepSortOperateVO, TaskStepSortVO} from "@/components/type/TaskSort.d";
import {Button, Drawer, Dropdown, MenuProps, message, Modal, Popconfirm} from "antd";
import TextArea from "antd/es/input/TextArea";
import {addStepItemAPI} from "@/components/service/StepSort";
import {
addStepItemAPI,
deleteStepItemAPI, insertStepItemAPI,
updateStepItemAPI,
updateStepItemIndexAPI
} from "@/components/service/StepSort";
import {QuestionCircleOutlined} from "@ant-design/icons";
const reorder = (list: TaskStepSortVO[], startIndex: number, endIndex: number) => {
const result = Array.from(list);
const [removed] = result.splice(startIndex, 1);
result.splice(endIndex, 0, removed);
return result;
};
const grid = 8;
const getItemStyle = (isDragging: boolean, draggableStyle: DraggingStyle | NotDraggingStyle | undefined): CSSProperties => ({
// some basic styles to make the items look a bit nicer
userSelect: "none",
// padding: grid,
margin: `0 0 ${grid}px 0`,
background: isDragging ? "lightgreen" : "white",
...draggableStyle
});
const getListStyle = (isDraggingOver: boolean) => ({
background: isDraggingOver ? "lightblue" : "white",
width: "100%"
@ -32,51 +30,200 @@ const StepSort = (props: { taskId: string, stepList: TaskStepSortVO[] }) => {
// 抽屉 start
const [open, setOpen] = useState(false);
const [dialogueOpen, setDialogueOpen] = useState(false);
const [sortItemList, setSortItemList] = useState<TaskStepSortVO[]>([]);
const [modalText, setModalText] = useState<TaskStepSortVO>({
const [clickTaskSortItem, setClickTaskSortItem] = useState<TaskStepSortVO>();
const [clickSortItemIndex, setClickSortItemIndex]
= useState<number|undefined>(undefined);
const initModalText = {
id: "",
stepDesc: undefined,
sortIndex: undefined,
taskId: props.taskId,
});
};
const [modalText, setModalText] = useState<TaskStepSortOperateVO>(initModalText);
const items: MenuProps['items'] = [
{
key: "0", label: "上方插入", onClick: () => {
setDialogueOpen(true)
setModalText({
id: "",
stepDesc: undefined,
sortIndex: undefined,
taskId: props.taskId,
lastId:clickSortItemIndex==0?undefined:state[clickSortItemIndex!-1].id,
nextId:clickTaskSortItem?.id,
operate:"last"
})
}
},
{
key: "1", label: "下方插入", onClick: () => {
setDialogueOpen(true)
setModalText({
id: "",
stepDesc: undefined,
sortIndex: undefined,
taskId: props.taskId,
lastId:clickTaskSortItem?.id,
nextId:clickSortItemIndex==state.length-1?undefined:state[clickSortItemIndex!+1].id,
operate:"next"
})
}
},
{
key: "2", label: "修改步骤", onClick: () => {
setDialogueOpen(true)
setModalText(clickTaskSortItem!)
}
},
{
key: "3", label:
<Popconfirm
title="删除步骤"
description="确认要删除步骤?"
icon={<QuestionCircleOutlined style={{color: 'red'}}/>}
okText="确认"
cancelText="取消"
onConfirm={() => {
deleteStepItemAPI(clickTaskSortItem!.id).then((response => {
console.log('response', response)
if (response.data.status.success) {
state.splice(clickSortItemIndex!,1);
setState([...state])
props.stepList.length = 0
props.stepList.push(...state)
message.success("删除步骤成功")
}
}));
}}
><a></a></Popconfirm>
},
]
useEffect(() => {
// 根据任务id查找步骤
}, []);
function onDragEnd(result: DropResult) {
const getItemStyle = (isDragging: boolean, draggableStyle: DraggingStyle | NotDraggingStyle | undefined, itemId: string): CSSProperties => ({
// some basic styles to make the items look a bit nicer
userSelect: "none",
// padding: grid,
margin: `0 0 ${grid}px 0`,
color: clickTaskSortItem && clickTaskSortItem.id == itemId ? "white" : "black",
background: isDragging ? "lightgreen" : clickTaskSortItem && clickTaskSortItem.id == itemId ? "#1677ff" : "white",
...draggableStyle
});
async function onDragEnd(result: DropResult) {
console.log({result})
const {source, destination} = result;
// dropped outside the list
if (!destination) {
return;
}
if (source.index == destination.index) {
return;
}
let lastId = "", nextId = "";
if (source.index > destination.index) {
if (destination.index != 0) {
lastId = state[destination.index - 1].id;
}
nextId = state[destination.index].id
} else {
lastId = state[destination.index].id;
if (destination.index != state.length - 1) {
nextId = state[destination.index + 1].id;
}
}
updateStepItemIndexAPI(lastId, state[source.index].id, nextId)
const items = reorder(state, source.index, destination.index);
let newState = [...state];
newState = items;
setState(newState);
props.stepList.length=0
props.stepList.length = 0
props.stepList.push(...newState)
message.info("移动成功");
}
const onClickTaskSortItem = (item: TaskStepSortVO, operate: string,index:number) => {
if (clickTaskSortItem == item && operate == 'L') {
setClickTaskSortItem(undefined)
setClickSortItemIndex(undefined)
} else {
setClickTaskSortItem(item)
setClickSortItemIndex(index)
}
}
const [confirmButtonLoading, setConfirmButtonLoading] = useState(false);
const confirmModalTextArea = () => {
setConfirmButtonLoading(true)
addStepItemAPI(modalText).then(res => {
if (res.data.status.success) {
message.info("添加步骤成功")
setModalText(
{...modalText, stepDesc: undefined})
setState([...state, res.data.data])
props.stepList.length=0
props.stepList.push(...state, res.data.data)
setDialogueOpen(false);
} else {
message.error(res.data.status.message)
}
}).finally(()=>{
setConfirmButtonLoading(false)
})
// 修改
if (modalText.id){
updateStepItemAPI(modalText).then(res=>{
if (res.data.status.success) {
message.info("修改步骤成功")
setModalText(initModalText)
state.splice(clickSortItemIndex!,1,res.data.data)
setState([...state])
props.stepList.length = 0
props.stepList.push(...state)
setDialogueOpen(false);
} else {
message.error(res.data.status.message)
}
}).finally(() => {
setConfirmButtonLoading(false)
})
}else if(modalText.operate){
insertStepItemAPI(modalText).then(res => {
if (res.data.status.success) {
message.info("添加步骤成功")
setModalText(initModalText)
let result:TaskStepSortVO[]=[];
if('next'==modalText.operate){
state.splice(clickSortItemIndex!,0,res.data.data)
}else if ('last'==modalText.operate){
if (clickSortItemIndex==0){
result = [res.data.data,...state]
}else {
state.splice(clickSortItemIndex!-1,0,res.data.data)
}
}
if (result.length>0){
props.stepList.length = 0
props.stepList.push(...result)
setState(result)
}else {
props.stepList.length = 0
props.stepList.push(...state)
setState(state)
}
setDialogueOpen(false);
} else {
message.error(res.data.status.message)
}
}).finally(() => {
setConfirmButtonLoading(false)
})
}else {
addStepItemAPI(modalText).then(res => {
if (res.data.status.success) {
message.info("添加步骤成功")
setModalText(initModalText)
setState([...state, res.data.data])
props.stepList.length = 0
props.stepList.push(...state, res.data.data)
setDialogueOpen(false);
} else {
message.error(res.data.status.message)
}
}).finally(() => {
setConfirmButtonLoading(false)
})
}
}
return (
@ -103,7 +250,7 @@ const StepSort = (props: { taskId: string, stepList: TaskStepSortVO[] }) => {
bottom: "0px"
}
}}
value={modalText.sortIndex}
value={modalText.stepDesc}
maxLength={255} autoFocus
onChange={(e) => setModalText(
{...modalText, stepDesc: e.target.value})}
@ -131,51 +278,59 @@ const StepSort = (props: { taskId: string, stepList: TaskStepSortVO[] }) => {
open={open}
footer={<Button type={"primary"} onClick={() => setDialogueOpen(true)}></Button>}
>
<div style={{display: "flex"}}>
<DragDropContext onDragEnd={onDragEnd}>
<Droppable key="sortDroppable" droppableId="sortDroppableId">
{(provided, snapshot) => (
<div
ref={provided.innerRef}
style={getListStyle(snapshot.isDraggingOver)}
{...provided.droppableProps}
>
{state.map((item, index) => (
<Draggable
key={item.id}
draggableId={item.id}
index={index}
>
{(provided, snapshot) => (
<div
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
style={getItemStyle(
snapshot.isDragging,
provided.draggableProps.style
)}
>
<div>
{`步骤${index + 1}`}
</div>
<Dropdown menu={{items}} trigger={['contextMenu']}>
<div style={{display: "flex"}}>
<DragDropContext onDragEnd={onDragEnd}>
<Droppable key="sortDroppable" droppableId="sortDroppableId">
{(provided, snapshot) => (
<div
ref={provided.innerRef}
style={getListStyle(snapshot.isDraggingOver)}
{...provided.droppableProps}
>
{state.map((item, index) => (
<Draggable
key={item.id}
draggableId={item.id}
index={index}
>
{(provided, snapshot) => (
<div
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
style={getItemStyle(
snapshot.isDragging,
provided.draggableProps.style,
item.id
)}
onClick={() => onClickTaskSortItem(item, "L",index)}
onContextMenu={() => onClickTaskSortItem(item, "R",index)}
>
<div style={{whiteSpace: 'pre-line',border: "solid",
borderRadius: grid,}}>
{item.stepDesc}
<div>
{`步骤${index + 1}`}
</div>
<div
>
<div style={{
whiteSpace: 'pre-line', border: "solid",
borderRadius: grid,
borderColor: clickTaskSortItem && clickTaskSortItem.id == item.id ? "#1677ff" : "black",
}}>
{item.stepDesc}
</div>
</div>
</div>
</div>
)}
</Draggable>
))}
{provided.placeholder}
</div>
)}
</Droppable>
</DragDropContext>
</div>
)}
</Draggable>
))}
{provided.placeholder}
</div>
)}
</Droppable>
</DragDropContext>
</div>
</Dropdown>
</Drawer>
</Fragment>
);

View File

@ -0,0 +1,8 @@
import {AxiosResponse} from "axios";
import {ResponseVO} from "@/lib/definitions";
import {httpReq} from "@/utils/axiosReq";
export const generateNextTimeAPI = (cron: string): Promise<AxiosResponse<ResponseVO<string[]>>> => {
return httpReq.get(process.env.NEXT_PUBLIC_TODO_REQUEST_URL +
`/task/schedule/nextTime?cron=${cron}`)
}

View File

@ -6,4 +6,21 @@ import {TaskStepSortVO} from "@/components/type/TaskSort.d";
export const addStepItemAPI= (data:TaskStepSortVO):Promise<AxiosResponse<ResponseVO<TaskStepSortVO>>> =>{
return httpReq.post(process.env.NEXT_PUBLIC_TODO_REQUEST_URL + `/task/step/sort/item`,
data)
}
export const insertStepItemAPI= (data:TaskStepSortVO):Promise<AxiosResponse<ResponseVO<TaskStepSortVO>>> =>{
return httpReq.post(process.env.NEXT_PUBLIC_TODO_REQUEST_URL + `/task/step/sort/insert/item`,
data)
}
export const updateStepItemAPI= (data:TaskStepSortVO):Promise<AxiosResponse<ResponseVO<TaskStepSortVO>>> =>{
return httpReq.put(process.env.NEXT_PUBLIC_TODO_REQUEST_URL + `/task/step/sort/item`,
data)
}
export const updateStepItemIndexAPI=(lastId:string,currentId:string ,nextId:string)=>{
return httpReq.get(process.env.NEXT_PUBLIC_TODO_REQUEST_URL +
`/task/step/sort/update/index?lastId=${lastId}&currentId=${currentId}&nextId=${nextId}`)
}
export const deleteStepItemAPI=(id:string)=>{
return httpReq.delete(process.env.NEXT_PUBLIC_TODO_REQUEST_URL +
`/task/step/sort/item?id=${id}`)
}

View File

@ -2,5 +2,11 @@ export type TaskStepSortVO = {
id: string,
sortIndex: number|undefined,
stepDesc: string|undefined,
taskId:string
}
taskId:string,
}
export type TaskStepSortOperateVO=TaskStepSortVO&{
lastId?:string,
nextId?:string,
[key: string]: unknown; // 允许任意额外的 string 类型字段,值类型为 unknown
}
// & Record<string, unknown>;

View File

@ -1,6 +1,7 @@
import React from "react";
import {Dayjs} from "dayjs";
import {TaskStepSortVO} from "@/components/type/TaskSort.d";
import {Data} from "@dnd-kit/core";
export type Request<T>={
data:T,
@ -40,6 +41,9 @@ export type TaskMessage ={
stepList?:TaskStepSortVO[];
}
// export type TaskSortMessage=Data&{
// stepList?:TaskStepSortVO[];
// }
export type DataType = TaskMessage&{
key: React.ReactNode;

View File

@ -55,7 +55,7 @@ export const DetailModelForm: React.FC<DetailModelFormProps> = (props) => {
// 团队第一层 pid必须为0
const [taskType, setTaskType] = useState('0')
const [spinning, setSpinning] = useState(true)
const [operationRequest,setOperationRequest] = useState(false)
const [operationRequest, setOperationRequest] = useState(false)
useEffect(() => {
if (props.itemId != undefined && (
props.operationId === OPERATION_BUTTON_TYPE.DETAIL || props.operationId === OPERATION_BUTTON_TYPE.UPDATE)) {
@ -117,10 +117,10 @@ export const DetailModelForm: React.FC<DetailModelFormProps> = (props) => {
// 推荐使用 request或者 initialValues ? <Form/> : null
return (
<Fragment>
<Spin spinning={spinning} fullscreen />
<Spin spinning={spinning} fullscreen/>
<ModalForm<DataType>
title={props.description}
open={!spinning&&props.open && !props.haveButton}
open={!spinning && props.open && !props.haveButton}
trigger={props.haveButton ?
<Button type="primary">
<PlusOutlined/>
@ -132,7 +132,7 @@ export const DetailModelForm: React.FC<DetailModelFormProps> = (props) => {
autoFocusFirstInput
modalProps={{
destroyOnClose: true,
maskClosable:false,
maskClosable: false,
onCancel: () => {
props.reloadData?.();
},
@ -189,7 +189,7 @@ export const DetailModelForm: React.FC<DetailModelFormProps> = (props) => {
onClick={() => props.closeOpen?.()}></Button>)
}
if (taskType == '2') {
result.push(<StepSort taskId={props.itemId!} stepList={requestTask?.stepList||[]}/>)
result.push(<StepSort taskId={props.itemId!} stepList={requestTask?.stepList || []}/>)
}
return result;
},
@ -433,8 +433,9 @@ export const DetailModelForm: React.FC<DetailModelFormProps> = (props) => {
disabled={editFormDisable}
/>
</ProForm.Group>
{taskType=="3"&&<SettingCron setCronFunction={
()=>{}
{taskType == "3" && <SettingCron setCronFunction={
() => {
}
}/>}
</ModalForm>