Compare commits

..

6 Commits

Author SHA1 Message Date
CrescentLeaf
2d48d2f536 feat(client): 登录注册 2025-12-06 17:01:24 +08:00
CrescentLeaf
4214ed9e10 睡觉 2025-12-06 17:01:15 +08:00
CrescentLeaf
198493cac1 等待对话框 2025-12-06 17:01:07 +08:00
CrescentLeaf
f57347b834 共享上下文 2025-12-06 17:00:58 +08:00
CrescentLeaf
f9dff68339 fix: stupid forgetting sha256 2025-12-06 16:58:20 +08:00
CrescentLeaf
48bd884690 fix(cp): 错误的注册方法返回值
* 不是, 我用户 ID 呢
2025-12-06 16:53:35 +08:00
7 changed files with 146 additions and 38 deletions

View File

@@ -180,18 +180,6 @@ export default class LingChairClient {
else
throw new CallbackError(re)
}
async register(args: {
nickname: string,
username?: string,
password: string,
}) {
try {
await this.registerOrThrow(args)
return true
} catch (_) {
return false
}
}
getBaseHttpUrl() {
const url = new URL(this.server_url)
return (({
@@ -204,6 +192,17 @@ export default class LingChairClient {
getUrlForFileByHash(file_hash?: string, defaultUrl?: string) {
return file_hash ? (this.getBaseHttpUrl() + '/uploaded_files/' + file_hash) : defaultUrl
}
async register(args: {
nickname: string,
username?: string,
password: string,
}) {
try {
return await this.registerOrThrow(args)
} catch (_) {
return null
}
}
async registerOrThrow({
nickname,
username,
@@ -216,10 +215,11 @@ export default class LingChairClient {
const re = await this.invoke('User.register', {
nickname,
username,
password,
password: crypto.createHash('sha256').update(password).digest('hex'),
})
if (re.code != 200)
throw new CallbackError(re)
return re.data!.user_id as string
}
async uploadFile({
chatId,

View File

@@ -2,16 +2,17 @@ import isMobileUI from "../utils/isMobileUI.ts"
import useEventListener from "../utils/useEventListener.ts"
import AvatarMySelf from "./AvatarMySelf.tsx"
import MainSharedContext from './MainSharedContext.ts'
import React from "react"
import * as React from 'react'
import { BrowserRouter, Outlet, Route, Routes } from "react-router"
import LoginDialog from "./main-page/LoginDialog.tsx"
import useAsyncEffect from "../utils/useAsyncEffect.ts"
import performAuth from "../performAuth.ts"
import { CallbackError } from "lingchair-client-protocol"
import showCircleProgressDialog from "./showCircleProgressDialog.ts"
import RegisterDialog from "./main-page/RegisterDialog.tsx"
import sleep from "../utils/sleep.ts"
export default function Main() {
const [showLoginDialog, setShowLoginDialog] = React.useState(false)
// 多页面切换
const navigationRef = React.useRef<HTMLElement>()
const [currentShowPage, setCurrentShowPage] = React.useState('Recents')
@@ -20,14 +21,19 @@ export default function Main() {
setCurrentShowPage((event.target as HTMLElementWithValue).value)
})
const [showLoginDialog, setShowLoginDialog] = React.useState(false)
const [showRegisterDialog, setShowRegisterDialog] = React.useState(false)
const sharedContext = {
ui_functions: React.useRef({
}),
setShowLoginDialog,
setShowRegisterDialog,
}
useAsyncEffect(async () => {
const waitingForAuth = showCircleProgressDialog("验证中...")
try {
await performAuth({})
} catch (e) {
@@ -35,6 +41,9 @@ export default function Main() {
if (e.code == 401 || e.code == 400)
setShowLoginDialog(true)
}
// 动画都没来得及, 稍微等一下 (
await sleep(100)
waitingForAuth.open = false
})
return (
@@ -53,6 +62,7 @@ export default function Main() {
<Outlet />
}
<LoginDialog open={showLoginDialog} />
<RegisterDialog open={showRegisterDialog} />
{
/**
* Default: 侧边列表提供列表切换

View File

@@ -1,5 +1,12 @@
import { createContext } from 'react'
const MainSharedContext = createContext({})
type shared = {
ui_functions: React.MutableRefObject<{
}>
setShowLoginDialog: React.Dispatch<React.SetStateAction<boolean>>
setShowRegisterDialog: React.Dispatch<React.SetStateAction<boolean>>
}
const MainSharedContext = createContext({} as shared)
export default MainSharedContext

View File

@@ -2,17 +2,27 @@ import * as React from 'react'
import { Button, Dialog, TextField } from "mdui"
import performAuth from '../../performAuth.ts'
import useEventListener from '../../utils/useEventListener.ts'
import showSnackbar from '../../utils/showSnackbar.ts'
import MainSharedContext from '../MainSharedContext.ts'
export default function LoginDialog({ ...props }: { open: boolean } & React.HTMLAttributes<Dialog>) {
const shared = React.useContext(MainSharedContext)
const loginDialogRef = React.useRef<Dialog>(null)
const loginButtonRef = React.useRef<Button>(null)
const registerButtonRef = React.useRef<Button>(null)
const loginInputAccountRef = React.useRef<TextField>(null)
const loginInputPasswordRef = React.useRef<TextField>(null)
useEventListener(loginButtonRef, 'click', async () => {
return (
<mdui-dialog {...props} headline="登录" ref={loginDialogRef}>
<mdui-text-field label="用户 ID / 用户名" ref={loginInputAccountRef}></mdui-text-field>
<div style={{
height: "10px",
}}></div>
<mdui-text-field label="密码" type="password" toggle-password ref={loginInputPasswordRef}></mdui-text-field>
<mdui-button slot="action" variant="text" onClick={() => shared.setShowRegisterDialog(true)}></mdui-button>
<mdui-button slot="action" variant="text" onClick={async () => {
const account = loginInputAccountRef.current!.value
const password = loginInputPasswordRef.current!.value
@@ -26,19 +36,7 @@ export default function LoginDialog({ ...props }: { open: boolean } & React.HTML
if (e instanceof Error)
showSnackbar({ message: '登录失败: ' + e.message })
}
})
return (
<mdui-dialog {...props} headline="登录" ref={loginDialogRef}>
<mdui-text-field label="用户 ID / 用户名" ref={loginInputAccountRef}></mdui-text-field>
<div style={{
height: "10px",
}}></div>
<mdui-text-field label="密码" type="password" toggle-password ref={loginInputPasswordRef}></mdui-text-field>
<mdui-button slot="action" variant="text" ref={registerButtonRef}></mdui-button>
<mdui-button slot="action" variant="text" ref={loginButtonRef}></mdui-button>
}}></mdui-button>
</mdui-dialog>
)
}

View File

@@ -0,0 +1,69 @@
import * as React from 'react'
import { Button, Dialog, TextField } from "mdui"
import MainSharedContext from '../MainSharedContext'
import showSnackbar from '../../utils/showSnackbar'
import showCircleProgressDialog from '../showCircleProgressDialog'
import getClient from '../../getClient'
import performAuth from '../../performAuth'
export default function RegisterDialog({ ...props }: { open: boolean } & React.HTMLAttributes<Dialog>) {
const shared = React.useContext(MainSharedContext)
const registerInputUserNameRef = React.useRef<TextField>(null)
const registerInputNickNameRef = React.useRef<TextField>(null)
const registerInputPasswordRef = React.useRef<TextField>(null)
return (
<mdui-dialog headline="注册" {...props}>
<mdui-text-field label="用户名 (可选)" ref={registerInputUserNameRef}></mdui-text-field>
<div style={{
height: "10px",
}}></div>
<mdui-text-field label="昵称" ref={registerInputNickNameRef}></mdui-text-field>
<div style={{
height: "10px",
}}></div>
<mdui-text-field label="密码" type="password" toggle-password ref={registerInputPasswordRef}></mdui-text-field>
<mdui-button slot="action" variant="text" onClick={() => shared.setShowRegisterDialog(false)}></mdui-button>
<mdui-button slot="action" variant="text" onClick={async () => {
const waitingForRegister = showCircleProgressDialog("注册中...")
const username = registerInputUserNameRef.current!.value
let user_id: string
try {
user_id = await getClient().registerOrThrow({
username: username,
nickname: registerInputNickNameRef.current!.value,
password: registerInputPasswordRef.current!.value,
})
} catch (e) {
user_id = ''
if (e instanceof Error) {
waitingForRegister.open = false
showSnackbar({ message: '注册失败: ' + e.message })
return
}
}
waitingForRegister.open = false
const waitingForLogin = showCircleProgressDialog("登录中...")
try {
await performAuth({
account: username == '' ? username : user_id,
password: registerInputPasswordRef.current!.value,
})
location.reload()
} catch (e) {
if (e instanceof Error) {
showSnackbar({ message: '登录失败: ' + e.message })
}
}
waitingForLogin.open = false
}}></mdui-button>
</mdui-dialog>
)
}

View File

@@ -0,0 +1,23 @@
import { $, dialog } from 'mdui'
export default function showCircleProgressDialog(text: string) {
const d = dialog({
body: `
<div style="display: flex; align-items: center;">
<mdui-circular-progress style="margin-left: 3px"></mdui-circular-progress>
<span style="margin-left: 20px;"></span>
</div>
`,
closeOnEsc: false,
closeOnOverlayClick: false,
})
$(d).addClass('waiting-dialog').find('span').text(text)
$(d.shadowRoot).append(`
<style>
.body {
overflow: hidden !important;
}
</style>
`)
return d
}

1
client/utils/sleep.ts Normal file
View File

@@ -0,0 +1 @@
export default (t: number) => new Promise((res) => setTimeout(res, t))