feat: vue3 frontend scaffold + org tree + customer list + chat timeline
M5b viewing frontend for the yunke data-mirror, under yunkes/. - Scaffold: Vite + Vue 3 + TS, with element-plus, @element-plus/icons-vue, pinia, vue-router@4, axios. Dev server on :5173 with /api and /api/media proxied to the FastAPI backend on :8000. - API client (src/api/): axios instance (baseURL '/api') whose response interceptor unwraps the axios envelope; typed wrappers (getCustomers, getMessages, getDepartments, getStaff) returning Page<T> / arrays; types mirroring the backend *Out schemas (Message typed as a permissive superset so file_th_url/duration/transcription_text degrade gracefully today and light up once MessageOut exposes them). - Layout: el-header top bar (title + channel switch) + el-aside department tree (built from the flat parent_id list via useOrgStore, with search filter) + el-main router-view. - CustomerList (/customers): el-table with channel filter, department filter driven by the tree selection (route query), pagination, and row-click into the chat timeline (channel + owner_account_id + talker_id). - ChatTimeline (/chat/:channel/:ownerId/:talkerId): asc-ordered message bubbles keyed by is_send (direction) and type (text/image/video/voice/file/ divider/system/unknown); lands on the most-recent page and prepends older pages via load-more with scroll anchoring. - Home '/' redirects to '/customers'. npm run build (vue-tsc -b + vite build) is clean. Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,97 @@
|
||||
// Typed endpoint wrappers for the M5b browse experience.
|
||||
//
|
||||
// Mirrors the FastAPI routes in yunke-server/app/api/routers/. Each wrapper
|
||||
// drops `undefined`/empty params so the query string only carries set filters
|
||||
// (the backend treats missing as "no filter").
|
||||
import { getJson } from './client'
|
||||
import type {
|
||||
Channel,
|
||||
Customer,
|
||||
Department,
|
||||
Message,
|
||||
Page,
|
||||
Staff,
|
||||
} from './types'
|
||||
|
||||
/** Drop entries whose value is `undefined`, `null`, or `''`. */
|
||||
function clean(params: Record<string, unknown>): Record<string, unknown> {
|
||||
const out: Record<string, unknown> = {}
|
||||
for (const [k, v] of Object.entries(params)) {
|
||||
if (v === undefined || v === null || v === '') continue
|
||||
out[k] = v
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
export interface CustomerQuery {
|
||||
channel?: Channel | 'all'
|
||||
page?: number
|
||||
page_size?: number
|
||||
owner_account_id?: string
|
||||
department_id?: string
|
||||
}
|
||||
|
||||
export function getCustomers(query: CustomerQuery = {}): Promise<Page<Customer>> {
|
||||
return getJson<Page<Customer>>(
|
||||
'/customers',
|
||||
clean({
|
||||
page: query.page ?? 1,
|
||||
page_size: query.page_size ?? 20,
|
||||
channel: query.channel === 'all' ? undefined : query.channel,
|
||||
owner_account_id: query.owner_account_id,
|
||||
department_id: query.department_id,
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
export interface MessageQuery {
|
||||
channel?: Channel
|
||||
owner_account_id: string
|
||||
talker_id?: string
|
||||
room_id?: string
|
||||
start?: number
|
||||
end?: number
|
||||
page?: number
|
||||
page_size?: number
|
||||
}
|
||||
|
||||
export function getMessages(query: MessageQuery): Promise<Page<Message>> {
|
||||
return getJson<Page<Message>>(
|
||||
'/messages',
|
||||
clean({
|
||||
channel: query.channel,
|
||||
owner_account_id: query.owner_account_id,
|
||||
talker_id: query.talker_id,
|
||||
room_id: query.room_id,
|
||||
start: query.start,
|
||||
end: query.end,
|
||||
page: query.page ?? 1,
|
||||
page_size: query.page_size ?? 50,
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
export function getDepartments(): Promise<Department[]> {
|
||||
return getJson<Department[]>('/departments')
|
||||
}
|
||||
|
||||
export interface StaffQuery {
|
||||
department_id?: string
|
||||
is_on_job?: boolean
|
||||
page?: number
|
||||
page_size?: number
|
||||
}
|
||||
|
||||
export function getStaff(query: StaffQuery = {}): Promise<Page<Staff>> {
|
||||
return getJson<Page<Staff>>(
|
||||
'/staff',
|
||||
clean({
|
||||
department_id: query.department_id,
|
||||
is_on_job: query.is_on_job,
|
||||
page: query.page ?? 1,
|
||||
page_size: query.page_size ?? 20,
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
export type { Channel, Customer, Department, Message, Page, Staff }
|
||||
Reference in New Issue
Block a user