|
36 | 36 | var out = []; |
37 | 37 | var seen = {}; |
38 | 38 |
|
39 | | - function push(n) { |
| 39 | + function push(n, kind) { |
40 | 40 | n = String(n || '').toLowerCase().trim(); |
41 | 41 | if (!n || seen[n]) { return; } |
42 | 42 | seen[n] = 1; |
43 | | - out.push(n); |
| 43 | + out.push({ t: n, kind: kind || 'token' }); |
44 | 44 | } |
45 | 45 |
|
46 | 46 | String(query).toLowerCase().split(/[\s,,、;;]+/).forEach(function (token) { |
47 | 47 | if (!token) { return; } |
48 | 48 |
|
49 | 49 | if (!CJK.test(token)) { |
50 | 50 | push(token); |
51 | | - token.split(/[^a-z0-9_\-\.\+#]+/).forEach(push); |
| 51 | + token.split(/[^a-z0-9_\-\.\+#]+/).forEach(function (p) { push(p); }); |
52 | 52 | return; |
53 | 53 | } |
54 | 54 |
|
55 | 55 | push(token); |
56 | 56 | token.replace(CJK_RUN, ' ').split(' ').filter(Boolean).forEach(function (run) { |
57 | 57 | if (run.length === 1) { push(run); return; } |
58 | | - for (var i = 0; i + 1 < run.length; i++) { push(run.substr(i, 2)); } |
| 58 | + for (var i = 0; i + 1 < run.length; i++) { push(run.substr(i, 2), 'gram'); } |
59 | 59 | }); |
60 | 60 | }); |
61 | 61 |
|
|
83 | 83 | var content = (doc.content || '').toLowerCase(); |
84 | 84 |
|
85 | 85 | var score = 0; |
86 | | - var matched = 0; |
| 86 | + var wholeHit = false; // 整词/整串命中(英文词、中文完整词) |
| 87 | + var gramTotal = 0; // 二字词总数 |
| 88 | + var gramHit = 0; // 二字词命中数 |
87 | 89 |
|
88 | 90 | needles.forEach(function (n) { |
89 | | - var s = countOccurrences(title, n) * 12 + |
90 | | - countOccurrences(tags, n) * 8 + |
91 | | - countOccurrences(cats, n) * 5 + |
92 | | - countOccurrences(excerpt, n) * 3 + |
93 | | - countOccurrences(content, n); |
94 | | - if (s > 0) { |
95 | | - matched += 1; |
96 | | - score += s; |
| 91 | + var hits = countOccurrences(title, n.t) * 12 + |
| 92 | + countOccurrences(tags, n.t) * 8 + |
| 93 | + countOccurrences(cats, n.t) * 5 + |
| 94 | + countOccurrences(excerpt, n.t) * 3 + |
| 95 | + countOccurrences(content, n.t); |
| 96 | + |
| 97 | + if (n.kind === 'gram') { |
| 98 | + gramTotal += 1; |
| 99 | + } |
| 100 | + |
| 101 | + if (hits > 0) { |
| 102 | + score += hits; |
| 103 | + if (n.kind !== 'gram') { wholeHit = true; } |
| 104 | + else { gramHit += 1; } |
97 | 105 | } |
98 | 106 | }); |
99 | 107 |
|
100 | | - if (!matched) { return null; } |
101 | | - score += matched * 10; |
| 108 | + // 匹配判定: |
| 109 | + // 1) 有整词命中,直接算匹配; |
| 110 | + // 2) 只有二字词时,要求覆盖率 ≥ 75% 且至少命中 3 个不同的二字词。 |
| 111 | + // 只按「过半」会误召回:「完全」「存在」这类常见词在长正文里很容易凑数。 |
| 112 | + if (!wholeHit) { |
| 113 | + if (!gramTotal) { return null; } |
| 114 | + var minGrams = Math.min(3, gramTotal); |
| 115 | + if (gramHit < minGrams || gramHit / gramTotal < 0.75) { return null; } |
| 116 | + } |
| 117 | + |
| 118 | + score += (wholeHit ? 10 : 0) + gramHit * 4; |
102 | 119 | return { doc: doc, score: score }; |
103 | 120 | } |
104 | 121 |
|
105 | 122 | /* ---------- 摘要与高亮 ---------- */ |
| 123 | + function plainNeedles(needles) { |
| 124 | + return needles.map(function (n) { return n.t; }) |
| 125 | + .filter(function (n, i, arr) { return n && arr.indexOf(n) === i; }); |
| 126 | + } |
| 127 | + |
106 | 128 | function highlight(text, needles) { |
107 | 129 | var escaped = escapeHtml(text); |
108 | 130 | var lower = escaped.toLowerCase(); |
109 | 131 | var ranges = []; |
110 | 132 |
|
111 | | - needles.slice().sort(function (a, b) { return b.length - a.length; }).forEach(function (n) { |
| 133 | + plainNeedles(needles).sort(function (a, b) { return b.length - a.length; }).forEach(function (n) { |
112 | 134 | var needle = escapeHtml(n).toLowerCase(); |
113 | 135 | if (!needle) { return; } |
114 | 136 | var pos = lower.indexOf(needle); |
|
152 | 174 |
|
153 | 175 | var best = -1; |
154 | 176 | var bestLen = 0; |
155 | | - needles.forEach(function (n) { |
| 177 | + plainNeedles(needles).forEach(function (n) { |
156 | 178 | var pos = lower.indexOf(n); |
157 | 179 | if (pos !== -1 && (best === -1 || pos < best)) { |
158 | 180 | best = pos; |
|
0 commit comments