Commit 7411c249 by t0ng7u

style(Account UX): resilient binding layout, copyable popovers, pastel header,…

 style(Account UX): resilient binding layout, copyable popovers, pastel header, and custom pay colors

- AccountManagement.js
  - Prevent action button from shifting when account IDs are long by adding gap, min-w-0, and flex-shrink-0; keep buttons in a fixed position.
  - Add copyable Popover for account identifiers (email/GitHub/OIDC/Telegram/LinuxDO) using Typography.Paragraph with copyable; reveal full text on hover.
  - Ensure ellipsis works by rendering the popover trigger as `block max-w-full truncate`.
  - Import Popover and wire up `renderAccountInfo` across all binding rows.

- UserInfoHeader.js
  - Apply unified `with-pastel-balls` background to match PricingVendorIntro.
  - Remove legacy absolute-positioned circles and top gradient bar to avoid visual overlap.

- RechargeCard.jsx
  - Colorize non-Alipay/WeChat/Stripe payment icons using backend `pay_methods[].color`; fallback to `var(--semi-color-text-2)`.
  - Add `showClear` to the redemption code input for quicker clearing.

Notes:
- No linter errors introduced.
- i18n strings and behavior remain unchanged except for improved UX and visual consistency.
parent bbe381f6
...@@ -106,7 +106,7 @@ const NotificationSettings = ({ ...@@ -106,7 +106,7 @@ const NotificationSettings = ({
onSubmit={handleSubmit} onSubmit={handleSubmit}
> >
{() => ( {() => (
<Tabs type="line" defaultActiveKey="notification"> <Tabs type="card" defaultActiveKey="notification">
{/* 通知配置 Tab */} {/* 通知配置 Tab */}
<TabPane <TabPane
tab={ tab={
...@@ -223,11 +223,11 @@ const NotificationSettings = ({ ...@@ -223,11 +223,11 @@ const NotificationSettings = ({
/> />
</div> </div>
<div className="text-xs text-gray-500 leading-relaxed"> <div className="text-xs text-gray-500 leading-relaxed">
<div><strong>type:</strong> 通知类型 (quota_exceed: 额度预警)</div> <div><strong>type:</strong> {t('通知类型 (quota_exceed: 额度预警)')} </div>
<div><strong>title:</strong> 通知标题</div> <div><strong>title:</strong> {t('通知标题')}</div>
<div><strong>content:</strong> 通知内容,支持 {`{{value}}`} 变量占位符</div> <div><strong>content:</strong> {t('通知内容,支持 {{value}} 变量占位符')}</div>
<div><strong>values:</strong> 按顺序替换content中的变量占位符</div> <div><strong>values:</strong> {t('按顺序替换content中的变量占位符')}</div>
<div><strong>timestamp:</strong> Unix时间戳</div> <div><strong>timestamp:</strong> {t('Unix时间戳')}</div>
</div> </div>
</div> </div>
</Form.Slot> </Form.Slot>
......
...@@ -19,12 +19,10 @@ For commercial licensing, please contact support@quantumnous.com ...@@ -19,12 +19,10 @@ For commercial licensing, please contact support@quantumnous.com
import React from 'react'; import React from 'react';
import { Avatar, Card, Tag, Divider, Typography } from '@douyinfe/semi-ui'; import { Avatar, Card, Tag, Divider, Typography } from '@douyinfe/semi-ui';
import { isRoot, isAdmin, renderQuota } from '../../../../helpers'; import { isRoot, isAdmin, renderQuota, stringToColor } from '../../../../helpers';
import { useTheme } from '../../../../context/Theme';
import { Coins, BarChart2, Users } from 'lucide-react'; import { Coins, BarChart2, Users } from 'lucide-react';
const UserInfoHeader = ({ t, userState }) => { const UserInfoHeader = ({ t, userState }) => {
const theme = useTheme();
const getUsername = () => { const getUsername = () => {
if (userState.user) { if (userState.user) {
...@@ -44,34 +42,19 @@ const UserInfoHeader = ({ t, userState }) => { ...@@ -44,34 +42,19 @@ const UserInfoHeader = ({ t, userState }) => {
}; };
return ( return (
<Card <Card className="!rounded-2xl with-pastel-balls">
className="!rounded-2xl !border-0" <div className="relative text-gray-600 dark:text-gray-300">
style={{
background: theme === 'dark'
? 'linear-gradient(135deg, #1e293b 0%, #334155 50%, #475569 100%)'
: 'linear-gradient(135deg, #f8fafc 0%, #e2e8f0 50%, #cbd5e1 100%)',
position: 'relative'
}}
bodyStyle={{ padding: 0 }}
>
{/* 装饰性背景元素 */}
<div className="absolute inset-0 overflow-hidden">
<div className="absolute -top-10 -right-10 w-40 h-40 bg-slate-400 dark:bg-slate-500 opacity-5 rounded-full"></div>
<div className="absolute -bottom-16 -left-16 w-48 h-48 bg-slate-300 dark:bg-slate-400 opacity-8 rounded-full"></div>
<div className="absolute top-1/2 right-1/4 w-24 h-24 bg-slate-400 dark:bg-slate-500 opacity-6 rounded-full"></div>
</div>
<div className="relative p-4 sm:p-6 md:p-8 text-gray-600 dark:text-gray-300">
<div className="flex justify-between items-start mb-4 sm:mb-6"> <div className="flex justify-between items-start mb-4 sm:mb-6">
<div className="flex items-center flex-1 min-w-0"> <div className="flex items-center flex-1 min-w-0">
<Avatar <Avatar
size='large' size='large'
className="mr-3 sm:mr-4 shadow-md flex-shrink-0 bg-slate-500 dark:bg-slate-400" className="mr-3 sm:mr-4 shadow-md flex-shrink-0"
color={stringToColor(getUsername())}
> >
{getAvatarText()} {getAvatarText()}
</Avatar> </Avatar>
<div className="flex-1 min-w-0"> <div className="flex-1 min-w-0">
<div className="text-base sm:text-lg font-semibold truncate text-gray-800 dark:text-gray-100"> <div className="text-base text-3xl font-semibold truncate text-gray-800 dark:text-gray-100">
{getUsername()} {getUsername()}
</div> </div>
<div className="mt-1 flex flex-wrap gap-1 sm:gap-2"> <div className="mt-1 flex flex-wrap gap-1 sm:gap-2">
...@@ -113,7 +96,7 @@ const UserInfoHeader = ({ t, userState }) => { ...@@ -113,7 +96,7 @@ const UserInfoHeader = ({ t, userState }) => {
{/* 右上角统计信息(Semi UI 卡片) */} {/* 右上角统计信息(Semi UI 卡片) */}
<div className="hidden sm:block flex-shrink-0 ml-2"> <div className="hidden sm:block flex-shrink-0 ml-2">
<Card size="small" className="!rounded-xl !border-0 shadow-sm" bodyStyle={{ padding: '8px 12px' }}> <Card size="small" className="!rounded-xl shadow-sm" bodyStyle={{ padding: '8px 12px' }}>
<div className="flex items-center gap-3 lg:gap-4"> <div className="flex items-center gap-3 lg:gap-4">
<div className="flex items-center justify-end gap-2"> <div className="flex items-center justify-end gap-2">
<Coins size={16} className="text-slate-600 dark:text-slate-300" /> <Coins size={16} className="text-slate-600 dark:text-slate-300" />
...@@ -182,11 +165,9 @@ const UserInfoHeader = ({ t, userState }) => { ...@@ -182,11 +165,9 @@ const UserInfoHeader = ({ t, userState }) => {
</div> </div>
</Card> </Card>
</div> </div>
<div className="absolute top-0 left-0 w-full h-2 bg-gradient-to-r from-slate-300 via-slate-400 to-slate-500 dark:from-slate-600 dark:via-slate-500 dark:to-slate-400 opacity-40"></div>
</div> </div>
</Card> </Card>
); );
}; };
export default UserInfoHeader; export default UserInfoHeader;
\ No newline at end of file
...@@ -54,7 +54,7 @@ const InvitationCard = ({ ...@@ -54,7 +54,7 @@ const InvitationCard = ({
{/* 收益展示区域 */} {/* 收益展示区域 */}
<div className='space-y-4'> <div className='space-y-4'>
{/* 主要收益卡片 - 待使用收益 */} {/* 主要收益卡片 - 待使用收益 */}
<Card className='!rounded-xl bg-slate-50 dark:bg-slate-800'> <Card className='!rounded-xl with-pastel-balls'>
<div className='flex justify-between items-center mb-3'> <div className='flex justify-between items-center mb-3'>
<div className="flex items-center"> <div className="flex items-center">
<TrendingUp size={16} className="mr-2 text-slate-600 dark:text-slate-300" /> <TrendingUp size={16} className="mr-2 text-slate-600 dark:text-slate-300" />
...@@ -75,7 +75,6 @@ const InvitationCard = ({ ...@@ -75,7 +75,6 @@ const InvitationCard = ({
<div className='text-2xl sm:text-3xl font-bold text-slate-900 dark:text-slate-100 mb-1'> <div className='text-2xl sm:text-3xl font-bold text-slate-900 dark:text-slate-100 mb-1'>
{renderQuota(userState?.user?.aff_quota || 0)} {renderQuota(userState?.user?.aff_quota || 0)}
</div> </div>
<div className="text-xs text-slate-500 dark:text-slate-400">{t('可随时划转到账户余额')}</div>
</Card> </Card>
{/* 统计数据网格 */} {/* 统计数据网格 */}
...@@ -88,7 +87,6 @@ const InvitationCard = ({ ...@@ -88,7 +87,6 @@ const InvitationCard = ({
<div className='text-xl font-semibold text-slate-900 dark:text-slate-100'> <div className='text-xl font-semibold text-slate-900 dark:text-slate-100'>
{renderQuota(userState?.user?.aff_history_quota || 0)} {renderQuota(userState?.user?.aff_history_quota || 0)}
</div> </div>
<div className="text-xs text-slate-500 dark:text-slate-400 mt-1">{t('累计获得')}</div>
</Card> </Card>
<Card className='!rounded-xl bg-slate-50 dark:bg-slate-800'> <Card className='!rounded-xl bg-slate-50 dark:bg-slate-800'>
...@@ -99,7 +97,6 @@ const InvitationCard = ({ ...@@ -99,7 +97,6 @@ const InvitationCard = ({
<div className='text-xl font-semibold text-slate-900 dark:text-slate-100 flex items-center'> <div className='text-xl font-semibold text-slate-900 dark:text-slate-100 flex items-center'>
{userState?.user?.aff_count || 0} {t('人')} {userState?.user?.aff_count || 0} {t('人')}
</div> </div>
<div className="text-xs text-slate-500 dark:text-slate-400 mt-1">{t('成功邀请')}</div>
</Card> </Card>
</div> </div>
...@@ -126,7 +123,7 @@ const InvitationCard = ({ ...@@ -126,7 +123,7 @@ const InvitationCard = ({
{/* 奖励说明 */} {/* 奖励说明 */}
<Card <Card
className='!rounded-xl' className='!rounded-xl with-pastel-balls-warm'
title={ title={
<Text strong className='text-slate-700'> <Text strong className='text-slate-700'>
{t('奖励说明')} {t('奖励说明')}
......
...@@ -224,7 +224,7 @@ const RechargeCard = ({ ...@@ -224,7 +224,7 @@ const RechargeCard = ({
) : payMethod.type === 'stripe' ? ( ) : payMethod.type === 'stripe' ? (
<SiStripe size={24} color="#635BFF" /> <SiStripe size={24} color="#635BFF" />
) : ( ) : (
<CreditCard size={24} className='text-slate-500' /> <CreditCard size={24} color={payMethod.color || 'var(--semi-color-text-2)'} />
)} )}
</div> </div>
<div className='text-sm font-medium text-slate-700 dark:text-slate-200'>{payMethod.name}</div> <div className='text-sm font-medium text-slate-700 dark:text-slate-200'>{payMethod.name}</div>
...@@ -269,6 +269,7 @@ const RechargeCard = ({ ...@@ -269,6 +269,7 @@ const RechargeCard = ({
onChange={(value) => setRedemptionCode(value)} onChange={(value) => setRedemptionCode(value)}
className='!rounded-lg' className='!rounded-lg'
prefix={<IconGift />} prefix={<IconGift />}
showClear
/> />
<div className='flex flex-col sm:flex-row gap-2'> <div className='flex flex-col sm:flex-row gap-2'>
......
...@@ -24,7 +24,7 @@ import { ...@@ -24,7 +24,7 @@ import {
Card, Card,
Skeleton, Skeleton,
} from '@douyinfe/semi-ui'; } from '@douyinfe/semi-ui';
import { SiAlipay, SiWechat } from 'react-icons/si'; import { SiAlipay, SiWechat, SiStripe } from 'react-icons/si';
import { CreditCard } from 'lucide-react'; import { CreditCard } from 'lucide-react';
const { Text } = Typography; const { Text } = Typography;
...@@ -86,11 +86,13 @@ const PaymentConfirmModal = ({ ...@@ -86,11 +86,13 @@ const PaymentConfirmModal = ({
return ( return (
<> <>
{payMethod.type === 'zfb' ? ( {payMethod.type === 'zfb' ? (
<SiAlipay className='mr-2' size={16} /> <SiAlipay className='mr-2' size={16} color="#1677FF" />
) : payMethod.type === 'wx' ? ( ) : payMethod.type === 'wx' ? (
<SiWechat className='mr-2' size={16} /> <SiWechat className='mr-2' size={16} color="#07C160" />
) : payMethod.type === 'stripe' ? (
<SiStripe className='mr-2' size={16} color="#635BFF" />
) : ( ) : (
<CreditCard className='mr-2' size={16} /> <CreditCard className='mr-2' size={16} color={payMethod.color || 'var(--semi-color-text-2)'} />
)} )}
<Text className='text-slate-900 dark:text-slate-100'>{payMethod.name}</Text> <Text className='text-slate-900 dark:text-slate-100'>{payMethod.name}</Text>
</> </>
...@@ -100,21 +102,21 @@ const PaymentConfirmModal = ({ ...@@ -100,21 +102,21 @@ const PaymentConfirmModal = ({
if (payWay === 'zfb') { if (payWay === 'zfb') {
return ( return (
<> <>
<SiAlipay className='mr-2' size={16} /> <SiAlipay className='mr-2' size={16} color="#1677FF" />
<Text className='text-slate-900 dark:text-slate-100'>{t('支付宝')}</Text> <Text className='text-slate-900 dark:text-slate-100'>{t('支付宝')}</Text>
</> </>
); );
} else if (payWay === 'stripe') { } else if (payWay === 'stripe') {
return ( return (
<> <>
<CreditCard className='mr-2' size={16} /> <SiStripe className='mr-2' size={16} color="#635BFF" />
<Text className='text-slate-900 dark:text-slate-100'>Stripe</Text> <Text className='text-slate-900 dark:text-slate-100'>Stripe</Text>
</> </>
); );
} else { } else {
return ( return (
<> <>
<SiWechat className='mr-2' size={16} /> <SiWechat className='mr-2' size={16} color="#07C160" />
<Text className='text-slate-900 dark:text-slate-100'>{t('微信')}</Text> <Text className='text-slate-900 dark:text-slate-100'>{t('微信')}</Text>
</> </>
); );
......
...@@ -853,13 +853,11 @@ ...@@ -853,13 +853,11 @@
"支付宝": "Alipay", "支付宝": "Alipay",
"待使用收益": "Proceeds to be used", "待使用收益": "Proceeds to be used",
"邀请人数": "Number of people invited", "邀请人数": "Number of people invited",
"可随时划转到账户余额": "Can be transferred to the account balance at any time",
"成功邀请": "Successfully invited",
"累计获得": "Accumulated",
"兑换码充值": "Redemption code recharge", "兑换码充值": "Redemption code recharge",
"奖励说明": "Reward description", "奖励说明": "Reward description",
"人": "", "人": "",
"选择支付方式": "Select payment method", "选择支付方式": "Select payment method",
"处理中": "Processing",
"账户充值": "Account recharge", "账户充值": "Account recharge",
"多种充值方式,安全便捷": "Multiple recharge methods, safe and convenient", "多种充值方式,安全便捷": "Multiple recharge methods, safe and convenient",
"支付方式": "Payment method", "支付方式": "Payment method",
......
...@@ -668,6 +668,59 @@ html.dark .with-pastel-balls::before { ...@@ -668,6 +668,59 @@ html.dark .with-pastel-balls::before {
mix-blend-mode: screen; mix-blend-mode: screen;
} }
/* ==================== 卡片马卡龙模糊球(温暖色系) ==================== */
.with-pastel-balls-warm {
position: relative;
overflow: hidden;
/* 温暖色系变量(明亮模式) */
--pb1: #ffe4b5;
/* 桃杏 */
--pb2: #d4ffdd;
/* 薄荷绿 */
--pb3: #ffecb3;
/* 浅金 */
--pb4: #e8f5e8;
/* 淡绿 */
--pb-opacity: 0.55;
}
.with-pastel-balls-warm::before {
content: '';
position: absolute;
inset: 0;
pointer-events: none;
z-index: 0;
background:
radial-gradient(circle at -5% -10%, var(--pb1) 0%, transparent 60%),
radial-gradient(circle at 105% -10%, var(--pb2) 0%, transparent 55%),
radial-gradient(circle at 5% 110%, var(--pb3) 0%, transparent 55%),
radial-gradient(circle at 105% 110%, var(--pb4) 0%, transparent 50%);
opacity: var(--pb-opacity);
}
.with-pastel-balls-warm>* {
position: relative;
z-index: 1;
}
/* 暗黑模式下更柔和的色彩和透明度 */
html.dark .with-pastel-balls-warm {
--pb1: #ffe4b5;
/* 桃杏 */
--pb2: #d4ffdd;
/* 薄荷绿 */
--pb3: #ffecb3;
/* 浅金 */
--pb4: #e8f5e8;
/* 淡绿 */
--pb-opacity: 0.36;
}
/* 暗黑模式下用更柔和的混合模式 */
html.dark .with-pastel-balls-warm::before {
mix-blend-mode: screen;
}
/* ==================== 表格卡片滚动设置 ==================== */ /* ==================== 表格卡片滚动设置 ==================== */
.table-scroll-card { .table-scroll-card {
display: flex; display: flex;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment