Forráskód Böngészése

feat: 更新前端

husenlin 1 hónapja
szülő
commit
62118300da

+ 11 - 0
frontend/src/api/module_payment/apikey/index.ts

@@ -1,5 +1,16 @@
 import request from "@/utils/request";
 
+export interface ApiKeyPageQuery {
+  page_no: number;
+  page_size: number;
+  api_key: string;
+  status: string;
+  expired_at?: string;
+  last_used_at?: string;
+  created_time: string
+  description?: string;
+}
+
 export interface ApiKeyCreateForm {
   tenant_id?: number;
   expired_days: number;

+ 1 - 1
frontend/src/components/CURD/CrudToolbarLeft.vue

@@ -17,7 +17,7 @@
       <el-button
         v-if="permCreate"
         v-hasPerm="permCreate"
-        type="success"
+        type="primary"
         icon="plus"
         @click="$emit('add')"
       >

+ 1 - 1
frontend/src/components/CURD/PageContent.vue

@@ -563,7 +563,7 @@ function handleDelete(id?: number | string) {
         props.contentConfig
           .deleteAction(ids)
           .then(() => {
-            ElMessage.success("删除成功");
+            // ElMessage.success("删除成功");
             removeIds.value = [];
             //清空选中项
             tableRef.value?.clearSelection();

+ 5 - 5
frontend/src/views/module_payment/account/components/AccountOverview.vue

@@ -347,15 +347,15 @@ function handleRefresh() {
   fetchAccountInfo();
 }
 
-function refresh() {
-  fetchAccountInfo();
+async function refresh() {
+  await fetchAccountInfo();
 }
 
 defineExpose({ refresh });
 
-onMounted(() => {
-  fetchAccountInfo();
-});
+// onMounted(() => {
+//   fetchAccountInfo();
+// });
 </script>
 
 <style lang="scss" scoped>

+ 12 - 3
frontend/src/views/module_payment/account/index.vue

@@ -126,7 +126,7 @@
                   :disabled="!!accountInfo.account_book_id" />
               </el-form-item> -->
               <el-form-item label="充值金额" prop="amount">
-                <el-input-number v-model="depositForm.amount" :min="0.01" :precision="2" :controls="false"
+                <el-input-number v-model="depositForm.amount" :min="0.00" :precision="2" :controls="false"
                   placeholder="请输入充值金额" style="width: 300px" />
                 <span class="ml-2">元</span>
               </el-form-item>
@@ -202,7 +202,7 @@
                   :placeholder="accountInfo.account_book_id || '请先开通资金账户'" :disabled="!!accountInfo.account_book_id" />
               </el-form-item> -->
               <el-form-item label="转账金额" prop="amount">
-                <el-input-number v-model="transferForm.amount" :min="0.02" :precision="2" :controls="false"
+                <el-input-number v-model="transferForm.amount" :min="0.00" :precision="2" :controls="false"
                   placeholder="请输入转账金额" style="width: 300px" />
                 <span class="ml-2">元</span>
               </el-form-item>
@@ -532,7 +532,7 @@ const enterpriseStore = useEnterpriseStore();
 
 const activeTab = ref("overview");
 const pageLoading = ref(false);
-const loadingText = ref("");
+const loadingText = ref("加载中...");
 
 const authorizeLoading = ref(false);
 const createLoading = ref(false);
@@ -1285,6 +1285,15 @@ watch(activeTab, async (newValue) => {
 //   }
 // })
 
+onMounted(async() => {
+  pageLoading.value = true;
+  try {
+    await overviewRef.value?.refresh();
+  } finally {
+    pageLoading.value = false;
+  }
+});
+
 </script>
 
 <style lang="scss" scoped>

+ 27 - 55
frontend/src/views/module_payment/apikey/index.vue

@@ -46,16 +46,6 @@
                   min-width="55"
                   align="center"
                 />
-                <el-table-column
-                  v-if="contentCols.find((col) => col.prop === 'index')?.show"
-                  fixed
-                  label="序号"
-                  min-width="60"
-                >
-                  <template #default="scope">
-                    {{ (pagination.currentPage - 1) * pagination.pageSize + scope.$index + 1 }}
-                  </template>
-                </el-table-column>
                 <el-table-column
                   v-if="contentCols.find((col) => col.prop === 'api_key')?.show"
                   key="api_key"
@@ -233,7 +223,7 @@
             <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>
@@ -275,51 +265,39 @@
             <div class="docs-content">
               <h2>1. 认证方式</h2>
               <p>使用API Key进行认证时,需要在请求头中添加以下信息:</p>
-              <pre><code>Authorization: ApiKey {api_key}:{signature}</code></pre>
+              <pre><code>Authorization: ApiKey {api_key}
+Signature: {signature}</code></pre>
               <p>其中:</p>
               <ul>
-                <li><strong>api_key</strong>:从管理界面获取的API Key</li>
-                <li><strong>signature</strong>:可选,请求签名(详见签名验证部分)</li>
+                <li><strong>Authorization</strong>:API Key认证头,格式为 <code>ApiKey {api_key}</code></li>
+                <li><strong>Signature</strong>:请求签名(必填),用于验证请求数据的完整性</li>
               </ul>
 
               <h2>2. 签名验证</h2>
-              <p>为了增强安全性,建议在请求中添加签名。签名生成步骤:</p>
+              <p>签名用于验证请求数据的完整性,防止数据被篡改。签名生成步骤:</p>
               <ol>
                 <li>将请求数据(JSON格式)按参数名升序排序</li>
-                <li>将排序后的参数拼接为字符串:<code>key1=value1&key2=value2</code></li>
+                <li>将排序后的参数拼接为字符串:<code>key1=value1&amp;key2=value2</code></li>
                 <li>使用API Secret作为密钥,通过HMAC-SHA256算法生成签名</li>
-                <li>将签名添加到Authorization头中</li>
+                <li>将签名添加到请求头 <code>Signature</code> 中</li>
               </ol>
 
               <h2>3. 注意事项</h2>
               <ul>
                 <li>API Key和Secret请妥善保管,不要泄露给他人</li>
+                <li>签名验证是<strong>必填</strong>的,未带签名或签名错误将返回401</li>
+                <li>签名使用HMAC-SHA256算法,密钥为API Secret</li>
+                <li>签名对象是请求体字典排序后的键值对字符串(key1=value1&amp;key2=value2)</li>
                 <li>定期更新API Key,建议每3-6个月更换一次</li>
                 <li>如发现API Key泄露,请立即禁用并重新生成</li>
-                <li>签名验证可选,但建议在生产环境中使用</li>
                 <li>API Key有过期时间,请在过期前及时更新</li>
               </ul>
 
               <h2>4. cURL示例</h2>
               <h3>4.1 租户转账接口</h3>
-              <pre><code># 基础认证(不带签名)
-curl -X POST 'https://api.example.com/payment/openapi/account/transfer' \
+              <pre><code>curl -X POST 'https://api.example.com/payment/openapi/account/transfer' \
   -H 'Authorization: ApiKey your_api_key' \
-  -H 'Content-Type: application/json' \
-  -d '{
-    "account_book_id": "资金账号",
-    "amount": 100.00,
-    "order_title": "转账标题",
-    "payee_info": {
-      "identity_type": "ALIPAY_ACCOUNT",
-      "name": "收款人姓名",
-      "identity": "收款人支付宝账号"
-    }
-  }'
-
-# 带签名认证
-curl -X POST 'https://api.example.com/payment/openapi/account/transfer' \
-  -H 'Authorization: ApiKey your_api_key:your_signature' \
+  -H 'Signature: your_signature' \
   -H 'Content-Type: application/json' \
   -d '{
     "account_book_id": "资金账号",
@@ -339,14 +317,14 @@ import hashlib
 import hmac
 
 def calculate_signature(api_secret, request_data):
-    \"\"\"
+    """
     对请求体字典进行签名
     request_data: dict 请求体数据
-    \"\"\"
+    """
     # 按参数名升序排序
     sorted_data = sorted(request_data.items(), key=lambda x: x[0])
-    # 拼接为 key1=value1&key2=value2 格式
-    sign_str = "&".join([f"{k}={v}" for k, v in sorted_data])
+    # 拼接为 key1=value1&amp;key2=value2 格式
+    sign_str = "&amp;".join([f"{k}={v}" for k, v in sorted_data])
     # HMAC-SHA256签名
     signature = hmac.new(
         api_secret.encode('utf-8'),
@@ -362,13 +340,15 @@ request_data = {
     "amount": 100.00
 }
 signature = calculate_signature(api_secret, request_data)
-# Authorization: ApiKey your_api_key:signature</code></pre>
+# 发送请求时设置请求头:
+# Authorization: ApiKey your_api_key
+# Signature: signature</code></pre>
 
-              <h3>4.3 注意事项</h3>
+              <h3>4.3 常见错误</h3>
               <ul>
-                <li>签名使用HMAC-SHA256算法,密钥为API Secret</li>
-                <li>签名对象是请求体字典排序后的键值对字符串(key1=value1&amp;key2=value2)</li>
-                <li>注意是字典排序后拼接,不是JSON字符串</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>
               </ul>
             </div>
           </el-card>
@@ -382,10 +362,8 @@ signature = calculate_signature(api_secret, request_data)
 import { ref, reactive } from "vue";
 import ApiKeyAPI, {
   ApiKeyCreateForm,
-  ApiKeyUpdateForm,
   ApiKeyPageQuery,
   ApiKeyResponse,
-  ApiKeyTable,
 } from "@/api/module_payment/apikey";
 import CrudToolbarLeft from "@/components/CURD/CrudToolbarLeft.vue";
 import CrudToolbarRight from "@/components/CURD/CrudToolbarRight.vue";
@@ -423,12 +401,6 @@ const searchConfig = reactive<ISearchConfig>({
   showNumber: 2,
   form: { labelWidth: "auto" },
   formItems: [
-    // {
-    //   prop: "tenant_id",
-    //   label: "租户ID",
-    //   type: "input",
-    //   attrs: { placeholder: "请输入租户ID", clearable: true },
-    // },
     {
       prop: "status",
       label: "状态",
@@ -454,7 +426,7 @@ const contentCols = reactive<
   }>
 >([
   { prop: "selection", label: "选择框", show: true },
-  { prop: "index", label: "序号", show: true },
+  { prop: "id", label: "ID", show: false },
   { prop: "api_key", label: "API Key", show: true },
   { prop: "status", label: "状态", show: true },
   { prop: "expired_at", label: "过期时间", show: true },
@@ -521,7 +493,7 @@ const initialFormData: ApiKeyCreateForm = {
   description: "",
 };
 
-function handleRowDelete(id: number) {
+async function handleRowDelete(id: number) {
   contentRef.value?.handleDelete(id);
 }
 
@@ -599,7 +571,7 @@ function downloadApiKeyCsv() {
   const link = document.createElement('a');
   const url = URL.createObjectURL(blob);
   link.setAttribute('href', url);
-  link.setAttribute('download', `api-key-${apiKeyDetail.id || Date.now()}.csv`);
+  link.setAttribute('download', `api-key-${apiKeyDetail.api_key || Date.now()}.csv`);
   link.style.visibility = 'hidden';
   document.body.appendChild(link);
   link.click();