989 lines
22 KiB
Plaintext
989 lines
22 KiB
Plaintext
<!-- 支付页面 -->
|
||
<template>
|
||
<view class="payment-page">
|
||
<view class="payment-content">
|
||
<!-- 价格明细 -->
|
||
<view class="price-detail-section">
|
||
<text class="section-title">价格明细</text>
|
||
<view class="price-detail">
|
||
<view class="price-row">
|
||
<text class="price-label">商品总价</text>
|
||
<text class="price-value">¥{{ productAmount.toFixed(2) }}</text>
|
||
</view>
|
||
<view class="price-row">
|
||
<text class="price-label">运费</text>
|
||
<text class="price-value">+¥{{ deliveryFee.toFixed(2) }}</text>
|
||
</view>
|
||
<view v-if="discountAmount > 0" class="price-row">
|
||
<text class="price-label">优惠减免</text>
|
||
<text class="price-value discount">-¥{{ discountAmount.toFixed(2) }}</text>
|
||
</view>
|
||
<view class="price-row total">
|
||
<text class="price-label">应付金额</text>
|
||
<text class="price-value total-price">¥{{ amount.toFixed(2) }}</text>
|
||
</view>
|
||
</view>
|
||
<text class="order-no">订单号: {{ orderNo }}</text>
|
||
</view>
|
||
|
||
<!-- 支付方式 -->
|
||
<view class="methods-section">
|
||
<text class="section-title">选择支付方式</text>
|
||
<view class="method-list">
|
||
<view v-for="method in paymentMethods"
|
||
:key="method.id"
|
||
:class="['method-item', { selected: selectedMethod === method.id }]"
|
||
@click="selectMethod(method)">
|
||
<view class="method-left">
|
||
<text class="method-icon">{{ getMethodIcon(method.id) }}</text>
|
||
<view class="method-info">
|
||
<text class="method-name">{{ method.name }}</text>
|
||
<text class="method-desc">{{ method.description }}</text>
|
||
</view>
|
||
</view>
|
||
<view v-if="selectedMethod === method.id" class="method-selected">
|
||
<text class="selected-icon">✓</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 余额支付 -->
|
||
<view v-if="selectedMethod === 'balance' && userBalance > 0" class="balance-section">
|
||
<view class="balance-info">
|
||
<text class="balance-label">账户余额</text>
|
||
<text class="balance-value">¥{{ userBalance.toFixed(2) }}</text>
|
||
</view>
|
||
<view v-if="userBalance < amount" class="balance-tip">
|
||
<text class="tip-text">余额不足,请选择其他支付方式</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 密码输入 -->
|
||
<view v-if="showPassword" class="password-section">
|
||
<text class="password-title">请输入支付密码</text>
|
||
<view class="password-input">
|
||
<view v-for="(_, index) in 6"
|
||
:key="index"
|
||
class="password-dot">
|
||
<text v-if="password.length > index">●</text>
|
||
</view>
|
||
</view>
|
||
<text class="forgot-password" @click="forgotPassword">忘记密码?</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 底部支付按钮 -->
|
||
<view class="payment-bottom">
|
||
<view class="price-summary">
|
||
<text class="summary-label">需支付:</text>
|
||
<text class="summary-price">¥{{ amount.toFixed(2) }}</text>
|
||
</view>
|
||
<button class="pay-btn"
|
||
:class="{ disabled: isPaying || (selectedMethod === 'balance' && userBalance < amount) }"
|
||
@click="confirmPayment">
|
||
<text v-if="!isPaying" class="pay-text">{{ getPayButtonText() }}</text>
|
||
<text v-else class="pay-text">支付中...</text>
|
||
</button>
|
||
</view>
|
||
|
||
<!-- 密码键盘 -->
|
||
<view v-if="showPassword" class="password-keyboard">
|
||
<view class="keyboard-grid">
|
||
<view v-for="num in 9"
|
||
:key="num"
|
||
class="keyboard-key"
|
||
@click="inputPassword(num.toString())">
|
||
<text class="key-text">{{ num }}</text>
|
||
</view>
|
||
<view class="keyboard-key"></view>
|
||
<view class="keyboard-key" @click="inputPassword('0')">
|
||
<text class="key-text">0</text>
|
||
</view>
|
||
<view class="keyboard-key" @click="deletePassword">
|
||
<text class="key-text">⌫</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script setup lang="uts">
|
||
import { ref, onMounted, watch, computed, onUnmounted } from 'vue'
|
||
import { onLoad, onBackPress } from '@dcloudio/uni-app'
|
||
// import { supabase as supa } from '@/components/supadb/aksupainstance.uts'
|
||
|
||
type PaymentMethodType = {
|
||
id: string
|
||
name: string
|
||
description: string
|
||
icon: string
|
||
enabled: boolean
|
||
}
|
||
|
||
const orderId = ref<string>('')
|
||
const orderNo = ref<string>('')
|
||
const amount = ref<number>(0)
|
||
const paymentMethods = ref<Array<PaymentMethodType>>([])
|
||
const selectedMethod = ref<string>('wechat')
|
||
const userBalance = ref<number>(0)
|
||
const isPaying = ref<boolean>(false)
|
||
const showPassword = ref<boolean>(false)
|
||
const password = ref<string>('')
|
||
|
||
// 价格相关变量
|
||
const productAmount = ref<number>(0) // 商品总价
|
||
const deliveryFee = ref<number>(0) // 运费
|
||
const discountAmount = ref<number>(0) // 优惠减免
|
||
|
||
// 生命周期
|
||
onMounted(() => {
|
||
const pages = getCurrentPages()
|
||
const currentPage = pages[pages.length - 1]
|
||
const options = currentPage.options as any
|
||
|
||
if (options.orderId) {
|
||
orderId.value = options.orderId
|
||
loadOrderInfo()
|
||
}
|
||
|
||
if (options.amount) {
|
||
amount.value = parseFloat(options.amount)
|
||
}
|
||
|
||
// 获取传递的价格详情
|
||
if (options.productAmount) {
|
||
productAmount.value = parseFloat(options.productAmount)
|
||
}
|
||
if (options.deliveryFee) {
|
||
deliveryFee.value = parseFloat(options.deliveryFee)
|
||
}
|
||
if (options.discountAmount) {
|
||
discountAmount.value = parseFloat(options.discountAmount)
|
||
}
|
||
|
||
// 如果没有传详情,尝试根据总价估算(兼容旧逻辑,但优先使用传参)
|
||
if (!options.productAmount && amount.value > 0) {
|
||
calculatePriceDetails(amount.value)
|
||
}
|
||
|
||
loadPaymentMethods()
|
||
loadUserBalance()
|
||
})
|
||
|
||
// 监听返回操作(包含系统返回键和导航栏返回按钮)
|
||
onBackPress((options) => {
|
||
// 如果是通过代码主动调用 navigateBack 返回,则允许
|
||
if (options.from === 'navigateBack') {
|
||
return false
|
||
}
|
||
|
||
// 否则拦截返回,显示确认弹窗
|
||
goBack()
|
||
return true
|
||
})
|
||
|
||
// 更新本地存储中的订单状态
|
||
const updateOrderInStorage = (status: number) => {
|
||
try {
|
||
// 尝试从 'orders' 读取 (checkout页面写入的key)
|
||
const ordersStr = uni.getStorageSync('orders')
|
||
let orders: any[] = []
|
||
if (ordersStr) {
|
||
orders = JSON.parse(ordersStr as string) as any[]
|
||
}
|
||
|
||
const index = orders.findIndex((o: any) => o.id === orderId.value)
|
||
if (index !== -1) {
|
||
orders[index].status = status
|
||
orders[index].payment_status = status === 2 ? 1 : 0 // 2=待发货(已支付), 1=待支付(未支付)
|
||
orders[index].updated_at = new Date().toISOString()
|
||
// 确保更新的是 'orders' key
|
||
uni.setStorageSync('orders', JSON.stringify(orders))
|
||
console.log('订单状态已更新到Storage (orders):', orderId.value, status)
|
||
} else {
|
||
console.warn('在Storage (orders)中未找到订单:', orderId.value)
|
||
}
|
||
} catch (e) {
|
||
console.error('更新订单状态失败', e)
|
||
}
|
||
}
|
||
|
||
// 加载订单信息
|
||
const loadOrderInfo = async () => {
|
||
try {
|
||
/* const { data, error } = await supa
|
||
.from('orders')
|
||
.select('order_no, actual_amount')
|
||
.eq('id', orderId.value)
|
||
.single()
|
||
|
||
if (error !== null) {
|
||
console.error('加载订单信息失败:', error)
|
||
return
|
||
}
|
||
|
||
if (data) {
|
||
orderNo.value = data.order_no
|
||
amount.value = data.actual_amount || amount.value
|
||
} */
|
||
|
||
// MOCK DATA
|
||
orderNo.value = 'ORD_MOCK_' + Date.now()
|
||
// Amount already set from options or default
|
||
} catch (err) {
|
||
console.error('加载订单信息异常:', err)
|
||
}
|
||
}
|
||
|
||
// 加载支付方式
|
||
const loadPaymentMethods = () => {
|
||
paymentMethods.value = [
|
||
{
|
||
id: 'wechat',
|
||
name: '微信支付',
|
||
description: '推荐安装微信5.0及以上版本使用',
|
||
icon: '💳',
|
||
enabled: true
|
||
},
|
||
{
|
||
id: 'alipay',
|
||
name: '支付宝',
|
||
description: '推荐安装支付宝10.0及以上版本使用',
|
||
icon: '💳',
|
||
enabled: true
|
||
},
|
||
{
|
||
id: 'balance',
|
||
name: '余额支付',
|
||
description: '使用账户余额支付',
|
||
icon: '💰',
|
||
enabled: true
|
||
},
|
||
{
|
||
id: 'bankcard',
|
||
name: '银行卡支付',
|
||
description: '支持储蓄卡、信用卡',
|
||
icon: '💳',
|
||
enabled: true
|
||
}
|
||
]
|
||
}
|
||
|
||
// 加载用户余额
|
||
const loadUserBalance = async () => {
|
||
const userId = getCurrentUserId()
|
||
if (!userId) return
|
||
|
||
try {
|
||
// 这里假设有用户钱包表
|
||
/* const { data, error } = await supa
|
||
.from('user_wallets')
|
||
.select('balance')
|
||
.eq('user_id', userId)
|
||
.single()
|
||
|
||
if (error !== null) {
|
||
console.error('加载用户余额失败:', error)
|
||
return
|
||
}
|
||
|
||
userBalance.value = data?.balance || 0 */
|
||
|
||
// MOCK BALANCE
|
||
userBalance.value = 10000.00
|
||
} catch (err) {
|
||
console.error('加载用户余额异常:', err)
|
||
}
|
||
}
|
||
|
||
// 获取当前用户ID
|
||
const getCurrentUserId = (): string => {
|
||
const userStore = uni.getStorageSync('userInfo')
|
||
return userStore?.id || ''
|
||
}
|
||
|
||
// 获取支付方式图标
|
||
const getMethodIcon = (methodId: string): string => {
|
||
const icons: Record<string, string> = {
|
||
wechat: '💳',
|
||
alipay: '💳',
|
||
balance: '💰',
|
||
bankcard: '💳'
|
||
}
|
||
return icons[methodId] || '💳'
|
||
}
|
||
|
||
// 选择支付方式
|
||
const selectMethod = (method: PaymentMethodType) => {
|
||
if (!method.enabled) {
|
||
uni.showToast({
|
||
title: '该支付方式暂不可用',
|
||
icon: 'none'
|
||
})
|
||
return
|
||
}
|
||
|
||
selectedMethod.value = method.id
|
||
showPassword.value = method.id === 'balance' || method.id === 'bankcard'
|
||
password.value = '' // 清空密码
|
||
}
|
||
|
||
// 获取支付按钮文本
|
||
const getPayButtonText = (): string => {
|
||
if (selectedMethod.value === 'balance' && userBalance.value < amount.value) {
|
||
return '余额不足'
|
||
}
|
||
|
||
const texts: Record<string, string> = {
|
||
wechat: '微信支付',
|
||
alipay: '支付宝支付',
|
||
balance: '余额支付',
|
||
bankcard: '银行卡支付'
|
||
}
|
||
|
||
return texts[selectedMethod.value] || '确认支付'
|
||
}
|
||
|
||
// 减少商品库存
|
||
const reduceStock = (orderId: string) => {
|
||
try {
|
||
// 读取订单
|
||
const ordersStr = uni.getStorageSync('orders')
|
||
if (!ordersStr) return
|
||
|
||
const orders = JSON.parse(ordersStr as string) as any[]
|
||
const order = orders.find((o: any) => o.id === orderId)
|
||
|
||
if (!order || !order.items) return
|
||
|
||
// 读取商品库(这里假设商品库也在本地,实际项目中通常在服务器端处理)
|
||
// 模拟:如果有本地商品缓存,则更新
|
||
/*
|
||
const productsStr = uni.getStorageSync('products')
|
||
if (productsStr) {
|
||
const products = JSON.parse(productsStr as string) as any[]
|
||
let hasChange = false
|
||
|
||
order.items.forEach((item: any) => {
|
||
const product = products.find((p: any) => p.id === item.product_id)
|
||
if (product && product.stock >= item.quantity) {
|
||
product.stock -= item.quantity
|
||
hasChange = true
|
||
console.log(`商品 ${product.name} 库存减少 ${item.quantity}, 剩余 ${product.stock}`)
|
||
}
|
||
})
|
||
|
||
if (hasChange) {
|
||
uni.setStorageSync('products', JSON.stringify(products))
|
||
}
|
||
}
|
||
*/
|
||
console.log('模拟扣减库存成功', order.items)
|
||
} catch (e) {
|
||
console.error('扣减库存失败', e)
|
||
}
|
||
}
|
||
|
||
// 确认支付
|
||
const confirmPayment = async () => {
|
||
if (isPaying.value) return
|
||
|
||
// 余额支付检查
|
||
if (selectedMethod.value === 'balance') {
|
||
if (userBalance.value < amount.value) {
|
||
uni.showToast({
|
||
title: '余额不足',
|
||
icon: 'none'
|
||
})
|
||
return
|
||
}
|
||
|
||
if (!showPassword.value) {
|
||
showPassword.value = true
|
||
return
|
||
}
|
||
|
||
if (password.value.length !== 6) {
|
||
uni.showToast({
|
||
title: '请输入6位支付密码',
|
||
icon: 'none'
|
||
})
|
||
return
|
||
}
|
||
}
|
||
|
||
isPaying.value = true
|
||
|
||
try {
|
||
// 模拟支付过程
|
||
await new Promise(resolve => setTimeout(resolve, 2000))
|
||
|
||
// 更新订单状态
|
||
updateOrderInStorage(2) // 2: 待发货(已支付)
|
||
|
||
// 扣减库存
|
||
reduceStock(orderId.value)
|
||
|
||
/* const { error } = await supa
|
||
.from('orders')
|
||
.update({
|
||
status: 2, // 待发货
|
||
payment_method: getPaymentMethodCode(selectedMethod.value),
|
||
payment_status: 1, // 已支付
|
||
updated_at: new Date().toISOString()
|
||
})
|
||
.eq('id', orderId.value)
|
||
|
||
if (error !== null) {
|
||
throw error
|
||
}
|
||
|
||
// 余额支付需要扣减余额
|
||
if (selectedMethod.value === 'balance') {
|
||
await updateUserBalance(-amount.value)
|
||
} */
|
||
|
||
// 支付成功
|
||
uni.showToast({
|
||
title: '支付成功',
|
||
icon: 'success',
|
||
duration: 2000
|
||
})
|
||
|
||
// 发布订单更新事件,让profile页面可以刷新数据
|
||
uni.$emit('orderUpdated', { orderId: orderId.value, status: 2 }) // 2: 待发货
|
||
|
||
// 跳转到支付成功页面
|
||
setTimeout(() => {
|
||
uni.redirectTo({
|
||
url: `/pages/mall/consumer/payment-success?orderId=${orderId.value}`
|
||
})
|
||
}, 1500)
|
||
|
||
} catch (err) {
|
||
console.error('支付失败:', err)
|
||
uni.showToast({
|
||
title: '支付失败',
|
||
icon: 'none'
|
||
})
|
||
} finally {
|
||
isPaying.value = false
|
||
}
|
||
}
|
||
|
||
// 获取支付方式代码
|
||
const getPaymentMethodCode = (methodId: string): number => {
|
||
const codes: Record<string, number> = {
|
||
wechat: 1,
|
||
alipay: 2,
|
||
balance: 3,
|
||
bankcard: 4
|
||
}
|
||
return codes[methodId] || 0
|
||
}
|
||
|
||
// 更新用户余额
|
||
const updateUserBalance = async (change: number) => {
|
||
const userId = getCurrentUserId()
|
||
if (!userId) return
|
||
|
||
try {
|
||
/* const { data: wallet, error: walletError } = await supa
|
||
.from('user_wallets')
|
||
.select('balance')
|
||
.eq('user_id', userId)
|
||
.single()
|
||
|
||
if (walletError !== null) {
|
||
console.error('查询钱包失败:', walletError)
|
||
return
|
||
}
|
||
|
||
const newBalance = (wallet?.balance || 0) + change
|
||
|
||
const { error: updateError } = await supa
|
||
.from('user_wallets')
|
||
.update({ balance: newBalance })
|
||
.eq('user_id', userId)
|
||
|
||
if (updateError !== null) {
|
||
console.error('更新余额失败:', updateError)
|
||
return
|
||
}
|
||
|
||
// 记录余额变动
|
||
const { error: recordError } = await supa
|
||
.from('balance_records')
|
||
.insert({
|
||
user_id: userId,
|
||
change_amount: change,
|
||
current_balance: newBalance,
|
||
change_type: 'order_payment',
|
||
related_id: orderId.value,
|
||
remark: `订单支付: ${orderNo.value}`
|
||
})
|
||
|
||
if (recordError !== null) {
|
||
console.error('记录余额变动失败:', recordError)
|
||
}
|
||
|
||
userBalance.value = newBalance */
|
||
} catch (err) {
|
||
console.error('更新余额异常:', err)
|
||
}
|
||
}
|
||
|
||
// 输入密码
|
||
const inputPassword = (num: string) => {
|
||
if (password.value.length >= 6) return
|
||
password.value += num
|
||
}
|
||
|
||
// 删除密码
|
||
const deletePassword = () => {
|
||
if (password.value.length > 0) {
|
||
password.value = password.value.slice(0, -1)
|
||
}
|
||
}
|
||
|
||
// 监听密码输入
|
||
watch(password, (newPassword) => {
|
||
if (newPassword.length === 6) {
|
||
// 自动验证密码
|
||
verifyPassword()
|
||
}
|
||
})
|
||
|
||
// 验证密码
|
||
const verifyPassword = async () => {
|
||
// 这里应该验证支付密码,这里简单模拟
|
||
const userId = getCurrentUserId()
|
||
|
||
try {
|
||
// 模拟验证
|
||
await new Promise(resolve => setTimeout(resolve, 500))
|
||
|
||
// 假设密码正确
|
||
const isCorrect = true
|
||
|
||
if (isCorrect) {
|
||
// 密码正确,继续支付
|
||
confirmPayment()
|
||
} else {
|
||
password.value = ''
|
||
uni.showToast({
|
||
title: '密码错误',
|
||
icon: 'none'
|
||
})
|
||
}
|
||
|
||
} catch (err) {
|
||
console.error('验证密码异常:', err)
|
||
}
|
||
}
|
||
|
||
// 忘记密码
|
||
const forgotPassword = () => {
|
||
uni.navigateTo({
|
||
url: '/pages/user/forgot-password'
|
||
})
|
||
}
|
||
|
||
// 计算价格明细
|
||
const calculatePriceDetails = (totalAmount: number) => {
|
||
// 模拟计算各项费用
|
||
// 假设商品总价占总金额的80%,运费占10%,优惠减免占10%
|
||
productAmount.value = totalAmount * 0.8
|
||
deliveryFee.value = totalAmount * 0.1
|
||
discountAmount.value = totalAmount * 0.1
|
||
|
||
// 确保总和等于应付金额
|
||
const calculatedTotal = productAmount.value + deliveryFee.value - discountAmount.value
|
||
if (Math.abs(calculatedTotal - totalAmount) > 0.01) {
|
||
// 调整商品总价以匹配应付金额
|
||
productAmount.value = totalAmount + discountAmount.value - deliveryFee.value
|
||
}
|
||
}
|
||
|
||
// 在组件卸载时移除返回键监听
|
||
onUnmounted(() => {
|
||
// uni.offBackPress() 在uni-app中不需要手动移除
|
||
})
|
||
|
||
// 返回
|
||
const goBack = () => {
|
||
uni.showModal({
|
||
title: '取消支付',
|
||
content: '确定要取消支付吗?取消后订单将保存到待支付订单中',
|
||
confirmText: '取消支付',
|
||
cancelText: '继续支付',
|
||
success: async (res) => {
|
||
if (res.confirm) {
|
||
// 用户确认取消支付,更新订单状态为待支付
|
||
await cancelPayment()
|
||
} else {
|
||
// 用户选择继续支付,留在当前页面
|
||
return
|
||
}
|
||
}
|
||
})
|
||
}
|
||
|
||
// 取消支付,更新订单状态
|
||
const cancelPayment = async () => {
|
||
try {
|
||
// 这里应该调用API更新订单状态为待支付(status: 1)
|
||
// 模拟更新订单状态
|
||
/* const { error } = await supa
|
||
.from('orders')
|
||
.update({
|
||
status: 1, // 待支付
|
||
updated_at: new Date().toISOString()
|
||
})
|
||
.eq('id', orderId.value)
|
||
|
||
if (error !== null) {
|
||
console.error('更新订单状态失败:', error)
|
||
uni.showToast({
|
||
title: '操作失败',
|
||
icon: 'none'
|
||
})
|
||
return
|
||
} */
|
||
|
||
// 更新本地存储
|
||
updateOrderInStorage(orderId.value, 1) // 1: 待支付
|
||
|
||
// 发布订单更新事件,让profile页面可以刷新数据
|
||
uni.$emit('orderUpdated', { orderId: orderId.value, status: 1 })
|
||
|
||
uni.showToast({
|
||
title: '已保存到待支付订单',
|
||
icon: 'success'
|
||
})
|
||
|
||
// 延迟返回,让用户看到提示
|
||
setTimeout(() => {
|
||
uni.navigateBack()
|
||
}, 1500)
|
||
|
||
} catch (err) {
|
||
console.error('取消支付异常:', err)
|
||
uni.showToast({
|
||
title: '操作失败',
|
||
icon: 'none'
|
||
})
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style scoped>
|
||
.payment-page {
|
||
display: flex;
|
||
flex-direction: column;
|
||
height: 100vh;
|
||
background-color: #f5f5f5;
|
||
}
|
||
|
||
.payment-header {
|
||
background-color: #ffffff;
|
||
padding: 15px;
|
||
display: flex;
|
||
align-items: center;
|
||
border-bottom: 1px solid #e5e5e5;
|
||
}
|
||
|
||
.back-btn {
|
||
font-size: 24px;
|
||
color: #333333;
|
||
padding: 5px;
|
||
margin-right: 15px;
|
||
}
|
||
|
||
.header-title {
|
||
font-size: 18px;
|
||
font-weight: bold;
|
||
color: #333333;
|
||
}
|
||
|
||
.payment-content {
|
||
flex: 1;
|
||
overflow-y: auto;
|
||
}
|
||
|
||
/* 价格明细部分 */
|
||
.price-detail-section {
|
||
background-color: #ffffff;
|
||
padding: 20px 15px;
|
||
margin-bottom: 10px;
|
||
}
|
||
|
||
.price-detail {
|
||
padding: 15px;
|
||
background-color: #f8f9fa;
|
||
border-radius: 8px;
|
||
margin-bottom: 15px;
|
||
}
|
||
|
||
.price-row {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
padding: 8px 0;
|
||
}
|
||
|
||
.price-row.total {
|
||
border-top: 1px solid #e5e5e5;
|
||
margin-top: 8px;
|
||
padding-top: 15px;
|
||
}
|
||
|
||
.price-label {
|
||
font-size: 14px;
|
||
color: #666666;
|
||
}
|
||
|
||
.price-value {
|
||
font-size: 14px;
|
||
color: #333333;
|
||
}
|
||
|
||
.price-value.discount {
|
||
color: #4caf50;
|
||
}
|
||
|
||
.price-value.total-price {
|
||
font-size: 18px;
|
||
color: #ff4757;
|
||
font-weight: bold;
|
||
}
|
||
|
||
.order-no {
|
||
display: block;
|
||
font-size: 12px;
|
||
color: #999999;
|
||
text-align: center;
|
||
}
|
||
|
||
.methods-section {
|
||
background-color: #ffffff;
|
||
padding: 20px 15px;
|
||
margin-bottom: 10px;
|
||
}
|
||
|
||
.section-title {
|
||
display: block;
|
||
font-size: 16px;
|
||
font-weight: bold;
|
||
color: #333333;
|
||
margin-bottom: 15px;
|
||
}
|
||
|
||
.method-list {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 10px;
|
||
}
|
||
|
||
.method-item {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
padding: 15px;
|
||
border: 1px solid #e5e5e5;
|
||
border-radius: 8px;
|
||
}
|
||
|
||
.method-item.selected {
|
||
border-color: #007aff;
|
||
background-color: #f0f8ff;
|
||
}
|
||
|
||
.method-left {
|
||
display: flex;
|
||
align-items: center;
|
||
}
|
||
|
||
.method-icon {
|
||
font-size: 24px;
|
||
margin-right: 15px;
|
||
}
|
||
|
||
.method-info {
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
|
||
.method-name {
|
||
font-size: 16px;
|
||
font-weight: bold;
|
||
color: #333333;
|
||
margin-bottom: 5px;
|
||
}
|
||
|
||
.method-desc {
|
||
font-size: 12px;
|
||
color: #999999;
|
||
}
|
||
|
||
.method-selected {
|
||
width: 24px;
|
||
height: 24px;
|
||
border-radius: 12px;
|
||
background-color: #007aff;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
.selected-icon {
|
||
color: #ffffff;
|
||
font-size: 14px;
|
||
}
|
||
|
||
.balance-section {
|
||
background-color: #ffffff;
|
||
padding: 15px;
|
||
margin-bottom: 10px;
|
||
}
|
||
|
||
.balance-info {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 10px;
|
||
}
|
||
|
||
.balance-label {
|
||
font-size: 14px;
|
||
color: #333333;
|
||
}
|
||
|
||
.balance-value {
|
||
font-size: 18px;
|
||
color: #ff4757;
|
||
font-weight: bold;
|
||
}
|
||
|
||
.balance-tip {
|
||
padding: 10px;
|
||
background-color: #fff0f0;
|
||
border-radius: 5px;
|
||
}
|
||
|
||
.tip-text {
|
||
font-size: 12px;
|
||
color: #ff4757;
|
||
}
|
||
|
||
.password-section {
|
||
background-color: #ffffff;
|
||
padding: 30px 15px;
|
||
text-align: center;
|
||
margin-bottom: 10px;
|
||
}
|
||
|
||
.password-title {
|
||
display: block;
|
||
font-size: 16px;
|
||
color: #333333;
|
||
margin-bottom: 30px;
|
||
}
|
||
|
||
.password-input {
|
||
display: flex;
|
||
justify-content: center;
|
||
gap: 15px;
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.password-dot {
|
||
width: 12px;
|
||
height: 12px;
|
||
border-radius: 6px;
|
||
background-color: #333333;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
.password-dot text {
|
||
color: #ffffff;
|
||
font-size: 8px;
|
||
}
|
||
|
||
.forgot-password {
|
||
color: #007aff;
|
||
font-size: 14px;
|
||
}
|
||
|
||
.payment-bottom {
|
||
background-color: #ffffff;
|
||
border-top: 1px solid #e5e5e5;
|
||
padding: 15px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
}
|
||
|
||
.price-summary {
|
||
display: flex;
|
||
align-items: baseline;
|
||
}
|
||
|
||
.summary-label {
|
||
font-size: 14px;
|
||
color: #333333;
|
||
margin-right: 5px;
|
||
}
|
||
|
||
.summary-price {
|
||
font-size: 20px;
|
||
color: #ff4757;
|
||
font-weight: bold;
|
||
}
|
||
|
||
.pay-btn {
|
||
background-color: #007aff;
|
||
color: #ffffff;
|
||
padding: 0 40px;
|
||
height: 45px;
|
||
border-radius: 22.5px;
|
||
font-size: 16px;
|
||
font-weight: bold;
|
||
border: none;
|
||
}
|
||
|
||
.pay-btn.disabled {
|
||
background-color: #cccccc;
|
||
opacity: 0.6;
|
||
}
|
||
|
||
.password-keyboard {
|
||
background-color: #ffffff;
|
||
border-top: 1px solid #e5e5e5;
|
||
padding: 10px;
|
||
}
|
||
|
||
.keyboard-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(3, 1fr);
|
||
grid-gap: 1px;
|
||
background-color: #e5e5e5;
|
||
}
|
||
|
||
.keyboard-key {
|
||
background-color: #ffffff;
|
||
height: 60px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
.key-text {
|
||
font-size: 24px;
|
||
color: #333333;
|
||
}
|
||
</style>
|