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