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/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0",
"antd-mobile": "^5.27.0",
"antd-mobile": "^5.28.0",
"antd-mobile-icons": "^0.3.0",
"axios": "^1.2.2",
"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 "./index.css"
import {useOutletContext, useSearchParams} from "react-router-dom";
import {addTaskLog, listTaskLog} from "../../api/detailLogTaskApi";
import dayjs from "dayjs";
import {copyTextToClipboard} from "../../utils/copyToClipBoard";
export function DetailLogTask() {
// 设置标题栏
@ -13,11 +14,32 @@ export function DetailLogTask() {
setRightDesc(<Fragment/>);
}, [])
let [params] = useSearchParams();
// 发送展示日记
const [currentShow, setCurrentShow] = useState('need');
const textAreaRef = useRef(null);
const [taskLogMap, setTaskLogMap] = useState([]);
const [taskLogList, setTaskLogList] = 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(() => {
return taskLogList.reduce((map, taskLog) => {
if (!map.has(dayjs(taskLog.createdDate).format("YYYY-MM-DD"))) {
@ -42,7 +64,14 @@ export function DetailLogTask() {
textAreaRef.current.focus();
// 获取之前的日志信息根据日期分组排序遍历map
listTaskLog(`{
"sortList":[{"direction":"DESC","property":"createdDate"}]
"sortList":[{"direction":"DESC","property":"createdDate"}],
"data": {
"andList":[{
"name":"taskId",
"operateType":"=",
"value":"${params.get('id')}"
}]
}
}`).then(res => {
console.log({res})
if (res.content.length > 0) {
@ -57,9 +86,17 @@ export function DetailLogTask() {
}
})
}, [])
function NoNeed(){
return <s>失效</s>
}
return <Fragment>
<div className="log-task-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 => {
return (<Fragment>
@ -72,7 +109,13 @@ export function DetailLogTask() {
</div>}
{taskLogMapMemory.get(key).map(taskLog => {
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>
})}</Fragment>)
})
@ -96,6 +139,19 @@ export function DetailLogTask() {
onClick={handleSend}
>发送
</Button>
<ActionSheet
extra='请选择你要进行的操作'
cancelText='取消'
visible={actionSheetVisible}
actions={actions}
onClose={() => setActionSheetVisible(false)}
onAction={action => {
if (action.key === 'copy') {
copyTextToClipboard(currentTask.description)
}
setActionSheetVisible(false)
}}
/>
</div>
</div>

View File

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

View File

@ -1,26 +1,82 @@
import {Calendar} from "antd-mobile";
import {Calendar, Tag} from "antd-mobile";
import dayjs from "dayjs";
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 calRef = useRef(null);
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 (
<Fragment>
<Calendar
ref={calRef}
selectionMode='single'
renderLabel={date => {
if (dayjs(date).isSame(today, 'day')) return '今天'
if (date.getDay() === 0 || date.getDay() === 6) {
return '周末'
}
allDay.push(date)
//
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}
onChange={val => {
setCurrentDay(val)
}}
/>
<TaskCount currentDay={currentDay}/>
<TaskCount currentDay={currentDay} taskCount={taskCount} today={today} backToToday={backToToday}/>
</Fragment>
)
}

View File

@ -1,79 +1,24 @@
import {Fragment, useEffect, useMemo, useState} from "react";
import {Card, Cascader, CascaderView} from "antd-mobile";
import {getTaskByPid} from "../../utils";
import dayjs from "dayjs";
import {DATE_TIME_FORMAT} from "../../utils/timeFormatUtil";
import {Fragment} from "react";
import {Card, CascaderView, Dialog, Toast} from "antd-mobile";
import {useChildList} from "../../hooks/useChildList";
import {useNavigate} from "react-router-dom";
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 navigate = useNavigate();
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?.expectedStartTime}</p>
<p>预计结束时间:{task?.expectedEndTime}</p>
</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()
}
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) {
if (!dayjsString) {
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()
}
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,
}