From 760937aa514ead232316ba141f724eb6ef894b53 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E5=8C=97=E6=9E=B3?=
<7854742+wang_rumeng@user.noreply.gitee.com>
Date: Wed, 24 Sep 2025 21:34:21 +0800
Subject: [PATCH 1/8] =?UTF-8?q?=E8=B0=83=E6=95=B4=20H5MediaViewer=20?=
=?UTF-8?q?=E7=BB=84=E4=BB=B6=E7=9A=84=E7=BC=A9=E7=95=A5=E5=9B=BE=E5=AE=BD?=
=?UTF-8?q?=E5=BA=A6=EF=BC=8C=E5=B9=B6=E5=B0=86=E8=BF=9B=E5=BA=A6=E6=8F=90?=
=?UTF-8?q?=E7=A4=BA=E4=B8=AD=E7=9A=84=E4=B8=AD=E6=96=87=E2=80=9C=E5=8D=B3?=
=?UTF-8?q?=E5=B0=86=E5=AE=8C=E6=88=90=E2=80=9D=E6=9B=B4=E6=94=B9=E4=B8=BA?=
=?UTF-8?q?=E8=8B=B1=E6=96=87=E2=80=9Ccompleted=E2=80=9D=E3=80=82?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
components/pages/work-flow/H5MediaViewer.tsx | 2 +-
components/ui/h5-progress-toast.tsx | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/components/pages/work-flow/H5MediaViewer.tsx b/components/pages/work-flow/H5MediaViewer.tsx
index 6ddcf36..e000ea1 100644
--- a/components/pages/work-flow/H5MediaViewer.tsx
+++ b/components/pages/work-flow/H5MediaViewer.tsx
@@ -377,7 +377,7 @@ export function H5MediaViewer({
{isFinalBarOpen && (
-
+
{pct}%
- {pct >= 100 ? '即将完成' : ''}
+ {pct >= 100 ? 'completed' : ''}
From 3a2eaa1ecc0a7d9f35b38bb0d9e09edc60f40d54 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E5=8C=97=E6=9E=B3?=
<7854742+wang_rumeng@user.noreply.gitee.com>
Date: Thu, 25 Sep 2025 11:29:53 +0800
Subject: [PATCH 2/8] =?UTF-8?q?=E9=80=82=E9=85=8D=E6=89=8B=E6=9C=BA?=
=?UTF-8?q?=E7=AB=AF=E5=BA=95=E9=83=A8=E7=8A=B6=E6=80=81?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
app/globals.css | 54 ++++++++++++++++++++--
app/layout.tsx | 2 +-
components/layout/dashboard-layout.tsx | 44 ++++++++++++++++--
hooks/useDeviceType.ts | 38 ++++++++-------
hooks/useSafeArea.ts | 64 ++++++++++++++++++++++++++
tailwind.config.js | 14 ++++++
6 files changed, 189 insertions(+), 27 deletions(-)
create mode 100644 hooks/useSafeArea.ts
diff --git a/app/globals.css b/app/globals.css
index f8f96bd..29fa569 100644
--- a/app/globals.css
+++ b/app/globals.css
@@ -50,7 +50,7 @@
*,
*:after,
*:before {
- box-sizing: border-box;
+ box-sizing: border-box;
}
:root {
@@ -93,10 +93,12 @@
--muted-foreground: 0 0% 63.9%;
--accent: 0 0% 14.9%;
--accent-foreground: 0 0% 98%;
-
+
/* 自定义渐变色变量 */
- --custom-blue: 186 100% 70%; /* rgb(106, 244, 249) */
- --custom-purple: 280 100% 62%; /* rgb(199, 59, 255) */
+ --custom-blue: 186 100% 70%;
+ /* rgb(106, 244, 249) */
+ --custom-purple: 280 100% 62%;
+ /* rgb(199, 59, 255) */
--custom-blue-rgb: 106, 244, 249;
--custom-purple-rgb: 199, 59, 255;
--destructive: 0 62.8% 30.6%;
@@ -162,6 +164,7 @@ body {
.hide-scrollbar::-webkit-scrollbar {
display: none !important;
}
+
*::-webkit-scrollbar {
display: none !important;
}
@@ -184,6 +187,7 @@ body {
border-radius: 8px;
padding: 8px;
}
+
.button-NxtqWZ:hover {
background-color: #2f3237 !important;
}
@@ -226,6 +230,7 @@ body {
height: 0;
pointer-events: none;
}
+
.ant-spin-nested-loading .ant-spin {
max-height: none !important;
}
@@ -236,6 +241,7 @@ body {
opacity: 0;
transform: translateY(-8px);
}
+
to {
opacity: 1;
transform: translateY(0);
@@ -256,9 +262,47 @@ body {
.ant-switch.ant-switch-checked:hover {
background: rgb(146 78 173) !important;
-}
+}
.language-dropdown li {
padding: unset !important;
margin: 0.25rem !important;
+}
+
+/* 安全区域变量定义 */
+:root {
+ --sat: env(safe-area-inset-top, 0px);
+ --sar: env(safe-area-inset-right, 0px);
+ --sab: env(safe-area-inset-bottom, 0px);
+ --sal: env(safe-area-inset-left, 0px);
+}
+
+/* 移动端适配:使用 dvh 动态视口高度 */
+@supports (height: 100dvh) {
+ body {
+ height: 100dvh;
+ }
+}
+
+/* 移动端安全区域处理 */
+@media (max-width: 768px) {
+ body {
+ /* 使用动态视口高度,考虑移动端浏览器地址栏 */
+ height: 100dvh;
+ height: calc(var(--vh, 1vh) * 100);
+ padding-bottom: env(safe-area-inset-bottom);
+ }
+}
+
+/* 针对移动端底部导航栏/状态栏的特殊处理 */
+@media (max-width: 768px) and (display-mode: browser) {
+ .mobile-safe-bottom {
+ padding-bottom: max(1rem, env(safe-area-inset-bottom));
+ margin-bottom: max(1rem, env(safe-area-inset-bottom));
+ }
+
+ .mobile-viewport-height {
+ height: 100dvh;
+ height: calc(var(--vh, 1vh) * 100);
+ }
}
\ No newline at end of file
diff --git a/app/layout.tsx b/app/layout.tsx
index db5e91e..118ff61 100644
--- a/app/layout.tsx
+++ b/app/layout.tsx
@@ -43,7 +43,7 @@ export default function RootLayout({
MovieFlow - AI Movie Studio
-
+
diff --git a/components/layout/dashboard-layout.tsx b/components/layout/dashboard-layout.tsx
index 6008f28..0d77dd4 100644
--- a/components/layout/dashboard-layout.tsx
+++ b/components/layout/dashboard-layout.tsx
@@ -1,6 +1,6 @@
"use client";
-import { useState } from 'react';
+import { useState, useEffect } from 'react';
import { Sidebar } from './sidebar';
import { TopBar } from './top-bar';
import { useDeviceType } from '@/hooks/useDeviceType';
@@ -10,9 +10,28 @@ interface DashboardLayoutProps {
}
export function DashboardLayout({ children }: DashboardLayoutProps) {
- const [sidebarCollapsed, setSidebarCollapsed] = useState(true); // 默认收起状态
+ const [sidebarCollapsed, setSidebarCollapsed] = useState(true);
const { deviceType, isMobile, isTablet, isDesktop } = useDeviceType();
+ // 处理移动端视口高度动态计算
+ useEffect(() => {
+ if (isMobile || isTablet) {
+ const setVH = () => {
+ const vh = window.innerHeight * 0.01;
+ document.documentElement.style.setProperty('--vh', `${vh}px`);
+ };
+
+ setVH();
+ window.addEventListener('resize', setVH);
+ window.addEventListener('orientationchange', setVH);
+
+ return () => {
+ window.removeEventListener('resize', setVH);
+ window.removeEventListener('orientationchange', setVH);
+ };
+ }
+ }, [isMobile, isTablet]);
+
// 根据设备类型设置布局样式
const getLayoutStyles = () => {
if (isMobile || isTablet) {
@@ -29,13 +48,28 @@ export function DashboardLayout({ children }: DashboardLayoutProps) {
};
};
+ // 获取移动端容器类名
+ const getMobileContainerClasses = () => {
+ if (isMobile || isTablet) {
+ return "mobile-viewport-height mobile-safe-bottom";
+ }
+ return "";
+ };
+
return (
-
+
{isDesktop &&
}
+ className={`top-[4rem] fixed right-0 bottom-0 px-4 ${getMobileContainerClasses()}`}
+ style={{
+ ...getLayoutStyles(),
+ // 移动端使用动态高度计算
+ height: (isMobile || isTablet)
+ ? 'calc(100dvh - 4rem)'
+ : 'calc(100vh - 4rem)'
+ }}
+ >
{children}
diff --git a/hooks/useDeviceType.ts b/hooks/useDeviceType.ts
index a46721f..30bb631 100644
--- a/hooks/useDeviceType.ts
+++ b/hooks/useDeviceType.ts
@@ -2,16 +2,16 @@ import { useState, useEffect } from 'react';
// 定义设备类型枚举
export enum DeviceType {
- MOBILE = 'mobile', // 手机
- TABLET = 'tablet', // 平板
- DESKTOP = 'desktop' // 桌面端
+ MOBILE = 'mobile',
+ TABLET = 'tablet',
+ DESKTOP = 'desktop'
}
// 定义屏幕断点
const BREAKPOINTS = {
- MOBILE: 480, // 0-480px 为手机
- TABLET: 1024, // 481-1024px 为平板
- DESKTOP: 1025 // 1025px 及以上为桌面端
+ MOBILE: 480,
+ TABLET: 1024,
+ DESKTOP: 1025
};
export function useDeviceType() {
@@ -22,35 +22,39 @@ export function useDeviceType() {
});
useEffect(() => {
- /**
- * 根据窗口宽度判断设备类型
- */
const getDeviceType = (width: number): DeviceType => {
if (width <= BREAKPOINTS.MOBILE) return DeviceType.MOBILE;
if (width <= BREAKPOINTS.TABLET) return DeviceType.TABLET;
return DeviceType.DESKTOP;
};
- /**
- * 处理窗口大小变化
- */
const handleResize = () => {
const width = window.innerWidth;
const height = window.innerHeight;
setWindowSize({ width, height });
setDeviceType(getDeviceType(width));
+
+ // 移动端动态视口高度处理
+ if (width <= BREAKPOINTS.TABLET) {
+ const vh = height * 0.01;
+ document.documentElement.style.setProperty('--vh', `${vh}px`);
+ }
};
- // 初始化设备类型
+ // 初始化
handleResize();
- // 添加窗口大小变化监听
+ // 添加事件监听
window.addEventListener('resize', handleResize);
+ window.addEventListener('orientationchange', () => {
+ // 延迟处理以确保获取正确的视口尺寸
+ setTimeout(handleResize, 100);
+ });
- // 清理监听器
return () => {
window.removeEventListener('resize', handleResize);
+ window.removeEventListener('orientationchange', handleResize);
};
}, []);
@@ -59,6 +63,8 @@ export function useDeviceType() {
windowSize,
isMobile: deviceType === DeviceType.MOBILE,
isTablet: deviceType === DeviceType.TABLET,
- isDesktop: deviceType === DeviceType.DESKTOP
+ isDesktop: deviceType === DeviceType.DESKTOP,
+ /** 是否为移动端设备(包括平板) */
+ isMobileDevice: deviceType === DeviceType.MOBILE || deviceType === DeviceType.TABLET
};
}
\ No newline at end of file
diff --git a/hooks/useSafeArea.ts b/hooks/useSafeArea.ts
new file mode 100644
index 0000000..42cdb71
--- /dev/null
+++ b/hooks/useSafeArea.ts
@@ -0,0 +1,64 @@
+import { useState, useEffect } from 'react';
+
+interface SafeAreaInsets {
+ top: number;
+ right: number;
+ bottom: number;
+ left: number;
+}
+
+export function useSafeArea() {
+ const [safeAreaInsets, setSafeAreaInsets] = useState
({
+ top: 0,
+ right: 0,
+ bottom: 0,
+ left: 0
+ });
+
+ const [viewportHeight, setViewportHeight] = useState(0);
+
+ useEffect(() => {
+ const updateSafeArea = () => {
+ // 获取 CSS 环境变量
+ const computedStyle = getComputedStyle(document.documentElement);
+
+ const top = parseInt(computedStyle.getPropertyValue('--sat').replace('px', '')) || 0;
+ const right = parseInt(computedStyle.getPropertyValue('--sar').replace('px', '')) || 0;
+ const bottom = parseInt(computedStyle.getPropertyValue('--sab').replace('px', '')) || 0;
+ const left = parseInt(computedStyle.getPropertyValue('--sal').replace('px', '')) || 0;
+
+ setSafeAreaInsets({ top, right, bottom, left });
+
+ // 设置动态视口高度
+ const vh = window.innerHeight;
+ setViewportHeight(vh);
+ document.documentElement.style.setProperty('--vh', `${vh * 0.01}px`);
+ };
+
+ updateSafeArea();
+
+ window.addEventListener('resize', updateSafeArea);
+ window.addEventListener('orientationchange', updateSafeArea);
+
+ // 延迟更新以处理移动端浏览器地址栏变化
+ const timeoutId = setTimeout(updateSafeArea, 500);
+
+ return () => {
+ window.removeEventListener('resize', updateSafeArea);
+ window.removeEventListener('orientationchange', updateSafeArea);
+ clearTimeout(timeoutId);
+ };
+ }, []);
+
+ return {
+ safeAreaInsets,
+ viewportHeight,
+ /** 获取考虑安全区域的样式 */
+ getSafeAreaStyle: (includeBottom = true) => ({
+ paddingTop: `max(1rem, ${safeAreaInsets.top}px)`,
+ paddingRight: `max(1rem, ${safeAreaInsets.right}px)`,
+ paddingBottom: includeBottom ? `max(1rem, ${safeAreaInsets.bottom}px)` : undefined,
+ paddingLeft: `max(1rem, ${safeAreaInsets.left}px)`,
+ })
+ };
+}
diff --git a/tailwind.config.js b/tailwind.config.js
index d01ae8a..fae3ec4 100644
--- a/tailwind.config.js
+++ b/tailwind.config.js
@@ -90,6 +90,20 @@ module.exports = {
'100': '100ms',
'200': '200ms',
'300': '300ms',
+ },
+ spacing: {
+ 'safe-top': 'env(safe-area-inset-top)',
+ 'safe-right': 'env(safe-area-inset-right)',
+ 'safe-bottom': 'env(safe-area-inset-bottom)',
+ 'safe-left': 'env(safe-area-inset-left)',
+ },
+ height: {
+ 'dvh': '100dvh',
+ 'safe-screen': 'calc(100dvh - env(safe-area-inset-top) - env(safe-area-inset-bottom))',
+ },
+ minHeight: {
+ 'dvh': '100dvh',
+ 'safe-screen': 'calc(100dvh - env(safe-area-inset-top) - env(safe-area-inset-bottom))',
}
},
},
From cbbc0b44f324f352025dbaa7fef174e67fd4ba72 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E5=8C=97=E6=9E=B3?=
<7854742+wang_rumeng@user.noreply.gitee.com>
Date: Thu, 25 Sep 2025 16:26:23 +0800
Subject: [PATCH 3/8] =?UTF-8?q?=E5=B0=9D=E8=AF=95=E8=B0=83=E6=95=B4H5?=
=?UTF-8?q?=E5=B8=83=E5=B1=80?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
components/pages/work-flow/H5MediaViewer.tsx | 11 +++++++----
components/pages/work-flow/H5TaskInfo.tsx | 2 +-
components/pages/work-flow/thumbnail-grid.tsx | 2 +-
3 files changed, 9 insertions(+), 6 deletions(-)
diff --git a/components/pages/work-flow/H5MediaViewer.tsx b/components/pages/work-flow/H5MediaViewer.tsx
index e000ea1..c0877e9 100644
--- a/components/pages/work-flow/H5MediaViewer.tsx
+++ b/components/pages/work-flow/H5MediaViewer.tsx
@@ -371,13 +371,13 @@ export function H5MediaViewer({
// 其他阶段:使用 Carousel
return (
-
+
{/* 左侧最终视频缩略图栏(H5) 视频暂停时展示 */}
{taskObject?.final?.url && !isPlaying && (
-
+
{isFinalBarOpen && (
-
+
)}
);
diff --git a/components/pages/work-flow/H5TaskInfo.tsx b/components/pages/work-flow/H5TaskInfo.tsx
index 9c25535..e8be32a 100644
--- a/components/pages/work-flow/H5TaskInfo.tsx
+++ b/components/pages/work-flow/H5TaskInfo.tsx
@@ -125,7 +125,7 @@ const H5TaskInfo: React.FC
= ({
return (
Date: Thu, 25 Sep 2025 17:17:42 +0800
Subject: [PATCH 4/8] =?UTF-8?q?=E4=BC=98=E5=8C=96H5=E9=80=82=E9=85=8D?=
=?UTF-8?q?=E6=A0=B7=E5=BC=8F?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
components/layout/top-bar.tsx | 2 +-
components/pages/work-flow/H5MediaViewer.tsx | 12 ++++++------
components/pages/work-flow/H5TaskInfo.tsx | 4 ++--
components/ui/script-modal.tsx | 2 +-
4 files changed, 10 insertions(+), 10 deletions(-)
diff --git a/components/layout/top-bar.tsx b/components/layout/top-bar.tsx
index 365fab3..7b95a2d 100644
--- a/components/layout/top-bar.tsx
+++ b/components/layout/top-bar.tsx
@@ -255,7 +255,7 @@ export function TopBar({ collapsed, isDesktop=true }: { collapsed: boolean, isDe
return (
(
(videoRefs.current[idx] = el)}
className="w-full h-full object-contain [transform:translateZ(0)] [backface-visibility:hidden] [will-change:transform] bg-black"
style={{
- maxHeight: 'calc(100vh - 20rem)',
+ maxHeight: 'calc(100vh - 16rem)',
}}
src={url}
preload="metadata"
@@ -225,7 +225,7 @@ export function H5MediaViewer({
>
) : (
{status === 0 && (
Generating...
@@ -251,7 +251,7 @@ export function H5MediaViewer({
// 渲染图片 slide
const renderImageSlides = () => (
(
))}
@@ -494,7 +494,7 @@ export function H5MediaViewer({
[data-alt='carousel-wrapper'] .slick-slide { display: flex !important;justify-content: center; }
.slick-slider { height: 100% !important;display: flex !important; }
.ant-carousel { height: 100% !important; }
- .slick-list { width: 100%;height: 100% !important;max-height: calc(100vh - 20rem); }
+ .slick-list { width: 100%;height: 100% !important;max-height: calc(100vh - 16rem); }
.slick-track { display: flex !important; align-items: center;height: 100% !important; }
`}
diff --git a/components/pages/work-flow/H5TaskInfo.tsx b/components/pages/work-flow/H5TaskInfo.tsx
index e8be32a..5cda52b 100644
--- a/components/pages/work-flow/H5TaskInfo.tsx
+++ b/components/pages/work-flow/H5TaskInfo.tsx
@@ -125,14 +125,14 @@ const H5TaskInfo: React.FC
= ({
return (
{/* 左侧标题区域 */}
-
+
Date: Thu, 25 Sep 2025 17:24:39 +0800
Subject: [PATCH 5/8] =?UTF-8?q?=E4=BF=AE=E5=A4=8D:=20=E4=BD=BF=E7=94=A8?=
=?UTF-8?q?=E9=A1=B5=E5=86=85=E8=AF=B7=E6=B1=82=E9=87=8D=E5=AE=9A=E5=90=91?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
app/pay-redirect/page.tsx | 81 ++++++++++++++++++++++++---------
app/pricing/page.tsx | 54 ++++------------------
components/layout/top-bar.tsx | 56 ++++-------------------
components/pages/home-page2.tsx | 38 ++++------------
4 files changed, 87 insertions(+), 142 deletions(-)
diff --git a/app/pay-redirect/page.tsx b/app/pay-redirect/page.tsx
index 24fc220..f11b8b5 100644
--- a/app/pay-redirect/page.tsx
+++ b/app/pay-redirect/page.tsx
@@ -2,38 +2,75 @@
import React from "react";
import { TailwindSpinner } from "@/components/common/GlobalLoad";
+import { useSearchParams } from "next/navigation";
+import { createCheckoutSession, buyTokens } from "@/lib/stripe";
export default function PayRedirectPage() {
- const [status, setStatus] = React.useState("Waiting for secure checkout redirect...");
+ const [status, setStatus] = React.useState("Preparing checkout...");
const [error, setError] = React.useState("");
+ const searchParams = useSearchParams();
React.useEffect(() => {
- const handleMessage = (event: MessageEvent) => {
+ const type = (searchParams.get("type") || "").toLowerCase();
+ if (!type) {
+ setStatus("");
+ setError("Missing payment type.");
+ return;
+ }
+
+ const redirectTo = async () => {
try {
- if (event.origin !== window.location.origin) return;
- const data = event.data || {};
- if (data?.type === "redirect-to-payment" && typeof data?.url === "string") {
- setStatus("Redirecting to Stripe Checkout...");
- window.location.href = data.url as string;
- } else if (data?.type === "redirect-error") {
- setError(typeof data?.message === "string" ? data.message : "Failed to create payment. Please close this page and try again.");
+ if (type === "token") {
+ const amountStr = searchParams.get("amount");
+ const pkg = searchParams.get("pkg") || "basic";
+ const amount = Number(amountStr);
+ if (!amount || amount <= 0) {
+ throw new Error("Invalid token amount");
+ }
+ setStatus("Creating token purchase session...");
+ const resp = await buyTokens({ token_amount: amount, package_type: pkg });
+ if (resp?.successful && resp?.data?.checkout_url) {
+ setStatus("Redirecting to Stripe Checkout...");
+ window.location.href = resp.data.checkout_url as string;
+ return;
+ }
+ throw new Error(resp?.message || "Failed to create token checkout session");
}
- } catch {
- setError("An error occurred while processing redirect info. Please close this page and try again.");
+
+ if (type === "subscription") {
+ const plan = searchParams.get("plan");
+ const billing = (searchParams.get("billing") || "month") as "month" | "year";
+ if (!plan) {
+ throw new Error("Missing plan name");
+ }
+ const currentUser = JSON.parse(localStorage.getItem("currentUser") || "{}");
+ if (!currentUser?.id) {
+ throw new Error("Not logged in. Please sign in and try again.");
+ }
+ setStatus("Creating subscription session...");
+ const result = await createCheckoutSession({
+ user_id: String(currentUser.id),
+ plan_name: plan,
+ billing_cycle: billing,
+ });
+ if (result?.successful && result?.data?.checkout_url) {
+ setStatus("Redirecting to Stripe Checkout...");
+ window.location.href = result.data.checkout_url as string;
+ return;
+ }
+ throw new Error(result?.message || "Failed to create subscription session");
+ }
+
+ throw new Error("Unsupported payment type");
+ } catch (e: unknown) {
+ setStatus("");
+ setError(e instanceof Error ? e.message : "Failed to prepare checkout.");
}
};
- window.addEventListener("message", handleMessage);
- const timeoutId = window.setTimeout(() => {
- setStatus("");
- setError("No redirect instruction received. It may be a network issue or the page was blocked. Please go back and try again.");
- }, 15000);
-
- return () => {
- window.removeEventListener("message", handleMessage);
- window.clearTimeout(timeoutId);
- };
- }, []);
+ redirectTo();
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [searchParams]);
return (
diff --git a/app/pricing/page.tsx b/app/pricing/page.tsx
index 9ea40bd..01291e4 100644
--- a/app/pricing/page.tsx
+++ b/app/pricing/page.tsx
@@ -85,54 +85,18 @@ function HomeModule5() {
const handleSubscribe = async (planName: string) => {
setLoadingPlan(planName);
-
- // 先同步打开同域重定向页,避免拦截
- const redirectWindow = window.open('/pay-redirect', '_blank');
- if (!redirectWindow) {
+ // 改为直接携带参数打开 pay-redirect,由其内部完成创建与跳转
+ const url = `/pay-redirect?type=subscription&plan=${encodeURIComponent(planName)}&billing=${encodeURIComponent(billingType)}`;
+ const win = window.open(url, '_blank');
+ // 通知当前窗口等待支付(显示loading模态框)
+ window.postMessage({
+ type: 'waiting-payment',
+ paymentType: 'subscription',
+ }, '*');
+ if (!win) {
setLoadingPlan(null);
throw new Error('Unable to open redirect window, please check popup settings');
}
-
- try {
- const { createCheckoutSession } = await import("@/lib/stripe");
-
- const User = JSON.parse(localStorage.getItem("currentUser") || "{}");
- if (!User.id) {
- throw new Error("Unable to obtain user ID, please log in again");
- }
-
- const result = await createCheckoutSession({
- user_id: String(User.id),
- plan_name: planName,
- billing_cycle: billingType,
- });
-
- if (!result.successful || !result.data?.checkout_url) {
- throw new Error("create checkout session failed");
- }
-
- // 通知当前窗口等待支付(显示loading模态框)
- window.postMessage({
- type: 'waiting-payment',
- paymentType: 'subscription',
- }, '*');
-
- // 通过 postMessage 通知新页面执行重定向
- redirectWindow.postMessage({
- type: 'redirect-to-payment',
- url: result.data.checkout_url,
- }, window.location.origin);
- } catch (error) {
- // 通知新页错误信息
- try {
- redirectWindow.postMessage({
- type: 'redirect-error',
- message: 'Failed to create checkout session',
- }, window.location.origin);
- } catch {}
- setLoadingPlan(null);
- throw new Error("create checkout session failed, please try again later");
- }
};
return (
{
if (!currentUser?.id) {
console.error("用户未登录");
@@ -99,52 +99,14 @@ export function TopBar({ collapsed, isDesktop=true }: { collapsed: boolean, isDe
console.error("Token数量必须大于0");
return;
}
-
- // 先同步打开同域新页面,避免被拦截
- const redirectWindow = window.open("/pay-redirect", "_blank");
- if (!redirectWindow) {
- console.error("无法打开支付重定向页面,可能被浏览器拦截");
- return;
- }
-
- setIsBuyingTokens(true);
- try {
- const response = await buyTokens({
- token_amount: tokenAmount,
- package_type: "basic"
- });
-
- if (response.successful && response.data?.checkout_url) {
- // 通知当前窗口等待支付,标识为Token购买
- window.postMessage({
- type: "waiting-payment",
- paymentType: "token"
- }, "*");
- sessionStorage.setItem('session_id', response.data.session_id);
- // 通过 postMessage 通知新页面进行重定向
- redirectWindow.postMessage({
- type: "redirect-to-payment",
- url: response.data.checkout_url
- }, window.location.origin);
- } else {
- console.error("创建Token购买失败:", response.message);
- // 通知新页显示错误
- redirectWindow.postMessage({
- type: "redirect-error",
- message: response.message || "创建支付失败"
- }, window.location.origin);
- }
- } catch (error: unknown) {
- console.error("Token购买失败:", error);
- try {
- redirectWindow.postMessage({
- type: "redirect-error",
- message: "网络或服务异常,请关闭此页重试"
- }, window.location.origin);
- } catch {}
- } finally {
- setIsBuyingTokens(false);
- }
+ // 直接打开带参数的 pay-redirect,新窗口内自行创建会话并跳转
+ const url = `/pay-redirect?type=token&amount=${encodeURIComponent(tokenAmount)}&pkg=basic`;
+ window.open(url, "_blank");
+ // 通知当前窗口等待支付(显示loading模态框)
+ window.postMessage({
+ type: 'waiting-payment',
+ paymentType: 'subscription',
+ }, '*');
};
// 处理自定义金额购买
diff --git a/components/pages/home-page2.tsx b/components/pages/home-page2.tsx
index f75a0c4..20e7dee 100644
--- a/components/pages/home-page2.tsx
+++ b/components/pages/home-page2.tsx
@@ -1249,34 +1249,16 @@ function HomeModule5() {
const handleSubscribe = async (planName: string) => {
localStorage.setItem("callBackUrl", pathname);
- try {
- // 使用新的Checkout Session方案(更简单!)
- const { createCheckoutSession, redirectToCheckout } = await import(
- "@/lib/stripe"
- );
-
- // 从localStorage获取当前用户信息
- const User = JSON.parse(localStorage.getItem("currentUser") || "{}");
-
- if (!User.id) {
- throw new Error("无法获取用户ID,请重新登录");
- }
-
- // 1. 创建Checkout Session
- const result = await createCheckoutSession({
- user_id: String(User.id),
- plan_name: planName,
- billing_cycle: billingType,
- });
-
- if (!result.successful || !result.data) {
- throw new Error("create checkout session failed");
- }
-
- setShowCallbackModal(true);
- window.open(result.data.checkout_url, "_blank");
- } catch (error) {
- throw new Error("create checkout session failed, please try again later");
+ // 改为直接携带参数打开 pay-redirect,由其内部完成创建与跳转
+ const url = `/pay-redirect?type=subscription&plan=${encodeURIComponent(planName)}&billing=${encodeURIComponent(billingType)}`;
+ const win = window.open(url, "_blank");
+ // 通知当前窗口等待支付(显示loading模态框)
+ window.postMessage({
+ type: 'waiting-payment',
+ paymentType: 'subscription',
+ }, '*');
+ if (!win) {
+ throw new Error("Unable to open redirect window, please check popup settings");
}
};
return (
From 3a741b32fba606657c0c82bcf629271de175a12f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E5=8C=97=E6=9E=B3?=
<7854742+wang_rumeng@user.noreply.gitee.com>
Date: Thu, 25 Sep 2025 17:28:31 +0800
Subject: [PATCH 6/8] =?UTF-8?q?=E7=BB=A7=E7=BB=AD=E4=BC=98=E5=8C=96H5?=
=?UTF-8?q?=E6=A0=B7=E5=BC=8F?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
components/pages/work-flow/H5MediaViewer.tsx | 12 ++++++------
components/pages/work-flow/H5TaskInfo.tsx | 2 +-
2 files changed, 7 insertions(+), 7 deletions(-)
diff --git a/components/pages/work-flow/H5MediaViewer.tsx b/components/pages/work-flow/H5MediaViewer.tsx
index abf1212..c0877e9 100644
--- a/components/pages/work-flow/H5MediaViewer.tsx
+++ b/components/pages/work-flow/H5MediaViewer.tsx
@@ -165,7 +165,7 @@ export function H5MediaViewer({
// 渲染视频 slide
const renderVideoSlides = () => (
(videoRefs.current[idx] = el)}
className="w-full h-full object-contain [transform:translateZ(0)] [backface-visibility:hidden] [will-change:transform] bg-black"
style={{
- maxHeight: 'calc(100vh - 16rem)',
+ maxHeight: 'calc(100vh - 20rem)',
}}
src={url}
preload="metadata"
@@ -225,7 +225,7 @@ export function H5MediaViewer({
>
) : (
{status === 0 && (
Generating...
@@ -251,7 +251,7 @@ export function H5MediaViewer({
// 渲染图片 slide
const renderImageSlides = () => (
(
))}
@@ -494,7 +494,7 @@ export function H5MediaViewer({
[data-alt='carousel-wrapper'] .slick-slide { display: flex !important;justify-content: center; }
.slick-slider { height: 100% !important;display: flex !important; }
.ant-carousel { height: 100% !important; }
- .slick-list { width: 100%;height: 100% !important;max-height: calc(100vh - 16rem); }
+ .slick-list { width: 100%;height: 100% !important;max-height: calc(100vh - 20rem); }
.slick-track { display: flex !important; align-items: center;height: 100% !important; }
`}
diff --git a/components/pages/work-flow/H5TaskInfo.tsx b/components/pages/work-flow/H5TaskInfo.tsx
index 5cda52b..9661827 100644
--- a/components/pages/work-flow/H5TaskInfo.tsx
+++ b/components/pages/work-flow/H5TaskInfo.tsx
@@ -132,7 +132,7 @@ const H5TaskInfo: React.FC
= ({
className="flex items-start gap-3"
>
{/* 左侧标题区域 */}
-
+
Date: Thu, 25 Sep 2025 17:46:40 +0800
Subject: [PATCH 7/8] =?UTF-8?q?=E4=BF=AE=E5=A4=8DH5=20top=E5=B1=82?=
=?UTF-8?q?=E7=BA=A7=E9=97=AE=E9=A2=98?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
components/layout/top-bar.tsx | 2 +-
components/pages/home-page2.tsx | 2 +-
components/pages/work-flow/H5MediaViewer.tsx | 2 +-
3 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/components/layout/top-bar.tsx b/components/layout/top-bar.tsx
index 7d59e7f..92d6839 100644
--- a/components/layout/top-bar.tsx
+++ b/components/layout/top-bar.tsx
@@ -217,7 +217,7 @@ export function TopBar({ collapsed, isDesktop=true }: { collapsed: boolean, isDe
return (
window.removeEventListener('home-menu-toggle' as any, handler as any);
}, []);
return (
-
+
{/* 桌面端菜单(居中,仅三个项) */}
diff --git a/components/pages/work-flow/H5MediaViewer.tsx b/components/pages/work-flow/H5MediaViewer.tsx
index c0877e9..8a4940f 100644
--- a/components/pages/work-flow/H5MediaViewer.tsx
+++ b/components/pages/work-flow/H5MediaViewer.tsx
@@ -301,7 +301,7 @@ export function H5MediaViewer({