@veypi/vhtml 0.7.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,73 @@
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
+
7
+ 1. Definitions.
8
+
9
+ "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document.
10
+
11
+ "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License.
12
+
13
+ "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity.
14
+
15
+ "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License.
16
+
17
+ "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files.
18
+
19
+ "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types.
20
+
21
+ "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below).
22
+
23
+ "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof.
24
+
25
+ "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution."
26
+
27
+ "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work.
28
+
29
+ 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form.
30
+
31
+ 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed.
32
+
33
+ 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions:
34
+
35
+ (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and
36
+
37
+ (b) You must cause any modified files to carry prominent notices stating that You changed the files; and
38
+
39
+ (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and
40
+
41
+ (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License.
42
+
43
+ You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License.
44
+
45
+ 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions.
46
+
47
+ 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file.
48
+
49
+ 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License.
50
+
51
+ 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages.
52
+
53
+ 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability.
54
+
55
+ END OF TERMS AND CONDITIONS
56
+
57
+ APPENDIX: How to apply the Apache License to your work.
58
+
59
+ To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives.
60
+
61
+ Copyright 2025 veypi
62
+
63
+ Licensed under the Apache License, Version 2.0 (the "License");
64
+ you may not use this file except in compliance with the License.
65
+ You may obtain a copy of the License at
66
+
67
+ http://www.apache.org/licenses/LICENSE-2.0
68
+
69
+ Unless required by applicable law or agreed to in writing, software
70
+ distributed under the License is distributed on an "AS IS" BASIS,
71
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
72
+ See the License for the specific language governing permissions and
73
+ limitations under the License.
package/README.md ADDED
@@ -0,0 +1,605 @@
1
+ # vhtml
2
+
3
+ [![Version](https://img.shields.io/npm/v/@veypi/vhtml?color=blue)](https://www.npmjs.com/package/@veypi/vhtml)
4
+ [![License](https://img.shields.io/badge/license-Apache%202.0-green)](LICENSE)
5
+
6
+ vhtml 是一个轻量级的响应式前端框架,提供了直观的数据绑定、组件化开发和路由功能。基于 HTML5 标准语法,无需复杂的编译过程,让开发更加简单高效。
7
+
8
+ ## ✨ 核心特性
9
+
10
+ - 🚀 **轻量级**:体积小巧,性能优异
11
+ - 📝 **HTML5 标准**:基于原生 HTML 语法,学习成本低
12
+ - 🔄 **响应式数据**:自动数据绑定与视图更新
13
+ - 🧩 **组件化**:支持可复用的组件开发
14
+ - 🛣️ **内置路由**:客户端路由与页面管理
15
+ - 🎨 **插槽系统**:灵活的内容分发机制
16
+ - 📦 **无需构建**:直接在浏览器中运行
17
+
18
+ ## 📦 安装
19
+
20
+ ### CDN 引入
21
+
22
+ ```html
23
+ <script src="https://vhtml.ai/assets/vhtml.min.js"></script>
24
+ ```
25
+
26
+ ## 🚀 快速开始
27
+
28
+ ### 基础 HTML 结构
29
+
30
+ ```html
31
+ <!DOCTYPE html>
32
+ <html>
33
+ <head>
34
+ <meta charset="UTF-8" />
35
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
36
+ <meta
37
+ name="description"
38
+ content="vhtml 示例页面"
39
+ details="展示 vhtml 基本功能的示例"
40
+ />
41
+ <title>vhtml 示例</title>
42
+ </head>
43
+ <style>
44
+ body {
45
+ font-family: Arial, sans-serif;
46
+ padding: 20px;
47
+ }
48
+ .counter {
49
+ text-align: center;
50
+ margin: 20px 0;
51
+ }
52
+ </style>
53
+ <body>
54
+ <div class="counter">
55
+ <h1>{{ title }}</h1>
56
+ <p>计数器:{{ count }}</p>
57
+ <button @click="increment">增加</button>
58
+ <button @click="decrement">减少</button>
59
+ <button @click="reset">重置</button>
60
+ </div>
61
+
62
+ <div v-if="count > 5">
63
+ <p>计数器大于 5!</p>
64
+ </div>
65
+
66
+ <ul>
67
+ <li v-for="(item, index) in items">
68
+ {{ index + 1 }}. {{ item.name }} - {{ item.value }}
69
+ </li>
70
+ </ul>
71
+ </body>
72
+
73
+ <script setup>
74
+ // 响应式数据定义
75
+ title = "vhtml 计数器示例";
76
+ count = 0;
77
+ items = [
78
+ { name: "项目一", value: "值1" },
79
+ { name: "项目二", value: "值2" },
80
+ { name: "项目三", value: "值3" },
81
+ ];
82
+
83
+ // 方法定义
84
+ increment = () => {
85
+ count++;
86
+ items.push({ name: `新项目${count}`, value: `值${count}` });
87
+ };
88
+
89
+ decrement = () => {
90
+ if (count > 0) {
91
+ count--;
92
+ items.pop();
93
+ }
94
+ };
95
+
96
+ reset = () => {
97
+ count = 0;
98
+ items = [
99
+ { name: "项目一", value: "值1" },
100
+ { name: "项目二", value: "值2" },
101
+ { name: "项目三", value: "值3" },
102
+ ];
103
+ };
104
+ </script>
105
+
106
+ <script>
107
+ // 页面初始化后执行
108
+ console.log("页面初始化完成,当前计数:", $data.count);
109
+
110
+ // 监听数据变化
111
+ $watch(() => {
112
+ console.log("计数变化:", $data.count);
113
+ if ($data.count >= 10) {
114
+ $message.success("恭喜!计数达到 10");
115
+ }
116
+ });
117
+
118
+ // DOM 操作示例
119
+ const buttons = $node.querySelectorAll("button");
120
+ buttons.forEach((btn) => {
121
+ btn.addEventListener("mouseover", () => {
122
+ btn.style.transform = "scale(1.05)";
123
+ });
124
+ btn.addEventListener("mouseout", () => {
125
+ btn.style.transform = "scale(1)";
126
+ });
127
+ });
128
+ </script>
129
+ </html>
130
+ ```
131
+
132
+ ## 📖 核心概念
133
+
134
+ ### 1. 变量池与 runtime
135
+
136
+ 运行时只保留四层变量池:
137
+
138
+ - `$sys`:系统变量池,默认提供 `$router`、`$emit`、`$message`
139
+ - `$data`:当前组件实例自己的响应式状态和公开方法
140
+ - `$ctx`:组件树上下文,沿父子关系继承,适合放页面和业务上下文
141
+ - `$mod`:模块变量池,默认提供 `scoped`、`$axios`、`$i18n`、`$t`、`$bus`
142
+
143
+ 表达式解析顺序固定为:
144
+
145
+ ```text
146
+ $sys > $data > $ctx > $mod
147
+ ```
148
+
149
+ 当前源码里,`$sys/$ctx/$mod` 会打包成一个 `runtime` 对象传递,`$data` 仍然单独传递。
150
+
151
+ 例如:
152
+
153
+ ```html
154
+ <script setup>
155
+ count = 0
156
+ save = () => {
157
+ $message.success('saved')
158
+ }
159
+ $ctx.recordId = 'abc123'
160
+ </script>
161
+ ```
162
+
163
+ 这里:
164
+
165
+ - `count` 和 `save` 属于 `$data`
166
+ - `$message` 来自 `$sys`
167
+ - `recordId` 被写入 `$ctx`
168
+ - `$axios`、`$t` 等模块能力来自 `$mod`
169
+
170
+ ### 1. 数据绑定
171
+
172
+ #### 文本插值
173
+
174
+ ```html
175
+ <div>{{ message }}</div>
176
+ <div>{{ user.name }}</div>
177
+ <div>{{ items.length }} 个项目</div>
178
+ ```
179
+
180
+ #### 属性绑定
181
+
182
+ ```html
183
+ <a :href="url">链接</a>
184
+ <img :src="imageUrl" :alt="imageTitle" />
185
+ <div :class="{ active: isActive, disabled: !enabled }">动态类</div>
186
+ <div :style="{ color: textColor, fontSize: fontSize + 'px' }">动态样式</div>
187
+ ```
188
+
189
+ #### 事件绑定
190
+
191
+ ```html
192
+ <button @click="handleClick">点击</button>
193
+ <input @input="handleInput" @keyup.enter="submit" />
194
+ <div @mouseover="onHover" @mouseleave="onLeave">悬停区域</div>
195
+ ```
196
+
197
+ #### 双向绑定
198
+
199
+ ```html
200
+ <input v:value="username" />
201
+ <textarea v:value="description"></textarea>
202
+ <my-component v:data="formData"></my-component>
203
+ ```
204
+
205
+ ### 2. 条件渲染
206
+
207
+ ```html
208
+ <div v-if="user.isLogin">欢迎回来,{{ user.name }}!</div>
209
+ <div v-else-if="user.isGuest">您好,访客!</div>
210
+ <div v-else>请登录</div>
211
+
212
+ <div v-show="showDetails">详细信息</div>
213
+ ```
214
+
215
+ ### 3. 列表渲染
216
+
217
+ ```html
218
+ <!-- 基础循环 -->
219
+ <div v-for="item in items">{{ item.name }}</div>
220
+
221
+ <!-- 带索引的循环 -->
222
+ <div v-for="(item, index) in items">{{ index + 1 }}. {{ item.name }}</div>
223
+
224
+ <!-- 对象循环 -->
225
+ <div v-for="(value, key) in userInfo">{{ key }}: {{ value }}</div>
226
+
227
+ <!-- 嵌套循环 -->
228
+ <div v-for="category in categories">
229
+ <h3>{{ category.name }}</h3>
230
+ <div v-for="product in category.products">
231
+ {{ product.name }} - ¥{{ product.price }}
232
+ </div>
233
+ </div>
234
+ ```
235
+
236
+ ### 4. 组件开发
237
+
238
+ #### 创建组件 (`/ui/user/card.html`)
239
+
240
+ ```html
241
+ <head>
242
+ <meta
243
+ name="description"
244
+ content="用户卡片组件"
245
+ details="显示用户信息的卡片组件"
246
+ />
247
+ </head>
248
+ <style>
249
+ body {
250
+ border: 1px solid #ddd;
251
+ border-radius: 8px;
252
+ padding: 16px;
253
+ margin: 8px;
254
+ background: white;
255
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
256
+ }
257
+ .avatar {
258
+ width: 60px;
259
+ height: 60px;
260
+ border-radius: 50%;
261
+ object-fit: cover;
262
+ }
263
+ .user-info {
264
+ margin-left: 16px;
265
+ }
266
+ </style>
267
+ <body>
268
+ <div style="display: flex; align-items: center;">
269
+ <img :src="avatar" :alt="name" class="avatar" />
270
+ <div class="user-info">
271
+ <h3>{{ name }}</h3>
272
+ <p>{{ email }}</p>
273
+ <p>{{ role }}</p>
274
+ <button @click="viewProfile">查看详情</button>
275
+ </div>
276
+ </div>
277
+
278
+ <vslot name="actions">
279
+ <button>默认操作</button>
280
+ </vslot>
281
+ </body>
282
+
283
+ <script setup>
284
+ // 组件属性
285
+ name = "用户名";
286
+ email = "user@example.com";
287
+ avatar = "/default-avatar.png";
288
+ role = "普通用户";
289
+
290
+ // 组件方法
291
+ viewProfile = () => {
292
+ $emit("profile_clicked", { name, email });
293
+ $router.push(`/user/${name}`);
294
+ };
295
+ </script>
296
+
297
+ <script>
298
+ // 组件初始化
299
+ console.log("用户卡片组件已加载:", $data.name);
300
+ </script>
301
+ ```
302
+
303
+ #### 使用组件
304
+
305
+ ```html
306
+ <body>
307
+ <!-- 基础使用 -->
308
+ <user-card
309
+ :name="currentUser.name"
310
+ :email="currentUser.email"
311
+ :avatar="currentUser.avatar"
312
+ @profile_clicked="handleProfileClick"
313
+ >
314
+ <!-- 自定义插槽内容 -->
315
+ <div vslot="actions">
316
+ <button @click="editUser">编辑</button>
317
+ <button @click="deleteUser">删除</button>
318
+ </div>
319
+ </user-card>
320
+
321
+ <!-- 双向绑定 -->
322
+ <user-form v:user="editingUser"></user-form>
323
+
324
+ <!-- 循环渲染组件 -->
325
+ <div v-for="user in users">
326
+ <user-card :name="user.name" :email="user.email" :avatar="user.avatar">
327
+ </user-card>
328
+ </div>
329
+ </body>
330
+
331
+ <script setup>
332
+ currentUser = {
333
+ name: "张三",
334
+ email: "zhangsan@example.com",
335
+ avatar: "/avatars/zhangsan.jpg",
336
+ };
337
+
338
+ users = [
339
+ { name: "李四", email: "lisi@example.com", avatar: "/avatars/lisi.jpg" },
340
+ {
341
+ name: "王五",
342
+ email: "wangwu@example.com",
343
+ avatar: "/avatars/wangwu.jpg",
344
+ },
345
+ ];
346
+
347
+ handleProfileClick = (userData) => {
348
+ console.log("用户点击了查看详情:", userData);
349
+ $message.info(`正在查看 ${userData.name} 的详情`);
350
+ };
351
+
352
+ editUser = () => {
353
+ $message.info("编辑用户功能");
354
+ };
355
+
356
+ deleteUser = () => {
357
+ $message
358
+ .confirm("确定要删除该用户吗?", {
359
+ title: "删除确认",
360
+ confirmText: "删除",
361
+ cancelText: "取消",
362
+ })
363
+ .then(() => {
364
+ $message.success("用户已删除");
365
+ })
366
+ .catch(() => {
367
+ $message.info("已取消删除");
368
+ });
369
+ };
370
+ </script>
371
+ ```
372
+
373
+ ### 5. 路由管理
374
+
375
+ ```html
376
+ <body>
377
+ <!-- 路由链接 -->
378
+ <nav>
379
+ <a href="/home">首页</a>
380
+ <a href="/about">关于</a>
381
+ <a href="/user/123">用户详情</a>
382
+ </nav>
383
+
384
+ <!-- 路由视图 -->
385
+ <vrouter></vrouter>
386
+ </body>
387
+
388
+ <script setup>
389
+ // 路由跳转方法
390
+ goToHome = () => {
391
+ $router.push("/home");
392
+ };
393
+
394
+ goBack = () => {
395
+ $router.back();
396
+ };
397
+
398
+ // 获取路由参数
399
+ userId = $router.params.id; // 从 /user/:id 获取
400
+ keyword = $router.query.q; // 从 ?q=keyword 获取
401
+ </script>
402
+ ```
403
+
404
+ ### 6. 数据请求
405
+
406
+ ```javascript
407
+ // GET 请求
408
+ $mod.$axios
409
+ .get("/api/users")
410
+ .then((users) => {
411
+ $data.users = users;
412
+ })
413
+ .catch((error) => {
414
+ console.error("获取用户列表失败:", error);
415
+ $message.error("加载失败,请重试");
416
+ });
417
+
418
+ // POST 请求
419
+ $mod.$axios
420
+ .post("/api/users", {
421
+ name: $data.newUser.name,
422
+ email: $data.newUser.email,
423
+ })
424
+ .then((result) => {
425
+ $message.success("用户创建成功");
426
+ $data.users.push(result);
427
+ })
428
+ .catch((error) => {
429
+ $message.error("创建失败:" + error.message);
430
+ });
431
+
432
+ // 带参数的请求
433
+ $mod.$axios
434
+ .get("/api/search", {
435
+ params: {
436
+ keyword: $data.searchText,
437
+ page: $data.currentPage,
438
+ },
439
+ })
440
+ .then((result) => {
441
+ $data.searchResults = result.items;
442
+ $data.totalPages = result.totalPages;
443
+ });
444
+ ```
445
+
446
+ 模块能力也可以直接在模板和脚本里读取,例如 `$t('common.save')`、`$bus.emit(...)`。这些值都来自 `$mod`。
447
+
448
+ ### 7. slot 语义
449
+
450
+ `vslot` 只保留一套语义:
451
+
452
+ - projected content 使用调用方的 `$sys/$data/$ctx/$mod`
453
+ - 子组件 `<vslot>` 的 fallback content 使用子组件自己的 runtime
454
+ - `vbind` 只为 slot outlet 追加这一次渲染的临时变量,不覆盖调用方 runtime
455
+
456
+ 这意味着跨模块调用组件时,slot 里的 `$mod` 和 `$t()` 仍然以调用方模块为准,不会被子模块抢走。
457
+
458
+ ## 🔧 内置功能
459
+
460
+ ### 消息提示
461
+
462
+ ```javascript
463
+ // 基础消息
464
+ $message.info("信息提示");
465
+ $message.success("操作成功");
466
+ $message.warning("警告信息");
467
+ $message.error("错误信息");
468
+
469
+ // 确认对话框
470
+ $message
471
+ .confirm("确定要执行此操作吗?", {
472
+ title: "操作确认",
473
+ confirmText: "确定",
474
+ cancelText: "取消",
475
+ })
476
+ .then(() => {
477
+ // 用户点击确定
478
+ })
479
+ .catch(() => {
480
+ // 用户点击取消
481
+ });
482
+
483
+ // 输入对话框
484
+ $message
485
+ .input("请输入新名称:", {
486
+ title: "重命名",
487
+ inputValue: "默认值",
488
+ confirmText: "确定",
489
+ cancelText: "取消",
490
+ })
491
+ .then((value) => {
492
+ console.log("用户输入:", value);
493
+ });
494
+ ```
495
+
496
+ ### 数据监听
497
+
498
+ ```javascript
499
+ // 监听数据变化
500
+ $watch(() => {
501
+ // 访问需要监听的数据
502
+ console.log("用户名变化:", $data.username);
503
+ console.log("邮箱变化:", $data.email);
504
+
505
+ // 可以执行相关逻辑
506
+ if ($data.username && $data.email) {
507
+ $data.isFormValid = true;
508
+ }
509
+ });
510
+
511
+ // 监听特定条件
512
+ $watch(() => {
513
+ if ($data.cart.items.length > 10) {
514
+ $message.warning("购物车商品过多,建议及时结算");
515
+ }
516
+ });
517
+ ```
518
+
519
+ ## 📋 最佳实践
520
+
521
+ ### 1. 项目结构
522
+
523
+ ```
524
+ ├── ui/ # 静态资源根目录
525
+ │ ├── assets # 非组件静态资源目录
526
+ │ │ ├──common.css # 全局公用样式
527
+ │ ├── layout/ # 布局文件目录
528
+ │ │ ├── default.html # 默认布局, 比如包含header,footer等公共部分
529
+ │ ├── page/ # 页面文件目录
530
+ │ │ ├── index.html
531
+ │ │ ├── 404.html
532
+ │ │ ├── **/**/*.html
533
+ │ │ └── ...
534
+ │ ├── **/**/*.html # 其他组件页面文件,
535
+ │ ├── root.html # vhtml 根页面,后端非资源请求默认返回该文件
536
+ │ └── app.js # 环境变量初始化, 注册页面路由
537
+
538
+ ```
539
+
540
+ ### 2. 命名规范
541
+
542
+ - **组件名称**:使用短横线分隔,如 `user-card`, `product-list`
543
+ - **事件名称**:使用蛇形命名,如 `item_selected`, `form_submitted`
544
+
545
+ ### 3. 性能优化
546
+
547
+ - 合理使用 `v-show` vs `v-if`
548
+ - 避免在同一元素上使用多个指令
549
+ - 大列表使用虚拟滚动
550
+ - 组件懒加载
551
+
552
+ ### 4. 错误处理
553
+
554
+ ```javascript
555
+ // API 错误处理
556
+ $axios
557
+ .get("/api/data")
558
+ .then((data) => {
559
+ $data.items = data;
560
+ })
561
+ .catch((error) => {
562
+ console.error("API 错误:", error);
563
+ $message.error("数据加载失败,请稍后重试");
564
+ // 设置默认数据或错误状态
565
+ $data.items = [];
566
+ $data.hasError = true;
567
+ });
568
+ ```
569
+
570
+ ## 🎯 高级功能
571
+
572
+ ### 插槽高级用法
573
+
574
+ ```html
575
+ <!-- 作用域插槽 -->
576
+ <vslot name="item" v="currentItem, currentIndex">
577
+ <div>默认项目模板</div>
578
+ </vslot>
579
+ ```
580
+
581
+ ### 自定义指令
582
+
583
+ ```html
584
+ <!-- DOM 引用 -->
585
+ <input vdom="searchInput" />
586
+
587
+ <script>
588
+ // 通过 vdom 获取 DOM 引用
589
+ $data.searchInput.focus();
590
+ </script>
591
+ ```
592
+
593
+ ## 🔗 链接
594
+
595
+ - [GitHub 仓库](https://github.com/veypi/vhtml)
596
+ - [NPM 包](https://www.npmjs.com/package/@veypi/vhtml)
597
+ - [问题反馈](https://github.com/veypi/vhtml/issues)
598
+
599
+ ## 📄 许可证
600
+
601
+ [Apache License 2.0](LICENSE)
602
+
603
+ ---
604
+
605
+ **vhtml** - 让前端开发更简单! 🚀