Compare commits

...

7 Commits

Author SHA1 Message Date
1708-huayu f6e4b6400a fix:处理邮箱登录token处理 2025-01-17 19:23:02 +08:00
1708-huayu 14c8cf2364 fix:发版ok 2025-01-16 19:10:45 +08:00
1708-huayu 76cd767afa fix:发版备份 2025-01-15 19:36:36 +08:00
1708-huayu 7c5ddacbaa fix:跳转mobile 2025-01-14 19:49:19 +08:00
1708-huayu 5639c6e0f0 fix:mobile 2025-01-13 19:33:53 +08:00
1708-huayu e01989fdfd fix:树中展示子任务 2025-01-06 18:55:20 +08:00
1708-huayu aa443207f4 fix:修改状态未更新 2025-01-02 19:03:47 +08:00
14 changed files with 5855 additions and 54 deletions

View File

@ -2,5 +2,8 @@
# NEXT_PUBLIC_TODO_REQUEST_URL=http://localhost:8092 # NEXT_PUBLIC_TODO_REQUEST_URL=http://localhost:8092
# NEXT_PUBLIC_SECURITY_REQUEST_URL=http://localhost:8091 # NEXT_PUBLIC_SECURITY_REQUEST_URL=http://localhost:8091
NEXT_PUBLIC_TODO_REQUEST_URL=http://localhost:80/todo-server # NEXT_PUBLIC_TODO_REQUEST_URL=http://localhost:80/todo-server
NEXT_PUBLIC_SECURITY_REQUEST_URL=http://localhost:80/security-server # NEXT_PUBLIC_SECURITY_REQUEST_URL=http://localhost:80/security-server
NEXT_PUBLIC_TODO_REQUEST_URL=http://www.huaruyu.com/todo-server
NEXT_PUBLIC_SECURITY_REQUEST_URL=http://www.huaruyu.com/security-server

15
docker/deploy.md Normal file
View File

@ -0,0 +1,15 @@
```shell
scp -r out/ shixiaohua@10.104.11.99:/home/shixiaohua/docker/todo-web
```
docker操作
```shell
docker stop task-manager-nginx
docker rm task-manager-nginx
docker rmi task-manager-nginx
docker build -t task-manager-nginx .
# docker run -d -p 3001:3001 --network task-manager --restart unless-stopped -v ./out:/usr/share/nginx/html --name task-manager-nginx task-manager-nginx
docker run -d -p 3001:80 --network task-manager --restart unless-stopped -v ./out:/usr/share/nginx/html --name task-manager-nginx task-manager-nginx
```

View File

@ -15,12 +15,14 @@ http {
default_type application/octet-stream; default_type application/octet-stream;
# HTTP 服务器监听端口 # HTTP 服务器监听端口
server { server {
listen 3001; listen 80;
# 启用 ETag Nginx 会为每个资源生成一个唯一的 ETag 当资源更新时ETag 值会改变。
etag on;
# 设置允许跨域的域,* 表示允许任何域,也可以设置特定的域,has been blocked by CORS policy: The 'Access-Control-Allow-Origin' header contains multiple values '*, *', but only one is allowed. # 设置允许跨域的域,* 表示允许任何域,也可以设置特定的域,has been blocked by CORS policy: The 'Access-Control-Allow-Origin' header contains multiple values '*, *', but only one is allowed.
add_header 'Access-Control-Allow-Origin' '*'; add_header 'Access-Control-Allow-Origin' '*';
# 允许的方法 # 允许的方法
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS' always; add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS' always;
# 允许的头信息字段 # 允许的头信息字段
add_header 'Access-Control-Allow-Headers' 'User-Agent,Keep-Alive,Content-Type,Authorization,Origin' always; add_header 'Access-Control-Allow-Headers' 'User-Agent,Keep-Alive,Content-Type,Authorization,Origin' always;
# 缓存时间 # 缓存时间
@ -42,6 +44,16 @@ 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 ^~ /mobile/ {
# index index.html index.htm;
# try_files $uri $uri.html $uri/ =404;
alias /usr/share/nginx/html/mobile/;
index index.html index.htm;
try_files $uri $uri/ /mobile/index.html;
}
location ^~ /todo-server/ { location ^~ /todo-server/ {
# 预检请求的处理 # 预检请求的处理
if ($request_method = 'OPTIONS') { if ($request_method = 'OPTIONS') {

5744
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -14,11 +14,13 @@
"@tailwindcss/forms": "^0.5.7", "@tailwindcss/forms": "^0.5.7",
"antd": "^5.16.1", "antd": "^5.16.1",
"axios": "^1.6.8", "axios": "^1.6.8",
"dayjs": "^1.11.13",
"js-cookie": "^3.0.5", "js-cookie": "^3.0.5",
"next": "14.1.3", "next": "14.1.3",
"postcss": "8.4.31", "postcss": "8.4.31",
"react": "^18", "react": "^18",
"react-big-calendar": "^1.12.2", "react-big-calendar": "^1.12.2",
"react-device-detect": "^2.2.3",
"react-dom": "^18", "react-dom": "^18",
"sass": "^1.77.3", "sass": "^1.77.3",
"tailwindcss": "3.3.3" "tailwindcss": "3.3.3"
@ -34,6 +36,7 @@
"eslint-config-next": "14.1.3", "eslint-config-next": "14.1.3",
"prettier": "3.0.3", "prettier": "3.0.3",
"prettier-plugin-tailwindcss": "0.5.4", "prettier-plugin-tailwindcss": "0.5.4",
"typescript": "^5" "typescript": "^5",
"react-beautiful-dnd": "^13.1.8"
} }
} }

View File

@ -1,6 +1,5 @@
import type {Metadata} from "next"; import type {Metadata} from "next";
import "@/ui/globals.css"; import "@/ui/globals.css";
import Script from "next/script";
export const metadata: Metadata = { export const metadata: Metadata = {
title: "任务管理", title: "任务管理",
@ -18,8 +17,6 @@ export default function RootLayout({
}>) { }>) {
return ( return (
<html> <html>
<Script src="https://cdn.jsdelivr.net/npm/dayjs@1/dayjs.min.js"/>
<Script src="https://cdn.jsdelivr.net/npm/dayjs@1/locale/zh-cn.js"/>
<head> <head>
<title></title> <title></title>
<link rel="icon" href="/favicon.ico"/> <link rel="icon" href="/favicon.ico"/>

View File

@ -2,11 +2,20 @@
import { useRouter} from "next/navigation"; import { useRouter} from "next/navigation";
import dayjs from "dayjs"; import dayjs from "dayjs";
import {useEffect} from "react"; import {useEffect} from "react";
import { isMobile } from 'react-device-detect';
export default function Home() { export default function Home() {
const {replace} = useRouter(); const {replace} = useRouter();
useEffect(()=>{ useEffect(()=>{
if(localStorage.getItem('platform-security')){
if (isMobile){
replace('/mobile/')
}else {
replace("/task/project")
}
}else {
replace("/login") replace("/login")
}
},[]) },[])
dayjs.locale('zh-cn') dayjs.locale('zh-cn')
return ( return (

View File

@ -13,8 +13,31 @@ import {DataType, ResponseVO, ResultPage} from "@/lib/definitions";
export default function Layout({children}: { children: React.ReactNode }) { export default function Layout({children}: { children: React.ReactNode }) {
const [resultDataTypeList, setResultDataTypeList] = useState<DataType[]>([]); const [resultDataTypeList, setResultDataTypeList] = useState<DataType[]>([]);
const [loadingState,setLoadingState] =useState(true) const [loadingState,setLoadingState] =useState(true)
const data = useContext(LocalContext);
console.log('data',data);
// 如果有pid,在前端过滤(防止中间数据不满足条件,导致子数据丢失),
// 无pid在后端过滤防止数据量过大
var pid = useSearchParams().get('pid');
console.log('pid!=null',pid!=null);
const refreshDate = (): void => { const refreshDate = (): void => {
const leftUp:{name:string,operateType:string,value:string|number|boolean}[] = []
setLoadingState(true) setLoadingState(true)
if (pid!=null) {
leftUp.push({name:"pid",value:pid,operateType:"="},
{name:'TREE-FILTER',value:"true",operateType: "TREE-FILTER"},
{name:'ALL-CHILD',value:"true",operateType: "ALL-CHILD"},
);
}else {
if (data.taskState.length>0){
leftUp.push({name:"state",value:data.taskState,operateType:"IN"});
}
if (data.expectedStartTime.length>0){
const parse = JSON.parse(data.expectedStartTime);
leftUp.push(...parse);
}
}
getTaskTreeResult(JSON.stringify({ getTaskTreeResult(JSON.stringify({
pageSize:1000, pageSize:1000,
pageNumber:1, pageNumber:1,
@ -44,28 +67,6 @@ export default function Layout({children}: { children: React.ReactNode }) {
document.getElementById('tenLeft').style.fontSize = divHeight/6*4 + 'px'; document.getElementById('tenLeft').style.fontSize = divHeight/6*4 + 'px';
refreshDate() refreshDate()
}, [useContext(LocalContext)]); }, [useContext(LocalContext)]);
const data = useContext(LocalContext);
const leftUp:{name:string,operateType:string,value:string|number|boolean}[] = []
var pid = useSearchParams().get('pid');
// 如果有pid,在前端过滤(防止中间数据不满足条件,导致子数据丢失),
// 无pid在后端过滤防止数据量过大
console.log('data',data);
console.log('pid!=null',pid!=null);
if (pid!=null) {
leftUp.push({name:"pid",value:pid,operateType:"="},
{name:'TREE-FILTER',value:"true",operateType: "TREE-FILTER"},
{name:'ALL-CHILD',value:"true",operateType: "ALL-CHILD"},
);
}else {
if (data.taskState.length>0){
leftUp.push({name:"state",value:data.taskState,operateType:"IN"});
}
if (data.expectedStartTime.length>0){
const parse = JSON.parse(data.expectedStartTime);
leftUp.push(...parse);
}
}
return ( return (
<div> <div>
<div className='firstRow' style={{display: 'flex'}}> <div className='firstRow' style={{display: 'flex'}}>

View File

@ -140,7 +140,7 @@ export const taskStateList: DictType[] = [
// { // {
// id: 6, // id: 6,
// code: '6', // code: '6',
// name: '排期中', // name: '关闭',
// order: 6, // order: 6,
// color: 'red' // color: 'red'
// }, // },
@ -173,6 +173,7 @@ export enum OPERATION_BUTTON_TYPE {
UPDATE, UPDATE,
DELETE, DELETE,
COMPLETE, COMPLETE,
SHOW_TREE,
SHOW_FOUR, SHOW_FOUR,
SHOW_CALENDAR, SHOW_CALENDAR,
ADD, ADD,

View File

@ -1,16 +1,10 @@
'use client' 'use client'
import { import {
AlipayOutlined,
LockOutlined, MailOutlined, LockOutlined, MailOutlined,
MobileOutlined,
TaobaoOutlined,
UserOutlined, UserOutlined,
WeiboOutlined,
} from '@ant-design/icons'; } from '@ant-design/icons';
import { import {
LoginForm,
LoginFormPage, LoginFormPage,
ProConfigProvider,
ProFormCaptcha, ProFormCaptcha,
ProFormCheckbox, ProFormCheckbox,
ProFormText, ProFormText,
@ -22,8 +16,8 @@ import {useState} from 'react';
import {CaptchaLoginSuccess, LoginObject} from "@/lib/login/definitions"; import {CaptchaLoginSuccess, LoginObject} from "@/lib/login/definitions";
import {httpReq} from "@/utils/axiosReq"; import {httpReq} from "@/utils/axiosReq";
import {useRouter} from 'next/navigation' import {useRouter} from 'next/navigation'
import {use} from 'react'; import { isMobile } from 'react-device-detect';
import Loading from "@/app/loading"; import Cookies from "js-cookie";
type LoginType = 'email' | 'account'; type LoginType = 'email' | 'account';
@ -37,6 +31,11 @@ const iconStyles: CSSProperties = {
export default function Page() { export default function Page() {
const [loaded, setLoaded] = useState(false); const [loaded, setLoaded] = useState(false);
useLayoutEffect(() => { useLayoutEffect(() => {
if (isMobile){
console.log("手机端")
}else {
console.log("PC端")
}
console.log('页面所有资源加载完毕'); console.log('页面所有资源加载完毕');
setLoaded(true); setLoaded(true);
}) })
@ -53,7 +52,11 @@ export default function Page() {
content: "使用帐号" + captchaLoginSuccess.username + "登录成功" content: "使用帐号" + captchaLoginSuccess.username + "登录成功"
}) })
localStorage.setItem('platform-security', captchaLoginSuccess.token) localStorage.setItem('platform-security', captchaLoginSuccess.token)
if (isMobile){
window.location.href = '/mobile/'
}else {
router.push('/task/project') router.push('/task/project')
}
setOpen(false) setOpen(false)
setLoading(false) setLoading(false)
} }
@ -110,9 +113,13 @@ export default function Page() {
// 删除名为 'platform-security' 的Cookie // 删除名为 'platform-security' 的Cookie
// Cookies.remove('platform-security'); // Cookies.remove('platform-security');
// 设置一个有效期为7天的Cookie // 设置一个有效期为7天的Cookie
// Cookies.set('platform-security', response.data.data, { expires: 7 }); Cookies.set('platform-security', response.data.data, { expires: 7 });
// 登录成功,跳转到首页或者回调 // 登录成功,跳转到首页或者回调
if (isMobile){
window.location.href = '/mobile/'
}else {
router.push('/task/project') router.push('/task/project')
}
} else { } else {
messageApi.open({ messageApi.open({
type: 'error', type: 'error',
@ -133,14 +140,18 @@ export default function Page() {
setCaptchaLoginSuccessList(response.data.data) setCaptchaLoginSuccessList(response.data.data)
setOpen(true) setOpen(true)
} else { } else {
localStorage.setItem('platform-security', response.data.data) localStorage.setItem('platform-security', response.data.data[0].token)
// 删除名为 'platform-security' 的Cookie // 删除名为 'platform-security' 的Cookie
// Cookies.remove('platform-security'); // Cookies.remove('platform-security');
// 设置一个有效期为7天的Cookie // 设置一个有效期为7天的Cookie
// Cookies.set('platform-security', response.data.data, { expires: 7 }); Cookies.set('platform-security', response.data.data[0].token, { expires: 7 });
// 登录成功,跳转到首页或者回调 // 登录成功,跳转到首页或者回调
if (isMobile){
window.location.href = '/mobile/'
}else {
router.push('/task/project') router.push('/task/project')
} }
}
} else { } else {
messageApi.open({ messageApi.open({
type: 'error', type: 'error',

View File

@ -110,6 +110,10 @@ class OperationButton extends React.Component<OperationButtonProps, OperationMod
}} }}
><a></a></Popconfirm>, ><a></a></Popconfirm>,
}, },
{
key: OPERATION_BUTTON_TYPE.SHOW_TREE,
label: <Link href={"/task/project?pid=" + this.props.itemId}></Link>,
},
{ {
key: OPERATION_BUTTON_TYPE.SHOW_FOUR, key: OPERATION_BUTTON_TYPE.SHOW_FOUR,
label: <Link href={"/task/four?pid=" + this.props.itemId}></Link>, label: <Link href={"/task/four?pid=" + this.props.itemId}></Link>,

View File

@ -7,6 +7,7 @@ import '@/ui/task/TitleOperation.modules.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";
interface TitleOperationProps { interface TitleOperationProps {
setTaskState: (value: string) => void; setTaskState: (value: string) => void;
@ -21,6 +22,7 @@ export const TitleOperation: React.FC<TitleOperationProps> = ({
}: TitleOperationProps) => { }: TitleOperationProps) => {
const {replace} = useRouter(); const {replace} = useRouter();
console.log('usePathname()', usePathname()); console.log('usePathname()', usePathname());
console.log('useSearchParams()', useSearchParams().get('pid'));
const data = useContext(LocalContext); const data = useContext(LocalContext);
const {RangePicker} = DatePicker; const {RangePicker} = DatePicker;
const expectStartTimeParseResult: RequestDateType[] = data.expectedStartTime.length > 0 ? JSON.parse(data.expectedStartTime) : [undefined, undefined] const expectStartTimeParseResult: RequestDateType[] = data.expectedStartTime.length > 0 ? JSON.parse(data.expectedStartTime) : [undefined, undefined]

View File

@ -1,7 +1,6 @@
'use client' 'use client'
import React, {useCallback, useContext, useEffect, useMemo, useRef, useState} from "react"; import React, {useCallback, useContext, useEffect, useMemo, useRef, useState} from "react";
import {Calendar, dayjsLocalizer, Event, SlotInfo, View} from 'react-big-calendar' import {Calendar, dayjsLocalizer, Event, SlotInfo, View} from 'react-big-calendar'
// https://day.js.org/docs/zh-CN/get-set/get-set
import dayjs, {Dayjs} from 'dayjs' import dayjs, {Dayjs} from 'dayjs'
import 'react-big-calendar/lib/css/react-big-calendar.css' import 'react-big-calendar/lib/css/react-big-calendar.css'
import 'react-big-calendar/lib/sass/styles.scss' import 'react-big-calendar/lib/sass/styles.scss'
@ -40,7 +39,7 @@ const CalShow: React.FC = () => {
start: dayjs(date).startOf('week').toDate(), start: dayjs(date).startOf('week').toDate(),
end: dayjs(date).endOf('week').toDate() end: dayjs(date).endOf('week').toDate()
}); });
const [state, setState] = useState<string>(useContext(LocalContext).taskState); const state=useContext(LocalContext).taskState;
const handleViewChange = (newView: View) => { const handleViewChange = (newView: View) => {
setView(newView); setView(newView);

View File

@ -18,6 +18,8 @@ import {DetailModelForm} from "@/ui/task/project/DetailModelForm";
import OperationButton from "@/ui/task/OperationButton"; import OperationButton from "@/ui/task/OperationButton";
import dayjs from "dayjs"; import dayjs from "dayjs";
import '@/ui/task/project/TreeTablePro.modules.css' import '@/ui/task/project/TreeTablePro.modules.css'
import {useSearchParams} from "next/navigation";
const TreeTablePro: React.FC = () => { const TreeTablePro: React.FC = () => {
const actionRef = useRef<ActionType>(); const actionRef = useRef<ActionType>();
@ -26,7 +28,10 @@ const TreeTablePro: React.FC = () => {
const [filterChecked, setFilterChecked] = React.useState(true); const [filterChecked, setFilterChecked] = React.useState(true);
const [current,setCurrent] = React.useState(1); const [current,setCurrent] = React.useState(1);
const [pageSize,setPageSize] = React.useState(10); const [pageSize,setPageSize] = React.useState(10);
const pathPid = useSearchParams().get('pid');
const pid = pathPid?pathPid:'0';
const { RangePicker } = DatePicker; const { RangePicker } = DatePicker;
console.log("TreeTablePro",useSearchParams().get('pid'))
const columns: ProColumns<DataType>[] = [ const columns: ProColumns<DataType>[] = [
{ {
key:'code', key:'code',
@ -145,6 +150,10 @@ const TreeTablePro: React.FC = () => {
actionRef.current?.reload( false); actionRef.current?.reload( false);
}} /></>) }} /></>)
} }
useEffect(()=>{
actionRef.current?.reload( false)
},[useSearchParams()])
return ( return (
<ProTable<DataType> <ProTable<DataType>
columns={columns} columns={columns}
@ -154,8 +163,9 @@ const TreeTablePro: React.FC = () => {
console.log('request',params,params.keyword,sort, filter); console.log('request',params,params.keyword,sort, filter);
const searchList=[] const searchList=[]
if (switchChecked) { if (switchChecked) {
searchList.push({name:'pid',value:'0',operateType:"="},{name:'tree',value:'TRUE',operateType:"TREE"}) searchList.push({name:'tree',value:'TRUE',operateType:"TREE"})
} }
searchList.push({name:"pid",value:pid,operateType:"="})
if (filterChecked) { if (filterChecked) {
searchList.push({name:'tree',value:'TRUE',operateType:"TREE-FILTER"}) searchList.push({name:'tree',value:'TRUE',operateType:"TREE-FILTER"})
} }