@zdsj/pbms-cli 0.1.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/README.md +101 -0
- package/bin/pbms.js +40 -0
- package/package.json +34 -0
- package/src/api.js +57 -0
- package/src/commands/budget.js +20 -0
- package/src/commands/config.js +46 -0
- package/src/commands/dashboard.js +33 -0
- package/src/commands/external.js +21 -0
- package/src/commands/internal.js +20 -0
- package/src/commands/log.js +47 -0
- package/src/commands/resource.js +99 -0
- package/src/commands/sync.js +24 -0
- package/src/config.js +59 -0
- package/src/utils/format.js +61 -0
- package/src/utils/prompts.js +19 -0
package/README.md
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# PBMS CLI
|
|
2
|
+
|
|
3
|
+
项目预算管理系统命令行工具。
|
|
4
|
+
|
|
5
|
+
## 安装
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
cd cli
|
|
9
|
+
npm install
|
|
10
|
+
|
|
11
|
+
# 可选:全局链接,安装后可在任意目录使用 pbms 命令
|
|
12
|
+
npm link
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
不链接也可以直接运行:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
node bin/pbms.js --help
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## 配置
|
|
22
|
+
|
|
23
|
+
首次使用需要配置服务端地址和 API Key:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
pbms config set base-url https://your-host/api
|
|
27
|
+
pbms config set api-key sk_xxxxxxxx
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
查看配置:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
pbms config get
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
配置文件保存在 `~/.pbms/config.json`。
|
|
37
|
+
|
|
38
|
+
## 命令一览
|
|
39
|
+
|
|
40
|
+
### 仪表盘
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
pbms dashboard
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### 公司预算(GSYSK)
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
pbms budget list [--keyword <kw>] [--type <yslx>] [--dept <gsbm>] [--page <p>] [--size <s>]
|
|
50
|
+
pbms budget get <id>
|
|
51
|
+
pbms budget create --data '{"ysbh":"...","ysmc":"..."}'
|
|
52
|
+
pbms budget update <id> --data '{"ysmc":"..."}'
|
|
53
|
+
pbms budget delete <id>
|
|
54
|
+
pbms budget export [--output file.xlsx]
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### 内部项目(NBXMK)
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
pbms internal list [--keyword <kw>] [--phase <xmjd>] [--group <yfz>] [--page <p>] [--size <s>]
|
|
61
|
+
pbms internal get <id>
|
|
62
|
+
pbms internal create --data '{"xmbh":"...","xmmc":"..."}'
|
|
63
|
+
pbms internal update <id> --data '{"xmmc":"..."}'
|
|
64
|
+
pbms internal delete <id>
|
|
65
|
+
pbms internal export [--output file.xlsx]
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### 外部项目(WBXMK)
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
pbms external list [--keyword <kw>] [--phase <xmjd>] [--customerType <khlx>] [--sales <fzxs>] [--page <p>] [--size <s>]
|
|
72
|
+
pbms external get <id>
|
|
73
|
+
pbms external create --data '{"xmbh":"...","xmmc":"..."}'
|
|
74
|
+
pbms external update <id> --data '{"xmmc":"..."}'
|
|
75
|
+
pbms external delete <id>
|
|
76
|
+
pbms external export [--output file.xlsx]
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### 审计日志
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
pbms log list [--keyword <kw>] [--table <table>] [--action <action>] [--page <p>] [--size <s>]
|
|
83
|
+
pbms log get <id>
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### 预算同步
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
pbms sync
|
|
90
|
+
pbms sync --delayed
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## 开发
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
npm run start -- <command>
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## 依赖
|
|
100
|
+
|
|
101
|
+
- Node.js >= 18
|
package/bin/pbms.js
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { Command } from 'commander';
|
|
4
|
+
import chalk from 'chalk';
|
|
5
|
+
|
|
6
|
+
process.on('unhandledRejection', (err) => {
|
|
7
|
+
const message = err?.response?.data?.message || err?.message || '未知错误';
|
|
8
|
+
console.error(chalk.red(`错误: ${message}`));
|
|
9
|
+
process.exit(1);
|
|
10
|
+
});
|
|
11
|
+
import { registerConfigCommand } from '../src/commands/config.js';
|
|
12
|
+
import { registerDashboardCommand } from '../src/commands/dashboard.js';
|
|
13
|
+
import { registerBudgetCommand } from '../src/commands/budget.js';
|
|
14
|
+
import { registerInternalCommand } from '../src/commands/internal.js';
|
|
15
|
+
import { registerExternalCommand } from '../src/commands/external.js';
|
|
16
|
+
import { registerLogCommand } from '../src/commands/log.js';
|
|
17
|
+
import { registerSyncCommand } from '../src/commands/sync.js';
|
|
18
|
+
import { readFileSync } from 'node:fs';
|
|
19
|
+
import { fileURLToPath } from 'node:url';
|
|
20
|
+
import path from 'node:path';
|
|
21
|
+
|
|
22
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
23
|
+
const pkg = JSON.parse(readFileSync(path.join(__dirname, '../package.json'), 'utf8'));
|
|
24
|
+
|
|
25
|
+
const program = new Command();
|
|
26
|
+
|
|
27
|
+
program
|
|
28
|
+
.name('pbms')
|
|
29
|
+
.description('项目预算管理系统命令行工具')
|
|
30
|
+
.version(pkg.version);
|
|
31
|
+
|
|
32
|
+
registerConfigCommand(program);
|
|
33
|
+
registerDashboardCommand(program);
|
|
34
|
+
registerBudgetCommand(program);
|
|
35
|
+
registerInternalCommand(program);
|
|
36
|
+
registerExternalCommand(program);
|
|
37
|
+
registerLogCommand(program);
|
|
38
|
+
registerSyncCommand(program);
|
|
39
|
+
|
|
40
|
+
program.parse();
|
package/package.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@zdsj/pbms-cli",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "项目预算管理系统命令行工具",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"pbms": "./bin/pbms.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"start": "node bin/pbms.js",
|
|
11
|
+
"test": "node --test"
|
|
12
|
+
},
|
|
13
|
+
"keywords": [
|
|
14
|
+
"cli",
|
|
15
|
+
"budget",
|
|
16
|
+
"management"
|
|
17
|
+
],
|
|
18
|
+
"author": "",
|
|
19
|
+
"license": "MIT",
|
|
20
|
+
"publishConfig": {
|
|
21
|
+
"access": "public"
|
|
22
|
+
},
|
|
23
|
+
"engines": {
|
|
24
|
+
"node": ">=18.0.0"
|
|
25
|
+
},
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"@inquirer/prompts": "^7.0.0",
|
|
28
|
+
"axios": "^1.7.9",
|
|
29
|
+
"chalk": "^5.4.1",
|
|
30
|
+
"cli-table3": "^0.6.5",
|
|
31
|
+
"commander": "^13.0.0",
|
|
32
|
+
"ora": "^8.1.1"
|
|
33
|
+
}
|
|
34
|
+
}
|
package/src/api.js
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import axios from 'axios';
|
|
2
|
+
import { loadConfig } from './config.js';
|
|
3
|
+
import chalk from 'chalk';
|
|
4
|
+
|
|
5
|
+
let client = null;
|
|
6
|
+
|
|
7
|
+
export function getClient() {
|
|
8
|
+
if (client) return client;
|
|
9
|
+
|
|
10
|
+
const config = loadConfig();
|
|
11
|
+
client = axios.create({
|
|
12
|
+
baseURL: config.baseUrl.replace(/\/$/, ''),
|
|
13
|
+
timeout: 30000,
|
|
14
|
+
headers: {
|
|
15
|
+
'Content-Type': 'application/json',
|
|
16
|
+
},
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
client.interceptors.request.use((req) => {
|
|
20
|
+
if (config.apiKey) {
|
|
21
|
+
req.headers['X-API-Key'] = config.apiKey;
|
|
22
|
+
}
|
|
23
|
+
return req;
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
client.interceptors.response.use(
|
|
27
|
+
(res) => res,
|
|
28
|
+
(err) => {
|
|
29
|
+
if (err.response) {
|
|
30
|
+
const { status, data } = err.response;
|
|
31
|
+
const message = data?.message || data?.error || `HTTP ${status}`;
|
|
32
|
+
if (status === 401) {
|
|
33
|
+
console.error(chalk.red('鉴权失败:请检查 base-url 和 api-key 是否配置正确'));
|
|
34
|
+
} else {
|
|
35
|
+
console.error(chalk.red(`请求失败: ${message}`));
|
|
36
|
+
}
|
|
37
|
+
} else if (err.request) {
|
|
38
|
+
console.error(chalk.red('无法连接到服务器,请检查 base-url 配置'));
|
|
39
|
+
} else {
|
|
40
|
+
console.error(chalk.red(`请求异常: ${err.message}`));
|
|
41
|
+
}
|
|
42
|
+
throw err;
|
|
43
|
+
}
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
return client;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function resetClient() {
|
|
50
|
+
client = null;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export async function request(method, url, options = {}) {
|
|
54
|
+
const c = getClient();
|
|
55
|
+
const res = await c.request({ method, url, ...options });
|
|
56
|
+
return res.data;
|
|
57
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { createResourceCommands } from './resource.js';
|
|
2
|
+
|
|
3
|
+
export function registerBudgetCommand(program) {
|
|
4
|
+
createResourceCommands(program, 'budget', '/gsysk', {
|
|
5
|
+
idField: 'id',
|
|
6
|
+
columns: [
|
|
7
|
+
{ field: 'id', title: 'ID' },
|
|
8
|
+
{ field: 'ysbh', title: '预算编号' },
|
|
9
|
+
{ field: 'ysmc', title: '预算名称' },
|
|
10
|
+
{ field: 'yslx', title: '预算类型' },
|
|
11
|
+
{ field: 'ysje', title: '预算金额' },
|
|
12
|
+
{ field: 'yyje', title: '已用金额' },
|
|
13
|
+
{ field: 'gsbm', title: '归属部门' },
|
|
14
|
+
],
|
|
15
|
+
searchParams: [
|
|
16
|
+
{ name: 'type', param: 'yslx', description: '预算类型' },
|
|
17
|
+
{ name: 'dept', param: 'gsbm', description: '归属部门' },
|
|
18
|
+
],
|
|
19
|
+
});
|
|
20
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { loadConfig, saveConfig, getConfigPath } from '../config.js';
|
|
2
|
+
import { Command } from 'commander';
|
|
3
|
+
import chalk from 'chalk';
|
|
4
|
+
|
|
5
|
+
export function registerConfigCommand(program) {
|
|
6
|
+
const configCmd = program.command('config').description('CLI 配置管理');
|
|
7
|
+
|
|
8
|
+
configCmd
|
|
9
|
+
.command('set <key> <value>')
|
|
10
|
+
.description('设置配置项(base-url, api-key)')
|
|
11
|
+
.action(async (key, value) => {
|
|
12
|
+
const validKeys = ['base-url', 'api-key'];
|
|
13
|
+
if (!validKeys.includes(key)) {
|
|
14
|
+
console.error(chalk.red(`未知配置项: ${key}。有效项: ${validKeys.join(', ')}`));
|
|
15
|
+
process.exit(1);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const config = loadConfig();
|
|
19
|
+
if (key === 'base-url') {
|
|
20
|
+
config.baseUrl = value;
|
|
21
|
+
} else if (key === 'api-key') {
|
|
22
|
+
config.apiKey = value;
|
|
23
|
+
}
|
|
24
|
+
saveConfig(config);
|
|
25
|
+
console.log(chalk.green(`已设置 ${key}`));
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
configCmd
|
|
29
|
+
.command('get [key]')
|
|
30
|
+
.description('查看配置项')
|
|
31
|
+
.action(async (key) => {
|
|
32
|
+
const config = loadConfig();
|
|
33
|
+
if (key) {
|
|
34
|
+
const displayKey = key === 'base-url' ? 'baseUrl' : key === 'api-key' ? 'apiKey' : key;
|
|
35
|
+
if (displayKey === 'apiKey') {
|
|
36
|
+
console.log(config[displayKey] ? '***已设置***' : '(未设置)');
|
|
37
|
+
} else {
|
|
38
|
+
console.log(config[displayKey] ?? '');
|
|
39
|
+
}
|
|
40
|
+
} else {
|
|
41
|
+
console.log('配置文件路径:', getConfigPath());
|
|
42
|
+
console.log('base-url:', config.baseUrl);
|
|
43
|
+
console.log('api-key:', config.apiKey ? '***已设置***' : '(未设置)');
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { request } from '../api.js';
|
|
2
|
+
import { formatApiResponse } from '../utils/format.js';
|
|
3
|
+
import Table from 'cli-table3';
|
|
4
|
+
import chalk from 'chalk';
|
|
5
|
+
|
|
6
|
+
export function registerDashboardCommand(program) {
|
|
7
|
+
program
|
|
8
|
+
.command('dashboard')
|
|
9
|
+
.description('查看仪表盘统计数据')
|
|
10
|
+
.action(async () => {
|
|
11
|
+
const res = await request('GET', '/dashboard');
|
|
12
|
+
const data = formatApiResponse(res);
|
|
13
|
+
|
|
14
|
+
const table = new Table({
|
|
15
|
+
head: [chalk.cyan('指标'), chalk.cyan('数值')],
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
const rows = [
|
|
19
|
+
['内部项目数', data.internalProjectCount ?? '-'],
|
|
20
|
+
['外部项目数', data.externalProjectCount ?? '-'],
|
|
21
|
+
['预算条数', data.budgetCount ?? '-'],
|
|
22
|
+
['预算总额', data.totalBudget ?? '-'],
|
|
23
|
+
['已用额度', data.usedBudget ?? '-'],
|
|
24
|
+
['余额', data.balance ?? '-'],
|
|
25
|
+
];
|
|
26
|
+
|
|
27
|
+
for (const row of rows) {
|
|
28
|
+
table.push(row);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
console.log(table.toString());
|
|
32
|
+
});
|
|
33
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { createResourceCommands } from './resource.js';
|
|
2
|
+
|
|
3
|
+
export function registerExternalCommand(program) {
|
|
4
|
+
createResourceCommands(program, 'external', '/wbxmk', {
|
|
5
|
+
idField: 'id',
|
|
6
|
+
columns: [
|
|
7
|
+
{ field: 'id', title: 'ID' },
|
|
8
|
+
{ field: 'xmbh', title: '项目编号' },
|
|
9
|
+
{ field: 'xmmc', title: '项目名称' },
|
|
10
|
+
{ field: 'xmys', title: '项目预算' },
|
|
11
|
+
{ field: 'syys', title: '使用预算' },
|
|
12
|
+
{ field: 'xmjd', title: '项目阶段' },
|
|
13
|
+
{ field: 'khmc', title: '客户名称' },
|
|
14
|
+
],
|
|
15
|
+
searchParams: [
|
|
16
|
+
{ name: 'phase', param: 'xmjd', description: '项目阶段' },
|
|
17
|
+
{ name: 'customerType', param: 'khlx', description: '客户类型' },
|
|
18
|
+
{ name: 'sales', param: 'fzxs', description: '负责销售' },
|
|
19
|
+
],
|
|
20
|
+
});
|
|
21
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { createResourceCommands } from './resource.js';
|
|
2
|
+
|
|
3
|
+
export function registerInternalCommand(program) {
|
|
4
|
+
createResourceCommands(program, 'internal', '/nbxmk', {
|
|
5
|
+
idField: 'id',
|
|
6
|
+
columns: [
|
|
7
|
+
{ field: 'id', title: 'ID' },
|
|
8
|
+
{ field: 'xmbh', title: '项目编号' },
|
|
9
|
+
{ field: 'xmmc', title: '项目名称' },
|
|
10
|
+
{ field: 'xmys', title: '项目预算' },
|
|
11
|
+
{ field: 'syys', title: '使用预算' },
|
|
12
|
+
{ field: 'xmjd', title: '项目阶段' },
|
|
13
|
+
{ field: 'yfz', title: '研发组' },
|
|
14
|
+
],
|
|
15
|
+
searchParams: [
|
|
16
|
+
{ name: 'phase', param: 'xmjd', description: '项目阶段' },
|
|
17
|
+
{ name: 'group', param: 'yfz', description: '研发组' },
|
|
18
|
+
],
|
|
19
|
+
});
|
|
20
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { request } from '../api.js';
|
|
2
|
+
import { printTable, printJson, printPageInfo, extractList, formatApiResponse } from '../utils/format.js';
|
|
3
|
+
|
|
4
|
+
export function registerLogCommand(program) {
|
|
5
|
+
const logCmd = program.command('log').description('审计日志查询');
|
|
6
|
+
|
|
7
|
+
logCmd
|
|
8
|
+
.command('list')
|
|
9
|
+
.description('查询审计日志列表')
|
|
10
|
+
.option('-k, --keyword <keyword>', '关键词')
|
|
11
|
+
.option('-t, --table <table>', '操作的表名')
|
|
12
|
+
.option('-a, --action <action>', '操作类型')
|
|
13
|
+
.option('-p, --page <page>', '页码', '0')
|
|
14
|
+
.option('-s, --size <size>', '每页大小', '10')
|
|
15
|
+
.action(async (opts) => {
|
|
16
|
+
const params = new URLSearchParams();
|
|
17
|
+
if (opts.keyword) params.set('keyword', opts.keyword);
|
|
18
|
+
if (opts.table) params.set('operTable', opts.table);
|
|
19
|
+
if (opts.action) params.set('operAction', opts.action);
|
|
20
|
+
params.set('page', opts.page);
|
|
21
|
+
params.set('size', opts.size);
|
|
22
|
+
|
|
23
|
+
const res = await request('GET', `/oper-log?${params.toString()}`);
|
|
24
|
+
const data = formatApiResponse(res);
|
|
25
|
+
const { list, page } = extractList(data);
|
|
26
|
+
|
|
27
|
+
const columns = [
|
|
28
|
+
{ field: 'id', title: 'ID' },
|
|
29
|
+
{ field: 'operType', title: '操作类型' },
|
|
30
|
+
{ field: 'operTable', title: '操作表' },
|
|
31
|
+
{ field: 'operUser', title: '操作人' },
|
|
32
|
+
{ field: 'operAction', title: '操作动作' },
|
|
33
|
+
{ field: 'operTime', title: '操作时间' },
|
|
34
|
+
];
|
|
35
|
+
const rows = list.map((item) => columns.map((col) => item[col.field] ?? ''));
|
|
36
|
+
printTable(columns.map((col) => col.title), rows);
|
|
37
|
+
printPageInfo(page);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
logCmd
|
|
41
|
+
.command('get <id>')
|
|
42
|
+
.description('查询审计日志详情')
|
|
43
|
+
.action(async (id) => {
|
|
44
|
+
const res = await request('GET', `/oper-log/${id}`);
|
|
45
|
+
printJson(formatApiResponse(res));
|
|
46
|
+
});
|
|
47
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { request } from '../api.js';
|
|
2
|
+
import { printTable, printJson, printPageInfo, extractList, formatApiResponse } from '../utils/format.js';
|
|
3
|
+
import chalk from 'chalk';
|
|
4
|
+
|
|
5
|
+
export function createResourceCommands(program, name, endpoint, options = {}) {
|
|
6
|
+
const { idField = 'id', columns = [], searchParams = [] } = options;
|
|
7
|
+
|
|
8
|
+
const parentCmd = program.command(name).description(`${name} 资源管理`);
|
|
9
|
+
|
|
10
|
+
const listCmd = parentCmd
|
|
11
|
+
.command('list')
|
|
12
|
+
.description('查询列表')
|
|
13
|
+
.option('-k, --keyword <keyword>', '关键词')
|
|
14
|
+
.option('-p, --page <page>', '页码', '0')
|
|
15
|
+
.option('-s, --size <size>', '每页大小', '10');
|
|
16
|
+
|
|
17
|
+
for (const sp of searchParams) {
|
|
18
|
+
listCmd.option(`--${sp.name} <value>`, sp.description || sp.param);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
listCmd.action(async (opts) => {
|
|
22
|
+
const params = new URLSearchParams();
|
|
23
|
+
if (opts.keyword) params.set('keyword', opts.keyword);
|
|
24
|
+
for (const sp of searchParams) {
|
|
25
|
+
if (opts[sp.name]) params.set(sp.param, opts[sp.name]);
|
|
26
|
+
}
|
|
27
|
+
params.set('page', opts.page);
|
|
28
|
+
params.set('size', opts.size);
|
|
29
|
+
|
|
30
|
+
const res = await request('GET', `${endpoint}?${params.toString()}`);
|
|
31
|
+
const data = formatApiResponse(res);
|
|
32
|
+
const { list, page } = extractList(data);
|
|
33
|
+
|
|
34
|
+
const rows = list.map((item) => columns.map((col) => item[col.field] ?? ''));
|
|
35
|
+
printTable(columns.map((col) => col.title), rows);
|
|
36
|
+
printPageInfo(page);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
parentCmd
|
|
40
|
+
.command('get <id>')
|
|
41
|
+
.description('查询详情')
|
|
42
|
+
.action(async (id) => {
|
|
43
|
+
const res = await request('GET', `${endpoint}/${id}`);
|
|
44
|
+
printJson(formatApiResponse(res));
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
parentCmd
|
|
48
|
+
.command('create')
|
|
49
|
+
.description('新增')
|
|
50
|
+
.option('-d, --data <json>', 'JSON 格式数据')
|
|
51
|
+
.action(async (opts) => {
|
|
52
|
+
let body;
|
|
53
|
+
if (opts.data) {
|
|
54
|
+
body = JSON.parse(opts.data);
|
|
55
|
+
} else {
|
|
56
|
+
console.error(chalk.red('请使用 --data 传入 JSON 数据'));
|
|
57
|
+
process.exit(1);
|
|
58
|
+
}
|
|
59
|
+
const res = await request('POST', endpoint, { data: body });
|
|
60
|
+
printJson(formatApiResponse(res));
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
parentCmd
|
|
64
|
+
.command('update <id>')
|
|
65
|
+
.description('更新')
|
|
66
|
+
.option('-d, --data <json>', 'JSON 格式数据')
|
|
67
|
+
.action(async (id, opts) => {
|
|
68
|
+
let body;
|
|
69
|
+
if (opts.data) {
|
|
70
|
+
body = JSON.parse(opts.data);
|
|
71
|
+
} else {
|
|
72
|
+
console.error(chalk.red('请使用 --data 传入 JSON 数据'));
|
|
73
|
+
process.exit(1);
|
|
74
|
+
}
|
|
75
|
+
const res = await request('PUT', `${endpoint}/${id}`, { data: body });
|
|
76
|
+
printJson(formatApiResponse(res));
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
parentCmd
|
|
80
|
+
.command('delete <id>')
|
|
81
|
+
.description('删除')
|
|
82
|
+
.action(async (id) => {
|
|
83
|
+
const res = await request('DELETE', `${endpoint}/${id}`);
|
|
84
|
+
printJson(formatApiResponse(res));
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
parentCmd
|
|
88
|
+
.command('export')
|
|
89
|
+
.description('导出 Excel')
|
|
90
|
+
.option('-o, --output <file>', '输出文件路径')
|
|
91
|
+
.action(async (opts) => {
|
|
92
|
+
const c = (await import('../api.js')).getClient();
|
|
93
|
+
const res = await c.get(`${endpoint}/export`, { responseType: 'arraybuffer' });
|
|
94
|
+
const output = opts.output || `${name}-export.xlsx`;
|
|
95
|
+
const fs = await import('node:fs');
|
|
96
|
+
fs.writeFileSync(output, Buffer.from(res.data));
|
|
97
|
+
console.log(chalk.green(`已导出到: ${output}`));
|
|
98
|
+
});
|
|
99
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { request } from '../api.js';
|
|
2
|
+
import { formatApiResponse, printJson } from '../utils/format.js';
|
|
3
|
+
import ora from 'ora';
|
|
4
|
+
import chalk from 'chalk';
|
|
5
|
+
|
|
6
|
+
export function registerSyncCommand(program) {
|
|
7
|
+
program
|
|
8
|
+
.command('sync')
|
|
9
|
+
.description('同步低代码流程金额到预算科目')
|
|
10
|
+
.option('--delayed', '延迟异步执行')
|
|
11
|
+
.action(async (opts) => {
|
|
12
|
+
const url = opts.delayed ? '/sync/gsysk/flow-amounts/delayed' : '/sync/gsysk/flow-amounts';
|
|
13
|
+
const spinner = ora('正在同步...').start();
|
|
14
|
+
try {
|
|
15
|
+
const res = await request('POST', url);
|
|
16
|
+
spinner.stop();
|
|
17
|
+
printJson(formatApiResponse(res));
|
|
18
|
+
} catch (err) {
|
|
19
|
+
spinner.stop();
|
|
20
|
+
console.error(chalk.red('同步失败'));
|
|
21
|
+
throw err;
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
}
|
package/src/config.js
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
|
|
5
|
+
const CONFIG_DIR = path.join(os.homedir(), '.pbms');
|
|
6
|
+
const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
|
|
7
|
+
|
|
8
|
+
const defaultConfig = {
|
|
9
|
+
baseUrl: 'http://localhost:17342/api',
|
|
10
|
+
apiKey: '',
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
function ensureConfigDir() {
|
|
14
|
+
if (!fs.existsSync(CONFIG_DIR)) {
|
|
15
|
+
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function loadConfig() {
|
|
20
|
+
ensureConfigDir();
|
|
21
|
+
if (!fs.existsSync(CONFIG_FILE)) {
|
|
22
|
+
return { ...defaultConfig };
|
|
23
|
+
}
|
|
24
|
+
try {
|
|
25
|
+
const raw = fs.readFileSync(CONFIG_FILE, 'utf8');
|
|
26
|
+
const parsed = JSON.parse(raw);
|
|
27
|
+
return { ...defaultConfig, ...parsed };
|
|
28
|
+
} catch (err) {
|
|
29
|
+
console.error('读取配置文件失败:', err.message);
|
|
30
|
+
return { ...defaultConfig };
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function saveConfig(config) {
|
|
35
|
+
ensureConfigDir();
|
|
36
|
+
fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2), { mode: 0o600 });
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function setConfigValue(key, value) {
|
|
40
|
+
const config = loadConfig();
|
|
41
|
+
if (!Object.prototype.hasOwnProperty.call(defaultConfig, key)) {
|
|
42
|
+
throw new Error(`未知配置项: ${key}。有效项: ${Object.keys(defaultConfig).join(', ')}`);
|
|
43
|
+
}
|
|
44
|
+
config[key] = value;
|
|
45
|
+
saveConfig(config);
|
|
46
|
+
return config;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function getConfigValue(key) {
|
|
50
|
+
const config = loadConfig();
|
|
51
|
+
if (key) {
|
|
52
|
+
return config[key];
|
|
53
|
+
}
|
|
54
|
+
return config;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function getConfigPath() {
|
|
58
|
+
return CONFIG_FILE;
|
|
59
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import Table from 'cli-table3';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
|
|
4
|
+
export function printJson(data) {
|
|
5
|
+
console.log(JSON.stringify(data, null, 2));
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function printTable(headers, rows) {
|
|
9
|
+
if (!rows || rows.length === 0) {
|
|
10
|
+
console.log(chalk.yellow('暂无数据'));
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const table = new Table({
|
|
15
|
+
head: headers.map((h) => chalk.cyan(h)),
|
|
16
|
+
wordWrap: true,
|
|
17
|
+
truncate: '...',
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
for (const row of rows) {
|
|
21
|
+
table.push(row);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
console.log(table.toString());
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function printPageInfo(page) {
|
|
28
|
+
if (!page) return;
|
|
29
|
+
const { number, size, totalElements, totalPages } = page;
|
|
30
|
+
console.log(
|
|
31
|
+
chalk.gray(
|
|
32
|
+
`第 ${number + 1} 页 / 共 ${totalPages} 页,每页 ${size} 条,总计 ${totalElements} 条`
|
|
33
|
+
)
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function formatApiResponse(data) {
|
|
38
|
+
if (data && typeof data === 'object' && 'code' in data) {
|
|
39
|
+
if (data.code !== 200) {
|
|
40
|
+
throw new Error(data.message || '接口返回错误');
|
|
41
|
+
}
|
|
42
|
+
return data.data;
|
|
43
|
+
}
|
|
44
|
+
return data;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function extractList(response) {
|
|
48
|
+
const data = formatApiResponse(response);
|
|
49
|
+
if (data && Array.isArray(data.content)) {
|
|
50
|
+
return {
|
|
51
|
+
list: data.content,
|
|
52
|
+
page: {
|
|
53
|
+
number: data.number ?? 0,
|
|
54
|
+
size: data.size ?? 0,
|
|
55
|
+
totalElements: data.totalElements ?? 0,
|
|
56
|
+
totalPages: data.totalPages ?? 0,
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
return { list: Array.isArray(data) ? data : [], page: null };
|
|
61
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { input, confirm } from '@inquirer/prompts';
|
|
2
|
+
|
|
3
|
+
export async function promptFor(fields) {
|
|
4
|
+
const result = {};
|
|
5
|
+
for (const field of fields) {
|
|
6
|
+
const value = await input({
|
|
7
|
+
message: field.message,
|
|
8
|
+
default: field.default || '',
|
|
9
|
+
});
|
|
10
|
+
if (value) {
|
|
11
|
+
result[field.name] = value;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
return result;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export async function confirmAction(message) {
|
|
18
|
+
return confirm({ message, default: false });
|
|
19
|
+
}
|