#
vincentlu
昨天 7853e55a785dc34d178889ff13708875269f1d43
#
1个文件已添加
1个文件已修改
396 ■■■■■ 已修改文件
zy-acs-flow/src/page/login/index.jsx 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
zy-acs-flow/src/page/login0/index.jsx 394 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zy-acs-flow/src/page/login/index.jsx
@@ -67,7 +67,7 @@
            </video>
            <Card sx={{
                width: 400,
                width: 380,
                marginTop: formPosition === 'middle' ? '6em' : 0,
                zIndex: 1,
                bgcolor: DEFAULT_THEME_MODE === 'light' ? '#fff' : '#121212',
zy-acs-flow/src/page/login0/index.jsx
New file
@@ -0,0 +1,394 @@
import React, { useState, useEffect, useMemo, useRef } from "react";
import { Box, Typography } from "@mui/material";
import { useLogin, useNotify } from "react-admin";
import { useLocation } from "react-router-dom";
import { SPA_NAME, SPA_VERSION } from "@/config/setting";
const asciiArt = `
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@   RRRRR      CCCCC      SSSSS   @@@@@@@
@@@@@@@   RR  RR   CC    CC   SS    S   @@@@@@@
@@@@@@@   RRRRR    CC         SSSSS     @@@@@@@
@@@@@@@   RR  RR   CC    CC        SS   @@@@@@@
@@@@@@@   RR   RR    CCCCC     SSSSS    @@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
`;
const promptTexts = {
    username: "Enter login ID: ",
    password: "Enter password: ",
};
const LABEL_COLOR = "#ff4cb2";
const VALUE_COLOR = "#7dff82";
const PROMPT_COLOR = "#46e46e";
const Login0 = () => {
    const login = useLogin();
    const notify = useNotify();
    const location = useLocation();
    const inputRef = useRef(null);
    const bootTime = useRef(Date.now());
    const [phase, setPhase] = useState("boot");
    const [inputValue, setInputValue] = useState("");
    const [history, setHistory] = useState([]);
    const [username, setUsername] = useState("");
    const [loading, setLoading] = useState(false);
    const [displayedPrompt, setDisplayedPrompt] = useState("");
    const [uptime, setUptime] = useState("0m 00s");
    const host = typeof window !== "undefined" ? window.location.host : "localhost";
    const platform = typeof navigator !== "undefined" ? navigator.platform : "RCS OS";
    const shellName = `${(SPA_NAME || "rcs").toLowerCase()}-shell`;
    const bootScript = useMemo(() => {
        const scanTime = new Date().toLocaleString();
        return [
            { text: `${host}@mystery-visitor:~$ neofetch`, tone: "command", delay: 300 },
            { text: "RCS Flow Console ready.", tone: "system", delay: 700 },
            { text: `Scan timestamp : ${scanTime}`, tone: "system", delay: 700 },
            { text: "Awaiting credentials ...", tone: "accent", delay: 800 },
        ];
    }, [host]);
    useEffect(() => {
        const id = setInterval(() => {
            const seconds = Math.floor((Date.now() - bootTime.current) / 1000);
            const minutes = Math.floor(seconds / 60);
            const remain = seconds % 60;
            setUptime(`${minutes}m ${remain.toString().padStart(2, "0")}s`);
        }, 1000);
        return () => clearInterval(id);
    }, []);
    useEffect(() => {
        if (phase !== "boot") {
            return;
        }
        const timers = [];
        let totalDelay = 0;
        bootScript.forEach((line, index) => {
            totalDelay += line.delay ?? 700;
            const timerId = setTimeout(() => {
                setHistory((prev) => [...prev, { text: line.text, tone: line.tone }]);
                if (index === bootScript.length - 1) {
                    const doneId = setTimeout(() => setPhase("username"), 400);
                    timers.push(doneId);
                }
            }, totalDelay);
            timers.push(timerId);
        });
        return () => {
            timers.forEach((timer) => clearTimeout(timer));
        };
    }, [phase, bootScript]);
    const systemInfo = useMemo(() => [
        { label: "Host", value: host || "localhost" },
        { label: "OS", value: platform },
        { label: "Shell", value: shellName },
        { label: "Version", value: SPA_VERSION || "1.0" },
        { label: "Theme", value: "homebrew console" },
        { label: "Uptime", value: uptime },
        { label: "Instruction", value: "Submit ID then password" },
    ], [host, platform, shellName, uptime]);
    useEffect(() => {
        if (phase !== "username" && phase !== "password") {
            setDisplayedPrompt("");
            return;
        }
        const text = promptTexts[phase];
        setDisplayedPrompt("");
        let index = 0;
        const timer = setInterval(() => {
            setDisplayedPrompt((prev) => prev + text[index]);
            index += 1;
            if (index >= text.length) {
                clearInterval(timer);
            }
        }, 35);
        return () => clearInterval(timer);
    }, [phase]);
    useEffect(() => {
        if ((phase === "username" || phase === "password") && !loading) {
            inputRef.current?.focus();
        }
    }, [phase, loading]);
    const pushHistory = (text, tone = "default") => {
        setHistory((prev) => [...prev, { text, tone }]);
    };
    const resetToUsername = (message) => {
        if (message) {
            pushHistory(message, "warning");
        }
        setPhase("username");
        setUsername("");
        setInputValue("");
    };
    const handleLogin = (payload) => {
        setLoading(true);
        setPhase("submitting");
        pushHistory(">> verifying credentials ...", "system");
        login(
            payload,
            location.state ? location.state.nextPathname : "/"
        )
            .then(() => {
                pushHistory(">> access granted. redirecting ...", "accent");
            })
            .catch((res) => {
                const { code, msg } = res || {};
                const message = msg || "Unknown error";
                if (code === 10003 || code === 10004) {
                    resetToUsername(`Error(${code}): ${message}`);
                } else if (code === 10001) {
                    setPhase("password");
                    setInputValue("");
                    pushHistory(`Error(${code}): ${message}`, "error");
                } else {
                    pushHistory(`Error: ${message}`, "error");
                    setPhase("password");
                    setInputValue("");
                }
                notify(message, { type: "error", messageArgs: { _: message } });
            })
            .finally(() => {
                setLoading(false);
            });
    };
    const handleAdvance = () => {
        if (loading) {
            return;
        }
        const trimmed = inputValue.trim();
        if (!trimmed) {
            pushHistory("Warning: input cannot be empty.", "warning");
            return;
        }
        if (phase === "username") {
            setUsername(trimmed);
            pushHistory(`${promptTexts.username}${trimmed}`, "command");
            setPhase("password");
            setInputValue("");
            return;
        }
        if (phase === "password") {
            pushHistory(`${promptTexts.password}${"*".repeat(trimmed.length)}`, "command");
            setInputValue("");
            handleLogin({ username, password: trimmed });
        }
    };
    const renderPrompt = phase === "username" || phase === "password";
    const topLine = `${host}@mystery-visitor:~$ neofetch`;
    const bottomLine = `${host}@mystery-visitor:~$ type "help" if you are lost ...`;
    const brandTop = (SPA_NAME || "RCS").toUpperCase();
    const brandBottom = "FLOW";
    return (
        <Box
            sx={{
                minHeight: "100vh",
                backgroundColor: "#030203",
                color: "#aee6b8",
                fontFamily: `'OCR A Std', 'Share Tech Mono', 'Courier New', monospace`,
                fontSize: { xs: 11, md: 13 },
                letterSpacing: "0.04em",
                display: "flex",
                flexDirection: "column",
                alignItems: "center",
                justifyContent: "space-between",
                padding: { xs: 2, md: 4 },
                gap: 3,
            }}
        >
            <Typography sx={{ color: PROMPT_COLOR, width: "100%", textAlign: "left" }}>
                {topLine}
            </Typography>
            <Box
                sx={{
                    width: "100%",
                    display: "flex",
                    flexDirection: { xs: "column", md: "row" },
                    gap: { xs: 3, md: 8 },
                }}
            >
                <Box sx={{ flex: 1, minWidth: 0 }}>
                    <Typography
                        component="pre"
                        sx={{
                            color: "#ff3f9c",
                            m: 0,
                            whiteSpace: "pre",
                            fontSize: { xs: 10, md: 14 },
                            lineHeight: 1.05,
                        }}
                    >
                        {asciiArt}
                    </Typography>
                </Box>
                <Box
                    sx={{
                        flex: 1,
                        display: "flex",
                        flexDirection: "column",
                        gap: 2,
                        minWidth: 0,
                    }}
                >
                    <Box>
                        <Typography
                            sx={{
                                fontSize: { xs: 26, md: 44 },
                                letterSpacing: { xs: "0.4em", md: "0.6em" },
                                color: "#6ef68c",
                                textTransform: "uppercase",
                                textShadow: "0 0 6px rgba(110,246,140,0.7)",
                            }}
                        >
                            {brandTop}
                        </Typography>
                        <Typography
                            sx={{
                                fontSize: { xs: 24, md: 40 },
                                letterSpacing: { xs: "0.35em", md: "0.55em" },
                                color: "#6ef68c",
                                textTransform: "uppercase",
                                textShadow: "0 0 6px rgba(110,246,140,0.7)",
                            }}
                        >
                            {brandBottom}
                        </Typography>
                    </Box>
                    <Box
                        component="ul"
                        sx={{
                            listStyle: "none",
                            m: 0,
                            p: 0,
                            display: "flex",
                            flexDirection: "column",
                            gap: 0.3,
                        }}
                    >
                        {systemInfo.map((item) => (
                            <li key={item.label} style={{ display: "flex", gap: "12px" }}>
                                <Typography component="span" sx={{ minWidth: 120, color: LABEL_COLOR }}>
                                    {item.label}:
                                </Typography>
                                <Typography component="span" sx={{ color: VALUE_COLOR }}>
                                    {item.value}
                                </Typography>
                            </li>
                        ))}
                    </Box>
                </Box>
            </Box>
            <Box
                component="form"
                onSubmit={(event) => {
                    event.preventDefault();
                    handleAdvance();
                }}
                sx={{
                    width: "100%",
                    display: "flex",
                    flexDirection: "column",
                    gap: 1,
                    mt: { xs: 2, md: 3 },
                    maxWidth: 960,
                }}
            >
                <Typography sx={{ color: "#f1f1f1", fontSize: { xs: 12, md: 13 } }}>
                    ACCESS LOG
                </Typography>
                <Box
                    sx={{
                        minHeight: 160,
                        maxHeight: 220,
                        overflowY: "auto",
                        color: "#c5e6c5",
                        display: "flex",
                        flexDirection: "column",
                        gap: 0.2,
                        fontSize: { xs: 11, md: 13 },
                    }}
                >
                    {history.map((line, index) => (
                        <Typography
                            key={`${line.tone}-${index}`}
                            sx={{
                                color:
                                    line.tone === "accent"
                                        ? "#73ee73"
                                        : line.tone === "system"
                                            ? "#58c8ff"
                                            : line.tone === "command"
                                                ? "#ffffff"
                                                : line.tone === "warning"
                                                    ? "#e6cd67"
                                                    : line.tone === "error"
                                                        ? "#ff7a7a"
                                                        : "#b8d9b8",
                            }}
                        >
                            {line.text}
                        </Typography>
                    ))}
                </Box>
                {renderPrompt && (
                    <Box sx={{ display: "flex", alignItems: "center", color: "#fff" }}>
                        <Typography component="span" sx={{ color: PROMPT_COLOR }}>
                            {displayedPrompt}
                        </Typography>
                        <Box
                            component="input"
                            ref={inputRef}
                            value={inputValue}
                            onChange={(event) => setInputValue(event.target.value)}
                            disabled={loading}
                            type={phase === "password" ? "password" : "text"}
                            autoComplete="off"
                            sx={{
                                flex: 1,
                                border: "none",
                                outline: "none",
                                background: "transparent",
                                color: "#ffffff",
                                fontFamily: `'OCR A Std', 'Courier New', monospace`,
                                fontSize: { xs: 12, md: 13 },
                                ml: 1,
                            }}
                        />
                    </Box>
                )}
                {phase === "submitting" && (
                    <Typography sx={{ color: "#73ee73" }}> CONNECTING ...</Typography>
                )}
            </Box>
            <Typography sx={{ color: PROMPT_COLOR, width: "100%", textAlign: "left" }}>
                {bottomLine}
            </Typography>
        </Box>
    );
};
export default Login0;