|
@@ -0,0 +1,701 @@
|
|
|
|
|
+<template>
|
|
|
|
|
+ <!-- 单根节点(含下单/详情弹层):AppMain 的 <transition mode="out-in"> 要求组件根为元素 -->
|
|
|
|
|
+ <div class="har-order">
|
|
|
|
|
+ <el-card>
|
|
|
|
|
+ <template #header>
|
|
|
|
|
+ <div class="card-header">
|
|
|
|
|
+ <span>订单管理</span>
|
|
|
|
|
+ <el-button
|
|
|
|
|
+ v-hasPerm="['module_payment:har:order:create']"
|
|
|
|
|
+ type="primary"
|
|
|
|
|
+ icon="Plus"
|
|
|
|
|
+ @click="openCreate"
|
|
|
|
|
+ >
|
|
|
|
|
+ 创建订单
|
|
|
|
|
+ </el-button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </template>
|
|
|
|
|
+
|
|
|
|
|
+ <div class="mb-4">
|
|
|
|
|
+ <el-form :inline="true" :model="searchForm">
|
|
|
|
|
+ <el-form-item label="企业">
|
|
|
|
|
+ <el-select
|
|
|
|
|
+ v-model="searchForm.enterprise_id"
|
|
|
|
|
+ placeholder="全部企业"
|
|
|
|
|
+ clearable
|
|
|
|
|
+ filterable
|
|
|
|
|
+ style="width: 190px"
|
|
|
|
|
+ >
|
|
|
|
|
+ <el-option
|
|
|
|
|
+ v-for="e in enterpriseStore.getEnterpriseList"
|
|
|
|
|
+ :key="e.enterprise_id"
|
|
|
|
|
+ :label="e.name || e.enterprise_id"
|
|
|
|
|
+ :value="e.enterprise_id"
|
|
|
|
|
+ />
|
|
|
|
|
+ </el-select>
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ <el-form-item label="商户编号">
|
|
|
|
|
+ <el-input
|
|
|
|
|
+ v-model="searchForm.merchant_code"
|
|
|
|
|
+ placeholder="商户编号"
|
|
|
|
|
+ clearable
|
|
|
|
|
+ style="width: 170px"
|
|
|
|
|
+ @keyup.enter="handleSearch"
|
|
|
|
|
+ />
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ <el-form-item label="订单号">
|
|
|
|
|
+ <el-input
|
|
|
|
|
+ v-model="searchForm.order_no"
|
|
|
|
|
+ placeholder="我方订单号"
|
|
|
|
|
+ clearable
|
|
|
|
|
+ style="width: 180px"
|
|
|
|
|
+ @keyup.enter="handleSearch"
|
|
|
|
|
+ />
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ <el-form-item label="商户订单号">
|
|
|
|
|
+ <el-input
|
|
|
|
|
+ v-model="searchForm.out_biz_no"
|
|
|
|
|
+ placeholder="企业自己的订单号"
|
|
|
|
|
+ clearable
|
|
|
|
|
+ style="width: 180px"
|
|
|
|
|
+ @keyup.enter="handleSearch"
|
|
|
|
|
+ />
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ <el-form-item label="交易状态">
|
|
|
|
|
+ <el-select
|
|
|
|
|
+ v-model="searchForm.trade_status"
|
|
|
|
|
+ placeholder="全部"
|
|
|
|
|
+ clearable
|
|
|
|
|
+ style="width: 140px"
|
|
|
|
|
+ >
|
|
|
|
|
+ <el-option
|
|
|
|
|
+ v-for="(text, value) in HAR_TRADE_STATUS_TEXT"
|
|
|
|
|
+ :key="value"
|
|
|
|
|
+ :label="text"
|
|
|
|
|
+ :value="value"
|
|
|
|
|
+ />
|
|
|
|
|
+ </el-select>
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ <el-form-item label="交易方式">
|
|
|
|
|
+ <el-select
|
|
|
|
|
+ v-model="searchForm.trade_type"
|
|
|
|
|
+ placeholder="全部"
|
|
|
|
|
+ clearable
|
|
|
|
|
+ style="width: 140px"
|
|
|
|
|
+ >
|
|
|
|
|
+ <el-option
|
|
|
|
|
+ v-for="t in HAR_TRADE_TYPE_OPTIONS"
|
|
|
|
|
+ :key="t.value"
|
|
|
|
|
+ :label="t.label"
|
|
|
|
|
+ :value="t.value"
|
|
|
|
|
+ />
|
|
|
|
|
+ </el-select>
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ <el-form-item label="创建日期">
|
|
|
|
|
+ <el-date-picker
|
|
|
|
|
+ v-model="dateRange"
|
|
|
|
|
+ type="daterange"
|
|
|
|
|
+ range-separator="至"
|
|
|
|
|
+ start-placeholder="开始日期"
|
|
|
|
|
+ end-placeholder="结束日期"
|
|
|
|
|
+ value-format="YYYY-MM-DD"
|
|
|
|
|
+ style="width: 250px"
|
|
|
|
|
+ @change="handleDateChange"
|
|
|
|
|
+ />
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ <el-form-item>
|
|
|
|
|
+ <el-button type="primary" @click="handleSearch">查询</el-button>
|
|
|
|
|
+ <el-button @click="handleReset">重置</el-button>
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ </el-form>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <el-table v-loading="loading" :data="list" border stripe>
|
|
|
|
|
+ <template #empty>
|
|
|
|
|
+ <el-empty description="暂无数据" />
|
|
|
|
|
+ </template>
|
|
|
|
|
+ <el-table-column prop="order_no" label="订单号" min-width="190" show-overflow-tooltip />
|
|
|
|
|
+ <el-table-column prop="out_biz_no" label="商户订单号" min-width="160" show-overflow-tooltip>
|
|
|
|
|
+ <template #default="{ row }">{{ row.out_biz_no || "-" }}</template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ <el-table-column prop="title" label="商品名称" min-width="140" show-overflow-tooltip>
|
|
|
|
|
+ <template #default="{ row }">{{ row.title || "-" }}</template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ <el-table-column label="金额(元)" width="110">
|
|
|
|
|
+ <template #default="{ row }">¥{{ row.price }}</template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ <el-table-column label="交易状态" width="100">
|
|
|
|
|
+ <template #default="{ row }">
|
|
|
|
|
+ <el-tag :type="HAR_TRADE_STATUS_TAG[row.trade_status] || 'info'">
|
|
|
|
|
+ {{ HAR_TRADE_STATUS_TEXT[row.trade_status] || row.trade_status }}
|
|
|
|
|
+ </el-tag>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ <el-table-column label="交易方式" width="110">
|
|
|
|
|
+ <template #default="{ row }">{{ tradeTypeText(row.trade_type) }}</template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ <el-table-column prop="merchant_code" label="商户编号" min-width="140">
|
|
|
|
|
+ <template #default="{ row }">{{ row.merchant_code || "-" }}</template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ <el-table-column prop="third_shop_code" label="门店编码" min-width="130">
|
|
|
|
|
+ <template #default="{ row }">{{ row.third_shop_code || "-" }}</template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ <el-table-column label="交易时间" width="170">
|
|
|
|
|
+ <template #default="{ row }">
|
|
|
|
|
+ {{ row.trade_time ? dayjs(row.trade_time).format("YYYY-MM-DD HH:mm:ss") : "-" }}
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ <el-table-column label="操作" width="150" fixed="right">
|
|
|
|
|
+ <template #default="{ row }">
|
|
|
|
|
+ <el-button
|
|
|
|
|
+ v-hasPerm="['module_payment:har:order:detail']"
|
|
|
|
|
+ size="small"
|
|
|
|
|
+ @click="openDetail(row)"
|
|
|
|
|
+ >
|
|
|
|
|
+ 详情
|
|
|
|
|
+ </el-button>
|
|
|
|
|
+ <el-button
|
|
|
|
|
+ v-hasPerm="['module_payment:har:order:sync']"
|
|
|
|
|
+ size="small"
|
|
|
|
|
+ type="primary"
|
|
|
|
|
+ :loading="syncingId === row.id"
|
|
|
|
|
+ @click="handleSync(row)"
|
|
|
|
|
+ >
|
|
|
|
|
+ 同步
|
|
|
|
|
+ </el-button>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ </el-table>
|
|
|
|
|
+
|
|
|
|
|
+ <div class="mt-4 flex justify-end">
|
|
|
|
|
+ <el-pagination
|
|
|
|
|
+ v-model:current-page="pageNo"
|
|
|
|
|
+ v-model:page-size="pageSize"
|
|
|
|
|
+ :total="total"
|
|
|
|
|
+ :page-sizes="[10, 20, 50, 100]"
|
|
|
|
|
+ layout="total, sizes, prev, pager, next, jumper"
|
|
|
|
|
+ @size-change="load"
|
|
|
|
|
+ @current-change="load"
|
|
|
|
|
+ />
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </el-card>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 创建订单 -->
|
|
|
|
|
+ <el-dialog v-model="createVisible" title="创建订单" width="640px" @closed="handleCreateClosed">
|
|
|
|
|
+ <el-alert
|
|
|
|
|
+ class="mb-4"
|
|
|
|
|
+ type="info"
|
|
|
|
|
+ :closable="false"
|
|
|
|
|
+ show-icon
|
|
|
|
|
+ title="填写付款码即为被扫收款(用户出示付款码);留空则生成收款链接,由用户扫码支付。"
|
|
|
|
|
+ />
|
|
|
|
|
+ <el-form ref="formRef" :model="form" :rules="rules" label-width="120px">
|
|
|
|
|
+ <el-form-item label="商户" prop="merchant_code">
|
|
|
|
|
+ <!-- 可清空 + 可输入:本租户配了多个商户时这里就是「钱进哪一家」的开关;
|
|
|
|
|
+ 列表拉不到时(未授权/接口异常)仍可手工敲编号,归属由后端按本租户校验 -->
|
|
|
|
|
+ <el-select
|
|
|
|
|
+ v-model="form.merchant_code"
|
|
|
|
|
+ placeholder="留空即按企业与租户自动匹配"
|
|
|
|
|
+ clearable
|
|
|
|
|
+ filterable
|
|
|
|
|
+ allow-create
|
|
|
|
|
+ default-first-option
|
|
|
|
|
+ style="width: 100%"
|
|
|
|
|
+ >
|
|
|
|
|
+ <el-option
|
|
|
|
|
+ v-for="m in merchantOptions"
|
|
|
|
|
+ :key="m.merchant_code"
|
|
|
|
|
+ :label="merchantLabel(m)"
|
|
|
|
|
+ :value="m.merchant_code"
|
|
|
|
|
+ />
|
|
|
|
|
+ </el-select>
|
|
|
|
|
+ <div v-if="merchantOptions.length > 1" class="form-tip">
|
|
|
|
|
+ 本租户配置了多个商户,请选择这笔款项收进哪一个商户
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ <el-form-item label="企业" prop="enterprise_id">
|
|
|
|
|
+ <el-select
|
|
|
|
|
+ v-model="form.enterprise_id"
|
|
|
|
|
+ placeholder="留空即按租户下单"
|
|
|
|
|
+ clearable
|
|
|
|
|
+ filterable
|
|
|
|
|
+ style="width: 100%"
|
|
|
|
|
+ >
|
|
|
|
|
+ <el-option
|
|
|
|
|
+ v-for="e in enterpriseStore.getEnterpriseList"
|
|
|
|
|
+ :key="e.enterprise_id"
|
|
|
|
|
+ :label="e.name || e.enterprise_id"
|
|
|
|
|
+ :value="e.enterprise_id"
|
|
|
|
|
+ />
|
|
|
|
|
+ </el-select>
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ <el-form-item label="商户订单号" prop="out_biz_no">
|
|
|
|
|
+ <el-input
|
|
|
|
|
+ v-model="form.out_biz_no"
|
|
|
|
|
+ placeholder="企业自己的订单号,便于对账,最长 64 位"
|
|
|
|
|
+ maxlength="64"
|
|
|
|
|
+ show-word-limit
|
|
|
|
|
+ />
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ <el-form-item label="金额(元)" prop="price">
|
|
|
|
|
+ <el-input-number
|
|
|
|
|
+ v-model="form.price"
|
|
|
|
|
+ :min="0.01"
|
|
|
|
|
+ :precision="2"
|
|
|
|
|
+ :step="1"
|
|
|
|
|
+ controls-position="right"
|
|
|
|
|
+ style="width: 200px"
|
|
|
|
|
+ />
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ <el-form-item label="商品名称" prop="title">
|
|
|
|
|
+ <el-input v-model="form.title" maxlength="64" show-word-limit />
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ <el-form-item label="交易方式" prop="trade_type">
|
|
|
|
|
+ <el-select v-model="form.trade_type" style="width: 100%" @change="handleTradeTypeChange">
|
|
|
|
|
+ <el-option
|
|
|
|
|
+ v-for="t in HAR_TRADE_TYPE_OPTIONS"
|
|
|
|
|
+ :key="t.value"
|
|
|
|
|
+ :label="t.label"
|
|
|
|
|
+ :value="t.value"
|
|
|
|
|
+ />
|
|
|
|
|
+ </el-select>
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+
|
|
|
|
|
+ <template v-if="form.trade_type === 'alipay_stage'">
|
|
|
|
|
+ <el-form-item label="分期数" prop="stage_num">
|
|
|
|
|
+ <el-select v-model="form.stage_num" placeholder="请选择分期数" style="width: 100%">
|
|
|
|
|
+ <el-option v-for="n in STAGE_NUM_OPTIONS" :key="n" :label="`${n} 期`" :value="n" />
|
|
|
|
|
+ </el-select>
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ <el-form-item label="手续费承担方" prop="percent">
|
|
|
|
|
+ <el-select v-model="form.percent" style="width: 100%">
|
|
|
|
|
+ <el-option label="商户承担" value="100" />
|
|
|
|
|
+ <el-option label="用户承担" value="0" />
|
|
|
|
|
+ </el-select>
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ </template>
|
|
|
|
|
+
|
|
|
|
|
+ <el-form-item label="门店编码" prop="third_shop_code">
|
|
|
|
|
+ <el-input
|
|
|
|
|
+ v-model="form.third_shop_code"
|
|
|
|
|
+ placeholder="商户侧门店编码,最长 32 位"
|
|
|
|
|
+ maxlength="32"
|
|
|
|
|
+ show-word-limit
|
|
|
|
|
+ />
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ <el-form-item label="平台门店编号" prop="shop_code">
|
|
|
|
|
+ <el-input v-model="form.shop_code" placeholder="上游返回的平台门店编号" />
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ <el-form-item label="付款码" prop="auth_code">
|
|
|
|
|
+ <el-input
|
|
|
|
|
+ v-model="form.auth_code"
|
|
|
|
|
+ placeholder="扫用户付款码得到,填写即走被扫收款"
|
|
|
|
|
+ clearable
|
|
|
|
|
+ />
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ <el-form-item label="支付超时" prop="pay_timeout">
|
|
|
|
|
+ <el-input-number
|
|
|
|
|
+ v-model="form.pay_timeout"
|
|
|
|
|
+ :min="2"
|
|
|
|
|
+ :max="60"
|
|
|
|
|
+ controls-position="right"
|
|
|
|
|
+ style="width: 200px"
|
|
|
|
|
+ />
|
|
|
|
|
+ <span class="ml-2 text-gray-400">分钟,仅主扫下单有效</span>
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ <el-form-item label="扩展参数" prop="extra_params">
|
|
|
|
|
+ <el-input
|
|
|
|
|
+ v-model="form.extra_params"
|
|
|
|
|
+ type="textarea"
|
|
|
|
|
+ :rows="2"
|
|
|
|
|
+ placeholder="上游透传字段,原样提交"
|
|
|
|
|
+ />
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ </el-form>
|
|
|
|
|
+ <template #footer>
|
|
|
|
|
+ <el-button @click="createVisible = false">取消</el-button>
|
|
|
|
|
+ <el-button type="primary" :loading="saving" @click="handleCreate">提交</el-button>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-dialog>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 订单详情 -->
|
|
|
|
|
+ <el-dialog v-model="detailVisible" title="订单详情" width="720px">
|
|
|
|
|
+ <el-descriptions v-if="detail" :column="2" border>
|
|
|
|
|
+ <el-descriptions-item label="订单号" :span="2">{{ detail.order_no }}</el-descriptions-item>
|
|
|
|
|
+ <el-descriptions-item label="商户订单号">
|
|
|
|
|
+ {{ detail.out_biz_no || "-" }}
|
|
|
|
|
+ </el-descriptions-item>
|
|
|
|
|
+ <el-descriptions-item label="交易状态">
|
|
|
|
|
+ <el-tag :type="HAR_TRADE_STATUS_TAG[detail.trade_status] || 'info'">
|
|
|
|
|
+ {{ HAR_TRADE_STATUS_TEXT[detail.trade_status] || detail.trade_status }}
|
|
|
|
|
+ </el-tag>
|
|
|
|
|
+ </el-descriptions-item>
|
|
|
|
|
+ <el-descriptions-item label="商品名称">{{ detail.title || "-" }}</el-descriptions-item>
|
|
|
|
|
+ <el-descriptions-item label="交易方式">
|
|
|
|
|
+ {{ tradeTypeText(detail.trade_type) }}
|
|
|
|
|
+ </el-descriptions-item>
|
|
|
|
|
+ <el-descriptions-item label="订单金额">¥{{ detail.price }}</el-descriptions-item>
|
|
|
|
|
+ <el-descriptions-item label="实收金额">
|
|
|
|
|
+ {{ detail.rcv_amount != null ? `¥${detail.rcv_amount}` : "-" }}
|
|
|
|
|
+ </el-descriptions-item>
|
|
|
|
|
+ <el-descriptions-item label="手续费">
|
|
|
|
|
+ {{ detail.fee_amount != null ? `¥${detail.fee_amount}` : "-" }}
|
|
|
|
|
+ </el-descriptions-item>
|
|
|
|
|
+ <el-descriptions-item label="渠道成本">
|
|
|
|
|
+ {{ detail.channel_amount != null ? `¥${detail.channel_amount}` : "-" }}
|
|
|
|
|
+ </el-descriptions-item>
|
|
|
|
|
+ <el-descriptions-item label="商户编号">
|
|
|
|
|
+ {{ detail.merchant_code || "-" }}
|
|
|
|
|
+ </el-descriptions-item>
|
|
|
|
|
+ <el-descriptions-item label="门店编码">
|
|
|
|
|
+ {{ detail.third_shop_code || "-" }}
|
|
|
|
|
+ </el-descriptions-item>
|
|
|
|
|
+ <el-descriptions-item label="平台门店编号">
|
|
|
|
|
+ {{ detail.shop_code || "-" }}
|
|
|
|
|
+ </el-descriptions-item>
|
|
|
|
|
+ <el-descriptions-item label="上游订单号" :span="2">
|
|
|
|
|
+ {{ detail.upstream_order_no || "-" }}
|
|
|
|
|
+ </el-descriptions-item>
|
|
|
|
|
+ <el-descriptions-item label="支付宝交易号" :span="2">
|
|
|
|
|
+ {{ detail.platform_trade_no || "-" }}
|
|
|
|
|
+ </el-descriptions-item>
|
|
|
|
|
+ <el-descriptions-item v-if="detail.trade_type === 'alipay_stage'" label="分期数">
|
|
|
|
|
+ {{ detail.stage_num ? `${detail.stage_num} 期` : "-" }}
|
|
|
|
|
+ </el-descriptions-item>
|
|
|
|
|
+ <el-descriptions-item v-if="detail.trade_type === 'alipay_stage'" label="手续费承担方">
|
|
|
|
|
+ {{ detail.percent === "100" ? "商户承担" : detail.percent === "0" ? "用户承担" : "-" }}
|
|
|
|
|
+ </el-descriptions-item>
|
|
|
|
|
+ <el-descriptions-item label="支付超时">
|
|
|
|
|
+ {{ detail.pay_timeout != null ? `${detail.pay_timeout} 分钟` : "-" }}
|
|
|
|
|
+ </el-descriptions-item>
|
|
|
|
|
+ <el-descriptions-item label="企业回调">
|
|
|
|
|
+ {{ HAR_NOTIFY_STATUS_TEXT[detail.notify_status || ""] || detail.notify_status || "-" }}
|
|
|
|
|
+ </el-descriptions-item>
|
|
|
|
|
+ <el-descriptions-item label="下单时间">
|
|
|
|
|
+ {{ formatTime(detail.created_time) }}
|
|
|
|
|
+ </el-descriptions-item>
|
|
|
|
|
+ <el-descriptions-item label="支付时间">
|
|
|
|
|
+ {{ formatTime(detail.trade_time) }}
|
|
|
|
|
+ </el-descriptions-item>
|
|
|
|
|
+ <el-descriptions-item label="退款时间">
|
|
|
|
|
+ {{ formatTime(detail.refund_time) }}
|
|
|
|
|
+ </el-descriptions-item>
|
|
|
|
|
+ <el-descriptions-item label="上游创建时间">
|
|
|
|
|
+ {{ formatTime(detail.upstream_create_time) }}
|
|
|
|
|
+ </el-descriptions-item>
|
|
|
|
|
+ <el-descriptions-item v-if="detail.pay_url" label="收款链接" :span="2">
|
|
|
|
|
+ <div class="pay-url">
|
|
|
|
|
+ <span class="pay-url__text">{{ detail.pay_url }}</span>
|
|
|
|
|
+ <el-button size="small" type="primary" @click="openPayUrl(detail.pay_url)">
|
|
|
|
|
+ 打开
|
|
|
|
|
+ </el-button>
|
|
|
|
|
+ <el-button size="small" @click="copyPayUrl(detail.pay_url)">复制</el-button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </el-descriptions-item>
|
|
|
|
|
+ <el-descriptions-item v-if="detail.extra_params" label="扩展参数" :span="2">
|
|
|
|
|
+ {{ detail.extra_params }}
|
|
|
|
|
+ </el-descriptions-item>
|
|
|
|
|
+ </el-descriptions>
|
|
|
|
|
+ <template #footer>
|
|
|
|
|
+ <el-button
|
|
|
|
|
+ v-hasPerm="['module_payment:har:order:sync']"
|
|
|
|
|
+ :loading="syncingId === detail?.id"
|
|
|
|
|
+ @click="detail && handleSync(detail)"
|
|
|
|
|
+ >
|
|
|
|
|
+ 同步上游状态
|
|
|
|
|
+ </el-button>
|
|
|
|
|
+ <el-button @click="detailVisible = false">关闭</el-button>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-dialog>
|
|
|
|
|
+ </div>
|
|
|
|
|
+</template>
|
|
|
|
|
+
|
|
|
|
|
+<script setup lang="ts">
|
|
|
|
|
+import { onMounted, reactive, ref } from "vue";
|
|
|
|
|
+import { ElMessage, type FormInstance, type FormRules } from "element-plus";
|
|
|
|
|
+import dayjs from "dayjs";
|
|
|
|
|
+import HarAPI, {
|
|
|
|
|
+ HAR_NOTIFY_STATUS_TEXT,
|
|
|
|
|
+ HAR_TRADE_STATUS_TAG,
|
|
|
|
|
+ HAR_TRADE_STATUS_TEXT,
|
|
|
|
|
+ HAR_TRADE_TYPE_OPTIONS,
|
|
|
|
|
+ type HarMerchantVO,
|
|
|
|
|
+ type HarOrderCreateParams,
|
|
|
|
|
+ type HarOrderVO,
|
|
|
|
|
+} from "@/api/module_payment/har";
|
|
|
|
|
+import { useEnterpriseStore } from "@/store/modules/enterprise.store";
|
|
|
|
|
+
|
|
|
|
|
+const enterpriseStore = useEnterpriseStore();
|
|
|
|
|
+
|
|
|
|
|
+/** 本租户可见的商户配置 —— 下单时用哪一条由这里选 */
|
|
|
|
|
+const merchantOptions = ref<HarMerchantVO[]>([]);
|
|
|
|
|
+
|
|
|
|
|
+/** 规范 5.1 给定的分期期数 */
|
|
|
|
|
+const STAGE_NUM_OPTIONS = ["3", "6", "12", "24"];
|
|
|
|
|
+
|
|
|
|
|
+const list = ref<HarOrderVO[]>([]);
|
|
|
|
|
+const total = ref(0);
|
|
|
|
|
+const pageNo = ref(1);
|
|
|
|
|
+const pageSize = ref(20);
|
|
|
|
|
+const loading = ref(false);
|
|
|
|
|
+const syncingId = ref<string | undefined>();
|
|
|
|
|
+
|
|
|
|
|
+const createVisible = ref(false);
|
|
|
|
|
+const saving = ref(false);
|
|
|
|
|
+const formRef = ref<FormInstance>();
|
|
|
|
|
+
|
|
|
|
|
+const detailVisible = ref(false);
|
|
|
|
|
+const detail = ref<HarOrderVO | null>(null);
|
|
|
|
|
+
|
|
|
|
|
+const dateRange = ref<string[] | null>(null);
|
|
|
|
|
+
|
|
|
|
|
+const searchForm = reactive({
|
|
|
|
|
+ enterprise_id: undefined as string | undefined,
|
|
|
|
|
+ merchant_code: "",
|
|
|
|
|
+ order_no: "",
|
|
|
|
|
+ out_biz_no: "",
|
|
|
|
|
+ trade_status: "",
|
|
|
|
|
+ trade_type: "",
|
|
|
|
|
+ start_date: undefined as string | undefined,
|
|
|
|
|
+ end_date: undefined as string | undefined,
|
|
|
|
|
+});
|
|
|
|
|
+
|
|
|
|
|
+/** price 在后端入参里是 BigDecimal(数字或字符串皆可),表单里固定为数字,提交时再断言 */
|
|
|
|
|
+type OrderForm = Omit<HarOrderCreateParams, "price"> & { price?: number };
|
|
|
|
|
+
|
|
|
|
|
+const defaultForm = (): OrderForm => ({
|
|
|
|
|
+ // 用 undefined 而不是空串:空串对 enterprise_id 是有含义的(=租户级),
|
|
|
|
|
+ // 对 merchant_code 不是 —— 留空只表示「没指定」,后端按 StrUtil.isNotBlank 分流
|
|
|
|
|
+ merchant_code: undefined,
|
|
|
|
|
+ enterprise_id: "",
|
|
|
|
|
+ out_biz_no: "",
|
|
|
|
|
+ price: undefined,
|
|
|
|
|
+ title: "",
|
|
|
|
|
+ trade_type: "alipay_payment",
|
|
|
|
|
+ stage_num: "",
|
|
|
|
|
+ percent: "",
|
|
|
|
|
+ third_shop_code: "",
|
|
|
|
|
+ shop_code: "",
|
|
|
|
|
+ auth_code: "",
|
|
|
|
|
+ pay_timeout: undefined,
|
|
|
|
|
+ extra_params: "",
|
|
|
|
|
+});
|
|
|
|
|
+
|
|
|
|
|
+const form = reactive<OrderForm>(defaultForm());
|
|
|
|
|
+
|
|
|
|
|
+const rules: FormRules = {
|
|
|
|
|
+ // 企业**非必填**:留空即按租户下单(用 enterprise_id IS NULL 的那条租户级商户映射),
|
|
|
|
|
+ // 与 HarOrderCreateDTO 上「选填」的注释、后端 HarOrderService#create 的分流一致。
|
|
|
|
|
+ // 商户同理非必填:本租户只有一条配置时留空即为那条;有多条时后端会 400 要求指定。
|
|
|
|
|
+ price: [{ required: true, message: "请填写金额", trigger: "blur" }],
|
|
|
|
|
+ title: [{ required: true, message: "请填写商品名称", trigger: "blur" }],
|
|
|
|
|
+ trade_type: [{ required: true, message: "请选择交易方式", trigger: "change" }],
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+const merchantLabel = (m: HarMerchantVO) =>
|
|
|
|
|
+ m.merchant_name ? `${m.merchant_name}(${m.merchant_code})` : m.merchant_code;
|
|
|
|
|
+
|
|
|
|
|
+async function loadMerchantOptions() {
|
|
|
|
|
+ try {
|
|
|
|
|
+ const res = await HarAPI.merchantList({ page_size: 100 });
|
|
|
|
|
+ merchantOptions.value = res.data.data?.items || res.data.data?.list || [];
|
|
|
|
|
+ } catch {
|
|
|
|
|
+ // 拉不到(未授权/接口异常)不阻塞下单:下拉留空,仍可手工输入编号,
|
|
|
|
|
+ // 归属由后端按本租户校验(跨租户的会被 404 挡掉)
|
|
|
|
|
+ merchantOptions.value = [];
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const tradeTypeText = (v?: string) =>
|
|
|
|
|
+ HAR_TRADE_TYPE_OPTIONS.find((t) => t.value === v)?.label || v || "-";
|
|
|
|
|
+
|
|
|
|
|
+const formatTime = (v?: string) => (v ? dayjs(v).format("YYYY-MM-DD HH:mm:ss") : "-");
|
|
|
|
|
+
|
|
|
|
|
+async function load() {
|
|
|
|
|
+ loading.value = true;
|
|
|
|
|
+ try {
|
|
|
|
|
+ const res = await HarAPI.orderList({
|
|
|
|
|
+ enterprise_id: searchForm.enterprise_id || undefined,
|
|
|
|
|
+ merchant_code: searchForm.merchant_code || undefined,
|
|
|
|
|
+ order_no: searchForm.order_no || undefined,
|
|
|
|
|
+ out_biz_no: searchForm.out_biz_no || undefined,
|
|
|
|
|
+ trade_status: searchForm.trade_status || undefined,
|
|
|
|
|
+ trade_type: searchForm.trade_type || undefined,
|
|
|
|
|
+ start_date: searchForm.start_date || undefined,
|
|
|
|
|
+ end_date: searchForm.end_date || undefined,
|
|
|
|
|
+ page_no: pageNo.value,
|
|
|
|
|
+ page_size: pageSize.value,
|
|
|
|
|
+ });
|
|
|
|
|
+ list.value = res.data.data?.items || res.data.data?.list || [];
|
|
|
|
|
+ total.value = res.data.data?.total || 0;
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ loading.value = false;
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function handleSearch() {
|
|
|
|
|
+ pageNo.value = 1;
|
|
|
|
|
+ load();
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function handleDateChange() {
|
|
|
|
|
+ if (dateRange.value && dateRange.value.length === 2) {
|
|
|
|
|
+ searchForm.start_date = dateRange.value[0];
|
|
|
|
|
+ searchForm.end_date = dateRange.value[1];
|
|
|
|
|
+ } else {
|
|
|
|
|
+ searchForm.start_date = undefined;
|
|
|
|
|
+ searchForm.end_date = undefined;
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function handleReset() {
|
|
|
|
|
+ searchForm.enterprise_id = undefined;
|
|
|
|
|
+ searchForm.merchant_code = "";
|
|
|
|
|
+ searchForm.order_no = "";
|
|
|
|
|
+ searchForm.out_biz_no = "";
|
|
|
|
|
+ searchForm.trade_status = "";
|
|
|
|
|
+ searchForm.trade_type = "";
|
|
|
|
|
+ searchForm.start_date = undefined;
|
|
|
|
|
+ searchForm.end_date = undefined;
|
|
|
|
|
+ dateRange.value = null;
|
|
|
|
|
+ handleSearch();
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function openCreate() {
|
|
|
|
|
+ Object.assign(form, defaultForm());
|
|
|
|
|
+ createVisible.value = true;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function handleCreateClosed() {
|
|
|
|
|
+ formRef.value?.clearValidate();
|
|
|
|
|
+ Object.assign(form, defaultForm());
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function handleTradeTypeChange(v: string) {
|
|
|
|
|
+ // 非分期时清掉分期字段,避免把无关参数透传给上游
|
|
|
|
|
+ if (v !== "alipay_stage") {
|
|
|
|
|
+ form.stage_num = "";
|
|
|
|
|
+ form.percent = "";
|
|
|
|
|
+ } else if (!form.percent) {
|
|
|
|
|
+ form.percent = "100";
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+async function handleCreate() {
|
|
|
|
|
+ if (form.trade_type === "alipay_stage" && (!form.stage_num || !form.percent)) {
|
|
|
|
|
+ ElMessage.warning("花呗分期必须选择分期数与手续费承担方");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ const valid = await formRef.value?.validate().catch(() => false);
|
|
|
|
|
+ if (!valid) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ saving.value = true;
|
|
|
|
|
+ try {
|
|
|
|
|
+ const res = await HarAPI.orderCreate({
|
|
|
|
|
+ ...form,
|
|
|
|
|
+ // 已过表单必填校验,此处 price 必然有值
|
|
|
|
|
+ price: form.price as number,
|
|
|
|
|
+ out_biz_no: form.out_biz_no || undefined,
|
|
|
|
|
+ stage_num: form.stage_num || undefined,
|
|
|
|
|
+ percent: form.percent || undefined,
|
|
|
|
|
+ third_shop_code: form.third_shop_code || undefined,
|
|
|
|
|
+ shop_code: form.shop_code || undefined,
|
|
|
|
|
+ auth_code: form.auth_code || undefined,
|
|
|
|
|
+ extra_params: form.extra_params || undefined,
|
|
|
|
|
+ });
|
|
|
|
|
+ const created = res.data.data;
|
|
|
|
|
+ createVisible.value = false;
|
|
|
|
|
+ detail.value = created || null;
|
|
|
|
|
+ detailVisible.value = true;
|
|
|
|
|
+ ElMessage.success(
|
|
|
|
|
+ created?.pay_url ? "下单成功,请在详情中打开收款链接" : "下单成功,等待用户付款"
|
|
|
|
|
+ );
|
|
|
|
|
+ load();
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ saving.value = false;
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+async function openDetail(row: HarOrderVO) {
|
|
|
|
|
+ const res = await HarAPI.orderDetail(row.id as string);
|
|
|
|
|
+ detail.value = res.data.data;
|
|
|
|
|
+ detailVisible.value = true;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+async function handleSync(row: HarOrderVO) {
|
|
|
|
|
+ syncingId.value = row.id;
|
|
|
|
|
+ try {
|
|
|
|
|
+ const res = await HarAPI.orderSync(row.id as string);
|
|
|
|
|
+ const updated = res.data.data;
|
|
|
|
|
+ ElMessage.success(
|
|
|
|
|
+ `已同步,当前状态:${HAR_TRADE_STATUS_TEXT[updated.trade_status] || updated.trade_status}`
|
|
|
|
|
+ );
|
|
|
|
|
+ // 详情打开着就同步刷新,避免弹层里看到的还是旧状态
|
|
|
|
|
+ if (detailVisible.value && detail.value?.id === row.id) {
|
|
|
|
|
+ detail.value = updated;
|
|
|
|
|
+ }
|
|
|
|
|
+ load();
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ syncingId.value = undefined;
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function openPayUrl(url?: string) {
|
|
|
|
|
+ if (!url) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ const w = window.open(url, "_blank");
|
|
|
|
|
+ if (!w) {
|
|
|
|
|
+ ElMessage.warning("收款链接弹窗被浏览器拦截,请允许弹窗后重试");
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+async function copyPayUrl(url?: string) {
|
|
|
|
|
+ if (!url) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ try {
|
|
|
|
|
+ await navigator.clipboard.writeText(url);
|
|
|
|
|
+ ElMessage.success("已复制收款链接");
|
|
|
|
|
+ } catch {
|
|
|
|
|
+ ElMessage.warning("浏览器拒绝了复制操作,请手动选中复制");
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+onMounted(async () => {
|
|
|
|
|
+ load();
|
|
|
|
|
+ loadMerchantOptions();
|
|
|
|
|
+ try {
|
|
|
|
|
+ await enterpriseStore.loadEnterpriseList();
|
|
|
|
|
+ } catch {
|
|
|
|
|
+ // 企业列表加载失败不阻塞列表查询
|
|
|
|
|
+ }
|
|
|
|
|
+});
|
|
|
|
|
+</script>
|
|
|
|
|
+
|
|
|
|
|
+<style lang="scss" scoped>
|
|
|
|
|
+.card-header {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ justify-content: space-between;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.form-tip {
|
|
|
|
|
+ font-size: 12px;
|
|
|
|
|
+ line-height: 1.6;
|
|
|
|
|
+ color: var(--el-text-color-secondary);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.pay-url {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ gap: 8px;
|
|
|
|
|
+
|
|
|
|
|
+ &__text {
|
|
|
|
|
+ flex: 1;
|
|
|
|
|
+ word-break: break-all;
|
|
|
|
|
+ color: var(--el-text-color-regular);
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+</style>
|