chore(api): 修复cookie列表错误

修复cookie列表错误
This commit is contained in:
danial
2025-10-10 20:27:01 +08:00
parent 2daca68d92
commit b75af9609f
14 changed files with 970 additions and 1798 deletions

View File

@@ -22,16 +22,6 @@ const JDOrder: AppRouteRecordRaw = {
roles: ['*']
}
},
{
path: 'cookie-history',
name: 'jdOrderCookieHistory',
component: () => import('@/views/jd-order/cookie/history.vue'),
meta: {
locale: 'Cookie变更历史',
requiresAuth: true,
roles: ['*']
}
},
{
path: 'order',
name: 'jdOrderManagement',
@@ -43,11 +33,11 @@ const JDOrder: AppRouteRecordRaw = {
}
},
{
path: 'order-history',
name: 'jdOrderHistory',
component: () => import('@/views/jd-order/order/history.vue'),
path: 'jd-orders',
name: 'jdOrders',
component: () => import('@/views/jd-order/jd-orders/index.vue'),
meta: {
locale: '订单变更历史',
locale: '京东订单',
requiresAuth: true,
roles: ['*']
}

View File

@@ -83,7 +83,7 @@ const { loading, setLoading } = useLoading(false);
const emit = defineEmits(['update:visible']);
const handleOk = (done: (closed: boolean) => void) => {
formDataRef.value.validate().then(async res => {
formDataRef.value.validate().then(async (res: any) => {
if (res) return done(false);
try {
setLoading(true);
@@ -101,6 +101,7 @@ const handleOk = (done: (closed: boolean) => void) => {
}
});
}
console.log('success');
done(true);
emit('update:visible', false);
formData.value = generateFormData();

View File

@@ -1,14 +1,7 @@
<template>
<a-button @click="handleClick">
<template #icon>
<icon-upload />
</template>
批量导入
</a-button>
<a-modal
v-model:visible="visible"
title="批量导入Cookie"
:visible="visible"
title="批量添加Cookie"
width="600px"
@cancel="handleCancel"
@before-ok="handleSubmit"
@@ -17,15 +10,13 @@
<a-form-item label="Cookie数据">
<a-textarea
v-model="formModel.cookieData"
placeholder="每行一个Cookie格式cookie值&#10;昵称&#10;邮箱&#10;余额"
placeholder="每行一个Cookie格式Cookie值,昵称,备注"
:auto-size="{ minRows: 6, maxRows: 12 }"
placeholder-class="batch-textarea"
/>
<div class="tip-text">
<icon-info-circle />
<span>
支持批量粘贴每行一个Cookie格式Cookie值,昵称,邮箱,余额
</span>
<span>支持批量粘贴每行一个Cookie格式Cookie值,昵称,备注</span>
</div>
</a-form-item>
</a-form>
@@ -37,13 +28,18 @@ import { ref, reactive } from 'vue';
import { Message } from '@arco-design/web-vue';
import { jdCookieClient } from '@/api';
interface Props {
visible: boolean;
}
interface Emits {
(e: 'update:visible', value: boolean): void;
(e: 'success'): void;
}
const props = defineProps<Props>();
const emit = defineEmits<Emits>();
const visible = ref(false);
const loading = ref(false);
// 表单数据
@@ -51,12 +47,8 @@ const formModel = reactive({
cookieData: ''
});
const handleClick = (): void => {
visible.value = true;
};
const handleCancel = (): void => {
visible.value = false;
emit('update:visible', false);
formModel.cookieData = '';
};
@@ -79,7 +71,7 @@ const handleSubmit = async (): Promise<boolean> => {
return {
cookieValue: parts[0] || '',
accountName: parts[1] || '',
remark: `批量导入${new Date().toLocaleString()}`
remark: `批量添加${new Date().toLocaleString()}`
};
})
.filter(account => account.cookieValue);
@@ -96,11 +88,11 @@ const handleSubmit = async (): Promise<boolean> => {
}
});
Message.success(`成功导入 ${cookieAccounts.length} 个Cookie`);
Message.success(`成功添加 ${cookieAccounts.length} 个Cookie`);
emit('success');
return true;
} catch (error) {
Message.error('批量导入失败');
Message.error('批量添加失败');
return false;
} finally {
loading.value = false;

View File

@@ -0,0 +1,417 @@
<template>
<a-drawer
:visible="visible"
:width="800"
:title="`Cookie变更记录 - ${cookieId}`"
:footer="false"
unmount-on-close
@cancel="handleCancel"
>
<a-spin :loading="loading" style="width: 100%">
<div v-if="historyData.length > 0" class="history-container">
<a-timeline>
<a-timeline-item
v-for="(item, index) in historyData"
:key="item.historyUuid || index"
:dot-color="getTimelineDotColor(item.changeType)"
:dot-type="index === 0 ? 'filled' : 'hollow'"
>
<div class="timeline-content">
<!-- 变更时间和类型 -->
<div class="timeline-header">
<div class="time-info">
<span class="change-time">
{{ formatTime(item.createdAt) }}
</span>
<a-tag
:color="getChangeTypeColor(item.changeType)"
size="small"
>
{{ getChangeTypeText(item.changeType) }}
</a-tag>
</div>
</div>
<!-- 状态变更信息 -->
<div
v-if="
item.statusBefore !== undefined &&
item.statusAfter !== undefined
"
class="status-change"
>
<div class="status-info">
<span class="label">状态变更:</span>
<a-tag
:color="getStatusColor(item.statusBefore)"
size="small"
class="status-before"
>
{{ getStatusText(item.statusBefore) }}
</a-tag>
<icon-right class="arrow-icon" />
<a-tag
:color="getStatusColor(item.statusAfter)"
size="small"
class="status-after"
>
{{ getStatusText(item.statusAfter) }}
</a-tag>
</div>
</div>
<!-- 失败次数 -->
<div
v-if="item.failureCount && item.failureCount > 0"
class="failure-info"
>
<span class="label">失败次数:</span>
<a-tag color="red" size="small">
{{ item.failureCount }} 次
</a-tag>
</div>
<!-- 关联订单 -->
<div v-if="item.userOrderId" class="order-info">
<span class="label">关联订单:</span>
<a-typography-text
copyable
:content="item.userOrderId"
class="order-id"
/>
</div>
<!-- 历史记录ID -->
<div class="history-id">
<span class="label">记录ID</span>
<a-typography-text
copyable
:content="item.historyUuid || ''"
class="uuid-text"
type="secondary"
size="small"
/>
</div>
</div>
</a-timeline-item>
</a-timeline>
<!-- 加载更多 -->
<div v-if="hasMore" class="load-more">
<a-button :loading="loadingMore" type="outline" @click="loadMore">
加载更多
</a-button>
</div>
</div>
<a-empty v-else description="暂无变更记录" />
</a-spin>
</a-drawer>
</template>
<script lang="ts" setup>
import { ref, reactive, watch } from 'vue';
import { Message } from '@arco-design/web-vue';
import { IconRight } from '@arco-design/web-vue/es/icon';
import { JDHistoryApi } from '@/api/generated';
import type { KamiApiJdCookieV1CookieHistoryInfo } from '@/api/generated';
interface Props {
visible: boolean;
cookieId: string;
}
interface Emits {
(e: 'update:visible', value: boolean): void;
}
const props = defineProps<Props>();
const emit = defineEmits<Emits>();
const historyApi = new JDHistoryApi();
// 状态管理
const loading = ref(false);
const loadingMore = ref(false);
const historyData = ref<KamiApiJdCookieV1CookieHistoryInfo[]>([]);
const hasMore = ref(false);
// 分页配置
const pagination = reactive({
current: 1,
pageSize: 20,
total: 0
});
// 处理取消
const handleCancel = (): void => {
emit('update:visible', false);
};
// 格式化时间
const formatTime = (timeStr?: string): string => {
if (!timeStr) return '-';
try {
const date = new Date(timeStr);
return date.toLocaleString('zh-CN', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit'
});
} catch {
return timeStr;
}
};
// 获取时间线节点颜色
const getTimelineDotColor = (changeType?: string): string => {
switch (changeType) {
case 'status_change':
return '#165DFF';
case 'failure_reset':
return '#FF7D00';
case 'auto_suspend':
return '#F53F3F';
case 'manual_resume':
return '#00B42A';
case 'order_check':
return '#722ED1';
default:
return '#86909C';
}
};
// 获取变更类型颜色
const getChangeTypeColor = (changeType?: string): string => {
switch (changeType) {
case 'status_change':
return 'blue';
case 'failure_reset':
return 'orange';
case 'auto_suspend':
return 'red';
case 'manual_resume':
return 'green';
case 'order_check':
return 'purple';
default:
return 'gray';
}
};
// 获取变更类型文本
const getChangeTypeText = (changeType?: string): string => {
switch (changeType) {
case 'status_change':
return '状态变更';
case 'failure_reset':
return '失败次数重置';
case 'auto_suspend':
return '自动暂停';
case 'manual_resume':
return '手动恢复';
case 'order_check':
return '订单检查';
default:
return changeType || '未知';
}
};
// 获取状态颜色
const getStatusColor = (status?: number): string => {
switch (status) {
case 1:
return 'green';
case 2:
return 'orange';
case 3:
return 'red';
default:
return 'gray';
}
};
// 获取状态文本
const getStatusText = (status?: number): string => {
switch (status) {
case 1:
return '正常';
case 2:
return '暂停';
case 3:
return '失效';
default:
return '未知';
}
};
// 获取变更记录数据
const fetchHistoryData = async (isLoadMore = false): Promise<void> => {
if (!props.cookieId) return;
if (isLoadMore) {
loadingMore.value = true;
} else {
loading.value = true;
pagination.current = 1;
}
try {
const response = await historyApi.apiJdCookieHistoryCookieGet({
cookieId: props.cookieId,
page: pagination.current,
size: pagination.pageSize
});
const newData = response.data.list || [];
if (isLoadMore) {
historyData.value = [...historyData.value, ...newData];
} else {
historyData.value = newData;
}
pagination.total = response.data.total || 0;
hasMore.value = historyData.value.length < pagination.total;
} catch (error) {
Message.error('获取变更记录失败');
if (!isLoadMore) {
historyData.value = [];
pagination.total = 0;
}
} finally {
loading.value = false;
loadingMore.value = false;
}
};
// 加载更多
const loadMore = (): void => {
pagination.current += 1;
fetchHistoryData(true);
};
// 监听弹窗显示状态
watch(
() => props.visible,
newVal => {
if (newVal && props.cookieId) {
pagination.current = 1;
historyData.value = [];
fetchHistoryData();
} else {
historyData.value = [];
pagination.total = 0;
hasMore.value = false;
}
}
);
</script>
<style scoped>
.history-container {
max-height: calc(100vh - 120px);
overflow-y: auto;
padding-right: 8px;
}
.timeline-content {
padding: 12px 0;
}
.timeline-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 8px;
}
.time-info {
display: flex;
align-items: center;
gap: 12px;
}
.change-time {
font-size: 14px;
font-weight: 500;
color: var(--color-text-1);
}
.status-change {
margin: 8px 0;
}
.status-info {
display: flex;
align-items: center;
gap: 8px;
}
.arrow-icon {
color: var(--color-text-3);
font-size: 16px;
}
.failure-info,
.order-info,
.history-id {
margin: 4px 0;
display: flex;
align-items: center;
gap: 8px;
}
.label {
font-size: 13px;
color: var(--color-text-3);
min-width: 70px;
}
.order-id {
font-family: Monaco, Menlo, 'Ubuntu Mono', monospace;
font-size: 13px;
}
.uuid-text {
font-family: Monaco, Menlo, 'Ubuntu Mono', monospace;
font-size: 12px;
}
.load-more {
text-align: center;
margin-top: 24px;
padding-top: 16px;
border-top: 1px solid var(--color-border-2);
}
:deep(.arco-timeline-item-content) {
padding-bottom: 16px;
}
:deep(.arco-timeline-item:last-child .arco-timeline-item-content) {
padding-bottom: 0;
}
/* 滚动条样式 */
.history-container::-webkit-scrollbar {
width: 6px;
}
.history-container::-webkit-scrollbar-track {
background: var(--color-fill-1);
border-radius: 3px;
}
.history-container::-webkit-scrollbar-thumb {
background: var(--color-border-3);
border-radius: 3px;
}
.history-container::-webkit-scrollbar-thumb:hover {
background: var(--color-border-4);
}
</style>

View File

@@ -1,319 +0,0 @@
<template>
<a-modal
:visible="visible"
title="Cookie变更历史"
width="900px"
footer
@cancel="handleCancel"
>
<template v-if="cookieId">
<!-- 筛选条件 -->
<a-row style="margin-bottom: 16px">
<a-col :flex="1">
<a-form :model="filterModel" layout="inline">
<a-form-item field="changeType" label="变更类型">
<a-select
v-model="filterModel.changeType"
placeholder="请选择变更类型"
allow-clear
style="width: 200px"
@change="fetchHistory"
>
<a-option value="create">创建</a-option>
<a-option value="update">更新</a-option>
<a-option value="status_change">状态变更</a-option>
<a-option value="balance_change">余额变更</a-option>
<a-option value="delete">删除</a-option>
</a-select>
</a-form-item>
</a-form>
</a-col>
<a-col flex="100px">
<a-button @click="fetchHistory">
<template #icon>
<icon-refresh />
</template>
刷新
</a-button>
</a-col>
</a-row>
<!-- 时间线 -->
<a-timeline v-if="historyData.length > 0" class="history-timeline">
<a-timeline-item
v-for="(item, index) in historyData"
:key="index"
:dot-color="getTimelineColor(item.changeType)"
>
<template #dot>
<icon-check-circle-fill v-if="item.changeType === 'create'" />
<icon-edit-fill v-else-if="item.changeType === 'update'" />
<icon-exclamation-circle-fill
v-else-if="item.changeType === 'status_change'"
/>
<icon-dollar-circle-fill
v-else-if="item.changeType === 'balance_change'"
/>
<icon-close-circle-fill v-else-if="item.changeType === 'delete'" />
<icon-info-circle-fill v-else />
</template>
<div class="timeline-content">
<div class="timeline-header">
<span class="timeline-title">
{{ getChangeTypeText(item.changeType) }}
</span>
<a-tag :color="getChangeTypeColor(item.changeType)" size="small">
{{ item.changeType }}
</a-tag>
</div>
<div class="timeline-time">
<icon-clock-circle />
{{ item.createdAt }}
</div>
<div class="timeline-description">
<a-descriptions :column="1" size="small" bordered>
<a-descriptions-item label="变更类型">
{{ item.changeType }}
</a-descriptions-item>
<a-descriptions-item
v-if="item.statusBefore"
label="变更前状态"
>
<a-tag :color="item.statusBefore === 1 ? 'green' : 'red'">
{{ item.statusBefore === 1 ? '正常' : '失效' }}
</a-tag>
</a-descriptions-item>
<a-descriptions-item v-if="item.statusAfter" label="变更后状态">
<a-tag :color="item.statusAfter === 1 ? 'green' : 'red'">
{{ item.statusAfter === 1 ? '正常' : '失效' }}
</a-tag>
</a-descriptions-item>
<a-descriptions-item v-if="item.userOrderId" label="关联订单">
{{ item.userOrderId }}
</a-descriptions-item>
</a-descriptions>
</div>
</div>
</a-timeline-item>
</a-timeline>
<!-- 空状态 -->
<a-empty v-else description="暂无变更历史" />
<!-- 分页 -->
<div v-if="historyData.length > 0" class="pagination-wrapper">
<a-pagination
:current="pagination.current"
:page-size="pagination.pageSize"
:total="pagination.total"
:show-total="true"
:show-page-size="true"
:page-size-options="[10, 20, 50, 100]"
@change="onPageChange"
@page-size-change="onPageSizeChange"
/>
</div>
</template>
<a-empty v-else description="请选择Cookie查看历史记录" />
</a-modal>
</template>
<script lang="ts" setup>
import { ref, reactive, watch } from 'vue';
import { Message } from '@arco-design/web-vue';
import { jdHistoryClient } from '@/api';
import type { KamiApiJdCookieV1CookieHistoryInfo } from '@/api/generated';
interface Props {
visible: boolean;
cookieId: string;
}
interface Emits {
(e: 'update:visible', value: boolean): void;
}
interface FilterModel {
changeType: string | undefined;
}
interface Pagination {
current: number;
pageSize: number;
total: number;
}
const props = defineProps<Props>();
const emit = defineEmits<Emits>();
const historyData = ref<KamiApiJdCookieV1CookieHistoryInfo[]>([]);
const loading = ref(false);
// 处理取消
const handleCancel = (): void => {
emit('update:visible', false);
};
const filterModel = reactive<FilterModel>({
changeType: undefined
});
const pagination = reactive<Pagination>({
current: 1,
pageSize: 20,
total: 0
});
// 监听弹窗显示状态
watch(
() => props.visible,
newVal => {
if (newVal && props.cookieId) {
fetchHistory();
} else {
historyData.value = [];
pagination.current = 1;
}
}
);
// 获取历史记录
const fetchHistory = async (): Promise<void> => {
if (!props.cookieId) return;
loading.value = true;
try {
const response = await jdHistoryClient.apiJdCookieHistoryCookieGet({
cookieId: props.cookieId,
page: pagination.current,
size: pagination.pageSize,
changeType: filterModel.changeType
});
historyData.value = response.data.list || [];
pagination.total = response.data.total || 0;
} catch (error) {
Message.error('获取变更历史失败');
historyData.value = [];
} finally {
loading.value = false;
}
};
// 分页变化
const onPageChange = (current: number): void => {
pagination.current = current;
fetchHistory();
};
const onPageSizeChange = (pageSize: number): void => {
pagination.pageSize = pageSize;
pagination.current = 1;
fetchHistory();
};
// 获取时间线颜色
const getTimelineColor = (changeType: string): string => {
const colors: Record<string, string> = {
create: '#00b42a',
update: '#165dff',
status_change: '#ff7d00',
balance_change: '#722ed1',
delete: '#f53f3f'
};
return colors[changeType] || '#86909c';
};
// 获取变更类型文本
const getChangeTypeText = (changeType: string): string => {
const texts: Record<string, string> = {
create: '创建Cookie',
update: '更新信息',
status_change: '状态变更',
balance_change: '余额变更',
delete: '删除Cookie'
};
return texts[changeType] || '未知变更';
};
// 获取变更类型颜色
const getChangeTypeColor = (changeType: string): string => {
const colors: Record<string, string> = {
create: 'green',
update: 'blue',
status_change: 'orange',
balance_change: 'purple',
delete: 'red'
};
return colors[changeType] || 'gray';
};
</script>
<style scoped>
.history-timeline {
max-height: 500px;
overflow-y: auto;
padding-right: 16px;
}
.timeline-content {
margin-left: 16px;
}
.timeline-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 8px;
}
.timeline-title {
font-weight: 600;
font-size: 16px;
color: var(--color-text-1);
}
.timeline-time {
display: flex;
align-items: center;
gap: 4px;
color: var(--color-text-3);
font-size: 12px;
margin-bottom: 12px;
}
.timeline-description {
background: var(--color-fill-1);
padding: 12px;
border-radius: 6px;
border-left: 3px solid var(--color-primary-light-4);
}
.old-value {
color: #f53f3f;
background: var(--color-danger-light-1);
padding: 2px 6px;
border-radius: 3px;
font-family: monospace;
font-size: 12px;
}
.new-value {
color: #00b42a;
background: var(--color-success-light-1);
padding: 2px 6px;
border-radius: 3px;
font-family: monospace;
font-size: 12px;
}
.pagination-wrapper {
display: flex;
justify-content: center;
margin-top: 24px;
padding-top: 16px;
border-top: 1px solid var(--color-border-2);
}
</style>

View File

@@ -3,6 +3,7 @@
:visible="visible"
:title="cookieId ? '编辑Cookie' : '添加Cookie'"
width="600px"
:loading="loading"
@before-ok="handleSubmit"
@cancel="handleCancel"
>
@@ -14,16 +15,9 @@
:auto-size="{ minRows: 3, maxRows: 6 }"
/>
</a-form-item>
<a-form-item field="accountName" label="账户昵称">
<a-form-item field="accountName" label="账户昵称" required>
<a-input v-model="formModel.accountName" placeholder="请输入账户昵称" />
</a-form-item>
<a-form-item field="status" label="状态">
<a-select v-model="formModel.status" placeholder="请选择状态">
<a-option :value="1">正常</a-option>
<a-option :value="0">失效</a-option>
<a-option :value="2">异常</a-option>
</a-select>
</a-form-item>
<a-form-item field="remark" label="备注">
<a-textarea
v-model="formModel.remark"
@@ -73,23 +67,41 @@ const formModel = reactive({
// 表单验证规则
const rules: Record<string, any[]> = {
cookieValue: [
{ required: true, message: '请输入Cookie内容' },
{ minLength: 10, message: 'Cookie内容不能少于10个字符' }
]
{ required: true, message: '请输入Cookie内容', trigger: 'blur' }
],
accountName: [{ required: true, message: '请输入账户昵称', trigger: 'blur' }]
};
// 监听props变化更新表单数据
watch(
() => props.visible,
newVal => {
if (newVal && props.cookieData) {
// 编辑模式
Object.assign(formModel, {
cookieValue: props.cookieData.cookieId || '',
accountName: props.cookieData.accountName || '',
status: props.cookieData.status || 1,
remark: props.cookieData.remark || ''
});
async newVal => {
if (newVal && props.cookieId) {
// 编辑模式 - 需要获取完整的 cookie 数据
try {
const response = await jdCookieClient.apiJdCookieAccountGetGet({
cookieId: props.cookieId
});
const account = response.data.account;
if (account) {
Object.assign(formModel, {
cookieValue: account.cookieId || '', // 这里的 cookieId 实际存储的是完整的 cookie 值
accountName: account.accountName || '',
status: account.status || 1,
remark: account.remark || ''
});
}
} catch (error) {
// 如果获取失败,使用传入的数据作为后备
if (props.cookieData) {
Object.assign(formModel, {
cookieValue: props.cookieData.cookieId || '',
accountName: props.cookieData.accountName || '',
status: props.cookieData.status || 1,
remark: props.cookieData.remark || ''
});
}
}
} else if (newVal) {
// 添加模式
Object.assign(formModel, {
@@ -103,45 +115,48 @@ watch(
);
// 提交表单
const handleSubmit = async (): Promise<boolean> => {
const valid = await formRef.value?.validate();
if (!valid) return false;
const handleSubmit = (done: (closed: boolean) => void) => {
formRef.value?.validate().then(async (res: any) => {
if (res) return done(false);
try {
loading.value = true;
loading.value = true;
try {
if (props.cookieId) {
// 编辑模式
const updateData: KamiApiJdCookieV1UpdateAccountReq = {
cookieId: props.cookieId,
cookieValue: formModel.cookieValue,
accountName: formModel.accountName,
status: formModel.status,
remark: formModel.remark
};
await jdCookieClient.apiJdCookieAccountUpdatePut({
kamiApiJdCookieV1UpdateAccountReq: updateData
});
} else {
// 添加模式
const createData: KamiApiJdCookieV1CreateAccountReq = {
cookieValue: formModel.cookieValue,
accountName: formModel.accountName,
remark: formModel.remark
};
await jdCookieClient.apiJdCookieAccountCreatePost({
kamiApiJdCookieV1CreateAccountReq: createData
});
if (props.cookieId) {
// 编辑模式
const updateData: KamiApiJdCookieV1UpdateAccountReq = {
cookieId: props.cookieId,
cookieValue: formModel.cookieValue,
accountName: formModel.accountName,
status: formModel.status,
remark: formModel.remark
};
await jdCookieClient.apiJdCookieAccountUpdatePut({
kamiApiJdCookieV1UpdateAccountReq: updateData
});
} else {
// 添加模式
const createData: KamiApiJdCookieV1CreateAccountReq = {
cookieValue: formModel.cookieValue,
accountName: formModel.accountName,
remark: formModel.remark
};
await jdCookieClient.apiJdCookieAccountCreatePost({
kamiApiJdCookieV1CreateAccountReq: createData
});
}
Message.success(props.cookieId ? '修改成功' : '添加成功');
emit('success');
done(true);
emit('update:visible', false);
} catch (error) {
console.error('提交失败:', error);
Message.error(props.cookieId ? '修改失败' : '添加失败');
done(false);
} finally {
loading.value = false;
}
Message.success(props.cookieId ? '修改成功' : '添加成功');
emit('success');
return true;
} catch (error) {
Message.error(props.cookieId ? '修改失败' : '添加失败');
return false;
} finally {
loading.value = false;
}
});
};
// 取消

View File

@@ -1,285 +0,0 @@
<template>
<div class="container">
<Breadcrumb :items="['京东订单管理', 'Cookie变更历史']" />
<a-card class="general-card" title="Cookie变更历史">
<a-row>
<a-col :flex="1">
<a-form
:model="formModel"
:label-col-props="{ span: 6 }"
:wrapper-col-props="{ span: 18 }"
label-align="left"
>
<a-row :gutter="16">
<a-col :span="8">
<a-form-item field="cookieId" label="Cookie ID">
<a-input
v-model="formModel.cookieId"
placeholder="请输入Cookie ID"
/>
</a-form-item>
</a-col>
<a-col :span="8">
<a-form-item field="changeType" label="变更类型">
<a-select
v-model="formModel.changeType"
placeholder="请选择变更类型"
allow-clear
>
<a-option value="create">创建</a-option>
<a-option value="update">更新</a-option>
<a-option value="status_change">状态变更</a-option>
<a-option value="balance_change">余额变更</a-option>
<a-option value="delete">删除</a-option>
</a-select>
</a-form-item>
</a-col>
</a-row>
</a-form>
</a-col>
<a-divider style="height: 42px" direction="vertical" />
<a-col flex="172px" style="text-align: right">
<a-space direction="horizontal" :size="18">
<a-button type="primary" @click="search">
<template #icon>
<icon-search />
</template>
搜索
</a-button>
<a-button @click="reset">
<template #icon>
<icon-refresh />
</template>
重置
</a-button>
</a-space>
</a-col>
</a-row>
<a-divider style="margin-top: 0" />
<a-timeline>
<a-timeline-item
v-for="record in renderData"
:key="record.historyUuid"
:dot-color="getTimelineColor(record.changeType)"
>
<template #dot>
<icon-clock-circle v-if="record.changeType === 'create'" />
<icon-edit v-else-if="record.changeType === 'update'" />
<icon-exclamation-circle
v-else-if="record.changeType === 'status_change'"
/>
<icon-transaction
v-else-if="record.changeType === 'balance_change'"
/>
<icon-delete v-else-if="record.changeType === 'delete'" />
<icon-info-circle v-else />
</template>
<div class="timeline-content">
<div class="timeline-header">
<span class="timeline-title">
{{ getChangeTypeText(record.changeType) }}
</span>
<span class="timeline-time">{{ record.createdAt }}</span>
</div>
<div class="timeline-body">
<a-descriptions :column="1" size="small">
<a-descriptions-item label="Cookie ID">
<a-typography-text copyable>
{{ record.cookieId }}
</a-typography-text>
</a-descriptions-item>
<a-descriptions-item label="变更类型">
{{ record.changeType }}
</a-descriptions-item>
<a-descriptions-item
v-if="record.statusBefore"
label="变更前状态"
>
<a-tag :color="record.statusBefore === 1 ? 'green' : 'red'">
{{ record.statusBefore === 1 ? '正常' : '失效' }}
</a-tag>
</a-descriptions-item>
<a-descriptions-item
v-if="record.statusAfter"
label="变更后状态"
>
<a-tag :color="record.statusAfter === 1 ? 'green' : 'red'">
{{ record.statusAfter === 1 ? '正常' : '失效' }}
</a-tag>
</a-descriptions-item>
<a-descriptions-item v-if="record.userOrderId" label="关联订单">
{{ record.userOrderId }}
</a-descriptions-item>
</a-descriptions>
</div>
</div>
</a-timeline-item>
</a-timeline>
<div class="pagination-wrapper">
<a-pagination
:total="pagination.total"
:current="pagination.current"
:page-size="pagination.pageSize"
:page-size-options="[10, 20, 50, 100]"
show-page-size
@change="onPageChange"
@page-size-change="onPageSizeChange"
/>
</div>
</a-card>
</div>
</template>
<script lang="ts" setup>
import { ref, reactive, onMounted } from 'vue';
import { Message } from '@arco-design/web-vue';
import useLoading from '@/hooks/loading';
import { Pagination } from '@/types/global';
import { jdHistoryClient } from '@/api';
import type {
KamiApiJdCookieV1CookieHistoryRes,
KamiApiJdCookieV1CookieHistoryInfo
} from '@/api/generated';
const { loading, setLoading } = useLoading(true);
// 分页配置
const basePagination: Pagination = {
current: 1,
pageSize: 20
};
const pagination = reactive({ ...basePagination });
// 类型定义
interface FormModel {
cookieId: string;
changeType: string;
}
interface QueryParams {
current?: number;
pageSize?: number;
cookieId?: string;
changeType?: string;
}
// 数据状态
const renderData = ref<KamiApiJdCookieV1CookieHistoryInfo[]>([]);
const formModel = reactive<FormModel>({
cookieId: '',
changeType: ''
});
// 获取变更记录列表
const fetchData = async (params: QueryParams = {}) => {
setLoading(true);
try {
const response = await jdHistoryClient.apiJdCookieHistoryCookieGet({
cookieId: params.cookieId || undefined,
page: params.current || pagination.current,
size: params.pageSize || pagination.pageSize,
changeType: params.changeType || undefined
});
renderData.value = response.data.list || [];
pagination.total = response.data.total || 0;
} catch (error) {
Message.error('获取变更历史失败');
} finally {
setLoading(false);
}
};
// 搜索
const search = (): void => {
fetchData({
current: 1,
...formModel
});
};
// 重置
const reset = (): void => {
formModel.cookieId = '';
formModel.changeType = '';
search();
};
// 分页变化
const onPageChange = (current: number): void => {
pagination.current = current;
fetchData(formModel);
};
const onPageSizeChange = (pageSize: number): void => {
pagination.pageSize = pageSize;
pagination.current = 1;
fetchData(formModel);
};
// 获取时间线颜色
const getTimelineColor = (changeType: string): string => {
const colors: Record<string, string> = {
create: '#00b42a',
update: '#165dff',
status_change: '#ff7d00',
balance_change: '#722ed1',
delete: '#f53f3f'
};
return colors[changeType] || '#86909c';
};
// 获取变更类型文本
const getChangeTypeText = (changeType: string): string => {
const texts: Record<string, string> = {
create: '创建Cookie',
update: '更新Cookie',
status_change: '状态变更',
balance_change: '余额变更',
delete: '删除Cookie'
};
return texts[changeType] || '未知操作';
};
onMounted(() => {
search();
});
</script>
<style scoped>
.timeline-content {
margin-left: 16px;
}
.timeline-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 8px;
}
.timeline-title {
font-weight: 500;
color: var(--color-text-1);
}
.timeline-time {
color: var(--color-text-3);
font-size: 12px;
}
.timeline-body {
background-color: var(--color-fill-1);
padding: 12px;
border-radius: 4px;
border-left: 3px solid var(--color-primary-light-3);
}
.pagination-wrapper {
display: flex;
justify-content: center;
margin-top: 20px;
}
</style>

View File

@@ -15,7 +15,7 @@
<a-form-item field="keyword" label="关键字">
<a-input
v-model="formModel.keyword"
placeholder="Cookie/昵称/邮箱"
placeholder="Cookie ID/账户名称"
/>
</a-form-item>
</a-col>
@@ -27,8 +27,8 @@
allow-clear
>
<a-option :value="1">正常</a-option>
<a-option :value="0">失效</a-option>
<a-option :value="2">异常</a-option>
<a-option :value="2">暂停</a-option>
<a-option :value="3">失效</a-option>
</a-select>
</a-form-item>
</a-col>
@@ -67,16 +67,11 @@
</template>
添加Cookie
</a-button>
<a-button
type="primary"
size="small"
:loading="batchChecking"
@click="batchCheckStatus"
>
<a-button type="secondary" size="small" @click="showBatchAddModal">
<template #icon>
<icon-check-circle />
<icon-plus-circle />
</template>
批量检查
批量添加Cookie
</a-button>
</a-space>
</a-row>
@@ -97,18 +92,10 @@
>
<template #status="{ record }">
<a-tag v-if="record.status === 1" color="green">正常</a-tag>
<a-tag v-else-if="record.status === 0" color="red">失效</a-tag>
<a-tag v-else-if="record.status === 2" color="orange">异常</a-tag>
<a-tag v-else-if="record.status === 2" color="orange">暂停</a-tag>
<a-tag v-else-if="record.status === 3" color="red">失效</a-tag>
<a-tag v-else color="gray">未知</a-tag>
</template>
<template #cookie="{ record }">
<a-tooltip :content="record.cookie">
<span>{{ record.cookie?.slice(0, 20) + '...' }}</span>
</a-tooltip>
</template>
<template #balance="{ record }">
<span class="arco-text-primary">¥{{ record.balance || '0.00' }}</span>
</template>
<template #operations="{ record }">
<a-space size="small">
<a-tooltip content="查看详情">
@@ -118,6 +105,13 @@
</template>
</a-button>
</a-tooltip>
<a-tooltip content="变更记录">
<a-button size="small" @click="showHistory(record)">
<template #icon>
<icon-history />
</template>
</a-button>
</a-tooltip>
<a-tooltip content="编辑">
<a-button
size="small"
@@ -129,17 +123,6 @@
</template>
</a-button>
</a-tooltip>
<a-tooltip content="查看历史">
<a-button
size="small"
status="success"
@click="showHistory(record)"
>
<template #icon>
<icon-history />
</template>
</a-button>
</a-tooltip>
<a-popconfirm
content="确认删除此Cookie吗"
@ok="deleteCookie(record.cookieId)"
@@ -171,11 +154,17 @@
:cookie-id="currentCookieId"
/>
<!-- 变更历史弹窗 -->
<cookie-history
<!-- Cookie变更记录侧边栏 -->
<cookie-history-drawer
v-model:visible="historyVisible"
:cookie-id="currentCookieId"
/>
<!-- 批量添加Cookie弹窗 -->
<batch-import-modal
v-model:visible="batchAddVisible"
@success="handleBatchAddSuccess"
/>
</div>
</template>
@@ -193,7 +182,8 @@ import type {
} from '@/api/generated';
import CookieModal from './components/cookie-modal.vue';
import CookieDetail from './components/cookie-detail.vue';
import CookieHistory from './components/cookie-history.vue';
import CookieHistoryDrawer from './components/cookie-history-drawer.vue';
import BatchImportModal from './components/batch-import-modal.vue';
const { loading, setLoading } = useLoading(true);
@@ -214,36 +204,34 @@ const columns: TableColumnData[] = [
return rowIndex + 1 + (pagination.current - 1) * pagination.pageSize;
}
},
// {
// title: 'Cookie ID',
// dataIndex: 'cookieId',
// width: 200,
// ellipsis: true,
// tooltip: true
// },
{
title: 'Cookie ID',
dataIndex: 'cookieId',
width: 200,
title: '账户名称',
dataIndex: 'accountName',
width: 150,
ellipsis: true,
tooltip: true
},
{
title: 'Cookie内容',
dataIndex: 'cookie',
slotName: 'cookie',
width: 200
},
{
title: '账户昵称',
dataIndex: 'nickname',
title: '连续失败次数',
dataIndex: 'failureCount',
width: 120
},
{
title: '邮箱',
dataIndex: 'email',
width: 180,
ellipsis: true,
tooltip: true
title: '最后使用时间',
dataIndex: 'lastUsedAt',
width: 180
},
{
title: '余额',
dataIndex: 'balance',
slotName: 'balance',
width: 100
title: '暂停解除时间',
dataIndex: 'suspendUntil',
width: 180
},
{
title: '状态',
@@ -256,6 +244,13 @@ const columns: TableColumnData[] = [
dataIndex: 'createdAt',
width: 180
},
{
title: '备注',
dataIndex: 'remark',
width: 200,
ellipsis: true,
tooltip: true
},
{
title: '操作',
dataIndex: 'operations',
@@ -289,9 +284,9 @@ const formModel = reactive<FormModel>({
const modalVisible = ref(false);
const detailVisible = ref(false);
const historyVisible = ref(false);
const batchAddVisible = ref(false);
const currentCookieId = ref('');
const currentCookie = ref<KamiApiJdCookieV1CookieAccountInfo | null>(null);
const batchChecking = ref(false);
// 获取Cookie列表
const fetchData = async (params: QueryParams = {}) => {
@@ -340,24 +335,6 @@ const onPageSizeChange = (pageSize: number): void => {
fetchData(formModel);
};
// 批量检查状态
const batchCheckStatus = async (): Promise<void> => {
batchChecking.value = true;
try {
await jdCookieClient.apiJdCookieAccountBatchCheckPost({
kamiApiJdCookieV1BatchCheckReq: {
cookieIds: renderData.value.map(item => item.cookieId)
}
});
Message.success('批量检查完成');
search();
} catch (error) {
Message.error('批量检查失败');
} finally {
batchChecking.value = false;
}
};
// 显示添加弹窗
const showAddModal = (): void => {
currentCookieId.value = '';
@@ -378,7 +355,7 @@ const showDetail = (record: KamiApiJdCookieV1CookieAccountInfo): void => {
detailVisible.value = true;
};
// 显示历史
// 显示变更记录
const showHistory = (record: KamiApiJdCookieV1CookieAccountInfo): void => {
currentCookieId.value = record.cookieId;
historyVisible.value = true;
@@ -395,11 +372,21 @@ const deleteCookie = async (cookieId: string): Promise<void> => {
}
};
// 显示批量添加弹窗
const showBatchAddModal = (): void => {
batchAddVisible.value = true;
};
// 弹窗成功回调
const handleModalSuccess = (): void => {
search();
};
// 批量添加成功回调
const handleBatchAddSuccess = (): void => {
search();
};
onMounted(() => {
search();
});

View File

@@ -0,0 +1,399 @@
<template>
<div class="container">
<Breadcrumb :items="['京东订单管理', '京东订单']" />
<a-card class="general-card" title="京东订单查询">
<a-row>
<a-col :flex="1">
<a-form
:model="formModel"
:label-col-props="{ span: 6 }"
:wrapper-col-props="{ span: 18 }"
label-align="left"
>
<a-row :gutter="16">
<a-col :span="8">
<a-form-item field="keyword" label="订单号">
<a-input
v-model="formModel.keyword"
placeholder="请输入订单号"
/>
</a-form-item>
</a-col>
<a-col :span="8">
<a-form-item field="status" label="订单状态">
<a-select
v-model="formModel.status"
placeholder="请选择订单状态"
allow-clear
>
<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>
<a-col :span="8">
<a-form-item field="dateRange" label="时间范围">
<a-range-picker
v-model="formModel.dateRange"
:shortcuts="dateRangeShortcuts"
format="YYYY-MM-DD"
/>
</a-form-item>
</a-col>
</a-row>
</a-form>
</a-col>
<a-divider style="height: 42px" direction="vertical" />
<a-col flex="172px" style="text-align: right">
<a-space direction="horizontal" :size="18">
<a-button type="primary" @click="search">
<template #icon>
<icon-search />
</template>
搜索
</a-button>
<a-button @click="reset">
<template #icon>
<icon-refresh />
</template>
重置
</a-button>
</a-space>
</a-col>
</a-row>
<a-divider style="margin-top: 0" />
<a-table
:loading="loading"
:pagination="{
current: pagination.current,
pageSize: pagination.pageSize,
total: pagination.total,
pageSizeOptions: [10, 20, 50, 100],
showPageSize: true
}"
:columns="columns"
:data="renderData"
:scroll="{ x: 1200 }"
@page-change="onPageChange"
@page-size-change="onPageSizeChange"
>
<template #status="{ record }">
<a-tag v-if="record.status === 1" color="orange">待支付</a-tag>
<a-tag v-else-if="record.status === 2" color="green">已支付</a-tag>
<a-tag v-else-if="record.status === 3" color="red">已过期</a-tag>
<a-tag v-else-if="record.status === 4" color="gray">已取消</a-tag>
<a-tag v-else color="gray">未知</a-tag>
</template>
<template #amount="{ record }">
<span class="arco-text-primary">
¥{{ record.amount?.toFixed(2) || '0.00' }}
</span>
</template>
<template #operations="{ record }">
<a-space size="small">
<a-tooltip content="查看详情">
<a-button size="small" @click="showDetail(record)">
<template #icon>
<icon-eye />
</template>
</a-button>
</a-tooltip>
</a-space>
</template>
</a-table>
</a-card>
<!-- 订单详情弹窗 -->
<a-modal
v-model:visible="detailVisible"
title="京东订单详情"
width="800px"
:footer="false"
>
<div v-if="currentOrder" class="order-detail">
<a-descriptions :column="2" bordered>
<a-descriptions-item label="京东订单号">
{{ currentOrder.jdOrderId }}
</a-descriptions-item>
<a-descriptions-item label="内部订单号">
{{ currentOrder.orderId }}
</a-descriptions-item>
<a-descriptions-item label="商品品类">
{{ currentOrder.category }}
</a-descriptions-item>
<a-descriptions-item label="订单金额">
<span class="arco-text-primary">
¥{{ currentOrder.amount?.toFixed(2) || '0.00' }}
</span>
</a-descriptions-item>
<a-descriptions-item label="订单状态">
<a-tag :color="getStatusColor(currentOrder.status)">
{{ getStatusText(currentOrder.status) }}
</a-tag>
</a-descriptions-item>
<a-descriptions-item label="支付时间">
{{ currentOrder.paidAt || '-' }}
</a-descriptions-item>
<a-descriptions-item label="创建时间">
{{ currentOrder.createdAt }}
</a-descriptions-item>
<a-descriptions-item label="更新时间">
{{ currentOrder.updatedAt }}
</a-descriptions-item>
</a-descriptions>
</div>
</a-modal>
</div>
</template>
<script lang="ts" setup>
import { ref, reactive, onMounted } from 'vue';
import { Message } from '@arco-design/web-vue';
import type { TableColumnData } from '@arco-design/web-vue';
import useLoading from '@/hooks/loading';
import { Pagination } from '@/types/global';
import { jdOrderClient } from '@/api';
import type { KamiApiJdCookieV1JdOrderInfo } from '@/api/generated';
const { loading, setLoading } = useLoading(true);
// 分页配置
const basePagination: Pagination = {
current: 1,
pageSize: 20
};
const pagination = reactive({ ...basePagination });
// 日期范围快捷选项
const dateRangeShortcuts = [
{
label: '今天',
value: () => {
const today = new Date();
return [today, today];
}
},
{
label: '最近7天',
value: () => {
const end = new Date();
const start = new Date();
start.setDate(start.getDate() - 6);
return [start, end];
}
},
{
label: '最近30天',
value: () => {
const end = new Date();
const start = new Date();
start.setDate(start.getDate() - 29);
return [start, end];
}
}
];
// 表格列配置
const columns: TableColumnData[] = [
{
title: '序号',
dataIndex: 'index',
width: 80,
render: ({ rowIndex }) => {
return rowIndex + 1 + (pagination.current - 1) * pagination.pageSize;
}
},
{
title: '京东订单号',
dataIndex: 'jdOrderId',
width: 180,
ellipsis: true,
tooltip: true
},
{
title: '内部订单号',
dataIndex: 'orderId',
width: 180,
ellipsis: true,
tooltip: true
},
{
title: '商品品类',
dataIndex: 'category',
width: 150,
ellipsis: true,
tooltip: true
},
{
title: '订单金额',
dataIndex: 'amount',
slotName: 'amount',
width: 120
},
{
title: '订单状态',
dataIndex: 'status',
slotName: 'status',
width: 100
},
{
title: '创建时间',
dataIndex: 'createdAt',
width: 180
},
{
title: '操作',
dataIndex: 'operations',
slotName: 'operations',
width: 120,
fixed: 'right'
}
];
// 类型定义
interface FormModel {
keyword: string;
status: number | undefined;
dateRange: (Date | undefined)[] | undefined;
}
interface QueryParams {
current?: number;
pageSize?: number;
keyword?: string;
status?: number;
startTime?: string;
endTime?: string;
}
// 数据状态
const renderData = ref<KamiApiJdCookieV1JdOrderInfo[]>([]);
const formModel = reactive<FormModel>({
keyword: '',
status: undefined,
dateRange: undefined
});
// 弹窗状态
const detailVisible = ref(false);
const currentOrder = ref<KamiApiJdCookieV1JdOrderInfo | null>(null);
// 获取订单列表
const fetchData = async (params: QueryParams = {}) => {
setLoading(true);
try {
const response = await jdOrderClient.apiJdCookieOrderJdListGet({
page: params.current || pagination.current,
size: params.pageSize || pagination.pageSize,
orderId: params.keyword,
status: params.status,
startTime: params.startTime,
endTime: params.endTime
});
renderData.value = response.data.list || [];
pagination.total = response.data.total || 0;
} catch (error) {
Message.error('获取订单列表失败');
} finally {
setLoading(false);
}
};
// 搜索
const search = (): void => {
const params: QueryParams = {
current: 1,
keyword: formModel.keyword,
status: formModel.status
};
if (formModel.dateRange && formModel.dateRange.length === 2) {
params.startTime = formModel.dateRange[0]?.toISOString().split('T')[0];
params.endTime = formModel.dateRange[1]?.toISOString().split('T')[0];
}
fetchData(params);
};
// 重置
const reset = (): void => {
formModel.keyword = '';
formModel.status = undefined;
formModel.dateRange = undefined;
search();
};
// 分页变化
const onPageChange = (current: number): void => {
pagination.current = current;
const params: QueryParams = {
keyword: formModel.keyword,
status: formModel.status
};
if (formModel.dateRange && formModel.dateRange.length === 2) {
params.startTime = formModel.dateRange[0]?.toISOString().split('T')[0];
params.endTime = formModel.dateRange[1]?.toISOString().split('T')[0];
}
fetchData(params);
};
const onPageSizeChange = (pageSize: number): void => {
pagination.pageSize = pageSize;
pagination.current = 1;
const params: QueryParams = {
keyword: formModel.keyword,
status: formModel.status
};
if (formModel.dateRange && formModel.dateRange.length === 2) {
params.startTime = formModel.dateRange[0]?.toISOString().split('T')[0];
params.endTime = formModel.dateRange[1]?.toISOString().split('T')[0];
}
fetchData(params);
};
// 获取状态颜色
const getStatusColor = (status: number): string => {
const colors: Record<number, string> = {
1: 'orange', // 待支付
2: 'green', // 已支付
3: 'red', // 已过期
4: 'gray' // 已取消
};
return colors[status] || 'gray';
};
// 获取状态文本
const getStatusText = (status: number): string => {
const statusMap: Record<number, string> = {
1: '待支付',
2: '已支付',
3: '已过期',
4: '已取消'
};
return statusMap[status] || '未知';
};
// 显示详情
const showDetail = (record: KamiApiJdCookieV1JdOrderInfo): void => {
currentOrder.value = record;
detailVisible.value = true;
};
onMounted(() => {
search();
});
</script>
<style scoped>
.order-detail {
padding: 16px 0;
}
</style>

View File

@@ -1,148 +0,0 @@
<template>
<a-button @click="handleClick">
<template #icon>
<icon-upload />
</template>
批量导入订单
</a-button>
<a-modal
v-model:visible="visible"
title="批量导入订单"
width="600px"
@cancel="handleCancel"
@before-ok="handleSubmit"
>
<a-form :model="formModel" layout="vertical">
<a-form-item label="订单数据">
<a-textarea
v-model="formModel.orderData"
placeholder="每行一个订单,格式:订单号,商品品类,金额,京东订单号&#10;例如ORDER001,数码产品,299.99,JD123456789"
:auto-size="{ minRows: 6, maxRows: 12 }"
placeholder-class="batch-textarea"
/>
<div class="tip-text">
<icon-info-circle />
<span>
支持批量粘贴每行一个订单格式订单号,商品品类,金额,京东订单号可选
</span>
</div>
</a-form-item>
</a-form>
</a-modal>
</template>
<script lang="ts" setup>
import { ref, reactive } from 'vue';
import { Message } from '@arco-design/web-vue';
import { jdOrderClient } from '@/api';
import type { KamiApiJdCookieV1CreateOrderReq } from '@/api/generated';
interface Emits {
(e: 'success'): void;
}
const emit = defineEmits<Emits>();
const visible = ref(false);
const loading = ref(false);
// 表单数据
const formModel = reactive({
orderData: ''
});
const handleClick = (): void => {
visible.value = true;
};
const handleCancel = (): void => {
visible.value = false;
formModel.orderData = '';
};
const handleSubmit = async (): Promise<boolean> => {
if (!formModel.orderData.trim()) {
Message.warning('请输入订单数据');
return false;
}
loading.value = true;
try {
// 解析批量数据
const lines = formModel.orderData
.trim()
.split('\n')
.filter(line => line.trim());
const orders = lines
.map(line => {
const parts = line.split(',').map(part => part.trim());
return {
orderId: parts[0] || '',
category: parts[1] || '',
amount: parts[2] ? parseFloat(parts[2]) : 0,
jdOrderId: parts[3] || '',
remark: `批量导入于 ${new Date().toLocaleString()}`
};
})
.filter(order => order.orderId && order.category && order.amount > 0);
if (orders.length === 0) {
Message.warning('没有有效的订单数据');
return false;
}
// 逐个创建订单
let successCount = 0;
for (const order of orders) {
try {
const createData: KamiApiJdCookieV1CreateOrderReq = {
orderId: order.orderId,
category: order.category,
amount: order.amount
};
await jdOrderClient.apiJdCookieOrderCreatePost({
kamiApiJdCookieV1CreateOrderReq: createData
});
successCount++;
} catch (error) {
console.error(`创建订单失败: ${order.orderId}`, error);
}
}
if (successCount > 0) {
Message.success(
`成功导入 ${successCount} 个订单${successCount < orders.length ? `,失败 ${orders.length - successCount}` : ''}`
);
emit('success');
return true;
} else {
Message.error('批量导入失败,所有订单都创建失败');
return false;
}
} catch (error) {
Message.error('批量导入失败');
return false;
} finally {
loading.value = false;
}
};
</script>
<style scoped>
.tip-text {
display: flex;
align-items: center;
gap: 4px;
color: var(--color-text-3);
font-size: 12px;
margin-top: 8px;
}
.batch-textarea {
font-family: monospace;
font-size: 12px;
}
</style>

View File

@@ -1,257 +0,0 @@
<template>
<a-modal
:visible="visible"
title="订单变更历史"
width="800px"
:footer="false"
@cancel="handleCancel"
>
<div v-if="orderId" class="order-history">
<a-timeline :loading="loading">
<a-timeline-item
v-for="record in renderData"
:key="record.historyUuid"
:dot-color="getTimelineColor(record.changeType)"
>
<template #dot>
<icon-clock-circle v-if="record.changeType === 'create'" />
<icon-edit v-else-if="record.changeType === 'update'" />
<icon-exclamation-circle
v-else-if="record.changeType === 'status_change'"
/>
<icon-transaction
v-else-if="record.changeType === 'payment_check'"
/>
<icon-link
v-else-if="record.changeType === 'payment_url_generated'"
/>
<icon-delete v-else-if="record.changeType === 'cancel'" />
<icon-info-circle v-else />
</template>
<div class="timeline-content">
<div class="timeline-header">
<span class="timeline-title">
{{ getChangeTypeText(record.changeType) }}
</span>
<span class="timeline-time">{{ record.createdAt }}</span>
</div>
<div class="timeline-body">
<a-descriptions :column="1" size="small">
<a-descriptions-item label="订单号">
<a-typography-text copyable>
{{ record.orderId }}
</a-typography-text>
</a-descriptions-item>
<a-descriptions-item v-if="record.jdOrderId" label="京东订单号">
<a-typography-text copyable>
{{ record.jdOrderId }}
</a-typography-text>
</a-descriptions-item>
<a-descriptions-item v-if="record.wxPayUrl" label="支付链接">
<a-link
:href="record.wxPayUrl"
target="_blank"
type="primary"
>
<template #icon>
<icon-link />
</template>
打开链接
</a-link>
</a-descriptions-item>
</a-descriptions>
</div>
</div>
</a-timeline-item>
</a-timeline>
<div v-if="renderData.length === 0 && !loading" class="empty-wrapper">
<a-empty description="暂无变更历史" />
</div>
<div v-if="renderData.length > 0" class="pagination-wrapper">
<a-pagination
:total="pagination.total"
:current="pagination.current"
:page-size="pagination.pageSize"
:page-size-options="[10, 20, 50]"
show-page-size
size="small"
@change="onPageChange"
@page-size-change="onPageSizeChange"
/>
</div>
</div>
<a-empty v-else description="暂无订单数据" />
</a-modal>
</template>
<script lang="ts" setup>
import { ref, reactive, watch } from 'vue';
import { Message } from '@arco-design/web-vue';
import { jdHistoryClient } from '@/api';
import { ApiJdCookieHistoryOrderGetOrderTypeEnum } from '@/api/generated';
import type {
KamiApiJdCookieV1OrderHistoryRes,
KamiApiJdCookieV1OrderHistoryInfo
} from '@/api/generated';
interface Props {
visible: boolean;
orderId: string;
}
interface Emits {
(e: 'update:visible', value: boolean): void;
}
const props = defineProps<Props>();
const emit = defineEmits<Emits>();
const loading = ref(false);
// 处理取消
const handleCancel = (): void => {
emit('update:visible', false);
};
const renderData = ref<KamiApiJdCookieV1OrderHistoryInfo[]>([]);
// 分页配置
const pagination = reactive({
current: 1,
pageSize: 20,
total: 0
});
// 获取订单变更历史
const fetchData = async () => {
if (!props.orderId) return;
loading.value = true;
try {
const response = await jdHistoryClient.apiJdCookieHistoryOrderGet({
orderId: props.orderId,
orderType: ApiJdCookieHistoryOrderGetOrderTypeEnum.User,
page: pagination.current,
size: pagination.pageSize
});
renderData.value = response.data.list || [];
pagination.total = response.data.total || 0;
} catch (error) {
Message.error('获取订单变更历史失败');
} finally {
loading.value = false;
}
};
// 分页变化
const onPageChange = (current: number): void => {
pagination.current = current;
fetchData();
};
const onPageSizeChange = (pageSize: number): void => {
pagination.pageSize = pageSize;
pagination.current = 1;
fetchData();
};
// 获取时间线颜色
const getTimelineColor = (changeType: string): string => {
const colors: Record<string, string> = {
create: '#00b42a',
update: '#165dff',
status_change: '#ff7d00',
payment_check: '#722ed1',
payment_url_generated: '#14c9c9',
cancel: '#f53f3f'
};
return colors[changeType] || '#86909c';
};
// 获取变更类型文本
const getChangeTypeText = (changeType: string): string => {
const texts: Record<string, string> = {
create: '创建订单',
update: '更新订单',
status_change: '状态变更',
payment_check: '检查支付',
payment_url_generated: '生成支付链接',
cancel: '取消订单'
};
return texts[changeType] || '未知操作';
};
// 监听orderId变化
watch(
() => props.orderId,
newOrderId => {
if (newOrderId && props.visible) {
pagination.current = 1;
fetchData();
}
},
{ immediate: true }
);
// 监听弹窗显示状态
watch(
() => props.visible,
visible => {
if (visible && props.orderId) {
pagination.current = 1;
fetchData();
}
}
);
</script>
<style scoped>
.order-history {
padding: 16px 0;
max-height: 600px;
overflow-y: auto;
}
.timeline-content {
margin-left: 16px;
}
.timeline-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 8px;
}
.timeline-title {
font-weight: 500;
color: var(--color-text-1);
}
.timeline-time {
color: var(--color-text-3);
font-size: 12px;
}
.timeline-body {
background-color: var(--color-fill-1);
padding: 12px;
border-radius: 4px;
border-left: 3px solid var(--color-primary-light-3);
}
.empty-wrapper {
text-align: center;
padding: 40px 0;
}
.pagination-wrapper {
display: flex;
justify-content: center;
margin-top: 20px;
padding-top: 16px;
border-top: 1px solid var(--color-border-2);
}
</style>

View File

@@ -1,104 +0,0 @@
<template>
<a-modal
:visible="visible"
title="创建订单"
width="600px"
@before-ok="handleSubmit"
@cancel="handleCancel"
>
<a-form ref="formRef" :model="formModel" :rules="rules" layout="vertical">
<a-form-item field="category" label="商品品类" required>
<a-input v-model="formModel.category" placeholder="请输入商品品类" />
</a-form-item>
<a-form-item field="amount" label="订单金额" required>
<a-input-number
v-model="formModel.amount"
placeholder="请输入订单金额"
:precision="2"
:min="0.01"
style="width: 100%"
>
<template #prefix>¥</template>
</a-input-number>
</a-form-item>
</a-form>
</a-modal>
</template>
<script lang="ts" setup>
import { ref, reactive } from 'vue';
import { Message, type FormInstance } from '@arco-design/web-vue';
import { jdOrderClient } from '@/api';
import type { KamiApiJdCookieV1CreateOrderReq } from '@/api/generated';
interface Props {
visible: boolean;
}
interface Emits {
(e: 'update:visible', value: boolean): void;
(e: 'success'): void;
}
const props = defineProps<Props>();
const emit = defineEmits<Emits>();
const formRef = ref<FormInstance>();
const loading = ref(false);
// 表单数据
const formModel = reactive({
category: '',
amount: 0
});
// 表单验证规则
const rules: Record<string, any[]> = {
category: [
{ required: true, message: '请输入商品品类' },
{ minLength: 1, message: '商品品类不能为空' }
],
amount: [
{ required: true, message: '请输入订单金额' },
{ type: 'number', min: 0.01, message: '订单金额必须大于0' }
]
};
// 提交表单
const handleSubmit = async (): Promise<boolean> => {
const valid = await formRef.value?.validate();
if (!valid) return false;
loading.value = true;
try {
const createData: KamiApiJdCookieV1CreateOrderReq = {
orderId: `ORDER_${Date.now()}`,
category: formModel.category,
amount: formModel.amount
};
await jdOrderClient.apiJdCookieOrderCreatePost({
kamiApiJdCookieV1CreateOrderReq: createData
});
Message.success('创建订单成功');
emit('success');
return true;
} catch (error) {
Message.error('创建订单失败');
return false;
} finally {
loading.value = false;
}
};
// 取消
const handleCancel = (): void => {
emit('update:visible', false);
// 重置表单
Object.assign(formModel, {
category: '',
amount: 0
});
};
</script>

View File

@@ -1,378 +0,0 @@
<template>
<div class="container">
<Breadcrumb :items="['京东订单管理', '订单变更历史']" />
<a-card class="general-card" title="订单变更历史">
<a-row>
<a-col :flex="1">
<a-form
:model="formModel"
:label-col-props="{ span: 6 }"
:wrapper-col-props="{ span: 18 }"
label-align="left"
>
<a-row :gutter="16">
<a-col :span="8">
<a-form-item field="orderId" label="订单号">
<a-input
v-model="formModel.orderId"
placeholder="请输入订单号"
/>
</a-form-item>
</a-col>
<a-col :span="8">
<a-form-item field="orderType" label="订单类型">
<a-select
v-model="formModel.orderType"
placeholder="请选择订单类型"
allow-clear
>
<a-option value="user">用户订单</a-option>
<a-option value="jd">京东订单</a-option>
</a-select>
</a-form-item>
</a-col>
<a-col :span="8">
<a-form-item field="dateRange" label="时间范围">
<a-range-picker
v-model="formModel.dateRange"
:shortcuts="dateRangeShortcuts"
format="YYYY-MM-DD"
/>
</a-form-item>
</a-col>
</a-row>
</a-form>
</a-col>
<a-divider style="height: 42px" direction="vertical" />
<a-col flex="172px" style="text-align: right">
<a-space direction="horizontal" :size="18">
<a-button type="primary" @click="search">
<template #icon>
<icon-search />
</template>
搜索
</a-button>
<a-button @click="reset">
<template #icon>
<icon-refresh />
</template>
重置
</a-button>
</a-space>
</a-col>
</a-row>
<a-divider style="margin-top: 0" />
<div class="timeline-wrapper">
<a-timeline :loading="loading">
<a-timeline-item
v-for="record in renderData"
:key="record.historyUuid"
:dot-color="getTimelineColor(record.changeType)"
>
<template #dot>
<icon-clock-circle v-if="record.changeType === 'create'" />
<icon-edit v-else-if="record.changeType === 'update'" />
<icon-exclamation-circle
v-else-if="record.changeType === 'status_change'"
/>
<icon-transaction
v-else-if="record.changeType === 'payment_check'"
/>
<icon-link
v-else-if="record.changeType === 'payment_url_generated'"
/>
<icon-delete v-else-if="record.changeType === 'cancel'" />
<icon-info-circle v-else />
</template>
<div class="timeline-content">
<div class="timeline-header">
<span class="timeline-title">
{{ getChangeTypeText(record.changeType) }}
</span>
<span class="timeline-time">{{ record.createdAt }}</span>
</div>
<div class="timeline-body">
<a-descriptions :column="1" size="small">
<a-descriptions-item label="订单号">
<a-typography-text copyable>
{{ record.orderId }}
</a-typography-text>
</a-descriptions-item>
<a-descriptions-item
v-if="record.jdOrderId"
label="京东订单号"
>
<a-typography-text copyable>
{{ record.jdOrderId }}
</a-typography-text>
</a-descriptions-item>
<a-descriptions-item v-if="record.wxPayUrl" label="支付链接">
<a-link
:href="record.wxPayUrl"
target="_blank"
type="primary"
>
<template #icon>
<icon-link />
</template>
打开链接
</a-link>
</a-descriptions-item>
</a-descriptions>
</div>
</div>
</a-timeline-item>
</a-timeline>
<div v-if="renderData.length === 0 && !loading" class="empty-wrapper">
<a-empty description="暂无变更历史" />
</div>
</div>
<div class="pagination-wrapper">
<a-pagination
:total="pagination.total"
:current="pagination.current"
:page-size="pagination.pageSize"
:page-size-options="[10, 20, 50, 100]"
show-page-size
@change="onPageChange"
@page-size-change="onPageSizeChange"
/>
</div>
</a-card>
</div>
</template>
<script lang="ts" setup>
import { ref, reactive, onMounted } from 'vue';
import { Message } from '@arco-design/web-vue';
import useLoading from '@/hooks/loading';
import { Pagination } from '@/types/global';
import { jdHistoryClient } from '@/api';
import { ApiJdCookieHistoryOrderGetOrderTypeEnum } from '@/api/generated';
import type {
KamiApiJdCookieV1OrderHistoryRes,
KamiApiJdCookieV1OrderHistoryInfo
} from '@/api/generated';
const { loading, setLoading } = useLoading(true);
// 分页配置
const basePagination: Pagination = {
current: 1,
pageSize: 20
};
const pagination = reactive({ ...basePagination });
// 日期范围快捷选项
const dateRangeShortcuts = [
{
label: '今天',
value: () => {
const today = new Date();
return [today, today];
}
},
{
label: '最近7天',
value: () => {
const end = new Date();
const start = new Date();
start.setDate(start.getDate() - 6);
return [start, end];
}
},
{
label: '最近30天',
value: () => {
const end = new Date();
const start = new Date();
start.setDate(start.getDate() - 29);
return [start, end];
}
}
];
// 类型定义
interface FormModel {
orderId: string;
orderType: string;
dateRange: (Date | undefined)[] | undefined;
}
interface QueryParams {
current?: number;
pageSize?: number;
orderId?: string;
orderType?: string;
startTime?: string;
endTime?: string;
}
// 数据状态
const renderData = ref<KamiApiJdCookieV1OrderHistoryInfo[]>([]);
const formModel = reactive<FormModel>({
orderId: '',
orderType: '',
dateRange: undefined
});
// 获取变更记录列表
const fetchData = async (params: QueryParams = {}) => {
if (!params.orderId) {
renderData.value = [];
pagination.total = 0;
return;
}
setLoading(true);
try {
const orderType =
params.orderType === 'jd'
? ApiJdCookieHistoryOrderGetOrderTypeEnum.Jd
: ApiJdCookieHistoryOrderGetOrderTypeEnum.User;
const response = await jdHistoryClient.apiJdCookieHistoryOrderGet({
orderId: params.orderId,
orderType,
page: params.current || pagination.current,
size: params.pageSize || pagination.pageSize
});
renderData.value = response.data.list || [];
pagination.total = response.data.total || 0;
} catch (error) {
Message.error('获取变更历史失败');
} finally {
setLoading(false);
}
};
// 搜索
const search = (): void => {
if (!formModel.orderId) {
Message.warning('请输入订单号');
return;
}
const params: QueryParams = {
current: 1,
orderId: formModel.orderId,
orderType: formModel.orderType
};
fetchData(params);
};
// 重置
const reset = (): void => {
formModel.orderId = '';
formModel.orderType = '';
formModel.dateRange = undefined;
renderData.value = [];
pagination.total = 0;
};
// 分页变化
const onPageChange = (current: number): void => {
pagination.current = current;
const params: QueryParams = {
orderId: formModel.orderId,
orderType: formModel.orderType
};
fetchData(params);
};
const onPageSizeChange = (pageSize: number): void => {
pagination.pageSize = pageSize;
pagination.current = 1;
const params: QueryParams = {
orderId: formModel.orderId,
orderType: formModel.orderType
};
fetchData(params);
};
// 获取时间线颜色
const getTimelineColor = (changeType: string): string => {
const colors: Record<string, string> = {
create: '#00b42a',
update: '#165dff',
status_change: '#ff7d00',
payment_check: '#722ed1',
payment_url_generated: '#14c9c9',
cancel: '#f53f3f'
};
return colors[changeType] || '#86909c';
};
// 获取变更类型文本
const getChangeTypeText = (changeType: string): string => {
const texts: Record<string, string> = {
create: '创建订单',
update: '更新订单',
status_change: '状态变更',
payment_check: '检查支付',
payment_url_generated: '生成支付链接',
cancel: '取消订单'
};
return texts[changeType] || '未知操作';
};
onMounted(() => {
// 初始时不需要加载数据,需要用户输入订单号
});
</script>
<style scoped>
.timeline-wrapper {
max-height: 600px;
overflow-y: auto;
padding: 16px 0;
}
.timeline-content {
margin-left: 16px;
}
.timeline-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 8px;
}
.timeline-title {
font-weight: 500;
color: var(--color-text-1);
}
.timeline-time {
color: var(--color-text-3);
font-size: 12px;
}
.timeline-body {
background-color: var(--color-fill-1);
padding: 12px;
border-radius: 4px;
border-left: 3px solid var(--color-primary-light-3);
}
.empty-wrapper {
text-align: center;
padding: 80px 0;
}
.pagination-wrapper {
display: flex;
justify-content: center;
margin-top: 20px;
padding-top: 16px;
border-top: 1px solid var(--color-border-2);
}
</style>

View File

@@ -64,33 +64,6 @@
</a-col>
</a-row>
<a-divider style="margin-top: 0" />
<a-row style="margin-bottom: 16px">
<a-space>
<a-button
v-if="checkTokenFromLogin()"
type="primary"
size="small"
@click="showCreateModal()"
>
<template #icon>
<icon-plus />
</template>
创建订单
</a-button>
<batch-import-modal />
<a-button
type="primary"
size="small"
:loading="batchChecking"
@click="batchCheckPayment"
>
<template #icon>
<icon-check-circle />
</template>
批量检查支付
</a-button>
</a-space>
</a-row>
<a-table
:loading="loading"
:pagination="{
@@ -143,29 +116,6 @@
</template>
</a-button>
</a-tooltip>
<a-tooltip content="查看历史">
<a-button
size="small"
status="success"
@click="showHistory(record)"
>
<template #icon>
<icon-history />
</template>
</a-button>
</a-tooltip>
<a-tooltip content="检查支付">
<a-button
size="small"
status="warning"
:loading="checkingOrders.includes(record.orderId)"
@click="checkPayment(record)"
>
<template #icon>
<icon-check-circle />
</template>
</a-button>
</a-tooltip>
<a-tooltip content="获取支付链接">
<a-button
size="small"
@@ -183,17 +133,8 @@
</a-table>
</a-card>
<!-- 创建订单弹窗 -->
<order-modal v-model:visible="modalVisible" @success="handleModalSuccess" />
<!-- 订单详情弹窗 -->
<order-detail v-model:visible="detailVisible" :order-data="currentOrder" />
<!-- 订单历史弹窗 -->
<order-history
v-model:visible="historyVisible"
:order-id="currentOrderId"
/>
</div>
</template>
@@ -203,16 +144,12 @@ import { Message } from '@arco-design/web-vue';
import type { TableColumnData } from '@arco-design/web-vue';
import useLoading from '@/hooks/loading';
import { Pagination } from '@/types/global';
import { checkTokenFromLogin } from '@/utils/auth';
import { jdOrderClient, jdHistoryClient } from '@/api';
import type {
KamiApiJdCookieV1ListOrderRes,
KamiApiJdCookieV1OrderInfo
} from '@/api/generated';
import OrderModal from './components/order-modal.vue';
import OrderDetail from './components/order-detail.vue';
import OrderHistory from './components/order-history.vue';
import BatchImportModal from './components/batch-import-modal.vue';
const { loading, setLoading } = useLoading(true);
@@ -343,13 +280,8 @@ const formModel = reactive<FormModel>({
});
// 弹窗状态
const modalVisible = ref(false);
const detailVisible = ref(false);
const historyVisible = ref(false);
const currentOrder = ref<KamiApiJdCookieV1OrderInfo | null>(null);
const currentOrderId = ref('');
const batchChecking = ref(false);
const checkingOrders = ref<string[]>([]);
const paymentUrlLoading = ref<string[]>([]);
// 获取订单列表
@@ -429,60 +361,6 @@ const onPageSizeChange = (pageSize: number): void => {
fetchData(params);
};
// 批量检查支付
const batchCheckPayment = async (): Promise<void> => {
batchChecking.value = true;
try {
const orderIds = renderData.value
.filter(order => order.status === 1)
.map(order => order.orderId);
if (orderIds.length === 0) {
Message.warning('没有待支付的订单');
return;
}
await Promise.all(
orderIds.map(orderId =>
jdOrderClient.apiJdCookieOrderJdPaymentStatusPost({
kamiApiJdCookieV1CheckJdOrderPaymentReq: {
jd_order_id: orderId
}
})
)
);
Message.success('批量检查支付完成');
search();
} catch (error) {
Message.error('批量检查支付失败');
} finally {
batchChecking.value = false;
}
};
// 检查支付
const checkPayment = async (
record: KamiApiJdCookieV1OrderInfo
): Promise<void> => {
checkingOrders.value.push(record.orderId);
try {
await jdOrderClient.apiJdCookieOrderJdPaymentStatusPost({
kamiApiJdCookieV1CheckJdOrderPaymentReq: {
jd_order_id: record.orderId
}
});
Message.success('检查支付完成');
search();
} catch (error) {
Message.error('检查支付失败');
} finally {
checkingOrders.value = checkingOrders.value.filter(
id => id !== record.orderId
);
}
};
// 获取支付链接
const getPaymentUrl = async (
record: KamiApiJdCookieV1OrderInfo
@@ -509,28 +387,12 @@ const getPaymentUrl = async (
}
};
// 显示创建弹窗
const showCreateModal = (): void => {
modalVisible.value = true;
};
// 显示详情
const showDetail = (record: KamiApiJdCookieV1OrderInfo): void => {
currentOrder.value = record;
detailVisible.value = true;
};
// 显示历史
const showHistory = (record: KamiApiJdCookieV1OrderInfo): void => {
currentOrderId.value = record.orderId;
historyVisible.value = true;
};
// 弹窗成功回调
const handleModalSuccess = (): void => {
search();
};
onMounted(() => {
search();
});