feat:日历滑动

This commit is contained in:
1708-huayu 2025-01-26 15:18:18 +08:00
parent 55cfe79dab
commit 69a1bd0de3
9 changed files with 302 additions and 81 deletions

33
package-lock.json generated
View File

@ -19,6 +19,7 @@
"node-gyp": "^9.3.1", "node-gyp": "^9.3.1",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-infinite-scroll-component": "^6.1.0",
"react-router-dom": "^6.6.2", "react-router-dom": "^6.6.2",
"react-scripts": "5.0.1", "react-scripts": "5.0.1",
"react-virtualized": "^9.22.3", "react-virtualized": "^9.22.3",
@ -14160,6 +14161,17 @@
"resolved": "https://registry.npmmirror.com/react-fast-compare/-/react-fast-compare-3.2.2.tgz", "resolved": "https://registry.npmmirror.com/react-fast-compare/-/react-fast-compare-3.2.2.tgz",
"integrity": "sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==" "integrity": "sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ=="
}, },
"node_modules/react-infinite-scroll-component": {
"version": "6.1.0",
"resolved": "https://registry.npmmirror.com/react-infinite-scroll-component/-/react-infinite-scroll-component-6.1.0.tgz",
"integrity": "sha512-SQu5nCqy8DxQWpnUVLx7V7b7LcA37aM7tvoWjTLZp1dk6EJibM5/4EJKzOnl07/BsM1Y40sKLuqjCwwH/xV0TQ==",
"dependencies": {
"throttle-debounce": "^2.1.0"
},
"peerDependencies": {
"react": ">=16.0.0"
}
},
"node_modules/react-is": { "node_modules/react-is": {
"version": "17.0.2", "version": "17.0.2",
"resolved": "https://registry.npmmirror.com/react-is/-/react-is-17.0.2.tgz", "resolved": "https://registry.npmmirror.com/react-is/-/react-is-17.0.2.tgz",
@ -15757,6 +15769,14 @@
"resolved": "https://registry.npmmirror.com/throat/-/throat-6.0.2.tgz", "resolved": "https://registry.npmmirror.com/throat/-/throat-6.0.2.tgz",
"integrity": "sha512-WKexMoJj3vEuK0yFEapj8y64V0A6xcuPuK9Gt1d0R+dzCSJc0lHqQytAbSB4cDAK0dWh4T0E2ETkoLE2WZ41OQ==" "integrity": "sha512-WKexMoJj3vEuK0yFEapj8y64V0A6xcuPuK9Gt1d0R+dzCSJc0lHqQytAbSB4cDAK0dWh4T0E2ETkoLE2WZ41OQ=="
}, },
"node_modules/throttle-debounce": {
"version": "2.3.0",
"resolved": "https://registry.npmmirror.com/throttle-debounce/-/throttle-debounce-2.3.0.tgz",
"integrity": "sha512-H7oLPV0P7+jgvrk+6mwwwBDmxTaxnu9HMXmloNLXwnNO0ZxZ31Orah2n8lU1eMPvsaowP2CX+USCgyovXfdOFQ==",
"engines": {
"node": ">=8"
}
},
"node_modules/thunky": { "node_modules/thunky": {
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmmirror.com/thunky/-/thunky-1.1.0.tgz", "resolved": "https://registry.npmmirror.com/thunky/-/thunky-1.1.0.tgz",
@ -27773,6 +27793,14 @@
"resolved": "https://registry.npmmirror.com/react-fast-compare/-/react-fast-compare-3.2.2.tgz", "resolved": "https://registry.npmmirror.com/react-fast-compare/-/react-fast-compare-3.2.2.tgz",
"integrity": "sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==" "integrity": "sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ=="
}, },
"react-infinite-scroll-component": {
"version": "6.1.0",
"resolved": "https://registry.npmmirror.com/react-infinite-scroll-component/-/react-infinite-scroll-component-6.1.0.tgz",
"integrity": "sha512-SQu5nCqy8DxQWpnUVLx7V7b7LcA37aM7tvoWjTLZp1dk6EJibM5/4EJKzOnl07/BsM1Y40sKLuqjCwwH/xV0TQ==",
"requires": {
"throttle-debounce": "^2.1.0"
}
},
"react-is": { "react-is": {
"version": "17.0.2", "version": "17.0.2",
"resolved": "https://registry.npmmirror.com/react-is/-/react-is-17.0.2.tgz", "resolved": "https://registry.npmmirror.com/react-is/-/react-is-17.0.2.tgz",
@ -29019,6 +29047,11 @@
"resolved": "https://registry.npmmirror.com/throat/-/throat-6.0.2.tgz", "resolved": "https://registry.npmmirror.com/throat/-/throat-6.0.2.tgz",
"integrity": "sha512-WKexMoJj3vEuK0yFEapj8y64V0A6xcuPuK9Gt1d0R+dzCSJc0lHqQytAbSB4cDAK0dWh4T0E2ETkoLE2WZ41OQ==" "integrity": "sha512-WKexMoJj3vEuK0yFEapj8y64V0A6xcuPuK9Gt1d0R+dzCSJc0lHqQytAbSB4cDAK0dWh4T0E2ETkoLE2WZ41OQ=="
}, },
"throttle-debounce": {
"version": "2.3.0",
"resolved": "https://registry.npmmirror.com/throttle-debounce/-/throttle-debounce-2.3.0.tgz",
"integrity": "sha512-H7oLPV0P7+jgvrk+6mwwwBDmxTaxnu9HMXmloNLXwnNO0ZxZ31Orah2n8lU1eMPvsaowP2CX+USCgyovXfdOFQ=="
},
"thunky": { "thunky": {
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmmirror.com/thunky/-/thunky-1.1.0.tgz", "resolved": "https://registry.npmmirror.com/thunky/-/thunky-1.1.0.tgz",

View File

@ -16,6 +16,7 @@
"node-gyp": "^9.3.1", "node-gyp": "^9.3.1",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-infinite-scroll-component": "^6.1.0",
"react-router-dom": "^6.6.2", "react-router-dom": "^6.6.2",
"react-scripts": "5.0.1", "react-scripts": "5.0.1",
"react-virtualized": "^9.22.3", "react-virtualized": "^9.22.3",

View File

@ -8,3 +8,18 @@ export const addTaskLog = (taskLog) => {
export const listTaskLog = (request) => { export const listTaskLog = (request) => {
return requestUtil.get('/todo-server/V2/search/task_message_diary?search=' + encodeURI(request)); return requestUtil.get('/todo-server/V2/search/task_message_diary?search=' + encodeURI(request));
} }
export const updateTaskLogEnableById = (enableFlag, id) => {
let request = {
"updateColumnList": [{"name": "enableFlag", "value": enableFlag, "operateType": "="}],
"conditionColumnList": [{"name": "id", "value": id, "operateType": "="}]
}
return requestUtil.put('/todo-server/search/task_message_diary', request);
}
export const deleteTaskLogById = (id) => {
let request = {
"updateColumnList": [{"name": "deleteFlag", "value": "1", "operateType": "="}],
"conditionColumnList": [{"name": "id", "value": id, "operateType": "="}]
}
return requestUtil.put('/todo-server/search/task_message_diary', request);
}

View File

@ -23,7 +23,7 @@ const DetailForm = () => {
const {setTitle, setRightDesc} = useOutletContext(); const {setTitle, setRightDesc} = useOutletContext();
const [currentPath, setCurrentPath] = React.useState(""); const [currentPath, setCurrentPath] = React.useState("");
const [updateFiledDisabled, setUpdateFiledDisabled] = React.useState(true); const [updateFiledDisabled, setUpdateFiledDisabled] = React.useState(true);
const [pName, setPName] = React.useState(); const [pName, setPName] = React.useState("");
const [pidArray, setPidArray] = React.useState([]); const [pidArray, setPidArray] = React.useState([]);
const [stateList, setStateList] = React.useState([]); const [stateList, setStateList] = React.useState([]);
const [priorityList, setPriorityList] = React.useState([]); const [priorityList, setPriorityList] = React.useState([]);
@ -47,6 +47,18 @@ const DetailForm = () => {
setCurrentPath("addTask"); setCurrentPath("addTask");
setUpdateFiledDisabled(false); setUpdateFiledDisabled(false);
setRightDesc(<Fragment/>) setRightDesc(<Fragment/>)
// 日志创建任务会传来pidArray和pName
if (params.get('pidArray') && params.get('pName') && params.get('description')) {
setPName(params.get('pName'));
setPidArray(params.get('pidArray').split(','))
form.setFieldValue("pidArray", params.get('pidArray').split(','))
form.setFieldValue("description", params.get('description'))
if (params.get('description').length>5){
form.setFieldValue("name", params.get('description').substring(0,5))
}else {
form.setFieldValue("description", params.get('description'))
}
}
} else if (location.pathname.endsWith("updateTask")) { } else if (location.pathname.endsWith("updateTask")) {
setTitle("修改任务"); setTitle("修改任务");
setCurrentPath("updateTask"); setCurrentPath("updateTask");

View File

@ -1,10 +1,20 @@
.log-task-detail { .log-task-detail {
display: flex;
height: calc(100vh - 45px); height: calc(100vh - 45px);
/* 确保子元素可以相对于此容器定位 */ /* 确保子元素可以相对于此容器定位 */
position: relative; position: relative;
display: flex;
flex-direction: column; flex-direction: column;
} }
.log-detail {
/*position: absolute;*/
overflow-y: auto;
width: 100%;
display: flex;
max-height: calc(100vh - 140px);
flex-direction: column-reverse;
}
.log-send { .log-send {
position: absolute; position: absolute;
bottom: 0; bottom: 0;
@ -20,6 +30,7 @@
width: 80px; width: 80px;
height: 40px; height: 40px;
} }
.detail-line { .detail-line {
border-radius: 6px; border-radius: 6px;
border: solid; border: solid;

View File

@ -1,10 +1,12 @@
import {ActionSheet, Button, Dialog, Divider, Tabs, TextArea, Toast} from "antd-mobile"; import {ActionSheet, Button, Dialog, Divider, Tabs, TextArea, Toast} from "antd-mobile";
import {Fragment, useEffect, useMemo, useRef, useState} from "react"; import React, {Fragment, useEffect, useMemo, useRef, useState} from "react";
import "./index.css" import "./index.css"
import {useOutletContext, useSearchParams} from "react-router-dom"; import {useNavigate, useOutletContext, useSearchParams} from "react-router-dom";
import {addTaskLog, listTaskLog} from "../../api/detailLogTaskApi"; import {addTaskLog, deleteTaskLogById, listTaskLog, updateTaskLogEnableById} from "../../api/detailLogTaskApi";
import dayjs from "dayjs"; import dayjs from "dayjs";
import {copyTextToClipboard} from "../../utils/copyToClipBoard"; import {copyTextToClipboard} from "../../utils/copyToClipBoard";
import {getPTask} from "../../utils";
import InfiniteScroll from "react-infinite-scroll-component";
export function DetailLogTask() { export function DetailLogTask() {
// 设置标题栏 // 设置标题栏
@ -14,13 +16,22 @@ export function DetailLogTask() {
setRightDesc(<Fragment/>); setRightDesc(<Fragment/>);
}, []) }, [])
let [params] = useSearchParams(); let [params] = useSearchParams();
// 路由
const navigate = useNavigate();
// 发送展示日记 // 发送展示日记
const [currentShow, setCurrentShow] = useState('need'); const [currentShow, setCurrentShow] = useState('need');
const textAreaRef = useRef(null); const textAreaRef = useRef(null);
const [taskLogList, setTaskLogList] = useState([]); const [taskLogList, setTaskLogList] = useState([]);
const [sendValue, setSendValue] = useState(''); const [sendValue, setSendValue] = useState('');
const [hasMore, setHasMore] = useState(true);
const [pageTime, setPageTime] = useState(new Date());
// 记录上一次的滚动高度
const previousScrollHeight = useRef(0);
// 点击操作面板 // 点击操作面板
const [actionSheetVisible, setActionSheetVisible] = useState(false) const [actionSheetVisible, setActionSheetVisible] = useState(false)
const [currentTask, setCurrentTask] = useState({}); const [currentTask, setCurrentTask] = useState({});
@ -28,20 +39,19 @@ export function DetailLogTask() {
{text: '复制', key: 'copy'}, {text: '复制', key: 'copy'},
{text: '有效', key: 'need'}, {text: '有效', key: 'need'},
{text: '创建任务', key: 'addTask'}, {text: '创建任务', key: 'addTask'},
{ {text: '删除', key: 'delete'},
text: '删除',
key: 'delete',
onClick: async () => {
const result = await Dialog.confirm({ content: '确定要删除吗?' })
if (result) {
Toast.show('执行了删除操作')
}
},
},
]); ]);
const taskLogMapMemory = useMemo(() => { const taskLogMapMemory = useMemo(() => {
return taskLogList.reduce((map, taskLog) => { return taskLogList
.filter(taskLog => {
if (currentShow === 'all') {
return true
} else if (currentShow === 'need' && taskLog.enableFlag === '1') {
return true
} else return currentShow === 'noNeed' && taskLog.enableFlag === '0';
})
.reduce((map, taskLog) => {
if (!map.has(dayjs(taskLog.createdDate).format("YYYY-MM-DD"))) { if (!map.has(dayjs(taskLog.createdDate).format("YYYY-MM-DD"))) {
map.set(dayjs(taskLog.createdDate).format("YYYY-MM-DD"), []); map.set(dayjs(taskLog.createdDate).format("YYYY-MM-DD"), []);
} }
@ -53,6 +63,7 @@ export function DetailLogTask() {
addTaskLog({ addTaskLog({
"description": sendValue, "description": sendValue,
"taskId": params.get('id'), "taskId": params.get('id'),
"enableFlag": '1'
}).then(res => { }).then(res => {
// 加入当前集合中 // 加入当前集合中
setTaskLogList([...taskLogList, res]); setTaskLogList([...taskLogList, res]);
@ -60,46 +71,83 @@ export function DetailLogTask() {
setSendValue('') setSendValue('')
textAreaRef.current.focus(); textAreaRef.current.focus();
}; };
useEffect(() => {
textAreaRef.current.focus(); async function fetchMessages() {
// 获取之前的日志信息根据日期分组排序遍历map // 获取之前的日志信息根据日期分组排序遍历map
listTaskLog(`{ const res = await listTaskLog(JSON.stringify({
"pageSize": 20,
"sortList": [{"direction": "DESC", "property": "createdDate"}], "sortList": [{"direction": "DESC", "property": "createdDate"}],
"data": { "data": {
"andList": [{ "andList": [{
"name": "taskId", "name": "taskId",
"operateType": "=", "operateType": "=",
"value":"${params.get('id')}" "value": params.get('id')
}, {
"name": "createdDate",
"operateType": "<",
"value": pageTime
}] }]
} }
}`).then(res => { }))
console.log({res}) console.log({res})
if (res.content.length > 0) { if (res.content.length > 0) {
setTaskLogList(res.content.reverse()); setPageTime(res.content[res.content.length - 1].createdDate)
// setTaskLogMap(res.content.reduce((map, taskLog) => { setTaskLogList([...taskLogList, ...res.content]);
// if (!map.has(dayjs(taskLog.createdDate).format("YYYY-MM-DD"))) { } else if (res.page.totalElements === 0) {
// map.set(dayjs(taskLog.createdDate).format("YYYY-MM-DD"), []); setHasMore(false);
// }
// map.get(dayjs(taskLog.createdDate).format("YYYY-MM-DD"))?.push(taskLog);
// return map;
// }, new Map()));
} }
}) }
useEffect(() => {
setPageTime(new Date())
textAreaRef.current.focus();
fetchMessages()
}, []) }, [])
function NoNeed() { function NoNeed() {
return <s>失效</s> return <s>失效</s>
} }
return <Fragment> return <Fragment>
<div className="log-task-detail"> <div className="log-task-detail">
<div className="log-detail">
<Tabs defaultActiveKey='need' onChange={(key) => setCurrentShow(key)}> <Tabs defaultActiveKey='need' onChange={(key) => setCurrentShow(key)}>
<Tabs.Tab title='全部' key='all'/> <Tabs.Tab title='全部' key='all'/>
<Tabs.Tab title='有效' key='need'/> <Tabs.Tab title='有效' key='need'/>
<Tabs.Tab key='noneed' title=<NoNeed/> /> <Tabs.Tab key='noNeed' title=<NoNeed/> />
</Tabs> </Tabs>
<div id="scrollableDiv" className="log-detail"
>
<InfiniteScroll
dataLength={taskLogList.length}
next={fetchMessages}
hasMore={hasMore}
inverse={true} // 设置为 true 来反转列表的方向,使新消息在底部
loader={<h4>加载中...</h4>}
style={{display: 'flex', flexDirection: 'column-reverse'}}
scrollableTarget="scrollableDiv"
endMessage={
<Divider>没有更久远的信息了</Divider>
}
>
{ {
Array.from(taskLogMapMemory.keys()).map(key => { Array.from(taskLogMapMemory.keys()).map(key => {
return (<Fragment> return (<Fragment>
{taskLogMapMemory.get(key).map(taskLog => {
return <div key={taskLog.id}
style={{display: 'flex', width: "80%", padding: "2px 20px"}}>
<span className="detail-line" onClick={() => {
if (taskLog.enableFlag === '0') {
actions[1] = {text: '有效', key: 'need'}
} else {
actions[1] = {text: '失效', key: 'noNeed'}
}
// setActions([...actions])
setCurrentTask(taskLog)
setActionSheetVisible(true)
}}>
{taskLog.description}</span>
</div>
})}
{dayjs().isSame(dayjs(key), "date") ? {dayjs().isSame(dayjs(key), "date") ?
<div key={key} style={{width: "100%", padding: "0 40px"}}> <div key={key} style={{width: "100%", padding: "0 40px"}}>
<Divider>今日</Divider> <Divider>今日</Divider>
@ -107,21 +155,11 @@ export function DetailLogTask() {
<div key={key} style={{width: "100%", padding: "0 20px"}}> <div key={key} style={{width: "100%", padding: "0 20px"}}>
<Divider>{key}</Divider> <Divider>{key}</Divider>
</div>} </div>}
{taskLogMapMemory.get(key).map(taskLog => { </Fragment>)
return <div key={taskLog.id} style={{ display: 'flex', width: "80%", padding: "2px 20px"}}>
<span className="detail-line" onClick={()=>{
actions[1]={ text: '失效', key: 'noneed' }
// setActions([...actions])
setCurrentTask(taskLog)
setActionSheetVisible(true)
}}>
{taskLog.description}</span>
</div>
})}</Fragment>)
}) })
} }
</InfiniteScroll>
</div> </div>
<div className="log-send"> <div className="log-send">
<TextArea <TextArea
ref={(node) => { ref={(node) => {
@ -133,6 +171,13 @@ export function DetailLogTask() {
rows={1} rows={1}
autoSize={{minRows: 1, maxRows: 5}} autoSize={{minRows: 1, maxRows: 5}}
maxLength={100} maxLength={100}
onKeyDown={event => {
if (event.key === 'Enter') {
handleSend();
// 阻止换行符插入
event.preventDefault();
}
}}
/> />
<Button <Button
className="send-button" className="send-button"
@ -148,6 +193,48 @@ export function DetailLogTask() {
onAction={action => { onAction={action => {
if (action.key === 'copy') { if (action.key === 'copy') {
copyTextToClipboard(currentTask.description) copyTextToClipboard(currentTask.description)
} else if (action.key === 'delete') {
Dialog.confirm({
content: '确定要删除吗?', onConfirm: () => {
deleteTaskLogById(currentTask.id).then(result => {
setTaskLogList(taskLogList.filter(log => log.id !== currentTask.id))
})
}
})
} else if (action.key === 'noNeed') {
updateTaskLogEnableById('0', currentTask.id).then(result => {
setTaskLogList(taskLogList.map(log => {
if (log.id === currentTask.id) {
log.enableFlag = '0'
}
return log;
})
)
});
} else if (action.key === 'need') {
updateTaskLogEnableById('1', currentTask.id).then(result => {
setTaskLogList(taskLogList.map(log => {
if (log.id === currentTask.id) {
log.enableFlag = '1'
}
return log;
})
)
});
} else if (action.key === 'addTask') {
// 跳转到新增和新建下一条差不多设置pidArray
// 获取父任务信息
Dialog.confirm({
content: '确定要创建任务吗?', onConfirm: () => {
getPTask(params.get('id')).then(res => {
let parentMessageVOList = res[0].parentMessageVOList;
console.log({res, parentMessageVOList});
// 进入新增页面
navigate(`/detail/addTask?
pName=${parentMessageVOList[parentMessageVOList.length - 1].name}&pidArray=${parentMessageVOList.map(parent => parent.id).join(',')}&description=${currentTask.description}`)
})
}
})
} }
setActionSheetVisible(false) setActionSheetVisible(false)
}} }}

View File

@ -232,7 +232,7 @@ const DetailSearchContext = () => {
<CalendarPicker <CalendarPicker
visible={visible} visible={visible}
selectionMode='single' selectionMode='single'
defaultDate={getFieldValue('todoDay')} defaultValue={getFieldValue('todoDay')??new Date()}
onClose={() => { onClose={() => {
setVisible(false) setVisible(false)
}} }}

View File

@ -140,9 +140,12 @@ const ToDoList = () => {
text: '删除', text: '删除',
color: 'danger', color: 'danger',
onClick: async () => { onClick: async () => {
await Dialog.confirm({ const result = await Dialog.confirm({
content: '确定要删除吗?', content: '确定要删除吗?',
}) })
if (result){
await deleteTaskById(item.id)
}
// 关闭当前激活的 SwipeAction // 关闭当前激活的 SwipeAction
closeActiveSwipeAction(); closeActiveSwipeAction();
localRefresh() localRefresh()

View File

@ -0,0 +1,59 @@
.chat-window {
display: flex;
flex-direction: column;
height: 80vh;
width: 300px;
margin: auto;
border: 1px solid #ccc;
border-radius: 5px;
box-shadow: 0 0 10px rgba(0, 0, 0, .1);
/*overflow:auto*/
}
.messages-container {
flex: 1;
padding: 10px;
overflow-y: auto;
}
.message {
margin: 5px 0;
padding: 8px;
border-radius: 4px;
max-width: 60%;
}
.message.user {
background-color: #d1e7dd;
align-self: flex-end;
}
.message.other {
background-color: #f8d7da;
align-self: flex-start;
}
.input-area {
display: flex;
border-top: 1px solid #ccc;
}
.input-area input {
flex: 1;
padding: 10px;
font-size: 16px;
border: none;
outline: none;
}
.input-area button {
padding: 10px;
border: none;
background-color: #007bff;
color: white;
cursor: pointer;
}
.input-area button:hover {
background-color: #0056b3;
}