@gogoqiu/tencent-http-server 0.0.3

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.
Files changed (84) hide show
  1. package/bin/gogoqiu-node-http-service +3 -0
  2. package/dist/build.d.ts +3 -0
  3. package/dist/build.d.ts.map +1 -0
  4. package/dist/build.js +3 -0
  5. package/dist/build.js.map +1 -0
  6. package/dist/routes/index.d.ts +4 -0
  7. package/dist/routes/index.d.ts.map +1 -0
  8. package/dist/routes/index.js +106 -0
  9. package/dist/routes/index.js.map +1 -0
  10. package/dist/server.d.ts +3 -0
  11. package/dist/server.d.ts.map +1 -0
  12. package/dist/server.js +318 -0
  13. package/dist/server.js.map +1 -0
  14. package/dist/serverCmd.d.ts +8 -0
  15. package/dist/serverCmd.d.ts.map +1 -0
  16. package/dist/serverCmd.js +44 -0
  17. package/dist/serverCmd.js.map +1 -0
  18. package/dist/shell/install.d.ts +5 -0
  19. package/dist/shell/install.d.ts.map +1 -0
  20. package/dist/shell/install.js +221 -0
  21. package/dist/shell/install.js.map +1 -0
  22. package/dist/shell/postinst.d.ts +2 -0
  23. package/dist/shell/postinst.d.ts.map +1 -0
  24. package/dist/shell/postinst.js +4 -0
  25. package/dist/shell/postinst.js.map +1 -0
  26. package/dist/shell.d.ts +2 -0
  27. package/dist/shell.d.ts.map +1 -0
  28. package/dist/shell.js +249 -0
  29. package/dist/shell.js.map +1 -0
  30. package/package.json +82 -0
  31. package/public/captures1//346/265/213/350/257/225/347/224/250captures +0 -0
  32. package/public/chat/offer.html +796 -0
  33. package/public/chat/options.html +58 -0
  34. package/public/chat/server-info.html +32 -0
  35. package/public/chat2.html +272 -0
  36. package/public/chat3.html +246 -0
  37. package/public/chat4.html +302 -0
  38. package/public/chat5.html +41 -0
  39. package/public/formdata.html +41 -0
  40. package/public/hls-player.htm +41 -0
  41. package/public/img/back.svg +1 -0
  42. package/public/img/offline.svg +1 -0
  43. package/public/img/online.svg +1 -0
  44. package/public/ip-record.html +28 -0
  45. package/public/js/encrypt.js +36 -0
  46. package/public/js/socket.io.min.js +7 -0
  47. package/public/js/socket.io.min.js.map +1 -0
  48. package/public/myhost/hostReg.html +35 -0
  49. package/public/mylog-chat.htm +260 -0
  50. package/public/mylog3.html +245 -0
  51. package/public/navbar.css +17 -0
  52. package/public/readme.txt +3 -0
  53. package/public/scroll.htm +139 -0
  54. package/public/ssh-client.html +0 -0
  55. package/public/upload-file.html +226 -0
  56. package/public/upload.html +23 -0
  57. package/public/uploads1/files-1757866537383-447469495.jpg +0 -0
  58. package/public/uploads1/files-1757867389485-764531720.jpg +0 -0
  59. package/public/uploads1/files-1757867518311-278635302.jpg +0 -0
  60. package/public/uploads1/files-1757867629687-688924576.jpg +0 -0
  61. package/public/uploads1/files-1757868630683-52261917.jpg +0 -0
  62. package/public/uploads1/files-1757869187061-619427683.jpg +0 -0
  63. package/public/uploads1/small_files-1757869187061-619427683.jpg +0 -0
  64. package/public/uploads1//346/265/213/350/257/225/347/224/250upload +0 -0
  65. package/public/utils.html +57 -0
  66. package/public/utils.js +161 -0
  67. package/public/webrtc/rtc-client.html +238 -0
  68. package/public/webrtc/rtc-file-transfer-client.html +238 -0
  69. package/public/webrtc/rtc-file-transfer-server.html +453 -0
  70. package/public/webrtc/rtc-server.html +453 -0
  71. package/public/webrtc/video-client-input.html +264 -0
  72. package/public/webrtc/video-server-input.html +312 -0
  73. package/public/webrtc/webrtc-chat-with-files.html +581 -0
  74. package/public/webrtc/webrtc-chat.html +367 -0
  75. package/public/webrtc/webrtc-file-offer.html +88 -0
  76. package/public/webrtc/webrtc1.html +186 -0
  77. package/readme.txt +71 -0
  78. package/views/chat.ejs +53 -0
  79. package/views/index.ejs +125 -0
  80. package/views/special-message.ejs +8 -0
  81. package/views/ssh.ejs +142 -0
  82. package/views/utils.ejs +0 -0
  83. package/views/webrtc/client.ejs +203 -0
  84. package/views/webrtc/server.ejs +365 -0
@@ -0,0 +1,260 @@
1
+ <!--
2
+ 登陆模式,长期设定本定storage,选择一个主机名
3
+ 数据结构:
4
+ 显示在线情况
5
+ -->
6
+ <title>聊天</title>
7
+ <script src="https://cdn.tailwindcss.com"></script>
8
+ <script src="/js/socket.io.min.js"></script>
9
+ <script src="/js/encrypt.js"></script>
10
+ <script src="/utils.js"></script>
11
+ <style>
12
+ body {
13
+ font-family: Arial, sans-serif;
14
+ }
15
+
16
+ #messages {
17
+ list-style-type: none;
18
+ height: 80%;
19
+ overflow-y: auto;
20
+ border: 2px solid #e2e8f0;
21
+ border-radius: 6px;
22
+ transition: border-color 0.3s ease;
23
+ }
24
+
25
+ #messages1 li {
26
+ padding: 8px;
27
+ margin-bottom: 2px;
28
+ background-color: #f3f3f3;
29
+ }
30
+
31
+ #form {
32
+ background: #000;
33
+ padding: 3px;
34
+ position: fixed;
35
+ bottom: 0;
36
+ width: 100%;
37
+ }
38
+
39
+ #input {
40
+ border: none;
41
+ padding: 10px;
42
+ width: 90%;
43
+ margin-right: .5%;
44
+ }
45
+
46
+ #send {
47
+ width: 9%;
48
+ background: rgb(130, 224, 170);
49
+ border: none;
50
+ padding: 10px;
51
+ }
52
+
53
+ .row {
54
+ display: flex;
55
+ flex-direction: row;
56
+ }
57
+
58
+ div .created{
59
+ padding-left: 10px;
60
+ }
61
+
62
+ div .from{
63
+ padding-left: 10px;
64
+ }
65
+ </style>
66
+ <link rel="stylesheet" href="navbar.css">
67
+ <div>
68
+ <div class="row">
69
+ <a href="/"><img id="icon" src="/img/back.svg"></img></a>
70
+ <img id="status" />
71
+ <select onchange="setHostname()"></select>
72
+ </div>
73
+ <ul id="messages" class="scrollable-list p-3 space-y-2"></ul>
74
+ <form id="form" action="">
75
+ <input id="input" autocomplete="off" /><button id="send">Send</button>
76
+ </form>
77
+ </div>
78
+ <script>
79
+
80
+ function setHostname() {
81
+ //console.log(document.querySelector('select').value)
82
+ localStorage.setItem('hostname', document.querySelector('select').value)
83
+ }
84
+
85
+ function getHostname() {
86
+ return localStorage.getItem('hostname')
87
+ }
88
+ /*
89
+ function fillselects() {
90
+ return new Promise((resolve, reject) => {
91
+ fetch('/hosts/dns').then(r => r.json()).then(r => {
92
+ if (r.retVal) {
93
+ const option = document.createElement('option')
94
+ option.value = "unknown"
95
+ option.text = "unknown"
96
+ document.querySelector('select').appendChild(option)
97
+ r.data.forEach(e => {
98
+ const option = document.createElement('option')
99
+ option.value = e.hostname
100
+ option.text = e.hostname
101
+ document.querySelector('select').appendChild(option)
102
+ })
103
+ resolve(null)
104
+ } else {
105
+ reject(r.msg)
106
+ }
107
+ })
108
+ })
109
+ }
110
+ */
111
+
112
+ let firstId = -1
113
+ window.onload = function () {
114
+
115
+ const socket = io();
116
+
117
+ const form = document.getElementById('form');
118
+ const input = document.getElementById('input');
119
+ const messages = document.getElementById('messages');
120
+ const status = document.getElementById("status")
121
+
122
+ form.addEventListener('submit', function (e) {
123
+ e.preventDefault();
124
+ //const user = document.querySelector('select').value;
125
+ if (input.value) {
126
+ socket.emit('mylog message', { method:"set", hostname: document.querySelector('select').value, message: input.value });
127
+ input.value = '';
128
+ }
129
+ });
130
+
131
+ /*
132
+ "'l;llljd", 发送异常了
133
+ */
134
+ socket.on('mylog message', function (msg) {
135
+ //console.log(msg);
136
+ //return
137
+ if( msg.method == "set" ){
138
+ msg.result.forEach( ( row )=>{
139
+ const item = document.createElement('li');
140
+ item.className = ('p-3 bg-blue-50 rounded-md border border-blue-100');
141
+ item.innerHTML = buildItem( row );
142
+ messages.appendChild(item);
143
+ }) //window.scrollTo(0, document.body.scrollHeight);
144
+ messages.scrollTop = messages.scrollHeight;
145
+ }else if( msg.method == "get" ){
146
+
147
+ msg.result.forEach( ( row )=>{
148
+ const item = document.createElement('li');
149
+ item.className = ('p-3 bg-blue-50 rounded-md border border-blue-100');
150
+ item.innerHTML = buildItem( row );
151
+ messages.insertBefore(item, document.querySelector("li"));
152
+ })
153
+ if( firstId==-1 )
154
+ messages.scrollTop = messages.scrollHeight + 50;
155
+ else
156
+ messages.scrollTop = 150;
157
+ if( msg.result.length ){
158
+ // 有bug, 可能会回转到3,4等,反复获取几条
159
+ firstId = msg.result[msg.result.length-1].id
160
+ }
161
+ }
162
+ });
163
+
164
+ socket.on('connect', function () {
165
+ //socket.emit('add user', username);
166
+ //document.getElementById("status").src = "/img/online.svg"
167
+ status.classList.remove('offline')
168
+ status.classList.add('online');
169
+ fillselects().then(() => {
170
+ if (localStorage.getItem('hostname')) {
171
+ document.querySelector('select').value = localStorage.getItem('hostname')
172
+ }
173
+ loadprev( socket )
174
+ })
175
+ });
176
+
177
+ socket.on('disconnect', function () {
178
+ //log('you have been disconnected');
179
+ //document.getElementById("status").src = "/img/offline.svg"
180
+ status.classList.remove('online')
181
+ status.classList.add('offline');
182
+ });
183
+
184
+ checkScrollPosition()
185
+
186
+ // 监听滚动事件
187
+ messages.addEventListener('wheel', function(event) {
188
+ // 判断滚动方向
189
+ const isScrollingDown = event.deltaY > 0;
190
+ //scrollDirection.textContent = isScrollingDown ? '向下滚动' : '向上滚动';
191
+ //scrollDirection.className = `font-medium ${isScrollingDown ? 'text-green-600' : 'text-red-600'}`;
192
+
193
+ // 检查是否滚动到顶部或底部(使用setTimeout确保在滚动完成后检测)
194
+ setTimeout(checkScrollPosition, 10);
195
+ });
196
+
197
+ // 也监听scroll事件,确保在所有滚动方式下都能检测(如拖动滚动条)
198
+ messages.addEventListener('scroll', function() {
199
+ checkScrollPosition();
200
+ });
201
+
202
+ function buildItem( row ){
203
+ return `<div class="item" id="${row.id}">
204
+ <div class="row">
205
+ <div class="time">${row.created}</div>
206
+ <div class="from">${row.from}</div>
207
+ </div>
208
+ <div class="message">${row.message}</div>
209
+ </div>`
210
+ }
211
+
212
+ function loadprev( ){
213
+ const hostname = document.querySelector('select').value
214
+ if( hostname!=="unknow"){
215
+ socket.emit('chat message', { method:"get", hostname: hostname, numWant: 10, id:firstId });
216
+ }
217
+ }
218
+
219
+ // 检查滚动位置的函数
220
+ // "messages.scrollTop为什么总是0"
221
+ function checkScrollPosition() {
222
+
223
+ // 清除之前的状态类
224
+ messages.classList.remove('scrolled-top', 'scrolled-bottom');
225
+
226
+ // 滚动元素的属性
227
+ const scrollTop = messages.scrollTop; // 已滚动的顶部距离
228
+ const scrollHeight = messages.scrollHeight; // 内容总高度
229
+ const clientHeight = messages.clientHeight; // 可见区域高度
230
+ const isAtTop = scrollTop <= 1; // 允许1px误差
231
+ const isAtBottom = (scrollTop + clientHeight) >= (scrollHeight - 1); // 允许1px误差
232
+
233
+ // 更新状态显示
234
+
235
+ if (isAtTop && isAtBottom) {
236
+ console.log("内容不足以滚动的情况")
237
+ //scrollStatus.textContent = '内容未超出可视区域';
238
+ //scrollStatus.className = 'font-medium text-purple-600';
239
+ //hideEdgeIndicator();
240
+ } else if (isAtTop) {
241
+ console.log('已滚动到顶部');
242
+ //scrollStatus.className = 'font-medium text-blue-600';
243
+ //messages.classList.add('scrolled-top');
244
+ //showEdgeIndicator('已滚动到顶部', 'blue-100', 'blue-600');
245
+ loadprev();
246
+ } else if (isAtBottom) {
247
+ console.log('已滚动到底部');
248
+ //scrollStatus.className = 'font-medium text-green-600';
249
+ //messages.classList.add('scrolled-bottom');
250
+ //showEdgeIndicator('已滚动到底部', 'green-100', 'green-600');
251
+ } else {
252
+ console.log('正常位置');
253
+ //scrollStatus.className = 'font-medium text-gray-800';
254
+ //hideEdgeIndicator();
255
+ }
256
+ }
257
+ }
258
+
259
+
260
+ </script>
@@ -0,0 +1,245 @@
1
+ <title>
2
+ mylog3匹配
3
+ </title>
4
+ <!--script src="socket.io/client-dist/socket.io.min.js"></script-->
5
+ <!--
6
+ "安装了socket.io, 如何在页面里引用node_modules/socket.io/client-dist/socket.io.min.js这个文件"
7
+ HTML 在 public 文件夹,node_modules 在根目录
8
+ -->
9
+ <!--script src="../node_modules/socket.io/client-dist/socket.io.min.js"></script-->
10
+ <!--script src="https://cdn.tailwindcss.com"></script-->
11
+ <script src="/js/socket.io.min.js"></script>
12
+ <script src="/js/encrypt.js"></script>
13
+ <script src="/utils.js"></script>
14
+ <style>
15
+ #mylog {
16
+ width: 50%;
17
+ height: 50%;
18
+ }
19
+
20
+ .board {
21
+ border: 1px solid black;
22
+ margin: 5px;
23
+ padding: 5px;
24
+ padding-left: 10px;
25
+ padding-right: 10px;
26
+ border-radius: 3px;
27
+ width: fit-content;
28
+ }
29
+
30
+ .platform-radio-group {
31
+ display: flex;
32
+ gap: 20px;
33
+ flex-wrap: wrap;
34
+ }
35
+
36
+ .platform-option {
37
+ display: flex;
38
+ flex-direction: column;
39
+ gap: 4px;
40
+ cursor: pointer;
41
+ padding: 10px 15px;
42
+ border: 1px solid #ddd;
43
+ border-radius: 6px;
44
+ min-width: 200px;
45
+ transition: all 0.2s ease;
46
+ }
47
+
48
+ .platform-option:has(input:checked) {
49
+ border-color: #42b983;
50
+ background-color: rgba(66, 185, 131, 0.1);
51
+ }
52
+
53
+ .platform-option:hover {
54
+ border-color: #42b983;
55
+ }
56
+
57
+ .platform-name {
58
+ font-weight: 500;
59
+ color: #333;
60
+ }
61
+
62
+ .platform-desc {
63
+ font-size: 12px;
64
+ color: #666;
65
+ }
66
+
67
+ .platform-option input {
68
+ align-self: flex-start;
69
+ margin-top: 2px;
70
+ }
71
+
72
+ #send {
73
+ width: 100px;
74
+ height: 50px;
75
+ }
76
+ </style>
77
+ <link rel="stylesheet" href="navbar.css">
78
+ <body>
79
+ <div>
80
+ <div class="board" style="display: flex;">
81
+ <div>
82
+ <a href="/"><img id="icon" class="back imgbutton"></img></a>
83
+ </div>
84
+ <div>
85
+ <img id="status" class="offline imgbutton" />
86
+ </div>
87
+ <div>
88
+ <h1>测试http/ws两种模式下的mylog的上传</h1>
89
+ </div>
90
+ <div>
91
+ <label>from</label>
92
+ <select id="from"></select>
93
+ </div>
94
+ <div>
95
+ <label>加密字符</label>
96
+ <input id="encKey" />
97
+ </div>
98
+ </div>
99
+ <div>
100
+ <div class="platform-radio-group">
101
+ <label class="platform-option">
102
+ <input type="radio" name="uploadPlatform" value="http" checked>
103
+ <span class="platform-name">HTTP</span>
104
+ <span class="platform-desc">使用HTTP传输</span>
105
+ </label>
106
+ <label class="platform-option">
107
+ <input type="radio" name="uploadPlatform" value="ws">
108
+ <span class="platform-name">Websocket</span>
109
+ <span class="platform-desc">使用Websocket传输</span>
110
+ </label>
111
+ </div>
112
+ </div>
113
+ </div>
114
+ <div>
115
+ <div>
116
+ <textarea id="mylog" placeholder="write something..."></textarea>
117
+ </div>
118
+ <div>
119
+ <button id="send">Send</button>
120
+ </div>
121
+ </div>
122
+ </body>
123
+ <script>
124
+ let socket;
125
+ const status = document.getElementById("status")
126
+ window.onload = function () {
127
+ socket = io();
128
+ socket.on('connect', function () {
129
+ status.classList.remove('offline')
130
+ status.classList.add('online');
131
+ console.log('connect');
132
+ fillselects(false).then(() => {
133
+ })
134
+ })
135
+ socket.on('mylog message', function (r) {
136
+ console.log(r);
137
+ if (r[r.length-1].retVal) {
138
+
139
+ }
140
+ })
141
+ socket.on('disconnect', function (reason) {
142
+ status.classList.remove('online')
143
+ status.classList.add('offline');
144
+ console.log(reason);
145
+ });
146
+ }
147
+
148
+ document.querySelector("#send").onclick = function () {
149
+ /*
150
+ document.getElementsByName("uploadPlatform")
151
+ console.log(document.getElementsByName("uploadPlatform")[0].value)
152
+ console.log(document.getElementsByName("uploadPlatform")[1].value)
153
+ */
154
+ const selectedRadio = document.querySelector('input[name="uploadPlatform"]:checked');
155
+
156
+ if (selectedRadio) {
157
+ // 获取选中的值(注意:你的HTML中缺少value属性,需要添加)
158
+ const selectedValue = selectedRadio.value;
159
+ console.log("选中的传输方式是:", selectedValue);
160
+
161
+ // 也可以通过相邻元素获取显示的名称
162
+ const platformName = selectedRadio.nextElementSibling.textContent;
163
+ console.log("选中的平台名称:", platformName);
164
+
165
+ } else {
166
+ console.log("没有选中任何传输方式");
167
+ }
168
+ let mylog = document.getElementById('mylog')
169
+ if( mylog.value=="" ){
170
+ alert("请输入要保存的日志")
171
+ return
172
+ }
173
+ if( selectedRadio.value =="http" ){
174
+ post_http()
175
+ }else if( selectedRadio.value =="ws" ){
176
+ post_ws()
177
+ }
178
+ }
179
+
180
+ function post_ws() {
181
+ let mylog = document.getElementById('mylog')
182
+ if( !prepost_mylog() || !mylog.value ){
183
+ alert("Please input something")
184
+ return
185
+ }
186
+ const value = encryptWithKeyword(mylog.value, getEncKey())
187
+ socket.emit('mylog message',
188
+ { command: "set",
189
+ from: getFromValue(),
190
+ content: value, source: 2, editor: 2 });
191
+ }
192
+
193
+ function post_http() {
194
+ let mylog = document.getElementById('mylog')
195
+ if( !prepost_mylog() || !mylog.value ){
196
+ alert("Please input something")
197
+ return
198
+ }
199
+ const fd = new FormData
200
+ fd.append("command", "set")
201
+ fd.append("from", getFromValue())
202
+ const value = encryptWithKeyword(mylog.value, getEncKey())
203
+ fd.append("content", value)
204
+ fd.append("source", 2)
205
+ fd.append("editor", 2)
206
+ fetch("/mylog/data", { method: "post", body: fd })
207
+ .then(r => r.json()).then((r) => {
208
+ if (r[r.length-1].retVal) {
209
+
210
+ } else {
211
+ console.log(r[r.length-1].msg)
212
+ }
213
+ })
214
+ }
215
+ /*
216
+ render mark-down
217
+ */
218
+ function markdown() {
219
+ let mylog = document.getElementById('mylog')
220
+ const fd = new FormData
221
+ fd.append("log", mylog.value)
222
+ fetch("/mylog/markdown", { method: "post", body: fd })
223
+ .then(r => r.json()).then((r) => {
224
+ if (r.retVal) {
225
+
226
+ } else {
227
+
228
+ }
229
+ })
230
+ }
231
+
232
+ function article() {
233
+ let mylog = document.getElementById('mylog')
234
+ const fd = new FormData
235
+ fd.append("log", mylog.value)
236
+ fetch("/mylog/article", { method: "post", body: fd })
237
+ .then(r => r.json()).then((r) => {
238
+ if (r.retVal) {
239
+
240
+ } else {
241
+
242
+ }
243
+ })
244
+ }
245
+ </script>
@@ -0,0 +1,17 @@
1
+ .imgbutton{
2
+ width: 48px;
3
+ height: 48px;
4
+ }
5
+ .back{
6
+ background-image: url("data:image/svg+xml;base64,PHN2ZyB0PSIxNzUzNjkwMjk2Njk4IiBjbGFzcz0iaWNvbiIgdmlld0JveD0iMCAwIDEwMjQgMTAyNCIgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHAtaWQ9IjIzNTkiIHdpZHRoPSI0OCIgaGVpZ2h0PSI0OCI+PHBhdGggZD0iTTUxNS41ODIxNjIgMTAyMy45OTQzNzFBNTE2LjM0MzExNiA1MTYuMzQzMTE2IDAgMCAxIDIwNC45NTc1MTMgOTIxLjY0Njg3NWE1MDIuMDE0NDY3IDUwMi4wMTQ0NjcgMCAwIDEtMTEzLjYwNTcyLTEyMi44MTY5OTVBNDg2LjY2MjM0MiA0ODYuNjYyMzQyIDAgMCAxIDIwLjczMjAyIDY0Mi4yMzgyMTJhNTExLjczNzQ3OSA1MTEuNzM3NDc5IDAgMCAxIDk5MC43MjM3NTktMjU5Ljk2MjYzOSA0MC45Mzg5OTggNDAuOTM4OTk4IDAgMCAxLTMuNTgyMTYyIDI5LjE2OTAzNiAzNi4zMzMzNjEgMzYuMzMzMzYxIDAgMCAxLTIzLjUzOTkyNCAxNy4zOTkwNzQgMzYuODQ1MDk4IDM2Ljg0NTA5OCAwIDAgMS0yOS4xNjkwMzYtMy41ODIxNjIgMzguODkyMDQ4IDM4Ljg5MjA0OCAwIDAgMS0xOC40MjI1NS0yMy41Mzk5MjQgNDM2LjAwMDMzMiA0MzYuMDAwMzMyIDAgMCAwLTQyMC4xMzY0Ny0zMjQuOTUzMjk5IDQ0Ni4yMzUwODEgNDQ2LjIzNTA4MSAwIDAgMC0xMTEuMDQ3MDMzIDE0LjMyODY0OSA0MzQuOTc2ODU3IDQzNC45NzY4NTcgMCAxIDAgNTM4Ljg1OTU2NSA0OTcuNDA4ODMgMzcuODY4NTczIDM3Ljg2ODU3MyAwIDAgMSAzNy4zNTY4MzYtMzIuMjM5NDYyaDYuNjUyNTg4YTM5LjkxNTUyMyAzOS45MTU1MjMgMCAwIDEgMjUuMDc1MTM2IDE1Ljg2Mzg2MiAzOC4zODAzMTEgMzguMzgwMzExIDAgMCAxIDYuMTQwODUgMjguNjU3Mjk5IDUxMS43Mzc0NzkgNTExLjczNzQ3OSAwIDAgMS0zNzQuNTkxODM1IDQwNS4yOTYwODMgNDYwLjU2MzczMSA0NjAuNTYzNzMxIDAgMCAxLTEyOS40Njk1ODIgMTcuOTEwODEyeiIgZmlsbD0iIzAwRTJGRiIgcC1pZD0iMjM2MCI+PC9wYXRoPjxwYXRoIGQ9Ik01MTIgNzc1LjgwMTY5NGEzNS44MjE2MjQgMzUuODIxNjI0IDAgMCAxLTI3LjEyMjA4Ni0xMS43Njk5NjJsLTIyNS4xNjQ0OTEtMjI0LjY1Mjc1M2EzOC44OTIwNDggMzguODkyMDQ4IDAgMCAxIDAtNTQuMjQ0MTczbDIyNS4xNjQ0OTEtMjI0LjY1Mjc1M2EzOS45MTU1MjMgMzkuOTE1NTIzIDAgMCAxIDI3LjEyMjA4Ni0xMS43Njk5NjIgMzcuODY4NTczIDM3Ljg2ODU3MyAwIDAgMSAyNy4xMjIwODYgMTEuNzY5OTYyIDM5LjkxNTUyMyAzOS45MTU1MjMgMCAwIDEgMTEuNzY5OTYyIDI3LjEyMjA4NiAzNS44MjE2MjQgMzUuODIxNjI0IDAgMCAxLTExLjc2OTk2MiAyNy4xMjIwODZsLTE1OC42Mzg2MTggMTU4LjYzODYxOWgzNTguMjE2MjM1YTM4Ljg5MjA0OCAzOC44OTIwNDggMCAxIDEgMCA3Ny4yNzIzNTloLTM1OC4yMTYyMzVsMTU5LjE1MDM1NiAxNTkuMTUwMzU2YTM4Ljg5MjA0OCAzOC44OTIwNDggMCAwIDEgMTEuNzY5OTYyIDI3LjEyMjA4NiAzNy44Njg1NzMgMzcuODY4NTczIDAgMCAxLTExLjc2OTk2MiAyNy4xMjIwODcgMzYuODQ1MDk4IDM2Ljg0NTA5OCAwIDAgMS0yNy42MzM4MjQgMTEuNzY5OTYyeiIgZmlsbD0iIzAwRTJGRiIgcC1pZD0iMjM2MSI+PC9wYXRoPjwvc3ZnPg==");
7
+ }
8
+ .online{
9
+ background-image: url("data:image/svg+xml;base64,PHN2ZyB0PSIxNzUzNTkxMDgxMTQ3IiBjbGFzcz0iaWNvbiIgdmlld0JveD0iMCAwIDEwMjQgMTAyNCIgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHAtaWQ9IjEyNzgiIHdpZHRoPSI0OCIgaGVpZ2h0PSI0OCI+PHBhdGggZD0iTTkzNi4zNDU2IDQzLjA0ODk2SDg3LjY5NTM2Yy00NC42NDY0IDAtODAuODU1MDQgMzguMDEwODgtODAuODU1MDQgODQuODI4MTZ2NTMyLjAyOTQ0YzAgNDYuODk5MiAzNi4yMDg2NCA4NC44NjkxMiA4MC44NTUwNCA4NC44NjkxMmgyODguMDMwNzJ2OTcuOTc2MzJIMzE4LjY2ODh2NjQuMjY2MjRoLTczLjcyOHY4Mi43ODAxNmg1MzQuMTE4NHYtODIuNzgwMTZoLTczLjY0NjA4di02NC4yNjYyNGgtNTcuMDk4MjR2LTk3Ljk3NjMyaDI4OC4wMzA3MmM0NC42NDY0IDAgODAuODU1MDQtMzguMDEwODggODAuODU1MDQtODQuODY5MTJWMTI3Ljg3NzEyYzAtNDYuODE3MjgtMzYuMjA4NjQtODQuODI4MTYtODAuODU1MDQtODQuODI4MTZ6IG0tNy41MzY2NCA2MTUuODMzNkg5NS4yMzJWMTI4Ljk0MjA4aDgzMy41NzY5NnY1MjkuOTQwNDh6IiBmaWxsPSIjMkIzQjk1IiBwLWlkPSIxMjc5Ij48L3BhdGg+PHBhdGggZD0iTTcxNS4zNjY0IDI2OC4wODMybC01NC4zOTQ4OC01NC4zOTQ4OC0xOTguMTY0NDggMTk4LjE2NDQ4TDM2My4xMTA0IDMxMi4xMTUyIDMwOC43MTU1MiAzNjYuNTEwMDhsMTU0LjkxMDcyIDE1NC45MTA3MiA1NC4zNTM5Mi01NC4zNTM5Mi0wLjgxOTItMC44NjAxNnoiIGZpbGw9IiMyQjNCOTUiIHAtaWQ9IjEyODAiPjwvcGF0aD48L3N2Zz4=");
10
+ }
11
+ .offline{
12
+ background-image: url("data:image/svg+xml;base64,PHN2ZyB0PSIxNzUzNTkxMDQ0MzM2IiBjbGFzcz0iaWNvbiIgdmlld0JveD0iMCAwIDEwMjQgMTAyNCIgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHAtaWQ9IjExMTUiIHdpZHRoPSI0OCIgaGVpZ2h0PSI0OCI+PHBhdGggZD0iTTkzMi45MDQ5NiA2OC4xMTY0OEg4NC4yNTQ3MmMtNDQuNjQ2NCAwLTgwLjg1NTA0IDM4LjAxMDg4LTgwLjg1NTA0IDg0LjgyODE2djUzMi4wMjk0NGMwIDQ2Ljg5OTIgMzYuMjA4NjQgODQuODY5MTIgODAuODU1MDQgODQuODY5MTJoMjg4LjAzMDcydjk3Ljk3NjMySDMxNS4yMjgxNnY2NC4yNjYyNGgtNzMuNzI4djgyLjc4MDE2aDUzNC4wNzc0NHYtODIuNzgwMTZoLTczLjY0NjA4di02NC4yNjYyNGgtNTcuMDk4MjR2LTk3Ljk3NjMyaDI4OC4wMzA3MmM0NC42NDY0IDAgODAuODU1MDQtMzguMDEwODggODAuODU1MDQtODQuODY5MTJWMTUyLjk0NDY0YzAuMDQwOTYtNDYuODE3MjgtMzYuMTY3NjgtODQuODI4MTYtODAuODE0MDgtODQuODI4MTZ6IG0tNy41MzY2NCA2MTUuODMzNkg5MS43OTEzNlYxNTQuMDUwNTZoODMzLjU3Njk2djUyOS44OTk1MnoiIGZpbGw9IiNmZjAwMDAiIHAtaWQ9IjExMTYiPjwvcGF0aD48cGF0aCBkPSJNNjcwLjgwMTkyIDI4Mi41ODMwNGwtNTMuNjE2NjQtNTQuMzEyOTYtMTA4LjU4NDk2IDExMC4wNTk1Mi0xMDguNjI1OTItMTEwLjA1OTUyLTUzLjYxNjY0IDU0LjMxMjk2IDEwOC42MjU5MiAxMTAuMDU5NTItMTA4LjYyNTkyIDExMC4xMDA0OEwzOTkuOTc0NCA1NTcuMDU2bDEwOC42MjU5Mi0xMTAuMDU5NTIgMTA4LjU4NDk2IDExMC4wNTk1MiA1My42MTY2NC01NC4zMTI5Ni0xMDguNjI1OTItMTEwLjEwMDQ4eiIgZmlsbD0iI2ZmMDAwMCIgcC1pZD0iMTExNyI+PC9wYXRoPjwvc3ZnPg==");
13
+ }
14
+ .options{
15
+ background-image: url('data:image/svg+xml;base64,PHN2ZyB0PSIxNzUzOTQxMjIyMDAyIiBjbGFzcz0iaWNvbiIgdmlld0JveD0iMCAwIDEwMjQgMTAyNCIgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHAtaWQ9Ijk2MzMiIHdpZHRoPSI0OCIgaGVpZ2h0PSI0OCI+PHBhdGggZD0iTTI0My43MTIgNTQxLjU5NFY4Ni40MjZjMC0xNi45NDgtMTMuNzczLTMwLjcyLTMwLjcyLTMwLjcycy0zMC43MiAxMy43NzItMzAuNzIgMzAuNzJ2NDU1LjIxOUMxMTcuODYyIDU1NS43NzYgNjkuNTMgNjEzLjIyMiA2OS41MyA2ODEuNzc5czQ4LjMzMiAxMjYuMDAzIDExMi43NDIgMTQwLjEzNXYxMDcuNzI0YzAgMTYuOTQ4IDEzLjc3MyAzMC43MiAzMC43MiAzMC43MnMzMC43Mi0xMy43NzIgMzAuNzItMzAuNzJWODIxLjkxNGM2NC40MS0xNC4xMzIgMTEyLjc0Mi03MS41NzggMTEyLjc0Mi0xNDAuMTM1IDAtNjguNjA4LTQ4LjM4NC0xMjYuMDU0LTExMi43NDItMTQwLjE4NXogbS0zMC43MiAyMjIuMTU2Yy00NS4yMSAwLTgyLjAyMi0zNi44MTItODIuMDIyLTgyLjAyMnMzNi44MTItODIuMDIyIDgyLjAyMi04Mi4wMjIgODIuMDIyIDM2LjgxMiA4Mi4wMjIgODIuMDIyLTM2LjgxMiA4Mi4wMjItODIuMDIyIDgyLjAyMnogbTcyOC4wMTMtODIuMDIyYzAtNjguNTU3LTQ4LjMzMy0xMjYuMDAzLTExMi43NDMtMTQwLjEzNFY4Ni40MjZjMC0xNi45NDgtMTMuNzcyLTMwLjcyLTMwLjcyLTMwLjcycy0zMC43MiAxMy43NzItMzAuNzIgMzAuNzJ2NDU1LjIxOWMtNjQuNDEgMTQuMTMxLTExMi43NDIgNzEuNTc3LTExMi43NDIgMTQwLjEzNHM0OC4zMzMgMTI2LjAwMyAxMTIuNzQyIDE0MC4xMzV2MTA3LjcyNGMwIDE2Ljk0OCAxMy43NzMgMzAuNzIgMzAuNzIgMzAuNzJzMzAuNzItMTMuNzcyIDMwLjcyLTMwLjcyVjgyMS45MTRjNjQuNDEtMTQuMTgzIDExMi43NDMtNzEuNjMgMTEyLjc0My0xNDAuMTg2ek03OTcuNTQyIDc2My43NWMtNDUuMjEgMC04Mi4wMjItMzYuODEyLTgyLjAyMi04Mi4wMjJzMzYuODEzLTgyLjAyMiA4Mi4wMjItODIuMDIyIDgyLjAyMyAzNi44MTIgODIuMDIzIDgyLjAyMi0zNi44MTMgODIuMDIyLTgyLjAyMyA4Mi4wMjJ6TTUwOC44MjYgNDI4LjIzN2MtMTYuOTQ4IDAtMzAuNzIgMTMuNzczLTMwLjcyIDMwLjcydjQ3MC42M2MwIDE2Ljk0NyAxMy43NzIgMzAuNzIgMzAuNzIgMzAuNzJzMzAuNzItMTMuNzczIDMwLjcyLTMwLjcydi00NzAuNjNjMC0xNi45NDctMTMuNzIyLTMwLjcyLTMwLjcyLTMwLjcyek01MDguODI2IDIzNi43NDljLTE2Ljk0OCAwLTMwLjcyLTEzLjc3My0zMC43Mi0zMC43MlY4Ni40MjZjMC0xNi45NDggMTMuNzcyLTMwLjcyIDMwLjcyLTMwLjcyczMwLjcyIDEzLjc3MiAzMC43MiAzMC43MnYxMTkuNjAzYzAgMTYuOTQ3LTEzLjcyMiAzMC43Mi0zMC43MiAzMC43MnoiIGZpbGw9IiM0MjQyNDIiIHAtaWQ9Ijk2MzQiPjwvcGF0aD48cGF0aCBkPSJNNTA4LjgyNiA0NzcuNjk2Yy03OS4xMDQgMC0xNDMuNDYzLTY0LjM1OC0xNDMuNDYzLTE0My40NjJTNDI5LjcyMiAxOTAuNzcgNTA4LjgyNiAxOTAuNzdzMTQzLjQ2MiA2NC4zNTkgMTQzLjQ2MiAxNDMuNDYzLTY0LjM1OCAxNDMuNDYyLTE0My40NjIgMTQzLjQ2MnogbTAtMjI1LjQ4NWMtNDUuMjEgMC04Mi4wMjMgMzYuODEzLTgyLjAyMyA4Mi4wMjNzMzYuODEzIDgyLjAyMiA4Mi4wMjMgODIuMDIyIDgyLjAyMi0zNi44MTMgODIuMDIyLTgyLjAyMi0zNi43NjItODIuMDIzLTgyLjAyMi04Mi4wMjN6IiBmaWxsPSIjRjhCNjJEIiBwLWlkPSI5NjM1Ij48L3BhdGg+PC9zdmc+');
16
+ background-size: contain;
17
+ }
@@ -0,0 +1,3 @@
1
+
2
+ 可以开发整个站点的,如vue项目,然后放置到此目录
3
+ 实现page.html与main.ts,静态动态分离
@@ -0,0 +1,139 @@
1
+ <!DOCTYPE html>
2
+ <html lang="zh-CN">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>UL滚动到顶/底检测</title>
7
+ <script src="https://cdn.tailwindcss.com"></script>
8
+ <link href="https://cdn.jsdelivr.net/npm/font-awesome@4.7.0/css/font-awesome.min.css" rel="stylesheet">
9
+ <style>
10
+ .scrollable-list {
11
+ height: 220px;
12
+ overflow-y: auto;
13
+ border: 2px solid #e2e8f0;
14
+ border-radius: 6px;
15
+ transition: border-color 0.3s ease;
16
+ }
17
+ .scrollable-list.scrolled-top {
18
+ border-color: #3b82f6;
19
+ }
20
+ .scrollable-list.scrolled-bottom {
21
+ border-color: #10b981;
22
+ }
23
+ .status-indicator {
24
+ transition: all 0.3s ease;
25
+ }
26
+ </style>
27
+ </head>
28
+ <body class="p-8 bg-gray-50 min-h-screen">
29
+ <div class="max-w-md mx-auto bg-white p-6 rounded-xl shadow-lg">
30
+ <h3 class="text-xl font-bold mb-4 text-gray-800 flex items-center">
31
+ <i class="fa fa-list-ul mr-2 text-blue-500"></i>滚动检测列表
32
+ </h3>
33
+
34
+ <ul id="scrollList" class="scrollable-list p-3 space-y-2">
35
+ <li class="p-3 bg-blue-50 rounded-md border border-blue-100">列表项 1</li>
36
+ <li class="p-3 bg-blue-50 rounded-md border border-blue-100">列表项 2</li>
37
+ <li class="p-3 bg-blue-50 rounded-md border border-blue-100">列表项 3</li>
38
+ <li class="p-3 bg-blue-50 rounded-md border border-blue-100">列表项 4</li>
39
+ <li class="p-3 bg-blue-50 rounded-md border border-blue-100">列表项 5</li>
40
+ <li class="p-3 bg-blue-50 rounded-md border border-blue-100">列表项 6</li>
41
+ <li class="p-3 bg-blue-50 rounded-md border border-blue-100">列表项 7</li>
42
+ <li class="p-3 bg-blue-50 rounded-md border border-blue-100">列表项 8</li>
43
+ <li class="p-3 bg-blue-50 rounded-md border border-blue-100">列表项 9</li>
44
+ <li class="p-3 bg-blue-50 rounded-md border border-blue-100">列表项 10</li>
45
+ <li class="p-3 bg-blue-50 rounded-md border border-blue-100">列表项 11</li>
46
+ <li class="p-3 bg-blue-50 rounded-md border border-blue-100">列表项 12</li>
47
+ </ul>
48
+
49
+ <div class="mt-4 p-4 bg-gray-50 rounded-lg">
50
+ <div class="flex justify-between items-center mb-2">
51
+ <span class="text-gray-600">滚动方向:</span>
52
+ <span id="scrollDirection" class="font-medium text-gray-800">未滚动</span>
53
+ </div>
54
+ <div class="flex justify-between items-center">
55
+ <span class="text-gray-600">滚动状态:</span>
56
+ <span id="scrollStatus" class="font-medium text-gray-800">正常位置</span>
57
+ </div>
58
+ <div id="edgeIndicator" class="mt-3 text-center status-indicator py-2 rounded hidden">
59
+ <i class="fa fa-info-circle mr-1"></i>
60
+ <span id="edgeText"></span>
61
+ </div>
62
+ </div>
63
+ </div>
64
+
65
+ <script>
66
+ // 获取元素引用
67
+ const scrollList = document.getElementById('scrollList');
68
+ const scrollDirection = document.getElementById('scrollDirection');
69
+ const scrollStatus = document.getElementById('scrollStatus');
70
+ const edgeIndicator = document.getElementById('edgeIndicator');
71
+ const edgeText = document.getElementById('edgeText');
72
+
73
+ // 监听滚动事件
74
+ scrollList.addEventListener('wheel', function(event) {
75
+ // 判断滚动方向
76
+ const isScrollingDown = event.deltaY > 0;
77
+ scrollDirection.textContent = isScrollingDown ? '向下滚动' : '向上滚动';
78
+ scrollDirection.className = `font-medium ${isScrollingDown ? 'text-green-600' : 'text-red-600'}`;
79
+
80
+ // 检查是否滚动到顶部或底部(使用setTimeout确保在滚动完成后检测)
81
+ setTimeout(checkScrollPosition, 10);
82
+ });
83
+
84
+ // 也监听scroll事件,确保在所有滚动方式下都能检测(如拖动滚动条)
85
+ scrollList.addEventListener('scroll', function() {
86
+ checkScrollPosition();
87
+ });
88
+
89
+ // 检查滚动位置的函数
90
+ function checkScrollPosition() {
91
+ // 清除之前的状态类
92
+ scrollList.classList.remove('scrolled-top', 'scrolled-bottom');
93
+
94
+ // 滚动元素的属性
95
+ const scrollTop = scrollList.scrollTop; // 已滚动的顶部距离
96
+ const scrollHeight = scrollList.scrollHeight; // 内容总高度
97
+ const clientHeight = scrollList.clientHeight; // 可见区域高度
98
+ const isAtTop = scrollTop <= 1; // 允许1px误差
99
+ const isAtBottom = (scrollTop + clientHeight) >= (scrollHeight - 1); // 允许1px误差
100
+
101
+ // 更新状态显示
102
+ if (isAtTop && isAtBottom) {
103
+ // 内容不足以滚动的情况
104
+ scrollStatus.textContent = '内容未超出可视区域';
105
+ scrollStatus.className = 'font-medium text-purple-600';
106
+ hideEdgeIndicator();
107
+ } else if (isAtTop) {
108
+ scrollStatus.textContent = '已滚动到顶部';
109
+ scrollStatus.className = 'font-medium text-blue-600';
110
+ scrollList.classList.add('scrolled-top');
111
+ showEdgeIndicator('已滚动到顶部', 'blue-100', 'blue-600');
112
+ } else if (isAtBottom) {
113
+ scrollStatus.textContent = '已滚动到底部';
114
+ scrollStatus.className = 'font-medium text-green-600';
115
+ scrollList.classList.add('scrolled-bottom');
116
+ showEdgeIndicator('已滚动到底部', 'green-100', 'green-600');
117
+ } else {
118
+ scrollStatus.textContent = '正常位置';
119
+ scrollStatus.className = 'font-medium text-gray-800';
120
+ hideEdgeIndicator();
121
+ }
122
+ }
123
+
124
+ // 显示边缘提示
125
+ function showEdgeIndicator(text, bgColor, textColor) {
126
+ edgeText.textContent = text;
127
+ edgeIndicator.className = `mt-3 text-center status-indicator py-2 rounded bg-${bgColor} text-${textColor}`;
128
+ }
129
+
130
+ // 隐藏边缘提示
131
+ function hideEdgeIndicator() {
132
+ edgeIndicator.className = 'mt-3 text-center status-indicator py-2 rounded hidden';
133
+ }
134
+
135
+ // 初始检查一次位置
136
+ checkScrollPosition();
137
+ </script>
138
+ </body>
139
+ </html>
Binary file