@base-web-kits/base-tools-ts 0.9.5 → 0.9.6

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.
@@ -0,0 +1,120 @@
1
+ // 兼容oss后期添加的参数
2
+ type OtherOption = Record<string, string | number | boolean | undefined>;
3
+
4
+ export type OSSGravity = 'nw' | 'north' | 'ne' | 'west' | 'center' | 'east' | 'sw' | 'south' | 'se';
5
+
6
+ export type OSSFormat = 'png' | 'jpg' | 'jpeg' | 'webp' | 'bmp' | 'gif' | 'tiff' | 'heic' | 'avif';
7
+
8
+ export type OSSResizeOption = {
9
+ p?: number;
10
+ w?: number;
11
+ h?: number;
12
+ m?: 'lfit' | 'mfit' | 'fill' | 'pad' | 'fixed';
13
+ l?: number;
14
+ s?: number;
15
+ limit?: 1 | 0;
16
+ color?: string;
17
+ } & OtherOption;
18
+
19
+ export type OSSCropOption = {
20
+ w?: number;
21
+ h?: number;
22
+ x?: number;
23
+ y?: number;
24
+ g?: OSSGravity;
25
+ p?: number;
26
+ } & OtherOption;
27
+
28
+ export type OSSIndexCropOption = {
29
+ x?: number;
30
+ y?: number;
31
+ i?: number;
32
+ } & OtherOption;
33
+
34
+ export type OSSQualityOption = {
35
+ q?: number;
36
+ Q?: number;
37
+ } & OtherOption;
38
+
39
+ export type OSSWatermarkOption = {
40
+ type?: string;
41
+ text?: string;
42
+ size?: number;
43
+ color?: string;
44
+ shadow?: number;
45
+ t?: number;
46
+ g?: OSSGravity;
47
+ x?: number;
48
+ y?: number;
49
+ voffset?: number;
50
+ fill?: 0 | 1;
51
+ padx?: number;
52
+ pady?: number;
53
+ image?: string;
54
+ P?: number;
55
+ } & OtherOption;
56
+
57
+ export type OSSBlurOption = {
58
+ r: number;
59
+ s: number;
60
+ g?: 'face' | 'faces';
61
+ p?: number;
62
+ } & OtherOption;
63
+
64
+ export type OSSRoundedCornersOption = {
65
+ r: number;
66
+ } & OtherOption;
67
+
68
+ export type OSSCircleOption = {
69
+ r: number;
70
+ } & OtherOption;
71
+
72
+ export type OSSImgOption = {
73
+ resize?: OSSResizeOption;
74
+ watermark?: OSSWatermarkOption;
75
+ flip?: 0 | 1 | 2;
76
+ crop?: OSSCropOption;
77
+ quality?: OSSQualityOption;
78
+ format?: OSSFormat;
79
+ info?: boolean;
80
+ 'auto-orient'?: 0 | 1;
81
+ circle?: OSSCircleOption;
82
+ indexcrop?: OSSIndexCropOption;
83
+ 'rounded-corners'?: OSSRoundedCornersOption;
84
+ blur?: OSSBlurOption;
85
+ rotate?: number;
86
+ interlace?: 0 | 1;
87
+ 'average-hue'?: boolean;
88
+ bright?: number;
89
+ sharpen?: number;
90
+ contrast?: number;
91
+ } & {
92
+ [action: string]: number | string | boolean | Record<string, unknown> | undefined;
93
+ };
94
+
95
+ export type OSSVideoOption = {
96
+ convert?: Record<string, unknown>;
97
+ animation?: Record<string, unknown>;
98
+ sprite?: Record<string, unknown>;
99
+ snapshots?: Record<string, unknown>;
100
+ concat?: Record<string, unknown>;
101
+ info?: boolean;
102
+ } & {
103
+ [action: string]: number | string | boolean | Record<string, unknown> | undefined;
104
+ };
105
+
106
+ export type OSSAudioOption = {
107
+ convert?: Record<string, unknown>;
108
+ concat?: Record<string, unknown>;
109
+ info?: boolean;
110
+ } & {
111
+ [action: string]: number | string | boolean | Record<string, unknown> | undefined;
112
+ };
113
+
114
+ export type OSSHlsOption = {
115
+ m3u8?: boolean | Record<string, unknown>;
116
+ } & {
117
+ [action: string]: number | string | boolean | Record<string, unknown> | undefined;
118
+ };
119
+
120
+ export type OSSOption = OSSImgOption | OSSVideoOption | OSSAudioOption | OSSHlsOption;
@@ -0,0 +1,168 @@
1
+ import type {
2
+ OSSOption,
3
+ OSSAudioOption,
4
+ OSSHlsOption,
5
+ OSSImgOption,
6
+ OSSVideoOption,
7
+ OSSWatermarkOption,
8
+ } from './index.d';
9
+
10
+ export * from './index.d';
11
+
12
+ /**
13
+ * oss图片处理
14
+ * 参考官方文档: https://help.aliyun.com/zh/oss/user-guide/img-parameters/?spm=a2c4g.11186623.help-menu-31815.d_0_11_2_1.572824a1a1W5Pf&scm=20140722.H_144582._.OR_help-T_cn~zh-V_1
15
+ * @param src 原始图片URL
16
+ * @param option 图片处理选项
17
+ * @returns 处理后的图片URL(格式: `{src}?x-oss-process=image/xx`)
18
+ * @example
19
+ * 缩放: getOSSImg('xx.jpg', { resize: { w: 100, h: 100 } })
20
+ * 水印: getOSSImg('xx.jpg', { watermark: { text: '水印' } });
21
+ * 翻转: getOSSImg('xx.jpg', { flip: 1 });
22
+ * 裁剪: getOSSImg('xx.jpg', { crop: { w: 100, h: 100 } });
23
+ * 质量: getOSSImg('xx.jpg', { quality: { q: 80 } });
24
+ * 格式转换: getOSSImg('xx.jpg', { format: 'jpg' });
25
+ * 获取信息: getOSSImg('xx.jpg', { info: true });
26
+ * 自适应方向: getOSSImg('xx.jpg', { 'auto-orient': 1 });
27
+ * 内切圆: getOSSImg('xx.jpg', { circle: { r: 100 } });
28
+ * 索引切割: getOSSImg('xx.jpg', { indexcrop: { x: 100 } });
29
+ * 圆角: getOSSImg('xx.jpg', { 'rounded-corners': { r: 10 } });
30
+ * 模糊: getOSSImg('xx.jpg', { blur: { r: 10, s: 10 } });
31
+ * 旋转: getOSSImg('xx.jpg', { rotate: 90 });
32
+ * 渐进显示: getOSSImg('xx.jpg', { interlace: 1 });
33
+ * 主色调: getOSSImg('xx.jpg', { 'average-hue': true });
34
+ * 亮度: getOSSImg('xx.jpg', { bright: 10 });
35
+ * 锐化: getOSSImg('xx.jpg', { sharpen: 100 });
36
+ * 对比度: getOSSImg('xx.jpg', { contrast: 100 });
37
+ */
38
+ export function getOSSImg(src: string, option: OSSImgOption) {
39
+ return buildOSSUrl(src, 'image', option);
40
+ }
41
+
42
+ /**
43
+ * oss视频处理
44
+ * 参考官方文档: https://help.aliyun.com/zh/oss/user-guide/audio-and-video-processing/
45
+ * @param src 原始视频URL
46
+ * @param option 视频处理选项
47
+ * @returns 处理后的URL(格式: `{src}?x-oss-process=video/xx`)
48
+ * @example
49
+ * 视频转码: getOSSVideo('xx.mp4', { convert: { format: 'mp4' } })
50
+ * 转为动图: getOSSVideo('xx.mp4', { animation: { format: 'gif' } })
51
+ * 雪碧图: getOSSVideo('xx.mp4', { sprite: { format: 'png' } })
52
+ * 多帧截取: getOSSVideo('xx.mp4', { snapshots: { count: 3 } })
53
+ * 视频拼接: getOSSVideo('xx.mp4', { concat: { list: 'a.mp4,b.mp4' } })
54
+ * 信息查询: getOSSVideo('xx.mp4', { info: true })
55
+ * 组合操作: getOSSVideo('xx.mp4', { convert: { format: 'mp4' }, snapshots: { count: 3 } })
56
+ */
57
+ export function getOSSVideo(src: string, option: OSSVideoOption) {
58
+ return buildOSSUrl(src, 'video', option);
59
+ }
60
+
61
+ /**
62
+ * oss音频处理
63
+ * 参考官方文档: https://help.aliyun.com/zh/oss/user-guide/audio-and-video-processing/
64
+ * @param src 原始音频URL
65
+ * @param option 音频处理选项
66
+ * @returns 处理后的URL(格式: `{src}?x-oss-process=audio/xx`)
67
+ * @example
68
+ * 音频转码: getOSSAudio('xx.mp3', { 'convert': { format: 'mp3' } })
69
+ * 音频拼接: getOSSAudio('xx.mp3', { 'concat': { list: 'a.mp3,b.mp3' } })
70
+ * 信息查询: getOSSAudio('xx.mp3', { 'info': true })
71
+ */
72
+ export function getOSSAudio(src: string, option: OSSAudioOption) {
73
+ return buildOSSUrl(src, 'audio', option);
74
+ }
75
+
76
+ /**
77
+ * oss直播处理(边转边播 HLS)
78
+ * 参考官方文档: https://help.aliyun.com/zh/oss/user-guide/audio-and-video-processing/
79
+ * @param src 原始视频URL
80
+ * @param option HLS 选项(或布尔)
81
+ * @returns 处理后的URL(格式: `{src}?x-oss-process=hls/xx`)
82
+ * @example
83
+ * 生成播放列表: getOSSHls('xx.mp4', { 'm3u8': true })
84
+ * 配置参数: getOSSHls('xx.mp4', { 'm3u8': { playlist: 1, segtime: 6 } })
85
+ */
86
+ export function getOSSHls(src: string, option: OSSHlsOption) {
87
+ return buildOSSUrl(src, 'hls', option);
88
+ }
89
+
90
+ /**
91
+ * 构造oss处理地址
92
+ * @param src 原始地址
93
+ * @param type oss处理类型,如`image`, `audio`, `video`, `hls`
94
+ * @param option oss处理选项
95
+ * @returns 处理后的URL(格式: `{src}?x-oss-process={type}/{segs.join('/')}`)
96
+ */
97
+ export function buildOSSUrl(src: string, type: string, option: OSSOption) {
98
+ if (!src || !option) return src;
99
+ if (src.startsWith('blob:')) return src;
100
+ if (src.includes('.svg')) return src;
101
+
102
+ const segs: string[] = [];
103
+
104
+ // 遍历选项,构造处理参数
105
+ for (const [k, v] of Object.entries(option)) {
106
+ const seg = k === 'watermark' ? getWatermark(v as OSSWatermarkOption) : getOSSSegs(k, v);
107
+ if (seg) segs.push(seg);
108
+ }
109
+
110
+ if (!segs.length) return src;
111
+
112
+ // 拼接处理参数(先移除查询参数,避免重复拼接)
113
+ const base = src.split('?')[0];
114
+ return `${base}?x-oss-process=${type}/${segs.join('/')}`;
115
+ }
116
+
117
+ /**
118
+ * 构造图片处理参数
119
+ * @param type 图片处理类型,如`resize`, `flip`, `format`, `info`
120
+ * @param option 图片处理选项
121
+ * @returns `object`返回格式为`resize,w_100,h_100`
122
+ * @returns `number`返回格式为`flip,1`
123
+ * @returns `string`返回格式为`format,jpg`
124
+ * @returns `true`返回格式为`info`, `false`返回空字符串
125
+ */
126
+ function getOSSSegs(type: string, option?: Record<string, unknown> | number | string | boolean) {
127
+ if (!option && option !== 0) return '';
128
+
129
+ if (option === true) return type;
130
+
131
+ if (typeof option === 'number' || typeof option === 'string') return `${type},${option}`;
132
+
133
+ const segs = Object.entries(option)
134
+ .map(([k, v]) => `${k}_${v}`)
135
+ .join(',');
136
+
137
+ return segs ? `${type},${segs}` : '';
138
+ }
139
+
140
+ /**
141
+ * 图片水印 (文本和图片已Base64编码)
142
+ * @returns 格式: `watermark,text_xxx`
143
+ */
144
+ function getWatermark(w?: OSSWatermarkOption) {
145
+ if (!w) return '';
146
+ if (w.image) w.image = toBase64Url(w.image);
147
+ if (w.text) w.text = toBase64Url(w.text);
148
+ if (w.type) w.type = toBase64Url(w.type);
149
+ return getOSSSegs('watermark', w);
150
+ }
151
+
152
+ /**
153
+ * Base64编码
154
+ */
155
+ function toBase64Url(s: string) {
156
+ let b64 = '';
157
+ if (typeof Buffer !== 'undefined') {
158
+ const buf = Buffer.from(s, 'utf-8');
159
+ b64 = buf.toString('base64');
160
+ } else {
161
+ try {
162
+ b64 = btoa(unescape(encodeURIComponent(s)));
163
+ } catch {
164
+ b64 = '';
165
+ }
166
+ }
167
+ return b64.replace(/=+$/g, '').replace(/\+/g, '-').replace(/\//g, '_');
168
+ }
@@ -0,0 +1,119 @@
1
+ /**
2
+ * 获取url的查询参数值
3
+ * - 采用纯JS解析,因为小程序不支持URLSearchParams
4
+ * @param key 参数名
5
+ * @param url 完整 URL 或仅查询串(如 "a=1&b=2")
6
+ * @returns 解码后的参数值 (若不存在|"null"|"undefined",则返回 null)
7
+ * @example
8
+ * const q = getUrlParam('q', 'https://a.com/?q=%E6%B5%8B%E8%AF%95'); // "测试"
9
+ * const a = getUrlParam('a', 'a=1'); // "1"
10
+ * const list = getUrlParam('list', 'list=[1,2]'); // "[1,2]"
11
+ * const list = getUrlParam('list', 'list=null'); // null
12
+ * const list = getUrlParam('list', 'list=undefined'); // null
13
+ */
14
+ export function getUrlParam(key: string, url: string) {
15
+ const raw = url.includes('?') ? url.slice(url.indexOf('?') + 1) : url.includes('=') ? url : '';
16
+ const qs = raw.split('#')[0];
17
+ if (!qs) return null;
18
+ const pairs = qs.split('&').filter(Boolean);
19
+ const decode = (s: string) => {
20
+ try {
21
+ return decodeURIComponent(s.replace(/\+/g, ' '));
22
+ } catch {
23
+ return s;
24
+ }
25
+ };
26
+ for (const pair of pairs) {
27
+ const i = pair.indexOf('=');
28
+ const k = i >= 0 ? pair.slice(0, i) : pair;
29
+ if (decode(k) === key) {
30
+ const v = i >= 0 ? decode(pair.slice(i + 1)) : '';
31
+ return v !== 'null' && v !== 'undefined' ? v : null;
32
+ }
33
+ }
34
+ return null;
35
+ }
36
+
37
+ /**
38
+ * 获取url的查询参数值,并转为number类型
39
+ * @param key 参数名
40
+ * @param url 完整 URL 或仅查询串(如 "a=1&b=2")
41
+ * @returns 解码后的参数值 (若不存在|"非数字字符串",则返回 null)
42
+ * @example
43
+ * const a = getUrlNumber('a', 'https://a.com/?a=1'); // 1
44
+ * const a = getUrlNumber('a', 'a=1'); // 1
45
+ * const a = getUrlNumber('a', 'a=1.2'); // 1.2
46
+ * const a = getUrlNumber('a', 'a=abc'); // null
47
+ */
48
+ export function getUrlNumber(key: string, url: string) {
49
+ const str = getUrlParam(key, url);
50
+ if (!str) return null;
51
+
52
+ const num = Number(str);
53
+ return isNaN(num) ? null : num;
54
+ }
55
+
56
+ /**
57
+ * 获取url的所有查询参数值
58
+ * - 采用纯JS解析,因为小程序不支持URLSearchParams
59
+ * @param url 完整 URL 或仅查询串(如 "a=1&b=2")
60
+ * @returns 解码后的键值对象(无参数返回空对象; "null"|"undefined"的参数会被忽略)
61
+ * @example
62
+ * const params = getUrlParamAll('a=1&b=2'); // { a: "1", b: "2" }
63
+ * const params = getUrlParamAll('https://a.com/?a=1&b=2'); // { a: "1", b: "2" }
64
+ * const params = getUrlParamAll('a=1&b=null'); // { a: "1" }
65
+ * const params = getUrlParamAll('a=1&b=undefined'); // { a: "1" }
66
+ */
67
+ export function getUrlParamAll(url: string) {
68
+ const raw = url.includes('?') ? url.slice(url.indexOf('?') + 1) : url.includes('=') ? url : '';
69
+ const qs = raw.split('#')[0];
70
+ const result: Record<string, string> = {};
71
+ if (!qs) return result;
72
+ const decode = (s: string) => {
73
+ try {
74
+ return decodeURIComponent(s.replace(/\+/g, ' '));
75
+ } catch {
76
+ return s;
77
+ }
78
+ };
79
+ for (const seg of qs.split('&')) {
80
+ if (!seg) continue;
81
+ const i = seg.indexOf('=');
82
+ const k = i >= 0 ? seg.slice(0, i) : seg;
83
+ const v = i >= 0 ? seg.slice(i + 1) : '';
84
+ const dv = decode(v);
85
+ if (dv !== 'null' && dv !== 'undefined') {
86
+ result[decode(k)] = dv;
87
+ }
88
+ }
89
+ return result;
90
+ }
91
+
92
+ /**
93
+ * 将对象参数拼接到 URL
94
+ * - 采用纯JS拼接,因为小程序不支持URLSearchParams
95
+ * @param url 基础地址
96
+ * @param param 将要追加的参数对象;`null/undefined` 值会被忽略,Object 会使用 `JSON.stringify`
97
+ * @returns 拼接后的完整 URL(保留原有哈希片段)
98
+ * @example
99
+ * const url = appendUrlParam('https://a.com', { q: '测试', list: [1, 2], a: null, b: undefined }); // 'https://a.com/?q=%E6%B5%8B%E8%AF%95&list=[1,2]'
100
+ */
101
+ export function appendUrlParam(url: string, param: Record<string, unknown>) {
102
+ if (!param || typeof param !== 'object') return url;
103
+
104
+ const hashIndex = url.indexOf('#');
105
+ const baseWithoutHash = hashIndex >= 0 ? url.slice(0, hashIndex) : url;
106
+ const hash = hashIndex >= 0 ? url.slice(hashIndex) : '';
107
+
108
+ const [base, existingQs] = baseWithoutHash.split('?');
109
+ const parts: string[] = [];
110
+ if (existingQs) parts.push(existingQs);
111
+ for (const key in param) {
112
+ const rawVal = param[key];
113
+ if (rawVal === null || rawVal === undefined) continue;
114
+ const val = typeof rawVal === 'object' ? JSON.stringify(rawVal) : String(rawVal);
115
+ parts.push(`${encodeURIComponent(key)}=${encodeURIComponent(val)}`);
116
+ }
117
+ const qs = parts.filter(Boolean).join('&');
118
+ return base + (qs ? `?${qs}` : '') + hash;
119
+ }
@@ -0,0 +1,103 @@
1
+ type QnOtherOption = Record<string, string | number | boolean | undefined>;
2
+
3
+ export type QnImageView2Option = {
4
+ mode?: 0 | 1 | 2 | 3 | 4 | 5;
5
+ w?: number;
6
+ h?: number;
7
+ l?: number;
8
+ limit?: 0 | 1;
9
+ q?: number;
10
+ format?: string;
11
+ } & QnOtherOption;
12
+
13
+ export type QnMogr2Option = {
14
+ thumbnail?: string;
15
+ crop?: string;
16
+ rotate?: number;
17
+ 'auto-orient'?: boolean;
18
+ format?: string;
19
+ interlace?: 0 | 1;
20
+ background?: string;
21
+ q?: number;
22
+ blur?: string | { r: number; s: number };
23
+ colors?: number;
24
+ } & QnOtherOption;
25
+
26
+ export type QnWatermarkOption = {
27
+ type?: 'image' | 'text' | number;
28
+ image?: string;
29
+ text?: string;
30
+ font?: string;
31
+ fontsize?: number;
32
+ fill?: string;
33
+ gravity?: string;
34
+ dx?: number;
35
+ dy?: number;
36
+ dissolve?: number;
37
+ } & QnOtherOption;
38
+
39
+ export type QnImgOption = {
40
+ imageView2?: QnImageView2Option;
41
+ imageMogr2?: QnMogr2Option;
42
+ watermark?: QnWatermarkOption;
43
+ imageslim?: boolean;
44
+ imageInfo?: boolean;
45
+ } & {
46
+ thumbnail?: string;
47
+ crop?: string;
48
+ rotate?: number;
49
+ 'auto-orient'?: boolean;
50
+ format?: string;
51
+ interlace?: 0 | 1;
52
+ background?: string;
53
+ q?: number;
54
+ blur?: string | { r: number; s: number };
55
+ colors?: number;
56
+ [action: string]: number | string | boolean | Record<string, unknown> | undefined;
57
+ };
58
+
59
+ export type QnAvthumbOption = {
60
+ format?: string;
61
+ s?: string;
62
+ vcodec?: 'libx264' | 'libx265' | 'copy' | string;
63
+ vb?: string; // e.g. '1.25m', '128k', support '!'
64
+ r?: number; // frame rate
65
+ acodec?: 'libmp3lame' | 'libfdk_aac' | 'copy' | string;
66
+ ab?: string; // e.g. '128k', support '!'
67
+ ar?: number; // sampling rate
68
+ } & QnOtherOption;
69
+
70
+ export type QnVframeOption = {
71
+ format?: 'jpg' | 'png';
72
+ offset?: number; // seconds, support decimals
73
+ w?: number;
74
+ h?: number;
75
+ } & QnOtherOption;
76
+
77
+ export type QnVideoOption = {
78
+ avthumb?: QnAvthumbOption;
79
+ vframe?: QnVframeOption;
80
+ } & {
81
+ [action: string]: number | string | boolean | Record<string, unknown> | undefined;
82
+ };
83
+
84
+ export type QnAudioOption = {
85
+ avthumb?: QnAvthumbOption;
86
+ } & {
87
+ [action: string]: number | string | boolean | Record<string, unknown> | undefined;
88
+ };
89
+
90
+ export type QnHlsOption = {
91
+ level?: number; // e.g. 3 for 720p preset
92
+ format?: 'm3u8';
93
+ segtime?: number; // 2-10 seconds
94
+ t?: string; // duration like '1.500s'
95
+ vcodec?: 'libx264' | 'libx265' | 'copy' | string;
96
+ vb?: string; // bitrate e.g. '1.25m', '128k', support '!'
97
+ r?: number; // frame rate [1,60]
98
+ s?: string; // resolution '1280x720'
99
+ acodec?: 'libmp3lame' | 'libfdk_aac' | 'copy' | string;
100
+ ab?: string; // audio bitrate
101
+ ar?: number; // audio sampling rate
102
+ output?: string; // m3u8 filename (url-safe base64)
103
+ } & QnOtherOption;