ol-base-components 2.8.12 → 2.8.13

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/package.json CHANGED
@@ -1,23 +1,23 @@
1
1
  {
2
2
  "name": "ol-base-components",
3
- "version": "2.8.12",
3
+ "version": "2.8.13",
4
4
  "private": false,
5
5
  "main": "src/package/index.js",
6
6
  "bin": {
7
7
  "init": "src/api/init.js",
8
8
  "run": "src/api/run.js",
9
9
  "api": "src/api/api.js",
10
- "add": "src/bin/add.js",
11
- "install-vscode": "scripts/install-vscode.js"
10
+ "add": "src/bin/add.js"
12
11
  },
13
12
  "scripts": {
14
13
  "serve": "vue-cli-service serve --no-verify",
15
14
  "build": "vue-cli-service build",
16
15
  "lint": "vue-cli-service lint",
17
16
  "add": "node src/bin/add.js",
18
- "vscode:build": "node scripts/build-vscode.js",
19
- "vscode:install": "node scripts/install-vscode.js",
20
- "postinstall": "node scripts/auto-install.js"
17
+ "openCloseloop": "node src/bin/openCloseloop.js",
18
+ "openLoop": "node src/bin/openLoop.js",
19
+ "news": "node src/bin/news.js"
20
+
21
21
  },
22
22
  "dependencies": {
23
23
  "commander": "^14.0.0",
@@ -43,39 +43,6 @@
43
43
  "vue-template-compiler": "^2.6.14",
44
44
  "@types/vscode": "^1.60.0"
45
45
  },
46
- "vscode": {
47
- "extension": {
48
- "name": "vue-page-generator",
49
- "displayName": "Vue 页面生成器",
50
- "description": "配合ol-base-components组件使用, 无需联调的CRUD页面",
51
- "version": "0.0.4",
52
- "engines": {
53
- "vscode": "^1.60.0"
54
- },
55
- "activationEvents": [
56
- "onCommand:vue-generator.createPage"
57
- ],
58
- "main": "./src/vscode/extension.js",
59
- "contributes": {
60
- "commands": [
61
- {
62
- "command": "vue-generator.createPage",
63
- "title": "生成 Vue 页面",
64
- "category": "Vue 生成器"
65
- }
66
- ],
67
- "menus": {
68
- "explorer/context": [
69
- {
70
- "command": "vue-generator.createPage",
71
- "group": "navigation",
72
- "when": "resourceFolder"
73
- }
74
- ]
75
- }
76
- }
77
- }
78
- },
79
46
  "eslintConfig": {
80
47
  "root": true,
81
48
  "env": {
package/readme1.md ADDED
@@ -0,0 +1,164 @@
1
+ 告别手写CRUD!命令行方式通过swagger实现一键生成页面
2
+ > 还在为重复的增删改查页面而烦恼?还在手动联调接口和组件?今天给大家分享一个革命性的Vue组件库,通过命令行方式实现快速页面生成,让你的开发效率提升10倍!
3
+
4
+ ## 💡 项目背景
5
+
6
+ 作为一名前端开发者,你是否经常遇到这样的场景:
7
+
8
+ - 接到新需求,又要写一套完整的CRUD页面
9
+ - 重复编写表格、表单、搜索框等基础组件
10
+ - 手动联调接口,调试各种数据格式问题
11
+ - ⏰ 一个简单的管理页面要花半天时间
12
+
13
+ 如果你也有这些困扰,那么今天分享的这个项目绝对能让你眼前一亮!
14
+
15
+ ## 🌟 项目介绍
16
+
17
+ **ol-base-components** 是一个基于 Element-UI 的企业级开发框架,最大的特色是**交互式命令行方式自动生成完整页面**,无需手动联调,几秒完成页面。
18
+
19
+ ## 快速开始
20
+
21
+ ### 安装
22
+
23
+ #### 方式一:npm 安装(推荐)
24
+ ```bash
25
+ # 安装组件库
26
+ npm install ol-base-components
27
+
28
+ # 安装依赖
29
+ npm install swagger-client@3.0.1
30
+ ```
31
+ ### 基本使用
32
+
33
+ #### 安装
34
+ ```javascript
35
+ // main.js
36
+ import Vue from "vue";
37
+ import App from "./App.vue";
38
+ import OlBaseComponents from "ol-base-components"; // 导入组件库
39
+
40
+ // 使用组件库
41
+ Vue.use(OlBaseComponents);
42
+
43
+ // 安装,可以在登录后调用
44
+ import { swaggerInstall } from "ol-base-components";
45
+ swaggerInstall("http://192.168.xxx.xxx:20019/swagger/v1/swagger.json").then(() => {
46
+ // 成功获取swagger数据后加载页面,这里可以写登录接口成功后执行的逻辑
47
+ });
48
+
49
+ // 卸载
50
+ import { swaggerUnload } from "ol-base-components";
51
+ swaggerUnload();
52
+ ```
53
+
54
+ ### 生成页面
55
+
56
+ #### 1. 生成API接口
57
+ ```bash
58
+ # 基本用法
59
+ npx init http://192.168.xxx.xxx:20019
60
+
61
+ # 自定义输出路径
62
+ npx api http://192.168.xxx.xxx:20019 src/api/swagger.js
63
+ ```
64
+
65
+ #### 2. 生成页面组件
66
+ ```bash
67
+ # 基本用法
68
+ npx add userManagement -p src/view
69
+
70
+ # 完整参数
71
+ npx add userManagement -p src/view \
72
+ -u /api/user/paged-result \
73
+ -e /api/user/export \
74
+ -m User
75
+ ```
76
+
77
+ ## 核心亮点:命令行快速生成
78
+
79
+ ### 1. 一键生成API接口
80
+
81
+ ```bash
82
+ # 从Swagger自动生成API接口
83
+ npx init http://192.168.xxx.xxx:20019
84
+ ```
85
+
86
+ 这个命令会:
87
+ - 自动获取Swagger文档
88
+ - 自动生成完整的API接口文件
89
+ - 包含详细的JSDoc注释
90
+ - 自动处理接口参数和返回值
91
+
92
+
93
+ ![run.png](https://p0-xtjj-private.juejin.cn/tos-cn-i-73owjymdk6/8acc65eed01247588bb81775897209c1~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAg6IWw6Ze055uY56qB5Ye655qE57qi5Yip:q75.awebp?policy=eyJ2bSI6MywidWlkIjoiMzIwNjYzODA5NzkzNDQ4NyJ9&rk3s=f64ab15b&x-orig-authkey=f32326d3454f2ac7e96d3d06cdbb035152127018&x-orig-expires=1757470144&x-orig-sign=rR9UsX5SyxXhGOl2RvfFhYdh%2Fhs%3D)
94
+
95
+ ![api.png](https://p0-xtjj-private.juejin.cn/tos-cn-i-73owjymdk6/882511142db942c3951f75467db492f6~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAg6IWw6Ze055uY56qB5Ye655qE57qi5Yip:q75.awebp?policy=eyJ2bSI6MywidWlkIjoiMzIwNjYzODA5NzkzNDQ4NyJ9&rk3s=f64ab15b&x-orig-authkey=f32326d3454f2ac7e96d3d06cdbb035152127018&x-orig-expires=1757470144&x-orig-sign=Jb2y66QqYKVuw6FLCLIKvcVko%2BY%3D)
96
+ ### 2. 交互式页面生成
97
+
98
+ ```bash
99
+ # 交互式生成完整CRUD页面
100
+ npx add userManagement -p src/view
101
+ ```
102
+
103
+ 运行后会弹出交互式配置:
104
+ - 📋 选择需要的功能(新增、编辑、删除、详情)
105
+ - 配置接口地址
106
+ - ⚙️ 设置字段映射
107
+ - 自动生成完整的Vue组件
108
+
109
+ #### 效果如下
110
+ ![effectPicture.png](https://p0-xtjj-private.juejin.cn/tos-cn-i-73owjymdk6/da3a7b5b10784e8aa365695010c8a3f9~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAg6IWw6Ze055uY56qB5Ye655qE57qi5Yip:q75.awebp?policy=eyJ2bSI6MywidWlkIjoiMzIwNjYzODA5NzkzNDQ4NyJ9&rk3s=f64ab15b&x-orig-authkey=f32326d3454f2ac7e96d3d06cdbb035152127018&x-orig-expires=1757470144&x-orig-sign=pzKlXLC7rAA9kmSkUIcE4x6SvWo%3D)
111
+
112
+
113
+ ![init.png](https://p0-xtjj-private.juejin.cn/tos-cn-i-73owjymdk6/a52f1a66540d43aeabe5d67b2f5b135c~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAg6IWw6Ze055uY56qB5Ye655qE57qi5Yip:q75.awebp?policy=eyJ2bSI6MywidWlkIjoiMzIwNjYzODA5NzkzNDQ4NyJ9&rk3s=f64ab15b&x-orig-authkey=f32326d3454f2ac7e96d3d06cdbb035152127018&x-orig-expires=1757470144&x-orig-sign=ggypHhBD0HsuejMBlrznimoQ6Vk%3D)
114
+
115
+
116
+
117
+ ## 🛠️ 技术实现
118
+
119
+ ### 核心架构
120
+
121
+ 项目采用模块化设计,主要包含:
122
+
123
+ 1. **组件库**:基于Element-UI二次封装
124
+ 2. **命令行工具**:Node.js脚本自动生成代码
125
+ 3. **Swagger集成**:自动解析API文档
126
+ 4. **模板引擎**:动态生成Vue组件代码
127
+
128
+ ## 搭配vscode插件 vue-page-generator
129
+ 为[ol-base-components](https://github.com/time202051/base-component)写的一个 VSCode 插件。取代之前命令行方式,通过可视化的交互方式更加简单方便的自动生成 CRUD 页面模板。
130
+
131
+ ### 安装
132
+ 在 VSCode 扩展商店中搜索"vue-page-generator"并安装
133
+
134
+ ### 使用方法
135
+ 1. 在 VSCode 文件资源管理器中右键选择一个文件夹
136
+ 2. 选择 "生成 CRUD 页面" 菜单项
137
+ 3. 在弹出的配置面板中填写相关信息
138
+ 4. 点击 "🚀 生成页面" 按钮
139
+
140
+
141
+ ### 步骤效果图
142
+ ![generator0.png](https://p0-xtjj-private.juejin.cn/tos-cn-i-73owjymdk6/10e05b492b294a638d89d440d082c2df~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAg6IWw6Ze055uY56qB5Ye655qE57qi5Yip:q75.awebp?policy=eyJ2bSI6MywidWlkIjoiMzIwNjYzODA5NzkzNDQ4NyJ9&rk3s=f64ab15b&x-orig-authkey=f32326d3454f2ac7e96d3d06cdbb035152127018&x-orig-expires=1757470144&x-orig-sign=uFgUAHfEA0Ut9zjcfZobbBEBwjM%3D)
143
+
144
+ ![generator1.png](https://p0-xtjj-private.juejin.cn/tos-cn-i-73owjymdk6/3e70349e19464390a291272e810abd0c~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAg6IWw6Ze055uY56qB5Ye655qE57qi5Yip:q75.awebp?policy=eyJ2bSI6MywidWlkIjoiMzIwNjYzODA5NzkzNDQ4NyJ9&rk3s=f64ab15b&x-orig-authkey=f32326d3454f2ac7e96d3d06cdbb035152127018&x-orig-expires=1757470144&x-orig-sign=GWuC1pjZlEmb8N0SnA%2Fx1HCnB%2BQ%3D)
145
+
146
+ ![generator2.png](https://p0-xtjj-private.juejin.cn/tos-cn-i-73owjymdk6/2483f171137e4ea88e6a822dc5a82e51~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAg6IWw6Ze055uY56qB5Ye655qE57qi5Yip:q75.awebp?policy=eyJ2bSI6MywidWlkIjoiMzIwNjYzODA5NzkzNDQ4NyJ9&rk3s=f64ab15b&x-orig-authkey=f32326d3454f2ac7e96d3d06cdbb035152127018&x-orig-expires=1757470144&x-orig-sign=%2BdKxpOHGP2g5nifHaBb1qe%2BJyU8%3D)
147
+
148
+ ![generator3.png](https://p0-xtjj-private.juejin.cn/tos-cn-i-73owjymdk6/2ad9ffc9acd444798c1768521a2050bc~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAg6IWw6Ze055uY56qB5Ye655qE57qi5Yip:q75.awebp?policy=eyJ2bSI6MywidWlkIjoiMzIwNjYzODA5NzkzNDQ4NyJ9&rk3s=f64ab15b&x-orig-authkey=f32326d3454f2ac7e96d3d06cdbb035152127018&x-orig-expires=1757470144&x-orig-sign=wq8FUrnsMZUjcAQpctbzKGuFWZw%3D)
149
+
150
+ ## 🎯 总结
151
+
152
+ ol-base-components 通过创新的命令行生成方式和交互式方式,彻底改变了传统的前端开发模式:
153
+
154
+ - ✅ **告别重复劳动**:不再手写CRUD页面
155
+ - ✅ **提升开发效率**:从小时级到分钟级
156
+ - ✅ **降低维护成本**:标准化代码结构
157
+ - ✅ **减少联调时间**:自动处理接口对接
158
+
159
+ 如果你也想体验这种革命性的开发方式,不妨试试这个项目。相信它会让你重新认识前端开发的效率边界!
160
+
161
+ ---
162
+ ### 如需了解更多可以查阅官网
163
+
164
+ **官网**:[https://time202051.github.io/baseCom.github.io/](https://time202051.github.io/baseCom.github.io/)
Binary file
Binary file
Binary file
@@ -162,9 +162,6 @@ const vue2Template = (moduleName, config = {}) => {
162
162
  if (config.hasExport) {
163
163
  methods.push(`
164
164
  export() {
165
- const timer = this.formSearchData.value.createdTime;
166
- this.formSearchData.value.BeginTime = timer ? timer[0] : "";
167
- this.formSearchData.value.EndTime = timer ? timer[1] : "";
168
165
  this.post({
169
166
  url: ${config.swaggerModule}.${exportUrlKey},
170
167
  isLoading: true,
@@ -327,6 +324,7 @@ export default {
327
324
  };
328
325
 
329
326
  const vue2Form = (moduleName, config = {}) => {
327
+
330
328
  let editUrlKey = "",
331
329
  detailUrlKey = "",
332
330
  baseUrlKey = "";
@@ -0,0 +1,171 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * 获取美联储相关新闻列表(优先官方RSS,失败则回退GDELT)
4
+ * 用法:
5
+ * node src/bin/fetchFedNews.js --limit=20 --json
6
+ * node src/bin/fetchFedNews.js --since=2024-01-01
7
+ *
8
+ * 选项:
9
+ * --limit 数量上限,默认 20
10
+ * --json 以 JSON 输出,否则打印为简洁文本
11
+ * --since 仅保留不早于该日期的新闻(YYYY-MM-DD)
12
+ */
13
+
14
+ const { URL } = require("url");
15
+ const LIMIT = Number((process.argv.find(a => a.startsWith("--limit=")) || "").split("=")[1]) || 20;
16
+ const AS_JSON = process.argv.includes("--json");
17
+ const SINCE = (process.argv.find(a => a.startsWith("--since=")) || "").split("=")[1] || "";
18
+ const sinceDate = SINCE ? new Date(SINCE) : null;
19
+
20
+ const FEEDS = [
21
+ { name: "Fed Press Monetary", url: "https://www.federalreserve.gov/feeds/press_monetary.xml", type: "rss" },
22
+ { name: "Fed Speeches", url: "https://www.federalreserve.gov/feeds/speeches.xml", type: "rss" },
23
+ { name: "NY Fed What's New", url: "https://www.newyorkfed.org/rss/feeds/whatsnew.xml", type: "rss" },
24
+ ];
25
+
26
+ const GDELT = {
27
+ name: "GDELT Docs",
28
+ url: "https://api.gdeltproject.org/api/v2/doc/doc?query=Federal%20Reserve%20OR%20FOMC%20OR%20Powell&mode=ArtList&maxrecords=50&format=json",
29
+ type: "json",
30
+ };
31
+
32
+ async function safeImportFastXmlParser() {
33
+ try {
34
+ // 优先使用项目中已安装的 fast-xml-parser
35
+ const mod = require("fast-xml-parser");
36
+ const XMLParser = mod.XMLParser || mod;
37
+ return XMLParser;
38
+ } catch {
39
+ return null;
40
+ }
41
+ }
42
+
43
+ async function fetchText(url) {
44
+ const res = await fetch(url, { redirect: "follow" });
45
+ if (!res.ok) throw new Error(`HTTP ${res.status} for ${url}`);
46
+ return await res.text();
47
+ }
48
+
49
+ async function fetchJSON(url) {
50
+ const res = await fetch(url, { redirect: "follow" });
51
+ if (!res.ok) throw new Error(`HTTP ${res.status} for ${url}`);
52
+ return await res.json();
53
+ }
54
+
55
+ function toDate(d) {
56
+ if (!d) return null;
57
+ const t = new Date(d);
58
+ return isNaN(t.getTime()) ? null : t;
59
+ }
60
+
61
+ function normalizeRssItem(it, sourceName) {
62
+ // 不同RSS字段命名差异处理
63
+ const link = it.link?.href || it.link || it.guid || "";
64
+ const pubDate = it.pubDate || it.published || it.updated || it["dc:date"] || it["atom:updated"] || "";
65
+ const desc = it.description || it.summary || it.content || "";
66
+ return {
67
+ source: sourceName,
68
+ title: String(it.title || "").trim(),
69
+ link: typeof link === "string" ? link : "",
70
+ publishedAt: toDate(pubDate)?.toISOString() || null,
71
+ description: typeof desc === "string" ? desc.replace(/<[^>]+>/g, "").trim() : "",
72
+ };
73
+ }
74
+
75
+ function normalizeGdeltItem(it) {
76
+ return {
77
+ source: "GDELT",
78
+ title: String(it.title || "").trim(),
79
+ link: it.url || "",
80
+ publishedAt: toDate(it.seendate || it.publishtime || it.datetime)?.toISOString() || null,
81
+ description: String(it.excerpt || it.subtitle || "").trim(),
82
+ };
83
+ }
84
+
85
+ function filterAndSort(items) {
86
+ let arr = items.filter(x => x && x.title && x.link);
87
+ if (sinceDate) {
88
+ arr = arr.filter(x => {
89
+ const t = x.publishedAt ? new Date(x.publishedAt) : null;
90
+ return t && t >= sinceDate;
91
+ });
92
+ }
93
+ arr.sort((a, b) => {
94
+ const ta = a.publishedAt ? new Date(a.publishedAt).getTime() : 0;
95
+ const tb = b.publishedAt ? new Date(b.publishedAt).getTime() : 0;
96
+ return tb - ta;
97
+ });
98
+ return arr.slice(0, LIMIT);
99
+ }
100
+
101
+ async function tryFetchRss(XMLParser) {
102
+ const results = [];
103
+ for (const feed of FEEDS) {
104
+ try {
105
+ const xml = await fetchText(feed.url);
106
+ if (!XMLParser) throw new Error("fast-xml-parser 未安装");
107
+ const parser = new XMLParser({ ignoreAttributes: false });
108
+ const data = parser.parse(xml);
109
+ let items = [];
110
+
111
+ // 兼容 RSS 2.0 / Atom
112
+ if (data?.rss?.channel?.item) {
113
+ items = Array.isArray(data.rss.channel.item) ? data.rss.channel.item : [data.rss.channel.item];
114
+ } else if (data?.feed?.entry) {
115
+ items = Array.isArray(data.feed.entry) ? data.feed.entry : [data.feed.entry];
116
+ }
117
+
118
+ for (const it of items) {
119
+ results.push(normalizeRssItem(it, feed.name));
120
+ }
121
+ } catch (e) {
122
+ // 单个源失败不影响总体,继续其他源
123
+ // console.warn(`[WARN] RSS源失败: ${feed.name} - ${e.message}`);
124
+ }
125
+ }
126
+ return results;
127
+ }
128
+
129
+ async function fallbackGdelt() {
130
+ try {
131
+ const data = await fetchJSON(GDELT.url);
132
+ const list = data?.articles || data?.documents || data?.matches || [];
133
+ return list.map(normalizeGdeltItem);
134
+ } catch {
135
+ return [];
136
+ }
137
+ }
138
+
139
+ function print(items) {
140
+ if (AS_JSON) {
141
+ console.log(JSON.stringify(items, null, 2));
142
+ return;
143
+ }
144
+ for (const it of items) {
145
+ const time = it.publishedAt ? new Date(it.publishedAt).toISOString().replace("T", " ").slice(0, 19) : "";
146
+ console.log(`- [${it.source}] ${time}`);
147
+ console.log(` ${it.title}`);
148
+ if (it.link) console.log(` ${it.link}`);
149
+ console.log("");
150
+ }
151
+ }
152
+
153
+ (async function main() {
154
+ try {
155
+ const XMLParser = await safeImportFastXmlParser();
156
+ let items = [];
157
+ if (XMLParser) {
158
+ items = await tryFetchRss(XMLParser);
159
+ }
160
+ // 若RSS拿不到,回退到GDELT
161
+ if (!items.length) {
162
+ const gd = await fallbackGdelt();
163
+ items = gd;
164
+ }
165
+ items = filterAndSort(items);
166
+ print(items);
167
+ } catch (e) {
168
+ console.error("获取新闻失败:", e.message);
169
+ process.exit(1);
170
+ }
171
+ })();
@@ -0,0 +1,154 @@
1
+ // open-close-loop.js
2
+ // 循环“打开→停留→关闭→间隔”的浏览器控制脚本(Windows)
3
+ // 优先使用指定的 Chrome 路径独立进程(只关闭本次打开窗口);否则使用默认浏览器并通过进程名强关(会关闭该浏览器所有窗口)。
4
+
5
+ const { spawn, exec, execSync } = require("child_process");
6
+ const fs = require("fs");
7
+ const path = require("path");
8
+ const os = require("os");
9
+
10
+ const COUNT = 0; // 循环次数;0 表示无限循环
11
+ const MODE = "headless"; // headless|minimized|default headless:无头模式浏览器在后台运行,不显示窗口/minimized:最小化模式浏览器启动后立即最小化到任务栏/default:默认模式正常显示浏览器窗口
12
+ // 支持多页面:同一轮“同时打开,同时关闭”
13
+ const URLS = ["https://juejin.cn/post/7542083098738475059"];
14
+ const OPEN_MS = 10000; // 打开停留时间
15
+ const CLOSE_MS = 1000; // 关闭后间隔
16
+ const BROWSER_PROC = "chrome.exe"; // 默认浏览器进程名(默认浏览器模式会强关所有该进程窗口)
17
+ const CHROME_PATH = "C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe";
18
+
19
+ const sleep = ms => new Promise(r => setTimeout(r, ms));
20
+
21
+ const isWin = process.platform === "win32";
22
+ if (!isWin) {
23
+ console.error("当前脚本仅适配 Windows。");
24
+ process.exit(1);
25
+ }
26
+
27
+ // 判断文件是否存在
28
+ function fileExists(file) {
29
+ try {
30
+ fs.accessSync(file, fs.constants.X_OK | fs.constants.F_OK);
31
+ return true;
32
+ } catch {
33
+ return false;
34
+ }
35
+ }
36
+
37
+ // 创建临时profile,避免复用已开 Chrome 实例
38
+ function makeTempProfile() {
39
+ const dir = fs.mkdtempSync(path.join(os.tmpdir(), "chrome-prof-"));
40
+ return dir;
41
+ }
42
+
43
+ // 优雅关闭与清理(单实例变量)
44
+ let currentProc = null;
45
+ let currentProfile = null;
46
+
47
+ async function cleanup() {
48
+ try {
49
+ if (currentProc && !currentProc.killed) {
50
+ try {
51
+ currentProc.kill();
52
+ } catch {}
53
+ try {
54
+ execSync(`taskkill /PID ${currentProc.pid} /T /F`, { stdio: "ignore" });
55
+ } catch {}
56
+ }
57
+ } catch {}
58
+ currentProc = null;
59
+
60
+ if (currentProfile) {
61
+ try {
62
+ fs.rmSync(currentProfile, { recursive: true, force: true });
63
+ } catch {}
64
+ currentProfile = null;
65
+ }
66
+ }
67
+
68
+ process.on("SIGINT", async () => {
69
+ await cleanup();
70
+ process.exit(0);
71
+ });
72
+ process.on("SIGTERM", async () => {
73
+ await cleanup();
74
+ process.exit(0);
75
+ });
76
+
77
+ // Chrome 独立进程:同一轮“并发打开→停留→并发关闭并清理”
78
+ async function openWithChromeLoop() {
79
+ const loops = COUNT > 0 ? COUNT : Infinity;
80
+ for (let i = 0; i < loops; i++) {
81
+ const procs = [];
82
+ const profiles = [];
83
+
84
+ for (const url of URLS) {
85
+ const prof = makeTempProfile();
86
+ profiles.push(prof);
87
+ const args = [
88
+ "--new-window",
89
+ `--user-data-dir=${prof}`,
90
+ "--no-first-run",
91
+ "--no-default-browser-check",
92
+ ...(MODE === "headless" ? ["--headless=new", "--disable-gpu", "--hide-scrollbars"] : []),
93
+ ...(MODE === "minimized" ? ["--start-minimized", "--window-size=1,1"] : []),
94
+ url,
95
+ ];
96
+ const p = spawn(CHROME_PATH, args, { stdio: "ignore" });
97
+ procs.push(p);
98
+ }
99
+
100
+ await sleep(OPEN_MS);
101
+
102
+ // 并发关闭刚打开的所有 Chrome 进程
103
+ for (const p of procs) {
104
+ try {
105
+ p.kill();
106
+ } catch {}
107
+ try {
108
+ execSync(`taskkill /PID ${p.pid} /T /F`, { stdio: "ignore" });
109
+ } catch {}
110
+ }
111
+ // 清理对应临时 profile
112
+ for (const prof of profiles) {
113
+ try {
114
+ fs.rmSync(prof, { recursive: true, force: true });
115
+ } catch {}
116
+ }
117
+
118
+ await sleep(CLOSE_MS);
119
+ }
120
+ await cleanup();
121
+ process.exit(0);
122
+ }
123
+
124
+ // 默认浏览器:同一轮“并发打开→停留→一次性强关该进程所有窗口”
125
+ async function openWithDefaultBrowserLoop() {
126
+ const loops = COUNT > 0 ? COUNT : Infinity;
127
+ for (let i = 0; i < loops; i++) {
128
+ for (const url of URLS) {
129
+ const startArgs =
130
+ MODE === "minimized" ? ["/c", "start", "/min", "", url] : ["/c", "start", "", url];
131
+ spawn("cmd", startArgs, { detached: true, stdio: "ignore" }).unref();
132
+ }
133
+
134
+ await sleep(OPEN_MS);
135
+
136
+ // 一次性结束该浏览器所有窗口(注意:这会关闭同类浏览器的所有窗口)
137
+ exec(`taskkill /IM ${BROWSER_PROC} /F`, () => {});
138
+ await sleep(CLOSE_MS);
139
+ }
140
+ await cleanup();
141
+ process.exit(0);
142
+ }
143
+
144
+ (async function main() {
145
+ const useChrome = fileExists(CHROME_PATH);
146
+ if (useChrome) {
147
+ await openWithChromeLoop();
148
+ } else {
149
+ console.warn(
150
+ `未找到 Chrome:${CHROME_PATH},将使用默认浏览器并通过进程名(${BROWSER_PROC})强制关闭。`
151
+ );
152
+ await openWithDefaultBrowserLoop();
153
+ }
154
+ })();