consumer模块完成度95%,优化安卓端界面和小程序测试2

This commit is contained in:
cyh666666
2026-03-12 15:20:45 +08:00
parent 77f9968d18
commit b2a6e5a142
633 changed files with 4405 additions and 1651 deletions

View File

@@ -2,79 +2,96 @@
<template>
<view class="payment-page">
<scroll-view class="payment-content" direction="vertical">
<!-- 价格明细 -->
<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 class="payment-amount-header">
<text class="amount-label">支付金额</text>
<view class="amount-value-row">
<text class="amount-currency">¥</text>
<text class="amount-number">{{ amount.toFixed(2) }}</text>
</view>
<text class="order-no">订单号: {{ orderNo }}</text>
<text class="order-no-text">订单号: {{ orderNo }}</text>
</view>
<!-- 支付方式 -->
<view class="methods-section">
<text class="section-title">选择支付方式</text>
<view class="methods-section-new">
<view class="section-header">
<text class="section-title">选择支付方式</text>
</view>
<view class="method-list">
<view v-for="method in paymentMethods"
:key="method.id"
:class="['method-item', { selected: selectedMethod === method.id }]"
class="method-item-modern"
@click="selectMethod(method)">
<view class="method-left">
<text class="method-icon">{{ getMethodIcon(method.id) }}</text>
<image class="method-img" :src="getMethodBrandIcon(method.id)" mode="aspectFit" />
<view class="method-info">
<text class="method-name">{{ method.name }}</text>
<text class="method-desc">{{ method.description }}</text>
<text class="method-desc" v-if="method.id === 'balance'">当前余额: ¥{{ userBalance.toFixed(2) }}</text>
<text class="method-desc" v-else>{{ method.description }}</text>
</view>
</view>
<view v-if="selectedMethod === method.id" class="method-selected">
<text class="selected-icon">✓</text>
<view class="method-right">
<view :class="['radio-circle', { checked: selectedMethod === method.id }]">
<view class="radio-inner" v-if="selectedMethod === method.id"></view>
</view>
</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-popup-mask" @click="closePasswordPopup">
<view class="password-popup-content" @click.stop="">
<view class="popup-header">
<text class="popup-close" @click="closePasswordPopup">✕</text>
<text class="popup-title">请输入支付密码</text>
<view class="popup-placeholder"></view>
</view>
<view class="popup-amount-info">
<text class="popup-amount-label">支付金额</text>
<view class="popup-amount-row">
<text class="popup-currency">¥</text>
<text class="popup-value">{{ amount.toFixed(2) }}</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" class="password-dot-text">●</text>
<view class="password-input-row">
<view v-for="(_, index) in 6"
:key="index"
class="password-box">
<view v-if="password.length > index" class="password-dot"></view>
</view>
</view>
<text class="forgot-password-link" @click="forgotPassword">忘记密码?</text>
<!-- 这里的密码键盘会被放在页面底部,但我们可以通过 CSS 控制它 -->
<!-- 移动键盘到弹窗内部 -->
<view class="password-keyboard-popup">
<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>
<text class="forgot-password" @click="forgotPassword">忘记密码?</text>
</view>
</scroll-view>
<!-- 底部支付按钮 -->
<view class="payment-bottom">
<view class="payment-bottom" v-if="!showPassword">
<view class="price-summary">
<text class="summary-label">需支付:</text>
<text class="summary-price">¥{{ amount.toFixed(2) }}</text>
@@ -87,24 +104,6 @@
</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>
@@ -415,6 +414,20 @@ const getMethodIcon = (methodId: string): string => {
return '💳'
}
// 获取支付品牌图片
const getMethodBrandIcon = (methodId: string): string => {
if (methodId === 'wechat') {
return '/static/logo.png' // 替换为真实的微信支付图标路径
} else if (methodId === 'alipay') {
return '/static/logo.png' // 替换为真实的支付宝图标路径
} else if (methodId === 'balance') {
return '/static/logo.png' // 替换为真实的余额支付图标路径
} else if (methodId === 'bankcard') {
return '/static/logo.png' // 替换为真实的银行卡支付图标路径
}
return '/static/logo.png'
}
// 选择支付方式
const selectMethod = (method: PaymentMethodType) => {
if (!method.enabled) {
@@ -426,10 +439,17 @@ const selectMethod = (method: PaymentMethodType) => {
}
selectedMethod.value = method.id
showPassword.value = method.id === 'balance' || method.id === 'bankcard'
// 切换方式时,除非点击支付,否则不自动弹出密码
showPassword.value = false
password.value = '' // 清空密码
}
// 关闭密码弹窗
const closePasswordPopup = () => {
showPassword.value = false
password.value = ''
}
// 获取支付按钮文本
const getPayButtonText = (): string => {
if (selectedMethod.value === 'balance' && userBalance.value < amount.value) {
@@ -457,9 +477,9 @@ const getPayButtonText = (): string => {
const confirmPayment = async () => {
if (isPaying.value) return
// 余额支付检查
if (selectedMethod.value === 'balance') {
if (userBalance.value < amount.value) {
// 余额支付或银行卡支付检查密码
if (selectedMethod.value === 'balance' || selectedMethod.value === 'bankcard') {
if (selectedMethod.value === 'balance' && userBalance.value < amount.value) {
uni.showToast({
title: '余额不足',
icon: 'none'
@@ -469,6 +489,7 @@ const confirmPayment = async () => {
if (!showPassword.value) {
showPassword.value = true
password.value = ''
return
}
@@ -649,137 +670,133 @@ onUnmounted(() => {
.payment-content {
flex: 1;
/* overflow-y: auto; */
background-color: #f8f8f8;
}
/* 价格明细部分 */
.price-detail-section {
.payment-amount-header {
background-color: #ffffff;
padding: 20px 15px;
margin-bottom: 10px;
}
.price-detail {
padding: 15px;
background-color: #f8f9fa;
border-radius: 8px;
margin-bottom: 15px;
}
.price-row {
padding: 40px 15px;
display: flex;
justify-content: space-between;
flex-direction: column;
align-items: center;
padding: 8px 0;
justify-content: center;
margin-bottom: 12px;
}
.price-row.total {
border-top: 1px solid #e5e5e5;
margin-top: 8px;
padding-top: 15px;
}
.price-label {
.amount-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;
color: #666;
margin-bottom: 10px;
}
.amount-value-row {
display: flex;
flex-direction: row;
align-items: baseline;
margin-bottom: 10px;
}
.amount-currency {
font-size: 20px;
font-weight: bold;
color: #333;
margin-right: 4px;
}
.amount-number {
font-size: 40px;
font-weight: bold;
color: #333;
}
.order-no-text {
font-size: 13px;
color: #999;
}
.methods-section-new {
background-color: #ffffff;
margin: 0 12px;
border-radius: 12px;
padding: 15px;
}
.section-header {
margin-bottom: 15px;
}
.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;
margin-bottom: 10px;
.method-item-modern {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
padding: 16px 0;
border-bottom: 0.5px solid #f5f5f5;
}
.method-item.selected {
border-color: #007aff;
background-color: #f0f8ff;
.method-item-modern:last-child {
border-bottom: none;
}
.method-left {
display: flex;
align-items: center;
display: flex;
flex-direction: row;
align-items: center;
}
.method-icon {
font-size: 24px;
margin-right: 15px;
.method-img {
width: 28px;
height: 28px;
margin-right: 12px;
}
.method-info {
display: flex;
flex-direction: column;
display: flex;
flex-direction: column;
}
.method-name {
font-size: 16px;
font-weight: bold;
color: #333333;
margin-bottom: 5px;
font-size: 15px;
color: #333;
}
.method-desc {
font-size: 12px;
color: #999999;
font-size: 11px;
color: #999;
margin-top: 2px;
}
.method-selected {
width: 24px;
height: 24px;
border-radius: 12px;
background-color: #007aff;
display: flex;
align-items: center;
justify-content: center;
.radio-circle {
width: 20px;
height: 20px;
border-radius: 10px;
border: 1px solid #ddd;
display: flex;
align-items: center;
justify-content: center;
}
.selected-icon {
color: #ffffff;
font-size: 14px;
.radio-circle.checked {
border-color: #ff5000;
background-color: #ff5000;
}
.radio-inner {
width: 10px;
height: 10px;
border-radius: 5px;
background-color: #ffffff;
}
.balance-section {
@@ -817,52 +834,186 @@ onUnmounted(() => {
color: #ff4757;
}
.password-section {
background-color: #ffffff;
padding: 30px 15px;
text-align: center;
margin-bottom: 10px;
/* 密码输入弹窗 */
.password-popup-mask {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.6);
display: flex;
flex-direction: column;
justify-content: flex-end;
z-index: 1000;
}
.password-title {
/* display: block; */
.password-popup-content {
background-color: #ffffff;
border-radius: 16px 16px 0 0;
padding: 20px 0; /* 减少左右内边距,让键盘撑满 */
display: flex;
flex-direction: column;
align-items: center;
animation: slideUp 0.3s ease-out;
width: 100%;
}
@keyframes slideUp {
from { transform: translateY(100%); }
to { transform: translateY(0); }
}
.popup-header {
width: 100%;
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
margin-bottom: 30px;
padding: 0 20px;
}
.popup-close {
font-size: 20px;
color: #999;
padding: 4px;
}
.popup-title {
font-size: 16px;
color: #333333;
font-weight: bold;
color: #333;
}
.popup-placeholder {
width: 28px;
}
.popup-amount-info {
display: flex;
flex-direction: column;
align-items: center;
margin-bottom: 30px;
}
.password-input {
.popup-amount-label {
font-size: 14px;
color: #666;
margin-bottom: 8px;
}
.popup-amount-row {
display: flex;
flex-direction: row;
align-items: baseline;
}
.popup-currency {
font-size: 18px;
font-weight: bold;
color: #333;
margin-right: 2px;
}
.popup-value {
font-size: 32px;
font-weight: bold;
color: #333;
}
.password-input-row {
display: flex;
flex-direction: row;
justify-content: center;
/* gap: 15px; */
margin-bottom: 20px;
}
.password-dot {
width: 12px;
height: 12px;
border-radius: 6px;
background-color: #333333;
.password-box {
width: 45px;
height: 45px;
border: 1px solid #ddd;
border-right: none;
display: flex;
align-items: center;
justify-content: center;
margin: 0 7.5px;
background-color: #f9f9f9;
}
.password-dot-text {
color: #ffffff;
font-size: 8px;
.password-box:first-child {
border-radius: 4px 0 0 4px;
}
.forgot-password {
color: #007aff;
font-size: 14px;
.password-box:last-child {
border-right: 1px solid #ddd;
border-radius: 0 4px 4px 0;
}
.password-dot {
width: 10px;
height: 10px;
border-radius: 5px;
background-color: #000;
}
.forgot-password-link {
font-size: 13px;
color: #576b95;
margin-bottom: 20px;
}
.password-section {
/* 移除旧的样式或保持隐藏 */
display: none;
}
/* 弹窗专用键盘样式 */
.password-keyboard-popup {
width: 100%;
background-color: #f5f5f5;
padding: 6px;
padding-bottom: env(safe-area-inset-bottom);
}
/* 键盘样式优化 */
.password-keyboard {
display: none; /* 隐藏独立键盘 */
}
.keyboard-grid {
display: flex;
flex-wrap: wrap;
background-color: #f5f5f5;
}
.keyboard-key {
width: 33.33%;
background-color: #ffffff;
height: 54px;
display: flex;
align-items: center;
justify-content: center;
border: 3px solid #f5f5f5;
box-sizing: border-box;
border-radius: 8px;
}
.keyboard-key:active {
background-color: #e0e0e0;
}
.key-text {
font-size: 22px;
font-weight: 500;
color: #333333;
}
.payment-bottom {
background-color: #ffffff;
border-top: 1px solid #e5e5e5;
padding: 15px;
border-top: 1px solid #f0f0f0;
padding: 12px 16px;
padding-bottom: constant(safe-area-inset-bottom);
padding-bottom: env(safe-area-inset-bottom);
display: flex;
align-items: center;
justify-content: space-between;
@@ -870,30 +1021,33 @@ onUnmounted(() => {
.price-summary {
display: flex;
align-items: flex-end;
flex-direction: row;
align-items: baseline;
}
.summary-label {
font-size: 14px;
color: #333333;
margin-right: 5px;
margin-right: 4px;
}
.summary-price {
font-size: 20px;
color: #ff4757;
font-size: 24px;
color: #ff5000;
font-weight: bold;
}
.pay-btn {
background-color: #007aff;
background-color: #ff5000;
color: #ffffff;
padding: 0 40px;
height: 45px;
border-radius: 22.5px;
height: 44px;
line-height: 44px;
border-radius: 22px;
font-size: 16px;
font-weight: bold;
border: none;
margin: 0;
}
.pay-btn.disabled {