feat(iframe): add AdminJdAccount and AdminJdOrder routes

Add new routes for managing JD accounts and orders in the admin interface. This includes the addition of new components for handling account details, order history, and summary views. The changes enable administrators to view and manage JD account and order data more effectively.
This commit is contained in:
danial
2025-03-31 12:28:54 +08:00
parent d3237e9350
commit 71c78c83be
8 changed files with 1968 additions and 0 deletions

View File

@@ -205,6 +205,26 @@ const IFRAME: AppRouteRecordRaw = {
requiresAuth: false,
roles: ['*']
}
},
{
path: 'AdminJdAccount',
name: 'iframeAdminJdAccount',
component: () => import('@/views/card-jd-ck/account/index.vue'),
meta: {
locale: '京东ck账户管理端',
requiresAuth: false,
roles: ['*']
}
},
{
path: 'AdminJdOrder',
name: 'iframeAdminJdOrder',
component: () => import('@/views/card-jd-ck/order/index.vue'),
meta: {
locale: '京东ck订单管理端',
requiresAuth: false,
roles: ['*']
}
}
]
};

View File

@@ -0,0 +1,237 @@
<template>
<a-modal
:visible="props.visible"
:ok-loading="loading"
draggable
@cancel="handleCancel"
>
<template #title>{{ props.id === '' ? '新增' : '编辑' }}Cookie</template>
<a-form ref="formDataRef" :model="formData">
<a-form-item field="name" label="名称" required>
<a-input v-model="formData.name" />
</a-form-item>
<a-form-item field="cookie" label="cookie" required>
<a-space
direction="vertical"
fill
style="width: -webkit-fill-available"
>
<a-textarea
v-model="formData.cookie"
:autoSize="{ minRows: 3, maxRows: 10 }"
/>
</a-space>
</a-form-item>
<a-form-item
field="maxAmountLimit"
label="充值限制(金额)"
tooltip="0表示没有限制"
required
>
<a-input-number v-model="formData.maxAmountLimit" />
</a-form-item>
<a-form-item
field="maxCountLimit"
label="充值限制(次数)"
tooltip="0表示没有限制"
required
>
<a-input-number v-model="formData.maxCountLimit" />
</a-form-item>
<a-form-item
field="status"
label="状态"
:disabled="![0, 1].includes(formData.status)"
>
<a-switch
v-model="formData.status"
:checked-value="1"
:unchecked-value="0"
/>
</a-form-item>
<a-form-item field="groupId" label="分组">
<a-select
v-model="formData.groupId"
placeholder="请选择分组"
:showSearch="true"
:filter-option="false"
:allowClear="true"
>
<a-option
v-for="(item, index) in groupList"
:key="index"
:value="item.id"
:label="item.name"
/>
</a-select>
</a-form-item>
<a-form-item field="remark" label="备注">
<a-textarea v-model="formData.remark" />
</a-form-item>
</a-form>
<template #footer>
<!-- <a-button
v-if="props.id === ''"
type="secondary"
status="warning"
:loading="renderData.loading"
@click="checkCookie"
>
检测Cookie
</a-button> -->
<a-space>
<a-button @click="handleCancel">取消</a-button>
<a-button type="primary" :loading="loading" @click="handleOk">
确定
</a-button>
<!-- <a-button
type="primary"
:loading="loading"
:disabled="!renderData.cookieStatus && props.id === ''"
@click="handleOk"
>
确定
</a-button> -->
</a-space>
</template>
</a-modal>
</template>
<script lang="ts" setup>
import useLoading from '@/hooks/loading';
import { FormInstance } from '@arco-design/web-vue';
import { ref, PropType, watch, reactive } from 'vue';
import { isNull } from '@/utils/is.ts';
import { apiClient } from '@/api/index.ts';
import {
addWalmartCard,
detectCookie,
updateWalmartCard
} from '@/api/card-walmart-account';
import { notification } from './component.tsx';
import {
KamiApiCardInfoWalmartV1AccountCreateReq,
KamiApiCardInfoWalmartV1AccountListRecord
} from '@/api/generated/index.ts';
import type { KamiInternalModelEntityV1CardRedeemAccountGroup } from '@/api/generated/index.ts';
const props = defineProps({
visible: {
type: Boolean,
default: false
},
id: {
type: String,
default: ''
},
account: {
type: Object as PropType<
Required<KamiApiCardInfoWalmartV1AccountListRecord>
>,
default: null
}
});
const generateFormData = () => {
return {
status: 1,
maxAmountLimit: 0,
maxCountLimit: 0,
cookie: '',
remark: '',
name: ''
};
};
const formData =
ref<KamiApiCardInfoWalmartV1AccountCreateReq>(generateFormData());
const formDataRef = ref<FormInstance>();
const { loading, setLoading } = useLoading(false);
const renderData = reactive<{ cookieStatus: boolean; loading: boolean }>({
cookieStatus: false,
loading: false
});
const emit = defineEmits(['update:visible']);
const groupList = ref<KamiInternalModelEntityV1CardRedeemAccountGroup[]>([]);
const handleOk = () => {
if (formData.value) {
formDataRef.value.validate().then(async res => {
if (res) return;
setLoading(true);
try {
if (props.id === '') {
await addWalmartCard(formData.value);
} else {
await updateWalmartCard({ id: props.id, ...formData.value });
}
emit('update:visible', false);
formData.value = generateFormData();
} catch (error) {
} finally {
setLoading(false);
}
});
}
};
watch(
() => props.account,
val => {
if (!isNull(val)) {
formData.value = { ...val };
}
}
);
const handleCancel = () => {
emit('update:visible', false);
formData.value = generateFormData();
};
watch(
() => formData.value.cookie,
val => {
renderData.cookieStatus = false;
}
);
const checkCookie = async () => {
renderData.loading = false;
try {
const cookieResp = await detectCookie({
cookie: formData.value.cookie.trim()
});
if (cookieResp.data.isAvailable) {
notification(
true,
cookieResp.data.nickname,
cookieResp.data.balance,
cookieResp.data.isExist
);
renderData.cookieStatus = true;
if (props.id === '') {
renderData.cookieStatus = !cookieResp.data.isExist;
}
} else {
notification(false);
}
} finally {
renderData.loading = false;
}
};
const getAllGroupList = () => {
apiClient.apiCardInfoWalmartGroupAllListGet().then(res => {
groupList.value = res.data.list;
});
};
getAllGroupList();
</script>
<style lang="less" scoped>
.footer {
display: flex;
justify-content: space-between;
margin-bottom: 10px;
}
</style>

View File

@@ -0,0 +1,249 @@
import { batchAdd, downloadWalmartCardData } from '@/api/card-walmart-account';
import type { KamiApiCardInfoWalmartV1AccountCookieCheckRes } from '@/api/generated';
import { getAPIBaseUrl } from '@/api/utils';
import useLoading from '@/hooks/loading';
import { getToken, getTokenFrom } from '@/utils/auth';
import {
Button,
Col,
type FileItem,
Message,
Modal,
Notification,
Row,
Space,
Table,
type TableColumnData,
type TableData,
Tag,
Upload
} from '@arco-design/web-vue';
import { isArray } from 'lodash';
import { defineComponent, reactive, ref } from 'vue';
const tableColumns: TableColumnData[] = [
{
title: '序号',
dataIndex: 'index',
render: (data: { rowIndex: number }) => {
return data.rowIndex + 1;
}
},
{
title: '名称',
dataIndex: 'name'
},
{
title: '昵称',
dataIndex: 'nickname'
},
{
title: '限额(金额)',
dataIndex: 'maxAmountLimit'
},
{
title: '限额(次数)',
dataIndex: 'maxCountLimit'
},
{
title: 'cookie',
dataIndex: 'cookie',
ellipsis: true,
tooltip: true
},
{
title: '是否存在系统内',
dataIndex: 'isExist',
render: (data: { record: TableData }) => {
let color = 'red';
let text = '不存在';
if (data.record.isExist) {
color = 'green';
text = '存在';
}
return (
<Tag size='small' color={color}>
{text}
</Tag>
);
}
},
{
title: '是否可用',
dataIndex: 'isAvailable',
render: (data: { record: TableData }) => {
let color = 'red';
let text = '不可用';
if (data.record.isAvailable) {
color = 'green';
text = '可用';
}
return (
<Tag size='small' color={color}>
{text}
</Tag>
);
}
}
];
export const notification = (
isSucceed: boolean = true,
nickname: string = '',
balance: number = 0,
isExist: boolean = false
) => {
if (isSucceed) {
const id = `${Date.now()}`;
Notification.success({
id,
title: '用户信息',
content: () => (
<div>
<div>{nickname}</div>
<div>{balance}</div>
<div>{isExist ? '存在' : '不存在'}</div>
</div>
),
closable: true,
footer: () => (
<Space>
<Button
status='normal'
type='primary'
onClick={() => Notification.remove(id)}
>
</Button>
</Space>
),
duration: 0
});
} else {
const id = `${Date.now()}`;
Notification.error({
id,
title: '用户信息',
content: 'cookie无效',
footer: () => (
<Space>
<Button
status='danger'
type='primary'
onClick={() => Notification.remove(id)}
>
</Button>
</Space>
),
duration: 0
});
}
};
export const batchImportModel = defineComponent({
name: 'batchImportWalmartAccountModel',
emits: {
close: () => {}
},
setup(props, { emit }) {
const state = reactive<{
visible: boolean;
}>({
visible: false
});
const { loading, setLoading } = useLoading(false);
const tableData = ref<KamiApiCardInfoWalmartV1AccountCookieCheckRes[]>([]);
return () => (
<>
<Button
type='primary'
size='small'
onClick={() => (state.visible = !state.visible)}
>
</Button>
<Modal
title='批量导入'
width='80%'
v-model:visible={state.visible}
onBeforeOk={async (done: (closed: boolean) => void) => {
try {
await batchAdd({ list: tableData.value });
emit('close');
Message.success('上传成功');
done(true);
} catch {
Message.error('上传失败');
done(false);
}
}}
>
<Row gutter={20}>
<Col span={4}>
<Space direction='vertical' fill>
<Button
long
status='warning'
onClick={() => downloadWalmartCardData()}
>
</Button>
<Upload
disabled={loading.value}
onBeforeUpload={async (
file: File
): Promise<boolean | File> => {
setLoading(true);
tableData.value = [];
return file;
}}
draggable
limit={1}
accept='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
onSuccess={(fileItem: FileItem) => {
setLoading(false);
if (fileItem.response.code === 0) {
if (
isArray(fileItem.response.data.list) &&
fileItem.response.data.list.length > 0
) {
tableData.value = fileItem.response.data.list;
}
Message.success(
`上传成功${fileItem.response.data?.msg ? '' + fileItem.response.data?.msg : ''}`
);
return;
}
Message.error(`上传失败:${fileItem.response.message}`);
}}
onError={() => {
Message.error('上传失败');
setLoading(false);
}}
action={`${getAPIBaseUrl()}/api/cardInfo/walmart/account/check`}
headers={{
Authorization: `Bearer ${getToken()}`,
tokenFrom: getTokenFrom()
}}
tip='点击批量上传ck'
/>
</Space>
</Col>
<Col span={20}>
<Table
pagination={false}
loading={loading.value}
data={tableData.value}
columns={tableColumns}
></Table>
</Col>
</Row>
</Modal>
</>
);
}
});
export default null;

View File

@@ -0,0 +1,159 @@
<template>
<a-drawer
:visible="drawVisible"
width="70%"
:footer="false"
unmountOnClose
@open="openDrawer"
@cancel="closeDrawer"
>
<template #title>账户流水</template>
<a-table
row-key="id"
:loading="loading"
:pagination="{
current: pagination.current,
pageSize: pagination.pageSize,
total: pagination.total,
pageSizeOptions: [10, 20, 50, 100],
showPageSize: true
}"
:columns="columns"
:data="renderData"
:bordered="false"
size="small"
column-resizable:bordered="{cell:true}"
@page-change="onPageChange"
@page-size-change="onPageSizeChange"
/>
</a-drawer>
</template>
<script lang="ts" setup>
import useLoading from '@/hooks/loading';
import { Pagination } from '@/types/global';
import { computed, onMounted, reactive, ref } from 'vue';
import { TableColumnData } from '@arco-design/web-vue';
import {
queryWalmartAccountWalletList,
WalmartAccountWalletParams
} from '@/api/card-walmart-account';
import { KamiInternalModelEntityV1CardRedeemAccountHistory } from '@/api/generated';
const basePagination: Pagination = {
current: 1,
pageSize: 50
};
const pagination = reactive({
...basePagination
});
const columns: TableColumnData[] = [
{
title: '序号',
slotName: 'index',
render: ({ rowIndex }) => {
return rowIndex + 1 + (pagination.current - 1) * pagination.pageSize;
}
},
{
title: '订单号',
dataIndex: 'orderNo'
},
{
title: '金额',
dataIndex: 'amount',
render: ({ record }) => {
switch (record.operationStatus) {
case 'return':
return `-${record.amount}`;
case 'deduct':
return `+${record.amount}`;
case 'initialize':
return `+${record.amount}`;
default:
return '未知';
}
}
},
{
title: '操作状态',
dataIndex: 'operationStatus',
render: ({ record }) => {
switch (record.operationStatus) {
case 'return':
return '退款';
case 'deduct':
return '加款';
case 'initialize':
return '初始化';
default:
return '未知';
}
}
},
{
title: '备注',
dataIndex: 'remark'
},
{
title: '创建时间',
dataIndex: 'createdAt',
slotName: 'createdAt'
}
];
const { loading, setLoading } = useLoading(true);
const renderData = ref<KamiInternalModelEntityV1CardRedeemAccountHistory[]>([]);
const props = defineProps<{
visible: boolean;
accountId: string;
}>();
const state = reactive({
visible: false
});
const emits = defineEmits<{
(e: 'update:visible', visible: boolean): void;
}>();
onMounted(() => {
state.visible = props.visible;
});
const drawVisible = computed(() => props.visible);
const fetchData = async (
params: WalmartAccountWalletParams = {
current: 1,
pageSize: 50
}
) => {
setLoading(true);
try {
params.accountId = props.accountId;
const {
data: { list, total }
} = await queryWalmartAccountWalletList(params);
renderData.value = list;
pagination.current = params.current;
pagination.pageSize = params.pageSize;
pagination.total = total;
} catch (err) {
console.error(err);
} finally {
setLoading(false);
}
};
const onPageChange = (current: number) => {
fetchData({ ...pagination, current });
};
const onPageSizeChange = (pageSize: number) => {
fetchData({ ...pagination, pageSize });
};
const closeDrawer = () => {
emits('update:visible', false);
};
const openDrawer = () => {
fetchData();
};
</script>

View File

@@ -0,0 +1,490 @@
<template>
<div class="container">
<Breadcrumb :items="['充值账户管理', '京东Cookie']" />
<a-space direction="vertical" fill>
<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="cookie" label="cookie">
<a-input
v-model="formModel.cookie"
placeholder="请输入Cookie"
/>
</a-form-item>
</a-col>
<a-col :span="8">
<a-form-item field="name" label="名称">
<a-input
v-model="formModel.name"
placeholder="请输入名称"
/>
</a-form-item>
</a-col>
<a-col :span="8">
<a-form-item field="nickname" label="沃尔玛昵称">
<a-input
v-model="formModel.nickname"
placeholder="请输入账户昵称"
/>
</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-row style="justify-content: space-between; margin-bottom: 16px">
<a-space>
<a-button
v-if="checkTokenFromLogin()"
type="primary"
size="small"
@click="showAddModel({ id: '', ...generateFormModel() })"
>
<template #icon>
<icon-plus />
</template>
添加
</a-button>
<a-button @click="download">
<template #icon>
<icon-download />
</template>
导出
</a-button>
<batchImportModel @close="() => search()" />
</a-space>
<a-tabs
default-active-key=""
type="capsule"
:onChange="onChangeRadio"
animation
>
<a-tab-pane key="" title="全部" />
<a-tab-pane
v-for="item in groupList"
:key="item.id"
:title="item.name"
:value="item.id"
/>
</a-tabs>
</a-row>
<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: 1080 }"
@page-change="onPageChange"
@page-size-change="onPageSizeChange"
>
<template #status="{ record }">
<a-space size="small">
<a-switch
v-if="[0, 1].includes(record.status)"
v-model="record.status"
size="small"
:checked-value="1"
:unchecked-value="0"
:beforeChange="
newValue => updateCurrentStatus(record, newValue)
"
/>
<a-tag
v-else
size="small"
:color="statusMapper(record.status).color"
>
{{ statusMapper(record.status).text }}
</a-tag>
</a-space>
</template>
<template #operations="{ record }">
<a-space size="small">
<a-tooltip content="修改">
<a-button
size="small"
status="warning"
@click="showAddModel(record)"
>
<template #icon>
<icon-pen />
</template>
</a-button>
</a-tooltip>
<a-tooltip content="详情">
<a-button size="small" @click="showDetailModel(record)">
<template #icon>
<icon-list />
</template>
</a-button>
</a-tooltip>
<a-popconfirm
type="warning"
content="确认刷新账号状态嘛?"
@ok="refreshButton(record)"
>
<a-tooltip content="刷新">
<a-button
v-if="record.status === 3"
size="small"
status="success"
>
<template #icon>
<icon-refresh />
</template>
</a-button>
</a-tooltip>
</a-popconfirm>
<a-tooltip content="检查账号状态">
<a-button
size="small"
status="warning"
@click="checkOne(record.id)"
>
<template #icon>
<icon-check-circle />
</template>
</a-button>
</a-tooltip>
<a-tooltip content="删除">
<a-button
size="small"
status="danger"
@click="deleteOne(record.id)"
>
<template #icon>
<icon-delete />
</template>
</a-button>
</a-tooltip>
</a-space>
</template>
</a-table>
</a-card>
<summary-card />
</a-space>
<add-modal
:id="state.accountId"
v-model:visible="state.addModalVisible"
:account="state.account"
/>
<account-detail
v-model:visible="state.accountDetailVisible"
:accountId="state.accountId"
/>
</div>
</template>
<script lang="ts" setup>
import useLoading from '@/hooks/loading';
import { Pagination } from '@/types/global';
import { checkTokenFromLogin } from '@/utils/auth';
import { onMounted, reactive, ref, watchEffect } from 'vue';
import { Notification, TableColumnData } from '@arco-design/web-vue';
import AddModal from './components/add-modal.vue';
import summaryCard from './summary.tsx';
import AccountDetail from './components/detail.vue';
import {
deleteWalmartCard,
downloadDataList,
refreshWalmartCardStatus,
updateWalmartCardStatus,
WalmartCardParams,
walmartCardUpdateRecord
} from '@/api/card-walmart-account';
import { batchImportModel } from './components/component.tsx';
import {
ApiCardInfoWalmartAccountGetListGetPageSizeEnum,
KamiApiCardInfoWalmartV1AccountListRecord,
KamiInternalModelEntityV1CardRedeemAccountGroup
} from '@/api/generated/index.ts';
import { apiClient } from '@/api/index.ts';
const basePagination: Pagination = {
current: 1,
pageSize: 50
};
const pagination = reactive({
...basePagination
});
const columns: TableColumnData[] = [
{
title: '序号',
dataIndex: 'index',
render: ({ rowIndex }) => {
return rowIndex + 1 + (pagination.current - 1) * pagination.pageSize;
}
},
{
title: '名称',
dataIndex: 'name'
},
{
title: '昵称',
dataIndex: 'nickname'
},
{
title: '余额',
dataIndex: 'balance'
},
{
title: '今日充值金额',
dataIndex: 'amountTodaySum'
},
{
title: '今日充值次数',
dataIndex: 'amountTodayCount'
},
{
title: '充值限制(金额)',
dataIndex: 'maxAmountLimit'
},
{
title: '充值限制(次数)',
dataIndex: 'maxCountLimit'
},
{
title: '累计充值金额',
dataIndex: 'amountTotalSum'
},
{
title: '累计充值次数',
dataIndex: 'amountTotalCount'
},
{
title: 'ck',
dataIndex: 'cookie',
ellipsis: true,
tooltip: true
},
{
title: '创建人',
dataIndex: 'uploadUser.username'
},
{
title: '状态',
dataIndex: 'status',
slotName: 'status'
},
{
title: '创建时间',
dataIndex: 'createdAt',
slotName: 'createdAt'
},
{
title: '操作',
dataIndex: 'operations',
slotName: 'operations',
fixed: 'right',
width: 220
}
];
const generateFormModel = () => {
return {
remark: '',
name: '',
cookie: '',
nickname: '',
maxAmountLimit: 0,
status: 1,
createdUserName: '',
groupId: null
};
};
const { loading, setLoading } = useLoading(true);
const renderData = ref<KamiApiCardInfoWalmartV1AccountListRecord[]>([]);
const formModel = ref(generateFormModel());
const state = reactive({
addModalVisible: false,
deployModalVisible: false,
accountDetailVisible: false,
accountId: '',
account: null
});
const groupList = ref<KamiInternalModelEntityV1CardRedeemAccountGroup[]>([]);
const fetchData = async (
params: WalmartCardParams = { current: 1, pageSize: 50 }
) => {
setLoading(true);
try {
const res = await apiClient.apiCardInfoWalmartAccountGetListGet(
params.current,
params.pageSize as ApiCardInfoWalmartAccountGetListGetPageSizeEnum,
formModel.value.name,
formModel.value.nickname,
formModel.value.cookie,
formModel.value.createdUserName,
formModel.value.groupId
);
renderData.value = res.data.list;
pagination.current = params.current;
pagination.pageSize = params.pageSize;
pagination.total = res.data.total;
} catch (err) {
} finally {
setLoading(false);
}
};
const onPageChange = (current: number) => {
fetchData({ ...pagination, current });
};
const onPageSizeChange = (pageSize: number) => {
fetchData({ ...pagination, pageSize });
};
const search = () => {
fetchData({
...basePagination,
...formModel.value
} as unknown as WalmartCardParams);
};
const reset = () => {
formModel.value = generateFormModel();
};
const getAllGroupList = () => {
apiClient.apiCardInfoWalmartGroupAllListGet().then(res => {
groupList.value = res.data.list;
});
};
const deleteOne = async (id: string) => {
try {
await deleteWalmartCard({ id });
} catch {
Notification.error({
id: 'walmartAccountNotice',
content: '删除沃尔玛卡失败',
closable: true
});
} finally {
fetchData({ ...pagination });
}
};
const checkOne = async (id: string) => {
try {
const result = await apiClient.apiCardInfoWalmartAccountStatusDetectGet(id);
if (result.data.status) {
Notification.info({
id: 'walmartAccountNotice',
content: 'ck有效',
closable: true
});
} else {
Notification.error({
id: 'walmartAccountNotice',
content: 'ck无效',
closable: true
});
}
} catch (err) {
Notification.error({
id: 'walmartAccountNotice',
content: '检测沃尔玛卡失败',
closable: true
});
}
};
const showAddModel = (record: walmartCardUpdateRecord) => {
state.addModalVisible = true;
state.accountId = record.id;
state.account = record;
if (state.account.groupId === 0) {
state.account.groupId = null;
}
};
const showDetailModel = (record: walmartCardUpdateRecord) => {
state.accountDetailVisible = true;
state.accountId = record.id;
};
watchEffect(() => {
// 目标账户和存储用户不能相同
if (!state.addModalVisible) {
search();
}
});
const updateCurrentStatus = async (
record: walmartCardUpdateRecord,
newValue: string | number | boolean
) => {
await updateWalmartCardStatus({ id: record.id, status: newValue as number });
record.status = newValue as number;
};
const statusMapper = (status: number): { text: string; color: string } => {
switch (status) {
case 0:
return { text: '失效', color: 'red' };
case 1:
return { text: '正常', color: 'green' };
case 2:
return { text: '充值过快', color: 'orange' };
case 3:
return { text: '充值限制(用户设置)', color: 'red' };
case 4:
return { text: '充值限制(其他)', color: 'red' };
case 5:
return { text: '充值限制(低额)', color: 'red' };
case 6:
return { text: '充值限制(安全原因)', color: 'red' };
case 7:
return { text: '充值限制(暂时限制)', color: 'red' };
case 8:
return { text: '充值限制(平台原因)', color: 'red' };
default:
return { text: '未知', color: 'gray' };
}
};
const refreshButton = (record: walmartCardUpdateRecord) => {
refreshWalmartCardStatus({ id: record.id }).then(() => {
fetchData();
});
};
onMounted(() => {
fetchData();
getAllGroupList();
});
const onChangeRadio = (value: string | number | boolean) => {
formModel.value.groupId = value as number;
// fetchData();
};
const download = () => {
downloadDataList();
};
</script>

View File

@@ -0,0 +1,213 @@
import { apiClient } from '@/api';
import type {
KamiApiCardInfoWalmartV1CardRedeemAccountSummary,
ApiCardInfoWalmartAccountDailySummaryGetPageSizeEnum
} from '@/api/generated';
import { handleDownLoadFile } from '@/api/utils';
import type { Pagination } from '@/types/global';
import {
Button,
Card,
Col,
DatePicker,
Divider,
Form,
FormItem,
Input,
Message,
Row,
Space,
Table,
type TableColumnData
} from '@arco-design/web-vue';
import { defineComponent, onMounted, reactive, ref } from 'vue';
interface IformData {
accountId: string;
username: string;
date: string;
}
export default defineComponent({
name: 'walmartSummary',
setup() {
const generateFormModel = (): IformData => {
return {
username: '',
accountId: '',
date: null
};
};
const formModel = ref(generateFormModel());
const basePagination: Pagination = {
current: 1,
pageSize: 50
};
const pagination = reactive({
...basePagination
});
const columns: TableColumnData[] = [
{
title: '序号',
render(data) {
return data.rowIndex + 1;
}
},
{
title: '用户名',
dataIndex: 'accountInfo.name'
},
{
title: '余额',
dataIndex: 'accountInfo.balance'
},
{
title: '今日充值金额',
dataIndex: 'amountTodaySum'
},
{
title: '今日充值次数',
dataIndex: 'amountTodayCount'
},
{
title: '累计充值金额',
dataIndex: 'amountTotalSum'
},
{
title: '累计充值次数',
dataIndex: 'amountTotalCount'
},
{
title: '上传人',
dataIndex: 'accountInfo.uploadUser.username'
},
{
title: '日期',
dataIndex: 'date'
}
];
const tableData = ref<KamiApiCardInfoWalmartV1CardRedeemAccountSummary[]>(
[]
);
const downloadExcel = () => {
apiClient
.apiCardInfoWalmartAccountSummaryDownloadGet(
pagination.current,
pagination.pageSize as ApiCardInfoWalmartAccountDailySummaryGetPageSizeEnum,
formModel.value.username,
formModel.value.date,
{
responseType: 'blob'
}
)
.then(res => {
// 判断是不是blob类型
if (!res.data) {
Message.error('下载账户统计明细失败');
return;
}
handleDownLoadFile(res.data as any, '沃尔玛账户统计明细.xlsx');
});
};
const fetchData = (
page: Pagination = pagination,
params: IformData = formModel.value
) => {
apiClient
.apiCardInfoWalmartAccountDailySummaryGet(
page.current,
page.pageSize as ApiCardInfoWalmartAccountDailySummaryGetPageSizeEnum,
params.username,
params.date
)
.then(res => {
tableData.value = res.data.list;
pagination.total = res.data.total;
});
};
const reset = () => {
formModel.value = generateFormModel();
};
onMounted(() => {
fetchData();
});
return () => (
<Card class='general-card' title='核销统计'>
<Space direction='vertical' fill>
<Row>
<Col flex={1}>
<Form model={formModel}>
<Row gutter={16}>
<Col span={8}>
<FormItem field='username' label='用户名'>
<Input
v-model={formModel.value.username}
placeholder='请输入用户名'
/>
</FormItem>
</Col>
<Col span={16}>
<FormItem field='date' label='查询日期'>
<DatePicker v-model={formModel.value.date} />
</FormItem>
</Col>
</Row>
</Form>
</Col>
<Divider style='height: 42px' direction='vertical' />
<Space direction='vertical' fill>
<Row>
<Col flex='172px' style='text-align: right'>
<Space direction='horizontal' size={18}>
<Button
type='primary'
onClick={() => fetchData()}
v-slots={{
icon: () => <icon-search />
}}
>
</Button>
<Button
onClick={reset}
v-slots={{
icon: () => <icon-refresh />
}}
>
</Button>
</Space>
</Col>
</Row>
<Row>
<Col>
<Button
type='secondary'
status='warning'
onClick={() => downloadExcel()}
v-slots={{
icon: () => <icon-download />
}}
>
</Button>
</Col>
</Row>
</Space>
</Row>
<Table
columns={columns}
data={tableData.value}
pagination={pagination}
onPageChange={(current: number) => {
pagination.current = current;
fetchData();
}}
/>
</Space>
</Card>
);
}
});

View File

@@ -0,0 +1,143 @@
<template>
<a-drawer
v-model:visible="state.visible"
width="40%"
title="充值操作日志"
:footer="false"
unmount-on-close
@close="closeDrawer"
>
<div v-if="props.orderNo !== ''">
<a-radio-group
v-model="state.isReverse"
type="button"
size="small"
class="sort-btn"
>
<a-radio :value="true">正序</a-radio>
<a-radio :value="false">逆序</a-radio>
</a-radio-group>
<a-timeline :reverse="state.isReverse">
<a-timeline-item
v-for="(item, index) in renderData"
:key="index"
:label="item.createdAt"
>
<a-space>
<span>
{{ mapStatus(item.operationStatus) }}
</span>
<span class="remark">
{{ item.remark }}
</span>
</a-space>
</a-timeline-item>
</a-timeline>
</div>
<div v-else>404</div>
</a-drawer>
</template>
<script setup lang="ts">
import { queryWalmartOrderHistoryList } from '@/api/card-walmart-order';
import { KamiInternalModelEntityV1CardRedeemOrderHistory } from '@/api/generated';
import { reactive, ref, watch } from 'vue';
const props = defineProps({
orderNo: {
type: String,
default: ''
}
});
const visible = defineModel<boolean>('visible');
const state = reactive<{ isReverse: boolean; visible: boolean }>({
isReverse: true,
visible: false
});
const renderData = ref<KamiInternalModelEntityV1CardRedeemOrderHistory[]>([]);
const getJDOrderHistoryList = async () => {
const res = await queryWalmartOrderHistoryList({
orderNo: props.orderNo
});
renderData.value = res.data.list;
};
watch(
() => visible.value,
newValue => {
state.visible = newValue;
if (newValue) {
getJDOrderHistoryList();
} else {
renderData.value = [];
}
}
);
const closeDrawer = () => {
visible.value = false;
// emit('update:visible', false);
};
const mapStatus = (opeationStatus: number) => {
switch (opeationStatus) {
case 100:
return '充值失败';
case 1:
return '添加订单';
case 2:
return '分配账号';
case 3:
return '充值成功';
case 4:
return '开始回调';
case 5:
return '订单退回';
case 6:
return '订单失败(账户问题)';
case 7:
return '分配账号失败';
case 8:
return '订单验证失败';
case 9:
return '开始回调';
case 10:
return '回调成功';
case 11:
return '回调失败 ';
case 12:
return '订单未处于充值结束状态';
case 13:
return '用户设置不触发回调';
case 14:
return '未知状态';
case 15:
return '服务器错误';
case 16:
return '开始处理';
case 17:
return ' 查询卡密余额';
case 18:
return ' 开始绑卡';
case 19:
return ' 绑卡结束';
default:
return '未知状态';
}
};
</script>
<style lang="less" scoped>
.sort-btn {
float: right;
}
.remark {
font-size: 0.8rem;
font-weight: 300;
}
</style>

View File

@@ -0,0 +1,457 @@
<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="giftCardPwd" label="卡密">
<a-input
v-model="formModel.giftCardPwd"
placeholder="请输入卡密"
/>
</a-form-item>
</a-col>
<a-col :span="8">
<a-form-item field="attach" label="商户订单号">
<a-input
v-model="formModel.attach"
placeholder="请输入商户订单号"
/>
</a-form-item>
</a-col>
<a-col :span="8">
<a-form-item field="merchantId" label="系统订单号">
<a-input
v-model="formModel.merchantId"
placeholder="请输入系统订单号"
/>
</a-form-item>
</a-col>
<a-col :span="8">
<a-form-item field="dateRange" label="创建时间">
<a-range-picker v-model="formModel.dateRange" />
</a-form-item>
</a-col>
<a-col :span="8">
<a-form-item field="accountNickName" label="账户昵称">
<a-input
v-model="formModel.accountNickName"
placeholder="请输入账户昵称"
/>
</a-form-item>
</a-col>
<a-col :span="8">
<a-form-item field="accountCk" label="账户Ck">
<a-input
v-model="formModel.accountCk"
placeholder="请输入账户Ck"
/>
</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="vertical" :size="18">
<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-space>
</a-col>
</a-row>
<a-divider style="margin-top: 0" />
<a-space direction="vertical" fill>
<a-row style="justify-content: space-between; margin-bottom: 16px">
<config v-if="state.showConfig" />
<a-tabs
default-active-key=""
type="capsule"
:onChange="onChangeRadio"
animation
>
<a-tab-pane key="" title="全部" />
<a-tab-pane
v-for="item in groupList"
:key="item.id"
:title="item.name"
:value="item.id"
/>
</a-tabs>
</a-row>
<a-table
:loading="loading"
:pagination="{
current: pagination.current,
pageSize: pagination.pageSize,
total: pagination.total,
pageSizeOptions: [10, 20, 50, 100],
showPageSize: true,
showTotal: true
}"
table-layout-fixed
column-resizable
:columns="columns"
:data="renderData"
:bordered="false"
column-resizable:bordered="{cell:true}"
@page-change="onPageChange"
@page-size-change="onPageSizeChange"
>
<template #status="{ record }">
<a-tag size="small" :color="statusMapper(record.status).color">
{{ statusMapper(record.status).text }}
</a-tag>
</template>
<template #orderStatus="{ record }">
<a-tag
v-if="record.orderStatus !== 0"
size="small"
:color="statusMapper(record.orderStatus).color"
>
{{ statusMapper(record.orderStatus).text }}
</a-tag>
</template>
<template #notifyStatus="{ record }">
<a-tag
v-if="[1, 2].includes(record.notifyStatus)"
size="small"
:color="record.notifyStatus === 1 ? 'green' : 'red'"
>
回调{{ record.notifyStatus === 1 ? '成功' : '失败' }}
</a-tag>
<a-tag v-else size="small" color="gray">未回调</a-tag>
</template>
<template #operation="{ record }">
<a-space size="small">
<a-tooltip v-if="record.status === 7" content="刷新调度">
<a-button
size="small"
status="warning"
@click="resetStatus(record.orderNo)"
>
<template #icon>
<icon-refresh />
</template>
</a-button>
</a-tooltip>
<a-tooltip content="详情">
<a-button
size="small"
@click="showOrderHistory(record.orderNo)"
>
<template #icon>
<icon-list />
</template>
</a-button>
</a-tooltip>
<!-- 回调 -->
<a-tooltip content="回调">
<a-button
v-if="
record.notifyStatus !== 1 &&
[1, 100, 17].includes(record.orderStatus)
"
size="small"
status="warning"
@click="callback(record.orderNo)"
>
<template #icon>
<icon-send />
</template>
</a-button>
</a-tooltip>
</a-space>
</template>
</a-table>
</a-space>
</a-card>
<order-history
v-model:visible="state.orderHistoryModalVisible"
:order-no="state.selectedOrderNo"
/>
</div>
</template>
<script lang="ts" setup>
import { TableColumnData, Notification, Message } from '@arco-design/web-vue';
import useLoading from '@/hooks/loading';
import { Pagination } from '@/types/global';
import { onMounted, reactive, ref } from 'vue';
import OrderHistory from './components/history.vue';
import { checkTokenFromIframe } from '@/utils/auth';
import { walmartCardOrderParams } from '@/api/card-walmart-order.ts';
import {
ApiCardInfoWalmartOrderListGetPageSizeEnum,
KamiInternalModelEntityV1CardRedeemAccountGroup,
KamiInternalModelEntityV1CardRedeemOrderInfo
} from '@/api/generated/index.ts';
import { apiClient } from '@/api/index.ts';
const basePagination: Pagination = {
current: 1,
pageSize: 50
};
const state = reactive({
orderHistoryModalVisible: false,
selectedOrderNo: '',
showConfig: false,
activeTableKey: 0
});
const pagination = reactive({
...basePagination
});
const groupList = ref<KamiInternalModelEntityV1CardRedeemAccountGroup[]>([]);
const columns: TableColumnData[] = [
{
title: '序号',
dataIndex: 'index',
render: ({ rowIndex }) => {
return rowIndex + 1 + (pagination.current - 1) * pagination.pageSize;
}
},
{
title: '系统订单号',
dataIndex: 'merchantId'
},
{
title: '商户订单号',
dataIndex: 'attach'
},
{
title: '分配账号',
dataIndex: 'accountName'
},
{
title: '卡种',
dataIndex: 'cardTypeName'
},
{
title: '卡号',
dataIndex: 'cardNo',
tooltip: true,
ellipsis: true
},
{
title: '礼品卡密码',
dataIndex: 'giftCardPwd'
},
{
title: '卡面金额',
dataIndex: 'orderAmount'
},
{
title: '实际金额',
dataIndex: 'actualAmount'
},
{
title: '充值状态',
dataIndex: 'status',
slotName: 'status'
},
{
title: '状态明细',
dataIndex: 'orderStatus',
slotName: 'orderStatus'
},
{
title: '回调状态',
dataIndex: 'notifyStatus',
slotName: 'notifyStatus'
},
// {
// title: '备注',
// dataIndex: 'remark'
// },
{
title: '创建时间',
dataIndex: 'createdAt'
},
{
title: '操作',
dataIndex: 'operation',
slotName: 'operation'
}
];
const generateFormModel = () => {
return {
merchantId: '',
attach: '',
giftCardPwd: '',
accountNickName: '',
accountCk: '',
status: null,
dateRange: [],
groupId: null
};
};
const resetStatus = (orderNo: string) => {
apiClient.apiCardInfoWalmartOrderStatusResetPut({ id: orderNo }).then(() => {
Message.success('重置成功');
search();
});
};
const { loading, setLoading } = useLoading(true);
const renderData = ref<KamiInternalModelEntityV1CardRedeemOrderInfo[]>([]);
const formModel = ref<{
merchantId: string;
accountNickName: string;
accountCk: string;
attach: string;
giftCardPwd: string;
status: number;
dateRange: string[];
groupId: number | null;
}>(generateFormModel());
const fetchData = async (
params: walmartCardOrderParams = {
current: 1,
pageSize: 50
}
) => {
setLoading(true);
try {
const res = await apiClient.apiCardInfoWalmartOrderListGet(
pagination.current,
pagination.pageSize as ApiCardInfoWalmartOrderListGetPageSizeEnum,
formModel.value.giftCardPwd,
formModel.value.merchantId,
formModel.value.attach,
formModel.value.accountNickName,
formModel.value.groupId,
formModel.value.accountCk,
formModel.value.dateRange
);
renderData.value = res.data.list;
pagination.current = params.current;
pagination.pageSize = params.pageSize;
pagination.total = res.data.total;
} catch (err) {
console.error(err);
} finally {
setLoading(false);
}
};
const onPageChange = (current: number) => {
fetchData({ ...pagination, current });
};
const onPageSizeChange = (pageSize: number) => {
fetchData({ ...pagination, pageSize, current: 1 });
};
const search = () => {
fetchData({
current: 1,
...basePagination,
...formModel.value
});
};
const showOrderHistory = (orderNo: string) => {
state.orderHistoryModalVisible = true;
state.selectedOrderNo = orderNo;
};
const statusMapper = (status: number): { text: string; color: string } => {
switch (status) {
case 100:
return { text: '失败', color: 'red' };
case 1:
return { text: '成功', color: 'green' };
case 2:
return { text: '待处理', color: 'orange' };
case 3:
return { text: '验证失败', color: 'red' };
case 4:
return { text: '账号失效', color: 'red' };
case 5:
return { text: '账号充值频繁', color: 'red' };
case 6:
return { text: '未知', color: 'gray' };
case 7:
return { text: '禁用(充值次数超限)', color: 'red' };
case 8:
return { text: '金额异议(充值成功)', color: 'pinkpurple' };
case 9:
return { text: '卡密类型错误', color: 'red' };
case 10:
return { text: '金额异议(充值失败)', color: 'red' };
case 11:
return { text: '卡密被绑定', color: 'red' };
case 12:
return { text: '重复绑卡', color: 'red' };
case 13:
return { text: '账号受限', color: 'red' };
case 14:
return { text: '匹配账号', color: 'orange' };
case 15:
return { text: '卡片无效/不存在', color: 'red' };
case 16:
return { text: '卡片过期', color: 'red' };
case 17:
return { text: '补卡成功', color: 'green' };
case 18:
return { text: '开始处理', color: 'green' };
case 19:
return { text: '订单重复上传', color: 'red' };
case 20:
return { text: '卡号不符合规则', color: 'red' };
default:
return { text: '未知', color: 'gray' };
}
};
const reset = () => {
formModel.value = generateFormModel();
};
onMounted(() => {
if (checkTokenFromIframe()) {
state.showConfig = true;
}
fetchData({ ...pagination });
});
const callback = async (orderNo: string) => {
try {
await apiClient.apiCardInfoWalmartOrderCallbackGet(orderNo);
Notification.success({
title: '成功',
content: '等待回调'
});
} catch (error) {}
};
const getAllGroupList = () => {
apiClient.apiCardInfoWalmartGroupAllListGet().then(res => {
groupList.value = res.data.list;
});
};
const onChangeRadio = (value: string | number | boolean) => {
formModel.value.groupId = value as number;
fetchData();
};
onMounted(() => {
getAllGroupList();
});
</script>