assistant-todo/src/components/DiaryOption.tsx

429 lines
18 KiB
TypeScript
Raw Normal View History

import React, {Fragment, useEffect, useRef, useState} from 'react';
import {Dropdown, List, MenuProps, message, Popconfirm} from 'antd';
2025-07-15 07:04:51 -04:00
import VirtualList from 'rc-virtual-list';
2025-07-18 07:02:14 -04:00
import {Button, Drawer} from 'antd';
import {ListDiary, SelectDiary} from "@/components/type/Diary";
import TextArea from "antd/es/input/TextArea";
import style from "@/components/DiaryOption.module.css"
import dayjs from "dayjs";
import {addTaskLogAPI, deleteTaskLogByIdAPI, editEnableFlagAPI} from "@/components/service/Diary";
2025-07-22 06:47:00 -04:00
import {ListRef} from "rc-virtual-list/lib/List";
import {copyToClipboard} from "@/lib/copyToClipboard";
import {useWindowSize} from "@/hooks/useWindowSize";
import {DetailModelForm} from "@/ui/task/project/DetailModelForm";
import {OPERATION_BUTTON_TYPE} from "@/lib/task/project/data";
2025-07-15 07:04:51 -04:00
2025-07-18 07:02:14 -04:00
const DiaryOption = (props: SelectDiary) => {
2025-07-15 07:04:51 -04:00
// 抽屉 start
const [open, setOpen] = useState(false);
const onClose = () => {
setOpen(false);
};
// 抽屉 end
const [shouldScroll, setShouldScroll] = React.useState(true);
2025-07-23 06:39:39 -04:00
// 设置高度 start
2025-07-29 06:47:26 -04:00
const {height} = useWindowSize();
2025-07-23 06:39:39 -04:00
const [containerHeight, setContainerHeight] = useState(400);
useEffect(() => {
setDiaryList([])
}, []);
2025-07-23 06:39:39 -04:00
useEffect(() => {
if (!open) return;
// 使用 setTimeout 确保 Drawer 内容已渲染
const timer = setTimeout(() => {
const innerDiv = document.querySelector('.ant-drawer-body');
const titleButtonHeight = document.querySelector('.titleButton');
// clientHeight可视高度包括 padding但不包括 border、margin 和滚动条)
// offsetHeight总高度包括 padding、border 和滚动条,但不包括 margin
// scrollHeight内容总高度包括不可见部分
if (innerDiv && titleButtonHeight) {
console.log(innerDiv.clientHeight)
setContainerHeight(innerDiv.clientHeight - titleButtonHeight.clientHeight)
}
}, 100);
return () => clearTimeout(timer);
// console.log({contentRef})
// const observer = new ResizeObserver((entries) => {
// const entry = entries[0];
// setContentHeight(entry.contentRect.height);
// });
//
// observer.observe(contentRef.current);
//
// return () => observer.disconnect();
2025-07-29 06:47:26 -04:00
}, [open, height]);
2025-07-23 06:39:39 -04:00
// 设置高度 end
2025-07-15 07:04:51 -04:00
2025-07-18 07:02:14 -04:00
// 头按钮设置 start
const [currentIndex, setCurrentIndex] = useState(1);
// 头按钮设置 end
2025-07-15 07:04:51 -04:00
// 数据 start
2025-07-18 07:02:14 -04:00
const [diaryList, setDiaryList] = useState<ListDiary[]>([]);
const [diaryReduceList, setDiaryReduceList] = useState<ListDiary[]>([])
2025-07-15 07:04:51 -04:00
const [page, setPage] = useState(1);
2025-07-22 06:47:00 -04:00
const noMore = {
id: '0',
keyId: 'o0',
createdDate: new Date(),
description: '没有更多了',
taskId: props.taskId,
enableFlag: 'day-separate'
};
2025-07-18 07:02:14 -04:00
const [noMoreFlag, setNoMoreFlag] = useState(false)
2025-07-22 06:47:00 -04:00
const [sendValue, setSendValue] = useState<string>();
const [sendValueFlag, setSendValueFlag] = useState(false);
const handleSend = () => {
if (sendValueFlag) {
2025-07-18 07:02:14 -04:00
return
}
setSendValueFlag(true);
if (!sendValue?.trim()) {
message.info("发送信息不能为空");
return;
}
addTaskLogAPI({
description: sendValue!,
taskId: props.taskId,
enableFlag: '1'
}).then(res => {
setDiaryList([res.data.data, ...diaryList])
setSendValue(undefined)
2025-07-23 06:39:39 -04:00
setShouldScroll(true);
2025-07-18 07:02:14 -04:00
}).finally(() => {
setSendValueFlag(false);
})
}
2025-07-15 07:04:51 -04:00
const appendData = (showMessage = true) => {
2025-07-29 06:47:26 -04:00
if (!open) {
return
}
2025-07-18 07:02:14 -04:00
const fakeDataUrl = process.env.NEXT_PUBLIC_TODO_REQUEST_URL + `/task/message/diary/select`;
fetch(fakeDataUrl, {
method: 'POST', headers: {
'Content-Type': 'application/json', // 指定 JSON 格式
'Authorization': `Bearer ${localStorage.getItem('platform-security')}`,
}, body: JSON.stringify({pageNumber: page, pageSize: 100, data: {taskId: props.taskId}})
})
2025-07-15 07:04:51 -04:00
.then((res) => res.json())
.then((body) => {
2025-07-18 07:02:14 -04:00
const results = Array.isArray(body.data.content) ? body.data.content : [];
if (results.length === 0) {
diaryList.push(noMore);
setNoMoreFlag(true);
}
setDiaryList(diaryList.concat(results));
2025-07-15 07:04:51 -04:00
setPage(page + 1);
showMessage && message.success(`${results.length} more items loaded!`);
2025-07-22 06:47:00 -04:00
if (!showMessage) {
if (listRef && listRef.current && typeof listRef.current.scrollTo == 'function') {
2025-07-23 06:39:39 -04:00
listRef.current.scrollTo({top: 9999999});
2025-07-22 06:47:00 -04:00
}
}
2025-07-15 07:04:51 -04:00
});
};
useEffect(() => {
appendData(false);
}, [open]);
2025-07-15 07:04:51 -04:00
2025-07-18 07:02:14 -04:00
useEffect(() => {
2025-07-22 06:47:00 -04:00
console.log("处理日志集合", diaryList)
2025-07-18 07:02:14 -04:00
const returnResult: ListDiary[] = []
diaryList.filter(taskLog => {
2025-07-22 06:47:00 -04:00
if (currentIndex === 0) {
return true
} else if (currentIndex === 1 && taskLog.enableFlag === "1") {
return true
} else if (currentIndex === 2 && taskLog.enableFlag === "0") {
return true
} else return false;
2025-07-18 07:02:14 -04:00
}).reduce((map, taskLog) => {
if (!map.has(dayjs(taskLog.createdDate).format("YYYY-MM-DD"))) {
map.set(dayjs(taskLog.createdDate).format("YYYY-MM-DD"), []);
}
map.get(dayjs(taskLog.createdDate).format("YYYY-MM-DD"))?.push(taskLog);
return map;
}, new Map()).forEach((value, Key) => {
returnResult.push(...value)
returnResult.push({
description: dayjs(Key).isSame(dayjs(), 'date') ? "今天" : dayjs(Key).format("YYYY-MM-DD"),
id: dayjs(Key).format("YYYY-MM-DD"),
enableFlag: "day-separate",
taskId: props.taskId,
createdDate: new Date()
})
})
2025-07-22 06:47:00 -04:00
setDiaryReduceList(returnResult.reverse());
}, [diaryList, currentIndex]);
2025-07-18 07:02:14 -04:00
2025-07-22 06:47:00 -04:00
const listRef = useRef<ListRef>(null);
2025-07-15 07:04:51 -04:00
const onScroll = (e: React.UIEvent<HTMLElement, UIEvent>) => {
// Refer to: https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollHeight#problems_and_solutions
2025-07-23 06:39:39 -04:00
// if (
// Math.abs(e.currentTarget.scrollHeight - e.currentTarget.scrollTop - containerHeight) <= 1 &&
// !noMoreFlag
// ) {
// appendData();
// }
if (e.currentTarget.scrollTop === 0 && !noMoreFlag) {
2025-07-15 07:04:51 -04:00
appendData();
2025-07-23 06:39:39 -04:00
setShouldScroll(false);
2025-07-15 07:04:51 -04:00
}
};
// 数据 end
2025-07-23 06:39:39 -04:00
// 滚动处理 start
2025-07-15 07:04:51 -04:00
2025-07-23 06:39:39 -04:00
// 条件滚动
useEffect(() => {
console.log({shouldScroll}, {listRef})
2025-07-23 06:39:39 -04:00
if (shouldScroll && listRef.current) {
listRef.current.scrollTo({
top: 99999,
});
}
}, [diaryList, shouldScroll, open]);
2025-07-23 06:39:39 -04:00
// 滚动处理 end
2025-07-18 07:02:14 -04:00
// 点击操作 start
const [clickTaskDiary, setClickTaskDiary] = useState<ListDiary>()
2025-07-22 06:47:00 -04:00
const onClickTAskDiary = (item: ListDiary, operate: string) => {
if (clickTaskDiary == item && operate == 'L') {
setClickTaskDiary(undefined)
} else {
setClickTaskDiary(item)
}
}
// 删除操作
const [popConfirmOpen, setPopConfirmOpen] = useState(false);
const [popConfirmLoading, setPopConfirmLoading] = useState(false);
const popConfirmOk = () => {
setPopConfirmLoading(true)
2025-07-29 06:47:26 -04:00
if (clickTaskDiary) {
deleteTaskLogByIdAPI(clickTaskDiary.id).then(res => {
if (res.data.status.success) {
setDiaryList(diaryList.filter(taskLog => taskLog.id != clickTaskDiary.id))
message.info("删除成功")
setPopConfirmLoading(false)
setPopConfirmOpen(false)
2025-07-29 06:47:26 -04:00
} else {
message.error(res.data.status.message)
}
})
}
}
2025-07-29 06:47:26 -04:00
const handleCancel = () => {
setPopConfirmOpen(false)
}
2025-07-29 06:47:26 -04:00
const editEnableFlag = (enableFlag: string) => {
if (clickTaskDiary) {
editEnableFlagAPI(clickTaskDiary.id, enableFlag).then(res => {
if (res.data.status.success) {
setDiaryList(diaryList.map(taskLog => {
2025-07-29 06:47:26 -04:00
if (taskLog.id == clickTaskDiary.id) {
taskLog.enableFlag = enableFlag;
}
return taskLog;
}))
message.info("设置成功")
2025-07-29 06:47:26 -04:00
} else {
message.error(res.data.status.message)
}
})
}
}
2025-07-29 06:47:26 -04:00
const [addTaskOpen, setAddTaskOpen] = useState(false)
const commonItems: MenuProps['items'] = [
2025-07-22 06:47:00 -04:00
{
label: '复制',
key: '1',
onClick: () => {
2025-07-29 06:47:26 -04:00
copyToClipboard(clickTaskDiary!.description).then(res => {
res && message.info(`复制成功${clickTaskDiary!.description.length > 5 ? clickTaskDiary!.description.substring(0, 5) + "..." : clickTaskDiary!.description}`)
2025-07-28 06:53:32 -04:00
})
2025-07-22 06:47:00 -04:00
}
},
{
label: '创建计划',
key: '3',
2025-07-29 06:47:26 -04:00
onClick: () => {
// 打开添加任务窗口
setAddTaskOpen(true)
}
2025-07-22 06:47:00 -04:00
},
{
label: '删除',
key: '4',
onClick: () => {
setPopConfirmOpen(true)
}
2025-07-22 06:47:00 -04:00
},
{
label: '取消',
key: '5',
2025-07-29 06:47:26 -04:00
onClick: () => {
setClickTaskDiary(undefined)
}
2025-07-22 06:47:00 -04:00
},
]
2025-07-29 06:47:26 -04:00
const items: MenuProps['items'] = [];
items.push(...commonItems)
2025-07-29 06:47:26 -04:00
items.splice(1, 0, {
label: '失效',
key: '2',
2025-07-29 06:47:26 -04:00
onClick: () => {
editEnableFlag("0")
},
})
2025-07-29 06:47:26 -04:00
const itemsEnable: MenuProps['items'] = [];
itemsEnable.push(...commonItems)
2025-07-29 06:47:26 -04:00
itemsEnable.splice(1, 0, {
label: '生效',
key: '2',
2025-07-29 06:47:26 -04:00
onClick: () => {
editEnableFlag("1")
},
})
2025-07-18 07:02:14 -04:00
// 点击操作 end
2025-07-15 07:04:51 -04:00
return (
<Fragment>
2025-07-29 06:47:26 -04:00
<Button type={open?"primary":"default"} onClick={() => setOpen(!open)}>
2025-07-15 07:04:51 -04:00
</Button>
<Drawer
2025-07-22 06:47:00 -04:00
style={{boxSizing: "border-box"}}
styles={{
body: {padding: "0 24px"}
}}
2025-07-15 07:04:51 -04:00
mask={false}
2025-07-18 07:02:14 -04:00
title={props.taskName}
closable={{'aria-label': 'Close Button'}}
2025-07-15 07:04:51 -04:00
onClose={onClose}
open={open}
2025-07-18 07:02:14 -04:00
footer={
<div style={{
display: 'flex',
alignItems: 'stretch', // 关键:强制子项等高
justifyContent: 'space-between',
height: 'auto', // 父容器高度由内容决定
}}>
<TextArea
rows={4}
maxLength={255}
showCount
classNames={{count: 'ant-input-data-count-inner'}}
styles={{
count: {
// color:"red",
bottom: "0px"
}
}}
style={{
resize: 'none',
flex: 1, // 占据剩余空间
}}
value={sendValue}
onChange={(val) => setSendValue(val.target.value)}
placeholder='输入日记心得,长按日记心得有惊喜'
onKeyDown={event => {
console.log({event})
if (event.ctrlKey && event.key === 'Enter') {
handleSend();
// 阻止换行符插入
event.preventDefault();
}
}}
/>
<Button
type="primary"
style={{
flexShrink: 0,
width: '2rem',
whiteSpace: 'normal',
wordWrap: 'break-word',
margin: 0,
padding: "2px",
height: 'auto'// 移除 ,依赖父容器的 alignItems: 'stretch'
}}
loading={sendValueFlag}
onClick={handleSend}
>
{sendValueFlag ? '发送中...' : '发送'}
</Button>
</div>
}
2025-07-15 07:04:51 -04:00
>
2025-07-23 06:39:39 -04:00
<div className="displayFlexRow titleButton"
2025-07-22 06:47:00 -04:00
style={{position: "sticky", top: "0", background: "white", zIndex: "100"}}>
2025-07-18 07:02:14 -04:00
<Button style={{flexGrow: 1}} onClick={() => setCurrentIndex(0)}
type={currentIndex == 0 ? "primary" : "default"}></Button>
<Button style={{flexGrow: 1}} onClick={() => setCurrentIndex(1)}
type={currentIndex == 1 ? "primary" : "default"}></Button>
<Button style={{flexGrow: 1}} onClick={() => setCurrentIndex(2)}
type={currentIndex == 2 ? "primary" : "default"}></Button>
2025-07-15 07:04:51 -04:00
</div>
2025-07-23 06:39:39 -04:00
<List>
2025-07-22 06:47:00 -04:00
<VirtualList
data={diaryReduceList}
2025-07-23 06:39:39 -04:00
height={containerHeight}
2025-07-22 06:47:00 -04:00
// itemHeight={47}
2025-07-23 06:39:39 -04:00
itemKey="id"
2025-07-22 06:47:00 -04:00
onScroll={onScroll}
2025-07-23 06:39:39 -04:00
// style={{height: "auto"}}
2025-07-22 06:47:00 -04:00
ref={listRef}
>
{item => (
item.enableFlag === 'day-separate' ?
<div className={style.container} key={item.keyId}>
<div className={style.lineWithText}>
<text className={style.centerText}>{item.description}</text>
2025-07-18 07:02:14 -04:00
</div>
2025-07-22 06:47:00 -04:00
</div>
: <div className={style.logTaskContent} key={item.id}>
<Dropdown menu={{items: (item.enableFlag == "1" ? items : itemsEnable)}}
trigger={['contextMenu']}>
<Popconfirm
title="警告"
description={`确认要删除日志:${item.description.length > 5 ? item.description.substring(0, 5) + '...' : item.description}?`}
2025-07-29 06:47:26 -04:00
open={popConfirmOpen && clickTaskDiary?.id == item.id}
onConfirm={popConfirmOk}
okButtonProps={{loading: popConfirmLoading}}
onCancel={handleCancel}
okText="Yes"
cancelText="No"
>
<div
className={`${style.detailLine} ${item.id === clickTaskDiary?.id ? style.detailLineClick : ''}`}
onClick={() => onClickTAskDiary(item, "L")}
onContextMenu={() => onClickTAskDiary(item, "R")}>
<text
style={{
textDecoration: item.enableFlag === '0' && currentIndex === 0 ? 'line-through' : '',
whiteSpace: 'pre-line'
}}>
{item.description}
</text>
</div>
</Popconfirm>
2025-07-22 06:47:00 -04:00
</Dropdown>
</div>
)}
</VirtualList>
</List>
2025-07-15 07:04:51 -04:00
</Drawer>
2025-07-29 06:47:26 -04:00
{addTaskOpen && <DetailModelForm pid={props.taskId}
operationId={OPERATION_BUTTON_TYPE.ADD_CHILD}
description={"新增计划"}
open={addTaskOpen}
haveButton={false}
reloadData={() => setAddTaskOpen(false)}
taskContent={clickTaskDiary?.description}
/>}
</Fragment>
2025-07-15 07:04:51 -04:00
);
};
export default DiaryOption;