u
This commit is contained in:
1 parent
ca2fa3543d
commit
7333e6695f
13 files changed
+211
-105
No files matched your search
@@ -1,7 +1,13 @@
|
|||||||
// Learn more https://docs.expo.io/guides/customizing-metro
|
// metro.config.js
|
||||||
const { getDefaultConfig } = require('expo/metro-config')
|
const { getDefaultConfig } = require('expo/metro-config')
|
||||||
const { withNativeWind } = require('nativewind/metro')
|
const { withNativeWind } = require('nativewind/metro')
|
||||||
|
|
||||||
/** @type {import('expo/metro-config').MetroConfig} */
|
/** @type {import('expo/metro-config').MetroConfig} */
|
||||||
const config = getDefaultConfig(__dirname)
|
const config = getDefaultConfig(__dirname)
|
||||||
|
|
||||||
|
config.resolver = {
|
||||||
|
...config.resolver,
|
||||||
|
unstable_enablePackageExports: true,
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = withNativeWind(config, { input: './src/styles/global.css' })
|
module.exports = withNativeWind(config, { input: './src/styles/global.css' })
|
||||||
@@ -15,6 +15,7 @@
|
|||||||
"@gluestack/ui-next-adapter": "^3.0.3",
|
"@gluestack/ui-next-adapter": "^3.0.3",
|
||||||
"@legendapp/motion": "^2.4.0",
|
"@legendapp/motion": "^2.4.0",
|
||||||
"@react-native-async-storage/async-storage": "2.2.0",
|
"@react-native-async-storage/async-storage": "2.2.0",
|
||||||
|
"alova": "^3.3.4",
|
||||||
"expo": "~54.0.20",
|
"expo": "~54.0.20",
|
||||||
"expo-constants": "~18.0.10",
|
"expo-constants": "~18.0.10",
|
||||||
"expo-font": "~14.0.9",
|
"expo-font": "~14.0.9",
|
||||||
|
|||||||
+22
@@ -23,6 +23,9 @@ importers:
|
|||||||
'@react-native-async-storage/async-storage':
|
'@react-native-async-storage/async-storage':
|
||||||
specifier: 2.2.0
|
specifier: 2.2.0
|
||||||
version: 2.2.0(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))
|
version: 2.2.0(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))
|
||||||
|
alova:
|
||||||
|
specifier: ^3.3.4
|
||||||
|
version: 3.3.4
|
||||||
expo:
|
expo:
|
||||||
specifier: ~54.0.20
|
specifier: ~54.0.20
|
||||||
version: 54.0.21(@babel/core@7.28.5)(@expo/metro-runtime@6.1.2)(expo-router@6.0.14)(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)
|
version: 54.0.21(@babel/core@7.28.5)(@expo/metro-runtime@6.1.2)(expo-router@6.0.14)(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)
|
||||||
@@ -108,6 +111,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==}
|
resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
|
|
||||||
|
'@alova/shared@1.3.1':
|
||||||
|
resolution: {integrity: sha512-ijSOaFLUFcVzMKSY3avoEE5C03/p9atjMDPBwvNkwnzaCrhv6/m4A121NdadF8YlHCRuifyYfz90IyEdMXTsJg==}
|
||||||
|
|
||||||
'@antfu/eslint-config@6.2.0':
|
'@antfu/eslint-config@6.2.0':
|
||||||
resolution: {integrity: sha512-ksasd+mJk441HHodwPh3Nhmwo9jSHUmgQyfTxMQM05U7SjDS0fy2KnXnPx0Vhc/CqKiJnx8wGpQCCJibyASX9Q==}
|
resolution: {integrity: sha512-ksasd+mJk441HHodwPh3Nhmwo9jSHUmgQyfTxMQM05U7SjDS0fy2KnXnPx0Vhc/CqKiJnx8wGpQCCJibyASX9Q==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
@@ -2133,6 +2139,10 @@ packages:
|
|||||||
ajv@6.12.6:
|
ajv@6.12.6:
|
||||||
resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==}
|
resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==}
|
||||||
|
|
||||||
|
alova@3.3.4:
|
||||||
|
resolution: {integrity: sha512-UKKqXdvf8aQ4C7m3brO77YWe5CDz8N59PdAUz7M8gowKUUXTutbk0Vk5DRBrCe0hMUyyNMUhdCZ38llGxCViyQ==}
|
||||||
|
engines: {node: '>= 18.0.0'}
|
||||||
|
|
||||||
anser@1.4.10:
|
anser@1.4.10:
|
||||||
resolution: {integrity: sha512-hCv9AqTQ8ycjpSd3upOJd7vFwW1JaoYQ7tpham03GJ1ca8/65rqn0RpaWpItOAd6ylW9wAw6luXYPJIyPFVOww==}
|
resolution: {integrity: sha512-hCv9AqTQ8ycjpSd3upOJd7vFwW1JaoYQ7tpham03GJ1ca8/65rqn0RpaWpItOAd6ylW9wAw6luXYPJIyPFVOww==}
|
||||||
|
|
||||||
@@ -4386,6 +4396,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==}
|
resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==}
|
||||||
engines: {node: '>= 0.6'}
|
engines: {node: '>= 0.6'}
|
||||||
|
|
||||||
|
rate-limiter-flexible@5.0.5:
|
||||||
|
resolution: {integrity: sha512-+/dSQfo+3FYwYygUs/V2BBdwGa9nFtakDwKt4l0bnvNB53TNT++QSFewwHX9qXrZJuMe9j+TUaU21lm5ARgqdQ==}
|
||||||
|
|
||||||
rc@1.2.8:
|
rc@1.2.8:
|
||||||
resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==}
|
resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
@@ -5306,6 +5319,8 @@ snapshots:
|
|||||||
|
|
||||||
'@alloc/quick-lru@5.2.0': {}
|
'@alloc/quick-lru@5.2.0': {}
|
||||||
|
|
||||||
|
'@alova/shared@1.3.1': {}
|
||||||
|
|
||||||
'@antfu/eslint-config@6.2.0(@vue/compiler-sfc@3.5.22)(eslint@9.39.0(jiti@1.21.7))(typescript@5.9.3)':
|
'@antfu/eslint-config@6.2.0(@vue/compiler-sfc@3.5.22)(eslint@9.39.0(jiti@1.21.7))(typescript@5.9.3)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@antfu/install-pkg': 1.1.0
|
'@antfu/install-pkg': 1.1.0
|
||||||
@@ -8212,6 +8227,11 @@ snapshots:
|
|||||||
json-schema-traverse: 0.4.1
|
json-schema-traverse: 0.4.1
|
||||||
uri-js: 4.4.1
|
uri-js: 4.4.1
|
||||||
|
|
||||||
|
alova@3.3.4:
|
||||||
|
dependencies:
|
||||||
|
'@alova/shared': 1.3.1
|
||||||
|
rate-limiter-flexible: 5.0.5
|
||||||
|
|
||||||
anser@1.4.10: {}
|
anser@1.4.10: {}
|
||||||
|
|
||||||
ansi-escapes@4.3.2:
|
ansi-escapes@4.3.2:
|
||||||
@@ -10899,6 +10919,8 @@ snapshots:
|
|||||||
|
|
||||||
range-parser@1.2.1: {}
|
range-parser@1.2.1: {}
|
||||||
|
|
||||||
|
rate-limiter-flexible@5.0.5: {}
|
||||||
|
|
||||||
rc@1.2.8:
|
rc@1.2.8:
|
||||||
dependencies:
|
dependencies:
|
||||||
deep-extend: 0.6.0
|
deep-extend: 0.6.0
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import {
|
|||||||
} from '@/components/ui/form-control'
|
} from '@/components/ui/form-control'
|
||||||
import { CheckIcon, EyeIcon, EyeOffIcon } from '@/components/ui/icon'
|
import { CheckIcon, EyeIcon, EyeOffIcon } from '@/components/ui/icon'
|
||||||
import { Input, InputField, InputIcon, InputSlot } from '@/components/ui/input'
|
import { Input, InputField, InputIcon, InputSlot } from '@/components/ui/input'
|
||||||
|
import alovaInstance from '@/request'
|
||||||
import { useToast } from '@/utils/toast'
|
import { useToast } from '@/utils/toast'
|
||||||
|
|
||||||
interface LoginForm {
|
interface LoginForm {
|
||||||
@@ -37,20 +38,13 @@ export default function AuthLogin() {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const defaultLogin = () => {
|
const defaultLogin = () => {
|
||||||
if (form.username === '') {
|
alovaInstance.Post('/auth/login/default', {
|
||||||
toast.show({
|
title: 'foo',
|
||||||
title: '账号不能为空!',
|
body: 'bar',
|
||||||
action: 'error',
|
userId: 1,
|
||||||
})
|
}).then((res) => {
|
||||||
return
|
console.log(res)
|
||||||
}
|
})
|
||||||
if (form.password === '') {
|
|
||||||
toast.show({
|
|
||||||
title: '密码不能为空!',
|
|
||||||
action: 'error',
|
|
||||||
})
|
|
||||||
}
|
|
||||||
//
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import type { AFormRef } from '@/components/common/ao'
|
import type { AFormRef } from '@/components/common/ao'
|
||||||
import { Lock, UserRound } from 'lucide-react-native'
|
import { Lock, UserRound } from 'lucide-react-native'
|
||||||
import { useEffect, useRef, useState } from 'react'
|
import { useRef, useState } from 'react'
|
||||||
import { Image, ScrollView, View } from 'react-native'
|
import { Image, ScrollView, View } from 'react-native'
|
||||||
|
|
||||||
import { AForm, AFormItem, AInput } from '@/components/common/ao'
|
import { AForm, AFormItem, AInput } from '@/components/common/ao'
|
||||||
@@ -11,28 +11,16 @@ interface RegisterForm {
|
|||||||
username: string
|
username: string
|
||||||
password: string
|
password: string
|
||||||
confirmPassword: string
|
confirmPassword: string
|
||||||
a: string
|
|
||||||
b: string
|
|
||||||
c: string
|
|
||||||
d: string
|
|
||||||
e: string
|
|
||||||
f: string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function AuthLogin() {
|
export default function AuthRegister() {
|
||||||
const scrollRef = useRef<ScrollView>(null)
|
const scrollRef = useRef<ScrollView>(null)
|
||||||
|
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
|
|
||||||
const [form, setForm] = useState<RegisterForm>({
|
const [form, setForm] = useState<RegisterForm>({
|
||||||
username: '',
|
username: '',
|
||||||
password: '',
|
password: '',
|
||||||
confirmPassword: '',
|
confirmPassword: '',
|
||||||
a: '',
|
|
||||||
b: '',
|
|
||||||
c: '',
|
|
||||||
d: '',
|
|
||||||
e: '',
|
|
||||||
f: '',
|
|
||||||
})
|
})
|
||||||
const [loading, setLoading] = useState(false)
|
const [loading, setLoading] = useState(false)
|
||||||
const formRef = useRef<AFormRef>(null)
|
const formRef = useRef<AFormRef>(null)
|
||||||
@@ -41,7 +29,7 @@ export default function AuthLogin() {
|
|||||||
setForm(prev => ({ ...prev, [field]: value }))
|
setForm(prev => ({ ...prev, [field]: value }))
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaultLogin = () => {
|
const handleRegister = () => {
|
||||||
if (formRef.current?.validate()) {
|
if (formRef.current?.validate()) {
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
toast.show({ title: '校验通过,开始注册', action: 'success' })
|
toast.show({ title: '校验通过,开始注册', action: 'success' })
|
||||||
@@ -55,10 +43,18 @@ export default function AuthLogin() {
|
|||||||
return (
|
return (
|
||||||
<ScrollView ref={scrollRef} style={{ flex: 1 }}>
|
<ScrollView ref={scrollRef} style={{ flex: 1 }}>
|
||||||
<View className="flex flex-col items-center px-6">
|
<View className="flex flex-col items-center px-6">
|
||||||
<Image source={require('@/static/images/logo.png')} style={{ width: 120, height: 120 }} className="my-12" />
|
<Image
|
||||||
|
source={require('@/static/images/logo.png')}
|
||||||
|
style={{ width: 120, height: 120 }}
|
||||||
|
className="my-12"
|
||||||
|
/>
|
||||||
|
|
||||||
<AForm ref={formRef} form={form} scrollRef={scrollRef}>
|
<AForm ref={formRef} form={form} scrollRef={scrollRef}>
|
||||||
<AFormItem name="username" label="账号" isRequired>
|
<AFormItem
|
||||||
|
name="username"
|
||||||
|
label="账号"
|
||||||
|
isRequired
|
||||||
|
>
|
||||||
<AInput
|
<AInput
|
||||||
placeholder="请输入账号"
|
placeholder="请输入账号"
|
||||||
leftIcon={UserRound}
|
leftIcon={UserRound}
|
||||||
@@ -70,9 +66,9 @@ export default function AuthLogin() {
|
|||||||
<AFormItem
|
<AFormItem
|
||||||
name="password"
|
name="password"
|
||||||
label="密码"
|
label="密码"
|
||||||
isRequired
|
|
||||||
rules={[
|
rules={[
|
||||||
(val: any) => val && !/\d{6}/.test(val) ? '请输入6位数字密码' : null,
|
{ required: true, message: '密码是必填项' },
|
||||||
|
{ pattern: /^\d{6}$/, message: '请输入6位数字密码' },
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<AInput
|
<AInput
|
||||||
@@ -87,9 +83,12 @@ export default function AuthLogin() {
|
|||||||
<AFormItem
|
<AFormItem
|
||||||
name="confirmPassword"
|
name="confirmPassword"
|
||||||
label="确认密码"
|
label="确认密码"
|
||||||
isRequired
|
|
||||||
rules={[
|
rules={[
|
||||||
(val: any) => val !== form.password ? '两次密码不一致' : null,
|
{ required: true, message: '确认密码是必填项' },
|
||||||
|
{
|
||||||
|
validator: val => val === form.password,
|
||||||
|
message: '两次密码不一致',
|
||||||
|
},
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<AInput
|
<AInput
|
||||||
@@ -100,21 +99,9 @@ export default function AuthLogin() {
|
|||||||
onChange={text => handleInputChange('confirmPassword', text)}
|
onChange={text => handleInputChange('confirmPassword', text)}
|
||||||
/>
|
/>
|
||||||
</AFormItem>
|
</AFormItem>
|
||||||
|
|
||||||
{/* 其他表单项 */}
|
|
||||||
{['a', 'b', 'c', 'd', 'e', 'f'].map(field => (
|
|
||||||
<AFormItem key={field} name={field} label={field} isRequired>
|
|
||||||
<AInput
|
|
||||||
placeholder="请输入"
|
|
||||||
isPassword
|
|
||||||
value={form[field]}
|
|
||||||
onChange={text => handleInputChange(field, text)}
|
|
||||||
/>
|
|
||||||
</AFormItem>
|
|
||||||
))}
|
|
||||||
</AForm>
|
</AForm>
|
||||||
|
|
||||||
<Button className="w-full mb-4" onPress={defaultLogin} disabled={loading}>
|
<Button className="w-full mb-4" onPress={handleRegister} disabled={loading}>
|
||||||
{loading
|
{loading
|
||||||
? (
|
? (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@@ -1,19 +1,19 @@
|
|||||||
import type { ScrollView } from 'react-native'
|
import type { ScrollView } from 'react-native'
|
||||||
import React, { createContext, forwardRef, useCallback, useEffect, useImperativeHandle, useRef } from 'react'
|
import React, { createContext, forwardRef, useCallback, useImperativeHandle, useRef } from 'react'
|
||||||
|
|
||||||
export interface FormRef {
|
export interface FormRef {
|
||||||
validate: () => boolean
|
validate: () => boolean
|
||||||
scrollToField: (name: string) => void // 新增的滚动方法
|
scrollToField: (name: string) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
interface FormProps<T extends Record<string, any>> {
|
interface FormProps {
|
||||||
children: React.ReactNode
|
children: React.ReactNode
|
||||||
form: T
|
form: Record<string, any>
|
||||||
scrollRef: React.RefObject<ScrollView>
|
scrollRef: React.RefObject<ScrollView | null>
|
||||||
}
|
}
|
||||||
|
|
||||||
interface FormContextType<T = any> {
|
interface FormContextType {
|
||||||
form: T
|
form: Record<string, any>
|
||||||
registerValidator: (name: string, validator: () => boolean) => void
|
registerValidator: (name: string, validator: () => boolean) => void
|
||||||
unregisterValidator: (name: string) => void
|
unregisterValidator: (name: string) => void
|
||||||
registerFieldPosition: (name: string, y: number) => void
|
registerFieldPosition: (name: string, y: number) => void
|
||||||
@@ -21,19 +21,16 @@ interface FormContextType<T = any> {
|
|||||||
|
|
||||||
export const FormContext = createContext<FormContextType | undefined>(undefined)
|
export const FormContext = createContext<FormContextType | undefined>(undefined)
|
||||||
|
|
||||||
const Form = forwardRef<FormRef, FormProps<any>>(({ children, form, scrollRef }, ref) => {
|
const Form = forwardRef<FormRef, FormProps>(({ children, form, scrollRef }, ref) => {
|
||||||
// 使用 Array 来代替 Map
|
const validatorsRef = useRef<Array<{ name: string, validator: () => boolean }>>([])
|
||||||
const validatorsRef = useRef<Array<{ name: string, validator: () => boolean }>>([]) // 持有验证器顺序
|
const fieldPositions = useRef<Map<string, number>>(new Map())
|
||||||
const fieldPositions = useRef<Map<string, number>>(new Map()) // 用来存储每个字段的 Y 坐标
|
|
||||||
|
|
||||||
const registerValidator = useCallback((name: string, validator: () => boolean) => {
|
const registerValidator = useCallback((name: string, validator: () => boolean) => {
|
||||||
console.log(`注册:${name}`)
|
|
||||||
validatorsRef.current.push({ name, validator })
|
validatorsRef.current.push({ name, validator })
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
const unregisterValidator = useCallback((name: string) => {
|
const unregisterValidator = useCallback((name: string) => {
|
||||||
console.log(`移除:${name}`)
|
validatorsRef.current = validatorsRef.current.filter(v => v.name !== name)
|
||||||
validatorsRef.current = validatorsRef.current.filter(validator => validator.name !== name)
|
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
const registerFieldPosition = useCallback((name: string, y: number) => {
|
const registerFieldPosition = useCallback((name: string, y: number) => {
|
||||||
@@ -44,34 +41,24 @@ const Form = forwardRef<FormRef, FormProps<any>>(({ children, form, scrollRef },
|
|||||||
let firstErrorField: string | null = null
|
let firstErrorField: string | null = null
|
||||||
let isValid = true
|
let isValid = true
|
||||||
|
|
||||||
// 按顺序遍历所有的验证器
|
|
||||||
for (const { name, validator } of validatorsRef.current) {
|
for (const { name, validator } of validatorsRef.current) {
|
||||||
const ok = validator()
|
if (!validator() && !firstErrorField) {
|
||||||
|
|
||||||
if (!ok && !firstErrorField) {
|
|
||||||
// 如果遇到第一个验证失败的字段,记录下来
|
|
||||||
firstErrorField = name
|
firstErrorField = name
|
||||||
isValid = false
|
isValid = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isValid)
|
if (!isValid && firstErrorField) {
|
||||||
return true
|
|
||||||
|
|
||||||
// 滚动到第一个错误字段
|
|
||||||
if (firstErrorField) {
|
|
||||||
scrollToField(firstErrorField)
|
scrollToField(firstErrorField)
|
||||||
}
|
}
|
||||||
|
|
||||||
return false
|
return isValid
|
||||||
}
|
}
|
||||||
|
|
||||||
const scrollToField = (name: string) => {
|
const scrollToField = (name: string) => {
|
||||||
if (scrollRef.current) {
|
const y = fieldPositions.current.get(name)
|
||||||
const y = fieldPositions.current.get(name)
|
if (y !== undefined && scrollRef.current) {
|
||||||
if (y !== undefined && scrollRef.current) {
|
scrollRef.current.scrollTo({ y, animated: true })
|
||||||
scrollRef.current.scrollTo({ y, animated: true })
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import React, { useContext, useEffect, useState } from 'react'
|
import React, { useContext, useEffect, useState } from 'react'
|
||||||
import { View } from 'react-native'
|
|
||||||
import {
|
import {
|
||||||
FormControl,
|
FormControl,
|
||||||
FormControlError,
|
FormControlError,
|
||||||
@@ -11,10 +10,21 @@ import {
|
|||||||
import { AlertCircleIcon } from '@/components/ui/icon'
|
import { AlertCircleIcon } from '@/components/ui/icon'
|
||||||
import { FormContext } from './Form'
|
import { FormContext } from './Form'
|
||||||
|
|
||||||
|
interface RuleType {
|
||||||
|
validator?: (val: any) => boolean
|
||||||
|
pattern?: RegExp
|
||||||
|
message?: string
|
||||||
|
len?: number
|
||||||
|
min?: number
|
||||||
|
max?: number
|
||||||
|
required?: boolean
|
||||||
|
whitespace?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
interface FormItemProps<T> {
|
interface FormItemProps<T> {
|
||||||
name: keyof T
|
name: keyof T
|
||||||
label?: string
|
label?: string
|
||||||
rules?: ((val: any) => string | null)[]
|
rules?: RuleType[]
|
||||||
children: React.ReactNode
|
children: React.ReactNode
|
||||||
isRequired?: boolean
|
isRequired?: boolean
|
||||||
}
|
}
|
||||||
@@ -33,21 +43,87 @@ function FormItem<T extends Record<string, any>>({
|
|||||||
if (!context)
|
if (!context)
|
||||||
return
|
return
|
||||||
|
|
||||||
const { registerValidator, unregisterValidator } = context
|
const currentName = String(name)
|
||||||
|
|
||||||
const validator = () => {
|
// 如果 isRequired 为 true,且 rules 中没有 required: true,则自动加
|
||||||
const value = context.form[name]
|
const hasRequiredInRules = rules.some(rule => rule.required)
|
||||||
|
const allRules = [...rules]
|
||||||
|
if (isRequired && !hasRequiredInRules) {
|
||||||
|
allRules.unshift({
|
||||||
|
required: true,
|
||||||
|
message: label ? `${label} 是必填项` : `${currentName} 是必填项`,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
if (isRequired && (value === undefined || value === null || value === '' || value.length === 0)) {
|
const validateFn = () => {
|
||||||
setError(`${String(label || name)} 是必填项`)
|
const value = context.form[currentName]
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const rule of rules) {
|
for (const rule of allRules) {
|
||||||
const msg = rule(value)
|
// 1. required 校验
|
||||||
if (msg) {
|
if (rule.required) {
|
||||||
setError(msg)
|
const empty
|
||||||
return false
|
= value === undefined
|
||||||
|
|| value === null
|
||||||
|
|| value === ''
|
||||||
|
|| (Array.isArray(value) && value.length === 0)
|
||||||
|
|| (rule.whitespace && typeof value === 'string' && value.trim() === '')
|
||||||
|
if (empty) {
|
||||||
|
setError(rule.message || `${label || currentName} 校验不通过`)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 自定义 validator 返回 true/false
|
||||||
|
if (rule.validator !== undefined) {
|
||||||
|
const ok = rule.validator(value)
|
||||||
|
if (!ok) {
|
||||||
|
setError(rule.message || `${label || currentName} 校验不通过`)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. pattern 正则
|
||||||
|
if (rule.pattern && typeof value === 'string') {
|
||||||
|
if (!rule.pattern.test(value)) {
|
||||||
|
setError(rule.message || `${label || currentName} 格式不正确`)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. len
|
||||||
|
if (rule.len !== undefined) {
|
||||||
|
if (
|
||||||
|
(typeof value === 'string' && value.length !== rule.len)
|
||||||
|
|| (typeof value === 'number' && value !== rule.len)
|
||||||
|
|| (Array.isArray(value) && value.length !== rule.len)
|
||||||
|
) {
|
||||||
|
setError(rule.message || `${label || currentName} 长度/值必须为 ${rule.len}`)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. min
|
||||||
|
if (rule.min !== undefined) {
|
||||||
|
if (
|
||||||
|
(typeof value === 'string' && value.length < rule.min)
|
||||||
|
|| (typeof value === 'number' && value < rule.min)
|
||||||
|
|| (Array.isArray(value) && value.length < rule.min)
|
||||||
|
) {
|
||||||
|
setError(rule.message || `${label || currentName} 最小值/长度为 ${rule.min}`)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 6. max
|
||||||
|
if (rule.max !== undefined) {
|
||||||
|
if (
|
||||||
|
(typeof value === 'string' && value.length > rule.max)
|
||||||
|
|| (typeof value === 'number' && value > rule.max)
|
||||||
|
|| (Array.isArray(value) && value.length > rule.max)
|
||||||
|
) {
|
||||||
|
setError(rule.message || `${label || currentName} 最大值/长度为 ${rule.max}`)
|
||||||
|
return false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -55,9 +131,12 @@ function FormItem<T extends Record<string, any>>({
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
registerValidator(String(name), validator)
|
context.registerValidator(currentName, validateFn)
|
||||||
return () => unregisterValidator(String(name))
|
|
||||||
}, [name])
|
return () => {
|
||||||
|
context.unregisterValidator(currentName)
|
||||||
|
}
|
||||||
|
}, [context, name, label, rules, isRequired])
|
||||||
|
|
||||||
const handleLayout = (event: any) => {
|
const handleLayout = (event: any) => {
|
||||||
const { y } = event.nativeEvent.layout
|
const { y } = event.nativeEvent.layout
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import { createAlova } from 'alova'
|
||||||
|
import { createServerTokenAuthentication } from 'alova/client'
|
||||||
|
import adapterFetch from 'alova/fetch'
|
||||||
|
const { onAuthRequired, onResponseRefreshToken } = createServerTokenAuthentication({
|
||||||
|
// ...
|
||||||
|
})
|
||||||
|
|
||||||
|
const alovaInstance = createAlova({
|
||||||
|
baseURL: 'http://192.168.1.136:8085/app',
|
||||||
|
requestAdapter: adapterFetch(),
|
||||||
|
// ...
|
||||||
|
beforeRequest: onAuthRequired((method) => {
|
||||||
|
// ...原请求前拦截器
|
||||||
|
}),
|
||||||
|
responded: onResponseRefreshToken((response, method) => {
|
||||||
|
// ...原响应成功拦截器
|
||||||
|
return response.json()
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
|
||||||
|
export default alovaInstance
|
||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"base-go-v2/internal/router"
|
"base-go-v2/internal/router"
|
||||||
"base-go-v2/internal/utils/mapx"
|
"base-go-v2/internal/utils/mapx"
|
||||||
"base-go-v2/internal/utils/uid"
|
"base-go-v2/internal/utils/uid"
|
||||||
|
"fmt"
|
||||||
)
|
)
|
||||||
|
|
||||||
func InitAuthRouter(r *router.Router) {
|
func InitAuthRouter(r *router.Router) {
|
||||||
@@ -14,18 +15,29 @@ func InitAuthRouter(r *router.Router) {
|
|||||||
group.POST("/register/default", registerDefault)
|
group.POST("/register/default", registerDefault)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func RefreshToken(c *router.Context) error {
|
||||||
|
bodyData, err := c.GetBodyWithRequired("accessToken", "refreshToken")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
accessToken := bodyData.GetString("accessToken")
|
||||||
|
refreshToken := bodyData.GetString("refreshToken")
|
||||||
|
}
|
||||||
|
|
||||||
// 默认登录
|
// 默认登录
|
||||||
func loginDefault(c *router.Context) error {
|
func loginDefault(c *router.Context) error {
|
||||||
|
fmt.Println("loginDefault 被调用")
|
||||||
|
|
||||||
return response.Success(c).Send()
|
return response.Success(c).Send()
|
||||||
}
|
}
|
||||||
|
|
||||||
func registerDefault(c *router.Context) error {
|
func registerDefault(c *router.Context) error {
|
||||||
params, err := c.GetBodyWithRequired("account", "password")
|
bodyData, err := c.GetBodyWithRequired("account", "password")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
account := params.GetString("account")
|
account := bodyData.GetString("account")
|
||||||
password := params.GetString("password")
|
password := bodyData.GetString("password")
|
||||||
|
|
||||||
// 构造查询条件,检查账号是否已存在
|
// 构造查询条件,检查账号是否已存在
|
||||||
accountQuery := mapx.New().SetKV("account", account)
|
accountQuery := mapx.New().SetKV("account", account)
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"base-go-v2/internal/api/auth"
|
||||||
"base-go-v2/internal/api/middleware"
|
"base-go-v2/internal/api/middleware"
|
||||||
"base-go-v2/internal/config"
|
"base-go-v2/internal/config"
|
||||||
response "base-go-v2/internal/reponse"
|
|
||||||
"base-go-v2/internal/router"
|
"base-go-v2/internal/router"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -15,9 +15,6 @@ func InitAppRouter() *router.Router {
|
|||||||
r.Use(middleware.LoggerMiddleware())
|
r.Use(middleware.LoggerMiddleware())
|
||||||
r.Use(middleware.ErrorMiddleware())
|
r.Use(middleware.ErrorMiddleware())
|
||||||
|
|
||||||
r.GET("/test", func(c *router.Context) error {
|
auth.InitAuthRouter(r)
|
||||||
return response.Success(c).Send()
|
|
||||||
})
|
|
||||||
|
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
Whitespace-only changes.
Whitespace-only changes.
Whitespace-only changes.
Reference in new issue
Block a user