@dalehkx/quote-cli 0.2.0 → 0.2.1
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 +1 -1
- package/src/commands/inquiry.mjs +42 -11
- package/src/commands/login.mjs +56 -46
package/package.json
CHANGED
package/src/commands/inquiry.mjs
CHANGED
|
@@ -123,11 +123,19 @@ export function registerInquiryCommands(program) {
|
|
|
123
123
|
.description('监听询价单报价状态(有新报价时提醒)')
|
|
124
124
|
.argument('<id>', '询价单 ID')
|
|
125
125
|
.option('-i, --interval <seconds>', '轮询间隔(秒)', '30')
|
|
126
|
+
.option('--timeout <seconds>', '最长等待时间(秒),超时后退出,0=不限制', '0')
|
|
127
|
+
.option('--exit-on-quotes <n>', '收到 n 条报价后自动退出', '0')
|
|
128
|
+
.option('--json', '以 JSON 格式输出结果(适合脚本/AI 调用)')
|
|
126
129
|
.action(async (id, opts) => {
|
|
127
|
-
const adapter
|
|
128
|
-
const interval
|
|
129
|
-
|
|
130
|
-
|
|
130
|
+
const adapter = getAdapter();
|
|
131
|
+
const interval = Math.max(10, parseInt(opts.interval, 10)) * 1000;
|
|
132
|
+
const timeout = parseInt(opts.timeout, 10) * 1000;
|
|
133
|
+
const exitOnQuotes = parseInt(opts.exitOnQuotes, 10);
|
|
134
|
+
const startTime = Date.now();
|
|
135
|
+
|
|
136
|
+
if (!opts.json) {
|
|
137
|
+
console.log(`监听询价单 ${id},每 ${interval / 1000} 秒检查一次,Ctrl+C 退出\n`);
|
|
138
|
+
}
|
|
131
139
|
|
|
132
140
|
let lastQuoteCount = 0;
|
|
133
141
|
let lastStatus = '';
|
|
@@ -139,20 +147,43 @@ export function registerInquiryCommands(program) {
|
|
|
139
147
|
const count = replies.length;
|
|
140
148
|
const time = new Date().toLocaleTimeString();
|
|
141
149
|
|
|
150
|
+
// 超时退出
|
|
151
|
+
if (timeout > 0 && Date.now() - startTime >= timeout) {
|
|
152
|
+
if (opts.json) {
|
|
153
|
+
console.log(JSON.stringify({ inquiryId: id, status: record.status, quotes: replies, timedOut: true }));
|
|
154
|
+
} else {
|
|
155
|
+
console.log(`\n[${time}] 等待超时,共 ${count} 条报价`);
|
|
156
|
+
}
|
|
157
|
+
process.exit(0);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// 达到目标报价数退出
|
|
161
|
+
if (exitOnQuotes > 0 && count >= exitOnQuotes) {
|
|
162
|
+
if (opts.json) {
|
|
163
|
+
console.log(JSON.stringify({ inquiryId: id, status: record.status, quotes: replies }));
|
|
164
|
+
} else {
|
|
165
|
+
console.log(`\n[${time}] 已收到 ${count} 条报价,退出监听`);
|
|
166
|
+
replies.forEach(r => console.log(` ${r.supplier} | ¥${r.price} | ${r.brand || '-'} | ${r.delivery ? r.delivery + '天' : '货期未知'}`));
|
|
167
|
+
}
|
|
168
|
+
process.exit(0);
|
|
169
|
+
}
|
|
170
|
+
|
|
142
171
|
if (count > lastQuoteCount) {
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
172
|
+
if (!opts.json) {
|
|
173
|
+
console.log(`\n[${time}] 🔔 新报价!共 ${count} 条`);
|
|
174
|
+
replies.slice(lastQuoteCount).forEach(r => {
|
|
175
|
+
console.log(` ${r.supplier} | ¥${r.price} | ${r.brand || '-'} | ${r.delivery ? r.delivery + '天' : '货期未知'}`);
|
|
176
|
+
});
|
|
177
|
+
}
|
|
147
178
|
lastQuoteCount = count;
|
|
148
179
|
} else if (record.status !== lastStatus) {
|
|
149
|
-
console.log(`[${time}] 状态变更: ${lastStatus || '-'} → ${record.status} 报价: ${count} 条`);
|
|
180
|
+
if (!opts.json) console.log(`[${time}] 状态变更: ${lastStatus || '-'} → ${record.status} 报价: ${count} 条`);
|
|
150
181
|
lastStatus = record.status;
|
|
151
182
|
} else {
|
|
152
|
-
process.stdout.write(`\r[${time}] 等待报价... 当前 ${count} 条`);
|
|
183
|
+
if (!opts.json) process.stdout.write(`\r[${time}] 等待报价... 当前 ${count} 条`);
|
|
153
184
|
}
|
|
154
185
|
} catch (e) {
|
|
155
|
-
console.error(`\n[错误] ${e.message}`);
|
|
186
|
+
if (!opts.json) console.error(`\n[错误] ${e.message}`);
|
|
156
187
|
}
|
|
157
188
|
};
|
|
158
189
|
|
package/src/commands/login.mjs
CHANGED
|
@@ -28,7 +28,6 @@ export function registerLoginCommands(program) {
|
|
|
28
28
|
try {
|
|
29
29
|
let tokenData;
|
|
30
30
|
|
|
31
|
-
// --test 强制走短信登录,手机号固定为测试账号
|
|
32
31
|
if (opts.test || opts.sms) {
|
|
33
32
|
const cellphone = opts.test ? TEST_PHONE : (opts.username || await ask('手机号: '));
|
|
34
33
|
|
|
@@ -56,10 +55,8 @@ export function registerLoginCommands(program) {
|
|
|
56
55
|
|
|
57
56
|
tokenData = await loginWithCellphone(apiBase, cellphone, verifyCode);
|
|
58
57
|
} else {
|
|
59
|
-
// 账号密码登录
|
|
60
58
|
const username = opts.username || await ask('账号: ');
|
|
61
59
|
|
|
62
|
-
// 检查账号是否存在
|
|
63
60
|
process.stdout.write('正在检查账号...');
|
|
64
61
|
const exists = await checkAccountExists(apiBase, username);
|
|
65
62
|
if (!exists) {
|
|
@@ -93,12 +90,32 @@ export function registerLoginCommands(program) {
|
|
|
93
90
|
.option('-u, --username <name>', '用户名')
|
|
94
91
|
.option('--phone <phone>', '手机号')
|
|
95
92
|
.option('-p, --password <pwd>', '密码')
|
|
93
|
+
.option('--code <code>', '验证码(非交互:先用 --send-code 发送)')
|
|
94
|
+
.option('--send-code', '仅发送注册验证码,不完成注册')
|
|
95
|
+
.option('--company <name>', '公司/门店名称')
|
|
96
|
+
.option('--province-id <id>', '省级 geoId(如 CN-11)')
|
|
97
|
+
.option('--province-name <name>', '省名称(如 北京市)')
|
|
98
|
+
.option('--city-id <id>', '市级 geoId(如 1351)')
|
|
99
|
+
.option('--city-name <name>', '市名称')
|
|
100
|
+
.option('--county-id <id>', '区县 geoId(如 9629)')
|
|
101
|
+
.option('--county-name <name>', '区县名称')
|
|
96
102
|
.option('--api-base <url>', 'API 地址', API_BASE_DEFAULT)
|
|
97
103
|
.action(async (opts) => {
|
|
98
104
|
const store = new Store();
|
|
99
105
|
const config = store.getConfig();
|
|
100
106
|
const apiBase = opts.apiBase || config.apiBase || API_BASE_DEFAULT;
|
|
101
107
|
|
|
108
|
+
// 仅发送验证码模式
|
|
109
|
+
if (opts.sendCode) {
|
|
110
|
+
if (!opts.phone) { console.error('✗ 请提供 --phone <手机号>'); process.exit(1); }
|
|
111
|
+
process.stdout.write('正在发送验证码...');
|
|
112
|
+
await sendRegisterCode(apiBase, opts.phone);
|
|
113
|
+
console.log(' 已发送,请查收短信后带 --code 完成注册');
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const isNonInteractive = opts.phone && opts.password && opts.username && opts.code;
|
|
118
|
+
|
|
102
119
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
103
120
|
const ask = (q) => new Promise((resolve) => rl.question(q, resolve));
|
|
104
121
|
const askHidden = (q) => new Promise((resolve) => {
|
|
@@ -112,14 +129,9 @@ export function registerLoginCommands(program) {
|
|
|
112
129
|
process.stdin.removeListener('data', onData);
|
|
113
130
|
process.stdout.write('\n');
|
|
114
131
|
resolve(input);
|
|
115
|
-
} else if (ch === '') {
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
input = input.slice(0, -1);
|
|
119
|
-
} else {
|
|
120
|
-
input += ch;
|
|
121
|
-
process.stdout.write('*');
|
|
122
|
-
}
|
|
132
|
+
} else if (ch === '') { process.exit(); }
|
|
133
|
+
else if (ch === '') { input = input.slice(0, -1); }
|
|
134
|
+
else { input += ch; process.stdout.write('*'); }
|
|
123
135
|
};
|
|
124
136
|
process.stdin.resume();
|
|
125
137
|
process.stdin.setEncoding('utf8');
|
|
@@ -129,68 +141,69 @@ export function registerLoginCommands(program) {
|
|
|
129
141
|
try {
|
|
130
142
|
const cellphone = opts.phone || await ask('手机号: ');
|
|
131
143
|
|
|
132
|
-
// 检查是否已注册
|
|
133
144
|
process.stdout.write('正在检查账号...');
|
|
134
145
|
const exists = await checkAccountExists(apiBase, cellphone);
|
|
146
|
+
console.log(exists ? ' 已注册' : ' 未注册');
|
|
135
147
|
if (exists) {
|
|
136
|
-
console.
|
|
137
|
-
|
|
138
|
-
rl.close();
|
|
139
|
-
process.exit(1);
|
|
148
|
+
console.error('✗ 该手机号已注册,请直接登录: quote login');
|
|
149
|
+
rl.close(); process.exit(1);
|
|
140
150
|
}
|
|
141
|
-
console.log(' 未注册');
|
|
142
151
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
152
|
+
if (!isNonInteractive) {
|
|
153
|
+
process.stdout.write('正在发送验证码...');
|
|
154
|
+
await sendRegisterCode(apiBase, cellphone);
|
|
155
|
+
console.log(' 已发送');
|
|
156
|
+
}
|
|
147
157
|
|
|
148
|
-
const verifyCode
|
|
149
|
-
const username
|
|
150
|
-
const password
|
|
158
|
+
const verifyCode = opts.code || await ask('验证码: ');
|
|
159
|
+
const username = opts.username || await ask('用户名: ');
|
|
160
|
+
const password = opts.password || await askHidden('密码: ');
|
|
151
161
|
|
|
152
|
-
rl.close();
|
|
162
|
+
if (!isNonInteractive) rl.close();
|
|
153
163
|
|
|
154
164
|
await registerUser(apiBase, { cellphone, password, username, verificationCode: verifyCode });
|
|
155
|
-
|
|
156
165
|
console.log('✓ 注册成功,正在自动登录...');
|
|
157
166
|
|
|
158
167
|
store.setConfig({ apiBase, mode: 'api' });
|
|
159
168
|
const tokenData = await loginWithPassword(apiBase, cellphone, password);
|
|
160
169
|
console.log(`✓ 登录成功 用户: ${tokenData.userLoginId || username}`);
|
|
161
170
|
|
|
162
|
-
//
|
|
171
|
+
// 公司信息 —— 有 flag 则非交互
|
|
172
|
+
if (opts.company && opts.provinceId && opts.cityId && opts.countyId) {
|
|
173
|
+
await saveCompanyInfo(apiBase, {
|
|
174
|
+
companyName: opts.company,
|
|
175
|
+
provinceId: opts.provinceId, provinceName: opts.provinceName || '',
|
|
176
|
+
cityId: opts.cityId, cityName: opts.cityName || '',
|
|
177
|
+
countyId: opts.countyId, countyName: opts.countyName || '',
|
|
178
|
+
});
|
|
179
|
+
console.log('✓ 公司信息已保存,使用 quote inquiry create 发布询价');
|
|
180
|
+
rl.close();
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
|
|
163
184
|
console.log('\n完善公司/门店信息(发布询价必须):');
|
|
164
185
|
const companyName = await ask('公司/门店名称: ');
|
|
165
186
|
|
|
166
|
-
// 选省
|
|
167
187
|
const provinces = await fetchAreas(apiBase, 'CHN');
|
|
168
188
|
provinces.forEach((p, i) => process.stdout.write(` ${String(i + 1).padStart(2)}. ${p.geoName}\n`));
|
|
169
|
-
const
|
|
170
|
-
const province = provinces[pIdx];
|
|
189
|
+
const province = provinces[parseInt(await ask(`选择省份 (1-${provinces.length}): `), 10) - 1];
|
|
171
190
|
|
|
172
|
-
// 选市
|
|
173
191
|
const cities = await fetchAreas(apiBase, province.geoId);
|
|
174
192
|
cities.forEach((c, i) => process.stdout.write(` ${String(i + 1).padStart(2)}. ${c.geoName}\n`));
|
|
175
|
-
const
|
|
176
|
-
const city = cities[cIdx];
|
|
193
|
+
const city = cities[parseInt(await ask(`选择城市 (1-${cities.length}): `), 10) - 1];
|
|
177
194
|
|
|
178
|
-
// 选区县
|
|
179
195
|
const counties = await fetchAreas(apiBase, city.geoId);
|
|
180
196
|
counties.forEach((d, i) => process.stdout.write(` ${String(i + 1).padStart(2)}. ${d.geoName}\n`));
|
|
181
|
-
const
|
|
182
|
-
const county = counties[dIdx];
|
|
197
|
+
const county = counties[parseInt(await ask(`选择区县 (1-${counties.length}): `), 10) - 1];
|
|
183
198
|
|
|
184
199
|
rl.close();
|
|
185
|
-
|
|
186
200
|
await saveCompanyInfo(apiBase, {
|
|
187
201
|
companyName,
|
|
188
|
-
provinceId:
|
|
189
|
-
cityId:
|
|
190
|
-
countyId:
|
|
202
|
+
provinceId: province.geoId, provinceName: province.geoName,
|
|
203
|
+
cityId: city.geoId, cityName: city.geoName,
|
|
204
|
+
countyId: county.geoId, countyName: county.geoName,
|
|
191
205
|
});
|
|
192
|
-
console.log('✓
|
|
193
|
-
console.log('账号已准备就绪,使用 quote inquiry create 发布询价');
|
|
206
|
+
console.log('✓ 公司信息已保存,使用 quote inquiry create 发布询价');
|
|
194
207
|
} catch (err) {
|
|
195
208
|
rl.close();
|
|
196
209
|
console.error(`✗ 注册失败: ${err.message}`);
|
|
@@ -202,10 +215,7 @@ export function registerLoginCommands(program) {
|
|
|
202
215
|
.command('logout')
|
|
203
216
|
.description('登出(清除本地令牌)')
|
|
204
217
|
.action(() => {
|
|
205
|
-
if (!isLoggedIn()) {
|
|
206
|
-
console.log('当前未登录');
|
|
207
|
-
return;
|
|
208
|
-
}
|
|
218
|
+
if (!isLoggedIn()) { console.log('当前未登录'); return; }
|
|
209
219
|
logout();
|
|
210
220
|
console.log('✓ 已登出,令牌已清除');
|
|
211
221
|
});
|