@pnt-team/pnt-component-frontend 0.0.138 → 0.0.140
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/lib/cjs/api/cos.d.ts +103 -0
- package/lib/cjs/api/cos.js +146 -0
- package/lib/cjs/api/index.d.ts +23 -0
- package/lib/cjs/api/index.js +38 -0
- package/lib/cjs/api/types/index.d.ts +117 -0
- package/lib/cjs/api/types/index.js +17 -0
- package/lib/cjs/assets/css/tdesign-theme.css +584 -0
- package/lib/cjs/assets/images/uploadImg.png +0 -0
- package/lib/cjs/assets/svg/inputSearch.svg +4 -0
- package/lib/cjs/assets/svg/inputTip.svg +5 -0
- package/lib/cjs/components/PntRegionSelect/index.scss +4 -3
- package/lib/esm/api/cos.d.ts +103 -0
- package/lib/esm/api/cos.js +189 -0
- package/lib/esm/api/index.d.ts +23 -0
- package/lib/esm/api/index.js +23 -0
- package/lib/esm/api/types/index.d.ts +117 -0
- package/lib/esm/api/types/index.js +1 -0
- package/lib/esm/assets/css/tdesign-theme.css +584 -0
- package/lib/esm/assets/images/uploadImg.png +0 -0
- package/lib/esm/assets/svg/inputSearch.svg +4 -0
- package/lib/esm/assets/svg/inputTip.svg +5 -0
- package/lib/esm/assets/svg/inputWarnTip.svg +5 -0
- package/lib/esm/components/PntBusinessSelect/index.d.ts +192 -0
- package/lib/esm/components/PntBusinessSelect/index.js +513 -0
- package/lib/esm/components/PntBusinessSelect/index.scss +27 -0
- package/lib/esm/components/PntCosUploader/index.d.ts +134 -0
- package/lib/esm/components/PntCosUploader/index.js +300 -0
- package/lib/esm/components/PntGeneralLayout/index.d.ts +114 -0
- package/lib/esm/components/PntGeneralLayout/index.js +103 -0
- package/lib/esm/components/PntGeneralLayout/index.scss +121 -0
- package/lib/esm/components/PntImage/index.d.ts +93 -0
- package/lib/esm/components/PntImage/index.js +200 -0
- package/lib/esm/components/PntImage/index.scss +61 -0
- package/lib/esm/components/PntRegionSelect/ImportDialog.d.ts +29 -0
- package/lib/esm/components/PntRegionSelect/ImportDialog.js +244 -0
- package/lib/esm/components/PntRegionSelect/index.d.ts +39 -0
- package/lib/esm/components/PntRegionSelect/index.js +471 -0
- package/lib/esm/components/PntRegionSelect/index.scss +175 -0
- package/lib/esm/components/PntRegionSelect/types.d.ts +181 -0
- package/lib/esm/components/PntRegionSelect/types.js +77 -0
- package/lib/esm/components/PntSecureBox/index.d.ts +122 -0
- package/lib/esm/components/PntSecureBox/index.js +166 -0
- package/lib/esm/components/PntSecureBox/index.scss +23 -0
- package/lib/esm/components/PntTable/index.d.ts +14 -0
- package/lib/esm/components/PntTable/index.js +404 -0
- package/lib/esm/components/PntTable/index.scss +290 -0
- package/lib/esm/components/PntTable/types.d.ts +250 -0
- package/lib/esm/components/PntTable/types.js +81 -0
- package/lib/esm/components/PntTable/useTableCore.d.ts +38 -0
- package/lib/esm/components/PntTable/useTableCore.js +499 -0
- 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/config.d.ts +97 -0
- package/lib/esm/config.js +60 -0
- package/lib/esm/global.css +2 -0
- package/lib/esm/global.d.ts +39 -0
- package/lib/esm/hooks/useHighSecurity.d.ts +88 -0
- package/lib/esm/hooks/useHighSecurity.js +250 -0
- package/lib/esm/index.d.ts +16 -0
- package/lib/esm/index.js +33 -0
- package/lib/esm/utils/const.d.ts +13 -0
- package/lib/esm/utils/const.js +23 -0
- package/lib/esm/utils/index.d.ts +6 -0
- package/lib/esm/utils/index.js +22 -0
- package/lib/umd/pnt-component-frontend.min.css +1 -0
- package/lib/umd/pnt-component-frontend.min.js +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
.pnt-business-select {
|
|
2
|
+
width: 100%;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
.pnt-business-select-option-label {
|
|
6
|
+
display: block;
|
|
7
|
+
width: 100%;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/* 弹层内容样式:loading / total-wrapper 统一高度与排版 */
|
|
11
|
+
.pnt-business-select-popup {
|
|
12
|
+
.t-popup__content {
|
|
13
|
+
.total-wrapper,
|
|
14
|
+
.t-loading {
|
|
15
|
+
height: 36px;
|
|
16
|
+
font-weight: 400;
|
|
17
|
+
line-height: 20px;
|
|
18
|
+
display: flex;
|
|
19
|
+
align-items: center;
|
|
20
|
+
padding: 0 14px;
|
|
21
|
+
}
|
|
22
|
+
.total-wrapper {
|
|
23
|
+
color: #808191;
|
|
24
|
+
font-size: 14px;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { type CosScene, type GetCosCredentialParams, type NormalizeCredential, type NormalizedCosCredential } from "../../api/cos";
|
|
2
|
+
import { usePntConfig, type PntHttpLike } from "../../config";
|
|
3
|
+
/**
|
|
4
|
+
* 文件名生成策略
|
|
5
|
+
* @description 内置三种策略;如需自定义可传函数
|
|
6
|
+
* - uuid: UUID v4 + 原扩展名(默认,最简单)
|
|
7
|
+
* - md5: 文件内容 MD5 + 原扩展名(与 pnt-tools CosApi 原策略一致)
|
|
8
|
+
* - timestamp: 时间戳(36进制)+自增计数器 + 原扩展名(与 SDK 定制场景原策略一致)
|
|
9
|
+
*/
|
|
10
|
+
export type FileNameStrategy = 'uuid' | 'md5' | 'timestamp' | ((file: File) => string | Promise<string>);
|
|
11
|
+
/**
|
|
12
|
+
* COS 上传配置
|
|
13
|
+
*/
|
|
14
|
+
export interface PntCosUploadOptions {
|
|
15
|
+
/**
|
|
16
|
+
* 业务场景,决定走哪个凭证接口
|
|
17
|
+
* @description 内置 4 个 scene,消费方可传 string 自定义并配合 getCredential 使用
|
|
18
|
+
*/
|
|
19
|
+
scene: CosScene;
|
|
20
|
+
/**
|
|
21
|
+
* 业务类型路径 key
|
|
22
|
+
* @description 通用场景透传给后端 path map 选取对应业务路径;
|
|
23
|
+
* sdk_customize 场景作为受控目录(filePath)下的子目录继续拼接
|
|
24
|
+
*/
|
|
25
|
+
pathKey?: string;
|
|
26
|
+
/**
|
|
27
|
+
* 业务上下文参数(gameId/env/updType 等)
|
|
28
|
+
* @description 按 scene 不同传递给后端;default/default_download 下 gameId 自动映射到 x-service-type 请求头
|
|
29
|
+
*/
|
|
30
|
+
context?: Record<string, string | number>;
|
|
31
|
+
/**
|
|
32
|
+
* 网关 audience
|
|
33
|
+
* @default '/player-network'
|
|
34
|
+
* @description 账号管理等子系统需覆盖为对应 audience(如 '/laas-pnt')
|
|
35
|
+
*/
|
|
36
|
+
audience?: string;
|
|
37
|
+
/**
|
|
38
|
+
* 文件名生成策略
|
|
39
|
+
* @default 'uuid'
|
|
40
|
+
*/
|
|
41
|
+
fileNameStrategy?: FileNameStrategy;
|
|
42
|
+
/**
|
|
43
|
+
* 是否走 CDN 域名拼接
|
|
44
|
+
* @description true 且凭证返回 cosHost 时用 cosHost + cosKey;否则用 COS putObject 返回的 Location
|
|
45
|
+
* @default true
|
|
46
|
+
*/
|
|
47
|
+
useCdn?: boolean;
|
|
48
|
+
/**
|
|
49
|
+
* 自定义凭证获取函数(覆盖内置 scene 路由)
|
|
50
|
+
* @description 后端接口非内置 scene 时使用;返回归一化后的凭证
|
|
51
|
+
*/
|
|
52
|
+
getCredential?: (http: ReturnType<typeof usePntConfig>['http'], params: GetCosCredentialParams) => Promise<NormalizedCosCredential>;
|
|
53
|
+
/**
|
|
54
|
+
* 自定义凭证响应归一化
|
|
55
|
+
* @description 后端字段不标准时使用;入参为内置 scene 接口返回的原始 data,需返回归一化凭证
|
|
56
|
+
*/
|
|
57
|
+
normalizeCredential?: NormalizeCredential;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* COS 上传结果
|
|
61
|
+
*/
|
|
62
|
+
export interface PntCosUploadResult {
|
|
63
|
+
/** 上传后的可访问 URL */
|
|
64
|
+
fileUrl: string;
|
|
65
|
+
/** COS 对象 Key */
|
|
66
|
+
cosKey: string;
|
|
67
|
+
/** 原始文件名 */
|
|
68
|
+
fileName: string;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* 单次请求覆盖参数
|
|
72
|
+
* @description 上传/下载入口的 gameId/task_id/pathKey 常来自表单或表格行数据,
|
|
73
|
+
* 可通过该参数在单次调用覆盖 hook 初始化时的配置
|
|
74
|
+
*/
|
|
75
|
+
export interface PntCosRequestOverrides {
|
|
76
|
+
/** 覆盖业务上下文(与初始化 context 浅合并,如行级 gameId/task_id) */
|
|
77
|
+
context?: Record<string, string | number>;
|
|
78
|
+
/** 覆盖网关 audience */
|
|
79
|
+
audience?: string;
|
|
80
|
+
/** 覆盖凭证场景 */
|
|
81
|
+
scene?: CosScene;
|
|
82
|
+
/** 覆盖业务路径 key */
|
|
83
|
+
pathKey?: string;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* COS 上传器实例
|
|
87
|
+
*/
|
|
88
|
+
export interface PntCosUploader {
|
|
89
|
+
/**
|
|
90
|
+
* 上传文件到 COS
|
|
91
|
+
* @param file 原始文件
|
|
92
|
+
* @param onProgress 上传进度回调(0~100)
|
|
93
|
+
* @param overrides 单次调用覆盖配置(表单/行级 gameId 等)
|
|
94
|
+
*/
|
|
95
|
+
upload: (file: File, onProgress?: (percent: number) => void, overrides?: PntCosRequestOverrides) => Promise<PntCosUploadResult>;
|
|
96
|
+
/**
|
|
97
|
+
* 获取文件预签名下载 URL(临时)
|
|
98
|
+
* @param cosKey COS 对象 Key,或完整 URL(自动剥离域名)
|
|
99
|
+
* @param overrides 单次调用覆盖配置(行级 gameId/task_id 等)
|
|
100
|
+
*/
|
|
101
|
+
getDownloadUrl: (cosKey: string, overrides?: PntCosRequestOverrides) => Promise<string>;
|
|
102
|
+
}
|
|
103
|
+
export type { CosScene, GetCosCredentialParams, NormalizeCredential, NormalizedCosCredential, };
|
|
104
|
+
export { normalizeSnakeCredential } from "../../api/cos";
|
|
105
|
+
/**
|
|
106
|
+
* 创建 COS 直传器实例(不依赖 React)
|
|
107
|
+
* @description 与 usePntCosUploader 共用同一套核心逻辑,适用于 class 兼容层、
|
|
108
|
+
* 工具函数、非 React 入口等场景;React 组件请优先使用 usePntCosUploader
|
|
109
|
+
*
|
|
110
|
+
* @param http 消费方 axios 实例(约定响应拦截器已解包 response.data)
|
|
111
|
+
* @param options 上传配置(固定值),或返回配置的 getter(需要动态配置时使用)
|
|
112
|
+
*
|
|
113
|
+
* @example
|
|
114
|
+
* ```ts
|
|
115
|
+
* const uploader = createPntCosUploader(http, { scene: 'default', pathKey: 'marketing_icon' });
|
|
116
|
+
* const { fileUrl } = await uploader.upload(file);
|
|
117
|
+
* ```
|
|
118
|
+
*/
|
|
119
|
+
export declare const createPntCosUploader: (http: PntHttpLike, options: PntCosUploadOptions | (() => PntCosUploadOptions)) => PntCosUploader;
|
|
120
|
+
/**
|
|
121
|
+
* COS 直传 Service Hook
|
|
122
|
+
* @description 内置凭证获取 + COS SDK 初始化 + 文件名策略 + URL 拼接,
|
|
123
|
+
* 收敛各项目重复的 4 份自实现;http 实例由 PntProvider 注入
|
|
124
|
+
*
|
|
125
|
+
* @param options 上传配置
|
|
126
|
+
*
|
|
127
|
+
* @example
|
|
128
|
+
* ```ts
|
|
129
|
+
* const uploader = usePntCosUploader({ scene: 'default', pathKey: 'marketing_icon' });
|
|
130
|
+
* const { fileUrl, cosKey } = await uploader.upload(file, (p) => setProgress(p));
|
|
131
|
+
* ```
|
|
132
|
+
*/
|
|
133
|
+
export declare const usePntCosUploader: (options: PntCosUploadOptions) => PntCosUploader;
|
|
134
|
+
export default usePntCosUploader;
|
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
import { fetchCosCredential } from "../../api/cos";
|
|
2
|
+
import { usePntConfig } from "../../config";
|
|
3
|
+
import { useMemo, useRef } from 'react';
|
|
4
|
+
import { v4 as uuidV4 } from 'uuid';
|
|
5
|
+
|
|
6
|
+
// cos-js-sdk-v5 / crypto-js 改为运行时动态 import(见 createCosClient / computeFileMd5):
|
|
7
|
+
// 不用上传功能的项目无需安装这两个包,Vite/webpack 不会把动态 import 当作硬依赖解析
|
|
8
|
+
|
|
9
|
+
// ==========================================
|
|
10
|
+
// 类型定义
|
|
11
|
+
// ==========================================
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* 文件名生成策略
|
|
15
|
+
* @description 内置三种策略;如需自定义可传函数
|
|
16
|
+
* - uuid: UUID v4 + 原扩展名(默认,最简单)
|
|
17
|
+
* - md5: 文件内容 MD5 + 原扩展名(与 pnt-tools CosApi 原策略一致)
|
|
18
|
+
* - timestamp: 时间戳(36进制)+自增计数器 + 原扩展名(与 SDK 定制场景原策略一致)
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* COS 上传配置
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* COS 上传结果
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* 单次请求覆盖参数
|
|
31
|
+
* @description 上传/下载入口的 gameId/task_id/pathKey 常来自表单或表格行数据,
|
|
32
|
+
* 可通过该参数在单次调用覆盖 hook 初始化时的配置
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* COS 上传器实例
|
|
37
|
+
*/
|
|
38
|
+
|
|
39
|
+
export { normalizeSnakeCredential } from "../../api/cos";
|
|
40
|
+
|
|
41
|
+
// ==========================================
|
|
42
|
+
// 工具函数
|
|
43
|
+
// ==========================================
|
|
44
|
+
|
|
45
|
+
/** 去除首尾斜杠 */
|
|
46
|
+
const trimSlash = (seg, leading = true) => seg.replace(/\/$/, '').replace(leading ? /^\// : '', '');
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* 提取文件扩展名(含点号)
|
|
50
|
+
*/
|
|
51
|
+
const getFileExt = filename => {
|
|
52
|
+
const idx = filename.lastIndexOf('.');
|
|
53
|
+
return idx > -1 ? filename.slice(idx) : '';
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* timestamp 策略计数器(同一会话内自增,避免同毫秒碰撞)
|
|
58
|
+
*/
|
|
59
|
+
let _timestampCounter = 0;
|
|
60
|
+
const generateTimestampName = () => `${Date.now().toString(36)}${(_timestampCounter++).toString(36)}`;
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* 计算文件内容 MD5(基于 crypto-js,与 pnt-tools CosApi 原策略一致)
|
|
64
|
+
* @description crypto-js 动态 import:仅 md5 文件名策略触发时加载
|
|
65
|
+
*/
|
|
66
|
+
const computeFileMd5 = async file => {
|
|
67
|
+
const {
|
|
68
|
+
default: CryptoJS
|
|
69
|
+
} = await import('crypto-js');
|
|
70
|
+
const buffer = await file.arrayBuffer();
|
|
71
|
+
return CryptoJS.MD5(CryptoJS.lib.WordArray.create(buffer)).toString();
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* 根据策略生成文件名(不含路径前缀)
|
|
76
|
+
*/
|
|
77
|
+
const generateFileName = async (file, strategy) => {
|
|
78
|
+
if (typeof strategy === 'function') {
|
|
79
|
+
return strategy(file);
|
|
80
|
+
}
|
|
81
|
+
const ext = getFileExt(file.name);
|
|
82
|
+
switch (strategy) {
|
|
83
|
+
case 'md5':
|
|
84
|
+
{
|
|
85
|
+
const md5 = await computeFileMd5(file);
|
|
86
|
+
return `${md5}${ext}`;
|
|
87
|
+
}
|
|
88
|
+
case 'timestamp':
|
|
89
|
+
return `${generateTimestampName()}${ext}`;
|
|
90
|
+
case 'uuid':
|
|
91
|
+
default:
|
|
92
|
+
return `${uuidV4()}${ext}`;
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* 拼接 COS Key 的目录前缀
|
|
98
|
+
* @description 优先级:filePath(可继续拼 pathKey 子目录) > path[pathKey] > 空
|
|
99
|
+
*/
|
|
100
|
+
const resolveBasePath = (cred, pathKey) => {
|
|
101
|
+
if (cred.filePath) {
|
|
102
|
+
// sdk_customize:受控目录前缀 + 业务自定义子目录
|
|
103
|
+
const prefix = cred.filePath.replace(/\/$/, '');
|
|
104
|
+
return pathKey ? `${prefix}/${trimSlash(pathKey)}` : prefix;
|
|
105
|
+
}
|
|
106
|
+
if (pathKey && cred.path) {
|
|
107
|
+
return cred.path[pathKey] || '';
|
|
108
|
+
}
|
|
109
|
+
return '';
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* 从 URL 或完整 Key 中剥离域名,得到纯 COS Key
|
|
114
|
+
*/
|
|
115
|
+
const stripHost = key => key.replace(/^https?:\/\/[^/]+/, '');
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* 用归一化凭证构造 COS SDK 实例
|
|
119
|
+
* @description cos-js-sdk-v5 动态 import:仅实际上传/下载触发时加载
|
|
120
|
+
*/
|
|
121
|
+
const createCosClient = async cred => {
|
|
122
|
+
const {
|
|
123
|
+
default: COS
|
|
124
|
+
} = await import('cos-js-sdk-v5');
|
|
125
|
+
return new COS({
|
|
126
|
+
getAuthorization: (_, callback) => {
|
|
127
|
+
callback({
|
|
128
|
+
TmpSecretId: cred.secretId,
|
|
129
|
+
TmpSecretKey: cred.secretKey,
|
|
130
|
+
SecurityToken: cred.sessionToken,
|
|
131
|
+
StartTime: cred.startTime,
|
|
132
|
+
ExpiredTime: cred.expiredTime
|
|
133
|
+
});
|
|
134
|
+
},
|
|
135
|
+
Protocol: 'https:',
|
|
136
|
+
FileParallelLimit: 3,
|
|
137
|
+
ChunkParallelLimit: 3,
|
|
138
|
+
ChunkSize: 1024 * 1024,
|
|
139
|
+
ProgressInterval: 1
|
|
140
|
+
});
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
// ==========================================
|
|
144
|
+
// createPntCosUploader 工厂(框架无关,可在非 React 上下文使用)
|
|
145
|
+
// ==========================================
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* 创建 COS 直传器实例(不依赖 React)
|
|
149
|
+
* @description 与 usePntCosUploader 共用同一套核心逻辑,适用于 class 兼容层、
|
|
150
|
+
* 工具函数、非 React 入口等场景;React 组件请优先使用 usePntCosUploader
|
|
151
|
+
*
|
|
152
|
+
* @param http 消费方 axios 实例(约定响应拦截器已解包 response.data)
|
|
153
|
+
* @param options 上传配置(固定值),或返回配置的 getter(需要动态配置时使用)
|
|
154
|
+
*
|
|
155
|
+
* @example
|
|
156
|
+
* ```ts
|
|
157
|
+
* const uploader = createPntCosUploader(http, { scene: 'default', pathKey: 'marketing_icon' });
|
|
158
|
+
* const { fileUrl } = await uploader.upload(file);
|
|
159
|
+
* ```
|
|
160
|
+
*/
|
|
161
|
+
export const createPntCosUploader = (http, options) => {
|
|
162
|
+
const getOptions = () => typeof options === 'function' ? options() : options;
|
|
163
|
+
const upload = async (file, onProgress, overrides) => {
|
|
164
|
+
const opts = getOptions();
|
|
165
|
+
const {
|
|
166
|
+
scene,
|
|
167
|
+
pathKey,
|
|
168
|
+
context,
|
|
169
|
+
audience,
|
|
170
|
+
fileNameStrategy = 'uuid',
|
|
171
|
+
useCdn = true,
|
|
172
|
+
getCredential,
|
|
173
|
+
normalizeCredential
|
|
174
|
+
} = opts;
|
|
175
|
+
// 单次调用覆盖(表单/行级 gameId、动态 pathKey 等),context 浅合并
|
|
176
|
+
const mergedContext = {
|
|
177
|
+
...context,
|
|
178
|
+
...overrides?.context
|
|
179
|
+
};
|
|
180
|
+
const mergedScene = overrides?.scene ?? scene;
|
|
181
|
+
const mergedAudience = overrides?.audience ?? audience;
|
|
182
|
+
const mergedPathKey = overrides?.pathKey ?? pathKey;
|
|
183
|
+
|
|
184
|
+
// 1. 获取凭证
|
|
185
|
+
const credParams = {
|
|
186
|
+
scene: mergedScene,
|
|
187
|
+
pathKey: mergedPathKey,
|
|
188
|
+
context: mergedContext,
|
|
189
|
+
audience: mergedAudience,
|
|
190
|
+
contentLength: file.size
|
|
191
|
+
};
|
|
192
|
+
const cred = getCredential ? await getCredential(http, credParams) : await fetchCosCredential(http, credParams, normalizeCredential);
|
|
193
|
+
|
|
194
|
+
// 2. 构造 COS Key
|
|
195
|
+
const basePath = resolveBasePath(cred, mergedPathKey);
|
|
196
|
+
const fileName = await generateFileName(file, fileNameStrategy);
|
|
197
|
+
const cosKey = basePath ? `${basePath.replace(/\/$/, '')}/${fileName}` : fileName;
|
|
198
|
+
|
|
199
|
+
// 3. putObject 上传
|
|
200
|
+
const cos = await createCosClient(cred);
|
|
201
|
+
return new Promise((resolve, reject) => {
|
|
202
|
+
cos.putObject({
|
|
203
|
+
Bucket: cred.bucket,
|
|
204
|
+
Region: cred.region,
|
|
205
|
+
Key: cosKey,
|
|
206
|
+
Body: file,
|
|
207
|
+
onProgress: onProgress ? progressData => {
|
|
208
|
+
onProgress(Math.round(progressData.percent * 100));
|
|
209
|
+
} : undefined
|
|
210
|
+
}, (err, data) => {
|
|
211
|
+
if (err) {
|
|
212
|
+
reject(err);
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
// URL 拼接:useCdn 且凭证带 cosHost 时走 CDN;否则用 COS Location
|
|
216
|
+
const fileUrl = useCdn && cred.cosHost ? `${cred.cosHost.replace(/\/$/, '')}/${cosKey}` : `https://${data.Location}`;
|
|
217
|
+
resolve({
|
|
218
|
+
fileUrl,
|
|
219
|
+
cosKey,
|
|
220
|
+
fileName: file.name
|
|
221
|
+
});
|
|
222
|
+
});
|
|
223
|
+
});
|
|
224
|
+
};
|
|
225
|
+
const getDownloadUrl = async (cosKey, overrides) => {
|
|
226
|
+
const opts = getOptions();
|
|
227
|
+
const {
|
|
228
|
+
scene,
|
|
229
|
+
pathKey,
|
|
230
|
+
context,
|
|
231
|
+
audience,
|
|
232
|
+
getCredential
|
|
233
|
+
} = opts;
|
|
234
|
+
// 单次调用覆盖(表格行级 gameId/task_id 等),context 浅合并
|
|
235
|
+
const mergedContext = {
|
|
236
|
+
...context,
|
|
237
|
+
...overrides?.context
|
|
238
|
+
};
|
|
239
|
+
const mergedScene = overrides?.scene ?? scene;
|
|
240
|
+
const mergedAudience = overrides?.audience ?? audience;
|
|
241
|
+
const mergedPathKey = overrides?.pathKey ?? pathKey;
|
|
242
|
+
// 下载凭证默认走 default_download 接口;sdk_customize 保持原 scene;
|
|
243
|
+
// 消费方传了 getCredential 时完全由其决定
|
|
244
|
+
const credParams = {
|
|
245
|
+
scene: getCredential ? mergedScene : mergedScene === 'sdk_customize' ? mergedScene : 'default_download',
|
|
246
|
+
pathKey: mergedPathKey,
|
|
247
|
+
context: mergedContext,
|
|
248
|
+
audience: mergedAudience,
|
|
249
|
+
fileUrl: cosKey
|
|
250
|
+
};
|
|
251
|
+
const cred = getCredential ? await getCredential(http, credParams) : await fetchCosCredential(http, credParams);
|
|
252
|
+
const cos = await createCosClient(cred);
|
|
253
|
+
return new Promise((resolve, reject) => {
|
|
254
|
+
cos.getObjectUrl({
|
|
255
|
+
Bucket: cred.bucket,
|
|
256
|
+
Region: cred.region,
|
|
257
|
+
Key: stripHost(cosKey),
|
|
258
|
+
Sign: true
|
|
259
|
+
}, (err, data) => {
|
|
260
|
+
if (err) {
|
|
261
|
+
reject(err);
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
resolve(data.Url);
|
|
265
|
+
});
|
|
266
|
+
});
|
|
267
|
+
};
|
|
268
|
+
return {
|
|
269
|
+
upload,
|
|
270
|
+
getDownloadUrl
|
|
271
|
+
};
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
// ==========================================
|
|
275
|
+
// usePntCosUploader Hook
|
|
276
|
+
// ==========================================
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* COS 直传 Service Hook
|
|
280
|
+
* @description 内置凭证获取 + COS SDK 初始化 + 文件名策略 + URL 拼接,
|
|
281
|
+
* 收敛各项目重复的 4 份自实现;http 实例由 PntProvider 注入
|
|
282
|
+
*
|
|
283
|
+
* @param options 上传配置
|
|
284
|
+
*
|
|
285
|
+
* @example
|
|
286
|
+
* ```ts
|
|
287
|
+
* const uploader = usePntCosUploader({ scene: 'default', pathKey: 'marketing_icon' });
|
|
288
|
+
* const { fileUrl, cosKey } = await uploader.upload(file, (p) => setProgress(p));
|
|
289
|
+
* ```
|
|
290
|
+
*/
|
|
291
|
+
export const usePntCosUploader = options => {
|
|
292
|
+
const {
|
|
293
|
+
http
|
|
294
|
+
} = usePntConfig();
|
|
295
|
+
// 用 ref 持有配置,避免 options 内联字面量导致 uploader 重建
|
|
296
|
+
const optionsRef = useRef(options);
|
|
297
|
+
optionsRef.current = options;
|
|
298
|
+
return useMemo(() => createPntCosUploader(http, () => optionsRef.current), [http]);
|
|
299
|
+
};
|
|
300
|
+
export default usePntCosUploader;
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import React, { FunctionComponent, ReactNode, Ref } from 'react';
|
|
2
|
+
import './index.scss';
|
|
3
|
+
interface PntGeneralLayoutProps {
|
|
4
|
+
/**
|
|
5
|
+
* 自定义类名
|
|
6
|
+
*/
|
|
7
|
+
className?: string;
|
|
8
|
+
/**
|
|
9
|
+
* 头部标题内容
|
|
10
|
+
* @description 传入后将渲染 Layout Header 区域
|
|
11
|
+
*/
|
|
12
|
+
headerTitle?: ReactNode;
|
|
13
|
+
/**
|
|
14
|
+
* 是否显示返回按钮
|
|
15
|
+
* @default false
|
|
16
|
+
*/
|
|
17
|
+
showBackButton?: boolean;
|
|
18
|
+
/**
|
|
19
|
+
* 返回按钮点击回调
|
|
20
|
+
*/
|
|
21
|
+
onBackButtonClick?: (() => void) | undefined;
|
|
22
|
+
/**
|
|
23
|
+
* 是否显示头部底部分割线
|
|
24
|
+
* @default true
|
|
25
|
+
*/
|
|
26
|
+
headerBordered?: boolean;
|
|
27
|
+
/**
|
|
28
|
+
* 子头部内容
|
|
29
|
+
* @description 位于 Header 下方,常用于放置面包屑、Tab 等
|
|
30
|
+
*/
|
|
31
|
+
subHeaderContent?: ReactNode;
|
|
32
|
+
/**
|
|
33
|
+
* 头部右侧操作区
|
|
34
|
+
* @description 常用于放置按钮组
|
|
35
|
+
*/
|
|
36
|
+
operation?: ReactNode;
|
|
37
|
+
/**
|
|
38
|
+
* 内容区 ref
|
|
39
|
+
*/
|
|
40
|
+
bodyRef?: Ref<HTMLDivElement>;
|
|
41
|
+
/**
|
|
42
|
+
* 页面主体内容
|
|
43
|
+
*/
|
|
44
|
+
children?: ReactNode;
|
|
45
|
+
/**
|
|
46
|
+
* 内容区自定义样式
|
|
47
|
+
*/
|
|
48
|
+
bodyStyle?: React.CSSProperties;
|
|
49
|
+
/**
|
|
50
|
+
* 内容区内边距
|
|
51
|
+
* @description 数字按 px 处理;需要全出血内容时可传 '24px 0'
|
|
52
|
+
* @default 24
|
|
53
|
+
*/
|
|
54
|
+
bodyPadding?: number | string;
|
|
55
|
+
/**
|
|
56
|
+
* 内容区卡片圆角(px)
|
|
57
|
+
* @description 作用于 body 内 t-card / tea-card / 各业务 wrapper、block 容器
|
|
58
|
+
* @default 4
|
|
59
|
+
*/
|
|
60
|
+
cardRadius?: number;
|
|
61
|
+
/**
|
|
62
|
+
* 底部按钮样式模式
|
|
63
|
+
* @description fixed-统一下按钮尺寸(高 36px、最小宽 92px、加粗中号字);
|
|
64
|
+
* auto-不约束按钮尺寸,仅保留按钮间距
|
|
65
|
+
* @default 'fixed'
|
|
66
|
+
*/
|
|
67
|
+
footerButton?: 'fixed' | 'auto';
|
|
68
|
+
/**
|
|
69
|
+
* 底部内容
|
|
70
|
+
* @description 传入后将渲染 Layout Footer 区域,常用于放置操作按钮
|
|
71
|
+
*/
|
|
72
|
+
footer?: ReactNode;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* 通用页面布局组件(L3 布局组件)
|
|
76
|
+
* @description 提供标准的「头部 + 子头部 + 内容区 + 底部」页面布局结构,
|
|
77
|
+
* 基于 TDesign Layout(flex column)封装,内容区自动填满剩余高度,
|
|
78
|
+
* 适用于后台管理系统的列表页、详情页、表单页等场景。
|
|
79
|
+
*
|
|
80
|
+
* @example
|
|
81
|
+
* ```tsx
|
|
82
|
+
* // 基础用法:仅内容区
|
|
83
|
+
* <PntGeneralLayout>
|
|
84
|
+
* <div>页面内容</div>
|
|
85
|
+
* </PntGeneralLayout>
|
|
86
|
+
*
|
|
87
|
+
* // 带标题和操作按钮
|
|
88
|
+
* <PntGeneralLayout
|
|
89
|
+
* headerTitle="页面标题"
|
|
90
|
+
* operation={<><Button>保存</Button><Button>取消</Button></>}
|
|
91
|
+
* >
|
|
92
|
+
* <Form />
|
|
93
|
+
* </PntGeneralLayout>
|
|
94
|
+
*
|
|
95
|
+
* // 带返回按钮和底部
|
|
96
|
+
* <PntGeneralLayout
|
|
97
|
+
* headerTitle="详情页"
|
|
98
|
+
* showBackButton
|
|
99
|
+
* onBackButtonClick={() => history.back()}
|
|
100
|
+
* footer={<><Button>提交</Button><Button>重置</Button></>}
|
|
101
|
+
* >
|
|
102
|
+
* <Detail />
|
|
103
|
+
* </PntGeneralLayout>
|
|
104
|
+
*
|
|
105
|
+
* // 全出血内容区(无头部边线、内容无左右内边距、底部按钮不强制尺寸)
|
|
106
|
+
* <PntGeneralLayout
|
|
107
|
+
* headerBordered={false}
|
|
108
|
+
* bodyPadding="24px 0"
|
|
109
|
+
* footerButton="auto"
|
|
110
|
+
* />
|
|
111
|
+
* ```
|
|
112
|
+
*/
|
|
113
|
+
declare const PntGeneralLayout: FunctionComponent<PntGeneralLayoutProps>;
|
|
114
|
+
export default PntGeneralLayout;
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { ArrowLeftIcon } from 'tdesign-icons-react';
|
|
3
|
+
import { Layout } from 'tdesign-react';
|
|
4
|
+
import "./index.scss";
|
|
5
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
6
|
+
import { jsxs as _jsxs } from "react/jsx-runtime";
|
|
7
|
+
/**
|
|
8
|
+
* 通用页面布局组件(L3 布局组件)
|
|
9
|
+
* @description 提供标准的「头部 + 子头部 + 内容区 + 底部」页面布局结构,
|
|
10
|
+
* 基于 TDesign Layout(flex column)封装,内容区自动填满剩余高度,
|
|
11
|
+
* 适用于后台管理系统的列表页、详情页、表单页等场景。
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```tsx
|
|
15
|
+
* // 基础用法:仅内容区
|
|
16
|
+
* <PntGeneralLayout>
|
|
17
|
+
* <div>页面内容</div>
|
|
18
|
+
* </PntGeneralLayout>
|
|
19
|
+
*
|
|
20
|
+
* // 带标题和操作按钮
|
|
21
|
+
* <PntGeneralLayout
|
|
22
|
+
* headerTitle="页面标题"
|
|
23
|
+
* operation={<><Button>保存</Button><Button>取消</Button></>}
|
|
24
|
+
* >
|
|
25
|
+
* <Form />
|
|
26
|
+
* </PntGeneralLayout>
|
|
27
|
+
*
|
|
28
|
+
* // 带返回按钮和底部
|
|
29
|
+
* <PntGeneralLayout
|
|
30
|
+
* headerTitle="详情页"
|
|
31
|
+
* showBackButton
|
|
32
|
+
* onBackButtonClick={() => history.back()}
|
|
33
|
+
* footer={<><Button>提交</Button><Button>重置</Button></>}
|
|
34
|
+
* >
|
|
35
|
+
* <Detail />
|
|
36
|
+
* </PntGeneralLayout>
|
|
37
|
+
*
|
|
38
|
+
* // 全出血内容区(无头部边线、内容无左右内边距、底部按钮不强制尺寸)
|
|
39
|
+
* <PntGeneralLayout
|
|
40
|
+
* headerBordered={false}
|
|
41
|
+
* bodyPadding="24px 0"
|
|
42
|
+
* footerButton="auto"
|
|
43
|
+
* />
|
|
44
|
+
* ```
|
|
45
|
+
*/
|
|
46
|
+
const PntGeneralLayout = prop => {
|
|
47
|
+
const {
|
|
48
|
+
className,
|
|
49
|
+
headerTitle,
|
|
50
|
+
showBackButton = false,
|
|
51
|
+
onBackButtonClick,
|
|
52
|
+
headerBordered = true,
|
|
53
|
+
subHeaderContent,
|
|
54
|
+
operation,
|
|
55
|
+
bodyRef,
|
|
56
|
+
children,
|
|
57
|
+
bodyStyle,
|
|
58
|
+
bodyPadding = 24,
|
|
59
|
+
cardRadius = 4,
|
|
60
|
+
footerButton = 'fixed',
|
|
61
|
+
footer
|
|
62
|
+
} = prop;
|
|
63
|
+
const {
|
|
64
|
+
Header,
|
|
65
|
+
Footer
|
|
66
|
+
} = Layout;
|
|
67
|
+
const layoutClassName = ['pnt-general-layout', !headerBordered && 'pnt-general-layout--borderless', footerButton === 'auto' && 'pnt-general-layout--footer-auto', className].filter(Boolean).join(' ');
|
|
68
|
+
const cssVars = {
|
|
69
|
+
'--pnt-layout-body-padding': typeof bodyPadding === 'number' ? `${bodyPadding}px` : bodyPadding,
|
|
70
|
+
'--pnt-layout-card-radius': `${cardRadius}px`
|
|
71
|
+
};
|
|
72
|
+
return /*#__PURE__*/_jsxs(Layout, {
|
|
73
|
+
className: layoutClassName,
|
|
74
|
+
style: cssVars,
|
|
75
|
+
children: [headerTitle && /*#__PURE__*/_jsxs(Header, {
|
|
76
|
+
children: [/*#__PURE__*/_jsxs("span", {
|
|
77
|
+
className: "header-title",
|
|
78
|
+
children: [showBackButton && /*#__PURE__*/_jsx(ArrowLeftIcon, {
|
|
79
|
+
className: "goback-icon",
|
|
80
|
+
size: "24px",
|
|
81
|
+
onClick: onBackButtonClick
|
|
82
|
+
}), /*#__PURE__*/_jsx("span", {
|
|
83
|
+
className: "title",
|
|
84
|
+
children: headerTitle
|
|
85
|
+
})]
|
|
86
|
+
}), operation && /*#__PURE__*/_jsx("span", {
|
|
87
|
+
className: "header-operation",
|
|
88
|
+
children: operation
|
|
89
|
+
})]
|
|
90
|
+
}), subHeaderContent && /*#__PURE__*/_jsx("div", {
|
|
91
|
+
className: "general-layout-sub-header",
|
|
92
|
+
children: subHeaderContent
|
|
93
|
+
}), /*#__PURE__*/_jsx("div", {
|
|
94
|
+
className: "general-layout-body",
|
|
95
|
+
ref: bodyRef,
|
|
96
|
+
style: bodyStyle,
|
|
97
|
+
children: children
|
|
98
|
+
}), footer && /*#__PURE__*/_jsx(Footer, {
|
|
99
|
+
children: footer
|
|
100
|
+
})]
|
|
101
|
+
});
|
|
102
|
+
};
|
|
103
|
+
export default PntGeneralLayout;
|