扫一扫支付

This commit is contained in:
dzc committed 2026-06-08 14:35:33 +08:00
1 parent 97d72bd4bb
commit 2e1c8e7103
7 files changed
+203 -79

No files matched your search

+3 -1
View File
@@ -24,7 +24,9 @@
"Push" : {},
"Payment" : {},
"OAuth" : {},
"Maps" : {}
"Maps" : {},
"Barcode" : {},
"Camera" : {}
},
/* 应用发布信息 */
"distribute" : {
+18 -22
View File
@@ -1,37 +1,31 @@
<template>
<view class="w-full h-full">
<view class="flex-col-center p-30rpx">
<crud-layout title="支付成功" bg="/static/page/blue-bg.png">
<view class="flex-col-center">
<view class="flex-col-center m-y-50rpx">
<svg t="1742978804614" class="icon" viewBox="0 0 1024 1024" version="1.1"
xmlns="http://www.w3.org/2000/svg" p-id="1708" width="64" height="64">
<path
d="M512 0C230.4 0 0 230.4 0 512s230.4 512 512 512 512-230.4 512-512S793.6 0 512 0z m288 371.2L460.8 710.4c-6.4 6.4-19.2 12.8-25.6 12.8-12.8 0-19.2-6.4-25.6-12.8L224 531.2c-12.8-12.8-12.8-38.4 0-57.6 12.8-12.8 38.4-12.8 57.6 0l147.2 147.2L736 313.6c12.8-12.8 38.4-12.8 57.6 0 19.2 19.2 19.2 44.8 6.4 57.6z"
fill="#2BCDA3" p-id="1709" data-spm-anchor-id="a313x.search_index.0.i2.beb13a81SVstw0"
class="selected"></path>
</svg>
<text class="text-36 mt-3 font-bold c-#333333!">支付成功</text>
<view class="flex-row-center size-280rpx">
<AppLottiePlayer :animationData="successAnimation" width="280rpx" height="280rpx" :loop="false" />
</view>
<text class="text-36rpx mt-24rpx font-bold c-#333333!">支付成功</text>
</view>
<view class="flex-col-center m-b-60rpx">
<text class="text-90 c-#333333!">¥{{ scanPayData.payPrice }}</text>
<text class="text-90rpx c-#333333!">¥{{ scanPayData.payPrice }}</text>
</view>
<view class="w-full p-10rpx flex-col justify-center">
<view class="flex-row justify-between m-15rpx">
<text class="sub-text-30 c-#333333!">商家</text>
<text class="sub-text-30">{{ scanPayData.mchName }}</text>
<text class="sub-text-30rpx c-#333333!">商家</text>
<text class="sub-text-30rpx">{{ scanPayData.mchName }}</text>
</view>
<hr />
<view class="flex-row justify-between m-15rpx">
<text class="sub-text-30 c-#333333!">支付方式</text>
<text class="sub-text-30">扫一扫</text>
<text class="sub-text-30rpx c-#333333!">支付方式</text>
<text class="sub-text-30rpx">扫一扫</text>
</view>
</view>
</view>
<view class="p-40rpx fixed bottom-30rpx left-0 right-0 font-bold">
<tm-button :block="true" @click="goBack">完成</tm-button>
</view>
</view>
<view class="h-100rpx" />
<tm-button :block="true" class="w-full p-40rpx" @tap="goBack">完成</tm-button>
</crud-layout>
</template>
<script setup lang="ts">
@@ -39,6 +33,9 @@ import { ref } from "vue"
import { onShow } from "@dcloudio/uni-app"
import { usePayStore } from "@/sheep/stores/pay"
import successAnimation from "@/static/lottie-json/success.json"
import AppLottiePlayer from "@@/components/common/app-lottie-player.vue"
const scanPayData = ref<any>({})
onShow(() => {
@@ -47,7 +44,6 @@ onShow(() => {
})
const goBack = () => {
const url: string = window.location.href.split("h5")[0] + "h5/?launchParam=coopMerchant#/pages/tabbar/order"
window.location.replace(url)
uni.navigateBack()
}
</script>
+10 -12
View File
@@ -1,31 +1,29 @@
<template>
<crud-layout bg="/static/page/blue-bg.png">
<crud-layout :title="scanPayState.mchName" bg="/static/page/blue-bg.png">
<view class="h-screen flex flex-col">
<view class="mx-auto text-34 py-2 h-80rpx">{{ scanPayState.mchName }}</view>
<tm-sheet :margin="[0, 10]" class="flex flex-row">
<span class="text-2xl text-[#333] mr-1 line-height-normal">¥</span>
<span class="text-2xl text-[#333] mr-8rpx line-height-normal">¥</span>
<span class="text-3xl font-medium text-[#333]">{{ displayAmount }}</span>
</tm-sheet>
<tm-sheet v-if="walletList.length > 0" :margin="[0, 10]" class="flex flex-col">
<view class="text-gray-700 font-bold text-28 mb-1">选择支付钱包:</view>
<view class="text-gray-700 font-bold text-28rpx mb-8rpx">选择支付钱包:</view>
<view v-for="(v, index) in walletList" :key="index"
class="border-6 border-solid border-#efefef my-2 duration-200 flex justify-between items-center p-3 rounded-2"
:class="{ 'border-6 border-solid border-blue!': selectWallet.id == v.id }"
class="border-6rpx border-solid border-#efefef my-16rpx duration-200 flex justify-between items-center p-24rpx rounded-16rpx"
:class="{ 'border-6rpx border-solid border-blue!': selectWallet.id == v.id }"
@click="selectWallet = v">
<view class="flex items-center">
<image class="w-70 h-70 mr-2" :src="filePath(v.walletTypeIcon)" />
<view class="text-32">{{ v.walletTypeName }}</view>
<image class="w-70rpx h-70rpx mr-16rpx" :src="filePath(v.walletTypeIcon)" />
<view class="text-32rpx">{{ v.walletTypeName }}</view>
</view>
<view class="text-28 flex items-center">
<view class="text-28rpx flex items-center">
<view>余额:</view>
<text class="main-text-28">{{ hooks.useMoney(v.balance) }} 元</text>
</view>
</view>
</tm-sheet>
<tm-button class="mt-1" :block="true" @click="handlePay()" :disabled="loading">支 付</tm-button>
<tm-button class="mt-30rpx" :block="true" @click="handlePay()" :disabled="loading">支 付</tm-button>
<number-keyboard v-if="numberKeyboardShow" v-model="amount" />
</view>
@@ -163,7 +161,7 @@ const pay = async () => {
mchName: scanPayState.mchName,
payPrice: Number(amount.value).toFixed(2),
})
router.jump("/pages/pay/payComplete", {}, 2)
router.jump("/pages/pay/pay-complete", {}, 2)
} catch (error: any) {
uni.showToast({ title: "请求错误", icon: "error" })
uni.hideLoading()
+8 -3
View File
@@ -81,6 +81,7 @@ import {
ROLE_ONLINE_CANTEEN_MCH,
} from "@@/utils/user-role"
import { APPENV } from "@/sheep/constant"
import { requestPermission } from "@/sheep/utils/permission"
import InformationStreamAd from "@@/components/ad/information-stream-ad.vue"
import AppHotUpdate from "@/sheep/components/app-version/app-hot-update.vue"
@@ -90,7 +91,7 @@ const state = reactive<any>({
wallet: [],
userInfo: {},
menuList: [
{ text: "核销", icon: "/static/icon/scan.png", path: "" },
{ text: "扫一扫", icon: "/static/icon/scan.png", path: "" },
{ text: "钱包", icon: "/static/icon/service_3.png", path: "/pages/wallet/list" },
{ text: "喜好", icon: "/static/icon/service_4.png", path: "/pages/tabbar/canteenAnalyse" },
{ text: "记录", icon: "/static/icon/service_1.png", path: "/pages/ordering-food/records" },
@@ -292,8 +293,12 @@ const actModalCancel = () => {
const { scanCodeAction, processUrlScanResult, processBase64ScanResult } = hooks.useScanCode(actModalShow)
// 核销
// 扫一扫
const appScanCode = async () => {
// #ifdef APP-PLUS
const cameraPermission = await requestPermission("camera")
if (cameraPermission !== "granted") return
// #endif
const scanResult = await scanCodeAction()
if (!scanResult) {
uni.showToast({ title: "扫码错误", icon: "none" })
@@ -332,7 +337,7 @@ const onTabMenu = async (item: any) => {
if (isLogin) {
if (item.path == "/pages/tabbar/index") return uni.switchTab({ url: item.path })
switch (item.text) {
case "核销":
case "扫一扫":
await appScanCode()
break
case "商品":
+1 -1
View File
@@ -133,7 +133,7 @@ const doFreeze = async () => {
const doTransferMoney = (v: any) => {
if (v.walletTypeId) {
usePayStore().setSourceWalletData(v)
router.jump("/pages/pay/transfer-money")
router.jump("/pages/pay/pages/pay/pay-complete")
return
}
uni.showToast({ title: "钱包类型id不存在", icon: "none" })
+7 -6
View File
@@ -4,7 +4,7 @@ import { router } from ".."
import { APPENV } from "../constant"
import { getAppInfo, isDevEnv } from "../utils/app"
export function useScanCode(actModalShow) {
export function useScanCode(actModalShow, { onlyFromCamera = true } = {}) {
async function scanCodeAction(): Promise<string | undefined> {
const appInfo = await getAppInfo()
if (isDevEnv() && appInfo != APPENV.APP) {
@@ -13,8 +13,8 @@ export function useScanCode(actModalShow) {
// const testQrCode = "eyJoYXJkRG9vcklkIjoiMTkxNDIxNTYwODQzNzQxMTg0MSIsImNhYmluZXRObyI6IjEifQ==" // 测试线上-智能柜-收款二维码
// const testQrCode = "eyJpZCI6IjE5MDU4MTY3NTEwNDM0NTI5MzAiLCJuYW1lIjoi5paw5Y6/5bqc6LaF5biCIn0=" // 邓本地-测试环境-新县府超市收款二维码
// const testQrCode = "eyJoYXJkRG9vcklkIjoiMTkxMzE1NzA4MDQxODI5NTgwOSIsImNhYmluZXRObyI6IjEifQ==" // 邓本地-测试单门智能柜柜门二维码
// const testQrCode = "http://192.168.101.45:8889/h5/#/pages/pay/scanPay?id=1905816751043452930&name=测试新县府超市" // 邓本地-测试环境-新县府超市收款二维码
// const testQrCode = "http://192.168.101.45:8889/h5/#/pages/pay/scanPay?hardDoorId=1913157080418295809&cabinetNo=1" // 测试智能柜柜门二维码
// const testQrCode = "http://192.168.101.45:8889/h5/#/pages/pay/scan-pay?id=1905816751043452930&name=测试新县府超市" // 邓本地-测试环境-新县府超市收款二维码
// const testQrCode = "http://192.168.101.45:8889/h5/#/pages/pay/scan-pay?hardDoorId=1913157080418295809&cabinetNo=1" // 测试智能柜柜门二维码
const testQrCode = "eyJjYW5jZWxJZCI6IjE5ODQyNDA4MzM5NDMyMDc5MzcifQ==" // 测试商户活动核销
uni.showModal({ content: "开发环境不是浙政钉和诗画环境,扫码仅供测试" })
return Promise.resolve(testQrCode)
@@ -22,6 +22,7 @@ export function useScanCode(actModalShow) {
if (appInfo == APPENV.APP) {
const scanResult = await new Promise<string>((resolve, reject) => {
uni.scanCode({
onlyFromCamera,
success: (res) => {
console.log("APP扫码成功:", JSON.stringify(res))
resolve(res.result || "")
@@ -64,12 +65,12 @@ export function useScanCode(actModalShow) {
// 扫收款机上生成的码支付
if (action === "harQrCodeScanPay") {
const { mchId, orderId } = paramsObj
router.jump("/pages/pay/scanPay", { action, mchId, orderId })
router.jump("/pages/pay/scan-pay", { action, mchId, orderId })
return
}
// 扫商户url收款码
if (id) {
router.jump("/pages/pay/scanPay", { id, name })
router.jump("/pages/pay/scan-pay", { id, name })
return
}
uni.showToast({ title: "无效的二维码", icon: "none" })
@@ -88,7 +89,7 @@ export function useScanCode(actModalShow) {
}
// 商户扫码支付
if (decodedJson.id) {
router.jump("/pages/pay/scanPay", { id: decodedJson.id })
router.jump("/pages/pay/scan-pay", { id: decodedJson.id })
return
}
// 智能柜扫码开门
+156 -34
View File
@@ -1,4 +1,9 @@
type PermissionId = "location" | "storage"
/**
* location - 位置权限
* storage - 存储权限
* camera - 相机权限
*/
type PermissionId = "location" | "storage" | "camera"
/**
* 权限请求状态
* - none: 未请求
@@ -14,16 +19,8 @@ type RequestStatus = "granted" | "denied" | "prompt"
export const gotoAppPermissionSetting = () => {
const system = uni.getSystemInfoSync()
if (system.platform === "ios") {
// @ts-ignore
const UIApplication = plus.ios.import("UIApplication")
const application2 = UIApplication.sharedApplication()
// @ts-ignore
const NSURL2 = plus.ios.import("NSURL")
const setting2 = NSURL2.URLWithString("app-settings:")
application2.openURL(setting2)
plus.ios.deleteObject(setting2)
plus.ios.deleteObject(NSURL2)
plus.ios.deleteObject(application2)
plus.runtime.openURL("app-settings:")
return
}
if (system.platform == "android") {
const Intent = plus.android.importClass("android.content.Intent")
@@ -45,30 +42,44 @@ export const gotoAppPermissionSetting = () => {
export const requestPermission = (permissionId: PermissionId): Promise<RequestStatus> => {
const system = uni.getSystemInfoSync()
if (system.platform === "android") {
return requestAndroid(permissionId)
return requestAndroidWithGuide(permissionId)
}
if (system.platform === "ios") {
return requestIos(permissionId)
return requestIosWithGuide(permissionId)
}
return Promise.resolve("denied")
}
/******************************** ios相关 ********************************/
const requestIos = (permissionId: PermissionId): Promise<RequestStatus> => {
return new Promise((resolve, reject) => {
switch (permissionId) {
case "location":
resolve(iosLocation())
break
case "storage":
resolve(iosStorage())
break
default:
resolve("denied")
break
const requestIosWithGuide = async (permissionId: PermissionId): Promise<RequestStatus> => {
const status = getIosPermissionStatus(permissionId)
console.log("status:", status, "permissionId:", permissionId)
if (status === "granted") return "granted"
if (status === "prompt") {
const result = await requestIosPermission(permissionId)
if (result === "denied") {
const shouldOpen = await showPermissionDeniedModal(permissionId)
if (shouldOpen) gotoAppPermissionSetting()
}
})
return result
}
const shouldOpen = await showPermissionDeniedModal(permissionId)
if (shouldOpen) gotoAppPermissionSetting()
return "denied"
}
const getIosPermissionStatus = (permissionId: PermissionId): RequestStatus => {
switch (permissionId) {
case "location":
return iosLocation()
case "storage":
return iosStorage()
case "camera":
return iosCamera()
default:
return "denied"
}
}
/**
@@ -101,8 +112,8 @@ const iosLocation = (): RequestStatus => {
const iosStorage = (): RequestStatus => {
let result: RequestStatus = "denied"
// @ts-ignore
const NSPhotoLibraryUsageDescription = plus.ios.import("NSPhotoLibraryUsageDescription")
const status = NSPhotoLibraryUsageDescription.authorizationStatus()
const PHPhotoLibrary = plus.ios.import("PHPhotoLibrary")
const status = PHPhotoLibrary.authorizationStatus()
if (status === 0) {
result = "prompt"
} else if (status === 3 || status === 4) {
@@ -110,17 +121,101 @@ const iosStorage = (): RequestStatus => {
} else {
result = "denied"
}
plus.ios.deleteObject(NSPhotoLibraryUsageDescription)
plus.ios.deleteObject(PHPhotoLibrary)
return result
}
const iosCamera = (): RequestStatus => {
let result: RequestStatus = "denied"
// @ts-ignore
const AVCaptureDevice = plus.ios.import("AVCaptureDevice")
const status = AVCaptureDevice.authorizationStatusForMediaType("vide")
if (status === 0) {
result = "prompt"
} else if (status === 3) {
result = "granted"
} else {
result = "denied"
}
plus.ios.deleteObject(AVCaptureDevice)
return result
}
const requestIosPermission = (permissionId: PermissionId): Promise<RequestStatus> => {
switch (permissionId) {
case "location":
return requestIosLocationPermission()
case "storage":
return requestIosStoragePermission()
case "camera":
return requestIosCameraPermission()
default:
return Promise.resolve("denied")
}
}
const requestIosCameraPermission = (): Promise<RequestStatus> => {
return new Promise((resolve) => {
// @ts-ignore
const AVCaptureDevice = plus.ios.import("AVCaptureDevice")
AVCaptureDevice.requestAccessForMediaTypecompletionHandler("vide", (granted: boolean) => {
plus.ios.deleteObject(AVCaptureDevice)
resolve(granted ? "granted" : "denied")
})
})
}
const requestIosStoragePermission = (): Promise<RequestStatus> => {
return new Promise((resolve) => {
// @ts-ignore
const PHPhotoLibrary = plus.ios.import("PHPhotoLibrary")
PHPhotoLibrary.requestAuthorization((status: number) => {
plus.ios.deleteObject(PHPhotoLibrary)
resolve(status === 3 || status === 4 ? "granted" : "denied")
})
})
}
const requestIosLocationPermission = (): Promise<RequestStatus> => {
return new Promise((resolve) => {
// @ts-ignore
const CLLocationManager = plus.ios.import("CLLocationManager")
const manager = new CLLocationManager()
manager.requestWhenInUseAuthorization()
let attempt = 0
const timer = setInterval(() => {
attempt += 1
const status = CLLocationManager.authorizationStatus()
if (status !== 0 || attempt >= 10) {
clearInterval(timer)
plus.ios.deleteObject(manager)
plus.ios.deleteObject(CLLocationManager)
if (status === 3 || status === 4) {
resolve("granted")
return
}
resolve("denied")
}
}, 300)
})
}
/******************************** andorid相关 ********************************/
/**
* 请求android权限
*/
const requestAndroidWithGuide = async (permissionID: PermissionId): Promise<RequestStatus> => {
const result = await requestAndroid(permissionID)
if (result === "denied") {
const shouldOpen = await showPermissionDeniedModal(permissionID)
if (shouldOpen) gotoAppPermissionSetting()
}
return result
}
const requestAndroid = (permissionID: PermissionId): Promise<RequestStatus> => {
return new Promise((resolve, reject) => {
return new Promise((resolve) => {
let androidPermissionKey = ""
switch (permissionID) {
case "location":
@@ -129,26 +224,26 @@ const requestAndroid = (permissionID: PermissionId): Promise<RequestStatus> => {
case "storage":
androidPermissionKey = "android.permission.WRITE_EXTERNAL_STORAGE"
break
case "camera":
androidPermissionKey = "android.permission.CAMERA"
break
}
if (!androidPermissionKey) {
resolve("denied")
}
plus.android.requestPermissions(
[permissionID],
[androidPermissionKey],
function (resultObj) {
let result: RequestStatus = "denied"
for (let i = 0; i < resultObj.granted.length; i++) {
const grantedPermission = resultObj.granted[i]
console.log("已获取的权限:")
result = "granted"
}
for (let i = 0; i < resultObj.deniedPresent.length; i++) {
const deniedPresentPermission = resultObj.deniedPresent[i]
console.log("拒绝本次申请的权限:")
result = "denied"
}
for (let i = 0; i < resultObj.deniedAlways.length; i++) {
const deniedAlwaysPermission = resultObj.deniedAlways[i]
console.log("永久拒绝申请的权限:")
result = "denied"
}
@@ -161,3 +256,30 @@ const requestAndroid = (permissionID: PermissionId): Promise<RequestStatus> => {
)
})
}
const getPermissionDisplayName = (permissionId: PermissionId) => {
switch (permissionId) {
case "location":
return "定位"
case "storage":
return "相册"
case "camera":
return "相机"
default:
return "相关"
}
}
const showPermissionDeniedModal = (permissionId: PermissionId): Promise<boolean> => {
const permissionName = getPermissionDisplayName(permissionId)
return new Promise((resolve) => {
uni.showModal({
title: `${permissionName}权限未开启`,
content: `请在系统设置中开启${permissionName}权限后再试`,
confirmText: "去设置",
cancelText: "取消",
success: (res) => resolve(!!res.confirm),
fail: () => resolve(false),
})
})
}