Browse Source

feat: 登录。注册页面,接口,后端还未完成

tuonian 1 year ago
parent
commit
5132f41f15

File diff suppressed because it is too large
+ 0 - 8
dist/assets/index-2d0a5350.js


File diff suppressed because it is too large
+ 0 - 0
dist/assets/index-649708a6.css


+ 45 - 45
dist/index.html

@@ -1,46 +1,46 @@
-<!DOCTYPE html><html lang="en"><head>
-    <meta charset="UTF-8">
-    <link rel="icon" type="image/svg+xml" href="/nginx-ui/vite.svg">
-    <meta name="viewport" content="width=device-width, initial-scale=1.0">
-    <title>NginxUI</title>
-    <script type="application/javascript" src="./config.js"></script>
-    <script crossorigin="">import('/nginx-ui/assets/index-96b4a255.js').finally(() => {
-            
-    const qiankunLifeCycle = window.moudleQiankunAppLifeCycles && window.moudleQiankunAppLifeCycles['nginx-ui'];
-    if (qiankunLifeCycle) {
-      window.proxy.vitemount((props) => qiankunLifeCycle.mount(props));
-      window.proxy.viteunmount((props) => qiankunLifeCycle.unmount(props));
-      window.proxy.vitebootstrap(() => qiankunLifeCycle.bootstrap());
-      window.proxy.viteupdate((props) => qiankunLifeCycle.update(props));
-    }
-  
-          })</script>
-    <link rel="stylesheet" href="/nginx-ui/assets/index-976dda2b.css">
-  </head>
-  <body>
-    <div id="nginx_ui_root"></div>
-    
-  
-
-<script>
-  const createDeffer = (hookName) => {
-    const d = new Promise((resolve, reject) => {
-      window.proxy && (window.proxy[`vite${hookName}`] = resolve)
-    })
-    return props => d.then(fn => fn(props));
-  }
-  const bootstrap = createDeffer('bootstrap');
-  const mount = createDeffer('mount');
-  const unmount = createDeffer('unmount');
-  const update = createDeffer('update');
-
-  ;(global => {
-    global.qiankunName = 'nginx-ui';
-    global['nginx-ui'] = {
-      bootstrap,
-      mount,
-      unmount,
-      update
-    };
-  })(window);
+<!DOCTYPE html><html lang="en"><head>
+    <meta charset="UTF-8">
+    <link rel="icon" type="image/svg+xml" href="/nginx-ui/vite.svg">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>NginxUI</title>
+    <script type="application/javascript" src="./config.js"></script>
+    <script crossorigin="">import('/nginx-ui/assets/index-2d0a5350.js').finally(() => {
+            
+    const qiankunLifeCycle = window.moudleQiankunAppLifeCycles && window.moudleQiankunAppLifeCycles['nginx-ui'];
+    if (qiankunLifeCycle) {
+      window.proxy.vitemount((props) => qiankunLifeCycle.mount(props));
+      window.proxy.viteunmount((props) => qiankunLifeCycle.unmount(props));
+      window.proxy.vitebootstrap(() => qiankunLifeCycle.bootstrap());
+      window.proxy.viteupdate((props) => qiankunLifeCycle.update(props));
+    }
+  
+          })</script>
+    <link rel="stylesheet" href="/nginx-ui/assets/index-649708a6.css">
+  </head>
+  <body>
+    <div id="nginx_ui_root"></div>
+    
+  
+
+<script>
+  const createDeffer = (hookName) => {
+    const d = new Promise((resolve, reject) => {
+      window.proxy && (window.proxy[`vite${hookName}`] = resolve)
+    })
+    return props => d.then(fn => fn(props));
+  }
+  const bootstrap = createDeffer('bootstrap');
+  const mount = createDeffer('mount');
+  const unmount = createDeffer('unmount');
+  const update = createDeffer('update');
+
+  ;(global => {
+    global.qiankunName = 'nginx-ui';
+    global['nginx-ui'] = {
+      bootstrap,
+      mount,
+      unmount,
+      update
+    };
+  })(window);
 </script></body></html>

+ 1 - 0
package.json

@@ -31,6 +31,7 @@
     "lodash": "^4.17.21",
     "npm": "^9.8.0",
     "planning-tools": "^0.1.1",
+    "query-string": "^8.1.0",
     "react": "^18.2.0",
     "react-dom": "^18.2.0",
     "react-redux": "^8.1.1",

+ 7 - 0
server/config/config.go

@@ -28,6 +28,13 @@ type CompleteOauth2Config struct {
 
 var OauthConfig = &CompleteOauth2Config{
 	Enable: false,
+	Config: &oauth2.Config{
+		ClientID:     "",
+		ClientSecret: "",
+		Endpoint:     oauth2.Endpoint{},
+		RedirectURL:  "",
+		Scopes:       []string{},
+	},
 }
 
 var Config = &AppConfig{}

+ 15 - 3
server/controllers/oauth2.go

@@ -16,6 +16,12 @@ type Oauth2Controller struct {
 	BaseController
 }
 
+type Oauth2SSOReq struct {
+	Code  string `json:"code"`
+	Scope string `json:"scope"`
+	State string `json:"state"`
+}
+
 // Get 获取oauth2.0的登录url
 func (c *Oauth2Controller) Get() {
 	state, err := utils.RandPassword(6)
@@ -29,13 +35,19 @@ func (c *Oauth2Controller) Get() {
 
 // Callback 用户注册
 func (c *Oauth2Controller) Callback() {
+	var ssoReq Oauth2SSOReq
+	err := json.Unmarshal(c.Ctx.Input.RequestBody, &ssoReq)
+	if err != nil {
+		logs.Error(err, string(c.Ctx.Input.RequestBody))
+		c.ErrorJson(err)
+		return
+	}
 	oauth := config.OauthConfig
-	code := c.GetString("code", "")
-	if len(code) == 0 {
+	if len(ssoReq.Code) == 0 {
 		c.setCode(-1).setMsg("登录失败(Code):code is empty").json()
 		return
 	}
-	token, err := oauth.Exchange(context.Background(), code)
+	token, err := oauth.Exchange(context.Background(), ssoReq.Code)
 	if err != nil {
 		logs.Error("ExchangeToken", err)
 		c.setCode(-1).setMsg("登录失败(Exchange):" + err.Error()).json()

+ 8 - 0
server/controllers/user.go

@@ -38,6 +38,14 @@ func (c *UserController) Login() {
 	c.setData(user).json()
 }
 
+func (c *UserController) User() {
+	user := c.RequiredUser()
+	if user == nil {
+		return
+	}
+	c.setData(user).json()
+}
+
 // Register 用户注册
 func (c *UserController) Register() {
 	var user models.User

BIN
server/data/db/sqlite.db


+ 4 - 1
server/middleware/auth.go

@@ -21,6 +21,8 @@ var whitelist = map[string]bool{
 	"/oauth2/callback": true,
 }
 
+var UnauthorizedResp = `{"code": 401, "msg":"未登录或者登录已过期!"}`
+
 type ThirdSession struct {
 	Enable     bool
 	CookieName string
@@ -101,7 +103,8 @@ func AuthFilter(ctx *context.Context) {
 
 func WriteForbidden(w http.ResponseWriter) {
 	w.WriteHeader(401)
-	_, err := w.Write([]byte("401 Unauthorized\n"))
+	w.Header().Set("Content-Type", "application/json")
+	_, err := w.Write([]byte(UnauthorizedResp))
 	if err != nil {
 		logs.Warn("writeForbidden write error", err)
 		return

+ 3 - 2
server/routers/router.go

@@ -34,9 +34,10 @@ func init() {
 		beego.NSRouter("/file/deploy", &controllers.FileController{}, "post:Deploy"),
 
 		beego.NSRouter("/user/login", &controllers.UserController{}, "post:Login"),
+		beego.NSRouter("/user/info", &controllers.UserController{}, "get:User"),
 		beego.NSRouter("/user/register", &controllers.UserController{}, "post:Register"),
-		beego.NSRouter("/oauth2", &controllers.UserController{}),
-		beego.NSRouter("/oauth2/callback", &controllers.UserController{}, "post:Callback"),
+		beego.NSRouter("/oauth2", &controllers.Oauth2Controller{}),
+		beego.NSRouter("/oauth2/callback", &controllers.Oauth2Controller{}, "post:Callback"),
 	)
 	beego.AddNamespace(ns)
 

+ 5 - 0
src/api/request.ts

@@ -1,6 +1,8 @@
 import axios, {AxiosResponse} from 'axios';
 import {BaseResp} from "../models/api.ts";
 import {Message, Notify} from "planning-tools";
+import {store} from "../store";
+import {UserActions} from "../store/slice/user.ts";
 console.log('env', import.meta.env)
 
 // eslint-disable-next-line @typescript-eslint/ban-ts-comment
@@ -61,6 +63,9 @@ request.interceptors.response.use((resp: AxiosResponse<BaseResp>)=>{
     errData.msg = 'request fail'
   }
   (!disableErrorMsg)&& Message.error(errData.msg)
+  if (error.response.statusCode == 401){
+    store.dispatch(UserActions.clearUser())
+  }
   return Promise.reject(errData)
 })
 

+ 29 - 0
src/api/user.ts

@@ -0,0 +1,29 @@
+import request from "./request.ts";
+
+export type LoginReq = {
+    account: string
+    password: string
+}
+
+export type RegisterReq = LoginReq & {
+    nickname?: string
+}
+
+export type SSOReq = {
+    code: string
+    scope: string
+    state: string
+}
+
+/**
+ * 登录相关的API
+ */
+export const LoginApis = {
+
+    login: (data: LoginReq) => request.post('/user/login', data),
+    signUp: (data: RegisterReq) => request.post('/user/register', data),
+    userinfo: () => request.get('/user/info', { disableErrorMsg: true } as never),
+    oauth2Url: ()=> request.get('/oauth2'),
+    oauth2Callback: (data: SSOReq) => request.post('/oauth2/callback', data)
+
+}

+ 10 - 0
src/models/user.ts

@@ -0,0 +1,10 @@
+/**
+ * 用户
+ */
+export type User = {
+    id: number
+    account: string
+    nickname: string
+    roles?: string
+    remark?: string
+}

+ 36 - 0
src/pages/login/index.less

@@ -0,0 +1,36 @@
+.login-page{
+  width: 100%;
+  height: 100%;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  background: #efefef;
+  .login-container{
+    width: 400px;
+    max-width: 100%;
+    min-height: 300px;
+    background: white;
+    padding: 10px 20px 20px;
+    border-radius: 10px;
+    .ant-form{
+      margin-top: 15px;
+    }
+    .login-btn{
+      text-align: center;
+      display: flex;
+      flex-direction: row;
+      align-items: flex-end;
+      padding-left: 60px;
+      .signup{
+        font-size: 12px;
+        line-height: 15px;
+        color: #666666;
+        margin-left: 10px;
+        a{
+          color: #1890ff;
+        }
+      }
+    }
+  }
+}

+ 129 - 0
src/pages/login/index.tsx

@@ -0,0 +1,129 @@
+
+import './index.less'
+import {Button, Form, Input, Spin, Tabs} from "antd";
+import {TabsProps} from "antd/lib/tabs";
+import {Link} from "react-router-dom";
+import {LoginApis, LoginReq, SSOReq} from "../../api/user.ts";
+import {useEffect, useState} from "react";
+import {useAppDispatch} from "../../store";
+import {UserActions} from "../../store/slice/user.ts";
+import {Message} from "planning-tools";
+import {useNavigate } from "react-router";
+import {useQuery} from "../../utils";
+
+const AccountPanel = ()=>{
+
+    const [loading,setLoading] = useState(false)
+    const dispatch = useAppDispatch()
+    const navigate = useNavigate()
+    const query = useQuery<{to?: string}>()
+
+    const onSubmit = (values: LoginReq)=>{
+        console.log('submit',values);
+        setLoading(true);
+        LoginApis.login(values)
+            .then(({data})=>{
+                console.log('login resp',data)
+                if (data.data){
+                    dispatch(UserActions.setUser(data.data));
+                    navigate(query?.to || '/')
+                }
+                if (data.code == 0){
+                    Message.success(data.msg)
+                }else {
+                    Message.warning(data.msg)
+                }
+            })
+            .finally(()=>{
+                setLoading(false)
+            })
+    }
+
+    return (
+        <Form onFinish={onSubmit} labelCol={{span: 4}}>
+            <Form.Item name="account" label="账号" rules={[{required: true,message: '请输入账号'}]}>
+                <Input placeholder="请输入账号" />
+            </Form.Item>
+            <Form.Item name="password" label="密码"  rules={[{required: true,message: '请输入密码'}]}>
+                <Input.Password placeholder="请输入密码" />
+            </Form.Item>
+            <div className="login-btn">
+                <Button loading={loading} htmlType="submit" type="primary">登录</Button>
+                <span className="signup">没有账号?<Link to="/signup">去注册</Link></span>
+            </div>
+        </Form>
+    )
+}
+
+
+export const LoginPage = ()=>{
+
+    const [activeKey,setActiveKey] = useState('account')
+    const ssoData = useQuery<SSOReq & {to?: string}>()
+    const [loading,setLoading] = useState(false)
+    const navigate = useNavigate()
+
+
+
+    const fetchSSO = ()=>{
+        LoginApis.oauth2Url()
+            .then(({data})=>{
+                if (data.code == 0){
+                    window.location.href = data.data.redirect_url
+                }else {
+                    Message.warning(data.msg)
+                }
+            })
+    }
+
+    const onSSOCallback = (data: SSOReq)=>{
+        setLoading(true);
+        LoginApis.oauth2Callback(data)
+            .then(({data})=>{
+                if (data.code === 0){
+                    Message.success(data.msg);
+                    navigate(ssoData?.to || '/')
+                }else {
+                    Message.warning(data.msg)
+                }
+            })
+            .finally(()=>{
+                setLoading(false)
+            })
+    }
+
+    const onActiveKeyChange = (ak: string)=>{
+        if (ak === 'sso'){
+            setActiveKey(ak);
+            fetchSSO()
+        }else if (!loading){
+            setActiveKey(ak);
+        }
+    }
+
+    useEffect(()=>{
+        if (ssoData?.code){
+            onSSOCallback(ssoData)
+        }
+        console.log('ssoData', ssoData)
+    },[ssoData])
+
+    const tabItems:TabsProps["items"] = [
+        {
+            label: '账号密码',
+            key: 'account',
+            children: <AccountPanel />
+        },
+        {
+            label: "SSO",
+            key: 'sso',
+            children: <Spin />
+        }
+    ]
+
+    return (<div className="login-page">
+        <div className="login-container">
+            <Tabs activeKey={activeKey} onChange={onActiveKeyChange} items={tabItems}></Tabs>
+        </div>
+    </div>)
+}

+ 33 - 0
src/pages/signup/index.less

@@ -0,0 +1,33 @@
+.signup-page{
+  background: #efefef;
+  width: 100%;
+  height: 100%;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  .container{
+    width: 400px;
+    background: white;
+    padding: 20px;
+    border-radius: 10px;
+    h5{
+      margin-bottom: 15px;
+      text-align: center;
+      font-size: 18px;
+      font-weight: bold;
+      border-bottom: dashed 1px #efefef;
+      padding-bottom: 10px;
+    }
+    .login-btn{
+      padding-left: 60px;
+      .signup{
+        margin-left: 5px;
+        font-size: 13px;
+        a{
+          color: #1890ff;
+        }
+      }
+    }
+  }
+}

+ 50 - 0
src/pages/signup/index.tsx

@@ -0,0 +1,50 @@
+import './index.less'
+import {Button, Form, Input} from "antd";
+import {Link} from "react-router-dom";
+import {useState} from "react";
+import {LoginApis} from "../../api/user.ts";
+import {useNavigate} from "react-router";
+import {Message} from "planning-tools";
+
+export const SignupPage = ()=>{
+
+    const [loading,setLoading] = useState(false)
+    const navigate = useNavigate()
+
+    const onSubmit = (values: any)=>{
+        setLoading(true)
+        LoginApis.signUp(values)
+            .then(({data})=>{
+                if (data.code === 0){
+                    Message.success(data.msg);
+                    navigate('/login')
+                }else {
+                    Message.warning(data.msg)
+                }
+            })
+            .finally(()=>{
+                setLoading(false)
+            })
+    }
+
+    return (<div className="signup-page">
+        <div className="container">
+            <h5>注册</h5>
+            <Form onFinish={onSubmit} labelCol={{span: 4}}>
+                <Form.Item name="account" label="账号" rules={[{required: true,message: '请输入账号'}]}>
+                    <Input placeholder="请输入账号" />
+                </Form.Item>
+                <Form.Item name="password" label="密码"  rules={[{required: true,message: '请输入密码'}]}>
+                    <Input.Password placeholder="请输入密码" />
+                </Form.Item>
+                <Form.Item name="nickname" label="姓名">
+                    <Input placeholder="请输入姓名" />
+                </Form.Item>
+                <div className="login-btn">
+                    <Button loading={loading} htmlType="submit" type="primary">提交</Button>
+                    <span className="signup">已有账号?<Link to="/login">去登录</Link></span>
+                </div>
+            </Form>
+        </div>
+    </div>)
+}

+ 15 - 0
src/routes/index.less

@@ -0,0 +1,15 @@
+.empty-loading{
+  height: 100%;
+  width: 100%;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  .ant-spin-dot{
+    font-size: 30px;
+  }
+  .hint-msg{
+    font-weight: lighter;
+    color: #666666;
+  }
+}

+ 132 - 74
src/routes/index.tsx

@@ -1,4 +1,4 @@
-import { Routes,Route, HashRouter} from 'react-router-dom';
+import {Routes, Route, HashRouter} from 'react-router-dom';
 import {NginxList} from "../pages/nginx/list.tsx";
 import {Nginx} from "../pages/nginx";
 import {NginxSettings} from "../pages/nginx/settings";
@@ -11,6 +11,16 @@ import {NginxUpstream} from "../pages/nginx/upstream";
 import {NginxCerts} from "../pages/nginx/certs";
 import {NginxStream} from "../pages/nginx/stream";
 import {HelpPage} from "../pages/nginx/help";
+import {LoginApis} from "../api/user.ts";
+import {useEffect, useState} from "react";
+import {useAppDispatch, useAppSelector} from "../store";
+import {Spin} from "antd";
+import * as React from "react";
+import './index.less'
+import {useLocation, useNavigate} from "react-router";
+import {LoginPage} from "../pages/login";
+import {SignupPage} from "../pages/signup";
+import {UserActions} from "../store/slice/user.ts";
 
 /**
  * @author tuonian
@@ -18,82 +28,130 @@ import {HelpPage} from "../pages/nginx/help";
  */
 
 const nginxRoutes = [
-  {
-    path: '/nginx/:id',
-    component: Nginx,
-    children: [
-      {
-        index: true,
-        path: '',
-        component: NginxSettings
-      },
-      {
-        path: 'http',
-        component: NginxHttp
-      },
-      {
-        path:  'certs',
-        component: NginxCerts
-      },
-      {
-        path: 'upstream',
-        component: NginxUpstream
-      },
-      {
-        path:  'stream',
-        component: NginxStream
-      },
-      {
-        path: 'server/:sid',
-        component: NginxServer
-      },
-      {
-        path: 'server/:sid/conf',
-        component: NginxServer
-      },
-      {
-        path: 'server/:sid/location/:locId',
-        component: ServerLocation
-      },
-      {
-        path: 'server/:sid/location-new',
-        component: NewLocation
-      },
-      {
-        path: 'server-new',
-        component: NewServer
-      },
-      {
-        path: 'help',
+    {
+        path: '/nginx/:id',
+        component: Nginx,
+        children: [
+            {
+                index: true,
+                path: '',
+                component: NginxSettings
+            },
+            {
+                path: 'http',
+                component: NginxHttp
+            },
+            {
+                path: 'certs',
+                component: NginxCerts
+            },
+            {
+                path: 'upstream',
+                component: NginxUpstream
+            },
+            {
+                path: 'stream',
+                component: NginxStream
+            },
+            {
+                path: 'server/:sid',
+                component: NginxServer
+            },
+            {
+                path: 'server/:sid/conf',
+                component: NginxServer
+            },
+            {
+                path: 'server/:sid/location/:locId',
+                component: ServerLocation
+            },
+            {
+                path: 'server/:sid/location-new',
+                component: NewLocation
+            },
+            {
+                path: 'server-new',
+                component: NewServer
+            },
+            {
+                path: 'help',
+                component: HelpPage
+            }
+        ]
+    },
+    {
+        path: '/nginx/help',
         component: HelpPage
-      }
-    ]
-  },
-  {
-    path: '/nginx/help',
-    component: HelpPage
-  }
+    }
 ]
 
-export const MyRouter = ()=>{
+type RouteWrapperProps = {
+    Component: React.ComponentType
+    [key: string]: any
+}
+export const RouteWrapper = ({Component, ...props}: RouteWrapperProps) => {
+
+    const [loading, setLoading] = useState(false)
+    const user = useAppSelector(state => state.user.user)
+    const navigate = useNavigate()
+    const location = useLocation()
+    const dispatch = useAppDispatch()
+
+    const fetchUser = () => {
+        setLoading(true);
+        LoginApis.userinfo().then(({data}) => {
+          dispatch(UserActions.setUser(data.data))
+            console.log('fetchUser', data)
+        }).catch(e => {
+            console.warn('userinfo fail', e);
+            navigate('/login?to=' + location.pathname)
+        })
+            .finally(() => {
+                setLoading(false)
+            })
+    }
+
+    useEffect(() => {
+        if (user?.account) {
+            setLoading(false)
+        } else {
+            fetchUser()
+        }
+    }, [user])
+
+    if (!user || loading) {
+        return (<div className="empty-loading">
+            <Spin></Spin>
+            <div className="hint-msg">加载中,请稍等...</div>
+        </div>)
+    }
+
+    return <Component {...props} />
+}
+
+export const MyRouter = () => {
+
 
- return (
-   <HashRouter basename={'/'}>
-     <Routes >
-       <Route path='/' Component={NginxList}/>
-       {
-         nginxRoutes.map((r)=>{
-           return (
-             <Route key={r.path} path={r.path} Component={r.component}>
-               {r.children?.map((c, cidx)=>{
-                 return (<Route key={r.path + cidx} index={c.index} path={c.path} Component={c.component} /> )
-               })}
-             </Route>
-           )
-         })
-       }
-     </Routes>
-   </HashRouter>
- )
+    return (
+        <HashRouter basename={'/'}>
+            <Routes>
+                <Route path='/' element={<RouteWrapper Component={NginxList}/>}/>
+                {
+                    nginxRoutes.map((r) => {
+                        return (
+                            <Route key={r.path} path={r.path} element={<RouteWrapper Component={r.component}/>}>
+                                {r.children?.map((c, cidx) => {
+                                    return (<Route key={r.path + cidx} index={c.index} path={c.path}
+                                                   element={<RouteWrapper Component={c.component}/>}/>)
+                                })}
+                            </Route>
+                        )
+                    })
+                }
+                <Route path="/login" Component={LoginPage} />
+              <Route path="/signup" Component={SignupPage} />
+            </Routes>
+        </HashRouter>
+    )
 }
 

+ 3 - 2
src/store/slice/user.ts

@@ -1,7 +1,8 @@
 import { createSlice, PayloadAction } from '@reduxjs/toolkit';
+import {User} from "../../models/user.ts";
 
 export type IUserState = {
-  user?: any;
+  user?: User;
   isLogin: boolean;
   isAdmin?: boolean;
 };
@@ -15,7 +16,7 @@ const userSlice = createSlice({
   name: 'user',
   initialState,
   reducers: {
-    setUser(state, action: PayloadAction<any>) {
+    setUser(state, action: PayloadAction<User>) {
       state.user = action.payload;
       state.isLogin = true;
       const roles = state.user?.roles || '';

+ 37 - 0
src/utils/index.ts

@@ -0,0 +1,37 @@
+import {useLocation} from "react-router";
+import {useEffect, useState} from "react";
+import querystring from "query-string";
+
+export const getFirst = (item: string | null | Array<string | null>)=>{
+    if (!item){
+        return ''
+    }
+    if (Array.isArray(item)){
+        return item.length ? item[0] ? item[0]: '' : ''
+    }
+    return item || ''
+}
+
+export function useQuery<T>  (){
+    const [query,setQuery] = useState<T>()
+    
+    const location = useLocation()
+
+    useEffect(()=>{
+        if (!location.search){
+            setQuery(undefined)
+            return
+        }
+        const query:querystring.ParsedQuery =  querystring.parse(location.search)
+        if (!query.code){
+            return;
+        }
+        const newQuery:any = {}
+        Object.keys(query).forEach(k=>{
+            newQuery[k] = getFirst(query[k])
+        })
+        setQuery(newQuery)
+    },[location])
+
+    return query
+}

+ 2 - 1
vite.config.ts

@@ -43,7 +43,8 @@ export default defineConfig({
   server:{
     proxy: {
       "/api":{
-        target: 'http://10.10.0.1:8081',
+        // target: 'http://10.10.0.1:8081',
+        target: 'http://127.0.0.1:8080',
         rewrite: path => path.replace(/^\/api/,"")
       }
     }

+ 24 - 0
yarn.lock

@@ -1993,6 +1993,11 @@ decode-named-character-reference@^1.0.0:
   dependencies:
     character-entities "^2.0.0"
 
+decode-uri-component@^0.4.1:
+  version "0.4.1"
+  resolved "https://mirrors.tencent.com/npm/decode-uri-component/-/decode-uri-component-0.4.1.tgz#2ac4859663c704be22bf7db760a1494a49ab2cc5"
+  integrity sha512-+8VxcR21HhTy8nOt6jf20w0c9CADrw1O8d+VZ/YzzCt4bJ3uBjw+D1q2osAB8RnpwwaeYBxy0HyKQxD5JBMuuQ==
+
 deep-eql@^4.1.2:
   version "4.1.3"
   resolved "https://registry.npmmirror.com/deep-eql/-/deep-eql-4.1.3.tgz#7c7775513092f7df98d8df9996dd085eb668cc6d"
@@ -2581,6 +2586,11 @@ fill-range@^7.0.1:
   dependencies:
     to-regex-range "^5.0.1"
 
+filter-obj@^5.1.0:
+  version "5.1.0"
+  resolved "https://mirrors.tencent.com/npm/filter-obj/-/filter-obj-5.1.0.tgz#5bd89676000a713d7db2e197f660274428e524ed"
+  integrity sha512-qWeTREPoT7I0bifpPUXtxkZJ1XJzxWtfoWWkdVGqa+eCr3SHW/Ocp89o8vLvbUuQnadybJpjOKu4V+RwO6sGng==
+
 find-up@^5.0.0:
   version "5.0.0"
   resolved "https://registry.npmmirror.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc"
@@ -4737,6 +4747,15 @@ qrcode-terminal@^0.12.0:
   resolved "https://registry.npmmirror.com/qrcode-terminal/-/qrcode-terminal-0.12.0.tgz#bb5b699ef7f9f0505092a3748be4464fe71b5819"
   integrity sha512-EXtzRZmC+YGmGlDFbXKxQiMZNwCLEO6BANKXG4iCtSIM0yqc/pappSx3RIKr4r0uh5JsBckOXeKrB3Iz7mdQpQ==
 
+query-string@^8.1.0:
+  version "8.1.0"
+  resolved "https://mirrors.tencent.com/npm/query-string/-/query-string-8.1.0.tgz#e7f95367737219544cd360a11a4f4ca03836e115"
+  integrity sha512-BFQeWxJOZxZGix7y+SByG3F36dA0AbTy9o6pSmKFcFz7DAj0re9Frkty3saBn3nHo3D0oZJ/+rx3r8H8r8Jbpw==
+  dependencies:
+    decode-uri-component "^0.4.1"
+    filter-obj "^5.1.0"
+    split-on-first "^3.0.0"
+
 queue-microtask@^1.2.2:
   version "1.2.3"
   resolved "https://registry.npmmirror.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243"
@@ -5588,6 +5607,11 @@ spdx-license-ids@^3.0.0:
   resolved "https://registry.npmmirror.com/spdx-license-ids/-/spdx-license-ids-3.0.13.tgz#7189a474c46f8d47c7b0da4b987bb45e908bd2d5"
   integrity sha512-XkD+zwiqXHikFZm4AX/7JSCXA98U5Db4AFd5XUg/+9UNtnH75+Z9KxtpYiJZx36mUDVOwH83pl7yvCer6ewM3w==
 
+split-on-first@^3.0.0:
+  version "3.0.0"
+  resolved "https://mirrors.tencent.com/npm/split-on-first/-/split-on-first-3.0.0.tgz#f04959c9ea8101b9b0bbf35a61b9ebea784a23e7"
+  integrity sha512-qxQJTx2ryR0Dw0ITYyekNQWpz6f8dGd7vffGNflQQ3Iqj9NJ6qiZ7ELpZsJ/QBhIVAiDfXdag3+Gp8RvWa62AA==
+
 ssri@^10.0.0, ssri@^10.0.1, ssri@^10.0.4:
   version "10.0.4"
   resolved "https://registry.npmmirror.com/ssri/-/ssri-10.0.4.tgz#5a20af378be586df139ddb2dfb3bf992cf0daba6"

Some files were not shown because too many files changed in this diff