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
12 changes: 12 additions & 0 deletions docs/docs.go
Original file line number Diff line number Diff line change
Expand Up @@ -1565,6 +1565,18 @@ const docTemplate = `{
"schema": {
"$ref": "#/definitions/util.ResponseAny"
}
},
"429": {
"description": "连续输入错误红包ID次数过多",
"schema": {
"$ref": "#/definitions/util.ResponseAny"
},
"headers": {
"Retry-After": {
"type": "integer",
"description": "再次尝试前需等待的秒数"
}
}
}
}
}
Expand Down
12 changes: 12 additions & 0 deletions docs/swagger.json
Original file line number Diff line number Diff line change
Expand Up @@ -1556,6 +1556,18 @@
"schema": {
"$ref": "#/definitions/util.ResponseAny"
}
},
"429": {
"description": "连续输入错误红包ID次数过多",
"schema": {
"$ref": "#/definitions/util.ResponseAny"
},
"headers": {
"Retry-After": {
"type": "integer",
"description": "再次尝试前需等待的秒数"
}
}
}
}
}
Expand Down
8 changes: 8 additions & 0 deletions docs/swagger.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1578,6 +1578,14 @@ paths:
description: OK
schema:
$ref: '#/definitions/util.ResponseAny'
"429":
description: 连续输入错误红包ID次数过多
headers:
Retry-After:
description: 再次尝试前需等待的秒数
type: integer
schema:
$ref: '#/definitions/util.ResponseAny'
tags:
- redenvelope
/api/v1/redenvelope/covers:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ export function RedEnvelopeCard({
animate={status === "opening" ? {
scale: [1, 0.92, 0],
opacity: [1, 0]
} : {}}
} : { scale: 1, opacity: 1 }}
transition={status === "opening" ? {
duration: 0.55,
ease: "easeInOut"
Expand Down
70 changes: 70 additions & 0 deletions frontend/components/common/redenvelope/red-envelope-share.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
"use client"

import { useState } from "react"
import Image from "next/image"
import { motion, useReducedMotion } from "motion/react"
import { QRCodeSVG } from "qrcode.react"

export function RedEnvelopeShare({ link }: { link: string }) {
const [playing, setPlaying] = useState(false)
const [replay, setReplay] = useState(0)
const reduceMotion = useReducedMotion()

return (
<>
<div className="-mt-2 flex justify-center py-1 md:hidden">
<div className="w-1/2 rounded-xl bg-white p-1 shadow-sm ring-1 ring-black/5 sm:p-2">
<QRCodeSVG value={link} size={224} marginSize={4} level="M" title="红包领取二维码" className="h-auto w-full" />
</div>
</div>
<div
className="relative mx-auto -mt-2 hidden w-fit grid-cols-[176px_90px] items-center gap-3 py-1 md:grid"
onPointerEnter={(event) => {
if (event.pointerType === "mouse") setPlaying(true)
}}
onPointerLeave={(event) => {
if (event.pointerType === "mouse") setPlaying(false)
}}
onFocus={() => setPlaying(true)}
onBlur={(event) => {
if (!event.currentTarget.contains(event.relatedTarget)) setPlaying(false)
}}
>
<motion.button
type="button"
aria-expanded={playing}
initial={false}
animate={{ x: playing ? 0 : 51 }}
transition={reduceMotion ? { duration: 0 } : { type: "spring", stiffness: 180, damping: 24 }}
className="relative z-10 aspect-square w-full rounded-xl bg-white p-1 shadow-sm ring-1 ring-black/5 outline-none focus-visible:ring-2 focus-visible:ring-red-500 sm:p-2"
onClick={() => {
setPlaying(true)
setReplay(value => value + 1)
}}
>
<QRCodeSVG value={link} size={224} marginSize={4} level="M" className="h-auto w-full" />
</motion.button>

<motion.div
initial={false}
animate={{ opacity: playing ? 1 : 0 }}
transition={{ duration: reduceMotion ? 0 : 0.3, delay: playing && !reduceMotion ? 0.15 : 0 }}
aria-hidden={!playing}
className="pointer-events-none relative h-44 min-w-0"
>
{playing && (
<Image
key={replay}
src="/red-envelope-scan.svg"
alt=""
width={128}
height={252}
unoptimized
className="absolute inset-0 h-full w-full"
/>
)}
</motion.div>
</div>
</>
)
}
25 changes: 21 additions & 4 deletions frontend/components/common/trade/red-envelope.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import * as React from "react"
import { useState, useEffect } from "react"
import Image from "next/image"
import { toast } from "sonner"
import { Gift, Copy, Check, ExternalLink, Pencil, X, ImagePlus, History } from "lucide-react"
import { Gift, Copy, Check, Share2, Pencil, X, ImagePlus, History } from "lucide-react"
import type { UploadImageResponse } from "@/lib/services"
import { getFileUrl } from "@/lib/services/upload/upload.service"
import { Input } from "@/components/ui/input"
Expand All @@ -18,6 +18,7 @@ import { ListData } from "@/components/common/general/list-data"
import { ImageCropper } from "@/components/common/redenvelope/image-cropper"
import { motion } from "motion/react"
import { RedEnvelopeCard } from "@/components/common/redenvelope/red-envelope-card"
import { RedEnvelopeShare } from "@/components/common/redenvelope/red-envelope-share"
import { useUser } from "@/contexts/user-context"
import services from "@/lib/services"
import type { RedEnvelopeType, CreateRedEnvelopeRequest, PublicConfigResponse, RedEnvelope, RedEnvelopeListResponse } from "@/lib/services"
Expand Down Expand Up @@ -696,7 +697,7 @@ export function RedEnvelope({ onSuccess }: { onSuccess?: () => void }) {
</DialogDescription>
</DialogHeader>

<div className="py-2">
<div className="pt-2">
<div className="flex gap-2">
<Input
value={resultLink}
Expand All @@ -713,13 +714,29 @@ export function RedEnvelope({ onSuccess }: { onSuccess?: () => void }) {
<Button
variant="secondary"
size="sm"
onClick={() => window.open(resultLink, "_blank")}
Comment thread
small-lovely-cat marked this conversation as resolved.
aria-label="分享红包"
title="分享红包"
onClick={async () => {
const shareData = { title: "领取红包", url: resultLink }
if (!navigator.share || !navigator.canShare?.(shareData)) {
window.open(resultLink, "_blank", "noopener,noreferrer")
return
}
try {
await navigator.share(shareData)
} catch (error) {
if (error instanceof Error && error.name === "AbortError") return
toast.error("分享失败")
}
}}
>
<ExternalLink className="size-3" />
<Share2 className="size-3" />
</Button>
</div>
</div>

{isResultOpen && resultLink && <RedEnvelopeShare link={resultLink} />}

<div className="flex justify-end">
<Button onClick={() => setIsResultOpen(false)} className="h-8 text-xs">
完成
Expand Down
1 change: 1 addition & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
"next": "16.3.3",
"next-themes": "^0.4.6",
"pinyin-match": "^1.2.10",
"qrcode.react": "^4.2.0",
"radix-ui": "^1.4.3",
"react": "19.2.3",
"react-day-picker": "^9.11.1",
Expand Down
12 changes: 12 additions & 0 deletions frontend/pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

54 changes: 54 additions & 0 deletions frontend/public/red-envelope-scan.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 2 additions & 2 deletions internal/apps/dashboard/routers.go
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ func GetUserBalanceStats(c *gin.Context) {
ctx := c.Request.Context()

var cachedStats UserBalanceStatsResponse
if err := db.GetJSON(ctx, dashboardCacheKeyPrefix, &cachedStats); err == nil {
if err := db.GetJSON(ctx, db.Redis, dashboardCacheKeyPrefix, &cachedStats); err == nil {
c.JSON(http.StatusOK, util.OK(cachedStats))
return
}
Expand Down Expand Up @@ -190,7 +190,7 @@ func GetUserBalanceStats(c *gin.Context) {
}

if cacheTTL, errGet := model.GetIntByKey(ctx, model.ConfigKeyUserBalanceStatsCacheTTL); errGet == nil && cacheTTL > 0 {
_ = db.SetJSON(ctx, dashboardCacheKeyPrefix, response, time.Duration(cacheTTL)*time.Second)
_ = db.SetJSON(ctx, db.Redis, dashboardCacheKeyPrefix, response, time.Duration(cacheTTL)*time.Second)
}

c.JSON(http.StatusOK, util.OK(response))
Expand Down
6 changes: 3 additions & 3 deletions internal/apps/oauth/risk.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,9 +77,9 @@ func checkOpenAPIUserRisk(ctx context.Context, userID uint64) (*openAPIUserRiskR

cacheKey := fmt.Sprintf(openAPIRiskCacheKeyFormat, userID)
var cached openAPIUserRiskResponse
if err := db.GetJSON(ctx, cacheKey, &cached); err == nil {
if err := db.GetJSON(ctx, db.Redis, cacheKey, &cached); err == nil {
return &cached, true
} else if err != nil && !errors.Is(err, redis.Nil) {
} else if !errors.Is(err, redis.Nil) {
logger.ErrorF(ctx, "[OpenAPIRisk] read cache failed, skip risk check: %v", err)
return nil, false
}
Expand All @@ -90,7 +90,7 @@ func checkOpenAPIUserRisk(ctx context.Context, userID uint64) (*openAPIUserRiskR
return nil, false
}

if err := db.SetJSON(ctx, cacheKey, risk, openAPIRiskCacheTTL()); err != nil {
if err := db.SetJSON(ctx, db.Redis, cacheKey, risk, openAPIRiskCacheTTL()); err != nil {
logger.ErrorF(ctx, "[OpenAPIRisk] write cache failed, skip risk check: %v", err)
return nil, false
}
Expand Down
Loading
Loading