feat(camel-oil): 更新账号相关API模型和界面组件

- 从账号历史记录模型中移除historyUuid字段
- 在创建令牌请求模型中新增phone字段用于绑定手机号
- 在令牌信息模型中新增phone字段
- 移除账号详情页面中的账号ID显示和统计信息卡片
- 更新账号历史记录组件的筛选条件和展示字段
- 修改账号列表中的状态选项和显示逻辑
- 移除账号关联订单统计弹窗及相关功能
- 更新订单历史记录模型枚举值,新增FillCard类型
- 调整.clau

```
This commit is contained in:
danial
2025-11-23 01:34:04 +08:00
parent 5c1505449b
commit 6dc807dfc3
19 changed files with 421 additions and 803 deletions

View File

@@ -9,7 +9,9 @@
"Bash(pnpm lint:*)",
"Bash(pnpm prettier:fix)",
"Bash(pnpm eslint:*)",
"Bash(mkdir:*)"
"Bash(mkdir:*)",
"Bash(pnpm build)",
"Bash(pnpm eslint:fix)"
],
"deny": [],
"ask": []

View File

@@ -4,7 +4,6 @@
| Name | Type | Description | Notes |
| ---------------- | ---------- | ------------ | --------------------------------- |
| **historyUuid** | **string** | 历史记录UUID | [optional] [default to undefined] |
| **accountId** | **number** | 账号ID | [optional] [default to undefined] |
| **changeType** | **string** | 变更类型 | [optional] [default to undefined] |
| **changeText** | **string** | 变更类型文本 | [optional] [default to undefined] |
@@ -20,7 +19,6 @@
import { KamiApiCamelOilV1AccountHistoryItem } from './api';
const instance: KamiApiCamelOilV1AccountHistoryItem = {
historyUuid,
accountId,
changeType,
changeText,

View File

@@ -3,9 +3,10 @@
## Properties
| Name | Type | Description | Notes |
| -------------- | ---------- | ----------- | --------------------------------- |
| -------------- | ---------- | ------------ | --------------------------------- |
| **tokenName** | **string** | Token名称 | [default to undefined] |
| **tokenValue** | **string** | Token值 | [default to undefined] |
| **phone** | **string** | 绑定的手机号 | [optional] [default to undefined] |
| **remark** | **string** | 备注 | [optional] [default to undefined] |
## Example
@@ -16,6 +17,7 @@ import { KamiApiCamelOilV1CreateTokenReq } from './api';
const instance: KamiApiCamelOilV1CreateTokenReq = {
tokenName,
tokenValue,
phone,
remark
};
```

View File

@@ -4,7 +4,6 @@
| Name | Type | Description | Notes |
| --------------- | ---------- | ------------ | --------------------------------- |
| **historyUuid** | **string** | 历史记录UUID | [optional] [default to undefined] |
| **orderNo** | **string** | 订单号 | [optional] [default to undefined] |
| **changeType** | **string** | 变更类型 | [optional] [default to undefined] |
| **changeText** | **string** | 变更类型文本 | [optional] [default to undefined] |
@@ -20,7 +19,6 @@
import { KamiApiCamelOilV1OrderHistoryItem } from './api';
const instance: KamiApiCamelOilV1OrderHistoryItem = {
historyUuid,
orderNo,
changeType,
changeText,

View File

@@ -7,6 +7,7 @@
| **id** | **number** | Token ID | [optional] [default to undefined] |
| **tokenName** | **string** | Token名称 | [optional] [default to undefined] |
| **tokenValue** | **string** | Token值 | [optional] [default to undefined] |
| **phone** | **string** | 绑定的手机号 | [optional] [default to undefined] |
| **status** | **number** | 状态 | [optional] [default to undefined] |
| **bindCount** | **number** | 已绑定卡密数量 | [optional] [default to undefined] |
| **totalBindAmount** | **object** | | [optional] [default to undefined] |
@@ -25,6 +26,7 @@ const instance: KamiApiCamelOilV1TokenInfo = {
id,
tokenName,
tokenValue,
phone,
status,
bindCount,
totalBindAmount,

View File

@@ -13,10 +13,6 @@
*/
export interface KamiApiCamelOilV1AccountHistoryItem {
/**
* 历史记录UUID
*/
historyUuid?: string;
/**
* 账号ID
*/

View File

@@ -21,6 +21,10 @@ export interface KamiApiCamelOilV1CreateTokenReq {
* Token值
*/
tokenValue: string;
/**
* 绑定的手机号
*/
phone?: string;
/**
* 备注
*/

View File

@@ -13,10 +13,6 @@
*/
export interface KamiApiCamelOilV1OrderHistoryItem {
/**
* 历史记录UUID
*/
historyUuid?: string;
/**
* 订单号
*/
@@ -57,6 +53,7 @@ export enum KamiApiCamelOilV1OrderHistoryItemChangeTypeEnum {
CheckPay = 'check_pay',
Create = 'create',
Fail = 'fail',
FillCard = 'fill_card',
GetPayUrl = 'get_pay_url',
Paid = 'paid',
Submit = 'submit',

View File

@@ -25,6 +25,10 @@ export interface KamiApiCamelOilV1TokenInfo {
* Token值
*/
tokenValue?: string;
/**
* 绑定的手机号
*/
phone?: string;
/**
* 状态
*/

View File

@@ -1,257 +0,0 @@
<template>
<a-modal
v-model:visible="modalVisible"
title="账号关联订单统计"
width="600px"
:footer="false"
>
<a-spin :loading="loading" style="width: 100%">
<div v-if="accountOrderStats" class="orders-stats">
<!-- 订单统计卡片 -->
<a-row :gutter="16" style="margin-bottom: 24px">
<a-col :span="6">
<a-statistic
title="总订单数"
:value="statistics.totalOrders"
suffix="单"
:value-style="{ color: '#165dff', fontSize: '24px' }"
/>
</a-col>
<a-col :span="6">
<a-statistic
title="已支付订单"
:value="statistics.paidOrders"
suffix="单"
:value-style="{ color: '#00b42a', fontSize: '24px' }"
/>
</a-col>
<a-col :span="6">
<a-statistic
title="待支付订单"
:value="statistics.pendingOrders"
suffix="单"
:value-style="{ color: '#ff7d00', fontSize: '24px' }"
/>
</a-col>
<a-col :span="6">
<a-statistic
title="超时订单"
:value="statistics.timeoutOrders"
suffix="单"
:value-style="{ color: '#f53f3f', fontSize: '24px' }"
/>
</a-col>
</a-row>
<!-- 详细统计信息 -->
<a-card title="详细统计" class="detail-stats-card">
<a-descriptions :column="2" bordered>
<a-descriptions-item label="总订单数">
<a-tag color="blue">{{ statistics.totalOrders }} </a-tag>
</a-descriptions-item>
<a-descriptions-item label="已支付订单">
<a-tag color="green">{{ statistics.paidOrders }} </a-tag>
</a-descriptions-item>
<a-descriptions-item label="待支付订单">
<a-tag color="orange">{{ statistics.pendingOrders }} </a-tag>
</a-descriptions-item>
<a-descriptions-item label="超时订单">
<a-tag color="red">{{ statistics.timeoutOrders }} </a-tag>
</a-descriptions-item>
<a-descriptions-item label="支付成功率">
<span :class="paymentRateClass">{{ paymentRate }}%</span>
</a-descriptions-item>
<a-descriptions-item label="待支付率">
<span :class="pendingRateClass">{{ pendingRate }}%</span>
</a-descriptions-item>
</a-descriptions>
</a-card>
</div>
<!-- 空状态 -->
<a-empty v-else description="暂无订单统计数据" />
</a-spin>
</a-modal>
</template>
<script lang="ts" setup>
import { computed, onMounted, reactive, ref, watch } from 'vue';
import { jdV2OrderClient } from '@/api/index.ts';
import type { KamiApiCamelOilV1AccountOrderListResOrderStats } from '@/api/generated/models/index.ts';
// Props 定义
interface Props {
visible: boolean;
accountId: string;
}
const props = withDefaults(defineProps<Props>(), {
visible: false,
accountId: ''
});
// Emits 定义
interface Emits {
(e: 'update:visible', value: boolean): void;
}
const emit = defineEmits<Emits>();
// 响应式数据
const loading = ref(false);
const accountOrderStats =
ref<KamiApiCamelOilV1AccountOrderListResOrderStats | null>(null);
// 统计数据
const statistics = reactive({
totalOrders: 0,
paidOrders: 0,
pendingOrders: 0,
timeoutOrders: 0
});
// 计算属性
const modalVisible = computed({
get: () => props.visible,
set: value => emit('update:visible', value)
});
const paymentRate = computed(() => {
if (statistics.totalOrders === 0) return 0;
return ((statistics.paidOrders / statistics.totalOrders) * 100).toFixed(2);
});
const pendingRate = computed(() => {
if (statistics.totalOrders === 0) return 0;
return ((statistics.pendingOrders / statistics.totalOrders) * 100).toFixed(2);
});
const paymentRateClass = computed(() => {
const rate = parseFloat(paymentRate.value.toString());
if (rate >= 90) return 'rate-excellent';
if (rate >= 70) return 'rate-good';
return 'rate-poor';
});
const pendingRateClass = computed(() => {
const rate = parseFloat(pendingRate.value.toString());
if (rate <= 10) return 'rate-excellent';
if (rate <= 30) return 'rate-good';
return 'rate-poor';
});
/**
* 获取账号订单统计数据
*/
const fetchAccountOrderStats = async () => {
if (!props.accountId) return;
loading.value = true;
try {
// 由于API结构问题这里暂时使用模拟数据
// 实际应该调用: camelOilOrderClient.apiCamelOilOrderAccountOrdersGet()
// 但是这个API返回的是单个账号的订单统计不是列表
// 模拟数据等待API完善
accountOrderStats.value = {
totalOrders: 150,
paidOrders: 120,
pendingOrders: 25,
timeoutOrders: 5
};
// 更新统计数据
if (accountOrderStats.value) {
Object.assign(statistics, {
totalOrders: accountOrderStats.value.totalOrders || 0,
paidOrders: accountOrderStats.value.paidOrders || 0,
pendingOrders: accountOrderStats.value.pendingOrders || 0,
timeoutOrders: accountOrderStats.value.timeoutOrders || 0
});
}
} catch (error) {
console.error('获取账号订单统计失败:', error);
accountOrderStats.value = null;
} finally {
loading.value = false;
}
};
// 监听弹窗显示状态
watch(
() => props.visible,
visible => {
if (visible && props.accountId) {
fetchAccountOrderStats();
}
},
{ immediate: true }
);
// 监听账号ID变化
watch(
() => props.accountId,
accountId => {
if (accountId && props.visible) {
fetchAccountOrderStats();
}
}
);
</script>
<style scoped>
.orders-stats {
padding: 16px 0;
}
.detail-stats-card {
margin-top: 16px;
}
.rate-excellent {
color: #00b42a;
font-weight: 600;
font-size: 16px;
}
.rate-good {
color: #ff7d00;
font-weight: 600;
font-size: 16px;
}
.rate-poor {
color: #f53f3f;
font-weight: 600;
font-size: 16px;
}
:deep(.arco-statistic) {
text-align: center;
padding: 16px;
border-radius: 6px;
background-color: #f7f8fa;
}
:deep(.arco-statistic-title) {
color: #86909c;
font-size: 14px;
margin-bottom: 8px;
}
:deep(.arco-statistic-value) {
color: #1d2129;
font-size: 24px;
font-weight: 600;
}
:deep(.arco-descriptions-item-label) {
font-weight: 600;
background-color: #f7f8fa;
}
:deep(.arco-modal-body) {
max-height: 70vh;
overflow-y: auto;
}
</style>

View File

@@ -10,9 +10,6 @@
<!-- 基本信息 -->
<a-card title="基本信息" class="detail-card">
<a-descriptions :column="2" bordered>
<a-descriptions-item label="账号ID">
{{ accountDetail.accountId }}
</a-descriptions-item>
<a-descriptions-item label="账号名称">
{{ accountDetail.accountName }}
</a-descriptions-item>
@@ -35,33 +32,6 @@
</a-descriptions-item>
</a-descriptions>
</a-card>
<!-- 统计信息 -->
<a-card title="统计信息" class="detail-card">
<a-row :gutter="16">
<a-col :span="8">
<a-statistic
title="今日充值金额"
:value="parseFloat(statistics.todayAmount)"
prefix="¥"
/>
</a-col>
<a-col :span="8">
<a-statistic
title="今日充值次数"
:value="parseFloat(statistics.todayCount)"
suffix="次"
/>
</a-col>
<a-col :span="8">
<a-statistic
title="本月充值金额"
:value="parseFloat(statistics.monthAmount)"
prefix="¥"
/>
</a-col>
</a-row>
</a-card>
</div>
<!-- 空状态 -->
@@ -98,13 +68,6 @@ const loading = ref(false);
const accountDetail =
ref<KamiApiCamelOilV1AccountStatisticsResAccountInfo | null>(null);
// 统计数据
const statistics = ref({
todayAmount: '0',
todayCount: '0',
monthAmount: '0'
});
// 计算属性
const modalVisible = computed({
get: () => props.visible,
@@ -112,26 +75,19 @@ const modalVisible = computed({
});
/**
* 获取账号详情和统计信息
* 获取账号详情
*/
const fetchAccountDetail = async () => {
if (!props.accountId) return;
loading.value = true;
try {
// 获取账号统计信息
// 获取账号详情信息
const { data } = await jdV2AccountClient.apiJdV2AccountStatisticsGet({
accountId: parseInt(props.accountId)
});
accountDetail.value = data.accountInfo;
// 暂时使用默认统计信息等待API完善
statistics.value = {
todayAmount: '0',
todayCount: '0',
monthAmount: '0'
};
} catch (error) {
console.error('获取账号详情失败:', error);
accountDetail.value = null;
@@ -148,20 +104,20 @@ const fetchAccountDetail = async () => {
const statusMapper = (
status: number | undefined
): { text: string; color: string } => {
if (!status) return { text: '未知', color: 'gray' };
if (!status) return { text: '未知状态', color: 'gray' };
switch (status) {
case 0:
return { text: '失效', color: 'red' };
return { text: '待登录', color: 'orange' }; // 需要登录验证
case 1:
return { text: '正常', color: 'green' };
return { text: '发送验证码', color: 'blue' }; // 正在发送验证码
case 2:
return { text: '充值过快', color: 'orange' };
return { text: '在线', color: 'green' }; // 账户在线,可用
case 3:
return { text: '账号受限', color: 'red' };
return { text: '已暂停', color: 'orange' }; // 账户被暂停使用
case 4:
return { text: '异常', color: 'gray' };
return { text: '已失效', color: 'red' }; // 账户已失效
default:
return { text: '未知', color: 'gray' };
return { text: '未知状态', color: 'gray' };
}
};
@@ -200,39 +156,8 @@ watch(
margin-bottom: 0;
}
.balance-text {
color: #00b42a;
font-weight: 600;
font-size: 16px;
}
.recharge-text {
color: #165dff;
font-weight: 600;
font-size: 16px;
}
:deep(.arco-descriptions-item-label) {
font-weight: 600;
background-color: #f7f8fa;
}
:deep(.arco-statistic) {
text-align: center;
padding: 16px;
border-radius: 6px;
background-color: #f7f8fa;
}
:deep(.arco-statistic-title) {
color: #86909c;
font-size: 14px;
margin-bottom: 8px;
}
:deep(.arco-statistic-value) {
color: #1d2129;
font-size: 24px;
font-weight: 600;
background-color: var(--color-fill-2);
}
</style>

View File

@@ -27,18 +27,26 @@
</a-form-item>
</a-col>
<a-col :span="8">
<a-form-item field="action" label="操作类型">
<a-form-item field="changeType" label="变更类型">
<a-select
v-model="filterForm.action"
placeholder="请选择操作类型"
v-model="filterForm.changeType"
placeholder="请选择变更类型"
allow-clear
style="width: 100%"
>
<a-option value="create">创建</a-option>
<a-option value="update">更新</a-option>
<a-option value="delete">删除</a-option>
<a-option value="recharge">充值</a-option>
<a-option value="consume">消费</a-option>
<a-option value="create">创建账号</a-option>
<a-option value="login">登录成功</a-option>
<a-option value="offline">检测到掉线</a-option>
<a-option value="login_fail">登录失败</a-option>
<a-option value="pause">订单数达到10暂停使用</a-option>
<a-option value="resume">次日重置恢复使用</a-option>
<a-option value="invalidate">
单日下单不足10个账号失效
</a-option>
<a-option value="order_bind">绑定订单</a-option>
<a-option value="order_complete">订单完成</a-option>
<a-option value="update">更新账号信息</a-option>
<a-option value="delete">删除账号</a-option>
</a-select>
</a-form-item>
</a-col>
@@ -79,31 +87,39 @@
@page-change="onPageChange"
@page-size-change="onPageSizeChange"
>
<template #action="{ record }">
<a-tag :color="actionMapper(record.action).color">
{{ actionMapper(record.action).text }}
<template #changeType="{ record }">
<a-tag :color="changeTypeMapper(record.changeType).color">
{{ changeTypeMapper(record.changeType).text }}
</a-tag>
</template>
<template #amount="{ record }">
<span
v-if="record.amount"
:class="{
'amount-positive': record.amount > 0,
'amount-negative': record.amount < 0
}"
<template #beforeStatus="{ record }">
<a-tag
v-if="
record.statusBefore !== undefined &&
record.statusBefore !== null
"
:color="statusColorMapper(record.statusBefore)"
>
{{ record.amount > 0 ? '+' : '' }}¥{{ record.amount }}
</span>
{{ statusMapper(record.statusBefore).text }}
</a-tag>
<span v-else>-</span>
</template>
<template #balance="{ record }">
<span class="balance-text">¥{{ record.balance || 0 }}</span>
<template #afterStatus="{ record }">
<a-tag
v-if="
record.statusAfter !== undefined && record.statusAfter !== null
"
:color="statusColorMapper(record.statusAfter)"
>
{{ statusMapper(record.statusAfter).text }}
</a-tag>
<span v-else>-</span>
</template>
<template #details="{ record }">
<a-tooltip v-if="record.details" :content="record.details">
<template #remark="{ record }">
<a-tooltip v-if="record.remark" :content="record.remark">
<a-button size="small" type="text">
<template #icon>
<icon-eye />
@@ -120,10 +136,9 @@
</template>
<script lang="ts" setup>
import { computed, onMounted, reactive, ref, watch } from 'vue';
import { computed, reactive, ref, watch } from 'vue';
import { Message } from '@arco-design/web-vue';
import { TableColumnData } from '@arco-design/web-vue';
import useLoading from '@/hooks/loading';
import { Pagination } from '@/types/global';
import { jdV2AccountClient } from '@/api/index.ts';
import type { KamiApiCamelOilV1AccountHistoryItem } from '@/api/generated/models/index.ts';
@@ -148,7 +163,7 @@ interface Emits {
const emit = defineEmits<Emits>();
// 响应式数据
const { loading, setLoading } = useLoading(false);
const loading = ref(false);
const tableLoading = ref(false);
const historyData = ref<KamiApiCamelOilV1AccountHistoryItem[]>([]);
@@ -163,7 +178,7 @@ const pagination = reactive({ ...basePagination });
// 筛选表单
const filterForm = reactive({
dateRange: [] as Date[],
action: undefined as string | undefined
changeType: undefined as string | undefined
});
// 时间快捷选项
@@ -220,43 +235,37 @@ const columns: TableColumnData[] = [
}
},
{
title: '操作类型',
dataIndex: 'action',
slotName: 'action',
title: '变更类型',
dataIndex: 'changeType',
slotName: 'changeType',
width: 180
},
{
title: '变更前状态',
dataIndex: 'statusBefore',
slotName: 'beforeStatus',
width: 100
},
{
title: '操作人',
dataIndex: 'operator',
width: 120
title: '变更后状态',
dataIndex: 'statusAfter',
slotName: 'afterStatus',
width: 100
},
{
title: '金额变化',
dataIndex: 'amount',
slotName: 'amount',
width: 120
},
{
title: '操作后余额',
dataIndex: 'balance',
slotName: 'balance',
width: 120
},
{
title: '操作说明',
dataIndex: 'description',
title: '备注说明',
dataIndex: 'remark',
width: 200,
ellipsis: true,
tooltip: true
},
{
title: '详细信息',
dataIndex: 'details',
slotName: 'details',
title: '失败次数',
dataIndex: 'failureCount',
width: 100
},
{
title: '操作时间',
title: '创建时间',
dataIndex: 'createdAt',
width: 180
}
@@ -338,31 +347,90 @@ const searchHistory = () => {
*/
const resetFilter = () => {
filterForm.dateRange = [];
filterForm.action = undefined;
filterForm.changeType = undefined;
searchHistory();
};
/**
* 操作类型映射器
* @param action 操作类型
* @returns 操作类型文本和颜色
* 变更类型映射器
* 基于后端Go定义的CamelOilAccountChangeType
* @param changeType 变更类型
* @returns 变更类型文本和颜色
*/
const actionMapper = (action: string): { text: string; color: string } => {
switch (action) {
const changeTypeMapper = (
changeType: string
): { text: string; color: string } => {
switch (changeType) {
case 'create':
return { text: '创建', color: 'green' };
return { text: '创建账号', color: 'green' };
case 'login':
return { text: '登录成功', color: 'blue' };
case 'offline':
return { text: '检测到掉线', color: 'orange' };
case 'login_fail':
return { text: '登录失败', color: 'red' };
case 'pause':
return { text: '订单数达到10暂停使用', color: 'orange' };
case 'resume':
return { text: '次日重置,恢复使用', color: 'green' };
case 'invalidate':
return { text: '单日下单不足10个账号失效', color: 'red' };
case 'order_bind':
return { text: '绑定订单', color: 'blue' };
case 'order_complete':
return { text: '订单完成', color: 'green' };
case 'update':
return { text: '更新', color: 'blue' };
return { text: '更新账号信息', color: 'blue' };
case 'delete':
return { text: '删除', color: 'red' };
case 'recharge':
return { text: '充值', color: 'green' };
case 'consume':
return { text: '消费', color: 'orange' };
case 'status_change':
return { text: '状态变更', color: 'purple' };
return { text: '删除账号', color: 'red' };
default:
return { text: '未知', color: 'gray' };
return { text: '未知变更', color: 'gray' };
}
};
/**
* 状态颜色映射器
* 基于后端Go定义的CamelOilAccountStatus
* @param status 状态值
* @returns 状态颜色
*/
const statusColorMapper = (status: number): string => {
switch (status) {
case 0:
return 'orange'; // 待登录
case 1:
return 'blue'; // 发送验证码
case 2:
return 'green'; // 在线
case 3:
return 'orange'; // 已暂停
case 4:
return 'red'; // 已失效
default:
return 'gray'; // 未知状态
}
};
/**
* 状态文本映射器
* 基于后端Go定义的CamelOilAccountStatus
* @param status 状态值
* @returns 状态文本
*/
const statusMapper = (status: number): { text: string } => {
switch (status) {
case 0:
return { text: '待登录' };
case 1:
return { text: '发送验证码' };
case 2:
return { text: '在线' };
case 3:
return { text: '已暂停' };
case 4:
return { text: '已失效' };
default:
return { text: '未知状态' };
}
};
@@ -393,23 +461,25 @@ watch(
padding: 16px 0;
}
.amount-positive {
color: #00b42a;
font-weight: 600;
}
.amount-negative {
color: #f53f3f;
font-weight: 600;
}
.balance-text {
color: #165dff;
font-weight: 600;
}
:deep(.arco-modal-body) {
max-height: 70vh;
overflow-y: auto;
/* 确保滚动条样式跟随主题 */
scrollbar-width: thin;
scrollbar-color: var(--color-border-3) transparent;
}
:deep(.arco-modal-body::-webkit-scrollbar) {
width: 6px;
}
:deep(.arco-modal-body::-webkit-scrollbar-thumb) {
background-color: var(--color-border-3);
border-radius: 3px;
}
:deep(.arco-modal-body::-webkit-scrollbar-track) {
background-color: transparent;
}
</style>

View File

@@ -1,135 +0,0 @@
<template>
<a-modal
v-model:visible="modalVisible"
title="订单详情"
width="700px"
:footer="false"
>
<div v-if="order" class="order-detail">
<a-descriptions :column="2" bordered>
<a-descriptions-item label="订单号">
{{ order.orderNo }}
</a-descriptions-item>
<a-descriptions-item label="商户订单号">
{{ order.merchantOrderNo || '-' }}
</a-descriptions-item>
<a-descriptions-item label="订单金额">
<span class="amount-text">¥{{ order.amount }}</span>
</a-descriptions-item>
<a-descriptions-item label="订单状态">
<a-tag :color="statusMapper(order.status).color">
{{ statusMapper(order.status).text }}
</a-tag>
</a-descriptions-item>
<a-descriptions-item label="支付状态">
<a-tag :color="payStatusMapper(order.payStatus).color">
{{ payStatusMapper(order.payStatus).text }}
</a-tag>
</a-descriptions-item>
<a-descriptions-item label="充值账号">
{{ order.accountName }}
</a-descriptions-item>
<a-descriptions-item label="充值手机">
{{ order.accountPhone }}
</a-descriptions-item>
<a-descriptions-item label="回调状态">
<a-tag :color="order.callbackStatus === 1 ? 'green' : 'red'">
{{ order.callbackStatus === 1 ? '成功' : '失败' }}
</a-tag>
</a-descriptions-item>
<a-descriptions-item label="创建时间" :span="2">
{{ order.createdAt }}
</a-descriptions-item>
<a-descriptions-item label="更新时间" :span="2">
{{ order.updatedAt }}
</a-descriptions-item>
<a-descriptions-item label="备注" :span="2">
{{ order.remark || '-' }}
</a-descriptions-item>
</a-descriptions>
</div>
</a-modal>
</template>
<script lang="ts" setup>
import { computed } from 'vue';
// Props 定义
interface Props {
visible: boolean;
order: any;
}
const props = withDefaults(defineProps<Props>(), {
visible: false,
order: null
});
// Emits 定义
interface Emits {
(e: 'update:visible', value: boolean): void;
}
const emit = defineEmits<Emits>();
// 计算属性
const modalVisible = computed({
get: () => props.visible,
set: value => emit('update:visible', value)
});
/**
* 订单状态映射器
* @param status 状态值
* @returns 状态文本和颜色
*/
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' };
default:
return { text: '未知', color: 'gray' };
}
};
/**
* 支付状态映射器
* @param status 支付状态
* @returns 支付状态文本和颜色
*/
const payStatusMapper = (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' };
default:
return { text: '未知', color: 'gray' };
}
};
</script>
<style scoped>
.order-detail {
padding: 16px 0;
}
.amount-text {
color: #165dff;
font-weight: 600;
font-size: 16px;
}
:deep(.arco-descriptions-item-label) {
font-weight: 600;
background-color: #f7f8fa;
}
</style>

View File

@@ -27,11 +27,11 @@
placeholder="请选择状态"
allow-clear
>
<a-option :value="0">失效</a-option>
<a-option :value="1">正常</a-option>
<a-option :value="2">充值过快</a-option>
<a-option :value="3">账号受限</a-option>
<a-option :value="4">异常</a-option>
<a-option :value="0">待登录</a-option>
<a-option :value="1">发送验证码</a-option>
<a-option :value="2">在线</a-option>
<a-option :value="3">已暂停</a-option>
<a-option :value="4">已失效</a-option>
</a-select>
</a-form-item>
</a-col>
@@ -88,8 +88,8 @@
>
<!-- 状态列模板 -->
<template #status="{ record }">
<a-tag size="small" :color="statusMapper(record.status).color">
{{ statusMapper(record.status).text }}
<a-tag size="small" :color="statusColorMapper(record.status)">
{{ record.statusText || statusMapper(record.status).text }}
</a-tag>
</template>
@@ -110,13 +110,6 @@
</template>
</a-button>
</a-tooltip>
<a-tooltip content="关联订单">
<a-button size="small" @click="showAccountOrders(record)">
<template #icon>
<icon-order />
</template>
</a-button>
</a-tooltip>
</a-space>
</template>
</a-table>
@@ -133,33 +126,22 @@
v-model:visible="state.historyModalVisible"
:account-id="state.accountId"
/>
<!-- 账号关联订单弹窗 -->
<account-orders
v-model:visible="state.ordersModalVisible"
:account-id="state.accountId"
/>
</div>
</template>
<script lang="ts" setup>
import { computed, onMounted, reactive, ref, watchEffect } from 'vue';
import { onMounted, reactive, ref } from 'vue';
import { Notification, TableColumnData } from '@arco-design/web-vue';
import useLoading from '@/hooks/loading';
import { Pagination } from '@/types/global';
import { checkTokenFromIframe, checkTokenFromLogin } from '@/utils/auth';
import { jdV2AccountClient } from '@/api/index.ts';
import type {
KamiApiCamelOilV1AccountListItem,
KamiApiCamelOilV1AccountListItemStatusEnum
} from '@/api/generated/models/index.ts';
import type { KamiApiCamelOilV1AccountListItem } from '@/api/generated/models/index.ts';
import type {
ApiJdV2AccountListGetPageSizeEnum,
ApiJdV2AccountListGetStatusEnum
} from '@/api/generated/apis/jdv2-account-api';
import AccountDetail from './components/detail.vue';
import AccountHistory from './components/history.vue';
import AccountOrders from './components/account-orders.vue';
// 基础分页配置
const basePagination: Pagination = {
@@ -182,13 +164,6 @@ const columns: TableColumnData[] = [
return rowIndex + 1 + (pagination.current - 1) * pagination.pageSize;
}
},
{
title: '账号ID',
dataIndex: 'accountId',
width: 120,
ellipsis: true,
tooltip: true
},
{
title: '账号名称',
dataIndex: 'accountName',
@@ -201,21 +176,6 @@ const columns: TableColumnData[] = [
ellipsis: true,
tooltip: true
},
{
title: '当日订单数',
dataIndex: 'dailyOrderCount',
width: 100
},
{
title: '累计订单数',
dataIndex: 'totalOrderCount',
width: 100
},
{
title: '剩余可下单',
dataIndex: 'remainingOrders',
width: 100
},
{
title: '最后使用时间',
dataIndex: 'lastUsedAt',
@@ -229,6 +189,20 @@ const columns: TableColumnData[] = [
slotName: 'status',
width: 100
},
{
title: '失败原因',
dataIndex: 'failureReason',
width: 150,
ellipsis: true,
tooltip: true
},
{
title: '备注',
dataIndex: 'remark',
width: 150,
ellipsis: true,
tooltip: true
},
{
title: '创建时间',
dataIndex: 'createdAt',
@@ -254,32 +228,12 @@ const generateSearchFormModel = () => ({
status: undefined as ApiJdV2AccountListGetStatusEnum | undefined
});
// 账号数据模型
const generateAccountModel = () => ({
accountId: '',
accountName: '',
phone: '',
status: undefined as KamiApiCamelOilV1AccountListItemStatusEnum | undefined,
statusText: '',
dailyOrderCount: 0,
totalOrderCount: 0,
remainingOrders: 0,
lastUsedAt: '',
lastLoginAt: '',
tokenExpireAt: '',
failureReason: '',
remark: '',
createdAt: '',
updatedAt: ''
});
const formModel = ref(generateSearchFormModel());
// 弹窗状态管理
const state = reactive({
detailModalVisible: false,
historyModalVisible: false,
ordersModalVisible: false,
accountId: '',
account: null as KamiApiCamelOilV1AccountListItem | null
});
@@ -385,33 +339,54 @@ const showHistoryModal = (record: KamiApiCamelOilV1AccountListItem) => {
};
/**
* 显示账号关联订单弹窗
* @param record 账号记录
* 状态颜色映射器
* 基于后端Go定义的状态
* - 0: 待登录
* - 1: 发送验证码
* - 2: 在线
* - 3: 已暂停
* - 4: 已失效
* 使用 Arco Design 标准颜色以兼容主题切换
* @param status 状态值
* @returns 状态颜色
*/
const showAccountOrders = (record: KamiApiCamelOilV1AccountListItem) => {
state.accountId = record.accountId?.toString() || '';
state.ordersModalVisible = true;
const statusColorMapper = (status: number): string => {
switch (status) {
case 0:
return 'orange'; // 待登录 - 橙色(等待状态)
case 1:
return 'blue'; // 发送验证码 - 蓝色(进行中状态)
case 2:
return 'green'; // 在线 - 绿色(正常状态)
case 3:
return 'orange'; // 已暂停 - 橙色(警告状态)
case 4:
return 'red'; // 已失效 - 红色(危险状态)
default:
return 'gray'; // 未知状态 - 灰色(中性状态)
}
};
/**
* 状态映射器
* 状态文本映射器(作为备用)
* 基于后端Go定义的状态
* @param status 状态值
* @returns 状态文本和颜色
* @returns 状态文本
*/
const statusMapper = (status: number): { text: string; color: string } => {
const statusMapper = (status: number): { text: string } => {
switch (status) {
case 0:
return { text: '失效', color: 'red' };
return { text: '待登录' }; // 需要登录验证
case 1:
return { text: '正常', color: 'green' };
return { text: '发送验证码' }; // 正在发送验证码
case 2:
return { text: '充值过快', color: 'orange' };
return { text: '在线' }; // 账户在线,可用
case 3:
return { text: '账号受限', color: 'red' };
return { text: '已暂停' }; // 账户被暂停使用
case 4:
return { text: '异常', color: 'gray' };
return { text: '已失效' }; // 账户已失效
default:
return { text: '未知', color: 'gray' };
return { text: '未知状态' };
}
};

View File

@@ -51,14 +51,6 @@
{{ payStatusMapper(order.payStatus).text }}
</a-tag>
</a-descriptions-item>
<a-descriptions-item label="回调状态">
<a-tag :color="order.callbackStatus === 1 ? 'green' : 'red'">
{{ order.callbackStatus === 1 ? '成功' : '失败' }}
</a-tag>
</a-descriptions-item>
<a-descriptions-item label="回调次数">
{{ order.callbackCount || 0 }}
</a-descriptions-item>
</a-descriptions>
</a-card>
@@ -68,42 +60,14 @@
<a-descriptions-item label="创建时间">
{{ order.createdAt }}
</a-descriptions-item>
<a-descriptions-item label="支付时间">
{{ order.payTime || '-' }}
</a-descriptions-item>
<a-descriptions-item label="完成时间">
{{ order.completeTime || '-' }}
</a-descriptions-item>
<a-descriptions-item label="最后回调时间">
{{ order.lastCallbackTime || '-' }}
<a-descriptions-item label="支付完成时间">
{{ order.paidAt || '-' }}
</a-descriptions-item>
<a-descriptions-item label="更新时间" :span="2">
{{ order.updatedAt }}
</a-descriptions-item>
</a-descriptions>
</a-card>
<!-- 其他信息 -->
<a-card title="其他信息" class="detail-card">
<a-descriptions :column="1" bordered>
<a-descriptions-item label="回调地址">
<code class="callback-url">{{ order.callbackUrl || '-' }}</code>
</a-descriptions-item>
<a-descriptions-item label="请求参数">
<pre class="request-params">{{
formatJson(order.requestParams) || '-'
}}</pre>
</a-descriptions-item>
<a-descriptions-item label="响应参数">
<pre class="response-params">{{
formatJson(order.responseParams) || '-'
}}</pre>
</a-descriptions-item>
<a-descriptions-item label="备注">
{{ order.remark || '-' }}
</a-descriptions-item>
</a-descriptions>
</a-card>
</div>
</a-modal>
</template>
@@ -137,56 +101,43 @@ const modalVisible = computed({
/**
* 订单状态映射器
* 基于后端Go定义的CamelOilOrderStatus
* @param status 状态值
* @returns 状态文本和颜色
*/
const statusMapper = (status: number): { text: string; color: string } => {
switch (status) {
case 0:
return { text: '待处理', color: 'orange' };
return { text: '待处理', color: 'orange' }; // CamelOilOrderStatusPending
case 1:
return { text: '成功', color: 'green' };
return { text: '处理中', color: 'blue' }; // CamelOilOrderStatusProcessing
case 2:
return { text: '失败', color: 'red' };
return { text: '已完成', color: 'green' }; // CamelOilOrderStatusCompleted
case 3:
return { text: '已取消', color: 'gray' };
return { text: '已失败', color: 'red' }; // CamelOilOrderStatusFailed
default:
return { text: '未知', color: 'gray' };
return { text: '未知状态', color: 'gray' };
}
};
/**
* 支付状态映射器
* 基于后端Go定义的CamelOilPayStatus
* @param status 支付状态
* @returns 支付状态文本和颜色
*/
const payStatusMapper = (status: number): { text: string; color: string } => {
switch (status) {
case 0:
return { text: '未支付', color: 'orange' };
return { text: '未支付', color: 'orange' }; // CamelOilPaymentStatusUnpaid
case 1:
return { text: '已支付', color: 'green' };
return { text: '已支付', color: 'green' }; // CamelOilPaymentStatusPaid
case 2:
return { text: '支付失败', color: 'red' };
return { text: '已退款', color: 'blue' }; // CamelOilPaymentStatusRefunded
case 3:
return { text: '已超时', color: 'red' }; // CamelOilPaymentStatusTimeout
default:
return { text: '未知', color: 'gray' };
}
};
/**
* 格式化JSON字符串
* @param json JSON对象或字符串
* @returns 格式化后的字符串
*/
const formatJson = (json: any): string => {
if (!json) return '';
try {
if (typeof json === 'string') {
return JSON.stringify(JSON.parse(json), null, 2);
}
return JSON.stringify(json, null, 2);
} catch {
return String(json);
return { text: '未知状态', color: 'gray' };
}
};
</script>
@@ -205,48 +156,41 @@ const formatJson = (json: any): string => {
}
.amount-text {
color: #165dff;
color: rgb(var(--primary-6));
font-weight: 600;
font-size: 16px;
}
.actual-amount-text {
color: #00b42a;
color: rgb(var(--success-6));
font-weight: 600;
font-size: 16px;
}
.callback-url {
background-color: #f7f8fa;
padding: 4px 8px;
border-radius: 4px;
font-family: Monaco, Menlo, 'Ubuntu Mono', monospace;
font-size: 12px;
word-break: break-all;
}
.request-params,
.response-params {
background-color: #f7f8fa;
padding: 12px;
border-radius: 6px;
font-family: Monaco, Menlo, 'Ubuntu Mono', monospace;
font-size: 12px;
line-height: 1.4;
white-space: pre-wrap;
word-break: break-all;
max-height: 200px;
overflow-y: auto;
margin: 0;
}
:deep(.arco-descriptions-item-label) {
font-weight: 600;
background-color: #f7f8fa;
background-color: var(--color-fill-2);
}
:deep(.arco-modal-body) {
max-height: 80vh;
overflow-y: auto;
/* 确保滚动条样式跟随主题 */
scrollbar-width: thin;
scrollbar-color: var(--color-border-3) transparent;
}
:deep(.arco-modal-body::-webkit-scrollbar) {
width: 6px;
}
:deep(.arco-modal-body::-webkit-scrollbar-thumb) {
background-color: var(--color-border-3);
border-radius: 3px;
}
:deep(.arco-modal-body::-webkit-scrollbar-track) {
background-color: transparent;
}
</style>

View File

@@ -21,21 +21,13 @@
</a-form-item>
</a-col>
<a-col :span="8">
<a-form-item field="merchantOrderNo" label="商户订单号">
<a-form-item field="merchantOrderId" label="商户订单号">
<a-input
v-model="formModel.merchantOrderNo"
v-model="formModel.merchantOrderId"
placeholder="请输入商户订单号"
/>
</a-form-item>
</a-col>
<a-col :span="8">
<a-form-item field="accountPhone" label="充值手机">
<a-input
v-model="formModel.accountPhone"
placeholder="请输入充值手机号"
/>
</a-form-item>
</a-col>
<a-col :span="8">
<a-form-item field="status" label="订单状态">
<a-select
@@ -44,9 +36,9 @@
allow-clear
>
<a-option :value="0">待处理</a-option>
<a-option :value="1">成功</a-option>
<a-option :value="2">失败</a-option>
<a-option :value="3">取消</a-option>
<a-option :value="1">处理中</a-option>
<a-option :value="2">已完成</a-option>
<a-option :value="3">失败</a-option>
</a-select>
</a-form-item>
</a-col>
@@ -140,8 +132,8 @@
<!-- 回调状态模板 -->
<template #callbackStatus="{ record }">
<a-tag :color="record.callbackStatus === 1 ? 'green' : 'red'">
{{ record.callbackStatus === 1 ? '成功' : '失败' }}
<a-tag :color="callbackStatusMapper(record.notifyStatus).color">
{{ callbackStatusMapper(record.notifyStatus).text }}
</a-tag>
</template>
@@ -198,6 +190,11 @@ import type {
ApiJdV2OrderListGetStatusEnum,
ApiJdV2OrderListGetPayStatusEnum
} from '@/api/generated/apis/jdv2-order-api';
import type {
KamiApiCamelOilV1OrderListItemStatusEnum,
KamiApiCamelOilV1OrderListItemPayStatusEnum,
KamiApiCamelOilV1OrderListItemNotifyStatusEnum
} from '@/api/generated/models/index.ts';
import OrderDetailModal from './components/order-detail-modal.vue';
import OrderHistoryModal from './components/order-history-modal.vue';
@@ -280,14 +277,9 @@ const columns: TableColumnData[] = [
tooltip: true
},
{
title: '账号ID',
dataIndex: 'accountId',
width: 120
},
{
title: '充值手机',
dataIndex: 'accountPhone',
width: 130
title: '账号名称',
dataIndex: 'accountName',
width: 150
},
{
title: '订单金额',
@@ -309,18 +301,27 @@ const columns: TableColumnData[] = [
},
{
title: '回调状态',
dataIndex: 'callbackStatus',
dataIndex: 'notifyStatus',
slotName: 'callbackStatus',
width: 100
},
{
title: '创建时间',
dataIndex: 'createdAt',
width: 180
title: '支付完成时间',
dataIndex: 'paidAt',
width: 180,
ellipsis: true,
tooltip: true
},
{
title: '更新时间',
dataIndex: 'updatedAt',
title: '失败原因',
dataIndex: 'failureReason',
width: 150,
ellipsis: true,
tooltip: true
},
{
title: '创建时间',
dataIndex: 'createdAt',
width: 180
},
{
@@ -335,8 +336,7 @@ const columns: TableColumnData[] = [
// 表单模型
const generateFormModel = () => ({
orderNo: '',
merchantOrderNo: '',
accountPhone: '',
merchantOrderId: '',
status: undefined as ApiJdV2OrderListGetStatusEnum | undefined,
payStatus: undefined as ApiJdV2OrderListGetPayStatusEnum | undefined,
dateRange: [] as Date[]
@@ -456,39 +456,64 @@ const showOrderHistory = (record: KamiApiCamelOilV1OrderListItem) => {
/**
* 订单状态映射器
* 基于后端Go定义的CamelOilOrderStatus
* @param status 状态值
* @returns 状态文本和颜色
*/
const statusMapper = (status: number): { text: string; color: string } => {
switch (status) {
case 0:
return { text: '待处理', color: 'orange' };
return { text: '待处理', color: 'orange' }; // CamelOilOrderStatusPending
case 1:
return { text: '成功', color: 'green' };
return { text: '处理中', color: 'blue' }; // CamelOilOrderStatusProcessing
case 2:
return { text: '失败', color: 'red' };
return { text: '已完成', color: 'green' }; // CamelOilOrderStatusCompleted
case 3:
return { text: '已取消', color: 'gray' };
return { text: '已失败', color: 'red' }; // CamelOilOrderStatusFailed
default:
return { text: '未知', color: 'gray' };
return { text: '未知状态', color: 'gray' };
}
};
/**
* 支付状态映射器
* 基于后端Go定义的CamelOilPayStatus
* @param status 支付状态
* @returns 支付状态文本和颜色
*/
const payStatusMapper = (status: number): { text: string; color: string } => {
switch (status) {
case 0:
return { text: '未支付', color: 'orange' };
return { text: '未支付', color: 'orange' }; // CamelOilPaymentStatusUnpaid
case 1:
return { text: '已支付', color: 'green' };
return { text: '已支付', color: 'green' }; // CamelOilPaymentStatusPaid
case 2:
return { text: '支付失败', color: 'red' };
return { text: '已退款', color: 'blue' }; // CamelOilPaymentStatusRefunded
case 3:
return { text: '已超时', color: 'red' }; // CamelOilPaymentStatusTimeout
default:
return { text: '未知', color: 'gray' };
return { text: '未知状态', color: 'gray' };
}
};
/**
* 回调状态映射器
* 基于后端Go定义的CamelOilNotifyStatus
* @param status 回调状态
* @returns 回调状态文本和颜色
*/
const callbackStatusMapper = (
status: number
): { text: string; color: string } => {
switch (status) {
case 0:
return { text: '未回调', color: 'orange' }; // CamelOilCallbackStatusPending
case 1:
return { text: '回调成功', color: 'green' }; // CamelOilCallbackStatusSuccess
case 2:
return { text: '回调失败', color: 'red' }; // CamelOilCallbackStatusFailed
default:
return { text: '未知状态', color: 'gray' };
}
};
@@ -516,7 +541,7 @@ onMounted(() => {
}
.amount-text {
color: #165dff;
color: rgb(var(--primary-6));
font-weight: 600;
}
</style>

View File

@@ -31,6 +31,13 @@
show-word-limit
/>
</a-form-item>
<a-form-item field="phone" label="绑定手机号" required>
<a-input
v-model="formModel.phone"
placeholder="请输入绑定的手机号"
:max-length="11"
/>
</a-form-item>
<a-form-item field="remark" label="备注">
<a-textarea
v-model="formModel.remark"
@@ -74,6 +81,7 @@ const generateFormModel = () => {
return {
tokenName: '',
tokenValue: '',
phone: '',
remark: ''
};
};
@@ -89,6 +97,10 @@ const rules = {
{ required: true, message: '请输入Token值' },
{ min: 1, max: 1000, message: 'Token值长度为1-1000个字符' }
],
phone: [
{ required: true, message: '请输入绑定手机号' },
{ match: /^1[3-9]\d{9}$/, message: '请输入正确的手机号码' }
],
remark: [{ max: 500, message: '备注长度不能超过500个字符' }]
};
@@ -144,8 +156,26 @@ const handleBeforeOk = async () => {
</script>
<style scoped>
/* 使用 Arco Design 的原生样式,确保主题兼容性 */
:deep(.arco-modal-body) {
max-height: 60vh;
overflow-y: auto;
/* 确保滚动条样式跟随主题 */
scrollbar-width: thin;
scrollbar-color: var(--color-border-3) transparent;
}
:deep(.arco-modal-body::-webkit-scrollbar) {
width: 6px;
}
:deep(.arco-modal-body::-webkit-scrollbar-thumb) {
background-color: var(--color-border-3);
border-radius: 3px;
}
:deep(.arco-modal-body::-webkit-scrollbar-track) {
background-color: transparent;
}
</style>

View File

@@ -211,8 +211,26 @@ watch(
</script>
<style scoped>
/* 使用 Arco Design 的原生样式,确保主题兼容性 */
:deep(.arco-modal-body) {
max-height: 70vh;
overflow-y: auto;
/* 确保滚动条样式跟随主题 */
scrollbar-width: thin;
scrollbar-color: var(--color-border-3) transparent;
}
:deep(.arco-modal-body::-webkit-scrollbar) {
width: 6px;
}
:deep(.arco-modal-body::-webkit-scrollbar-thumb) {
background-color: var(--color-border-3);
border-radius: 3px;
}
:deep(.arco-modal-body::-webkit-scrollbar-track) {
background-color: transparent;
}
</style>

View File

@@ -178,12 +178,31 @@ const columns: TableColumnData[] = [
width: 150
},
{
title: 'Token',
dataIndex: 'token',
title: 'Token',
dataIndex: 'tokenValue',
width: 300,
ellipsis: true,
tooltip: true
},
{
title: '绑定手机号',
dataIndex: 'phone',
width: 130,
ellipsis: true,
tooltip: true
},
{
title: '绑定数量',
dataIndex: 'bindCount',
width: 100
},
{
title: '最后使用时间',
dataIndex: 'lastUsedAt',
width: 180,
ellipsis: true,
tooltip: true
},
{
title: '状态',
dataIndex: 'status',
@@ -191,20 +210,17 @@ const columns: TableColumnData[] = [
width: 100
},
{
title: '创建人',
dataIndex: 'createdBy',
width: 120
title: '备注',
dataIndex: 'remark',
width: 150,
ellipsis: true,
tooltip: true
},
{
title: '创建时间',
dataIndex: 'createdAt',
width: 180
},
{
title: '更新时间',
dataIndex: 'updatedAt',
width: 180
},
{
title: '操作',
dataIndex: 'operations',
@@ -342,4 +358,8 @@ onMounted(() => {
.container {
padding: 0 20px 20px;
}
.general-card {
min-height: calc(100vh - 170px);
}
</style>