feat(account): 新增删除所有过期无效账号功能
- 在 JDV2AccountApi 中新增 apiJdV2AccountDeleteExpiredDelete 方法支持删除过期账号 - 更新接口文档,添加删除过期账号 API 说明及示例 - 新增 KamiApiCamelOilV1DeleteExpiredAccountsRes 模型表示删除结果 - 在 camel-oil 业务路由中调整 Token 管理相关配置 - 账户列表页面增加“删除过期Token”按钮及对应逻辑实现 - 新增删除操作的确认弹窗及成功/失败通知提示 - 修改 Token 表单校验,优化充值金额和次数的输入检查 - 重构 Token 新增编辑模态框,加入加载中状态管理 - 删除账户绑卡记录弹窗组件,实现抽屉式 Token 详情及绑卡记录展示
This commit is contained in:
@@ -1,2 +1,2 @@
|
|||||||
VITE_API_BASE_URL= 'http://49.233.216.171:12310'
|
VITE_API_BASE_URL= 'http://127.0.0.1:12401'
|
||||||
# VITE_API_BASE_URL='https://partial.kkknametrans.buzz'
|
# VITE_API_BASE_URL='https://partial.kkknametrans.buzz'
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ docs/KamiApiCamelOilV1CheckAccountReq.md
|
|||||||
docs/KamiApiCamelOilV1CheckAccountRes.md
|
docs/KamiApiCamelOilV1CheckAccountRes.md
|
||||||
docs/KamiApiCamelOilV1CreateTokenReq.md
|
docs/KamiApiCamelOilV1CreateTokenReq.md
|
||||||
docs/KamiApiCamelOilV1CreateTokenRes.md
|
docs/KamiApiCamelOilV1CreateTokenRes.md
|
||||||
|
docs/KamiApiCamelOilV1DeleteExpiredAccountsRes.md
|
||||||
docs/KamiApiCamelOilV1DeleteTokenReq.md
|
docs/KamiApiCamelOilV1DeleteTokenReq.md
|
||||||
docs/KamiApiCamelOilV1DeleteTokenRes.md
|
docs/KamiApiCamelOilV1DeleteTokenRes.md
|
||||||
docs/KamiApiCamelOilV1DenominationSetting.md
|
docs/KamiApiCamelOilV1DenominationSetting.md
|
||||||
@@ -488,6 +489,7 @@ models/kami-api-camel-oil-v1-check-account-req.ts
|
|||||||
models/kami-api-camel-oil-v1-check-account-res.ts
|
models/kami-api-camel-oil-v1-check-account-res.ts
|
||||||
models/kami-api-camel-oil-v1-create-token-req.ts
|
models/kami-api-camel-oil-v1-create-token-req.ts
|
||||||
models/kami-api-camel-oil-v1-create-token-res.ts
|
models/kami-api-camel-oil-v1-create-token-res.ts
|
||||||
|
models/kami-api-camel-oil-v1-delete-expired-accounts-res.ts
|
||||||
models/kami-api-camel-oil-v1-delete-token-req.ts
|
models/kami-api-camel-oil-v1-delete-token-req.ts
|
||||||
models/kami-api-camel-oil-v1-delete-token-res.ts
|
models/kami-api-camel-oil-v1-delete-token-res.ts
|
||||||
models/kami-api-camel-oil-v1-denomination-setting.ts
|
models/kami-api-camel-oil-v1-denomination-setting.ts
|
||||||
|
|||||||
@@ -47,6 +47,8 @@ import type { KamiApiCamelOilV1CheckAccountReq } from '../models';
|
|||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import type { KamiApiCamelOilV1CheckAccountRes } from '../models';
|
import type { KamiApiCamelOilV1CheckAccountRes } from '../models';
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
|
import type { KamiApiCamelOilV1DeleteExpiredAccountsRes } from '../models';
|
||||||
|
// @ts-ignore
|
||||||
import type { KamiApiCamelOilV1ListAccountRes } from '../models';
|
import type { KamiApiCamelOilV1ListAccountRes } from '../models';
|
||||||
/**
|
/**
|
||||||
* JDV2AccountApi - axios parameter creator
|
* JDV2AccountApi - axios parameter creator
|
||||||
@@ -103,6 +105,45 @@ export const JDV2AccountApiAxiosParamCreator = function (
|
|||||||
options: localVarRequestOptions
|
options: localVarRequestOptions
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @summary 删除所有过期无效账号
|
||||||
|
* @param {*} [options] Override http request option.
|
||||||
|
* @throws {RequiredError}
|
||||||
|
*/
|
||||||
|
apiJdV2AccountDeleteExpiredDelete: async (
|
||||||
|
options: RawAxiosRequestConfig = {}
|
||||||
|
): Promise<RequestArgs> => {
|
||||||
|
const localVarPath = `/api/jd-v2/account/delete-expired`;
|
||||||
|
// 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: 'DELETE',
|
||||||
|
...baseOptions,
|
||||||
|
...options
|
||||||
|
};
|
||||||
|
const localVarHeaderParameter = {} as any;
|
||||||
|
const localVarQueryParameter = {} as any;
|
||||||
|
|
||||||
|
setSearchParams(localVarUrlObj, localVarQueryParameter);
|
||||||
|
let headersFromBaseOptions =
|
||||||
|
baseOptions && baseOptions.headers ? baseOptions.headers : {};
|
||||||
|
localVarRequestOptions.headers = {
|
||||||
|
...localVarHeaderParameter,
|
||||||
|
...headersFromBaseOptions,
|
||||||
|
...options.headers
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
url: toPathString(localVarUrlObj),
|
||||||
|
options: localVarRequestOptions
|
||||||
|
};
|
||||||
|
},
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @summary 账号历史记录
|
* @summary 账号历史记录
|
||||||
@@ -324,6 +365,37 @@ export const JDV2AccountApiFp = function (configuration?: Configuration) {
|
|||||||
configuration
|
configuration
|
||||||
)(axios, localVarOperationServerBasePath || basePath);
|
)(axios, localVarOperationServerBasePath || basePath);
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @summary 删除所有过期无效账号
|
||||||
|
* @param {*} [options] Override http request option.
|
||||||
|
* @throws {RequiredError}
|
||||||
|
*/
|
||||||
|
async apiJdV2AccountDeleteExpiredDelete(
|
||||||
|
options?: RawAxiosRequestConfig
|
||||||
|
): Promise<
|
||||||
|
(
|
||||||
|
axios?: AxiosInstance,
|
||||||
|
basePath?: string
|
||||||
|
) => AxiosPromise<KamiApiCamelOilV1DeleteExpiredAccountsRes>
|
||||||
|
> {
|
||||||
|
const localVarAxiosArgs =
|
||||||
|
await localVarAxiosParamCreator.apiJdV2AccountDeleteExpiredDelete(
|
||||||
|
options
|
||||||
|
);
|
||||||
|
const localVarOperationServerIndex = configuration?.serverIndex ?? 0;
|
||||||
|
const localVarOperationServerBasePath =
|
||||||
|
operationServerMap[
|
||||||
|
'JDV2AccountApi.apiJdV2AccountDeleteExpiredDelete'
|
||||||
|
]?.[localVarOperationServerIndex]?.url;
|
||||||
|
return (axios, basePath) =>
|
||||||
|
createRequestFunction(
|
||||||
|
localVarAxiosArgs,
|
||||||
|
globalAxios,
|
||||||
|
BASE_PATH,
|
||||||
|
configuration
|
||||||
|
)(axios, localVarOperationServerBasePath || basePath);
|
||||||
|
},
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @summary 账号历史记录
|
* @summary 账号历史记录
|
||||||
@@ -472,6 +544,19 @@ export const JDV2AccountApiFactory = function (
|
|||||||
)
|
)
|
||||||
.then(request => request(axios, basePath));
|
.then(request => request(axios, basePath));
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @summary 删除所有过期无效账号
|
||||||
|
* @param {*} [options] Override http request option.
|
||||||
|
* @throws {RequiredError}
|
||||||
|
*/
|
||||||
|
apiJdV2AccountDeleteExpiredDelete(
|
||||||
|
options?: RawAxiosRequestConfig
|
||||||
|
): AxiosPromise<KamiApiCamelOilV1DeleteExpiredAccountsRes> {
|
||||||
|
return localVarFp
|
||||||
|
.apiJdV2AccountDeleteExpiredDelete(options)
|
||||||
|
.then(request => request(axios, basePath));
|
||||||
|
},
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @summary 账号历史记录
|
* @summary 账号历史记录
|
||||||
@@ -547,6 +632,16 @@ export interface JDV2AccountApiInterface {
|
|||||||
options?: RawAxiosRequestConfig
|
options?: RawAxiosRequestConfig
|
||||||
): AxiosPromise<KamiApiCamelOilV1CheckAccountRes>;
|
): AxiosPromise<KamiApiCamelOilV1CheckAccountRes>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @summary 删除所有过期无效账号
|
||||||
|
* @param {*} [options] Override http request option.
|
||||||
|
* @throws {RequiredError}
|
||||||
|
*/
|
||||||
|
apiJdV2AccountDeleteExpiredDelete(
|
||||||
|
options?: RawAxiosRequestConfig
|
||||||
|
): AxiosPromise<KamiApiCamelOilV1DeleteExpiredAccountsRes>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @summary 账号历史记录
|
* @summary 账号历史记录
|
||||||
@@ -669,6 +764,18 @@ export class JDV2AccountApi extends BaseAPI implements JDV2AccountApiInterface {
|
|||||||
.then(request => request(this.axios, this.basePath));
|
.then(request => request(this.axios, this.basePath));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @summary 删除所有过期无效账号
|
||||||
|
* @param {*} [options] Override http request option.
|
||||||
|
* @throws {RequiredError}
|
||||||
|
*/
|
||||||
|
public apiJdV2AccountDeleteExpiredDelete(options?: RawAxiosRequestConfig) {
|
||||||
|
return JDV2AccountApiFp(this.configuration)
|
||||||
|
.apiJdV2AccountDeleteExpiredDelete(options)
|
||||||
|
.then(request => request(this.axios, this.basePath));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @summary 账号历史记录
|
* @summary 账号历史记录
|
||||||
|
|||||||
@@ -2,12 +2,13 @@
|
|||||||
|
|
||||||
All URIs are relative to _http://localhost_
|
All URIs are relative to _http://localhost_
|
||||||
|
|
||||||
| Method | HTTP request | Description |
|
| Method | HTTP request | Description |
|
||||||
| --------------------------------------------------------------- | ------------------------------------- | ------------ |
|
| --------------------------------------------------------------------------- | -------------------------------------------- | -------------------- |
|
||||||
| [**apiJdV2AccountCheckPost**](#apijdv2accountcheckpost) | **POST** /api/jd-v2/account/check | 棂测账号状态 |
|
| [**apiJdV2AccountCheckPost**](#apijdv2accountcheckpost) | **POST** /api/jd-v2/account/check | 棂测账号状态 |
|
||||||
| [**apiJdV2AccountHistoryGet**](#apijdv2accounthistoryget) | **GET** /api/jd-v2/account/history | 账号历史记录 |
|
| [**apiJdV2AccountDeleteExpiredDelete**](#apijdv2accountdeleteexpireddelete) | **DELETE** /api/jd-v2/account/delete-expired | 删除所有过期无效账号 |
|
||||||
| [**apiJdV2AccountListGet**](#apijdv2accountlistget) | **GET** /api/jd-v2/account/list | 账号列表 |
|
| [**apiJdV2AccountHistoryGet**](#apijdv2accounthistoryget) | **GET** /api/jd-v2/account/history | 账号历史记录 |
|
||||||
| [**apiJdV2AccountStatisticsGet**](#apijdv2accountstatisticsget) | **GET** /api/jd-v2/account/statistics | 账号统计信息 |
|
| [**apiJdV2AccountListGet**](#apijdv2accountlistget) | **GET** /api/jd-v2/account/list | 账号列表 |
|
||||||
|
| [**apiJdV2AccountStatisticsGet**](#apijdv2accountstatisticsget) | **GET** /api/jd-v2/account/statistics | 账号统计信息 |
|
||||||
|
|
||||||
# **apiJdV2AccountCheckPost**
|
# **apiJdV2AccountCheckPost**
|
||||||
|
|
||||||
@@ -59,6 +60,46 @@ No authorization required
|
|||||||
|
|
||||||
[[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)
|
[[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)
|
||||||
|
|
||||||
|
# **apiJdV2AccountDeleteExpiredDelete**
|
||||||
|
|
||||||
|
> KamiApiCamelOilV1DeleteExpiredAccountsRes apiJdV2AccountDeleteExpiredDelete()
|
||||||
|
|
||||||
|
### Example
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { JDV2AccountApi, Configuration } from './api';
|
||||||
|
|
||||||
|
const configuration = new Configuration();
|
||||||
|
const apiInstance = new JDV2AccountApi(configuration);
|
||||||
|
|
||||||
|
const { status, data } = await apiInstance.apiJdV2AccountDeleteExpiredDelete();
|
||||||
|
```
|
||||||
|
|
||||||
|
### Parameters
|
||||||
|
|
||||||
|
This endpoint does not have any parameters.
|
||||||
|
|
||||||
|
### Return type
|
||||||
|
|
||||||
|
**KamiApiCamelOilV1DeleteExpiredAccountsRes**
|
||||||
|
|
||||||
|
### Authorization
|
||||||
|
|
||||||
|
No authorization required
|
||||||
|
|
||||||
|
### HTTP request headers
|
||||||
|
|
||||||
|
- **Content-Type**: Not defined
|
||||||
|
- **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)
|
||||||
|
|
||||||
# **apiJdV2AccountHistoryGet**
|
# **apiJdV2AccountHistoryGet**
|
||||||
|
|
||||||
> KamiApiCamelOilV1AccountHistoryRes apiJdV2AccountHistoryGet()
|
> KamiApiCamelOilV1AccountHistoryRes apiJdV2AccountHistoryGet()
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
| Name | Type | Description | Notes |
|
| Name | Type | Description | Notes |
|
||||||
| ----------------------- | ---------- | ------------ | --------------------------------- |
|
| ----------------------- | ---------- | ------------ | --------------------------------- |
|
||||||
| **name** | **string** | Token名称 | [default to undefined] |
|
| **name** | **string** | Token名称 | [default to undefined] |
|
||||||
| **phone** | **string** | 绑定的手机号 | [default to undefined] |
|
| **phone** | **string** | 绑定的手机号 | [optional] [default to undefined] |
|
||||||
| **remark** | **string** | 备注 | [optional] [default to undefined] |
|
| **remark** | **string** | 备注 | [optional] [default to undefined] |
|
||||||
| **rechargeLimitAmount** | **number** | 充值金额限制 | [default to undefined] |
|
| **rechargeLimitAmount** | **number** | 充值金额限制 | [default to undefined] |
|
||||||
| **rechargeLimitCount** | **number** | 充值次数限制 | [default to undefined] |
|
| **rechargeLimitCount** | **number** | 充值次数限制 | [default to undefined] |
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
# KamiApiCamelOilV1DeleteExpiredAccountsRes
|
||||||
|
|
||||||
|
## Properties
|
||||||
|
|
||||||
|
| Name | Type | Description | Notes |
|
||||||
|
| ---------------- | ---------- | -------------- | --------------------------------- |
|
||||||
|
| **message** | **string** | 操作结果信息 | [optional] [default to undefined] |
|
||||||
|
| **deletedCount** | **number** | 删除的账号数量 | [optional] [default to undefined] |
|
||||||
|
|
||||||
|
## Example
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { KamiApiCamelOilV1DeleteExpiredAccountsRes } from './api';
|
||||||
|
|
||||||
|
const instance: KamiApiCamelOilV1DeleteExpiredAccountsRes = {
|
||||||
|
message,
|
||||||
|
deletedCount
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
||||||
@@ -17,6 +17,7 @@ export * from './kami-api-camel-oil-v1-check-account-req';
|
|||||||
export * from './kami-api-camel-oil-v1-check-account-res';
|
export * from './kami-api-camel-oil-v1-check-account-res';
|
||||||
export * from './kami-api-camel-oil-v1-create-token-req';
|
export * from './kami-api-camel-oil-v1-create-token-req';
|
||||||
export * from './kami-api-camel-oil-v1-create-token-res';
|
export * from './kami-api-camel-oil-v1-create-token-res';
|
||||||
|
export * from './kami-api-camel-oil-v1-delete-expired-accounts-res';
|
||||||
export * from './kami-api-camel-oil-v1-delete-token-req';
|
export * from './kami-api-camel-oil-v1-delete-token-req';
|
||||||
export * from './kami-api-camel-oil-v1-delete-token-res';
|
export * from './kami-api-camel-oil-v1-delete-token-res';
|
||||||
export * from './kami-api-camel-oil-v1-denomination-setting';
|
export * from './kami-api-camel-oil-v1-denomination-setting';
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ export interface KamiApiCamelOilV1CreateTokenReq {
|
|||||||
/**
|
/**
|
||||||
* 绑定的手机号
|
* 绑定的手机号
|
||||||
*/
|
*/
|
||||||
phone: string;
|
phone?: string;
|
||||||
/**
|
/**
|
||||||
* 备注
|
* 备注
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
/* 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 KamiApiCamelOilV1DeleteExpiredAccountsRes {
|
||||||
|
/**
|
||||||
|
* 操作结果信息
|
||||||
|
*/
|
||||||
|
message?: string;
|
||||||
|
/**
|
||||||
|
* 删除的账号数量
|
||||||
|
*/
|
||||||
|
deletedCount?: number;
|
||||||
|
}
|
||||||
@@ -12,19 +12,18 @@ const CamelOilMgt: AppRouteRecordRaw = {
|
|||||||
meta: {
|
meta: {
|
||||||
locale: '骆驼加油',
|
locale: '骆驼加油',
|
||||||
requiresAuth: true,
|
requiresAuth: true,
|
||||||
icon: 'icon-car',
|
icon: 'icon-safe',
|
||||||
order: 5
|
order: 5
|
||||||
},
|
},
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
path: 'token',
|
path: 'token',
|
||||||
name: 'camelOilToken',
|
name: 'camelOilTokenUser',
|
||||||
component: () => import('@/views/camel-oil-info/token/index.vue'),
|
component: () => import('@/views/camel-oil-info/token/index.vue'),
|
||||||
meta: {
|
meta: {
|
||||||
locale: 'Token管理',
|
locale: 'Token管理',
|
||||||
requiresAuth: true,
|
requiresAuth: true,
|
||||||
roles: ['*'],
|
roles: ['*']
|
||||||
activeMenu: '/camel-oil/token'
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -61,6 +61,17 @@
|
|||||||
<!-- 操作按钮区域 -->
|
<!-- 操作按钮区域 -->
|
||||||
<a-row style="margin-bottom: 16px">
|
<a-row style="margin-bottom: 16px">
|
||||||
<a-space>
|
<a-space>
|
||||||
|
<a-button
|
||||||
|
type="primary"
|
||||||
|
status="danger"
|
||||||
|
:loading="deleteLoading"
|
||||||
|
@click="deleteExpiredTokens"
|
||||||
|
>
|
||||||
|
<template #icon>
|
||||||
|
<icon-delete />
|
||||||
|
</template>
|
||||||
|
删除过期Token
|
||||||
|
</a-button>
|
||||||
<a-button @click="download">
|
<a-button @click="download">
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<icon-download />
|
<icon-download />
|
||||||
@@ -131,7 +142,7 @@
|
|||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { onMounted, reactive, ref } from 'vue';
|
import { onMounted, reactive, ref } from 'vue';
|
||||||
import { Notification, TableColumnData } from '@arco-design/web-vue';
|
import { Notification, Modal, TableColumnData } from '@arco-design/web-vue';
|
||||||
import useLoading from '@/hooks/loading';
|
import useLoading from '@/hooks/loading';
|
||||||
import { Pagination } from '@/types/global';
|
import { Pagination } from '@/types/global';
|
||||||
import { jdV2AccountClient } from '@/api/index.ts';
|
import { jdV2AccountClient } from '@/api/index.ts';
|
||||||
@@ -152,6 +163,8 @@ const basePagination: Pagination = {
|
|||||||
// 状态管理
|
// 状态管理
|
||||||
const pagination = reactive({ ...basePagination });
|
const pagination = reactive({ ...basePagination });
|
||||||
const { loading, setLoading } = useLoading(true);
|
const { loading, setLoading } = useLoading(true);
|
||||||
|
const { loading: deleteLoading, setLoading: setDeleteLoading } =
|
||||||
|
useLoading(false);
|
||||||
const renderData = ref<KamiApiCamelOilV1AccountListItem[]>([]);
|
const renderData = ref<KamiApiCamelOilV1AccountListItem[]>([]);
|
||||||
|
|
||||||
// 表格列配置
|
// 表格列配置
|
||||||
@@ -390,6 +403,46 @@ const statusMapper = (status: number): { text: string } => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除所有过期Token
|
||||||
|
*/
|
||||||
|
const deleteExpiredTokens = async () => {
|
||||||
|
// 显示确认对话框
|
||||||
|
Modal.confirm({
|
||||||
|
title: '确认删除',
|
||||||
|
content: '此操作将删除所有过期的Token,操作不可恢复,是否继续?',
|
||||||
|
okText: '确认删除',
|
||||||
|
cancelText: '取消',
|
||||||
|
okButtonProps: {
|
||||||
|
status: 'danger'
|
||||||
|
},
|
||||||
|
onOk: async () => {
|
||||||
|
try {
|
||||||
|
setDeleteLoading(true);
|
||||||
|
|
||||||
|
// 调用删除API
|
||||||
|
await jdV2AccountClient.apiJdV2AccountDeleteExpiredDelete();
|
||||||
|
|
||||||
|
Notification.success({
|
||||||
|
content: '删除过期Token成功',
|
||||||
|
closable: true
|
||||||
|
});
|
||||||
|
|
||||||
|
// 刷新列表数据
|
||||||
|
fetchAccountData();
|
||||||
|
} catch (err) {
|
||||||
|
console.error('删除过期Token失败:', err);
|
||||||
|
Notification.error({
|
||||||
|
content: '删除过期Token失败,请稍后重试',
|
||||||
|
closable: true
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
setDeleteLoading(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 导出功能
|
* 导出功能
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
v-model:visible="modalVisible"
|
v-model:visible="modalVisible"
|
||||||
:title="isEditMode ? '编辑Token' : '新增Token'"
|
:title="isEditMode ? '编辑Token' : '新增Token'"
|
||||||
width="600px"
|
width="600px"
|
||||||
|
:ok-loading="loading"
|
||||||
@cancel="handleCancel"
|
@cancel="handleCancel"
|
||||||
@before-ok="handleBeforeOk"
|
@before-ok="handleBeforeOk"
|
||||||
>
|
>
|
||||||
@@ -66,6 +67,7 @@ import { ref, reactive, computed, watch } from 'vue';
|
|||||||
import { Notification, FormInstance } from '@arco-design/web-vue';
|
import { Notification, FormInstance } from '@arco-design/web-vue';
|
||||||
import { KamiApiCamelOilV1TokenInfo } from '@/api/generated/index.ts';
|
import { KamiApiCamelOilV1TokenInfo } from '@/api/generated/index.ts';
|
||||||
import { jdV2TokenClient } from '@/api/index.ts';
|
import { jdV2TokenClient } from '@/api/index.ts';
|
||||||
|
import useLoading from '@/hooks/loading';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
visible: boolean;
|
visible: boolean;
|
||||||
@@ -90,6 +92,8 @@ const isEditMode = computed(() => !!props.editData);
|
|||||||
|
|
||||||
const formRef = ref<FormInstance>();
|
const formRef = ref<FormInstance>();
|
||||||
|
|
||||||
|
const { loading, setLoading } = useLoading(false);
|
||||||
|
|
||||||
const generateFormModel = () => {
|
const generateFormModel = () => {
|
||||||
return {
|
return {
|
||||||
tokenId: 0,
|
tokenId: 0,
|
||||||
@@ -125,11 +129,37 @@ const rules = computed(() => ({
|
|||||||
remark: [{ max: 500, message: '备注长度不能超过500个字符' }],
|
remark: [{ max: 500, message: '备注长度不能超过500个字符' }],
|
||||||
rechargeLimitAmount: [
|
rechargeLimitAmount: [
|
||||||
{ required: true, message: '请输入充值金额限制' },
|
{ required: true, message: '请输入充值金额限制' },
|
||||||
{ type: 'number', min: 0, message: '充值金额限制不能小于0' }
|
{
|
||||||
|
validator: (
|
||||||
|
value: number | string | null | undefined,
|
||||||
|
callback: (error?: string) => void
|
||||||
|
) => {
|
||||||
|
if (value === null || value === undefined || value === '') {
|
||||||
|
callback('请输入充值金额限制');
|
||||||
|
} else if (Number(value) < 0) {
|
||||||
|
callback('充值金额限制不能小于0');
|
||||||
|
} else {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
],
|
],
|
||||||
rechargeLimitCount: [
|
rechargeLimitCount: [
|
||||||
{ required: true, message: '请输入充值次数限制' },
|
{ required: true, message: '请输入充值次数限制' },
|
||||||
{ type: 'number', min: 0, message: '充值次数限制不能小于0' }
|
{
|
||||||
|
validator: (
|
||||||
|
value: number | string | null | undefined,
|
||||||
|
callback: (error?: string) => void
|
||||||
|
) => {
|
||||||
|
if (value === null || value === undefined || value === '') {
|
||||||
|
callback('请输入充值次数限制');
|
||||||
|
} else if (Number(value) < 0) {
|
||||||
|
callback('充值次数限制不能小于0');
|
||||||
|
} else {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@@ -161,59 +191,74 @@ const handleCancel = () => {
|
|||||||
emit('update:visible', false);
|
emit('update:visible', false);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleBeforeOk = async () => {
|
const handleBeforeOk = (done: (closed: boolean) => void) => {
|
||||||
try {
|
formRef.value?.validate().then(async (res: any) => {
|
||||||
const valid = await formRef.value?.validate();
|
if (res) {
|
||||||
if (!valid) {
|
console.log('Validation failed:', res);
|
||||||
return false;
|
return done(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isEditMode.value) {
|
try {
|
||||||
// 编辑账户
|
setLoading(true);
|
||||||
await jdV2TokenClient.apiTokenUpdatePost({
|
console.log('Validation passed, making API call...');
|
||||||
kamiApiCamelOilV1UpdateTokenReq: {
|
|
||||||
tokenId: formModel.tokenId,
|
|
||||||
name: formModel.name,
|
|
||||||
remark: formModel.remark,
|
|
||||||
rechargeLimitAmount: formModel.rechargeLimitAmount,
|
|
||||||
rechargeLimitCount: formModel.rechargeLimitCount
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
Notification.success({
|
if (isEditMode.value) {
|
||||||
content: '编辑账户成功',
|
// 编辑账户
|
||||||
closable: true
|
console.log('Editing token...');
|
||||||
});
|
await jdV2TokenClient.apiTokenUpdatePost({
|
||||||
} else {
|
kamiApiCamelOilV1UpdateTokenReq: {
|
||||||
// 新增账户
|
tokenId: formModel.tokenId,
|
||||||
await jdV2TokenClient.apiTokenCreatePost({
|
name: formModel.name,
|
||||||
kamiApiCamelOilV1CreateTokenReq: {
|
remark: formModel.remark,
|
||||||
name: formModel.name,
|
rechargeLimitAmount: formModel.rechargeLimitAmount,
|
||||||
phone: formModel.phone,
|
rechargeLimitCount: formModel.rechargeLimitCount
|
||||||
remark: formModel.remark,
|
}
|
||||||
rechargeLimitAmount: formModel.rechargeLimitAmount,
|
});
|
||||||
rechargeLimitCount: formModel.rechargeLimitCount
|
|
||||||
}
|
Notification.success({
|
||||||
});
|
content: '编辑账户成功',
|
||||||
|
closable: true
|
||||||
Notification.success({
|
});
|
||||||
content: '新增账户成功',
|
} else {
|
||||||
|
// 新增账户
|
||||||
|
console.log('Creating token...');
|
||||||
|
console.log('Form data:', formModel);
|
||||||
|
|
||||||
|
await jdV2TokenClient.apiTokenCreatePost({
|
||||||
|
kamiApiCamelOilV1CreateTokenReq: {
|
||||||
|
name: formModel.name,
|
||||||
|
phone: formModel.phone,
|
||||||
|
remark: formModel.remark,
|
||||||
|
rechargeLimitAmount: formModel.rechargeLimitAmount,
|
||||||
|
rechargeLimitCount: formModel.rechargeLimitCount
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Notification.success({
|
||||||
|
content: '新增账户成功',
|
||||||
|
closable: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 操作成功,触发成功事件并关闭模态框
|
||||||
|
console.log('API call successful, emitting success and closing modal');
|
||||||
|
emit('success');
|
||||||
|
emit('update:visible', false);
|
||||||
|
done(true);
|
||||||
|
} catch (err) {
|
||||||
|
const action = isEditMode.value ? '编辑' : '新增';
|
||||||
|
console.error(`${action}账户失败:`, err);
|
||||||
|
Notification.error({
|
||||||
|
content: `${action}账户失败,请重试`,
|
||||||
closable: true
|
closable: true
|
||||||
});
|
});
|
||||||
|
// 操作失败,阻止模态框关闭
|
||||||
|
console.log('API call failed, keeping modal open');
|
||||||
|
done(false);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
}
|
}
|
||||||
|
});
|
||||||
emit('success');
|
|
||||||
emit('update:visible', false);
|
|
||||||
return true;
|
|
||||||
} catch (err) {
|
|
||||||
const action = isEditMode.value ? '编辑' : '新增';
|
|
||||||
console.error(`${action}账户失败:`, err);
|
|
||||||
Notification.error({
|
|
||||||
content: `${action}账户失败`,
|
|
||||||
closable: true
|
|
||||||
});
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -1,607 +0,0 @@
|
|||||||
<template>
|
|
||||||
<a-modal
|
|
||||||
v-model:visible="modalVisible"
|
|
||||||
:title="`账户绑卡记录 - ${tokenName}`"
|
|
||||||
width="90%"
|
|
||||||
:footer="false"
|
|
||||||
@cancel="handleCancel"
|
|
||||||
>
|
|
||||||
<!-- 视图切换和统计信息 -->
|
|
||||||
<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>
|
|
||||||
|
|
||||||
<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 {
|
|
||||||
ApiCardBindingListByTokenGetPageSizeEnum,
|
|
||||||
KamiApiCamelOilV1CardBindingInfo
|
|
||||||
} from '@/api/generated/index.ts';
|
|
||||||
import { jdV2TokenClient } from '@/api/index.ts';
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
visible: boolean;
|
|
||||||
tokenId: number;
|
|
||||||
tokenName: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Emits {
|
|
||||||
(e: 'update:visible', value: boolean): void;
|
|
||||||
}
|
|
||||||
|
|
||||||
const props = withDefaults(defineProps<Props>(), {
|
|
||||||
tokenId: 0,
|
|
||||||
tokenName: ''
|
|
||||||
});
|
|
||||||
|
|
||||||
const emit = defineEmits<Emits>();
|
|
||||||
|
|
||||||
const modalVisible = computed({
|
|
||||||
get: () => props.visible,
|
|
||||||
set: value => emit('update:visible', value)
|
|
||||||
});
|
|
||||||
|
|
||||||
const basePagination: Pagination = {
|
|
||||||
current: 1,
|
|
||||||
pageSize: 20
|
|
||||||
};
|
|
||||||
const pagination = reactive({
|
|
||||||
...basePagination
|
|
||||||
});
|
|
||||||
|
|
||||||
const columns: TableColumnData[] = [
|
|
||||||
{
|
|
||||||
title: '序号',
|
|
||||||
dataIndex: 'index',
|
|
||||||
width: 80,
|
|
||||||
render: ({ rowIndex }) => {
|
|
||||||
return rowIndex + 1 + (pagination.current - 1) * pagination.pageSize;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '绑卡ID',
|
|
||||||
dataIndex: 'id',
|
|
||||||
width: 100
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '账户 ID',
|
|
||||||
dataIndex: 'tokenId',
|
|
||||||
width: 100
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '账户名称',
|
|
||||||
dataIndex: 'name',
|
|
||||||
width: 150,
|
|
||||||
ellipsis: true,
|
|
||||||
tooltip: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '订单ID',
|
|
||||||
dataIndex: 'orderId',
|
|
||||||
width: 100
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '卡号',
|
|
||||||
dataIndex: 'cardNumber',
|
|
||||||
width: 200,
|
|
||||||
ellipsis: true,
|
|
||||||
tooltip: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '卡密',
|
|
||||||
dataIndex: 'cardPassword',
|
|
||||||
width: 150,
|
|
||||||
ellipsis: true,
|
|
||||||
tooltip: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '金额',
|
|
||||||
dataIndex: 'amount',
|
|
||||||
slotName: 'amount',
|
|
||||||
width: 120,
|
|
||||||
ellipsis: true,
|
|
||||||
tooltip: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '创建时间',
|
|
||||||
dataIndex: 'createdAt',
|
|
||||||
slotName: 'createdAt',
|
|
||||||
width: 180
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
setLoading(true);
|
|
||||||
try {
|
|
||||||
const { data } = await jdV2TokenClient.apiCardBindingListByTokenGet({
|
|
||||||
current: params.current,
|
|
||||||
pageSize:
|
|
||||||
params.pageSize as unknown as ApiCardBindingListByTokenGetPageSizeEnum,
|
|
||||||
tokenId: props.tokenId
|
|
||||||
});
|
|
||||||
|
|
||||||
renderData.value = data.list || [];
|
|
||||||
pagination.current = params.current;
|
|
||||||
pagination.pageSize = params.pageSize;
|
|
||||||
pagination.total = data.total || 0;
|
|
||||||
} catch (err) {
|
|
||||||
console.error('获取绑卡记录失败:', err);
|
|
||||||
renderData.value = [];
|
|
||||||
pagination.total = 0;
|
|
||||||
} finally {
|
|
||||||
setLoading(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const onPageChange = (current: number) => {
|
|
||||||
fetchData({ ...pagination, current });
|
|
||||||
};
|
|
||||||
|
|
||||||
const onPageSizeChange = (pageSize: number) => {
|
|
||||||
fetchData({ ...pagination, pageSize });
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleCancel = () => {
|
|
||||||
emit('update:visible', false);
|
|
||||||
};
|
|
||||||
|
|
||||||
const formatAmount = (amount: any): string => {
|
|
||||||
if (!amount) return '-';
|
|
||||||
if (typeof amount === 'object') {
|
|
||||||
return JSON.stringify(amount);
|
|
||||||
}
|
|
||||||
return String(amount);
|
|
||||||
};
|
|
||||||
|
|
||||||
const formatDateTime = (dateTime: 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'
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// 监听弹窗显示状态
|
|
||||||
watch(
|
|
||||||
() => props.visible,
|
|
||||||
visible => {
|
|
||||||
if (visible && props.tokenId) {
|
|
||||||
// 重置分页并获取数据
|
|
||||||
Object.assign(pagination, basePagination);
|
|
||||||
fetchData();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
/* 响应式设计 */
|
|
||||||
@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: 80vh;
|
|
||||||
overflow-y: auto;
|
|
||||||
padding: 20px 24px;
|
|
||||||
|
|
||||||
/* 确保滚动条样式跟随主题 */
|
|
||||||
scrollbar-width: thin;
|
|
||||||
scrollbar-color: var(--color-border-3) transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.arco-modal-body::-webkit-scrollbar) {
|
|
||||||
width: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.arco-modal-body::-webkit-scrollbar-thumb) {
|
|
||||||
background-color: var(--color-border-3);
|
|
||||||
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>
|
|
||||||
598
src/views/camel-oil-info/token/components/token-drawer.vue
Normal file
598
src/views/camel-oil-info/token/components/token-drawer.vue
Normal file
@@ -0,0 +1,598 @@
|
|||||||
|
<template>
|
||||||
|
<a-drawer
|
||||||
|
v-model:visible="drawerVisible"
|
||||||
|
:title="drawerTitle"
|
||||||
|
width="800px"
|
||||||
|
placement="right"
|
||||||
|
:footer="false"
|
||||||
|
@cancel="handleCancel"
|
||||||
|
>
|
||||||
|
<!-- 标签页切换 -->
|
||||||
|
<a-tabs v-model:active-key="activeTab" class="drawer-tabs">
|
||||||
|
<!-- Token详情标签页 -->
|
||||||
|
<a-tab-pane key="details" title="Token详情">
|
||||||
|
<div class="token-details">
|
||||||
|
<a-descriptions
|
||||||
|
:column="1"
|
||||||
|
:data="state.descriptionData"
|
||||||
|
:label-style="{ width: '120px', fontWeight: '500' }"
|
||||||
|
:value-style="{ flex: 1 }"
|
||||||
|
bordered
|
||||||
|
>
|
||||||
|
<template #label="{ label }">
|
||||||
|
<span>{{ label }}</span>
|
||||||
|
</template>
|
||||||
|
<template #value="{ data }">
|
||||||
|
<span v-if="data.field === 'status'" class="status-tag">
|
||||||
|
<a-tag :color="statusMapper(data.value).color" size="small">
|
||||||
|
{{ statusMapper(data.value).text }}
|
||||||
|
</a-tag>
|
||||||
|
</span>
|
||||||
|
<span v-else-if="data.field === 'loginToken'" class="token-value">
|
||||||
|
<a-typography-text :ellipsis="{ rows: 2, showTooltip: true }">
|
||||||
|
{{ data.value || '-' }}
|
||||||
|
</a-typography-text>
|
||||||
|
</span>
|
||||||
|
<span v-else class="normal-value">
|
||||||
|
{{ data.value || '-' }}
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</a-descriptions>
|
||||||
|
</div>
|
||||||
|
</a-tab-pane>
|
||||||
|
|
||||||
|
<!-- 绑卡记录标签页 -->
|
||||||
|
<a-tab-pane key="bindings" title="绑卡记录">
|
||||||
|
<div class="binding-records">
|
||||||
|
<!-- 视图切换 -->
|
||||||
|
<div class="records-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>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 表格视图 -->
|
||||||
|
<div v-if="viewMode === 'table'" class="table-view">
|
||||||
|
<a-table
|
||||||
|
:loading="bindingLoading"
|
||||||
|
:pagination="{
|
||||||
|
current: bindingPagination.current,
|
||||||
|
pageSize: bindingPagination.pageSize,
|
||||||
|
total: bindingPagination.total,
|
||||||
|
pageSizeOptions: [10, 20, 50, 100],
|
||||||
|
showPageSize: true,
|
||||||
|
showTotal: true
|
||||||
|
}"
|
||||||
|
:columns="bindingColumns"
|
||||||
|
:data="bindingRenderData"
|
||||||
|
:scroll="{ x: 1200 }"
|
||||||
|
@page-change="onBindingPageChange"
|
||||||
|
@page-size-change="onBindingPageSizeChange"
|
||||||
|
>
|
||||||
|
<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="bindingLoading" style="width: 100%">
|
||||||
|
<div class="card-grid">
|
||||||
|
<a-card
|
||||||
|
v-for="record in bindingRenderData"
|
||||||
|
: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"
|
||||||
|
:value-style="{ color: '#00b42a', fontSize: '16px' }"
|
||||||
|
>
|
||||||
|
<template #prefix>¥</template>
|
||||||
|
</a-statistic>
|
||||||
|
</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="创建时间">
|
||||||
|
{{ formatDateTime(record.createdAt) }}
|
||||||
|
</a-descriptions-item>
|
||||||
|
|
||||||
|
<a-descriptions-item v-if="record.orderId" label="订单ID">
|
||||||
|
{{ record.orderId }}
|
||||||
|
</a-descriptions-item>
|
||||||
|
</a-descriptions>
|
||||||
|
</div>
|
||||||
|
</a-card>
|
||||||
|
</div>
|
||||||
|
</a-spin>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a-tab-pane>
|
||||||
|
</a-tabs>
|
||||||
|
</a-drawer>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { computed, reactive, ref, watch } from 'vue';
|
||||||
|
import { KamiApiCamelOilV1TokenInfo } from '@/api/generated/index.ts';
|
||||||
|
import { jdV2TokenClient } from '@/api/index.ts';
|
||||||
|
import useLoading from '@/hooks/loading';
|
||||||
|
import { Pagination } from '@/types/global';
|
||||||
|
import { TableColumnData } from '@arco-design/web-vue';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
visible: boolean;
|
||||||
|
tokenData?: KamiApiCamelOilV1TokenInfo | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Emits {
|
||||||
|
(e: 'update:visible', value: boolean): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps<Props>();
|
||||||
|
const emit = defineEmits<Emits>();
|
||||||
|
|
||||||
|
const drawerVisible = computed({
|
||||||
|
get: () => props.visible,
|
||||||
|
set: value => emit('update:visible', value)
|
||||||
|
});
|
||||||
|
|
||||||
|
const drawerTitle = computed(() => {
|
||||||
|
return props.tokenData ? `Token详情 - ${props.tokenData.name}` : 'Token详情';
|
||||||
|
});
|
||||||
|
|
||||||
|
const activeTab = ref('details');
|
||||||
|
|
||||||
|
const state = reactive({
|
||||||
|
descriptionData: [] as Array<{ label: string; value: any; field: string }>
|
||||||
|
});
|
||||||
|
|
||||||
|
const { loading: bindingLoading, setLoading: setBindingLoading } =
|
||||||
|
useLoading(true);
|
||||||
|
const viewMode = ref('table');
|
||||||
|
const bindingRenderData = ref([]);
|
||||||
|
|
||||||
|
const basePagination: Pagination = {
|
||||||
|
current: 1,
|
||||||
|
pageSize: 20
|
||||||
|
};
|
||||||
|
const bindingPagination = reactive({
|
||||||
|
...basePagination
|
||||||
|
});
|
||||||
|
|
||||||
|
const statusMapper = (status: number): { text: string; color: string } => {
|
||||||
|
switch (status) {
|
||||||
|
case 0:
|
||||||
|
return { text: '待验证码', color: 'orange' };
|
||||||
|
case 1:
|
||||||
|
return { text: '已登录', color: 'green' };
|
||||||
|
case 2:
|
||||||
|
return { text: '已禁用', color: 'red' };
|
||||||
|
case 3:
|
||||||
|
return { text: '已过期', color: 'gray' };
|
||||||
|
case 4:
|
||||||
|
return { text: '登录失败', color: 'red' };
|
||||||
|
case 5:
|
||||||
|
return { text: '验证码已发送', color: 'blue' };
|
||||||
|
case 6:
|
||||||
|
return { text: '验证码验证失败', color: 'red' };
|
||||||
|
default:
|
||||||
|
return { text: '未知', color: 'gray' };
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
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 formatCurrency = (amount: number | undefined | null): string => {
|
||||||
|
if (amount === undefined || amount === null) return '-';
|
||||||
|
return `¥${Number(amount).toFixed(2)}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const formatAmount = (amount: number | undefined): string => {
|
||||||
|
if (amount === undefined) return '0.00';
|
||||||
|
return Number(amount).toFixed(2);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getAmountColor = (amount: number): string => {
|
||||||
|
if (amount >= 1000) return 'red';
|
||||||
|
if (amount >= 500) return 'orange';
|
||||||
|
if (amount >= 100) return 'gold';
|
||||||
|
return 'blue';
|
||||||
|
};
|
||||||
|
|
||||||
|
// 绑卡记录表格列定义
|
||||||
|
const bindingColumns: TableColumnData[] = [
|
||||||
|
{
|
||||||
|
title: 'ID',
|
||||||
|
dataIndex: 'id',
|
||||||
|
width: 80
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '卡号',
|
||||||
|
dataIndex: 'cardNumber',
|
||||||
|
slotName: 'cardNumber',
|
||||||
|
width: 200,
|
||||||
|
ellipsis: true,
|
||||||
|
tooltip: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '卡密',
|
||||||
|
dataIndex: 'cardPassword',
|
||||||
|
slotName: 'cardPassword',
|
||||||
|
width: 200,
|
||||||
|
ellipsis: true,
|
||||||
|
tooltip: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '金额',
|
||||||
|
dataIndex: 'amount',
|
||||||
|
slotName: 'amount',
|
||||||
|
width: 120
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '订单ID',
|
||||||
|
dataIndex: 'orderId',
|
||||||
|
width: 150,
|
||||||
|
ellipsis: true,
|
||||||
|
tooltip: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '创建时间',
|
||||||
|
dataIndex: 'createdAt',
|
||||||
|
slotName: 'createdAt',
|
||||||
|
width: 180
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
// 更新Token描述数据
|
||||||
|
const updateDescriptionData = () => {
|
||||||
|
console.log('updateDescriptionData called, tokenData:', props.tokenData);
|
||||||
|
|
||||||
|
if (!props.tokenData) {
|
||||||
|
state.descriptionData = [];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = props.tokenData;
|
||||||
|
|
||||||
|
state.descriptionData = [
|
||||||
|
{
|
||||||
|
label: '账户ID',
|
||||||
|
value: data.id,
|
||||||
|
field: 'id'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '账户名称',
|
||||||
|
value: data.name,
|
||||||
|
field: 'name'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '登录Token',
|
||||||
|
value: data.loginToken,
|
||||||
|
field: 'loginToken'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '绑定手机号',
|
||||||
|
value: data.phone,
|
||||||
|
field: 'phone'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '账户状态',
|
||||||
|
value: data.status,
|
||||||
|
field: 'status'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '绑定数量',
|
||||||
|
value: data.bindCount || 0,
|
||||||
|
field: 'bindCount'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '累计绑定金额',
|
||||||
|
value: formatCurrency(data.totalBindAmount),
|
||||||
|
field: 'totalBindAmount'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '总充值金额',
|
||||||
|
value: formatCurrency(data.totalRechargeAmount),
|
||||||
|
field: 'totalRechargeAmount'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '充值金额限制',
|
||||||
|
value: formatCurrency(data.rechargeLimitAmount),
|
||||||
|
field: 'rechargeLimitAmount'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '充值次数限制',
|
||||||
|
value: data.rechargeLimitCount || 0,
|
||||||
|
field: 'rechargeLimitCount'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '最后绑定时间',
|
||||||
|
value: formatDateTime(data.lastBindAt),
|
||||||
|
field: 'lastBindAt'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '最后使用时间',
|
||||||
|
value: formatDateTime(data.lastUsedAt),
|
||||||
|
field: 'lastUsedAt'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '备注信息',
|
||||||
|
value: data.remark,
|
||||||
|
field: 'remark'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '创建时间',
|
||||||
|
value: formatDateTime(data.createdAt),
|
||||||
|
field: 'createdAt'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '更新时间',
|
||||||
|
value: formatDateTime(data.updatedAt),
|
||||||
|
field: 'updatedAt'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
// 获取绑卡记录数据
|
||||||
|
const fetchBindingData = async (params: any = { current: 1, pageSize: 20 }) => {
|
||||||
|
if (!props.tokenData) return;
|
||||||
|
|
||||||
|
setBindingLoading(true);
|
||||||
|
try {
|
||||||
|
const { data } = await jdV2TokenClient.apiCardBindingListByTokenGet({
|
||||||
|
current: params.current,
|
||||||
|
pageSize: params.pageSize,
|
||||||
|
tokenId: props.tokenData.id
|
||||||
|
});
|
||||||
|
|
||||||
|
bindingRenderData.value = data.list || [];
|
||||||
|
bindingPagination.current = params.current;
|
||||||
|
bindingPagination.pageSize = params.pageSize;
|
||||||
|
bindingPagination.total = data.total || 0;
|
||||||
|
} catch (err) {
|
||||||
|
console.error('获取绑卡记录失败:', err);
|
||||||
|
bindingRenderData.value = [];
|
||||||
|
bindingPagination.total = 0;
|
||||||
|
} finally {
|
||||||
|
setBindingLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onBindingPageChange = (current: number) => {
|
||||||
|
fetchBindingData({ ...bindingPagination, current });
|
||||||
|
};
|
||||||
|
|
||||||
|
const onBindingPageSizeChange = (pageSize: number) => {
|
||||||
|
fetchBindingData({ ...bindingPagination, pageSize });
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCancel = () => {
|
||||||
|
emit('update:visible', false);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 监听 tokenData 变化,自动更新描述数据
|
||||||
|
watch(
|
||||||
|
() => props.tokenData,
|
||||||
|
newTokenData => {
|
||||||
|
console.log('tokenData changed:', newTokenData);
|
||||||
|
updateDescriptionData();
|
||||||
|
console.log('descriptionData after update:', state.descriptionData);
|
||||||
|
|
||||||
|
// 切换到绑卡记录标签页时获取数据
|
||||||
|
if (newTokenData && activeTab.value === 'bindings') {
|
||||||
|
fetchBindingData();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ immediate: true }
|
||||||
|
);
|
||||||
|
|
||||||
|
// 监听标签页切换
|
||||||
|
watch(activeTab, newTab => {
|
||||||
|
if (newTab === 'bindings' && props.tokenData) {
|
||||||
|
fetchBindingData();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.drawer-tabs {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.token-details {
|
||||||
|
padding: 16px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-tag {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.token-value {
|
||||||
|
max-width: 300px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.normal-value {
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
|
||||||
|
.records-header {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
padding: 16px;
|
||||||
|
background: var(--color-fill-1);
|
||||||
|
border-radius: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.view-controls {
|
||||||
|
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(350px, 1fr));
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.binding-card {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-title {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-amount {
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-content {
|
||||||
|
margin-top: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.copy-text {
|
||||||
|
font-family: 'Courier New', monospace;
|
||||||
|
background: var(--color-fill-2);
|
||||||
|
padding: 4px 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.arco-descriptions-item-label) {
|
||||||
|
font-weight: 600;
|
||||||
|
background-color: var(--color-fill-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.arco-descriptions-item-value) {
|
||||||
|
background-color: var(--color-bg-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.arco-descriptions-item) {
|
||||||
|
padding: 12px 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.arco-drawer-body) {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.arco-tabs-content) {
|
||||||
|
padding: 0 16px 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.arco-typography) {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -104,14 +104,25 @@
|
|||||||
<!-- 操作列模板 -->
|
<!-- 操作列模板 -->
|
||||||
<template #operations="{ record }">
|
<template #operations="{ record }">
|
||||||
<a-space size="small">
|
<a-space size="small">
|
||||||
<a-tooltip content="查看绑卡记录">
|
<a-tooltip content="编辑Token">
|
||||||
<a-button
|
<a-button
|
||||||
size="small"
|
size="small"
|
||||||
status="normal"
|
type="secondary"
|
||||||
@click="showBindingRecords(record)"
|
@click="showEditModal(record)"
|
||||||
>
|
>
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<icon-list />
|
<icon-edit />
|
||||||
|
</template>
|
||||||
|
</a-button>
|
||||||
|
</a-tooltip>
|
||||||
|
<a-tooltip content="查看详情和绑卡记录">
|
||||||
|
<a-button
|
||||||
|
size="small"
|
||||||
|
type="secondary"
|
||||||
|
@click="showTokenDrawer(record)"
|
||||||
|
>
|
||||||
|
<template #icon>
|
||||||
|
<icon-eye />
|
||||||
</template>
|
</template>
|
||||||
</a-button>
|
</a-button>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
@@ -138,11 +149,17 @@
|
|||||||
@success="handleModalSuccess"
|
@success="handleModalSuccess"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<!-- 绑卡记录弹窗 -->
|
<!-- 编辑Token弹窗 -->
|
||||||
<binding-records-modal
|
<add-edit-modal
|
||||||
v-model:visible="state.bindingRecordsVisible"
|
v-model:visible="state.editModalVisible"
|
||||||
:token-id="state.currentTokenId"
|
:edit-data="state.currentEditData"
|
||||||
:token-name="state.currentTokenName"
|
@success="handleEditModalSuccess"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- Token详情侧滑框 -->
|
||||||
|
<token-drawer
|
||||||
|
v-model:visible="state.tokenDrawerVisible"
|
||||||
|
:token-data="state.currentTokenData"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -153,7 +170,7 @@ import { Pagination } from '@/types/global';
|
|||||||
import { onMounted, reactive, ref } from 'vue';
|
import { onMounted, reactive, ref } from 'vue';
|
||||||
import { Notification, TableColumnData } from '@arco-design/web-vue';
|
import { Notification, TableColumnData } from '@arco-design/web-vue';
|
||||||
import AddEditModal from './components/add-edit-modal.vue';
|
import AddEditModal from './components/add-edit-modal.vue';
|
||||||
import BindingRecordsModal from './components/binding-records-modal.vue';
|
import TokenDrawer from './components/token-drawer.vue';
|
||||||
import {
|
import {
|
||||||
ApiTokenListGetPageSizeEnum,
|
ApiTokenListGetPageSizeEnum,
|
||||||
KamiApiCamelOilV1TokenInfo
|
KamiApiCamelOilV1TokenInfo
|
||||||
@@ -180,19 +197,14 @@ const columns: TableColumnData[] = [
|
|||||||
{
|
{
|
||||||
title: '账户名称',
|
title: '账户名称',
|
||||||
dataIndex: 'name',
|
dataIndex: 'name',
|
||||||
width: 150
|
width: 180,
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '登录Token',
|
|
||||||
dataIndex: 'loginToken',
|
|
||||||
width: 300,
|
|
||||||
ellipsis: true,
|
ellipsis: true,
|
||||||
tooltip: true
|
tooltip: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '绑定手机号',
|
title: '绑定手机号',
|
||||||
dataIndex: 'phone',
|
dataIndex: 'phone',
|
||||||
width: 130,
|
width: 140,
|
||||||
ellipsis: true,
|
ellipsis: true,
|
||||||
tooltip: true
|
tooltip: true
|
||||||
},
|
},
|
||||||
@@ -201,88 +213,37 @@ const columns: TableColumnData[] = [
|
|||||||
dataIndex: 'bindCount',
|
dataIndex: 'bindCount',
|
||||||
width: 100
|
width: 100
|
||||||
},
|
},
|
||||||
{
|
|
||||||
title: '累计绑定金额',
|
|
||||||
dataIndex: 'totalBindAmount',
|
|
||||||
width: 120,
|
|
||||||
render: ({ record }) => {
|
|
||||||
if (!record.totalBindAmount) return '-';
|
|
||||||
return `¥${Number(record.totalBindAmount).toFixed(2)}`;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
title: '总充值金额',
|
title: '总充值金额',
|
||||||
dataIndex: 'totalRechargeAmount',
|
dataIndex: 'totalRechargeAmount',
|
||||||
width: 120,
|
width: 130,
|
||||||
render: ({ record }) => {
|
render: ({ record }) => {
|
||||||
if (!record.totalRechargeAmount) return '-';
|
if (!record.totalRechargeAmount) return '-';
|
||||||
return `¥${Number(record.totalRechargeAmount).toFixed(2)}`;
|
return `¥${Number(record.totalRechargeAmount).toFixed(2)}`;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '充值限制',
|
title: '状态',
|
||||||
dataIndex: 'rechargeLimitAmount',
|
dataIndex: 'status',
|
||||||
width: 110,
|
slotName: 'status',
|
||||||
render: ({ record }) => {
|
width: 110
|
||||||
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: '最后使用时间',
|
title: '最后使用时间',
|
||||||
dataIndex: 'lastUsedAt',
|
dataIndex: 'lastUsedAt',
|
||||||
width: 180,
|
width: 170,
|
||||||
ellipsis: true,
|
ellipsis: true,
|
||||||
tooltip: true,
|
tooltip: true,
|
||||||
render: ({ record }) => {
|
render: ({ record }) => {
|
||||||
return formatDateTime(record.lastUsedAt);
|
return formatDateTime(record.lastUsedAt);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
|
||||||
title: '状态',
|
|
||||||
dataIndex: 'status',
|
|
||||||
slotName: 'status',
|
|
||||||
width: 100
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '备注',
|
|
||||||
dataIndex: 'remark',
|
|
||||||
width: 150,
|
|
||||||
ellipsis: true,
|
|
||||||
tooltip: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '创建时间',
|
|
||||||
dataIndex: 'createdAt',
|
|
||||||
width: 180,
|
|
||||||
render: ({ record }) => {
|
|
||||||
return formatDateTime(record.createdAt);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
title: '操作',
|
title: '操作',
|
||||||
dataIndex: 'operations',
|
dataIndex: 'operations',
|
||||||
slotName: 'operations',
|
slotName: 'operations',
|
||||||
fixed: 'right',
|
fixed: 'right',
|
||||||
width: 150
|
width: 250
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -299,9 +260,10 @@ const formModel = ref(generateFormModel());
|
|||||||
|
|
||||||
const state = reactive({
|
const state = reactive({
|
||||||
addEditModalVisible: false,
|
addEditModalVisible: false,
|
||||||
bindingRecordsVisible: false,
|
editModalVisible: false,
|
||||||
currentTokenId: 0,
|
tokenDrawerVisible: false,
|
||||||
currentTokenName: ''
|
currentTokenData: null as KamiApiCamelOilV1TokenInfo | null,
|
||||||
|
currentEditData: null as KamiApiCamelOilV1TokenInfo | null
|
||||||
});
|
});
|
||||||
|
|
||||||
const fetchData = async (params: any = { current: 1, pageSize: 50 }) => {
|
const fetchData = async (params: any = { current: 1, pageSize: 50 }) => {
|
||||||
@@ -353,10 +315,24 @@ const showAddModal = () => {
|
|||||||
state.addEditModalVisible = true;
|
state.addEditModalVisible = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
const showBindingRecords = (record: KamiApiCamelOilV1TokenInfo) => {
|
const showEditModal = (record: KamiApiCamelOilV1TokenInfo) => {
|
||||||
state.currentTokenId = record.id;
|
console.log('showEditModal called with record:', record);
|
||||||
state.currentTokenName = record.name;
|
state.currentEditData = record;
|
||||||
state.bindingRecordsVisible = true;
|
state.editModalVisible = true;
|
||||||
|
console.log(
|
||||||
|
'editModalVisible set to true, currentEditData:',
|
||||||
|
state.currentEditData
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const showTokenDrawer = (record: KamiApiCamelOilV1TokenInfo) => {
|
||||||
|
console.log('showTokenDrawer called with record:', record);
|
||||||
|
state.currentTokenData = record;
|
||||||
|
state.tokenDrawerVisible = true;
|
||||||
|
console.log(
|
||||||
|
'tokenDrawerVisible set to true, currentTokenData:',
|
||||||
|
state.currentTokenData
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const deleteToken = async (tokenId: number) => {
|
const deleteToken = async (tokenId: number) => {
|
||||||
@@ -386,12 +362,26 @@ const handleModalSuccess = () => {
|
|||||||
fetchData({ ...pagination });
|
fetchData({ ...pagination });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleEditModalSuccess = () => {
|
||||||
|
fetchData({ ...pagination });
|
||||||
|
};
|
||||||
|
|
||||||
const statusMapper = (status: number): { text: string; color: string } => {
|
const statusMapper = (status: number): { text: string; color: string } => {
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case 0:
|
case 0:
|
||||||
return { text: '禁用', color: 'red' };
|
return { text: '待验证码', color: 'orange' };
|
||||||
case 1:
|
case 1:
|
||||||
return { text: '启用', color: 'green' };
|
return { text: '已登录', color: 'green' };
|
||||||
|
case 2:
|
||||||
|
return { text: '已禁用', color: 'red' };
|
||||||
|
case 3:
|
||||||
|
return { text: '已过期', color: 'gray' };
|
||||||
|
case 4:
|
||||||
|
return { text: '登录失败', color: 'red' };
|
||||||
|
case 5:
|
||||||
|
return { text: '验证码已发送', color: 'blue' };
|
||||||
|
case 6:
|
||||||
|
return { text: '验证码验证失败', color: 'red' };
|
||||||
default:
|
default:
|
||||||
return { text: '未知', color: 'gray' };
|
return { text: '未知', color: 'gray' };
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user