Vue 3 + TypeScript + Element Plus 版本
javascript
<template>
<div class="payment-container">
<el-form
ref="paymentFormRef"
:model="formData"
:rules="formRules"
label-position="top"
class="payment-form"
@submit.prevent="handleSubmit"
>
<!-- 固定金额选项 -->
<div class="amount-options">
<div class="amount-row">
<div
v-for="amount in fixedAmounts.slice(0, 3)"
:key="amount.value"
class="amount-item"
:class="{ 'amount-item-active': selectedAmount === amount.value && !isCustomAmount }"
@click="selectFixedAmount(amount.value)"
>
{{ amount.label }}
</div>
</div>
<div class="amount-row">
<div
v-for="amount in fixedAmounts.slice(3)"
:key="amount.value"
class="amount-item"
:class="{ 'amount-item-active': selectedAmount === amount.value && !isCustomAmount }"
@click="selectFixedAmount(amount.value)"
>
{{ amount.label }}
</div>
</div>
</div>
<!-- 自定义金额 -->
<div class="custom-amount-section">
<div
class="amount-item custom-label"
:class="{ 'amount-item-active': isCustomAmount }"
@click="focusCustomAmount"
>
其他金额
</div>
<el-form-item prop="customAmount" class="custom-input-item">
<el-input
v-model="formData.customAmount"
type="number"
placeholder="请输入金额"
:class="{ 'custom-input-active': isCustomAmount }"
@focus="focusCustomAmount"
@input="handleAmountInput"
>
<template #append>元</template>
</el-input>
</el-form-item>
</div>
<!-- 提交按钮 -->
<el-form-item class="submit-item">
<el-button
type="primary"
:loading="loading"
@click="handleSubmit"
>
立即支付
</el-button>
</el-form-item>
</el-form>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, onMounted } from 'vue'
import { ElMessage, ElMessageBox, type FormInstance, type FormRules } from 'element-plus'
import axios from 'axios'
// 微信支付相关类型定义
interface WeixinPayParams {
appId: string
timeStamp: string
nonceStr: string
package: string
signType: string
paySign: string
}
interface PayResponseData {
code: number
msg: string
data?: WeixinPayParams
}
interface FixedAmount {
value: number
label: string
}
// 表单数据
const formData = reactive({
customAmount: ''
})
// 表单验证规则
const formRules: FormRules = {
customAmount: [
{
validator: (rule: any, value: string, callback: any) => {
if (isCustomAmount.value && (!value || Number(value) <= 0)) {
callback(new Error('请输入有效的金额'))
} else {
callback()
}
},
trigger: 'blur'
}
]
}
// 响应式数据
const paymentFormRef = ref<FormInstance>()
const loading = ref(false)
const selectedAmount = ref<number>(50)
const isCustomAmount = ref(false)
// 固定金额选项
const fixedAmounts: FixedAmount[] = [
{ value: 50, label: '50元' },
{ value: 100, label: '100元' },
{ value: 200, label: '200元' },
{ value: 300, label: '300元' },
{ value: 500, label: '500元' }
]
// 选择固定金额
const selectFixedAmount = (amount: number) => {
selectedAmount.value = amount
isCustomAmount.value = false
formData.customAmount = ''
}
// 聚焦自定义金额输入框
const focusCustomAmount = () => {
isCustomAmount.value = true
selectedAmount.value = 0
}
// 处理金额输入
const handleAmountInput = (value: string) => {
// 限制只能输入数字和小数点
const cleaned = value.replace(/[^\d.]/g, '')
if (cleaned !== value) {
formData.customAmount = cleaned
}
// 限制小数点后最多三位
const parts = cleaned.split('.')
if (parts.length > 1 && parts[1].length > 3) {
formData.customAmount = `${parts[0]}.${parts[1].slice(0, 3)}`
}
if (cleaned) {
isCustomAmount.value = true
selectedAmount.value = Number(cleaned)
}
}
// 获取当前支付金额
const getPaymentAmount = (): number => {
if (isCustomAmount.value && formData.customAmount) {
return Number(formData.customAmount)
}
return selectedAmount.value
}
// 微信支付函数
const onBridgeReady = (payData: WeixinPayParams) => {
const wechat = (window as any).WeixinJSBridge
if (!wechat) {
ElMessage.error('微信支付环境初始化失败')
return
}
wechat.invoke(
'getBrandWCPayRequest',
{
appId: payData.appId,
timeStamp: payData.timeStamp.toString(),
nonceStr: payData.nonceStr,
package: payData.package,
signType: payData.signType,
paySign: payData.paySign
},
(res: any) => {
if (res.err_msg === 'get_brand_wcpay_request:ok') {
ElMessage.success('支付成功')
// 支付成功后的处理逻辑
} else if (res.err_msg === 'get_brand_wcpay_request:cancel') {
ElMessage.warning('支付已取消')
} else {
ElMessage.error(`支付失败: ${res.err_msg}`)
}
}
)
}
// 初始化微信支付
const initWechatPayment = () => {
if (typeof (window as any).WeixinJSBridge === 'undefined') {
if (document.addEventListener) {
document.addEventListener(
'WeixinJSBridgeReady',
() => {},
false
)
} else if ((document as any).attachEvent) {
;(document as any).attachEvent('WeixinJSBridgeReady', () => {})
;(document as any).attachEvent('onWeixinJSBridgeReady', () => {})
}
}
}
// 提交表单
const handleSubmit = async () => {
const amount = getPaymentAmount()
if (amount <= 0) {
ElMessage.warning('请选择或输入支付金额')
return
}
try {
loading.value = true
// 获取微信code(这里假设code存储在localStorage)
const code = localStorage.getItem('wx-code')
if (!code) {
ElMessage.warning('请重新授权获取用户信息')
return
}
// 调用支付接口
const response = await axios.post<PayResponseData>('/h5/jsapi/pay', {
code,
money: amount
})
if (response.data.code === 0 && response.data.data) {
onBridgeReady(response.data.data)
} else {
ElMessage.error(response.data.msg || '支付失败')
}
} catch (error: any) {
console.error('支付请求失败:', error)
ElMessage.error(error.message || '网络错误,请重试')
} finally {
loading.value = false
}
}
// 组件挂载时初始化
onMounted(() => {
initWechatPayment()
// 验证是否有微信环境
if (!/(micromessenger)/i.test(navigator.userAgent)) {
ElMessage.warning('请在微信客户端中打开')
}
})
</script>
<style scoped>
.payment-container {
padding: 20px;
background-color: #ffffff;
min-height: 100vh;
}
.payment-form {
max-width: 400px;
margin: 0 auto;
}
.amount-options {
margin-bottom: 20px;
}
.amount-row {
display: flex;
justify-content: center;
gap: 20px;
margin-bottom: 20px;
}
.amount-item {
width: 100px;
height: 50px;
line-height: 50px;
text-align: center;
border: 1px solid #dcdfe6;
border-radius: 4px;
cursor: pointer;
transition: all 0.3s;
font-size: 16px;
color: #606266;
user-select: none;
}
.amount-item:hover {
border-color: #409eff;
color: #409eff;
}
.amount-item-active {
border-color: #409eff !important;
color: #409eff !important;
background-color: #ecf5ff;
}
.custom-amount-section {
display: flex;
align-items: center;
justify-content: center;
gap: 20px;
margin-bottom: 40px;
}
.custom-label {
width: 100px;
}
.custom-input-item {
margin: 0;
}
.custom-input-item :deep(.el-input) {
width: 120px;
}
.custom-input-active :deep(.el-input__inner) {
border-color: #409eff;
}
.submit-item {
text-align: center;
}
.submit-item :deep(.el-button) {
width: 200px;
height: 44px;
font-size: 16px;
}
</style>