Altra 客戶管理平台 — UI Interaction Spec
v1.0  |  2026-05-05  |  工程師串接參考文件  |  涵蓋:客戶名單頁 / 貿小七搜尋頁 / 任務進度
1
標注熱點
GET
查詢(讀取)
POST
新增/觸發
PATCH
更新
DELETE
刪除/封鎖
Frame A — 客戶名單頁(contacts page)
客戶名單
資料來源:貿小七 / CSV 手動匯入
掃描 Bounce
A5
匯出 CSV
A6
匯入 CSV
A7
貿小七搜尋 →
+ 聯絡人
A8
總聯絡人
689
總公司數
42
有效
612
Bounce
21
待確認
56
A1
搜尋公司或聯絡人...
全部狀態 ▾
全部客群 ▾
全部產品 ▾
A2
搜尋標籤... ▾
清除篩選
A3
公司聯絡人職位Email產品搜尋標籤狀態
鴻海精密 王小明 採購經理 ming@foxconn.com Low-Dk PI foxconn.com 有效
封鎖
A9
台積電 李大華 工程師 ta@tsmc.com HDI tsmc.com Bounce
封鎖
日月光 陳美玲 業務 mei@ase.com FIW ase.com 有效
封鎖
1–50 / 共 689 筆
上一頁
下一頁
A1
統計數字(Stats Cards)— 頁面載入自動執行
呼叫
GET /contacts/stats
回傳
total: 689 // 總聯絡人數 companies: 42 // 不重複公司數 有效: 612 Bounce: 21 待確認: 56
點擊卡片 → 自動觸發 f-status 篩選(呼叫 A3)
A2
產品篩選下拉選單 — 頁面載入自動填入
呼叫
GET /products
回傳
[{ id: "low-dk-pi", name: "Low-Dk PI", segments: [...] }, { id: "hdi", name: "HDI", segments: [...] }, { id: "varnish", name: "凡立水", segments: [...] }, { id: "fiw", name: "FIW", segments: [...] }]
同一 loadProducts() 呼叫同時填入 A3 篩選下拉 + B1 產品按鈕
A3
篩選器觸發(Filter Bar)
呼叫
GET /contacts
Query
status: "有效" | "Bounce" | "待確認" | "封鎖" segment_tag: "潛在客戶" | "現有客戶" | "冷客戶" product_tag: "Low-Dk PI" | "HDI" | "凡立水" | "FIW" search_tag: "foxconn.com" | ... // 域名 limit: 50 // 有搜尋詞時為 200
觸發時機
任何篩選條件改變 / 頁面載入 / 匯入/掃描完成後
文字搜尋(公司/聯絡人/Email)為 client-side 過濾,不重新呼叫 API
A5
掃描 Bounce 按鈕
呼叫
POST /contacts/scan-bounce
Body
(無 body)
執行
IMAP 掃描 → 比對 email → 更新狀態
回傳
{ scanned: 38, // 掃描封數 bounced_found: 5, updated: 3, // 更新筆數 details: [{email, company}] }
寫入
contacts.status = 'Bounce'
contacts.bounce_reason = '信箱不存在'
完成後自動重新呼叫 A3(fetchContacts)+ A1(updateStats)
A6
匯出 CSV 按鈕
呼叫
GET /contacts/export
Query
帶入目前篩選條件(同 A3 的 params)
回傳
CSV 檔案下載(UTF-8 with BOM)
欄位
company_name, contact_name, title, email, phone, segment_tag, product_tag (/ 分隔), search_tag (/ 分隔), status, source_tag
A7
匯入 CSV / XLSX 按鈕
呼叫
POST /contacts/import
Body
Content-Type: multipart/form-data file: .csv 或 .xlsx // Query params(選填) product_tag: "Low-Dk PI" search_tag: "foxconn.com"
過濾
① contact_name 非空 ② 郵箱狀態 = "綠色" (貿小七欄位)
回傳
{ imported: 120, merged: 8, skipped_dup: 30, skipped_filter: 12 }
寫入
新增:contacts INSERT
合併:UPDATE product_tag + search_tag(追加,不覆蓋)
A8
+ 聯絡人(手動新增 Modal)
呼叫
POST /contacts
Body
{ email: "..." // 必填,唯一鍵 company_name, contact_name, title, phone segment_tag: "潛在客戶" | "現有客戶" | "冷客戶" source_tag: "CSV手動" // 自動填入 }
寫入
contacts INSERT,status 預設 '有效'
Email 已存在時回傳 409 Conflict
A9
封鎖按鈕(每列)
呼叫
DELETE /contacts/:id
執行
軟刪除 — 不實際移除,僅改狀態
寫入
contacts.status = '封鎖'
Frame B — 貿小七搜尋頁(maoxiaoqi search — 設定階段)
貿小七搜尋
模擬人工操作批次抓取全球企業庫
← 客戶名單
批次搜尋
單一搜尋
Low-Dk PI 新產品
HDI
凡立水
FIW
B1
PCB 製造商(台灣)✓
HDI 高密度板廠
天線模組廠
5G / AI 伺服器廠
RF 高頻電路板廠
B2
↓ 選擇後自動帶入步驟 3 公司名單(來自產品知識庫)
支援簡稱,系統自動比對正確公司網域
開始搜尋
B3
B1
產品按鈕 — 從產品知識庫載入
呼叫
GET /products
時機
頁面初始化(與 A2 共用同一次呼叫)
回傳
[{ id: "low-dk-pi", name: "Low-Dk PI", status: "新產品", segments: [ ... ] // 目標客群清單 }, ...]
⚠️ 待串接:目前為 hardcode mock(products.py:6-55)。需決定是否移入 MySQL
B2
目標客群 Chips — 從產品知識庫載入,點擊帶出公司名單
渲染來源
B1 選產品後,讀取 product.segments[](來自產品知識庫)
點擊 Chip
GET /products/:id
回傳
{ id: "low-dk-pi", segments: [{ name: "PCB 製造商(台灣)", keywords: "PCB manufacturer Taiwan\n...", companies: [ // ⚠️ 待新增欄位 "鴻海精密", "台積電", "欣興電子", ... ] }] }
填入步驟 3
segment.companies[] 逐行填入步驟 3 textarea
⚠️ 待串接:
① 產品知識庫 segments schema 需新增 companies[] 欄位
② 點擊 chip 後需呼叫 API 取得公司名單並填入步驟 3
③ 目前 toggleSegment()(contacts_ui.html:557)僅更新標籤名,未填入公司
B3
開始搜尋 按鈕
呼叫
POST /jobs/trigger
Body
{ keywords: ["鴻海精密", "台積電", ...] // 來自步驟 3(由產品知識庫帶入,可手動編輯) mode: "domain" tag: "PCB 製造商(台灣)" // 選取的客群名稱 product_tag: "Low-Dk PI" // 選取的產品名稱 export: true max_pages: 1 }
Server
轉發至 n8n(N8N_SUBMIT_URL),不直接執行腳本
回傳
{ status: "triggered", keywords_count: 5 }
送出後:① 清空步驟1-3 ② 3秒後輪詢 GET /jobs?limit=1(進入 Frame C)
Frame C — 任務進度(job progress polling)
貿小七搜尋
母任務
Low-Dk PI
搜尋中… 已加入 48 筆
重置
C1
⏳ 排隊中(1 個任務)
子任務(公司)
1 鴻海精密 進行中
2 台積電 未開始
3 日月光 未開始
C2
搜尋歷史 最近 5 筆
05/04 14:22 Low-Dk PI 台積電 等 3 家 完成 ✓ 線索池 +48
05/03 10:08 HDI 鴻海精密 等 5 家 完成 ✓ 線索池 +120
C3
C1
任務狀態輪詢(母任務 Header)
階段 1
// 等待 Windows 接手(n8n job → MySQL job) GET /jobs?limit=1 每 5 秒輪詢 判斷:latest.id !== preExistingJobId → 鎖定 currentJobId,進入階段 2
階段 2
// 已鎖定 job,精確追蹤進度 GET /jobs/:id 每 3 秒輪詢 → 更新 status badge + done_count + 子任務列表 → 直到 status = "done" | "failed"
Job 欄位
status: pending → running → done/failed done_count: 目前完成幾家 current_keyword: "鴻海精密" added_count: 加入線索池幾筆 result: { imported, merged, skipped_filter... } error: null 或錯誤訊息
完成後自動執行:fetchContacts() + updateStats() + loadJobHistory()
⚠️ 殘留偵測:updated_at > 2小時 → 顯示「已中斷」(不再輪詢)
C2
子任務列表(Keywords Rows)— 純前端渲染
API
無獨立呼叫,資料來自 GET /jobs/:id 的回傳
邏輯
job.keywords[i] 的狀態判斷: i < done_count → "已完成" kw === current_keyword → "進行中" (高亮藍底) i >= done_count → "未開始"
進行中的列自動 scrollIntoView
C3
搜尋歷史 — 載入 + 展開詳情
呼叫
GET /jobs?limit=5
時機
頁面載入 + 每次任務完成後重新載入
顯示
每列:日期 | tag(產品標籤) | 公司列表 | 狀態 | 加入筆數 展開:子任務明細 + 匯入摘要(imported/merged/skipped)
歷史中 tag 欄位 = job.tag(送出時的 kw-tag 值,可能為產品名或客群名)
殘留超過 2 小時 = 自動顯示「已中斷」
背景執行流程 — Windows 公司電腦(使用者不可見)
POST /jobs/trigger → n8n → Windows 腳本 → 貿小七 → MySQL
🔗
n8n Webhook
接收 keywords/
mode/product_tag
建立執行任務
💻
Windows 腳本啟動
n8n 呼叫
maoxiaoqi_batch_
search.py
🌐
Domain Lookup
公司名 → Tavily API
→ 主網域
快取至 domain_cache
🤖
貿小七搜尋
Playwright 操作
輸入域名搜尋
建立線索 + 匯出
⚙️
POST /contacts/import
匯入 CSV
過濾郵箱狀態
帶入 product_tag
🗄️
MySQL 寫入完成
altra_marketing.
contacts 新增
PATCH /jobs/:id = done
# Windows 啟動指令(每次使用前需確認已啟動)
uvicorn api.main:app --reload   # Step 1: 啟動 API (port 8000)
# Step 2: 瀏覽器開啟 http://localhost:8000/ui
# Step 3: 腳本由 n8n 自動觸發,無需手動執行
⚠️ 待串接項目摘要(明天討論)
① GET /products 真實化
現況:hardcode mock data in products.py
待做:討論是否移入 MySQL
影響:A2、B1 兩個呼叫點
② 客群點擊 → 關鍵字填入
現況:toggleSegment() 僅更新 tag,不影響搜尋詞
待做:點擊客群 chip → segment.keywords 自動加入步驟3 textarea
影響:B2 行為
③ 聯絡人依產品篩選
現況:product_tag 過濾已可用(A3)
待做:UI 加入「依產品查看對應公司列表」視圖
影響:A3 + 新 UI 元件
Altra Tech 數位部 / AI 部  ·  UI Interaction Spec v1.0  ·  2026-05-05