找到
173
篇与
站长可乐
相关的结果
-
Token中文名定了:词元 AI热潮中,一个概念的地位正在渐渐凸显——Token,它是排行榜上大模型调用量的评估标准,也是大模型厂商销售套餐的计费单位。 日前在中国发展高层论坛2026年年会上,国家数据局局长刘烈宏表示,Token“词元”不仅是智能时代的价值锚点,更是连接技术供给与商业需求的“结算单位”,为商业模式的落地提供了可量化的可能;也从官方角度上为Token给出了翻译:“词元”。 他指出,2024年年初,中国日均Token调用量为1000亿;到2025年底,跃升至100万亿;今年3月,已突破140万亿,两年增长超千倍。今年1月底以来,有的模型企业创下20天收入超越2025年全年总收入的业绩纪录。这组数字背后,是一套以Token计费为基础的新型商业逻辑正在加速演进。 作为大模型处理信息的最小信息单元,Token具有智能时代可计量、可定价、可交易的特征。刘烈宏表示,围绕Token的调用、分发与结算,一套新的价值体系正在加速演进形成,并成为人工智能产业可能变现的重要路径。中国日均Token调用量的大幅增长也表明,随着中国数据要素市场化配置改革的纵深推进,人工智能高质量数据的供给体系正在形成,“数据供给—价值释放”的良性循环已初现端倪。 换句话说,AI时代,Token经济,或者说“词元经济”正在崛起。 黄仁勋在最新的采访中,为Token经济的前景给出了颇为乐观的预期。他认为,计算机现在已经开始转变为可以创收的“工厂”,这些工厂生产出来的商品即为人们愿意消费的Token,对不同受众来说都极具吸引力、极具价值。由此,Token开始出现分层。 “事实证明,‘智能’本身是一种可以规模化的产品。存在非常高端的智能产品,也就是一些用于特定用途的Token,人们愿意为此付费。比如,有人愿意为每一百万个Token支付1000美元,这种情况很快就会出现。这不是‘会不会’的问题,而只是‘什么时候’的问题。” 这种“词元经济”已有端倪。 据日前媒体报道显示,在Meta、OpenAI等科技公司内部,已出现内部员工Token消耗量排行榜。同时,“Token预算”成为程序员的一项职业福利,就像牙科保险或免费午餐一样;而一些人为了让自己的工作尽可能实现自动化,每月甚至会消耗价值数千美元的额度。 此外3月16日,阿里巴巴正式成立Alibaba Token Hub(ATH)事业群,该群以“创造Token、输送Token、应用Token”为核心目标,由阿里巴巴CEO吴泳铭直接负责。券商认为,本次调整是阿里AI战略从模型能力转向Token经济的战略调整,印证AI产业底层商业模式正转向Token化变现周期,或驱动AI投资良性正循环。 开源证券指出,“Token=AI芯片(国产算力+算力租赁)=AIDC”,随着Tokens的持续增长,CDN需求亦或大幅增长,重视三大核心主线:AIDC、算力租赁、CDN。(来源:财联社 编辑:徐宏博) -
张雪峰骤然离世!心源性猝死,这6个信号要警惕 据新华社消息,今晚(3月24日),苏州峰学蔚来教育科技有限公司发布讣告称,张雪峰因突发疾病,经抢救无效不幸去世。 记者了解到,今天中午12点26分,张雪峰在公司跑步后出现不适,被紧急送至医院。遗憾的是,经全力抢救无效于下午3点50分不幸去世。医院诊断,原因为心源性猝死。 mn4qm9vg.png图片 公开资料显示,张雪峰,本名张子彪,男,汉族,1984年5月18日出生于黑龙江省齐齐哈尔市富裕县,祖籍山东临清,毕业于郑州大学。 张雪峰毕业于郑州大学,2016年6月凭借《七分钟解读34所985高校》走红网络。2021年,张雪峰离开了打拼14年的北京,到苏州创立了峰学蔚来。该公司目前拥有升学规划指导、素质能力拓展、图书研发策划、实习就业、研学教育、直播电商等多个业务板块。 作为“网红”名师,张雪峰经常活跃在网络上,分享观点看法。此外,张雪峰还多次因为公司福利待遇登上热搜,诸如公司“上4休3”、春节放假等等。 什么是猝死及心源性猝死? 猝死是指急性症状发生后即刻或24小时内发生的意外死亡,多数学者倾向于将猝死时间限定为发病1小时内。通常发病前很少有明显的症状或前兆。心脏疾病引起的猝死称为心源性猝死,是最见的猝死原因。 中青年心源性猝死有哪些病因? 总体来说,心源性猝死中90%的病因为急性心肌梗死。但对于40岁以下的年轻人而言,心源性猝死的病因组成会有所不同。其中70%为结构性病变,另有30%通常由恶性心律失常引起,称为“心律失常性猝死综合征”(SADS)(图1)。 mn4qmxv5.png图片 图1:年轻人心源性猝死病因 心源性猝死前有哪些表现? 通常心源性猝死前没有任何征兆。但如果出现以下情况,需引起重视,并立即就诊: 1) 曾有不明原因的晕厥:尤其在活动中或应激时出现,往往提示有心脏问题。 2) 胸痛、胸闷、胸部不适:尤其是活动、劳累、用力时出现。 3) 呼吸急促或窒息感:尤其发生在活动、劳累、用力时。 4) 心悸:可能伴随出汗、头晕、恶心等,尤其与活动、劳累、用力等相关。 5) 体检时有心脏杂音。 6) 有遗传性心脏病家族史或猝死家族史。 该怎样预防? ▶ 培养健康的生活方式:营养均衡、适量运动、控制体重、戒烟限酒、减轻压力均有助于预防心脏疾病的发生。切记:避免过度劳累和剧烈运动!尤其对于有心脏疾病、心脏疾病高危因素或心脏疾病家族史的患者,过度劳累或剧烈运动会导致心脏负荷过大,增加心源性猝死的风险。 ▶ 定期体检:可尽早发现心脏疾病或心脏疾病的高危因素,如高血压、高血脂、糖尿病等,从而能够早发现、早治疗。重要提示: 如有心脏病家族史(家族中有人罹患遗传性心肌病(如:肥厚型心肌病等)、离子通道病、心源性或不明原因猝死等),需及早就诊,完善常规心电图、心脏超声、动态心电图等检查,必要时行冠脉影像学、心脏磁共振、电生理检查、基因检测等。 ▶ 药物或器械预防:如有心脏疾病或心脏疾病高危因素,应在医生指导下使用相应药物治疗。 转发提醒! 来源:新华社 上海瑞金医院 -
宝塔上怎么删除几百万条文件? 发现服务器有几百万条数据,怎么删除呢? 用以下代码删除的时候,提示错误: [root@S202601217452 ~]# rm -rf /www/wwwroot/123.com/cache/html/* bash: /usr/bin/rm: Argument list too long这是遇到的 Argument list too long 错误,是因为 rm -rf * 会让系统把所有文件名展开成参数传给 rm,而几百万文件的参数数量远超 Linux 系统的限制。使用 rsync 和 find 方案就能解决这个问题的,下面是最快捷的执行步骤: 方案 1:rsync 极速清空(推荐,百万级文件秒清) 直接复制下面的命令一键执行,无需分步操作: # 创建空目录 + 同步清空目标目录 + 删除临时空目录 mkdir -p /tmp/empty && rsync -a --delete /tmp/empty/ /www/wwwroot/bbs.lightweb.vip/cache/html/ && rm -rf /tmp/empty方案 2:find 逐行删除(稳妥,适合怕误操作的场景) 如果想更直观看到删除过程(或分批删除),用这个命令: # 只删除文件(保留目录结构),避免参数溢出 find /www/wwwroot/bbs.lightweb.vip/cache/html -type f -delete这个命令会逐个遍历文件并删除,不会触发参数过长问题; 如果想看到删除进度,可加 -print 参数: find /www/wwwroot/bbs.lightweb.vip/cache/html -type f -print -delete验证是否删除成功 执行完后,用下面的命令检查: # 统计剩余文件数(正常应该返回 0) ls /www/wwwroot/bbs.lightweb.vip/cache/html | wc -l为什么这两个方案能解决问题? rsync:本质是 “用空目录覆盖目标目录”,不遍历文件名,直接清空,效率最高; find -delete:逐行处理文件,每次只传一个文件路径给 rm,不会触发参数数量限制; 而 rm -rf *:会先把所有文件名拼起来当参数,参数长度超系统上限就报错。 总结 优先用 rsync 命令,一键清空百万级文件无压力,速度最快; find -delete 更稳妥,适合需要观察删除过程的场景; 避免再用 rm -rf * 处理超大量文件,必触发参数过长错误。 -
如何分割WPS中某个项目的分数据? 以门店商品列表为例,我们可以通过筛选自提门店字段的数据,对文档进行分割成门店单数据文件。 步骤 1:打开宏编辑器(正确操作) 打开你的订单表格,按 Alt + F11 打开 VBA 编辑器(WPS/Excel 都支持); 左侧「工程资源管理器」→ 右键你的工作簿名称 → 插入 → 模块; 在模块里粘贴下面的 VBA 代码(已适配路径、门店名称、报错处理): Sub SplitOrderByStore_WPS() ' 声明所有变量(WPS兼容版) Dim ws As Worksheet Dim dict As Object Dim lastRow As Long, storeCol As Long, i As Long, j As Long Dim storeName As String, savePath As String Dim newWB As Workbook, newWS As Worksheet Dim rowNum As Long Dim rowArr As Variant Dim keyArr As Variant ' 存储字典所有key的数组 ' 1. 初始化工作表和字典 Set ws = ActiveSheet Set dict = CreateObject("Scripting.Dictionary") dict.CompareMode = 1 ' 忽略大小写 ' 2. 定位「自提门店」列(严格匹配表头) On Error Resume Next storeCol = ws.rows(1).Find(What:="自提门店", LookIn:=xlValues, LookAt:=xlWhole, MatchCase:=False).Column On Error GoTo 0 If storeCol = 0 Then MsgBox "错误:未找到「自提门店」列,请检查表头(无空格/错别字)!", vbExclamation Exit Sub End If ' 3. 检查数据有效性 lastRow = ws.Cells(ws.rows.Count, storeCol).End(xlUp).Row If lastRow < 2 Then MsgBox "错误:没有可拆分的订单数据(数据需从第2行开始)!", vbExclamation Exit Sub End If ' 4. 存储每个门店对应的行号(用字典分组) For i = 2 To lastRow storeName = Trim(ws.Cells(i, storeCol).Value) ' 过滤空门店名 If storeName <> "" Then If Not dict.Exists(storeName) Then dict(storeName) = "" ' 初始化行号字符串 End If dict(storeName) = dict(storeName) & i & "," ' 拼接行号(用逗号分隔) End If Next i ' 5. 若没有有效门店,直接退出 If dict.Count = 0 Then MsgBox "错误:未识别到任何门店名称!", vbExclamation Exit Sub End If ' 6. 选择文件保存文件夹 With Application.FileDialog(msoFileDialogFolderPicker) .Title = "选择拆分文件的保存文件夹" .AllowMultiSelect = False If .Show <> -1 Then Exit Sub ' 用户取消选择则退出 savePath = .SelectedItems(1) & "\" End With ' 7. 把字典Keys转为数组(核心:用下标遍历替代For Each) keyArr = dict.Keys ' 提取所有门店名到数组 ' 8. 批量导出每个门店的文件(纯下标遍历) Application.ScreenUpdating = False ' 关闭屏幕刷新,提速 For j = 0 To UBound(keyArr) ' 遍历门店数组(下标从0开始) storeName = keyArr(j) ' 创建新工作簿和工作表 Set newWB = Workbooks.Add Set newWS = newWB.Sheets(1) newWS.Name = "订单数据" ' 复制表头到新文件 ws.rows(1).Copy newWS.rows(1) ' 拆分该行门店的所有行号,并用下标遍历 rowNum = 2 ' 新表从第2行开始粘贴数据 rowArr = Split(Left(dict(storeName), Len(dict(storeName)) - 1), ",") ' 去掉最后一个逗号 ' 下标遍历行号数组(替代For Each) For i = 0 To UBound(rowArr) ws.rows(CLng(rowArr(i))).Copy newWS.rows(rowNum) rowNum = rowNum + 1 Next i ' 处理门店名特殊字符(避免保存失败) Dim safeName As String safeName = Replace(Replace(Replace(Replace(storeName, "/", ""), "\", ""), ":", ""), "*", "") safeName = Replace(Replace(Replace(Replace(safeName, "?", ""), """", ""), "<", ""), ">", "") safeName = Replace(safeName, "|", "") ' 保存文件(兼容Excel/WPS格式) newWB.SaveAs Filename:=savePath & safeName & ".xlsx", FileFormat:=xlOpenXMLWorkbook newWB.Close SaveChanges:=False ' 关闭新文件 Next j Application.ScreenUpdating = True ' 恢复屏幕刷新 MsgBox "拆分成功!所有文件已保存到:" & vbCrLf & savePath, vbInformation ' 释放对象,避免内存泄漏 Set ws = Nothing Set dict = Nothing Set newWB = Nothing Set newWS = Nothing End Sub步骤 2:点击编辑器顶部的「运行」按钮(▶️),或按F5; 步骤 3:在弹出的对话框中选择空文件夹(建议新建),点击「确定」; 步骤 4:等待几秒,弹出「拆分成功」提示后,去目标文件夹查看即可。 -
智能排版小工具 mm0nfh3e.png图片 本工具旨在帮助您快速整理老师们提交的德育案例、校刊稿件、教学手册等 Word 文档,自动识别标题层级,并允许您对文字样式进行局部调整,最终导出格式规范的 Word 或 PDF 文件。 ✨ 主要功能: 支持上传 ZIP 压缩包(内含 .docx 文件或嵌套 ZIP),自动解压并提取所有文档。自动识别常见的标题序号(如“一、”“(一)”“1.”“1.1”等)及无序号标题(如“背景:”“总结”),并按首次出现顺序分配层级。在预览区可以直接修改文字内容(点击文字即可编辑)。用鼠标选中文字后,可通过操作台单独修改选中部分的字体、字号、颜色。将当前预览内容(含图片、样式)导出为 .doc 文件,可用 Microsoft Word 打开。通过浏览器打印功能生成 PDF 文件,保留所有排版和图片。 <!DOCTYPE html> <html lang="zh"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>智能校刊排版 · 选中编辑版</title> <style> * { box-sizing: border-box; } body { font-family: 'Segoe UI', Roboto, 'Helvetica Neue', sans-serif; background-color: #f5f3f0; margin: 0; min-height: 100vh; display: flex; align-items: center; justify-content: center; padding: 20px; } .card { max-width: 1300px; width: 100%; background: white; border-radius: 28px; box-shadow: 0 25px 50px -12px rgba(0,0,0,0.3); padding: 35px; } h1 { margin-top: 0; color: #3a2c1b; font-weight: 400; border-left: 6px solid #b78c5a; padding-left: 20px; font-size: 2.2rem; } .sub { color: #6b5a48; margin-bottom: 30px; font-style: italic; } .upload-area { border: 2px dashed #c9b69c; border-radius: 32px; padding: 40px 20px; text-align: center; background: #fefcf9; transition: background 0.2s; cursor: pointer; } .upload-area:hover { background: #f9f3ea; } .upload-icon { font-size: 48px; margin-bottom: 10px; color: #b78c5a; } /* 操作台样式 */ .toolbar { background: #f0e9e0; border-radius: 50px; padding: 15px 25px; margin: 20px 0; display: flex; flex-wrap: wrap; gap: 15px; align-items: center; justify-content: center; } .tool-item { display: flex; align-items: center; gap: 8px; } .tool-item label { font-weight: 500; color: #5e4b36; font-size: 0.9rem; } .tool-item select, .tool-item input[type="number"], .tool-item input[type="color"] { padding: 6px 10px; border: 1px solid #dcd0c0; border-radius: 30px; background: white; font-size: 0.9rem; } .tool-item input[type="number"] { width: 70px; } .style-btn { background: #b78c5a; color: white; border: none; padding: 6px 18px; border-radius: 30px; cursor: pointer; font-weight: 500; font-size: 0.9rem; margin-left: 5px; } .style-btn:hover { background: #a07344; } .btn-group { display: flex; gap: 15px; flex-wrap: wrap; margin: 20px 0 20px; justify-content: center; } button { background: #b78c5a; border: none; color: white; padding: 12px 30px; border-radius: 40px; font-size: 1.1rem; font-weight: 500; cursor: pointer; box-shadow: 0 8px 16px -4px rgba(140,90,40,0.3); transition: all 0.2s; border: 1px solid transparent; min-width: 140px; } button:disabled { opacity: 0.4; pointer-events: none; filter: grayscale(0.5); } button.secondary { background: #ece3d9; color: #5e4b36; box-shadow: none; } button.secondary:hover { background: #dfd2c2; } button:hover { background: #a07344; transform: translateY(-2px); } .preview { background: #fefcf9; border-radius: 20px; border: 1px solid #e7dccc; padding: 25px; margin-top: 20px; } .preview h3 { margin-top: 0; color: #4e3e2b; font-weight: 400; display: flex; align-items: center; gap: 8px; } /* 预览容器 */ .preview-content { background: white; border-radius: 16px; padding: 2cm 2cm 2cm 2cm; max-height: 600px; overflow-y: auto; box-shadow: inset 0 0 0 1px #eee7de; font-family: '宋体', 'SimSun', serif; font-size: 10.5pt; line-height: 1.5; outline: none; } /* 标题样式(基础结构,字体大小固定) */ .preview-content .chapter-title { font-family: '黑体', 'Hei', sans-serif; font-size: 18pt; font-weight: bold; text-align: center; margin: 24pt 0 18pt 0; } .preview-content .h1 { font-family: '黑体', 'Hei', sans-serif; font-size: 16pt; font-weight: bold; margin: 18pt 0 12pt 0; } .preview-content .h2 { font-family: '黑体', 'Hei', sans-serif; font-size: 14pt; font-weight: bold; margin: 12pt 0 6pt 0; } .preview-content .h3 { font-family: '黑体', 'Hei', sans-serif; font-size: 12pt; font-weight: bold; margin: 6pt 0 3pt 0; } .preview-content .h4 { font-family: '黑体', 'Hei', sans-serif; font-size: 11pt; font-weight: bold; margin: 3pt 0 3pt 0; } .preview-content .h5 { font-family: '黑体', 'Hei', sans-serif; font-size: 10.5pt; font-weight: bold; margin: 3pt 0 3pt 0; } .preview-content p { font-family: '宋体', 'SimSun', serif; font-size: 10.5pt; line-height: 1.5; margin: 0 0 0 0; text-indent: 2em; text-align: justify; } .preview-content .image-container { text-align: center; margin: 12pt 0; } .preview-content .image-container img { max-width: 100%; height: auto; display: block; margin: 0 auto; } .status { margin: 15px 0; padding: 12px; border-radius: 40px; background: #f0e9e0; color: #3f352b; text-align: center; font-weight: 500; min-height: 60px; display: flex; align-items: center; justify-content: center; word-break: break-word; } .footer-note { margin-top: 20px; font-size: 0.8rem; color: #9b8c7a; text-align: center; } hr { border: none; border-top: 1px solid #e2d7cb; margin: 20px 0; } @media print { body * { visibility: hidden; } .printable-area, .printable-area * { visibility: visible; } .printable-area { position: absolute; left: 0; top: 0; width: 100%; background: white; padding: 2cm 2cm 2cm 2cm !important; margin: 0; box-shadow: none; max-height: none; overflow: visible; } .toolbar, .btn-group, .upload-area, .preview h3, .status, .footer-note, hr { display: none !important; } } </style> <!-- 核心库 --> <script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js"></script> <script>if (typeof JSZip === 'undefined') document.write('<script src="https://unpkg.com/jszip@3.10.1/dist/jszip.min.js"><\/script>');</script> <script src="https://cdnjs.cloudflare.com/ajax/libs/mammoth/1.6.0/mammoth.browser.min.js"></script> <script>if (typeof mammoth === 'undefined') document.write('<script src="https://unpkg.com/mammoth@1.6.0/mammoth.browser.min.js"><\/script>');</script> <script src="https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/2.0.5/FileSaver.min.js"></script> <script>if (typeof saveAs === 'undefined') document.write('<script src="https://unpkg.com/file-saver@2.0.5/dist/FileSaver.min.js"><\/script>');</script> </head> <body> <div class="card"> <h1>📚 智能校刊排版 · 选中编辑版</h1> <div class="sub">先选中文字,再点击操作台按钮修改选中内容的样式</div> <!-- 上传区域 --> <div class="upload-area" id="dropZone"> <div class="upload-icon">📂</div> <div><strong>点击选择 或 拖拽ZIP到此</strong></div> <div style="font-size:0.9rem; margin-top:8px; color:#7f6e5b;">支持多级标题,图片自动居中</div> <input type="file" id="fileInput" accept=".zip,application/zip" style="display:none;"> </div> <!-- 操作台(选中编辑版) --> <div class="toolbar" id="toolbar"> <div class="tool-item"> <label>字体</label> <select id="fontSelect"> <option value="宋体">宋体</option> <option value="黑体">黑体</option> <option value="楷体">楷体</option> <option value="仿宋">仿宋</option> <option value="微软雅黑">微软雅黑</option> </select> <button class="style-btn" id="applyFontBtn">应用</button> </div> <div class="tool-item"> <label>字号</label> <select id="fontSizeSelect"> <option value="1">8pt (极小)</option> <option value="2">10pt (小)</option> <option value="3" selected>12pt (正常)</option> <option value="4">14pt (中)</option> <option value="5">18pt (大)</option> <option value="6">24pt (很大)</option> <option value="7">36pt (极大)</option> </select> <button class="style-btn" id="applySizeBtn">应用</button> </div> <div class="tool-item"> <label>字色</label> <input type="color" id="colorInput" value="#000000"> <button class="style-btn" id="applyColorBtn">应用</button> </div> </div> <!-- 状态和按钮组 --> <div class="status" id="statusMsg">⏳ 等待上传压缩包</div> <div class="btn-group"> <button id="processBtn" disabled>🧠 智能顺序定级</button> <button id="downloadWordBtn" disabled class="secondary">📄 导出 Word (.doc)</button> <button id="printPdfBtn" disabled class="secondary">🖨️ 打印 / PDF</button> </div> <!-- 预览区域 (可编辑) --> <div class="preview"> <h3>🔍 可编辑预览 (选中文字后修改样式)</h3> <div class="preview-content" id="previewContent" contenteditable="true"> <p style="color:#b7a48e; text-align:center;">上传并解析后点击「智能顺序定级」生成预览</p> </div> </div> <div class="footer-note">※ 选中文字后点击操作台按钮,可单独修改选中部分的样式</div> </div> <script> (function() { // DOM 元素 const dropZone = document.getElementById('dropZone'); const fileInput = document.getElementById('fileInput'); const statusMsg = document.getElementById('statusMsg'); const processBtn = document.getElementById('processBtn'); const downloadWordBtn = document.getElementById('downloadWordBtn'); const printPdfBtn = document.getElementById('printPdfBtn'); const previewContent = document.getElementById('previewContent'); // 操作台按钮 const applyFontBtn = document.getElementById('applyFontBtn'); const applySizeBtn = document.getElementById('applySizeBtn'); const applyColorBtn = document.getElementById('applyColorBtn'); const fontSelect = document.getElementById('fontSelect'); const fontSizeSelect = document.getElementById('fontSizeSelect'); const colorInput = document.getElementById('colorInput'); // 全局数据 let chapters = []; let hasError = false; // ---------- 选中编辑功能 ---------- // 使用 mousedown 而不是 click,防止按钮点击导致选区丢失 applyFontBtn.addEventListener('mousedown', (e) => { e.preventDefault(); // 阻止默认行为,避免失去焦点 const font = fontSelect.value; document.execCommand('fontName', false, font); }); applySizeBtn.addEventListener('mousedown', (e) => { e.preventDefault(); const size = fontSizeSelect.value; // 1-7 的数字 document.execCommand('fontSize', false, size); }); applyColorBtn.addEventListener('mousedown', (e) => { e.preventDefault(); const color = colorInput.value; document.execCommand('foreColor', false, color); }); // ---------- 状态更新 ---------- function setStatus(msg, isError = false, isWarning = false) { statusMsg.innerText = msg; if (isError) { statusMsg.style.background = '#ffded9'; statusMsg.style.color = '#b33c2f'; } else if (isWarning) { statusMsg.style.background = '#fff3d6'; statusMsg.style.color = '#8a6d3b'; } else { statusMsg.style.background = '#f0e9e0'; statusMsg.style.color = '#3f352b'; } } function escapeHtml(unsafe) { return unsafe.replace(/[&<>"']/g, function(m) { if(m === '&') return '&'; if(m === '<') return '<'; if(m === '>') return '>'; if(m === '"') return '"'; return '''; }); } function extractTitleFromFilename(path) { let fileName = path.split('/').pop(); fileName = fileName.replace(/\.docx$/i, '').replace(/[_-]/g, ' '); return fileName || '未命名案例'; } // ---------- 递归解压ZIP ---------- async function extractAllFiles(zipFile) { let fileEntries = []; try { const rootZip = await JSZip.loadAsync(zipFile); const queue = [{ zip: rootZip, pathPrefix: '' }]; while (queue.length > 0) { const { zip, pathPrefix } = queue.shift(); const entries = Object.keys(zip.files); for (const relPath of entries) { const zipEntry = zip.files[relPath]; if (zipEntry.dir) continue; const fullPath = pathPrefix ? `${pathPrefix}/${relPath}` : relPath; try { if (relPath.toLowerCase().endsWith('.zip')) { const data = await zipEntry.async('arraybuffer'); const nestedZip = await JSZip.loadAsync(data); queue.push({ zip: nestedZip, pathPrefix: fullPath.replace(/\.zip$/i, '') }); } else { const data = await zipEntry.async('arraybuffer'); fileEntries.push({ path: fullPath, data: data }); } } catch (innerErr) { console.warn('跳过文件:', fullPath, innerErr); setStatus(`⚠️ 文件 ${fullPath} 解压失败,已跳过`, false, true); } } } } catch (err) { throw new Error('解压失败: ' + err.message); } return fileEntries; } // 提取 HTML(含图片) async function extractHtmlFromDocx(arrayBuffer) { const result = await mammoth.convertToHtml({ arrayBuffer }); return result.value; } // 上传处理 async function handleFileUpload(file) { setStatus('📦 正在解压...'); hasError = false; try { const allFiles = await extractAllFiles(file); const docxFiles = allFiles.filter(f => f.path.toLowerCase().endsWith('.docx')); if (docxFiles.length === 0) { setStatus('❌ 未找到 .docx 文件', true); return false; } setStatus(`🔍 找到 ${docxFiles.length} 个文档,提取中...`); docxFiles.sort((a, b) => a.path.localeCompare(b.path, 'zh')); const chapterPromises = docxFiles.map(async (file) => { try { const html = await extractHtmlFromDocx(file.data); const plainText = html.replace(/<[^>]*>/g, '').replace(/\s+/g, ' ').trim(); const paragraphs = plainText.split(/\n/).filter(p => p.trim().length > 0); return { title: extractTitleFromFilename(file.path), html: html, paragraphs: paragraphs }; } catch (err) { console.error('解析失败:', file.path, err); hasError = true; return { title: extractTitleFromFilename(file.path), html: '<p>文档解析错误</p>', paragraphs: ['[文档解析错误]'] }; } }); chapters = await Promise.all(chapterPromises); if (hasError) { setStatus('✅ 解析完成(部分错误),可点击「智能顺序定级」', false, true); } else { setStatus(`✅ 解析完成,共 ${chapters.length} 章`); } return true; } catch (err) { setStatus('❌ 解压失败:' + err.message, true); return false; } } // ---------- 标题识别 ---------- const headingKeywords = [ '摘要','引言','前言','背景','目的','意义','对象','方法','过程','结果', '分析','讨论','结论','建议','反思','总结','附录','参考文献','注','说明', '关键词','概述','缘由','思路','举措','成效','思考','问题','对策','缘由', '基本情况','主要做法','取得成效','存在不足','下一步打算','案例描述', '案例反思','案例背景','案例过程','案例分析','案例启示','教育建议', '教育对策','教育反思','教育叙事','教育案例','德育案例','管理案例' ]; function detectTitlePattern(text) { const trimmed = text.trim(); if (!trimmed || trimmed.length > 80 || trimmed.length < 2) return null; const patterns = [ { name: 'chinese_num', regex: /^\s*[一二三四五六七八九十百千]+[、..]/ }, { name: 'paren_chinese', regex: /^\s*[((][一二三四五六七八九十百千]+[))]/ }, { name: 'arabic_num', regex: /^\s*\d+[、..]/ }, { name: 'paren_arabic', regex: /^\s*[((]\d+[))]/ }, { name: 'multi_arabic', regex: /^\s*\d+(\.\d+)+[\s\.::]?/ } ]; for (let p of patterns) if (p.regex.test(trimmed)) return p.name; if (/[。!?;]$/.test(trimmed)) return null; if (/[::]/.test(trimmed)) { let parts = trimmed.split(/[::]/); if (parts[0].length < 25) return 'plain_heading'; } if (headingKeywords.includes(trimmed)) return 'plain_heading'; for (let kw of headingKeywords) if (trimmed.startsWith(kw) && trimmed.length < 30) return 'plain_heading'; return null; } function enhanceHeadingDetection(paragraphs) { let candidates = []; paragraphs.forEach((para, idx) => { let pattern = detectTitlePattern(para); if (pattern) candidates.push({ idx, pattern }); }); let order = []; let seen = new Set(); candidates.forEach(c => { if (!seen.has(c.pattern)) { seen.add(c.pattern); order.push(c.pattern); } }); const levelMap = {}; order.forEach((p, i) => { if (i === 0) levelMap[p] = 'h1'; else if (i === 1) levelMap[p] = 'h2'; else if (i === 2) levelMap[p] = 'h3'; else if (i === 3) levelMap[p] = 'h4'; else levelMap[p] = 'h5'; }); let classified = paragraphs.map((text, idx) => { let cand = candidates.find(c => c.idx === idx); return { text, level: cand ? levelMap[cand.pattern] : 'body' }; }); for (let i = 0; i < classified.length; i++) { if (classified[i].level === 'body') { let t = classified[i].text; if (t.length < 40 && !/[。!?;]$/.test(t)) { let prevLevel = i > 0 ? classified[i-1].level : null; let nextLevel = i < classified.length-1 ? classified[i+1].level : null; if (prevLevel && prevLevel.startsWith('h')) { classified[i].level = prevLevel; } else if (nextLevel && nextLevel.startsWith('h')) { classified[i].level = nextLevel; } } } } return classified; } // 构建预览HTML function buildPreviewHtmlFromChapters() { if (!chapters.length) return '<p style="color:#b7a48e;">请先上传并解析文档</p>'; let html = ''; for (const ch of chapters) { if (!ch.classified) continue; html += `<div class="chapter-title">${escapeHtml(ch.title)}</div>`; const tempDiv = document.createElement('div'); tempDiv.innerHTML = ch.html; const children = Array.from(tempDiv.children); if (children.length === ch.classified.length) { for (let i = 0; i < children.length; i++) { const elem = children[i]; const level = ch.classified[i].level; if (level.startsWith('h')) { const wrapper = document.createElement('div'); wrapper.className = level; wrapper.innerHTML = elem.innerHTML; html += wrapper.outerHTML; } else { html += elem.outerHTML; } } } else { let content = ch.html .replace(/<h1>/g, '<div class="h1">').replace(/<\/h1>/g, '</div>') .replace(/<h2>/g, '<div class="h2">').replace(/<\/h2>/g, '</div>') .replace(/<h3>/g, '<div class="h3">').replace(/<\/h3>/g, '</div>') .replace(/<h4>/g, '<div class="h4">').replace(/<\/h4>/g, '</div>') .replace(/<h5>/g, '<div class="h5">').replace(/<\/h5>/g, '</div>') .replace(/<h6>/g, '<div class="h5">').replace(/<\/h6>/g, '</div>') .replace(/<img([^>]+)>/g, '<div class="image-container"><img$1></div>'); html += content; } html += `<div style="page-break-before: always; margin: 0;"></div>`; } return html; } function updatePreview() { let html = buildPreviewHtmlFromChapters(); previewContent.innerHTML = html; } // 下载函数 function downloadBlob(blob, filename) { return new Promise((resolve, reject) => { try { if (typeof window.saveAs === 'function') { window.saveAs(blob, filename); resolve(); } else { const url = window.URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = filename; document.body.appendChild(a); a.click(); setTimeout(() => { document.body.removeChild(a); window.URL.revokeObjectURL(url); resolve(); }, 100); } } catch (err) { reject(err); } }); } // 事件绑定 dropZone.addEventListener('click', () => fileInput.click()); dropZone.addEventListener('dragover', (e) => e.preventDefault()); dropZone.addEventListener('drop', async (e) => { e.preventDefault(); const file = e.dataTransfer.files[0]; if (file && (file.type === 'application/zip' || file.name.endsWith('.zip'))) { const success = await handleFileUpload(file); if (success) { processBtn.disabled = false; downloadWordBtn.disabled = true; printPdfBtn.disabled = true; } } else { setStatus('请上传ZIP压缩包', true); } }); fileInput.addEventListener('change', async (e) => { const file = e.target.files[0]; if (file) { const success = await handleFileUpload(file); if (success) { processBtn.disabled = false; downloadWordBtn.disabled = true; printPdfBtn.disabled = true; } } }); processBtn.addEventListener('click', () => { if (!chapters.length) { setStatus('请先上传有效压缩包', true); return; } try { for (let i = 0; i < chapters.length; i++) { chapters[i].classified = enhanceHeadingDetection(chapters[i].paragraphs); } updatePreview(); downloadWordBtn.disabled = false; printPdfBtn.disabled = false; setStatus('🎯 标题识别完成,可选中文字修改样式'); } catch (err) { setStatus('❌ 处理失败: ' + err.message, true); } }); // 导出 Word downloadWordBtn.addEventListener('click', async () => { if (!chapters.length) { setStatus('没有可导出的内容', true); return; } setStatus('📄 正在准备导出 Word 文件...'); try { const fullHtml = previewContent.innerHTML; const styles = document.querySelector('style').innerHTML; const fullDocument = `<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <style> ${styles} .preview-content { padding: 2cm; max-height: none; overflow: visible; } body { margin: 0; background: white; } </style> </head> <body> <div class="preview-content"> ${fullHtml} </div> </body> </html>`; const blob = new Blob([fullDocument], { type: 'application/msword' }); await downloadBlob(blob, '校刊排版.doc'); setStatus('✅ 文档已导出为 .doc 格式'); } catch (err) { console.error(err); setStatus('❌ 导出失败: ' + err.message, true); } }); // 打印 PDF printPdfBtn.addEventListener('click', () => { if (!chapters.length) { setStatus('没有可打印的内容', true); return; } previewContent.classList.add('printable-area'); setTimeout(() => window.print(), 50); window.addEventListener('afterprint', function onAfterPrint() { previewContent.classList.remove('printable-area'); window.removeEventListener('afterprint', onAfterPrint); }); }); // 初始预览 previewContent.innerHTML = '<p style="color:#b7a48e; text-align:center;">上传并解析后点击「智能顺序定级」预览</p>'; })(); </script> </body> </html>