docs(api): 添加产品动态时间线前端API文档

- 新增产品动态时间线接口文档,明确前端调用规范
- 定义接口请求参数、响应结构和字段语义说明
- 提供请求示例和错误码说明
- 添加左侧筛选项映射规则和时间格式说明

feat(product): 实现产品首页动态时间线功能

- 重构产品首页布局结构,采用档案横幅型设计
- 新增对象基础概述横幅模块
- 实现产品动态时间线面板组件
- 集成需求池管理概览和最近变化区域
- 添加扩展信息区预留模块位

chore(docs): 更新代理工作说明和前端测试策略

- 添加前端任务测试策略说明
- 更新代理工作流程规范
- 明确git操作执行边界
- 优化组件类型声明更新
This commit is contained in:
2026-04-24 16:38:43 +08:00
parent 4122dfa50d
commit 5b9c7e781b
14 changed files with 3584 additions and 958 deletions

View File

@@ -18,6 +18,23 @@ type ProductResponse = Omit<Api.Product.Product, 'id' | 'managerUserId'> & {
type ProductPageResponse = Api.Product.PageResult<ProductResponse>;
type ProductActivityTimelineItemResponse = Omit<
Api.Product.ProductActivityTimelineItem,
'id' | 'operatorUserId' | 'targetUserId' | 'occurredAt'
> & {
id: string | number;
operatorUserId?: string | number | null;
targetUserId?: string | number | null;
occurredAt: number | string;
};
type ProductActivityTimelinePageResponse = Omit<
Api.Product.PageResult<ProductActivityTimelineItemResponse>,
'total'
> & {
total: number | string;
};
function normalizeProduct(product: ProductResponse): Api.Product.Product {
return {
...product,
@@ -26,6 +43,54 @@ function normalizeProduct(product: ProductResponse): Api.Product.Product {
};
}
function normalizeOccurredAt(occurredAt: number | string) {
const value = Number(occurredAt);
return Number.isFinite(value) ? value : 0;
}
function normalizePageTotal(total: number | string) {
const value = Number(total);
return Number.isFinite(value) ? Math.max(0, value) : 0;
}
function normalizeProductActivityTimelineItem(
item: ProductActivityTimelineItemResponse
): Api.Product.ProductActivityTimelineItem {
return {
...item,
id: normalizeStringId(item.id),
operatorUserId: normalizeNullableStringId(item.operatorUserId),
targetUserId: normalizeNullableStringId(item.targetUserId),
occurredAt: normalizeOccurredAt(item.occurredAt)
};
}
function createProductActivityTimelinePageQuery(params: Api.Product.ProductActivityTimelinePageParams) {
const query = new URLSearchParams();
query.append('pageNo', String(params.pageNo));
query.append('pageSize', String(params.pageSize));
if (params.activityType) {
query.append('activityType', params.activityType);
}
params.actionTypes?.forEach(actionType => {
if (actionType) {
query.append('actionTypes', actionType);
}
});
if (params.startTime && params.endTime) {
query.append('startTime', params.startTime);
query.append('endTime', params.endTime);
}
return query.toString();
}
/** 鑾峰彇浜у搧鍒嗛〉 */
export async function fetchGetProductPage(params?: Api.Product.ProductSearchParams) {
const result = await request<ProductPageResponse>({
@@ -123,6 +188,24 @@ export async function fetchGetProductMembers(id: string) {
);
}
export async function fetchGetProductActivityTimelinePage(
id: string,
params: Api.Product.ProductActivityTimelinePageParams
) {
const query = createProductActivityTimelinePageQuery(params);
const url = query ? `${PRODUCT_PREFIX}/${id}/activities/page?${query}` : `${PRODUCT_PREFIX}/${id}/activities/page`;
const result = await request<ProductActivityTimelinePageResponse>({
...safeJsonRequestConfig,
url,
method: 'get'
});
return mapServiceResult(result as ServiceRequestResult<ProductActivityTimelinePageResponse>, data => ({
total: normalizePageTotal(data.total),
list: data.list.map(normalizeProductActivityTimelineItem)
}));
}
export async function fetchCreateProductMember(id: string, data: Api.Product.CreateProductMemberParams) {
const result = await request<string | number>({
...safeJsonRequestConfig,