浏览代码

交易订单

Xiao_123 9 月之前
父节点
当前提交
f39e130909

+ 28 - 0
src/api/menduner/system/order/index.ts

@@ -0,0 +1,28 @@
+import request from '@/config/axios'
+
+// 交易订单	 VO
+export interface TradeOrderVO {
+  userId: number // 用户编号
+  type: string // 订单类型
+  spuId: number // 商品编号
+  spuName: string // 商品名字
+  price: number // 价格,单位:分
+}
+
+// 交易订单	 API
+export const TradeOrderApi = {
+  // 查询订单	分页
+  getTradeOrderPage: async (params: any) => {
+    return await request.get({ url: `/menduner/system/trade/order/page`, params })
+  },
+
+  // 创建订单	
+  createTradeOrder: async (data: TradeOrderVO) => {
+    return await request.post({ url: `/menduner/system/trade/order/create`, data })
+  },
+
+  // 发起订单退款	
+  updateTradeOrder: async (id: number) => {
+    return await request.put({ url: `/menduner/system/trade/order/refund?id=${id}` })
+  }
+}

+ 18 - 0
src/router/modules/remaining.ts

@@ -458,6 +458,24 @@ const remainingRouter: AppRouteRecordRaw[] = [
       }
     ]
   },
+  {
+    path: '/order',
+    component: Layout,
+    name: 'order',
+    meta: { hidden: true },
+    children: [
+      {
+        path: 'cashier',
+        name: 'PayCashierOrder',
+        meta: {
+          title: '收银台',
+          noCache: true,
+          hidden: true
+        },
+        component: () => import('@/views/menduner/system/order/pay.vue')
+      }
+    ]
+  },
   {
     path: '/pay',
     component: Layout,

+ 2 - 1
src/utils/dict.ts

@@ -233,5 +233,6 @@ export enum DICT_TYPE {
   MENDUNER_BALANCE_BIZ_TYPE = 'menduner_balance_biz_type', // 余额记录-业务类型
   MENDUNER_ENTERPRISE_AUTH_STATUS = 'menduner_enterprise_auth_status',
   MENDUNER_TAG_TYPE = 'menduner_tag_type',
-  MENDUNER_HUNT_STATUS = 'menduner_hunt_status'
+  MENDUNER_HUNT_STATUS = 'menduner_hunt_status',
+  MENDUNER_TRADE_ORDER_TYPE = 'menduner_trade_order_type' // 订单类型
 }

+ 123 - 0
src/views/menduner/system/order/TradeOrderForm.vue

@@ -0,0 +1,123 @@
+<template>
+  <Dialog title="发起订单" v-model="dialogVisible">
+    <el-form
+      ref="formRef"
+      :model="formData"
+      :rules="formRules"
+      label-width="80px"
+      v-loading="formLoading"
+    >
+      <el-form-item label="用户编号" prop="userId">
+        <el-input v-model="formData.userId" placeholder="请输入用户编号" />
+      </el-form-item>
+      <el-form-item label="订单类型" prop="type">
+        <el-select v-model="formData.type" placeholder="请选择订单类型">
+          <el-option
+            v-for="dict in getStrDictOptions(DICT_TYPE.MENDUNER_TRADE_ORDER_TYPE)"
+            :key="dict.value"
+            :label="dict.label"
+            :value="dict.value"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="商品编号" prop="spuId">
+        <el-input v-model="formData.spuId" placeholder="请输入商品编号" />
+      </el-form-item>
+      <el-form-item label="商品名字" prop="spuName">
+        <el-input v-model="formData.spuName" placeholder="请输入商品名字" />
+      </el-form-item>
+      <el-form-item label="价格" prop="price">
+        <el-input-number v-model="formData.price" :precision="2" :step="0.1" :min="0.01" />
+      </el-form-item>
+    </el-form>
+    <template #footer>
+      <el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button>
+      <el-button @click="dialogVisible = false">取 消</el-button>
+    </template>
+  </Dialog>
+</template>
+
+<script setup lang="ts">
+import { TradeOrderApi, TradeOrderVO } from '@/api/menduner/system/order'
+import { getStrDictOptions, DICT_TYPE } from '@/utils/dict'
+
+/** 交易订单	 表单 */
+defineOptions({ name: 'TradeOrderForm' })
+
+const { t } = useI18n() // 国际化
+const message = useMessage() // 消息弹窗
+
+const dialogVisible = ref(false) // 弹窗的是否展示
+const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
+const formType = ref('') // 表单的类型:create - 新增;update - 修改
+const formData = ref({
+  userId: undefined,
+  type: undefined,
+  spuId: undefined,
+  spuName: undefined,
+  price: 100,
+})
+const formRules = reactive({
+  userId: [{ required: true, message: '用户编号不能为空', trigger: 'blur' }],
+  type: [{ required: true, message: '订单类型不能为空', trigger: 'change' }],
+  spuId: [{ required: true, message: '商品编号不能为空', trigger: 'blur' }],
+  spuName: [{ required: true, message: '商品名字不能为空', trigger: 'blur' }],
+  price: [{ required: true, message: '价格不能为空', trigger: 'blur' }],
+})
+const formRef = ref() // 表单 Ref
+
+/** 打开弹窗 */
+const open = async (type: string) => {
+  dialogVisible.value = true
+  formType.value = type
+  resetForm()
+  // 修改时,设置数据
+  // if (id) {
+  //   formLoading.value = true
+  //   try {
+  //     formData.value = await TradeOrderApi.getTradeOrder(id)
+  //   } finally {
+  //     formLoading.value = false
+  //   }
+  // }
+}
+defineExpose({ open }) // 提供 open 方法,用于打开弹窗
+
+/** 提交表单 */
+const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
+const submitForm = async () => {
+  // 校验表单
+  await formRef.value.validate()
+  // 提交请求
+  formLoading.value = true
+  try {
+    const data = formData.value as unknown as TradeOrderVO
+    // if (formType.value === 'create') {
+    //   await TradeOrderApi.createTradeOrder(data)
+    //   message.success(t('common.createSuccess'))
+    // } else {
+    //   await TradeOrderApi.updateTradeOrder(data)
+    //   message.success(t('common.updateSuccess'))
+    // }
+    await TradeOrderApi.createTradeOrder(data)
+    message.success(t('common.createSuccess'))
+    dialogVisible.value = false
+    // 发送操作成功的事件
+    emit('success')
+  } finally {
+    formLoading.value = false
+  }
+}
+
+/** 重置表单 */
+const resetForm = () => {
+  formData.value = {
+    userId: undefined,
+    type: undefined,
+    spuId: undefined,
+    spuName: undefined,
+    price: 100
+  }
+  formRef.value?.resetFields()
+}
+</script>

+ 226 - 0
src/views/menduner/system/order/index.vue

@@ -0,0 +1,226 @@
+<template>
+  <ContentWrap>
+    <!-- 搜索工作栏 -->
+    <el-form
+      class="-mb-15px"
+      :model="queryParams"
+      ref="queryFormRef"
+      :inline="true"
+      label-width="140px"
+    >
+      <el-form-item label="用户编号" prop="userId">
+        <el-input
+          v-model="queryParams.userId"
+          placeholder="请输入用户编号"
+          clearable
+          @keyup.enter="handleQuery"
+          class="!w-240px"
+        />
+      </el-form-item>
+      <el-form-item label="订单类型" prop="type">
+        <el-select
+          v-model="queryParams.type"
+          placeholder="请选择订单类型"
+          clearable
+          class="!w-240px"
+        >
+          <el-option label="请选择字典生成" value="" />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="商品编号" prop="spuId">
+        <el-input
+          v-model="queryParams.spuId"
+          placeholder="请输入商品编号"
+          clearable
+          @keyup.enter="handleQuery"
+          class="!w-240px"
+        />
+      </el-form-item>
+      <el-form-item label="商品名字" prop="spuName">
+        <el-input
+          v-model="queryParams.spuName"
+          placeholder="请输入商品名字"
+          clearable
+          @keyup.enter="handleQuery"
+          class="!w-240px"
+        />
+      </el-form-item>
+      <el-form-item label="是否已支付" prop="payStatus">
+        <el-select
+          v-model="queryParams.payStatus"
+          placeholder="请选择是否已支付"
+          clearable
+          class="!w-240px"
+        >
+          <el-option label="请选择字典生成" value="" />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="支付订单编号" prop="payOrderId">
+        <el-input
+          v-model="queryParams.payOrderId"
+          placeholder="请输入支付订单编号"
+          clearable
+          @keyup.enter="handleQuery"
+          class="!w-240px"
+        />
+      </el-form-item>
+      <el-form-item label="订单支付时间" prop="payTime">
+        <el-date-picker
+          v-model="queryParams.payTime"
+          value-format="YYYY-MM-DD HH:mm:ss"
+          type="daterange"
+          start-placeholder="开始日期"
+          end-placeholder="结束日期"
+          :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
+          class="!w-240px"
+        />
+      </el-form-item>
+      <el-form-item>
+        <el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
+        <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
+          <el-button v-hasPermi="['menduner:system:trade-order:create']" type="primary" plain @click="openForm('create')"><Icon icon="ep:plus" />发起订单</el-button>
+      </el-form-item>
+    </el-form>
+  </ContentWrap>
+
+  <!-- 列表 -->
+  <ContentWrap>
+    <el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
+      <el-table-column label="编号" align="center" prop="id" />
+      <el-table-column label="用户编号" align="center" prop="userId" />
+      <el-table-column label="商品名字" align="center" prop="spuName" />
+      <el-table-column label="价格" align="center" prop="price" />
+      <el-table-column label="是否已支付" align="center" prop="payStatus">
+        <template #default="scope">
+          {{ scope.row.payStatus ? '已支付' : '未支付' }}
+        </template>
+      </el-table-column>
+      <el-table-column label="支付订单编号" align="center" prop="payOrderId" />
+      <el-table-column label="支付渠道" align="center" prop="payChannelCode" />
+      <el-table-column label="订单支付时间" align="center" prop="payTime" :formatter="dateFormatter" width="180px"/>
+      <el-table-column label="退款订单编号" align="center" prop="payRefundId" />
+      <el-table-column label="退款金额" align="center" prop="refundPrice" />
+      <el-table-column label="退款时间" align="center" prop="refundTime" :formatter="dateFormatter" width="180px"/>
+      <el-table-column label="创建时间" align="center" prop="createTime" :formatter="dateFormatter" width="180px"/>
+      <el-table-column label="操作" align="center">
+        <template #default="scope">
+          <el-button link type="primary" @click="handlePay(scope.row)" v-if="!scope.row.payStatus">
+            前往支付
+          </el-button>
+          <el-button
+            link
+            type="danger"
+            @click="handleRefund(scope.row)"
+            v-if="scope.row.payStatus && !scope.row.payRefundId"
+          >
+            发起退款
+          </el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+    <!-- 分页 -->
+    <Pagination
+      :total="total"
+      v-model:page="queryParams.pageNo"
+      v-model:limit="queryParams.pageSize"
+      @pagination="getList"
+    />
+  </ContentWrap>
+
+  <!-- 表单弹窗:添加/修改 -->
+  <TradeOrderForm ref="formRef" @success="getList" />
+</template>
+
+<script setup lang="ts">
+import { dateFormatter } from '@/utils/formatTime'
+import { TradeOrderApi, TradeOrderVO } from '@/api/menduner/system/order'
+import TradeOrderForm from './TradeOrderForm.vue'
+
+/** 交易订单	 列表 */
+defineOptions({ name: 'TradeOrder' })
+
+const message = useMessage() // 消息弹窗
+const router = useRouter()
+const loading = ref(true) // 列表的加载中
+const list = ref<TradeOrderVO[]>([]) // 列表的数据
+const total = ref(0) // 列表的总页数
+const queryParams = reactive({
+  pageNo: 1,
+  pageSize: 10,
+  userId: undefined,
+  type: undefined,
+  terminal: undefined,
+  userIp: undefined,
+  status: undefined,
+  spuId: undefined,
+  spuName: undefined,
+  price: undefined,
+  payStatus: undefined,
+  payOrderId: undefined,
+  payChannelCode: undefined,
+  payTime: [],
+  refundStatus: undefined,
+  payRefundId: undefined,
+  refundPrice: undefined,
+  refundTime: [],
+  createTime: []
+})
+const queryFormRef = ref() // 搜索的表单
+
+/** 查询列表 */
+const getList = async () => {
+  loading.value = true
+  try {
+    const data = await TradeOrderApi.getTradeOrderPage(queryParams)
+    list.value = data.list
+    total.value = data.total
+  } finally {
+    loading.value = false
+  }
+}
+
+/** 搜索按钮操作 */
+const handleQuery = () => {
+  queryParams.pageNo = 1
+  getList()
+}
+
+/** 重置按钮操作 */
+const resetQuery = () => {
+  queryFormRef.value.resetFields()
+  handleQuery()
+}
+
+/** 添加/修改操作 */
+const formRef = ref()
+const openForm = (type: string, id?: number) => {
+  formRef.value.open(type, id)
+}
+
+/** 支付按钮操作 */
+const handlePay = (row: any) => {
+  router.push({
+    name: 'PayCashierOrder',
+    query: {
+      id: row.payOrderId,
+      returnUrl: encodeURIComponent('/pay/demo/order?id=' + row.id)
+    }
+  })
+}
+
+/** 退款按钮操作 */
+const handleRefund = async (row: any) => {
+  const id = row.id
+  try {
+    await message.confirm('是否确认退款编号为"' + id + '"的示例订单?')
+    await TradeOrderApi.updateTradeOrder(id)
+    await getList()
+    message.success('发起退款成功!')
+  } catch {}
+}
+
+/** 初始化 **/
+onMounted(() => {
+  getList()
+})
+</script>

+ 475 - 0
src/views/menduner/system/order/pay.vue

@@ -0,0 +1,475 @@
+<template>
+  <!-- 支付信息 -->
+  <el-card v-loading="loading">
+    <el-descriptions title="支付信息" :column="3" border>
+      <el-descriptions-item label="支付单号">{{ payOrder.id }}</el-descriptions-item>
+      <el-descriptions-item label="商品标题">{{ payOrder.subject }}</el-descriptions-item>
+      <el-descriptions-item label="商品内容">{{ payOrder.body }}</el-descriptions-item>
+      <el-descriptions-item label="支付金额">
+        ¥{{ (payOrder.price / 100.0).toFixed(2) }}
+      </el-descriptions-item>
+      <el-descriptions-item label="创建时间">
+        {{ formatDate(payOrder.createTime) }}
+      </el-descriptions-item>
+      <el-descriptions-item label="过期时间">
+        {{ formatDate(payOrder.expireTime) }}
+      </el-descriptions-item>
+    </el-descriptions>
+  </el-card>
+
+  <!-- 支付选择框 -->
+  <el-card style="margin-top: 10px" v-loading="submitLoading" element-loading-text="提交支付中...">
+    <!-- 支付宝 -->
+    <el-descriptions title="选择支付宝支付" />
+    <div class="pay-channel-container">
+      <div
+        class="box"
+        v-for="channel in channelsAlipay"
+        :key="channel.code"
+        @click="submit(channel.code)"
+      >
+        <img :src="channel.icon" />
+        <div class="title">{{ channel.name }}</div>
+      </div>
+    </div>
+    <!-- 微信支付 -->
+    <el-descriptions title="选择微信支付" style="margin-top: 20px" />
+    <div class="pay-channel-container">
+      <div
+        class="box"
+        v-for="channel in channelsWechat"
+        :key="channel.code"
+        @click="submit(channel.code)"
+      >
+        <img :src="channel.icon" />
+        <div class="title">{{ channel.name }}</div>
+      </div>
+    </div>
+    <!-- 其它支付 -->
+    <el-descriptions title="选择其它支付" style="margin-top: 20px" />
+    <div class="pay-channel-container">
+      <div
+        class="box"
+        v-for="channel in channelsMock"
+        :key="channel.code"
+        @click="submit(channel.code)"
+      >
+        <img :src="channel.icon" />
+        <div class="title">{{ channel.name }}</div>
+      </div>
+    </div>
+  </el-card>
+
+  <!-- 展示形式:二维码 URL -->
+  <Dialog
+    :title="qrCode.title"
+    v-model="qrCode.visible"
+    width="350px"
+    append-to-body
+    :close-on-press-escape="false"
+  >
+    <Qrcode :text="qrCode.url" :width="310" />
+  </Dialog>
+
+  <!-- 展示形式:BarCode 条形码 -->
+  <Dialog
+    :title="barCode.title"
+    v-model="barCode.visible"
+    width="500px"
+    append-to-body
+    :close-on-press-escape="false"
+  >
+    <el-form ref="form" label-width="80px">
+      <el-row>
+        <el-col :span="24">
+          <el-form-item label="条形码" prop="name">
+            <el-input v-model="barCode.value" placeholder="请输入条形码" required />
+          </el-form-item>
+        </el-col>
+        <el-col :span="24">
+          <div style="text-align: right">
+            或使用
+            <el-link
+              type="danger"
+              target="_blank"
+              href="https://baike.baidu.com/item/条码支付/10711903"
+            >
+              (扫码枪/扫码盒)
+            </el-link>
+            扫码
+          </div>
+        </el-col>
+      </el-row>
+    </el-form>
+    <template #footer>
+      <el-button
+        type="primary"
+        @click="submit0(barCode.channelCode)"
+        :disabled="barCode.value.length === 0"
+      >
+        确认支付
+      </el-button>
+      <el-button @click="barCode.visible = false">取 消</el-button>
+    </template>
+  </Dialog>
+</template>
+
+<script lang="ts" setup>
+import { Qrcode } from '@/components/Qrcode'
+import * as PayOrderApi from '@/api/pay/order'
+import { PayChannelEnum, PayDisplayModeEnum, PayOrderStatusEnum } from '@/utils/constants'
+import { formatDate } from '@/utils/formatTime'
+import { useTagsViewStore } from '@/store/modules/tagsView'
+
+// 导入图标
+import svg_alipay_pc from '@/assets/svgs/pay/icon/alipay_pc.svg'
+import svg_alipay_wap from '@/assets/svgs/pay/icon/alipay_wap.svg'
+import svg_alipay_app from '@/assets/svgs/pay/icon/alipay_app.svg'
+import svg_alipay_qr from '@/assets/svgs/pay/icon/alipay_qr.svg'
+import svg_alipay_bar from '@/assets/svgs/pay/icon/alipay_bar.svg'
+import svg_wx_pub from '@/assets/svgs/pay/icon/wx_pub.svg'
+import svg_wx_lite from '@/assets/svgs/pay/icon/wx_lite.svg'
+import svg_wx_app from '@/assets/svgs/pay/icon/wx_app.svg'
+import svg_wx_native from '@/assets/svgs/pay/icon/wx_native.svg'
+import svg_wx_bar from '@/assets/svgs/pay/icon/wx_bar.svg'
+import svg_mock from '@/assets/svgs/pay/icon/mock.svg'
+
+defineOptions({ name: 'PayCashierOrder' })
+
+const message = useMessage() // 消息弹窗
+const route = useRoute() // 路由
+const { currentRoute, replace } = useRouter() // 路由 push
+const { delView } = useTagsViewStore() // 视图操作
+
+const id = ref(undefined) // 支付单号
+const returnUrl = ref<string | undefined>(undefined) // 支付完的回调地址
+const loading = ref(false) // 支付信息的 loading
+const payOrder = ref({}) // 支付信息
+const channelsAlipay = [
+  {
+    name: '支付宝 PC 网站支付',
+    icon: svg_alipay_pc,
+    code: 'alipay_pc'
+  },
+  {
+    name: '支付宝 Wap 网站支付',
+    icon: svg_alipay_wap,
+    code: 'alipay_wap'
+  },
+  {
+    name: '支付宝 App 网站支付',
+    icon: svg_alipay_app,
+    code: 'alipay_app'
+  },
+  {
+    name: '支付宝扫码支付',
+    icon: svg_alipay_qr,
+    code: 'alipay_qr'
+  },
+  {
+    name: '支付宝条码支付',
+    icon: svg_alipay_bar,
+    code: 'alipay_bar'
+  }
+]
+const channelsWechat = [
+  {
+    name: '微信公众号支付',
+    icon: svg_wx_pub,
+    code: 'wx_pub'
+  },
+  {
+    name: '微信小程序支付',
+    icon: svg_wx_lite,
+    code: 'wx_lite'
+  },
+  {
+    name: '微信 App 支付',
+    icon: svg_wx_app,
+    code: 'wx_app'
+  },
+  {
+    name: '微信扫码支付',
+    icon: svg_wx_native,
+    code: 'wx_native'
+  },
+  {
+    name: '微信条码支付',
+    icon: svg_wx_bar,
+    code: 'wx_bar'
+  }
+]
+const channelsMock = [
+  {
+    name: '钱包支付',
+    icon: svg_mock,
+    code: 'wallet'
+  },
+  {
+    name: '模拟支付',
+    icon: svg_mock,
+    code: 'mock'
+  }
+]
+
+const submitLoading = ref(false) // 提交支付的 loading
+const interval = ref<any>(undefined) // 定时任务,轮询是否完成支付
+const qrCode = ref({
+  // 展示形式:二维码
+  url: '',
+  title: '',
+  visible: false
+})
+const barCode = ref({
+  // 展示形式:条形码
+  channelCode: '',
+  value: '',
+  title: '',
+  visible: false
+})
+
+/** 获得支付信息 */
+const getDetail = async () => {
+  // 1.1 未传递订单编号
+  if (!id.value) {
+    message.error('未传递支付单号,无法查看对应的支付信息')
+    goReturnUrl()
+    return
+  }
+  const data = await PayOrderApi.getOrder(id.value)
+  payOrder.value = data
+  // 1.2 无法查询到支付信息
+  if (!data) {
+    message.error('支付订单不存在,请检查!')
+    goReturnUrl()
+    return
+  }
+  // 1.3 如果已支付、或者已关闭,则直接跳转
+  if (data.status === PayOrderStatusEnum.SUCCESS.status) {
+    message.success('支付成功')
+    goReturnUrl()
+    return
+  } else if (data.status === PayOrderStatusEnum.CLOSED.status) {
+    message.error('无法支付,原因:订单已关闭')
+    goReturnUrl()
+    return
+  }
+}
+
+/** 提交支付 */
+const submit = (channelCode) => {
+  // 条形码支付,需要特殊处理
+  if (channelCode === PayChannelEnum.ALIPAY_BAR.code) {
+    barCode.value = {
+      channelCode: channelCode,
+      value: '',
+      title: '“支付宝”条码支付',
+      visible: true
+    }
+    return
+  }
+  if (channelCode === PayChannelEnum.WX_BAR.code) {
+    barCode.value = {
+      channelCode: channelCode,
+      value: '',
+      title: '“微信”条码支付',
+      visible: true
+    }
+    return
+  }
+
+  // 微信公众号、小程序支付,无法在 PC 网页中进行
+  if (channelCode === PayChannelEnum.WX_PUB.code) {
+    message.error('微信公众号支付:不支持 PC 网站')
+    return
+  }
+  if (channelCode === PayChannelEnum.WX_LITE.code) {
+    message.error('微信小程序:不支持 PC 网站')
+    return
+  }
+
+  // 默认的提交处理
+  submit0(channelCode)
+}
+
+const submit0 = async (channelCode) => {
+  submitLoading.value = true
+  try {
+    const formData = {
+      id: id.value,
+      channelCode: channelCode,
+      returnUrl: location.href, // 支付成功后,支付渠道跳转回当前页;再由当前页,跳转回 {@link returnUrl} 对应的地址
+      ...buildSubmitParam(channelCode)
+    }
+    const data = await PayOrderApi.submitOrder(formData)
+    // 直接返回已支付的情况,例如说扫码支付
+    if (data.status === PayOrderStatusEnum.SUCCESS.status) {
+      clearQueryInterval()
+      message.success('支付成功!')
+      goReturnUrl()
+      return
+    }
+
+    // 展示对应的界面
+    if (data.displayMode === PayDisplayModeEnum.URL.mode) {
+      displayUrl(channelCode, data)
+    } else if (data.displayMode === PayDisplayModeEnum.QR_CODE.mode) {
+      displayQrCode(channelCode, data)
+    } else if (data.displayMode === PayDisplayModeEnum.APP.mode) {
+      displayApp(channelCode)
+    }
+
+    // 打开轮询任务
+    createQueryInterval()
+  } finally {
+    submitLoading.value = false
+  }
+}
+
+/** 构建提交支付的额外参数 */
+const buildSubmitParam = (channelCode) => {
+  // ① 支付宝 BarCode 支付时,需要传递 authCode 条形码
+  if (channelCode === PayChannelEnum.ALIPAY_BAR.code) {
+    return {
+      channelExtras: {
+        auth_code: barCode.value.value
+      }
+    }
+  }
+  // ② 微信 BarCode 支付时,需要传递 authCode 条形码
+  if (channelCode === PayChannelEnum.WX_BAR.code) {
+    return {
+      channelExtras: {
+        authCode: barCode.value.value
+      }
+    }
+  }
+  return {}
+}
+
+/** 提交支付后,URL 的展示形式 */
+const displayUrl = (_channelCode, data) => {
+  location.href = data.displayContent
+  submitLoading.value = false
+}
+
+/** 提交支付后(扫码支付) */
+const displayQrCode = (channelCode, data) => {
+  let title = '请使用手机浏览器“扫一扫”'
+  if (channelCode === PayChannelEnum.ALIPAY_WAP.code) {
+    // 考虑到 WAP 测试,所以引导手机浏览器搞
+  } else if (channelCode.indexOf('alipay_') === 0) {
+    title = '请使用支付宝“扫一扫”扫码支付'
+  } else if (channelCode.indexOf('wx_') === 0) {
+    title = '请使用微信“扫一扫”扫码支付'
+  }
+  qrCode.value = {
+    title: title,
+    url: data.displayContent,
+    visible: true
+  }
+  submitLoading.value = false
+}
+
+/** 提交支付后(App) */
+const displayApp = (channelCode) => {
+  if (channelCode === PayChannelEnum.ALIPAY_APP.code) {
+    message.error('支付宝 App 支付:无法在网页支付!')
+  }
+  if (channelCode === PayChannelEnum.WX_APP.code) {
+    message.error('微信 App 支付:无法在网页支付!')
+  }
+  submitLoading.value = false
+}
+
+/** 轮询查询任务 */
+const createQueryInterval = () => {
+  if (interval.value) {
+    return
+  }
+  interval.value = setInterval(async () => {
+    const data = await PayOrderApi.getOrder(id.value)
+    // 已支付
+    if (data.status === PayOrderStatusEnum.SUCCESS.status) {
+      clearQueryInterval()
+      message.success('支付成功!')
+      goReturnUrl()
+    }
+    // 已取消
+    if (data.status === PayOrderStatusEnum.CLOSED.status) {
+      clearQueryInterval()
+      message.error('支付已关闭!')
+      goReturnUrl()
+    }
+  }, 1000 * 2)
+}
+
+/** 清空查询任务 */
+const clearQueryInterval = () => {
+  // 清空各种弹窗
+  qrCode.value = {
+    title: '',
+    url: '',
+    visible: false
+  }
+  // 清空任务
+  clearInterval(interval.value)
+  interval.value = undefined
+}
+
+const goReturnUrl = () => {
+  // 清理任务
+  clearQueryInterval()
+
+  // 未配置的情况下,只能关闭
+  if (!returnUrl.value) {
+    delView(unref(currentRoute))
+    return
+  }
+
+  // 如果有配置,且是 http 开头,则浏览器跳转
+  delView(unref(currentRoute))
+  
+  replace({
+    path: '/menduner/trade-order',
+    query: {
+      reload: new Date().getTime()
+    }
+  })
+}
+
+/** 初始化 */
+onMounted(() => {
+  id.value = route.query.id
+  if (route.query.returnUrl) {
+    returnUrl.value = decodeURIComponent(route.query.returnUrl)
+  }
+  getDetail()
+})
+</script>
+
+<style lang="scss" scoped>
+.pay-channel-container {
+  display: flex;
+  margin-top: -10px;
+
+  .box {
+    width: 160px;
+    padding-top: 10px;
+    padding-bottom: 5px;
+    margin-right: 10px;
+    text-align: center;
+    cursor: pointer;
+    border: 1px solid #e6ebf5;
+
+    img {
+      width: 40px;
+      height: 40px;
+    }
+
+    .title {
+      padding-top: 5px;
+    }
+  }
+}
+</style>

+ 5 - 0
src/views/pay/cashier/index.vue

@@ -200,6 +200,11 @@ const channelsWechat = [
   }
 ]
 const channelsMock = [
+  {
+    name: '模拟支付',
+    icon: svg_mock,
+    code: 'mock'
+  },
   {
     name: '模拟支付',
     icon: svg_mock,