feat:添加hook

This commit is contained in:
1708-huayu 2025-01-17 19:21:37 +08:00
parent 0e3275fe33
commit 6982280de2
13 changed files with 337 additions and 98 deletions

11
package-lock.json generated
View File

@ -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",

View File

@ -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",

View File

@ -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(<AddOutline/>)
tagList.push(<AddOutline key = "add-icon"/>)
} if (dict) {
tagList.push(<Tag key={dict.id} color={dict.jsonValue?.color}>
{dict.itemName}
@ -87,7 +87,7 @@ const DetailSearchBar = (props) => {
onClick={() => {
navigate("/detail/searchTask");
}}>
<SearchOutline/>
<SearchOutline key="search-icon"/>
{/*根据search处理搜素框展示内容*/}
{tags}
</div>
@ -102,7 +102,7 @@ const DetailSearchBar = (props) => {
}});
setTags([]);
}}>
<CloseOutline style={{float: "right"}}/>
<CloseOutline key="close-icon" style={{float: "right"}}/>
</div>
}
{/*<SearchBar placeholder='请输入内容,无法放入标签' onFocus={() => {*/}

View File

@ -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 <h1>{isOnline ? '✅ Online' : '❌ Disconnected'}</h1>;
}

85
src/hooks/useChildList.js Normal file
View File

@ -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
};
}

View File

@ -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;
}

View File

@ -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",

View File

@ -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' },
],
]

View File

@ -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 <div>pc端可访问:http://www.hauruyu.com
<Button color={"danger"} onClick={()=>{
const [columnSort, setColumnSort] = useState()
return <div>
<List header='网站:http://www.hauruyu.com'>
<List.Item extra={<Switch defaultChecked/>}>新建任务时父任务只展示未完成任务</List.Item>
<Picker
columns={columnSortConstant}
value={columnSort}
onConfirm={setColumnSort}
onSelect={(val, extend) => {
console.log('onSelect', val, extend.items)
}}>
{(items, {open}) => {
return (
// <Space align='end'>
<List.Item onClick={open} extra={items.every(item => item === null)
? '未选择'
: items.map(item => item?.label ?? '未选择').join(' - ')}>
任务排序方式:
</List.Item>
// </Space>
)
}}
</Picker>
<List.Item description='管理已授权的产品和设备' clickable>
授权管理
</List.Item>
<List.Item title='副标题信息A' description='副标题信息B' clickable>
这里是主信息
</List.Item>
</List>
{/*<Form*/}
{/* layout='horizontal'*/}
{/* footer={*/}
{/* <Button block type='submit' color='primary' size='large'>*/}
{/* 提交*/}
{/* </Button>*/}
{/* }*/}
{/*>*/}
{/* <Form.Header>网站:http://www.hauruyu.com</Form.Header>*/}
{/* <Form.Item*/}
{/* name='delivery'*/}
{/* label='新建任务时,父任务只展示未完成任务'*/}
{/* childElementPosition='right'*/}
{/* >*/}
{/* <Switch/>*/}
{/* </Form.Item>*/}
{/*</Form>*/}
<Button color={"danger"} onClick={() => {
localStorage.removeItem('platform-security');
Cookies.remove('platform-security');
window.location.href="http:///www.huaruyu.com/login"
window.location.href = "http:///www.huaruyu.com/login"
}}>退出登录</Button>
</div>
}

View File

@ -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",

View File

@ -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),
}))
}
return generate('0') ?? []
}, [valueToOptions])
async function fetchOptionsForValue(v, level) {
if (v in valueToOptions) return
// if (level >= 3) {
// 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]: null,
// [v]: options,
// }))
// }else {
// setValueToOptions(prev => ({
// ...prev,
// [v]: undefined,
// }))
// 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)
// }, [])
}
useEffect(() => {
fetchOptionsForValue('0', 0)
}, [])
const {task, options, changeTaskId,} = useChildList();
return <Fragment>
<Card title='任务名称'>
任务详情
<Card title={task?.name} style={{height: "30vh", borderRadius: "10px"}}>
<p>{task?.description}</p>
<p>预计开始时间:{task?.expectedStartTime}</p>
<p>预计结束时间:{task?.expectedEndTime}</p>
</Card>
<CascaderView options={options} onChange={value => {
fetchOptionsForValue(value)
}}/>
<CascaderView
options={options}
onChange={value => {
// fetchOptionsForValue(value[value.length-1])
console.log({value})
changeTaskId(value)
}}
/>
</Fragment>
}

View File

@ -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) => {

View File

@ -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}