From c867f2b648ce3dfb5a679ca86bb38f228317fc3b Mon Sep 17 00:00:00 2001
From: liujie <1793218484@qq.com>
Date: 星期四, 17 九月 2026 14:26:10 +0800
Subject: [PATCH] fix(小程序): 修复协议页无法解析后台 Word 富文本 HTML

---
 mingdaojingjiang_uniapp/mingdaojingjiang_uniapp/pages/agreement-detail/index.vue |    7 +++
 mingdaojingjiang_uniapp/mingdaojingjiang_uniapp/common/html-parser.js            |   81 ++++++++++++++++++++++++++++++++++++++--
 2 files changed, 82 insertions(+), 6 deletions(-)

diff --git a/mingdaojingjiang_uniapp/mingdaojingjiang_uniapp/common/html-parser.js b/mingdaojingjiang_uniapp/mingdaojingjiang_uniapp/common/html-parser.js
index 20a89b2..9372e70 100644
--- a/mingdaojingjiang_uniapp/mingdaojingjiang_uniapp/common/html-parser.js
+++ b/mingdaojingjiang_uniapp/mingdaojingjiang_uniapp/common/html-parser.js
@@ -79,8 +79,10 @@
  *
  */
 // Regular Expressions for parsing tags and attributes
-var startTag = /^<([-A-Za-z0-9_]+)((?:\s+[a-zA-Z_:][-a-zA-Z0-9_:.]*(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)>/;
-var endTag = /^<\/([-A-Za-z0-9_]+)[^>]*>/;
+// fixed by 名导精讲 标签名允许冒号,兼容 Word 导出的 <o:p>、<v:shape> 等命名空间标签,
+// 否则解析循环无法前进会抛出 Parse Error,导致页面把整段 HTML 当纯文本显示。
+var startTag = /^<([-A-Za-z0-9_:]+)((?:\s+[a-zA-Z_:][-a-zA-Z0-9_:.]*(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)>/;
+var endTag = /^<\/([-A-Za-z0-9_:]+)[^>]*>/;
 var attr = /([a-zA-Z_:][-a-zA-Z0-9_:.]*)(?:\s*=\s*(?:(?:"((?:\\.|[^"])*)")|(?:'((?:\\.|[^'])*)')|([^>\s]+)))?/g; // Empty Elements - HTML 5
 
 var empty = makeMap('area,base,basefont,br,col,frame,hr,img,input,link,meta,param,embed,command,keygen,source,track,wbr'); // Block Elements - HTML 5
@@ -121,6 +123,10 @@
           }
 
           html = html.substring(index + 3);
+          chars = false;
+        } else {
+          // 未闭合注释:吞掉剩余内容,避免解析循环无法前进抛出 Parse Error
+          html = '';
           chars = false;
         } // end tag
 
@@ -251,7 +257,72 @@
 }
 
 function removeDOCTYPE(html) {
-  return html.replace(/<\?xml.*\?>\n/, '').replace(/<!doctype.*>\n/, '').replace(/<!DOCTYPE.*>\n/, '');
+	return html
+		.replace(/<\?xml[\s\S]*?\?>/gi, '')
+		.replace(/<!DOCTYPE[^>]*>/gi, '');
+}
+
+// Word 导出 HTML 专用清洗:去掉小程序富文本无法处理的噪声内容。
+function sanitizeWordHtml(html) {
+	return removeDOCTYPE(html)
+		// downlevel-revealed 条件注释(<![if !supportLists]> / <![endif]>)不是标准注释,会让解析循环卡死
+		.replace(/<!\[if[\s\S]*?\]>/gi, '')
+		.replace(/<!\[endif\]>/gi, '')
+		// 标准注释与 downlevel-hidden 条件注释块(内含 <xml><w:WordDocument> 等大段配置)
+		.replace(/<!--[\s\S]*?-->/g, '')
+		.replace(/<head[\s\S]*?<\/head>/gi, '')
+		.replace(/<(style|script|xml)[\s\S]*?<\/\1>/gi, '')
+		// html/body 外壳 rich-text 不支持,可能连同子树一起被丢弃,只保留内部节点
+		.replace(/<\/?html[^>]*>/gi, '')
+		.replace(/<\/?body[^>]*>/gi, '')
+		// 命名空间标签(<o:p>、<v:shape>、<st1:place> 等)只删标签本身,保留其中的文本
+		.replace(/<\/?[A-Za-z][\w.-]*:[\w.-]*(?:\s[^>]*)?\/?>/g, '');
+}
+
+var namedEntities = {
+	nbsp: ' ',
+	ensp: ' ',
+	emsp: ' ',
+	quot: '"',
+	apos: "'",
+	amp: '&',
+	lt: '<',
+	gt: '>',
+	ldquo: '\u201c',
+	rdquo: '\u201d',
+	lsquo: '\u2018',
+	rsquo: '\u2019',
+	mdash: '\u2014',
+	ndash: '\u2013',
+	hellip: '\u2026',
+	middot: '\u00b7',
+	bull: '\u2022',
+	dagger: '\u2020',
+	permil: '\u2030',
+	lsaquo: '\u2039',
+	rsaquo: '\u203a',
+	times: '\u00d7',
+	divide: '\u00f7',
+	copy: '\u00a9',
+	reg: '\u00ae',
+	trade: '\u2122',
+	deg: '\u00b0',
+	minus: '\u2212',
+	szlig: '\u00df'
+};
+
+// rich-text 的文本节点不会解码实体,Word 正文里的 &ldquo; 等需要在这里转成字符。
+function decodeEntities(text) {
+	if (text.indexOf('&') < 0) return text;
+	return text.replace(/&(#[xX]?[0-9a-fA-F]+|[a-zA-Z][a-zA-Z0-9]*);/g, function (all, name) {
+		if (name[0] === '#') {
+			var code = name[1] === 'x' || name[1] === 'X' ? parseInt(name.slice(2), 16) : parseInt(name.slice(1), 10);
+			return isNaN(code) || code < 0 || code > 0x10ffff ? all : String.fromCodePoint(code);
+		}
+		// amp 最后解,避免 &amp;lt; 被二次解码
+		if (name === 'amp') return '&';
+		return Object.prototype.hasOwnProperty.call(namedEntities, name) ? namedEntities[name] : all;
+	});
 }
 
 function parseAttrs(attrs) {
@@ -270,7 +341,7 @@
 }
 
 function parseHtml(html) {
-  html = removeDOCTYPE(html);
+	html = sanitizeWordHtml(html);
   var stacks = [];
   var results = {
     node: 'root',
@@ -317,7 +388,7 @@
     chars: function chars(text) {
       var node = {
         type: 'text',
-        text: text
+        text: decodeEntities(text)
       };
 
       if (stacks.length === 0) {
diff --git a/mingdaojingjiang_uniapp/mingdaojingjiang_uniapp/pages/agreement-detail/index.vue b/mingdaojingjiang_uniapp/mingdaojingjiang_uniapp/pages/agreement-detail/index.vue
index 86013cc..b8ac51c 100644
--- a/mingdaojingjiang_uniapp/mingdaojingjiang_uniapp/pages/agreement-detail/index.vue
+++ b/mingdaojingjiang_uniapp/mingdaojingjiang_uniapp/pages/agreement-detail/index.vue
@@ -111,7 +111,12 @@
   try {
     return htmlParser(html)
   } catch (error) {
-    return [{ type: 'text', text: content || '' }]
+    // 兜底只保留可读文本,避免把 HTML 源码直接展示给用户
+    const plainText = (content || '')
+      .replace(/<style[\s\S]*?<\/style>/gi, '')
+      .replace(/<[^>]+>/g, '')
+      .replace(/&nbsp;/g, ' ')
+    return [{ type: 'text', text: plainText }]
   }
 })
 

--
Gitblit v1.7.1