feat(mobile-uni): 彻底完成登录页、首页、学习中心与问答页UI/UX极简重构及单条消息追问功能
This commit is contained in:
@@ -0,0 +1,122 @@
|
||||
import { apiRequest } from './api';
|
||||
|
||||
export const broadcastQuestionContextStorageKey = 'aihr_broadcast_question_context';
|
||||
export const broadcastQuestionContextTtlMs = 15 * 60 * 1000;
|
||||
|
||||
export interface BroadcastQuestionContext {
|
||||
messageId: number;
|
||||
accountKey: string;
|
||||
expiresAt: number;
|
||||
}
|
||||
|
||||
export interface BroadcastMessageSummary {
|
||||
id: number;
|
||||
title: string;
|
||||
read: boolean;
|
||||
publishedAt: string;
|
||||
}
|
||||
|
||||
export interface BroadcastMessage extends BroadcastMessageSummary {
|
||||
content: string;
|
||||
}
|
||||
|
||||
export interface BroadcastMessagePage {
|
||||
total: number;
|
||||
pageNum: number;
|
||||
pageSize: number;
|
||||
rows: BroadcastMessageSummary[];
|
||||
}
|
||||
|
||||
export const getBroadcastUnreadCount = async () => {
|
||||
const result = await apiRequest<{ unreadCount: number }>({
|
||||
url: '/api/aihr/broadcast/unread-count',
|
||||
method: 'GET',
|
||||
timeout: 15000
|
||||
});
|
||||
return Math.max(0, Number(result?.unreadCount) || 0);
|
||||
};
|
||||
|
||||
export const listBroadcastMessages = (pageNum = 1, pageSize = 20) =>
|
||||
apiRequest<BroadcastMessagePage>({
|
||||
url: `/api/aihr/broadcast/messages?pageNum=${pageNum}&pageSize=${pageSize}`,
|
||||
method: 'GET',
|
||||
timeout: 15000
|
||||
});
|
||||
|
||||
export const getBroadcastMessage = (id: number) =>
|
||||
apiRequest<BroadcastMessage>({
|
||||
url: `/api/aihr/broadcast/messages/${id}`,
|
||||
method: 'GET',
|
||||
timeout: 15000
|
||||
});
|
||||
|
||||
export const markBroadcastMessageRead = (id: number) =>
|
||||
apiRequest<void>({
|
||||
url: `/api/aihr/broadcast/messages/${id}/read`,
|
||||
method: 'POST',
|
||||
timeout: 15000
|
||||
});
|
||||
|
||||
const isSafeMessageId = (value: unknown): value is number =>
|
||||
typeof value === 'number' && Number.isSafeInteger(value) && value > 0;
|
||||
|
||||
const parseQuestionContext = (value: unknown): BroadcastQuestionContext | null => {
|
||||
if (!value || typeof value !== 'object') return null;
|
||||
const candidate = value as Partial<BroadcastQuestionContext>;
|
||||
return isSafeMessageId(candidate.messageId)
|
||||
&& typeof candidate.accountKey === 'string'
|
||||
&& candidate.accountKey.length > 0
|
||||
&& typeof candidate.expiresAt === 'number'
|
||||
&& Number.isSafeInteger(candidate.expiresAt)
|
||||
&& candidate.expiresAt > 0
|
||||
? {
|
||||
messageId: candidate.messageId,
|
||||
accountKey: candidate.accountKey,
|
||||
expiresAt: candidate.expiresAt
|
||||
}
|
||||
: null;
|
||||
};
|
||||
|
||||
export const clearBroadcastQuestionContext = () => {
|
||||
try {
|
||||
uni.removeStorageSync(broadcastQuestionContextStorageKey);
|
||||
} catch {
|
||||
// Storage is only a navigation handoff. A failed cleanup must not expose content.
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Stores only a short-lived, account-bound message identifier. The title, body and
|
||||
* tenant are deliberately never written to local storage or navigation URLs.
|
||||
*/
|
||||
export const saveBroadcastQuestionContext = (
|
||||
messageId: number,
|
||||
accountKey: string,
|
||||
ttlMs = broadcastQuestionContextTtlMs
|
||||
) => {
|
||||
if (!isSafeMessageId(messageId) || !accountKey || !Number.isSafeInteger(ttlMs) || ttlMs <= 0) return false;
|
||||
try {
|
||||
uni.setStorageSync(broadcastQuestionContextStorageKey, {
|
||||
messageId,
|
||||
accountKey,
|
||||
expiresAt: Date.now() + ttlMs
|
||||
} satisfies BroadcastQuestionContext);
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
/** Returns a valid account-bound context and clears stale, malformed or foreign data. */
|
||||
export const readBroadcastQuestionContext = (accountKey: string, now = Date.now()): BroadcastQuestionContext | null => {
|
||||
try {
|
||||
const context = parseQuestionContext(uni.getStorageSync(broadcastQuestionContextStorageKey));
|
||||
if (!context || context.accountKey !== accountKey || context.expiresAt <= now) {
|
||||
clearBroadcastQuestionContext();
|
||||
return null;
|
||||
}
|
||||
return context;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
@@ -83,7 +83,8 @@ export const searchKnowledge = async (
|
||||
position = getSelectedPosition(),
|
||||
conversation?: KnowledgeConversationState,
|
||||
projectCode?: string,
|
||||
source: 'mobile_uni_sop' | 'mobile_uni_voice' = 'mobile_uni_sop'
|
||||
source: 'mobile_uni_sop' | 'mobile_uni_voice' = 'mobile_uni_sop',
|
||||
broadcastMessageId?: number
|
||||
) => {
|
||||
const response = await apiRequest<UnifiedKnowledgeResponse>({
|
||||
url: '/api/knowledge/query',
|
||||
@@ -96,7 +97,10 @@ export const searchKnowledge = async (
|
||||
limit: 5,
|
||||
conversationId: conversation?.conversationId,
|
||||
contextVersion: conversation?.contextVersion,
|
||||
projectCode
|
||||
projectCode,
|
||||
...(typeof broadcastMessageId === 'number' && Number.isSafeInteger(broadcastMessageId) && broadcastMessageId > 0
|
||||
? { broadcastMessageId }
|
||||
: {})
|
||||
},
|
||||
timeout: 30000
|
||||
});
|
||||
@@ -198,7 +202,8 @@ const normalizeUnifiedResponse = (response: UnifiedKnowledgeResponse): Knowledge
|
||||
intent: response.intent,
|
||||
rewrittenQuery: response.rewrittenQuery,
|
||||
resources: response.resources || [],
|
||||
memoryCandidate: response.memoryCandidate || legacy.memoryCandidate
|
||||
memoryCandidate: response.memoryCandidate || legacy.memoryCandidate,
|
||||
broadcastContext: response.broadcastContext || legacy.broadcastContext || null
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import type { ProjectWorkResult, WorkResult } from '@/types/api';
|
||||
import { apiRequest } from './api';
|
||||
|
||||
const params = (projectCode: string, workDate?: string) => {
|
||||
const params = (projectCode: string, workDate?: string, pageNum?: number, pageSize?: number) => {
|
||||
const query = new URLSearchParams({ projectCode });
|
||||
if (workDate) query.set('workDate', workDate);
|
||||
if (pageNum) query.set('pageNum', String(pageNum));
|
||||
if (pageSize) query.set('pageSize', String(pageSize));
|
||||
return query.toString();
|
||||
};
|
||||
|
||||
@@ -14,9 +16,9 @@ export const generateMyWorkResult = (projectCode: string, workDate?: string) =>
|
||||
timeout: 20000
|
||||
});
|
||||
|
||||
export const getProjectWorkResult = (projectCode: string, workDate?: string) =>
|
||||
export const getProjectWorkResult = (projectCode: string, workDate?: string, pageNum = 1, pageSize = 20) =>
|
||||
apiRequest<ProjectWorkResult>({
|
||||
url: `/api/aihr/work-results/project?${params(projectCode, workDate)}`,
|
||||
url: `/api/aihr/work-results/project?${params(projectCode, workDate, pageNum, pageSize)}`,
|
||||
method: 'GET',
|
||||
timeout: 20000
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user