feat(projects): 1、站内信、通知功能完善;2、项目列表按会议需求重新开发
This commit is contained in:
@@ -2,11 +2,14 @@ export * from './auth';
|
||||
export * from './dict';
|
||||
export * from './file';
|
||||
export * from './infra';
|
||||
export * from './notice';
|
||||
export * from './notify-message';
|
||||
export * from './object-context';
|
||||
export * from './overtime-application';
|
||||
export * from './personal-item';
|
||||
export * from './product';
|
||||
export * from './project';
|
||||
export * from './project-group';
|
||||
export * from './project-shared';
|
||||
export * from './route';
|
||||
export * from './system-manage';
|
||||
|
||||
28
src/service/api/notice.ts
Normal file
28
src/service/api/notice.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import { SYSTEM_SERVICE_PREFIX } from '@/constants/service';
|
||||
import { request } from '../request';
|
||||
import { type ServiceRequestResult, mapServiceResult, normalizeStringId, safeJsonRequestConfig } from './shared';
|
||||
|
||||
const NOTICE_PREFIX = `${SYSTEM_SERVICE_PREFIX}/notice`;
|
||||
|
||||
type NoticeResponse = Omit<Api.Notice.Notice, 'id'> & {
|
||||
id: string | number;
|
||||
};
|
||||
|
||||
function normalizeNotice(data: NoticeResponse): Api.Notice.Notice {
|
||||
return {
|
||||
...data,
|
||||
id: normalizeStringId(data.id)
|
||||
};
|
||||
}
|
||||
|
||||
/** 获取最近公告(status=0,按 id 倒序;登录即可,工作台公告卡片用) */
|
||||
export async function fetchGetRecentNotices(size?: number) {
|
||||
const result = await request<NoticeResponse[]>({
|
||||
url: `${NOTICE_PREFIX}/recent`,
|
||||
method: 'get',
|
||||
params: { size },
|
||||
...safeJsonRequestConfig
|
||||
});
|
||||
|
||||
return mapServiceResult(result as ServiceRequestResult<NoticeResponse[]>, data => data.map(normalizeNotice));
|
||||
}
|
||||
60
src/service/api/notify-message.ts
Normal file
60
src/service/api/notify-message.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
import { SYSTEM_SERVICE_PREFIX } from '@/constants/service';
|
||||
import { request } from '../request';
|
||||
import { type ServiceRequestResult, mapServiceResult, normalizeStringId, safeJsonRequestConfig } from './shared';
|
||||
|
||||
const NOTIFY_MESSAGE_PREFIX = `${SYSTEM_SERVICE_PREFIX}/notify-message`;
|
||||
|
||||
type NotifyMessageResponse = Omit<Api.NotifyMessage.NotifyMessage, 'id'> & {
|
||||
id: string | number;
|
||||
};
|
||||
|
||||
type MyNotifyMessagePageResponse = Omit<Api.NotifyMessage.PageResult<Api.NotifyMessage.NotifyMessage>, 'list'> & {
|
||||
list: NotifyMessageResponse[];
|
||||
};
|
||||
|
||||
function normalizeNotifyMessage(data: NotifyMessageResponse): Api.NotifyMessage.NotifyMessage {
|
||||
return {
|
||||
...data,
|
||||
id: normalizeStringId(data.id)
|
||||
};
|
||||
}
|
||||
|
||||
/** 获取当前用户未读站内信数量(铃铛红点轮询用) */
|
||||
export function fetchGetUnreadNotifyCount() {
|
||||
return request<number>({
|
||||
url: `${NOTIFY_MESSAGE_PREFIX}/get-unread-count`,
|
||||
method: 'get'
|
||||
});
|
||||
}
|
||||
|
||||
/** 分页获取我的站内信(消息列表唯一数据源;未读传 readStatus=false、已读传 true) */
|
||||
export async function fetchGetMyNotifyMessagePage(params: Api.NotifyMessage.MyPageParams) {
|
||||
const result = await request<MyNotifyMessagePageResponse>({
|
||||
url: `${NOTIFY_MESSAGE_PREFIX}/my-page`,
|
||||
method: 'get',
|
||||
params,
|
||||
...safeJsonRequestConfig
|
||||
});
|
||||
|
||||
return mapServiceResult(result as ServiceRequestResult<MyNotifyMessagePageResponse>, data => ({
|
||||
...data,
|
||||
list: data.list.map(normalizeNotifyMessage)
|
||||
}));
|
||||
}
|
||||
|
||||
/** 批量标记站内信已读(后端幂等:重复提交、非本人条目均安全) */
|
||||
export function fetchUpdateNotifyMessageRead(ids: string[]) {
|
||||
// 后端约定 ids 逗号分隔
|
||||
return request<boolean>({
|
||||
url: `${NOTIFY_MESSAGE_PREFIX}/update-read?ids=${ids.join(',')}`,
|
||||
method: 'put'
|
||||
});
|
||||
}
|
||||
|
||||
/** 当前用户全部站内信标记已读 */
|
||||
export function fetchUpdateAllNotifyMessageRead() {
|
||||
return request<boolean>({
|
||||
url: `${NOTIFY_MESSAGE_PREFIX}/update-all-read`,
|
||||
method: 'put'
|
||||
});
|
||||
}
|
||||
@@ -106,13 +106,34 @@ export async function fetchGetProductPage(params?: Api.Product.ProductSearchPara
|
||||
}));
|
||||
}
|
||||
|
||||
type ProductOverviewSummaryResponse = Omit<Api.Product.ProductOverviewSummary, 'total' | 'items'> & {
|
||||
/** 后端 overview-summary 升级(total/items)灰度期间可能缺省,适配层兜底 */
|
||||
total?: number | null;
|
||||
items?: Api.Product.OverviewStatusItem[] | null;
|
||||
};
|
||||
|
||||
/** 归一化产品概览统计:total/items 兜底,保证业务层拿到完整结构 */
|
||||
function normalizeProductOverviewSummary(data: ProductOverviewSummaryResponse): Api.Product.ProductOverviewSummary {
|
||||
return {
|
||||
...data,
|
||||
statusCounts: data.statusCounts ?? {},
|
||||
total: data.total ?? 0,
|
||||
items: data.items ?? []
|
||||
};
|
||||
}
|
||||
|
||||
/** 获取产品入口页概览统计 */
|
||||
export function fetchGetProductOverviewSummary() {
|
||||
return request<Api.Product.ProductOverviewSummary>({
|
||||
export async function fetchGetProductOverviewSummary() {
|
||||
const result = await request<ProductOverviewSummaryResponse>({
|
||||
...safeJsonRequestConfig,
|
||||
url: `${PRODUCT_PREFIX}/overview-summary`,
|
||||
method: 'get'
|
||||
});
|
||||
|
||||
return mapServiceResult(
|
||||
result as ServiceRequestResult<ProductOverviewSummaryResponse>,
|
||||
normalizeProductOverviewSummary
|
||||
);
|
||||
}
|
||||
|
||||
/** 获取产品详情 */
|
||||
|
||||
62
src/service/api/project-group.ts
Normal file
62
src/service/api/project-group.ts
Normal file
@@ -0,0 +1,62 @@
|
||||
import { WEB_SERVICE_PREFIX } from '@/constants/service';
|
||||
import { request } from '../request';
|
||||
import {
|
||||
type ServiceRequestResult,
|
||||
mapServiceResult,
|
||||
normalizeNullableStringId,
|
||||
safeJsonRequestConfig
|
||||
} from './shared';
|
||||
import { type ProjectResponse, normalizeProject } from './project';
|
||||
|
||||
const PROJECT_PREFIX = `${WEB_SERVICE_PREFIX}/project/project`;
|
||||
|
||||
/**
|
||||
* group-page 原始响应。
|
||||
* 组级 managerUserId、productId:后端对小数值 Long(如 1001)仍按数字返回,需 String() 归一;
|
||||
* projects 字段与 page 接口项目行完全一致,复用 ProjectResponse / normalizeProject。
|
||||
*/
|
||||
type ProjectGroupResponse = Omit<Api.Project.ProjectGroup, 'productId' | 'managerUserId' | 'projects'> & {
|
||||
productId?: string | number | null;
|
||||
managerUserId?: string | number | null;
|
||||
projects: ProjectResponse[];
|
||||
};
|
||||
|
||||
type ProjectGroupPageResponse = Omit<Api.Project.ProjectGroupPageResult, 'list'> & {
|
||||
list: ProjectGroupResponse[];
|
||||
};
|
||||
|
||||
/** 归一化分组:组级 ID String 化,组内项目复用 normalizeProject(id/managerUserId/productId/日期统一口径) */
|
||||
function normalizeProjectGroup(group: ProjectGroupResponse): Api.Project.ProjectGroup {
|
||||
return {
|
||||
...group,
|
||||
productId: normalizeNullableStringId(group.productId),
|
||||
managerUserId: normalizeNullableStringId(group.managerUserId),
|
||||
projects: Array.isArray(group.projects) ? group.projects.map(normalizeProject) : []
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 项目列表「按产品分组」分页。
|
||||
*
|
||||
* 后端契约见《项目列表产品分组-前端API-2026-06-10》:
|
||||
* - pageNo/pageSize 为产品组维度分页;statusCode 不传 = 「全部」口径(后端从状态机推导,
|
||||
* 当前等价 pending/active/paused/completed,不含 cancelled/archived)。
|
||||
* - 组内 projects 仅返前 topN 条(默认 5),projectTotal 为该口径组内全量计数;
|
||||
* 剩余项目由页面按 productId / orphanOnly + statusCodes 走 page 接口展开拉取。
|
||||
* - typeCounts / hasBaseline 现状恒按「全部」口径统计,不随 statusCode 变化;其中 typeCounts 已提需求
|
||||
* 改为与 projectTotal 同口径(见《2026-06-11-项目分组接口typeCounts口径-后端接口需求》),后端落地后更新本注释;
|
||||
* hasBaseline = 存在非已取消的主线项目(已归档/完成也算占坑),前端直接消费、不自行推导。
|
||||
*/
|
||||
export async function fetchGetProjectGroupPage(params?: Api.Project.ProjectGroupSearchParams) {
|
||||
const result = await request<ProjectGroupPageResponse>({
|
||||
...safeJsonRequestConfig,
|
||||
url: `${PROJECT_PREFIX}/group-page`,
|
||||
method: 'get',
|
||||
params
|
||||
});
|
||||
|
||||
return mapServiceResult(result as ServiceRequestResult<ProjectGroupPageResponse>, data => ({
|
||||
...data,
|
||||
list: Array.isArray(data.list) ? data.list.map(normalizeProjectGroup) : []
|
||||
}));
|
||||
}
|
||||
@@ -37,7 +37,7 @@ import {
|
||||
|
||||
const PROJECT_PREFIX = `${WEB_SERVICE_PREFIX}/project/project`;
|
||||
|
||||
type ProjectResponse = Omit<
|
||||
export type ProjectResponse = Omit<
|
||||
Api.Project.Project,
|
||||
'id' | 'managerUserId' | 'productId' | 'plannedStartDate' | 'plannedEndDate' | 'actualStartDate' | 'actualEndDate'
|
||||
> & {
|
||||
@@ -79,7 +79,7 @@ function getTaskPrefix(projectId: string, executionId: string) {
|
||||
}
|
||||
|
||||
/** 归一化项目数据 */
|
||||
function normalizeProject(project: ProjectResponse): Api.Project.Project {
|
||||
export function normalizeProject(project: ProjectResponse): Api.Project.Project {
|
||||
return {
|
||||
...project,
|
||||
id: normalizeStringId(project.id),
|
||||
@@ -136,13 +136,34 @@ export async function fetchGetProjectPage(params?: Api.Project.ProjectSearchPara
|
||||
}));
|
||||
}
|
||||
|
||||
type ProjectOverviewSummaryResponse = Omit<Api.Project.ProjectOverviewSummary, 'total' | 'items'> & {
|
||||
/** 后端 overview-summary 升级(total/items)灰度期间可能缺省,适配层兜底 */
|
||||
total?: number | null;
|
||||
items?: Api.Project.OverviewStatusItem[] | null;
|
||||
};
|
||||
|
||||
/** 归一化项目概览统计:total/items 兜底,保证业务层拿到完整结构 */
|
||||
function normalizeProjectOverviewSummary(data: ProjectOverviewSummaryResponse): Api.Project.ProjectOverviewSummary {
|
||||
return {
|
||||
...data,
|
||||
statusCounts: data.statusCounts ?? {},
|
||||
total: data.total ?? 0,
|
||||
items: data.items ?? []
|
||||
};
|
||||
}
|
||||
|
||||
/** 获取项目入口页概览统计 */
|
||||
export function fetchGetProjectOverviewSummary() {
|
||||
return request<Api.Project.ProjectOverviewSummary>({
|
||||
export async function fetchGetProjectOverviewSummary() {
|
||||
const result = await request<ProjectOverviewSummaryResponse>({
|
||||
...safeJsonRequestConfig,
|
||||
url: `${PROJECT_PREFIX}/overview-summary`,
|
||||
method: 'get'
|
||||
});
|
||||
|
||||
return mapServiceResult(
|
||||
result as ServiceRequestResult<ProjectOverviewSummaryResponse>,
|
||||
normalizeProjectOverviewSummary
|
||||
);
|
||||
}
|
||||
|
||||
/** 获取项目详情 */
|
||||
|
||||
Reference in New Issue
Block a user