feat:顺序计划
This commit is contained in:
parent
b90b63853f
commit
5083347a41
|
@ -100,7 +100,7 @@ const DiaryOption = (props: SelectDiary) => {
|
||||||
if (!open) {
|
if (!open) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
setSendValueFlag(true)
|
setSelectLoading(true)
|
||||||
const fakeDataUrl = process.env.NEXT_PUBLIC_TODO_REQUEST_URL + `/task/message/diary/select`;
|
const fakeDataUrl = process.env.NEXT_PUBLIC_TODO_REQUEST_URL + `/task/message/diary/select`;
|
||||||
fetch(fakeDataUrl, {
|
fetch(fakeDataUrl, {
|
||||||
method: 'POST', headers: {
|
method: 'POST', headers: {
|
||||||
|
@ -123,7 +123,7 @@ const DiaryOption = (props: SelectDiary) => {
|
||||||
listRef.current.scrollTo({top: 9999999});
|
listRef.current.scrollTo({top: 9999999});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
setSendValueFlag(false)
|
setSelectLoading(false)
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -190,7 +190,7 @@ const DiaryOption = (props: SelectDiary) => {
|
||||||
// 滚动处理 end
|
// 滚动处理 end
|
||||||
// 点击操作 start
|
// 点击操作 start
|
||||||
const [clickTaskDiary, setClickTaskDiary] = useState<ListDiary>()
|
const [clickTaskDiary, setClickTaskDiary] = useState<ListDiary>()
|
||||||
const onClickTAskDiary = (item: ListDiary, operate: string) => {
|
const onClickTaskDiary = (item: ListDiary, operate: string) => {
|
||||||
if (clickTaskDiary == item && operate == 'L') {
|
if (clickTaskDiary == item && operate == 'L') {
|
||||||
setClickTaskDiary(undefined)
|
setClickTaskDiary(undefined)
|
||||||
} else {
|
} else {
|
||||||
|
@ -368,7 +368,7 @@ const DiaryOption = (props: SelectDiary) => {
|
||||||
<Button style={{flexGrow: 1}} onClick={() => setCurrentIndex(2)}
|
<Button style={{flexGrow: 1}} onClick={() => setCurrentIndex(2)}
|
||||||
type={currentIndex == 2 ? "primary" : "default"}>失效</Button>
|
type={currentIndex == 2 ? "primary" : "default"}>失效</Button>
|
||||||
</div>
|
</div>
|
||||||
<List loading={sendValueFlag}>
|
<List loading={selectLoading}>
|
||||||
<VirtualList
|
<VirtualList
|
||||||
data={diaryReduceList}
|
data={diaryReduceList}
|
||||||
height={containerHeight}
|
height={containerHeight}
|
||||||
|
@ -400,8 +400,8 @@ const DiaryOption = (props: SelectDiary) => {
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className={`${style.detailLine} ${item.id === clickTaskDiary?.id ? style.detailLineClick : ''}`}
|
className={`${style.detailLine} ${item.id === clickTaskDiary?.id ? style.detailLineClick : ''}`}
|
||||||
onClick={() => onClickTAskDiary(item, "L")}
|
onClick={() => onClickTaskDiary(item, "L")}
|
||||||
onContextMenu={() => onClickTAskDiary(item, "R")}>
|
onContextMenu={() => onClickTaskDiary(item, "R")}>
|
||||||
<text
|
<text
|
||||||
style={{
|
style={{
|
||||||
textDecoration: item.enableFlag === '0' && currentIndex === 0 ? 'line-through' : '',
|
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 dayjs, {Dayjs} from 'dayjs';
|
||||||
import {Button, Input, Modal, Segmented} from 'antd';
|
import {Button, Input, Modal, Segmented} from 'antd';
|
||||||
import style from "@/components/SettingCton.module.css"
|
import style from "@/components/SettingCton.module.css"
|
||||||
|
import {generateNextTimeAPI} from "@/components/service/ScheduleTask";
|
||||||
|
|
||||||
type PresetType = 'everyMinute' | 'everyHour' | 'daily' | 'weekly' | 'monthly';
|
type PresetType = 'everyMinute' | 'everyHour' | 'daily' | 'weekly' | 'monthly';
|
||||||
|
|
||||||
|
@ -10,8 +11,144 @@ interface CronGeneratorProps {
|
||||||
cron?: string;
|
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 CronGenerator: React.FC<CronGeneratorProps> = ({setCronFunction, cron}) => {
|
||||||
const [showModal,setShowModal] = useState(false);
|
const [showModal, setShowModal] = useState(false);
|
||||||
const [current, setCurrent] = useState<number>(0);
|
const [current, setCurrent] = useState<number>(0);
|
||||||
const [cronSeconds, setCronSeconds] = useState<string>(cron ? cron.split(' ')[0] : '*');
|
const [cronSeconds, setCronSeconds] = useState<string>(cron ? cron.split(' ')[0] : '*');
|
||||||
const [cronMinutes, setCronMinutes] = useState<string>(cron ? cron.split(' ')[1] : '*');
|
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>(
|
const [fullCronExpression, setFullCronExpression] = useState<string>(
|
||||||
`${cronMinutes} ${cronHours} ${cronDayOfMonth} ${cronMonth} ${cronDayOfWeek}`.trim()
|
`${cronMinutes} ${cronHours} ${cronDayOfMonth} ${cronMonth} ${cronDayOfWeek}`.trim()
|
||||||
);
|
);
|
||||||
|
const [showTimeListModal, setShowTimeListModal] = useState(false);
|
||||||
|
|
||||||
|
const [canReadCron,setCanReadCron]=useState<string>()
|
||||||
const titleItem = ['具体时间', '周期', '自定义cron'];
|
const titleItem = ['具体时间', '周期', '自定义cron'];
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -34,17 +173,20 @@ const CronGenerator: React.FC<CronGeneratorProps> = ({setCronFunction, cron}) =>
|
||||||
const onClickItem = (index: number) => {
|
const onClickItem = (index: number) => {
|
||||||
setCurrent(index);
|
setCurrent(index);
|
||||||
};
|
};
|
||||||
|
useEffect(() => {
|
||||||
|
setCanReadCron(cronToChinese(fullCronExpression))
|
||||||
|
}, [fullCronExpression]);
|
||||||
|
|
||||||
const onClickConfirmCron = () => {
|
const onClickConfirmCron = () => {
|
||||||
if (fullCronExpression) {
|
if (fullCronExpression) {
|
||||||
try {
|
try {
|
||||||
// Replace generateNextTimeAPI with your actual API call
|
generateNextTimeAPI('0 ' + fullCronExpression).then(res => {
|
||||||
// generateNextTimeAPI('0 ' + fullCronExpression).then(res => {
|
setNextOccurrences(res.data.data.map((next: string) => dayjs(next)));
|
||||||
// setNextOccurrences(res.map((next: string) => dayjs(next)));
|
setCronFunction(false);
|
||||||
// setCronFunction(false);
|
});
|
||||||
// });
|
|
||||||
console.log('Cron confirmed:', fullCronExpression);
|
console.log('Cron confirmed:', fullCronExpression);
|
||||||
setCronFunction(false);
|
setCronFunction(false);
|
||||||
|
setShowModal(false)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Cron expression is invalid', error);
|
console.error('Cron expression is invalid', error);
|
||||||
}
|
}
|
||||||
|
@ -69,10 +211,10 @@ const CronGenerator: React.FC<CronGeneratorProps> = ({setCronFunction, cron}) =>
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
console.log('cron' + fullCronExpression);
|
console.log('cron' + fullCronExpression);
|
||||||
// Replace generateNextTimeAPI with your actual API call
|
generateNextTimeAPI('0 ' + fullCronExpression).then(res => {
|
||||||
// generateNextTimeAPI('0 ' + fullCronExpression).then(res => {
|
setNextOccurrences(res.data.data.map((next: string) => dayjs(next)));
|
||||||
// setNextOccurrences(res.map((next: string) => dayjs(next)));
|
});
|
||||||
// });
|
setShowTimeListModal(true)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Cron expression is invalid', error);
|
console.error('Cron expression is invalid', error);
|
||||||
}
|
}
|
||||||
|
@ -125,17 +267,18 @@ const CronGenerator: React.FC<CronGeneratorProps> = ({setCronFunction, cron}) =>
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<Button type="primary" onClick={()=>setShowModal(!showModal)}>
|
<text>{canReadCron}</text>
|
||||||
|
<Button type="primary" onClick={() => setShowModal(!showModal)}>
|
||||||
设置时间
|
设置时间
|
||||||
</Button>
|
</Button>
|
||||||
<Modal
|
<Modal
|
||||||
title="设置时间"
|
title="设置时间"
|
||||||
closable={{ 'aria-label': 'Custom Close Button' }}
|
closable={{'aria-label': 'Custom Close Button'}}
|
||||||
open={showModal}
|
open={showModal}
|
||||||
// onOk={handleOk}
|
onOk={onClickConfirmCron}
|
||||||
onCancel={()=>setShowModal(false)}
|
onCancel={() => setShowModal(false)}
|
||||||
>
|
>
|
||||||
<div className={style.container}>
|
{/*<div className={style.container}>*/}
|
||||||
<div className={style.cronForm}>
|
<div className={style.cronForm}>
|
||||||
<div>
|
<div>
|
||||||
<Segmented
|
<Segmented
|
||||||
|
@ -248,19 +391,24 @@ const CronGenerator: React.FC<CronGeneratorProps> = ({setCronFunction, cron}) =>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{nextOccurrences.length > 0 && (
|
{nextOccurrences.length > 0 && (
|
||||||
<div className={style.results}>
|
// <div className={style.results}>
|
||||||
<div className={style.resultsTitle}>最近触发时间:</div>
|
// <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) => (
|
{nextOccurrences.map((item, index) => (
|
||||||
<div className={style.occurrenceItem} key={index}>
|
<div className={style.occurrenceItem} key={index}>
|
||||||
<div>{dayjs(item).format('YYYY-MM-DD HH:mm')}</div>
|
<div>{dayjs(item).format('YYYY-MM-DD HH:mm')}</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</Modal>
|
||||||
|
// </div>
|
||||||
)}
|
)}
|
||||||
<Button type="primary" onClick={onClickConfirmCron}>
|
{/*</div>*/}
|
||||||
确认
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</Modal>
|
</Modal>
|
||||||
</Fragment>
|
</Fragment>
|
||||||
|
|
||||||
|
|
|
@ -1,27 +1,25 @@
|
||||||
import React, {CSSProperties, Fragment, useEffect, useState} from "react";
|
import React, {CSSProperties, Fragment, useEffect, useState} from "react";
|
||||||
import {DragDropContext, Droppable, Draggable, DropResult, DraggingStyle, NotDraggingStyle} from "react-beautiful-dnd";
|
import {DragDropContext, Droppable, Draggable, DropResult, DraggingStyle, NotDraggingStyle} from "react-beautiful-dnd";
|
||||||
import {TaskStepSortVO} from "@/components/type/TaskSort.d";
|
import {TaskStepSortOperateVO, TaskStepSortVO} from "@/components/type/TaskSort.d";
|
||||||
import {Button, Drawer, message, Modal} from "antd";
|
import {Button, Drawer, Dropdown, MenuProps, message, Modal, Popconfirm} from "antd";
|
||||||
import TextArea from "antd/es/input/TextArea";
|
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 reorder = (list: TaskStepSortVO[], startIndex: number, endIndex: number) => {
|
||||||
const result = Array.from(list);
|
const result = Array.from(list);
|
||||||
const [removed] = result.splice(startIndex, 1);
|
const [removed] = result.splice(startIndex, 1);
|
||||||
result.splice(endIndex, 0, removed);
|
result.splice(endIndex, 0, removed);
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
const grid = 8;
|
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) => ({
|
const getListStyle = (isDraggingOver: boolean) => ({
|
||||||
background: isDraggingOver ? "lightblue" : "white",
|
background: isDraggingOver ? "lightblue" : "white",
|
||||||
width: "100%"
|
width: "100%"
|
||||||
|
@ -32,52 +30,201 @@ const StepSort = (props: { taskId: string, stepList: TaskStepSortVO[] }) => {
|
||||||
// 抽屉 start
|
// 抽屉 start
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
const [dialogueOpen, setDialogueOpen] = useState(false);
|
const [dialogueOpen, setDialogueOpen] = useState(false);
|
||||||
const [sortItemList, setSortItemList] = useState<TaskStepSortVO[]>([]);
|
const [clickTaskSortItem, setClickTaskSortItem] = useState<TaskStepSortVO>();
|
||||||
const [modalText, setModalText] = useState<TaskStepSortVO>({
|
const [clickSortItemIndex, setClickSortItemIndex]
|
||||||
|
= useState<number|undefined>(undefined);
|
||||||
|
const initModalText = {
|
||||||
id: "",
|
id: "",
|
||||||
stepDesc: undefined,
|
stepDesc: undefined,
|
||||||
sortIndex: undefined,
|
sortIndex: undefined,
|
||||||
taskId: props.taskId,
|
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(() => {
|
useEffect(() => {
|
||||||
// 根据任务id查找步骤
|
// 根据任务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;
|
const {source, destination} = result;
|
||||||
// dropped outside the list
|
// dropped outside the list
|
||||||
if (!destination) {
|
if (!destination) {
|
||||||
return;
|
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);
|
const items = reorder(state, source.index, destination.index);
|
||||||
let newState = [...state];
|
let newState = [...state];
|
||||||
newState = items;
|
newState = items;
|
||||||
setState(newState);
|
setState(newState);
|
||||||
props.stepList.length=0
|
props.stepList.length = 0
|
||||||
props.stepList.push(...newState)
|
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 [confirmButtonLoading, setConfirmButtonLoading] = useState(false);
|
||||||
const confirmModalTextArea = () => {
|
const confirmModalTextArea = () => {
|
||||||
setConfirmButtonLoading(true)
|
setConfirmButtonLoading(true)
|
||||||
|
// 修改
|
||||||
|
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 => {
|
addStepItemAPI(modalText).then(res => {
|
||||||
if (res.data.status.success) {
|
if (res.data.status.success) {
|
||||||
message.info("添加步骤成功")
|
message.info("添加步骤成功")
|
||||||
setModalText(
|
setModalText(initModalText)
|
||||||
{...modalText, stepDesc: undefined})
|
|
||||||
setState([...state, res.data.data])
|
setState([...state, res.data.data])
|
||||||
props.stepList.length=0
|
props.stepList.length = 0
|
||||||
props.stepList.push(...state, res.data.data)
|
props.stepList.push(...state, res.data.data)
|
||||||
setDialogueOpen(false);
|
setDialogueOpen(false);
|
||||||
} else {
|
} else {
|
||||||
message.error(res.data.status.message)
|
message.error(res.data.status.message)
|
||||||
}
|
}
|
||||||
}).finally(()=>{
|
}).finally(() => {
|
||||||
setConfirmButtonLoading(false)
|
setConfirmButtonLoading(false)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
|
@ -103,7 +250,7 @@ const StepSort = (props: { taskId: string, stepList: TaskStepSortVO[] }) => {
|
||||||
bottom: "0px"
|
bottom: "0px"
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
value={modalText.sortIndex}
|
value={modalText.stepDesc}
|
||||||
maxLength={255} autoFocus
|
maxLength={255} autoFocus
|
||||||
onChange={(e) => setModalText(
|
onChange={(e) => setModalText(
|
||||||
{...modalText, stepDesc: e.target.value})}
|
{...modalText, stepDesc: e.target.value})}
|
||||||
|
@ -131,6 +278,7 @@ const StepSort = (props: { taskId: string, stepList: TaskStepSortVO[] }) => {
|
||||||
open={open}
|
open={open}
|
||||||
footer={<Button type={"primary"} onClick={() => setDialogueOpen(true)}>添加步骤</Button>}
|
footer={<Button type={"primary"} onClick={() => setDialogueOpen(true)}>添加步骤</Button>}
|
||||||
>
|
>
|
||||||
|
<Dropdown menu={{items}} trigger={['contextMenu']}>
|
||||||
<div style={{display: "flex"}}>
|
<div style={{display: "flex"}}>
|
||||||
<DragDropContext onDragEnd={onDragEnd}>
|
<DragDropContext onDragEnd={onDragEnd}>
|
||||||
<Droppable key="sortDroppable" droppableId="sortDroppableId">
|
<Droppable key="sortDroppable" droppableId="sortDroppableId">
|
||||||
|
@ -153,16 +301,22 @@ const StepSort = (props: { taskId: string, stepList: TaskStepSortVO[] }) => {
|
||||||
{...provided.dragHandleProps}
|
{...provided.dragHandleProps}
|
||||||
style={getItemStyle(
|
style={getItemStyle(
|
||||||
snapshot.isDragging,
|
snapshot.isDragging,
|
||||||
provided.draggableProps.style
|
provided.draggableProps.style,
|
||||||
|
item.id
|
||||||
)}
|
)}
|
||||||
|
onClick={() => onClickTaskSortItem(item, "L",index)}
|
||||||
|
onContextMenu={() => onClickTaskSortItem(item, "R",index)}
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
{`步骤${index + 1}`}
|
{`步骤${index + 1}`}
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
>
|
>
|
||||||
<div style={{whiteSpace: 'pre-line',border: "solid",
|
<div style={{
|
||||||
borderRadius: grid,}}>
|
whiteSpace: 'pre-line', border: "solid",
|
||||||
|
borderRadius: grid,
|
||||||
|
borderColor: clickTaskSortItem && clickTaskSortItem.id == item.id ? "#1677ff" : "black",
|
||||||
|
}}>
|
||||||
{item.stepDesc}
|
{item.stepDesc}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -176,6 +330,7 @@ const StepSort = (props: { taskId: string, stepList: TaskStepSortVO[] }) => {
|
||||||
</Droppable>
|
</Droppable>
|
||||||
</DragDropContext>
|
</DragDropContext>
|
||||||
</div>
|
</div>
|
||||||
|
</Dropdown>
|
||||||
</Drawer>
|
</Drawer>
|
||||||
</Fragment>
|
</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}`)
|
||||||
|
}
|
|
@ -7,3 +7,20 @@ export const addStepItemAPI= (data:TaskStepSortVO):Promise<AxiosResponse<Respons
|
||||||
return httpReq.post(process.env.NEXT_PUBLIC_TODO_REQUEST_URL + `/task/step/sort/item`,
|
return httpReq.post(process.env.NEXT_PUBLIC_TODO_REQUEST_URL + `/task/step/sort/item`,
|
||||||
data)
|
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,
|
id: string,
|
||||||
sortIndex: number|undefined,
|
sortIndex: number|undefined,
|
||||||
stepDesc: string|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 React from "react";
|
||||||
import {Dayjs} from "dayjs";
|
import {Dayjs} from "dayjs";
|
||||||
import {TaskStepSortVO} from "@/components/type/TaskSort.d";
|
import {TaskStepSortVO} from "@/components/type/TaskSort.d";
|
||||||
|
import {Data} from "@dnd-kit/core";
|
||||||
|
|
||||||
export type Request<T>={
|
export type Request<T>={
|
||||||
data:T,
|
data:T,
|
||||||
|
@ -40,6 +41,9 @@ export type TaskMessage ={
|
||||||
stepList?:TaskStepSortVO[];
|
stepList?:TaskStepSortVO[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// export type TaskSortMessage=Data&{
|
||||||
|
// stepList?:TaskStepSortVO[];
|
||||||
|
// }
|
||||||
|
|
||||||
export type DataType = TaskMessage&{
|
export type DataType = TaskMessage&{
|
||||||
key: React.ReactNode;
|
key: React.ReactNode;
|
||||||
|
|
|
@ -55,7 +55,7 @@ export const DetailModelForm: React.FC<DetailModelFormProps> = (props) => {
|
||||||
// 团队第一层 pid必须为0
|
// 团队第一层 pid必须为0
|
||||||
const [taskType, setTaskType] = useState('0')
|
const [taskType, setTaskType] = useState('0')
|
||||||
const [spinning, setSpinning] = useState(true)
|
const [spinning, setSpinning] = useState(true)
|
||||||
const [operationRequest,setOperationRequest] = useState(false)
|
const [operationRequest, setOperationRequest] = useState(false)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (props.itemId != undefined && (
|
if (props.itemId != undefined && (
|
||||||
props.operationId === OPERATION_BUTTON_TYPE.DETAIL || props.operationId === OPERATION_BUTTON_TYPE.UPDATE)) {
|
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
|
// 推荐使用 request,或者 initialValues ? <Form/> : null
|
||||||
return (
|
return (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<Spin spinning={spinning} fullscreen />
|
<Spin spinning={spinning} fullscreen/>
|
||||||
<ModalForm<DataType>
|
<ModalForm<DataType>
|
||||||
title={props.description}
|
title={props.description}
|
||||||
open={!spinning&&props.open && !props.haveButton}
|
open={!spinning && props.open && !props.haveButton}
|
||||||
trigger={props.haveButton ?
|
trigger={props.haveButton ?
|
||||||
<Button type="primary">
|
<Button type="primary">
|
||||||
<PlusOutlined/>
|
<PlusOutlined/>
|
||||||
|
@ -132,7 +132,7 @@ export const DetailModelForm: React.FC<DetailModelFormProps> = (props) => {
|
||||||
autoFocusFirstInput
|
autoFocusFirstInput
|
||||||
modalProps={{
|
modalProps={{
|
||||||
destroyOnClose: true,
|
destroyOnClose: true,
|
||||||
maskClosable:false,
|
maskClosable: false,
|
||||||
onCancel: () => {
|
onCancel: () => {
|
||||||
props.reloadData?.();
|
props.reloadData?.();
|
||||||
},
|
},
|
||||||
|
@ -189,7 +189,7 @@ export const DetailModelForm: React.FC<DetailModelFormProps> = (props) => {
|
||||||
onClick={() => props.closeOpen?.()}>关闭</Button>)
|
onClick={() => props.closeOpen?.()}>关闭</Button>)
|
||||||
}
|
}
|
||||||
if (taskType == '2') {
|
if (taskType == '2') {
|
||||||
result.push(<StepSort taskId={props.itemId!} stepList={requestTask?.stepList||[]}/>)
|
result.push(<StepSort taskId={props.itemId!} stepList={requestTask?.stepList || []}/>)
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
},
|
},
|
||||||
|
@ -433,8 +433,9 @@ export const DetailModelForm: React.FC<DetailModelFormProps> = (props) => {
|
||||||
disabled={editFormDisable}
|
disabled={editFormDisable}
|
||||||
/>
|
/>
|
||||||
</ProForm.Group>
|
</ProForm.Group>
|
||||||
{taskType=="3"&&<SettingCron setCronFunction={
|
{taskType == "3" && <SettingCron setCronFunction={
|
||||||
()=>{}
|
() => {
|
||||||
|
}
|
||||||
}/>}
|
}/>}
|
||||||
|
|
||||||
</ModalForm>
|
</ModalForm>
|
||||||
|
|
Loading…
Reference in New Issue