Compare commits

...

4 Commits

Author SHA1 Message Date
1708-huayu 5eaf84df48 feat:日历格式处理 2025-08-22 19:12:39 +08:00
1708-huayu 252b3f1478 feat:时间块先显示描述后显示时间,刷新token接口限制,删除成功提示 2025-08-20 19:02:19 +08:00
1708-huayu ae11147281 feat:日历头样式,更多显示,打包处理 2025-08-19 18:52:33 +08:00
1708-huayu 34b85a177c feat:添加复制功能及农历 2025-08-18 18:52:28 +08:00
32 changed files with 435 additions and 120 deletions

View File

@ -1,2 +1,2 @@
NEXT_PUBLIC_TODO_REQUEST_URL=https://www.huaruyu.com/todo-server NEXT_PUBLIC_TODO_REQUEST_URL=/todoWeb
NEXT_PUBLIC_SECURITY_REQUEST_URL=https://www.huaruyu.com/security-server NEXT_PUBLIC_SECURITY_REQUEST_URL=/securityWeb

View File

@ -1,14 +1,15 @@
```shell ```shell
scp -r out/ shixiaohua@10.104.11.99:/home/shixiaohua/docker/todo-web scp -r out/ shixiaohua@10.104.11.101:/home/shixiaohua/docker/todo-web
scp -r cert/ shixiaohua@10.104.11.99:/home/shixiaohua/docker/todo-web scp -r cert/ shixiaohua@10.104.11.101:/home/shixiaohua/docker/todo-web
scp nginx.conf shixiaohua@10.104.11.99:/home/shixiaohua/docker/todo-web scp nginx.conf shixiaohua@10.104.11.101:/home/shixiaohua/docker/todo-web
scp Dockerfile shixiaohua@10.104.11.99:/home/shixiaohua/docker/todo-web scp Dockerfile shixiaohua@10.104.11.101:/home/shixiaohua/docker/todo-web
ssh shixiaohua@10.104.11.99 ssh shixiaohua@10.104.11.99
``` ```
docker操作 docker操作
```shell ```shell
scp nginx.conf shixiaohua@10.104.11.101:/home/shixiaohua/docker/todo-web
docker stop task-manager-nginx docker stop task-manager-nginx
docker rm task-manager-nginx docker rm task-manager-nginx
docker rmi task-manager-nginx docker rmi task-manager-nginx
@ -30,4 +31,9 @@ docker run -d -p 3001:80 -p 3002:443 --network task-manager --restart unless-sto
# 复制证书到云服务器 # 复制证书到云服务器
scp -r cert/ root@121.36.71.28:/usr/local/software/nginx-1.28.0/ scp -r cert/ root@121.36.71.28:/usr/local/software/nginx-1.28.0/
scp -r out/ root@121.36.71.28:/usr/share/nginx/html
cp -rf ./* ../
``` ```

View File

@ -13,6 +13,11 @@ events {
http { http {
include /etc/nginx/mime.types; include /etc/nginx/mime.types;
default_type application/octet-stream; default_type application/octet-stream;
limit_req_zone $binary_remote_addr zone=general_limit:10m rate=20r/s;
limit_req_zone $binary_remote_addr zone=api_limit:10m rate=5r/s;
log_format blocked '$time_local|$remote_addr|$request|$status|$http_referer';
# HTTPS 服务器监听端口 # HTTPS 服务器监听端口
# 443 # 443
# HTTP 服务器监听端口 # HTTP 服务器监听端口
@ -67,7 +72,28 @@ http {
try_files $uri $uri.html $uri/ =404; try_files $uri $uri.html $uri/ =404;
# try_files $uri $uri/ =404; # try_files $uri $uri/ =404;
} }
# 第二个页面的配置
location /todo {
# 关键配置:禁用重定向中的端口和绝对路径
absolute_redirect off;
port_in_redirect off;
# 指定根目录
alias /usr/share/nginx/html/todo;
index index.html index.htm;
# 正确的文件查找逻辑:所有路由都返回 index.html
try_files $uri $uri/ /todo/index.html;
# 安全头部
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
# 确保不会重定向到带端口的URL
add_header X-Forwarded-Host $host;
add_header X-Forwarded-Port 443;
add_header X-Forwarded-Proto https;
}
# 第二个页面的配置 # 第二个页面的配置
location ^~ /mobile/ { location ^~ /mobile/ {
# index index.html index.htm; # index index.html index.htm;
@ -90,6 +116,9 @@ http {
client_body_buffer_size 16k; client_body_buffer_size 16k;
client_max_body_size 100M; client_max_body_size 100M;
} }
location ^~ /security-server/ { location ^~ /security-server/ {
# 预检请求的处理 # 预检请求的处理
if ($request_method = 'OPTIONS') { if ($request_method = 'OPTIONS') {
@ -103,6 +132,20 @@ http {
client_body_buffer_size 16k; client_body_buffer_size 16k;
client_max_body_size 100M; client_max_body_size 100M;
}
location ^~ /securityWeb/ {
# 预检请求的处理
if ($request_method = 'OPTIONS') {
return 204;
}
# rewrite ^/security-server/(.*)$ /$1 break;
proxy_pass http://localhost:8091/;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
client_body_buffer_size 16k;
client_max_body_size 100M;
} }
location /task/ { location /task/ {
# 预检请求的处理 # 预检请求的处理

View File

@ -13,6 +13,11 @@ events {
http { http {
include /etc/nginx/mime.types; include /etc/nginx/mime.types;
default_type application/octet-stream; default_type application/octet-stream;
limit_req_zone $binary_remote_addr zone=general_limit:10m rate=20r/s;
limit_req_zone $binary_remote_addr zone=api_limit:10m rate=5r/s;
log_format blocked '$time_local|$remote_addr|$request|$status|$http_referer';
# HTTPS 服务器监听端口 # HTTPS 服务器监听端口
# 443 # 443
# HTTP 服务器监听端口 # HTTP 服务器监听端口
@ -90,6 +95,19 @@ http {
client_body_buffer_size 16k; client_body_buffer_size 16k;
client_max_body_size 100M; client_max_body_size 100M;
} }
location ^~ /todoWeb/ {
# 预检请求的处理
if ($request_method = 'OPTIONS') {
return 204;
}
# rewrite ^/todo-server/(.*)$ /$1 break;
proxy_pass http://huayu-platform-todo:8092/;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
client_body_buffer_size 16k;
client_max_body_size 100M;
}
location ^~ /security-server/ { location ^~ /security-server/ {
# 预检请求的处理 # 预检请求的处理
if ($request_method = 'OPTIONS') { if ($request_method = 'OPTIONS') {
@ -103,6 +121,20 @@ http {
client_body_buffer_size 16k; client_body_buffer_size 16k;
client_max_body_size 100M; client_max_body_size 100M;
}
location ^~ /securityWeb/ {
# 预检请求的处理
if ($request_method = 'OPTIONS') {
return 204;
}
# rewrite ^/security-server/(.*)$ /$1 break;
proxy_pass http://huayu-platform-security:8091/;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
client_body_buffer_size 16k;
client_max_body_size 100M;
} }
location /task/ { location /task/ {
# 预检请求的处理 # 预检请求的处理

View File

@ -1,5 +1,11 @@
/** @type {import('next').NextConfig} */ /** @type {import('next').NextConfig} */
const isProduction = process.env.NODE_ENV === 'production'
const nextConfig = { const nextConfig = {
// 核心配置:构建前清空输出目录
cleanDistDir: true,
basePath: isProduction ? "/todo" : '',
// 静态文件前缀
assetPrefix: isProduction ? "/todo" : '',
// Middleware cannot be used with "output: export". // Middleware cannot be used with "output: export".
output: 'export', output: 'export',
// Optional: Change links `/me` -> `/me/` and emit `/me.html` -> `/me/index.html` // Optional: Change links `/me` -> `/me/` and emit `/me.html` -> `/me/index.html`
@ -7,11 +13,15 @@ const nextConfig = {
// Optional: Prevent automatic `/me` -> `/me/`, instead preserve `href` // Optional: Prevent automatic `/me` -> `/me/`, instead preserve `href`
// skipTrailingSlashRedirect: true, // skipTrailingSlashRedirect: true,
// Optional: Change the output directory `out` -> `dist` // Optional: Change the output directory `out` -> `dist`
distDir: 'docker/out', distDir: 'docker/outd',
// 严格模式下react-beautiful-dnd无法使用 // 严格模式下react-beautiful-dnd无法使用
reactStrictMode:false, reactStrictMode:false,
eslint: {
// Warning: This allows production builds to successfully complete even if
// your project has ESLint errors.
ignoreDuringBuilds: true,
},
}; };
export default nextConfig; export default nextConfig;

14
package-lock.json generated
View File

@ -16,6 +16,7 @@
"axios": "^1.6.8", "axios": "^1.6.8",
"dayjs": "^1.11.13", "dayjs": "^1.11.13",
"js-cookie": "^3.0.5", "js-cookie": "^3.0.5",
"lunar-calendar": "^0.1.4",
"next": "14.2.29", "next": "14.2.29",
"postcss": "8.4.31", "postcss": "8.4.31",
"react": "^18", "react": "^18",
@ -4840,6 +4841,14 @@
"node": "14 || >=16.14" "node": "14 || >=16.14"
} }
}, },
"node_modules/lunar-calendar": {
"version": "0.1.4",
"resolved": "https://registry.npmmirror.com/lunar-calendar/-/lunar-calendar-0.1.4.tgz",
"integrity": "sha512-5r87vbg5yg56z/jkf3A+Ur+ZggUTiJw1VATT9P7RELQgWcTNhfJ+OLkNYroSna6r65bMqyaAgapo9vRN40L75A==",
"engines": {
"node": "*"
}
},
"node_modules/luxon": { "node_modules/luxon": {
"version": "3.4.4", "version": "3.4.4",
"resolved": "https://registry.npmmirror.com/luxon/-/luxon-3.4.4.tgz", "resolved": "https://registry.npmmirror.com/luxon/-/luxon-3.4.4.tgz",
@ -11500,6 +11509,11 @@
"resolved": "https://registry.npmmirror.com/lru-cache/-/lru-cache-10.2.0.tgz", "resolved": "https://registry.npmmirror.com/lru-cache/-/lru-cache-10.2.0.tgz",
"integrity": "sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q==" "integrity": "sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q=="
}, },
"lunar-calendar": {
"version": "0.1.4",
"resolved": "https://registry.npmmirror.com/lunar-calendar/-/lunar-calendar-0.1.4.tgz",
"integrity": "sha512-5r87vbg5yg56z/jkf3A+Ur+ZggUTiJw1VATT9P7RELQgWcTNhfJ+OLkNYroSna6r65bMqyaAgapo9vRN40L75A=="
},
"luxon": { "luxon": {
"version": "3.4.4", "version": "3.4.4",
"resolved": "https://registry.npmmirror.com/luxon/-/luxon-3.4.4.tgz", "resolved": "https://registry.npmmirror.com/luxon/-/luxon-3.4.4.tgz",

View File

@ -18,6 +18,7 @@
"axios": "^1.6.8", "axios": "^1.6.8",
"dayjs": "^1.11.13", "dayjs": "^1.11.13",
"js-cookie": "^3.0.5", "js-cookie": "^3.0.5",
"lunar-calendar": "^0.1.4",
"next": "14.2.29", "next": "14.2.29",
"postcss": "8.4.31", "postcss": "8.4.31",
"react": "^18", "react": "^18",

View File

@ -1,10 +1,5 @@
import type {Metadata} from "next";
import "@/ui/globals.css"; import "@/ui/globals.css";
export const metadata: Metadata = {
title: "马上行计划管理",
description: "马上行计划管理",
};
/** /**
* Root Layout (Required) * Root Layout (Required)
* @param children * @param children
@ -20,7 +15,7 @@ export default function RootLayout({
<head> <head>
<title></title> <title></title>
<link rel="icon" href="/favicon.ico"/> <link rel="icon" href="/favicon.ico"/>
<script src="/static/iconfont.js"></script> <script src="/static/iconfont.js" async ></script>
{/*FOUCFlash of Unstyled Content*/} {/*FOUCFlash of Unstyled Content*/}
{/*<link rel="preload" href="https://cdn.jsdelivr.net/npm/antd/dist/antd.min.css" as="style"/>*/} {/*<link rel="preload" href="https://cdn.jsdelivr.net/npm/antd/dist/antd.min.css" as="style"/>*/}
{/*<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/antd/dist/antd.min.css"/>*/} {/*<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/antd/dist/antd.min.css"/>*/}

View File

@ -5,9 +5,9 @@ import {useEffect} from "react";
export default function Home() { export default function Home() {
const {replace} = useRouter(); const {replace} = useRouter();
useEffect(()=>{
const pathName = usePathname() const pathName = usePathname()
const searchParams = useSearchParams() const searchParams = useSearchParams()
useEffect(()=>{
console.log({pathName},{searchParams}) console.log({pathName},{searchParams})
if(localStorage.getItem('platform-security')){ if(localStorage.getItem('platform-security')){
if (!pathName){ if (!pathName){
@ -17,11 +17,13 @@ export default function Home() {
}else { }else {
replace("/task/project") replace("/task/project")
} }
}else if (pathName =="/"){
replace("/task/project")
} }
}else { }else {
replace("/login") replace("/login")
} }
},[]) },)
dayjs.locale('zh-cn') dayjs.locale('zh-cn')
return ( return (
<main className="flex min-h-screen flex-col p-6"> <main className="flex min-h-screen flex-col p-6">

View File

@ -30,7 +30,7 @@ export default function Layout({children}: { children: React.ReactNode }) {
const [spinning,setSpinning]=useState(false); const [spinning,setSpinning]=useState(false);
useEffect(() => { useEffect(() => {
selectData() selectData()
}, [data]) }, [data,pid])
function selectData() { function selectData() {
setSpinning(true) setSpinning(true)

View File

@ -66,7 +66,7 @@ export default function Layout({children}: { children: React.ReactNode }) {
// @ts-ignore // @ts-ignore
document.getElementById('tenLeft').style.fontSize = divHeight/6*4 + 'px'; document.getElementById('tenLeft').style.fontSize = divHeight/6*4 + 'px';
refreshDate() refreshDate()
}, [useContext(LocalContext)]); }, [data]);
return ( return (
<div> <div>
<div className='firstRow' style={{display: 'flex'}}> <div className='firstRow' style={{display: 'flex'}}>

View File

@ -0,0 +1,19 @@
import React from 'react';
import {DateHeaderProps} from "react-big-calendar";
import {lunarDateShow} from "@/utils/timeFormatUtil";
// 定义为 React 函数组件
const CalHeader: React.FC<DateHeaderProps> = ({date, label, drilldownView, onDrillDown}) => {
if (!drilldownView) {
return <span>{label}</span>
}
return (
<div className="rbc-button-link " style={{display: "flex", justifyContent: "space-between"}}
onClick={onDrillDown}>
<div>{label}</div>
<div>{lunarDateShow(date)}</div>
</div>
)
};
export default CalHeader;

View File

@ -1,5 +1,5 @@
import {Button, Form, message, Popconfirm} from "antd"; import {Button, Form, message, Popconfirm} from "antd";
import React, {useEffect, useState} from "react"; import React, {useEffect, useRef, useState} from "react";
import { import {
ModalForm, ModalForm,
ProFormDateTimeRangePicker, ProFormDateTimeRangePicker,
@ -17,7 +17,6 @@ import dayjs, {UnitTypeShort} from "dayjs";
import {onceConsumerRead} from "@/utils/codeToReadName"; import {onceConsumerRead} from "@/utils/codeToReadName";
import {betweenTime} from "@/utils/timeFormatUtil"; import {betweenTime} from "@/utils/timeFormatUtil";
import {QuestionCircleOutlined} from "@ant-design/icons"; import {QuestionCircleOutlined} from "@ant-design/icons";
import {deleteTask} from "@/lib/task/project/data";
interface ClickRecordProps { interface ClickRecordProps {
openClickRecord?: boolean; openClickRecord?: boolean;
@ -67,6 +66,7 @@ const ClickRecord: React.FC<ClickRecordProps> = ({
form.setFieldsValue(data) form.setFieldsValue(data)
} }
}, [recordId]); }, [recordId]);
return ( return (
<ModalForm<TaskScheduleRecordForm> <ModalForm<TaskScheduleRecordForm>
@ -139,8 +139,11 @@ const ClickRecord: React.FC<ClickRecordProps> = ({
} else { } else {
await clickRecordAPI(values); await clickRecordAPI(values);
} }
console.log("before reloadData")
reloadData?.();
console.log("after reloadData")
// 然后在onFinish中使用
setOpenClickRecord?.(false) setOpenClickRecord?.(false)
reloadData?.()
return true return true
}catch (e){ }catch (e){
console.error(e) console.error(e)

View File

@ -159,7 +159,7 @@ const DiaryOption = (props: SelectDiary) => {
}) })
}) })
setDiaryReduceList(returnResult.reverse()); setDiaryReduceList(returnResult.reverse());
}, [diaryList, currentIndex]); }, [diaryList, currentIndex,props.taskId]);
const listRef = useRef<ListRef>(null); const listRef = useRef<ListRef>(null);
const onScroll = (e: React.UIEvent<HTMLElement, UIEvent>) => { const onScroll = (e: React.UIEvent<HTMLElement, UIEvent>) => {

View File

@ -183,7 +183,7 @@ const CronGenerator: React.FC<CronGeneratorProps> = ({setCronFunction,setExpecte
console.log({fullCronExpression}) console.log({fullCronExpression})
setCanReadCron(cronToChinese(fullCronExpression)) setCanReadCron(cronToChinese(fullCronExpression))
} }
}, [fullCronExpression]); }, [fullCronExpression,cron]);
const onClickConfirmCron = () => { const onClickConfirmCron = () => {
if (fullCronExpression) { if (fullCronExpression) {

View File

@ -1,9 +1,15 @@
import {TaskMessage} from "@/lib/definitions"; import {TaskMessage} from "@/lib/definitions";
import React, {Fragment} from "react"; import React, {Fragment} from "react";
import {SmileOutlined} from "@ant-design/icons"; import {CopyOutlined, SmileOutlined} from "@ant-design/icons";
import {copyToClipboard} from "@/lib/copyToClipboard";
const TaskNameAndIcon = (props: { task: TaskMessage }) => { const TaskNameAndIcon = (props: { task: TaskMessage }) => {
return (<Fragment> return (<Fragment>
<CopyOutlined onClick={(e) => {
e.preventDefault(); // 阻止默认行为(如果有)
e.stopPropagation(); // 阻止事件冒泡
copyToClipboard(props.task.name)
}}/>
{props.task.fId && {props.task.fId &&
<svg className="icon" aria-hidden="true"> <svg className="icon" aria-hidden="true">
<use xlinkHref="#icon-tuandui"></use> <use xlinkHref="#icon-tuandui"></use>

View File

@ -206,7 +206,7 @@ const TaskRemindComponent = (props: ITaskRemind) => {
) : ( ) : (
props.remindTypeList.map((remindType, index) => ( props.remindTypeList.map((remindType, index) => (
// <div key={index}>{remindType.split(",").join(", ")}</div> // <div key={index}>{remindType.split(",").join(", ")}</div>
<Fragment> <Fragment key={remindType}>
<ConfigProvider <ConfigProvider
theme={{ theme={{
components: { components: {

View File

@ -103,7 +103,7 @@ const TeamMember = (props: { taskId: string, closeOpen?: () => void, reloadData?
return () => { return () => {
isMounted = false; isMounted = false;
}; };
}, [open]) }, [open,props.taskId])
const removeTeam = (teamMember: TeamMemberVO) => { const removeTeam = (teamMember: TeamMemberVO) => {
removeTeamAPI(teamMember).then(res => { removeTeamAPI(teamMember).then(res => {
if (res.data.status.success) { if (res.data.status.success) {

View File

@ -13,5 +13,5 @@ export const askLoginAPI = (data:{}):Promise<AxiosResponse<ResponseVO<AskLoginRe
} }
export const refreshTokenAPI = ():Promise<AxiosResponse<ResponseVO<string>>> =>{ export const refreshTokenAPI = ():Promise<AxiosResponse<ResponseVO<string>>> =>{
return httpReq.get(process.env.NEXT_PUBLIC_SECURITY_REQUEST_URL + "/V2/wx/login/refreshToken") return httpReq.post(process.env.NEXT_PUBLIC_SECURITY_REQUEST_URL + "/V2/wx/login/refreshToken")
} }

View File

@ -5,4 +5,5 @@ export interface TaskEvent extends Event {
state?:any; state?:any;
taskType?:string; taskType?:string;
priority?:any; priority?:any;
description?:string;
} }

29
src/lib/type/lunar.d.ts vendored Normal file
View File

@ -0,0 +1,29 @@
declare module 'lunar-calendar' {
export interface LunarDate {
lunarYear: number;
lunarMonth: number;
lunarDay: number;
zodiac: string;
ganZhiYear: string;
ganZhiMonth: string;
ganZhiDay: string;
lunarMonthName: string;
lunarDayName: string;
solarTerm: string;
// 其他农历字段...
// 二十四节气
term:string;
// 农历节日
lunarFestival:string;
// 公历节日
solarFestival:string;
}
export function solarToLunar(
year: number,
month: number,
day: number
): LunarDate;
// 其他可能的方法...
}

View File

@ -118,7 +118,7 @@ class OperationButton extends React.Component<OperationButtonProps, OperationMod
deleteTask(this.props.itemId).then((response => { deleteTask(this.props.itemId).then((response => {
console.log('response', response) console.log('response', response)
if (response.status.success) { if (response.status.success) {
message.success("删除任务成功" + response.data) message.success("删除任务成功")
this.props.refreshDate?.() this.props.refreshDate?.()
} }
})); }));

View File

@ -52,7 +52,7 @@ const RightOption: React.FC<OperationButtonProps> = (props) => {
deleteTask(props.itemId).then((response => { deleteTask(props.itemId).then((response => {
console.log('response', response) console.log('response', response)
if (response.status.success) { if (response.status.success) {
message.success("删除任务成功" + response.data) message.success("删除任务成功")
props.refreshDate?.() props.refreshDate?.()
} }
})); }));

View File

@ -1,15 +1,16 @@
'use client' 'use client'
import React, {Fragment, useContext, useEffect, useState} from "react"; import React, {Fragment, useContext, useEffect, useState} from "react";
import {Button, Checkbox, CheckboxOptionType, DatePicker, MenuProps, message, Select, Space} from "antd"; import {Button, Checkbox, CheckboxOptionType, DatePicker, MenuProps, message, Popconfirm, Select, Space} from "antd";
import {usePathname, useRouter} from "next/navigation"; import {usePathname, useRouter} from "next/navigation";
import {DetailModelForm} from "@/ui/task/project/DetailModelForm"; import {DetailModelForm} from "@/ui/task/project/DetailModelForm";
import {OPERATION_BUTTON_TYPE, taskStateList} from "@/lib/task/project/data"; import {deleteTask, OPERATION_BUTTON_TYPE, taskStateList} from "@/lib/task/project/data";
import style from '@/ui/task/TitleOperation.module.css' import style from '@/ui/task/TitleOperation.module.css'
import LocalContext from "@/ui/LocalContent"; import LocalContext from "@/ui/LocalContent";
import {RequestDateType} from "@/ui/task/RequestDateType"; import {RequestDateType} from "@/ui/task/RequestDateType";
import dayjs, {Dayjs} from "dayjs"; import dayjs, {Dayjs} from "dayjs";
import {useSearchParams} from "next/dist/client/components/navigation"; import {useSearchParams} from "next/dist/client/components/navigation";
import Dropdown from "antd/es/dropdown/dropdown"; import Dropdown from "antd/es/dropdown/dropdown";
import {QuestionCircleOutlined} from "@ant-design/icons";
interface TitleOperationProps { interface TitleOperationProps {
setTaskState: (value: string) => void; setTaskState: (value: string) => void;
@ -69,24 +70,44 @@ export const TitleOperation: React.FC<TitleOperationProps> = ({
const onClick: MenuProps['onClick'] = ({key}) => { const onClick: MenuProps['onClick'] = ({key}) => {
if (key == "1") { if (key == "1") {
localStorage.removeItem('platform-security') // localStorage.removeItem('platform-security')
replace(`/login`) // replace(`/login`)
} else if (key == "2") { } else if (key == "2") {
replace(pathName) replace(pathName)
setPathParam(undefined) setPathParam(undefined)
refreshData() // refreshData()
} }
}; };
const items: MenuProps['items'] = [ const items: MenuProps['items'] = [
{ {
label: '退出登录', label: <Popconfirm
title="退出登录"
description="确认要退出登录?"
icon={<QuestionCircleOutlined style={{color: 'red'}}/>}
okText="确认"
cancelText="取消"
onConfirm={() => {
localStorage.removeItem('platform-security')
replace(`/login`)
}}
><a>退</a></Popconfirm>,
key: '1', key: '1',
}, },
]; ];
const itemsPid: MenuProps['items'] = [ const itemsPid: MenuProps['items'] = [
{ {
label: '退出登录', label: <Popconfirm
title="退出登录"
description="确认要退出登录?"
icon={<QuestionCircleOutlined style={{color: 'red'}}/>}
okText="确认"
cancelText="取消"
onConfirm={() => {
localStorage.removeItem('platform-security')
replace(`/login`)
}}
><a>退</a></Popconfirm>,
key: '1', key: '1',
}, },
{ {

View File

@ -1,6 +1,15 @@
'use client' 'use client'
import React, {Fragment, useCallback, useContext, useEffect, useMemo, useRef, useState} from "react"; import React, {Fragment, useCallback, useContext, useEffect, useMemo, useRef, useState} from "react";
import {Calendar, dateFnsLocalizer, dayjsLocalizer, Event, SlotInfo, View} from 'react-big-calendar' import {
Calendar,
Culture,
dateFnsLocalizer,
DateLocalizer,
dayjsLocalizer,
Event,
SlotInfo,
View
} from 'react-big-calendar'
import dayjs, {Dayjs} from 'dayjs' import dayjs, {Dayjs} from 'dayjs'
import 'dayjs/locale/zh-cn'; import 'dayjs/locale/zh-cn';
import 'react-big-calendar/lib/css/react-big-calendar.css' import 'react-big-calendar/lib/css/react-big-calendar.css'
@ -20,7 +29,11 @@ import {TaskWebSelectVO} from "@/lib/task/project/definitions";
import {message, Modal, Spin} from "antd"; import {message, Modal, Spin} from "antd";
import ClickRecord from "@/components/ClickRecord"; import ClickRecord from "@/components/ClickRecord";
import {editClickRecordRangeAPI} from "@/components/service/ScheduleTask"; import {editClickRecordRangeAPI} from "@/components/service/ScheduleTask";
import {ExclamationCircleFilled, LoadingOutlined} from "@ant-design/icons"; import {CopyOutlined, ExclamationCircleFilled, LoadingOutlined} from "@ant-design/icons";
import {copyToClipboard} from "@/lib/copyToClipboard";
import {solarToLunar} from "lunar-calendar";
import CalHeader from "@/components/CalHeader";
import {lunarDateShow} from "@/utils/timeFormatUtil";
const {confirm} = Modal; const {confirm} = Modal;
/** /**
@ -28,6 +41,7 @@ const {confirm} = Modal;
* @constructor * @constructor
*/ */
const localizer = dayjsLocalizer(dayjs) const localizer = dayjsLocalizer(dayjs)
const DragAndDropCalendar = withDragAndDrop(Calendar) const DragAndDropCalendar = withDragAndDrop(Calendar)
const CalShow: React.FC = () => { const CalShow: React.FC = () => {
dayjs.locale('zh-cn') dayjs.locale('zh-cn')
@ -64,7 +78,9 @@ const CalShow: React.FC = () => {
expectedEndTime: range.end expectedEndTime: range.end
} }
}) })
const dateFormat = (date: Date, culture?: Culture, localizer?: DateLocalizer) => {
return `${date.getDate()}/${lunarDateShow(date)}`;
};
const handleViewChange = (newView: View) => { const handleViewChange = (newView: View) => {
setView(newView); setView(newView);
}; };
@ -75,19 +91,23 @@ const CalShow: React.FC = () => {
clickRef && typeof clickRef.current === 'number' && !isNaN(clickRef.current) && isFinite(clickRef.current) && window.clearTimeout(clickRef.current) clickRef && typeof clickRef.current === 'number' && !isNaN(clickRef.current) && isFinite(clickRef.current) && window.clearTimeout(clickRef.current)
} }
/**
*
* @param newDate
*/
const handleNavigate = (newDate: Date) => { const handleNavigate = (newDate: Date) => {
console.log('handleNavigate', newDate) console.log('handleNavigate', newDate)
setDate(newDate); setDate(newDate);
const searchList: SearchObject[] = [] // const searchList: SearchObject[] = []
if (pid != null) { // if (pid != null) {
searchList.push({name: "pid", value: pid, operateType: "="}, // searchList.push({name: "pid", value: pid, operateType: "="},
{name: 'ALL-CHILD', value: "true", operateType: "ALL-CHILD"}, // {name: 'ALL-CHILD', value: "true", operateType: "ALL-CHILD"},
{name: 'TREE-FILTER', value: "true", operateType: "TREE-FILTER"}, // {name: 'TREE-FILTER', value: "true", operateType: "TREE-FILTER"},
); // );
searchObject.data.pid = pid // searchObject.data.pid = pid
setSearchObject({...searchObject}) // setSearchObject({...searchObject})
} // }
loadData(searchList); // loadData();
}; };
useEffect(() => { useEffect(() => {
@ -102,6 +122,10 @@ const CalShow: React.FC = () => {
// ); // );
searchObject.data.pid = pid searchObject.data.pid = pid
setSearchObject({...searchObject}) setSearchObject({...searchObject})
} else {
const {pid, ...search} = searchObject.data
searchObject.data = search
setSearchObject({...searchObject})
} }
// searchListE.push({name: 'expectedStartTime', value: range.start, operateType: ">="}) // searchListE.push({name: 'expectedStartTime', value: range.start, operateType: ">="})
// searchListE.push({name: 'expectedStartTime', value: range.end, operateType: "<="}) // searchListE.push({name: 'expectedStartTime', value: range.end, operateType: "<="})
@ -115,7 +139,7 @@ const CalShow: React.FC = () => {
return () => { return () => {
clearClickTimeout() clearClickTimeout()
} }
}, [state, taskTypeList, refreshData, range]); }, [state, taskTypeList, refreshData, range, pid]);
const calMessages = { const calMessages = {
week: '周', week: '周',
work_week: '工作周', work_week: '工作周',
@ -135,6 +159,7 @@ const CalShow: React.FC = () => {
noEventsInRange: '暂无计划', noEventsInRange: '暂无计划',
} }
const loadData = (searchList?: SearchObject[]) => { const loadData = (searchList?: SearchObject[]) => {
console.log("loadData")
setSpinning(true) setSpinning(true)
searchObject.data.state = state searchObject.data.state = state
searchObject.data.taskTypeList = taskTypeList searchObject.data.taskTypeList = taskTypeList
@ -145,10 +170,19 @@ const CalShow: React.FC = () => {
if (responseD.status.success) { if (responseD.status.success) {
let result: TaskEvent[] = responseD.data.content.map<TaskEvent>(taskState => { let result: TaskEvent[] = responseD.data.content.map<TaskEvent>(taskState => {
return { return {
start: dayjs(taskState.expectedStartTime).toDate(), start: taskState.expectedStartTime ? dayjs(taskState.expectedStartTime).toDate() : dayjs(taskState.expectedEndTime).startOf('day').toDate(),
end: dayjs(taskState.expectedEndTime).toDate(), end: taskState.expectedEndTime ? dayjs(taskState.expectedEndTime).toDate() : dayjs(taskState.expectedStartTime).endOf('day').toDate(),
title: <Fragment><TaskNameAndIcon task={taskState}/> title: <Fragment>
<div>{taskState.description}</div> <div>
<TaskNameAndIcon task={taskState}/>
</div>
<div>
{view != 'month' && taskState.description && <CopyOutlined onClick={(e) => {
e.preventDefault(); // 阻止默认行为(如果有)
e.stopPropagation(); // 阻止事件冒泡
copyToClipboard(taskState.description)
}}/>} {view != 'month' && taskState.description}
</div>
</Fragment>, </Fragment>,
// style: { // style: {
// backgroundColor: 'green', // backgroundColor: 'green',
@ -159,7 +193,8 @@ const CalShow: React.FC = () => {
id: taskState.id, id: taskState.id,
state: taskState.state, state: taskState.state,
priority: taskState.priority, priority: taskState.priority,
taskType: taskState.taskType taskType: taskState.taskType,
description:taskState.description
} }
}); });
console.log('responseD.data.content:', result) console.log('responseD.data.content:', result)
@ -198,9 +233,13 @@ const CalShow: React.FC = () => {
// } // }
// }) // })
} }
/**
* from表单使用使
*/
const reloadData = () => { const reloadData = () => {
setOpen(false) setOpen(false)
handleNavigate(date) loadData();
// handleNavigate(date)
} }
const handleSelectSlot = useCallback( const handleSelectSlot = useCallback(
({start, end}: SlotInfo) => { ({start, end}: SlotInfo) => {
@ -340,39 +379,49 @@ const CalShow: React.FC = () => {
console.log("rangeChange:", rangeLet, (current ? current : view)) console.log("rangeChange:", rangeLet, (current ? current : view))
// view 为天的时候类型为数组index:0为当天 // view 为天的时候类型为数组index:0为当天
if ((current ? current : view) === "day" && Array.isArray(rangeLet)) { if ((current ? current : view) === "day" && Array.isArray(rangeLet)) {
if (range.start.valueOf() > rangeLet[0].valueOf()) { // if (range.start.valueOf() > rangeLet[0].valueOf()) {
// setRange({...range, start: rangeLet[0]})
range.start = rangeLet[0]; range.start = rangeLet[0];
} else if (range.end.valueOf() < rangeLet[0].valueOf()) { // }
// setRange({...range, end: rangeLet[0]}) // if (range.end.valueOf() < rangeLet[0].valueOf()) {
range.end=rangeLet[0] range.end = dayjs(rangeLet[0]).add(1, 'd').toDate()
} // }
} }
// 为周的时候类型为数组,周一到周日七天 // 为周的时候类型为数组,周一到周日七天
if ((current ? current : view) === "week" && Array.isArray(rangeLet)) { if ((current ? current : view) === "week" && Array.isArray(rangeLet)) {
if (range.start.valueOf() > rangeLet[0].valueOf()) { // if (range.start.valueOf() > rangeLet[0].valueOf()) {
// setRange({...range, start: rangeLet[0]})
range.start = rangeLet[0]; range.start = rangeLet[0];
} // }
if (range.end.valueOf() < rangeLet[6].valueOf()) { // if (range.end.valueOf() < rangeLet[6].valueOf()) {
// setRange({...range, end: rangeLet[6]}) range.end = dayjs(rangeLet[6]).add(1, 'd').toDate()
range.end=rangeLet[6] // }
}
} }
// 为月的时候类型为对象 // 为月的时候类型为对象
if ((current ? current : view) === "month" && rangeLet && !Array.isArray(rangeLet)) { if ((current ? current : view) === "month" && rangeLet && !Array.isArray(rangeLet)) {
if (range.start.valueOf() > rangeLet.start.valueOf()) { // if (range.start.valueOf() > rangeLet.start.valueOf()) {
// setRange({...range, start: rangeLet.start})
range.start = rangeLet.start range.start = rangeLet.start
} // }
if (range.end.valueOf() < rangeLet.end.valueOf()) { // if (range.end.valueOf() < rangeLet.end.valueOf()) {
// setRange({...range, end: rangeLet.end}) range.end = dayjs(rangeLet.end).add(1, 'd').toDate()
range.end = rangeLet.end
} // }
} }
setRange({...range}) setRange({...range})
} }
const toolTipShow = (event:TaskEvent) =>{
console.log("toolTipShow", event)
let result=""
if (view!='month'){
result += "\n"
}
result += "计划:"+event.name
if (event.description){
result += "\n描述:"+event.description
}
return result;
}
return <div className="App" style={{height: '90vh'}}> return <div className="App" style={{height: '90vh'}}>
{open && <DetailModelForm operationId={operationId} {open && <DetailModelForm operationId={operationId}
description={description} description={description}
@ -388,9 +437,23 @@ const CalShow: React.FC = () => {
reloadData={loadData}/>} reloadData={loadData}/>}
<Spin spinning={spinning} indicator={<LoadingOutlined style={{fontSize: 48}} spin/>} fullscreen/> <Spin spinning={spinning} indicator={<LoadingOutlined style={{fontSize: 48}} spin/>} fullscreen/>
<DragAndDropCalendar <DragAndDropCalendar
// 本地设置 // 本地设置
localizer={localizer} localizer={localizer}
messages={calMessages} messages={calMessages}
formats={{
dayFormat: dateFormat,
// dateFormat: dateFormat,
monthHeaderFormat: (solarDate) => {
const lunarDate = solarToLunar(
solarDate.getFullYear(),
solarDate.getMonth() + 1,
1
);
return `${solarDate.getFullYear()}${solarDate.getMonth() + 1}月 (农历${lunarDate.lunarMonthName})`;
},
dayHeaderFormat: dateFormat
}}
// 修改style // 修改style
eventPropGetter={eventPropGetter} eventPropGetter={eventPropGetter}
events={events} events={events}
@ -417,6 +480,14 @@ const CalShow: React.FC = () => {
resizable resizable
onEventResize={moveEvent} onEventResize={moveEvent}
onEventDrop={moveEvent} onEventDrop={moveEvent}
// 组件设置
components={
{
month: {
dateHeader: CalHeader
}
}}
tooltipAccessor={toolTipShow}
/> />
</div> </div>
} }

View File

@ -18,3 +18,26 @@
background-color: green !important; background-color: green !important;
color: black !important; color: black !important;
} }
/* 修改计划展示,先展示描述后展示时间 start */
.rbc-addons-dnd-resizable{
display: flex;
flex-direction: column;
}
.rbc-event-label{
/*设置顺序*/
order: 2;
}
.rbc-day-slot .rbc-event-content{
flex-grow: 0;
min-height:unset;
}
.rbc-day-slot .rbc-event-content{
/* 不自动增长 */
flex-grow: 0;
/* 移除组件内属性 min-height */
min-height:unset;
}
/* 修改计划展示,先展示描述后展示时间 end */

View File

@ -123,7 +123,7 @@ export const DetailForm: React.FC<DetailFormProps> = (props) => {
if (props.operationId === OPERATION_BUTTON_TYPE.ADD_CHILD) { if (props.operationId === OPERATION_BUTTON_TYPE.ADD_CHILD) {
addTask(request).then(response => { addTask(request).then(response => {
console.log('response', response) console.log('response', response)
if (response.status.success) { if (response.data.status.success) {
message.success("添加任务成功:" + response.data) message.success("添加任务成功:" + response.data)
props.handleCancel() props.handleCancel()
} }
@ -140,11 +140,11 @@ export const DetailForm: React.FC<DetailFormProps> = (props) => {
} }
updateTask(request).then(response => { updateTask(request).then(response => {
console.log('response', response) console.log('response', response)
if (response.status.success) { if (response.data.status.success) {
message.success("修改任务成功:" + response.data) message.success("修改任务成功:" + response.data)
props.handleCancel() props.handleCancel()
}else { }else {
message.error(response.status.message) message.error(response.data.status.message)
} }
} }
) )

View File

@ -10,6 +10,8 @@ import {getTaskState, taskPriorityList, taskStateList} from "@/lib/task/project/
import 'react-virtualized/styles.css'; import 'react-virtualized/styles.css';
import RightOption from "@/ui/task/RightOption"; import RightOption from "@/ui/task/RightOption";
import TaskNameAndIcon from "@/components/TaskNameAndIcon"; import TaskNameAndIcon from "@/components/TaskNameAndIcon";
import {copyToClipboard} from "@/lib/copyToClipboard";
import {CopyOutlined} from "@ant-design/icons";
interface DroppableTableProps { interface DroppableTableProps {
@ -99,9 +101,13 @@ export const DroppableTable = React.memo((props: DroppableTableProps) => {
<div className='displayFlexRow'> <TaskNameAndIcon task={record}/></div> <div className='displayFlexRow'> <TaskNameAndIcon task={record}/></div>
</Tooltip> </Tooltip>
</div> </div>
<div style={{width: '45%', boxSizing: 'border-box', minWidth: 0}} <div style={{width: '45%', boxSizing: 'border-box', minWidth: 0,display: 'flex',justifyContent:"start"}}
className='displayFlexRow'> >
<Tooltip placement="topLeft" title={record.description}> <Tooltip placement="topLeft" title={record.description} className="displayFlexRow">
{record.description && <CopyOutlined onClick={(e) => {
e.preventDefault();
copyToClipboard(record.description)
}}/>}
<div className='displayFlexRow text-ellipsis'>{record.description}</div> <div className='displayFlexRow text-ellipsis'>{record.description}</div>
</Tooltip> </Tooltip>
</div> </div>

View File

@ -364,7 +364,7 @@ export const DetailModelForm: React.FC<DetailModelFormProps> = (props) => {
deleteTask(props.itemId).then((response => { deleteTask(props.itemId).then((response => {
console.log('response', response) console.log('response', response)
if (response.status.success) { if (response.status.success) {
message.success("删除任务成功" + response.data) message.success("删除任务成功")
initData() initData()
props.reloadData?.() props.reloadData?.()
} }
@ -481,8 +481,6 @@ export const DetailModelForm: React.FC<DetailModelFormProps> = (props) => {
} }
); );
} else { } else {
await addTask(values).then(response => { await addTask(values).then(response => {
console.log('response', response) console.log('response', response)
if (response.data.status.success) { if (response.data.status.success) {

View File

@ -1,6 +1,6 @@
'use client' 'use client'
import { import {
CheckSquareFilled, CheckSquareFilled, CopyOutlined,
QuestionCircleOutlined QuestionCircleOutlined
} from '@ant-design/icons'; } from '@ant-design/icons';
import type {ActionType, FormInstance, ProColumns, ProFormInstance} from '@ant-design/pro-components'; import type {ActionType, FormInstance, ProColumns, ProFormInstance} from '@ant-design/pro-components';
@ -21,6 +21,7 @@ import '@/ui/task/project/TreeTablePro.modules.css'
import {useSearchParams} from "next/navigation"; import {useSearchParams} from "next/navigation";
import {TaskWebSelectVO} from "@/lib/task/project/definitions"; import {TaskWebSelectVO} from "@/lib/task/project/definitions";
import TaskNameAndIcon from "@/components/TaskNameAndIcon"; import TaskNameAndIcon from "@/components/TaskNameAndIcon";
import {copyToClipboard} from "@/lib/copyToClipboard";
const TreeTablePro: React.FC = (props: { joinId?: string }) => { const TreeTablePro: React.FC = (props: { joinId?: string }) => {
// 刷新表格 // 刷新表格
@ -64,9 +65,15 @@ const TreeTablePro: React.FC = (props: { joinId?: string }) => {
title: '任务描述', title: '任务描述',
dataIndex: 'description', dataIndex: 'description',
render: (_, record) => { render: (_, record) => {
return <Tooltip placement="topLeft" title={record.description}> return <Fragment>
<div className='text-ellipsis'>{record.description}</div>
<Tooltip placement="topLeft" title={record.description}>
<div className='text-ellipsis'>{record.description && <CopyOutlined onClick={(e) => {
e.preventDefault();
copyToClipboard(record.description)
}}/>}{record.description}</div>
</Tooltip> </Tooltip>
</Fragment>
} }
}, },
{ {

View File

@ -1,4 +1,4 @@
import axios, {CancelTokenSource} from "axios"; import axios, {AxiosInterceptorOptions, CancelTokenSource} from "axios";
import {message} from "antd"; import {message} from "antd";
import {refreshTokenAPI} from "@/lib/login/service"; import {refreshTokenAPI} from "@/lib/login/service";
@ -19,11 +19,17 @@ const cancelTokenSource: CancelTokenSource = axios.CancelToken.source();
httpReq.defaults.headers.common['Accept'] = 'application/json'; httpReq.defaults.headers.common['Accept'] = 'application/json';
// 请求前处理 // 请求前处理
httpReq.interceptors.request.use((config) => { httpReq.interceptors.request.use((config) => {
console.log("config.url", config.url)
// 从本地存储中获取 token // 从本地存储中获取 token
const token = localStorage.getItem('platform-security'); const token = localStorage.getItem('platform-security');
if (token) { if (token) {
config.headers.Authorization = `Bearer ${token}`; config.headers.Authorization = `Bearer ${token}`;
reduceToken(token) if (config.url && config.url.indexOf("refreshToken") == -1 &&
config.url.indexOf("/ask/login") == -1 &&
config.url.indexOf("/generate/qrcode") == -1
) {
reduceToken(token);
}
} }
config.headers.set("source-client", "web") config.headers.set("source-client", "web")
return config; return config;
@ -32,6 +38,9 @@ httpReq.interceptors.request.use((config) => {
console.info("interceptors错误提示.request" + error) console.info("interceptors错误提示.request" + error)
return Promise.reject(error); return Promise.reject(error);
}) })
httpReq.interceptors.response.use((onFulfilled) => {
return onFulfilled;
})
function reduceToken(token: string) { function reduceToken(token: string) {
if (!token || token.split('.').length !== 3) { if (!token || token.split('.').length !== 3) {
@ -62,7 +71,7 @@ function reduceToken(token: string) {
console.log("updateTokendecodedToken:", decodedToken); console.log("updateTokendecodedToken:", decodedToken);
const endTime = decodedToken.exp; const endTime = decodedToken.exp;
console.log('距离过期' + (((endTime as number) - (Date.now() / 1000)) / 60 / 60 + '小时')) console.log('距离过期' + (((endTime as number) - (Date.now() / 1000)) / 60 / 60 + '小时'))
if (((endTime as number) - (Date.now() / 1000)) / 60 / 60 / 24 < 3) { if (((endTime as number) - (Date.now() / 1000)) / 60 / 60 < 3) {
// 小于3小时更新toke3小时或12小时不动过期、内不动 // 小于3小时更新toke3小时或12小时不动过期、内不动
refreshTokenAPI().then(res => { refreshTokenAPI().then(res => {
if (res.data.status.success) { if (res.data.status.success) {

View File

@ -1,5 +1,6 @@
import dayjs, { Dayjs, isDayjs } from "dayjs"; import dayjs, { Dayjs, isDayjs } from "dayjs";
import utc from "dayjs/plugin/utc"; import utc from "dayjs/plugin/utc";
import {solarToLunar} from "lunar-calendar";
dayjs.extend(utc) dayjs.extend(utc)
const DATE_FORMAT = "YYYY-MM-DD" const DATE_FORMAT = "YYYY-MM-DD"
// 到秒没啥意义 // 到秒没啥意义
@ -236,8 +237,26 @@ function dayjsWeek(day: Dayjs): string {
} }
return "" return ""
} }
function lunarDateShow(date:Date){
const lunarDate = solarToLunar(
date.getFullYear(),
date.getMonth() + 1,
date.getDate()
);
if(lunarDate.term){
return lunarDate.term;
}
if(lunarDate.lunarFestival && lunarDate.lunarMonthName.indexOf("闰")==-1){
return lunarDate.lunarFestival;
}
if(lunarDate.solarFestival){
return lunarDate.solarFestival
}
return lunarDate.lunarMonthName+lunarDate.lunarDayName;
}
export { export {
DATE_TIME_FORMAT, DATE_FORMAT, DATE_TIME_FORMAT_SIMPLE, DATE_TIME_FORMAT, DATE_FORMAT, DATE_TIME_FORMAT_SIMPLE,
dayStartUtcFormat, nextDayStartUtcFormat, betweenTime, dayStartUtcFormat, nextDayStartUtcFormat, betweenTime,
dateStartUtcFormat, nextDateStartUtcFormat, cronToChinese, dayjsWeek dateStartUtcFormat, nextDateStartUtcFormat, cronToChinese, dayjsWeek,lunarDateShow
} }