feat:顺序计划
This commit is contained in:
parent
b90b63853f
commit
5083347a41
|
@ -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' : '',
|
||||
|
|
|
@ -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-6,0=周日)</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-6,0=周日)</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>
|
||||
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
|
|
|
@ -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}`)
|
||||
}
|
|
@ -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}¤tId=${currentId}&nextId=${nextId}`)
|
||||
}
|
||||
|
||||
export const deleteStepItemAPI=(id:string)=>{
|
||||
return httpReq.delete(process.env.NEXT_PUBLIC_TODO_REQUEST_URL +
|
||||
`/task/step/sort/item?id=${id}`)
|
||||
}
|
|
@ -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>;
|
|
@ -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;
|
||||
|
|
|
@ -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>
|
||||
|
|
Loading…
Reference in New Issue