feat: login
This commit is contained in:
10
app.vue
10
app.vue
@@ -1,3 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
const loginState = useLoginState()
|
||||
|
||||
onMounted(() => {
|
||||
loginState.checkSession()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<NuxtLayout>
|
||||
@@ -5,6 +13,6 @@
|
||||
</NuxtLayout>
|
||||
|
||||
<UModals/>
|
||||
<UNotifications />
|
||||
<UNotifications/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
26
components/LoginNeededContent.vue
Normal file
26
components/LoginNeededContent.vue
Normal file
@@ -0,0 +1,26 @@
|
||||
<script setup lang="ts">
|
||||
const loginState = useLoginState()
|
||||
|
||||
defineProps({
|
||||
contentClass: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="!loginState.is_logged_in"
|
||||
class="w-full flex flex-col justify-center items-center gap-2 bg-neutral-100 dark:bg-neutral-900">
|
||||
<Icon name="i-tabler-user-circle" class="text-7xl text-neutral-300 dark:text-neutral-700"/>
|
||||
<p class="text-sm text-neutral-500 dark:text-neutral-400">请登录后使用</p>
|
||||
<UButton class="mt-2 font-bold" to="/login" color="black" variant="solid" size="xs">登录</UButton>
|
||||
</div>
|
||||
<div :class="contentClass" v-else>
|
||||
<slot/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -1,14 +1,15 @@
|
||||
<script setup lang="ts">
|
||||
import {Label, PinInputInput, PinInputRoot} from 'radix-vue'
|
||||
import {useFetchWrapped} from "~/composables/useFetchWrapped";
|
||||
|
||||
const toast = useToast()
|
||||
const modal = useModal()
|
||||
const loginState = useLoginState()
|
||||
|
||||
const sms_triggered = ref(false)
|
||||
const sms_sending = ref(false)
|
||||
const sms_counting_down = ref(0)
|
||||
const final_loading = ref(false)
|
||||
const handleComplete = (e: string[]) => toast.add({title: `提交验证码 ${e.join('')}`, color: 'indigo', icon: 'i-tabler-circle-check'})
|
||||
|
||||
const items = [
|
||||
{
|
||||
@@ -25,20 +26,85 @@ const items = [
|
||||
},
|
||||
]
|
||||
|
||||
const accountForm = reactive({username: '', password: ''})
|
||||
const accountForm = reactive<req.user.Login>({username: '', password: ''})
|
||||
const smsForm = reactive({mobile: '', sms_code: []})
|
||||
|
||||
function onSubmit(form: any) {
|
||||
function onSubmit(form: req.user.Login) {
|
||||
console.log('Submitted form:', form)
|
||||
final_loading.value = true
|
||||
useFetchWrapped<req.user.Login, BaseResponse<resp.user.Login>>('App.User_User.Login', {
|
||||
username: form.username,
|
||||
password: form.password
|
||||
}).then(res => {
|
||||
final_loading.value = false
|
||||
if (res.ret !== 200) {
|
||||
toast.add({
|
||||
title: '登录失败',
|
||||
description: res.msg || '未知错误',
|
||||
color: 'red',
|
||||
icon: 'i-tabler-circle-x'
|
||||
})
|
||||
return
|
||||
}
|
||||
if (!res.data.is_login) {
|
||||
toast.add({
|
||||
title: '登录失败',
|
||||
description: res.msg || '账号或密码错误',
|
||||
color: 'red',
|
||||
icon: 'i-tabler-circle-x'
|
||||
})
|
||||
return
|
||||
}
|
||||
if (!res.data.token || !res.data.user_id) {
|
||||
toast.add({
|
||||
title: '登录失败',
|
||||
description: res.msg || '无法获取登录状态',
|
||||
color: 'red',
|
||||
icon: 'i-tabler-circle-x'
|
||||
})
|
||||
return
|
||||
}
|
||||
loginState.token = res.data.token
|
||||
loginState.user.id = res.data.user_id
|
||||
loginState.updateProfile().then(() => {
|
||||
loginState.checkSession()
|
||||
modal.close()
|
||||
toast.add({
|
||||
title: '登录成功',
|
||||
description: `${loginState.user.username}, 欢迎回来`,
|
||||
color: 'primary',
|
||||
icon: 'i-tabler-login-2'
|
||||
})
|
||||
}).catch(err => {
|
||||
toast.add({
|
||||
title: '登录失败',
|
||||
description: err.msg || '网络错误',
|
||||
color: 'red',
|
||||
icon: 'i-tabler-circle-x'
|
||||
})
|
||||
}).finally(() => {
|
||||
final_loading.value = false
|
||||
})
|
||||
}).catch(err => {
|
||||
toast.add({
|
||||
title: '登录失败',
|
||||
description: err.msg || '网络错误',
|
||||
color: 'red',
|
||||
icon: 'i-tabler-circle-x'
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const obtainSmsCode = () => {
|
||||
smsForm.sms_code = []
|
||||
sms_sending.value = true
|
||||
setTimeout(() => {
|
||||
|
||||
useFetchWrapped<req.user.SmsLogin, BaseResponse<resp.user.SmsLogin>>('App.User_User.MobileLogin', {
|
||||
mobile: smsForm.mobile
|
||||
}).then(res => {
|
||||
sms_triggered.value = true
|
||||
sms_sending.value = false
|
||||
sms_counting_down.value = 15 // TODO: modify this to change the countdown time
|
||||
sms_counting_down.value = 60 // TODO: modify this to change the countdown time
|
||||
toast.add({title: '短信验证码已发送', color: 'indigo', icon: 'i-tabler-circle-check'})
|
||||
const interval = setInterval(() => {
|
||||
sms_counting_down.value--
|
||||
@@ -46,7 +112,64 @@ const obtainSmsCode = () => {
|
||||
clearInterval(interval)
|
||||
}
|
||||
}, 1000)
|
||||
}, 2000)
|
||||
}).catch(err => {
|
||||
toast.add({
|
||||
title: '验证码发送失败',
|
||||
description: err.msg || '网络错误',
|
||||
color: 'red',
|
||||
icon: 'i-tabler-circle-x'
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const handle_sms_verify = (e: string[]) => {
|
||||
final_loading.value = true
|
||||
useFetchWrapped<req.user.SmsLoginVerify, BaseResponse<resp.user.SmsLoginVerify>>('App.User_User.MobileLoginVerify', {
|
||||
mobile: smsForm.mobile,
|
||||
sms_code: e.join('')
|
||||
}).then(res => {
|
||||
if (res.ret !== 200) {
|
||||
smsForm.sms_code = []
|
||||
toast.add({
|
||||
title: '登录失败',
|
||||
description: res.msg || '未知错误',
|
||||
color: 'red',
|
||||
icon: 'i-tabler-circle-x'
|
||||
})
|
||||
return
|
||||
}
|
||||
if (!res.data.token || !res.data.person_id) {
|
||||
smsForm.sms_code = []
|
||||
toast.add({
|
||||
title: '登录失败',
|
||||
description: res.msg || '无法获取登录状态',
|
||||
color: 'red',
|
||||
icon: 'i-tabler-circle-x'
|
||||
})
|
||||
return
|
||||
}
|
||||
loginState.token = res.data.token
|
||||
loginState.user.id = res.data.person_id
|
||||
loginState.updateProfile().then(() => {
|
||||
loginState.checkSession()
|
||||
modal.close()
|
||||
toast.add({
|
||||
title: '登录成功',
|
||||
description: `${loginState.user.username}, 欢迎回来`,
|
||||
color: 'primary',
|
||||
icon: 'i-tabler-login-2'
|
||||
})
|
||||
}).catch(err => {
|
||||
toast.add({
|
||||
title: '登录失败',
|
||||
description: err.msg || '网络错误',
|
||||
color: 'red',
|
||||
icon: 'i-tabler-circle-x'
|
||||
})
|
||||
}).finally(() => {
|
||||
final_loading.value = false
|
||||
})
|
||||
}).finally(() => final_loading.value = false)
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -58,7 +181,8 @@ const obtainSmsCode = () => {
|
||||
<h3 class="text-base font-semibold leading-6 text-gray-900 dark:text-white">
|
||||
登录眩生花 AI 助手
|
||||
</h3>
|
||||
<UButton color="gray" variant="ghost" icon="i-heroicons-x-mark-20-solid" class="-my-1" @click="modal.close()"/>
|
||||
<UButton color="gray" variant="ghost" icon="i-heroicons-x-mark-20-solid" class="-my-1"
|
||||
@click="modal.close()"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -71,7 +195,7 @@ const obtainSmsCode = () => {
|
||||
</div>
|
||||
</template>
|
||||
<template #item="{ item }">
|
||||
<UCard @submit.prevent="() => onSubmit(item.key === 'account' ? accountForm : smsForm)">
|
||||
<UCard @submit.prevent="() => onSubmit(accountForm)">
|
||||
<template #header>
|
||||
<p class="text-base font-semibold leading-6 text-gray-900 dark:text-white">
|
||||
{{ item.label }}
|
||||
@@ -93,14 +217,14 @@ const obtainSmsCode = () => {
|
||||
<div v-else-if="item.key === 'sms'" class="space-y-3">
|
||||
<UFormGroup label="手机号" name="mobile" required>
|
||||
<UButtonGroup class="w-full">
|
||||
<UInput v-model="smsForm.mobile" type="sms" class="w-full" required>
|
||||
<UInput v-model="smsForm.mobile" :disabled="final_loading" type="sms" class="w-full" required>
|
||||
<template #leading>
|
||||
<span class="text-gray-500 dark:text-gray-400 text-xs">+86</span>
|
||||
</template>
|
||||
</UInput>
|
||||
<UButton :label="sms_counting_down ? `${sms_counting_down}秒后重发` : '获取验证码'"
|
||||
@click="obtainSmsCode"
|
||||
:loading="sms_sending" :disabled="!!sms_counting_down"
|
||||
:loading="sms_sending" :disabled="!!sms_counting_down || final_loading"
|
||||
class="text-xs font-bold" color="gray"/>
|
||||
</UButtonGroup>
|
||||
</UFormGroup>
|
||||
@@ -110,17 +234,18 @@ const obtainSmsCode = () => {
|
||||
<PinInputRoot
|
||||
id="sms-input"
|
||||
v-model="smsForm.sms_code"
|
||||
:disabled="sms_sending"
|
||||
:disabled="sms_sending || final_loading"
|
||||
placeholder="○"
|
||||
class="w-full flex gap-2 justify-between items-center mt-1"
|
||||
@complete="handleComplete"
|
||||
class="w-full flex gap-2 justify-between md:justify-start items-center mt-1"
|
||||
@complete="handle_sms_verify"
|
||||
type="number" otp required
|
||||
>
|
||||
<PinInputInput
|
||||
v-for="(id, index) in 6"
|
||||
v-for="(id, index) in 4"
|
||||
:key="id"
|
||||
:index="index"
|
||||
class="pin-input"
|
||||
:autofocus="index === 0"
|
||||
/>
|
||||
</PinInputRoot>
|
||||
</div>
|
||||
@@ -128,7 +253,7 @@ const obtainSmsCode = () => {
|
||||
</div>
|
||||
|
||||
<template #footer v-if="item.key !== 'sms'">
|
||||
<UButton type="submit" color="black" :disabled="final_loading">
|
||||
<UButton type="submit" color="black" :loading="final_loading">
|
||||
登录
|
||||
</UButton>
|
||||
</template>
|
||||
@@ -152,7 +277,7 @@ const obtainSmsCode = () => {
|
||||
}
|
||||
|
||||
.pin-input {
|
||||
@apply w-full aspect-square rounded text-center shadow caret-transparent;
|
||||
@apply w-full md:w-16 aspect-square rounded text-center shadow caret-transparent;
|
||||
@apply outline-0 ring-indigo-500 focus:ring font-bold;
|
||||
}
|
||||
|
||||
|
||||
22
composables/useDefer.ts
Normal file
22
composables/useDefer.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
export const useDefer = (maxFrame: number = 1000) => {
|
||||
const frame = ref(1)
|
||||
let rafId: number
|
||||
|
||||
function updateFrame() {
|
||||
rafId = requestAnimationFrame(() => {
|
||||
frame.value++
|
||||
if (frame.value > maxFrame) return
|
||||
updateFrame()
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
updateFrame()
|
||||
})
|
||||
onUnmounted(() => {
|
||||
cancelAnimationFrame(rafId)
|
||||
})
|
||||
return (n: number) => {
|
||||
return frame.value >= n
|
||||
}
|
||||
}
|
||||
21
composables/useFetchWrapped.ts
Normal file
21
composables/useFetchWrapped.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import {useFormPayload} from "~/composables/useFormPayload";
|
||||
|
||||
export const useFetchWrapped = <TypeReq, TypeResp>(
|
||||
action: string,
|
||||
payload?: TypeReq,
|
||||
options?: {
|
||||
method?: 'GET' | 'POST'
|
||||
headers?: Record<string, string>
|
||||
baseURL?: string
|
||||
}
|
||||
) => {
|
||||
const runtimeConfig = useRuntimeConfig()
|
||||
return $fetch<TypeResp>('/', {
|
||||
baseURL: options?.baseURL || runtimeConfig.public.API_BASE,
|
||||
method: options?.method || 'POST',
|
||||
query: {
|
||||
s: action
|
||||
},
|
||||
body: useFormPayload(payload as object)
|
||||
})
|
||||
}
|
||||
10
composables/useFormPayload.ts
Normal file
10
composables/useFormPayload.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
export const useFormPayload = (payload: object) => {
|
||||
const formData = new FormData()
|
||||
for (const dataKey in payload) {
|
||||
if (payload.hasOwnProperty(dataKey)) {
|
||||
// @ts-ignore
|
||||
formData.append(dataKey, payload[dataKey])
|
||||
}
|
||||
}
|
||||
return formData
|
||||
}
|
||||
64
composables/useLoginState.ts
Normal file
64
composables/useLoginState.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
import {useFetchWrapped} from "~/composables/useFetchWrapped";
|
||||
|
||||
export const useLoginState = defineStore('loginState', () => {
|
||||
const is_logged_in = ref(false)
|
||||
const token = ref<string | null>(null)
|
||||
const user = ref<UserSchema>({} as UserSchema)
|
||||
|
||||
const checkSession = () => {
|
||||
return new Promise<boolean>(resolve => {
|
||||
if (!token.value) return resolve(false)
|
||||
useFetchWrapped<AuthedRequest, BaseResponse<resp.user.CheckSession>>('App.User_User.CheckSession', {
|
||||
token: token.value,
|
||||
user_id: user.value.id
|
||||
}).then(res => {
|
||||
if (res.ret !== 200) {
|
||||
resolve(false)
|
||||
return
|
||||
}
|
||||
resolve(res.data.is_login)
|
||||
// update global state
|
||||
is_logged_in.value = res.data.is_login
|
||||
}).catch(err => resolve(false))
|
||||
})
|
||||
}
|
||||
|
||||
const updateProfile = () => {
|
||||
return new Promise<UserSchema>((resolve, reject) => {
|
||||
if (!token.value) return reject('token is empty')
|
||||
useFetchWrapped<AuthedRequest, BaseResponse<resp.user.Profile>>('App.User_User.Profile', {
|
||||
token: token.value,
|
||||
user_id: user.value.id
|
||||
}).then(res => {
|
||||
if (res.ret !== 200) {
|
||||
reject(res.msg || '未知错误')
|
||||
return
|
||||
}
|
||||
user.value = res.data.profile
|
||||
resolve(res.data.profile)
|
||||
}).catch(err => reject(err || '未知错误'))
|
||||
})
|
||||
}
|
||||
|
||||
const logout = () => new Promise<void>(resolve => {
|
||||
token.value = null
|
||||
user.value = {} as UserSchema
|
||||
is_logged_in.value = false
|
||||
resolve()
|
||||
})
|
||||
|
||||
return {
|
||||
is_logged_in,
|
||||
token,
|
||||
user,
|
||||
checkSession,
|
||||
updateProfile,
|
||||
logout
|
||||
}
|
||||
}, {
|
||||
persist: {
|
||||
key: 'xsh_assistant_persisted_state',
|
||||
storage: persistedState.localStorage,
|
||||
paths: ['token', 'user']
|
||||
}
|
||||
})
|
||||
@@ -4,6 +4,8 @@ import ModalAuthentication from "~/components/ModalAuthentication.vue";
|
||||
const colorMode = useColorMode()
|
||||
const dayjs = useDayjs()
|
||||
const modal = useModal()
|
||||
const toast = useToast()
|
||||
const loginState = useLoginState()
|
||||
|
||||
const isDark = computed({
|
||||
get() {
|
||||
@@ -40,7 +42,13 @@ const items = [
|
||||
icon: 'i-tabler-user-circle'
|
||||
}], [{
|
||||
label: '注销登录',
|
||||
icon: 'i-tabler-logout'
|
||||
icon: 'i-tabler-logout',
|
||||
click: () => loginState.logout().then(() => toast.add({
|
||||
title: '退出登录',
|
||||
description: `您已成功退出登录账号`,
|
||||
color: 'indigo',
|
||||
icon: 'i-tabler-logout-2'
|
||||
}))
|
||||
}]
|
||||
]
|
||||
|
||||
@@ -68,9 +76,10 @@ const open_login_modal = () => {
|
||||
aria-label="Theme"
|
||||
@click="isDark = !isDark"
|
||||
/>
|
||||
<UButton label="登录或注册" size="xs" class="font-bold" color="indigo" @click="open_login_modal"/>
|
||||
<UButton v-if="!loginState.is_logged_in" label="登录或注册" size="xs" class="font-bold" color="indigo"
|
||||
@click="open_login_modal"/>
|
||||
</ClientOnly>
|
||||
<UDropdown :items="items" :popper="{ placement: 'bottom-start' }"
|
||||
<UDropdown v-if="loginState.is_logged_in" :items="items" :popper="{ placement: 'bottom-start' }"
|
||||
:ui="{ item: { disabled: 'cursor-text select-text' } }">
|
||||
<UAvatar :src="void 0" icon="i-tabler-user" size="md"/>
|
||||
|
||||
@@ -78,10 +87,12 @@ const open_login_modal = () => {
|
||||
<div class="text-left">
|
||||
<p class="flex items-center gap-1">
|
||||
已登录为
|
||||
<UBadge color="amber" size="xs" variant="subtle">OP</UBadge>
|
||||
<UBadge v-if="loginState.user.auth_code === 2" color="amber" size="xs" variant="subtle">
|
||||
OP
|
||||
</UBadge>
|
||||
</p>
|
||||
<p class="truncate whitespace-nowrap max-w-40 font-medium text-gray-900 dark:text-white">
|
||||
{{ item.label }}
|
||||
{{ loginState.user?.username }}
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
// https://nuxt.com/docs/api/configuration/nuxt-config
|
||||
export default defineNuxtConfig({
|
||||
devtools: {enabled: true},
|
||||
runtimeConfig: {
|
||||
public: {
|
||||
API_BASE: 'https://service1.fenshenzhike.com/'
|
||||
}
|
||||
},
|
||||
modules: [
|
||||
'@nuxt/ui',
|
||||
'radix-vue/nuxt',
|
||||
|
||||
@@ -24,11 +24,11 @@ const props = defineProps({
|
||||
})
|
||||
|
||||
const expand_prompt = ref(false)
|
||||
const show_meta = ref(false)
|
||||
const show_meta = ref(true)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="">
|
||||
<div class="w-full">
|
||||
<div class="flex items-center gap-1">
|
||||
<UIcon :name="icon"/>
|
||||
<h1 class="text-sm font-semibold">
|
||||
|
||||
@@ -4,11 +4,16 @@ import image2 from '~/assets/example/2.jpg';
|
||||
import image3 from '~/assets/example/3.jpg';
|
||||
import OptionBlock from "~/pages/aigc/drawing/components/OptionBlock.vue";
|
||||
import ResultBlock from "~/pages/aigc/drawing/components/ResultBlock.vue";
|
||||
import {useLoginState} from "~/composables/useLoginState";
|
||||
import ModalAuthentication from "~/components/ModalAuthentication.vue";
|
||||
|
||||
useHead({
|
||||
title: '绘画 | XSH AI'
|
||||
})
|
||||
|
||||
const modal = useModal()
|
||||
const loginState = useLoginState()
|
||||
|
||||
const leftSection = ref<HTMLElement | null>(null)
|
||||
const leftHandler = ref<HTMLElement | null>(null)
|
||||
|
||||
@@ -54,7 +59,7 @@ const images2 = [
|
||||
<template>
|
||||
<div class="w-full flex">
|
||||
<div ref="leftSection"
|
||||
class="hidden md:block relative h-[calc(100vh-4rem)] overflow-hidden bg-neutral-200 dark:bg-neutral-800 transition-all"
|
||||
class="sticky hidden md:block h-[calc(100vh-4rem)] overflow-hidden bg-neutral-200 dark:bg-neutral-800 transition-all"
|
||||
style="width: 320px">
|
||||
<div ref="leftHandler"
|
||||
class="absolute inset-0 left-auto hidden xl:flex flex-col justify-center items-center cursor-ew-resize px-1 group"
|
||||
@@ -77,28 +82,29 @@ const images2 = [
|
||||
</OptionBlock>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-1 h-screen flex flex-col gap-4 bg-neutral-100 dark:bg-neutral-900 p-4 overflow-y-auto">
|
||||
<ResultBlock :images="images"
|
||||
<div class="flex-1 h-screen flex flex-col gap-4 bg-neutral-100 dark:bg-neutral-900 p-4 pb-20 overflow-y-auto">
|
||||
<div v-if="!loginState.is_logged_in"
|
||||
class="w-full h-full flex flex-col justify-center items-center gap-2 bg-neutral-100 dark:bg-neutral-900">
|
||||
<Icon name="i-tabler-user-circle" class="text-7xl text-neutral-300 dark:text-neutral-700"/>
|
||||
<p class="text-sm text-neutral-500 dark:text-neutral-400">请登录后使用</p>
|
||||
<UButton class="mt-2 font-bold" @click="modal.open(ModalAuthentication)" color="black" variant="solid"
|
||||
size="xs">登录
|
||||
</UButton>
|
||||
</div>
|
||||
<ResultBlock v-else :images="images" v-for="i in 12" :key="i"
|
||||
title="XX大模型 · 文生图" :meta="{
|
||||
id: 'd166429411dfc6722e54c032cdba97a2',
|
||||
aspect: '9:16',
|
||||
cost: '1500',
|
||||
modal: '混元大模型',
|
||||
ratio: '16:9',
|
||||
datetime: 1709106270
|
||||
}"
|
||||
id: 'd166429411dfc6722e54c032cdba97a2',
|
||||
aspect: '9:16',
|
||||
cost: '1500',
|
||||
modal: '混元大模型',
|
||||
ratio: '16:9',
|
||||
datetime: 1709106270
|
||||
}"
|
||||
prompt="这是, 一组, 测试用的, 提示词, 很长, 很长很长, 很长, 很长, 很长, 很长, 很长, 很长, 很长, 很长, 很长, 很长, 很长, 很长, 很长, 很长, 很长, 很长, 很长, 很长, 很长, 很长">
|
||||
<template #header-right>
|
||||
<UButton color="gray" size="xs" icon="i-tabler-trash" variant="ghost"></UButton>
|
||||
</template>
|
||||
</ResultBlock>
|
||||
<ResultBlock :images="images2"
|
||||
title="XX大模型 · 图生图"
|
||||
prompt="这是, 一组, 测试用的, 提示词">
|
||||
<template #header-right>
|
||||
<UButton color="gray" size="xs" icon="i-tabler-trash" variant="ghost"></UButton>
|
||||
</template>
|
||||
</ResultBlock>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
70
typings/schema.d.ts
vendored
Normal file
70
typings/schema.d.ts
vendored
Normal file
@@ -0,0 +1,70 @@
|
||||
interface AuthedRequest {
|
||||
token?: string
|
||||
user_id?: number
|
||||
}
|
||||
|
||||
interface BaseResponse<T> {
|
||||
ret: number
|
||||
msg: string
|
||||
data: T
|
||||
}
|
||||
|
||||
interface UserSchema {
|
||||
id: number
|
||||
username: string
|
||||
nickname: string
|
||||
avatar: string
|
||||
sex: 0 | 1 | 2 // 0: 未知, 1: 男, 2: 女
|
||||
email: string
|
||||
mobile: string
|
||||
auth_code: 0 | 1 | 2 // 0: Banned, 1: User, 2: Operator
|
||||
}
|
||||
|
||||
namespace req {
|
||||
namespace user {
|
||||
/**
|
||||
* @description 用户登录
|
||||
* @param username 用户名或手机号
|
||||
* @param password 密码
|
||||
*/
|
||||
interface Login {
|
||||
username: string
|
||||
password: string
|
||||
}
|
||||
|
||||
interface SmsLogin {
|
||||
mobile: string
|
||||
}
|
||||
|
||||
interface SmsLoginVerify {
|
||||
mobile: string
|
||||
sms_code: string
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace resp {
|
||||
namespace user {
|
||||
interface CheckSession {
|
||||
is_login: boolean
|
||||
}
|
||||
|
||||
interface Login {
|
||||
is_login: boolean
|
||||
token?: string
|
||||
user_id?: number
|
||||
}
|
||||
|
||||
interface Profile {
|
||||
profile: UserSchema
|
||||
}
|
||||
interface SmsLogin {
|
||||
message: string
|
||||
}
|
||||
|
||||
interface SmsLoginVerify {
|
||||
token: string
|
||||
person_id: number
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user