@windrun-huaiin/third-ui 7.3.2 → 7.3.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/main/faq.js +12 -0
- package/dist/main/faq.mjs +12 -0
- package/dist/main/gallery.js +12 -0
- package/dist/main/gallery.mjs +12 -0
- package/dist/main/index.d.ts +2 -0
- package/dist/main/index.js +4 -0
- package/dist/main/index.mjs +2 -0
- package/dist/main/money-price/money-price-button.d.ts +2 -0
- package/dist/main/money-price/money-price-button.js +96 -0
- package/dist/main/money-price/money-price-button.mjs +94 -0
- package/dist/main/money-price/money-price-config.d.ts +8 -0
- package/dist/main/money-price/money-price-config.js +223 -0
- package/dist/main/money-price/money-price-config.mjs +219 -0
- package/dist/main/money-price/money-price-interactive.d.ts +2 -0
- package/dist/main/money-price/money-price-interactive.js +293 -0
- package/dist/main/money-price/money-price-interactive.mjs +291 -0
- package/dist/main/money-price/money-price-types.d.ts +116 -0
- package/dist/main/money-price/money-price-types.js +14 -0
- package/dist/main/money-price/money-price-types.mjs +14 -0
- package/dist/main/money-price/money-price.d.ts +2 -0
- package/dist/main/money-price/money-price.js +93 -0
- package/dist/main/money-price/money-price.mjs +91 -0
- package/dist/main/price-plan.js +13 -0
- package/dist/main/price-plan.mjs +13 -0
- package/dist/main/server.d.ts +4 -0
- package/dist/main/server.js +11 -0
- package/dist/main/server.mjs +3 -0
- package/dist/node_modules/.pnpm/cose-base@1.0.3/node_modules/cose-base/cose-base.js +1 -1
- package/dist/node_modules/.pnpm/cose-base@2.2.0/node_modules/cose-base/cose-base.js +1 -1
- package/dist/node_modules/.pnpm/layout-base@1.0.2/node_modules/layout-base/layout-base.js +1 -1
- package/dist/node_modules/.pnpm/layout-base@2.0.1/node_modules/layout-base/layout-base.js +1 -1
- package/package.json +1 -1
- package/src/main/index.ts +5 -1
- package/src/main/money-price/money-price-button.tsx +105 -0
- package/src/main/money-price/money-price-config.ts +229 -0
- package/src/main/money-price/money-price-interactive.tsx +356 -0
- package/src/main/money-price/money-price-types.ts +138 -0
- package/src/main/money-price/money-price.tsx +307 -0
- package/src/main/server.ts +25 -1
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
+
import { GradientButton } from '@third-ui/fuma/mdx';
|
|
3
|
+
import { cn } from '@windrun-huaiin/lib/utils';
|
|
4
|
+
import { getTranslations } from 'next-intl/server';
|
|
5
|
+
import { getActiveProviderConfig } from './money-price-config';
|
|
6
|
+
import { MoneyPriceInteractive } from './money-price-interactive';
|
|
7
|
+
import type { MoneyPriceData, MoneyPriceProps } from './money-price-types';
|
|
8
|
+
|
|
9
|
+
export async function MoneyPrice({
|
|
10
|
+
locale,
|
|
11
|
+
config,
|
|
12
|
+
upgradeApiEndpoint,
|
|
13
|
+
signInPath,
|
|
14
|
+
sectionClassName
|
|
15
|
+
}: MoneyPriceProps) {
|
|
16
|
+
const t = await getTranslations({ locale, namespace: 'moneyPrice' });
|
|
17
|
+
|
|
18
|
+
const data: MoneyPriceData = {
|
|
19
|
+
title: t('title'),
|
|
20
|
+
subtitle: t('subtitle'),
|
|
21
|
+
billingSwitch: t.raw('billingSwitch') as {
|
|
22
|
+
options: Array<{
|
|
23
|
+
key: string;
|
|
24
|
+
name: string;
|
|
25
|
+
unit: string;
|
|
26
|
+
discountText: string;
|
|
27
|
+
subTitle?: string;
|
|
28
|
+
}>;
|
|
29
|
+
defaultKey: string;
|
|
30
|
+
},
|
|
31
|
+
plans: t.raw('plans') as Array<any>,
|
|
32
|
+
buttonTexts: t.raw('buttonTexts') as {
|
|
33
|
+
getStarted: string;
|
|
34
|
+
getPro: string;
|
|
35
|
+
getUltra: string;
|
|
36
|
+
currentPlan: string;
|
|
37
|
+
upgrade: string;
|
|
38
|
+
},
|
|
39
|
+
currency: config.display.currency
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
// 获取激活的支付供应商配置
|
|
43
|
+
const providerConfig = getActiveProviderConfig(config);
|
|
44
|
+
const minPlanFeaturesCount = config.display.minFeaturesCount;
|
|
45
|
+
|
|
46
|
+
// 使用默认计费类型进行静态渲染
|
|
47
|
+
const defaultBilling = data.billingSwitch.defaultKey;
|
|
48
|
+
const defaultBillingDisplay = data.billingSwitch.options.find(
|
|
49
|
+
(opt: any) => opt.key === defaultBilling
|
|
50
|
+
) || data.billingSwitch.options[0];
|
|
51
|
+
|
|
52
|
+
// 计算特性数量
|
|
53
|
+
const maxFeaturesCount = Math.max(
|
|
54
|
+
...data.plans.map((plan: any) => plan.features?.length || 0),
|
|
55
|
+
minPlanFeaturesCount || 0
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
// 处理卡片高度对齐
|
|
59
|
+
const getFeatureRows = (plan: any) => {
|
|
60
|
+
const features = plan.features || [];
|
|
61
|
+
const filled = [...features];
|
|
62
|
+
while (filled.length < maxFeaturesCount) filled.push(null);
|
|
63
|
+
return filled;
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
// 静态价格渲染逻辑
|
|
67
|
+
function renderPrice(plan: any, billingKey = data.billingSwitch.defaultKey) {
|
|
68
|
+
const productConfig = providerConfig.products[plan.key as 'free' | 'pro' | 'ultra'];
|
|
69
|
+
const pricing = productConfig.plans[billingKey as 'monthly' | 'yearly'];
|
|
70
|
+
const currentBillingDisplay = data.billingSwitch.options.find(
|
|
71
|
+
(opt: any) => opt.key === billingKey
|
|
72
|
+
) || defaultBillingDisplay;
|
|
73
|
+
const billingSubTitle = currentBillingDisplay?.subTitle || '';
|
|
74
|
+
|
|
75
|
+
// 免费计划
|
|
76
|
+
if (pricing.amount === 0) {
|
|
77
|
+
return (
|
|
78
|
+
<div className="flex flex-col items-start w-full" data-price-container={plan.key}>
|
|
79
|
+
<div className="flex items-end gap-2">
|
|
80
|
+
<span className="text-4xl font-extrabold text-gray-900 dark:text-gray-100" data-price-value={plan.key}>
|
|
81
|
+
Free
|
|
82
|
+
</span>
|
|
83
|
+
</div>
|
|
84
|
+
<div className="flex items-center gap-2 min-h-[24px] mt-1">
|
|
85
|
+
<span className={cn('text-xs text-gray-700 dark:text-gray-300 font-medium',
|
|
86
|
+
plan.showBillingSubTitle === false && 'opacity-0 select-none')}
|
|
87
|
+
data-price-subtitle={plan.key}>
|
|
88
|
+
{plan.showBillingSubTitle === false ? '' : billingSubTitle}
|
|
89
|
+
</span>
|
|
90
|
+
</div>
|
|
91
|
+
</div>
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// 付费计划
|
|
96
|
+
const hasDiscount = pricing.discountPercent && pricing.discountPercent > 0;
|
|
97
|
+
const unit = currentBillingDisplay.unit || '';
|
|
98
|
+
let discountText = '';
|
|
99
|
+
|
|
100
|
+
if (hasDiscount && currentBillingDisplay.discountText) {
|
|
101
|
+
discountText = currentBillingDisplay.discountText.replace(
|
|
102
|
+
'{percent}',
|
|
103
|
+
String(pricing.discountPercent)
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return (
|
|
108
|
+
<div className="flex flex-col items-start w-full" data-price-container={plan.key}>
|
|
109
|
+
<div className="flex items-end gap-2">
|
|
110
|
+
<span className="text-4xl font-extrabold text-gray-900 dark:text-gray-100" data-price-value={plan.key}>
|
|
111
|
+
{data.currency}{pricing.amount}
|
|
112
|
+
</span>
|
|
113
|
+
<span className="text-lg text-gray-700 dark:text-gray-300 font-medium mb-1" data-price-unit={plan.key}>
|
|
114
|
+
{unit}
|
|
115
|
+
</span>
|
|
116
|
+
</div>
|
|
117
|
+
<div className="flex items-center gap-2 min-h-[24px] mt-1">
|
|
118
|
+
{hasDiscount && pricing.originalAmount && (
|
|
119
|
+
<>
|
|
120
|
+
<span className="text-base text-gray-400 line-through" data-price-original={plan.key}>
|
|
121
|
+
{data.currency}{pricing.originalAmount}
|
|
122
|
+
</span>
|
|
123
|
+
{discountText && (
|
|
124
|
+
<span className="px-2 py-0.5 text-xs rounded bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-200 font-semibold align-middle"
|
|
125
|
+
data-price-discount={plan.key}>
|
|
126
|
+
{discountText}
|
|
127
|
+
</span>
|
|
128
|
+
)}
|
|
129
|
+
</>
|
|
130
|
+
)}
|
|
131
|
+
<span className={cn('text-xs text-gray-700 dark:text-gray-300 font-medium',
|
|
132
|
+
plan.showBillingSubTitle === false && 'opacity-0 select-none')}
|
|
133
|
+
data-price-subtitle={plan.key}>
|
|
134
|
+
{plan.showBillingSubTitle === false ? '' : billingSubTitle}
|
|
135
|
+
</span>
|
|
136
|
+
</div>
|
|
137
|
+
</div>
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return (
|
|
142
|
+
<section id="money-pricing" className={cn("px-4 py-10 md:px-16 md:py-16 mx-auto max-w-7xl scroll-mt-10", sectionClassName)}>
|
|
143
|
+
{/* 标题和副标题 */}
|
|
144
|
+
<h2 className="text-3xl md:text-4xl font-bold text-center mb-3">
|
|
145
|
+
{data.title}
|
|
146
|
+
</h2>
|
|
147
|
+
<p className="text-center text-gray-600 dark:text-gray-400 mb-8 text-base md:text-lg mx-auto">
|
|
148
|
+
{data.subtitle}
|
|
149
|
+
</p>
|
|
150
|
+
|
|
151
|
+
{/* 计费切换按钮 */}
|
|
152
|
+
<div className="flex flex-col items-center">
|
|
153
|
+
<div className="flex items-center relative mb-3">
|
|
154
|
+
<div className="flex bg-white dark:bg-gray-900 border border-gray-300 dark:border-gray-700 rounded-full p-1" data-billing-switch>
|
|
155
|
+
<button
|
|
156
|
+
className={cn(
|
|
157
|
+
'min-w-[120px] px-6 py-2 font-medium transition text-lg relative',
|
|
158
|
+
data.billingSwitch.defaultKey === 'monthly'
|
|
159
|
+
? 'text-white bg-gradient-to-r from-purple-400 to-pink-500 hover:from-purple-500 hover:to-pink-600 dark:from-purple-500 dark:to-pink-600 dark:hover:from-purple-600 rounded-full shadow-sm'
|
|
160
|
+
: 'text-gray-800 dark:text-gray-200 hover:text-gray-900 dark:hover:text-gray-100 rounded-full'
|
|
161
|
+
)}
|
|
162
|
+
data-billing-button="monthly"
|
|
163
|
+
type="button"
|
|
164
|
+
>
|
|
165
|
+
{(data.billingSwitch.options.find((opt: any) => opt.key === 'monthly')?.name) || 'Monthly'}
|
|
166
|
+
</button>
|
|
167
|
+
<button
|
|
168
|
+
className={cn(
|
|
169
|
+
'min-w-[120px] px-6 py-2 font-medium transition text-lg relative',
|
|
170
|
+
data.billingSwitch.defaultKey === 'yearly'
|
|
171
|
+
? 'text-white bg-gradient-to-r from-purple-400 to-pink-500 hover:from-purple-500 hover:to-pink-600 dark:from-purple-500 dark:to-pink-600 dark:hover:from-purple-600 rounded-full shadow-sm'
|
|
172
|
+
: 'text-gray-800 dark:text-gray-200 hover:text-gray-900 dark:hover:text-gray-100 rounded-full'
|
|
173
|
+
)}
|
|
174
|
+
data-billing-button="yearly"
|
|
175
|
+
type="button"
|
|
176
|
+
>
|
|
177
|
+
{(data.billingSwitch.options.find((opt: any) => opt.key === 'yearly')?.name) || 'Yearly'}
|
|
178
|
+
</button>
|
|
179
|
+
</div>
|
|
180
|
+
</div>
|
|
181
|
+
|
|
182
|
+
{/* 折扣信息 - 默认计费的静态渲染 */}
|
|
183
|
+
<div className="h-8 flex items-center justify-center mb-3" data-discount-info>
|
|
184
|
+
{(() => {
|
|
185
|
+
const opt = data.billingSwitch.options.find((opt: any) => opt.key === data.billingSwitch.defaultKey);
|
|
186
|
+
|
|
187
|
+
// 检查默认计费类型是否有折扣
|
|
188
|
+
let hasDiscount = false;
|
|
189
|
+
let discountPercent = 0;
|
|
190
|
+
|
|
191
|
+
['pro', 'ultra'].forEach(planKey => {
|
|
192
|
+
const product = providerConfig.products[planKey as 'pro' | 'ultra'];
|
|
193
|
+
const pricing = product.plans[data.billingSwitch.defaultKey as 'monthly' | 'yearly'];
|
|
194
|
+
if (pricing.discountPercent) {
|
|
195
|
+
hasDiscount = true;
|
|
196
|
+
discountPercent = pricing.discountPercent;
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
if (!(opt && hasDiscount && opt.discountText)) return null;
|
|
201
|
+
|
|
202
|
+
return (
|
|
203
|
+
<span className="px-2 py-1 text-xs rounded bg-yellow-100 text-yellow-800 font-semibold align-middle text-center inline-flex items-center justify-center whitespace-nowrap">
|
|
204
|
+
{opt.discountText.replace(
|
|
205
|
+
'{percent}',
|
|
206
|
+
String(discountPercent)
|
|
207
|
+
)}
|
|
208
|
+
</span>
|
|
209
|
+
);
|
|
210
|
+
})()}
|
|
211
|
+
</div>
|
|
212
|
+
</div>
|
|
213
|
+
|
|
214
|
+
{/* 价格卡片区域 */}
|
|
215
|
+
<div className="grid grid-cols-1 md:grid-cols-3 gap-8">
|
|
216
|
+
{data.plans.map((plan: any, _idx: number) => (
|
|
217
|
+
<div
|
|
218
|
+
key={plan.key}
|
|
219
|
+
data-price-plan={plan.key}
|
|
220
|
+
className={cn(
|
|
221
|
+
'flex flex-col bg-white dark:bg-gray-800/60 rounded-2xl border border-gray-300 dark:border-[#7c3aed40] transition p-8 h-full shadow-sm dark:shadow-none',
|
|
222
|
+
'hover:border-2 hover:border-purple-500',
|
|
223
|
+
'focus-within:border-2 focus-within:border-purple-500'
|
|
224
|
+
)}
|
|
225
|
+
style={{ minHeight: maxFeaturesCount*100 }}
|
|
226
|
+
>
|
|
227
|
+
{/* 标题和标签 */}
|
|
228
|
+
<div className="flex items-center gap-2 mb-2">
|
|
229
|
+
<span className="text-xl font-bold text-gray-900 dark:text-gray-100">{plan.title}</span>
|
|
230
|
+
{plan.titleTags && plan.titleTags.map((tag: string, i: number) => (
|
|
231
|
+
<span key={i} className="px-2 py-0.5 text-xs rounded bg-orange-100 text-orange-800 dark:bg-orange-900 dark:text-orange-200 font-semibold align-middle">
|
|
232
|
+
{tag}
|
|
233
|
+
</span>
|
|
234
|
+
))}
|
|
235
|
+
</div>
|
|
236
|
+
|
|
237
|
+
{/* 价格和单位/折扣 */}
|
|
238
|
+
{renderPrice(plan)}
|
|
239
|
+
|
|
240
|
+
{/* 特性列表 */}
|
|
241
|
+
<ul className="flex-1 mb-6 mt-4">
|
|
242
|
+
{getFeatureRows(plan).map((feature: any, i: number) => (
|
|
243
|
+
<li key={i} className="flex items-center gap-2 mb-2 min-h-[28px]" data-feature-item={`${plan.key}-${i}`}>
|
|
244
|
+
{/* 图标 */}
|
|
245
|
+
{feature ? (
|
|
246
|
+
<span className="inline-flex items-center justify-center w-5 h-5 rounded-full bg-green-100 text-green-700 dark:bg-green-900 dark:text-green-200 mr-1">
|
|
247
|
+
{feature.icon ? <span>{feature.icon}</span> : <span className="font-bold">✓</span>}
|
|
248
|
+
</span>
|
|
249
|
+
) : (
|
|
250
|
+
<span className="inline-flex items-center justify-center w-5 h-5 rounded-full mr-1"> </span>
|
|
251
|
+
)}
|
|
252
|
+
{/* 标签 */}
|
|
253
|
+
{feature && feature.tag && (
|
|
254
|
+
<span className="px-1 py-0.5 text-[6px] rounded bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200 font-semibold align-middle">
|
|
255
|
+
{feature.tag}
|
|
256
|
+
</span>
|
|
257
|
+
)}
|
|
258
|
+
{/* 描述 + 提示 */}
|
|
259
|
+
{feature ? (
|
|
260
|
+
<span className="relative group cursor-pointer text-sm text-gray-800 dark:text-gray-200">
|
|
261
|
+
{feature.description}
|
|
262
|
+
{feature.tooltip && (
|
|
263
|
+
<span
|
|
264
|
+
className="ml-1 align-middle inline-flex"
|
|
265
|
+
data-tooltip-trigger={`${plan.key}-${i}`}
|
|
266
|
+
data-tooltip-content={feature.tooltip}
|
|
267
|
+
>
|
|
268
|
+
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
269
|
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2}
|
|
270
|
+
d="M8.228 9c.549-1.165 2.03-2 3.772-2 2.21 0 4 1.343 4 3 0 1.4-1.278 2.575-3.006 2.907-.542.104-.994.54-.994 1.093m0 3h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
|
271
|
+
</svg>
|
|
272
|
+
</span>
|
|
273
|
+
)}
|
|
274
|
+
</span>
|
|
275
|
+
) : (
|
|
276
|
+
<span> </span>
|
|
277
|
+
)}
|
|
278
|
+
</li>
|
|
279
|
+
))}
|
|
280
|
+
</ul>
|
|
281
|
+
|
|
282
|
+
{/* 占位符,确保卡片高度一致 */}
|
|
283
|
+
<div className="flex-1" />
|
|
284
|
+
|
|
285
|
+
{/* 按钮占位,客户端会替换 */}
|
|
286
|
+
<div data-button-placeholder={plan.key}>
|
|
287
|
+
<GradientButton
|
|
288
|
+
title={data.buttonTexts.getStarted}
|
|
289
|
+
disabled={true}
|
|
290
|
+
align="center"
|
|
291
|
+
className="w-full"
|
|
292
|
+
/>
|
|
293
|
+
</div>
|
|
294
|
+
</div>
|
|
295
|
+
))}
|
|
296
|
+
</div>
|
|
297
|
+
|
|
298
|
+
{/* 客户端增强组件 */}
|
|
299
|
+
<MoneyPriceInteractive
|
|
300
|
+
data={data}
|
|
301
|
+
config={config}
|
|
302
|
+
upgradeApiEndpoint={upgradeApiEndpoint}
|
|
303
|
+
signInPath={signInPath}
|
|
304
|
+
/>
|
|
305
|
+
</section>
|
|
306
|
+
);
|
|
307
|
+
}
|
package/src/main/server.ts
CHANGED
|
@@ -7,4 +7,28 @@ export * from './faq';
|
|
|
7
7
|
export * from './seo-content';
|
|
8
8
|
export * from './cta';
|
|
9
9
|
export * from './footer';
|
|
10
|
-
export * from './price-plan';
|
|
10
|
+
export * from './price-plan';
|
|
11
|
+
|
|
12
|
+
// Money Price Server Component and Types
|
|
13
|
+
export { MoneyPrice } from './money-price/money-price';
|
|
14
|
+
export {
|
|
15
|
+
moneyPriceConfig,
|
|
16
|
+
getActiveProviderConfig,
|
|
17
|
+
getProductPricing
|
|
18
|
+
} from './money-price/money-price-config';
|
|
19
|
+
|
|
20
|
+
// Money Price Types (shared between server and client)
|
|
21
|
+
export type {
|
|
22
|
+
MoneyPriceConfig,
|
|
23
|
+
MoneyPriceProps,
|
|
24
|
+
MoneyPriceInteractiveProps,
|
|
25
|
+
MoneyPriceButtonProps,
|
|
26
|
+
MoneyPriceData,
|
|
27
|
+
PaymentProvider,
|
|
28
|
+
PaymentProviderConfig,
|
|
29
|
+
EnhancePricePlan,
|
|
30
|
+
ProductConfig,
|
|
31
|
+
UserContext
|
|
32
|
+
} from './money-price/money-price-types';
|
|
33
|
+
|
|
34
|
+
export { UserState } from './money-price/money-price-types';
|