feat(account): 新增删除所有过期无效账号功能

- 在 JDV2AccountApi 中新增 apiJdV2AccountDeleteExpiredDelete 方法支持删除过期账号
- 更新接口文档,添加删除过期账号 API 说明及示例
- 新增 KamiApiCamelOilV1DeleteExpiredAccountsRes 模型表示删除结果
- 在 camel-oil 业务路由中调整 Token 管理相关配置
- 账户列表页面增加“删除过期Token”按钮及对应逻辑实现
- 新增删除操作的确认弹窗及成功/失败通知提示
- 修改 Token 表单校验,优化充值金额和次数的输入检查
- 重构 Token 新增编辑模态框,加入加载中状态管理
- 删除账户绑卡记录弹窗组件,实现抽屉式 Token 详情及绑卡记录展示
This commit is contained in:
danial
2025-12-09 20:16:23 +08:00
parent 86a19135a8
commit 44b61956bd
15 changed files with 1028 additions and 754 deletions

View File

@@ -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'

View File

@@ -42,6 +42,7 @@ docs/KamiApiCamelOilV1CheckAccountReq.md
docs/KamiApiCamelOilV1CheckAccountRes.md
docs/KamiApiCamelOilV1CreateTokenReq.md
docs/KamiApiCamelOilV1CreateTokenRes.md
docs/KamiApiCamelOilV1DeleteExpiredAccountsRes.md
docs/KamiApiCamelOilV1DeleteTokenReq.md
docs/KamiApiCamelOilV1DeleteTokenRes.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-create-token-req.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-res.ts
models/kami-api-camel-oil-v1-denomination-setting.ts

View File

@@ -47,6 +47,8 @@ import type { KamiApiCamelOilV1CheckAccountReq } from '../models';
// @ts-ignore
import type { KamiApiCamelOilV1CheckAccountRes } from '../models';
// @ts-ignore
import type { KamiApiCamelOilV1DeleteExpiredAccountsRes } from '../models';
// @ts-ignore
import type { KamiApiCamelOilV1ListAccountRes } from '../models';
/**
* JDV2AccountApi - axios parameter creator
@@ -103,6 +105,45 @@ export const JDV2AccountApiAxiosParamCreator = function (
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 账号历史记录
@@ -324,6 +365,37 @@ export const JDV2AccountApiFp = function (configuration?: Configuration) {
configuration
)(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 账号历史记录
@@ -472,6 +544,19 @@ export const JDV2AccountApiFactory = function (
)
.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 账号历史记录
@@ -547,6 +632,16 @@ export interface JDV2AccountApiInterface {
options?: RawAxiosRequestConfig
): AxiosPromise<KamiApiCamelOilV1CheckAccountRes>;
/**
*
* @summary 删除所有过期无效账号
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
apiJdV2AccountDeleteExpiredDelete(
options?: RawAxiosRequestConfig
): AxiosPromise<KamiApiCamelOilV1DeleteExpiredAccountsRes>;
/**
*
* @summary 账号历史记录
@@ -669,6 +764,18 @@ export class JDV2AccountApi extends BaseAPI implements JDV2AccountApiInterface {
.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 账号历史记录

View File

@@ -2,12 +2,13 @@
All URIs are relative to _http://localhost_
| Method | HTTP request | Description |
| --------------------------------------------------------------- | ------------------------------------- | ------------ |
| [**apiJdV2AccountCheckPost**](#apijdv2accountcheckpost) | **POST** /api/jd-v2/account/check | 棂测账号状态 |
| [**apiJdV2AccountHistoryGet**](#apijdv2accounthistoryget) | **GET** /api/jd-v2/account/history | 账号历史记录 |
| [**apiJdV2AccountListGet**](#apijdv2accountlistget) | **GET** /api/jd-v2/account/list | 账号列表 |
| [**apiJdV2AccountStatisticsGet**](#apijdv2accountstatisticsget) | **GET** /api/jd-v2/account/statistics | 账号统计信息 |
| Method | HTTP request | Description |
| --------------------------------------------------------------------------- | -------------------------------------------- | -------------------- |
| [**apiJdV2AccountCheckPost**](#apijdv2accountcheckpost) | **POST** /api/jd-v2/account/check | 棂测账号状态 |
| [**apiJdV2AccountDeleteExpiredDelete**](#apijdv2accountdeleteexpireddelete) | **DELETE** /api/jd-v2/account/delete-expired | 删除所有过期无效账号 |
| [**apiJdV2AccountHistoryGet**](#apijdv2accounthistoryget) | **GET** /api/jd-v2/account/history | 账号历史记录 |
| [**apiJdV2AccountListGet**](#apijdv2accountlistget) | **GET** /api/jd-v2/account/list | 账号列表 |
| [**apiJdV2AccountStatisticsGet**](#apijdv2accountstatisticsget) | **GET** /api/jd-v2/account/statistics | 账号统计信息 |
# **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)
# **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**
> KamiApiCamelOilV1AccountHistoryRes apiJdV2AccountHistoryGet()

View File

@@ -5,7 +5,7 @@
| Name | Type | Description | Notes |
| ----------------------- | ---------- | ------------ | --------------------------------- |
| **name** | **string** | Token名称 | [default to undefined] |
| **phone** | **string** | 绑定的手机号 | [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] |

View File

@@ -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)

View File

@@ -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-create-token-req';
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-res';
export * from './kami-api-camel-oil-v1-denomination-setting';

View File

@@ -20,7 +20,7 @@ export interface KamiApiCamelOilV1CreateTokenReq {
/**
* 绑定的手机号
*/
phone: string;
phone?: string;
/**
* 备注
*/

View File

@@ -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;
}

View File

@@ -12,19 +12,18 @@ const CamelOilMgt: AppRouteRecordRaw = {
meta: {
locale: '骆驼加油',
requiresAuth: true,
icon: 'icon-car',
icon: 'icon-safe',
order: 5
},
children: [
{
path: 'token',
name: 'camelOilToken',
name: 'camelOilTokenUser',
component: () => import('@/views/camel-oil-info/token/index.vue'),
meta: {
locale: 'Token管理',
requiresAuth: true,
roles: ['*'],
activeMenu: '/camel-oil/token'
roles: ['*']
}
}
]

View File

@@ -61,6 +61,17 @@
<!-- 操作按钮区域 -->
<a-row style="margin-bottom: 16px">
<a-space>
<a-button
type="primary"
status="danger"
:loading="deleteLoading"
@click="deleteExpiredTokens"
>
<template #icon>
<icon-delete />
</template>
删除过期Token
</a-button>
<a-button @click="download">
<template #icon>
<icon-download />
@@ -131,7 +142,7 @@
<script lang="ts" setup>
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 { Pagination } from '@/types/global';
import { jdV2AccountClient } from '@/api/index.ts';
@@ -152,6 +163,8 @@ const basePagination: Pagination = {
// 状态管理
const pagination = reactive({ ...basePagination });
const { loading, setLoading } = useLoading(true);
const { loading: deleteLoading, setLoading: setDeleteLoading } =
useLoading(false);
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);
}
}
});
};
/**
* 导出功能
*/

View File

@@ -3,6 +3,7 @@
v-model:visible="modalVisible"
:title="isEditMode ? '编辑Token' : '新增Token'"
width="600px"
:ok-loading="loading"
@cancel="handleCancel"
@before-ok="handleBeforeOk"
>
@@ -66,6 +67,7 @@ import { ref, reactive, computed, watch } from 'vue';
import { Notification, FormInstance } from '@arco-design/web-vue';
import { KamiApiCamelOilV1TokenInfo } from '@/api/generated/index.ts';
import { jdV2TokenClient } from '@/api/index.ts';
import useLoading from '@/hooks/loading';
interface Props {
visible: boolean;
@@ -90,6 +92,8 @@ const isEditMode = computed(() => !!props.editData);
const formRef = ref<FormInstance>();
const { loading, setLoading } = useLoading(false);
const generateFormModel = () => {
return {
tokenId: 0,
@@ -125,11 +129,37 @@ const rules = computed(() => ({
remark: [{ max: 500, message: '备注长度不能超过500个字符' }],
rechargeLimitAmount: [
{ 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: [
{ 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);
};
const handleBeforeOk = async () => {
try {
const valid = await formRef.value?.validate();
if (!valid) {
return false;
const handleBeforeOk = (done: (closed: boolean) => void) => {
formRef.value?.validate().then(async (res: any) => {
if (res) {
console.log('Validation failed:', res);
return done(false);
}
if (isEditMode.value) {
// 编辑账户
await jdV2TokenClient.apiTokenUpdatePost({
kamiApiCamelOilV1UpdateTokenReq: {
tokenId: formModel.tokenId,
name: formModel.name,
remark: formModel.remark,
rechargeLimitAmount: formModel.rechargeLimitAmount,
rechargeLimitCount: formModel.rechargeLimitCount
}
});
try {
setLoading(true);
console.log('Validation passed, making API call...');
Notification.success({
content: '编辑账户成功',
closable: true
});
} else {
// 新增账户
await jdV2TokenClient.apiTokenCreatePost({
kamiApiCamelOilV1CreateTokenReq: {
name: formModel.name,
phone: formModel.phone,
remark: formModel.remark,
rechargeLimitAmount: formModel.rechargeLimitAmount,
rechargeLimitCount: formModel.rechargeLimitCount
}
});
Notification.success({
content: '新增账户成功',
if (isEditMode.value) {
// 编辑账户
console.log('Editing token...');
await jdV2TokenClient.apiTokenUpdatePost({
kamiApiCamelOilV1UpdateTokenReq: {
tokenId: formModel.tokenId,
name: formModel.name,
remark: formModel.remark,
rechargeLimitAmount: formModel.rechargeLimitAmount,
rechargeLimitCount: formModel.rechargeLimitCount
}
});
Notification.success({
content: '编辑账户成功',
closable: true
});
} 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
});
// 操作失败,阻止模态框关闭
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>

View File

@@ -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>

View 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>

View File

@@ -104,14 +104,25 @@
<!-- 操作列模板 -->
<template #operations="{ record }">
<a-space size="small">
<a-tooltip content="查看绑卡记录">
<a-tooltip content="编辑Token">
<a-button
size="small"
status="normal"
@click="showBindingRecords(record)"
type="secondary"
@click="showEditModal(record)"
>
<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>
</a-button>
</a-tooltip>
@@ -138,11 +149,17 @@
@success="handleModalSuccess"
/>
<!-- 绑卡记录弹窗 -->
<binding-records-modal
v-model:visible="state.bindingRecordsVisible"
:token-id="state.currentTokenId"
:token-name="state.currentTokenName"
<!-- 编辑Token弹窗 -->
<add-edit-modal
v-model:visible="state.editModalVisible"
:edit-data="state.currentEditData"
@success="handleEditModalSuccess"
/>
<!-- Token详情侧滑框 -->
<token-drawer
v-model:visible="state.tokenDrawerVisible"
:token-data="state.currentTokenData"
/>
</div>
</template>
@@ -153,7 +170,7 @@ import { Pagination } from '@/types/global';
import { onMounted, reactive, ref } from 'vue';
import { Notification, TableColumnData } from '@arco-design/web-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 {
ApiTokenListGetPageSizeEnum,
KamiApiCamelOilV1TokenInfo
@@ -180,19 +197,14 @@ const columns: TableColumnData[] = [
{
title: '账户名称',
dataIndex: 'name',
width: 150
},
{
title: '登录Token',
dataIndex: 'loginToken',
width: 300,
width: 180,
ellipsis: true,
tooltip: true
},
{
title: '绑定手机号',
dataIndex: 'phone',
width: 130,
width: 140,
ellipsis: true,
tooltip: true
},
@@ -201,88 +213,37 @@ 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,
width: 130,
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: 'status',
slotName: 'status',
width: 110
},
{
title: '最后使用时间',
dataIndex: 'lastUsedAt',
width: 180,
width: 170,
ellipsis: true,
tooltip: true,
render: ({ record }) => {
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: '操作',
dataIndex: 'operations',
slotName: 'operations',
fixed: 'right',
width: 150
width: 250
}
];
@@ -299,9 +260,10 @@ const formModel = ref(generateFormModel());
const state = reactive({
addEditModalVisible: false,
bindingRecordsVisible: false,
currentTokenId: 0,
currentTokenName: ''
editModalVisible: false,
tokenDrawerVisible: false,
currentTokenData: null as KamiApiCamelOilV1TokenInfo | null,
currentEditData: null as KamiApiCamelOilV1TokenInfo | null
});
const fetchData = async (params: any = { current: 1, pageSize: 50 }) => {
@@ -353,10 +315,24 @@ const showAddModal = () => {
state.addEditModalVisible = true;
};
const showBindingRecords = (record: KamiApiCamelOilV1TokenInfo) => {
state.currentTokenId = record.id;
state.currentTokenName = record.name;
state.bindingRecordsVisible = true;
const showEditModal = (record: KamiApiCamelOilV1TokenInfo) => {
console.log('showEditModal called with record:', record);
state.currentEditData = record;
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) => {
@@ -386,12 +362,26 @@ const handleModalSuccess = () => {
fetchData({ ...pagination });
};
const handleEditModalSuccess = () => {
fetchData({ ...pagination });
};
const statusMapper = (status: number): { text: string; color: string } => {
switch (status) {
case 0:
return { text: '禁用', color: 'red' };
return { text: '待验证码', color: 'orange' };
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:
return { text: '未知', color: 'gray' };
}