From 311f772ca9d032580b25d96792564ae9f1689d51 Mon Sep 17 00:00:00 2001 From: Hermes CI Fix Date: Mon, 13 Jul 2026 15:04:08 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20Excel=E5=AF=BC=E5=85=A5=E6=94=AF?= =?UTF-8?q?=E6=8C=81=E5=A4=9A=E6=96=87=E4=BB=B6=E9=80=89=E6=8B=A9(Ctrl/Shi?= =?UTF-8?q?ft=E5=A4=9A=E9=80=89)+=E9=80=90=E6=96=87=E4=BB=B6=E7=BB=93?= =?UTF-8?q?=E6=9E=9C=E5=B1=95=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../app/api/__pycache__/data.cpython-312.pyc | Bin 7263 -> 8165 bytes frontend/src/views/DataManage.vue | 46 +++++++++++++----- 2 files changed, 35 insertions(+), 11 deletions(-) diff --git a/backend/app/api/__pycache__/data.cpython-312.pyc b/backend/app/api/__pycache__/data.cpython-312.pyc index b531ac6d529471e8e86151bc6a29fcb13f83fd5b..a167b66d4b8a3295ac05497858e8dfafb047ec34 100644 GIT binary patch delta 2675 zcma)7eN0=|6~E8V&(Gf<7-RDhFiAS%hLDhiCC#!x0t*A$G#kl?maxq6dyr?qCiguD zik@fMG{V-1fW5W23aDn=ABjR5GPD(Ck&>xo4^A?DMt zi8j9M{~)%DW<>9prqpy0`}-mhtW(p-OJN^q`*I}OM2ql(YRcEb6o@qAoGrYe%h~dz zqMmULp(_{FkAAKNXn}9wvx*UbDH8Q41NSr;wTlMWD%u&3U|Xj)s1(Sjc$#ENg5Oka zc_|vuWhgBHsxRI&Wik6kOiXD{6M6wIv{Gl;#tuX1wV?$!O_Mnq(Js=Wena2#DQ!F0 z2gWs{KM80E^em#(0JupG5Wb4}DY~ABLHx8LTM&F}Fu?Y%ygBmtH@|+oaA)=Uoxk55 zUHRqA%2Zw0q^%1hA?RZNQ+r@=AfeVM8no%Ick46S?6V}TSMDX2Oot=HE zaJ$g}#UAjk-o3LtH?e&0^771$<(rc$@BAv___}IDw5xaDTur_D$?#|dvwkvsWhLZ&{h1dGhZ3lY1iv zj9(DOr?@#qFc@oUYM$InqLLJMFN#p1z8se_w{jttPO^_kgouuZUtZocAx{{kidonSr6F@5e~9UZY`om zrhEFivwi-4xL08cgd-sV;w_X(jv16m7#@Jqh|%tr$sP_Z+Q)Z8e6)N40*FAGPGQNff zzNSTAQ^t4nBWrW&aQwp9P+}-q`}W?q_Ac5g($?nTW(3l1)8UxaKHQuwEShYaXp>%g z28t+WLLVpN?Mu$0aei`OVjwwmZFk09lX32R;H+D8)@7Xg;`${=;duLG$3#c+R0?KX z&u1LlA2@0k9km(9p13Y+cTUn1bkdr!SH-En+T3qloGrNJzV4oL&6a1)&aq>OW6AcE zGxeRBm@7r6s}7}0>pwI%WX*+-tgf-1L{D;C#=0d=erzlFs=MO7f5Ee0ykDIzefdN4 zaU7#Jj_DG*(ZQ^ z-$L{K#{1u0crmlPWzp8MsBejrOD1c4;Kv7(oBwDk%bIQDr+?v0b^N^S+PRsEw6}K7 znMNCDu8TKj-6g4$b0>e#|6$*I;-cw9T6>~y?JNOo-hVzT0=91xpY!+vTqB_@eh zpEh{}tG}x*Z>`q8XKePO!TZ%zYwdi@e3&rRBVULw2RtZ!VCkR&NNtj;tO=rX{lIiC8*mua#XZxDwhv81vUw-KnEDZV7Kmf-?|avU<*9}!SPLaj+DG=?29=XC&w zv5pV!3;jXVMfT4SWkr5|Z={J#O{ CYNn9@ delta 1831 zcmaJ?TWl0n7(TPJv$xrqy|dlgE-h7I?F9v)HKHJ`wgpiXYzcy}bf)cYx4X-kS#H@) zB}mws8rqzYmH=9fCMB%`HZeXJ#rUWXG@HnBLhyw);cWxHs4t#>CrgQze!{@3J%X6nh?g&m(k3CU^ML0eJu^+-F>l*F}hi zk(bFaa|fyS`^me0heHn-W`xMKDnB`Bvzg){@|L@r+!5^D6Az_YklYGVgld#CE0&xv zD#urXv8DsB}>5Z zaIGV2!@;Z_2Xvl%EYu8UAG$C%HlSD(G2tEd6b@KMZ)l~nP@N9~5372{2h zvD${#kUN&uBhIW-GYE?7gv5#~j4hdwnJst$%3jAI#hn!{AjNZuXIZ@$O}kJl8t2A-`jS7&MSSxe z_RZX;drp=WL-lbD1B!4h$YBN%j!N5ic6X@#v1Ck-rIJ)okM*euYE7$2C6*i@ZQ|DF z+0JP3%6E&WzgWtTFMjw*T?Q^*-O{B`m(E>X`sk~OiCTwMO^XhwR5%pX`vxPm~L<|X^v^hD0M{B>6YP?q9!!FhApPT;f#upQ%ip=p{v*+CiKU!reinwO)-TE{fSgm zr+idVsHHD8JRH*_F7k^rEJNj~(3i?2b;=zeZfQl8k(M7y$Ea!O2yS2znu`H>Noo+^ zW`YMSV;mWhdWbA<8S(lj8z&k`^L-Q$e4~~;mtTL|w_;qIJUVf7>cq^tLa?RaTbsAt z_V~wlP4-UoPVJe-g}^ffPh;Lvbo(Z)6V@rY;9i{t(ry%di^S4}Hw(PDH zDEd}RZk*Uy^o4GR!c&y%>6WIx{^mv-d`@?fQlVvHOO(yVTAhSKVJ46!?_v@2uq}xz1|y zR43nA`?dQOPHblm40Xb-g+3z)kAx*1R_R{OE44Pq~t%6Z-asSco*LBxz$pq|$H89a`Jc=zcnE?Z`PyZElpj)$<5kS4LE5_Vz)wPCsseA!(@>XbWEk`$11hnO zP#?$qS?0|^<1Xf>@16hLXuOE{|3~=;Z)!2Jmk$=<4c4rlVWXfL`$+&zgQLb246u*< zP;=3<9#r^zQFB~NC2<=`)SesJ3Rbv2#yf=yQ43CGSPN5gI>nk7It_0DfXhS8N~}*W zzc0$CGpv_mN!=)i4l%IbWrQ;zL#6(-ZDJpZOhSDbpJpojDYOBGC7$EBTZp_Lj@Pf^ gwsBK?O9eB@>uRT|`{@s~J-M4UQ|ZIRF3v diff --git a/frontend/src/views/DataManage.vue b/frontend/src/views/DataManage.vue index 73528e4f..5e194908 100644 --- a/frontend/src/views/DataManage.vue +++ b/frontend/src/views/DataManage.vue @@ -6,12 +6,15 @@ - +
拖拽或点击上传Excel文件
-
支持 .xlsx .xls,需包含 kpi_code, period, actual_value 三列
+
支持 .xlsx .xls,可一次选择多个文件(按住Ctrl/Shift多选)
- 导入数据 +
+ {{ f.name }} +
+ 导入 {{ files.length }} 个文件
@@ -94,18 +97,39 @@ import MyDialog from '../components/MyDialog.vue' const activeTab = ref('import') // === Excel导入 === -const file = ref(null) +const files = ref([]) const uploading = ref(false) const result = ref('') -function handleFile(f: any) { file.value = f.raw } + +function handleFile(f: any) { + // el-upload multiple 模式下每次 onChange 传单个文件 + if (f.raw && !files.value.some(ex => ex.name === f.raw.name && ex.size === f.raw.size)) { + files.value.push(f.raw) + } +} +function handleRemove(f: any) { + files.value = files.value.filter(ex => !(ex.name === f.name && ex.size === f.size)) +} +function removeFile(i: number) { + files.value.splice(i, 1) +} async function doImport() { - if (!file.value) return + if (files.value.length === 0) return uploading.value = true - try { - const r: any = await dataApi.importExcel(file.value) - result.value = r.message || '导入成功' - file.value = null - } catch (e) { ElMessage.error('导入失败') } + result.value = '' + const msgs: string[] = [] + for (const f of files.value) { + try { + const r: any = await dataApi.importExcel(f) + const skipped = r.skipped || 0 + const total = r.total || 0 + msgs.push(`📄 ${f.name}: ${total}条导入${skipped > 0 ? `, ${skipped}条跳过` : ''}`) + } catch (e: any) { + msgs.push(`❌ ${f.name}: ${e?.response?.data?.detail || '导入失败'}`) + } + } + files.value = [] + result.value = msgs.join('\n') uploading.value = false }