{"id":72,"date":"2026-07-12T15:35:17","date_gmt":"2026-07-12T07:35:17","guid":{"rendered":"https:\/\/st.eyou.tw\/ai01\/?page_id=72"},"modified":"2026-07-12T15:40:38","modified_gmt":"2026-07-12T07:40:38","slug":"%e8%b2%a8%e9%81%8b%e6%99%ba%e8%83%bd%e8%aa%bf%e5%ba%a6%e6%88%b0%e6%83%85%e5%ae%a4","status":"publish","type":"page","link":"https:\/\/st.eyou.tw\/ai01\/%e8%b2%a8%e9%81%8b%e6%99%ba%e8%83%bd%e8%aa%bf%e5%ba%a6%e6%88%b0%e6%83%85%e5%ae%a4\/","title":{"rendered":"\u8ca8\u904b\u667a\u80fd\u8abf\u5ea6\u6230\u60c5\u5ba4"},"content":{"rendered":"\n<!DOCTYPE html>\n<html lang=\"zh-Hant\">\n<head>\n<meta charset=\"UTF-8\" \/>\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" \/>\n<title>\u8ca8\u904b\u667a\u80fd\u8abf\u5ea6\u6230\u60c5\u5ba4<\/title>\n<script src=\"https:\/\/unpkg.com\/react@18\/umd\/react.production.min.js\"><\/script>\n<script src=\"https:\/\/unpkg.com\/react-dom@18\/umd\/react-dom.production.min.js\"><\/script>\n<script src=\"https:\/\/unpkg.com\/@babel\/standalone\/babel.min.js\"><\/script>\n<script src=\"https:\/\/cdn.tailwindcss.com\"><\/script>\n<style>\n  body { font-family: \"PingFang TC\",\"Microsoft JhengHei\",\"Noto Sans TC\",sans-serif; }\n  ::-webkit-scrollbar { width: 8px; height: 8px; }\n  ::-webkit-scrollbar-thumb { background: #cbd5e1; border-radius: 4px; }\n  @keyframes pulseDot { 0%,100%{opacity:1} 50%{opacity:.35} }\n  .pulse { animation: pulseDot 1.4s ease-in-out infinite; }\n  @keyframes toastIn { from{opacity:0; transform:translateY(-8px)} to{opacity:1; transform:translateY(0)} }\n  .toast-in { animation: toastIn .18s ease-out; }\n<\/style>\n<\/head>\n<body class=\"bg-slate-100\">\n<div id=\"root\"><\/div>\n\n<script type=\"text\/plain\" id=\"app-source\">\nconst { useState, useEffect, useMemo, useRef, useCallback } = React;\n\n\/* ---------- \u5e38\u6578 ---------- *\/\nconst DISTRICTS = [\"\u6843\u5712\u5340\",\"\u4e2d\u58e2\u5340\",\"\u5e73\u93ae\u5340\",\"\u516b\u5fb7\u5340\",\"\u9f9c\u5c71\u5340\",\"\u8606\u7af9\u5340\",\"\u5927\u5712\u5340\",\"\u694a\u6885\u5340\"];\nconst CAR_TYPES = [\"3.5\u5678\u5ec2\u5f0f\",\"5\u5678\u5ec2\u5f0f\",\"8\u5678\u5ec2\u5f0f\",\"\u5e73\u677f\u8eca\",\"\u62d6\u677f\u8eca\",\"\u51b7\u93c8\u8eca\"];\nconst TAG_OPTIONS = [\"\u5c08\u8dd1\u9577\u7a0b\",\"\u914d\u5408\u5ea6\u6975\u4f73\",\"\u51b7\u93c8\u5c08\u5bb6\",\"\u7279\u5b9a\u5de5\u696d\u5340\u719f\u5ba2\",\"\u65b0\u5408\u4f5c\"];\nconst GREEN_MIN = 60;   \/\/ 60\u5206\u9418\u5167 \u7da0\u71c8\nconst YELLOW_MIN = 120; \/\/ 60~120\u5206\u9418 \u9ec3\u71c8\uff0c\u8d85\u904e\u7d05\u71c8\n\n\/* ---------- \u5c0f\u5de5\u5177 ---------- *\/\nconst nowTs = () => Date.now();\nconst minutesAgo = (m) => nowTs() - m * 60000;\n\nfunction elapsedMinutes(ts) {\n  return Math.floor((nowTs() - ts) \/ 60000);\n}\nfunction statusOf(ts) {\n  const m = elapsedMinutes(ts);\n  if (m < GREEN_MIN) return \"green\";\n  if (m < YELLOW_MIN) return \"yellow\";\n  return \"red\";\n}\nfunction fmtElapsed(ts) {\n  const m = elapsedMinutes(ts);\n  if (m < 1) return \"\u525b\u525b\";\n  if (m < 60) return `${m} \u5206\u9418\u524d`;\n  const h = Math.floor(m \/ 60);\n  const mm = m % 60;\n  return `${h} \u5c0f\u6642 ${mm} \u5206\u524d`;\n}\nfunction fmtClock(ts) {\n  const d = new Date(ts);\n  return d.toLocaleTimeString(\"zh-TW\", { hour: \"2-digit\", minute: \"2-digit\" });\n}\nconst uid = () => Math.random().toString(36).slice(2, 10);\n\nconst LIGHT_STYLE = {\n  green:  { dot: \"bg-emerald-500\", ring: \"ring-emerald-200\", text: \"text-emerald-700\", bg: \"bg-emerald-50\", label: \"\ud83d\udfe2 \u53ef\u7528\" },\n  yellow: { dot: \"bg-amber-400\",   ring: \"ring-amber-200\",   text: \"text-amber-700\",   bg: \"bg-amber-50\",   label: \"\ud83d\udfe1 \u5f85\u78ba\u8a8d\" },\n  red:    { dot: \"bg-rose-500\",    ring: \"ring-rose-200\",    text: \"text-rose-700\",    bg: \"bg-rose-50\",    label: \"\ud83d\udd34 \u53ef\u80fd\u5df2\u6d3e\u8d70\" },\n};\n\nconst ENTITY_TYPES = [\"\u8eca\u884c\", \"\u81ea\u7136\u4eba\u9760\u884c\"];\nconst ENTITY_BADGE = {\n  \"\u8eca\u884c\":     { text: \"\ud83c\udfe2 \u8eca\u884c\",     cls: \"bg-slate-100 text-slate-600\" },\n  \"\u81ea\u7136\u4eba\u9760\u884c\": { text: \"\ud83e\uddcd \u81ea\u7136\u4eba\u9760\u884c\", cls: \"bg-sky-100 text-sky-700\" },\n};\n\nfunction isValidTaxId(v) {\n  return \/^\\d{8}$\/.test((v || \"\").trim());\n}\nfunction isValidNationalId(v) {\n  return \/^[A-Za-z][12]\\d{8}$\/.test((v || \"\").trim().toUpperCase());\n}\nfunction maskNationalId(v) {\n  const id = (v || \"\").trim().toUpperCase();\n  if (id.length < 6) return id;\n  return id.slice(0, 3) + \"***\" + id.slice(-3);\n}\n\n\/* ---------- \u7a2e\u5b50\u8cc7\u6599 ---------- *\/\nconst seedCompanies = [\n  { id: \"c1\", name: \"\u9d3b\u904b\u901a\u904b\", contact: \"\u9673\u8001\u95c6\", phone: \"03-2201234\", fleetSize: 12, reputation: 5, tags: [\"\u5c08\u8dd1\u9577\u7a0b\",\"\u914d\u5408\u5ea6\u6975\u4f73\"], entityType: \"\u8eca\u884c\", affiliatedCompany: \"\", taxId: \"12345678\", nationalId: \"\" },\n  { id: \"c2\", name: \"\u6843\u5712\u9806\u98a8\u8ca8\u904b\", contact: \"\u6797\u7d93\u7406\", phone: \"03-3654321\", fleetSize: 8,  reputation: 4, tags: [\"\u51b7\u93c8\u5c08\u5bb6\"], entityType: \"\u8eca\u884c\", affiliatedCompany: \"\", taxId: \"23456789\", nationalId: \"\" },\n  { id: \"c3\", name: \"\u4e2d\u58e2\u5927\u660c\u8eca\u884c\", contact: \"\u738b\u5927\u54e5\", phone: \"03-4551122\", fleetSize: 6,  reputation: 3, tags: [\"\u7279\u5b9a\u5de5\u696d\u5340\u719f\u5ba2\"], entityType: \"\u8eca\u884c\", affiliatedCompany: \"\", taxId: \"34567890\", nationalId: \"\" },\n  { id: \"c4\", name: \"\u516b\u5fb7\u6c38\u5b89\u7269\u6d41\", contact: \"\u5f35\u59d0\", phone: \"03-3789456\", fleetSize: 15, reputation: 5, tags: [\"\u914d\u5408\u5ea6\u6975\u4f73\"], entityType: \"\u8eca\u884c\", affiliatedCompany: \"\", taxId: \"45678901\", nationalId: \"\" },\n  { id: \"c5\", name: \"\u9f9c\u5c71\u946b\u946b\u8eca\u968a\", contact: \"\u674e\u5148\u751f\", phone: \"03-3299887\", fleetSize: 4,  reputation: 2, tags: [\"\u65b0\u5408\u4f5c\"], entityType: \"\u8eca\u884c\", affiliatedCompany: \"\", taxId: \"56789012\", nationalId: \"\" },\n  { id: \"c6\", name: \"\u738b\u5927\u660e\", contact: \"\u738b\u5927\u660e\", phone: \"0912-345678\", fleetSize: 1, reputation: 4, tags: [\"\u914d\u5408\u5ea6\u6975\u4f73\"], entityType: \"\u81ea\u7136\u4eba\u9760\u884c\", affiliatedCompany: \"\u9d3b\u904b\u901a\u904b\", taxId: \"\", nationalId: \"A123456789\" },\n];\n\nconst seedCapacity = [\n  { id: uid(), companyId: \"c1\", carType: \"3.5\u5678\u5ec2\u5f0f\", count: 2, district: \"\u6843\u5712\u5340\", loggedAt: minutesAgo(8) },\n  { id: uid(), companyId: \"c2\", carType: \"\u51b7\u93c8\u8eca\",    count: 1, district: \"\u4e2d\u58e2\u5340\", loggedAt: minutesAgo(35) },\n  { id: uid(), companyId: \"c3\", carType: \"8\u5678\u5ec2\u5f0f\",   count: 3, district: \"\u4e2d\u58e2\u5340\", loggedAt: minutesAgo(95) },\n  { id: uid(), companyId: \"c4\", carType: \"5\u5678\u5ec2\u5f0f\",   count: 2, district: \"\u516b\u5fb7\u5340\", loggedAt: minutesAgo(150) },\n  { id: uid(), companyId: \"c5\", carType: \"\u5e73\u677f\u8eca\",    count: 1, district: \"\u9f9c\u5c71\u5340\", loggedAt: minutesAgo(15) },\n  { id: uid(), companyId: \"c1\", carType: \"\u62d6\u677f\u8eca\",    count: 1, district: \"\u8606\u7af9\u5340\", loggedAt: minutesAgo(70) },\n  { id: uid(), companyId: \"c6\", carType: \"3.5\u5678\u5ec2\u5f0f\", count: 1, district: \"\u6843\u5712\u5340\", loggedAt: minutesAgo(20) },\n];\n\nconst seedOrders = [\n  { id: uid(), companyId: \"c4\", carType: \"5\u5678\u5ec2\u5f0f\", count: 1, district: \"\u516b\u5fb7\u5340\", createdAt: minutesAgo(180), payment: \"\u5df2\u4ed8\u6b3e\", urgent: false },\n];\n\n\/* ---------- \u5c0f\u5143\u4ef6 ---------- *\/\nfunction StatusDot({ status, size = \"w-3 h-3\" }) {\n  const s = LIGHT_STYLE[status];\n  return <span className={`inline-block ${size} rounded-full ${s.dot} ${status === \"red\" ? \"\" : \"pulse\"}`}><\/span>;\n}\n\nfunction BigButton({ children, onClick, color = \"indigo\", className = \"\", disabled }) {\n  const palette = {\n    indigo: \"bg-indigo-600 hover:bg-indigo-700 active:bg-indigo-800\",\n    emerald: \"bg-emerald-600 hover:bg-emerald-700 active:bg-emerald-800\",\n    rose: \"bg-rose-600 hover:bg-rose-700 active:bg-rose-800\",\n    slate: \"bg-slate-600 hover:bg-slate-700 active:bg-slate-800\",\n  };\n  return (\n    <button\n      onClick={onClick}\n      disabled={disabled}\n      className={`${palette[color]} disabled:opacity-40 disabled:cursor-not-allowed text-white font-bold rounded-xl shadow-sm transition-colors ${className}`}\n    >\n      {children}\n    <\/button>\n  );\n}\n\nfunction Modal({ title, onClose, children, wide }) {\n  useEffect(() => {\n    const onKey = (e) => { if (e.key === \"Escape\") onClose(); };\n    window.addEventListener(\"keydown\", onKey);\n    return () => window.removeEventListener(\"keydown\", onKey);\n  }, [onClose]);\n  return (\n    <div className=\"fixed inset-0 z-50 flex items-center justify-center bg-black\/40 px-4\" onClick={onClose}>\n      <div\n        className={`bg-white rounded-2xl shadow-2xl w-full ${wide ? \"max-w-2xl\" : \"max-w-md\"} max-h-[88vh] overflow-y-auto`}\n        onClick={(e) => e.stopPropagation()}\n      >\n        <div className=\"flex items-center justify-between px-6 py-4 border-b border-slate-200 sticky top-0 bg-white rounded-t-2xl\">\n          <h2 className=\"text-xl font-extrabold text-slate-800\">{title}<\/h2>\n          <button onClick={onClose} className=\"text-slate-400 hover:text-slate-700 text-2xl leading-none px-2\">\u00d7<\/button>\n        <\/div>\n        <div className=\"p-6\">{children}<\/div>\n      <\/div>\n    <\/div>\n  );\n}\n\nfunction FieldLabel({ children }) {\n  return <label className=\"block text-sm font-bold text-slate-600 mb-1.5\">{children}<\/label>;\n}\n\nconst selectCls = \"w-full rounded-lg border border-slate-300 px-3 py-2.5 text-base focus:outline-none focus:ring-2 focus:ring-indigo-400 bg-white\";\nconst inputCls = selectCls;\n\n\/* ---------- \u4e3b\u61c9\u7528\u7a0b\u5f0f ---------- *\/\nfunction App() {\n  const [companies, setCompanies] = useState(seedCompanies);\n  const [capacity, setCapacity] = useState(seedCapacity);\n  const [orders, setOrders] = useState(seedOrders);\n\n  const [clock, setClock] = useState(nowTs());\n  useEffect(() => {\n    const t = setInterval(() => setClock(nowTs()), 15000);\n    return () => clearInterval(t);\n  }, []);\n\n  const [showAddCapacity, setShowAddCapacity] = useState(false);\n  const [showCRM, setShowCRM] = useState(false);\n  const [toast, setToast] = useState(null);\n  const [regionFilter, setRegionFilter] = useState(\"\u5168\u90e8\");\n\n  const [matchForm, setMatchForm] = useState({ district: \"\u6843\u5712\u5340\", carType: \"3.5\u5678\u5ec2\u5f0f\", count: 1, urgent: false, note: \"\" });\n  const [matchResults, setMatchResults] = useState(null);\n\n  const flashToast = useCallback((msg, tone = \"emerald\") => {\n    setToast({ msg, tone, key: uid() });\n    setTimeout(() => setToast((t) => (t && t.msg === msg ? null : t)), 2600);\n  }, []);\n\n  \/* \u5feb\u6377\u9375: N \u65b0\u589e\u7a7a\u8eca\u76e4\u9ede \/ F \u805a\u7126\u5a92\u5408\u5340 \/ Esc \u5df2\u5728 Modal \u5167\u8655\u7406 *\/\n  const matchDistrictRef = useRef(null);\n  useEffect(() => {\n    const onKey = (e) => {\n      if (e.target.tagName === \"INPUT\" || e.target.tagName === \"TEXTAREA\" || e.target.tagName === \"SELECT\") return;\n      if (e.key === \"n\" || e.key === \"N\") setShowAddCapacity(true);\n      if (e.key === \"f\" || e.key === \"F\") matchDistrictRef.current && matchDistrictRef.current.focus();\n    };\n    window.addEventListener(\"keydown\", onKey);\n    return () => window.removeEventListener(\"keydown\", onKey);\n  }, []);\n\n  const companyMap = useMemo(() => Object.fromEntries(companies.map((c) => [c.id, c])), [companies]);\n\n  const activeCapacity = capacity; \/\/ \u5168\u90e8\u8996\u70ba\u73fe\u5b58\u76e4\u9ede\uff0c\u6578\u91cf\u6b78\u96f6\u6642\u53e6\u884c\u79fb\u9664\n\n  \/* \u5340\u57df\u7e3d\u89bd\u7d71\u8a08 *\/\n  const regionStats = useMemo(() => {\n    return DISTRICTS.map((d) => {\n      const recs = activeCapacity.filter((r) => r.district === d);\n      const total = recs.reduce((s, r) => s + r.count, 0);\n      const hasRed = recs.some((r) => statusOf(r.loggedAt) === \"red\") && recs.every((r) => statusOf(r.loggedAt) !== \"green\");\n      let tone = \"red\";\n      if (total === 0) tone = \"red\";\n      else if (recs.some((r) => statusOf(r.loggedAt) === \"green\")) tone = \"green\";\n      else tone = \"yellow\";\n      return { district: d, total, tone, count: recs.length };\n    });\n  }, [activeCapacity, clock]);\n\n  \/* \u65b0\u589e\u7a7a\u8eca\u76e4\u9ede *\/\n  function submitCapacity(form) {\n    let companyId = form.companyId;\n    let updatedCompanies = companies;\n    if (form.newCompanyName && form.newCompanyName.trim()) {\n      const nc = {\n        id: uid(),\n        name: form.newCompanyName.trim(),\n        contact: form.newContact || \"\",\n        phone: form.newPhone || \"\",\n        fleetSize: 1,\n        reputation: 3,\n        tags: [],\n        entityType: form.entityType || \"\u8eca\u884c\",\n        affiliatedCompany: form.entityType === \"\u81ea\u7136\u4eba\u9760\u884c\" ? (form.affiliatedCompany || \"\") : \"\",\n        taxId: form.entityType === \"\u81ea\u7136\u4eba\u9760\u884c\" ? \"\" : (form.taxId || \"\"),\n        nationalId: form.entityType === \"\u81ea\u7136\u4eba\u9760\u884c\" ? (form.nationalId || \"\") : \"\",\n      };\n      updatedCompanies = [...companies, nc];\n      companyId = nc.id;\n      setCompanies(updatedCompanies);\n    }\n    if (!companyId) { flashToast(\"\u8acb\u9078\u64c7\u6216\u8f38\u5165\u8eca\u884c\u540d\u7a31\", \"rose\"); return; }\n    const rec = {\n      id: uid(),\n      companyId,\n      carType: form.carType,\n      count: Number(form.count) || 1,\n      district: form.district,\n      loggedAt: nowTs(),\n    };\n    setCapacity((prev) => [rec, ...prev]);\n    setShowAddCapacity(false);\n    const name = updatedCompanies.find((c) => c.id === companyId)?.name || \"\";\n    flashToast(`\u5df2\u767b\u9304\u300c${name}\u300d${form.district} ${form.carType} x${rec.count}`, \"emerald\");\n  }\n\n  function reconfirm(id) {\n    setCapacity((prev) => prev.map((r) => (r.id === id ? { ...r, loggedAt: nowTs() } : r)));\n    flashToast(\"\u5df2\u91cd\u65b0\u78ba\u8a8d\uff0c\u72c0\u614b\u8f49\u70ba\u7da0\u71c8\", \"emerald\");\n  }\n  function markGone(id) {\n    setCapacity((prev) => prev.filter((r) => r.id !== id));\n    flashToast(\"\u5df2\u5f9e\u76e4\u9ede\u6e05\u55ae\u79fb\u9664\", \"slate\");\n  }\n\n  \/* \u5a92\u5408\u641c\u5c0b *\/\n  function runMatch() {\n    const results = capacity\n      .filter((r) => r.carType === matchForm.carType)\n      .map((r) => ({ ...r, status: statusOf(r.loggedAt), company: companyMap[r.companyId] }))\n      .filter((r) => r.status !== \"red\")\n      .sort((a, b) => {\n        const sameDistrictA = a.district === matchForm.district ? 0 : 1;\n        const sameDistrictB = b.district === matchForm.district ? 0 : 1;\n        if (sameDistrictA !== sameDistrictB) return sameDistrictA - sameDistrictB;\n        const order = { green: 0, yellow: 1 };\n        if (order[a.status] !== order[b.status]) return order[a.status] - order[b.status];\n        return (b.company?.reputation || 0) - (a.company?.reputation || 0);\n      });\n    setMatchResults(results);\n  }\n\n  function dispatchOrder(rec) {\n    const qty = Math.min(matchForm.count || 1, rec.count);\n    setCapacity((prev) =>\n      prev\n        .map((r) => (r.id === rec.id ? { ...r, count: r.count - qty } : r))\n        .filter((r) => r.count > 0)\n    );\n    const order = {\n      id: uid(),\n      companyId: rec.companyId,\n      carType: rec.carType,\n      count: qty,\n      district: matchForm.district,\n      createdAt: nowTs(),\n      payment: \"\u5f85\u4ed8\u6b3e\",\n      urgent: matchForm.urgent,\n    };\n    setOrders((prev) => [order, ...prev]);\n    setMatchResults((prev) => (prev ? prev.filter((r) => r.id !== rec.id || r.count - qty > 0).map((r) => (r.id === rec.id ? { ...r, count: r.count - qty } : r)) : prev));\n    flashToast(`\u2705 \u5df2\u6d3e\u55ae\u7d66\u300c${companyMap[rec.companyId]?.name}\u300d`, \"emerald\");\n  }\n\n  function cyclePayment(id) {\n    const cycle = [\"\u5f85\u4ed8\u6b3e\", \"\u5df2\u4ed8\u6b3e\", \"\u5e73\u53f0\u4ee3\u588a\"];\n    setOrders((prev) =>\n      prev.map((o) => (o.id === id ? { ...o, payment: cycle[(cycle.indexOf(o.payment) + 1) % cycle.length] } : o))\n    );\n  }\n\n  const PAYMENT_STYLE = {\n    \"\u5f85\u4ed8\u6b3e\": \"bg-rose-100 text-rose-700 border border-rose-300\",\n    \"\u5df2\u4ed8\u6b3e\": \"bg-emerald-100 text-emerald-700 border border-emerald-300\",\n    \"\u5e73\u53f0\u4ee3\u588a\": \"bg-amber-100 text-amber-700 border border-amber-300\",\n  };\n\n  const sortedCapacity = useMemo(() => {\n    const order = { green: 0, yellow: 1, red: 2 };\n    return [...capacity]\n      .filter((r) => regionFilter === \"\u5168\u90e8\" || r.district === regionFilter)\n      .sort((a, b) => order[statusOf(a.loggedAt)] - order[statusOf(b.loggedAt)] || b.loggedAt - a.loggedAt);\n  }, [capacity, regionFilter, clock]);\n\n  return (\n    <div className=\"min-h-screen pb-10\">\n      {\/* Header *\/}\n      <header className=\"bg-slate-900 text-white sticky top-0 z-30 shadow-md\">\n        <div className=\"max-w-[1600px] mx-auto px-5 py-3 flex items-center justify-between flex-wrap gap-2\">\n          <div className=\"flex items-center gap-3\">\n            <span className=\"text-2xl\">\ud83d\ude9a<\/span>\n            <div>\n              <h1 className=\"text-lg font-extrabold leading-tight\">\u8ca8\u904b\u667a\u80fd\u8abf\u5ea6\u6230\u60c5\u5ba4<\/h1>\n              <p className=\"text-xs text-slate-400\">\u6843\u5712\u5340\u57df\u5373\u6642\u904b\u529b\u76e4\u9ede\u30fb\u5c08\u54e1\u64cd\u4f5c\u53f0<\/p>\n            <\/div>\n          <\/div>\n          <div className=\"flex items-center gap-3\">\n            <div className=\"text-right text-sm text-slate-300 hidden sm:block\">\n              \u73fe\u5728\u6642\u9593 <span className=\"font-mono text-white\">{new Date(clock).toLocaleTimeString(\"zh-TW\")}<\/span>\n            <\/div>\n            <BigButton color=\"slate\" className=\"px-4 py-2 text-sm\" onClick={() => setShowCRM(true)}>\ud83c\udfe2 \u8eca\u884c\u7ba1\u7406<\/BigButton>\n            <BigButton color=\"emerald\" className=\"px-4 py-2 text-sm\" onClick={() => setShowAddCapacity(true)}>\n              \u2795 \u65b0\u589e\u7a7a\u8eca\u76e4\u9ede <span className=\"opacity-60 font-normal\">(N)<\/span>\n            <\/BigButton>\n          <\/div>\n        <\/div>\n      <\/header>\n\n      <main className=\"max-w-[1600px] mx-auto px-5 pt-5 space-y-5\">\n        {\/* \u5340\u57df\u7e3d\u89bd *\/}\n        <section className=\"bg-white rounded-2xl shadow-sm p-4\">\n          <h2 className=\"text-base font-extrabold text-slate-700 mb-3\">\ud83d\udccd \u5340\u57df\u904b\u529b\u7e3d\u89bd\uff08\u6843\u5712\u5e02\uff09<\/h2>\n          <div className=\"grid grid-cols-2 sm:grid-cols-4 lg:grid-cols-8 gap-3\">\n            {regionStats.map((r) => {\n              const s = LIGHT_STYLE[r.tone];\n              return (\n                <button\n                  key={r.district}\n                  onClick={() => setRegionFilter(r.district === regionFilter ? \"\u5168\u90e8\" : r.district)}\n                  className={`rounded-xl p-3 text-left border-2 transition ${s.bg} ${\n                    regionFilter === r.district ? \"border-indigo-500\" : \"border-transparent\"\n                  } hover:border-indigo-300`}\n                >\n                  <div className=\"flex items-center justify-between\">\n                    <span className=\"font-bold text-slate-700 text-sm\">{r.district}<\/span>\n                    <StatusDot status={r.tone} size=\"w-2.5 h-2.5\" \/>\n                  <\/div>\n                  <div className={`text-2xl font-black mt-1 ${s.text}`}>{r.total}<\/div>\n                  <div className=\"text-[11px] text-slate-500\">\u53ef\u7528\u8eca\u8f1b\uff08{r.count} \u7b46\u76e4\u9ede\uff09<\/div>\n                <\/button>\n              );\n            })}\n          <\/div>\n          {regionFilter !== \"\u5168\u90e8\" && (\n            <button onClick={() => setRegionFilter(\"\u5168\u90e8\")} className=\"mt-3 text-xs text-indigo-600 font-bold hover:underline\">\n              \u00d7 \u6e05\u9664\u7be9\u9078\uff08\u76ee\u524d\u50c5\u986f\u793a {regionFilter}\uff09\n            <\/button>\n          )}\n        <\/section>\n\n        {\/* \u4e09\u6b04\u4e3b\u5340\u584a *\/}\n        <div className=\"grid grid-cols-1 xl:grid-cols-12 gap-5\">\n          {\/* \u7a7a\u8eca\u76e4\u9ede\u6e05\u55ae *\/}\n          <section className=\"xl:col-span-5 bg-white rounded-2xl shadow-sm p-4 flex flex-col\">\n            <div className=\"flex items-center justify-between mb-3\">\n              <h2 className=\"text-base font-extrabold text-slate-700\">\ud83d\udccb \u7a7a\u8eca\u76e4\u9ede\u6e05\u55ae<\/h2>\n              <span className=\"text-xs text-slate-400\">{sortedCapacity.length} \u7b46<\/span>\n            <\/div>\n            <div className=\"space-y-2 overflow-y-auto max-h-[620px] pr-1\">\n              {sortedCapacity.length === 0 && (\n                <p className=\"text-sm text-slate-400 text-center py-10\">\u76ee\u524d\u6c92\u6709\u7b26\u5408\u7684\u7a7a\u8eca\u76e4\u9ede\u7d00\u9304<\/p>\n              )}\n              {sortedCapacity.map((r) => {\n                const st = statusOf(r.loggedAt);\n                const s = LIGHT_STYLE[st];\n                const company = companyMap[r.companyId];\n                return (\n                  <div key={r.id} className={`rounded-xl p-3 border ${s.bg} border-transparent ring-1 ${s.ring}`}>\n                    <div className=\"flex items-start justify-between gap-2\">\n                      <div className=\"flex items-center gap-2 min-w-0\">\n                        <StatusDot status={st} \/>\n                        <div className=\"min-w-0\">\n                          <div className=\"flex items-center gap-1.5 min-w-0\">\n                            <span className=\"font-bold text-slate-800 truncate\">{company?.name || \"\u672a\u77e5\u8eca\u884c\"}<\/span>\n                            {company?.entityType === \"\u81ea\u7136\u4eba\u9760\u884c\" && (\n                              <span className={`text-[10px] font-bold px-1.5 py-0.5 rounded-full whitespace-nowrap ${ENTITY_BADGE[\"\u81ea\u7136\u4eba\u9760\u884c\"].cls}`}>\n                                {ENTITY_BADGE[\"\u81ea\u7136\u4eba\u9760\u884c\"].text}{company.affiliatedCompany ? `\u30fb\u9760${company.affiliatedCompany}` : \"\"}\n                              <\/span>\n                            )}\n                          <\/div>\n                          <div className=\"text-xs text-slate-500\">{r.district}\u30fb{r.carType}\u30fb\u5171 {r.count} \u53f0<\/div>\n                        <\/div>\n                      <\/div>\n                      <div className={`text-xs font-bold px-2 py-1 rounded-full whitespace-nowrap ${s.text} ${s.bg} ring-1 ${s.ring}`}>\n                        {s.label}\n                      <\/div>\n                    <\/div>\n                    <div className=\"flex items-center justify-between mt-2 text-xs text-slate-400\">\n                      <span>{fmtClock(r.loggedAt)} \u767b\u9304\u30fb{fmtElapsed(r.loggedAt)}<\/span>\n                      <div className=\"flex gap-1.5\">\n                        <button\n                          onClick={() => reconfirm(r.id)}\n                          className=\"px-2.5 py-1 rounded-lg bg-white border border-slate-300 text-slate-600 font-bold hover:bg-slate-50\"\n                        >\u2705 \u78ba\u8a8d\u9084\u5728<\/button>\n                        <button\n                          onClick={() => markGone(r.id)}\n                          className=\"px-2.5 py-1 rounded-lg bg-white border border-rose-300 text-rose-500 font-bold hover:bg-rose-50\"\n                        >\ud83d\udeab \u5df2\u6d3e\u8d70<\/button>\n                      <\/div>\n                    <\/div>\n                  <\/div>\n                );\n              })}\n            <\/div>\n          <\/section>\n\n          {\/* \u6975\u901f\u5a92\u5408\u6d3e\u55ae *\/}\n          <section className=\"xl:col-span-4 bg-white rounded-2xl shadow-sm p-4\">\n            <h2 className=\"text-base font-extrabold text-slate-700 mb-3\">\u26a1 \u6975\u901f\u5a92\u5408\u6d3e\u55ae<\/h2>\n            <div className=\"space-y-3\">\n              <div>\n                <FieldLabel>\u51fa\u767c\u5340\u57df<\/FieldLabel>\n                <select ref={matchDistrictRef} className={selectCls} value={matchForm.district} onChange={(e) => setMatchForm({ ...matchForm, district: e.target.value })}>\n                  {DISTRICTS.map((d) => <option key={d}>{d}<\/option>)}\n                <\/select>\n              <\/div>\n              <div className=\"grid grid-cols-2 gap-3\">\n                <div>\n                  <FieldLabel>\u9700\u6c42\u8eca\u578b<\/FieldLabel>\n                  <select className={selectCls} value={matchForm.carType} onChange={(e) => setMatchForm({ ...matchForm, carType: e.target.value })}>\n                    {CAR_TYPES.map((t) => <option key={t}>{t}<\/option>)}\n                  <\/select>\n                <\/div>\n                <div>\n                  <FieldLabel>\u9700\u6c42\u6578\u91cf<\/FieldLabel>\n                  <input type=\"number\" min=\"1\" className={inputCls} value={matchForm.count}\n                    onChange={(e) => setMatchForm({ ...matchForm, count: e.target.value })} \/>\n                <\/div>\n              <\/div>\n              <label className=\"flex items-center gap-2 text-sm font-bold text-slate-600\">\n                <input type=\"checkbox\" className=\"w-4 h-4\" checked={matchForm.urgent}\n                  onChange={(e) => setMatchForm({ ...matchForm, urgent: e.target.checked })} \/>\n                \ud83d\udea8 \u6a19\u8a18\u70ba\u6025\u4ef6\n              <\/label>\n              <div>\n                <FieldLabel>\u5099\u8a3b<\/FieldLabel>\n                <input className={inputCls} placeholder=\"\u4f8b\u5982\uff1a\u9700\u81ea\u5099\u68e7\u677f \/ \u8ca8\u7269\u6613\u788e\" value={matchForm.note}\n                  onChange={(e) => setMatchForm({ ...matchForm, note: e.target.value })} \/>\n              <\/div>\n              <BigButton color=\"indigo\" className=\"w-full py-3 text-base\" onClick={runMatch}>\n                \ud83d\udd0d \u641c\u5c0b\u5468\u908a\u53ef\u7528\u7a7a\u8eca\n              <\/BigButton>\n            <\/div>\n\n            <div className=\"mt-4 space-y-2\">\n              {matchResults === null && (\n                <p className=\"text-sm text-slate-400 text-center py-6\">\u8f38\u5165\u9700\u6c42\u5f8c\u9ede\u64ca\u641c\u5c0b\uff0c\u5c07\u81ea\u52d5\u6392\u5e8f\u6700\u9069\u5408\u7684\u8eca\u884c<\/p>\n              )}\n              {matchResults && matchResults.length === 0 && (\n                <div className=\"text-center py-8\">\n                  <p className=\"text-rose-500 font-bold\">\ud83d\udd34 \u76ee\u524d\u8cc7\u6599\u9f4a\u5168\uff0c\u66ab\u7121\u7b26\u5408\u7684\u7a7a\u8eca<\/p>\n                  <p className=\"text-xs text-slate-400 mt-1\">\u5efa\u8b70\u81f4\u96fb\u9130\u8fd1\u5340\u57df\u8eca\u884c\u78ba\u8a8d\u6700\u65b0\u72c0\u6cc1<\/p>\n                <\/div>\n              )}\n              {matchResults && matchResults.map((r) => {\n                const s = LIGHT_STYLE[r.status];\n                return (\n                  <div key={r.id} className={`rounded-xl p-3 border ${s.bg} ring-1 ${s.ring} flex items-center justify-between gap-2`}>\n                    <div className=\"min-w-0\">\n                      <div className=\"flex items-center gap-1.5\">\n                        <StatusDot status={r.status} size=\"w-2.5 h-2.5\" \/>\n                        <span className=\"font-bold text-slate-800 truncate\">{r.company?.name}<\/span>\n                        <span className=\"text-amber-500 text-xs\">{\"\u2605\".repeat(r.company?.reputation || 0)}<\/span>\n                      <\/div>\n                      {r.company?.entityType === \"\u81ea\u7136\u4eba\u9760\u884c\" && (\n                        <span className={`inline-block text-[10px] font-bold px-1.5 py-0.5 rounded-full mt-1 ${ENTITY_BADGE[\"\u81ea\u7136\u4eba\u9760\u884c\"].cls}`}>\n                          {ENTITY_BADGE[\"\u81ea\u7136\u4eba\u9760\u884c\"].text}{r.company.affiliatedCompany ? `\u30fb\u9760${r.company.affiliatedCompany}` : \"\"}\n                        <\/span>\n                      )}\n                      <div className=\"text-xs text-slate-500 mt-0.5\">\n                        {r.district}{r.district !== matchForm.district ? \"\uff08\u9130\u8fd1\u5340\uff09\" : \"\uff08\u540c\u5340\uff09\"}\u30fb\u5269 {r.count} \u53f0\u30fb{fmtElapsed(r.loggedAt)}\n                      <\/div>\n                      {r.company?.tags?.length > 0 && (\n                        <div className=\"flex flex-wrap gap-1 mt-1\">\n                          {r.company.tags.map((t) => (\n                            <span key={t} className=\"text-[10px] bg-white border border-slate-200 text-slate-500 px-1.5 py-0.5 rounded-full\">{t}<\/span>\n                          ))}\n                        <\/div>\n                      )}\n                    <\/div>\n                    <BigButton color=\"emerald\" className=\"px-3 py-2 text-sm whitespace-nowrap\" onClick={() => dispatchOrder(r)}>\n                      \u78ba\u8a8d\u6d3e\u55ae\n                    <\/BigButton>\n                  <\/div>\n                );\n              })}\n            <\/div>\n          <\/section>\n\n          {\/* \u4eca\u65e5\u6d3e\u55ae\u7d00\u9304 *\/}\n          <section className=\"xl:col-span-3 bg-white rounded-2xl shadow-sm p-4\">\n            <div className=\"flex items-center justify-between mb-3\">\n              <h2 className=\"text-base font-extrabold text-slate-700\">\ud83e\uddfe \u4eca\u65e5\u6d3e\u55ae\u7d00\u9304<\/h2>\n              <span className=\"text-xs text-slate-400\">{orders.length} \u7b46<\/span>\n            <\/div>\n            <div className=\"space-y-2 overflow-y-auto max-h-[620px] pr-1\">\n              {orders.length === 0 && <p className=\"text-sm text-slate-400 text-center py-10\">\u5c1a\u7121\u6d3e\u55ae\u7d00\u9304<\/p>}\n              {orders.map((o) => (\n                <div key={o.id} className=\"rounded-xl p-3 border border-slate-200\">\n                  <div className=\"flex items-center justify-between gap-2\">\n                    <span className=\"font-bold text-slate-800 truncate\">{companyMap[o.companyId]?.name}<\/span>\n                    {o.urgent && <span className=\"text-[10px] bg-rose-100 text-rose-600 font-bold px-1.5 py-0.5 rounded-full\">\u6025\u4ef6<\/span>}\n                  <\/div>\n                  <div className=\"text-xs text-slate-500 mt-0.5\">{o.district}\u30fb{o.carType}\u30fb{o.count} \u53f0<\/div>\n                  <div className=\"text-[11px] text-slate-400 mt-0.5\">{fmtClock(o.createdAt)} \u6d3e\u55ae<\/div>\n                  <button\n                    onClick={() => cyclePayment(o.id)}\n                    className={`mt-2 text-xs font-bold px-2.5 py-1 rounded-full ${PAYMENT_STYLE[o.payment]}`}\n                    title=\"\u9ede\u64ca\u5207\u63db\u7d50\u5e33\u72c0\u614b\"\n                  >\n                    {o.payment} \u21bb\n                  <\/button>\n                <\/div>\n              ))}\n            <\/div>\n          <\/section>\n        <\/div>\n      <\/main>\n\n      {\/* \u65b0\u589e\u7a7a\u8eca\u76e4\u9ede Modal *\/}\n      {showAddCapacity && (\n        <AddCapacityModal\n          companies={companies}\n          onClose={() => setShowAddCapacity(false)}\n          onSubmit={submitCapacity}\n        \/>\n      )}\n\n      {\/* \u8eca\u884c\u7ba1\u7406 Drawer(Modal) *\/}\n      {showCRM && (\n        <CRMModal\n          companies={companies}\n          setCompanies={setCompanies}\n          onClose={() => setShowCRM(false)}\n        \/>\n      )}\n\n      {\/* Toast *\/}\n      {toast && (\n        <div className=\"fixed bottom-6 left-1\/2 -translate-x-1\/2 z-50 toast-in\">\n          <div className={`px-5 py-3 rounded-xl shadow-lg text-white font-bold text-sm ${\n            toast.tone === \"rose\" ? \"bg-rose-600\" : toast.tone === \"slate\" ? \"bg-slate-700\" : \"bg-emerald-600\"\n          }`}>\n            {toast.msg}\n          <\/div>\n        <\/div>\n      )}\n\n      {\/* \u5feb\u6377\u9375\u63d0\u793a *\/}\n      <div className=\"fixed bottom-4 right-4 text-[11px] text-slate-400 hidden lg:block\">\n        \u5feb\u6377\u9375\uff1a <kbd className=\"px-1 bg-white border rounded\">N<\/kbd> \u65b0\u589e\u76e4\u9ede\u3000<kbd className=\"px-1 bg-white border rounded\">F<\/kbd> \u805a\u7126\u641c\u5c0b\u3000<kbd className=\"px-1 bg-white border rounded\">Esc<\/kbd> \u95dc\u9589\u8996\u7a97\n      <\/div>\n    <\/div>\n  );\n}\n\nfunction AddCapacityModal({ companies, onClose, onSubmit }) {\n  const [mode, setMode] = useState(\"existing\"); \/\/ existing | new\n  const [form, setForm] = useState({\n    companyId: companies[0]?.id || \"\",\n    newCompanyName: \"\", newContact: \"\", newPhone: \"\",\n    entityType: \"\u8eca\u884c\", affiliatedCompany: \"\", taxId: \"\", nationalId: \"\",\n    carType: CAR_TYPES[0], count: 1, district: DISTRICTS[0],\n  });\n  const [error, setError] = useState(\"\");\n\n  function handleSubmit() {\n    if (mode === \"new\") {\n      if (!form.newCompanyName.trim()) {\n        setError(isIndividual ? \"\u8acb\u8f38\u5165\u53f8\u6a5f\u59d3\u540d\" : \"\u8acb\u8f38\u5165\u8eca\u884c\u540d\u7a31\");\n        return;\n      }\n      if (isIndividual && !isValidNationalId(form.nationalId)) {\n        setError(\"\u8acb\u8f38\u5165\u6b63\u78ba\u683c\u5f0f\u7684\u8eab\u4efd\u8b49\u5b57\u865f\uff08\u4f8b\uff1aA123456789\uff09\");\n        return;\n      }\n      if (!isIndividual && !isValidTaxId(form.taxId)) {\n        setError(\"\u8acb\u8f38\u5165\u6b63\u78ba\u683c\u5f0f\u7684\u7d71\u4e00\u7de8\u865f\uff088\u78bc\u6578\u5b57\uff09\");\n        return;\n      }\n      setError(\"\");\n      onSubmit({ ...form, companyId: \"\" });\n    } else {\n      setError(\"\");\n      onSubmit({ ...form, newCompanyName: \"\" });\n    }\n  }\n\n  const isIndividual = form.entityType === \"\u81ea\u7136\u4eba\u9760\u884c\";\n\n  return (\n    <Modal title=\"\u2795 \u65b0\u589e\u7a7a\u8eca\u76e4\u9ede\" onClose={onClose}>\n      <div className=\"space-y-4\">\n        <div className=\"flex gap-2\">\n          <button\n            onClick={() => setMode(\"existing\")}\n            className={`flex-1 py-2 rounded-lg font-bold text-sm border-2 ${mode === \"existing\" ? \"border-indigo-500 bg-indigo-50 text-indigo-700\" : \"border-slate-200 text-slate-500\"}`}\n          >\u65e2\u6709\u8eca\u884c\uff0f\u9760\u884c<\/button>\n          <button\n            onClick={() => setMode(\"new\")}\n            className={`flex-1 py-2 rounded-lg font-bold text-sm border-2 ${mode === \"new\" ? \"border-indigo-500 bg-indigo-50 text-indigo-700\" : \"border-slate-200 text-slate-500\"}`}\n          >\u2795 \u65b0\u589e\u8eca\u884c\uff0f\u53f8\u6a5f<\/button>\n        <\/div>\n\n        {mode === \"existing\" ? (\n          <div>\n            <FieldLabel>\u9078\u64c7\u8eca\u884c \/ \u9760\u884c\u53f8\u6a5f<\/FieldLabel>\n            <select className={selectCls} value={form.companyId} onChange={(e) => setForm({ ...form, companyId: e.target.value })}>\n              {companies.map((c) => (\n                <option key={c.id} value={c.id}>\n                  {(c.entityType === \"\u81ea\u7136\u4eba\u9760\u884c\" ? \"\ud83e\uddcd \" : \"\ud83c\udfe2 \") + c.name + (c.entityType === \"\u81ea\u7136\u4eba\u9760\u884c\" ? \"\uff08\u81ea\u7136\u4eba\u9760\u884c\uff09\" : \"\")}\n                <\/option>\n              ))}\n            <\/select>\n          <\/div>\n        ) : (\n          <div className=\"space-y-3\">\n            <div>\n              <FieldLabel>\u8eab\u4efd\u5225<\/FieldLabel>\n              <div className=\"flex gap-2\">\n                {ENTITY_TYPES.map((et) => (\n                  <button\n                    key={et}\n                    type=\"button\"\n                    onClick={() => setForm({ ...form, entityType: et })}\n                    className={`flex-1 py-2 rounded-lg font-bold text-sm border-2 ${\n                      form.entityType === et ? \"border-sky-500 bg-sky-50 text-sky-700\" : \"border-slate-200 text-slate-500\"\n                    }`}\n                  >\n                    {et === \"\u81ea\u7136\u4eba\u9760\u884c\" ? \"\ud83e\uddcd \u81ea\u7136\u4eba\u9760\u884c\uff08\u500b\u4eba\u8eca\u4e3b\uff09\" : \"\ud83c\udfe2 \u8eca\u884c\uff08\u516c\u53f8\uff09\"}\n                  <\/button>\n                ))}\n              <\/div>\n            <\/div>\n            <div>\n              <FieldLabel>{isIndividual ? \"\u53f8\u6a5f\u59d3\u540d\" : \"\u8eca\u884c\u540d\u7a31\"}<\/FieldLabel>\n              <input className={inputCls} placeholder={isIndividual ? \"\u8acb\u8f38\u5165\u53f8\u6a5f\u59d3\u540d\" : \"\u8acb\u8f38\u5165\u8eca\u884c\u540d\u7a31\"} value={form.newCompanyName}\n                onChange={(e) => setForm({ ...form, newCompanyName: e.target.value })} \/>\n            <\/div>\n            {isIndividual ? (\n              <div>\n                <FieldLabel>\u8eab\u4efd\u8b49\u5b57\u865f<\/FieldLabel>\n                <input className={inputCls} placeholder=\"\u4f8b\uff1aA123456789\" maxLength={10} value={form.nationalId}\n                  onChange={(e) => setForm({ ...form, nationalId: e.target.value.toUpperCase() })} \/>\n              <\/div>\n            ) : (\n              <div>\n                <FieldLabel>\u516c\u53f8\u7d71\u4e00\u7de8\u865f<\/FieldLabel>\n                <input className={inputCls} placeholder=\"\u4f8b\uff1a12345678\uff088\u78bc\u6578\u5b57\uff09\" maxLength={8} inputMode=\"numeric\" value={form.taxId}\n                  onChange={(e) => setForm({ ...form, taxId: e.target.value.replace(\/\\D\/g, \"\") })} \/>\n              <\/div>\n            )}\n            <div className=\"grid grid-cols-2 gap-3\">\n              <div>\n                <FieldLabel>\u806f\u7d61\u4eba<\/FieldLabel>\n                <input className={inputCls} value={form.newContact} onChange={(e) => setForm({ ...form, newContact: e.target.value })} \/>\n              <\/div>\n              <div>\n                <FieldLabel>\u806f\u7d61\u96fb\u8a71<\/FieldLabel>\n                <input className={inputCls} value={form.newPhone} onChange={(e) => setForm({ ...form, newPhone: e.target.value })} \/>\n              <\/div>\n            <\/div>\n            {isIndividual && (\n              <div>\n                <FieldLabel>\u9760\u884c\u8eca\u884c\uff08\u9078\u586b\uff09<\/FieldLabel>\n                <input className={inputCls} placeholder=\"\u4f8b\u5982\uff1a\u639b\u9760\u65bc\u9d3b\u904b\u901a\u904b\" value={form.affiliatedCompany}\n                  onChange={(e) => setForm({ ...form, affiliatedCompany: e.target.value })} \/>\n              <\/div>\n            )}\n            {error && <p className=\"text-sm text-rose-600 font-bold\">\u26a0 {error}<\/p>}\n          <\/div>\n        )}\n\n        <div className=\"grid grid-cols-2 gap-3\">\n          <div>\n            <FieldLabel>\u8eca\u578b<\/FieldLabel>\n            <select className={selectCls} value={form.carType} onChange={(e) => setForm({ ...form, carType: e.target.value })}>\n              {CAR_TYPES.map((t) => <option key={t}>{t}<\/option>)}\n            <\/select>\n          <\/div>\n          <div>\n            <FieldLabel>\u7a7a\u8eca\u6578\u91cf<\/FieldLabel>\n            <input type=\"number\" min=\"1\" className={inputCls} value={form.count} onChange={(e) => setForm({ ...form, count: e.target.value })} \/>\n          <\/div>\n        <\/div>\n        <div>\n          <FieldLabel>\u7a7a\u51fa\u5340\u57df<\/FieldLabel>\n          <select className={selectCls} value={form.district} onChange={(e) => setForm({ ...form, district: e.target.value })}>\n            {DISTRICTS.map((d) => <option key={d}>{d}<\/option>)}\n          <\/select>\n        <\/div>\n\n        <div className=\"flex gap-3 pt-2\">\n          <BigButton color=\"slate\" className=\"flex-1 py-3\" onClick={onClose}>\u53d6\u6d88<\/BigButton>\n          <BigButton color=\"emerald\" className=\"flex-1 py-3\" onClick={handleSubmit}>\u2705 \u78ba\u8a8d\u9001\u51fa<\/BigButton>\n        <\/div>\n      <\/div>\n    <\/Modal>\n  );\n}\n\nconst NEW_COMPANY_DEFAULT = { name: \"\", contact: \"\", phone: \"\", fleetSize: 1, tags: [], entityType: \"\u8eca\u884c\", affiliatedCompany: \"\", taxId: \"\", nationalId: \"\" };\n\nfunction CRMModal({ companies, setCompanies, onClose }) {\n  const [adding, setAdding] = useState(false);\n  const [newC, setNewC] = useState(NEW_COMPANY_DEFAULT);\n  const [error, setError] = useState(\"\");\n\n  function addCompany() {\n    if (!newC.name.trim()) {\n      setError(newC.entityType === \"\u81ea\u7136\u4eba\u9760\u884c\" ? \"\u8acb\u8f38\u5165\u53f8\u6a5f\u59d3\u540d\" : \"\u8acb\u8f38\u5165\u8eca\u884c\u540d\u7a31\");\n      return;\n    }\n    if (newC.entityType === \"\u81ea\u7136\u4eba\u9760\u884c\" && !isValidNationalId(newC.nationalId)) {\n      setError(\"\u8acb\u8f38\u5165\u6b63\u78ba\u683c\u5f0f\u7684\u8eab\u4efd\u8b49\u5b57\u865f\uff08\u4f8b\uff1aA123456789\uff09\");\n      return;\n    }\n    if (newC.entityType !== \"\u81ea\u7136\u4eba\u9760\u884c\" && !isValidTaxId(newC.taxId)) {\n      setError(\"\u8acb\u8f38\u5165\u6b63\u78ba\u683c\u5f0f\u7684\u7d71\u4e00\u7de8\u865f\uff088\u78bc\u6578\u5b57\uff09\");\n      return;\n    }\n    setError(\"\");\n    setCompanies((prev) => [...prev, {\n      id: uid(),\n      reputation: 3,\n      ...newC,\n      fleetSize: Number(newC.fleetSize) || 1,\n      affiliatedCompany: newC.entityType === \"\u81ea\u7136\u4eba\u9760\u884c\" ? newC.affiliatedCompany : \"\",\n      taxId: newC.entityType === \"\u81ea\u7136\u4eba\u9760\u884c\" ? \"\" : newC.taxId,\n      nationalId: newC.entityType === \"\u81ea\u7136\u4eba\u9760\u884c\" ? newC.nationalId : \"\",\n    }]);\n    setNewC(NEW_COMPANY_DEFAULT);\n    setAdding(false);\n  }\n  function toggleTag(cid, tag) {\n    setCompanies((prev) => prev.map((c) => c.id === cid\n      ? { ...c, tags: c.tags.includes(tag) ? c.tags.filter((t) => t !== tag) : [...c.tags, tag] }\n      : c));\n  }\n  function changeRep(cid, delta) {\n    setCompanies((prev) => prev.map((c) => c.id === cid\n      ? { ...c, reputation: Math.max(1, Math.min(5, c.reputation + delta)) }\n      : c));\n  }\n\n  return (\n    <Modal title=\"\ud83c\udfe2 \u8eca\u884c\u8cc7\u6599\u5eab\u7ba1\u7406\" onClose={onClose} wide>\n      <div className=\"flex justify-end mb-3\">\n        <BigButton color=\"indigo\" className=\"px-4 py-2 text-sm\" onClick={() => { setAdding((v) => !v); setError(\"\"); }}>\n          {adding ? \"\uff0d \u53d6\u6d88\u65b0\u589e\" : \"\uff0b \u65b0\u589e\u8eca\u884c\uff0f\u53f8\u6a5f\"}\n        <\/BigButton>\n      <\/div>\n\n      {adding && (\n        <div className=\"bg-slate-50 rounded-xl p-4 mb-4 space-y-3\">\n          <div>\n            <FieldLabel>\u8eab\u4efd\u5225<\/FieldLabel>\n            <div className=\"flex gap-2\">\n              {ENTITY_TYPES.map((et) => (\n                <button\n                  key={et}\n                  type=\"button\"\n                  onClick={() => setNewC({ ...newC, entityType: et })}\n                  className={`flex-1 py-2 rounded-lg font-bold text-sm border-2 ${\n                    newC.entityType === et ? \"border-sky-500 bg-sky-50 text-sky-700\" : \"border-slate-200 text-slate-500\"\n                  }`}\n                >\n                  {et === \"\u81ea\u7136\u4eba\u9760\u884c\" ? \"\ud83e\uddcd \u81ea\u7136\u4eba\u9760\u884c\uff08\u500b\u4eba\u8eca\u4e3b\uff09\" : \"\ud83c\udfe2 \u8eca\u884c\uff08\u516c\u53f8\uff09\"}\n                <\/button>\n              ))}\n            <\/div>\n          <\/div>\n          <div className=\"grid grid-cols-2 gap-3\">\n            <div>\n              <FieldLabel>{newC.entityType === \"\u81ea\u7136\u4eba\u9760\u884c\" ? \"\u53f8\u6a5f\u59d3\u540d\" : \"\u8eca\u884c\u540d\u7a31\"}<\/FieldLabel>\n              <input className={inputCls} value={newC.name} onChange={(e) => setNewC({ ...newC, name: e.target.value })} \/>\n            <\/div>\n            {newC.entityType === \"\u81ea\u7136\u4eba\u9760\u884c\" ? (\n              <div>\n                <FieldLabel>\u8eab\u4efd\u8b49\u5b57\u865f<\/FieldLabel>\n                <input className={inputCls} placeholder=\"\u4f8b\uff1aA123456789\" maxLength={10} value={newC.nationalId}\n                  onChange={(e) => setNewC({ ...newC, nationalId: e.target.value.toUpperCase() })} \/>\n              <\/div>\n            ) : (\n              <div>\n                <FieldLabel>\u516c\u53f8\u7d71\u4e00\u7de8\u865f<\/FieldLabel>\n                <input className={inputCls} placeholder=\"\u4f8b\uff1a12345678\uff088\u78bc\u6578\u5b57\uff09\" maxLength={8} inputMode=\"numeric\" value={newC.taxId}\n                  onChange={(e) => setNewC({ ...newC, taxId: e.target.value.replace(\/\\D\/g, \"\") })} \/>\n              <\/div>\n            )}\n            <div>\n              <FieldLabel>\u806f\u7d61\u4eba<\/FieldLabel>\n              <input className={inputCls} value={newC.contact} onChange={(e) => setNewC({ ...newC, contact: e.target.value })} \/>\n            <\/div>\n            <div>\n              <FieldLabel>\u806f\u7d61\u96fb\u8a71<\/FieldLabel>\n              <input className={inputCls} value={newC.phone} onChange={(e) => setNewC({ ...newC, phone: e.target.value })} \/>\n            <\/div>\n            {newC.entityType === \"\u81ea\u7136\u4eba\u9760\u884c\" ? (\n              <div>\n                <FieldLabel>\u9760\u884c\u8eca\u884c\uff08\u9078\u586b\uff09<\/FieldLabel>\n                <input className={inputCls} placeholder=\"\u4f8b\u5982\uff1a\u639b\u9760\u65bc\u9d3b\u904b\u901a\u904b\" value={newC.affiliatedCompany}\n                  onChange={(e) => setNewC({ ...newC, affiliatedCompany: e.target.value })} \/>\n              <\/div>\n            ) : (\n              <div>\n                <FieldLabel>\u8eca\u968a\u898f\u6a21\uff08\u53f0\uff09<\/FieldLabel>\n                <input type=\"number\" min=\"1\" className={inputCls} value={newC.fleetSize} onChange={(e) => setNewC({ ...newC, fleetSize: e.target.value })} \/>\n              <\/div>\n            )}\n          <\/div>\n          {error && <p className=\"text-sm text-rose-600 font-bold\">\u26a0 {error}<\/p>}\n          <BigButton color=\"emerald\" className=\"px-5 py-2.5 text-sm\" onClick={addCompany}>\u2705 \u78ba\u8a8d\u65b0\u589e<\/BigButton>\n        <\/div>\n      )}\n\n      <div className=\"space-y-3 max-h-[55vh] overflow-y-auto pr-1\">\n        {companies.map((c) => (\n          <div key={c.id} className=\"border border-slate-200 rounded-xl p-3\">\n            <div className=\"flex items-center justify-between flex-wrap gap-2\">\n              <div>\n                <div className=\"flex items-center gap-1.5\">\n                  <span className=\"font-extrabold text-slate-800\">{c.name}<\/span>\n                  <span className={`text-[10px] font-bold px-1.5 py-0.5 rounded-full whitespace-nowrap ${ENTITY_BADGE[c.entityType || \"\u8eca\u884c\"].cls}`}>\n                    {ENTITY_BADGE[c.entityType || \"\u8eca\u884c\"].text}\n                  <\/span>\n                <\/div>\n                <div className=\"text-xs text-slate-500\">\n                  {c.contact}\u30fb{c.phone}\n                  {c.entityType === \"\u81ea\u7136\u4eba\u9760\u884c\"\n                    ? (c.affiliatedCompany ? `\u30fb\u9760\u884c\u65bc ${c.affiliatedCompany}` : \"\u30fb\u7368\u7acb\u63a5\u6848\")\n                    : `\u30fb\u8eca\u968a ${c.fleetSize} \u53f0`}\n                <\/div>\n                <div className=\"text-xs text-slate-400 mt-0.5 font-mono\">\n                  {c.entityType === \"\u81ea\u7136\u4eba\u9760\u884c\"\n                    ? (c.nationalId ? `\u8eab\u5206\u8b49 ${maskNationalId(c.nationalId)}` : \"\u672a\u586b\u8eab\u5206\u8b49\u5b57\u865f\")\n                    : (c.taxId ? `\u7d71\u7de8 ${c.taxId}` : \"\u672a\u586b\u7d71\u4e00\u7de8\u865f\")}\n                <\/div>\n              <\/div>\n              <div className=\"flex items-center gap-1.5\">\n                <button onClick={() => changeRep(c.id, -1)} className=\"w-7 h-7 rounded-full bg-slate-100 text-slate-500 font-bold hover:bg-slate-200\">\uff0d<\/button>\n                <span className=\"text-amber-500 font-bold w-14 text-center\">{\"\u2605\".repeat(c.reputation)}<\/span>\n                <button onClick={() => changeRep(c.id, 1)} className=\"w-7 h-7 rounded-full bg-slate-100 text-slate-500 font-bold hover:bg-slate-200\">\uff0b<\/button>\n              <\/div>\n            <\/div>\n            <div className=\"flex flex-wrap gap-1.5 mt-2\">\n              {TAG_OPTIONS.map((t) => {\n                const on = c.tags.includes(t);\n                return (\n                  <button\n                    key={t}\n                    onClick={() => toggleTag(c.id, t)}\n                    className={`text-xs px-2.5 py-1 rounded-full border font-bold ${\n                      on ? \"bg-indigo-600 border-indigo-600 text-white\" : \"bg-white border-slate-300 text-slate-500\"\n                    }`}\n                  >{t}<\/button>\n                );\n              })}\n            <\/div>\n          <\/div>\n        ))}\n      <\/div>\n    <\/Modal>\n  );\n}\n\nReactDOM.createRoot(document.getElementById(\"root\")).render(<App \/>);\n<\/script>\n<script>\n  var src = document.getElementById(\"app-source\").textContent;\n  var out = Babel.transform(src, { presets: [[\"react\", { runtime: \"classic\" }]] }).code;\n  var s = document.createElement(\"script\");\n  s.textContent = out;\n  document.body.appendChild(s);\n<\/script>\n<\/body>\n<\/html>\n\n","protected":false},"excerpt":{"rendered":"<p>\u8ca8\u904b\u667a\u80fd\u8abf\u5ea6\u6230\u60c5\u5ba4<\/p>\n","protected":false},"author":1,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"footnotes":""},"class_list":["post-72","page","type-page","status-publish","hentry","entry","owp-thumbs-layout-horizontal","owp-btn-normal","owp-tabs-layout-horizontal","has-no-thumbnails","has-product-nav"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v27.9 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>\u8ca8\u904b\u667a\u80fd\u8abf\u5ea6\u6230\u60c5\u5ba4 - \u5947\u54fa\u7684\u6843\u5712\u7b46\u8a18<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/st.eyou.tw\/ai01\/\u8ca8\u904b\u667a\u80fd\u8abf\u5ea6\u6230\u60c5\u5ba4\/\" \/>\n<meta property=\"og:locale\" content=\"zh_TW\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"\u8ca8\u904b\u667a\u80fd\u8abf\u5ea6\u6230\u60c5\u5ba4 - \u5947\u54fa\u7684\u6843\u5712\u7b46\u8a18\" \/>\n<meta property=\"og:description\" content=\"\u8ca8\u904b\u667a\u80fd\u8abf\u5ea6\u6230\u60c5\u5ba4\" \/>\n<meta property=\"og:url\" content=\"https:\/\/st.eyou.tw\/ai01\/\u8ca8\u904b\u667a\u80fd\u8abf\u5ea6\u6230\u60c5\u5ba4\/\" \/>\n<meta property=\"og:site_name\" content=\"\u5947\u54fa\u7684\u6843\u5712\u7b46\u8a18\" \/>\n<meta property=\"article:modified_time\" content=\"2026-07-12T07:40:38+00:00\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"\u9810\u4f30\u95b1\u8b80\u6642\u9593\" \/>\n\t<meta name=\"twitter:data1\" content=\"1 \u5206\u9418\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/st.eyou.tw\\\/ai01\\\/%e8%b2%a8%e9%81%8b%e6%99%ba%e8%83%bd%e8%aa%bf%e5%ba%a6%e6%88%b0%e6%83%85%e5%ae%a4\\\/\",\"url\":\"https:\\\/\\\/st.eyou.tw\\\/ai01\\\/%e8%b2%a8%e9%81%8b%e6%99%ba%e8%83%bd%e8%aa%bf%e5%ba%a6%e6%88%b0%e6%83%85%e5%ae%a4\\\/\",\"name\":\"\u8ca8\u904b\u667a\u80fd\u8abf\u5ea6\u6230\u60c5\u5ba4 - \u5947\u54fa\u7684\u6843\u5712\u7b46\u8a18\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/st.eyou.tw\\\/ai01\\\/#website\"},\"datePublished\":\"2026-07-12T07:35:17+00:00\",\"dateModified\":\"2026-07-12T07:40:38+00:00\",\"breadcrumb\":{\"@id\":\"https:\\\/\\\/st.eyou.tw\\\/ai01\\\/%e8%b2%a8%e9%81%8b%e6%99%ba%e8%83%bd%e8%aa%bf%e5%ba%a6%e6%88%b0%e6%83%85%e5%ae%a4\\\/#breadcrumb\"},\"inLanguage\":\"zh-TW\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/st.eyou.tw\\\/ai01\\\/%e8%b2%a8%e9%81%8b%e6%99%ba%e8%83%bd%e8%aa%bf%e5%ba%a6%e6%88%b0%e6%83%85%e5%ae%a4\\\/\"]}]},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/st.eyou.tw\\\/ai01\\\/%e8%b2%a8%e9%81%8b%e6%99%ba%e8%83%bd%e8%aa%bf%e5%ba%a6%e6%88%b0%e6%83%85%e5%ae%a4\\\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"\u9996\u9801\",\"item\":\"https:\\\/\\\/st.eyou.tw\\\/ai01\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"\u8ca8\u904b\u667a\u80fd\u8abf\u5ea6\u6230\u60c5\u5ba4\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/st.eyou.tw\\\/ai01\\\/#website\",\"url\":\"https:\\\/\\\/st.eyou.tw\\\/ai01\\\/\",\"name\":\"\u5947\u54fa\u7684\u6843\u5712\u7b46\u8a18\",\"description\":\"\",\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/st.eyou.tw\\\/ai01\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"zh-TW\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"\u8ca8\u904b\u667a\u80fd\u8abf\u5ea6\u6230\u60c5\u5ba4 - \u5947\u54fa\u7684\u6843\u5712\u7b46\u8a18","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/st.eyou.tw\/ai01\/\u8ca8\u904b\u667a\u80fd\u8abf\u5ea6\u6230\u60c5\u5ba4\/","og_locale":"zh_TW","og_type":"article","og_title":"\u8ca8\u904b\u667a\u80fd\u8abf\u5ea6\u6230\u60c5\u5ba4 - \u5947\u54fa\u7684\u6843\u5712\u7b46\u8a18","og_description":"\u8ca8\u904b\u667a\u80fd\u8abf\u5ea6\u6230\u60c5\u5ba4","og_url":"https:\/\/st.eyou.tw\/ai01\/\u8ca8\u904b\u667a\u80fd\u8abf\u5ea6\u6230\u60c5\u5ba4\/","og_site_name":"\u5947\u54fa\u7684\u6843\u5712\u7b46\u8a18","article_modified_time":"2026-07-12T07:40:38+00:00","twitter_card":"summary_large_image","twitter_misc":{"\u9810\u4f30\u95b1\u8b80\u6642\u9593":"1 \u5206\u9418"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"WebPage","@id":"https:\/\/st.eyou.tw\/ai01\/%e8%b2%a8%e9%81%8b%e6%99%ba%e8%83%bd%e8%aa%bf%e5%ba%a6%e6%88%b0%e6%83%85%e5%ae%a4\/","url":"https:\/\/st.eyou.tw\/ai01\/%e8%b2%a8%e9%81%8b%e6%99%ba%e8%83%bd%e8%aa%bf%e5%ba%a6%e6%88%b0%e6%83%85%e5%ae%a4\/","name":"\u8ca8\u904b\u667a\u80fd\u8abf\u5ea6\u6230\u60c5\u5ba4 - \u5947\u54fa\u7684\u6843\u5712\u7b46\u8a18","isPartOf":{"@id":"https:\/\/st.eyou.tw\/ai01\/#website"},"datePublished":"2026-07-12T07:35:17+00:00","dateModified":"2026-07-12T07:40:38+00:00","breadcrumb":{"@id":"https:\/\/st.eyou.tw\/ai01\/%e8%b2%a8%e9%81%8b%e6%99%ba%e8%83%bd%e8%aa%bf%e5%ba%a6%e6%88%b0%e6%83%85%e5%ae%a4\/#breadcrumb"},"inLanguage":"zh-TW","potentialAction":[{"@type":"ReadAction","target":["https:\/\/st.eyou.tw\/ai01\/%e8%b2%a8%e9%81%8b%e6%99%ba%e8%83%bd%e8%aa%bf%e5%ba%a6%e6%88%b0%e6%83%85%e5%ae%a4\/"]}]},{"@type":"BreadcrumbList","@id":"https:\/\/st.eyou.tw\/ai01\/%e8%b2%a8%e9%81%8b%e6%99%ba%e8%83%bd%e8%aa%bf%e5%ba%a6%e6%88%b0%e6%83%85%e5%ae%a4\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"\u9996\u9801","item":"https:\/\/st.eyou.tw\/ai01\/"},{"@type":"ListItem","position":2,"name":"\u8ca8\u904b\u667a\u80fd\u8abf\u5ea6\u6230\u60c5\u5ba4"}]},{"@type":"WebSite","@id":"https:\/\/st.eyou.tw\/ai01\/#website","url":"https:\/\/st.eyou.tw\/ai01\/","name":"\u5947\u54fa\u7684\u6843\u5712\u7b46\u8a18","description":"","potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/st.eyou.tw\/ai01\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"zh-TW"}]}},"_links":{"self":[{"href":"https:\/\/st.eyou.tw\/ai01\/wp-json\/wp\/v2\/pages\/72","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/st.eyou.tw\/ai01\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/st.eyou.tw\/ai01\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/st.eyou.tw\/ai01\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/st.eyou.tw\/ai01\/wp-json\/wp\/v2\/comments?post=72"}],"version-history":[{"count":2,"href":"https:\/\/st.eyou.tw\/ai01\/wp-json\/wp\/v2\/pages\/72\/revisions"}],"predecessor-version":[{"id":78,"href":"https:\/\/st.eyou.tw\/ai01\/wp-json\/wp\/v2\/pages\/72\/revisions\/78"}],"wp:attachment":[{"href":"https:\/\/st.eyou.tw\/ai01\/wp-json\/wp\/v2\/media?parent=72"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}