feat: 新增报告结果查看并完善产品模块配置
Some checks failed
CI / Test (ubuntu-latest) (push) Has been cancelled
CI / Test (windows-latest) (push) Has been cancelled
CI / Lint (ubuntu-latest) (push) Has been cancelled
CI / Lint (windows-latest) (push) Has been cancelled
CI / Check (ubuntu-latest) (push) Has been cancelled
CI / Check (windows-latest) (push) Has been cancelled
CodeQL / Analyze (${{ matrix.language }}) (none, javascript-typescript) (push) Has been cancelled
Deploy Website on push / Deploy Push Playground Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Docs Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Antd Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Element Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Naive Ftp (push) Has been cancelled
Release Drafter / update_release_draft (push) Has been cancelled
CI / CI OK (push) Has been cancelled
Deploy Website on push / Rerun on failure (push) Has been cancelled
Some checks failed
CI / Test (ubuntu-latest) (push) Has been cancelled
CI / Test (windows-latest) (push) Has been cancelled
CI / Lint (ubuntu-latest) (push) Has been cancelled
CI / Lint (windows-latest) (push) Has been cancelled
CI / Check (ubuntu-latest) (push) Has been cancelled
CI / Check (windows-latest) (push) Has been cancelled
CodeQL / Analyze (${{ matrix.language }}) (none, javascript-typescript) (push) Has been cancelled
Deploy Website on push / Deploy Push Playground Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Docs Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Antd Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Element Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Naive Ftp (push) Has been cancelled
Release Drafter / update_release_draft (push) Has been cancelled
CI / CI OK (push) Has been cancelled
Deploy Website on push / Rerun on failure (push) Has been cancelled
This commit is contained in:
parent
d3609c21c0
commit
db606f10b3
@ -1,59 +1 @@
|
|||||||
import type { Recordable } from '@vben/types';
|
export * from './order';
|
||||||
|
|
||||||
import { requestClient } from '#/api/request';
|
|
||||||
|
|
||||||
export namespace OrderApi {
|
|
||||||
export interface Order {
|
|
||||||
id: number;
|
|
||||||
order_no: string;
|
|
||||||
platform_order_id: string;
|
|
||||||
product_name: string;
|
|
||||||
payment_platform: 'alipay' | 'appleiap' | 'wechat';
|
|
||||||
payment_scene: 'app' | 'h5' | 'mini_program' | 'public_account';
|
|
||||||
amount: number;
|
|
||||||
status: 'closed' | 'failed' | 'paid' | 'pending' | 'refunded';
|
|
||||||
create_time: string;
|
|
||||||
pay_time: null | string;
|
|
||||||
refund_time: null | string;
|
|
||||||
is_promotion: 0 | 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface OrderList {
|
|
||||||
total: number;
|
|
||||||
items: Order[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface RefundOrderRequest {
|
|
||||||
refund_amount: number;
|
|
||||||
refund_reason: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface RefundOrderResponse {
|
|
||||||
status: string;
|
|
||||||
refund_no: string;
|
|
||||||
amount: number;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取订单列表数据
|
|
||||||
*/
|
|
||||||
async function getOrderList(params: Recordable<any>) {
|
|
||||||
return requestClient.get<OrderApi.OrderList>('/order/list', {
|
|
||||||
params,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 订单退款
|
|
||||||
* @param id 订单 ID
|
|
||||||
* @param data 退款请求数据
|
|
||||||
*/
|
|
||||||
async function refundOrder(id: number, data: OrderApi.RefundOrderRequest) {
|
|
||||||
return requestClient.post<OrderApi.RefundOrderResponse>(
|
|
||||||
`/order/refund/${id}`,
|
|
||||||
data,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export { getOrderList, refundOrder };
|
|
||||||
|
60
apps/web-antd/src/api/order/order.ts
Normal file
60
apps/web-antd/src/api/order/order.ts
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
import type { Recordable } from '@vben/types';
|
||||||
|
|
||||||
|
import { requestClient } from '#/api/request';
|
||||||
|
|
||||||
|
export namespace OrderApi {
|
||||||
|
export interface Order {
|
||||||
|
id: number;
|
||||||
|
order_no: string;
|
||||||
|
platform_order_id: string;
|
||||||
|
product_name: string;
|
||||||
|
payment_platform: 'alipay' | 'appleiap' | 'wechat';
|
||||||
|
payment_scene: 'app' | 'h5' | 'mini_program' | 'public_account';
|
||||||
|
amount: number;
|
||||||
|
status: 'closed' | 'failed' | 'paid' | 'pending' | 'refunded';
|
||||||
|
query_state: 'failed' | 'pending' | 'processing' | 'success';
|
||||||
|
create_time: string;
|
||||||
|
pay_time: null | string;
|
||||||
|
refund_time: null | string;
|
||||||
|
is_promotion: 0 | 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface OrderList {
|
||||||
|
total: number;
|
||||||
|
items: Order[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RefundOrderRequest {
|
||||||
|
refund_amount: number;
|
||||||
|
refund_reason: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RefundOrderResponse {
|
||||||
|
status: string;
|
||||||
|
refund_no: string;
|
||||||
|
amount: number;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取订单列表数据
|
||||||
|
*/
|
||||||
|
async function getOrderList(params: Recordable<any>) {
|
||||||
|
return requestClient.get<OrderApi.OrderList>('/order/list', {
|
||||||
|
params,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 订单退款
|
||||||
|
* @param id 订单 ID
|
||||||
|
* @param data 退款请求数据
|
||||||
|
*/
|
||||||
|
async function refundOrder(id: number, data: OrderApi.RefundOrderRequest) {
|
||||||
|
return requestClient.post<OrderApi.RefundOrderResponse>(
|
||||||
|
`/order/refund/${id}`,
|
||||||
|
data,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export { getOrderList, refundOrder };
|
50
apps/web-antd/src/api/order/query.ts
Normal file
50
apps/web-antd/src/api/order/query.ts
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
import type { Recordable } from '@vben/types';
|
||||||
|
|
||||||
|
import { requestClient } from '#/api/request';
|
||||||
|
|
||||||
|
export namespace OrderQueryApi {
|
||||||
|
export interface QueryItem {
|
||||||
|
feature: Recordable<any>;
|
||||||
|
data: Recordable<any>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface QueryDetail {
|
||||||
|
id: number;
|
||||||
|
order_id: number;
|
||||||
|
user_id: number;
|
||||||
|
product_name: string;
|
||||||
|
query_params: Recordable<any>;
|
||||||
|
query_data: QueryItem[];
|
||||||
|
create_time: string;
|
||||||
|
update_time: string;
|
||||||
|
query_state: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GetQueryDetailRequest {
|
||||||
|
order_id: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GetQueryDetailResponse {
|
||||||
|
id: number;
|
||||||
|
order_id: number;
|
||||||
|
user_id: number;
|
||||||
|
product_name: string;
|
||||||
|
query_params: Recordable<any>;
|
||||||
|
query_data: QueryItem[];
|
||||||
|
create_time: string;
|
||||||
|
update_time: string;
|
||||||
|
query_state: string;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取订单查询详情
|
||||||
|
* @param orderId 订单ID
|
||||||
|
*/
|
||||||
|
async function getOrderQueryDetail(orderId: number) {
|
||||||
|
return requestClient.get<OrderQueryApi.GetQueryDetailResponse>(
|
||||||
|
`/query/detail/${orderId}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export { getOrderQueryDetail };
|
@ -77,6 +77,20 @@ export function useColumns<T = OrderApi.Order>(
|
|||||||
title: '支付状态',
|
title: '支付状态',
|
||||||
width: 120,
|
width: 120,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
cellRender: {
|
||||||
|
name: 'CellTag',
|
||||||
|
options: [
|
||||||
|
{ value: 'pending', color: 'warning', label: '查询中' },
|
||||||
|
{ value: 'success', color: 'success', label: '查询成功' },
|
||||||
|
{ value: 'failed', color: 'error', label: '查询失败' },
|
||||||
|
{ value: 'processing', color: 'warning', label: '查询中' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
field: 'query_state',
|
||||||
|
title: '查询状态',
|
||||||
|
width: 120,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
field: 'create_time',
|
field: 'create_time',
|
||||||
title: '创建时间',
|
title: '创建时间',
|
||||||
@ -121,12 +135,19 @@ export function useColumns<T = OrderApi.Order>(
|
|||||||
return row.status !== 'paid';
|
return row.status !== 'paid';
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
code: 'query',
|
||||||
|
text: '查询结果',
|
||||||
|
disabled: (row: OrderApi.Order) => {
|
||||||
|
return row.query_state !== 'success';
|
||||||
|
},
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
field: 'operation',
|
field: 'operation',
|
||||||
fixed: 'right',
|
fixed: 'right',
|
||||||
title: '操作',
|
title: '操作',
|
||||||
width: 100,
|
width: 180,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,8 @@ import type {
|
|||||||
} from '#/adapter/vxe-table';
|
} from '#/adapter/vxe-table';
|
||||||
import type { OrderApi } from '#/api/order';
|
import type { OrderApi } from '#/api/order';
|
||||||
|
|
||||||
|
import { useRouter } from 'vue-router';
|
||||||
|
|
||||||
import { Page, useVbenDrawer } from '@vben/common-ui';
|
import { Page, useVbenDrawer } from '@vben/common-ui';
|
||||||
|
|
||||||
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||||
@ -56,8 +58,19 @@ const [RefundDrawer, refundDrawerApi] = useVbenDrawer({
|
|||||||
destroyOnClose: true,
|
destroyOnClose: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
function onActionClick(e: OnActionClickParams<OrderApi.Order>) {
|
function onActionClick(e: OnActionClickParams<OrderApi.Order>) {
|
||||||
switch (e.code) {
|
switch (e.code) {
|
||||||
|
case 'query': {
|
||||||
|
router.push({
|
||||||
|
name: 'OrderQueryDetail',
|
||||||
|
params: {
|
||||||
|
id: e.row.id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
case 'refund': {
|
case 'refund': {
|
||||||
onRefund(e.row);
|
onRefund(e.row);
|
||||||
break;
|
break;
|
||||||
|
253
apps/web-antd/src/views/order/query.vue
Normal file
253
apps/web-antd/src/views/order/query.vue
Normal file
@ -0,0 +1,253 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import type { JsonViewerAction } from '@vben/common-ui';
|
||||||
|
|
||||||
|
import type { OrderQueryApi } from '#/api/order/query';
|
||||||
|
|
||||||
|
import { onMounted, ref } from 'vue';
|
||||||
|
import { useRoute, useRouter } from 'vue-router';
|
||||||
|
|
||||||
|
import { JsonViewer, Page } from '@vben/common-ui';
|
||||||
|
import { MdiArrowLeft } from '@vben/icons';
|
||||||
|
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Card,
|
||||||
|
Collapse,
|
||||||
|
Descriptions,
|
||||||
|
message,
|
||||||
|
Tag,
|
||||||
|
} from 'ant-design-vue';
|
||||||
|
|
||||||
|
import { getOrderQueryDetail } from '#/api/order/query';
|
||||||
|
|
||||||
|
const route = useRoute();
|
||||||
|
const router = useRouter();
|
||||||
|
const orderId = Number(route.params.id);
|
||||||
|
const loading = ref(false);
|
||||||
|
const queryDetail = ref<OrderQueryApi.QueryDetail>();
|
||||||
|
|
||||||
|
// 查询状态配置
|
||||||
|
const queryStateConfig = [
|
||||||
|
{ value: 'pending', color: 'warning', label: '查询中' },
|
||||||
|
{ value: 'success', color: 'success', label: '查询成功' },
|
||||||
|
{ value: 'failed', color: 'error', label: '查询失败' },
|
||||||
|
{ value: 'processing', color: 'warning', label: '查询中' },
|
||||||
|
] as const;
|
||||||
|
|
||||||
|
// 获取查询状态配置
|
||||||
|
function getQueryStateConfig(state: string) {
|
||||||
|
return (
|
||||||
|
queryStateConfig.find((item) => item.value === state) || {
|
||||||
|
color: 'default',
|
||||||
|
label: state,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 字段名称映射
|
||||||
|
const fieldNameMap: Record<string, string> = {
|
||||||
|
// 基础字段
|
||||||
|
name: '姓名',
|
||||||
|
id_card: '身份证号',
|
||||||
|
mobile: '手机号',
|
||||||
|
code: '验证码',
|
||||||
|
// 企业相关
|
||||||
|
ent_name: '企业名称',
|
||||||
|
ent_code: '统一社会信用代码',
|
||||||
|
// 婚姻相关
|
||||||
|
name_man: '男方姓名',
|
||||||
|
id_card_man: '男方身份证号',
|
||||||
|
name_woman: '女方姓名',
|
||||||
|
id_card_woman: '女方身份证号',
|
||||||
|
// 车辆相关
|
||||||
|
car_type: '车辆类型',
|
||||||
|
car_license: '车牌号',
|
||||||
|
vin_code: '车架号',
|
||||||
|
car_driving_permit: '行驶证号',
|
||||||
|
// 银行卡相关
|
||||||
|
bank_card: '银行卡号',
|
||||||
|
// 学历相关
|
||||||
|
certificate_number: '证书编号',
|
||||||
|
// 日期相关
|
||||||
|
start_date: '开始日期',
|
||||||
|
};
|
||||||
|
|
||||||
|
// 获取字段显示名称
|
||||||
|
function getFieldDisplayName(key: string): string {
|
||||||
|
return fieldNameMap[key] || key;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 返回订单管理页面
|
||||||
|
function handleBack() {
|
||||||
|
router.push('/order');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取查询详情
|
||||||
|
async function fetchQueryDetail() {
|
||||||
|
if (!orderId) return;
|
||||||
|
|
||||||
|
loading.value = true;
|
||||||
|
try {
|
||||||
|
const res = await getOrderQueryDetail(orderId);
|
||||||
|
queryDetail.value = res;
|
||||||
|
} catch {
|
||||||
|
message.error('获取查询详情失败');
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleCopied(_event: JsonViewerAction) {
|
||||||
|
message.success('已复制JSON');
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
fetchQueryDetail();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Page>
|
||||||
|
<div class="p-4">
|
||||||
|
<div class="mb-4 flex items-center">
|
||||||
|
<Button @click="handleBack">
|
||||||
|
<template #icon><MdiArrowLeft /></template>
|
||||||
|
返回订单管理
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Card :loading="loading" class="mb-4">
|
||||||
|
<template #title>
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<span class="text-lg font-medium">订单查询详情</span>
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<span class="text-gray-500">查询状态:</span>
|
||||||
|
<Tag
|
||||||
|
v-if="queryDetail"
|
||||||
|
:color="getQueryStateConfig(queryDetail.query_state).color"
|
||||||
|
>
|
||||||
|
{{ getQueryStateConfig(queryDetail.query_state).label }}
|
||||||
|
</Tag>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template v-if="queryDetail">
|
||||||
|
<Descriptions :column="2" bordered>
|
||||||
|
<Descriptions.Item label="订单ID">
|
||||||
|
{{ queryDetail.order_id }}
|
||||||
|
</Descriptions.Item>
|
||||||
|
<Descriptions.Item label="用户ID">
|
||||||
|
{{ queryDetail.user_id }}
|
||||||
|
</Descriptions.Item>
|
||||||
|
<Descriptions.Item label="产品名称">
|
||||||
|
{{ queryDetail.product_name }}
|
||||||
|
</Descriptions.Item>
|
||||||
|
<Descriptions.Item label="创建时间">
|
||||||
|
{{ queryDetail.create_time }}
|
||||||
|
</Descriptions.Item>
|
||||||
|
<Descriptions.Item label="更新时间">
|
||||||
|
{{ queryDetail.update_time }}
|
||||||
|
</Descriptions.Item>
|
||||||
|
</Descriptions>
|
||||||
|
</template>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<template v-if="queryDetail">
|
||||||
|
<Card class="mb-4">
|
||||||
|
<template #title>
|
||||||
|
<span class="text-lg font-medium">查询参数</span>
|
||||||
|
</template>
|
||||||
|
<template v-if="queryDetail.query_params">
|
||||||
|
<Descriptions :column="2" bordered>
|
||||||
|
<Descriptions.Item
|
||||||
|
v-for="(value, key) in queryDetail.query_params"
|
||||||
|
:key="key"
|
||||||
|
:label="getFieldDisplayName(key)"
|
||||||
|
>
|
||||||
|
{{ value }}
|
||||||
|
</Descriptions.Item>
|
||||||
|
</Descriptions>
|
||||||
|
</template>
|
||||||
|
<div v-else class="text-gray-500">暂无查询参数</div>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<Card>
|
||||||
|
<template #title>
|
||||||
|
<span class="text-lg font-medium">查询数据</span>
|
||||||
|
</template>
|
||||||
|
<template v-if="queryDetail.query_data?.length">
|
||||||
|
<Collapse
|
||||||
|
:default-active-key="
|
||||||
|
queryDetail.query_data.map((_, index) => index)
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<Collapse.Panel
|
||||||
|
v-for="(item, index) in queryDetail.query_data"
|
||||||
|
:key="index"
|
||||||
|
>
|
||||||
|
<template #header>
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<span class="text-lg font-medium">{{
|
||||||
|
item.feature.featureName
|
||||||
|
}}</span>
|
||||||
|
<Tag color="blue">API: {{ item.data.apiID }}</Tag>
|
||||||
|
</div>
|
||||||
|
<Tag
|
||||||
|
:color="
|
||||||
|
String(item.data.success) === 'true'
|
||||||
|
? 'success'
|
||||||
|
: 'error'
|
||||||
|
"
|
||||||
|
>
|
||||||
|
{{
|
||||||
|
String(item.data.success) === 'true'
|
||||||
|
? '查询成功'
|
||||||
|
: '查询失败'
|
||||||
|
}}
|
||||||
|
</Tag>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<div class="grid gap-4">
|
||||||
|
<div v-if="item.data.data">
|
||||||
|
<div class="mb-2 font-medium">查询结果:</div>
|
||||||
|
<JsonViewer
|
||||||
|
:value="item.data.data"
|
||||||
|
copyable
|
||||||
|
:expand-depth="2"
|
||||||
|
boxed
|
||||||
|
@copied="handleCopied"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="text-gray-500">
|
||||||
|
查询时间: {{ item.data.timestamp }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Collapse.Panel>
|
||||||
|
</Collapse>
|
||||||
|
</template>
|
||||||
|
<div v-else class="py-4 text-center text-gray-500">暂无查询数据</div>
|
||||||
|
</Card>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<Card>
|
||||||
|
<div class="py-8 text-center text-gray-500">
|
||||||
|
{{ loading ? '加载中...' : '暂无查询数据' }}
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</Page>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
:deep(.ant-collapse-header) {
|
||||||
|
padding: 12px 16px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.ant-collapse-content-box) {
|
||||||
|
padding: 16px !important;
|
||||||
|
}
|
||||||
|
</style>
|
@ -1,5 +1,6 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type { TableColumnsType } from 'ant-design-vue';
|
import type { TableColumnsType } from 'ant-design-vue';
|
||||||
|
// @ts-expect-error: sortablejs 没有类型声明
|
||||||
import type { SortableEvent } from 'sortablejs';
|
import type { SortableEvent } from 'sortablejs';
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
@ -72,6 +73,7 @@ const [Modal, modalApi] = useVbenModal({
|
|||||||
await updateProductFeatures(productId, { features });
|
await updateProductFeatures(productId, { features });
|
||||||
message.success('保存成功');
|
message.success('保存成功');
|
||||||
emit('success');
|
emit('success');
|
||||||
|
modalApi.close(); // 保存成功后关闭Modal
|
||||||
return true;
|
return true;
|
||||||
} catch {
|
} catch {
|
||||||
message.error('保存失败');
|
message.error('保存失败');
|
||||||
@ -84,19 +86,15 @@ const loading = ref(false);
|
|||||||
const tempFeatureList = ref<TempFeatureItem[]>([]);
|
const tempFeatureList = ref<TempFeatureItem[]>([]);
|
||||||
|
|
||||||
// 表格配置
|
// 表格配置
|
||||||
const [Grid, _gridApi] = useVbenVxeGrid({
|
const [Grid] = useVbenVxeGrid({
|
||||||
formOptions: {
|
formOptions: {
|
||||||
schema: useGridFormSchema(),
|
schema: useGridFormSchema(),
|
||||||
submitOnChange: true,
|
submitOnChange: true,
|
||||||
|
showCollapseButton: false,
|
||||||
},
|
},
|
||||||
|
separator: false,
|
||||||
gridOptions: {
|
gridOptions: {
|
||||||
columns: (
|
columns: (useColumns(onActionClick) || []).map((col) => {
|
||||||
useColumns((e: OnActionClickParams<FeatureApi.FeatureItem>) => {
|
|
||||||
if (e.code === 'add' && e.row) {
|
|
||||||
handleAddFeature(e.row);
|
|
||||||
}
|
|
||||||
}) || []
|
|
||||||
).map((col) => {
|
|
||||||
if (col.field === 'operation' && col.cellRender) {
|
if (col.field === 'operation' && col.cellRender) {
|
||||||
return {
|
return {
|
||||||
...col,
|
...col,
|
||||||
@ -105,13 +103,23 @@ const [Grid, _gridApi] = useVbenVxeGrid({
|
|||||||
options: [
|
options: [
|
||||||
{
|
{
|
||||||
code: 'add',
|
code: 'add',
|
||||||
title: '添加',
|
text: '添加',
|
||||||
show: (row: FeatureApi.FeatureItem) => {
|
show: (row: FeatureApi.FeatureItem) => {
|
||||||
return !tempFeatureList.value.some(
|
return !tempFeatureList.value.some(
|
||||||
(item) => item.feature_id === row.id,
|
(item) => item.feature_id === row.id,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
code: 'added',
|
||||||
|
text: '已添加',
|
||||||
|
disabled: true,
|
||||||
|
show: (row: FeatureApi.FeatureItem) => {
|
||||||
|
return tempFeatureList.value.some(
|
||||||
|
(item) => item.feature_id === row.id,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@ -120,6 +128,11 @@ const [Grid, _gridApi] = useVbenVxeGrid({
|
|||||||
}),
|
}),
|
||||||
height: 500,
|
height: 500,
|
||||||
keepSource: true,
|
keepSource: true,
|
||||||
|
pagerConfig: {
|
||||||
|
pageSize: 8,
|
||||||
|
pageSizes: [8, 20, 50, 100],
|
||||||
|
pagerCount: 5,
|
||||||
|
},
|
||||||
proxyConfig: {
|
proxyConfig: {
|
||||||
ajax: {
|
ajax: {
|
||||||
query: async ({ page }, formValues) => {
|
query: async ({ page }, formValues) => {
|
||||||
@ -134,13 +147,6 @@ const [Grid, _gridApi] = useVbenVxeGrid({
|
|||||||
rowConfig: {
|
rowConfig: {
|
||||||
keyField: 'id',
|
keyField: 'id',
|
||||||
},
|
},
|
||||||
toolbarConfig: {
|
|
||||||
custom: true,
|
|
||||||
export: false,
|
|
||||||
refresh: { code: 'query' },
|
|
||||||
search: true,
|
|
||||||
zoom: true,
|
|
||||||
},
|
|
||||||
} as VxeTableGridOptions<FeatureApi.FeatureItem>,
|
} as VxeTableGridOptions<FeatureApi.FeatureItem>,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -215,10 +221,22 @@ async function loadFeatureList() {
|
|||||||
try {
|
try {
|
||||||
const res = await getProductFeatureList(productId);
|
const res = await getProductFeatureList(productId);
|
||||||
// 转换为临时数据格式
|
// 转换为临时数据格式
|
||||||
tempFeatureList.value = res.map((item) => ({
|
let tempList = res.map((item) => ({
|
||||||
...item,
|
...item,
|
||||||
temp_id: `existing_${item.id}`,
|
temp_id: `existing_${item.id}`,
|
||||||
}));
|
}));
|
||||||
|
// 对sort字段进行排序,如果全为0则按原顺序赋递增sort
|
||||||
|
const allSortZero = tempList.every((item) => !item.sort || item.sort === 0);
|
||||||
|
if (allSortZero) {
|
||||||
|
tempList.forEach((item, idx) => {
|
||||||
|
item.sort = idx + 1;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
tempList = [...tempList]
|
||||||
|
.sort((a, b) => (a.sort || 0) - (b.sort || 0))
|
||||||
|
.map((item, idx) => ({ ...item, sort: idx + 1 }));
|
||||||
|
}
|
||||||
|
tempFeatureList.value = tempList;
|
||||||
initSortable();
|
initSortable();
|
||||||
} finally {
|
} finally {
|
||||||
loading.value = false;
|
loading.value = false;
|
||||||
@ -234,7 +252,8 @@ async function initSortable() {
|
|||||||
animation: 150,
|
animation: 150,
|
||||||
handle: '.ant-table-row',
|
handle: '.ant-table-row',
|
||||||
onEnd: async (evt: SortableEvent) => {
|
onEnd: async (evt: SortableEvent) => {
|
||||||
const { newIndex, oldIndex } = evt;
|
let { newIndex, oldIndex } = evt;
|
||||||
|
// 兼容性保护,如果为 undefined/null 则不处理
|
||||||
if (
|
if (
|
||||||
typeof newIndex !== 'number' ||
|
typeof newIndex !== 'number' ||
|
||||||
typeof oldIndex !== 'number' ||
|
typeof oldIndex !== 'number' ||
|
||||||
@ -242,6 +261,10 @@ async function initSortable() {
|
|||||||
)
|
)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
// 1-based 转为 0-based
|
||||||
|
newIndex = newIndex - 1;
|
||||||
|
oldIndex = oldIndex - 1;
|
||||||
|
|
||||||
// 重新排序列表
|
// 重新排序列表
|
||||||
const newList = [...tempFeatureList.value];
|
const newList = [...tempFeatureList.value];
|
||||||
const [removed] = newList.splice(oldIndex, 1);
|
const [removed] = newList.splice(oldIndex, 1);
|
||||||
@ -258,7 +281,15 @@ async function initSortable() {
|
|||||||
|
|
||||||
await initializeSortable();
|
await initializeSortable();
|
||||||
}
|
}
|
||||||
|
// 操作处理函数
|
||||||
|
function onActionClick(e: OnActionClickParams<FeatureApi.FeatureItem>) {
|
||||||
|
switch (e.code) {
|
||||||
|
case 'add': {
|
||||||
|
handleAddFeature(e.row);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
// 处理添加模块
|
// 处理添加模块
|
||||||
function handleAddFeature(feature: FeatureApi.FeatureItem) {
|
function handleAddFeature(feature: FeatureApi.FeatureItem) {
|
||||||
// 获取当前最大排序值
|
// 获取当前最大排序值
|
||||||
@ -299,20 +330,22 @@ function handleRemoveFeature(record: TempFeatureItem) {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Modal class="w-[calc(100vw-200px)]">
|
<Modal class="w-[calc(100vw-200px)]">
|
||||||
<div class="p-4">
|
<div class="px-2">
|
||||||
<div class="mb-4 text-gray-500">
|
|
||||||
提示:可以通过拖拽行来调整模块顺序,通过开关控制模块的启用状态和重要程度
|
|
||||||
</div>
|
|
||||||
<div class="flex gap-4">
|
<div class="flex gap-4">
|
||||||
<!-- 左侧:可选模块列表 -->
|
<!-- 左侧:可选模块列表 -->
|
||||||
<div class="w-[600px] flex-shrink-0">
|
<div class="w-[600px] flex-shrink-0">
|
||||||
<div class="mb-2 text-base font-medium">可选模块</div>
|
<!-- <div class="mb-2 text-base font-medium">可选模块</div>
|
||||||
|
<div class="mb-4 text-sm text-gray-500">
|
||||||
|
提示:点击添加可以快速添加模块到已关联模块列表
|
||||||
|
</div> -->
|
||||||
<Grid />
|
<Grid />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 右侧:已关联模块列表 -->
|
<!-- 右侧:已关联模块列表 -->
|
||||||
<div class="flex-1">
|
<div class="flex-1">
|
||||||
<div class="mb-2 text-base font-medium">已关联模块</div>
|
<div class="mb-2 text-base font-medium">已关联模块</div>
|
||||||
|
<div class="mb-4 text-sm text-gray-500">
|
||||||
|
提示:可以通过拖拽行来调整模块顺序,通过开关控制模块的启用状态和重要程度
|
||||||
|
</div>
|
||||||
<Table
|
<Table
|
||||||
:columns="columns"
|
:columns="columns"
|
||||||
:data-source="tempFeatureList"
|
:data-source="tempFeatureList"
|
||||||
@ -321,6 +354,7 @@ function handleRemoveFeature(record: TempFeatureItem) {
|
|||||||
:row-key="(record) => record.temp_id"
|
:row-key="(record) => record.temp_id"
|
||||||
:scroll="{ y: 500 }"
|
:scroll="{ y: 500 }"
|
||||||
class="sortable-table"
|
class="sortable-table"
|
||||||
|
size="small"
|
||||||
>
|
>
|
||||||
<template #bodyCell="{ column, record }">
|
<template #bodyCell="{ column, record }">
|
||||||
<template v-if="column.dataIndex === 'sort'">
|
<template v-if="column.dataIndex === 'sort'">
|
||||||
|
File diff suppressed because one or more lines are too long
@ -56,6 +56,10 @@
|
|||||||
"update:deps": "npx taze -r -w",
|
"update:deps": "npx taze -r -w",
|
||||||
"version": "pnpm exec changeset version && pnpm install --no-frozen-lockfile"
|
"version": "pnpm exec changeset version && pnpm install --no-frozen-lockfile"
|
||||||
},
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@wangeditor/editor": "^5.1.23",
|
||||||
|
"@wangeditor/editor-for-vue": "^5.1.12"
|
||||||
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@changesets/changelog-github": "catalog:",
|
"@changesets/changelog-github": "catalog:",
|
||||||
"@changesets/cli": "catalog:",
|
"@changesets/cli": "catalog:",
|
||||||
@ -114,9 +118,5 @@
|
|||||||
"canvas",
|
"canvas",
|
||||||
"node-gyp"
|
"node-gyp"
|
||||||
]
|
]
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"@wangeditor/editor": "^5.1.23",
|
|
||||||
"@wangeditor/editor-for-vue": "^5.1.12"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,3 +11,5 @@ export const MdiGithub = createIconifyIcon('mdi:github');
|
|||||||
export const MdiGoogle = createIconifyIcon('mdi:google');
|
export const MdiGoogle = createIconifyIcon('mdi:google');
|
||||||
|
|
||||||
export const MdiQqchat = createIconifyIcon('mdi:qqchat');
|
export const MdiQqchat = createIconifyIcon('mdi:qqchat');
|
||||||
|
|
||||||
|
export const MdiArrowLeft = createIconifyIcon('mdi:arrow-left');
|
||||||
|
Loading…
Reference in New Issue
Block a user