consumer模块完成度95%,优化安卓端界面和小程序测试2
This commit is contained in:
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user