mirror of
https://gitee.com/JavaLionLi/plus-ui.git
synced 2026-09-13 07:43:43 +08:00
fix: 优化前端安全与生命周期清理
- 登录页记住我仅保存用户名和 rememberMe,并清理历史明文密码缓存 - 修复下载 Blob 链接释放时机,提升浏览器兼容性 - 关闭消息推送时清理事件处理器并阻止重复重连 - 稳定流程审批组件 variables 默认值,避免无效重复加载 - 忽略 IDE 本地配置文件 - 删除未使用的 RightToolbar 组件
This commit is contained in:
parent
cf53df4f4a
commit
d5e1b42ad1
2
.gitignore
vendored
2
.gitignore
vendored
@ -6,3 +6,5 @@ src/.umi
|
||||
src/.umi-production
|
||||
.env.local
|
||||
*.log
|
||||
.idea/
|
||||
*.iml
|
||||
|
||||
@ -1,109 +0,0 @@
|
||||
import { MenuOutlined, ReloadOutlined, SearchOutlined } from '@ant-design/icons';
|
||||
import { Button, Checkbox, Popover, Space, Tooltip } from 'antd';
|
||||
import { useEffect, useMemo } from 'react';
|
||||
|
||||
export interface RightToolbarColumn {
|
||||
key: string;
|
||||
label: string;
|
||||
visible?: boolean;
|
||||
}
|
||||
|
||||
export interface RightToolbarProps {
|
||||
showSearch?: boolean;
|
||||
onShowSearchChange?: (value: boolean) => void;
|
||||
search?: boolean;
|
||||
columns?: RightToolbarColumn[];
|
||||
onColumnsChange?: (columns: RightToolbarColumn[]) => void;
|
||||
onRefresh?: () => void;
|
||||
storageKey?: string;
|
||||
gutter?: number;
|
||||
}
|
||||
|
||||
function readStorage(storageKey?: string) {
|
||||
if (!storageKey) {
|
||||
return undefined;
|
||||
}
|
||||
try {
|
||||
const text = localStorage.getItem(storageKey);
|
||||
return text ? (JSON.parse(text) as Record<string, boolean>) : undefined;
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
function writeStorage(storageKey: string | undefined, columns: RightToolbarColumn[]) {
|
||||
if (!storageKey) {
|
||||
return;
|
||||
}
|
||||
const state = Object.fromEntries(columns.map(column => [column.key, column.visible !== false]));
|
||||
localStorage.setItem(storageKey, JSON.stringify(state));
|
||||
}
|
||||
|
||||
export default function RightToolbar({
|
||||
showSearch = true,
|
||||
onShowSearchChange,
|
||||
search = true,
|
||||
columns,
|
||||
onColumnsChange,
|
||||
onRefresh,
|
||||
storageKey,
|
||||
gutter = 10
|
||||
}: RightToolbarProps) {
|
||||
const checkedKeys = useMemo(
|
||||
() => (columns || []).filter(column => column.visible !== false).map(column => column.key),
|
||||
[columns]
|
||||
);
|
||||
|
||||
// biome-ignore lint/correctness/useExhaustiveDependencies: storage restore should run when storage key changes, not on every parent columns render.
|
||||
useEffect(() => {
|
||||
const stored = readStorage(storageKey);
|
||||
if (!stored || !columns?.length) {
|
||||
return;
|
||||
}
|
||||
const nextColumns = columns.map(column => ({ ...column, visible: stored[column.key] ?? column.visible ?? true }));
|
||||
onColumnsChange?.(nextColumns);
|
||||
}, [storageKey]);
|
||||
|
||||
const changeColumns = (keys: string[]) => {
|
||||
const nextColumns = (columns || []).map(column => ({ ...column, visible: keys.includes(column.key) }));
|
||||
writeStorage(storageKey, nextColumns);
|
||||
onColumnsChange?.(nextColumns);
|
||||
};
|
||||
|
||||
return (
|
||||
<Space size={8} className="right-toolbar-react" style={{ marginRight: gutter / 2 }}>
|
||||
{search && (
|
||||
<Tooltip title={showSearch ? '隐藏搜索' : '显示搜索'}>
|
||||
<Button shape="circle" icon={<SearchOutlined />} onClick={() => onShowSearchChange?.(!showSearch)} />
|
||||
</Tooltip>
|
||||
)}
|
||||
<Tooltip title="刷新">
|
||||
<Button shape="circle" icon={<ReloadOutlined />} onClick={onRefresh} />
|
||||
</Tooltip>
|
||||
{columns?.length ? (
|
||||
<Popover
|
||||
placement="bottomRight"
|
||||
trigger="click"
|
||||
content={
|
||||
<div className="right-toolbar-column-popover">
|
||||
<div className="right-toolbar-column-title">显示/隐藏列</div>
|
||||
<Checkbox.Group value={checkedKeys} onChange={values => changeColumns(values as string[])}>
|
||||
<Space direction="vertical" size={6}>
|
||||
{columns.map(column => (
|
||||
<Checkbox key={column.key} value={column.key}>
|
||||
{column.label}
|
||||
</Checkbox>
|
||||
))}
|
||||
</Space>
|
||||
</Checkbox.Group>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<Tooltip title="显示/隐藏列">
|
||||
<Button shape="circle" icon={<MenuOutlined />} />
|
||||
</Tooltip>
|
||||
</Popover>
|
||||
) : null}
|
||||
</Space>
|
||||
);
|
||||
}
|
||||
@ -44,11 +44,13 @@ interface SubmitVerifyProps {
|
||||
onSubmitted: () => void;
|
||||
}
|
||||
|
||||
const emptyVariables: Record<string, unknown> = {};
|
||||
|
||||
function buttonVisible(task?: FlowTaskVO, code?: string) {
|
||||
return !!task?.buttonList?.find(item => item.code === code && item.show);
|
||||
}
|
||||
|
||||
export default function SubmitVerify({ open, taskId, variables = {}, onOpenChange, onSubmitted }: SubmitVerifyProps) {
|
||||
export default function SubmitVerify({ open, taskId, variables = emptyVariables, onOpenChange, onSubmitted }: SubmitVerifyProps) {
|
||||
const [approveForm] = Form.useForm<{
|
||||
message?: string;
|
||||
messageType?: string[];
|
||||
|
||||
@ -40,10 +40,9 @@ function getRememberedLogin() {
|
||||
if (typeof window === 'undefined') return { rememberMe: false };
|
||||
const rememberMe = localStorage.getItem('rememberMe') === 'true';
|
||||
const username = localStorage.getItem('username') || undefined;
|
||||
const password = localStorage.getItem('password') || undefined;
|
||||
return {
|
||||
username: rememberMe ? username : 'admin',
|
||||
password: rememberMe ? password : 'admin123',
|
||||
password: rememberMe ? undefined : 'admin123',
|
||||
rememberMe
|
||||
};
|
||||
}
|
||||
@ -172,13 +171,12 @@ export default function Login() {
|
||||
try {
|
||||
if (values.rememberMe) {
|
||||
localStorage.setItem('username', String(values.username || ''));
|
||||
localStorage.setItem('password', String(values.password || ''));
|
||||
localStorage.setItem('rememberMe', 'true');
|
||||
} else {
|
||||
localStorage.removeItem('username');
|
||||
localStorage.removeItem('password');
|
||||
localStorage.removeItem('rememberMe');
|
||||
}
|
||||
localStorage.removeItem('password');
|
||||
const res = await login({
|
||||
username: values.username,
|
||||
password: values.password,
|
||||
|
||||
@ -32,8 +32,11 @@ export function saveBlob(blob: Blob, fileName: string) {
|
||||
const link = document.createElement('a');
|
||||
link.href = objectUrl;
|
||||
link.download = fileName;
|
||||
link.style.display = 'none';
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
window.URL.revokeObjectURL(objectUrl);
|
||||
document.body.removeChild(link);
|
||||
window.setTimeout(() => window.URL.revokeObjectURL(objectUrl), 0);
|
||||
}
|
||||
|
||||
export async function saveValidatedBlob(blob: Blob, fileName: string) {
|
||||
|
||||
@ -13,6 +13,7 @@ let eventSource: EventSource | undefined;
|
||||
let webSocket: WebSocket | undefined;
|
||||
let reconnectTimer: number | undefined;
|
||||
let reconnectAttempts = 0;
|
||||
let pushClosed = true;
|
||||
|
||||
function messageEnabled() {
|
||||
return appEnv.messageEnabled;
|
||||
@ -91,6 +92,7 @@ function clearReconnectTimer() {
|
||||
}
|
||||
|
||||
function scheduleReconnect(connect: () => void, maxRetries: number, delay: number) {
|
||||
if (pushClosed) return;
|
||||
if (reconnectAttempts >= maxRetries) return;
|
||||
reconnectAttempts += 1;
|
||||
clearReconnectTimer();
|
||||
@ -99,6 +101,7 @@ function scheduleReconnect(connect: () => void, maxRetries: number, delay: numbe
|
||||
|
||||
function initSsePush(path: string) {
|
||||
const connect = () => {
|
||||
if (pushClosed) return;
|
||||
eventSource?.close();
|
||||
eventSource = new EventSource(buildHttpUrl(path));
|
||||
eventSource.onopen = () => {
|
||||
@ -117,6 +120,7 @@ function initSsePush(path: string) {
|
||||
|
||||
function initWsPush(path: string) {
|
||||
const connect = () => {
|
||||
if (pushClosed) return;
|
||||
webSocket?.close();
|
||||
webSocket = new WebSocket(buildWsUrl(path));
|
||||
webSocket.onopen = () => {
|
||||
@ -149,6 +153,7 @@ export async function initMessageBox() {
|
||||
export function initPush() {
|
||||
closePush();
|
||||
if (!messageEnabled()) return;
|
||||
pushClosed = false;
|
||||
if (appEnv.messageTransport === 'websocket') {
|
||||
initWsPush(appEnv.messagePath);
|
||||
return;
|
||||
@ -157,7 +162,19 @@ export function initPush() {
|
||||
}
|
||||
|
||||
export function closePush() {
|
||||
pushClosed = true;
|
||||
clearReconnectTimer();
|
||||
if (webSocket) {
|
||||
webSocket.onopen = null;
|
||||
webSocket.onmessage = null;
|
||||
webSocket.onclose = null;
|
||||
webSocket.onerror = null;
|
||||
}
|
||||
if (eventSource) {
|
||||
eventSource.onopen = null;
|
||||
eventSource.onmessage = null;
|
||||
eventSource.onerror = null;
|
||||
}
|
||||
eventSource?.close();
|
||||
webSocket?.close();
|
||||
eventSource = undefined;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user