ソースを参照

chore: 构建前端+修复apikey文档页标签闭合

alphah 2 週間 前
コミット
f9d09082c4
2 ファイル変更289 行追加90 行削除
  1. BIN
      frontend/dist.zip
  2. 289 90
      frontend/src/views/module_payment/apikey/index.vue

BIN
frontend/dist.zip


+ 289 - 90
frontend/src/views/module_payment/apikey/index.vue

@@ -138,9 +138,11 @@
                       type="primary"
                       size="small"
                       link
-                      @click="handleUpdateStatus(scope.row.id, scope.row.status === '0' ? '1' : '0')"
+                      @click="
+                        handleUpdateStatus(scope.row.id, scope.row.status === '0' ? '1' : '0')
+                      "
                     >
-                      {{ scope.row.status === '0' ? '禁用' : '启用' }}
+                      {{ scope.row.status === "0" ? "禁用" : "启用" }}
                     </el-button>
                     <el-button
                       v-hasPerm="['module_system:tenant:api-key:delete']"
@@ -176,7 +178,13 @@
               <el-input v-model="formData.tenant_id" placeholder="可选,默认使用当前租户" :maxlength="20" />
             </el-form-item> -->
             <el-form-item label="过期天数" prop="expired_days">
-              <el-input v-model.number="formData.expired_days" type="number" placeholder="请输入过期天数" min="1" :maxlength="4" />
+              <el-input
+                v-model.number="formData.expired_days"
+                type="number"
+                placeholder="请输入过期天数"
+                min="1"
+                :maxlength="4"
+              />
             </el-form-item>
             <el-form-item label="描述" prop="description">
               <el-input
@@ -240,7 +248,11 @@
             <el-descriptions-item label="API Secret">
               <div class="api-key-detail">
                 <span>{{ apiKeyDetail.api_secret }}</span>
-                <el-button type="text" size="small" @click="copyToClipboard(apiKeyDetail.api_secret || '')">
+                <el-button
+                  type="text"
+                  size="small"
+                  @click="copyToClipboard(apiKeyDetail.api_secret || '')"
+                >
                   复制
                 </el-button>
               </div>
@@ -294,14 +306,10 @@
             </template>
             <div class="config-empty">
               <el-empty description="暂无开放平台配置" />
-              <p style="margin: 16px 0; color: #606266;">
+              <p style="margin: 16px 0; color: #606266">
                 请先创建开放平台配置,才能收到平台回调通知。
               </p>
-              <el-button
-                type="primary"
-                :loading="configLoading"
-                @click="handleCreateConfig"
-              >
+              <el-button type="primary" :loading="configLoading" @click="handleCreateConfig">
                 创建配置
               </el-button>
             </div>
@@ -347,15 +355,11 @@
                 />
               </el-form-item> -->
               <el-form-item label="回调地址">
-                <el-input
-                  v-model="configForm.return_url"
-                  placeholder="请输入回调地址"
-                  clearable
-                />
+                <el-input v-model="configForm.return_url" placeholder="请输入回调地址" clearable />
               </el-form-item>
               <el-form-item label="状态">
                 <el-tag :type="configForm.status === 'ENABLED' ? 'success' : 'info'">
-                  {{ configForm.status === 'ENABLED' ? '启用' : '禁用' }}
+                  {{ configForm.status === "ENABLED" ? "启用" : "禁用" }}
                 </el-tag>
               </el-form-item>
               <!-- <el-form-item label="描述">
@@ -367,11 +371,7 @@
                 />
               </el-form-item> -->
               <el-form-item>
-                <el-button
-                  type="primary"
-                  :loading="configLoading"
-                  @click="handleSaveConfig"
-                >
+                <el-button type="primary" :loading="configLoading" @click="handleSaveConfig">
                   保存配置
                 </el-button>
               </el-form-item>
@@ -389,31 +389,71 @@
               <div class="sidebar-section">
                 <h3>认证</h3>
                 <ul>
-                  <li @click="activeSection = 'auth'" :class="{ active: activeSection === 'auth' }">认证方式</li>
-                  <li @click="activeSection = 'signature'" :class="{ active: activeSection === 'signature' }">签名验证</li>
-                  <li @click="activeSection = 'notes'" :class="{ active: activeSection === 'notes' }">注意事项</li>
+                  <li @click="activeSection = 'auth'" :class="{ active: activeSection === 'auth' }">
+                    认证方式
+                  </li>
+                  <li
+                    @click="activeSection = 'signature'"
+                    :class="{ active: activeSection === 'signature' }"
+                  >
+                    签名验证
+                  </li>
+                  <li
+                    @click="activeSection = 'notes'"
+                    :class="{ active: activeSection === 'notes' }"
+                  >
+                    注意事项
+                  </li>
                 </ul>
               </div>
               <div class="sidebar-section">
                 <h3>账户接口</h3>
                 <ul>
-                  <li @click="activeSection = 'transfer'" :class="{ active: activeSection === 'transfer' }">发起转账</li>
-                  <li @click="activeSection = 'transfer_query'" :class="{ active: activeSection === 'transfer_query' }">查询转账</li>
-                  <li @click="activeSection = 'balance'" :class="{ active: activeSection === 'balance' }">账户余额</li>
+                  <li
+                    @click="activeSection = 'transfer'"
+                    :class="{ active: activeSection === 'transfer' }"
+                  >
+                    发起转账
+                  </li>
+                  <li
+                    @click="activeSection = 'transfer_query'"
+                    :class="{ active: activeSection === 'transfer_query' }"
+                  >
+                    查询转账
+                  </li>
+                  <li
+                    @click="activeSection = 'balance'"
+                    :class="{ active: activeSection === 'balance' }"
+                  >
+                    账户余额
+                  </li>
                 </ul>
               </div>
               <div class="sidebar-section">
                 <h3>回调通知</h3>
                 <ul>
-                  <li @click="activeSection = 'callback'" :class="{ active: activeSection === 'callback' }">通知接口</li>
+                  <li
+                    @click="activeSection = 'callback'"
+                    :class="{ active: activeSection === 'callback' }"
+                  >
+                    通知接口
+                  </li>
                 </ul>
               </div>
               <div class="sidebar-section">
                 <h3>其他</h3>
                 <ul>
-                  <li @click="activeSection = 'errors'" :class="{ active: activeSection === 'errors' }">常见错误</li>
-                  <li @click="activeSection = 'php'" :class="{ active: activeSection === 'php' }">PHP示例代码</li>
-                </ul> identity_type
+                  <li
+                    @click="activeSection = 'errors'"
+                    :class="{ active: activeSection === 'errors' }"
+                  >
+                    常见错误
+                  </li>
+                  <li @click="activeSection = 'php'" :class="{ active: activeSection === 'php' }">
+                    PHP示例代码
+                  </li>
+                </ul>
+                identity_type
               </div>
             </el-scrollbar>
           </div>
@@ -434,8 +474,15 @@
 Signature: {signature}</code></pre>
                 <p>其中:</p>
                 <ul>
-                  <li><strong>Authorization</strong>:API Key认证头,格式为 <code>ApiKey {api_key}</code></li>
-                  <li><strong>Signature</strong>:请求签名(必填),用于验证请求数据的完整性</li>
+                  <li>
+                    <strong>Authorization</strong>
+                    :API Key认证头,格式为
+                    <code>ApiKey {api_key}</code>
+                  </li>
+                  <li>
+                    <strong>Signature</strong>
+                    :请求签名(必填),用于验证请求数据的完整性
+                  </li>
                 </ul>
               </div>
 
@@ -443,13 +490,36 @@ Signature: {signature}</code></pre>
                 <h2>2. 签名验证</h2>
                 <p>签名用于验证请求数据的完整性,防止数据被篡改。签名生成步骤:</p>
                 <ol>
-                  <li>过滤请求参数:排除 <code>sign</code> 参数、<code>null</code> 值、空字符串、空数组、空对象</li>
+                  <li>
+                    过滤请求参数:排除
+                    <code>sign</code>
+                    参数、
+                    <code>null</code>
+                    值、空字符串、空数组、空对象
+                  </li>
                   <li>将过滤后的参数按参数名ASCII码升序排序</li>
-                  <li>对字典或列表类型的值进行JSON序列化(<code>sort_keys=true</code>,<code>separators=(',', ':')</code>)</li>
-                  <li>对每个参数值进行URL编码(<code>UTF-8</code>编码)</li>
-                  <li>将排序后的参数拼接为字符串:<code>key1=value1&amp;key2=value2</code></li>
+                  <li>
+                    对字典或列表类型的值进行JSON序列化(
+                    <code>sort_keys=true</code>
+                    ,
+                    <code>separators=(',', ':')</code>
+                    )
+                  </li>
+                  <li>
+                    对每个参数值进行URL编码(
+                    <code>UTF-8</code>
+                    编码)
+                  </li>
+                  <li>
+                    将排序后的参数拼接为字符串:
+                    <code>key1=value1&amp;key2=value2</code>
+                  </li>
                   <li>使用API Secret作为密钥,通过HMAC-SHA256算法生成签名</li>
-                  <li>将签名添加到请求头 <code>Signature</code> 中</li>
+                  <li>
+                    将签名添加到请求头
+                    <code>Signature</code>
+                    中
+                  </li>
                 </ol>
                 <h3>2.1 签名计算示例</h3>
                 <pre><code># 原始请求数据
@@ -500,10 +570,24 @@ Signature: {signature}</code></pre>
                 <h2>3. 注意事项</h2>
                 <ul>
                   <li>API Key和Secret请妥善保管,不要泄露给他人</li>
-                  <li>签名验证是<strong>必填</strong>的,未带签名或签名错误将返回401</li>
+                  <li>
+                    签名验证是
+                    <strong>必填</strong>
+                    的,未带签名或签名错误将返回401
+                  </li>
                   <li>签名使用HMAC-SHA256算法,密钥为API Secret</li>
-                  <li>签名计算前会自动过滤:<code>sign</code>参数、<code>null</code>值、空字符串、空数组、空对象</li>
-                  <li>嵌套对象(如<code>payee_info</code>)会先进行JSON序列化再参与签名</li>
+                  <li>
+                    签名计算前会自动过滤:
+                    <code>sign</code>
+                    参数、
+                    <code>null</code>
+                    值、空字符串、空数组、空对象
+                  </li>
+                  <li>
+                    嵌套对象(如
+                    <code>payee_info</code>
+                    )会先进行JSON序列化再参与签名
+                  </li>
                   <li>参数值会进行URL编码(UTF-8),确保中文字符正确处理</li>
                   <li>定期更新API Key,建议每3-6个月更换一次</li>
                   <li>如发现API Key泄露,请立即禁用并重新生成</li>
@@ -567,7 +651,7 @@ Signature: {signature}</code></pre>
                       <td>
                         <div class="expandable-section">
                           <span @click="toggleExpand('payee_info')" class="expand-btn">
-                            {{ expandedSections.payee_info ? '▼' : '▶' }} 收款方信息
+                            {{ expandedSections.payee_info ? "▼" : "▶" }} 收款方信息
                           </span>
                           <div v-if="expandedSections.payee_info" class="expandable-content">
                             <table class="api-table nested-table">
@@ -604,10 +688,17 @@ Signature: {signature}</code></pre>
                                   <td>否</td>
                                   <td>
                                     <div class="expandable-section">
-                                      <span @click="toggleExpand('bankcard_ext_info')" class="expand-btn">
-                                        {{ expandedSections.bankcard_ext_info ? '▼' : '▶' }} 银行卡信息(当 identity_type 为 bank 时必填)
+                                      <span
+                                        @click="toggleExpand('bankcard_ext_info')"
+                                        class="expand-btn"
+                                      >
+                                        {{ expandedSections.bankcard_ext_info ? "▼" : "▶" }}
+                                        银行卡信息(当 identity_type 为 bank 时必填)
                                       </span>
-                                      <div v-if="expandedSections.bankcard_ext_info" class="expandable-content">
+                                      <div
+                                        v-if="expandedSections.bankcard_ext_info"
+                                        class="expandable-content"
+                                      >
                                         <table class="api-table nested-table">
                                           <thead>
                                             <tr>
@@ -696,7 +787,9 @@ Signature: {signature}</code></pre>
                 <p>根据三方订单号查询转账状态和详情</p>
 
                 <h4>API接口地址</h4>
-                <p><code>POST https://api.qcsj88888.com/payment/openapi/account/transfer/query</code></p>
+                <p>
+                  <code>POST https://api.qcsj88888.com/payment/openapi/account/transfer/query</code>
+                </p>
 
                 <h4>请求参数</h4>
                 <table class="api-table">
@@ -780,7 +873,9 @@ Signature: {signature}</code></pre>
                 <p>查询指定企业资金专户的余额信息</p>
 
                 <h4>API接口地址</h4>
-                <p><code>POST https://api.qcsj88888.com/payment/openapi/account/balance/query</code></p>
+                <p>
+                  <code>POST https://api.qcsj88888.com/payment/openapi/account/balance/query</code>
+                </p>
 
                 <h4>请求参数</h4>
                 <table class="api-table">
@@ -869,9 +964,23 @@ Signature: {signature}</code></pre>
 
                 <h4>注意事项</h4>
                 <ul>
-                  <li>返回结果为<strong>数组</strong>,一个企业可能有多个资金专户</li>
-                  <li>余额单位为<strong>元</strong>,精确到小数点后两位</li>
-                  <li>仅返回 <code>scene</code> 为 <code>B2B_TRANS</code> 的资金专户</li>
+                  <li>
+                    返回结果为
+                    <strong>数组</strong>
+                    ,一个企业可能有多个资金专户
+                  </li>
+                  <li>
+                    余额单位为
+                    <strong>元</strong>
+                    ,精确到小数点后两位
+                  </li>
+                  <li>
+                    仅返回
+                    <code>scene</code>
+                    为
+                    <code>B2B_TRANS</code>
+                    的资金专户
+                  </li>
                 </ul>
               </div>
 
@@ -882,17 +991,36 @@ Signature: {signature}</code></pre>
 
                 <h4>回调地址配置</h4>
                 <p>系统按照以下优先级获取回调地址:</p>
-                <ol style="margin-left: 20px;">
-                  <li><strong>API Key 级别</strong>:在创建/编辑 API Key 时配置回调地址(优先级最高)</li>
-                  <li><strong>开放平台配置</strong>:在 <strong>应用配置</strong> 页面设置默认回调地址</li>
+                <ol style="margin-left: 20px">
+                  <li>
+                    <strong>API Key 级别</strong>
+                    :在创建/编辑 API Key 时配置回调地址(优先级最高)
+                  </li>
+                  <li>
+                    <strong>开放平台配置</strong>
+                    :在
+                    <strong>应用配置</strong>
+                    页面设置默认回调地址
+                  </li>
                 </ol>
-                <p style="color: #909399; margin-top: 8px;">说明:如果 API Key 已配置回调地址,则优先使用;否则使用开放平台配置中的回调地址。</p>
+                <p style="color: #909399; margin-top: 8px">
+                  说明:如果 API Key 已配置回调地址,则优先使用;否则使用开放平台配置中的回调地址。
+                </p>
 
                 <h4>通知方式</h4>
                 <ul>
-                  <li><strong>POST 请求</strong>:系统通过 HTTP POST 方式将通知数据发送到商户的回调地址</li>
-                  <li><strong>表单形式</strong>:通知参数以表单形式提交(Content-Type: multipart/form-data)</li>
-                  <li><strong>重试机制</strong>:如果通知失败,系统会自动重试(最多2次,间隔1秒、2秒)</li>
+                  <li>
+                    <strong>POST 请求</strong>
+                    :系统通过 HTTP POST 方式将通知数据发送到商户的回调地址
+                  </li>
+                  <li>
+                    <strong>表单形式</strong>
+                    :通知参数以表单形式提交(Content-Type: multipart/form-data)
+                  </li>
+                  <li>
+                    <strong>重试机制</strong>
+                    :如果通知失败,系统会自动重试(最多2次,间隔1秒、2秒)
+                  </li>
                 </ul>
 
                 <h4>请求参数</h4>
@@ -936,7 +1064,9 @@ Signature: {signature}</code></pre>
                     <tr>
                       <td>status</td>
                       <td>string</td>
-                      <td>转账状态:DEALING(处理中)、SUCCESS(成功)、FAIL(失败)、REFUND(已退款)</td>
+                      <td>
+                        转账状态:DEALING(处理中)、SUCCESS(成功)、FAIL(失败)、REFUND(已退款)
+                      </td>
                     </tr>
                     <tr>
                       <td>order_no</td>
@@ -967,7 +1097,8 @@ Signature: {signature}</code></pre>
                 </table>
 
                 <h4>通知示例</h4>
-                <pre><code>POST /your/callback/url HTTP/1.1
+                成功示例
+                <pre v-pre><code>POST /your/callback/url HTTP/1.1
 Content-Type: multipart/form-data; boundary=----WebKitFormBoundary
 
 ------WebKitFormBoundary
@@ -989,30 +1120,95 @@ Content-Disposition: form-data; name="content"
   "created_time": "2026-04-27 11:22:33",
   "updated_time": "2026-04-27 11:25:45"
 }
+
+------WebKitFormBoundary--</code></pre>
+                失败示例
+                <pre><code>POST /your/callback/url HTTP/1.1
+Content-Type: multipart/form-data; boundary=----WebKitFormBoundary
+
+------WebKitFormBoundary
+Content-Disposition: form-data; name="notify_id"
+
+n12535554089713704963
+------WebKitFormBoundary
+Content-Disposition: form-data; name="timestamp"
+
+1779037365774
+------WebKitFormBoundary
+Content-Disposition: form-data; name="content"
+
+{
+  "status": "FAIL",
+  "third_biz_no": "商户订单号202604270001",
+  "amount": "1.00",
+  "error_msg": "收款账号不存在或姓名有误,建议核实账号和姓名是否准确"
+}
+------WebKitFormBoundary--
 ------WebKitFormBoundary--</code></pre>
 
                 <h4>响应要求</h4>
-                <p>商户服务端收到通知后,需要返回 HTTP 200 状态码表示成功接收。如果返回非 200 状态码或超时,系统会进行重试。</p>
+                <p>
+                  商户服务端收到通知后,需要返回 HTTP 200 状态码表示成功接收。如果返回非 200
+                  状态码或超时,系统会进行重试。
+                </p>
 
                 <h4>注意事项</h4>
                 <ul>
-                  <li>回调地址需要支持 <strong>HTTPS</strong> 协议</li>
-                  <li>确保回调接口能够在 <strong>5秒内</strong> 返回响应</li>
-                  <li><span style="color: #f56c6c;"><strong>重要</strong>:回调通知不包含签名验证,收到通知后请主动调用查询接口确认订单状态,以确保数据真实性</span></li>
-                  <li>通知可能会重复发送,请确保业务逻辑支持 <strong>幂等性</strong></li>
-                  <li>系统最多重试 <strong>2次</strong>,重试间隔为 1 秒和 2 秒</li>
+                  <li>
+                    回调地址需要支持
+                    <strong>HTTPS</strong>
+                    协议
+                  </li>
+                  <li>
+                    确保回调接口能够在
+                    <strong>5秒内</strong>
+                    返回响应
+                  </li>
+                  <li>
+                    <span style="color: #f56c6c">
+                      <strong>重要</strong>
+                      :回调通知不包含签名验证,收到通知后请主动调用查询接口确认订单状态,以确保数据真实性
+                    </span>
+                  </li>
+                  <li>
+                    通知可能会重复发送,请确保业务逻辑支持
+                    <strong>幂等性</strong>
+                  </li>
+                  <li>
+                    系统最多重试
+                    <strong>2次</strong>
+                    ,重试间隔为 1 秒和 2 秒
+                  </li>
                 </ul>
               </div>
 
               <div v-else-if="activeSection === 'errors'" class="section-content">
                 <h2>8. 常见错误</h2>
                 <ul>
-                  <li><strong>401 Invalid API Key</strong>:API Key无效或已过期</li>
-                  <li><strong>401 Signature header required</strong>:未提供Signature请求头</li>
-                  <li><strong>401 Invalid Signature</strong>:签名验证失败,请检查签名计算方式</li>
-                  <li><strong>400 Bad Request</strong>:请求参数错误</li>
-                  <li><strong>403 Forbidden</strong>:无权限访问</li>
-                  <li><strong>500 Internal Server Error</strong>:服务器内部错误</li>
+                  <li>
+                    <strong>401 Invalid API Key</strong>
+                    :API Key无效或已过期
+                  </li>
+                  <li>
+                    <strong>401 Signature header required</strong>
+                    :未提供Signature请求头
+                  </li>
+                  <li>
+                    <strong>401 Invalid Signature</strong>
+                    :签名验证失败,请检查签名计算方式
+                  </li>
+                  <li>
+                    <strong>400 Bad Request</strong>
+                    :请求参数错误
+                  </li>
+                  <li>
+                    <strong>403 Forbidden</strong>
+                    :无权限访问
+                  </li>
+                  <li>
+                    <strong>500 Internal Server Error</strong>
+                    :服务器内部错误
+                  </li>
                 </ul>
               </div>
 
@@ -1141,7 +1337,7 @@ const configFormRef = ref();
 const configLoading = ref(true);
 const expandedSections = ref({
   payee_info: false,
-  bankcard_ext_info: false
+  bankcard_ext_info: false,
 });
 
 const configExists = ref(false);
@@ -1234,7 +1430,7 @@ function getSectionTitle() {
     balance: "账户余额",
     callback: "回调通知",
     errors: "常见错误",
-    php: "PHP示例代码"
+    php: "PHP示例代码",
   };
   return titles[activeSection.value] || "API文档";
 }
@@ -1396,44 +1592,47 @@ async function handleSubmit() {
 async function handleUpdateStatus(id: number, status: string) {
   try {
     await ApiKeyAPI.updateApiKeyStatus(id, { status });
-    ElMessage.success(`API Key已${status === '0' ? '启用' : '禁用'}`);
+    ElMessage.success(`API Key已${status === "0" ? "启用" : "禁用"}`);
     refreshList();
   } catch (error: unknown) {
     console.error(error);
-    ElMessage.error('操作失败');
+    ElMessage.error("操作失败");
   }
 }
 
 function copyToClipboard(text: string) {
-  navigator.clipboard.writeText(text).then(() => {
-    ElMessage.success('复制成功');
-  }).catch(() => {
-    ElMessage.error('复制失败');
-  });
+  navigator.clipboard
+    .writeText(text)
+    .then(() => {
+      ElMessage.success("复制成功");
+    })
+    .catch(() => {
+      ElMessage.error("复制失败");
+    });
 }
 
 function handleCloseApiKeyDetail() {
-  ElMessage.warning('请务必已保存API Key和Secret,关闭后将无法再次查看');
+  ElMessage.warning("请务必已保存API Key和Secret,关闭后将无法再次查看");
   apiKeyDetailVisible.value = false;
 }
 
 function handleConfirmApiKeyDetail() {
-  ElMessage.success('已确认保存');
+  ElMessage.success("已确认保存");
   apiKeyDetailVisible.value = false;
 }
 
 function downloadApiKeyCsv() {
-  const csvContent = `API Key,API Secret,状态,过期时间,创建时间,描述\n"${apiKeyDetail.api_key}","${apiKeyDetail.api_secret}","${apiKeyDetail.status === '0' ? '正常' : '禁用'}","${apiKeyDetail.expired_at}","${apiKeyDetail.created_time}","${apiKeyDetail.description || ''}"`;
-  const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
-  const link = document.createElement('a');
+  const csvContent = `API Key,API Secret,状态,过期时间,创建时间,描述\n"${apiKeyDetail.api_key}","${apiKeyDetail.api_secret}","${apiKeyDetail.status === "0" ? "正常" : "禁用"}","${apiKeyDetail.expired_at}","${apiKeyDetail.created_time}","${apiKeyDetail.description || ""}"`;
+  const blob = new Blob([csvContent], { type: "text/csv;charset=utf-8;" });
+  const link = document.createElement("a");
   const url = URL.createObjectURL(blob);
-  link.setAttribute('href', url);
-  link.setAttribute('download', `api-key-${apiKeyDetail.api_key || Date.now()}.csv`);
-  link.style.visibility = 'hidden';
+  link.setAttribute("href", url);
+  link.setAttribute("download", `api-key-${apiKeyDetail.api_key || Date.now()}.csv`);
+  link.style.visibility = "hidden";
   document.body.appendChild(link);
   link.click();
   document.body.removeChild(link);
-  ElMessage.success('CSV已下载');
+  ElMessage.success("CSV已下载");
 }
 </script>
 
@@ -1621,7 +1820,7 @@ function downloadApiKeyCsv() {
 }
 
 .section-content code {
-  font-family: 'Courier New', Courier, monospace;
+  font-family: "Courier New", Courier, monospace;
   font-size: 14px;
   color: #303133;
 }