meminsight-test-demo 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/README.md +36 -0
- package/docs/Build.md +68 -0
- package/docs/Interface.md +35 -0
- package/docs/UserGuide.md +332 -0
- package/docs/resources/analyzer.png +0 -0
- package/docs/resources/cfg_filter_filetype_json.png +0 -0
- package/docs/resources/filetype_txt.png +0 -0
- package/docs/resources/framework.png +0 -0
- package/docs/resources/output.png +0 -0
- package/docs/resources/process.png +0 -0
- package/package.json +54 -0
- package/packages/cfg/ArkStatCfg.json +33 -0
- package/packages/core/jest.config.js +8 -0
- package/packages/core/package.json +61 -0
- package/packages/core/src/Index.ts +53 -0
- package/packages/core/src/analyzer/AnalysisInfo.ts +298 -0
- package/packages/core/src/analyzer/ArkAnalyzer.ts +42 -0
- package/packages/core/src/analyzer/ArkCmpCfg.ts +22 -0
- package/packages/core/src/analyzer/ArkCompareAnalyzer.ts +173 -0
- package/packages/core/src/analyzer/ArkLeakAnalyzer.ts +196 -0
- package/packages/core/src/analyzer/ArkSerializer.ts +163 -0
- package/packages/core/src/analyzer/ArkStatAnalyzer.ts +191 -0
- package/packages/core/src/analyzer/ArkStatCfg.ts +77 -0
- package/packages/core/src/analyzer/ArkTracePath.ts +269 -0
- package/packages/core/src/analyzer/ArkTracer.ts +42 -0
- package/packages/core/src/analyzer/ArkXAnalyzer.ts +631 -0
- package/packages/core/src/analyzer/IAnalyzer.ts +27 -0
- package/packages/core/src/file/FileReader.ts +82 -0
- package/packages/core/src/file/FileService.ts +50 -0
- package/packages/core/src/file/FileWriter.ts +148 -0
- package/packages/core/src/report/Reporter.ts +81 -0
- package/packages/core/src/report/templates/template.ejs +101 -0
- package/packages/core/src/report/templates/template.ts +103 -0
- package/packages/core/src/shell/DeviceShell.ts +179 -0
- package/packages/core/src/shell/Shell.ts +99 -0
- package/packages/core/src/types/Constants.ts +16 -0
- package/packages/core/src/types/LeakTypes.ts +21 -0
- package/packages/core/src/types/OhosTypes.ts +115 -0
- package/packages/core/src/utils/Common.ts +37 -0
- package/packages/core/src/utils/Finder.ts +390 -0
- package/packages/core/src/utils/Loader.ts +53 -0
- package/packages/core/src/utils/Log.ts +252 -0
- package/packages/core/src/utils/Output.ts +271 -0
- package/packages/core/tsconfig.json +10 -0
- package/packages/exampletools/package.json +52 -0
- package/packages/exampletools/src/MemTest.ts +64 -0
- package/packages/exampletools/tsconfig.json +15 -0
- package/packages/meminsight/jest.config.js +8 -0
- package/packages/meminsight/package.json +52 -0
- package/packages/meminsight/src/Index.ts +4 -0
- package/packages/meminsight/src/Version.ts +7 -0
- package/packages/meminsight/src/process/ArkCompareProc.ts +160 -0
- package/packages/meminsight/src/process/ArkGCProc.ts +61 -0
- package/packages/meminsight/src/process/ArkLeakProc.ts +47 -0
- package/packages/meminsight/src/process/ArkStatProc.ts +320 -0
- package/packages/meminsight/src/process/ArkXProc.ts +73 -0
- package/packages/meminsight/src/process/HMemXProc.ts +50 -0
- package/packages/meminsight/src/process/IProcess.ts +12 -0
- package/packages/meminsight/tsconfig.json +15 -0
- package/packages/stack/README.md +31 -0
- package/packages/stack/libs/hstack_lib-1.0.0.tgz +0 -0
- package/packages/stack/libs/hstack_lib-1.0.1.tgz +0 -0
- package/packages/stack/libs/hstack_lib-1.0.4.tgz +0 -0
- package/packages/stack/libs/lib_list.json +34 -0
- package/packages/stack/package.json +27 -0
- package/packages/stack/src/Index.js +29 -0
- package/packages/stack/src/StackTracer.js +53 -0
- package/packages/templates/ArkLeaks.template +9 -0
- package/packages/templates/ArkNodes.template +9 -0
- package/packages/templates/ArkPaths.template +9 -0
- package/test/scripts/merge.py +145 -0
- package/test/scripts/stat.py +175 -0
- package/test/test_ark_stat_proc.sh +14 -0
- package/tsconfig.base.json +38 -0
- package/tsconfig.json +8 -0
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import * as fse from 'fs-extra';
|
|
2
|
+
import { Log, ILoggable } from '../utils/Log';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* interface of reader
|
|
6
|
+
*/
|
|
7
|
+
export interface IReader {
|
|
8
|
+
getType(): string;
|
|
9
|
+
readFromFile<T>(filepath: string, encoding: BufferEncoding): Promise<T | undefined>;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* base reader
|
|
14
|
+
*/
|
|
15
|
+
export abstract class BaseReader implements ILoggable, IReader {
|
|
16
|
+
public readonly DOMAIN: string;
|
|
17
|
+
public readonly TAG: string;
|
|
18
|
+
protected type: string;
|
|
19
|
+
/**
|
|
20
|
+
* get type
|
|
21
|
+
* @returns type
|
|
22
|
+
*/
|
|
23
|
+
public getType(): string {
|
|
24
|
+
return this.type;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* write to file
|
|
29
|
+
* @param file filepath
|
|
30
|
+
* @param data data
|
|
31
|
+
* @returns T | undefined
|
|
32
|
+
*/
|
|
33
|
+
public async readFromFile<T>(
|
|
34
|
+
filepath: string,
|
|
35
|
+
encoding: BufferEncoding = 'utf-8'): Promise<T | undefined> {
|
|
36
|
+
return undefined;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* JsonReader
|
|
42
|
+
*/
|
|
43
|
+
export class JsonReader extends BaseReader {
|
|
44
|
+
public readonly DOMAIN: string;
|
|
45
|
+
public readonly TAG: string;
|
|
46
|
+
protected type: string;
|
|
47
|
+
private static _instance: JsonReader;
|
|
48
|
+
|
|
49
|
+
constructor() {
|
|
50
|
+
super()
|
|
51
|
+
this.DOMAIN = 'meminsight';
|
|
52
|
+
this.TAG = JsonReader.name;
|
|
53
|
+
this.type = JsonReader.name;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* JsonReader instance
|
|
58
|
+
* @returns instance
|
|
59
|
+
*/
|
|
60
|
+
public static instance(): JsonReader {
|
|
61
|
+
if (!JsonReader._instance) {
|
|
62
|
+
JsonReader._instance = new JsonReader();
|
|
63
|
+
}
|
|
64
|
+
return JsonReader._instance;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* read json file
|
|
69
|
+
* @param filepath json file path
|
|
70
|
+
* @param encode encoding, default is utf-
|
|
71
|
+
* @returns T | undefined
|
|
72
|
+
*/
|
|
73
|
+
public async readFromFile<T>(filepath: string,
|
|
74
|
+
encoding: BufferEncoding = 'utf-8') : Promise<T | undefined> {
|
|
75
|
+
try {
|
|
76
|
+
return fse.readJSONSync(filepath, encoding) as T;
|
|
77
|
+
} catch (error) {
|
|
78
|
+
Log.errorX(this, `Failed to read json from file, Status Code: ${error}`)
|
|
79
|
+
return undefined;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { createWriteStream } from 'fs';
|
|
2
|
+
import axios from 'axios';
|
|
3
|
+
import { Log, ILoggable } from '../utils/Log';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* File Service
|
|
7
|
+
*/
|
|
8
|
+
export class FileService implements ILoggable {
|
|
9
|
+
public readonly DOMAIN = 'meminsight';
|
|
10
|
+
public readonly TAG = FileService.name;
|
|
11
|
+
private static _instance: FileService;
|
|
12
|
+
private constructor() {}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* FileService instance
|
|
16
|
+
* @returns FileService instance
|
|
17
|
+
*/
|
|
18
|
+
public static instance(): FileService {
|
|
19
|
+
if (!FileService._instance) {
|
|
20
|
+
FileService._instance = new FileService();
|
|
21
|
+
}
|
|
22
|
+
return FileService._instance;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
*
|
|
27
|
+
* @param url file url
|
|
28
|
+
* @param filepath filepath
|
|
29
|
+
* @returns
|
|
30
|
+
*/
|
|
31
|
+
public async download(url: string, filepath: string): Promise<void> {
|
|
32
|
+
const response = await axios({
|
|
33
|
+
url,
|
|
34
|
+
method: 'GET',
|
|
35
|
+
responseType: 'stream'
|
|
36
|
+
});
|
|
37
|
+
const fileWriter = createWriteStream(filepath);
|
|
38
|
+
response.data.pipe(fileWriter);
|
|
39
|
+
return new Promise((resolve, reject) => {
|
|
40
|
+
fileWriter.on('finish', () => {
|
|
41
|
+
Log.infoX(this, `File ${url} downloaded to ${filepath} successfully`);
|
|
42
|
+
resolve();
|
|
43
|
+
});
|
|
44
|
+
fileWriter.on('error', (err) => {
|
|
45
|
+
Log.errorX(this, `Failed to download file ${url}, Status code: ${err}`);
|
|
46
|
+
reject(err);
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
|
|
2
|
+
import * as fse from 'fs-extra';
|
|
3
|
+
import { ILoggable } from '../utils/Log';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* writer interface
|
|
7
|
+
*/
|
|
8
|
+
export interface IWriter {
|
|
9
|
+
/**
|
|
10
|
+
* get type of writer
|
|
11
|
+
*/
|
|
12
|
+
getType(): string;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* write to file
|
|
16
|
+
* @param file filepath
|
|
17
|
+
* @param data data
|
|
18
|
+
* @param encode encode, default is utf-8
|
|
19
|
+
*/
|
|
20
|
+
writeToFile(filepath: string, data: any, encode: BufferEncoding): Promise<void>;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* base writer
|
|
25
|
+
*/
|
|
26
|
+
export class BaseWriter implements ILoggable, IWriter {
|
|
27
|
+
public readonly DOMAIN: string;
|
|
28
|
+
public readonly TAG: string;
|
|
29
|
+
protected type: string;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* get type
|
|
33
|
+
* @returns type
|
|
34
|
+
*/
|
|
35
|
+
public getType(): string {
|
|
36
|
+
return this.type;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* write to file
|
|
41
|
+
* @param file filepath
|
|
42
|
+
* @param data data
|
|
43
|
+
* @param encoding encoding, default is utf-8
|
|
44
|
+
*/
|
|
45
|
+
public async writeToFile(filepath: string, data: any, encoding: BufferEncoding): Promise<void> {
|
|
46
|
+
const buf = Buffer.isBuffer(data) ? data : Buffer.from(data, encoding);
|
|
47
|
+
fse.outputFileSync(filepath, buf);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* text writer
|
|
53
|
+
*/
|
|
54
|
+
export class TextWriter extends BaseWriter {
|
|
55
|
+
public readonly DOMAIN: string;
|
|
56
|
+
public readonly TAG: string;
|
|
57
|
+
protected type: string;
|
|
58
|
+
protected static _instance: IWriter;
|
|
59
|
+
|
|
60
|
+
public static instance(): IWriter {
|
|
61
|
+
if (this._instance == null) {
|
|
62
|
+
this._instance = new TextWriter();
|
|
63
|
+
}
|
|
64
|
+
return this._instance;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
constructor() {
|
|
68
|
+
super();
|
|
69
|
+
this.DOMAIN = 'meminsight';
|
|
70
|
+
this.TAG = TextWriter.name;
|
|
71
|
+
this.type = TextWriter.name;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
public async writeToFile(filepath: string, data: any, encoding: BufferEncoding): Promise<void> {
|
|
75
|
+
super.writeToFile(filepath, data, encoding);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* json writer
|
|
81
|
+
*/
|
|
82
|
+
export class JsonWriter extends BaseWriter {
|
|
83
|
+
public readonly DOMAIN: string;
|
|
84
|
+
public readonly TAG: string;
|
|
85
|
+
protected type: string;
|
|
86
|
+
protected static _instance: IWriter;
|
|
87
|
+
|
|
88
|
+
public static instance(): IWriter {
|
|
89
|
+
if (this._instance == null) {
|
|
90
|
+
this._instance = new JsonWriter();
|
|
91
|
+
}
|
|
92
|
+
return this._instance;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
constructor() {
|
|
96
|
+
super();
|
|
97
|
+
this.DOMAIN = 'meminsight';
|
|
98
|
+
this.TAG = JsonWriter.name;
|
|
99
|
+
this.type = JsonWriter.name;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
public async writeToFile(filepath: string, data: any, encoding: BufferEncoding): Promise<void> {
|
|
103
|
+
fse.outputJSONSync(filepath, data, { encoding: encoding, spaces: 4 });
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* html writer
|
|
109
|
+
*/
|
|
110
|
+
export class HtmlWriter extends BaseWriter {
|
|
111
|
+
public readonly DOMAIN: string;
|
|
112
|
+
public readonly TAG: string;
|
|
113
|
+
protected type: string;
|
|
114
|
+
protected static _instance: IWriter;
|
|
115
|
+
|
|
116
|
+
public static instance(): IWriter {
|
|
117
|
+
if (this._instance == null) {
|
|
118
|
+
this._instance = new HtmlWriter();
|
|
119
|
+
}
|
|
120
|
+
return this._instance;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
constructor() {
|
|
124
|
+
super();
|
|
125
|
+
this.DOMAIN = 'meminsight';
|
|
126
|
+
this.TAG = HtmlWriter.name;
|
|
127
|
+
this.type = HtmlWriter.name;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
public async writeToFile(filepath: string, data: any, encoding: BufferEncoding): Promise<void> {
|
|
131
|
+
super.writeToFile(filepath, data, encoding);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* 从template读取数据并将data插入模板数据中
|
|
136
|
+
* @param template 模板路径
|
|
137
|
+
* @param data 待插入数据
|
|
138
|
+
* @param delimiter 分隔符
|
|
139
|
+
* @return 插入data后的模板数据
|
|
140
|
+
*/
|
|
141
|
+
public getDataByTemplate(template: string, data: string, delimiter: string): string {
|
|
142
|
+
const templateStrs = fse.readFileSync(template, 'utf-8').split(delimiter);
|
|
143
|
+
if (templateStrs.length !== 2) {
|
|
144
|
+
return '';
|
|
145
|
+
}
|
|
146
|
+
return templateStrs[0] + delimiter + data + templateStrs[1];
|
|
147
|
+
}
|
|
148
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import ejs from 'ejs';
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { formatDate } from '../utils/Common'
|
|
5
|
+
import { TEMPLATE_CTX } from './templates/template';
|
|
6
|
+
|
|
7
|
+
export interface ReportConfig {
|
|
8
|
+
fileName: string;
|
|
9
|
+
fileTitle: string;
|
|
10
|
+
createTime: string;
|
|
11
|
+
data: ReportData[];
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface ReportTable {
|
|
15
|
+
[key: string]: any;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface ReportData {
|
|
19
|
+
feature: string;
|
|
20
|
+
messages: string[];
|
|
21
|
+
columns: Map<string, string>;
|
|
22
|
+
data: ReportTable;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export class ReporterHTML {
|
|
26
|
+
/**
|
|
27
|
+
* 生成HTML
|
|
28
|
+
*/
|
|
29
|
+
private static generate(config: ReportConfig): string {
|
|
30
|
+
const templateData = {
|
|
31
|
+
title: config.fileTitle,
|
|
32
|
+
time: config.createTime,
|
|
33
|
+
data: config.data
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
return ejs.render(TEMPLATE_CTX, templateData);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* 生成并保存HTML
|
|
41
|
+
*/
|
|
42
|
+
private static generateAndSave(config: ReportConfig, outputDir?: string): string {
|
|
43
|
+
const html = this.generate(config);
|
|
44
|
+
|
|
45
|
+
// 确定输出目录
|
|
46
|
+
const dir = outputDir || path.join(process.cwd(), 'output');
|
|
47
|
+
if (!fs.existsSync(dir)) {
|
|
48
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// 确定文件名
|
|
52
|
+
const fileName = config.fileName || `${config.fileTitle.replace(/\s+/g, '-')}.html`;
|
|
53
|
+
const filePath = path.join(dir, fileName);
|
|
54
|
+
|
|
55
|
+
// 保存文件
|
|
56
|
+
fs.writeFileSync(filePath, html, 'utf-8');
|
|
57
|
+
|
|
58
|
+
return filePath;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
public static generateByTemplate(datas: ReportData[], fileName?: string) {
|
|
62
|
+
let generateTime = formatDate(new Date());
|
|
63
|
+
let fileConfig: ReportConfig = {
|
|
64
|
+
fileName: `${fileName ? fileName.split('.')[0] : 'report'}-${generateTime}.html`,
|
|
65
|
+
fileTitle: `${fileName || ''} 内存分析报告`,
|
|
66
|
+
createTime: generateTime,
|
|
67
|
+
data: datas
|
|
68
|
+
};
|
|
69
|
+
try {
|
|
70
|
+
const filePath = ReporterHTML.generateAndSave(fileConfig);
|
|
71
|
+
console.log('🎉 报告生成成功!');
|
|
72
|
+
console.log(`📁 文件: ${filePath}`);
|
|
73
|
+
console.log('🔗 用浏览器打开查看结果');
|
|
74
|
+
} catch (error) {
|
|
75
|
+
console.error('❌ 出错了:', error);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// 生成报告
|
|
81
|
+
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
|
|
2
|
+
<!DOCTYPE html>
|
|
3
|
+
<html>
|
|
4
|
+
<head>
|
|
5
|
+
<meta charset="UTF-8">
|
|
6
|
+
<title><%= title %></title>
|
|
7
|
+
<style>
|
|
8
|
+
body {
|
|
9
|
+
font-family: Arial, sans-serif;
|
|
10
|
+
margin: 20px;
|
|
11
|
+
line-height: 1.6;
|
|
12
|
+
}
|
|
13
|
+
h1 { color: #333; }
|
|
14
|
+
.info {
|
|
15
|
+
color: #666;
|
|
16
|
+
margin-bottom: 20px;
|
|
17
|
+
padding: 10px;
|
|
18
|
+
background: #f5f5f5;
|
|
19
|
+
border-radius: 5px;
|
|
20
|
+
display: flex;
|
|
21
|
+
flex-direction: column;
|
|
22
|
+
align-items: start;
|
|
23
|
+
}
|
|
24
|
+
table {
|
|
25
|
+
width: 100%;
|
|
26
|
+
border-collapse: collapse;
|
|
27
|
+
margin-top: 20px;
|
|
28
|
+
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
|
|
29
|
+
font-size: 14px;
|
|
30
|
+
}
|
|
31
|
+
th {
|
|
32
|
+
background-color: #4CAF50;
|
|
33
|
+
color: white;
|
|
34
|
+
padding: 12px;
|
|
35
|
+
text-align: left;
|
|
36
|
+
}
|
|
37
|
+
td {
|
|
38
|
+
padding: 10px;
|
|
39
|
+
border-bottom: 1px solid #ddd;
|
|
40
|
+
max-width: 300px;
|
|
41
|
+
word-break: break-word;
|
|
42
|
+
}
|
|
43
|
+
tr:hover {
|
|
44
|
+
background-color: #f5f5f5;
|
|
45
|
+
}
|
|
46
|
+
tr:nth-child(even) {
|
|
47
|
+
background-color: #f9f9f9;
|
|
48
|
+
}
|
|
49
|
+
.empty {
|
|
50
|
+
text-align: center;
|
|
51
|
+
color: #999;
|
|
52
|
+
padding: 20px;
|
|
53
|
+
}
|
|
54
|
+
.analyze-container {
|
|
55
|
+
display: flex;
|
|
56
|
+
flex-direction: column;
|
|
57
|
+
align-items: start;
|
|
58
|
+
}
|
|
59
|
+
</style>
|
|
60
|
+
</head>
|
|
61
|
+
<body>
|
|
62
|
+
<h1><%= title %></h1>
|
|
63
|
+
<div class="info">
|
|
64
|
+
生成时间: <%= time %> | 共 <%= data.length %> 个功能
|
|
65
|
+
</div>
|
|
66
|
+
<% if (data.length === 0) { %>
|
|
67
|
+
<div class="empty">暂无数据</div>
|
|
68
|
+
<% } else { %>
|
|
69
|
+
<% data.forEach(function(analyze) { %>
|
|
70
|
+
<div class="analyze-container">
|
|
71
|
+
<h2><%= analyze.feature %></h2>
|
|
72
|
+
<div class="info">
|
|
73
|
+
<% analyze.messages.forEach(function(mes) { %>
|
|
74
|
+
<div>
|
|
75
|
+
<%= mes %>
|
|
76
|
+
</div>
|
|
77
|
+
<% }) %>
|
|
78
|
+
</div>
|
|
79
|
+
<table>
|
|
80
|
+
<thead>
|
|
81
|
+
<tr>
|
|
82
|
+
<% analyze.columns.forEach(function(value, key) { %>
|
|
83
|
+
<th><%= value %></th>
|
|
84
|
+
<% }); %>
|
|
85
|
+
</tr>
|
|
86
|
+
</thead>
|
|
87
|
+
<tbody>
|
|
88
|
+
<% analyze.data.forEach(function(row, index) { %>
|
|
89
|
+
<tr>
|
|
90
|
+
<% analyze.columns.forEach(function(value, col) { %>
|
|
91
|
+
<td><%= row[col] !== undefined ? row[col] : '-' %></td>
|
|
92
|
+
<% }); %>
|
|
93
|
+
</tr>
|
|
94
|
+
<% }); %>
|
|
95
|
+
</tbody>
|
|
96
|
+
</table>
|
|
97
|
+
</div>
|
|
98
|
+
<% }) %>
|
|
99
|
+
<% } %>
|
|
100
|
+
</body>
|
|
101
|
+
</html>
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
const TEMPLATE_CTX = `
|
|
2
|
+
<!DOCTYPE html>
|
|
3
|
+
<html>
|
|
4
|
+
<head>
|
|
5
|
+
<meta charset="UTF-8">
|
|
6
|
+
<title><%= title %></title>
|
|
7
|
+
<style>
|
|
8
|
+
body {
|
|
9
|
+
font-family: Arial, sans-serif;
|
|
10
|
+
margin: 20px;
|
|
11
|
+
line-height: 1.6;
|
|
12
|
+
}
|
|
13
|
+
h1 { color: #333; }
|
|
14
|
+
.info {
|
|
15
|
+
color: #666;
|
|
16
|
+
margin-bottom: 20px;
|
|
17
|
+
padding: 10px;
|
|
18
|
+
background: #f5f5f5;
|
|
19
|
+
border-radius: 5px;
|
|
20
|
+
display: flex;
|
|
21
|
+
flex-direction: column;
|
|
22
|
+
align-items: start;
|
|
23
|
+
}
|
|
24
|
+
table {
|
|
25
|
+
width: 100%;
|
|
26
|
+
border-collapse: collapse;
|
|
27
|
+
margin-top: 20px;
|
|
28
|
+
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
|
|
29
|
+
font-size: 14px;
|
|
30
|
+
}
|
|
31
|
+
th {
|
|
32
|
+
background-color: #4CAF50;
|
|
33
|
+
color: white;
|
|
34
|
+
padding: 12px;
|
|
35
|
+
text-align: left;
|
|
36
|
+
}
|
|
37
|
+
td {
|
|
38
|
+
padding: 10px;
|
|
39
|
+
border-bottom: 1px solid #ddd;
|
|
40
|
+
max-width: 300px;
|
|
41
|
+
word-break: break-word;
|
|
42
|
+
}
|
|
43
|
+
tr:hover {
|
|
44
|
+
background-color: #f5f5f5;
|
|
45
|
+
}
|
|
46
|
+
tr:nth-child(even) {
|
|
47
|
+
background-color: #f9f9f9;
|
|
48
|
+
}
|
|
49
|
+
.empty {
|
|
50
|
+
text-align: center;
|
|
51
|
+
color: #999;
|
|
52
|
+
padding: 20px;
|
|
53
|
+
}
|
|
54
|
+
.analyze-container {
|
|
55
|
+
display: flex;
|
|
56
|
+
flex-direction: column;
|
|
57
|
+
align-items: start;
|
|
58
|
+
}
|
|
59
|
+
</style>
|
|
60
|
+
</head>
|
|
61
|
+
<body>
|
|
62
|
+
<h1><%= title %></h1>
|
|
63
|
+
<div class="info">
|
|
64
|
+
生成时间: <%= time %> | 共 <%= data.length %> 个功能
|
|
65
|
+
</div>
|
|
66
|
+
<% if (data.length === 0) { %>
|
|
67
|
+
<div class="empty">暂无数据</div>
|
|
68
|
+
<% } else { %>
|
|
69
|
+
<% data.forEach(function(analyze) { %>
|
|
70
|
+
<div class="analyze-container">
|
|
71
|
+
<h2><%= analyze.feature %></h2>
|
|
72
|
+
<div class="info">
|
|
73
|
+
<% analyze.messages.forEach(function(mes) { %>
|
|
74
|
+
<div>
|
|
75
|
+
<%= mes %>
|
|
76
|
+
</div>
|
|
77
|
+
<% }) %>
|
|
78
|
+
</div>
|
|
79
|
+
<table>
|
|
80
|
+
<thead>
|
|
81
|
+
<tr>
|
|
82
|
+
<% analyze.columns.forEach(function(value, key) { %>
|
|
83
|
+
<th><%= value %></th>
|
|
84
|
+
<% }); %>
|
|
85
|
+
</tr>
|
|
86
|
+
</thead>
|
|
87
|
+
<tbody>
|
|
88
|
+
<% analyze.data.forEach(function(row, index) { %>
|
|
89
|
+
<tr>
|
|
90
|
+
<% analyze.columns.forEach(function(value, col) { %>
|
|
91
|
+
<td><%= row[col] !== undefined ? row[col] : '-' %></td>
|
|
92
|
+
<% }); %>
|
|
93
|
+
</tr>
|
|
94
|
+
<% }); %>
|
|
95
|
+
</tbody>
|
|
96
|
+
</table>
|
|
97
|
+
</div>
|
|
98
|
+
<% }) %>
|
|
99
|
+
<% } %>
|
|
100
|
+
</body>
|
|
101
|
+
</html>
|
|
102
|
+
`
|
|
103
|
+
export { TEMPLATE_CTX }
|