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 = () => (
(
scene
))} @@ -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 = () => (
(
scene
))} @@ -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({