@pnt-team/pnt-component-frontend 0.0.92 → 0.0.96
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 +26 -4
- package/lib/cjs/api/cos.d.ts +103 -0
- package/lib/cjs/api/cos.js +137 -0
- package/lib/cjs/components/PntCosUploader/index.d.ts +135 -0
- package/lib/cjs/components/PntCosUploader/index.js +209 -0
- package/lib/cjs/components/PntImage/index.d.ts +9 -3
- package/lib/cjs/components/PntImage/index.js +9 -1
- package/lib/cjs/components/PntSecureBox/index.js +8 -3
- package/lib/cjs/components/PntUpload/index.d.ts +149 -0
- package/lib/cjs/components/PntUpload/index.js +228 -0
- package/lib/cjs/components/PntUpload/index.scss +101 -0
- package/lib/cjs/hooks/useHighSecurity.d.ts +55 -15
- package/lib/cjs/hooks/useHighSecurity.js +163 -35
- package/lib/cjs/index.d.ts +6 -0
- package/lib/cjs/index.js +18 -2
- package/lib/esm/api/cos.d.ts +103 -0
- package/lib/esm/api/cos.js +189 -0
- package/lib/esm/components/PntCosUploader/index.d.ts +135 -0
- package/lib/esm/components/PntCosUploader/index.js +289 -0
- package/lib/esm/components/PntImage/index.d.ts +9 -3
- package/lib/esm/components/PntImage/index.js +22 -3
- package/lib/esm/components/PntSecureBox/index.js +9 -3
- package/lib/esm/components/PntUpload/index.d.ts +149 -0
- package/lib/esm/components/PntUpload/index.js +276 -0
- package/lib/esm/components/PntUpload/index.scss +101 -0
- package/lib/esm/hooks/useHighSecurity.d.ts +55 -15
- package/lib/esm/hooks/useHighSecurity.js +174 -32
- package/lib/esm/index.d.ts +6 -0
- package/lib/esm/index.js +9 -1
- package/lib/umd/pnt-component-frontend.min.css +1 -1
- package/lib/umd/pnt-component-frontend.min.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -66,7 +66,8 @@ import {
|
|
|
66
66
|
| `src` | `string \| undefined` | ✅ | - | 图片 URL;为空则展示 defaultImg 或内置 uploadImg |
|
|
67
67
|
| `alt` | `string` | ❌ | `''` | alt 文本,纯预览模式建议传入以提升可访问性 |
|
|
68
68
|
| `defaultImg` | `string` | ❌ | 内置 uploadImg | 默认图片(仅上传预览模式生效) |
|
|
69
|
-
| `
|
|
69
|
+
| `size` | `'small' \| 'medium' \| 'large'` | ❌ | `'large'` | 快捷正方形尺寸:small=36px、medium=60px、large=136px |
|
|
70
|
+
| `style` | `React.CSSProperties` | ❌ | - | 自定义样式;width/height 优先级高于 size |
|
|
70
71
|
| `loading` | `boolean` | ❌ | `false` | 上传中状态(仅上传预览模式生效) |
|
|
71
72
|
| `onRemove` | `(e: any) => void` | ❌ | - | 删除回调(传入即启用上传预览模式) |
|
|
72
73
|
| `container` | `HTMLElement \| (() => HTMLElement)` | ❌ | - | attach 容器(弹窗场景需要,透传给 tdesign ImageViewer) |
|
|
@@ -88,14 +89,14 @@ import {
|
|
|
88
89
|
loading={uploading}
|
|
89
90
|
defaultImg="/path/to/default.png"
|
|
90
91
|
onRemove={handleRemove}
|
|
91
|
-
|
|
92
|
+
size="large"
|
|
92
93
|
/>
|
|
93
94
|
|
|
94
95
|
// 纯预览模式
|
|
95
96
|
<PntImage
|
|
96
97
|
src={url}
|
|
97
98
|
alt="avatar"
|
|
98
|
-
|
|
99
|
+
size="medium"
|
|
99
100
|
container={dialogContainer}
|
|
100
101
|
/>
|
|
101
102
|
|
|
@@ -103,7 +104,7 @@ import {
|
|
|
103
104
|
<PntImage
|
|
104
105
|
src={url}
|
|
105
106
|
alt="avatar"
|
|
106
|
-
|
|
107
|
+
size="medium"
|
|
107
108
|
texts={{ loading: 'Loading', loadFailed: 'Failed' }}
|
|
108
109
|
/>
|
|
109
110
|
```
|
|
@@ -207,6 +208,27 @@ import { PntSecureBox } from '@pnt-team/pnt-component-frontend';
|
|
|
207
208
|
/>;
|
|
208
209
|
```
|
|
209
210
|
|
|
211
|
+
**非展示场景(查看 → 复制 / 批量解锁拼装)请用 `useHighSecurity`:**
|
|
212
|
+
|
|
213
|
+
```tsx
|
|
214
|
+
import { useHighSecurity } from '@pnt-team/pnt-component-frontend';
|
|
215
|
+
|
|
216
|
+
const { securityData, isUnlocked, requestSecurityData, clearSecurityData } =
|
|
217
|
+
useHighSecurity({ gameId, env });
|
|
218
|
+
|
|
219
|
+
// 第一次点击解锁、第二次点击复制;切换业务时 clearSecurityData()
|
|
220
|
+
const res = await requestSecurityData({
|
|
221
|
+
gameId,
|
|
222
|
+
env,
|
|
223
|
+
param: [{ key: 'SDK_KEY', value: cipher }],
|
|
224
|
+
});
|
|
225
|
+
if (res.length) navigator.clipboard.writeText(securityData.SDK_KEY);
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
- 模块级缓存按 `gameId + env + key` 复合键隔离,同页多实例共享,同一 key 同页只请求一次;在途并发自动去重
|
|
229
|
+
- 解锁失败返回 `[]` 且不抛错;`env` 必填
|
|
230
|
+
- 另有 `isSecurityData(value)` 导出,用于判断 `_high_security_:` 密文
|
|
231
|
+
|
|
210
232
|
---
|
|
211
233
|
|
|
212
234
|
### L3 布局组件
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import type { AxiosInstance } from 'axios';
|
|
2
|
+
/**
|
|
3
|
+
* 归一化后的 COS 临时凭证
|
|
4
|
+
* @description 各后端接口字段命名不统一(snake/camel),组件库内部统一为 camelCase
|
|
5
|
+
*/
|
|
6
|
+
export interface NormalizedCosCredential {
|
|
7
|
+
/** COS 存储桶 */
|
|
8
|
+
bucket: string;
|
|
9
|
+
/** COS 地域 */
|
|
10
|
+
region: string;
|
|
11
|
+
/** 临时密钥 ID */
|
|
12
|
+
secretId: string;
|
|
13
|
+
/** 临时密钥 Key */
|
|
14
|
+
secretKey: string;
|
|
15
|
+
/** 临时会话令牌 */
|
|
16
|
+
sessionToken: string;
|
|
17
|
+
/** 凭证生效时间(秒) */
|
|
18
|
+
startTime: number;
|
|
19
|
+
/** 凭证过期时间(秒) */
|
|
20
|
+
expiredTime: number;
|
|
21
|
+
/**
|
|
22
|
+
* 业务路径映射
|
|
23
|
+
* @description 后端按业务类型返回的可写路径 map;消费方按 pathKey 选取
|
|
24
|
+
*/
|
|
25
|
+
path?: Record<string, string>;
|
|
26
|
+
/**
|
|
27
|
+
* 受控目录前缀(SDK 定制场景返回)
|
|
28
|
+
* @description 前端在该前缀下自行拼接文件名;可与 pathKey 继续拼接子目录
|
|
29
|
+
*/
|
|
30
|
+
filePath?: string;
|
|
31
|
+
/**
|
|
32
|
+
* CDN 访问域名(不含尾斜杠)
|
|
33
|
+
* @description SDK 定制场景返回 cdn_url;通用场景无此字段时组件按 COS Location 拼接
|
|
34
|
+
*/
|
|
35
|
+
cosHost?: string;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* 通用 COS 凭证响应(snake_case,pnt-tools / account-business)
|
|
39
|
+
*/
|
|
40
|
+
interface SnakeCosCredential {
|
|
41
|
+
bucket: string;
|
|
42
|
+
region: string;
|
|
43
|
+
secret_id: string;
|
|
44
|
+
secret_key: string;
|
|
45
|
+
token: string;
|
|
46
|
+
start_time: number;
|
|
47
|
+
expired_time: number;
|
|
48
|
+
path?: Record<string, string>;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* 内置业务场景
|
|
52
|
+
* @description 决定走哪个后端凭证接口
|
|
53
|
+
* - default: 通用上传(GET /v2/business/operation/get-cos-temp-token)
|
|
54
|
+
* - default_download: 通用下载/上传(POST /v2/business/operation/get-download-cos-temp-token,snake_case)
|
|
55
|
+
* - sdk_customize: SDK 定制服务(POST /internal/sdk_customize/upload_token,含 cdn_url/file_path)
|
|
56
|
+
* - ai_translate: AI 翻译服务(POST /business/aiTranslate/import/cosToken,camelCase)
|
|
57
|
+
*/
|
|
58
|
+
export type CosScene = 'default' | 'default_download' | 'sdk_customize' | 'ai_translate' | (string & Record<never, never>);
|
|
59
|
+
/**
|
|
60
|
+
* 凭证获取参数
|
|
61
|
+
*/
|
|
62
|
+
export interface GetCosCredentialParams {
|
|
63
|
+
/** 业务场景 */
|
|
64
|
+
scene: CosScene;
|
|
65
|
+
/** 业务类型路径 key(透传到后端 path map 选取;sdk_customize 场景作为受控目录下的子目录) */
|
|
66
|
+
pathKey?: string;
|
|
67
|
+
/**
|
|
68
|
+
* 业务上下文参数(gameId/env/updType 等,按 scene 不同传递)
|
|
69
|
+
* @description default/default_download 场景下 gameId 会自动映射到请求头 x-service-type,
|
|
70
|
+
* 其余字段透传到 query/body;ai_translate 场景整体透传到 body
|
|
71
|
+
*/
|
|
72
|
+
context?: Record<string, string | number>;
|
|
73
|
+
/** 文件大小(字节),后端用于校验配额 */
|
|
74
|
+
contentLength?: number;
|
|
75
|
+
/** 已上传文件 URL/Key(下载场景使用) */
|
|
76
|
+
fileUrl?: string;
|
|
77
|
+
/**
|
|
78
|
+
* 网关 audience
|
|
79
|
+
* @default '/player-network'
|
|
80
|
+
* @description 账号管理等子系统需覆盖为对应 audience(如 '/laas-pnt');
|
|
81
|
+
* 消费方注入的 http 实例若已统一追加 audience,URL 上存在该参数时拦截器不会重复追加
|
|
82
|
+
*/
|
|
83
|
+
audience?: string;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* 自定义原始响应归一化函数
|
|
87
|
+
* @description 后端返回字段不符合内置 scene 约定时使用;入参为后端响应的原始 data
|
|
88
|
+
*/
|
|
89
|
+
export type NormalizeCredential = (raw: unknown) => NormalizedCosCredential;
|
|
90
|
+
/**
|
|
91
|
+
* snake_case 凭证归一化(导出供 getCredential 自定义逃生舱复用)
|
|
92
|
+
* @description 消费方自行请求凭证(如项目 http 实例为非标准签名)时,
|
|
93
|
+
* 可在 getCredential 内调用本函数把后端 data 转为组件库统一结构
|
|
94
|
+
*/
|
|
95
|
+
export declare const normalizeSnakeCredential: (raw: SnakeCosCredential) => NormalizedCosCredential;
|
|
96
|
+
/**
|
|
97
|
+
* 获取 COS 临时凭证(按 scene 路由到不同后端接口,内部归一化字段命名)
|
|
98
|
+
* @param http 由 PntProvider 注入的 axios 实例
|
|
99
|
+
* @param params 凭证获取参数
|
|
100
|
+
* @param customNormalize 可选的原始响应自定义归一化
|
|
101
|
+
*/
|
|
102
|
+
export declare const fetchCosCredential: (http: AxiosInstance, params: GetCosCredentialParams, customNormalize?: NormalizeCredential) => Promise<NormalizedCosCredential>;
|
|
103
|
+
export {};
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
3
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
5
|
+
var __export = (target, all) => {
|
|
6
|
+
for (var name in all)
|
|
7
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
8
|
+
};
|
|
9
|
+
var __copyProps = (to, from, except, desc) => {
|
|
10
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
11
|
+
for (let key of __getOwnPropNames(from))
|
|
12
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
13
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
14
|
+
}
|
|
15
|
+
return to;
|
|
16
|
+
};
|
|
17
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
18
|
+
|
|
19
|
+
// src/api/cos.ts
|
|
20
|
+
var cos_exports = {};
|
|
21
|
+
__export(cos_exports, {
|
|
22
|
+
fetchCosCredential: () => fetchCosCredential,
|
|
23
|
+
normalizeSnakeCredential: () => normalizeSnakeCredential
|
|
24
|
+
});
|
|
25
|
+
module.exports = __toCommonJS(cos_exports);
|
|
26
|
+
var normalizeSnake = (raw) => ({
|
|
27
|
+
bucket: raw.bucket,
|
|
28
|
+
region: raw.region,
|
|
29
|
+
secretId: raw.secret_id,
|
|
30
|
+
secretKey: raw.secret_key,
|
|
31
|
+
sessionToken: raw.token,
|
|
32
|
+
startTime: raw.start_time,
|
|
33
|
+
expiredTime: raw.expired_time,
|
|
34
|
+
path: raw.path
|
|
35
|
+
});
|
|
36
|
+
var normalizeSnakeCredential = (raw) => normalizeSnake(raw);
|
|
37
|
+
var normalizeAiTranslate = (raw) => {
|
|
38
|
+
var _a, _b;
|
|
39
|
+
return {
|
|
40
|
+
bucket: raw.bucket || "",
|
|
41
|
+
region: raw.region || "",
|
|
42
|
+
secretId: raw.secretId || "",
|
|
43
|
+
secretKey: raw.secretKey || "",
|
|
44
|
+
sessionToken: raw.token || "",
|
|
45
|
+
startTime: (_a = raw.startTime) != null ? _a : 0,
|
|
46
|
+
expiredTime: (_b = raw.expiredTime) != null ? _b : 0,
|
|
47
|
+
// aiTranslate path 类型为 Record<string, unknown>,这里收窄为 string
|
|
48
|
+
path: raw.path ? Object.fromEntries(
|
|
49
|
+
Object.entries(raw.path).filter(
|
|
50
|
+
(v) => typeof v[1] === "string"
|
|
51
|
+
)
|
|
52
|
+
) : void 0
|
|
53
|
+
};
|
|
54
|
+
};
|
|
55
|
+
var normalizeSdkCustomize = (raw) => ({
|
|
56
|
+
bucket: raw.bucket,
|
|
57
|
+
region: raw.region,
|
|
58
|
+
secretId: raw.secret_id,
|
|
59
|
+
secretKey: raw.secret_key,
|
|
60
|
+
sessionToken: raw.token,
|
|
61
|
+
startTime: raw.start_time,
|
|
62
|
+
expiredTime: raw.expired_time,
|
|
63
|
+
filePath: raw.file_path,
|
|
64
|
+
cosHost: raw.cdn_url
|
|
65
|
+
});
|
|
66
|
+
var DEFAULT_AUDIENCE = "/player-network";
|
|
67
|
+
var resolveCredential = (res, normalize, customNormalize) => {
|
|
68
|
+
var _a, _b;
|
|
69
|
+
const success = res.code === 0 || res.ret === 0;
|
|
70
|
+
if (!success || res.data === null || typeof res.data === "undefined") {
|
|
71
|
+
throw new Error(
|
|
72
|
+
`[PntCosUploader] 获取 COS 凭证失败: code=${(_a = res.code) != null ? _a : "null"} ret=${(_b = res.ret) != null ? _b : "null"} ${res.msg || ""}`
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
return customNormalize ? customNormalize(res.data) : normalize(res.data);
|
|
76
|
+
};
|
|
77
|
+
var fetchCosCredential = async (http, params, customNormalize) => {
|
|
78
|
+
const {
|
|
79
|
+
scene,
|
|
80
|
+
context = {},
|
|
81
|
+
contentLength,
|
|
82
|
+
fileUrl,
|
|
83
|
+
audience = DEFAULT_AUDIENCE
|
|
84
|
+
} = params;
|
|
85
|
+
switch (scene) {
|
|
86
|
+
case "default": {
|
|
87
|
+
const { gameId, ...restContext } = context;
|
|
88
|
+
const res = await http.get(`/v2/business/operation/get-cos-temp-token?audience=${audience}`, {
|
|
89
|
+
params: {
|
|
90
|
+
content_length: contentLength,
|
|
91
|
+
...restContext
|
|
92
|
+
},
|
|
93
|
+
headers: gameId !== void 0 ? { "x-service-type": String(gameId) } : void 0
|
|
94
|
+
});
|
|
95
|
+
return resolveCredential(res, normalizeSnake, customNormalize);
|
|
96
|
+
}
|
|
97
|
+
case "default_download": {
|
|
98
|
+
const { gameId, ...restContext } = context;
|
|
99
|
+
const res = await http.post(
|
|
100
|
+
`/v2/business/operation/get-download-cos-temp-token?audience=${audience}`,
|
|
101
|
+
{
|
|
102
|
+
file_url: fileUrl,
|
|
103
|
+
content_length: contentLength,
|
|
104
|
+
...restContext
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
headers: gameId !== void 0 ? { "x-service-type": String(gameId) } : void 0
|
|
108
|
+
}
|
|
109
|
+
);
|
|
110
|
+
return resolveCredential(res, normalizeSnake, customNormalize);
|
|
111
|
+
}
|
|
112
|
+
case "sdk_customize": {
|
|
113
|
+
const res = await http.post("/internal/sdk_customize/upload_token", {
|
|
114
|
+
content_length: contentLength,
|
|
115
|
+
...context
|
|
116
|
+
});
|
|
117
|
+
return resolveCredential(res, normalizeSdkCustomize, customNormalize);
|
|
118
|
+
}
|
|
119
|
+
case "ai_translate": {
|
|
120
|
+
const res = await http.post("/business/aiTranslate/import/cosToken", {
|
|
121
|
+
contentLength,
|
|
122
|
+
...context
|
|
123
|
+
});
|
|
124
|
+
return resolveCredential(res, normalizeAiTranslate, customNormalize);
|
|
125
|
+
}
|
|
126
|
+
default: {
|
|
127
|
+
throw new Error(
|
|
128
|
+
`[PntCosUploader] 未支持的 scene: ${scene};请通过 getCredential 自定义`
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
134
|
+
0 && (module.exports = {
|
|
135
|
+
fetchCosCredential,
|
|
136
|
+
normalizeSnakeCredential
|
|
137
|
+
});
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { type CosScene, type GetCosCredentialParams, type NormalizeCredential, type NormalizedCosCredential } from "../../api/cos";
|
|
2
|
+
import { usePntConfig } from "../../config";
|
|
3
|
+
import type { AxiosInstance } from 'axios';
|
|
4
|
+
/**
|
|
5
|
+
* 文件名生成策略
|
|
6
|
+
* @description 内置三种策略;如需自定义可传函数
|
|
7
|
+
* - uuid: UUID v4 + 原扩展名(默认,最简单)
|
|
8
|
+
* - md5: 文件内容 MD5 + 原扩展名(与 pnt-tools CosApi 原策略一致)
|
|
9
|
+
* - timestamp: 时间戳(36进制)+自增计数器 + 原扩展名(与 SDK 定制场景原策略一致)
|
|
10
|
+
*/
|
|
11
|
+
export type FileNameStrategy = 'uuid' | 'md5' | 'timestamp' | ((file: File) => string | Promise<string>);
|
|
12
|
+
/**
|
|
13
|
+
* COS 上传配置
|
|
14
|
+
*/
|
|
15
|
+
export interface PntCosUploadOptions {
|
|
16
|
+
/**
|
|
17
|
+
* 业务场景,决定走哪个凭证接口
|
|
18
|
+
* @description 内置 4 个 scene,消费方可传 string 自定义并配合 getCredential 使用
|
|
19
|
+
*/
|
|
20
|
+
scene: CosScene;
|
|
21
|
+
/**
|
|
22
|
+
* 业务类型路径 key
|
|
23
|
+
* @description 通用场景透传给后端 path map 选取对应业务路径;
|
|
24
|
+
* sdk_customize 场景作为受控目录(filePath)下的子目录继续拼接
|
|
25
|
+
*/
|
|
26
|
+
pathKey?: string;
|
|
27
|
+
/**
|
|
28
|
+
* 业务上下文参数(gameId/env/updType 等)
|
|
29
|
+
* @description 按 scene 不同传递给后端;default/default_download 下 gameId 自动映射到 x-service-type 请求头
|
|
30
|
+
*/
|
|
31
|
+
context?: Record<string, string | number>;
|
|
32
|
+
/**
|
|
33
|
+
* 网关 audience
|
|
34
|
+
* @default '/player-network'
|
|
35
|
+
* @description 账号管理等子系统需覆盖为对应 audience(如 '/laas-pnt')
|
|
36
|
+
*/
|
|
37
|
+
audience?: string;
|
|
38
|
+
/**
|
|
39
|
+
* 文件名生成策略
|
|
40
|
+
* @default 'uuid'
|
|
41
|
+
*/
|
|
42
|
+
fileNameStrategy?: FileNameStrategy;
|
|
43
|
+
/**
|
|
44
|
+
* 是否走 CDN 域名拼接
|
|
45
|
+
* @description true 且凭证返回 cosHost 时用 cosHost + cosKey;否则用 COS putObject 返回的 Location
|
|
46
|
+
* @default true
|
|
47
|
+
*/
|
|
48
|
+
useCdn?: boolean;
|
|
49
|
+
/**
|
|
50
|
+
* 自定义凭证获取函数(覆盖内置 scene 路由)
|
|
51
|
+
* @description 后端接口非内置 scene 时使用;返回归一化后的凭证
|
|
52
|
+
*/
|
|
53
|
+
getCredential?: (http: ReturnType<typeof usePntConfig>['http'], params: GetCosCredentialParams) => Promise<NormalizedCosCredential>;
|
|
54
|
+
/**
|
|
55
|
+
* 自定义凭证响应归一化
|
|
56
|
+
* @description 后端字段不标准时使用;入参为内置 scene 接口返回的原始 data,需返回归一化凭证
|
|
57
|
+
*/
|
|
58
|
+
normalizeCredential?: NormalizeCredential;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* COS 上传结果
|
|
62
|
+
*/
|
|
63
|
+
export interface PntCosUploadResult {
|
|
64
|
+
/** 上传后的可访问 URL */
|
|
65
|
+
fileUrl: string;
|
|
66
|
+
/** COS 对象 Key */
|
|
67
|
+
cosKey: string;
|
|
68
|
+
/** 原始文件名 */
|
|
69
|
+
fileName: string;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* 单次请求覆盖参数
|
|
73
|
+
* @description 上传/下载入口的 gameId/task_id/pathKey 常来自表单或表格行数据,
|
|
74
|
+
* 可通过该参数在单次调用覆盖 hook 初始化时的配置
|
|
75
|
+
*/
|
|
76
|
+
export interface PntCosRequestOverrides {
|
|
77
|
+
/** 覆盖业务上下文(与初始化 context 浅合并,如行级 gameId/task_id) */
|
|
78
|
+
context?: Record<string, string | number>;
|
|
79
|
+
/** 覆盖网关 audience */
|
|
80
|
+
audience?: string;
|
|
81
|
+
/** 覆盖凭证场景 */
|
|
82
|
+
scene?: CosScene;
|
|
83
|
+
/** 覆盖业务路径 key */
|
|
84
|
+
pathKey?: string;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* COS 上传器实例
|
|
88
|
+
*/
|
|
89
|
+
export interface PntCosUploader {
|
|
90
|
+
/**
|
|
91
|
+
* 上传文件到 COS
|
|
92
|
+
* @param file 原始文件
|
|
93
|
+
* @param onProgress 上传进度回调(0~100)
|
|
94
|
+
* @param overrides 单次调用覆盖配置(表单/行级 gameId 等)
|
|
95
|
+
*/
|
|
96
|
+
upload: (file: File, onProgress?: (percent: number) => void, overrides?: PntCosRequestOverrides) => Promise<PntCosUploadResult>;
|
|
97
|
+
/**
|
|
98
|
+
* 获取文件预签名下载 URL(临时)
|
|
99
|
+
* @param cosKey COS 对象 Key,或完整 URL(自动剥离域名)
|
|
100
|
+
* @param overrides 单次调用覆盖配置(行级 gameId/task_id 等)
|
|
101
|
+
*/
|
|
102
|
+
getDownloadUrl: (cosKey: string, overrides?: PntCosRequestOverrides) => Promise<string>;
|
|
103
|
+
}
|
|
104
|
+
export type { CosScene, GetCosCredentialParams, NormalizeCredential, NormalizedCosCredential, };
|
|
105
|
+
export { normalizeSnakeCredential } from "../../api/cos";
|
|
106
|
+
/**
|
|
107
|
+
* 创建 COS 直传器实例(不依赖 React)
|
|
108
|
+
* @description 与 usePntCosUploader 共用同一套核心逻辑,适用于 class 兼容层、
|
|
109
|
+
* 工具函数、非 React 入口等场景;React 组件请优先使用 usePntCosUploader
|
|
110
|
+
*
|
|
111
|
+
* @param http 消费方 axios 实例(约定响应拦截器已解包 response.data)
|
|
112
|
+
* @param options 上传配置(固定值),或返回配置的 getter(需要动态配置时使用)
|
|
113
|
+
*
|
|
114
|
+
* @example
|
|
115
|
+
* ```ts
|
|
116
|
+
* const uploader = createPntCosUploader(http, { scene: 'default', pathKey: 'marketing_icon' });
|
|
117
|
+
* const { fileUrl } = await uploader.upload(file);
|
|
118
|
+
* ```
|
|
119
|
+
*/
|
|
120
|
+
export declare const createPntCosUploader: (http: AxiosInstance, options: PntCosUploadOptions | (() => PntCosUploadOptions)) => PntCosUploader;
|
|
121
|
+
/**
|
|
122
|
+
* COS 直传 Service Hook
|
|
123
|
+
* @description 内置凭证获取 + COS SDK 初始化 + 文件名策略 + URL 拼接,
|
|
124
|
+
* 收敛各项目重复的 4 份自实现;http 实例由 PntProvider 注入
|
|
125
|
+
*
|
|
126
|
+
* @param options 上传配置
|
|
127
|
+
*
|
|
128
|
+
* @example
|
|
129
|
+
* ```ts
|
|
130
|
+
* const uploader = usePntCosUploader({ scene: 'default', pathKey: 'marketing_icon' });
|
|
131
|
+
* const { fileUrl, cosKey } = await uploader.upload(file, (p) => setProgress(p));
|
|
132
|
+
* ```
|
|
133
|
+
*/
|
|
134
|
+
export declare const usePntCosUploader: (options: PntCosUploadOptions) => PntCosUploader;
|
|
135
|
+
export default usePntCosUploader;
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
var __create = Object.create;
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all)
|
|
9
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
|
+
};
|
|
11
|
+
var __copyProps = (to, from, except, desc) => {
|
|
12
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
13
|
+
for (let key of __getOwnPropNames(from))
|
|
14
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
15
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
16
|
+
}
|
|
17
|
+
return to;
|
|
18
|
+
};
|
|
19
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
20
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
21
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
22
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
23
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
24
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
25
|
+
mod
|
|
26
|
+
));
|
|
27
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
28
|
+
|
|
29
|
+
// src/components/PntCosUploader/index.ts
|
|
30
|
+
var PntCosUploader_exports = {};
|
|
31
|
+
__export(PntCosUploader_exports, {
|
|
32
|
+
createPntCosUploader: () => createPntCosUploader,
|
|
33
|
+
default: () => PntCosUploader_default,
|
|
34
|
+
normalizeSnakeCredential: () => import_cos2.normalizeSnakeCredential,
|
|
35
|
+
usePntCosUploader: () => usePntCosUploader
|
|
36
|
+
});
|
|
37
|
+
module.exports = __toCommonJS(PntCosUploader_exports);
|
|
38
|
+
var import_cos = require("../../api/cos");
|
|
39
|
+
var import_config = require("../../config");
|
|
40
|
+
var import_cos_js_sdk_v5 = __toESM(require("cos-js-sdk-v5"));
|
|
41
|
+
var import_crypto_js = __toESM(require("crypto-js"));
|
|
42
|
+
var import_react = require("react");
|
|
43
|
+
var import_uuid = require("uuid");
|
|
44
|
+
var import_cos2 = require("../../api/cos");
|
|
45
|
+
var trimSlash = (seg, leading = true) => seg.replace(/\/$/, "").replace(leading ? /^\// : "", "");
|
|
46
|
+
var getFileExt = (filename) => {
|
|
47
|
+
const idx = filename.lastIndexOf(".");
|
|
48
|
+
return idx > -1 ? filename.slice(idx) : "";
|
|
49
|
+
};
|
|
50
|
+
var _timestampCounter = 0;
|
|
51
|
+
var generateTimestampName = () => `${Date.now().toString(36)}${(_timestampCounter++).toString(36)}`;
|
|
52
|
+
var computeFileMd5 = async (file) => {
|
|
53
|
+
const buffer = await file.arrayBuffer();
|
|
54
|
+
return import_crypto_js.default.MD5(import_crypto_js.default.lib.WordArray.create(buffer)).toString();
|
|
55
|
+
};
|
|
56
|
+
var generateFileName = async (file, strategy) => {
|
|
57
|
+
if (typeof strategy === "function") {
|
|
58
|
+
return strategy(file);
|
|
59
|
+
}
|
|
60
|
+
const ext = getFileExt(file.name);
|
|
61
|
+
switch (strategy) {
|
|
62
|
+
case "md5": {
|
|
63
|
+
const md5 = await computeFileMd5(file);
|
|
64
|
+
return `${md5}${ext}`;
|
|
65
|
+
}
|
|
66
|
+
case "timestamp":
|
|
67
|
+
return `${generateTimestampName()}${ext}`;
|
|
68
|
+
case "uuid":
|
|
69
|
+
default:
|
|
70
|
+
return `${(0, import_uuid.v4)()}${ext}`;
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
var resolveBasePath = (cred, pathKey) => {
|
|
74
|
+
if (cred.filePath) {
|
|
75
|
+
const prefix = cred.filePath.replace(/\/$/, "");
|
|
76
|
+
return pathKey ? `${prefix}/${trimSlash(pathKey)}` : prefix;
|
|
77
|
+
}
|
|
78
|
+
if (pathKey && cred.path) {
|
|
79
|
+
return cred.path[pathKey] || "";
|
|
80
|
+
}
|
|
81
|
+
return "";
|
|
82
|
+
};
|
|
83
|
+
var stripHost = (key) => key.replace(/^https?:\/\/[^/]+/, "");
|
|
84
|
+
var createCosClient = (cred) => new import_cos_js_sdk_v5.default({
|
|
85
|
+
getAuthorization: (_, callback) => {
|
|
86
|
+
callback({
|
|
87
|
+
TmpSecretId: cred.secretId,
|
|
88
|
+
TmpSecretKey: cred.secretKey,
|
|
89
|
+
SecurityToken: cred.sessionToken,
|
|
90
|
+
StartTime: cred.startTime,
|
|
91
|
+
ExpiredTime: cred.expiredTime
|
|
92
|
+
});
|
|
93
|
+
},
|
|
94
|
+
Protocol: "https:",
|
|
95
|
+
FileParallelLimit: 3,
|
|
96
|
+
ChunkParallelLimit: 3,
|
|
97
|
+
ChunkSize: 1024 * 1024,
|
|
98
|
+
ProgressInterval: 1
|
|
99
|
+
});
|
|
100
|
+
var createPntCosUploader = (http, options) => {
|
|
101
|
+
const getOptions = () => typeof options === "function" ? options() : options;
|
|
102
|
+
const upload = async (file, onProgress, overrides) => {
|
|
103
|
+
var _a, _b, _c;
|
|
104
|
+
const opts = getOptions();
|
|
105
|
+
const {
|
|
106
|
+
scene,
|
|
107
|
+
pathKey,
|
|
108
|
+
context,
|
|
109
|
+
audience,
|
|
110
|
+
fileNameStrategy = "uuid",
|
|
111
|
+
useCdn = true,
|
|
112
|
+
getCredential,
|
|
113
|
+
normalizeCredential
|
|
114
|
+
} = opts;
|
|
115
|
+
const mergedContext = { ...context, ...overrides == null ? void 0 : overrides.context };
|
|
116
|
+
const mergedScene = (_a = overrides == null ? void 0 : overrides.scene) != null ? _a : scene;
|
|
117
|
+
const mergedAudience = (_b = overrides == null ? void 0 : overrides.audience) != null ? _b : audience;
|
|
118
|
+
const mergedPathKey = (_c = overrides == null ? void 0 : overrides.pathKey) != null ? _c : pathKey;
|
|
119
|
+
const credParams = {
|
|
120
|
+
scene: mergedScene,
|
|
121
|
+
pathKey: mergedPathKey,
|
|
122
|
+
context: mergedContext,
|
|
123
|
+
audience: mergedAudience,
|
|
124
|
+
contentLength: file.size
|
|
125
|
+
};
|
|
126
|
+
const cred = getCredential ? await getCredential(http, credParams) : await (0, import_cos.fetchCosCredential)(http, credParams, normalizeCredential);
|
|
127
|
+
const basePath = resolveBasePath(cred, mergedPathKey);
|
|
128
|
+
const fileName = await generateFileName(file, fileNameStrategy);
|
|
129
|
+
const cosKey = basePath ? `${basePath.replace(/\/$/, "")}/${fileName}` : fileName;
|
|
130
|
+
const cos = createCosClient(cred);
|
|
131
|
+
return new Promise((resolve, reject) => {
|
|
132
|
+
cos.putObject(
|
|
133
|
+
{
|
|
134
|
+
Bucket: cred.bucket,
|
|
135
|
+
Region: cred.region,
|
|
136
|
+
Key: cosKey,
|
|
137
|
+
Body: file,
|
|
138
|
+
onProgress: onProgress ? (progressData) => {
|
|
139
|
+
onProgress(Math.round(progressData.percent * 100));
|
|
140
|
+
} : void 0
|
|
141
|
+
},
|
|
142
|
+
(err, data) => {
|
|
143
|
+
if (err) {
|
|
144
|
+
reject(err);
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
const fileUrl = useCdn && cred.cosHost ? `${cred.cosHost.replace(/\/$/, "")}/${cosKey}` : `https://${data.Location}`;
|
|
148
|
+
resolve({
|
|
149
|
+
fileUrl,
|
|
150
|
+
cosKey,
|
|
151
|
+
fileName: file.name
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
);
|
|
155
|
+
});
|
|
156
|
+
};
|
|
157
|
+
const getDownloadUrl = async (cosKey, overrides) => {
|
|
158
|
+
var _a, _b, _c;
|
|
159
|
+
const opts = getOptions();
|
|
160
|
+
const { scene, pathKey, context, audience, getCredential } = opts;
|
|
161
|
+
const mergedContext = { ...context, ...overrides == null ? void 0 : overrides.context };
|
|
162
|
+
const mergedScene = (_a = overrides == null ? void 0 : overrides.scene) != null ? _a : scene;
|
|
163
|
+
const mergedAudience = (_b = overrides == null ? void 0 : overrides.audience) != null ? _b : audience;
|
|
164
|
+
const mergedPathKey = (_c = overrides == null ? void 0 : overrides.pathKey) != null ? _c : pathKey;
|
|
165
|
+
const credParams = {
|
|
166
|
+
scene: getCredential ? mergedScene : mergedScene === "sdk_customize" ? mergedScene : "default_download",
|
|
167
|
+
pathKey: mergedPathKey,
|
|
168
|
+
context: mergedContext,
|
|
169
|
+
audience: mergedAudience,
|
|
170
|
+
fileUrl: cosKey
|
|
171
|
+
};
|
|
172
|
+
const cred = getCredential ? await getCredential(http, credParams) : await (0, import_cos.fetchCosCredential)(http, credParams);
|
|
173
|
+
const cos = createCosClient(cred);
|
|
174
|
+
return new Promise((resolve, reject) => {
|
|
175
|
+
cos.getObjectUrl(
|
|
176
|
+
{
|
|
177
|
+
Bucket: cred.bucket,
|
|
178
|
+
Region: cred.region,
|
|
179
|
+
Key: stripHost(cosKey),
|
|
180
|
+
Sign: true
|
|
181
|
+
},
|
|
182
|
+
(err, data) => {
|
|
183
|
+
if (err) {
|
|
184
|
+
reject(err);
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
resolve(data.Url);
|
|
188
|
+
}
|
|
189
|
+
);
|
|
190
|
+
});
|
|
191
|
+
};
|
|
192
|
+
return { upload, getDownloadUrl };
|
|
193
|
+
};
|
|
194
|
+
var usePntCosUploader = (options) => {
|
|
195
|
+
const { http } = (0, import_config.usePntConfig)();
|
|
196
|
+
const optionsRef = (0, import_react.useRef)(options);
|
|
197
|
+
optionsRef.current = options;
|
|
198
|
+
return (0, import_react.useMemo)(
|
|
199
|
+
() => createPntCosUploader(http, () => optionsRef.current),
|
|
200
|
+
[http]
|
|
201
|
+
);
|
|
202
|
+
};
|
|
203
|
+
var PntCosUploader_default = usePntCosUploader;
|
|
204
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
205
|
+
0 && (module.exports = {
|
|
206
|
+
createPntCosUploader,
|
|
207
|
+
normalizeSnakeCredential,
|
|
208
|
+
usePntCosUploader
|
|
209
|
+
});
|
|
@@ -28,9 +28,15 @@ export interface PntImageProps {
|
|
|
28
28
|
* @description src 为空时展示的占位图;不传则使用内置的 uploadImg
|
|
29
29
|
*/
|
|
30
30
|
defaultImg?: string;
|
|
31
|
+
/**
|
|
32
|
+
* 快捷尺寸(正方形)
|
|
33
|
+
* @description 预设尺寸:`small`=36px、`medium`=60px、`large`=136px;默认 `large`
|
|
34
|
+
* 若 `style` 中显式指定了 width/height,则以 style 为准;非正方形场景请使用 `style`
|
|
35
|
+
*/
|
|
36
|
+
size?: 'small' | 'medium' | 'large';
|
|
31
37
|
/**
|
|
32
38
|
* 自定义样式
|
|
33
|
-
* @description
|
|
39
|
+
* @description 一般用于控制图片宽高;其中 width/height 优先级高于 size
|
|
34
40
|
*/
|
|
35
41
|
style?: React.CSSProperties;
|
|
36
42
|
/**
|
|
@@ -69,14 +75,14 @@ export interface PntImageProps {
|
|
|
69
75
|
* loading={loading}
|
|
70
76
|
* defaultImg={defaultImg}
|
|
71
77
|
* onRemove={handleRemove}
|
|
72
|
-
*
|
|
78
|
+
* size="large"
|
|
73
79
|
* />
|
|
74
80
|
*
|
|
75
81
|
* // 纯预览模式
|
|
76
82
|
* <PntImage
|
|
77
83
|
* src={url}
|
|
78
84
|
* alt="avatar"
|
|
79
|
-
*
|
|
85
|
+
* size="medium"
|
|
80
86
|
* container={container}
|
|
81
87
|
* />
|
|
82
88
|
* ```
|