This commit is contained in:
zhangjianjun 2026-02-04 18:23:44 +08:00
parent e9537f7cc0
commit e6c7f1e99c
20 changed files with 871 additions and 1 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
unpackage/

1
1.txt
View File

@ -1 +0,0 @@
11

20
App.vue Normal file
View File

@ -0,0 +1,20 @@
<script>
export default {
globalData: {
token: '',
},
onLaunch: function() {
console.log('App Launch')
},
onShow: function() {
console.log('App Show')
},
onHide: function() {
console.log('App Hide')
}
}
</script>
<style>
/*每个页面公共css */
</style>

20
index.html Normal file
View File

@ -0,0 +1,20 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<script>
var coverSupport = 'CSS' in window && typeof CSS.supports === 'function' && (CSS.supports('top: env(a)') ||
CSS.supports('top: constant(a)'))
document.write(
'<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0' +
(coverSupport ? ', viewport-fit=cover' : '') + '" />')
</script>
<title></title>
<!--preload-links-->
<!--app-context-->
</head>
<body>
<div id="app"><!--app-html--></div>
<script type="module" src="/main.js"></script>
</body>
</html>

3
lib/baseUrl.js Normal file
View File

@ -0,0 +1,3 @@
// const BASE_URL = 'https://u.batiao8.com' //测试
const BASE_URL = 'https://pay.batiao8.com' //正式
export default BASE_URL

47
lib/request.js Normal file
View File

@ -0,0 +1,47 @@
import BASE_URL from './baseUrl.js'
const request = async ({
url,
method,
data
}) => {
try {
const app = getApp();
const globalData = app.globalData || {}
let header = {};
if (globalData.token) {
header['x-token'] = globalData.token;
}
let res = await uni.request({
url: BASE_URL + url,
method: method,
data: data,
// header: header
})
console.log('--res', res)
if (res.data) {
if(res.data.code && res.data.code !== 200) {
uni.showToast({
title: res.data.message.split(',')[0],
icon: 'none',
})
}
return res.data
} else {
uni.showToast({
title: res.data.message,
icon: 'none',
})
return {}
}
} catch (err) {
console.log('--err', err)
uni.showToast({
title: err.errMsg,
icon: 'none',
})
return {}
}
}
export default request

22
main.js Normal file
View File

@ -0,0 +1,22 @@
import App from './App'
// #ifndef VUE3
import Vue from 'vue'
import './uni.promisify.adaptor'
Vue.config.productionTip = false
App.mpType = 'app'
const app = new Vue({
...App
})
app.$mount()
// #endif
// #ifdef VUE3
import { createSSRApp } from 'vue'
export function createApp() {
const app = createSSRApp(App)
return {
app
}
}
// #endif

73
manifest.json Normal file
View File

@ -0,0 +1,73 @@
{
"name" : "batiao-pay",
"appid" : "",
"description" : "",
"versionName" : "1.0.0",
"versionCode" : "100",
"transformPx" : false,
/* 5+App */
"app-plus" : {
"usingComponents" : true,
"nvueStyleCompiler" : "uni-app",
"compilerVersion" : 3,
"splashscreen" : {
"alwaysShowBeforeRender" : true,
"waiting" : true,
"autoclose" : true,
"delay" : 0
},
/* */
"modules" : {},
/* */
"distribute" : {
/* android */
"android" : {
"permissions" : [
"<uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\"/>",
"<uses-permission android:name=\"android.permission.MOUNT_UNMOUNT_FILESYSTEMS\"/>",
"<uses-permission android:name=\"android.permission.VIBRATE\"/>",
"<uses-permission android:name=\"android.permission.READ_LOGS\"/>",
"<uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\"/>",
"<uses-feature android:name=\"android.hardware.camera.autofocus\"/>",
"<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\"/>",
"<uses-permission android:name=\"android.permission.CAMERA\"/>",
"<uses-permission android:name=\"android.permission.GET_ACCOUNTS\"/>",
"<uses-permission android:name=\"android.permission.READ_PHONE_STATE\"/>",
"<uses-permission android:name=\"android.permission.CHANGE_WIFI_STATE\"/>",
"<uses-permission android:name=\"android.permission.WAKE_LOCK\"/>",
"<uses-permission android:name=\"android.permission.FLASHLIGHT\"/>",
"<uses-feature android:name=\"android.hardware.camera\"/>",
"<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>"
]
},
/* ios */
"ios" : {},
/* SDK */
"sdkConfigs" : {}
}
},
/* */
"quickapp" : {},
/* */
"mp-weixin" : {
"appid" : "wxb0e5bc0e275ba55c",
"setting" : {
"urlCheck" : false
},
"usingComponents" : true
},
"mp-alipay" : {
"usingComponents" : true,
"appid" : "2021005194639068"
},
"mp-baidu" : {
"usingComponents" : true
},
"mp-toutiao" : {
"usingComponents" : true
},
"uniStatistics" : {
"enable" : false
},
"vueVersion" : "3"
}

17
pages.json Normal file
View File

@ -0,0 +1,17 @@
{
"pages": [ //pageshttps://uniapp.dcloud.io/collocation/pages
{
"path": "pages/index/index",
"style": {
"navigationBarTitleText": ""
}
}
],
"globalStyle": {
"navigationBarTextStyle": "black",
"navigationBarTitleText": "",
"navigationBarBackgroundColor": "#F8F8F8",
"backgroundColor": "#F8F8F8"
},
"uniIdRouter": {}
}

412
pages/index/index.vue Normal file
View File

@ -0,0 +1,412 @@
<template>
<view class="wrapper" style="height: 100vh">
<!-- 浏览器不支持提示 -->
<view class="wrapper" id="pay-result" style="background: #fff" v-if="envErrType">
<view class="pay-result-container">
<image src="/static/image/payerr.png" style="width: 96px; height: 96px"></image>
<view class="env-err-text">{{ envErrText }}</view>
</view>
</view>
<view class="wrapper" id="pay-result" style="background: #fff" v-else-if="orderInfo.status === 2">
<view class="pay-result-container">
<image v-if="browerEnv === 'alipay'" src="/static/image/ali-payok.png" style="width: 96px; height: 96px"></image>
<image v-if="browerEnv === 'weixin'" src="/static/image/wx-payok.png" style="width: 96px; height: 96px"></image>
<view class="pay-result-text">支付已完成</view>
</view>
</view>
<!-- 支付页面 -->
<view class="wrapper" id="pay-page" v-else-if="currentPage === 'pay-page'">
<view class="item-name">{{ orderInfo.goodsName }}</view>
<view class="item-count">
<text style="font-size: 26px">¥&nbsp;</text>{{ numberFormat(orderInfo.totalFee) }}
</view>
<!-- <view class="company">重庆八条科技有限公司</view> -->
<!-- 支付按钮 -->
<view class="pay-methods">
<view v-for="item in payMethodList" :key="item.id" class="pay-method-item">
<view class="pay-method-item-left">
<image :src="item.logo" style="width: 30px; height: 30px"></image>
<text class="pay-method-name">{{ item.name }}</text>
</view>
<view class="pay-method-item-right">
<image :src="item.checkIcon" style="width: 18px; height: 18px"></image>
</view>
</view>
</view>
<button id="pay-btn" :class="[`${browerEnv}-btn`]" @click="goPay">
立即支付
</button>
</view>
<!-- 支付结果页面 -->
<view class="wrapper" id="pay-result" style="background: #fff" v-else-if="currentPage === 'pay-result'">
<view class="pay-result-container">
<image :src="payResultItem.icon" style="width: 96px; height: 96px"></image>
<view class="pay-result-text">{{ payResultItem.resultText }}</view>
<button v-if="fromApp" class="back-btn" open-type="launchApp" app-parameter="wechat" :binderror="launchAppError">返回APP</button>
<button v-else class="back-btn" :style="payResultItem.style" @click="goBackPay">
{{ payResultItem.backText }}
</button>
</view>
</view>
</view>
</template>
<script>
import request from "@/lib/request.js";
export default {
data() {
return {
authCode: '',
encOrderId: "",
outTradeNo: "",
browerEnv: "",
orderInfo: {},
currentPage: "pay-page",
paySuccess: true,
payMethodList: [{
name: "支付宝支付",
id: "alipay",
logo: "/static/image/ali-logo.png",
checkIcon: "/static/image/ali-check.png",
},
{
name: "微信支付",
id: "weixin",
logo: "/static/image/wx-logo.png",
checkIcon: "/static/image/wx-check.png",
},
],
timer: null,
fromApp: false,
};
},
computed: {
payResultList() {
return [{
icon: "/static/image/payerr.png",
resultText: "抱歉,订单支付失败",
backText: "重新支付",
style: {background: this.browerEnv === "weixin" ? '#05bf5e' : '#2c78fe', color: '#fff'},
},
{
icon: this.browerEnv === "weixin" ?
"/static/image/wx-payok.png" : "/static/image/ali-payok.png",
resultText: "支付成功",
backText: "返回",
},
];
},
payResultItem() {
return this.paySuccess ? this.payResultList[1] : this.payResultList[0];
},
envErrType() {
if(!this.browerEnv) return '';
if (this.browerEnv === "browser") {
return "envErr:browser";
} else if (!this.encOrderId && !this.outTradeNo) {
return "envErr:noParams";
} else {
return ''
}
},
envErrText() {
const group = {
"envErr:browser": "不支持浏览器,请使用支付宝或微信重新扫码",
"envErr:noParams": "请通过扫描二维码使用小程序",
"envErr:outTradeNo": "获取订单信息失败",
};
return group[this.envErrType];
},
},
methods: {
launchAppError(e) {
console.log(e.detail.errMsg)
},
numberFormat(value) {
if (value) return value.toFixed(2);
return "0.00";
},
//
getAliUserInfo() {
//
// #ifdef MP-ALIPAY
my.getAuthCode({
scopes: ["auth_base"],
success: (res) => {
console.log("获取授权码成功", res);
this.authCode = res.authCode;
},
fail: (error) => {
console.error("获取授权码失败", error);
uni.showToast({
icon: "none",
title: "获取授权码失败",
});
},
});
// #endif
},
//
getWxUserInfo() {
// #ifdef MP-WEIXIN
uni.getProvider({
service: 'oauth',
success: (res) => {
console.log('getProvider', res.provider)
uni.login({
provider: res.provider,
success: (loginRes) => {
this.authCode = loginRes.code
console.log('loginRes', this.authCode)
}
})
}
})
// #endif
},
//
async goPay() {
uni.showLoading()
try {
//
const {
outTradeNo,
paySource = "jsapi"
} = this.orderInfo;
let extra = {};
// #ifdef MP-ALIPAY
extra.alipayAuthCode = this.authCode;
// #endif
// #ifdef MP-WEIXIN
extra.weixinAuthCode = this.authCode;
// #endif
console.log('authCode',this.authCode)
if (!this.authCode) {
console.error("没有授权码");
return;
}
let orderData = await this.handleOrder({
outTradeNo,
payType: this.browerEnv,
paySource: paySource,
extra,
});
let tradeNo = orderData.tradeNo;
console.log("tradeNo", tradeNo);
if(this.browerEnv === 'alipay') {
//
let res = await this.handleAlipay({
tradeNO: tradeNo,
});
console.log('支付宝支付成功', res)
} else if(this.browerEnv === 'weixin') {
let res = await this.handleWechatPay(orderData);
console.log('微信支付成功', res)
}
//
let orderRes = await this.getOrderInfo()
this.currentPage = "pay-result";
if (orderRes.status === 2) {
this.paySuccess = true;
// clearInterval(this.timer)
// this.timer = null
} else {
this.paySuccess = false;
}
uni.hideLoading()
} catch (error) {
console.log('error', error)
this.currentPage = "pay-result";
this.paySuccess = false; //
uni.hideLoading()
}
},
//
async getOrderInfo() {
let res = await request({
url: "/api/pay/order",
method: "GET",
data: {
encOrderId: this.encOrderId,
outTradeNo: this.outTradeNo
},
});
const data = {
...res.data,
totalFee: res.data.totalFee / 100,
};
return data;
},
//
async handleOrder(data) {
let res = await request({
url: "/api/pay/order",
method: "POST",
data: data,
});
console.log("handleOrder", res.data);
return res.data || {};
},
//
handleWechatPay(payParams) {
return new Promise((resolve, reject) => {
// Uniapp使uni.requestPayment API
uni.requestPayment({
provider: "wxpay",
timeStamp: payParams.timeStamp,
nonceStr: payParams.nonceStr,
package: payParams.package,
signType: payParams.signType,
paySign: payParams.paySign,
success: (res) => {
console.log("支付成功:", res);
resolve();
},
fail: (err) => {
console.error("支付失败:", err);
reject(new Error("支付失败"));
},
});
});
},
//
handleAlipay(payParams) {
console.log("支付宝支付处理");
return new Promise((resolve, reject) => {
// Uniapp使uni.requestPayment API
uni.requestPayment({
provider: "alipay",
orderInfo: payParams.tradeNO, //
success: (res) => {
if (res.resultCode === "9000") {
console.log("支付成功", res);
//
resolve();
} else {
console.log("支付处理中或失败", res);
//
resolve();
}
},
fail: (err) => {
console.error("支付失败:", err);
reject(new Error("支付失败"));
},
});
});
},
async goBackPay() {
this.orderInfo = await this.getOrderInfo();
this.currentPage = "pay-page";
},
// URL
getQuery() {
const pages = getCurrentPages();
const currentPage = pages[pages.length - 1];
const options = currentPage.options || {};
return options;
},
// //
getBrowserEnv() {
// H5
// #ifdef MP-WEIXIN
return "weixin";
// #endif
// #ifdef MP-ALIPAY
return "alipay";
// #endif
},
extractUrlParams(url) {
if (!url) {
return {};
}
// 使URL
const params = {};
const queryString = url.split("?")[1];
if (queryString) {
const pairs = queryString.split("&");
for (let i = 0; i < pairs.length; i++) {
const pair = pairs[i].split("=");
const key = decodeURIComponent(pair[0]);
const value = decodeURIComponent(pair[1] || "");
params[key] = value;
}
}
return params;
},
},
async onShow() {
console.log("on Show");
//
// #ifdef MP-ALIPAY
if (my) {
let res = my.getLaunchOptionsSync();
console.log("res", res);
const qrCode = res.query ? res.query.qrCode : '';
let {
encOrderId
} = this.extractUrlParams(qrCode);
if (encOrderId) {
this.encOrderId = encOrderId;
this.orderInfo = await this.getOrderInfo();
}
this.getAliUserInfo()
}
// #endif
},
async onLoad(options) {
console.log("options", options);
const env = this.getBrowserEnv();
console.log('env', env)
this.browerEnv = env;
if (env === "browser") return;
//
this.payMethodList = this.payMethodList.filter((item) => item.id === env);
// #ifdef MP-WEIXIN
if(options.q) { // q:
let {
encOrderId
} = this.extractUrlParams(decodeURIComponent(options.q));
console.log('encOrderId', encOrderId)
if (encOrderId) {
this.encOrderId = encOrderId;
this.orderInfo = await this.getOrderInfo();
}
}
if(options.outTradeNo) {
// app
this.fromApp = true;
this.outTradeNo = options.outTradeNo;
this.orderInfo = await this.getOrderInfo();
}
console.log('encOrderId', this.encOrderId)
console.log('outTradeNo', this.outTradeNo)
this.getWxUserInfo()
// #endif
},
};
</script>
<style>
@import url("/static/app.css");
</style>

167
static/app.css Normal file
View File

@ -0,0 +1,167 @@
body, page {
font-family: -apple-system, BlinkMacSystemFont, sans-serif;
text-align: center;
background-color: #f2f2f2;
margin: 0;
padding: 0;
height: 100vh;
overflow: hidden;
}
.wrapper {
width: 100%;
height: 100%;
box-sizing: border-box;
display: flex;
flex-flow: column;
align-items: center;
}
.item-name {
/* width: 160rpx; */
height: 56rpx;
margin-top: 140rpx;
font-family: PingFang SC, PingFang SC;
font-weight: 500;
font-size: 40rpx;
color: #1A1A1A;
line-height: 56rpx;
text-align: center;
font-style: normal;
text-transform: none;
}
.item-count {
/* width: 250rpx; */
height: 92rpx;
font-family: D-DIN-PRO, D-DIN-PRO;
font-weight: 500;
font-size: 80rpx;
color: #FF2C2C;
line-height: 72rpx;
text-align: center;
font-style: normal;
text-transform: none;
margin: 12rpx 0 16rpx 0;
}
.company {
/* width: 280rpx; */
height: 40rpx;
font-family: PingFang SC, PingFang SC;
font-weight: 400;
font-size: 28rpx;
color: #53896D;
line-height: 40rpx;
text-align: center;
font-style: normal;
text-transform: none;
}
.pay-methods {
margin-top: 80rpx;
margin-bottom: 120rpx;
}
.pay-method-item {
width: 640rpx;
height: 120rpx;
background: #FFFFFF;
box-shadow: 0rpx 0rpx 20rpx 0rpx rgba(36, 36, 36, 0.05);
border-radius: 16rpx;
padding: 0 28rpx;
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20rpx;
}
.pay-method-item:last-child {
margin-bottom: 0;
}
.pay-method-item-left {
display: flex;
align-items: center;
}
.pay-method-name {
margin-left: 16rpx;
}
#pay-btn {
width: 686rpx;
height: 104rpx;
border-radius: 1842rpx;
display: flex;
align-items: center;
justify-content: center;
font-family: PingFang SC, PingFang SC;
font-weight: 500;
font-size: 32rpx;
color: #FFFFFF;
line-height: 48rpx;
text-align: center;
font-style: normal;
text-transform: none;
}
.weixin-btn {
background: #06BF5E;
}
.alipay-btn {
background: #2c78fe;
}
.pay-result-container {
margin-top: 140rpx;
display: flex;
flex-flow: column;
justify-content: center;
align-items: center;
}
.pay-result-text {
font-family: PingFang SC, PingFang SC;
font-weight: 400;
font-size: 36rpx;
color: #1A1A1A;
line-height: 52rpx;
text-align: center;
font-style: normal;
text-transform: none;
margin: 52rpx 0 140rpx 0;
}
.back-btn {
width: 312rpx;
height: 104rpx;
background: #E7E7E7;
border-radius: 1842rpx;
display: flex;
align-items: center;
justify-content: center;
font-family: PingFang SC, PingFang SC;
font-weight: 400;
font-size: 32rpx;
color: #666666;
line-height: 48rpx;
text-align: center;
font-style: normal;
text-transform: none;
}
.env-err-text {
margin-top: 82rpx;
width: 504rpx;
height: 112rpx;
font-family: AlibabaPuHuiTi, AlibabaPuHuiTi;
font-weight: 500;
font-size: 36rpx;
color: #F64F50;
line-height: 56rpx;
text-align: center;
font-style: normal;
text-transform: none;
}

BIN
static/image/ali-check.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

BIN
static/image/ali-logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

BIN
static/image/ali-payok.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

BIN
static/image/payerr.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

BIN
static/image/wx-check.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

BIN
static/image/wx-logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

BIN
static/image/wx-payok.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 KiB

13
uni.promisify.adaptor.js Normal file
View File

@ -0,0 +1,13 @@
uni.addInterceptor({
returnValue (res) {
if (!(!!res && (typeof res === "object" || typeof res === "function") && typeof res.then === "function")) {
return res;
}
return new Promise((resolve, reject) => {
res.then((res) => {
if (!res) return resolve(res)
return res[0] ? reject(res[0]) : resolve(res[1])
});
});
},
});

76
uni.scss Normal file
View File

@ -0,0 +1,76 @@
/**
* 这里是uni-app内置的常用样式变量
*
* uni-app 官方扩展插件及插件市场https://ext.dcloud.net.cn上很多三方插件均使用了这些样式变量
* 如果你是插件开发者建议你使用scss预处理并在插件代码中直接使用这些变量无需 import 这个文件方便用户通过搭积木的方式开发整体风格一致的App
*
*/
/**
* 如果你是App开发者插件使用者你可以通过修改这些变量来定制自己的插件主题实现自定义主题功能
*
* 如果你的项目同样使用了scss预处理你也可以直接在你的 scss 代码中使用如下变量同时无需 import 这个文件
*/
/* 颜色变量 */
/* 行为相关颜色 */
$uni-color-primary: #007aff;
$uni-color-success: #4cd964;
$uni-color-warning: #f0ad4e;
$uni-color-error: #dd524d;
/* 文字基本颜色 */
$uni-text-color:#333;//基本色
$uni-text-color-inverse:#fff;//反色
$uni-text-color-grey:#999;//辅助灰色如加载更多的提示信息
$uni-text-color-placeholder: #808080;
$uni-text-color-disable:#c0c0c0;
/* 背景颜色 */
$uni-bg-color:#ffffff;
$uni-bg-color-grey:#f8f8f8;
$uni-bg-color-hover:#f1f1f1;//点击状态颜色
$uni-bg-color-mask:rgba(0, 0, 0, 0.4);//遮罩颜色
/* 边框颜色 */
$uni-border-color:#c8c7cc;
/* 尺寸变量 */
/* 文字尺寸 */
$uni-font-size-sm:12px;
$uni-font-size-base:14px;
$uni-font-size-lg:16px;
/* 图片尺寸 */
$uni-img-size-sm:20px;
$uni-img-size-base:26px;
$uni-img-size-lg:40px;
/* Border Radius */
$uni-border-radius-sm: 2px;
$uni-border-radius-base: 3px;
$uni-border-radius-lg: 6px;
$uni-border-radius-circle: 50%;
/* 水平间距 */
$uni-spacing-row-sm: 5px;
$uni-spacing-row-base: 10px;
$uni-spacing-row-lg: 15px;
/* 垂直间距 */
$uni-spacing-col-sm: 4px;
$uni-spacing-col-base: 8px;
$uni-spacing-col-lg: 12px;
/* 透明度 */
$uni-opacity-disabled: 0.3; // 组件禁用态的透明度
/* 文章场景相关 */
$uni-color-title: #2C405A; // 文章标题颜色
$uni-font-size-title:20px;
$uni-color-subtitle: #555555; // 二级标题颜色
$uni-font-size-subtitle:26px;
$uni-color-paragraph: #3F536E; // 文章段落颜色
$uni-font-size-paragraph:15px;