m8-codex-mcp 1.0.0
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/README.md +113 -0
- package/data/m8mpdoc/UI/347/273/204/344/273/266/345/272/223/000-/347/273/204/344/273/266/345/272/223/344/270/213/350/275/275/344/275/277/347/224/250.md +188 -0
- package/data/m8mpdoc/UI/347/273/204/344/273/266/345/272/223/001-actionsheet/345/212/250/344/275/234/351/235/242/346/235/277.md +460 -0
- package/data/m8mpdoc/UI/347/273/204/344/273/266/345/272/223/004-amap/345/234/260/345/233/276.md +285 -0
- package/data/m8mpdoc/UI/347/273/204/344/273/266/345/272/223/005-button/346/214/211/351/222/256.md +211 -0
- package/data/m8mpdoc/UI/347/273/204/344/273/266/345/272/223/008-cell/345/215/225/345/205/203/346/240/274.md +213 -0
- package/data/m8mpdoc/UI/347/273/204/344/273/266/345/272/223/009-checkbox/345/244/215/351/200/211/346/241/206.md +501 -0
- package/data/m8mpdoc/UI/347/273/204/344/273/266/345/272/223/010-circle/347/216/257/345/275/242/350/277/233/345/272/246/346/235/241.md +168 -0
- package/data/m8mpdoc/UI/347/273/204/344/273/266/345/272/223/011-datepicker/346/227/245/346/234/237/351/200/211/346/213/251.md +617 -0
- package/data/m8mpdoc/UI/347/273/204/344/273/266/345/272/223/013-field/350/276/223/345/205/245/346/241/206.md +539 -0
- package/data/m8mpdoc/UI/347/273/204/344/273/266/345/272/223/015-form/350/241/250/345/215/225.md +999 -0
- package/data/m8mpdoc/UI/347/273/204/344/273/266/345/272/223/018-header/345/244/264/351/203/250/345/257/274/350/210/252/346/240/217.md +150 -0
- package/data/m8mpdoc/UI/347/273/204/344/273/266/345/272/223/019-icon/345/233/276/346/240/207.md +133 -0
- package/data/m8mpdoc/UI/347/273/204/344/273/266/345/272/223/020-loading/345/212/240/350/275/275.md +117 -0
- package/data/m8mpdoc/UI/347/273/204/344/273/266/345/272/223/021-noticebar/351/200/232/347/237/245/346/240/217.md +152 -0
- package/data/m8mpdoc/UI/347/273/204/344/273/266/345/272/223/022-numberkeyboard/346/225/260/345/255/227/351/224/256/347/233/230.md +427 -0
- package/data/m8mpdoc/UI/347/273/204/344/273/266/345/272/223/023-pagination/345/210/206/351/241/265.md +212 -0
- package/data/m8mpdoc/UI/347/273/204/344/273/266/345/272/223/024-panel/351/235/242/346/235/277.md +85 -0
- package/data/m8mpdoc/UI/347/273/204/344/273/266/345/272/223/025-passwordinput/345/257/206/347/240/201/350/276/223/345/205/245/346/241/206.md +175 -0
- package/data/m8mpdoc/UI/347/273/204/344/273/266/345/272/223/026-picker/351/200/211/346/213/251/345/231/250.md +519 -0
- package/data/m8mpdoc/UI/347/273/204/344/273/266/345/272/223/027-popup/345/274/271/345/207/272/345/261/202.md +152 -0
- package/data/m8mpdoc/UI/347/273/204/344/273/266/345/272/223/028-progress/350/277/233/345/272/246/346/235/241.md +103 -0
- package/data/m8mpdoc/UI/347/273/204/344/273/266/345/272/223/029-radio/345/215/225/351/200/211/346/241/206.md +285 -0
- package/data/m8mpdoc/UI/347/273/204/344/273/266/345/272/223/030-rate/350/257/204/345/210/206.md +189 -0
- package/data/m8mpdoc/UI/347/273/204/344/273/266/345/272/223/031-search/346/220/234/347/264/242.md +217 -0
- package/data/m8mpdoc/UI/347/273/204/344/273/266/345/272/223/032-slider/346/273/221/345/235/227.md +166 -0
- package/data/m8mpdoc/UI/347/273/204/344/273/266/345/272/223/033-stepper/346/255/245/350/277/233/345/231/250.md +340 -0
- package/data/m8mpdoc/UI/347/273/204/344/273/266/345/272/223/035-swipecell/346/273/221/345/212/250/345/215/225/345/205/203/346/240/274.md +265 -0
- package/data/m8mpdoc/UI/347/273/204/344/273/266/345/272/223/036-switch/345/274/200/345/205/263.md +196 -0
- package/data/m8mpdoc/UI/347/273/204/344/273/266/345/272/223/037-switchcell/345/274/200/345/205/263/345/215/225/345/205/203/346/240/274.md +115 -0
- package/data/m8mpdoc/UI/347/273/204/344/273/266/345/272/223/038-tag/346/240/207/350/256/260.md +232 -0
- package/data/m8mpdoc/UI/347/273/204/344/273/266/345/272/223/039-treeselect/345/210/206/347/261/273/351/200/211/346/213/251.md +631 -0
- package/data/m8mpdoc/UI/347/273/204/344/273/266/345/272/223/040-uploader/346/226/207/344/273/266/344/270/212/344/274/240.md +531 -0
- package/data/m8mpdoc/UI/347/273/204/344/273/266/345/272/223/041-verifycode/351/252/214/350/257/201/347/240/201.md +111 -0
- package/data/m8mpdoc/UI/347/273/204/344/273/266/345/272/223/042-minirefresh/344/270/213/346/213/211/345/210/267/346/226/260.md +337 -0
- package/data/m8mpdoc/UI/347/273/204/344/273/266/345/272/223/043-layout/345/270/203/345/261/200.md +150 -0
- package/data/m8mpdoc/UI/347/273/204/344/273/266/345/272/223/044-image/345/233/276/347/211/207.md +144 -0
- package/data/m8mpdoc/UI/347/273/204/344/273/266/345/272/223/045-toast/350/275/273/346/217/220/347/244/272.md +429 -0
- package/data/m8mpdoc/UI/347/273/204/344/273/266/345/272/223/046-calendar/346/227/245/345/216/206.md +467 -0
- package/data/m8mpdoc/UI/347/273/204/344/273/266/345/272/223/047-area/347/234/201/345/270/202/345/214/272/351/200/211/346/213/251.md +295 -0
- package/data/m8mpdoc/UI/347/273/204/344/273/266/345/272/223/048-tab/346/240/207/347/255/276/351/241/265.md +577 -0
- package/data/m8mpdoc/UI/347/273/204/344/273/266/345/272/223/049-dialog/345/274/271/345/207/272/346/241/206.md +491 -0
- package/data/m8mpdoc/UI/347/273/204/344/273/266/345/272/223/050-dropdownmenu/344/270/213/346/213/211/350/217/234/345/215/225.md +265 -0
- package/data/m8mpdoc/UI/347/273/204/344/273/266/345/272/223/051-notify/346/266/210/346/201/257/351/200/232/347/237/245.md +203 -0
- package/data/m8mpdoc/UI/347/273/204/344/273/266/345/272/223/052-overlay/351/201/256/347/275/251/345/261/202.md +139 -0
- package/data/m8mpdoc/UI/347/273/204/344/273/266/345/272/223/053-collapse/346/212/230/345/217/240/351/235/242/346/235/277.md +199 -0
- package/data/m8mpdoc/UI/347/273/204/344/273/266/345/272/223/054-grid/345/256/253/346/240/274.md +183 -0
- package/data/m8mpdoc/UI/347/273/204/344/273/266/345/272/223/055-countdown/345/200/222/350/256/241/346/227/266.md +289 -0
- package/data/m8mpdoc/UI/347/273/204/344/273/266/345/272/223/056-divider/345/210/206/345/211/262/347/272/277.md +97 -0
- package/data/m8mpdoc/UI/347/273/204/344/273/266/345/272/223/057-empty/347/251/272/347/212/266/346/200/201.md +146 -0
- package/data/m8mpdoc/UI/347/273/204/344/273/266/345/272/223/058-imagepreview/345/233/276/347/211/207/351/242/204/350/247/210.md +292 -0
- package/data/m8mpdoc/UI/347/273/204/344/273/266/345/272/223/059-lazyload/346/207/222/345/212/240/350/275/275.md +120 -0
- package/data/m8mpdoc/UI/347/273/204/344/273/266/345/272/223/060-skeleton/351/252/250/346/236/266/345/261/217.md +114 -0
- package/data/m8mpdoc/UI/347/273/204/344/273/266/345/272/223/061-steps/346/255/245/351/252/244/346/235/241.md +119 -0
- package/data/m8mpdoc/UI/347/273/204/344/273/266/345/272/223/062-sticky/347/262/230/346/200/247/345/270/203/345/261/200.md +208 -0
- package/data/m8mpdoc/UI/347/273/204/344/273/266/345/272/223/063-indexbar/347/264/242/345/274/225/346/240/217.md +161 -0
- package/data/m8mpdoc/UI/347/273/204/344/273/266/345/272/223/064-sidebar/344/276/247/350/276/271/345/257/274/350/210/252.md +248 -0
- package/data/m8mpdoc/UI/347/273/204/344/273/266/345/272/223/065-tabbar/346/240/207/347/255/276/346/240/217.md +314 -0
- package/data/m8mpdoc/UI/347/273/204/344/273/266/345/272/223/066-badge/345/276/275/346/240/207.md +162 -0
- package/data/m8mpdoc/UI/347/273/204/344/273/266/345/272/223/067-popover/346/260/224/346/263/241/345/274/271/345/207/272/346/241/206.md +325 -0
- package/data/m8mpdoc/UI/347/273/204/344/273/266/345/272/223/068-cascader/347/272/247/350/201/224/351/200/211/346/213/251.md +360 -0
- package/data/m8mpdoc/UI/347/273/204/344/273/266/345/272/223/069-selectperson/351/200/211/344/272/272/347/273/204/344/273/266.md +595 -0
- package/data/m8mpdoc/UI/347/273/204/344/273/266/345/272/223/070-swipe/350/275/256/346/222/255.md +262 -0
- package/data/m8mpdoc/UI/347/273/204/344/273/266/345/272/223/071-/345/233/275/351/231/205/345/214/226.md +51 -0
- package/data/m8mpdoc/UI/347/273/204/344/273/266/345/272/223/072-easycalendar/346/227/245/345/216/206.md +132 -0
- package/data/m8mpdoc/UI/347/273/204/344/273/266/345/272/223/073-qrcode/344/272/214/347/273/264/347/240/201.md +1538 -0
- package/data/m8mpdoc/UI/347/273/204/344/273/266/345/272/223/074-imagescale/345/233/276/347/211/207/350/243/201/345/211/252.md +261 -0
- package/data/m8mpdoc/UI/347/273/204/344/273/266/345/272/223/075-dragsort/346/213/226/346/213/275/346/216/222/345/272/217.md +161 -0
- package/data/m8mpdoc/UI/347/273/204/344/273/266/345/272/223/076-chart/345/233/276/350/241/250.md +381 -0
- package/data/m8mpdoc/UI/347/273/204/344/273/266/345/272/223/077-rtc/351/237/263/350/247/206/351/242/221.md +531 -0
- package/data/m8mpdoc/UI/347/273/204/344/273/266/345/272/223/078-table/350/241/250/346/240/274.md +849 -0
- package/data/m8mpdoc//345/205/270/345/236/213/346/241/210/344/276/213/003-/345/210/227/350/241/250/350/257/246/346/203/205.md +247 -0
- package/data/m8mpdoc//345/205/270/345/236/213/346/241/210/344/276/213/003-/345/210/227/350/241/250/350/257/246/346/203/205vue3.md +276 -0
- package/data/m8mpdoc//345/205/270/345/236/213/346/241/210/344/276/213/003-/350/241/250/345/215/225/346/217/220/344/272/244.md +130 -0
- package/data/m8mpdoc//345/205/270/345/236/213/346/241/210/344/276/213/003-/350/241/250/345/215/225/346/217/220/344/272/244vue3.md +115 -0
- package/data/m8mpdoc//346/240/270/345/277/203/351/200/232/347/224/250Util/345/267/245/345/205/267/345/272/223/Ajax/344/270/216/346/226/207/344/273/266/344/270/212/344/274/240.md +456 -0
- package/data/m8mpdoc//346/240/270/345/277/203/351/200/232/347/224/250Util/345/267/245/345/205/267/345/272/223//345/267/245/345/205/267/345/207/275/346/225/260/345/272/223.md +398 -0
- package/data/standards/01-project/naming.md +158 -0
- package/data/standards/01-project/structure.md +106 -0
- package/data/standards/01-project/version-detection.md +195 -0
- package/data/standards/02-vue/basic.md +242 -0
- package/data/standards/02-vue/component.md +299 -0
- package/data/standards/02-vue/examples.md +240 -0
- package/data/standards/02-vue/performance.md +74 -0
- package/data/standards/02-vue/state-management.md +293 -0
- package/data/standards/03-css/index.md +165 -0
- package/data/standards/04-api/ajax.md +178 -0
- package/data/standards/04-api/ejs-api.md +192 -0
- package/data/standards/04-api/util.md +166 -0
- package/data/standards/05-typescript/index.md +166 -0
- package/data/standards/06-mock/index.md +154 -0
- package/data/standards/07-router/index.md +141 -0
- package/data/standards/README.md +82 -0
- package/data/standards/_index.md +215 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2 -0
- package/dist/knowledge/index.d.ts +36 -0
- package/dist/knowledge/index.d.ts.map +1 -0
- package/dist/knowledge/index.js +1 -0
- package/dist/templates/vue2.d.ts +41 -0
- package/dist/templates/vue2.d.ts.map +1 -0
- package/dist/templates/vue2.js +1 -0
- package/dist/templates/vue3.d.ts +41 -0
- package/dist/templates/vue3.d.ts.map +1 -0
- package/dist/templates/vue3.js +1 -0
- package/dist/tools/generate_module_structure.d.ts +21 -0
- package/dist/tools/generate_module_structure.d.ts.map +1 -0
- package/dist/tools/generate_module_structure.js +1 -0
- package/package.json +47 -0
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
# 组件开发规范
|
|
2
|
+
|
|
3
|
+
> 定义 Vue 组件开发的标准规范与最佳实践。
|
|
4
|
+
|
|
5
|
+
## 📝 组件使用原则
|
|
6
|
+
|
|
7
|
+
### API 优先原则
|
|
8
|
+
|
|
9
|
+
> **优先使用 EJS API,仅在 API 无法满足时才考虑使用组件形式**
|
|
10
|
+
|
|
11
|
+
| 场景 | 优先使用 | 备选方案 |
|
|
12
|
+
| ------------- | --------------------- | ------------- |
|
|
13
|
+
| 单选/多选列表 | `ejs.ui.picker` | `em-picker` |
|
|
14
|
+
| 日期选择 | `ejs.ui.pickDate` | - |
|
|
15
|
+
| 时间选择 | `ejs.ui.pickTime` | - |
|
|
16
|
+
| 日期时间选择 | `ejs.ui.pickDateTime` | - |
|
|
17
|
+
| 确认对话框 | `ejs.ui.confirm` | `em-dialog` |
|
|
18
|
+
| 提示信息 | `ejs.ui.alert` | - |
|
|
19
|
+
| 操作提示 | `ejs.ui.toast` | - |
|
|
20
|
+
| 加载提示 | `ejs.ui.showWaiting` | `em-loading` |
|
|
21
|
+
| 级联选择 | - | `em-cascader` |
|
|
22
|
+
|
|
23
|
+
## 🏗️ 组件开发规范
|
|
24
|
+
|
|
25
|
+
### 文件命名
|
|
26
|
+
|
|
27
|
+
| 类型 | 命名规则 | 示例 |
|
|
28
|
+
| -------- | ------------- | --------------- |
|
|
29
|
+
| 通用组件 | PascalCase | `UserCard.vue` |
|
|
30
|
+
| 页面组件 | 小写 + 下划线 | `user_list.vue` |
|
|
31
|
+
|
|
32
|
+
### Vue3 组件要求
|
|
33
|
+
|
|
34
|
+
**必须使用 `<script setup>` 语法**:
|
|
35
|
+
|
|
36
|
+
```vue
|
|
37
|
+
<template>
|
|
38
|
+
<view class="user-card">
|
|
39
|
+
<text>{{ userName }}</text>
|
|
40
|
+
</view>
|
|
41
|
+
</template>
|
|
42
|
+
|
|
43
|
+
<script lang="ts" setup>
|
|
44
|
+
import { ref, computed } from "vue";
|
|
45
|
+
|
|
46
|
+
// Props 定义
|
|
47
|
+
interface Props {
|
|
48
|
+
userId: string;
|
|
49
|
+
userName?: string;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
53
|
+
userName: "默认用户",
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
// Emits 定义
|
|
57
|
+
const emit = defineEmits<{
|
|
58
|
+
(e: "update", value: string): void;
|
|
59
|
+
(e: "delete", id: string): void;
|
|
60
|
+
}>();
|
|
61
|
+
|
|
62
|
+
// 响应式数据
|
|
63
|
+
const count = ref(0);
|
|
64
|
+
|
|
65
|
+
// 计算属性
|
|
66
|
+
const displayName = computed(() => `用户: ${props.userName}`);
|
|
67
|
+
|
|
68
|
+
// 方法
|
|
69
|
+
const handleClick = () => {
|
|
70
|
+
emit("update", props.userId);
|
|
71
|
+
};
|
|
72
|
+
</script>
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### Props 规范
|
|
76
|
+
|
|
77
|
+
**必须指定类型和默认值**:
|
|
78
|
+
|
|
79
|
+
```javascript
|
|
80
|
+
// Vue2
|
|
81
|
+
export default {
|
|
82
|
+
props: {
|
|
83
|
+
// ✅ 正确:指定类型和默认值
|
|
84
|
+
title: {
|
|
85
|
+
type: String,
|
|
86
|
+
default: "",
|
|
87
|
+
},
|
|
88
|
+
list: {
|
|
89
|
+
type: Array,
|
|
90
|
+
default: () => [],
|
|
91
|
+
},
|
|
92
|
+
config: {
|
|
93
|
+
type: Object,
|
|
94
|
+
default: () => ({}),
|
|
95
|
+
},
|
|
96
|
+
// 必填属性
|
|
97
|
+
userId: {
|
|
98
|
+
type: String,
|
|
99
|
+
required: true,
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
};
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
```typescript
|
|
106
|
+
// Vue3
|
|
107
|
+
interface Props {
|
|
108
|
+
title?: string;
|
|
109
|
+
list?: string[];
|
|
110
|
+
config?: Record<string, any>;
|
|
111
|
+
userId: string; // 必填
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
115
|
+
title: "",
|
|
116
|
+
list: () => [],
|
|
117
|
+
config: () => ({}),
|
|
118
|
+
});
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### 组件通信
|
|
122
|
+
|
|
123
|
+
**禁止直接修改 props,使用 emit 通知父组件更新**:
|
|
124
|
+
|
|
125
|
+
```vue
|
|
126
|
+
<!-- ❌ 错误:直接修改 props -->
|
|
127
|
+
<script setup>
|
|
128
|
+
const props = defineProps<{ value: string }>();
|
|
129
|
+
// 错误!
|
|
130
|
+
props.value = 'new value';
|
|
131
|
+
</script>
|
|
132
|
+
|
|
133
|
+
<!-- ✅ 正确:使用 emit -->
|
|
134
|
+
<script setup>
|
|
135
|
+
const props = defineProps<{ value: string }>();
|
|
136
|
+
const emit = defineEmits<{
|
|
137
|
+
(e: 'update:value', value: string): void;
|
|
138
|
+
}>();
|
|
139
|
+
|
|
140
|
+
const updateValue = (newValue: string) => {
|
|
141
|
+
emit('update:value', newValue);
|
|
142
|
+
};
|
|
143
|
+
</script>
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
## 📦 常用组件使用规范
|
|
147
|
+
|
|
148
|
+
### em-field 输入框
|
|
149
|
+
|
|
150
|
+
#### readonly 状态
|
|
151
|
+
|
|
152
|
+
**必须同时添加 `is-link` 和 `clickable` 属性**:
|
|
153
|
+
|
|
154
|
+
```vue
|
|
155
|
+
<!-- ❌ 错误示例 -->
|
|
156
|
+
<em-field v-model="date" readonly label="选择日期" />
|
|
157
|
+
|
|
158
|
+
<!-- ✅ 正确示例 -->
|
|
159
|
+
<em-field
|
|
160
|
+
v-model="date"
|
|
161
|
+
readonly
|
|
162
|
+
clickable
|
|
163
|
+
is-link
|
|
164
|
+
label="选择日期"
|
|
165
|
+
@click="showDatePicker"
|
|
166
|
+
/>
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
#### textarea 类型
|
|
170
|
+
|
|
171
|
+
**建议添加 `autosize` 属性**:
|
|
172
|
+
|
|
173
|
+
```vue
|
|
174
|
+
<em-field
|
|
175
|
+
v-model="content"
|
|
176
|
+
type="textarea"
|
|
177
|
+
label="内容"
|
|
178
|
+
placeholder="请输入内容"
|
|
179
|
+
autosize
|
|
180
|
+
/>
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
#### 输入格式限制
|
|
184
|
+
|
|
185
|
+
```vue
|
|
186
|
+
<!-- 手机号:仅数字 -->
|
|
187
|
+
<em-field
|
|
188
|
+
v-model="phone"
|
|
189
|
+
type="tel"
|
|
190
|
+
label="手机号"
|
|
191
|
+
placeholder="请输入手机号"
|
|
192
|
+
:rules="[
|
|
193
|
+
{
|
|
194
|
+
validator: (val) => Util.string.isMobile(val),
|
|
195
|
+
message: '请输入正确的手机号',
|
|
196
|
+
},
|
|
197
|
+
]"
|
|
198
|
+
/>
|
|
199
|
+
|
|
200
|
+
<!-- 带小数位限制 -->
|
|
201
|
+
<em-field
|
|
202
|
+
v-model="amount"
|
|
203
|
+
type="number"
|
|
204
|
+
label="金额"
|
|
205
|
+
placeholder="请输入金额"
|
|
206
|
+
/>
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
### em-uploader 附件上传
|
|
210
|
+
|
|
211
|
+
**样式上自成一行,不通过 Slots 传入 em-field**:
|
|
212
|
+
|
|
213
|
+
```vue
|
|
214
|
+
<!-- ✅ 正确:独立使用 -->
|
|
215
|
+
<em-field label="附件">
|
|
216
|
+
<template #input>
|
|
217
|
+
<!-- 仅显示标签 -->
|
|
218
|
+
</template>
|
|
219
|
+
</em-field>
|
|
220
|
+
<em-uploader :after-read="afterRead" />
|
|
221
|
+
|
|
222
|
+
<!-- ❌ 错误:嵌入 em-field -->
|
|
223
|
+
<em-field label="附件">
|
|
224
|
+
<template #input>
|
|
225
|
+
<em-uploader :after-read="afterRead" />
|
|
226
|
+
</template>
|
|
227
|
+
</em-field>
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
### em-search 搜索框
|
|
231
|
+
|
|
232
|
+
```vue
|
|
233
|
+
<em-search
|
|
234
|
+
v-model="keyword"
|
|
235
|
+
placeholder="请输入搜索关键词"
|
|
236
|
+
@search="onSearch"
|
|
237
|
+
/>
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
### em-picker 选择器
|
|
241
|
+
|
|
242
|
+
**使用注意事项**:
|
|
243
|
+
|
|
244
|
+
1. 无需与 `em-popup` 绑定使用
|
|
245
|
+
2. 必须添加 `show-toolbar` 属性
|
|
246
|
+
|
|
247
|
+
```vue
|
|
248
|
+
<!-- Vue3 示例 -->
|
|
249
|
+
<template>
|
|
250
|
+
<em-picker
|
|
251
|
+
v-model="selectedValue"
|
|
252
|
+
:columns="columns"
|
|
253
|
+
show-toolbar
|
|
254
|
+
@confirm="onConfirm"
|
|
255
|
+
@cancel="onCancel"
|
|
256
|
+
/>
|
|
257
|
+
</template>
|
|
258
|
+
|
|
259
|
+
<script lang="ts" setup>
|
|
260
|
+
import { ref } from "vue";
|
|
261
|
+
|
|
262
|
+
const columns = ref(["选项1", "选项2", "选项3"]);
|
|
263
|
+
const selectedValue = ref("");
|
|
264
|
+
|
|
265
|
+
const onConfirm = (value: string) => {
|
|
266
|
+
selectedValue.value = value;
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
const onCancel = () => {
|
|
270
|
+
// 取消选择
|
|
271
|
+
};
|
|
272
|
+
</script>
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
## 🔍 响应式数据管理
|
|
276
|
+
|
|
277
|
+
### Vue3 响应式原则
|
|
278
|
+
|
|
279
|
+
> **不是所有数据都需要是响应式的**
|
|
280
|
+
|
|
281
|
+
```typescript
|
|
282
|
+
// ✅ 需要响应式:页面显示相关
|
|
283
|
+
const userName = ref(""); // 需要显示
|
|
284
|
+
const userList = ref([]); // 需要渲染列表
|
|
285
|
+
|
|
286
|
+
// ❌ 不需要响应式:非渲染数据
|
|
287
|
+
const API_URL = "/api/users"; // 常量
|
|
288
|
+
let timer: number; // 定时器引用
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
## ✅ 组件检查清单
|
|
292
|
+
|
|
293
|
+
- [ ] 通用组件使用 PascalCase 命名
|
|
294
|
+
- [ ] Vue3 使用 `<script setup>` 语法
|
|
295
|
+
- [ ] Props 指定类型和默认值
|
|
296
|
+
- [ ] 使用 emit 而非直接修改 props
|
|
297
|
+
- [ ] readonly 输入框添加 `is-link` 和 `clickable`
|
|
298
|
+
- [ ] textarea 添加 `autosize` 属性
|
|
299
|
+
- [ ] 输入框设置 placeholder
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
# Vue 典型示例
|
|
2
|
+
|
|
3
|
+
> 提供 Vue2 和 Vue3 的表单页面与列表页面典型代码示例。
|
|
4
|
+
|
|
5
|
+
## 📝 表单页面示例
|
|
6
|
+
|
|
7
|
+
### Vue2 表单
|
|
8
|
+
|
|
9
|
+
```vue
|
|
10
|
+
<template>
|
|
11
|
+
<em-form @submit="onSubmit" @failed="onFailed">
|
|
12
|
+
<em-field
|
|
13
|
+
v-model="form.username"
|
|
14
|
+
label="用户名"
|
|
15
|
+
placeholder="请输入用户名"
|
|
16
|
+
:rules="[{ required: true, message: '请填写用户名' }]"
|
|
17
|
+
/>
|
|
18
|
+
<em-field
|
|
19
|
+
v-model="form.password"
|
|
20
|
+
type="password"
|
|
21
|
+
label="密码"
|
|
22
|
+
placeholder="请输入密码"
|
|
23
|
+
:rules="[{ required: true, message: '请填写密码' }]"
|
|
24
|
+
/>
|
|
25
|
+
<div style="margin: 16px;">
|
|
26
|
+
<em-button round block type="info" native-type="submit">提交</em-button>
|
|
27
|
+
</div>
|
|
28
|
+
</em-form>
|
|
29
|
+
</template>
|
|
30
|
+
|
|
31
|
+
<script>
|
|
32
|
+
export default {
|
|
33
|
+
data() {
|
|
34
|
+
return {
|
|
35
|
+
form: {
|
|
36
|
+
username: "", // 用户名
|
|
37
|
+
password: "", // 用户密码
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
},
|
|
41
|
+
computed: {},
|
|
42
|
+
watch: {},
|
|
43
|
+
created() {},
|
|
44
|
+
mounted() {},
|
|
45
|
+
methods: {
|
|
46
|
+
// 验证通过后触发
|
|
47
|
+
onSubmit(value) {
|
|
48
|
+
console.log("submit", this.form);
|
|
49
|
+
},
|
|
50
|
+
// 验证不通过后触发
|
|
51
|
+
onFailed(errorInfo) {
|
|
52
|
+
console.log("error", errorInfo);
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
</script>
|
|
57
|
+
|
|
58
|
+
<style lang="scss" scoped>
|
|
59
|
+
@import "./css/form.scss";
|
|
60
|
+
</style>
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Vue3 表单
|
|
64
|
+
|
|
65
|
+
```vue
|
|
66
|
+
<template>
|
|
67
|
+
<em-form @submit="onSubmit" @failed="onFailed">
|
|
68
|
+
<em-field
|
|
69
|
+
v-model="form.username"
|
|
70
|
+
label="用户名"
|
|
71
|
+
placeholder="请输入用户名"
|
|
72
|
+
:rules="[{ required: true, message: '请填写用户名' }]"
|
|
73
|
+
/>
|
|
74
|
+
<em-field
|
|
75
|
+
v-model="form.password"
|
|
76
|
+
type="password"
|
|
77
|
+
label="密码"
|
|
78
|
+
placeholder="请输入密码"
|
|
79
|
+
:rules="[{ required: true, message: '请填写密码' }]"
|
|
80
|
+
/>
|
|
81
|
+
<div style="margin: 16px">
|
|
82
|
+
<em-button round block type="info" native-type="submit">提交</em-button>
|
|
83
|
+
</div>
|
|
84
|
+
</em-form>
|
|
85
|
+
</template>
|
|
86
|
+
|
|
87
|
+
<script lang="ts" setup>
|
|
88
|
+
import { ref } from "vue";
|
|
89
|
+
|
|
90
|
+
const form = ref({
|
|
91
|
+
username: "", // 用户名
|
|
92
|
+
password: "", // 用户密码
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
const onSubmit = () => {
|
|
96
|
+
console.log("onSubmit", form.value);
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
const onFailed = (errors) => {
|
|
100
|
+
console.log("onFailed", errors);
|
|
101
|
+
};
|
|
102
|
+
</script>
|
|
103
|
+
|
|
104
|
+
<style lang="scss" scoped>
|
|
105
|
+
@import "./css/form.scss";
|
|
106
|
+
</style>
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## 📋 列表页面示例
|
|
110
|
+
|
|
111
|
+
### Vue2 列表
|
|
112
|
+
|
|
113
|
+
```vue
|
|
114
|
+
<template>
|
|
115
|
+
<view class="container">
|
|
116
|
+
<em-minirefresh
|
|
117
|
+
ref="scrollPull"
|
|
118
|
+
:initPageIndex="initPageIndex"
|
|
119
|
+
:page-size="pageSize"
|
|
120
|
+
:url="url"
|
|
121
|
+
:request-data="dataRequest"
|
|
122
|
+
:change-data="dataChange"
|
|
123
|
+
>
|
|
124
|
+
<template v-slot:default="{ listData }">
|
|
125
|
+
<em-cell
|
|
126
|
+
v-for="(item, index) in listData"
|
|
127
|
+
:key="index"
|
|
128
|
+
:title="item.title"
|
|
129
|
+
@click="itemClick(item)"
|
|
130
|
+
/>
|
|
131
|
+
</template>
|
|
132
|
+
</em-minirefresh>
|
|
133
|
+
</view>
|
|
134
|
+
</template>
|
|
135
|
+
|
|
136
|
+
<script>
|
|
137
|
+
export default {
|
|
138
|
+
data() {
|
|
139
|
+
return {
|
|
140
|
+
url: `${Config.serverUrl}/rest/list`, // 请求地址
|
|
141
|
+
initPageIndex: 0, // 初始页码
|
|
142
|
+
pageSize: 10, // 每页条数
|
|
143
|
+
};
|
|
144
|
+
},
|
|
145
|
+
methods: {
|
|
146
|
+
// 请求参数函数
|
|
147
|
+
dataRequest(currPage, pageSize) {
|
|
148
|
+
return {
|
|
149
|
+
params: JSON.stringify({
|
|
150
|
+
currentpageindex: currPage,
|
|
151
|
+
pagesize: pageSize,
|
|
152
|
+
}),
|
|
153
|
+
};
|
|
154
|
+
},
|
|
155
|
+
// 数据处理函数
|
|
156
|
+
dataChange(res) {
|
|
157
|
+
if (res?.status?.code === 1) {
|
|
158
|
+
return res.custom.infolist;
|
|
159
|
+
}
|
|
160
|
+
console.error("接口返回参数错误");
|
|
161
|
+
return [];
|
|
162
|
+
},
|
|
163
|
+
// 列表项点击
|
|
164
|
+
itemClick(item) {
|
|
165
|
+
ejs.page.open({
|
|
166
|
+
pageUrl: "./detail",
|
|
167
|
+
data: { id: item.id },
|
|
168
|
+
});
|
|
169
|
+
},
|
|
170
|
+
},
|
|
171
|
+
};
|
|
172
|
+
</script>
|
|
173
|
+
|
|
174
|
+
<style lang="scss" scoped>
|
|
175
|
+
@import "./css/list.scss";
|
|
176
|
+
</style>
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
### Vue3 列表
|
|
180
|
+
|
|
181
|
+
```vue
|
|
182
|
+
<template>
|
|
183
|
+
<view class="container">
|
|
184
|
+
<em-minirefresh
|
|
185
|
+
ref="scrollPull"
|
|
186
|
+
:page-size="pageSize"
|
|
187
|
+
:url="url"
|
|
188
|
+
:request-data="dataRequest"
|
|
189
|
+
:change-data="dataChange"
|
|
190
|
+
@update:listData="(val) => (listData = val)"
|
|
191
|
+
>
|
|
192
|
+
<em-cell
|
|
193
|
+
v-for="(item, index) in listData"
|
|
194
|
+
:key="index"
|
|
195
|
+
:title="item.title"
|
|
196
|
+
@click="itemClick(item)"
|
|
197
|
+
/>
|
|
198
|
+
</em-minirefresh>
|
|
199
|
+
</view>
|
|
200
|
+
</template>
|
|
201
|
+
|
|
202
|
+
<script lang="ts" setup>
|
|
203
|
+
import { ref } from "vue";
|
|
204
|
+
|
|
205
|
+
const url = `${Config.serverUrl}/rest/list`; // 请求地址
|
|
206
|
+
const pageSize = ref(15); // 每页条数
|
|
207
|
+
const listData = ref([]); // 列表数据
|
|
208
|
+
|
|
209
|
+
// 请求参数函数
|
|
210
|
+
const dataRequest = (currPage, pageSize) => {
|
|
211
|
+
return {
|
|
212
|
+
params: JSON.stringify({
|
|
213
|
+
currentpageindex: currPage,
|
|
214
|
+
pagesize: pageSize,
|
|
215
|
+
}),
|
|
216
|
+
};
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
// 数据处理函数
|
|
220
|
+
const dataChange = (res) => {
|
|
221
|
+
if (res.status.code === 1) {
|
|
222
|
+
return res.custom.infolist;
|
|
223
|
+
}
|
|
224
|
+
console.error("接口返回参数错误");
|
|
225
|
+
return [];
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
// 列表项点击
|
|
229
|
+
const itemClick = (item) => {
|
|
230
|
+
ejs.page.open({
|
|
231
|
+
pageUrl: "./detail",
|
|
232
|
+
data: { id: item.id },
|
|
233
|
+
});
|
|
234
|
+
};
|
|
235
|
+
</script>
|
|
236
|
+
|
|
237
|
+
<style lang="scss" scoped>
|
|
238
|
+
@import "./css/list.scss";
|
|
239
|
+
</style>
|
|
240
|
+
```
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# 性能优化规范
|
|
2
|
+
|
|
3
|
+
> 定义 Vue 应用的性能优化策略与最佳实践。
|
|
4
|
+
|
|
5
|
+
## 🎯 核心性能指标
|
|
6
|
+
|
|
7
|
+
| 指标 | 说明 | 目标 |
|
|
8
|
+
| ------- | ------------ | ------- |
|
|
9
|
+
| **LCP** | 最大内容绘制 | < 2.5s |
|
|
10
|
+
| **FID** | 首次输入延迟 | < 100ms |
|
|
11
|
+
| **CLS** | 累计布局偏移 | < 0.1 |
|
|
12
|
+
|
|
13
|
+
**检测工具**:Lighthouse、WebPageTest
|
|
14
|
+
|
|
15
|
+
## ⚡ 渲染优化
|
|
16
|
+
|
|
17
|
+
### v-show vs v-if
|
|
18
|
+
|
|
19
|
+
| 指令 | 特点 | 适用场景 |
|
|
20
|
+
| -------- | ------------- | ------------ |
|
|
21
|
+
| `v-show` | CSS 切换显示 | 频繁切换 |
|
|
22
|
+
| `v-if` | 完全销毁/创建 | 条件很少改变 |
|
|
23
|
+
|
|
24
|
+
### 合理使用 computed
|
|
25
|
+
|
|
26
|
+
```javascript
|
|
27
|
+
// ✅ 使用 computed:有缓存
|
|
28
|
+
const filteredList = computed(() => list.value.filter((item) => item.active));
|
|
29
|
+
|
|
30
|
+
// ❌ 避免 methods:每次渲染都重新计算
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### keep-alive 缓存
|
|
34
|
+
|
|
35
|
+
```vue
|
|
36
|
+
<keep-alive include="UserList,OrderList">
|
|
37
|
+
<router-view />
|
|
38
|
+
</keep-alive>
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## 📦 代码分割
|
|
42
|
+
|
|
43
|
+
### Vue3 异步组件
|
|
44
|
+
|
|
45
|
+
```typescript
|
|
46
|
+
import { defineAsyncComponent } from "vue";
|
|
47
|
+
const AsyncComponent = defineAsyncComponent(
|
|
48
|
+
() => import("./HeavyComponent.vue")
|
|
49
|
+
);
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Suspense 包裹
|
|
53
|
+
|
|
54
|
+
```vue
|
|
55
|
+
<Suspense>
|
|
56
|
+
<template #default><AsyncComponent /></template>
|
|
57
|
+
<template #fallback><div>加载中...</div></template>
|
|
58
|
+
</Suspense>
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## 🖼️ 图片优化
|
|
62
|
+
|
|
63
|
+
- 使用 WebP 格式
|
|
64
|
+
- 设置尺寸属性(避免 CLS)
|
|
65
|
+
- 启用懒加载 `lazy-load`
|
|
66
|
+
- 压缩图片 < 100KB
|
|
67
|
+
|
|
68
|
+
## ✅ 性能检查清单
|
|
69
|
+
|
|
70
|
+
- [ ] 频繁切换使用 `v-show`
|
|
71
|
+
- [ ] 使用 `computed` 替代方法调用
|
|
72
|
+
- [ ] 适当使用 `keep-alive` 缓存
|
|
73
|
+
- [ ] 非关键组件动态加载
|
|
74
|
+
- [ ] 图片使用 WebP 格式并懒加载
|