1 | <!DOCTYPE html>
|
2 | <html lang="en">
|
3 | <head>
|
4 | <meta charset="UTF-8" />
|
5 | <meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
6 | <meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
7 | <title></title>
|
8 | </head>
|
9 |
|
10 | <style>
|
11 | body {
|
12 | overflow-wrap: break-word;
|
13 | font-size: 18px;
|
14 | }
|
15 | h3 {
|
16 | font-family: system-ui, -apple-system, BlinkMacSystemFont,
|
17 | 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans',
|
18 | 'Helvetica Neue', sans-serif;
|
19 | font-weight: normal;
|
20 | }
|
21 | </style>
|
22 |
|
23 | <body></body>
|
24 | <script type="module">
|
25 | import {
|
26 | createSignal,
|
27 | createResource,
|
28 | onMount,
|
29 | createEffect,
|
30 | } from 'https://esm.sh/solid-js';
|
31 |
|
32 | import html from 'https://esm.sh/solid-js/html';
|
33 | import * as echarts from 'https://cdn.jsdelivr.net/npm/echarts@5.3.3/dist/echarts.esm.min.js';
|
34 | const getFontData = (nameTable) =>
|
35 | Object.fromEntries(
|
36 | Object.entries(nameTable).map(([key, val]) => {
|
37 | return [key, typeof val === 'string' ? val : val.en];
|
38 | })
|
39 | );
|
40 | function _formatBytes(bytes, decimals = 2) {
|
41 | if (bytes === 0) return '0 Bytes';
|
42 | const k = 1024;
|
43 | const dm = decimals < 0 ? 0 : decimals;
|
44 | const sizes = [
|
45 | 'Bytes',
|
46 | 'KB',
|
47 | 'MB',
|
48 | 'GB',
|
49 | 'TB',
|
50 | 'PB',
|
51 | 'EB',
|
52 | 'ZB',
|
53 | 'YB',
|
54 | ];
|
55 | const i = Math.floor(Math.log(bytes) / Math.log(k));
|
56 | return (
|
57 | parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) +
|
58 | ' ' +
|
59 | sizes[i]
|
60 | );
|
61 | }
|
62 | const range = [
|
63 | ['基本汉字', 0x4e00, 0x9fa5],
|
64 | ['基本汉字补充', 0x9fa6, 0x9fff],
|
65 | ['扩展A', 0x3400, 0x4dbf],
|
66 | ['扩展B', 0x20000, 0x2a6df],
|
67 | ['扩展C', 0x2a700, 0x2b738],
|
68 | ['扩展D', 0x2b740, 0x2b81d],
|
69 | ['扩展E', 0x2b820, 0x2cea1],
|
70 | ['扩展F', 0x2ceb0, 0x2ebe0],
|
71 | ['扩展G', 0x30000, 0x3134a],
|
72 | ['康熙部首', 0x2f00, 0x2fd5],
|
73 | ['部首扩展', 0x2e80, 0x2ef3],
|
74 | ['兼容汉字', 0xf900, 0xfad9],
|
75 | ['兼容扩展', 0x2f800, 0x2fa1d],
|
76 | ['PUA(GBK)部件', 0xe815, 0xe86f],
|
77 | ['部件扩展', 0xe400, 0xe5e8],
|
78 | ['PUA增补', 0xe600, 0xe6cf],
|
79 | ['汉字笔画', 0x31c0, 0x31e3],
|
80 | ['汉字结构', 0x2ff0, 0x2ffb],
|
81 | ['汉语注音', 0x3105, 0x312f],
|
82 | ['注音扩展', 0x31a0, 0x31ba],
|
83 | ['〇', 0x3007, 0x3007],
|
84 | ];
|
85 | import { UnicodeRange } from 'https://esm.sh/@japont/unicode-range@1.0.0';
|
86 | const RangeAnalyze = (data) => {
|
87 | const total = data.reduce((col, cur) => {
|
88 | if (cur.chars.startsWith('U+')) {
|
89 | return (
|
90 | col +
|
91 | String.fromCharCode(
|
92 | ...UnicodeRange.parse(cur.chars.split(','))
|
93 | )
|
94 | );
|
95 | }
|
96 | return col + cur.chars;
|
97 | }, '');
|
98 | const result = range.map(([name, min, max]) => {
|
99 | let exist = '';
|
100 | let voids = '';
|
101 | for (let i = min; i <= max; i++) {
|
102 | const char = String.fromCharCode(i);
|
103 | const isExist = total.includes(char);
|
104 | if (isExist) {
|
105 | exist += char;
|
106 | } else {
|
107 | voids += char;
|
108 | }
|
109 | }
|
110 | return [name, exist, voids];
|
111 | });
|
112 | return html`
|
113 | <table style="width:80%;margin:auto;padding:1rem">
|
114 | <thead>
|
115 | <tr>
|
116 | <th>位置</th>
|
117 | <th>存在</th>
|
118 | <th>不存在</th>
|
119 | <th>覆盖率</th>
|
120 | </tr>
|
121 | </thead>
|
122 | ${result.map(([name, exist, voids]) => {
|
123 | const coverage =
|
124 | (exist.length * 100) /
|
125 | (exist.length + voids.length);
|
126 | return html`
|
127 | <tr>
|
128 | <td>${name}</td>
|
129 | <td>${exist.length}</td>
|
130 | <td>${voids.length}</td>
|
131 | <td>${coverage.toFixed(2)}%</td>
|
132 | </tr>
|
133 | `;
|
134 | })}
|
135 | </table>
|
136 | `;
|
137 | };
|
138 |
|
139 | const TimeAnalyze = (record, message) => {
|
140 | record.pop();
|
141 | const total = record.reduce(
|
142 | (col, cur) => col + cur.end - cur.start,
|
143 | 0
|
144 | );
|
145 | let chartDom;
|
146 |
|
147 |
|
148 | onMount(() => {
|
149 |
|
150 | setTimeout(() => {
|
151 | let myChart = echarts.init(chartDom);
|
152 | let option = {
|
153 | tooltip: {
|
154 | trigger: 'axis',
|
155 | axisPointer: {
|
156 | type: 'shadow',
|
157 | },
|
158 | },
|
159 | title: {
|
160 | text: '打包时间分布图',
|
161 | subtext: `时间为 ms; 总时间 ${total} ms;\n${message.fontFamily}`,
|
162 | },
|
163 | legend: {
|
164 | top: '15%',
|
165 | },
|
166 | grid: {
|
167 | left: '10%',
|
168 | right: '10%',
|
169 | bottom: '30%',
|
170 | top: '30%',
|
171 | },
|
172 | xAxis: {
|
173 | type: 'value',
|
174 | },
|
175 | yAxis: {
|
176 | type: 'category',
|
177 | data: ['时间轴'],
|
178 | },
|
179 | series: record.map((i) => {
|
180 | return {
|
181 | name: i.name,
|
182 | type: 'bar',
|
183 | stack: 'total',
|
184 | label: {
|
185 | show: true,
|
186 | },
|
187 | emphasis: {
|
188 | focus: 'series',
|
189 | },
|
190 | data: [
|
191 | parseFloat((i.end - i.start).toFixed(1)),
|
192 | ],
|
193 | };
|
194 | }),
|
195 | };
|
196 |
|
197 | option && myChart.setOption(option);
|
198 | }, 1500);
|
199 | });
|
200 | return html`<div
|
201 | ref=${function (dom) {
|
202 | chartDom = dom;
|
203 | }}
|
204 | style="width: 600px;height:400px;margin:auto"
|
205 | ></div>`;
|
206 | };
|
207 | const DataAnalyze = (data, message) => {
|
208 | const total = data.reduce((col, cur) => col + cur.size, 0);
|
209 |
|
210 | let chartDom;
|
211 | onMount(() => {
|
212 |
|
213 | setTimeout(() => {
|
214 | let myChart = echarts.init(chartDom);
|
215 | let option = {
|
216 | tooltip: {
|
217 | trigger: 'item',
|
218 | formatter(data) {
|
219 | return (
|
220 | `第 ${data.dataIndex + 1} 分包\n` +
|
221 | data.data.name +
|
222 | '\n' +
|
223 | _formatBytes(data.data.value)
|
224 | );
|
225 | },
|
226 | },
|
227 |
|
228 | title: {
|
229 | text: message.fontFamily,
|
230 | subtext: `总共 ${
|
231 | data.length
|
232 | } 分包; 总大小 ${_formatBytes(
|
233 | total
|
234 | )} 点击跳转查看;`,
|
235 | left: 'center',
|
236 | },
|
237 | series: [
|
238 | {
|
239 | name: '分包信息',
|
240 | type: 'pie',
|
241 | radius: ['40%', '70%'],
|
242 | avoidLabelOverlap: false,
|
243 | itemStyle: {
|
244 | borderRadius: 10,
|
245 | borderColor: '#fff',
|
246 | borderWidth: 2,
|
247 | },
|
248 |
|
249 | emphasis: {
|
250 | label: {
|
251 | show: true,
|
252 | fontSize: '18',
|
253 | fontWeight: 'bold',
|
254 | },
|
255 | },
|
256 | labelLine: {
|
257 | show: true,
|
258 | },
|
259 | label: {
|
260 | show: true,
|
261 | minMargin: 5,
|
262 | edgeDistance: 10,
|
263 | lineHeight: 15,
|
264 | formatter(data) {
|
265 | return (
|
266 | (
|
267 | (data.data.value * 100) /
|
268 | total
|
269 | ).toFixed(2) + '%'
|
270 | );
|
271 | },
|
272 | },
|
273 | data: data.map((i) => ({
|
274 | value: i.size,
|
275 | name: i.name.slice(0, 7),
|
276 | hash: i.name,
|
277 | })),
|
278 | },
|
279 | ],
|
280 | };
|
281 |
|
282 | option && myChart.setOption(option);
|
283 | myChart.on('click', (data) => {
|
284 | document
|
285 | .getElementById(data.data.hash)
|
286 | .scrollIntoView();
|
287 | });
|
288 | }, 300);
|
289 | });
|
290 | return html`<div
|
291 | ref=${function (dom) {
|
292 | chartDom = dom;
|
293 | }}
|
294 | style="width: 600px;height:600px;margin:auto"
|
295 | ></div>`;
|
296 | };
|
297 | const CharList = (data) => {
|
298 | return data.map((i) => {
|
299 | const chars = i.chars.startsWith('U+')
|
300 | ? String.fromCharCode(
|
301 | ...UnicodeRange.parse(i.chars.split(','))
|
302 | )
|
303 | : i.chars;
|
304 | return html`<div>
|
305 | <h3 id="${i.name}">
|
306 | 分片名称 ${i.name} | 分片大小 ${_formatBytes(i.size)}
|
307 | </h3>
|
308 | <p>${chars}</p>
|
309 | </div>`;
|
310 | });
|
311 | };
|
312 | const BaseMessage = (message) => {
|
313 | return html`
|
314 | <table style="margin:auto">
|
315 | ${Object.entries(message).map((i) => {
|
316 | return html`
|
317 | <tr>
|
318 | <td>${i[0].en}</td>
|
319 | <td>${i[1].en}</td>
|
320 | </tr>
|
321 | `;
|
322 | })}
|
323 | </table>
|
324 | `;
|
325 | };
|
326 | const App = () => {
|
327 | const [data] = createResource(() =>
|
328 | fetch('./reporter.json')
|
329 | .then((res) => res.json())
|
330 | .then((res) => {
|
331 | res.message =
|
332 | res.message.windows || res.message.macintosh;
|
333 | return res;
|
334 | })
|
335 | );
|
336 | createEffect(() => {
|
337 | if (data()) {
|
338 | console.log(data().message);
|
339 | document.body.style.fontFamily = `"${
|
340 | getFontData(data().message).fontFamily ||
|
341 | getFontData(data().message).preferredFamily
|
342 | }"`;
|
343 |
|
344 | document.querySelector('title').textContent = getFontData(
|
345 | data().message
|
346 | ).fontFamily;
|
347 | const link = document.createElement('link');
|
348 | link.rel = 'stylesheet';
|
349 | link.href =
|
350 | './' + (data().config.cssFileName || 'result') + '.css';
|
351 | document.head.appendChild(link);
|
352 | }
|
353 | });
|
354 | const content = () =>
|
355 | html` <div>
|
356 | ${RangeAnalyze(
|
357 | data().data,
|
358 | getFontData(data().message)
|
359 | )}
|
360 | ${DataAnalyze(data().data, getFontData(data().message))}
|
361 | ${TimeAnalyze(
|
362 | data().record,
|
363 | getFontData(data().message)
|
364 | )}
|
365 | ${BaseMessage(getFontData(data().message))}
|
366 | </div>
|
367 | ${CharList(data().data)}`;
|
368 | return html`<div>
|
369 | ${() => (data.loading ? `<div>加载中</div>` : content())}
|
370 | </div>`;
|
371 | };
|
372 |
|
373 | import { render } from 'https://esm.sh/solid-js/web';
|
374 | render(App, document.body);
|
375 | </script>
|
376 | </html>
|