From 6982280de2dcc208cf451966adb2dea26321f685 Mon Sep 17 00:00:00 2001 From: 1708-huayu <57060237+1708-huayu@users.noreply.github.com> Date: Fri, 17 Jan 2025 19:21:37 +0800 Subject: [PATCH] =?UTF-8?q?feat:=E6=B7=BB=E5=8A=A0hook?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-lock.json | 11 ++ package.json | 2 +- src/components/DetailSearchBar/index.js | 6 +- src/components/Online/index.js | 21 +++ src/hooks/useChildList.js | 85 +++++++++++ src/hooks/useOnlineStatus.js | 25 ++++ src/pages/DetailSearchContext/index.js | 15 +- .../PersonalCenter/columnSortConstant.js | 13 ++ src/pages/PersonalCenter/index.js | 65 ++++++++- src/pages/TaskCount/index.js | 18 +-- src/pages/ToDoTree/index.js | 138 ++++++++++-------- src/utils/index.js | 2 +- src/utils/timeFormatUtil.js | 34 +++-- 13 files changed, 337 insertions(+), 98 deletions(-) create mode 100644 src/components/Online/index.js create mode 100644 src/hooks/useChildList.js create mode 100644 src/hooks/useOnlineStatus.js create mode 100644 src/pages/PersonalCenter/columnSortConstant.js diff --git a/package-lock.json b/package-lock.json index 5b5ea7b..14568cf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,6 +14,7 @@ "antd-mobile-icons": "^0.3.0", "axios": "^1.2.2", "dayjs": "^1.11.13", + "dayjs-plugin-utc": "^0.1.2", "js-cookie": "^2.2.1", "node-gyp": "^9.3.1", "react": "^18.2.0", @@ -6431,6 +6432,11 @@ "resolved": "https://registry.npmmirror.com/dayjs/-/dayjs-1.11.13.tgz", "integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==" }, + "node_modules/dayjs-plugin-utc": { + "version": "0.1.2", + "resolved": "https://registry.npmmirror.com/dayjs-plugin-utc/-/dayjs-plugin-utc-0.1.2.tgz", + "integrity": "sha512-ExERH5o3oo6jFOdkvMP3gytTCQ9Ksi5PtylclJWghr7k7m3o2U5QrwtdiJkOxLOH4ghr0EKhpqGefzGz1VvVJg==" + }, "node_modules/debug": { "version": "4.3.4", "resolved": "https://registry.npmmirror.com/debug/-/debug-4.3.4.tgz", @@ -21947,6 +21953,11 @@ "resolved": "https://registry.npmmirror.com/dayjs/-/dayjs-1.11.13.tgz", "integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==" }, + "dayjs-plugin-utc": { + "version": "0.1.2", + "resolved": "https://registry.npmmirror.com/dayjs-plugin-utc/-/dayjs-plugin-utc-0.1.2.tgz", + "integrity": "sha512-ExERH5o3oo6jFOdkvMP3gytTCQ9Ksi5PtylclJWghr7k7m3o2U5QrwtdiJkOxLOH4ghr0EKhpqGefzGz1VvVJg==" + }, "debug": { "version": "4.3.4", "resolved": "https://registry.npmmirror.com/debug/-/debug-4.3.4.tgz", diff --git a/package.json b/package.json index e705b14..1b61d72 100644 --- a/package.json +++ b/package.json @@ -11,10 +11,10 @@ "antd-mobile-icons": "^0.3.0", "axios": "^1.2.2", "dayjs": "^1.11.13", + "dayjs-plugin-utc": "^0.1.2", "js-cookie": "^2.2.1", "node-gyp": "^9.3.1", "react": "^18.2.0", - "react-beautiful-dnd": "^13.1.1", "react-dom": "^18.2.0", "react-router-dom": "^6.6.2", "react-scripts": "5.0.1", diff --git a/src/components/DetailSearchBar/index.js b/src/components/DetailSearchBar/index.js index 3f3ea40..8c02710 100644 --- a/src/components/DetailSearchBar/index.js +++ b/src/components/DetailSearchBar/index.js @@ -68,7 +68,7 @@ const DetailSearchBar = (props) => { if (searchObj.name === "state" && searchObj.value === "10") { const dict = stateDictionary.get(searchObj.value); if (dict && tagList.length > 0) { - tagList.push() + tagList.push() } if (dict) { tagList.push( {dict.itemName} @@ -87,7 +87,7 @@ const DetailSearchBar = (props) => { onClick={() => { navigate("/detail/searchTask"); }}> - + {/*根据search处理搜素框展示内容*/} {tags} @@ -102,7 +102,7 @@ const DetailSearchBar = (props) => { }}); setTags([]); }}> - + } {/* {*/} diff --git a/src/components/Online/index.js b/src/components/Online/index.js new file mode 100644 index 0000000..583a545 --- /dev/null +++ b/src/components/Online/index.js @@ -0,0 +1,21 @@ +import {useEffect, useState} from "react"; + +export default function StatusBar() { + const [isOnline, setIsOnline] = useState(true); + useEffect(() => { + function handleOnline() { + setIsOnline(true); + } + function handleOffline() { + setIsOnline(false); + } + window.addEventListener('online', handleOnline); + window.addEventListener('offline', handleOffline); + return () => { + window.removeEventListener('online', handleOnline); + window.removeEventListener('offline', handleOffline); + }; + }, []); + + return

{isOnline ? '✅ Online' : '❌ Disconnected'}

; +} \ No newline at end of file diff --git a/src/hooks/useChildList.js b/src/hooks/useChildList.js new file mode 100644 index 0000000..bea99c2 --- /dev/null +++ b/src/hooks/useChildList.js @@ -0,0 +1,85 @@ +import {useEffect, useMemo, useState} from "react"; +import dayjs from "dayjs"; +import {DATE_TIME_FORMAT} from "../utils/timeFormatUtil"; +import {getTaskByPid} from "../utils"; +import {Cascader} from "antd-mobile"; + +export function useChildList() { + const [task, setTask] = useState({ + name: "想做的事情", + description: "想做的事情描述", + expectedStartTime: dayjs().format(DATE_TIME_FORMAT), + expectedEndTime: "", + state: "", + priority: "", + }); + // Map key为pid和他的子项 + const [childList, setChildList] = useState({}); + + useEffect(() => { + fetchOptionsForValue('0', 0) + }, []) + + // list嵌套选中项的list + const options = useMemo(() => { + function generate(v) { + const options = childList[v] + if (options === null) { + return undefined + } + if (options === undefined) { + return Cascader.optionSkeleton + } + return options.map(option => ({ + ...option, + children: generate(option.value), + })) + } + + return generate('0') ?? [] + }, [childList]) + + async function fetchOptionsForValue(v, level) { + if (v in childList) return + const data = await getTaskByPid(v) + const options = + data.content.length === 0 + ? null + : data.content.map(task => ({ + value: task.id, + label: task.name, + ...task, + })) + + setChildList(prev => ({ + ...prev, + [v]: options, + })) + + } + + function changeTaskId(taskId) { + if (!(taskId && taskId.length > 0)) { + return; + } + let currentTaskId = taskId[taskId.length - 1]; + fetchOptionsForValue(currentTaskId); + if (taskId.length > 1) { + setTask(childList[taskId[taskId.length - 2]].find(task => task.id === currentTaskId)) + } else { + setTask(childList['0'].find(task => task.id === currentTaskId)) + } + } + + function removeTaskId(taskId) { + childList.delete(taskId); + setChildList(...childList) + } + + return { + changeTaskId, + removeTaskId, + options, + task + }; +} \ No newline at end of file diff --git a/src/hooks/useOnlineStatus.js b/src/hooks/useOnlineStatus.js new file mode 100644 index 0000000..fa4a3a1 --- /dev/null +++ b/src/hooks/useOnlineStatus.js @@ -0,0 +1,25 @@ +import { useState, useEffect } from 'react'; + +// React 应用是由组件构成,而组件由内置或自定义 Hook 构成。 +// React 组件名称必须以大写字母开头,比如 StatusBar 和 SaveButton。React 组件还需要返回一些 React 能够显示的内容,比如一段 JSX。 +// Hook 的名称必须以 use 开头,然后紧跟一个大写字母,就像内置的 useState 或者本文早前的自定义 useOnlineStatus 一样。Hook 可以返回任意值。 +// 自定义 Hook 共享的是状态逻辑,而不是状态本身 +// 随着时间的推移,应用中大部分 Effect 都会存在于自定义 Hook 内部。 +export function useOnlineStatus() { + const [isOnline, setIsOnline] = useState(true); + useEffect(() => { + function handleOnline() { + setIsOnline(true); + } + function handleOffline() { + setIsOnline(false); + } + window.addEventListener('online', handleOnline); + window.addEventListener('offline', handleOffline); + return () => { + window.removeEventListener('online', handleOnline); + window.removeEventListener('offline', handleOffline); + }; + }, []); + return isOnline; +} diff --git a/src/pages/DetailSearchContext/index.js b/src/pages/DetailSearchContext/index.js index 679cd23..01e6006 100644 --- a/src/pages/DetailSearchContext/index.js +++ b/src/pages/DetailSearchContext/index.js @@ -6,6 +6,7 @@ import {CloseCircleFill} from "antd-mobile-icons"; import {useLocation, useNavigate, useOutletContext} from "react-router-dom"; import {getDictionary} from "../../utils/dictUtil"; import {MyRootContext, UPDATE_SEARCH} from "../../components/MyRootContext"; +import {dayStartUtcFormat, nextDayStartUtcFormat} from "../../utils/timeFormatUtil"; const DetailSearchContext = () => { const navigate = useNavigate(); @@ -78,7 +79,7 @@ const DetailSearchContext = () => { searchCondition.push({"name": "name", "value": pidArray[pidArray.length - 1], "operateType": "="}) } if (name && name !== "") { - searchCondition.push({"name": "name", "value": name, "operateType": "LIKE"}) + searchCondition.push({"name": "name", "value": name, "operateType": "%"}) } if (priority && priority.length !== 0) { searchCondition.push({"name": "priority", "value": priority.join(","), "operateType": "IN"}) @@ -89,22 +90,22 @@ const DetailSearchContext = () => { if (todoDay) { andSearchModel.andList = [{ "name": "expectedStartTime", - "value": dayjs(todoDay).add(1, "d").set('h', 0).set('m', 0).set('s', 0).set('ms', 0).format(), + "value": nextDayStartUtcFormat(todoDay), "operateType": "<" }, { "name": "expectedEndTime", - "value": dayjs(todoDay).set('h', 0).set('m', 0).set('s', 0).set('ms', 0).format(), + "value": dayStartUtcFormat(todoDay), "operateType": ">" }] andSearchModel.orSearchModel = { "andList": [ { "name": "expectedStartTime", - "value": dayjs(todoDay).add(1, "d").set('h', 0).set('m', 0).set('s', 0).set('ms', 0).format(), + "value": nextDayStartUtcFormat(todoDay), "operateType": "<" }, { "name": "expectedStartTime", - "value": dayjs(todoDay).set('h', 0).set('m', 0).set('s', 0).set('ms', 0).format(), + "value": dayStartUtcFormat(todoDay), "operateType": ">" }, { "name": "expectedEndTime", @@ -114,11 +115,11 @@ const DetailSearchContext = () => { "andList": [ { "name": "expectedEndTime", - "value": dayjs(todoDay).add(1, "d").set('h', 0).set('m', 0).set('s', 0).set('ms', 0).format(), + "value": nextDayStartUtcFormat(todoDay), "operateType": "<" }, { "name": "expectedEndTime", - "value": dayjs(todoDay).set('h', 0).set('m', 0).set('s', 0).set('ms', 0).format(), + "value": dayStartUtcFormat(todoDay), "operateType": ">" }, { "name": "expectedStartTime", diff --git a/src/pages/PersonalCenter/columnSortConstant.js b/src/pages/PersonalCenter/columnSortConstant.js new file mode 100644 index 0000000..e592c63 --- /dev/null +++ b/src/pages/PersonalCenter/columnSortConstant.js @@ -0,0 +1,13 @@ +export const columnSortConstant = [ + [ + { label: '创建时间', value: 'createdDate' }, + { label: '修改时间', value: 'lastModifiedDate' }, + { label: '期望开始时间', value: 'expectedStartTime' }, + { label: '期望结束时间', value: 'expectedEndTime' }, + { label: '任务优先级', value: 'priority' }, + ], + [ + { label: '升序', value: 'am' }, + { label: '降序', value: 'pm' }, + ], +] \ No newline at end of file diff --git a/src/pages/PersonalCenter/index.js b/src/pages/PersonalCenter/index.js index fcd4823..4fa7872 100644 --- a/src/pages/PersonalCenter/index.js +++ b/src/pages/PersonalCenter/index.js @@ -1,13 +1,62 @@ -import React from "react"; -import {Button} from "antd-mobile"; +import React, {useState} from "react"; +import {Button, Form, List, Picker, Space, Switch} from "antd-mobile"; import Cookies from "js-cookie"; +import {columnSortConstant} from "./columnSortConstant"; export function PersonalCenter() { - return
pc端可访问:http://www.hauruyu.com - + const [columnSort, setColumnSort] = useState() + return
+ + }>新建任务时父任务只展示未完成任务 + + { + console.log('onSelect', val, extend.items) + }}> + {(items, {open}) => { + return ( + // + item === null) + ? '未选择' + : items.map(item => item?.label ?? '未选择').join(' - ')}> + 任务排序方式: + + // + ) + }} + + + + 授权管理 + + + 这里是主信息 + + + {/**/} + {/* 提交*/} + {/* */} + {/* }*/} + {/*>*/} + {/* 网站:http://www.hauruyu.com*/} + {/* */} + {/* */} + {/* */} + {/**/} +
} \ No newline at end of file diff --git a/src/pages/TaskCount/index.js b/src/pages/TaskCount/index.js index 1dbd22e..ffbaa8e 100644 --- a/src/pages/TaskCount/index.js +++ b/src/pages/TaskCount/index.js @@ -1,7 +1,7 @@ import React, {Fragment, useContext, useEffect, useMemo} from "react"; import {getTaskCount} from "../../utils"; import dayjs from "dayjs"; -import {DATE_FORMAT} from "../../utils/timeFormatUtil"; +import {DATE_FORMAT, dayStartUtcFormat, nextDayStartUtcFormat} from "../../utils/timeFormatUtil"; import {getDictionary} from "../../utils/dictUtil"; import {Tag} from "antd-mobile"; import {useNavigate} from "react-router-dom"; @@ -18,8 +18,8 @@ const TaskCount = (props) => { useEffect(() => { console.log("useEffect"); if (currentDay) { - getTaskCount(dayjs(currentDay).set('h', 0).set('m', 0).set('s', 0).set('ms', 0), - dayjs(currentDay).add(1, "d").set('h', 0).set('m', 0).set('s', 0).set('ms', 0)) + getTaskCount(dayStartUtcFormat(currentDay), + nextDayStartUtcFormat(currentDay)) .then(taskCount => { setTaskCount(taskCount) }) @@ -42,22 +42,22 @@ const TaskCount = (props) => { if (currentDay) { andSearchModel.andList = [{ "name": "expectedStartTime", - "value": dayjs(currentDay).add(1, "d").set('h', 0).set('m', 0).set('s', 0).set('ms', 0).format(), + "value": nextDayStartUtcFormat(currentDay), "operateType": "<" }, { "name": "expectedEndTime", - "value": dayjs(currentDay).set('h', 0).set('m', 0).set('s', 0).set('ms', 0).format(), + "value": dayStartUtcFormat(currentDay), "operateType": ">" }] andSearchModel.orSearchModel = { "andList": [ { "name": "expectedStartTime", - "value": dayjs(currentDay).add(1, "d").set('h', 0).set('m', 0).set('s', 0).set('ms', 0).format(), + "value": nextDayStartUtcFormat(currentDay), "operateType": "<" }, { "name": "expectedStartTime", - "value": dayjs(currentDay).set('h', 0).set('m', 0).set('s', 0).set('ms', 0).format(), + "value": dayStartUtcFormat(currentDay), "operateType": ">" }, { "name": "expectedEndTime", @@ -67,11 +67,11 @@ const TaskCount = (props) => { "andList": [ { "name": "expectedEndTime", - "value": dayjs(currentDay).add(1, "d").set('h', 0).set('m', 0).set('s', 0).set('ms', 0).format(), + "value": nextDayStartUtcFormat(currentDay), "operateType": "<" }, { "name": "expectedEndTime", - "value": dayjs(currentDay).set('h', 0).set('m', 0).set('s', 0).set('ms', 0).format(), + "value": dayStartUtcFormat(currentDay), "operateType": ">" }, { "name": "expectedStartTime", diff --git a/src/pages/ToDoTree/index.js b/src/pages/ToDoTree/index.js index 656ecb7..f6d15c8 100644 --- a/src/pages/ToDoTree/index.js +++ b/src/pages/ToDoTree/index.js @@ -1,70 +1,90 @@ 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 {useChildList} from "../../hooks/useChildList"; export default () => { - const [valueToOptions, setValueToOptions] = useState([]) - const options = useMemo(() => { - function generate(v) { - const options = valueToOptions[v] - if (options === null) { - return undefined - } - if (options === undefined) { - return Cascader.optionSkeleton - } - return options.map(option => ({ - ...option, - children: generate(option.value), - })) - } + // 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, + // })) + // } + // } - return generate('0') ?? [] - }, [valueToOptions]) + // useEffect(() => { + // fetchOptionsForValue('0', 0) + // }, []) - 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]: [], - })) - } - - - } - - useEffect(() => { - fetchOptionsForValue('0', 0) - }, []) + const {task, options, changeTaskId,} = useChildList(); return - - 任务详情 + +

{task?.description}

+ +

预计开始时间:{task?.expectedStartTime}

+

预计结束时间:{task?.expectedEndTime}

- { - fetchOptionsForValue(value) - }}/> + { + // fetchOptionsForValue(value[value.length-1]) + console.log({value}) + changeTaskId(value) + }} + + />
} \ No newline at end of file diff --git a/src/utils/index.js b/src/utils/index.js index 8b34ec6..c8ed1bb 100644 --- a/src/utils/index.js +++ b/src/utils/index.js @@ -2,7 +2,7 @@ import {requestUtil} from "./requestUtil"; export const getTaskList = (data) => { let request = encodeURI(JSON.stringify(data)) - return requestUtil.get('/V2/search/task_message_tree?search=' + request); + return requestUtil.get('/todo-server/V2/search/task_message_tree?search=' + request); } // 根据pid获取未完成的任务 export const getTaskByPid = (pid) => { diff --git a/src/utils/timeFormatUtil.js b/src/utils/timeFormatUtil.js index b6f9ae2..6470191 100644 --- a/src/utils/timeFormatUtil.js +++ b/src/utils/timeFormatUtil.js @@ -1,23 +1,37 @@ -import dayjs from "dayjs"; - +import dayjs, {isDayjs} from "dayjs"; +import utc from "dayjs/plugin/utc"; +dayjs.extend(utc) const DATE_FORMAT = "YYYY-MM-DD" // 到秒没啥意义 const DATE_TIME_FORMAT = "YYYY-MM-DD HH:mm" const DATE_TIME_FORMAT_SIMPLE = "MM-DD HH:mm" -function dayStartUtcFormat(dayjs) { - if (dayjs) { - return dayjs.set('h', 0).set('m', 0).set('s', 0).set('ms', 0).format() +function dayStartUtcFormat(dayjsString) { + + if (!dayjsString) { + return } - return null; + let dayJsObj; + if (isDayjs(dayjsString)) { + dayJsObj = dayjsString; + }else { + dayJsObj = dayjs(dayjsString); + } + return dayJsObj.set('h', 0).set('m', 0).set('s', 0).set('ms', 0).utc().format() } -function nextDayStartUtcFormat(dayjs) { - if (dayjs) { - return dayjs.add(1, "d").set('h', 0).set('m', 0).set('s', 0).set('ms', 0).format() +function nextDayStartUtcFormat(dayjsString) { + if (!dayjsString) { + return } - return null + let dayJsObj; + if (isDayjs(dayjsString)) { + dayJsObj = dayjsString; + }else { + dayJsObj = dayjs(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} \ No newline at end of file