Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 33 additions & 1 deletion docs/docs.go
Original file line number Diff line number Diff line change
Expand Up @@ -1752,6 +1752,24 @@ const docTemplate = `{
"schema": {
"$ref": "#/definitions/util.ResponseAny"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/util.ResponseAny"
}
},
"401": {
"description": "Unauthorized",
"schema": {
"$ref": "#/definitions/util.ResponseAny"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/util.ResponseAny"
}
}
}
}
Expand Down Expand Up @@ -2620,9 +2638,19 @@ const docTemplate = `{
"pay_key"
],
"properties": {
"current_pay_key": {
"description": "CurrentPayKey 当前安全密码;已设置安全密码的用户修改时必须提供",
"type": "string",
"maxLength": 6,
"minLength": 6,
"example": "123456"
},
"pay_key": {
"description": "PayKey 新安全密码,必须为6位数字",
"type": "string",
"maxLength": 6
"maxLength": 6,
"minLength": 6,
"example": "654321"
}
}
},
Expand Down Expand Up @@ -2697,6 +2725,10 @@ const docTemplate = `{
"type": "object",
"properties": {
"data": {},
"error_code": {
"type": "string",
"example": ""
},
"error_msg": {
"type": "string",
"example": ""
Expand Down
34 changes: 33 additions & 1 deletion docs/swagger.json
Original file line number Diff line number Diff line change
Expand Up @@ -1743,6 +1743,24 @@
"schema": {
"$ref": "#/definitions/util.ResponseAny"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/util.ResponseAny"
}
},
"401": {
"description": "Unauthorized",
"schema": {
"$ref": "#/definitions/util.ResponseAny"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/util.ResponseAny"
}
}
}
}
Expand Down Expand Up @@ -2611,9 +2629,19 @@
"pay_key"
],
"properties": {
"current_pay_key": {
"description": "CurrentPayKey 当前安全密码;已设置安全密码的用户修改时必须提供",
"type": "string",
"maxLength": 6,
"minLength": 6,
"example": "123456"
},
"pay_key": {
"description": "PayKey 新安全密码,必须为6位数字",
"type": "string",
"maxLength": 6
"maxLength": 6,
"minLength": 6,
"example": "654321"
}
}
},
Expand Down Expand Up @@ -2688,6 +2716,10 @@
"type": "object",
"properties": {
"data": {},
"error_code": {
"type": "string",
"example": ""
},
"error_msg": {
"type": "string",
"example": ""
Expand Down
24 changes: 24 additions & 0 deletions docs/swagger.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -516,8 +516,17 @@ definitions:
type: object
user.UpdatePayKeyRequest:
properties:
current_pay_key:
description: CurrentPayKey 当前安全密码;已设置安全密码的用户修改时必须提供
example: "123456"
maxLength: 6
minLength: 6
type: string
pay_key:
description: PayKey 新安全密码,必须为6位数字
example: "654321"
maxLength: 6
minLength: 6
type: string
required:
- pay_key
Expand Down Expand Up @@ -570,6 +579,9 @@ definitions:
util.ResponseAny:
properties:
data: {}
error_code:
example: ""
type: string
error_msg:
example: ""
type: string
Expand Down Expand Up @@ -1679,6 +1691,18 @@ paths:
description: OK
schema:
$ref: '#/definitions/util.ResponseAny'
"400":
description: Bad Request
schema:
$ref: '#/definitions/util.ResponseAny'
"401":
description: Unauthorized
schema:
$ref: '#/definitions/util.ResponseAny'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/util.ResponseAny'
tags:
- user
/f/{id}:
Expand Down
85 changes: 69 additions & 16 deletions frontend/components/common/settings/security.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,34 @@ import { Breadcrumb, BreadcrumbItem, BreadcrumbLink, BreadcrumbList, BreadcrumbP
import { Button } from "@/components/ui/button"
import { InputOTP, InputOTPGroup, InputOTPSlot } from "@/components/ui/input-otp"
import { Spinner } from "@/components/ui/spinner"
import { UserService } from "@/lib/services/user"
import { useUser } from "@/contexts/user-context"

export function SecurityMain() {
const router = useRouter()
const [setupStep, setSetupStep] = React.useState<'password' | 'confirm'>('password')
const { user, updatePayKey } = useUser()
const hasPayKey = user?.is_pay_key ?? true
const [setupStep, setSetupStep] = React.useState<'current' | 'password' | 'confirm'>('current')
const [currentPayKey, setCurrentPayKey] = React.useState("")
const [payKey, setPayKey] = React.useState("")
const [confirmPayKey, setConfirmPayKey] = React.useState("")
const [isSubmitting, setIsSubmitting] = React.useState(false)

const isCurrentPayKeyValid = currentPayKey.length === 6 && /^\d{6}$/.test(currentPayKey)
const isPayKeyValid = payKey.length === 6 && /^\d{6}$/.test(payKey)
const isConfirmValid = confirmPayKey.length === 6 && /^\d{6}$/.test(confirmPayKey)
const passwordsMatch = payKey === confirmPayKey

React.useEffect(() => {
if (user && !user.is_pay_key) {
setSetupStep('password')
}
}, [user])

/* 原安全密码输入 */
const handleCurrentPayKeyChange = (value: string) => {
setCurrentPayKey(value.replaceAll(/\D/g, ''))
}

/* 安全密码输入 */
const handlePayKeyChange = (value: string) => {
const numericValue = value.replaceAll(/\D/g, '')
Expand All @@ -39,7 +54,13 @@ export function SecurityMain() {
const handlePayKeySubmit = async (e: React.FormEvent) => {
e.preventDefault()

if (setupStep === 'password') {
if (setupStep === 'current') {
if (!isCurrentPayKeyValid) {
toast.error("原安全密码必须为6位数字")
return
}
setSetupStep('password')
} else if (setupStep === 'password') {
if (!isPayKeyValid) {
toast.error("安全密码必须为6位数字")
return
Expand All @@ -60,7 +81,10 @@ export function SecurityMain() {

setIsSubmitting(true)
try {
await UserService.updatePayKey(payKey)
await updatePayKey(
payKey,
hasPayKey ? currentPayKey : undefined,
)
toast.success("修改成功", {
description: "您的安全密码已成功更新",
})
Expand All @@ -71,7 +95,9 @@ export function SecurityMain() {
toast.error("修改失败", {
description: error instanceof Error ? error.message : "更新密码时发生错误,请稍后重试",
})
setSetupStep('password')
setSetupStep(hasPayKey ? 'current' : 'password')
setCurrentPayKey("")
setPayKey("")
setConfirmPayKey("")
} finally {
setIsSubmitting(false)
Expand Down Expand Up @@ -121,18 +147,38 @@ export function SecurityMain() {
>
<div className="flex flex-col items-center gap-2 mb-4">
<h3 className="text-xl font-bold tracking-tight text-center">
{setupStep === 'password' ? '设置新密码' : '确认新密码'}
{setupStep === 'current'
? '验证原密码'
: setupStep === 'password'
? '设置新密码'
: '确认新密码'}
</h3>
<p className="text-sm text-muted-foreground text-center max-w-[320px] mx-auto">
{setupStep === 'password'
? '请输入新的6位数字安全密码'
: '请再次输入密码进行确认'}
{setupStep === 'current'
? '请输入当前的6位数字安全密码'
: setupStep === 'password'
? '请输入新的6位数字安全密码'
: '请再次输入密码进行确认'}
</p>
</div>

<form onSubmit={handlePayKeySubmit} className="space-y-6">
<div className="flex justify-center">
{setupStep === 'password' ? (
{setupStep === 'current' ? (
<InputOTP
maxLength={6}
value={currentPayKey}
onChange={handleCurrentPayKeyChange}
disabled={isSubmitting}
autoFocus
>
<InputOTPGroup className="gap-2">
{[0, 1, 2, 3, 4, 5].map((i) => (
<InputOTPSlot key={i} index={i} className="w-10 h-10 sm:w-11 sm:h-11 border-input transition-all ring-offset-background focus-within:ring-2 focus-within:ring-ring focus-within:ring-offset-2" />
))}
</InputOTPGroup>
</InputOTP>
) : setupStep === 'password' ? (
<InputOTP
maxLength={6}
value={payKey}
Expand Down Expand Up @@ -175,13 +221,18 @@ export function SecurityMain() {
</div>

<div className="flex gap-4 sm:mx-8">
{setupStep === 'confirm' && (
{setupStep !== 'current' && hasPayKey && (
<Button
type="button"
variant="secondary"
onClick={() => {
setSetupStep('password')
setConfirmPayKey('')
if (setupStep === 'confirm') {
setSetupStep('password')
setConfirmPayKey('')
} else {
setSetupStep('current')
setPayKey('')
}
}}
className="flex-1 h-10 rounded-full tracking-wide text-sm font-bold transition-all active:scale-95"
>
Expand All @@ -190,15 +241,17 @@ export function SecurityMain() {
)}
<Button
type="submit"
className={`flex-1 h-10 rounded-full tracking-wide bg-primary hover:bg-primary/90 text-primary-foreground text-sm font-bold shadow-lg shadow-primary/20 hover:shadow-primary/30 transition-all active:scale-95 ${setupStep === 'password' ? 'w-full' : ''}`}
className={`flex-1 h-10 rounded-full tracking-wide bg-primary hover:bg-primary/90 text-primary-foreground text-sm font-bold shadow-lg shadow-primary/20 hover:shadow-primary/30 transition-all active:scale-95 ${setupStep === 'current' || (!hasPayKey && setupStep === 'password') ? 'w-full' : ''}`}
disabled={
setupStep === 'password'
setupStep === 'current'
? !isCurrentPayKeyValid
: setupStep === 'password'
? !isPayKeyValid
: isSubmitting || !isConfirmValid
}
>
{isSubmitting && <Spinner className="mr-2" />}
{setupStep === 'password' ? '继续' : '保存修改'}
{setupStep === 'confirm' ? '保存修改' : '继续'}
</Button>
</div>
</form>
Expand Down
6 changes: 3 additions & 3 deletions frontend/contexts/user-context.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ interface UserState {
/** 用户上下文接口 */
interface UserContextValue extends UserState {
refetch: () => Promise<void>
updatePayKey: (payKey: string) => Promise<void>
updatePayKey: (payKey: string, currentPayKey?: string) => Promise<void>
getTrustLevelLabel: (trustLevel: TrustLevel) => string
getPayLevelLabel: (payLevel: PayLevel) => string
logout: () => Promise<void>
Expand Down Expand Up @@ -101,8 +101,8 @@ export function UserProvider({ children }: { children: ReactNode }) {
}, [fetchUser])

/** 更新支付密码 */
const updatePayKey = useCallback(async (payKey: string) => {
await services.user.updatePayKey(payKey)
const updatePayKey = useCallback(async (payKey: string, currentPayKey?: string) => {
await services.user.updatePayKey(payKey, currentPayKey)
await fetchUser()
}, [fetchUser])

Expand Down
1 change: 1 addition & 0 deletions frontend/lib/services/core/api-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,7 @@ apiClient.interceptors.response.use(
new ValidationError(
error.response.data?.error_msg || '请求参数验证失败',
error.response.data?.details,
error.response.data?.error_code || 'VALIDATION_ERROR',
),
);
}
Expand Down
8 changes: 6 additions & 2 deletions frontend/lib/services/core/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,8 +88,12 @@ export class ServerError extends ApiErrorBase {
* 验证错误 (400)
*/
export class ValidationError extends ApiErrorBase {
constructor(message = '请求参数验证失败', details?: unknown) {
super(message, 'VALIDATION_ERROR', 400, details);
constructor(
message = '请求参数验证失败',
details?: unknown,
code = 'VALIDATION_ERROR',
) {
super(message, code, 400, details);
this.name = 'ValidationError';
Object.setPrototypeOf(this, ValidationError.prototype);
}
Expand Down
2 changes: 1 addition & 1 deletion frontend/lib/services/user/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
* import { UserService } from '@/lib/services';
*
* // 更新支付密钥
* await UserService.updatePayKey('123456');
* await UserService.updatePayKey('654321', '123456');
* ```
*/

Expand Down
3 changes: 2 additions & 1 deletion frontend/lib/services/user/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
* 更新支付密钥请求
*/
export interface UpdatePayKeyRequest {
/** 当前支付密钥(已设置支付密钥时必填) */
current_pay_key?: string;
/** 新的支付密钥(6位数字) */
pay_key: string;
}

Loading