Compare commits

..

6 Commits

Author SHA1 Message Date
shanshanzhong147 511ce4ff65 Merge branch 'internal' into main
PR Check / Lint, Check, Test and Build (push) Has been cancelled
Build and Release / Build (push) Has been cancelled
2026-06-23 08:49:48 -07:00
shanshanzhong147 64fe23f877 fix: show user subscription speed override
PR Check / Lint, Check, Test and Build (push) Has been cancelled
Build and Release / Build (push) Has been cancelled
2026-06-11 02:39:16 -07:00
shanshanzhong147 09c5216f60 修复(#14): 调整 USDT 提现方式枚举口径
PR Check / Lint, Check, Test and Build (push) Has been cancelled
Build and Release / Build (push) Has been cancelled
2026-06-09 06:37:15 -07:00
shanshanzhong147 4d84095af2 修复(#14): 支持 USDT 提现方式展示 2026-06-09 06:37:14 -07:00
shanshanzhong147 d66b664198 修复(#122): 补齐促销价档位提交
PR Check / Lint, Check, Test and Build (push) Has been cancelled
Build and Release / Build (push) Has been cancelled
2026-05-29 02:36:30 -07:00
shanshanzhong147 a7d89acc7f 新功能(#113): 发布促销管理后台页面到main
PR Check / Lint, Check, Test and Build (push) Has been cancelled
Build and Release / Build (push) Has been cancelled
2026-05-29 00:40:33 -07:00
6 changed files with 57 additions and 27 deletions
@@ -54,6 +54,10 @@ function toUserSubscribePayload(values: SubscriptionFormValues) {
}; };
} }
function resolvePlanSpeed(row: API.UserSubscribe) {
return row.plan_speed_limit ?? row.subscribe?.speed_limit ?? 0;
}
export default function UserSubscription({ userId }: { userId: number }) { export default function UserSubscription({ userId }: { userId: number }) {
const { t } = useTranslation("user"); const { t } = useTranslation("user");
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
@@ -315,10 +319,11 @@ export default function UserSubscription({ userId }: { userId: number }) {
cell: ({ row }) => { cell: ({ row }) => {
const userSpeed = row.original.speed_limit ?? 0; const userSpeed = row.original.speed_limit ?? 0;
const effectiveSpeed = row.original.effective_speed ?? 0; const effectiveSpeed = row.original.effective_speed ?? 0;
const planSpeed = row.original.subscribe?.speed_limit ?? 0; const planSpeed = resolvePlanSpeed(row.original);
const speed = effectiveSpeed || userSpeed || planSpeed; const speed = effectiveSpeed || userSpeed || planSpeed;
const source = const source = row.original.is_throttled
userSpeed > 0 ? t("effectiveSpeed", "Effective Speed")
: userSpeed > 0
? t("userOverride", "User Override") ? t("userOverride", "User Override")
: t("subscriptionDefault", "Subscription Default"); : t("subscriptionDefault", "Subscription Default");
@@ -335,6 +340,16 @@ export default function UserSubscription({ userId }: { userId: number }) {
<span className="text-muted-foreground text-xs"> <span className="text-muted-foreground text-xs">
{source} {source}
</span> </span>
<span className="text-muted-foreground text-xs">
{t("userOverride", "User Override")}:{" "}
<span className="inline-block">
<Display type="trafficSpeed" value={userSpeed} />
</span>{" "}
/ {t("subscriptionDefault", "Subscription Default")}:{" "}
<span className="inline-block">
<Display type="trafficSpeed" value={planSpeed} />
</span>
</span>
</div> </div>
); );
}, },
@@ -87,11 +87,12 @@ function SpeedLimitCard({ subscriptionId }: { subscriptionId: number }) {
if (detail.status !== 1) return null; if (detail.status !== 1) return null;
const baseSpeed = detail.subscribe?.speed_limit ?? 0; const baseSpeed =
detail.plan_speed_limit ?? detail.subscribe?.speed_limit ?? 0;
const userSpeed = detail.speed_limit ?? 0; const userSpeed = detail.speed_limit ?? 0;
const effectiveSpeed = detail.effective_speed ?? 0; const effectiveSpeed = detail.effective_speed ?? 0;
const isThrottled = detail.is_throttled; const isThrottled = detail.is_throttled;
const rules = detail.traffic_limit || []; const rules = detail.user_traffic_limit || detail.traffic_limit || [];
const hasLimitConfig = const hasLimitConfig =
baseSpeed > 0 || userSpeed > 0 || effectiveSpeed > 0 || rules.length > 0; baseSpeed > 0 || userSpeed > 0 || effectiveSpeed > 0 || rules.length > 0;
@@ -40,6 +40,12 @@ interface Props {
type SubscriptionTrafficLimitRule = API.UserSubscribeTrafficLimitRule; type SubscriptionTrafficLimitRule = API.UserSubscribeTrafficLimitRule;
function getUserTrafficLimitRules(
data?: Pick<API.UserSubscribe, "traffic_limit" | "user_traffic_limit">
) {
return data?.user_traffic_limit || data?.traffic_limit || [];
}
const defaultLimitRule: SubscriptionTrafficLimitRule = { const defaultLimitRule: SubscriptionTrafficLimitRule = {
stat_type: "day", stat_type: "day",
stat_value: 1, stat_value: 1,
@@ -98,7 +104,7 @@ export function SubscriptionForm({
traffic: initialData?.traffic || 0, traffic: initialData?.traffic || 0,
speed_limit: initialData?.speed_limit || 0, speed_limit: initialData?.speed_limit || 0,
device_limit: initialData?.device_limit || 0, device_limit: initialData?.device_limit || 0,
traffic_limit: initialData?.traffic_limit || [], traffic_limit: getUserTrafficLimitRules(initialData),
upload: initialData?.upload || 0, upload: initialData?.upload || 0,
download: initialData?.download || 0, download: initialData?.download || 0,
expired_at: initialData?.expire_time || 0, expired_at: initialData?.expire_time || 0,
+2 -2
View File
@@ -44,7 +44,7 @@ const WITHDRAWAL_METHOD_MAP: Record<number, string> = {
0: "其他", 0: "其他",
1: "支付宝", 1: "支付宝",
2: "微信", 2: "微信",
3: "银行卡", 3: "USDT(TRC20)",
}; };
const rejectSchema = z.object({ const rejectSchema = z.object({
@@ -143,7 +143,7 @@ export default function WithdrawalPage() {
const methodOptions = [ const methodOptions = [
{ value: "1", label: "支付宝" }, { value: "1", label: "支付宝" },
{ value: "2", label: "微信" }, { value: "2", label: "微信" },
{ value: "3", label: "银行卡" }, { value: "3", label: "USDT(TRC20)" },
{ value: "0", label: "其他" }, { value: "0", label: "其他" },
]; ];
+18 -14
View File
@@ -63,7 +63,7 @@ import { useGlobalStore } from "@/stores/global";
const WITHDRAWAL_METHODS = [ const WITHDRAWAL_METHODS = [
{ value: 1, label: "支付宝" }, { value: 1, label: "支付宝" },
{ value: 2, label: "微信" }, { value: 2, label: "微信" },
{ value: 3, label: "银行卡" }, { value: 3, label: "USDT(TRC20)" },
{ value: 0, label: "其他" }, { value: 0, label: "其他" },
] as const; ] as const;
@@ -111,13 +111,21 @@ function QrCodeView({ url }: { url: string }) {
<img <img
alt="收款码" alt="收款码"
className="h-16 w-16 cursor-zoom-in object-cover" className="h-16 w-16 cursor-zoom-in object-cover"
height={64}
src={url} src={url}
width={64}
/> />
</button> </button>
{zoomOpen && ( {zoomOpen && (
<Dialog onOpenChange={setZoomOpen} open={zoomOpen}> <Dialog onOpenChange={setZoomOpen} open={zoomOpen}>
<DialogContent className="max-w-sm"> <DialogContent className="max-w-sm">
<img alt="收款码" className="w-full" src={url} /> <img
alt="收款码"
className="w-full"
height={320}
src={url}
width={320}
/>
</DialogContent> </DialogContent>
</Dialog> </Dialog>
)} )}
@@ -153,19 +161,17 @@ const withdrawalFormSchema = z
if (!data.account) { if (!data.account) {
ctx.addIssue({ ctx.addIssue({
code: z.ZodIssueCode.custom, code: z.ZodIssueCode.custom,
message: "银行卡收款账号为必填", message: "USDT(TRC20) 地址为必填",
path: ["account"], path: ["account"],
}); });
} }
} else if (data.method === 0) { } else if (data.method === 0 && !(data.account || data.content)) {
if (!data.account && !data.content) {
ctx.addIssue({ ctx.addIssue({
code: z.ZodIssueCode.custom, code: z.ZodIssueCode.custom,
message: "收款账号和备注至少填一项", message: "收款账号和备注至少填一项",
path: ["account"], path: ["account"],
}); });
} }
}
}); });
type WithdrawalFormValues = z.infer<typeof withdrawalFormSchema>; type WithdrawalFormValues = z.infer<typeof withdrawalFormSchema>;
@@ -421,9 +427,7 @@ export default function Affiliate() {
<FormItem> <FormItem>
<FormLabel> <FormLabel>
<span className="ml-1 text-destructive"> <span className="ml-1 text-destructive">*</span>
*
</span>
</FormLabel> </FormLabel>
{watchedQrCodeUrl ? ( {watchedQrCodeUrl ? (
<div className="relative inline-block"> <div className="relative inline-block">
@@ -446,7 +450,9 @@ export default function Affiliate() {
returnType="file" returnType="file"
> >
<div className="flex h-24 w-24 flex-col items-center justify-center rounded border-2 border-dashed text-muted-foreground text-xs"> <div className="flex h-24 w-24 flex-col items-center justify-center rounded border-2 border-dashed text-muted-foreground text-xs">
{qrUploading ? "上传中..." : "点击上传\n收款码"} {qrUploading
? "上传中..."
: "点击上传\n收款码"}
</div> </div>
</UploadImage> </UploadImage>
)} )}
@@ -474,7 +480,7 @@ export default function Affiliate() {
<Input <Input
placeholder={ placeholder={
watchedMethod === 3 watchedMethod === 3
? "请输入银行卡号或账户信息" ? "请输入 USDT(TRC20) 收款地址"
: "请输入收款账号(与备注至少填一项)" : "请输入收款账号(与备注至少填一项)"
} }
{...field} {...field}
@@ -519,9 +525,7 @@ export default function Affiliate() {
</Button> </Button>
<Button <Button
disabled={ disabled={
submitting || submitting || qrUploading || availableCommission <= 0
qrUploading ||
availableCommission <= 0
} }
form="withdrawal-form" form="withdrawal-form"
type="submit" type="submit"
+4
View File
@@ -2664,11 +2664,13 @@ declare namespace API {
download: number; download: number;
upload: number; upload: number;
speed_limit?: number; speed_limit?: number;
plan_speed_limit?: number;
device_limit?: number; device_limit?: number;
effective_speed?: number; effective_speed?: number;
is_throttled?: boolean; is_throttled?: boolean;
throttle_rule?: string; throttle_rule?: string;
traffic_limit?: UserSubscribeTrafficLimitRule[]; traffic_limit?: UserSubscribeTrafficLimitRule[];
user_traffic_limit?: UserSubscribeTrafficLimitRule[];
token: string; token: string;
status: number; status: number;
short: string; short: string;
@@ -2699,8 +2701,10 @@ declare namespace API {
download: number; download: number;
upload: number; upload: number;
speed_limit?: number; speed_limit?: number;
plan_speed_limit?: number;
device_limit?: number; device_limit?: number;
traffic_limit?: UserSubscribeTrafficLimitRule[]; traffic_limit?: UserSubscribeTrafficLimitRule[];
user_traffic_limit?: UserSubscribeTrafficLimitRule[];
token: string; token: string;
status: number; status: number;
effective_speed: number; effective_speed: number;