This commit is contained in:
2026-04-24 20:30:52 +08:00
commit 704bc34625
152 changed files with 45612 additions and 0 deletions

BIN
public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

1
public/logo.svg Normal file
View File

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1766318220539" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="7296" width="200" height="200" xmlns:xlink="http://www.w3.org/1999/xlink"><path d="M581.818182 572.509091m-328.610909 0a328.610909 328.610909 0 1 0 657.221818 0 328.610909 328.610909 0 1 0-657.221818 0Z" fill="#FB8339" p-id="7297"></path><path d="M899.258182 470.109091c19.549091 0 35.374545-15.825455 35.374545-35.374546V158.254545c0-15.825455-11.170909-29.789091-26.996363-33.512727-1.861818-0.930909-3.723636-1.861818-6.516364-2.792727l-380.741818-83.781818c-3.723636-0.930909-6.516364-0.930909-9.309091 0h-13.032727l-374.225455 83.781818c-2.792727 0.930909-4.654545 1.861818-6.516364 2.792727-15.825455 3.723636-26.996364 17.687273-26.996363 33.512727v393.774546c0 2.792727 0.930909 4.654545 0.930909 5.585454 11.170909 243.898182 216.901818 400.290909 415.185454 429.149091h10.24c177.803636-25.134545 383.534545-159.185455 413.323637-391.912727 2.792727-19.549091-11.170909-37.236364-30.72-39.098182-9.309091-0.930909-18.618182 0.930909-26.065455 7.447273S861.090909 577.163636 860.16 586.472727c-25.134545 194.56-197.352727 307.2-349.090909 330.472728-175.010909-26.996364-351.883636-165.701818-351.883636-378.88V185.250909l349.090909-78.196364 354.676363 78.196364v249.483636c0.930909 19.549091 16.756364 35.374545 36.305455 35.374546z" p-id="7298"></path><path d="M692.596364 413.323636c-5.585455-81.92-57.716364-124.741818-156.392728-127.534545H336.058182V716.8h80.989091V545.512727h80.058182L623.709091 715.869091h104.261818L585.541818 538.065455c70.749091-13.032727 107.054545-54.923636 107.054546-124.741819zM417.047273 353.745455h105.192727c76.334545 0 84.712727 34.443636 84.712727 61.44 0.930909 17.687273-3.723636 30.72-13.032727 40.02909-13.032727 13.963636-37.236364 20.48-71.68 20.48H417.047273V353.745455z" p-id="7299"></path></svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

18
public/manifest.json Normal file
View File

@@ -0,0 +1,18 @@
{
"name": "RiskGuard",
"short_name": "RiskGuard",
"display": "standalone",
"start_url": "./?utm_source=homescreen",
"theme_color": "#002140",
"background_color": "#001529",
"icons": [
{
"src": "icons/icon-192x192.png",
"sizes": "192x192"
},
{
"src": "icons/icon-512x512.png",
"sizes": "512x512"
}
]
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

208
public/scripts/loading.js Normal file
View File

@@ -0,0 +1,208 @@
/**
* loading placeholder
* Solve the problem that the white background will be displayed first when refreshing the page under the dark theme
*/
(function () {
const _root = document.querySelector('#root');
const theme = localStorage.getItem('navTheme');
// Solve the problem that the white background will be displayed first when refreshing the page under the dark theme
try {
const html = document.documentElement;
if(theme === 'realDark') {
html.dataset.theme = 'dark';
}else {
html.removeAttribute('data-theme');
}
} catch (error) {}
if (_root && _root.innerHTML === '') {
_root.innerHTML = `
<style>
html,
body,
#root {
height: 100%;
margin: 0;
padding: 0;
}
#root {
background-repeat: no-repeat;
background-size: 100% auto;
}
.loading-title {
font-size: 1.1rem;
}
.loading-sub-title {
margin-top: 20px;
font-size: 1rem;
color: #888;
}
.page-loading-warp {
display: flex;
align-items: center;
justify-content: center;
padding: 26px;
}
.ant-spin {
position: absolute;
display: none;
-webkit-box-sizing: border-box;
box-sizing: border-box;
margin: 0;
padding: 0;
color: rgba(0, 0, 0, 0.65);
color: #1890ff;
font-size: 14px;
font-variant: tabular-nums;
line-height: 1.5;
text-align: center;
list-style: none;
opacity: 0;
-webkit-transition: -webkit-transform 0.3s
cubic-bezier(0.78, 0.14, 0.15, 0.86);
transition: -webkit-transform 0.3s
cubic-bezier(0.78, 0.14, 0.15, 0.86);
transition: transform 0.3s cubic-bezier(0.78, 0.14, 0.15, 0.86);
transition: transform 0.3s cubic-bezier(0.78, 0.14, 0.15, 0.86),
-webkit-transform 0.3s cubic-bezier(0.78, 0.14, 0.15, 0.86);
-webkit-font-feature-settings: "tnum";
font-feature-settings: "tnum";
}
.ant-spin-spinning {
position: static;
display: inline-block;
opacity: 1;
}
.ant-spin-dot {
position: relative;
display: inline-block;
width: 20px;
height: 20px;
font-size: 20px;
}
.ant-spin-dot-item {
position: absolute;
display: block;
width: 9px;
height: 9px;
background-color: #1890ff;
border-radius: 100%;
-webkit-transform: scale(0.75);
-ms-transform: scale(0.75);
transform: scale(0.75);
-webkit-transform-origin: 50% 50%;
-ms-transform-origin: 50% 50%;
transform-origin: 50% 50%;
opacity: 0.3;
-webkit-animation: antspinmove 1s infinite linear alternate;
animation: antSpinMove 1s infinite linear alternate;
}
.ant-spin-dot-item:nth-child(1) {
top: 0;
left: 0;
}
.ant-spin-dot-item:nth-child(2) {
top: 0;
right: 0;
-webkit-animation-delay: 0.4s;
animation-delay: 0.4s;
}
.ant-spin-dot-item:nth-child(3) {
right: 0;
bottom: 0;
-webkit-animation-delay: 0.8s;
animation-delay: 0.8s;
}
.ant-spin-dot-item:nth-child(4) {
bottom: 0;
left: 0;
-webkit-animation-delay: 1.2s;
animation-delay: 1.2s;
}
.ant-spin-dot-spin {
-webkit-transform: rotate(45deg);
-ms-transform: rotate(45deg);
transform: rotate(45deg);
-webkit-animation: antrotate 1.2s infinite linear;
animation: antRotate 1.2s infinite linear;
}
.ant-spin-lg .ant-spin-dot {
width: 32px;
height: 32px;
font-size: 32px;
}
.ant-spin-lg .ant-spin-dot i {
width: 14px;
height: 14px;
}
@media all and (-ms-high-contrast: none), (-ms-high-contrast: active) {
.ant-spin-blur {
background: #fff;
opacity: 0.5;
}
}
@-webkit-keyframes antSpinMove {
to {
opacity: 1;
}
}
@keyframes antSpinMove {
to {
opacity: 1;
}
}
@-webkit-keyframes antRotate {
to {
-webkit-transform: rotate(405deg);
transform: rotate(405deg);
}
}
@keyframes antRotate {
to {
-webkit-transform: rotate(405deg);
transform: rotate(405deg);
}
}
</style>
<div style="
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100%;
min-height: 362px;
">
<div class="page-loading-warp">
<div class="ant-spin ant-spin-lg ant-spin-spinning">
<span class="ant-spin-dot ant-spin-dot-spin">
<i class="ant-spin-dot-item"></i>
<i class="ant-spin-dot-item"></i>
<i class="ant-spin-dot-item"></i>
<i class="ant-spin-dot-item"></i>
</span>
</div>
</div>
</div>
`;
}
})();

Binary file not shown.

131
public/sw.js Normal file
View File

@@ -0,0 +1,131 @@
// Parse the parameters in the current script's own URL
const scriptUrl = new URL(self.location);
// Get the incoming version, and provide a default value if it doesn't exist
const version = scriptUrl.searchParams.get("v") || "default-version";
// Dynamically concatenate CACHE_NAME
const CACHE_NAME = `risk-guard-cache-${version}`;
console.log("Current Service Worker CACHE_NAME:", CACHE_NAME);
// 例如umi.1a2b3c4d.js, logo.7b3e198f.png
const HASH_REGEX =
/\.[0-9a-f]{8}\.(js|css|png|jpe?g|gif|webp|svg|ico|woff2?|eot|ttf)$/i;
// 1. Installation phase: forcibly skip waiting and directly enter the activation state
self.addEventListener("install", (event) => {
self.skipWaiting();
});
// 2. Activation phase: clean up old caches and immediately take control of the page
self.addEventListener("activate", (event) => {
event.waitUntil(
caches.keys().then((cacheNames) => {
return Promise.all(
cacheNames.map((cache) => {
if (cache !== CACHE_NAME) {
console.log("Cleaning old cache:", cache);
return caches.delete(cache);
}
}),
);
}),
);
self.clients.claim(); // Immediately control all open clients (tabs)
});
// 3. Fetch event: this is the key to trigger the installation icon to be displayed
self.addEventListener("fetch", (event) => {
const { request } = event;
const url = new URL(request.url);
// If not a GET request, skip processing and go straight to the cache
if (request.method !== "GET") return;
// If not an http or https protocol (like chrome-extension), skip processing and go straight to the cache
if (!(url.protocol === "http:" || url.protocol === "https:")) {
return;
}
// Ignore HMR (hot module replacement) and API requests (you can adjust according to your actual API prefix)
if (
url.pathname.includes("/api/") ||
url.pathname.includes("/sockjs-node/")
) {
return;
}
// 1. Handle navigation requests: fetch from network first, then cache if successful
if (request.mode === "navigate") {
event.respondWith(
fetch(request).catch(() => {
return caches.match(request);
}),
);
return;
}
// 2. Handle static assets (js, css, images) - cache first
const isStaticAsset = url.pathname.match(
/\.(js|css|png|jpg|jpeg|svg|gif|woff2?|otf|ttf)$/,
);
const isHashed = HASH_REGEX.test(url.pathname);
if (isStaticAsset) {
if (isHashed) {
// ==========================================
// Strategy A: Resources with Hash -> Cache First
// ==========================================
event.respondWith(
caches.match(request).then((response) => {
// If cache hit, return the cached response immediately
if (response) return response;
// Otherwise, fetch from network first
return fetch(request).then((networkResponse) => {
if (
!networkResponse ||
networkResponse.status !== 200 ||
(networkResponse.type !== "basic" &&
networkResponse.type !== "cors")
) {
return networkResponse;
}
// Clone the response to allow multiple requests
const responseToCache = networkResponse.clone();
caches.open(CACHE_NAME).then((cache) => {
cache.put(request, responseToCache);
});
return networkResponse;
});
}),
);
} else {
// ==========================================
// Strategy B: Resources without Hash (such as index.html) -> Network First
// Ensure that the latest code is always retrieved, only fall back to cache when offline
// ==========================================
event.respondWith(
fetch(request)
.then((networkResponse) => {
// Network request successful, updating cache
if (networkResponse && networkResponse.status === 200) {
const responseToCache = networkResponse.clone();
caches.open(CACHE_NAME).then((cache) => {
cache.put(request, responseToCache);
});
}
return networkResponse;
})
.catch(() => {
// Network request failed (for example, the user disconnected from the Internet), try reading the historical version from the cache
console.log("[SW] Network offline, try reading cache:", request.url);
return caches.match(request);
}),
);
}
}
});