feat:商城支付

This commit is contained in:
dzc committed 2026-06-25 18:25:52 +08:00
1 parent 705cd7d47c
commit 8730295312
26 files changed
+1333 -233

No files matched your search

+1 -1
View File
@@ -2,7 +2,7 @@
"version" : "1.0",
"configurations" : [
{
"customPlaygroundType" : "local",
"customPlaygroundType" : "device",
"localRepoPath" : "C:/workcode/vue/biz-mobile-cloud-zax",
"openVueDevtools" : true,
"packageName" : "z",
+183
View File
@@ -0,0 +1,183 @@
# Uni-App AI Editor Role
你是一名专注于 `uni-app` 多端应用开发的高级工程师与 AI 编程助手,负责在当前项目中进行需求理解、页面开发、问题修复、结构优化与文档维护。
## 1. 角色定位
- 精通 `uni-app`、`Vue 3`、`TypeScript`、`Vite`、`Pinia`、`UnoCSS`、`Sass`
- 熟悉 `uni-app` 多端差异,理解 `H5`、`App`、各类小程序端的兼容性约束
- 站在“可维护、可扩展、可交付”的角度完成代码修改
- 默认使用中文输出,并以工程协作风格给出清晰结论
## 2. 工作目标
- 优先完成用户明确提出的需求
- 保持现有业务行为稳定,避免无关改动
- 编写结构清晰、命名明确、便于维护的代码
- 优先使用最简单可靠的方案,不做过度设计
- 所有改动应尽量符合当前项目既有结构与编码习惯
## 3. 项目技术栈
当前项目是一个基于 `uni-app` 的多端应用,核心技术栈如下:
- 框架:`uni-app` + `Vue 3`
- 构建:`Vite 5` + `@dcloudio/vite-plugin-uni`
- 语言:`TypeScript`
- 状态管理:`Pinia`
- 样式:`UnoCSS` + `Sass`
- 请求层:`luch-request`
- 国际化:`vue-i18n`
- 图表:`@qiun/uni-ucharts`
- UI 组件:`tm-ui`、`z-paging`
当前项目的主要目标端为:
- `App`
- `H5`
因此在分析、设计、编码、修复和优化时,应默认优先保障这两个端的兼容性、交互一致性和运行稳定性。
## 4. 项目结构认知
请在理解和修改代码时遵循以下目录职责:
- `src/pages`:业务页面
- `src/components`:通用业务组件
- `src/sheep/api`:接口封装层
- `src/sheep/components`:项目级公共组件
- `src/sheep/hooks`:组合式逻辑复用
- `src/sheep/stores`:Pinia 状态管理
- `src/sheep/utils`:工具函数与基础能力
- `src/sheep/constant`:常量定义
- `src/pages.json`:页面路由、页面样式、分包与 easycom 配置
- `src/manifest.json`:应用平台能力与打包配置
- `vite.config.ts`:Vite 与 uni 构建配置
- `unocss.config.ts`:原子化样式配置
## 5. 项目约定
### 5.1 别名约定
- `@` 指向 `src`
- `@@` 指向 `src/sheep`
### 5.2 组件约定
项目启用了 `easycom`,常见规则如下:
- `base-*` 对应 `src/sheep/components/base`
- `crud-*` 对应 `src/sheep/components/crud`
- `tm-*` 对应 `src/uni_modules/tm-ui/components`
- `z-paging*` 使用 `z-paging` 组件约定
### 5.3 多端约定
- 涉及平台差异时,优先使用 `#ifdef`、`#ifndef` 等 uni 条件编译
- 不要把仅某一端可用的 API 直接写成全端通用逻辑
- 涉及原生能力时,必须明确适用平台和降级策略
- 默认优先适配 `App` 与 `H5`,其他端仅在用户明确要求时再做专项兼容
## 6. 编码原则
- 优先复用现有模块、组件、hooks、stores,不重复造轮子
- 优先保持接口调用风格与项目现有 `api` 层一致
- 优先保持状态管理风格与现有 `Pinia store` 一致
- 保持函数短小、职责单一
- 命名应语义化,避免随意缩写
- 新增逻辑要考虑异常处理、空值处理和边界条件
- 对用户输入、上传、跳转、支付、权限等敏感流程要有安全意识
- 仅在必要时添加注释,注释应解释“为什么”,而不是“这行代码做了什么”
## 7. 页面开发规范
- 新页面优先放在 `src/pages` 的对应业务域目录下
- 页面标题、导航栏配置、是否禁用滚动等信息,优先在 `pages.json` 中维护
- 页面应按“数据、状态、行为、视图”分层组织
- 复杂页面应适当拆分为局部组件或组合式函数
- 表单页要注意校验、提交态、重复提交保护和错误提示
- 列表页要注意分页、空态、加载态、刷新态
## 8. 样式规范
- 优先使用 `UnoCSS`
- 复杂样式或组件局部样式可使用 `scss`
- 保持样式语义清晰,避免无意义的层级嵌套
- 兼顾移动端适配,优先考虑 `rpx` 与 uni 场景下的表现
- 优先保证 `App` 与 `H5` 的视觉一致性
- 避免仅在单一端可用、但在 `App` 或 `H5` 表现异常的 CSS 特性
## 9. 请求与数据流规范
- 接口定义优先放在 `src/sheep/api`
- 组件或页面中不要直接散落大量请求实现细节
- 共享状态放入 `Pinia`
- 页面私有状态优先使用页面本地状态
- 请求失败必须给出合理反馈,避免静默失败
## 10. 修改代码前的思考顺序
每次开始工作前,按以下顺序思考:
1. 用户要解决的真实问题是什么
2. 现有项目中是否已有相似实现可以复用
3. 这次改动应该落在哪一层:页面、组件、hooks、store、api、utils、配置
4. 是否涉及多端差异、权限、登录态、路由、支付、上传等敏感流程
5. 是否需要同步修改 `pages.json`、`manifest.json`、`vite.config.ts` 或类型声明
## 11. 操作边界
- 非用户明确要求时,不主动大规模重构
- 非必要时,不修改已有稳定业务逻辑
- 非必要时,不修改第三方库代码
- 非必要时,不引入新的重型依赖
- 不要为了“代码更优雅”而改变现有页面交互
- 未经用户要求,不默认运行某个平台的打包命令
- 若仅需提供方案或文档,优先输出文档而不是直接大改代码
## 12. 修改 `uni-app` 项目时的特别注意事项
- 关注 `pages.json` 中的页面路径、分包配置与 `tabBar` 配置
- 注意 `uni_modules` 组件的兼容性与版本行为
- 涉及 `tm-ui`、`z-paging` 等组件时,优先沿用现有项目写法
- 注意 `App.vue`、全局样式、全局 store 对页面行为的影响
- 涉及路由、登录态、商户、支付、地址、订单等核心链路时,先理解现有数据流再修改
- 涉及 `vite.config.ts` 时,优先使用对 `uni-app` 友好的保守配置
- 任何页面、组件或样式调整,都要优先检查 `App` 与 `H5` 是否会出现布局、滚动、事件、导航或原生能力差异
## 13. 输出要求
在向用户汇报时:
- 使用中文
- 先给结果,再说明原因
- 说明改了哪些文件
- 如果存在风险、前提条件或待确认项,要明确指出
- 如果没有真正修改代码,不要假装已经完成实现
## 14. 默认执行策略
当用户提出需求时,默认采用以下策略:
1. 先理解问题并定位应修改的层级
2. 优先读取相关页面、组件、store、api、配置文件
3. 在最小改动范围内完成需求
4. 保持与当前项目风格一致
5. 交付后说明改动点、影响范围和后续建议
## 15. 适用场景
该角色适用于以下任务:
- `uni-app` 页面开发
- `uni-app` 组件封装
- `Pinia store` 设计与修复
- 接口调用与数据流整理
- `pages.json`、`manifest.json`、`vite.config.ts` 配置调整
- 样式优化与移动端适配
- 多端兼容问题排查
- 构建报错、运行报错、路由问题、权限问题修复
## 16. 一句话工作准则
在这个项目中,始终以 `uni-app` 多端兼容、最小改动、结构清晰、业务稳定 为最高优先级完成任务。
+22 -6
View File
@@ -3,7 +3,6 @@ import { adApi, userApi } from "@@/api"
import { storageConstant } from "@@/constant"
import pinia from "@@/stores/index"
import { useRiderPushBannerStore } from "@@/stores/rider-push-banner"
import { useTabbarStore } from "@@/stores/tabbar"
import { globalWebSocket } from "@/sheep/utils/global-websocket"
import { handleCanteenDeliveryWebSocketFrame } from "@/sheep/utils/canteen-delivery-ws-push"
@@ -82,12 +81,19 @@ export default {
return userData
},
async _initTabbar() {
const tabbarStore = useTabbarStore(pinia)
tabbarStore.init()
const tabbarStore = await this._getTabbarStore()
tabbarStore?.init()
},
_syncTabbar() {
const tabbarStore = useTabbarStore(pinia)
tabbarStore.syncByCurrentRoute()
async _syncTabbar() {
const tabbarStore = await this._getTabbarStore()
tabbarStore?.syncByCurrentRoute()
},
async _getTabbarStore() {
// #ifdef APP-NVUE
return null
// #endif
const { useTabbarStore } = await import("@@/stores/tabbar")
return useTabbarStore(pinia)
},
async reportSplashAd() {
try {
@@ -136,6 +142,16 @@ $theme-gray: #999;
padding: 8rpx 15rpx;
}
.primary_btn {
border: 1px solid white;
background: $theme-blue;
color: white;
font-weight: 500;
font-size: 28rpx;
border-radius: 10rpx;
padding: 8rpx 15rpx;
}
.prefix_title {
font-size: 32rpx;
font-weight: 500;
+2 -2
View File
@@ -2,8 +2,8 @@
"name" : "资安信",
"appid" : "__UNI__1BFE88F",
"description" : "资安信后勤管理系统",
"versionName" : "1.5.02",
"versionCode" : 1502,
"versionName" : "1.4.02",
"versionCode" : 1402,
"transformPx" : false,
/* 5+App特有相关 */
"app-plus" : {
+1 -1
View File
@@ -2,7 +2,7 @@
import { reactive, computed, ref } from "vue"
import { adApi } from "@@/api/index"
import { hooks, router } from "@@/index"
import { hooks } from "@@/index"
const data = {
}
+3 -5
View File
@@ -1,9 +1,7 @@
<script setup lang="ts">
import { ref, nextTick } from "vue"
import { onShow, onHide, onBackPress } from "@dcloudio/uni-app"
import { router } from "@@/index"
const adPageRef = ref()
onShow(() => {
@@ -17,7 +15,7 @@
})
onBackPress(() => {
router.jump("/pages/tabbar/my")
uni.switchTab({ url: "/pages/tabbar/my" })
return true
})
@@ -32,4 +30,4 @@
<template>
<ad-content-page class="size-full" ref="adPageRef" adpid="1696053352" @load="onadload" @error="onaderror" />
</template>
</template>
+2 -4
View File
@@ -2,8 +2,6 @@
import { ref, nextTick } from "vue"
import { onShow, onHide, onBackPress } from "@dcloudio/uni-app"
import { router } from "@@/index"
const adPageRef = ref()
onShow(() => {
@@ -17,7 +15,7 @@
})
onBackPress(() => {
router.jump("/pages/tabbar/my")
uni.switchTab({ url: "/pages/tabbar/my" })
return true
})
@@ -34,4 +32,4 @@
<view class="full">
<ad-content-page class="size-full" ref="adPageRef" adpid="1696053352" @load="onadload" @error="onaderror" />
</view>
</template>
</template>
+171 -38
View File
@@ -1,49 +1,182 @@
<script setup lang="ts">
import { computed, reactive } from "vue"
import { onLoad } from "@dcloudio/uni-app"
import { hooks, router } from "@@/index"
import { orderApi } from "@@/api"
import AppLottiePlayer from "@@/components/common/app-lottie-player.vue"
import InformationStreamAd from "@@/components/ad/information-stream-ad.vue"
import loadingAnimation from "@/static/lottie-json/loading.json"
import successAnimation from "@/static/lottie-json/success.json"
import errorAnimation from "@/static/lottie-json/fail.json"
type ResultPhase = "loading" | "success" | "error"
const POLL_INTERVAL_MS = 1000
const POLL_TIMEOUT_MS = 10000
let orderPollToken = 0
// backBtnType
// 'back' 回退订单列表
// 'detail' 查看订单详情
const state = reactive<any>({
phase: "loading" as ResultPhase,
orderId: "",
backBtnType: 'back',
orderDetail: {},
failMessage: "",
})
const titleText = computed(() => {
if (state.phase === "loading") return "充值结果确认中"
return state.phase === "success" ? "充值成功" : "充值失败"
})
const descText = computed(() => {
if (state.phase === "loading") return "正在查询订单状态,请稍候..."
if (state.phase === "success") return "充值金额已到账,可前往钱包查看最新余额"
return state.failMessage || "未查询到本次充值结果,请稍后在充值记录中确认"
})
const currentAnimation = computed(() => {
if (state.phase === "loading") return loadingAnimation
return state.phase === "success" ? successAnimation : errorAnimation
})
const currentLoop = computed(() => state.phase === "loading")
const currentAnimationSize = computed(() => {
if (state.phase === "loading") {
return { width: "140rpx", height: "140rpx" }
}
if (state.phase === "success") {
return { width: "280rpx", height: "280rpx" }
}
return { width: "280rpx", height: "280rpx" }
})
const payOrderAmount = computed(() => Number(state.orderDetail?.payPrice || 0))
const mchNameText = computed(() => state.orderDetail?.merchantName || "--")
const payTypeText = computed(() => {
const text = hooks.useEnum('hardwarePayTypeEnum', state.orderDetail?.payType)
return text ? `${text}支付` : "--"
})
const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms))
const isPaySuccess = (orderStatus?: string | number) => ["5", "200"].includes(String(orderStatus ?? ""))
// 查询订单详情
const getOrder = async (polling = false) => {
const currentToken = ++orderPollToken
try {
state.phase = "loading"
state.failMessage = ""
const startTime = Date.now()
while (true) {
const res: any = await orderApi.detail({ id: state.orderId })
if (currentToken !== orderPollToken) return
const orderDetail = res?.data
state.orderDetail = orderDetail || {}
if (!orderDetail) {
state.failMessage = "未查询到本次订单"
state.phase = "error"
return
}
if (isPaySuccess(orderDetail.orderStatus)) {
state.phase = "success"
return
}
if (!polling || Date.now() - startTime >= POLL_TIMEOUT_MS) {
state.phase = "error"
return
}
await sleep(POLL_INTERVAL_MS)
if (currentToken !== orderPollToken) return
}
} catch (error) {
if (currentToken !== orderPollToken) return
console.error("查询订单详情意外的错误:", error)
state.failMessage = "查询订单详情失败"
state.phase = "error"
}
}
onLoad((options: any) => {
if (options.orderId) {
state.orderId = options.orderId || ""
getOrder(true)
} else {
uni.showToast({ title: "参数错误", icon: "none" })
router.back()
}
if (options.backBtnType) {
state.backBtnType = options.backBtnType
}
})
</script>
<template>
<crud-layout title="支付成功" bg="/static/page/blue-bg.png">
<view class="flex-col-center">
<view class="flex-col-center m-y-50rpx">
<crud-layout title="充值结果" bg="/static/page/blue-bg.png">
<view class="h-30rpx" />
<view class="bg-white rounded-24rpx shadow-main px-30rpx pt-10rpx pb-24rpx">
<view class="flex-col-center pt-10rpx pb-20rpx h-500rpx">
<view class="flex-row-center size-280rpx">
<AppLottiePlayer :animationData="successAnimation" width="280rpx" height="280rpx" :loop="false" />
<AppLottiePlayer :animationData="currentAnimation" :width="currentAnimationSize.width"
:height="currentAnimationSize.height" :loop="currentLoop" />
</view>
<view class="text-38rpx font-700 color-#222">{{ titleText }}</view>
<view class="mt-10rpx text-24rpx color-#8a94a6 text-center">{{ descText }}</view>
<view class="text-58rpx font-700 color-#222">¥{{ hooks.useMoney(payOrderAmount) }}
</view>
<text class="text-36rpx mt-24rpx font-bold c-#333333!">支付成功</text>
</view>
<view class="flex-col-center m-b-60rpx">
<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-30rpx c-#333333!">商家</text>
<text class="sub-text-30rpx">{{ scanPayData.mchName }}</text>
<view class="pt-10rpx">
<view class="flex items-center justify-between py-16rpx">
<text class="text-26rpx color-#8a94a6">商户</text>
<text class="text-28rpx color-#333 font-500 max-w-380rpx text-right">{{ mchNameText }}</text>
</view>
<view class="flex-row justify-between m-15rpx">
<text class="sub-text-30rpx c-#333333!">支付方式</text>
<text class="sub-text-30rpx">扫一扫</text>
<view class="flex items-center justify-between py-16rpx">
<text class="text-26rpx color-#8a94a6">充值方式</text>
<text class="text-28rpx color-#333 font-500 max-w-380rpx text-right">
{{ payTypeText }}
</text>
</view>
<view class="flex items-center justify-between py-16rpx">
<text class="text-26rpx color-#8a94a6">订单状态</text>
<text class="text-28rpx font-500 max-w-380rpx text-right"
:class="state.phase === 'success' ? 'color-#1dbb84' : state.phase === 'error' ? 'color-#ff5a5f' : 'color-#0094ff'">
{{ state.phase === "loading" ? "确认中" : state.phase === "success" ? "支付成功" : "支付失败" }}
</text>
</view>
</view>
</view>
<view class="h-100rpx" />
<tm-button :block="true" class="w-full p-40rpx" @tap="goBack">完成</tm-button>
<view v-if="state.phase !== 'loading'"
class="fixed left-30rpx right-30rpx bottom-30rpx z-20 flex flex-col items-center gap-20rpx">
<!-- 广告 -->
<information-stream-ad class="w-full" />
<slot v-if="state.phase === 'success'">
<view v-if="state.backBtnType === 'back'"
class="w-full h-88rpx rounded-full flex items-center justify-center text-30rpx font-700 color-white! bg-gradient-to-r from-#0094ff to-#36b4ff shadow-[0_16rpx_32rpx_rgba(0,148,255,0.18)]"
@tap="router.jump('/pages/tabbar/order', {}, 2)">
返回
</view>
<view v-else
class="w-full h-88rpx rounded-full flex items-center justify-center text-30rpx font-700 color-white! bg-gradient-to-r from-#0094ff to-#36b4ff shadow-[0_16rpx_32rpx_rgba(0,148,255,0.18)]"
@tap="router.jump('/pages/order/detail', { id: state.orderId }, 2)">
查看订单详情
</view>
</slot>
<view v-else
class="w-full h-88rpx rounded-full flex items-center justify-center text-30rpx font-700 color-white! bg-gradient-to-r from-#0094ff to-#36b4ff shadow-[0_16rpx_32rpx_rgba(0,148,255,0.18)]"
@tap="getOrder(true)">
重新查询
</view>
</view>
</crud-layout>
</template>
<script setup lang="ts">
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(() => {
if (!usePayStore().scanPayData.mchId) return uni.showToast({ title: "支付信息不存在", icon: "none" })
scanPayData.value = usePayStore().scanPayData
})
const goBack = () => {
uni.navigateBack()
}
</script>
+6 -1
View File
@@ -9,6 +9,7 @@ import { orderApi, refundApi, commonApi } from "@@/api"
import { filePath } from "@@/utils/file"
import OrderSimpleCarList from "@@/components/order/order-simple-car-list.vue"
import SelectMapAddress from "@@/components/address/select-map-address.vue"
const data = {
payType: [
@@ -136,9 +137,13 @@ const doSubmit = async () => {
<view class="h-30rpx" />
<order-simple-car-list :mchName="state.detail.merchantName" :carList="state.shopCarList"
:takeOutSetting="null" />
<slot>
<view class="h-20rpx" />
<select-map-address :canSelect="false" />
</slot>
<view class="h-20rpx" />
<view class="card p-x-20rpx p-y-30rpx m-b-20rpx">
<view class="sub-text-28">售后类型</view>
<view class="sub-text-24">售后类型</view>
<view class="m-t-20rpx">
<tm-radio-group @change="radioChange" direction="column" gap="20" v-model="applyForm.way">
<tm-radio :disabled="v.disabled" v-for="(v, i) in data.payType" :key="i" v-show="v.visible"
+51 -11
View File
@@ -1,11 +1,13 @@
<template>
<crud-layout title="结算" mainIsScroll bg="/static/page/blue-bg.png">
<view class="h-20rpx" />
<select-map-address />
<select-map-address @selectAddress="selectAddress" />
<view class="h-20rpx" />
<order-simple-car-list :take-out-setting="null" :mch-name="'未知商户'" :car-list="cartList" />
<order-simple-car-list :take-out-setting="null" :mch-name="state.mchDetail?.name ?? '未知商户'"
:car-list="cartList" />
<view class="h-20rpx" />
<pay-card />
<pay-card :visiblePayBtn="false" @selectPayType="selectPayType" />
<view class="h-40rpx" />
<template #main-bottom>
<view class="flex justify-end items-center p-32rpx">
<view class="flex-col mr-32rpx">
@@ -21,33 +23,71 @@
<script setup lang="ts">
import { computed, reactive, ref } from "vue"
import { hooks } from "@/sheep"
import useCartBuy from "./use-cart-buy"
import { onLoad } from "@dcloudio/uni-app"
import { walletApi } from "@/sheep/api"
import { hooks, router } from "@/sheep"
import { AggregationPayTool } from "@@/utils/pay-tool"
import { merchantApi } from "@@/api"
import SelectMapAddress from "@@/components/address/select-map-address.vue"
import OrderSimpleCarList from "@@/components/order/order-simple-car-list.vue"
import PayCard from "@@/components/pay/pay-card.vue"
const { cartList, totPrice, totalCount, getShopCart, doBuy } = useCartBuy()
import useCartBuy from "./use-cart-buy"
const { cartList, totPrice, totalCount, getShopCart, getCarParentId } = useCartBuy()
const state = reactive<any>({
mchId: "",
showDrawer: false,
curPickupId: "",
mchDetail: {},
mapAddressId: '',
payType: '',
walletTypeId: '',
})
onLoad((option: any) => {
const { mchId } = option
if (!mchId) return uni.showToast({ title: "参数错误", icon: "error" })
state.mchId = option.mchId
getMchDetail()
getShopCart(mchId)
})
// 获取商户详情
const getMchDetail = async () => {
try {
const { code, data } = await merchantApi.detail({ id: state.mchId })
if (code !== 0) throw new Error(data?.msg ?? "获取商户详情失败")
state.mchDetail = data
} catch (error) {
console.log("获取商户详情,意外的错误:", error);
}
}
// 选择支付方式
const selectPayType = (type: string, typeValue: any) => {
state.payType = type
state.walletTypeId = typeValue?.extra?.walletTypeId ?? ''
}
const submitOrder = () => {
doBuy(null)
// 选择地址
const selectAddress = (addressId: string, address: any) => {
state.mapAddressId = addressId
}
const submitOrder = async () => {
if (!state.mapAddressId) return uni.showToast({ title: "请选择地址", icon: "none" })
if (!state.payType) return uni.showToast({ title: "请选择支付方式", icon: "none" })
const res = await AggregationPayTool.shopPay({
parentCarId: getCarParentId.value,
mapAddressId: state.mapAddressId,
payType: state.payType,
walletTypeId: state.walletTypeId,
})
if (res?.orderId) {
router.jump("/pages/pay/pay-complete", {
orderId: res?.orderId,
})
}
}
</script>
+6 -33
View File
@@ -45,42 +45,15 @@ export default function useCartBuy() {
await cartStore.removeProductCount(productId, merchantId)
}
// 获取购物车商品的parentCarId
const getCarParentId = computed(() => {
return cartList.value[0].parentCarId
})
const goSettlement = (mchId: string) => {
uni.navigateTo({ url: `/pages/prosperity-shop/settlement?mchId=${mchId}` })
}
// 点击提交订单
const doBuy = async (pickUpId: string) => {
if (isEmpty(cartList.value)) return uni.showToast({ title: "请至少购买一件商品", icon: "none" })
uni.showModal({
title: "提示",
content: "是否确定购买",
confirmColor: "#0094ff",
success: async (res) => {
if (!res.confirm) return
submitPay(pickUpId)
},
})
}
// 确认提交订单
const submitPay = async (pickUpId: string) => {
try {
uni.showLoading({ title: "支付中...", mask: false })
const { code, msg }: any = await orderApi.appCreateFarmerReserveOrder({
carParentId: cartList.value[0].parentCarId,
pickUpId,
})
if (code != 0) throw new Error(msg)
uni.showToast({ title: "支付成功", icon: "none" })
router.jump("/pages/tabbar/order")
} catch (error: any) {
uni.showToast({ title: error.message, icon: "none" })
} finally {
uni.hideLoading()
}
}
return {
totPrice,
totalCount,
@@ -90,6 +63,6 @@ export default function useCartBuy() {
addProductCount,
removeProductCount,
goSettlement,
doBuy,
getCarParentId,
}
}
+19 -25
View File
@@ -7,22 +7,25 @@ import { orderApi } from "@@/api"
import zTabs from "@@/components/other/z-tabs/z-tabs.vue"
import AppHotUpdate from "@/sheep/components/app-version/app-hot-update.vue"
import { getLaunchParam } from "@/sheep/utils/app"
import orderCar from "@/sheep/components/order/order-car.vue"
import OrderCar from "@/sheep/components/order/order-car.vue"
import OrderConfirmReceipt from "@/sheep/components/order/order-confirm-receipt.vue"
const appHotUpdateRef = ref<any>(null)
const orderConfirmReceiptRef = ref<any>(null)
const data = {
tabs: [
{ id: null, name: "全部", disabled: false },
{ id: 1, name: "未支付", disabled: false },
{ id: 5, name: "已支付", disabled: false },
{ id: 10, name: "售后", disabled: false },
{ id: 20, name: "已核销", disabled: false },
{ pa: { orderStatusCodes: null }, name: "全部", disabled: false },
{ pa: { orderStatusCodes: [200] }, name: "待发货", disabled: false },
{ pa: { orderStatusCodes: [5, 210] }, name: "待收货/待使用", disabled: false },
{ pa: { orderStatusCodes: [20, 220] }, name: "已核销/已收货", disabled: false },
{ pa: { orderStatusCodes: [10] }, name: "售后", disabled: false },
{ pa: { orderStatusCodes: [1] }, name: "待支付", disabled: false },
{ pa: { orderStatusCodes: [30] }, name: "已关闭", disabled: false },
],
}
const state = reactive<any>({
pa: {
orderStatus: data.tabs[0].id,
...data.tabs[0].pa,
// 售后状态筛选使用逗号拼接后的字符串,便于直接透传给分页接口。
refundStatusCodes: [],
},
@@ -74,9 +77,9 @@ onShow(async () => {
})
const doTab = (e: any) => {
const target = data.tabs[e]
state.pa.orderStatus = target.id
state.pa = target.pa
// 只有“售后”tab 才需要售后状态筛选,切走时清空,避免影响其它状态查询。
if (target.id !== 10) {
if (!target.pa.orderStatusCodes?.includes(10)) {
state.activeRefundStatusList = []
state.pa.refundStatusCodes = []
}
@@ -86,17 +89,7 @@ const doTab = (e: any) => {
const pageRef = ref<any>(null)
const pageHook = hooks.usePage(
pageRef,
getLaunchParam() == "canteen" // 食堂
? orderApi.caneenPage
: getLaunchParam() == "selfMerchant"
? orderApi.supPage
: getLaunchParam() == "coopMerchant"
? orderApi.supPage
: getLaunchParam() == "onlineShop"
? orderApi.supPage
: getLaunchParam() == "prosperityShop"
? orderApi.farmerSpuPage
: async () => [],
orderApi.page,
null,
"data.records",
)
@@ -115,8 +108,8 @@ const query = (page: number, pagesize: number) => {
:auto-clean-list-when-reload="false" :auto-scroll-to-top-when-reload="false" @scroll="pageHook.scroll">
<template #top>
<view class="bg-white rounded-16rpx px-20rpx my-20rpx">
<z-tabs :list="data.tabs" @change="doTab" />
<view v-if="state.pa.orderStatus == 10"
<z-tabs :list="data.tabs" :scroll-count="4" @change="doTab" />
<view v-if="state.pa.orderStatusCodes?.includes(10)"
class="flex flex-items-center justify-between py-10rpx second-text-24 font-400!">
<text>可筛选售后状态</text>
<view class="flex-row-center flex-wrap-center">
@@ -133,10 +126,11 @@ const query = (page: number, pagesize: number) => {
</template>
<view class="card rounded-24rpx bg-white p-20rpx pb-0rpx mb-20rpx" v-for="(v, index) in pageHook.list"
:key="v.id">
<order-car :cardetail="v" :footerOperate="['refund', 'qrCodeDetail']" />
<order-car :cardetail="v" :footerOperate="['refund', 'qrCodeDetail', 'expressTracking']"
@confirmReceipt="orderConfirmReceiptRef.open(v.id, v.carList)" />
</view>
</z-paging>
<order-confirm-receipt ref="orderConfirmReceiptRef" @refresh="query" />
<!-- #ifdef APP -->
<app-hot-update ref="appHotUpdateRef" :autoPopup="true" />
<!-- #endif -->
+1 -1
View File
@@ -1,5 +1,5 @@
<template>
<crud-layout bg="/static/page/blue-bg.png">
<crud-layout title="商城" bg="/static/page/blue-bg.png">
<z-paging ref="pageRef" height="100%" :fixed="false" v-model="pageHook.list" :default-page-size="10"
safe-area-inset-bottom :loading-more-enabled="true" :show-scrollbar="false" @query="query"
:auto-clean-list-when-reload="false" :auto-scroll-to-top-when-reload="false" @scroll="pageHook.scroll">
+19 -6
View File
@@ -22,6 +22,13 @@ export default {
params,
}),
// 食堂订单
page: (params: any) =>
request({
url: `/hard/hard-order/app/page-join`,
method: "GET",
params,
}),
// 食堂订单
caneenPage: (params: any) =>
request({
url: `/hard/hard-order/app/caneen-page-join`,
@@ -126,15 +133,21 @@ export default {
data,
}),
/**
* 创建共富商城预约订单
* @body body.carParentId 购物车id
* @body body.walletTypeId 钱包类型id
* @body body.pickUpId 自提点id
* 创建商城预约订单
*/
appCreateFarmerReserveOrder: (data: any) =>
appCreateShopReserveOrder: (data: any) =>
request({
url: "/hard/hard-order/app/create-farmer-order",
url: "/hard/hard-order/app/create-shop-order",
method: "POST",
data,
}),
/**
* 确认收货
* @param orderId 订单id
*/
confirmReceipt: (orderId: string) =>
request({
url: `/hard/hard-order/app/confirm-receipt/${orderId}`,
method: "PUT",
}),
}
@@ -16,7 +16,7 @@
<text>{{ state.defaultAddress.inputAddress }}</text>
</view>
</view>
<view class="iconfont icon-youjiantou second-text-20!" />
<view v-if="canSelect" class="iconfont icon-youjiantou second-text-20!" />
</view>
<view class="flex-row items-center second-text-22 gap-8rpx">
<text>{{ state.defaultAddress.username }}</text>
@@ -51,34 +51,37 @@
:default-page-size="10" safe-area-inset-bottom :loading-more-enabled="true" @query="query"
:auto-clean-list-when-reload="false" :auto-scroll-to-top-when-reload="false" @scroll="pageHook.scroll"
:show-loading-more-no-more-view="false">
<view class="flex items-center p-x-30rpx p-y-20rpx m-b-30rpx" v-for="v in pageHook.list" :key="v.id">
<view class="flex-1 flex-row items-center justify-between gap-4rpx" @tap="doSelectAppt(v)">
<tm-radio v-model="state.selectedAddressId" :value="v.id" />
<view class="flex-col justify-items-start flex-1 gap-16rpx">
<view class="flex-row gap-8rpx">
<text
class="rounded-8rpx border border-[#d1e9ff] bg-[#eff8ff] px-18rpx py-12rpx text-20rpx line-height-1 text-[#1570ef]">
{{ hooks.useEnum('mapAddressTag', v.tag) }}
</text>
<view class=" flex-row-center sub-text-26 gap-4rpx">
<text>{{ v.adressDetail }}</text>
<text>{{ v.inputAddress }}</text>
<view class="flex-col" v-for="v in pageHook.list" :key="v.id">
<view class="flex items-center p-x-30rpx p-y-20rpx">
<view class="flex-1 flex-row items-center justify-between gap-4rpx" @tap="doSelectAppt(v)">
<tm-radio v-model="state.selectedAddressId" :value="v.id" />
<view class="flex-col justify-items-start flex-1 gap-16rpx">
<view class="flex-row gap-8rpx">
<text
class="rounded-8rpx border border-[#d1e9ff] bg-[#eff8ff] px-18rpx py-12rpx text-20rpx line-height-1 text-[#1570ef]">
{{ hooks.useEnum('mapAddressTag', v.tag) }}
</text>
<view class=" flex-row-center sub-text-26 gap-4rpx">
<text>{{ v.adressDetail }}</text>
<text>{{ v.inputAddress }}</text>
</view>
</view>
<view class="flex-row items-center second-text-22 gap-8rpx">
<text>{{ v.username }}</text>
<text>{{ v.userMoblie }}</text>
<text
class="rounded-8rpx border border-[#d1e9ff] bg-[#eff8ff] px-18rpx py-12rpx text-20rpx line-height-1 text-[#1570ef]"
v-if="v.isDefault == '1'">
默认
</text>
</view>
</view>
<view class="flex-row items-center second-text-22 gap-8rpx">
<text>{{ v.username }}</text>
<text>{{ v.userMoblie }}</text>
<text
class="rounded-8rpx border border-[#d1e9ff] bg-[#eff8ff] px-18rpx py-12rpx text-20rpx line-height-1 text-[#1570ef]"
v-if="v.isDefault == '1'">
默认
</text>
</view>
</view>
<view>
<text class="iconfont icon-bianji sub-text-36!"></text>
</view>
</view>
<view>
<text class="iconfont icon-bianji sub-text-36!"></text>
</view>
<view class="address-border" />
</view>
<template #bottom>
<view class="w-full my-20rpx">
@@ -99,6 +102,16 @@ import { mapAddressApi } from "@@/api"
const emit = defineEmits(["selectAddress"])
export type Props = {
// 是否可以选择地址
canSelect?: boolean
}
const props = withDefaults(defineProps<Props>(), {
canSelect: true,
})
const state = reactive<any>({
visibleSelectAddress: false,
selectedAddressId: "",
@@ -123,12 +136,14 @@ const getDefaultAddress = async () => {
if (code != 0) throw new Error()
state.defaultAddress = data
state.selectedAddressId = data.id
emit("selectAddress", data.id, data)
} catch (error) {
console.log("获取默认地址意外的错误", error)
}
}
const showSelectAddress = () => {
if (!props.canSelect) return
state.visibleSelectAddress = true
}
@@ -144,3 +159,10 @@ const addAddress = () => {
router.jump('/pages/user/map-address/index')
}
</script>
<style scoped lang="scss">
.address-border {
border: 1rpx solid #f8f8f8;
margin: 10rpx 0;
}
</style>
+58 -27
View File
@@ -8,8 +8,11 @@ import {
} from "@@/utils/user-role"
import { filePath } from "@@/utils/file"
import { openExpressTracking } from "@@/utils/open-foreign-app"
const emit = defineEmits(['cancel'])
import OrderStatusTag from "./order-status-tag.vue"
const emit = defineEmits(['cancel', 'confirmReceipt'])
export type Props = {
// 订单详情
@@ -27,7 +30,7 @@ export type Props = {
// 底部操作区域控制。
// 传 true 时,等价于 ['refund', 'qrCodeDetail'],表示两个按钮都允许显示。
// 传数组时,可按需精确控制具体显示哪些按钮。
footerOperate?: boolean | Array<'refund' | 'qrCodeDetail' | 'cancel'>
footerOperate?: boolean | Array<'refund' | 'qrCodeDetail' | 'cancel' | 'expressTracking'>
}
const props = withDefaults(defineProps<Props>(), {
@@ -61,7 +64,7 @@ const toggleGoodsCollapse = (order: any, index: number) => {
// `footerOperate === true` 时,视为开启全部按钮;
// 未传或传 false 时,不显示任何底部操作按钮;
// 传数组时,仅显示数组中包含的操作项。
const hasFooterOperate = (operate: 'refund' | 'qrCodeDetail' | 'cancel') => {
const hasFooterOperate = (operate: 'refund' | 'qrCodeDetail' | 'cancel' | 'expressTracking') => {
if (props.footerOperate === true) return true
if (!Array.isArray(props.footerOperate)) return false
return props.footerOperate.includes(operate)
@@ -69,10 +72,10 @@ const hasFooterOperate = (operate: 'refund' | 'qrCodeDetail' | 'cancel') => {
// 退款按钮显示条件:
// 1. footerOperate 允许 refund;
// 2. 订单状态为 5;
// 3. 订单类型为 2。
// 2. 订单状态为 5、210
// 3. 订单类型为 2、7
const showRefundButton = (order: any) => {
return hasFooterOperate('refund') && String(order?.orderStatus) === '5' && String(order?.orderType) === '2'
return hasFooterOperate('refund') && ['5', '210'].includes(String(order?.orderStatus)) && ['2', '7'].includes(String(order?.orderType))
}
// 显示订单详情按钮条件:
@@ -114,6 +117,20 @@ const showCancelButton = (order: any) => {
)
}
// 手动确认收货按钮显示条件:
// 1. footerOperate允许expressTracking;
// 2. 订单状态为,210
// 3. 订单类型为,7
const showExpressTrackingButton = (order: any) => {
return (
hasFooterOperate('expressTracking') &&
String(order?.orderStatus) === '210' &&
['7'].includes(String(order?.orderType))
)
}
// 查看订单和核销码
const doToDetail = (v: any) => {
router.jump("/pages/order/detail", { id: v.id })
@@ -124,6 +141,16 @@ const doRefund = (v: any) => {
router.jump("/pages/pay/refund-apply", { id: v.id })
}
// 物流查询
const doShipment = (v: any) => {
openExpressTracking(v.shipmentNo)
}
// 确认收货
const doExpressTracking = (v: any) => {
emit('confirmReceipt', v)
}
const doCancel = (v: any) => {
emit('cancel', v)
}
@@ -149,16 +176,9 @@ const doCancel = (v: any) => {
</view>
</view>
<view class="flex-shrink-0">
<tm-tag v-if="props.cardetail.orderStatus == 1" color="info" size="xs">未支付</tm-tag>
<tm-tag v-if="props.cardetail.orderStatus == 1" color="info" size="xs">待支付</tm-tag>
<view class="w-100rpx flex-row-center">
<slot v-if="props.cardetail.orderStatus == 5">
<tm-tag v-if="props.cardetail.orderType == 2" color="success" size="xs">已预约</tm-tag>
<tm-tag v-else color="success" size="xs">已支付</tm-tag>
</slot>
<tm-tag v-if="props.cardetail.orderStatus == 10" color="error" size="xs">售后</tm-tag>
<tm-tag v-if="props.cardetail.orderStatus == 20" size="xs">已核销</tm-tag>
<tm-tag v-if="props.cardetail.orderStatus == 30" color="info" size="xs">已取消(智能柜)</tm-tag>
<tm-tag v-if="props.cardetail.orderStatus == 110" color="success" size="xs">配送中</tm-tag>
<order-status-tag :cardetail="props.cardetail" />
</view>
</view>
</view>
@@ -172,15 +192,7 @@ const doCancel = (v: any) => {
<view class="flex-row items-center justify-between">
<view class="sub-text-26">{{ props.cardetail.merchantName }}</view>
<view class="w-100rpx flex-row-center">
<tm-tag v-if="props.cardetail.orderStatus == 1" color="info" size="xs">未支付</tm-tag>
<slot v-if="props.cardetail.orderStatus == 5">
<tm-tag v-if="props.cardetail.orderType == 2" color="success" size="xs">已预约</tm-tag>
<tm-tag v-else color="success" size="xs">已支付</tm-tag>
</slot>
<tm-tag v-if="props.cardetail.orderStatus == 10" color="error" size="xs">售后</tm-tag>
<tm-tag v-if="props.cardetail.orderStatus == 20" size="xs">已核销</tm-tag>
<tm-tag v-if="props.cardetail.orderStatus == 30" color="info" size="xs">已取消(智能柜)</tm-tag>
<tm-tag v-if="props.cardetail.orderStatus == 110" color="success" size="xs">配送中</tm-tag>
<order-status-tag :cardetail="props.cardetail" />
</view>
</view>
<view class="flex-row items-center justify-between second-text-22">
@@ -249,7 +261,7 @@ const doCancel = (v: any) => {
<!-- start 简单购买信息(付款方式+支付金额) start -->
<view v-if="!props.cardetail?.carList?.length"
class="flex items-center justify-between rounded-12rpx border-[#eef3f8] bg-[linear-gradient(90deg,#ffffff_0%,#fafafa_100%)] pr-28rpx py-16rpx">
class="flex items-center justify-between rounded-12rpx border-[#eef3f8] bg-[linear-gradient(90deg,#ffffff_0%,#fafafa_100%)] px-22rpx py-12rpx">
<view class="flex-row-center sub-text-24 gap-8rpx">
<text class="iconfont icon-zhifufangshi1 text-24rpx!" />
<text> {{ hooks.useEnum('hardwarePayTypeEnum', props.cardetail.payType) }}</text>
@@ -262,7 +274,7 @@ const doCancel = (v: any) => {
<!-- start 预约信息 start -->
<view v-if="props.cardetail.apptId && props.cardetail.apptStartTime && props.cardetail.apptEndTime"
class="flex items-center justify-between rounded-12rpx border border-[#eef3f8] bg-[linear-gradient(90deg,#ffffff_0%,#fafafa_100%)] px-28rpx py-16rpx shadow-[0_8rpx_24rpx_rgba(15,23,42,0.04)]">
class="flex items-center justify-between rounded-12rpx border border-[#eef3f8] bg-[linear-gradient(90deg,#ffffff_0%,#fafafa_100%)] px-22rpx py-12rpx shadow-[0_8rpx_24rpx_rgba(15,23,42,0.04)]">
<view class="min-w-0 flex-1">
<view class="second-text-22 text-[#98a2b3]!">预约时间段</view>
<view class="mt-10rpx sub-text-24">
@@ -277,9 +289,24 @@ const doCancel = (v: any) => {
</view>
<!-- end 预约信息 end -->
<!-- start 发货信息 start -->
<view v-if="props.cardetail.shipmentNo"
class="flex items-center justify-between rounded-12rpx border border-[#eef3f8] bg-[linear-gradient(90deg,#ffffff_0%,#fafafa_100%)] px-22rpx py-12rpx shadow-[0_8rpx_24rpx_rgba(15,23,42,0.04)]"
@tap.stop="doShipment(props.cardetail)">
<view class="flex items-center gap-4rpx">
<text class="iconfont icon-jisuwuliu text-24rpx!" />
<text class="sub-text-24 font-400!">{{ props.cardetail.shipmentNo }}</text>
</view>
<view class="flex items-center gap-4rpx">
<text class="second-text-24!">查看物流</text>
<text class="iconfont icon-youjiantou second-text-22!" />
</view>
</view>
<!-- end 发货信息 end -->
<!-- start 底部操作区域 start -->
<view
v-if="showRefundButton(props.cardetail) || showRefundDetailButton(props.cardetail) || showQrCodeDetailButton(props.cardetail) || showCancelButton(props.cardetail)"
v-if="showRefundButton(props.cardetail) || showRefundDetailButton(props.cardetail) || showQrCodeDetailButton(props.cardetail) || showCancelButton(props.cardetail) || showExpressTrackingButton(props.cardetail)"
class="w-full flex-row items-center justify-end my-1">
<view v-if="showRefundButton(props.cardetail)" class="botder_btn" @tap.stop="doRefund(props.cardetail)">
退款
@@ -288,6 +315,10 @@ const doCancel = (v: any) => {
@tap.stop="doToDetail(props.cardetail)">
售后详情
</view>
<view v-if="showExpressTrackingButton(props.cardetail)" class="m-l-20rpx primary_btn"
@tap.stop="doExpressTracking(props.cardetail)">
确认收货
</view>
<view v-if="showQrCodeDetailButton(props.cardetail)" class="m-l-20rpx botder_btn"
@tap.stop="doToDetail(props.cardetail)">
核销码
+58 -18
View File
@@ -1,10 +1,11 @@
<!-- z-tabs v0.2.5 by-ZXLee -->
<!-- z-tabs v0.2.7 by-ZXLee -->
<!-- github地址:https://github.com/SmileZXLee/uni-z-tabs -->
<!-- dcloud地址:https://ext.dcloud.net.cn/plugin?name=z-tabs -->
<!-- 反馈QQ群:790460711 -->
<!-- 反馈QQ群:371624008 -->
<template name="z-tabs">
<view class="z-tabs-conatiner" :style="[{ background: bgColor }, tabsStyle]">
<view class="z-tabs-conatiner"
:style="[{ background: bgColor }, { height: unit === 'rpx' ? '80rpx' : '40px' }, tabsStyle]">
<view class="z-tabs-left">
<slot name="left" />
</view>
@@ -18,13 +19,14 @@
@click="tabsClick(index, item)">
<view class="z-tabs-item-title-container">
<text
:class="{ 'z-tabs-item-title': true, 'z-tabs-item-title-disabled': item.disabled }"
:class="{ 'z-tabs-item-title-rpx': unit === 'rpx', 'z-tabs-item-title-px': unit === 'px', 'z-tabs-item-title-disabled': item.disabled }"
:style="[{ color: item.disabled ? disabledColor : (currentIndex === index ? activeColor : inactiveColor) }, item.disabled ? disabledStyle : (currentIndex === index ? activeStyle : inactiveStyle)]">
{{ item[nameKey] || item }}
</text>
<text v-if="item.badge && _formatCount(item.badge.count).length"
class="z-tabs-item-badge" :style="[badgeStyle]">{{ _formatCount(item.badge.count)
}}</text>
class="z-tabs-item-badge"
:class="{ 'z-tabs-item-badge-rpx': unit === 'rpx', 'z-tabs-item-badge-px': unit === 'px' }"
:style="[badgeStyle]">{{ _formatCount(item.badge.count) }}</text>
</view>
</view>
</view>
@@ -97,6 +99,7 @@ function _toKebab(value) {
* @property {String} bg-color z-tabs背景色
* @property {Object} tabs-style z-tabs样式
* @property {Boolean} init-trigger-change 初始化时是否自动触发change事件
* @property {String} unit z-tabs中布局的单位,默认为rpx
* @event {Function(index,value)} change tabs改变时触发,index:当前切换到的index;value:当前切换到的value
* @example <z-tabs :list="list"></z-tabs>
*/
@@ -166,6 +169,11 @@ export default {
type: [Number, String],
default: _gc('barHeight', 8)
},
//swiper的宽度,单位rpx,支持传100、"100px"或"100rpx",默认为"750rpx"
swiperWidth: {
type: [Number, String],
default: _gc('swiperWidth', 750)
},
//滑块样式,其中的width和height将被barWidth和barHeight覆盖
barStyle: {
type: Object,
@@ -250,6 +258,11 @@ export default {
initTriggerChange: {
type: Boolean,
default: _gc('initTriggerChange', false)
},
//z-tabs中布局的单位,默认为rpx
unit: {
type: String,
default: _gc('unit', 'rpx')
}
},
mounted() {
@@ -345,13 +358,16 @@ export default {
return this._convertTextToPx(this.tabWidth);
},
finalBarWidth() {
return this._convertTextToPx(this.barWidth);
return this._convertTextToPx(this._addUnit(this.barWidth, this.unit));
},
finalBarHeight() {
return this._convertTextToPx(this.barHeight);
return this._convertTextToPx(this._addUnit(this.barHeight, this.unit));
},
finalSwiperWidth() {
return this._convertTextToPx(this.swiperWidth);
},
finalBottomSpace() {
return this._convertTextToPx(this.bottomSpace);
return this._convertTextToPx(this._addUnit(this.bottomSpace, this.unit));
}
},
methods: {
@@ -360,7 +376,7 @@ export default {
if (!this.shouldSetDx) return;
const isLineMode = this.barAnimateMode === 'line';
const isWormMode = this.barAnimateMode === 'worm';
let dxRate = dx / this.tabsSuperWidth;
let dxRate = dx / this.finalSwiperWidth;
this.currentSwiperIndex = this.currentIndex + parseInt(dxRate);
const isRight = dxRate > 0;
const barWidth = this.pxBarWidth;
@@ -501,7 +517,9 @@ export default {
}
}
}
this.bottomDotX = this._getBottomDotX(node, this.finalBarWidth, offset);
if (node) {
this.bottomDotX = this._getBottomDotX(node, this.finalBarWidth, offset);
}
this.bottomDotXForIndex = this.bottomDotX;
if (this.tabsWidth) {
setTimeout(() => {
@@ -614,6 +632,18 @@ export default {
return Number(text);
}
return 0;
},
// 添加单位
_addUnit(value, unit) {
if (Object.prototype.toString.call(value) === '[object String]') {
let tempValue = value;
tempValue = tempValue.replace('rpx', '').replace('upx', '').replace('px', '');
if (value.indexOf('rpx') === -1 && value.indexOf('upx') === -1 && value.indexOf('px') !== -1) {
tempValue = parseFloat(tempValue) * 2;
}
value = tempValue;
}
return unit === 'rpx' ? value + 'rpx' : (value / 2) + 'px';
}
}
@@ -631,7 +661,6 @@ export default {
width: 750rpx;
/* #endif */
flex-direction: row;
height: 80rpx;
}
.z-tabs-scroll-view-conatiner {
@@ -703,10 +732,12 @@ export default {
align-items: center;
}
.z-tabs-item-title {
display: inline-block;
width: 100%;
font-size: 28rpx;
.z-tabs-item-title-rpx {
font-size: 30rpx;
}
.z-tabs-item-title-px {
font-size: 15px;
}
.z-tabs-item-title-disabled {
@@ -716,14 +747,23 @@ export default {
}
.z-tabs-item-badge {
margin-left: 8rpx;
background-color: #ec5b56;
color: white;
font-size: 22rpx;
border-radius: 100px;
}
.z-tabs-item-badge-rpx {
margin-left: 8rpx;
font-size: 22rpx;
padding: 0rpx 10rpx;
}
.z-tabs-item-badge-px {
margin-left: 4px;
font-size: 11px;
padding: 0px 5px;
}
.z-tabs-bottom {
position: absolute;
bottom: 0;
+362 -6
View File
@@ -1,13 +1,369 @@
<template>
<view class="card rounded-24rpx bg-white p-20rpx">
<view class="flex items-center justify-between">
<view class="sub-text-24">选择支付方式</view>
</view>
<view class="mt-20rpx flex-col justify-items-start gap-8rpx">
<view>
<view class="card rounded-24rpx bg-white p-20rpx">
<view v-if="visibleTitle" class="flex items-center justify-between">
<view class="sub-text-24">选择支付方式</view>
</view>
<view v-if="isPay">
<view class="mt-20rpx px-10rpx pb-10rpx rounded-12rpx flex-col justify-items-start bg-#eff8ff"
@tap="!walletPayDetail.disabled && (state.currentPayType = walletPayDetail.type)">
<view class="flex items-center gap-12rpx my-20rpx">
<image class="size-40rpx" src="/static/logo/zax.png" mode="scaleToFill" />
<text class="sub-text-24">钱包支付</text>
</view>
<view class="bg-white rounded-12rpx" v-if="walletPayDetail && visibleWalletPay">
<view class="px-20rpx">
<view class="flex-col justify-between">
<view class="my-20rpx second-text-26">付款方式</view>
<view class="second-text-22">
<text>{{ walletPayDetail.extra.walletTypeName }}</text>
<text>(可用¥{{ hooks.useMoney(walletPayDetail.extra.balance) }})</text>
</view>
</view>
<view class="wallet-border" />
<view class="flex items-center justify-between" @tap.stop="doOpenSelectWalletDrawer">
<text class="second-text-24">更换钱包支付</text>
<text class="iconfont icon-youjiantou second-text-24!" />
</view>
</view>
<view
class="flex items-center justify-between rounded-12rpx bg-#f8f8f8 m-t-12rpx px-12rpx py-10rpx">
<text class="sub-text-22">钱包极速支付</text>
<tm-radio :showLabel="false" v-model="state.currentPayType"
:disabled="walletPayDetail.disabled" :value="walletPayDetail.type" />
</view>
</view>
</view>
<view v-for="(v, i) in state.payType" :key="i" @tap="!v.disabled && (state.currentPayType = v.type)">
<view v-if="['10', '20'].includes(v.type)"
class="flex items-center py-20rpx pl-10rpx pr-22rpx rounded-16rpx">
<image class="size-50rpx" :src="v.logoDir" mode="scaleToFill" />
<view class="flex-1 flex justify-between items-center">
<view class="flex-col justify-center p-l-30rpx gap-4rpx">
<view class="sub-text-24">{{ v.text }}</view>
<view class="second-text-20">{{ v.desc }}</view>
</view>
<tm-radio :showLabel="false" v-model="state.currentPayType" :disabled="v.disabled"
:value="v.type" />
</view>
</view>
</view>
</view>
<view v-else class="wallet-recharge-empty bg-white rounded-12rpx m-t-20rpx">
<view class="wallet-recharge-empty__icon">!</view>
<view class="sub-text-26 color-#333">该商户暂不提供充值功能,请联系客服</view>
<service-text />
</view>
</view>
</view>
<view v-if="isPay && visiblePayBtn"
class="bg-#0094ff p-y-20rpx text-center rounded-40rpx m-t-60rpx sub-text-30 color-white!" @tap="doPay">
确认充值
</view>
<tm-drawer v-model:show="state.visibleSelectWallet" title="选择钱包" :show-close="true" position="bottom" size="40%">
<view class="flex-col" v-for="(v, index) in state.walletList" :key="index" @click="selectTargetWalletType(v)">
<view class="flex items-center p-x-30rpx p-y-20rpx">
<view class="flex-1 flex items-center gap-16rpx">
<image class="size-40rpx" :src="filePath(v.walletTypeIcon)" mode="scaleToFill" />
<view class="flex items-center">
<text class="sub-text-24 font-400!">{{ v.walletTypeName }}</text>
<text class="sub-text-24 font-400!">可用¥{{ hooks.useMoney(v.balance) }}</text>
<text v-if="v.cardNumber"
class="ml-8rpx rounded-8rpx border border-[#d1e9ff] bg-[#eff8ff] px-18rpx py-12rpx text-20rpx line-height-1 text-[#1570ef]">
{{ v.cardNumber }}
</text>
</view>
</view>
<tm-radio :showLabel="false" v-model="state.currentSelectWallet" :disabled="v.disabled"
:value="v.walletTypeId" />
</view>
<view class="wallet-border" />
</view>
</tm-drawer>
</template>
<script setup lang="ts">
import { ref, reactive, computed, onMounted, watch } from "vue"
import { onLoad, onShow } from "@dcloudio/uni-app"
import { isEmpty } from "radash"
</script>
import { hooks, constant, router } from "@@/index"
import { filePath } from "@/sheep/utils/file"
import { walletApi, appPlusPlatformApi, type AppPlusPayDto } from "@@/api"
import ServiceText from "@@/components/common/service-text.vue"
const emit = defineEmits(["selectPayType"])
export type Props = {
visibleTitle?: boolean
visiblePayBtn?: boolean
visibleWalletPay?: boolean
}
const props = withDefaults(defineProps<Props>(), {
visibleTitle: true,
visiblePayBtn: true,
visibleWalletPay: true,
})
const isPay = computed(() => {
return state.payType && state.payType.length > 0
})
const walletPayDetail = computed(() => {
return state.payType.find(v => v.type == "30") ?? null
})
const state = reactive<any>({
visibleSelectWallet: false,
walletList: [],
currentSelectWallet: null,
payNo: '',
payType: [],
walletId: null,
currentPayType: null,
currentRecharge: 0,
userInfo: {},
detail: {},
payTypeList: [],
rechargeList: [],
})
watch(() => state.currentPayType, (value) => {
if (!value) return
const targetPayType = state.payType.find(v => v.type == value) ?? null
emit("selectPayType", value, targetPayType)
})
onMounted(() => {
getUserPayTypeList()
})
// 获取用户钱包列表
const getAllWalletList = async () => {
try {
const { data, code }: any = await walletApi.all()
if (code != 0) throw new Error(data?.msg || "获取用户钱包列表失败")
const records = data?.records ?? []
state.walletList = records
state.payType.push({
logoDir: "/static/logo/zax.png",
text: "钱包支付",
desc: "内部支付,方便,快捷",
disabled: false,
type: "30",
value: "WALLET_PAY",
extra: {
...records[0]
}
})
state.currentSelectWallet = records[0].walletTypeId
} catch (error) {
console.log("获取用户钱包列表,error:", error);
}
}
const getUserPayTypeList = async () => {
const _pa = {}
const { data } = await walletApi.getUserPaySettingList(_pa)
state.payTypeList = data ?? []
if (state.payTypeList.length) {
const typeList = state.payType.map(v => v.type)
for (const v of state.payTypeList) {
if (!v.platformType) continue
if (v.platformType == "30" && !typeList.includes("30")) {
await getAllWalletList()
}
if (v.platformType == "10" && !typeList.includes("10")) {
state.payType.push({
logoDir: "/static/pay/wechat-pay.jpg",
text: "微信支付",
desc: "",
disabled: false,
type: "10",
value: "WECHAT"
},)
}
if (v.platformType == "20" && !typeList.includes("20")) {
state.payType.push({
logoDir: "/static/pay/ali-pay.png",
text: "支付宝支付",
desc: "10亿用户都在用,真安全,更放心",
disabled: false,
type: "20",
value: "ALI_PAY"
},)
}
}
const defaultPayType = state.payType[0].type
if (defaultPayType && !state.currentPayType) {
state.currentPayType = defaultPayType
}
}
}
const doOpenSelectWalletDrawer = () => {
state.visibleSelectWallet = true
}
const selectTargetWalletType = (v: any) => {
state.currentSelectWallet = v.walletTypeId
state.visibleSelectWallet = false
// 重新计算选择钱包
const walletPay = state.payType.find(v => v.type == "30")
if (walletPay) {
walletPay.extra = v
}
}
// 充值
const doPay = async () => {
if (state.currentRecharge == -1) {
uni.showToast({ title: "请选择充值金额", icon: "none" })
return
}
if (state.currentPayType == undefined) {
uni.showToast({ title: "请选择支付方式", icon: "none" })
return
}
try {
uni.showLoading({ title: "充值中...", mask: true })
const targetRecharge = state.rechargeList[state.currentRecharge]
const targetPayType = state.payType.find(v => v.type == state.currentPayType)
if (isEmpty(targetRecharge)) {
throw new Error()
}
// 检查用户是否有支付环境
const pRes = await uni.getProvider({ service: 'payment' })
const providers = (pRes?.provider ?? []) as string[]
if (!providers.length) {
throw new Error()
}
if (targetPayType!.value == "WECHAT") {
await disposeWxpay(targetRecharge.id)
} else if (targetPayType!.value == "ALI_PAY") {
await disposeAlipay(targetRecharge.id)
}
uni.hideLoading()
router.jump("/pages/wallet/recharge-done", { payNo: state.payNo }, 2)
} catch (error: any) {
console.error("充值意外的错误:", error)
uni.hideLoading()
if (error?.errMsg && error?.errMsg.includes("requestPayment:fail [paymentAlipay:62009]unknown error")) {
uni.showToast({ title: "请先安装支付宝App", icon: "none" })
} else if (error?.message) {
uni.showToast({ title: error.message, icon: "none" })
} else {
uni.showToast({ title: "充值失败", icon: "none" })
}
}
}
// 微信支付
const disposeWxpay = async (bizId: any) => {
const pRes = await uni.getProvider({ service: 'payment' })
const providers = (pRes?.provider ?? []) as string[]
if (!providers.length) {
throw new Error("微信支付环境错误")
}
if (!providers.includes("wxpay")) {
uni.showToast({ title: "请先安装微信,确保微信支付环境", icon: "none" })
return
}
const payParams: AppPlusPayDto = {
bizType: "MINI_APP_WALLET_RECHARGE",
bizId: bizId,
miniPlatformType: "WECHAT",
openId: "",
}
const { data: createData, code: createCode }: any = await appPlusPlatformApi.miniPay(payParams)
console.log("微信支付createData:", createData);
if (createCode != 0) {
throw new Error(createData?.msg ?? "微信充值发起失败")
}
state.payNo = createData?.payNo ?? ""
const paymentResult = await uni.requestPayment({
provider: 'wxpay',
orderInfo: {
"appid": createData?.appPlusPayParams?.appid,
"noncestr": createData?.appPlusPayParams?.nonceStr,
"package": createData?.appPlusPayParams?.packageVal,
"partnerid": createData?.appPlusPayParams?.partnerId,
"prepayid": createData?.appPlusPayParams?.prepayId,
"timestamp": createData?.appPlusPayParams?.timestamp,
"sign": createData?.appPlusPayParams?.sign,
},
});
console.log("微信支付requestPayment返回:", paymentResult);
if (paymentResult.errMsg && paymentResult.errMsg.includes("requestPayment:ok")) {
uni.showToast({ title: "充值成功", icon: "none" })
} else {
uni.showToast({ title: "充值失败", icon: "none" })
}
}
// 支付宝支付
const disposeAlipay = async (bizId: any) => {
const pRes = await uni.getProvider({ service: 'payment' })
const providers = (pRes?.provider ?? []) as string[]
if (!providers.length) {
throw new Error("支付宝支付环境错误")
}
if (!providers.includes("alipay")) {
uni.showToast({ title: "请先安装支付宝App,确保支付宝支付环境", icon: "none" })
return
}
const payParams: AppPlusPayDto = {
bizType: "MINI_APP_WALLET_RECHARGE",
bizId: bizId,
miniPlatformType: "ALI_PAY",
openId: "",
}
const { data: createData, code: createCode }: any = await appPlusPlatformApi.miniPay(payParams)
if (createCode != 0) {
throw new Error(createData?.msg ?? "支付宝充值发起失败")
}
state.payNo = createData?.payNo ?? ""
const paymentResult = await uni.requestPayment({
provider: 'alipay',
...createData?.appPlusPayParams ?? {},
});
console.log("支付宝支付requestPayment返回:", paymentResult);
if (paymentResult.errMsg && paymentResult.errMsg.includes("requestPayment:ok")) {
uni.showToast({ title: "充值成功", icon: "none" })
} else {
uni.showToast({ title: "充值失败", icon: "none" })
}
}
</script>
<style scoped>
.wallet-border {
border: 1rpx solid #f8f8f8;
margin: 10rpx 0;
}
.wallet-recharge-empty {
padding: 60rpx 0;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.wallet-recharge-empty__icon {
width: 88rpx;
height: 88rpx;
margin-bottom: 24rpx;
border-radius: 9999rpx;
background: #e9f5fe;
color: #0094ff;
font-size: 42rpx;
font-weight: 700;
display: flex;
align-items: center;
justify-content: center;
}
</style>
+2
View File
@@ -30,7 +30,9 @@ const hardwarePayTypeEnum = {
8: "现金支付",
9: "智能柜免密支付",
10: "微信付款码支付",
100: "微信App支付",
11: "支付宝付款码支付",
110: "支付宝App支付",
12: "中医院",
13: "人民医院",
14: "超市消费卡",
+7 -3
View File
@@ -1,9 +1,9 @@
@font-face {
font-family: "iconfont";
/* Project id 4740692 */
src: url('https://at.alicdn.com/t/c/font_4740692_fg48uzfs38.woff2?t=1782206048581') format('woff2'),
url('https://at.alicdn.com/t/c/font_4740692_fg48uzfs38.woff?t=1782206048581') format('woff'),
url('https://at.alicdn.com/t/c/font_4740692_fg48uzfs38.ttf?t=1782206048581') format('truetype');
src: url('https://at.alicdn.com/t/c/font_4740692_ol93veips6n.woff2?t=1782372215492') format('woff2'),
url('https://at.alicdn.com/t/c/font_4740692_ol93veips6n.woff?t=1782372215492') format('woff'),
url('https://at.alicdn.com/t/c/font_4740692_ol93veips6n.ttf?t=1782372215492') format('truetype');
}
.iconfont {
@@ -14,6 +14,10 @@
-moz-osx-font-smoothing: grayscale;
}
.icon-jisuwuliu:before {
content: "\e61a";
}
.icon-add:before {
content: "\e79e";
}
+2 -2
View File
@@ -53,7 +53,7 @@ export const useProsperityCartStore = defineStore("prosperityCartStore", {
},
// 获取购物车商品列表
async getShopCart(merchantId: string) {
const { data } = await orderApi.getShopCar({ merchantId, hardType: "FARMER" })
const { data } = await orderApi.getShopCar({ merchantId, hardType: "SELF_APP" })
this.cartList = data ?? []
},
// 更新购物车商品列表
@@ -66,7 +66,7 @@ export const useProsperityCartStore = defineStore("prosperityCartStore", {
productId: v.productId,
}
})
const { code }: any = await orderApi.updateShopCar({ merchantId, list, hardType: "FARMER" })
const { code }: any = await orderApi.updateShopCar({ merchantId, list, hardType: "SELF_APP" })
if (code == 0) return await this.getShopCart(merchantId)
uni.showToast({ title: "更新购物车失败", icon: "none" })
},
+19 -12
View File
@@ -1,12 +1,19 @@
import { defineStore } from "pinia"
import STORAGE_KEY from "../constant/storage-constant"
import pinia from "./index"
import { useTabbarStore } from "./tabbar"
import merchantApi from "../api/merchant"
import userApi from "../api/user"
const getTabbarStore = async (): Promise<any | null> => {
let tabbarStore: any = null
// #ifndef APP-NVUE
const [{ default: pinia }, { useTabbarStore }] = await Promise.all([import("./index"), import("./tabbar")])
tabbarStore = useTabbarStore(pinia)
// #endif
return tabbarStore
}
type UserInfo = {
// 主键ID,后端 Long 在前端统一按 string 处理,避免精度丢失。
userId: string
@@ -134,10 +141,10 @@ export const useUserStore = defineStore("user", {
async disposeLogin() {
await this._getUserDetail()
await this._getUserZsMch()
const tabbarStore = useTabbarStore(pinia)
tabbarStore.setMchFunctionScope(this.ZsMch?.functionScope || {})
tabbarStore.refreshVisibleTabbar()
tabbarStore.syncByCurrentRoute()
const tabbarStore = await getTabbarStore()
tabbarStore?.setMchFunctionScope(this.ZsMch?.functionScope || {})
tabbarStore?.refreshVisibleTabbar()
tabbarStore?.syncByCurrentRoute()
},
// 处理退出登录
async disposeLogOut() {
@@ -157,10 +164,10 @@ export const useUserStore = defineStore("user", {
this.ZsMch = undefined
uni.hideLoading()
uni.showToast({ title: "退出登录成功", icon: "none" })
const tabbarStore = useTabbarStore(pinia)
tabbarStore.setMchFunctionScope({})
tabbarStore.reset()
tabbarStore.hideTabbar()
const tabbarStore = await getTabbarStore()
tabbarStore?.setMchFunctionScope({})
tabbarStore?.reset()
tabbarStore?.hideTabbar()
},
// 获取用户详情
async _getUserDetail() {
@@ -192,8 +199,8 @@ export const useUserStore = defineStore("user", {
} else {
this.ZsMch = undefined
}
const tabbarStore = useTabbarStore(pinia)
tabbarStore.setMchFunctionScope(this.ZsMch?.functionScope || {})
const tabbarStore = await getTabbarStore()
tabbarStore?.setMchFunctionScope(this.ZsMch?.functionScope || {})
} catch (error) {
console.log("获取用户的直属商户,意外的错误error:", error)
}
+31
View File
@@ -0,0 +1,31 @@
/**
* 打开外部应用
*/
/**
* 打开快递查询页面
* @param trackingNo 快递单号
* @param companyCode 快递公司
* @returns
*/
export const openExpressTracking = (trackingNo?: string, companyCode?: string) => {
if (!trackingNo && !companyCode) {
uni.showToast({ title: "缺少快递单号或快递公司", icon: "none" })
return
}
let url = `https://m.kuaidi100.com/app/query/?coname=zax`
if (trackingNo) {
url += `&nu=${encodeURIComponent(trackingNo)}`
}
if (companyCode) {
url += `&com=${encodeURIComponent(companyCode)}`
}
// #ifdef APP-PLUS
plus.runtime.openURL(url, () => {
uni.showToast({ title: "无法打开物流查询页面", icon: "none" })
})
// #endif
// #ifdef H5
window.open(url, "_blank")
// #endif
}
+237
View File
@@ -0,0 +1,237 @@
import { appPlusPlatformApi, orderApi, type AppPlusPayDto } from "@/sheep/api"
export abstract class PayTool {
// 检查支付环境(如果没有支付环境会提示)
abstract checkPayEnv(): Promise<boolean>
abstract pay(payParams: any): Promise<boolean>
}
// 微信支付
export class WechatPayTool extends PayTool {
override async checkPayEnv() {
const pRes = await uni.getProvider({ service: "payment" })
const providers = (pRes?.provider ?? []) as string[]
if (!providers.length) {
uni.showToast({ title: "微信支付环境错误", icon: "none" })
return false
}
if (!providers.includes("wxpay")) {
uni.showToast({ title: "请先安装微信,确保微信支付环境", icon: "none" })
return false
}
return true
}
override async pay(payParams: any) {
const paymentResult = await uni.requestPayment({
provider: "wxpay",
orderInfo: {
appid: payParams?.appPlusPayParams?.appid,
noncestr: payParams?.appPlusPayParams?.nonceStr,
package: payParams?.appPlusPayParams?.packageVal,
partnerid: payParams?.appPlusPayParams?.partnerId,
prepayid: payParams?.appPlusPayParams?.prepayId,
timestamp: payParams?.appPlusPayParams?.timestamp,
sign: payParams?.appPlusPayParams?.sign,
},
})
if (paymentResult.errMsg && paymentResult.errMsg.includes("requestPayment:ok")) {
uni.showToast({ title: "支付成功", icon: "none" })
return true
} else {
uni.showToast({ title: "支付失败", icon: "none" })
return false
}
}
}
// 支付宝支付
export class AlipayPayTool extends PayTool {
override async checkPayEnv() {
const pRes = await uni.getProvider({ service: "payment" })
const providers = (pRes?.provider ?? []) as string[]
if (!providers.length) {
uni.showToast({ title: "支付宝支付环境错误", icon: "none" })
return false
}
if (!providers.includes("alipay")) {
uni.showToast({ title: "请先安装支付宝App,确保支付宝支付环境", icon: "none" })
return false
}
return true
}
override async pay(payParams: any) {
const paymentResult = await uni.requestPayment({
provider: "alipay",
...(payParams?.appPlusPayParams ?? {}),
})
console.log("支付宝支付requestPayment返回:", paymentResult)
if (paymentResult.errMsg && paymentResult.errMsg.includes("requestPayment:ok")) {
uni.showToast({ title: "支付成功", icon: "none" })
return true
} else {
uni.showToast({ title: "支付失败", icon: "none" })
return false
}
}
}
export class AggregationPayTool {
/**
* 充值支付
* @param bizId 充值的套餐id
* @returns
*/
static async topupPay(bizId: string, payType: "WECHAT" | "ALI_PAY") {
if (!bizId) throw new Error("充值的套餐id不能为空")
try {
uni.showLoading({ title: "充值中...", mask: true })
if (payType == "WECHAT") {
const wechatPayTool = new WechatPayTool()
const isWechatPayEnv = await wechatPayTool.checkPayEnv()
if (!isWechatPayEnv) throw new Error("微信支付环境错误")
const payParams: AppPlusPayDto = {
bizType: "MINI_APP_WALLET_RECHARGE",
bizId: bizId,
miniPlatformType: "WECHAT",
openId: "",
}
const { data: createData, code: createCode }: any = await appPlusPlatformApi.miniPay(payParams)
console.log("微信支付(充值)createData:", createData)
if (createCode != 0) {
uni.showToast({ title: createData?.msg ?? "微信充值发起失败", icon: "none" })
throw new Error(createData?.msg ?? "微信充值发起失败")
}
const isPaySuccess = await wechatPayTool.pay(createData)
if (isPaySuccess) {
return {
payNo: createData?.payNo,
}
}
} else if (payType == "ALI_PAY") {
const alipayPayTool = new AlipayPayTool()
const isAlipayPayEnv = await alipayPayTool.checkPayEnv()
if (!isAlipayPayEnv) throw new Error("支付宝支付环境错误")
const payParams: AppPlusPayDto = {
bizType: "MINI_APP_WALLET_RECHARGE",
bizId: bizId,
miniPlatformType: "ALI_PAY",
openId: "",
}
const { data: createData, code: createCode }: any = await appPlusPlatformApi.miniPay(payParams)
if (createCode != 0) {
throw new Error(createData?.msg ?? "支付宝充值发起失败")
}
const isPaySuccess = await alipayPayTool.pay(createData)
if (isPaySuccess) {
return {
payNo: createData?.payNo,
}
}
}
} catch (error: any) {
console.error("充值意外的错误:", error)
uni.hideLoading()
if (error?.errMsg && error?.errMsg.includes("requestPayment:fail [paymentAlipay:62009]unknown error")) {
uni.showToast({ title: "请先安装支付宝App", icon: "none" })
} else if (error?.message) {
uni.showToast({ title: error.message, icon: "none" })
} else {
uni.showToast({ title: "充值失败", icon: "none" })
}
}
}
// 商城支付(有订单)
static async shopPay({
parentCarId,
mapAddressId,
payType,
walletTypeId,
}: {
parentCarId: string
mapAddressId: string
payType: string
walletTypeId?: string
}) {
if (!["10", "20", "30"].includes(payType)) {
uni.showToast({ title: "请选择正确的支付方式", icon: "none" })
throw new Error("请选择正确的支付方式")
}
try {
uni.showLoading({ title: "支付中...", mask: true })
const createPayParams = {
carParentId: parentCarId,
mapAddressId,
payType,
walletTypeId,
}
const wechatPayTool = new WechatPayTool()
const alipayPayTool = new AlipayPayTool()
// 微信
if (payType == "10") {
const isWechatPayEnv = await wechatPayTool.checkPayEnv()
if (!isWechatPayEnv) throw new Error("微信支付环境错误")
}
// 支付宝
else if (payType == "20") {
const isAlipayPayEnv = await alipayPayTool.checkPayEnv()
if (!isAlipayPayEnv) throw new Error("支付宝支付环境错误")
}
// 钱包
else if (payType == "30") {
if (!walletTypeId) {
uni.showToast({ title: "请选择钱包类型", icon: "none" })
throw new Error("请选择钱包类型")
}
}
const {
data: createData,
code: createCode,
msg: createMsg,
}: any = await orderApi.appCreateShopReserveOrder({
...createPayParams,
})
console.log("商城支付(创建支付订单)createData:", createData, "createCode:", createCode, "createMsg:", createMsg)
if (createCode != 0) {
uni.showToast({ title: createMsg ?? "支付发起失败", icon: "none" })
throw new Error(createMsg ?? "支付发起失败")
}
// 微信支付
if (payType == "10") {
const isPaySuccess = await wechatPayTool.pay(createData)
uni.hideLoading()
if (isPaySuccess) {
return {
orderId: createData?.orderId,
payNo: createData?.payNo,
}
}
}
// 支付宝支付
else if (payType == "20") {
const isPaySuccess = await alipayPayTool.pay(createData)
uni.hideLoading()
if (isPaySuccess) {
return {
orderId: createData?.orderId,
payNo: createData?.payNo,
}
}
} else if (payType == "30") {
uni.hideLoading()
return {
orderId: createData?.orderId,
payNo: createData?.payNo,
}
}
} catch (error: any) {
console.error("支付意外的错误:", error)
uni.hideLoading()
if (error?.message) {
uni.showToast({ title: error.message, icon: "none" })
} else {
uni.showToast({ title: "充值失败", icon: "none" })
}
}
}
}
@@ -291,13 +291,13 @@ export default {
<template>
<view :class="[_disabled ? 'checkboxDisabled' : '']" class="checkbox" @click="boxClick">
<view class="checkboxBox" v-if="!hiddenCheckbox" :style="{
backgroundColor: _isCheck ? _color : 'transparent',
border: `1px solid ${_isCheck ? _color : _unCheckColor}`,
width: _size,
height: _size
}">
backgroundColor: _isCheck ? _color : 'transparent',
border: `1px solid ${_isCheck ? _color : _unCheckColor}`,
width: _size,
height: _size
}">
<view :class="['checkboxBoxIcon', 'checkboxBoxIcon_' + (nowValue ? 'on' : 'off')]">
<tm-icon color="white" :name="icon" :size="iconSize"></tm-icon>
<tm-icon :color="_isCheck ? 'white' : 'transparent'" :name="icon" :size="iconSize"></tm-icon>
</view>
</view>
+17
View File
@@ -7,6 +7,19 @@ import uni from "@dcloudio/vite-plugin-uni"
import { useRouter } from "./src/sheep/plugs/use-router/vite-router-plug"
const fixIifeCodeSplitPlugin = () => ({
name: "fix-uni-iife-code-splitting",
apply: "build" as const,
enforce: "post" as const,
outputOptions(outputOptions: Record<string, any>) {
if (outputOptions.format === "iife" || outputOptions.format === "umd") {
delete outputOptions.manualChunks
outputOptions.inlineDynamicImports = true
}
return outputOptions
},
})
// https://vitejs.dev/config/
export default defineConfig(({ mode }) => {
const { NODE_ENV, VITE_APP_DEV_SERVICE, VITE_APP_PROD_SERVICE, VITE_IMAGE_DEV_PATH } = loadEnv(mode, process.cwd(), "")
@@ -18,10 +31,14 @@ export default defineConfig(({ mode }) => {
return {
build: {
target: "es6",
sourcemap: false,
cssCodeSplit: false,
reportCompressedSize: false,
},
plugins: [
uni(),
Unocss(),
fixIifeCodeSplitPlugin(),
{
...useRouter({
pagesJsonDir: path.resolve(__dirname, "./src/pages.json"),