Pārlūkot izejas kodu

新增支付宝支付配置项

jialuyu 1 mēnesi atpakaļ
vecāks
revīzija
1ebf4cdaa6

+ 15 - 0
implementation_plan.md

@@ -0,0 +1,15 @@
+# 支付宝支付配置前端实现计划
+
+本计划旨在 `sj-admin-web` 的系统配置模块中,新增支付宝支付配置项,并实现微信支付与支付宝支付之间的 Tab 切换。
+
+## 目标
+- 在支付配置选项卡中引入子选项卡。
+- 实现支付宝支付的前端界面配置功能。
+
+## 实现步骤
+1. **新建 `alipay.vue` 组件**:
+   - 包含支付宝应用ID、应用私钥的文本输入。
+   - 包含应用公钥、支付宝公钥、支付宝根证书的文件上传功能。
+2. **更新 `index.vue`**:
+   - 在“支付配置”模块增加 Element Plus 的 `el-tabs` 组件。
+   - 整合 `wxpay.vue` 与 `alipay.vue`。

+ 247 - 0
src/views/system/sysconfig/alipay.vue

@@ -0,0 +1,247 @@
+<template>
+  <div class="p-2">
+    <el-card shadow="hover">
+      <template #header>
+        <div class="card-header">
+          <span>支付宝支付配置</span>
+          <el-button type="primary" @click="handleSave" :loading="saving">保存配置</el-button>
+        </div>
+      </template>
+
+      <el-form :model="form" label-width="140px" v-loading="loading">
+        <el-form-item label="支付宝应用ID">
+          <el-input v-model="form.appId" placeholder="请输入支付宝应用ID (AppID)" style="width: 500px" />
+        </el-form-item>
+
+        <el-form-item label="应用私钥">
+          <el-input 
+            v-model="form.appPrivateKey" 
+            type="textarea" 
+            :rows="4" 
+            placeholder="请输入支付宝应用私钥" 
+            style="width: 500px" 
+          />
+        </el-form-item>
+
+        <el-form-item label="应用公钥">
+          <div>
+            <el-input v-model="form.appPublicKeyPath"  style="width: 500px; margin-bottom: 10px;" />
+            <el-upload
+              action="#"
+              :auto-upload="false"
+              :show-file-list="false"
+              accept=".crt,.txt,.pem"
+              :on-change="handleAppPublicKeyChange"
+            >
+              <el-button type="primary">上传应用公钥文件</el-button>
+            </el-upload>
+            <div class="tip-text">支付宝应用公钥文件 (appCertPublicKey_*.crt),在支付宝开放平台下载</div>
+            <div v-if="form.appPublicKeyUploaded" class="success-text">✓ 应用公钥已上传</div>
+          </div>
+        </el-form-item>
+
+        <el-form-item label="支付宝公钥">
+          <div>
+            <el-input v-model="form.alipayPublicKeyPath"  style="width: 500px; margin-bottom: 10px;" />
+            <el-upload
+              action="#"
+              :auto-upload="false"
+              :show-file-list="false"
+              accept=".crt,.txt,.pem"
+              :on-change="handleAlipayPublicKeyChange"
+            >
+              <el-button type="primary">上传支付宝公钥文件</el-button>
+            </el-upload>
+            <div class="tip-text">支付宝公钥文件 (alipayCertPublicKey_RSA2.crt),在支付宝开放平台下载</div>
+            <div v-if="form.alipayPublicKeyUploaded" class="success-text">✓ 支付宝公钥已上传</div>
+          </div>
+        </el-form-item>
+
+        <el-form-item label="支付宝根证书">
+          <div>
+            <el-input v-model="form.alipayRootCertPath"  style="width: 500px; margin-bottom: 10px;" />
+            <el-upload
+              action="#"
+              :auto-upload="false"
+              :show-file-list="false"
+              accept=".crt,.pem"
+              :on-change="handleAlipayRootCertChange"
+            >
+              <el-button type="primary">上传支付宝根证书</el-button>
+            </el-upload>
+            <div class="tip-text">支付宝根证书文件 (alipayRootCert.crt),在支付宝开放平台下载</div>
+            <div v-if="form.alipayRootCertUploaded" class="success-text">✓ 支付宝根证书已上传</div>
+          </div>
+        </el-form-item>
+
+      </el-form>
+    </el-card>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { ref, onMounted } from 'vue'
+import { ElMessage } from 'element-plus'
+import request from '@/utils/request'
+
+// 状态控制
+const loading = ref(false)
+const saving = ref(false)
+
+// 文件上传的暂存状态
+const appPublicKeyFile = ref<File | null>(null)
+const alipayPublicKeyFile = ref<File | null>(null)
+const alipayRootCertFile = ref<File | null>(null)
+
+// 表单数据
+const form = ref({
+  appId: '',
+  appPrivateKey: '',
+  appPublicKeyPath: '',
+  alipayPublicKeyPath: '',
+  alipayRootCertPath: '',
+  appPublicKeyUploaded: false,
+  alipayPublicKeyUploaded: false,
+  alipayRootCertUploaded: false
+})
+
+// 处理应用公钥选择
+const handleAppPublicKeyChange = (file: any) => {
+  appPublicKeyFile.value = file.raw
+  ElMessage.success('应用公钥文件已选择,保存时将一并上传')
+}
+
+// 处理支付宝公钥选择
+const handleAlipayPublicKeyChange = (file: any) => {
+  alipayPublicKeyFile.value = file.raw
+  ElMessage.success('支付宝公钥文件已选择,保存时将一并上传')
+}
+
+// 处理支付宝根证书选择
+const handleAlipayRootCertChange = (file: any) => {
+  alipayRootCertFile.value = file.raw
+  ElMessage.success('支付宝根证书文件已选择,保存时将一并上传')
+}
+
+// 获取配置信息
+const getConfig = async () => {
+  loading.value = true
+  try {
+    // 调用后端的获取支付宝配置接口
+    const res = await request.get('/miniapp/paymentConfig/alipay')
+    if (res.code === 200 && res.data) {
+      form.value.appId = res.data.appId || ''
+      form.value.appPrivateKey = res.data.appPrivateKey || ''
+      form.value.appPublicKeyPath = res.data.appPublicKeyPath || ''
+      form.value.alipayPublicKeyPath = res.data.alipayPublicKeyPath || ''
+      form.value.alipayRootCertPath = res.data.alipayRootCertPath || ''
+      form.value.appPublicKeyUploaded = res.data.appPublicKeyUploaded || false
+      form.value.alipayPublicKeyUploaded = res.data.alipayPublicKeyUploaded || false
+      form.value.alipayRootCertUploaded = res.data.alipayRootCertUploaded || false
+    }
+  } catch (e) {
+    console.error('获取支付宝配置失败', e)
+  } finally {
+    loading.value = false
+  }
+}
+
+// 保存配置
+const handleSave = async () => {
+  saving.value = true
+  try {
+    // 1. 上传应用公钥
+    if (appPublicKeyFile.value) {
+      const formData = new FormData()
+      formData.append('file', appPublicKeyFile.value)
+      const uploadRes = await request.post('/miniapp/paymentConfig/uploadAlipayAppPublicKey', formData, {
+        headers: { 'Content-Type': 'multipart/form-data' }
+      })
+      if (uploadRes.code !== 200) {
+        ElMessage.error('应用公钥上传失败:' + uploadRes.msg)
+        saving.value = false
+        return
+      }
+      form.value.appPublicKeyUploaded = true
+      form.value.appPublicKeyPath = uploadRes.data
+      appPublicKeyFile.value = null
+    }
+
+    // 2. 上传支付宝公钥
+    if (alipayPublicKeyFile.value) {
+      const formData = new FormData()
+      formData.append('file', alipayPublicKeyFile.value)
+      const uploadRes = await request.post('/miniapp/paymentConfig/uploadAlipayPublicKey', formData, {
+        headers: { 'Content-Type': 'multipart/form-data' }
+      })
+      if (uploadRes.code !== 200) {
+        ElMessage.error('支付宝公钥上传失败:' + uploadRes.msg)
+        saving.value = false
+        return
+      }
+      form.value.alipayPublicKeyUploaded = true
+      form.value.alipayPublicKeyPath = uploadRes.data
+      alipayPublicKeyFile.value = null
+    }
+
+    // 3. 上传支付宝根证书
+    if (alipayRootCertFile.value) {
+      const formData = new FormData()
+      formData.append('file', alipayRootCertFile.value)
+      const uploadRes = await request.post('/miniapp/paymentConfig/uploadAlipayRootCert', formData, {
+        headers: { 'Content-Type': 'multipart/form-data' }
+      })
+      if (uploadRes.code !== 200) {
+        ElMessage.error('支付宝根证书上传失败:' + uploadRes.msg)
+        saving.value = false
+        return
+      }
+      form.value.alipayRootCertUploaded = true
+      form.value.alipayRootCertPath = uploadRes.data
+      alipayRootCertFile.value = null
+    }
+    
+    // 4. 保存文本配置
+    const res = await request.put('/miniapp/paymentConfig/alipay', {
+      appId: form.value.appId,
+      appPrivateKey: form.value.appPrivateKey,
+      appPublicKeyPath: form.value.appPublicKeyPath,
+      alipayPublicKeyPath: form.value.alipayPublicKeyPath,
+      alipayRootCertPath: form.value.alipayRootCertPath
+    })
+    
+    if (res.code === 200) {
+      ElMessage.success('保存成功')
+      getConfig() // 重新获取最新配置
+    } else {
+      ElMessage.error(res.msg || '保存失败')
+    }
+  } catch (e) {
+    ElMessage.error('保存失败')
+  } finally {
+    saving.value = false
+  }
+}
+
+onMounted(() => {
+  getConfig()
+})
+</script>
+
+<style scoped>
+.card-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+}
+.tip-text {
+  color: #909399;
+  font-size: 12px;
+  margin-top: 4px;
+}
+.success-text {
+  color: #67c23a;
+  font-size: 12px;
+  margin-top: 4px;
+}
+</style>

+ 22 - 1
src/views/system/sysconfig/index.vue

@@ -13,7 +13,20 @@
         <el-tab-pane label="短信配置" name="sms" />
         <el-tab-pane label="支付配置" name="payment">
           <div class="agreement-container animated fadeIn">
-            <wxpay-config v-if="activeMainTab === 'payment'" />
+            <!-- 支付配置子选项卡 -->
+            <div class="sub-tabs-wrapper">
+              <div 
+                v-for="tab in paymentSubTabs" 
+                :key="tab.name"
+                :class="['sub-tab-item', activePaymentSubTab === tab.name ? 'active' : '']"
+                @click="activePaymentSubTab = tab.name"
+              >
+                {{ tab.label }}
+              </div>
+            </div>
+            
+            <wxpay-config v-if="activeMainTab === 'payment' && activePaymentSubTab === 'wxpay'" />
+            <alipay-config v-if="activeMainTab === 'payment' && activePaymentSubTab === 'alipay'" />
           </div>
         </el-tab-pane>
         <el-tab-pane label="协议配置" name="agreement">
@@ -63,6 +76,7 @@
 import { ref, reactive, watch, onMounted } from 'vue';
 import Editor from '@/components/Editor/index.vue';
 import WxpayConfig from './wxpay.vue';
+import AlipayConfig from './alipay.vue';
 import StorageConfig from './storage.vue';
 import { ElMessage } from 'element-plus';
 import request from '@/utils/request';
@@ -70,6 +84,13 @@ import request from '@/utils/request';
 // 主选项卡
 const activeMainTab = ref('agreement');
 
+// 支付子选项卡
+const activePaymentSubTab = ref('wxpay');
+const paymentSubTabs = [
+  { label: '微信小程序支付', name: 'wxpay' },
+  { label: '支付宝支付', name: 'alipay' }
+];
+
 // 表单数据
 const form = reactive({
   title: '用户服务协议',

+ 2 - 1
src/views/system/sysconfig/wxpay.vue

@@ -173,7 +173,7 @@ const handleSave = async () => {
         return
       }
       form.value.privateKeyUploaded = true
-      // 如果上传成功,后端通常会返回路径,这里我们可以刷新一下或者让后端直接在保存接口处理
+      form.value.privateKeyPath = uploadRes.data
       privateKeyFile.value = null
     }
 
@@ -190,6 +190,7 @@ const handleSave = async () => {
         return
       }
       form.value.certUploaded = true
+      form.value.certPath = uploadRes.data
       certFile.value = null
     }
 

+ 5 - 0
task.md

@@ -0,0 +1,5 @@
+# 支付配置任务列表
+
+- [x] 1. 新建支付宝支付配置组件 `alipay.vue` <!-- id: 0 -->
+- [x] 2. 在 `index.vue` 中重构支付配置 Tab,支持微信支付和支付宝支付的切换 <!-- id: 1 -->
+- [ ] 3. 验证前端配置界面渲染是否正常 <!-- id: 2 -->