ol-base-components 3.4.5 → 3.4.6
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/dist/index.mjs +7105 -0
- package/dist/index.umd.js +1 -0
- package/dist/style.css +1 -0
- package/package.json +18 -4
- package/.eslintrc +0 -59
- package/.github/deploy.yml +0 -81
- package/.prettierignore +0 -6
- package/.prettierrc +0 -13
- package/.trae/rules/project.md +0 -2
- package/babel.config.js +0 -5
- package/jsconfig.json +0 -19
- package/readme1.md +0 -164
- package/src/App.vue +0 -932
- package/src/assets/Snipaste_2025-09-03_14-30-49.png +0 -0
- package/src/assets/api.png +0 -0
- package/src/assets/css/iconfont.css +0 -342
- package/src/assets/duojibiaotou.png +0 -0
- package/src/assets/effectPicture.png +0 -0
- package/src/assets/generator0.png +0 -0
- package/src/assets/generator1.png +0 -0
- package/src/assets/generator2.png +0 -0
- package/src/assets/icon/printModel.svg +0 -1
- package/src/assets/init.png +0 -0
- package/src/assets/logo.png +0 -0
- package/src/assets/olBaseComponentsLogo.svg +0 -100
- package/src/assets/print.svg +0 -1
- package/src/assets/run.png +0 -0
- package/src/assets/vscodecj.png +0 -0
- package/src/bin/initTemplate.js +0 -409
- package/src/bin/news.js +0 -171
- package/src/bin/openCloseloop.js +0 -154
- package/src/bin/openLoop.js +0 -154
- package/src/main.js +0 -13
- package/src/package/customSearch/index.js +0 -7
- package/src/package/customSearch/src/index.vue +0 -120
- package/src/package/dialog/index.js +0 -7
- package/src/package/dialog/src/index.vue +0 -419
- package/src/package/form/index.js +0 -7
- package/src/package/form/src/index.vue +0 -405
- package/src/package/formSearch/index.js +0 -7
- package/src/package/formSearch/src/components/SearchConfigDialog.vue +0 -957
- package/src/package/formSearch/src/index.js +0 -29
- package/src/package/formSearch/src/index.vue +0 -928
- package/src/package/index.js +0 -243
- package/src/package/numberRange/index.js +0 -7
- package/src/package/numberRange/src/index.vue +0 -351
- package/src/package/print/index.js +0 -76
- package/src/package/print/src/components/PaperSelector.vue +0 -109
- package/src/package/print/src/index.vue +0 -622
- package/src/package/print/src/provide/provider1.js +0 -215
- package/src/package/printModel/index.js +0 -7
- package/src/package/printModel/src/index.vue +0 -493
- package/src/package/table/index.js +0 -12
- package/src/package/table/src/TableColumn.vue +0 -77
- package/src/package/table/src/components/PrintTemplateSelector.vue +0 -210
- package/src/package/table/src/index.vue +0 -945
- package/src/package/table/src/nodata.jpg +0 -0
- package/src/package/table/src/printTable.vue +0 -196
- package/src/utils/getEnum.js +0 -8
- package/src/utils/initData.js +0 -138
- package/vue.config.js +0 -21
- /package/{public → dist}/favicon.ico +0 -0
- /package/{public → dist}/index.html +0 -0
- /package/{public → dist}/print-lock.css +0 -0
package/src/bin/initTemplate.js
DELETED
|
@@ -1,409 +0,0 @@
|
|
|
1
|
-
function generateKeyName(url, method) {
|
|
2
|
-
// 移除前缀 "/api/app"
|
|
3
|
-
const cleanedUrl = url.replace(/\/api\/app/, "");
|
|
4
|
-
const arr = cleanedUrl.split("/");
|
|
5
|
-
|
|
6
|
-
// 处理 {xxx} 转换为 ByXxx
|
|
7
|
-
const processedArr = arr.map(
|
|
8
|
-
item =>
|
|
9
|
-
item
|
|
10
|
-
.replace(/{(.*?)}/, (_, param) => `By${param.charAt(0).toUpperCase() + param.slice(1)}`) // 处理 {xxx}
|
|
11
|
-
.replace(/[-_]/g, "") // 去除 - 和 _
|
|
12
|
-
);
|
|
13
|
-
|
|
14
|
-
// 删除第一个空项
|
|
15
|
-
if (processedArr[0] === "") {
|
|
16
|
-
processedArr.shift();
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
// 去重和拼接相邻相同的项
|
|
20
|
-
const resultArr = [];
|
|
21
|
-
for (let i = 0; i < processedArr.length; i++) {
|
|
22
|
-
if (i === 0 || processedArr[i] !== processedArr[i - 1]) {
|
|
23
|
-
// 将每项首字母大写
|
|
24
|
-
const capitalizedItem = processedArr[i].charAt(0).toUpperCase() + processedArr[i].slice(1);
|
|
25
|
-
resultArr.push(capitalizedItem);
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
const key = resultArr.join("");
|
|
29
|
-
return `${method.toLowerCase()}${key}`;
|
|
30
|
-
}
|
|
31
|
-
const vue2Template = (moduleName, config = {}) => {
|
|
32
|
-
// 生成各种接口的key名称
|
|
33
|
-
let pageUrlKey = "",
|
|
34
|
-
exportUrlKey = "",
|
|
35
|
-
addUrlKey = "",
|
|
36
|
-
editUrlKey = "",
|
|
37
|
-
deleteUrlKey = "",
|
|
38
|
-
detailUrlKey = "",
|
|
39
|
-
baseUrlKey = ""; //接口选择优先级:新增 > 编辑 > 详,
|
|
40
|
-
|
|
41
|
-
if (config.pageUrl) pageUrlKey = generateKeyName(config.pageUrl, "get");
|
|
42
|
-
if (config.exportUrl) exportUrlKey = generateKeyName(config.exportUrl, "post");
|
|
43
|
-
if (config.detailUrl) {
|
|
44
|
-
detailUrlKey = generateKeyName(config.detailUrl, "get");
|
|
45
|
-
baseUrlKey = `${detailUrlKey}CompleteUrl`; //补充后缀
|
|
46
|
-
}
|
|
47
|
-
if (config.editUrl) {
|
|
48
|
-
editUrlKey = generateKeyName(config.editUrl, "put");
|
|
49
|
-
baseUrlKey = `${editUrlKey}CompleteUrl`; //补充后缀
|
|
50
|
-
}
|
|
51
|
-
if (config.addUrl) baseUrlKey = addUrlKey = generateKeyName(config.addUrl, "post");
|
|
52
|
-
if (config.deleteUrl) deleteUrlKey = generateKeyName(config.deleteUrl, "delete");
|
|
53
|
-
|
|
54
|
-
// 生成导入语句
|
|
55
|
-
const generateImports = () => {
|
|
56
|
-
const imports = [];
|
|
57
|
-
if (config.pageUrl) imports.push(`${pageUrlKey}`);
|
|
58
|
-
if (config.addUrl) imports.push(`${addUrlKey}`);
|
|
59
|
-
if (config.editUrl) imports.push(`${editUrlKey}`);
|
|
60
|
-
if (config.detailUrl) imports.push(`${detailUrlKey}`);
|
|
61
|
-
if (config.deleteUrl) imports.push(`${deleteUrlKey}`);
|
|
62
|
-
return imports.join(", ");
|
|
63
|
-
};
|
|
64
|
-
|
|
65
|
-
// 生成方法
|
|
66
|
-
const generateMethods = () => {
|
|
67
|
-
const methods = [];
|
|
68
|
-
|
|
69
|
-
// onCancel
|
|
70
|
-
if (config.hasAdd || config.hasEdit || config.hasDetail) {
|
|
71
|
-
methods.push(`
|
|
72
|
-
onCancel() {
|
|
73
|
-
this.formConfig.dialogVisible = false;
|
|
74
|
-
}`);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
// onSubmit
|
|
78
|
-
if (config.hasAdd || config.hasEdit) {
|
|
79
|
-
methods.push(`
|
|
80
|
-
async onSubmit({ form, data }) {
|
|
81
|
-
if(form.type === 1){
|
|
82
|
-
//新建
|
|
83
|
-
const res = await ${addUrlKey}(data);
|
|
84
|
-
if(res.code !== 200) return;
|
|
85
|
-
this.$message("新建成功");
|
|
86
|
-
}else if (form.type === 2) {
|
|
87
|
-
//编辑
|
|
88
|
-
const res = await ${editUrlKey}(data['${config.rowId}'], data);
|
|
89
|
-
if(res.code !== 200) return;
|
|
90
|
-
this.$message("编辑成功");
|
|
91
|
-
this.init();
|
|
92
|
-
};
|
|
93
|
-
this.init();
|
|
94
|
-
this.onCancel()
|
|
95
|
-
}`);
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
if (config.hasAdd) {
|
|
99
|
-
methods.push(`
|
|
100
|
-
addBtnHandler() {
|
|
101
|
-
this.formConfig.title = "新增";
|
|
102
|
-
this.formConfig.type = 1;
|
|
103
|
-
this.formConfig.formData = {};
|
|
104
|
-
this.formConfig.dialogVisible = true;
|
|
105
|
-
}`);
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
if (config.hasEdit) {
|
|
109
|
-
methods.push(`
|
|
110
|
-
${config.hasDetail ? `async ` : ``}editBtnHandler() {
|
|
111
|
-
const data = this.multipleSelection;
|
|
112
|
-
if(data.length !== 1) return this.$message.info("请选择一条数据");
|
|
113
|
-
const row = data[0];
|
|
114
|
-
${
|
|
115
|
-
config.hasDetail
|
|
116
|
-
? `const { result = {} } = await ${detailUrlKey}(row.${config.rowId});
|
|
117
|
-
this.formConfig.formData = result || {};`
|
|
118
|
-
: `this.formConfig.formData = { ...row };`
|
|
119
|
-
}
|
|
120
|
-
this.formConfig.title = "编辑";
|
|
121
|
-
this.formConfig.type = 2;
|
|
122
|
-
this.formConfig.dialogVisible = true;
|
|
123
|
-
}`);
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
// 有详情
|
|
127
|
-
if (config.hasDetail) {
|
|
128
|
-
methods.push(`
|
|
129
|
-
async detailBtnHandler() {
|
|
130
|
-
const data = this.multipleSelection;
|
|
131
|
-
if(data.length !== 1) return this.$message.info("请选择一条数据");
|
|
132
|
-
const row = data[0];
|
|
133
|
-
const { result = {} } = await ${detailUrlKey}(row.${config.rowId});
|
|
134
|
-
this.formConfig.formData = result || {};
|
|
135
|
-
this.formConfig.title = "详情";
|
|
136
|
-
this.formConfig.type = 0;
|
|
137
|
-
this.formConfig.dialogVisible = true;
|
|
138
|
-
}`);
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
if (config.hasDelete) {
|
|
142
|
-
methods.push(`
|
|
143
|
-
deleteBtnHandler() {
|
|
144
|
-
const data = this.multipleSelection;
|
|
145
|
-
if(data.length !== 1) return this.$message.info("请选择一条数据");
|
|
146
|
-
const row = data[0];
|
|
147
|
-
this.$confirm('确认删除当前数据吗?', '提示', {
|
|
148
|
-
confirmButtonText: '确定',
|
|
149
|
-
cancelButtonText: '取消',
|
|
150
|
-
type: 'warning'
|
|
151
|
-
}).then(() => {
|
|
152
|
-
${deleteUrlKey}(row.${config.rowId}).then(() => {
|
|
153
|
-
this.$message.success('删除成功');
|
|
154
|
-
this.init();
|
|
155
|
-
}).catch(() => {
|
|
156
|
-
this.$message.error('删除失败');
|
|
157
|
-
});
|
|
158
|
-
}).catch(() => {});
|
|
159
|
-
}`);
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
if (config.hasExport) {
|
|
163
|
-
methods.push(`
|
|
164
|
-
export() {
|
|
165
|
-
this.post({
|
|
166
|
-
url: ${config.swaggerModule}.${exportUrlKey},
|
|
167
|
-
isLoading: true,
|
|
168
|
-
responseType: "blob",
|
|
169
|
-
data: Object.assign(this.formSearchData.value, {
|
|
170
|
-
Page: this.paginations.page,
|
|
171
|
-
MaxResultCount: this.paginations.limit
|
|
172
|
-
})
|
|
173
|
-
}).then(res => {
|
|
174
|
-
this.fnexsl(res);
|
|
175
|
-
});
|
|
176
|
-
}`);
|
|
177
|
-
}
|
|
178
|
-
return methods.join(",");
|
|
179
|
-
};
|
|
180
|
-
|
|
181
|
-
return `<!--
|
|
182
|
-
Filename: ${moduleName}.vue
|
|
183
|
-
name: ${moduleName}
|
|
184
|
-
Created Date: ${new Date().toLocaleString()}
|
|
185
|
-
Author:
|
|
186
|
-
-->
|
|
187
|
-
<template>
|
|
188
|
-
<div class="ol-container">
|
|
189
|
-
<ol-search
|
|
190
|
-
:url="swaggerUrl.${pageUrlKey}"
|
|
191
|
-
:form-search-data="formSearchData"
|
|
192
|
-
@handleSearch="handleSearch"
|
|
193
|
-
@handleReset="handleReset"
|
|
194
|
-
/>
|
|
195
|
-
<ol-table
|
|
196
|
-
:url="swaggerUrl.${pageUrlKey}"
|
|
197
|
-
:paginations="paginations"
|
|
198
|
-
:btnlist="this.hasBtn(this)"
|
|
199
|
-
:empty-img="tableData.emptyImg"
|
|
200
|
-
:table-data="tableData"
|
|
201
|
-
:multiple-selection="multipleSelection"
|
|
202
|
-
@SelectionChange="SelectionChange"
|
|
203
|
-
@handleSizeChange="handleSizeChange"
|
|
204
|
-
@handleindexChange="handleindexChange"
|
|
205
|
-
/>
|
|
206
|
-
${config.hasDialog ? ` <el-dialog :title="formConfig.title" :visible.sync="formConfig.dialogVisible" width="80%">
|
|
207
|
-
<FormModule
|
|
208
|
-
v-if="formConfig.dialogVisible"
|
|
209
|
-
:formData="formConfig.formData"
|
|
210
|
-
:type="formConfig.type"
|
|
211
|
-
@onCancel="onCancel"
|
|
212
|
-
@onSubmit="onSubmit"
|
|
213
|
-
/>
|
|
214
|
-
</el-dialog>
|
|
215
|
-
</div>` : "</div>"}
|
|
216
|
-
</template>
|
|
217
|
-
<script>
|
|
218
|
-
import { ${generateImports()} } from "@/api/modules";
|
|
219
|
-
import { ${config.swaggerModule} } from '@/api/swagger';
|
|
220
|
-
${config.hasDialog ? `import FormModule from "./components/formModule.vue"` : ""}
|
|
221
|
-
export default {
|
|
222
|
-
name: "${moduleName}",
|
|
223
|
-
${
|
|
224
|
-
config.hasDialog
|
|
225
|
-
? `components: {
|
|
226
|
-
FormModule
|
|
227
|
-
},`
|
|
228
|
-
: ""
|
|
229
|
-
}
|
|
230
|
-
data() {
|
|
231
|
-
return {
|
|
232
|
-
swaggerUrl: ${config.swaggerModule},
|
|
233
|
-
multipleSelection: [],
|
|
234
|
-
// 查询表单
|
|
235
|
-
formSearchData: {
|
|
236
|
-
reset: true, // 重置
|
|
237
|
-
expendShow: true, // 展开
|
|
238
|
-
value: {},
|
|
239
|
-
tableSearch: []
|
|
240
|
-
},
|
|
241
|
-
// 表格数据
|
|
242
|
-
tableData: {
|
|
243
|
-
loading: false,
|
|
244
|
-
emptyImg: true,
|
|
245
|
-
options: {
|
|
246
|
-
selection: true, // 多选框
|
|
247
|
-
index: null, // 序号
|
|
248
|
-
headTool: true, // 开启头部工具栏
|
|
249
|
-
refreshBtn: true, // 开启表格头部刷新按钮
|
|
250
|
-
downloadBtn: true // 开启表格头部下载按钮
|
|
251
|
-
}, // 序号和复选框
|
|
252
|
-
rows: [], // 表数据
|
|
253
|
-
columns: [],
|
|
254
|
-
operatesAttrs: {},
|
|
255
|
-
operates: [], // 表格里面的操作按钮
|
|
256
|
-
tableHeightDiff: 330
|
|
257
|
-
},
|
|
258
|
-
paginations: {
|
|
259
|
-
page: 1, // 当前位于那页面
|
|
260
|
-
total: 10, // 总数
|
|
261
|
-
limit: 30, // 一页显示多少条
|
|
262
|
-
pagetionShow: true
|
|
263
|
-
},
|
|
264
|
-
${
|
|
265
|
-
config.hasDialog
|
|
266
|
-
? `formConfig: {
|
|
267
|
-
type: 1,
|
|
268
|
-
formData: {},
|
|
269
|
-
title:"",
|
|
270
|
-
dialogVisible: false
|
|
271
|
-
}`
|
|
272
|
-
: ""
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
},
|
|
276
|
-
created() {
|
|
277
|
-
this.init()
|
|
278
|
-
},
|
|
279
|
-
methods: {
|
|
280
|
-
async init() {
|
|
281
|
-
const params = {
|
|
282
|
-
...this.formSearchData.value,
|
|
283
|
-
Page: this.paginations.page,
|
|
284
|
-
MaxResultCount: this.paginations.limit
|
|
285
|
-
};
|
|
286
|
-
const { result: { items = [], totalCount = 0 } = {} } = await ${pageUrlKey}(params, {
|
|
287
|
-
isLoading: true
|
|
288
|
-
});
|
|
289
|
-
this.tableData.rows = items;
|
|
290
|
-
this.paginations.total = totalCount;
|
|
291
|
-
this.tableData.emptyImg = true;
|
|
292
|
-
},
|
|
293
|
-
handleSearch(from) {
|
|
294
|
-
this.formSearchData.value = { ...from };
|
|
295
|
-
this.paginations.page = 1;
|
|
296
|
-
this.init();
|
|
297
|
-
},
|
|
298
|
-
handleReset() {
|
|
299
|
-
for (let key in this.formSearchData.value) {
|
|
300
|
-
this.formSearchData.value[key] = null;
|
|
301
|
-
}
|
|
302
|
-
this.paginations.page = 1;
|
|
303
|
-
},
|
|
304
|
-
SelectionChange(row) {
|
|
305
|
-
this.multipleSelection = row;
|
|
306
|
-
},
|
|
307
|
-
handleSizeChange(val) {
|
|
308
|
-
this.paginations.page = 1;
|
|
309
|
-
this.paginations.limit = val;
|
|
310
|
-
this.init();
|
|
311
|
-
},
|
|
312
|
-
handleindexChange(val) {
|
|
313
|
-
this.paginations.page = val;
|
|
314
|
-
this.init();
|
|
315
|
-
},${generateMethods()}
|
|
316
|
-
}
|
|
317
|
-
}
|
|
318
|
-
</script>
|
|
319
|
-
`;
|
|
320
|
-
};
|
|
321
|
-
|
|
322
|
-
const vue2Form = (moduleName, config = {}) => {
|
|
323
|
-
|
|
324
|
-
let editUrlKey = "",
|
|
325
|
-
detailUrlKey = "",
|
|
326
|
-
baseUrlKey = "";
|
|
327
|
-
|
|
328
|
-
if (config.detailUrl) {
|
|
329
|
-
detailUrlKey = generateKeyName(config.detailUrl, "get");
|
|
330
|
-
baseUrlKey = `${detailUrlKey}CompleteUrl`; //补充后缀
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
if (config.editUrl) {
|
|
334
|
-
editUrlKey = generateKeyName(config.editUrl, "put");
|
|
335
|
-
baseUrlKey = `${editUrlKey}CompleteUrl`; //补充后缀
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
if (config.addUrl) baseUrlKey = generateKeyName(config.addUrl, "post");
|
|
339
|
-
|
|
340
|
-
return `<!--
|
|
341
|
-
Filename: ${moduleName}.vue
|
|
342
|
-
name: ${moduleName}
|
|
343
|
-
Created Date: ${new Date().toLocaleString()}
|
|
344
|
-
Author:
|
|
345
|
-
-->
|
|
346
|
-
<template>
|
|
347
|
-
<ol-form
|
|
348
|
-
:url="swaggerUrl.${baseUrlKey}"
|
|
349
|
-
:form="form"
|
|
350
|
-
@onCancel="onCancel"
|
|
351
|
-
@onSubmit="onSubmit"
|
|
352
|
-
/>
|
|
353
|
-
</template>
|
|
354
|
-
<script>
|
|
355
|
-
import { ${config.swaggerModule} } from '@/api/swagger';
|
|
356
|
-
export default {
|
|
357
|
-
name: "${moduleName}Form",
|
|
358
|
-
props: {
|
|
359
|
-
formData: {
|
|
360
|
-
type: Object,
|
|
361
|
-
default: () => ({})
|
|
362
|
-
},
|
|
363
|
-
type: {
|
|
364
|
-
type: Number,
|
|
365
|
-
default: 1
|
|
366
|
-
}
|
|
367
|
-
},
|
|
368
|
-
data() {
|
|
369
|
-
return {
|
|
370
|
-
swaggerUrl: ${config.swaggerModule},
|
|
371
|
-
form: {
|
|
372
|
-
type: this.type, // 0详情,1新增, 2编辑
|
|
373
|
-
title: "",
|
|
374
|
-
defaultValue: {}, // 默认值
|
|
375
|
-
value: {},
|
|
376
|
-
model: [],
|
|
377
|
-
rules: {},
|
|
378
|
-
attrs: {},
|
|
379
|
-
}
|
|
380
|
-
}
|
|
381
|
-
},
|
|
382
|
-
watch: {
|
|
383
|
-
type: {
|
|
384
|
-
handler(val){
|
|
385
|
-
this.form.type = val;
|
|
386
|
-
},
|
|
387
|
-
immediate: true
|
|
388
|
-
}
|
|
389
|
-
},
|
|
390
|
-
created(){
|
|
391
|
-
if(this.type !== 1) this.form.value = { ...this.formData };
|
|
392
|
-
},
|
|
393
|
-
methods: {
|
|
394
|
-
onCancel() {
|
|
395
|
-
this.$emit("onCancel");
|
|
396
|
-
},
|
|
397
|
-
onSubmit({form, data}) {
|
|
398
|
-
this.$emit("onSubmit", { form, data });
|
|
399
|
-
}
|
|
400
|
-
}
|
|
401
|
-
}
|
|
402
|
-
</script>
|
|
403
|
-
`;
|
|
404
|
-
};
|
|
405
|
-
|
|
406
|
-
module.exports = {
|
|
407
|
-
vue2Template,
|
|
408
|
-
vue2Form,
|
|
409
|
-
};
|
package/src/bin/news.js
DELETED
|
@@ -1,171 +0,0 @@
|
|
|
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 && 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) && 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) && 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 && data.rss && data.rss.channel && data.rss.channel.item) {
|
|
113
|
-
items = Array.isArray(data.rss.channel.item) ? data.rss.channel.item : [data.rss.channel.item];
|
|
114
|
-
} else if (data && data.feed && 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 && data.articles) || (data && data.documents) || (data && 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
|
-
})();
|