فهرست منبع

!116 【重构】Vue3 管理后台:[支付管理 -> 应用信息][支付管理 -> 支付订单][支付管理 -> 退款订单] 使用 Element Plus 原生实现
Merge pull request !116 from 东方白/dev

芋道源码 1 سال پیش
والد
کامیت
0d96645bb3

+ 4 - 0
src/utils/constants.ts

@@ -114,6 +114,10 @@ export const PayChannelEnum = {
   ALIPAY_QR: {
     code: 'alipay_qr',
     name: '支付宝扫码支付'
+  },
+  ALIPAY_BAR: {
+    code: 'alipay_bar',
+    name: '支付宝条码支付'
   }
 }
 

+ 158 - 0
src/views/pay/app/AppForm.vue

@@ -0,0 +1,158 @@
+<template>
+  <Dialog :title="dialogTitle" v-model="dialogVisible" width="50%">
+    <el-form
+      ref="formRef"
+      :model="formData"
+      :rules="formRules"
+      label-width="160px"
+      v-loading="formLoading"
+    >
+      <el-form-item label="应用名" prop="name">
+        <el-input v-model="formData.name" placeholder="请输入应用名" />
+      </el-form-item>
+
+      <el-form-item label="所属商户" prop="merchantId">
+        <el-select
+          v-model="formData.merchantId"
+          filterable
+          remote
+          reserve-keyword
+          placeholder="请选择所属商户"
+          :remote-method="handleGetMerchantListByName"
+          :loading="formLoading"
+        >
+          <el-option
+            v-for="item in merchantList"
+            :key="item.id"
+            :label="item.name"
+            :value="item.id"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="开启状态" prop="status">
+        <el-radio-group v-model="formData.status">
+          <el-radio
+            v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
+            :key="dict.value"
+            :label="dict.value"
+          >
+            {{ dict.label }}
+          </el-radio>
+        </el-radio-group>
+      </el-form-item>
+      <el-form-item label="支付结果的回调地址" prop="payNotifyUrl">
+        <el-input v-model="formData.payNotifyUrl" placeholder="请输入支付结果的回调地址" />
+      </el-form-item>
+      <el-form-item label="退款结果的回调地址" prop="refundNotifyUrl">
+        <el-input v-model="formData.refundNotifyUrl" placeholder="请输入退款结果的回调地址" />
+      </el-form-item>
+      <el-form-item label="备注" prop="remark">
+        <el-input v-model="formData.remark" placeholder="请输入备注" />
+      </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 { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
+import * as AppApi from '@/api/pay/app'
+import * as MerchantApi from '@/api/pay/merchant'
+import { CommonStatusEnum } from '@/utils/constants'
+
+const { t } = useI18n() // 国际化
+const message = useMessage() // 消息弹窗
+const dialogVisible = ref(false) // 弹窗的是否展示
+const dialogTitle = ref('') // 弹窗的标题
+const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
+const formType = ref('') // 表单的类型:create - 新增;update - 修改
+const formData = ref({
+  id: undefined,
+  name: undefined,
+  packageId: undefined,
+  contactName: undefined,
+  contactMobile: undefined,
+  accountCount: undefined,
+  expireTime: undefined,
+  domain: undefined,
+  status: CommonStatusEnum.ENABLE
+})
+const formRules = reactive({
+  name: [{ required: true, message: '应用名不能为空', trigger: 'blur' }],
+  status: [{ required: true, message: '开启状态不能为空', trigger: 'blur' }],
+  payNotifyUrl: [{ required: true, message: '支付结果的回调地址不能为空', trigger: 'blur' }],
+  refundNotifyUrl: [{ required: true, message: '退款结果的回调地址不能为空', trigger: 'blur' }],
+  merchantId: [{ required: true, message: '商户编号不能为空', trigger: 'blur' }]
+})
+const formRef = ref() // 表单 Ref
+const merchantList = ref([]) // 商户列表
+
+/** 打开弹窗 */
+const open = async (type: string, id?: number) => {
+  dialogVisible.value = true
+  dialogTitle.value = t('action.' + type)
+  formType.value = type
+  resetForm()
+  // 修改时,设置数据
+  if (id) {
+    formLoading.value = true
+    try {
+      formData.value = await AppApi.getApp(id)
+    } finally {
+      formLoading.value = false
+    }
+  }
+  await handleGetMerchantListByName(null)
+}
+defineExpose({ open }) // 提供 open 方法,用于打开弹窗
+
+/** 提交表单 */
+const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
+const submitForm = async () => {
+  // 校验表单
+  if (!formRef) return
+  const valid = await formRef.value.validate()
+  if (!valid) return
+  // 提交请求
+  formLoading.value = true
+  try {
+    const data = formData.value as unknown as AppApi.AppVO
+    if (formType.value === 'create') {
+      await AppApi.createApp(data)
+      message.success(t('common.createSuccess'))
+    } else {
+      await AppApi.updateApp(data)
+      message.success(t('common.updateSuccess'))
+    }
+    dialogVisible.value = false
+    // 发送操作成功的事件
+    emit('success')
+  } finally {
+    formLoading.value = false
+  }
+}
+
+/**
+ * 根据商户名称模糊匹配商户信息
+ * @param name 商户名称
+ */
+const handleGetMerchantListByName = async (name) => {
+  merchantList.value = await MerchantApi.getMerchantListByName(name)
+}
+
+/** 重置表单 */
+const resetForm = () => {
+  formData.value = {
+    id: undefined,
+    name: undefined,
+    status: CommonStatusEnum.ENABLE,
+    remark: undefined,
+    payNotifyUrl: undefined,
+    refundNotifyUrl: undefined,
+    merchantId: undefined
+  }
+  formRef.value?.resetFields()
+}
+</script>

+ 0 - 71
src/views/pay/app/app.data.ts

@@ -1,71 +0,0 @@
-import type { VxeCrudSchema } from '@/hooks/web/useVxeCrudSchemas'
-const { t } = useI18n() // 国际化
-
-// 表单校验
-export const rules = reactive({
-  name: [required],
-  status: [required],
-  payNotifyUrl: [required],
-  refundNotifyUrl: [required],
-  merchantId: [required]
-})
-
-// CrudSchema
-const crudSchemas = reactive<VxeCrudSchema>({
-  primaryKey: 'id',
-  primaryType: 'seq',
-  primaryTitle: '编号',
-  action: true,
-  columns: [
-    {
-      title: '应用名',
-      field: 'name',
-      isSearch: true
-    },
-    {
-      title: '商户名称',
-      field: 'payMerchant',
-      isSearch: true
-    },
-    {
-      title: t('common.status'),
-      field: 'status',
-      dictType: DICT_TYPE.COMMON_STATUS,
-      dictClass: 'number',
-      isSearch: true
-    },
-    {
-      title: '支付结果的回调地址',
-      field: 'payNotifyUrl',
-      isSearch: true
-    },
-    {
-      title: '退款结果的回调地址',
-      field: 'refundNotifyUrl',
-      isSearch: true
-    },
-    {
-      title: '商户名称',
-      field: 'merchantName',
-      isSearch: true
-    },
-    {
-      title: '备注',
-      field: 'remark',
-      isTable: false,
-      isSearch: true
-    },
-    {
-      title: t('common.createTime'),
-      field: 'createTime',
-      isForm: false,
-      search: {
-        show: true,
-        itemRender: {
-          name: 'XDataTimePicker'
-        }
-      }
-    }
-  ]
-})
-export const { allSchemas } = useVxeCrudSchemas(crudSchemas)

+ 450 - 130
src/views/pay/app/index.vue

@@ -1,155 +1,475 @@
 <template>
+  <!-- 搜索 -->
   <ContentWrap>
-    <!-- 列表 -->
-    <XTable @register="registerTable">
-      <template #toolbar_buttons>
-        <!-- 操作:新增 -->
-        <XButton
-          type="primary"
-          preIcon="ep:zoom-in"
-          :title="t('action.add')"
-          v-hasPermi="['pay:app:create']"
-          @click="handleCreate()"
-        />
-        <!-- 操作:导出 -->
-        <XButton
-          type="warning"
-          preIcon="ep:download"
-          :title="t('action.export')"
-          v-hasPermi="['pay:app:export']"
-          @click="exportList('应用信息.xls')"
+    <el-form
+      class="-mb-15px"
+      :model="queryParams"
+      ref="queryFormRef"
+      :inline="true"
+      label-width="68px"
+    >
+      <el-form-item label="应用名" prop="name">
+        <el-input
+          v-model="queryParams.name"
+          placeholder="请输入应用名"
+          clearable
+          @keyup.enter="handleQuery"
+          class="!w-240px"
         />
-      </template>
-      <template #actionbtns_default="{ row }">
-        <!-- 操作:修改 -->
-        <XTextButton
-          preIcon="ep:edit"
-          :title="t('action.edit')"
-          v-hasPermi="['pay:app:update']"
-          @click="handleUpdate(row.id)"
+      </el-form-item>
+      <el-form-item label="商户名称" prop="contactName">
+        <el-input
+          v-model="queryParams.contactName"
+          placeholder="请输入商户名称"
+          clearable
+          @keyup.enter="handleQuery"
+          class="!w-240px"
         />
-        <!-- 操作:详情 -->
-        <XTextButton
-          preIcon="ep:view"
-          :title="t('action.detail')"
-          v-hasPermi="['pay:app:query']"
-          @click="handleDetail(row.id)"
+      </el-form-item>
+      <el-form-item label="开启状态" prop="status">
+        <el-select
+          v-model="queryParams.status"
+          placeholder="请选择开启状态"
+          clearable
+          class="!w-240px"
+        >
+          <el-option
+            v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
+            :key="dict.value"
+            :label="dict.label"
+            :value="dict.value"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="创建时间" prop="createTime">
+        <el-date-picker
+          v-model="queryParams.createTime"
+          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"
         />
-        <!-- 操作:删除 -->
-        <XTextButton
-          preIcon="ep:delete"
-          :title="t('action.del')"
-          v-hasPermi="['pay:app:delete']"
-          @click="deleteData(row.id)"
-        />
-      </template>
-    </XTable>
+      </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
+          type="primary"
+          plain
+          @click="openForm('create')"
+          v-hasPermi="['system:tenant:create']"
+        >
+          <Icon icon="ep:plus" class="mr-5px" />
+          新增
+        </el-button>
+        <el-button
+          type="success"
+          plain
+          @click="handleExport"
+          :loading="exportLoading"
+          v-hasPermi="['system:tenant:export']"
+        >
+          <Icon icon="ep:download" class="mr-5px" />
+          导出
+        </el-button>
+      </el-form-item>
+    </el-form>
   </ContentWrap>
 
-  <XModal v-model="dialogVisible" :title="dialogTitle">
-    <!-- 对话框(添加 / 修改) -->
-    <Form
-      v-if="['create', 'update'].includes(actionType)"
-      :schema="allSchemas.formSchema"
-      :rules="rules"
-      ref="formRef"
-    />
-    <!-- 对话框(详情) -->
-    <Descriptions
-      v-if="actionType === 'detail'"
-      :schema="allSchemas.detailSchema"
-      :data="detailData"
-    />
-    <!-- 操作按钮 -->
-    <template #footer>
-      <!-- 按钮:保存 -->
-      <XButton
-        v-if="['create', 'update'].includes(actionType)"
-        type="primary"
-        :title="t('action.save')"
-        :loading="actionLoading"
-        @click="submitForm()"
+  <!-- 列表 -->
+  <ContentWrap>
+    <el-table v-loading="loading" :data="list">
+      <el-table-column label="应用编号" align="center" prop="id" />
+      <el-table-column label="应用名" align="center" prop="name" />
+      <el-table-column label="开启状态" align="center" prop="status">
+        <template #default="scope">
+          <dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="scope.row.status" />
+        </template>
+      </el-table-column>
+      <el-table-column label="商户名称" align="center" prop="payMerchant.name" />
+      <el-table-column label="支付宝配置" align="center">
+        <el-table-column :label="payChannelEnum.ALIPAY_APP.name" align="center">
+          <template #default="scope">
+            <el-button
+              type="success"
+              v-if="judgeChannelExist(scope.row.channelCodes, payChannelEnum.ALIPAY_APP.code)"
+              @click="
+                handleUpdateChannel(scope.row, payChannelEnum.ALIPAY_APP.code, payType.ALIPAY)
+              "
+              circle
+            >
+              <Icon icon="ep:check" />
+            </el-button>
+            <el-button
+              v-else
+              type="danger"
+              circle
+              @click="
+                handleCreateChannel(scope.row, payChannelEnum.ALIPAY_APP.code, payType.ALIPAY)
+              "
+            >
+              <Icon icon="ep:close" />
+            </el-button>
+          </template>
+        </el-table-column>
+        <el-table-column :label="payChannelEnum.ALIPAY_PC.name" align="center">
+          <template #default="scope">
+            <el-button
+              type="success"
+              circle
+              v-if="judgeChannelExist(scope.row.channelCodes, payChannelEnum.ALIPAY_PC.code)"
+              @click="handleUpdateChannel(scope.row, payChannelEnum.ALIPAY_PC.code, payType.ALIPAY)"
+            >
+              <Icon icon="ep:check" />
+            </el-button>
+            <el-button
+              v-else
+              type="danger"
+              circle
+              @click="handleCreateChannel(scope.row, payChannelEnum.ALIPAY_PC.code, payType.ALIPAY)"
+            >
+              <Icon icon="ep:close" />
+            </el-button>
+          </template>
+        </el-table-column>
+        <el-table-column :label="payChannelEnum.ALIPAY_WAP.name" align="center">
+          <template #default="scope">
+            <el-button
+              type="success"
+              circle
+              v-if="judgeChannelExist(scope.row.channelCodes, payChannelEnum.ALIPAY_WAP.code)"
+              @click="
+                handleUpdateChannel(scope.row, payChannelEnum.ALIPAY_WAP.code, payType.ALIPAY)
+              "
+            >
+              <Icon icon="ep:check" />
+            </el-button>
+            <el-button
+              v-else
+              type="danger"
+              circle
+              @click="
+                handleCreateChannel(scope.row, payChannelEnum.ALIPAY_WAP.code, payType.ALIPAY)
+              "
+            >
+              <Icon icon="ep:close" />
+            </el-button>
+          </template>
+        </el-table-column>
+        <el-table-column :label="payChannelEnum.ALIPAY_QR.name" align="center">
+          <template #default="scope">
+            <el-button
+              type="success"
+              circle
+              v-if="judgeChannelExist(scope.row.channelCodes, payChannelEnum.ALIPAY_QR.code)"
+              @click="handleUpdateChannel(scope.row, payChannelEnum.ALIPAY_QR.code, payType.ALIPAY)"
+            >
+              <Icon icon="ep:check" />
+            </el-button>
+            <el-button
+              v-else
+              type="danger"
+              circle
+              @click="handleCreateChannel(scope.row, payChannelEnum.ALIPAY_QR.code, payType.ALIPAY)"
+            >
+              <Icon icon="ep:close" />
+            </el-button>
+          </template>
+        </el-table-column>
+        <el-table-column :label="payChannelEnum.ALIPAY_BAR.name" align="center">
+          <template #default="scope">
+            <el-button
+              type="success"
+              circle
+              v-if="judgeChannelExist(scope.row.channelCodes, payChannelEnum.ALIPAY_BAR.code)"
+              @click="
+                handleUpdateChannel(scope.row, payChannelEnum.ALIPAY_BAR.code, payType.ALIPAY)
+              "
+            >
+              <Icon icon="ep:check" />
+            </el-button>
+            <el-button
+              v-else
+              type="danger"
+              circle
+              @click="
+                handleCreateChannel(scope.row, payChannelEnum.ALIPAY_BAR.code, payType.ALIPAY)
+              "
+            >
+              <Icon icon="ep:close" />
+            </el-button>
+          </template>
+        </el-table-column>
+      </el-table-column>
+      <el-table-column label="微信配置" align="center">
+        <el-table-column :label="payChannelEnum.WX_LITE.name" align="center">
+          <template #default="scope">
+            <el-button
+              type="success"
+              circle
+              v-if="judgeChannelExist(scope.row.channelCodes, payChannelEnum.WX_LITE.code)"
+              @click="handleUpdateChannel(scope.row, payChannelEnum.WX_LITE.code, payType.WECHAT)"
+            >
+              <Icon icon="ep:check" />
+            </el-button>
+            <el-button
+              v-else
+              type="danger"
+              circle
+              @click="handleCreateChannel(scope.row, payChannelEnum.WX_LITE.code, payType.WECHAT)"
+            >
+              <Icon icon="ep:close" />
+            </el-button>
+          </template>
+        </el-table-column>
+        <el-table-column :label="payChannelEnum.WX_PUB.name" align="center">
+          <template #default="scope">
+            <el-button
+              type="success"
+              circle
+              v-if="judgeChannelExist(scope.row.channelCodes, payChannelEnum.WX_PUB.code)"
+              @click="handleUpdateChannel(scope.row, payChannelEnum.WX_PUB.code, payType.WECHAT)"
+            >
+              <Icon icon="ep:check" />
+            </el-button>
+            <el-button
+              v-else
+              type="danger"
+              circle
+              @click="handleCreateChannel(scope.row, payChannelEnum.WX_PUB.code, payType.WECHAT)"
+            >
+              <Icon icon="ep:close" />
+            </el-button>
+          </template>
+        </el-table-column>
+        <el-table-column :label="payChannelEnum.WX_APP.name" align="center">
+          <template #default="scope">
+            <el-button
+              type="success"
+              circle
+              v-if="judgeChannelExist(scope.row.channelCodes, payChannelEnum.WX_APP.code)"
+              @click="handleUpdateChannel(scope.row, payChannelEnum.WX_APP.code, payType.WECHAT)"
+            >
+              <Icon icon="ep:check" />
+            </el-button>
+            <el-button
+              v-else
+              type="danger"
+              circle
+              @click="handleCreateChannel(scope.row, payChannelEnum.WX_APP.code, payType.WECHAT)"
+            >
+              <Icon icon="ep:close" />
+            </el-button>
+          </template>
+        </el-table-column>
+      </el-table-column>
+      <el-table-column
+        label="创建时间"
+        align="center"
+        prop="createTime"
+        width="180"
+        :formatter="dateFormatter"
       />
-      <!-- 按钮:关闭 -->
-      <XButton :loading="actionLoading" :title="t('dialog.close')" @click="dialogVisible = false" />
-    </template>
-  </XModal>
+      <el-table-column label="操作" align="center" min-width="110" fixed="right">
+        <template #default="scope">
+          <el-button
+            link
+            type="primary"
+            @click="openForm('update', scope.row.id)"
+            v-hasPermi="['system:tenant:update']"
+          >
+            编辑
+          </el-button>
+          <el-button
+            link
+            type="danger"
+            @click="handleDelete(scope.row.id)"
+            v-hasPermi="['system:tenant:delete']"
+          >
+            删除
+          </el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+    <!-- 分页 -->
+    <Pagination
+      :total="total"
+      v-model:page="queryParams.pageNo"
+      v-model:limit="queryParams.pageSize"
+      @pagination="getList"
+    />
+  </ContentWrap>
+
+  <!-- 表单弹窗:添加/修改 -->
+  <AppForm ref="formRef" @success="getList" />
 </template>
-<script setup lang="ts" name="PayApp">
-import type { FormExpose } from '@/components/Form'
-import { rules, allSchemas } from './app.data'
+<script setup lang="ts" name="App">
+import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
+import download from '@/utils/download'
 import * as AppApi from '@/api/pay/app'
+import ContentWrap from '@/components/ContentWrap/src/ContentWrap.vue'
+import { dateFormatter } from '@/utils/formatTime'
+import AppForm from '@/views/pay/app/AppForm.vue'
+import { PayChannelEnum as payChannelEnum, PayType } from '@/utils/constants'
 
-const { t } = useI18n() // 国际化
 const message = useMessage() // 消息弹窗
+const { t } = useI18n() // 国际化
 
-// 列表相关的变量
-const [registerTable, { reload, deleteData, exportList }] = useXTable({
-  allSchemas: allSchemas,
-  getListApi: AppApi.getAppPage,
-  deleteApi: AppApi.deleteApp,
-  exportListApi: AppApi.exportApp
+const loading = ref(true) // 列表的加载中
+const total = ref(0) // 列表的总页数
+const list = ref([]) // 列表的数据
+const queryParams = reactive({
+  pageNo: 1,
+  pageSize: 10,
+  name: undefined,
+  status: undefined,
+  remark: undefined,
+  payNotifyUrl: undefined,
+  refundNotifyUrl: undefined,
+  merchantName: undefined,
+  createTime: []
 })
+const queryFormRef = ref() // 搜索的表单
+const exportLoading = ref(false) // 导出的加载中
+const channelParam = reactive({
+  loading: false,
+  // 是否修改
+  edit: false,
+  // 微信是否显示
+  wechatOpen: false,
+  // 支付宝是否显示
+  aliPayOpen: false,
+  // 应用ID
+  appId: null,
+  // 渠道编码
+  payCode: null,
+  // 商户对象
+  payMerchant: {
+    // 编号
+    id: null,
+    // 名称
+    name: null
+  }
+}) // 微信组件传参参数
 
-// ========== CRUD 相关 ==========
-const actionLoading = ref(false) // 遮罩层
-const actionType = ref('') // 操作按钮的类型
-const dialogVisible = ref(false) // 是否显示弹出层
-const dialogTitle = ref('edit') // 弹出层标题
-const formRef = ref<FormExpose>() // 表单 Ref
-const detailData = ref() // 详情 Ref
-
-// 设置标题
-const setDialogTile = (type: string) => {
-  dialogTitle.value = t('action.' + type)
-  actionType.value = type
-  dialogVisible.value = true
+/** 查询列表 */
+const getList = async () => {
+  loading.value = true
+  try {
+    const data = await AppApi.getAppPage(queryParams)
+    list.value = data.list
+    total.value = data.total
+  } finally {
+    loading.value = false
+  }
 }
 
-// 新增操作
-const handleCreate = () => {
-  setDialogTile('create')
+/** 搜索按钮操作 */
+const handleQuery = () => {
+  queryParams.pageNo = 1
+  getList()
 }
 
-// 修改操作
-const handleUpdate = async (rowId: number) => {
-  setDialogTile('update')
-  // 设置数据
-  const res = await AppApi.getApp(rowId)
-  unref(formRef)?.setValues(res)
+/** 重置按钮操作 */
+const resetQuery = () => {
+  queryFormRef.value.resetFields()
+  handleQuery()
 }
 
-// 详情操作
-const handleDetail = async (rowId: number) => {
-  setDialogTile('detail')
-  const res = await AppApi.getApp(rowId)
-  detailData.value = res
+/** 添加/修改操作 */
+const formRef = ref()
+const openForm = (type: string, id?: number) => {
+  formRef.value.open(type, id)
 }
 
-// 提交按钮
-const submitForm = async () => {
-  const elForm = unref(formRef)?.getElFormRef()
-  if (!elForm) return
-  elForm.validate(async (valid) => {
-    if (valid) {
-      actionLoading.value = true
-      // 提交请求
-      try {
-        const data = unref(formRef)?.formModel as AppApi.AppVO
-        if (actionType.value === 'create') {
-          await AppApi.createApp(data)
-          message.success(t('common.createSuccess'))
-        } else {
-          await AppApi.updateApp(data)
-          message.success(t('common.updateSuccess'))
-        }
-        dialogVisible.value = false
-      } finally {
-        actionLoading.value = false
-        // 刷新列表
-        await reload()
-      }
-    }
-  })
+/** 删除按钮操作 */
+const handleDelete = async (id: number) => {
+  try {
+    // 删除的二次确认
+    await message.delConfirm()
+    // 发起删除
+    await AppApi.deleteApp(id)
+    message.success(t('common.delSuccess'))
+    // 刷新列表
+    await getList()
+  } catch {}
 }
+
+/** 导出按钮操作 */
+const handleExport = async () => {
+  try {
+    // 导出的二次确认
+    await message.exportConfirm()
+    // 发起导出
+    exportLoading.value = true
+    const data = await AppApi.exportApp(queryParams)
+    download.excel(data, '支付应用信息.xls')
+  } catch {
+  } finally {
+    exportLoading.value = false
+  }
+}
+
+/**
+ * 根据渠道编码判断渠道列表中是否存在
+ * @param channels 渠道列表
+ * @param channelCode 渠道编码
+ */
+const judgeChannelExist = (channels, channelCode) => {
+  if (!channels) {
+    return false
+  }
+  return channels.indexOf(channelCode) !== -1
+}
+
+/**
+ * 修改支付渠道信息
+ * @param row 行记录
+ * @param payCode 支付编码
+ * @param type 支付类型
+ */
+const handleUpdateChannel = async (row, payCode, type) => {
+  await settingChannelParam(row, payCode, type)
+  channelParam.edit = true
+  channelParam.loading = true
+}
+
+/**
+ * 新增支付渠道信息
+ */
+const handleCreateChannel = async (row, payCode, type) => {
+  await settingChannelParam(row, payCode, type)
+  channelParam.edit = false
+  channelParam.loading = false
+}
+
+const settingChannelParam = async (row, payCode, type) => {
+  if (type === PayType.WECHAT) {
+    channelParam.wechatOpen = true
+    channelParam.aliPayOpen = false
+  }
+  if (type === PayType.ALIPAY) {
+    channelParam.aliPayOpen = true
+    channelParam.wechatOpen = false
+  }
+  channelParam.edit = false
+  channelParam.loading = false
+  channelParam.appId = row.id
+  channelParam.payCode = payCode
+  channelParam.payMerchant = row.payMerchant
+}
+
+/** 初始化 **/
+onMounted(async () => {
+  await getList()
+})
 </script>

+ 343 - 63
src/views/pay/order/index.vue

@@ -1,79 +1,359 @@
 <template>
   <ContentWrap>
-    <!-- 列表 -->
-    <XTable @register="registerTable">
-      <template #toolbar_buttons>
-        <!-- 操作:新增 -->
-        <XButton
-          type="primary"
-          preIcon="ep:zoom-in"
-          :title="t('action.add')"
-          v-hasPermi="['pay:order:create']"
-          @click="handleCreate()"
+    <el-form
+      class="-mb-15px"
+      :model="queryParams"
+      ref="queryFormRef"
+      :inline="true"
+      label-width="120px"
+    >
+      <el-form-item label="所属商户" prop="merchantId">
+        <el-select
+          v-model="queryParams.merchantId"
+          clearable
+          @clear="
+            () => {
+              queryParams.merchantId = null
+            }
+          "
+          filterable
+          remote
+          reserve-keyword
+          placeholder="请选择所属商户"
+          @change="handleGetAppListByMerchantId"
+          :remote-method="handleGetMerchantListByName"
+          :loading="merchantLoading"
+        >
+          <el-option
+            v-for="item in merchantList"
+            :key="item.id"
+            :label="item.name"
+            :value="item.id"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="应用编号" prop="appId">
+        <el-select clearable v-model="queryParams.appId" filterable placeholder="请选择应用信息">
+          <el-option v-for="item in appList" :key="item.id" :label="item.name" :value="item.id" />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="渠道编码" prop="channelCode">
+        <el-select
+          v-model="queryParams.channelCode"
+          placeholder="请输入渠道编码"
+          clearable
+          @clear="
+            () => {
+              queryParams.channelCode = null
+            }
+          "
+        >
+          <el-option
+            v-for="dict in getDictOptions(DICT_TYPE.PAY_CHANNEL_CODE_TYPE)"
+            :key="dict.value"
+            :label="dict.label"
+            :value="dict.value"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="商户订单编号" prop="merchantOrderId">
+        <el-input
+          v-model="queryParams.merchantOrderId"
+          placeholder="请输入商户订单编号"
+          clearable
+          @keyup.enter="handleQuery"
         />
-        <!-- 操作:导出 -->
-        <XButton
-          type="warning"
-          preIcon="ep:download"
-          :title="t('action.export')"
-          v-hasPermi="['pay:order:export']"
-          @click="exportList('订单数据.xls')"
+      </el-form-item>
+      <el-form-item label="渠道订单号" prop="channelOrderNo">
+        <el-input
+          v-model="queryParams.channelOrderNo"
+          placeholder="请输入渠道订单号"
+          clearable
+          @keyup.enter="handleQuery"
         />
-      </template>
-      <template #actionbtns_default="{ row }">
-        <!-- 操作:详情 -->
-        <XTextButton
-          preIcon="ep:view"
-          :title="t('action.detail')"
-          v-hasPermi="['pay:order:query']"
-          @click="handleDetail(row.id)"
+      </el-form-item>
+      <el-form-item label="支付状态" prop="status">
+        <el-select v-model="queryParams.status" placeholder="请选择支付状态" clearable size="small">
+          <el-option
+            v-for="dict in getDictOptions(DICT_TYPE.PAY_ORDER_STATUS)"
+            :key="parseInt(dict.value)"
+            :label="dict.label"
+            :value="parseInt(dict.value)"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="退款状态" prop="refundStatus">
+        <el-select v-model="queryParams.refundStatus" placeholder="请选择退款状态" clearable>
+          <el-option
+            v-for="dict in getDictOptions(DICT_TYPE.PAY_ORDER_REFUND_STATUS)"
+            :key="parseInt(dict.value)"
+            :label="dict.label"
+            :value="parseInt(dict.value)"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="回调商户状态" prop="notifyStatus">
+        <el-select
+          v-model="queryParams.notifyStatus"
+          placeholder="请选择订单回调商户状态"
+          clearable
+        >
+          <el-option
+            v-for="dict in getDictOptions(DICT_TYPE.PAY_ORDER_NOTIFY_STATUS)"
+            :key="parseInt(dict.value)"
+            :label="dict.label"
+            :value="parseInt(dict.value)"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="创建时间" prop="createTime">
+        <el-date-picker
+          v-model="queryParams.createTime"
+          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"
         />
-      </template>
-    </XTable>
+      </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
+          type="success"
+          plain
+          @click="handleExport"
+          :loading="exportLoading"
+          v-hasPermi="['system:tenant:export']"
+        >
+          <Icon icon="ep:download" class="mr-5px" />
+          导出
+        </el-button>
+      </el-form-item>
+    </el-form>
   </ContentWrap>
-  <XModal v-model="dialogVisible" :title="dialogTitle">
-    <!-- 对话框(详情) -->
-    <Descriptions :schema="allSchemas.detailSchema" :data="detailData" />
-    <!-- 操作按钮 -->
-    <template #footer>
-      <!-- 按钮:关闭 -->
-      <XButton :loading="actionLoading" :title="t('dialog.close')" @click="dialogVisible = false" />
-    </template>
-  </XModal>
+  <content-wrap>
+    <el-table v-loading="loading" :data="list">
+      <el-table-column label="订单编号" align="center" prop="id" />
+      <el-table-column label="商户名称" align="center" prop="merchantName" width="120" />
+      <el-table-column label="应用名称" align="center" prop="appName" width="120" />
+      <el-table-column label="渠道名称" align="center" prop="channelCodeName" width="120" />
+      <el-table-column label="渠道订单号" align="center" prop="merchantOrderId" width="120" />
+      <el-table-column label="商品标题" align="center" prop="subject" width="250" />
+      <el-table-column label="商品描述" align="center" prop="body" width="250" />
+      <el-table-column label="异步通知地址" align="center" prop="notifyUrl" width="250" />
+      <el-table-column label="回调状态" align="center" prop="notifyStatus">
+        <template #default="scope">
+          <dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="scope.row.status" />
+        </template>
+      </el-table-column>
+      <el-table-column label="支付订单" align="left">
+        <template #default="scope">
+          <p class="order-font">
+            <el-tag size="small">商户</el-tag>
+            {{ scope.row.merchantOrderId }}
+          </p>
+          <p class="order-font">
+            <el-tag size="small" type="warning">支付</el-tag>
+            {{ scope.row.channelOrderNo }}
+          </p>
+        </template>
+      </el-table-column>
+      <el-table-column label="支付金额" align="center" prop="amount">
+        <template #default="scope">
+          ¥{{ parseFloat(scope.row.amount / 100).toFixed(2) }}
+        </template>
+      </el-table-column>
+      <el-table-column label="手续金额" align="center" prop="channelFeeAmount">
+        <template #default="scope">
+          ¥{{ parseFloat(scope.row.channelFeeAmount / 100).toFixed(2) }}
+        </template>
+      </el-table-column>
+      <el-table-column label="退款金额" align="center" prop="refundAmount">
+        <template #default="scope">
+          ¥{{ parseFloat(scope.row.refundAmount / 100).toFixed(2) }}
+        </template>
+      </el-table-column>
+      <el-table-column label="支付状态" align="center" prop="status">
+        <template #default="scope">
+          <dict-tag :type="DICT_TYPE.PAY_ORDER_STATUS" :value="scope.row.status" />
+        </template>
+      </el-table-column>
+      <el-table-column label="回调状态" align="center" prop="notifyStatus">
+        <template #default="scope">
+          <dict-tag :type="DICT_TYPE.PAY_ORDER_NOTIFY_STATUS" :value="scope.row.notifyStatus" />
+        </template>
+      </el-table-column>
+      <el-table-column
+        label="创建时间"
+        align="center"
+        prop="createTime"
+        width="100"
+        :formatter="dateFormatter"
+      />
+      <el-table-column
+        label="支付时间"
+        align="center"
+        prop="successTime"
+        width="100"
+        :formatter="dateFormatter"
+      />
+      <el-table-column
+        label="操作"
+        align="center"
+        fixed="right"
+        class-name="small-padding fixed-width"
+      >
+        <template #default="scope">
+          <el-button
+            size="small"
+            type="text"
+            icon="el-icon-search"
+            @click="openForm(scope.row.id)"
+            v-hasPermi="['pay:order:query']"
+            >查看详情
+          </el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+    <!-- 分页组件 -->
+    <!-- 分页 -->
+    <Pagination
+      :total="total"
+      v-model:page="queryParams.pageNo"
+      v-model:limit="queryParams.pageSize"
+      @pagination="getList"
+    />
+  </content-wrap>
+
+  <!-- 表单弹窗:预览 -->
+  <OrderForm ref="formRef" @success="getList" />
 </template>
-<script setup lang="ts" name="PayOrder">
-import { allSchemas } from './order.data'
+<script setup lang="ts" name="Order">
+import { DICT_TYPE, getDictOptions } from '@/utils/dict'
+import ContentWrap from '@/components/ContentWrap/src/ContentWrap.vue'
+import * as MerchantApi from '@/api/pay/merchant'
 import * as OrderApi from '@/api/pay/order'
+import download from '@/utils/download'
+import * as AppApi from '@/api/pay/app'
+import { dateFormatter } from '@/utils/formatTime'
+import OrderForm from '@/views/pay/order/orderForm.vue'
+
+const message = useMessage() // 消息弹窗
 
-const { t } = useI18n() // 国际化
-// 列表相关的变量
-const [registerTable, { exportList }] = useXTable({
-  allSchemas: allSchemas,
-  getListApi: OrderApi.getOrderPage,
-  exportListApi: OrderApi.exportOrder
+// const { t } = useI18n() // 国际化
+const queryFormRef = ref() // 搜索的表单
+const merchantList = ref([]) // 商户列表
+const merchantLoading = ref(false) // 商户加载遮罩层
+const appList = ref([]) // 支付应用列表集合
+const loading = ref(false) // 列表的加载中
+const exportLoading = ref(false) // 导出等待
+const total = ref(0) // 列表的总页数
+const list = ref([]) // 列表的数据
+const queryParams = reactive({
+  pageNo: 1,
+  pageSize: 10,
+  merchantId: undefined,
+  appId: undefined,
+  channelId: undefined,
+  channelCode: undefined,
+  merchantOrderId: undefined,
+  subject: undefined,
+  body: undefined,
+  notifyUrl: undefined,
+  notifyStatus: undefined,
+  amount: undefined,
+  channelFeeRate: undefined,
+  channelFeeAmount: undefined,
+  status: undefined,
+  userIp: undefined,
+  successExtensionId: undefined,
+  refundStatus: undefined,
+  refundTimes: undefined,
+  refundAmount: undefined,
+  channelUserId: undefined,
+  channelOrderNo: undefined,
+  expireTime: [],
+  successTime: [],
+  notifyTime: [],
+  createTime: []
 })
-// ========== CRUD 相关 ==========
-const actionLoading = ref(false) // 遮罩层
-const actionType = ref('') // 操作按钮的类型
-const dialogVisible = ref(false) // 是否显示弹出层
-const dialogTitle = ref('edit') // 弹出层标题
-const detailData = ref() // 详情 Ref
-// 设置标题
-const setDialogTile = (type: string) => {
-  dialogTitle.value = t('action.' + type)
-  actionType.value = type
-  dialogVisible.value = true
+
+/**
+ * 根据商户名称模糊匹配商户信息
+ * @param name 商户名称
+ */
+const handleGetMerchantListByName = async (name) => {
+  merchantList.value = await MerchantApi.getMerchantListByName(name)
+}
+
+/**
+ * 根据商户 ID 查询支付应用信息
+ */
+const handleGetAppListByMerchantId = () => {
+  queryParams.appId = undefined
+  if (queryParams.merchantId) {
+    AppApi.getAppListByMerchantId(queryParams.merchantId).then((response) => {
+      appList.value = response.data
+    })
+  }
+}
+
+/** 搜索按钮操作 */
+const handleQuery = () => {
+  queryParams.pageNo = 1
+  getList()
 }
 
-// 新增操作
-const handleCreate = () => {
-  setDialogTile('create')
+/** 查询列表 */
+const getList = async () => {
+  loading.value = true
+  try {
+    const data = await OrderApi.getOrderPage(queryParams)
+    list.value = data.list
+    total.value = data.total
+  } finally {
+    loading.value = false
+  }
 }
 
-// 详情操作
-const handleDetail = async (rowId: number) => {
-  setDialogTile('detail')
-  const res = await OrderApi.getOrder(rowId)
-  detailData.value = res
+/** 重置按钮操作 */
+const resetQuery = () => {
+  queryFormRef.value.resetFields()
+  handleQuery()
 }
+
+/** 导出按钮操作 */
+const handleExport = async () => {
+  // 处理查询参数
+  // 导出的二次确认
+  await message.exportConfirm()
+  // 发起导出
+  exportLoading.value = true
+  const data = await OrderApi.exportOrder(queryParams)
+  download.excel(data, '支付订单.xls')
+}
+/** 预览详情 */
+const formRef = ref()
+const openForm = (id?: number) => {
+  formRef.value.open(id)
+}
+/** 初始化 **/
+onMounted(async () => {
+  await getList()
+})
 </script>
+
+<style>
+.order-font {
+  font-size: 12px;
+  padding: 2px 0;
+}
+</style>

+ 0 - 152
src/views/pay/order/order.data.ts

@@ -1,152 +0,0 @@
-import type { VxeCrudSchema } from '@/hooks/web/useVxeCrudSchemas'
-const { t } = useI18n() // 国际化
-
-// 表单校验
-export const rules = reactive({
-  merchantId: [required],
-  appId: [required],
-  merchantOrderId: [required],
-  subject: [required],
-  body: [required],
-  notifyUrl: [required],
-  notifyStatus: [required],
-  amount: [required],
-  status: [required],
-  userIp: [required],
-  expireTime: [required],
-  refundStatus: [required],
-  refundTimes: [required],
-  refundAmount: [required]
-})
-// CrudSchema
-const crudSchemas = reactive<VxeCrudSchema>({
-  primaryKey: 'id',
-  primaryType: 'seq',
-  primaryTitle: '岗位编号',
-  action: true,
-  columns: [
-    {
-      title: '商户编号',
-      field: 'merchantId',
-      isSearch: true
-    },
-    {
-      title: '应用编号',
-      field: 'appId',
-      isSearch: true
-    },
-    {
-      title: '渠道编号',
-      field: 'channelId'
-    },
-    {
-      title: '渠道编码',
-      field: 'channelCode',
-      isSearch: true
-    },
-    {
-      title: '渠道订单号',
-      field: 'merchantOrderId',
-      isSearch: true
-    },
-    {
-      title: '商品标题',
-      field: 'subject'
-    },
-    {
-      title: '商品描述',
-      field: 'body'
-    },
-    {
-      title: '异步通知地址',
-      field: 'notifyUrl'
-    },
-    {
-      title: '回调状态',
-      field: 'notifyStatus',
-      dictType: DICT_TYPE.PAY_ORDER_NOTIFY_STATUS,
-      dictClass: 'number'
-    },
-    {
-      title: '支付金额',
-      field: 'amount',
-      isSearch: true
-    },
-    {
-      title: '渠道手续费',
-      field: 'channelFeeRate',
-      isSearch: true
-    },
-    {
-      title: '渠道手续金额',
-      field: 'channelFeeAmount',
-      isSearch: true
-    },
-    {
-      title: '支付状态',
-      field: 'status',
-      dictType: DICT_TYPE.PAY_ORDER_STATUS,
-      dictClass: 'number',
-      isSearch: true
-    },
-    {
-      title: '用户 IP',
-      field: 'userIp'
-    },
-    {
-      title: '订单失效时间',
-      field: 'expireTime',
-      formatter: 'formatDate'
-    },
-    {
-      title: '支付时间',
-      field: 'successTime',
-      formatter: 'formatDate'
-    },
-    {
-      title: '支付通知时间',
-      field: 'notifyTime',
-      formatter: 'formatDate'
-    },
-    {
-      title: '拓展编号',
-      field: 'successExtensionId'
-    },
-    {
-      title: '退款状态',
-      field: 'refundStatus',
-      dictType: DICT_TYPE.PAY_ORDER_REFUND_STATUS,
-      dictClass: 'number',
-      isSearch: true
-    },
-    {
-      title: '退款次数',
-      field: 'refundTimes'
-    },
-    {
-      title: '退款总金额',
-      field: 'refundAmount'
-    },
-    {
-      title: '渠道用户编号',
-      field: 'channelUserId'
-    },
-    {
-      title: '渠道订单号',
-      field: 'channelOrderNo'
-    },
-    {
-      title: t('common.createTime'),
-      field: 'createTime',
-      formatter: 'formatDate',
-      isForm: false,
-      search: {
-        show: true,
-        itemRender: {
-          name: 'XDataTimePicker'
-        }
-      }
-    }
-  ]
-})
-export const { allSchemas } = useVxeCrudSchemas(crudSchemas)

+ 152 - 0
src/views/pay/order/orderForm.vue

@@ -0,0 +1,152 @@
+<template>
+  <Dialog :title="dialogTitle" v-model="dialogVisible" width="50%">
+    <el-descriptions :column="2" label-class-name="desc-label">
+      <el-descriptions-item label="商户名称">{{ orderDetail.merchantName }}</el-descriptions-item>
+      <el-descriptions-item label="应用名称">{{ orderDetail.appName }}</el-descriptions-item>
+      <el-descriptions-item label="商品名称">{{ orderDetail.subject }}</el-descriptions-item>
+    </el-descriptions>
+    <el-divider />
+    <el-descriptions :column="2" label-class-name="desc-label">
+      <el-descriptions-item label="商户订单号">
+        <el-tag size="small">{{ orderDetail.merchantOrderId }}</el-tag>
+      </el-descriptions-item>
+      <el-descriptions-item label="渠道订单号">
+        <el-tag class="tag-purple" size="small">{{ orderDetail.channelOrderNo }}</el-tag>
+      </el-descriptions-item>
+      <el-descriptions-item label="支付订单号">
+        <el-tag v-if="orderDetail.payOrderExtension.no !== ''" class="tag-pink" size="small">
+          {{ orderDetail.payOrderExtension.no }}
+        </el-tag>
+      </el-descriptions-item>
+      <el-descriptions-item label="金额">
+        <el-tag type="success" size="small">{{ parseFloat(orderDetail.amount / 100, 2) }}</el-tag>
+      </el-descriptions-item>
+      <el-descriptions-item label="手续费">
+        <el-tag type="warning" size="small"
+          >{{ parseFloat(orderDetail.channelFeeAmount / 100, 2) }}
+        </el-tag>
+      </el-descriptions-item>
+      <el-descriptions-item label="手续费比例">
+        {{ parseFloat(orderDetail.channelFeeRate / 100, 2) }}%
+      </el-descriptions-item>
+      <el-descriptions-item label="支付状态">
+        <dict-tag :type="DICT_TYPE.PAY_ORDER_STATUS" :value="orderDetail.status" />
+      </el-descriptions-item>
+      <el-descriptions-item label="回调状态">
+        <dict-tag :type="DICT_TYPE.PAY_ORDER_NOTIFY_STATUS" :value="orderDetail.notifyStatus" />
+      </el-descriptions-item>
+      <el-descriptions-item label="回调地址">{{ orderDetail.notifyUrl }}</el-descriptions-item>
+      <el-descriptions-item label="创建时间" :formatter="dateFormatter">
+        {{ formatDate(orderDetail.createTime) }}
+      </el-descriptions-item>
+      <el-descriptions-item label="支付时间" :formatter="dateFormatter">
+        {{ formatDate(orderDetail.successTime) }}
+      </el-descriptions-item>
+      <el-descriptions-item label="失效时间" :formatter="dateFormatter">
+        {{ formatDate(orderDetail.expireTime) }}
+      </el-descriptions-item>
+      <el-descriptions-item label="通知时间" :formatter="dateFormatter">
+        {{ formatDate(orderDetail.notifyTime) }}
+      </el-descriptions-item>
+    </el-descriptions>
+    <el-divider />
+    <el-descriptions :column="2" label-class-name="desc-label">
+      <el-descriptions-item label="支付渠道"
+        >{{ orderDetail.channelCodeName }}
+      </el-descriptions-item>
+      <el-descriptions-item label="支付IP">{{ orderDetail.userIp }}</el-descriptions-item>
+      <el-descriptions-item label="退款状态">
+        <dict-tag :type="DICT_TYPE.PAY_ORDER_REFUND_STATUS" :value="orderDetail.refundStatus" />
+      </el-descriptions-item>
+      <el-descriptions-item label="退款次数">{{ orderDetail.refundTimes }}</el-descriptions-item>
+      <el-descriptions-item label="退款金额">
+        <el-tag type="warning">
+          {{ parseFloat(orderDetail.refundAmount / 100, 2) }}
+        </el-tag>
+      </el-descriptions-item>
+    </el-descriptions>
+    <el-divider />
+    <el-descriptions :column="1" label-class-name="desc-label" direction="vertical" border>
+      <el-descriptions-item label="商品描述">
+        {{ orderDetail.body }}
+      </el-descriptions-item>
+      <el-descriptions-item label="支付通道异步回调内容">
+        {{ orderDetail.payOrderExtension.channelNotifyData }}
+      </el-descriptions-item>
+    </el-descriptions>
+  </Dialog>
+</template>
+<script setup lang="ts" name="orderForm">
+import { DICT_TYPE } from '@/utils/dict'
+import * as OrderApi from '@/api/pay/order'
+import { dateFormatter, formatDate } from '@/utils/formatTime'
+
+const { t } = useI18n() // 国际化
+// const message = useMessage() // 消息弹窗
+const dialogVisible = ref(false) // 弹窗的是否展示
+const dialogTitle = ref('订单详情') // 弹窗的标题
+const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
+const defaultOrderDetail = {
+  merchantName: '',
+  appName: '',
+  channelCodeName: '',
+  subject: '',
+  merchantOrderId: null,
+  channelOrderNo: '',
+  body: '',
+  amount: null,
+  channelFeeRate: null,
+  channelFeeAmount: null,
+  userIp: '',
+  status: null,
+  notifyUrl: '',
+  notifyStatus: null,
+  refundStatus: null,
+  refundTimes: '',
+  refundAmount: null,
+  createTime: '',
+  successTime: '',
+  notifyTime: '',
+  expireTime: '',
+  payOrderExtension: {
+    channelNotifyData: '',
+    no: ''
+  }
+}
+const orderDetail = ref(JSON.parse(JSON.stringify(defaultOrderDetail)))
+
+/** 打开弹窗 */
+const open = async (id?: number) => {
+  dialogVisible.value = true
+  dialogTitle.value = t('action.preview')
+  // 修改时,设置数据
+  if (id) {
+    formLoading.value = true
+    try {
+      orderDetail.value = await OrderApi.getOrderDetail(id)
+      if (orderDetail.value.payOrderExtension === null) {
+        orderDetail.value.payOrderExtension = Object.assign(
+          defaultOrderDetail.payOrderExtension,
+          {}
+        )
+      }
+    } finally {
+      formLoading.value = false
+    }
+  }
+}
+defineExpose({ open }) // 提供 open 方法,用于打开弹窗
+</script>
+<style>
+.tag-purple {
+  color: #722ed1;
+  background: #f9f0ff;
+  border-color: #d3adf7;
+}
+
+.tag-pink {
+  color: #eb2f96;
+  background: #fff0f6;
+  border-color: #ffadd2;
+}
+</style>

+ 346 - 46
src/views/pay/refund/index.vue

@@ -1,59 +1,359 @@
 <template>
   <ContentWrap>
-    <!-- 列表 -->
-    <XTable @register="registerTable">
-      <template #toolbar_buttons>
-        <!-- 操作:导出 -->
-        <XButton
-          type="warning"
-          preIcon="ep:download"
-          :title="t('action.export')"
-          v-hasPermi="['pay:refund:export']"
-          @click="exportList('退款订单.xls')"
+    <!-- 搜索工作栏 -->
+    <el-form
+      :model="queryParams"
+      ref="queryFormRef"
+      size="small"
+      :inline="true"
+      label-width="120px"
+    >
+      <el-form-item label="所属商户" prop="merchantId">
+        <el-select
+          v-model="queryParams.merchantId"
+          clearable
+          @clear="
+            () => {
+              queryParams.merchantId = null
+            }
+          "
+          filterable
+          remote
+          reserve-keyword
+          placeholder="请选择所属商户"
+          @change="handleGetAppListByMerchantId"
+          :remote-method="handleGetMerchantListByName"
+          :loading="merchantLoading"
+        >
+          <el-option
+            v-for="item in merchantList"
+            :key="item.id"
+            :label="item.name"
+            :value="item.id"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="应用编号" prop="appId">
+        <el-select clearable v-model="queryParams.appId" filterable placeholder="请选择应用信息">
+          <el-option v-for="item in appList" :key="item.id" :label="item.name" :value="item.id" />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="渠道编码" prop="channelCode">
+        <el-select
+          v-model="queryParams.channelCode"
+          placeholder="请输入渠道编码"
+          clearable
+          @clear="
+            () => {
+              queryParams.channelCode = null
+            }
+          "
+        >
+          <el-option
+            v-for="dict in getDictOptions(DICT_TYPE.PAY_CHANNEL_CODE_TYPE)"
+            :key="dict.value"
+            :label="dict.label"
+            :value="dict.value"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="退款类型" prop="type">
+        <el-select v-model="queryParams.type" placeholder="请选择退款类型" clearable>
+          <el-option
+            v-for="dict in getDictOptions(DICT_TYPE.PAY_REFUND_ORDER_TYPE)"
+            :key="parseInt(dict.value)"
+            :label="dict.label"
+            :value="parseInt(dict.value)"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="商户退款订单号" prop="merchantRefundNo">
+        <el-input
+          v-model="queryParams.merchantRefundNo"
+          placeholder="请输入商户退款订单号"
+          clearable
+          @keyup.enter="handleQuery"
         />
-      </template>
-      <template #actionbtns_default="{ row }">
-        <!-- 操作:详情 -->
-        <XTextButton
-          preIcon="ep:view"
-          :title="t('action.detail')"
-          v-hasPermi="['pay:refund:query']"
-          @click="handleDetail(row.id)"
+      </el-form-item>
+      <el-form-item label="退款状态" prop="status">
+        <el-select v-model="queryParams.status" placeholder="请选择退款状态" clearable>
+          <el-option
+            v-for="dict in getDictOptions(DICT_TYPE.PAY_REFUND_ORDER_STATUS)"
+            :key="parseInt(dict.value)"
+            :label="dict.label"
+            :value="parseInt(dict.value)"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="退款回调状态" prop="notifyStatus">
+        <el-select
+          v-model="queryParams.notifyStatus"
+          placeholder="请选择通知商户退款结果的回调状态"
+          clearable
+        >
+          <el-option
+            v-for="dict in getDictOptions(DICT_TYPE.PAY_ORDER_NOTIFY_STATUS)"
+            :key="parseInt(dict.value)"
+            :label="dict.label"
+            :value="parseInt(dict.value)"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="创建时间" prop="createTime">
+        <el-date-picker
+          v-model="queryParams.createTime"
+          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"
         />
-      </template>
-    </XTable>
+      </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
+          type="success"
+          plain
+          @click="handleExport"
+          :loading="exportLoading"
+          v-hasPermi="['system:tenant:export']"
+        >
+          <Icon icon="ep:download" class="mr-5px" />
+          导出
+        </el-button>
+      </el-form-item>
+    </el-form>
   </ContentWrap>
-
-  <XModal v-model="dialogVisible" :title="t('action.detail')">
-    <!-- 对话框(详情) -->
-    <Descriptions :schema="allSchemas.detailSchema" :data="detailData" />
-    <!-- 操作按钮 -->
-    <template #footer>
-      <el-button @click="dialogVisible = false">{{ t('dialog.close') }}</el-button>
-    </template>
-  </XModal>
+  <el-table v-loading="loading" :data="list">
+    <el-table-column label="编号" align="center" prop="id" />
+    <el-table-column label="商户名称" align="center" prop="merchantName" width="120" />
+    <el-table-column label="应用名称" align="center" prop="appName" width="120" />
+    <el-table-column label="渠道名称" align="center" prop="channelCodeName" width="120" />
+    <el-table-column label="交易订单号" align="center" prop="tradeNo" width="140" />
+    <el-table-column label="商户订单编号" align="center" prop="merchantOrderId" width="140" />
+    <el-table-column label="商户订单号" align="left" width="230">
+      <template #default="scope">
+        <p class="order-font">
+          <el-tag size="small">退款</el-tag>
+          {{ scope.row.merchantRefundNo }}
+        </p>
+        <p class="order-font">
+          <el-tag type="success">交易</el-tag>
+          {{ scope.row.merchantOrderId }}
+        </p>
+      </template>
+    </el-table-column>
+    <el-table-column label="支付订单号" align="center" prop="merchantRefundNo" width="250">
+      <template #default="scope">
+        <p class="order-font">
+          <el-tag size="small">交易</el-tag>
+          {{ scope.row.tradeNo }}
+        </p>
+        <p class="order-font">
+          <el-tag size="small" type="warning">渠道</el-tag>
+          {{ scope.row.channelOrderNo }}
+        </p>
+      </template>
+    </el-table-column>
+    <el-table-column label="支付金额(元)" align="center" prop="payAmount" width="100">
+      <template #default="scope">
+        ¥{{ parseFloat(scope.row.payAmount / 100).toFixed(2) }}
+      </template>
+    </el-table-column>
+    <el-table-column label="退款金额(元)" align="center" prop="refundAmount" width="100">
+      <template #default="scope">
+        ¥{{ parseFloat(scope.row.refundAmount / 100).toFixed(2) }}
+      </template>
+    </el-table-column>
+    <el-table-column label="退款类型" align="center" prop="type" width="80">
+      <template #default="scope">
+        <dict-tag :type="DICT_TYPE.PAY_REFUND_ORDER_TYPE" :value="scope.row.type" />
+      </template>
+    </el-table-column>
+    <el-table-column label="退款状态" align="center" prop="status">
+      <template #default="scope">
+        <dict-tag :type="DICT_TYPE.PAY_REFUND_ORDER_STATUS" :value="scope.row.status" />
+      </template>
+    </el-table-column>
+    <el-table-column label="回调状态" align="center" prop="notifyStatus">
+      <template #default="scope">
+        <dict-tag :type="DICT_TYPE.PAY_ORDER_NOTIFY_STATUS" :value="scope.row.notifyStatus" />
+      </template>
+    </el-table-column>
+    <el-table-column
+      label="退款原因"
+      align="center"
+      prop="reason"
+      width="140"
+      :show-overflow-tooltip="true"
+    />
+    <el-table-column
+      label="创建时间"
+      align="center"
+      prop="createTime"
+      width="100"
+      :formatter="dateFormatter"
+    />
+    <el-table-column
+      label="退款成功时间"
+      align="center"
+      prop="successTime"
+      width="100"
+      :formatter="dateFormatter"
+    />
+    <el-table-column
+      label="操作"
+      align="center"
+      fixed="right"
+      class-name="small-padding fixed-width"
+    >
+      <template #default="scope">
+        <el-button
+          size="small"
+          type="text"
+          icon="el-icon-search"
+          @click="openForm(scope.row.id)"
+          v-hasPermi="['pay:order:query']"
+          >查看详情
+        </el-button>
+      </template>
+    </el-table-column>
+  </el-table>
+  <content-wrap>
+    <!-- 分页 -->
+    <Pagination
+      :total="total"
+      v-model:page="queryParams.pageNo"
+      v-model:limit="queryParams.pageSize"
+      @pagination="getList"
+    />
+  </content-wrap>
+  <!-- 表单弹窗:预览 -->
+  <RefundForm ref="formRef" @success="getList" />
 </template>
-<script setup lang="ts" name="PayRefund">
-import { allSchemas } from './refund.data'
+<script setup lang="ts" name="Refund">
+import * as AppApi from '@/api/pay/app'
+import * as MerchantApi from '@/api/pay/merchant'
 import * as RefundApi from '@/api/pay/refund'
+import { DICT_TYPE, getDictOptions } from '@/utils/dict'
+import download from '@/utils/download'
+import { dateFormatter } from '@/utils/formatTime'
+import RefundForm from '@/views/pay/refund/refundForm.vue'
 
-const { t } = useI18n() // 国际化
-
-// 列表相关的变量
-const [registerTable, { exportList }] = useXTable({
-  allSchemas: allSchemas,
-  getListApi: RefundApi.getRefundPage,
-  exportListApi: RefundApi.exportRefund
+const merchantLoading = ref(false) // 商户加载遮罩层
+const message = useMessage() // 消息弹窗
+const appList = ref([]) // 支付应用列表集合
+const merchantList = ref([]) // 商户列表
+const exportLoading = ref(false) // 导出等待
+const loading = ref(false) // 列表遮罩层
+const total = ref(0) // 列表的总页数
+const list = ref([]) // 列表的数据
+const queryFormRef = ref() // 搜索的表单
+const queryParams = reactive({
+  pageNo: 1,
+  pageSize: 10,
+  merchantId: undefined,
+  appId: undefined,
+  channelId: undefined,
+  channelCode: undefined,
+  orderId: undefined,
+  tradeNo: undefined,
+  merchantOrderId: undefined,
+  merchantRefundNo: undefined,
+  notifyUrl: undefined,
+  notifyStatus: undefined,
+  status: undefined,
+  type: undefined,
+  payAmount: undefined,
+  refundAmount: undefined,
+  reason: undefined,
+  userIp: undefined,
+  channelOrderNo: undefined,
+  channelRefundNo: undefined,
+  channelErrorCode: undefined,
+  channelErrorMsg: undefined,
+  channelExtras: undefined,
+  expireTime: [],
+  successTime: [],
+  notifyTime: [],
+  createTime: []
 })
 
-// ========== CRUD 相关 ==========
-const dialogVisible = ref(false) // 是否显示弹出层
-const detailData = ref() // 详情 Ref
+/**
+ * 根据商户 ID 查询支付应用信息
+ */
+const handleGetAppListByMerchantId = () => {
+  queryParams.appId = undefined
+  if (queryParams.merchantId) {
+    AppApi.getAppListByMerchantId(queryParams.merchantId).then((response) => {
+      appList.value = response.data
+    })
+  }
+}
 
-// 详情操作
-const handleDetail = async (rowId: number) => {
-  // 设置数据
-  detailData.value = RefundApi.getRefund(rowId)
-  dialogVisible.value = true
+/**
+ * 根据商户名称模糊匹配商户信息
+ * @param name 商户名称
+ */
+const handleGetMerchantListByName = async (name) => {
+  merchantList.value = await MerchantApi.getMerchantListByName(name)
 }
+
+/** 搜索按钮操作 */
+const handleQuery = () => {
+  queryParams.pageNo = 1
+  getList()
+}
+
+/** 查询列表 */
+const getList = async () => {
+  loading.value = true
+  try {
+    const data = await RefundApi.getRefundPage(queryParams)
+    list.value = data.list
+    total.value = data.total
+  } finally {
+    loading.value = false
+  }
+}
+
+/** 重置按钮操作 */
+const resetQuery = () => {
+  queryFormRef.value.resetFields()
+  handleQuery()
+}
+
+/** 导出按钮操作 */
+const handleExport = async () => {
+  // 导出的二次确认
+  await message.exportConfirm()
+  // 发起导出
+  exportLoading.value = true
+  const data = await RefundApi.exportRefund(queryParams)
+  download.excel(data, '退款订单.xls')
+}
+/** 预览详情 */
+const formRef = ref()
+const openForm = (id?: number) => {
+  formRef.value.open(id)
+}
+/** 初始化 **/
+onMounted(async () => {
+  await getList()
+})
 </script>
+
+<style>
+.order-font {
+  font-size: 12px;
+  padding: 2px 0;
+}
+</style>

+ 0 - 173
src/views/pay/refund/refund.data.ts

@@ -1,173 +0,0 @@
-import type { VxeCrudSchema } from '@/hooks/web/useVxeCrudSchemas'
-const { t } = useI18n() // 国际化
-
-// CrudSchema
-const crudSchemas = reactive<VxeCrudSchema>({
-  primaryKey: 'id',
-  primaryType: 'seq',
-  primaryTitle: '序号',
-  action: true,
-  columns: [
-    {
-      title: '商户编号',
-      field: 'merchantId',
-      isSearch: true
-    },
-    {
-      title: '应用编号',
-      field: 'appId',
-      isSearch: true
-    },
-    {
-      title: '渠道编号',
-      field: 'channelId',
-      isSearch: true
-    },
-    {
-      title: '渠道编码',
-      field: 'channelCode',
-      dictType: DICT_TYPE.PAY_CHANNEL_CODE_TYPE,
-      dictClass: 'number',
-      isSearch: true
-    },
-    {
-      title: '支付订单编号',
-      field: 'orderId',
-      isSearch: true
-    },
-    {
-      title: '交易订单号',
-      field: 'tradeNo',
-      isSearch: true
-    },
-    {
-      title: '商户订单号',
-      field: 'merchantOrderId',
-      isSearch: true
-    },
-    {
-      title: '商户退款单号',
-      field: 'merchantRefundNo',
-      isSearch: true
-    },
-    {
-      title: '回调地址',
-      field: 'notifyUrl',
-      isSearch: true
-    },
-    {
-      title: '回调状态',
-      field: 'notifyStatus',
-      dictType: DICT_TYPE.PAY_ORDER_NOTIFY_STATUS,
-      dictClass: 'number',
-      isSearch: true
-    },
-    {
-      title: '退款类型',
-      field: 'type',
-      dictType: DICT_TYPE.PAY_REFUND_ORDER_TYPE,
-      dictClass: 'number',
-      isSearch: true
-    },
-    {
-      title: t('common.status'),
-      field: 'status',
-      dictType: DICT_TYPE.PAY_REFUND_ORDER_STATUS,
-      dictClass: 'number',
-      isSearch: true
-    },
-    {
-      title: '支付金额',
-      field: 'payAmount',
-      formatter: 'formatAmount',
-      isSearch: true
-    },
-    {
-      title: '退款金额',
-      field: 'refundAmount',
-      formatter: 'formatAmount',
-      isSearch: true
-    },
-    {
-      title: '退款原因',
-      field: 'reason',
-      isSearch: true
-    },
-    {
-      title: '用户IP',
-      field: 'userIp',
-      isSearch: true
-    },
-    {
-      title: '渠道订单号',
-      field: 'channelOrderNo',
-      isSearch: true
-    },
-    {
-      title: '渠道退款单号',
-      field: 'channelRefundNo',
-      isSearch: true
-    },
-    {
-      title: '渠道调用报错时',
-      field: 'channelErrorCode'
-    },
-    {
-      title: '渠道调用报错时',
-      field: 'channelErrorMsg'
-    },
-    {
-      title: '支付渠道的额外参数',
-      field: 'channelExtras'
-    },
-    {
-      title: '退款失效时间',
-      field: 'expireTime',
-      formatter: 'formatDate',
-      isForm: false,
-      search: {
-        show: true,
-        itemRender: {
-          name: 'XDataTimePicker'
-        }
-      }
-    },
-    {
-      title: '退款成功时间',
-      field: 'successTime',
-      formatter: 'formatDate',
-      isForm: false,
-      search: {
-        show: true,
-        itemRender: {
-          name: 'XDataTimePicker'
-        }
-      }
-    },
-    {
-      title: '退款通知时间',
-      field: 'notifyTime',
-      formatter: 'formatDate',
-      isForm: false,
-      search: {
-        show: true,
-        itemRender: {
-          name: 'XDataTimePicker'
-        }
-      }
-    },
-    {
-      title: t('common.createTime'),
-      field: 'createTime',
-      formatter: 'formatDate',
-      isForm: false,
-      search: {
-        show: true,
-        itemRender: {
-          name: 'XDataTimePicker'
-        }
-      }
-    }
-  ]
-})
-export const { allSchemas } = useVxeCrudSchemas(crudSchemas)

+ 154 - 0
src/views/pay/refund/refundForm.vue

@@ -0,0 +1,154 @@
+<template>
+  <Dialog :title="dialogTitle" v-model="dialogVisible" width="50%">
+    <el-descriptions :column="2" label-class-name="desc-label">
+      <el-descriptions-item label="商户名称">{{ refundDetail.merchantName }}</el-descriptions-item>
+      <el-descriptions-item label="应用名称">{{ refundDetail.appName }}</el-descriptions-item>
+      <el-descriptions-item label="商品名称">{{ refundDetail.subject }}</el-descriptions-item>
+    </el-descriptions>
+    <el-divider />
+    <el-descriptions :column="2" label-class-name="desc-label">
+      <el-descriptions-item label="商户退款单号">
+        <el-tag size="small">{{ refundDetail.merchantRefundNo }}</el-tag>
+      </el-descriptions-item>
+      <el-descriptions-item label="商户订单号"
+        >{{ refundDetail.merchantOrderId }}
+      </el-descriptions-item>
+      <el-descriptions-item label="交易订单号">{{ refundDetail.tradeNo }}</el-descriptions-item>
+    </el-descriptions>
+    <el-divider />
+    <el-descriptions :column="2" label-class-name="desc-label">
+      <el-descriptions-item label="支付金额">
+        {{ parseFloat(refundDetail.payAmount / 100).toFixed(2) }}
+      </el-descriptions-item>
+      <el-descriptions-item label="退款金额" size="small">
+        <el-tag class="tag-purple" size="small">
+          {{ parseFloat(refundDetail.refundAmount / 100).toFixed(2) }}
+        </el-tag>
+      </el-descriptions-item>
+      <el-descriptions-item label="退款类型">
+        <dict-tag :type="DICT_TYPE.PAY_REFUND_ORDER_TYPE" :value="refundDetail.type" />
+      </el-descriptions-item>
+      <el-descriptions-item label="退款状态">
+        <dict-tag :type="DICT_TYPE.PAY_REFUND_ORDER_STATUS" :value="refundDetail.status" />
+      </el-descriptions-item>
+      <el-descriptions-item label="创建时间"
+        >{{ formatDate(refundDetail.createTime) }}
+      </el-descriptions-item>
+      <el-descriptions-item label="退款成功时间"
+        >{{ formatDate(refundDetail.successTime) }}
+      </el-descriptions-item>
+      <el-descriptions-item label="退款失效时间"
+        >{{ formatDate(refundDetail.expireTime) }}
+      </el-descriptions-item>
+      <el-descriptions-item label="更新时间"
+        >{{ formatDate(refundDetail.updateTime) }}
+      </el-descriptions-item>
+    </el-descriptions>
+    <el-divider />
+    <el-descriptions :column="2" label-class-name="desc-label">
+      <el-descriptions-item label="支付渠道">
+        {{ refundDetail.channelCodeName }}
+      </el-descriptions-item>
+      <el-descriptions-item label="支付IP" size="small">
+        {{ refundDetail.userIp }}
+      </el-descriptions-item>
+      <el-descriptions-item label="回调地址">{{ refundDetail.notifyUrl }}</el-descriptions-item>
+      <el-descriptions-item label="回调状态">
+        <dict-tag :type="DICT_TYPE.PAY_ORDER_NOTIFY_STATUS" :value="refundDetail.notifyStatus" />
+      </el-descriptions-item>
+      <el-descriptions-item label="回调时间"
+        >{{ formatDate(refundDetail.notifyTime) }}
+      </el-descriptions-item>
+    </el-descriptions>
+    <el-divider />
+    <el-descriptions :column="2" label-class-name="desc-label">
+      <el-descriptions-item label="渠道订单号"
+        >{{ refundDetail.channelOrderNo }}
+      </el-descriptions-item>
+      <el-descriptions-item label="渠道退款单号"
+        >{{ refundDetail.channelRefundNo }}
+      </el-descriptions-item>
+      <el-descriptions-item label="渠道错误码"
+        >{{ refundDetail.channelErrorCode }}
+      </el-descriptions-item>
+      <el-descriptions-item label="渠道错误码描述"
+        >{{ refundDetail.channelErrorMsg }}
+      </el-descriptions-item>
+    </el-descriptions>
+    <br />
+    <el-descriptions :column="1" label-class-name="desc-label" direction="vertical" border>
+      <el-descriptions-item label="渠道额外参数"
+        >{{ refundDetail.channelExtras }}
+      </el-descriptions-item>
+      <el-descriptions-item label="退款原因">{{ refundDetail.reason }}</el-descriptions-item>
+    </el-descriptions>
+  </Dialog>
+</template>
+<script setup lang="ts" name="refundForm">
+import { DICT_TYPE } from '@/utils/dict'
+import * as RefundApi from '@/api/pay/refund'
+import { formatDate } from '@/utils/formatTime'
+
+const { t } = useI18n() // 国际化
+// const message = useMessage() // 消息弹窗
+const dialogVisible = ref(false) // 弹窗的是否展示
+const dialogTitle = ref('退款订单详情') // 弹窗的标题
+const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
+const defaultrefundDetail = {
+  id: null,
+  appId: null,
+  appName: '',
+  channelCode: '',
+  channelCodeName: '',
+  channelErrorCode: '',
+  channelErrorMsg: '',
+  channelExtras: '',
+  channelId: null,
+  channelOrderNo: '',
+  channelRefundNo: '',
+  createTime: null,
+  expireTime: null,
+  merchantId: null,
+  merchantName: '',
+  merchantOrderId: '',
+  merchantRefundNo: '',
+  notifyStatus: null,
+  notifyTime: null,
+  notifyUrl: '',
+  orderId: null,
+  payAmount: null,
+  reason: '',
+  refundAmount: null,
+  status: null,
+  subject: '',
+  successTime: null,
+  tradeNo: '',
+  type: null,
+  userIp: ''
+}
+const refundDetail = ref(JSON.parse(JSON.stringify(defaultrefundDetail)))
+
+/** 打开弹窗 */
+const open = async (id?: number) => {
+  dialogVisible.value = true
+  dialogTitle.value = t('action.preview')
+  // 修改时,设置数据
+  if (id) {
+    formLoading.value = true
+    try {
+      refundDetail.value = await RefundApi.getRefundApi(id)
+    } finally {
+      formLoading.value = false
+    }
+  }
+}
+defineExpose({ open }) // 提供 open 方法,用于打开弹窗
+</script>
+
+<style>
+.tag-purple {
+  color: #722ed1;
+  background: #f9f0ff;
+  border-color: #d3adf7;
+}
+</style>