-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathsearch.xml
282 lines (135 loc) · 467 KB
/
search.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
<?xml version="1.0" encoding="utf-8"?>
<search>
<entry>
<title>Keep主题页面定制记录</title>
<link href="/2024/10/28/2024-10-28-keep-zhu-ti-ye-mian-ding-zhi-ji-lu/"/>
<url>/2024/10/28/2024-10-28-keep-zhu-ti-ye-mian-ding-zhi-ji-lu/</url>
<content type="html"><![CDATA[<p>几个月前改配置没记录,现在忘了只好研究补记。</p><span id="more"></span><h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><ul><li><a class="link" href="https://keep-docs.xpoet.cn/advanced/custom-style.html#%E4%BD%BF%E7%94%A8%E6%B5%81%E7%A8%8B">Hexo定制样式使用流程<i class="fas fa-external-link-alt"></i></a></li></ul><h2 id="主题颜色"><a href="#主题颜色" class="headerlink" title="主题颜色"></a>主题颜色</h2><ul><li>更好的深色</li></ul><figure class="highlight css"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="selector-class">.dark-mode</span> {</span><br><span class="line"> --<span class="attribute">background-color</span>-<span class="number">1</span>: <span class="number">#222222</span>;</span><br><span class="line"> --<span class="attribute">background-color</span>-<span class="number">2</span>: <span class="number">#222222</span>;</span><br><span class="line"> --<span class="attribute">background-color</span>-<span class="number">3</span>: <span class="number">#222222</span>;</span><br><span class="line"> --<span class="attribute">content</span>-<span class="attribute">background-color</span>: <span class="number">#222222</span>;</span><br><span class="line"> --scrollbar-<span class="attribute">color</span>: <span class="number">#badc58</span>;</span><br><span class="line"> --scrollbar-<span class="attribute">background-color</span>: <span class="number">#222222</span>;</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><h2 id="工具页面"><a href="#工具页面" class="headerlink" title="工具页面"></a>工具页面</h2><ul><li>工具页面将布局从表格修改为列表,以至于能够展示足够长的描述。</li></ul><figure class="highlight css"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="selector-class">.page-template-container</span> <span class="selector-class">.tools-nav-box</span> <span class="selector-class">.tool-item-list</span> {</span><br><span class="line"> <span class="attribute">display</span>: table-column;</span><br><span class="line"> grid-gap: <span class="number">1.2rem</span>;</span><br><span class="line"> grid-template-<span class="attribute">columns</span>: <span class="built_in">repeat</span>(<span class="number">4</span>, <span class="number">1</span>fr);</span><br><span class="line"> <span class="attribute">width</span>: <span class="number">100%</span>;</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><ul><li>工具页面左侧标题列表拥有更长的显示宽度</li></ul><figure class="highlight css"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="selector-class">.page-template-container</span> <span class="selector-class">.tools-nav-box</span> <span class="selector-class">.tools-nav-list</span> {</span><br><span class="line"> <span class="attribute">width</span>: <span class="number">20rem</span>;</span><br><span class="line"> <span class="attribute">margin-right</span>: <span class="number">0.5rem</span>;</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><h2 id="文章页面"><a href="#文章页面" class="headerlink" title="文章页面"></a>文章页面</h2><ul><li>左侧标题列表拥有更长的显示宽度</li></ul><figure class="highlight css"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="selector-class">.post-toc-wrap</span> {</span><br><span class="line"> <span class="attribute">width</span>: <span class="number">20rem</span>;</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure>]]></content>
<categories>
<category> Hexo </category>
</categories>
<tags>
<tag> theme </tag>
</tags>
</entry>
<entry>
<title>北境之地资源加成模组</title>
<link href="/2024/05/14/2024-05-14-bei-jing-zhi-di-zi-yuan-jia-cheng-mo-zu/"/>
<url>/2024/05/14/2024-05-14-bei-jing-zhi-di-zi-yuan-jia-cheng-mo-zu/</url>
<content type="html"><![CDATA[<p>记录一次<a class="link" href="https://northgard.net/">Northgard(北境之地)<i class="fas fa-external-link-alt"></i></a>模组编写的过程</p><span id="more"></span><h1 id="游戏介绍"><a href="#游戏介绍" class="headerlink" title="游戏介绍"></a>游戏介绍</h1><blockquote><p>AFTER YEARS OF TIRELESS EXPLORATIONS…</p><p>Brave Vikings have discovered a new land filled with mystery, danger and riches: Northgard.</p><p>The boldest Northmen have set sail to explore and conquer these new shores, bring fame to their Clan and write history through conquest, trading, or devotion to the Gods.</p></blockquote><p>经过多年不懈的探索……</p><p>勇敢的维京人发现了一片充满神秘、危险和财富的新大陆:北加德。</p><p>最大胆的北方人已经起航去探索和征服这些新的海岸,通过征服、贸易或对神的忠诚来为他们的氏族带来名声,并写下历史。</p><h1 id="准备工作"><a href="#准备工作" class="headerlink" title="准备工作"></a>准备工作</h1><p>确保你已经阅读完了<a class="link" href="https://northgard.net/map-editor/how-to-mod/">Northgard官方帮助文档<i class="fas fa-external-link-alt"></i></a>,并且你已经知道了如何创建一个mod。确保我们的步骤一致你需要检查一下步骤:</p><ol><li>在Northgard的根目录下创建一个<code>Northgard\mods</code>目录</li><li>打开Northgard——创意——创建地图</li><li>地图大小:巨大</li><li>玩家人数:8</li><li>难度:普通</li><li>游戏类型:随机</li><li>点击创建地图</li><li>然后保存地图并设置一个名称,比如<code>cheatRes</code></li><li>创意——启动北加尔编辑器——加载刚才创建的模组目录</li><li>从游戏目录中复制<code>Northgard\NGEditor\res\script.hx</code>到<code>cheatRes</code>目录下</li><li>打开北加尔编辑器——Database——Diff——Create</li><li>创建一个模组预览图片比如x512、x1024的正方形大小,命名为<code>preview.jpg</code></li></ol><p>你的模组目录<code>Northgard\mods\cheatRes</code>下应该有以下四个文件:</p><table><thead><tr><th>cdb.diff</th><th>修改游戏规则的数据库文件</th></tr></thead><tbody><tr><td>map.dat</td><td>在游戏中创建的自定义地图</td></tr><tr><td>preview.jpg</td><td>上传创意工坊的模组预览图</td></tr><tr><td>script.hx</td><td>修改游戏逻辑的</td></tr></tbody></table><h1 id="目标"><a href="#目标" class="headerlink" title="目标"></a>目标</h1><p>每隔一段时间自动增加资源,比如:木材、食物、石材、铁锭等</p><h1 id="script-hx"><a href="#script-hx" class="headerlink" title="script.hx"></a>script.hx</h1><p>首先阅读一遍示例代码,可以发现主要用的只有三个方法:<code>saveState()</code>、<code>init()</code>、<code>regularUpdate(dt : Float)</code>,其他不需要的方法都可以删掉了</p><ul><li><code>saveState()</code>:用于保存游戏的,保持默认就好</li><li><code>init()</code>:模组执行逻辑的主入口且只执行一次</li><li><code>regularUpdate(dt : Float)</code>:每0.5秒就会执行一次</li></ul><p>从这三个方法中可以发现主要代码写在<code>regularUpdate</code>即可,通读官方提供的<a class="link" href="https://northgard.net/doc/api/current/">API文档<i class="fas fa-external-link-alt"></i></a>可以发现有一个<code>Player</code>表示玩家点进去后发现有一个类似添加资源的函数<code>addResource(kind:ResourceKind, amount:Float, produced:Bool = true):Void</code>,这里第一个参数应该是指要添加的资源类型,但遗憾的是文档中并没有提及有哪些资源类型,第二个是指资源的数量,第三个可选。</p><p>那就只能参考别人的模组看看怎么写,打开创意工坊订阅最热门的模组。下载完成后打开<code>SteamLibrary\steamapps\workshop\content</code>目录下<strong>修改日期</strong>排序中最新的目录,只需要关注<code>script.hx</code>文件,在代码里搜索关键字<code>addResource</code>发现有一行类似的<code>me().addResource(Resource.Fame, 100);</code>,看到这里让我突然想起官方文档中对于玩家的修改使用的就是<code>me()</code>这样就不用判断是否是AI还是玩家了。</p><p>尝试编写模组:</p><figure class="highlight hx"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 保存游戏(保持默认)</span></span><br><span class="line">savedInt = <span class="number">0</span>; </span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">saveState</span></span>() {</span><br><span class="line">state.scriptProps = {</span><br><span class="line">savedInt : <span class="type">savedInt</span>,</span><br><span class="line">};</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 开局执行一次</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">init</span></span>() {</span><br><span class="line"><span class="keyword">if</span> (state.time == <span class="number">0</span>){</span><br><span class="line"><span class="comment">// 声望</span></span><br><span class="line">me().addResource(Resource.Fame, <span class="number">100</span>);</span><br><span class="line">}</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>打开游戏选择单人游戏,添加模组,选择本地模组,进入游戏。</p><p>可以看到开局增加了100声望,同时增加了货币、食物、木材、知识。从这一点可以发现<code>Resource.Fame</code>是指声望。</p><p>继续查找其他资源类型,又看到一个资源类型<code>Resource.Money</code>,进入游戏发现增加的是货币。然后再次返回别人的代码中查找,遗憾的是没有发现其他资源类型。</p><p>到这里可以大胆的推测一下游戏中的其他资源类型,比如:</p><figure class="highlight hx"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 石头</span></span><br><span class="line">Resource.Stone</span><br><span class="line"><span class="comment">// 铁矿</span></span><br><span class="line">Resource.Iron</span><br><span class="line"><span class="comment">// 木头</span></span><br><span class="line">Resource.Wood</span><br><span class="line"><span class="comment">// 食物</span></span><br><span class="line">Resource.Food</span><br></pre></td></tr></tbody></table></figure><p>然后代入参数类型再次尝试运行模组,令人高兴的是推测是正确的。</p><p>然后添加所有可能的资源类型,然后保存。</p><figure class="highlight hx"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 保存游戏(保持默认)</span></span><br><span class="line">savedInt = <span class="number">0</span>; </span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">saveState</span></span>() {</span><br><span class="line">state.scriptProps = {</span><br><span class="line">savedInt : <span class="type">savedInt</span>,</span><br><span class="line">};</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 开局执行一次</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">init</span></span>() {</span><br><span class="line"><span class="keyword">if</span> (state.time == <span class="number">0</span>){</span><br><span class="line"><span class="comment">// 声望</span></span><br><span class="line">me().addResource(Resource.Fame, <span class="number">100</span>);</span><br><span class="line">}</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 0.5秒执行一次</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">regularUpdate</span></span>(dt : <span class="type">Float</span>) {</span><br><span class="line"><span class="comment">// 金钱</span></span><br><span class="line">me().addResource(Resource.Money,<span class="number">1</span>);</span><br><span class="line"><span class="comment">// 石头</span></span><br><span class="line">me().addResource(Resource.Stone,<span class="number">1</span>);</span><br><span class="line"><span class="comment">// 铁矿</span></span><br><span class="line">me().addResource(Resource.Iron,<span class="number">1</span>);</span><br><span class="line"><span class="comment">// 木头</span></span><br><span class="line">me().addResource(Resource.Wood,<span class="number">1</span>);</span><br><span class="line"><span class="comment">// 食物</span></span><br><span class="line">me().addResource(Resource.Food,<span class="number">1</span>);</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>到这里一个模组就编写完成了!</p><h1 id="上传模组"><a href="#上传模组" class="headerlink" title="上传模组"></a>上传模组</h1><p>确保你的模组目录中拥有一个名为<code>preview</code>的图片文件,这将用来在模组浏览页面展示你的模组。</p><p>然后打开游戏——创意——上传模组,上传成功后打开创意工坊中你发布的文件,完善模组信息比如标题和描述以及需要展示的图片或者视频。</p><blockquote><p>注意:添加的模组标题和描述必须是符合当前语言的,你可以添加:日语、简体中文、英语,其他国家的人会看到不同的语言</p></blockquote><h1 id="订阅我"><a href="#订阅我" class="headerlink" title="订阅我"></a>订阅我</h1><p><a class="link" href="https://steamcommunity.com/sharedfiles/filedetails/?id=3245924858">萌新资源大礼包(cheatRes)<i class="fas fa-external-link-alt"></i></a></p>]]></content>
<categories>
<category> Mod </category>
</categories>
<tags>
<tag> Northgard </tag>
</tags>
</entry>
<entry>
<title>Hexo环境配置</title>
<link href="/2024/02/25/2024-02-25-hexo-huan-jing-pei-zhi/"/>
<url>/2024/02/25/2024-02-25-hexo-huan-jing-pei-zhi/</url>
<content type="html"><![CDATA[<p>电脑有时会出现一些奇怪的问题要重装系统,导致很多环境要重新配置。Hexo是环境配置中最复杂的每次都要在百度查很久,为此今天重新配置环境就顺便写一篇文章来记录。</p><span id="more"></span><h2 id="0x1-暂停QSync双向同步"><a href="#0x1-暂停QSync双向同步" class="headerlink" title="0x1 暂停QSync双向同步"></a>0x1 暂停QSync双向同步</h2><p>重新初始化Hexo会覆盖整个目录,所以<strong>得先提前把博客备份出来</strong>。然后删除本地的Hexo目录里的文件夹和NAS端的,然后再打开QSync等待同步完成。</p><p><strong>注意:只保留一个空的Hexo目录</strong></p><figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">> dir Hexo</span><br><span class="line">0 个文件 0 字节</span><br></pre></td></tr></tbody></table></figure><h2 id="0x2-安装Node-js"><a href="#0x2-安装Node-js" class="headerlink" title="0x2 安装Node.js"></a>0x2 安装Node.js</h2><ol><li><p>删除之前安装的Node.js安装目录,否则会安装失败</p></li><li><p><a class="link" href="https://nodejs.org/en">安装推荐的LTS版本<i class="fas fa-external-link-alt"></i></a>,LTS是长期支持的稳定版本</p></li><li><p>检查安装是否成功,显示版本号就算没问题了</p><figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">> node -v</span><br><span class="line">v20.11.1</span><br></pre></td></tr></tbody></table></figure></li></ol><h2 id="0x3-初始化blog"><a href="#0x3-初始化blog" class="headerlink" title="0x3 初始化blog"></a>0x3 初始化blog</h2><ol><li><p>打开Clash的TUN模式,在打开系统{代}{理},TUN表示{代}{理}所有应用程序。</p></li><li><p>在Hexo目录下打开终端,依次执行</p><figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">> npm install hexo-cli -g</span><br><span class="line">> hexo init blog</span><br><span class="line">> <span class="built_in">cd</span> blog</span><br><span class="line">> npm install</span><br><span class="line">> hexo server</span><br></pre></td></tr></tbody></table></figure></li><li><p>如果一切顺利会显示一个本地服务器地址<code>http://localhost:4000/</code>,复制在浏览器里打开能够看到一个Hello World默认文章,就表示已经成功了。</p></li></ol><h2 id="0x4-运行自己的blog"><a href="#0x4-运行自己的blog" class="headerlink" title="0x4 运行自己的blog"></a>0x4 运行自己的blog</h2><ol><li>把刚才备份博客目录解压缩在Hexo目录下</li><li>在自己blog目录下打开终端执行<code>hexo s</code></li><li>如果能够显示<code>http://localhost:4000</code>,并且能够正常在浏览器中打开就算本地配置完成了</li></ol><h2 id="0x5-Gitee-SSH配置"><a href="#0x5-Gitee-SSH配置" class="headerlink" title="0x5 Gitee SSH配置"></a>0x5 Gitee SSH配置</h2><ol><li><p>设置Gitee账户和用户名</p><figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">> git config --global user.email <span class="string">"you@example.com"</span></span><br><span class="line">> git config --global user.name <span class="string">"Your Name"</span></span><br></pre></td></tr></tbody></table></figure></li><li><p>生成SSH密钥</p><figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">> ssh-keygen -t rsa -C <span class="string">"Your Name"</span></span><br></pre></td></tr></tbody></table></figure></li><li><p>SSH密钥保存位置使用默认路径,不使用密码。只需要连按三次回车即可</p><figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">> ssh-keygen -t rsa -C <span class="string">"Your Name"</span></span><br><span class="line">Enter file <span class="keyword">in</span> <span class="built_in">which</span> to save the key (C:\Users\user/.ssh/id_rsa):</span><br><span class="line">Enter passphrase (empty <span class="keyword">for</span> no passphrase):</span><br><span class="line">Enter same passphrase again:</span><br></pre></td></tr></tbody></table></figure></li><li><p>查看密钥,密钥保存位置在上一步命令执行结果中有</p><figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">> notepad C:\Users\user/.ssh/id_rsa.pub</span><br></pre></td></tr></tbody></table></figure></li><li><p>打开你的Gitee设置页面,在安全设置下选择SSH公钥,把刚才打开的密钥文件内容全部复制进来,然后加上标题,点击确定就完成配置了。<strong>需要删除之前的公钥,注意不是打开blog的仓库设置,是你的账户设置</strong></p></li><li><p>检查配置是否成功</p><figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">> ssh -T git@gitee.com</span><br><span class="line"><span class="comment"># 在"?"后面输入yes,如果配置正确会显示你的Gitee账户名称</span></span><br><span class="line">Are you sure you want to <span class="built_in">continue</span> connecting (yes/no/[fingerprint])?yes</span><br></pre></td></tr></tbody></table></figure></li></ol><h2 id="0x6-发布Hexo到Gitee"><a href="#0x6-发布Hexo到Gitee" class="headerlink" title="0x6 发布Hexo到Gitee"></a>0x6 发布Hexo到Gitee</h2><ol><li><p>在自己的blog目录下执行,删除之前生成的文件</p><figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">> hexo clean</span><br><span class="line">INFO Validating config</span><br><span class="line">INFO Deleted database.</span><br><span class="line">INFO Deleted public folder.</span><br></pre></td></tr></tbody></table></figure></li><li><p> 重新生成文件</p></li></ol> <figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">> hexo g</span><br><span class="line">INFO Validating config</span><br><span class="line">INFO Start processing</span><br><span class="line">...</span><br><span class="line">INFO 108 files generated <span class="keyword">in</span> 877 ms</span><br></pre></td></tr></tbody></table></figure><ol start="3"><li> 运行本地服务器,检查网站没问题就按Ctrl+C停止运行本地服务器</li></ol> <figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">> hexo s</span><br><span class="line">INFO Validating config</span><br><span class="line">INFO Start processing</span><br><span class="line">INFO Hexo is running at http://localhost:4000 . Press Ctrl+C to stop.</span><br></pre></td></tr></tbody></table></figure><ol start="4"><li><p>同步到Gitee仓库</p><figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">> hexo d</span><br></pre></td></tr></tbody></table></figure></li></ol><h2 id="0x7-Gitee部署Hexo"><a href="#0x7-Gitee部署Hexo" class="headerlink" title="0x7 Gitee部署Hexo"></a>0x7 Gitee部署Hexo</h2><ol><li>打开Gitee同步blog的仓库</li><li>点击右上角的<strong>管理</strong>,确保仓库处于<strong>开源</strong>状态,然后点击保存。有时候会因为没有签协议而被改为私有</li><li>返回到仓库页面,点击<strong>服务</strong>中的<strong>Gitee Pages</strong>,点击<strong>更新</strong></li><li>等几分钟再查看blog</li></ol><p>至此,一切都已经配置完成。</p>]]></content>
<categories>
<category> 环境配置 </category>
</categories>
<tags>
<tag> Hexo </tag>
</tags>
</entry>
<entry>
<title>基于Node.js跨平台局域网消息传输与QNAP部署</title>
<link href="/2023/08/28/2023-08-28-node-js-kua-ping-tai-ju-yu-wang-xiao-xi-chuan-shu-yu-qnap-bu-shu-shou-ce/"/>
<url>/2023/08/28/2023-08-28-node-js-kua-ping-tai-ju-yu-wang-xiao-xi-chuan-shu-yu-qnap-bu-shu-shou-ce/</url>
<content type="html"><![CDATA[<h2 id="0x1-引言"><a href="#0x1-引言" class="headerlink" title="0x1 引言"></a>0x1 引言</h2><h3 id="0x1-1-目的与背景"><a href="#0x1-1-目的与背景" class="headerlink" title="0x1.1 目的与背景"></a>0x1.1 目的与背景</h3><p><strong>当我刚进入大学时</strong>,我得到了我的第一台游戏本。当时,我经常需要在手机和电脑之间传输文件和文档。最初,我使用QQ和手机热点来进行传输,这显得相当繁琐。后来,我尝试了蓝牙传输,但速度过慢。尽管我使用了小米的热点文件传输功能,但仅为了传输简单的文本或图片,这样的操作还是过于复杂。</p><p><strong>毕业后</strong>,我的生活出现了很大的改变。拥有了路由器后,内网传输变得相对简单。但当我获得了iPhone和MacBook,我发现跨操作系统传输文件并不简单。例如,从iPhone向Windows传输文本或链接时,我还需依赖QQ等软件。尝试使用Windows的SMB共享服务和MacBook的文件共享都有其局限性和问题。</p><p><strong>后来</strong>,我转向开源的云盘解决方案,但传输速度的限制使我非常不满意。直到我获得了QNAP,大部分的文件传输问题得到了解决。然而,在局域网下保持Windows和MacBook数据的一致性,尤其是文本数据,依然是一个问题。显然,依赖QQ这样的方式效率低下。</p><p><strong>因此</strong>,创建一个简单的网页成为了一种必要,让任何设备仅通过输入一个地址就能发送消息,避免了下载和设置各种软件的麻烦。</p><h3 id="0x1-2-目标受众"><a href="#0x1-2-目标受众" class="headerlink" title="0x1.2 目标受众"></a>0x1.2 目标受众</h3><ol><li><p><strong>大学生和职场新人</strong>:他们经常在不同的设备之间传输文件和文档,尤其是在手机和电脑之间,需要一个高效的解决方案。</p></li><li><p><strong>跨操作系统用户</strong>:特别是那些使用iPhone和Windows或MacBook的人,他们经常面临操作系统之间的传输难题。</p></li><li><p><strong>网络技术爱好者</strong>:对于那些寻求局域网下文件和数据传输解决方案的人,他们可能已经尝试了各种方法,但仍然在寻找更加高效的方法。</p></li><li><p><strong>对效率有追求的人</strong>:不想依赖于多个应用程序和软件,而是寻求一个集中的、简化的解决方案来处理日常的文件和数据传输任务。</p></li><li><p><strong>云存储和网络存储用户</strong>:特别是那些使用开源云盘或QNAP这样的解决方案的人,他们在寻找更快、更直接的传输方法。</p></li></ol><h2 id="0x2-必要的理论知识解释"><a href="#0x2-必要的理论知识解释" class="headerlink" title="0x2 必要的理论知识解释"></a>0x2 必要的理论知识解释</h2><h3 id="0x2-1-基础概念"><a href="#0x2-1-基础概念" class="headerlink" title="0x2.1 基础概念"></a>0x2.1 基础概念</h3><ol><li><p><strong>文件传输</strong>:</p><ul><li><strong>定义</strong>:文件传输指的是从一个设备、系统或者位置将文件或数据移动到另一个设备、系统或位置的过程。</li><li><strong>应用</strong>:例如,通过USB、网络或云服务将文档从电脑传输到手机。</li></ul></li><li><p><strong>操作系统差异</strong>:</p><ul><li><strong>定义</strong>:不同的操作系统(如Windows、MacOS、Linux等)在架构、功能和界面上有所不同。</li><li><strong>应用</strong>:某些软件可能只在特定操作系统上运行,或在不同系统上有不同的表现。</li></ul></li><li><p><strong>局域网(LAN)原理</strong>:</p><ul><li><strong>定义</strong>:局域网是一个限定在较小地理范围内(如家庭、办公室)的计算机网络。</li><li><strong>应用</strong>:局域网使得同一位置的多台设备可以共享资源,如打印机或文件。</li></ul></li><li><p><strong>蓝牙和热点传输</strong>:</p><ul><li><strong>定义</strong>:蓝牙是一种无线技术标准,用于短距离数据交换,而热点传输则是通过Wi-Fi将文件从一个设备分享到另一个设备。</li><li><strong>应用</strong>:例如,用蓝牙耳机听音乐或使用手机热点给电脑上网。</li></ul></li><li><p><strong>云存储与网络存储</strong>:</p><ul><li><strong>定义</strong>:云存储是通过互联网存储数据在远程服务器上的服务;网络存储则是在局域网内提供集中化的数据存储。</li><li><strong>应用</strong>:如,使用Google Drive或Dropbox来存储文件或使用NAS来在家庭网络中共享文件。</li></ul></li><li><p><strong>跨平台兼容性</strong>:</p><ul><li><strong>定义</strong>:指的是软件或应用在多个操作系统或设备上的运行能力。</li><li><strong>应用</strong>:例如,一个应用既可以在Android上运行,也可以在iOS上运行。</li></ul></li><li><p><strong>Web技术基础</strong>:</p><ul><li><strong>定义</strong>:关于如何构建和呈现互联网内容的技术和原理。</li><li><strong>应用</strong>:例如,使用HTML、CSS和JavaScript来构建网页。</li></ul></li><li><p><strong>Node.js</strong>:</p><ul><li><strong>定义</strong>:是一个允许在服务器上运行JavaScript的运行时环境。</li><li><strong>应用</strong>:例如,构建后端API或Web应用。</li></ul></li><li><p><strong>HTML</strong>:</p><ul><li><strong>定义</strong>:是用来描述网页结构的标记语言。</li><li><strong>应用</strong>:例如,定义网页中的标题、段落和链接。</li></ul></li><li><p><strong>CSS</strong>:</p></li></ol><ul><li><strong>定义</strong>:是用于描述网页外观和格式的语言。</li><li><strong>应用</strong>:例如,设置字体、颜色和布局。</li></ul><ol start="11"><li><strong>Socket</strong>:</li></ol><ul><li><strong>定义</strong>:是计算机之间进行通信的端点。</li><li><strong>应用</strong>:例如,实时聊天应用。</li></ul><ol start="12"><li><strong>Server</strong>:</li></ol><ul><li><strong>定义</strong>:是为其他计算机或应用提供资源、服务或数据的系统或应用。</li><li><strong>应用</strong>:例如,Web服务器存储和提供网页内容。</li></ul><ol start="13"><li><strong>Docker</strong>:</li></ol><ul><li><strong>定义</strong>:是一个平台,用于创建、运行和管理容器化的应用。</li><li><strong>应用</strong>:例如,为应用提供一致的运行环境。</li></ul><ol start="14"><li><strong>IP地址</strong>:</li></ol><ul><li><strong>定义</strong>:是分配给联网设备的数字地址,用于识别和定位。</li><li><strong>应用</strong>:例如,访问特定的网站或远程服务器。</li></ul><ol start="15"><li><strong>User Agent</strong>:</li></ol><ul><li><strong>定义</strong>:是描述浏览器或其他客户端如何与服务器通信的字符串。</li><li><strong>应用</strong>:例如,网站可以识别用户的设备和浏览器,并据此提供优化的内容。</li></ul><h3 id="0x2-2-相关技术背景"><a href="#0x2-2-相关技术背景" class="headerlink" title="0x2.2 相关技术背景"></a>0x2.2 相关技术背景</h3><p>随着技术的日益发展,文件传输和数据共享已经成为现代工作和生活中不可或缺的部分。无论是学生、企业家还是日常用户,我们都经常面临跨设备、跨操作系统的数据传输挑战。</p><ol><li><p><strong>文件传输</strong>:这是一种基本的需求,涉及将文件从一个设备传送到另一个设备。尽管有许多可用的方法,例如USB、蓝牙和热点传输,但它们都有自己的局限性和速度问题。</p></li><li><p><strong>操作系统差异</strong>:由于Windows、MacOS和其他操作系统的内在差异,许多应用和服务在跨操作系统通信时面临挑战。</p></li><li><p><strong>局域网原理</strong>:局域网为设备提供了一种在有限的地理范围内共享资源的方式。例如,家庭或办公室内的设备可以通过局域网共享打印机或文件。</p></li><li><p><strong>蓝牙与热点传输</strong>:尽管这两种技术都提供了一种无线传输方式,但它们通常受限于传输距离和速度。</p></li><li><p><strong>云存储与网络存储</strong>:为了解决大文件传输的问题,许多用户转向了云存储或网络存储解决方案,如Google Drive或NAS。</p></li><li><p><strong>跨平台兼容性</strong>:这是当今技术世界中的一大挑战。由于多样性的增加,需要开发可以在多个平台上运行的应用。</p></li><li><p><strong>Web技术基础</strong>:HTML、CSS和JavaScript等技术为创建跨平台解决方案提供了基础,允许用户通过浏览器访问应用和服务。</p></li><li><p><strong>服务器和后端技术</strong>:Node.js提供了一个强大的平台,用于构建后端服务和API,而Socket技术使实时通信成为可能。这些技术经常被用于创建动态、实时的Web应用。</p></li><li><p><strong>容器化技术</strong>:Docker为开发者提供了一种方式,将其应用及其所有依赖性封装在一个容器中,确保其在任何环境中的一致性和可移植性。</p></li><li><p><strong>网络识别</strong>:IP地址和User Agent允许设备和网络服务识别请求的来源,从而为用户提供个性化的体验和内容。</p></li></ol><h2 id="0x3-Node-js原型开发"><a href="#0x3-Node-js原型开发" class="headerlink" title="0x3 Node.js原型开发"></a>0x3 Node.js原型开发</h2><h3 id="0x3-1-环境配置"><a href="#0x3-1-环境配置" class="headerlink" title="0x3.1 环境配置"></a>0x3.1 环境配置</h3><p>初始化项目</p><figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">mkdir realtime-text</span><br><span class="line"><span class="built_in">cd</span> realtime-text</span><br></pre></td></tr></tbody></table></figure><p>安装<a class="link" href="https://nodejs.org/en">Node.js<i class="fas fa-external-link-alt"></i></a>,最好选择LTS版本,在项目根目录执行</p><figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">npm init -y</span><br><span class="line">npm install express socket.io</span><br><span class="line">npm install multer</span><br></pre></td></tr></tbody></table></figure><h3 id="0x3-2-基本功能实现"><a href="#0x3-2-基本功能实现" class="headerlink" title="0x3.2 基本功能实现"></a>0x3.2 基本功能实现</h3><p><strong>创建服务器 (server.js)</strong></p><figure class="highlight javascript"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> express = <span class="built_in">require</span>(<span class="string">'express'</span>);</span><br><span class="line"><span class="keyword">const</span> http = <span class="built_in">require</span>(<span class="string">'http'</span>);</span><br><span class="line"><span class="keyword">const</span> socketIo = <span class="built_in">require</span>(<span class="string">'socket.io'</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> app = express();</span><br><span class="line"><span class="keyword">const</span> server = http.createServer(app);</span><br><span class="line"><span class="keyword">const</span> io = socketIo(server);</span><br><span class="line"></span><br><span class="line">app.use(<span class="string">'/socket.io'</span>, express.static(<span class="string">'node_modules/socket.io/client-dist/'</span>));</span><br><span class="line"></span><br><span class="line">app.get(<span class="string">'/'</span>, <span class="function">(<span class="params">req, res</span>) =></span> {</span><br><span class="line"> res.sendFile(__dirname + <span class="string">'/index.html'</span>);</span><br><span class="line">});</span><br><span class="line"></span><br><span class="line">io.on(<span class="string">'connection'</span>, <span class="function">(<span class="params">socket</span>) =></span> {</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'a user connected'</span>);</span><br><span class="line"></span><br><span class="line"> socket.on(<span class="string">'send text'</span>, <span class="function">(<span class="params">text</span>) =></span> {</span><br><span class="line"> io.emit(<span class="string">'receive text'</span>, text);</span><br><span class="line"> });</span><br><span class="line"></span><br><span class="line"> socket.on(<span class="string">'disconnect'</span>, <span class="function">() =></span> {</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'user disconnected'</span>);</span><br><span class="line"> });</span><br><span class="line">});</span><br><span class="line"></span><br><span class="line">server.listen(<span class="number">3000</span>, <span class="function">() =></span> {</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'listening on *:3000'</span>);</span><br><span class="line">});</span><br></pre></td></tr></tbody></table></figure><p><strong>创建前端页面 (index.html)</strong></p><figure class="highlight html"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta"><!doctype <span class="meta-keyword">html</span>></span></span><br><span class="line"><span class="tag"><<span class="name">html</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">head</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">title</span>></span>Realtime Text<span class="tag"></<span class="name">title</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">head</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">body</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">textarea</span> <span class="attr">id</span>=<span class="string">"textArea"</span> <span class="attr">rows</span>=<span class="string">"10"</span> <span class="attr">cols</span>=<span class="string">"50"</span>></span><span class="tag"></<span class="name">textarea</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">button</span> <span class="attr">id</span>=<span class="string">"sendButton"</span>></span>Send<span class="tag"></<span class="name">button</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">hr</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">h3</span>></span>Received Text:<span class="tag"></<span class="name">h3</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">div</span> <span class="attr">id</span>=<span class="string">"receivedText"</span>></span><span class="tag"></<span class="name">div</span>></span></span><br><span class="line"></span><br><span class="line"> <span class="tag"><<span class="name">script</span> <span class="attr">src</span>=<span class="string">"/socket.io/socket.io.js"</span>></span><span class="tag"></<span class="name">script</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">script</span>></span><span class="javascript"></span></span><br><span class="line"><span class="javascript"> <span class="keyword">var</span> socket = io();</span></span><br><span class="line"><span class="javascript"></span></span><br><span class="line"><span class="javascript"> <span class="built_in">document</span>.getElementById(<span class="string">'sendButton'</span>).onclick = <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>{</span></span><br><span class="line"><span class="javascript"> <span class="keyword">var</span> text = <span class="built_in">document</span>.getElementById(<span class="string">'textArea'</span>).value;</span></span><br><span class="line"><span class="javascript"> socket.emit(<span class="string">'send text'</span>, text);</span></span><br><span class="line"><span class="javascript"> };</span></span><br><span class="line"><span class="javascript"></span></span><br><span class="line"><span class="javascript"> socket.on(<span class="string">'receive text'</span>, <span class="function"><span class="keyword">function</span>(<span class="params">text</span>) </span>{</span></span><br><span class="line"><span class="javascript"> <span class="built_in">document</span>.getElementById(<span class="string">'receivedText'</span>).innerText = text;</span></span><br><span class="line"><span class="javascript"> });</span></span><br><span class="line"><span class="javascript"> </span><span class="tag"></<span class="name">script</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">body</span>></span></span><br><span class="line"><span class="tag"></<span class="name">html</span>></span></span><br></pre></td></tr></tbody></table></figure><p><strong>启动服务器</strong></p><p>本地服务器默认地址:<a class="link" href="http://localhost:3000/">http://localhost:3000/<i class="fas fa-external-link-alt"></i></a></p><figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">node server.js</span><br></pre></td></tr></tbody></table></figure><p><strong>创建启动服务器脚本 (run.bat)</strong></p><figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">node server.js</span><br><span class="line">pause</span><br></pre></td></tr></tbody></table></figure><h2 id="0x4-按需复制,无需深究学习"><a href="#0x4-按需复制,无需深究学习" class="headerlink" title="0x4 按需复制,无需深究学习"></a>0x4 按需复制,无需深究学习</h2><p>不想学习思路直接运行代码,按照以下步骤操作,如果需要上传NAS请继续阅读 <strong>0xA Docker封装与测试</strong></p><p>创建对应的目录结构,并把<code>index.html</code>和<code>server.js</code>这两个的代码复制到你的网站根目录下</p><p>下载<a class="link" href="https://www.jetbrains.com/lp/mono/">JetBrains Mono<i class="fas fa-external-link-alt"></i></a>字体,解压<code>JetBrainsMono-Regular.ttf</code>、<code>JetBrainsMono-Regular.woff2</code>到网站根目录</p><p>现在你的目录结构应该是这样的</p><figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"> D:\realtime_text 的目录</span><br><span class="line">2023/08/27 04:51 11,332 index.html</span><br><span class="line">2023/01/14 23:20 273,900 JetBrainsMono-Regular.ttf</span><br><span class="line">2023/01/14 23:20 92,164 JetBrainsMono-Regular.woff2</span><br><span class="line">2023/08/27 01:47 <DIR> node_modules</span><br><span class="line">2023/08/27 01:12 52 run.bat</span><br><span class="line">2023/08/27 01:49 1,942 server.js</span><br><span class="line">2023/08/27 13:27 <DIR> uploads</span><br></pre></td></tr></tbody></table></figure><h3 id="0x4-1-index-html"><a href="#0x4-1-index-html" class="headerlink" title="0x4.1 index.html"></a>0x4.1 index.html</h3><figure class="highlight html"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br><span class="line">161</span><br><span class="line">162</span><br><span class="line">163</span><br><span class="line">164</span><br><span class="line">165</span><br><span class="line">166</span><br><span class="line">167</span><br><span class="line">168</span><br><span class="line">169</span><br><span class="line">170</span><br><span class="line">171</span><br><span class="line">172</span><br><span class="line">173</span><br><span class="line">174</span><br><span class="line">175</span><br><span class="line">176</span><br><span class="line">177</span><br><span class="line">178</span><br><span class="line">179</span><br><span class="line">180</span><br><span class="line">181</span><br><span class="line">182</span><br><span class="line">183</span><br><span class="line">184</span><br><span class="line">185</span><br><span class="line">186</span><br><span class="line">187</span><br><span class="line">188</span><br><span class="line">189</span><br><span class="line">190</span><br><span class="line">191</span><br><span class="line">192</span><br><span class="line">193</span><br><span class="line">194</span><br><span class="line">195</span><br><span class="line">196</span><br><span class="line">197</span><br><span class="line">198</span><br><span class="line">199</span><br><span class="line">200</span><br><span class="line">201</span><br><span class="line">202</span><br><span class="line">203</span><br><span class="line">204</span><br><span class="line">205</span><br><span class="line">206</span><br><span class="line">207</span><br><span class="line">208</span><br><span class="line">209</span><br><span class="line">210</span><br><span class="line">211</span><br><span class="line">212</span><br><span class="line">213</span><br><span class="line">214</span><br><span class="line">215</span><br><span class="line">216</span><br><span class="line">217</span><br><span class="line">218</span><br><span class="line">219</span><br><span class="line">220</span><br><span class="line">221</span><br><span class="line">222</span><br><span class="line">223</span><br><span class="line">224</span><br><span class="line">225</span><br><span class="line">226</span><br><span class="line">227</span><br><span class="line">228</span><br><span class="line">229</span><br><span class="line">230</span><br><span class="line">231</span><br><span class="line">232</span><br><span class="line">233</span><br><span class="line">234</span><br><span class="line">235</span><br><span class="line">236</span><br><span class="line">237</span><br><span class="line">238</span><br><span class="line">239</span><br><span class="line">240</span><br><span class="line">241</span><br><span class="line">242</span><br><span class="line">243</span><br><span class="line">244</span><br><span class="line">245</span><br><span class="line">246</span><br><span class="line">247</span><br><span class="line">248</span><br><span class="line">249</span><br><span class="line">250</span><br><span class="line">251</span><br><span class="line">252</span><br><span class="line">253</span><br><span class="line">254</span><br><span class="line">255</span><br><span class="line">256</span><br><span class="line">257</span><br><span class="line">258</span><br><span class="line">259</span><br><span class="line">260</span><br><span class="line">261</span><br><span class="line">262</span><br><span class="line">263</span><br><span class="line">264</span><br><span class="line">265</span><br><span class="line">266</span><br><span class="line">267</span><br><span class="line">268</span><br><span class="line">269</span><br><span class="line">270</span><br><span class="line">271</span><br><span class="line">272</span><br><span class="line">273</span><br><span class="line">274</span><br><span class="line">275</span><br><span class="line">276</span><br><span class="line">277</span><br><span class="line">278</span><br><span class="line">279</span><br><span class="line">280</span><br><span class="line">281</span><br><span class="line">282</span><br><span class="line">283</span><br><span class="line">284</span><br><span class="line">285</span><br><span class="line">286</span><br><span class="line">287</span><br><span class="line">288</span><br><span class="line">289</span><br><span class="line">290</span><br><span class="line">291</span><br><span class="line">292</span><br><span class="line">293</span><br><span class="line">294</span><br><span class="line">295</span><br><span class="line">296</span><br><span class="line">297</span><br><span class="line">298</span><br><span class="line">299</span><br><span class="line">300</span><br><span class="line">301</span><br><span class="line">302</span><br><span class="line">303</span><br><span class="line">304</span><br><span class="line">305</span><br><span class="line">306</span><br><span class="line">307</span><br><span class="line">308</span><br><span class="line">309</span><br><span class="line">310</span><br><span class="line">311</span><br><span class="line">312</span><br><span class="line">313</span><br><span class="line">314</span><br><span class="line">315</span><br><span class="line">316</span><br><span class="line">317</span><br><span class="line">318</span><br><span class="line">319</span><br><span class="line">320</span><br><span class="line">321</span><br><span class="line">322</span><br><span class="line">323</span><br><span class="line">324</span><br><span class="line">325</span><br><span class="line">326</span><br><span class="line">327</span><br><span class="line">328</span><br><span class="line">329</span><br><span class="line">330</span><br><span class="line">331</span><br><span class="line">332</span><br><span class="line">333</span><br><span class="line">334</span><br><span class="line">335</span><br><span class="line">336</span><br><span class="line">337</span><br><span class="line">338</span><br><span class="line">339</span><br><span class="line">340</span><br><span class="line">341</span><br><span class="line">342</span><br><span class="line">343</span><br><span class="line">344</span><br><span class="line">345</span><br><span class="line">346</span><br><span class="line">347</span><br><span class="line">348</span><br><span class="line">349</span><br><span class="line">350</span><br><span class="line">351</span><br><span class="line">352</span><br><span class="line">353</span><br><span class="line">354</span><br><span class="line">355</span><br><span class="line">356</span><br><span class="line">357</span><br><span class="line">358</span><br><span class="line">359</span><br><span class="line">360</span><br><span class="line">361</span><br><span class="line">362</span><br><span class="line">363</span><br><span class="line">364</span><br><span class="line">365</span><br><span class="line">366</span><br><span class="line">367</span><br><span class="line">368</span><br><span class="line">369</span><br><span class="line">370</span><br><span class="line">371</span><br><span class="line">372</span><br><span class="line">373</span><br><span class="line">374</span><br><span class="line">375</span><br><span class="line">376</span><br><span class="line">377</span><br><span class="line">378</span><br><span class="line">379</span><br><span class="line">380</span><br><span class="line">381</span><br><span class="line">382</span><br><span class="line">383</span><br><span class="line">384</span><br><span class="line">385</span><br><span class="line">386</span><br><span class="line">387</span><br><span class="line">388</span><br><span class="line">389</span><br><span class="line">390</span><br><span class="line">391</span><br><span class="line">392</span><br><span class="line">393</span><br><span class="line">394</span><br><span class="line">395</span><br><span class="line">396</span><br><span class="line">397</span><br><span class="line">398</span><br><span class="line">399</span><br><span class="line">400</span><br><span class="line">401</span><br><span class="line">402</span><br><span class="line">403</span><br><span class="line">404</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta"><!doctype <span class="meta-keyword">html</span>></span></span><br><span class="line"><span class="tag"><<span class="name">html</span>></span></span><br><span class="line"><span class="tag"><<span class="name">head</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">title</span>></span>Realtime Text<span class="tag"></<span class="name">title</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">style</span>></span><span class="css"></span></span><br><span class="line"><span class="css"> <span class="selector-attr">[data-theme=<span class="string">"dark"</span>]</span> <span class="selector-tag">input</span>, <span class="selector-attr">[data-theme=<span class="string">"dark"</span>]</span> <span class="selector-tag">textarea</span>, <span class="selector-attr">[data-theme=<span class="string">"dark"</span>]</span> <span class="selector-tag">button</span> {</span></span><br><span class="line"><span class="css"> <span class="attribute">color</span>: white; <span class="comment">/* 将文字颜色设置为白色 */</span></span></span><br><span class="line"><span class="css"> <span class="attribute">border-color</span>: white; <span class="comment">/* 如果需要, 也可以设置边框颜色 */</span></span></span><br><span class="line"><span class="css"> }</span></span><br><span class="line"><span class="css"></span></span><br><span class="line"><span class="css"> <span class="selector-pseudo">:root</span> {</span></span><br><span class="line"><span class="css"> <span class="comment">/* 浅色模式的设置 */</span></span></span><br><span class="line"><span class="css"> --text-<span class="attribute">color</span>: black;</span></span><br><span class="line"><span class="css"> --<span class="attribute">background-color</span>: white;</span></span><br><span class="line"><span class="css"> --<span class="selector-tag">input</span>-bg: <span class="number">#fff</span>;</span></span><br><span class="line"><span class="css"> --container-bg: <span class="number">#fff</span>;</span></span><br><span class="line"><span class="css"> --<span class="selector-tag">header</span>-bg: <span class="number">#fff</span>;</span></span><br><span class="line"><span class="css"> --<span class="selector-tag">button</span>-<span class="attribute">color</span>: black; <span class="comment">/* 新增 */</span></span></span><br><span class="line"><span class="css"> }</span></span><br><span class="line"><span class="css"></span></span><br><span class="line"><span class="css"> <span class="selector-attr">[data-theme=<span class="string">"dark"</span>]</span> {</span></span><br><span class="line"><span class="css"> <span class="comment">/* 深色模式的设置 */</span></span></span><br><span class="line"><span class="css"> --text-<span class="attribute">color</span>: black; <span class="comment">/* 文本颜色为黑色 */</span></span></span><br><span class="line"><span class="css"> --<span class="attribute">background-color</span>: white; <span class="comment">/* 背景颜色为白色 */</span></span></span><br><span class="line"><span class="css"> }</span></span><br><span class="line"><span class="css"></span></span><br><span class="line"><span class="css"> <span class="selector-class">.bottom-controls</span> {</span></span><br><span class="line"><span class="css"> <span class="attribute">display</span>: flex;</span></span><br><span class="line"><span class="css"> <span class="attribute">justify-content</span>: center;</span></span><br><span class="line"><span class="css"> }</span></span><br><span class="line"><span class="css"> <span class="selector-class">.bottom-controls</span> > <span class="selector-tag">div</span> {</span></span><br><span class="line"><span class="css"> <span class="attribute">display</span>: flex; <span class="comment">/* 这确保了内部容器的子元素水平对齐 */</span></span></span><br><span class="line"><span class="css"> <span class="attribute">align-items</span>: center;</span></span><br><span class="line"><span class="css"> }</span></span><br><span class="line"><span class="css"> <span class="comment">/* 为固定底部工具栏添加填充 */</span></span></span><br><span class="line"><span class="css"> <span class="selector-tag">div</span><span class="selector-attr">[style*=<span class="string">"position: fixed; bottom: 0;"</span>]</span> {</span></span><br><span class="line"><span class="css"> <span class="attribute">padding-left</span>: <span class="number">10px</span>;</span></span><br><span class="line"><span class="css"> <span class="attribute">padding-right</span>: <span class="number">10px</span>;</span></span><br><span class="line"><span class="css"> }</span></span><br><span class="line"><span class="css"> <span class="comment">/* 保证body的背景颜色被应用 */</span></span></span><br><span class="line"><span class="css"> <span class="selector-tag">body</span> {</span></span><br><span class="line"><span class="css"> <span class="attribute">display</span>: flex;</span></span><br><span class="line"><span class="css"> <span class="attribute">justify-content</span>: center;</span></span><br><span class="line"><span class="css"> <span class="attribute">align-items</span>: center;</span></span><br><span class="line"><span class="css"> <span class="attribute">height</span>: <span class="number">100vh</span>;</span></span><br><span class="line"><span class="css"> <span class="attribute">background-color</span>: <span class="built_in">var</span>(--background-color) <span class="meta">!important</span>;</span></span><br><span class="line"><span class="css"> <span class="attribute">color</span>: <span class="built_in">var</span>(--text-color);</span></span><br><span class="line"><span class="css"> <span class="attribute">font-family</span>: <span class="string">"JetBrainsMono"</span>, Arial, sans-serif;</span></span><br><span class="line"><span class="css"> }</span></span><br><span class="line"><span class="css"> <span class="selector-class">.container</span> {</span></span><br><span class="line"><span class="css"> <span class="attribute">border</span>: <span class="number">1px</span> solid <span class="number">#ddd</span>;</span></span><br><span class="line"><span class="css"> <span class="attribute">background-color</span>: <span class="number">#fff</span>;</span></span><br><span class="line"><span class="css"> }</span></span><br><span class="line"><span class="css"> <span class="comment">/* 小屏幕的样式(如iphone) */</span></span></span><br><span class="line"><span class="css"> <span class="keyword">@media</span> (<span class="attribute">max-width</span>: <span class="number">500px</span>) {</span></span><br><span class="line"><span class="css"> <span class="selector-class">.bottom-controls</span> {</span></span><br><span class="line"><span class="css"> <span class="attribute">justify-content</span>: flex-start; <span class="comment">/* 这将使内部容器与起始位置对齐 */</span></span></span><br><span class="line"><span class="css"> }</span></span><br><span class="line"><span class="css"> <span class="selector-class">.bottom-controls</span> > <span class="selector-tag">div</span> {</span></span><br><span class="line"><span class="css"> <span class="attribute">flex-direction</span>: column; <span class="comment">/* 这确保了内部容器的子容器是垂直堆叠的 */</span></span></span><br><span class="line"><span class="css"> <span class="attribute">align-items</span>: center;</span></span><br><span class="line"><span class="css"> <span class="attribute">width</span>: <span class="number">100%</span>;</span></span><br><span class="line"><span class="css"> }</span></span><br><span class="line"><span class="css"></span></span><br><span class="line"><span class="css"> <span class="comment">/* 更新底部控件的容器样式 */</span></span></span><br><span class="line"><span class="css"> <span class="selector-tag">div</span><span class="selector-attr">[style*=<span class="string">"position: fixed; bottom: 0;"</span>]</span> {</span></span><br><span class="line"><span class="css"> <span class="attribute">background-color</span>: <span class="built_in">var</span>(--container-bg);</span></span><br><span class="line"><span class="css"></span></span><br><span class="line"><span class="css"> <span class="attribute">padding-left</span>: <span class="number">10px</span>;</span></span><br><span class="line"><span class="css"> <span class="attribute">padding-right</span>: <span class="number">10px</span>;</span></span><br><span class="line"><span class="css"> <span class="attribute">display</span>: flex;</span></span><br><span class="line"><span class="css"> <span class="attribute">flex-direction</span>: column; <span class="comment">/* 确保控件垂直堆叠 */</span></span></span><br><span class="line"><span class="css"> }</span></span><br><span class="line"><span class="css"></span></span><br><span class="line"><span class="css"> <span class="comment">/* 确保所有控件拉伸以填充可用宽度 */</span></span></span><br><span class="line"><span class="css"> <span class="selector-id">#deviceName</span>, <span class="selector-id">#textArea</span>, <span class="selector-id">#imageUpload</span>, <span class="selector-id">#sendButton</span> {</span></span><br><span class="line"><span class="css"> <span class="attribute">background-color</span>: <span class="built_in">var</span>(--container-bg);</span></span><br><span class="line"><span class="css"></span></span><br><span class="line"><span class="css"> <span class="attribute">width</span>: <span class="number">100%</span>;</span></span><br><span class="line"><span class="css"> <span class="attribute">box-sizing</span>: border-box; <span class="comment">/* 确保填充和边框包含在宽度中 */</span></span></span><br><span class="line"><span class="css"> <span class="attribute">margin-bottom</span>: <span class="number">5px</span>; <span class="comment">/* 添加控件之间的间距 */</span></span></span><br><span class="line"><span class="css"> }</span></span><br><span class="line"><span class="css"></span></span><br><span class="line"><span class="css"> <span class="comment">/* 增加高度,便于触摸 */</span></span></span><br><span class="line"><span class="css"> <span class="selector-id">#deviceName</span>, <span class="selector-id">#textArea</span>, <span class="selector-id">#sendButton</span> {</span></span><br><span class="line"><span class="css"> <span class="attribute">height</span>: <span class="number">25px</span>;</span></span><br><span class="line"><span class="css"> }</span></span><br><span class="line"><span class="css"> <span class="comment">/* 在邮件列表的底部添加填充 */</span></span></span><br><span class="line"><span class="css"> <span class="selector-id">#messages</span> {</span></span><br><span class="line"><span class="css"> <span class="attribute">padding-bottom</span>: <span class="number">70px</span>; <span class="comment">/* 根据底部工具栏的高度调整此值 */</span></span></span><br><span class="line"><span class="css"> }</span></span><br><span class="line"><span class="css"> <span class="selector-id">#deleteButton</span>{</span></span><br><span class="line"><span class="css"> <span class="attribute">width</span>: <span class="number">100%</span>;</span></span><br><span class="line"><span class="css"> <span class="attribute">height</span>: <span class="number">25px</span>;</span></span><br><span class="line"><span class="css"> }</span></span><br><span class="line"><span class="css"> <span class="selector-class">.container</span>{</span></span><br><span class="line"><span class="css"> <span class="attribute">max-height</span>: <span class="number">480px</span>;</span></span><br><span class="line"><span class="css"> }</span></span><br><span class="line"><span class="css"> <span class="selector-id">#themeToggle</span>{</span></span><br><span class="line"><span class="css"> <span class="attribute">width</span>: <span class="number">100%</span>;</span></span><br><span class="line"><span class="css"> <span class="attribute">height</span>: <span class="number">25px</span>;</span></span><br><span class="line"><span class="css"> <span class="attribute">margin-bottom</span>: <span class="number">5px</span>;</span></span><br><span class="line"><span class="css"> }</span></span><br><span class="line"><span class="css"> }</span></span><br><span class="line"><span class="css"> <span class="comment">/* 大屏幕的样式(例如,Windows, MacBook) */</span></span></span><br><span class="line"><span class="css"> <span class="keyword">@media</span> (<span class="attribute">min-width</span>: <span class="number">501px</span>) {</span></span><br><span class="line"><span class="css"> <span class="selector-id">#deviceName</span>, <span class="selector-id">#textArea</span>, <span class="selector-id">#imageUpload</span>, <span class="selector-id">#sendButton</span> {</span></span><br><span class="line"><span class="css"> <span class="attribute">background-color</span>: <span class="built_in">var</span>(--input-bg);</span></span><br><span class="line"><span class="css"></span></span><br><span class="line"><span class="css"> <span class="attribute">height</span>: <span class="number">60px</span>;</span></span><br><span class="line"><span class="css"> <span class="attribute">font-size</span>: <span class="number">18px</span>;</span></span><br><span class="line"><span class="css"> <span class="attribute">padding</span>: <span class="number">10px</span>;</span></span><br><span class="line"><span class="css"> <span class="attribute">margin-bottom</span>: <span class="number">10px</span>;</span></span><br><span class="line"><span class="css"> }</span></span><br><span class="line"><span class="css"> <span class="selector-id">#textArea</span> {</span></span><br><span class="line"><span class="css"> <span class="attribute">width</span>: <span class="number">50%</span>;</span></span><br><span class="line"><span class="css"> }</span></span><br><span class="line"><span class="css"> <span class="selector-id">#sendButton</span> {</span></span><br><span class="line"><span class="css"> <span class="attribute">padding</span>: <span class="number">10px</span> <span class="number">20px</span>; <span class="comment">/* 调整按钮的填充 */</span></span></span><br><span class="line"><span class="css"> <span class="attribute">margin-left</span>: <span class="number">10px</span>; <span class="comment">/* 在发送按钮的左侧添加一些边距,以保持间距 */</span></span></span><br><span class="line"><span class="css"> <span class="attribute">margin-right</span>: <span class="number">5px</span>;</span></span><br><span class="line"><span class="css"> <span class="attribute">width</span>: <span class="number">100px</span>;</span></span><br><span class="line"><span class="css"> }</span></span><br><span class="line"><span class="css"> <span class="selector-id">#deleteButton</span>{</span></span><br><span class="line"><span class="css"> <span class="attribute">margin-left</span>: <span class="number">5px</span>;</span></span><br><span class="line"><span class="css"> <span class="attribute">width</span>: <span class="number">150px</span>;</span></span><br><span class="line"><span class="css"> <span class="attribute">height</span>: <span class="number">50px</span>;</span></span><br><span class="line"><span class="css"> }</span></span><br><span class="line"><span class="css"> <span class="selector-id">#themeToggle</span>{</span></span><br><span class="line"><span class="css"> <span class="attribute">margin-right</span>: <span class="number">10px</span>;</span></span><br><span class="line"><span class="css"> <span class="attribute">width</span>: <span class="number">100px</span>;</span></span><br><span class="line"><span class="css"> <span class="attribute">height</span>: <span class="number">50px</span>;</span></span><br><span class="line"><span class="css"> }</span></span><br><span class="line"><span class="css"> }</span></span><br><span class="line"><span class="css"> <span class="selector-tag">body</span>, <span class="selector-tag">button</span> {</span></span><br><span class="line"><span class="css"> <span class="attribute">background-color</span>: <span class="built_in">var</span>(--bg-color);</span></span><br><span class="line"><span class="css"> <span class="attribute">color</span>: <span class="built_in">var</span>(--text-color);</span></span><br><span class="line"><span class="css"> }</span></span><br><span class="line"><span class="css"> <span class="comment">/* 为了确保按钮的文本颜色能够在两种主题下都清晰可见 */</span></span></span><br><span class="line"><span class="css"> <span class="selector-tag">button</span> {</span></span><br><span class="line"><span class="css"> <span class="attribute">color</span>: <span class="built_in">var</span>(--button-color);</span></span><br><span class="line"><span class="css"> }</span></span><br><span class="line"><span class="css"> <span class="comment">/* 底部控件的文本颜色 */</span></span></span><br><span class="line"><span class="css"> <span class="selector-class">.bottom-controls</span> <span class="selector-tag">input</span>,</span></span><br><span class="line"><span class="css"> <span class="selector-class">.bottom-controls</span> <span class="selector-tag">textarea</span>,</span></span><br><span class="line"><span class="css"> <span class="selector-class">.bottom-controls</span> <span class="selector-tag">button</span> {</span></span><br><span class="line"><span class="css"> <span class="attribute">color</span>: <span class="built_in">var</span>(--text-color);</span></span><br><span class="line"><span class="css"> <span class="attribute">border-color</span>: <span class="built_in">var</span>(--text-color);</span></span><br><span class="line"><span class="css"> }</span></span><br><span class="line"><span class="css"> <span class="selector-class">.container</span> {</span></span><br><span class="line"><span class="css"> <span class="attribute">background-color</span>: <span class="built_in">var</span>(--container-bg);</span></span><br><span class="line"><span class="css"> <span class="attribute">overflow-y</span>: auto;</span></span><br><span class="line"><span class="css"> <span class="attribute">min-width</span>: <span class="number">350px</span>;</span></span><br><span class="line"><span class="css"> <span class="attribute">position</span>: sticky;</span></span><br><span class="line"><span class="css"> <span class="attribute">margin-bottom</span>: <span class="number">100px</span>;</span></span><br><span class="line"><span class="css"> }</span></span><br><span class="line"><span class="css"> <span class="selector-tag">body</span><span class="selector-class">.dark-theme</span> {</span></span><br><span class="line"><span class="css"> <span class="comment">/* 深色模式的设置 */</span></span></span><br><span class="line"><span class="css"> --text-<span class="attribute">color</span>: white;</span></span><br><span class="line"><span class="css"> --<span class="attribute">background-color</span>: <span class="number">#555</span>;</span></span><br><span class="line"><span class="css"> --<span class="selector-tag">input</span>-bg: <span class="number">#555</span>;</span></span><br><span class="line"><span class="css"> --container-bg: <span class="number">#444</span>;</span></span><br><span class="line"><span class="css"> --<span class="selector-tag">header</span>-bg: <span class="number">#444</span>;</span></span><br><span class="line"><span class="css"> --<span class="selector-tag">button</span>-<span class="attribute">color</span>: white; <span class="comment">/* 新增 */</span></span></span><br><span class="line"><span class="css"> }</span></span><br><span class="line"><span class="css"> <span class="selector-id">#header</span> {</span></span><br><span class="line"><span class="css"> <span class="attribute">background-color</span>: <span class="built_in">var</span>(--header-bg);</span></span><br><span class="line"><span class="css"> }</span></span><br><span class="line"><span class="css"> <span class="keyword">@font-face</span> {</span></span><br><span class="line"><span class="css"> <span class="attribute">font-family</span>: <span class="string">"JetBrainsMono"</span>; <span class="comment">/* 可以为字体起一个名字 */</span></span></span><br><span class="line"><span class="css"> <span class="attribute">src</span>: <span class="built_in">url</span>(<span class="string">'JetBrainsMono-Regular.woff2'</span>) <span class="built_in">format</span>(<span class="string">'woff2'</span>), /* 最优先 */</span></span><br><span class="line"><span class="css"> <span class="built_in">url</span>(<span class="string">'JetBrainsMono-Regular.ttf'</span>) <span class="built_in">format</span>(<span class="string">'truetype'</span>);</span></span><br><span class="line"><span class="css"> <span class="attribute">font-weight</span>: normal;</span></span><br><span class="line"><span class="css"> <span class="attribute">font-style</span>: normal;</span></span><br><span class="line"><span class="css"> }</span></span><br><span class="line"><span class="css"></span></span><br><span class="line"><span class="css"> </span><span class="tag"></<span class="name">style</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">meta</span> <span class="attr">name</span>=<span class="string">"viewport"</span> <span class="attr">content</span>=<span class="string">"width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"</span>></span></span><br><span class="line"><span class="tag"></<span class="name">head</span>></span></span><br><span class="line"><span class="tag"><<span class="name">body</span>></span></span><br><span class="line"></span><br><span class="line"><span class="tag"><<span class="name">div</span> <span class="attr">class</span>=<span class="string">"container"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">div</span> <span class="attr">id</span>=<span class="string">"header"</span> <span class="attr">style</span>=<span class="string">"background-color: var(--container-bg);"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">ul</span> <span class="attr">id</span>=<span class="string">"messages"</span> <span class="attr">style</span>=<span class="string">"max-height: 70vh"</span>></span><span class="tag"></<span class="name">ul</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">div</span>></span></span><br><span class="line"><span class="tag"></<span class="name">div</span>></span></span><br><span class="line"></span><br><span class="line"><span class="comment"><!-- 调整底部工具栏结构 --></span></span><br><span class="line"><span class="tag"><<span class="name">div</span> <span class="attr">class</span>=<span class="string">"bottom-controls"</span> <span class="attr">style</span>=<span class="string">"position: fixed; bottom: 0; width: 100%; padding: 5px; box-shadow: 0 -2px 5px rgba(0,0,0,0.1); background-color: var(--container-bg);"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">div</span> <span class="attr">style</span>=<span class="string">"display: flex; align-items: center;"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">button</span> <span class="attr">id</span>=<span class="string">"themeToggle"</span>></span>切换主题<span class="tag"></<span class="name">button</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">input</span> <span class="attr">type</span>=<span class="string">"text"</span> <span class="attr">id</span>=<span class="string">"deviceName"</span> <span class="attr">placeholder</span>=<span class="string">"Device Name"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">input</span> <span class="attr">type</span>=<span class="string">"file"</span> <span class="attr">id</span>=<span class="string">"imageUpload"</span> <span class="attr">accept</span>=<span class="string">"image/*"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">textarea</span> <span class="attr">id</span>=<span class="string">"textArea"</span> <span class="attr">placeholder</span>=<span class="string">"写点什么?!"</span>></span><span class="tag"></<span class="name">textarea</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">button</span> <span class="attr">id</span>=<span class="string">"sendButton"</span>></span>发送<span class="tag"></<span class="name">button</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">button</span> <span class="attr">id</span>=<span class="string">"deleteButton"</span>></span>删除选中消息<span class="tag"></<span class="name">button</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">div</span>></span></span><br><span class="line"><span class="tag"></<span class="name">div</span>></span></span><br><span class="line"></span><br><span class="line"><span class="tag"><<span class="name">script</span> <span class="attr">src</span>=<span class="string">"/socket.io/socket.io.js"</span>></span><span class="tag"></<span class="name">script</span>></span></span><br><span class="line"><span class="tag"><<span class="name">script</span>></span><span class="javascript"></span></span><br><span class="line"><span class="javascript"> <span class="built_in">window</span>.addEventListener(<span class="string">'load'</span>, <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>{</span></span><br><span class="line"><span class="javascript"> <span class="built_in">setTimeout</span>(<span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>{</span></span><br><span class="line"><span class="javascript"> <span class="built_in">window</span>.scrollTo(<span class="number">0</span>, <span class="number">1</span>);</span></span><br><span class="line"><span class="javascript"> }, <span class="number">0</span>);</span></span><br><span class="line"><span class="javascript"></span></span><br><span class="line"><span class="javascript"> <span class="comment">// 请求历史消息</span></span></span><br><span class="line"><span class="javascript"> fetch(<span class="string">'/messages'</span>)</span></span><br><span class="line"><span class="javascript"> .then(<span class="function"><span class="params">response</span> =></span> response.json())</span></span><br><span class="line"><span class="javascript"> .then(<span class="function"><span class="params">data</span> =></span> {</span></span><br><span class="line"><span class="javascript"> <span class="keyword">const</span> messagesDiv = <span class="built_in">document</span>.getElementById(<span class="string">'messages'</span>);</span></span><br><span class="line"><span class="javascript"> data.messages.forEach(<span class="function"><span class="params">message</span> =></span> {</span></span><br><span class="line"><span class="javascript"> <span class="keyword">const</span> p = <span class="built_in">document</span>.createElement(<span class="string">'p'</span>);</span></span><br><span class="line"><span class="javascript"> p.textContent = message;</span></span><br><span class="line"><span class="javascript"> messagesDiv.appendChild(p);</span></span><br><span class="line"><span class="javascript"> });</span></span><br><span class="line"><span class="javascript"> })</span></span><br><span class="line"><span class="javascript"> .catch(<span class="function"><span class="params">error</span> =></span> {</span></span><br><span class="line"><span class="javascript"> <span class="built_in">console</span>.error(<span class="string">'获取消息错误:'</span>, error);</span></span><br><span class="line"><span class="javascript"> });</span></span><br><span class="line"><span class="javascript"> });</span></span><br><span class="line"><span class="javascript"></span></span><br><span class="line"><span class="javascript"></span></span><br><span class="line"><span class="javascript"> <span class="built_in">document</span>.getElementById(<span class="string">'messages'</span>).scrollTop = <span class="built_in">document</span>.getElementById(<span class="string">'messages'</span>).scrollHeight;</span></span><br><span class="line"><span class="javascript"></span></span><br><span class="line"><span class="javascript"></span></span><br><span class="line"><span class="javascript"> <span class="keyword">var</span> socket = io();</span></span><br><span class="line"><span class="javascript"></span></span><br><span class="line"><span class="javascript"> <span class="built_in">document</span>.getElementById(<span class="string">'sendButton'</span>).onclick = <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>{</span></span><br><span class="line"><span class="javascript"> <span class="keyword">var</span> text = <span class="built_in">document</span>.getElementById(<span class="string">'textArea'</span>).value;</span></span><br><span class="line"><span class="javascript"> <span class="keyword">var</span> deviceName = <span class="built_in">document</span>.getElementById(<span class="string">'deviceName'</span>).value || <span class="string">"匿名设备"</span>;</span></span><br><span class="line"><span class="javascript"> <span class="keyword">var</span> imageFile = <span class="built_in">document</span>.getElementById(<span class="string">'imageUpload'</span>).files[<span class="number">0</span>];</span></span><br><span class="line"><span class="javascript"> <span class="keyword">var</span> listCheckBox = <span class="built_in">document</span>.getElementById(<span class="string">'listCheckBox'</span>);</span></span><br><span class="line"><span class="javascript"></span></span><br><span class="line"><span class="javascript"> <span class="keyword">if</span> (imageFile) {</span></span><br><span class="line"><span class="javascript"> <span class="keyword">var</span> formData = <span class="keyword">new</span> FormData();</span></span><br><span class="line"><span class="javascript"> formData.append(<span class="string">'image'</span>, imageFile);</span></span><br><span class="line"><span class="javascript"> formData.append(<span class="string">'deviceName'</span>, deviceName);</span></span><br><span class="line"><span class="javascript"> formData.append(<span class="string">'text'</span>, text);</span></span><br><span class="line"><span class="javascript"> formData.append(<span class="string">'listCheckBox'</span>, listCheckBox);</span></span><br><span class="line"><span class="javascript"></span></span><br><span class="line"><span class="javascript"> fetch(<span class="string">'/upload-image'</span>, {</span></span><br><span class="line"><span class="javascript"> <span class="attr">method</span>: <span class="string">'POST'</span>,</span></span><br><span class="line"><span class="javascript"> <span class="attr">body</span>: formData</span></span><br><span class="line"><span class="javascript"> }).then(<span class="function"><span class="params">response</span> =></span> response.json()).then(<span class="function"><span class="params">data</span> =></span> {</span></span><br><span class="line"><span class="javascript"> socket.emit(<span class="string">'send text'</span>, data);</span></span><br><span class="line"><span class="javascript"> });</span></span><br><span class="line"><span class="javascript"> } <span class="keyword">else</span> {</span></span><br><span class="line"><span class="javascript"> socket.emit(<span class="string">'send text'</span>, {text, deviceName });</span></span><br><span class="line"><span class="javascript"> }</span></span><br><span class="line"><span class="javascript"> <span class="comment">// 在所有情况下都清除textArea和图片选择器的值</span></span></span><br><span class="line"><span class="javascript"> <span class="built_in">document</span>.getElementById(<span class="string">'textArea'</span>).value = <span class="string">''</span>;</span></span><br><span class="line"><span class="javascript"> <span class="built_in">document</span>.getElementById(<span class="string">'imageUpload'</span>).value = <span class="string">''</span>; <span class="comment">// 这将清除文件输入选择器的内容</span></span></span><br><span class="line"><span class="javascript"> };</span></span><br><span class="line"><span class="javascript"></span></span><br><span class="line"><span class="javascript"> <span class="keyword">var</span> messageId = <span class="number">0</span>; <span class="comment">// 初始化一个全局变量</span></span></span><br><span class="line"><span class="javascript"> socket.on(<span class="string">'receive text'</span>, <span class="function"><span class="keyword">function</span>(<span class="params">data</span>) </span>{</span></span><br><span class="line"><span class="javascript"> <span class="keyword">var</span> li = <span class="built_in">document</span>.createElement(<span class="string">"li"</span>);</span></span><br><span class="line"><span class="javascript"></span></span><br><span class="line"><span class="javascript"> <span class="comment">// 创建复选框</span></span></span><br><span class="line"><span class="javascript"> <span class="keyword">var</span> checkbox = <span class="built_in">document</span>.createElement(<span class="string">"input"</span>);</span></span><br><span class="line"><span class="javascript"> checkbox.type = <span class="string">"checkbox"</span>;</span></span><br><span class="line"><span class="javascript"> checkbox.className = <span class="string">"messageCheckbox"</span>;</span></span><br><span class="line"><span class="javascript"> li.appendChild(checkbox); <span class="comment">// 将复选框添加到列表项中</span></span></span><br><span class="line"><span class="javascript"></span></span><br><span class="line"><span class="javascript"> <span class="keyword">var</span> time = data.timestamp || <span class="keyword">new</span> <span class="built_in">Date</span>().toLocaleString(); <span class="comment">// 获取时间戳,如果没有则使用当前时间</span></span></span><br><span class="line"><span class="javascript"></span></span><br><span class="line"><span class="javascript"> <span class="keyword">var</span> content = <span class="string">`[<span class="subst">${data.deviceName}</span> @ <span class="subst">${time}</span>]: `</span>; <span class="comment">// 显示设备名和时间</span></span></span><br><span class="line"><span class="javascript"> <span class="keyword">if</span> (data.text.startsWith(<span class="string">"```"</span>) && data.text.endsWith(<span class="string">"```"</span>)) {</span></span><br><span class="line"><span class="javascript"> <span class="comment">// 如果文本以 "```" 开头和结尾,将其识别为代码</span></span></span><br><span class="line"><span class="javascript"> content += <span class="string">`<pre><span class="subst">${data.text.slice(<span class="number">3</span>, -<span class="number">3</span>)}</span></pre>`</span>;</span></span><br><span class="line"><span class="javascript"> } <span class="keyword">else</span> {</span></span><br><span class="line"><span class="javascript"> content += <span class="string">`<span class="subst">${data.text}</span>`</span>;</span></span><br><span class="line"><span class="javascript"> }</span></span><br><span class="line"><span class="javascript"></span></span><br><span class="line"><span class="javascript"> <span class="keyword">if</span> (data.imageUrl) {</span></span><br><span class="line"><span class="javascript"> content += <span class="string">`<br><img src="<span class="subst">${data.imageUrl}</span>" alt="Uploaded image" style="max-width: 300px;">`</span>;</span></span><br><span class="line"><span class="javascript"> }</span></span><br><span class="line"><span class="javascript"></span></span><br><span class="line"><span class="javascript"> li.innerHTML += content;</span></span><br><span class="line"><span class="javascript"> <span class="built_in">document</span>.getElementById(<span class="string">'messages'</span>).appendChild(li);</span></span><br><span class="line"><span class="javascript"></span></span><br><span class="line"><span class="javascript"> li.setAttribute(<span class="string">'data-message-id'</span>, messageId); <span class="comment">// 为每个消息设置唯一ID</span></span></span><br><span class="line"><span class="javascript"> messageId++;</span></span><br><span class="line"><span class="javascript"></span></span><br><span class="line"><span class="javascript"> <span class="built_in">console</span>.log(<span class="string">"设备名称和IP地址:"</span>, data.deviceName);</span></span><br><span class="line"><span class="javascript"> });</span></span><br><span class="line"><span class="javascript"></span></span><br><span class="line"><span class="javascript"> <span class="comment">// 发送HTTP DELETE请求到后端来删除消息</span></span></span><br><span class="line"><span class="javascript"> <span class="built_in">document</span>.getElementById(<span class="string">'deleteButton'</span>).onclick = <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>{</span></span><br><span class="line"><span class="javascript"> <span class="keyword">var</span> selectedMessages = <span class="built_in">document</span>.querySelectorAll(<span class="string">'.messageCheckbox:checked'</span>);</span></span><br><span class="line"><span class="javascript"> <span class="keyword">var</span> messageIdsToDelete = [];</span></span><br><span class="line"><span class="javascript"> selectedMessages.forEach(<span class="function"><span class="keyword">function</span>(<span class="params">checkbox</span>) </span>{</span></span><br><span class="line"><span class="javascript"> <span class="keyword">var</span> li = checkbox.closest(<span class="string">'li'</span>);</span></span><br><span class="line"><span class="javascript"> messageIdsToDelete.push(li.getAttribute(<span class="string">'data-message-id'</span>));</span></span><br><span class="line"><span class="javascript"> li.remove();</span></span><br><span class="line"><span class="javascript"> });</span></span><br><span class="line"><span class="javascript"> socket.emit(<span class="string">'delete messages'</span>, messageIdsToDelete);</span></span><br><span class="line"><span class="javascript"></span></span><br><span class="line"><span class="javascript"> <span class="comment">// 向后端发送删除请求</span></span></span><br><span class="line"><span class="javascript"> messageIdsToDelete.forEach(<span class="function"><span class="params">id</span> =></span> {</span></span><br><span class="line"><span class="javascript"> fetch(<span class="string">`/message/<span class="subst">${id}</span>`</span>, {</span></span><br><span class="line"><span class="javascript"> <span class="attr">method</span>: <span class="string">'DELETE'</span></span></span><br><span class="line"><span class="javascript"> }).then(<span class="function"><span class="params">response</span> =></span> {</span></span><br><span class="line"><span class="javascript"> <span class="keyword">if</span> (!response.ok) {</span></span><br><span class="line"><span class="javascript"> <span class="comment">// 以某种方式处理错误响应</span></span></span><br><span class="line"><span class="javascript"> <span class="built_in">console</span>.error(<span class="string">'从服务器删除消息失败'</span>);</span></span><br><span class="line"><span class="javascript"> }</span></span><br><span class="line"><span class="javascript"> });</span></span><br><span class="line"><span class="javascript"> });</span></span><br><span class="line"><span class="javascript"> };</span></span><br><span class="line"><span class="javascript"></span></span><br><span class="line"><span class="javascript"></span></span><br><span class="line"><span class="javascript"> socket.on(<span class="string">'delete messages'</span>, <span class="function"><span class="keyword">function</span>(<span class="params">messageIds</span>) </span>{</span></span><br><span class="line"><span class="javascript"> messageIds.forEach(<span class="function"><span class="keyword">function</span>(<span class="params">messageId</span>) </span>{</span></span><br><span class="line"><span class="javascript"> <span class="keyword">var</span> li = <span class="built_in">document</span>.querySelector(<span class="string">'li[data-message-id="'</span> + messageId + <span class="string">'"]'</span>);</span></span><br><span class="line"><span class="javascript"> <span class="keyword">if</span> (li) li.remove();</span></span><br><span class="line"><span class="javascript"> });</span></span><br><span class="line"><span class="javascript"> io.emit(<span class="string">'delete messages'</span>, messageIds); <span class="comment">// 向所有客户端广播消息ID,以供删除</span></span></span><br><span class="line"><span class="javascript"> });</span></span><br><span class="line"><span class="javascript"></span></span><br><span class="line"><span class="javascript"></span></span><br><span class="line"><span class="javascript"></span></span><br><span class="line"><span class="javascript"> <span class="built_in">document</span>.addEventListener(<span class="string">'click'</span>, <span class="function"><span class="keyword">function</span>(<span class="params">event</span>) </span>{</span></span><br><span class="line"><span class="javascript"> <span class="comment">// 如果消息中的图像被点击</span></span></span><br><span class="line"><span class="javascript"> <span class="keyword">if</span> (event.target.tagName === <span class="string">'IMG'</span> && event.target.closest(<span class="string">'#messages'</span>)) {</span></span><br><span class="line"><span class="javascript"> <span class="keyword">var</span> modal = <span class="built_in">document</span>.getElementById(<span class="string">'imageModal'</span>);</span></span><br><span class="line"><span class="javascript"> <span class="keyword">var</span> modalImage = <span class="built_in">document</span>.getElementById(<span class="string">'modalImage'</span>);</span></span><br><span class="line"><span class="javascript"></span></span><br><span class="line"><span class="javascript"> modalImage.src = event.target.src;</span></span><br><span class="line"><span class="javascript"> modal.style.display = <span class="string">'flex'</span>;</span></span><br><span class="line"><span class="javascript"></span></span><br><span class="line"><span class="javascript"> modal.addEventListener(<span class="string">'click'</span>, <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>{</span></span><br><span class="line"><span class="javascript"> modal.style.display = <span class="string">'none'</span>;</span></span><br><span class="line"><span class="javascript"> });</span></span><br><span class="line"><span class="javascript"> }</span></span><br><span class="line"><span class="javascript"> });</span></span><br><span class="line"><span class="javascript"></span></span><br><span class="line"><span class="javascript"> <span class="comment">// 按下'Escape'关闭模式</span></span></span><br><span class="line"><span class="javascript"> <span class="built_in">document</span>.addEventListener(<span class="string">'keydown'</span>, <span class="function"><span class="keyword">function</span>(<span class="params">event</span>) </span>{</span></span><br><span class="line"><span class="javascript"> <span class="keyword">if</span> (event.key === <span class="string">'Escape'</span>) {</span></span><br><span class="line"><span class="javascript"> <span class="keyword">var</span> modal = <span class="built_in">document</span>.getElementById(<span class="string">'imageModal'</span>);</span></span><br><span class="line"><span class="javascript"> modal.style.display = <span class="string">'none'</span>;</span></span><br><span class="line"><span class="javascript"> }</span></span><br><span class="line"><span class="javascript"> });</span></span><br><span class="line"><span class="javascript"></span></span><br><span class="line"><span class="javascript"> <span class="function"><span class="keyword">function</span> <span class="title">getDeviceNameFromUserAgent</span>(<span class="params"></span>) </span>{</span></span><br><span class="line"><span class="javascript"> <span class="keyword">var</span> userAgent = navigator.userAgent || navigator.vendor || <span class="built_in">window</span>.opera;</span></span><br><span class="line"><span class="javascript"></span></span><br><span class="line"><span class="javascript"> <span class="comment">// Windows Phone必须先行一步,因为它的用户界面也包含“Android”。</span></span></span><br><span class="line"><span class="javascript"> <span class="keyword">if</span> (<span class="regexp">/windows phone/i</span>.test(userAgent)) {</span></span><br><span class="line"><span class="javascript"> <span class="keyword">return</span> <span class="string">"Windows Phone"</span>;</span></span><br><span class="line"><span class="javascript"> }</span></span><br><span class="line"><span class="javascript"></span></span><br><span class="line"><span class="javascript"> <span class="keyword">if</span> (<span class="regexp">/android/i</span>.test(userAgent)) {</span></span><br><span class="line"><span class="javascript"> <span class="keyword">return</span> <span class="string">"Android Device"</span>;</span></span><br><span class="line"><span class="javascript"> }</span></span><br><span class="line"><span class="javascript"></span></span><br><span class="line"><span class="javascript"> <span class="comment">// iOS检测</span></span></span><br><span class="line"><span class="javascript"> <span class="keyword">if</span> (<span class="regexp">/iPad|iPhone|iPod/</span>.test(userAgent) && !<span class="built_in">window</span>.MSStream) {</span></span><br><span class="line"><span class="javascript"> <span class="keyword">return</span> <span class="string">"iOS Device"</span>;</span></span><br><span class="line"><span class="javascript"> }</span></span><br><span class="line"><span class="javascript"></span></span><br><span class="line"><span class="javascript"> <span class="keyword">if</span> (<span class="regexp">/macintosh|mac os x/i</span>.test(userAgent)) {</span></span><br><span class="line"><span class="javascript"> <span class="keyword">return</span> <span class="string">"Mac"</span>;</span></span><br><span class="line"><span class="javascript"> }</span></span><br><span class="line"><span class="javascript"></span></span><br><span class="line"><span class="javascript"> <span class="keyword">if</span> (<span class="regexp">/windows/i</span>.test(userAgent)) {</span></span><br><span class="line"><span class="javascript"> <span class="keyword">return</span> <span class="string">"Windows PC"</span>;</span></span><br><span class="line"><span class="javascript"> }</span></span><br><span class="line"><span class="javascript"></span></span><br><span class="line"><span class="javascript"> <span class="keyword">return</span> <span class="string">"匿名设备"</span>;</span></span><br><span class="line"><span class="javascript"> }</span></span><br><span class="line"><span class="javascript"></span></span><br><span class="line"><span class="javascript"> <span class="comment">// 使用函数来自动填充设备名称</span></span></span><br><span class="line"><span class="javascript"> <span class="built_in">document</span>.getElementById(<span class="string">'deviceName'</span>).value = getDeviceNameFromUserAgent();</span></span><br><span class="line"><span class="javascript"></span></span><br><span class="line"><span class="javascript"> <span class="comment">// 切换主题</span></span></span><br><span class="line"><span class="javascript"> <span class="built_in">document</span>.getElementById(<span class="string">'themeToggle'</span>).addEventListener(<span class="string">'click'</span>, <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>{</span></span><br><span class="line"><span class="javascript"> <span class="built_in">document</span>.body.classList.toggle(<span class="string">'dark-theme'</span>);</span></span><br><span class="line"><span class="javascript"> });</span></span><br><span class="line"><span class="javascript"></span></span><br><span class="line"><span class="javascript"></span></span><br><span class="line"><span class="javascript"> socket.on(<span class="string">'load history'</span>, <span class="function"><span class="keyword">function</span>(<span class="params">history</span>) </span>{</span></span><br><span class="line"><span class="javascript"> history.forEach(<span class="function"><span class="params">data</span> =></span> {</span></span><br><span class="line"><span class="javascript"> <span class="keyword">var</span> li = <span class="built_in">document</span>.createElement(<span class="string">"li"</span>);</span></span><br><span class="line"><span class="javascript"> <span class="keyword">var</span> content = <span class="string">`[<span class="subst">${data.deviceName}</span>]: <span class="subst">${data.text}</span>`</span>;</span></span><br><span class="line"><span class="javascript"> <span class="keyword">if</span> (data.imageUrl) {</span></span><br><span class="line"><span class="javascript"> content += <span class="string">`<br><img src="<span class="subst">${data.imageUrl}</span>" alt="Uploaded image" style="max-width: 300px;">`</span>;</span></span><br><span class="line"><span class="javascript"> }</span></span><br><span class="line"><span class="javascript"> li.innerHTML = content;</span></span><br><span class="line"><span class="javascript"> <span class="built_in">document</span>.getElementById(<span class="string">'messages'</span>).appendChild(li);</span></span><br><span class="line"><span class="javascript"> });</span></span><br><span class="line"><span class="javascript"> });</span></span><br><span class="line"><span class="javascript"></span></span><br><span class="line"><span class="javascript"></span><span class="tag"></<span class="name">script</span>></span></span><br><span class="line"></span><br><span class="line"><span class="comment"><!-- 图像模块 --></span></span><br><span class="line"><span class="tag"><<span class="name">div</span> <span class="attr">id</span>=<span class="string">"imageModal"</span> <span class="attr">style</span>=<span class="string">"display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0,0,0,0.7); z-index: 9999; align-items: center; justify-content: center;"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">img</span> <span class="attr">id</span>=<span class="string">"modalImage"</span> <span class="attr">src</span>=<span class="string">""</span> <span class="attr">style</span>=<span class="string">"max-width: 90%; max-height: 90%;"</span>></span></span><br><span class="line"><span class="tag"></<span class="name">div</span>></span></span><br><span class="line"></span><br><span class="line"><span class="tag"></<span class="name">body</span>></span></span><br><span class="line"><span class="tag"></<span class="name">html</span>></span></span><br></pre></td></tr></tbody></table></figure><h3 id="0x4-2-server-js"><a href="#0x4-2-server-js" class="headerlink" title="0x4.2 server.js"></a>0x4.2 server.js</h3><figure class="highlight javascript"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br><span class="line">161</span><br><span class="line">162</span><br><span class="line">163</span><br><span class="line">164</span><br><span class="line">165</span><br><span class="line">166</span><br><span class="line">167</span><br><span class="line">168</span><br><span class="line">169</span><br><span class="line">170</span><br><span class="line">171</span><br><span class="line">172</span><br><span class="line">173</span><br><span class="line">174</span><br><span class="line">175</span><br><span class="line">176</span><br><span class="line">177</span><br><span class="line">178</span><br><span class="line">179</span><br><span class="line">180</span><br><span class="line">181</span><br><span class="line">182</span><br><span class="line">183</span><br><span class="line">184</span><br><span class="line">185</span><br><span class="line">186</span><br><span class="line">187</span><br><span class="line">188</span><br><span class="line">189</span><br><span class="line">190</span><br><span class="line">191</span><br><span class="line">192</span><br><span class="line">193</span><br><span class="line">194</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> express = <span class="built_in">require</span>(<span class="string">'express'</span>);</span><br><span class="line"><span class="keyword">const</span> http = <span class="built_in">require</span>(<span class="string">'http'</span>);</span><br><span class="line"><span class="keyword">const</span> socketIo = <span class="built_in">require</span>(<span class="string">'socket.io'</span>);</span><br><span class="line"><span class="keyword">const</span> multer = <span class="built_in">require</span>(<span class="string">'multer'</span>);</span><br><span class="line"><span class="keyword">const</span> path = <span class="built_in">require</span>(<span class="string">'path'</span>);</span><br><span class="line"><span class="keyword">const</span> sqlite3 = <span class="built_in">require</span>(<span class="string">'sqlite3'</span>).verbose();</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> app = express();</span><br><span class="line"><span class="keyword">const</span> server = http.createServer(app);</span><br><span class="line"><span class="keyword">const</span> io = socketIo(server);</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> messageHistory = [];</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment">// 创建或打开数据库</span></span><br><span class="line"><span class="keyword">const</span> db = <span class="keyword">new</span> sqlite3.Database(<span class="string">'./messages.db'</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 创建message表,如果存在则什么都不做</span></span><br><span class="line"><span class="comment">// 包含id、content、deviceName、 ipAddress、 timestamp字段</span></span><br><span class="line">db.run(<span class="string">"CREATE TABLE IF NOT EXISTS messages (id INTEGER PRIMARY KEY AUTOINCREMENT, content TEXT, deviceName TEXT, ipAddress TEXT, timestamp TEXT)"</span>, <span class="function">(<span class="params">err</span>) =></span> {</span><br><span class="line"> <span class="keyword">if</span> (err) {</span><br><span class="line"> <span class="built_in">console</span>.error(<span class="string">"创建/检查表错误:"</span>, err);</span><br><span class="line"> <span class="keyword">return</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">"表检查/创建成功"</span>);</span><br><span class="line">});</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment">// 在服务器启动时从数据库加载消息历史记录</span></span><br><span class="line">db.all(<span class="string">"SELECT id, content, deviceName, ipAddress, timestamp FROM messages"</span>, [], <span class="function">(<span class="params">err, rows</span>) =></span> {</span><br><span class="line"> <span class="keyword">if</span> (err) {</span><br><span class="line"> <span class="built_in">console</span>.error(<span class="string">"从数据库获取历史消息出错:"</span>, err);</span><br><span class="line"> <span class="keyword">return</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 遍历数据库每条记录</span></span><br><span class="line"> rows.forEach(<span class="function"><span class="params">row</span> =></span> {</span><br><span class="line"> <span class="keyword">const</span> message = {</span><br><span class="line"> <span class="attr">id</span>: row.id, <span class="comment">// 确保这里保留 id</span></span><br><span class="line"> <span class="attr">text</span>: row.content,</span><br><span class="line"> <span class="attr">deviceName</span>: row.deviceName,</span><br><span class="line"> <span class="attr">ipAddress</span>: row.ipAddress,</span><br><span class="line"> <span class="attr">timestamp</span>: row.timestamp</span><br><span class="line"> };</span><br><span class="line"> <span class="comment">// 将数据库中的数据保存在 messageHistory</span></span><br><span class="line"> messageHistory.push(message);</span><br><span class="line"> });</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">"从数据库加载历史消息"</span>);</span><br><span class="line">});</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">app.use(express.json());</span><br><span class="line"></span><br><span class="line">app.post(<span class="string">'/send'</span>, <span class="function">(<span class="params">req, res</span>) =></span> {</span><br><span class="line"> <span class="keyword">const</span> messageContent = req.body.message;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (messageContent) {</span><br><span class="line"> <span class="comment">// 存储消息到数据库</span></span><br><span class="line"> db.run(<span class="string">"INSERT INTO messages (content, deviceName, ipAddress, timestamp) VALUES (?, ?, ?, ?)"</span>, [data.text, data.deviceName, clientIpAddress, data.timestamp],</span><br><span class="line"> <span class="function">(<span class="params">err</span>) =></span> {</span><br><span class="line"> <span class="keyword">if</span> (err) {</span><br><span class="line"> <span class="built_in">console</span>.error(<span class="string">"插入数据库出错:"</span>, err);</span><br><span class="line"> <span class="keyword">return</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'收到的信息:'</span>, data.text || messageContent);</span><br><span class="line"> });</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> res.status(<span class="number">400</span>).send({<span class="attr">error</span>: <span class="string">"消息内容为空"</span>});</span><br><span class="line"> }</span><br><span class="line">});</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">app.use(<span class="string">'/socket.io'</span>, express.static(<span class="string">'node_modules/socket.io/client-dist/'</span>));</span><br><span class="line"></span><br><span class="line">app.get(<span class="string">'/'</span>, <span class="function">(<span class="params">req, res</span>) =></span> {</span><br><span class="line"> res.sendFile(__dirname + <span class="string">'/index.html'</span>);</span><br><span class="line">});</span><br><span class="line"></span><br><span class="line">io.on(<span class="string">'connection'</span>, <span class="function">(<span class="params">socket</span>) =></span> {</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'有一个用户连接'</span>);</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">function</span> <span class="title">formatMessageForEmission</span>(<span class="params">originalMessage</span>) </span>{</span><br><span class="line"> <span class="keyword">return</span> {</span><br><span class="line"> ...originalMessage,</span><br><span class="line"> <span class="attr">deviceName</span>: <span class="string">`<span class="subst">${originalMessage.deviceName}</span> (<span class="subst">${originalMessage.ipAddress}</span>)`</span></span><br><span class="line"> };</span><br><span class="line"></span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 发送历史消息给新连接的用户</span></span><br><span class="line"> messageHistory.forEach(<span class="function">(<span class="params">message</span>) =></span> {</span><br><span class="line"> socket.emit(<span class="string">'receive text'</span>, formatMessageForEmission(message));</span><br><span class="line"> <span class="comment">// console.log("format的设备和IP地址:",message)</span></span><br><span class="line"> });</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"> socket.on(<span class="string">'send text'</span>, <span class="function">(<span class="params">data</span>) =></span> {</span><br><span class="line"> <span class="comment">// 获取客户端的IP地址</span></span><br><span class="line"> <span class="keyword">let</span> clientIpAddress = socket.request.connection.remoteAddress.replace(<span class="regexp">/^::ffff:/</span>, <span class="string">''</span>); <span class="comment">// 清除IPv6前缀</span></span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (clientIpAddress === <span class="string">'::1'</span> || clientIpAddress === <span class="string">'127.0.0.1'</span>) {</span><br><span class="line"> clientIpAddress = <span class="string">'Localhost'</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 添加ipAddress字段到data对象</span></span><br><span class="line"> data.ipAddress = clientIpAddress;</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 默认设备名称</span></span><br><span class="line"> <span class="keyword">if</span> (!data.deviceName || data.deviceName.trim() === <span class="string">""</span>) {</span><br><span class="line"> data.deviceName = <span class="string">"匿名设备"</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 添加消息发送时间</span></span><br><span class="line"> data.timestamp = <span class="keyword">new</span> <span class="built_in">Date</span>().toLocaleString();</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 添加消息到数据库中</span></span><br><span class="line"> db.run(<span class="string">"INSERT INTO messages (content, deviceName, ipAddress, timestamp) VALUES (?, ?, ?, ?)"</span>,</span><br><span class="line"> [data.text, data.deviceName, clientIpAddress, data.timestamp],</span><br><span class="line"> <span class="function"><span class="keyword">function</span>(<span class="params">err</span>) </span>{ <span class="comment">// 使用函数关键字以便访问 this.lastID</span></span><br><span class="line"> <span class="keyword">if</span> (err) {</span><br><span class="line"> <span class="built_in">console</span>.error(<span class="string">"插入数据库出错:"</span>, err);</span><br><span class="line"> <span class="keyword">return</span>;</span><br><span class="line"> }</span><br><span class="line"> data.id = <span class="built_in">this</span>.lastID; <span class="comment">// 获取新插入的行的 ID</span></span><br><span class="line"> messageHistory.push(data); <span class="comment">// 将新消息添加到历史记录</span></span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'通过套接字收到的消息:'</span>, data.text);</span><br><span class="line"> }</span><br><span class="line"> );</span><br><span class="line"></span><br><span class="line"> io.emit(<span class="string">'receive text'</span>, formatMessageForEmission(data));</span><br><span class="line"> });</span><br><span class="line"></span><br><span class="line"> socket.on(<span class="string">'disconnect'</span>, <span class="function">() =></span> {</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'有一个用户断开连接'</span>);</span><br><span class="line"> });</span><br><span class="line">});</span><br><span class="line"></span><br><span class="line"><span class="comment">// 注意:仅使用一个侦听器,app.listen 或 server.listen</span></span><br><span class="line">server.listen(<span class="number">3000</span>, <span class="function">() =></span> {</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'服务器地址:http://localhost:3000'</span>);</span><br><span class="line">});</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> storage = multer.diskStorage({</span><br><span class="line"> <span class="attr">destination</span>: <span class="function">(<span class="params">req, file, cb</span>) =></span> {</span><br><span class="line"> cb(<span class="literal">null</span>, <span class="string">'uploads/'</span>)</span><br><span class="line"> },</span><br><span class="line"> <span class="attr">filename</span>: <span class="function">(<span class="params">req, file, cb</span>) =></span> {</span><br><span class="line"> cb(<span class="literal">null</span>, <span class="built_in">Date</span>.now() + path.extname(file.originalname)) </span><br><span class="line"> }</span><br><span class="line">});</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> upload = multer({ <span class="attr">storage</span>: storage });</span><br><span class="line"></span><br><span class="line">app.post(<span class="string">'/upload-image'</span>, upload.single(<span class="string">'image'</span>), <span class="function">(<span class="params">req, res</span>) =></span> {</span><br><span class="line"> <span class="keyword">let</span> imageUrl = <span class="string">`/uploads/<span class="subst">${req.file.filename}</span>`</span>;</span><br><span class="line"> res.json({ <span class="attr">text</span>: req.body.text, <span class="attr">deviceName</span>: req.body.deviceName, <span class="attr">imageUrl</span>: imageUrl });</span><br><span class="line">});</span><br><span class="line"></span><br><span class="line">app.use(<span class="string">'/uploads'</span>, express.static(path.join(__dirname, <span class="string">'uploads'</span>)));</span><br><span class="line"></span><br><span class="line"><span class="comment">// 静态文件处理</span></span><br><span class="line">app.use(<span class="string">'/css'</span>, express.static(path.join(__dirname, <span class="string">'css'</span>)));</span><br><span class="line">app.use(<span class="string">'/js'</span>, express.static(path.join(__dirname, <span class="string">'js'</span>)));</span><br><span class="line">app.use(<span class="string">'/'</span>, express.static(path.join(__dirname, <span class="string">'JetBrainsMono-Regular.ttf'</span>)));</span><br><span class="line">app.use(<span class="string">'/'</span>, express.static(path.join(__dirname, <span class="string">'JetBrainsMono-Regular.woff2'</span>)));</span><br><span class="line"></span><br><span class="line">app.get(<span class="string">'/'</span>, <span class="function">(<span class="params">req, res</span>) =></span> {</span><br><span class="line"> res.sendFile(path.join(__dirname, <span class="string">'index.html'</span>));</span><br><span class="line">});</span><br><span class="line"></span><br><span class="line">app.delete(<span class="string">'/message/:id'</span>, <span class="function">(<span class="params">req, res</span>) =></span> {</span><br><span class="line"> <span class="keyword">const</span> messageId = req.params.id;</span><br><span class="line"> <span class="keyword">if</span> (messageId) {</span><br><span class="line"> db.run(<span class="string">"DELETE FROM messages WHERE id=?"</span>, [messageId], <span class="function">(<span class="params">err</span>) =></span> {</span><br><span class="line"> <span class="keyword">if</span> (err) {</span><br><span class="line"> <span class="built_in">console</span>.error(<span class="string">"从数据库删除消息出错:"</span>, err);</span><br><span class="line"> res.status(<span class="number">500</span>).send({<span class="attr">error</span>: <span class="string">"删除消息失败"</span>});</span><br><span class="line"> <span class="keyword">return</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 从 messageHistory 数组中删除消息</span></span><br><span class="line"> <span class="keyword">const</span> index = messageHistory.findIndex(<span class="function"><span class="params">message</span> =></span> message.id === <span class="built_in">Number</span>(messageId));</span><br><span class="line"> <span class="keyword">if</span> (index !== -<span class="number">1</span>) {</span><br><span class="line"> messageHistory.splice(index, <span class="number">1</span>);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> res.send({<span class="attr">status</span>: <span class="string">"消息已删除"</span>});</span><br><span class="line"> });</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> res.status(<span class="number">400</span>).send({<span class="attr">error</span>: <span class="string">"无效的消息ID"</span>});</span><br><span class="line"> }</span><br><span class="line">});</span><br></pre></td></tr></tbody></table></figure><p><strong>启动服务器</strong></p><figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">node server.js</span><br></pre></td></tr></tbody></table></figure><h3 id="0x4-3-直接下载已打包好的"><a href="#0x4-3-直接下载已打包好的" class="headerlink" title="0x4.3 直接下载已打包好的"></a>0x4.3 直接下载已打包好的</h3><p>避免重复上传已打包文件参考:<a class="link" href="https://www.52pojie.cn/thread-1835478-1-1.html">局域网文件和消息传输<i class="fas fa-external-link-alt"></i></a></p><p>请确保您具有<a class="link" href="https://www.52pojie.cn/">吾爱破解<i class="fas fa-external-link-alt"></i></a>的账号,且用户组为<strong>锋芒初露</strong>,本帖的权限设置旨在防止第三方网站未经告知私自获取</p><p align="center" style="background-color:#222222; font-size: 2em;"><span style="color:yellow;">要继续学习请往下看</span></p><h2 id="0x5-界面图像优化与适配"><a href="#0x5-界面图像优化与适配" class="headerlink" title="0x5 界面图像优化与适配"></a>0x5 界面图像优化与适配</h2><h3 id="0x5-1-消息展示与设备自适应"><a href="#0x5-1-消息展示与设备自适应" class="headerlink" title="0x5.1 消息展示与设备自适应"></a>0x5.1 消息展示与设备自适应</h3><h4 id="0x5-1-1-设备信息消息中心"><a href="#0x5-1-1-设备信息消息中心" class="headerlink" title="0x5.1.1 设备信息消息中心"></a>0x5.1.1 设备信息消息中心</h4><ul><li><strong>美化布局</strong>:使用简单的内联CSS让布局居中</li><li><strong>显示多条消息</strong>:而不是只覆盖显示一条</li><li><strong>显示设备名称/IP</strong>:为简化起见,将让用户输入一个设备名称。关于IP地址,我们可以从请求头中获取</li></ul><p>更新<code>index.html</code>如下:</p><figure class="highlight html"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta"><!doctype <span class="meta-keyword">html</span>></span></span><br><span class="line"><span class="tag"><<span class="name">html</span>></span></span><br><span class="line"><span class="tag"><<span class="name">head</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">title</span>></span>Realtime Text<span class="tag"></<span class="name">title</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">style</span>></span><span class="css"></span></span><br><span class="line"><span class="css"> <span class="selector-tag">body</span> {</span></span><br><span class="line"><span class="css"> <span class="attribute">font-family</span>: Arial, sans-serif;</span></span><br><span class="line"><span class="css"> <span class="attribute">display</span>: flex;</span></span><br><span class="line"><span class="css"> <span class="attribute">justify-content</span>: center;</span></span><br><span class="line"><span class="css"> <span class="attribute">align-items</span>: center;</span></span><br><span class="line"><span class="css"> <span class="attribute">height</span>: <span class="number">100vh</span>;</span></span><br><span class="line"><span class="css"> <span class="attribute">background-color</span>: <span class="number">#f4f4f4</span>;</span></span><br><span class="line"><span class="css"> }</span></span><br><span class="line"><span class="css"> <span class="selector-class">.container</span> {</span></span><br><span class="line"><span class="css"> <span class="attribute">padding</span>: <span class="number">20px</span>;</span></span><br><span class="line"><span class="css"> <span class="attribute">border</span>: <span class="number">1px</span> solid <span class="number">#ddd</span>;</span></span><br><span class="line"><span class="css"> <span class="attribute">background-color</span>: <span class="number">#fff</span>;</span></span><br><span class="line"><span class="css"> }</span></span><br><span class="line"><span class="css"> </span><span class="tag"></<span class="name">style</span>></span></span><br><span class="line"><span class="tag"></<span class="name">head</span>></span></span><br><span class="line"><span class="tag"><<span class="name">body</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">div</span> <span class="attr">class</span>=<span class="string">"container"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">input</span> <span class="attr">id</span>=<span class="string">"deviceName"</span> <span class="attr">placeholder</span>=<span class="string">"Enter Device Name"</span> /></span></span><br><span class="line"> <span class="tag"><<span class="name">textarea</span> <span class="attr">id</span>=<span class="string">"textArea"</span> <span class="attr">rows</span>=<span class="string">"4"</span> <span class="attr">cols</span>=<span class="string">"50"</span> <span class="attr">placeholder</span>=<span class="string">"Type your message..."</span>></span><span class="tag"></<span class="name">textarea</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">button</span> <span class="attr">id</span>=<span class="string">"sendButton"</span>></span>Send<span class="tag"></<span class="name">button</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">hr</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">h3</span>></span>Received Messages:<span class="tag"></<span class="name">h3</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">ul</span> <span class="attr">id</span>=<span class="string">"messages"</span>></span><span class="tag"></<span class="name">ul</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">div</span>></span></span><br><span class="line"></span><br><span class="line"> <span class="tag"><<span class="name">script</span> <span class="attr">src</span>=<span class="string">"/socket.io/socket.io.js"</span>></span><span class="tag"></<span class="name">script</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">script</span>></span><span class="javascript"></span></span><br><span class="line"><span class="javascript"> <span class="keyword">var</span> socket = io();</span></span><br><span class="line"><span class="javascript"></span></span><br><span class="line"><span class="javascript"> <span class="built_in">document</span>.getElementById(<span class="string">'sendButton'</span>).onclick = <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>{</span></span><br><span class="line"><span class="javascript"> <span class="keyword">var</span> text = <span class="built_in">document</span>.getElementById(<span class="string">'textArea'</span>).value;</span></span><br><span class="line"><span class="javascript"> <span class="keyword">var</span> deviceName = <span class="built_in">document</span>.getElementById(<span class="string">'deviceName'</span>).value || <span class="string">"Unknown"</span>;</span></span><br><span class="line"><span class="javascript"> socket.emit(<span class="string">'send text'</span>, { text, deviceName });</span></span><br><span class="line"><span class="javascript"> };</span></span><br><span class="line"><span class="javascript"></span></span><br><span class="line"><span class="javascript"> socket.on(<span class="string">'receive text'</span>, <span class="function"><span class="keyword">function</span>(<span class="params">data</span>) </span>{</span></span><br><span class="line"><span class="javascript"> <span class="keyword">var</span> li = <span class="built_in">document</span>.createElement(<span class="string">"li"</span>);</span></span><br><span class="line"><span class="javascript"> li.innerText = <span class="string">`[<span class="subst">${data.deviceName}</span>]: <span class="subst">${data.text}</span>`</span>;</span></span><br><span class="line"><span class="javascript"> <span class="built_in">document</span>.getElementById(<span class="string">'messages'</span>).appendChild(li);</span></span><br><span class="line"><span class="javascript"> });</span></span><br><span class="line"><span class="javascript"> </span><span class="tag"></<span class="name">script</span>></span></span><br><span class="line"><span class="tag"></<span class="name">body</span>></span></span><br><span class="line"><span class="tag"></<span class="name">html</span>></span></span><br></pre></td></tr></tbody></table></figure><p>在<code>server.js</code>中,我们也要做相应的修改以支持新的消息格式并发送设备的IP地址:</p><figure class="highlight javascript"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">io.on(<span class="string">'connection'</span>, <span class="function">(<span class="params">socket</span>) =></span> {</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'a user connected'</span>);</span><br><span class="line"></span><br><span class="line"> socket.on(<span class="string">'send text'</span>, <span class="function">(<span class="params">data</span>) =></span> {</span><br><span class="line"> <span class="keyword">let</span> clientIpAddress = socket.request.headers[<span class="string">'x-forwarded-for'</span>] || socket.request.connection.remoteAddress;</span><br><span class="line"> data.ipAddress = clientIpAddress;</span><br><span class="line"> io.emit(<span class="string">'receive text'</span>, data);</span><br><span class="line"> });</span><br><span class="line"></span><br><span class="line"> socket.on(<span class="string">'disconnect'</span>, <span class="function">() =></span> {</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'user disconnected'</span>);</span><br><span class="line"> });</span><br><span class="line">});</span><br></pre></td></tr></tbody></table></figure><p>这样,当用户发送消息时,它们将以<code>[DeviceName]: Message</code>的格式显示,并在服务器端的console中记录IP地址。</p><p>我们可以在<code>server.js</code>中获取连接的客户端的局域网IP地址,并将其与设备名称一同发送到前端。</p><p>首先,修改<code>server.js</code>的消息处理部分以包含IP地址:</p><figure class="highlight javascript"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line">io.on(<span class="string">'connection'</span>, <span class="function">(<span class="params">socket</span>) =></span> {</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'a user connected'</span>);</span><br><span class="line"></span><br><span class="line"> socket.on(<span class="string">'send text'</span>, <span class="function">(<span class="params">data</span>) =></span> {</span><br><span class="line"> <span class="comment">// 获取客户端的IP地址,清除IPv6前缀,使其更易读</span></span><br><span class="line"> <span class="keyword">let</span> clientIpAddress = socket.request.connection.remoteAddress.replace(<span class="regexp">/^::ffff:/</span>, <span class="string">''</span>);</span><br><span class="line"><span class="comment">// 如果从运行服务器的同一台机器上连接,将会显示“Localhost”而不是::1或127.0.0.1</span></span><br><span class="line"> <span class="keyword">if</span> (clientIpAddress === <span class="string">'::1'</span> || clientIpAddress === <span class="string">'127.0.0.1'</span>) {</span><br><span class="line"> clientIpAddress = <span class="string">'Localhost'</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 默认设备名称</span></span><br><span class="line"> <span class="keyword">if</span> (!data.deviceName || data.deviceName.trim() === <span class="string">""</span>) {</span><br><span class="line"> data.deviceName = <span class="string">"匿名设备"</span>;</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 将设备名和IP地址结合在一起</span></span><br><span class="line"> data.deviceName = <span class="string">`<span class="subst">${data.deviceName}</span> (<span class="subst">${clientIpAddress}</span>)`</span>;</span><br><span class="line"></span><br><span class="line"> io.emit(<span class="string">'receive text'</span>, data);</span><br><span class="line"> });</span><br><span class="line"></span><br><span class="line"> socket.on(<span class="string">'disconnect'</span>, <span class="function">() =></span> {</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'user disconnected'</span>);</span><br><span class="line"> });</span><br><span class="line">});</span><br></pre></td></tr></tbody></table></figure><p>更新<code>index.html</code>如下:</p><figure class="highlight javascript"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> socket = io();</span><br><span class="line"></span><br><span class="line"><span class="built_in">document</span>.getElementById(<span class="string">'sendButton'</span>).onclick = <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>{</span><br><span class="line"> <span class="keyword">var</span> text = <span class="built_in">document</span>.getElementById(<span class="string">'textArea'</span>).value;</span><br><span class="line"> <span class="keyword">var</span> deviceName = <span class="built_in">document</span>.getElementById(<span class="string">'deviceName'</span>).value || <span class="string">"匿名设备"</span>;</span><br><span class="line"> socket.emit(<span class="string">'send text'</span>, { text, deviceName });</span><br><span class="line">};</span><br></pre></td></tr></tbody></table></figure><p>现在,在发送消息时,如果用户没有输入设备名称,它将显示为“匿名设备”,并且每个消息都会在设备名称后面带有IP地址。</p><p>当用户发送消息时,它们将以 <code>[DeviceName (IP Address)]: Message</code> 的格式显示。</p><h4 id="0x5-1-2-iPhone页面自适应"><a href="#0x5-1-2-iPhone页面自适应" class="headerlink" title="0x5.1.2 iPhone页面自适应"></a>0x5.1.2 iPhone页面自适应</h4><p><em>现在我希望在iPhone的浏览器中更好的显示,因为我在iPhone中需要先缩小页面,然后在移动到页面中间再放大才能继续输入消息</em></p><p>为了确保页面在移动设备上正常显示,需要添加一个<code>viewport</code>元数据标签来控制页面的缩放。</p><p>在<code>index.html</code>文件的<code><head></code>部分,添加以下<code>meta</code>标签:</p><figure class="highlight html"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">meta</span> <span class="attr">name</span>=<span class="string">"viewport"</span> <span class="attr">content</span>=<span class="string">"width=device-width, initial-scale=1.0"</span>></span></span><br></pre></td></tr></tbody></table></figure><p>所以<code><head></code>部分现在应该是这样的:</p><figure class="highlight html"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">head</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">title</span>></span>Realtime Text<span class="tag"></<span class="name">title</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">meta</span> <span class="attr">name</span>=<span class="string">"viewport"</span> <span class="attr">content</span>=<span class="string">"width=device-width, initial-scale=1.0"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">style</span>></span><span class="css"></span></span><br><span class="line"><span class="css"> <span class="selector-tag">body</span> {</span></span><br><span class="line"><span class="css"> <span class="attribute">font-family</span>: Arial, sans-serif;</span></span><br><span class="line"><span class="css"> <span class="attribute">display</span>: flex;</span></span><br><span class="line"><span class="css"> <span class="attribute">justify-content</span>: center;</span></span><br><span class="line"><span class="css"> <span class="attribute">align-items</span>: center;</span></span><br><span class="line"><span class="css"> <span class="attribute">height</span>: <span class="number">100vh</span>;</span></span><br><span class="line"><span class="css"> <span class="attribute">background-color</span>: <span class="number">#f4f4f4</span>;</span></span><br><span class="line"><span class="css"> }</span></span><br><span class="line"><span class="css"> <span class="selector-class">.container</span> {</span></span><br><span class="line"><span class="css"> <span class="attribute">padding</span>: <span class="number">20px</span>;</span></span><br><span class="line"><span class="css"> <span class="attribute">border</span>: <span class="number">1px</span> solid <span class="number">#ddd</span>;</span></span><br><span class="line"><span class="css"> <span class="attribute">background-color</span>: <span class="number">#fff</span>;</span></span><br><span class="line"><span class="css"> }</span></span><br><span class="line"><span class="css"> </span><span class="tag"></<span class="name">style</span>></span></span><br><span class="line"><span class="tag"></<span class="name">head</span>></span></span><br></pre></td></tr></tbody></table></figure><p>添加此元数据标签后,页面应该会根据设备的屏幕大小自动调整缩放,并且在iPhone等移动设备上应该可以正常显示。</p><h3 id="0x5-2-图像优化与传输"><a href="#0x5-2-图像优化与传输" class="headerlink" title="0x5.2 图像优化与传输"></a>0x5.2 图像优化与传输</h3><h4 id="0x5-2-1-图像传输逻辑"><a href="#0x5-2-1-图像传输逻辑" class="headerlink" title="0x5.2.1 图像传输逻辑"></a>0x5.2.1 图像传输逻辑</h4><ol><li>在前端添加一个文件输入以选择图片</li><li>上传图片到服务器</li><li>服务器保存图片并返回一个可访问的URL</li><li>在客户端中显示图片</li></ol><p>修改<code>index.html</code>来添加文件输入,在<code><div class="container"></code>内添加一个文件输入:</p><figure class="highlight html"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">input</span> <span class="attr">type</span>=<span class="string">"file"</span> <span class="attr">id</span>=<span class="string">"imageUpload"</span> <span class="attr">accept</span>=<span class="string">"image/*"</span>></span></span><br></pre></td></tr></tbody></table></figure><p>并修改发送按钮的逻辑来检查是否选择了图片:</p><figure class="highlight javascript"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">document</span>.getElementById(<span class="string">'sendButton'</span>).onclick = <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>{</span><br><span class="line"> <span class="keyword">var</span> text = <span class="built_in">document</span>.getElementById(<span class="string">'textArea'</span>).value;</span><br><span class="line"> <span class="keyword">var</span> deviceName = <span class="built_in">document</span>.getElementById(<span class="string">'deviceName'</span>).value || <span class="string">"匿名设备"</span>;</span><br><span class="line"> <span class="keyword">var</span> imageFile = <span class="built_in">document</span>.getElementById(<span class="string">'imageUpload'</span>).files[<span class="number">0</span>];</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (imageFile) {</span><br><span class="line"> <span class="keyword">var</span> formData = <span class="keyword">new</span> FormData();</span><br><span class="line"> formData.append(<span class="string">'image'</span>, imageFile);</span><br><span class="line"> formData.append(<span class="string">'deviceName'</span>, deviceName);</span><br><span class="line"> formData.append(<span class="string">'text'</span>, text);</span><br><span class="line"></span><br><span class="line"> fetch(<span class="string">'/upload-image'</span>, {</span><br><span class="line"> <span class="attr">method</span>: <span class="string">'POST'</span>,</span><br><span class="line"> <span class="attr">body</span>: formData</span><br><span class="line"> }).then(<span class="function"><span class="params">response</span> =></span> response.json()).then(<span class="function"><span class="params">data</span> =></span> {</span><br><span class="line"> socket.emit(<span class="string">'send text'</span>, data);</span><br><span class="line"> });</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> socket.emit(<span class="string">'send text'</span>, { text, deviceName });</span><br><span class="line"> }</span><br><span class="line">};</span><br></pre></td></tr></tbody></table></figure><p><code>multer</code>在服务器上处理图像上传,它是一个node.js中间件,用于处理<code>multipart/form-data</code>,主要用于上传文件</p><p>然后,在<code>server.js</code>中添加以下代码来处理图片上传:</p><figure class="highlight javascript"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> multer = <span class="built_in">require</span>(<span class="string">'multer'</span>);</span><br><span class="line"><span class="keyword">const</span> path = <span class="built_in">require</span>(<span class="string">'path'</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> storage = multer.diskStorage({</span><br><span class="line"> <span class="attr">destination</span>: <span class="function">(<span class="params">req, file, cb</span>) =></span> {</span><br><span class="line"> cb(<span class="literal">null</span>, <span class="string">'uploads/'</span>)</span><br><span class="line"> },</span><br><span class="line"> <span class="attr">filename</span>: <span class="function">(<span class="params">req, file, cb</span>) =></span> {</span><br><span class="line"> cb(<span class="literal">null</span>, <span class="built_in">Date</span>.now() + path.extname(file.originalname)) </span><br><span class="line"> }</span><br><span class="line">});</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> upload = multer({ <span class="attr">storage</span>: storage });</span><br><span class="line"></span><br><span class="line">app.post(<span class="string">'/upload-image'</span>, upload.single(<span class="string">'image'</span>), <span class="function">(<span class="params">req, res</span>) =></span> {</span><br><span class="line"> <span class="keyword">let</span> imageUrl = <span class="string">`/uploads/<span class="subst">${req.file.filename}</span>`</span>;</span><br><span class="line"> res.json({ <span class="attr">text</span>: req.body.text, <span class="attr">deviceName</span>: req.body.deviceName, <span class="attr">imageUrl</span>: imageUrl });</span><br><span class="line">});</span><br><span class="line"></span><br><span class="line">app.use(<span class="string">'/uploads'</span>, express.static(path.join(__dirname, <span class="string">'uploads'</span>)));</span><br></pre></td></tr></tbody></table></figure><p><strong>确保在服务器目录中创建一个名为<code>uploads</code>的文件夹来存储上传的图片</strong></p><p>修改前端显示逻辑,修改<code>socket.on('receive text', ...)</code>逻辑以处理可能的图片消息:</p><figure class="highlight javascript"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">socket.on(<span class="string">'receive text'</span>, <span class="function"><span class="keyword">function</span>(<span class="params">data</span>) </span>{</span><br><span class="line"> <span class="keyword">var</span> li = <span class="built_in">document</span>.createElement(<span class="string">"li"</span>);</span><br><span class="line"> <span class="keyword">var</span> content = <span class="string">`[<span class="subst">${data.deviceName}</span>]: <span class="subst">${data.text}</span>`</span>;</span><br><span class="line"> <span class="keyword">if</span> (data.imageUrl) {</span><br><span class="line"> content += <span class="string">`<br><img src="<span class="subst">${data.imageUrl}</span>" alt="Uploaded image" style="max-width: 300px;">`</span>;</span><br><span class="line"> }</span><br><span class="line"> li.innerHTML = content;</span><br><span class="line"> <span class="built_in">document</span>.getElementById(<span class="string">'messages'</span>).appendChild(li);</span><br><span class="line">});</span><br></pre></td></tr></tbody></table></figure><p>现在,应该能够在前端选择图片,上传到服务器,并在接收消息时看到图片显示</p><h4 id="0x5-2-2-图片显示与优化"><a href="#0x5-2-2-图片显示与优化" class="headerlink" title="0x5.2.2 图片显示与优化"></a>0x5.2.2 图片显示与优化</h4><p><em>现在我发现在发送多条消息时,发送消息的控件显示在屏幕之外时无法再继续输入消息了</em></p><p>这个问题出现的原因是,随着消息的增加,页面内容增长,但浏览器并没有自动滚动以保持输入控件在可见范围内。</p><p>为了解决这个问题,我们可以强制页面在添加新消息后滚动到底部。这样,输入框和按钮始终保持在屏幕上的可见范围内</p><p>首先更新HTML结构,我们需要对HTML的结构进行一些修改以分离消息列表和输入区域。将输入区域和按钮放入一个固定的底部工具栏中,使其始终位于屏幕底部。</p><p>在<code>index.html</code>中<code><div class="container"></code>应该更改为以下结构:</p><figure class="highlight html"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">div</span> <span class="attr">class</span>=<span class="string">"container"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">ul</span> <span class="attr">id</span>=<span class="string">"messages"</span> <span class="attr">style</span>=<span class="string">"max-height: 80vh; overflow-y: auto;"</span>></span><span class="tag"></<span class="name">ul</span>></span></span><br><span class="line"></span><br><span class="line"> <span class="tag"><<span class="name">div</span> <span class="attr">style</span>=<span class="string">"position: fixed; bottom: 0; width: 100%; background-color: white; padding: 10px;"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">input</span> <span class="attr">type</span>=<span class="string">"text"</span> <span class="attr">id</span>=<span class="string">"deviceName"</span> <span class="attr">placeholder</span>=<span class="string">"Device Name"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">textarea</span> <span class="attr">id</span>=<span class="string">"textArea"</span> <span class="attr">placeholder</span>=<span class="string">"Type your message..."</span>></span><span class="tag"></<span class="name">textarea</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">input</span> <span class="attr">type</span>=<span class="string">"file"</span> <span class="attr">id</span>=<span class="string">"imageUpload"</span> <span class="attr">accept</span>=<span class="string">"image/*"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">button</span> <span class="attr">id</span>=<span class="string">"sendButton"</span>></span>Send<span class="tag"></<span class="name">button</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">div</span>></span></span><br><span class="line"><span class="tag"></<span class="name">div</span>></span></span><br></pre></td></tr></tbody></table></figure><p>上述更改将消息列表的最大高度限制为视口的80%,并为其添加了滚动条。发送消息的工具栏被固定在底部。</p><p>添加自动滚动逻辑,在每次添加消息到列表后,执行滚动到底部的逻辑</p><p>在<code>socket.on('receive text', ...)</code>的末尾添加以下代码:</p><figure class="highlight javascript"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">document</span>.getElementById(<span class="string">'messages'</span>).scrollTop = <span class="built_in">document</span>.getElementById(<span class="string">'messages'</span>).scrollHeight;</span><br></pre></td></tr></tbody></table></figure><p>这段代码会将消息列表滚动到最底部,确保新消息总是可见的。</p><p>这些更改应该确保无论发送多少消息,输入框和按钮始终保持在屏幕上的可见范围内。</p><h5 id="消息多发导致标签被遮挡"><a href="#消息多发导致标签被遮挡" class="headerlink" title="消息多发导致标签被遮挡"></a>消息多发导致标签被遮挡</h5><p>我们需要对HTML和CSS进行调整以确保”Received Messages:”标签始终可见。</p><p>以下是如何修改的,修改HTML结构</p><p>在<code>index.html</code>中,将”Received Messages:”标签从当前的位置移到工具栏上方,并为其添加一个包围元素:</p><figure class="highlight html"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">div</span> <span class="attr">class</span>=<span class="string">"container"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">div</span> <span class="attr">id</span>=<span class="string">"header"</span> <span class="attr">style</span>=<span class="string">"background-color: white; padding: 10px;"</span>></span></span><br><span class="line"> Received Messages:</span><br><span class="line"> <span class="tag"></<span class="name">div</span>></span></span><br><span class="line"> </span><br><span class="line"> <span class="tag"><<span class="name">ul</span> <span class="attr">id</span>=<span class="string">"messages"</span> <span class="attr">style</span>=<span class="string">"max-height: 70vh; overflow-y: auto;"</span>></span><span class="tag"></<span class="name">ul</span>></span></span><br><span class="line"></span><br><span class="line"> <span class="tag"><<span class="name">div</span> <span class="attr">style</span>=<span class="string">"position: fixed; bottom: 0; width: 100%; background-color: white; padding: 10px;"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">input</span> <span class="attr">type</span>=<span class="string">"text"</span> <span class="attr">id</span>=<span class="string">"deviceName"</span> <span class="attr">placeholder</span>=<span class="string">"Device Name"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">textarea</span> <span class="attr">id</span>=<span class="string">"textArea"</span> <span class="attr">placeholder</span>=<span class="string">"Type your message..."</span>></span><span class="tag"></<span class="name">textarea</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">input</span> <span class="attr">type</span>=<span class="string">"file"</span> <span class="attr">id</span>=<span class="string">"imageUpload"</span> <span class="attr">accept</span>=<span class="string">"image/*"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">button</span> <span class="attr">id</span>=<span class="string">"sendButton"</span>></span>Send<span class="tag"></<span class="name">button</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">div</span>></span></span><br><span class="line"><span class="tag"></<span class="name">div</span>></span></span><br></pre></td></tr></tbody></table></figure><p>调整CSS样式,修改<code><ul id="messages"></code>的样式来考虑标题的高度。确保消息列表在标题和工具栏之间:</p><figure class="highlight css"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="selector-id">#messages</span> {</span><br><span class="line"> <span class="attribute">margin-top</span>: <span class="number">30px</span>;</span><br><span class="line"> <span class="attribute">max-height</span>: <span class="number">70vh</span>;</span><br><span class="line"> <span class="attribute">overflow-y</span>: auto;</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>这些更改应该确保”Received Messages:”标签不会被挡住,并且在添加消息时,列表仍然会正确滚动。</p><h3 id="0x5-3-iPhone界面与交互优化"><a href="#0x5-3-iPhone界面与交互优化" class="headerlink" title="0x5.3 iPhone界面与交互优化"></a>0x5.3 iPhone界面与交互优化</h3><h4 id="0x5-3-1-点击图片放大原尺寸"><a href="#0x5-3-1-点击图片放大原尺寸" class="headerlink" title="0x5.3.1 点击图片放大原尺寸"></a>0x5.3.1 点击图片放大原尺寸</h4><p>为了实现图片点击放大的效果,可以使用一个简单的模态窗口(modal)。当用户点击图片时,模态窗口会显示,并且在其中展示原始尺寸的图片。当用户点击模态窗口之外的任何位置或按下Esc键时,模态窗口会关闭。</p><p>在 <code>index.html</code> 的 <code><body></code> 末尾添加模态窗口的HTML结构:</p><figure class="highlight html"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"><!-- Image Modal --></span></span><br><span class="line"><span class="tag"><<span class="name">div</span> <span class="attr">id</span>=<span class="string">"imageModal"</span> <span class="attr">style</span>=<span class="string">"display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0,0,0,0.7); z-index: 9999; align-items: center; justify-content: center;"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">img</span> <span class="attr">id</span>=<span class="string">"modalImage"</span> <span class="attr">src</span>=<span class="string">""</span> <span class="attr">style</span>=<span class="string">"max-width: 90%; max-height: 90%;"</span>></span></span><br><span class="line"><span class="tag"></<span class="name">div</span>></span></span><br></pre></td></tr></tbody></table></figure><p>添加显示和关闭模态窗口的逻辑,在现有的JavaScript代码下添加以下代码:</p><figure class="highlight javascript"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">document</span>.addEventListener(<span class="string">'click'</span>, <span class="function"><span class="keyword">function</span>(<span class="params">event</span>) </span>{</span><br><span class="line"> <span class="comment">// If an image inside the messages is clicked</span></span><br><span class="line"> <span class="keyword">if</span> (event.target.tagName === <span class="string">'IMG'</span> && event.target.closest(<span class="string">'#messages'</span>)) {</span><br><span class="line"> <span class="keyword">var</span> modal = <span class="built_in">document</span>.getElementById(<span class="string">'imageModal'</span>);</span><br><span class="line"> <span class="keyword">var</span> modalImage = <span class="built_in">document</span>.getElementById(<span class="string">'modalImage'</span>);</span><br><span class="line"></span><br><span class="line"> modalImage.src = event.target.src;</span><br><span class="line"> modal.style.display = <span class="string">'flex'</span>;</span><br><span class="line"></span><br><span class="line"> modal.addEventListener(<span class="string">'click'</span>, <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>{</span><br><span class="line"> modal.style.display = <span class="string">'none'</span>;</span><br><span class="line"> });</span><br><span class="line"> }</span><br><span class="line">});</span><br><span class="line"></span><br><span class="line"><span class="comment">// Close modal on pressing 'Escape'</span></span><br><span class="line"><span class="built_in">document</span>.addEventListener(<span class="string">'keydown'</span>, <span class="function"><span class="keyword">function</span>(<span class="params">event</span>) </span>{</span><br><span class="line"> <span class="keyword">if</span> (event.key === <span class="string">'Escape'</span>) {</span><br><span class="line"> <span class="keyword">var</span> modal = <span class="built_in">document</span>.getElementById(<span class="string">'imageModal'</span>);</span><br><span class="line"> modal.style.display = <span class="string">'none'</span>;</span><br><span class="line"> }</span><br><span class="line">});</span><br></pre></td></tr></tbody></table></figure><p>这些代码首先会检查是否点击了消息列表中的图片。如果是,则显示模态窗口并设置图片的源。点击模态窗口的任何位置都会关闭它。如果按下’Escape’键,也会关闭模态窗口。</p><p>现在,当点击图片时,应该能够在模态窗口中放大显示图片,而点击模态窗口的任何位置或按下Esc键都会关闭它。</p><h4 id="0x5-3-2-iPhone浏览器send按钮超屏"><a href="#0x5-3-2-iPhone浏览器send按钮超屏" class="headerlink" title="0x5.3.2 iPhone浏览器send按钮超屏"></a>0x5.3.2 iPhone浏览器send按钮超屏</h4><p>这个问题可能是由于固定底部工具栏的内容太多或样式设置不合适,在某些屏幕尺寸或设备上导致了溢出。为了解决这个问题,我们需要对工具栏的布局和样式进行调整,确保在较小的屏幕上也能适应。</p><p>我们可以采用以下策略:</p><ol><li>将输入框和发送按钮放在一行。</li><li>调整宽度和样式以适应不同的屏幕尺寸。</li><li>使用flex布局简化样式和布局。</li></ol><p>更新HTML结构,在<code>index.html</code>中,将工具栏部分修改为:</p><figure class="highlight html"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">div</span> <span class="attr">style</span>=<span class="string">"position: fixed; bottom: 0; width: 100%; background-color: white; padding: 5px; box-shadow: 0 -2px 5px rgba(0,0,0,0.1);"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">div</span> <span class="attr">style</span>=<span class="string">"display: flex; align-items: center;"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">input</span> <span class="attr">type</span>=<span class="string">"text"</span> <span class="attr">id</span>=<span class="string">"deviceName"</span> <span class="attr">placeholder</span>=<span class="string">"Device Name"</span> <span class="attr">style</span>=<span class="string">"flex: 1; margin-right: 5px;"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">input</span> <span class="attr">type</span>=<span class="string">"file"</span> <span class="attr">id</span>=<span class="string">"imageUpload"</span> <span class="attr">accept</span>=<span class="string">"image/*"</span> <span class="attr">style</span>=<span class="string">"margin-right: 5px;"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">textarea</span> <span class="attr">id</span>=<span class="string">"textArea"</span> <span class="attr">placeholder</span>=<span class="string">"Type your message..."</span> <span class="attr">style</span>=<span class="string">"flex: 2; margin-right: 5px;"</span>></span><span class="tag"></<span class="name">textarea</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">button</span> <span class="attr">id</span>=<span class="string">"sendButton"</span>></span>Send<span class="tag"></<span class="name">button</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">div</span>></span></span><br><span class="line"><span class="tag"></<span class="name">div</span>></span></span><br></pre></td></tr></tbody></table></figure><p>调整样式,确保每个元素都有合适的宽度和间距,使其适应小屏幕。这里,我们使用了<code>flex</code>布局,让<code>deviceName</code>和<code>textArea</code>元素根据剩余空间动态调整其宽度。</p><p>现在,工具栏应该能够更好地适应较小的屏幕,如iPhone的浏览器,并且发送按钮应该始终可见和可用。可能需要根据实际设备和屏幕尺寸进行进一步的微调。</p><h4 id="0x5-3-3-iPhone输入框超出屏幕"><a href="#0x5-3-3-iPhone输入框超出屏幕" class="headerlink" title="0x5.3.3 iPhone输入框超出屏幕"></a>0x5.3.3 iPhone输入框超出屏幕</h4><p>为了在iPhone的浏览器中获得更好的响应式布局,我们可以采用以下策略:</p><ol><li>使用媒体查询来针对不同的屏幕尺寸进行样式调整。</li><li>在较小的屏幕上,考虑简化布局,可能需要将一些控件移至其他位置。</li></ol><p>更新HTML结构,在<code>index.html</code>中,继续使用上一个给出的工具栏HTML结构。</p><p>添加CSS媒体查询,我们将为小屏幕(如iPhone)添加特定的样式。为此,在的CSS中添加以下内容:</p><figure class="highlight css"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/* 大屏幕的默认样式 */</span></span><br><span class="line"><span class="selector-id">#deviceName</span>, <span class="selector-id">#textArea</span> {</span><br><span class="line"> <span class="attribute">flex</span>: <span class="number">1</span>;</span><br><span class="line"> <span class="attribute">margin-right</span>: <span class="number">5px</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">/* 小屏幕的样式(如iphone) */</span></span><br><span class="line"><span class="keyword">@media</span> (<span class="attribute">max-width</span>: <span class="number">500px</span>) {</span><br><span class="line"> <span class="selector-id">#deviceName</span> {</span><br><span class="line"> <span class="attribute">display</span>: none; <span class="comment">/* 在小屏幕上隐藏设备名称输入 */</span></span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="selector-id">#imageUpload</span> {</span><br><span class="line"> <span class="attribute">width</span>: <span class="number">40px</span>; <span class="comment">/* 根据需要调整此值 */</span></span><br><span class="line"> <span class="attribute">margin-right</span>: <span class="number">5px</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="selector-id">#textArea</span> {</span><br><span class="line"> <span class="attribute">flex</span>: <span class="number">3</span>; <span class="comment">/* 给文本区域更多的空间 */</span></span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="selector-id">#sendButton</span> {</span><br><span class="line"> <span class="attribute">flex-shrink</span>: <span class="number">0</span>; <span class="comment">/* 防止按钮收缩 */</span></span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>这里的做法是,当屏幕宽度小于或等于500px时(可以根据需要调整此值),隐藏了<code>deviceName</code>输入框以节省空间,并给予<code>textArea</code>更多的空间。此外,还调整了上传按钮和发送按钮的尺寸和样式,以更好地适应小屏幕。</p><p>这些调整应该会改进iPhone浏览器上的布局,使其更加友好,可能需要根据实际设备和屏幕尺寸进行进一步的微调</p><h4 id="0x5-3-4-iPhone按钮输入框重叠修正"><a href="#0x5-3-4-iPhone按钮输入框重叠修正" class="headerlink" title="0x5.3.4 iPhone按钮输入框重叠修正"></a>0x5.3.4 iPhone按钮输入框重叠修正</h4><p>为了实现这个目标,我们需要对较小屏幕的布局进行改进,使其变为多行排列。</p><p>更新HTML结构,在<code>index.html</code>中,继续使用上一个给出的工具栏HTML结构。</p><p>调整CSS媒体查询以实现多行排列布局,我们将在小屏幕上采用多行排列布局。添加或修改以下CSS代码:</p><figure class="highlight css"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/* 大屏幕的默认样式 */</span></span><br><span class="line"><span class="selector-id">#deviceName</span>, <span class="selector-id">#textArea</span> {</span><br><span class="line"> <span class="attribute">flex</span>: <span class="number">1</span>;</span><br><span class="line"> <span class="attribute">margin-right</span>: <span class="number">5px</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">/* 小屏幕的样式(如iphone) */</span></span><br><span class="line"><span class="keyword">@media</span> (<span class="attribute">max-width</span>: <span class="number">500px</span>) {</span><br><span class="line"> <span class="comment">/* 对于较小的屏幕,将伸缩方向更改为列 */</span></span><br><span class="line"> <span class="selector-tag">div</span><span class="selector-attr">[style*=<span class="string">"position: fixed; bottom: 0;"</span>]</span> > <span class="selector-tag">div</span> {</span><br><span class="line"> <span class="attribute">flex-direction</span>: column;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="selector-id">#deviceName</span> {</span><br><span class="line"> <span class="attribute">margin-bottom</span>: <span class="number">5px</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="selector-id">#imageUpload</span> {</span><br><span class="line"> <span class="attribute">margin-right</span>: <span class="number">0</span>; <span class="comment">/* 重置margin-right */</span></span><br><span class="line"> <span class="attribute">margin-bottom</span>: <span class="number">5px</span>; </span><br><span class="line"> <span class="attribute">width</span>: <span class="number">100%</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="selector-id">#textArea</span> {</span><br><span class="line"> <span class="attribute">margin-right</span>: <span class="number">0</span>; <span class="comment">/* 重置margin-right */</span></span><br><span class="line"> <span class="attribute">margin-bottom</span>: <span class="number">5px</span>;</span><br><span class="line"> <span class="attribute">flex</span>: <span class="number">3</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="selector-id">#sendButton</span> {</span><br><span class="line"> <span class="attribute">width</span>: <span class="number">100%</span>; </span><br><span class="line"> <span class="attribute">margin-bottom</span>: <span class="number">5px</span>; <span class="comment">/* 留出一点间距 */</span></span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>这样的改变会使得在小屏幕上,设备名称、图片上传、文本输入框和发送按钮都单独占据一行,而不会相互重叠。这应该会在iPhone上提供更好的用户体验</p><h4 id="0x5-3-5-iPhone布局对齐与点击优化"><a href="#0x5-3-5-iPhone布局对齐与点击优化" class="headerlink" title="0x5.3.5 iPhone布局对齐与点击优化"></a>0x5.3.5 iPhone布局对齐与点击优化</h4><p><strong>更新媒体查询</strong></p><figure class="highlight css"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/* 小屏幕的样式(如iphone) */</span></span><br><span class="line"><span class="keyword">@media</span> (<span class="attribute">max-width</span>: <span class="number">500px</span>) {</span><br><span class="line"> <span class="comment">/* 对于较小的屏幕,将伸缩方向更改为列 */</span></span><br><span class="line"> <span class="selector-tag">div</span><span class="selector-attr">[style*=<span class="string">"position: fixed; bottom: 0;"</span>]</span> > <span class="selector-tag">div</span> {</span><br><span class="line"> <span class="attribute">flex-direction</span>: column;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="selector-id">#deviceName</span>, <span class="selector-id">#textArea</span> {</span><br><span class="line"> <span class="attribute">margin-right</span>: <span class="number">0</span>; <span class="comment">/* 重置margin-right */</span></span><br><span class="line"> <span class="attribute">margin-bottom</span>: <span class="number">5px</span>;</span><br><span class="line"> <span class="attribute">width</span>: <span class="number">100%</span>; <span class="comment">/* 全宽 */</span></span><br><span class="line"> <span class="attribute">height</span>: <span class="number">50px</span>; <span class="comment">/* 增加高度,便于触摸 */</span></span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="selector-id">#imageUpload</span> {</span><br><span class="line"> <span class="attribute">margin-right</span>: <span class="number">0</span>; <span class="comment">/* 重置margin-right */</span></span><br><span class="line"> <span class="attribute">margin-bottom</span>: <span class="number">5px</span>; </span><br><span class="line"> <span class="attribute">width</span>: <span class="number">100%</span>; <span class="comment">/* 全宽 */</span></span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="selector-id">#sendButton</span> {</span><br><span class="line"> <span class="attribute">width</span>: <span class="number">100%</span>; <span class="comment">/* 全宽 */</span></span><br><span class="line"> <span class="attribute">height</span>: <span class="number">50px</span>; <span class="comment">/* 增加高度,便于触摸 */</span></span><br><span class="line"> <span class="attribute">margin-bottom</span>: <span class="number">5px</span>; <span class="comment">/* 留出一点间距 */</span></span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>这些调整会使得在小屏幕上,每一个元素都占满了整个屏幕宽度,并且增大了输入框和按钮的高度,以便用户更容易地进行触摸操作。</p><p>这应该会提供更好的用户体验,特别是考虑到iPhone和其他移动设备上的拇指操作。可能还需要根据实际设备和需求进行进一步的微调。</p><h4 id="0x5-3-6-控件边缘重叠与iPhone点击"><a href="#0x5-3-6-控件边缘重叠与iPhone点击" class="headerlink" title="0x5.3.6 控件边缘重叠与iPhone点击"></a>0x5.3.6 控件边缘重叠与iPhone点击</h4><ol><li>控件与屏幕边缘的间距问题</li><li>iPhone上首次加载时的交互问题</li></ol><p><strong>设置控件与屏幕边缘的间距</strong></p><p>为了增加边距,可以为工具栏内部的元素添加适当的间距。在的CSS中,可以添加或修改以下样式:</p><figure class="highlight css"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/* 为固定底部工具栏添加填充 */</span></span><br><span class="line"><span class="selector-tag">div</span><span class="selector-attr">[style*=<span class="string">"position: fixed; bottom: 0;"</span>]</span> {</span><br><span class="line"> <span class="attribute">padding-left</span>: <span class="number">10px</span>;</span><br><span class="line"> <span class="attribute">padding-right</span>: <span class="number">10px</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">/* ... (CSS的其余部分) */</span></span><br></pre></td></tr></tbody></table></figure><p>这会为底部工具栏的左右两侧增加10px的间距。可以根据需要调整这个值</p><p><strong>iPhone上首次加载时的交互问题</strong></p><p>iPhone上的Safari浏览器和其他移动浏览器有一个常见的问题,即当页面首次加载时,固定的元素可能不会响应触摸事件。这是因为浏览器的地址栏和底部工具栏可能会覆盖页面的部分内容。通常,通过滚动页面可以暂时解决这个问题。</p><p>为了解决这个问题,可以尝试在页面首次加载时自动滚动页面一小段距离,这样可以确保底部工具栏不会被浏览器的界面元素覆盖。以下是如何实现:</p><p>在<code>index.html</code>的<code><script></code>标签内部,添加以下JavaScript代码:</p><figure class="highlight javascript"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">window</span>.addEventListener(<span class="string">'load'</span>, <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>{</span><br><span class="line"> <span class="built_in">setTimeout</span>(<span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>{</span><br><span class="line"> <span class="built_in">window</span>.scrollTo(<span class="number">0</span>, <span class="number">1</span>);</span><br><span class="line"> }, <span class="number">0</span>);</span><br><span class="line">});</span><br></pre></td></tr></tbody></table></figure><p>上述代码会在页面加载完成后,立即将页面向上滚动1px。这样可以确保底部的固定工具栏不会被浏览器的界面元素覆盖,并且应该可以响应用户的触摸事件</p><h4 id="0x5-3-7-iPhone上两侧间距问题"><a href="#0x5-3-7-iPhone上两侧间距问题" class="headerlink" title="0x5.3.7 iPhone上两侧间距问题"></a>0x5.3.7 iPhone上两侧间距问题</h4><p>对于在iPhone上的边距问题,我们需要更具体地处理与小屏幕或特定设备相关的CSS样式。</p><p>下面是如何更具体地针对较小屏幕(如iPhone)设置边距:</p><p>首先,确保已经使用了适当的元数据标签来控制视口。在的HTML文件的<code><head></code>部分,添加以下标签:</p><figure class="highlight html"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">meta</span> <span class="attr">name</span>=<span class="string">"viewport"</span> <span class="attr">content</span>=<span class="string">"width=device-width, initial-scale=1.0"</span>></span></span><br></pre></td></tr></tbody></table></figure><p>这会确保页面的宽度匹配设备的宽度,并且页面的初始缩放级别是1.0。</p><p>接下来,修改的CSS媒体查询以增加边距。将下面的代码添加或合并到的CSS中:</p><figure class="highlight css"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/* 大屏幕的默认样式 */</span></span><br><span class="line"><span class="comment">/* ... (你现在的样式) */</span></span><br><span class="line"></span><br><span class="line"><span class="comment">/* 小屏幕的样式(如iphone) */</span></span><br><span class="line"><span class="keyword">@media</span> (<span class="attribute">max-width</span>: <span class="number">500px</span>) {</span><br><span class="line"> <span class="comment">/* ... (你当前的小屏幕风格) */</span></span><br><span class="line"></span><br><span class="line"> <span class="comment">/* 为整个底部工具栏添加填充 */</span></span><br><span class="line"> <span class="selector-tag">div</span><span class="selector-attr">[style*=<span class="string">"position: fixed; bottom: 0;"</span>]</span> {</span><br><span class="line"> <span class="attribute">padding</span>: <span class="number">0</span> <span class="number">10px</span>; <span class="comment">/* 这将在左右两边添加10px的内边距 */</span></span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>通过添加这些更改,iPhone上的边距应该会生效,并且页面内容会根据设备的屏幕宽度适当地调整。</p><h4 id="0x5-3-8-iPhone输入框宽度异常"><a href="#0x5-3-8-iPhone输入框宽度异常" class="headerlink" title="0x5.3.8 iPhone输入框宽度异常"></a>0x5.3.8 iPhone输入框宽度异常</h4><p>这个问题可能是由于<code>padding</code>和<code>width: 100%</code>的组合引起的。当我们为一个元素同时设置了<code>padding</code>和<code>width: 100%</code>时,该元素的总宽度会超过其父元素的宽度,因为这里的宽度是指内容宽度,而不包括边距、边框或内边距。</p><p>为了解决这个问题,可以使用<code>box-sizing</code>属性,将其设置为<code>border-box</code>,这样元素的总宽度将包括内容、内边距和边框(但不包括外边距)。</p><ol><li>保证边距。</li><li>使用<code>flexbox</code>来确保子元素在小屏幕上垂直排列。</li></ol><p>在CSS中,找到与小屏幕相关的媒体查询,并进行如下修改:</p><figure class="highlight css"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">@media</span> (<span class="attribute">max-width</span>: <span class="number">500px</span>) {</span><br><span class="line"> <span class="selector-tag">div</span><span class="selector-attr">[style*=<span class="string">"position: fixed; bottom: 0;"</span>]</span> {</span><br><span class="line"> <span class="attribute">width</span>: <span class="number">100%</span>; </span><br><span class="line"> <span class="attribute">padding</span>: <span class="number">5px</span>; </span><br><span class="line"> <span class="attribute">box-sizing</span>: border-box; </span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="selector-id">#deviceName</span>, <span class="selector-id">#textArea</span>, <span class="selector-id">#imageUpload</span>, <span class="selector-id">#sendButton</span> {</span><br><span class="line"> <span class="attribute">box-sizing</span>: border-box; </span><br><span class="line"> <span class="attribute">width</span>: <span class="number">100%</span>; </span><br><span class="line"> <span class="attribute">margin-bottom</span>: <span class="number">5px</span>; </span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="selector-id">#deviceName</span>, <span class="selector-id">#textArea</span> {</span><br><span class="line"> <span class="attribute">height</span>: <span class="number">50px</span>; </span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="selector-id">#sendButton</span> {</span><br><span class="line"> <span class="attribute">height</span>: <span class="number">50px</span>; </span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>这些样式应该确保了在小屏幕设备上,特别是iPhone上,所有的控件都正常排列,并且有足够的边距。</p><h4 id="0x5-3-9-iPhone布局校验与滑动优化"><a href="#0x5-3-9-iPhone布局校验与滑动优化" class="headerlink" title="0x5.3.9 iPhone布局校验与滑动优化"></a>0x5.3.9 iPhone布局校验与滑动优化</h4><ol><li><strong>控件应该按照一行行排列</strong>: 这需要调整底部控件的样式。当前的代码中已经使用了<code>flex-direction: column</code>,这是正确的,但的底部控件的子控件仍然在一个flex容器中,这使得它们在同一行上。需要分离它们以使它们在单独的行上。</li><li><strong>两侧与屏幕保持10px的间距</strong>: 可以在底部控件中设置<code>padding-left</code>和<code>padding-right</code>为<code>10px</code>。</li><li><strong>第一次打开页面需要滚动一小段距离</strong>: 已经有了这个功能,使用<code>window.scrollTo(0, 1)</code>在页面加载时向下滚动。这通常用于隐藏浏览器的地址栏,但在最新版本的iOS中可能不起作用。</li></ol><figure class="highlight css"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/* ... 现有的样式 ... */</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">@media</span> (<span class="attribute">max-width</span>: <span class="number">500px</span>) {</span><br><span class="line"> <span class="comment">/* ... 其他样式 ... */</span></span><br><span class="line"></span><br><span class="line"> <span class="selector-tag">div</span><span class="selector-attr">[style*=<span class="string">"position: fixed; bottom: 0;"</span>]</span> {</span><br><span class="line"> <span class="attribute">padding-left</span>: <span class="number">10px</span>;</span><br><span class="line"> <span class="attribute">padding-right</span>: <span class="number">10px</span>;</span><br><span class="line"> <span class="attribute">display</span>: flex;</span><br><span class="line"> <span class="attribute">flex-direction</span>: column; </span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="selector-id">#deviceName</span>, <span class="selector-id">#textArea</span>, <span class="selector-id">#imageUpload</span>, <span class="selector-id">#sendButton</span> {</span><br><span class="line"> <span class="attribute">width</span>: <span class="number">100%</span>; </span><br><span class="line"> <span class="attribute">box-sizing</span>: border-box; </span><br><span class="line"> <span class="attribute">margin-bottom</span>: <span class="number">5px</span>; </span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="selector-id">#deviceName</span>, <span class="selector-id">#textArea</span>, <span class="selector-id">#sendButton</span> {</span><br><span class="line"> <span class="attribute">height</span>: <span class="number">50px</span>;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><figure class="highlight html"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"><!-- ... 现有的HTML ... --></span></span><br><span class="line"></span><br><span class="line"><span class="comment"><!-- 调整底部工具栏结构 --></span></span><br><span class="line"><span class="tag"><<span class="name">div</span> <span class="attr">style</span>=<span class="string">"position: fixed; bottom: 0; width: 100%; background-color: white; padding: 5px; box-shadow: 0 -2px 5px rgba(0,0,0,0.1);"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">input</span> <span class="attr">type</span>=<span class="string">"text"</span> <span class="attr">id</span>=<span class="string">"deviceName"</span> <span class="attr">placeholder</span>=<span class="string">"Device Name"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">input</span> <span class="attr">type</span>=<span class="string">"file"</span> <span class="attr">id</span>=<span class="string">"imageUpload"</span> <span class="attr">accept</span>=<span class="string">"image/*"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">textarea</span> <span class="attr">id</span>=<span class="string">"textArea"</span> <span class="attr">placeholder</span>=<span class="string">"Type your message..."</span>></span><span class="tag"></<span class="name">textarea</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">button</span> <span class="attr">id</span>=<span class="string">"sendButton"</span>></span>Send<span class="tag"></<span class="name">button</span>></span></span><br><span class="line"><span class="tag"></<span class="name">div</span>></span></span><br></pre></td></tr></tbody></table></figure><h4 id="0x5-3-A-iPhone布局遮挡与间距修正"><a href="#0x5-3-A-iPhone布局遮挡与间距修正" class="headerlink" title="0x5.3.A iPhone布局遮挡与间距修正"></a>0x5.3.A iPhone布局遮挡与间距修正</h4><ol><li><strong>两侧控件的距离问题</strong>: 如果右侧和屏幕之间的距离是10px,但左侧并非如此,可能是某些样式或其他浏览器默认样式干扰了。可以考虑为每个子控件设置<code>margin-left</code>和<code>margin-right</code>,以确保他们都有正确的边距。</li><li><strong>图片消息被底部的输入框挡住</strong>: 一个常见的方法是为消息列表添加底部内边距,该边距的高度与底部工具栏的高度相同,这样最后一条消息(无论是文本还是图片)都可以完全滚动到视图中而不被挡住。</li></ol><figure class="highlight css"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/* ... 其他样式 ... */</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">@media</span> (<span class="attribute">max-width</span>: <span class="number">500px</span>) {</span><br><span class="line"> <span class="comment">/* ... 其他样式 ... */</span></span><br><span class="line"></span><br><span class="line"> <span class="selector-id">#deviceName</span>, <span class="selector-id">#textArea</span>, <span class="selector-id">#imageUpload</span>, <span class="selector-id">#sendButton</span> {</span><br><span class="line"> <span class="attribute">margin-left</span>: <span class="number">10px</span>;</span><br><span class="line"> <span class="attribute">margin-right</span>: <span class="number">10px</span>;</span><br><span class="line"> <span class="comment">/* ... 现有的其他样式 ... */</span></span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="selector-id">#messages</span> {</span><br><span class="line"> <span class="attribute">padding-bottom</span>: <span class="number">70px</span>; </span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><h3 id="0x5-4-消息发送与设备名自动填充"><a href="#0x5-4-消息发送与设备名自动填充" class="headerlink" title="0x5.4 消息发送与设备名自动填充"></a>0x5.4 消息发送与设备名自动填充</h3><h4 id="0x5-4-1-发送后清除已选图像"><a href="#0x5-4-1-发送后清除已选图像" class="headerlink" title="0x5.4.1 发送后清除已选图像"></a>0x5.4.1 发送后清除已选图像</h4><p>为了在发送完消息后清除上次选择的图像,可以在发送消息的函数中清除 <code><input type="file"></code> 的值。这将重置文件输入,并确保下次用户点击发送按钮时不会再次发送同一张图片。</p><figure class="highlight javascript"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">document</span>.getElementById(<span class="string">'sendButton'</span>).onclick = <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>{</span><br><span class="line"> <span class="keyword">var</span> text = <span class="built_in">document</span>.getElementById(<span class="string">'textArea'</span>).value;</span><br><span class="line"> <span class="keyword">var</span> deviceName = <span class="built_in">document</span>.getElementById(<span class="string">'deviceName'</span>).value || <span class="string">"匿名设备"</span>;</span><br><span class="line"> <span class="keyword">var</span> imageFile = <span class="built_in">document</span>.getElementById(<span class="string">'imageUpload'</span>).files[<span class="number">0</span>];</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (imageFile) {</span><br><span class="line"> <span class="keyword">var</span> formData = <span class="keyword">new</span> FormData();</span><br><span class="line"> formData.append(<span class="string">'image'</span>, imageFile);</span><br><span class="line"> formData.append(<span class="string">'deviceName'</span>, deviceName);</span><br><span class="line"> formData.append(<span class="string">'text'</span>, text);</span><br><span class="line"></span><br><span class="line"> fetch(<span class="string">'/upload-image'</span>, {</span><br><span class="line"> <span class="attr">method</span>: <span class="string">'POST'</span>,</span><br><span class="line"> <span class="attr">body</span>: formData</span><br><span class="line"> }).then(<span class="function"><span class="params">response</span> =></span> response.json()).then(<span class="function"><span class="params">data</span> =></span> {</span><br><span class="line"> socket.emit(<span class="string">'send text'</span>, data);</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 在此处清除文件输入的值</span></span><br><span class="line"> <span class="built_in">document</span>.getElementById(<span class="string">'imageUpload'</span>).value = <span class="string">""</span>;</span><br><span class="line"> });</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> socket.emit(<span class="string">'send text'</span>, { text, deviceName });</span><br><span class="line"> }</span><br><span class="line">};</span><br></pre></td></tr></tbody></table></figure><p>以上的修改确保了每次发送图像消息后,文件输入都会被清除。这样,用户在发送下一条消息时,之前选择的图片不会再次被发送</p><h4 id="0x5-4-2-自动填充设备名称"><a href="#0x5-4-2-自动填充设备名称" class="headerlink" title="0x5.4.2 自动填充设备名称"></a>0x5.4.2 自动填充设备名称</h4><p>为了自动获取设备名称,首先需要明确一个事实:Web 浏览器出于隐私和安全的考虑,并不允许直接获取设备的真实名称。但是,可以使用一些方法来近似识别或标识设备,例如使用浏览器的用户代理字符串。</p><p>用户代理 (User Agent) 字符串包含关于浏览器、操作系统和设备的信息,可以通过这些信息来推断或标识用户的设备类型</p><figure class="highlight javascript"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">getDeviceNameFromUserAgent</span>(<span class="params"></span>) </span>{</span><br><span class="line"> <span class="keyword">var</span> userAgent = navigator.userAgent || navigator.vendor || <span class="built_in">window</span>.opera;</span><br><span class="line"></span><br><span class="line"> <span class="comment">// Windows Phone必须先行一步,因为它的用户界面也包含“Android”。</span></span><br><span class="line"> <span class="keyword">if</span> (<span class="regexp">/windows phone/i</span>.test(userAgent)) {</span><br><span class="line"> <span class="keyword">return</span> <span class="string">"Windows Phone"</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (<span class="regexp">/android/i</span>.test(userAgent)) {</span><br><span class="line"> <span class="keyword">return</span> <span class="string">"Android Device"</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// iOS检测</span></span><br><span class="line"> <span class="keyword">if</span> (<span class="regexp">/iPad|iPhone|iPod/</span>.test(userAgent) && !<span class="built_in">window</span>.MSStream) {</span><br><span class="line"> <span class="keyword">return</span> <span class="string">"iOS Device"</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (<span class="regexp">/macintosh|mac os x/i</span>.test(userAgent)) {</span><br><span class="line"> <span class="keyword">return</span> <span class="string">"Mac"</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (<span class="regexp">/windows/i</span>.test(userAgent)) {</span><br><span class="line"> <span class="keyword">return</span> <span class="string">"Windows PC"</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> <span class="string">"Unknown Device"</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 使用函数来自动填充设备名称</span></span><br><span class="line"><span class="built_in">document</span>.getElementById(<span class="string">'deviceName'</span>).value = getDeviceNameFromUserAgent();</span><br></pre></td></tr></tbody></table></figure><p>此函数根据用户代理字符串来近似地识别设备,并返回一个描述性名称。注意,这只是一个基本的示例,而真实的环境中可能需要更复杂的逻辑来准确识别设备。</p><p>这种方法只是提供了一个近似的设备名称,并不能真正的识别具体的设备型号或品牌</p><h4 id="0x5-4-3-消息发送后自动清空输入框"><a href="#0x5-4-3-消息发送后自动清空输入框" class="headerlink" title="0x5.4.3 消息发送后自动清空输入框"></a>0x5.4.3 消息发送后自动清空输入框</h4><p>为了在发送完消息后自动清除 <code>textArea</code> (你之前的代码中使用这个ID代表消息输入框)中的值,你可以在发送消息的函数中添加一行代码来实现这个功能</p><figure class="highlight javascript"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">document</span>.getElementById(<span class="string">'sendButton'</span>).onclick = <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>{</span><br><span class="line"> <span class="keyword">var</span> text = <span class="built_in">document</span>.getElementById(<span class="string">'textArea'</span>).value;</span><br><span class="line"> <span class="keyword">var</span> deviceName = <span class="built_in">document</span>.getElementById(<span class="string">'deviceName'</span>).value || <span class="string">"匿名设备"</span>;</span><br><span class="line"> <span class="keyword">var</span> imageFile = <span class="built_in">document</span>.getElementById(<span class="string">'imageUpload'</span>).files[<span class="number">0</span>];</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 如果选择了图片,则发送图片</span></span><br><span class="line"> <span class="keyword">if</span> (imageFile) {</span><br><span class="line"> <span class="keyword">var</span> formData = <span class="keyword">new</span> FormData();</span><br><span class="line"> formData.append(<span class="string">'image'</span>, imageFile);</span><br><span class="line"> formData.append(<span class="string">'deviceName'</span>, deviceName);</span><br><span class="line"> formData.append(<span class="string">'text'</span>, text);</span><br><span class="line"></span><br><span class="line"> fetch(<span class="string">'/upload-image'</span>, {</span><br><span class="line"> <span class="attr">method</span>: <span class="string">'POST'</span>,</span><br><span class="line"> <span class="attr">body</span>: formData</span><br><span class="line"> }).then(<span class="function"><span class="params">response</span> =></span> response.json()).then(<span class="function"><span class="params">data</span> =></span> {</span><br><span class="line"> socket.emit(<span class="string">'send text'</span>, data);</span><br><span class="line"> });</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="comment">// 如果没有选择图片,则直接发送文本消息</span></span><br><span class="line"> socket.emit(<span class="string">'send text'</span>, { text, deviceName });</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 在所有情况下都清除textArea和图片选择器的值</span></span><br><span class="line"> <span class="built_in">document</span>.getElementById(<span class="string">'textArea'</span>).value = <span class="string">''</span>;</span><br><span class="line"> <span class="built_in">document</span>.getElementById(<span class="string">'imageUpload'</span>).value = <span class="string">''</span>; <span class="comment">// 这将清除文件输入选择器的内容</span></span><br><span class="line">};</span><br></pre></td></tr></tbody></table></figure><p>我在函数的底部添加了清除<code>textArea</code>和图片选择器<code>imageUpload</code>的代码。这样,无论是否选择了图片,都会在发送完消息后清除文本框和图片选择器的内容。</p><h3 id="0x5-5-大屏适配与控件布局调整"><a href="#0x5-5-大屏适配与控件布局调整" class="headerlink" title="0x5.5 大屏适配与控件布局调整"></a>0x5.5 大屏适配与控件布局调整</h3><h4 id="0x5-5-1-Windows居中-iPhone逐行"><a href="#0x5-5-1-Windows居中-iPhone逐行" class="headerlink" title="0x5.5.1 Windows居中 iPhone逐行"></a>0x5.5.1 Windows居中 iPhone逐行</h4><p>修改CSS以在大屏幕上对底部控件进行水平居中:</p><figure class="highlight css"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="selector-class">.bottom-controls</span> {</span><br><span class="line"> <span class="attribute">display</span>: flex;</span><br><span class="line"> <span class="attribute">justify-content</span>: center; <span class="comment">/* 内容器水平居中 */</span></span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="selector-class">.bottom-controls</span> > <span class="selector-tag">div</span> {</span><br><span class="line"> <span class="attribute">display</span>: flex; <span class="comment">/* 这确保了内部容器的子元素水平对齐 */</span></span><br><span class="line"> <span class="attribute">align-items</span>: center;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">/* ... 其他样式 ... */</span></span><br><span class="line"></span><br></pre></td></tr></tbody></table></figure><p>使用媒体查询来特定地为小屏幕(例如iPhone)定义样式:</p><figure class="highlight css"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/* 小屏幕的样式(如iphone) */</span></span><br><span class="line"><span class="keyword">@media</span> (<span class="attribute">max-width</span>: <span class="number">500px</span>) {</span><br><span class="line"> <span class="selector-class">.bottom-controls</span> {</span><br><span class="line"> <span class="attribute">justify-content</span>: flex-start; <span class="comment">/* 这将使内部容器与起始位置对齐 */</span></span><br><span class="line"> }</span><br><span class="line"> <span class="selector-class">.bottom-controls</span> > <span class="selector-tag">div</span> {</span><br><span class="line"> <span class="attribute">flex-direction</span>: column; <span class="comment">/* 这确保了内部容器的子容器是垂直堆叠的 */</span></span><br><span class="line"> <span class="attribute">align-items</span>: center;</span><br><span class="line"> <span class="attribute">width</span>: <span class="number">100%</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="selector-id">#deviceName</span>, <span class="selector-id">#textArea</span>, <span class="selector-id">#imageUpload</span>, <span class="selector-id">#sendButton</span> {</span><br><span class="line"> <span class="attribute">display</span>: block;</span><br><span class="line"> <span class="attribute">width</span>: <span class="number">100%</span>; <span class="comment">/* 确保控件在小屏幕上占满宽度 */</span></span><br><span class="line"> }</span><br><span class="line"> <span class="comment">/* ... 你的其他小屏幕风格 ... */</span></span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>这样,底部的控件在大屏幕上会水平居中,而在小屏幕(如iPhone)上,则会按照行排列。</p><h4 id="0x5-5-2-扩大大屏幕底部控件尺寸"><a href="#0x5-5-2-扩大大屏幕底部控件尺寸" class="headerlink" title="0x5.5.2 扩大大屏幕底部控件尺寸"></a>0x5.5.2 扩大大屏幕底部控件尺寸</h4><p>为了在大屏幕上使控件更大,可以使用媒体查询来区分大屏幕和小屏幕的样式。在大屏幕的样式中,可以增加控件的高度和字体大小。</p><figure class="highlight css"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/* 大屏幕的样式(例如,Windows, MacBook) */</span></span><br><span class="line"><span class="keyword">@media</span> (<span class="attribute">min-width</span>: <span class="number">501px</span>) {</span><br><span class="line"> <span class="selector-id">#deviceName</span>, <span class="selector-id">#textArea</span>, <span class="selector-id">#imageUpload</span>, <span class="selector-id">#sendButton</span> {</span><br><span class="line"> <span class="attribute">height</span>: <span class="number">60px</span>; <span class="comment">/* 增加控件的高度 */</span></span><br><span class="line"> <span class="attribute">font-size</span>: <span class="number">18px</span>; <span class="comment">/* 增加字体大小以获得更好的可视性 */</span></span><br><span class="line"> <span class="attribute">padding</span>: <span class="number">10px</span>; <span class="comment">/* 为美观添加一些填充 */</span></span><br><span class="line"> }</span><br><span class="line"> <span class="selector-id">#sendButton</span> {</span><br><span class="line"> <span class="attribute">padding</span>: <span class="number">10px</span> <span class="number">20px</span>; <span class="comment">/* 调整按钮的填充 */</span></span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>这段代码将确保在宽度超过501px的屏幕上,控件的高度、字体大小和内部间距都增加了,从而更适合大屏幕使用。可以根据实际需要进一步调整这些值。</p><h4 id="0x5-5-3-大屏幕输入框长度及控件间距"><a href="#0x5-5-3-大屏幕输入框长度及控件间距" class="headerlink" title="0x5.5.3 大屏幕输入框长度及控件间距"></a>0x5.5.3 大屏幕输入框长度及控件间距</h4><p>为了在大屏幕上调整输入框的长度并在输入框和按钮之间添加合适的距离,可以进行以下修改:</p><ol><li>在大屏幕上为输入框和按钮添加合适的间距</li><li>为大屏幕上的输入框增加宽度</li></ol><figure class="highlight css"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/* 大屏幕的样式(例如,Windows, MacBook) */</span></span><br><span class="line"><span class="keyword">@media</span> (<span class="attribute">min-width</span>: <span class="number">501px</span>) {</span><br><span class="line"> <span class="selector-id">#deviceName</span>, <span class="selector-id">#textArea</span>, <span class="selector-id">#imageUpload</span>, <span class="selector-id">#sendButton</span> {</span><br><span class="line"> <span class="attribute">height</span>: <span class="number">60px</span>; </span><br><span class="line"> <span class="attribute">font-size</span>: <span class="number">18px</span>; </span><br><span class="line"> <span class="attribute">padding</span>: <span class="number">10px</span>; </span><br><span class="line"> <span class="attribute">margin-right</span>: <span class="number">10px</span>; </span><br><span class="line"> }</span><br><span class="line"> <span class="selector-id">#sendButton</span> {</span><br><span class="line"> <span class="attribute">padding</span>: <span class="number">10px</span> <span class="number">20px</span>; </span><br><span class="line"> }</span><br><span class="line"> <span class="selector-id">#textArea</span> {</span><br><span class="line"> <span class="attribute">width</span>: <span class="number">60%</span>; </span><br><span class="line"> }</span><br><span class="line"> <span class="comment">/* 可以移除伸缩容器中最后一个元素的右距 */</span></span><br><span class="line"> <span class="selector-tag">div</span><span class="selector-attr">[style*=<span class="string">"position: fixed; bottom: 0;"</span>]</span> > <span class="selector-tag">div</span> > *<span class="selector-pseudo">:last-child</span> {</span><br><span class="line"> <span class="attribute">margin-right</span>: <span class="number">0</span>;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>这段代码增加了<code>#textArea</code>的宽度并在所有底部控件之间添加了<code>margin-right</code>。为了确保最后一个元素(按钮)不与其父元素的右边界有太大的间隔,我添加了一个特定选择器来移除其右边距</p><h3 id="0x5-6-增加深浅色主题"><a href="#0x5-6-增加深浅色主题" class="headerlink" title="0x5.6 增加深浅色主题"></a>0x5.6 增加深浅色主题</h3><ol><li>在HTML中添加一个切换按钮。</li><li>为深色模式定义CSS样式。</li><li>使用JavaScript监听按钮点击事件来切换主题。</li></ol><p>更新<code>index.html</code>如下:</p><figure class="highlight css"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br></pre></td><td class="code"><pre><span class="line"><span class="selector-attr">[data-theme=<span class="string">"dark"</span>]</span> <span class="selector-tag">input</span>, <span class="selector-attr">[data-theme=<span class="string">"dark"</span>]</span> <span class="selector-tag">textarea</span>, <span class="selector-attr">[data-theme=<span class="string">"dark"</span>]</span> <span class="selector-tag">button</span> {</span><br><span class="line"> <span class="attribute">color</span>: white; <span class="comment">/* 将文字颜色设置为白色 */</span></span><br><span class="line"> <span class="attribute">border-color</span>: white; <span class="comment">/* 如果需要, 也可以设置边框颜色 */</span></span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="selector-pseudo">:root</span> {</span><br><span class="line"> <span class="comment">/* 浅色模式的设置 */</span></span><br><span class="line"> --text-<span class="attribute">color</span>: black;</span><br><span class="line"> --<span class="attribute">background-color</span>: white;</span><br><span class="line"> --<span class="selector-tag">input</span>-bg: <span class="number">#fff</span>;</span><br><span class="line"> --container-bg: <span class="number">#fff</span>;</span><br><span class="line"> --<span class="selector-tag">header</span>-bg: <span class="number">#fff</span>;</span><br><span class="line"> --<span class="selector-tag">button</span>-<span class="attribute">color</span>: black; <span class="comment">/* 新增 */</span></span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="selector-attr">[data-theme=<span class="string">"dark"</span>]</span> {</span><br><span class="line"> <span class="comment">/* 深色模式的设置 */</span></span><br><span class="line"> --text-<span class="attribute">color</span>: black; <span class="comment">/* 文本颜色为黑色 */</span></span><br><span class="line"> --<span class="attribute">background-color</span>: white; <span class="comment">/* 背景颜色为白色 */</span></span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="selector-class">.bottom-controls</span> {</span><br><span class="line"> <span class="attribute">display</span>: flex;</span><br><span class="line"> <span class="attribute">justify-content</span>: center; </span><br><span class="line"> }</span><br><span class="line"> <span class="selector-class">.bottom-controls</span> > <span class="selector-tag">div</span> {</span><br><span class="line"> <span class="attribute">display</span>: flex; </span><br><span class="line"> <span class="attribute">align-items</span>: center;</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">/* 为固定底部工具栏添加填充 */</span></span><br><span class="line"> <span class="selector-tag">div</span><span class="selector-attr">[style*=<span class="string">"position: fixed; bottom: 0;"</span>]</span> {</span><br><span class="line"> <span class="attribute">padding-left</span>: <span class="number">10px</span>;</span><br><span class="line"> <span class="attribute">padding-right</span>: <span class="number">10px</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">/* 保证body的背景颜色被应用 */</span></span><br><span class="line"> <span class="selector-tag">body</span> {</span><br><span class="line"> <span class="attribute">font-family</span>: Arial, sans-serif;</span><br><span class="line"> <span class="attribute">display</span>: flex;</span><br><span class="line"> <span class="attribute">justify-content</span>: center;</span><br><span class="line"> <span class="attribute">align-items</span>: center;</span><br><span class="line"> <span class="attribute">height</span>: <span class="number">100vh</span>;</span><br><span class="line"> <span class="attribute">background-color</span>: <span class="built_in">var</span>(--background-color) <span class="meta">!important</span>; </span><br><span class="line"> <span class="attribute">color</span>: <span class="built_in">var</span>(--text-color);</span><br><span class="line"> <span class="attribute">font-family</span>: <span class="string">"JetBrainsMono"</span>, Arial, sans-serif;</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">/* 如果有特定的元素想要应用不同的文本或背景颜色,可以为它们单独设置: */</span></span><br><span class="line"> <span class="selector-class">.element</span> {</span><br><span class="line"> <span class="attribute">color</span>: <span class="built_in">var</span>(--text-color);</span><br><span class="line"> <span class="attribute">background-color</span>: <span class="built_in">var</span>(--background-color);</span><br><span class="line"> }</span><br><span class="line"> <span class="selector-class">.container</span> {</span><br><span class="line"> <span class="attribute">padding</span>: <span class="number">20px</span>;</span><br><span class="line"> <span class="attribute">border</span>: <span class="number">1px</span> solid <span class="number">#ddd</span>;</span><br><span class="line"> <span class="attribute">background-color</span>: <span class="number">#fff</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">/* 小屏幕的样式(如iphone) */</span></span><br><span class="line"> <span class="keyword">@media</span> (<span class="attribute">max-width</span>: <span class="number">500px</span>) {</span><br><span class="line"> <span class="selector-class">.bottom-controls</span> {</span><br><span class="line"> <span class="attribute">justify-content</span>: flex-start; </span><br><span class="line"> }</span><br><span class="line"> <span class="selector-class">.bottom-controls</span> > <span class="selector-tag">div</span> {</span><br><span class="line"> <span class="attribute">flex-direction</span>: column; </span><br><span class="line"> <span class="attribute">align-items</span>: center;</span><br><span class="line"> <span class="attribute">width</span>: <span class="number">100%</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">/* 更新底部控件的容器样式 */</span></span><br><span class="line"> <span class="selector-tag">div</span><span class="selector-attr">[style*=<span class="string">"position: fixed; bottom: 0;"</span>]</span> {</span><br><span class="line"> <span class="attribute">background-color</span>: <span class="built_in">var</span>(--container-bg);</span><br><span class="line"></span><br><span class="line"> <span class="attribute">padding-left</span>: <span class="number">10px</span>;</span><br><span class="line"> <span class="attribute">padding-right</span>: <span class="number">10px</span>;</span><br><span class="line"> <span class="attribute">display</span>: flex;</span><br><span class="line"> <span class="attribute">flex-direction</span>: column; </span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">/* 确保所有控件拉伸以填充可用宽度 */</span></span><br><span class="line"> <span class="selector-id">#deviceName</span>, <span class="selector-id">#textArea</span>, <span class="selector-id">#imageUpload</span>, <span class="selector-id">#sendButton</span> {</span><br><span class="line"> <span class="attribute">background-color</span>: <span class="built_in">var</span>(--container-bg);</span><br><span class="line"></span><br><span class="line"> <span class="attribute">width</span>: <span class="number">100%</span>; </span><br><span class="line"> <span class="attribute">box-sizing</span>: border-box; </span><br><span class="line"> <span class="attribute">margin-bottom</span>: <span class="number">5px</span>; </span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">/* 增加高度,便于触摸 */</span></span><br><span class="line"> <span class="selector-id">#deviceName</span>, <span class="selector-id">#textArea</span>, <span class="selector-id">#sendButton</span> {</span><br><span class="line"> <span class="attribute">height</span>: <span class="number">50px</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">/* 在邮件列表的底部添加填充 */</span></span><br><span class="line"> <span class="selector-id">#messages</span> {</span><br><span class="line"> <span class="attribute">padding-bottom</span>: <span class="number">70px</span>;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">/* 大屏幕的样式(例如,Windows, MacBook) */</span></span><br><span class="line"> <span class="keyword">@media</span> (<span class="attribute">min-width</span>: <span class="number">501px</span>) {</span><br><span class="line"> <span class="selector-id">#deviceName</span>, <span class="selector-id">#textArea</span>, <span class="selector-id">#imageUpload</span>, <span class="selector-id">#sendButton</span> {</span><br><span class="line"> <span class="attribute">background-color</span>: <span class="built_in">var</span>(--input-bg);</span><br><span class="line"></span><br><span class="line"> <span class="attribute">height</span>: <span class="number">60px</span>; </span><br><span class="line"> <span class="attribute">font-size</span>: <span class="number">18px</span>;</span><br><span class="line"> <span class="attribute">padding</span>: <span class="number">10px</span>; </span><br><span class="line"> <span class="attribute">margin-bottom</span>: <span class="number">10px</span>; </span><br><span class="line"> }</span><br><span class="line"> <span class="selector-id">#textArea</span> {</span><br><span class="line"> <span class="attribute">width</span>: <span class="number">50%</span>; </span><br><span class="line"> }</span><br><span class="line"> <span class="selector-id">#sendButton</span> {</span><br><span class="line"> <span class="attribute">padding</span>: <span class="number">10px</span> <span class="number">20px</span>;</span><br><span class="line"> <span class="attribute">margin-left</span>: <span class="number">10px</span>; </span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="selector-tag">body</span>, <span class="selector-tag">button</span> {</span><br><span class="line"> <span class="attribute">background-color</span>: <span class="built_in">var</span>(--bg-color);</span><br><span class="line"> <span class="attribute">color</span>: <span class="built_in">var</span>(--text-color);</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">/* 为了确保按钮的文本颜色能够在两种主题下都清晰可见 */</span></span><br><span class="line"> <span class="selector-tag">button</span> {</span><br><span class="line"> <span class="attribute">color</span>: <span class="built_in">var</span>(--button-color);</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">/* 底部控件的文本颜色 */</span></span><br><span class="line"> <span class="selector-class">.bottom-controls</span> <span class="selector-tag">input</span>, </span><br><span class="line"> <span class="selector-class">.bottom-controls</span> <span class="selector-tag">textarea</span>, </span><br><span class="line"> <span class="selector-class">.bottom-controls</span> <span class="selector-tag">button</span> {</span><br><span class="line"> <span class="attribute">color</span>: <span class="built_in">var</span>(--text-color);</span><br><span class="line"> <span class="attribute">border-color</span>: <span class="built_in">var</span>(--text-color); </span><br><span class="line"> }</span><br><span class="line"> <span class="selector-class">.container</span> {</span><br><span class="line"> <span class="attribute">background-color</span>: <span class="built_in">var</span>(--container-bg);</span><br><span class="line"> }</span><br><span class="line"> <span class="selector-tag">body</span><span class="selector-class">.dark-theme</span> {</span><br><span class="line"> <span class="comment">/* 深色模式的设置 */</span></span><br><span class="line"> --text-<span class="attribute">color</span>: white;</span><br><span class="line"> --<span class="attribute">background-color</span>: <span class="number">#555</span>;</span><br><span class="line"> --<span class="selector-tag">input</span>-bg: <span class="number">#555</span>;</span><br><span class="line"> --container-bg: <span class="number">#444</span>;</span><br><span class="line"> --<span class="selector-tag">header</span>-bg: <span class="number">#444</span>;</span><br><span class="line"> --<span class="selector-tag">button</span>-<span class="attribute">color</span>: white; <span class="comment">/* 新增 */</span></span><br><span class="line"> }</span><br><span class="line"> <span class="selector-id">#header</span> {</span><br><span class="line"> <span class="attribute">background-color</span>: <span class="built_in">var</span>(--header-bg);</span><br><span class="line"> }</span><br></pre></td></tr></tbody></table></figure><figure class="highlight javascript"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 切换主题</span></span><br><span class="line"> <span class="built_in">document</span>.getElementById(<span class="string">'themeToggle'</span>).addEventListener(<span class="string">'click'</span>, <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>{</span><br><span class="line"> <span class="built_in">document</span>.body.classList.toggle(<span class="string">'dark-theme'</span>);</span><br><span class="line"> });</span><br></pre></td></tr></tbody></table></figure><p>现在,当点击”Toggle Theme”按钮,页面的主题应该会在深色和浅色模式之间切换,并且文本应该会根据相应的模式改变颜色。</p><h2 id="0x6-导入自定义字体"><a href="#0x6-导入自定义字体" class="headerlink" title="0x6 导入自定义字体"></a>0x6 导入自定义字体</h2><ol><li><strong>选择字体</strong>: 首先,需要一个字体文件。这可以是<code>.ttf</code>、<code>.woff</code>、<code>.woff2</code>、<code>.eot</code>等格式。确保拥有使用该字体的权利,特别是如果打算在商业项目中使用。</li><li><strong>字体文件上传</strong>: 将字体文件上传到的服务器或CDN,并确保知道文件的URL。</li><li><strong>在CSS中导入字体</strong>: 在CSS文件或<code><style></code>标签中,使用<code>@font-face</code>规则来定义和导入字体。</li></ol><p>下载<a class="link" href="https://www.jetbrains.com/lp/mono/">JetBrains Mono<i class="fas fa-external-link-alt"></i></a>字体,解压<code>JetBrainsMono-Regular.ttf</code>、<code>JetBrainsMono-Regular.woff2</code>到网站根目录</p><figure class="highlight css"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">@font-face</span> {</span><br><span class="line"> <span class="attribute">font-family</span>: <span class="string">"JetBrainsMono"</span>; <span class="comment">/* 可以为字体起一个名字 */</span></span><br><span class="line"> <span class="attribute">src</span>: <span class="built_in">url</span>(<span class="string">'JetBrainsMono-Regular.woff2'</span>) <span class="built_in">format</span>(<span class="string">'woff2'</span>), /* 最优先 */</span><br><span class="line"> <span class="built_in">url</span>(<span class="string">'JetBrainsMono-Regular.ttf'</span>) <span class="built_in">format</span>(<span class="string">'truetype'</span>);</span><br><span class="line"> <span class="attribute">font-weight</span>: normal;</span><br><span class="line"> <span class="attribute">font-style</span>: normal;</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>在这里,<code>path-to-your-font</code>应该是字体文件的URL。如果有多种格式的字体文件,列出它们都是一个好主意,因为这可以确保更好的兼容性</p><figure class="highlight css"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/* 保证body的背景颜色被应用 */</span></span><br><span class="line"><span class="selector-tag">body</span> {</span><br><span class="line"> <span class="comment">/* ... 其他样式 ... */</span></span><br><span class="line"> <span class="attribute">font-family</span>: <span class="string">"JetBrainsMono"</span>, Arial, sans-serif;</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>在这里,<code>"MyCustomFont"</code>是我们为自定义字体定义的名字,<code>Arial, sans-serif</code>是后备字体,用于在某些情况下(例如,如果自定义字体加载失败)</p><p>考虑性能:字体文件可能很大,所以考虑只包括真正需要的字体权重和样式。例如,如果只需要常规和加粗,那么不要加载斜体或其他权重</p><h2 id="0x7-添加自动加载历史记录功能"><a href="#0x7-添加自动加载历史记录功能" class="headerlink" title="0x7 添加自动加载历史记录功能"></a>0x7 添加自动加载历史记录功能</h2><p>如果你想添加一个自动加载历史记录的功能,你需要在服务器端存储这些历史消息。这样,当新用户连接时,你就可以将这些历史消息发送给他们。</p><p>更新<code>server.js</code>如下:</p><p>在服务器上添加一个变量来存储消息</p><figure class="highlight javascript"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> messageHistory = [];</span><br></pre></td></tr></tbody></table></figure><p>当接收到新的文本消息时,将它添加到历史记录中</p><figure class="highlight javascript"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">socket.on(<span class="string">'send text'</span>, <span class="function">(<span class="params">data</span>) =></span> {</span><br><span class="line"> <span class="comment">// ...(其他代码不变)</span></span><br><span class="line"></span><br><span class="line"> <span class="comment">// 将消息添加到历史记录</span></span><br><span class="line"> messageHistory.push(data);</span><br><span class="line"></span><br><span class="line"> io.emit(<span class="string">'receive text'</span>, data);</span><br><span class="line">});</span><br></pre></td></tr></tbody></table></figure><p>当新用户连接时,发送所有历史消息给他们</p><figure class="highlight javascript"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">io.on(<span class="string">'connection'</span>, <span class="function">(<span class="params">socket</span>) =></span> {</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'a user connected'</span>);</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 发送历史消息给新连接的用户</span></span><br><span class="line"> messageHistory.forEach(<span class="function">(<span class="params">message</span>) =></span> {</span><br><span class="line"> socket.emit(<span class="string">'receive text'</span>, message);</span><br><span class="line"> });</span><br><span class="line"></span><br><span class="line"> <span class="comment">// ...(其他代码不变)</span></span><br><span class="line">});</span><br></pre></td></tr></tbody></table></figure><p><code>index.html</code>在客户端的 JavaScript 里,你不需要做任何修改。因为客户端已经设置了一个监听函数来处理 ‘receive text’ 事件,所以当它接收到历史消息时,会自动将其添加到消息列表。</p><p>这样,当有新用户加入聊天时,服务器就会自动发送所有历史消息给他们</p><h2 id="0x8-添加消息代码格式化和时间戳"><a href="#0x8-添加消息代码格式化和时间戳" class="headerlink" title="0x8 添加消息代码格式化和时间戳"></a>0x8 添加消息代码格式化和时间戳</h2><p>为了在消息中添加发送时间,可以在服务器端获取当前时间,并将其与其他数据一起发送。同时,也需要在客户端的JavaScript中修改接收数据的部分,以显示这一时间。</p><p>首先,在 <code>server.js</code> 的 <code>'send text'</code> 事件处理程序中添加发送时间:</p><figure class="highlight javascript"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">socket.on(<span class="string">'send text'</span>, <span class="function">(<span class="params">data</span>) =></span> {</span><br><span class="line"> <span class="comment">// ...其它代码</span></span><br><span class="line"></span><br><span class="line"> <span class="comment">// 添加消息发送时间</span></span><br><span class="line"> data.timestamp = <span class="keyword">new</span> <span class="built_in">Date</span>().toLocaleString();</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 将新消息添加到历史记录</span></span><br><span class="line"> messageHistory.push(data);</span><br><span class="line"></span><br><span class="line"> io.emit(<span class="string">'receive text'</span>, data);</span><br><span class="line">});</span><br></pre></td></tr></tbody></table></figure><p>修改 <code>index.html</code> 在接收消息部分,添加代码格式支持和时间戳</p><figure class="highlight javascript"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line">socket.on(<span class="string">'receive text'</span>, <span class="function"><span class="keyword">function</span>(<span class="params">data</span>) </span>{</span><br><span class="line"> <span class="keyword">var</span> li = <span class="built_in">document</span>.createElement(<span class="string">"li"</span>);</span><br><span class="line"> <span class="keyword">var</span> time = data.timestamp || <span class="keyword">new</span> <span class="built_in">Date</span>().toLocaleString(); <span class="comment">// 获取时间戳,如果没有则使用当前时间</span></span><br><span class="line"></span><br><span class="line"> <span class="keyword">var</span> content = <span class="string">`[<span class="subst">${data.deviceName}</span> @ <span class="subst">${time}</span>]: `</span>; <span class="comment">// 显示设备名和时间</span></span><br><span class="line"> <span class="keyword">if</span> (data.text.startsWith(<span class="string">"```"</span>) && data.text.endsWith(<span class="string">"```"</span>)) {</span><br><span class="line"> <span class="comment">// 如果文本以 "```" 开头和结尾,将其识别为代码</span></span><br><span class="line"> content += <span class="string">`<pre><span class="subst">${data.text.slice(<span class="number">3</span>, -<span class="number">3</span>)}</span></pre>`</span>;</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> content += <span class="string">`<span class="subst">${data.text}</span>`</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (data.imageUrl) {</span><br><span class="line"> content += <span class="string">`<br><img src="<span class="subst">${data.imageUrl}</span>" alt="Uploaded image" style="max-width: 300px;">`</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> li.innerHTML = content;</span><br><span class="line"> <span class="built_in">document</span>.getElementById(<span class="string">'messages'</span>).appendChild(li);</span><br><span class="line">});</span><br></pre></td></tr></tbody></table></figure><p>这样,如果用户在 <code>textArea</code> 输入的文本以 **”```”** 开头和结尾,这段文本将会被认为是代码,并用 <code><pre></code> 标签进行包裹,以维持其格式。同时,每条消息也会显示发送时间。</p><h2 id="0x9-增加消息删除与数据库"><a href="#0x9-增加消息删除与数据库" class="headerlink" title="0x9 增加消息删除与数据库"></a>0x9 增加消息删除与数据库</h2><h3 id="0x9-1-增加消息删除"><a href="#0x9-1-增加消息删除" class="headerlink" title="0x9.1 增加消息删除"></a>0x9.1 增加消息删除</h3><p>创建一个复选框要对应每条消息,所以必须设置每条消息和复选框的ID保持一致,在选择复选框时应该同时删除后面的消息</p><p>修改<code>index.html</code>文件</p><figure class="highlight javascript"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> messageId = <span class="number">0</span>; <span class="comment">// 初始化一个全局变量</span></span><br><span class="line"> socket.on(<span class="string">'receive text'</span>, <span class="function"><span class="keyword">function</span>(<span class="params">data</span>) </span>{</span><br><span class="line"> <span class="keyword">var</span> li = <span class="built_in">document</span>.createElement(<span class="string">"li"</span>);</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 创建复选框</span></span><br><span class="line"> <span class="keyword">var</span> checkbox = <span class="built_in">document</span>.createElement(<span class="string">"input"</span>);</span><br><span class="line"> checkbox.type = <span class="string">"checkbox"</span>;</span><br><span class="line"> checkbox.className = <span class="string">"messageCheckbox"</span>;</span><br><span class="line"> li.appendChild(checkbox); <span class="comment">// 将复选框添加到列表项中</span></span><br><span class="line"></span><br><span class="line"> <span class="keyword">var</span> time = data.timestamp || <span class="keyword">new</span> <span class="built_in">Date</span>().toLocaleString(); <span class="comment">// 获取时间戳,如果没有则使用当前时间</span></span><br><span class="line"></span><br><span class="line"> <span class="keyword">var</span> content = <span class="string">`[<span class="subst">${data.deviceName}</span> @ <span class="subst">${time}</span>]: `</span>; <span class="comment">// 显示设备名和时间</span></span><br><span class="line"> <span class="keyword">if</span> (data.text.startsWith(<span class="string">"```"</span>) && data.text.endsWith(<span class="string">"```"</span>)) {</span><br><span class="line"> <span class="comment">// 如果文本以 "```" 开头和结尾,将其识别为代码</span></span><br><span class="line"> content += <span class="string">`<pre><span class="subst">${data.text.slice(<span class="number">3</span>, -<span class="number">3</span>)}</span></pre>`</span>;</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> content += <span class="string">`<span class="subst">${data.text}</span>`</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (data.imageUrl) {</span><br><span class="line"> content += <span class="string">`<br><img src="<span class="subst">${data.imageUrl}</span>" alt="Uploaded image" style="max-width: 300px;">`</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> li.innerHTML += content;</span><br><span class="line"> <span class="built_in">document</span>.getElementById(<span class="string">'messages'</span>).appendChild(li);</span><br><span class="line"></span><br><span class="line"> li.setAttribute(<span class="string">'data-message-id'</span>, messageId); <span class="comment">// 为每个消息设置唯一ID</span></span><br><span class="line"> messageId++;</span><br><span class="line"></span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">"设备名称和IP地址:"</span>, data.deviceName);</span><br><span class="line"> });</span><br></pre></td></tr></tbody></table></figure><p>向服务器发送删除请求</p><figure class="highlight javascript"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">document</span>.getElementById(<span class="string">'deleteButton'</span>).onclick = <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>{</span><br><span class="line"> <span class="keyword">var</span> selectedMessages = <span class="built_in">document</span>.querySelectorAll(<span class="string">'.messageCheckbox:checked'</span>);</span><br><span class="line"> <span class="keyword">var</span> messageIdsToDelete = [];</span><br><span class="line"> selectedMessages.forEach(<span class="function"><span class="keyword">function</span>(<span class="params">checkbox</span>) </span>{</span><br><span class="line"> <span class="keyword">var</span> li = checkbox.closest(<span class="string">'li'</span>);</span><br><span class="line"> messageIdsToDelete.push(li.getAttribute(<span class="string">'data-message-id'</span>));</span><br><span class="line"> li.remove();</span><br><span class="line"> });</span><br><span class="line"> socket.emit(<span class="string">'delete messages'</span>, messageIdsToDelete);</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 向后端发送删除请求</span></span><br><span class="line"> messageIdsToDelete.forEach(<span class="function"><span class="params">id</span> =></span> {</span><br><span class="line"> fetch(<span class="string">`/message/<span class="subst">${id}</span>`</span>, {</span><br><span class="line"> <span class="attr">method</span>: <span class="string">'DELETE'</span></span><br><span class="line"> }).then(<span class="function"><span class="params">response</span> =></span> {</span><br><span class="line"> <span class="keyword">if</span> (!response.ok) {</span><br><span class="line"> <span class="comment">// 以某种方式处理错误响应</span></span><br><span class="line"> <span class="built_in">console</span>.error(<span class="string">'从服务器删除消息失败'</span>);</span><br><span class="line"> }</span><br><span class="line"> });</span><br><span class="line"> });</span><br><span class="line">};</span><br></pre></td></tr></tbody></table></figure><p>向所有客户端广播消息ID,以供删除</p><figure class="highlight javascript"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">socket.on(<span class="string">'delete messages'</span>, <span class="function"><span class="keyword">function</span>(<span class="params">messageIds</span>) </span>{</span><br><span class="line"> messageIds.forEach(<span class="function"><span class="keyword">function</span>(<span class="params">messageId</span>) </span>{</span><br><span class="line"> <span class="keyword">var</span> li = <span class="built_in">document</span>.querySelector(<span class="string">'li[data-message-id="'</span> + messageId + <span class="string">'"]'</span>);</span><br><span class="line"> <span class="keyword">if</span> (li) li.remove();</span><br><span class="line"> });</span><br><span class="line"> io.emit(<span class="string">'delete messages'</span>, messageIds);</span><br><span class="line"> });</span><br></pre></td></tr></tbody></table></figure><h3 id="0x9-2-前端数据库联动删消息"><a href="#0x9-2-前端数据库联动删消息" class="headerlink" title="0x9.2 前端数据库联动删消息"></a>0x9.2 前端数据库联动删消息</h3><h4 id="0x9-2-1-创建数据库"><a href="#0x9-2-1-创建数据库" class="headerlink" title="0x9.2.1 创建数据库"></a>0x9.2.1 创建数据库</h4><p>修改<code>server.js</code>文件</p><figure class="highlight javascript"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> db = <span class="keyword">new</span> sqlite3.Database(<span class="string">'./messages.db'</span>);</span><br></pre></td></tr></tbody></table></figure><h4 id="0x9-2-2-创建表"><a href="#0x9-2-2-创建表" class="headerlink" title="0x9.2.2 创建表"></a>0x9.2.2 创建表</h4><p>创建message表,如果存在则什么都不做,包含id、content、deviceName、 ipAddress、 timestamp字段</p><figure class="highlight javascript"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">db.run(<span class="string">"CREATE TABLE IF NOT EXISTS messages (id INTEGER PRIMARY KEY AUTOINCREMENT, content TEXT, deviceName TEXT, ipAddress TEXT, timestamp TEXT)"</span>, <span class="function">(<span class="params">err</span>) =></span> {</span><br><span class="line"> <span class="keyword">if</span> (err) {</span><br><span class="line"> <span class="built_in">console</span>.error(<span class="string">"创建/检查表错误:"</span>, err);</span><br><span class="line"> <span class="keyword">return</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">"表检查/创建成功"</span>);</span><br><span class="line">});</span><br></pre></td></tr></tbody></table></figure><h4 id="0x9-2-3-读取数据库所有消息"><a href="#0x9-2-3-读取数据库所有消息" class="headerlink" title="0x9.2.3 读取数据库所有消息"></a>0x9.2.3 读取数据库所有消息</h4><p>从数据库中查询所有的消息记录,并将每条消息存储到<code>messageHistory</code>数组中,若查询过程中出现错误,会在控制台中打印错误信息</p><figure class="highlight javascript"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line">db.all(<span class="string">"SELECT id, content, deviceName, ipAddress, timestamp FROM messages"</span>, [], <span class="function">(<span class="params">err, rows</span>) =></span> {</span><br><span class="line"> <span class="keyword">if</span> (err) {</span><br><span class="line"> <span class="built_in">console</span>.error(<span class="string">"从数据库获取历史消息出错:"</span>, err);</span><br><span class="line"> <span class="keyword">return</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 遍历数据库每条记录</span></span><br><span class="line"> rows.forEach(<span class="function"><span class="params">row</span> =></span> {</span><br><span class="line"> <span class="keyword">const</span> message = {</span><br><span class="line"> <span class="attr">id</span>: row.id, <span class="comment">// 确保这里保留 id</span></span><br><span class="line"> <span class="attr">text</span>: row.content,</span><br><span class="line"> <span class="attr">deviceName</span>: row.deviceName,</span><br><span class="line"> <span class="attr">ipAddress</span>: row.ipAddress,</span><br><span class="line"> <span class="attr">timestamp</span>: row.timestamp</span><br><span class="line"> };</span><br><span class="line"> <span class="comment">// 将数据库中的数据保存在 messageHistory</span></span><br><span class="line"> messageHistory.push(message);</span><br><span class="line"> });</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">"从数据库加载历史消息"</span>);</span><br><span class="line">});</span><br></pre></td></tr></tbody></table></figure><h4 id="0x9-2-4-保存前端消息到数据库"><a href="#0x9-2-4-保存前端消息到数据库" class="headerlink" title="0x9.2.4 保存前端消息到数据库"></a>0x9.2.4 保存前端消息到数据库</h4><p>定义了一个HTTP POST路由<code>/send</code>,用于从请求体中接收消息内容,并在消息内容存在时将其存储到数据库中,若消息内容为空,则返回一个错误响应</p><figure class="highlight javascript"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">app.post(<span class="string">'/send'</span>, <span class="function">(<span class="params">req, res</span>) =></span> {</span><br><span class="line"> <span class="keyword">const</span> messageContent = req.body.message;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (messageContent) {</span><br><span class="line"> <span class="comment">// 存储消息到数据库</span></span><br><span class="line"> db.run(<span class="string">"INSERT INTO messages (content, deviceName, ipAddress, timestamp) VALUES (?, ?, ?, ?)"</span>, [data.text, data.deviceName, clientIpAddress, data.timestamp],</span><br><span class="line"> <span class="function">(<span class="params">err</span>) =></span> {</span><br><span class="line"> <span class="keyword">if</span> (err) {</span><br><span class="line"> <span class="built_in">console</span>.error(<span class="string">"插入数据库出错:"</span>, err);</span><br><span class="line"> <span class="keyword">return</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'收到的信息:'</span>, data.text || messageContent);</span><br><span class="line"> });</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> res.status(<span class="number">400</span>).send({<span class="attr">error</span>: <span class="string">"消息内容为空"</span>});</span><br><span class="line"> }</span><br><span class="line">});</span><br></pre></td></tr></tbody></table></figure><h4 id="0x9-2-5-广播所有消息给用户"><a href="#0x9-2-5-广播所有消息给用户" class="headerlink" title="0x9.2.5 广播所有消息给用户"></a>0x9.2.5 广播所有消息给用户</h4><p>当一个用户与服务器建立Socket连接时的操作:首先打印出用户已连接的消息,然后将存储在<code>messageHistory</code>数组中的每条历史消息(经过修改以显示设备名和IP地址)发送给新连接的用户</p><figure class="highlight javascript"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">io.on(<span class="string">'connection'</span>, <span class="function">(<span class="params">socket</span>) =></span> {</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'有一个用户连接'</span>);</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">function</span> <span class="title">formatMessageForEmission</span>(<span class="params">originalMessage</span>) </span>{</span><br><span class="line"> <span class="keyword">return</span> {</span><br><span class="line"> ...originalMessage,</span><br><span class="line"> <span class="attr">deviceName</span>: <span class="string">`<span class="subst">${originalMessage.deviceName}</span> (<span class="subst">${originalMessage.ipAddress}</span>)`</span></span><br><span class="line"> };</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 发送历史消息给新连接的用户</span></span><br><span class="line"> messageHistory.forEach(<span class="function">(<span class="params">message</span>) =></span> {</span><br><span class="line"> socket.emit(<span class="string">'receive text'</span>, formatMessageForEmission(message));</span><br><span class="line"> <span class="comment">// console.log("format的设备和IP地址:",message)</span></span><br><span class="line"> });</span><br></pre></td></tr></tbody></table></figure><h4 id="0x9-2-6-广播Socket客户端消息"><a href="#0x9-2-6-广播Socket客户端消息" class="headerlink" title="0x9.2.6 广播Socket客户端消息"></a>0x9.2.6 广播Socket客户端消息</h4><p>当服务器通过Socket接收到客户端发送的<code>send text</code>事件时,首先获取和处理客户端的IP地址,为没有提供设备名称的消息设置默认名称,然后将消息及相关信息添加到数据库中。之后,会修改消息的设备名称,将新消息添加到历史记录数组,并通过Socket广播这条消息给所有连接的客户端</p><figure class="highlight javascript"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><span class="line">socket.on(<span class="string">'send text'</span>, <span class="function">(<span class="params">data</span>) =></span> {</span><br><span class="line"> <span class="comment">// 获取客户端的IP地址</span></span><br><span class="line"> <span class="keyword">let</span> clientIpAddress = socket.request.connection.remoteAddress.replace(<span class="regexp">/^::ffff:/</span>, <span class="string">''</span>); <span class="comment">// 清除IPv6前缀</span></span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (clientIpAddress === <span class="string">'::1'</span> || clientIpAddress === <span class="string">'127.0.0.1'</span>) {</span><br><span class="line"> clientIpAddress = <span class="string">'Localhost'</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 添加ipAddress字段到data对象</span></span><br><span class="line"> data.ipAddress = clientIpAddress;</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 默认设备名称</span></span><br><span class="line"> <span class="keyword">if</span> (!data.deviceName || data.deviceName.trim() === <span class="string">""</span>) {</span><br><span class="line"> data.deviceName = <span class="string">"匿名设备"</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 添加消息发送时间</span></span><br><span class="line"> data.timestamp = <span class="keyword">new</span> <span class="built_in">Date</span>().toLocaleString();</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 添加消息到数据库中</span></span><br><span class="line"> db.run(<span class="string">"INSERT INTO messages (content, deviceName, ipAddress, timestamp) VALUES (?, ?, ?, ?)"</span>,</span><br><span class="line"> [data.text, data.deviceName, clientIpAddress, data.timestamp],</span><br><span class="line"> <span class="function"><span class="keyword">function</span>(<span class="params">err</span>) </span>{ <span class="comment">// 使用函数关键字以便访问 this.lastID</span></span><br><span class="line"> <span class="keyword">if</span> (err) {</span><br><span class="line"> <span class="built_in">console</span>.error(<span class="string">"插入数据库出错:"</span>, err);</span><br><span class="line"> <span class="keyword">return</span>;</span><br><span class="line"> }</span><br><span class="line"> data.id = <span class="built_in">this</span>.lastID; <span class="comment">// 获取新插入的行的 ID</span></span><br><span class="line"> messageHistory.push(data); <span class="comment">// 将新消息添加到历史记录</span></span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'通过套接字收到的消息:'</span>, data.text);</span><br><span class="line"> }</span><br><span class="line"> );</span><br><span class="line"></span><br><span class="line"> io.emit(<span class="string">'receive text'</span>, formatMessageForEmission(data));</span><br><span class="line"> });</span><br></pre></td></tr></tbody></table></figure><h4 id="0x9-2-7-处理DELETE请求删数据库消息"><a href="#0x9-2-7-处理DELETE请求删数据库消息" class="headerlink" title="0x9.2.7 处理DELETE请求删数据库消息"></a>0x9.2.7 处理DELETE请求删数据库消息</h4><p>当收到HTTP DELETE请求时,根据指定的消息ID从数据库中删除相应的消息记录;若删除过程中出现错误,则返回错误信息,否则通知请求者消息已被删除。如果未提供有效的消息ID,则返回“无效的消息ID”错误。</p><figure class="highlight javascript"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line">app.delete(<span class="string">'/message/:id'</span>, <span class="function">(<span class="params">req, res</span>) =></span> {</span><br><span class="line"> <span class="keyword">const</span> messageId = req.params.id;</span><br><span class="line"> <span class="keyword">if</span> (messageId) {</span><br><span class="line"> db.run(<span class="string">"DELETE FROM messages WHERE id=?"</span>, [messageId], <span class="function">(<span class="params">err</span>) =></span> {</span><br><span class="line"> <span class="keyword">if</span> (err) {</span><br><span class="line"> <span class="built_in">console</span>.error(<span class="string">"从数据库删除消息出错:"</span>, err);</span><br><span class="line"> res.status(<span class="number">500</span>).send({<span class="attr">error</span>: <span class="string">"删除消息失败"</span>});</span><br><span class="line"> <span class="keyword">return</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 从 messageHistory 数组中删除消息</span></span><br><span class="line"> <span class="keyword">const</span> index = messageHistory.findIndex(<span class="function"><span class="params">message</span> =></span> message.id === <span class="built_in">Number</span>(messageId));</span><br><span class="line"> <span class="keyword">if</span> (index !== -<span class="number">1</span>) {</span><br><span class="line"> messageHistory.splice(index, <span class="number">1</span>);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> res.send({<span class="attr">status</span>: <span class="string">"消息已删除"</span>});</span><br><span class="line"> });</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> res.status(<span class="number">400</span>).send({<span class="attr">error</span>: <span class="string">"无效的消息ID"</span>});</span><br><span class="line"> }</span><br><span class="line">});</span><br></pre></td></tr></tbody></table></figure><h2 id="0xA-Docker封装与测试"><a href="#0xA-Docker封装与测试" class="headerlink" title="0xA Docker封装与测试"></a>0xA Docker封装与测试</h2><p>部署Node.js网站到QNAP NAS上的一个优雅的方式是使用Docker。这种方法提供了隔离,便于管理和升级,而不会影响主机系统</p><h3 id="0xA-1-Docker环境准备"><a href="#0xA-1-Docker环境准备" class="headerlink" title="0xA.1 Docker环境准备"></a>0xA.1 Docker环境准备</h3><h4 id="0xA-1-1-创建Dockerfile"><a href="#0xA-1-1-创建Dockerfile" class="headerlink" title="0xA.1.1 创建Dockerfile"></a>0xA.1.1 创建Dockerfile</h4><ol><li><p><strong>打开终端或命令提示符</strong>:导航到Node.js项目的根目录。</p></li><li><p><strong>创建一个新的Dockerfile</strong>:</p><p>如果使用的是Linux或Mac,可以在终端中输入以下命令:</p><figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">touch Dockerfile</span><br></pre></td></tr></tbody></table></figure><p>如果在Windows上,可以使用命令提示符或PowerShell并输入以下命令:</p><figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">echo</span>. > Dockerfile</span><br></pre></td></tr></tbody></table></figure></li><li><p><strong>编辑Dockerfile</strong>:</p><p>使用喜欢的文本编辑器打开新创建的Dockerfile。例如,可以使用VS Code、Notepad++、vim等。</p></li><li><p><strong>在Dockerfile中输入内容</strong>:</p><p>复制上文给出的Dockerfile内容,并粘贴到的Dockerfile中:</p></li></ol><figure class="highlight dockerfile"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 或者选择的其他Node.js版本</span></span><br><span class="line"><span class="keyword">FROM</span> node:<span class="number">14</span> AS build-stage</span><br><span class="line"></span><br><span class="line"><span class="comment"># 设置工作目录</span></span><br><span class="line"><span class="keyword">WORKDIR</span><span class="bash"> /usr/src/app</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 将package.json和package-lock.json文件复制到工作目录</span></span><br><span class="line"><span class="keyword">COPY</span><span class="bash"> package*.json ./</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 安装应用程序的依赖项</span></span><br><span class="line"><span class="keyword">RUN</span><span class="bash"> npm install</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 复制应用程序的源代码到工作目录</span></span><br><span class="line"><span class="keyword">COPY</span><span class="bash"> . .</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 暴露端口</span></span><br><span class="line"><span class="comment"># 或的应用程序运行的其他端口</span></span><br><span class="line"><span class="keyword">EXPOSE</span> <span class="number">3000</span> </span><br><span class="line"></span><br><span class="line"><span class="comment"># 定义Docker容器的启动命令</span></span><br><span class="line"><span class="keyword">CMD</span><span class="bash"> [<span class="string">"npm"</span>, <span class="string">"start"</span>]</span></span><br></pre></td></tr></tbody></table></figure><p><strong>保存并关闭文件</strong></p><h4 id="0xA-1-2-构建Docker镜像"><a href="#0xA-1-2-构建Docker镜像" class="headerlink" title="0xA.1.2 构建Docker镜像"></a>0xA.1.2 构建Docker镜像</h4><p>在项目的目录中执行以下命令,以构建一个Docker镜像:</p><figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker build -t realtimetext .</span><br></pre></td></tr></tbody></table></figure><p>要确保的镜像已经正确构建,可以使用以下命令列出所有本地的Docker镜像:</p><figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker images</span><br></pre></td></tr></tbody></table></figure><h4 id="0xA-1-3-运行Docker容器"><a href="#0xA-1-3-运行Docker容器" class="headerlink" title="0xA.1.3 运行Docker容器"></a>0xA.1.3 运行Docker容器</h4><p>确保Docker已安装并正在运行,然后运行以下命令以启动的Node.js应用:</p><figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker run -p 4000:3000 realtime_text</span><br></pre></td></tr></tbody></table></figure><h3 id="0xA-2-导出容器上传并运行"><a href="#0xA-2-导出容器上传并运行" class="headerlink" title="0xA.2 导出容器上传并运行"></a>0xA.2 导出容器上传并运行</h3><h4 id="0xA-2-1-导出Docker容器"><a href="#0xA-2-1-导出Docker容器" class="headerlink" title="0xA.2.1 导出Docker容器"></a>0xA.2.1 导出Docker容器</h4><p>首先,需要确定一个有效的路径来保存tar文件</p><figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">mkdir docker-images</span><br></pre></td></tr></tbody></table></figure><p>查看当前运行的docker ID</p><figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker ps</span><br></pre></td></tr></tbody></table></figure><p>将容器提交为新的Docker镜像</p><figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker commit [CONTAINER_ID] realtime_text</span><br></pre></td></tr></tbody></table></figure><p>这应该会将的<code>realtime_text</code>镜像保存为<code>realtime_text.tar</code>文件,位于<code>docker-images/</code>目录下</p><figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker save -o docker-images/realtime_text.tar realtime_text</span><br></pre></td></tr></tbody></table></figure><h4 id="0xA-2-2-上传Docker容器"><a href="#0xA-2-2-上传Docker容器" class="headerlink" title="0xA.2.2 上传Docker容器"></a>0xA.2.2 上传Docker容器</h4><ol><li><p>需要确保在QNAP中的FileStation创建一个名为<code>Container</code>的目录</p></li><li><p>在App Center中搜索并安装<code>Container Station</code>,等待服务启动完成就可以打开了</p></li><li><p>打开<strong>映像</strong>页面,点击右上角的<strong>导入</strong>,从<strong>本地计算机</strong>选择刚导出的Docker镜像文件<strong>realtime_text</strong>,再点击导入</p></li></ol><h4 id="0xA-2-3-运行Docker容器"><a href="#0xA-2-3-运行Docker容器" class="headerlink" title="0xA.2.3 运行Docker容器"></a>0xA.2.3 运行Docker容器</h4><p>在容器页面启动名为<strong>realtime_text</strong>的容器,查看其详细信息及Web URL:<a href="http://192.168.50.145:32769,就是部署后的网页地址">http://192.168.50.145:32769,就是部署后的网页地址</a></p><h3 id="0xA-3-维护容器占用空间"><a href="#0xA-3-维护容器占用空间" class="headerlink" title="0xA.3 维护容器占用空间"></a>0xA.3 维护容器占用空间</h3><p>长期发送图片消息会导致upload目录占用较大空间,必要情况下可以在容器页面选择容器后的设置按钮,选择执行<code>/bin/sh</code>控制台</p><figure class="highlight sh"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">cd</span> uploads</span><br><span class="line">rm *</span><br></pre></td></tr></tbody></table></figure><h2 id="0xB-常见问题与解决方案"><a href="#0xB-常见问题与解决方案" class="headerlink" title="0xB 常见问题与解决方案"></a>0xB 常见问题与解决方案</h2><h3 id="0xB-1-构建失败"><a href="#0xB-1-构建失败" class="headerlink" title="0xB.1 构建失败"></a>0xB.1 构建失败</h3><ul><li><p>在构建Docker时出现任何问题请先确保你在<strong>realtime_text</strong>根目录下</p></li><li><p>构建docker的镜像名必须是小写</p></li><li><p>构建docker镜像必须指定导出目录</p></li><li><p>如果你在Linux下创建docker请确保相关文件有执行的权限</p><figure class="highlight sh"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">chmod +x script_name.sh</span><br><span class="line">./script_name.sh</span><br></pre></td></tr></tbody></table></figure></li></ul><h3 id="0xB-2-端口占用"><a href="#0xB-2-端口占用" class="headerlink" title="0xB.2 端口占用"></a>0xB.2 端口占用</h3><p> 在运行时报告了一个端口占用错误</p> <figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line">“node:events:491</span><br><span class="line"> throw er; // Unhandled <span class="string">'error'</span> event</span><br><span class="line"> ^</span><br><span class="line">Error: listen EADDRINUSE: address already <span class="keyword">in</span> use :::3000</span><br><span class="line"> at Server.setupListenHandle [as _listen2] (node:net:1485:16)</span><br><span class="line"> at listenInCluster (node:net:1533:12)</span><br><span class="line"> at Server.listen (node:net:1621:7)</span><br><span class="line"> at Object.<anonymous> (D:\code\win-code\NodeJS\realtime_text\server.js:60:8)</span><br><span class="line"> at Module._compile (node:internal/modules/cjs/loader:1159:14)</span><br><span class="line"> at Module._extensions..js (node:internal/modules/cjs/loader:1213:10)</span><br><span class="line"> at Module.load (node:internal/modules/cjs/loader:1037:32)</span><br><span class="line"> at Module._load (node:internal/modules/cjs/loader:878:12)</span><br><span class="line"> at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:81:12)</span><br><span class="line"> at node:internal/main/run_main_module:23:47</span><br><span class="line">Emitted <span class="string">'error'</span> event on Server instance at:</span><br><span class="line"> at emitErrorNT (node:net:1512:8)</span><br><span class="line"> at process.processTicksAndRejections (node:internal/process/task_queues:82:21) {</span><br><span class="line"> code: <span class="string">'EADDRINUSE'</span>,</span><br><span class="line"> errno: -4091,</span><br><span class="line"> syscall: <span class="string">'listen'</span>,</span><br><span class="line"> address: <span class="string">'::'</span>,</span><br><span class="line"> port: 3000</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">Node.js v18.12.1”</span><br></pre></td></tr></tbody></table></figure><p> 这个错误 <code>Error: listen EADDRINUSE: address already in use :::3000</code> 意味着端口 3000 已经被其他程序或者相同的程序的另一个实例使用了。你有几种方式来解决这个问题:</p><ol><li><p><strong>杀掉使用该端口的进程</strong></p><ol><li>在 Windows 上:<ol><li>打开命令行窗口,然后运行 <code>netstat -ano | findstr :3000</code>。</li><li>这会显示使用端口 3000 的所有进程。找到进程 ID(PID)。</li><li>执行 <code>taskkill /PID <PID> /F</code> 来杀掉进程。</li></ol></li><li>在 macOS 或 Linux 上:<ol><li>打开终端,然后运行 <code>lsof -i :3000</code>。</li><li>这会显示使用端口 3000 的所有进程。找到进程 ID(PID)。</li><li>执行 <code>kill -9 <PID></code> 来杀掉进程。</li></ol></li></ol></li><li><p><strong>更改应用程序使用的端口</strong></p><p>在你的 <code>server.js</code> 文件中,找到下面这行代码:</p><figure class="highlight javascript"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> port = process.env.PORT || <span class="number">3000</span>;</span><br></pre></td></tr></tbody></table></figure><p>你可以更改 3000 到其他未被使用的端口号,例如:</p><figure class="highlight javascript"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> port = process.env.PORT || <span class="number">4000</span>;</span><br></pre></td></tr></tbody></table></figure><p>然后重新运行你的应用程序。</p></li><li><p><strong>重启计算机</strong></p><p>如果上面的方法没有解决问题或者你不确定如何进行,重启计算机通常能够解决端口冲突的问题。</p><p>希望这能帮到你!</p></li></ol><h3 id="0xB-3-数据库自增ID列"><a href="#0xB-3-数据库自增ID列" class="headerlink" title="0xB.3 数据库自增ID列"></a>0xB.3 数据库自增ID列</h3><p>DataGrip 是一个流行的数据库管理工具,允许你通过其图形界面来进行各种数据库操作。要在 DataGrip 中修改表的 <code>id</code> 列为自增,可以按照以下步骤进行:</p><ol><li><p><strong>打开 DataGrip</strong> 并连接到你的 SQLite 数据库。</p></li><li><p>在左侧的 <strong>数据库浏览器</strong> 中,找到你的表。</p></li><li><p><strong>右键点击</strong> 你想要修改的表,然后选择 <strong>Modify Table</strong>。</p></li><li><p>在打开的窗口中,找到 <code>id</code> 列。</p></li><li><p>选中 <code>id</code> 列,然后在右侧的属性面板中找到 <strong>Identity</strong> 或 <strong>Auto Increment</strong> 选项并勾选。</p></li><li><p><strong>右键点击</strong> 该列名,并从上下文菜单中选择 <strong>Set Primary Key</strong>。</p></li><li><p>最后,点击窗口下方的 <strong>提交</strong> 或 <strong>应用</strong> 按钮来保存更改。</p></li></ol><p>请注意,不同的数据库系统和不同的版本可能会有些微差异。确保在进行此类操作之前备份你的数据。</p><p>如果 DataGrip 不直接支持 SQLite 的自增特性或有任何其他问题,你可能需要使用 SQL 语句来手动修改表结构,或者像之前所描述的那样,通过创建一个新表、复制数据、删除旧表、然后重命名新表的方式来达到目的。</p><h3 id="0xB-4-确保前端页面与数据库内容同步"><a href="#0xB-4-确保前端页面与数据库内容同步" class="headerlink" title="0xB.4 确保前端页面与数据库内容同步"></a>0xB.4 确保前端页面与数据库内容同步</h3><p>前端消息的ID与数据库ID对应。若前端删除消息失败,可能是ID不匹配或Docker中的sqlite3库问题。若需手动删除消息,以下是相关sql语句。<strong>不使用Docker容器可避免该问题,并注意Docker会将24小时制改为12小时制</strong></p><h4 id="0xB-4-1-连接数据库"><a href="#0xB-4-1-连接数据库" class="headerlink" title="0xB.4.1 连接数据库"></a>0xB.4.1 连接数据库</h4><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sqlite3 messages.db</span><br></pre></td></tr></tbody></table></figure><h4 id="0xB-4-2-查询表"><a href="#0xB-4-2-查询表" class="headerlink" title="0xB.4.2 查询表"></a>0xB.4.2 查询表</h4><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">SELECT * FROM messages;</span><br></pre></td></tr></tbody></table></figure><h4 id="0xB-4-3-查ID列"><a href="#0xB-4-3-查ID列" class="headerlink" title="0xB.4.3 查ID列"></a>0xB.4.3 查ID列</h4><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">SELECT id FROM messages;</span><br></pre></td></tr></tbody></table></figure><h4 id="0xB-4-4-删除指定ID行"><a href="#0xB-4-4-删除指定ID行" class="headerlink" title="0xB.4.4 删除指定ID行"></a>0xB.4.4 删除指定ID行</h4><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">DELETE FROM messages WHERE id = 5;</span><br></pre></td></tr></tbody></table></figure><h4 id="0xB-4-5-删除指定多行ID"><a href="#0xB-4-5-删除指定多行ID" class="headerlink" title="0xB.4.5 删除指定多行ID"></a>0xB.4.5 删除指定多行ID</h4><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">DELETE FROM messages WHERE id IN (3, 4, 5);</span><br></pre></td></tr></tbody></table></figure><h4 id="0xB-4-6-查询指定ID列的值"><a href="#0xB-4-6-查询指定ID列的值" class="headerlink" title="0xB.4.6 查询指定ID列的值"></a>0xB.4.6 查询指定ID列的值</h4><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">SELECT * FROM messages WHERE id = 5;</span><br></pre></td></tr></tbody></table></figure><h4 id="0xB-4-7-查询指定多行ID列的值"><a href="#0xB-4-7-查询指定多行ID列的值" class="headerlink" title="0xB.4.7 查询指定多行ID列的值"></a>0xB.4.7 查询指定多行ID列的值</h4><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">SELECT * FROM messages WHERE id IN (3, 4, 5);</span><br></pre></td></tr></tbody></table></figure><h4 id="0xB-4-8-修改指定ID"><a href="#0xB-4-8-修改指定ID" class="headerlink" title="0xB.4.8 修改指定ID"></a>0xB.4.8 修改指定ID</h4><p>在这个语句中,<code>18</code> 替代了第一个 <code>?</code>,<code>21</code> 替代了第二个 <code>?</code>,而 <code>21</code> 替代了第三个 <code>?</code>。这样的话,当你执行这个SQL语句时,它会将 <code>messages</code> 表中 <code>rowid</code> 为 21,且 <code>id</code> 为 21 的行的 <code>id</code> 字段的值更新为 18。</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">UPDATE "messages" SET "id" = 18 WHERE "rowid" = 21 AND "id" = 21</span><br></pre></td></tr></tbody></table></figure><h4 id="0xB-4-9-SSH连接正在运行的docker"><a href="#0xB-4-9-SSH连接正在运行的docker" class="headerlink" title="0xB.4.9 SSH连接正在运行的docker"></a>0xB.4.9 SSH连接正在运行的docker</h4><figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker <span class="built_in">exec</span> -it <container_id_or_name> /bin/bash</span><br></pre></td></tr></tbody></table></figure><h4 id="0xB-4-A-docker中安装vim"><a href="#0xB-4-A-docker中安装vim" class="headerlink" title="0xB.4.A docker中安装vim"></a>0xB.4.A docker中安装vim</h4><figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">apt-get update</span><br><span class="line">apt-get install -y vim</span><br></pre></td></tr></tbody></table></figure><h4 id="0xB-4-B-重置id列自增值"><a href="#0xB-4-B-重置id列自增值" class="headerlink" title="0xB.4.B 重置id列自增值"></a>0xB.4.B 重置id列自增值</h4><p>查询当前最大值</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">SELECT MAX(id) FROM messages;</span><br></pre></td></tr></tbody></table></figure><p><strong>将自增ID的SEQ设置为最大ID值:</strong>使用以下SQL语句来设置自增ID列的序列(sequence)为当前最大ID值</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">UPDATE sqlite_sequence SET seq = 31 WHERE name = 'messages';</span><br></pre></td></tr></tbody></table></figure><p><strong>删除消息后需重启docker容器生效</strong></p><h2 id="0xC-运行效果"><a href="#0xC-运行效果" class="headerlink" title="0xC 运行效果"></a>0xC 运行效果</h2><p>等一会,加载图片比较慢</p><h3 id="0xC-1-大屏幕设备"><a href="#0xC-1-大屏幕设备" class="headerlink" title="0xC.1 大屏幕设备"></a>0xC.1 大屏幕设备</h3><p><img src="https://pic.imgdb.cn/item/6508153d204c2e34d39a873c.jpg"></p><p><img src="https://pic.imgdb.cn/item/6508153d204c2e34d39a874b.jpg"></p><h3 id="0xC-2-小屏幕设备"><a href="#0xC-2-小屏幕设备" class="headerlink" title="0xC.2 小屏幕设备"></a>0xC.2 小屏幕设备</h3><img src="https://pic.imgdb.cn/item/6508153e204c2e34d39a8784.png" style="zoom: 25%;"><img src="https://pic.imgdb.cn/item/6508153d204c2e34d39a875c.png" style="zoom:25%;">]]></content>
<categories>
<category> Node.js </category>
</categories>
<tags>
<tag> QNAP </tag>
</tags>
</entry>
<entry>
<title>基于MCreator+Fabric+Forge的自定义工作台</title>
<link href="/2023/07/01/2023-07-01-mcreator-zhi-zuo-zi-ding-yi-gong-zuo-tai/"/>
<url>/2023/07/01/2023-07-01-mcreator-zhi-zuo-zi-ding-yi-gong-zuo-tai/</url>
<content type="html"><![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>玩Minecraft已经有9年了一直都想给Minecraft写Mod,但苦于不会写代码并且几年前就连搭建开发环境都一直没有成功。现在MCreator可以解决这些问题,只需要几分钟就能设计出属于你的Mod</p><h2 id="名词释义"><a href="#名词释义" class="headerlink" title="名词释义"></a>名词释义</h2><p><a class="link" href="https://baike.baidu.com/item/%E6%B8%B8%E6%88%8F%E6%A8%A1%E7%BB%84/105567?fromtitle=MOD&fromid=2316956&fr=aladdin">模组<i class="fas fa-external-link-alt"></i></a> <code>Modification</code> 缩写为 <code>Mod</code> ,简体中文翻译为模组,它是游戏的一种修改或增强程序.比如玩家向游戏中添加了游戏本身不存在的物体使游戏更加有趣.</p><p><a class="link" href="https://mcreator.net/">MCreator<i class="fas fa-external-link-alt"></i></a>是开源软件,用于使用直观的易于学习的界面或集成的代码编辑器制作Minecraft Java版mod, Bedrock Edition Add-Ons和数据包。它在世界范围内被Minecraft玩家,mod开发者使用,<a class="link" href="https://mcreator.net/education">用于教育和STEM研讨会<i class="fas fa-external-link-alt"></i></a>。</p><blockquote><p>MCreator is open source software used to make Minecraft Java Edition mods, Bedrock Edition Add-Ons, and data packs using an intuitive easy-to-learn interface or with an integrated code editor. It is used worldwide by Minecraft players, mod developers, <a class="link" href="https://mcreator.net/education">for education and by STEM workshops<i class="fas fa-external-link-alt"></i></a>.</p></blockquote><p><a class="link" href="https://fabricmc.net/">Fabric<i class="fas fa-external-link-alt"></i></a>是一个轻量级的,实验性的Minecraft建模工具链。</p><blockquote><p>Fabric is a lightweight, experimental modding toolchain for Minecraft.</p></blockquote><p><a class="link" href="https://files.minecraftforge.net/net/minecraftforge/forge/">Forge<i class="fas fa-external-link-alt"></i></a>是Minecraft的主流API接口,其功能与Fabric相似</p><p><a class="link" href="https://www.minecraft.net/zh-hans">Minecraft《我的世界》<i class="fas fa-external-link-alt"></i></a>是一款3D沙盒电子游戏,由Mojang Studios开发。玩家可无拘无束地与由方块、实体构成的3个维度环境互动。</p><h2 id="参考链接"><a href="#参考链接" class="headerlink" title="参考链接"></a>参考链接</h2><ul><li><a class="link" href="https://www.bilibili.com/video/BV1S64y1b7cR/?vd_source=b7b560adb501335e6510131cc5584100">[MCreator]自定义工作台,GUI,脚本以及大家对MCR的误解-by PBS_我的世界_演示<i class="fas fa-external-link-alt"></i></a></li><li><a class="link" href="https://www.youtube.com/watch?v=oy28aQFdshU">MCREATOR TUTORIAL: Custom Crafting Table<i class="fas fa-external-link-alt"></i></a></li><li><a class="link" href="https://www.youtube.com/watch?v=J0D_2AvTbU0&t=909s">MCreator 5x5,7x7, or AnyxAny size Crafting Table.<i class="fas fa-external-link-alt"></i></a></li><li><a class="link" href="https://www.youtube.com/watch?v=0vnkebRDZfY&t=431s">MCreator Tutorial: How to make a furnace | 1.9.1<i class="fas fa-external-link-alt"></i></a></li><li><a class="link" href="https://www.youtube.com/watch?v=qdGJY7ljujM">MCreator Tutorial: Using procedures for a custom crafting block<i class="fas fa-external-link-alt"></i></a></li></ul><h2 id="搭建环境"><a href="#搭建环境" class="headerlink" title="搭建环境"></a>搭建环境</h2><h3 id="MCreator-Forge"><a href="#MCreator-Forge" class="headerlink" title="MCreator+Forge"></a>MCreator+Forge</h3><ol><li>打开科学上网</li><li>下载安装并打开<a class="link" href="https://mcreator.net/">MCreator<i class="fas fa-external-link-alt"></i></a></li><li>设置MCreator科学上网</li></ol><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"># 打开 C:\Users\username\.mcreator\gradle\gradle.properties</span><br><span class="line">systemProp.http.proxyHost=127.0.0.1</span><br><span class="line">systemProp.https.proxyHost=127.0.0.1</span><br><span class="line">systemProp.https.proxyPort=你的科学端口</span><br><span class="line">systemProp.http.proxyPort=你的科学端口</span><br><span class="line"></span><br><span class="line">#如果没有效果就安装Android Studio,打开 C:\Users\username\.gradle\gradle.properties</span><br><span class="line">systemProp.http.proxyHost=127.0.0.1</span><br><span class="line">systemProp.https.proxyHost=127.0.0.1</span><br><span class="line">systemProp.https.proxyPort=你的科学端口</span><br><span class="line">systemProp.http.proxyPort=你的科学端口</span><br></pre></td></tr></tbody></table></figure><ol start="4"><li><p>设置完保存并重启Mcreator</p></li><li><p>设置中文简体,依次点击:Preferences—Interface language—Chinese(China)—Apply—Save,设置完保存并重启Mcreator</p></li><li><p>新建工作区,工作区类型选择 <strong>Minecraft Forge 模组</strong>,模组显示名称 <strong>testcase</strong>,模组ID / 命名空间 <strong>testcase</strong>,Minercaft版本(生成器) **Minecraft Forge for 1.19.2 (43.2.0)**,模组Java包名称 <strong>str.stringod.testcase</strong>,工作区文件夹 <strong>D:\code\Minecraft\testcase</strong>,点击创建新的工作区。<em>如果Minecraft版本没有1.19.2可以选择任意版本,如果在新建工作区的时候提示Build Field类似的提示请重复第3-4步骤</em></p></li></ol><p>如果你想给旧版本开发模组,但是在Minecraft版本中没有找到,那么你需要在<a class="link" href="https://mcreator.net/plugins">MCreator plugins<i class="fas fa-external-link-alt"></i></a>下载对应版本,然后把下载的文件放在<code>.\MCreator\plugins\</code>目录下,然后重启MCreator</p><h3 id="MCreator-Fabric"><a href="#MCreator-Fabric" class="headerlink" title="MCreator+Fabric"></a>MCreator+Fabric</h3><ol><li>打开科学上网</li><li>下载安装并打开<a class="link" href="https://mcreator.net/">MCreator<i class="fas fa-external-link-alt"></i></a></li><li>设置MCreator科学上网</li></ol><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"># 打开 C:\Users\username\.mcreator\gradle\gradle.properties</span><br><span class="line">systemProp.http.proxyHost=127.0.0.1</span><br><span class="line">systemProp.https.proxyHost=127.0.0.1</span><br><span class="line">systemProp.https.proxyPort=你的科学端口</span><br><span class="line">systemProp.http.proxyPort=你的科学端口</span><br><span class="line"></span><br><span class="line">#如果没有效果就安装Android Studio,打开 C:\Users\username\.gradle\gradle.properties</span><br><span class="line">systemProp.http.proxyHost=127.0.0.1</span><br><span class="line">systemProp.https.proxyHost=127.0.0.1</span><br><span class="line">systemProp.https.proxyPort=你的科学端口</span><br><span class="line">systemProp.http.proxyPort=你的科学端口</span><br></pre></td></tr></tbody></table></figure><ol start="4"><li><p>设置完保存并重启Mcreator</p></li><li><p>设置中文简体,依次点击:Preferences—Interface language—Chinese(China)—Apply—Save,设置完保存并重启Mcreator</p></li><li><p>在<a class="link" href="https://mcreator.net/plugins">MCreator plugins<i class="fas fa-external-link-alt"></i></a>下载<a class="link" href="https://mcreator.net/plugin/64512/mcreator-fabric-generator">Fabric版本<i class="fas fa-external-link-alt"></i></a>,然后把下载的文件放在<code>.\MCreator\plugins\</code>目录下,然后重启MCreator</p></li><li><p>新建工作区,工作区类型选择 <strong>Minecraft Fabric 模组</strong>,模组显示名称 <strong>testcase</strong>,模组ID / 命名空间 <strong>testcase</strong>,Minercaft版本(生成器) <strong>Minecraft Fabric for 1.19.2 - 0.67.1</strong>,模组Java包名称 <strong>str.stringod.testcase</strong>,工作区文件夹 <strong>D:\code\Minecraft\testcase</strong>,点击创建新的工作区。<em>如果Minecraft版本没有1.19.2可以选择任意版本,如果在新建工作区的时候提示Build Field类似的提示请重复第3-4步骤</em></p></li></ol><h2 id="制作Forge模组"><a href="#制作Forge模组" class="headerlink" title="制作Forge模组"></a>制作Forge模组</h2><h3 id="方块外观"><a href="#方块外观" class="headerlink" title="方块外观"></a>方块外观</h3><ol><li>新建模组元素</li><li>方块</li><li>方块名称<strong>StringodCrafttable</strong></li><li>创建新方块</li><li>导入方块并使用纹理</li></ol><p><img src="https://pic.imgdb.cn/item/649fc3331ddac507cc668e4c.jpg" alt="1"></p><h3 id="方块属性"><a href="#方块属性" class="headerlink" title="方块属性"></a>方块属性</h3><ol><li>创造模式物品栏选项卡<strong>Building Blocks</strong></li><li>硬度<strong>15</strong></li><li>亮度<strong>15</strong></li><li>光不透明度<strong>0</strong></li><li>启用发光渲染</li><li>能够摧毁它的工具<strong>pickaxe</strong></li></ol><p><img src="https://pic.imgdb.cn/item/649fc3341ddac507cc668e91.jpg" alt="2"></p><h3 id="方块生成"><a href="#方块生成" class="headerlink" title="方块生成"></a>方块生成</h3><ol><li>此方块可以替换:删除可以替换的方块</li><li>保存模组元素</li></ol><p><img src="https://pic.imgdb.cn/item/649fc3341ddac507cc668f2d.jpg" alt="3"></p><h3 id="方块配方"><a href="#方块配方" class="headerlink" title="方块配方"></a>方块配方</h3><ol><li>新建模组元素</li><li>配方</li><li>配方名称<strong>MakeStringodCrafttable</strong></li><li>创建新配方</li><li>配方组名称<strong>stringod</strong></li></ol><p><img src="https://pic.imgdb.cn/item/649fc3341ddac507cc668fb3.jpg" alt="4"></p><p>添加完配方后,保存模组元素</p><h3 id="方块用户图形界面"><a href="#方块用户图形界面" class="headerlink" title="方块用户图形界面"></a>方块用户图形界面</h3><ol><li>新建模组元素</li><li>用户图形界面</li><li>用户图形界面名称<strong>UiStringodCrafttable</strong></li><li>创建新用户图形界面</li><li>在界面左侧按照顺序从左到右,从上到下依次添加9个<strong>输入槽</strong></li><li>在界面右侧添加一个<strong>输出槽</strong></li><li>在界面中间添加一个<strong>文本标签</strong>,文本<strong>———></strong></li></ol><p><img src="https://pic.imgdb.cn/item/649fc3341ddac507cc669059.jpg" alt="5"></p><p>添加完成后保存模组元素</p><h3 id="牛蛋配方流程"><a href="#牛蛋配方流程" class="headerlink" title="牛蛋配方流程"></a>牛蛋配方流程</h3><ol><li>新建模组元素</li><li>流程</li><li>流程名称<strong>CrafttableCowEgg</strong></li><li>创建新流程</li><li>流程模板</li><li>GUI Template - 3 x 3 Crafting table</li><li>然后删除非必要的代码块</li></ol><p><img src="https://pic.imgdb.cn/item/649fc3771ddac507cc67d8dc.jpg" alt="6"></p><p>添加好代码块,保存模组元素</p><h3 id="配方调用流程"><a href="#配方调用流程" class="headerlink" title="配方调用流程"></a>配方调用流程</h3><ol><li>新建模组元素</li><li>流程</li><li>流程名称<strong>StringodCrafttableCall</strong></li><li>创建新流程</li><li>点击左侧的<strong>高级</strong>,选择调用流程,选取<strong>CrafttableCowEgg</strong>流程</li></ol><p><img src="https://pic.imgdb.cn/item/649fc3771ddac507cc67d967.jpg" alt="7"></p><p>添加好代码块,保存模组元素</p><h3 id="方块绑定用户图形界面"><a href="#方块绑定用户图形界面" class="headerlink" title="方块绑定用户图形界面"></a>方块绑定用户图形界面</h3><ol><li>打开<strong>StringodCrafttable</strong></li><li>方块实体</li><li>为此方块启用实体功能</li><li>绑定此方块到图形界面:<strong>UiStringodCrafttable</strong></li><li>启用右键打开绑定的界面</li><li>物品栏大小(储存槽数量):10</li></ol><p><img src="https://pic.imgdb.cn/item/649fc3771ddac507cc67db94.jpg" alt="8"></p><p>完成后保存模组元素</p><h3 id="用户图形界面绑定配方调用流程"><a href="#用户图形界面绑定配方调用流程" class="headerlink" title="用户图形界面绑定配方调用流程"></a>用户图形界面绑定配方调用流程</h3><ol><li>打开<strong>UiStringodCrafttable</strong></li><li>界面左下角展开GUI流程触发器</li><li>当这个界面打开时,每刻发生<strong>StringodCrafttableCall</strong></li></ol><p><img src="https://pic.imgdb.cn/item/649fc3781ddac507cc67dc00.jpg" alt="9"></p><p>完成后保存模组元素</p><h3 id="用户图形界面绑定输入槽和输出槽配方调用流程"><a href="#用户图形界面绑定输入槽和输出槽配方调用流程" class="headerlink" title="用户图形界面绑定输入槽和输出槽配方调用流程"></a>用户图形界面绑定输入槽和输出槽配方调用流程</h3><ol><li>打开<strong>UiStringodCrafttable</strong></li><li>依次双击9个输入槽和1个输出槽</li><li>当储存槽内容改变时执行<strong>StringodCrafttableCall</strong></li></ol><p><img src="https://pic.imgdb.cn/item/649fc3781ddac507cc67dcfa.jpg" alt="10"></p><p>完成后保存模组元素</p><h2 id="构建并运行模组"><a href="#构建并运行模组" class="headerlink" title="构建并运行模组"></a>构建并运行模组</h2><p>点击右上角的绿色三角形运行游戏</p><p><img src="https://pic.imgdb.cn/item/649fc3b51ddac507cc68e929.gif" alt="11"></p><h2 id="制作Fabric模组"><a href="#制作Fabric模组" class="headerlink" title="制作Fabric模组"></a>制作Fabric模组</h2><h3 id="方块外观-1"><a href="#方块外观-1" class="headerlink" title="方块外观"></a>方块外观</h3><ol><li>新建模组元素</li><li>方块</li><li>方块名称<strong>StringodCrafttable</strong></li><li>创建新方块</li><li>导入方块并使用纹理</li></ol><p><img src="https://pic.imgdb.cn/item/649fc3331ddac507cc668e4c.jpg" alt="1"></p><h3 id="方块属性-1"><a href="#方块属性-1" class="headerlink" title="方块属性"></a>方块属性</h3><ol><li>创造模式物品栏选项卡<strong>Building Blocks</strong></li><li>硬度<strong>15</strong></li><li>亮度<strong>15</strong></li><li>光不透明度<strong>0</strong></li><li>启用发光渲染</li><li>能够摧毁它的工具<strong>pickaxe</strong></li></ol><p><img src="https://pic.imgdb.cn/item/649fc3341ddac507cc668e91.jpg" alt="2"></p><h3 id="方块生成-1"><a href="#方块生成-1" class="headerlink" title="方块生成"></a>方块生成</h3><ol><li>此方块可以替换:删除可以替换的方块</li><li>保存模组元素</li></ol><p><img src="https://pic.imgdb.cn/item/649fc3341ddac507cc668f2d.jpg" alt="3"></p><h3 id="方块配方-1"><a href="#方块配方-1" class="headerlink" title="方块配方"></a>方块配方</h3><ol><li>新建模组元素</li><li>配方</li><li>配方名称<strong>MakeStringodCrafttable</strong></li><li>创建新配方</li><li>配方组名称<strong>stringod</strong></li></ol><p><img src="https://pic.imgdb.cn/item/649fc3341ddac507cc668fb3.jpg" alt="4"></p><p>添加完配方后,保存模组元素</p><h3 id="方块用户图形界面-1"><a href="#方块用户图形界面-1" class="headerlink" title="方块用户图形界面"></a>方块用户图形界面</h3><ol><li>新建模组元素</li><li>用户图形界面</li><li>用户图形界面名称<strong>UiStringodCrafttable</strong></li><li>创建新用户图形界面</li><li>在界面左侧按照顺序从左到右,从上到下依次添加9个<strong>输入槽</strong></li><li>在界面右侧添加一个<strong>输出槽</strong></li><li>在界面中间添加一个<strong>文本标签</strong>,文本<strong>———></strong></li></ol><p><img src="https://pic.imgdb.cn/item/649fc3341ddac507cc669059.jpg" alt="5"></p><p>添加完成后保存模组元素</p><h3 id="牛蛋配方流程-1"><a href="#牛蛋配方流程-1" class="headerlink" title="牛蛋配方流程"></a>牛蛋配方流程</h3><ol><li>新建模组元素</li><li>流程</li><li>流程名称<strong>CrafttableCowEgg</strong></li><li>创建新流程</li><li>流程模板</li><li>GUI Template - 3 x 3 Crafting table</li><li>然后删除非必要的代码块</li></ol><p><img src="https://pic.imgdb.cn/item/649fc3771ddac507cc67d8dc.jpg" alt="6"></p><p>添加好代码块,保存模组元素</p><h3 id="配方调用流程-1"><a href="#配方调用流程-1" class="headerlink" title="配方调用流程"></a>配方调用流程</h3><ol><li>新建模组元素</li><li>流程</li><li>流程名称<strong>StringodCrafttableCall</strong></li><li>创建新流程</li><li>点击左侧的<strong>高级</strong>,选择调用流程,选取<strong>CrafttableCowEgg</strong>流程</li></ol><p><img src="https://pic.imgdb.cn/item/649fc3771ddac507cc67d967.jpg" alt="7"></p><p>添加好代码块,保存模组元素</p><h3 id="方块绑定用户图形界面-1"><a href="#方块绑定用户图形界面-1" class="headerlink" title="方块绑定用户图形界面"></a>方块绑定用户图形界面</h3><ol><li>打开<strong>StringodCrafttable</strong></li><li>方块实体</li><li>为此方块启用实体功能</li><li>绑定此方块到图形界面:<strong>UiStringodCrafttable</strong></li><li>启用右键打开绑定的界面</li><li>物品栏大小(储存槽数量):10</li></ol><p><img src="https://pic.imgdb.cn/item/649fc3771ddac507cc67db94.jpg" alt="8"></p><p>完成后保存模组元素</p><h3 id="用户图形界面绑定配方调用流程-1"><a href="#用户图形界面绑定配方调用流程-1" class="headerlink" title="用户图形界面绑定配方调用流程"></a>用户图形界面绑定配方调用流程</h3><ol><li>打开<strong>UiStringodCrafttable</strong></li><li>界面左下角展开GUI流程触发器</li><li>当这个界面打开时<strong>StringodCrafttableCall</strong></li></ol><p><strong>Fabric 1.19.2版本的插件显然不支持在图形用户界面每刻更新,所以只能在第二次选择当打开图形界面时更新配方</strong></p><p><img src="https://pic.imgdb.cn/item/649fca001ddac507cc79720c.jpg" alt="9_1"></p><p>完成后保存模组元素</p><h3 id="用户图形界面绑定输入槽和输出槽配方调用流程-1"><a href="#用户图形界面绑定输入槽和输出槽配方调用流程-1" class="headerlink" title="用户图形界面绑定输入槽和输出槽配方调用流程"></a>用户图形界面绑定输入槽和输出槽配方调用流程</h3><ol><li>打开<strong>UiStringodCrafttable</strong></li><li>依次双击9个输入槽和1个输出槽</li><li>当储存槽内容改变时执行<strong>StringodCrafttableCall</strong></li></ol><p><img src="https://pic.imgdb.cn/item/649fc3781ddac507cc67dcfa.jpg" alt="10"></p><p>完成后保存模组元素</p><h2 id="构建并运行模组-1"><a href="#构建并运行模组-1" class="headerlink" title="构建并运行模组"></a>构建并运行模组</h2><p>点击右上角的绿色三角形运行游戏</p><p><img src="https://pic.imgdb.cn/item/649fca031ddac507cc797842.gif" alt="10_2"></p><h2 id="导出模组"><a href="#导出模组" class="headerlink" title="导出模组"></a>导出模组</h2><ol><li>点击MCreator窗口顶部菜单栏中的<strong>工作区</strong></li><li>导出模组用于分发</li><li>导出模组而不捐赠,<em>如果你有多余的钱可以捐赠</em></li><li>选择一个位置并给你的模组命名</li></ol><h2 id="下载示例模组"><a href="#下载示例模组" class="headerlink" title="下载示例模组"></a>下载示例模组</h2><p>目前不直接提供下载,防止某些三方网站没有经过允许私自转发售卖,有需要可以发邮件告诉我</p>]]></content>
<categories>
<category> Minecraft </category>
</categories>
<tags>
<tag> MCreator </tag>
</tags>
</entry>
<entry>
<title>植物大战僵尸阳光修改器</title>
<link href="/2023/06/24/2023-06-24-zhi-wu-da-zhan-jiang-shi-yang-guang-xiu-gai-qi/"/>
<url>/2023/06/24/2023-06-24-zhi-wu-da-zhan-jiang-shi-yang-guang-xiu-gai-qi/</url>
<content type="html"><![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p><strong>一直都想搞一个植物大战僵尸的修改器,想通过学习自己制作一个</strong></p><p>打算使用Cheat Engine修改器找到阳光的真正地址,然后再用易语言制作一个针对植物大战僵尸的阳光修改器</p><h2 id="名词释义"><a href="#名词释义" class="headerlink" title="名词释义"></a>名词释义</h2><p><a class="link" href="https://store.steampowered.com/app/3590/Plants_vs_Zombies_GOTY_Edition/">植物大战僵尸<i class="fas fa-external-link-alt"></i></a>:可怕的僵尸即将入侵你的家,唯一的防御方式就是你栽种的植物!武装你的植物,切换他们不同的功能,诸如强悍的豌豆射手或樱桃炸弹,更加快速有效的将僵尸阻挡在入侵的道路上。不同的敌人,不同的玩法构成五种不同的游戏模式,加之夕阳、浓雾以及泳池之类的障碍增加了其挑战性,奇特的游戏乐趣永无止境!</p><p><a class="link" href="https://cheatengine.org/">Cheat Engine<i class="fas fa-external-link-alt"></i></a>:是一款旨在帮助您在没有互联网连接的情况下修改单人游戏的修改器,以便您可以根据自己的喜好使游戏变得更难或更容易(例如:发现 100hp 太容易了,尝试玩最大 1 HP 的游戏),简称CE</p><blockquote><p>Cheat Engine is a tool designed to help you with modifying single player games without internet connection so you can make them harder or easier depending on your preference(e.g: Find that 100hp is too easy, try playing a game with a max of 1 HP)</p></blockquote><p><a class="link" href="http://www.dywt.com.cn/">易语言<i class="fas fa-external-link-alt"></i></a>:是一个自主开发,适合国情,不同层次不同专业的人员易学易用的汉语编程语言。易语言降低了广大电脑用户编程的门槛,尤其是根本不懂英文或者英文了解很少的用户,可以通过使用本语言极其快速地进入Windows程序编写的大门</p><h2 id="功能"><a href="#功能" class="headerlink" title="功能"></a>功能</h2><ul><li>设置一个很大的阳光数值</li></ul><h2 id="下载地址"><a href="#下载地址" class="headerlink" title="下载地址"></a>下载地址</h2><p>目前不直接提供下载,防止某些三方网站没有经过允许私自转发售卖,有需要可以发邮件告诉我</p><h2 id="参考链接"><a href="#参考链接" class="headerlink" title="参考链接"></a>参考链接</h2><ul><li><a class="link" href="https://isbase.cc/tip/gamesecurity/second/game2.html">游戏安全系列教程-植物大战僵尸(一)<i class="fas fa-external-link-alt"></i></a></li><li><a class="link" href="https://www.52pojie.cn/thread-1028632-1-1.html">植物大战僵尸修改器DIY(一)<i class="fas fa-external-link-alt"></i></a></li></ul><h2 id="准备工具"><a href="#准备工具" class="headerlink" title="准备工具"></a>准备工具</h2><ul><li>Cheat Engine v6.7</li><li>易语言 v5.9</li><li>易语言超级模块 v9.2</li></ul><h2 id="基础概念"><a href="#基础概念" class="headerlink" title="基础概念"></a>基础概念</h2><h3 id="内存"><a href="#内存" class="headerlink" title="内存"></a>内存</h3><blockquote><p><strong>内存,一切皆内存,我们操作的全部都是内存。</strong></p><p>所有地址相关的概念,都可以理解为坐标,用来给我们做标记的而已。</p><p>如果实在搞不明白,你想想指南针为啥指向北边,为啥叫南北,只是定义,只是公认</p></blockquote><h3 id="基址"><a href="#基址" class="headerlink" title="基址"></a>基址</h3><blockquote><p>以阳光为例,由于内存是动态分配的,我们每一次搜索存放阳光的内存地址都会不一样。</p><p>基址,就是指每一次分配我们都能通过偏移,来找到动态的地址,一般是由<strong>模块地址+固定的偏移</strong>实现。</p><p>例:我知道阳光在内存中存放地址,是模块地址+400的内存位置,所以阳光基址就一定是模块地址+400。所以动态就变成了,30格的时候,30+400。在40格的时候,40+400了。</p></blockquote><h3 id="指针"><a href="#指针" class="headerlink" title="指针"></a>指针</h3><blockquote><p>指针其实就可以理解成外号,你的外号叫二狗子,别人先知道你的外号,然后熟悉了才可以根据外号找到你的真名。</p><p>更多的用处是引用,就像别人往往更愿意叫你二狗子,而不愿意叫你真名一样。</p><p>例:一个指针叫做point,他指向内存的0xFF923200。那他就可以有两个作用,一个就是直接使用指向的内存地址FF923200;另一个可以直接查到FF923200里的值。</p></blockquote><h3 id="为什么要找基址"><a href="#为什么要找基址" class="headerlink" title="为什么要找基址"></a>为什么要找基址</h3><blockquote><p>因为游戏退出基址不会变化,只有游戏更新后才会变化,而要是用找到的内存地址,例如:血的内存地址,游戏退出是会变化的,不方便我们编写辅助调用。</p></blockquote><h2 id="寻找阳光的数量的地址"><a href="#寻找阳光的数量的地址" class="headerlink" title="寻找阳光的数量的地址"></a>寻找阳光的数量的地址</h2><p><strong>需要注意的是每次、每个人搜索的地址都是不是一样的,所以只需要和我的步骤保持一致就行</strong></p><p>首先打开游戏,使用CE加载游戏进程。</p><p><img src="https://pic.imgdb.cn/item/649698281ddac507cc5b16b7.jpg"></p><p>进入游戏,在CE的数值处输入起始默认的50阳光,然后点击<strong>首次扫描</strong></p><p><img src="https://pic.imgdb.cn/item/649698281ddac507cc5b16d1.jpg"></p><p>选择好要加入战斗的植物,然后种下一个向日葵,此时阳光数量变为0,再次使用CE修改数值为0,然后点击<strong>再次扫描</strong></p><p><img src="https://pic.imgdb.cn/item/649698281ddac507cc5b16fa.jpg"></p><p>此时列表中出现了两个结果,第二个结果的<strong>当前值</strong>就是阳光现在的数量,选中这个结果右键选择<strong>找出是什么改写了这个地址</strong>,然后在弹出的确认提示框中点击<strong>Yes</strong>,然后回到游戏收获一个阳光</p><p><img src="https://pic.imgdb.cn/item/649698281ddac507cc5b1744.jpg"></p><p>选中<code>0041F4D0 - 01 88 78550000 - add [eax+00005578],ecx</code>这一行点击右侧的<strong>详细信息</strong>,然后在弹出的窗口中复制这个红色框的地址,需要记住的是这个蓝色框的地址<strong>5578</strong></p><p><img src="https://pic.imgdb.cn/item/649698281ddac507cc5b1799.jpg"></p><p>勾选Hex复选框,输入刚才复制的红色框地址,点击<strong>新的扫描</strong>,然后再次点击<strong>首次扫描</strong>会看到有47个结果,首先关注蓝色框地址这一列,如果没有绿色的地址那么就从上自下找一个地址相差比较大的,比如红色框中的地址就和上面的地址相差比较大。选中这个红色框的地址右键选择<strong>找出是什么访问了这个地址</strong></p><p><img src="https://pic.imgdb.cn/item/6496988c1ddac507cc5ba804.jpg"></p><p>和第5步骤相同,在弹出的窗口中可以看到有很多指令,如果你的窗口中没有任何指令,可以在游戏中拾取1个阳光。选择第一个地址<code>004577C3 - 8B B7 68080000 - mov esi,[edi+00000868]</code>然后点击右侧的详细信息,在弹出的窗口中可以看到和之前一样的界面,这里同样复制红色框中的地址,并手动记录下蓝色框的数字<code>868</code></p><p><img src="https://pic.imgdb.cn/item/6496988c1ddac507cc5ba83a.jpg"></p><p>和第6步骤相同,把刚才复制的地址粘贴到数字窗口并确保你已经选中了<strong>Hex复选框</strong>,然后点击<strong>新的扫描</strong>,再次点击<strong>首次扫描</strong>,到这里你应该能在地址列看到绿色的地址,当然如果你并没有看到绿色的地址,那么你可以向下滚动鼠标。如果你依然没有找到绿色的地址,那么请重复以上的步骤直到找到绿色的地址。当你找到绿色的地址时选中其中一个地址并双击会被添加到下面的窗口</p><p><img src="https://pic.imgdb.cn/item/6496988c1ddac507cc5ba879.jpg"></p><p>双击地址00731C50会打开一个小窗口,取消勾选<strong>十六进制</strong>,勾选<strong>指针</strong>,填写刚才手动记住的两个值5578和868,这里的5578和868是阳光地址的偏移量,然后你会看到这个窗口中的地址20994C40后面的数字是25,这就是游戏中阳光的真正地址和阳光的真实数值,然后点击确定</p><p><img src="https://pic.imgdb.cn/item/6496988c1ddac507cc5ba8cd.jpg"></p><p>你会看到在下面窗口中刚才的地址00731C50已经变成了P->20994C40,然后导出保存这个地址,下次打开CE直接加载这个文件就可以直接修改。需要注意的是当前这个地址的数值还是25,你需要双击数值并修改成你需要的一个数值,比如4789。下次打开这个脚本就可以直接修改阳光为4789</p><p><img src="https://pic.imgdb.cn/item/6496988c1ddac507cc5ba927.jpg"></p><h2 id="使用CE生成修改器"><a href="#使用CE生成修改器" class="headerlink" title="使用CE生成修改器"></a>使用CE生成修改器</h2><p>在CE中依次点击<strong>文件</strong>—<strong>从表单中生成通用修改器Lua脚本</strong>,点击添加热键设置激活的键位和功能描述,也可以设置激活和禁用的声音,修改完成可以设置修改器的程序名称、标题、程序图标。这里需要注意的是一定要确保CE和CE要生成的修改器名称和路径都是英文并不含<strong>特殊字符</strong>的,否则会生成失败。</p><blockquote><p>特殊字符:\ / * ? : | “<> . , ; : </p></blockquote><p><img src="https://pic.imgdb.cn/item/649698bc1ddac507cc5be82f.jpg"></p><p>下次打开游戏就可以直接使用CE生成的修改器来修改游戏的阳光数量了。如果你的修改器没有生效,但是你发现却可以使用之前导出的P->20994C40地址的脚本来修改游戏,在这种情况下最好还是使用其他语言编写修改器来达到和CE生成修改器一样的目的</p><h2 id="使用易语言编写修改器"><a href="#使用易语言编写修改器" class="headerlink" title="使用易语言编写修改器"></a>使用易语言编写修改器</h2><h3 id="搭建环境"><a href="#搭建环境" class="headerlink" title="搭建环境"></a>搭建环境</h3><p>新建一个Windows窗口程序,创建好之后双击窗口会打开代码编辑页面</p><p><img src="https://pic.imgdb.cn/item/649698bc1ddac507cc5be84a.jpg"></p><p>首先需要把<strong>超级模块</strong>加入<strong>模块引用列表</strong></p><p><img src="https://pic.imgdb.cn/item/649698bc1ddac507cc5be876.jpg"></p><h3 id="编写代码"><a href="#编写代码" class="headerlink" title="编写代码"></a>编写代码</h3><h4 id="思路"><a href="#思路" class="headerlink" title="思路"></a>思路</h4><p>首先根据之前CE的操作步骤,我们可以先记录下每一个步骤的关键信息</p><ol><li>首先使用CE打开了游戏进程</li><li>寻找阳光在当前游戏进程中的地址</li><li>根据阳光的地址找到偏移量</li><li>根据阳光的地址偏移量找到了游戏基址</li><li>根据游戏基址又找到了阳光的真实地址</li><li>最后生成修改器</li></ol><h4 id="开始编写"><a href="#开始编写" class="headerlink" title="开始编写"></a>开始编写</h4><p>首先创建四个程序集变量,如果担心出问题可以按照我的写,接着空白处往下写就行</p><p><img src="https://pic.imgdb.cn/item/649698bc1ddac507cc5be8a0.jpg"></p><p>根据游戏的进程名获取进程ID,从CE界面的上面不难发现进程名popcapgame1.exe</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">进程ID = 取进程ID (“popcapgame1.exe”)</span><br></pre></td></tr></tbody></table></figure><p>然后打开CE修改器就是刚才导出的脚本,看到下面红色框出来的地址00731C50,这个地址就是游戏的基址</p><p><img src="https://pic.imgdb.cn/item/6496988c1ddac507cc5ba8cd.jpg"></p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">游戏基址 = 读内存整数型 (进程ID, 十六到十 (“00731C50”))</span><br></pre></td></tr></tbody></table></figure><p>然后在游戏的基址之上加入5578和868的偏移量,切记先加868,然后才是5578</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">阳光偏移 = 读内存整数型 (进程ID, 游戏基址 + 十六到十 (“868”))</span><br><span class="line">阳光基址 = 阳光偏移 + 十六到十 (“5578”)</span><br></pre></td></tr></tbody></table></figure><p>然后就可以设置阳光的数量了,这块的<code>到整数 (阳光基址) + 54188</code>是每次打开修改器修改的阳光数量,值得注意的是每次打开游戏时阳光基址都会变在加上54188就会变得更大。这里的阳光基址相对于游戏基址不会改变,因为每次打开游戏的游戏基址都并不是固定的,阳光基址是在游戏基址基础之上确定的相对的地址</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">写内存整数型 (进程ID, 阳光基址, 到整数 (阳光基址) + 54188)</span><br></pre></td></tr></tbody></table></figure><p>然后保存代码,设置窗口标题和背景图</p><p><img src="https://pic.imgdb.cn/item/649698bc1ddac507cc5be8f8.jpg"></p><p>还可以设置程序的图标和个人信息</p><p><img src="https://pic.imgdb.cn/item/649699361ddac507cc5c8f91.jpg"></p><h2 id="调试并运行修改器"><a href="#调试并运行修改器" class="headerlink" title="调试并运行修改器"></a>调试并运行修改器</h2><p>打开游戏进入战斗界面,就可以点击红色框的运行修改器,蓝色框为停止运行修改器。<strong>如果你的修改器不能正常工作,请停止运行修改器并检查重复以上步骤</strong></p><p><img src="https://pic.imgdb.cn/item/649699361ddac507cc5c8fef.jpg"></p><h2 id="生成修改器"><a href="#生成修改器" class="headerlink" title="生成修改器"></a>生成修改器</h2><p>依次点击编译—独立编译,在弹出的对话框中点击确定,然后选择你要放在哪个位置下</p><p><img src="https://pic.imgdb.cn/item/649699371ddac507cc5c9042.jpg"></p><h2 id="效果图"><a href="#效果图" class="headerlink" title="效果图"></a>效果图</h2><p><img src="https://pic.imgdb.cn/item/649699371ddac507cc5c90ad.jpg"></p>]]></content>
<categories>
<category> 易 </category>
</categories>
<tags>
<tag> 植物大战僵尸 </tag>
</tags>
</entry>
<entry>
<title>Minecraft BSL 汉化包</title>
<link href="/2022/11/26/2022-11-26-minecraft-bsl-han-hua-bao/"/>
<url>/2022/11/26/2022-11-26-minecraft-bsl-han-hua-bao/</url>
<content type="html"><![CDATA[<p><a class="link" href="https://bitslablab.com/bslshaders/">BSL<i class="fas fa-external-link-alt"></i></a> 是 Minecraft 的光影包,前两天在官网下载了最新版发现没有中文支持,然后自己就花了两天时间汉化完了。</p><h2 id="需要注意的"><a href="#需要注意的" class="headerlink" title="需要注意的"></a>需要注意的</h2><ul><li>只有一两处还是英文,这两个还有英文的地方我在原英文语言文件中没找到,应该是其他我不知道文件中的,不过总体上不影响使用。</li><li>有几个单词翻译过来比较奇怪,保守一点就没翻译。</li><li>中文语言文件位置 <code>BSL_v8.2.zip\shaders\lang\zh_CN.lang</code>,有翻译不准确的兄弟们自己看着改吧!</li></ul><h2 id="实际效果图"><a href="#实际效果图" class="headerlink" title="实际效果图"></a>实际效果图</h2><p><img src="https://pic.imgdb.cn/item/6380cd6f16f2c2beb121edae.jpg"></p><p><img src="https://pic.imgdb.cn/item/6380cd6f16f2c2beb121ed6d.jpg"></p><p><img src="https://pic.imgdb.cn/item/6380cd6f16f2c2beb121ed93.jpg"></p><p><img src="https://pic.imgdb.cn/item/6380cd6f16f2c2beb121eda0.jpg"></p><h2 id="下载"><a href="#下载" class="headerlink" title="下载"></a>下载</h2><p><a class="link" href="https://www.52pojie.cn/thread-1718700-1-1.html">不想替换语言文件的兄弟可以直接拿去用<i class="fas fa-external-link-alt"></i></a></p><p>请确保您具有<a class="link" href="https://www.52pojie.cn/">吾爱破解<i class="fas fa-external-link-alt"></i></a>的账号,本帖的权限设置旨在防止第三方网站未经告知私自获取</p><h2 id="汉化思路"><a href="#汉化思路" class="headerlink" title="汉化思路"></a>汉化思路</h2><ol><li>先打开光影包找到语言文件路径 <code>BSL_v8.2.zip\shaders\lang\en_US.lang</code> ,从文件名不难看出 <code>lang</code> 应该是 <code>language</code> 语言的缩写,其次是 <code>en_US</code> 这表示 <strong>英语 美国</strong> ,直接百度查一下 <strong>en_US</strong> 是什么意思,然后根据这个意思搜索 <strong>中文简体</strong> 的表示方法</li><li>把 <code>en_US.lang</code> 文件解压出来,重命名为 <code>zh_CN.lang</code>,准备汉化</li><li>打开文件看到第一行是 <code>#shaders/lang/en_us.lang</code> 把 <code>en_us.lang</code> 改成对应的 <code>zh_cn.lang</code></li><li>从第3行不难看出 <strong>=</strong> 前面的是代码,后面的则是要翻译的语言,并且 <strong>#</strong> 是注释后面写什么都行</li></ol><figure class="highlight xml"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">#Profiles en_US</span><br><span class="line">profile.LOW=Low</span><br><span class="line">profile.MEDIUM=Medium</span><br><span class="line">profile.HIGH=High</span><br><span class="line">profile.ULTRA=Ultra</span><br><span class="line"></span><br><span class="line">#Profiles zh_cn</span><br><span class="line">profile.LOW=最低</span><br><span class="line">profile.MEDIUM=中等</span><br><span class="line">profile.HIGH=高</span><br></pre></td></tr></tbody></table></figure><p>全部翻译完就可以把文件压缩到与 <code>en_US.lang</code> 同一目录,导入游戏就可以用了</p><p><del>我才发现我前面几行连代码都改成中文了,第一张图里</del></p>]]></content>
<categories>
<category> Minecraft </category>
</categories>
<tags>
<tag> 汉化 </tag>
</tags>
</entry>
<entry>
<title>Absolute Pace 绝对和平的生存</title>
<link href="/2022/10/01/2022-03-02-jue-dui-he-ping-de-sheng-cun/"/>
<url>/2022/10/01/2022-03-02-jue-dui-he-ping-de-sheng-cun/</url>
<content type="html"><![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>在 Minecraft 世界中体验各式各样的探险、生存与建造乐趣!</p><p><strong>想在和平模式下玩生存模式,但是和平模式不生成敌对怪物,所以需要一种方式代替在困难度下生成怪物凋落物。</strong></p><p>模组:游戏允许玩家加入自己对游戏某些功能的修改,可以提高游戏的趣味性</p><p>和平模式:没有难度、没有敌对势力生成、玩家受到非致死伤害时自动恢复生命</p><p>怪物的凋落物:一般情况下能够获得怪物的凋落物,意味着玩家困难度设置的是“简单、中等、困难”</p><h2 id="模组介绍"><a href="#模组介绍" class="headerlink" title="模组介绍"></a>模组介绍</h2><p>这个模组名为 Absolute Pace,它的作用是向游戏中添加模拟怪物被击杀时的凋落物。</p><h2 id="扩展模块"><a href="#扩展模块" class="headerlink" title="扩展模块"></a>扩展模块</h2><p>扩展模块将突破绝对和平的限制,将会围绕有困难度的模式下做出修改。</p><p>未来可能添加的扩展模块:</p><ul><li><input checked="" disabled="" type="checkbox"> 安全的矿物世界</li><li><input disabled="" type="checkbox"> 绝对和平专用合成工作台、熔炉、烟熏炉</li><li><input checked="" disabled="" type="checkbox"> 绝对和平专用方块、箱子、发光方块</li><li><input checked="" disabled="" type="checkbox"> 更加简单的物品合成配方、合成数量</li><li><input checked="" disabled="" type="checkbox"> 生物物品掉落盒</li><li><input checked="" disabled="" type="checkbox"> 蜘蛛恐惧症模式</li></ul><h2 id="现在已经具备的功能"><a href="#现在已经具备的功能" class="headerlink" title="现在已经具备的功能"></a>现在已经具备的功能</h2><p>理论上支持1.16.5以上的版本</p><h3 id="和平模式下提供更多非可合成的配方"><a href="#和平模式下提供更多非可合成的配方" class="headerlink" title="和平模式下提供更多非可合成的配方"></a>和平模式下提供更多非可合成的配方</h3><ul><li>牛奶合成恶魂之泪【<strong>牛奶</strong> 和 <strong>恶魂之泪</strong>毕竟都是 <strong>白色</strong> 的</li></ul><p><img src="https://pic.imgdb.cn/item/621f68cb5baa1a80ab016a5c.jpg" alt="恶魂之泪"></p><ul><li>压缩书架【解压缩书架时不知道为什么多出来了很多个</li></ul><p><img src="https://pic.imgdb.cn/item/621f68cb5baa1a80ab016a57.jpg" alt="压缩书架"></p><ul><li>三叉戟【昨晚溺尸把三叉戟的制作配方传授给了我</li></ul><p><img src="https://pic.imgdb.cn/item/621f68cb5baa1a80ab016a60.jpg" alt="三叉戟"></p><ul><li>这两天熬夜和史蒂夫肝了更多的配方<ul><li>可以合成大部分敌对生物的凋落物</li><li>降低了常用物品合成难度:比如9个书架能合成64个书架</li><li>可以合成怪物蛋、各种马鞍、烟花、潜影盒、鞘翅、烈焰棒、暗影珍珠</li></ul></li></ul><h3 id="更多安全的维度"><a href="#更多安全的维度" class="headerlink" title="更多安全的维度"></a>更多安全的维度</h3><blockquote><p>安全的维度:不会自然生成敌对怪物和友善的生物,只有你才能进入的世界</p></blockquote><ul><li>铁块空岛【魔改末地</li></ul><p><img src="https://pic.imgdb.cn/item/621f6e2e5baa1a80ab063381.png" alt="铁块空岛"></p><ul><li>绿宝石大陆</li></ul><p><img src="https://pic.imgdb.cn/item/621f6e2e5baa1a80ab063393.png" alt="绿宝石大陆"></p><ul><li>受支持的高级维度还包括:<ul><li>黄金块空岛</li><li>钻石块大陆</li><li>煤炭大陆</li><li>石英石大陆</li></ul></li></ul><h3 id="更多的独有特殊方块"><a href="#更多的独有特殊方块" class="headerlink" title="更多的独有特殊方块"></a>更多的独有特殊方块</h3><blockquote><p>特殊方块:具有象征性意义的方块,在未来的规划中将会添加新的工作模式至该方块【看上去很炫的样子在夜晚</p></blockquote><ul><li>就是用来滑稽的</li></ul><p><img src="https://pic.imgdb.cn/item/621f700c5baa1a80ab085714.png" alt="QQ头像方块"></p><ul><li>微信和博客头像的方块【目前每10s能生成一个绿宝石</li></ul><p><img src="https://pic.imgdb.cn/item/621f700c5baa1a80ab08571a.png" alt="微信和博客头像的方块"></p><p><img src="https://pic.imgdb.cn/item/621f72f45baa1a80ab0ad7fb.png" alt="绿宝石盒"></p><h3 id="高级燃料"><a href="#高级燃料" class="headerlink" title="高级燃料"></a>高级燃料</h3><blockquote><p>高级燃料:可燃烧更长的时间</p><p>水桶:据说地球上水资源是古老文明最强大的能量</p><p>岩浆桶:地球内部喷出地表的热腾腾液体</p></blockquote><ul><li>水桶:提供四分钟的燃烧时间</li><li>岩浆桶:提供八分钟的燃烧时间</li></ul><h3 id="怪物盒:模拟怪物被干死之后掉落的物品【复现了不支持当前版本的-Tiny-Farm"><a href="#怪物盒:模拟怪物被干死之后掉落的物品【复现了不支持当前版本的-Tiny-Farm" class="headerlink" title="怪物盒:模拟怪物被干死之后掉落的物品【复现了不支持当前版本的 Tiny Farm"></a>怪物盒:模拟怪物被干死之后掉落的物品【复现了不支持当前版本的 <strong>Tiny Farm</strong></h3><blockquote><p>预计添加所有生物的怪物盒,这其中包含了敌对生物盒和友善的生物</p></blockquote><ul><li>蜘蛛盒</li></ul><p><img src="https://pic.imgdb.cn/item/621f69135baa1a80ab0195bb.png" alt="蜘蛛盒"></p><ul><li>烈焰人盒</li><li>骷髅生物盒</li><li>苦力怕生物盒</li><li>粘液怪生物盒</li><li>岩浆怪生物盒</li><li>末影人生物盒</li><li>僵尸生物盒</li><li>溺尸生物盒</li><li>僵尸猪人生物盒</li><li>唤魔者生物盒</li><li>掠夺者生物盒</li></ul><h3 id="打印机:可以复制物品"><a href="#打印机:可以复制物品" class="headerlink" title="打印机:可以复制物品"></a>打印机:可以复制物品</h3><p><img src="https://pic.imgdb.cn/item/6223602f5baa1a80ab8c8149.gif" alt="打印机:复制物品"></p><h3 id="蜘蛛恐惧症模式"><a href="#蜘蛛恐惧症模式" class="headerlink" title="蜘蛛恐惧症模式"></a>蜘蛛恐惧症模式</h3><ul><li>当新的实体生成时会干掉距离玩家50个方块之内的所有蜘蛛</li></ul><h3 id="矿石"><a href="#矿石" class="headerlink" title="矿石"></a>矿石</h3><ul><li>经验矿石:生成范围1-64层,掉落1236经验</li></ul><h3 id="武器"><a href="#武器" class="headerlink" title="武器"></a>武器</h3><p><img src="https://pic.imgdb.cn/item/6300d65f16f2c2beb1e563c8.png" alt="40米大刀"></p><ul><li>40米大刀:你有事么?!先跟我的40米大刀聊聊吧<ul><li>攻击速度为6,是钻石剑攻击速度的3.75倍</li><li>附魔能力提升至69</li><li>对生物的攻击伤害提升至369</li><li>使用次数耐久设置为了3699</li></ul></li></ul><h3 id="工具"><a href="#工具" class="headerlink" title="工具"></a>工具</h3><p><img src="https://pic.imgdb.cn/item/6300d65f16f2c2beb1e563db.png" alt="铁匠稿"></p><ul><li>铁匠稿:给铁匠塞了 <strong>亿</strong> 点点钱,他就把祖传的 <strong>铁匠稿</strong> 送给了我<ul><li>效率为36,相当于效率Ⅴ附魔书的1.38倍</li><li>附魔能力设置为了69</li><li>攻击速度设置为了1</li><li>对生物的攻击伤害设置为了9</li><li>使用次数耐久设置为了3699</li></ul></li></ul><p><img src="https://pic.imgdb.cn/item/6300d65f16f2c2beb1e563cb.png" alt="斧子"></p><ul><li>斧子:woc,你拿我斧子干啥了?!咋变成屎黄色了?<ul><li>效率为:36</li><li>附魔能力:69</li><li>攻击速度:1</li><li>攻击伤害:9</li><li>耐久:3699</li></ul></li></ul><p><img src="https://pic.imgdb.cn/item/6300d65f16f2c2beb1e563d4.png" alt="铲子"></p><ul><li>铲子:嗯?!女朋友送了一顶好看的绿帽子,还顺手帮我做了把绿色的铲子,可真贴心呐<ul><li>效率:36</li><li>附魔能力:69</li><li>攻击速度:1</li><li>伤害:4</li><li>耐久:3699</li></ul></li></ul><h3 id="矿场"><a href="#矿场" class="headerlink" title="矿场"></a>矿场</h3><p>去掉了掉落经验瓶,由采集经验矿石代替</p><p><img src="https://pic.imgdb.cn/item/6300dfad16f2c2beb1eaa196.png" alt="矿场"></p><ul><li>木炭盒</li><li>煤盒</li><li>铜锭盒</li><li>钻石盒</li><li>绿宝石盒</li><li>黄金盒</li><li>铁盒</li><li>下届碎片盒</li></ul><h3 id="超大容量的箱子"><a href="#超大容量的箱子" class="headerlink" title="超大容量的箱子"></a>超大容量的箱子</h3><p><del><strong>永远不会装满的箱子</strong></del></p><ul><li>180存储位,比大箱子大3.33倍</li><li><del>一个红色位删除多余物品</del></li></ul><p><img src="https://pic.imgdb.cn/item/632f25f916f2c2beb167bbbf.png" alt="超大容量箱子"></p><h3 id="跳的超高"><a href="#跳的超高" class="headerlink" title="跳的超高"></a>跳的超高</h3><ul><li>跳跃因子是普通跳跃的4倍,高度约为17格</li><li>跳跃自带1级缓降效果</li></ul><p><img src="https://pic.imgdb.cn/item/632f2a1616f2c2beb16d36db.png" alt="跳的超高"></p><h3 id="高清画"><a href="#高清画" class="headerlink" title="高清画"></a>高清画</h3><p>添加几幅高清画</p><p><img src="https://pic.imgdb.cn/item/632f2a9a16f2c2beb16e1cb6.png" alt="阳光下的狐狸"></p><p><img src="https://pic.imgdb.cn/item/632f2ab116f2c2beb16e38ef.png" alt="少女"></p><h3 id="盔甲"><a href="#盔甲" class="headerlink" title="盔甲"></a>盔甲</h3><p>真正强大的盔甲是自己的那颗心</p><ul><li>头盔<ul><li>+33 护甲值</li><li>+9 盔甲韧性</li><li>+50 击退抗性</li></ul></li><li>胸甲<ul><li>+36 护甲值</li><li>+9 盔甲韧性</li><li>+50 击退抗性</li></ul></li><li>护腿<ul><li>+36 护甲值</li><li>+9 盔甲韧性</li><li>+50 击退抗性</li></ul></li><li>靴子<ul><li>+69 护甲值</li><li>+9 盔甲韧性</li><li>+50 击退抗性</li></ul></li></ul><p><img src="https://pic1.imgdb.cn/item/6331b05916f2c2beb1fe2a64.png" alt="盔甲外观"></p><h3 id="压缩的煤块"><a href="#压缩的煤块" class="headerlink" title="压缩的煤块"></a>压缩的煤块</h3><ul><li>生成范围1-125</li><li>掉落5个煤</li></ul><p><img src="https://pic1.imgdb.cn/item/6337b1b716f2c2beb1658931.gif" alt="压缩的煤块"></p><h3 id="回收站"><a href="#回收站" class="headerlink" title="回收站"></a>回收站</h3><p>将超大容量箱子中的删除物品位单独分离出来</p><p><img src="https://pic1.imgdb.cn/item/6337d77d16f2c2beb18a963d.png" alt="回收站"></p><h3 id="铜装备"><a href="#铜装备" class="headerlink" title="铜装备"></a>铜装备</h3><p><strong>铜变得更加实用</strong></p><ul><li>武器:铜剑<ul><li>伤害:10 <del>【钻石剑:7】</del></li><li>耐久:1741</li></ul></li><li>工具:铜斧、稿、锹<ul><li>伤害:8</li><li>耐久:1741</li></ul></li><li>铜锹<ul><li>伤害:7</li><li>耐久:1741</li></ul></li><li>盔甲:被一个TNT炸的只剩下1格血<ul><li>帽子<ul><li>护甲:8</li><li>耐久:660</li></ul></li><li>上衣<ul><li>护甲:20</li><li>耐久:960</li></ul></li><li>裤子<ul><li>护甲:24</li><li>耐久:900</li></ul></li><li>鞋子<ul><li>护甲:8</li><li>耐久:780</li></ul></li></ul></li></ul><h2 id="下载地址"><a href="#下载地址" class="headerlink" title="下载地址"></a>下载地址</h2><p>目前不直接提供下载,防止某些三方网站没有经过允许私自转发售卖,有需要可以发邮件告诉我</p>]]></content>
<categories>
<category> Mod </category>
</categories>
<tags>
<tag> Minecraft </tag>
</tags>
</entry>
<entry>
<title>文明六自定义领袖特色建筑</title>
<link href="/2021/06/01/2021-06-01-wen-ming-liu-zi-ding-yi-ling-xiu-te-se-jian-zhu/"/>
<url>/2021/06/01/2021-06-01-wen-ming-liu-zi-ding-yi-ling-xiu-te-se-jian-zhu/</url>
<content type="html"><![CDATA[<p>只允许主机、特定领袖、特定文明使用特定功能的模组</p><h2 id="背景"><a href="#背景" class="headerlink" title="背景"></a>背景</h2><p>一直想实现一个在联机模式下,只允许主机,特定领袖,特定文明使用特定功能的模组.</p><h2 id="模组介绍"><a href="#模组介绍" class="headerlink" title="模组介绍"></a>模组介绍</h2><p>这是一个建筑添加与德国特色工业区下,每回合建筑生产一定的资源并修改城市每回合所产出的资源.</p><p>建筑本身加成:黄金 + 5, 食物 + 5, 文化 + 1, 科技 + 1, 信仰 + 5, 生产力 + 5</p><p>城市资源加成:黄金 + 116, 信仰 + 116, 文化 + 5, 生产力 + 20, 食物 + 20, 科技 + 5</p><table><thead><tr><th>建筑名称</th><th>加成类型</th><th>建筑每回合生产</th><th>城市每回合生产</th></tr></thead><tbody><tr><td>少年的野心/DefinCivilizationTraitBuilding</td><td>黄金</td><td>+5</td><td>+116</td></tr><tr><td></td><td>食物</td><td>+5</td><td>+116</td></tr><tr><td></td><td>文化</td><td>+1</td><td>+5</td></tr><tr><td></td><td>科技</td><td>+1</td><td>+5</td></tr><tr><td></td><td>信仰</td><td>+5</td><td>+116</td></tr><tr><td></td><td>生产力</td><td>+5</td><td>+20</td></tr></tbody></table><h2 id="订阅地址"><a href="#订阅地址" class="headerlink" title="订阅地址"></a>订阅地址</h2><p>你可以直接订阅这个模组:<a class="link" href="https://steamcommunity.com/sharedfiles/filedetails/?id=2504308659">Stem 创意工坊<i class="fas fa-external-link-alt"></i></a></p><h2 id="参考链接"><a href="#参考链接" class="headerlink" title="参考链接"></a>参考链接</h2><ul><li> <a class="link" href="https://steamcommunity.com/sharedfiles/filedetails/?id=2217426255">玉皇大帝<i class="fas fa-external-link-alt"></i></a></li><li> <a class="link" href="https://steamcommunity.com/sharedfiles/filedetails/?id=2503117573">StringOD Of House<i class="fas fa-external-link-alt"></i></a></li></ul><h2 id="思路记录"><a href="#思路记录" class="headerlink" title="思路记录"></a>思路记录</h2><p><img src="https://pic.imgdb.cn/item/60b6179939f6859bc2dec87b.png" alt="玉皇大帝开发者终于回复我了"></p><p>下午打开 Steam 时收到来自玉皇大帝模组开发者的回复.</p><blockquote><p>墨影斋主人:在你的建筑下添加一个修改器,判定建造条件是 “玩家”,如果是绑定领袖或者文明,就直接用 TraitType</p></blockquote><p>我迫不及待想要去尝试下</p><p>打开 HBX(HBuilderX) 检索一下 <code>TraitType</code> 看下这玩意怎么写的.</p><p>我在 <code>Civilizations.xml</code> 文件中的 <strong>第 200 行</strong> 找到了一条相关有可能的但与玉皇大帝作者说的完全不同的代码</p><figure class="highlight xml"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">Row</span> <span class="attr">CivilizationType</span>=<span class="string">"CIVILIZATION_GERMANY"</span> <span class="attr">LeaderType</span>=<span class="string">"LEADER_BARBAROSSA"</span></span></span><br></pre></td></tr></tbody></table></figure><p>第 2657 - 2659 行有点像</p><figure class="highlight xml"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">Row</span> <span class="attr">CivilizationType</span>=<span class="string">"CIVILIZATION_GERMANY"</span> <span class="attr">TraitType</span>=<span class="string">"TRAIT_CIVILIZATION_IMPERIAL_FREE_CITIES"</span>/></span></span><br><span class="line"><span class="tag"><<span class="name">Row</span> <span class="attr">CivilizationType</span>=<span class="string">"CIVILIZATION_GERMANY"</span> <span class="attr">TraitType</span>=<span class="string">"TRAIT_CIVILIZATION_DISTRICT_HANSA"</span>/></span></span><br><span class="line"><span class="tag"><<span class="name">Row</span> <span class="attr">CivilizationType</span>=<span class="string">"CIVILIZATION_GERMANY"</span> <span class="attr">TraitType</span>=<span class="string">"TRAIT_CIVILIZATION_UNIT_GERMAN_UBOAT"</span>/></span></span><br></pre></td></tr></tbody></table></figure><p>我没有在修改器文件<code>Modifiers.xml</code>文件中找到相关代码,据猜测这可能是自由添加,或许游戏源文件没有可能说明那只是一部分示例代码,并且游戏开发商不可能想象到各种可能性.</p><h3 id="修改器文件如何编写"><a href="#修改器文件如何编写" class="headerlink" title="修改器文件如何编写"></a>修改器文件如何编写</h3><h4 id="第一种可能性"><a href="#第一种可能性" class="headerlink" title="第一种可能性"></a>第一种可能性</h4><p>他说的是修改器<code>Modifiers</code> 应该不可能是 <code>ModifierArguments</code>, <code>BuildingModifiers</code>这两个修改器.</p><p>我没有见过单独绑定领袖的写法,在读一遍修改器文件吧<code>Modifiers.xml</code></p><p>这两千多行代码全都是一个写法</p><figure class="highlight xml"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">Types</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">Row</span> <span class="attr">Type</span>=<span class="string">"MODIFIER_ALL_PLAYERS_ATTACH_MODIFIER"</span> <span class="attr">Kind</span>=<span class="string">"KIND_MODIFIER"</span>/></span></span><br><span class="line"><span class="tag"></<span class="name">Types</span>></span></span><br><span class="line"><span class="tag"><<span class="name">DynamicModifiers</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">Row</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">ModifierType</span>></span>MODIFIER_ALL_PLAYERS_ATTACH_MODIFIER<span class="tag"></<span class="name">ModifierType</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">CollectionType</span>></span>COLLECTION_ALL_PLAYERS<span class="tag"></<span class="name">CollectionType</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">EffectType</span>></span>EFFECT_ATTACH_MODIFIER<span class="tag"></<span class="name">EffectType</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">Row</span>></span></span><br><span class="line"><span class="tag"></<span class="name">DynamicModifiers</span>></span></span><br></pre></td></tr></tbody></table></figure><p>根据这段代码,可以猜测出一种可能性.</p><ul><li> 第六行代码的<strong>修改器类型</strong>和第 2 行的类型相等,这说明引用的是建筑名称</li><li> 后面第7和8行不知道表达的什么意思</li><li> 可能需要拼接代码</li></ul><figure class="highlight xml"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">Types</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">Row</span> <span class="attr">Type</span>=<span class="string">"BUILDING_DEFIN_CIVILIZATION_TRAIT_BUILDING"</span> <span class="attr">Kind</span>=<span class="string">"KIND_BUILDING"</span> /></span></span><br><span class="line"><span class="tag"></<span class="name">Types</span>></span></span><br><span class="line"><span class="tag"><<span class="name">Modifiers</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">Row</span>></span></span><br><span class="line"> <span class="comment"><!-- 声明建筑要修改的建筑名称 --></span></span><br><span class="line"> <span class="tag"><<span class="name">ModifierType</span>></span>BUILDING_DEFIN_CIVILIZATION_TRAIT_BUILDING<span class="tag"></<span class="name">ModifierType</span>></span></span><br><span class="line"> <span class="comment"><!-- 绑定玩家拥有使用当前建筑权限 --></span></span><br><span class="line"> <span class="tag"><<span class="name">OwnerRequirementSetId</span>></span>PLAYER_IS_HUMAN<span class="tag"></<span class="name">OwnerRequirementSetId</span>></span></span><br><span class="line"> <span class="comment"><!-- 绑定指定的领袖使用当前建筑: 德国 --></span></span><br><span class="line"> <span class="tag"><<span class="name">TraitType</span>></span>CIVILIZATION_GERMANY<span class="tag"></<span class="name">TraitType</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">Row</span>></span></span><br><span class="line"><span class="tag"></<span class="name">Modifiers</span>></span></span><br></pre></td></tr></tbody></table></figure><p>第 10 行完全是文明类型和区域类型的两种结合写法,先构建一下试试效果吧.</p><p><img src="https://pic.imgdb.cn/item/60b6179939f6859bc2dec85e.png" alt="woc难道我成功了"></p><p>试了下其他文明是否有这个建筑,很遗憾,并没有成功绑定领袖</p><h4 id="第二种可能性"><a href="#第二种可能性" class="headerlink" title="第二种可能性"></a>第二种可能性</h4><p>我打开了领袖<code>Leaders.xml</code> 文件,终于发现了一个比较合理的代码.</p><p>换了好几个关键字,终于用巴巴罗萨的英译名在第 828 行找到了<code>LEADER_BARBAROSSA</code></p><figure class="highlight xml"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">Row</span> <span class="attr">LeaderType</span>=<span class="string">"LEADER_BARBAROSSA"</span> <span class="attr">TraitType</span>=<span class="string">"TRAIT_LEADER_HOLY_ROMAN_EMPEROR"</span>/></span></span><br></pre></td></tr></tbody></table></figure><p>应该是这一行了,很遗憾,也不是这个</p><h4 id="第三种可能性"><a href="#第三种可能性" class="headerlink" title="第三种可能性"></a>第三种可能性</h4><p>我阅读了玉皇大帝的领袖文件,我猜测写法应该是这样.</p><figure class="highlight xml"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">Row</span> </span></span><br><span class="line"><span class="tag"> <span class="attr">BuildingType</span>=<span class="string">"BUILDING_DEFIN_CIVILIZATION_TRAIT_BUILDING"</span></span></span><br><span class="line"><span class="tag"> </span></span><br><span class="line"><span class="tag"> <span class="attr">PrereqDistrict</span>=<span class="string">"DISTRICT_CITY_CENTER"</span> </span></span><br><span class="line"><span class="tag"> <span class="attr">PurchaseYield</span>=<span class="string">"YIELD_GOLD"</span> </span></span><br><span class="line"><span class="tag"> <span class="attr">Cost</span>=<span class="string">"0"</span> </span></span><br><span class="line"><span class="tag"> <span class="attr">AdvisorType</span>=<span class="string">"ADVISOR_GENERIC"</span> </span></span><br><span class="line"><span class="tag"> <span class="attr">TraitType</span>=<span class="string">"TRAIT_LEADER_HOLY_ROMAN_EMPEROR"</span>/></span></span><br></pre></td></tr></tbody></table></figure><p>我直接追加到了建筑名称的后面,当我使用其它领袖的时候,他成功的没有显示出来.当然不排除是我写错了,在没有写正确的情况下是不会显示的.</p><p>我再试试德国,万一我写对了呢.对吧!</p><p><img src="https://pic.imgdb.cn/item/60b6179539f6859bc2dea553.png" alt="这下才是真正成功了"></p><p>竟然真成功了,再试试一下热座模式,因为仅允许玩家使用和德国使用,看看这两个条件冲突不</p><p>还真能用,没有任何冲突,终于完成了</p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>即时在不依靠国内的各种文档,视频教程,依然可以依靠阅读英文文档和官网的帮助以及自己的猜测和摸索一样能达成目标.</p><p>虽然我最后实现功能的方式和玉皇大帝模组开发者说的答案有点出入,但还是他提醒了我,我愣是依靠一条信息找到了答案"领袖,文明,特征<code>TraitType</code>"才完成了我一直以来的目标.</p>]]></content>
<categories>
<category> Civ6 </category>
</categories>
<tags>
<tag> Mod </tag>
</tags>
</entry>
<entry>
<title>文明六自定义市中心区域建筑模组</title>
<link href="/2021/02/04/2021-02-04-wen-ming-liu-zi-ding-yi-shi-zhong-xin-qu-yu-jian-zhu-mo-zu/"/>
<url>/2021/02/04/2021-02-04-wen-ming-liu-zi-ding-yi-shi-zhong-xin-qu-yu-jian-zhu-mo-zu/</url>
<content type="html"><![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>在文明 6 的创意工坊中订阅了两个功能极为相似的模组,感觉这两个模组的代码应该差不多,于是打算分析完成这两个模组就自己写一个.</p><p><a class="link" href="https://store.steampowered.com/app/289070/Sid_Meiers_Civilization_VI/">文明六<i class="fas fa-external-link-alt"></i></a><code>Sid Meier's Civilization VI</code> 是一款回合制策略游戏,让玩家尝试建立起一个帝国,并接受时间的考验.玩家将创建及带领自己的文明从石器时代迈向信息时代,并成为世界的领导者.</p><p><a class="link" href="https://baike.baidu.com/item/%E6%B8%B8%E6%88%8F%E6%A8%A1%E7%BB%84/105567?fromtitle=MOD&fromid=2316956&fr=aladdin">模组<i class="fas fa-external-link-alt"></i></a> <code>Modification</code> 缩写为 <code>Mod</code> ,简体中文翻译为模组,它是游戏的一种修改或增强程序.比如玩家向游戏中添加了游戏本身不存在的物体使游戏更加有趣.</p><p><strong>创意工坊</strong> 是 <code>Steam</code> 游戏平台针对特定游戏提供一个 <code>Mod</code> 下载页面,玩家可以下载自己喜欢的 <code>Mod</code> 来增强自己的游戏体验</p><p>游戏中开局你将扮演一个领袖在地图上创建你的第一座城市,城市下有很多的区域比如:学院、市场、娱乐区域、圣地区域(市民进行宗教活动的区域)、军营和港口等.</p><p>市中心区域是城市创建后首先要建造的基础设施用来保障市民的生活.</p><p>前段时间在 B 站看到了一个文明六的创建一个简单模组的教程,于是乎把压抑已久的想法想要实现一下.</p><p>但是当打开模组开发工具时,我发现模组开发并不那么容易,跟着这个 B 站教程做了一遍之后遇到了许多困难.过了几个月,也就是前几天,我决定从别人的模组开始入手学习,制作一个属于自己的模组.</p><p>本文主要讲解这个模组的设计思路,以及如何创建属于自己的模组.</p><h2 id="模组介绍"><a href="#模组介绍" class="headerlink" title="模组介绍"></a>模组介绍</h2><p>这个模组名为<code>StringOD of House</code> ,它的作用是,向文明六任意国家的任意已创建城市的市中心区域下新增一个带有各种加成的建筑物,目前添加的建筑为 “<strong>少年的出租屋</strong> ”</p><p>它的加成和模组信息如下图:</p><p><img src="https://pic.imgdb.cn/item/60212ece3ffa7d37b3b05c1d.png" alt="资源加成表"></p><p><img src="https://pic.imgdb.cn/item/6021312b3ffa7d37b3b18a68.png" alt="Mod 信息"></p><p><img src="https://pic.imgdb.cn/item/6021314b3ffa7d37b3b1987f.png" alt="建筑信息"></p><h2 id="下载地址"><a href="#下载地址" class="headerlink" title="下载地址"></a>下载地址</h2><p>如果你不想学习创建属于自己的 <code>Mod</code> 你可以直接下载并体验它<strong>【目前不直接提供下载,防止某些三方网站没有经过允许私自转发售卖,有需要可以发邮件告诉我】</strong></p><p>模组的存放路径为 <code>%userprofile%\Documents\My Games\Sid Meier's Civilization VI\Mods</code>,你需要解压缩把<code>StringOD</code> 文件夹放在 <code>Mods</code> 目录下.</p><p>如果你想学习创建属于自己的 <code>Mod</code> 并向游戏中添加自己更多的想法,请耐心的花费十分钟的时间看完这篇文章.</p><h2 id="参考链接"><a href="#参考链接" class="headerlink" title="参考链接"></a>参考链接</h2><ul><li><a class="link" href="https://www.bilibili.com/video/BV1VW411U7tN">文明 6 Mod 制作教程 (1) 做一个最简单的 MOD<i class="fas fa-external-link-alt"></i></a></li><li><a class="link" href="https://steamcommunity.com/sharedfiles/filedetails/?id=1395251854">God’s House - Cheat Mod<i class="fas fa-external-link-alt"></i></a></li><li><a class="link" href="https://steamcommunity.com/sharedfiles/filedetails/?id=1956601153">Player Only Cheat Building<i class="fas fa-external-link-alt"></i></a></li></ul><h2 id="搭建环境"><a href="#搭建环境" class="headerlink" title="搭建环境"></a>搭建环境</h2><ol><li><code>Steam</code> 中打开 <strong>库</strong></li><li>点击 <strong>库</strong> 下面的 <strong>游戏</strong> 下拉框,勾选 <strong>工具</strong><blockquote><p>库 下面的 主页 下面的</p></blockquote></li><li>安装 <code>Sid Meier's Civilization VI Development Tools</code></li><li>启动之后选择 <code>ModBuddy</code></li></ol><p>至此,环境搭建完成</p><h2 id="创建你的第一个建筑模组"><a href="#创建你的第一个建筑模组" class="headerlink" title="创建你的第一个建筑模组"></a>创建你的第一个建筑模组</h2><p>这个时候你应该能看到 <code>Start Page</code> 的页面.</p><ol><li> 创建新的模组选择 <code>New Mod</code></li><li> 更改下面的模组名称 <code>Name</code> 最好是英文</li><li> 更改模组的存储位置 <code>Location</code> (当然可以不更改)</li><li> 选择右小角的 <code>OK</code></li></ol><p>至此,当你看到一个英文的 <code>Starter Project</code> 页面时说明你的第一个模组创建成功.</p><h2 id="开始分析别人的模组"><a href="#开始分析别人的模组" class="headerlink" title="开始分析别人的模组"></a>开始分析别人的模组</h2><h3 id="模组介绍-1"><a href="#模组介绍-1" class="headerlink" title="模组介绍"></a>模组介绍</h3><p>这是上文提到的两个相似的模组,他们都在市中心区域添加了单独的建筑:</p><ul><li> <code>God's House - Cheat Mod</code> 针对当前城市修改了城市的产量,这其中包含:黄金,信仰,文化,科技,食物,生产力,住房以及宜居度.</li><li> <code>Player Only Cheat Building</code> 针对当前城市修改了城市的产量,这其中包含:黄金,信仰,文化,科技,食物,生产力,住房,宜居度.战略资源包含了:铁,煤,马,硝石,铝,铀.</li></ul><p>这两个模组在每一回合都会生产预先设置好的资源.</p><h3 id="寻找模组文件"><a href="#寻找模组文件" class="headerlink" title="寻找模组文件"></a>寻找模组文件</h3><p>首先,得找到模组的文件路径.我发现从创意工坊下载的模组都是一串数字代替的名字.比如:</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">1109505115</span><br><span class="line">1122081356</span><br></pre></td></tr></tbody></table></figure><p>这个时候我想到每个模组文件中一定会包含模组在游戏中显示的名字或模组的建筑名称.(用已知字符串搜索整个可能存在这些文件的目录下的每个文件中的内容)</p><p>启动 <code>HBuilderX</code> 打开 <code>Steam</code> 的创意工坊目录 <code>..\SteamLibrary\steamapps\workshop\content\289070</code>,然后在这个目录下的所有文件中查找 包含有 <code>God's House</code> 字符串的文件,根据搜索结果得到模组的目录名称 <code>1395251854</code></p><p>第二个模组的文件路径所使用的搜索字符串是游戏中显示的建筑名称,创意工坊给出的模组名称和模组中显示的名称不一样.最后根据游戏中显示的建筑名称 <code>YoRHa_Core</code> 在整个目录检索到的目录名称为 <code>1956601153</code>.</p><p>至此,得到了两个模组的文件路径.</p><h3 id="分析文件结构"><a href="#分析文件结构" class="headerlink" title="分析文件结构"></a>分析文件结构</h3><h4 id="从文件名称猜测"><a href="#从文件名称猜测" class="headerlink" title="从文件名称猜测"></a>从文件名称猜测</h4><p>先对比一下两个模组的文件结构.</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">1956601153 - YoRHa_Core</span><br><span class="line">Blu.png</span><br><span class="line">YoRHaCore.modinfo</span><br><span class="line">YoRHa_Core_Gameplay.xml</span><br><span class="line">YoRHa_Core_Icons.xml</span><br><span class="line">YoRHa_Core_Text.xml</span><br><span class="line"></span><br><span class="line">1395251854 - God's House</span><br><span class="line">GodsHouseCheatMod.modinfo</span><br><span class="line">NewBuilding_Gameplay.xml</span><br><span class="line">NewBuilding_Icons.xml</span><br><span class="line">NewBuilding_Text.xml</span><br></pre></td></tr></tbody></table></figure><p>可以看出感觉这两个模组差不多,唯一不同的是 <code>YoRHa_Core</code> 目录下多了一个 <code>Blu.png</code> 这是一个图片文件,猜测可能是背景图.</p><p>从模组的文件名字中能大概猜测到每个文件的作用.</p><ul><li> <code>xxx_Gameplay.xml</code> 应该是整个模组最主要的部分</li><li> <code>xxx_Icons.xml</code> 应该是该模组在游戏中显示的图标</li><li> <code>xxx_Text.xml</code> 应该是存放文本的文件,比如游戏中显示文字</li><li> <code>xxx.modinfo</code> 应该是用来存储当前模组的基本信息文件</li></ul><p>为了进一步确定每个文件的作用和功能就得打开文件读代码.</p><h4 id="打开每个文件读代码"><a href="#打开每个文件读代码" class="headerlink" title="打开每个文件读代码"></a>打开每个文件读代码</h4><p>在这里就用 <code>YoRHa_Core</code> 这个 <code>Mod</code> 来解释和翻译.</p><p>打开文件翻译每对标签的中文翻译.先从最外层往内层翻译.</p><ul><li> <code>xxx_Gameplay.xml</code> 模组的最重要的游戏数据文件</li></ul><figure class="highlight xml"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"><!-- 游戏数据, 整个模组的代码都得写在这个里面 --></span></span><br><span class="line"><span class="tag"><<span class="name">GameData</span>></span></span><br><span class="line"> </span><br><span class="line"> <span class="comment"><!-- 建筑类名称和类型 Kind为建筑分类, 一般情况下不用改--></span></span><br><span class="line"><span class="tag"><<span class="name">Types</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">Row</span> <span class="attr">Type</span>=<span class="string">"BUILDING_YORHA_CORE"</span> <span class="attr">Kind</span>=<span class="string">"KIND_BUILDING"</span> /></span></span><br><span class="line"> <span class="tag"></<span class="name">Types</span>></span></span><br><span class="line"> </span><br><span class="line"> <span class="comment"><!-- 建筑成本 附加属性 --></span></span><br><span class="line"> <span class="tag"><<span class="name">Buildings</span>></span></span><br><span class="line"> <span class="comment"><!--</span></span><br><span class="line"><span class="comment">BuildingType 这是建筑类型, 一般的和建筑名称可以是一样的(自己命名)</span></span><br><span class="line"><span class="comment">Name 这是建筑名称</span></span><br><span class="line"><span class="comment">PrereqDistrict="DISTRICT_CITY_CENTER" 表示这个模组中的建筑将添加在市中心区域下</span></span><br><span class="line"><span class="comment">PurchaseYield="YIELD_FAITH" 表示当前模组的建筑建造成本必须拥有的信仰值, 实际上也需要生产力</span></span><br><span class="line"><span class="comment">Cost="10" 表示需要多少信仰值才能建造当前模组中提供的建筑</span></span><br><span class="line"><span class="comment">AdvisorType="ADVISOR_GENERIC" 经过翻译是顾问类型为一般, 具体不知道什么意义, 默认不用改</span></span><br><span class="line"><span class="comment">--></span></span><br><span class="line"><span class="tag"><<span class="name">Row</span> <span class="attr">BuildingType</span>=<span class="string">"BUILDING_YORHA_CORE"</span> /></span></span><br><span class="line"> <span class="tag"></<span class="name">Buildings</span>></span></span><br><span class="line"> </span><br><span class="line"> <span class="comment"><!-- 建筑加成 建筑每回合生产的资源 --></span></span><br><span class="line"> <span class="tag"><<span class="name">Building_YieldChanges</span>></span></span><br><span class="line"> <span class="comment"><!-- 建筑类型, 建筑生产资源类型为信仰, 生产数值为1 --></span></span><br><span class="line"> <span class="tag"><<span class="name">Row</span> <span class="attr">BuildingType</span>=<span class="string">"BUILDING_YORHA_CORE"</span> <span class="attr">YieldType</span>=<span class="string">"YIELD_FAITH"</span> <span class="attr">YieldChange</span>=<span class="string">"1"</span>/></span></span><br><span class="line"> <span class="tag"></<span class="name">Building_YieldChanges</span>></span></span><br><span class="line"> </span><br><span class="line"> <span class="comment"><!-- 修改类型 每个建筑可以被附加各种不同的属性需要提前声明才能被引用和赋值 --></span></span><br><span class="line"> <span class="tag"><<span class="name">Modifiers</span>></span></span><br><span class="line"> <span class="comment"><!-- </span></span><br><span class="line"><span class="comment">ModifierId 建筑属性ID, 每种加成都需要一个特殊的名字</span></span><br><span class="line"><span class="comment">ModifierType 建筑引用类型 这个必须调用游戏已有的建筑类型</span></span><br><span class="line"><span class="comment">OwnerRequirementSetId 指定只有玩家可以使用, AI不可以</span></span><br><span class="line"><span class="comment"> --></span></span><br><span class="line"> <span class="tag"><<span class="name">Row</span>></span></span><br><span class="line"><span class="tag"><<span class="name">ModifierId</span>></span>YORHA_CORE_FAITH<span class="tag"></<span class="name">ModifierId</span>></span></span><br><span class="line"><span class="tag"><<span class="name">ModifierType</span>></span>MODIFIER_SINGLE_CITY_ADJUST_CITY_YIELD_MODIFIER<span class="tag"></<span class="name">ModifierType</span>></span></span><br><span class="line"><span class="tag"><<span class="name">OwnerRequirementSetId</span>></span>PLAYER_IS_HUMAN<span class="tag"></<span class="name">OwnerRequirementSetId</span>></span></span><br><span class="line"><span class="tag"></<span class="name">Row</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">Modifiers</span>></span></span><br><span class="line"> </span><br><span class="line"> <span class="comment"><!-- 修改参数 引用修改类型并重新赋值 --></span></span><br><span class="line"> <span class="tag"><<span class="name">ModifierArguments</span>></span></span><br><span class="line"> <span class="comment"><!-- 这里是主要修改部分, 其他部分可以不用管, 每一对 Value 标签中的数字可以随便改, 这就是城市的生产资源 --></span></span><br><span class="line"> <span class="comment"><!-- 信仰 --></span></span><br><span class="line"> <span class="tag"><<span class="name">Row</span>></span></span><br><span class="line"> <span class="comment"><!-- 引用 Modifiers 中的名称 --></span></span><br><span class="line"><span class="tag"><<span class="name">ModifierId</span>></span>YORHA_CORE_FAITH<span class="tag"></<span class="name">ModifierId</span>></span></span><br><span class="line"> <span class="comment"><!-- 生产收益 --></span></span><br><span class="line"><span class="tag"><<span class="name">Name</span>></span>Amount<span class="tag"></<span class="name">Name</span>></span></span><br><span class="line"> <span class="comment"><!-- 生产信仰 116 --></span></span><br><span class="line"><span class="tag"><<span class="name">Value</span>></span>116<span class="tag"></<span class="name">Value</span>></span></span><br><span class="line"><span class="tag"></<span class="name">Row</span>></span></span><br><span class="line"><span class="tag"><<span class="name">Row</span>></span></span><br><span class="line"> <span class="comment"><!-- 引用 Modifiers 中的名称 --></span></span><br><span class="line"><span class="tag"><<span class="name">ModifierId</span>></span>YORHA_CORE_FAITH<span class="tag"></<span class="name">ModifierId</span>></span></span><br><span class="line"> <span class="comment"><!-- 生产收益类型名称 --></span></span><br><span class="line"><span class="tag"><<span class="name">Name</span>></span>YieldType<span class="tag"></<span class="name">Name</span>></span></span><br><span class="line"><span class="tag"><<span class="name">Value</span>></span>YIELD_FAITH<span class="tag"></<span class="name">Value</span>></span></span><br><span class="line"><span class="tag"></<span class="name">Row</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">ModifierArguments</span>></span></span><br><span class="line"> </span><br><span class="line"> <span class="comment"><!-- 建筑类型引用 --></span></span><br><span class="line"> <span class="tag"><<span class="name">BuildingModifiers</span>></span></span><br><span class="line"><span class="comment"><!-- 建筑类型名称 和 建筑ID名称 --></span></span><br><span class="line"> <span class="tag"><<span class="name">Row</span>></span></span><br><span class="line"><span class="tag"><<span class="name">BuildingType</span>></span>BUILDING_YORHA_CORE<span class="tag"></<span class="name">BuildingType</span>></span></span><br><span class="line"><span class="tag"><<span class="name">ModifierId</span>></span>YORHA_CORE_FAITH<span class="tag"></<span class="name">ModifierId</span>></span></span><br><span class="line"><span class="tag"></<span class="name">Row</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">BuildingModifiers</span>></span></span><br><span class="line"> </span><br><span class="line"><span class="tag"></<span class="name">GameData</span>></span></span><br></pre></td></tr></tbody></table></figure><ul><li> <code>xxx_Icons.xml</code> 这是一个图标引用文件,主要作用是针对这个模组在生产队列中给建筑提供图标</li></ul><figure class="highlight xml"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"><!-- 图标命名 引用游戏自带图标 26代表工厂建筑图标 --></span></span><br><span class="line"><span class="tag"><<span class="name">IconDefinitions</span>></span></span><br><span class="line"><span class="tag"><<span class="name">Row</span> /></span></span><br><span class="line"><span class="tag"></<span class="name">IconDefinitions</span>></span></span><br></pre></td></tr></tbody></table></figure><ul><li> <code>xxx_Text.xml</code> 这是一个语言文件,当你的游戏主要语言更改时模组会选择一个已提供的语言进行显示,如果没有就默认显示英文(模组默认支持的语种)</li></ul><figure class="highlight xml"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"><!-- 在游戏中显示的各种名称和解释以及描述, 针对不同的语种 --></span></span><br><span class="line"><span class="tag"><<span class="name">LocalizedText</span>></span></span><br><span class="line"> <span class="comment"><!-- 英语 --></span></span><br><span class="line"><span class="tag"><<span class="name">Row</span> <span class="attr">Tag</span>=<span class="string">"LOC_BUILDING_YORHA_CORE"</span> <span class="attr">Language</span>=<span class="string">"en_US"</span>></span></span><br><span class="line"><span class="tag"><<span class="name">Text</span>></span>Core of YoRHa Android<span class="tag"></<span class="name">Text</span>></span></span><br><span class="line"><span class="tag"></<span class="name">Row</span>></span></span><br><span class="line"> <span class="comment"><!-- 韩语 --></span></span><br><span class="line"><span class="tag"><<span class="name">Row</span> <span class="attr">Tag</span>=<span class="string">"LOC_BUILDING_YORHA_CORE"</span> <span class="attr">Language</span>=<span class="string">"ko_KR"</span>></span></span><br><span class="line"><span class="tag"><<span class="name">Text</span>></span>요르하 안드로이드 코어<span class="tag"></<span class="name">Text</span>></span></span><br><span class="line"><span class="tag"></<span class="name">Row</span>></span></span><br><span class="line"> <span class="comment"><!-- 简体中文 --></span></span><br><span class="line"><span class="tag"><<span class="name">Row</span> <span class="attr">Tag</span>=<span class="string">"LOC_BUILDING_YORHA_CORE"</span> <span class="attr">Language</span>=<span class="string">"zh_CN"</span>></span></span><br><span class="line"><span class="tag"><<span class="name">Text</span>></span>YoRHa的机器人核心<span class="tag"></<span class="name">Text</span>></span></span><br><span class="line"><span class="tag"></<span class="name">Row</span>></span></span><br><span class="line"><span class="tag"></<span class="name">LocalizedText</span>></span></span><br></pre></td></tr></tbody></table></figure><ul><li> <code>xxx.modinfo</code> 这是一个模组创建完成且在发布时所需要填写的信息文件</li></ul><figure class="highlight xml"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"><!-- 模组唯一ID --></span></span><br><span class="line"><span class="tag"><<span class="name">Mod</span> <span class="attr">id</span>=<span class="string">"b630189f-13ab-4eb7-a131-fc4c32625397"</span> <span class="attr">version</span>=<span class="string">"1"</span>></span></span><br><span class="line"> <span class="comment"><!-- 属性 --></span></span><br><span class="line"> <span class="tag"><<span class="name">Properties</span>></span></span><br><span class="line"> <span class="comment"><!-- 模组名称 在构建模组时名称必须为英文, 但是可以用其他文本编辑器更改成中文, 这是我后来汉化的文件 --></span></span><br><span class="line"> <span class="tag"><<span class="name">Name</span>></span>YoRHa的机器人核心<span class="tag"></<span class="name">Name</span>></span></span><br><span class="line"> <span class="comment"><!-- 描述 --></span></span><br><span class="line"> <span class="tag"><<span class="name">Description</span>></span></span><br><span class="line">只添加玩家操作建筑。</span><br><span class="line">这个mod是以下mod的集成:</span><br><span class="line">1. 上帝的房子-欺骗Mod由,僵尸马特。</span><br><span class="line">2. 由Surt Ignir Magnis设计的资源生产建筑。</span><br><span class="line">3. 由 阳光下的少年(StringOD) 汉化, 做出大量调整使得玩家在局域网模式下可以更好的发展</span><br><span class="line"><span class="tag"></<span class="name">Description</span>></span></span><br><span class="line"> <span class="comment"><!-- 创建编号 --></span></span><br><span class="line"> <span class="tag"><<span class="name">Created</span>></span>1577944373<span class="tag"></<span class="name">Created</span>></span></span><br><span class="line"> <span class="comment"><!-- 概要 --></span></span><br><span class="line"> <span class="tag"><<span class="name">Teaser</span>></span></span><br><span class="line">只添加玩家操作建筑。</span><br><span class="line">这个mod是以下mod的集成:</span><br><span class="line">1. 上帝的房子-欺骗Mod由,僵尸马特。</span><br><span class="line">2. 由Surt Ignir Magnis设计的资源生产建筑。</span><br><span class="line">3. 由 阳光下的少年(StringOD) 汉化, 做出大量调整使得玩家在局域网模式下可以更好的发展</span><br><span class="line"><span class="tag"></<span class="name">Teaser</span>></span></span><br><span class="line"> <span class="comment"><!-- 作者 --></span></span><br><span class="line"> <span class="tag"><<span class="name">Authors</span>></span>Unknown Enemy<span class="tag"></<span class="name">Authors</span>></span></span><br><span class="line"> <span class="comment"><!-- 感谢名单 --></span></span><br><span class="line"> <span class="tag"><<span class="name">SpecialThanks</span>></span>L U N A / R \, Zombie Matt, and Surt Ignir Magnis.<span class="tag"></<span class="name">SpecialThanks</span>></span></span><br><span class="line"> <span class="comment"><!-- 兼容版本号 --></span></span><br><span class="line"> <span class="tag"><<span class="name">CompatibleVersions</span>></span>1.2,2.0<span class="tag"></<span class="name">CompatibleVersions</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">Properties</span>></span></span><br><span class="line"> <span class="comment"><!-- 依赖关系 --></span></span><br><span class="line"> <span class="tag"><<span class="name">Dependencies</span>></span></span><br><span class="line"> <span class="comment"><!-- 依赖的模组ID和名称 --></span></span><br><span class="line"> <span class="tag"><<span class="name">Mod</span> <span class="attr">id</span>=<span class="string">"4873eb62-8ccc-4574-b784-dda455e74e68"</span> <span class="attr">title</span>=<span class="string">"Expansion: Gathering Storm"</span> /></span></span><br><span class="line"> <span class="tag"></<span class="name">Dependencies</span>></span></span><br><span class="line"> </span><br><span class="line"> <span class="comment"><!-- 整个模组所作出的更改将更新到游戏文件中 --></span></span><br><span class="line"> <span class="tag"><<span class="name">InGameActions</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">UpdateDatabase</span> <span class="attr">id</span>=<span class="string">"Gameplay"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">File</span>></span>YoRHa_Core_Gameplay.xml<span class="tag"></<span class="name">File</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">UpdateDatabase</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">UpdateIcons</span> <span class="attr">id</span>=<span class="string">"Icons"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">File</span>></span>YoRHa_Core_Icons.xml<span class="tag"></<span class="name">File</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">UpdateIcons</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">UpdateText</span> <span class="attr">id</span>=<span class="string">"Text"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">File</span>></span>YoRHa_Core_Text.xml<span class="tag"></<span class="name">File</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">UpdateText</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">InGameActions</span>></span></span><br><span class="line"> <span class="comment"><!-- 需要更新的文件 --></span></span><br><span class="line"> <span class="tag"><<span class="name">Files</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">File</span>></span>YoRHa_Core_Gameplay.xml<span class="tag"></<span class="name">File</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">File</span>></span>YoRHa_Core_Icons.xml<span class="tag"></<span class="name">File</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">File</span>></span>YoRHa_Core_Text.xml<span class="tag"></<span class="name">File</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">Files</span>></span></span><br><span class="line"><span class="tag"></<span class="name">Mod</span>></span></span><br></pre></td></tr></tbody></table></figure><p>至此,每个文件的作用以及具体写法已经了解了.</p><h2 id="开始编写模组"><a href="#开始编写模组" class="headerlink" title="开始编写模组"></a>开始编写模组</h2><p>至此,我们应该已经创建完成了属于我们的第一个模组.<a href="#toc-heading-8">如果你还没有完成创建,你可能需要先完成上一个步骤</a></p><h3 id="描述需求"><a href="#描述需求" class="headerlink" title="描述需求"></a>描述需求</h3><p>添加一个市中心下的建筑,建造成本为 100 金币,每回合生产3文化值,每个回合建筑生产1个点数艺术家,作家,音乐家工程师,城市每回合产出 100 黄金.</p><h3 id="编写模组"><a href="#编写模组" class="headerlink" title="编写模组"></a>编写模组</h3><p>打开 <code>Solution Explorer</code> 面板时,我们创建的项目下多了两个文件 <code>xxx_Art.xml</code> 直接删掉这个文件,我也不知道这个文件的作用,<code>GettingStarted.html</code> 这个也可以删掉这是一个帮助页面.为了学习别人的模组最好和别人保持一致.</p><p>你可能会感到疑惑,为什么没有出现 <code>xxx.modinfo</code> 文件?</p><p>我后来生成模组时发现,这是因为这个文件是在后来构建或者运行模组时编译器自动生成的.</p><h3 id="xxx-Gameplay-xml-文件代码编写"><a href="#xxx-Gameplay-xml-文件代码编写" class="headerlink" title="xxx_Gameplay.xml 文件代码编写"></a>xxx_Gameplay.xml 文件代码编写</h3><p>直接看代码,先把英文注释翻译为中文.</p><figure class="highlight xml"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">GameData</span>></span></span><br><span class="line"> <span class="comment"><!-- 几乎所有的游戏类型都应该首先添加到类型表中。 --></span></span><br><span class="line"> <span class="comment"><!-- 这是引用任何类型的主要方式 --></span></span><br><span class="line"> <span class="tag"><<span class="name">Types</span>></span><span class="tag"></<span class="name">Types</span>></span></span><br><span class="line"> <span class="comment"><!--在这里定义建筑。</span></span><br><span class="line"><span class="comment">关于附加属性和价值的例子,请看建筑物。在这个目录下的所有文件中 /Base/Assets/Gameplay/Data/--></span></span><br><span class="line"> <span class="tag"><<span class="name">Buildings</span>></span><span class="tag"></<span class="name">Buildings</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">Building_YieldChanges</span>></span><span class="tag"></<span class="name">Building_YieldChanges</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">Building_GreatPersonPoints</span>></span><span class="tag"></<span class="name">Building_GreatPersonPoints</span>></span></span><br><span class="line"><span class="tag"></<span class="name">GameData</span>></span></span><br></pre></td></tr></tbody></table></figure><p>这里最有意思的就是第三个注释中提供的文件路径,打开资源浏览器找到这个目录<code>..\SteamLibrary\steamapps\common\Sid Meier's Civilization VI\Base\Assets\Gameplay\Data</code>.</p><p>这个目录下有很多的文件都是英文,猜测应该是游戏中每个分类的数据文件.我是说再写模组时想要修改游戏本身的数据就得需要这些文件.还是得先翻译一下这些英文都是什么意思才行.在当前目录下打开命令行</p><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#</span><span class="bash"> 输出当前目录所有文件名到 CivFileList.txt</span></span><br><span class="line">dir /B > CivFileList.txt</span><br></pre></td></tr></tbody></table></figure><p>然后打开 <code>CivFileList.txt</code> 这个文件,拷贝所有英文到 谷歌或其他翻译词典里面翻译.得到中文翻译:</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"># 部分翻译</span><br><span class="line">Agendas</span><br><span class="line">Barbarians</span><br><span class="line">Behavior Trees</span><br><span class="line">Beliefs</span><br><span class="line">Buildings</span><br><span class="line"></span><br><span class="line">议程</span><br><span class="line">野蛮人</span><br><span class="line">行为树</span><br><span class="line">信仰</span><br><span class="line">建筑</span><br></pre></td></tr></tbody></table></figure><p>先用代码编辑器打开一个与我们写模组最有直接关系的文件 <code>Buildings.xml</code>,没了解过 <code>xml</code> 文件的大兄弟,就可以把它当作 <code>HTML</code> 文件一样看待.主要关注标签的译文和标签内的代码,尤其是出现频率最高的单词比如:<code>xxxName, xxxType</code> 之类的.先通读这两千多行代码,主要关注注释和标签内前几行内容,得先知道这个文件整体结构怎么写的.</p><p>到这里,已经了解完了这个文件.至少是靠着翻译读了一遍.看这个文件的目的为了能在自己的模组中添加更多的内容.</p><p>打开别人的模组 <code>YoRHaCore</code> 中对应的文件 <code>YoRHa_Core_Gameplay.xml</code> 来复制粘贴到我的模组中然后测试运行,看效果.</p><ul><li> 根据 <code>YoRHa_Core_Gameplay.xml</code> 复制粘贴的修改城市每回合黄金产量为 116,每回合产出伟人点数 + 1(艺术家,工程师,音乐家,作家),<code>BUILDING_TestCase</code> 建筑每回合产出 3 个文化值.</li></ul><figure class="highlight xml"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta"><?xml version="1.0" encoding="utf-8" ?></span></span><br><span class="line"><span class="tag"><<span class="name">GameData</span>></span></span><br><span class="line"> <span class="comment"><!-- 几乎所有的游戏类型都应该首先添加到类型表中。 --></span></span><br><span class="line"> <span class="comment"><!-- 这是引用任何类型的主要方式 --></span></span><br><span class="line"> <span class="tag"><<span class="name">Types</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">Row</span> <span class="attr">Type</span>=<span class="string">"BUILDING_TestCase"</span> <span class="attr">Kind</span>=<span class="string">"KIND_BUILDING"</span> /></span></span><br><span class="line"> <span class="tag"></<span class="name">Types</span>></span></span><br><span class="line"></span><br><span class="line"> <span class="comment"><!-- 在这里定义建筑。关于附加属性和价值的例子,请看建筑物。都在这个目录下的所有xml文件中 /Base/Assets/Gameplay/Data/--></span></span><br><span class="line"> <span class="tag"><<span class="name">Buildings</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">Row</span> <span class="attr">BuildingType</span>=<span class="string">"BUILDING_TestCase"</span> /></span></span><br><span class="line"> <span class="tag"></<span class="name">Buildings</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">Building_YieldChanges</span>></span></span><br><span class="line"> <span class="comment"><!-- 建筑每回合生产3个文化值 --></span></span><br><span class="line"> <span class="tag"><<span class="name">Row</span> <span class="attr">BuildingType</span>=<span class="string">"BUILDING_TestCase"</span> <span class="attr">YieldType</span>=<span class="string">"YIELD_CULTURE"</span> <span class="attr">YieldChange</span>=<span class="string">"3"</span>/></span></span><br><span class="line"> <span class="tag"></<span class="name">Building_YieldChanges</span>></span></span><br><span class="line"><span class="comment"><!-- 伟人点数 --></span></span><br><span class="line"> <span class="tag"><<span class="name">Building_GreatPersonPoints</span>></span></span><br><span class="line"> <span class="comment"><!-- 艺术家 --></span></span><br><span class="line"> <span class="tag"><<span class="name">Row</span> <span class="attr">BuildingType</span>=<span class="string">"BUILDING_TestCase"</span> <span class="attr">GreatPersonClassType</span>=<span class="string">"GREAT_PERSON_CLASS_ARTIST"</span> <span class="attr">PointsPerTurn</span>=<span class="string">"1"</span>/></span></span><br><span class="line"> <span class="comment"><!-- 工程师 --></span></span><br><span class="line"> <span class="tag"><<span class="name">Row</span> <span class="attr">BuildingType</span>=<span class="string">"BUILDING_TestCase"</span> <span class="attr">GreatPersonClassType</span>=<span class="string">"GREAT_PERSON_CLASS_ENGINEER"</span> <span class="attr">PointsPerTurn</span>=<span class="string">"1"</span>/></span></span><br><span class="line"> <span class="comment"><!-- 音乐家 --></span></span><br><span class="line"> <span class="tag"><<span class="name">Row</span> <span class="attr">BuildingType</span>=<span class="string">"BUILDING_TestCase"</span> <span class="attr">GreatPersonClassType</span>=<span class="string">"GREAT_PERSON_CLASS_MUSICIAN"</span> <span class="attr">PointsPerTurn</span>=<span class="string">"1"</span>/></span></span><br><span class="line"> <span class="comment"><!-- 作家 --></span></span><br><span class="line"> <span class="tag"><<span class="name">Row</span> <span class="attr">BuildingType</span>=<span class="string">"BUILDING_TestCase"</span> <span class="attr">GreatPersonClassType</span>=<span class="string">"GREAT_PERSON_CLASS_WRITER"</span> <span class="attr">PointsPerTurn</span>=<span class="string">"1"</span>/></span></span><br><span class="line"> <span class="tag"></<span class="name">Building_GreatPersonPoints</span>></span></span><br><span class="line"><span class="tag"><<span class="name">Modifiers</span>></span></span><br><span class="line"><span class="tag"><<span class="name">Row</span>></span></span><br><span class="line"><span class="comment"><!-- 黄金 --></span></span><br><span class="line"><span class="tag"><<span class="name">ModifierId</span>></span>YORHA_CORE_GOLD<span class="tag"></<span class="name">ModifierId</span>></span></span><br><span class="line"><span class="tag"><<span class="name">ModifierType</span>></span>MODIFIER_SINGLE_CITY_ADJUST_CITY_YIELD_MODIFIER<span class="tag"></<span class="name">ModifierType</span>></span></span><br><span class="line"><span class="tag"><<span class="name">OwnerRequirementSetId</span>></span>PLAYER_IS_HUMAN<span class="tag"></<span class="name">OwnerRequirementSetId</span>></span></span><br><span class="line"><span class="tag"></<span class="name">Row</span>></span></span><br><span class="line"><span class="tag"></<span class="name">Modifiers</span>></span></span><br><span class="line"><span class="tag"><<span class="name">ModifierArguments</span>></span></span><br><span class="line"><span class="tag"><<span class="name">Row</span>></span></span><br><span class="line"><span class="tag"><<span class="name">ModifierId</span>></span>YORHA_CORE_GOLD<span class="tag"></<span class="name">ModifierId</span>></span></span><br><span class="line"><span class="tag"><<span class="name">Name</span>></span>Amount<span class="tag"></<span class="name">Name</span>></span></span><br><span class="line"><span class="comment"><!-- 城市每回合产出黄金数 --></span></span><br><span class="line"><span class="tag"><<span class="name">Value</span>></span>116<span class="tag"></<span class="name">Value</span>></span></span><br><span class="line"><span class="tag"></<span class="name">Row</span>></span></span><br><span class="line"><span class="tag"><<span class="name">Row</span>></span></span><br><span class="line"><span class="tag"><<span class="name">ModifierId</span>></span>YORHA_CORE_GOLD<span class="tag"></<span class="name">ModifierId</span>></span></span><br><span class="line"><span class="tag"><<span class="name">Name</span>></span>YieldType<span class="tag"></<span class="name">Name</span>></span></span><br><span class="line"><span class="tag"><<span class="name">Value</span>></span>YIELD_GOLD<span class="tag"></<span class="name">Value</span>></span></span><br><span class="line"><span class="tag"></<span class="name">Row</span>></span></span><br><span class="line"><span class="tag"></<span class="name">ModifierArguments</span>></span></span><br><span class="line"><span class="tag"><<span class="name">BuildingModifiers</span>></span></span><br><span class="line"><span class="tag"><<span class="name">Row</span>></span></span><br><span class="line"><span class="tag"><<span class="name">BuildingType</span>></span>BUILDING_YORHA_CORE<span class="tag"></<span class="name">BuildingType</span>></span></span><br><span class="line"><span class="tag"><<span class="name">ModifierId</span>></span>YORHA_CORE_GOLD<span class="tag"></<span class="name">ModifierId</span>></span></span><br><span class="line"><span class="tag"></<span class="name">Row</span>></span></span><br><span class="line"><span class="tag"></<span class="name">BuildingModifiers</span>></span></span><br><span class="line"><span class="tag"></<span class="name">GameData</span>></span></span><br></pre></td></tr></tbody></table></figure><p>这其中的几行的解释已经在前面解释过了.<a href="#toc-heading-14">我想在看一遍每行代码的注解</a></p><h4 id="从游戏提供的代码抽取需要的代码到模组中"><a href="#从游戏提供的代码抽取需要的代码到模组中" class="headerlink" title="从游戏提供的代码抽取需要的代码到模组中"></a>从游戏提供的代码抽取需要的代码到模组中</h4><p>写到这里你可以看得出每对标签都对应着每一个文件中的内容,比如:<code>..\SteamLibrary\steamapps\common\Sid Meier's Civilization VI\Base\Assets\Gameplay\Data</code> 目录下的文件 <code>Buildings.xml</code> 对应着代码中的标签 <code><Buildings></Buildings></code>,<code><Modifiers></Modifiers></code>.</p><p>每对标签中对应的代码其实在原始游戏文件中都已经存在,只不过我们是把我们需要的某个文件中的东西拿出来组成一个新的东西.</p><p>比如:<code>Buildings.xml</code> 文件中第 380 行中建筑提供每回合的 2 科技值的生产</p><figure class="highlight xml"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"><!-- 图书馆 生产2科技--></span></span><br><span class="line"><span class="tag"><<span class="name">Row</span> <span class="attr">BuildingType</span>=<span class="string">"BUILDING_LIBRARY"</span> <span class="attr">YieldType</span>=<span class="string">"YIELD_SCIENCE"</span> <span class="attr">YieldChange</span>=<span class="string">"2"</span>/></span></span><br></pre></td></tr></tbody></table></figure><p>在 <code>ModifierArguments</code> 标签中允许修改建筑属性参数,在<code>Buildings.xml</code>的第 879 行中是对金字塔奇观的建造费用调整,这个值可以是任意的某个值.</p><figure class="highlight xml"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">Row</span>></span></span><br><span class="line"> <span class="comment"><!-- PYRAMID ADJUST BUILDER CHARGES 金字塔调整建造费 --></span></span><br><span class="line"> <span class="tag"><<span class="name">ModifierId</span>></span>PYRAMID_ADJUST_BUILDER_CHARGES<span class="tag"></<span class="name">ModifierId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">Name</span>></span>Amount<span class="tag"></<span class="name">Name</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">Value</span>></span>1<span class="tag"></<span class="name">Value</span>></span></span><br><span class="line"><span class="tag"></<span class="name">Row</span>></span></span><br></pre></td></tr></tbody></table></figure><p>示例:在我上文提到的两个模组中,建筑每回合生产都只是写了一个信仰每回合 + 1.</p><figure class="highlight xml"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">Building_YieldChanges</span>></span></span><br><span class="line"><span class="tag"><<span class="name">Row</span> <span class="attr">BuildingType</span>=<span class="string">"BUILDING_YORHA_CORE"</span> <span class="attr">YieldType</span>=<span class="string">"YIELD_FAITH"</span> <span class="attr">YieldChange</span>=<span class="string">"1"</span>/></span></span><br><span class="line"><span class="tag"></<span class="name">Building_YieldChanges</span>></span></span><br></pre></td></tr></tbody></table></figure><p>可以根据收益类型看出,肯定不止有信仰这一种.首先打开最有可能出现的文件 <code>Buildings.xml</code> ,尝试找一下金币,文化,科技的加成.</p><ul><li> 第 338 行,这是一个剧场奇观.每回合 + 2 的文化</li></ul><figure class="highlight xml"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">Row</span> <span class="attr">BuildingType</span>=<span class="string">"BUILDING_AMPHITHEATER"</span> <span class="attr">YieldType</span>=<span class="string">"YIELD_CULTURE"</span> <span class="attr">YieldChange</span>=<span class="string">"2"</span>/></span></span><br></pre></td></tr></tbody></table></figure><ul><li> 第 363 行,这是一个银行建筑,每回合 + 5 黄金</li></ul><figure class="highlight xml"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">Row</span> <span class="attr">BuildingType</span>=<span class="string">"BUILDING_BANK"</span> <span class="attr">YieldType</span>=<span class="string">"YIELD_GOLD"</span> <span class="attr">YieldChange</span>=<span class="string">"5"</span>/></span></span><br></pre></td></tr></tbody></table></figure><ul><li> 第 380 行,这是一个图书馆,每回合 + 2 科技值</li></ul><figure class="highlight xml"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">Row</span> <span class="attr">BuildingType</span>=<span class="string">"BUILDING_LIBRARY"</span> <span class="attr">YieldType</span>=<span class="string">"YIELD_SCIENCE"</span> <span class="attr">YieldChange</span>=<span class="string">"2"</span>/></span></span><br></pre></td></tr></tbody></table></figure><p>至此,这个文件下的代码就是把需要的代码添加到我们创建模组中对应的标签下,如果模组中没有对应添加的标签,那就从你抽取的那个代码的文件中把标签照着添加到你的模组中缺少的文件中.不知道你写的对不对就构建运行进游戏看看,有没有生效,如果没有就是你写的有问题.</p><h3 id="xxx-Icons-xml-图标文件编写"><a href="#xxx-Icons-xml-图标文件编写" class="headerlink" title="xxx_Icons.xml 图标文件编写"></a>xxx_Icons.xml 图标文件编写</h3><p>这个文件默认是不用改的,除非你不想使用默认的图标.</p><figure class="highlight xml"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"><!-- </span></span><br><span class="line"><span class="comment">For simplicity, reuse an existing icon. </span></span><br><span class="line"><span class="comment">为简单起见,重用现有的图标。</span></span><br><span class="line"><span class="comment">--></span></span><br><span class="line"><span class="tag"><<span class="name">IconDefinitions</span>></span></span><br><span class="line"> <span class="comment"><!-- </span></span><br><span class="line"><span class="comment">Name 是建筑显示的图标名称 </span></span><br><span class="line"><span class="comment">Atlas 是图标所属建筑</span></span><br><span class="line"><span class="comment">Index 是图标对应所属建筑的序号</span></span><br><span class="line"><span class="comment">26 是工厂的图标</span></span><br><span class="line"><span class="comment">--></span></span><br><span class="line"><span class="tag"><<span class="name">Row</span> /></span></span><br><span class="line"><span class="tag"></<span class="name">IconDefinitions</span>></span></span><br></pre></td></tr></tbody></table></figure><p>这个目录下的所有文件是针对每个分类下的图标显示 <code>..\SteamLibrary\steamapps\common\Sid Meier's Civilization VI\Base\Assets\UI\Icons</code> ,这个 <code>Index="26"</code> 的来历,就在这个目录下的 <code>Icons_Buildings.xml</code> 文件中的 第 41 行.</p><figure class="highlight xml"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">Row</span> /></span></span><br></pre></td></tr></tbody></table></figure><p>如果你想使用其他图标你只需要更改的属性,只有两个 <code>Altas</code> 和 <code>Index</code> 务必要保持这两个一致.</p><p>你也可以使用其他类型的单位图标,比如不是建筑的图标.</p><h3 id="xxx-Text-xml-模组建筑描述文件编写"><a href="#xxx-Text-xml-模组建筑描述文件编写" class="headerlink" title="xxx_Text.xml 模组建筑描述文件编写"></a>xxx_Text.xml 模组建筑描述文件编写</h3><p>这个文件主要存放的是建筑的语言信息,看到这里你也发现了.所有的代码都是英文没有包含一个中文.(除注释之外)</p><figure class="highlight xml"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">GameData</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">LocalizedText</span>></span></span><br><span class="line"> <span class="comment"><!-- </span></span><br><span class="line"><span class="comment">Tag 是建筑的英文名称, 这样方便在模组中的各个文件互相引用</span></span><br><span class="line"><span class="comment">Language 是语言类型</span></span><br><span class="line"><span class="comment">en_US 是(英语)美国</span></span><br><span class="line"><span class="comment">zh_CN 是(简中)中国</span></span><br><span class="line"><span class="comment">ko_KR 是(韩语)韩国</span></span><br><span class="line"><span class="comment">Text 是这个建筑模组在游戏中显示的名称</span></span><br><span class="line"><span class="comment">--></span></span><br><span class="line"> <span class="tag"><<span class="name">Row</span> <span class="attr">Tag</span>=<span class="string">"LOC_BUILDING_GAME_STUDIO_NAME"</span> <span class="attr">Language</span>=<span class="string">"en_US"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">Text</span>></span>在游戏中显示的建筑名称<span class="tag"></<span class="name">Text</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">Row</span>></span></span><br><span class="line"></span><br><span class="line"> <span class="tag"><<span class="name">Row</span> <span class="attr">Tag</span>=<span class="string">"LOC_BUILDING_GAME_STUDIO_DESCRIPTION"</span> <span class="attr">Language</span>=<span class="string">"en_US"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">Text</span>></span>模组的描述<span class="tag"></<span class="name">Text</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">Row</span>></span></span><br><span class="line"></span><br><span class="line"> <span class="tag"><<span class="name">Row</span> <span class="attr">Tag</span>=<span class="string">"LOC_PEDIA_BUILDINGS_PAGE_BUILDING_GAME_STUDIO_CHAPTER_HISTORY_PARA_1"</span> <span class="attr">Language</span>=<span class="string">"en_US"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">Text</span>></span>模组的小故事或者说历史背景<span class="tag"></<span class="name">Text</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">Row</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">LocalizedText</span>></span></span><br><span class="line"><span class="tag"></<span class="name">GameData</span>></span></span><br></pre></td></tr></tbody></table></figure><h3 id="xxx-modinfo-模组信息文件编写"><a href="#xxx-modinfo-模组信息文件编写" class="headerlink" title="xxx_modinfo 模组信息文件编写"></a>xxx_modinfo 模组信息文件编写</h3><p>这个文件不需要自己创建 ,它会由编译器自动生成.</p><p>找到菜单栏中的 <code>PROJECT</code> 中的 <code>XXX Properties...</code> 会打开一个界面,选择左侧的 <code>Mod Info</code> 然后填写你的模组名称和描述以及小故事,还有感谢的人.</p><p>在选择左侧的 <code>In-Game Actions</code> 你看到了左边显示的三个<code>Type</code> 和三个 <code>Id</code> 选择第一个 <code>Type</code> 中的 <code>UpdataDatabase</code> ID 为 <code>Gameplay</code> ,这时你会看到右侧会显示一个大框里面显示了一个文件 <code>NewBuilding_Gameplay.xml</code> 文件,选中这个文件然后点击右侧的 <code>Remove</code> 然后在选择 <code>Add</code> 在弹出的新窗口中选择 <code>File</code> 列表框中的你模组对应的那个 <code>xxx_Gameplay.xml</code> 文件,然后点击 <code>OK</code> .然后在依次更新 <code>Icons</code> 文件和 <code>Text</code> 文件.</p><p>完成之后保存所有文件,至此就可以构建解决方案和模组了,并测试运行了.</p><h2 id="生成你的模组"><a href="#生成你的模组" class="headerlink" title="生成你的模组"></a>生成你的模组</h2><ol><li> 菜单栏中选择 <code>BUILD</code>然后选择 <code>Build Solution</code></li><li> 菜单栏中选择 <code>BUILD</code> 然后选择 <code>Build xxxx</code></li></ol><p>此时,你的模组已经成功生成.你可以打开游戏并在"额外内容中启用你的本地模组".</p><h2 id="删除你的模组"><a href="#删除你的模组" class="headerlink" title="删除你的模组"></a>删除你的模组</h2><p>在调试模组中经常出现问题,所以我们不得不删除解决方案和模组重新生成.</p><ol><li> 菜单栏中选择 <code>BUILD</code> 然后选择 <code>Clear Solution</code></li><li> 菜单栏中选择 <code>BUILD</code> 然后选择 <code>Clear xxxx</code></li></ol><p>这只是删除了你生成的模组文件,你的代码并没有被删除.</p><p>至此,你已经学会了如何编写你的第一个模组.</p>]]></content>
<categories>
<category> Civ6 </category>
</categories>
<tags>
<tag> Mod </tag>
</tags>
</entry>
<entry>
<title>我的世界自定义配方脚本生成器</title>
<link href="/2020/11/26/2020-11-26-wo-de-shi-jie-zi-ding-yi-pei-fang-jiao-ben-sheng-cheng-qi/"/>
<url>/2020/11/26/2020-11-26-wo-de-shi-jie-zi-ding-yi-pei-fang-jiao-ben-sheng-cheng-qi/</url>
<content type="html"><![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>在 Minecraft 世界中体验各式各样的探险、生存与建造乐趣!</p><p><strong>想在和平模式下玩生存模式,但是和平模式不生成敌对怪物,所以需要一种方式代替在困难度下生成怪物凋落物。</strong></p><p>模组:游戏允许玩家加入自己对游戏某些功能的修改,可以提高游戏的趣味性</p><p>和平模式:没有难度、没有敌对势力生成、玩家受到非致死伤害时自动恢复生命</p><p>怪物的凋落物:一般情况下能够获得怪物的凋落物,意味着玩家困难度设置的是“简单、中等、困难”</p><h2 id="工具介绍"><a href="#工具介绍" class="headerlink" title="工具介绍"></a>工具介绍</h2><p>这个工具名为<strong>我的世界自定义配方脚本生成器</strong>,它的作用是向游戏中添加新的工作台配方,在和平难度下获得的物品来合成非和平难度下的物品。可以不用手写代码工具会自动生成,只需要你选择生成前后的配方即可。</p><p><img src="https://pic.imgdb.cn/item/5fc8f6e7394ac52378756ea4.png" alt="工具界面"></p><h2 id="基于CraftTweaker手动生成配方"><a href="#基于CraftTweaker手动生成配方" class="headerlink" title="基于CraftTweaker手动生成配方"></a>基于CraftTweaker手动生成配方</h2><p>CraftTweaker 允许你使用简单的脚本语言来自定义你的整合包或服务器,你可以添加或删除配方、更改配方、更改名称、更改熔炉配方、矿物词典、工具描述等。<del>代码和说明必须是英文</del></p><p>手动写代码自定义几个配方还是很容易的,如果想完整实现我的设想就需要大量的重复性操作来解决这种问题。</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">// 龙蛋: 黑曜石围绕着鸡蛋, 最坚硬的蛋</span><br><span class="line">craftingTable.addShapeless("StringOD_LongDan", <item:minecraft:dragon_egg> * 1, [</span><br><span class="line"><item:minecraft:obsidian>, <item:minecraft:obsidian>, <item:minecraft:obsidian>, </span><br><span class="line"><item:minecraft:obsidian>, <item:minecraft:egg>, <item:minecraft:obsidian>, </span><br><span class="line"><item:minecraft:obsidian>, <item:minecraft:obsidian>, <item:minecraft:obsidian>]);</span><br><span class="line"></span><br><span class="line">// 刷怪箱: 铁栅栏围绕着箱子, 网格一样的箱子</span><br><span class="line">craftingTable.addShapeless("StringOD_ShuaGuaiXiang", <item:minecraft:spawner> * 1, [</span><br><span class="line"><item:minecraft:iron_bars>, <item:minecraft:iron_bars>, <item:minecraft:iron_bars>, </span><br><span class="line"><item:minecraft:iron_bars>, <item:minecraft:chest>, <item:minecraft:iron_bars>, </span><br><span class="line"><item:minecraft:iron_bars>, <item:minecraft:iron_bars>, <item:minecraft:iron_bars>]);</span><br></pre></td></tr></tbody></table></figure><h2 id="自动生成配方工具设计思路"><a href="#自动生成配方工具设计思路" class="headerlink" title="自动生成配方工具设计思路"></a>自动生成配方工具设计思路</h2><p><img src="https://pic.imgdb.cn/item/5fbfcd4615e77190842989e8.png" alt="AutoSpawnZScript思路"></p><h2 id="工具界面演化"><a href="#工具界面演化" class="headerlink" title="工具界面演化"></a>工具界面演化</h2><p><img src="https://pic.imgdb.cn/item/5fbfcd4e15e7719084298c1c.png" alt="AutoSpawnZScript - v0.1"></p><p><img src="https://pic.imgdb.cn/item/5fc1fe4815e7719084ac7401.png" alt="AutoSpawnZScript_v0.2"></p><p><img src="https://pic.imgdb.cn/item/5fc24ad1d590d4788a77c9f3.png" alt="AutoSpawnZScript - v0.3"></p><p><img src="https://pic.imgdb.cn/item/5fc33ca1d590d4788ab3da9e.png" alt="AutoSpawnZScript - v0.4"></p><p><img src="https://pic.imgdb.cn/item/5fc35cfcd590d4788abcd132.png" alt="AutoSpawnZScript - v0.5"></p><p><img src="https://pic.imgdb.cn/item/5fc8c9a6394ac523785585cc.png" alt="AutoSpawnZScript - v0.8"></p><p><img src="https://pic.imgdb.cn/item/5fcd0c4c394ac52378a81be8.png" alt="AutoSpawnZScript - v0.9"></p><p><img src="https://pic.imgdb.cn/item/5fd9e2853ffa7d37b350e13d.png" alt="AutoSpawnZScriptEToJava - v1.0"></p><h2 id="下载地址"><a href="#下载地址" class="headerlink" title="下载地址"></a>下载地址</h2><p>目前不直接提供下载,防止某些三方网站没有经过允许私自转发售卖,有需要可以发邮件告诉我</p>]]></content>
<categories>
<category> 易 </category>
</categories>
<tags>
<tag> Minecraft </tag>
</tags>
</entry>
</search>