From 2d800094b14c0a0a5df25dcfc4fea4fb6db51e2f Mon Sep 17 00:00:00 2001 From: Daniel Date: Mon, 2 Mar 2026 14:23:36 +0800 Subject: [PATCH] =?UTF-8?q?fix:=E4=BC=98=E5=8C=96docker=20p=E9=85=8D?= =?UTF-8?q?=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 10 +++ .../__pycache__/cleaner_ai.cpython-311.pyc | Bin 0 -> 4794 bytes crawler/__pycache__/config.cpython-311.pyc | Bin 0 -> 2725 bytes crawler/__pycache__/db_writer.cpython-311.pyc | Bin 0 -> 5673 bytes crawler/__pycache__/parser.cpython-311.pyc | Bin 0 -> 2370 bytes crawler/__pycache__/parser_ai.cpython-311.pyc | Bin 0 -> 4699 bytes .../realtime_conflict_service.cpython-311.pyc | Bin 0 -> 22870 bytes .../translate_utils.cpython-311.pyc | Bin 0 -> 2297 bytes .../__pycache__/rss_scraper.cpython-311.pyc | Bin 3322 -> 3677 bytes docker-compose.dev.yml | 13 +++ server/db.js | 12 +++ server/routes.js | 19 ++++ src/components/HeaderPanel.tsx | 82 +++++++++++++++++- 13 files changed, 135 insertions(+), 1 deletion(-) create mode 100644 crawler/__pycache__/cleaner_ai.cpython-311.pyc create mode 100644 crawler/__pycache__/config.cpython-311.pyc create mode 100644 crawler/__pycache__/db_writer.cpython-311.pyc create mode 100644 crawler/__pycache__/parser.cpython-311.pyc create mode 100644 crawler/__pycache__/parser_ai.cpython-311.pyc create mode 100644 crawler/__pycache__/realtime_conflict_service.cpython-311.pyc create mode 100644 crawler/__pycache__/translate_utils.cpython-311.pyc create mode 100644 docker-compose.dev.yml diff --git a/README.md b/README.md index 6f49cbd..d070f40 100644 --- a/README.md +++ b/README.md @@ -90,6 +90,16 @@ docker compose up -d **拉取镜像超时?** 在 Docker Desktop 配置镜像加速,见 [docs/DOCKER_MIRROR.md](docs/DOCKER_MIRROR.md) +**开发时无需每次 rebuild**:使用开发模式挂载源码 + 热重载: + +```bash +docker compose -f docker-compose.yml -f docker-compose.dev.yml up -d +``` + +- API:`node --watch` 监听 `server/` 变更并自动重启 +- 爬虫:`uvicorn --reload` 监听 `crawler/` 变更并自动重启 +- 修改 `server/` 或 `crawler/` 后,服务会自动重载,无需重新 build + 环境变量(可选,在 .env 或 docker-compose.yml 中配置): - `VITE_MAPBOX_ACCESS_TOKEN`:Mapbox 令牌,构建时注入 diff --git a/crawler/__pycache__/cleaner_ai.cpython-311.pyc b/crawler/__pycache__/cleaner_ai.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..df451e635b62aeaea692c0a2f5859a5de6d5760b GIT binary patch literal 4794 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 CNZmdF literal 0 HcmV?d00001 diff --git a/crawler/__pycache__/db_writer.cpython-311.pyc b/crawler/__pycache__/db_writer.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..db00cd24bd341b3b5e848f63b985fbbdbed61994 GIT binary patch literal 5673 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 literal 0 HcmV?d00001 diff --git a/crawler/__pycache__/parser_ai.cpython-311.pyc b/crawler/__pycache__/parser_ai.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..cb9b7212e3726d0ef9bbec94ed7564b13bd4bc04 GIT binary patch literal 4699 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 W3*5rg5})pVI@{$g_B@#C_xwMieYFb! literal 0 HcmV?d00001 diff --git a/crawler/__pycache__/realtime_conflict_service.cpython-311.pyc b/crawler/__pycache__/realtime_conflict_service.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ef0d3a0153a5939d406d651e6b5f54cbfa80d135 GIT binary patch literal 22870 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 yGDaunl|`(wD6Xi+|c&4F+L literal 0 HcmV?d00001 diff --git a/crawler/__pycache__/translate_utils.cpython-311.pyc b/crawler/__pycache__/translate_utils.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..58d7e0498cf417a64bfdae1c7c9c30e23dfb8696 GIT binary patch literal 2297 zcmbtV|8G-O6u<9F`}$>D$J#xZ3^NqAl?)kTSWF0JvN?==k!^`GA+_$kjk30H-uKGZ z@k%Q4rRdax8ygX%F@Q5*7{=%#CM1SG;B_{kdC8I`&@p~;3BN4F7|(rOyLReNp1ya_ zJ@LJx_MBajVx`UbEuB%m%NPy+3zx+sIwT{LjU&ji>m zmO=t6ngo+*?qLPwFG&p(>_{*PaWGpDa-e)%$U z?z6{tuRfZJKE8JsR8L6aeQrv(?C)26Qqb$ySw$RFbTbx}5Ds=z#9)ccz!`X-&cZN; zRD?Gw1$q@F3M4RDq8q?(<^*&DY;NZ51gg)_V47527)>$dUC>dI)gXaU(G40Rz?-Gw zco9ma6l+e0iXwt%ac;Ia(vTe?!PIA<0VinQ_k<*QW61H|q0lR0W8>ySp;vo)dJaD! z)%R{Q==ay$CY|w%LER)P*w?SqSk$>5Ur_M+{W=ry4!UVfBI;cC5noW0MVSPxsd=_~ zi?_dji-NsD+3!_EPe}3k<(K-ydWFX)djgWI_`{whA6yPT7I@`u7@{a`cPt>vR5`jW zu8a*`7&@-g&9&a#f-^Sy`K^tWDZ$C!Evv290fa3TaRg_5=+B`UI-n{&l7oE3xkk5UyO{v^cg-l8>;}{m6#?91Lfz%6-6iokj{}xt-KZ4 z8-Q&pC71;EG+mW5Mc`SQ7tl)2Y;}UU1sy&Up{+=z1#YSCI>91X{anBjVJJi+l@V-$ z{Z>iwE)xa&`tK|eHdqVMN+FspVltwc{!cWO#m}p3VmV>KtoYpEL|B#TGcd!f%68Kf zAUizpB2xHerws_h9uK17j|b2oa}W(s1b=r~NC2D6g&wsSHbLI^kkbP_4>t{CF`-DGLu*4Z;#gO z4eu2L5)OlfUeVd!ysu+Vb7!lk<;{-f*Y~ux=v1RlZS`p!X?C=3?lq$5mOwaQxM8vZ zOzs%8!t!j6&ZSRN!aC~>DbkQ>=*Z?>hPy0zD@_^fq@Ad3B-TqBS$UPIihxdjnd8*=Mlw`jCzw>jHTC|S4V zjB;L#6jBU(*$hrK24M&BcjZh)k0Ry!N*$<(T7DmTS`^J@`-*G1tB^M1N;{k}`wVA2 zHFWmG$rBpCX<}EBYfN#Cle?z5CXH*#8d&VHmUGr&>lcns97#)6%2E|=p5bh8J)Yzwr_TA6lKjlVnX z*r+)+rptF|#ZQ{A)%e<5zMH|`83xP6E4DxxF!J+tfx zNJ7Xj?!*lsl0PUp0t4iys998}p}I7(MvY&=WUYpZo&_^YZ65DfK)@2623t9lt)|My Rug2|~f>z`QpIrQ!9 zxAVSv^XBdBd$Z3w>jB5NcDn@-Ira3L)M@jg!wbJ!>>U|}02u`)$sp4g+<6P)1uN05 zNe-EbZbKGbw-en;bO#x05uBaWk1k}Mjqt6DKfdRLTKi-=A&RNwTs$$2;`c1@Iy40K zCT3=eKQTL@=iVPo7YupHL=h4Q;|_wFH7-gxH50!qWzslU3~|4Y_z3^uXq3?m>1Y`z7)X7g6bv(2M*-Q)R9N`wnoHX(p)UotN;PqrkPyiK;r z%vFHl!sjwmwr$SX{*-Oa0YV`8i$56s6Lu;1!cN{;bd&j*0U+U-qstGfDYuKxLxDrb4j1s-vM z(%C8(u5sZC7j7_xZbPi@4m3c>>|UjCZKT1N?45N_cg@qQwt3VYgO#E8E0L3`Z?IwF zyslM1;28ylQ@FK%Eubyf0MOp~0#v70;TtUB*4EaZbOvt||Lu0S4vL}f?J66}xfQ1B z9jX<)Lly7aE11xs9c5wp+X8ezBNqr$K}p40_w6W8 zRek$vzI}?R?g^D<%gN=7RnK6}Gg$E)Tsf<7s;{f!*-cuxcm1hwb94!^8QN4E`?Ry9i@metPIm(XWLEw248Y7+L!E#y&mXYHdF(# zS|FyF>;8e2wyOU?&3|B#S6F3Cb++HM-LR>_x72}ym9Ar@Tv=LX%hK(9DSv-l^>@{~ z`y5jAJ_w_GB`3! zv|m}zsE>`hG*di|vXk*R_7ew~{su5d;b8H)wc{i6OahAnjwe!jjp)@vicZ3M8EM?b zY&N}KOk6r8N*ae!laijb-vLbjeR@Ve=MQDFXf`bz#%Bme|61aoMaDMn++6`pt(dVT V`lGN_*U2M}<6u#GX;y6D|6E`7E=nu37Qc5yGt8OrupsiL|J56GFfPsREonlDR?%L2H zoTbt>$wSp>_5>Ky*q}mJ-X<=CTdfd2u|rxN_1*S*}b@P!u_P-EuwR;4Qf8 z0~(GHB3C#|SR zo=IO)^EuRMK;}mnGoCQvfo_t8ss*noG@*AtF9@Xbf||J`Dd{WH_1S48DyYv0I8uAM zFe`)ab@Sj6DgCuf0dN~kfg(_$VKNcpeWtav0MEZvq&T2D)(gO8Lbpw$L?}`+d`REN z#SiekW<|Qt+5=SAKRy7x0MQVf0rT-$@E&~z%tDR6yguhBIR8hHQGFYHjS(5qasNDC z>cRSQGf;FC9ouM5Oxwx|Zey_jVvu>9s^56X#!Boqw(Es#m*%(wL{4MxfO~Y2Qv;h? z{>+Y~sDG=~U8_ea!*Iy6_7D!Mhd2Co4G=ReS($q~BoJL@k2&AoLF2-MKp zYy>rCYqsc7i7K9(wr%~QSKU|^Xq@KV_H%q1FgJZ_+{0fidjV(;jYV#a#hrULcV2)v zIzeMu5TydNxwDyvxi zF(brvg`dIhU*5v+B`@I58F!+6c#Pu4DXznVh??Rp7OhCeXI!G>C#g;XeHbiS5JeOr zE=z!rEvGOuBNaqkNqdwLRMNpUrPP9byxlh0+E(SV65e=eA(un1VM-6al`rwz%z=6&Iycq`MHW*_ zskzag{gEb!Is8uv=ya2!xln`esPR4K$m4;^c)Ie+g$gfJd7;J&`lvp-+8`B>3)xVz zvF>ft$D1?`uCA{B6lz~O*92kCZ-YM8Xp5D{%;Dt|)wX1(BS~-ZL(OqWWa{J1O zYIL|39j-(#elz^-&<_VHXWpqEIafP!4o4~%F6wVI!n@4<)o`K~PUx>U_-J{^+_Sv9 z${(ol2P!50&`MArt%qY3elNbtQ)^Fsw88h9Z&&&L8sCq5^tIPpBXvGf=F7Xw{LOrC+8>i$ws8d9IHn9Ymt8aWFr_Y4=yH_68eZe zH2G?fw>8VsOxTIi2B-noN{~zA;vgwvFWrp zok^$BPEf+)AO{m-2pq&WJW;v(BF`xtxzl=71`3Yn2XcXxitYZ zIayIHMwBzEEu1d^B4@QNs%NfO^V8zhoOBG0;}|(=D5Ys!@uk9j74U+~sQ83|ZWve? QEHg`on*b006YORF1`!#vu>b%7 diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml new file mode 100644 index 0000000..d291f0d --- /dev/null +++ b/docker-compose.dev.yml @@ -0,0 +1,13 @@ +# 开发模式:挂载源码 + 热重载,代码更新后无需重新 build +# 使用: docker compose -f docker-compose.yml -f docker-compose.dev.yml up -d +# 或: docker compose --profile dev up -d (需在 dev 服务加 profiles) +services: + api: + volumes: + - ./server:/app/server:ro + command: ["node", "--watch", "server/index.js"] + + crawler: + volumes: + - ./crawler:/app + command: ["uvicorn", "realtime_conflict_service:app", "--host", "0.0.0.0", "--port", "8000", "--reload"] diff --git a/server/db.js b/server/db.js index f7d77b5..b55a414 100644 --- a/server/db.js +++ b/server/db.js @@ -162,4 +162,16 @@ try { `) } catch (_) {} +// 后台留言:供开发者收集用户反馈 +try { + db.exec(` + CREATE TABLE IF NOT EXISTS feedback ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + content TEXT NOT NULL, + ip TEXT, + created_at TEXT NOT NULL DEFAULT (datetime('now')) + ) + `) +} catch (_) {} + module.exports = db diff --git a/server/routes.js b/server/routes.js index 8119323..d8427e3 100644 --- a/server/routes.js +++ b/server/routes.js @@ -8,6 +8,7 @@ const router = express.Router() router.get('/db/dashboard', (req, res) => { try { const tables = [ + 'feedback', 'situation', 'force_summary', 'power_index', @@ -23,6 +24,7 @@ router.get('/db/dashboard', (req, res) => { ] const data = {} const timeSort = { + feedback: 'created_at DESC', situation: 'updated_at DESC', situation_update: 'timestamp DESC', gdelt_events: 'event_time DESC', @@ -89,6 +91,23 @@ router.post('/visit', (req, res) => { } }) +router.post('/feedback', (req, res) => { + try { + const content = (req.body?.content ?? '').toString().trim() + if (!content || content.length > 2000) { + return res.status(400).json({ ok: false, error: '留言内容 1–2000 字' }) + } + const ip = getClientIp(req) + db.prepare( + 'INSERT INTO feedback (content, ip) VALUES (?, ?)' + ).run(content.slice(0, 2000), ip) + res.json({ ok: true }) + } catch (err) { + console.error(err) + res.status(500).json({ ok: false, error: err.message }) + } +}) + router.get('/stats', (req, res) => { try { const viewers = db.prepare( diff --git a/src/components/HeaderPanel.tsx b/src/components/HeaderPanel.tsx index 6cdfda6..5bb96f5 100644 --- a/src/components/HeaderPanel.tsx +++ b/src/components/HeaderPanel.tsx @@ -3,7 +3,7 @@ import { StatCard } from './StatCard' import { useSituationStore } from '@/store/situationStore' import { useReplaySituation } from '@/hooks/useReplaySituation' import { usePlaybackStore } from '@/store/playbackStore' -import { Wifi, WifiOff, Clock, Share2, Heart, Eye } from 'lucide-react' +import { Wifi, WifiOff, Clock, Share2, Heart, Eye, MessageSquare } from 'lucide-react' const STORAGE_LIKES = 'us-iran-dashboard-likes' @@ -25,6 +25,10 @@ export function HeaderPanel() { const [liked, setLiked] = useState(false) const [viewers, setViewers] = useState(0) const [cumulative, setCumulative] = useState(0) + const [feedbackOpen, setFeedbackOpen] = useState(false) + const [feedbackText, setFeedbackText] = useState('') + const [feedbackSending, setFeedbackSending] = useState(false) + const [feedbackDone, setFeedbackDone] = useState(false) useEffect(() => { const timer = setInterval(() => setNow(new Date()), 1000) @@ -69,6 +73,32 @@ export function HeaderPanel() { return navigator.clipboard?.writeText(text) ?? Promise.resolve() } + const handleFeedback = async () => { + const text = feedbackText.trim() + if (!text || feedbackSending) return + setFeedbackSending(true) + try { + const res = await fetch('/api/feedback', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ content: text }), + }) + const data = await res.json() + if (data.ok) { + setFeedbackText('') + setFeedbackDone(true) + setTimeout(() => { + setFeedbackOpen(false) + setFeedbackDone(false) + }, 800) + } + } catch { + setFeedbackDone(false) + } finally { + setFeedbackSending(false) + } + } + const handleLike = () => { if (liked) return setLiked(true) @@ -127,6 +157,14 @@ export function HeaderPanel() { | 累积 {cumulative} +