This commit is contained in:
Alex-larget
2026-03-17 15:17:33 +08:00
parent b57b132226
commit 88915276d1
8 changed files with 778 additions and 235 deletions

View File

@@ -9,8 +9,8 @@ const { checkAndExecute } = require('./utils/ruleEngine.js')
App({
globalData: {
// API 基础地址(切换环境时注释/取消注释)
baseUrl: 'https://soulapi.quwanzhi.com',
// baseUrl: 'http://localhost:8080', // 本地调试
// baseUrl: 'https://soulapi.quwanzhi.com',
baseUrl: 'http://localhost:8080', // 本地调试
// baseUrl: 'https://souldev.quwanzhi.com', // 测试环境

View File

@@ -11,7 +11,9 @@ Page({
detail: null,
loading: true,
paying: false,
isInitiator: false // 是否发起人发起人看到「分享给好友」UI好友看到「帮他付款」
isInitiator: false,
requesterMsg: '',
amountDisplay: '0.00' // 预格式化金额WXML 中 toFixed 可能不生效
},
onLoad(options) {
@@ -35,7 +37,13 @@ Page({
if (res && res.success) {
const myId = app.globalData.userInfo?.id || ''
const isInitiator = !!myId && res.initiatorUserId === myId
this.setData({ detail: res, loading: false, isInitiator })
const requesterMsg = isInitiator
? '分享给好友,好友打开后即可为你完成支付。'
: (res.initiatorMsg || `" 请帮我代付「${res.sectionTitle || res.description || '该商品'}」,非常感谢! "`)
const amountDisplay = (res.amount != null && res.amount !== '')
? Number(res.amount).toFixed(2)
: '0.00'
this.setData({ detail: res, loading: false, isInitiator, requesterMsg, amountDisplay })
} else {
this.setData({ loading: false })
wx.showToast({ title: res?.error || '加载失败', icon: 'none' })
@@ -47,14 +55,24 @@ Page({
},
async doPay() {
if (!app.globalData.isLoggedIn || !app.globalData.userInfo) {
wx.showToast({ title: '请先登录', icon: 'none' })
setTimeout(() => wx.switchTab({ url: '/pages/my/my' }), 1500)
return
}
const openId = app.globalData.openId || ''
// 优先尝试静默获取 openId有会话时可直接发起支付
let openId = app.globalData.openId || wx.getStorageSync('openId')
if (!openId) {
wx.showToast({ title: '请先完成微信授权', icon: 'none' })
wx.showLoading({ title: '获取支付凭证...', mask: true })
openId = await app.getOpenId()
wx.hideLoading()
}
if (!openId) {
const { confirm } = await new Promise(r => {
wx.showModal({
title: '请先登录',
content: '登录后可帮他完成代付',
confirmText: '去登录',
cancelText: '取消',
success: res => r(res)
})
})
if (confirm) wx.switchTab({ url: '/pages/my/my' })
return
}
const { requestSn, detail } = this.data
@@ -98,7 +116,22 @@ Page({
if (e.errMsg && e.errMsg.includes('cancel')) {
wx.showToast({ title: '已取消支付', icon: 'none' })
} else {
wx.showToast({ title: e.message || e.error || '支付失败', icon: 'none' })
const errMsg = e.message || e.error || '支付失败'
const isPrepayError = /prepay_id|创建订单|支付请求/i.test(errMsg)
if (isPrepayError) {
const { confirm } = await new Promise(r => {
wx.showModal({
title: '订单创建失败',
content: errMsg + '\n\n是否重新创建订单并重试',
confirmText: '重新创建订单',
cancelText: '取消',
success: res => r(res)
})
})
if (confirm) this.doPay()
} else {
wx.showToast({ title: errMsg, icon: 'none' })
}
}
}
},
@@ -107,6 +140,20 @@ Page({
app.goBackOrToHome()
},
goToInitiatorProfile() {
const { detail } = this.data
if (!detail?.initiatorUserId) return
wx.navigateTo({ url: `/pages/member-detail/member-detail?id=${detail.initiatorUserId}` })
},
goToArticle() {
const { detail } = this.data
if (!detail || detail.productType !== 'section' || !detail.productId) return
const mid = detail.productMid || app.getSectionMid?.(detail.productId)
const q = mid ? `mid=${mid}` : `id=${detail.productId}`
wx.navigateTo({ url: `/pages/read/read?${q}` })
},
onShareAppMessage() {
const { requestSn } = this.data
return {

View File

@@ -1,4 +1,4 @@
<!-- Soul创业派对 - 代付详情页(美团式:发起人看到分享入口,好友看到帮他付款 -->
<!-- Soul创业派对 - 代付详情页(参考 yulan代付视角+发起视角,忽略 nav -->
<view class="page">
<view class="nav-bar" style="padding-top: {{statusBarHeight}}px;">
<view class="nav-content">
@@ -20,50 +20,55 @@
</view>
</block>
<block wx:elif="{{detail}}">
<!-- 营销:章节标题+内容预览,吸引代付人 -->
<view class="article-preview" wx:if="{{detail.sectionTitle || detail.contentPreview}}">
<text class="article-title">{{detail.sectionTitle || detail.description || '代付商品'}}</text>
<text class="article-content" wx:if="{{detail.contentPreview}}">{{detail.contentPreview}}</text>
</view>
<view class="card">
<view class="card-header">
<view class="card-badge">代付订单</view>
<text class="initiator" wx:if="{{!isInitiator}}">{{detail.initiatorNickname || '好友'}} 请你帮忙付款</text>
<text class="initiator" wx:else>分享给好友,好友帮你付款</text>
</view>
<view class="card-divider"></view>
<view class="card-body">
<view class="row product-row" wx:if="{{!detail.contentPreview}}">
<text class="label">商品</text>
<text class="value product-desc">{{detail.sectionTitle || detail.description || '-'}}</text>
<!-- 产品 Hero 卡片(订单详情) -->
<section class="hero-card">
<view class="hero-glow"></view>
<view class="hero-inner">
<view class="hero-decor">
<image class="hero-decor-img" src="/assets/icons/info.svg" mode="aspectFit"/>
</view>
<view class="row amount-row">
<text class="label">金额</text>
<text class="amount">¥{{detail.amount ? detail.amount.toFixed(2) : '0.00'}}</text>
<view class="hero-badge">订单详情</view>
<text class="hero-title">{{detail.sectionTitle || detail.description || '代付商品'}}</text>
<text class="hero-desc" wx:if="{{detail.contentPreview}}">{{detail.contentPreview}}</text>
<view class="hero-footer">
<view class="hero-amount-wrap">
<text class="hero-amount-label">应付金额</text>
<view class="hero-amount-row">
<text class="hero-currency">¥</text>
<text class="hero-amount">{{amountDisplay}}</text>
</view>
</view>
<view class="hero-arrow-wrap" bindtap="goToArticle" wx:if="{{detail.productType === 'section' && detail.productId}}">
<image class="hero-arrow" src="/assets/icons/arrow-right.svg" mode="aspectFit"/>
</view>
<view class="hero-arrow-wrap hero-arrow-placeholder" wx:else></view>
</view>
</view>
</section>
<!-- 发起人信息 -->
<section class="requester-card">
<view class="requester-header" bindtap="goToInitiatorProfile">
<view class="requester-avatar">
<image wx:if="{{detail.initiatorAvatar}}" class="avatar-img" src="{{detail.initiatorAvatar}}" mode="aspectFill"/>
<image wx:else class="avatar-img icon-avatar" src="/assets/icons/user.svg" mode="aspectFit"/>
</view>
<view class="requester-info">
<text class="requester-name">{{detail.initiatorNickname || '好友'}}</text>
<text class="requester-label">发起代付请求</text>
</view>
</view>
<view class="requester-msg-wrap">
<view class="requester-msg-bar"></view>
<text class="requester-msg">{{requesterMsg}}</text>
</view>
</section>
<!-- 安全徽章 -->
<view class="security-badge">
<text class="security-icon">🛡</text>
<text class="security-text">安全支付保障 · 资金由平台托管</text>
</view>
<!-- 发起人:分享给好友 -->
<block wx:if="{{isInitiator}}">
<view class="tips">
<text class="tips-icon">💡</text>
<text>分享给好友,好友打开后点击「帮他付款」即可为你代付</text>
</view>
<button class="pay-btn share-btn" open-type="share">
<image class="btn-icon-img" src="/assets/icons/share.svg" mode="aspectFit"/>
<text>分享给好友</text>
</button>
</block>
<!-- 好友:帮他付款 -->
<block wx:else>
<view class="tips">
<text class="tips-icon">✓</text>
<text>付款后,{{detail.initiatorNickname || '好友'}}将获得对应权益</text>
</view>
<button class="pay-btn" bindtap="doPay" disabled="{{paying}}">
{{paying ? '支付中...' : '帮他付款'}}
</button>
</block>
</block>
<block wx:else>
<view class="empty">
@@ -71,4 +76,34 @@
</view>
</block>
</view>
<!-- 底部浮动操作栏 -->
<view class="footer-bar" wx:if="{{detail && !loading}}">
<view class="footer-bg"></view>
<view class="footer-inner">
<view class="footer-summary">
<text class="footer-label">合计</text>
<text class="footer-amount">
<text class="footer-currency">¥</text>{{amountDisplay}}
</text>
</view>
<!-- 发起人:分享给好友 -->
<button wx:if="{{isInitiator}}" class="footer-btn share-btn" open-type="share">
<image class="btn-icon" src="/assets/icons/share.svg" mode="aspectFit"/>
<text>发送给好友</text>
</button>
<!-- 好友:帮他付款 -->
<button wx:else class="footer-btn pay-btn" bindtap="doPay" disabled="{{paying}}">
<image class="btn-icon" src="/assets/icons/wallet.svg" mode="aspectFit"/>
<text>{{paying ? '支付中...' : '立即帮他付款'}}</text>
</button>
</view>
</view>
<!-- 背景光效 -->
<view class="bg-effects">
<view class="bg-glow bg-glow-1"></view>
<view class="bg-glow bg-glow-2"></view>
<view class="bg-dots"></view>
</view>
</view>

View File

@@ -1,7 +1,8 @@
/* Soul创业派对 - 代付详情页 */
/* Soul创业派对 - 代付详情页(参考 yulan 深色主题、青绿主色) */
.page {
min-height: 100vh;
background: linear-gradient(180deg, #0a0a0a 0%, #000 40%, #000 100%);
background: #050505;
position: relative;
}
.nav-bar {
@@ -10,10 +11,10 @@
left: 0;
right: 0;
z-index: 100;
background: rgba(0, 0, 0, 0.92);
backdrop-filter: blur(20rpx);
-webkit-backdrop-filter: blur(20rpx);
border-bottom: 1rpx solid rgba(255, 255, 255, 0.06);
background: rgba(5, 5, 5, 0.6);
backdrop-filter: blur(40rpx);
-webkit-backdrop-filter: blur(40rpx);
border-bottom: 1rpx solid rgba(255, 255, 255, 0.05);
}
.nav-content {
@@ -32,7 +33,6 @@
display: flex;
align-items: center;
justify-content: center;
transition: opacity 0.2s;
}
.nav-back:active {
@@ -45,16 +45,18 @@
}
.nav-title {
font-size: 34rpx;
font-weight: 600;
color: #fff;
letter-spacing: 0.5rpx;
font-size: 28rpx;
font-weight: 700;
color: rgba(255, 255, 255, 0.5);
letter-spacing: 0.2em;
text-transform: uppercase;
}
.content {
padding: 20rpx;
padding: 24rpx 24rpx 200rpx;
}
/* 加载 */
.loading-box {
display: flex;
flex-direction: column;
@@ -66,8 +68,8 @@
.loading-spinner {
width: 48rpx;
height: 48rpx;
border: 4rpx solid rgba(0, 206, 209, 0.2);
border-top-color: #00CED1;
border: 4rpx solid rgba(20, 184, 166, 0.2);
border-top-color: #14b8a6;
border-radius: 50%;
animation: spin 0.8s linear infinite;
}
@@ -82,28 +84,72 @@
color: rgba(255, 255, 255, 0.45);
}
/* 营销:章节标题+内容预览,与订单卡片统一风格 */
.article-preview {
background: linear-gradient(145deg, #1a1a1c 0%, #141416 100%);
border-radius: 24rpx;
/* 产品 Hero 卡片 */
.hero-card {
position: relative;
margin-bottom: 32rpx;
}
.hero-glow {
position: absolute;
inset: -4rpx;
background: linear-gradient(180deg, rgba(20, 184, 166, 0.2) 0%, transparent 100%);
border-radius: 40rpx;
filter: blur(24rpx);
opacity: 0.5;
}
.hero-inner {
position: relative;
background: rgba(24, 24, 27, 0.8);
border: 1rpx solid rgba(255, 255, 255, 0.1);
border-radius: 40rpx;
padding: 48rpx;
overflow: hidden;
}
.hero-decor {
position: absolute;
top: 0;
right: 0;
padding: 24rpx;
margin-bottom: 16rpx;
border: 1rpx solid rgba(0, 206, 209, 0.1);
opacity: 0.1;
}
.article-title {
.hero-decor-img {
width: 96rpx;
height: 96rpx;
}
.hero-badge {
display: inline-block;
font-size: 20rpx;
font-weight: 900;
letter-spacing: 0.2em;
color: #14b8a6;
background: rgba(20, 184, 166, 0.1);
border: 1rpx solid rgba(20, 184, 166, 0.2);
padding: 6rpx 24rpx;
border-radius: 999rpx;
margin-bottom: 24rpx;
}
.hero-title {
display: block;
font-size: 30rpx;
font-weight: 600;
font-size: 36rpx;
font-weight: 700;
color: #fff;
line-height: 1.5;
margin-bottom: 12rpx;
line-height: 1.3;
letter-spacing: -0.5rpx;
margin: 0 0 16rpx;
}
.article-content {
font-size: 26rpx;
color: rgba(255, 255, 255, 0.6);
line-height: 1.65;
.hero-desc {
display: block;
font-size: 28rpx;
color: rgba(255, 255, 255, 0.5);
line-height: 1.5;
margin: 0;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
@@ -111,154 +157,325 @@
-webkit-box-orient: vertical;
}
/* 订单卡片:与文章预览统一圆角、边距 */
.card {
background: linear-gradient(145deg, #1a1a1c 0%, #141416 100%);
border-radius: 24rpx;
overflow: hidden;
margin-bottom: 24rpx;
border: 1rpx solid rgba(0, 206, 209, 0.1);
}
.card-header {
padding: 24rpx;
}
.card-badge {
display: inline-block;
font-size: 22rpx;
color: rgba(0, 206, 209, 0.9);
background: rgba(0, 206, 209, 0.08);
padding: 6rpx 14rpx;
border-radius: 8rpx;
margin-bottom: 12rpx;
letter-spacing: 0.5rpx;
}
.initiator {
display: block;
font-size: 32rpx;
font-weight: 600;
color: #fff;
line-height: 1.4;
letter-spacing: 0.3rpx;
}
.card-divider {
height: 1rpx;
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.08), transparent);
margin: 0 24rpx;
}
.card-body {
padding: 20rpx 24rpx 24rpx;
}
.row {
.hero-footer {
margin-top: 40rpx;
padding-top: 32rpx;
border-top: 1rpx solid rgba(255, 255, 255, 0.05);
display: flex;
align-items: center;
justify-content: space-between;
align-items: flex-start;
}
.hero-amount-wrap {
display: flex;
flex-direction: column;
gap: 8rpx;
}
.hero-amount-label {
font-size: 20rpx;
font-weight: 700;
letter-spacing: 0.2em;
color: rgba(255, 255, 255, 0.4);
text-transform: uppercase;
}
.hero-amount-row {
display: flex;
align-items: baseline;
gap: 4rpx;
}
.hero-currency {
font-size: 36rpx;
font-weight: 700;
color: #14b8a6;
}
.hero-amount {
font-size: 60rpx;
font-weight: 700;
font-family: 'JetBrains Mono', 'SF Mono', monospace;
color: #fff;
letter-spacing: -1rpx;
}
.hero-arrow-wrap {
width: 96rpx;
height: 96rpx;
border-radius: 32rpx;
background: rgba(20, 184, 166, 0.1);
border: 1rpx solid rgba(20, 184, 166, 0.2);
display: flex;
align-items: center;
justify-content: center;
}
.hero-arrow {
width: 40rpx;
height: 40rpx;
filter: invert(72%) sepia(45%) saturate(800%) hue-rotate(130deg);
}
/* 发起人信息卡片 */
.requester-card {
background: rgba(24, 24, 27, 0.3);
border: 1rpx solid rgba(255, 255, 255, 0.05);
border-radius: 48rpx;
padding: 24rpx;
margin-bottom: 16rpx;
}
.row:last-child {
margin-bottom: 0;
}
.label {
font-size: 26rpx;
color: rgba(255, 255, 255, 0.45);
flex-shrink: 0;
width: 80rpx;
}
.product-row .value {
flex: 1;
text-align: right;
font-size: 28rpx;
color: rgba(255, 255, 255, 0.95);
line-height: 1.5;
word-break: break-all;
}
.product-desc {
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
}
.amount-row {
align-items: center;
}
.amount-row .amount {
font-size: 44rpx;
font-weight: 700;
color: #00CED1;
letter-spacing: 1rpx;
text-shadow: 0 0 24rpx rgba(0, 206, 209, 0.3);
}
/* 提示文案 */
.tips {
.requester-header {
display: flex;
align-items: flex-start;
gap: 10rpx;
padding: 0 4rpx 24rpx;
font-size: 26rpx;
color: rgba(255, 255, 255, 0.5);
line-height: 1.5;
align-items: center;
gap: 32rpx;
margin-bottom: 24rpx;
}
.tips-icon {
flex-shrink: 0;
font-size: 28rpx;
opacity: 0.8;
.requester-header:active {
opacity: 0.85;
}
/* 主按钮 */
.pay-btn {
width: 100%;
.requester-avatar {
width: 96rpx;
height: 96rpx;
line-height: 96rpx;
background: linear-gradient(135deg, #00CED1 0%, #18a8a8 50%, #20B2AA 100%);
color: #fff;
font-size: 34rpx;
font-weight: 600;
border-radius: 50rpx;
border: none;
box-shadow: 0 8rpx 24rpx rgba(0, 206, 209, 0.35);
transition: opacity 0.2s, transform 0.1s;
border-radius: 50%;
background: linear-gradient(180deg, #3f3f46 0%, #18181b 100%);
border: 1rpx solid rgba(255, 255, 255, 0.1);
overflow: hidden;
flex-shrink: 0;
}
.pay-btn:active {
opacity: 0.92;
transform: scale(0.99);
.avatar-img {
width: 100%;
height: 100%;
}
.pay-btn[disabled] {
opacity: 0.6;
transform: none;
.icon-avatar {
padding: 24rpx;
filter: brightness(0) invert(0.6);
}
.share-btn {
.requester-info {
flex: 1;
min-width: 0;
}
.requester-name {
display: block;
font-size: 28rpx;
font-weight: 700;
color: rgba(255, 255, 255, 0.9);
margin-bottom: 4rpx;
}
.requester-label {
font-size: 20rpx;
font-weight: 700;
letter-spacing: 0.2em;
color: rgba(255, 255, 255, 0.4);
text-transform: uppercase;
}
.requester-msg-wrap {
position: relative;
padding-left: 20rpx;
}
.requester-msg-bar {
position: absolute;
left: 0;
top: 0;
bottom: 0;
width: 4rpx;
background: rgba(20, 184, 166, 0.3);
border-radius: 2rpx;
}
.requester-msg {
font-size: 28rpx;
color: rgba(255, 255, 255, 0.5);
font-style: italic;
line-height: 1.6;
}
/* 安全徽章 */
.security-badge {
display: flex;
align-items: center;
justify-content: center;
gap: 16rpx;
padding: 32rpx 0;
}
.btn-icon-img {
width: 40rpx;
height: 40rpx;
filter: brightness(0) invert(1);
.security-icon {
font-size: 32rpx;
opacity: 0.6;
}
.security-text {
font-size: 20rpx;
font-weight: 700;
letter-spacing: 0.2em;
color: rgba(255, 255, 255, 0.35);
text-transform: uppercase;
}
/* 空状态 */
.empty {
text-align: center;
padding: 120rpx 0;
font-size: 28rpx;
color: rgba(255, 255, 255, 0.45);
}
/* 底部浮动操作栏 */
.footer-bar {
position: fixed;
bottom: 0;
left: 0;
right: 0;
z-index: 20;
padding: 32rpx;
}
.footer-bg {
position: absolute;
inset: 0;
background: rgba(24, 24, 27, 0.9);
backdrop-filter: blur(40rpx);
-webkit-backdrop-filter: blur(40rpx);
border-top: 1rpx solid rgba(255, 255, 255, 0.1);
border-radius: 50rpx;
box-shadow: 0 -20rpx 100rpx rgba(0, 0, 0, 0.5);
}
.footer-inner {
position: relative;
display: flex;
align-items: center;
gap: 32rpx;
padding: 24rpx 24rpx 24rpx 48rpx;
}
.footer-summary {
flex: 1;
}
.footer-label {
display: block;
font-size: 20rpx;
font-weight: 700;
letter-spacing: 0.2em;
color: rgba(255, 255, 255, 0.4);
text-transform: uppercase;
margin-bottom: 4rpx;
}
.footer-amount {
font-size: 40rpx;
font-weight: 700;
font-family: 'JetBrains Mono', 'SF Mono', monospace;
color: #fff;
letter-spacing: -1rpx;
}
.footer-currency {
font-size: 28rpx;
color: #14b8a6;
margin-right: 4rpx;
}
.footer-btn {
display: flex;
align-items: center;
justify-content: center;
gap: 16rpx;
padding: 32rpx 48rpx;
border-radius: 36rpx;
font-size: 28rpx;
font-weight: 900;
letter-spacing: 0.1em;
text-transform: uppercase;
border: none;
box-shadow: 0 16rpx 40rpx rgba(20, 184, 166, 0.3);
}
.footer-btn::after {
border: none;
}
.share-btn {
background: #14b8a6;
color: #000;
}
.pay-btn {
background: #14b8a6;
color: #000;
}
.pay-btn[disabled] {
opacity: 0.6;
}
.footer-btn:active {
transform: scale(0.98);
}
.btn-icon {
width: 40rpx;
height: 40rpx;
}
.share-btn .btn-icon,
.pay-btn .btn-icon {
filter: brightness(0);
}
/* 背景光效 */
.bg-effects {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
pointer-events: none;
z-index: -1;
}
.bg-glow {
position: absolute;
border-radius: 50%;
filter: blur(150rpx);
}
.bg-glow-1 {
top: -20%;
left: -10%;
width: 80%;
height: 60%;
background: rgba(20, 184, 166, 0.05);
animation: pulse-slow 8s infinite;
}
.bg-glow-2 {
bottom: -10%;
right: -10%;
width: 60%;
height: 50%;
background: rgba(20, 184, 166, 0.05);
}
@keyframes pulse-slow {
0%, 100% { opacity: 0.3; }
50% { opacity: 0.6; }
}
.bg-dots {
position: absolute;
inset: 0;
background-image: radial-gradient(rgba(255,255,255,0.02) 1rpx, transparent 1rpx);
background-size: 64rpx 64rpx;
}

View File

@@ -24,19 +24,12 @@
"miniprogram": {
"list": [
{
"name": "pages/gift-pay/detail",
"name": "代付",
"pathName": "pages/gift-pay/detail",
"query": "requestSn=GPRMP20260317114238341300",
"query": "requestSn=GPRMP20260317145140501100",
"scene": null,
"launchMode": "default"
},
{
"name": "pages/read/read",
"pathName": "pages/read/read",
"query": "mid=219",
"launchMode": "default",
"scene": null
},
{
"name": "唤醒",
"pathName": "pages/read/read",
@@ -57,13 +50,6 @@
"query": "mid=20",
"launchMode": "default",
"scene": null
},
{
"name": "pages/read/read",
"pathName": "pages/read/read",
"query": "mid=1",
"launchMode": "default",
"scene": null
}
]
}

210
miniprogram/yulan.html Normal file
View File

@@ -0,0 +1,210 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>代付页面预览 - Premium FriendPay</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://unpkg.com/lucide@latest"></script>
<script src="https://unpkg.com/react@18/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;600;700;900&family=JetBrains+Mono:wght@700&display=swap" rel="stylesheet">
<style>
body {
font-family: 'Inter', sans-serif;
background-color: #050505;
color: white;
margin: 0;
-webkit-font-smoothing: antialiased;
}
.font-mono {
font-family: 'JetBrains Mono', monospace;
}
@keyframes pulse-slow {
0%, 100% { opacity: 0.3; }
50% { opacity: 0.6; }
}
.animate-pulse-slow {
animation: pulse-slow 8s infinite;
}
</style>
</head>
<body>
<div id="root"></div>
<script type="text/babel">
const { useState, useEffect } = React;
// Simple Icon component to wrap Lucide
const Icon = ({ name, className }) => {
useEffect(() => {
lucide.createIcons();
}, [name]);
return <i data-lucide={name} className={className}></i>;
};
function App() {
const [viewMode, setViewMode] = useState('payer');
const content = {
payer: {
title: '帮他付款',
productName: 'AI 提效实战课:从入门到精通',
productDesc: '第 123 场直播回放 · 包含所有课件与实战案例',
requesterName: '好**',
requesterMsg: '“ 这门课对我很有帮助,希望能帮我代付一下,非常感谢! ”',
amount: '199.00',
buttonText: '立即帮他付款',
buttonIcon: 'credit-card',
},
requester: {
title: '找朋友代付',
productName: '3000万流水如何跑出来 (退税模式解析)',
productDesc: '深度解析企业退税合规与流水结构优化',
requesterName: '你自己',
requesterMsg: '分享给好友,好友打开后即可为你完成支付。',
amount: '299.00',
buttonText: '发送给好友',
buttonIcon: 'share-2',
}
};
const current = content[viewMode];
return (
<div className="min-h-screen flex flex-col relative overflow-x-hidden">
{/* View Switcher */}
<div className="fixed top-6 left-1/2 -translate-x-1/2 z-50 flex bg-zinc-900/80 backdrop-blur-xl rounded-full p-1 border border-white/5 shadow-2xl">
<button
onClick={() => setViewMode('payer')}
className={`px-5 py-2 rounded-full text-xs font-bold tracking-wide transition-all duration-300 ${viewMode === 'payer' ? 'bg-[#14b8a6] text-black shadow-[0_0_15px_rgba(20,184,166,0.4)]' : 'text-zinc-500 hover:text-zinc-300'}`}
>
代付视角
</button>
<button
onClick={() => setViewMode('requester')}
className={`px-5 py-2 rounded-full text-xs font-bold tracking-wide transition-all duration-300 ${viewMode === 'requester' ? 'bg-[#14b8a6] text-black shadow-[0_0_15px_rgba(20,184,166,0.4)]' : 'text-zinc-500 hover:text-zinc-300'}`}
>
发起视角
</button>
</div>
<div className="max-w-md mx-auto w-full min-h-screen flex flex-col relative">
{/* Navigation */}
<header className="px-6 py-8 flex items-center justify-between sticky top-0 bg-[#050505]/60 backdrop-blur-xl z-10">
<button className="w-10 h-10 flex items-center justify-center bg-zinc-900/50 border border-white/5 rounded-full hover:bg-zinc-800 transition-colors">
<Icon name="chevron-left" className="w-5 h-5" />
</button>
<h1 className="text-sm font-bold uppercase tracking-[0.2em] text-zinc-400">{current.title}</h1>
<div className="flex items-center gap-2 bg-zinc-900/50 border border-white/5 rounded-full px-3 py-1.5">
<Icon name="more-horizontal" className="w-4 h-4 text-zinc-500" />
<div className="w-[1px] h-3 bg-white/10" />
<Icon name="circle" className="w-3 h-3 fill-white text-white" />
</div>
</header>
<main className="flex-1 px-6 pb-40 space-y-8">
{/* Product Hero Card */}
<section className="relative group">
<div className="absolute -inset-0.5 bg-gradient-to-b from-[#14b8a6]/20 to-transparent rounded-[2rem] blur-xl opacity-50"></div>
<div className="relative bg-zinc-900/80 border border-white/10 rounded-[2rem] p-8 overflow-hidden transition-all duration-500 hover:border-[#14b8a6]/30">
<div className="absolute top-0 right-0 p-6 opacity-10">
<Icon name="info" className="w-12 h-12" />
</div>
<div className="space-y-4">
<div className="inline-block px-3 py-1 rounded-full bg-[#14b8a6]/10 border border-[#14b8a6]/20 text-[#14b8a6] text-[10px] font-black uppercase tracking-widest">
订单详情
</div>
<h2 className="text-2xl font-bold leading-tight tracking-tight">
{current.productName}
</h2>
<p className="text-zinc-400 text-sm font-medium">
{current.productDesc}
</p>
</div>
<div className="mt-10 pt-8 border-t border-white/5 flex items-center justify-between">
<div className="space-y-1">
<p className="text-[10px] uppercase tracking-widest text-zinc-500 font-bold">应付金额</p>
<div className="flex items-baseline gap-1">
<span className="text-[#14b8a6] text-lg font-bold">¥</span>
<span className="text-3xl font-mono font-bold tracking-tighter">{current.amount}</span>
</div>
</div>
<div className="w-12 h-12 rounded-2xl bg-[#14b8a6]/10 border border-[#14b8a6]/20 flex items-center justify-center">
<Icon name="arrow-right" className="w-5 h-5 text-[#14b8a6]" />
</div>
</div>
</div>
</section>
{/* Requester Info */}
<section className="bg-zinc-900/30 border border-white/5 rounded-3xl p-6 space-y-4">
<div className="flex items-center gap-4">
<div className="w-12 h-12 rounded-full bg-gradient-to-br from-zinc-700 to-zinc-900 border border-white/10 flex items-center justify-center shadow-inner">
<Icon name="user" className="w-6 h-6 text-zinc-400" />
</div>
<div>
<h4 className="text-sm font-bold text-zinc-200">{current.requesterName}</h4>
<p className="text-[10px] uppercase tracking-widest text-zinc-500 font-bold">发起代付请求</p>
</div>
</div>
<div className="relative">
<div className="absolute left-0 top-0 bottom-0 w-1 bg-[#14b8a6]/30 rounded-full" />
<p className="pl-5 text-zinc-400 text-sm italic leading-relaxed">
{current.requesterMsg}
</p>
</div>
</section>
{/* Security Badge */}
<div className="flex items-center justify-center gap-2 py-4">
<Icon name="shield-check" className="w-4 h-4 text-[#14b8a6]/60" />
<span className="text-[10px] uppercase tracking-[0.2em] text-zinc-600 font-bold">
安全支付保障 · 资金由平台托管
</span>
</div>
</main>
{/* Floating Action Bar */}
<footer className="fixed bottom-0 left-0 right-0 p-8 z-20">
<div className="max-w-md mx-auto relative">
<div className="absolute inset-0 bg-zinc-900/80 backdrop-blur-2xl border border-white/10 rounded-[2.5rem] shadow-[0_20px_50px_rgba(0,0,0,0.5)]" />
<div className="relative p-3 flex items-center gap-4">
<div className="flex-1 pl-6">
<p className="text-[10px] uppercase tracking-widest text-zinc-500 font-bold mb-0.5">合计</p>
<p className="text-xl font-mono font-bold tracking-tighter">
<span className="text-[#14b8a6] text-sm mr-1">¥</span>
{current.amount}
</p>
</div>
<button
className="bg-[#14b8a6] hover:bg-[#0d9488] text-black font-black px-8 py-4 rounded-[1.8rem] flex items-center justify-center transition-all shadow-[0_8px_20px_rgba(20,184,166,0.3)] active:scale-95"
>
<Icon name={current.buttonIcon} className="w-5 h-5 mr-2" />
<span className="text-sm uppercase tracking-wider">{current.buttonText}</span>
</button>
</div>
</div>
</footer>
{/* Ambient Background Effects */}
<div className="fixed top-0 left-0 w-full h-full overflow-hidden pointer-events-none -z-10">
<div className="absolute top-[-20%] left-[-10%] w-[80%] h-[60%] bg-[#14b8a6]/5 blur-[150px] rounded-full animate-pulse-slow" />
<div className="absolute bottom-[-10%] right-[-10%] w-[60%] h-[50%] bg-[#14b8a6]/5 blur-[120px] rounded-full" />
<div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-full h-full opacity-[0.02] bg-[radial-gradient(#fff_1px,transparent_1px)] [background-size:32px_32px]" />
</div>
</div>
</div>
);
}
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<App />);
</script>
</body>
</html>