create-pardx-scaffold 0.1.0 → 0.1.2
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/template/.cursor/worktrees.json +37 -0
- package/template/.dockerignore +49 -0
- package/template/.mcp.json +26 -0
- package/template/.nvmrc +1 -0
- package/template/CLAUDE.md +85 -0
- package/template/apps/api/libs/domain/services/index.ts +7 -0
- package/template/apps/api/libs/{infra/shared-services → domain/services}/ip-info/ip-info.module.ts +2 -0
- package/template/apps/api/libs/{infra/shared-services → domain/services}/ip-info/ip-info.service.ts +2 -0
- package/template/apps/api/libs/infra/clients/internal/file-storage/dto/file.dto.ts +1 -1
- package/template/apps/api/libs/infra/shared-services/email/email.module.ts +0 -2
- package/template/apps/api/libs/infra/shared-services/file-storage/bucket-resolver.ts +1 -1
- package/template/apps/api/libs/infra/shared-services/file-storage/file-storage.module.ts +1 -1
- package/template/apps/api/libs/infra/shared-services/sms/sms.module.ts +0 -2
- package/template/apps/api/package.json +15 -15
- package/template/apps/api/prisma/migrations/migration_lock.toml +3 -0
- package/template/apps/api/src/app.module.ts +1 -1
- package/template/apps/web/.env.example +6 -4
- package/template/apps/web/components/error-boundary.tsx +166 -0
- package/template/apps/web/components/index.ts +10 -0
- package/template/apps/web/components.json +20 -0
- package/template/apps/web/config.ts +115 -0
- package/template/apps/web/eslint.config.mjs +4 -0
- package/template/apps/web/lib/api/avatar-upload.ts +1 -0
- package/template/apps/web/lib/api/contracts/client.ts +51 -30
- package/template/apps/web/lib/api/contracts/hooks/index.ts +0 -3
- package/template/apps/web/lib/api/contracts/hooks/notification.ts +42 -124
- package/template/apps/web/lib/api.ts +24 -1
- package/template/apps/web/lib/dynamic-import.tsx +121 -0
- package/template/apps/web/lib/logger.ts +113 -0
- package/template/apps/web/lib/upload/api.ts +37 -105
- package/template/apps/web/lib/upload/batch-uploader.ts +7 -74
- package/template/apps/web/lib/upload/uploader.ts +10 -74
- package/template/apps/web/locales/zh-CN/assessment.json +5 -0
- package/template/apps/web/locales/zh-CN/chat.json +6 -0
- package/template/apps/web/locales/zh-CN/common.json +38 -0
- package/template/apps/web/locales/zh-CN/creative.json +5 -0
- package/template/apps/web/locales/zh-CN/daily-challenge.json +6 -0
- package/template/apps/web/locales/zh-CN/errors.json +16 -0
- package/template/apps/web/locales/zh-CN/forms.json +18 -0
- package/template/apps/web/locales/zh-CN/memory.json +5 -0
- package/template/apps/web/locales/zh-CN/navigation.json +12 -0
- package/template/apps/web/locales/zh-CN/recommendation.json +5 -0
- package/template/apps/web/locales/zh-CN/recruitment.json +5 -0
- package/template/apps/web/locales/zh-CN/settings.json +7 -0
- package/template/apps/web/locales/zh-CN/subscription.json +6 -0
- package/template/apps/web/locales/zh-CN/validation.json +8 -0
- package/template/apps/web/package.json +14 -15
- package/template/apps/web/postcss.config.mjs +1 -0
- package/template/apps/web/proxy.ts +102 -0
- package/template/apps/web/public/logo.svg +21 -0
- package/template/apps/web/vitest.config.ts +69 -0
- package/template/apps/web/vitest.setup.ts +80 -0
- package/template/package.json +7 -7
- package/template/packages/constants/package.json +3 -1
- package/template/packages/constants/tsconfig.build.esm.json +8 -0
- package/template/packages/contracts/package.json +2 -2
- package/template/packages/contracts/src/schemas/uploader.schema.ts +33 -10
- package/template/packages/ui/.storybook/main.ts +28 -0
- package/template/packages/ui/.storybook/preview.ts +40 -0
- package/template/packages/ui/eslint.config.js +3 -0
- package/template/packages/ui/package.json +15 -2
- package/template/packages/ui/src/components/button.stories.tsx +171 -0
- package/template/packages/ui/src/styles/globals.css +1 -1
- package/template/packages/ui/tsconfig.json +1 -1
- package/template/packages/utils/package.json +2 -2
- package/template/packages/utils/tsconfig.build.esm.json +8 -0
- package/template/packages/validators/package.json +1 -1
- package/template/pnpm-lock.yaml +2263 -999
- package/template/scripts/export-scaffold-for-create.js +65 -0
- package/template/apps/api/libs/infra/utils/download.ts +0 -21
- package/template/apps/web/lib/api/client.ts +0 -649
- package/template/apps/web/lib/audio-buffer-queue.ts +0 -273
- package/template/apps/web/lib/upload/folder-utils.ts +0 -295
- /package/template/apps/api/libs/{infra/shared-services → domain/services}/ip-info/index.ts +0 -0
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Frontend Logger
|
|
3
|
+
*
|
|
4
|
+
* 浏览器兼容的日志库,替代 console.log
|
|
5
|
+
* 支持日志级别控制和生产环境静默
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
type LogLevel = 'debug' | 'info' | 'warn' | 'error';
|
|
9
|
+
|
|
10
|
+
interface LoggerConfig {
|
|
11
|
+
level: LogLevel;
|
|
12
|
+
prefix?: string;
|
|
13
|
+
enableInProduction?: boolean;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const LOG_LEVELS: Record<LogLevel, number> = {
|
|
17
|
+
debug: 0,
|
|
18
|
+
info: 1,
|
|
19
|
+
warn: 2,
|
|
20
|
+
error: 3,
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
class Logger {
|
|
24
|
+
private level: LogLevel;
|
|
25
|
+
private prefix: string;
|
|
26
|
+
private enableInProduction: boolean;
|
|
27
|
+
|
|
28
|
+
constructor(config: Partial<LoggerConfig> = {}) {
|
|
29
|
+
this.level = config.level ?? this.getDefaultLevel();
|
|
30
|
+
this.prefix = config.prefix ?? '[App]';
|
|
31
|
+
this.enableInProduction = config.enableInProduction ?? false;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
private getDefaultLevel(): LogLevel {
|
|
35
|
+
if (typeof window === 'undefined') {
|
|
36
|
+
return 'info';
|
|
37
|
+
}
|
|
38
|
+
return process.env.NODE_ENV === 'production' ? 'warn' : 'debug';
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
private shouldLog(level: LogLevel): boolean {
|
|
42
|
+
// 在生产环境中,除非明确启用,否则只记录 warn 和 error
|
|
43
|
+
if (
|
|
44
|
+
process.env.NODE_ENV === 'production' &&
|
|
45
|
+
!this.enableInProduction &&
|
|
46
|
+
LOG_LEVELS[level] < LOG_LEVELS.warn
|
|
47
|
+
) {
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
return LOG_LEVELS[level] >= LOG_LEVELS[this.level];
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
private formatMessage(level: LogLevel, message: string): string {
|
|
54
|
+
const timestamp = new Date().toISOString();
|
|
55
|
+
return `${timestamp} ${this.prefix} [${level.toUpperCase()}] ${message}`;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
debug(message: string, ...args: unknown[]): void {
|
|
59
|
+
if (this.shouldLog('debug')) {
|
|
60
|
+
console.debug(this.formatMessage('debug', message), ...args);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
info(message: string, ...args: unknown[]): void {
|
|
65
|
+
if (this.shouldLog('info')) {
|
|
66
|
+
console.info(this.formatMessage('info', message), ...args);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
warn(message: string, ...args: unknown[]): void {
|
|
71
|
+
if (this.shouldLog('warn')) {
|
|
72
|
+
console.warn(this.formatMessage('warn', message), ...args);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
error(message: string, ...args: unknown[]): void {
|
|
77
|
+
if (this.shouldLog('error')) {
|
|
78
|
+
console.error(this.formatMessage('error', message), ...args);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* 设置日志级别
|
|
84
|
+
*/
|
|
85
|
+
setLevel(level: LogLevel): void {
|
|
86
|
+
this.level = level;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* 创建带有自定义前缀的子 logger
|
|
91
|
+
*/
|
|
92
|
+
child(prefix: string): Logger {
|
|
93
|
+
return new Logger({
|
|
94
|
+
level: this.level,
|
|
95
|
+
prefix: `${this.prefix}:${prefix}`,
|
|
96
|
+
enableInProduction: this.enableInProduction,
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// 默认 logger 实例
|
|
102
|
+
export const logger = new Logger();
|
|
103
|
+
|
|
104
|
+
// 创建模块专用 logger
|
|
105
|
+
export const createLogger = (prefix: string): Logger => {
|
|
106
|
+
return logger.child(prefix);
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
// 导出类型
|
|
110
|
+
export type { LogLevel, LoggerConfig };
|
|
111
|
+
export { Logger };
|
|
112
|
+
|
|
113
|
+
export default logger;
|
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
import { uploaderClient } from '../api/contracts/client';
|
|
2
2
|
import { UploadError, UploadErrorCode } from './errors';
|
|
3
|
+
import type {
|
|
4
|
+
FileSourceResponse,
|
|
5
|
+
TokenResponse,
|
|
6
|
+
UploadMetadata,
|
|
7
|
+
} from '@repo/contracts';
|
|
8
|
+
|
|
9
|
+
// Re-export UploadMetadata type from contracts
|
|
10
|
+
export type { UploadMetadata };
|
|
3
11
|
|
|
4
12
|
// Helper function: extract error message from response (for logging only)
|
|
5
13
|
function getErrorMsg(body: unknown, defaultMsg: string): string {
|
|
@@ -14,87 +22,38 @@ function getErrorMsg(body: unknown, defaultMsg: string): string {
|
|
|
14
22
|
return defaultMsg;
|
|
15
23
|
}
|
|
16
24
|
|
|
17
|
-
// Type guard for folderId
|
|
18
|
-
type FolderId = 'root' | `${string}-${string}-${string}-${string}-${string}`;
|
|
19
|
-
|
|
20
|
-
function isValidFolderId(id: string): id is FolderId {
|
|
21
|
-
return (
|
|
22
|
-
id === 'root' ||
|
|
23
|
-
/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(id)
|
|
24
|
-
);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
25
|
/**
|
|
28
|
-
*
|
|
29
|
-
*/
|
|
30
|
-
export interface UploadMetadata {
|
|
31
|
-
jobDescriptionId?: string; // 简历上传使用
|
|
32
|
-
meetingId?: string; // 会议录音上传使用
|
|
33
|
-
autoParse?: boolean; // 是否自动触发解析
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* 获取私有上传 Token
|
|
26
|
+
* 获取私有上传 Token 参数
|
|
38
27
|
*/
|
|
39
28
|
export interface GetUploadTokenParams {
|
|
40
29
|
filename: string;
|
|
41
30
|
signature: string;
|
|
42
31
|
fsize: number;
|
|
43
|
-
folderId: string;
|
|
44
|
-
spaceId: string;
|
|
45
32
|
thumbImg?: string;
|
|
46
|
-
|
|
33
|
+
sha256?: string;
|
|
47
34
|
metadata?: UploadMetadata;
|
|
48
35
|
}
|
|
49
36
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
spaceId: string;
|
|
55
|
-
parentId: string | null;
|
|
56
|
-
fsize: number;
|
|
57
|
-
url?: string;
|
|
58
|
-
fileKeyId?: string;
|
|
59
|
-
origin?: string;
|
|
60
|
-
createdAt?: string;
|
|
61
|
-
updatedAt?: string;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
export interface UploadTokenData {
|
|
65
|
-
// 文件不存在时返回的字段
|
|
66
|
-
token?: string;
|
|
67
|
-
key?: string;
|
|
68
|
-
fileId?: string;
|
|
69
|
-
|
|
70
|
-
// 文件已存在(秒传)时返回的字段
|
|
71
|
-
success?: boolean;
|
|
72
|
-
fileSystem?: FileSystem;
|
|
73
|
-
space?: Record<string, unknown>;
|
|
74
|
-
parent?: Record<string, unknown>;
|
|
75
|
-
usage?: Record<string, unknown>;
|
|
76
|
-
}
|
|
37
|
+
/**
|
|
38
|
+
* 上传 Token 响应数据
|
|
39
|
+
*/
|
|
40
|
+
export type UploadTokenData = TokenResponse;
|
|
77
41
|
|
|
78
42
|
export async function getUploadTokenPrivate(
|
|
79
43
|
params: GetUploadTokenParams,
|
|
80
44
|
): Promise<UploadTokenData> {
|
|
81
|
-
if (!isValidFolderId(params.folderId)) {
|
|
82
|
-
throw new UploadError(
|
|
83
|
-
UploadErrorCode.GET_UPLOAD_TOKEN_FAILED,
|
|
84
|
-
'Invalid folderId format',
|
|
85
|
-
);
|
|
86
|
-
}
|
|
87
|
-
|
|
88
45
|
const response = await uploaderClient.getPrivateToken({
|
|
89
46
|
body: {
|
|
90
47
|
filename: params.filename,
|
|
91
48
|
signature: params.signature,
|
|
92
49
|
fsize: params.fsize,
|
|
50
|
+
sha256: params.sha256,
|
|
51
|
+
metadata: params.metadata,
|
|
93
52
|
},
|
|
94
53
|
});
|
|
95
54
|
|
|
96
55
|
if (response.status === 200) {
|
|
97
|
-
return response.body.data
|
|
56
|
+
return response.body.data;
|
|
98
57
|
}
|
|
99
58
|
|
|
100
59
|
console.error(getErrorMsg(response.body, 'Failed to get upload token'));
|
|
@@ -102,49 +61,36 @@ export async function getUploadTokenPrivate(
|
|
|
102
61
|
}
|
|
103
62
|
|
|
104
63
|
/**
|
|
105
|
-
*
|
|
64
|
+
* 初始化分片上传参数
|
|
106
65
|
*/
|
|
107
66
|
export interface InitMultipartUploadParams {
|
|
108
67
|
signature: string;
|
|
109
68
|
filename: string;
|
|
110
69
|
fsize: number;
|
|
111
|
-
|
|
112
|
-
folderId: string;
|
|
70
|
+
sha256?: string;
|
|
113
71
|
metadata?: UploadMetadata;
|
|
114
72
|
}
|
|
115
73
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
spaceId: string;
|
|
121
|
-
// 秒传时可能返回完整的文件信息
|
|
122
|
-
success?: boolean;
|
|
123
|
-
fileSystem?: FileSystem;
|
|
124
|
-
space?: Record<string, unknown>;
|
|
125
|
-
parent?: Record<string, unknown>;
|
|
126
|
-
}
|
|
74
|
+
/**
|
|
75
|
+
* 初始化分片上传响应数据
|
|
76
|
+
*/
|
|
77
|
+
export type InitMultipartUploadData = TokenResponse;
|
|
127
78
|
|
|
128
79
|
export async function initMultipartUpload(
|
|
129
80
|
params: InitMultipartUploadParams,
|
|
130
81
|
): Promise<InitMultipartUploadData> {
|
|
131
|
-
if (!isValidFolderId(params.folderId)) {
|
|
132
|
-
throw new UploadError(
|
|
133
|
-
UploadErrorCode.INIT_MULTIPART_FAILED,
|
|
134
|
-
'Invalid folderId format',
|
|
135
|
-
);
|
|
136
|
-
}
|
|
137
|
-
|
|
138
82
|
const response = await uploaderClient.initMultipart({
|
|
139
83
|
body: {
|
|
140
84
|
signature: params.signature,
|
|
141
85
|
filename: params.filename,
|
|
142
86
|
fsize: params.fsize,
|
|
87
|
+
sha256: params.sha256,
|
|
88
|
+
metadata: params.metadata,
|
|
143
89
|
},
|
|
144
90
|
});
|
|
145
91
|
|
|
146
92
|
if (response.status === 200) {
|
|
147
|
-
return response.body.data
|
|
93
|
+
return response.body.data;
|
|
148
94
|
}
|
|
149
95
|
|
|
150
96
|
console.error(getErrorMsg(response.body, 'Failed to init multipart upload'));
|
|
@@ -152,7 +98,7 @@ export async function initMultipartUpload(
|
|
|
152
98
|
}
|
|
153
99
|
|
|
154
100
|
/**
|
|
155
|
-
* 获取分片上传 Token
|
|
101
|
+
* 获取分片上传 Token 参数
|
|
156
102
|
*/
|
|
157
103
|
export interface GetChunkUploadTokenParams {
|
|
158
104
|
signature: string;
|
|
@@ -160,11 +106,12 @@ export interface GetChunkUploadTokenParams {
|
|
|
160
106
|
uploadId: string;
|
|
161
107
|
partNumber: number;
|
|
162
108
|
fsize: number;
|
|
163
|
-
spaceId: string;
|
|
164
|
-
folderId: string;
|
|
165
109
|
key: string;
|
|
166
110
|
}
|
|
167
111
|
|
|
112
|
+
/**
|
|
113
|
+
* 分片上传 Token 响应数据
|
|
114
|
+
*/
|
|
168
115
|
export interface ChunkUploadTokenData {
|
|
169
116
|
token: string;
|
|
170
117
|
}
|
|
@@ -192,30 +139,17 @@ export async function getChunkUploadToken(
|
|
|
192
139
|
}
|
|
193
140
|
|
|
194
141
|
/**
|
|
195
|
-
*
|
|
142
|
+
* 完成上传参数
|
|
196
143
|
*/
|
|
197
144
|
export interface CompleteUploadParams {
|
|
198
145
|
signature: string;
|
|
199
|
-
filename: string;
|
|
200
146
|
fileId: string;
|
|
201
|
-
fsize: number;
|
|
202
|
-
spaceId: string;
|
|
203
|
-
uploadId?: string;
|
|
204
|
-
parts?: Array<{ ETag: string; PartNumber: number }>;
|
|
205
|
-
key?: string;
|
|
206
|
-
thumbImg?: string;
|
|
207
|
-
metadata?: UploadMetadata;
|
|
208
147
|
}
|
|
209
148
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
fileSystem?: FileSystem;
|
|
215
|
-
};
|
|
216
|
-
fileSystem?: FileSystem;
|
|
217
|
-
success?: boolean;
|
|
218
|
-
}
|
|
149
|
+
/**
|
|
150
|
+
* 完成上传响应数据
|
|
151
|
+
*/
|
|
152
|
+
export type CompleteUploadData = FileSourceResponse;
|
|
219
153
|
|
|
220
154
|
export async function completeUpload(
|
|
221
155
|
params: CompleteUploadParams,
|
|
@@ -228,7 +162,7 @@ export async function completeUpload(
|
|
|
228
162
|
});
|
|
229
163
|
|
|
230
164
|
if (response.status === 200) {
|
|
231
|
-
return response.body.data
|
|
165
|
+
return response.body.data;
|
|
232
166
|
}
|
|
233
167
|
|
|
234
168
|
console.error(getErrorMsg(response.body, 'Failed to complete upload'));
|
|
@@ -236,12 +170,10 @@ export async function completeUpload(
|
|
|
236
170
|
}
|
|
237
171
|
|
|
238
172
|
/**
|
|
239
|
-
*
|
|
173
|
+
* 取消上传参数
|
|
240
174
|
*/
|
|
241
175
|
export interface AbortUploadParams {
|
|
242
176
|
signature: string;
|
|
243
|
-
uploadId: string;
|
|
244
|
-
spaceId: string;
|
|
245
177
|
fileId: string;
|
|
246
178
|
}
|
|
247
179
|
|
|
@@ -29,8 +29,6 @@ export interface BatchUploadProgress {
|
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
export interface BatchUploadOptions {
|
|
32
|
-
spaceId: string;
|
|
33
|
-
folderId: string;
|
|
34
32
|
files: File[];
|
|
35
33
|
metadata?: UploadMetadata;
|
|
36
34
|
onProgress?: (progress: BatchUploadProgress) => void;
|
|
@@ -47,39 +45,11 @@ const MAX_CONCURRENT_UPLOADS = 3;
|
|
|
47
45
|
|
|
48
46
|
/**
|
|
49
47
|
* 批量上传文件(带并发控制)
|
|
50
|
-
* 支持素材空间和简历空间上传
|
|
51
48
|
*/
|
|
52
|
-
export function uploadFiles(
|
|
53
|
-
options: BatchUploadOptions,
|
|
54
|
-
): Promise<{ success: number; failed: number }>;
|
|
55
|
-
/**
|
|
56
|
-
* @deprecated 使用 uploadFiles(options) 代替
|
|
57
|
-
*/
|
|
58
|
-
export function uploadFiles(
|
|
59
|
-
spaceId: string,
|
|
60
|
-
folderId: string,
|
|
61
|
-
files: File[],
|
|
62
|
-
onProgress?: (progress: BatchUploadProgress) => void,
|
|
63
|
-
): Promise<{ success: number; failed: number }>;
|
|
64
49
|
export async function uploadFiles(
|
|
65
|
-
|
|
66
|
-
folderIdArg?: string,
|
|
67
|
-
filesArg?: File[],
|
|
68
|
-
onProgressArg?: (progress: BatchUploadProgress) => void,
|
|
50
|
+
options: BatchUploadOptions,
|
|
69
51
|
): Promise<{ success: number; failed: number }> {
|
|
70
|
-
|
|
71
|
-
const options: BatchUploadOptions =
|
|
72
|
-
typeof optionsOrSpaceId === 'string'
|
|
73
|
-
? {
|
|
74
|
-
spaceId: optionsOrSpaceId,
|
|
75
|
-
folderId: folderIdArg!,
|
|
76
|
-
files: filesArg!,
|
|
77
|
-
onProgress: onProgressArg,
|
|
78
|
-
}
|
|
79
|
-
: optionsOrSpaceId;
|
|
80
|
-
|
|
81
|
-
const { spaceId, folderId, files, metadata, onProgress, onFileComplete } =
|
|
82
|
-
options;
|
|
52
|
+
const { files, metadata, onProgress, onFileComplete } = options;
|
|
83
53
|
|
|
84
54
|
if (files.length === 0) {
|
|
85
55
|
return { success: 0, failed: 0 };
|
|
@@ -138,8 +108,6 @@ export async function uploadFiles(
|
|
|
138
108
|
|
|
139
109
|
const result = await uploadFile({
|
|
140
110
|
file,
|
|
141
|
-
folderId,
|
|
142
|
-
spaceId,
|
|
143
111
|
metadata,
|
|
144
112
|
callbacks: {
|
|
145
113
|
onCalculating: (progress) => {
|
|
@@ -173,18 +141,14 @@ export async function uploadFiles(
|
|
|
173
141
|
updateProgress();
|
|
174
142
|
},
|
|
175
143
|
onComplete: (completeResult) => {
|
|
176
|
-
//
|
|
177
|
-
const isInstant = isInstantUploadResult(completeResult);
|
|
178
|
-
|
|
179
|
-
// 从完成结果中提取 fileId(秒传时特别重要)
|
|
144
|
+
// 从完成结果中提取 fileId
|
|
180
145
|
const completedFileId = getFileIdFromResult(completeResult);
|
|
181
146
|
if (completedFileId) {
|
|
182
147
|
fileProgress.fileId = completedFileId;
|
|
183
148
|
}
|
|
184
149
|
|
|
185
|
-
fileProgress.status =
|
|
150
|
+
fileProgress.status = 'success';
|
|
186
151
|
fileProgress.progress = 100;
|
|
187
|
-
fileProgress.isInstantUpload = isInstant;
|
|
188
152
|
fileProgress.uploadSpeed = undefined;
|
|
189
153
|
completedCount++;
|
|
190
154
|
updateProgress();
|
|
@@ -211,9 +175,8 @@ export async function uploadFiles(
|
|
|
211
175
|
}
|
|
212
176
|
|
|
213
177
|
// 调用文件完成回调
|
|
214
|
-
const isInstant = isInstantUploadResult(result);
|
|
215
178
|
if (fileProgress.fileId) {
|
|
216
|
-
onFileComplete?.(file, fileProgress.fileId, result,
|
|
179
|
+
onFileComplete?.(file, fileProgress.fileId, result, false);
|
|
217
180
|
}
|
|
218
181
|
|
|
219
182
|
return { success: true };
|
|
@@ -246,41 +209,11 @@ export async function uploadFiles(
|
|
|
246
209
|
* 从上传结果中提取文件 ID
|
|
247
210
|
*/
|
|
248
211
|
function getFileIdFromResult(result: UploadResult): string | null {
|
|
249
|
-
if ('
|
|
250
|
-
return result.
|
|
212
|
+
if ('id' in result && result.id) {
|
|
213
|
+
return result.id;
|
|
251
214
|
}
|
|
252
215
|
if ('fileId' in result && result.fileId) {
|
|
253
216
|
return result.fileId;
|
|
254
217
|
}
|
|
255
|
-
if ('data' in result && result.data?.fileSystem?.id) {
|
|
256
|
-
return result.data.fileSystem.id;
|
|
257
|
-
}
|
|
258
218
|
return null;
|
|
259
219
|
}
|
|
260
|
-
|
|
261
|
-
/**
|
|
262
|
-
* 检查上传结果是否为秒传
|
|
263
|
-
* 秒传的判断条件:
|
|
264
|
-
* 1. fileSystem 存在(文件已存在)
|
|
265
|
-
* 2. 满足以下任一条件:
|
|
266
|
-
* - success === true(普通上传秒传)
|
|
267
|
-
* - uploadId 不存在或为空(大文件分片上传秒传)
|
|
268
|
-
*/
|
|
269
|
-
function isInstantUploadResult(result: UploadResult): boolean {
|
|
270
|
-
// 必须有 fileSystem 才可能是秒传
|
|
271
|
-
if (!('fileSystem' in result) || !result.fileSystem) {
|
|
272
|
-
return false;
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
// 普通上传秒传:success === true
|
|
276
|
-
if ('success' in result && result.success === true) {
|
|
277
|
-
return true;
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
// 大文件分片上传秒传:uploadId 不存在或为空
|
|
281
|
-
if (!('uploadId' in result) || !result.uploadId) {
|
|
282
|
-
return true;
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
return false;
|
|
286
|
-
}
|