diff --git a/package-lock.json b/package-lock.json index 31615e1..19e8ce1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -36,6 +36,7 @@ "@types/react-virtualized": "^9.22.2", "@types/uuid": "^10.0.0", "@vercel/style-guide": "^5.0.1", + "cross-env": "^10.0.0", "eslint": "^8", "eslint-config-next": "14.2.29", "prettier": "3.0.3", @@ -958,6 +959,12 @@ "resolved": "https://registry.npmmirror.com/@emotion/unitless/-/unitless-0.7.5.tgz", "integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==" }, + "node_modules/@epic-web/invariant": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/@epic-web/invariant/-/invariant-1.0.0.tgz", + "integrity": "sha512-lrTPqgvfFQtR/eY/qkIzp98OGdNJu0m5ji3q/nJI8v3SXkRKEnWiOxMmbvcSoAIzv/cGiuvRy57k4suKQSAdwA==", + "dev": true + }, "node_modules/@eslint-community/eslint-utils": { "version": "4.4.0", "resolved": "https://registry.npmmirror.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", @@ -2579,10 +2586,27 @@ "toggle-selection": "^1.0.6" } }, + "node_modules/cross-env": { + "version": "10.0.0", + "resolved": "https://registry.npmmirror.com/cross-env/-/cross-env-10.0.0.tgz", + "integrity": "sha512-aU8qlEK/nHYtVuN4p7UQgAwVljzMg8hB4YK5ThRqD2l/ziSnryncPNn7bMLt5cFYsKVKBh8HqLqyCoTupEUu7Q==", + "dev": true, + "dependencies": { + "@epic-web/invariant": "^1.0.0", + "cross-spawn": "^7.0.6" + }, + "bin": { + "cross-env": "dist/bin/cross-env.js", + "cross-env-shell": "dist/bin/cross-env-shell.js" + }, + "engines": { + "node": ">=20" + } + }, "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmmirror.com/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "version": "7.0.6", + "resolved": "https://registry.npmmirror.com/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", @@ -8461,6 +8485,12 @@ "resolved": "https://registry.npmmirror.com/@emotion/unitless/-/unitless-0.7.5.tgz", "integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==" }, + "@epic-web/invariant": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/@epic-web/invariant/-/invariant-1.0.0.tgz", + "integrity": "sha512-lrTPqgvfFQtR/eY/qkIzp98OGdNJu0m5ji3q/nJI8v3SXkRKEnWiOxMmbvcSoAIzv/cGiuvRy57k4suKQSAdwA==", + "dev": true + }, "@eslint-community/eslint-utils": { "version": "4.4.0", "resolved": "https://registry.npmmirror.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", @@ -9704,10 +9734,20 @@ "toggle-selection": "^1.0.6" } }, + "cross-env": { + "version": "10.0.0", + "resolved": "https://registry.npmmirror.com/cross-env/-/cross-env-10.0.0.tgz", + "integrity": "sha512-aU8qlEK/nHYtVuN4p7UQgAwVljzMg8hB4YK5ThRqD2l/ziSnryncPNn7bMLt5cFYsKVKBh8HqLqyCoTupEUu7Q==", + "dev": true, + "requires": { + "@epic-web/invariant": "^1.0.0", + "cross-spawn": "^7.0.6" + } + }, "cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmmirror.com/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "version": "7.0.6", + "resolved": "https://registry.npmmirror.com/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "requires": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", diff --git a/package.json b/package.json index f518bfc..72783ee 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,7 @@ "private": true, "scripts": { "dev": "next dev", + "dev:staging": "cross-env NODE_ENV=production NEXT_PUBLIC_API_URL=https://staging.api.com next dev", "build": "next build", "start": "next start", "lint": "next lint" @@ -37,6 +38,7 @@ "@types/react-virtualized": "^9.22.2", "@types/uuid": "^10.0.0", "@vercel/style-guide": "^5.0.1", + "cross-env": "^10.0.0", "eslint": "^8", "eslint-config-next": "14.2.29", "prettier": "3.0.3", diff --git a/src/components/TeamMember.tsx b/src/components/TeamMember.tsx index 326d0c0..f411775 100644 --- a/src/components/TeamMember.tsx +++ b/src/components/TeamMember.tsx @@ -5,9 +5,15 @@ import {ActionType, ProColumns, ProTable} from "@ant-design/pro-components"; import {Params} from "next/dist/shared/lib/router/utils/route-matcher"; import {TeamMemberVO} from "@/components/type/Share.d"; import type {FilterValue, SorterResult, TableCurrentDataSource} from "antd/es/table/interface"; -import {allowAddTeamAPI, listTeamMemberAPI, quitTeamAPI, removeTeamAPI} from "@/components/service/Share"; +import { + allowAddTeamAPI, + listTaskAdminAPI, + listTeamMemberAPI, + quitTeamAPI, + removeTeamAPI +} from "@/components/service/Share"; -const TeamMember = (props: { taskId: string,closeOpen?: () => void,reloadData?: () => void }) => { +const TeamMember = (props: { taskId: string, closeOpen?: () => void, reloadData?: () => void }) => { const [open, setOpen] = useState(false); const onClose = () => { setOpen(false); @@ -21,23 +27,82 @@ const TeamMember = (props: { taskId: string,closeOpen?: () => void,reloadData?: const [buttonLoading, setButtonLoading] = React.useState(false); const [currentUser, setCurrentUser] = React.useState<{ username: string, adminFlag: boolean }>(); - const [page,setPage]=React.useState({pageSize:10, pageIndex:1}); + const [page, setPage] = React.useState({pageSize: 10, pageIndex: 1}); useEffect(() => { + let isMounted = true; // 防止组件卸载后设置状态 if (open) { - setLoading(true); - listTeamMemberAPI(props.taskId).then((res) => { - if (res.data.status.success) { - setUserList(res.data.data.splice(0, 10)) - setTotalUserList(res.data.data) - setTotalElement(res.data.data.length) - setTimeout(() => { - ref.current?.reload() - }, 100) + const fetchData = async () => { + try { + setLoading(true); + + const [teamRes, adminRes] = await Promise.all([ + listTeamMemberAPI(props.taskId), + listTaskAdminAPI(props.taskId,) + ]); + + if (!isMounted) return; // 检查组件是否仍挂载 + + // 处理团队成员数据 + if (teamRes.data?.status?.success) { + const teamData = [...teamRes.data.data]; // 复制数组避免污染原数据 + setUserList(teamData.slice(0, 10)); + setTotalUserList(teamData); + setTotalElement(teamData.length); + requestAnimationFrame(() => ref.current?.reload()); // 更安全的DOM更新方式 + } + + // 处理管理员数据 + if (adminRes.data?.status?.success) { + const message = localStorage.getItem('user-message'); + if (message) { + try { + const { username } = JSON.parse(message); + const isAdmin = adminRes.data.data.some( + user => user.username === username + ); + setCurrentUser({ username, adminFlag: isAdmin }); + } catch (e) { + console.error('用户信息解析错误', e); + } + } + } + } catch (err) { + console.error('数据加载失败', err); + // 这里可以添加错误状态设置(如 setError(true)) + } finally { + if (isMounted) setLoading(false); } - setLoading(false) - }) + }; + + fetchData(); + // setLoading(true); + // Promise.all([listTeamMemberAPI(props.taskId).then((res) => { + // if (res.data.status.success) { + // setUserList(res.data.data.splice(0, 10)) + // setTotalUserList(res.data.data) + // setTotalElement(res.data.data.length) + // setTimeout(() => { + // ref.current?.reload() + // }, 100) + // } + // }),listTaskAdminAPI(props.taskId).then(res => { + // if (res.data.status.success) { + // let message = localStorage.getItem('user-message') + // if (message) { + // let username = JSON.parse(message).username; + // const adminFlagList = res.data.data.filter(user => user.username == username); + // setCurrentUser({ + // username, + // adminFlag: adminFlagList.length > 0 + // }) + // } + // } + // })]).then(()=>{setLoading(false)}) } + return () => { + isMounted = false; + }; }, [open]) const removeTeam = (teamMember: TeamMemberVO) => { removeTeamAPI(teamMember).then(res => { @@ -63,7 +128,7 @@ const TeamMember = (props: { taskId: string,closeOpen?: () => void,reloadData?: allowAddTeamAPI({ userId: [teamMember.userId], taskId: props.taskId - }).then(res=>{ + }).then(res => { message.success("添加成功") setTotalUserList(res.data.data) setUserList(res.data.data.splice((page.pageIndex - 1) * (page.pageSize), page.pageSize)) @@ -88,7 +153,7 @@ const TeamMember = (props: { taskId: string,closeOpen?: () => void,reloadData?: width: 120, valueType: 'option', render: (node, entity) => { - // 管理员可展示提出,普通用户自己展示退出 + // 管理员可展示移出,普通用户自己展示退出 if (currentUser?.adminFlag && entity.userState == '1' && entity.username != currentUser.username) { return @@ -97,9 +162,11 @@ const TeamMember = (props: { taskId: string,closeOpen?: () => void,reloadData?: } else if (!currentUser?.adminFlag && entity.userState == '1' && entity.username != currentUser?.username) { return } else if (!currentUser?.adminFlag && entity.userState == '1' && entity.username == currentUser?.username) { - return + return } else if (entity.userState == '0') { - return + return } return undefined; }, @@ -146,8 +213,8 @@ const TeamMember = (props: { taskId: string,closeOpen?: () => void,reloadData?: sorter: SorterResult | SorterResult[], extra: TableCurrentDataSource) => { setPage({ - pageSize: pagination.pageSize||10, - pageIndex: pagination.current||1, + pageSize: pagination.pageSize || 10, + pageIndex: pagination.current || 1, }) setUserList(totalUserList.splice((pagination.current ?? 1 - 1) * (pagination.pageSize ?? 10), pagination.pageSize)) }} diff --git a/src/components/service/Share.tsx b/src/components/service/Share.tsx index d4c3dde..34c99c4 100644 --- a/src/components/service/Share.tsx +++ b/src/components/service/Share.tsx @@ -10,6 +10,10 @@ export const addTaskPassAPI= (data:ShareVO):Promise>> =>{ return httpReq.get(process.env.NEXT_PUBLIC_TODO_REQUEST_URL + `/task/team/member/list?taskId=${taskId}`) } +export const listTaskAdminAPI = (taskId: string):Promise>> => { + return httpReq.get(process.env.NEXT_PUBLIC_TODO_REQUEST_URL + `/task/team/admin/list?taskId=${taskId}`) +} + export const removeTeamAPI = (teamMember: TeamMemberVO):Promise>> =>{ return httpReq.post(process.env.NEXT_PUBLIC_TODO_REQUEST_URL + `/task/team/member/remove`,teamMember) } diff --git a/src/lib/login/service.ts b/src/lib/login/service.ts index 97cc471..e594ec5 100644 --- a/src/lib/login/service.ts +++ b/src/lib/login/service.ts @@ -10,4 +10,8 @@ export const generateQrcodeAPI = (data:{}) => { export const askLoginAPI = (data:{}):Promise>> =>{ return httpReq.post(process.env.NEXT_PUBLIC_SECURITY_REQUEST_URL + "/V2/wx/login/pc/ask/login", data) +} + +export const refreshTokenAPI = ():Promise>> =>{ + return httpReq.get(process.env.NEXT_PUBLIC_SECURITY_REQUEST_URL + "/V2/wx/login/refreshToken") } \ No newline at end of file diff --git a/src/ui/task/calendar/CalShow.tsx b/src/ui/task/calendar/CalShow.tsx index 6aa9836..5b37978 100644 --- a/src/ui/task/calendar/CalShow.tsx +++ b/src/ui/task/calendar/CalShow.tsx @@ -200,7 +200,7 @@ const CalShow: React.FC = () => { } const reloadData = () => { setOpen(false) - handleNavigate(expectedStartTime ? expectedStartTime.toDate() : date) + handleNavigate(date) } const handleSelectSlot = useCallback( ({start, end}: SlotInfo) => { @@ -341,30 +341,37 @@ const CalShow: React.FC = () => { // view 为天的时候类型为数组,index:0为当天 if ((current ? current : view) === "day" && Array.isArray(rangeLet)) { if (range.start.valueOf() > rangeLet[0].valueOf()) { - setRange({...range, start: rangeLet[0]}) + // setRange({...range, start: rangeLet[0]}) + range.start=rangeLet[0]; } else if (range.end.valueOf() < rangeLet[0].valueOf()) { - setRange({...range, end: rangeLet[0]}) + // setRange({...range, end: rangeLet[0]}) + range.end=rangeLet[0] } } // 为周的时候类型为数组,周一到周日七天 if ((current ? current : view) === "week" && Array.isArray(rangeLet)) { if (range.start.valueOf() > rangeLet[0].valueOf()) { - setRange({...range, start: rangeLet[0]}) + // setRange({...range, start: rangeLet[0]}) + range.start=rangeLet[0]; } if (range.end.valueOf() < rangeLet[6].valueOf()) { - setRange({...range, end: rangeLet[6]}) + // setRange({...range, end: rangeLet[6]}) + range.end=rangeLet[6] } } - // 为周的时候类型为对象 + // 为月的时候类型为对象 if ((current ? current : view) === "month" && rangeLet && !Array.isArray(rangeLet)) { if (range.start.valueOf() > rangeLet.start.valueOf()) { - setRange({...range, start: rangeLet.start}) + // setRange({...range, start: rangeLet.start}) + range.start = rangeLet.start } if (range.end.valueOf() < rangeLet.end.valueOf()) { - setRange({...range, end: rangeLet.end}) + // setRange({...range, end: rangeLet.end}) + range.end = rangeLet.end + } } - + setRange({...range}) } return
{open && { config.headers.Authorization = `Bearer ${token}`; reduceToken(token) } - config.headers.set("source-client","web") + config.headers.set("source-client", "web") return config; }, (error) => { console.info("interceptors错误提示.request" + error) return Promise.reject(error); }) -function reduceToken(token:string) { -// 如果有效期还不到3小时,增更新token有效期到5小时 + +function reduceToken(token: string) { + if (!token || token.split('.').length !== 3) { + console.error("Token 格式错误,应为 header.payload.signature"); + return; + } + + function decodeBase64(standardBase64: string) { + // 解码 Base64 并解析 JSON + return decodeURIComponent( + atob(standardBase64) + .split('') + .map(c => '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2)) + .join('') + ); + } + + try { + // 2. 提取 payload 部分 + const payloadBase64 = token.split('.')[1]; + const standardBase64 = payloadBase64 + .replace(/-/g, '+') + .replace(/_/g, '/') + .padEnd(payloadBase64.length + (4 - payloadBase64.length % 4) % 4, '='); + const decodedStr = decodeBase64(standardBase64); + // 6. 解析 JSON + const decodedToken = JSON.parse(decodedStr); + console.log("updateToken:decodedToken:", decodedToken); + const endTime = decodedToken.exp; + console.log('距离过期' + (((endTime as number) - (Date.now() / 1000)) / 60 / 60 + '小时')) + if (((endTime as number) - (Date.now() / 1000)) / 60 / 60 / 24 < 3) { + // 小于3小时更新toke3小时或12小时不动过期、内不动 + refreshTokenAPI().then(res=>{ + if (res.data.status.success){ + localStorage.setItem('platform-security', res.data.data) + } + }) + } + } catch (error) { + console.error("解码 Token 失败:", error); + } } + // 响应前处理 httpReq.interceptors.response.use( (response) => { @@ -43,7 +84,7 @@ httpReq.interceptors.response.use( message.error('系统异常'); } else if (response.status >= 400 && response.status <= 500) { message.warning('无权限'); - }else if(response.data.status.success == false){ + } else if (response.data.status.success == false) { message.error(response.data.status.message); return Promise.reject(response.data.status.message); } @@ -59,7 +100,7 @@ httpReq.interceptors.response.use( message.error('系统异常'); } else if (error.response.status >= 400 && error.response.status <= 500) { message.warning('无权限'); - window.location.href = `/login?callBack=${encodeURIComponent(window.location.pathname+window.location.search)}`; + window.location.href = `/login?callBack=${encodeURIComponent(window.location.pathname + window.location.search)}`; } // switch (error.response.status) { // case 401: