feat:日历日期范围设置,刷新token,团队管理员设置

This commit is contained in:
assistant 2025-08-17 17:00:26 +08:00
parent 67d99b8e29
commit 2cc85e67dc
7 changed files with 205 additions and 40 deletions

52
package-lock.json generated
View File

@ -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",

View File

@ -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",

View File

@ -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 <Button key={`${entity.id}remove`} loading={buttonLoading} danger
onClick={(event) => removeTeam(entity)}></Button>
@ -97,9 +162,11 @@ const TeamMember = (props: { taskId: string,closeOpen?: () => void,reloadData?:
} else if (!currentUser?.adminFlag && entity.userState == '1' && entity.username != currentUser?.username) {
return <Button key={`${entity.id}team`} loading={buttonLoading} color='default'></Button>
} else if (!currentUser?.adminFlag && entity.userState == '1' && entity.username == currentUser?.username) {
return <Button key={`${entity.id}quit`} loading={buttonLoading} danger onClick={(event) => quitTeam(entity)}>退</Button>
return <Button key={`${entity.id}quit`} loading={buttonLoading} danger
onClick={(event) => quitTeam(entity)}>退</Button>
} else if (entity.userState == '0') {
return <Button key={`${entity.id}allow`} loading={buttonLoading} color='primary' onClick={(event) => allowAddTeam(entity)}></Button>
return <Button key={`${entity.id}allow`} loading={buttonLoading} color='primary'
onClick={(event) => allowAddTeam(entity)}></Button>
}
return undefined;
},
@ -146,8 +213,8 @@ const TeamMember = (props: { taskId: string,closeOpen?: () => void,reloadData?:
sorter: SorterResult<TeamMemberVO> | SorterResult<TeamMemberVO>[], extra:
TableCurrentDataSource<TeamMemberVO>) => {
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))
}}

View File

@ -10,6 +10,10 @@ export const addTaskPassAPI= (data:ShareVO):Promise<AxiosResponse<ResponseVO<Sha
export const listTeamMemberAPI = (taskId:string):Promise<AxiosResponse<ResponseVO<TeamMemberVO[]>>> =>{
return httpReq.get(process.env.NEXT_PUBLIC_TODO_REQUEST_URL + `/task/team/member/list?taskId=${taskId}`)
}
export const listTaskAdminAPI = (taskId: string):Promise<AxiosResponse<ResponseVO<TeamMemberVO[]>>> => {
return httpReq.get(process.env.NEXT_PUBLIC_TODO_REQUEST_URL + `/task/team/admin/list?taskId=${taskId}`)
}
export const removeTeamAPI = (teamMember: TeamMemberVO):Promise<AxiosResponse<ResponseVO<TeamMemberVO[]>>> =>{
return httpReq.post(process.env.NEXT_PUBLIC_TODO_REQUEST_URL + `/task/team/member/remove`,teamMember)
}

View File

@ -10,4 +10,8 @@ export const generateQrcodeAPI = (data:{}) => {
export const askLoginAPI = (data:{}):Promise<AxiosResponse<ResponseVO<AskLoginResult[]>>> =>{
return httpReq.post(process.env.NEXT_PUBLIC_SECURITY_REQUEST_URL + "/V2/wx/login/pc/ask/login",
data)
}
export const refreshTokenAPI = ():Promise<AxiosResponse<ResponseVO<string>>> =>{
return httpReq.get(process.env.NEXT_PUBLIC_SECURITY_REQUEST_URL + "/V2/wx/login/refreshToken")
}

View File

@ -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 <div className="App" style={{height: '90vh'}}>
{open && <DetailModelForm operationId={operationId}

View File

@ -1,5 +1,6 @@
import axios, {CancelTokenSource} from "axios";
import {message} from "antd";
import {refreshTokenAPI} from "@/lib/login/service";
export const httpReq = axios.create({
@ -24,17 +25,57 @@ httpReq.interceptors.request.use((config) => {
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("updateTokendecodedToken:", 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: