Compare commits

...

2 Commits

Author SHA1 Message Date
1708-huayu b90b63853f feat:排序处理,添加计划名称前缀组件 2025-07-31 19:18:20 +08:00
1708-huayu 88777b39be feat:取消pc链接分享 2025-07-30 18:43:37 +08:00
21 changed files with 526 additions and 119 deletions

View File

@ -1,17 +1,22 @@
'use client'
import { useRouter} from "next/navigation";
import {usePathname,useSearchParams, useRouter} from "next/navigation";
import dayjs from "dayjs";
import {useEffect} from "react";
import { isMobile } from 'react-device-detect';
export default function Home() {
const {replace} = useRouter();
useEffect(()=>{
const pathName = usePathname()
const searchParams = useSearchParams()
console.log({pathName},{searchParams})
if(localStorage.getItem('platform-security')){
if (isMobile){
replace('/mobile/')
}else {
replace("/task/project")
if (!pathName){
var callBack = searchParams.get("callBack");
if (callBack){
replace(decodeURI(callBack))
}else {
replace("/task/project")
}
}
}else {
replace("/login")

View File

@ -62,6 +62,7 @@ const DiaryOption = (props: SelectDiary) => {
const [diaryList, setDiaryList] = useState<ListDiary[]>([]);
const [diaryReduceList, setDiaryReduceList] = useState<ListDiary[]>([])
const [page, setPage] = useState(1);
const [selectLoading,setSelectLoading]=useState<boolean>(false);
const noMore = {
id: '0',
keyId: 'o0',
@ -99,6 +100,7 @@ const DiaryOption = (props: SelectDiary) => {
if (!open) {
return
}
setSendValueFlag(true)
const fakeDataUrl = process.env.NEXT_PUBLIC_TODO_REQUEST_URL + `/task/message/diary/select`;
fetch(fakeDataUrl, {
method: 'POST', headers: {
@ -121,6 +123,7 @@ const DiaryOption = (props: SelectDiary) => {
listRef.current.scrollTo({top: 9999999});
}
}
setSendValueFlag(false)
});
};
@ -134,9 +137,9 @@ const DiaryOption = (props: SelectDiary) => {
diaryList.filter(taskLog => {
if (currentIndex === 0) {
return true
} else if (currentIndex === 1 && taskLog.enableFlag === "1") {
} else if (currentIndex === 1 && taskLog.enableFlag !== "0") {
return true
} else if (currentIndex === 2 && taskLog.enableFlag === "0") {
} else if (currentIndex === 2 && taskLog.enableFlag !== "1") {
return true
} else return false;
}).reduce((map, taskLog) => {
@ -288,7 +291,6 @@ const DiaryOption = (props: SelectDiary) => {
},
})
// 点击操作 end
return (
<Fragment>
<Button type={open?"primary":"default"} onClick={() => setOpen(!open)}>
@ -366,7 +368,7 @@ const DiaryOption = (props: SelectDiary) => {
<Button style={{flexGrow: 1}} onClick={() => setCurrentIndex(2)}
type={currentIndex == 2 ? "primary" : "default"}></Button>
</div>
<List>
<List loading={sendValueFlag}>
<VirtualList
data={diaryReduceList}
height={containerHeight}

View File

@ -1,7 +1,8 @@
import React, {Fragment, useEffect, useState} from 'react';
import {Button, Image, Input, Modal, QRCode, Space } from 'antd';
import {Button, Image, Input, message, Modal, QRCode, Space} from 'antd';
import {v4 as uuidv4} from 'uuid';
import {copyToClipboard} from "@/lib/copyToClipboard";
import {addTaskPassAPI} from "@/components/service/Share";
const ShareOption = (props: { taskId: string }) => {
// const [loading, setLoading] = useState(false);
@ -9,7 +10,7 @@ const ShareOption = (props: { taskId: string }) => {
const [buttonIndex, setButtonIndex] = useState(1);
const [qrCodeValue, setQrCodeValue] = useState<string>("-");
const [qrCodeStatus, setQrCodeStatus] = useState<'active' | 'expired' | 'loading' | 'scanned'>("loading");
const [joinId,setJoinId] = useState("");
function doDownload(url: string, fileName: string) {
const a = document.createElement('a');
a.download = fileName;
@ -37,7 +38,22 @@ const ShareOption = (props: { taskId: string }) => {
};
const generateQrcode = () => {
const clientId: string = uuidv4().substring(0,32);
// 分享人必须有权限
// 生成分享信息同时适用链接和二维码
addTaskPassAPI({taskId:props.taskId,pass:clientId,joinCheck:'1'}).then(res=>{
if (res.data.status.success){
let qrCodeData = {
taskId: props.taskId, pass: clientId,
passId: res.data.data.id,
local: "马上行计划管理",
opType: "SHARE_OPTION"
}
setJoinId(res.data.data.id!)
setQrCodeValue(JSON.stringify(qrCodeData))
setQrCodeStatus("active")
}
});
}
const showModal = () => {
setOpen(true);
@ -47,14 +63,7 @@ const ShareOption = (props: { taskId: string }) => {
setOpen(false);
};
useEffect(() => {
// 分享人必须有权限
// 生成分享信息同时适用链接和二维码
const clientId: string = uuidv4();
let qrCodeData = {
taskId: props.taskId, pass: clientId, local: "马上行计划管理", opType: "SHARE_OPTION"
}
setQrCodeValue(JSON.stringify(qrCodeData))
setQrCodeStatus("active")
generateQrcode()
}, []);
return (
@ -72,22 +81,22 @@ const ShareOption = (props: { taskId: string }) => {
<Button key="back" onClick={handleCancel}>
</Button>,
<Button key="qrcode"
type={buttonIndex == 1 ? "primary" : "default"}
// loading={loading}
onClick={() => setButtonIndex(1)}>
使
</Button>,
<Button
key="link"
// href="https://google.com"
// target="_blank"
type={buttonIndex == 2 ? "primary" : "default"}
// loading={loading}
onClick={() => setButtonIndex(2)}
>
使
</Button>,
// <Button key="qrcode"
// type={buttonIndex == 1 ? "primary" : "default"}
// // loading={loading}
// onClick={() => setButtonIndex(1)}>
// 使用二维码
// </Button>,
// <Button
// key="link"
// // href="https://google.com"
// // target="_blank"
// type={buttonIndex == 2 ? "primary" : "default"}
// // loading={loading}
// onClick={() => setButtonIndex(2)}
// >
// 使用连接
// </Button>,
]}
>
{buttonIndex == 1 && <div className="displayFlexRow">
@ -109,7 +118,8 @@ const ShareOption = (props: { taskId: string }) => {
<Space.Compact style={{width: '100%'}}>
<Input defaultValue="https://www.huaruyu.com" readOnly/>
<Button loading={qrCodeStatus=="loading"}
onClick={()=>copyToClipboard("https://www.huaruyu.com")}
onClick={()=>{copyToClipboard(`https://www.huaruyu.com/task/project?joinId=${joinId}`);
message.info("复制链接成功过")}}
type="primary"></Button>
</Space.Compact>
</div>}

View File

@ -1,15 +1,9 @@
import React, {CSSProperties, Fragment, useState} from "react";
import React, {CSSProperties, Fragment, useEffect, useState} from "react";
import {DragDropContext, Droppable, Draggable, DropResult, DraggingStyle, NotDraggingStyle} from "react-beautiful-dnd";
import {TaskStepSortVO} from "@/components/type/TaskSort.d";
import {Button, Drawer} from "antd";
// fake data generator
const getItems = (count: number, offset = 0) =>
Array.from({length: count}, (v, k) => k).map(k => ({
id: `item-${k + offset}-${new Date().getTime()}`,
stepDesc: `item ${k + offset}`,
sortIndex: k + offset,
}));
import {Button, Drawer, message, Modal} from "antd";
import TextArea from "antd/es/input/TextArea";
import {addStepItemAPI} from "@/components/service/StepSort";
const reorder = (list: TaskStepSortVO[], startIndex: number, endIndex: number) => {
const result = Array.from(list);
@ -23,25 +17,33 @@ const grid = 8;
const getItemStyle = (isDragging: boolean, draggableStyle: DraggingStyle | NotDraggingStyle | undefined): CSSProperties => ({
// some basic styles to make the items look a bit nicer
userSelect: "none",
padding: grid * 2,
// padding: grid,
margin: `0 0 ${grid}px 0`,
// change background colour if dragging
background: isDragging ? "lightgreen" : "grey",
// styles we need to apply on draggables
background: isDragging ? "lightgreen" : "white",
...draggableStyle
});
const getListStyle = (isDraggingOver: boolean) => ({
background: isDraggingOver ? "lightblue" : "lightgrey",
padding: grid,
width: 250
background: isDraggingOver ? "lightblue" : "white",
width: "100%"
});
const StepSort = (props: { taskId: string }) => {
const [state, setState] = useState<TaskStepSortVO[]>(getItems(5, 10));
const StepSort = (props: { taskId: string, stepList: TaskStepSortVO[] }) => {
const [state, setState] = useState<TaskStepSortVO[]>(props.stepList);
// 抽屉 start
const [open, setOpen] = useState(false);
const [dialogueOpen, setDialogueOpen] = useState(false);
const [sortItemList, setSortItemList] = useState<TaskStepSortVO[]>([]);
const [modalText, setModalText] = useState<TaskStepSortVO>({
id: "",
stepDesc: undefined,
sortIndex: undefined,
taskId: props.taskId,
});
useEffect(() => {
// 根据任务id查找步骤
}, []);
function onDragEnd(result: DropResult) {
const {source, destination} = result;
@ -53,14 +55,82 @@ const StepSort = (props: { taskId: string }) => {
let newState = [...state];
newState = items;
setState(newState);
props.stepList.length=0
props.stepList.push(...newState)
}
const [confirmButtonLoading, setConfirmButtonLoading] = useState(false);
const confirmModalTextArea = () => {
setConfirmButtonLoading(true)
addStepItemAPI(modalText).then(res => {
if (res.data.status.success) {
message.info("添加步骤成功")
setModalText(
{...modalText, stepDesc: undefined})
setState([...state, res.data.data])
props.stepList.length=0
props.stepList.push(...state, res.data.data)
setDialogueOpen(false);
} else {
message.error(res.data.status.message)
}
}).finally(()=>{
setConfirmButtonLoading(false)
})
}
return (
<Fragment>
<Modal
title={`步骤`}
closable={false}
open={dialogueOpen}
confirmLoading={confirmButtonLoading}
onCancel={
() => {
setDialogueOpen(false);
}
}
destroyOnClose={true}
onOk={confirmModalTextArea}
>
<TextArea autoSize={{minRows: 4, maxRows: 20}}
placeholder="最大长度255"
showCount
styles={{
count: {
// color:"red",
bottom: "0px"
}
}}
value={modalText.sortIndex}
maxLength={255} autoFocus
onChange={(e) => setModalText(
{...modalText, stepDesc: e.target.value})}
onKeyDown={event => {
if (event.ctrlKey && event.key === 'Enter') {
confirmModalTextArea();
}
}}
/>
</Modal>
<Button type={open ? "primary" : "default"} onClick={() => setOpen(!open)}>
</Button>
<Drawer>
<Drawer
style={{boxSizing: "border-box"}}
styles={{
// body: {padding: "0 24px"}
}}
mask={false}
maskClosable={false}
title="步骤列表"
closable={{'aria-label': 'Close Button'}}
onClose={() => setOpen(false)}
open={open}
footer={<Button type={"primary"} onClick={() => setDialogueOpen(true)}></Button>}
>
<div style={{display: "flex"}}>
<DragDropContext onDragEnd={onDragEnd}>
<Droppable key="sortDroppable" droppableId="sortDroppableId">
@ -86,13 +156,15 @@ const StepSort = (props: { taskId: string }) => {
provided.draggableProps.style
)}
>
<div>
{`步骤${index + 1}`}
</div>
<div
style={{
display: "flex",
justifyContent: "space-around"
}}
>
{item.stepDesc}
<div style={{whiteSpace: 'pre-line',border: "solid",
borderRadius: grid,}}>
{item.stepDesc}
</div>
</div>
</div>
)}

View File

@ -0,0 +1,24 @@
import {TaskMessage} from "@/lib/definitions";
import React, {Fragment} from "react";
const TaskNameAndIcon = (props: {task:TaskMessage}) => {
return (<Fragment>
{props.task.fId &&
<svg className="icon" aria-hidden="true">
<use xlinkHref="#icon-tuandui"></use>
</svg>
}
{props.task.taskType == '2' &&
<svg className="icon" aria-hidden="true">
<use xlinkHref="#icon-youxuliebiao"></use>
</svg>
}
{props.task.taskType == '3' &&
<svg className="icon" aria-hidden="true">
<use xlinkHref="#icon-dingshi"></use>
</svg>
}
<text>{props.task.name}</text>
</Fragment>)
}
export default TaskNameAndIcon;

View File

@ -0,0 +1,9 @@
import {AxiosResponse} from "axios";
import {ResponseVO} from "@/lib/definitions";
import {httpReq} from "@/utils/axiosReq";
import {ShareVO} from "@/components/type/Share.d";
export const addTaskPassAPI= (data:ShareVO):Promise<AxiosResponse<ResponseVO<ShareVO>>> =>{
return httpReq.post(process.env.NEXT_PUBLIC_TODO_REQUEST_URL + "/task/pass",
data)
}

View File

@ -0,0 +1,9 @@
import {AxiosResponse} from "axios";
import {ResponseVO} from "@/lib/definitions";
import {httpReq} from "@/utils/axiosReq";
import {TaskStepSortVO} from "@/components/type/TaskSort.d";
export const addStepItemAPI= (data:TaskStepSortVO):Promise<AxiosResponse<ResponseVO<TaskStepSortVO>>> =>{
return httpReq.post(process.env.NEXT_PUBLIC_TODO_REQUEST_URL + `/task/step/sort/item`,
data)
}

View File

@ -0,0 +1 @@
export interface ShareVO { taskId: string, pass: string, joinCheck: string ,id?:string}

View File

@ -1,5 +1,6 @@
export type TaskStepSortVO = {
id: string,
sortIndex: number,
stepDesc: string
sortIndex: number|undefined,
stepDesc: string|undefined,
taskId:string
}

View File

@ -1,5 +1,6 @@
import React from "react";
import {Dayjs} from "dayjs";
import {TaskStepSortVO} from "@/components/type/TaskSort.d";
export type Request<T>={
data:T,
@ -36,6 +37,7 @@ export type TaskMessage ={
fId?:string;
fName?:string;
taskType?:string;
stepList?:TaskStepSortVO[];
}
@ -51,6 +53,7 @@ export type DataType = TaskMessage&{
actualTimeRange?:(string|Dayjs|undefined)[]
children: DataType[]|undefined;
sortNo?:number;
}
export type DictType={
id:number;

View File

@ -52,7 +52,7 @@ export async function getTask(id: string): Promise<ResponseVO<DataType>> {
return response.data;
}
export function addTask(task: DataType): Promise<AxiosResponse<ResponseVO<TaskMessage>>> {
export function addTask(task: DataType): Promise<AxiosResponse<ResponseVO<DataType>>> {
noStore();
// 使用 Axios 发送 POST 请求添加数据
switch(task.taskType){
@ -68,12 +68,21 @@ export function addTask(task: DataType): Promise<AxiosResponse<ResponseVO<TaskMe
}
}
export async function updateTask(task: DataType): Promise<ResponseVO<string>> {
export async function updateTask(task: DataType): Promise<AxiosResponse<ResponseVO<TaskMessage>>> {
noStore();
// 使用 Axios 发送 PUT 请求修改数据
const response: AxiosResponse<ResponseVO<string>> = await httpReq.put(process.env.NEXT_PUBLIC_TODO_REQUEST_URL + '/task', task);
// 从响应中提取数据并返回
return response.data;
// 使用 Axios 发送 POST 请求添加数据
switch(task.taskType){
// 常规任务
case '0':return httpReq.post(process.env.NEXT_PUBLIC_TODO_REQUEST_URL + '/V2/task', task);
// 团队任务
case '1':return httpReq.post(process.env.NEXT_PUBLIC_TODO_REQUEST_URL + '/V2/task', task);
// 顺序
case '2':return httpReq.post(process.env.NEXT_PUBLIC_TODO_REQUEST_URL + '/task/step/sort', task);
// 周期
case '3':return httpReq.post(process.env.NEXT_PUBLIC_TODO_REQUEST_URL + '/task/schedule', task);
default:throw new Error("创建任务必选任务类型");
}
}
export async function deleteTask(id: string): Promise<ResponseVO<string>> {

View File

@ -0,0 +1,5 @@
export interface TaskPassVO {
taskId:string;
pass:string;
shareUser:string;
}

View File

@ -0,0 +1,23 @@
import {DataType, ResponseVO} from "@/lib/definitions";
import {unstable_noStore as noStore} from "next/dist/server/web/spec-extension/unstable-no-store";
import {AxiosResponse} from "axios";
import {httpReq} from "@/utils/axiosReq";
import {TaskPassVO} from "@/lib/task/project/team.d";
export async function getJoinMessageAPI(joinId: string): Promise<ResponseVO<TaskPassVO>> {
noStore();
// 使用 Axios 发送 PUT 请求获取数据
const response: AxiosResponse<ResponseVO<TaskPassVO>> = await httpReq.get(
process.env.NEXT_PUBLIC_TODO_REQUEST_URL + `/task/pass/${joinId}`);
// 从响应中提取数据并返回
return response.data;
}
export async function getTaskInSecurityAPI(requestParam: TaskPassVO): Promise<ResponseVO<DataType>> {
noStore();
// 使用 Axios 发送 PUT 请求获取数据
const response: AxiosResponse<ResponseVO<DataType>> = await httpReq.put(
process.env.NEXT_PUBLIC_TODO_REQUEST_URL + `/inSecurity/task/${requestParam.taskId}/${requestParam.pass}`);
// 从响应中提取数据并返回
return response.data;
}

View File

@ -24,7 +24,7 @@ html {
}
.icon {
/* em 当前元素的 font-size 值,如果元素没有显式设置 font-size则继承父元素的 font-size。 控制与字体大小相关的属性*/
width: 1em;
width: 2em;
height: 1em;
vertical-align: -0.15em;
fill: currentColor;

View File

@ -8,7 +8,7 @@ import React, {useLayoutEffect} from 'react';
import {useState} from 'react';
import {LoginObject} from "@/lib/login/definitions";
import {useRouter} from 'next/navigation'
import { v4 as uuidv4 } from 'uuid';
import {v4 as uuidv4} from 'uuid';
import {askLoginAPI, generateQrcodeAPI} from "@/lib/login/service";
import Cookies from "js-cookie";
@ -22,33 +22,36 @@ export default function XcxLoginPage() {
const [qrCodeStatus, setQrCodeStatus] = useState<'active' | 'expired' | 'loading' | 'scanned'>();
const [qrCodeValue, setQrCodeValue] = useState<string>("-");
const generateQrcode = () => {
if (qrCodeStatus=='loading') {
message.info({content:"请耐心等待"})
}else if (qrCodeStatus=='expired') {
if (qrCodeStatus == 'loading') {
message.info({content: "请耐心等待"})
} else if (qrCodeStatus == 'expired') {
setQrCode()
}else if (qrCodeStatus=='active') {
} else if (qrCodeStatus == 'active') {
}else if (qrCodeStatus=='scanned') {
} else if (qrCodeStatus == 'scanned') {
}else {
} else {
setQrCode()
}
}
function setQrCode(){
function setQrCode() {
// 生成唯一id
setQrCodeStatus("loading")
const clientId: string = uuidv4();
generateQrcodeAPI({clientId}).then(res=>{
let qrCodeData={
clientId,serverId:res.data.data,local:"马上行计划管理",
opType:"LOGIN_OPTION"
generateQrcodeAPI({clientId}).then(res => {
let qrCodeData = {
clientId, serverId: res.data.data, local: "马上行计划管理",
opType: "LOGIN_OPTION"
}
setQrCodeValue(JSON.stringify(qrCodeData))
setQrCodeStatus("active")
let timeout=undefined;
let timeout: ReturnType<typeof setTimeout> | null = null;
// 设置定时器每2000毫秒2秒执行一次myFunction
let timerId =undefined;
timerId = setInterval(askLogin,2000,timeout,timerId,qrCodeData);
let timerId: ReturnType<typeof setInterval> | null = null;
timerId = setInterval(() => {
askLogin(timeout, timerId, qrCodeData);
}, 2000);
// 如果需要停止定时器,可以调用 clearInterval(timerId);
// 例如1分钟后停止定时器
timeout = setTimeout(() => {
@ -56,16 +59,21 @@ export default function XcxLoginPage() {
clearInterval(timerId);
console.log("定时器已停止");
}, 60000);
}).catch(()=>{
}).catch(() => {
setQrCodeStatus("expired")
})
}
function askLogin(timeout:any,timerId:any,qrCodeData:{}){
askLoginAPI(qrCodeData).then(res=>{
if (res.data.status.success&&res.data.data.length>0){
function askLogin(timeout: ReturnType<typeof setTimeout> | null, timerId: ReturnType<typeof setInterval> | null,
qrCodeData: {}) {
askLoginAPI(qrCodeData).then(res => {
if (res.data.status.success && res.data.data.length > 0) {
setQrCodeStatus("scanned")
if(timeout){
console.log({timerId}, {timeout})
if (timeout) {
clearTimeout(timeout);
}
if (timerId) {
clearInterval(timerId);
}
// localStorage.removeItem("platform-security")
@ -73,12 +81,28 @@ export default function XcxLoginPage() {
// 删除名为 'platform-security' 的Cookie
// Cookies.remove('platform-security');
// 设置一个有效期为7天的Cookie
Cookies.set('platform-security', res.data.data[0].token, { expires: 7 });
router.push('/task/project')
Cookies.set('platform-security', res.data.data[0].token, {expires: 7});
// 获取路径名 (pathname) - 这通常是你所说的“请求路径”
console.log(window.location.pathname); // 例如: "/path/to/page"
if (window.location.pathname&&window.location.pathname.indexOf("/login")==-1) {
return;
}
// 获取查询字符串(例如: "?name=John&age=30"
const queryString = window.location.search;
// 创建 URLSearchParams 对象
const urlParams = new URLSearchParams(queryString);
let callBack = urlParams.get("callBack")
if (callBack) {
console.log({queryString}, decodeURIComponent(callBack))
router.push(decodeURIComponent(callBack))
} else {
router.push('/task/project')
}
}
})
}
// 二维码 end
const {token} = theme.useToken();
const router = useRouter()
@ -97,7 +121,7 @@ export default function XcxLoginPage() {
{loaded ? <LoginFormPage
backgroundImageUrl="/20-1733751222585.jpg"
title="马上行计划管理"
subTitle={qrCodeShow?undefined:"使用微信扫码打开小程序"}
subTitle={qrCodeShow ? undefined : "使用微信扫码打开小程序"}
containerStyle={{
backgroundColor: 'rgba(0, 0, 0,0.65)',
backdropFilter: 'blur(4px)',
@ -110,11 +134,17 @@ export default function XcxLoginPage() {
}
setQrCodeShow(!qrCodeShow)
}}
submitter={{ searchConfig: { submitText: qrCodeShow?"在我的-PC扫码登录或者返回":"已打开微信小程序,生成登录码。",resetText: '重置2'}}}
submitter={{
searchConfig: {
submitText: qrCodeShow ? "在我的-PC扫码登录或者返回" : "已打开微信小程序,生成登录码。",
resetText: '重置2'
}
}}
>
<div className="displayFlexColumn" style={{margin:'20px'}}>
<div className="displayFlexColumn" style={{margin: '20px'}}>
{
qrCodeShow?<QRCode value={qrCodeValue} size={300} status={qrCodeStatus} onRefresh={generateQrcode} />:
qrCodeShow ?
<QRCode value={qrCodeValue} size={300} status={qrCodeStatus} onRefresh={generateQrcode}/> :
<Image width={300} src="/static/pc-Web.png"/>
}
</div>

View File

@ -1,5 +1,5 @@
'use client'
import React, {useCallback, useContext, useEffect, useMemo, useRef, useState} from "react";
import React, {Fragment, useCallback, useContext, useEffect, useMemo, useRef, useState} from "react";
import {Calendar, dayjsLocalizer, Event, SlotInfo, View} from 'react-big-calendar'
import dayjs, {Dayjs} from 'dayjs'
import 'react-big-calendar/lib/css/react-big-calendar.css'
@ -14,6 +14,8 @@ import LocalContext from "@/ui/LocalContent";
import withDragAndDrop, {EventInteractionArgs} from "react-big-calendar/lib/addons/dragAndDrop";
import {TaskEvent} from "@/lib/task/calendar/data";
import {editExpectAPI} from "@/lib/task/calendar/service";
import TaskNamePrefixIcon from "@/components/TaskNameAndIcon";
import TaskNameAndIcon from "@/components/TaskNameAndIcon";
/**
* https://github.com/jquense/react-big-calendar?tab=readme-ov-file
@ -116,7 +118,7 @@ const CalShow: React.FC = () => {
return {
start: dayjs(taskState.expectedStartTime).toDate(),
end: dayjs(taskState.expectedEndTime).toDate(),
title: taskState.name,
title: <TaskNameAndIcon task={taskState}/>,
resource: taskState.id,
id: taskState.id,
state: taskState.state,
@ -271,7 +273,7 @@ const CalShow: React.FC = () => {
haveButton={false}
itemId={itemId}
reloadData={reloadData} expectedStartTime={expectedStartTime}
closeOpen={()=>setOpen(false)}
closeOpen={() => setOpen(false)}
expectedEndTime={expectedEndTime}/>}
<DragAndDropCalendar
// 本地设置

View File

@ -132,6 +132,7 @@ export const DetailModelForm: React.FC<DetailModelFormProps> = (props) => {
autoFocusFirstInput
modalProps={{
destroyOnClose: true,
maskClosable:false,
onCancel: () => {
props.reloadData?.();
},
@ -184,12 +185,12 @@ export const DetailModelForm: React.FC<DetailModelFormProps> = (props) => {
if (editFormDisable && taskType == '1') {
result.push(<ShareOption taskId={props.itemId!}/>)
}
if (editFormDisable && taskType == '2') {
result.push(<StepSort taskId={props.itemId!}/>)
}
result.push(<Button type="primary" key="close"
onClick={() => props.closeOpen?.()}></Button>)
}
if (taskType == '2') {
result.push(<StepSort taskId={props.itemId!} stepList={requestTask?.stepList||[]}/>)
}
return result;
},
} : {
@ -204,7 +205,10 @@ export const DetailModelForm: React.FC<DetailModelFormProps> = (props) => {
// }
// return defaultDoms;
const result = defaultDoms.filter(defaultButton => defaultButton.key == 'rest');
result.push(<Button type="primary" key="create-team"
// if (taskType == '2') {
// result.push(<StepSort taskId={props.itemId!} stepList={requestTask?.stepList||[]}/>)
// }
result.push(<Button type="primary" key="create-team" loading={operationRequest}
onClick={() => form.submit()}>{taskType == '1' ? "创建团队" : "确认"}
</Button>)
return result;
@ -249,16 +253,15 @@ export const DetailModelForm: React.FC<DetailModelFormProps> = (props) => {
if (props.operationId === OPERATION_BUTTON_TYPE.UPDATE || (props.operationId === OPERATION_BUTTON_TYPE.DETAIL && !editFormDisable)) {
await updateTask(values).then(response => {
console.log('response', response)
if (response.status.success) {
message.success("修改任务成功:" + response.data)
if (response.data.status.success) {
message.success("修改任务成功:" + response.data.data.name)
// 树任务重新刷新
// 四象限任务重新刷新
// 如果可以直接更新列表而不请求。。。。。。
console.log('props.reloadData?.()', props.reloadData)
props.reloadData?.()
result = true
} else {
message.error(response.status.message)
message.error(response.data.status.message)
result = false
}
}
@ -267,6 +270,8 @@ export const DetailModelForm: React.FC<DetailModelFormProps> = (props) => {
await addTask(values).then(response => {
console.log('response', response)
if (response.data.status.success) {
setRequestTask(response.data.data)
message.success(`添加计划${response.data.data.name}成功`)
// 树任务重新刷新
// 四象限任务重新刷新

View File

@ -0,0 +1,194 @@
import {
ModalForm,
ProForm,
ProFormDateTimeRangePicker,
ProFormSelect,
ProFormText, ProFormTextArea,
} from '@ant-design/pro-components';
import {Button, Form, message, Spin} from 'antd';
import React, {Fragment, useEffect, useState} from "react";
import {
TASK_TYPE,
taskPriorityList,
taskStateList
} from "@/lib/task/project/data";
import {DataType} from "@/lib/definitions";
import dayjs, {Dayjs} from "dayjs";
import {getJoinMessageAPI, getTaskInSecurityAPI} from "@/lib/task/project/team";
export type JoinTeamPropsVO = {
joinId: string, reloadData: () => void, open: boolean,
closeOpen: () => void
}
export const JoinTeam = (props: JoinTeamPropsVO) => {
console.log("JoinTeam:props:", props)
const [form] = Form.useForm<DataType>();
const [requestTask, setRequestTask] = useState<DataType>()
const [spinning, setSpinning] = useState(true)
const [title, setTitle] = useState("");
useEffect(() => {
getJoinMessageAPI(props.joinId).then(teamRes => {
if (!teamRes.status.success) {
return
}
setTitle(`来自的${teamRes.data.shareUser}协作邀请`)
getTaskInSecurityAPI(teamRes.data).then(task => {
if (task.status.success) {
setRequestTask(task.data)
task.data.state = taskStateList.find(taskState => taskState.code === task.data.state?.toString())!.name;
task.data.priority = taskPriorityList.find(taskPriority => taskPriority.code === task.data.priority?.toString())!.name;
task.data.actualTimeRange = [task.data.actualStartTime ? dayjs(task.data.actualStartTime) : undefined,
task.data.actualEndTime ? dayjs(task.data.actualEndTime) : undefined];
task.data.expectedTimeRange = [task.data.expectedStartTime ? dayjs(task.data.expectedStartTime) : undefined,
task.data.expectedEndTime ? dayjs(task.data.expectedEndTime) : undefined];
form.setFieldsValue(task.data)
} else {
message.error(task.status.message);
props.reloadData?.()
}
}).finally(() => {
setSpinning(false)
}
)
})
}, [])
return (
<Fragment>
<Spin spinning={spinning} fullscreen/>
<ModalForm<DataType>
title={title}
open={!spinning && props.open}
form={form}
autoFocusFirstInput
modalProps={{
destroyOnClose: true,
onCancel: () => {
props.reloadData?.();
},
}}
readonly={true}
submitter={{
render: (prop, defaultDoms) => {
console.log("submitter render: ", {prop})
let result = [
<Button type="primary" key="close"
onClick={() => props.closeOpen?.()}></Button>,
<Button type="primary" key="close"
onClick={() => props.closeOpen?.()}></Button>,
<Button type="primary" key="close"
onClick={() => props.closeOpen?.()}></Button>,
]
return result;
},
}}
onFinish={async (values) => {
}}
>
<ProFormText width="sm" name="id" hidden={true} label="主键"/>
<ProFormText width="sm" name="code" hidden={true} label="任务编码"/>
<ProFormText width="sm" name="pPid" hidden={true} label="祖宗id"/>
<ProForm.Group>
<ProFormSelect
required={true}
options={TASK_TYPE}
width="sm"
name="taskType"
label="任务类型"
rules={[
{
required: true,
message: "请输入计划类型"
}
]}
/>
<ProFormText
required={true}
width="sm"
name="fName"
label="团队名称"
tooltip="最长为 10 位"
placeholder="请输入团队名称"
/>
<ProFormText
required={true}
width="sm"
name="name"
label="任务名称"
tooltip="最长为 10 位"
placeholder="请输入任务名称"
rules={[
{
required: true,
message: "请输入计划名称"
}, {
max: 10,
message: "名称长度不易超过10个字"
}
]}
/>
</ProForm.Group>
<ProFormTextArea
width="xl"
name="description"
label="任务描述"
tooltip="最长255个字"
placeholder="请输入任务描述"
/>
<ProForm.Group>
<ProFormSelect
request={async () =>
taskPriorityList.map
(
taskState => {
return {'label': taskState.name, 'value': taskState.code}
}
)
}
width="sm"
name="priority"
label="任务优先级"
initialValue='3'
rules={[{required: true, message: "请选择计划优先级"}]}
/>
<ProFormSelect
width="sm"
options={taskStateList.map(taskState => {
return {'label': taskState.name, 'value': taskState.code}
})}
name="state"
label="任务状态"
initialValue='8'
rules={
[
{
required: true,
message: "请选择计划状态"
}
]
}
/>
</ProForm.Group>
<ProForm.Group>
<ProFormDateTimeRangePicker
initialValue={[dayjs(), undefined]}
name="expectedTimeRange"
label="期望时间"
fieldProps={{allowEmpty: [true, true], showTime: true, needConfirm: true}}
placeholder={['开始时间', '结束时间']}
/>
<ProFormDateTimeRangePicker
name="actualTimeRange"
label="实际时间"
fieldProps={{allowEmpty: [true, true], showTime: true, needConfirm: true}}
placeholder={['开始时间', '结束时间']}
/>
</ProForm.Group>
</ModalForm>
</Fragment>
);
};

View File

@ -20,8 +20,9 @@ import dayjs from "dayjs";
import '@/ui/task/project/TreeTablePro.modules.css'
import {useSearchParams} from "next/navigation";
import {TaskWebSelectVO} from "@/lib/task/project/definitions";
import TaskNameAndIcon from "@/components/TaskNameAndIcon";
const TreeTablePro: React.FC = () => {
const TreeTablePro: React.FC = (props:{joinId?:string}) => {
// 刷新表格
const actionRef = useRef<ActionType>();
// 列表和树切换
@ -55,12 +56,7 @@ const TreeTablePro: React.FC = () => {
],
},
render: (_, record) => {
return <Fragment>
{record.fId && <svg className="icon" aria-hidden="true">
<use xlinkHref="#icon-tuandui"></use>
</svg>}
<text>{record.name}</text>
</Fragment>
return <TaskNameAndIcon task={record}/>
}
},
{
@ -182,6 +178,12 @@ const TreeTablePro: React.FC = () => {
}}/></>)
}
useEffect(() => {
if (props.joinId){
}
}, []);
useEffect(() => {
actionRef.current?.reload(false)
}, [useSearchParams()])

View File

@ -1,5 +1,6 @@
import axios, {Canceler, CancelToken, CancelTokenSource} from "axios";
import {message} from "antd";
import {usePathname, useSearchParams} from "next/navigation";
export const httpReq = axios.create({
@ -53,7 +54,7 @@ httpReq.interceptors.response.use(
message.error('系统异常');
} else if (error.response.status >= 400 && error.response.status <= 500) {
message.warning('无权限');
window.location.href = '/login';
window.location.href = `/login?callBack=${encodeURIComponent(window.location.pathname+window.location.search)}`;
}
// switch (error.response.status) {
// case 401: