feat: full-text search (messages + customers)
Backend (yunke-server): - GET /api/search?q=&type=messages|customers|all&channel=&start=&end=&page=&page_size= LIKE '%q%' scan over message.text + customer friend_nickname/real_name/ phone/wechat_phone. type=all returns both sections; each is its own Page[T]. - type=messages orders by timestamp desc; type=customers orders by last_chat_time desc (nulls last). - q required: missing/empty/whitespace -> HTTP 400. - 14 new tests in tests/test_api_search.py (TDD red -> green). Frontend (yunkes): - New /search route + 搜索 nav item with Search icon. - SearchView.vue: search bar, two stacked sections (消息结果 / 客户结果), counts + independent pagination per section, keyword highlighting (escaped), click message -> /chat/:channel/:ownerId/:talkerId, click customer -> all-fields drawer (CustomerList pattern reuse). - search() wrapper in src/api/index.ts with SearchQuery/SearchPayload types. - Empty state: "输入关键词搜索消息和客户". Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -215,6 +215,49 @@ export function getSyncStatus(): Promise<SyncStatus> {
|
||||
return getJson<SyncStatus>('/sync/status')
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Full-text search (messages + customers)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export type SearchType = 'messages' | 'customers' | 'all'
|
||||
|
||||
export interface SearchQuery {
|
||||
q: string
|
||||
/** which section(s) to query; defaults to `all`. */
|
||||
type?: SearchType
|
||||
channel?: Channel | 'all'
|
||||
/** ms-epoch inclusive window compared against message.timestamp. */
|
||||
start?: number
|
||||
end?: number
|
||||
page?: number
|
||||
page_size?: number
|
||||
}
|
||||
|
||||
/**
|
||||
* Combined search payload returned by `/api/search`. Each requested section is
|
||||
* present; unrequested sections are omitted entirely by the backend (so the
|
||||
* caller can branch on `section in payload`).
|
||||
*/
|
||||
export interface SearchPayload {
|
||||
messages?: Page<Message>
|
||||
customers?: Page<Customer>
|
||||
}
|
||||
|
||||
export function search(query: SearchQuery): Promise<SearchPayload> {
|
||||
return getJson<SearchPayload>(
|
||||
'/search',
|
||||
clean({
|
||||
q: query.q,
|
||||
type: query.type ?? 'all',
|
||||
channel: query.channel === 'all' ? undefined : query.channel,
|
||||
start: query.start,
|
||||
end: query.end,
|
||||
page: query.page ?? 1,
|
||||
page_size: query.page_size ?? 20,
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
export type {
|
||||
Account,
|
||||
CallChannel,
|
||||
|
||||
Reference in New Issue
Block a user