Commit 8ce7e1f5 authored by rocosen's avatar rocosen

feat:登录

parent f53a7015
......@@ -112,6 +112,7 @@
"@emotion/styled": "^11.10.4",
"@mui/icons-material": "^5.10.14",
"@mui/material": "^5.10.8",
"axios": "^1.1.3",
"electron-debug": "^3.2.0",
"electron-log": "^4.4.8",
"electron-root-path": "^1.1.0",
......@@ -119,7 +120,8 @@
"history": "^5.3.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-router-dom": "^6.3.0"
"react-router-dom": "^6.3.0",
"tss-react": "^4.4.4"
},
"devDependencies": {
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.7",
......
<?xml version="1.0" encoding="UTF-8"?>
<svg width="187px" height="42px" viewBox="0 0 187 42" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>北鲲云logo_备份</title>
<defs>
<polygon id="path-1" points="0 0 75.6028237 0 75.6028237 24.4008147 0 24.4008147"></polygon>
</defs>
<g id="最新版本" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="登录初始化" transform="translate(-828.000000, -248.000000)">
<g id="编组-9" transform="translate(695.000000, 180.000000)">
<g id="北鲲云logo_备份" transform="translate(133.000000, 68.000000)">
<path d="M61.9394468,25.0906397 C61.9394468,25.0906397 56.3541226,21.5107675 52.5768196,19.1189791 C45.5019249,14.6367879 37.1342295,11.1127363 28.7925439,12.5411016 C41.5504692,15.0920822 49.839909,25.4478459 53.0852549,28.4915643 L61.9394468,25.0906397 Z" id="Fill-1" fill="#00CCCC"></path>
<g id="编组" transform="translate(22.623039, 17.599185)">
<mask id="mask-2" fill="white">
<use xlink:href="#path-1"></use>
</mask>
<g id="Clip-4"></g>
<path d="M75.5781774,4.57426286 C75.3814075,2.36785417 67.203923,-1.36133257 53.6378842,0.508084806 C34.8431949,3.0969683 25.1605339,12.927145 0,9.62476796 C3.0730026,11.7333182 6.80190478,13.9477669 11.2357859,16.1716339 C30.6268915,25.8954528 48.8491387,26.6009063 61.477015,20.3944773 C74.124342,14.1774815 75.8450606,7.57640291 75.5781774,4.57426286" id="Fill-3" fill="#0066FF" mask="url(#mask-2)"></path>
</g>
<path d="M98.162677,21.9803041 C97.072979,29.5402114 80.464697,29.0839982 63.4332468,15.3374174 C51.447926,5.66367637 36.4997484,-2.47005803 22.7213339,0.694260523 C40.2944667,3.13589696 50.8146436,14.5768324 60.8456099,23.3039196 C72.6242092,33.5485014 84.8494988,37.6185846 93.4386168,31.7948602 C94.930223,30.5013373 98.8658466,26.2495498 98.162677,21.9803041" id="Fill-5" fill="#00CCCC"></path>
<path d="M11.1502928,21.3519428 C7.03463605,20.0416507 4.80819624,21.3737657 0.310308351,20.6026138 C7.25741573,24.5622043 15.4290198,26.3723553 22.6231073,27.2239073 C24.7109035,27.4731476 27.1049369,28.0607575 30.3220113,27.8618247 C28.146234,27.6240701 25.8232187,26.4961714 22.7210399,23.7954169 C17.9526499,19.6428662 10.7099354,13.6629358 0,12.7068637 C6.2129522,15.4730869 7.81967999,18.8742412 11.1502928,21.3519428" id="Fill-7" fill="#0066FF"></path>
<polygon id="Fill-9" fill="#4D4D4D" points="111.667514 24.3925371 104.344282 24.3925371 104.344282 27.1442882 111.667514 27.1442882 111.667514 33.6459004 104.346317 37.0799039 104.346317 40.3342706 111.667514 36.8981996 111.667514 41.3066524 114.598706 41.3066524 114.598706 16.7400547 111.667514 16.7400547"></polygon>
<path d="M121.042625,29.2279833 C123.57485,28.2367649 126.316058,26.9595517 128.284209,25.7018643 L128.284209,22.4500245 C126.095314,23.7442365 123.654915,24.9274964 121.042625,25.9582258 L121.042625,16.7397791 L118.107588,16.7397791 L118.107588,38.4569969 C118.107588,40.5058214 118.849207,41.3068362 121.042625,41.3068362 L127.776,41.3068362 L128.771837,38.5566931 L121.042625,38.5566931 L121.042625,29.2279833 Z" id="Fill-11" fill="#4D4D4D"></path>
<path d="M187,28.3230227 L187,25.5712716 L162.573395,25.5712716 L162.573395,28.3230227 L169.091906,28.3230227 L164.347264,38.5565782 C163.677342,39.9973482 164.645585,41.2584813 166.297321,41.2584813 L173.567855,41.2584813 L176.293005,41.2584813 L183.571455,41.2584813 C185.218215,41.2584813 186.184423,39.9973482 185.516536,38.5565782 L182.472936,31.9911054 L179.161095,31.9911054 L182.203565,38.5565782 L176.293005,38.5565782 L173.567855,38.5565782 L167.66114,38.5565782 L172.404651,28.3230227 L187,28.3230227 Z" id="Fill-13" fill="#4D4D4D"></path>
<polygon id="Fill-15" fill="#4D4D4D" points="165.355608 20.2395958 184.214304 20.2395958 184.214304 17.4892229 165.355608 17.4892229"></polygon>
<polygon id="Fill-17" fill="#4D4D4D" points="133.452065 41.3068362 144.188688 39.3142918 144.188688 36.7982278 133.452065 38.7916911"></polygon>
<path d="M134.23505,33.9252333 L134.23505,36.0454991 L136.488857,36.0454991 L137.902207,36.0454991 L140.105804,36.0454991 L143.821588,36.0454991 L143.821588,34.6862778 L143.821588,33.9252333 L143.821588,26.3246665 C143.821588,24.8179685 143.279905,24.2310477 141.668202,24.2310477 L141.437054,24.2310477 C142.22549,22.6053575 143.060066,20.4072185 143.060066,19.6500792 C143.060066,18.8750222 142.862391,18.6848185 141.980772,18.6848185 L138.424439,18.6848185 C138.59633,18.0400096 138.730903,17.3903768 138.791517,16.7395953 L136.343202,16.7395953 C135.885203,19.1290865 135.201258,20.8620534 133.304351,22.984846 L133.304351,26.0508467 C133.632754,25.805971 133.936051,25.5457043 134.23505,25.2849783 L134.23505,33.9252333 Z M136.488857,29.1016863 L137.901755,29.1016863 L137.901755,26.3513134 L136.488857,26.3513134 L136.488857,29.1016863 Z M136.488857,33.9252333 L137.901755,33.9252333 L137.901755,31.1746308 L136.488857,31.1746308 L136.488857,33.9252333 Z M140.105804,33.9252333 L141.569817,33.9252333 L141.569817,31.1746308 L140.105804,31.1746308 L140.105804,33.9252333 Z M140.105804,29.1016863 L141.569817,29.1016863 L141.569817,26.3513134 L140.105804,26.3513134 L140.105804,29.1016863 Z M137.625373,20.8558511 L140.513366,20.8558511 C140.166418,21.8792297 139.714752,22.9370654 138.9763,24.2310477 L137.902207,24.2310477 L136.488857,24.2310477 L135.758999,24.2310477 L135.323391,24.2310477 C136.289147,23.1909 137.055192,22.0499076 137.625373,20.8558511 L137.625373,20.8558511 Z" id="Fill-18" fill="#4D4D4D"></path>
<path d="M157.169438,27.9255246 L157.169438,26.5651547 L157.169438,25.8061777 L157.169438,19.3139838 C157.169438,17.8088938 156.628661,17.2208244 155.0156,17.2208244 L147.294305,17.2208244 L146.563316,17.2208244 L145.040951,17.2208244 L145.040951,25.8061777 L145.040951,27.9255246 L147.294305,27.9255246 L157.169438,27.9255246 Z M147.294305,21.5128119 L154.918346,21.5128119 L154.918346,19.3406307 L147.294305,19.3406307 L147.294305,21.5128119 Z M147.294305,25.8061777 L154.918346,25.8061777 L154.918346,23.6335371 L147.294305,23.6335371 L147.294305,25.8061777 Z" id="Fill-19" fill="#4D4D4D"></path>
<polygon id="Fill-20" fill="#4D4D4D" points="147.294395 34.4741823 151.288598 34.4741823 151.288598 32.3539166 147.294395 32.3539166 147.294395 29.1278048 145.041041 29.1278048 145.041041 32.3539166 145.041041 34.4741823 145.041041 39.1865475 145.041041 41.3068132 147.294395 41.3068132 150.879678 41.3068132 151.64414 39.1865475 147.294395 39.1865475"></polygon>
<path d="M154.397064,34.916337 C155.831901,34.3983305 157.025412,33.8881344 158.298536,33.2688239 L158.298536,31.0242826 C157.001438,31.6626594 155.795487,32.1666533 154.397064,32.6688094 L154.397064,29.1279886 L152.142579,29.1279886 L152.142579,39.2129188 C152.142579,40.7214545 152.685392,41.3065376 154.298905,41.3065376 L158.000893,41.3065376 L158.746583,39.1867313 L154.397064,39.1867313 L154.397064,34.916337 Z" id="Fill-21" fill="#4D4D4D"></path>
</g>
</g>
</g>
</g>
</svg>
\ No newline at end of file
import Controller from './controller';
import store from '@/commons/reduxs/store';
// import { setRequest } from "@/commons/reduxs/actions";
class Axios {
static async request({
url = '',
type = 'get',
param = {},
actions,
timeout,
isheader = true,
noheader = false,
fileType = false,
other,
}) {
try {
let res = await Controller.awaitAxios({
url,
type,
param,
timeout,
isheader,
noheader,
fileType,
other,
});
return {
code: 200,
status: 'success',
res,
};
} catch (error) {
// actions && store.dispatch(setRequest(error));
if (!actions) {
return {
code: 40001,
status: 'fail',
error: error.response ? error.response.data : error,
};
}
}
}
}
export default Axios;
import axios from "axios";
import { Constants, MESSAGETIP } from "../utils/constants";
import HeadersComponent from "./header";
let headers = {
"Content-Type": "application/json",
"Accept-Language": Constants.ACCEPT_LANGUAGE
};
let headersTemp = {}
class Controller {
static awaitAxios({ url, type, param, store, timeout, isheader, noheader, fileType, other }) {
// 发送 POST 请求
return new Promise((resolve, reject) => {
if (isheader && !HeadersComponent(headers)) {
reject({ message: MESSAGETIP.TOKENERROR, status: "error" })
return
}
if (isheader && typeof isheader === "object") {
headersTemp = {
...headers,
...isheader
}
}
const json = { url: url || '', method: type || 'get', responseType: fileType ? 'arraybuffer' : '', headers: noheader ? {} : (isheader && typeof isheader === "object" ? headersTemp : headers), data: param, timeout: timeout || 200000, ...other }
axios(json).then(function (response) {
if(fileType){
resolve(response)
}else{
resolve(response.data)
}
}).catch((error) => {
reject(error)
});
})
}
}
export default Controller
\ No newline at end of file
import { withToken, getToken, getLanguage } from "../utils/storage";
import UserStore from "@/console/common/stores/UserStore"
const HeadersComponent = (headers) => {
let token = (UserStore.getAccess() && UserStore.getAccess().access_token) || getToken().access_token
if (token) {
headers['Authorization'] = 'Bearer ' + token;
headers['Accept-Language'] = getLanguage() === 'en' ? 'en-US,en;q=0.5' : 'zh-CN,zh,q=0.5';
return true;
}
return false;
}
export default HeadersComponent
\ No newline at end of file
......@@ -4,8 +4,10 @@ import { SnackbarContent } from '@mui/material';
import Button from '@mui/material/Button';
import IconButton from '@mui/material/IconButton';
import CloseIcon from '@mui/icons-material/Close';
import { useNavigate } from 'react-router-dom';
export default () => {
const navigate = useNavigate();
const [onloading, setOnloading] = useState(false);
const [messages, setMessages] = useState({
message: '',
......@@ -17,9 +19,6 @@ export default () => {
const action = (
<React.Fragment>
{/* <Button color="secondary" size="small" onClick={handleClose}>
UNDO
</Button> */}
<IconButton
size="small"
aria-label="close"
......@@ -40,7 +39,7 @@ export default () => {
vertical: 'top',
horizontal: 'center',
}}
open={open}
open={messages['message'] ? true : false}
onClose={handleClose}
message="I love snacks"
key={messages['message']}
......@@ -50,5 +49,5 @@ export default () => {
);
};
return { render, setMessages, setOnloading };
return { render, navigate, setMessages, setOnloading };
};
const BACKEND_API_URI_PREFIX = 'https://www.cloudam.cn';
//const BACKEND_API_URI_PREFIX = 'http://47.57.4.97'
// const BACKEND_API_URI_PREFIX = "http://47.75.104.171";
export { BACKEND_API_URI_PREFIX };
import { BACKEND_API_URI_PREFIX } from './api_address';
const API = {
API_USER_LOGIN: `${BACKEND_API_URI_PREFIX}/uaa/oauth/token`,
};
export default API;
......@@ -10,106 +10,124 @@
*/
import path from 'path';
import { app, BrowserWindow, shell, ipcMain } from 'electron';
import { autoUpdater } from 'electron-updater';
import log from 'electron-log';
import MenuBuilder from './menu';
import { resolveHtmlPath } from './util';
class AppUpdater {
constructor() {
log.transports.file.level = 'info';
autoUpdater.logger = log;
autoUpdater.checkForUpdatesAndNotify();
}
constructor() {
log.transports.file.level = 'info';
autoUpdater.logger = log;
autoUpdater.checkForUpdatesAndNotify();
}
}
let mainWindow: BrowserWindow | null = null;
ipcMain.on('ipc-example', async (event, arg) => {
const msgTemplate = (pingPong: string) => `IPC test: ${pingPong}`;
console.log(msgTemplate(arg));
event.reply('ipc-example', msgTemplate('pong'));
ipcMain.on('resize-me-please', (event) => {
if (mainWindow) {
mainWindow.setContentSize(500, 500);
mainWindow.setResizable(true);
mainWindow.setMaximizable(false);
mainWindow.center();
}
});
if (process.env.NODE_ENV === 'production') {
const sourceMapSupport = require('source-map-support');
sourceMapSupport.install();
const sourceMapSupport = require('source-map-support');
sourceMapSupport.install();
}
const isDebug =
process.env.NODE_ENV === 'development' || process.env.DEBUG_PROD === 'true';
process.env.NODE_ENV === 'development' || process.env.DEBUG_PROD === 'true';
if (isDebug) {
require('electron-debug')();
require('electron-debug')();
}
const installExtensions = async () => {
const installer = require('electron-devtools-installer');
const forceDownload = !!process.env.UPGRADE_EXTENSIONS;
const extensions = ['REACT_DEVELOPER_TOOLS'];
return installer
.default(
extensions.map((name) => installer[name]),
forceDownload
)
.catch(console.log);
const installer = require('electron-devtools-installer');
const forceDownload = !!process.env.UPGRADE_EXTENSIONS;
const extensions = ['REACT_DEVELOPER_TOOLS'];
return installer
.default(
extensions.map((name) => installer[name]),
forceDownload
)
.catch(console.log);
};
const createWindow = async () => {
if (isDebug) {
await installExtensions();
}
const RESOURCES_PATH = app.isPackaged
? path.join(process.resourcesPath, 'assets')
: path.join(__dirname, '../../assets');
const getAssetPath = (...paths: string[]): string => {
return path.join(RESOURCES_PATH, ...paths);
};
mainWindow = new BrowserWindow({
show: false,
width: 1024,
height: 728,
icon: getAssetPath('icon.png'),
webPreferences: {
sandbox: false,
nodeIntegration: true,
contextIsolation: false,
},
});
mainWindow.loadURL(resolveHtmlPath('index.html'));
mainWindow.on('ready-to-show', () => {
if (!mainWindow) {
throw new Error('"mainWindow" is not defined');
if (isDebug) {
await installExtensions();
}
if (process.env.START_MINIMIZED) {
mainWindow.minimize();
} else {
mainWindow.show();
}
});
mainWindow.on('closed', () => {
mainWindow = null;
});
const RESOURCES_PATH = app.isPackaged
? path.join(process.resourcesPath, 'assets')
: path.join(__dirname, '../../assets');
const getAssetPath = (...paths: string[]): string => {
return path.join(RESOURCES_PATH, ...paths);
};
mainWindow = new BrowserWindow({
show: false,
width: 480,
height: 486,
minWidth: 100,
minHeight: 100,
icon: getAssetPath('icon.png'),
frame: true,
resizable: false,
webPreferences: {
sandbox: false,
nodeIntegration: true,
contextIsolation: false,
},
});
const menuBuilder = new MenuBuilder(mainWindow);
menuBuilder.buildMenu();
mainWindow.loadURL(resolveHtmlPath('index.html'));
mainWindow.on('ready-to-show', () => {
if (!mainWindow) {
throw new Error('"mainWindow" is not defined');
}
if (process.env.START_MINIMIZED) {
mainWindow.minimize();
} else {
mainWindow.show();
}
});
// Open urls in the user's browser
mainWindow.webContents.setWindowOpenHandler((edata) => {
shell.openExternal(edata.url);
return { action: 'deny' };
});
mainWindow.on('closed', () => {
mainWindow = null;
});
// Remove this if your app does not use auto updates
// eslint-disable-next-line
new AppUpdater();
const menuBuilder = new MenuBuilder(mainWindow);
menuBuilder.buildMenu();
// Open urls in the user's browser
mainWindow.webContents.setWindowOpenHandler((edata) => {
shell.openExternal(edata.url);
return { action: 'deny' };
});
// Remove this if your app does not use auto updates
// eslint-disable-next-line
new AppUpdater();
// function initLoginWindow(windowObj: any) {
// windowObj.setContentSize(500, 500);
// windowObj.setResizable(false);
// windowObj.setMaximizable(false);
// windowObj.center();
// }
// ipcMain.on('showLoginWindow', (event) => {
// initLoginWindow(mainWindow);
// });
};
/**
......@@ -117,21 +135,20 @@ const createWindow = async () => {
*/
app.on('window-all-closed', () => {
// Respect the OSX convention of having the application in memory even
// after all windows have been closed
if (process.platform !== 'darwin') {
app.quit();
}
// Respect the OSX convention of having the application in memory even
// after all windows have been closed
if (process.platform !== 'darwin') {
app.quit();
}
});
app
.whenReady()
.then(() => {
createWindow();
app.on('activate', () => {
// On macOS it's common to re-create a window in the app when the
// dock icon is clicked and there are no other windows open.
if (mainWindow === null) createWindow();
});
})
.catch(console.log);
app.whenReady()
.then(() => {
createWindow();
app.on('activate', () => {
// On macOS it's common to re-create a window in the app when the
// dock icon is clicked and there are no other windows open.
if (mainWindow === null) createWindow();
});
})
.catch(console.log);
......@@ -4,19 +4,19 @@
*/
body {
position: relative;
color: white;
color: #1E2633;
height: 100vh;
background: linear-gradient(
/* background: linear-gradient(
200.96deg,
#fedc2a -29.09%,
#dd5789 51.77%,
#7a2c9e 129.35%
);
); */
font-family: sans-serif;
overflow-y: hidden;
display: flex;
/* display: flex;
justify-content: center;
align-items: center;
align-items: center; */
}
button {
......
import React from 'react';
import { Grid } from '@mui/material';
import { useNavigate } from 'react-router-dom';
import React, { useEffect, useState } from 'react';
import { shell } from 'electron';
//js
import { createBrowserHistory } from 'history';
import public from 'commons/public';
import Api from '../../commons/utils/api_manager';
//ui
import TextField from '@mui/material/TextField';
import { Button, Grid } from '@mui/material';
import logo from '../../commons/assets/img/logo.svg';
import FormControlLabel from '@mui/material/FormControlLabel';
import Checkbox from '@mui/material/Checkbox';
import { makeStyles } from 'tss-react/mui';
import Button from '@mui/material/Button';
const history = createBrowserHistory();
console.log('history: ', history);
const { ipcRenderer } = require('electron');
const electron = window.require('electron');
const axios = require('axios');
const useStyles = makeStyles()((theme) => {
return {
loginBody: {
display: 'flex',
alignItems: 'center',
flexDirection: 'column',
width: '300px',
margin: '0 auto',
marginTop: '10%',
},
root: {
height: '40px',
width: '300px',
},
input: {
padding: '7.5px 14px',
},
label: {
fontSize: '14px',
overflow: 'inherit',
lineHeight: '8px',
},
label2: {
fontSize: '14px',
},
rootButton: {
backgroundColor: '#1370FF',
boxShadow: 'none !important',
color: '#ffffff',
'&:hover': { backgroundColor: '#0055D9', transform: 'inherit' },
},
};
});
export default (props) => {
console.log('props: ', props);
const { render } = public();
const navigate = useNavigate();
const { classes } = useStyles();
const { render, navigate } = public();
const [username, setUsername] = useState('');
const [usernameError, setUsernameError] = useState(false);
const [password, setPassword] = useState('');
const [passwordError, setPasswordError] = useState(false);
const [checked, setChecked] = useState(true);
useEffect(() => {
// electron.ipcRenderer.send('resize-me-please');
}, []);
const handleChange = (event) => {
setChecked(event.target.checked);
};
const login = () => {
if (username.length === 0) {
setUsernameError(true);
return;
} else if (password.length === 0) {
setPasswordError(true);
return;
}
};
return render(
<Grid>
<Grid className={classes.loginBody}>
<img
src={logo}
alt=""
style={{ width: '187px', marginBottom: '50px' }}
/>
<Grid sx={{ marginBottom: '16px' }}>
<TextField
id="username"
label="用户名/手机号"
variant="outlined"
onChange={(e) => {
setUsernameError(false);
setUsername(e.target.value);
}}
error={usernameError}
helperText={usernameError ? '请输入您的用户名' : ''}
InputLabelProps={{
classes: {
outlined: classes.label,
},
}}
InputProps={{
classes: {
root: classes.root,
input: classes.input,
},
}}
/>
</Grid>
<Grid sx={{ marginBottom: '16px' }}>
<TextField
id="username"
label="密码"
variant="outlined"
type={'password'}
onChange={(e) => {
setPasswordError(false);
setPassword(e.target.value);
}}
error={passwordError}
helperText={passwordError ? '请输入您的密码' : ''}
InputLabelProps={{
classes: {
outlined: classes.label,
},
}}
InputProps={{
classes: {
root: classes.root,
input: classes.input,
},
}}
/>
</Grid>
<Grid
onClick={() => {
navigate('/test');
sx={{
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
width: '100%',
marginBottom: '20px',
}}
>
<FormControlLabel
classes={{
label: classes.label2,
}}
control={
<Checkbox checked={checked} onChange={handleChange} />
}
label="记住登录状态"
/>
<Grid
sx={{
fontSize: '14px',
color: '#1370FF',
cursor: 'pointer',
}}
onClick={() => {
shell.openExternal(
'https://www.bkunyun.com/v2/pages/forgot-password-page'
);
}}
>
忘记密码
</Grid>
</Grid>
console.log('2222222222222');
<Button
variant="contained"
sx={{ width: '300px' }}
classes={{
root: classes.rootButton,
}}
onClick={() => {
login();
// navigate('/test');
// electron.ipcRenderer.send('resize-me-please');
}}
>
eee2222222222wwww222
登录
</Button>
<Grid sx={{ display: 'flex', fontSize: '14px', marginTop: '44px' }}>
新用户?
<span
style={{ color: '#1370FF', cursor: 'pointer' }}
onClick={() => {
// electron.ipcRenderer.send('test');
shell.openExternal(
'https://www.bkunyun.com/v2/pages/register-page'
);
}}
>
创建帐号
</span>
</Grid>
<Grid onClick={() => {}}>1111111</Grid>
</Grid>
);
};
import React from 'react';
import { Grid } from '@mui/material';
import public from 'commons/public';
export default (props) => {
console.log('props: ', props);
const { render, navigate } = public();
return (
<Grid
onClick={() => {
console.log('2222222222222');
navigate('/');
}}
>
11111111111
......
......@@ -6,7 +6,7 @@
http-equiv="Content-Security-Policy"
content="script-src 'self' 'unsafe-inline'"
/>
<title>Hello Electron React!</title>
<title>北鲲云盘</title>
</head>
<body>
<div id="root"></div>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment