hifun-tools 1.3.29 → 1.3.31
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/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/init/index.d.ts +43 -28
- package/dist/init/index.js +146 -119
- package/dist/init/utils.d.ts +23 -4
- package/dist/init/utils.js +133 -138
- package/dist/msg/index.d.ts +18 -0
- package/dist/msg/index.js +270 -0
- package/package.json +5 -1
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
package/dist/init/index.d.ts
CHANGED
|
@@ -1,42 +1,57 @@
|
|
|
1
|
-
import { filterSmartLines, getOptimalDecodedString,
|
|
1
|
+
import { isDomainMatch, filterSmartLines, getOptimalDecodedString, matchBrowser, TenantDict, toStandardUrl } from "./utils";
|
|
2
2
|
import { AesDecrypt, AesEncrypt } from "./ende";
|
|
3
|
-
type TenantConfig = {
|
|
4
|
-
PUBLIC_TITLE: string;
|
|
5
|
-
SITE_TITLE: string;
|
|
6
|
-
SITE_URL: string;
|
|
7
|
-
USER_TENANT: string;
|
|
8
|
-
};
|
|
9
3
|
declare class InitCls {
|
|
4
|
+
AesDecrypt: typeof AesDecrypt;
|
|
5
|
+
AesEncrypt: typeof AesEncrypt;
|
|
6
|
+
private appLine;
|
|
7
|
+
private appTenant;
|
|
8
|
+
private defaultBaseUrl;
|
|
10
9
|
private domainBaseUrl;
|
|
10
|
+
private initialized;
|
|
11
|
+
private localPath;
|
|
12
|
+
private tenant;
|
|
11
13
|
private tenantConfig;
|
|
12
14
|
private tenantDict;
|
|
13
15
|
private tenantDictList;
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
16
|
+
/** 初始化配置入口 */
|
|
17
|
+
InitConfig(options: {
|
|
18
|
+
fileType?: string[];
|
|
19
|
+
defaultBaseUrl?: string;
|
|
20
|
+
localPath?: string;
|
|
21
|
+
appLine?: string;
|
|
22
|
+
appTenant?: string;
|
|
23
|
+
}): Promise<(string | null)[]>;
|
|
24
|
+
/** 是否 App 环境 */
|
|
25
|
+
getIsApp(): boolean;
|
|
26
|
+
/** 获取租户名 */
|
|
27
|
+
getTenant(): string;
|
|
28
|
+
/** 获取租户配置 */
|
|
20
29
|
getTenantConfig(): TenantConfig | null;
|
|
30
|
+
/** 获取租户字典 */
|
|
21
31
|
getTenantDict(): TenantDict | null;
|
|
32
|
+
/** 获取租户字典列表 */
|
|
22
33
|
getTenantDictList(): TenantDict[];
|
|
23
|
-
/**
|
|
24
|
-
|
|
34
|
+
/** 获取基础 URL */
|
|
35
|
+
getBaseUrl(): string;
|
|
36
|
+
/** 获取图片路径 */
|
|
25
37
|
getImgPath(imgName: string): string;
|
|
38
|
+
/** 严格初始化租户信息 */
|
|
39
|
+
private _initializeTenant;
|
|
26
40
|
/** 严格同步获取租户信息 */
|
|
27
|
-
private
|
|
28
|
-
/**
|
|
29
|
-
private
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
appTenant?: string;
|
|
36
|
-
}): Promise<any[]>;
|
|
37
|
-
private LoadGatewayConfig;
|
|
38
|
-
AesEncrypt: typeof AesEncrypt;
|
|
39
|
-
AesDecrypt: typeof AesDecrypt;
|
|
41
|
+
private _getTenantInfoStrictSync;
|
|
42
|
+
/** 匹配默认租户 */
|
|
43
|
+
private _matchDefaultTenant;
|
|
44
|
+
refreshHttp(): void;
|
|
45
|
+
/** 获取 lineDict.txt 内容 */
|
|
46
|
+
private _fetchLineDict;
|
|
47
|
+
/** 获取并处理 lineAddress.txt */
|
|
48
|
+
private _loadGatewayConfig;
|
|
40
49
|
}
|
|
41
50
|
declare const HF: InitCls;
|
|
51
|
+
export type TenantConfig = {
|
|
52
|
+
PUBLIC_TITLE: string;
|
|
53
|
+
SITE_TITLE: string;
|
|
54
|
+
SITE_URL: string;
|
|
55
|
+
USER_TENANT: string;
|
|
56
|
+
};
|
|
42
57
|
export { HF, isDomainMatch, toStandardUrl, AesEncrypt, AesDecrypt, filterSmartLines, matchBrowser, getOptimalDecodedString, };
|
package/dist/init/index.js
CHANGED
|
@@ -1,188 +1,215 @@
|
|
|
1
|
+
// index.ts
|
|
1
2
|
import { getResource } from "./getResource";
|
|
2
|
-
import { filterSmartLines, getOptimalDecodedString,
|
|
3
|
+
import { isDomainMatch, filterSmartLines, getOptimalDecodedString, matchBrowser, toStandardUrl, difArr, } from "./utils";
|
|
3
4
|
import { AesDecrypt, AesEncrypt } from "./ende";
|
|
5
|
+
import { rewardMsg } from "../msg";
|
|
4
6
|
class InitCls {
|
|
7
|
+
AesDecrypt = AesDecrypt;
|
|
8
|
+
AesEncrypt = AesEncrypt;
|
|
9
|
+
appLine = "";
|
|
10
|
+
appTenant = "";
|
|
11
|
+
defaultBaseUrl = "";
|
|
5
12
|
domainBaseUrl = "";
|
|
13
|
+
initialized = false;
|
|
14
|
+
localPath = "";
|
|
15
|
+
tenant = "";
|
|
6
16
|
tenantConfig = null;
|
|
7
17
|
tenantDict = null;
|
|
8
18
|
tenantDictList = [];
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
if (
|
|
15
|
-
|
|
19
|
+
/** 初始化配置入口 */
|
|
20
|
+
async InitConfig(options) {
|
|
21
|
+
const { fileType, defaultBaseUrl, localPath, appLine, appTenant } = options;
|
|
22
|
+
if (defaultBaseUrl)
|
|
23
|
+
this.defaultBaseUrl = defaultBaseUrl;
|
|
24
|
+
if (localPath)
|
|
25
|
+
this.localPath = localPath;
|
|
26
|
+
if (appLine && appTenant) {
|
|
27
|
+
this.appLine = appLine;
|
|
28
|
+
this.appTenant = appTenant;
|
|
29
|
+
console.info("📱 appLine:", appLine, "appTenant:", appTenant);
|
|
16
30
|
}
|
|
17
|
-
|
|
31
|
+
console.info("📝 加载类型:", fileType);
|
|
32
|
+
const results = [];
|
|
33
|
+
if (fileType?.includes("lineDict")) {
|
|
34
|
+
console.info("初始化 lineDict...");
|
|
35
|
+
results.push(await this._initializeTenant());
|
|
36
|
+
}
|
|
37
|
+
if (fileType?.includes("lineAddress")) {
|
|
38
|
+
console.info("初始化 lineAddress...");
|
|
39
|
+
results.push(await this._loadGatewayConfig());
|
|
40
|
+
}
|
|
41
|
+
console.info("✅ 所有初始化完成:", results);
|
|
42
|
+
return results;
|
|
43
|
+
}
|
|
44
|
+
/** 是否 App 环境 */
|
|
45
|
+
getIsApp() {
|
|
46
|
+
return !!this.appTenant && !!this.appLine;
|
|
18
47
|
}
|
|
19
|
-
/**
|
|
48
|
+
/** 获取租户名 */
|
|
49
|
+
getTenant() {
|
|
50
|
+
return this.tenant;
|
|
51
|
+
}
|
|
52
|
+
/** 获取租户配置 */
|
|
20
53
|
getTenantConfig() {
|
|
21
54
|
if (!this.initialized || !this.tenant) {
|
|
22
|
-
throw new Error("
|
|
55
|
+
throw new Error("租户尚未初始化");
|
|
23
56
|
}
|
|
24
57
|
return this.tenantConfig;
|
|
25
58
|
}
|
|
59
|
+
/** 获取租户字典 */
|
|
26
60
|
getTenantDict() {
|
|
27
61
|
if (!this.initialized || !this.tenant) {
|
|
28
|
-
throw new Error("
|
|
62
|
+
throw new Error("租户尚未初始化");
|
|
29
63
|
}
|
|
30
64
|
return this.tenantDict;
|
|
31
65
|
}
|
|
66
|
+
/** 获取租户字典列表 */
|
|
32
67
|
getTenantDictList() {
|
|
33
68
|
if (!this.initialized || !this.tenant) {
|
|
34
|
-
throw new Error("
|
|
69
|
+
throw new Error("租户尚未初始化");
|
|
35
70
|
}
|
|
36
71
|
return this.tenantDictList;
|
|
37
72
|
}
|
|
38
|
-
/**
|
|
39
|
-
|
|
40
|
-
|
|
73
|
+
/** 获取基础 URL */
|
|
74
|
+
getBaseUrl() {
|
|
75
|
+
if (!this.domainBaseUrl)
|
|
76
|
+
throw new Error("域名尚未初始化");
|
|
77
|
+
return this.domainBaseUrl;
|
|
41
78
|
}
|
|
79
|
+
/** 获取图片路径 */
|
|
42
80
|
getImgPath(imgName) {
|
|
43
81
|
if (!this.initialized || !this.tenant) {
|
|
44
|
-
throw new Error("
|
|
82
|
+
throw new Error("租户尚未初始化");
|
|
45
83
|
}
|
|
46
84
|
return getResource(`${this.tenant}/image/${imgName}`);
|
|
47
85
|
}
|
|
48
|
-
/**
|
|
49
|
-
async
|
|
50
|
-
try {
|
|
51
|
-
const response = await fetch(`/lineDict.txt?t=${Date.now()}`, {
|
|
52
|
-
method: "GET",
|
|
53
|
-
cache: "no-store",
|
|
54
|
-
});
|
|
55
|
-
if (!response.ok) {
|
|
56
|
-
throw new Error(`HTTP 状态码错误: ${response.status}`);
|
|
57
|
-
}
|
|
58
|
-
const rawText = await response.text();
|
|
59
|
-
const rawData = AesDecrypt(rawText);
|
|
60
|
-
const data = JSON.parse(rawData);
|
|
61
|
-
const host = location.host;
|
|
62
|
-
const matchedEntry = data.find((item) => {
|
|
63
|
-
return isDomainMatch([item.line], host);
|
|
64
|
-
});
|
|
65
|
-
this.tenantDictList = data;
|
|
66
|
-
if (matchedEntry) {
|
|
67
|
-
this.tenantDict = matchedEntry;
|
|
68
|
-
console.info(`🏠 匹配租户: ${matchedEntry.tenant}`);
|
|
69
|
-
return { tenant: matchedEntry.tenant };
|
|
70
|
-
}
|
|
71
|
-
// 默认租户匹配
|
|
72
|
-
if (host.includes("iggame") || host.includes("localhost")) {
|
|
73
|
-
console.info(`🏠 iggame匹配到默认租户2`);
|
|
74
|
-
return { tenant: "iggame" };
|
|
75
|
-
}
|
|
76
|
-
throw new Error("未找到匹配租户");
|
|
77
|
-
}
|
|
78
|
-
catch (error) {
|
|
79
|
-
console.error("解析 lineDict.txt 失败:", error);
|
|
80
|
-
const host = location.host;
|
|
81
|
-
if (host.includes("iggame") ||
|
|
82
|
-
host.includes("localhost") ||
|
|
83
|
-
(!!this.localPath &&
|
|
84
|
-
location.origin.includes(this.localPath.replace(/\/+$/, "")))) {
|
|
85
|
-
console.info(`🏠 iggame匹配到默认租户1`);
|
|
86
|
-
return { tenant: "iggame" };
|
|
87
|
-
}
|
|
88
|
-
throw new Error("无法获取有效的租户信息");
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
/** 严格初始化(必须在应用启动时调用) */
|
|
92
|
-
async initialize(tenant) {
|
|
86
|
+
/** 严格初始化租户信息 */
|
|
87
|
+
async _initializeTenant() {
|
|
93
88
|
if (this.initialized)
|
|
94
89
|
return this.tenant;
|
|
95
90
|
try {
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
else {
|
|
99
|
-
const tenantInfo = await this.getTenantInfoStrictSync();
|
|
100
|
-
this.tenant = tenantInfo.tenant;
|
|
101
|
-
}
|
|
91
|
+
const tenantInfo = await this._getTenantInfoStrictSync();
|
|
92
|
+
this.tenant = tenantInfo.tenant;
|
|
102
93
|
this.tenantConfig = getResource(`${this.tenant}/config.json`);
|
|
103
94
|
this.initialized = true;
|
|
104
|
-
console.info(
|
|
95
|
+
console.info("🏢 租户初始化成功:", this.tenant);
|
|
105
96
|
return this.tenant;
|
|
106
97
|
}
|
|
107
|
-
catch (
|
|
108
|
-
console.error("
|
|
109
|
-
throw
|
|
98
|
+
catch (err) {
|
|
99
|
+
console.error("❌ 租户初始化失败:", err);
|
|
100
|
+
throw err;
|
|
110
101
|
}
|
|
111
102
|
}
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
const
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
103
|
+
/** 严格同步获取租户信息 */
|
|
104
|
+
async _getTenantInfoStrictSync() {
|
|
105
|
+
try {
|
|
106
|
+
const rawText = await this._fetchLineDict();
|
|
107
|
+
const data = JSON.parse(AesDecrypt(rawText));
|
|
108
|
+
const host = location.host;
|
|
109
|
+
this.tenantDictList = [...data];
|
|
110
|
+
if (this.getIsApp()) {
|
|
111
|
+
this.tenantDict = {
|
|
112
|
+
browserCheck: [],
|
|
113
|
+
line: host,
|
|
114
|
+
lineGroup: this.appLine,
|
|
115
|
+
tenant: this.appTenant,
|
|
116
|
+
};
|
|
117
|
+
this.tenantDictList.push(this.tenantDict);
|
|
118
|
+
console.info("🏠 匹配 App 租户:", this.tenantDict);
|
|
119
|
+
return { tenant: this.appTenant };
|
|
120
|
+
}
|
|
121
|
+
const matched = data.find((item) => isDomainMatch([item.line], host));
|
|
122
|
+
if (matched) {
|
|
123
|
+
this.tenantDict = matched;
|
|
124
|
+
console.info("🏠 匹配租户:", matched.tenant);
|
|
125
|
+
return { tenant: matched.tenant };
|
|
126
|
+
}
|
|
127
|
+
return this._matchDefaultTenant();
|
|
122
128
|
}
|
|
123
|
-
|
|
124
|
-
|
|
129
|
+
catch (err) {
|
|
130
|
+
console.warn("⚠️ 解析 lineDict.txt 失败:", err);
|
|
131
|
+
return this._matchDefaultTenant();
|
|
125
132
|
}
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
133
|
+
}
|
|
134
|
+
/** 匹配默认租户 */
|
|
135
|
+
_matchDefaultTenant() {
|
|
136
|
+
const host = location.host;
|
|
137
|
+
if (host.includes("iggame") ||
|
|
138
|
+
host.includes("localhost") ||
|
|
139
|
+
(!!this.localPath &&
|
|
140
|
+
location.origin.includes(this.localPath.replace(/\/+$/, "")))) {
|
|
141
|
+
console.info("🏠 匹配默认租户 iggame");
|
|
142
|
+
return { tenant: "iggame" };
|
|
143
|
+
}
|
|
144
|
+
throw new Error("无法获取有效的租户信息");
|
|
145
|
+
}
|
|
146
|
+
refreshHttp() {
|
|
147
|
+
try {
|
|
148
|
+
getOptimalDecodedString([this.getBaseUrl()]);
|
|
132
149
|
}
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
150
|
+
catch (error) {
|
|
151
|
+
rewardMsg({
|
|
152
|
+
title: "提示",
|
|
153
|
+
text: "网络环境异常,刷新页面以重置",
|
|
154
|
+
onSubmit() {
|
|
155
|
+
location.reload();
|
|
156
|
+
},
|
|
157
|
+
});
|
|
137
158
|
}
|
|
138
|
-
console.log("所有初始化完成:", results);
|
|
139
|
-
return results;
|
|
140
159
|
}
|
|
141
|
-
|
|
160
|
+
/** 获取 lineDict.txt 内容 */
|
|
161
|
+
async _fetchLineDict() {
|
|
162
|
+
const response = await fetch(`/lineDict.txt?t=${Date.now()}`, {
|
|
163
|
+
method: "GET",
|
|
164
|
+
cache: "no-store",
|
|
165
|
+
});
|
|
166
|
+
if (!response.ok)
|
|
167
|
+
throw new Error(`HTTP 错误: ${response.status}`);
|
|
168
|
+
return response.text();
|
|
169
|
+
}
|
|
170
|
+
/** 获取并处理 lineAddress.txt */
|
|
171
|
+
async _loadGatewayConfig() {
|
|
142
172
|
if (this.domainBaseUrl)
|
|
143
173
|
return this.domainBaseUrl;
|
|
144
174
|
try {
|
|
145
175
|
const response = await fetch(`/lineAddress.txt?t=${Date.now()}`);
|
|
146
176
|
const configText = await response.text();
|
|
147
|
-
|
|
148
|
-
const { lineGroup } = this.getTenantDict();
|
|
177
|
+
const OriginBaseUrl = JSON.parse(AesDecrypt(configText));
|
|
149
178
|
const dictList = this.getTenantDictList();
|
|
150
|
-
|
|
151
|
-
|
|
179
|
+
const lineGroup = this.getTenantDict()?.lineGroup;
|
|
180
|
+
let baseUrl = filterSmartLines(dictList, OriginBaseUrl, lineGroup);
|
|
181
|
+
try {
|
|
182
|
+
this.domainBaseUrl = toStandardUrl(await getOptimalDecodedString(baseUrl));
|
|
183
|
+
console.info("✅ 成功加载生产环境配置:", this.domainBaseUrl);
|
|
184
|
+
}
|
|
185
|
+
catch {
|
|
186
|
+
// 备用策略
|
|
187
|
+
let allBaseUrl = filterSmartLines(dictList, OriginBaseUrl);
|
|
152
188
|
try {
|
|
153
|
-
this.domainBaseUrl = toStandardUrl(await getOptimalDecodedString(baseUrl));
|
|
154
|
-
console.
|
|
189
|
+
this.domainBaseUrl = toStandardUrl(await getOptimalDecodedString(difArr(allBaseUrl, baseUrl)));
|
|
190
|
+
console.info("✅ 备用配置成功:", this.domainBaseUrl);
|
|
155
191
|
}
|
|
156
|
-
catch
|
|
157
|
-
this.domainBaseUrl = toStandardUrl(
|
|
158
|
-
console.warn("⚠️
|
|
192
|
+
catch {
|
|
193
|
+
this.domainBaseUrl = toStandardUrl(allBaseUrl[0]);
|
|
194
|
+
console.warn("⚠️ 备选测速失败,使用备用配置:", allBaseUrl);
|
|
159
195
|
}
|
|
160
196
|
}
|
|
161
|
-
else {
|
|
162
|
-
this.domainBaseUrl = toStandardUrl(baseUrl);
|
|
163
|
-
console.log("✅ 成功加载生产环境配置:", this.domainBaseUrl);
|
|
164
|
-
}
|
|
165
197
|
return this.domainBaseUrl;
|
|
166
198
|
}
|
|
167
|
-
catch (
|
|
168
|
-
console.error(
|
|
199
|
+
catch (err) {
|
|
200
|
+
console.error("⚠️ 加载 lineAddress.txt 失败:", err);
|
|
169
201
|
if ((this.defaultBaseUrl &&
|
|
170
202
|
(location.host.includes("iggame") ||
|
|
171
203
|
location.host.includes("localhost"))) ||
|
|
172
204
|
(!!this.localPath &&
|
|
173
205
|
location.origin.includes(this.localPath.replace(/\/+$/, "")))) {
|
|
174
|
-
console.info(`🏠 iggame匹配到默认链接`, this.defaultBaseUrl);
|
|
175
206
|
this.domainBaseUrl = toStandardUrl(this.defaultBaseUrl);
|
|
207
|
+
console.info("🏠 使用默认 BaseUrl:", this.domainBaseUrl);
|
|
176
208
|
return this.domainBaseUrl;
|
|
177
209
|
}
|
|
178
|
-
|
|
179
|
-
console.warn("⚠️ 加载 lineAddress.txt 失败:", error);
|
|
180
|
-
return null;
|
|
181
|
-
}
|
|
210
|
+
return null;
|
|
182
211
|
}
|
|
183
212
|
}
|
|
184
|
-
AesEncrypt = AesEncrypt;
|
|
185
|
-
AesDecrypt = AesDecrypt;
|
|
186
213
|
}
|
|
187
214
|
const HF = new InitCls();
|
|
188
215
|
export { HF, isDomainMatch, toStandardUrl, AesEncrypt, AesDecrypt, filterSmartLines, matchBrowser, getOptimalDecodedString, };
|
package/dist/init/utils.d.ts
CHANGED
|
@@ -4,9 +4,28 @@ export type TenantDict = {
|
|
|
4
4
|
lineGroup: string;
|
|
5
5
|
tenant: string;
|
|
6
6
|
};
|
|
7
|
-
|
|
8
|
-
export declare function
|
|
7
|
+
/** 保留端口,去掉协议 */
|
|
8
|
+
export declare function normalizeHost(hostname: string): string;
|
|
9
|
+
/** 域名严格匹配,允许 www 特例 */
|
|
10
|
+
export declare function isDomainStrictEqual(a: string, b: string): boolean;
|
|
9
11
|
export declare function isDomainMatch(list: string[], target: string): boolean;
|
|
10
|
-
|
|
12
|
+
/** 检查是否 IPv4 */
|
|
13
|
+
export declare function isIPv4(str: string): boolean;
|
|
14
|
+
export declare function detectBrowser(): string;
|
|
15
|
+
/** 匹配浏览器 */
|
|
11
16
|
export declare function matchBrowser(checkItem: string, currentBrowser: string): boolean;
|
|
12
|
-
|
|
17
|
+
/** 标准化 URL */
|
|
18
|
+
export declare function toStandardUrl(input: string): string;
|
|
19
|
+
/** 根据浏览器和租户过滤线路 */
|
|
20
|
+
export declare function filterSmartLines(tenantList: TenantDict[], lineList: string[], groupType?: string, tenant?: string): string[];
|
|
21
|
+
/** 并发测速获取最佳 URL(限制超时和并发) */
|
|
22
|
+
export declare function getOptimalDecodedString(arr: string[], options?: {
|
|
23
|
+
error?: (arr: string[]) => void;
|
|
24
|
+
filterIp?: boolean;
|
|
25
|
+
}): Promise<string>;
|
|
26
|
+
/**
|
|
27
|
+
* 返回两个数组的差集(对称差集)
|
|
28
|
+
* @param arr1 第一个数组
|
|
29
|
+
* @param arr2 第二个数组
|
|
30
|
+
*/
|
|
31
|
+
export declare function difArr(arr1: string[], arr2: string[]): string[];
|
package/dist/init/utils.js
CHANGED
|
@@ -1,135 +1,45 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
str = url.hostname;
|
|
6
|
-
}
|
|
7
|
-
catch {
|
|
8
|
-
return false;
|
|
9
|
-
}
|
|
10
|
-
}
|
|
11
|
-
if (str === "localhost")
|
|
12
|
-
return false;
|
|
13
|
-
const ipv4Regex = /^(25[0-5]|2[0-4]\d|1?\d{1,2})(\.(25[0-5]|2[0-4]\d|1?\d{1,2})){3}$/;
|
|
14
|
-
return ipv4Regex.test(str);
|
|
15
|
-
}
|
|
16
|
-
export async function getOptimalDecodedString(encodedArray, filterIp = true) {
|
|
17
|
-
if (!encodedArray?.length)
|
|
18
|
-
throw new Error("输入数组为空或无效");
|
|
19
|
-
const hostname = window.location.hostname;
|
|
20
|
-
const isIp = isIPv4(hostname);
|
|
21
|
-
const decodedList = encodedArray
|
|
22
|
-
.filter(Boolean)
|
|
23
|
-
.map((v) => v.trim());
|
|
24
|
-
const ipList = decodedList.filter((v) => isIPv4(v));
|
|
25
|
-
const domainList = decodedList.filter((v) => !isIPv4(v));
|
|
26
|
-
const getOptimal = (arr) => new Promise((resolve) => {
|
|
27
|
-
if (!arr.length) {
|
|
28
|
-
resolve("");
|
|
29
|
-
return;
|
|
30
|
-
}
|
|
31
|
-
const currentProtocol = window.location.protocol;
|
|
32
|
-
const filteredArr = arr.filter((url) => url.startsWith("http") ? url.startsWith(currentProtocol) : true);
|
|
33
|
-
const targetArr = filteredArr.length ? filteredArr : arr;
|
|
34
|
-
if (targetArr.length === 1)
|
|
35
|
-
return resolve(targetArr[0]);
|
|
36
|
-
let hasResolved = false;
|
|
37
|
-
targetArr.forEach((url) => {
|
|
38
|
-
const testUrl = `${toStandardUrl(url)}/health?t=${Date.now()}`;
|
|
39
|
-
const controller = new AbortController();
|
|
40
|
-
const timeoutId = setTimeout(() => {
|
|
41
|
-
controller.abort();
|
|
42
|
-
}, 3000);
|
|
43
|
-
fetch(testUrl, { method: "GET", signal: controller.signal })
|
|
44
|
-
.then(() => {
|
|
45
|
-
clearTimeout(timeoutId);
|
|
46
|
-
if (!hasResolved) {
|
|
47
|
-
hasResolved = true;
|
|
48
|
-
resolve(url);
|
|
49
|
-
}
|
|
50
|
-
})
|
|
51
|
-
.catch(() => clearTimeout(timeoutId));
|
|
52
|
-
});
|
|
53
|
-
setTimeout(() => {
|
|
54
|
-
if (!hasResolved) {
|
|
55
|
-
hasResolved = true;
|
|
56
|
-
resolve(targetArr[0]);
|
|
57
|
-
}
|
|
58
|
-
}, 3500);
|
|
59
|
-
});
|
|
60
|
-
return !filterIp
|
|
61
|
-
? await getOptimal(decodedList)
|
|
62
|
-
: isIp
|
|
63
|
-
? await getOptimal(ipList)
|
|
64
|
-
: await getOptimal(domainList);
|
|
65
|
-
}
|
|
66
|
-
/**
|
|
67
|
-
* normalizeHost:保留端口,只清理协议
|
|
68
|
-
*/
|
|
69
|
-
function normalizeHost(hostname) {
|
|
1
|
+
/** 浏览器识别(结果缓存) */
|
|
2
|
+
let cachedBrowser = null;
|
|
3
|
+
/** 保留端口,去掉协议 */
|
|
4
|
+
export function normalizeHost(hostname) {
|
|
70
5
|
hostname = hostname.trim().toLowerCase();
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
urlStr = "http://" + hostname;
|
|
75
|
-
}
|
|
6
|
+
const urlStr = /^https?:\/\//i.test(hostname)
|
|
7
|
+
? hostname
|
|
8
|
+
: `http://${hostname}`;
|
|
76
9
|
try {
|
|
77
10
|
const url = new URL(urlStr);
|
|
78
|
-
// 保留 hostname + port
|
|
79
11
|
return url.port ? `${url.hostname}:${url.port}` : url.hostname;
|
|
80
12
|
}
|
|
81
|
-
catch
|
|
82
|
-
return hostname;
|
|
13
|
+
catch {
|
|
14
|
+
return hostname;
|
|
83
15
|
}
|
|
84
16
|
}
|
|
85
|
-
/**
|
|
86
|
-
|
|
87
|
-
*/
|
|
88
|
-
function isDomainStrictEqual(a, b) {
|
|
89
|
-
a = normalizeHost(a);
|
|
90
|
-
b = normalizeHost(b);
|
|
91
|
-
if (a === b)
|
|
92
|
-
return true;
|
|
17
|
+
/** 域名严格匹配,允许 www 特例 */
|
|
18
|
+
export function isDomainStrictEqual(a, b) {
|
|
93
19
|
const stripWww = (h) => (h.startsWith("www.") ? h.slice(4) : h);
|
|
94
|
-
return stripWww(a) === stripWww(b);
|
|
20
|
+
return stripWww(normalizeHost(a)) === stripWww(normalizeHost(b));
|
|
95
21
|
}
|
|
96
22
|
export function isDomainMatch(list, target) {
|
|
97
23
|
const t = normalizeHost(target);
|
|
98
|
-
return list.some((item) =>
|
|
99
|
-
const h = normalizeHost(item);
|
|
100
|
-
return isDomainStrictEqual(h, t);
|
|
101
|
-
});
|
|
24
|
+
return list.some((item) => isDomainStrictEqual(item, t));
|
|
102
25
|
}
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
let protocol = window?.location?.protocol || "https:";
|
|
109
|
-
if (!/^https?:\/\//i.test(input)) {
|
|
110
|
-
input = protocol + "//" + input;
|
|
26
|
+
/** 检查是否 IPv4 */
|
|
27
|
+
export function isIPv4(str) {
|
|
28
|
+
if (str.startsWith("http")) {
|
|
29
|
+
try {
|
|
30
|
+
str = new URL(str).hostname;
|
|
111
31
|
}
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
const port = url.port;
|
|
115
|
-
const isHostnameValid = hostname === "localhost" ||
|
|
116
|
-
(/^[a-zA-Z0-9.-]+$/.test(hostname) &&
|
|
117
|
-
(hostname.includes(".") || isIPv4(hostname)));
|
|
118
|
-
if (!isHostnameValid) {
|
|
119
|
-
return "";
|
|
32
|
+
catch {
|
|
33
|
+
return false;
|
|
120
34
|
}
|
|
121
|
-
return port
|
|
122
|
-
? `${url.protocol}//${hostname}:${port}`
|
|
123
|
-
: `${url.protocol}//${hostname}`;
|
|
124
|
-
}
|
|
125
|
-
catch (e) {
|
|
126
|
-
return "";
|
|
127
35
|
}
|
|
36
|
+
if (str === "localhost")
|
|
37
|
+
return false;
|
|
38
|
+
return /^(25[0-5]|2[0-4]\d|1?\d{1,2})(\.(25[0-5]|2[0-4]\d|1?\d{1,2})){3}$/.test(str);
|
|
128
39
|
}
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
function detectBrowser() {
|
|
40
|
+
export function detectBrowser() {
|
|
41
|
+
if (cachedBrowser)
|
|
42
|
+
return cachedBrowser;
|
|
133
43
|
const ua = navigator.userAgent.toLowerCase();
|
|
134
44
|
const rules = [
|
|
135
45
|
{ key: "baidu", regex: /baidubrowser|baiduboxapp|baidu/i },
|
|
@@ -152,43 +62,128 @@ function detectBrowser() {
|
|
|
152
62
|
{ key: "alipay", regex: /alipayclient/i },
|
|
153
63
|
{ key: "douyin", regex: /aweme|douyin/i },
|
|
154
64
|
];
|
|
155
|
-
for (const
|
|
156
|
-
if (
|
|
65
|
+
for (const { key, regex, exclude } of rules) {
|
|
66
|
+
if (exclude && exclude.test(ua))
|
|
157
67
|
continue;
|
|
158
|
-
if (
|
|
159
|
-
return
|
|
68
|
+
if (regex.test(ua))
|
|
69
|
+
return (cachedBrowser = key);
|
|
160
70
|
}
|
|
161
|
-
return "unknown";
|
|
71
|
+
return (cachedBrowser = "unknown");
|
|
162
72
|
}
|
|
73
|
+
/** 匹配浏览器 */
|
|
163
74
|
export function matchBrowser(checkItem, currentBrowser) {
|
|
164
75
|
if (!checkItem || !currentBrowser)
|
|
165
76
|
return false;
|
|
166
|
-
const normalized = checkItem.
|
|
77
|
+
const normalized = checkItem.toLowerCase();
|
|
167
78
|
const browser = currentBrowser.toLowerCase();
|
|
168
79
|
if (normalized.startsWith("/") && normalized.endsWith("/")) {
|
|
169
80
|
try {
|
|
170
|
-
|
|
171
|
-
const regex = new RegExp(body, "i");
|
|
172
|
-
return regex.test(browser);
|
|
81
|
+
return new RegExp(normalized.slice(1, -1), "i").test(browser);
|
|
173
82
|
}
|
|
174
|
-
catch
|
|
83
|
+
catch { }
|
|
175
84
|
}
|
|
176
85
|
return browser.includes(normalized);
|
|
177
86
|
}
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
87
|
+
/** 标准化 URL */
|
|
88
|
+
export function toStandardUrl(input) {
|
|
89
|
+
if (!input)
|
|
90
|
+
return "";
|
|
91
|
+
input = input.trim();
|
|
92
|
+
const protocol = window?.location?.protocol || "https:";
|
|
93
|
+
if (!/^https?:\/\//i.test(input))
|
|
94
|
+
input = `${protocol}//${input}`;
|
|
95
|
+
try {
|
|
96
|
+
const url = new URL(input);
|
|
97
|
+
const hostname = url.hostname;
|
|
98
|
+
const port = url.port;
|
|
99
|
+
if (hostname !== "localhost" &&
|
|
100
|
+
!(/^[a-zA-Z0-9.-]+$/.test(hostname) &&
|
|
101
|
+
(hostname.includes(".") || isIPv4(hostname)))) {
|
|
102
|
+
return "";
|
|
103
|
+
}
|
|
104
|
+
return port
|
|
105
|
+
? `${url.protocol}//${hostname}:${port}`
|
|
106
|
+
: `${url.protocol}//${hostname}`;
|
|
181
107
|
}
|
|
108
|
+
catch {
|
|
109
|
+
return "";
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
/** 根据浏览器和租户过滤线路 */
|
|
113
|
+
export function filterSmartLines(tenantList, lineList, groupType, tenant) {
|
|
114
|
+
if (!Array.isArray(tenantList) || !Array.isArray(lineList))
|
|
115
|
+
throw new Error("参数必须是数组");
|
|
182
116
|
const currentBrowser = detectBrowser();
|
|
183
|
-
let
|
|
184
|
-
const checks = item.browserCheck || [];
|
|
185
|
-
const blocked = checks.some((checkItem) => matchBrowser(checkItem, currentBrowser));
|
|
186
|
-
return !blocked;
|
|
187
|
-
});
|
|
117
|
+
let filtered = tenantList.filter((item) => !(item.browserCheck || []).some((check) => matchBrowser(check, currentBrowser)));
|
|
188
118
|
if (tenant)
|
|
189
|
-
|
|
119
|
+
filtered = filtered.filter((item) => item.tenant === tenant);
|
|
190
120
|
if (groupType)
|
|
191
|
-
|
|
192
|
-
const lines =
|
|
193
|
-
return
|
|
121
|
+
filtered = filtered.filter((item) => item.lineGroup === groupType);
|
|
122
|
+
const lines = filtered.map((item) => toStandardUrl(item.line));
|
|
123
|
+
return lineList.map(toStandardUrl).filter((v) => isDomainMatch(lines, v));
|
|
124
|
+
}
|
|
125
|
+
/** 并发测速获取最佳 URL(限制超时和并发) */
|
|
126
|
+
export async function getOptimalDecodedString(arr, options) {
|
|
127
|
+
if (!arr?.length)
|
|
128
|
+
throw new Error("输入数组为空");
|
|
129
|
+
const { error, filterIp = true } = options || {};
|
|
130
|
+
const hostname = window.location.hostname;
|
|
131
|
+
const isIp = isIPv4(hostname);
|
|
132
|
+
// 过滤逻辑
|
|
133
|
+
let targetArr = arr.filter(Boolean).map((v) => v.trim());
|
|
134
|
+
if (filterIp) {
|
|
135
|
+
targetArr = isIp
|
|
136
|
+
? targetArr.filter(isIPv4)
|
|
137
|
+
: targetArr.filter((v) => !isIPv4(v));
|
|
138
|
+
}
|
|
139
|
+
if (!targetArr.length)
|
|
140
|
+
throw new Error("无可用地址");
|
|
141
|
+
const failedUrls = [];
|
|
142
|
+
const controllerMap = {};
|
|
143
|
+
return new Promise((resolve, reject) => {
|
|
144
|
+
let resolved = false;
|
|
145
|
+
targetArr.forEach((url) => {
|
|
146
|
+
const controller = new AbortController();
|
|
147
|
+
controllerMap[url] = controller;
|
|
148
|
+
const timeoutId = setTimeout(() => {
|
|
149
|
+
controller.abort();
|
|
150
|
+
failedUrls.push(url);
|
|
151
|
+
}, 3000);
|
|
152
|
+
fetch(`${toStandardUrl(url)}/health?t=${Date.now()}`, {
|
|
153
|
+
signal: controller.signal,
|
|
154
|
+
})
|
|
155
|
+
.then(() => {
|
|
156
|
+
clearTimeout(timeoutId);
|
|
157
|
+
if (!resolved) {
|
|
158
|
+
resolved = true;
|
|
159
|
+
resolve(url);
|
|
160
|
+
}
|
|
161
|
+
})
|
|
162
|
+
.catch(() => {
|
|
163
|
+
clearTimeout(timeoutId);
|
|
164
|
+
failedUrls.push(url);
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
// 等待所有请求完成
|
|
168
|
+
setTimeout(() => {
|
|
169
|
+
if (failedUrls.length && error) {
|
|
170
|
+
error(failedUrls);
|
|
171
|
+
}
|
|
172
|
+
if (!resolved) {
|
|
173
|
+
reject({ message: "测速全部失败", failedUrls });
|
|
174
|
+
}
|
|
175
|
+
}, 3500);
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* 返回两个数组的差集(对称差集)
|
|
180
|
+
* @param arr1 第一个数组
|
|
181
|
+
* @param arr2 第二个数组
|
|
182
|
+
*/
|
|
183
|
+
export function difArr(arr1, arr2) {
|
|
184
|
+
const set1 = new Set(arr1.map((v) => v.trim()));
|
|
185
|
+
const set2 = new Set(arr2.map((v) => v.trim()));
|
|
186
|
+
const diff1 = [...set1].filter((v) => !set2.has(v));
|
|
187
|
+
const diff2 = [...set2].filter((v) => !set1.has(v));
|
|
188
|
+
return [...diff1, ...diff2];
|
|
194
189
|
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export interface MsgOptions {
|
|
2
|
+
type?: "info" | "success" | "warning" | "error";
|
|
3
|
+
duration?: number;
|
|
4
|
+
closable?: boolean;
|
|
5
|
+
onClose?: () => void;
|
|
6
|
+
}
|
|
7
|
+
export declare function msg(message: string, options?: MsgOptions): void;
|
|
8
|
+
export interface RewardOptions {
|
|
9
|
+
title?: string;
|
|
10
|
+
text?: string;
|
|
11
|
+
topImg?: string;
|
|
12
|
+
submitText?: string;
|
|
13
|
+
cancelText?: string;
|
|
14
|
+
onSubmit?: () => void;
|
|
15
|
+
onCancel?: () => void;
|
|
16
|
+
onClose?: () => void;
|
|
17
|
+
}
|
|
18
|
+
export declare function rewardMsg(options: RewardOptions): void;
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
let isRewardShowing = false;
|
|
2
|
+
// --------------------------- 普通消息 ---------------------------
|
|
3
|
+
let messageStackContainer = null;
|
|
4
|
+
// --------------------------- reward 弹窗 ---------------------------
|
|
5
|
+
let rewardMask = null;
|
|
6
|
+
const rewardQueue = [];
|
|
7
|
+
let rewardStackContainer = null;
|
|
8
|
+
function getMessageStackContainer() {
|
|
9
|
+
if (!messageStackContainer) {
|
|
10
|
+
messageStackContainer = document.createElement("div");
|
|
11
|
+
Object.assign(messageStackContainer.style, {
|
|
12
|
+
position: "fixed",
|
|
13
|
+
bottom: "10vh",
|
|
14
|
+
left: "50%",
|
|
15
|
+
transform: "translateX(-50%)",
|
|
16
|
+
display: "flex",
|
|
17
|
+
flexDirection: "column",
|
|
18
|
+
alignItems: "center",
|
|
19
|
+
gap: "10px",
|
|
20
|
+
zIndex: "9999",
|
|
21
|
+
pointerEvents: "none",
|
|
22
|
+
width: "100%",
|
|
23
|
+
maxWidth: "calc(100% - 40px)",
|
|
24
|
+
});
|
|
25
|
+
document.body.appendChild(messageStackContainer);
|
|
26
|
+
}
|
|
27
|
+
return messageStackContainer;
|
|
28
|
+
}
|
|
29
|
+
function hideMsg(element, callback) {
|
|
30
|
+
element.style.opacity = "0";
|
|
31
|
+
element.style.transform += " translateY(20px)";
|
|
32
|
+
setTimeout(() => {
|
|
33
|
+
element.parentNode?.removeChild(element);
|
|
34
|
+
callback?.();
|
|
35
|
+
}, 300);
|
|
36
|
+
}
|
|
37
|
+
function getColorByType(type) {
|
|
38
|
+
switch (type) {
|
|
39
|
+
case "success":
|
|
40
|
+
return "#67C23A";
|
|
41
|
+
case "warning":
|
|
42
|
+
return "#E6A23C";
|
|
43
|
+
case "error":
|
|
44
|
+
return "#F56C6C";
|
|
45
|
+
case "info":
|
|
46
|
+
return "#409EFF";
|
|
47
|
+
default:
|
|
48
|
+
return "#409EFF";
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
function getRewardMask() {
|
|
52
|
+
if (!rewardMask) {
|
|
53
|
+
rewardMask = document.createElement("div");
|
|
54
|
+
Object.assign(rewardMask.style, {
|
|
55
|
+
position: "fixed",
|
|
56
|
+
top: "0",
|
|
57
|
+
left: "0",
|
|
58
|
+
width: "100vw",
|
|
59
|
+
height: "100vh",
|
|
60
|
+
background: "rgba(0,0,0,0.5)",
|
|
61
|
+
zIndex: "100000",
|
|
62
|
+
pointerEvents: "auto",
|
|
63
|
+
display: "flex",
|
|
64
|
+
justifyContent: "center",
|
|
65
|
+
alignItems: "center",
|
|
66
|
+
});
|
|
67
|
+
document.body.appendChild(rewardMask);
|
|
68
|
+
}
|
|
69
|
+
return rewardMask;
|
|
70
|
+
}
|
|
71
|
+
function getRewardStackContainer() {
|
|
72
|
+
if (!rewardStackContainer) {
|
|
73
|
+
rewardStackContainer = document.createElement("div");
|
|
74
|
+
Object.assign(rewardStackContainer.style, {
|
|
75
|
+
position: "relative",
|
|
76
|
+
display: "flex",
|
|
77
|
+
flexDirection: "column",
|
|
78
|
+
alignItems: "center",
|
|
79
|
+
gap: "12px",
|
|
80
|
+
width: "100%",
|
|
81
|
+
maxWidth: "calc(100% - 40px)",
|
|
82
|
+
});
|
|
83
|
+
getRewardMask().appendChild(rewardStackContainer);
|
|
84
|
+
}
|
|
85
|
+
return rewardStackContainer;
|
|
86
|
+
}
|
|
87
|
+
function showNextReward() {
|
|
88
|
+
if (isRewardShowing || rewardQueue.length === 0)
|
|
89
|
+
return;
|
|
90
|
+
const options = rewardQueue.shift();
|
|
91
|
+
isRewardShowing = true;
|
|
92
|
+
const mask = getRewardMask();
|
|
93
|
+
const originalOverflow = document.body.style.overflow;
|
|
94
|
+
document.body.style.overflow = "hidden";
|
|
95
|
+
const container = document.createElement("div");
|
|
96
|
+
container.className = "rewardWindow";
|
|
97
|
+
Object.assign(container.style, {
|
|
98
|
+
position: "relative",
|
|
99
|
+
width: "calc(100% - 60px)",
|
|
100
|
+
maxWidth: "370px",
|
|
101
|
+
paddingTop: options.topImg ? "88px" : "23px",
|
|
102
|
+
backgroundColor: "#fff",
|
|
103
|
+
borderRadius: "12px",
|
|
104
|
+
boxSizing: "border-box",
|
|
105
|
+
display: "flex",
|
|
106
|
+
flexDirection: "column",
|
|
107
|
+
alignItems: "center",
|
|
108
|
+
textAlign: "center",
|
|
109
|
+
opacity: "0",
|
|
110
|
+
transform: "scale(0.9)",
|
|
111
|
+
transition: "all 0.3s cubic-bezier(0.4,0,0.2,1)",
|
|
112
|
+
overflow: "hidden",
|
|
113
|
+
zIndex: "100001",
|
|
114
|
+
});
|
|
115
|
+
if (options.title) {
|
|
116
|
+
const titleEl = document.createElement("div");
|
|
117
|
+
titleEl.textContent = options.title;
|
|
118
|
+
Object.assign(titleEl.style, {
|
|
119
|
+
fontSize: "16px",
|
|
120
|
+
fontWeight: "600",
|
|
121
|
+
color: "#303442",
|
|
122
|
+
});
|
|
123
|
+
container.appendChild(titleEl);
|
|
124
|
+
}
|
|
125
|
+
if (options.topImg) {
|
|
126
|
+
const imgEl = document.createElement("img");
|
|
127
|
+
imgEl.src = options.topImg;
|
|
128
|
+
Object.assign(imgEl.style, {
|
|
129
|
+
position: "absolute",
|
|
130
|
+
top: "-68px",
|
|
131
|
+
height: "150px",
|
|
132
|
+
width: "150px",
|
|
133
|
+
});
|
|
134
|
+
container.appendChild(imgEl);
|
|
135
|
+
}
|
|
136
|
+
if (options.text) {
|
|
137
|
+
const textEl = document.createElement("div");
|
|
138
|
+
textEl.innerHTML = options.text;
|
|
139
|
+
Object.assign(textEl.style, {
|
|
140
|
+
padding: "14px 22px 12px",
|
|
141
|
+
fontSize: "12px",
|
|
142
|
+
fontWeight: "500",
|
|
143
|
+
lineHeight: "16px",
|
|
144
|
+
textAlign: "center",
|
|
145
|
+
width: "100%",
|
|
146
|
+
overflow: "hidden",
|
|
147
|
+
wordBreak: "break-word",
|
|
148
|
+
color: "#7981A4",
|
|
149
|
+
});
|
|
150
|
+
container.appendChild(textEl);
|
|
151
|
+
}
|
|
152
|
+
const btnContainer = document.createElement("div");
|
|
153
|
+
Object.assign(btnContainer.style, {
|
|
154
|
+
display: "flex",
|
|
155
|
+
width: "100%",
|
|
156
|
+
height: "44px",
|
|
157
|
+
borderTop: "1px solid #f2f2f6",
|
|
158
|
+
padding: "8px 0",
|
|
159
|
+
});
|
|
160
|
+
const btnStyle = {
|
|
161
|
+
flex: "1",
|
|
162
|
+
border: "none",
|
|
163
|
+
background: "transparent",
|
|
164
|
+
cursor: "pointer",
|
|
165
|
+
fontSize: "14px",
|
|
166
|
+
fontWeight: "500",
|
|
167
|
+
};
|
|
168
|
+
if (options.cancelText) {
|
|
169
|
+
const cancelBtn = document.createElement("button");
|
|
170
|
+
cancelBtn.textContent = options.cancelText;
|
|
171
|
+
Object.assign(cancelBtn.style, {
|
|
172
|
+
...btnStyle,
|
|
173
|
+
borderRight: "1px solid #f2f2f6",
|
|
174
|
+
color: "#303442",
|
|
175
|
+
});
|
|
176
|
+
cancelBtn.onclick = () => closeReward(container, options.onCancel ?? options.onClose, originalOverflow);
|
|
177
|
+
btnContainer.appendChild(cancelBtn);
|
|
178
|
+
}
|
|
179
|
+
const confirmBtn = document.createElement("button");
|
|
180
|
+
confirmBtn.textContent = options.submitText || "确认";
|
|
181
|
+
Object.assign(confirmBtn.style, {
|
|
182
|
+
...btnStyle,
|
|
183
|
+
color: "#179CFF",
|
|
184
|
+
});
|
|
185
|
+
confirmBtn.onclick = () => {
|
|
186
|
+
options.onSubmit?.();
|
|
187
|
+
closeReward(container, options.onClose, originalOverflow);
|
|
188
|
+
};
|
|
189
|
+
btnContainer.appendChild(confirmBtn);
|
|
190
|
+
container.appendChild(btnContainer);
|
|
191
|
+
const stack = getRewardStackContainer();
|
|
192
|
+
stack.appendChild(container);
|
|
193
|
+
requestAnimationFrame(() => {
|
|
194
|
+
container.style.opacity = "1";
|
|
195
|
+
container.style.transform = "scale(1)";
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
function closeReward(container, callback, originalOverflow) {
|
|
199
|
+
container.style.opacity = "0";
|
|
200
|
+
container.style.transform = "scale(0.9)";
|
|
201
|
+
setTimeout(() => {
|
|
202
|
+
container.parentNode?.removeChild(container);
|
|
203
|
+
callback?.();
|
|
204
|
+
if (rewardStackContainer?.childElementCount === 0) {
|
|
205
|
+
rewardMask?.remove();
|
|
206
|
+
rewardMask = null;
|
|
207
|
+
rewardStackContainer = null;
|
|
208
|
+
document.body.style.overflow = originalOverflow ?? "";
|
|
209
|
+
}
|
|
210
|
+
isRewardShowing = false;
|
|
211
|
+
showNextReward(); // 显示下一个弹窗
|
|
212
|
+
}, 300);
|
|
213
|
+
}
|
|
214
|
+
export function msg(message, options = {}) {
|
|
215
|
+
const { type = "info", duration = 3000, closable = false, onClose } = options;
|
|
216
|
+
const container = document.createElement("div");
|
|
217
|
+
Object.assign(container.style, {
|
|
218
|
+
backgroundColor: getColorByType(type),
|
|
219
|
+
color: "#fff",
|
|
220
|
+
borderRadius: "12px",
|
|
221
|
+
fontSize: "12px",
|
|
222
|
+
fontWeight: "500",
|
|
223
|
+
padding: "10px 16px",
|
|
224
|
+
maxWidth: "400px",
|
|
225
|
+
minWidth: "120px",
|
|
226
|
+
textAlign: "center",
|
|
227
|
+
display: "-webkit-box",
|
|
228
|
+
WebkitLineClamp: "2",
|
|
229
|
+
WebkitBoxOrient: "vertical",
|
|
230
|
+
overflow: "hidden",
|
|
231
|
+
textOverflow: "ellipsis",
|
|
232
|
+
wordWrap: "break-word",
|
|
233
|
+
boxShadow: "0 6px 18px rgba(0,0,0,0.12)",
|
|
234
|
+
opacity: "0",
|
|
235
|
+
transform: "translateY(20px)",
|
|
236
|
+
transition: "all 0.3s cubic-bezier(0.4,0,0.2,1)",
|
|
237
|
+
pointerEvents: "auto",
|
|
238
|
+
position: "relative",
|
|
239
|
+
});
|
|
240
|
+
container.textContent = message;
|
|
241
|
+
if (closable) {
|
|
242
|
+
const closeBtn = document.createElement("span");
|
|
243
|
+
closeBtn.innerHTML = "×";
|
|
244
|
+
Object.assign(closeBtn.style, {
|
|
245
|
+
position: "absolute",
|
|
246
|
+
top: "50%",
|
|
247
|
+
right: "10px",
|
|
248
|
+
transform: "translateY(-50%)",
|
|
249
|
+
cursor: "pointer",
|
|
250
|
+
fontSize: "14px",
|
|
251
|
+
fontWeight: "bold",
|
|
252
|
+
});
|
|
253
|
+
closeBtn.onclick = () => hideMsg(container, onClose);
|
|
254
|
+
container.appendChild(closeBtn);
|
|
255
|
+
container.style.paddingRight = "30px";
|
|
256
|
+
}
|
|
257
|
+
const stack = getMessageStackContainer();
|
|
258
|
+
stack.appendChild(container);
|
|
259
|
+
requestAnimationFrame(() => {
|
|
260
|
+
container.style.opacity = "1";
|
|
261
|
+
container.style.transform = "translateY(0)";
|
|
262
|
+
});
|
|
263
|
+
if (duration > 0) {
|
|
264
|
+
setTimeout(() => hideMsg(container, onClose), duration);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
export function rewardMsg(options) {
|
|
268
|
+
rewardQueue.push(options);
|
|
269
|
+
showNextReward();
|
|
270
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hifun-tools",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.31",
|
|
4
4
|
"main": "dist/index.js",
|
|
5
5
|
"module": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -17,9 +17,13 @@
|
|
|
17
17
|
"dist"
|
|
18
18
|
],
|
|
19
19
|
"devDependencies": {
|
|
20
|
+
"@babel/core": "^7.28.5",
|
|
21
|
+
"@babel/preset-typescript": "^7.28.5",
|
|
20
22
|
"@types/jest": "^30.0.0",
|
|
21
23
|
"jest": "^30.2.0",
|
|
22
24
|
"jest-environment-jsdom": "^30.2.0",
|
|
25
|
+
"jscodeshift": "^17.3.0",
|
|
26
|
+
"prettier": "^3.6.2",
|
|
23
27
|
"ts-jest": "^29.4.5",
|
|
24
28
|
"typescript": "^5.6.3"
|
|
25
29
|
},
|