feat:二维码下载

This commit is contained in:
1708-huayu 2025-07-29 18:47:26 +08:00
parent 5493acab8e
commit 4e3bc8ddc2
8 changed files with 904 additions and 371 deletions

View File

@ -16,16 +16,13 @@ import {OPERATION_BUTTON_TYPE} from "@/lib/task/project/data";
const DiaryOption = (props: SelectDiary) => { const DiaryOption = (props: SelectDiary) => {
// 抽屉 start // 抽屉 start
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
const showDrawer = () => {
setOpen(true);
};
const onClose = () => { const onClose = () => {
setOpen(false); setOpen(false);
}; };
// 抽屉 end // 抽屉 end
const [shouldScroll, setShouldScroll] = React.useState(true); const [shouldScroll, setShouldScroll] = React.useState(true);
// 设置高度 start // 设置高度 start
const { height } = useWindowSize(); const {height} = useWindowSize();
const [containerHeight, setContainerHeight] = useState(400); const [containerHeight, setContainerHeight] = useState(400);
useEffect(() => { useEffect(() => {
setDiaryList([]) setDiaryList([])
@ -55,7 +52,7 @@ const DiaryOption = (props: SelectDiary) => {
// observer.observe(contentRef.current); // observer.observe(contentRef.current);
// //
// return () => observer.disconnect(); // return () => observer.disconnect();
}, [open,height]); }, [open, height]);
// 设置高度 end // 设置高度 end
// 头按钮设置 start // 头按钮设置 start
@ -99,7 +96,7 @@ const DiaryOption = (props: SelectDiary) => {
} }
const appendData = (showMessage = true) => { const appendData = (showMessage = true) => {
if (!open){ if (!open) {
return return
} }
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`;
@ -202,58 +199,58 @@ const DiaryOption = (props: SelectDiary) => {
const [popConfirmLoading, setPopConfirmLoading] = useState(false); const [popConfirmLoading, setPopConfirmLoading] = useState(false);
const popConfirmOk = () => { const popConfirmOk = () => {
setPopConfirmLoading(true) setPopConfirmLoading(true)
if (clickTaskDiary){ if (clickTaskDiary) {
deleteTaskLogByIdAPI(clickTaskDiary.id).then(res=>{ deleteTaskLogByIdAPI(clickTaskDiary.id).then(res => {
if (res.data.status.success){ if (res.data.status.success) {
setDiaryList(diaryList.filter(taskLog => taskLog.id != clickTaskDiary.id)) setDiaryList(diaryList.filter(taskLog => taskLog.id != clickTaskDiary.id))
message.info("删除成功") message.info("删除成功")
setPopConfirmLoading(false) setPopConfirmLoading(false)
setPopConfirmOpen(false) setPopConfirmOpen(false)
}else { } else {
message.error(res.data.status.message) message.error(res.data.status.message)
} }
}) })
} }
} }
const handleCancel = () =>{ const handleCancel = () => {
setPopConfirmOpen(false) setPopConfirmOpen(false)
} }
const editEnableFlag = (enableFlag:string) => { const editEnableFlag = (enableFlag: string) => {
if (clickTaskDiary){ if (clickTaskDiary) {
editEnableFlagAPI(clickTaskDiary.id,enableFlag).then(res=>{ editEnableFlagAPI(clickTaskDiary.id, enableFlag).then(res => {
if (res.data.status.success){ if (res.data.status.success) {
setDiaryList(diaryList.map(taskLog => { setDiaryList(diaryList.map(taskLog => {
if(taskLog.id == clickTaskDiary.id){ if (taskLog.id == clickTaskDiary.id) {
taskLog.enableFlag = enableFlag; taskLog.enableFlag = enableFlag;
} }
return taskLog; return taskLog;
})) }))
message.info("设置成功") message.info("设置成功")
}else { } else {
message.error(res.data.status.message) message.error(res.data.status.message)
} }
}) })
} }
} }
const [addTaskOpen,setAddTaskOpen] = useState(false) const [addTaskOpen, setAddTaskOpen] = useState(false)
const commonItems: MenuProps['items'] = [ const commonItems: MenuProps['items'] = [
{ {
label: '复制', label: '复制',
key: '1', key: '1',
onClick: () => { onClick: () => {
copyToClipboard(clickTaskDiary!.description).then(res=>{ copyToClipboard(clickTaskDiary!.description).then(res => {
res && message.info(`复制成功${clickTaskDiary!.description.length>5?clickTaskDiary!.description.substring(0,5)+"...":clickTaskDiary!.description}`) res && message.info(`复制成功${clickTaskDiary!.description.length > 5 ? clickTaskDiary!.description.substring(0, 5) + "..." : clickTaskDiary!.description}`)
}) })
} }
}, },
{ {
label: '创建计划', label: '创建计划',
key: '3', key: '3',
onClick:()=>{ onClick: () => {
// 打开添加任务窗口 // 打开添加任务窗口
setAddTaskOpen(true) setAddTaskOpen(true)
} }
}, },
@ -267,28 +264,26 @@ const DiaryOption = (props: SelectDiary) => {
{ {
label: '取消', label: '取消',
key: '5', key: '5',
onClick:()=>{ onClick: () => {
setClickTaskDiary(undefined) setClickTaskDiary(undefined)
} }
}, },
] ]
const items: MenuProps['items'] = [ const items: MenuProps['items'] = [];
];
items.push(...commonItems) items.push(...commonItems)
items.splice(1,0,{ items.splice(1, 0, {
label: '失效', label: '失效',
key: '2', key: '2',
onClick:()=>{ onClick: () => {
editEnableFlag("0") editEnableFlag("0")
}, },
}) })
const itemsEnable: MenuProps['items'] = [ const itemsEnable: MenuProps['items'] = [];
];
itemsEnable.push(...commonItems) itemsEnable.push(...commonItems)
itemsEnable.splice(1,0,{ itemsEnable.splice(1, 0, {
label: '生效', label: '生效',
key: '2', key: '2',
onClick:()=>{ onClick: () => {
editEnableFlag("1") editEnableFlag("1")
}, },
}) })
@ -296,7 +291,7 @@ const DiaryOption = (props: SelectDiary) => {
return ( return (
<Fragment> <Fragment>
<Button type="primary" onClick={showDrawer}> <Button type={open?"primary":"default"} onClick={() => setOpen(!open)}>
</Button> </Button>
<Drawer <Drawer
@ -394,7 +389,7 @@ const DiaryOption = (props: SelectDiary) => {
<Popconfirm <Popconfirm
title="警告" title="警告"
description={`确认要删除日志:${item.description.length > 5 ? item.description.substring(0, 5) + '...' : item.description}?`} description={`确认要删除日志:${item.description.length > 5 ? item.description.substring(0, 5) + '...' : item.description}?`}
open={popConfirmOpen&&clickTaskDiary?.id==item.id} open={popConfirmOpen && clickTaskDiary?.id == item.id}
onConfirm={popConfirmOk} onConfirm={popConfirmOk}
okButtonProps={{loading: popConfirmLoading}} okButtonProps={{loading: popConfirmLoading}}
onCancel={handleCancel} onCancel={handleCancel}
@ -420,13 +415,13 @@ const DiaryOption = (props: SelectDiary) => {
</VirtualList> </VirtualList>
</List> </List>
</Drawer> </Drawer>
{addTaskOpen&&<DetailModelForm pid={props.taskId} {addTaskOpen && <DetailModelForm pid={props.taskId}
operationId={OPERATION_BUTTON_TYPE.ADD_CHILD} operationId={OPERATION_BUTTON_TYPE.ADD_CHILD}
description={"新增计划"} description={"新增计划"}
open={addTaskOpen} open={addTaskOpen}
haveButton={false} haveButton={false}
reloadData={()=>setAddTaskOpen(false)} reloadData={() => setAddTaskOpen(false)}
taskContent={clickTaskDiary?.description} taskContent={clickTaskDiary?.description}
/>} />}
</Fragment> </Fragment>
); );

View File

@ -0,0 +1,270 @@
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"
type PresetType = 'everyMinute' | 'everyHour' | 'daily' | 'weekly' | 'monthly';
interface CronGeneratorProps {
setCronFunction: (data: boolean) => void;
cron?: string;
}
const CronGenerator: React.FC<CronGeneratorProps> = ({setCronFunction, cron}) => {
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] : '*');
const [cronHours, setCronHours] = useState<string>(cron ? cron.split(' ')[2] : '*');
const [cronDayOfMonth, setCronDayOfMonth] = useState<string>(cron ? cron.split(' ')[3] : '*');
const [cronMonth, setCronMonth] = useState<string>(cron ? cron.split(' ')[4] : '*');
const [cronDayOfWeek, setCronDayOfWeek] = useState<string>(cron ? cron.split(' ')[5] : '*');
const [everyNumber, setEveryNumber] = useState<number>(1);
const [nextOccurrences, setNextOccurrences] = useState<Dayjs[]>([]);
const [fullCronExpression, setFullCronExpression] = useState<string>(
`${cronMinutes} ${cronHours} ${cronDayOfMonth} ${cronMonth} ${cronDayOfWeek}`.trim()
);
const titleItem = ['具体时间', '周期', '自定义cron'];
useEffect(() => {
setFullCronExpression(`${cronMinutes} ${cronHours} ${cronDayOfMonth} ${cronMonth} ${cronDayOfWeek}`.trim());
}, [cronMinutes, cronHours, cronDayOfMonth, cronMonth, cronDayOfWeek]);
const onClickItem = (index: number) => {
setCurrent(index);
};
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);
// });
console.log('Cron confirmed:', fullCronExpression);
setCronFunction(false);
} catch (error) {
console.error('Cron expression is invalid', error);
}
} else {
setCronFunction(false);
}
};
const resetOccurrences = () => {
setCronSeconds('0');
setCronMinutes('*');
setCronHours('*');
setCronDayOfMonth('*');
setCronMonth('*');
setCronDayOfWeek('*');
};
const generateNextOccurrences = () => {
try {
if (!fullCronExpression) {
console.error('Please set the next generation time correctly');
return;
}
console.log('cron' + fullCronExpression);
// Replace generateNextTimeAPI with your actual API call
// generateNextTimeAPI('0 ' + fullCronExpression).then(res => {
// setNextOccurrences(res.map((next: string) => dayjs(next)));
// });
} catch (error) {
console.error('Cron expression is invalid', error);
}
};
const setPreset = (type: PresetType) => {
switch (type) {
case 'everyMinute':
setCronSeconds('0');
setCronMinutes(`*/${everyNumber}`);
setCronHours('*');
setCronDayOfMonth('*');
setCronMonth('*');
setCronDayOfWeek('*');
break;
case 'everyHour':
setCronSeconds('0');
setCronMinutes('0');
setCronHours(`*/${everyNumber}`);
setCronDayOfMonth('*');
setCronMonth('*');
setCronDayOfWeek('*');
break;
case 'daily':
setCronSeconds('0');
setCronMinutes('0');
setCronHours('0');
setCronDayOfMonth(`*/${everyNumber}`);
setCronMonth('*');
setCronDayOfWeek('*');
break;
case 'weekly':
setCronSeconds('0');
setCronMinutes('0');
setCronHours('0');
setCronDayOfMonth('*');
setCronMonth('*');
setCronDayOfWeek(`${everyNumber}`);
break;
case 'monthly':
setCronSeconds('0');
setCronMinutes('0');
setCronHours('0');
setCronDayOfMonth('1');
setCronMonth('*');
setCronDayOfWeek('*');
break;
}
};
return (
<Fragment>
<Button type="primary" onClick={()=>setShowModal(!showModal)}>
</Button>
<Modal
title="设置时间"
closable={{ 'aria-label': 'Custom Close Button' }}
open={showModal}
// onOk={handleOk}
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>
{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>
))}
</div>
)}
<Button type="primary" onClick={onClickConfirmCron}>
</Button>
</div>
</Modal>
</Fragment>
);
};
export default CronGenerator;

View File

@ -0,0 +1,87 @@
.container {
padding: 20px;
background-color: #f5f5f5;
min-height: 100vh;
}
.header {
margin-bottom: 20px;
}
.title {
font-size: 20px;
font-weight: bold;
color: #000;
}
.content {
margin-top: 10px;
}
.cronForm {
background-color: #fff;
border-radius: 8px;
padding: 15px;
margin-bottom: 20px;
}
.formItem {
margin-bottom: 15px;
}
.label {
display: block;
margin-bottom: 5px;
font-size: 14px;
color: #333;
}
.input {
width: 90%;
padding: 10px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 14px;
}
.results {
background-color: #fff;
border-radius: 8px;
padding: 15px;
margin-bottom: 20px;
}
.resultsTitle {
font-weight: bold;
margin-bottom: 10px;
display: block;
}
.occurrence-item {
padding: 8px 0;
border-bottom: 1px solid #eee;
}
.presetButtons {
display: flex;
flex-wrap: wrap;
gap: 10px;
margin-bottom: 10px;
}
.buttonFlex {
display: flex;
gap: 10px;
margin-top: 10px;
}
.presetBtn {
background-color: #f0f0f0;
color: #000;
padding: 8px 12px;
border-radius: 4px;
font-size: 14px;
flex: 1;
min-width: 100px;
border: 1px solid #d9d9d9;
}

View File

@ -1,39 +1,60 @@
import React, {Fragment, useEffect, useState} from 'react'; import React, {Fragment, useEffect, useState} from 'react';
import {Button, Image, Modal, QRCode} from 'antd'; import {Button, Image, Input, Modal, QRCode, Space } from 'antd';
import { v4 as uuidv4 } from 'uuid'; import {v4 as uuidv4} from 'uuid';
import {copyToClipboard} from "@/lib/copyToClipboard";
const ShareOption = (props:{taskId:string} ) => { const ShareOption = (props: { taskId: string }) => {
const [loading, setLoading] = useState(false); // const [loading, setLoading] = useState(false);
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
const [buttonIndex, setButtonIndex] = useState(0); const [buttonIndex, setButtonIndex] = useState(1);
const [qrCodeValue, setQrCodeValue] = useState<string>("-"); const [qrCodeValue, setQrCodeValue] = useState<string>("-");
const [qrCodeStatus, setQrCodeStatus]=useState<'active' | 'expired' | 'loading' | 'scanned'>("active"); const [qrCodeStatus, setQrCodeStatus] = useState<'active' | 'expired' | 'loading' | 'scanned'>("loading");
const generateQrcode =()=>{ function doDownload(url: string, fileName: string) {
const a = document.createElement('a');
a.download = fileName;
a.href = url;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
}
const downloadCanvasQRCode = () => {
const canvas = document.getElementById('myqrcode')?.querySelector<HTMLCanvasElement>('canvas');
if (canvas) {
const url = canvas.toDataURL();
doDownload(url, 'QRCode.png');
}
};
const downloadSvgQRCode = () => {
const svg = document.getElementById('myqrcode')?.querySelector<SVGElement>('svg');
const svgData = new XMLSerializer().serializeToString(svg!);
const blob = new Blob([svgData], {type: 'image/svg+xml;charset=utf-8'});
const url = URL.createObjectURL(blob);
console.log({url})
doDownload(url, '微信小程序马上行计划管理扫码加入计划.svg');
};
const generateQrcode = () => {
} }
const showModal = () => { const showModal = () => {
setOpen(true); setOpen(true);
}; };
const handleOk = () => {
setLoading(true);
setTimeout(() => {
setLoading(false);
setOpen(false);
}, 3000);
};
const handleCancel = () => { const handleCancel = () => {
setOpen(false); setOpen(false);
}; };
useEffect(() => { useEffect(() => {
// 分享人必须有权限 // 分享人必须有权限
// 生成分享信息同时适用链接和二维码
const clientId: string = uuidv4(); const clientId: string = uuidv4();
let qrCodeData={ let qrCodeData = {
taskId:props.taskId,pass:clientId,local:"马上行计划管理",opType:"SHARE_OPTION" taskId: props.taskId, pass: clientId, local: "马上行计划管理", opType: "SHARE_OPTION"
} }
setQrCodeValue(JSON.stringify(qrCodeData)) setQrCodeValue(JSON.stringify(qrCodeData))
setQrCodeStatus("active")
}, []); }, []);
return ( return (
@ -43,41 +64,55 @@ const ShareOption = (props:{taskId:string} ) => {
</Button> </Button>
<Modal <Modal
open={open} open={open}
title="Title" title="分享好友"
onOk={handleOk} // onOk={handleOk}
onCancel={handleCancel} onCancel={handleCancel}
width={700}
footer={[ footer={[
<Button key="back" onClick={handleCancel}> <Button key="back" onClick={handleCancel}>
</Button>, </Button>,
<Button key="submit" type="primary" loading={loading} onClick={handleOk}> <Button key="qrcode"
type={buttonIndex == 1 ? "primary" : "default"}
// loading={loading}
onClick={() => setButtonIndex(1)}>
使 使
</Button>, </Button>,
<Button <Button
key="link" key="link"
href="https://google.com" // href="https://google.com"
target="_blank" // target="_blank"
type="primary" type={buttonIndex == 2 ? "primary" : "default"}
loading={loading} // loading={loading}
onClick={handleOk} onClick={() => setButtonIndex(2)}
> >
使 使
</Button>, </Button>,
]} ]}
> >
{buttonIndex==1&& <div> {buttonIndex == 1 && <div className="displayFlexRow">
<div> <div className="displayFlexColumn">
<div className="title"></div> <div className="title"></div>
<Image width={300} src="/static/pc-Web.png"/> <Image width={300} src="/static/pc-Web.png"/>
<Button></Button> <Button
onClick={() => doDownload("/static/pc-Web.png", '微信小程序马上行计划管理.png')}></Button>
</div> </div>
<div> <div className="displayFlexColumn" id="myqrcode">
<div className="title">7</div> <div className="title">7</div>
<QRCode value={qrCodeValue} size={300} status={qrCodeStatus} onRefresh={generateQrcode}/> <QRCode type="svg" value={qrCodeValue} size={300} status={qrCodeStatus}
<Button></Button> onRefresh={generateQrcode}/>
<Button onClick={downloadSvgQRCode}></Button>
</div> </div>
</div>} </div>}
{buttonIndex == 2 && <div>
<div className="title">7</div>
<Space.Compact style={{width: '100%'}}>
<Input defaultValue="https://www.huaruyu.com" readOnly/>
<Button loading={qrCodeStatus=="loading"}
onClick={()=>copyToClipboard("https://www.huaruyu.com")}
type="primary"></Button>
</Space.Compact>
</div>}
</Modal> </Modal>
</Fragment> </Fragment>
); );

112
src/components/StepSort.tsx Normal file
View File

@ -0,0 +1,112 @@
import React, {CSSProperties, Fragment, useState} from "react";
import {DragDropContext, Droppable, Draggable, DropResult, DraggingStyle, NotDraggingStyle} from "react-beautiful-dnd";
import {TaskStepSortVO} from "@/components/type/TaskSort.d";
import {Button, Drawer} from "antd";
// fake data generator
const getItems = (count: number, offset = 0) =>
Array.from({length: count}, (v, k) => k).map(k => ({
id: `item-${k + offset}-${new Date().getTime()}`,
stepDesc: `item ${k + offset}`,
sortIndex: k + offset,
}));
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 * 2,
margin: `0 0 ${grid}px 0`,
// change background colour if dragging
background: isDragging ? "lightgreen" : "grey",
// styles we need to apply on draggables
...draggableStyle
});
const getListStyle = (isDraggingOver: boolean) => ({
background: isDraggingOver ? "lightblue" : "lightgrey",
padding: grid,
width: 250
});
const StepSort = (props: { taskId: string }) => {
const [state, setState] = useState<TaskStepSortVO[]>(getItems(5, 10));
// 抽屉 start
const [open, setOpen] = useState(false);
function onDragEnd(result: DropResult) {
const {source, destination} = result;
// dropped outside the list
if (!destination) {
return;
}
const items = reorder(state, source.index, destination.index);
let newState = [...state];
newState = items;
setState(newState);
}
return (
<Fragment>
<Button type={open ? "primary" : "default"} onClick={() => setOpen(!open)}>
</Button>
<Drawer>
<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
style={{
display: "flex",
justifyContent: "space-around"
}}
>
{item.stepDesc}
</div>
</div>
)}
</Draggable>
))}
{provided.placeholder}
</div>
)}
</Droppable>
</DragDropContext>
</div>
</Drawer>
</Fragment>
);
}
export default StepSort

View File

@ -0,0 +1,5 @@
export type TaskStepSortVO = {
id: string,
sortIndex: number,
stepDesc: string
}

View File

@ -148,7 +148,7 @@ const CalShow: React.FC = () => {
clearClickTimeout() clearClickTimeout()
clickRef.current = window.setTimeout(() => { clickRef.current = window.setTimeout(() => {
// window.alert(event.title); // window.alert(event.title);
console.log(event) console.log("event")
setOperationId(OPERATION_BUTTON_TYPE.DETAIL) setOperationId(OPERATION_BUTTON_TYPE.DETAIL)
setDescription("任务详情") setDescription("任务详情")
setItemId(event.resource) setItemId(event.resource)
@ -265,7 +265,10 @@ const CalShow: React.FC = () => {
} }
return <div className="App" style={{height: '90vh'}}> return <div className="App" style={{height: '90vh'}}>
{open && <DetailModelForm operationId={operationId} description={description} open={open} haveButton={false} {open && <DetailModelForm operationId={operationId}
description={description}
open={open}
haveButton={false}
itemId={itemId} itemId={itemId}
reloadData={reloadData} expectedStartTime={expectedStartTime} reloadData={reloadData} expectedStartTime={expectedStartTime}
closeOpen={()=>setOpen(false)} closeOpen={()=>setOpen(false)}

View File

@ -6,8 +6,8 @@ import {
ProFormSelect, ProFormSelect,
ProFormText, ProFormTextArea, ProFormTreeSelect, ProFormText, ProFormTextArea, ProFormTreeSelect,
} from '@ant-design/pro-components'; } from '@ant-design/pro-components';
import {Button, Form, message, Popconfirm} from 'antd'; import {Button, Form, message, Popconfirm, Spin} from 'antd';
import React, {useEffect, useState} from "react"; import React, {Fragment, useEffect, useState} from "react";
import { import {
addTask, deleteTask, getTask, addTask, deleteTask, getTask,
getTaskTreeResult, getTaskTreeResult,
@ -19,12 +19,14 @@ import {DataType} from "@/lib/definitions";
import dayjs, {Dayjs} from "dayjs"; import dayjs, {Dayjs} from "dayjs";
import DiaryOption from "@/components/DiaryOption"; import DiaryOption from "@/components/DiaryOption";
import ShareOption from "@/components/ShareOption"; import ShareOption from "@/components/ShareOption";
import StepSort from "@/components/StepSort";
import SettingCron from "@/components/SettingCron";
export type DetailModelFormProps = { export type DetailModelFormProps = {
// 当前内容id // 当前内容id
itemId?: string, itemId?: string,
pid?: string, pid?: string,
taskType?:string, taskType?: string,
// 祖宗任务id // 祖宗任务id
pPid?: string, pPid?: string,
// 操作id // 操作id
@ -52,6 +54,8 @@ export const DetailModelForm: React.FC<DetailModelFormProps> = (props) => {
const [editFormDisable, setEditFormDisable] = useState(props.operationId === OPERATION_BUTTON_TYPE.DETAIL) const [editFormDisable, setEditFormDisable] = useState(props.operationId === OPERATION_BUTTON_TYPE.DETAIL)
// 团队第一层 pid必须为0 // 团队第一层 pid必须为0
const [taskType, setTaskType] = useState('0') const [taskType, setTaskType] = useState('0')
const [spinning, setSpinning] = useState(true)
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)) {
@ -69,13 +73,19 @@ export const DetailModelForm: React.FC<DetailModelFormProps> = (props) => {
if (task.data.pid == "0") { if (task.data.pid == "0") {
form.setFieldValue("pid", undefined) form.setFieldValue("pid", undefined)
} }
if (task.data.taskType) {
setTaskType(task.data.taskType)
}
setRequestTask(task.data) setRequestTask(task.data)
console.log("form.setFieldsValue(task.data)" + JSON.stringify(task.data)) console.log("form.setFieldsValue(task.data)" + JSON.stringify(task.data))
} else { } else {
message.error(task.status.message); message.error(task.status.message);
props.reloadData?.() props.reloadData?.()
} }
}) }).finally(() => {
setSpinning(false)
}
)
} else if (props.operationId === OPERATION_BUTTON_TYPE.ADD || props.operationId === OPERATION_BUTTON_TYPE.ADD_CHILD) { } else if (props.operationId === OPERATION_BUTTON_TYPE.ADD || props.operationId === OPERATION_BUTTON_TYPE.ADD_CHILD) {
let data = { let data = {
'expectedTimeRange': [props.expectedStartTime ? props.expectedStartTime : dayjs(), props.expectedEndTime], 'expectedTimeRange': [props.expectedStartTime ? props.expectedStartTime : dayjs(), props.expectedEndTime],
@ -84,6 +94,7 @@ export const DetailModelForm: React.FC<DetailModelFormProps> = (props) => {
'name': props.taskContent && props.taskContent?.length > 10 ? props.taskContent.substring(0, 10) : props.taskContent, 'name': props.taskContent && props.taskContent?.length > 10 ? props.taskContent.substring(0, 10) : props.taskContent,
}; };
form.setFieldsValue(data) form.setFieldsValue(data)
setSpinning(false)
} }
}, [props]) }, [props])
@ -105,309 +116,324 @@ export const DetailModelForm: React.FC<DetailModelFormProps> = (props) => {
// 所以你需要用 setFieldsValue 来更新。 initialValues 只在 form 初始化时生效且只生效一次,如果你需要异步加载, // 所以你需要用 setFieldsValue 来更新。 initialValues 只在 form 初始化时生效且只生效一次,如果你需要异步加载,
// 推荐使用 request或者 initialValues ? <Form/> : null // 推荐使用 request或者 initialValues ? <Form/> : null
return ( return (
<ModalForm<DataType> <Fragment>
title={props.description} <Spin spinning={spinning} fullscreen />
open={props.open && !props.haveButton} <ModalForm<DataType>
trigger={props.haveButton ? title={props.description}
<Button type="primary"> open={!spinning&&props.open && !props.haveButton}
<PlusOutlined/> trigger={props.haveButton ?
{props.description} <Button type="primary">
</Button> : undefined <PlusOutlined/>
} {props.description}
form={form} </Button> : undefined
autoFocusFirstInput }
modalProps={{ form={form}
destroyOnClose: true, loading={operationRequest}
onCancel: () => { autoFocusFirstInput
props.reloadData?.(); modalProps={{
}, destroyOnClose: true,
}} onCancel: () => {
readonly={editFormDisable} props.reloadData?.();
submitter={props.itemId !== undefined && props.itemId !== '-1' ? { },
render: (prop, defaultDoms) => { }}
console.log("submitter render: ", {prop}) readonly={editFormDisable}
let result = [ submitter={props.itemId !== undefined && props.itemId !== '-1' ? {
editFormDisable ? <Button render: (prop, defaultDoms) => {
key="edit" console.log("submitter render: ", {prop})
onClick={() => { let result = [
// props.submit(); editFormDisable ? <Button
setEditFormDisable(false) key="edit"
}} onClick={() => {
></Button> : undefined, // props.submit();
props.operationId === OPERATION_BUTTON_TYPE.DETAIL || props.operationId === OPERATION_BUTTON_TYPE.UPDATE ? setEditFormDisable(false)
<Popconfirm
key='delete'
title="删除任务"
description="确认要删除任务?"
icon={<QuestionCircleOutlined style={{color: 'red'}}/>}
okText="确认"
cancelText="取消"
onConfirm={() => {
if (props.itemId !== undefined) {
deleteTask(props.itemId).then((response => {
console.log('response', response)
if (response.status.success) {
message.success("删除任务成功:" + response.data)
props.reloadData?.()
}
}));
}
}} }}
> ></Button> : undefined,
<Button type="primary" key="delete" danger> props.operationId === OPERATION_BUTTON_TYPE.DETAIL || props.operationId === OPERATION_BUTTON_TYPE.UPDATE ?
<Popconfirm
</Button> key='delete'
</Popconfirm> : undefined, title="删除任务"
requestTask && requestTask.id ? description="确认要删除任务?"
<DiaryOption key="diary" taskId={requestTask.id} taskName={requestTask.name}/> : undefined, icon={<QuestionCircleOutlined style={{color: 'red'}}/>}
] okText="确认"
cancelText="取消"
onConfirm={() => {
if (props.itemId !== undefined) {
deleteTask(props.itemId).then((response => {
console.log('response', response)
if (response.status.success) {
message.success("删除任务成功:" + response.data)
props.reloadData?.()
}
}));
}
}}
>
<Button type="primary" key="delete" danger>
</Button>
</Popconfirm> : undefined,
requestTask && requestTask.id ?
<DiaryOption key="diary" taskId={requestTask.id}
taskName={requestTask.name}/> : undefined,
]
// 非新增或者编辑状态不展示 // 非新增或者编辑状态不展示
if (!editFormDisable) { if (!editFormDisable) {
result.push(...defaultDoms) result.push(...defaultDoms)
} else {
if (editFormDisable && taskType == '1') {
result.push(<ShareOption taskId={props.itemId!}/>)
}
if (editFormDisable && taskType == '2') {
result.push(<StepSort taskId={props.itemId!}/>)
}
result.push(<Button type="primary" key="close"
onClick={() => props.closeOpen?.()}></Button>)
}
return result;
},
} : {
render: (prop, defaultDoms) => {
console.log("submitter render: ", {prop}, {props}, {taskType}, {defaultDoms})
// if ( prop.searchConfig) {
// if (taskType == '1') {
// prop.searchConfig.submitText = "创建团队"
// }else {
// prop.searchConfig.submitText = "确认"
// }
// }
// return defaultDoms;
const result = defaultDoms.filter(defaultButton => defaultButton.key == 'rest');
result.push(<Button type="primary" key="create-team"
onClick={() => form.submit()}>{taskType == '1' ? "创建团队" : "确认"}
</Button>)
return result;
}
}
}
onFinish={async (values) => {
{/* onFinish 返回true关闭窗口范湖false不关闭窗口 */
}
console.log('Received values of form: ', values, {...requestTask, ...values});
setOperationRequest(true)
if (requestTask) {
const {sortNo} = requestTask;
values.sortNo = sortNo;
}
if (values.pid === undefined) {
values.pid = '0'
}
if (values.expectedTimeRange?.[0] != undefined) {
values.expectedStartTime = dayjs(values.expectedTimeRange[0]).format()
}
if (values.expectedTimeRange?.[1] != undefined) {
values.expectedEndTime = dayjs(values.expectedTimeRange[1]).format()
}
if (values.actualTimeRange?.[0] != undefined) {
values.actualStartTime = dayjs(values.actualTimeRange[0]).toDate()
}
if (values.actualTimeRange?.[1] != undefined) {
values.actualEndTime = dayjs(values.actualTimeRange[1]).toDate()
}
var result: boolean = false;
let state = taskStateList.find(taskState => taskState.name === values.state?.toString());
if (state) {
values.state = state.code
}
let priority = taskPriorityList.find(taskPriority => taskPriority.name === values.priority?.toString())
if (priority) {
values.priority = priority.code
}
// todo 修改
if (props.operationId === OPERATION_BUTTON_TYPE.UPDATE || (props.operationId === OPERATION_BUTTON_TYPE.DETAIL && !editFormDisable)) {
await updateTask(values).then(response => {
console.log('response', response)
if (response.status.success) {
message.success("修改任务成功:" + response.data)
// 树任务重新刷新
// 四象限任务重新刷新
// 如果可以直接更新列表而不请求。。。。。。
console.log('props.reloadData?.()', props.reloadData)
props.reloadData?.()
result = true
} else {
message.error(response.status.message)
result = false
}
}
);
} else { } else {
if (props.taskType=='1') { await addTask(values).then(response => {
result.push(<ShareOption taskId={props.itemId!}/>) console.log('response', response)
} if (response.data.status.success) {
result.push(<Button type="primary" key="close" message.success(`添加计划${response.data.data.name}成功`)
onClick={() => props.closeOpen?.()}></Button>) // 树任务重新刷新
// 四象限任务重新刷新
// 如果可以直接更新列表而不请求。。。。。。
console.log('props.reloadData?.()', props.reloadData)
result = (taskType != '1')
props.reloadData?.()
} else {
message.error(response.data.status.message)
result = false
}
}
);
} }
setOperationRequest(false)
return result; return result;
}, }}
} : { >
render: (prop, defaultDoms) => { <ProFormText width="sm" name="id" hidden={true} label="主键"/>
console.log("submitter render: ", {prop}, {props},{taskType},{defaultDoms}) <ProFormText width="sm" name="code" initialValue={props.itemId} hidden={true} label="任务编码"/>
// if ( prop.searchConfig) { <ProFormText width="sm" name="pPid" initialValue={props.pPid} hidden={true} label="祖宗id"/>
// if (taskType == '1') { <ProForm.Group>
// prop.searchConfig.submitText = "创建团队" <ProFormSelect
// }else { required={true}
// prop.searchConfig.submitText = "确认" options={TASK_TYPE}
// } width="sm"
// } name="taskType"
// return defaultDoms; label="任务类型"
const result = defaultDoms.filter(defaultButton=>defaultButton.key=='rest'); initialValue={taskType}
result.push(<Button type="primary" key="create-team" onClick={() => form.submit()}>{taskType=='1'?"创建团队":"确认"} disabled={editFormDisable}
</Button>) onChange={(value: string, option) => {
return result; setTaskType(value)
} }}
} rules={[
}
onFinish={async (values) => {
{/* onFinish 返回true关闭窗口范湖false不关闭窗口 */
}
console.log('Received values of form: ', values, {...requestTask, ...values});
if (requestTask) {
const {sortNo} = requestTask;
values.sortNo = sortNo;
}
if (values.pid === undefined) {
values.pid = '0'
}
if (values.expectedTimeRange?.[0] != undefined) {
values.expectedStartTime = dayjs(values.expectedTimeRange[0]).format()
}
if (values.expectedTimeRange?.[1] != undefined) {
values.expectedEndTime = dayjs(values.expectedTimeRange[1]).format()
}
if (values.actualTimeRange?.[0] != undefined) {
values.actualStartTime = dayjs(values.actualTimeRange[0]).toDate()
}
if (values.actualTimeRange?.[1] != undefined) {
values.actualEndTime = dayjs(values.actualTimeRange[1]).toDate()
}
var result: boolean = false;
let state = taskStateList.find(taskState => taskState.name === values.state?.toString());
if (state) {
values.state = state.code
}
let priority = taskPriorityList.find(taskPriority => taskPriority.name === values.priority?.toString())
if (priority) {
values.priority = priority.code
}
// todo 修改
if (props.operationId === OPERATION_BUTTON_TYPE.UPDATE || (props.operationId === OPERATION_BUTTON_TYPE.DETAIL && !editFormDisable)) {
await updateTask(values).then(response => {
console.log('response', response)
if (response.status.success) {
message.success("修改任务成功:" + response.data)
// 树任务重新刷新
// 四象限任务重新刷新
// 如果可以直接更新列表而不请求。。。。。。
console.log('props.reloadData?.()', props.reloadData)
props.reloadData?.()
result = true
} else {
message.error(response.status.message)
result = false
}
}
);
} else {
await addTask(values).then(response => {
console.log('response', response)
if (response.data.status.success) {
message.success(`添加计划${response.data.data.name}成功`)
// 树任务重新刷新
// 四象限任务重新刷新
// 如果可以直接更新列表而不请求。。。。。。
console.log('props.reloadData?.()', props.reloadData)
result = (taskType!='1')
props.reloadData?.()
} else {
message.error(response.data.status.message)
result = false
}
}
);
}
return result;
}}
>
<ProFormText width="sm" name="id" hidden={true} label="主键"/>
<ProFormText width="sm" name="code" initialValue={props.itemId} hidden={true} label="任务编码"/>
<ProFormText width="sm" name="pPid" initialValue={props.pPid} hidden={true} label="祖宗id"/>
<ProForm.Group>
<ProFormSelect
required={true}
options={TASK_TYPE}
width="sm"
name="taskType"
label="任务类型"
initialValue={taskType}
disabled={editFormDisable}
onChange={(value: string, option) => {
setTaskType(value)
}}
rules={[
{
required: true,
message: "请输入计划类型"
}
]}
/>
<ProFormText
required={true}
hidden={taskType != '1'}
width="sm"
name="fName"
label="团队名称"
tooltip="最长为 10 位"
placeholder="请输入团队名称"
disabled={editFormDisable}
/>
<ProFormTreeSelect
hidden={taskType == '1'}
width="sm"
request={() => {
return getTaskTreeResult(JSON.stringify(
{
pageSize: 1000,
pageNumber: 1,
data: [{code: 'pid', value: '0', operateType: '='},
// 如果父任务完成会导致父任务不展示
// {
// code: 'state',
// value: '8,9',
// operateType: 'IN'
// },
{code: '', value: true, operateType: "TREE"}]
}
)).then(result => childReduce(result.data.content))
}}
name="pid"
label="父级任务"
fieldProps={{
onSelect: (e, node) => {
console.log('onSelect', e, node);
// setPid(e)
},
}}
disabled={editFormDisable}
/>
<ProFormText
required={true}
width="sm"
name="name"
label="任务名称"
tooltip="最长为 10 位"
placeholder="请输入任务名称"
disabled={editFormDisable}
rules={[
{
required: true,
message: "请输入计划名称"
}, {
max: 10,
message: "名称长度不易超过10个字"
}
]}
/>
</ProForm.Group>
<ProFormTextArea
width="xl"
name="description"
label="任务描述"
tooltip="最长255个字"
placeholder="请输入任务描述"
disabled={editFormDisable}
/>
<ProForm.Group>
<ProFormSelect
request={async () =>
taskPriorityList.map
(
taskState => {
return {'label': taskState.name, 'value': taskState.code}
}
)
}
width="sm"
name="priority"
label="任务优先级"
initialValue='3'
disabled={editFormDisable}
rules={[{required: true, message: "请选择计划优先级"}]}
/>
<ProFormSelect
width="sm"
options={taskStateList.map(taskState => {
return {'label': taskState.name, 'value': taskState.code}
})}
name="state"
label="任务状态"
initialValue='8'
disabled={editFormDisable}
rules={
[
{ {
required: true, required: true,
message: "请选择计划状态" message: "请输入计划类型"
} }
] ]}
} />
/> <ProFormText
</ProForm.Group> required={true}
hidden={taskType != '1'}
<ProForm.Group> width="sm"
<ProFormDateTimeRangePicker name="fName"
initialValue={[dayjs(), undefined]} label="团队名称"
name="expectedTimeRange" tooltip="最长为 10 位"
label="期望时间" placeholder="请输入团队名称"
fieldProps={{allowEmpty: [true, true], showTime: true, needConfirm: true}} disabled={editFormDisable}
placeholder={['开始时间', '结束时间']} />
<ProFormTreeSelect
hidden={taskType == '1'}
width="sm"
request={() => {
return getTaskTreeResult(JSON.stringify(
{
pageSize: 1000,
pageNumber: 1,
data: [{code: 'pid', value: '0', operateType: '='},
// 如果父任务完成会导致父任务不展示
// {
// code: 'state',
// value: '8,9',
// operateType: 'IN'
// },
{code: '', value: true, operateType: "TREE"}]
}
)).then(result => childReduce(result.data.content))
}}
name="pid"
label="父级任务"
fieldProps={{
onSelect: (e, node) => {
console.log('onSelect', e, node);
// setPid(e)
},
}}
disabled={editFormDisable}
/>
<ProFormText
required={true}
width="sm"
name="name"
label="任务名称"
tooltip="最长为 10 位"
placeholder="请输入任务名称"
disabled={editFormDisable}
rules={[
{
required: true,
message: "请输入计划名称"
}, {
max: 10,
message: "名称长度不易超过10个字"
}
]}
/>
</ProForm.Group>
<ProFormTextArea
width="xl"
name="description"
label="任务描述"
tooltip="最长255个字"
placeholder="请输入任务描述"
disabled={editFormDisable} disabled={editFormDisable}
/> />
<ProFormDateTimeRangePicker
name="actualTimeRange"
label="实际时间"
fieldProps={{allowEmpty: [true, true], showTime: true, needConfirm: true}}
placeholder={['开始时间', '结束时间']}
disabled={editFormDisable}
/>
</ProForm.Group>
</ModalForm> <ProForm.Group>
<ProFormSelect
request={async () =>
taskPriorityList.map
(
taskState => {
return {'label': taskState.name, 'value': taskState.code}
}
)
}
width="sm"
name="priority"
label="任务优先级"
initialValue='3'
disabled={editFormDisable}
rules={[{required: true, message: "请选择计划优先级"}]}
/>
<ProFormSelect
width="sm"
options={taskStateList.map(taskState => {
return {'label': taskState.name, 'value': taskState.code}
})}
name="state"
label="任务状态"
initialValue='8'
disabled={editFormDisable}
rules={
[
{
required: true,
message: "请选择计划状态"
}
]
}
/>
</ProForm.Group>
<ProForm.Group>
<ProFormDateTimeRangePicker
initialValue={[dayjs(), undefined]}
name="expectedTimeRange"
label="期望时间"
fieldProps={{allowEmpty: [true, true], showTime: true, needConfirm: true}}
placeholder={['开始时间', '结束时间']}
disabled={editFormDisable}
/>
<ProFormDateTimeRangePicker
name="actualTimeRange"
label="实际时间"
fieldProps={{allowEmpty: [true, true], showTime: true, needConfirm: true}}
placeholder={['开始时间', '结束时间']}
disabled={editFormDisable}
/>
</ProForm.Group>
{taskType=="3"&&<SettingCron setCronFunction={
()=>{}
}/>}
</ModalForm>
</Fragment>
); );
}; };