diff --git a/src/api/api.ts b/src/api/api.ts index c609b0d..5f84634 100644 --- a/src/api/api.ts +++ b/src/api/api.ts @@ -25,10 +25,14 @@ type ListApiResponse = { data?: ListApiData; }; export type ProductPlanCustomInfo = { - rightTags?: string; discount_info?: string; point_amount?: string; - illustrator?: string; + buttonText?: string; + bonus_gift?: string; + goal_title?: string; + goal_description?: string; + feature_title?: string; + new_user?: string | boolean; }; export type ProductPlan = ApiParams & { id?: EntityId; @@ -36,11 +40,11 @@ export type ProductPlan = ApiParams & { name?: string; description?: string; planCategory?: string; + recommended?: boolean; price?: number | string; price_amount?: number | string; original_price_amount?: number | string; level?: number | string; - recommended?: boolean; coins?: number | string; pointsGrantMode?: string; custom_info?: ProductPlanCustomInfo; diff --git a/src/assets/images/subscription/subscribe-plan-feature-title-icon.png b/src/assets/images/subscription/subscribe-plan-feature-title-icon.png new file mode 100644 index 0000000..3f4f7ef Binary files /dev/null and b/src/assets/images/subscription/subscribe-plan-feature-title-icon.png differ diff --git a/src/assets/images/subscription/subscribe-plan-goal-icon.png b/src/assets/images/subscription/subscribe-plan-goal-icon.png new file mode 100644 index 0000000..52415e9 Binary files /dev/null and b/src/assets/images/subscription/subscribe-plan-goal-icon.png differ diff --git a/src/components/subscribe-plan-list/index.scss b/src/components/subscribe-plan-list/index.scss index 113e13c..4d611cd 100644 --- a/src/components/subscribe-plan-list/index.scss +++ b/src/components/subscribe-plan-list/index.scss @@ -23,7 +23,7 @@ align-items: center; justify-content: center; width: 100%; - margin-bottom: 9px; + margin-bottom: 60px; box-sizing: border-box; .billing_cycle { @@ -114,15 +114,15 @@ .plan-grid { display: grid; - grid-template-columns: repeat(4, 357px); + grid-template-columns: repeat(4, 313px); align-items: start; justify-content: center; - gap: 10px; + gap: 12px; width: 100%; } .plan_grid_item { - width: 357px; + width: 313px; min-width: 0; } @@ -131,6 +131,69 @@ flex-direction: column; width: 100%; min-width: 0; + gap: 0; + } + + .plan_card.plan_card_new-user, + .plan_card.plan_card_recommended { + box-sizing: border-box; + border-radius: 26px 26px 0 0; + } + + .plan_item__highlight-frame { + display: flex; + flex-direction: column; + width: calc(100% + 2px); + max-width: none; + min-width: 0; + margin: -32px 0 0 -1px; + border: 1px solid transparent; + border-radius: 26px; + box-sizing: border-box; + background: transparent; + } + + .plan_item__new-user-label { + border-color: #00dcca; + background: #00dcca; + } + + .plan_item__recommended-label { + border-color: #6f47f5; + background: #6f47f5; + } + + .plan_item__highlight-label { + display: flex; + align-items: flex-start; + justify-content: center; + width: 100%; + height: 31px; + padding-top: 6px; + border-radius: 25px 25px 0 0; + box-sizing: border-box; + background: transparent; + color: #333333; + font-size: 14px; + font-weight: 700; + line-height: 24px; + text-align: center; + white-space: nowrap; + } + + .plan_item__recommended-label .plan_item__highlight-label { + color: #ffffff; + } + + .plan_item__highlight-content { + display: flex; + flex-direction: column; + width: 313px; + max-width: 100%; + min-width: 0; + margin: 0 auto; + border-radius: 26px; + background: #ffffff; } .plan_item__header { @@ -138,21 +201,29 @@ z-index: 2; display: flex; flex-direction: column; - gap: 10px; - min-height: 287px; - padding: 24px 20px; + gap: 16px; + min-height: 266px; + padding: 20px; border-radius: 26px; box-sizing: border-box; background: #ffffff; box-shadow: 0 0 5px rgba(0, 0, 0, 0.1); } + .plan_card.plan_card_new-user .plan_item__header { + background: linear-gradient(180deg, #ddfefb 0%, #ffffff 24.04%); + } + + .plan_card.plan_card_recommended .plan_item__header { + background: linear-gradient(180deg, #f3edff 0%, #ffffff 24.04%); + } + .plan_item__title-row { display: flex; - align-items: flex-start; + align-items: center; justify-content: space-between; gap: 8px; - min-height: 28px; + min-height: 20px; } .plan_item__title { @@ -160,17 +231,9 @@ align-items: center; min-width: 0; color: #333333; - font-size: 20px; - font-weight: 500; - line-height: 28px; - } - - .plan_item__crown { - flex: 0 0 auto; - width: 24px; - height: 24px; - margin-right: 8px; - display: block; + font-size: 16px; + font-weight: 700; + line-height: 20px; } .plan_item__title span { @@ -180,37 +243,30 @@ white-space: nowrap; } - .plan_item__title-badges { - display: flex; - flex: 0 0 auto; - flex-wrap: wrap; - justify-content: flex-end; - gap: 6px; - max-width: 124px; - } - - .plan_item__title-badge { + .plan_item__discount-badge { display: inline-flex; + flex: 0 0 auto; align-items: center; justify-content: center; - height: 28px; - padding: 0 15px; - border-radius: 26px; + min-width: 66px; + max-width: 96px; + height: 20px; + padding: 0 10px; + overflow: hidden; + border-radius: 100px; box-sizing: border-box; + background: #f3efff; + color: #6f47f5; font-size: 12px; - font-weight: 500; - line-height: 26px; + font-weight: 400; + line-height: 20px; + text-overflow: ellipsis; white-space: nowrap; } - .plan_item__title-badge_recommended { - color: #e55936; - background: #ffe3c3; - } - - .plan_item__title-badge_top { - color: #6f47f5; - background: #ebe5ff; + .plan_card.plan_card_new-user .plan_item__discount-badge { + background: #f0fffe; + color: #12a397; } .plan_item__price-block { @@ -224,7 +280,6 @@ display: flex; align-items: flex-start; min-height: 44px; - padding-right: 72px; box-sizing: border-box; } @@ -240,7 +295,7 @@ margin-left: 2px; color: #333333; font-size: 36px; - font-weight: 500; + font-weight: 700; line-height: 44px; } @@ -252,7 +307,7 @@ margin-top: 18px; margin-left: 4px; color: #666666; - font-size: 16px; + font-size: 12px; font-weight: 400; line-height: 24px; white-space: nowrap; @@ -263,35 +318,16 @@ margin-top: 18px; margin-left: 4px; color: #999999; - font-size: 16px; - font-weight: 500; + font-size: 12px; + font-weight: 400; line-height: 24px; text-decoration: line-through; white-space: nowrap; } - .plan_item__price-discount { - position: absolute; - top: 20px; - right: 0; - display: inline-flex; - align-items: center; - justify-content: center; - height: 20px; - padding: 0 10px; - border-radius: 9px; - box-sizing: border-box; - background: #ebe5ff; - color: #333333; - font-size: 12px; - font-weight: 500; - line-height: 20px; - white-space: nowrap; - } - .plan_item__price-subtitle { margin: 0; - color: #666666; + color: #999999; font-size: 12px; font-weight: 400; line-height: 24px; @@ -301,7 +337,7 @@ position: relative; display: flex; align-items: center; - width: 314px; + width: 273px; max-width: 100%; height: 49px; padding: 0 12px; @@ -311,6 +347,10 @@ background: #f3efff; } + .plan_card.plan_card_new-user .plan_points { + background: #f0fffe; + } + .plan_points__content { position: relative; z-index: 1; @@ -378,7 +418,7 @@ } .plan_item__cta.arco-btn:not(.arco-btn-disabled) { - background: linear-gradient(90deg, #333333 0%, #6f47f5 100%) !important; + background: #333333 !important; } .plan_item__cta.arco-btn-disabled, @@ -392,7 +432,7 @@ .plan_tier_selector { width: 100%; - max-width: 314px; + max-width: 273px; height: 44px; margin: 0 auto; box-sizing: border-box; @@ -488,76 +528,144 @@ } .plan_commission { - position: relative; - width: 314px; + display: flex; + align-items: center; + width: 100%; max-width: 100%; - height: 35px; + min-height: 24px; padding: 0; box-sizing: border-box; overflow: hidden; - border-radius: 8px; - background: #f5f2ff; + background: transparent; } .plan_commission .desc_highlight { margin-right: 0; color: #6f47f5; - font-weight: 500; + font-weight: 700; } .plan_commission__text { - position: absolute; - top: 50%; - left: 18px; - z-index: 1; display: block; overflow: hidden; - width: 178px; - height: 24px; - transform: translateY(-50%); + width: 100%; + min-width: 0; color: #333333; - font-size: 14px; - font-weight: 500; + font-size: 12px; + font-weight: 400; line-height: 24px; text-overflow: ellipsis; white-space: nowrap; } - .plan_commission__badge { - position: absolute; - top: 8px; - right: 10px; + .plan_item__features { + position: relative; z-index: 1; - display: inline-flex; - align-items: center; - justify-content: center; - width: 43px; - height: 20px; - padding: 0; - border: 1px solid #6f47f5; - border-radius: 5px; + margin-top: -58px; + padding: 70px 20px 20px; + border-radius: 26px; box-sizing: border-box; - background: transparent; - color: #6f47f5; - font-size: 12px; - font-weight: 500; - line-height: normal; - white-space: nowrap; + background: #ffffff; + box-shadow: 0 0 5px rgba(0, 0, 0, 0.1); } - .plan_item__features { - position: relative; - z-index: 1; - min-height: 510px; - margin-top: -10px; - padding: 46px 20px 24px; + .plan_goal { + display: flex; + flex-direction: column; + align-items: flex-start; + width: 100%; + min-height: 102px; + padding: 20px; border-radius: 26px; box-sizing: border-box; - background: linear-gradient(0deg, #ffffff 70%, #f4f4f5 100%); - box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1); + background: #f9f6ff; + } + + .plan_card.plan_card_new-user .plan_goal { + background: #f0fffe; + } + + .plan_goal__title { + display: flex; + align-items: center; + gap: 5px; + max-width: 100%; + color: #6f47f5; + font-size: 14px; + font-weight: 700; + line-height: 28px; + } + + .plan_card.plan_card_new-user .plan_goal__title { + color: #049d90; + } + + .plan_goal__icon { + display: block; + flex: 0 0 auto; + width: 15px; + height: 15px; + object-fit: contain; + } + + .plan_card.plan_card_new-user .plan_goal__icon { + filter: brightness(0) saturate(100%) invert(42%) sepia(98%) saturate(800%) hue-rotate(135deg) brightness(92%) contrast(97%); + } + + .plan_goal__title span { + overflow: hidden; + min-width: 0; + text-overflow: ellipsis; + white-space: nowrap; } - .plan_item__feature-list { + .plan_goal__desc { + display: flex; + flex-direction: column; + margin: 6px 0 0; + color: #333333; + font-size: 12px; + font-weight: 400; + line-height: 18px; + } + + .plan_goal__desc:first-child { + margin-top: 0; + } + + .plan_description { + width: 100%; + } + + .plan_feature_header { + display: flex; + align-items: center; + gap: 5px; + width: 100%; + min-width: 0; + } + + .plan_feature_header__icon { + display: block; + flex: 0 0 auto; + width: 16px; + height: 14px; + object-fit: contain; + } + + .plan_feature_header__text { + display: block; + overflow: hidden; + min-width: 0; + color: #6f47f5; + font-size: 14px; + font-weight: 700; + line-height: 28px; + text-overflow: ellipsis; + white-space: nowrap; + } + + .plan_description__list { margin: 0; padding: 0; list-style: none; @@ -567,8 +675,8 @@ display: flex; align-items: flex-start; min-width: 0; - margin: 0 0 10px; - color: #666666; + margin: 0; + color: #333333; font-size: 12px; font-weight: 400; line-height: 24px; @@ -578,42 +686,49 @@ margin-bottom: 0; } + .plan_feature_divider { + display: block; + width: 100%; + height: 1px; + margin: 20px 0; + padding: 0; + background: #d9d9db; + } + .plan_feature.plan_feature_section, .plan_feature.plan_feature_title { display: block; margin: 0 0 10px; - color: #666666; - font-size: 12px; - font-weight: 500; - line-height: 24px; + color: #333333; + font-size: 14px; + font-weight: 700; + line-height: 28px; } .plan_feature__check { flex: 0 0 auto; display: block; - width: 14px; - height: 14px; - margin-top: 5px; - margin-right: 8px; + width: 16px; + height: 16px; + margin-top: 3px; + margin-right: 5px; } .plan_feature__text { flex: 1 1 auto; min-width: 0; - color: #666666; + color: #333333; font-size: 12px; - font-weight: 500; + font-weight: 400; line-height: 24px; } .desc_highlight { color: #6f47f5; - font-weight: 500; + font-weight: 700; } } - - @media (max-width: 820px) { .plans { .plans__points-row { @@ -629,161 +744,13 @@ } .plan-grid { - grid-template-columns: repeat(4, 320px); + grid-template-columns: repeat(4, 313px); width: max-content; justify-content: flex-start; } .plan_grid_item { - width: 320px; - } - - .plan_item__header { - min-height: 287px; - } - - .plan_item__title-row { - flex-direction: column; - } - - .plan_item__title-badges { - max-width: 100%; - justify-content: flex-start; - } - - .plan_item__price { - padding-right: 0; - } - - .plan_item__price-discount { - position: static; - margin-top: 20px; - margin-left: 8px; + width: 313px; } } -} - -/* Popi Future Creator 开通成功弹窗(createPortal → body) */ -.subscribe-fc-modal-overlay { - position: fixed; - inset: 0; - z-index: 10050; - box-sizing: border-box; - margin: 0; - padding: 16px; - display: flex; - align-items: center; - justify-content: center; - background: rgba(51, 51, 51, 0.3); - pointer-events: auto; - touch-action: none; - overscroll-behavior: contain; -} - -.subscribe-fc-modal-dialog { - position: relative; - box-sizing: border-box; - width: 100%; - max-width: 413px; - padding: 45px; - border-radius: 9px; - background: #ffffff; - overflow: visible; - touch-action: auto; -} - -.subscribe-fc-modal-dialog__close { - position: absolute; - top: 10px; - right: 10px; - z-index: 1; - width: 20px; - height: 20px; - margin: 0; - padding: 0; - border: none; - background: transparent; - cursor: pointer; - display: inline-flex; - align-items: center; - justify-content: center; -} - -.subscribe-fc-modal-dialog__close img { - display: block; - width: 20px; - height: 20px; - object-fit: cover; -} - -.subscribe-fc-modal-dialog__body { - box-sizing: border-box; - display: flex; - flex-direction: column; - align-items: center; - width: 100%; -} - -.subscribe-fc-modal-dialog__title { - margin: 0; - width: 100%; - text-align: center; - font-size: 18px; - font-weight: 700; - line-height: 22px; - color: #333333; -} - -.subscribe-fc-modal-dialog__success { - margin: 17px 0 0; - width: 100%; - text-align: center; - font-size: 18px; - font-weight: 700; - line-height: 22px; - color: #5c3bc7; -} - -.subscribe-fc-modal-dialog__qrWrap { - box-sizing: border-box; - width: 221px; - height: 221px; - margin-top: 10px; - flex-shrink: 0; -} - -.subscribe-fc-modal-dialog__qr { - display: block; - width: 100%; - height: 100%; - object-fit: cover; -} - -.subscribe-fc-modal-dialog__qrPlaceholder { - width: 100%; - height: 100%; - background: #f0f0f0; - border-radius: 4px; -} - -.subscribe-fc-modal-dialog__tip { - box-sizing: border-box; - display: flex; - align-items: center; - justify-content: center; - gap: 5px; - margin: 10px 0 0; - width: 100%; - font-size: 12px; - font-weight: 400; - line-height: 22px; - color: #666666; -} - -.subscribe-fc-modal-dialog__tipIcon { - flex: 0 0 auto; - display: block; - width: 14px; - height: 14px; - object-fit: cover; } \ No newline at end of file diff --git a/src/components/subscribe-plan-list/index.tsx b/src/components/subscribe-plan-list/index.tsx index 426c983..17dad6e 100644 --- a/src/components/subscribe-plan-list/index.tsx +++ b/src/components/subscribe-plan-list/index.tsx @@ -1,12 +1,10 @@ import { Button } from "@arco-design/web-react"; -import { useCallback, useEffect, useMemo, useState } from "react"; -import { createPortal } from "react-dom"; +import { Fragment, useCallback, useEffect, useMemo, useState } from "react"; import { useTranslation } from "react-i18next"; import { getPlanList } from "@/api/api"; import type { ProductPlan } from "@/api/api"; -import { getMemberLevelLabel } from "@/constants"; +// import { memberLevelMap } from "@/constants"; import { openPaySubscribeDialog } from "@/dialog"; -import { useMemberLevelStore } from "@/store/useMemberLevelStore"; import { useParamConfigStore } from "@/store/useParamConfigStore"; import { useUserStore } from "@/store/useUserStore"; @@ -39,10 +37,13 @@ type PlanGroup = { const PLAN_INLINE_TAG_RE = /<\/?(?:mark|title|section)>/gi; const HIGHLIGHT_CLASS_NAME = "desc_highlight"; const SUBSCRIBE_PLAN_ASSETS = { - crown: new URL("@/assets/images/subscription/crown.png", import.meta.url).href, check: new URL("@/assets/images/home/feature-check-icon.png", import.meta.url).href, pointsIcon: new URL("@/assets/images/points/points-active.png", import.meta.url).href, }; +const SUBSCRIBE_PLAN_LOCAL_ASSETS = { + goal: new URL("@/assets/images/subscription/subscribe-plan-goal-icon.png", import.meta.url).href, + featureTitle: new URL("@/assets/images/subscription/subscribe-plan-feature-title-icon.png", import.meta.url).href, +}; function getPlanKey(plan: ProductPlan, index: number): string { if (plan.id != null && plan.id !== "") { @@ -66,6 +67,39 @@ function formatPlanTierLabel(plan: ProductPlan): string { return String(coins); } +function formatCurrencyAmount(value: number): string { + if (!Number.isFinite(value)) { + return "0"; + } + + if (Number.isInteger(value)) { + return String(value); + } + + return value.toFixed(2).replace(/\.?0+$/, ""); +} + +function formatPointPriceValue(plan: ProductPlan, price: number): string | null { + const coins = Number(plan.coins); + if (!Number.isFinite(price) || !Number.isFinite(coins) || price <= 0 || coins <= 0) { + return null; + } + + return formatCurrencyAmount((price / coins) * 100); +} + +function getNewUserLabel(value: ProductPlanCustomInfo["new_user"], fallback: string): string { + if (typeof value === "string") { + return value.trim(); + } + + if (value === true) { + return fallback; + } + + return ""; +} + /** 去掉 mark/title/section 标签,保留纯文本 */ function stripPlanInlineTags(text: string): string { return text.replace(PLAN_INLINE_TAG_RE, "").trim(); @@ -135,7 +169,7 @@ function parseMarkSegments(text: string): PlanDescriptionSegment[] { /** 将描述拆成权益列表行 */ function splitPlanDescriptionLines(description: string): string[] { - return description.split("\n").filter((l) => l.trim() !== ""); + return description.split(/\n|\\n/).filter((l) => l.trim() !== ""); } /** 套餐描述行:解析 mark 片段;分组标题行解析外壳内的 mark */ @@ -158,12 +192,10 @@ export default function SubscribePlanList({ embedded = false, onBillingCycleChan const { t } = useTranslation(); const user = useUserStore((s) => s.user); const serviceQrCode = useParamConfigStore((s) => s.config?.systemConfig?.serviceQrCode ?? ""); - const memberLevelMap = useMemberLevelStore((s) => s.memberLevelMap); const [billingCycle, setBillingCycleState] = useState("monthly"); const [planList, setPlanList] = useState([]); const [selectedPlanKeyByGroup, setSelectedPlanKeyByGroup] = useState>({}); - const [futureCreatorModalOpen, setFutureCreatorModalOpen] = useState(false); const userMemberLevel = Number(user?.memberLevel ?? 0); // 拉取套餐列表 @@ -223,13 +255,7 @@ export default function SubscribePlanList({ embedded = false, onBillingCycleChan void openPaySubscribeDialog({ type: "subscribe", plan }); }, []); - const openFutureCreatorModal = useCallback(() => { - setFutureCreatorModalOpen(true); - }, []); - const joinCls = (...parts: Array) => parts.filter(Boolean).join(" "); - const closeIconSrc = new URL("@/assets/images/common/icons/close.png", import.meta.url).href; - const wechatIconSrc = new URL("@/assets/images/common/payments/wxpay-logo.png", import.meta.url).href; return (
@@ -284,34 +310,37 @@ export default function SubscribePlanList({ embedded = false, onBillingCycleChan const price = Number(plan.price ?? 0) / 100; // 价格单位为分,需要除以100转换为元 const original = Number(plan.original_price_amount) > 0 ? Number(plan.original_price_amount) : null; // 原价 const level = Number(plan.level ?? -1); // 会员等级 - const titleBadges: Array<{ type: string; text: string }> = []; // - if (plan.recommended) titleBadges.push({ type: "recommended", text: t("subscribe.recommended_plan") }); - if (plan.custom_info?.rightTags) titleBadges.push({ type: "top", text: plan.custom_info.rightTags }); - const isLevel4Plan = level === 4; const isFreePlan = price <= 0; - const isPurchasedLevel4Plan = isLevel4Plan && userMemberLevel === 4; // 已购买4级会员 Future Creator 教学项目 + const isPurchasedLevel4Plan = isLevel4Plan && userMemberLevel === 4; // 已购买4级会员 Future Creator 课程 const ctaDisabled = isPurchasedLevel4Plan ? false : userMemberLevel === level || userMemberLevel > level; // 按钮是否禁用 - const userMemberLevelLabel = getMemberLevelLabel(memberLevelMap, userMemberLevel, t); const ctaLabel = isPurchasedLevel4Plan ? t("subscribe.contact_service") : userMemberLevel === level // 当前会员等级 ? t("subscribe.current_plan") : userMemberLevel > level // 当前会员等级大于计划等级 - ? t("subscribe.already_member_level", { level: userMemberLevelLabel }) + ? t("subscribe.already_member_level", { level: t(memberLevelMap[userMemberLevel] ?? memberLevelMap[0]) }) : t("subscribe.subscribe_now"); // 订阅按钮文本 // 点击CTA按钮 const handleCtaClick = () => { - if (isPurchasedLevel4Plan) { - openFutureCreatorModal(); - return; - } showPaySubscribe(plan); }; // 渲染描述段落 - const priceUnit = isLevel4Plan ? t("subscribe.per_100_days") : plan.planCategory === "yearly" ? t("subscribe.per_year") : t("subscribe.per_month"); - const pointsPeriod = isLevel4Plan ? t("subscribe.points_granted_once") : plan.planCategory === "yearly" ? (plan.pointsGrantMode === "monthly" ? t("subscribe.points_period_monthly") : t("subscribe.points_period_yearly")) : t("subscribe.points_period_monthly"); + const priceUnit = plan.planCategory === "yearly" ? t("subscribe.per_year") : t("subscribe.per_month"); + const pointsPeriod = plan.planCategory === "yearly" ? (plan.pointsGrantMode === "monthly" ? t("subscribe.points_period_monthly") : t("subscribe.points_period_yearly")) : t("subscribe.points_period_monthly"); + const pointPriceValue = formatPointPriceValue(plan, price); + const pointPriceText = plan.custom_info?.point_amount ?? (pointPriceValue ? t("subscribe.plan_point_price", { value: pointPriceValue }) : ""); + const discountText = price > 0 ? (plan.custom_info?.discount_info ?? t("subscribe.plan_default_discount")) : ""; + const descriptionLines = splitPlanDescriptionLines(plan.description || ""); + const goalTitle = plan.custom_info?.goal_title?.trim() ?? ""; + const goalDescriptionLines = splitPlanDescriptionLines(plan.custom_info?.goal_description ?? ""); + const showPlanGoal = goalTitle !== "" || goalDescriptionLines.length > 0; + const featureTitle = plan.custom_info?.feature_title?.trim() ?? ""; + const newUserLabel = getNewUserLabel(plan.custom_info?.new_user, t("subscribe.plan_new_user")); + const isNewUserPlan = newUserLabel !== ""; + const recommendedLabel = plan.recommended === true && !isNewUserPlan ? t("subscribe.plan_recommended") : ""; + const isRecommendedPlan = recommendedLabel !== ""; const renderDescSegments = (line: string) => getPlanDescriptionSegments(line).map((seg, segIdx) => ( @@ -319,188 +348,173 @@ export default function SubscribePlanList({ embedded = false, onBillingCycleChan )); - return ( -
-
-
-
-
- {price > 0 ? : null} - {plan.title} -
- {titleBadges.length > 0 ? ( -
- {titleBadges.map((badge) => ( - - {badge.text} - - ))} -
- ) : null} + const planContent = ( + <> +
+
+
+ {plan.title}
+ {discountText ? {discountText} : null} +
-
-
- - {price} - {priceUnit} - {original != null && original > price ? ¥{original} : null} - {plan.custom_info?.discount_info && price > 0 ? {plan.custom_info.discount_info} : null} -
- {plan.custom_info?.point_amount ?

{plan.custom_info.point_amount}

: null} +
+
+ + {formatCurrencyAmount(price)} + {priceUnit} + {original != null && original > price ? ¥{original} : null}
+ {pointPriceText ?

{pointPriceText}

: null} +
- {price > 0 && plan.coins != null ? ( -
- - - {plan.coins} - - {t("subscribe.points_unit")} - {pointsPeriod} - + {price > 0 && plan.coins != null ? ( +
+ + + {plan.coins} + + {t("subscribe.points_unit")} + {pointsPeriod} + +
+ ) : null} + + + + {planGroup.plans.length > 1 ? ( +
+
+ {planGroup.plans.map((item) => { + const active = item.key === selectedPlanItem.key; + + return ( + + ); + })}
- ) : null} - - - - {planGroup.plans.length > 1 ? ( -
-
- {planGroup.plans.map((item) => { - const active = item.key === selectedPlanItem.key; - - return ( - - ); - })} -
-
-
+
+
- ) : null} +
+ ) : null} + + {plan.custom_info?.bonus_gift ? ( +
+ + {t("subscribe.bonus_gift_prefix")} + {plan.custom_info.bonus_gift} + +
+ ) : null} +
- {plan.custom_info?.illustrator ? ( -
- - {t("subscribe.commission_highlight")} - {plan.custom_info.illustrator} - - {t("subscribe.limited_time_badge")} -
- ) : null} -
+
+ {showPlanGoal ? ( +
+ {goalTitle ? ( +
+ + {goalTitle} +
+ ) : null} + {goalDescriptionLines.length > 0 ? ( +

+ {goalDescriptionLines.map((line, lineIdx) => ( + {line} + ))} +

+ ) : null} +
+ ) : null} + + {descriptionLines.length > 0 ? ( +
+ {featureTitle ? ( + <> + + ) : null} +
+ + ); + const highlightLabel = isNewUserPlan ? newUserLabel : recommendedLabel; + const highlightClassName = isNewUserPlan ? "plan_item__new-user-label" : isRecommendedPlan ? "plan_item__recommended-label" : ""; -
-
    - {splitPlanDescriptionLines(plan.description || "").map((line, lineIdx) => - isSectionLine(line) ? ( -
  • - {renderDescSegments(line)} -
  • - ) : ( -
  • - - {renderDescSegments(line)} -
  • - ), - )} -
-
+ return ( +
+
+ {highlightLabel ? ( +
+
{highlightLabel}
+
{planContent}
+
+ ) : ( + planContent + )}
); })}
- - {futureCreatorModalOpen - ? createPortal( -
{ - setFutureCreatorModalOpen(false); - }} - > - , - document.body, - ) - : null}
); } diff --git a/src/dialog/pay-subscribe/index.tsx b/src/dialog/pay-subscribe/index.tsx index 7e4f0da..4b30808 100644 --- a/src/dialog/pay-subscribe/index.tsx +++ b/src/dialog/pay-subscribe/index.tsx @@ -11,7 +11,7 @@ import { getToken } from "@/utils/auth"; import { useUserStore } from "@/store/useUserStore"; import { checkGatewayPayment, checkGatewayWxPayment, checkPointsPayment, checkPointsWxPayment, createPointsOrder, createPointsOrderByWxPay, createSubscriptionOrder, createSubscriptionOrderByWxPay } from "@/api/api"; import type { PaymentOrderData, PaymentStatusData, ProductPlan } from "@/api/api"; -import { openLatestPrivacyPolicyLink, PRIVACY_POLICY_KEY_FUTURE_CREATOR, PRIVACY_POLICY_KEY_PAYMENT_TERMS } from "@/utils/openLatestPrivacyPolicyLink"; +import { openLatestPrivacyPolicyLink, PRIVACY_POLICY_KEY_PAYMENT_TERMS } from "@/utils/openLatestPrivacyPolicyLink"; import "./index.scss"; type PayType = "subscribe" | "points"; @@ -152,7 +152,6 @@ export default function PaySubscribe({ onClose, type, plan, afterPaySuccess }: P }, [payType, plan?.name, plan?.title, t]); const originalPrice = Number(plan?.original_price_amount ?? 0); const discountInfo = plan?.custom_info?.discount_info; - const isFutureCreatorPlan = Number(plan?.level ?? 0) === 4; /** 支付宝始终蒙层;微信仅首次同意前蒙层 */ const showAgreementOverlay = paymentMethod === "ALIPAY" || !wxAgreed; @@ -541,29 +540,16 @@ export default function PaySubscribe({ onClose, type, plan, afterPaySuccess }: P {t("subscribe.read_before_pay")}
- {isFutureCreatorPlan ? ( - { - e.preventDefault(); - void openLatestPrivacyPolicyLink(PRIVACY_POLICY_KEY_FUTURE_CREATOR); - }} - > - {t("subscribe.future_creator_payment_terms")} - - ) : ( - { - e.preventDefault(); - void openLatestPrivacyPolicyLink(PRIVACY_POLICY_KEY_PAYMENT_TERMS); - }} - > - {t("subscribe.payment_terms")} - - )} + { + e.preventDefault(); + void openLatestPrivacyPolicyLink(PRIVACY_POLICY_KEY_PAYMENT_TERMS); + }} + > + {t("subscribe.payment_terms")} +

); diff --git a/src/locales/en-US.json b/src/locales/en-US.json index 640f8d0..a0f28a3 100644 --- a/src/locales/en-US.json +++ b/src/locales/en-US.json @@ -581,7 +581,6 @@ "subscribe.creator_member": "Creator", "subscribe.per_month": "/month", "subscribe.per_year": "/year", - "subscribe.per_100_days": "100 days", "subscribe.current_plan": "Current plan", "subscribe.subscribe_now": "Sponsor now", "subscribe.contact_service": "Purchased — contact support", @@ -593,7 +592,6 @@ "subscribe.yuan": " yuan", "subscribe.read_before_pay": "Please read before payment", "subscribe.payment_terms": "'Popi.art paid service agreement '", - "subscribe.future_creator_payment_terms": "Popi Future Creator Plan User Service Agreement", "subscribe.agree_and_pay": "Agree and Pay", "subscribe.please_complete_wechat_payment": "Please complete payment in WeChat", "subscribe.please_complete_alipay_payment": "Please complete payment in Alipay", @@ -610,17 +608,13 @@ "subscribe.promo_carousel_label": "Membership benefit campaigns", "subscribe.billing_badge_hot": "HOT", "subscribe.billing_badge_value": "Best value", - "subscribe.recommended_plan": "Recommended", "subscribe.discount_off_8": "OFF 8%", "subscribe.discount_off_15": "OFF 15%", "subscribe.other_cycle_unavailable": "Other billing cycles are not available yet. Please choose single purchase", "subscribe.points_unit": "points /", "subscribe.points_period_monthly": "month", "subscribe.points_period_yearly": "year", - "subscribe.points_granted_once": "Granted in full once", - "subscribe.commission_highlight": "1-on-1 artist commission", - "subscribe.commission_suffix": ": character art ×1", - "subscribe.limited_time_badge": "Limited", + "subscribe.bonus_gift_prefix": "Bonus: ", "subscribe.pay_result_timeout": "Payment check timed out. Please check your order or account later", "subscribe.pay_notice_timeout_title": "Result not confirmed yet", "subscribe.pay_notice_missing_plan_title": "Cannot start payment", @@ -635,6 +629,14 @@ "subscribe.buy_now": "Buy now", "subscribe.points_package": "Points package", "subscribe.already_member_level": "You are already \"{{level}}\"", + "subscribe.plan_cta_goal": "Finish the first work", + "subscribe.plan_default_discount": "30% off limited offer", + "subscribe.plan_point_price": "100 points ≈ ¥{{value}}", + "subscribe.plan_new_user": "Best for beginners", + "subscribe.plan_recommended": "Recommended", + "subscribe.plan_goal_title": "More trials / steadier output", + "subscribe.plan_goal_line1": "Combine images, characters, videos,", + "subscribe.plan_goal_line2": "and shots into the first publishable work", "subscribe.qa_title": "FAQ", "subscribe.qa_subtitle": "Popi points and content rights", "subscribe.qa_footnote_line1": "Note: The rules above are for FAQ display on the official site. Actual terms follow", diff --git a/src/locales/zh-CN.json b/src/locales/zh-CN.json index bba46c3..a5271b7 100644 --- a/src/locales/zh-CN.json +++ b/src/locales/zh-CN.json @@ -581,7 +581,6 @@ "subscribe.creator_member": "Creator", "subscribe.per_month": "每月", "subscribe.per_year": "每年", - "subscribe.per_100_days": "100天", "subscribe.current_plan": "当前计划", "subscribe.subscribe_now": "立即开通", "subscribe.contact_service": "已购买,联系客服", @@ -593,7 +592,6 @@ "subscribe.yuan": "元", "subscribe.read_before_pay": "支付前请阅读", "subscribe.payment_terms": "《“Popi.art”付费服务协议》", - "subscribe.future_creator_payment_terms": "Popi Future Creator 计划用户服务协议", "subscribe.agree_and_pay": "同意并支付", "subscribe.please_complete_wechat_payment": "请微信扫码完成支付", "subscribe.please_complete_alipay_payment": "请在跳转页面扫码完成支付", @@ -610,17 +608,13 @@ "subscribe.promo_carousel_label": "会员权益活动", "subscribe.billing_badge_hot": "HOT", "subscribe.billing_badge_value": "性价比", - "subscribe.recommended_plan": "主推计划", "subscribe.discount_off_8": "OFF 8%", "subscribe.discount_off_15": "OFF 15%", "subscribe.other_cycle_unavailable": "其他周期暂未开放,请选择单次购买", "subscribe.points_unit": "积分/", "subscribe.points_period_monthly": "每月", "subscribe.points_period_yearly": "每年", - "subscribe.points_granted_once": "一次全额获得", - "subscribe.commission_highlight": "画师一对一约稿", - "subscribe.commission_suffix": ":角色图×1", - "subscribe.limited_time_badge": "限时", + "subscribe.bonus_gift_prefix": "附赠:", "subscribe.pay_result_timeout": "支付结果查询超时,请稍后在订单或账户中查看", "subscribe.pay_notice_timeout_title": "尚未确认结果", "subscribe.pay_notice_missing_plan_title": "无法发起支付", @@ -635,6 +629,14 @@ "subscribe.buy_now": "立即购买", "subscribe.points_package": "积分包", "subscribe.already_member_level": "你已是{{level}}", + "subscribe.plan_cta_goal": "完成第一条作品", + "subscribe.plan_default_discount": "限时7折", + "subscribe.plan_point_price": "每100积分≈¥{{value}}元", + "subscribe.plan_new_user": "新人首选", + "subscribe.plan_recommended": "官方推荐", + "subscribe.plan_goal_title": "多次试错 / 稳定出作品", + "subscribe.plan_goal_line1": "把图片、角色、视频和镜头", + "subscribe.plan_goal_line2": "组合成第一条可发布作品", "subscribe.qa_title": "常见问题", "subscribe.qa_subtitle": "Popi 积分与内容权益规则", "subscribe.qa_footnote_line1": "注:以上规则用于官网常见问题展示,具体执行以", diff --git a/src/locales/zh-TW.json b/src/locales/zh-TW.json index 93d41ae..364667e 100644 --- a/src/locales/zh-TW.json +++ b/src/locales/zh-TW.json @@ -582,7 +582,6 @@ "subscribe.creator_member": "Creator", "subscribe.per_month": "每月", "subscribe.per_year": "每年", - "subscribe.per_100_days": "100天", "subscribe.current_plan": "目前方案", "subscribe.subscribe_now": "立即贊助", "subscribe.contact_service": "已購買,聯絡客服", @@ -594,7 +593,6 @@ "subscribe.yuan": "元", "subscribe.read_before_pay": "支付前請閱讀", "subscribe.payment_terms": "《“Popi.art”付費服務協議》", - "subscribe.future_creator_payment_terms": "Popi Future Creator 計劃用戶服務協議", "subscribe.agree_and_pay": "同意並支付", "subscribe.please_complete_wechat_payment": "請微信掃碼完成支付", "subscribe.please_complete_alipay_payment": "請在跳轉頁面掃碼完成支付", @@ -611,17 +609,13 @@ "subscribe.promo_carousel_label": "會員權益活動", "subscribe.billing_badge_hot": "HOT", "subscribe.billing_badge_value": "性價比", - "subscribe.recommended_plan": "主推計劃", "subscribe.discount_off_8": "OFF 8%", "subscribe.discount_off_15": "OFF 15%", "subscribe.other_cycle_unavailable": "其他週期暫未開放,請選擇單次購買", "subscribe.points_unit": "積分/", "subscribe.points_period_monthly": "每月", "subscribe.points_period_yearly": "每年", - "subscribe.points_granted_once": "一次全額獲得", - "subscribe.commission_highlight": "畫師一對一約稿", - "subscribe.commission_suffix": ":角色圖×1", - "subscribe.limited_time_badge": "限時", + "subscribe.bonus_gift_prefix": "附贈:", "subscribe.pay_result_timeout": "支付結果查詢超時,請稍後在訂單或帳戶中查看", "subscribe.pay_notice_timeout_title": "尚未確認結果", "subscribe.pay_notice_missing_plan_title": "無法發起支付", @@ -636,6 +630,14 @@ "subscribe.buy_now": "立即購買", "subscribe.points_package": "積分包", "subscribe.already_member_level": "你已是{{level}}", + "subscribe.plan_cta_goal": "完成第一條作品", + "subscribe.plan_default_discount": "限時7折", + "subscribe.plan_point_price": "每100積分≈¥{{value}}元", + "subscribe.plan_new_user": "新人首選", + "subscribe.plan_recommended": "官方推薦", + "subscribe.plan_goal_title": "多次試錯 / 穩定出作品", + "subscribe.plan_goal_line1": "把圖片、角色、影片和鏡頭", + "subscribe.plan_goal_line2": "組合成第一條可發布作品", "subscribe.qa_title": "常見問題", "subscribe.qa_subtitle": "Popi 積分與內容權益規則", "subscribe.qa_footnote_line1": "註:以上規則用於官網常見問題展示,具體執行以", diff --git a/src/pages/Teaching/index.scss b/src/pages/Teaching/index.scss index cc62f26..92c2e8f 100644 --- a/src/pages/Teaching/index.scss +++ b/src/pages/Teaching/index.scss @@ -237,9 +237,12 @@ justify-content: center; width: 68px; height: 24px; + padding: 0; + border: 0; border-radius: 100px; background: #ff9a27; color: #ffffff; + cursor: pointer; font-size: 12px; font-weight: 700; line-height: 17px; @@ -247,6 +250,16 @@ backdrop-filter: blur(15px); } + .teachingPage__memberAction:hover, + .teachingPage__memberAction:focus-visible { + background: #ff8a00; + } + + .teachingPage__memberAction:focus-visible { + outline: 2px solid #ffffff; + outline-offset: 2px; + } + .teachingPage__toolbar { display: flex; align-items: center; diff --git a/src/pages/Teaching/index.tsx b/src/pages/Teaching/index.tsx index d5ab639..f95456f 100644 --- a/src/pages/Teaching/index.tsx +++ b/src/pages/Teaching/index.tsx @@ -4,6 +4,7 @@ import { Link } from "react-router-dom"; import { getTeachingCategoryList, getTeachingContentList } from "@/api/api"; import type { TeachingCategoryItem, TeachingContentItem } from "@/api/api"; import { getMemberLevelLabel, hasMemberLevelLabel, ROUTES } from "@/constants"; +import { openSubscribePlanDialog } from "@/dialog"; import { useMemberLevelStore } from "@/store/useMemberLevelStore"; import "./index.scss"; @@ -111,6 +112,10 @@ export default function Teaching() { const shouldShowTeachingStatus = isTeachingListLoading || teachingListError != null || teachingItems.length === 0; const teachingStatusText = isTeachingListLoading ? t("common.loading") : teachingListError?.message || t("pages.teaching.empty"); + const handleOpenSubscribePlan = (): void => { + void openSubscribePlanDialog(); + }; + useEffect(() => { let cancelled = false; @@ -201,40 +206,43 @@ export default function Teaching() { ) : null} - + + ) : ( + + + )}
diff --git a/src/pages/subscribe/index.scss b/src/pages/subscribe/index.scss index db0b4fc..185d09a 100644 --- a/src/pages/subscribe/index.scss +++ b/src/pages/subscribe/index.scss @@ -50,9 +50,9 @@ .subscribe-overview-card { position: relative; display: grid; - grid-template-columns: 146px minmax(0, 518px); + grid-template-columns: 313px minmax(0, 1fr); align-items: start; - gap: 77px; + gap: 20px; height: 132px; padding: 20px 30px; border-radius: 26px; @@ -102,6 +102,7 @@ } .user-plan__meta { + flex: 1 1 auto; min-width: 0; } @@ -109,13 +110,15 @@ display: flex; align-items: center; gap: 5px; + width: 100%; min-width: 0; height: 20px; } .user-plan__name { + flex: 0 0 220px; overflow: hidden; - max-width: 72px; + width: 220px; color: #333333; font-size: 16px; font-weight: 400; @@ -126,6 +129,7 @@ .user-plan__tag { display: inline-flex; + flex: 0 0 auto; align-items: center; justify-content: center; min-width: 39px; @@ -413,8 +417,8 @@ } .subscribe-overview-card { - grid-template-columns: 146px minmax(0, 1fr); - gap: 42px; + grid-template-columns: 313px minmax(0, 1fr); + gap: 20px; } .subscribe-promo-card.subscribe-promo-card_membership { @@ -458,6 +462,11 @@ margin-top: 0; } + .user-plan__name { + flex: 1 1 auto; + width: auto; + } + .points-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 14px; @@ -489,4 +498,4 @@ } } } -} \ No newline at end of file +} diff --git a/src/utils/index.ts b/src/utils/index.ts index 9c956d0..b1ef470 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -12,7 +12,6 @@ export { openLatestPrivacyPolicyLink, PRIVACY_POLICY_KEY_AI_USE_NOTICE, PRIVACY_POLICY_KEY_EVIDENCE_PRESERVATION, - PRIVACY_POLICY_KEY_FUTURE_CREATOR, PRIVACY_POLICY_KEY_MUSIC_USE_NOTICE, PRIVACY_POLICY_KEY_PAYMENT_TERMS, PRIVACY_POLICY_KEY_PRIVACY_POLICY, diff --git a/src/utils/openLatestPrivacyPolicyLink.ts b/src/utils/openLatestPrivacyPolicyLink.ts index 578c48c..aaa8ac0 100644 --- a/src/utils/openLatestPrivacyPolicyLink.ts +++ b/src/utils/openLatestPrivacyPolicyLink.ts @@ -14,8 +14,6 @@ export const PRIVACY_POLICY_KEY_PAYMENT_TERMS = "web.paymentTerms"; export const PRIVACY_POLICY_KEY_EVIDENCE_PRESERVATION = "web.popiartEvidencePreservationAgreement"; /** 内容协议 key:音乐功能使用须知 */ export const PRIVACY_POLICY_KEY_MUSIC_USE_NOTICE = "web.musicUseNotice"; -/** 内容协议 key:Popi Future Creator 计划用户服务协议 */ -export const PRIVACY_POLICY_KEY_FUTURE_CREATOR = "web.futureCreatorUserServiceAgreement"; interface PrivacyPolicyItem { id: number;