hifun-tools 1.3.28 → 1.3.30
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 +147 -119
- package/dist/init/utils.d.ts +23 -4
- package/dist/init/utils.js +133 -134
- package/dist/msg/index.d.ts +18 -0
- package/dist/msg/index.js +262 -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,216 @@
|
|
|
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;
|
|
18
43
|
}
|
|
19
|
-
/**
|
|
44
|
+
/** 是否 App 环境 */
|
|
45
|
+
getIsApp() {
|
|
46
|
+
return !!this.appTenant && !!this.appLine;
|
|
47
|
+
}
|
|
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
|
+
rewardMsg({
|
|
148
|
+
title: "提示",
|
|
149
|
+
text: "网络环境异常,刷新页面以重置",
|
|
150
|
+
onSubmit() {
|
|
151
|
+
location.reload();
|
|
152
|
+
},
|
|
153
|
+
});
|
|
154
|
+
try {
|
|
155
|
+
getOptimalDecodedString([this.getBaseUrl()]);
|
|
132
156
|
}
|
|
133
|
-
|
|
134
|
-
console.log(
|
|
135
|
-
const res2 = await this.LoadGatewayConfig();
|
|
136
|
-
results.push(res2);
|
|
157
|
+
catch (error) {
|
|
158
|
+
console.log(error);
|
|
137
159
|
}
|
|
138
|
-
console.log("所有初始化完成:", results);
|
|
139
|
-
return results;
|
|
140
160
|
}
|
|
141
|
-
|
|
161
|
+
/** 获取 lineDict.txt 内容 */
|
|
162
|
+
async _fetchLineDict() {
|
|
163
|
+
const response = await fetch(`/lineDict.txt?t=${Date.now()}`, {
|
|
164
|
+
method: "GET",
|
|
165
|
+
cache: "no-store",
|
|
166
|
+
});
|
|
167
|
+
if (!response.ok)
|
|
168
|
+
throw new Error(`HTTP 错误: ${response.status}`);
|
|
169
|
+
return response.text();
|
|
170
|
+
}
|
|
171
|
+
/** 获取并处理 lineAddress.txt */
|
|
172
|
+
async _loadGatewayConfig() {
|
|
142
173
|
if (this.domainBaseUrl)
|
|
143
174
|
return this.domainBaseUrl;
|
|
144
175
|
try {
|
|
145
176
|
const response = await fetch(`/lineAddress.txt?t=${Date.now()}`);
|
|
146
177
|
const configText = await response.text();
|
|
147
|
-
|
|
148
|
-
const { lineGroup } = this.getTenantDict();
|
|
178
|
+
const OriginBaseUrl = JSON.parse(AesDecrypt(configText));
|
|
149
179
|
const dictList = this.getTenantDictList();
|
|
150
|
-
|
|
151
|
-
|
|
180
|
+
const lineGroup = this.getTenantDict()?.lineGroup;
|
|
181
|
+
let baseUrl = filterSmartLines(dictList, OriginBaseUrl, lineGroup);
|
|
182
|
+
try {
|
|
183
|
+
this.domainBaseUrl = toStandardUrl(await getOptimalDecodedString(baseUrl));
|
|
184
|
+
console.info("✅ 成功加载生产环境配置:", this.domainBaseUrl);
|
|
185
|
+
}
|
|
186
|
+
catch {
|
|
187
|
+
// 备用策略
|
|
188
|
+
let allBaseUrl = filterSmartLines(dictList, OriginBaseUrl);
|
|
152
189
|
try {
|
|
153
|
-
this.domainBaseUrl = toStandardUrl(await getOptimalDecodedString(baseUrl));
|
|
154
|
-
console.
|
|
190
|
+
this.domainBaseUrl = toStandardUrl(await getOptimalDecodedString(difArr(allBaseUrl, baseUrl)));
|
|
191
|
+
console.info("✅ 备用配置成功:", this.domainBaseUrl);
|
|
155
192
|
}
|
|
156
|
-
catch
|
|
157
|
-
this.domainBaseUrl = toStandardUrl(
|
|
158
|
-
console.warn("⚠️
|
|
193
|
+
catch {
|
|
194
|
+
this.domainBaseUrl = toStandardUrl(allBaseUrl[0]);
|
|
195
|
+
console.warn("⚠️ 备选测速失败,使用备用配置:", allBaseUrl);
|
|
159
196
|
}
|
|
160
197
|
}
|
|
161
|
-
else {
|
|
162
|
-
this.domainBaseUrl = toStandardUrl(baseUrl);
|
|
163
|
-
console.log("✅ 成功加载生产环境配置:", this.domainBaseUrl);
|
|
164
|
-
}
|
|
165
198
|
return this.domainBaseUrl;
|
|
166
199
|
}
|
|
167
|
-
catch (
|
|
168
|
-
console.error(
|
|
200
|
+
catch (err) {
|
|
201
|
+
console.error("⚠️ 加载 lineAddress.txt 失败:", err);
|
|
169
202
|
if ((this.defaultBaseUrl &&
|
|
170
203
|
(location.host.includes("iggame") ||
|
|
171
204
|
location.host.includes("localhost"))) ||
|
|
172
205
|
(!!this.localPath &&
|
|
173
206
|
location.origin.includes(this.localPath.replace(/\/+$/, "")))) {
|
|
174
|
-
console.info(`🏠 iggame匹配到默认链接`, this.defaultBaseUrl);
|
|
175
207
|
this.domainBaseUrl = toStandardUrl(this.defaultBaseUrl);
|
|
208
|
+
console.info("🏠 使用默认 BaseUrl:", this.domainBaseUrl);
|
|
176
209
|
return this.domainBaseUrl;
|
|
177
210
|
}
|
|
178
|
-
|
|
179
|
-
console.warn("⚠️ 加载 lineAddress.txt 失败:", error);
|
|
180
|
-
return null;
|
|
181
|
-
}
|
|
211
|
+
return null;
|
|
182
212
|
}
|
|
183
213
|
}
|
|
184
|
-
AesEncrypt = AesEncrypt;
|
|
185
|
-
AesDecrypt = AesDecrypt;
|
|
186
214
|
}
|
|
187
215
|
const HF = new InitCls();
|
|
188
216
|
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,131 +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) {
|
|
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 isIp ? await getOptimal(ipList) : await getOptimal(domainList);
|
|
61
|
-
}
|
|
62
|
-
/**
|
|
63
|
-
* normalizeHost:保留端口,只清理协议
|
|
64
|
-
*/
|
|
65
|
-
function normalizeHost(hostname) {
|
|
1
|
+
/** 浏览器识别(结果缓存) */
|
|
2
|
+
let cachedBrowser = null;
|
|
3
|
+
/** 保留端口,去掉协议 */
|
|
4
|
+
export function normalizeHost(hostname) {
|
|
66
5
|
hostname = hostname.trim().toLowerCase();
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
urlStr = "http://" + hostname;
|
|
71
|
-
}
|
|
6
|
+
const urlStr = /^https?:\/\//i.test(hostname)
|
|
7
|
+
? hostname
|
|
8
|
+
: `http://${hostname}`;
|
|
72
9
|
try {
|
|
73
10
|
const url = new URL(urlStr);
|
|
74
|
-
// 保留 hostname + port
|
|
75
11
|
return url.port ? `${url.hostname}:${url.port}` : url.hostname;
|
|
76
12
|
}
|
|
77
|
-
catch
|
|
78
|
-
return hostname;
|
|
13
|
+
catch {
|
|
14
|
+
return hostname;
|
|
79
15
|
}
|
|
80
16
|
}
|
|
81
|
-
/**
|
|
82
|
-
|
|
83
|
-
*/
|
|
84
|
-
function isDomainStrictEqual(a, b) {
|
|
85
|
-
a = normalizeHost(a);
|
|
86
|
-
b = normalizeHost(b);
|
|
87
|
-
if (a === b)
|
|
88
|
-
return true;
|
|
17
|
+
/** 域名严格匹配,允许 www 特例 */
|
|
18
|
+
export function isDomainStrictEqual(a, b) {
|
|
89
19
|
const stripWww = (h) => (h.startsWith("www.") ? h.slice(4) : h);
|
|
90
|
-
return stripWww(a) === stripWww(b);
|
|
20
|
+
return stripWww(normalizeHost(a)) === stripWww(normalizeHost(b));
|
|
91
21
|
}
|
|
92
22
|
export function isDomainMatch(list, target) {
|
|
93
23
|
const t = normalizeHost(target);
|
|
94
|
-
return list.some((item) =>
|
|
95
|
-
const h = normalizeHost(item);
|
|
96
|
-
return isDomainStrictEqual(h, t);
|
|
97
|
-
});
|
|
24
|
+
return list.some((item) => isDomainStrictEqual(item, t));
|
|
98
25
|
}
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
let protocol = window?.location?.protocol || "https:";
|
|
105
|
-
if (!/^https?:\/\//i.test(input)) {
|
|
106
|
-
input = protocol + "//" + input;
|
|
26
|
+
/** 检查是否 IPv4 */
|
|
27
|
+
export function isIPv4(str) {
|
|
28
|
+
if (str.startsWith("http")) {
|
|
29
|
+
try {
|
|
30
|
+
str = new URL(str).hostname;
|
|
107
31
|
}
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
const port = url.port;
|
|
111
|
-
const isHostnameValid = hostname === "localhost" ||
|
|
112
|
-
(/^[a-zA-Z0-9.-]+$/.test(hostname) &&
|
|
113
|
-
(hostname.includes(".") || isIPv4(hostname)));
|
|
114
|
-
if (!isHostnameValid) {
|
|
115
|
-
return "";
|
|
32
|
+
catch {
|
|
33
|
+
return false;
|
|
116
34
|
}
|
|
117
|
-
return port
|
|
118
|
-
? `${url.protocol}//${hostname}:${port}`
|
|
119
|
-
: `${url.protocol}//${hostname}`;
|
|
120
|
-
}
|
|
121
|
-
catch (e) {
|
|
122
|
-
return "";
|
|
123
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);
|
|
124
39
|
}
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
function detectBrowser() {
|
|
40
|
+
export function detectBrowser() {
|
|
41
|
+
if (cachedBrowser)
|
|
42
|
+
return cachedBrowser;
|
|
129
43
|
const ua = navigator.userAgent.toLowerCase();
|
|
130
44
|
const rules = [
|
|
131
45
|
{ key: "baidu", regex: /baidubrowser|baiduboxapp|baidu/i },
|
|
@@ -148,43 +62,128 @@ function detectBrowser() {
|
|
|
148
62
|
{ key: "alipay", regex: /alipayclient/i },
|
|
149
63
|
{ key: "douyin", regex: /aweme|douyin/i },
|
|
150
64
|
];
|
|
151
|
-
for (const
|
|
152
|
-
if (
|
|
65
|
+
for (const { key, regex, exclude } of rules) {
|
|
66
|
+
if (exclude && exclude.test(ua))
|
|
153
67
|
continue;
|
|
154
|
-
if (
|
|
155
|
-
return
|
|
68
|
+
if (regex.test(ua))
|
|
69
|
+
return (cachedBrowser = key);
|
|
156
70
|
}
|
|
157
|
-
return "unknown";
|
|
71
|
+
return (cachedBrowser = "unknown");
|
|
158
72
|
}
|
|
73
|
+
/** 匹配浏览器 */
|
|
159
74
|
export function matchBrowser(checkItem, currentBrowser) {
|
|
160
75
|
if (!checkItem || !currentBrowser)
|
|
161
76
|
return false;
|
|
162
|
-
const normalized = checkItem.
|
|
77
|
+
const normalized = checkItem.toLowerCase();
|
|
163
78
|
const browser = currentBrowser.toLowerCase();
|
|
164
79
|
if (normalized.startsWith("/") && normalized.endsWith("/")) {
|
|
165
80
|
try {
|
|
166
|
-
|
|
167
|
-
const regex = new RegExp(body, "i");
|
|
168
|
-
return regex.test(browser);
|
|
81
|
+
return new RegExp(normalized.slice(1, -1), "i").test(browser);
|
|
169
82
|
}
|
|
170
|
-
catch
|
|
83
|
+
catch { }
|
|
171
84
|
}
|
|
172
85
|
return browser.includes(normalized);
|
|
173
86
|
}
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
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}`;
|
|
177
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("参数必须是数组");
|
|
178
116
|
const currentBrowser = detectBrowser();
|
|
179
|
-
let
|
|
180
|
-
const checks = item.browserCheck || [];
|
|
181
|
-
const blocked = checks.some((checkItem) => matchBrowser(checkItem, currentBrowser));
|
|
182
|
-
return !blocked;
|
|
183
|
-
});
|
|
117
|
+
let filtered = tenantList.filter((item) => !(item.browserCheck || []).some((check) => matchBrowser(check, currentBrowser)));
|
|
184
118
|
if (tenant)
|
|
185
|
-
|
|
119
|
+
filtered = filtered.filter((item) => item.tenant === tenant);
|
|
186
120
|
if (groupType)
|
|
187
|
-
|
|
188
|
-
const lines =
|
|
189
|
-
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];
|
|
190
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,262 @@
|
|
|
1
|
+
// --------------------------- 普通消息 ---------------------------
|
|
2
|
+
let messageStackContainer = null;
|
|
3
|
+
// --------------------------- reward 弹窗 ---------------------------
|
|
4
|
+
let rewardMask = null;
|
|
5
|
+
let rewardStackContainer = null;
|
|
6
|
+
function getMessageStackContainer() {
|
|
7
|
+
if (!messageStackContainer) {
|
|
8
|
+
messageStackContainer = document.createElement("div");
|
|
9
|
+
Object.assign(messageStackContainer.style, {
|
|
10
|
+
position: "fixed",
|
|
11
|
+
bottom: "10vh",
|
|
12
|
+
left: "50%",
|
|
13
|
+
transform: "translateX(-50%)",
|
|
14
|
+
display: "flex",
|
|
15
|
+
flexDirection: "column",
|
|
16
|
+
alignItems: "center",
|
|
17
|
+
gap: "10px",
|
|
18
|
+
zIndex: "9999",
|
|
19
|
+
pointerEvents: "none",
|
|
20
|
+
width: "100%",
|
|
21
|
+
maxWidth: "calc(100% - 40px)",
|
|
22
|
+
});
|
|
23
|
+
document.body.appendChild(messageStackContainer);
|
|
24
|
+
}
|
|
25
|
+
return messageStackContainer;
|
|
26
|
+
}
|
|
27
|
+
function hideMsg(element, callback) {
|
|
28
|
+
element.style.opacity = "0";
|
|
29
|
+
element.style.transform += " translateY(20px)";
|
|
30
|
+
setTimeout(() => {
|
|
31
|
+
element.parentNode?.removeChild(element);
|
|
32
|
+
callback?.();
|
|
33
|
+
}, 300);
|
|
34
|
+
}
|
|
35
|
+
function getColorByType(type) {
|
|
36
|
+
switch (type) {
|
|
37
|
+
case "success":
|
|
38
|
+
return "#67C23A";
|
|
39
|
+
case "warning":
|
|
40
|
+
return "#E6A23C";
|
|
41
|
+
case "error":
|
|
42
|
+
return "#F56C6C";
|
|
43
|
+
case "info":
|
|
44
|
+
return "#409EFF";
|
|
45
|
+
default:
|
|
46
|
+
return "#409EFF";
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
function getRewardMask() {
|
|
50
|
+
if (!rewardMask) {
|
|
51
|
+
rewardMask = document.createElement("div");
|
|
52
|
+
Object.assign(rewardMask.style, {
|
|
53
|
+
position: "fixed",
|
|
54
|
+
top: "0",
|
|
55
|
+
left: "0",
|
|
56
|
+
width: "100vw",
|
|
57
|
+
height: "100vh",
|
|
58
|
+
background: "rgba(0,0,0,0.5)", // 使用 rgba,蒙层透明不影响弹窗
|
|
59
|
+
zIndex: "100000",
|
|
60
|
+
pointerEvents: "auto",
|
|
61
|
+
display: "flex",
|
|
62
|
+
justifyContent: "center",
|
|
63
|
+
alignItems: "center",
|
|
64
|
+
});
|
|
65
|
+
document.body.appendChild(rewardMask);
|
|
66
|
+
}
|
|
67
|
+
return rewardMask;
|
|
68
|
+
}
|
|
69
|
+
function getRewardStackContainer() {
|
|
70
|
+
if (!rewardStackContainer) {
|
|
71
|
+
rewardStackContainer = document.createElement("div");
|
|
72
|
+
Object.assign(rewardStackContainer.style, {
|
|
73
|
+
position: "relative",
|
|
74
|
+
display: "flex",
|
|
75
|
+
flexDirection: "column",
|
|
76
|
+
alignItems: "center",
|
|
77
|
+
gap: "12px",
|
|
78
|
+
width: "100%",
|
|
79
|
+
maxWidth: "calc(100% - 40px)",
|
|
80
|
+
});
|
|
81
|
+
getRewardMask().appendChild(rewardStackContainer);
|
|
82
|
+
}
|
|
83
|
+
return rewardStackContainer;
|
|
84
|
+
}
|
|
85
|
+
export function msg(message, options = {}) {
|
|
86
|
+
const { type = "info", duration = 3000, closable = false, onClose } = options;
|
|
87
|
+
const container = document.createElement("div");
|
|
88
|
+
Object.assign(container.style, {
|
|
89
|
+
backgroundColor: getColorByType(type),
|
|
90
|
+
color: "#fff",
|
|
91
|
+
borderRadius: "12px",
|
|
92
|
+
fontSize: "12px",
|
|
93
|
+
fontWeight: "500",
|
|
94
|
+
padding: "10px 16px",
|
|
95
|
+
maxWidth: "400px",
|
|
96
|
+
minWidth: "120px",
|
|
97
|
+
textAlign: "center",
|
|
98
|
+
display: "-webkit-box",
|
|
99
|
+
WebkitLineClamp: "2",
|
|
100
|
+
WebkitBoxOrient: "vertical",
|
|
101
|
+
overflow: "hidden",
|
|
102
|
+
textOverflow: "ellipsis",
|
|
103
|
+
wordWrap: "break-word",
|
|
104
|
+
boxShadow: "0 6px 18px rgba(0,0,0,0.12)",
|
|
105
|
+
opacity: "0",
|
|
106
|
+
transform: "translateY(20px)",
|
|
107
|
+
transition: "all 0.3s cubic-bezier(0.4,0,0.2,1)",
|
|
108
|
+
pointerEvents: "auto",
|
|
109
|
+
position: "relative",
|
|
110
|
+
});
|
|
111
|
+
container.textContent = message;
|
|
112
|
+
if (closable) {
|
|
113
|
+
const closeBtn = document.createElement("span");
|
|
114
|
+
closeBtn.innerHTML = "×";
|
|
115
|
+
Object.assign(closeBtn.style, {
|
|
116
|
+
position: "absolute",
|
|
117
|
+
top: "50%",
|
|
118
|
+
right: "10px",
|
|
119
|
+
transform: "translateY(-50%)",
|
|
120
|
+
cursor: "pointer",
|
|
121
|
+
fontSize: "14px",
|
|
122
|
+
fontWeight: "bold",
|
|
123
|
+
});
|
|
124
|
+
closeBtn.onclick = () => hideMsg(container, onClose);
|
|
125
|
+
container.appendChild(closeBtn);
|
|
126
|
+
container.style.paddingRight = "30px";
|
|
127
|
+
}
|
|
128
|
+
const stack = getMessageStackContainer();
|
|
129
|
+
stack.appendChild(container);
|
|
130
|
+
requestAnimationFrame(() => {
|
|
131
|
+
container.style.opacity = "1";
|
|
132
|
+
container.style.transform = "translateY(0)";
|
|
133
|
+
});
|
|
134
|
+
if (duration > 0) {
|
|
135
|
+
setTimeout(() => hideMsg(container, onClose), duration);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
export function rewardMsg(options) {
|
|
139
|
+
const { title, text, topImg, submitText, cancelText, onSubmit, onCancel, onClose, } = options;
|
|
140
|
+
const mask = getRewardMask();
|
|
141
|
+
const originalOverflow = document.body.style.overflow;
|
|
142
|
+
document.body.style.overflow = "hidden"; // 禁止下方滚动
|
|
143
|
+
const container = document.createElement("div");
|
|
144
|
+
container.className = "rewardWindow";
|
|
145
|
+
Object.assign(container.style, {
|
|
146
|
+
position: "relative",
|
|
147
|
+
width: "calc(100% - 60px)",
|
|
148
|
+
maxWidth: "370px",
|
|
149
|
+
paddingTop: topImg ? "88px" : "23px",
|
|
150
|
+
backgroundColor: "#fff",
|
|
151
|
+
borderRadius: "12px",
|
|
152
|
+
boxSizing: "border-box",
|
|
153
|
+
display: "flex",
|
|
154
|
+
flexDirection: "column",
|
|
155
|
+
alignItems: "center",
|
|
156
|
+
textAlign: "center",
|
|
157
|
+
opacity: "0",
|
|
158
|
+
transform: "translateY(-20px) scale(0.9)",
|
|
159
|
+
transition: "all 0.3s cubic-bezier(0.4,0,0.2,1)",
|
|
160
|
+
overflow: "hidden",
|
|
161
|
+
zIndex: "100001",
|
|
162
|
+
});
|
|
163
|
+
// 标题
|
|
164
|
+
if (title) {
|
|
165
|
+
const titleEl = document.createElement("div");
|
|
166
|
+
titleEl.textContent = title;
|
|
167
|
+
Object.assign(titleEl.style, {
|
|
168
|
+
fontSize: "16px",
|
|
169
|
+
fontWeight: "600",
|
|
170
|
+
color: "#303442",
|
|
171
|
+
});
|
|
172
|
+
container.appendChild(titleEl);
|
|
173
|
+
}
|
|
174
|
+
// 顶部图片
|
|
175
|
+
if (topImg) {
|
|
176
|
+
const imgEl = document.createElement("img");
|
|
177
|
+
imgEl.src = topImg;
|
|
178
|
+
Object.assign(imgEl.style, {
|
|
179
|
+
position: "absolute",
|
|
180
|
+
top: "-68px",
|
|
181
|
+
height: "150px",
|
|
182
|
+
width: "150px",
|
|
183
|
+
});
|
|
184
|
+
container.appendChild(imgEl);
|
|
185
|
+
}
|
|
186
|
+
// 文本内容
|
|
187
|
+
if (text) {
|
|
188
|
+
const textEl = document.createElement("div");
|
|
189
|
+
textEl.innerHTML = text;
|
|
190
|
+
Object.assign(textEl.style, {
|
|
191
|
+
padding: "14px 22px 12px",
|
|
192
|
+
fontSize: "12px",
|
|
193
|
+
fontWeight: "500",
|
|
194
|
+
lineHeight: "16px",
|
|
195
|
+
textAlign: "center",
|
|
196
|
+
width: "100%",
|
|
197
|
+
overflow: "hidden",
|
|
198
|
+
wordBreak: "break-word",
|
|
199
|
+
color: "#7981A4",
|
|
200
|
+
});
|
|
201
|
+
container.appendChild(textEl);
|
|
202
|
+
}
|
|
203
|
+
// 按钮容器
|
|
204
|
+
const btnContainer = document.createElement("div");
|
|
205
|
+
Object.assign(btnContainer.style, {
|
|
206
|
+
display: "flex",
|
|
207
|
+
width: "100%",
|
|
208
|
+
height: "44px",
|
|
209
|
+
borderTop: "1px solid #f2f2f6",
|
|
210
|
+
padding: "8px 0",
|
|
211
|
+
});
|
|
212
|
+
const btnStyle = {
|
|
213
|
+
flex: "1",
|
|
214
|
+
border: "none",
|
|
215
|
+
background: "transparent",
|
|
216
|
+
cursor: "pointer",
|
|
217
|
+
fontSize: "14px",
|
|
218
|
+
fontWeight: "500",
|
|
219
|
+
};
|
|
220
|
+
// 仅当传入 cancelText 才显示取消按钮
|
|
221
|
+
if (cancelText) {
|
|
222
|
+
const cancelBtn = document.createElement("button");
|
|
223
|
+
cancelBtn.textContent = cancelText;
|
|
224
|
+
Object.assign(cancelBtn.style, {
|
|
225
|
+
...btnStyle,
|
|
226
|
+
borderRight: "1px solid #f2f2f6",
|
|
227
|
+
color: "#303442",
|
|
228
|
+
});
|
|
229
|
+
cancelBtn.onclick = () => {
|
|
230
|
+
hideMsg(container, onCancel ?? onClose);
|
|
231
|
+
// 如果这是最后一个弹窗,隐藏蒙层并恢复滚动
|
|
232
|
+
if (rewardStackContainer?.childElementCount === 1) {
|
|
233
|
+
hideMsg(mask);
|
|
234
|
+
document.body.style.overflow = originalOverflow;
|
|
235
|
+
}
|
|
236
|
+
};
|
|
237
|
+
btnContainer.appendChild(cancelBtn);
|
|
238
|
+
}
|
|
239
|
+
// 确认按钮始终显示
|
|
240
|
+
const confirmBtn = document.createElement("button");
|
|
241
|
+
confirmBtn.textContent = submitText || "确认";
|
|
242
|
+
Object.assign(confirmBtn.style, {
|
|
243
|
+
...btnStyle,
|
|
244
|
+
color: "#179CFF",
|
|
245
|
+
});
|
|
246
|
+
confirmBtn.onclick = () => {
|
|
247
|
+
onSubmit?.();
|
|
248
|
+
hideMsg(container, onClose);
|
|
249
|
+
if (rewardStackContainer?.childElementCount === 1) {
|
|
250
|
+
hideMsg(mask);
|
|
251
|
+
document.body.style.overflow = originalOverflow;
|
|
252
|
+
}
|
|
253
|
+
};
|
|
254
|
+
btnContainer.appendChild(confirmBtn);
|
|
255
|
+
container.appendChild(btnContainer);
|
|
256
|
+
const stack = getRewardStackContainer();
|
|
257
|
+
stack.appendChild(container);
|
|
258
|
+
requestAnimationFrame(() => {
|
|
259
|
+
container.style.opacity = "1";
|
|
260
|
+
container.style.transform = "translateY(0) scale(1)";
|
|
261
|
+
});
|
|
262
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hifun-tools",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.30",
|
|
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
|
},
|