添加密码登录&调整接口数据展示&配置底部导航跳转
This commit is contained in:
@@ -16,12 +16,13 @@ export async function login(props: {
|
|||||||
username: string
|
username: string
|
||||||
password: string
|
password: string
|
||||||
remember: boolean
|
remember: boolean
|
||||||
|
mode: 'phone_code' | 'password'
|
||||||
}): Promise<ApiResponse> {
|
}): Promise<ApiResponse> {
|
||||||
// 尝试登录
|
// 尝试登录
|
||||||
const result = await callByDevice<TokenResp>('/api/auth/token', {
|
const result = await callByDevice<TokenResp>('/api/auth/token', {
|
||||||
...props,
|
...props,
|
||||||
grant_type: 'password',
|
grant_type: 'password',
|
||||||
login_type: 'phone_code',
|
login_type: props.mode,
|
||||||
})
|
})
|
||||||
|
|
||||||
if (!result.success) {
|
if (!result.success) {
|
||||||
|
|||||||
@@ -4,32 +4,32 @@ import {ApiResponse, ExtraResp} from '@/lib/api'
|
|||||||
import {callByUser} from './base'
|
import {callByUser} from './base'
|
||||||
import {listAnnouncements} from './announcement'
|
import {listAnnouncements} from './announcement'
|
||||||
|
|
||||||
type listAccountReq = {
|
type statisticsResourceUsageReq = {
|
||||||
resource_no?: string
|
resource_no?: string
|
||||||
create_after?: Date
|
create_after?: Date
|
||||||
create_before?: Date
|
create_before?: Date
|
||||||
}
|
}
|
||||||
|
|
||||||
type listAccountResp = {
|
type statisticsResourceUsageResp = {
|
||||||
date: string
|
date: string
|
||||||
count: number
|
count: number
|
||||||
}[]
|
}[]
|
||||||
|
|
||||||
export async function listAccount(props: listAccountReq) {
|
export async function statisticsResourceUsage(props: statisticsResourceUsageReq) {
|
||||||
return await callByUser<listAccountResp>('/api/resource/statistics/usage', props)
|
return await callByUser<statisticsResourceUsageResp>('/api/resource/statistics/usage', props)
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function statisticsResourceFree() {
|
export async function statisticsResourceFree() {
|
||||||
return await callByUser<{
|
return await callByUser<{
|
||||||
long: {
|
long: {
|
||||||
ResourceCount: number
|
resource_count: number
|
||||||
ResourceDailyFreeSum: number
|
resource_daily_free_sum: number
|
||||||
ResourceQuotaSum: number
|
resource_quota_sum: number
|
||||||
}
|
}
|
||||||
short: {
|
short: {
|
||||||
ResourceCount: number
|
resource_count: number
|
||||||
ResourceDailyFreeSum: number
|
resource_daily_free_sum: number
|
||||||
ResourceQuotaSum: number
|
resource_quota_sum: number
|
||||||
}
|
}
|
||||||
}>('/api/resource/statistics/free')
|
}>('/api/resource/statistics/free')
|
||||||
}
|
}
|
||||||
@@ -37,7 +37,7 @@ export async function statisticsResourceFree() {
|
|||||||
type listInitializationResp = {
|
type listInitializationResp = {
|
||||||
anno: ExtraResp<typeof listAnnouncements>
|
anno: ExtraResp<typeof listAnnouncements>
|
||||||
free: ExtraResp<typeof statisticsResourceFree>
|
free: ExtraResp<typeof statisticsResourceFree>
|
||||||
usage: ExtraResp<typeof listAccount>
|
usage: ExtraResp<typeof statisticsResourceUsage>
|
||||||
}
|
}
|
||||||
export async function listInitialization(): Promise<ApiResponse<listInitializationResp>> {
|
export async function listInitialization(): Promise<ApiResponse<listInitializationResp>> {
|
||||||
const free = await statisticsResourceFree()
|
const free = await statisticsResourceFree()
|
||||||
@@ -59,7 +59,7 @@ export async function listInitialization(): Promise<ApiResponse<listInitializati
|
|||||||
message: '公告数据获取失败',
|
message: '公告数据获取失败',
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const usage = await listAccount({
|
const usage = await statisticsResourceUsage({
|
||||||
create_after: new Date(),
|
create_after: new Date(),
|
||||||
create_before: new Date(),
|
create_before: new Date(),
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import {useState, useCallback, useRef, useContext} from 'react'
|
import {useState, useCallback, useRef} from 'react'
|
||||||
import {Input} from '@/components/ui/input'
|
import {Input} from '@/components/ui/input'
|
||||||
import {Button} from '@/components/ui/button'
|
import {Button} from '@/components/ui/button'
|
||||||
import {Checkbox} from '@/components/ui/checkbox'
|
import {Checkbox} from '@/components/ui/checkbox'
|
||||||
@@ -32,24 +32,31 @@ import Link from 'next/link'
|
|||||||
|
|
||||||
export type LoginPageProps = {}
|
export type LoginPageProps = {}
|
||||||
|
|
||||||
// 定义表单验证模式
|
const smsSchema = zod.object({
|
||||||
const formSchema = zod.object({
|
|
||||||
username: zod.string().min(11, '请输入正确的手机号码').max(11, '请输入正确的手机号码'),
|
username: zod.string().min(11, '请输入正确的手机号码').max(11, '请输入正确的手机号码'),
|
||||||
password: zod.string().min(1, '请输入验证码'),
|
password: zod.string().min(1, '请输入验证码'),
|
||||||
remember: zod.boolean().default(false),
|
remember: zod.boolean().default(false),
|
||||||
})
|
})
|
||||||
|
|
||||||
type FormValues = zod.infer<typeof formSchema>
|
const pwdSchema = zod.object({
|
||||||
|
username: zod.string().min(11, '请输入正确的手机号码').max(11, '请输入正确的手机号码'),
|
||||||
|
password: zod.string().min(6, '请输入至少6位密码'),
|
||||||
|
remember: zod.boolean().default(false),
|
||||||
|
})
|
||||||
|
|
||||||
|
type FormValues = zod.infer<typeof smsSchema>
|
||||||
|
|
||||||
export default function LoginPage(props: LoginPageProps) {
|
export default function LoginPage(props: LoginPageProps) {
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const [submitting, setSubmitting] = useState(false)
|
const [submitting, setSubmitting] = useState(false)
|
||||||
const [countdown, setCountdown] = useState(0)
|
const [countdown, setCountdown] = useState(0)
|
||||||
const [showCaptcha, setShowCaptcha] = useState(false)
|
const [showCaptcha, setShowCaptcha] = useState(false)
|
||||||
|
const [loginMode, setLoginMode] = useState<'sms' | 'password'>('sms')
|
||||||
|
const [showPwd, setShowPwd] = useState(false)
|
||||||
const timerRef = useRef<NodeJS.Timeout>(undefined)
|
const timerRef = useRef<NodeJS.Timeout>(undefined)
|
||||||
|
|
||||||
const form = useForm<FormValues>({
|
const form = useForm<FormValues>({
|
||||||
resolver: zodResolver(formSchema),
|
resolver: zodResolver(loginMode === 'sms' ? smsSchema : pwdSchema),
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
username: '',
|
username: '',
|
||||||
password: '',
|
password: '',
|
||||||
@@ -103,7 +110,6 @@ export default function LoginPage(props: LoginPageProps) {
|
|||||||
toast.error(resp.message)
|
toast.error(resp.message)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
setShowCaptcha(false)
|
setShowCaptcha(false)
|
||||||
waiting = parseInt(resp.message)
|
waiting = parseInt(resp.message)
|
||||||
console.log(resp.message)
|
console.log(resp.message)
|
||||||
@@ -132,12 +138,30 @@ export default function LoginPage(props: LoginPageProps) {
|
|||||||
return prev - 1
|
return prev - 1
|
||||||
})
|
})
|
||||||
}, 1000)
|
}, 1000)
|
||||||
|
|
||||||
return false
|
return false
|
||||||
}, [username])
|
}, [username])
|
||||||
|
|
||||||
// 处理表单提交
|
// 处理表单提交
|
||||||
const onSubmit = async (values: FormValues) => {
|
const onSubmit = async (values: FormValues) => {
|
||||||
|
// 密码登录时增加更严格的校验
|
||||||
|
if (loginMode === 'password') {
|
||||||
|
const pwd = values.password || ''
|
||||||
|
// 至少6位,包含字母和数字
|
||||||
|
if (pwd.length < 6) {
|
||||||
|
form.setError('password', {
|
||||||
|
type: 'manual',
|
||||||
|
message: '密码长度至少6位',
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!/[A-Za-z]/.test(pwd) || !/[0-9]/.test(pwd)) {
|
||||||
|
form.setError('password', {
|
||||||
|
type: 'manual',
|
||||||
|
message: '密码需包含字母和数字',
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
setSubmitting(true)
|
setSubmitting(true)
|
||||||
|
|
||||||
@@ -153,7 +177,7 @@ export default function LoginPage(props: LoginPageProps) {
|
|||||||
if (!values.password) {
|
if (!values.password) {
|
||||||
form.setError('password', {
|
form.setError('password', {
|
||||||
type: 'manual',
|
type: 'manual',
|
||||||
message: '请输入验证码',
|
message: loginMode === 'sms' ? '请输入验证码' : '请输入密码',
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -163,11 +187,12 @@ export default function LoginPage(props: LoginPageProps) {
|
|||||||
username: values.username,
|
username: values.username,
|
||||||
password: values.password,
|
password: values.password,
|
||||||
remember: values.remember,
|
remember: values.remember,
|
||||||
|
mode: loginMode === 'sms' ? 'phone_code' : 'password', // 后端区分登录方式
|
||||||
})
|
})
|
||||||
|
|
||||||
// 登录失败
|
// 登录失败
|
||||||
if (!result.success) {
|
if (!result.success) {
|
||||||
throw new Error(result.message || '请检查手机号码和验证码是否正确')
|
throw new Error(result.message || '请检查账号和密码/验证码是否正确')
|
||||||
}
|
}
|
||||||
|
|
||||||
// 登录成功
|
// 登录成功
|
||||||
@@ -193,7 +218,6 @@ export default function LoginPage(props: LoginPageProps) {
|
|||||||
|
|
||||||
const params = useSearchParams()
|
const params = useSearchParams()
|
||||||
const redirect = params.get('redirect')
|
const redirect = params.get('redirect')
|
||||||
|
|
||||||
const refreshProfile = useProfileStore(store => store.refreshProfile)
|
const refreshProfile = useProfileStore(store => store.refreshProfile)
|
||||||
|
|
||||||
// ======================
|
// ======================
|
||||||
@@ -207,7 +231,6 @@ export default function LoginPage(props: LoginPageProps) {
|
|||||||
`flex justify-center xl:justify-end items-center`,
|
`flex justify-center xl:justify-end items-center`,
|
||||||
)}>
|
)}>
|
||||||
<Image src={bg} alt="背景图" fill priority className="absolute -z-20 object-cover"/>
|
<Image src={bg} alt="背景图" fill priority className="absolute -z-20 object-cover"/>
|
||||||
|
|
||||||
<Link href="/">
|
<Link href="/">
|
||||||
<Image src={logo} alt="logo" priority height={64} className="absolute top-8 left-8 -z-10"/>
|
<Image src={logo} alt="logo" priority height={64} className="absolute top-8 left-8 -z-10"/>
|
||||||
</Link>
|
</Link>
|
||||||
@@ -217,8 +240,32 @@ export default function LoginPage(props: LoginPageProps) {
|
|||||||
<CardHeader className="text-center">
|
<CardHeader className="text-center">
|
||||||
<CardTitle className="text-2xl">登录/注册</CardTitle>
|
<CardTitle className="text-2xl">登录/注册</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
|
|
||||||
<CardContent className="px-8">
|
<CardContent className="px-8">
|
||||||
|
{/* 登录方式切换 */}
|
||||||
|
<Card className="mb-6">
|
||||||
|
<CardContent className="flex justify-center gap-2">
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
theme={loginMode === 'sms' ? 'gradient' : 'outline'}
|
||||||
|
onClick={() => {
|
||||||
|
setLoginMode('sms')
|
||||||
|
form.reset({username: form.getValues('username'), password: '', remember: false})
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
验证码登录
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
theme={loginMode === 'password' ? 'gradient' : 'outline'}
|
||||||
|
onClick={() => {
|
||||||
|
setLoginMode('password')
|
||||||
|
form.reset({username: form.getValues('username'), password: '', remember: false})
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
密码登录
|
||||||
|
</Button>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
<Form<FormValues> className="space-y-6" onSubmit={onSubmit} form={form}>
|
<Form<FormValues> className="space-y-6" onSubmit={onSubmit} form={form}>
|
||||||
<FormField name="username" label="手机号码">
|
<FormField name="username" label="手机号码">
|
||||||
{({id, field}) => (
|
{({id, field}) => (
|
||||||
@@ -231,9 +278,9 @@ export default function LoginPage(props: LoginPageProps) {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</FormField>
|
</FormField>
|
||||||
|
<FormField name="password" label={loginMode === 'sms' ? '验证码' : '密码'}>
|
||||||
<FormField name="password" label="验证码">
|
{({id, field}) =>
|
||||||
{({id, field}) => (
|
loginMode === 'sms' ? (
|
||||||
<div className="flex space-x-4">
|
<div className="flex space-x-4">
|
||||||
<Input
|
<Input
|
||||||
{...field}
|
{...field}
|
||||||
@@ -251,9 +298,45 @@ export default function LoginPage(props: LoginPageProps) {
|
|||||||
{countdown > 0 ? `${countdown}秒后重发` : '获取验证码'}
|
{countdown > 0 ? `${countdown}秒后重发` : '获取验证码'}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="relative">
|
||||||
|
<Input
|
||||||
|
{...field}
|
||||||
|
id={id}
|
||||||
|
type={showPwd ? 'text' : 'password'}
|
||||||
|
className="h-12 pr-10"
|
||||||
|
placeholder="至少6位密码,需包含字母和数字"
|
||||||
|
autoComplete="current-password"
|
||||||
|
minLength={6}
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
tabIndex={-1}
|
||||||
|
className="absolute right-2 top-1/2 -translate-y-1/2 text-gray-400 hover:text-gray-600"
|
||||||
|
onClick={() => setShowPwd(v => !v)}
|
||||||
|
aria-label={showPwd ? '隐藏密码' : '显示密码'}
|
||||||
|
>
|
||||||
|
{showPwd ? (
|
||||||
|
// 眼睛开 icon
|
||||||
|
<svg width="20" height="20" fill="none" viewBox="0 0 20 20">
|
||||||
|
<path stroke="currentColor" strokeWidth="1.5" d="M1.667 10S4.167 4.167 10 4.167 18.333 10 18.333 10 15.833 15.833 10 15.833 1.667 10 1.667 10Z"/>
|
||||||
|
<circle cx="10" cy="10" r="2.5" stroke="currentColor" strokeWidth="1.5"/>
|
||||||
|
</svg>
|
||||||
|
) : (
|
||||||
|
// 眼睛关 icon
|
||||||
|
<>
|
||||||
|
<svg width="20" height="20" fill="none" viewBox="0 0 20 20">
|
||||||
|
<path stroke="currentColor" strokeWidth="1.5" d="M1.667 10S4.167 4.167 10 4.167c1.57 0 2.97.33 4.13.87M18.333 10s-2.5 5.833-8.333 5.833c-1.57 0-2.97-.33-4.13-.87"/>
|
||||||
|
<circle cx="10" cy="10" r="2.5" stroke="currentColor" strokeWidth="1.5"/>
|
||||||
|
<path stroke="currentColor" strokeWidth="1.5" d="M3 3l14 14"/>
|
||||||
|
</svg>
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
</FormField>
|
</FormField>
|
||||||
|
|
||||||
<FormField name="remember">
|
<FormField name="remember">
|
||||||
{({id, field}) => (
|
{({id, field}) => (
|
||||||
<div className="flex flex-row items-start space-x-2 space-y-0">
|
<div className="flex flex-row items-start space-x-2 space-y-0">
|
||||||
@@ -268,7 +351,6 @@ export default function LoginPage(props: LoginPageProps) {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</FormField>
|
</FormField>
|
||||||
|
|
||||||
<div className="flex flex-col gap-3">
|
<div className="flex flex-col gap-3">
|
||||||
<Button
|
<Button
|
||||||
className="w-full h-12 text-lg"
|
className="w-full h-12 text-lg"
|
||||||
@@ -276,9 +358,8 @@ export default function LoginPage(props: LoginPageProps) {
|
|||||||
theme="gradient"
|
theme="gradient"
|
||||||
disabled={submitting}
|
disabled={submitting}
|
||||||
>
|
>
|
||||||
{submitting ? '登录中...' : '注册 / 登录'}
|
{submitting ? '登录中...' : (loginMode === 'sms' ? '注册 / 登录' : '密码登录')}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<p className="text-xs text-center text-gray-500">
|
<p className="text-xs text-center text-gray-500">
|
||||||
登录即表示您同意
|
登录即表示您同意
|
||||||
<a href="#" className="text-blue-600 hover:text-blue-500">《用户协议》</a>
|
<a href="#" className="text-blue-600 hover:text-blue-500">《用户协议》</a>
|
||||||
@@ -289,14 +370,14 @@ export default function LoginPage(props: LoginPageProps) {
|
|||||||
</Form>
|
</Form>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
{/* 图形验证码弹窗 */}
|
{/* 图形验证码弹窗 */}
|
||||||
|
{loginMode === 'sms' && (
|
||||||
<Captcha
|
<Captcha
|
||||||
showCaptcha={showCaptcha}
|
showCaptcha={showCaptcha}
|
||||||
setShowCaptcha={setShowCaptcha}
|
setShowCaptcha={setShowCaptcha}
|
||||||
handleSendCode={sendCode}
|
handleSendCode={sendCode}
|
||||||
/>
|
/>
|
||||||
|
)}
|
||||||
</main>
|
</main>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ export default function Footer(props: FooterProps) {
|
|||||||
<SiteNavList
|
<SiteNavList
|
||||||
title="使用案例"
|
title="使用案例"
|
||||||
items={[
|
items={[
|
||||||
{name: `数据抓取`, href: `#`},
|
{name: `数据抓取`, href: `/data-capture`},
|
||||||
{name: `媒体矩阵`, href: `#`},
|
{name: `媒体矩阵`, href: `#`},
|
||||||
{name: `广告验证`, href: `#`},
|
{name: `广告验证`, href: `#`},
|
||||||
{name: `价格监控`, href: `#`},
|
{name: `价格监控`, href: `#`},
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import zod from 'zod'
|
|||||||
import {merge} from '@/lib/utils'
|
import {merge} from '@/lib/utils'
|
||||||
import {Button} from '@/components/ui/button'
|
import {Button} from '@/components/ui/button'
|
||||||
import {useState} from 'react'
|
import {useState} from 'react'
|
||||||
import {listAccount} from '@/actions/dashboard'
|
import {statisticsResourceUsage} from '@/actions/dashboard'
|
||||||
import {ExtraResp} from '@/lib/api'
|
import {ExtraResp} from '@/lib/api'
|
||||||
import {toast} from 'sonner'
|
import {toast} from 'sonner'
|
||||||
import {addDays, format} from 'date-fns'
|
import {addDays, format} from 'date-fns'
|
||||||
@@ -20,19 +20,13 @@ import {Label} from '@/components/ui/label'
|
|||||||
import {ChartConfig, ChartContainer} from '@/components/ui/chart'
|
import {ChartConfig, ChartContainer} from '@/components/ui/chart'
|
||||||
import {CartesianGrid, XAxis, YAxis, Tooltip, Area, AreaChart, Legend} from 'recharts'
|
import {CartesianGrid, XAxis, YAxis, Tooltip, Area, AreaChart, Legend} from 'recharts'
|
||||||
|
|
||||||
type ChartDataItem = {
|
|
||||||
date: string
|
|
||||||
count: number
|
|
||||||
count2?: number
|
|
||||||
}
|
|
||||||
|
|
||||||
type ChartsProps = {
|
type ChartsProps = {
|
||||||
initialData?: ExtraResp<typeof listAccount>
|
initialData?: ExtraResp<typeof statisticsResourceUsage>
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Charts({initialData}: ChartsProps) {
|
export default function Charts({initialData}: ChartsProps) {
|
||||||
// const [submittedData, setSubmittedData] = useState<ExtraReq<typeof listAccount>>()
|
// const [submittedData, setSubmittedData] = useState<ExtraReq<typeof listAccount>>()
|
||||||
const [submittedData, setSubmittedData] = useState<ExtraResp<typeof listAccount>>(initialData || [])
|
const [submittedData, setSubmittedData] = useState<ExtraResp<typeof statisticsResourceUsage>>(initialData || [])
|
||||||
const formSchema = zod.object({
|
const formSchema = zod.object({
|
||||||
resource_no: zod.string().optional(),
|
resource_no: zod.string().optional(),
|
||||||
create_after: zod.date().optional(),
|
create_after: zod.date().optional(),
|
||||||
@@ -58,7 +52,7 @@ export default function Charts({initialData}: ChartsProps) {
|
|||||||
create_before: value.create_before ?? today,
|
create_before: value.create_before ?? today,
|
||||||
}
|
}
|
||||||
|
|
||||||
const resp = await listAccount(res)
|
const resp = await statisticsResourceUsage(res)
|
||||||
if (!resp.success) {
|
if (!resp.success) {
|
||||||
toast.error('接口请求失败:' + resp.message)
|
toast.error('接口请求失败:' + resp.message)
|
||||||
return
|
return
|
||||||
@@ -149,7 +143,7 @@ const config = {
|
|||||||
} satisfies ChartConfig
|
} satisfies ChartConfig
|
||||||
|
|
||||||
type DashboardChartProps = {
|
type DashboardChartProps = {
|
||||||
data: ExtraResp<typeof listAccount>
|
data: ExtraResp<typeof statisticsResourceUsage>
|
||||||
}
|
}
|
||||||
|
|
||||||
function DashboardChart(props: DashboardChartProps) {
|
function DashboardChart(props: DashboardChartProps) {
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import Charts from './_client/charts'
|
|||||||
import UserCenter from './_client/userCenter'
|
import UserCenter from './_client/userCenter'
|
||||||
import soon from './_assets/coming-soon.svg'
|
import soon from './_assets/coming-soon.svg'
|
||||||
import mask from './_assets/Mask group.webp'
|
import mask from './_assets/Mask group.webp'
|
||||||
import {Button} from '@/components/ui/button'
|
import {ExtraResp} from '@/lib/api'
|
||||||
|
|
||||||
export type DashboardPageProps = {}
|
export type DashboardPageProps = {}
|
||||||
|
|
||||||
@@ -43,12 +43,7 @@ export default async function DashboardPage(props: DashboardPageProps) {
|
|||||||
|
|
||||||
{/* 磁贴集 */}
|
{/* 磁贴集 */}
|
||||||
{initData && (
|
{initData && (
|
||||||
<Pins
|
<Pins {...initData.free}/>
|
||||||
short_term={String(initData.free.short.ResourceCount)}
|
|
||||||
short_term_monthly={String(initData.free.short.ResourceQuotaSum)}
|
|
||||||
long_term={String(initData.free.long.ResourceCount)}
|
|
||||||
long_term_monthly={String(initData.free.long.ResourceDailyFreeSum)}
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* 图表 */}
|
{/* 图表 */}
|
||||||
@@ -69,13 +64,9 @@ export default async function DashboardPage(props: DashboardPageProps) {
|
|||||||
</Page>
|
</Page>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
type DashboardChartProps = {
|
type DashboardPinsProps = ExtraResp<typeof listInitialization>['free']
|
||||||
short_term: string
|
|
||||||
short_term_monthly: string
|
function Pins(props: DashboardPinsProps) {
|
||||||
long_term: string
|
|
||||||
long_term_monthly: string
|
|
||||||
}
|
|
||||||
function Pins(props: DashboardChartProps) {
|
|
||||||
return (
|
return (
|
||||||
<div className="flex md:row-start-2 md:col-start-1 md:col-span-3 gap-4 max-md:flex-col">
|
<div className="flex md:row-start-2 md:col-start-1 md:col-span-3 gap-4 max-md:flex-col">
|
||||||
{/* 短效 */}
|
{/* 短效 */}
|
||||||
@@ -91,7 +82,7 @@ function Pins(props: DashboardChartProps) {
|
|||||||
<h4>包时</h4>
|
<h4>包时</h4>
|
||||||
<p className="flex flex-col items-end">
|
<p className="flex flex-col items-end">
|
||||||
<span className="text-sm text-weak">当日可提取数量</span>
|
<span className="text-sm text-weak">当日可提取数量</span>
|
||||||
<span className="text-sm">{props.short_term}</span>
|
<span className="text-sm">{props.short.resource_daily_free_sum}</span>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="border-b"></div>
|
<div className="border-b"></div>
|
||||||
@@ -99,7 +90,7 @@ function Pins(props: DashboardChartProps) {
|
|||||||
<h4 className="text-balance">包量</h4>
|
<h4 className="text-balance">包量</h4>
|
||||||
<p className="flex flex-col items-end">
|
<p className="flex flex-col items-end">
|
||||||
<span className="text-sm text-weak">剩余可提取数量</span>
|
<span className="text-sm text-weak">剩余可提取数量</span>
|
||||||
<span className="text-sm">{props.short_term_monthly}</span>
|
<span className="text-sm">{props.short.resource_quota_sum}</span>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
@@ -118,7 +109,7 @@ function Pins(props: DashboardChartProps) {
|
|||||||
<h4 className="text-balance">包时</h4>
|
<h4 className="text-balance">包时</h4>
|
||||||
<p className="flex flex-col items-end">
|
<p className="flex flex-col items-end">
|
||||||
<span className="text-sm text-weak" >当日可提取数量</span>
|
<span className="text-sm text-weak" >当日可提取数量</span>
|
||||||
<span className="text-sm">{props.long_term}</span>
|
<span className="text-sm">{props.long.resource_daily_free_sum}</span>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="border-b"></div>
|
<div className="border-b"></div>
|
||||||
@@ -126,7 +117,7 @@ function Pins(props: DashboardChartProps) {
|
|||||||
<h4 className="text-balance">包量</h4>
|
<h4 className="text-balance">包量</h4>
|
||||||
<p className="flex flex-col items-end">
|
<p className="flex flex-col items-end">
|
||||||
<span className="text-sm text-weak">剩余可提取数量</span>
|
<span className="text-sm text-weak">剩余可提取数量</span>
|
||||||
<span className="text-sm">{props.long_term_monthly}</span>
|
<span className="text-sm">{props.long.resource_quota_sum}</span>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
|
|||||||
@@ -324,7 +324,16 @@ function PasswordForm(props: {
|
|||||||
type Schema = z.infer<typeof schema>
|
type Schema = z.infer<typeof schema>
|
||||||
|
|
||||||
const form = useForm<Schema>({
|
const form = useForm<Schema>({
|
||||||
resolver: zodResolver(schema),
|
resolver: zodResolver(
|
||||||
|
schema.refine(
|
||||||
|
data =>
|
||||||
|
/^(?=.*[a-z])(?=.*[A-Z]).{6,}$/.test(data.password),
|
||||||
|
{
|
||||||
|
message: '密码需包含大小写字母,且不少于6位',
|
||||||
|
path: ['password'],
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
phone: '',
|
phone: '',
|
||||||
captcha: '',
|
captcha: '',
|
||||||
@@ -333,6 +342,7 @@ function PasswordForm(props: {
|
|||||||
confirm_password: '',
|
confirm_password: '',
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
const router = useRouter()
|
||||||
const handler = form.handleSubmit(async (value) => {
|
const handler = form.handleSubmit(async (value) => {
|
||||||
try {
|
try {
|
||||||
const resp = await updatePassword({
|
const resp = await updatePassword({
|
||||||
@@ -344,8 +354,10 @@ function PasswordForm(props: {
|
|||||||
throw new Error(resp.message)
|
throw new Error(resp.message)
|
||||||
}
|
}
|
||||||
|
|
||||||
toast.success(`保存成功`)
|
toast.success(`保存成功,请重新登录`)
|
||||||
setOpen(false)
|
setOpen(false)
|
||||||
|
// 立即跳转到登录页
|
||||||
|
router.replace('/login')
|
||||||
}
|
}
|
||||||
catch (e) {
|
catch (e) {
|
||||||
console.error(e)
|
console.error(e)
|
||||||
|
|||||||
@@ -230,14 +230,14 @@ export default function WhitelistPage(props: WhitelistPageProps) {
|
|||||||
<Plus/>
|
<Plus/>
|
||||||
添加白名单
|
添加白名单
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
{/* <Button
|
||||||
theme="fail"
|
theme="fail"
|
||||||
className="ml-2"
|
className="ml-2"
|
||||||
disabled={selection.size === 0 || wait}
|
disabled={selection.size === 0 || wait}
|
||||||
onClick={() => confirmRemove()}>
|
onClick={() => confirmRemove()}>
|
||||||
<Trash2/>
|
<Trash2/>
|
||||||
删除选中
|
删除选中
|
||||||
</Button>
|
</Button> */}
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* 数据表 */}
|
{/* 数据表 */}
|
||||||
|
|||||||
@@ -276,7 +276,11 @@ const FormFields = memo(() => {
|
|||||||
{...field}
|
{...field}
|
||||||
id={id}
|
id={id}
|
||||||
type="number"
|
type="number"
|
||||||
onChange={e => field.onChange(Number(e.target.value))}
|
min={1}
|
||||||
|
onChange={(e) => {
|
||||||
|
const value = Math.max(1, Number(e.target.value))
|
||||||
|
field.onChange(value)
|
||||||
|
}}
|
||||||
className="h-10"
|
className="h-10"
|
||||||
placeholder="输入提取数量"
|
placeholder="输入提取数量"
|
||||||
/>
|
/>
|
||||||
|
|||||||
Reference in New Issue
Block a user