feat(order): 添加用户查单总结功能

- 新增用户查单总结接口及相关请求响应模型
- 在订单查询页面展示AI智能总结区域
- 支持查询商户订单号以获取总结信息
- 总结加载时显示loading状态,支持异常提示
- 优化订单详情和总结数据并行获取体验
- 完善接口文档及示例代码
This commit is contained in:
danial
2025-11-28 22:09:22 +08:00
parent 52e2369d4b
commit 9d50d37af3
9 changed files with 398 additions and 18 deletions

View File

@@ -337,6 +337,8 @@ docs/KamiApiMerchantV1MerchantSampleAllListRes.md
docs/KamiApiMerchantV1OrderQueryRecord.md
docs/KamiApiMerchantV1OrderQueryReq.md
docs/KamiApiMerchantV1OrderQueryRes.md
docs/KamiApiMerchantV1OrderQuerySummaryReq.md
docs/KamiApiMerchantV1OrderQuerySummaryRes.md
docs/KamiApiMerchantV1PlatformRateRecord.md
docs/KamiApiMerchantV1SampleAllListRes.md
docs/KamiApiMerchantV1StealCreateReq.md
@@ -767,6 +769,8 @@ models/kami-api-merchant-v1-merchant-sample-all-list-res.ts
models/kami-api-merchant-v1-order-query-record.ts
models/kami-api-merchant-v1-order-query-req.ts
models/kami-api-merchant-v1-order-query-res.ts
models/kami-api-merchant-v1-order-query-summary-req.ts
models/kami-api-merchant-v1-order-query-summary-res.ts
models/kami-api-merchant-v1-platform-rate-record.ts
models/kami-api-merchant-v1-sample-all-list-res.ts
models/kami-api-merchant-v1-steal-create-req.ts

View File

@@ -307,6 +307,8 @@ import type { KamiApiMerchantV1MerchantSampleAllListRes } from '../models';
// @ts-ignore
import type { KamiApiMerchantV1OrderQueryRes } from '../models';
// @ts-ignore
import type { KamiApiMerchantV1OrderQuerySummaryRes } from '../models';
// @ts-ignore
import type { KamiApiMerchantV1StealCreateReq } from '../models';
// @ts-ignore
import type { KamiApiMerchantV1StealListRes } from '../models';
@@ -7851,6 +7853,51 @@ export const DefaultApiAxiosParamCreator = function (
options: localVarRequestOptions
};
},
/**
*
* @summary 用户查单总结
* @param {string} [merchantOrderNo] 商户订单号
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
apiMerchantOrderSummaryGet: async (
merchantOrderNo?: string,
options: RawAxiosRequestConfig = {}
): Promise<RequestArgs> => {
const localVarPath = `/api/merchant/order/summary`;
// use dummy base URL string because the URL constructor only accepts absolute URLs.
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
let baseOptions;
if (configuration) {
baseOptions = configuration.baseOptions;
}
const localVarRequestOptions = {
method: 'GET',
...baseOptions,
...options
};
const localVarHeaderParameter = {} as any;
const localVarQueryParameter = {} as any;
if (merchantOrderNo !== undefined) {
localVarQueryParameter['merchantOrderNo'] = merchantOrderNo;
}
setSearchParams(localVarUrlObj, localVarQueryParameter);
let headersFromBaseOptions =
baseOptions && baseOptions.headers ? baseOptions.headers : {};
localVarRequestOptions.headers = {
...localVarHeaderParameter,
...headersFromBaseOptions,
...options.headers
};
return {
url: toPathString(localVarUrlObj),
options: localVarRequestOptions
};
},
/**
*
* @summary 健康检查
@@ -15802,6 +15849,40 @@ export const DefaultApiFp = function (configuration?: Configuration) {
configuration
)(axios, localVarOperationServerBasePath || basePath);
},
/**
*
* @summary 用户查单总结
* @param {string} [merchantOrderNo] 商户订单号
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
async apiMerchantOrderSummaryGet(
merchantOrderNo?: string,
options?: RawAxiosRequestConfig
): Promise<
(
axios?: AxiosInstance,
basePath?: string
) => AxiosPromise<KamiApiMerchantV1OrderQuerySummaryRes>
> {
const localVarAxiosArgs =
await localVarAxiosParamCreator.apiMerchantOrderSummaryGet(
merchantOrderNo,
options
);
const localVarOperationServerIndex = configuration?.serverIndex ?? 0;
const localVarOperationServerBasePath =
operationServerMap['DefaultApi.apiMerchantOrderSummaryGet']?.[
localVarOperationServerIndex
]?.url;
return (axios, basePath) =>
createRequestFunction(
localVarAxiosArgs,
globalAxios,
BASE_PATH,
configuration
)(axios, localVarOperationServerBasePath || basePath);
},
/**
*
* @summary 健康检查
@@ -20368,6 +20449,21 @@ export const DefaultApiFactory = function (
.apiMerchantOrderQueryGet(requestParameters.merchantOrderNo, options)
.then(request => request(axios, basePath));
},
/**
*
* @summary 用户查单总结
* @param {DefaultApiApiMerchantOrderSummaryGetRequest} requestParameters Request parameters.
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
apiMerchantOrderSummaryGet(
requestParameters: DefaultApiApiMerchantOrderSummaryGetRequest = {},
options?: RawAxiosRequestConfig
): AxiosPromise<KamiApiMerchantV1OrderQuerySummaryRes> {
return localVarFp
.apiMerchantOrderSummaryGet(requestParameters.merchantOrderNo, options)
.then(request => request(axios, basePath));
},
/**
*
* @summary 健康检查
@@ -23053,6 +23149,18 @@ export interface DefaultApiInterface {
options?: RawAxiosRequestConfig
): AxiosPromise<KamiApiMerchantV1OrderQueryRes>;
/**
*
* @summary 用户查单总结
* @param {DefaultApiApiMerchantOrderSummaryGetRequest} requestParameters Request parameters.
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
apiMerchantOrderSummaryGet(
requestParameters?: DefaultApiApiMerchantOrderSummaryGetRequest,
options?: RawAxiosRequestConfig
): AxiosPromise<KamiApiMerchantV1OrderQuerySummaryRes>;
/**
*
* @summary 健康检查
@@ -25319,6 +25427,16 @@ export interface DefaultApiApiMerchantOrderQueryGetRequest {
readonly merchantOrderNo?: string;
}
/**
* Request parameters for apiMerchantOrderSummaryGet operation in DefaultApi.
*/
export interface DefaultApiApiMerchantOrderSummaryGetRequest {
/**
* 商户订单号
*/
readonly merchantOrderNo?: string;
}
/**
* Request parameters for apiOrderInfoSummaryDailyStatsGet operation in DefaultApi.
*/
@@ -28572,6 +28690,22 @@ export class DefaultApi extends BaseAPI implements DefaultApiInterface {
.then(request => request(this.axios, this.basePath));
}
/**
*
* @summary 用户查单总结
* @param {DefaultApiApiMerchantOrderSummaryGetRequest} requestParameters Request parameters.
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
public apiMerchantOrderSummaryGet(
requestParameters: DefaultApiApiMerchantOrderSummaryGetRequest = {},
options?: RawAxiosRequestConfig
) {
return DefaultApiFp(this.configuration)
.apiMerchantOrderSummaryGet(requestParameters.merchantOrderNo, options)
.then(request => request(this.axios, this.basePath));
}
/**
*
* @summary 健康检查

View File

@@ -143,6 +143,7 @@ All URIs are relative to _http://localhost_
| [**apiMerchantListAllGet**](#apimerchantlistallget) | **GET** /api/merchant/list/all | 获取商户配置选择列表 |
| [**apiMerchantListSampleAllGet**](#apimerchantlistsampleallget) | **GET** /api/merchant/list/sampleAll | 获取所有商户信息 |
| [**apiMerchantOrderQueryGet**](#apimerchantorderqueryget) | **GET** /api/merchant/order/query | 用户查单详情 |
| [**apiMerchantOrderSummaryGet**](#apimerchantordersummaryget) | **GET** /api/merchant/order/summary | 用户查单总结 |
| [**apiMonitorHeathcheckGet**](#apimonitorheathcheckget) | **GET** /api/monitor/heathcheck | 健康检查 |
| [**apiOrderInfoSummaryDailyStatsGet**](#apiorderinfosummarydailystatsget) | **GET** /api/orderInfo/summary/dailyStats | 获取订单每日汇总 |
| [**apiOrderInfoSummaryGetListGet**](#apiorderinfosummarygetlistget) | **GET** /api/orderInfo/summary/getList | 获取订单汇总 |
@@ -7147,6 +7148,51 @@ No authorization required
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
# **apiMerchantOrderSummaryGet**
> KamiApiMerchantV1OrderQuerySummaryRes apiMerchantOrderSummaryGet()
### Example
```typescript
import { DefaultApi, Configuration } from './api';
const configuration = new Configuration();
const apiInstance = new DefaultApi(configuration);
let merchantOrderNo: string; //商户订单号 (optional) (default to undefined)
const { status, data } =
await apiInstance.apiMerchantOrderSummaryGet(merchantOrderNo);
```
### Parameters
| Name | Type | Description | Notes |
| ------------------- | ------------ | ----------- | -------------------------------- |
| **merchantOrderNo** | [**string**] | 商户订单号 | (optional) defaults to undefined |
### Return type
**KamiApiMerchantV1OrderQuerySummaryRes**
### Authorization
No authorization required
### HTTP request headers
- **Content-Type**: Not defined
- **Accept**: application/json
### HTTP response details
| Status code | Description | Response headers |
| ----------- | ----------- | ---------------- |
| **200** | | - |
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
# **apiMonitorHeathcheckGet**
> KamiApiMonitorV1HealthCheckRes apiMonitorHeathcheckGet()

View File

@@ -0,0 +1,19 @@
# KamiApiMerchantV1OrderQuerySummaryReq
## Properties
| Name | Type | Description | Notes |
| ------------------- | ---------- | ----------- | --------------------------------- |
| **merchantOrderNo** | **string** | 商户订单号 | [optional] [default to undefined] |
## Example
```typescript
import { KamiApiMerchantV1OrderQuerySummaryReq } from './api';
const instance: KamiApiMerchantV1OrderQuerySummaryReq = {
merchantOrderNo
};
```
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)

View File

@@ -0,0 +1,19 @@
# KamiApiMerchantV1OrderQuerySummaryRes
## Properties
| Name | Type | Description | Notes |
| ----------- | ---------- | ----------- | --------------------------------- |
| **message** | **string** | 结论 | [optional] [default to undefined] |
## Example
```typescript
import { KamiApiMerchantV1OrderQuerySummaryRes } from './api';
const instance: KamiApiMerchantV1OrderQuerySummaryRes = {
message
};
```
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)

View File

@@ -316,6 +316,8 @@ export * from './kami-api-merchant-v1-merchant-sample-all-list-res';
export * from './kami-api-merchant-v1-order-query-record';
export * from './kami-api-merchant-v1-order-query-req';
export * from './kami-api-merchant-v1-order-query-res';
export * from './kami-api-merchant-v1-order-query-summary-req';
export * from './kami-api-merchant-v1-order-query-summary-res';
export * from './kami-api-merchant-v1-platform-rate-record';
export * from './kami-api-merchant-v1-sample-all-list-res';
export * from './kami-api-merchant-v1-steal-create-req';

View File

@@ -0,0 +1,20 @@
/* tslint:disable */
/**
*
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
*
* The version of the OpenAPI document:
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
export interface KamiApiMerchantV1OrderQuerySummaryReq {
/**
* 商户订单号
*/
merchantOrderNo?: string;
}

View File

@@ -0,0 +1,20 @@
/* tslint:disable */
/**
*
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
*
* The version of the OpenAPI document:
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
export interface KamiApiMerchantV1OrderQuerySummaryRes {
/**
* 结论
*/
message?: string;
}

View File

@@ -7,6 +7,7 @@
<a-input-search
size="large"
:loading="loading"
:disabled="loading"
placeholder="输入待查询的商户订单号"
search-button
@search="search"
@@ -27,41 +28,115 @@
查询结果
</a-typography-title>
<a-descriptions
v-show="renderData.createTime"
v-show="renderData.createTime || loading"
title="订单信息"
:column="2"
>
<a-descriptions-item label="平台订单号">
<a-tag>{{ state.value }}</a-tag>
<a-skeleton v-if="loading" :animation="true">
<a-skeleton-shape />
</a-skeleton>
<a-tag v-else>{{ state.value }}</a-tag>
</a-descriptions-item>
<a-descriptions-item label="系统订单号">
<a-tag>{{ renderData.bankOrderId }}</a-tag>
<a-skeleton v-if="loading" :animation="true">
<a-skeleton-shape />
</a-skeleton>
<a-tag v-else>{{ renderData.bankOrderId }}</a-tag>
</a-descriptions-item>
<a-descriptions-item label="订单时间">
<a-tag>{{ renderData.createTime }}</a-tag>
<a-skeleton v-if="loading" :animation="true">
<a-skeleton-shape />
</a-skeleton>
<a-tag v-else>{{ renderData.createTime }}</a-tag>
</a-descriptions-item>
<a-descriptions-item label="订单金额">
<a-tag>{{ renderData.amount }}</a-tag>
<a-skeleton v-if="loading" :animation="true">
<a-skeleton-shape />
</a-skeleton>
<a-tag v-else>{{ renderData.amount }}</a-tag>
</a-descriptions-item>
</a-descriptions>
<!-- AI 总结区域 -->
<div v-show="renderData?.list?.length || summaryLoading">
<a-divider />
<a-space direction="vertical" fill>
<a-typography-title :heading="5" style="text-align: center">
AI 智能总结
</a-typography-title>
<!-- 总结加载状态 -->
<div
v-if="summaryLoading"
style="text-align: center; padding: 20px 0"
>
<a-space direction="vertical" align="center" size="large">
<a-spin size="large" />
<a-typography-text type="secondary">
AI 正在分析订单数据请稍候...
</a-typography-text>
<a-typography-text type="secondary" style="font-size: 12px">
此过程可能需要较长时间感谢您的耐心等待
</a-typography-text>
</a-space>
</div>
<!-- 总结内容 -->
<a-card
v-else-if="summaryData?.message"
:bordered="false"
style="background-color: var(--color-fill-2)"
>
<template #extra>
<a-tag color="blue" size="small">
<icon-robot />
AI 生成
</a-tag>
</template>
<a-typography-paragraph
style="margin: 0; line-height: 1.6"
:deep="false"
>
{{ summaryData.message }}
</a-typography-paragraph>
</a-card>
<!-- 总结为空或错误状态 -->
<a-empty v-else description="AI 总结暂不可用" :image="null" />
</a-space>
</div>
<!-- 详细记录分割线 -->
<a-divider v-show="renderData?.list?.length">详细记录</a-divider>
<template v-if="loading && renderData?.list?.length">
<a-skeleton v-for="n in 3" :key="`skeleton-${n}`" :animation="true">
<a-skeleton-line :line-height="24" />
</a-skeleton>
</template>
<a-typography-paragraph
v-for="(item, index) in renderData?.list"
v-else
:key="index"
>
{{ item.createdAt }}{{ item.operation }} {{ item.remark }}
</a-typography-paragraph>
<a-typography-paragraph
v-show="!renderData?.list?.length"
v-show="!renderData?.list?.length && !loading"
style="text-align: center"
>
{{
loading
? '正在查询'
: state.value
? '没有查询到结果'
: '请输入查询条件'
}}
{{ state.value ? '没有查询到结果' : '请输入查询条件' }}
</a-typography-paragraph>
<a-space
v-show="loading && !renderData?.list?.length"
direction="vertical"
align="center"
size="large"
>
<a-spin size="large" />
<a-typography-text type="secondary">正在查询</a-typography-text>
</a-space>
</a-space>
</div>
</div>
@@ -69,25 +144,66 @@
<script lang="ts" setup>
import { apiClient } from '@/api';
import { KamiApiMerchantV1OrderQueryRes } from '@/api/generated';
import {
KamiApiMerchantV1OrderQueryRes,
KamiApiMerchantV1OrderQuerySummaryRes
} from '@/api/generated';
import useLoading from '@/hooks/loading';
import { Message } from '@arco-design/web-vue';
import { reactive, ref } from 'vue';
const renderData = ref<KamiApiMerchantV1OrderQueryRes>({});
const summaryData = ref<KamiApiMerchantV1OrderQuerySummaryRes | null>(null);
const { loading, setLoading } = useLoading(false);
const { loading: summaryLoading, setLoading: setSummaryLoading } =
useLoading(false);
const state = reactive({
value: ''
});
const search = async (value: string, ev: MouseEvent) => {
const fetchSummary = async (merchantOrderNo: string) => {
if (!merchantOrderNo) return;
try {
setSummaryLoading(true);
summaryData.value = null;
const summaryResponse = await apiClient.apiMerchantOrderSummaryGet({
merchantOrderNo
});
summaryData.value = summaryResponse.data;
} catch (error) {
console.error('获取 AI 总结失败:', error);
Message.warning('AI 总结获取失败,请稍后重试');
} finally {
setSummaryLoading(false);
}
};
const search = async (value: string) => {
try {
state.value = value;
setLoading(true);
const data = await apiClient.apiMerchantOrderQueryGet({
merchantOrderNo: value
});
// 重置总结数据
summaryData.value = null;
// 并行查询订单详情和 AI 总结
const [data] = await Promise.all([
apiClient.apiMerchantOrderQueryGet({
merchantOrderNo: value
}),
// 在有订单数据时才获取总结
fetchSummary(value).catch(() => null) // 忽略总结接口错误,不影响主查询
]);
renderData.value = data.data;
// 如果订单有详情但总结还未获取,再次尝试获取总结
if (data.data?.list?.length && !summaryLoading.value) {
fetchSummary(value);
}
} catch (error) {
Message.error('查询失败,请重试');
} finally {
setLoading(false);
}