diff --git a/frontend/miniapp/src/i18n/index.ts b/frontend/miniapp/src/i18n/index.ts index c26b3d0..6696dc8 100644 --- a/frontend/miniapp/src/i18n/index.ts +++ b/frontend/miniapp/src/i18n/index.ts @@ -312,6 +312,162 @@ const translations: Record> = { // ── Coupon Detail (stores) ── 'coupon_stores_count': '全国 12,800+ 门店', + + // ── Purchase / Order Confirm ── + 'purchase_title': '确认订单', + 'purchase_quantity': '购买数量', + 'purchase_wechat_pay': '微信支付', + 'purchase_payment_method': '支付方式', + 'purchase_unit_price': '单价', + 'purchase_count': '数量', + 'purchase_confirm_pay': '确认支付', + 'purchase_buying_note': '您正在购买平台担保的消费券', + 'purchase_save_badge': '比面值节省', + 'purchase_price_detail': '价格明细', + + // ── Payment Success ── + 'payment_success_title': '支付成功', + 'payment_success_hint': '券已到账,可在「我的券」中查看', + 'payment_success_coupon_name': '券名称', + 'payment_success_pay_amount': '支付金额', + 'payment_success_order_no': '订单号', + 'payment_success_pay_time': '支付时间', + 'payment_success_view_coupon': '查看我的券', + 'payment_success_continue': '继续逛逛', + + // ── My Coupon Detail ── + 'my_coupon_detail_title': '券详情', + 'my_coupon_active': '可使用', + 'my_coupon_show_qr_hint': '出示此二维码给商户扫描核销', + 'my_coupon_switch_barcode': '切换条形码', + 'my_coupon_purchase_price': '购买价格', + 'my_coupon_order_no': '订单号', + 'my_coupon_resell_count': '剩余可转售次数', + 'my_coupon_usage_note': '使用说明', + 'my_coupon_use_in_store': '全国门店通用', + 'my_coupon_use_in_time': '请在有效期内使用', + 'my_coupon_one_per_visit': '每次消费仅可使用一张', + 'my_coupon_no_cash': '不可兑换现金', + 'my_coupon_transfer': '转赠好友', + 'my_coupon_sell': '挂单出售', + 'my_coupon_extract_wallet': '提取到外部钱包', + 'my_coupon_require_kyc': '需KYC L2+认证', + 'my_coupon_view_trades': '查看交易记录', + 'my_coupon_help': '使用帮助', + 'my_coupon_sell_in_app': '出售功能请使用App', + + // ── Transfer ── + 'transfer_title': '转赠', + 'transfer_share_card': '分享小程序卡片', + 'transfer_share_desc': '直接分享给微信好友', + 'transfer_scan': '扫码转赠', + 'transfer_scan_desc': '扫描对方接收码', + 'transfer_input': '输入ID', + 'transfer_input_desc': '手动输入对方信息', + 'transfer_recent': '最近转赠', + 'transfer_manage': '管理', + 'transfer_no_recent': '暂无最近转赠记录', + 'transfer_expired': '已过期', + 'transfer_refresh': '刷新', + 'transfer_history': '转赠记录', + 'transfer_last_transfer': '最近转赠', + 'transfer_input_recipient': '输入收件人', + 'transfer_recipient_hint': 'ID / 邮箱 / 手机号', + 'transfer_paste': '粘贴', + 'transfer_select_coupon': '选择转赠的券', + 'transfer_confirm': '确认转赠', + 'transfer_to': '转赠给', + 'transfer_confirm_btn': '确认转赠', + 'transfer_contact_email': '邮箱', + 'transfer_contact_phone': '手机', + 'transfer_warning': '转赠后将无法撤回,请确认信息无误', + 'transfer_success': '转赠成功', + 'transfer_outgoing': '转出', + 'transfer_incoming': '收到', + + // ── AI Chat ── + 'ai_chat_title': 'AI 助手', + 'ai_chat_greeting': '你好!我是 Genex AI 助手,可以帮你发现高性价比好券。试试问我:', + 'ai_chat_suggest1': '推荐适合我的券', + 'ai_chat_suggest2': '星巴克券值不值得买?', + 'ai_chat_suggest3': '帮我做比价分析', + 'ai_chat_suggest4': '我的券快到期了怎么办?', + 'ai_chat_input_hint': '问我任何关于券的问题...', + 'ai_chat_send': '发送', + + // ── Orders (additional) ── + 'order_view_detail': '查看详情', + 'order_empty': '暂无订单', + 'order_status_paid': '已支付', + + // ── Messages ── + 'messages_title': '消息', + 'messages_mark_all_read': '全部已读', + 'messages_tab_trade': '交易', + 'messages_tab_expiry': '到期', + 'messages_tab_announcement': '公告', + 'messages_type_price': '价格提醒', + 'messages_empty': '暂无消息', + + // ── Wallet ── + 'wallet_balance': '我的余额', + 'wallet_total_balance': '总余额', + 'wallet_available': '可用', + 'wallet_frozen': '冻结中', + 'wallet_records': '交易记录', + 'wallet_buy_in': '买入', + 'wallet_sell_out': '卖出', + 'wallet_gift_transfer': '转赠', + 'wallet_redeem_use': '核销', + 'wallet_read_only_hint': '完整钱包功能请使用App', + 'wallet_filter': '筛选', + 'wallet_deposit_record': '充值', + 'wallet_withdraw_record': '提现', + + // ── Settings ── + 'settings_title': '设置', + 'settings_notifications': '通知设置', + 'settings_trade_notify': '交易通知', + 'settings_expiry_remind': '到期提醒', + 'settings_marketing_push': '营销推送', + 'settings_general': '通用', + 'settings_clear_cache': '清除缓存', + 'settings_about': '关于', + 'settings_version': '版本', + 'settings_help_center': '帮助中心', + 'settings_logout': '退出登录', + 'settings_select_language': '选择语言', + 'settings_select_currency': '选择计价货币', + 'settings_currency_note': '此设置影响价格显示的计价货币', + 'settings_cache_cleared': '缓存已清除', + + // ── KYC ── + 'kyc_title': '身份认证', + 'kyc_current_level': '当前认证等级', + 'kyc_daily_limit': '每日购买限额', + 'kyc_l1_title': 'L1 基础认证', + 'kyc_l1_desc': '手机号 + 邮箱验证', + 'kyc_l1_limit': '¥500', + 'kyc_l1_feature': '可购买券、出示核销', + 'kyc_l2_title': 'L2 身份认证', + 'kyc_l2_desc': '身份证 / 护照验证', + 'kyc_l2_limit': '¥5,000', + 'kyc_l2_feature': '解锁二级市场交易、P2P转赠', + 'kyc_l3_title': 'L3 高级认证', + 'kyc_l3_desc': '视频面审 + 地址证明', + 'kyc_l3_limit': '无限额', + 'kyc_l3_feature': '解锁大额交易、提现无限制', + 'kyc_completed': '已完成', + 'kyc_go_verify': '去认证', + 'kyc_verify_in_app': '请使用App完成认证', + + // ── Detail (enhancement) ── + 'detail_nearby_stores': '附近门店', + 'detail_view_all': '查看全部', + 'detail_similar_coupons': '相似好券', + + // ── AI Assistant (profile) ── + 'profile_ai_assistant': 'AI 助手', }, 'en-US': { @@ -606,6 +762,162 @@ const translations: Record> = { // ── Coupon Detail (stores) ── 'coupon_stores_count': '12,800+ stores nationwide', + + // ── Purchase / Order Confirm ── + 'purchase_title': 'Confirm Order', + 'purchase_quantity': 'Quantity', + 'purchase_wechat_pay': 'WeChat Pay', + 'purchase_payment_method': 'Payment Method', + 'purchase_unit_price': 'Unit Price', + 'purchase_count': 'Qty', + 'purchase_confirm_pay': 'Confirm Payment', + 'purchase_buying_note': 'You are purchasing a platform-guaranteed consumer coupon', + 'purchase_save_badge': 'Save vs. face value', + 'purchase_price_detail': 'Price Details', + + // ── Payment Success ── + 'payment_success_title': 'Payment Successful', + 'payment_success_hint': 'Coupon is in your account. View in "My Coupons".', + 'payment_success_coupon_name': 'Coupon Name', + 'payment_success_pay_amount': 'Amount Paid', + 'payment_success_order_no': 'Order Number', + 'payment_success_pay_time': 'Payment Time', + 'payment_success_view_coupon': 'View My Coupons', + 'payment_success_continue': 'Continue Browsing', + + // ── My Coupon Detail ── + 'my_coupon_detail_title': 'Coupon Details', + 'my_coupon_active': 'Active', + 'my_coupon_show_qr_hint': 'Show this QR code to the merchant for scanning', + 'my_coupon_switch_barcode': 'Switch to Barcode', + 'my_coupon_purchase_price': 'Purchase Price', + 'my_coupon_order_no': 'Order Number', + 'my_coupon_resell_count': 'Resale Remaining', + 'my_coupon_usage_note': 'Usage Instructions', + 'my_coupon_use_in_store': 'Valid at all stores nationwide', + 'my_coupon_use_in_time': 'Use before expiry date', + 'my_coupon_one_per_visit': 'One coupon per visit', + 'my_coupon_no_cash': 'Not redeemable for cash', + 'my_coupon_transfer': 'Gift to Friend', + 'my_coupon_sell': 'List for Sale', + 'my_coupon_extract_wallet': 'Extract to External Wallet', + 'my_coupon_require_kyc': 'Requires KYC L2+ Verification', + 'my_coupon_view_trades': 'View Trade History', + 'my_coupon_help': 'Help', + 'my_coupon_sell_in_app': 'Use the App to sell coupons', + + // ── Transfer ── + 'transfer_title': 'Gift', + 'transfer_share_card': 'Share Mini Program Card', + 'transfer_share_desc': 'Share directly with WeChat friends', + 'transfer_scan': 'Scan to Gift', + 'transfer_scan_desc': 'Scan recipient\'s receive code', + 'transfer_input': 'Enter ID', + 'transfer_input_desc': 'Enter recipient info manually', + 'transfer_recent': 'Recent Gifts', + 'transfer_manage': 'Manage', + 'transfer_no_recent': 'No recent gift records', + 'transfer_expired': 'Expired', + 'transfer_refresh': 'Refresh', + 'transfer_history': 'Gift History', + 'transfer_last_transfer': 'Last Gift', + 'transfer_input_recipient': 'Enter Recipient', + 'transfer_recipient_hint': 'ID / Email / Phone', + 'transfer_paste': 'Paste', + 'transfer_select_coupon': 'Select Coupon to Gift', + 'transfer_confirm': 'Confirm Gift', + 'transfer_to': 'Gift to', + 'transfer_confirm_btn': 'Confirm Gift', + 'transfer_contact_email': 'Email', + 'transfer_contact_phone': 'Phone', + 'transfer_warning': 'Gifts cannot be revoked. Please verify the details.', + 'transfer_success': 'Gift Sent Successfully', + 'transfer_outgoing': 'Sent', + 'transfer_incoming': 'Received', + + // ── AI Chat ── + 'ai_chat_title': 'AI Assistant', + 'ai_chat_greeting': 'Hi! I\'m the Genex AI Assistant. I can help you find the best coupon deals. Try asking me:', + 'ai_chat_suggest1': 'Recommend coupons for me', + 'ai_chat_suggest2': 'Is the Starbucks coupon worth it?', + 'ai_chat_suggest3': 'Compare prices for me', + 'ai_chat_suggest4': 'My coupons are expiring soon, what should I do?', + 'ai_chat_input_hint': 'Ask me anything about coupons...', + 'ai_chat_send': 'Send', + + // ── Orders (additional) ── + 'order_view_detail': 'View Details', + 'order_empty': 'No orders yet', + 'order_status_paid': 'Paid', + + // ── Messages ── + 'messages_title': 'Messages', + 'messages_mark_all_read': 'Mark All Read', + 'messages_tab_trade': 'Trades', + 'messages_tab_expiry': 'Expiring', + 'messages_tab_announcement': 'Announcements', + 'messages_type_price': 'Price Alert', + 'messages_empty': 'No messages', + + // ── Wallet ── + 'wallet_balance': 'My Balance', + 'wallet_total_balance': 'Total Balance', + 'wallet_available': 'Available', + 'wallet_frozen': 'Frozen', + 'wallet_records': 'Transaction Records', + 'wallet_buy_in': 'Buy', + 'wallet_sell_out': 'Sell', + 'wallet_gift_transfer': 'Gift', + 'wallet_redeem_use': 'Redeem', + 'wallet_read_only_hint': 'Use the App for full wallet features', + 'wallet_filter': 'Filter', + 'wallet_deposit_record': 'Deposit', + 'wallet_withdraw_record': 'Withdraw', + + // ── Settings ── + 'settings_title': 'Settings', + 'settings_notifications': 'Notifications', + 'settings_trade_notify': 'Trade Notifications', + 'settings_expiry_remind': 'Expiry Reminders', + 'settings_marketing_push': 'Promotional Notifications', + 'settings_general': 'General', + 'settings_clear_cache': 'Clear Cache', + 'settings_about': 'About', + 'settings_version': 'Version', + 'settings_help_center': 'Help Center', + 'settings_logout': 'Log Out', + 'settings_select_language': 'Select Language', + 'settings_select_currency': 'Select Currency', + 'settings_currency_note': 'This affects the pricing currency display', + 'settings_cache_cleared': 'Cache cleared', + + // ── KYC ── + 'kyc_title': 'Identity Verification', + 'kyc_current_level': 'Current Level', + 'kyc_daily_limit': 'Daily Purchase Limit', + 'kyc_l1_title': 'L1 Basic Verification', + 'kyc_l1_desc': 'Phone + Email Verification', + 'kyc_l1_limit': '¥500', + 'kyc_l1_feature': 'Buy coupons, redeem at stores', + 'kyc_l2_title': 'L2 Identity Verification', + 'kyc_l2_desc': 'ID Card / Passport Verification', + 'kyc_l2_limit': '¥5,000', + 'kyc_l2_feature': 'Unlock secondary market, P2P gifting', + 'kyc_l3_title': 'L3 Advanced Verification', + 'kyc_l3_desc': 'Video verification + Address proof', + 'kyc_l3_limit': 'Unlimited', + 'kyc_l3_feature': 'Unlock large transactions, unlimited withdrawals', + 'kyc_completed': 'Completed', + 'kyc_go_verify': 'Verify Now', + 'kyc_verify_in_app': 'Please use the App to complete verification', + + // ── Detail (enhancement) ── + 'detail_nearby_stores': 'Nearby Stores', + 'detail_view_all': 'View All', + 'detail_similar_coupons': 'Similar Coupons', + + // ── AI Assistant (profile) ── + 'profile_ai_assistant': 'AI Assistant', }, 'ja-JP': { @@ -900,5 +1212,161 @@ const translations: Record> = { // ── Coupon Detail (stores) ── 'coupon_stores_count': '全国12,800+店舗', + + // ── Purchase / Order Confirm ── + 'purchase_title': '注文確認', + 'purchase_quantity': '購入数量', + 'purchase_wechat_pay': 'WeChat Pay', + 'purchase_payment_method': '支払い方法', + 'purchase_unit_price': '単価', + 'purchase_count': '数量', + 'purchase_confirm_pay': '支払いを確認', + 'purchase_buying_note': 'プラットフォーム保証付きの消費クーポンを購入します', + 'purchase_save_badge': '額面よりお得', + 'purchase_price_detail': '価格詳細', + + // ── Payment Success ── + 'payment_success_title': '支払い完了', + 'payment_success_hint': 'クーポンがアカウントに届きました。「マイクーポン」でご確認ください。', + 'payment_success_coupon_name': 'クーポン名', + 'payment_success_pay_amount': '支払い金額', + 'payment_success_order_no': '注文番号', + 'payment_success_pay_time': '支払い日時', + 'payment_success_view_coupon': 'マイクーポンを見る', + 'payment_success_continue': '買い物を続ける', + + // ── My Coupon Detail ── + 'my_coupon_detail_title': 'クーポン詳細', + 'my_coupon_active': '利用可能', + 'my_coupon_show_qr_hint': 'このQRコードを店舗スタッフに提示してください', + 'my_coupon_switch_barcode': 'バーコードに切替', + 'my_coupon_purchase_price': '購入価格', + 'my_coupon_order_no': '注文番号', + 'my_coupon_resell_count': '残り転売回数', + 'my_coupon_usage_note': '利用案内', + 'my_coupon_use_in_store': '全国の店舗で利用可能', + 'my_coupon_use_in_time': '有効期限内にご利用ください', + 'my_coupon_one_per_visit': '1回の来店につき1枚まで', + 'my_coupon_no_cash': '現金との交換不可', + 'my_coupon_transfer': '友達にプレゼント', + 'my_coupon_sell': '出品する', + 'my_coupon_extract_wallet': '外部ウォレットに引出', + 'my_coupon_require_kyc': 'KYC L2+認証が必要', + 'my_coupon_view_trades': '取引履歴を見る', + 'my_coupon_help': 'ヘルプ', + 'my_coupon_sell_in_app': 'アプリで出品してください', + + // ── Transfer ── + 'transfer_title': 'ギフト', + 'transfer_share_card': 'ミニプログラムカードをシェア', + 'transfer_share_desc': 'WeChat友達に直接シェア', + 'transfer_scan': 'スキャンしてギフト', + 'transfer_scan_desc': '相手の受取コードをスキャン', + 'transfer_input': 'IDを入力', + 'transfer_input_desc': '相手の情報を手動入力', + 'transfer_recent': '最近のギフト', + 'transfer_manage': '管理', + 'transfer_no_recent': '最近のギフト記録はありません', + 'transfer_expired': '期限切れ', + 'transfer_refresh': '更新', + 'transfer_history': 'ギフト履歴', + 'transfer_last_transfer': '最後のギフト', + 'transfer_input_recipient': '受取人を入力', + 'transfer_recipient_hint': 'ID / メール / 電話番号', + 'transfer_paste': '貼り付け', + 'transfer_select_coupon': 'ギフトするクーポンを選択', + 'transfer_confirm': 'ギフトを確認', + 'transfer_to': 'ギフト先', + 'transfer_confirm_btn': 'ギフトを確認', + 'transfer_contact_email': 'メール', + 'transfer_contact_phone': '電話', + 'transfer_warning': 'ギフトは取り消しできません。情報をご確認ください。', + 'transfer_success': 'ギフト完了', + 'transfer_outgoing': '送信', + 'transfer_incoming': '受取', + + // ── AI Chat ── + 'ai_chat_title': 'AIアシスタント', + 'ai_chat_greeting': 'こんにちは!Genex AIアシスタントです。お得なクーポンを見つけるお手伝いをします。こんな質問をどうぞ:', + 'ai_chat_suggest1': 'おすすめのクーポンは?', + 'ai_chat_suggest2': 'スタバのクーポンは買い?', + 'ai_chat_suggest3': '価格を比較して', + 'ai_chat_suggest4': 'クーポンの期限が近い、どうする?', + 'ai_chat_input_hint': 'クーポンについて何でも聞いてください...', + 'ai_chat_send': '送信', + + // ── Orders (additional) ── + 'order_view_detail': '詳細を見る', + 'order_empty': '注文はありません', + 'order_status_paid': '支払い済み', + + // ── Messages ── + 'messages_title': 'メッセージ', + 'messages_mark_all_read': 'すべて既読', + 'messages_tab_trade': '取引', + 'messages_tab_expiry': '期限切れ', + 'messages_tab_announcement': 'お知らせ', + 'messages_type_price': '価格アラート', + 'messages_empty': 'メッセージはありません', + + // ── Wallet ── + 'wallet_balance': '残高', + 'wallet_total_balance': '総残高', + 'wallet_available': '利用可能', + 'wallet_frozen': '凍結中', + 'wallet_records': '取引履歴', + 'wallet_buy_in': '購入', + 'wallet_sell_out': '売却', + 'wallet_gift_transfer': 'ギフト', + 'wallet_redeem_use': '使用', + 'wallet_read_only_hint': 'フル機能はアプリをご利用ください', + 'wallet_filter': 'フィルター', + 'wallet_deposit_record': '入金', + 'wallet_withdraw_record': '出金', + + // ── Settings ── + 'settings_title': '設定', + 'settings_notifications': '通知設定', + 'settings_trade_notify': '取引通知', + 'settings_expiry_remind': '期限リマインダー', + 'settings_marketing_push': 'プロモーション通知', + 'settings_general': '一般', + 'settings_clear_cache': 'キャッシュを消去', + 'settings_about': 'アプリについて', + 'settings_version': 'バージョン', + 'settings_help_center': 'ヘルプセンター', + 'settings_logout': 'ログアウト', + 'settings_select_language': '言語を選択', + 'settings_select_currency': '通貨を選択', + 'settings_currency_note': 'この設定は価格表示の通貨に影響します', + 'settings_cache_cleared': 'キャッシュを消去しました', + + // ── KYC ── + 'kyc_title': '本人確認', + 'kyc_current_level': '現在の認証レベル', + 'kyc_daily_limit': '1日の購入限度額', + 'kyc_l1_title': 'L1 基本認証', + 'kyc_l1_desc': '電話番号 + メール認証', + 'kyc_l1_limit': '¥500', + 'kyc_l1_feature': 'クーポン購入・使用が可能', + 'kyc_l2_title': 'L2 本人確認', + 'kyc_l2_desc': '身分証明書 / パスポート認証', + 'kyc_l2_limit': '¥5,000', + 'kyc_l2_feature': '二次市場取引・P2Pギフトを解放', + 'kyc_l3_title': 'L3 上級認証', + 'kyc_l3_desc': 'ビデオ認証 + 住所証明', + 'kyc_l3_limit': '無制限', + 'kyc_l3_feature': '大口取引・無制限出金を解放', + 'kyc_completed': '完了', + 'kyc_go_verify': '認証する', + 'kyc_verify_in_app': 'アプリで認証を完了してください', + + // ── Detail (enhancement) ── + 'detail_nearby_stores': '近くの店舗', + 'detail_view_all': 'すべて見る', + 'detail_similar_coupons': '似ているクーポン', + + // ── AI Assistant (profile) ── + 'profile_ai_assistant': 'AIアシスタント', }, }; diff --git a/frontend/miniapp/src/pages/ai-chat/index.tsx b/frontend/miniapp/src/pages/ai-chat/index.tsx new file mode 100644 index 0000000..91763d9 --- /dev/null +++ b/frontend/miniapp/src/pages/ai-chat/index.tsx @@ -0,0 +1,210 @@ +import React from 'react'; +import { t } from '@/i18n'; +// Taro mini-program - AI Chat Assistant + +/** + * P1. AI Chat - Full-screen AI chat interface + * + * Suggestion chips, message list, fixed input bar + */ + +interface ChatMessage { + isAi: boolean; + text: string; +} + +const MOCK_AI_RESPONSE = + '根据您的偏好和消费习惯,推荐以下高性价比券:\n\n1. 星巴克 ¥25 礼品卡 - 当前售价 ¥21.25(8.5折),信用AAA\n2. Amazon ¥100 购物券 - 当前售价 ¥85(8.5折),信用AA\n\n这两张券的折扣率在同类中最优,且发行方信用等级高。'; + +const AiChatPage: React.FC = () => { + const [messages, setMessages] = React.useState([ + { isAi: true, text: t('ai_chat_greeting') }, + ]); + const [inputText, setInputText] = React.useState(''); + const scrollId = React.useRef('msg-0'); + const [scrollToId, setScrollToId] = React.useState('msg-0'); + + const suggestions = [ + t('ai_chat_suggest1'), + t('ai_chat_suggest2'), + t('ai_chat_suggest3'), + t('ai_chat_suggest4'), + ]; + + const addMessages = (userText: string) => { + const userMsg: ChatMessage = { isAi: false, text: userText }; + setMessages((prev) => { + const next = [...prev, userMsg]; + const userIdx = next.length - 1; + setScrollToId(`msg-${userIdx}`); + return next; + }); + setInputText(''); + + setTimeout(() => { + setMessages((prev) => { + const next = [...prev, { isAi: true, text: MOCK_AI_RESPONSE }]; + const aiIdx = next.length - 1; + setScrollToId(`msg-${aiIdx}`); + return next; + }); + }, 800); + }; + + const handleSend = () => { + const text = inputText.trim(); + if (!text) return; + addMessages(text); + }; + + const handleChipTap = (chip: string) => { + addMessages(chip); + }; + + return ( + + {/* Header */} + + {t('ai_chat_title')} + + + {/* Message List */} + + {messages.map((msg, i) => ( + + {msg.isAi && ( + + + + )} + + + {msg.text} + + + + ))} + + {/* Suggestion Chips */} + {messages.length <= 2 && ( + + + {suggestions.map((chip, i) => ( + handleChipTap(chip)} + > + {chip} + + ))} + + + )} + + {/* Bottom spacer for scroll */} + + + + {/* Input Bar */} + + + setInputText(e.detail.value)} + onConfirm={handleSend} + confirmType="send" + /> + + + + + + + ); +}; + +export default AiChatPage; + +/* +CSS (index.scss): + +.chat-page { + display: flex; flex-direction: column; + height: 100vh; background: #F8F9FC; +} + +.chat-header { + display: flex; align-items: center; justify-content: center; + height: 88rpx; background: white; + border-bottom: 1rpx solid #F1F3F8; + padding-top: env(safe-area-inset-top); +} +.chat-header-title { font-size: 32rpx; font-weight: 600; color: #141723; } + +.chat-messages { + flex: 1; padding: 24rpx 32rpx; overflow-y: auto; +} + +.msg-row { display: flex; margin-bottom: 24rpx; } +.msg-ai { justify-content: flex-start; } +.msg-user { justify-content: flex-end; } + +.ai-avatar { + width: 64rpx; height: 64rpx; border-radius: 16rpx; + background: linear-gradient(135deg, #6C5CE7, #9B8FFF); + display: flex; align-items: center; justify-content: center; + margin-right: 16rpx; flex-shrink: 0; +} +.ai-avatar-icon { font-size: 28rpx; } + +.msg-bubble { max-width: 75%; border-radius: 20rpx; padding: 20rpx 24rpx; } +.bubble-ai { background: #F1F3F8; border-top-left-radius: 4rpx; } +.bubble-user { background: #6C5CE7; border-top-right-radius: 4rpx; } + +.msg-text { font-size: 28rpx; line-height: 1.6; white-space: pre-wrap; } +.text-ai { color: #141723; } +.text-user { color: white; } + +.chips-container { padding: 8rpx 0 16rpx; } +.chips-scroll { white-space: nowrap; } +.chip { + display: inline-block; margin-right: 16rpx; + padding: 14rpx 24rpx; background: #F3F1FF; + border: 1rpx solid rgba(108,92,231,0.2); + border-radius: 999rpx; +} +.chip-text { font-size: 24rpx; color: #6C5CE7; white-space: nowrap; } + +.msg-spacer { height: 32rpx; } + +.input-bar { + display: flex; align-items: center; + padding: 16rpx 24rpx; padding-bottom: calc(16rpx + env(safe-area-inset-bottom)); + background: white; border-top: 1rpx solid #F1F3F8; +} +.input-wrap { + flex: 1; height: 72rpx; background: #F1F3F8; border-radius: 999rpx; + display: flex; align-items: center; padding: 0 24rpx; +} +.chat-input { flex: 1; font-size: 28rpx; color: #141723; background: transparent; } +.send-btn { + width: 72rpx; height: 72rpx; margin-left: 16rpx; + background: #6C5CE7; border-radius: 50%; + display: flex; align-items: center; justify-content: center; +} +.send-icon { color: white; font-size: 36rpx; font-weight: 700; } +*/ diff --git a/frontend/miniapp/src/pages/detail/index.tsx b/frontend/miniapp/src/pages/detail/index.tsx index c809550..6063498 100644 --- a/frontend/miniapp/src/pages/detail/index.tsx +++ b/frontend/miniapp/src/pages/detail/index.tsx @@ -1,4 +1,5 @@ import React from 'react'; +import Taro from '@tarojs/taro'; import { t } from '@/i18n'; // Taro mini-program - Coupon Detail + Purchase @@ -80,13 +81,60 @@ const DetailPage: React.FC = () => { + {/* Nearby Stores */} + + + {t('detail_nearby_stores')} + {t('detail_view_all')} > + + {[ + { name: '星巴克 国贸店', distance: '0.8km' }, + { name: '星巴克 三里屯店', distance: '1.2km' }, + { name: '星巴克 望京店', distance: '2.5km' }, + ].map((store, i) => ( + + {store.name} + + {store.distance} + + + ))} + + + {/* Similar Coupons */} + + + {t('detail_similar_coupons')} + {t('more')} > + + + {[ + { name: 'Costa ¥20 咖啡券', price: '¥16.00', discount: '8折' }, + { name: 'Luckin ¥15 咖啡券', price: '¥12.75', discount: '8.5折' }, + { name: 'Tim Hortons ¥18 饮品券', price: '¥14.40', discount: '8折' }, + { name: 'Pacific Coffee ¥22', price: '¥17.60', discount: '8折' }, + ].map((item, i) => ( + + + 🎫 + + {item.name} + + {item.price} + {item.discount} + + + ))} + + + {/* Bottom Buy Bar */} {t('order_total')} ¥21.25 - + Taro.navigateTo({ url: '/pages/purchase/index' })}> {t('coupon_buy_now')} @@ -175,6 +223,52 @@ CSS (index.scss): .utility-icon { color: #00C48C; margin-right: 12rpx; font-weight: 700; } .utility-text { font-size: 24rpx; color: #3D4459; } +.section-card { + background: white; border-radius: 16rpx; padding: 24rpx; + margin: 0 32rpx 16rpx; border: 1rpx solid #F1F3F8; +} +.section-header { + display: flex; justify-content: space-between; align-items: center; + margin-bottom: 16rpx; +} +.section-title { font-size: 28rpx; font-weight: 600; color: #141723; } +.section-link { font-size: 24rpx; color: #6C5CE7; } + +.store-row { + display: flex; justify-content: space-between; align-items: center; + padding: 16rpx 0; border-bottom: 1rpx solid #F1F3F8; +} +.store-row:last-child { border-bottom: none; } +.store-name { font-size: 26rpx; color: #141723; } +.store-distance-badge { + padding: 4rpx 16rpx; background: #F3F1FF; border-radius: 999rpx; +} +.store-distance { font-size: 22rpx; color: #6C5CE7; font-weight: 500; } + +.similar-scroll { white-space: nowrap; } +.similar-card { + display: inline-block; width: 200rpx; margin-right: 16rpx; + background: #F8F9FC; border-radius: 12rpx; padding: 16rpx; + vertical-align: top; +} +.similar-icon-wrap { + width: 100%; height: 100rpx; background: #F3F1FF; border-radius: 8rpx; + display: flex; align-items: center; justify-content: center; + margin-bottom: 12rpx; +} +.similar-icon { font-size: 36rpx; } +.similar-name { + font-size: 22rpx; color: #141723; font-weight: 500; + overflow: hidden; text-overflow: ellipsis; white-space: nowrap; + display: block; margin-bottom: 8rpx; +} +.similar-price-row { display: flex; align-items: center; } +.similar-price { font-size: 24rpx; color: #6C5CE7; font-weight: 700; } +.similar-discount { + font-size: 18rpx; color: #9B8FFF; margin-left: 8rpx; + padding: 2rpx 8rpx; background: #F3F1FF; border-radius: 999rpx; +} + .buy-bar { position: fixed; bottom: 0; left: 0; right: 0; display: flex; align-items: center; justify-content: space-between; diff --git a/frontend/miniapp/src/pages/home/index.tsx b/frontend/miniapp/src/pages/home/index.tsx index 378845f..7a2124b 100644 --- a/frontend/miniapp/src/pages/home/index.tsx +++ b/frontend/miniapp/src/pages/home/index.tsx @@ -1,4 +1,5 @@ import React from 'react'; +import Taro from '@tarojs/taro'; import { t } from '@/i18n'; // Taro mini-program component (WeChat / Alipay) @@ -13,7 +14,7 @@ const HomePage: React.FC = () => { return ( {/* Search Bar */} - + Taro.navigateTo({ url: '/pages/search/index' })}> 🔍 {t('home_search_hint')} @@ -59,7 +60,7 @@ const HomePage: React.FC = () => { {/* AI Suggestion (轻量版) */} - + Taro.navigateTo({ url: '/pages/ai-chat/index' })}> {t('home_recommended')} @@ -81,7 +82,7 @@ const HomePage: React.FC = () => { { brand: 'Nike', name: 'Nike ¥80 运动券', price: '¥68.00', face: '¥80', discount: '8.5折' }, { brand: 'Target', name: 'Target ¥30 折扣券', price: '¥24.00', face: '¥30', discount: '8折' }, ].map((coupon, i) => ( - + Taro.navigateTo({ url: '/pages/detail/index' })}> 🎫 diff --git a/frontend/miniapp/src/pages/kyc/index.tsx b/frontend/miniapp/src/pages/kyc/index.tsx new file mode 100644 index 0000000..32b0565 --- /dev/null +++ b/frontend/miniapp/src/pages/kyc/index.tsx @@ -0,0 +1,183 @@ +import React from 'react'; +import Taro from '@tarojs/taro'; +import { t } from '@/i18n'; +// Taro mini-program component + +/** + * P2. KYC 身份认证 + * + * 当前认证等级 + 3级认证卡片 + */ + +interface KycTier { + level: string; + titleKey: string; + descKey: string; + featureKey: string; + limitKey: string; + status: 'completed' | 'locked'; + icon: string; +} + +const tiers: KycTier[] = [ + { + level: 'L1', + titleKey: 'kyc_l1_title', + descKey: 'kyc_l1_desc', + featureKey: 'kyc_l1_feature', + limitKey: 'kyc_l1_limit', + status: 'completed', + icon: '\u2705', + }, + { + level: 'L2', + titleKey: 'kyc_l2_title', + descKey: 'kyc_l2_desc', + featureKey: 'kyc_l2_feature', + limitKey: 'kyc_l2_limit', + status: 'locked', + icon: '\uD83D\uDD12', + }, + { + level: 'L3', + titleKey: 'kyc_l3_title', + descKey: 'kyc_l3_desc', + featureKey: 'kyc_l3_feature', + limitKey: 'kyc_l3_limit', + status: 'locked', + icon: '\uD83D\uDD12', + }, +]; + +const KycPage: React.FC = () => { + const handleVerify = () => { + Taro.showModal({ + title: '', + content: t('kyc_verify_in_app'), + showCancel: false, + }); + }; + + return ( + + {/* Current Level Card */} + + + {'\uD83D\uDEE1\uFE0F'} + {t('kyc_current_level')} + + {t('kyc_l1_title')} + + {t('kyc_daily_limit')} + {t('kyc_l1_limit')} + + + + {/* Tier Cards */} + + {tiers.map((tier) => ( + + + + {tier.icon} + {t(tier.titleKey)} + + {tier.status === 'completed' && ( + + {t('kyc_completed')} + + )} + + + {t(tier.descKey)} + + + + + {t(tier.featureKey)} + + + + {t('kyc_daily_limit')}: {t(tier.limitKey)} + + + + {tier.status === 'locked' && ( + + {t('kyc_go_verify')} + + )} + + ))} + + + ); +}; + +export default KycPage; + +/* +CSS: + +.kyc-page { background: #F8F9FC; min-height: 100vh; padding-bottom: 60rpx; } + +.kyc-current-card { + margin: 32rpx; padding: 36rpx 32rpx; + background: linear-gradient(135deg, #6C5CE7, #4834D4); + border-radius: 24rpx; +} +.kyc-current-top { + display: flex; align-items: center; margin-bottom: 16rpx; +} +.kyc-shield-icon { font-size: 36rpx; margin-right: 10rpx; } +.kyc-current-label { font-size: 24rpx; color: rgba(255,255,255,0.7); } +.kyc-current-title { + display: block; font-size: 40rpx; font-weight: 700; color: white; + margin-bottom: 20rpx; +} +.kyc-current-limit { + display: flex; align-items: center; + padding-top: 20rpx; border-top: 1rpx solid rgba(255,255,255,0.2); +} +.kyc-limit-label { font-size: 24rpx; color: rgba(255,255,255,0.6); margin-right: 12rpx; } +.kyc-limit-value { font-size: 30rpx; font-weight: 600; color: white; } + +.kyc-tiers { padding: 0 32rpx; } + +.kyc-tier-card { + background: white; border-radius: 20rpx; padding: 28rpx; + margin-bottom: 20rpx; border: 2rpx solid #F1F3F8; +} +.kyc-tier-completed { border-color: #00C48C; } +.kyc-tier-locked { border-color: #F1F3F8; } + +.kyc-tier-header { + display: flex; align-items: center; justify-content: space-between; + margin-bottom: 12rpx; +} +.kyc-tier-icon-row { display: flex; align-items: center; } +.kyc-tier-icon { font-size: 32rpx; margin-right: 12rpx; } +.kyc-tier-title { font-size: 30rpx; font-weight: 600; color: #141723; } + +.kyc-completed-badge { + padding: 6rpx 16rpx; background: #E6FAF3; border-radius: 999rpx; +} +.kyc-completed-text { font-size: 22rpx; color: #00C48C; font-weight: 500; } + +.kyc-tier-desc { font-size: 24rpx; color: #5C6478; margin-bottom: 16rpx; } + +.kyc-tier-features { margin-bottom: 16rpx; } +.kyc-feature-row { display: flex; align-items: flex-start; margin-bottom: 8rpx; } +.kyc-feature-dot { color: #A0A8BE; margin-right: 10rpx; font-size: 24rpx; } +.kyc-feature-text { font-size: 24rpx; color: #5C6478; } + +.kyc-verify-btn { + display: flex; align-items: center; justify-content: center; + padding: 18rpx 0; border: 2rpx solid #6C5CE7; + border-radius: 12rpx; margin-top: 8rpx; +} +.kyc-verify-text { font-size: 28rpx; color: #6C5CE7; font-weight: 500; } +*/ diff --git a/frontend/miniapp/src/pages/messages/index.tsx b/frontend/miniapp/src/pages/messages/index.tsx new file mode 100644 index 0000000..1b856d8 --- /dev/null +++ b/frontend/miniapp/src/pages/messages/index.tsx @@ -0,0 +1,172 @@ +import React from 'react'; +import { t } from '@/i18n'; +// Taro mini-program component + +/** + * P2. 消息中心 + * + * 交易通知、到期提醒、价格提醒、公告 + */ + +interface Message { + id: number; + title: string; + body: string; + time: string; + type: 'transaction' | 'expiry' | 'price' | 'announcement'; + isRead: boolean; +} + +const mockMessages: Message[] = [ + { id: 1, title: '购买成功', body: '您已成功购买 星巴克 ¥25 礼品卡,花费 ¥21.25', time: '14:32', type: 'transaction', isRead: false }, + { id: 2, title: '券即将到期', body: 'Target ¥30 折扣券 将于3天后到期', time: '10:15', type: 'expiry', isRead: false }, + { id: 3, title: '价格提醒', body: 'Amazon ¥100 购物券 价格降至 ¥82', time: '昨天', type: 'price', isRead: true }, + { id: 4, title: '出售成交', body: 'Nike ¥80 运动券 已售出,收入 ¥68', time: '02/07', type: 'transaction', isRead: true }, + { id: 5, title: '核销成功', body: 'Walmart ¥50 生活券 已核销', time: '02/06', type: 'transaction', isRead: true }, +]; + +const typeIconMap: Record = { + transaction: '\uD83D\uDD04', + expiry: '\u23F0', + price: '\uD83D\uDCC8', + announcement: '\uD83D\uDCE2', +}; + +const typeColorMap: Record = { + transaction: '#6C5CE7', + expiry: '#FF9500', + price: '#00C48C', + announcement: '#3498DB', +}; + +const MessagesPage: React.FC = () => { + const [activeTab, setActiveTab] = React.useState(0); + + const tabs = [ + { label: t('all'), filter: null }, + { label: t('messages_tab_trade'), filter: 'transaction' }, + { label: t('messages_tab_expiry'), filter: 'expiry' }, + { label: t('messages_tab_announcement'), filter: 'announcement' }, + ]; + + const filteredMessages = activeTab === 0 + ? mockMessages + : mockMessages.filter((m) => m.type === tabs[activeTab].filter); + + return ( + + {/* Header */} + + {t('messages_title')} + {t('messages_mark_all_read')} + + + {/* Tab Row */} + + {tabs.map((tab, i) => ( + setActiveTab(i)} + > + + {tab.label} + + + ))} + + + {/* Message List */} + + {filteredMessages.length === 0 ? ( + + {typeIconMap.announcement} + {t('messages_empty')} + + ) : ( + filteredMessages.map((msg) => ( + + + {typeIconMap[msg.type]} + + + + + {msg.title} + + {msg.time} + + {msg.body} + + {!msg.isRead && } + + )) + )} + + + ); +}; + +export default MessagesPage; + +/* +CSS: + +.messages-page { background: #F8F9FC; min-height: 100vh; } + +.msg-header { + display: flex; align-items: center; justify-content: space-between; + padding: 48rpx 32rpx 24rpx; background: white; +} +.msg-header-title { font-size: 36rpx; font-weight: 700; color: #141723; } +.msg-mark-read { font-size: 24rpx; color: #6C5CE7; } + +.msg-tabs { + display: flex; padding: 0 32rpx 20rpx; background: white; + border-bottom: 1rpx solid #F1F3F8; gap: 12rpx; +} +.msg-tab { + padding: 10rpx 24rpx; border-radius: 999rpx; + background: #F1F3F8; +} +.msg-tab-active { background: #6C5CE7; } +.msg-tab-text { font-size: 24rpx; color: #5C6478; } +.msg-tab-text-active { color: white; font-weight: 500; } + +.msg-list { padding: 16rpx 0; } + +.msg-item { + display: flex; align-items: flex-start; + padding: 24rpx 32rpx; background: white; + margin-bottom: 2rpx; position: relative; +} +.msg-icon-circle { + width: 72rpx; height: 72rpx; border-radius: 50%; + display: flex; align-items: center; justify-content: center; + flex-shrink: 0; +} +.msg-icon-emoji { font-size: 32rpx; } +.msg-content { flex: 1; margin-left: 20rpx; margin-right: 16rpx; } +.msg-title-row { + display: flex; align-items: center; justify-content: space-between; +} +.msg-title { font-size: 28rpx; color: #141723; } +.msg-title-unread { font-weight: 600; } +.msg-time { font-size: 22rpx; color: #A0A8BE; flex-shrink: 0; } +.msg-body { + font-size: 24rpx; color: #5C6478; margin-top: 8rpx; + display: -webkit-box; -webkit-line-clamp: 2; + -webkit-box-orient: vertical; overflow: hidden; +} +.msg-unread-dot { + width: 16rpx; height: 16rpx; border-radius: 50%; + background: #6C5CE7; flex-shrink: 0; margin-top: 6rpx; +} + +.msg-empty { + display: flex; flex-direction: column; align-items: center; + padding: 120rpx 0; +} +.msg-empty-icon { font-size: 80rpx; margin-bottom: 20rpx; } +.msg-empty-text { font-size: 28rpx; color: #A0A8BE; } +*/ diff --git a/frontend/miniapp/src/pages/my-coupon-detail/index.tsx b/frontend/miniapp/src/pages/my-coupon-detail/index.tsx new file mode 100644 index 0000000..9acf4fd --- /dev/null +++ b/frontend/miniapp/src/pages/my-coupon-detail/index.tsx @@ -0,0 +1,176 @@ +import React from 'react'; +import { t } from '@/i18n'; +import Taro from '@tarojs/taro'; +// Taro mini-program component (WeChat / Alipay) + +/** + * E2. 小程序核心页面 - 持有券详情 + * + * QR code card (gradient bg) + coupon info + action buttons + usage rules + * 支持转赠 / 出售操作 + */ + +const infoRows = [ + { label: t('coupon_face_value'), value: '¥25.00' }, + { label: t('my_coupon_purchase_price'), value: '¥21.25' }, + { label: t('coupon_valid_until'), value: '2026/12/31' }, + { label: t('my_coupon_order_no'), value: 'GNX-20260209-001234' }, + { label: t('my_coupon_resell_count'), value: '3次' }, +]; + +const usageRules = [ + t('my_coupon_use_in_store'), + t('my_coupon_use_in_time'), + t('my_coupon_one_per_visit'), + t('my_coupon_no_cash'), +]; + +const MyCouponDetailPage: React.FC = () => { + const handleTransfer = () => { + Taro.navigateTo({ url: '/pages/transfer/index' }); + }; + + const handleSell = () => { + Taro.showModal({ + title: t('my_coupon_sell'), + content: t('my_coupon_sell_in_app'), + showCancel: false, + confirmText: t('confirm'), + }); + }; + + return ( + + {/* QR Code Card */} + + + + Starbucks + + {t('my_coupon_active')} + + + 星巴克 ¥25 礼品卡 + + + + + QR CODE + + + + GNX-STB-A1B2C3D4 + {t('my_coupon_show_qr_hint')} + + + {/* Info Card */} + + {infoRows.map((item, i) => ( + + {item.label} + {item.value} + + ))} + + + {/* Action Buttons */} + + + {t('my_coupon_transfer')} + + + {t('my_coupon_sell')} + + + + {/* Usage Rules */} + + {t('my_coupon_usage_note')} + {usageRules.map((rule, i) => ( + + + {rule} + + ))} + + + ); +}; + +export default MyCouponDetailPage; + +/* +CSS (index.scss): + +.coupon-detail-page { background: #F8F9FC; min-height: 100vh; padding: 0 32rpx 40rpx; } + +.qr-card { + margin-top: 24rpx; padding: 32rpx; + background: linear-gradient(135deg, #6C5CE7, #9B8FFF); + border-radius: 24rpx; + display: flex; flex-direction: column; align-items: center; +} +.qr-header { width: 100%; margin-bottom: 24rpx; } +.qr-brand-row { + display: flex; align-items: center; justify-content: space-between; +} +.qr-brand { font-size: 26rpx; color: rgba(255,255,255,0.8); } +.qr-status { + padding: 4rpx 16rpx; background: rgba(0,196,140,0.2); + border: 1rpx solid rgba(0,196,140,0.5); border-radius: 999rpx; +} +.qr-status-text { font-size: 22rpx; color: #00E6A0; font-weight: 600; } +.qr-name { + font-size: 32rpx; font-weight: 600; color: white; margin-top: 8rpx; +} + +.qr-code-area { + width: 360rpx; height: 360rpx; + background: white; border-radius: 16rpx; + display: flex; align-items: center; justify-content: center; + margin-bottom: 20rpx; +} +.qr-placeholder { + width: 280rpx; height: 280rpx; + border: 4rpx dashed #E4E7F0; border-radius: 8rpx; + display: flex; align-items: center; justify-content: center; +} +.qr-placeholder-text { font-size: 28rpx; color: #A0A8BE; font-weight: 600; } + +.qr-code-text { + font-size: 28rpx; color: white; font-weight: 600; + font-family: 'Menlo', 'Courier New', monospace; + letter-spacing: 2rpx; margin-bottom: 8rpx; +} +.qr-hint { font-size: 22rpx; color: rgba(255,255,255,0.7); } + +.info-card { + background: white; border-radius: 16rpx; padding: 24rpx; + border: 1rpx solid #F1F3F8; margin-top: 20rpx; +} +.info-row { + display: flex; justify-content: space-between; + padding: 14rpx 0; border-bottom: 1rpx solid #F1F3F8; +} +.info-row:last-child { border-bottom: none; } +.info-label { font-size: 26rpx; color: #5C6478; } +.info-value { font-size: 26rpx; color: #141723; font-weight: 500; } + +.action-row { + display: flex; gap: 20rpx; margin-top: 20rpx; +} +.action-btn { flex: 1; height: 88rpx; border-radius: 16rpx; display: flex; align-items: center; justify-content: center; } +.action-btn-secondary { background: #F3F1FF; } +.action-btn-secondary-text { color: #6C5CE7; font-size: 28rpx; font-weight: 600; } +.action-btn-outline { background: white; border: 2rpx solid #F1F3F8; } +.action-btn-outline-text { color: #5C6478; font-size: 28rpx; font-weight: 600; } + +.rules-card { + background: white; border-radius: 16rpx; padding: 24rpx; + border: 1rpx solid #F1F3F8; margin-top: 20rpx; +} +.rules-title { font-size: 28rpx; font-weight: 600; color: #141723; margin-bottom: 16rpx; } +.rule-item { display: flex; align-items: flex-start; margin-bottom: 12rpx; } +.rule-dot { color: #A0A8BE; margin-right: 12rpx; font-size: 24rpx; } +.rule-text { font-size: 24rpx; color: #5C6478; line-height: 1.5; } +*/ diff --git a/frontend/miniapp/src/pages/my-coupons/index.tsx b/frontend/miniapp/src/pages/my-coupons/index.tsx index 4bb7007..0cc3179 100644 --- a/frontend/miniapp/src/pages/my-coupons/index.tsx +++ b/frontend/miniapp/src/pages/my-coupons/index.tsx @@ -1,4 +1,5 @@ import React from 'react'; +import Taro from '@tarojs/taro'; import { t } from '@/i18n'; // Taro mini-program component @@ -32,7 +33,7 @@ const MyCouponsPage: React.FC = () => { { brand: 'Amazon', name: 'Amazon ¥100 购物券', expiry: '2026-03-20', status: 'active' }, { brand: 'Nike', name: 'Nike ¥80 运动券', expiry: '2026-05-01', status: 'active' }, ].map((coupon, i) => ( - + Taro.navigateTo({ url: '/pages/my-coupon-detail/index' })}> 🎫 diff --git a/frontend/miniapp/src/pages/orders/index.tsx b/frontend/miniapp/src/pages/orders/index.tsx index 30d74d2..89d7020 100644 --- a/frontend/miniapp/src/pages/orders/index.tsx +++ b/frontend/miniapp/src/pages/orders/index.tsx @@ -1 +1,208 @@ -test \ No newline at end of file +import React from 'react'; +import { t } from '@/i18n'; +// Taro mini-program - Order List + +/** + * P1. Order List - Tab-filtered order management + * + * Tabs: all / pending payment / pending delivery / completed / cancelled + * Mock order data, status badges, filter logic + */ + +type OrderStatus = 'pending_payment' | 'pending_delivery' | 'completed' | 'cancelled'; + +interface Order { + id: string; + brand: string; + name: string; + price: string; + status: OrderStatus; + time: string; + orderNo: string; +} + +const STATUS_COLORS: Record = { + pending_payment: '#FF9500', + pending_delivery: '#6C5CE7', + completed: '#00C48C', + cancelled: '#A0A8BE', +}; + +const STATUS_LABELS: Record = { + pending_payment: 'order_pending_payment', + pending_delivery: 'order_pending_delivery', + completed: 'order_completed', + cancelled: 'order_cancelled', +}; + +const MOCK_ORDERS: Order[] = [ + { id: '1', brand: 'Starbucks', name: '星巴克 ¥25 礼品卡', price: '¥21.25', status: 'completed', time: '2026-02-09 14:32', orderNo: 'GNX-20260209-001' }, + { id: '2', brand: 'Amazon', name: 'Amazon ¥100 购物券', price: '¥85.00', status: 'pending_payment', time: '2026-02-10 10:15', orderNo: 'GNX-20260210-002' }, + { id: '3', brand: 'Target', name: 'Target ¥30 折扣券', price: '¥24.00', status: 'completed', time: '2026-02-08 16:20', orderNo: 'GNX-20260208-003' }, + { id: '4', brand: 'Nike', name: 'Nike ¥80 运动券', price: '¥68.00', status: 'pending_delivery', time: '2026-02-07 09:30', orderNo: 'GNX-20260207-004' }, + { id: '5', brand: 'Walmart', name: 'Walmart ¥50 生活券', price: '¥42.50', status: 'cancelled', time: '2026-02-06 11:45', orderNo: 'GNX-20260206-005' }, +]; + +const TAB_STATUS_MAP: (OrderStatus | null)[] = [ + null, + 'pending_payment', + 'pending_delivery', + 'completed', + 'cancelled', +]; + +const OrdersPage: React.FC = () => { + const [activeTab, setActiveTab] = React.useState(0); + + const tabs = [ + t('order_all'), + t('order_pending_payment'), + t('order_pending_delivery'), + t('order_completed'), + t('order_cancelled'), + ]; + + const filteredOrders = React.useMemo(() => { + const statusFilter = TAB_STATUS_MAP[activeTab]; + if (statusFilter === null) return MOCK_ORDERS; + return MOCK_ORDERS.filter((o) => o.status === statusFilter); + }, [activeTab]); + + return ( + + {/* Header */} + + {t('order_list')} + + + {/* Tab Row */} + + + {tabs.map((tab, i) => ( + setActiveTab(i)} + > + + {tab} + + {activeTab === i && } + + ))} + + + + {/* Order List */} + + {filteredOrders.length === 0 ? ( + + 📦 + {t('order_empty')} + + ) : ( + filteredOrders.map((order) => ( + + {/* Top row: icon + brand + name */} + + 🎫 + + {order.brand} + {order.name} + + + {/* Middle row: price + status */} + + {order.price} + + + {t(STATUS_LABELS[order.status])} + + + + {/* Bottom row: order no + time */} + + {order.orderNo} + {order.time} + + + )) + )} + + + + ); +}; + +export default OrdersPage; + +/* +CSS (index.scss): + +.orders-page { + display: flex; flex-direction: column; + min-height: 100vh; background: #F8F9FC; +} + +.orders-header { + display: flex; align-items: center; justify-content: center; + height: 88rpx; background: white; + padding-top: env(safe-area-inset-top); +} +.orders-header-title { font-size: 32rpx; font-weight: 600; color: #141723; } + +.tab-scroll { background: white; border-bottom: 1rpx solid #F1F3F8; } +.tab-row { + display: flex; white-space: nowrap; padding: 0 16rpx; +} +.tab-item { + display: inline-flex; flex-direction: column; align-items: center; + padding: 20rpx 24rpx; position: relative; +} +.tab-text { font-size: 26rpx; color: #5C6478; white-space: nowrap; } +.tab-text-active { color: #6C5CE7; font-weight: 600; } +.tab-indicator { + position: absolute; bottom: 0; left: 50%; transform: translateX(-50%); + width: 40rpx; height: 4rpx; background: #6C5CE7; border-radius: 2rpx; +} + +.order-list { flex: 1; padding: 16rpx 32rpx; } + +.order-card { + background: white; border-radius: 16rpx; padding: 24rpx; + margin-bottom: 16rpx; border: 1rpx solid #F1F3F8; +} + +.order-top { display: flex; align-items: center; margin-bottom: 16rpx; } +.order-coupon-icon { font-size: 40rpx; margin-right: 16rpx; } +.order-name-col { display: flex; flex-direction: column; } +.order-brand { font-size: 22rpx; color: #A0A8BE; } +.order-name { font-size: 28rpx; font-weight: 500; color: #141723; margin-top: 4rpx; } + +.order-mid { + display: flex; align-items: center; justify-content: space-between; + margin-bottom: 16rpx; padding-bottom: 16rpx; border-bottom: 1rpx solid #F1F3F8; +} +.order-price { font-size: 32rpx; font-weight: 700; color: #6C5CE7; } +.status-badge { padding: 4rpx 16rpx; border-radius: 999rpx; } +.status-text { font-size: 22rpx; font-weight: 600; } + +.order-bottom { display: flex; justify-content: space-between; } +.order-no { font-size: 22rpx; color: #A0A8BE; } +.order-time { font-size: 22rpx; color: #A0A8BE; } + +.empty-state { + display: flex; flex-direction: column; align-items: center; + padding: 120rpx 0; +} +.empty-icon { font-size: 80rpx; opacity: 0.3; margin-bottom: 16rpx; } +.empty-text { font-size: 28rpx; color: #A0A8BE; } + +.list-spacer { height: 120rpx; } +*/ diff --git a/frontend/miniapp/src/pages/payment-success/index.tsx b/frontend/miniapp/src/pages/payment-success/index.tsx new file mode 100644 index 0000000..b691b3b --- /dev/null +++ b/frontend/miniapp/src/pages/payment-success/index.tsx @@ -0,0 +1,117 @@ +import React from 'react'; +import { t } from '@/i18n'; +import Taro from '@tarojs/taro'; +// Taro mini-program component (WeChat / Alipay) + +/** + * E1. 小程序核心页面 - 支付成功页 + * + * 支付成功庆祝动画 + 订单信息卡片 + 跳转按钮 + */ + +const orderInfo = [ + { label: t('payment_success_coupon_name'), value: '星巴克 ¥25 礼品卡' }, + { label: t('payment_success_pay_amount'), value: '¥21.25' }, + { label: t('payment_success_order_no'), value: 'GNX-20260209-001234' }, + { label: t('payment_success_pay_time'), value: '2026-02-09 14:32' }, +]; + +const PaymentSuccessPage: React.FC = () => { + const handleViewCoupon = () => { + Taro.reLaunch({ url: '/pages/my-coupons/index' }); + }; + + const handleContinue = () => { + Taro.reLaunch({ url: '/pages/home/index' }); + }; + + return ( + + {/* Success Icon */} + + + + + {t('payment_success_title')} + {t('payment_success_hint')} + + + {/* Order Info Card */} + + {orderInfo.map((item, i) => ( + + {item.label} + {item.value} + + ))} + + + {/* Action Buttons */} + + + {t('payment_success_view_coupon')} + + + {t('payment_success_continue')} + + + + ); +}; + +export default PaymentSuccessPage; + +/* +CSS (index.scss): + +.success-page { background: #F8F9FC; min-height: 100vh; padding: 0 32rpx; } + +.success-hero { + display: flex; flex-direction: column; align-items: center; + padding-top: 120rpx; padding-bottom: 48rpx; +} +.success-circle { + width: 160rpx; height: 160rpx; border-radius: 50%; + background: linear-gradient(135deg, #00C48C, #00E6A0); + display: flex; align-items: center; justify-content: center; + box-shadow: 0 16rpx 40rpx rgba(0,196,140,0.3); +} +.success-check { color: white; font-size: 72rpx; font-weight: 700; } +.success-title { + font-size: 40rpx; font-weight: 700; color: #141723; + margin-top: 32rpx; +} +.success-hint { + font-size: 26rpx; color: #5C6478; margin-top: 12rpx; + text-align: center; +} + +.order-card { + background: white; border-radius: 16rpx; padding: 28rpx; + border: 1rpx solid #F1F3F8; margin-bottom: 40rpx; +} +.order-row { + display: flex; justify-content: space-between; + padding: 16rpx 0; border-bottom: 1rpx solid #F1F3F8; +} +.order-row:last-child { border-bottom: none; } +.order-label { font-size: 26rpx; color: #5C6478; } +.order-value { font-size: 26rpx; color: #141723; font-weight: 500; } + +.action-buttons { + display: flex; flex-direction: column; gap: 20rpx; + padding: 0 16rpx; +} +.btn-primary { + height: 96rpx; border-radius: 16rpx; + background: linear-gradient(135deg, #6C5CE7, #9B8FFF); + display: flex; align-items: center; justify-content: center; +} +.btn-primary-text { color: white; font-size: 30rpx; font-weight: 600; } +.btn-outline { + height: 96rpx; border-radius: 16rpx; + background: white; border: 2rpx solid #6C5CE7; + display: flex; align-items: center; justify-content: center; +} +.btn-outline-text { color: #6C5CE7; font-size: 30rpx; font-weight: 600; } +*/ diff --git a/frontend/miniapp/src/pages/profile/index.tsx b/frontend/miniapp/src/pages/profile/index.tsx index 188e35b..6500337 100644 --- a/frontend/miniapp/src/pages/profile/index.tsx +++ b/frontend/miniapp/src/pages/profile/index.tsx @@ -1,4 +1,5 @@ import React from 'react'; +import Taro from '@tarojs/taro'; import { t } from '@/i18n'; // Taro mini-program component @@ -43,10 +44,11 @@ const ProfilePage: React.FC = () => { {[ { icon: '🎫', label: t('my_coupons'), path: '/pages/my-coupons/index' }, { icon: '📋', label: t('profile_orders'), path: '/pages/orders/index' }, - { icon: '💳', label: t('profile_payment'), path: '' }, - { icon: '🔔', label: t('profile_notifications'), path: '' }, + { icon: '💳', label: t('profile_payment'), path: '/pages/wallet/index' }, + { icon: '🔔', label: t('profile_notifications'), path: '/pages/messages/index' }, + { icon: '✨', label: t('profile_ai_assistant'), path: '/pages/ai-chat/index' }, ].map((item, i) => ( - + Taro.navigateTo({ url: item.path })}> {item.icon} {item.label} @@ -56,12 +58,13 @@ const ProfilePage: React.FC = () => { {[ - { icon: '🌐', label: t('profile_language'), value: '简体中文' }, - { icon: '💰', label: t('profile_currency'), value: 'USD' }, - { icon: '❓', label: t('profile_help'), value: '' }, - { icon: '⚙️', label: t('profile_settings'), value: '' }, + { icon: '🛡️', label: t('profile_kyc'), path: '/pages/kyc/index', value: '' }, + { icon: '🌐', label: t('profile_language'), path: '/pages/settings/index', value: '简体中文' }, + { icon: '💰', label: t('profile_currency'), path: '/pages/settings/index', value: 'USD' }, + { icon: '❓', label: t('profile_help'), path: '', value: '' }, + { icon: '⚙️', label: t('profile_settings'), path: '/pages/settings/index', value: '' }, ].map((item, i) => ( - + item.path && Taro.navigateTo({ url: item.path })}> {item.icon} {item.label} {item.value ? {item.value} : null} diff --git a/frontend/miniapp/src/pages/purchase/index.tsx b/frontend/miniapp/src/pages/purchase/index.tsx index 2bf0590..741ab66 100644 --- a/frontend/miniapp/src/pages/purchase/index.tsx +++ b/frontend/miniapp/src/pages/purchase/index.tsx @@ -1,14 +1,37 @@ import React from 'react'; +import { t } from '@/i18n'; +import Taro from '@tarojs/taro'; // Taro mini-program component (WeChat / Alipay) /** - * E1. 小程序核心页面 - 购买页 + * E1. 小程序核心页面 - 购买/确认订单页 * - * Coupon summary card, quantity selector, price calculation, - * payment button (微信支付/支付宝/H5支付), order confirmation + * Coupon summary card, quantity selector, payment method, + * price breakdown, fixed bottom bar with confirm button */ +const UNIT_PRICE = 21.25; +const FACE_VALUE = 25; + const PurchasePage: React.FC = () => { + const [quantity, setQuantity] = React.useState(1); + + const totalPrice = (UNIT_PRICE * quantity).toFixed(2); + const totalFace = (FACE_VALUE * quantity).toFixed(2); + const savings = ((FACE_VALUE - UNIT_PRICE) * quantity).toFixed(2); + + const handleMinus = () => { + if (quantity > 1) setQuantity(quantity - 1); + }; + + const handlePlus = () => { + if (quantity < 10) setQuantity(quantity + 1); + }; + + const handleConfirm = () => { + Taro.navigateTo({ url: '/pages/payment-success/index' }); + }; + return ( {/* Coupon Summary Card */} @@ -22,12 +45,199 @@ const PurchasePage: React.FC = () => { ¥21.25 ¥25 - 8.1折 + + 8.5折 + + + {/* Quantity Selector */} + + + {t('purchase_quantity')} + + + - + + + {quantity} + + = 10 ? 'qty-btn-disabled' : ''}`} onClick={handlePlus}> + + + + + + + + {/* Payment Method */} + + {t('purchase_payment_method')} + + + 💬 + + {t('purchase_wechat_pay')} + + + + + + + {/* Price Breakdown */} + + {t('purchase_price_detail')} + + {t('purchase_unit_price')} + ¥{UNIT_PRICE.toFixed(2)} + + + {t('purchase_count')} + x{quantity} + + + + {t('order_total')} + ¥{totalPrice} + + + 🏷️ + {t('purchase_save_badge')} ¥{savings} + + + + {/* Info Notice */} + + 🛡️ + {t('purchase_buying_note')} + + + {/* Fixed Bottom Bar */} + + + {t('order_total')} + ¥{totalPrice} + + + {t('purchase_confirm_pay')} + + ); }; export default PurchasePage; + +/* +CSS (index.scss): + +.purchase-page { padding: 24rpx 32rpx; padding-bottom: 180rpx; background: #F8F9FC; min-height: 100vh; } + +.coupon-summary { + display: flex; padding: 24rpx; + background: white; border-radius: 16rpx; + border: 1rpx solid #F1F3F8; margin-bottom: 20rpx; +} +.summary-image { + width: 140rpx; height: 140rpx; background: #F3F1FF; + border-radius: 12rpx; display: flex; + align-items: center; justify-content: center; flex-shrink: 0; +} +.summary-icon { font-size: 48rpx; } +.summary-info { + flex: 1; padding-left: 20rpx; + display: flex; flex-direction: column; justify-content: space-between; +} +.summary-brand { font-size: 22rpx; color: #A0A8BE; } +.summary-name { font-size: 28rpx; font-weight: 500; color: #141723; margin-top: 4rpx; } +.summary-price-row { display: flex; align-items: flex-end; margin-top: 8rpx; } +.summary-price { font-size: 32rpx; font-weight: 700; color: #6C5CE7; } +.summary-face { font-size: 22rpx; color: #A0A8BE; text-decoration: line-through; margin-left: 8rpx; } +.summary-discount { + margin-left: 8rpx; padding: 2rpx 10rpx; + background: linear-gradient(135deg, #6C5CE7, #9B8FFF); + border-radius: 999rpx; +} +.summary-discount-text { color: white; font-size: 20rpx; font-weight: 700; } + +.section-card { + background: white; border-radius: 16rpx; padding: 24rpx; + border: 1rpx solid #F1F3F8; margin-bottom: 20rpx; +} +.card-title { font-size: 28rpx; font-weight: 600; color: #141723; margin-bottom: 20rpx; } + +.section-row { + display: flex; justify-content: space-between; align-items: center; +} +.section-label { font-size: 28rpx; font-weight: 500; color: #141723; } + +.qty-selector { display: flex; align-items: center; } +.qty-btn { + width: 56rpx; height: 56rpx; border-radius: 12rpx; + background: #F3F1FF; display: flex; + align-items: center; justify-content: center; +} +.qty-btn-disabled { background: #F1F3F8; opacity: 0.5; } +.qty-btn-text { font-size: 32rpx; font-weight: 600; color: #6C5CE7; } +.qty-value { + width: 80rpx; display: flex; align-items: center; justify-content: center; +} +.qty-value-text { font-size: 32rpx; font-weight: 600; color: #141723; } + +.payment-row { + display: flex; align-items: center; padding: 12rpx 0; +} +.payment-icon-wrap { + width: 48rpx; height: 48rpx; border-radius: 12rpx; + background: #E6FAF3; display: flex; + align-items: center; justify-content: center; margin-right: 16rpx; +} +.payment-icon { font-size: 28rpx; } +.payment-name { flex: 1; font-size: 28rpx; color: #141723; } +.payment-check { + width: 40rpx; height: 40rpx; border-radius: 50%; + background: #00C48C; display: flex; + align-items: center; justify-content: center; +} +.payment-check-text { color: white; font-size: 24rpx; font-weight: 700; } + +.price-row { + display: flex; justify-content: space-between; padding: 12rpx 0; +} +.price-label { font-size: 26rpx; color: #5C6478; } +.price-value { font-size: 26rpx; color: #141723; } +.price-divider { height: 1rpx; background: #F1F3F8; margin: 12rpx 0; } +.price-row-total { padding-top: 16rpx; } +.price-label-total { font-size: 28rpx; font-weight: 600; color: #141723; } +.price-value-total { font-size: 36rpx; font-weight: 700; color: #6C5CE7; } + +.savings-badge { + display: flex; align-items: center; margin-top: 12rpx; + padding: 10rpx 16rpx; background: #E6FAF3; border-radius: 8rpx; +} +.savings-icon { font-size: 24rpx; margin-right: 8rpx; } +.savings-text { font-size: 24rpx; color: #00C48C; font-weight: 500; } + +.info-notice { + display: flex; align-items: center; + padding: 20rpx 24rpx; background: #E6FAF3; + border-radius: 12rpx; margin-bottom: 20rpx; +} +.notice-icon { font-size: 28rpx; margin-right: 12rpx; } +.notice-text { font-size: 24rpx; color: #3D4459; } + +.bottom-bar { + position: fixed; bottom: 0; left: 0; right: 0; + display: flex; align-items: center; justify-content: space-between; + padding: 20rpx 32rpx; padding-bottom: calc(20rpx + env(safe-area-inset-bottom)); + background: white; border-top: 1rpx solid #F1F3F8; +} +.bottom-left { display: flex; flex-direction: column; } +.bottom-label { font-size: 22rpx; color: #A0A8BE; } +.bottom-price { font-size: 40rpx; font-weight: 700; color: #6C5CE7; } +.bottom-btn { + padding: 20rpx 56rpx; border-radius: 16rpx; + background: linear-gradient(135deg, #6C5CE7, #9B8FFF); +} +.bottom-btn-text { color: white; font-size: 30rpx; font-weight: 600; } +*/ diff --git a/frontend/miniapp/src/pages/search/index.tsx b/frontend/miniapp/src/pages/search/index.tsx new file mode 100644 index 0000000..dc4dce8 --- /dev/null +++ b/frontend/miniapp/src/pages/search/index.tsx @@ -0,0 +1,159 @@ +import React from 'react'; +import { t } from '@/i18n'; +import Taro from '@tarojs/taro'; +import CouponCard from '@/components/coupon-card'; +// Taro mini-program component (WeChat / Alipay) + +/** + * E1. 小程序核心页面 - 搜索页 + * + * 热门搜索标签 + 搜索历史 + 搜索结果 + * 输入内容后展示券列表,点击跳转详情 + */ + +const mockResults = [ + { brand: 'Starbucks', name: '星巴克 ¥25 礼品卡', price: '¥21.25', faceValue: '¥25', discount: '8.5折' }, + { brand: 'Amazon', name: 'Amazon ¥100 购物券', price: '¥85.00', faceValue: '¥100', discount: '8.5折' }, + { brand: 'Walmart', name: 'Walmart ¥50 购物券', price: '¥42.50', faceValue: '¥50', discount: '8.5折' }, + { brand: 'Target', name: 'Target ¥30 折扣券', price: '¥24.00', faceValue: '¥30', discount: '8折' }, + { brand: 'Nike', name: 'Nike ¥80 运动券', price: '¥68.00', faceValue: '¥80', discount: '8.5折' }, +]; + +const hotTags = ['Starbucks', 'Amazon', t('category_food'), t('coupon_sort_discount'), t('category_travel'), 'Nike']; + +const historyItems = ['星巴克 礼品卡', 'Nike 运动券', '餐饮 折扣']; + +const SearchPage: React.FC = () => { + const [searchText, setSearchText] = React.useState(''); + const showResults = searchText.length > 0; + + const handleResultTap = () => { + Taro.navigateTo({ url: '/pages/detail/index' }); + }; + + return ( + + {/* Header: Search Input + Cancel */} + + + 🔍 + setSearchText(e.detail.value)} + focus + /> + {searchText.length > 0 && ( + setSearchText('')}>✕ + )} + + Taro.navigateBack()}>{t('cancel')} + + + {!showResults ? ( + + {/* Hot Tags */} + + {t('search_hot_keywords')} + + {hotTags.map((tag, i) => ( + setSearchText(tag)}> + {tag} + + ))} + + + + {/* Search History */} + + + {t('search_history')} + {t('search_clear_history')} + + {historyItems.map((item, i) => ( + setSearchText(item)}> + 🕐 + {item} + + ))} + + + ) : ( + + + {t('search_result_count').replace('{count}', String(mockResults.length))} + + + {mockResults.map((coupon, i) => ( + + ))} + + + )} + + ); +}; + +export default SearchPage; + +/* +CSS (index.scss): + +.search-page { background: #F8F9FC; min-height: 100vh; } + +.search-header { + display: flex; align-items: center; + padding: 20rpx 32rpx; background: white; + border-bottom: 1rpx solid #F1F3F8; +} +.search-input-wrap { + flex: 1; display: flex; align-items: center; + height: 72rpx; padding: 0 24rpx; + background: #F1F3F8; border-radius: 999rpx; +} +.search-icon { font-size: 28rpx; margin-right: 12rpx; flex-shrink: 0; } +.search-input { flex: 1; font-size: 28rpx; color: #141723; background: transparent; } +.search-clear { + font-size: 24rpx; color: #A0A8BE; margin-left: 12rpx; + width: 40rpx; height: 40rpx; display: flex; align-items: center; justify-content: center; +} +.search-cancel { font-size: 28rpx; color: #6C5CE7; margin-left: 24rpx; flex-shrink: 0; } + +.search-body { padding: 0 32rpx; height: calc(100vh - 112rpx); } +.search-results { padding: 0 32rpx; height: calc(100vh - 112rpx); } + +.section { margin-top: 32rpx; } +.section-header { + display: flex; justify-content: space-between; align-items: center; + margin-bottom: 20rpx; +} +.section-title { font-size: 30rpx; font-weight: 600; color: #141723; } +.section-clear { font-size: 24rpx; color: #A0A8BE; } + +.tag-wrap { display: flex; flex-wrap: wrap; gap: 16rpx; margin-top: 20rpx; } +.hot-tag { + padding: 12rpx 28rpx; background: #F3F1FF; + border-radius: 999rpx; border: 1rpx solid rgba(108,92,231,0.12); +} +.hot-tag-text { font-size: 26rpx; color: #6C5CE7; } + +.history-item { + display: flex; align-items: center; padding: 20rpx 0; + border-bottom: 1rpx solid #F1F3F8; +} +.history-icon { font-size: 24rpx; margin-right: 16rpx; color: #A0A8BE; } +.history-text { font-size: 28rpx; color: #5C6478; } + +.result-count { padding: 20rpx 0; } +.result-count-text { font-size: 24rpx; color: #A0A8BE; } +.result-list { padding-bottom: 40rpx; } +*/ diff --git a/frontend/miniapp/src/pages/settings/index.tsx b/frontend/miniapp/src/pages/settings/index.tsx new file mode 100644 index 0000000..922bfbc --- /dev/null +++ b/frontend/miniapp/src/pages/settings/index.tsx @@ -0,0 +1,237 @@ +import React from 'react'; +import Taro from '@tarojs/taro'; +import { t } from '@/i18n'; +// Taro mini-program component + +/** + * P2. 设置页面 + * + * 通知开关、语言/货币选择、关于、退出登录 + */ + +const languages = [ + { value: 'zh-CN', label: '简体中文' }, + { value: 'en-US', label: 'English' }, + { value: 'ja-JP', label: '日本語' }, +]; + +const currencies = [ + { code: 'CNY', symbol: '¥', label: 'CNY (¥)' }, + { code: 'USD', symbol: '$', label: 'USD ($)' }, + { code: 'EUR', symbol: '€', label: 'EUR (€)' }, + { code: 'GBP', symbol: '£', label: 'GBP (£)' }, + { code: 'JPY', symbol: '¥', label: 'JPY (¥)' }, + { code: 'HKD', symbol: 'HK$', label: 'HKD (HK$)' }, +]; + +const SettingsPage: React.FC = () => { + const [notifyTrade, setNotifyTrade] = React.useState(true); + const [notifyExpiry, setNotifyExpiry] = React.useState(true); + const [notifyMarketing, setNotifyMarketing] = React.useState(false); + const [showLangPicker, setShowLangPicker] = React.useState(false); + const [showCurrencyPicker, setShowCurrencyPicker] = React.useState(false); + const [selectedLang, setSelectedLang] = React.useState('zh-CN'); + const [selectedCurrency, setSelectedCurrency] = React.useState('USD'); + + const currentLangLabel = languages.find((l) => l.value === selectedLang)?.label || '简体中文'; + const currentCurrencyLabel = currencies.find((c) => c.code === selectedCurrency)?.label || 'USD ($)'; + + const handleClearCache = () => { + Taro.showToast({ title: t('settings_cache_cleared'), icon: 'success' }); + }; + + const toggleItems = [ + { label: t('settings_trade_notify'), value: notifyTrade, setter: setNotifyTrade }, + { label: t('settings_expiry_remind'), value: notifyExpiry, setter: setNotifyExpiry }, + { label: t('settings_marketing_push'), value: notifyMarketing, setter: setNotifyMarketing }, + ]; + + return ( + + {/* Section: Notifications */} + + {t('settings_notifications')} + {toggleItems.map((item, i) => ( + + {item.label} + item.setter(!item.value)} + > + + + + ))} + + + {/* Section: General */} + + {t('settings_general')} + + setShowLangPicker(true)}> + {t('profile_language')} + + {currentLangLabel} + + + + + setShowCurrencyPicker(true)}> + {t('profile_currency')} + + {currentCurrencyLabel} + + + + + + {t('settings_clear_cache')} + + + + + {/* Section: About */} + + {t('settings_about')} + + + {t('settings_version')} + v1.0.0 + + + + {t('login_user_agreement')} + + + + + {t('login_privacy_policy')} + + + + + {t('settings_help_center')} + + + + + {/* Logout Button */} + + {t('settings_logout')} + + + {/* Language Picker Modal */} + {showLangPicker && ( + setShowLangPicker(false)}> + e.stopPropagation()}> + + {t('settings_select_language')} + setShowLangPicker(false)}>✕ + + {languages.map((lang) => ( + { setSelectedLang(lang.value); setShowLangPicker(false); }} + > + {lang.label} + {selectedLang === lang.value && } + + ))} + + + )} + + {/* Currency Picker Modal */} + {showCurrencyPicker && ( + setShowCurrencyPicker(false)}> + e.stopPropagation()}> + + {t('settings_select_currency')} + setShowCurrencyPicker(false)}>✕ + + {t('settings_currency_note')} + {currencies.map((cur) => ( + { setSelectedCurrency(cur.code); setShowCurrencyPicker(false); }} + > + {cur.label} + {selectedCurrency === cur.code && } + + ))} + + + )} + + ); +}; + +export default SettingsPage; + +/* +CSS: + +.settings-page { background: #F8F9FC; min-height: 100vh; padding-bottom: 60rpx; } + +.settings-section { + background: white; margin-top: 16rpx; +} +.settings-section-title { + display: block; font-size: 24rpx; color: #A0A8BE; + padding: 24rpx 32rpx 8rpx; font-weight: 500; text-transform: uppercase; +} + +.settings-row { + display: flex; align-items: center; justify-content: space-between; + padding: 28rpx 32rpx; border-bottom: 1rpx solid #F1F3F8; +} +.settings-row-label { font-size: 28rpx; color: #141723; } +.settings-row-right { display: flex; align-items: center; } +.settings-row-value { font-size: 26rpx; color: #A0A8BE; margin-right: 8rpx; } +.settings-row-arrow { font-size: 32rpx; color: #CDD2DE; } + +.toggle-track { + width: 88rpx; height: 48rpx; border-radius: 999rpx; + background: #E4E7F0; position: relative; transition: background 0.2s; +} +.toggle-track-on { background: #6C5CE7; } +.toggle-thumb { + width: 40rpx; height: 40rpx; border-radius: 50%; + background: white; position: absolute; top: 4rpx; left: 4rpx; + box-shadow: 0 2rpx 6rpx rgba(0,0,0,0.15); transition: left 0.2s; +} +.toggle-thumb-on { left: 44rpx; } + +.settings-logout-btn { + margin: 48rpx 32rpx 0; padding: 24rpx 0; + border: 2rpx solid #E74C3C; border-radius: 16rpx; + display: flex; align-items: center; justify-content: center; +} +.settings-logout-text { font-size: 30rpx; color: #E74C3C; font-weight: 500; } + +.picker-overlay { + position: fixed; top: 0; left: 0; right: 0; bottom: 0; + background: rgba(0,0,0,0.5); z-index: 999; + display: flex; align-items: flex-end; justify-content: center; +} +.picker-modal { + width: 100%; background: white; border-radius: 24rpx 24rpx 0 0; + padding: 32rpx 32rpx calc(32rpx + env(safe-area-inset-bottom)); +} +.picker-header { + display: flex; align-items: center; justify-content: space-between; + margin-bottom: 16rpx; +} +.picker-title { font-size: 32rpx; font-weight: 600; color: #141723; } +.picker-close { font-size: 32rpx; color: #A0A8BE; padding: 8rpx; } +.picker-note { font-size: 22rpx; color: #A0A8BE; margin-bottom: 16rpx; } + +.picker-option { + display: flex; align-items: center; justify-content: space-between; + padding: 28rpx 16rpx; border-bottom: 1rpx solid #F1F3F8; +} +.picker-option-label { font-size: 28rpx; color: #141723; } +.picker-check { font-size: 32rpx; color: #6C5CE7; font-weight: 700; } +*/ diff --git a/frontend/miniapp/src/pages/transfer/index.tsx b/frontend/miniapp/src/pages/transfer/index.tsx new file mode 100644 index 0000000..3c5b892 --- /dev/null +++ b/frontend/miniapp/src/pages/transfer/index.tsx @@ -0,0 +1,407 @@ +import React from 'react'; +import { t } from '@/i18n'; +import Taro from '@tarojs/taro'; +// Taro mini-program component (WeChat / Alipay) + +/** + * E2. 小程序核心页面 - 转赠页 + * + * 两种转赠方式入口 + 最近联系人 + 转赠记录 + * 底部弹窗:输入ID / 选择券 / 确认转赠 + */ + +const recentRecipients = [ + { name: 'Alice', contact: 'alice@g***l.com', type: t('transfer_contact_email'), time: '3天前', expired: false }, + { name: 'Bob', contact: '138****1234', type: t('transfer_contact_phone'), time: '12天前', expired: false }, + { name: 'Charlie', contact: 'GNX-USR-7B2E', type: 'ID', time: '30天前', expired: true }, +]; + +const transferHistory = [ + { name: '星巴克 ¥25', to: 'Alice', direction: 'out', time: '3天前' }, + { name: 'Nike ¥80', to: 'Bob', direction: 'in', time: '7天前' }, + { name: 'Walmart ¥50', to: 'Diana', direction: 'out', time: '15天前' }, +]; + +const selectableCoupons = [ + { brand: 'Starbucks', name: '星巴克 ¥25 礼品卡', price: '¥21.25' }, + { brand: 'Amazon', name: 'Amazon ¥100 购物券', price: '¥85.00' }, + { brand: 'Target', name: 'Target ¥30 折扣券', price: '¥24.00' }, +]; + +const TransferPage: React.FC = () => { + const [showInputModal, setShowInputModal] = React.useState(false); + const [showSelectModal, setShowSelectModal] = React.useState(false); + const [showConfirmModal, setShowConfirmModal] = React.useState(false); + const [recipientInput, setRecipientInput] = React.useState(''); + const [selectedCouponIndex, setSelectedCouponIndex] = React.useState(0); + + const handleShareCard = () => { + // WeChat native share via Taro + Taro.showShareMenu({ withShareTicket: true }); + }; + + const handleInputEntry = () => { + setShowInputModal(true); + }; + + const handlePaste = () => { + Taro.getClipboardData({ + success: (res) => { + if (res.data) setRecipientInput(res.data); + }, + }); + }; + + const handleInputConfirm = () => { + if (!recipientInput.trim()) return; + setShowInputModal(false); + setShowSelectModal(true); + }; + + const handleCouponSelect = (index: number) => { + setSelectedCouponIndex(index); + setShowSelectModal(false); + setShowConfirmModal(true); + }; + + const handleConfirmTransfer = () => { + setShowConfirmModal(false); + Taro.showToast({ title: t('transfer_success'), icon: 'success' }); + }; + + const handleRecipientTap = (recipient: typeof recentRecipients[0]) => { + if (recipient.expired) return; + setRecipientInput(recipient.contact); + setShowSelectModal(true); + }; + + return ( + + {/* Entry Cards */} + + + 🔗 + {t('transfer_share_card')} + {t('transfer_share_desc')} + + + ✏️ + {t('transfer_input')} + {t('transfer_input_desc')} + + + + {/* Recent Recipients */} + + + {t('transfer_recent')} + {t('transfer_manage')} + + {recentRecipients.map((r, i) => ( + handleRecipientTap(r)} + > + + {r.name[0]} + + + + {r.name} + + {r.type} + + + {r.contact} + + + {r.expired ? ( + {t('transfer_expired')} + ) : ( + + {r.time} + + + )} + + + ))} + + + {/* Transfer History */} + + + {t('transfer_history')} + + {transferHistory.map((item, i) => ( + + + {item.direction === 'out' ? '↑' : '↓'} + + + {item.name} + + {item.direction === 'out' ? `→ ${item.to}` : `← ${item.to}`} + + + {item.time} + + ))} + + + {/* Input Modal (Bottom Popup) */} + {showInputModal && ( + setShowInputModal(false)}> + e.stopPropagation()}> + + {t('transfer_input_recipient')} + setShowInputModal(false)}>✕ + + + setRecipientInput(e.detail.value)} + /> + + {t('transfer_paste')} + + + + {t('confirm')} + + + + )} + + {/* Select Coupon Modal */} + {showSelectModal && ( + setShowSelectModal(false)}> + e.stopPropagation()}> + + {t('transfer_select_coupon')} + setShowSelectModal(false)}>✕ + + {selectableCoupons.map((coupon, i) => ( + handleCouponSelect(i)}> + + 🎫 + + + {coupon.brand} + {coupon.name} + + {coupon.price} + + ))} + + + )} + + {/* Confirm Modal */} + {showConfirmModal && ( + setShowConfirmModal(false)}> + e.stopPropagation()}> + + {t('transfer_confirm')} + setShowConfirmModal(false)}>✕ + + + + + {t('transfer_select_coupon')} + {selectableCoupons[selectedCouponIndex].name} + + + {t('transfer_to')} + {recipientInput} + + + + + ⚠️ + {t('transfer_warning')} + + + + setShowConfirmModal(false)}> + {t('cancel')} + + + {t('transfer_confirm_btn')} + + + + + )} + + ); +}; + +export default TransferPage; + +/* +CSS (index.scss): + +.transfer-page { background: #F8F9FC; min-height: 100vh; padding: 24rpx 32rpx 40rpx; } + +.entry-row { display: flex; gap: 20rpx; margin-bottom: 24rpx; } +.entry-card { + flex: 1; padding: 28rpx 20rpx; border-radius: 16rpx; + display: flex; flex-direction: column; align-items: center; +} +.entry-card-gradient { + background: linear-gradient(135deg, #6C5CE7, #9B8FFF); +} +.entry-card-gradient .entry-title { color: white; } +.entry-card-gradient .entry-desc { color: rgba(255,255,255,0.7); } +.entry-card-white { + background: white; border: 1rpx solid #F1F3F8; +} +.entry-card-white .entry-title { color: #141723; } +.entry-card-white .entry-desc { color: #A0A8BE; } +.entry-icon { font-size: 40rpx; margin-bottom: 12rpx; } +.entry-title { font-size: 28rpx; font-weight: 600; margin-bottom: 4rpx; } +.entry-desc { font-size: 22rpx; text-align: center; } + +.section { margin-bottom: 24rpx; } +.section-header { + display: flex; justify-content: space-between; align-items: center; + margin-bottom: 16rpx; +} +.section-title { font-size: 30rpx; font-weight: 600; color: #141723; } +.section-action { font-size: 24rpx; color: #6C5CE7; } + +.recipient-item { + display: flex; align-items: center; + background: white; border-radius: 16rpx; padding: 20rpx; + border: 1rpx solid #F1F3F8; margin-bottom: 12rpx; +} +.recipient-expired { opacity: 0.5; } +.recipient-avatar { + width: 72rpx; height: 72rpx; border-radius: 50%; + background: #F3F1FF; display: flex; + align-items: center; justify-content: center; flex-shrink: 0; +} +.recipient-avatar-text { font-size: 28rpx; font-weight: 700; color: #6C5CE7; } +.recipient-info { flex: 1; margin-left: 16rpx; } +.recipient-name-row { display: flex; align-items: center; } +.recipient-name { font-size: 28rpx; font-weight: 500; color: #141723; } +.recipient-badge { + margin-left: 8rpx; padding: 2rpx 10rpx; + background: #F3F1FF; border-radius: 999rpx; +} +.recipient-badge-text { font-size: 20rpx; color: #6C5CE7; } +.recipient-contact { font-size: 24rpx; color: #A0A8BE; margin-top: 4rpx; } +.recipient-right { margin-left: 12rpx; flex-shrink: 0; } +.recipient-expired-text { font-size: 22rpx; color: #A0A8BE; } +.recipient-time-row { display: flex; align-items: center; } +.recipient-time { font-size: 22rpx; color: #A0A8BE; } +.recipient-chevron { font-size: 28rpx; color: #A0A8BE; margin-left: 4rpx; } + +.history-item { + display: flex; align-items: center; + background: white; border-radius: 16rpx; padding: 20rpx; + border: 1rpx solid #F1F3F8; margin-bottom: 12rpx; +} +.history-arrow { + width: 56rpx; height: 56rpx; border-radius: 12rpx; + display: flex; align-items: center; justify-content: center; + flex-shrink: 0; margin-right: 16rpx; +} +.arrow-out { background: #FFF0E6; } +.arrow-in { background: #E6FAF3; } +.history-arrow-text { font-size: 28rpx; font-weight: 700; } +.arrow-out .history-arrow-text { color: #FF8C42; } +.arrow-in .history-arrow-text { color: #00C48C; } +.history-info { flex: 1; } +.history-name { font-size: 28rpx; font-weight: 500; color: #141723; } +.history-detail { font-size: 24rpx; color: #A0A8BE; margin-top: 4rpx; } +.history-time { font-size: 22rpx; color: #A0A8BE; flex-shrink: 0; } + +/* Modal Overlay & Sheet */ +.modal-overlay { + position: fixed; top: 0; left: 0; right: 0; bottom: 0; + background: rgba(0,0,0,0.5); z-index: 1000; + display: flex; align-items: flex-end; justify-content: center; +} +.modal-sheet { + width: 100%; background: white; border-radius: 32rpx 32rpx 0 0; + padding: 32rpx; padding-bottom: calc(32rpx + env(safe-area-inset-bottom)); +} +.modal-sheet-tall { max-height: 70vh; } +.modal-header { + display: flex; justify-content: space-between; align-items: center; + margin-bottom: 28rpx; +} +.modal-title { font-size: 32rpx; font-weight: 600; color: #141723; } +.modal-close { font-size: 28rpx; color: #A0A8BE; padding: 8rpx; } + +.modal-input-row { + display: flex; align-items: center; gap: 16rpx; margin-bottom: 24rpx; +} +.modal-input { + flex: 1; height: 80rpx; padding: 0 24rpx; + background: #F1F3F8; border-radius: 12rpx; + font-size: 28rpx; color: #141723; +} +.modal-paste-btn { + padding: 0 24rpx; height: 80rpx; border-radius: 12rpx; + background: #F3F1FF; display: flex; align-items: center; +} +.modal-paste-text { font-size: 26rpx; color: #6C5CE7; font-weight: 500; } + +.modal-confirm-btn { + height: 88rpx; border-radius: 16rpx; + background: linear-gradient(135deg, #6C5CE7, #9B8FFF); + display: flex; align-items: center; justify-content: center; +} +.modal-confirm-text { color: white; font-size: 30rpx; font-weight: 600; } + +.select-coupon-item { + display: flex; align-items: center; padding: 20rpx; + border-bottom: 1rpx solid #F1F3F8; +} +.select-coupon-icon { + width: 80rpx; height: 80rpx; background: #F3F1FF; + border-radius: 12rpx; display: flex; + align-items: center; justify-content: center; flex-shrink: 0; +} +.select-coupon-icon-text { font-size: 32rpx; } +.select-coupon-info { flex: 1; margin-left: 16rpx; } +.select-coupon-brand { font-size: 22rpx; color: #A0A8BE; } +.select-coupon-name { font-size: 28rpx; font-weight: 500; color: #141723; margin-top: 4rpx; } +.select-coupon-price { font-size: 28rpx; font-weight: 700; color: #6C5CE7; flex-shrink: 0; } + +.confirm-info { + background: #F8F9FC; border-radius: 12rpx; padding: 20rpx; margin-bottom: 20rpx; +} +.confirm-row { + display: flex; justify-content: space-between; padding: 12rpx 0; +} +.confirm-label { font-size: 26rpx; color: #5C6478; } +.confirm-value { font-size: 26rpx; color: #141723; font-weight: 500; } + +.confirm-warning { + display: flex; align-items: center; padding: 16rpx 20rpx; + background: #FFF8E6; border-radius: 12rpx; margin-bottom: 24rpx; +} +.confirm-warning-icon { font-size: 28rpx; margin-right: 12rpx; } +.confirm-warning-text { font-size: 24rpx; color: #B8860B; } + +.confirm-buttons { display: flex; gap: 20rpx; } +.confirm-btn-cancel { + flex: 1; height: 88rpx; border-radius: 16rpx; + background: #F1F3F8; display: flex; + align-items: center; justify-content: center; +} +.confirm-btn-cancel-text { font-size: 28rpx; color: #5C6478; font-weight: 600; } +.confirm-btn-ok { + flex: 1; height: 88rpx; border-radius: 16rpx; + background: linear-gradient(135deg, #6C5CE7, #9B8FFF); + display: flex; align-items: center; justify-content: center; +} +.confirm-btn-ok-text { color: white; font-size: 28rpx; font-weight: 600; } +*/ diff --git a/frontend/miniapp/src/pages/wallet/index.tsx b/frontend/miniapp/src/pages/wallet/index.tsx new file mode 100644 index 0000000..aa39af4 --- /dev/null +++ b/frontend/miniapp/src/pages/wallet/index.tsx @@ -0,0 +1,155 @@ +import React from 'react'; +import { t } from '@/i18n'; +// Taro mini-program component + +/** + * P2. 钱包页面 (只读) + * + * 余额展示 + 交易记录,无充值/提现操作 + */ + +interface Transaction { + id: number; + icon: string; + title: string; + amount: string; + time: string; + isPositive: boolean; + color: string; +} + +const mockTransactions: Transaction[] = [ + { id: 1, icon: '\uD83D\uDED2', title: '买入 Starbucks ¥25', amount: '-¥21.25', time: '14:32 today', isPositive: false, color: '#6C5CE7' }, + { id: 2, icon: '\uD83D\uDCB0', title: '卖出 Amazon ¥50', amount: '+¥42.50', time: '10:15 today', isPositive: true, color: '#00C48C' }, + { id: 3, icon: '\u2795', title: '充值', amount: '+¥500.00', time: '09:20 today', isPositive: true, color: '#3498DB' }, + { id: 4, icon: '\uD83C\uDF81', title: '转赠 Target', amount: '-¥30.00', time: '02/07', isPositive: false, color: '#FF9500' }, + { id: 5, icon: '\u2705', title: '核销 Nike', amount: '¥0', time: '02/06', isPositive: false, color: '#A0A8BE' }, + { id: 6, icon: '\uD83C\uDFE6', title: '提现', amount: '-¥200.00', time: '02/05', isPositive: false, color: '#E74C3C' }, +]; + +const WalletPage: React.FC = () => { + return ( + + {/* Balance Card */} + + {t('wallet_total_balance')} + $1,234.56 + + + {t('wallet_available')} + $1,034.56 + + + + {t('wallet_frozen')} + $200.00 + + + + + {/* Notice Bar */} + + ℹ️ + {t('wallet_read_only_hint')} + {t('download_btn')} + + + {/* Transaction Records */} + + + {t('wallet_records')} + {t('wallet_filter')} + + + + {mockTransactions.map((tx, i) => ( + + + + {tx.icon} + + + {tx.title} + {tx.time} + + + {tx.amount} + + + {i < mockTransactions.length - 1 && } + + ))} + + + + ); +}; + +export default WalletPage; + +/* +CSS: + +.wallet-page { background: #F8F9FC; min-height: 100vh; padding-bottom: 60rpx; } + +.wallet-balance-card { + margin: 32rpx; padding: 40rpx 32rpx; + background: linear-gradient(135deg, #6C5CE7, #4834D4); + border-radius: 24rpx; +} +.wallet-balance-label { font-size: 26rpx; color: rgba(255,255,255,0.7); } +.wallet-balance-amount { + display: block; font-size: 56rpx; font-weight: 700; color: white; + margin-top: 12rpx; +} +.wallet-sub-row { + display: flex; align-items: center; margin-top: 28rpx; + padding-top: 24rpx; border-top: 1rpx solid rgba(255,255,255,0.2); +} +.wallet-sub-item { flex: 1; } +.wallet-sub-label { font-size: 22rpx; color: rgba(255,255,255,0.6); } +.wallet-sub-value { display: block; font-size: 30rpx; font-weight: 600; color: white; margin-top: 4rpx; } +.wallet-sub-divider { + width: 1rpx; height: 48rpx; background: rgba(255,255,255,0.2); + margin: 0 32rpx; +} + +.wallet-notice { + display: flex; align-items: center; margin: 0 32rpx 24rpx; + padding: 16rpx 20rpx; background: #FFF9E6; + border-radius: 12rpx; border: 1rpx solid #FFE8A3; +} +.wallet-notice-icon { font-size: 28rpx; margin-right: 10rpx; } +.wallet-notice-text { flex: 1; font-size: 24rpx; color: #8B6914; } +.wallet-notice-link { font-size: 24rpx; color: #6C5CE7; font-weight: 500; } + +.wallet-records-section { + margin: 0 32rpx; background: white; border-radius: 20rpx; + overflow: hidden; border: 1rpx solid #F1F3F8; +} +.wallet-records-header { + display: flex; align-items: center; justify-content: space-between; + padding: 24rpx 24rpx 16rpx; +} +.wallet-records-title { font-size: 30rpx; font-weight: 600; color: #141723; } +.wallet-records-filter { font-size: 24rpx; color: #6C5CE7; } + +.wallet-records-list { padding: 0 24rpx; } +.wallet-tx-item { + display: flex; align-items: center; padding: 20rpx 0; +} +.wallet-tx-icon { + width: 64rpx; height: 64rpx; border-radius: 50%; + display: flex; align-items: center; justify-content: center; + flex-shrink: 0; +} +.wallet-tx-emoji { font-size: 28rpx; } +.wallet-tx-info { flex: 1; margin-left: 18rpx; } +.wallet-tx-title { font-size: 28rpx; color: #141723; font-weight: 500; } +.wallet-tx-time { display: block; font-size: 22rpx; color: #A0A8BE; margin-top: 4rpx; } +.wallet-tx-amount { font-size: 30rpx; font-weight: 600; flex-shrink: 0; } +.wallet-tx-divider { height: 1rpx; background: #F1F3F8; margin-left: 82rpx; } +*/