feat:树任务跳转

This commit is contained in:
1708-huayu 2025-01-22 19:24:51 +08:00
parent 7ddeb50e3f
commit 779a28a364
9 changed files with 512 additions and 507 deletions

629
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -7,7 +7,7 @@
"@testing-library/jest-dom": "^5.16.5", "@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^13.4.0", "@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0", "@testing-library/user-event": "^13.5.0",
"antd-mobile": "^5.27.0", "antd-mobile": "^5.28.0",
"antd-mobile-icons": "^0.3.0", "antd-mobile-icons": "^0.3.0",
"axios": "^1.2.2", "axios": "^1.2.2",
"dayjs": "^1.11.13", "dayjs": "^1.11.13",

View File

@ -1,9 +1,10 @@
import {Button, Divider, TextArea} from "antd-mobile"; import {ActionSheet, Button, Dialog, Divider, Tabs, TextArea, Toast} from "antd-mobile";
import {Fragment, useEffect, useMemo, useRef, useState} from "react"; import {Fragment, useEffect, useMemo, useRef, useState} from "react";
import "./index.css" import "./index.css"
import {useOutletContext, useSearchParams} from "react-router-dom"; import {useOutletContext, useSearchParams} from "react-router-dom";
import {addTaskLog, listTaskLog} from "../../api/detailLogTaskApi"; import {addTaskLog, listTaskLog} from "../../api/detailLogTaskApi";
import dayjs from "dayjs"; import dayjs from "dayjs";
import {copyTextToClipboard} from "../../utils/copyToClipBoard";
export function DetailLogTask() { export function DetailLogTask() {
// 设置标题栏 // 设置标题栏
@ -13,11 +14,32 @@ export function DetailLogTask() {
setRightDesc(<Fragment/>); setRightDesc(<Fragment/>);
}, []) }, [])
let [params] = useSearchParams(); let [params] = useSearchParams();
// 发送展示日记
const [currentShow, setCurrentShow] = useState('need');
const textAreaRef = useRef(null); const textAreaRef = useRef(null);
const [taskLogMap, setTaskLogMap] = useState([]);
const [taskLogList, setTaskLogList] = useState([]); const [taskLogList, setTaskLogList] = useState([]);
const [sendValue, setSendValue] = useState([]); const [sendValue, setSendValue] = useState([]);
// 点击操作面板
const [actionSheetVisible, setActionSheetVisible] = useState(false)
const [currentTask, setCurrentTask] = useState({});
const [actions,setActions] = useState([
{ text: '复制', key: 'copy' },
{ text: '有效', key: 'edit' },
{ text: '创建任务', key: 'addTask'},
{
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.reduce((map, taskLog) => {
if (!map.has(dayjs(taskLog.createdDate).format("YYYY-MM-DD"))) { if (!map.has(dayjs(taskLog.createdDate).format("YYYY-MM-DD"))) {
@ -42,7 +64,14 @@ export function DetailLogTask() {
textAreaRef.current.focus(); textAreaRef.current.focus();
// 获取之前的日志信息根据日期分组排序遍历map // 获取之前的日志信息根据日期分组排序遍历map
listTaskLog(`{ listTaskLog(`{
"sortList":[{"direction":"DESC","property":"createdDate"}] "sortList":[{"direction":"DESC","property":"createdDate"}],
"data": {
"andList":[{
"name":"taskId",
"operateType":"=",
"value":"${params.get('id')}"
}]
}
}`).then(res => { }`).then(res => {
console.log({res}) console.log({res})
if (res.content.length > 0) { if (res.content.length > 0) {
@ -57,9 +86,17 @@ export function DetailLogTask() {
} }
}) })
}, []) }, [])
function NoNeed(){
return <s>失效</s>
}
return <Fragment> return <Fragment>
<div className="log-task-detail"> <div className="log-task-detail">
<div className="log-detail"> <div className="log-detail">
<Tabs defaultActiveKey='need' onChange={(key)=>setCurrentShow(key)}>
<Tabs.Tab title='全部' key='all' />
<Tabs.Tab title='有效' key='need' />
<Tabs.Tab key='noneed' title=<NoNeed/> />
</Tabs>
{ {
Array.from(taskLogMapMemory.keys()).map(key => { Array.from(taskLogMapMemory.keys()).map(key => {
return (<Fragment> return (<Fragment>
@ -72,7 +109,13 @@ export function DetailLogTask() {
</div>} </div>}
{taskLogMapMemory.get(key).map(taskLog => { {taskLogMapMemory.get(key).map(taskLog => {
return <div key={taskLog.id} style={{ display: 'flex', width: "80%", padding: "2px 20px"}}> return <div key={taskLog.id} style={{ display: 'flex', width: "80%", padding: "2px 20px"}}>
<span className="detail-line">{taskLog.description}</span> <span className="detail-line" onClick={()=>{
actions[1]={ text: '失效', key: 'edit' }
// setActions([...actions])
setCurrentTask(taskLog)
setActionSheetVisible(true)
}}>
{taskLog.description}</span>
</div> </div>
})}</Fragment>) })}</Fragment>)
}) })
@ -96,6 +139,19 @@ export function DetailLogTask() {
onClick={handleSend} onClick={handleSend}
>发送 >发送
</Button> </Button>
<ActionSheet
extra='请选择你要进行的操作'
cancelText='取消'
visible={actionSheetVisible}
actions={actions}
onClose={() => setActionSheetVisible(false)}
onAction={action => {
if (action.key === 'copy') {
copyTextToClipboard(currentTask.description)
}
setActionSheetVisible(false)
}}
/>
</div> </div>
</div> </div>

View File

@ -3,40 +3,19 @@ import {getTaskCount} from "../../utils";
import dayjs from "dayjs"; import dayjs from "dayjs";
import {DATE_FORMAT, dayStartUtcFormat, nextDayStartUtcFormat} from "../../utils/timeFormatUtil"; import {DATE_FORMAT, dayStartUtcFormat, nextDayStartUtcFormat} from "../../utils/timeFormatUtil";
import {getDictionary} from "../../utils/dictUtil"; import {getDictionary} from "../../utils/dictUtil";
import {Tag} from "antd-mobile"; import {Divider, Tag} from "antd-mobile";
import {useNavigate} from "react-router-dom"; import {useNavigate} from "react-router-dom";
import {MyRootContext, UPDATE_SEARCH} from "../../components/MyRootContext"; import {MyRootContext, UPDATE_SEARCH} from "../../components/MyRootContext";
const TaskCount = (props) => { const TaskCount = (props) => {
let currentDay = props.currentDay; const {currentDay, taskCount, today, backToToday} = props;
const navigate = useNavigate(); const navigate = useNavigate();
const [taskCount, setTaskCount] = React.useState([]);
const [stateMap, setStateMap] = React.useState(new Map); const [stateMap, setStateMap] = React.useState(new Map);
const [priorityMap, setPriorityMap] = React.useState(new Map); const [priorityMap, setPriorityMap] = React.useState(new Map);
const {dispatch } = useContext(MyRootContext); const {dispatch} = useContext(MyRootContext);
useEffect(() => {
console.log("useEffect");
if (currentDay) {
getTaskCount(dayStartUtcFormat(currentDay),
nextDayStartUtcFormat(currentDay))
.then(taskCount => {
setTaskCount(taskCount)
})
getDictionary("2").then(state => {
setStateMap(state)
})
getDictionary("1").then(priority => {
console.log(priority)
setPriorityMap(priority)
})
}else {
setTaskCount([])
}
}, [currentDay]) const todoDayDetail = () => {
const todoDayDetail = ()=>{
let andSearchModel = {} let andSearchModel = {}
let orSearchModel = {andSearchModel} let orSearchModel = {andSearchModel}
if (currentDay) { if (currentDay) {
@ -82,49 +61,56 @@ const TaskCount = (props) => {
} }
} }
console.log({orSearchModel}) console.log({orSearchModel})
dispatch({type:UPDATE_SEARCH,search:{ dispatch({
type: UPDATE_SEARCH, search: {
"pageSize": 12, "pageSize": 12,
"pageNumber": 1, "pageNumber": 1,
"data": { "data": {
orSearchModel orSearchModel
} }
}}) }
})
navigate("/home/listTask") navigate("/home/listTask")
} }
useEffect(() => {
console.log("useEffect");
getDictionary("2").then(state => {
setStateMap(state)
})
getDictionary("1").then(priority => {
console.log(priority)
setPriorityMap(priority)
})
}, [])
return ( return (
<div style={{margin:"20px"}}> <div style={{margin: "20px"}}>
<h2>TODO日{currentDay && dayjs(currentDay).format(DATE_FORMAT)}代办 <h2>TODO日{currentDay && dayjs(currentDay).format(DATE_FORMAT)}代办
{currentDay&&<a onClick={todoDayDetail}>详情</a>}</h2> {currentDay && <a onClick={todoDayDetail}>详情</a>}
<h3>任务状态</h3> {!dayjs(currentDay).isSame(today, 'date') &&
{ <Fragment><Divider direction='vertical'/><a onClick={() => backToToday()}>回到今天</a></Fragment>}
// taskCount.map(task => { </h2>
// // if (dayjs(task.todoDay).isSame(dayjs(currentDay))){ {taskCount.filter(taskC => dayjs(taskC.todoDay).isSame(dayjs(currentDay), 'date'))?.map(taskC => {
// // console.log(dict); return <Fragment>
// // return <span key={task.todoDay}>{task.todoDay}</span> <h3>任务状态</h3>
// return Array.from(stateMap.entries()).map(([item,value]) => { {
// console.log("key",item,"value",value,task.state) Object.keys(taskC.state).map(ob => {
// return <Tag color={value.josnValue?.color}>value.itemName</Tag> + task.state[item] return <div style={{marginBottom: "20px"}} key={ob}><Tag color={
// }) stateMap.get(ob).jsonValue ? stateMap.get(ob).jsonValue.color : "default"
// }>{stateMap.get(ob).itemName}</Tag>
// // return task.priority.map((key,value)=>getDictionary(2).get(key)+value) {taskC.state[ob]} 项代办;</div>;
// // } })}
// }) <h3>优先级</h3>
taskCount[0] && Object.keys(taskCount[0].state).map(ob => { {
return <div style={{marginBottom:"20px"}} key={ob}><Tag color={ Object.keys(taskC.priority).map(ob => {
stateMap.get(ob).jsonValue?stateMap.get(ob).jsonValue.color:"default" console.log("stateMap.get(ob).jsonValue?.color", priorityMap.get(ob))
}>{stateMap.get(ob).itemName}</Tag> return <div style={{marginBottom: "20px"}} key={ob}><Tag
{taskCount[0].state[ob]} 项代办;</div>; color={priorityMap.get(ob).jsonValue.color}>{priorityMap.get(ob).itemName}</Tag>
}) {taskC.priority[ob]} 项代办;</div>;
} })
<h3>优先级</h3> }
{ </Fragment>
taskCount[0] && Object.keys(taskCount[0].priority).map(ob => { })
console.log("stateMap.get(ob).jsonValue?.color", priorityMap.get(ob))
return <div style={{marginBottom:"20px"}} key={ob}><Tag
color={priorityMap.get(ob).jsonValue.color}>{priorityMap.get(ob).itemName}</Tag>
{taskCount[0].priority[ob]} 项代办;</div>;
})
} }
</div> </div>
) )

View File

@ -1,26 +1,82 @@
import {Calendar} from "antd-mobile"; import {Calendar, Tag} from "antd-mobile";
import dayjs from "dayjs"; import dayjs from "dayjs";
import {TaskCount} from "../TaskCount"; import {TaskCount} from "../TaskCount";
import React,{Fragment} from "react"; import React, {Fragment, useEffect, useLayoutEffect, useRef} from "react";
import {
dateStartUtcFormat,
nextDateStartUtcFormat,
} from "../../utils/timeFormatUtil";
import {getTaskCount} from "../../utils";
import {FrownFill, SmileFill} from "antd-mobile-icons";
import {NEW, OVERDUE, UNDER_WAY} from "../../utils/commonConstant";
const ToDoCal = (props) => { const ToDoCal = (props) => {
const calRef = useRef(null);
const [currentDay, setCurrentDay] = React.useState(new Date()) const [currentDay, setCurrentDay] = React.useState(new Date())
const today = dayjs() const today = new Date()
const [allDay,setAllDay] = React.useState([])
const [taskCount,setTaskCount] = React.useState([])
function listTaskCount(){
if (allDay.length === 0){
return
}
getTaskCount(dateStartUtcFormat(allDay[0]),
nextDateStartUtcFormat(allDay[allDay.length-1]))
.then(res => {
setTaskCount(res)
})
}
useLayoutEffect(()=>{
console.log("useLayoutEffect",allDay)
listTaskCount();
},[])
function backToToday(){
calRef.current.jumpToToday();
setCurrentDay(today)
}
return ( return (
<Fragment> <Fragment>
<Calendar <Calendar
ref={calRef}
selectionMode='single' selectionMode='single'
renderLabel={date => { renderLabel={date => {
if (dayjs(date).isSame(today, 'day')) return '今天' allDay.push(date)
if (date.getDay() === 0 || date.getDay() === 6) {
return '周末' //
} return taskCount.filter(taskC => dayjs(taskC.todoDay).isSame(dayjs(date), 'date'))?.map(taskC => {
//
// Object.keys()
// Object.entries()
if (Object.keys(taskC.state).length>0){
console.log("taskC.state",taskC.state)
if (taskC.state[OVERDUE]){
return <FrownFill color='var(--adm-color-danger)'/>;
}
// warn
if (taskC.state[NEW]||taskC.state[UNDER_WAY]){
return <SmileFill color='var(--adm-color-warning)'/>;
}
// 绿
return <SmileFill color='var(--adm-color-success)'/>;
}
})
// if (dayjs(date).isSame(today, 'day')) return ''
// if (date.getDay() === 0 || date.getDay() === 6) {
// return ''
// }
}} }}
// renderDate={date=>{
// return <span style={{"color":"red"}}>{dayjs(date).date()}</span>
// }}
defaultValue={currentDay} defaultValue={currentDay}
onChange={val => { onChange={val => {
setCurrentDay(val) setCurrentDay(val)
}} }}
/> />
<TaskCount currentDay={currentDay}/> <TaskCount currentDay={currentDay} taskCount={taskCount} today={today} backToToday={backToToday}/>
</Fragment> </Fragment>
) )
} }

View File

@ -1,79 +1,24 @@
import {Fragment, useEffect, useMemo, useState} from "react"; import {Fragment} from "react";
import {Card, Cascader, CascaderView} from "antd-mobile"; import {Card, CascaderView, Dialog, Toast} from "antd-mobile";
import {getTaskByPid} from "../../utils";
import dayjs from "dayjs";
import {DATE_TIME_FORMAT} from "../../utils/timeFormatUtil";
import {useChildList} from "../../hooks/useChildList"; import {useChildList} from "../../hooks/useChildList";
import {useNavigate} from "react-router-dom";
export default () => { export default () => {
// const [valueToOptions, setValueToOptions] = useState([])
// const [currentTask, setCurrentTask] = useState({
// name:"想做的事情",
// description:"想做的事情描述",
// expectedStartTime:dayjs().format(DATE_TIME_FORMAT),
// expectedEndTime:"",
// state:"",
// priority:"",
// })
// const options = useMemo(() => {
// function generate(v) {
// const options = valueToOptions[v]
// if (options === null || options === undefined) {
// return undefined
// }
// // if (options === undefined) {
// // return Cascader.optionSkeleton
// // }
// return options.map(option => ({
// ...option,
// children: generate(option.value),
// }))
// }
//
// return generate('0') ?? []
// }, [valueToOptions])
//
// async function fetchOptionsForValue(v, level) {
// if (v in valueToOptions) return
// // if (level >= 3) {
// // setValueToOptions(prev => ({
// // ...prev,
// // [v]: null,
// // }))
// // return
// // }
// const data = await getTaskByPid(v)
// console.log("await getTaskByPid(v)", data.content)
// const options =
// data.content.length === 0
// ? null
// : data.content.map(task => ({
// value: task.id,
// label: task.name,
// }))
// console.log("await getTaskByPid(v) options", options)
// if (options){
// setValueToOptions(prev => ({
// ...prev,
// [v]: options,
// }))
// }else {
// setValueToOptions(prev => ({
// ...prev,
// [v]: undefined,
// }))
// }
// }
// useEffect(() => {
// fetchOptionsForValue('0', 0)
// }, [])
const {task, options, changeTaskId,} = useChildList(); const {task, options, changeTaskId,} = useChildList();
const navigate = useNavigate();
return <Fragment> return <Fragment>
<Card title={task?.name} style={{height: "30vh", borderRadius: "10px"}}> <Card title={task?.name} style={{height: "30vh", borderRadius: "10px"}}
onClick={async ()=>{
if (task&&task.id) {
const result = await Dialog.confirm({ content: '确定要进入详情页吗?' })
if (result) {
navigate(`/detail/selectTask?id=${task.id}`)
}
}
}}
>
<p>{task?.description}</p> <p>{task?.description}</p>
<p>预计开始时间:{task?.expectedStartTime}</p> <p>预计开始时间:{task?.expectedStartTime}</p>
<p>预计结束时间:{task?.expectedEndTime}</p> <p>预计结束时间:{task?.expectedEndTime}</p>
</Card> </Card>

View File

@ -0,0 +1,5 @@
const OVERDUE= '10';
const COMPLETE = '7';
const NEW = '8';
const UNDER_WAY = '9';
export { OVERDUE, COMPLETE, NEW, UNDER_WAY };

View File

@ -0,0 +1,39 @@
export function copyTextToClipboard(text) {
return new Promise((resolve, reject) => {
if (navigator.clipboard && window.isSecureContext) {
// 尝试使用现代剪贴板 API
navigator.clipboard.writeText(text)
.then(() => resolve('文本已成功复制到剪贴板'))
.catch((err) => reject(`无法复制文本:${err.message}`));
} else {
// 回退到旧方法
fallbackCopyTextToClipboard(text)
.then(message => resolve(message))
.catch(err => reject(err));
}
});
}
function fallbackCopyTextToClipboard(text) {
return new Promise((resolve, reject) => {
const textArea = document.createElement("textarea");
textArea.value = text;
// 避免 CSS 样式影响滚动条
textArea.style.position = 'fixed';
textArea.style.left = '-9999px';
document.body.appendChild(textArea);
textArea.focus();
textArea.select();
try {
const successful = document.execCommand('copy');
const msg = successful ? '文本已成功复制到剪贴板' : '无法复制文本';
resolve(msg);
} catch (err) {
reject(`无法复制文本:${err.message}`);
}
document.body.removeChild(textArea);
});
}

View File

@ -21,6 +21,22 @@ function dayStartUtcFormat(dayjsString) {
return dayJsObj.set('h', 0).set('m', 0).set('s', 0).set('ms', 0).utc().format() return dayJsObj.set('h', 0).set('m', 0).set('s', 0).set('ms', 0).utc().format()
} }
function dateStartUtcFormat(dateObject) {
if (!dateObject) {
return
}
let dayJsObj= dayjs(dateObject);
return dayJsObj.set('h', 0).set('m', 0).set('s', 0).set('ms', 0).utc().format()
}
function nextDateStartUtcFormat(dateObject) {
if (!dateObject) {
return
}
let dayJsObj= dayjs(dateObject);
return dayJsObj.add(1, "d").set('h', 0).set('m', 0).set('s', 0).set('ms', 0).utc().format()
}
function nextDayStartUtcFormat(dayjsString) { function nextDayStartUtcFormat(dayjsString) {
if (!dayjsString) { if (!dayjsString) {
return return
@ -34,4 +50,7 @@ function nextDayStartUtcFormat(dayjsString) {
return dayJsObj.add(1, "d").set('h', 0).set('m', 0).set('s', 0).set('ms', 0).utc().format() return dayJsObj.add(1, "d").set('h', 0).set('m', 0).set('s', 0).set('ms', 0).utc().format()
} }
export {DATE_TIME_FORMAT, DATE_FORMAT,DATE_TIME_FORMAT_SIMPLE,dayStartUtcFormat,nextDayStartUtcFormat} export {DATE_TIME_FORMAT, DATE_FORMAT,DATE_TIME_FORMAT_SIMPLE,
dayStartUtcFormat,nextDayStartUtcFormat,
dateStartUtcFormat,nextDateStartUtcFormat,
}