assistant-todo/src/components/DiaryOption.tsx

431 lines
18 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import React, {Fragment, useEffect, useRef, useState} from 'react';
import {Dropdown, List, MenuProps, message, Popconfirm} from 'antd';
import VirtualList from 'rc-virtual-list';
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";
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";
const DiaryOption = (props: SelectDiary) => {
// 抽屉 start
const [open, setOpen] = useState(false);
const onClose = () => {
setOpen(false);
};
// 抽屉 end
const [shouldScroll, setShouldScroll] = React.useState(true);
// 设置高度 start
const {height} = useWindowSize();
const [containerHeight, setContainerHeight] = useState(400);
useEffect(() => {
setDiaryList([])
}, []);
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();
}, [open, height]);
// 设置高度 end
// 头按钮设置 start
const [currentIndex, setCurrentIndex] = useState(1);
// 头按钮设置 end
// 数据 start
const [diaryList, setDiaryList] = useState<ListDiary[]>([]);
const [diaryReduceList, setDiaryReduceList] = useState<ListDiary[]>([])
const [page, setPage] = useState(1);
const [selectLoading,setSelectLoading]=useState<boolean>(false);
const noMore = {
id: '0',
keyId: 'o0',
createdDate: new Date(),
description: '没有更多了',
taskId: props.taskId,
enableFlag: 'day-separate'
};
const [noMoreFlag, setNoMoreFlag] = useState(false)
const [sendValue, setSendValue] = useState<string>();
const [sendValueFlag, setSendValueFlag] = useState(false);
const handleSend = () => {
if (sendValueFlag) {
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)
setShouldScroll(true);
}).finally(() => {
setSendValueFlag(false);
})
}
const appendData = (showMessage = true) => {
if (!open) {
return
}
setSelectLoading(true)
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}})
})
.then((res) => res.json())
.then((body) => {
const results = Array.isArray(body.data.content) ? body.data.content : [];
if (results.length === 0) {
diaryList.push(noMore);
setNoMoreFlag(true);
}
setDiaryList(diaryList.concat(results));
setPage(page + 1);
showMessage && message.success(`${results.length} more items loaded!`);
if (!showMessage) {
if (listRef && listRef.current && typeof listRef.current.scrollTo == 'function') {
listRef.current.scrollTo({top: 9999999});
}
}
setSelectLoading(false)
});
};
useEffect(() => {
appendData(false);
}, [open]);
useEffect(() => {
console.log("处理日志集合", diaryList)
const returnResult: ListDiary[] = []
diaryList.filter(taskLog => {
if (currentIndex === 0) {
return true
} else if (currentIndex === 1 && taskLog.enableFlag !== "0") {
return true
} else if (currentIndex === 2 && taskLog.enableFlag !== "1") {
return true
} else return false;
}).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()
})
})
setDiaryReduceList(returnResult.reverse());
}, [diaryList, currentIndex]);
const listRef = useRef<ListRef>(null);
const onScroll = (e: React.UIEvent<HTMLElement, UIEvent>) => {
// Refer to: https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollHeight#problems_and_solutions
// if (
// Math.abs(e.currentTarget.scrollHeight - e.currentTarget.scrollTop - containerHeight) <= 1 &&
// !noMoreFlag
// ) {
// appendData();
// }
if (e.currentTarget.scrollTop === 0 && !noMoreFlag) {
appendData();
setShouldScroll(false);
}
};
// 数据 end
// 滚动处理 start
// 条件滚动
useEffect(() => {
console.log({shouldScroll}, {listRef})
if (shouldScroll && listRef.current) {
listRef.current.scrollTo({
top: 99999,
});
}
}, [diaryList, shouldScroll, open]);
// 滚动处理 end
// 点击操作 start
const [clickTaskDiary, setClickTaskDiary] = useState<ListDiary>()
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)
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)
} else {
message.error(res.data.status.message)
}
})
}
}
const handleCancel = () => {
setPopConfirmOpen(false)
}
const editEnableFlag = (enableFlag: string) => {
if (clickTaskDiary) {
editEnableFlagAPI(clickTaskDiary.id, enableFlag).then(res => {
if (res.data.status.success) {
setDiaryList(diaryList.map(taskLog => {
if (taskLog.id == clickTaskDiary.id) {
taskLog.enableFlag = enableFlag;
}
return taskLog;
}))
message.info("设置成功")
} else {
message.error(res.data.status.message)
}
})
}
}
const [addTaskOpen, setAddTaskOpen] = useState(false)
const commonItems: MenuProps['items'] = [
{
label: '复制',
key: '1',
onClick: () => {
copyToClipboard(clickTaskDiary!.description).then(res => {
res && message.info(`复制成功${clickTaskDiary!.description.length > 5 ? clickTaskDiary!.description.substring(0, 5) + "..." : clickTaskDiary!.description}`)
})
}
},
{
label: '创建计划',
key: '3',
onClick: () => {
// 打开添加任务窗口
setAddTaskOpen(true)
}
},
{
label: '删除',
key: '4',
onClick: () => {
setPopConfirmOpen(true)
}
},
{
label: '取消',
key: '5',
onClick: () => {
setClickTaskDiary(undefined)
}
},
]
const items: MenuProps['items'] = [];
items.push(...commonItems)
items.splice(1, 0, {
label: '失效',
key: '2',
onClick: () => {
editEnableFlag("0")
},
})
const itemsEnable: MenuProps['items'] = [];
itemsEnable.push(...commonItems)
itemsEnable.splice(1, 0, {
label: '生效',
key: '2',
onClick: () => {
editEnableFlag("1")
},
})
// 点击操作 end
return (
<Fragment>
<Button type={open?"primary":"default"} onClick={() => setOpen(!open)}>
</Button>
<Drawer
style={{boxSizing: "border-box"}}
styles={{
body: {padding: "0 24px"}
}}
mask={false}
title={props.taskName}
closable={{'aria-label': 'Close Button'}}
onClose={onClose}
open={open}
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>
}
>
<div className="displayFlexRow titleButton"
style={{position: "sticky", top: "0", background: "white", zIndex: "100"}}>
<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>
</div>
<List loading={selectLoading}>
<VirtualList
data={diaryReduceList}
height={containerHeight}
// itemHeight={47}
itemKey="id"
onScroll={onScroll}
// style={{height: "auto"}}
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>
</div>
</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}?`}
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>
</Dropdown>
</div>
)}
</VirtualList>
</List>
</Drawer>
{addTaskOpen && <DetailModelForm pid={props.taskId}
operationId={OPERATION_BUTTON_TYPE.ADD_CHILD}
description={"新增计划"}
open={addTaskOpen}
haveButton={false}
reloadData={() => setAddTaskOpen(false)}
taskContent={clickTaskDiary?.description}
/>}
</Fragment>
);
};
export default DiaryOption;