feat:日历滑动

This commit is contained in:
1708-huayu 2025-01-24 18:56:31 +08:00
parent c2eb04b4b0
commit 55cfe79dab
5 changed files with 234 additions and 111 deletions

View File

@ -5,7 +5,7 @@ import {
Button, Button,
Dialog, Dialog,
TextArea, TextArea,
Space, Tag, Radio Space, Tag, Radio, Checkbox
} from 'antd-mobile' } from 'antd-mobile'
import ParentTask from "../ParentTask"; import ParentTask from "../ParentTask";
import "./index.css" import "./index.css"
@ -13,25 +13,34 @@ import {addTask, getPTask, getTaskById, updateTask} from "../../utils";
import {useLocation, useNavigate, useOutletContext, useSearchParams} from "react-router-dom"; import {useLocation, useNavigate, useOutletContext, useSearchParams} from "react-router-dom";
import dayjs from "dayjs"; import dayjs from "dayjs";
import DataPickItemPopup from "../DataPickItemPopup"; import DataPickItemPopup from "../DataPickItemPopup";
import {getDictionary} from "../../utils/dictUtil";
const DetailForm= () => { const DetailForm = () => {
// 进入此页面的操作:添加,修改,详情(按钮为添加任务日志) // 进入此页面的操作:添加,修改,详情(按钮为添加任务日志)
const location = useLocation(); const location = useLocation();
let [params] = useSearchParams(); let [params] = useSearchParams();
// 设置标题栏 // 设置标题栏
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 [priorityList, setPriorityList] = React.useState([]);
// 路由 // 路由
const navigate = useNavigate(); const navigate = useNavigate();
// 获取form引用 // 获取form引用
const [form] = Form.useForm(); const [form] = Form.useForm();
const addEditPName=(name)=>{ const addEditPName = (name) => {
setPName(name) setPName(name)
} }
useEffect(() => { useEffect(() => {
getDictionary("2").then(stateDictionary => {
setStateList(Array.from(stateDictionary.values()));
})
getDictionary("1").then(priorityDictionary => {
setPriorityList(Array.from(priorityDictionary.values()));
})
window.scrollTo(0, 0); window.scrollTo(0, 0);
if (location.pathname.endsWith("addTask")) { if (location.pathname.endsWith("addTask")) {
setTitle("添加任务"); setTitle("添加任务");
@ -50,25 +59,27 @@ const DetailForm= () => {
initData(params.get('id')); initData(params.get('id'));
setUpdateFiledDisabled(true); setUpdateFiledDisabled(true);
setRightDesc(<div> setRightDesc(<div>
<Button type="button" color="danger" onClick={() => {Dialog.show({ <Button type="button" color="danger" onClick={() => {
content: `进入任务编辑`, Dialog.show({
closeOnAction: true, content: `进入任务编辑`,
actions: [ closeOnAction: true,
[ actions: [
{ [
key: 'cancel', {
text: '取消', key: 'cancel',
}, text: '取消',
{ },
key: 'confirm', {
text: '确认', key: 'confirm',
onClick: () => { text: '确认',
navigate(`/detail/updateTask?id=${params.get('id')}`) onClick: () => {
navigate(`/detail/updateTask?id=${params.get('id')}`)
}
} }
} ],
], ],
], })
})}}>编辑</Button> }}>编辑</Button>
</div>) </div>)
} else { } else {
// todo 异常处理 // todo 异常处理
@ -85,7 +96,7 @@ const DetailForm= () => {
console.log({res, parentMessageVOList}); console.log({res, parentMessageVOList});
setPName(parentMessageVOList[parentMessageVOList.length - 1].name); setPName(parentMessageVOList[parentMessageVOList.length - 1].name);
setPidArray(parentMessageVOList.map(parent => parent.id)) setPidArray(parentMessageVOList.map(parent => parent.id))
form.setFieldValue("pidArray",parentMessageVOList.map(parent => parent.id)) form.setFieldValue("pidArray", parentMessageVOList.map(parent => parent.id))
}) })
} }
@ -218,33 +229,52 @@ const DetailForm= () => {
showCount showCount
/> />
</Form.Item> </Form.Item>
<Form.Item name='state' initialValue='8' label='任务状态' rules={[{required: true, message: '任务状态不能为空'}]} disabled={updateFiledDisabled}> <Form.Item name='state' initialValue='8' label='任务状态'
rules={[{required: true, message: '任务状态不能为空'}]} disabled={updateFiledDisabled}>
<Radio.Group> <Radio.Group>
<Space direction='vertical'> <Space direction='vertical'>
<Radio value='8'><Tag color='primary'>未开始</Tag></Radio> {/*<Radio value='8'><Tag color='primary'>未开始</Tag></Radio>*/}
<Radio value='9'><Tag color='warning'>进行中</Tag></Radio> {/*<Radio value='9'><Tag color='warning'>进行中</Tag></Radio>*/}
<Radio value='7'><Tag color='success'>已完成</Tag></Radio> {/*<Radio value='7'><Tag color='success'>已完成</Tag></Radio>*/}
<Radio value='10'><Tag color='danger'>已逾期</Tag></Radio> {/*<Radio value='10'><Tag color='danger'>已逾期</Tag></Radio>*/}
{
stateList.map(stateDict =>
<Radio key={stateDict.itemCode} value={stateDict.itemCode}>
<Tag key={stateDict.itemCode}
color={stateDict.jsonValue?.color}>{stateDict.itemName}</Tag>
</Radio>
)
}
</Space> </Space>
</Radio.Group> </Radio.Group>
</Form.Item> </Form.Item>
<Form.Item name='priority' label='任务优先级' initialValue='2' rules={[{required: true, message: '任务优先级不能为空'}]} disabled={updateFiledDisabled}> <Form.Item name='priority' label='任务优先级' initialValue='2'
rules={[{required: true, message: '任务优先级不能为空'}]} disabled={updateFiledDisabled}>
<Radio.Group> <Radio.Group>
<Space direction='vertical'> <Space direction='vertical'>
<Radio value='3'><Tag color='danger'>紧急重要</Tag></Radio> {/*<Radio value='3'><Tag color='danger'>紧急重要</Tag></Radio>*/}
<Radio value='2'><Tag color='warning'>不紧急重要</Tag></Radio> {/*<Radio value='2'><Tag color='warning'>不紧急重要</Tag></Radio>*/}
<Radio value='1'><Tag>紧急不重要</Tag></Radio> {/*<Radio value='1'><Tag>紧急不重要</Tag></Radio>*/}
<Radio value='0'><Tag color='success'>不紧急不重要</Tag></Radio> {/*<Radio value='0'><Tag color='success'>不紧急不重要</Tag></Radio>*/}
{
priorityList.map(stateDict =>
<Radio key={stateDict.itemCode} value={stateDict.itemCode}>
<Tag key={stateDict.itemCode}
color={stateDict.jsonValue?.color}>{stateDict.itemName}</Tag>
</Radio>
)
}
</Space> </Space>
</Radio.Group> </Radio.Group>
</Form.Item> </Form.Item>
<DataPickItemPopup disabled={updateFiledDisabled} fieldName={"expectedStartTime"} <DataPickItemPopup disabled={updateFiledDisabled} fieldName={"expectedStartTime"}
labelName={"预计开始时间"}/> labelName={"预计开始时间"}/>
<DataPickItemPopup disabled={updateFiledDisabled} fieldName={"expectedEndTime"} <DataPickItemPopup disabled={updateFiledDisabled} fieldName={"expectedEndTime"}
labelName={"预计结束时间"}/> labelName={"预计结束时间"}/>
<DataPickItemPopup disabled={updateFiledDisabled} fieldName={"actualStartTime"} <DataPickItemPopup disabled={updateFiledDisabled} fieldName={"actualStartTime"}
labelName={"实际开始时间"}/> labelName={"实际开始时间"}/>
<DataPickItemPopup disabled={updateFiledDisabled} fieldName={"actualEndTime"} labelName={"实际结束时间"}/> <DataPickItemPopup disabled={updateFiledDisabled} fieldName={"actualEndTime"}
labelName={"实际结束时间"}/>
</Form> </Form>
</> </>
) )

View File

@ -0,0 +1,3 @@
.adm-cascader-header-title{
flex: 0.5;
}

View File

@ -1,18 +1,27 @@
import {Cascader, Input, Toast} from "antd-mobile"; import {Cascader, Input, SearchBar, Toast} from "antd-mobile";
import React, {useEffect, useMemo, useState} from "react"; import React, {useEffect, useMemo, useState} from "react";
import { import {
Form, Form,
} from 'antd-mobile' } from 'antd-mobile'
import {getTaskByPid} from "../../utils"; import {getTaskByPid} from "../../utils";
import "./index.css"
import {CloseCircleFill} from "antd-mobile-icons";
const ParentTask = (props) => { const ParentTask = (props) => {
const [valueToOptions, setValueToOptions] = useState([]) const [valueToOptions, setValueToOptions] = useState([])
const {form, disabled, pName, pidArray, addEditPName} = props; const {form, disabled, pName, pidArray, addEditPName} = props;
const [parentValue, setParentValue] = useState(pidArray ?? []) const [parentValue, setParentValue] = useState(pidArray ?? [])
const [visible, setVisible] = useState(false) const [visible, setVisible] = useState(false)
const [searchValue, setSearchValue] = useState("");
const [currentLab, setCurrentLab] = useState(0);
const [selectValue, setSelectValue] = useState([]);
// 当前标签
// let currentLab=0;
// let selectValue=[];
const options = useMemo(() => { const options = useMemo(() => {
console.log("useMemo")
function generate(v) { function generate(v) {
const options = valueToOptions[v] const options = valueToOptions[v]
if (options === null) { if (options === null) {
@ -21,6 +30,27 @@ const ParentTask = (props) => {
if (options === undefined) { if (options === undefined) {
return Cascader.optionSkeleton return Cascader.optionSkeleton
} }
// 如果有搜索有值,需要过滤,
// 根据currentLab查看需要过滤第几层当前层可能没有值就会停留在上一层
if (searchValue && searchValue.length > 0) {
if (selectValue.length <= currentLab + 1) {
// 可能展示最新的,或没有停留在当前页面,正常是没有选择的时候搜索
console.log({searchValue}, {currentLab}, {selectValue})
if (currentLab === 0 && v === '0') {
return options.filter(option => option.label.includes(searchValue)).map(option => ({
...option,
children: generate(option.value),
}))
} else if (v === selectValue[currentLab - 1]) {
return options.filter(option => option.label.includes(searchValue)).map(option => ({
...option,
children: generate(option.value),
}))
}
} else {
// 用户切换回之前的标签,暂不处理
}
}
return options.map(option => ({ return options.map(option => ({
...option, ...option,
children: generate(option.value), children: generate(option.value),
@ -28,7 +58,7 @@ const ParentTask = (props) => {
} }
return generate('0') ?? [] return generate('0') ?? []
}, [valueToOptions]) }, [valueToOptions, searchValue])
async function fetchOptionsForValue(v, level) { async function fetchOptionsForValue(v, level) {
if (v in valueToOptions) return if (v in valueToOptions) return
@ -72,13 +102,38 @@ const ParentTask = (props) => {
return <Form.Item return <Form.Item
name='pidArray' name='pidArray'
label='主线任务' label='主线任务'
value={parentValue}
disabled={disabled}
arrow={
(form.getFieldValue('pidArray') && form.getFieldValue('pidArray').length > 0) ? (
<CloseCircleFill
style={{
color: 'var(--adm-color-light)',
fontSize: 14,
}}
onClick={e => {
form.setFieldsValue({pidArray: []})
setParentValue([])
// 阻止冒泡
e.stopPropagation()
// 阻止所有后续事件处理程序
// e.nativeEvent.stopImmediatePropagation();
}}
/>) : true
}
onClick={() => { onClick={() => {
setVisible(true) setVisible(true)
}} }}
value={parentValue}
disabled={disabled}
> >
<Cascader <Cascader
title={<SearchBar placeholder='搜索当前层相关标题' onChange={
// 获取当前选卡,过滤当前选项
(value) => {
console.log("搜索" + value)
setSearchValue(value);
}
}/>}
options={options} options={options}
visible={visible} visible={visible}
defaultValue={pidArray} defaultValue={pidArray}
@ -91,11 +146,13 @@ const ParentTask = (props) => {
setParentValue(val[val.length - 1]) setParentValue(val[val.length - 1])
form.setFieldValue('pidArray', val) form.setFieldValue('pidArray', val)
}} }}
// onSelect={(val, extend) => { onTabsChange={index => {
// console.log('onSelect', val, extend.items) console.log(index);
// }} setCurrentLab(index)
}}
onSelect={value => { onSelect={value => {
console.log("value", value) console.log("value", value, currentLab)
setSelectValue(value)
value.forEach((v, index) => { value.forEach((v, index) => {
fetchOptionsForValue(v, index + 1) fetchOptionsForValue(v, index + 1)
}) })
@ -103,10 +160,11 @@ const ParentTask = (props) => {
> >
{items => { {items => {
if (items.every(item => item === null)) { if (items.every(item => item === null)) {
return pName ? (<span>{pName}</span>) : disabled ? return (pName && form.getFieldValue('pidArray') && form.getFieldValue('pidArray').length > 0) ? (
<span>{pName}</span>) : disabled ?
(<span>主线任务选填</span>) : (<span>主线任务选填</span>) :
(<span style={{color: "#cccccc"}}>主线任务选填</span>) (<span style={{color: "#cccccc"}}>主线任务选填</span>)
} else { } else if (items) {
if (addEditPName) { if (addEditPName) {
addEditPName(items[items.length - 1].label) addEditPName(items[items.length - 1].label)
} }

View File

@ -26,7 +26,7 @@ export function DetailLogTask() {
const [currentTask, setCurrentTask] = useState({}); const [currentTask, setCurrentTask] = useState({});
const [actions,setActions] = useState([ const [actions,setActions] = useState([
{ text: '复制', key: 'copy' }, { text: '复制', key: 'copy' },
{ text: '有效', key: 'edit' }, { text: '有效', key: 'need' },
{ text: '创建任务', key: 'addTask'}, { text: '创建任务', key: 'addTask'},
{ {
text: '删除', text: '删除',
@ -48,7 +48,7 @@ export function DetailLogTask() {
map.get(dayjs(taskLog.createdDate).format("YYYY-MM-DD"))?.push(taskLog); map.get(dayjs(taskLog.createdDate).format("YYYY-MM-DD"))?.push(taskLog);
return map; return map;
}, new Map()); }, new Map());
}, [taskLogList]) }, [taskLogList,currentShow])
const handleSend = () => { const handleSend = () => {
addTaskLog({ addTaskLog({
"description": sendValue, "description": sendValue,
@ -110,7 +110,7 @@ export function DetailLogTask() {
{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" onClick={()=>{ <span className="detail-line" onClick={()=>{
actions[1]={ text: '失效', key: 'edit' } actions[1]={ text: '失效', key: 'noneed' }
// setActions([...actions]) // setActions([...actions])
setCurrentTask(taskLog) setCurrentTask(taskLog)
setActionSheetVisible(true) setActionSheetVisible(true)

View File

@ -1,4 +1,4 @@
import {Calendar, Cascader, Tag} from "antd-mobile"; import {Calendar, Cascader, SwipeAction, Tag} from "antd-mobile";
import dayjs from "dayjs"; import dayjs from "dayjs";
import {TaskCount} from "../TaskCount"; import {TaskCount} from "../TaskCount";
import React, {Fragment, useEffect, useLayoutEffect, useMemo, useRef, useState} from "react"; import React, {Fragment, useEffect, useLayoutEffect, useMemo, useRef, useState} from "react";
@ -12,6 +12,7 @@ import './index.css'
const ToDoCal = (props) => { const ToDoCal = (props) => {
const today = new Date() const today = new Date()
const calRef = useRef(null); const calRef = useRef(null);
const refSwip = useRef(null);
const [currentDay, setCurrentDay] = React.useState(new Date()) const [currentDay, setCurrentDay] = React.useState(new Date())
const [currentMonth, setCurrentMonth] = React.useState(dayjs().set("date", 1).format('YYYY-MM-DD')); const [currentMonth, setCurrentMonth] = React.useState(dayjs().set("date", 1).format('YYYY-MM-DD'));
// Map keyvalue // Map keyvalue
@ -57,70 +58,101 @@ const ToDoCal = (props) => {
return ( return (
<Fragment> <Fragment>
<Calendar <SwipeAction
ref={calRef} ref={refSwip}
selectionMode='single' rightActions={[
// renderLabel={date => { {
// // key: 'next',
// return listTaskMap?.get(currentMonth)?.filter(taskC => dayjs(taskC.todoDay).isSame(dayjs(date), 'date'))?.map(taskC => { text: '自送进入下一个月',
// // color: 'success',
// // Object.keys() },
// // Object.entries() ]}
// if (Object.keys(taskC.state).length > 0) { leftActions={[
// console.log("taskC.state", taskC.state) {
// if (taskC.state[OVERDUE]) { key: 'last',
// return <FrownFill color='var(--adm-color-danger)'/>; text: '自送进入上一个月',
// } color: 'success',
// // warn },
// if (taskC.state[NEW] || taskC.state[UNDER_WAY]) { ]}
// return <SmileFill color='var(--adm-color-warning)'/>; onActionsReveal={(sideType) => {
// } console.log(sideType,sideType === 'left',sideType === 'right',currentMonth)
// // 绿 if (sideType === 'left') {
// return <SmileFill color='var(--adm-color-success)'/>; const newMonth = dayjs(currentMonth).subtract(1, 'months')
// } calRef.current.jumpTo({year: newMonth.year(), month: newMonth.month()+1})
// }) console.log(newMonth.year(),newMonth.month()+1)
// } else if (sideType === 'right') {
// // if (dayjs(date).isSame(today, 'day')) return '' const newMonth = dayjs(currentMonth).add(1, 'months')
// // if (date.getDay() === 0 || date.getDay() === 6) { calRef.current.jumpTo({year: newMonth.year(), month: newMonth.month()+1})
// // return '' console.log(newMonth.year(),newMonth.month()+1)
// // } }
// }} refSwip.current?.close()
renderDate={date => { }}
return listTaskMap?.get(currentMonth)?.filter(taskC => dayjs(taskC.todoDay).isSame(dayjs(date), 'date'))?.map(taskC => { >
// <Calendar
// Object.keys() ref={calRef}
// Object.entries() selectionMode='single'
if (Object.keys(taskC.state).length > 0) { // renderLabel={date => {
console.log("taskC.state", taskC.state) // //
if (taskC.state[OVERDUE]) { // return listTaskMap?.get(currentMonth)?.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 listTaskMap?.get(currentMonth)?.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 <div className='cal-day-circle' style={{
borderColor: 'var(--adm-color-danger)',
}}><span>{dayjs(date).date()}</span></div>;
}
// warn
if (taskC.state[NEW] || taskC.state[UNDER_WAY]) {
return <div className='cal-day-circle' style={{
borderColor: 'var(--adm-color-warning)',
}}><span>{dayjs(date).date()}</span></div>;
}
// 绿
return <div className='cal-day-circle' style={{ return <div className='cal-day-circle' style={{
borderColor: 'var(--adm-color-danger)', borderColor: 'var(--adm-color-success)',
}}><span>{dayjs(date).date()}</span></div>; }}><span>{dayjs(date).date()}</span></div>;
} }
// warn return <span>{dayjs(date).date()}</span>;
if (taskC.state[NEW] || taskC.state[UNDER_WAY]) { })
return <div className='cal-day-circle' style={{ }}
borderColor: 'var(--adm-color-warning)', defaultValue={currentDay}
}}><span>{dayjs(date).date()}</span></div>; onChange={val => {
} setCurrentDay(val)
// 绿 }}
return <div className='cal-day-circle' style={{ onPageChange={(year, month) => {
borderColor: 'var(--adm-color-success)', console.log(year, month)
}}><span>{dayjs(date).date()}</span></div>; setCurrentMonth(`${year}-${month}-01`)
} }}
return <span>{dayjs(date).date()}</span>;
})
}}
defaultValue={currentDay}
onChange={val => {
setCurrentDay(val)
}}
onPageChange={(year, month) => {
console.log(year, month)
setCurrentMonth(`${year}-${month}-01`)
}}
/> />
</SwipeAction>
<TaskCount currentDay={currentDay} taskCount={listTaskMap?.get(currentMonth)} today={today} <TaskCount currentDay={currentDay} taskCount={listTaskMap?.get(currentMonth)} today={today}
currentMonth={currentMonth} currentMonth={currentMonth}
backToToday={backToToday}/> backToToday={backToToday}/>