@nbtca/prompt 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.
Files changed (76) hide show
  1. package/.github/workflows/ci.yml +40 -0
  2. package/.github/workflows/publish.yml +59 -0
  3. package/DATA_MANAGEMENT.md +409 -0
  4. package/LICENSE +21 -0
  5. package/README.md +235 -0
  6. package/assets/Prompt_demo.gif +0 -0
  7. package/bin/nbtca-welcome.js +2 -0
  8. package/dist/config/data.d.ts +22 -0
  9. package/dist/config/data.d.ts.map +1 -0
  10. package/dist/config/data.js +25 -0
  11. package/dist/config/data.js.map +1 -0
  12. package/dist/config/theme.d.ts +23 -0
  13. package/dist/config/theme.d.ts.map +1 -0
  14. package/dist/config/theme.js +25 -0
  15. package/dist/config/theme.js.map +1 -0
  16. package/dist/core/logo.d.ts +9 -0
  17. package/dist/core/logo.d.ts.map +1 -0
  18. package/dist/core/logo.js +71 -0
  19. package/dist/core/logo.js.map +1 -0
  20. package/dist/core/menu.d.ts +9 -0
  21. package/dist/core/menu.d.ts.map +1 -0
  22. package/dist/core/menu.js +153 -0
  23. package/dist/core/menu.js.map +1 -0
  24. package/dist/core/ui.d.ts +49 -0
  25. package/dist/core/ui.d.ts.map +1 -0
  26. package/dist/core/ui.js +95 -0
  27. package/dist/core/ui.js.map +1 -0
  28. package/dist/features/calendar.d.ts +27 -0
  29. package/dist/features/calendar.d.ts.map +1 -0
  30. package/dist/features/calendar.js +121 -0
  31. package/dist/features/calendar.js.map +1 -0
  32. package/dist/features/docs.d.ts +21 -0
  33. package/dist/features/docs.d.ts.map +1 -0
  34. package/dist/features/docs.js +109 -0
  35. package/dist/features/docs.js.map +1 -0
  36. package/dist/features/repair.d.ts +9 -0
  37. package/dist/features/repair.d.ts.map +1 -0
  38. package/dist/features/repair.js +37 -0
  39. package/dist/features/repair.js.map +1 -0
  40. package/dist/features/website.d.ts +17 -0
  41. package/dist/features/website.d.ts.map +1 -0
  42. package/dist/features/website.js +39 -0
  43. package/dist/features/website.js.map +1 -0
  44. package/dist/index.d.ts +6 -0
  45. package/dist/index.d.ts.map +1 -0
  46. package/dist/index.js +8 -0
  47. package/dist/index.js.map +1 -0
  48. package/dist/logo/ascii-logo.txt +6 -0
  49. package/dist/logo/logo.txt +2 -0
  50. package/dist/main.d.ts +9 -0
  51. package/dist/main.d.ts.map +1 -0
  52. package/dist/main.js +36 -0
  53. package/dist/main.js.map +1 -0
  54. package/dist/types.d.ts +48 -0
  55. package/dist/types.d.ts.map +1 -0
  56. package/dist/types.js +6 -0
  57. package/dist/types.js.map +1 -0
  58. package/package.json +64 -0
  59. package/src/config/data.js +484 -0
  60. package/src/config/data.ts +28 -0
  61. package/src/config/theme.js +138 -0
  62. package/src/config/theme.ts +26 -0
  63. package/src/core/logo.ts +80 -0
  64. package/src/core/menu.ts +165 -0
  65. package/src/core/ui.ts +119 -0
  66. package/src/features/calendar.ts +153 -0
  67. package/src/features/docs.ts +120 -0
  68. package/src/features/repair.ts +40 -0
  69. package/src/features/website.ts +43 -0
  70. package/src/index.ts +9 -0
  71. package/src/logo/ascii-logo.txt +6 -0
  72. package/src/logo/logo.txt +2 -0
  73. package/src/logo/printLogo.js +26 -0
  74. package/src/main.ts +39 -0
  75. package/src/types.ts +51 -0
  76. package/tsconfig.json +53 -0
@@ -0,0 +1,138 @@
1
+ // Theme configuration for NBTCA Welcome.
2
+
3
+ export const themes = {
4
+ default: {
5
+ name: '默认主题',
6
+ colors: {
7
+ primary: [23, 147, 209], // archBlue
8
+ secondary: [34, 197, 94], // nbtcaGreen
9
+ accent: [147, 51, 234], // nbtcaPurple
10
+ warning: [249, 115, 22], // nbtcaOrange
11
+ error: [239, 68, 68], // red
12
+ success: [34, 197, 94], // green
13
+ info: [59, 130, 246], // blue
14
+ text: [255, 255, 255], // white
15
+ muted: [156, 163, 175] // gray
16
+ },
17
+ symbols: {
18
+ logo: '🎓',
19
+ loading: ['⚡', '🚀', '💻', '🔧', '⚙️', '🎯', '🌟', '💡'],
20
+ success: '✅',
21
+ warning: '⚠️',
22
+ error: '❌',
23
+ info: 'ℹ️'
24
+ }
25
+ },
26
+
27
+ dark: {
28
+ name: '深色主题',
29
+ colors: {
30
+ primary: [59, 130, 246], // blue
31
+ secondary: [16, 185, 129], // emerald
32
+ accent: [139, 92, 246], // violet
33
+ warning: [245, 158, 11], // amber
34
+ error: [239, 68, 68], // red
35
+ success: [34, 197, 94], // green
36
+ info: [6, 182, 212], // cyan
37
+ text: [255, 255, 255], // white
38
+ muted: [107, 114, 128] // gray
39
+ },
40
+ symbols: {
41
+ logo: '🌙',
42
+ loading: ['🌑', '🌒', '🌓', '🌔', '🌕', '🌖', '🌗', '🌘'],
43
+ success: '✅',
44
+ warning: '⚠️',
45
+ error: '❌',
46
+ info: 'ℹ️'
47
+ }
48
+ },
49
+
50
+ light: {
51
+ name: '浅色主题',
52
+ colors: {
53
+ primary: [37, 99, 235], // blue
54
+ secondary: [5, 150, 105], // emerald
55
+ accent: [124, 58, 237], // violet
56
+ warning: [217, 119, 6], // orange
57
+ error: [220, 38, 38], // red
58
+ success: [22, 163, 74], // green
59
+ info: [8, 145, 178], // cyan
60
+ text: [17, 24, 39], // gray-900
61
+ muted: [107, 114, 128] // gray
62
+ },
63
+ symbols: {
64
+ logo: '☀️',
65
+ loading: ['🌞', '🌤️', '⛅', '🌥️', '☁️', '🌦️', '🌧️', '🌈'],
66
+ success: '✅',
67
+ warning: '⚠️',
68
+ error: '❌',
69
+ info: 'ℹ️'
70
+ }
71
+ },
72
+
73
+ nbtca: {
74
+ name: 'NBTCA主题',
75
+ colors: {
76
+ primary: [23, 147, 209], // archBlue
77
+ secondary: [34, 197, 94], // nbtcaGreen
78
+ accent: [147, 51, 234], // nbtcaPurple
79
+ warning: [249, 115, 22], // nbtcaOrange
80
+ error: [236, 72, 153], // nbtcaPink
81
+ success: [34, 197, 94], // green
82
+ info: [59, 130, 246], // blue
83
+ text: [255, 255, 255], // white
84
+ muted: [156, 163, 175] // gray
85
+ },
86
+ symbols: {
87
+ logo: '🎓',
88
+ loading: ['⚡', '🚀', '💻', '🔧', '⚙️', '🎯', '🌟', '💡'],
89
+ success: '✅',
90
+ warning: '⚠️',
91
+ error: '❌',
92
+ info: 'ℹ️'
93
+ }
94
+ }
95
+ };
96
+
97
+ /**
98
+ * Get current theme configuration.
99
+ * @param {string} themeName - Theme name.
100
+ * @returns {Object} Theme configuration.
101
+ */
102
+ export function getTheme(themeName = 'default') {
103
+ return themes[themeName] || themes.default;
104
+ }
105
+
106
+ /**
107
+ * Get color from theme.
108
+ * @param {string} themeName - Theme name.
109
+ * @param {string} colorName - Color name.
110
+ * @returns {Array} RGB color array.
111
+ */
112
+ export function getThemeColor(themeName, colorName) {
113
+ const theme = getTheme(themeName);
114
+ return theme.colors[colorName] || theme.colors.primary;
115
+ }
116
+
117
+ /**
118
+ * Get symbol from theme.
119
+ * @param {string} themeName - Theme name.
120
+ * @param {string} symbolName - Symbol name.
121
+ * @returns {string|Array} Symbol or symbol array.
122
+ */
123
+ export function getThemeSymbol(themeName, symbolName) {
124
+ const theme = getTheme(themeName);
125
+ return theme.symbols[symbolName] || theme.symbols.logo;
126
+ }
127
+
128
+ /**
129
+ * List available themes.
130
+ * @returns {Array} Array of theme names and descriptions.
131
+ */
132
+ export function listThemes() {
133
+ return Object.entries(themes).map(([key, theme]) => ({
134
+ name: theme.name,
135
+ value: key,
136
+ description: `使用 ${theme.name}`
137
+ }));
138
+ }
@@ -0,0 +1,26 @@
1
+ /**
2
+ * 极简主题配置
3
+ * 只保留基础颜色定义
4
+ */
5
+
6
+ /**
7
+ * 基础颜色常量
8
+ */
9
+ export const COLORS = {
10
+ // 主题色
11
+ cyan: '#00bcd4',
12
+ blue: '#2196f3',
13
+ green: '#4caf50',
14
+ yellow: '#ffeb3b',
15
+ red: '#f44336',
16
+ gray: '#9e9e9e',
17
+ white: '#ffffff',
18
+
19
+ // 语义色
20
+ primary: '#00bcd4',
21
+ success: '#4caf50',
22
+ warning: '#ffeb3b',
23
+ error: '#f44336',
24
+ info: '#2196f3',
25
+ muted: '#9e9e9e'
26
+ } as const;
@@ -0,0 +1,80 @@
1
+ /**
2
+ * 智能Logo显示模块
3
+ * 尝试显示iTerm2图片格式logo,失败则降级到ASCII艺术字
4
+ */
5
+
6
+ import { readFileSync } from 'fs';
7
+ import { fileURLToPath } from 'url';
8
+ import { dirname, join } from 'path';
9
+ import chalk from 'chalk';
10
+
11
+ const __filename = fileURLToPath(import.meta.url);
12
+ const __dirname = dirname(__filename);
13
+
14
+ /**
15
+ * 计算字符串的实际显示宽度(考虑中文字符)
16
+ */
17
+ function getDisplayWidth(str: string): number {
18
+ let width = 0;
19
+ for (const char of str) {
20
+ // 中文字符、全角符号等占2个宽度
21
+ width += char.charCodeAt(0) > 127 ? 2 : 1;
22
+ }
23
+ return width;
24
+ }
25
+
26
+ /**
27
+ * 尝试读取并显示logo文件
28
+ */
29
+ export async function printLogo(): Promise<void> {
30
+ try {
31
+ // 尝试读取iTerm2图片格式logo
32
+ const logoPath = join(__dirname, '../logo/logo.txt');
33
+ const logoContent = readFileSync(logoPath, 'utf-8');
34
+
35
+ // 如果成功读取且内容有效,直接显示
36
+ if (logoContent && logoContent.length > 100) {
37
+ console.log(logoContent);
38
+ printDescription();
39
+ return;
40
+ }
41
+ } catch (error) {
42
+ // iTerm2 logo读取失败,继续尝试ASCII logo
43
+ }
44
+
45
+ // 降级:显示ASCII艺术字logo
46
+ try {
47
+ const asciiLogoPath = join(__dirname, '../logo/ascii-logo.txt');
48
+ const asciiContent = readFileSync(asciiLogoPath, 'utf-8');
49
+
50
+ // 使用cyan颜色显示ASCII logo,不居中(ASCII艺术本身已经设计好)
51
+ console.log();
52
+ const lines = asciiContent.split('\n').filter(line => line.trim());
53
+ lines.forEach(line => {
54
+ console.log(chalk.cyan(line));
55
+ });
56
+
57
+ printDescription();
58
+ } catch (error) {
59
+ // 如果ASCII logo也失败,显示简单的文本logo
60
+ console.log(chalk.cyan.bold('\n NBTCA'));
61
+ printDescription();
62
+ }
63
+ }
64
+
65
+ /**
66
+ * 显示描述文字
67
+ */
68
+ function printDescription(): void {
69
+ const description = '浙大宁波理工学院计算机协会';
70
+ const terminalWidth = process.stdout.columns || 80;
71
+
72
+ // 计算实际显示宽度
73
+ const displayWidth = getDisplayWidth(description);
74
+ const padding = Math.max(0, Math.floor((terminalWidth - displayWidth) / 2));
75
+
76
+ console.log();
77
+ console.log(' '.repeat(padding) + chalk.gray(description));
78
+ console.log();
79
+ }
80
+
@@ -0,0 +1,165 @@
1
+ /**
2
+ * 极简菜单系统
3
+ * 6大核心功能菜单
4
+ */
5
+
6
+ import inquirer from 'inquirer';
7
+ import chalk from 'chalk';
8
+ import { showCalendar } from '../features/calendar.js';
9
+ import { openRepairService } from '../features/repair.js';
10
+ import { showDocsMenu } from '../features/docs.js';
11
+ import { openHomepage, openGithub } from '../features/website.js';
12
+ import { printDivider, printNewLine } from './ui.js';
13
+ import { APP_INFO, URLS } from '../config/data.js';
14
+
15
+ /**
16
+ * 主菜单选项
17
+ */
18
+ const MAIN_MENU = [
19
+ {
20
+ name: chalk.cyan('› ') + chalk.white('近期活动 ') + chalk.gray('查看最近30天的社团活动'),
21
+ value: 'events',
22
+ short: '近期活动'
23
+ },
24
+ {
25
+ name: chalk.cyan('› ') + chalk.white('维修服务 ') + chalk.gray('电脑维修、软件安装'),
26
+ value: 'repair',
27
+ short: '维修服务'
28
+ },
29
+ {
30
+ name: chalk.cyan('› ') + chalk.white('知识库 ') + chalk.gray('技术文档、教程资源'),
31
+ value: 'docs',
32
+ short: '知识库'
33
+ },
34
+ {
35
+ name: chalk.cyan('› ') + chalk.white('官方网站 ') + chalk.gray('访问NBTCA主页'),
36
+ value: 'website',
37
+ short: '官方网站'
38
+ },
39
+ {
40
+ name: chalk.cyan('› ') + chalk.white('GitHub ') + chalk.gray('开源项目与代码'),
41
+ value: 'github',
42
+ short: 'GitHub'
43
+ },
44
+ {
45
+ name: chalk.cyan('› ') + chalk.white('关于 ') + chalk.gray('项目信息与帮助'),
46
+ value: 'about',
47
+ short: '关于'
48
+ },
49
+ {
50
+ name: chalk.gray('─'.repeat(50)),
51
+ value: 'separator',
52
+ disabled: true
53
+ },
54
+ {
55
+ name: chalk.red('› ') + chalk.white('退出'),
56
+ value: 'exit',
57
+ short: '退出'
58
+ }
59
+ ];
60
+
61
+ /**
62
+ * 显示主菜单
63
+ */
64
+ export async function showMainMenu(): Promise<void> {
65
+ while (true) {
66
+ try {
67
+ const { action } = await inquirer.prompt([
68
+ {
69
+ type: 'list',
70
+ name: 'action',
71
+ message: chalk.bold('请选择功能:'),
72
+ choices: MAIN_MENU,
73
+ pageSize: 15,
74
+ loop: false
75
+ }
76
+ ]);
77
+
78
+ // 处理用户选择
79
+ if (action === 'exit') {
80
+ console.log();
81
+ console.log(chalk.cyan('再见!感谢使用NBTCA工具'));
82
+ console.log();
83
+ process.exit(0);
84
+ }
85
+
86
+ await handleAction(action);
87
+
88
+ // 操作完成后显示分隔线
89
+ printNewLine();
90
+ printDivider();
91
+ printNewLine();
92
+ } catch (err: any) {
93
+ // 处理Ctrl+C退出
94
+ if (err.message?.includes('User force closed')) {
95
+ console.log();
96
+ console.log(chalk.cyan('再见!'));
97
+ console.log();
98
+ process.exit(0);
99
+ }
100
+ throw err;
101
+ }
102
+ }
103
+ }
104
+
105
+ /**
106
+ * 处理用户操作
107
+ */
108
+ async function handleAction(action: string): Promise<void> {
109
+ switch (action) {
110
+ case 'events':
111
+ await showCalendar();
112
+ break;
113
+
114
+ case 'repair':
115
+ await openRepairService();
116
+ break;
117
+
118
+ case 'docs':
119
+ await showDocsMenu();
120
+ break;
121
+
122
+ case 'website':
123
+ await openHomepage();
124
+ break;
125
+
126
+ case 'github':
127
+ await openGithub();
128
+ break;
129
+
130
+ case 'about':
131
+ showAbout();
132
+ break;
133
+
134
+ default:
135
+ console.log(chalk.gray('未知操作'));
136
+ }
137
+ }
138
+
139
+ /**
140
+ * 显示关于信息
141
+ */
142
+ function showAbout(): void {
143
+ console.log();
144
+ console.log(chalk.cyan.bold(' 关于 NBTCA'));
145
+ console.log();
146
+ printDivider();
147
+ console.log();
148
+ console.log(chalk.white(' 项目名称: ') + chalk.cyan(APP_INFO.name));
149
+ console.log(chalk.white(' 版本: ') + chalk.cyan(`v${APP_INFO.version}`));
150
+ console.log(chalk.white(' 描述: ') + chalk.gray(APP_INFO.fullDescription));
151
+ console.log();
152
+ console.log(chalk.white(' GitHub: ') + chalk.cyan(APP_INFO.repository));
153
+ console.log(chalk.white(' 官网: ') + chalk.cyan(URLS.homepage));
154
+ console.log(chalk.white(' 邮箱: ') + chalk.cyan(URLS.email));
155
+ console.log();
156
+ console.log(chalk.gray(' 功能特性:'));
157
+ console.log(chalk.gray(' • 查看社团近期活动'));
158
+ console.log(chalk.gray(' • 在线维修服务'));
159
+ console.log(chalk.gray(' • 技术知识库访问'));
160
+ console.log(chalk.gray(' • 快速访问官网和GitHub'));
161
+ console.log();
162
+ console.log(chalk.white(' 开源协议: ') + chalk.cyan('MIT License'));
163
+ console.log(chalk.white(' 作者: ') + chalk.cyan('m1ngsama <contact@m1ng.space>'));
164
+ console.log();
165
+ }
package/src/core/ui.ts ADDED
@@ -0,0 +1,119 @@
1
+ /**
2
+ * 极简UI组件库
3
+ * 提供基础的终端UI组件
4
+ */
5
+
6
+ import chalk from 'chalk';
7
+
8
+ /**
9
+ * 菜单项接口
10
+ */
11
+ export interface MenuItem {
12
+ name: string;
13
+ value: string;
14
+ description?: string;
15
+ }
16
+
17
+ /**
18
+ * 计算字符串的实际显示宽度(考虑中文字符)
19
+ */
20
+ function getDisplayWidth(str: string): number {
21
+ let width = 0;
22
+ for (const char of str) {
23
+ // 中文字符、全角符号等占2个宽度
24
+ width += char.charCodeAt(0) > 127 ? 2 : 1;
25
+ }
26
+ return width;
27
+ }
28
+
29
+ /**
30
+ * 显示顶部边框标题
31
+ */
32
+ export function printHeader(title: string): void {
33
+ const terminalWidth = process.stdout.columns || 80;
34
+ const titleWidth = getDisplayWidth(title);
35
+
36
+ // 计算分隔线长度
37
+ const sideWidth = Math.max(2, Math.floor((terminalWidth - titleWidth - 4) / 2));
38
+ const leftLine = '─'.repeat(sideWidth);
39
+ const rightLine = '─'.repeat(sideWidth);
40
+
41
+ // 显示标题行
42
+ console.log(chalk.cyan(`${leftLine} ${title} ${rightLine}`));
43
+ console.log();
44
+ }
45
+
46
+ /**
47
+ * 显示分隔线
48
+ */
49
+ export function printDivider(): void {
50
+ const terminalWidth = process.stdout.columns || 80;
51
+ console.log(chalk.gray('─'.repeat(terminalWidth)));
52
+ }
53
+
54
+ /**
55
+ * 显示加载spinner
56
+ */
57
+ export async function showSpinner(text: string, duration: number): Promise<void> {
58
+ const frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
59
+ let i = 0;
60
+ const startTime = Date.now();
61
+
62
+ return new Promise((resolve) => {
63
+ const interval = setInterval(() => {
64
+ process.stdout.write(`\r${chalk.cyan(frames[i])} ${text}`);
65
+ i = (i + 1) % frames.length;
66
+
67
+ if (Date.now() - startTime >= duration) {
68
+ clearInterval(interval);
69
+ process.stdout.write('\r' + ' '.repeat(text.length + 5) + '\r');
70
+ resolve();
71
+ }
72
+ }, 80);
73
+ });
74
+ }
75
+
76
+ /**
77
+ * 显示成功消息
78
+ */
79
+ export function success(msg: string): void {
80
+ console.log(chalk.green('✓') + ' ' + msg);
81
+ }
82
+
83
+ /**
84
+ * 显示错误消息
85
+ */
86
+ export function error(msg: string): void {
87
+ console.log(chalk.red('✗') + ' ' + msg);
88
+ }
89
+
90
+ /**
91
+ * 显示信息消息
92
+ */
93
+ export function info(msg: string): void {
94
+ console.log(chalk.blue('ℹ') + ' ' + msg);
95
+ }
96
+
97
+ /**
98
+ * 显示警告消息
99
+ */
100
+ export function warning(msg: string): void {
101
+ console.log(chalk.yellow('⚠') + ' ' + msg);
102
+ }
103
+
104
+ /**
105
+ * 清屏
106
+ */
107
+ export function clearScreen(): void {
108
+ console.clear();
109
+ }
110
+
111
+ /**
112
+ * 打印空行
113
+ */
114
+ export function printNewLine(count: number = 1): void {
115
+ for (let i = 0; i < count; i++) {
116
+ console.log();
117
+ }
118
+ }
119
+
@@ -0,0 +1,153 @@
1
+ /**
2
+ * ICS日历功能模块
3
+ * 从远程获取并显示近期活动
4
+ */
5
+
6
+ import axios from 'axios';
7
+ import ICAL from 'ical.js';
8
+ import chalk from 'chalk';
9
+ import { error, info, printDivider } from '../core/ui.js';
10
+
11
+ /**
12
+ * 活动接口
13
+ */
14
+ export interface Event {
15
+ date: string;
16
+ time: string;
17
+ title: string;
18
+ location: string;
19
+ startDate: Date;
20
+ }
21
+
22
+ /**
23
+ * 从远程获取ICS文件并解析活动
24
+ */
25
+ export async function fetchEvents(): Promise<Event[]> {
26
+ try {
27
+ const response = await axios.get('https://ical.nbtca.space', {
28
+ timeout: 5000,
29
+ headers: {
30
+ 'User-Agent': 'NBTCA-CLI/2.3.1'
31
+ }
32
+ });
33
+
34
+ const jcalData = ICAL.parse(response.data);
35
+ const comp = new ICAL.Component(jcalData);
36
+ const vevents = comp.getAllSubcomponents('vevent');
37
+
38
+ const events: Event[] = [];
39
+ const now = new Date();
40
+ const thirtyDaysLater = new Date(now.getTime() + 30 * 24 * 60 * 60 * 1000);
41
+
42
+ for (const vevent of vevents) {
43
+ const event = new ICAL.Event(vevent);
44
+ const startDate = event.startDate.toJSDate();
45
+
46
+ // 只显示未来30天内的活动
47
+ if (startDate >= now && startDate <= thirtyDaysLater) {
48
+ events.push({
49
+ date: formatDate(startDate),
50
+ time: formatTime(startDate),
51
+ title: event.summary || '未命名活动',
52
+ location: event.location || '待定',
53
+ startDate: startDate
54
+ });
55
+ }
56
+ }
57
+
58
+ // 按日期排序
59
+ events.sort((a, b) => a.startDate.getTime() - b.startDate.getTime());
60
+
61
+ return events;
62
+ } catch (err) {
63
+ throw new Error('获取活动日历失败');
64
+ }
65
+ }
66
+
67
+ /**
68
+ * 以表格形式显示活动
69
+ */
70
+ export function displayEvents(events: Event[]): void {
71
+ if (events.length === 0) {
72
+ info('近期暂无活动安排');
73
+ return;
74
+ }
75
+
76
+ console.log();
77
+ console.log(chalk.cyan.bold(' 近期活动') + chalk.gray(` (最近30天)`));
78
+ console.log();
79
+
80
+ // 表头
81
+ const dateWidth = 14;
82
+ const titleWidth = 25;
83
+ const locationWidth = 15;
84
+
85
+ console.log(
86
+ ' ' +
87
+ chalk.bold('日期时间'.padEnd(dateWidth)) +
88
+ chalk.bold('活动名称'.padEnd(titleWidth)) +
89
+ chalk.bold('地点')
90
+ );
91
+
92
+ printDivider();
93
+
94
+ // 活动列表
95
+ events.forEach(event => {
96
+ const dateTime = `${event.date} ${event.time}`.padEnd(dateWidth);
97
+ const title = truncate(event.title, titleWidth - 2).padEnd(titleWidth);
98
+ const location = truncate(event.location, locationWidth);
99
+
100
+ console.log(
101
+ ' ' +
102
+ chalk.cyan(dateTime) +
103
+ chalk.white(title) +
104
+ chalk.gray(location)
105
+ );
106
+ });
107
+
108
+ console.log();
109
+ }
110
+
111
+ /**
112
+ * 格式化日期
113
+ */
114
+ function formatDate(date: Date): string {
115
+ const month = String(date.getMonth() + 1).padStart(2, '0');
116
+ const day = String(date.getDate()).padStart(2, '0');
117
+ return `${month}-${day}`;
118
+ }
119
+
120
+ /**
121
+ * 格式化时间
122
+ */
123
+ function formatTime(date: Date): string {
124
+ const hours = String(date.getHours()).padStart(2, '0');
125
+ const minutes = String(date.getMinutes()).padStart(2, '0');
126
+ return `${hours}:${minutes}`;
127
+ }
128
+
129
+ /**
130
+ * 截断字符串
131
+ */
132
+ function truncate(str: string, maxLength: number): string {
133
+ if (str.length <= maxLength) {
134
+ return str;
135
+ }
136
+ return str.substring(0, maxLength - 3) + '...';
137
+ }
138
+
139
+ /**
140
+ * 主函数:获取并显示活动
141
+ */
142
+ export async function showCalendar(): Promise<void> {
143
+ try {
144
+ info('正在获取活动日历...');
145
+ const events = await fetchEvents();
146
+ console.log('\r' + ' '.repeat(50) + '\r'); // 清除加载提示
147
+ displayEvents(events);
148
+ } catch (err) {
149
+ error('无法获取活动日历');
150
+ console.log(chalk.gray(' 请稍后重试或访问 https://nbtca.space'));
151
+ console.log();
152
+ }
153
+ }