├── image
├── overview.png
└── lighthouse.png
├── .fatherrc.js
├── package.json
├── index.js
├── .gitignore
├── README.md
├── test
├── workbox.js
├── index.html
└── service-worker.js
├── index.css
└── App.jsx
/image/overview.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lie5860/hypergryph-gacha/HEAD/image/overview.png
--------------------------------------------------------------------------------
/image/lighthouse.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lie5860/hypergryph-gacha/HEAD/image/lighthouse.png
--------------------------------------------------------------------------------
/.fatherrc.js:
--------------------------------------------------------------------------------
1 | export default {
2 | entry: './index.js',
3 | // esm: 'webpack',
4 | umd: true,
5 | }
6 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-hello",
3 | "version": "0.0.1",
4 | "scripts": {
5 | "build": "father-build"
6 | },
7 | "dependencies": {},
8 | "devDependencies": {
9 | "father-build": "^1.21.1",
10 | "@babel/core": "^7.12.3"
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | import React, {createElement} from 'react';
2 | import ReactDOM from 'react-dom';
3 | import App from './App.jsx'
4 |
5 |
6 | // 初始化
7 | export function bootstrap() {
8 | }
9 |
10 | // 挂载
11 | export function mount(container, props) {
12 | console.log(props, 'props')
13 | ReactDOM.render(createElement(App, props, null), container);
14 | }
15 |
16 | // 更新
17 | export function updated(attrName, value, container, props) {
18 | ReactDOM.render(createElement(App, props, null), container);
19 | }
20 |
21 | window.App = {
22 | bootstrap, mount, updated
23 | }
24 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 |
3 | .pnp
4 | .pnp.js
5 | .env.*.local
6 | .history
7 | .rts*
8 | *.log*
9 | *.pid
10 | *.pid.*
11 | *.report
12 | *.lcov
13 | lib-cov
14 |
15 | node_modules/
16 | .npm
17 | .lock-wscript
18 | .yarn-integrity
19 | .node_repl_history
20 | .nyc_output
21 | *.tsbuildinfo
22 | .eslintcache
23 | .sonarlint
24 |
25 | dist/
26 | coverage/
27 | release/
28 | output/
29 | output_resource/
30 |
31 | .vscode/**/*
32 | !.vscode/settings.json
33 | !.vscode/extensions.json
34 | .idea/
35 |
36 | **/*/typings/auto-generated
37 | **/*/adapters/**/index.ts
38 | **/*/adapters/**/index.js
39 |
40 | .changeset/pre.json
41 |
42 | .pnpm-store/
43 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # hypergryph-gacha
2 | 一个明日方舟的寻访记录查看工具,可以方便的统计出货概率,缓存历史寻访记录。
3 |
4 | 数据使用阿里云FC node写入,私有mongo存储。
5 |
6 | 该站点当前所有部分使用React开发,使用magic转Web Component使用。
7 |
8 | 线上访问地址:[ak.saki.cc](http://ak.saki.cc)
9 |
10 | 概览图
11 |
12 | 
13 |
14 | Lighthouse结果
15 |
16 | 
17 |
18 | 本地调试方式 npm i => npm run build => 打开test目录下的index.html访问
19 |
20 | 欢迎小伙伴提Issue给这个小工具提一些建议和想法。
21 |
22 | ## 版本履历
23 |
24 | #### v0.8
25 | 通过Lighthouse报告进行部分优化
26 |
27 | #### v0.7
28 | 域名支持HTTPS 增加Service Worker缓存 支持无网络访问
29 |
30 | #### v0.6
31 | 增加暗黑模式
32 |
33 | mongo增加索引、支持追加数据,优化非首次爬取时长
34 |
35 | #### v0.5
36 | 引入shoelace webcomponent组件库优化页面
37 |
38 | #### v0.4
39 | 使用magic将项目使用react打包成webcomponent使用
40 |
41 | 优化分析,合并所有非限定池,并根据最终结果按照时间倒序展示各池子情况
42 |
43 | #### v0.3
44 | 支持数据远端存储,爬取的数据将存入mongo进行分析
45 |
--------------------------------------------------------------------------------
/test/workbox.js:
--------------------------------------------------------------------------------
1 | var workbox=function(){"use strict";try{self.workbox.v["workbox:sw:3.3.0"]=1}catch(t){}const t={backgroundSync:"background-sync",broadcastUpdate:"broadcast-cache-update",cacheableResponse:"cacheable-response",core:"core",expiration:"cache-expiration",googleAnalytics:"google-analytics",precaching:"precaching",rangeRequests:"range-requests",routing:"routing",strategies:"strategies",streams:"streams"};return new class{constructor(){return this.v={},this.t={debug:"localhost"===self.location.hostname,modulePathPrefix:null,modulePathCb:null},this.e=this.t.debug?"dev":"prod",this.s=!1,new Proxy(this,{get(e,s){if(e[s])return e[s];const o=t[s];return o&&e.loadModule(`workbox-${o}`),e[s]}})}setConfig(t={}){if(this.s)throw new Error("Config must be set before accessing workbox.* modules");Object.assign(this.t,t),this.e=this.t.debug?"dev":"prod"}skipWaiting(){self.addEventListener("install",()=>self.skipWaiting())}clientsClaim(){self.addEventListener("activate",()=>self.clients.claim())}loadModule(t){const e=this.o(t);try{importScripts(e),this.s=!0}catch(s){throw console.error(`Unable to import module '${t}' from '${e}'.`),s}}o(t){if(this.t.modulePathCb)return this.t.modulePathCb(t,this.t.debug);let e=["https://storage.googleapis.com/workbox-cdn/releases/3.3.0"];const s=`${t}.${this.e}.js`,o=this.t.modulePathPrefix;return o&&""===(e=o.split("/"))[e.length-1]&&e.splice(e.length-1,1),e.push(s),e.join("/")}}}();
2 |
--------------------------------------------------------------------------------
/test/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
8 |
11 |
13 |
14 | hypergryph-gacha
15 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
35 |
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/test/service-worker.js:
--------------------------------------------------------------------------------
1 | //首先是异常处理
2 | self.addEventListener('error', function (e) {
3 | self.clients.matchAll()
4 | .then(function (clients) {
5 | if (clients && clients.length) {
6 | clients[0].postMessage({
7 | type: 'ERROR',
8 | msg: e.message || null,
9 | stack: e.error ? e.error.stack : null
10 | });
11 | }
12 | });
13 | });
14 | self.addEventListener('unhandledrejection', function (e) {
15 | self.clients.matchAll()
16 | .then(function (clients) {
17 | if (clients && clients.length) {
18 | clients[0].postMessage({
19 | type: 'REJECTION',
20 | msg: e.reason ? e.reason.message : null,
21 | stack: e.reason ? e.reason.stack : null
22 | });
23 | }
24 | });
25 | })
26 | //然后引入workbox
27 | importScripts(
28 | "./workbox.js"
29 | );
30 | if (workbox) {
31 | console.log('workbox加载成功');
32 | } else {
33 | console.log('workbox加载失败');
34 | }
35 | //关闭控制台中的输出
36 | workbox.setConfig({debug: false});
37 |
38 | //直接激活跳过等待阶段
39 | workbox.skipWaiting();
40 | workbox.clientsClaim();
41 | // 定义缓存名称
42 | workbox.core.setCacheNameDetails({
43 | prefix: 'react-wc',
44 | suffix: 'v1'
45 | });
46 | const urls = [
47 | 'https://unpkg.com/@magic-microservices/magic@1.1.3/dist/index.umd.js',
48 | 'https://cdn.jsdelivr.net/npm/echarts@5.3.0/dist/echarts.js',
49 | 'https://unpkg.com/react@17/umd/react.production.min.js',
50 | 'https://unpkg.com/react-dom@17/umd/react-dom.production.min.js',
51 | 'https://unpkg.com/axios/dist/axios.min.js',
52 | ]
53 | var fileList = urls.map(url => ({url}));
54 | //precache 适用于支持跨域的cdn和域内静态资源
55 | workbox.precaching.suppressWarnings();
56 | workbox.precaching.precacheAndRoute(fileList, {
57 | "ignoreUrlParametersMatching": [/./]
58 | });
59 | //staleWhileRevalidate
60 | // 这种策略的意思是当请求的路由有对应的 Cache 缓存结果就直接返回,
61 | // 在返回 Cache 缓存结果的同时会在后台发起网络请求拿到请求结果并更新 Cache 缓存,
62 | // 如果本来就没有 Cache 缓存的话,直接就发起网络请求并返回结果,这对用户来说是一种非常安全的策略,能保证用户最快速的拿到请求的结果。
63 | // 但是也有一定的缺点,就是还是会有网络请求占用了用户的网络带宽。可以像如下的方式使用 State While Revalidate 策略:
64 | workbox.routing.registerRoute(
65 | new RegExp('https://74082082-1683720436570405.test.functioncompute.com/'),
66 | workbox.strategies.staleWhileRevalidate({
67 | //cache名称
68 | cacheName: 'lf-sw:static',
69 | plugins: [
70 | new workbox.expiration.Plugin({
71 | //cache最大数量
72 | maxEntries: 30
73 | })
74 | ]
75 | })
76 | );
77 | //cacheFirst
78 | // 这个策略的意思就是当匹配到请求之后直接从 Cache 缓存中取得结果,
79 | // 如果 Cache 缓存中没有结果,那就会发起网络请求,
80 | // 拿到网络请求结果并将结果更新至 Cache 缓存,
81 | // 并将结果返回给客户端。这种策略比较适合结果不怎么变动且对实时性要求不高的请求。
82 | // 可以像如下方式使用 Cache First 策略:
83 | workbox.routing.registerRoute(
84 | new RegExp('https://cdn\.jsdelivr\.net/npm/@shoelace-style/'),
85 | workbox.strategies.cacheFirst({
86 | cacheName: 'lf-sw:img',
87 | plugins: [
88 | //如果要拿到域外的资源,必须配置
89 | //因为跨域使用fetch配置了
90 | //mode: 'no-cors',所以status返回值为0,故而需要兼容
91 | new workbox.cacheableResponse.Plugin({
92 | statuses: [0, 200]
93 | }),
94 | new workbox.expiration.Plugin({
95 | maxEntries: 150,
96 | //缓存的时间
97 | maxAgeSeconds: 60 * 60 * 24 * 365
98 | })
99 | ]
100 | })
101 | );
102 | console.log('Hello from service-worker.js');
103 |
--------------------------------------------------------------------------------
/index.css:
--------------------------------------------------------------------------------
1 | * {
2 | -webkit-font-smoothing: antialiased;
3 | -webkit-overflow-scrolling: touch;
4 | -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
5 | -webkit-text-size-adjust: none;
6 | -webkit-touch-callout: none;
7 | box-sizing: border-box;
8 | }
9 |
10 | :root {
11 | --three-color: var(--sl-color-neutral-600);
12 | --four-color: var(--sl-color-purple-600);
13 | --five-color: var(--sl-color-amber-500);
14 | --six-color: var(--sl-color-orange-600);
15 | --tip: var(--sl-color-neutral-600);
16 | --black: var(--sl-color-neutral-1000);
17 | --white: var(--sl-color-neutral-0);
18 | }
19 |
20 | html {
21 | background: var(--sl-color-neutral-0);
22 | color: var(--sl-color-neutral-1000);
23 | }
24 |
25 | body {
26 | background-color: var(--white);
27 | }
28 |
29 | @media (min-width: 800px) and (-webkit-device-pixel-ratio: 1) {
30 | .detail-data {
31 | break-inside: avoid;
32 | counter-increment: item-counter;
33 | float: unset !important;
34 | }
35 |
36 | .dataGrid {
37 | column-count: 2; /*css3新增,把contaner容器中的内容分为三列*/
38 | column-gap: 10px; /*定义列之间的间隙为20px*/
39 | }
40 | }
41 |
42 | @media (min-width: 1600px) and (-webkit-device-pixel-ratio: 2) {
43 | .detail-data {
44 | break-inside: avoid;
45 | counter-increment: item-counter;
46 | float: unset !important;
47 | }
48 |
49 | .dataGrid {
50 | column-count: 2; /*css3新增,把contaner容器中的内容分为三列*/
51 | column-gap: 10px; /*定义列之间的间隙为20px*/
52 | }
53 | }
54 |
55 | @media (min-width: 2400px) and (-webkit-device-pixel-ratio: 3) {
56 | .detail-data {
57 | break-inside: avoid;
58 | counter-increment: item-counter;
59 | float: unset !important;
60 | }
61 |
62 | .dataGrid {
63 | column-count: 2; /*css3新增,把contaner容器中的内容分为三列*/
64 | column-gap: 10px; /*定义列之间的间隙为20px*/
65 | }
66 | }
67 |
68 | a {
69 | color: var(--sl-color-primary-600);
70 | }
71 |
72 | .title {
73 | font-size: 20px;
74 | font-weight: 700;
75 | margin: 0;
76 | }
77 |
78 | body {
79 | margin: 0;
80 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
81 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
82 | sans-serif;
83 | -webkit-font-smoothing: antialiased;
84 | -moz-osx-font-smoothing: grayscale;
85 |
86 | box-sizing: border-box;
87 | }
88 |
89 | .main-container {
90 | padding: 20px;
91 | font-size: 1rem;
92 | }
93 |
94 | .alert--closable {
95 | margin-bottom: 1rem;
96 | }
97 |
98 | .token {
99 | display: inline-block;
100 | width: calc(100% - 84px);
101 | }
102 |
103 | .submitBtn {
104 | display: inline-block;
105 | }
106 |
107 | .dataGrid {
108 | text-align: center;
109 | }
110 |
111 | .pBlue {
112 | color: var(--sl-color-primary-700);
113 | }
114 |
115 | .detail-data {
116 | box-sizing: border-box;
117 | padding: 10px;
118 | text-align: left;
119 | line-height: 1.5;
120 | float: left;
121 | width: 100%;
122 | }
123 |
124 | .pt10 {
125 | padding-top: 10px
126 | }
127 |
128 | .clean-float:after { /*伪元素是行内元素 正常浏览器清除浮动方法*/
129 | content: "";
130 | display: block;
131 | height: 0;
132 | clear: both;
133 | visibility: hidden;
134 | }
135 |
136 | .spinner-icon {
137 | position: fixed;
138 | top: 0;
139 | bottom: 0;
140 | left: 0;
141 | right: 0;
142 | display: flex;
143 | align-items: center;
144 | justify-content: center;
145 | background-color: transparent;
146 | }
147 |
148 | .spinner-bg {
149 | position: fixed;
150 | top: 0;
151 | bottom: 0;
152 | left: 0;
153 | right: 0;
154 | display: flex;
155 | align-items: center;
156 | justify-content: center;
157 | background-color: var(--sl-color-neutral-700);
158 | opacity: 0.5;
159 | }
160 |
161 | .echart-c {
162 | display: flex;
163 | align-items: center;
164 | justify-content: center;
165 | }
166 |
167 | .avatar-me {
168 | display: flex;
169 | justify-content: center;
170 | align-items: center;
171 | }
172 |
173 | .new-tag {
174 | font-family: 'monospace';
175 | color: var(--sl-color-primary-400);
176 | }
177 |
178 |
179 | .tip {
180 | font-family: 'monospace';
181 | color: var(--sl-color-neutral-600);
182 | }
183 |
184 | .title-container {
185 | text-align: center;
186 | margin: 10px 0;
187 | }
188 |
--------------------------------------------------------------------------------
/App.jsx:
--------------------------------------------------------------------------------
1 | import './index.css'
2 | // 可注释
3 | // import React from "react";
4 | // import axios from 'axios';
5 | // import * as echarts from 'echarts/core';
6 | // import {
7 | // TitleComponent,
8 | // TooltipComponent,
9 | // LegendComponent
10 | // } from 'echarts/components';
11 | // import {PieChart} from 'echarts/charts';
12 | // import {LabelLayout} from 'echarts/features';
13 | // import {CanvasRenderer} from 'echarts/renderers';
14 | //
15 | // echarts.use([
16 | // TitleComponent,
17 | // TooltipComponent,
18 | // LegendComponent,
19 | // PieChart,
20 | // CanvasRenderer,
21 | // LabelLayout
22 | // ]);
23 |
24 | // 可注释 end
25 | function dateFormat(fmt, date) {
26 | let ret;
27 | const opt = {
28 | "Y+": date.getFullYear().toString(), // 年
29 | "m+": (date.getMonth() + 1).toString(), // 月
30 | "d+": date.getDate().toString(), // 日
31 | "H+": date.getHours().toString(), // 时
32 | "M+": date.getMinutes().toString(), // 分
33 | "S+": date.getSeconds().toString(), // 秒
34 | // 有其他格式化字符需求可以继续添加,必须转化成字符串
35 | };
36 | for (let k in opt) {
37 | ret = new RegExp("(" + k + ")").exec(fmt);
38 | if (ret) {
39 | fmt = fmt.replace(
40 | ret[1],
41 | ret[1].length == 1 ? opt[k] : opt[k].padStart(ret[1].length, "0")
42 | );
43 | }
44 | }
45 | return fmt;
46 | }
47 |
48 | const transformEchartsData = (item, color) => {
49 | const {
50 | name,
51 | firstTime,
52 | lastTime,
53 | threeTime,
54 | fourTime,
55 | fiveTime,
56 | sixTime,
57 | } = item;
58 | return {
59 | title: {
60 | top: "0%",
61 | text: name,
62 | subtext: `${dateFormat(
63 | "YYYY-mm-dd HH:MM:SS",
64 | new Date(firstTime * 1000)
65 | )} - ${dateFormat(
66 | "YYYY-mm-dd HH:MM:SS",
67 | new Date(lastTime * 1000)
68 | )}`,
69 | textStyle: {
70 | color: color.black
71 | },
72 | subtextStyle: {
73 | color: color.tip
74 | },
75 | left: "center",
76 | },
77 | tooltip: {
78 | trigger: "item",
79 | },
80 | itemStyle: {
81 | borderColor: color.white,
82 | borderWidth: 1
83 | },
84 | legend: {
85 | top: "14%",
86 | left: "center",
87 | textStyle: {
88 | color: color.tip
89 | }
90 | },
91 | label: {
92 | alignTo: 'edge',
93 | formatter: "{b}\n{c}个\n{d}%",
94 | minMargin: 5,
95 | edgeDistance: 10,
96 | lineHeight: 15,
97 | color: color.black
98 | },
99 | labelLine: {
100 | length: 15,
101 | length2: 0,
102 | maxSurfaceAngle: 80
103 | },
104 | series: [
105 | {
106 | type: "pie",
107 | legendHoverLink: false,
108 | radius: ["15%", "45%"],
109 | center: ['50%', '60%'],
110 | data: [
111 | {
112 | value: sixTime || '--',
113 | name: "6星干员",
114 | itemStyle: {color: color['color6']},
115 | },
116 | {
117 | value: fiveTime || '--',
118 | name: "5星干员",
119 | itemStyle: {color: color['color5']},
120 | },
121 | {
122 | value: fourTime || '--',
123 | name: "4星干员",
124 | itemStyle: {color: color['color4']},
125 | },
126 | {
127 | value: threeTime || '--',
128 | name: "3星干员",
129 | itemStyle: {color: color['color3']},
130 | },
131 | ],
132 | emphasis: {
133 | disabled: true
134 | },
135 | },
136 | ],
137 | };
138 | }
139 | const ListItem = ({data, color}) => {
140 | const {ts, pool, times, name, isNew} = data;
141 | return <>
142 |
143 |
144 | [{times}]
146 | {name}{isNew ? NEW : ''}
147 |
148 | ({pool})
149 |
150 |
151 |
152 | >
153 | }
154 | const GridItem = ({item, color}) => {
155 | const ref = React.useRef(null);
156 | const echartRef = React.useRef(null);
157 | const {total, sixTag, fiveTag, avgSix, avgFive, name, sixItem, fiveItem} = item;
158 |
159 | React.useEffect(() => {
160 | if (ref.current) {
161 | echartRef.current = echarts.init(ref.current);
162 | echartRef.current?.setOption(transformEchartsData(item, color));
163 | echartRef.current?.resize()
164 | return () => {
165 | echartRef.current?.clear();
166 | };
167 | }
168 | }, [])
169 | React.useEffect(() => {
170 | echartRef.current?.setOption(transformEchartsData(item, color));
171 | echartRef.current?.resize()
172 | }, [color])
173 | return
174 |
185 |
186 |
189 |
平均出货次数:
190 |
198 |
199 |
200 | {sixItem.map((item, i) => )}
201 |
202 |
203 |
204 |
205 | {fiveItem.map((item, i) => )}
206 |
207 |
208 |
209 |
210 | }
211 | const DataGrid = ({res, color}) => {
212 | const {data, uid, nickName} = res;
213 | return
214 |
215 |
UID: {uid}
216 |
{nickName}
217 |
218 |
219 | {data.map((item, i) => )}
220 |
221 |
window.open('https://github.com/lie5860/hypergryph-gacha')}>
222 |
226 | 本站由FC与WebComponent驱动
227 |
228 |
229 | }
230 |
231 | export default function Home() {
232 | const [token, setToken] = React.useState('')
233 | const [res, setRes] = React.useState(null)
234 | const [loading, setLoading] = React.useState(false)
235 | const [darkMode, setDarkMode] = React.useState(false)
236 | const [color, setColor] = React.useState({})
237 | const ref = React.useRef()
238 | React.useEffect(() => {
239 | // 绑定媒体查询变更时间
240 | window.matchMedia("(prefers-color-scheme: dark)").addEventListener('change', sql => {
241 | setDarkMode(sql.matches)
242 | });
243 | //初始化业务数据
244 | const token = localStorage.getItem('token');
245 | token && setToken(token)
246 | const uid = localStorage.getItem("lastUid");
247 | uid && setRes(JSON.parse(localStorage.getItem("dealData" + uid) || "{}"))
248 | ref.current.addEventListener('sl-input', (e) => {
249 | setToken(e.target.value)
250 | });
251 | }, [])
252 | React.useEffect(() => {
253 | // 初始化颜色
254 | const obj = {}
255 | const dict = {
256 | '--three-color': 'color3',
257 | '--four-color': 'color4',
258 | '--five-color': 'color5',
259 | '--six-color': 'color6',
260 | '--tip': 'tip',
261 | '--black': 'black',
262 | '--white': 'white',
263 | }
264 | const style = getComputedStyle(document.documentElement);
265 | Object.keys(dict).forEach(k => {
266 | obj[dict[k]] = style.getPropertyValue(k)?.trim()
267 | })
268 | setColor(obj)
269 | }, [darkMode])
270 | const clear = () => {
271 | localStorage.setItem("lastUid", "");
272 | setRes(null)
273 | }
274 | const init = (e) => {
275 | e.preventDefault();
276 | localStorage.setItem('token', token)
277 | let dealValue = token;
278 | try {
279 | dealValue = /"content":"(.*?)"/.exec(dealValue)[1]
280 | // dealValue = JSON.parse(token).data.token;
281 | } catch (e) {
282 | }
283 | setLoading(true)
284 | const url = `https://1683720436570405.cn-hangzhou.fc.aliyuncs.com/2016-08-15/proxy/hypergryph.LATEST/hypergryph_test/?token=${encodeURIComponent(
285 | dealValue
286 | )}`;
287 | axios
288 | .get(url, {responseType: "json"})
289 | .then(function (response) {
290 | const {uid, msg, data, nickName} = response.data;
291 | if (msg) {
292 | alert(msg);
293 | clear();
294 | } else {
295 | localStorage.setItem("lastUid", uid);
296 | localStorage.setItem(
297 | "dealData" + uid,
298 | JSON.stringify({data, uid, nickName})
299 | );
300 | setRes(response.data)
301 | }
302 | })
303 | .catch(function (error) {
304 | if (error.message === 'Network Error') {
305 | alert('请连接网络后重试。')
306 | } else {
307 | clear();
308 | }
309 | }).finally(() => {
310 | setLoading(false)
311 | });
312 | return true
313 | }
314 | return (
315 |
316 |
317 | 请首先在 官网 登录,随后访问 此处 复制全部内容(如了解JSON也可仅复制data中的token),然后在此填入。(请使用同一浏览器
319 |
320 |
321 |
335 | {loading && <>
336 |
337 |
338 |
339 |
340 | >}
341 | {!loading && res &&
}
342 |
343 | )
344 | }
345 |
--------------------------------------------------------------------------------