feat(token): 新增修改 Token 接口及绑定记录视图切换功能

- 新增 apiTokenUpdatePost 接口支持修改 Token 信息
- 扩展 Token 数据结构,增加充值金额限制和次数限制相关字段
- 调整新增/编辑 Token 弹窗,支持编辑模式下的充值限制显示和输入
- 新增绑定记录卡片视图与表格视图切换功能
- 绑定记录视图显示总记录数和总金额统计信息
- 卡片视图改进绑定记录样式和交互,支持复制卡号和卡密
- 弹窗样式和分页控件优化,提升用户体验
- 修复并完善 Api 文档,补充修改 Token 接口说明和示例代码
This commit is contained in:
danial
2025-12-01 23:41:55 +08:00
parent 6f5b7680af
commit 246ff9f34e
17 changed files with 931 additions and 81 deletions

View File

@@ -63,6 +63,8 @@ docs/KamiApiCamelOilV1OrderListItem.md
docs/KamiApiCamelOilV1SubmitOrderReq.md
docs/KamiApiCamelOilV1SubmitOrderRes.md
docs/KamiApiCamelOilV1TokenInfo.md
docs/KamiApiCamelOilV1UpdateTokenReq.md
docs/KamiApiCamelOilV1UpdateTokenRes.md
docs/KamiApiCardInfoAppleV1AppleCardListRecord.md
docs/KamiApiCardInfoAppleV1AppleCardListRecordUploadUser.md
docs/KamiApiCardInfoAppleV1CallBackOrderManualReq.md
@@ -494,6 +496,8 @@ models/kami-api-camel-oil-v1-order-list-item.ts
models/kami-api-camel-oil-v1-submit-order-req.ts
models/kami-api-camel-oil-v1-submit-order-res.ts
models/kami-api-camel-oil-v1-token-info.ts
models/kami-api-camel-oil-v1-update-token-req.ts
models/kami-api-camel-oil-v1-update-token-res.ts
models/kami-api-card-info-apple-v1-apple-card-list-record-upload-user.ts
models/kami-api-card-info-apple-v1-apple-card-list-record.ts
models/kami-api-card-info-apple-v1-call-back-order-manual-req.ts

View File

@@ -52,6 +52,10 @@ import type { KamiApiCamelOilV1GetTokenRes } from '../models';
import type { KamiApiCamelOilV1ListCardBindingsByTokenRes } from '../models';
// @ts-ignore
import type { KamiApiCamelOilV1ListTokensRes } from '../models';
// @ts-ignore
import type { KamiApiCamelOilV1UpdateTokenReq } from '../models';
// @ts-ignore
import type { KamiApiCamelOilV1UpdateTokenRes } from '../models';
/**
* JDV2TokenManagementApi - axios parameter creator
*/
@@ -327,6 +331,54 @@ export const JDV2TokenManagementApiAxiosParamCreator = function (
...options.headers
};
return {
url: toPathString(localVarUrlObj),
options: localVarRequestOptions
};
},
/**
*
* @summary 修改 Token
* @param {KamiApiCamelOilV1UpdateTokenReq} [kamiApiCamelOilV1UpdateTokenReq]
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
apiTokenUpdatePost: async (
kamiApiCamelOilV1UpdateTokenReq?: KamiApiCamelOilV1UpdateTokenReq,
options: RawAxiosRequestConfig = {}
): Promise<RequestArgs> => {
const localVarPath = `/api/token/update`;
// use dummy base URL string because the URL constructor only accepts absolute URLs.
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
let baseOptions;
if (configuration) {
baseOptions = configuration.baseOptions;
}
const localVarRequestOptions = {
method: 'POST',
...baseOptions,
...options
};
const localVarHeaderParameter = {} as any;
const localVarQueryParameter = {} as any;
localVarHeaderParameter['Content-Type'] = 'application/json';
setSearchParams(localVarUrlObj, localVarQueryParameter);
let headersFromBaseOptions =
baseOptions && baseOptions.headers ? baseOptions.headers : {};
localVarRequestOptions.headers = {
...localVarHeaderParameter,
...headersFromBaseOptions,
...options.headers
};
localVarRequestOptions.data = serializeDataIfNeeded(
kamiApiCamelOilV1UpdateTokenReq,
localVarRequestOptions,
configuration
);
return {
url: toPathString(localVarUrlObj),
options: localVarRequestOptions
@@ -526,6 +578,40 @@ export const JDV2TokenManagementApiFp = function (
BASE_PATH,
configuration
)(axios, localVarOperationServerBasePath || basePath);
},
/**
*
* @summary 修改 Token
* @param {KamiApiCamelOilV1UpdateTokenReq} [kamiApiCamelOilV1UpdateTokenReq]
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
async apiTokenUpdatePost(
kamiApiCamelOilV1UpdateTokenReq?: KamiApiCamelOilV1UpdateTokenReq,
options?: RawAxiosRequestConfig
): Promise<
(
axios?: AxiosInstance,
basePath?: string
) => AxiosPromise<KamiApiCamelOilV1UpdateTokenRes>
> {
const localVarAxiosArgs =
await localVarAxiosParamCreator.apiTokenUpdatePost(
kamiApiCamelOilV1UpdateTokenReq,
options
);
const localVarOperationServerIndex = configuration?.serverIndex ?? 0;
const localVarOperationServerBasePath =
operationServerMap['JDV2TokenManagementApi.apiTokenUpdatePost']?.[
localVarOperationServerIndex
]?.url;
return (axios, basePath) =>
createRequestFunction(
localVarAxiosArgs,
globalAxios,
BASE_PATH,
configuration
)(axios, localVarOperationServerBasePath || basePath);
}
};
};
@@ -631,6 +717,24 @@ export const JDV2TokenManagementApiFactory = function (
options
)
.then(request => request(axios, basePath));
},
/**
*
* @summary 修改 Token
* @param {JDV2TokenManagementApiApiTokenUpdatePostRequest} requestParameters Request parameters.
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
apiTokenUpdatePost(
requestParameters: JDV2TokenManagementApiApiTokenUpdatePostRequest = {},
options?: RawAxiosRequestConfig
): AxiosPromise<KamiApiCamelOilV1UpdateTokenRes> {
return localVarFp
.apiTokenUpdatePost(
requestParameters.kamiApiCamelOilV1UpdateTokenReq,
options
)
.then(request => request(axios, basePath));
}
};
};
@@ -698,6 +802,18 @@ export interface JDV2TokenManagementApiInterface {
requestParameters: JDV2TokenManagementApiApiTokenListGetRequest,
options?: RawAxiosRequestConfig
): AxiosPromise<KamiApiCamelOilV1ListTokensRes>;
/**
*
* @summary 修改 Token
* @param {JDV2TokenManagementApiApiTokenUpdatePostRequest} requestParameters Request parameters.
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
apiTokenUpdatePost(
requestParameters?: JDV2TokenManagementApiApiTokenUpdatePostRequest,
options?: RawAxiosRequestConfig
): AxiosPromise<KamiApiCamelOilV1UpdateTokenRes>;
}
/**
@@ -769,6 +885,13 @@ export interface JDV2TokenManagementApiApiTokenListGetRequest {
readonly status?: number;
}
/**
* Request parameters for apiTokenUpdatePost operation in JDV2TokenManagementApi.
*/
export interface JDV2TokenManagementApiApiTokenUpdatePostRequest {
readonly kamiApiCamelOilV1UpdateTokenReq?: KamiApiCamelOilV1UpdateTokenReq;
}
/**
* JDV2TokenManagementApi - object-oriented interface
*/
@@ -872,6 +995,25 @@ export class JDV2TokenManagementApi
)
.then(request => request(this.axios, this.basePath));
}
/**
*
* @summary 修改 Token
* @param {JDV2TokenManagementApiApiTokenUpdatePostRequest} requestParameters Request parameters.
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
public apiTokenUpdatePost(
requestParameters: JDV2TokenManagementApiApiTokenUpdatePostRequest = {},
options?: RawAxiosRequestConfig
) {
return JDV2TokenManagementApiFp(this.configuration)
.apiTokenUpdatePost(
requestParameters.kamiApiCamelOilV1UpdateTokenReq,
options
)
.then(request => request(this.axios, this.basePath));
}
}
export enum ApiCardBindingListByTokenGetPageSizeEnum {

View File

@@ -9,6 +9,7 @@ All URIs are relative to _http://localhost_
| [**apiTokenDeletePost**](#apitokendeletepost) | **POST** /api/token/delete | 删除 Token |
| [**apiTokenGetGet**](#apitokengetget) | **GET** /api/token/get | 获取 Token 信息 |
| [**apiTokenListGet**](#apitokenlistget) | **GET** /api/token/list | 列出 Token |
| [**apiTokenUpdatePost**](#apitokenupdatepost) | **POST** /api/token/update | 修改 Token |
# **apiCardBindingListByTokenGet**
@@ -260,3 +261,53 @@ No authorization required
| **200** | | - |
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
# **apiTokenUpdatePost**
> KamiApiCamelOilV1UpdateTokenRes apiTokenUpdatePost()
### Example
```typescript
import {
JDV2TokenManagementApi,
Configuration,
KamiApiCamelOilV1UpdateTokenReq
} from './api';
const configuration = new Configuration();
const apiInstance = new JDV2TokenManagementApi(configuration);
let kamiApiCamelOilV1UpdateTokenReq: KamiApiCamelOilV1UpdateTokenReq; // (optional)
const { status, data } = await apiInstance.apiTokenUpdatePost(
kamiApiCamelOilV1UpdateTokenReq
);
```
### Parameters
| Name | Type | Description | Notes |
| ----------------------------------- | ----------------------------------- | ----------- | ----- |
| **kamiApiCamelOilV1UpdateTokenReq** | **KamiApiCamelOilV1UpdateTokenReq** | | |
### Return type
**KamiApiCamelOilV1UpdateTokenRes**
### Authorization
No authorization required
### HTTP request headers
- **Content-Type**: application/json
- **Accept**: application/json
### HTTP response details
| Status code | Description | Response headers |
| ----------- | ----------- | ---------------- |
| **200** | | - |
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)

View File

@@ -10,7 +10,7 @@
| **orderId** | **number** | 订单ID | [optional] [default to undefined] |
| **cardNumber** | **string** | 卡号 | [optional] [default to undefined] |
| **cardPassword** | **string** | 卡密 | [optional] [default to undefined] |
| **amount** | **object** | | [optional] [default to undefined] |
| **amount** | **number** | 绑定金额 | [optional] [default to undefined] |
| **createdAt** | **string** | 创建时间 | [optional] [default to undefined] |
## Example

View File

@@ -2,12 +2,14 @@
## Properties
| Name | Type | Description | Notes |
| -------------- | ---------- | ------------ | --------------------------------- |
| **tokenName** | **string** | Token名称 | [default to undefined] |
| **tokenValue** | **string** | Token值 | [default to undefined] |
| **phone** | **string** | 绑定的手机号 | [optional] [default to undefined] |
| **remark** | **string** | 备注 | [optional] [default to undefined] |
| Name | Type | Description | Notes |
| ----------------------- | ---------- | ------------ | --------------------------------- |
| **tokenName** | **string** | Token名称 | [default to undefined] |
| **tokenValue** | **string** | Token值 | [default to undefined] |
| **phone** | **string** | 绑定的手机号 | [optional] [default to undefined] |
| **remark** | **string** | 备注 | [optional] [default to undefined] |
| **rechargeLimitAmount** | **number** | 充值金额限制 | [default to undefined] |
| **rechargeLimitCount** | **number** | 充值次数限制 | [default to undefined] |
## Example
@@ -18,7 +20,9 @@ const instance: KamiApiCamelOilV1CreateTokenReq = {
tokenName,
tokenValue,
phone,
remark
remark,
rechargeLimitAmount,
rechargeLimitCount
};
```

View File

@@ -2,20 +2,23 @@
## Properties
| Name | Type | Description | Notes |
| ------------------- | ---------- | -------------- | --------------------------------- |
| **id** | **number** | Token ID | [optional] [default to undefined] |
| **tokenName** | **string** | Token名称 | [optional] [default to undefined] |
| **tokenValue** | **string** | Token值 | [optional] [default to undefined] |
| **phone** | **string** | 绑定的手机号 | [optional] [default to undefined] |
| **status** | **number** | 状态 | [optional] [default to undefined] |
| **bindCount** | **number** | 已绑定卡密数量 | [optional] [default to undefined] |
| **totalBindAmount** | **object** | | [optional] [default to undefined] |
| **lastBindAt** | **string** | 最后绑定时间 | [optional] [default to undefined] |
| **lastUsedAt** | **string** | 最后使用时间 | [optional] [default to undefined] |
| **remark** | **string** | 备注 | [optional] [default to undefined] |
| **createdAt** | **string** | 创建时间 | [optional] [default to undefined] |
| **updatedAt** | **string** | 更新时间 | [optional] [default to undefined] |
| Name | Type | Description | Notes |
| ----------------------- | ---------- | -------------- | --------------------------------- |
| **id** | **number** | Token ID | [optional] [default to undefined] |
| **tokenName** | **string** | Token名称 | [optional] [default to undefined] |
| **tokenValue** | **string** | Token值 | [optional] [default to undefined] |
| **phone** | **string** | 绑定的手机号 | [optional] [default to undefined] |
| **status** | **number** | 状态 | [optional] [default to undefined] |
| **bindCount** | **number** | 已绑定卡密数量 | [optional] [default to undefined] |
| **totalBindAmount** | **number** | 累计绑定金额 | [optional] [default to undefined] |
| **totalRechargeAmount** | **number** | 总充值金额 | [optional] [default to undefined] |
| **rechargeLimitAmount** | **number** | 充值金额限制 | [optional] [default to undefined] |
| **rechargeLimitCount** | **number** | 充值次数限制 | [optional] [default to undefined] |
| **lastBindAt** | **string** | 最后绑定时间 | [optional] [default to undefined] |
| **lastUsedAt** | **string** | 最后使用时间 | [optional] [default to undefined] |
| **remark** | **string** | 备注 | [optional] [default to undefined] |
| **createdAt** | **string** | 创建时间 | [optional] [default to undefined] |
| **updatedAt** | **string** | 更新时间 | [optional] [default to undefined] |
## Example
@@ -30,6 +33,9 @@ const instance: KamiApiCamelOilV1TokenInfo = {
status,
bindCount,
totalBindAmount,
totalRechargeAmount,
rechargeLimitAmount,
rechargeLimitCount,
lastBindAt,
lastUsedAt,
remark,

View File

@@ -0,0 +1,29 @@
# KamiApiCamelOilV1UpdateTokenReq
## Properties
| Name | Type | Description | Notes |
| ----------------------- | ---------- | ------------ | --------------------------------- |
| **tokenId** | **number** | Token ID | [default to undefined] |
| **tokenName** | **string** | Token名称 | [default to undefined] |
| **phone** | **string** | 绑定的手机号 | [optional] [default to undefined] |
| **remark** | **string** | 备注 | [optional] [default to undefined] |
| **rechargeLimitAmount** | **number** | 充值金额限制 | [default to undefined] |
| **rechargeLimitCount** | **number** | 充值次数限制 | [default to undefined] |
## Example
```typescript
import { KamiApiCamelOilV1UpdateTokenReq } from './api';
const instance: KamiApiCamelOilV1UpdateTokenReq = {
tokenId,
tokenName,
phone,
remark,
rechargeLimitAmount,
rechargeLimitCount
};
```
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)

View File

@@ -0,0 +1,19 @@
# KamiApiCamelOilV1UpdateTokenRes
## Properties
| Name | Type | Description | Notes |
| ----------- | ---------- | ----------- | --------------------------------- |
| **message** | **string** | 信息 | [optional] [default to undefined] |
## Example
```typescript
import { KamiApiCamelOilV1UpdateTokenRes } from './api';
const instance: KamiApiCamelOilV1UpdateTokenRes = {
message
};
```
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)

View File

@@ -42,6 +42,8 @@ export * from './kami-api-camel-oil-v1-order-list-item';
export * from './kami-api-camel-oil-v1-submit-order-req';
export * from './kami-api-camel-oil-v1-submit-order-res';
export * from './kami-api-camel-oil-v1-token-info';
export * from './kami-api-camel-oil-v1-update-token-req';
export * from './kami-api-camel-oil-v1-update-token-res';
export * from './kami-api-card-info-apple-v1-apple-card-list-record';
export * from './kami-api-card-info-apple-v1-apple-card-list-record-upload-user';
export * from './kami-api-card-info-apple-v1-call-back-order-manual-req';

View File

@@ -37,7 +37,10 @@ export interface KamiApiCamelOilV1CardBindingInfo {
* 卡密
*/
cardPassword?: string;
amount?: object;
/**
* 绑定金额
*/
amount?: number;
/**
* 创建时间
*/

View File

@@ -29,4 +29,12 @@ export interface KamiApiCamelOilV1CreateTokenReq {
* 备注
*/
remark?: string;
/**
* 充值金额限制
*/
rechargeLimitAmount: number;
/**
* 充值次数限制
*/
rechargeLimitCount: number;
}

View File

@@ -37,7 +37,22 @@ export interface KamiApiCamelOilV1TokenInfo {
* 已绑定卡密数量
*/
bindCount?: number;
totalBindAmount?: object;
/**
* 累计绑定金额
*/
totalBindAmount?: number;
/**
* 总充值金额
*/
totalRechargeAmount?: number;
/**
* 充值金额限制
*/
rechargeLimitAmount?: number;
/**
* 充值次数限制
*/
rechargeLimitCount?: number;
/**
* 最后绑定时间
*/

View File

@@ -0,0 +1,40 @@
/* tslint:disable */
/**
*
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
*
* The version of the OpenAPI document:
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
export interface KamiApiCamelOilV1UpdateTokenReq {
/**
* Token ID
*/
tokenId: number;
/**
* Token名称
*/
tokenName: string;
/**
* 绑定的手机号
*/
phone?: string;
/**
* 备注
*/
remark?: string;
/**
* 充值金额限制
*/
rechargeLimitAmount: number;
/**
* 充值次数限制
*/
rechargeLimitCount: number;
}

View File

@@ -0,0 +1,20 @@
/* tslint:disable */
/**
*
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
*
* The version of the OpenAPI document:
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
export interface KamiApiCamelOilV1UpdateTokenRes {
/**
* 信息
*/
message?: string;
}

View File

@@ -1,7 +1,7 @@
<template>
<a-modal
v-model:visible="modalVisible"
title="新增Token"
:title="isEditMode ? '编辑Token' : '新增Token'"
width="600px"
@cancel="handleCancel"
@before-ok="handleBeforeOk"
@@ -22,7 +22,12 @@
show-word-limit
/>
</a-form-item>
<a-form-item field="tokenValue" label="Token值" required>
<a-form-item
v-if="!isEditMode"
field="tokenValue"
label="Token值"
required
>
<a-textarea
v-model="formModel.tokenValue"
placeholder="请输入Token值"
@@ -47,6 +52,24 @@
show-word-limit
/>
</a-form-item>
<a-form-item field="rechargeLimitAmount" label="充值金额限制" required>
<a-input-number
v-model="formModel.rechargeLimitAmount"
placeholder="请输入充值金额限制"
:min="0"
:precision="2"
style="width: 100%"
/>
</a-form-item>
<a-form-item field="rechargeLimitCount" label="充值次数限制" required>
<a-input-number
v-model="formModel.rechargeLimitCount"
placeholder="请输入充值次数限制"
:min="0"
:precision="0"
style="width: 100%"
/>
</a-form-item>
</a-form>
</a-modal>
</template>
@@ -59,6 +82,7 @@ import { jdV2TokenClient } from '@/api/index.ts';
interface Props {
visible: boolean;
editData?: KamiApiCamelOilV1TokenInfo;
}
interface Emits {
@@ -75,42 +99,68 @@ const modalVisible = computed({
set: value => emit('update:visible', value)
});
const isEditMode = computed(() => !!props.editData);
const formRef = ref<FormInstance>();
const generateFormModel = () => {
return {
tokenId: 0,
tokenName: '',
tokenValue: '',
phone: '',
remark: ''
remark: '',
rechargeLimitAmount: 0,
rechargeLimitCount: 0
};
};
const formModel = reactive(generateFormModel());
const rules = {
const rules = computed(() => ({
tokenName: [
{ required: true, message: '请输入Token名称' },
{ min: 1, max: 100, message: 'Token名称长度为1-100个字符' }
],
tokenValue: [
{ required: true, message: '请输入Token值' },
{ required: !isEditMode.value, message: '请输入Token值' },
{ min: 1, max: 1000, message: 'Token值长度为1-1000个字符' }
],
phone: [
{ required: true, message: '请输入绑定手机号' },
{ match: /^1[3-9]\d{9}$/, message: '请输入正确的手机号码' }
],
remark: [{ max: 500, message: '备注长度不能超过500个字符' }]
};
remark: [{ max: 500, message: '备注长度不能超过500个字符' }],
rechargeLimitAmount: [
{ required: true, message: '请输入充值金额限制' },
{ type: 'number', min: 0, message: '充值金额限制不能小于0' }
],
rechargeLimitCount: [
{ required: true, message: '请输入充值次数限制' },
{ type: 'number', min: 0, message: '充值次数限制不能小于0' }
]
}));
// 监听弹窗显示状态,重置表单
// 监听弹窗显示状态和编辑数据,更新表单
watch(
() => props.visible,
visible => {
[() => props.visible, () => props.editData],
([visible, editData]) => {
if (visible) {
// 重置表单
Object.assign(formModel, generateFormModel());
if (editData && isEditMode.value) {
// 编辑模式,填充现有数据
Object.assign(formModel, {
tokenName: editData.tokenName || '',
tokenValue: editData.tokenValue || '',
phone: editData.phone || '',
remark: editData.remark || '',
rechargeLimitAmount: editData.rechargeLimitAmount || 0,
rechargeLimitCount: editData.rechargeLimitCount || 0,
tokenId: editData.id || 0
});
} else {
// 新增模式,重置表单
Object.assign(formModel, generateFormModel());
}
}
},
{ immediate: true }
@@ -127,27 +177,50 @@ const handleBeforeOk = async () => {
return false;
}
// 新增Token
await jdV2TokenClient.apiTokenCreatePost({
kamiApiCamelOilV1CreateTokenReq: {
tokenName: formModel.tokenName,
tokenValue: formModel.tokenValue,
remark: formModel.remark
}
});
if (isEditMode.value) {
// 编辑Token
await jdV2TokenClient.apiTokenUpdatePost({
kamiApiCamelOilV1UpdateTokenReq: {
tokenId: formModel.tokenId,
tokenName: formModel.tokenName,
phone: formModel.phone,
remark: formModel.remark,
rechargeLimitAmount: formModel.rechargeLimitAmount,
rechargeLimitCount: formModel.rechargeLimitCount
}
});
Notification.success({
content: '新增Token成功',
closable: true
});
Notification.success({
content: '编辑Token成功',
closable: true
});
} else {
// 新增Token
await jdV2TokenClient.apiTokenCreatePost({
kamiApiCamelOilV1CreateTokenReq: {
tokenName: formModel.tokenName,
tokenValue: formModel.tokenValue,
phone: formModel.phone,
remark: formModel.remark,
rechargeLimitAmount: formModel.rechargeLimitAmount,
rechargeLimitCount: formModel.rechargeLimitCount
}
});
Notification.success({
content: '新增Token成功',
closable: true
});
}
emit('success');
emit('update:visible', false);
return true;
} catch (err) {
console.error('新增Token失败:', err);
const action = isEditMode.value ? '编辑' : '新增';
console.error(`${action}Token失败:`, err);
Notification.error({
content: '新增Token失败',
content: `${action}Token失败`,
closable: true
});
return false;

View File

@@ -2,40 +2,188 @@
<a-modal
v-model:visible="modalVisible"
:title="`Token绑卡记录 - ${tokenName}`"
width="80%"
width="90%"
:footer="false"
@cancel="handleCancel"
>
<a-table
:loading="loading"
:pagination="{
current: pagination.current,
pageSize: pagination.pageSize,
total: pagination.total,
pageSizeOptions: [10, 20, 50, 100],
showPageSize: true,
showTotal: true
}"
:columns="columns"
:data="renderData"
:scroll="{ x: 1200 }"
@page-change="onPageChange"
@page-size-change="onPageSizeChange"
>
<template #createdAt="{ record }">
{{ formatDateTime(record.createdAt) }}
</template>
<!-- 视图切换和统计信息 -->
<div class="modal-header">
<div class="view-controls">
<a-radio-group v-model="viewMode" type="button" size="small">
<a-radio value="table">
<template #radio="{ checked }">
<a-button :type="checked ? 'primary' : 'secondary'" size="small">
<template #icon>
<icon-list />
</template>
表格视图
</a-button>
</template>
</a-radio>
<a-radio value="card">
<template #radio="{ checked }">
<a-button :type="checked ? 'primary' : 'secondary'" size="small">
<template #icon>
<icon-apps />
</template>
卡片视图
</a-button>
</template>
</a-radio>
</a-radio-group>
</div>
<template #amount="{ record }">
{{ formatAmount(record.amount) }}
</template>
</a-table>
<div class="stats-info">
<a-space size="large">
<a-statistic title="总记录数" :value="pagination.total" />
<a-statistic
title="总金额"
:value="totalAmount"
:precision="2"
prefix="¥"
/>
</a-space>
</div>
</div>
<!-- 表格视图 -->
<div v-if="viewMode === 'table'" class="table-view">
<a-table
:loading="loading"
:pagination="{
current: pagination.current,
pageSize: pagination.pageSize,
total: pagination.total,
pageSizeOptions: [10, 20, 50, 100],
showPageSize: true,
showTotal: true
}"
:columns="columns"
:data="renderData"
:scroll="{ x: 1200 }"
@page-change="onPageChange"
@page-size-change="onPageSizeChange"
>
<template #createdAt="{ record }">
{{ formatDateTime(record.createdAt) }}
</template>
<template #amount="{ record }">
<a-tag :color="getAmountColor(record.amount)">
¥{{ formatAmount(record.amount) }}
</a-tag>
</template>
<template #cardNumber="{ record }">
<a-typography-text
:copyable="{ text: record.cardNumber }"
:ellipsis="{ rows: 1, showTooltip: true }"
>
{{ record.cardNumber }}
</a-typography-text>
</template>
<template #cardPassword="{ record }">
<a-typography-text
:copyable="{ text: record.cardPassword }"
:ellipsis="{ rows: 1, showTooltip: true }"
>
{{ record.cardPassword }}
</a-typography-text>
</template>
</a-table>
</div>
<!-- 卡片视图 -->
<div v-else class="card-view">
<a-spin :loading="loading" style="width: 100%">
<div class="card-grid">
<a-card
v-for="record in renderData"
:key="record.id"
class="binding-card"
:hoverable="true"
>
<template #header>
<div class="card-header">
<div class="card-title">
<a-tag color="blue" size="small">ID: {{ record.id }}</a-tag>
<a-tag v-if="record.orderId" color="green" size="small">
订单: {{ record.orderId }}
</a-tag>
</div>
<div class="card-amount">
<a-statistic
:value="record.amount"
:precision="2"
prefix="¥"
:value-style="{ color: '#00b42a', fontSize: '16px' }"
/>
</div>
</div>
</template>
<div class="card-content">
<a-descriptions :column="1" size="small">
<a-descriptions-item label="卡号">
<a-typography-text
:copyable="{ text: record.cardNumber }"
:ellipsis="{ rows: 1, showTooltip: true }"
class="copy-text"
>
{{ record.cardNumber }}
</a-typography-text>
</a-descriptions-item>
<a-descriptions-item label="卡密">
<a-typography-text
:copyable="{ text: record.cardPassword }"
:ellipsis="{ rows: 1, showTooltip: true }"
class="copy-text"
>
{{ record.cardPassword }}
</a-typography-text>
</a-descriptions-item>
<a-descriptions-item label="创建时间">
<a-typography-text type="secondary">
{{ formatDateTime(record.createdAt) }}
</a-typography-text>
</a-descriptions-item>
</a-descriptions>
</div>
</a-card>
</div>
<!-- 分页控制 -->
<div v-if="pagination.total > 0" class="card-pagination">
<a-pagination
:current="pagination.current"
:page-size="pagination.pageSize"
:total="pagination.total"
:page-size-options="[10, 20, 50, 100]"
show-page-size
show-total
@change="onPageChange"
@page-size-change="onPageSizeChange"
/>
</div>
<!-- 空状态 -->
<a-empty
v-if="!loading && renderData.length === 0"
description="暂无绑卡记录"
/>
</a-spin>
</div>
</a-modal>
</template>
<script lang="ts" setup>
import { ref, reactive, computed, watch } from 'vue';
import { TableColumnData } from '@arco-design/web-vue';
import IconList from '@arco-design/web-vue/es/icon/icon-list';
import IconApps from '@arco-design/web-vue/es/icon/icon-apps';
import useLoading from '@/hooks/loading';
import { Pagination } from '@/types/global';
import {
@@ -138,6 +286,24 @@ const columns: TableColumnData[] = [
const { loading, setLoading } = useLoading(true);
const renderData = ref<KamiApiCamelOilV1CardBindingInfo[]>([]);
// 视图模式
const viewMode = ref<'table' | 'card'>('table');
// 计算总金额
const totalAmount = computed(() => {
return renderData.value.reduce((sum, record) => {
return sum + (Number(record.amount) || 0);
}, 0);
});
// 根据金额获取颜色
const getAmountColor = (amount: number | undefined): string => {
if (!amount) return 'gray';
if (amount < 100) return 'orange';
if (amount < 500) return 'blue';
return 'green';
};
const fetchData = async (params: any = { current: 1, pageSize: 20 }) => {
if (!props.tokenId) {
return;
@@ -211,10 +377,134 @@ watch(
</script>
<style scoped>
/* 使用 Arco Design 的原生样式,确保主题兼容性 */
/* 响应式设计 */
@media (width <= 768px) {
.card-grid {
grid-template-columns: 1fr;
gap: 12px;
}
.modal-header {
flex-direction: column;
gap: 16px;
align-items: stretch;
}
.view-controls {
justify-content: center;
}
.stats-info {
justify-content: center;
}
.card-header {
flex-direction: column;
gap: 12px;
align-items: flex-start;
}
}
@media (width <= 480px) {
.card-grid {
grid-template-columns: 1fr;
}
}
.modal-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
padding: 12px 16px;
background: var(--color-bg-2);
border-radius: 6px;
border: 1px solid var(--color-border-2);
}
.view-controls {
display: flex;
align-items: center;
}
.stats-info {
display: flex;
align-items: center;
}
/* 表格视图样式 */
.table-view {
margin-top: 16px;
}
/* 卡片视图样式 */
.card-view {
margin-top: 16px;
}
.card-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(400px, 1fr));
gap: 16px;
margin-bottom: 20px;
min-height: 200px;
}
.binding-card {
transition: all 0.2s ease;
border: 1px solid var(--color-border-2);
}
.binding-card:hover {
border-color: var(--color-primary-light-4);
box-shadow: 0 4px 12px rgb(0 0 0 / 8%);
transform: translateY(-2px);
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
}
.card-title {
display: flex;
gap: 8px;
align-items: center;
flex-wrap: wrap;
}
.card-amount {
display: flex;
align-items: center;
}
.card-content {
padding: 0;
}
.copy-text {
font-family: Monaco, Menlo, 'Ubuntu Mono', monospace;
font-size: 13px;
background: var(--color-fill-2);
padding: 2px 6px;
border-radius: 4px;
word-break: break-all;
}
.card-pagination {
display: flex;
justify-content: center;
margin-top: 24px;
padding: 16px 0;
}
/* 模态框样式优化 */
:deep(.arco-modal-body) {
max-height: 70vh;
max-height: 80vh;
overflow-y: auto;
padding: 20px 24px;
/* 确保滚动条样式跟随主题 */
scrollbar-width: thin;
@@ -222,15 +512,96 @@ watch(
}
:deep(.arco-modal-body::-webkit-scrollbar) {
width: 6px;
width: 8px;
}
:deep(.arco-modal-body::-webkit-scrollbar-thumb) {
background-color: var(--color-border-3);
border-radius: 3px;
border-radius: 4px;
}
:deep(.arco-modal-body::-webkit-scrollbar-track) {
background-color: transparent;
}
/* 表格优化 */
:deep(.arco-table) {
border-radius: 6px;
overflow: hidden;
}
:deep(.arco-table-th) {
background: var(--color-bg-2);
font-weight: 600;
}
:deep(.arco-table-container) {
border: 1px solid var(--color-border-2);
}
/* 单选按钮组样式 */
:deep(.arco-radio-group) {
background: transparent;
}
:deep(.arco-radio-button) {
background: var(--color-bg-2);
border-color: var(--color-border-2);
}
:deep(.arco-radio-button:hover) {
border-color: var(--color-primary-light-4);
}
/* 统计信息样式 */
:deep(.arco-statistic) {
text-align: center;
}
:deep(.arco-statistic-title) {
font-size: 14px;
color: var(--color-text-3);
margin-bottom: 4px;
}
:deep(.arco-statistic-value) {
font-size: 20px;
font-weight: 600;
}
/* 卡片描述列表样式 */
:deep(.arco-descriptions-item-label) {
font-weight: 500;
color: var(--color-text-2);
min-width: 60px;
}
:deep(.arco-descriptions-item-value) {
color: var(--color-text-1);
}
/* 加载状态优化 */
:deep(.arco-spin-container) {
min-height: 300px;
display: flex;
flex-direction: column;
}
/* 空状态样式 */
:deep(.arco-empty) {
margin: 40px 0;
}
/* 标签样式优化 */
:deep(.arco-tag) {
border-radius: 4px;
font-weight: 500;
}
/* 按钮图标样式 */
:deep(.arco-btn .arco-icon) {
margin-right: 4px;
}
/* 模态框头部样式 */
</style>

View File

@@ -85,7 +85,7 @@
}"
:columns="columns"
:data="renderData"
:scroll="{ x: 1200 }"
:scroll="{ x: 1800 }"
@page-change="onPageChange"
@page-size-change="onPageSizeChange"
>
@@ -196,12 +196,60 @@ const columns: TableColumnData[] = [
dataIndex: 'bindCount',
width: 100
},
{
title: '累计绑定金额',
dataIndex: 'totalBindAmount',
width: 120,
render: ({ record }) => {
if (!record.totalBindAmount) return '-';
return `¥${Number(record.totalBindAmount).toFixed(2)}`;
}
},
{
title: '总充值金额',
dataIndex: 'totalRechargeAmount',
width: 120,
render: ({ record }) => {
if (!record.totalRechargeAmount) return '-';
return `¥${Number(record.totalRechargeAmount).toFixed(2)}`;
}
},
{
title: '充值限制',
dataIndex: 'rechargeLimitAmount',
width: 110,
render: ({ record }) => {
if (!record.rechargeLimitAmount) return '-';
return `¥${Number(record.rechargeLimitAmount).toFixed(2)}`;
}
},
{
title: '次数限制',
dataIndex: 'rechargeLimitCount',
width: 100,
render: ({ record }) => {
return record.rechargeLimitCount || '-';
}
},
{
title: '最后绑定时间',
dataIndex: 'lastBindAt',
width: 180,
ellipsis: true,
tooltip: true,
render: ({ record }) => {
return formatDateTime(record.lastBindAt);
}
},
{
title: '最后使用时间',
dataIndex: 'lastUsedAt',
width: 180,
ellipsis: true,
tooltip: true
tooltip: true,
render: ({ record }) => {
return formatDateTime(record.lastUsedAt);
}
},
{
title: '状态',
@@ -219,7 +267,10 @@ const columns: TableColumnData[] = [
{
title: '创建时间',
dataIndex: 'createdAt',
width: 180
width: 180,
render: ({ record }) => {
return formatDateTime(record.createdAt);
}
},
{
title: '操作',
@@ -341,6 +392,18 @@ const statusMapper = (status: number): { text: string; color: string } => {
}
};
const formatDateTime = (dateTime: string | undefined): string => {
if (!dateTime) return '-';
return new Date(dateTime).toLocaleString('zh-CN', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit'
});
};
const download = () => {
// TODO: 实现导出功能
Notification.info({