@flun/html-template 4.0.10
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/.env +9 -0
- package/LICENSE +15 -0
- package/build.js +3 -0
- package/compile.js +349 -0
- package/copy-files.js +200 -0
- package/customize/account.js +726 -0
- package/customize/data.json +484 -0
- package/customize/functions.js +48 -0
- package/customize/hotReloadInjector.js +25 -0
- package/customize/routes.js +141 -0
- package/customize/users.json +44 -0
- package/customize/variables.js +70 -0
- package/dev-server.js +344 -0
- package/dev.js +4 -0
- package/f-CHANGELOG.md +4 -0
- package/f-README.md +485 -0
- package/index.d.ts +133 -0
- package/index.js +4 -0
- package/package.json +77 -0
- package/restoreDefaults.js +8 -0
- package/services/templateService.js +962 -0
- package/static/about.css +118 -0
- package/static/auth.js +27 -0
- package/static/constants.css +138 -0
- package/static/img/dark.png +0 -0
- package/static/img/favicon.ico +0 -0
- package/static/img/light.png +0 -0
- package/static/img/top.png +0 -0
- package/static/index.css +86 -0
- package/static/mouseOrTouch.js +156 -0
- package/static/public.css +288 -0
- package/static/script.css +318 -0
- package/static/script.js +392 -0
- package/static/styling.css +874 -0
- package/static/styling.js +933 -0
- package/static/themeImg.css +10 -0
- package/static/themeImg.js +19 -0
- package/static/themeModule.js +222 -0
- package/static/topImg.css +19 -0
- package/static/topImg.js +21 -0
- package/static/utils/browser13.js +270 -0
- package/static/utils/closebrackets.js +166 -0
- package/static/utils/css-lint.js +308 -0
- package/static/utils/custom-css-hint.js +876 -0
- package/static/utils/foldgutter.js +141 -0
- package/static/utils/match-highlighter.js +70 -0
- package/templates/about.html +236 -0
- package/templates/account/2fa.html +184 -0
- package/templates/account/forgot-password.html +226 -0
- package/templates/account/login.html +230 -0
- package/templates/account/profile.html +977 -0
- package/templates/account/register.html +224 -0
- package/templates/account/reset-password.html +205 -0
- package/templates/account/verify-email.html +163 -0
- package/templates/base.html +71 -0
- package/templates/footer-content.html +5 -0
- package/templates/index.html +140 -0
- package/templates/script.html +209 -0
- package/templates/test-include.html +11 -0
package/static/about.css
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
.abt_content_main {
|
|
2
|
+
display: flex;
|
|
3
|
+
gap: 20px;
|
|
4
|
+
margin-top: 30px;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
/* 模板测试样式 */
|
|
8
|
+
.template-test {
|
|
9
|
+
background: #f8e8e8;
|
|
10
|
+
padding: 20px;
|
|
11
|
+
border-radius: 8px;
|
|
12
|
+
margin: 20px 0;
|
|
13
|
+
border-left: 4px solid #F44336;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.template-test h3 {
|
|
17
|
+
color: #F44336;
|
|
18
|
+
margin-top: 0;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
.test-result {
|
|
22
|
+
background: rgb(145, 76, 76);
|
|
23
|
+
padding: 10px;
|
|
24
|
+
border-radius: 4px;
|
|
25
|
+
margin: 10px 0;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/* 条件判断和循环测试样式 */
|
|
29
|
+
.logic-test {
|
|
30
|
+
background: #fff8e8;
|
|
31
|
+
padding: 20px;
|
|
32
|
+
border-radius: 8px;
|
|
33
|
+
margin: 20px 0;
|
|
34
|
+
border-left: 4px solid #FF9800;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.logic-test h3 {
|
|
38
|
+
color: #FF9800;
|
|
39
|
+
margin-top: 0;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
.product-list {
|
|
43
|
+
display: grid;
|
|
44
|
+
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
|
|
45
|
+
gap: 15px;
|
|
46
|
+
margin: 15px 0;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.product-card {
|
|
50
|
+
background: rgb(36, 119, 150);
|
|
51
|
+
padding: 15px;
|
|
52
|
+
border-radius: 5px;
|
|
53
|
+
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.user-status {
|
|
57
|
+
padding: 10px;
|
|
58
|
+
margin: 10px 0;
|
|
59
|
+
border-radius: 5px;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.logged-in {
|
|
63
|
+
background: #5b3caa;
|
|
64
|
+
border-left: 4px solid #4CAF50;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
.logged-out {
|
|
68
|
+
background: #ab4c4c;
|
|
69
|
+
border-left: 4px solid #F44336;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
.team-list {
|
|
73
|
+
margin: 15px 0;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
.team-member {
|
|
77
|
+
padding: 10px;
|
|
78
|
+
margin: 5px 0;
|
|
79
|
+
background: #425b72;
|
|
80
|
+
border-radius: 5px;
|
|
81
|
+
border-left: 4px solid #2196F3;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/* 复杂能测试样式 */
|
|
85
|
+
.new-feature-test {
|
|
86
|
+
background: #a6c46e;
|
|
87
|
+
padding: 20px;
|
|
88
|
+
border-radius: 8px;
|
|
89
|
+
margin: 20px 0;
|
|
90
|
+
border-left: 4px solid #2196F3;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
.new-feature-test h3 {
|
|
94
|
+
color: #2196F3;
|
|
95
|
+
margin-top: 0;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
.control-flow {
|
|
99
|
+
background: #432e96;
|
|
100
|
+
padding: 15px;
|
|
101
|
+
border-radius: 5px;
|
|
102
|
+
margin: 10px 0;
|
|
103
|
+
border-left: 4px solid #4CAF50;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
.empty-state {
|
|
107
|
+
background: #b8965f;
|
|
108
|
+
padding: 15px;
|
|
109
|
+
border-radius: 5px;
|
|
110
|
+
margin: 10px 0;
|
|
111
|
+
border-left: 4px solid #FF9800;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
hr {
|
|
115
|
+
border: none;
|
|
116
|
+
border-top: 2px solid #aa2c2c;
|
|
117
|
+
margin: 20px 0;
|
|
118
|
+
}
|
package/static/auth.js
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// /static/auth.js
|
|
2
|
+
|
|
3
|
+
(function () {
|
|
4
|
+
const userCenter = document.getElementById('userCenter');
|
|
5
|
+
if (!userCenter) return;
|
|
6
|
+
|
|
7
|
+
async function checkLoginStatus() {
|
|
8
|
+
try {
|
|
9
|
+
const res = await fetch('/api/user', {
|
|
10
|
+
method: 'GET', credentials: 'include'
|
|
11
|
+
}), data = await res.json();
|
|
12
|
+
|
|
13
|
+
if (data?.username) {
|
|
14
|
+
userCenter.style.display = 'flex';
|
|
15
|
+
const link = userCenter.querySelector('a');
|
|
16
|
+
if (link) link.innerHTML = `👤 ${data.username}`;
|
|
17
|
+
document.dispatchEvent(new CustomEvent('userCenterReady')); // 创建并派发自定义事件,通知用户中心已准备好
|
|
18
|
+
}
|
|
19
|
+
else userCenter.style.display = 'none';
|
|
20
|
+
} catch (err) {
|
|
21
|
+
userCenter.style.display = 'none';
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', checkLoginStatus);
|
|
26
|
+
else checkLoginStatus();
|
|
27
|
+
})();
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/* ================================================================ *
|
|
2
|
+
* 主题变量定义
|
|
3
|
+
* ================================================================ */
|
|
4
|
+
|
|
5
|
+
/* 浅色主题变量定义 */
|
|
6
|
+
:root {
|
|
7
|
+
/* 基础背景色 */
|
|
8
|
+
--bg-color: #f3f3f3;
|
|
9
|
+
/* 页面渐变背景色 */
|
|
10
|
+
--body-bg: linear-gradient(135deg, #f5f7fa 0%, #e4e7f1 100%);
|
|
11
|
+
/* 主要文字颜色 */
|
|
12
|
+
--text-color: #000000;
|
|
13
|
+
|
|
14
|
+
/* 头部渐变背景色 */
|
|
15
|
+
--header-bg: linear-gradient(to right, #f8f9fa, #e8f4fc);
|
|
16
|
+
/* 头部文字颜色 */
|
|
17
|
+
--header-text: #3498db;
|
|
18
|
+
/* 头部阴影 */
|
|
19
|
+
--header-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
|
20
|
+
/* 头部链接半透明背景 */
|
|
21
|
+
--header-link-bg: rgba(255, 255, 255, 0.2);
|
|
22
|
+
/* 头部链接悬停半透明背景 */
|
|
23
|
+
--header-link-hover-bg: rgba(255, 255, 255, 0.15);
|
|
24
|
+
/* 用户中心链接阴影 */
|
|
25
|
+
--link-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
|
26
|
+
|
|
27
|
+
/* 内容容器背景色 */
|
|
28
|
+
--container-bg: white;
|
|
29
|
+
/* 容器阴影 */
|
|
30
|
+
--container-shadow: 0 6px 20px rgba(0, 0, 0, 0.08);
|
|
31
|
+
/* 页脚背景色 */
|
|
32
|
+
--footer-bg: #a3c3e2;
|
|
33
|
+
|
|
34
|
+
/* 一级标题颜色 */
|
|
35
|
+
--h1-color: #2c3e50;
|
|
36
|
+
/* 一级标题下边框颜色 */
|
|
37
|
+
--h1-border: #3498db;
|
|
38
|
+
/* 二级标题颜色 */
|
|
39
|
+
--h2-color: #2980b9;
|
|
40
|
+
/* 段落文字颜色 */
|
|
41
|
+
--p-color: #000000;
|
|
42
|
+
|
|
43
|
+
/* 列表项背景色 */
|
|
44
|
+
--li-bg: #f8f9fa;
|
|
45
|
+
/* 列表项悬停背景色 */
|
|
46
|
+
--li-hover: #e8f4fc;
|
|
47
|
+
/* 列表项阴影 */
|
|
48
|
+
--item-shadow: 0 2px 5px rgba(0, 0, 0, 0.05);
|
|
49
|
+
|
|
50
|
+
/* 链接颜色 */
|
|
51
|
+
--link-color: #3498db;
|
|
52
|
+
/* 链接悬停颜色 */
|
|
53
|
+
--link-hover: #2980b9;
|
|
54
|
+
|
|
55
|
+
/* 按钮渐变背景 */
|
|
56
|
+
--btn-bg: linear-gradient(to right, #3498db, #2980b9);
|
|
57
|
+
/* 按钮悬停渐变背景 */
|
|
58
|
+
--btn-hover: linear-gradient(to right, #2980b9, #2573a7);
|
|
59
|
+
/* 按钮阴影 */
|
|
60
|
+
--btn-shadow: 0 4px 8px rgba(52, 152, 219, 0.3);
|
|
61
|
+
/* 按钮悬停阴影 */
|
|
62
|
+
--btn-shadow-hover: 0 6px 12px rgba(52, 152, 219, 0.4);
|
|
63
|
+
|
|
64
|
+
/* 内容区域渐变起始色 */
|
|
65
|
+
--content-bg-from: #ffffff;
|
|
66
|
+
/* 内容区域渐变结束色 */
|
|
67
|
+
--content-bg-to: #f8f9fa;
|
|
68
|
+
/* 内容区域边框色 */
|
|
69
|
+
--content-border: #5c738f;
|
|
70
|
+
/* 内容区域阴影颜色 */
|
|
71
|
+
--content-shadow: rgba(0, 0, 0, 0.05);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/* 深色主题变量定义 */
|
|
75
|
+
[data-theme="dark"] {
|
|
76
|
+
/* 深色模式基础背景色 */
|
|
77
|
+
--bg-color: #121212;
|
|
78
|
+
/* 深色模式渐变背景 */
|
|
79
|
+
--body-bg: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
|
|
80
|
+
/* 深色模式文字颜色 */
|
|
81
|
+
--text-color: #e0e0e0;
|
|
82
|
+
|
|
83
|
+
/* 深色模式头部渐变背景 */
|
|
84
|
+
--header-bg: linear-gradient(to right, #1e1e2e, #1e1e28);
|
|
85
|
+
/* 深色模式头部文字颜色 */
|
|
86
|
+
--header-text: #e0e0e0;
|
|
87
|
+
/* 深色模式头部阴影(更暗) */
|
|
88
|
+
--header-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
|
|
89
|
+
/* 深色模式头部链接半透明背景(降低透明度) */
|
|
90
|
+
--header-link-bg: rgba(255, 255, 255, 0.1);
|
|
91
|
+
--header-link-hover-bg: rgba(255, 255, 255, 0.15);
|
|
92
|
+
--link-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
|
|
93
|
+
|
|
94
|
+
/* 深色模式内容容器背景 */
|
|
95
|
+
--container-bg: #222236;
|
|
96
|
+
/* 深色模式容器阴影 */
|
|
97
|
+
--container-shadow: 0 6px 20px rgba(0, 0, 0, 0.2);
|
|
98
|
+
/* 深色模式页脚背景 */
|
|
99
|
+
--footer-bg: #3a3a4a;
|
|
100
|
+
|
|
101
|
+
/* 深色模式一级标题颜色 */
|
|
102
|
+
--h1-color: #bb86fc;
|
|
103
|
+
/* 深色模式一级标题下边框颜色 */
|
|
104
|
+
--h1-border: #3700b3;
|
|
105
|
+
/* 深色模式二级标题颜色 */
|
|
106
|
+
--h2-color: #03dac6;
|
|
107
|
+
/* 深色模式段落文字颜色 */
|
|
108
|
+
--p-color: #cfd8dc;
|
|
109
|
+
|
|
110
|
+
/* 深色模式列表项背景 */
|
|
111
|
+
--li-bg: #2d2d3a;
|
|
112
|
+
/* 深色模式列表项悬停背景 */
|
|
113
|
+
--li-hover: #3a3a4a;
|
|
114
|
+
/* 深色模式列表项阴影 */
|
|
115
|
+
--item-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
|
|
116
|
+
|
|
117
|
+
/* 深色模式链接颜色 */
|
|
118
|
+
--link-color: #bb86fc;
|
|
119
|
+
/* 深色模式链接悬停颜色 */
|
|
120
|
+
--link-hover: #9a67ea;
|
|
121
|
+
|
|
122
|
+
/* 深色模式按钮渐变背景 */
|
|
123
|
+
--btn-bg: linear-gradient(to right, #6200ea, #3700b3);
|
|
124
|
+
/* 深色模式按钮悬停渐变背景 */
|
|
125
|
+
--btn-hover: linear-gradient(to right, #3700b3, #1a006e);
|
|
126
|
+
/* 深色模式按钮阴影(匹配紫色系) */
|
|
127
|
+
--btn-shadow: 0 4px 8px rgba(98, 0, 234, 0.3);
|
|
128
|
+
--btn-shadow-hover: 0 6px 12px rgba(98, 0, 234, 0.4);
|
|
129
|
+
|
|
130
|
+
/* 深色模式内容区域渐变起始色 */
|
|
131
|
+
--content-bg-from: #252536;
|
|
132
|
+
/* 深色模式内容区域渐变结束色 */
|
|
133
|
+
--content-bg-to: #1d1d2c;
|
|
134
|
+
/* 深色模式内容区域边框色 */
|
|
135
|
+
--content-border: #353589;
|
|
136
|
+
/* 深色模式内容区域阴影颜色 */
|
|
137
|
+
--content-shadow: rgba(0, 0, 0, 0.2);
|
|
138
|
+
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/static/index.css
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/* 内容区主样式 */
|
|
2
|
+
.ind_content_main {
|
|
3
|
+
margin: 40px auto 0;
|
|
4
|
+
padding: 30px;
|
|
5
|
+
background: linear-gradient(to bottom, var(--content-bg-from), var(--content-bg-to));
|
|
6
|
+
border-radius: 10px;
|
|
7
|
+
max-width: 800px;
|
|
8
|
+
border: 1px solid var(--content-border);
|
|
9
|
+
box-shadow: 0 4px 15px var(--content-shadow);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
.ind_content_main h2 {
|
|
13
|
+
margin-top: 0;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/* 模板测试样式 */
|
|
17
|
+
.template-test {
|
|
18
|
+
background: #e8f4f8;
|
|
19
|
+
padding: 20px;
|
|
20
|
+
border-radius: 8px;
|
|
21
|
+
margin: 20px 0;
|
|
22
|
+
border-left: 4px solid #2196F3;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.template-test h3 {
|
|
26
|
+
color: #2196F3;
|
|
27
|
+
margin-top: 0;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.test-result {
|
|
31
|
+
background: rgb(98, 154, 92);
|
|
32
|
+
padding: 10px;
|
|
33
|
+
border-radius: 4px;
|
|
34
|
+
margin: 10px 0;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/* 用户功能测试样式 */
|
|
38
|
+
.user-function-test {
|
|
39
|
+
background: #f0e8f8;
|
|
40
|
+
padding: 20px;
|
|
41
|
+
border-radius: 8px;
|
|
42
|
+
margin: 20px 0;
|
|
43
|
+
border-left: 4px solid #9C27B0;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.user-function-test h3 {
|
|
47
|
+
color: #9C27B0;
|
|
48
|
+
margin-top: 0;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/* 路由测试样式 */
|
|
52
|
+
.route-test {
|
|
53
|
+
background: #e8f8e8;
|
|
54
|
+
padding: 20px;
|
|
55
|
+
border-radius: 8px;
|
|
56
|
+
margin: 20px 0;
|
|
57
|
+
border-left: 4px solid #4CAF50;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.route-test h3 {
|
|
61
|
+
color: #4CAF50;
|
|
62
|
+
margin-top: 0;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
.test-button {
|
|
66
|
+
background: #4CAF50;
|
|
67
|
+
color: var(--text-color);
|
|
68
|
+
border: none;
|
|
69
|
+
padding: 10px 15px;
|
|
70
|
+
border-radius: 4px;
|
|
71
|
+
cursor: pointer;
|
|
72
|
+
margin: 5px;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
.test-button:hover {
|
|
76
|
+
background: #45a049;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
.api-result {
|
|
80
|
+
background: rgb(77, 106, 155);
|
|
81
|
+
padding: 10px;
|
|
82
|
+
border-radius: 4px;
|
|
83
|
+
margin: 10px 0;
|
|
84
|
+
border: 1px solid #ddd;
|
|
85
|
+
font-family: monospace;
|
|
86
|
+
}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
// 项目/static/mouseOrTouch.js
|
|
2
|
+
const devViewSize = `${window.innerWidth}x${window.innerHeight}`; // 使用设备视口作为设备唯一标识
|
|
3
|
+
let zIndex = 1001; // 全局层级计数器
|
|
4
|
+
/**
|
|
5
|
+
* 获取元素储存样式数据(位置)并通过API数据应用样式
|
|
6
|
+
* @param {HTMLElement} element - 需要设置位置的DOM元素
|
|
7
|
+
* @param {string} [api=null] - 获取位置信息的路由API
|
|
8
|
+
* @returns {void}
|
|
9
|
+
*/
|
|
10
|
+
function getStyle(element, api = null) {
|
|
11
|
+
fetch(api)
|
|
12
|
+
.then(res => res.ok ? res.json() : Promise.reject(`Network error:${element}`))
|
|
13
|
+
.then(position => Object.assign(element.style, position[devViewSize]?.style ?? {}))
|
|
14
|
+
.catch(error => console.error(`获取${devViewSize}的储存数据失败:`, error));
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* 为元素添加鼠标和触摸事件支持,实现拖拽和点击功能
|
|
19
|
+
* @param {HTMLElement} element - 需要添加交互功能的DOM元素
|
|
20
|
+
* @param {Function} onClick - 点击事件回调函数
|
|
21
|
+
* @param {string} [api=null] - 更新位置信息的路由API
|
|
22
|
+
* @param {boolean} [isEndShow=false] - 拖拽结束后是否显示元素
|
|
23
|
+
* @returns {void}
|
|
24
|
+
*/
|
|
25
|
+
function mouseOrTouch(element, onClick = null, api = null, isEndShow = false) {
|
|
26
|
+
let dragStartX, dragStartY, initialX, initialY, isDragging = false, hasDragged = false, touchStartTime = 0, touchIdentifier = null;
|
|
27
|
+
const originalZIndex = element.style.zIndex || getComputedStyle(element).zIndex;
|
|
28
|
+
|
|
29
|
+
setupEventBubbleBlocking();
|
|
30
|
+
element.onclick = e => {
|
|
31
|
+
if (hasDragged) return e.preventDefault();
|
|
32
|
+
if (onClick) onClick();
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
element.addEventListener('contextmenu', e => e.preventDefault());
|
|
36
|
+
element.addEventListener('mousedown', startDrag);
|
|
37
|
+
element.addEventListener('touchstart', handleTouchStart, { passive: true });
|
|
38
|
+
|
|
39
|
+
document.addEventListener('mousemove', drag);
|
|
40
|
+
document.addEventListener('mouseup', endDrag);
|
|
41
|
+
document.addEventListener('touchmove', handleTouchMove, { passive: false });
|
|
42
|
+
document.addEventListener('touchend', handleTouchEnd, { passive: true });
|
|
43
|
+
|
|
44
|
+
// ==================== 内部函数 ====================
|
|
45
|
+
function setupEventBubbleBlocking() {
|
|
46
|
+
const interactiveSelectors = ['textarea', 'input', 'select', 'a'];
|
|
47
|
+
interactiveSelectors.forEach(selector => {
|
|
48
|
+
document.querySelectorAll(selector).forEach(el => {
|
|
49
|
+
if (el.dataset.bubbleBlocked) return;
|
|
50
|
+
const stop = e => e.stopPropagation();
|
|
51
|
+
el.addEventListener('mousedown', stop);
|
|
52
|
+
el.addEventListener('touchstart', stop, { passive: true });
|
|
53
|
+
el.dataset.bubbleBlocked = true;
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function handleTouchStart(e) {
|
|
59
|
+
touchStartTime = Date.now(), touchIdentifier = e.touches[0].identifier, startDrag(e);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function handleTouchMove(e) {
|
|
63
|
+
if (touchIdentifier === null) return;
|
|
64
|
+
if (Array.from(e.touches).some(touch => touch.identifier === touchIdentifier)) {
|
|
65
|
+
if (isDragging) e.preventDefault();
|
|
66
|
+
drag(e);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function handleTouchEnd(e) {
|
|
71
|
+
if (touchIdentifier === null) return;
|
|
72
|
+
const changed = Array.from(e.changedTouches);
|
|
73
|
+
if (changed.some(touch => touch.identifier === touchIdentifier)) {
|
|
74
|
+
const touchDuration = Date.now() - touchStartTime, touch = changed[0];
|
|
75
|
+
if (onClick && !hasDragged && touchDuration < 250) {
|
|
76
|
+
const clickEvent = new MouseEvent('click', {
|
|
77
|
+
bubbles: true, cancelable: true, clientX: touch.clientX, clientY: touch.clientY
|
|
78
|
+
});
|
|
79
|
+
element.dispatchEvent(clickEvent), onClick();
|
|
80
|
+
}
|
|
81
|
+
endDrag(), touchIdentifier = null;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function startDrag(e) {
|
|
86
|
+
e.preventDefault(), isDragging = true, hasDragged = false;
|
|
87
|
+
|
|
88
|
+
const rect = element.getBoundingClientRect();
|
|
89
|
+
initialX = rect.left, initialY = rect.top, element.style.zIndex = zIndex++;
|
|
90
|
+
|
|
91
|
+
const clientX = e.type === 'mousedown' ? e.clientX : e.touches[0].clientX,
|
|
92
|
+
clientY = e.type === 'mousedown' ? e.clientY : e.touches[0].clientY;
|
|
93
|
+
|
|
94
|
+
dragStartX = clientX, dragStartY = clientY;
|
|
95
|
+
element.style.transition = 'none', element.style.cursor = 'grabbing';
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function drag(e) {
|
|
99
|
+
if (!isDragging) return;
|
|
100
|
+
|
|
101
|
+
let clientX, clientY;
|
|
102
|
+
if (e.type === 'mousemove') e.preventDefault(), clientX = e.clientX, clientY = e.clientY;
|
|
103
|
+
else {
|
|
104
|
+
const touch = Array.from(e.touches).find(t => t.identifier === touchIdentifier);
|
|
105
|
+
if (!touch) return;
|
|
106
|
+
clientX = touch.clientX, clientY = touch.clientY;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const deltaX = clientX - dragStartX, deltaY = clientY - dragStartY;
|
|
110
|
+
if (Math.abs(deltaX) > 5 || Math.abs(deltaY) > 5) hasDragged = true;
|
|
111
|
+
|
|
112
|
+
const newX = initialX + deltaX, newY = initialY + deltaY, minX = -element.offsetWidth + 20,
|
|
113
|
+
maxX = window.innerWidth - 20, minY = -element.offsetHeight + 20, maxY = window.innerHeight - 20;
|
|
114
|
+
|
|
115
|
+
element.style.left = `${Math.min(Math.max(minX, newX), maxX)}px`;
|
|
116
|
+
element.style.right = 'auto';
|
|
117
|
+
element.style.top = `${Math.min(Math.max(minY, newY), maxY)}px`;
|
|
118
|
+
element.style.bottom = 'auto';
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function endDrag() {
|
|
122
|
+
if (!isDragging) return;
|
|
123
|
+
const { left, top, right, bottom } = element.style, finalPosition = { [devViewSize]: { style: { left, top, right, bottom } } };
|
|
124
|
+
|
|
125
|
+
isDragging = false, element.style.transition = '', element.style.cursor = '';
|
|
126
|
+
if (isEndShow) element.classList.add('show');
|
|
127
|
+
if (api && hasDragged) {
|
|
128
|
+
fetch(api, {
|
|
129
|
+
method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(finalPosition)
|
|
130
|
+
})
|
|
131
|
+
.then(res => res.ok ? res.json() : Promise.reject(`Network error:${element}`))
|
|
132
|
+
.catch(error => console.error(`更新${devViewSize}储存样式数据失败:`, error));
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
element.style.zIndex = originalZIndex, setTimeout(() => hasDragged = false, 100);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* 为元素添加点击和触摸事件支持
|
|
140
|
+
* @param {HTMLElement} element - DOM元素
|
|
141
|
+
* @param {Function} handler - 事件处理函数
|
|
142
|
+
* @param {Object} options - 选项(阻止默认行为等)
|
|
143
|
+
*/
|
|
144
|
+
function addTapSupport(element, handler, options = { preventDefault: true }) {
|
|
145
|
+
if (!element || !handler) return;
|
|
146
|
+
|
|
147
|
+
element.addEventListener('click', handler); // 鼠标点击事件
|
|
148
|
+
// 触摸结束事件(模拟点击)
|
|
149
|
+
element.addEventListener('touchend', (e) => {
|
|
150
|
+
if (options.preventDefault && e.cancelable) e.preventDefault();
|
|
151
|
+
handler(e);
|
|
152
|
+
});
|
|
153
|
+
element.addEventListener('keydown', (e) => {
|
|
154
|
+
if (e.key === 'Enter' || e.key === ' ') handler(e);
|
|
155
|
+
});
|
|
156
|
+
}
|